@lunora/do 1.0.0-alpha.3 → 1.0.0-alpha.30
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/LICENSE.md +6 -0
- package/__assets__/package-og.svg +1 -1
- package/dist/index.d.mts +1375 -289
- package/dist/index.d.ts +1375 -289
- package/dist/index.mjs +35 -30
- package/dist/packem_shared/{ADMIN_FUNCTION_PREFIX-Dzdqq5J2.mjs → ADMIN_FUNCTIONS-CAHLZMj8.mjs} +62 -8
- package/dist/packem_shared/{matchesStaticWhere-CFk6adSu.mjs → AGGREGATE_SQL_FUNCTION-CQsu2Xga.mjs} +5 -3
- package/dist/packem_shared/{applyCdcChanges-Ctdmxmrv.mjs → CDC_LOG_TABLE-DjJEHiM2.mjs} +7 -3
- package/dist/packem_shared/{ConflictError-C0STs6bU.mjs → ConflictError-CLoq37xH.mjs} +4 -5
- package/dist/packem_shared/{CountRlsUnsupportedError-28ZvvwKS.mjs → CountRlsUnsupportedError-BGxj0pgS.mjs} +4 -4
- package/dist/packem_shared/{DATA_MIGRATION_STATE_TABLE-PTtTiQ7U.mjs → DATA_MIGRATION_STATE_TABLE-CYwBpyTr.mjs} +11 -3
- package/dist/packem_shared/{assertFlatPredicate-DyVYReuT.mjs → DEFAULT_MAX_RELATION_KEYS-BEan1CRD.mjs} +58 -7
- package/dist/packem_shared/{assertReadonly-dDcFE1YZ.mjs → MAX_SQL_ROWS-D57CJaT9.mjs} +3 -1
- package/dist/packem_shared/NotFoundError-C70b9hLw.mjs +9 -0
- package/dist/packem_shared/{assertValidClientId-CBZ1zC96.mjs → NotUniqueError-BigrdT_W.mjs} +241 -90
- package/dist/packem_shared/{rank-CrkEIpF4.mjs → RANK_TIEBREAK-CXhdcA1o.mjs} +2 -13
- package/dist/packem_shared/{guardWriter-u3UlnCH5.mjs → RLS_UNWRAP_SYMBOL-DTvHvRzY.mjs} +22 -9
- package/dist/packem_shared/{ROOT_DO_SIZE_WARN_BYTES-DQkmGiCS.mjs → ROOT_DO_SIZE_WARN_BYTES-BA8QOChj.mjs} +2878 -326
- package/dist/packem_shared/{ReactiveCache-ByVzgH3d.mjs → ReactiveCache-BYlSGY0N.mjs} +1 -28
- package/dist/packem_shared/{SESSION_DO_TTL_DEFAULT-ilPZsVwu.mjs → SESSION_DO_TTL_DEFAULT-BnSKgVO4.mjs} +16 -27
- package/dist/packem_shared/{SHARD_REGISTRY_DO_NAME-BsAbi5Mn.mjs → SHARD_REGISTRY_DO_NAME-D99roc-r.mjs} +12 -14
- package/dist/packem_shared/{applyOnDelete-CMif2RKw.mjs → applyOnDelete-BXSq3S70.mjs} +23 -12
- package/dist/packem_shared/{buildSeekWhere-lVsNXSLy.mjs → applySelect-WQY8m62C.mjs} +21 -2
- package/dist/packem_shared/{armRestore-BJk53Ro8.mjs → armRestore-4Px61hHS.mjs} +4 -10
- package/dist/packem_shared/{backfillAggregateIndexes-BF5eL7kW.mjs → backfillAggregateIndexes-DDoT-UUI.mjs} +3 -2
- package/dist/packem_shared/{compileWhereSql-CXrhFA3G.mjs → compileWhereSql-DE6yfRcQ.mjs} +5 -3
- package/dist/packem_shared/constant-time-equal-BVRWZgES.mjs +12 -0
- package/dist/packem_shared/{createSystemReader-8CzSZP9V.mjs → createSystemReader-D12eNH13.mjs} +4 -2
- package/dist/packem_shared/ctx-db-idempotency-BdcNpvY4.mjs +108 -0
- package/dist/packem_shared/ctx-db-shapes-Cz9dHyh1.mjs +53 -0
- package/dist/packem_shared/diffExternalSource-Cx9HUPJj.mjs +44 -0
- package/dist/packem_shared/{exportShardRows-DZEhUeyI.mjs → exportShardRows-Dy3oFZ26.mjs} +4 -3
- package/dist/packem_shared/isSourceDue-CYkt7Ru8.mjs +41 -0
- package/dist/packem_shared/json-response-BdbtpOhm.mjs +3 -0
- package/dist/packem_shared/materializeExternalRows-CTqZisSC.mjs +23 -0
- package/dist/packem_shared/{runShardMigrations-C3bn5r93.mjs → runShardMigrations-BGx4v2B6.mjs} +6 -4
- package/dist/packem_shared/serialize-sql-BlRUoiQe.mjs +14 -0
- package/dist/packem_shared/{serveRelationFanout-Clr1a05L.mjs → serveRelationFanout-BgaNg3Hu.mjs} +4 -7
- package/dist/packem_shared/stableStringify-MydiuScU.mjs +40 -0
- package/dist/packem_shared/subscription-delivery-CWigSEr3.mjs +348 -0
- package/dist/packem_shared/subscriptionListDeltas-DRLvFlM1.mjs +1 -0
- package/package.json +5 -3
- package/dist/packem_shared/NotFoundError-CMuMZt81.mjs +0 -10
- package/dist/packem_shared/ctx-db-idempotency-DkC9rP91.mjs +0 -35
- package/dist/packem_shared/encodePartitionKey-C6blLR5K.mjs +0 -1
- /package/dist/packem_shared/{AUTH_METRICS_BUCKET_MS-CiHHYeJi.mjs → AUTH_METRICS_BUCKETS_TABLE-CiHHYeJi.mjs} +0 -0
- /package/dist/packem_shared/{ensureFunctionMetricsTables-UDNVD7FS.mjs → FUNCTION_METRICS_BUCKETS_TABLE-UDNVD7FS.mjs} +0 -0
- /package/dist/packem_shared/{clearCapturedMail-CPpgl-dX.mjs → MAIL_RETENTION-CPpgl-dX.mjs} +0 -0
- /package/dist/packem_shared/{buildSecurityAudit-CCAvoFlr.mjs → MIN_ADMIN_TOKEN_LENGTH-CCAvoFlr.mjs} +0 -0
- /package/dist/packem_shared/{ftsTableName-BLEMawrp.mjs → buildFtsMatch-BLEMawrp.mjs} +0 -0
- /package/dist/packem_shared/{runTriggers-5N6_Fx0A.mjs → hasTrigger-5N6_Fx0A.mjs} +0 -0
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LunoraError } from '@lunora/errors';
|
|
1
2
|
import { SQL } from 'drizzle-orm';
|
|
2
3
|
import { DrizzleSqliteDODatabase } from 'drizzle-orm/durable-sqlite';
|
|
3
4
|
/**
|
|
@@ -110,17 +111,13 @@ interface GroupByEntry {
|
|
|
110
111
|
value: AggregateResult;
|
|
111
112
|
}
|
|
112
113
|
/**
|
|
113
|
-
* Thrown when `count` runs in an RLS-restricted ctx.
|
|
114
|
-
* (`
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
* the operation is invalid in this context, not malformed.
|
|
114
|
+
* Thrown when `count` runs in an RLS-restricted ctx. A `LunoraError` subclass
|
|
115
|
+
* (`code: "COUNT_RLS_UNSUPPORTED"`, `status: 422`) recognised structurally by the
|
|
116
|
+
* runtime's error mapper (via `isLunoraError`), so `@lunora/do` needs no runtime
|
|
117
|
+
* dependency on `@lunora/server`. 422 = the operation is invalid in this context,
|
|
118
|
+
* not malformed.
|
|
119
119
|
*/
|
|
120
|
-
declare class CountRlsUnsupportedError extends
|
|
121
|
-
readonly code: string;
|
|
122
|
-
override readonly name = "LunoraError";
|
|
123
|
-
readonly status: number;
|
|
120
|
+
declare class CountRlsUnsupportedError extends LunoraError {
|
|
124
121
|
constructor(table?: string);
|
|
125
122
|
}
|
|
126
123
|
/**
|
|
@@ -195,10 +192,17 @@ interface RelationDefinitionLike {
|
|
|
195
192
|
readonly references: string;
|
|
196
193
|
readonly table: string;
|
|
197
194
|
}
|
|
198
|
-
/** Per-relation refinements: filter / order / cap / recurse into the children. */
|
|
195
|
+
/** Per-relation refinements: filter / order / cap / project / recurse into the children. */
|
|
199
196
|
interface NestedWith {
|
|
200
197
|
limit?: number;
|
|
201
198
|
orderBy?: OrderByInput[];
|
|
199
|
+
/**
|
|
200
|
+
* Project each loaded child down to these fields (like the top-level
|
|
201
|
+
* `findMany` `select`). Applied AFTER grouping, so the join key stays
|
|
202
|
+
* available to map children to parents; `_id`/`_creationTime` and any deeper
|
|
203
|
+
* `with` relations are always retained.
|
|
204
|
+
*/
|
|
205
|
+
select?: ReadonlyArray<string>;
|
|
202
206
|
where?: WhereInput;
|
|
203
207
|
with?: WithInput;
|
|
204
208
|
}
|
|
@@ -212,8 +216,17 @@ interface WithInput {
|
|
|
212
216
|
_count?: Record<string, true>;
|
|
213
217
|
}
|
|
214
218
|
interface ResolveWithOptions {
|
|
215
|
-
counter: (tableName: string, where?: WhereInput) => Promise<number>;
|
|
216
219
|
fetcher: (tableName: string, args: QueryArgs) => Promise<QueryPage>;
|
|
220
|
+
/**
|
|
221
|
+
* Grouped aggregate: for every FK value in `values`, return the count of
|
|
222
|
+
* child rows in `tableName` whose `whereField` equals that value,
|
|
223
|
+
* optionally AND-ing in `policyWhere` (the child table's RLS read filter).
|
|
224
|
+
* Returns a `Map` keyed by FK value with the per-group count — missing
|
|
225
|
+
* keys (groups with zero children) are not included; callers default to 0.
|
|
226
|
+
* A single `GROUP BY :whereField … WHERE :whereField IN (values)` query
|
|
227
|
+
* replaces the former one-query-per-distinct-value loop.
|
|
228
|
+
*/
|
|
229
|
+
groupedCounter: (tableName: string, whereField: string, values: unknown[], policyWhere?: WhereInput) => Promise<Map<unknown, number>>;
|
|
217
230
|
parents: Record<string, unknown>[];
|
|
218
231
|
/**
|
|
219
232
|
* Per-target-table read filter (RLS) applied to each relation fetch/count and
|
|
@@ -227,7 +240,18 @@ interface ResolveWithOptions {
|
|
|
227
240
|
tableName: string;
|
|
228
241
|
with: WithInput;
|
|
229
242
|
}
|
|
243
|
+
/**
|
|
244
|
+
* Cross-backend fan-out for grouped `_count` on backends whose `groupedCounter`
|
|
245
|
+
* must fall back to scalar calls (e.g. a DO's global-D1 child or the sql-store's
|
|
246
|
+
* cross-shard reverse direction). Issues one `counter(table, where)` per FK
|
|
247
|
+
* value in parallel and collects the results into a Map.
|
|
248
|
+
*
|
|
249
|
+
* Used by both the DO and sql-store `relationGroupedCounter` implementations so
|
|
250
|
+
* the parallel fan-out logic isn't duplicated.
|
|
251
|
+
*/
|
|
252
|
+
declare const fanOutScalarCounts: (counter: (tableName: string, where?: WhereInput) => Promise<number>, tableName: string, whereField: string, values: unknown[], policyWhere: WhereInput | undefined) => Promise<Map<unknown, number>>;
|
|
230
253
|
/** Distinct, non-nullish values of `field` across `rows`, preserving first-seen order. */
|
|
254
|
+
|
|
231
255
|
/**
|
|
232
256
|
* Resolve every requested relation on `parents` (a single already-fetched
|
|
233
257
|
* page), mutating each parent in place: `one` → `Doc | null`, `many` →
|
|
@@ -289,9 +313,9 @@ declare const applyOnDelete: (options: ApplyOnDeleteOptions) => Promise<void>;
|
|
|
289
313
|
* fakes (which never carry a runtime parser) keep working.
|
|
290
314
|
*/
|
|
291
315
|
declare const runRowValidators: (definition: TableDefinitionLike, document: Record<string, unknown>) => void;
|
|
292
|
-
type SortDirection
|
|
316
|
+
type SortDirection = "asc" | "desc";
|
|
293
317
|
/** A single `{ field: "asc" | "desc" }` entry; `orderBy` is an ordered list of these. */
|
|
294
|
-
type OrderByInput = Record<string, SortDirection
|
|
318
|
+
type OrderByInput = Record<string, SortDirection>;
|
|
295
319
|
interface QueryArgs {
|
|
296
320
|
/**
|
|
297
321
|
* Predicate injected by the runtime (e.g. by `@lunora/server`'s RLS
|
|
@@ -304,6 +328,12 @@ interface QueryArgs {
|
|
|
304
328
|
*/
|
|
305
329
|
baseWhere?: WhereInput;
|
|
306
330
|
cursor?: null | string;
|
|
331
|
+
/**
|
|
332
|
+
* Opt a list read OUT of soft-delete scoping: when `true`, rows whose
|
|
333
|
+
* soft-delete column is set are INCLUDED. Has no effect on a table without
|
|
334
|
+
* `.softDelete()`. Default (absent/false) hides soft-deleted rows.
|
|
335
|
+
*/
|
|
336
|
+
includeDeleted?: boolean;
|
|
307
337
|
limit?: number;
|
|
308
338
|
orderBy?: OrderByInput[];
|
|
309
339
|
/**
|
|
@@ -324,6 +354,15 @@ interface QueryArgs {
|
|
|
324
354
|
* reads (`findMany`/`findFirst`) — this flag specifically guards `count`.
|
|
325
355
|
*/
|
|
326
356
|
restrictsCounts?: boolean;
|
|
357
|
+
/**
|
|
358
|
+
* Project each returned row down to these fields. The system fields `_id` and
|
|
359
|
+
* `_creationTime` are always retained (cursors + by-id reuse depend on them),
|
|
360
|
+
* and any relations attached via `with` (their relation keys and `_count`)
|
|
361
|
+
* survive the trim. Applied AFTER the rows are read and relations resolved, so
|
|
362
|
+
* read-dependency tracking and cursor encoding see the full row — only the
|
|
363
|
+
* payload returned to the caller is narrowed. Omit for the full document.
|
|
364
|
+
*/
|
|
365
|
+
select?: ReadonlyArray<string>;
|
|
327
366
|
where?: WhereInput;
|
|
328
367
|
with?: WithInput;
|
|
329
368
|
}
|
|
@@ -341,7 +380,7 @@ interface QueryPage {
|
|
|
341
380
|
splitCursor?: null | string;
|
|
342
381
|
}
|
|
343
382
|
interface OrderKey {
|
|
344
|
-
direction: SortDirection
|
|
383
|
+
direction: SortDirection;
|
|
345
384
|
field: string;
|
|
346
385
|
}
|
|
347
386
|
/**
|
|
@@ -376,6 +415,26 @@ declare const buildSeekWhere: (keys: OrderKey[], cursorValues: unknown[]) => Whe
|
|
|
376
415
|
* the page it terminates. Reactive pagination uses this for a page's fixed end
|
|
377
416
|
* cursor; the shared compiler renders it per dialect.
|
|
378
417
|
*/
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Project `page` rows down to `select` — plus the always-kept system fields and
|
|
421
|
+
* the relation/`_count` keys attached by a `with` load (passed as `withInput`,
|
|
422
|
+
* the same object handed to `findMany`). Returns the page unchanged when
|
|
423
|
+
* `select` is undefined. Pure; callers apply it AFTER relation resolution +
|
|
424
|
+
* cursor encoding so only the returned payload is trimmed (dependency tracking +
|
|
425
|
+
* the cursor still see the full row).
|
|
426
|
+
*/
|
|
427
|
+
declare const applySelect: (page: Record<string, unknown>[], select: ReadonlyArray<string> | undefined, withInput?: Record<string, unknown>) => Record<string, unknown>[];
|
|
428
|
+
/**
|
|
429
|
+
* The read-scope predicate that hides soft-deleted rows — `{ [field]: { isNull:
|
|
430
|
+
* true } }` matching the live rows whose soft-delete column is null/absent — or
|
|
431
|
+
* `undefined` when the table isn't `.softDelete()` or the read opted in via
|
|
432
|
+
* `includeDeleted`. AND-merge it into a list read's `where` (the by-id path
|
|
433
|
+
* never calls this, so `get`/`patch`/`replace`/`restore` still address the row).
|
|
434
|
+
*/
|
|
435
|
+
declare const softDeleteScope: (softDeleteMode: {
|
|
436
|
+
field: string;
|
|
437
|
+
} | undefined, includeDeleted: boolean | undefined) => undefined | WhereInput;
|
|
379
438
|
type RankDirection = "asc" | "desc";
|
|
380
439
|
interface RankSortKeyLike {
|
|
381
440
|
readonly direction: RankDirection;
|
|
@@ -554,6 +613,7 @@ declare const rankTableName: (table: string, indexName: string) => string;
|
|
|
554
613
|
* - `sortValues[i]` === `serializeSqlValue(doc[index.sortBy[i].field])` — the same transform `syncRankIndexEntry` applies to the stored `__sort_k<i>__` column, so the comparison is byte-for-byte (and JSON-safe for the cross-shard wire) regardless of which shard owns the row. `rankBefore` re-applies it idempotently, so a direct caller passing raw values still works.
|
|
555
614
|
* - `rowId` === `doc._id`, the `__id__` tiebreak.
|
|
556
615
|
*/
|
|
616
|
+
declare const stableStringify: (value: unknown) => string;
|
|
557
617
|
/** A single memoized result, the deps it read, and any active subscribers. */
|
|
558
618
|
interface CacheEntry {
|
|
559
619
|
/** Approximate serialized size of `result`, charged against `maxBytes`. */
|
|
@@ -686,16 +746,6 @@ declare class ReactiveCache {
|
|
|
686
746
|
private evict;
|
|
687
747
|
}
|
|
688
748
|
/**
|
|
689
|
-
* Stable, sorted JSON encoding of `args` for use in a cache key. Object keys
|
|
690
|
-
* are visited in lexical order at every depth so `{ a: 1, b: 2 }` and
|
|
691
|
-
* `{ b: 2, a: 1 }` hash to the same string. Arrays preserve their order
|
|
692
|
-
* (the index IS the key). `undefined` values are skipped at the object level
|
|
693
|
-
* so `{ a: undefined }` collides with `{}` — matches Convex behavior and
|
|
694
|
-
* avoids spurious cache misses on optional args. Inside arrays `undefined`
|
|
695
|
-
* encodes as `null` to keep positional semantics.
|
|
696
|
-
*/
|
|
697
|
-
declare const stableStringify: (value: unknown) => string;
|
|
698
|
-
/**
|
|
699
749
|
* Compose a cache key from a function path, a stably-encoded args object, and
|
|
700
750
|
* the caller's identity discriminator. Exported so the wiring layer and tests
|
|
701
751
|
* build identical keys without each side reinventing the format.
|
|
@@ -706,39 +756,17 @@ declare const stableStringify: (value: unknown) => string;
|
|
|
706
756
|
* RLS-filtered list, or `getMyProfile()` with no args) would otherwise memoize
|
|
707
757
|
* the first caller's result under an identity-independent key and serve it to
|
|
708
758
|
* everyone. Anonymous/subscription callers pass `null` (their own bucket).
|
|
709
|
-
*/
|
|
710
|
-
declare const reactiveCacheKey: (functionPath: string, args: Record<string, unknown>, identity: null | string) => string;
|
|
711
|
-
/**
|
|
712
|
-
* `ctx.db.system` — a best-effort, read-only reader over Lunora's *system*
|
|
713
|
-
* tables.
|
|
714
|
-
*
|
|
715
|
-
* Convex surfaces a handful of read-only system tables (`_scheduled_functions`,
|
|
716
|
-
* `_storage`, ...) through `ctx.db.system`. Lunora mirrors that surface, but with
|
|
717
|
-
* one load-bearing caveat: **the data these tables expose does not live in the
|
|
718
|
-
* shard's SQLite**. Scheduled functions live in the `SchedulerDO`; storage
|
|
719
|
-
* objects live in R2. So unlike `ctx.db.<table>` — which reads the same
|
|
720
|
-
* transactional SQLite snapshot the mutation writes into — `ctx.db.system`
|
|
721
|
-
* reaches across a network/DO boundary on every call.
|
|
722
759
|
*
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
*
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
*
|
|
731
|
-
* recorded. Treat the result as a point-in-time best effort.
|
|
732
|
-
*
|
|
733
|
-
* Read-only: there is no `insert`/`patch`/`delete`. Mutate scheduled jobs via
|
|
734
|
-
* `ctx.scheduler`, storage objects via `ctx.storage`.
|
|
735
|
-
*
|
|
736
|
-
* The reader is intentionally minimal: `query(table).collect()` returns the full
|
|
737
|
-
* list (no indexes, no filtering, no pagination) and `get(table, id)` resolves a
|
|
738
|
-
* single row. That matches Convex's system-table read surface closely enough for
|
|
739
|
-
* the studio / introspection use cases without re-implementing the query
|
|
740
|
-
* planner against a remote source.
|
|
760
|
+
* The discriminator is an opaque `null | string`, NOT just a userId: the wiring
|
|
761
|
+
* layer (`ShardDO#runCachedQuery`) folds the FULL resolved identity — userId
|
|
762
|
+
* plus the `getIdentity()` claims (active-org / role / tenant) — into a single
|
|
763
|
+
* `stableStringify`'d string before it reaches here. That matters because RLS
|
|
764
|
+
* can key on a claim OTHER than userId: a multi-tenant caller whose userId is
|
|
765
|
+
* stable but whose active-org claim varies request-to-request must NOT share a
|
|
766
|
+
* cache entry across those requests. Encoding the whole identity, not the
|
|
767
|
+
* userId alone, is what keeps those contexts isolated.
|
|
741
768
|
*/
|
|
769
|
+
declare const reactiveCacheKey: (functionPath: string, args: Record<string, unknown>, identity: null | string) => string;
|
|
742
770
|
/** The system tables `ctx.db.system` can read. */
|
|
743
771
|
type SystemTableName = "_scheduled_functions" | "_storage";
|
|
744
772
|
/**
|
|
@@ -858,13 +886,26 @@ type TriggerTimingLike = "after" | "before";
|
|
|
858
886
|
/** The CRUD operation a trigger reacts to. `patch` and `replace` both map to `update`. */
|
|
859
887
|
type TriggerOpLike = "delete" | "insert" | "update";
|
|
860
888
|
/**
|
|
889
|
+
* A schedulable durable-workflow reference — the generated `workflows.<name>` /
|
|
890
|
+
* `agents.<name>` object (carries its `WORKFLOW_*`/`AGENT_*` binding + stable
|
|
891
|
+
* name). Structural mirror so a scheduled target can be a workflow/agent, not
|
|
892
|
+
* just a function path, without this package depending on `@lunora/scheduler`.
|
|
893
|
+
*/
|
|
894
|
+
interface SchedulableWorkflowReferenceLike {
|
|
895
|
+
readonly binding?: string;
|
|
896
|
+
readonly isLunoraWorkflow: true;
|
|
897
|
+
readonly name?: string;
|
|
898
|
+
}
|
|
899
|
+
/**
|
|
861
900
|
* Structural mirror of `@lunora/server`'s `Scheduler` (kept local so this
|
|
862
901
|
* package takes no runtime dependency on the server package — same reasoning
|
|
863
|
-
* as `RelationDefinitionLike`).
|
|
902
|
+
* as `RelationDefinitionLike`). `target` is a function path (`"ns:fn"`) or a
|
|
903
|
+
* generated `workflows.<name>` / `agents.<name>` reference (starts a fresh
|
|
904
|
+
* durable instance on fire).
|
|
864
905
|
*/
|
|
865
906
|
interface SchedulerLike {
|
|
866
|
-
runAfter: (delayMs: number,
|
|
867
|
-
runAt: (timestampMs: number,
|
|
907
|
+
runAfter: (delayMs: number, target: SchedulableWorkflowReferenceLike | string, args?: Record<string, unknown>) => Promise<string>;
|
|
908
|
+
runAt: (timestampMs: number, target: SchedulableWorkflowReferenceLike | string, args?: Record<string, unknown>) => Promise<string>;
|
|
868
909
|
}
|
|
869
910
|
/** What a trigger handler observes about the write that fired it. */
|
|
870
911
|
interface TriggerEventLike {
|
|
@@ -951,7 +992,32 @@ interface SubscriptionQuery {
|
|
|
951
992
|
*/
|
|
952
993
|
table?: string;
|
|
953
994
|
}
|
|
995
|
+
/**
|
|
996
|
+
* A live shape subscription registered on a socket — the partial-replication
|
|
997
|
+
* parallel to {@link SubscriptionQuery}. The client names a `defineShape` shape
|
|
998
|
+
* and supplies validated `args`; the DO resolves it to a table + RLS-composed
|
|
999
|
+
* `effectiveWhere` under the socket's verified identity (never the client's
|
|
1000
|
+
* word) and pokes the membership diff. `sinceSeq`/`sinceEpoch` carry the
|
|
1001
|
+
* client's last applied checkpoint for resume.
|
|
1002
|
+
*/
|
|
1003
|
+
interface ShapeSubscriptionQuery {
|
|
1004
|
+
/** Validated shape arguments (e.g. `{ channelId }`); forwarded to `resolveShape`. */
|
|
1005
|
+
args?: Record<string, unknown>;
|
|
1006
|
+
/** Registered shape name (the `defineShape` export the codegen subclass resolves). */
|
|
1007
|
+
name: string;
|
|
1008
|
+
/** Resume epoch the client persisted alongside {@link ShapeSubscriptionQuery.sinceSeq} (see {@link SubscriptionQuery.sinceEpoch}). */
|
|
1009
|
+
sinceEpoch?: string;
|
|
1010
|
+
/** Resume checkpoint: the `__cdc_log` cursor the client's view of this shape last reflected (see {@link SubscriptionQuery.sinceSeq}). */
|
|
1011
|
+
sinceSeq?: number;
|
|
1012
|
+
}
|
|
954
1013
|
interface SubscriptionEnvelope {
|
|
1014
|
+
/**
|
|
1015
|
+
* Stable per-client id carried by the `connect` envelope. Recorded on the
|
|
1016
|
+
* socket attachment so a shape poke can echo this client's
|
|
1017
|
+
* `__client_watermark` as its `lastMutationId`. Ignored on other envelope
|
|
1018
|
+
* types; absent for clients that don't use custom mutators.
|
|
1019
|
+
*/
|
|
1020
|
+
clientId?: string;
|
|
955
1021
|
/**
|
|
956
1022
|
* App-supplied connection context carried by the `connect` envelope (e.g.
|
|
957
1023
|
* `{ roomId, sessionId }`). Merged into the socket attachment and forwarded
|
|
@@ -966,6 +1032,19 @@ interface SubscriptionEnvelope {
|
|
|
966
1032
|
id: string;
|
|
967
1033
|
query?: SubscriptionQuery;
|
|
968
1034
|
/**
|
|
1035
|
+
* Shape descriptor of a `shape_subscribe` envelope: the named shape + its
|
|
1036
|
+
* validated args. Carries the client's resume checkpoint via
|
|
1037
|
+
* {@link SubscriptionEnvelope.sinceCheckpoint}/{@link SubscriptionEnvelope.sinceEpoch}.
|
|
1038
|
+
*/
|
|
1039
|
+
shape?: {
|
|
1040
|
+
args?: Record<string, unknown>;
|
|
1041
|
+
name: string;
|
|
1042
|
+
};
|
|
1043
|
+
/** Resume checkpoint on a `shape_subscribe` envelope (the `__cdc_log` cursor the client's shape view is at). */
|
|
1044
|
+
sinceCheckpoint?: number;
|
|
1045
|
+
/** CDC epoch the {@link SubscriptionEnvelope.sinceCheckpoint} belongs to. */
|
|
1046
|
+
sinceEpoch?: string;
|
|
1047
|
+
/**
|
|
969
1048
|
* Topic of a `whisper`/`whisper_subscribe`/`whisper_unsubscribe` envelope —
|
|
970
1049
|
* an app-chosen channel name (e.g. `"room:42:cursors"`) scoped to this shard.
|
|
971
1050
|
*/
|
|
@@ -983,7 +1062,7 @@ interface SubscriptionEnvelope {
|
|
|
983
1062
|
* this shard with NO SQLite/CDC write (AnyCable-style whispering — typing
|
|
984
1063
|
* indicators, live cursors). The sender never receives its own whisper.
|
|
985
1064
|
*/
|
|
986
|
-
type: "ack" | "connect" | "stream" | "subscribe" | "unsubscribe" | "whisper" | "whisper_subscribe" | "whisper_unsubscribe";
|
|
1065
|
+
type: "ack" | "connect" | "shape_subscribe" | "shape_unsubscribe" | "stream" | "subscribe" | "unsubscribe" | "whisper" | "whisper_subscribe" | "whisper_unsubscribe";
|
|
987
1066
|
}
|
|
988
1067
|
/**
|
|
989
1068
|
* The argument a connection-lifecycle hook receives. Structurally matches
|
|
@@ -1058,6 +1137,15 @@ interface SocketAttachment {
|
|
|
1058
1137
|
*/
|
|
1059
1138
|
admin?: boolean;
|
|
1060
1139
|
/**
|
|
1140
|
+
* Stable per-client id from the `connect` envelope (the same id the client
|
|
1141
|
+
* stamps on its custom-mutator pushes). Lets a shape poke echo this client's
|
|
1142
|
+
* `__client_watermark` as the poke's `lastMutationId`, so a `@lunora/db`
|
|
1143
|
+
* collection can drop the optimistic overlay for writes this poke has
|
|
1144
|
+
* synced. Absent for clients that don't use custom mutators. Persisted so it
|
|
1145
|
+
* survives hibernation.
|
|
1146
|
+
*/
|
|
1147
|
+
clientId?: string;
|
|
1148
|
+
/**
|
|
1061
1149
|
* `true` once the socket's `connect` envelope has fired the `onConnect`
|
|
1062
1150
|
* hooks. Gates the dispatch so a client that re-sends `connect` (or a
|
|
1063
1151
|
* duplicate frame) can't re-fire the hooks for an already-announced socket —
|
|
@@ -1091,6 +1179,14 @@ interface SocketAttachment {
|
|
|
1091
1179
|
* hooks so they run under the connecting user.
|
|
1092
1180
|
*/
|
|
1093
1181
|
identity?: Record<string, unknown>;
|
|
1182
|
+
/**
|
|
1183
|
+
* Live shape subscriptions registered on this socket, keyed by the
|
|
1184
|
+
* client-supplied subscription id. The partial-replication parallel to
|
|
1185
|
+
* {@link SocketAttachment.subs}: the poke protocol fans membership diffs to
|
|
1186
|
+
* these, while `subs` drives the legacy `data`/`delta` re-execution path.
|
|
1187
|
+
* Absent until the socket sends its first `shape_subscribe`.
|
|
1188
|
+
*/
|
|
1189
|
+
shapes?: Record<string, ShapeSubscriptionQuery>;
|
|
1094
1190
|
subs: Record<string, SubscriptionQuery>;
|
|
1095
1191
|
/**
|
|
1096
1192
|
* Verified user id resolved at upgrade (from `x-lunora-userid`), or absent
|
|
@@ -1106,6 +1202,36 @@ interface SocketAttachment {
|
|
|
1106
1202
|
whispers?: string[];
|
|
1107
1203
|
}
|
|
1108
1204
|
/**
|
|
1205
|
+
* A `shape_subscribe`'s resolved query: which `table` to replicate, the
|
|
1206
|
+
* identity-scoped `effectiveWhere` (the shape predicate AND-merged with the
|
|
1207
|
+
* table's RLS read base-where), the optional projected `columns` allow-list,
|
|
1208
|
+
* and whether the table is `.global()` (served by the latency-tiered poll path
|
|
1209
|
+
* rather than the CDC poke path). The codegen subclass's `resolveShape` builds
|
|
1210
|
+
* it under the socket's verified identity, so the membership query the poke
|
|
1211
|
+
* protocol runs is RLS-correct by construction.
|
|
1212
|
+
*/
|
|
1213
|
+
interface ResolvedShape {
|
|
1214
|
+
columns?: ReadonlyArray<string>;
|
|
1215
|
+
effectiveWhere?: WhereInput;
|
|
1216
|
+
/** `true` when the shape's table is `.global()` (lives in D1, not this DO's SQLite) — no per-DO op-log to diff, so served by the poll path. */
|
|
1217
|
+
global?: boolean;
|
|
1218
|
+
table: string;
|
|
1219
|
+
}
|
|
1220
|
+
/**
|
|
1221
|
+
* Identity a subscription/shape query is executed under, threaded EXPLICITLY
|
|
1222
|
+
* into the codegen `resolveShape`/`buildCtx` rather than read from the shared,
|
|
1223
|
+
* per-request identity fields. The value passed is the socket's OWN verified
|
|
1224
|
+
* identity (stamped on the {@link SocketAttachment} at the WS upgrade from the
|
|
1225
|
+
* runtime-minted `x-lunora-userid`/`x-lunora-identity` headers the client can't
|
|
1226
|
+
* forge), passed BY VALUE so a deferred refresh or interleaved RPC can't clobber
|
|
1227
|
+
* it. An anonymous socket leaves both fields `undefined`, so an RLS/`ctx.auth`
|
|
1228
|
+
* query fails closed (empty/denied) rather than leaking another user's data.
|
|
1229
|
+
*/
|
|
1230
|
+
interface SubscriptionIdentity {
|
|
1231
|
+
identity?: Record<string, unknown>;
|
|
1232
|
+
userId?: string;
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1109
1235
|
* One-shot backfill of every declared aggregate index. Used by tests and
|
|
1110
1236
|
* production hosts that want to populate counters up-front instead of on first
|
|
1111
1237
|
* read. Idempotent: counter rows that already exist are left alone, so it's
|
|
@@ -1147,10 +1273,16 @@ interface CdcChange {
|
|
|
1147
1273
|
* Read changelog entries newer than `sinceSeq` in commit order, up to `limit`
|
|
1148
1274
|
* (clamped to [1, 10000]). Returns the rows plus the cursor to resume from (the
|
|
1149
1275
|
* last `seq`, or `sinceSeq` when the page is empty).
|
|
1276
|
+
*
|
|
1277
|
+
* The optional `tables` set narrows the page to changes on those tables — the
|
|
1278
|
+
* shape/poke path reads one filtered page per flush so it never scans op-log
|
|
1279
|
+
* entries for tables no live shape is watching. Omit it (or pass an empty set)
|
|
1280
|
+
* for the full, unfiltered page (the existing streaming-export/resume callers).
|
|
1150
1281
|
*/
|
|
1151
1282
|
declare const readCdcChanges: (sql: SqlExec, options?: {
|
|
1152
1283
|
limit?: number;
|
|
1153
1284
|
sinceSeq?: number;
|
|
1285
|
+
tables?: ReadonlySet<string>;
|
|
1154
1286
|
}) => {
|
|
1155
1287
|
changes: CdcChange[];
|
|
1156
1288
|
cursor: number;
|
|
@@ -1186,6 +1318,11 @@ declare const applyCdcChanges: (writer: DatabaseWriterLike, changes: ReadonlyArr
|
|
|
1186
1318
|
declare const runShardMigrations: (sql: SqlExec, schema: SchemaLike, options?: {
|
|
1187
1319
|
cdc?: boolean;
|
|
1188
1320
|
}) => void;
|
|
1321
|
+
/** One shape member: its `_id` key plus the decoded document (id + creationTime merged in). */
|
|
1322
|
+
interface ShapeRow {
|
|
1323
|
+
doc: Record<string, unknown>;
|
|
1324
|
+
id: string;
|
|
1325
|
+
}
|
|
1189
1326
|
/**
|
|
1190
1327
|
* Structural projection of `state.storage.sql` (workerd's SqlStorage). We
|
|
1191
1328
|
* only require the `exec` overload — the cursor it returns is iterable and
|
|
@@ -1233,6 +1370,16 @@ interface TableDefinitionLike {
|
|
|
1233
1370
|
field?: string;
|
|
1234
1371
|
kind: "global" | "root" | "shardBy";
|
|
1235
1372
|
};
|
|
1373
|
+
/**
|
|
1374
|
+
* Mirror of `@lunora/server`'s `TableDefinition.softDeleteMode` (set by
|
|
1375
|
+
* `.softDelete()`). When present, `delete()` flips the `field` column to a
|
|
1376
|
+
* timestamp instead of physically removing the row (cascading as a soft
|
|
1377
|
+
* delete), and list reads scope out rows whose `field` is set unless
|
|
1378
|
+
* `includeDeleted` is passed. By-id reads/writes are unaffected.
|
|
1379
|
+
*/
|
|
1380
|
+
readonly softDeleteMode?: {
|
|
1381
|
+
field: string;
|
|
1382
|
+
};
|
|
1236
1383
|
readonly triggerMap?: Record<string, TriggerDefinitionLike>;
|
|
1237
1384
|
}
|
|
1238
1385
|
interface IndexDefinitionLike {
|
|
@@ -1525,7 +1672,16 @@ interface DatabaseWriterLike {
|
|
|
1525
1672
|
* RLS-aware ctx seam from §3.2).
|
|
1526
1673
|
*/
|
|
1527
1674
|
count: (tableName: string, where?: RestrictableQueryOptions | WhereInput) => Promise<number>;
|
|
1528
|
-
|
|
1675
|
+
/**
|
|
1676
|
+
* Delete a row by id. On a `.softDelete()` table this flips the marker column
|
|
1677
|
+
* (cascading as a soft delete) instead of removing the row; pass
|
|
1678
|
+
* `options.hard` to force a physical removal (which cascades as a physical
|
|
1679
|
+
* delete, reaching already-soft-deleted children too). Non-soft tables ignore
|
|
1680
|
+
* `options.hard` — they always delete physically.
|
|
1681
|
+
*/
|
|
1682
|
+
delete: (id: string, expectedTable?: string, options?: {
|
|
1683
|
+
hard?: boolean;
|
|
1684
|
+
}) => Promise<void>;
|
|
1529
1685
|
/**
|
|
1530
1686
|
* Delete many rows by id in one call (a loop over `delete()`). The returned
|
|
1531
1687
|
* `deleted` is the number of ids **requested**, not rows actually removed (an
|
|
@@ -1545,6 +1701,18 @@ interface DatabaseWriterLike {
|
|
|
1545
1701
|
}, expectedTable?: string) => Promise<{
|
|
1546
1702
|
deleted: number;
|
|
1547
1703
|
}>;
|
|
1704
|
+
/**
|
|
1705
|
+
* Delete every row matching `where` in one call. Matching rows are resolved
|
|
1706
|
+
* first, then each row is deleted through the single-row delete pipeline so
|
|
1707
|
+
* companions, CDC, and broadcast stay correct. **Atomic within a mutation** —
|
|
1708
|
+
* the DO wraps a mutation's dispatch in a BEGIN/COMMIT span, so a mid-batch
|
|
1709
|
+
* throw rolls the whole mutation back. (An action has no transaction span.)
|
|
1710
|
+
*/
|
|
1711
|
+
deleteWhere?: (tableName: string, where: WhereInput, options?: {
|
|
1712
|
+
limit?: number;
|
|
1713
|
+
}) => Promise<{
|
|
1714
|
+
deleted: number;
|
|
1715
|
+
}>;
|
|
1548
1716
|
findFirst: (tableName: string, args?: QueryArgs) => Promise<Record<string, unknown> | null>;
|
|
1549
1717
|
findFirstOrThrow: (tableName: string, args?: QueryArgs) => Promise<Record<string, unknown>>;
|
|
1550
1718
|
findMany: (tableName: string, args?: QueryArgs) => Promise<QueryPage>;
|
|
@@ -1580,11 +1748,13 @@ interface DatabaseWriterLike {
|
|
|
1580
1748
|
* Insert many documents into one table (a loop over `insert()`),
|
|
1581
1749
|
* returning the minted ids in input order. Each row gets defaults,
|
|
1582
1750
|
* validators, triggers, companion sync, CDC, and broadcast exactly as a
|
|
1583
|
-
* single insert; the caller pays one round-trip instead of N.
|
|
1584
|
-
*
|
|
1585
|
-
*
|
|
1586
|
-
*
|
|
1587
|
-
*
|
|
1751
|
+
* single insert; the caller pays one round-trip instead of N. Pass
|
|
1752
|
+
* `options.skipDuplicates: true` to turn UNIQUE-constraint breaches into
|
|
1753
|
+
* `null` results for that row instead of failing the whole batch.
|
|
1754
|
+
* **Atomic within a mutation** — the DO wraps a mutation's dispatch in a
|
|
1755
|
+
* BEGIN/COMMIT span, so a mid-batch throw rolls the whole mutation back. (An
|
|
1756
|
+
* action has no transaction span — there, the prior inserts persist; the
|
|
1757
|
+
* in-memory test harness mirrors the span.)
|
|
1588
1758
|
* Rejects a batch larger than `options.limit` (default {@link DEFAULT_BATCH_LIMIT}).
|
|
1589
1759
|
*
|
|
1590
1760
|
* Optional on the interface (like `rankBefore`): the DO writer implements it;
|
|
@@ -1594,7 +1764,8 @@ interface DatabaseWriterLike {
|
|
|
1594
1764
|
*/
|
|
1595
1765
|
insertMany?: (tableName: string, documents: ReadonlyArray<Record<string, unknown>>, options?: {
|
|
1596
1766
|
limit?: number;
|
|
1597
|
-
|
|
1767
|
+
skipDuplicates?: boolean;
|
|
1768
|
+
}) => Promise<(string | null)[]>;
|
|
1598
1769
|
/**
|
|
1599
1770
|
* Trusted bulk insert: one multi-row `INSERT` that **skips per-row `.check()`
|
|
1600
1771
|
* validators and before/after triggers** for throughput on data the caller
|
|
@@ -1649,7 +1820,25 @@ interface DatabaseWriterLike {
|
|
|
1649
1820
|
patch: Record<string, unknown>;
|
|
1650
1821
|
}>, options?: {
|
|
1651
1822
|
limit?: number;
|
|
1652
|
-
}, expectedTable?: string) => Promise<
|
|
1823
|
+
}, expectedTable?: string) => Promise<{
|
|
1824
|
+
patched: number;
|
|
1825
|
+
}>;
|
|
1826
|
+
/**
|
|
1827
|
+
* Patch every row matching `where` with the same `patch` in one call.
|
|
1828
|
+
* Matching rows are resolved first, then each row is patched through the
|
|
1829
|
+
* single-row patch pipeline so companions, CDC, and broadcast stay correct.
|
|
1830
|
+
* **Atomic within a mutation** — the DO wraps a mutation's dispatch in a
|
|
1831
|
+
* BEGIN/COMMIT span, so a mid-batch throw rolls the whole mutation back. (An
|
|
1832
|
+
* action has no transaction span.)
|
|
1833
|
+
*/
|
|
1834
|
+
patchWhere?: (tableName: string, args: {
|
|
1835
|
+
patch: Record<string, unknown>;
|
|
1836
|
+
where: WhereInput;
|
|
1837
|
+
}, options?: {
|
|
1838
|
+
limit?: number;
|
|
1839
|
+
}) => Promise<{
|
|
1840
|
+
patched: number;
|
|
1841
|
+
}>;
|
|
1653
1842
|
query: (tableName: string) => TableReaderLike;
|
|
1654
1843
|
/**
|
|
1655
1844
|
* Return the 1-based position of `options.row` within its partition under
|
|
@@ -1700,7 +1889,17 @@ interface DatabaseWriterLike {
|
|
|
1700
1889
|
* since a global table has no shard boundaries to merge across.
|
|
1701
1890
|
*/
|
|
1702
1891
|
rankPageRows?: (tableName: string, indexName: string, options?: RankPageOptions) => Promise<ShardRankPageResult>;
|
|
1703
|
-
replace: (id: string, document: Record<string, unknown>, expectedTable?: string
|
|
1892
|
+
replace: (id: string, document: Record<string, unknown>, expectedTable?: string, options?: {
|
|
1893
|
+
allowExplicitId?: boolean;
|
|
1894
|
+
}) => Promise<void>;
|
|
1895
|
+
/**
|
|
1896
|
+
* Un-soft-delete a row: clears the `.softDelete()` marker column (a by-id
|
|
1897
|
+
* UPDATE, so it works on a row that list reads currently hide). Throws when
|
|
1898
|
+
* the row's table isn't `.softDelete()`. Optional on the interface — the DO
|
|
1899
|
+
* writer implements it; the `.global()` twin does too, so a restore on a
|
|
1900
|
+
* global table routes through the DO writer's global fallback.
|
|
1901
|
+
*/
|
|
1902
|
+
restore?: (id: string, expectedTable?: string) => Promise<void>;
|
|
1704
1903
|
/**
|
|
1705
1904
|
* Best-effort, read-only reader over Lunora's system tables
|
|
1706
1905
|
* (`_scheduled_functions`, `_storage`). Eventually consistent and **not**
|
|
@@ -1717,14 +1916,12 @@ interface DatabaseWriterLike {
|
|
|
1717
1916
|
system?: SystemDatabaseReader;
|
|
1718
1917
|
}
|
|
1719
1918
|
/**
|
|
1720
|
-
* Thrown by `.unique()` when more than one row matches.
|
|
1721
|
-
*
|
|
1722
|
-
* cross-package
|
|
1723
|
-
*
|
|
1919
|
+
* Thrown by `.unique()` when more than one row matches. A `LunoraError` subclass
|
|
1920
|
+
* (`code: "NOT_UNIQUE"`, `status: 400`) recognised structurally by the
|
|
1921
|
+
* cross-package transport mapper (via `isLunoraError`) without an `instanceof`
|
|
1922
|
+
* check against `@lunora/do`.
|
|
1724
1923
|
*/
|
|
1725
|
-
declare class NotUniqueError extends
|
|
1726
|
-
readonly code: string;
|
|
1727
|
-
readonly status: number;
|
|
1924
|
+
declare class NotUniqueError extends LunoraError {
|
|
1728
1925
|
constructor(message?: string);
|
|
1729
1926
|
}
|
|
1730
1927
|
/**
|
|
@@ -1918,6 +2115,22 @@ declare const readAggregateValue: (op: string, row: {
|
|
|
1918
2115
|
* string.
|
|
1919
2116
|
*/
|
|
1920
2117
|
declare const encodeAggregateKey: (by: ReadonlyArray<string>, source: Record<string, unknown>) => string;
|
|
2118
|
+
/** One recorded admin operation, in monotonic `seq` order. */
|
|
2119
|
+
interface AuditEntry {
|
|
2120
|
+
/** JSON-decoded extra context (the acting user, op-specific counts, …); absent when none was recorded. */
|
|
2121
|
+
detail?: Record<string, unknown>;
|
|
2122
|
+
/** Primary key of the affected row, when the op targets one. */
|
|
2123
|
+
id?: string;
|
|
2124
|
+
/** Short op identifier, e.g. `writeRow` or `runMigration`. */
|
|
2125
|
+
op: string;
|
|
2126
|
+
/** Monotonic per-shard cursor — strictly increasing, never reused. */
|
|
2127
|
+
seq: number;
|
|
2128
|
+
/** Affected table, when the op targets one. */
|
|
2129
|
+
table?: string;
|
|
2130
|
+
/** Wall-clock millis when the op was recorded. */
|
|
2131
|
+
ts: number;
|
|
2132
|
+
}
|
|
2133
|
+
/** Fields accepted when appending one audit entry; `seq` is assigned by the table. */
|
|
1921
2134
|
/** Reserved single-row auth accumulator table. Auto-hidden from the data browser by the `__lunora` prefix. */
|
|
1922
2135
|
declare const AUTH_METRICS_TABLE = "__lunora_auth_metrics";
|
|
1923
2136
|
/** Reserved coarse time-series table: app-wide auth attempt/failure counts bucketed by a fixed window. */
|
|
@@ -2155,6 +2368,112 @@ interface RenderedSql {
|
|
|
2155
2368
|
* identifiers quoted and placeholders numbered the way that engine expects.
|
|
2156
2369
|
*/
|
|
2157
2370
|
declare const renderSql: (engine: SqlEngine, query: SQL) => RenderedSql;
|
|
2371
|
+
/** The result of {@link diffExternalSource}: the changes to replay, and the baseline the next tick diffs from. */
|
|
2372
|
+
interface ExternalSourceDiffResult {
|
|
2373
|
+
/** Ordered for `applyCdcChanges`: upserts in pulled order, then deletes in baseline order. */
|
|
2374
|
+
changes: CdcChange[];
|
|
2375
|
+
/** `id → canonical-value JSON` — pass back as the `baseline` next tick (or persist for an incremental cursor). */
|
|
2376
|
+
nextBaseline: Map<string, string>;
|
|
2377
|
+
}
|
|
2378
|
+
/**
|
|
2379
|
+
* Project a row to the document the ingest loop stores + compares on: `_id` plus
|
|
2380
|
+
* either the `columns` allow-list or every field except the framework-assigned
|
|
2381
|
+
* `_creationTime` (which the source never supplies). Returned as a plain object;
|
|
2382
|
+
* key order is irrelevant because {@link stableStringify} sorts keys. Used for BOTH
|
|
2383
|
+
* the pulled side here and the local baseline, so the two are byte-identical for an
|
|
2384
|
+
* unchanged row.
|
|
2385
|
+
*/
|
|
2386
|
+
|
|
2387
|
+
/**
|
|
2388
|
+
* Diff a sourced table's freshly-pulled membership against the local baseline.
|
|
2389
|
+
* Returns the `CdcChange[]` to apply (in stable order: upserts in pulled order,
|
|
2390
|
+
* then deletes in baseline order) and the next baseline (`id → canonical JSON`).
|
|
2391
|
+
*/
|
|
2392
|
+
declare const diffExternalSource: (pulled: ReadonlyArray<Record<string, unknown>>, baseline: ReadonlyMap<string, string>, options: {
|
|
2393
|
+
columns?: ReadonlyArray<string>;
|
|
2394
|
+
table: string;
|
|
2395
|
+
}) => ExternalSourceDiffResult;
|
|
2396
|
+
/** The outcome of one materialize pass: how many changes were applied, and the baseline the next tick diffs from. */
|
|
2397
|
+
interface MaterializeResult {
|
|
2398
|
+
/** Number of `CdcChange`s applied (inserts + updates + deletes). Zero on a steady-state tick. */
|
|
2399
|
+
applied: number;
|
|
2400
|
+
/** `id → projected-value JSON` — the post-tick membership, to feed back as `baseline` next tick. */
|
|
2401
|
+
nextBaseline: Map<string, string>;
|
|
2402
|
+
}
|
|
2403
|
+
/**
|
|
2404
|
+
* Diff `pulled` against `baseline` and apply the delta to `writer`. Returns the
|
|
2405
|
+
* applied count and the next baseline. A steady-state tick (membership unchanged)
|
|
2406
|
+
* applies nothing and returns `applied: 0`.
|
|
2407
|
+
*/
|
|
2408
|
+
declare const materializeExternalRows: (writer: DatabaseWriterLike, pulled: ReadonlyArray<Record<string, unknown>>, baseline: ReadonlyMap<string, string>, options: {
|
|
2409
|
+
columns?: ReadonlyArray<string>;
|
|
2410
|
+
table: string;
|
|
2411
|
+
}) => Promise<MaterializeResult>;
|
|
2412
|
+
/**
|
|
2413
|
+
* Read the materialized table's current membership as the canonical full-pull
|
|
2414
|
+
* baseline (`id → canonical JSON`). Reuses the shape scanner (`selectShapeRows`)
|
|
2415
|
+
* and the SAME {@link projectExternalSourceRow} + {@link stableStringify} the diff
|
|
2416
|
+
* uses, so a stored row (with `_creationTime` + arbitrary key order) compares
|
|
2417
|
+
* byte-identical to its freshly-pulled source counterpart — an unchanged row
|
|
2418
|
+
* produces no spurious update.
|
|
2419
|
+
*/
|
|
2420
|
+
declare const readExternalSourceBaseline: (sql: SqlExec, table: string, columns?: ReadonlyArray<string>) => Map<string, string>;
|
|
2421
|
+
/**
|
|
2422
|
+
* Run one full-pull materialize tick: read the table's current membership as the
|
|
2423
|
+
* baseline, diff the freshly-pulled rows against it, and apply the delta. This is
|
|
2424
|
+
* the system-driven loop body the DO poll alarm calls — the table IS the baseline
|
|
2425
|
+
* (design §1 Fact A), so no separate snapshot is kept. Pass both the read handle
|
|
2426
|
+
* (`sql`) and the validated `writer` (the DO has both); they must address the same
|
|
2427
|
+
* table.
|
|
2428
|
+
*/
|
|
2429
|
+
declare const runExternalSourceTick: (sql: SqlExec, writer: DatabaseWriterLike, pulled: ReadonlyArray<Record<string, unknown>>, options: {
|
|
2430
|
+
columns?: ReadonlyArray<string>;
|
|
2431
|
+
table: string;
|
|
2432
|
+
}) => Promise<MaterializeResult>;
|
|
2433
|
+
/** The minimal SqlClient surface the poll loop calls (mirrors `@lunora/hyperdrive`'s `SqlClient`). */
|
|
2434
|
+
interface SourceClientLike {
|
|
2435
|
+
query: <Row = Record<string, unknown>>(text: string, parameters?: ReadonlyArray<unknown>) => Promise<Row[]>;
|
|
2436
|
+
}
|
|
2437
|
+
/** Poll cadence: `"manual"` (never auto-poll) or a minimum interval between polls. */
|
|
2438
|
+
type SourceRefresh = "manual" | {
|
|
2439
|
+
everyMs: number;
|
|
2440
|
+
};
|
|
2441
|
+
/** The runtime `.source(...)` config the poll loop reads — a structural mirror of `@lunora/server`'s `ExternalSourceDefinition` (only the fields the tick uses). */
|
|
2442
|
+
interface ExternalSourceLike {
|
|
2443
|
+
binding: string;
|
|
2444
|
+
columns?: ReadonlyArray<string>;
|
|
2445
|
+
idColumn?: string;
|
|
2446
|
+
map?: (row: Record<string, unknown>) => Record<string, unknown>;
|
|
2447
|
+
query: string;
|
|
2448
|
+
refresh?: SourceRefresh;
|
|
2449
|
+
tenantBy?: (shardKey: string) => ReadonlyArray<unknown>;
|
|
2450
|
+
}
|
|
2451
|
+
/**
|
|
2452
|
+
* Lift an external row to a Lunora document: the `idColumn` value becomes a
|
|
2453
|
+
* stringified `_id`, then either `map` shapes the body or every other column is
|
|
2454
|
+
* copied verbatim. Throws on a missing/null id, and on a non-scalar id, so a
|
|
2455
|
+
* misconfigured query fails loudly instead of materializing rows under the literal
|
|
2456
|
+
* id `"undefined"` (or collapsing many rows onto one id). Shared with
|
|
2457
|
+
* `@lunora/hyperdrive`'s `projectSourceRow`.
|
|
2458
|
+
*/
|
|
2459
|
+
declare const liftSourceId: (row: Record<string, unknown>, options?: {
|
|
2460
|
+
idColumn?: string;
|
|
2461
|
+
map?: (row: Record<string, unknown>) => Record<string, unknown>;
|
|
2462
|
+
}) => Record<string, unknown>;
|
|
2463
|
+
/**
|
|
2464
|
+
* Whether a source should poll on this alarm tick. `"manual"` never auto-polls;
|
|
2465
|
+
* `{ everyMs }` polls at most once per interval (the alarm floor still bounds it
|
|
2466
|
+
* from below); an omitted `refresh` polls every tick. `lastPolledMs` is `undefined`
|
|
2467
|
+
* before the first poll (always due).
|
|
2468
|
+
*/
|
|
2469
|
+
declare const isSourceDue: (refresh: SourceRefresh | undefined, lastPolledMs: number | undefined, nowMs: number) => boolean;
|
|
2470
|
+
/**
|
|
2471
|
+
* Pull a sourced table's tenant slice from `client`, project each row through
|
|
2472
|
+
* {@link liftSourceId}, and materialize it via {@link runExternalSourceTick}
|
|
2473
|
+
* (read local baseline → diff → apply through the validated CDC writer). The
|
|
2474
|
+
* per-table body the DO poll alarm runs; `shardKey` binds into `tenantBy`.
|
|
2475
|
+
*/
|
|
2476
|
+
declare const pullExternalSourceTick: (sql: SqlExec, writer: DatabaseWriterLike, client: SourceClientLike, table: string, source: ExternalSourceLike, shardKey: string) => Promise<MaterializeResult>;
|
|
2158
2477
|
/**
|
|
2159
2478
|
* Reserved `functionPath` prefix for admin introspection RPCs. These travel
|
|
2160
2479
|
* over the same `/_lunora/rpc` → shard `/rpc` path as ordinary functions, but
|
|
@@ -2177,6 +2496,19 @@ declare const ADMIN_FUNCTION_PREFIX = "__lunora_admin__:";
|
|
|
2177
2496
|
*/
|
|
2178
2497
|
declare const RELATION_FUNCTION_PREFIX = "__lunora_relation__:";
|
|
2179
2498
|
/**
|
|
2499
|
+
* Reserved `functionPath` prefix for live feature-flag reads. The React client's
|
|
2500
|
+
* `useFlag`/`useFlags` subscribe to `__lunora_flags__:eval` over the same WS
|
|
2501
|
+
* channel as a user query; `ShardDO` intercepts it before user dispatch and
|
|
2502
|
+
* serves it from the codegen-overridden flag-subscription read hook, which
|
|
2503
|
+
* evaluates the flag through the app's OpenFeature provider under the socket's
|
|
2504
|
+
* verified identity. Like the other reserved prefixes it is NOT admin-gated (a
|
|
2505
|
+
* flag read is public, scoped to the subscriber's own targeting context), and
|
|
2506
|
+
* the `__lunora_` namespace is reserved so a real `<file>:<function>` can't
|
|
2507
|
+
* collide. Re-evaluated on every write-flush so values stay live within a
|
|
2508
|
+
* session (provider-side flips with no intervening write surface on reconnect).
|
|
2509
|
+
*/
|
|
2510
|
+
declare const FLAGS_FUNCTION_PREFIX = "__lunora_flags__:";
|
|
2511
|
+
/**
|
|
2180
2512
|
* Fully-qualified reserved paths the data browser invokes. The
|
|
2181
2513
|
* `__lunora_admin__:` prefix is spelled out inline rather than interpolated so
|
|
2182
2514
|
* the values stay emittable under `--isolatedDeclarations`.
|
|
@@ -2185,6 +2517,7 @@ declare const ADMIN_FUNCTIONS: {
|
|
|
2185
2517
|
readonly applyCdc: "__lunora_admin__:applyCdc";
|
|
2186
2518
|
readonly cdcSync: "__lunora_admin__:cdcSync";
|
|
2187
2519
|
readonly clearCapturedMail: "__lunora_admin__:clearCapturedMail";
|
|
2520
|
+
readonly clearQueueMessages: "__lunora_admin__:clearQueueMessages";
|
|
2188
2521
|
readonly clearTable: "__lunora_admin__:clearTable";
|
|
2189
2522
|
readonly createWorkflowInstance: "__lunora_admin__:createWorkflowInstance";
|
|
2190
2523
|
readonly deleteRows: "__lunora_admin__:deleteRows";
|
|
@@ -2196,17 +2529,22 @@ declare const ADMIN_FUNCTIONS: {
|
|
|
2196
2529
|
readonly getAuditLog: "__lunora_admin__:getAuditLog";
|
|
2197
2530
|
readonly getAuthMetrics: "__lunora_admin__:getAuthMetrics";
|
|
2198
2531
|
readonly getCapturedMail: "__lunora_admin__:getCapturedMail";
|
|
2532
|
+
readonly getFanoutMetrics: "__lunora_admin__:getFanoutMetrics";
|
|
2199
2533
|
readonly getFunctionStats: "__lunora_admin__:getFunctionStats";
|
|
2534
|
+
readonly getIssues: "__lunora_admin__:getIssues";
|
|
2200
2535
|
readonly listSubscriptions: "__lunora_admin__:listSubscriptions";
|
|
2201
2536
|
readonly listTableIndexes: "__lunora_admin__:listTableIndexes";
|
|
2202
2537
|
readonly getLogs: "__lunora_admin__:getLogs";
|
|
2203
2538
|
readonly getMetrics: "__lunora_admin__:getMetrics";
|
|
2204
2539
|
readonly getPitrBookmark: "__lunora_admin__:getPitrBookmark";
|
|
2540
|
+
readonly getQueueMessages: "__lunora_admin__:getQueueMessages";
|
|
2205
2541
|
readonly getRequestLog: "__lunora_admin__:getRequestLog";
|
|
2206
2542
|
readonly getSecurityAudit: "__lunora_admin__:getSecurityAudit";
|
|
2207
2543
|
readonly getSettings: "__lunora_admin__:getSettings";
|
|
2208
2544
|
readonly getWorkflowInstanceStatus: "__lunora_admin__:getWorkflowInstanceStatus";
|
|
2209
2545
|
readonly importShard: "__lunora_admin__:importShard";
|
|
2546
|
+
readonly listFlags: "__lunora_admin__:listFlags";
|
|
2547
|
+
readonly listQueues: "__lunora_admin__:listQueues";
|
|
2210
2548
|
readonly listTables: "__lunora_admin__:listTables";
|
|
2211
2549
|
readonly listWorkflows: "__lunora_admin__:listWorkflows";
|
|
2212
2550
|
readonly maskPolicies: "__lunora_admin__:maskPolicies";
|
|
@@ -2218,10 +2556,13 @@ declare const ADMIN_FUNCTIONS: {
|
|
|
2218
2556
|
readonly recordAuthEvent: "__lunora_admin__:recordAuthEvent";
|
|
2219
2557
|
readonly recordContainerEvent: "__lunora_admin__:recordContainerEvent";
|
|
2220
2558
|
readonly recordMail: "__lunora_admin__:recordMail";
|
|
2559
|
+
readonly recordQueueMessage: "__lunora_admin__:recordQueueMessage";
|
|
2560
|
+
readonly replayQueueMessage: "__lunora_admin__:replayQueueMessage";
|
|
2221
2561
|
readonly rlsPolicies: "__lunora_admin__:rlsPolicies";
|
|
2222
2562
|
readonly runAs: "__lunora_admin__:runAs";
|
|
2223
2563
|
readonly runMigration: "__lunora_admin__:runMigration";
|
|
2224
2564
|
readonly runSql: "__lunora_admin__:runSql";
|
|
2565
|
+
readonly sendQueueMessage: "__lunora_admin__:sendQueueMessage";
|
|
2225
2566
|
readonly sendTestMail: "__lunora_admin__:sendTestMail";
|
|
2226
2567
|
readonly storageOrphans: "__lunora_admin__:storageOrphans";
|
|
2227
2568
|
readonly storageReferences: "__lunora_admin__:storageReferences";
|
|
@@ -2234,30 +2575,6 @@ interface TableInfo {
|
|
|
2234
2575
|
name: string;
|
|
2235
2576
|
rowCount: number;
|
|
2236
2577
|
}
|
|
2237
|
-
/**
|
|
2238
|
-
* One recorded admin operation served by `__lunora_admin__:getAuditLog`, sourced
|
|
2239
|
-
* from the reserved `__lunora_audit__` table (see `audit-log.ts`). Unlike the
|
|
2240
|
-
* in-memory `getMetrics`/`getFunctionStats` counters, the audit log is durable —
|
|
2241
|
-
* it survives hibernation/restart and is bounded only by a retention cap. `seq`
|
|
2242
|
-
* is a monotonic per-shard cursor the studio pages through; `op` is the short
|
|
2243
|
-
* op name (`writeRow`, `runMigration`, `importShard`, `applyCdc`); `table`/`id`
|
|
2244
|
-
* are present when the op targets one; `detail` carries op-specific context
|
|
2245
|
-
* (notably the acting `userId`).
|
|
2246
|
-
*/
|
|
2247
|
-
interface AuditEntry {
|
|
2248
|
-
/** JSON extra context (acting user, op-specific counts, …); absent when none was recorded. */
|
|
2249
|
-
detail?: Record<string, unknown>;
|
|
2250
|
-
/** Primary key of the affected row, when the op targets one. */
|
|
2251
|
-
id?: string;
|
|
2252
|
-
/** Short op identifier, e.g. `writeRow`. */
|
|
2253
|
-
op: string;
|
|
2254
|
-
/** Monotonic per-shard cursor — strictly increasing, never reused. */
|
|
2255
|
-
seq: number;
|
|
2256
|
-
/** Affected table, when the op targets one. */
|
|
2257
|
-
table?: string;
|
|
2258
|
-
/** Epoch-ms the op was recorded. */
|
|
2259
|
-
ts: number;
|
|
2260
|
-
}
|
|
2261
2578
|
/** Payload of a `__lunora_admin__:getAuditLog` call: the recorded entries, newest first. */
|
|
2262
2579
|
interface AuditLogResult {
|
|
2263
2580
|
entries: AuditEntry[];
|
|
@@ -2510,20 +2827,66 @@ interface StorageRulesResult {
|
|
|
2510
2827
|
* package's tests and the studio's fails the build if the two key sets diverge.
|
|
2511
2828
|
*/
|
|
2512
2829
|
interface StudioFeaturesResult {
|
|
2830
|
+
/** `@lunora/bindings/analytics` / `ctx.analytics` is used, or it is a declared dependency. */
|
|
2831
|
+
analytics: boolean;
|
|
2832
|
+
/** `@lunora/auth` is a declared dependency (backs the Users / Sessions / Organizations / Configuration pages). */
|
|
2833
|
+
auth: boolean;
|
|
2834
|
+
/** `@lunora/container` / `ctx.containers` is used, the app declares containers, or it is a declared dependency. */
|
|
2835
|
+
containers: boolean;
|
|
2836
|
+
/** `@lunora/flags` / `ctx.flags` is used, or it is a declared dependency. */
|
|
2837
|
+
flags: boolean;
|
|
2838
|
+
/** `@lunora/bindings/kv` / `ctx.kv` is used, or it is a declared dependency. */
|
|
2839
|
+
kv: boolean;
|
|
2513
2840
|
/** `@lunora/mail` is imported by a `lunora/` source or a declared dependency. */
|
|
2514
2841
|
mail: boolean;
|
|
2515
2842
|
/** `@lunora/payment` is used (import or `ctx.payments`) or a declared dependency. */
|
|
2516
2843
|
payments: boolean;
|
|
2844
|
+
/** `@lunora/queue` / `ctx.queues` is used, the app declares queues, or it is a declared dependency. */
|
|
2845
|
+
queues: boolean;
|
|
2517
2846
|
/** `@lunora/scheduler` / `ctx.scheduler` is used, the app declares crons, or it is a declared dependency. */
|
|
2518
2847
|
scheduler: boolean;
|
|
2519
2848
|
/** `@lunora/storage` / `ctx.storage` is used, the schema declares storage columns/rules, or it is a declared dependency. */
|
|
2520
2849
|
storage: boolean;
|
|
2521
|
-
/** The schema declares vector indexes, `@lunora/vectors` / `ctx.vectors` is used, or it is a declared dependency. */
|
|
2850
|
+
/** The schema declares vector indexes, `@lunora/bindings/vectors` / `ctx.vectors` is used, or it is a declared dependency. */
|
|
2522
2851
|
vectors: boolean;
|
|
2523
2852
|
/** `@lunora/workflow` / `ctx.workflows` is used, the app declares workflows, or it is a declared dependency. */
|
|
2524
2853
|
workflows: boolean;
|
|
2525
2854
|
}
|
|
2526
2855
|
/**
|
|
2856
|
+
* One feature flag evaluated under a supplied targeting context, surfaced by
|
|
2857
|
+
* `__lunora_admin__:listFlags` for the studio's read-only Flags page. The `key`
|
|
2858
|
+
* and `type` are statically discovered by `@lunora/codegen` from the app's
|
|
2859
|
+
* `ctx.flags.<type>("key", …)` reads; `value`/`reason`/`variant`/`errorCode`
|
|
2860
|
+
* come from the live OpenFeature evaluation (the codegen subclass overrides the
|
|
2861
|
+
* base `evaluateFlags` hook). `value` is the resolved flag value as JSON.
|
|
2862
|
+
*/
|
|
2863
|
+
interface FlagEvaluation {
|
|
2864
|
+
/** OpenFeature `errorCode` when the evaluation failed (the value falls back to the default). */
|
|
2865
|
+
errorCode?: string;
|
|
2866
|
+
/** The discovered flag key (the first argument of a `ctx.flags.<type>(...)` read). */
|
|
2867
|
+
key: string;
|
|
2868
|
+
/** OpenFeature `reason` for the resolution (`TARGETING_MATCH`, `DEFAULT`, `ERROR`, …). */
|
|
2869
|
+
reason?: string;
|
|
2870
|
+
/** The flag's value type, derived from which `ctx.flags.<type>` method read it. */
|
|
2871
|
+
type: "boolean" | "number" | "object" | "string";
|
|
2872
|
+
/** The resolved value (JSON), or the type default when unconfigured / on error. */
|
|
2873
|
+
value: unknown;
|
|
2874
|
+
/** OpenFeature `variant` identifier when the provider reports one. */
|
|
2875
|
+
variant?: string;
|
|
2876
|
+
}
|
|
2877
|
+
/**
|
|
2878
|
+
* Payload of a `__lunora_admin__:listFlags` call: every statically-discovered
|
|
2879
|
+
* flag evaluated under the supplied targeting context. `configured` is `false`
|
|
2880
|
+
* when the app wires no `@lunora/flags` provider (the base hook), so the studio
|
|
2881
|
+
* can distinguish "no flags configured" from "configured but zero flags read".
|
|
2882
|
+
*/
|
|
2883
|
+
interface FlagsResult {
|
|
2884
|
+
/** `true` when an `@lunora/flags` provider is wired (the codegen override ran). */
|
|
2885
|
+
configured: boolean;
|
|
2886
|
+
/** Each discovered flag evaluated under the request's targeting context. */
|
|
2887
|
+
flags: FlagEvaluation[];
|
|
2888
|
+
}
|
|
2889
|
+
/**
|
|
2527
2890
|
* One declared Cloudflare Workflow, surfaced by `__lunora_admin__:listWorkflows`
|
|
2528
2891
|
* for the studio's Workflows page. Statically discovered by `@lunora/codegen`
|
|
2529
2892
|
* from `lunora/workflows.ts` (the codegen subclass overrides the base hook);
|
|
@@ -2544,6 +2907,28 @@ interface WorkflowsResult {
|
|
|
2544
2907
|
workflows: WorkflowMetadata[];
|
|
2545
2908
|
}
|
|
2546
2909
|
/**
|
|
2910
|
+
* One declared Cloudflare Queue, surfaced by `__lunora_admin__:listQueues` for
|
|
2911
|
+
* the studio's Queues page. Statically discovered by `@lunora/codegen` from
|
|
2912
|
+
* `lunora/queues.ts` (the codegen subclass overrides the base hook); queues are
|
|
2913
|
+
* not Durable Objects and carry no runtime state in the shard, so this is pure
|
|
2914
|
+
* declaration metadata. `binding` is the generated `QUEUE_*` producer binding,
|
|
2915
|
+
* `name` the deployed `queues.producers[].queue`, `exportName` the
|
|
2916
|
+
* `lunora/queues.ts` export (`ctx.queues.<exportName>`), `mode` whether the
|
|
2917
|
+
* queue is consumed by a worker (`push`) or polled externally (`pull`), and
|
|
2918
|
+
* `deadLetterQueue` the optional DLQ a push consumer dead-letters to.
|
|
2919
|
+
*/
|
|
2920
|
+
interface QueueMetadata {
|
|
2921
|
+
binding: string;
|
|
2922
|
+
deadLetterQueue?: string;
|
|
2923
|
+
exportName: string;
|
|
2924
|
+
mode: "pull" | "push";
|
|
2925
|
+
name: string;
|
|
2926
|
+
}
|
|
2927
|
+
/** Payload of a `__lunora_admin__:listQueues` call: every declared queue, sorted by export name. */
|
|
2928
|
+
interface QueuesResult {
|
|
2929
|
+
queues: QueueMetadata[];
|
|
2930
|
+
}
|
|
2931
|
+
/**
|
|
2547
2932
|
* Lifecycle state of a workflow instance, mirrored from `@lunora/workflow`'s
|
|
2548
2933
|
* `WorkflowInstanceStatus` so `@lunora/do` carries no dependency on the workflow
|
|
2549
2934
|
* package. Returned by `getWorkflowInstanceStatus` and `createWorkflowInstance`.
|
|
@@ -2617,7 +3002,12 @@ interface TablePage {
|
|
|
2617
3002
|
*/
|
|
2618
3003
|
refs?: Record<string, string>;
|
|
2619
3004
|
rows: Record<string, unknown>[];
|
|
2620
|
-
|
|
3005
|
+
/**
|
|
3006
|
+
* Total rows matching the predicate. Absent when the read passed
|
|
3007
|
+
* `skipCount: true` (the caller sources the count from a separate,
|
|
3008
|
+
* predicate-keyed read instead of recomputing it per page).
|
|
3009
|
+
*/
|
|
3010
|
+
total?: number;
|
|
2621
3011
|
}
|
|
2622
3012
|
/** Comparison a {@link FilterClause} applies. `contains` is a case-sensitive substring (LIKE); the rest are direct SQL comparisons. */
|
|
2623
3013
|
type FilterOperator = "contains" | "eq" | "gt" | "gte" | "lt" | "lte" | "ne";
|
|
@@ -2633,8 +3023,6 @@ interface FilterClause {
|
|
|
2633
3023
|
operator: FilterOperator;
|
|
2634
3024
|
value?: unknown;
|
|
2635
3025
|
}
|
|
2636
|
-
/** Sort direction for an {@link OrderByClause}. */
|
|
2637
|
-
type SortDirection = "asc" | "desc";
|
|
2638
3026
|
/**
|
|
2639
3027
|
* A server-side sort over one displayed column. `column` resolves the same way a
|
|
2640
3028
|
* {@link FilterClause}'s does — a physical/meta column orders by its identifier, a
|
|
@@ -2675,6 +3063,14 @@ interface ReadTablePageOptions {
|
|
|
2675
3063
|
* Empty/whitespace is treated as no filter.
|
|
2676
3064
|
*/
|
|
2677
3065
|
search?: string;
|
|
3066
|
+
/**
|
|
3067
|
+
* Skip the `SELECT COUNT(*)` and return the page with `total` absent. The
|
|
3068
|
+
* data browser splits the row count into a separate predicate-keyed read (one
|
|
3069
|
+
* that excludes `offset`), so paging never re-counts; the page read passes
|
|
3070
|
+
* this to avoid recomputing the same total per offset. Unset → the COUNT runs
|
|
3071
|
+
* (today's behavior) and `total` is populated.
|
|
3072
|
+
*/
|
|
3073
|
+
skipCount?: boolean;
|
|
2678
3074
|
table: string;
|
|
2679
3075
|
}
|
|
2680
3076
|
/**
|
|
@@ -2951,10 +3347,14 @@ type LogLevel = "debug" | "error" | "info" | "warn";
|
|
|
2951
3347
|
/**
|
|
2952
3348
|
* One buffered log line. `functionPath` is the RPC that produced it (when the
|
|
2953
3349
|
* entry came from the RPC dispatch site); `timestamp` is `Date.now()` at the
|
|
2954
|
-
* moment it was pushed.
|
|
3350
|
+
* moment it was pushed. `instance`/`exitCode` are populated for container
|
|
3351
|
+
* lifecycle entries: `instance` correlates the per-instance Durable Object id,
|
|
3352
|
+
* `exitCode` carries the process exit code parsed out of a `stop` event.
|
|
2955
3353
|
*/
|
|
2956
3354
|
interface LogEntry {
|
|
3355
|
+
exitCode?: number;
|
|
2957
3356
|
functionPath?: string;
|
|
3357
|
+
instance?: string;
|
|
2958
3358
|
level: LogLevel;
|
|
2959
3359
|
message: string;
|
|
2960
3360
|
timestamp: number;
|
|
@@ -3080,13 +3480,11 @@ declare const clearCapturedMail: (sql: SqlExec) => {
|
|
|
3080
3480
|
/**
|
|
3081
3481
|
* Thrown by `findFirstOrThrow` when no document matches the query.
|
|
3082
3482
|
*
|
|
3083
|
-
*
|
|
3084
|
-
*
|
|
3085
|
-
* `instanceof` check against `@lunora/do`.
|
|
3483
|
+
* A `LunoraError` subclass (`code: "NOT_FOUND"`, `status: 404`) so the
|
|
3484
|
+
* cross-package transport mapper maps it to a 404 structurally (via
|
|
3485
|
+
* `isLunoraError`) without an `instanceof` check against `@lunora/do`.
|
|
3086
3486
|
*/
|
|
3087
|
-
declare class NotFoundError extends
|
|
3088
|
-
readonly code: string;
|
|
3089
|
-
readonly status: number;
|
|
3487
|
+
declare class NotFoundError extends LunoraError {
|
|
3090
3488
|
constructor(message?: string);
|
|
3091
3489
|
}
|
|
3092
3490
|
/**
|
|
@@ -3284,6 +3682,16 @@ declare const assertFlatPredicate: (where: WhereInput | undefined, schema: Resol
|
|
|
3284
3682
|
* and issues no extra query).
|
|
3285
3683
|
*/
|
|
3286
3684
|
declare const resolveRelationPredicates: (where: WhereInput | undefined, options: ResolveRelationPredicatesOptions) => Promise<WhereInput | undefined>;
|
|
3685
|
+
/**
|
|
3686
|
+
* Registration-time guard for partial-replication shapes. A live shape can only
|
|
3687
|
+
* be poked from the op-log of its OWN shard Durable Object, so an
|
|
3688
|
+
* `effectiveWhere` that joins to a `.shardBy()` table reaches rows that live in
|
|
3689
|
+
* other DOs the poke loop can never observe. Reject such a shape up front with
|
|
3690
|
+
* the two supported remedies. Called from the generated `resolveShape` override
|
|
3691
|
+
* the moment a socket subscribes (the first point the compiled predicate and the
|
|
3692
|
+
* schema's shard modes are both in hand).
|
|
3693
|
+
*/
|
|
3694
|
+
declare const assertShapeShardable: (effectiveWhere: WhereInput | undefined, schema: ResolveContext["schema"], table: string) => void;
|
|
3287
3695
|
/** Severity of a `ctx.log.*` call, mirroring the console method names (`log` is the default level, distinct from `info`). */
|
|
3288
3696
|
type ContextLogLevel = "debug" | "error" | "info" | "log" | "warn";
|
|
3289
3697
|
/** The fields {@link emitLogEvent} ships for one `ctx.log.*` call. */
|
|
@@ -3312,24 +3720,6 @@ interface LogEventInput {
|
|
|
3312
3720
|
* that want the raw values.
|
|
3313
3721
|
*/
|
|
3314
3722
|
/**
|
|
3315
|
-
* Secure-by-default write-path guard for `ctx.db`.
|
|
3316
|
-
*
|
|
3317
|
-
* When a schema is marked `defineSchema(...).rls("required")`, the generated
|
|
3318
|
-
* user-facing ctx wraps its writer with {@link guardWriter}. The guard denies
|
|
3319
|
-
* every read/write against a PROTECTED table (any table not marked `.public()`)
|
|
3320
|
-
* so a procedure that forgot `.use(rls(policies))` fails CLOSED — it can never
|
|
3321
|
-
* silently see or mutate a protected table through the unwrapped writer.
|
|
3322
|
-
*
|
|
3323
|
-
* The guard is transparent to the RLS middleware: it hangs the UNWRAPPED writer
|
|
3324
|
-
* off {@link RLS_UNWRAP_SYMBOL}, so `@lunora/server`'s `rls()` middleware
|
|
3325
|
-
* recovers the raw writer (via the same `Symbol.for` key — no cross-package
|
|
3326
|
-
* import) to evaluate policies and issue policy-filtered reads without tripping
|
|
3327
|
-
* the guard. Only procedures that NEVER engaged RLS keep talking to the guard.
|
|
3328
|
-
*
|
|
3329
|
-
* Admin / migration / studio writers (built from `createShardCtxDb` WITHOUT
|
|
3330
|
-
* `enforceRls`) are never guarded — they are trusted system paths.
|
|
3331
|
-
*/
|
|
3332
|
-
/**
|
|
3333
3723
|
* Well-known symbol the guard hangs the unwrapped writer off of. `Symbol.for`
|
|
3334
3724
|
* (the cross-realm global registry) lets `@lunora/server`'s RLS middleware read
|
|
3335
3725
|
* it WITHOUT importing this module — both sides reference the same registered
|
|
@@ -3338,14 +3728,12 @@ interface LogEventInput {
|
|
|
3338
3728
|
declare const RLS_UNWRAP_SYMBOL: symbol;
|
|
3339
3729
|
/**
|
|
3340
3730
|
* Thrown when a raw (non-RLS) handler touches a protected table under a
|
|
3341
|
-
* `.rls("required")` schema.
|
|
3342
|
-
*
|
|
3343
|
-
* deliberately avoid a hard `@lunora/do` runtime dependency —
|
|
3344
|
-
*
|
|
3345
|
-
*/
|
|
3346
|
-
declare class RlsRequiredError extends
|
|
3347
|
-
readonly code: string;
|
|
3348
|
-
readonly status: number;
|
|
3731
|
+
* `.rls("required")` schema. A `LunoraError` subclass (`code: "RLS_REQUIRED"`,
|
|
3732
|
+
* `status: 403`) recognised structurally across packages (via `isLunoraError`) —
|
|
3733
|
+
* which deliberately avoid a hard `@lunora/do` runtime dependency — without an
|
|
3734
|
+
* `instanceof` check. `table` is kept as an own property.
|
|
3735
|
+
*/
|
|
3736
|
+
declare class RlsRequiredError extends LunoraError {
|
|
3349
3737
|
readonly table: string;
|
|
3350
3738
|
constructor(table: string);
|
|
3351
3739
|
}
|
|
@@ -3480,43 +3868,6 @@ declare const MIN_AUTH_SECRET_LENGTH = 32;
|
|
|
3480
3868
|
* a *missing* token is never itself a finding here (introspection is simply off).
|
|
3481
3869
|
*/
|
|
3482
3870
|
declare const buildSecurityAudit: (rawEnv: unknown) => SecurityAuditResult;
|
|
3483
|
-
/**
|
|
3484
|
-
* Durable Object that owns auth session state.
|
|
3485
|
-
*
|
|
3486
|
-
* `@lunora/auth` used to write sessions directly into D1 alongside user
|
|
3487
|
-
* records. That worked but coupled session lifecycle to a global database —
|
|
3488
|
-
* every read had to cross the region, every write contended with user
|
|
3489
|
-
* inserts. SessionDO owns sessions in a DO-local KV store: same-prefix
|
|
3490
|
-
* tokens co-locate (via `idFromName(token.slice(0, 16))`) so the DO instance
|
|
3491
|
-
* count stays bounded; reads and writes never round-trip to D1.
|
|
3492
|
-
*
|
|
3493
|
-
* Wire shape: HTTP only, never RPC. The auth package calls
|
|
3494
|
-
*
|
|
3495
|
-
* `await env.SESSION.get(env.SESSION.idFromName(prefix)).fetch(...)`
|
|
3496
|
-
*
|
|
3497
|
-
* with one of:
|
|
3498
|
-
*
|
|
3499
|
-
* POST /create body: { token, userId, ttlSeconds }
|
|
3500
|
-
* GET /get header: `x-lunora-session-token: <token>`
|
|
3501
|
-
* DELETE /revoke header: `x-lunora-session-token: <token>`
|
|
3502
|
-
*
|
|
3503
|
-
* Every request must additionally carry an `x-lunora-session-secret` header
|
|
3504
|
-
* whose value matches `env.SESSION_DO_SECRET`. The DO is reachable from any
|
|
3505
|
-
* worker bound to its namespace, so a shared secret is the only thing that
|
|
3506
|
-
* prevents a compromised or misbehaving worker from reading arbitrary
|
|
3507
|
-
* sessions — the binding alone is not an auth surface.
|
|
3508
|
-
*
|
|
3509
|
-
* The DO returns JSON bodies that `@lunora/auth` reshapes into its public
|
|
3510
|
-
* `AuthSession` type. Keep the surface narrow — anything more elaborate
|
|
3511
|
-
* should ride on top via a wrapper, not by widening this contract.
|
|
3512
|
-
*
|
|
3513
|
-
* # Subclassing
|
|
3514
|
-
*
|
|
3515
|
-
* Apps subclass `SessionDO` (or use the codegen subclass) and register the
|
|
3516
|
-
* subclass in `wrangler.jsonc` as `SESSION`. The platform DO binding requires
|
|
3517
|
-
* a concrete `DurableObject` class today; the structural state shape used by
|
|
3518
|
-
* the unit tests is preserved so plain-object doubles still work.
|
|
3519
|
-
*/
|
|
3520
3871
|
/** Default TTL for new sessions (7 days), matching `@lunora/auth`. */
|
|
3521
3872
|
declare const SESSION_DO_TTL_DEFAULT: number;
|
|
3522
3873
|
/** Hard ceiling on the requested TTL — 90 days. Longer sessions should ride on top via refresh. */
|
|
@@ -3582,6 +3933,44 @@ declare class SessionDO {
|
|
|
3582
3933
|
private handleRevoke;
|
|
3583
3934
|
}
|
|
3584
3935
|
/**
|
|
3936
|
+
* Diff the previously-sent list snapshot (`previousJson`, the memo's
|
|
3937
|
+
* `lastJson`) against the new query result and produce per-row
|
|
3938
|
+
* {@link MutationDelta}s the client can merge in place via `applyDelta` —
|
|
3939
|
+
* Convex-parity live-pagination deltas (server half of gap #20).
|
|
3940
|
+
*
|
|
3941
|
+
* Returns `undefined` (caller falls back to a full `{type:"data"}` snapshot)
|
|
3942
|
+
* unless ALL of these hold:
|
|
3943
|
+
*
|
|
3944
|
+
* 1. `previousJson` parses to an array (there IS a previous list to diff against).
|
|
3945
|
+
* 2. `nextResult` is also an array.
|
|
3946
|
+
* 3. Every row in both arrays is a plain object carrying a string `_id`.
|
|
3947
|
+
* 4. Order preservation — rows present in BOTH arrays appear in the same relative order.
|
|
3948
|
+
* 5. Chattiness cap — the number of deltas does not exceed the new array length (a near-total change is cheaper as a snapshot).
|
|
3949
|
+
*
|
|
3950
|
+
* Diff is keyed by `_id`: rows only in prev → `delete`; rows only in next →
|
|
3951
|
+
* `insert`; rows in both whose JSON differs → `update`. Insert/update carry the
|
|
3952
|
+
* full new `row`; delete omits it (matching the wire contract `@lunora/client`
|
|
3953
|
+
* parses). Deltas are ordered deletes-then-inserts/updates so the client never
|
|
3954
|
+
* sees a transient over-length page.
|
|
3955
|
+
*
|
|
3956
|
+
* Per-row serialization is done exactly **once** per refresh (finding #6). Each
|
|
3957
|
+
* row is stringified a single time into a fingerprint reused for both the
|
|
3958
|
+
* `prev !== next` change-detection compare and — when the caller passes the
|
|
3959
|
+
* optional `frames` sink — the pre-serialized delta frame body. The returned
|
|
3960
|
+
* `MutationDelta[]` shape is unchanged; `frames`, when supplied, receives the
|
|
3961
|
+
* exact `JSON.stringify(delta)` string for each returned delta, in the same
|
|
3962
|
+
* order, so the caller can splice it straight into the `{type:"delta"}` frame
|
|
3963
|
+
* without serializing the delta (and the row inside it) a second time.
|
|
3964
|
+
* @returns the per-row deltas to send, or `undefined` when any precondition fails and a full snapshot should be sent instead
|
|
3965
|
+
*/
|
|
3966
|
+
declare const subscriptionListDeltas: (previousJson: string, nextResult: unknown, table: string, frames?: string[]) => MutationDelta[] | undefined;
|
|
3967
|
+
/**
|
|
3968
|
+
* Send one WebSocket frame, reporting whether it left the socket. A throw from
|
|
3969
|
+
* `ws.send` (socket closed mid-flush, outbound buffer gone) is the only
|
|
3970
|
+
* delivery-failure signal the runtime exposes; callers use the boolean to decide
|
|
3971
|
+
* whether to advance a subscription's delivered-diff baseline.
|
|
3972
|
+
*/
|
|
3973
|
+
/**
|
|
3585
3974
|
* Optional programmatic log sink, resolved from `createShardDO({ observability })`.
|
|
3586
3975
|
* Structurally a subset of `@lunora/runtime`'s `ObservabilitySink`, so a user can
|
|
3587
3976
|
* pass the SAME sink object to `createWorker` (which drives `onRpc`) and
|
|
@@ -3634,6 +4023,13 @@ interface ShardDOState {
|
|
|
3634
4023
|
getCurrentBookmark?: () => Promise<string>;
|
|
3635
4024
|
/** Native PITR: arm a restore to `bookmark` on next restart; returns the undo bookmark. */
|
|
3636
4025
|
onNextSessionRestoreBookmark?: (bookmark: string) => Promise<string>;
|
|
4026
|
+
/**
|
|
4027
|
+
* Arm the DO's single alarm to fire at `scheduledTime` (ms epoch),
|
|
4028
|
+
* waking {@link ShardDO.alarm}. Used by the global-shape poll loop.
|
|
4029
|
+
* Optional: present on the real runtime, absent in the unit harness
|
|
4030
|
+
* (where the poll loop degrades to seed-only).
|
|
4031
|
+
*/
|
|
4032
|
+
setAlarm?: (scheduledTime: Date | number) => Promise<void>;
|
|
3637
4033
|
sql: {
|
|
3638
4034
|
[key: string]: unknown;
|
|
3639
4035
|
/**
|
|
@@ -3678,34 +4074,15 @@ interface SubscriptionOutcome {
|
|
|
3678
4074
|
tables: Set<string>;
|
|
3679
4075
|
}
|
|
3680
4076
|
/**
|
|
3681
|
-
*
|
|
3682
|
-
* `
|
|
3683
|
-
*
|
|
3684
|
-
*
|
|
3685
|
-
* The value passed is the socket's OWN verified identity, captured at the WS
|
|
3686
|
-
* upgrade from the runtime-forwarded, server-minted `x-lunora-userid` /
|
|
3687
|
-
* `x-lunora-identity` headers (the client cannot forge them — the runtime
|
|
3688
|
-
* strips any client-supplied copies) and stamped on the {@link SocketAttachment}.
|
|
3689
|
-
* `seedSubscription` and `refreshSubscriptions` read it off the attachment and
|
|
3690
|
-
* pass it here BY VALUE — never by reading the mutable per-request
|
|
3691
|
-
* `currentRequestUserId`/`currentRequestIdentity` instance fields, which a
|
|
3692
|
-
* deferred (`waitUntil`) refresh or a concurrently-interleaved RPC could be
|
|
3693
|
-
* mutating. That value-passing is what keeps a subscription re-run from
|
|
3694
|
-
* observing or clobbering an in-flight RPC's identity.
|
|
3695
|
-
*
|
|
3696
|
-
* Developer-facing consequence: a query that authorizes or filters on the
|
|
3697
|
-
* caller's identity — via `.use(rls(...))` or by reading `ctx.auth.userId` —
|
|
3698
|
-
* evaluates over the live channel under the CONNECTING user, so its seed and
|
|
3699
|
-
* every write-driven refresh return that user's rows, matching the one-shot
|
|
3700
|
-
* `fetch` RPC. An anonymous socket (no identity resolved at upgrade) leaves
|
|
3701
|
-
* both fields `undefined`, so such a query fails closed (empty/denied) rather
|
|
3702
|
-
* than leaking another user's data. See the lunora-realtime skill
|
|
3703
|
-
* ("Authorization & live queries").
|
|
4077
|
+
* Classification of a watermarked custom-mutator push against the shard's
|
|
4078
|
+
* `__client_watermark`: `expected` is the next in-order sequence, `kind`
|
|
4079
|
+
* whether the push is a replay (`"already"`), the next one (`"next"`), or an
|
|
4080
|
+
* out-of-order arrival (`"gap"`).
|
|
3704
4081
|
*/
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
}
|
|
4082
|
+
type ClientMutationClass = {
|
|
4083
|
+
expected: number;
|
|
4084
|
+
kind: "already" | "gap" | "next";
|
|
4085
|
+
};
|
|
3709
4086
|
/**
|
|
3710
4087
|
* Optional shard-level configuration passed through `super(state, env, …)`.
|
|
3711
4088
|
* Reserved as a bag rather than positional args so subclasses don't break
|
|
@@ -3834,38 +4211,6 @@ interface RunShardRankPageArgs {
|
|
|
3834
4211
|
take?: number;
|
|
3835
4212
|
}
|
|
3836
4213
|
/**
|
|
3837
|
-
* Diff the previously-sent list snapshot (`previousJson`, the memo's
|
|
3838
|
-
* `lastJson`) against the new query result and produce per-row
|
|
3839
|
-
* {@link MutationDelta}s the client can merge in place via `applyDelta` —
|
|
3840
|
-
* Convex-parity live-pagination deltas (server half of gap #20).
|
|
3841
|
-
*
|
|
3842
|
-
* Returns `undefined` (caller falls back to a full `{type:"data"}` snapshot)
|
|
3843
|
-
* unless ALL of these hold:
|
|
3844
|
-
*
|
|
3845
|
-
* 1. `previousJson` parses to an array (there IS a previous list to diff against).
|
|
3846
|
-
* 2. `nextResult` is also an array.
|
|
3847
|
-
* 3. Every row in both arrays is a plain object carrying a string `_id`.
|
|
3848
|
-
* 4. Order preservation — rows present in BOTH arrays appear in the same relative order.
|
|
3849
|
-
* 5. Chattiness cap — the number of deltas does not exceed the new array length (a near-total change is cheaper as a snapshot).
|
|
3850
|
-
*
|
|
3851
|
-
* Diff is keyed by `_id`: rows only in prev → `delete`; rows only in next →
|
|
3852
|
-
* `insert`; rows in both whose JSON differs → `update`. Insert/update carry the
|
|
3853
|
-
* full new `row`; delete omits it (matching the wire contract `@lunora/client`
|
|
3854
|
-
* parses). Deltas are ordered deletes-then-inserts/updates so the client never
|
|
3855
|
-
* sees a transient over-length page.
|
|
3856
|
-
*
|
|
3857
|
-
* Per-row serialization is done exactly **once** per refresh (finding #6). Each
|
|
3858
|
-
* row is stringified a single time into a fingerprint reused for both the
|
|
3859
|
-
* `prev !== next` change-detection compare and — when the caller passes the
|
|
3860
|
-
* optional `frames` sink — the pre-serialized delta frame body. The returned
|
|
3861
|
-
* `MutationDelta[]` shape is unchanged; `frames`, when supplied, receives the
|
|
3862
|
-
* exact `JSON.stringify(delta)` string for each returned delta, in the same
|
|
3863
|
-
* order, so the caller can splice it straight into the `{type:"delta"}` frame
|
|
3864
|
-
* without serializing the delta (and the row inside it) a second time.
|
|
3865
|
-
* @returns the per-row deltas to send, or `undefined` when any precondition fails and a full snapshot should be sent instead
|
|
3866
|
-
*/
|
|
3867
|
-
declare const subscriptionListDeltas: (previousJson: string, nextResult: unknown, table: string, frames?: string[]) => MutationDelta[] | undefined;
|
|
3868
|
-
/**
|
|
3869
4214
|
* Threshold at which a `__root__` DO triggers the size warning. 1 GiB —
|
|
3870
4215
|
* exactly 10% of the 10 GiB per-DO SQLite ceiling, leaving plenty of runway
|
|
3871
4216
|
* to plan a `.shardBy()` migration before the wall hits.
|
|
@@ -3918,6 +4263,28 @@ declare abstract class ShardDO {
|
|
|
3918
4263
|
*/
|
|
3919
4264
|
protected static readonly MAX_SUBSCRIPTIONS_PER_SOCKET = 32;
|
|
3920
4265
|
/**
|
|
4266
|
+
* Poll interval (ms) for `.global()`-table shapes. A global table lives in
|
|
4267
|
+
* D1 with no per-DO op-log, so its shapes can't be poke-live; the DO re-reads
|
|
4268
|
+
* each subscribed global shape's membership from D1 on an alarm every
|
|
4269
|
+
* `GLOBAL_SHAPE_POLL_INTERVAL_MS` and pokes only the diff. This is the
|
|
4270
|
+
* latency floor for a global-shape update — deliberately coarse (seconds, not
|
|
4271
|
+
* the sub-millisecond poke-live path) since the D1 read fans out per tick.
|
|
4272
|
+
*/
|
|
4273
|
+
protected static readonly GLOBAL_SHAPE_POLL_INTERVAL_MS = 2e3;
|
|
4274
|
+
/**
|
|
4275
|
+
* Upper bound on a `.global()`-shape's materialized membership. Each global
|
|
4276
|
+
* shape keeps its ENTIRE current membership as a per-socket snapshot
|
|
4277
|
+
* (`Map<rowKey, hash>`) so the poll loop can diff it; that snapshot — and the
|
|
4278
|
+
* read buffer feeding it — scale with the membership size, multiplied by every
|
|
4279
|
+
* subscribed socket. An unbounded membership (a global table with no narrowing
|
|
4280
|
+
* shape predicate or RLS read scope) would grow them without limit and evict
|
|
4281
|
+
* the DO. A shape whose membership exceeds this cap is failed closed (left
|
|
4282
|
+
* empty, logged) rather than retained — the developer must narrow it. Sized
|
|
4283
|
+
* well above any reasonable per-identity replicated set so legitimate shapes
|
|
4284
|
+
* never trip it.
|
|
4285
|
+
*/
|
|
4286
|
+
protected static readonly GLOBAL_SHAPE_MAX_ROWS = 5e4;
|
|
4287
|
+
/**
|
|
3921
4288
|
* Per-socket whisper-topic cap. Topic membership rides the same hibernation
|
|
3922
4289
|
* attachment as `subs`, so bound it for the same reason — a runaway
|
|
3923
4290
|
* `whisper_subscribe` loop must not wedge the attachment past the runtime's
|
|
@@ -4006,6 +4373,8 @@ declare abstract class ShardDO {
|
|
|
4006
4373
|
* cleared in the `finally` block of `fetch` like the other per-request fields.
|
|
4007
4374
|
*/
|
|
4008
4375
|
private currentRequestIp;
|
|
4376
|
+
/** W3C `traceparent` of the inbound RPC; forwarded onto outbound container fetches. */
|
|
4377
|
+
private currentRequestTraceparent;
|
|
4009
4378
|
/**
|
|
4010
4379
|
* Client-issued idempotency key for the in-flight mutation, forwarded via the
|
|
4011
4380
|
* `x-lunora-mutation-id` header. When set, the dispatch path dedups the call
|
|
@@ -4017,6 +4386,39 @@ declare abstract class ShardDO {
|
|
|
4017
4386
|
*/
|
|
4018
4387
|
private currentRequestMutationId;
|
|
4019
4388
|
/**
|
|
4389
|
+
* Stable per-device client id for the in-flight custom-mutator push,
|
|
4390
|
+
* forwarded via the `x-lunora-client-id` header. Backs the
|
|
4391
|
+
* `__client_watermark` table: the dispatch path classifies the paired
|
|
4392
|
+
* `currentRequestClientSeq` against the stored high-watermark (already
|
|
4393
|
+
* processed / next / out-of-order gap). Absent on legacy mutations and
|
|
4394
|
+
* queries (those keep the `__idempotency` path). Cleared in `fetch`'s
|
|
4395
|
+
* `finally`.
|
|
4396
|
+
*/
|
|
4397
|
+
private currentRequestClientId;
|
|
4398
|
+
/**
|
|
4399
|
+
* Monotonic per-client mutation sequence for the in-flight custom-mutator
|
|
4400
|
+
* push, forwarded via the `x-lunora-client-seq` header (numeric). Paired
|
|
4401
|
+
* with `currentRequestClientId` to drive the watermark classification.
|
|
4402
|
+
* `undefined` when absent or non-numeric.
|
|
4403
|
+
*/
|
|
4404
|
+
private currentRequestClientSeq;
|
|
4405
|
+
/**
|
|
4406
|
+
* The in-flight push's custom-mutator classification, stashed by `fetch`
|
|
4407
|
+
* before `handleRpc` so the in-transaction bookkeeping ({@link
|
|
4408
|
+
* ShardDO.commitMutationBookkeeping}) can advance the `__client_watermark` for
|
|
4409
|
+
* a `"next"` push inside the same commit as the writes. `undefined` for an
|
|
4410
|
+
* ordinary mutation / non-mutator push. Cleared per request.
|
|
4411
|
+
*/
|
|
4412
|
+
private currentMutatorClass;
|
|
4413
|
+
/**
|
|
4414
|
+
* Set once a mutation's replay bookkeeping (idempotency row + watermark
|
|
4415
|
+
* advance) has committed INSIDE the handler transaction, so the post-dispatch
|
|
4416
|
+
* path skips the now-redundant best-effort writes. Cleared per request; stays
|
|
4417
|
+
* `false` for actions/queries (no transaction wrapper) so their dispatch-level
|
|
4418
|
+
* idempotency persist still runs.
|
|
4419
|
+
*/
|
|
4420
|
+
private mutationBookkeepingCommitted;
|
|
4421
|
+
/**
|
|
4020
4422
|
* Wall-clock millis of the last `__idempotency` GC sweep on this warm
|
|
4021
4423
|
* instance. The dedup write throttles `trimIdempotent` to at most once an
|
|
4022
4424
|
* hour off this field (in-memory, so a fresh instance just sweeps on its
|
|
@@ -4047,6 +4449,18 @@ declare abstract class ShardDO {
|
|
|
4047
4449
|
*/
|
|
4048
4450
|
private pendingChangedTables;
|
|
4049
4451
|
/**
|
|
4452
|
+
* Coalesced set of tables awaiting a subscription-refresh pass, merged
|
|
4453
|
+
* across every {@link ShardDO.flushChangedTables} call that lands while a
|
|
4454
|
+
* pass is already draining. The single drain loop
|
|
4455
|
+
* ({@link ShardDO.drainSubscriptionRefreshes}) owns this set; a burst of N
|
|
4456
|
+
* writes to the same table therefore collapses into one (or two) refresh
|
|
4457
|
+
* passes instead of N, so each affected subscription's handler re-runs once
|
|
4458
|
+
* per burst rather than once per write. `undefined` when nothing is pending.
|
|
4459
|
+
*/
|
|
4460
|
+
private pendingRefreshTables;
|
|
4461
|
+
/** True while {@link ShardDO.drainSubscriptionRefreshes} is running; the single-waiter gate that coalesces concurrent flushes. */
|
|
4462
|
+
private refreshInFlight;
|
|
4463
|
+
/**
|
|
4050
4464
|
* Last pushed result per `(socket, subId)`, keyed by socket. Lets
|
|
4051
4465
|
* `refreshSubscriptions` skip re-running queries whose tables were
|
|
4052
4466
|
* untouched and suppress pushes when the re-run result is unchanged. Held
|
|
@@ -4054,6 +4468,39 @@ declare abstract class ShardDO {
|
|
|
4054
4468
|
* memo simply forces one re-run and (at most) one redundant push.
|
|
4055
4469
|
*/
|
|
4056
4470
|
private readonly subMemos;
|
|
4471
|
+
/**
|
|
4472
|
+
* Per-socket poke baseline for shape subscriptions: maps each shape's
|
|
4473
|
+
* subscription id to the `__cdc_log` cursor it has been poked through.
|
|
4474
|
+
* `pokeShapeSubscribers` reads each op page since this cursor and advances
|
|
4475
|
+
* it to the flush watermark. In-memory only (like {@link ShardDO.subMemos});
|
|
4476
|
+
* a cold memo on a reconnected/hibernated socket re-seeds from the client's
|
|
4477
|
+
* `sinceCheckpoint`.
|
|
4478
|
+
*/
|
|
4479
|
+
private readonly shapeMemos;
|
|
4480
|
+
/**
|
|
4481
|
+
* Per-socket, per-**global**-shape membership snapshot: maps each global
|
|
4482
|
+
* shape's subscription id to a `key → projected-value JSON` map of the rows
|
|
4483
|
+
* last poked to that socket. A `.global()` (D1) table has no op-log to diff,
|
|
4484
|
+
* so {@link ShardDO.refreshGlobalShape} re-reads the full membership on each
|
|
4485
|
+
* alarm tick and diffs it against this snapshot to compute the poke. Parallel
|
|
4486
|
+
* to {@link ShardDO.shapeMemos} (the cursor baseline for poke-live shapes).
|
|
4487
|
+
*
|
|
4488
|
+
* This is a hot in-memory **cache** over the durable `__global_shape_snapshot`
|
|
4489
|
+
* table (keyed by the socket's `connectionId` + subId): a hibernation eviction
|
|
4490
|
+
* clears the WeakMap, so on the next alarm wake {@link ShardDO.readGlobalSnapshot}
|
|
4491
|
+
* misses and re-loads the baseline from SQLite — without it, the diff would run
|
|
4492
|
+
* against an empty baseline and a row deleted from D1 while the DO slept would
|
|
4493
|
+
* never be poked as a `delete`, lingering on the client as a phantom row.
|
|
4494
|
+
*/
|
|
4495
|
+
private readonly globalShapeSnapshots;
|
|
4496
|
+
/**
|
|
4497
|
+
* Whether a global-shape poll alarm is currently armed. Guards
|
|
4498
|
+
* {@link ShardDO.scheduleGlobalPoll} from re-arming on every seed; reset in
|
|
4499
|
+
* {@link ShardDO.alarm} before the poll so a still-subscribed shape re-arms.
|
|
4500
|
+
*/
|
|
4501
|
+
private globalPollScheduled;
|
|
4502
|
+
/** Monotonic per-DO poke id source; correlates a poke's `pokeStart`/`pokePart`/`pokeEnd` frames. */
|
|
4503
|
+
private pokeSequence;
|
|
4057
4504
|
/** Per-socket whisper-rate token bucket (see {@link ShardDO.WHISPER_RATE_BURST}). In-memory; resets on hibernation. */
|
|
4058
4505
|
private readonly whisperBuckets;
|
|
4059
4506
|
/**
|
|
@@ -4072,6 +4519,35 @@ declare abstract class ShardDO {
|
|
|
4072
4519
|
*/
|
|
4073
4520
|
private readonly metrics;
|
|
4074
4521
|
/**
|
|
4522
|
+
* Running fan-out cost counters surfaced by the
|
|
4523
|
+
* `__lunora_admin__:getFanoutMetrics` RPC — one tally for the reactive
|
|
4524
|
+
* shape-poke path (`pokeShapeSubscribers`) and one for the whisper broadcast
|
|
4525
|
+
* path (`broadcastWhisper`). Each pass records the sockets it iterated (the
|
|
4526
|
+
* O(subscribers) cost) and delivered to. In-memory and reset on
|
|
4527
|
+
* hibernation/restart, sharing `metrics.sinceMs` as the "since this instance
|
|
4528
|
+
* woke" epoch. This is the observability half of plan 075's auto-elastic
|
|
4529
|
+
* relay tier (Phase 1): measure the per-flush fan-out cost so the promotion
|
|
4530
|
+
* threshold is grounded in real numbers, with no behavior change.
|
|
4531
|
+
*/
|
|
4532
|
+
private readonly fanout;
|
|
4533
|
+
/**
|
|
4534
|
+
* The runtime's Durable Object namespace binding name (e.g. `"SHARD"`),
|
|
4535
|
+
* forwarded as `x-lunora-shard-binding` on every request so a DO can address
|
|
4536
|
+
* its siblings (`this.env[binding].getByName(...)`) for the relay hub. Absent
|
|
4537
|
+
* in single-DO mode / the unit harness — when absent, the relay tier is inert
|
|
4538
|
+
* and whispers stay shard-local (no behavior change). In-memory; re-learned per
|
|
4539
|
+
* request.
|
|
4540
|
+
*/
|
|
4541
|
+
private shardBinding;
|
|
4542
|
+
/**
|
|
4543
|
+
* The auto-elastic fan-out relay collaborator (plan 075) — an {@link OwnerRelay}
|
|
4544
|
+
* or {@link RelayMember} chosen ONCE from this DO's name, or `undefined` for an
|
|
4545
|
+
* unnamed (single-DO) DO where the relay tier is inert. All relay state +
|
|
4546
|
+
* transport lives on it, reached back through the {@link RelayHost} adapter, so
|
|
4547
|
+
* owner-only state can never sit next to relay-only state on this class.
|
|
4548
|
+
*/
|
|
4549
|
+
private readonly relay;
|
|
4550
|
+
/**
|
|
4075
4551
|
* Declared indexes (`table:index`) a query has exercised since this instance
|
|
4076
4552
|
* woke, stamped by `getCtxDbIndexUseHook`. In-memory and reset on
|
|
4077
4553
|
* hibernation/restart — drives the `unused_index` runtime advisory.
|
|
@@ -4172,6 +4648,15 @@ declare abstract class ShardDO {
|
|
|
4172
4648
|
webSocketClose(ws: WebSocket, _code: number, _reason: string, _wasClean: boolean): Promise<void>;
|
|
4173
4649
|
/** Hibernation API: invoked on socket error. */
|
|
4174
4650
|
webSocketError(_ws: WebSocket, _error: unknown): void;
|
|
4651
|
+
/**
|
|
4652
|
+
* Durable Object alarm handler — the heartbeat for `.global()`-table shapes.
|
|
4653
|
+
* The runtime wakes this when the poll alarm armed by `scheduleGlobalPoll`
|
|
4654
|
+
* fires; it refreshes every subscribed global shape (diff-poke from the global
|
|
4655
|
+
* backend) and re-arms while any remain. With no global subscribers left, the
|
|
4656
|
+
* alarm is not re-armed and the DO goes idle. A base-only / global-free DO
|
|
4657
|
+
* never arms it, so this stays dormant there.
|
|
4658
|
+
*/
|
|
4659
|
+
alarm(): Promise<void>;
|
|
4175
4660
|
/** Subclasses implement function dispatch. */
|
|
4176
4661
|
abstract handleRpc(functionPath: string, args: Record<string, unknown>): Promise<unknown>;
|
|
4177
4662
|
/**
|
|
@@ -4300,6 +4785,12 @@ declare abstract class ShardDO {
|
|
|
4300
4785
|
*/
|
|
4301
4786
|
protected getCurrentIp(): string | undefined;
|
|
4302
4787
|
/**
|
|
4788
|
+
* W3C `traceparent` of the inbound RPC (forwarded by the runtime), or
|
|
4789
|
+
* `undefined`. `buildCtx` passes it to `createContainerContext` so outbound
|
|
4790
|
+
* container fetches carry it and the container's spans join the same trace.
|
|
4791
|
+
*/
|
|
4792
|
+
protected getCurrentTraceparent(): string | undefined;
|
|
4793
|
+
/**
|
|
4303
4794
|
* Identity claims (email, name, roles, …) forwarded by the runtime's
|
|
4304
4795
|
* `resolveIdentity` hook. Returns `undefined` for anonymous requests
|
|
4305
4796
|
* or when no extra claims were attached. Use this to populate the
|
|
@@ -4418,6 +4909,35 @@ declare abstract class ShardDO {
|
|
|
4418
4909
|
*/
|
|
4419
4910
|
protected studioFeatures(): StudioFeaturesResult;
|
|
4420
4911
|
/**
|
|
4912
|
+
* Evaluate every statically-discovered feature flag under `context` for the
|
|
4913
|
+
* studio's read-only Flags page (`__lunora_admin__:listFlags`). The flag keys
|
|
4914
|
+
* + value types are discovered by `@lunora/codegen` from the app's
|
|
4915
|
+
* `ctx.flags.<type>("key", …)` reads and evaluated through the configured
|
|
4916
|
+
* `@lunora/flags` provider — work only the codegen subclass can do, so it
|
|
4917
|
+
* overrides this. The base class wires no provider and reports
|
|
4918
|
+
* `configured: false` with zero flags (an un-generated `ShardDO` has none).
|
|
4919
|
+
*/
|
|
4920
|
+
protected evaluateFlags(_context?: Record<string, unknown>): Promise<FlagsResult>;
|
|
4921
|
+
/**
|
|
4922
|
+
* Serve one reserved {@link FLAGS_FUNCTION_PREFIX} live flag read for the
|
|
4923
|
+
* React client's `useFlag`/`useFlags`. `functionPath` carries the flag key +
|
|
4924
|
+
* type and `args` the per-subscriber targeting context; the codegen subclass
|
|
4925
|
+
* overrides this to evaluate the flag through the app's `@lunora/flags`
|
|
4926
|
+
* provider under `identity` and return the resolved value. The base class
|
|
4927
|
+
* wires no provider, so it returns `null` — `resolveReactiveOutcome` reads
|
|
4928
|
+
* `null` as "nothing to deliver" and the subscriber keeps its default.
|
|
4929
|
+
*/
|
|
4930
|
+
protected runFlagSubscriptionRead(_functionPath: string, _arguments: Record<string, unknown>, _identity?: SubscriptionIdentity): Promise<unknown>;
|
|
4931
|
+
/**
|
|
4932
|
+
* The Cloudflare Queues declared by this app, surfaced via
|
|
4933
|
+
* `__lunora_admin__:listQueues` for the studio's Queues page. Queues are NOT
|
|
4934
|
+
* Durable Objects and hold no shard state, so this is pure declaration
|
|
4935
|
+
* metadata statically discovered by `@lunora/codegen` from `lunora/queues.ts`
|
|
4936
|
+
* and emitted into the generated subclass, which overrides this. The base
|
|
4937
|
+
* class can't see the user's project, so it reports none.
|
|
4938
|
+
*/
|
|
4939
|
+
protected queuesMetadata(): QueuesResult;
|
|
4940
|
+
/**
|
|
4421
4941
|
* The Cloudflare Workflows declared by this app, surfaced via
|
|
4422
4942
|
* `__lunora_admin__:listWorkflows` for the studio's Workflows page. Workflows
|
|
4423
4943
|
* are NOT Durable Objects and hold no shard state, so this is pure
|
|
@@ -4580,16 +5100,117 @@ declare abstract class ShardDO {
|
|
|
4580
5100
|
* unless the request carried an `x-lunora-mutation-id` header (queries and
|
|
4581
5101
|
* legacy clients leave `currentRequestMutationId` undefined).
|
|
4582
5102
|
*
|
|
4583
|
-
*
|
|
4584
|
-
*
|
|
4585
|
-
*
|
|
4586
|
-
*
|
|
4587
|
-
*
|
|
4588
|
-
*
|
|
4589
|
-
*
|
|
5103
|
+
* For a mutation this runs INSIDE the handler's transaction (via
|
|
5104
|
+
* {@link ShardDO.commitMutationBookkeeping}, which `handleRpc` invokes before
|
|
5105
|
+
* the transaction commits), so the dedup row is durable iff the writes are —
|
|
5106
|
+
* closing the crash window where the writes commit but the replay guard does
|
|
5107
|
+
* not. Actions/queries aren't transaction-wrapped, so they call this on the
|
|
5108
|
+
* live dispatch path right after the handler resolves, through the same
|
|
5109
|
+
* `this.sql` handle. `INSERT OR IGNORE` keeps a concurrent double-dispatch (or
|
|
5110
|
+
* the now-skipped post-dispatch call) of the same id idempotent. Also runs the
|
|
5111
|
+
* throttled dedup-table GC.
|
|
4590
5112
|
*/
|
|
4591
5113
|
protected persistIdempotentResult(result: unknown): void;
|
|
4592
5114
|
/**
|
|
5115
|
+
* Whether `functionPath` names a registered custom mutator (a `defineMutator`
|
|
5116
|
+
* declaration) rather than an ordinary `mutation`. The base class knows of no
|
|
5117
|
+
* mutators, so the default is `false`; the codegen-generated subclass
|
|
5118
|
+
* overrides this to consult its mutator registry. When `true` (and the push
|
|
5119
|
+
* carries a `clientId`/`clientSeq`), the dispatch path applies the
|
|
5120
|
+
* `__client_watermark` ordering semantics instead of the legacy idempotency
|
|
5121
|
+
* dedup.
|
|
5122
|
+
*/
|
|
5123
|
+
protected isCustomMutator(_functionPath: string): boolean;
|
|
5124
|
+
/**
|
|
5125
|
+
* Classify an in-flight custom-mutator push against the shard's stored
|
|
5126
|
+
* high-watermark for `currentRequestClientId`. The watermark is the highest
|
|
5127
|
+
* per-client sequence the DO has applied, so the push is exactly one of:
|
|
5128
|
+
*
|
|
5129
|
+
* - `"already"` — `seq <= watermark`: a replay of a confirmed (or in-flight,
|
|
5130
|
+
* now-resent) mutation. The handler must NOT re-run; the dispatch path returns
|
|
5131
|
+
* a benign ack so the client drops the pending overlay.
|
|
5132
|
+
* - `"next"` — `seq == watermark + 1`: the next mutation in order. Run the
|
|
5133
|
+
* authoritative `server` impl and advance the watermark in the same commit.
|
|
5134
|
+
* - `"gap"` — `seq > watermark + 1`: an out-of-order arrival (an earlier push
|
|
5135
|
+
* was lost). Halt: the client must resend from `watermark + 1`.
|
|
5136
|
+
*
|
|
5137
|
+
* Returns `undefined` when the push is not a watermarked custom mutator
|
|
5138
|
+
* (missing client id/seq, or a stub `sql` handle without the table) so the
|
|
5139
|
+
* caller falls through to the legacy idempotency path.
|
|
5140
|
+
*/
|
|
5141
|
+
protected classifyClientMutation(): ClientMutationClass | undefined;
|
|
5142
|
+
/**
|
|
5143
|
+
* Terminal response for a watermarked custom-mutator push that is NOT the
|
|
5144
|
+
* next-in-order mutation — an idempotent replay ack (`"already"`) or an
|
|
5145
|
+
* out-of-order halt (`"gap"`). Returns `undefined` for an ordinary mutation
|
|
5146
|
+
* or a `"next"` push so `fetch` proceeds to the authoritative handler. Records
|
|
5147
|
+
* the function call on the short-circuit paths so metrics stay attributed.
|
|
5148
|
+
*/
|
|
5149
|
+
protected rejectNonNextMutation(functionPath: string, mutatorClass: ClientMutationClass | undefined, dispatchStartedAt: number): Response | undefined;
|
|
5150
|
+
/**
|
|
5151
|
+
* Respond to a dispatch that hit the `(identity, mutationId)` idempotency
|
|
5152
|
+
* cache. Records the (zero-work) function call, then: for a `"next"` custom
|
|
5153
|
+
* mutator whose handler already committed but whose watermark advance was
|
|
5154
|
+
* lost to a crash in between, re-advance and echo `lastMutationId` exactly as
|
|
5155
|
+
* the post-commit path does (otherwise the cached branch returns a bare
|
|
5156
|
+
* result with a stale watermark and the client reports every later seq as a
|
|
5157
|
+
* gap forever); for everything else, return the bare cached `{ result }`.
|
|
5158
|
+
*/
|
|
5159
|
+
protected respondFromIdempotencyCache(functionPath: string, dispatchStartedAt: number, mutatorClass: ClientMutationClass | undefined, cachedValue: unknown): Response;
|
|
5160
|
+
/**
|
|
5161
|
+
* The CDC cursor a just-committed plain mutation landed at — the post-write
|
|
5162
|
+
* high-watermark, on the same scale as the `cursor` on `data`/`delta` frames.
|
|
5163
|
+
* The client drops a pending per-call optimistic overlay once it sees a frame
|
|
5164
|
+
* with `cursor >= commitCursor` (gapless reconciliation that keeps mutations
|
|
5165
|
+
* concurrent, without the serialized custom-mutator watermark). Scoped to a
|
|
5166
|
+
* mutation (carries an `x-lunora-mutation-id`) on a CDC-enabled shard;
|
|
5167
|
+
* `undefined` otherwise, leaving the wire byte-identical for queries/actions
|
|
5168
|
+
* and CDC-off shards.
|
|
5169
|
+
*/
|
|
5170
|
+
protected mutationCommitCursor(): number | undefined;
|
|
5171
|
+
/**
|
|
5172
|
+
* Build the success response for a dispatched RPC. A `"next"` custom-mutator
|
|
5173
|
+
* push echoes the applied `lastMutationId` so the client drops the pending
|
|
5174
|
+
* optimistic overlay as soon as the ack lands; a plain mutation echoes
|
|
5175
|
+
* `commitCursor` (see {@link mutationCommitCursor}); other calls return the
|
|
5176
|
+
* bare `{ result }` envelope unchanged.
|
|
5177
|
+
*/
|
|
5178
|
+
protected buildDispatchResponse(mutatorClass: ClientMutationClass | undefined, result: unknown): Response;
|
|
5179
|
+
/**
|
|
5180
|
+
* Commit a mutation's replay bookkeeping — the `(identity, mutationId)`
|
|
5181
|
+
* idempotency dedup row and, for a `"next"` custom-mutator push, the
|
|
5182
|
+
* `__client_watermark` advance — INSIDE the handler's transaction. Called by
|
|
5183
|
+
* the generated `handleRpc` mutation branch after the user handler resolves
|
|
5184
|
+
* but before the transaction commits, so the writes, the dedup row, and the
|
|
5185
|
+
* watermark land in one atomic commit: a crash can't leave the writes durable
|
|
5186
|
+
* without the replay guard (which a re-dispatch would otherwise re-run) nor
|
|
5187
|
+
* without the watermark. Sets {@link ShardDO.mutationBookkeepingCommitted} so
|
|
5188
|
+
* `fetch` skips the redundant post-dispatch persist.
|
|
5189
|
+
*/
|
|
5190
|
+
protected commitMutationBookkeeping(result: unknown): void;
|
|
5191
|
+
/**
|
|
5192
|
+
* Best-effort replay bookkeeping for the live dispatch path, run after
|
|
5193
|
+
* `handleRpc` returns. A generated mutation already committed it atomically
|
|
5194
|
+
* inside its transaction (via {@link ShardDO.commitMutationBookkeeping}, which
|
|
5195
|
+
* sets the flag), so this skips. Actions/queries aren't transaction-wrapped,
|
|
5196
|
+
* so they record their dedup row here (a no-op without an `x-lunora-mutation-id`),
|
|
5197
|
+
* and a `"next"` push advances its watermark (the gap self-heals on replay).
|
|
5198
|
+
*/
|
|
5199
|
+
protected recordPostDispatchBookkeeping(result: unknown, mutatorClass: ClientMutationClass | undefined): void;
|
|
5200
|
+
/**
|
|
5201
|
+
* Advance the stored high-watermark for the in-flight custom mutator to
|
|
5202
|
+
* `currentRequestClientSeq` through the same `this.sql` handle. On the
|
|
5203
|
+
* transactional path ({@link ShardDO.commitMutationBookkeeping}, `strict`) it
|
|
5204
|
+
* runs inside the handler's commit, so the watermark is durable iff the writes
|
|
5205
|
+
* are; a failure rethrows to roll the mutation back. On the best-effort
|
|
5206
|
+
* cache-hit recovery path (`strict` omitted) a missing table is swallowed —
|
|
5207
|
+
* the replay re-runs and re-advances (the read side treats a missing row as
|
|
5208
|
+
* watermark 0), so the gap self-heals.
|
|
5209
|
+
*/
|
|
5210
|
+
protected advanceClientMutationWatermark(options?: {
|
|
5211
|
+
strict?: boolean;
|
|
5212
|
+
}): void;
|
|
5213
|
+
/**
|
|
4593
5214
|
* Replay a batch of CDC changes into this shard (point-in-time recovery).
|
|
4594
5215
|
* Schema-aware — it builds a `createShardCtxDb` writer — so the base class
|
|
4595
5216
|
* can't implement it; the codegen-generated subclass overrides this to call
|
|
@@ -4608,6 +5229,17 @@ declare abstract class ShardDO {
|
|
|
4608
5229
|
protected subscribe(ws: WebSocket, subId: string, query: SubscriptionQuery): "ok" | "serialize_failed" | "too_many";
|
|
4609
5230
|
protected unsubscribe(ws: WebSocket, subId: string): void;
|
|
4610
5231
|
/**
|
|
5232
|
+
* Register a live shape subscription on a socket — the partial-replication
|
|
5233
|
+
* parallel to {@link ShardDO.subscribe}. Stores the descriptor in the
|
|
5234
|
+
* attachment's `shapes` registry (created lazily) so it survives
|
|
5235
|
+
* hibernation, sharing the per-socket cap with `subs`. Returns a status the
|
|
5236
|
+
* caller surfaces as a structured error frame; never throws (a thrown
|
|
5237
|
+
* `webSocketMessage` is a fatal-channel error under the hibernation API).
|
|
5238
|
+
*/
|
|
5239
|
+
protected shapeSubscribe(ws: WebSocket, subId: string, shape: ShapeSubscriptionQuery): "ok" | "serialize_failed" | "too_many";
|
|
5240
|
+
/** Remove a shape subscription and its poke baseline. Mirrors {@link ShardDO.unsubscribe}'s rollback-on-serialize-failure contract. */
|
|
5241
|
+
protected shapeUnsubscribe(ws: WebSocket, subId: string): void;
|
|
5242
|
+
/**
|
|
4611
5243
|
* Decide whether a single subscription is interested in a mutation
|
|
4612
5244
|
* delta. The default implementation checks the table name, then runs a
|
|
4613
5245
|
* shallow-equality predicate over `query.args` against `delta.row`. A
|
|
@@ -4645,6 +5277,74 @@ declare abstract class ShardDO {
|
|
|
4645
5277
|
*/
|
|
4646
5278
|
protected executeSubscription(_functionPath: string, _args: Record<string, unknown>, _identity?: SubscriptionIdentity): Promise<SubscriptionOutcome | null>;
|
|
4647
5279
|
/**
|
|
5280
|
+
* Resolve a named shape to its concrete query plan for `identity`. The base
|
|
5281
|
+
* class has no shape registry, so it returns `undefined` — partial
|
|
5282
|
+
* replication is disabled and a `shape_subscribe` is rejected. The
|
|
5283
|
+
* codegen-generated subclass overrides this to look the shape up in the
|
|
5284
|
+
* project's `defineShape` registry, evaluate its `where(ctx, args)` under the
|
|
5285
|
+
* subscriber's verified identity, and AND-compose it with the table's RLS
|
|
5286
|
+
* read base-where into {@link ResolvedShape.effectiveWhere}.
|
|
5287
|
+
*
|
|
5288
|
+
* `identity` is the socket's OWN verified identity (the same unforgeable
|
|
5289
|
+
* value `refreshSubscriptions` threads), passed by value so this never reads
|
|
5290
|
+
* the mutable per-request identity fields. Returning `undefined` is the
|
|
5291
|
+
* fail-closed signal — an unknown shape, or an RLS-required table with no
|
|
5292
|
+
* policy resolving for this identity, yields no subscription rather than
|
|
5293
|
+
* leaking rows.
|
|
5294
|
+
*/
|
|
5295
|
+
protected resolveShape(_name: string, _args: Record<string, unknown>, _identity?: SubscriptionIdentity): ResolvedShape | undefined;
|
|
5296
|
+
/**
|
|
5297
|
+
* The RLS-uniform gate (plan 075 Phase 3): whether a reactive shape may be
|
|
5298
|
+
* relay-multicast — i.e. one delta is correct for **every** subscriber. The owner
|
|
5299
|
+
* decides it (see {@link OwnerRelay.isShapeRelayUniform} — a static RLS read-policy
|
|
5300
|
+
* guard plus claim-exhaustive `Proxy` probes, fail-closed); this thin delegation
|
|
5301
|
+
* is the seam the gate test exercises. A non-owner DO is never relay-uniform.
|
|
5302
|
+
*/
|
|
5303
|
+
protected isShapeRelayUniform(name: string, args: Record<string, unknown>): boolean;
|
|
5304
|
+
/**
|
|
5305
|
+
* Read the FULL current membership of a `.global()`-table shape from its D1
|
|
5306
|
+
* (or Hyperdrive) backend — the seed/poll source for the latency-tiered
|
|
5307
|
+
* global shape path. A `.global()` table lives in another store with no
|
|
5308
|
+
* per-DO op-log, so this is the only way to learn its rows from inside the
|
|
5309
|
+
* shard DO; {@link ShardDO.seedGlobalShape} calls it once on subscribe and
|
|
5310
|
+
* {@link ShardDO.refreshGlobalShape} on every alarm tick, diffing the result
|
|
5311
|
+
* against the per-socket snapshot to compute the poke.
|
|
5312
|
+
*
|
|
5313
|
+
* The base class has no global backend, so it returns `[]` (a base-only DO,
|
|
5314
|
+
* or a project with no global tables, never resolves a global shape). The
|
|
5315
|
+
* codegen subclass overrides it to drain `globalDb.findMany(table, { where:
|
|
5316
|
+
* effectiveWhere })` under the socket's verified `identity` — the same
|
|
5317
|
+
* unforgeable value `resolveShape` composed the RLS predicate with, so the
|
|
5318
|
+
* D1 read is identity-scoped exactly like the poke-live path.
|
|
5319
|
+
*/
|
|
5320
|
+
protected readGlobalShapeRows(_resolved: ResolvedShape, _identity?: SubscriptionIdentity): Promise<ShapeRow[]>;
|
|
5321
|
+
/**
|
|
5322
|
+
* Poll external-source (`.source(...)`) tables once (plan 077): materialize
|
|
5323
|
+
* each sourced table's freshly-pulled tenant slice into this DO's SQLite. The
|
|
5324
|
+
* base `ShardDO` has no sourced tables, so it returns `0` and the ingest tier
|
|
5325
|
+
* stays dormant — zero behavior change for every existing DO. The codegen
|
|
5326
|
+
* subclass overrides it to, per sourced table, build a `createShardCtxDb`
|
|
5327
|
+
* writer, read the tenant slice from Hyperdrive under this DO's shard key, and
|
|
5328
|
+
* run `runExternalSourceTick` (read local baseline → diff → apply via the
|
|
5329
|
+
* validated CDC writer). Returns the number of sourced tables still being
|
|
5330
|
+
* polled, so the shared poll alarm ({@link ShardDO.alarm}) re-arms while ingest
|
|
5331
|
+
* is active.
|
|
5332
|
+
*/
|
|
5333
|
+
protected pollExternalSources(): Promise<number>;
|
|
5334
|
+
/**
|
|
5335
|
+
* Arm the shared poll alarm for external-source ingest (plan 077). The alarm is
|
|
5336
|
+
* shared with the global-shape poll tier; the codegen subclass calls this once
|
|
5337
|
+
* (on construction / first sourced write) so a sourced DO starts its ingest
|
|
5338
|
+
* loop, after which {@link ShardDO.alarm} re-arms itself while
|
|
5339
|
+
* {@link ShardDO.pollExternalSources} reports remaining work. Idempotent; a
|
|
5340
|
+
* no-op when the runtime exposes no `setAlarm` (unit harness).
|
|
5341
|
+
*/
|
|
5342
|
+
protected scheduleSourcePoll(): Promise<void>;
|
|
5343
|
+
/** This DO's shard key (its DO name), or `__root__` for the single-DO default. The `tenantBy` mapper binds it into the source query. */
|
|
5344
|
+
protected currentShardKey(): string;
|
|
5345
|
+
/** Record a contained external-source ingest failure (one sourced table's poll) into the log ring without aborting the others. */
|
|
5346
|
+
protected recordExternalSourceError(table: string, error: unknown): void;
|
|
5347
|
+
/**
|
|
4648
5348
|
* Look up a streaming-query function and return a thunk that produces the
|
|
4649
5349
|
* `AsyncIterable<unknown>` when handed an {@link AbortSignal}. The codegen
|
|
4650
5350
|
* subclass overrides this to dispatch via `LUNORA_FUNCTIONS`; the base
|
|
@@ -4837,6 +5537,26 @@ declare abstract class ShardDO {
|
|
|
4837
5537
|
*/
|
|
4838
5538
|
private errorToResponse;
|
|
4839
5539
|
/**
|
|
5540
|
+
* Batch dispatch (plan 088). Applies each `calls[]` entry through the SAME
|
|
5541
|
+
* single-call `/rpc` path (via a nested `this.fetch`), **sequentially**, so
|
|
5542
|
+
* the per-`(identity, mutationId)` idempotency dedup and the per-client
|
|
5543
|
+
* `__client_watermark` ordering are enforced entry-by-entry exactly as for an
|
|
5544
|
+
* individual call — no duplication of the dispatch core, no reordering.
|
|
5545
|
+
*
|
|
5546
|
+
* Failures are **per-slot, not fail-fast**: an entry that throws (or a
|
|
5547
|
+
* custom-mutator `OUT_OF_ORDER` gap) is captured in its own result slot and
|
|
5548
|
+
* later entries still run. Ordering is still safe — a later same-client
|
|
5549
|
+
* mutator after a gap re-classifies as a gap too (the watermark never
|
|
5550
|
+
* advanced), so it cannot apply out of order; unrelated entries/queries are
|
|
5551
|
+
* independent. The response is `{ results: [{ id, status, body }] }` in
|
|
5552
|
+
* request order; each `body` is the untouched single-call envelope (its
|
|
5553
|
+
* `result` already wire-encoded), so the client demuxes + decodes each
|
|
5554
|
+
* exactly as one call.
|
|
5555
|
+
*/
|
|
5556
|
+
private handleBatchRpc;
|
|
5557
|
+
/** Dispatch one batch entry through the single-call `/rpc` path and capture its envelope (plan 088). */
|
|
5558
|
+
private dispatchBatchEntry;
|
|
5559
|
+
/**
|
|
4840
5560
|
* Serve a reserved admin-introspection RPC (`__lunora_admin__:*`) for the
|
|
4841
5561
|
* data browser. Gated by `env.LUNORA_ADMIN_TOKEN`: introspection is
|
|
4842
5562
|
* **disabled unless the token is configured**, and when it is, the request
|
|
@@ -4874,6 +5594,14 @@ declare abstract class ShardDO {
|
|
|
4874
5594
|
* the panel renders it alongside `ctx.log` lines. Admin-gated by
|
|
4875
5595
|
* `handleAdminRpc`'s caller (the same `LUNORA_ADMIN_TOKEN` bearer as every
|
|
4876
5596
|
* other admin write).
|
|
5597
|
+
*
|
|
5598
|
+
* An `error`-level lifecycle event (a crash/`onError`, or a non-zero-exit
|
|
5599
|
+
* `stop`) is ALSO appended as an `error`-outcome row to the durable
|
|
5600
|
+
* `__lunora_reqlog__` — the same readout `getIssues` groups over — so a
|
|
5601
|
+
* crash-looping container folds into the Issues list right beside Worker
|
|
5602
|
+
* errors (they share the `fingerprintError` hash over `functionPath ::
|
|
5603
|
+
* bucket(message)`). The in-memory buffer stays the live Logs feed; the
|
|
5604
|
+
* durable row is what survives hibernation for triage.
|
|
4877
5605
|
*/
|
|
4878
5606
|
private handleRecordContainerEvent;
|
|
4879
5607
|
/**
|
|
@@ -4920,6 +5648,16 @@ declare abstract class ShardDO {
|
|
|
4920
5648
|
*/
|
|
4921
5649
|
private handleGetWorkflowInstanceStatus;
|
|
4922
5650
|
/**
|
|
5651
|
+
* Serve `__lunora_admin__:listFlags` — the studio's read-only Flags page.
|
|
5652
|
+
* Evaluates every statically-discovered feature flag under an optional
|
|
5653
|
+
* `args.context` targeting context (the studio's editable context editor)
|
|
5654
|
+
* via the {@link evaluateFlags} hook, which the codegen subclass overrides
|
|
5655
|
+
* with live OpenFeature evaluation. Read-only: a flag lookup mutates no shard
|
|
5656
|
+
* state, so nothing is flushed or audited. Admin-gated by `handleAdminRpc`'s
|
|
5657
|
+
* caller.
|
|
5658
|
+
*/
|
|
5659
|
+
private handleListFlags;
|
|
5660
|
+
/**
|
|
4923
5661
|
* Run `run()` with the per-request identity pinned to (`userId`, `identity`),
|
|
4924
5662
|
* then restore the prior values in a `finally` (even if `run()` throws), so the
|
|
4925
5663
|
* forced identity can never leak into a later dispatch on this DO instance. The
|
|
@@ -4963,6 +5701,54 @@ declare abstract class ShardDO {
|
|
|
4963
5701
|
*/
|
|
4964
5702
|
private handleSendTestMail;
|
|
4965
5703
|
/**
|
|
5704
|
+
* Serve `__lunora_admin__:recordQueueMessage` — the capture sink the generated
|
|
5705
|
+
* worker `queue()` handler (via `@lunora/queue`'s `dispatchQueueBatch`) posts
|
|
5706
|
+
* every consumed message batch to. Records it into the reserved
|
|
5707
|
+
* `__lunora_queue_messages` table (bounded, auto-trimmed) so the studio Queues
|
|
5708
|
+
* panel shows one unified consumed-message log across every push consumer. Like
|
|
5709
|
+
* the mail catcher, the gate is the admin token alone — a token holder can
|
|
5710
|
+
* already mutate the shard, so a token-gated capture insert adds no privilege.
|
|
5711
|
+
*/
|
|
5712
|
+
private handleRecordQueueMessage;
|
|
5713
|
+
/** Empty the dev queue consumed-message log (studio "clear log" action). Admin-gated by the caller. */
|
|
5714
|
+
private handleClearQueueMessages;
|
|
5715
|
+
/**
|
|
5716
|
+
* Serve `__lunora_admin__:sendQueueMessage` — the studio's "Send test message"
|
|
5717
|
+
* button. Resolves the declared queue's `QUEUE_*` producer binding and calls
|
|
5718
|
+
* `.send(body, { delaySeconds?, contentType? })`, or `.sendBatch(...)` when a
|
|
5719
|
+
* `batch` array is supplied. No SQLite write happens here (the message is only
|
|
5720
|
+
* captured once a consumer processes it), so this only records an audit entry.
|
|
5721
|
+
* Admin-gated by `handleAdminRpc`'s caller.
|
|
5722
|
+
*/
|
|
5723
|
+
private handleSendQueueMessage;
|
|
5724
|
+
/**
|
|
5725
|
+
* Serve `__lunora_admin__:replayQueueMessage` — the studio's one-click replay /
|
|
5726
|
+
* DLQ redrive. Looks the captured row up by id, resolves the destination export
|
|
5727
|
+
* (explicit `target` → the parent queue when the message was captured off a
|
|
5728
|
+
* dead-letter queue → the queue it was consumed from), and re-enqueues the
|
|
5729
|
+
* stored body onto that producer. Records an audit entry; no SQLite write beyond
|
|
5730
|
+
* that (the replayed message is re-captured when a consumer processes it).
|
|
5731
|
+
* Admin-gated by `handleAdminRpc`'s caller.
|
|
5732
|
+
*/
|
|
5733
|
+
private handleReplayQueueMessage;
|
|
5734
|
+
/**
|
|
5735
|
+
* Resolve a declared queue's runtime producer binding from this shard's `env`.
|
|
5736
|
+
* Looks the `exportName` up in {@link queuesMetadata} (the codegen subclass's
|
|
5737
|
+
* statically-discovered list) to find its generated `QUEUE_*` binding, then
|
|
5738
|
+
* reads `env[binding]` and validates it carries `send`/`sendBatch`. A bad export
|
|
5739
|
+
* name or a missing/malformed binding throws a 400 `LunoraError` so the studio
|
|
5740
|
+
* surfaces an actionable message. Mirrors {@link resolveWorkflowBinding}.
|
|
5741
|
+
*/
|
|
5742
|
+
private resolveQueueBinding;
|
|
5743
|
+
/**
|
|
5744
|
+
* Pick the replay destination export for a captured message's origin queue.
|
|
5745
|
+
* When the message was consumed off a queue that is another queue's dead-letter
|
|
5746
|
+
* queue, prefer that PARENT queue's producer (a DLQ usually has no producer of
|
|
5747
|
+
* its own) so replay redrives onto the original; otherwise re-enqueue onto the
|
|
5748
|
+
* queue the message came from. Returns `undefined` when neither is declared.
|
|
5749
|
+
*/
|
|
5750
|
+
private resolveReplayTarget;
|
|
5751
|
+
/**
|
|
4966
5752
|
* Append one durable audit entry for a state-changing admin op that just
|
|
4967
5753
|
* succeeded, folding the acting user (from `getCurrentUserId`) into `detail`.
|
|
4968
5754
|
* Called only on the success path, so a rejected/validated op leaves no
|
|
@@ -5004,6 +5790,25 @@ declare abstract class ShardDO {
|
|
|
5004
5790
|
*/
|
|
5005
5791
|
private recordRequestLog;
|
|
5006
5792
|
/**
|
|
5793
|
+
* The single durable-row + Logpush write seam for `__lunora_reqlog__`. Both
|
|
5794
|
+
* the per-dispatch {@link recordRequestLog} and the container-crash path
|
|
5795
|
+
* ({@link handleRecordContainerEvent}) funnel through here so they can't
|
|
5796
|
+
* drift — before this was extracted the container writer persisted the row
|
|
5797
|
+
* but silently skipped the Logpush emit every other error got.
|
|
5798
|
+
*
|
|
5799
|
+
* Best-effort by contract: a SQL failure (e.g. a test double with no `sql`
|
|
5800
|
+
* handle) or a serialization hiccup in the emit must NEVER turn a served
|
|
5801
|
+
* request — or a container event push — into a failed one, so each half is
|
|
5802
|
+
* swallowed independently.
|
|
5803
|
+
*
|
|
5804
|
+
* Errors are always streamed to `console` (rare, high-value — they ride CF
|
|
5805
|
+
* Workers Logs at error level and the dev-server formats them in the
|
|
5806
|
+
* terminal), redacted in prod like any other event. The full per-dispatch
|
|
5807
|
+
* summary stream (successful OKs too) stays opt-in behind
|
|
5808
|
+
* `LUNORA_REQUEST_LOG_EMIT` so a hot shard doesn't emit a line per call.
|
|
5809
|
+
*/
|
|
5810
|
+
private persistRequestLog;
|
|
5811
|
+
/**
|
|
5007
5812
|
* Resolve the request-log knobs from the Worker `env`, all PLAN3 §3.3 decisions.
|
|
5008
5813
|
*
|
|
5009
5814
|
* `captureRaw`: raw (un-redacted) args/identity in a dev environment, redacted
|
|
@@ -5111,6 +5916,20 @@ declare abstract class ShardDO {
|
|
|
5111
5916
|
* Read-only: it touches no SQLite and mutates no socket state.
|
|
5112
5917
|
*/
|
|
5113
5918
|
private collectSubscriptions;
|
|
5919
|
+
/**
|
|
5920
|
+
* Assemble the `__lunora_admin__:getFanoutMetrics` payload for the Studio
|
|
5921
|
+
* fan-out observability panel (plan 075 Phase 1). The point-in-time topic
|
|
5922
|
+
* subscriber counts are folded live from each socket's attachment via
|
|
5923
|
+
* {@link summarizeFanoutTopics}; the running per-path cost counters are the
|
|
5924
|
+
* in-memory {@link ShardDO.fanout} tallies, sharing `metrics.sinceMs` as the
|
|
5925
|
+
* "since this instance woke" epoch. Touches no SQLite and mutates no socket
|
|
5926
|
+
* state; it does call `relay.relayCount()`, which advances the promotion
|
|
5927
|
+
* latch — safe here because that transition is a pure, monotonic function of
|
|
5928
|
+
* the live socket count (DOs are single-threaded), so a metrics poll only ever
|
|
5929
|
+
* drives the latch to the same state the routing path would compute for the
|
|
5930
|
+
* same count, never a divergent one.
|
|
5931
|
+
*/
|
|
5932
|
+
private collectFanoutMetrics;
|
|
5114
5933
|
/** Resolve a `getAuditLog` admin read, parsing the optional `limit`/`sinceSeq` cursor args and ensuring the reserved table first. */
|
|
5115
5934
|
private readAdminAuditLog;
|
|
5116
5935
|
/**
|
|
@@ -5123,6 +5942,18 @@ declare abstract class ShardDO {
|
|
|
5123
5942
|
*/
|
|
5124
5943
|
private readAdminRequestLog;
|
|
5125
5944
|
/**
|
|
5945
|
+
* Resolve a `getIssues` admin read: fold the recent `error`-outcome
|
|
5946
|
+
* request-log rows into grouped {@link readErrorIssues Issues} by fingerprint,
|
|
5947
|
+
* accepting the same optional correlation filters as `getRequestLog`
|
|
5948
|
+
* (function-path prefix, exact shardKey/userId) plus a `limit` on rows
|
|
5949
|
+
* scanned. This is a read over the bounded reqlog readout — no new store —
|
|
5950
|
+
* so a self-hosted worker gets grouped error triage for free. Carries the
|
|
5951
|
+
* {@link ADMIN_WILDCARD} like the other log reads so a live Issues
|
|
5952
|
+
* subscription re-runs on every write-flush (the per-socket JSON memo still
|
|
5953
|
+
* suppresses byte-identical pushes).
|
|
5954
|
+
*/
|
|
5955
|
+
private readAdminIssues;
|
|
5956
|
+
/**
|
|
5126
5957
|
* Resolve a `getAuthMetrics` admin read: the durable app-level auth
|
|
5127
5958
|
* attempt/failure counters + minute-bucketed history the studio SLO panel
|
|
5128
5959
|
* charts (PLAN3 §2.3). Auth runs as a top-level `/api/auth/*` worker route,
|
|
@@ -5154,6 +5985,17 @@ declare abstract class ShardDO {
|
|
|
5154
5985
|
* JSON memo still suppresses byte-identical pushes).
|
|
5155
5986
|
*/
|
|
5156
5987
|
private readAdminCapturedMail;
|
|
5988
|
+
/**
|
|
5989
|
+
* Resolve a `getQueueMessages` admin read — the dev queue catcher's consumed
|
|
5990
|
+
* message log (`queue-catcher.ts`), newest-first, optionally filtered to one
|
|
5991
|
+
* queue. Best-effort: a SQL failure returns an empty log rather than throwing.
|
|
5992
|
+
* Reported against the {@link QUEUE_TABLE} so this read participates in
|
|
5993
|
+
* table-scoped subscription invalidation, but new captures arrive via the
|
|
5994
|
+
* worker→root-shard `recordQueueMessage` write, which (like the mail catcher)
|
|
5995
|
+
* inserts directly without a `flushChangedTables` — so the panel refreshes on
|
|
5996
|
+
* its poll (`useAutoRefresh`) rather than a live push.
|
|
5997
|
+
*/
|
|
5998
|
+
private readAdminQueueMessages;
|
|
5157
5999
|
/** Resolve a `readTablePage` admin read, parsing the loosely-typed args into the reader's options. */
|
|
5158
6000
|
private readAdminTablePage;
|
|
5159
6001
|
/**
|
|
@@ -5179,6 +6021,49 @@ declare abstract class ShardDO {
|
|
|
5179
6021
|
*/
|
|
5180
6022
|
private executeAdminSubscription;
|
|
5181
6023
|
/**
|
|
6024
|
+
* Resolve one subscription (seed or refresh) to its {@link SubscriptionOutcome}
|
|
6025
|
+
* by routing the `functionPath` to the right read path — shared by
|
|
6026
|
+
* {@link seedSubscription} and {@link refreshSubscriptions} so both branch
|
|
6027
|
+
* identically:
|
|
6028
|
+
* - `__lunora_admin__:*` → {@link executeAdminSubscription} (raw SQLite read).
|
|
6029
|
+
* - {@link FLAGS_FUNCTION_PREFIX} → {@link runFlagSubscriptionRead} (the codegen subclass evaluates the flag through the configured provider). The value isn't bound to any table, so it is tagged with the {@link ADMIN_WILDCARD} dep — re-evaluated on every write-flush so a live `useFlag` stays current within a session. A `null` read means "nothing to deliver" (no provider, or a flag that resolved to `null`).
|
|
6030
|
+
* - everything else → {@link executeSubscription} (the user query, under the socket's own by-value identity).
|
|
6031
|
+
*/
|
|
6032
|
+
private resolveReactiveOutcome;
|
|
6033
|
+
/**
|
|
6034
|
+
* SECURITY BOUNDARY for cross-socket reactive dedup. A read is
|
|
6035
|
+
* identity-INDEPENDENT only when its result cannot vary by the caller's
|
|
6036
|
+
* verified identity — i.e. the admin/reserved introspection reads, which
|
|
6037
|
+
* route to {@link executeAdminSubscription} and ignore the
|
|
6038
|
+
* {@link SubscriptionIdentity} entirely.
|
|
6039
|
+
*
|
|
6040
|
+
* Everything else is identity-DEPENDENT and must NEVER be shared across
|
|
6041
|
+
* sockets: a user query may be `rls()` / `ctx.auth`-scoped (different rows
|
|
6042
|
+
* per identity), and a flag read ({@link FLAGS_FUNCTION_PREFIX}) evaluates
|
|
6043
|
+
* the provider with the subscriber's identity (per-user targeting). Sharing
|
|
6044
|
+
* one socket's result with another would leak one identity's rows/flags to a
|
|
6045
|
+
* different identity, so this predicate gates {@link resolveReactiveOutcomeDeduped}
|
|
6046
|
+
* shut for them.
|
|
6047
|
+
*/
|
|
6048
|
+
protected isIdentityIndependent(functionPath: string): boolean;
|
|
6049
|
+
/**
|
|
6050
|
+
* Memoizing wrapper over {@link resolveReactiveOutcome}: flush-local sharing across sockets.
|
|
6051
|
+
* Within a single {@link refreshSubscriptions} pass, N sockets subscribed to
|
|
6052
|
+
* the SAME identity-independent `(functionPath, args)` re-run the query N
|
|
6053
|
+
* times today (see the Case-6 fan-out characterization). When the read is
|
|
6054
|
+
* identity-independent (admin/reserved — see {@link isIdentityIndependent})
|
|
6055
|
+
* its result is the same for every socket, so the first run is cached (by its
|
|
6056
|
+
* in-flight Promise, since the bounded worker pool runs sockets in parallel)
|
|
6057
|
+
* and shared with the rest — collapsing N runs to ONE.
|
|
6058
|
+
*
|
|
6059
|
+
* Identity-DEPENDENT reads are passed straight through, UNCACHED: each socket
|
|
6060
|
+
* must evaluate under its own by-value identity (RLS / `ctx.auth` / per-user
|
|
6061
|
+
* flags), so they never share a result. The `cache` is created fresh per
|
|
6062
|
+
* flush by the caller, so a result is never reused across passes (it would go
|
|
6063
|
+
* stale after the next write).
|
|
6064
|
+
*/
|
|
6065
|
+
private resolveReactiveOutcomeDeduped;
|
|
6066
|
+
/**
|
|
5182
6067
|
* Constant-time bearer check against `env.LUNORA_ADMIN_TOKEN`. Returns
|
|
5183
6068
|
* `false` (closed) when the token is unset so admin introspection is
|
|
5184
6069
|
* opt-in rather than exposed by default.
|
|
@@ -5210,6 +6095,17 @@ declare abstract class ShardDO {
|
|
|
5210
6095
|
*/
|
|
5211
6096
|
private flushChangedTables;
|
|
5212
6097
|
/**
|
|
6098
|
+
* Drain {@link ShardDO.pendingRefreshTables} one coalesced batch at a time
|
|
6099
|
+
* until it is empty, then release the {@link ShardDO.refreshInFlight} gate.
|
|
6100
|
+
* Tables merged by a `flushChangedTables` that lands mid-pass are picked up
|
|
6101
|
+
* by the next loop iteration, so every committed write is observed by a
|
|
6102
|
+
* refresh that runs after it — bursts simply share a pass. The post-write
|
|
6103
|
+
* high-watermark and live-socket set are re-read inside each
|
|
6104
|
+
* `refreshSubscriptions` / `pokeShapeSubscribers` call, so a later batch
|
|
6105
|
+
* always reflects the latest committed state.
|
|
6106
|
+
*/
|
|
6107
|
+
private drainSubscriptionRefreshes;
|
|
6108
|
+
/**
|
|
5213
6109
|
* For every live subscription whose query reads one of `changed`, re-run
|
|
5214
6110
|
* the query and push a fresh `{ type: "data" }` frame when the result
|
|
5215
6111
|
* differs from the last one sent. Subscriptions with no `functionPath`
|
|
@@ -5282,6 +6178,216 @@ declare abstract class ShardDO {
|
|
|
5282
6178
|
*/
|
|
5283
6179
|
private seedSubscription;
|
|
5284
6180
|
/**
|
|
6181
|
+
* Drive the full `shape_subscribe` flow as one failure-aware unit: persist the
|
|
6182
|
+
* attachment, seed the shape, and ack ONLY once both succeed. A persist
|
|
6183
|
+
* rejection (`too_many`/`serialize_failed`) or a seed that can't resolve the
|
|
6184
|
+
* shape (unknown / RLS-denied / cross-shard-invalid) rolls the attachment back
|
|
6185
|
+
* and sends an `error` frame instead of acking — so a client is never left
|
|
6186
|
+
* acked but subscribed to a shape that will never deliver. Never throws (a
|
|
6187
|
+
* thrown `webSocketMessage` is fatal to the hibernating socket).
|
|
6188
|
+
*/
|
|
6189
|
+
private handleShapeSubscribe;
|
|
6190
|
+
/** Send a structured `error` frame for a failed `shape_subscribe`, swallowing a send on an already-closed socket. */
|
|
6191
|
+
private sendShapeSubscribeError;
|
|
6192
|
+
/**
|
|
6193
|
+
* Seed a freshly-registered shape subscription. Resolves the shape under the
|
|
6194
|
+
* socket's verified identity, then ships either:
|
|
6195
|
+
*
|
|
6196
|
+
* - a **catch-up** poke (the membership diff in `(sinceCheckpoint, cursor]`)
|
|
6197
|
+
* when the client supplied a still-current checkpoint within the CDC retention
|
|
6198
|
+
* window and on this epoch — the cheap reconnect path; or
|
|
6199
|
+
* - a **full** insert-poke of the shape's entire current membership — a
|
|
6200
|
+
* first-time subscribe, or a reconnect that fell outside retention / forked
|
|
6201
|
+
* epoch.
|
|
6202
|
+
*
|
|
6203
|
+
* Either way the per-socket shape memo advances to the flush watermark so
|
|
6204
|
+
* later `pokeShapeSubscribers` passes diff from the right point.
|
|
6205
|
+
*
|
|
6206
|
+
* Returns `"ok"` once the shape resolved and its seed poke was attempted, or a
|
|
6207
|
+
* `{ code, message }` failure when the shape can't be resolved — an unknown /
|
|
6208
|
+
* RLS-denied shape (a base class with no registry resolves nothing), or a
|
|
6209
|
+
* `resolveShape` that threw (e.g. a cross-shard-join guard). The caller rolls
|
|
6210
|
+
* back the persisted attachment and errors instead of acking, so a client is
|
|
6211
|
+
* never left subscribed to a shape that will never deliver.
|
|
6212
|
+
*/
|
|
6213
|
+
private seedShapeSubscription;
|
|
6214
|
+
/**
|
|
6215
|
+
* Seed a non-`.global()` (op-log-backed) shape: either a catch-up diff over
|
|
6216
|
+
* `(sinceSeq, cursor]` when the client supplied a still-current checkpoint on
|
|
6217
|
+
* this epoch within the CDC retention window, or a full membership insert-poke
|
|
6218
|
+
* otherwise. The memo advances to `cursor` only once the poke is delivered, so
|
|
6219
|
+
* a failed send re-diffs from the prior point rather than skipping rows. May
|
|
6220
|
+
* throw (a stub `sql` handle, a membership probe failure); the caller converts
|
|
6221
|
+
* it to a structured `shape_subscribe` error.
|
|
6222
|
+
*/
|
|
6223
|
+
private seedOpLogShape;
|
|
6224
|
+
/**
|
|
6225
|
+
* Compute an op-log shape seed (cursor, epoch, the resume base, and the
|
|
6226
|
+
* membership `rowsPatch`) WITHOUT sending — the shared core of
|
|
6227
|
+
* {@link ShardDO.seedOpLogShape} (sends to a local socket) and the owner relay's
|
|
6228
|
+
* `buildShapeSeedFrames` (serializes the frames for a relay to deliver, plan 075
|
|
6229
|
+
* Phase 3, via the {@link RelayHost} seam). Resume only when CDC is on, the client is on this
|
|
6230
|
+
* epoch, its checkpoint doesn't run ahead of ours, and the log still covers it;
|
|
6231
|
+
* else a full re-seed. A fully-compacted log only proves "nothing missed" when
|
|
6232
|
+
* the client is already at `cursor`.
|
|
6233
|
+
* @returns the cursor/epoch, the resume base (`baseCheckpoint`), and the membership patch
|
|
6234
|
+
*/
|
|
6235
|
+
private computeOpLogShapeSeed;
|
|
6236
|
+
/**
|
|
6237
|
+
* Fan the membership diff of every shape affected by this flush to its
|
|
6238
|
+
* subscribers — the partial-replication parallel to
|
|
6239
|
+
* {@link ShardDO.refreshSubscriptions}, called alongside it from
|
|
6240
|
+
* {@link ShardDO.flushChangedTables}. For each socket (bounded fan-out, same
|
|
6241
|
+
* concurrency + `awaitWsDrain` backpressure as the subscription path) it
|
|
6242
|
+
* resolves each shape under the socket's identity, diffs only the shapes
|
|
6243
|
+
* whose table changed in `(memoCursor, frameCursor]`, and emits one poke
|
|
6244
|
+
* carrying a part per changed shape. No-op when no socket holds a shape.
|
|
6245
|
+
*/
|
|
6246
|
+
private pokeShapeSubscribers;
|
|
6247
|
+
/**
|
|
6248
|
+
* Diff every op-log-backed shape a socket holds against this flush, splitting
|
|
6249
|
+
* the results into the poke parts to send and the per-shape memo advances. A
|
|
6250
|
+
* `.global()` shape (driven by the alarm poll loop, not this flush) and a shape
|
|
6251
|
+
* whose table didn't change are skipped; a shape whose resolve/diff throws is
|
|
6252
|
+
* logged and skipped with its memo unadvanced so a later flush retries. Empty
|
|
6253
|
+
* diffs advance unconditionally; part-bearing shapes advance only once the
|
|
6254
|
+
* caller confirms the poke was delivered.
|
|
6255
|
+
*/
|
|
6256
|
+
private collectShapePokeParts;
|
|
6257
|
+
/**
|
|
6258
|
+
* Drain the op-log range `(sinceSeq, upTo]` for `table` into the latest op per
|
|
6259
|
+
* row id (collapsing multiple ops on the same row to the newest). Within one
|
|
6260
|
+
* flush, every shape over the SAME `(table, sinceSeq, upTo)` reads the
|
|
6261
|
+
* identical changelog slice, so the drained map is memoized in the
|
|
6262
|
+
* caller-supplied `cache` (created fresh per flush) — N shapes on a table
|
|
6263
|
+
* share ONE changelog drain instead of re-scanning it per shape. The
|
|
6264
|
+
* per-shape membership probe still runs per shape (its predicate is
|
|
6265
|
+
* identity/args-specific), so only the shared op read is collapsed.
|
|
6266
|
+
*/
|
|
6267
|
+
private readShapeOpRange;
|
|
6268
|
+
/**
|
|
6269
|
+
* Read one page of the `__cdc_log` for a shape diff (table-scoped). A thin
|
|
6270
|
+
* protected seam over {@link readCdcChanges}: it isolates the single
|
|
6271
|
+
* changelog read that {@link readShapeOpRange} memoizes per flush, and gives
|
|
6272
|
+
* tests a point to count the reads the op-range cache collapses.
|
|
6273
|
+
*/
|
|
6274
|
+
protected readShapeCdcPage(sql: SqlExec, sinceSeq: number, tables: ReadonlySet<string>): {
|
|
6275
|
+
changes: CdcChange[];
|
|
6276
|
+
cursor: number;
|
|
6277
|
+
};
|
|
6278
|
+
/**
|
|
6279
|
+
* Build the row-ops for a shape over the op range `(sinceSeq, upTo]`. Reads
|
|
6280
|
+
* the changelog (drained across pages via {@link readShapeOpRange}, shared
|
|
6281
|
+
* across same-range shapes in a flush), collapses to the latest op per row,
|
|
6282
|
+
* then runs ONE membership probe ({@link selectShapeMemberIds}) over the
|
|
6283
|
+
* changed ids: a row still in the set → upsert with its post-image doc
|
|
6284
|
+
* (projected to the shape's columns); a row that left the set, or any delete,
|
|
6285
|
+
* → `delete(key)` (a delete carries no post-image, so membership is
|
|
6286
|
+
* unknowable from the op alone — the client no-ops an unknown key).
|
|
6287
|
+
*/
|
|
6288
|
+
private buildShapeDiff;
|
|
6289
|
+
/** Build the full insert-poke of a shape's current membership — the first-seed/full-reseed rowset. */
|
|
6290
|
+
private buildShapeSeed;
|
|
6291
|
+
/**
|
|
6292
|
+
* Seed a `.global()`-table shape: read its full membership from D1, ship it
|
|
6293
|
+
* as one insert-poke, record the membership snapshot the alarm poll loop will
|
|
6294
|
+
* diff against, and arm the poll alarm. A global shape has no op-log cursor,
|
|
6295
|
+
* so the poke is stamped at this DO's current cursor (informational only) and
|
|
6296
|
+
* carries no resume base — a reconnect always re-seeds full.
|
|
6297
|
+
*/
|
|
6298
|
+
private seedGlobalShape;
|
|
6299
|
+
/**
|
|
6300
|
+
* Re-read a global shape's membership from D1 and poke only the diff against
|
|
6301
|
+
* the socket's last snapshot: a new key → `insert`, a changed projected value
|
|
6302
|
+
* → `update`, a vanished key → `delete`. The snapshot advances to the fresh
|
|
6303
|
+
* membership even when the diff is empty, so the next tick compares from here.
|
|
6304
|
+
* No frame is sent when nothing changed (the common steady-state tick).
|
|
6305
|
+
*/
|
|
6306
|
+
private refreshGlobalShape;
|
|
6307
|
+
/**
|
|
6308
|
+
* Read a socket's global-shape baseline, preferring the hot in-memory cache
|
|
6309
|
+
* and falling back to the durable `__global_shape_snapshot` table on a miss (a
|
|
6310
|
+
* cold socket after a hibernation eviction). The loaded baseline repopulates
|
|
6311
|
+
* the cache so subsequent ticks in this wake hit memory. An empty
|
|
6312
|
+
* `connectionId` (a socket that never went through the lifecycle-aware upgrade,
|
|
6313
|
+
* e.g. a unit harness) skips the durable read and behaves as in-memory-only.
|
|
6314
|
+
*/
|
|
6315
|
+
private readGlobalSnapshot;
|
|
6316
|
+
/** Record a socket's latest global-shape membership snapshot in the in-memory cache (creating the per-socket map lazily). */
|
|
6317
|
+
private recordGlobalSnapshot;
|
|
6318
|
+
/**
|
|
6319
|
+
* Load a durable global-shape baseline from SQLite, or an empty map when none
|
|
6320
|
+
* is stored / the durable path is unavailable. A stub `sql` handle (unit
|
|
6321
|
+
* harness) or a missing table degrades to in-memory-only behavior rather than
|
|
6322
|
+
* failing the poll tick.
|
|
6323
|
+
*/
|
|
6324
|
+
private loadGlobalSnapshot;
|
|
6325
|
+
/**
|
|
6326
|
+
* Persist a socket's global-shape baseline to SQLite so the poll-loop diff
|
|
6327
|
+
* survives hibernation. A no-op for a connection-id-less socket or a stub
|
|
6328
|
+
* `sql` handle (the in-memory cache then carries the baseline for the DO's
|
|
6329
|
+
* lifetime, matching the pre-durable behavior).
|
|
6330
|
+
*/
|
|
6331
|
+
private saveGlobalSnapshot;
|
|
6332
|
+
/**
|
|
6333
|
+
* Arm the poll alarm for `.global()` shapes if one isn't already pending.
|
|
6334
|
+
* Idempotent — every global-shape seed calls it, but only the first arms the
|
|
6335
|
+
* alarm. Degrades to a no-op when the runtime exposes no `setAlarm` (the unit
|
|
6336
|
+
* harness): a global shape is then seed-only, which the poll-loop tests assert
|
|
6337
|
+
* by driving {@link ShardDO.alarm} directly.
|
|
6338
|
+
*/
|
|
6339
|
+
private scheduleGlobalPoll;
|
|
6340
|
+
/**
|
|
6341
|
+
* Record a contained shape-tier error (poll / poke / seed) into the DO's log
|
|
6342
|
+
* ring without aborting the rest of the pass. The shape pipeline is a
|
|
6343
|
+
* best-effort fan-out: one socket's read or one shape's resolve failing must
|
|
6344
|
+
* never take down the others — so callers swallow the throw and surface it
|
|
6345
|
+
* here for diagnosis. `context` is a synthetic `shape:phase:subId` path.
|
|
6346
|
+
*/
|
|
6347
|
+
private recordShapeError;
|
|
6348
|
+
/**
|
|
6349
|
+
* Guard a global shape's materialized membership against {@link
|
|
6350
|
+
* ShardDO.GLOBAL_SHAPE_MAX_ROWS}. Returns `true` when the row count is within
|
|
6351
|
+
* the cap; otherwise records a diagnosable error and returns `false` so the
|
|
6352
|
+
* caller fails the shape closed (no snapshot retained, no poke sent) rather
|
|
6353
|
+
* than risking a DO eviction on an unbounded global table. The transient read
|
|
6354
|
+
* buffer is bounded by the same gate — an over-cap membership is dropped, not
|
|
6355
|
+
* snapshotted per socket.
|
|
6356
|
+
*/
|
|
6357
|
+
private withinGlobalShapeBound;
|
|
6358
|
+
/**
|
|
6359
|
+
* Refresh every `.global()`-table shape held across all live sockets, one
|
|
6360
|
+
* diff-poke per (socket, shape). Returns the number of global shapes still
|
|
6361
|
+
* subscribed so {@link ShardDO.alarm} knows whether to re-arm. Expired sockets
|
|
6362
|
+
* are dropped in passing (mirrors {@link ShardDO.pokeShapeSubscribers}).
|
|
6363
|
+
*/
|
|
6364
|
+
private pollGlobalShapes;
|
|
6365
|
+
/**
|
|
6366
|
+
* Refresh one socket's `.global()`-table shapes, containing per-shape
|
|
6367
|
+
* failures so a single throw never aborts the poll tick (and with it the
|
|
6368
|
+
* re-arm). Returns the count of global shapes still subscribed on this socket
|
|
6369
|
+
* — a failed `resolveShape`/read keeps its shape counted so the alarm keeps
|
|
6370
|
+
* polling and retries next tick.
|
|
6371
|
+
*/
|
|
6372
|
+
private pollSocketGlobalShapes;
|
|
6373
|
+
/**
|
|
6374
|
+
* Send one poke (`pokeStart` → `pokePart` per shape → `pokeEnd`) to a socket.
|
|
6375
|
+
* All parts apply atomically at `pokeEnd`. Returns `true` when every frame was
|
|
6376
|
+
* handed to the socket, `false` when a send threw mid-poke (the socket closed)
|
|
6377
|
+
* — callers must NOT advance their shape baselines on a `false` so the client
|
|
6378
|
+
* re-receives the rows on its next flush/reconnect instead of losing them.
|
|
6379
|
+
*/
|
|
6380
|
+
private sendPoke;
|
|
6381
|
+
/**
|
|
6382
|
+
* The recipient client's `__client_watermark` for stamping a poke's
|
|
6383
|
+
* `lastMutationId`, or `undefined` when the socket announced no `clientId`
|
|
6384
|
+
* (a client that doesn't use custom mutators — nothing to drop an overlay
|
|
6385
|
+
* for). Read off the attachment so it survives hibernation.
|
|
6386
|
+
*/
|
|
6387
|
+
private socketClientWatermark;
|
|
6388
|
+
/** Record a shape's poke baseline cursor on a socket (creating the per-socket map lazily). */
|
|
6389
|
+
private recordShapeMemo;
|
|
6390
|
+
/**
|
|
5285
6391
|
* Record `outcome` as this socket's diff baseline for `subId` without
|
|
5286
6392
|
* sending a frame. Used by the resume fast-path, where the client keeps its
|
|
5287
6393
|
* cached value but the server still needs a baseline so the next
|
|
@@ -5353,6 +6459,15 @@ declare abstract class ShardDO {
|
|
|
5353
6459
|
* on older runtimes, where it degrades to a no-op.
|
|
5354
6460
|
*/
|
|
5355
6461
|
private armWebSocketKeepalive;
|
|
6462
|
+
/**
|
|
6463
|
+
* Route the non-RPC requests `fetch` handles before the shard-local RPC
|
|
6464
|
+
* endpoint: a WebSocket upgrade, and the internal `/_lunora/relay` owner↔relay
|
|
6465
|
+
* control channel (never reachable by a client — the runtime forwards only
|
|
6466
|
+
* worker-internal traffic there). Returns `undefined` for an RPC request, which
|
|
6467
|
+
* `fetch` then dispatches.
|
|
6468
|
+
* @returns the routed response, or `undefined` when this is an RPC request
|
|
6469
|
+
*/
|
|
6470
|
+
private routeNonRpc;
|
|
5356
6471
|
private handleWebSocketUpgrade;
|
|
5357
6472
|
/**
|
|
5358
6473
|
* Whether this shard has a `__cdc_log` table. The single source of the
|
|
@@ -5400,44 +6515,16 @@ declare abstract class ShardDO {
|
|
|
5400
6515
|
* but per-topic auth does not exist here; see `whisperSubscribe` on the client.
|
|
5401
6516
|
*/
|
|
5402
6517
|
private broadcastWhisper;
|
|
6518
|
+
/**
|
|
6519
|
+
* Deliver an already-serialized whisper `frame` to every local socket joined to
|
|
6520
|
+
* `topic`, excluding `exclude` (the sender, or `undefined` for a frame the relay
|
|
6521
|
+
* hub forwarded in — its sender lives on another DO). Records the fan-out pass
|
|
6522
|
+
* for `getFanoutMetrics` (plan 075 Phase 1). Pure delivery — no SQLite, no CDC.
|
|
6523
|
+
* @returns the number of sockets the frame was sent to
|
|
6524
|
+
*/
|
|
6525
|
+
private deliverWhisperLocal;
|
|
5403
6526
|
private readAttachment;
|
|
5404
6527
|
}
|
|
5405
|
-
/**
|
|
5406
|
-
* Durable Object that owns the live set of shard keys per sharded table.
|
|
5407
|
-
*
|
|
5408
|
-
* The query coordinator (`@lunora/runtime`) fans out cross-shard reads to
|
|
5409
|
-
* every live shard. With the static registry, the app supplies the shard
|
|
5410
|
-
* key list at boot — which is fine for fixed-cardinality deployments
|
|
5411
|
-
* (a known set of tenants) and unworkable for dynamic ones (one shard per
|
|
5412
|
-
* user-created channel, organisation, project, …).
|
|
5413
|
-
*
|
|
5414
|
-
* `ShardRegistryDO` is the persistent source of truth. A worker:
|
|
5415
|
-
*
|
|
5416
|
-
* - calls `POST /register {table, shardKey}` when a sharded table first
|
|
5417
|
-
* sees a write on a new key (typically from `ctx.db.<table>.insert` via
|
|
5418
|
-
* the worker's onWrite hook, fired through `ctx.waitUntil` so the
|
|
5419
|
-
* user-facing write doesn't pay the registry round-trip);
|
|
5420
|
-
* - calls `POST /unregister {table, shardKey}` when a shard is decommissioned;
|
|
5421
|
-
* - calls `GET /list?table=X` to materialise the fan-out target list. The
|
|
5422
|
-
* client (`createDynamicShardRegistry` in `@lunora/runtime`) caches the
|
|
5423
|
-
* answer with a small TTL so a wide fan-out doesn't pay a registry
|
|
5424
|
-
* round-trip on every call.
|
|
5425
|
-
*
|
|
5426
|
-
* Single-instance contract: deploy one DO instance per environment, by
|
|
5427
|
-
* convention named {@link SHARD_REGISTRY_DO_NAME}. The DO is small (just a
|
|
5428
|
-
* `Map<table, Set<shardKey>>`) and writes are infrequent (only on first-seen
|
|
5429
|
-
* shardKey per table), so a single instance is sufficient up to tens of
|
|
5430
|
-
* thousands of distinct shard keys.
|
|
5431
|
-
*
|
|
5432
|
-
* Wire shape: HTTP only, never RPC.
|
|
5433
|
-
*
|
|
5434
|
-
* POST /register body: { table, shardKey }
|
|
5435
|
-
* POST /unregister body: { table, shardKey }
|
|
5436
|
-
* GET /list?table=...
|
|
5437
|
-
* GET /snapshot (debug: returns the full table → [keys] map)
|
|
5438
|
-
*
|
|
5439
|
-
* Responses are JSON; the client shapes them. Keep the surface narrow.
|
|
5440
|
-
*/
|
|
5441
6528
|
/** Conventional DO instance name, passed to `idFromName` to address the single registry instance. */
|
|
5442
6529
|
declare const SHARD_REGISTRY_DO_NAME: string;
|
|
5443
6530
|
/**
|
|
@@ -5542,18 +6629,14 @@ type ConflictKind = "conflict" | "occ" | "restrict" | "trigger" | "unique";
|
|
|
5542
6629
|
* Thrown by mutation handlers when an optimistic-concurrency check fails (or a
|
|
5543
6630
|
* related write guard trips — see {@link ConflictKind}).
|
|
5544
6631
|
*
|
|
5545
|
-
*
|
|
5546
|
-
*
|
|
5547
|
-
*
|
|
5548
|
-
*
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
*/
|
|
5552
|
-
declare class ConflictError extends Error {
|
|
5553
|
-
readonly code: string;
|
|
6632
|
+
* A `LunoraError` subclass (`code: "CONFLICT"`, `status: 409`) so the runtime/DO
|
|
6633
|
+
* transport mappers recognise it structurally (via `isLunoraError`) — structural
|
|
6634
|
+
* callers across packages still avoid a hard `instanceof` dependency on
|
|
6635
|
+
* `@lunora/do`. `kind` is kept as an own property for the metrics layer.
|
|
6636
|
+
*/
|
|
6637
|
+
declare class ConflictError extends LunoraError {
|
|
5554
6638
|
/** Why the conflict fired; `occ` is the contention signal the metrics layer counts. */
|
|
5555
6639
|
readonly kind: ConflictKind;
|
|
5556
|
-
readonly status: number;
|
|
5557
6640
|
constructor(message?: string, kind?: ConflictKind);
|
|
5558
6641
|
}
|
|
5559
6642
|
/**
|
|
@@ -5573,8 +6656,11 @@ interface WhereSqlStrategy {
|
|
|
5573
6656
|
fieldRef: FieldRefSql;
|
|
5574
6657
|
/**
|
|
5575
6658
|
* Dialect `contains` rendering given the field reference and the (already
|
|
5576
|
-
* bound) search term. Absent ⇒ the portable
|
|
5577
|
-
* concat form (SQLite/Postgres); MySQL
|
|
6659
|
+
* bound, already wildcard-escaped) search term. Absent ⇒ the portable
|
|
6660
|
+
* `… LIKE '%' || term || '%' ESCAPE '\'` concat form (SQLite/Postgres); MySQL
|
|
6661
|
+
* supplies a `CONCAT(...)` variant. The term is escaped by
|
|
6662
|
+
* {@link compileContains}, so an implementation MUST pair it with
|
|
6663
|
+
* `ESCAPE '\'` for the literal-match to hold.
|
|
5578
6664
|
*/
|
|
5579
6665
|
likeContains?: (reference: SQL, term: SQL) => SQL;
|
|
5580
6666
|
/**
|
|
@@ -5596,4 +6682,4 @@ interface WhereSqlStrategy {
|
|
|
5596
6682
|
* `undefined` when the input imposes no constraint (empty `where`).
|
|
5597
6683
|
*/
|
|
5598
6684
|
declare const compileWhereSql: (where: WhereInput | undefined, strategy: WhereSqlStrategy) => SQL | undefined;
|
|
5599
|
-
export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, AGGREGATE_SQL_FUNCTION, AUTH_METRICS_BUCKETS_TABLE, AUTH_METRICS_BUCKET_MS, AUTH_METRICS_BUCKET_RETENTION, AUTH_METRICS_TABLE, type AdvisoriesResult, type AdvisoryFinding, type AggregateIndexDefinitionLike, type AggregateOp, type AggregateOptions, type AggregateResult, type AggregateTally, type ApplyOnDeleteOptions, type AuditEntry, type AuditLogResult, type AuthMetrics, type AuthMetricsBucket, type BroadcastDelta, CDC_LOG_TABLE, type CacheEntry, type CapturedMailRow, type CdcChange, type Clock, type ColumnMeta, type ColumnMetaLike, ConflictError, type CountArgs, CountRlsUnsupportedError, type CtxDbOptions, DATA_MIGRATION_STATE_TABLE, DEFAULT_MAX_RELATION_KEYS, type DataMigrationDocument, type DataMigrationLike, type DataMigrationTransform, type DatabaseWriterLike, type DependencyTracker, type DeployInfo, type ExportRow, type ExportShardAdminArgs, type ExportShardArgs, FUNCTION_METRICS_BUCKETS_TABLE, FUNCTION_METRICS_BUCKET_MS, FUNCTION_METRICS_BUCKET_RETENTION, FUNCTION_METRICS_INDEX_TABLE, FUNCTION_METRICS_TABLE, type FacetColumnOptions, type FacetColumnResult, type FacetValue, type FieldOperators, type FunctionCallStat, type FunctionMetricBucket, type FunctionMetricIndexHit, type FunctionStatsResult, type GroupByEntry, type GroupByOptions, type HibernatableWebSocket, type IdGenerator, type ImportError, type ImportShardAdminArgs, type ImportShardArgs, type ImportShardResult, type IndexDefinitionLike, type IndexRangeBuilderLike, LogBuffer, type LogEntry, type LogEventInput, type LogLevel, type LogSink, MAIL_RETENTION, MAIL_TABLE, MAX_SQL_ROWS, MIN_ADMIN_TOKEN_LENGTH, MIN_AUTH_SECRET_LENGTH, type MaskColumnMetadata, type MaskPoliciesResult, type MigrationDirection, type MigrationRunResult, type MigrationStatus, type MigrationStatusRow, type MutationDelta, type NestedWith, NotFoundError, NotUniqueError, type OnDeleteActionLike, type OrderByInput, type OrderKey, type PaginationOptions, type PitrBookmarkResult, type PitrRestoreArgs, type PitrRestoreResult, type PitrStorage, type QueryArgs, type QueryPage, RANK_TIEBREAK, RELATION_FUNCTION_PREFIX, RLS_UNWRAP_SYMBOL, ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, type RankDirection, type RankIndexDefinitionLike, type RankOptions, type RankPage, type RankPageOptions, type RankPageRow, type RankPageRowKey, type RankResult, type RankSortKeyLike, ReactiveCache, type ReactiveCacheOptions, type ReadHook, type ReadTablePageOptions, type RecordAuthEventInput, type RecordFunctionMetricInput, type RecordMailInput, type RelationDefinitionLike, type RenderedSql, type ResolveRelationPredicatesOptions, type ResolveWithOptions, type RestrictableQueryOptions, type RlsPoliciesResult, type RlsPolicyMetadata, RlsRequiredError, type RlsRoleMetadata, type RpcRequest, type RunDataMigrationOptions, type RunShardApplyCdcArgs, type RunShardApplyCdcResult, type RunShardBulkDeleteArgs, type RunShardBulkDeleteResult, type RunShardExportArgs, type RunShardImportArgs, type RunShardMigrationArgs, type RunShardRankBeforeArgs, type RunShardRankPageArgs, type RunShardWriteArgs, type RunShardWriteResult, type RunTriggersOptions, SCAN_DEP, SESSION_DO_TTL_DEFAULT, SHARD_REGISTRY_DO_NAME, type ScheduledFunctionDoc, type SchedulerLike, type SchemaLike, type SearchFilterBuilderLike, type SecurityAuditResult, type SecurityFinding, type SecurityFindingKind, type SecurityFindingLevel, type SelectMatchingIdsOptions, type ServerDefaultContextLike, SessionDO, type SessionRecord, type SettingEntry, type SettingKind, type SettingsResult, ShardDO, type ShardDOOptions, type ShardDOState, type ShardRankPageResult, ShardRegistryDO, type SocketAttachment, type SortDirection
|
|
6685
|
+
export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, AGGREGATE_SQL_FUNCTION, AUTH_METRICS_BUCKETS_TABLE, AUTH_METRICS_BUCKET_MS, AUTH_METRICS_BUCKET_RETENTION, AUTH_METRICS_TABLE, type AdvisoriesResult, type AdvisoryFinding, type AggregateIndexDefinitionLike, type AggregateOp, type AggregateOptions, type AggregateResult, type AggregateTally, type ApplyOnDeleteOptions, type AuditEntry, type AuditLogResult, type AuthMetrics, type AuthMetricsBucket, type BroadcastDelta, CDC_LOG_TABLE, type CacheEntry, type CapturedMailRow, type CdcChange, type Clock, type ColumnMeta, type ColumnMetaLike, ConflictError, type CountArgs, CountRlsUnsupportedError, type CtxDbOptions, DATA_MIGRATION_STATE_TABLE, DEFAULT_MAX_RELATION_KEYS, type DataMigrationDocument, type DataMigrationLike, type DataMigrationTransform, type DatabaseWriterLike, type DependencyTracker, type DeployInfo, type ExportRow, type ExportShardAdminArgs, type ExportShardArgs, type ExternalSourceDiffResult, type ExternalSourceLike, FLAGS_FUNCTION_PREFIX, FUNCTION_METRICS_BUCKETS_TABLE, FUNCTION_METRICS_BUCKET_MS, FUNCTION_METRICS_BUCKET_RETENTION, FUNCTION_METRICS_INDEX_TABLE, FUNCTION_METRICS_TABLE, type FacetColumnOptions, type FacetColumnResult, type FacetValue, type FieldOperators, type FlagEvaluation, type FlagsResult, type FunctionCallStat, type FunctionMetricBucket, type FunctionMetricIndexHit, type FunctionStatsResult, type GroupByEntry, type GroupByOptions, type HibernatableWebSocket, type IdGenerator, type ImportError, type ImportShardAdminArgs, type ImportShardArgs, type ImportShardResult, type IndexDefinitionLike, type IndexRangeBuilderLike, LogBuffer, type LogEntry, type LogEventInput, type LogLevel, type LogSink, MAIL_RETENTION, MAIL_TABLE, MAX_SQL_ROWS, MIN_ADMIN_TOKEN_LENGTH, MIN_AUTH_SECRET_LENGTH, type MaskColumnMetadata, type MaskPoliciesResult, type MaterializeResult, type MigrationDirection, type MigrationRunResult, type MigrationStatus, type MigrationStatusRow, type MutationDelta, type NestedWith, NotFoundError, NotUniqueError, type OnDeleteActionLike, type OrderByInput, type OrderKey, type PaginationOptions, type PitrBookmarkResult, type PitrRestoreArgs, type PitrRestoreResult, type PitrStorage, type QueryArgs, type QueryPage, type QueueMetadata, type QueuesResult, RANK_TIEBREAK, RELATION_FUNCTION_PREFIX, RLS_UNWRAP_SYMBOL, ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, type RankDirection, type RankIndexDefinitionLike, type RankOptions, type RankPage, type RankPageOptions, type RankPageRow, type RankPageRowKey, type RankResult, type RankSortKeyLike, ReactiveCache, type ReactiveCacheOptions, type ReadHook, type ReadTablePageOptions, type RecordAuthEventInput, type RecordFunctionMetricInput, type RecordMailInput, type RelationDefinitionLike, type RenderedSql, type ResolveRelationPredicatesOptions, type ResolveWithOptions, type RestrictableQueryOptions, type RlsPoliciesResult, type RlsPolicyMetadata, RlsRequiredError, type RlsRoleMetadata, type RpcRequest, type RunDataMigrationOptions, type RunShardApplyCdcArgs, type RunShardApplyCdcResult, type RunShardBulkDeleteArgs, type RunShardBulkDeleteResult, type RunShardExportArgs, type RunShardImportArgs, type RunShardMigrationArgs, type RunShardRankBeforeArgs, type RunShardRankPageArgs, type RunShardWriteArgs, type RunShardWriteResult, type RunTriggersOptions, SCAN_DEP, SESSION_DO_TTL_DEFAULT, SHARD_REGISTRY_DO_NAME, type SchedulableWorkflowReferenceLike, type ScheduledFunctionDoc, type SchedulerLike, type SchemaLike, type SearchFilterBuilderLike, type SecurityAuditResult, type SecurityFinding, type SecurityFindingKind, type SecurityFindingLevel, type SelectMatchingIdsOptions, type ServerDefaultContextLike, SessionDO, type SessionRecord, type SettingEntry, type SettingKind, type SettingsResult, type ShapeSubscriptionQuery, ShardDO, type ShardDOOptions, type ShardDOState, type ShardRankPageResult, ShardRegistryDO, type SocketAttachment, type SortDirection, type SourceClientLike, type SourceRefresh, type SqlConsoleResult, type SqlCursor, type SqlEngine, type SqlExec, type StorageRuleMetadata, type StorageRulesResult, type StudioFeaturesResult, type SubscriptionEnvelope, type SubscriptionOutcome, type SubscriptionQuery, type SystemDatabaseReader, type SystemDoc, type SystemQuery, type SystemReaderOptions, type SystemReaderSchedulerLike, type SystemReaderStorageLike, type SystemTableName, type TableColumnsResult, type TableDefinitionLike, type TableIndexInfo, type TableIndexesResult, type TableInfo, type TablePage, type TableReaderLike, type TablesColumnsResult, type TransactionSqlLike, type TriggerContextLike, type TriggerDefinitionLike, type TriggerEventLike, type TriggerOpLike, type TriggerTimingLike, type ValidatorLike, type WhereInput, type WhereSqlStrategy, type WithInput, type WorkflowMetadata, type WorkflowsResult, type WriteEvent, type WriteHook, aggregateSqlFunction, aggregateTableName, applyCdcChanges, applyOnDelete, applySelect, armRestore, assertFlatPredicate, assertReadonly, assertShapeShardable, assertValidClientId, backfillAggregateIndexes, backfillRankIndexes, buildFtsMatch, buildSecurityAudit, buildSeekWhere, clearCapturedMail, coerceAggregateNumber, compileWhereSql, containsRelationPredicate, createDependencyTracker, createShardCtxDb, createSystemReader, decodeCursor, depKey, diffExternalSource, encodeAggregateKey, encodeCursor, encodePartitionKey, ensureAuthMetricsTables, ensureFunctionMetricsTables, ensureMailTable, exportShardRows, exportShardTable, facetColumn, fanOutScalarCounts, foldAggregateTally, ftsTableName, guardWriter, hasTrigger, importShardRows, isRelationPredicate, isSourceDue, liftSourceId, listTables, matchesRankStaticWhere, matchesStaticWhere, materializeExternalRows, mergeWhere, normalizeCountArgument, normalizeIdStructurally, normalizeOrderKeys, parseExportShardArgs, parseImportShardArgs, planAggregateLookup, pullExternalSourceTick, rankTableName, reactiveCacheKey, readAggregateValue, readAuthMetrics, readBookmark, readCapturedMail, readCdcChanges, readExternalSourceBaseline, readFunctionMetricBuckets, readFunctionMetricIndexHits, readFunctionMetrics, readFunctionMetricsTotals, readMigrationStatus, readTablePage, recordAuthEvent, recordCapturedMail, recordFunctionMetric, renderSql, resolveRankPartition, resolveRelationPredicates, resolveWith, runDataMigration, runExternalSourceTick, runReadonlySql, runRowValidators, runShardMigrations, runTriggers, scoreDocument, selectExportTables, selectIndexForAggregate, selectIndexForCount, selectIndexForGroupBy, selectMatchingIds, serveRelationFanout, softDeleteScope, sortColumnName, stableStringify, stringifySearchText, subscriptionListDeltas, throwingScheduler, tokenizeSearch, trimCdcChanges, validateImportRow };
|