@lunora/do 1.0.0-alpha.6 → 1.0.0-alpha.8

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/dist/index.d.mts CHANGED
@@ -596,6 +596,7 @@ declare const rankTableName: (table: string, indexName: string) => string;
596
596
  * - `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.
597
597
  * - `rowId` === `doc._id`, the `__id__` tiebreak.
598
598
  */
599
+ declare const stableStringify: (value: unknown) => string;
599
600
  /** A single memoized result, the deps it read, and any active subscribers. */
600
601
  interface CacheEntry {
601
602
  /** Approximate serialized size of `result`, charged against `maxBytes`. */
@@ -728,16 +729,6 @@ declare class ReactiveCache {
728
729
  private evict;
729
730
  }
730
731
  /**
731
- * Stable, sorted JSON encoding of `args` for use in a cache key. Object keys
732
- * are visited in lexical order at every depth so `{ a: 1, b: 2 }` and
733
- * `{ b: 2, a: 1 }` hash to the same string. Arrays preserve their order
734
- * (the index IS the key). `undefined` values are skipped at the object level
735
- * so `{ a: undefined }` collides with `{}` — matches Convex behavior and
736
- * avoids spurious cache misses on optional args. Inside arrays `undefined`
737
- * encodes as `null` to keep positional semantics.
738
- */
739
- declare const stableStringify: (value: unknown) => string;
740
- /**
741
732
  * Compose a cache key from a function path, a stably-encoded args object, and
742
733
  * the caller's identity discriminator. Exported so the wiring layer and tests
743
734
  * build identical keys without each side reinventing the format.
@@ -2262,6 +2253,19 @@ declare const ADMIN_FUNCTION_PREFIX = "__lunora_admin__:";
2262
2253
  */
2263
2254
  declare const RELATION_FUNCTION_PREFIX = "__lunora_relation__:";
2264
2255
  /**
2256
+ * Reserved `functionPath` prefix for live feature-flag reads. The React client's
2257
+ * `useFlag`/`useFlags` subscribe to `__lunora_flags__:eval` over the same WS
2258
+ * channel as a user query; `ShardDO` intercepts it before user dispatch and
2259
+ * serves it from the codegen-overridden flag-subscription read hook, which
2260
+ * evaluates the flag through the app's OpenFeature provider under the socket's
2261
+ * verified identity. Like the other reserved prefixes it is NOT admin-gated (a
2262
+ * flag read is public, scoped to the subscriber's own targeting context), and
2263
+ * the `__lunora_` namespace is reserved so a real `<file>:<function>` can't
2264
+ * collide. Re-evaluated on every write-flush so values stay live within a
2265
+ * session (provider-side flips with no intervening write surface on reconnect).
2266
+ */
2267
+ declare const FLAGS_FUNCTION_PREFIX = "__lunora_flags__:";
2268
+ /**
2265
2269
  * Fully-qualified reserved paths the data browser invokes. The
2266
2270
  * `__lunora_admin__:` prefix is spelled out inline rather than interpolated so
2267
2271
  * the values stay emittable under `--isolatedDeclarations`.
@@ -2292,6 +2296,7 @@ declare const ADMIN_FUNCTIONS: {
2292
2296
  readonly getSettings: "__lunora_admin__:getSettings";
2293
2297
  readonly getWorkflowInstanceStatus: "__lunora_admin__:getWorkflowInstanceStatus";
2294
2298
  readonly importShard: "__lunora_admin__:importShard";
2299
+ readonly listFlags: "__lunora_admin__:listFlags";
2295
2300
  readonly listQueues: "__lunora_admin__:listQueues";
2296
2301
  readonly listTables: "__lunora_admin__:listTables";
2297
2302
  readonly listWorkflows: "__lunora_admin__:listWorkflows";
@@ -2572,6 +2577,8 @@ interface StorageRulesResult {
2572
2577
  * package's tests and the studio's fails the build if the two key sets diverge.
2573
2578
  */
2574
2579
  interface StudioFeaturesResult {
2580
+ /** `@lunora/flags` / `ctx.flags` is used, or it is a declared dependency. */
2581
+ flags: boolean;
2575
2582
  /** `@lunora/mail` is imported by a `lunora/` source or a declared dependency. */
2576
2583
  mail: boolean;
2577
2584
  /** `@lunora/payment` is used (import or `ctx.payments`) or a declared dependency. */
@@ -2588,6 +2595,40 @@ interface StudioFeaturesResult {
2588
2595
  workflows: boolean;
2589
2596
  }
2590
2597
  /**
2598
+ * One feature flag evaluated under a supplied targeting context, surfaced by
2599
+ * `__lunora_admin__:listFlags` for the studio's read-only Flags page. The `key`
2600
+ * and `type` are statically discovered by `@lunora/codegen` from the app's
2601
+ * `ctx.flags.<type>("key", …)` reads; `value`/`reason`/`variant`/`errorCode`
2602
+ * come from the live OpenFeature evaluation (the codegen subclass overrides the
2603
+ * base `evaluateFlags` hook). `value` is the resolved flag value as JSON.
2604
+ */
2605
+ interface FlagEvaluation {
2606
+ /** OpenFeature `errorCode` when the evaluation failed (the value falls back to the default). */
2607
+ errorCode?: string;
2608
+ /** The discovered flag key (the first argument of a `ctx.flags.<type>(...)` read). */
2609
+ key: string;
2610
+ /** OpenFeature `reason` for the resolution (`TARGETING_MATCH`, `DEFAULT`, `ERROR`, …). */
2611
+ reason?: string;
2612
+ /** The flag's value type, derived from which `ctx.flags.<type>` method read it. */
2613
+ type: "boolean" | "number" | "object" | "string";
2614
+ /** The resolved value (JSON), or the type default when unconfigured / on error. */
2615
+ value: unknown;
2616
+ /** OpenFeature `variant` identifier when the provider reports one. */
2617
+ variant?: string;
2618
+ }
2619
+ /**
2620
+ * Payload of a `__lunora_admin__:listFlags` call: every statically-discovered
2621
+ * flag evaluated under the supplied targeting context. `configured` is `false`
2622
+ * when the app wires no `@lunora/flags` provider (the base hook), so the studio
2623
+ * can distinguish "no flags configured" from "configured but zero flags read".
2624
+ */
2625
+ interface FlagsResult {
2626
+ /** `true` when an `@lunora/flags` provider is wired (the codegen override ran). */
2627
+ configured: boolean;
2628
+ /** Each discovered flag evaluated under the request's targeting context. */
2629
+ flags: FlagEvaluation[];
2630
+ }
2631
+ /**
2591
2632
  * One declared Cloudflare Workflow, surfaced by `__lunora_admin__:listWorkflows`
2592
2633
  * for the studio's Workflows page. Statically discovered by `@lunora/codegen`
2593
2634
  * from `lunora/workflows.ts` (the codegen subclass overrides the base hook);
@@ -3666,6 +3707,44 @@ declare class SessionDO {
3666
3707
  private handleRevoke;
3667
3708
  }
3668
3709
  /**
3710
+ * Diff the previously-sent list snapshot (`previousJson`, the memo's
3711
+ * `lastJson`) against the new query result and produce per-row
3712
+ * {@link MutationDelta}s the client can merge in place via `applyDelta` —
3713
+ * Convex-parity live-pagination deltas (server half of gap #20).
3714
+ *
3715
+ * Returns `undefined` (caller falls back to a full `{type:"data"}` snapshot)
3716
+ * unless ALL of these hold:
3717
+ *
3718
+ * 1. `previousJson` parses to an array (there IS a previous list to diff against).
3719
+ * 2. `nextResult` is also an array.
3720
+ * 3. Every row in both arrays is a plain object carrying a string `_id`.
3721
+ * 4. Order preservation — rows present in BOTH arrays appear in the same relative order.
3722
+ * 5. Chattiness cap — the number of deltas does not exceed the new array length (a near-total change is cheaper as a snapshot).
3723
+ *
3724
+ * Diff is keyed by `_id`: rows only in prev → `delete`; rows only in next →
3725
+ * `insert`; rows in both whose JSON differs → `update`. Insert/update carry the
3726
+ * full new `row`; delete omits it (matching the wire contract `@lunora/client`
3727
+ * parses). Deltas are ordered deletes-then-inserts/updates so the client never
3728
+ * sees a transient over-length page.
3729
+ *
3730
+ * Per-row serialization is done exactly **once** per refresh (finding #6). Each
3731
+ * row is stringified a single time into a fingerprint reused for both the
3732
+ * `prev !== next` change-detection compare and — when the caller passes the
3733
+ * optional `frames` sink — the pre-serialized delta frame body. The returned
3734
+ * `MutationDelta[]` shape is unchanged; `frames`, when supplied, receives the
3735
+ * exact `JSON.stringify(delta)` string for each returned delta, in the same
3736
+ * order, so the caller can splice it straight into the `{type:"delta"}` frame
3737
+ * without serializing the delta (and the row inside it) a second time.
3738
+ * @returns the per-row deltas to send, or `undefined` when any precondition fails and a full snapshot should be sent instead
3739
+ */
3740
+ declare const subscriptionListDeltas: (previousJson: string, nextResult: unknown, table: string, frames?: string[]) => MutationDelta[] | undefined;
3741
+ /**
3742
+ * Send one WebSocket frame, reporting whether it left the socket. A throw from
3743
+ * `ws.send` (socket closed mid-flush, outbound buffer gone) is the only
3744
+ * delivery-failure signal the runtime exposes; callers use the boolean to decide
3745
+ * whether to advance a subscription's delivered-diff baseline.
3746
+ */
3747
+ /**
3669
3748
  * Optional programmatic log sink, resolved from `createShardDO({ observability })`.
3670
3749
  * Structurally a subset of `@lunora/runtime`'s `ObservabilitySink`, so a user can
3671
3750
  * pass the SAME sink object to `createWorker` (which drives `onRpc`) and
@@ -3918,38 +3997,6 @@ interface RunShardRankPageArgs {
3918
3997
  take?: number;
3919
3998
  }
3920
3999
  /**
3921
- * Diff the previously-sent list snapshot (`previousJson`, the memo's
3922
- * `lastJson`) against the new query result and produce per-row
3923
- * {@link MutationDelta}s the client can merge in place via `applyDelta` —
3924
- * Convex-parity live-pagination deltas (server half of gap #20).
3925
- *
3926
- * Returns `undefined` (caller falls back to a full `{type:"data"}` snapshot)
3927
- * unless ALL of these hold:
3928
- *
3929
- * 1. `previousJson` parses to an array (there IS a previous list to diff against).
3930
- * 2. `nextResult` is also an array.
3931
- * 3. Every row in both arrays is a plain object carrying a string `_id`.
3932
- * 4. Order preservation — rows present in BOTH arrays appear in the same relative order.
3933
- * 5. Chattiness cap — the number of deltas does not exceed the new array length (a near-total change is cheaper as a snapshot).
3934
- *
3935
- * Diff is keyed by `_id`: rows only in prev → `delete`; rows only in next →
3936
- * `insert`; rows in both whose JSON differs → `update`. Insert/update carry the
3937
- * full new `row`; delete omits it (matching the wire contract `@lunora/client`
3938
- * parses). Deltas are ordered deletes-then-inserts/updates so the client never
3939
- * sees a transient over-length page.
3940
- *
3941
- * Per-row serialization is done exactly **once** per refresh (finding #6). Each
3942
- * row is stringified a single time into a fingerprint reused for both the
3943
- * `prev !== next` change-detection compare and — when the caller passes the
3944
- * optional `frames` sink — the pre-serialized delta frame body. The returned
3945
- * `MutationDelta[]` shape is unchanged; `frames`, when supplied, receives the
3946
- * exact `JSON.stringify(delta)` string for each returned delta, in the same
3947
- * order, so the caller can splice it straight into the `{type:"delta"}` frame
3948
- * without serializing the delta (and the row inside it) a second time.
3949
- * @returns the per-row deltas to send, or `undefined` when any precondition fails and a full snapshot should be sent instead
3950
- */
3951
- declare const subscriptionListDeltas: (previousJson: string, nextResult: unknown, table: string, frames?: string[]) => MutationDelta[] | undefined;
3952
- /**
3953
4000
  * Threshold at which a `__root__` DO triggers the size warning. 1 GiB —
3954
4001
  * exactly 10% of the 10 GiB per-DO SQLite ceiling, leaving plenty of runway
3955
4002
  * to plan a `.shardBy()` migration before the wall hits.
@@ -4131,6 +4178,18 @@ declare abstract class ShardDO {
4131
4178
  */
4132
4179
  private pendingChangedTables;
4133
4180
  /**
4181
+ * Coalesced set of tables awaiting a subscription-refresh pass, merged
4182
+ * across every {@link ShardDO.flushChangedTables} call that lands while a
4183
+ * pass is already draining. The single drain loop
4184
+ * ({@link ShardDO.drainSubscriptionRefreshes}) owns this set; a burst of N
4185
+ * writes to the same table therefore collapses into one (or two) refresh
4186
+ * passes instead of N, so each affected subscription's handler re-runs once
4187
+ * per burst rather than once per write. `undefined` when nothing is pending.
4188
+ */
4189
+ private pendingRefreshTables;
4190
+ /** True while {@link ShardDO.drainSubscriptionRefreshes} is running; the single-waiter gate that coalesces concurrent flushes. */
4191
+ private refreshInFlight;
4192
+ /**
4134
4193
  * Last pushed result per `(socket, subId)`, keyed by socket. Lets
4135
4194
  * `refreshSubscriptions` skip re-running queries whose tables were
4136
4195
  * untouched and suppress pushes when the re-run result is unchanged. Held
@@ -4502,6 +4561,26 @@ declare abstract class ShardDO {
4502
4561
  */
4503
4562
  protected studioFeatures(): StudioFeaturesResult;
4504
4563
  /**
4564
+ * Evaluate every statically-discovered feature flag under `context` for the
4565
+ * studio's read-only Flags page (`__lunora_admin__:listFlags`). The flag keys
4566
+ * + value types are discovered by `@lunora/codegen` from the app's
4567
+ * `ctx.flags.<type>("key", …)` reads and evaluated through the configured
4568
+ * `@lunora/flags` provider — work only the codegen subclass can do, so it
4569
+ * overrides this. The base class wires no provider and reports
4570
+ * `configured: false` with zero flags (an un-generated `ShardDO` has none).
4571
+ */
4572
+ protected evaluateFlags(_context?: Record<string, unknown>): Promise<FlagsResult>;
4573
+ /**
4574
+ * Serve one reserved {@link FLAGS_FUNCTION_PREFIX} live flag read for the
4575
+ * React client's `useFlag`/`useFlags`. `functionPath` carries the flag key +
4576
+ * type and `args` the per-subscriber targeting context; the codegen subclass
4577
+ * overrides this to evaluate the flag through the app's `@lunora/flags`
4578
+ * provider under `identity` and return the resolved value. The base class
4579
+ * wires no provider, so it returns `null` — `resolveReactiveOutcome` reads
4580
+ * `null` as "nothing to deliver" and the subscriber keeps its default.
4581
+ */
4582
+ protected runFlagSubscriptionRead(_functionPath: string, _arguments: Record<string, unknown>, _identity?: SubscriptionIdentity): Promise<unknown>;
4583
+ /**
4505
4584
  * The Cloudflare Queues declared by this app, surfaced via
4506
4585
  * `__lunora_admin__:listQueues` for the studio's Queues page. Queues are NOT
4507
4586
  * Durable Objects and hold no shard state, so this is pure declaration
@@ -5013,6 +5092,16 @@ declare abstract class ShardDO {
5013
5092
  */
5014
5093
  private handleGetWorkflowInstanceStatus;
5015
5094
  /**
5095
+ * Serve `__lunora_admin__:listFlags` — the studio's read-only Flags page.
5096
+ * Evaluates every statically-discovered feature flag under an optional
5097
+ * `args.context` targeting context (the studio's editable context editor)
5098
+ * via the {@link evaluateFlags} hook, which the codegen subclass overrides
5099
+ * with live OpenFeature evaluation. Read-only: a flag lookup mutates no shard
5100
+ * state, so nothing is flushed or audited. Admin-gated by `handleAdminRpc`'s
5101
+ * caller.
5102
+ */
5103
+ private handleListFlags;
5104
+ /**
5016
5105
  * Run `run()` with the per-request identity pinned to (`userId`, `identity`),
5017
5106
  * then restore the prior values in a `finally` (even if `run()` throws), so the
5018
5107
  * forced identity can never leak into a later dispatch on this DO instance. The
@@ -5272,6 +5361,16 @@ declare abstract class ShardDO {
5272
5361
  */
5273
5362
  private executeAdminSubscription;
5274
5363
  /**
5364
+ * Resolve one subscription (seed or refresh) to its {@link SubscriptionOutcome}
5365
+ * by routing the `functionPath` to the right read path — shared by
5366
+ * {@link seedSubscription} and {@link refreshSubscriptions} so both branch
5367
+ * identically:
5368
+ * - `__lunora_admin__:*` → {@link executeAdminSubscription} (raw SQLite read).
5369
+ * - {@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`).
5370
+ * - everything else → {@link executeSubscription} (the user query, under the socket's own by-value identity).
5371
+ */
5372
+ private resolveReactiveOutcome;
5373
+ /**
5275
5374
  * Constant-time bearer check against `env.LUNORA_ADMIN_TOKEN`. Returns
5276
5375
  * `false` (closed) when the token is unset so admin introspection is
5277
5376
  * opt-in rather than exposed by default.
@@ -5303,6 +5402,17 @@ declare abstract class ShardDO {
5303
5402
  */
5304
5403
  private flushChangedTables;
5305
5404
  /**
5405
+ * Drain {@link ShardDO.pendingRefreshTables} one coalesced batch at a time
5406
+ * until it is empty, then release the {@link ShardDO.refreshInFlight} gate.
5407
+ * Tables merged by a `flushChangedTables` that lands mid-pass are picked up
5408
+ * by the next loop iteration, so every committed write is observed by a
5409
+ * refresh that runs after it — bursts simply share a pass. The post-write
5410
+ * high-watermark and live-socket set are re-read inside each
5411
+ * `refreshSubscriptions` call, so a later batch always reflects the latest
5412
+ * committed state.
5413
+ */
5414
+ private drainSubscriptionRefreshes;
5415
+ /**
5306
5416
  * For every live subscription whose query reads one of `changed`, re-run
5307
5417
  * the query and push a fresh `{ type: "data" }` frame when the result
5308
5418
  * differs from the last one sent. Subscriptions with no `functionPath`
@@ -5689,4 +5799,4 @@ interface WhereSqlStrategy {
5689
5799
  * `undefined` when the input imposes no constraint (empty `where`).
5690
5800
  */
5691
5801
  declare const compileWhereSql: (where: WhereInput | undefined, strategy: WhereSqlStrategy) => SQL | undefined;
5692
- 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, 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 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, 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, assertValidClientId, backfillAggregateIndexes, backfillRankIndexes, buildFtsMatch, buildSecurityAudit, buildSeekWhere, clearCapturedMail, coerceAggregateNumber, compileWhereSql, containsRelationPredicate, createDependencyTracker, createShardCtxDb, createSystemReader, decodeCursor, depKey, encodeAggregateKey, encodeCursor, encodePartitionKey, ensureAuthMetricsTables, ensureFunctionMetricsTables, ensureMailTable, exportShardRows, exportShardTable, facetColumn, foldAggregateTally, ftsTableName, guardWriter, hasTrigger, importShardRows, isRelationPredicate, listTables, matchesRankStaticWhere, matchesStaticWhere, mergeWhere, normalizeCountArgument, normalizeIdStructurally, normalizeOrderKeys, parseExportShardArgs, parseImportShardArgs, planAggregateLookup, rankTableName, reactiveCacheKey, readAggregateValue, readAuthMetrics, readBookmark, readCapturedMail, readCdcChanges, readFunctionMetricBuckets, readFunctionMetricIndexHits, readFunctionMetrics, readFunctionMetricsTotals, readMigrationStatus, readTablePage, recordAuthEvent, recordCapturedMail, recordFunctionMetric, renderSql, resolveRankPartition, resolveRelationPredicates, resolveWith, runDataMigration, runReadonlySql, runRowValidators, runShardMigrations, runTriggers, scoreDocument, selectExportTables, selectIndexForAggregate, selectIndexForCount, selectIndexForGroupBy, selectMatchingIds, serveRelationFanout, softDeleteScope, sortColumnName, stableStringify, stringifySearchText, subscriptionListDeltas, throwingScheduler, tokenizeSearch, trimCdcChanges, validateImportRow };
5802
+ 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, 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 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 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, 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, assertValidClientId, backfillAggregateIndexes, backfillRankIndexes, buildFtsMatch, buildSecurityAudit, buildSeekWhere, clearCapturedMail, coerceAggregateNumber, compileWhereSql, containsRelationPredicate, createDependencyTracker, createShardCtxDb, createSystemReader, decodeCursor, depKey, encodeAggregateKey, encodeCursor, encodePartitionKey, ensureAuthMetricsTables, ensureFunctionMetricsTables, ensureMailTable, exportShardRows, exportShardTable, facetColumn, foldAggregateTally, ftsTableName, guardWriter, hasTrigger, importShardRows, isRelationPredicate, listTables, matchesRankStaticWhere, matchesStaticWhere, mergeWhere, normalizeCountArgument, normalizeIdStructurally, normalizeOrderKeys, parseExportShardArgs, parseImportShardArgs, planAggregateLookup, rankTableName, reactiveCacheKey, readAggregateValue, readAuthMetrics, readBookmark, readCapturedMail, readCdcChanges, readFunctionMetricBuckets, readFunctionMetricIndexHits, readFunctionMetrics, readFunctionMetricsTotals, readMigrationStatus, readTablePage, recordAuthEvent, recordCapturedMail, recordFunctionMetric, renderSql, resolveRankPartition, resolveRelationPredicates, resolveWith, runDataMigration, runReadonlySql, runRowValidators, runShardMigrations, runTriggers, scoreDocument, selectExportTables, selectIndexForAggregate, selectIndexForCount, selectIndexForGroupBy, selectMatchingIds, serveRelationFanout, softDeleteScope, sortColumnName, stableStringify, stringifySearchText, subscriptionListDeltas, throwingScheduler, tokenizeSearch, trimCdcChanges, validateImportRow };
package/dist/index.d.ts CHANGED
@@ -596,6 +596,7 @@ declare const rankTableName: (table: string, indexName: string) => string;
596
596
  * - `sortValues[i]` === `serializeSqlValue(doc[index.sortBy[i].field])` — the same transform `syncRankIndexEntry` applies to the stored `__sort_k&lt;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.
597
597
  * - `rowId` === `doc._id`, the `__id__` tiebreak.
598
598
  */
599
+ declare const stableStringify: (value: unknown) => string;
599
600
  /** A single memoized result, the deps it read, and any active subscribers. */
600
601
  interface CacheEntry {
601
602
  /** Approximate serialized size of `result`, charged against `maxBytes`. */
@@ -728,16 +729,6 @@ declare class ReactiveCache {
728
729
  private evict;
729
730
  }
730
731
  /**
731
- * Stable, sorted JSON encoding of `args` for use in a cache key. Object keys
732
- * are visited in lexical order at every depth so `{ a: 1, b: 2 }` and
733
- * `{ b: 2, a: 1 }` hash to the same string. Arrays preserve their order
734
- * (the index IS the key). `undefined` values are skipped at the object level
735
- * so `{ a: undefined }` collides with `{}` — matches Convex behavior and
736
- * avoids spurious cache misses on optional args. Inside arrays `undefined`
737
- * encodes as `null` to keep positional semantics.
738
- */
739
- declare const stableStringify: (value: unknown) => string;
740
- /**
741
732
  * Compose a cache key from a function path, a stably-encoded args object, and
742
733
  * the caller's identity discriminator. Exported so the wiring layer and tests
743
734
  * build identical keys without each side reinventing the format.
@@ -2262,6 +2253,19 @@ declare const ADMIN_FUNCTION_PREFIX = "__lunora_admin__:";
2262
2253
  */
2263
2254
  declare const RELATION_FUNCTION_PREFIX = "__lunora_relation__:";
2264
2255
  /**
2256
+ * Reserved `functionPath` prefix for live feature-flag reads. The React client's
2257
+ * `useFlag`/`useFlags` subscribe to `__lunora_flags__:eval` over the same WS
2258
+ * channel as a user query; `ShardDO` intercepts it before user dispatch and
2259
+ * serves it from the codegen-overridden flag-subscription read hook, which
2260
+ * evaluates the flag through the app's OpenFeature provider under the socket's
2261
+ * verified identity. Like the other reserved prefixes it is NOT admin-gated (a
2262
+ * flag read is public, scoped to the subscriber's own targeting context), and
2263
+ * the `__lunora_` namespace is reserved so a real `&lt;file>:&lt;function>` can't
2264
+ * collide. Re-evaluated on every write-flush so values stay live within a
2265
+ * session (provider-side flips with no intervening write surface on reconnect).
2266
+ */
2267
+ declare const FLAGS_FUNCTION_PREFIX = "__lunora_flags__:";
2268
+ /**
2265
2269
  * Fully-qualified reserved paths the data browser invokes. The
2266
2270
  * `__lunora_admin__:` prefix is spelled out inline rather than interpolated so
2267
2271
  * the values stay emittable under `--isolatedDeclarations`.
@@ -2292,6 +2296,7 @@ declare const ADMIN_FUNCTIONS: {
2292
2296
  readonly getSettings: "__lunora_admin__:getSettings";
2293
2297
  readonly getWorkflowInstanceStatus: "__lunora_admin__:getWorkflowInstanceStatus";
2294
2298
  readonly importShard: "__lunora_admin__:importShard";
2299
+ readonly listFlags: "__lunora_admin__:listFlags";
2295
2300
  readonly listQueues: "__lunora_admin__:listQueues";
2296
2301
  readonly listTables: "__lunora_admin__:listTables";
2297
2302
  readonly listWorkflows: "__lunora_admin__:listWorkflows";
@@ -2572,6 +2577,8 @@ interface StorageRulesResult {
2572
2577
  * package's tests and the studio's fails the build if the two key sets diverge.
2573
2578
  */
2574
2579
  interface StudioFeaturesResult {
2580
+ /** `@lunora/flags` / `ctx.flags` is used, or it is a declared dependency. */
2581
+ flags: boolean;
2575
2582
  /** `@lunora/mail` is imported by a `lunora/` source or a declared dependency. */
2576
2583
  mail: boolean;
2577
2584
  /** `@lunora/payment` is used (import or `ctx.payments`) or a declared dependency. */
@@ -2588,6 +2595,40 @@ interface StudioFeaturesResult {
2588
2595
  workflows: boolean;
2589
2596
  }
2590
2597
  /**
2598
+ * One feature flag evaluated under a supplied targeting context, surfaced by
2599
+ * `__lunora_admin__:listFlags` for the studio's read-only Flags page. The `key`
2600
+ * and `type` are statically discovered by `@lunora/codegen` from the app's
2601
+ * `ctx.flags.&lt;type>("key", …)` reads; `value`/`reason`/`variant`/`errorCode`
2602
+ * come from the live OpenFeature evaluation (the codegen subclass overrides the
2603
+ * base `evaluateFlags` hook). `value` is the resolved flag value as JSON.
2604
+ */
2605
+ interface FlagEvaluation {
2606
+ /** OpenFeature `errorCode` when the evaluation failed (the value falls back to the default). */
2607
+ errorCode?: string;
2608
+ /** The discovered flag key (the first argument of a `ctx.flags.&lt;type>(...)` read). */
2609
+ key: string;
2610
+ /** OpenFeature `reason` for the resolution (`TARGETING_MATCH`, `DEFAULT`, `ERROR`, …). */
2611
+ reason?: string;
2612
+ /** The flag's value type, derived from which `ctx.flags.&lt;type>` method read it. */
2613
+ type: "boolean" | "number" | "object" | "string";
2614
+ /** The resolved value (JSON), or the type default when unconfigured / on error. */
2615
+ value: unknown;
2616
+ /** OpenFeature `variant` identifier when the provider reports one. */
2617
+ variant?: string;
2618
+ }
2619
+ /**
2620
+ * Payload of a `__lunora_admin__:listFlags` call: every statically-discovered
2621
+ * flag evaluated under the supplied targeting context. `configured` is `false`
2622
+ * when the app wires no `@lunora/flags` provider (the base hook), so the studio
2623
+ * can distinguish "no flags configured" from "configured but zero flags read".
2624
+ */
2625
+ interface FlagsResult {
2626
+ /** `true` when an `@lunora/flags` provider is wired (the codegen override ran). */
2627
+ configured: boolean;
2628
+ /** Each discovered flag evaluated under the request's targeting context. */
2629
+ flags: FlagEvaluation[];
2630
+ }
2631
+ /**
2591
2632
  * One declared Cloudflare Workflow, surfaced by `__lunora_admin__:listWorkflows`
2592
2633
  * for the studio's Workflows page. Statically discovered by `@lunora/codegen`
2593
2634
  * from `lunora/workflows.ts` (the codegen subclass overrides the base hook);
@@ -3666,6 +3707,44 @@ declare class SessionDO {
3666
3707
  private handleRevoke;
3667
3708
  }
3668
3709
  /**
3710
+ * Diff the previously-sent list snapshot (`previousJson`, the memo's
3711
+ * `lastJson`) against the new query result and produce per-row
3712
+ * {@link MutationDelta}s the client can merge in place via `applyDelta` —
3713
+ * Convex-parity live-pagination deltas (server half of gap #20).
3714
+ *
3715
+ * Returns `undefined` (caller falls back to a full `{type:"data"}` snapshot)
3716
+ * unless ALL of these hold:
3717
+ *
3718
+ * 1. `previousJson` parses to an array (there IS a previous list to diff against).
3719
+ * 2. `nextResult` is also an array.
3720
+ * 3. Every row in both arrays is a plain object carrying a string `_id`.
3721
+ * 4. Order preservation — rows present in BOTH arrays appear in the same relative order.
3722
+ * 5. Chattiness cap — the number of deltas does not exceed the new array length (a near-total change is cheaper as a snapshot).
3723
+ *
3724
+ * Diff is keyed by `_id`: rows only in prev → `delete`; rows only in next →
3725
+ * `insert`; rows in both whose JSON differs → `update`. Insert/update carry the
3726
+ * full new `row`; delete omits it (matching the wire contract `@lunora/client`
3727
+ * parses). Deltas are ordered deletes-then-inserts/updates so the client never
3728
+ * sees a transient over-length page.
3729
+ *
3730
+ * Per-row serialization is done exactly **once** per refresh (finding #6). Each
3731
+ * row is stringified a single time into a fingerprint reused for both the
3732
+ * `prev !== next` change-detection compare and — when the caller passes the
3733
+ * optional `frames` sink — the pre-serialized delta frame body. The returned
3734
+ * `MutationDelta[]` shape is unchanged; `frames`, when supplied, receives the
3735
+ * exact `JSON.stringify(delta)` string for each returned delta, in the same
3736
+ * order, so the caller can splice it straight into the `{type:"delta"}` frame
3737
+ * without serializing the delta (and the row inside it) a second time.
3738
+ * @returns the per-row deltas to send, or `undefined` when any precondition fails and a full snapshot should be sent instead
3739
+ */
3740
+ declare const subscriptionListDeltas: (previousJson: string, nextResult: unknown, table: string, frames?: string[]) => MutationDelta[] | undefined;
3741
+ /**
3742
+ * Send one WebSocket frame, reporting whether it left the socket. A throw from
3743
+ * `ws.send` (socket closed mid-flush, outbound buffer gone) is the only
3744
+ * delivery-failure signal the runtime exposes; callers use the boolean to decide
3745
+ * whether to advance a subscription's delivered-diff baseline.
3746
+ */
3747
+ /**
3669
3748
  * Optional programmatic log sink, resolved from `createShardDO({ observability })`.
3670
3749
  * Structurally a subset of `@lunora/runtime`'s `ObservabilitySink`, so a user can
3671
3750
  * pass the SAME sink object to `createWorker` (which drives `onRpc`) and
@@ -3918,38 +3997,6 @@ interface RunShardRankPageArgs {
3918
3997
  take?: number;
3919
3998
  }
3920
3999
  /**
3921
- * Diff the previously-sent list snapshot (`previousJson`, the memo's
3922
- * `lastJson`) against the new query result and produce per-row
3923
- * {@link MutationDelta}s the client can merge in place via `applyDelta` —
3924
- * Convex-parity live-pagination deltas (server half of gap #20).
3925
- *
3926
- * Returns `undefined` (caller falls back to a full `{type:"data"}` snapshot)
3927
- * unless ALL of these hold:
3928
- *
3929
- * 1. `previousJson` parses to an array (there IS a previous list to diff against).
3930
- * 2. `nextResult` is also an array.
3931
- * 3. Every row in both arrays is a plain object carrying a string `_id`.
3932
- * 4. Order preservation — rows present in BOTH arrays appear in the same relative order.
3933
- * 5. Chattiness cap — the number of deltas does not exceed the new array length (a near-total change is cheaper as a snapshot).
3934
- *
3935
- * Diff is keyed by `_id`: rows only in prev → `delete`; rows only in next →
3936
- * `insert`; rows in both whose JSON differs → `update`. Insert/update carry the
3937
- * full new `row`; delete omits it (matching the wire contract `@lunora/client`
3938
- * parses). Deltas are ordered deletes-then-inserts/updates so the client never
3939
- * sees a transient over-length page.
3940
- *
3941
- * Per-row serialization is done exactly **once** per refresh (finding #6). Each
3942
- * row is stringified a single time into a fingerprint reused for both the
3943
- * `prev !== next` change-detection compare and — when the caller passes the
3944
- * optional `frames` sink — the pre-serialized delta frame body. The returned
3945
- * `MutationDelta[]` shape is unchanged; `frames`, when supplied, receives the
3946
- * exact `JSON.stringify(delta)` string for each returned delta, in the same
3947
- * order, so the caller can splice it straight into the `{type:"delta"}` frame
3948
- * without serializing the delta (and the row inside it) a second time.
3949
- * @returns the per-row deltas to send, or `undefined` when any precondition fails and a full snapshot should be sent instead
3950
- */
3951
- declare const subscriptionListDeltas: (previousJson: string, nextResult: unknown, table: string, frames?: string[]) => MutationDelta[] | undefined;
3952
- /**
3953
4000
  * Threshold at which a `__root__` DO triggers the size warning. 1 GiB —
3954
4001
  * exactly 10% of the 10 GiB per-DO SQLite ceiling, leaving plenty of runway
3955
4002
  * to plan a `.shardBy()` migration before the wall hits.
@@ -4131,6 +4178,18 @@ declare abstract class ShardDO {
4131
4178
  */
4132
4179
  private pendingChangedTables;
4133
4180
  /**
4181
+ * Coalesced set of tables awaiting a subscription-refresh pass, merged
4182
+ * across every {@link ShardDO.flushChangedTables} call that lands while a
4183
+ * pass is already draining. The single drain loop
4184
+ * ({@link ShardDO.drainSubscriptionRefreshes}) owns this set; a burst of N
4185
+ * writes to the same table therefore collapses into one (or two) refresh
4186
+ * passes instead of N, so each affected subscription's handler re-runs once
4187
+ * per burst rather than once per write. `undefined` when nothing is pending.
4188
+ */
4189
+ private pendingRefreshTables;
4190
+ /** True while {@link ShardDO.drainSubscriptionRefreshes} is running; the single-waiter gate that coalesces concurrent flushes. */
4191
+ private refreshInFlight;
4192
+ /**
4134
4193
  * Last pushed result per `(socket, subId)`, keyed by socket. Lets
4135
4194
  * `refreshSubscriptions` skip re-running queries whose tables were
4136
4195
  * untouched and suppress pushes when the re-run result is unchanged. Held
@@ -4502,6 +4561,26 @@ declare abstract class ShardDO {
4502
4561
  */
4503
4562
  protected studioFeatures(): StudioFeaturesResult;
4504
4563
  /**
4564
+ * Evaluate every statically-discovered feature flag under `context` for the
4565
+ * studio's read-only Flags page (`__lunora_admin__:listFlags`). The flag keys
4566
+ * + value types are discovered by `@lunora/codegen` from the app's
4567
+ * `ctx.flags.&lt;type>("key", …)` reads and evaluated through the configured
4568
+ * `@lunora/flags` provider — work only the codegen subclass can do, so it
4569
+ * overrides this. The base class wires no provider and reports
4570
+ * `configured: false` with zero flags (an un-generated `ShardDO` has none).
4571
+ */
4572
+ protected evaluateFlags(_context?: Record<string, unknown>): Promise<FlagsResult>;
4573
+ /**
4574
+ * Serve one reserved {@link FLAGS_FUNCTION_PREFIX} live flag read for the
4575
+ * React client's `useFlag`/`useFlags`. `functionPath` carries the flag key +
4576
+ * type and `args` the per-subscriber targeting context; the codegen subclass
4577
+ * overrides this to evaluate the flag through the app's `@lunora/flags`
4578
+ * provider under `identity` and return the resolved value. The base class
4579
+ * wires no provider, so it returns `null` — `resolveReactiveOutcome` reads
4580
+ * `null` as "nothing to deliver" and the subscriber keeps its default.
4581
+ */
4582
+ protected runFlagSubscriptionRead(_functionPath: string, _arguments: Record<string, unknown>, _identity?: SubscriptionIdentity): Promise<unknown>;
4583
+ /**
4505
4584
  * The Cloudflare Queues declared by this app, surfaced via
4506
4585
  * `__lunora_admin__:listQueues` for the studio's Queues page. Queues are NOT
4507
4586
  * Durable Objects and hold no shard state, so this is pure declaration
@@ -5013,6 +5092,16 @@ declare abstract class ShardDO {
5013
5092
  */
5014
5093
  private handleGetWorkflowInstanceStatus;
5015
5094
  /**
5095
+ * Serve `__lunora_admin__:listFlags` — the studio's read-only Flags page.
5096
+ * Evaluates every statically-discovered feature flag under an optional
5097
+ * `args.context` targeting context (the studio's editable context editor)
5098
+ * via the {@link evaluateFlags} hook, which the codegen subclass overrides
5099
+ * with live OpenFeature evaluation. Read-only: a flag lookup mutates no shard
5100
+ * state, so nothing is flushed or audited. Admin-gated by `handleAdminRpc`'s
5101
+ * caller.
5102
+ */
5103
+ private handleListFlags;
5104
+ /**
5016
5105
  * Run `run()` with the per-request identity pinned to (`userId`, `identity`),
5017
5106
  * then restore the prior values in a `finally` (even if `run()` throws), so the
5018
5107
  * forced identity can never leak into a later dispatch on this DO instance. The
@@ -5272,6 +5361,16 @@ declare abstract class ShardDO {
5272
5361
  */
5273
5362
  private executeAdminSubscription;
5274
5363
  /**
5364
+ * Resolve one subscription (seed or refresh) to its {@link SubscriptionOutcome}
5365
+ * by routing the `functionPath` to the right read path — shared by
5366
+ * {@link seedSubscription} and {@link refreshSubscriptions} so both branch
5367
+ * identically:
5368
+ * - `__lunora_admin__:*` → {@link executeAdminSubscription} (raw SQLite read).
5369
+ * - {@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`).
5370
+ * - everything else → {@link executeSubscription} (the user query, under the socket's own by-value identity).
5371
+ */
5372
+ private resolveReactiveOutcome;
5373
+ /**
5275
5374
  * Constant-time bearer check against `env.LUNORA_ADMIN_TOKEN`. Returns
5276
5375
  * `false` (closed) when the token is unset so admin introspection is
5277
5376
  * opt-in rather than exposed by default.
@@ -5303,6 +5402,17 @@ declare abstract class ShardDO {
5303
5402
  */
5304
5403
  private flushChangedTables;
5305
5404
  /**
5405
+ * Drain {@link ShardDO.pendingRefreshTables} one coalesced batch at a time
5406
+ * until it is empty, then release the {@link ShardDO.refreshInFlight} gate.
5407
+ * Tables merged by a `flushChangedTables` that lands mid-pass are picked up
5408
+ * by the next loop iteration, so every committed write is observed by a
5409
+ * refresh that runs after it — bursts simply share a pass. The post-write
5410
+ * high-watermark and live-socket set are re-read inside each
5411
+ * `refreshSubscriptions` call, so a later batch always reflects the latest
5412
+ * committed state.
5413
+ */
5414
+ private drainSubscriptionRefreshes;
5415
+ /**
5306
5416
  * For every live subscription whose query reads one of `changed`, re-run
5307
5417
  * the query and push a fresh `{ type: "data" }` frame when the result
5308
5418
  * differs from the last one sent. Subscriptions with no `functionPath`
@@ -5689,4 +5799,4 @@ interface WhereSqlStrategy {
5689
5799
  * `undefined` when the input imposes no constraint (empty `where`).
5690
5800
  */
5691
5801
  declare const compileWhereSql: (where: WhereInput | undefined, strategy: WhereSqlStrategy) => SQL | undefined;
5692
- 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, 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 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, 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, assertValidClientId, backfillAggregateIndexes, backfillRankIndexes, buildFtsMatch, buildSecurityAudit, buildSeekWhere, clearCapturedMail, coerceAggregateNumber, compileWhereSql, containsRelationPredicate, createDependencyTracker, createShardCtxDb, createSystemReader, decodeCursor, depKey, encodeAggregateKey, encodeCursor, encodePartitionKey, ensureAuthMetricsTables, ensureFunctionMetricsTables, ensureMailTable, exportShardRows, exportShardTable, facetColumn, foldAggregateTally, ftsTableName, guardWriter, hasTrigger, importShardRows, isRelationPredicate, listTables, matchesRankStaticWhere, matchesStaticWhere, mergeWhere, normalizeCountArgument, normalizeIdStructurally, normalizeOrderKeys, parseExportShardArgs, parseImportShardArgs, planAggregateLookup, rankTableName, reactiveCacheKey, readAggregateValue, readAuthMetrics, readBookmark, readCapturedMail, readCdcChanges, readFunctionMetricBuckets, readFunctionMetricIndexHits, readFunctionMetrics, readFunctionMetricsTotals, readMigrationStatus, readTablePage, recordAuthEvent, recordCapturedMail, recordFunctionMetric, renderSql, resolveRankPartition, resolveRelationPredicates, resolveWith, runDataMigration, runReadonlySql, runRowValidators, runShardMigrations, runTriggers, scoreDocument, selectExportTables, selectIndexForAggregate, selectIndexForCount, selectIndexForGroupBy, selectMatchingIds, serveRelationFanout, softDeleteScope, sortColumnName, stableStringify, stringifySearchText, subscriptionListDeltas, throwingScheduler, tokenizeSearch, trimCdcChanges, validateImportRow };
5802
+ 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, 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 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 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, 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, assertValidClientId, backfillAggregateIndexes, backfillRankIndexes, buildFtsMatch, buildSecurityAudit, buildSeekWhere, clearCapturedMail, coerceAggregateNumber, compileWhereSql, containsRelationPredicate, createDependencyTracker, createShardCtxDb, createSystemReader, decodeCursor, depKey, encodeAggregateKey, encodeCursor, encodePartitionKey, ensureAuthMetricsTables, ensureFunctionMetricsTables, ensureMailTable, exportShardRows, exportShardTable, facetColumn, foldAggregateTally, ftsTableName, guardWriter, hasTrigger, importShardRows, isRelationPredicate, listTables, matchesRankStaticWhere, matchesStaticWhere, mergeWhere, normalizeCountArgument, normalizeIdStructurally, normalizeOrderKeys, parseExportShardArgs, parseImportShardArgs, planAggregateLookup, rankTableName, reactiveCacheKey, readAggregateValue, readAuthMetrics, readBookmark, readCapturedMail, readCdcChanges, readFunctionMetricBuckets, readFunctionMetricIndexHits, readFunctionMetrics, readFunctionMetricsTotals, readMigrationStatus, readTablePage, recordAuthEvent, recordCapturedMail, recordFunctionMetric, renderSql, resolveRankPartition, resolveRelationPredicates, resolveWith, runDataMigration, runReadonlySql, runRowValidators, runShardMigrations, runTriggers, scoreDocument, selectExportTables, selectIndexForAggregate, selectIndexForCount, selectIndexForGroupBy, selectMatchingIds, serveRelationFanout, softDeleteScope, sortColumnName, stableStringify, stringifySearchText, subscriptionListDeltas, throwingScheduler, tokenizeSearch, trimCdcChanges, validateImportRow };
package/dist/index.mjs CHANGED
@@ -8,22 +8,22 @@ export { DATA_MIGRATION_STATE_TABLE, readMigrationStatus, runDataMigration } fro
8
8
  export { SCAN_DEP, createDependencyTracker, depKey } from './packem_shared/SCAN_DEP-DLJF8dsj.mjs';
