@lunora/do 1.0.0-alpha.109 → 1.0.0-alpha.110

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
@@ -1,6 +1,6 @@
1
- import { SchemaLike, DatabaseWriterLike, CrossShardReadArgs, CdcChange, FilterClause, ExportRow, MigrationDirection, ReactiveCache, ShapeProbeCounters, GlobalPollCounters, ReactiveCacheOptions, ShardSocketLike, TransactionHeadroomTracker, LifecycleDispatchInfo, MigrationRunResult, SearchBackfillProgress, TableIndexInfo, ColumnMeta, AdvisoryFinding, AdvisorProcedure, RlsPoliciesResult, MaskPoliciesResult, StorageRulesResult, StudioFeaturesResult, FlagsResult, SubscriptionIdentity, QueuesResult, WorkflowsResult, ImportShardResult, ShardRankPageResult, SubscriptionQuery, ShapeSubscriptionQuery, MutationDelta, KeyRange, ResolvedShape, ShapeRow, TtlSweepSpec, TransactionLimits, IndexKeyEntry, SqlExec, CdcChangeKey } from '@lunora/shard-engine';
1
+ import { SchemaLike, DatabaseWriterLike, CrossShardReadArgs, CdcChange, FilterClause, ExportRow, MigrationDirection, ReadFootprint, DependencyTracker, ReactiveCache, ShapeProbeCounters, GlobalPollCounters, ReactiveCacheOptions, ShardSocketLike, TransactionHeadroomTracker, LifecycleDispatchInfo, MigrationRunResult, SearchBackfillProgress, TableIndexInfo, ColumnMeta, AdvisoryFinding, AdvisorProcedure, RlsPoliciesResult, MaskPoliciesResult, StorageRulesResult, StudioFeaturesResult, FlagsResult, SubscriptionIdentity, QueuesResult, WorkflowsResult, ImportShardResult, ShardRankPageResult, SubscriptionQuery, ShapeSubscriptionQuery, MutationDelta, KeyRange, ResolvedShape, ShapeRow, TtlSweepSpec, TransactionLimits, IndexKeyEntry, SqlExec, CdcChangeKey } from '@lunora/shard-engine';
2
2
  export { type AdvisorProcedure, type AdvisoryFinding, type AggregateIndexDefinitionLike, type DataMigrationLike, type DatabaseWriterLike, type ExportRow, type ExternalSourceLike, type FlagsResult, type ImportShardResult, type KeyRange, type MaskPoliciesResult, type MigrationRunResult, type MutationDelta, type QueuesResult, REPROJECTION_MIGRATION_PREFIX, type RankIndexDefinitionLike, type RlsPoliciesResult, type SchedulerLike, type SchemaLike, type SearchBackfillProgress, type ShardRankPageResult, type SourceClientLike, type SqlExec, type StorageRulesResult, type StudioFeaturesResult, type SystemReaderStorageLike, type TransactionHeadroomTracker, UNVOUCHABLE_DEP, type ValidatorLike, type WhereInput, type WorkflowsResult, type WriteHook, applyCdcChanges, assertShapeShardable, backfillSearchIndexes, buildReprojectionMigration, clearMemoryTables, countLegacyRows, createReadFootprint, createShardCtxDb, exportShardRows, importShardRows, isSourceDue, markUnvouchableReads, pullExternalSourceIncrementalTick, pullExternalSourceTick, reprojectionMigrationId, reprojectionTables, runDataMigration, runShardMigrations, subscriptionListDeltas } from '@lunora/shard-engine';
3
- import { DatabaseInstrumentation, MetricHistoryOptions, LogEventInput, ContextLogLevel, TraceAnchor, ContextTracer, ContextFetch, ContextMetrics } from '@lunora/observability';
3
+ import { ContextLogLevel, DatabaseInstrumentation, MetricHistoryOptions, LogEventInput, TraceAnchor, ContextTracer, ContextFetch, ContextMetrics } from '@lunora/observability';
4
4
  import { DrizzleSqliteDODatabase } from 'drizzle-orm/durable-sqlite';
5
5
  export { type ShardPlatform, type WorkerPlatform, type WorkerPlatformOptions, createShardAlarms, createShardDirectory, createShardHost, createShardKvStore, createShardPlatform, createSocketHost, createWorkerPlatform } from '@lunora/platform-cloudflare';
