@lunora/db 1.0.0-alpha.23 → 1.0.0-alpha.25

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.
@@ -1,5 +1,5 @@
1
- export { type C as CheckpointRegistry, type L as LunoraCollectionConfig, type a as LunoraCollectionOptions, c as createCheckpointRegistry, l as lunoraCollectionOptions } from "../packem_shared/collection-options.d-Cd6j65i-.mjs";
2
- export { type C as CollectionDef, type I as InsertBinding, type L as LunoraDb, d as defineCollections } from "../packem_shared/define-collections.d-CSpxihIj.mjs";
1
+ export { type C as CheckpointRegistry, type L as LunoraCollectionConfig, type a as LunoraCollectionOptions, c as createCheckpointRegistry, l as lunoraCollectionOptions } from "../packem_shared/collection-options.d-BPVkj5FW.mjs";
2
+ export { type C as CollectionDef, type I as InsertBinding, type L as LunoraDb, d as defineCollections } from "../packem_shared/define-collections.d-C9paBiXl.mjs";
3
3
  import '@lunora/client';
4
4
  import '@tanstack/db';
5
5
  import '@tanstack/offline-transactions';
@@ -1,5 +1,5 @@
1
- export { type C as CheckpointRegistry, type L as LunoraCollectionConfig, type a as LunoraCollectionOptions, c as createCheckpointRegistry, l as lunoraCollectionOptions } from "../packem_shared/collection-options.d-Cd6j65i-.js";
2
- export { type C as CollectionDef, type I as InsertBinding, type L as LunoraDb, d as defineCollections } from "../packem_shared/define-collections.d-CNkWbDe0.js";
1
+ export { type C as CheckpointRegistry, type L as LunoraCollectionConfig, type a as LunoraCollectionOptions, c as createCheckpointRegistry, l as lunoraCollectionOptions } from "../packem_shared/collection-options.d-BPVkj5FW.js";
2
+ export { type C as CollectionDef, type I as InsertBinding, type L as LunoraDb, d as defineCollections } from "../packem_shared/define-collections.d-Dkzt5xPY.js";
3
3
  import '@lunora/client';
4
4
  import '@tanstack/db';
5
5
  import '@tanstack/offline-transactions';
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- export { type C as CheckpointRegistry, type E as ExecutorOutboxSinkOptions, type L as LunoraCollectionConfig, type a as LunoraCollectionOptions, O as OUTBOX_MUTATION_FN_NAME, type b as OutboxExecutor, type d as OutboxMutationMetadata, type R as Row, type S as SyncWriter, c as createCheckpointRegistry, e as createExecutorOutboxSink, f as createOptimisticOnlineDetector, l as lunoraCollectionOptions, m as makeDiffEmit, r as runOutboxMutation, t as toMap } from "./packem_shared/collection-options.d-Cd6j65i-.mjs";
2
- export { type C as CollectionDef, type D as DefineCollectionsOptions, type I as InsertBinding, type L as LunoraDb, type W as WriteRejectedEvent, d as defineCollections } from "./packem_shared/define-collections.d-CSpxihIj.mjs";
3
- export { type B as BindMutatorsContext, type a as BoundMutators, type C as ClientMutatorContext, type b as ClientMutatorDef, c as bindMutators, d as defineMutator } from "./packem_shared/define-mutators.d-B4FIhFEy.mjs";
1
+ export { type C as CheckpointRegistry, type E as ExecutorOutboxSinkOptions, type L as LunoraCollectionConfig, type a as LunoraCollectionOptions, O as OUTBOX_MUTATION_FN_NAME, type b as OutboxExecutor, type d as OutboxMutationMetadata, type R as Row, type S as SyncWriter, c as createCheckpointRegistry, e as createExecutorOutboxSink, f as createOptimisticOnlineDetector, l as lunoraCollectionOptions, m as makeDiffEmit, r as runOutboxMutation, t as toMap } from "./packem_shared/collection-options.d-BPVkj5FW.mjs";
2
+ export { type C as CollectionDef, type D as DefineCollectionsOptions, type I as InsertBinding, type L as LunoraDb, type W as WriteRejectedEvent, d as defineCollections } from "./packem_shared/define-collections.d-C9paBiXl.mjs";
3
+ export { type B as BindMutatorsContext, type a as BoundMutators, type C as ClientMutatorContext, type b as ClientMutatorDef, c as bindMutators, d as defineMutator } from "./packem_shared/define-mutators.d-DTU85KL6.mjs";
4
4
  import '@lunora/client';
5
5
  import '@tanstack/db';