9
9
  export { renderSql } from './packem_shared/renderSql-D6eUcn2N.mjs';
10
10
  export { FUNCTION_METRICS_BUCKETS_TABLE, FUNCTION_METRICS_BUCKET_MS, FUNCTION_METRICS_BUCKET_RETENTION, FUNCTION_METRICS_INDEX_TABLE, FUNCTION_METRICS_TABLE, ensureFunctionMetricsTables, readFunctionMetricBuckets, readFunctionMetricIndexHits, readFunctionMetrics, readFunctionMetricsTotals, recordFunctionMetric } from './packem_shared/FUNCTION_METRICS_BUCKETS_TABLE-UDNVD7FS.mjs';
11
- export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, RELATION_FUNCTION_PREFIX, facetColumn, listTables, readTablePage, selectMatchingIds } from './packem_shared/ADMIN_FUNCTIONS-CHcC8fKV.mjs';
11
+ export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, FLAGS_FUNCTION_PREFIX, RELATION_FUNCTION_PREFIX, facetColumn, listTables, readTablePage, selectMatchingIds } from './packem_shared/ADMIN_FUNCTIONS-D_UiYJFk.mjs';
12
12
  export { LogBuffer } from './packem_shared/LogBuffer-B_Ezju_N.mjs';
13
13
  export { MAIL_RETENTION, MAIL_TABLE, clearCapturedMail, ensureMailTable, readCapturedMail, recordCapturedMail } from './packem_shared/MAIL_RETENTION-CPpgl-dX.mjs';