6
6
  /**
@@ -744,24 +744,95 @@ type ClientMutationClass = {
744
744
  kind: "already" | "gap" | "next";
745
745
  };
746
746
  /**
747
- * Optional shard-level configuration passed through `super(state, env, …)`.
748
- * Reserved as a bag rather than positional args so subclasses don't break
749
- * when new knobs land. Today the only knob is the reactive cache; future
750
- * additions should keep the same shape (per-feature options object).
747
+ * The read-set / cache-hit attribution for ONE `/rpc` dispatch, filled in by
748
+ * {@link ShardDO.runCachedQuery} and read back by {@link ShardDO.recordRequestLog}.
749
+ *
750
+ * A mutable object THREADED BY VALUE rather than a `currentRequest*` field on
751
+ * the instance, for the same reason `dispatchHeadroom` and `dispatchTrace`
752
+ * already are: a Durable Object serves concurrent `/rpc` dispatches, and every
753
+ * one of these values is written after the handler's awaits. Off a shared field
754
+ * they were whatever the LAST dispatch to resolve wrote — so the request log
755
+ * filed one request's cache hit and read tables under another's, and a sibling's
756
+ * prologue/epilogue could blank them out entirely. Both fields stay `undefined`
757
+ * when the dispatch never reached the cache (a write/action, a cache-less shard,
758
+ * or a query that fell through the re-entry guard) — the request log renders
759
+ * that as "unknown" rather than asserting a read set.
760
+ */
761
+ interface QueryAttribution {
762
+ cacheHit?: boolean;
763
+ readTables?: Set<string>;
764
+ }
765
+ /**
766
+ * The reactive-cache read capture for ONE query dispatch: the dependency
767
+ * tracker the cache indexes the entry by, plus the range footprint its
768
+ * range-precise invalidation reads.
769
+ *
770
+ * Minted by {@link ShardDO.runCachedQuery} and threaded BY VALUE — handed to
771
+ * its `run` callback, on through `handleRpc`'s fourth parameter, and into the
772
+ * generated `buildCtx`, which binds it into the ctx-db read hooks via
773
+ * {@link ShardDO.getCtxDbReadHook} / {@link ShardDO.getCtxDbReadRangeHook}. It
774
+ * is NOT an instance field: a Durable Object serves concurrent `/rpc`
775
+ * dispatches, and a shared field holds one capture for all of them — the second
776
+ * query's reads would land in the first one's dep set and the second would be
777
+ * skipped by the re-entry guard entirely (never read from the cache, never
778
+ * stored).
779
+ *
780
+ * `AsyncLocalStorage` would carry it implicitly, but workerd only enables ALS
781
+ * under `nodejs_compat` and shard DOs run the slimmer `sqlite_compat` profile —
782
+ * see the header of `@lunora/shard-engine`'s `dependency-tracker.ts`.
783
+ */
784
+ interface QueryReadScope {
785
+ /** Range footprint for this dispatch — the `onReadRange` channel. */
786
+ footprint: ReadFootprint;
787
+ /** Dependency tracker for this dispatch — the `onRead` channel. */
788
+ tracker: DependencyTracker;
789
+ }
790
+ /**
791
+ * Shard-level configuration passed through `super(state, env, …)` by the
792
+ * generated subclass, which sources every key from the app's
793
+ * `createShardDO(config)` argument. A bag rather than positional args so
794
+ * subclasses don't break when a knob lands.
751
795
  */
752
796
  interface ShardDOOptions {
797
+ /**
798
+ * Whether every writer this shard builds spreads {@link ShardDO.ctxDbTuning}.
799
+ * The emitter sets it, because it is the only thing that can see inside the
800
+ * generated `buildCtx` and the admin/maintenance writers to know.
801
+ *
802
+ * Left unset (a hand-written subclass), {@link ShardDO.recordChangedTable}
803
+ * keeps its coarse per-table invalidation backstop, so a writer that never
804
+ * received the cache cannot serve a pre-write read. Set wrongly, it would
805
+ * disable that backstop — which is why it is declared rather than inferred
806
+ * from a call to the accessor.
807
+ */
808
+ ctxDbCacheWired?: boolean;
809
+ /**
810
+ * Ceiling on the join keys one relation-crossing `where` predicate may pull
811
+ * back via semijoin pre-resolution before failing closed. Reaches
812
+ * `createShardCtxDb` through {@link ShardDO.ctxDbTuning}; `undefined` keeps
813
+ * the engine default (`DEFAULT_MAX_RELATION_KEYS`).
814
+ */
815
+ maxRelationKeys?: number;
753
816
  /**
754
817
  * Enable the per-shard reactive query cache. When provided, the dispatch
755
- * path uses {@link ShardDO.runCachedQuery} to memoize query results by
756
- * `(functionPath, stable-stringified args)`. Omit to keep the legacy
757
- * behavior (every dispatch re-runs the handler).
818
+ * path routes every registered `query` through {@link ShardDO.runCachedQuery},
819
+ * memoizing results by `(identity, functionPath, stable-stringified args)`.
820
+ * Omit for the zero-overhead default (every dispatch re-runs the handler).
758
821
  *
759
- * The cache is invisible to the WS subscription bridge: invalidations
760
- * land via the ctx-db write hooks (`@lunora/do`'s `createShardCtxDb`
761
- * `cache` option) BEFORE the broadcast goes out, so subscribers that
762
- * re-run their queries in response always observe the post-write state.
822
+ * The cache is invisible to the WS subscription bridge: invalidations land
823
+ * via the ctx-db write hooks (the `cache` option {@link ShardDO.ctxDbTuning}
824
+ * supplies) BEFORE the broadcast goes out, so subscribers that re-run their
825
+ * queries in response always observe the post-write state.
763
826
  */
764
827
  reactiveCache?: ReactiveCacheOptions;
828
+ /**
829
+ * Resolution policy for relation-crossing `where` predicates whose child is
830
+ * co-located in this shard — `"auto"` (cost-based, the engine default),
831
+ * `"always"` (inline correlated EXISTS) or `"never"` (universal semijoin).
832
+ * All three return identical rows. Reaches `createShardCtxDb` through
833
+ * {@link ShardDO.ctxDbTuning}.
834
+ */
835
+ relationExistsPushDown?: "always" | "auto" | "never";
765
836
  }