6
6
  import '@tanstack/offline-transactions';
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export { type C as CheckpointRegistry, type E as ExecutorOutboxSinkOptions, type L as LunoraCollectionConfig, type a as LunoraCollectionOptions, O as OUTBOX_MUTATION_FN_NAME, type b as OutboxExecutor, type d as OutboxMutationMetadata, type R as Row, type S as SyncWriter, c as createCheckpointRegistry, e as createExecutorOutboxSink, f as createOptimisticOnlineDetector, l as lunoraCollectionOptions, m as makeDiffEmit, r as runOutboxMutation, t as toMap } from "./packem_shared/collection-options.d-Cd6j65i-.js";
2
- export { type C as CollectionDef, type D as DefineCollectionsOptions, type I as InsertBinding, type L as LunoraDb, type W as WriteRejectedEvent, d as defineCollections } from "./packem_shared/define-collections.d-CNkWbDe0.js";
3
- export { type B as BindMutatorsContext, type a as BoundMutators, type C as ClientMutatorContext, type b as ClientMutatorDef, c as bindMutators, d as defineMutator } from "./packem_shared/define-mutators.d-Cg_oXn60.js";
1
+ export { type C as CheckpointRegistry, type E as ExecutorOutboxSinkOptions, type L as LunoraCollectionConfig, type a as LunoraCollectionOptions, O as OUTBOX_MUTATION_FN_NAME, type b as OutboxExecutor, type d as OutboxMutationMetadata, type R as Row, type S as SyncWriter, c as createCheckpointRegistry, e as createExecutorOutboxSink, f as createOptimisticOnlineDetector, l as lunoraCollectionOptions, m as makeDiffEmit, r as runOutboxMutation, t as toMap } from "./packem_shared/collection-options.d-BPVkj5FW.js";
2
+ export { type C as CollectionDef, type D as DefineCollectionsOptions, type I as InsertBinding, type L as LunoraDb, type W as WriteRejectedEvent, d as defineCollections } from "./packem_shared/define-collections.d-Dkzt5xPY.js";
3
+ export { type B as BindMutatorsContext, type a as BoundMutators, type C as ClientMutatorContext, type b as ClientMutatorDef, c as bindMutators, d as defineMutator } from "./packem_shared/define-mutators.d-DukVT0Y3.js";
4
4
  import '@lunora/client';
5
5
  import '@tanstack/db';
6
6
  import '@tanstack/offline-transactions';
@@ -1,5 +1,5 @@
1
- export { type B as BindMutatorsContext, type a as BoundMutators, type C as ClientMutatorContext, type b as ClientMutatorDef, c as bindMutators, d as defineMutator } from "../packem_shared/define-mutators.d-B4FIhFEy.mjs";
1
+ export { type B as BindMutatorsContext, type a as BoundMutators, type C as ClientMutatorContext, type b as ClientMutatorDef, c as bindMutators, d as defineMutator } from "../packem_shared/define-mutators.d-DTU85KL6.mjs";
2
2
  import '@lunora/client';
3
3
  import '@tanstack/db';
4
- import "../packem_shared/collection-options.d-Cd6j65i-.mjs";
4
+ import "../packem_shared/collection-options.d-BPVkj5FW.mjs";
5
5
  import '@tanstack/offline-transactions';
@@ -1,5 +1,5 @@
1
- export { type B as BindMutatorsContext, type a as BoundMutators, type C as ClientMutatorContext, type b as ClientMutatorDef, c as bindMutators, d as defineMutator } from "../packem_shared/define-mutators.d-Cg_oXn60.js";
1
+ export { type B as BindMutatorsContext, type a as BoundMutators, type C as ClientMutatorContext, type b as ClientMutatorDef, c as bindMutators, d as defineMutator } from "../packem_shared/define-mutators.d-DukVT0Y3.js";
2
2
  import '@lunora/client';
3
3
  import '@tanstack/db';
4
- import "../packem_shared/collection-options.d-Cd6j65i-.js";
4
+ import "../packem_shared/collection-options.d-BPVkj5FW.js";
5
5
  import '@tanstack/offline-transactions';