14
14
  export { default as NotFoundError } from './packem_shared/NotFoundError-CMuMZt81.mjs';
15
15
  export { armRestore, readBookmark } from './packem_shared/armRestore-BJk53Ro8.mjs';
16
16
  export { applySelect, buildSeekWhere, decodeCursor, encodeCursor, normalizeOrderKeys, softDeleteScope } from './packem_shared/applySelect-BvZdFUBT.mjs';
17
17
  export { R as RANK_TIEBREAK, e as encodePartitionKey, m as matchesRankStaticWhere, r as rankTableName, a as resolveRankPartition, s as sortColumnName } from './packem_shared/rank-CrkEIpF4.mjs';
18
- export { ReactiveCache, reactiveCacheKey, stableStringify } from './packem_shared/ReactiveCache-ByVzgH3d.mjs';
19
- export { serveRelationFanout } from './packem_shared/serveRelationFanout-oxaM6_WL.mjs';
18
+ export { ReactiveCache, reactiveCacheKey } from './packem_shared/ReactiveCache-1hDydFyv.mjs';
19
+ export { serveRelationFanout } from './packem_shared/serveRelationFanout-C6lDaesn.mjs';
20
20
  export { DEFAULT_MAX_RELATION_KEYS, assertFlatPredicate, containsRelationPredicate, isRelationPredicate, resolveRelationPredicates } from './packem_shared/DEFAULT_MAX_RELATION_KEYS-Dou2PWdO.mjs';