766
837
  /**
767
838
  * Threshold at which a `__root__` DO triggers the size warning. 1 GiB —
@@ -900,15 +971,14 @@ declare abstract class ShardDO {
900
971
  protected state: ShardDOState;
901
972
  protected env: unknown;
902
973
  /**
903
- * Opt-in per-shard reactive query cache. When the subclass passes
904
- * `ReactiveCacheOptions` to `super(state, env, { reactiveCache: { … } })`
905
- * the cache is instantiated here and exposed to subclasses via
906
- * `runCachedQuery`; when omitted (today's default) it stays
907
- * undefined and the dispatch path runs with zero cache overhead.
974
+ * Opt-in per-shard reactive query cache. Instantiated when the generated
975
+ * subclass passes `reactiveCache` through
976
+ * `super(state, env, { reactiveCache: { } })`; otherwise undefined and the
977
+ * dispatch path runs with zero cache overhead.
908
978
  *
909
- * The cache is per-shard and in-memory only — it is lost on DO restart
910
- * and on workerd hibernation. That's fine: a cold shard simply re-runs
911
- * the query on the first call, just like it does today.
979
+ * The cache is per-shard and in-memory only — it is lost on DO restart and
980
+ * on workerd hibernation. That's fine: a cold shard simply re-runs the query
981
+ * on the first call.
912
982
  */
913
983
  protected readonly reactiveCache: ReactiveCache | undefined;
914
984
  /**
@@ -933,6 +1003,21 @@ declare abstract class ShardDO {
933
1003
  * changelog proved their table had not moved.
934
1004
  */
935
1005
  protected globalPoll: GlobalPollCounters;
1006
+ /** ctx-db relation knobs from {@link ShardDOOptions}, handed on by {@link ShardDO.ctxDbTuning}. */
1007
+ private readonly ctxDbRelationOptions;
1008
+ /**
1009
+ * Whether {@link ShardDO.ctxDbTuning} has been consulted, i.e. whether the
1010
+ * subclass's `createShardCtxDb` call is wired to the precise, per-row
1011
+ * invalidation half of the cache contract.
1012
+ *
1013
+ * It gates a coarse fallback in {@link ShardDO.recordChangedTable}: a shard
1014
+ * whose writer never received the cache would otherwise keep serving
1015
+ * memoized results across writes — stale reads, silently. The fallback drops
1016
+ * every entry on a written table, which is strictly more invalidation than
1017
+ * the wired path does, so the two never disagree about correctness; only
1018
+ * about hit rate.
1019
+ */
1020
+ private readonly ctxDbCacheWired;
936
1021
  /**
937
1022
  * The host-neutral engine runner. `fetch` and `alarm` delegate through it, so
938
1023
  * the dispatch entry points name a platform contract rather than a Durable
@@ -1369,32 +1454,15 @@ declare abstract class ShardDO {
1369
1454
  * raw events — production aggregation ships to a collector via the sink.
1370
1455
  */
1371
1456
  private readonly metricSeries;
