Document Version: 1.0
Last Updated: January 2026
Status: Draft
Related Audits: API Layer Audit, Data Layer Audit
This document formally specifies the requirements for data shape handling across Nightscout's REST API, WebSocket, and storage layers. It serves as a contract for:
- Client developers - Knowing what input shapes are supported
- Maintainers - Preserving backward compatibility during refactoring
- Testers - Validating behavior against formal requirements
- Migration planners - Understanding what must be preserved during MongoDB driver upgrades
| Term | Definition |
|---|---|
| Single Object | A JSON object representing one document: { "field": "value" } |
| Array Input | A JSON array containing one or more objects: [{ "field": "value" }] |
| Batch | An array containing multiple objects for bulk insertion |
| Shape | The structural format of input or output data (single vs array) |
| Normalization | Converting diverse input shapes to a consistent internal format |
The API v1 POST endpoints MUST accept both single objects and arrays as valid input for the following collections:
| Collection | Single Object | Array | Requirement ID |
|---|---|---|---|
/api/v1/treatments |
MUST accept | MUST accept | REQ-API-001a |
/api/v1/entries |
MUST accept | MUST accept | REQ-API-001b |
/api/v1/devicestatus |
MUST accept | MUST accept | REQ-API-001c |
/api/v1/profile |
MUST accept | Single only (typical) | REQ-API-001d |
/api/v1/food |
MUST accept | Single only (typical) | REQ-API-001e |
/api/v1/activity |
NOT supported | MUST accept (array required) | REQ-API-001f |
Rationale: AAPS, Loop, and xDrip clients may send either format for treatments/entries/devicestatus based on whether they're uploading a single reading or batch-syncing historical data. Profile and food are typically single-document operations; activity requires array input by design.
All successful POST responses MUST return an array, regardless of input shape:
Input: { "sgv": 120 }
Output: [{ "_id": "...", "sgv": 120 }]
Input: [{ "sgv": 120 }, { "sgv": 125 }]
Output: [{ "_id": "...", "sgv": 120 }, { "_id": "...", "sgv": 125 }]
Rationale: Consistent response shapes simplify client-side parsing logic.
| Input | Expected Behavior | Requirement ID |
|---|---|---|
Empty object {} |
Return empty array [] (no database write) |
REQ-API-003a |
Empty array [] |
Return empty array [] (no database write) |
REQ-API-003b |
The API MUST support batch inserts of at least 100 documents in a single request without data loss or timeout.
Test Reference: tests/api.shape-handling.test.js - "handles large batch array"
The WebSocket dbAdd message handler MUST accept both single objects and arrays:
| Collection | Single Object | Array | Requirement ID |
|---|---|---|---|
treatments |
MUST accept | MUST accept | REQ-WS-001a |
devicestatus |
MUST accept | MUST accept | REQ-WS-001b |
entries |
MUST accept | MUST accept | REQ-WS-001c |
Implementation Note: Array inputs are processed sequentially to ensure each document gets a unique _id and triggers appropriate events.
The callback response for dbAdd MUST return an array of created documents:
socket.emit('dbAdd', { collection: 'treatments', data: singleObj }, (result) => {
// result is always an array: [{ _id, ...singleObj }]
});Each successful document insertion via dbAdd MUST:
- Emit a
data-updateevent on the internal bus - Emit a
data-receivedevent after all insertions complete
The implementation MUST NOT pass arrays directly to insertOne(). Arrays MUST be iterated and each item inserted individually.
Rationale: MongoDB's insertOne([a, b]) creates a single document {0: a, 1: b}, not multiple documents. This was identified as a bug during MongoDB 5.x migration testing.
| Collection | Single Object | Array | Normalization | Requirement ID |
|---|---|---|---|---|
treatments |
MUST accept | MUST accept | Wrap single in array | REQ-STORAGE-001a |
devicestatus |
MUST accept | MUST accept | Wrap single in array | REQ-STORAGE-001b |
entries |
MUST accept | MUST accept | Auto-detected | REQ-STORAGE-001c |
profile |
MUST accept | N/A | Single only | REQ-STORAGE-001d |
food |
MUST accept | N/A | Single only | REQ-STORAGE-001e |
activity |
NOT supported | MUST accept | Array only | REQ-STORAGE-001f |
The storage layer MUST add a created_at timestamp to documents that lack one:
if (!doc.created_at) {
doc.created_at = new Date().toISOString();
}When processing arrays, the storage layer MUST:
- Process documents sequentially (not in parallel)
- Stop on first error OR complete all insertions
- Return all successfully created documents
Rationale: Sequential processing prevents race conditions with closure variables in async callbacks.
| Scenario | Expected Behavior |
|---|---|
| Single object fails | Return error, no documents created |
| One item in array fails | Implementation-specific (see notes) |
| All items fail | Return error, no documents created |
Note: Current implementation varies by endpoint. REST API may return partial success; WebSocket continues processing remaining items. This should be standardized in future versions.
Error responses SHOULD include:
- HTTP status code (for REST API)
- Error message describing the failure
- Optional: identifier of failed document (for batch operations)
Batch inserts SHOULD use efficient database operations:
- For arrays ≤10 items: Sequential
insertOne()acceptable - For arrays >10 items: Consider
insertMany()or bulk operations
Current Status: All implementations use sequential insertion. Optimization opportunity identified.
The data-received event triggering data updates is throttled to 15 seconds (UPDATE_THROTTLE in bootevent.js). This is intentional to reduce database load.
| Requirement | Test File | Test Case | Status |
|---|---|---|---|
| REQ-API-001a | api.shape-handling.test.js |
"treatments POST accepts single/array" | Covered |
| REQ-API-001b | api.shape-handling.test.js |
Entries tests (via API tests) | Covered |
| REQ-API-001c | api.shape-handling.test.js |
"devicestatus POST accepts single/array" | Covered |
| REQ-API-001d | storage.shape-handling.test.js |
"profile create() accepts single" | Covered |
| REQ-API-001e | storage.shape-handling.test.js |
"food create() accepts single" | Covered |
| REQ-API-001f | storage.shape-handling.test.js |
"activity create() array only" | Covered |
| REQ-API-002 | api.shape-handling.test.js |
"single object input returns array response" | Covered |
| REQ-API-003 | api.shape-handling.test.js |
"POST with empty object/array" | Covered |
| REQ-API-004 | api.shape-handling.test.js |
"handles large batch array" | Covered |
| REQ-WS-001 | websocket.shape-handling.test.js |
"dbAdd accepts single/array" | Covered |
| REQ-WS-002 | websocket.shape-handling.test.js |
Callback response validation | Implicit |
| REQ-WS-003 | websocket.shape-handling.test.js |
Event emission on dbAdd | Implicit |
| REQ-WS-004 | websocket.shape-handling.test.js |
"dbAdd with array input" | Covered |
| REQ-STORAGE-001 | storage.shape-handling.test.js |
"create() accepts single/array" | Covered |
| REQ-STORAGE-002 | storage.shape-handling.test.js |
Timestamp added (implicit) | Partial |
| REQ-STORAGE-003 | storage.shape-handling.test.js |
"handles large batch" | Covered |
| REQ-ERR-001 | N/A | Partial failure semantics | Future |
| REQ-ERR-002 | N/A | Error response format | Future |
| REQ-PERF-001 | N/A | Batch efficiency | Aspirational |
| REQ-PERF-002 | N/A | Throttling (design doc) | Documented |
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0 | January 2026 | Nightscout Team | Initial specification based on MongoDB 5.x migration testing |
- API Layer Audit - Endpoint inventory and response formats
- Data Layer Audit - MongoDB collection schemas
- Shape Handling Tests - Detailed test cases
- API v1 Compatibility Requirements - Client compatibility requirements