@kedem/okdb 1.1.0 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/okdb.js +1 -1
- package/docs/views.md +190 -28
- package/okdb.js +1 -1
- package/package.json +1 -1
- package/types/features/views.d.ts +93 -0
package/docs/views.md
CHANGED
|
@@ -166,6 +166,129 @@ stats.orders.revenue.value; // → total revenue
|
|
|
166
166
|
|
|
167
167
|
---
|
|
168
168
|
|
|
169
|
+
## Bucketing
|
|
170
|
+
|
|
171
|
+
Bucketed views partition their aggregations by a time period (or a custom grouping field), producing a per-bucket series of incremental results instead of a single global document.
|
|
172
|
+
|
|
173
|
+
### Bucket config
|
|
174
|
+
|
|
175
|
+
Add a `bucket` object to the view definition:
|
|
176
|
+
|
|
177
|
+
```js
|
|
178
|
+
{
|
|
179
|
+
type: string, // source type (required)
|
|
180
|
+
filter: object, // optional
|
|
181
|
+
reduce: object, // required
|
|
182
|
+
bucket: {
|
|
183
|
+
field: string, // required — dot path to the timestamp or grouping field
|
|
184
|
+
preset: 'time', // use preset OR project, not both
|
|
185
|
+
granularity: string, // required — bucket size (see below)
|
|
186
|
+
},
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### `preset: 'time'` granularities
|
|
191
|
+
|
|
192
|
+
| `granularity` | Bucket size |
|
|
193
|
+
| ------------- | -------------------- |
|
|
194
|
+
| `minute` | 1 minute |
|
|
195
|
+
| `hour` | 1 hour |
|
|
196
|
+
| `day` | 1 calendar day (UTC) |
|
|
197
|
+
| `week` | ISO week |
|
|
198
|
+
| `month` | Calendar month |
|
|
199
|
+
| `quarter` | Calendar quarter |
|
|
200
|
+
| `year` | Calendar year |
|
|
201
|
+
|
|
202
|
+
The `field` value is interpreted as a Unix timestamp in milliseconds or an ISO date string.
|
|
203
|
+
|
|
204
|
+
#### Custom projection (JavaScript only)
|
|
205
|
+
|
|
206
|
+
Supply a `project` function instead of `preset` to map each field value to an arbitrary string bucket key:
|
|
207
|
+
|
|
208
|
+
```js
|
|
209
|
+
bucket: {
|
|
210
|
+
field: 'category',
|
|
211
|
+
granularity: 'category',
|
|
212
|
+
project(value) {
|
|
213
|
+
return String(value ?? 'unknown').toLowerCase();
|
|
214
|
+
},
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Custom projections are not JSON-serializable and cannot be used in synced environments (`BUCKET_PROJECT_NOT_SYNCABLE`).
|
|
219
|
+
|
|
220
|
+
### Create example
|
|
221
|
+
|
|
222
|
+
```js
|
|
223
|
+
await env.views.create('salesByDay', {
|
|
224
|
+
type: 'orders',
|
|
225
|
+
filter: { status: 'completed' },
|
|
226
|
+
reduce: {
|
|
227
|
+
revenue: { $sum: 'amount' },
|
|
228
|
+
count: { $count: true },
|
|
229
|
+
},
|
|
230
|
+
bucket: {
|
|
231
|
+
field: 'completedAt', // Unix ms or ISO date string on each source doc
|
|
232
|
+
preset: 'time',
|
|
233
|
+
granularity: 'day',
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### `range(name, options?)` — per-bucket aggregates
|
|
239
|
+
|
|
240
|
+
Returns a sparse ordered array of bucket entries. Only buckets with at least one document appear.
|
|
241
|
+
|
|
242
|
+
```js
|
|
243
|
+
const rows = env.views.range('salesByDay', {
|
|
244
|
+
from: '2024-01-01', // ISO date / epoch ms / Date — optional lower bound
|
|
245
|
+
to: '2024-03-31', // inclusive upper bound — optional
|
|
246
|
+
granularity: 'day', // must match the view's granularity, or omit
|
|
247
|
+
includePartial: false, // true = include the bucket containing Date.now()
|
|
248
|
+
});
|
|
249
|
+
// → [
|
|
250
|
+
// { bucketKey: '2024-01-01', granularity: 'day', reducers: { revenue: { value: 1200 }, count: { value: 4 } }, refs: {} },
|
|
251
|
+
// { bucketKey: '2024-01-02', granularity: 'day', reducers: { revenue: { value: 890 }, count: { value: 3 } }, refs: {} },
|
|
252
|
+
// ]
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
Each entry:
|
|
256
|
+
|
|
257
|
+
- `bucketKey` — canonical string key for the bucket (ISO date string for `preset:'time'`)
|
|
258
|
+
- `granularity` — the view's configured granularity
|
|
259
|
+
- `reducers` — `{ [outputField]: { value: N } }` for scalar reducers; `{ [outputField]: { [groupKey]: { value: N } } }` for `$countBy`
|
|
260
|
+
- `refs` — `{ [refName]: { [subField]: { value: N } } }` for `$ref` sub-views
|
|
261
|
+
|
|
262
|
+
### `listBuckets(name, options)` — enumerate populated buckets
|
|
263
|
+
|
|
264
|
+
Returns an ordered list of bucket keys that have at least one document, with per-bucket counts.
|
|
265
|
+
|
|
266
|
+
```js
|
|
267
|
+
const buckets = env.views.listBuckets('salesByDay', {
|
|
268
|
+
granularity: 'day', // required — must equal the view's configured granularity
|
|
269
|
+
from: '2024-01-01', // optional
|
|
270
|
+
to: '2024-12-31', // optional
|
|
271
|
+
limit: 10, // optional — cap result count
|
|
272
|
+
reverse: true, // optional — true = latest-first
|
|
273
|
+
});
|
|
274
|
+
// → [
|
|
275
|
+
// { bucketKey: '2024-12-31', count: 5 },
|
|
276
|
+
// { bucketKey: '2024-12-30', count: 12 },
|
|
277
|
+
// ]
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### Items scoped to a bucket
|
|
281
|
+
|
|
282
|
+
When a reducer has `items: true`, pass a `bucket` option to `.items()` to restrict results to a single bucket:
|
|
283
|
+
|
|
284
|
+
```js
|
|
285
|
+
const view = env.views.get('salesByDay');
|
|
286
|
+
const items = view.count.items({ bucket: { granularity: 'day', key: '2024-01-15' } });
|
|
287
|
+
// → Order[] (only orders in the 2024-01-15 bucket)
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
169
292
|
## Lifecycle
|
|
170
293
|
|
|
171
294
|
### States
|
|
@@ -255,6 +378,10 @@ env.views.getMeta(name) // → { state, clock, error, refs
|
|
|
255
378
|
await env.views.getDefinition(name) // → stored definition | null
|
|
256
379
|
await env.views.list() // → string[]
|
|
257
380
|
|
|
381
|
+
// Bucketing (bucketed views only)
|
|
382
|
+
env.views.range(name, options?) // → BucketEntry[] | null (synchronous)
|
|
383
|
+
env.views.listBuckets(name, options) // → { bucketKey, count }[] (synchronous)
|
|
384
|
+
|
|
258
385
|
// Lifecycle
|
|
259
386
|
await env.views.rebuild(name)
|
|
260
387
|
await env.views.stop(name)
|
|
@@ -271,19 +398,22 @@ env.views.registerReducer(name, { apply })
|
|
|
271
398
|
|
|
272
399
|
Views are scoped per environment. The `{env}` segment is the environment name (e.g. `default`).
|
|
273
400
|
|
|
274
|
-
| Method | Path | Description
|
|
275
|
-
| -------- | ---------------------------------------- |
|
|
276
|
-
| `GET` | `/api/env/{env}/views` | List view names
|
|
277
|
-
| `POST` | `/api/env/{env}/views` | Create a view
|
|
278
|
-
| `GET` | `/api/env/{env}/views/{name}` | Get current view output
|
|
279
|
-
| `GET` | `/api/env/{env}/views/{name}/meta` | Get view state/meta
|
|
280
|
-
| `GET` | `/api/env/{env}/views/{name}/definition` | Get stored definition
|
|
281
|
-
| `DELETE` | `/api/env/{env}/views/{name}` | Remove a view
|
|
282
|
-
| `POST` | `/api/env/{env}/views/{name}/rebuild` | Rebuild from scratch
|
|
283
|
-
| `POST` | `/api/env/{env}/views/{name}/stop` | Stop processing
|
|
284
|
-
| `POST` | `/api/env/{env}/views/{name}/start` | Start / resume
|
|
285
|
-
|
|
286
|
-
|
|
401
|
+
| Method | Path | Description |
|
|
402
|
+
| -------- | ---------------------------------------- | ------------------------------------------------ |
|
|
403
|
+
| `GET` | `/api/env/{env}/views` | List view names |
|
|
404
|
+
| `POST` | `/api/env/{env}/views` | Create a view |
|
|
405
|
+
| `GET` | `/api/env/{env}/views/{name}` | Get current view output |
|
|
406
|
+
| `GET` | `/api/env/{env}/views/{name}/meta` | Get view state/meta |
|
|
407
|
+
| `GET` | `/api/env/{env}/views/{name}/definition` | Get stored definition |
|
|
408
|
+
| `DELETE` | `/api/env/{env}/views/{name}` | Remove a view |
|
|
409
|
+
| `POST` | `/api/env/{env}/views/{name}/rebuild` | Rebuild from scratch |
|
|
410
|
+
| `POST` | `/api/env/{env}/views/{name}/stop` | Stop processing |
|
|
411
|
+
| `POST` | `/api/env/{env}/views/{name}/start` | Start / resume |
|
|
412
|
+
| `GET` | `/api/env/{env}/views/{name}/range` | Per-bucket aggregates (bucketed views only) |
|
|
413
|
+
| `GET` | `/api/env/{env}/views/{name}/buckets` | List populated bucket keys (bucketed views only) |
|
|
414
|
+
| `POST` | `/api/env/{env}/views/{name}/items` | Paginated source items for a view or sub-scope |
|
|
415
|
+
|
|
416
|
+
**Create body (standard view):**
|
|
287
417
|
|
|
288
418
|
```json
|
|
289
419
|
{
|
|
@@ -297,6 +427,25 @@ Views are scoped per environment. The `{env}` segment is the environment name (e
|
|
|
297
427
|
}
|
|
298
428
|
```
|
|
299
429
|
|
|
430
|
+
**Create body (bucketed view):**
|
|
431
|
+
|
|
432
|
+
```json
|
|
433
|
+
{
|
|
434
|
+
"name": "salesByDay",
|
|
435
|
+
"type": "orders",
|
|
436
|
+
"filter": { "status": "completed" },
|
|
437
|
+
"reduce": {
|
|
438
|
+
"revenue": { "$sum": "amount" },
|
|
439
|
+
"count": { "$count": true }
|
|
440
|
+
},
|
|
441
|
+
"bucket": {
|
|
442
|
+
"field": "completedAt",
|
|
443
|
+
"preset": "time",
|
|
444
|
+
"granularity": "day"
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
```
|
|
448
|
+
|
|
300
449
|
**Delete with managed indexes:**
|
|
301
450
|
|
|
302
451
|
```json
|
|
@@ -307,7 +456,7 @@ Views are scoped per environment. The `{env}` segment is the environment name (e
|
|
|
307
456
|
|
|
308
457
|
## MCP
|
|
309
458
|
|
|
310
|
-
Views are available as the `views` capability. Actions: `list_views`, `create_view`, `get_view`, `get_view_meta`, `get_view_definition`, `remove_view`, `rebuild_view`, `stop_view`, `start_view`.
|
|
459
|
+
Views are available as the `views` capability. Actions: `list_views`, `create_view`, `get_view`, `get_view_meta`, `get_view_definition`, `remove_view`, `rebuild_view`, `stop_view`, `start_view`, `get_view_range`, `list_view_buckets`, `list_view_items`.
|
|
311
460
|
|
|
312
461
|
---
|
|
313
462
|
|
|
@@ -335,17 +484,30 @@ View definitions are stored in the `~views` system type within their environment
|
|
|
335
484
|
|
|
336
485
|
## Error codes
|
|
337
486
|
|
|
338
|
-
| Code
|
|
339
|
-
|
|
|
340
|
-
| `VIEW_INVALID_NAME`
|
|
341
|
-
| `VIEW_INVALID_DEFINITION`
|
|
342
|
-
| `VIEW_MISSING_TYPE`
|
|
343
|
-
| `VIEW_MISSING_REDUCE`
|
|
344
|
-
| `VIEW_TYPE_NOT_REGISTERED`
|
|
345
|
-
| `VIEW_ALREADY_EXISTS`
|
|
346
|
-
| `VIEW_NOT_FOUND`
|
|
347
|
-
| `VIEW_HAS_ORPHANED_INDEXES`
|
|
348
|
-
| `VIEW_REF_UNSUPPORTED_REDUCER`
|
|
349
|
-
| `VIEW_INVALID_REDUCER_NAME`
|
|
350
|
-
| `VIEW_REDUCER_CONFLICT`
|
|
351
|
-
| `VIEW_INVALID_REDUCER`
|
|
487
|
+
| Code | Cause |
|
|
488
|
+
| --------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
489
|
+
| `VIEW_INVALID_NAME` | Name is empty, contains whitespace, or is not a string |
|
|
490
|
+
| `VIEW_INVALID_DEFINITION` | Definition is not an object |
|
|
491
|
+
| `VIEW_MISSING_TYPE` | `type` field missing or not a string |
|
|
492
|
+
| `VIEW_MISSING_REDUCE` | `reduce` field missing or empty |
|
|
493
|
+
| `VIEW_TYPE_NOT_REGISTERED` | The source type does not exist in this environment |
|
|
494
|
+
| `VIEW_ALREADY_EXISTS` | A view with that name already exists |
|
|
495
|
+
| `VIEW_NOT_FOUND` | No view with that name |
|
|
496
|
+
| `VIEW_HAS_ORPHANED_INDEXES` | Removing the view would leave auto-created indexes with no owner; pass `managedIndexes` option |
|
|
497
|
+
| `VIEW_REF_UNSUPPORTED_REDUCER` | `$min`/`$max` used inside a `$ref` sub-view |
|
|
498
|
+
| `VIEW_INVALID_REDUCER_NAME` | Custom reducer name does not start with `$` |
|
|
499
|
+
| `VIEW_REDUCER_CONFLICT` | Custom reducer name conflicts with a built-in |
|
|
500
|
+
| `VIEW_INVALID_REDUCER` | Custom reducer missing an `apply` function |
|
|
501
|
+
| `BUCKET_CONFIG_INVALID` | `bucket` config is missing required fields, uses an unsupported preset, or has an invalid granularity |
|
|
502
|
+
| `BUCKET_PROJECT_NOT_SYNCABLE` | `bucket.project` is a function and cannot be used in a synced environment; use `preset:'time'` |
|
|
503
|
+
| `BUCKET_INVALID_TIME_VALUE` | A document's bucket field could not be parsed as a time value |
|
|
504
|
+
| `VIEW_NOT_BUCKETED` | `range()` or `listBuckets()` was called on a view that has no `bucket` config |
|
|
505
|
+
| `VIEW_RANGE_GRANULARITY_MISMATCH` | The `granularity` option does not match the view's configured granularity |
|
|
506
|
+
| `VIEW_RANGE_GRANULARITY_REQUIRED` | `listBuckets()` was called without a `granularity` option |
|
|
507
|
+
| `VIEW_ITEMS_SCOPE_INVALID` | `scope` is not one of `view`, `reducer`, `ref`, `ref-reducer` |
|
|
508
|
+
| `VIEW_ITEMS_REDUCER_NOT_FOUND` | The named reducer does not exist on the view |
|
|
509
|
+
| `VIEW_ITEMS_REF_NOT_FOUND` | The named `$ref` slot does not exist on the view |
|
|
510
|
+
| `VIEW_ITEMS_GROUP_NOT_FOUND` | The named group key does not exist on the `$countBy` reducer |
|
|
511
|
+
| `VIEW_ITEMS_NOT_AVAILABLE` | `items()` was requested but the reducer does not have `items: true` |
|
|
512
|
+
| `VIEW_ITEMS_LIMIT_INVALID` | `limit` is not a non-negative integer |
|
|
513
|
+
| `VIEW_ITEMS_OFFSET_INVALID` | `offset` is not a non-negative integer |
|