1372
- /**
1373
- * In-flight dependency tracker for the currently-executing query. Set by
1374
- * `runCachedQuery` so the ctx-db hooks (wired via `onRead`) can
1375
- * stamp deps without threading the tracker explicitly through every
1376
- * generated handler signature. Cleared in the `finally` of the same
1377
- * call so a leaked tracker can never bleed into a sibling RPC.
1378
- */
1379
- private currentTracker;
1380
- /**
1381
- * In-flight range footprint (the `onReadRange` channel) for the
1382
- * currently-executing cached query. Set by `runCachedQuery` alongside
1383
- * `currentTracker` so `getCtxDbReadRangeHook` — and the range-marking half
1384
- * of `getCtxDbReadHook` — can stamp it without threading it explicitly
1385
- * through every generated handler signature. `ReactiveCache.run`'s ranges
1386
- * thunk reads it lazily, AFTER the handler resolves, so it always sees the
1387
- * footprint's final state. Cleared in the same `finally` as `currentTracker`.
1388
- */
1389
- private currentReadFootprint;
1390
1457
  /**
1391
1458
  * Tables the in-flight dispatch full-scanned (read via `SCAN_DEP`, no index
1392
1459
  * / point lookup). Allocated at the top of each `/rpc` dispatch and drained
1393
1460
  * into `recordFunctionCall` once the handler returns, so the durable
1394
1461
  * `__lunora_metrics_scans` attribution can pin a slow function to the
1395
- * table(s) it scanned. Independent of `currentTracker` (which only exists
1396
- * when the reactive cache is enabled), so the causal signal is collected
1397
- * even on a cache-less shard. Stamped by `getCtxDbReadHook`.
1462
+ * table(s) it scanned. Independent of the per-dispatch
1463
+ * {@link QueryReadScope} (which only exists when the reactive cache is
1464
+ * enabled), so the causal signal is collected even on a cache-less shard.
1465
+ * Stamped by `getCtxDbReadHook`.
1398
1466
  */
1399
1467
  private currentScannedTables;
1400
1468
  /**
@@ -1414,18 +1482,6 @@ declare abstract class ShardDO {
1414
1482
  * down with it.
1415
1483
  */
1416
1484
  private currentTransactionHeadroom;
1417
- /**
1418
- * Read-tables + cache-hit captured for the current `/rpc` dispatch, so the
1419
- * dispatch site can fold them into the durable request log
1420
- * (`request-log.ts`). Populated by `runCachedQuery` — the one place that
1421
- * both holds the per-query dependency tracker AND learns whether the
1422
- * reactive cache served the result — and reset per request in `fetch`.
1423
- * `undefined`/empty when the reactive cache is disabled or the path is a
1424
- * write/action (which doesn't run through the cache), which is exactly why
1425
- * the request log treats those fields as "unknown" rather than asserting a
1426
- * read set on the hot path.
1427
- */
1428
- private currentRequestReadTables;
1429
1485
  /**
1430
1486
  * Per-DISTINCT-statement SQL samples collected during the current `/rpc`
1431
1487
  * dispatch by the instrumented `sql` getter, keyed by the raw query text.
@@ -1471,8 +1527,6 @@ declare abstract class ShardDO {
1471
1527
  * leaderboard contribution reads as partial rather than complete.
1472
1528
  */
1473
1529
  private currentStmtSamplesTruncated;
1474
- /** Whether the current dispatch's cached query was served from cache; `undefined` until `runCachedQuery` resolves one. */
1475
- private currentRequestCacheHit;
1476
1530
  constructor(state: ShardDOState, env: unknown, options?: ShardDOOptions);
1477
1531
  /**
1478
1532
  * Worker-side fetch entry point. Delegates to the host-neutral
@@ -1534,8 +1588,17 @@ declare abstract class ShardDO {
1534
1588
  * their own tracker (`dispatchLifecycle`, `handleRunAs`) omit it and the
1535
1589
  * codegen subclass falls back to `this.transactionHeadroom()`, unchanged from
1536
1590
  * before this parameter existed.
1591
+ *
1592
+ * `scope` is the same shape of BY-VALUE thread for the reactive cache: the
1593
+ * `/rpc` query path routes through {@link ShardDO.runCachedQuery}, which
1594
+ * mints a {@link QueryReadScope} per dispatch and passes it here so the ctx
1595
+ * this dispatch builds stamps reads into ITS OWN tracker and footprint.
1596
+ * Implementations must hand it to `getCtxDbReadHook(scope)` /
1597
+ * `getCtxDbReadRangeHook(scope)` on the `createShardCtxDb(...)` call that
1598
+ * builds the ctx; both factories return unbound (tracker-less) hooks when it
1599
+ * is omitted, which is what every non-cached dispatch passes.
1537
1600
  */