21
21
  export { applyOnDelete, resolveWith, runRowValidators } from './packem_shared/applyOnDelete-sA7o1CqD.mjs';
22
22
  export { RLS_UNWRAP_SYMBOL, RlsRequiredError, guardWriter } from './packem_shared/RLS_UNWRAP_SYMBOL-EtGQdC9d.mjs';
23
23
  export { buildFtsMatch, ftsTableName, scoreDocument, stringifySearchText, tokenizeSearch } from './packem_shared/buildFtsMatch-BLEMawrp.mjs';
24
24
  export { M as MIN_ADMIN_TOKEN_LENGTH, a as MIN_AUTH_SECRET_LENGTH, b as buildSecurityAudit } from './packem_shared/security-audit-CucgBice.mjs';
25
25
  export { SESSION_DO_TTL_DEFAULT, SessionDO } from './packem_shared/SESSION_DO_TTL_DEFAULT-ilPZsVwu.mjs';
26
- export { ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, ShardDO, subscriptionListDeltas } from './packem_shared/ROOT_DO_SIZE_WARN_BYTES-DfwcxW8F.mjs';
26
+ export { ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, ShardDO } from './packem_shared/ROOT_DO_SIZE_WARN_BYTES-BCz6GIDw.mjs';
27
27
  export { SHARD_REGISTRY_DO_NAME, ShardRegistryDO } from './packem_shared/SHARD_REGISTRY_DO_NAME-BsAbi5Mn.mjs';