@@ -0,0 +1,227 @@
1
+ import { OutboxSink, LunoraClient, FunctionReference, SubscriptionError } from '@lunora/client';
2
+ import { CollectionConfig } from '@tanstack/db';
3
+ import { OnlineDetector } from '@tanstack/offline-transactions';
4
+ /**
5
+ * Reserved `mutationFns` key the unified outbox routes raw `client.mutation`
6
+ * offline writes through. `defineCollections` registers a handler under this
7
+ * name that reads `transaction.metadata` (functionPath + args) and replays the
8
+ * write, so a db app's direct mutations ride the same durable executor as its
9
+ * collection inserts instead of the standalone {@link OutboxSink} fallback.
10
+ */
11
+ declare const OUTBOX_MUTATION_FN_NAME = "__lunora_outbox__";
12
+ /** The metadata an outbox-routed transaction carries so its replay can call `client.mutation`. */
13
+ interface OutboxMutationMetadata extends Record<string, unknown> {
14
+ args: Record<string, unknown>;
15
+ clientId: string;
16
+ functionPath: string;
17
+ /** Stable `${clientId}:${mutationId}` replay key; passed back as the mutation id so a committed-but-unacked replay is server-idempotent. */
18
+ idempotencyKey: string;
19
+ /** Issuing identity fingerprint; the replay handler drops the write when it no longer matches. */
20
+ identity: string | null;
21
+ mutationId: number;
22
+ shardKey?: string;
23
+ }
24
+ /** A committable outbox transaction handle (the `OfflineTransaction` the executor mints). */
25
+ interface OutboxTransaction {
26
+ commit?: () => Promise<unknown>;
27
+ mutate: (callback: () => void) => unknown;
28
+ }
29
+ /**
30
+ * The slice of the TanStack `OfflineExecutor` the {@link createExecutorOutboxSink}
31
+ * drives. Declared structurally so `@lunora/db`'s outbox glue doesn't widen its
32
+ * coupling to the executor's full surface (and stays unit-testable with a fake).
33
+ */
34
+ interface OutboxExecutor {
35
+ createOfflineTransaction: (options: {
36
+ autoCommit?: boolean;
37
+ idempotencyKey?: string;
38
+ metadata?: Record<string, unknown>;
39
+ mutationFnName: string;
40
+ }) => OutboxTransaction;
41
+ getPendingCount: () => number;
42
+ }
43
+ /** Tuning for {@link createExecutorOutboxSink}. */
44
+ interface ExecutorOutboxSinkOptions {
45
+ /** Max persisted-but-unconfirmed writes before `enqueue` rejects with `OFFLINE_QUEUE_OVERFLOW` (default 1000, matching `OfflineQueue`). */
46
+ maxItems?: number;
47
+ /** `mutationFns` key the replay handler is registered under (default {@link OUTBOX_MUTATION_FN_NAME}). */
48
+ mutationFnName?: string;
49
+ }
50
+ /**
51
+ * The blessed {@link OutboxSink} over the TanStack `OfflineExecutor` — the single
52
+ * durable write path for a `@lunora/db` app. It persists each offline write as an
53
+ * executor transaction carrying the `client.mutation` target in `metadata`, then
54
+ * ports the two semantics the executor lacks vs the built-in `OfflineQueue`.
55
+ *
56
+ * First: a `maxItems` cap that **rejects** a new write at capacity with an
57
+ * `OFFLINE_QUEUE_OVERFLOW`-coded error — the {@link OutboxSink} contract, matching
58
+ * `OfflineQueue`. Rejecting (rather than evicting the oldest) preserves the
59
+ * at-least-once promise: an already-persisted write is never silently dropped, and
60
+ * the caller surfaces back-pressure to the issuing mutation (which rolls its
61
+ * optimistic write back).
62
+ * Second: the identity guard lives in the replay handler (`defineCollections`), which drops a write whose captured `identity` no longer matches — see {@link OutboxMutationMetadata}.
63
+ */
64
+ declare const createExecutorOutboxSink: (executor: OutboxExecutor, options?: ExecutorOutboxSinkOptions) => OutboxSink;
65
+ /** A row carrying the Lunora document id. */
66
+ type Row = Record<string, unknown> & {
67
+ _id: string;
68
+ };
69
+ /** The subset of a TanStack DB sync write channel that {@link makeDiffEmit} drives. */
70
+ interface SyncWriter<T extends object> {
71
+ begin: () => void;
72
+ commit: () => void;
73
+ write: (message: {
74
+ type: "insert" | "update";
75
+ value: T;
76
+ } | {
77
+ key: string;
78
+ type: "delete";
79
+ }) => void;
80
+ }
81
+ /** Index a row list into a keyed map. */
82
+ declare const toMap: <T extends object>(rows: ReadonlyArray<T>, getKey: (row: T) => string) => Map<string, T>;
83
+ /**
84
+ * Build an `emit(next)` that diffs a desired keyed snapshot into a collection's
85
+ * sync channel — only changed rows are written, so a reconnect snapshot or a
86
+ * scope change never churns the synced view out from under a pending optimistic
87
+ * row. The last-synced base is tracked in `syncedJson`.
88
+ *
89
+ * Change detection compares rows by `JSON.stringify`, which is key-order
90
+ * sensitive — safe here because server snapshots have stable column order
91
+ * across reconnects (same query projection). A sync source with unstable key
92
+ * ordering would need a structural compare instead.
93
+ *
94
+ * `syncedJson` holds the JSON-serialized form of each last-synced row, keyed
95
+ * by row id. It is the **sole** synced-state map — no parallel row-object map
96
+ * is kept. Each incoming value is serialized exactly once per tick (for both
97
+ * comparison and cache update), so the previous value is never re-serialized.
98
+ *
99
+ * Lifecycle: `syncedJson` must be owned by the caller at the same scope as any
100
+ * other per-collection state (e.g. outside the `sync.sync` callback), so the
101
+ * cache persists correctly across sync restarts. A new `makeDiffEmit` closure
102
+ * created on restart receives the same map reference and starts from the
103
+ * committed synced state — no spurious diffs on reconnect.
104
+ */
105
+ declare const makeDiffEmit: <T extends object>(syncedJson: Map<string, string>, writer: SyncWriter<T>) => (next: Map<string, T>) => void;
106
+ /**
107
+ * Run a Lunora mutation under the outbox's retry policy.
108
+ *
109
+ * The retryable/permanent split keys on whether the failure carries a server
110
+ * application error `code` (set by `@lunora/client`'s rpc when the server returns
111
+ * a `{ error: { code, … } }` envelope — validation, conflict, etc.). A coded
112
+ * error is a definite verdict: surface it as a `NonRetriableError` so the executor
113
+ * stops and TanStack DB rolls the optimistic insert back. Everything without a
114
+ * code is transient — a `fetch` network failure (`TypeError`) or an HTTP/infra
115
+ * blip the rpc surfaces as a code-less `Error` (a 5xx gateway page, a non-JSON
116
+ * body) — so it's rethrown as-is and the durable outbox replays it. Keying on
117
+ * `error instanceof TypeError` alone would wrongly drop the latter.
118
+ */
119
+ declare const runOutboxMutation: (mutate: () => Promise<unknown>) => Promise<void>;
120
+ /**
121
+ * An "always attempt" online detector. We deliberately don't trust
122
+ * `navigator.onLine`: some environments (and Playwright's `setOffline` under
123
+ * Firefox) leave it stuck, which would freeze the outbox. Instead the executor
124
+ * always tries the send and {@link runOutboxMutation}'s transient-error retry
125
+ * handles real offline; the periodic tick nudges the executor to drain the outbox
126
+ * so a queued write replays promptly once connectivity returns.
127
+ *
128
+ * `isOnline` is therefore intentionally always `true` — it gates the executor's
129
+ * attempts, not a UI signal. A consumer that wants to show real connectivity
130
+ * should read `navigator.onLine` itself, separately from this detector.
131
+ */
132
+ declare const createOptimisticOnlineDetector: () => OnlineDetector;
133
+ /**
134
+ * Resolves the TanStack optimistic-overlay drop against the server's confirmed
135
+ * watermarks. A mutator's optimistic transaction returns `awaitMutationId(id)`
136
+ * (or `awaitCheckpoint(cursor)`); TanStack keeps the overlay until that promise
137
+ * settles, so the row de-duplicates exactly as the synced server value lands — no
138
+ * flash of the optimistic row disappearing then reappearing.
139
+ *
140
+ * `resolve` is called by whoever owns the watermark stream — a `data`/`delta`
141
+ * frame's `lastMutationId`, or a shape poke's `checkpoint` — to advance the gates.
142
+ */
143
+ interface CheckpointRegistry {
144
+ /** Resolve once the server has acknowledged the op-log `cursor`. */
145
+ awaitCheckpoint: (cursor: number) => Promise<void>;
146
+ /** Resolve once the server has echoed a `lastMutationId >= id` for this client. */
147
+ awaitMutationId: (id: number) => Promise<void>;
148
+ /** Advance the gates from a frame's watermark; later callers past the mark settle immediately. */
149
+ resolve: (watermark: {
150
+ checkpoint?: number;
151
+ mutationId?: number;
152
+ }) => void;
153
+ }
154
+ /** A standalone checkpoint/mutation-id registry (also embedded in {@link lunoraCollectionOptions}). */
155
+ declare const createCheckpointRegistry: () => CheckpointRegistry;
156
+ /**
157
+ * A replication-shape sync source (the local-first partial-replication path).
158
+ * Mutually exclusive with {@link LunoraCollectionConfig.list}: the collection
159
+ * live-syncs the named shape's rowset via the client's poke protocol
160
+ * (`subscribeShape`) instead of a full-table `list` query subscription.
161
+ */
162
+ interface ShapeSource {
163
+ /** Validated shape parameters (the partition selector — e.g. `{ channelId }`). */
164
+ args?: Record<string, unknown>;
165
+ /** The `defineShape` export name registered in `LUNORA_SHAPES`. */
166
+ name: string;
167
+ /** Routes the subscription to a specific shard's DO when the table is sharded. */
168
+ shardKey?: string;
169
+ }
170
+ /** Declarative inputs for {@link lunoraCollectionOptions}. */
171
+ interface LunoraCollectionConfig<TRow extends Row> {
172
+ /** The Lunora client to subscribe through. */
173
+ client: LunoraClient;
174
+ /** Row key extractor — defaults to `row._id`. */
175
+ getKey?: (row: TRow) => string;
176
+ /** Collection id (TanStack identity) — defaults to the `list` function path (or `shape:` + the shape name). */
177
+ id?: string;
178
+ /** The Lunora query that lists the rows (the full-table sync source). Mutually exclusive with {@link LunoraCollectionConfig.shape}. */
179
+ list?: FunctionReference;
180
+ /**
181
+ * When the collection starts syncing. `"lazy"` (default) starts on the first
182
+ * `useLiveQuery` subscriber; `"eager"` starts at creation (TanStack's
183
+ * `startSync`) — for small "instant" reference data you want warm at boot.
184
+ * No effect on a `scopeBy` collection, which has nothing to sync until scoped.
185
+ * (Even eager, TanStack pauses sync while there are no subscribers, per its
186
+ * `gcTime` lifecycle — "warm while referenced", not pinned forever.)
187
+ */
188
+ load?: "eager" | "lazy";
189
+ /** Notified when the underlying subscription errors; the collection always leaves `loading` regardless. */
190
+ onError?: (error: SubscriptionError) => void;
191
+ /** When set, the collection stays empty until {@link LunoraCollectionOptions.scope} points it at args (sharded). */
192
+ scopeBy?: string;
193
+ /** A replication shape as the sync source (partial replication). Mutually exclusive with {@link LunoraCollectionConfig.list}. */
194
+ shape?: ShapeSource;
195
+ /**
196
+ * Routes the `list` subscription — and the confirmed-mutation watermark its
197
+ * data/`settled` frames advance the checkpoint gate from — to a specific
198
+ * shard's DO. Applies to the `list` source; a `shape` carries its own
199
+ * {@link ShapeSource.shardKey}. Without it the list path falls back to the
200
+ * default ("") watermark bucket, which must not be compared against a
201
+ * per-shard mutator's sequence line (it would drop a sharded overlay early or
202
+ * hang it forever).
203
+ */
204
+ shardKey?: string;
205
+ }
206
+ /** The result of {@link lunoraCollectionOptions}: a TanStack collection config plus its sync controls. */
207
+ interface LunoraCollectionOptions<TRow extends Row> {
208
+ /** Resolves optimistic-overlay drops against the server's confirmed watermarks. */
209
+ checkpoints: CheckpointRegistry;
210
+ /** Pass to TanStack's `createCollection`. */
211
+ config: CollectionConfig<TRow, string>;
212
+ /** Re-point a `scopeBy` collection's subscription (omit `args` to detach). No-op for unscoped collections. */
213
+ scope: (args?: Record<string, unknown>) => void;
214
+ }
215
+ /**
216
+ * Build a TanStack DB collection config (+ sync controls) that live-syncs a
217
+ * Lunora `list` query through the client. This is the reusable core lifted out of
218
+ * {@link import("./define-collections").defineCollections}: the same `makeDiffEmit`
219
+ * diff-into-channel, `autoIndex:"eager"` + B-tree indexes, scoped-resubscribe, and
220
+ * fail-safe `markReady`-on-error behavior, exposed as a standalone
221
+ * collection-options creator so apps (and codegen) can compose it directly.
222
+ *
223
+ * The returned `checkpoints` registry lets a mutator runtime resolve optimistic
224
+ * overlays against confirmed server watermarks (see {@link CheckpointRegistry}).
225
+ */
226
+ declare const lunoraCollectionOptions: <TRow extends Row>(options: LunoraCollectionConfig<TRow>) => LunoraCollectionOptions<TRow>;
227
+ export { CheckpointRegistry as C, ExecutorOutboxSinkOptions as E, LunoraCollectionConfig as L, OUTBOX_MUTATION_FN_NAME as O, Row as R, SyncWriter as S, LunoraCollectionOptions as a, OutboxExecutor as b, createCheckpointRegistry as c, OutboxMutationMetadata as d, createExecutorOutboxSink as e, createOptimisticOnlineDetector as f, lunoraCollectionOptions as l, makeDiffEmit as m, runOutboxMutation as r, toMap as t };
@@ -0,0 +1,227 @@
1
+ import { OutboxSink, LunoraClient, FunctionReference, SubscriptionError } from '@lunora/client';
2
+ import { CollectionConfig } from '@tanstack/db';
3
+ import { OnlineDetector } from '@tanstack/offline-transactions';
4
+ /**
5
+ * Reserved `mutationFns` key the unified outbox routes raw `client.mutation`
6
+ * offline writes through. `defineCollections` registers a handler under this
7
+ * name that reads `transaction.metadata` (functionPath + args) and replays the
8
+ * write, so a db app's direct mutations ride the same durable executor as its
9
+ * collection inserts instead of the standalone {@link OutboxSink} fallback.
10
+ */
11
+ declare const OUTBOX_MUTATION_FN_NAME = "__lunora_outbox__";
12
+ /** The metadata an outbox-routed transaction carries so its replay can call `client.mutation`. */
13
+ interface OutboxMutationMetadata extends Record<string, unknown> {
14
+ args: Record<string, unknown>;
15
+ clientId: string;
16
+ functionPath: string;
17
+ /** Stable `${clientId}:${mutationId}` replay key; passed back as the mutation id so a committed-but-unacked replay is server-idempotent. */
18
+ idempotencyKey: string;
19
+ /** Issuing identity fingerprint; the replay handler drops the write when it no longer matches. */
20
+ identity: string | null;
21
+ mutationId: number;
22
+ shardKey?: string;
23
+ }
24
+ /** A committable outbox transaction handle (the `OfflineTransaction` the executor mints). */
25
+ interface OutboxTransaction {
26
+ commit?: () => Promise<unknown>;
27
+ mutate: (callback: () => void) => unknown;
28
+ }
29
+ /**
30
+ * The slice of the TanStack `OfflineExecutor` the {@link createExecutorOutboxSink}
31
+ * drives. Declared structurally so `@lunora/db`'s outbox glue doesn't widen its
32
+ * coupling to the executor's full surface (and stays unit-testable with a fake).
33
+ */
34
+ interface OutboxExecutor {
35
+ createOfflineTransaction: (options: {
36
+ autoCommit?: boolean;
37
+ idempotencyKey?: string;
38
+ metadata?: Record<string, unknown>;
39
+ mutationFnName: string;
40
+ }) => OutboxTransaction;
41
+ getPendingCount: () => number;
42
+ }
43
+ /** Tuning for {@link createExecutorOutboxSink}. */
44
+ interface ExecutorOutboxSinkOptions {
45
+ /** Max persisted-but-unconfirmed writes before `enqueue` rejects with `OFFLINE_QUEUE_OVERFLOW` (default 1000, matching `OfflineQueue`). */
46
+ maxItems?: number;
47
+ /** `mutationFns` key the replay handler is registered under (default {@link OUTBOX_MUTATION_FN_NAME}). */
48
+ mutationFnName?: string;
49
+ }
50
+ /**
51
+ * The blessed {@link OutboxSink} over the TanStack `OfflineExecutor` — the single
52
+ * durable write path for a `@lunora/db` app. It persists each offline write as an
53
+ * executor transaction carrying the `client.mutation` target in `metadata`, then
54
+ * ports the two semantics the executor lacks vs the built-in `OfflineQueue`.
55
+ *
56
+ * First: a `maxItems` cap that **rejects** a new write at capacity with an
57
+ * `OFFLINE_QUEUE_OVERFLOW`-coded error — the {@link OutboxSink} contract, matching
58
+ * `OfflineQueue`. Rejecting (rather than evicting the oldest) preserves the
59
+ * at-least-once promise: an already-persisted write is never silently dropped, and
60
+ * the caller surfaces back-pressure to the issuing mutation (which rolls its
61
+ * optimistic write back).
62
+ * Second: the identity guard lives in the replay handler (`defineCollections`), which drops a write whose captured `identity` no longer matches — see {@link OutboxMutationMetadata}.
63
+ */
64
+ declare const createExecutorOutboxSink: (executor: OutboxExecutor, options?: ExecutorOutboxSinkOptions) => OutboxSink;
65
+ /** A row carrying the Lunora document id. */
66
+ type Row = Record<string, unknown> & {
67
+ _id: string;
68
+ };
69
+ /** The subset of a TanStack DB sync write channel that {@link makeDiffEmit} drives. */
70
+ interface SyncWriter<T extends object> {
71
+ begin: () => void;
72
+ commit: () => void;
73
+ write: (message: {
74
+ type: "insert" | "update";
75
+ value: T;
76
+ } | {
77
+ key: string;
78
+ type: "delete";
79
+ }) => void;
80
+ }
81
+ /** Index a row list into a keyed map. */
82
+ declare const toMap: <T extends object>(rows: ReadonlyArray<T>, getKey: (row: T) => string) => Map<string, T>;
83
+ /**
84
+ * Build an `emit(next)` that diffs a desired keyed snapshot into a collection's
85
+ * sync channel — only changed rows are written, so a reconnect snapshot or a
86
+ * scope change never churns the synced view out from under a pending optimistic
87
+ * row. The last-synced base is tracked in `syncedJson`.
88
+ *
89
+ * Change detection compares rows by `JSON.stringify`, which is key-order
90
+ * sensitive — safe here because server snapshots have stable column order
91
+ * across reconnects (same query projection). A sync source with unstable key
92
+ * ordering would need a structural compare instead.
93
+ *
94
+ * `syncedJson` holds the JSON-serialized form of each last-synced row, keyed
95
+ * by row id. It is the **sole** synced-state map — no parallel row-object map
96
+ * is kept. Each incoming value is serialized exactly once per tick (for both
97
+ * comparison and cache update), so the previous value is never re-serialized.
98
+ *
99
+ * Lifecycle: `syncedJson` must be owned by the caller at the same scope as any
100
+ * other per-collection state (e.g. outside the `sync.sync` callback), so the
101
+ * cache persists correctly across sync restarts. A new `makeDiffEmit` closure
102
+ * created on restart receives the same map reference and starts from the
103
+ * committed synced state — no spurious diffs on reconnect.
104
+ */
105
+ declare const makeDiffEmit: <T extends object>(syncedJson: Map<string, string>, writer: SyncWriter<T>) => (next: Map<string, T>) => void;
106
+ /**
107
+ * Run a Lunora mutation under the outbox's retry policy.
108
+ *
109
+ * The retryable/permanent split keys on whether the failure carries a server
110
+ * application error `code` (set by `@lunora/client`'s rpc when the server returns
111
+ * a `{ error: { code, … } }` envelope — validation, conflict, etc.). A coded
112
+ * error is a definite verdict: surface it as a `NonRetriableError` so the executor
113
+ * stops and TanStack DB rolls the optimistic insert back. Everything without a
114
+ * code is transient — a `fetch` network failure (`TypeError`) or an HTTP/infra
115
+ * blip the rpc surfaces as a code-less `Error` (a 5xx gateway page, a non-JSON
116
+ * body) — so it's rethrown as-is and the durable outbox replays it. Keying on
117
+ * `error instanceof TypeError` alone would wrongly drop the latter.
118
+ */
119
+ declare const runOutboxMutation: (mutate: () => Promise<unknown>) => Promise<void>;
120
+ /**
121
+ * An "always attempt" online detector. We deliberately don't trust
122
+ * `navigator.onLine`: some environments (and Playwright's `setOffline` under
123
+ * Firefox) leave it stuck, which would freeze the outbox. Instead the executor
124
+ * always tries the send and {@link runOutboxMutation}'s transient-error retry
125
+ * handles real offline; the periodic tick nudges the executor to drain the outbox
126
+ * so a queued write replays promptly once connectivity returns.
127
+ *
128
+ * `isOnline` is therefore intentionally always `true` — it gates the executor's
129
+ * attempts, not a UI signal. A consumer that wants to show real connectivity
130
+ * should read `navigator.onLine` itself, separately from this detector.
131
+ */
132
+ declare const createOptimisticOnlineDetector: () => OnlineDetector;
133
+ /**
134
+ * Resolves the TanStack optimistic-overlay drop against the server's confirmed
135
+ * watermarks. A mutator's optimistic transaction returns `awaitMutationId(id)`
136
+ * (or `awaitCheckpoint(cursor)`); TanStack keeps the overlay until that promise
137
+ * settles, so the row de-duplicates exactly as the synced server value lands — no
138
+ * flash of the optimistic row disappearing then reappearing.
139
+ *
140
+ * `resolve` is called by whoever owns the watermark stream — a `data`/`delta`
141
+ * frame's `lastMutationId`, or a shape poke's `checkpoint` — to advance the gates.
142
+ */
143
+ interface CheckpointRegistry {
144
+ /** Resolve once the server has acknowledged the op-log `cursor`. */
145
+ awaitCheckpoint: (cursor: number) => Promise<void>;
146
+ /** Resolve once the server has echoed a `lastMutationId >= id` for this client. */
147
+ awaitMutationId: (id: number) => Promise<void>;
148
+ /** Advance the gates from a frame's watermark; later callers past the mark settle immediately. */
149
+ resolve: (watermark: {
150
+ checkpoint?: number;
151
+ mutationId?: number;
152
+ }) => void;
153
+ }
154
+ /** A standalone checkpoint/mutation-id registry (also embedded in {@link lunoraCollectionOptions}). */
155
+ declare const createCheckpointRegistry: () => CheckpointRegistry;
156
+ /**
157
+ * A replication-shape sync source (the local-first partial-replication path).
158
+ * Mutually exclusive with {@link LunoraCollectionConfig.list}: the collection
159
+ * live-syncs the named shape's rowset via the client's poke protocol
160
+ * (`subscribeShape`) instead of a full-table `list` query subscription.
161
+ */
162
+ interface ShapeSource {
163
+ /** Validated shape parameters (the partition selector — e.g. `{ channelId }`). */
164
+ args?: Record<string, unknown>;
165
+ /** The `defineShape` export name registered in `LUNORA_SHAPES`. */
166
+ name: string;
167
+ /** Routes the subscription to a specific shard's DO when the table is sharded. */
168
+ shardKey?: string;
169
+ }
170
+ /** Declarative inputs for {@link lunoraCollectionOptions}. */
171
+ interface LunoraCollectionConfig<TRow extends Row> {
172
+ /** The Lunora client to subscribe through. */
173
+ client: LunoraClient;
174
+ /** Row key extractor — defaults to `row._id`. */
175
+ getKey?: (row: TRow) => string;
176
+ /** Collection id (TanStack identity) — defaults to the `list` function path (or `shape:` + the shape name). */
177
+ id?: string;
178
+ /** The Lunora query that lists the rows (the full-table sync source). Mutually exclusive with {@link LunoraCollectionConfig.shape}. */
179
+ list?: FunctionReference;
180
+ /**
181
+ * When the collection starts syncing. `"lazy"` (default) starts on the first
182
+ * `useLiveQuery` subscriber; `"eager"` starts at creation (TanStack's
183
+ * `startSync`) — for small "instant" reference data you want warm at boot.
184
+ * No effect on a `scopeBy` collection, which has nothing to sync until scoped.
185
+ * (Even eager, TanStack pauses sync while there are no subscribers, per its
186
+ * `gcTime` lifecycle — "warm while referenced", not pinned forever.)
187
+ */
188
+ load?: "eager" | "lazy";
189
+ /** Notified when the underlying subscription errors; the collection always leaves `loading` regardless. */
190
+ onError?: (error: SubscriptionError) => void;
191
+ /** When set, the collection stays empty until {@link LunoraCollectionOptions.scope} points it at args (sharded). */
192
+ scopeBy?: string;
193
+ /** A replication shape as the sync source (partial replication). Mutually exclusive with {@link LunoraCollectionConfig.list}. */
194
+ shape?: ShapeSource;
195
+ /**
196
+ * Routes the `list` subscription — and the confirmed-mutation watermark its
197
+ * data/`settled` frames advance the checkpoint gate from — to a specific
198
+ * shard's DO. Applies to the `list` source; a `shape` carries its own
199
+ * {@link ShapeSource.shardKey}. Without it the list path falls back to the
200
+ * default ("") watermark bucket, which must not be compared against a
201
+ * per-shard mutator's sequence line (it would drop a sharded overlay early or
202
+ * hang it forever).
203
+ */
204
+ shardKey?: string;
205
+ }
206
+ /** The result of {@link lunoraCollectionOptions}: a TanStack collection config plus its sync controls. */
207
+ interface LunoraCollectionOptions<TRow extends Row> {
208
+ /** Resolves optimistic-overlay drops against the server's confirmed watermarks. */
209
+ checkpoints: CheckpointRegistry;
210
+ /** Pass to TanStack's `createCollection`. */
211
+ config: CollectionConfig<TRow, string>;
212
+ /** Re-point a `scopeBy` collection's subscription (omit `args` to detach). No-op for unscoped collections. */
213
+ scope: (args?: Record<string, unknown>) => void;
214
+ }
215
+ /**
216
+ * Build a TanStack DB collection config (+ sync controls) that live-syncs a
217
+ * Lunora `list` query through the client. This is the reusable core lifted out of
218
+ * {@link import("./define-collections").defineCollections}: the same `makeDiffEmit`
219
+ * diff-into-channel, `autoIndex:"eager"` + B-tree indexes, scoped-resubscribe, and
220
+ * fail-safe `markReady`-on-error behavior, exposed as a standalone
221
+ * collection-options creator so apps (and codegen) can compose it directly.
222
+ *
223
+ * The returned `checkpoints` registry lets a mutator runtime resolve optimistic
224
+ * overlays against confirmed server watermarks (see {@link CheckpointRegistry}).
225
+ */
226
+ declare const lunoraCollectionOptions: <TRow extends Row>(options: LunoraCollectionConfig<TRow>) => LunoraCollectionOptions<TRow>;
227
+ export { CheckpointRegistry as C, ExecutorOutboxSinkOptions as E, LunoraCollectionConfig as L, OUTBOX_MUTATION_FN_NAME as O, Row as R, SyncWriter as S, LunoraCollectionOptions as a, OutboxExecutor as b, createCheckpointRegistry as c, OutboxMutationMetadata as d, createExecutorOutboxSink as e, createOptimisticOnlineDetector as f, lunoraCollectionOptions as l, makeDiffEmit as m, runOutboxMutation as r, toMap as t };