1538
- abstract handleRpc(functionPath: string, args: Record<string, unknown>, headroom?: TransactionHeadroomTracker): Promise<unknown>;
1601
+ abstract handleRpc(functionPath: string, args: Record<string, unknown>, headroom?: TransactionHeadroomTracker, scope?: QueryReadScope): Promise<unknown>;
1539
1602
  /**
1540
1603
  * The registered function paths to dispatch on a lifecycle moment —
1541
1604
  * `connect`/`disconnect` per socket, `init` once per Durable Object instance,
@@ -2614,30 +2677,36 @@ declare abstract class ShardDO {
2614
2677
  iterator: (signal: AbortSignal) => AsyncIterable<unknown>;
2615
2678
  };
2616
2679
  /**
2617
- * Wrap a query handler in the reactive cache. The subclass passes the
2618
- * function path, parsed args, and a `run` callback that resolves to the
2619
- * handler's return value. When the cache is configured we key by
2620
- * `(functionPath, stable-stringified args)`, allocate a fresh dep
2621
- * tracker, store it on `this.currentTracker` so `getCtxDbReadHook` reads
2622
- * stamp into it, and restore the prior tracker in `finally`. When the
2623
- * cache is absent we just call `run()` same shape, zero overhead.
2624
- *
2625
- * Subclasses should ALSO pass `getCtxDbReadHook()` as the `onRead`
2680
+ * Wrap a query handler in the reactive cache. The `/rpc` dispatch path calls
2681
+ * this for every path {@link ShardDO.isQueryFunction} recognises, so a
2682
+ * subclass does NOT wrap its own `handleRpc` see the re-entry guard below
2683
+ * for why doing both would be worse than doing neither. When the cache is
2684
+ * configured we key by `(identity, functionPath, stable-stringified args)`,
2685
+ * mint a fresh {@link QueryReadScope} (dep tracker + read footprint) and
2686
+ * hand it to `run` which threads it through `handleRpc` into the ctx the
2687
+ * handler reads through, so this dispatch's reads stamp THIS dispatch's
2688
+ * tracker even while a sibling dispatch is parked on an await. When the
2689
+ * cache is absent we just call `run()` with no scope — same shape, zero
2690
+ * overhead.
2691
+ *
2692
+ * Subclasses must ALSO pass `getCtxDbReadHook(scope)` as the `onRead`
2626
2693
  * option on their `createShardCtxDb(...)` call so the tracker actually
2627
- * collects deps. Without that wiring the cache will memoize results
2628
- * with empty dep sets, so writes never invalidate them and stale
2629
- * results stick around — the {@link ReactiveCache} class is contract-
2630
- * neutral about who fills `deps`.
2631
- *
2632
- * Also allocates a fresh {@link ReadFootprint} alongside the tracker,
2633
- * stored on `this.currentReadFootprint` so `getCtxDbReadRangeHook()` —
2634
- * and the range-marking half of `getCtxDbReadHook()` can stamp it. Its
2694
+ * collects deps. Without that wiring the cache memoizes results with empty
2695
+ * dep sets, so the write hooks never invalidate them the
2696
+ * {@link ReactiveCache} class is contract-neutral about who fills `deps`,
2697
+ * and dep-less entries survive `invalidate` AND `invalidateTable` alike, so
2698
+ * neither the ctx-db hooks nor the {@link ShardDO.recordChangedTable}
2699
+ * backstop can rescue that omission.
2700
+ *
2701
+ * The scope's {@link ReadFootprint} is the ranges channel:
2702
+ * `getCtxDbReadRangeHook(scope)` — and the range-marking half of
2703
+ * `getCtxDbReadHook(scope)` — stamp it. Its
2635
2704
  * `ranges()` is handed to `reactiveCache.run` as a LAZY 4th argument (a
2636
2705
  * thunk, evaluated only after `run()` resolves), the same deferral
2637
2706
  * `deps` already relies on: the footprint is only complete once the
2638
2707
  * handler has actually run. Subclasses that also want range-precise
2639
- * invalidation should pass `getCtxDbReadRangeHook()` as `onReadRange` on
2640
- * the same `createShardCtxDb(...)` call — mirroring `onRead` above. A
2708
+ * invalidation should pass `getCtxDbReadRangeHook(scope)` as `onReadRange`
2709
+ * on the same `createShardCtxDb(...)` call — mirroring `onRead` above. A
2641
2710
  * subclass that only wires `onRead` still works: `ranges()` degrades to
2642
2711
  * `undefined` and every read is treated as a whole-table dependency, per
2643
2712
  * `ReactiveCache.run`'s own default. When `onReadRange` IS wired, a table
@@ -2647,22 +2716,24 @@ declare abstract class ShardDO {
2647
2716
  * method's body. Only a table read EXCLUSIVELY through provable ranges
2648
2717
  * gets range-precise invalidation instead.
2649
2718
  */