28
28
  export { MAX_SQL_ROWS, assertReadonly, runReadonlySql } from './packem_shared/MAX_SQL_ROWS-dDcFE1YZ.mjs';
29
29
  export { createSystemReader } from './packem_shared/createSystemReader-8CzSZP9V.mjs';
@@ -33,3 +33,5 @@ export { compileWhereSql } from './packem_shared/compileWhereSql-CXrhFA3G.mjs';
33
33
  export { CDC_LOG_TABLE, applyCdcChanges, readCdcChanges, trimCdcChanges } from './packem_shared/CDC_LOG_TABLE-Ctdmxmrv.mjs';
34
34
  export { backfillAggregateIndexes, backfillRankIndexes } from './packem_shared/backfillAggregateIndexes-BbVPvciS.mjs';
35
35
  export { runShardMigrations } from './packem_shared/runShardMigrations-PabobOjF.mjs';
36
+ export { stableStringify } from './packem_shared/stableStringify-CyHKJXre.mjs';
37
+ export { subscriptionListDeltas } from './packem_shared/subscriptionListDeltas-ce84gpwL.mjs';
@@ -1,5 +1,6 @@
1
1
  const ADMIN_FUNCTION_PREFIX = "__lunora_admin__:";
2
2
  const RELATION_FUNCTION_PREFIX = "__lunora_relation__:";