2650
- protected runCachedQuery<R>(functionPath: string, args: Record<string, unknown>, run: () => Promise<R>): Promise<R>;
2719
+ protected runCachedQuery<R>(functionPath: string, args: Record<string, unknown>, run: (scope?: QueryReadScope) => Promise<R>, attribution?: QueryAttribution, outer?: QueryReadScope): Promise<R>;
2651
2720
  /**
2652
2721
  * Returns an `onRead` callback suitable to hand to `createShardCtxDb`'s
2653
- * `onRead` option. The returned function stamps the in-flight tracker (set
2654
- * by `runCachedQuery`) when one exists and is a no-op otherwise — so
2722
+ * `onRead` option, BOUND to the dispatch whose {@link QueryReadScope} is
2723
+ * passed in. `runCachedQuery` mints that scope and threads it through
2724
+ * `handleRpc`; a dispatch with no cache scope (a mutation, an action, a
2725
+ * cache-less shard) passes nothing and gets a hook that stamps no deps — so
2655
2726
  * subclasses can wire this hook unconditionally without checking whether
2656
2727
  * the cache is enabled.
2657
2728
  *
2658
2729
  * It ALSO records the table into {@link currentScannedTables} whenever the
2659
- * read was a full-table scan (the `SCAN_DEP` sentinel). That set is drained
2660
- * into `recordFunctionCall` after dispatch to build the durable per-function
2661
- * full-scan attribution — and unlike the tracker, it's collected even when
2662
- * the reactive cache is off, since the causal signal is independent of
2663
- * caching.
2730
+ * read was a full-table scan (the `SCAN_DEP` sentinel), scope or no scope.
2731
+ * That set is drained into `recordFunctionCall` after dispatch to build the
2732
+ * durable per-function full-scan attribution — unlike the tracker, it's
2733
+ * collected even when the reactive cache is off, since the causal signal is
2734
+ * independent of caching.
2664
2735
  *
2665
- * It ALSO marks the table unnarrowable on {@link currentReadFootprint},
2736
+ * It ALSO marks the table unnarrowable on the scope's `footprint`,
2666
2737
  * mirroring `executeSubscription`'s wiring (which hands a single
2667
2738
  * `ReadFootprint`'s `onRead`/`onReadRange` pair straight to `buildCtx`).
2668
2739
  * `ctx-db.ts`'s reader calls this `onRead` and `onReadRange` mutually
@@ -2672,19 +2743,20 @@ declare abstract class ShardDO {
2672
2743
  * "read this table outside a range" signal {@link ReadFootprint.ranges}
2673
2744
  * needs to drop that table from the narrowed set.
2674
2745
  */
2675
- protected getCtxDbReadHook(): (table: string, idOrScan?: string) => void;
2746
+ protected getCtxDbReadHook(scope?: QueryReadScope): (table: string, idOrScan?: string) => void;
2676
2747
  /**
2677
2748
  * Returns an `onReadRange` callback suitable to hand to
2678
- * `createShardCtxDb`'s `onReadRange` option, alongside `getCtxDbReadHook()`
2679
- * as `onRead` on the same call — the pairing `executeSubscription` already
2680
- * uses via `ReadFootprint`. Stamps the in-flight footprint (set by
2681
- * `runCachedQuery`) when one exists and is a no-op otherwise, so subclasses
2682
- * can wire this hook unconditionally regardless of whether the cache is
2683
- * enabled. Without this wiring `runCachedQuery`'s ranges thunk always
2684
- * observes an empty footprint and every cached query degrades to the prior
2685
- * whole-table dependency — safe, just not range-precise.
2686
- */
2687
- protected getCtxDbReadRangeHook(): (range: KeyRange) => void;
2749
+ * `createShardCtxDb`'s `onReadRange` option, alongside
2750
+ * `getCtxDbReadHook(scope)` as `onRead` on the same call — the pairing
2751
+ * `executeSubscription` already uses via `ReadFootprint`. Stamps the
2752
+ * footprint of the {@link QueryReadScope} passed in and is a no-op when
2753
+ * none is, so subclasses can wire this hook unconditionally regardless of
2754
+ * whether the cache is enabled. Without this wiring `runCachedQuery`'s
2755
+ * ranges thunk always observes an empty footprint and every cached query
2756
+ * degrades to the prior whole-table dependency — safe, just not
2757
+ * range-precise.
2758
+ */
2759
+ protected getCtxDbReadRangeHook(scope?: QueryReadScope): (range: KeyRange) => void;
2688
2760
  /**
2689
2761
  * Read hook recording which declared indexes a query actually exercises.
2690
2762
  * Two destinations, both stamped here so a single hook serves the live and
@@ -2698,6 +2770,52 @@ declare abstract class ShardDO {
2698
2770
  * to `createShardCtxDb` by the generated subclass.
2699
2771
  */
2700
2772
  protected getCtxDbIndexUseHook(): (table: string, indexName: string) => void;