3
+ const FLAGS_FUNCTION_PREFIX = "__lunora_flags__:";
3
4
  const ADMIN_FUNCTIONS = {
4
5
  applyCdc: "__lunora_admin__:applyCdc",
5
6
  cdcSync: "__lunora_admin__:cdcSync",
@@ -27,6 +28,7 @@ const ADMIN_FUNCTIONS = {
27
28
  // eslint-disable-next-line no-secrets/no-secrets -- reserved admin RPC path constant, not a credential
28
29
  getWorkflowInstanceStatus: "__lunora_admin__:getWorkflowInstanceStatus",
29
30
  importShard: "__lunora_admin__:importShard",
31
+ listFlags: "__lunora_admin__:listFlags",
30
32
  listQueues: "__lunora_admin__:listQueues",
31
33
  listTables: "__lunora_admin__:listTables",
32
34
  listWorkflows: "__lunora_admin__:listWorkflows",
@@ -311,4 +313,4 @@ const summarizeSubscriptions = (attachments) => {
311
313
  return { connections, totalConnections: connections.length, totalSubscriptions };
312
314
  };
313
315
 
314
- export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, MAX_PAGE_SIZE, RELATION_FUNCTION_PREFIX, facetColumn, findStorageReferences, listTables, readTablePage, selectMatchingIds, summarizeSubscriptions };
316
+ export { ADMIN_FUNCTIONS, ADMIN_FUNCTION_PREFIX, FLAGS_FUNCTION_PREFIX, MAX_PAGE_SIZE, RELATION_FUNCTION_PREFIX, facetColumn, findStorageReferences, listTables, readTablePage, selectMatchingIds, summarizeSubscriptions };
@@ -4,14 +4,15 @@ import { recordAuthEvent, readAuthMetrics } from './AUTH_METRICS_BUCKETS_TABLE-C
4
4
  import { DATA_MIGRATION_STATE_TABLE, readMigrationStatus } from './DATA_MIGRATION_STATE_TABLE-PTtTiQ7U.mjs';
5
5
  import { SCAN_DEP, createDependencyTracker, tableFromDepKey } from './SCAN_DEP-DLJF8dsj.mjs';
6
6
  import { readFunctionMetricsTotals, readFunctionMetricIndexHits, recordFunctionMetric, mergeScanAttribution, readFunctionMetrics, readFunctionMetricBuckets } from './FUNCTION_METRICS_BUCKETS_TABLE-UDNVD7FS.mjs';
7
- import { ADMIN_FUNCTION_PREFIX, RELATION_FUNCTION_PREFIX, selectMatchingIds, ADMIN_FUNCTIONS, findStorageReferences, listTables, summarizeSubscriptions, readTablePage, facetColumn, MAX_PAGE_SIZE } from './ADMIN_FUNCTIONS-CHcC8fKV.mjs';
7
+ import { ADMIN_FUNCTION_PREFIX, RELATION_FUNCTION_PREFIX, selectMatchingIds, ADMIN_FUNCTIONS, findStorageReferences, listTables, summarizeSubscriptions, readTablePage, facetColumn, FLAGS_FUNCTION_PREFIX, MAX_PAGE_SIZE } from './ADMIN_FUNCTIONS-D_UiYJFk.mjs';
8
8
  import { LogBuffer } from './LogBuffer-B_Ezju_N.mjs';
9
9
  import { recordCapturedMail, clearCapturedMail, readCapturedMail, MAIL_TABLE } from './MAIL_RETENTION-CPpgl-dX.mjs';
10
10
  import { readBookmark, armRestore } from './armRestore-BJk53Ro8.mjs';
11
- import { ReactiveCache, reactiveCacheKey } from './ReactiveCache-ByVzgH3d.mjs';
11
+ import { ReactiveCache, reactiveCacheKey } from './ReactiveCache-1hDydFyv.mjs';
12
12
  import { redact, standardRules } from '@visulima/redact';
13
13
  import { i as isDevEnvironment, c as buildSettings, b as buildSecurityAudit } from './security-audit-CucgBice.mjs';
14
14
  import { runReadonlySql } from './MAX_SQL_ROWS-dDcFE1YZ.mjs';
15
+ import { trySendFrame, subscriptionListDeltas, sendDeltaFrames } from './subscriptionListDeltas-ce84gpwL.mjs';
15
16
  import { ConflictError } from './ConflictError-C0STs6bU.mjs';
16
17
  import { CDC_LOG_TABLE, readCdcChanges, readCdcCursor, readCdcEpoch, minCdcSeq, bumpCdcEpoch } from './CDC_LOG_TABLE-Ctdmxmrv.mjs';
17
18
  import { r as readIdempotent, w as writeIdempotent, t as trimIdempotent } from './ctx-db-idempotency-DkC9rP91.mjs';
@@ -413,97 +414,7 @@ const findDanglingReferences = (sql, storageColumns, liveKeys) => {
413
414
 
414
415
  const WS_KEEPALIVE_PING = "lunora-ping";
415
416
  const WS_KEEPALIVE_PONG = "lunora-pong";
416
- const ROW_ID_FIELD = "_id";
417
- const DELTA_FALLBACK_TABLE = "__lunora__";
418
- const readRowId = (row) => {
419
- if (typeof row !== "object" || row === null || Array.isArray(row)) {
420
- return void 0;
421
- }
422
- const id = row[ROW_ID_FIELD];
423
- return typeof id === "string" ? id : void 0;
424
- };
425
- const indexRowsById = (rows) => {
426
- const byId = /* @__PURE__ */ new Map();
427
- const order = [];
428
- for (const row of rows) {
429
- const id = readRowId(row);
430
- if (id === void 0 || byId.has(id)) {
431
- return void 0;
432
- }
433
- byId.set(id, row);
434
- order.push(id);
435
- }
436
- return { byId, order };
437
- };
438
- const survivorsKeepOrder = (previous, next) => {
439
- const survivingPrevious = previous.order.filter((id) => next.byId.has(id));
440
- const survivingNext = next.order.filter((id) => previous.byId.has(id));
441
- if (survivingPrevious.length !== survivingNext.length) {
442
- return false;
443
- }
444
- return survivingPrevious.every((id, index) => survivingNext[index] === id);
445
- };
446
- const collectDeleteDeltas = (previous, next, deltaTable, tableJson) => {
447
- const out = [];
448
- for (const id of previous.order) {
449
- if (!next.byId.has(id)) {
450
- out.push({
451
- delta: { key: id, op: "delete", table: deltaTable },
452
- frame: `{"key":${JSON.stringify(id)},"op":"delete","table":${tableJson}}`
453
- });
454
- }
455
- }
456
- return out;
457
- };
458
- const collectUpsertDeltas = (previous, next, deltaTable, tableJson) => {
459
- const out = [];
460
- for (const id of next.order) {
461
- const nextRow = next.byId.get(id);
462
- const previousRow = previous.byId.get(id);
463
- const nextFingerprint = JSON.stringify(nextRow);
464
- const previousFingerprint = previousRow === void 0 ? void 0 : JSON.stringify(previousRow);
465
- if (previousFingerprint === nextFingerprint) {
466
- continue;
467
- }
468
- const op = previousFingerprint === void 0 ? "insert" : "update";
469
- out.push({
470
- delta: { key: id, op, row: nextRow, table: deltaTable },
471
- frame: `{"key":${JSON.stringify(id)},"op":"${op}","row":${nextFingerprint},"table":${tableJson}}`
472
- });
473
- }
474
- return out;
475
- };
476
- const subscriptionListDeltas = (previousJson, nextResult, table, frames) => {
477
- let parsed;
478
- try {
479
- parsed = JSON.parse(previousJson);
480
- } catch {
481
- return void 0;
482
- }
483
- if (!Array.isArray(parsed) || !Array.isArray(nextResult)) {
484
- return void 0;
485
- }
486
- const previous = indexRowsById(parsed);
487
- const next = indexRowsById(nextResult);
488
- if (previous === void 0 || next === void 0) {
489
- return void 0;
490
- }
491
- if (!survivorsKeepOrder(previous, next)) {
492
- return void 0;
493
- }
494
- const deltaTable = table === "" ? DELTA_FALLBACK_TABLE : table;
495
- const tableJson = JSON.stringify(deltaTable);
496
- const framed = [...collectDeleteDeltas(previous, next, deltaTable, tableJson), ...collectUpsertDeltas(previous, next, deltaTable, tableJson)];
497
- if (framed.length > next.order.length) {
498
- return void 0;
499
- }
500
- if (frames !== void 0) {
501
- for (const { frame } of framed) {
502
- frames.push(frame);
503
- }
504
- }
505
- return framed.map(({ delta }) => delta);
506
- };
417
+ const UNDELIVERED_BASELINE = "<undelivered>";
507
418
  const ROOT_DO_SIZE_WARN_BYTES = 1073741824;
508
419
  const CDC_RESUME_SCAN_LIMIT = 1e4;
509
420
  const IDEMPOTENCY_RETENTION_MS = 864e5;
@@ -1133,6 +1044,18 @@ class ShardDO {
1133
1044
  * the common read-only path allocates nothing.
1134
1045
  */
1135
1046
  pendingChangedTables = void 0;
1047
+ /**
1048
+ * Coalesced set of tables awaiting a subscription-refresh pass, merged
1049
+ * across every {@link ShardDO.flushChangedTables} call that lands while a
1050
+ * pass is already draining. The single drain loop
1051
+ * ({@link ShardDO.drainSubscriptionRefreshes}) owns this set; a burst of N
1052
+ * writes to the same table therefore collapses into one (or two) refresh
1053
+ * passes instead of N, so each affected subscription's handler re-runs once
1054
+ * per burst rather than once per write. `undefined` when nothing is pending.
1055
+ */
1056
+ pendingRefreshTables = void 0;
1057
+ /** True while {@link ShardDO.drainSubscriptionRefreshes} is running; the single-waiter gate that coalesces concurrent flushes. */
1058
+ refreshInFlight = false;
1136
1059
  /**
1137
1060
  * Last pushed result per `(socket, subId)`, keyed by socket. Lets
1138
1061
  * `refreshSubscriptions` skip re-running queries whose tables were
@@ -1853,7 +1776,33 @@ class ShardDO {
1853
1776
  */
1854
1777
  // eslint-disable-next-line class-methods-use-this -- base-class override hook: the codegen subclass overrides this with the statically-discovered feature flags
1855
1778
  studioFeatures() {
1856
- return { mail: false, payments: false, queues: false, scheduler: false, storage: false, vectors: false, workflows: false };
1779
+ return { flags: false, mail: false, payments: false, queues: false, scheduler: false, storage: false, vectors: false, workflows: false };
1780
+ }
1781
+ /**
1782
+ * Evaluate every statically-discovered feature flag under `context` for the
1783
+ * studio's read-only Flags page (`__lunora_admin__:listFlags`). The flag keys
1784
+ * + value types are discovered by `@lunora/codegen` from the app's
1785
+ * `ctx.flags.&lt;type>("key", …)` reads and evaluated through the configured
1786
+ * `@lunora/flags` provider — work only the codegen subclass can do, so it
1787
+ * overrides this. The base class wires no provider and reports
1788
+ * `configured: false` with zero flags (an un-generated `ShardDO` has none).
1789
+ */
1790
+ // eslint-disable-next-line class-methods-use-this -- base-class override hook: the codegen subclass overrides this with live OpenFeature evaluation over the discovered flag keys
1791
+ evaluateFlags(_context) {
1792
+ return Promise.resolve({ configured: false, flags: [] });
1793
+ }
1794
+ /**
1795
+ * Serve one reserved {@link FLAGS_FUNCTION_PREFIX} live flag read for the
1796
+ * React client's `useFlag`/`useFlags`. `functionPath` carries the flag key +
1797
+ * type and `args` the per-subscriber targeting context; the codegen subclass
1798
+ * overrides this to evaluate the flag through the app's `@lunora/flags`
1799
+ * provider under `identity` and return the resolved value. The base class
1800
+ * wires no provider, so it returns `null` — `resolveReactiveOutcome` reads
1801
+ * `null` as "nothing to deliver" and the subscriber keeps its default.
1802
+ */
1803
+ // eslint-disable-next-line class-methods-use-this -- base-class override hook: the codegen subclass overrides this to evaluate the flag through the configured provider
1804
+ runFlagSubscriptionRead(_functionPath, _arguments, _identity) {
1805
+ return Promise.resolve(null);
1857
1806
  }
1858
1807
  /**
1859
1808
  * The Cloudflare Queues declared by this app, surfaced via
@@ -2249,10 +2198,7 @@ class ShardDO {
2249
2198
  if (!this.matchesSubscription(query, delta)) {
2250
2199
  continue;
2251
2200
  }
2252
- try {
2253
- ws.send(`{"type":"delta","id":${JSON.stringify(subId)},"delta":${deltaJson}}`);
2254
- } catch {
2255
- }
2201
+ trySendFrame(ws, `{"type":"delta","id":${JSON.stringify(subId)},"delta":${deltaJson}}`);
2256
2202
  }
2257
2203
  }
2258
2204
  }
@@ -2808,6 +2754,9 @@ class ShardDO {
2808
2754
  if (functionPath === ADMIN_FUNCTIONS.getWorkflowInstanceStatus) {
2809
2755
  return this.handleGetWorkflowInstanceStatus(args);
2810
2756
  }
2757
+ if (functionPath === ADMIN_FUNCTIONS.listFlags) {
2758
+ return this.handleListFlags(args);
2759
+ }
2811
2760
  return this.handlePitrAdminOp(functionPath, args);
2812
2761
  }
2813
2762
  /**
@@ -2928,6 +2877,21 @@ class ShardDO {
2928
2877
  return jsonResponse({ result }, 200);
2929
2878
  }
2930
2879
  /* eslint-enable no-secrets/no-secrets */
2880
+ /**
2881
+ * Serve `__lunora_admin__:listFlags` — the studio's read-only Flags page.
2882
+ * Evaluates every statically-discovered feature flag under an optional
2883
+ * `args.context` targeting context (the studio's editable context editor)
2884
+ * via the {@link evaluateFlags} hook, which the codegen subclass overrides
2885
+ * with live OpenFeature evaluation. Read-only: a flag lookup mutates no shard
2886
+ * state, so nothing is flushed or audited. Admin-gated by `handleAdminRpc`'s
2887
+ * caller.
2888
+ */
2889
+ async handleListFlags(args) {
2890
+ const rawContext = args.context;
2891
+ const context = typeof rawContext === "object" && rawContext !== null && !Array.isArray(rawContext) ? rawContext : void 0;
2892
+ const result = await this.evaluateFlags(context);
2893
+ return jsonResponse({ result }, 200);
2894
+ }
2931
2895
  /**
2932
2896
  * Run `run()` with the per-request identity pinned to (`userId`, `identity`),
2933
2897
  * then restore the prior values in a `finally` (even if `run()` throws), so the
@@ -3469,6 +3433,25 @@ class ShardDO {
3469
3433
  const read = this.readAdminOp(functionPath, args);
3470
3434
  return read ? { result: read.result, tables: read.tables } : null;
3471
3435
  }
3436
+ /**
3437
+ * Resolve one subscription (seed or refresh) to its {@link SubscriptionOutcome}
3438
+ * by routing the `functionPath` to the right read path — shared by
3439
+ * {@link seedSubscription} and {@link refreshSubscriptions} so both branch
3440
+ * identically:
3441
+ * - `__lunora_admin__:*` → {@link executeAdminSubscription} (raw SQLite read).
3442
+ * - {@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`).
3443
+ * - everything else → {@link executeSubscription} (the user query, under the socket's own by-value identity).
3444
+ */
3445
+ async resolveReactiveOutcome(functionPath, args, isAdmin, identity) {
3446
+ if (isAdmin) {
3447
+ return this.executeAdminSubscription(functionPath, args);
3448
+ }
3449
+ if (functionPath.startsWith(FLAGS_FUNCTION_PREFIX)) {
3450
+ const result = await this.runFlagSubscriptionRead(functionPath, args, identity);
3451
+ return result === null ? null : { result, tables: /* @__PURE__ */ new Set([ADMIN_WILDCARD]) };
3452
+ }
3453
+ return this.executeSubscription(functionPath, args, identity);
3454
+ }
3472
3455
  /**
3473
3456
  * Constant-time bearer check against `env.LUNORA_ADMIN_TOKEN`. Returns
3474
3457
  * `false` (closed) when the token is unset so admin introspection is
@@ -3567,11 +3550,47 @@ class ShardDO {
3567
3550
  if (!changed || changed.size === 0) {
3568
3551
  return;
3569
3552
  }
3553
+ if (this.pendingRefreshTables) {
3554
+ for (const table of changed) {
3555
+ this.pendingRefreshTables.add(table);
3556
+ }
3557
+ } else {
3558
+ this.pendingRefreshTables = changed;
3559
+ }
3560
+ if (this.refreshInFlight) {
3561
+ return;
3562
+ }
3570
3563
  if (typeof this.state.waitUntil === "function") {
3571
- this.state.waitUntil(this.refreshSubscriptions(changed));
3564
+ this.state.waitUntil(this.drainSubscriptionRefreshes());
3572
3565
  return;
3573
3566
  }
3574
- await this.refreshSubscriptions(changed);
3567
+ await this.drainSubscriptionRefreshes();
3568
+ }
3569
+ /**
3570
+ * Drain {@link ShardDO.pendingRefreshTables} one coalesced batch at a time
3571
+ * until it is empty, then release the {@link ShardDO.refreshInFlight} gate.
3572
+ * Tables merged by a `flushChangedTables` that lands mid-pass are picked up
3573
+ * by the next loop iteration, so every committed write is observed by a
3574
+ * refresh that runs after it — bursts simply share a pass. The post-write
3575
+ * high-watermark and live-socket set are re-read inside each
3576
+ * `refreshSubscriptions` call, so a later batch always reflects the latest
3577
+ * committed state.
3578
+ */
3579
+ async drainSubscriptionRefreshes() {
3580
+ if (this.refreshInFlight) {
3581
+ return;
3582
+ }
3583
+ this.refreshInFlight = true;
3584
+ try {
3585
+ let batch = this.pendingRefreshTables;
3586
+ while (batch && batch.size > 0) {
3587
+ this.pendingRefreshTables = void 0;
3588
+ await this.refreshSubscriptions(batch);
3589
+ batch = this.pendingRefreshTables;
3590
+ }
3591
+ } finally {
3592
+ this.refreshInFlight = false;
3593
+ }
3575
3594
  }
3576
3595
  /**
3577
3596
  * For every live subscription whose query reads one of `changed`, re-run
@@ -3649,15 +3668,10 @@ class ShardDO {
3649
3668
  continue;
3650
3669
  }
3651
3670
  try {
3652
- const outcome = isAdmin ? this.executeAdminSubscription(functionPath, query.args ?? {}) : (
3653
- // Re-run under the socket's OWN verified identity (stamped on the
3654
- // attachment at upgrade, unforgeable by the client) — passed BY
3655
- // VALUE, so this deferred re-run never reads or mutates the shared
3656
- // per-request identity fields. Without it an `rls()` / `ctx.auth`
3657
- // scoped live query would evaluate anonymous and return zero rows.
3658
- // eslint-disable-next-line no-await-in-loop -- subscriptions on a socket re-run sequentially; each shares the single SQLite handle
3659
- await this.executeSubscription(functionPath, query.args ?? {}, { identity: attachment.identity, userId: attachment.userId })
3660
- );
3671
+ const outcome = await this.resolveReactiveOutcome(functionPath, query.args ?? {}, isAdmin, {
3672
+ identity: attachment.identity,
3673
+ userId: attachment.userId
3674
+ });
3661
3675
  if (!outcome) {
3662
3676
  continue;
3663
3677
  }
@@ -3699,7 +3713,10 @@ class ShardDO {
3699
3713
  async seedSubscription(ws, subId, query, functionPath, isAdmin) {
3700
3714
  const seedArgs = query.args ?? {};
3701
3715
  const attachment = this.readAttachment(ws);
3702
- const outcome = isAdmin ? this.executeAdminSubscription(functionPath, seedArgs) : await this.executeSubscription(functionPath, seedArgs, { identity: attachment.identity, userId: attachment.userId });
3716
+ const outcome = await this.resolveReactiveOutcome(functionPath, seedArgs, isAdmin, {
3717
+ identity: attachment.identity,
3718
+ userId: attachment.userId
3719
+ });
3703
3720
  if (!outcome) {
3704
3721
  return;
3705
3722
  }
@@ -3763,21 +3780,8 @@ class ShardDO {
3763
3780
  }
3764
3781
  const deltaFrames = [];
3765
3782
  const deltas = existing === void 0 ? void 0 : subscriptionListDeltas(existing.lastJson, outcome.result, outcome.tables.values().next().value ?? "", deltaFrames);
3766
- memos.set(subId, { lastJson: json, tables: outcome.tables });
3767
- if (deltas !== void 0) {
3768
- const idJson = JSON.stringify(subId);
3769
- for (const deltaBody of deltaFrames) {
3770
- try {
3771
- ws.send(`{"type":"delta","id":${idJson},"delta":${deltaBody}${cursorSuffix}}`);
3772
- } catch {
3773
- }
3774
- }
3775
- return;
3776
- }
3777
- try {
3778
- ws.send(`{"type":"data","id":${JSON.stringify(subId)},"data":${json}${cursorSuffix}}`);
3779
- } catch {
3780
- }
3783
+ const delivered = deltas === void 0 ? trySendFrame(ws, `{"type":"data","id":${JSON.stringify(subId)},"data":${json}${cursorSuffix}}`) : sendDeltaFrames(ws, subId, deltaFrames, cursorSuffix);
3784
+ memos.set(subId, { lastJson: delivered ? json : existing?.lastJson ?? UNDELIVERED_BASELINE, tables: outcome.tables });
3781
3785
  }
3782
3786
  /**
3783
3787
  * Gate the upgrade request against two complementary controls:
@@ -3999,10 +4003,7 @@ class ShardDO {
3999
4003
  if (ws === sender || this.readAttachment(ws).whispers?.includes(topic) !== true) {
4000
4004
  continue;
4001
4005
  }
4002
- try {
4003
- ws.send(frame);
4004
- } catch {
4005
- }
4006
+ trySendFrame(ws, frame);
4006
4007
  }
4007
4008
  }
4008
4009
  // eslint-disable-next-line class-methods-use-this -- cohesive DO instance method grouped with the hibernation/attachment helpers; reads only the socket
@@ -1,11 +1,6 @@
1
+ import { stableStringify } from './stableStringify-CyHKJXre.mjs';
1
2
  import { depKey, SCAN_DEP } from './SCAN_DEP-DLJF8dsj.mjs';
2
3
 
3
- const compareKeys = (a, b) => {
4
- if (a < b) {
5
- return -1;
6
- }
7
- return a > b ? 1 : 0;
8
- };
9
4
  const DEFAULT_MAX_ENTRIES = 1e3;
10
5
  const DEFAULT_MAX_BYTES = 4 * 1024 * 1024;
11
6
  const estimateBytes = (value) => {
@@ -232,28 +227,6 @@ class ReactiveCache {
232
227
  }
233
228
  }
234
229
  }
235
- const stableStringify = (value) => {
236
- if (value === void 0) {
237
- return "null";
238
- }
239
- if (value === null || typeof value !== "object") {
240
- return JSON.stringify(value);
241
- }
242
- if (Array.isArray(value)) {
243
- return `[${value.map((item) => stableStringify(item)).join(",")}]`;
244
- }
245
- const record = value;
246
- const keys = Object.keys(record).toSorted(compareKeys);
247
- const parts = [];
248
- for (const key of keys) {
249
- const raw = record[key];
250
- if (raw === void 0) {
251
- continue;
252
- }
253
- parts.push(`${JSON.stringify(key)}:${stableStringify(raw)}`);
254
- }
255
- return `{${parts.join(",")}}`;
256
- };
257
230
  const reactiveCacheKey = (functionPath, args, identity) => `${identity ?? "\0anon"}\0${functionPath}:${stableStringify(args)}`;
258
231
 
259
232
  export { ReactiveCache, reactiveCacheKey, stableStringify };
@@ -1,4 +1,4 @@
1
- import { RELATION_FUNCTION_PREFIX } from './ADMIN_FUNCTIONS-CHcC8fKV.mjs';
1
+ import { RELATION_FUNCTION_PREFIX } from './ADMIN_FUNCTIONS-D_UiYJFk.mjs';
2
2
 
3
3
  const serveRelationFanout = async (schema, database, functionPath, args) => {
4
4
  const table = typeof args["table"] === "string" ? args["table"] : "";
@@ -0,0 +1,30 @@
1
+ const compareKeys = (a, b) => {
2
+ if (a < b) {
3
+ return -1;
4
+ }
5
+ return a > b ? 1 : 0;
6
+ };
7
+ const stableStringify = (value) => {
8
+ if (value === void 0) {
9
+ return "null";
10
+ }
11
+ if (value === null || typeof value !== "object") {
12
+ return JSON.stringify(value);
13
+ }
14
+ if (Array.isArray(value)) {
15
+ return `[${value.map((item) => stableStringify(item)).join(",")}]`;
16
+ }
17
+ const record = value;
18
+ const keys = Object.keys(record).toSorted(compareKeys);
19
+ const parts = [];
20
+ for (const key of keys) {
21
+ const raw = record[key];
22
+ if (raw === void 0) {
23
+ continue;
24
+ }
25
+ parts.push(`${JSON.stringify(key)}:${stableStringify(raw)}`);
26
+ }
27
+ return `{${parts.join(",")}}`;
28
+ };
29
+
30
+ export { stableStringify };
@@ -0,0 +1,111 @@
1
+ const ROW_ID_FIELD = "_id";
2
+ const DELTA_FALLBACK_TABLE = "__lunora__";
3
+ const readRowId = (row) => {
4
+ if (typeof row !== "object" || row === null || Array.isArray(row)) {
5
+ return void 0;
6
+ }
7
+ const id = row[ROW_ID_FIELD];
8
+ return typeof id === "string" ? id : void 0;
9
+ };
10
+ const indexRowsById = (rows) => {
11
+ const byId = /* @__PURE__ */ new Map();
12
+ const order = [];
13
+ for (const row of rows) {
14
+ const id = readRowId(row);
15
+ if (id === void 0 || byId.has(id)) {
16
+ return void 0;
17
+ }
18
+ byId.set(id, row);
19
+ order.push(id);
20
+ }
21
+ return { byId, order };
22
+ };
23
+ const survivorsKeepOrder = (previous, next) => {
24
+ const survivingPrevious = previous.order.filter((id) => next.byId.has(id));
25
+ const survivingNext = next.order.filter((id) => previous.byId.has(id));
26
+ if (survivingPrevious.length !== survivingNext.length) {
27
+ return false;
28
+ }
29
+ return survivingPrevious.every((id, index) => survivingNext[index] === id);
30
+ };
31
+ const collectDeleteDeltas = (previous, next, deltaTable, tableJson) => {
32
+ const out = [];
33
+ for (const id of previous.order) {
34
+ if (!next.byId.has(id)) {
35
+ out.push({
36
+ delta: { key: id, op: "delete", table: deltaTable },
37
+ frame: `{"key":${JSON.stringify(id)},"op":"delete","table":${tableJson}}`
38
+ });
39
+ }
40
+ }
41
+ return out;
42
+ };
43
+ const collectUpsertDeltas = (previous, next, deltaTable, tableJson) => {
44
+ const out = [];
45
+ for (const id of next.order) {
46
+ const nextRow = next.byId.get(id);
47
+ const previousRow = previous.byId.get(id);
48
+ const nextFingerprint = JSON.stringify(nextRow);
49
+ const previousFingerprint = previousRow === void 0 ? void 0 : JSON.stringify(previousRow);
50
+ if (previousFingerprint === nextFingerprint) {
51
+ continue;
52
+ }
53
+ const op = previousFingerprint === void 0 ? "insert" : "update";
54
+ out.push({
55
+ delta: { key: id, op, row: nextRow, table: deltaTable },
56
+ frame: `{"key":${JSON.stringify(id)},"op":"${op}","row":${nextFingerprint},"table":${tableJson}}`
57
+ });
58
+ }
59
+ return out;
60
+ };
61
+ const subscriptionListDeltas = (previousJson, nextResult, table, frames) => {
62
+ let parsed;
63
+ try {
64
+ parsed = JSON.parse(previousJson);
65
+ } catch {
66
+ return void 0;
67
+ }
68
+ if (!Array.isArray(parsed) || !Array.isArray(nextResult)) {
69
+ return void 0;
70
+ }
71
+ const previous = indexRowsById(parsed);
72
+ const next = indexRowsById(nextResult);
73
+ if (previous === void 0 || next === void 0) {
74
+ return void 0;
75
+ }
76
+ if (!survivorsKeepOrder(previous, next)) {
77
+ return void 0;
78
+ }
79
+ const deltaTable = table === "" ? DELTA_FALLBACK_TABLE : table;
80
+ const tableJson = JSON.stringify(deltaTable);
81
+ const framed = [...collectDeleteDeltas(previous, next, deltaTable, tableJson), ...collectUpsertDeltas(previous, next, deltaTable, tableJson)];
82
+ if (framed.length > next.order.length) {
83
+ return void 0;
84
+ }
85
+ if (frames !== void 0) {
86
+ for (const { frame } of framed) {
87
+ frames.push(frame);
88
+ }
89
+ }
90
+ return framed.map(({ delta }) => delta);
91
+ };
92
+ const trySendFrame = (ws, frame) => {
93
+ try {
94
+ ws.send(frame);
95
+ return true;
96
+ } catch {
97
+ return false;
98
+ }
99
+ };
100
+ const sendDeltaFrames = (ws, subId, deltaFrames, cursorSuffix) => {
101
+ const idJson = JSON.stringify(subId);
102
+ let delivered = true;
103
+ for (const deltaBody of deltaFrames) {
104
+ if (!trySendFrame(ws, `{"type":"delta","id":${idJson},"delta":${deltaBody}${cursorSuffix}}`)) {
105
+ delivered = false;
106
+ }
107
+ }
108
+ return delivered;
109
+ };
110
+
111
+ export { sendDeltaFrames, subscriptionListDeltas, trySendFrame };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/do",
3
- "version": "1.0.0-alpha.6",
3
+ "version": "1.0.0-alpha.8",
4
4
  "description": "Lunora Durable Objects: ShardDO (SQLite, OCC, hibernated WebSocket subscriptions) and SessionDO",
5
5
  "keywords": [
6
6
  "cloudflare",