2773
+ /**
2774
+ * The `createShardCtxDb` options this DO's configuration decides, as one
2775
+ * spreadable slice: the reactive cache (invalidation half of the contract)
2776
+ * plus the two relation-resolution knobs from {@link ShardDOOptions}.
2777
+ *
2778
+ * The generated `buildCtx` spreads this FIRST into its `createShardCtxDb`
2779
+ * call, so a per-request option it sets afterwards still wins:
2780
+ *
2781
+ * ```ts
2782
+ * createShardCtxDb({ ...this.ctxDbTuning(), auth: …, schema, sql, … })
2783
+ * ```
2784
+ *
2785
+ * One accessor rather than three, because it is one decision — "how this
2786
+ * deployment configured its ctx-db" — and the emitter should not have to
2787
+ * grow a line per knob. Only keys the app actually set are present, so
2788
+ * spreading never overwrites an engine default with `undefined`.
2789
+ *
2790
+ * Handing over `cache` is what makes writes invalidate at row + index-range
2791
+ * precision (`ctx-db.ts` calls `cache.invalidate(table, id, indexKeys)` on
2792
+ * every `insert`/`patch`/`replace`/`delete`). EVERY writer the emitter builds
2793
+ * spreads this — the user-facing ctx and all three admin/maintenance writers —
2794
+ * because a writer that skips it leaves post-write reads answering from the
2795
+ * pre-write snapshot.
2796
+ *
2797
+ * Pure: it reports the slice and records nothing. Whether the emitter actually
2798
+ * wired it is a fact the emitter knows statically and declares through
2799
+ * {@link ShardDOOptions.ctxDbCacheWired}; inferring it from a call to this
2800
+ * accessor made reading the slice — in a test, or to log it — silently disarm
2801
+ * the invalidation backstop in {@link ShardDO.recordChangedTable}.
2802
+ */
2803
+ protected ctxDbTuning(): {
2804
+ cache?: ReactiveCache;
2805
+ maxRelationKeys?: number;
2806
+ relationExistsPushDown?: "always" | "auto" | "never";
2807
+ };
2808
+ /**
2809
+ * Whether `functionPath` names a registered `query` — the only kind whose
2810
+ * result may be memoized by the reactive cache.
2811
+ *
2812
+ * The base class has no function registry, so the default is `false`: the
2813
+ * conservative answer, since caching an `action` would skip its outbound
2814
+ * side effects on a hit and caching a `mutation` is meaningless. The
2815
+ * codegen-generated subclass overrides it with the real `LUNORA_FUNCTIONS`
2816
+ * lookup, which is what production dispatch uses.
2817
+ */
2818
+ protected isQueryFunction(_functionPath: string): boolean;
2701
2819
  /**
2702
2820
  * Ceilings for one transaction. The base class uses the engine defaults
2703
2821
  * (sized for a 128 MiB Durable Object isolate); a subclass overrides this
@@ -3547,10 +3665,15 @@ declare abstract class ShardDO {
3547
3665
  * The correlated fields all come from data the dispatch already holds, with
3548
3666
  * no extra hot-path bookkeeping. `tablesWritten` is snapshotted from
3549
3667
  * `pendingChangedTables` by the caller before `flushChangedTables` drains it.
3550
- * `tablesRead` and `cacheHit` are captured by `runCachedQuery`, so they are
3551
- * present only for cached query pathsa write/action doesn't run through
3552
- * the cache, and an instance with the reactive cache disabled never captures
3553
- * them and are left empty/`undefined` rather than recomputed here.
3668
+ * `tablesRead` and `cacheHit` arrive in `attribution`, the per-dispatch
3669
+ * object `beginDispatch` minted and `runCachedQuery` filled in passed BY
3670
+ * VALUE for the same reason `trace` is, since both are read here, after the
3671
+ * handler's awaits, where a shared field belongs to whichever concurrent
3672
+ * dispatch resolved last. They are present only for cached query paths — a
3673
+ * write/action doesn't run through the cache, an instance with the reactive
3674
+ * cache disabled never captures them, and a query that hit the re-entry
3675
+ * guard passed through uncached — and are left empty/`undefined` rather
3676
+ * than recomputed here.
3554
3677
  * `subscriptionsReRun` is left `0`: the write-driven subscription refresh
3555
3678
  * runs off the response path via `waitUntil` (see `flushChangedTables`), so a
3556
3679
  * per-request count isn't available synchronously at this site, and threading
@@ -3711,7 +3834,7 @@ declare abstract class ShardDO {
3711
3834
  * per identity), and a flag read ({@link FLAGS_FUNCTION_PREFIX}) evaluates
3712
3835
  * the provider with the subscriber's identity (per-user targeting). Sharing
3713
3836
  * one socket's result with another would leak one identity's rows/flags to a
3714
- * different identity, so this predicate gates {@link resolveReactiveOutcomeDeduped}
3837
+ * different identity, so this predicate gates {@link ShardDO.resolveReactiveOutcomeDeduped}
3715
3838
  * shut for them.
3716
3839
  */
3717
3840
  protected isIdentityIndependent(functionPath: string): boolean;
@@ -3835,15 +3958,20 @@ declare abstract class ShardDO {
3835
3958
  * run would have to fan one failure to every sharing socket while preserving
3836
3959
  * the "leave memo untouched ⇒ re-run next flush" contract.
3837
3960
  *
3838
- * The framework's INTENDED answer to this fan-out already exists: the opt-in
3839
- * {@link ReactiveCache} (`ShardDOOptions.reactiveCache`). Refreshes run with
3840
- * an explicit anonymous {@link SubscriptionIdentity}, so the cache key
3841
- * `reactiveCacheKey(functionPath, args, null)` is identical across all
3842
- * sockets N identical subscriptions collapse to ONE handler run plus N
3843
- * cache hits, with every per-run side effect honored exactly once by design.
3844
- * Recommended remediation is to document/enable ReactiveCache for
3845
- * high-fanout shards rather than bolt a second, semantically-divergent dedup
3846
- * into this loop.
3961
+ * The opt-in {@link ReactiveCache} (`ShardDOOptions.reactiveCache`) does NOT
3962
+ * cover this, despite the shared key shape: it wraps `/rpc` query dispatch
3963
+ * (see {@link ShardDO.runCachedQuery}), while a refresh runs through
3964
+ * `executeSubscription`, which never consults it. It cannot, either — a
3965
+ * subscription's re-run is only half about the value. The other half is the
3966
+ * `tables`/`ranges` footprint it reports, which is what decides whether the
3967
+ * NEXT write re-runs it; a cache hit produces a value with no footprint, so
3968
+ * routing refreshes through the cache would quietly empty every memo's
3969
+ * dependency set and stop the subscription updating at all.
3970
+ *
3971
+ * So this fan-out stands as characterized. Collapsing it needs a dedup that
3972
+ * shares the footprint alongside the value — {@link ShardDO.resolveReactiveOutcomeDeduped}
3973
+ * is that, per flush, for the identity-independent subset — not a second
3974
+ * memo bolted into this loop.
3847
3975
  */
3848
3976
  /**
3849
3977
  * Count and log a subscription-delivery error that its caller is about to
@@ -4493,11 +4621,6 @@ declare abstract class ShardDO {
4493
4621
  private deliverWhisperLocal;
4494
4622
  private readAttachment;
4495
4623
  }
4496
- /**
4497
- * @deprecated Renamed to {@link TelemetrySink} — it carries spans and metrics as
4498
- * well as logs. Kept as an alias so existing import sites keep working.
4499
- */
4500
- type LogSink = TelemetrySink;
4501
4624
  /** Conventional DO instance name, passed to `idFromName` to address the single registry instance. */
4502
4625
  declare const SHARD_REGISTRY_DO_NAME: string;
4503
4626
  /**
@@ -4557,4 +4680,4 @@ declare class ShardRegistryDO {
4557
4680
  /** The in-memory map as a JSON-safe `table → [keys]` object. */
4558
4681
  private serializeTables;
4559
4682
  }
4560
- export { type HibernatableWebSocket, type LogSink, ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, type RunShardApplyCdcArgs, type RunShardApplyCdcResult, type RunShardBulkDeleteArgs, type RunShardBulkDeleteResult, type RunShardExportArgs, type RunShardImportArgs, type RunShardMigrationArgs, type RunShardRankBeforeArgs, type RunShardRankPageArgs, type RunShardWriteArgs, type RunShardWriteResult, SESSION_DO_TTL_DEFAULT, SHARD_REGISTRY_DO_NAME, SessionDO, type SessionRecord, ShardDO, type ShardDOOptions, type ShardDOState, ShardRegistryDO, type SubscriptionOutcome, type TelemetrySink, type TraceRefLike, serveRelationFanout };
4683
+ export { type HibernatableWebSocket, type QueryReadScope, ROOT_DO_SIZE_WARN_BYTES, ROOT_SHARD_NAME, type RunShardApplyCdcArgs, type RunShardApplyCdcResult, type RunShardBulkDeleteArgs, type RunShardBulkDeleteResult, type RunShardExportArgs, type RunShardImportArgs, type RunShardMigrationArgs, type RunShardRankBeforeArgs, type RunShardRankPageArgs, type RunShardWriteArgs, type RunShardWriteResult, SESSION_DO_TTL_DEFAULT, SHARD_REGISTRY_DO_NAME, SessionDO, type SessionRecord, ShardDO, type ShardDOOptions, type ShardDOState, ShardRegistryDO, type SubscriptionOutcome, type TelemetrySink, type TraceRefLike, serveRelationFanout };