@lunora/db 1.0.0-alpha.31 → 1.0.0-alpha.33

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
@@ -2,7 +2,7 @@ import { Collection } from '@tanstack/db';
2
2
  import { R as Row } from "./packem_shared/collection-options.d-B_2IXvdU.mjs";
3
3
  export { b as CHECKPOINT_FALLBACK_MS, type d as CheckpointFallbackEvent, type C as CheckpointRegistry, type e as CheckpointRegistryOptions, type f as CheckpointRegistryStats, type g as CheckpointWatermark, type E as ExecutorOutboxSinkOptions, type L as LunoraCollectionConfig, type a as LunoraCollectionOptions, O as OUTBOX_MUTATION_FN_NAME, type h as OutboxExecutor, type i as OutboxMutationMetadata, type S as SyncWriter, c as createCheckpointRegistry, j as createExecutorOutboxSink, k as createOptimisticOnlineDetector, m as getShardCheckpoints, l as lunoraCollectionOptions, n as makeDiffEmit, r as releaseShardCheckpoints, o as runOutboxMutation, s as shardCheckpointStats, t as toMap } from "./packem_shared/collection-options.d-B_2IXvdU.mjs";
4
4
  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-C6J1Y4Q_.mjs";
5
- export { type B as BindMutatorsContext, type a as BoundMutators, type C as ClientMutatorContext, type b as ClientMutatorDef, D as DIRECT_TRANSACTION_METADATA_KEY, type M as MutatorReference, c as bindMutators, d as defineMutator } from "./packem_shared/define-mutators.d--WcIPEJa.mjs";
5
+ export { type B as BindMutatorsContext, type a as BoundMutatorApi, type b as BoundMutators, type C as ClientMutatorContext, type c as ClientMutatorDef, type d as CollectionMap, D as DIRECT_TRANSACTION_METADATA_KEY, type g as MutatorReference, type M as MutatorRejectedEvent, e as bindMutators, f as defineMutator, i as initMutators } from "./packem_shared/define-mutators.d-BXQN7fju.mjs";
6
6
  import '@lunora/client';
7
7
  import '@tanstack/offline-transactions';
8
8
  /** One row to insert. `_id` may be pre-minted client-side so the optimistic row keys match the persisted one. */
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import { Collection } from '@tanstack/db';
2
2
  import { R as Row } from "./packem_shared/collection-options.d-B_2IXvdU.js";
3
3
  export { b as CHECKPOINT_FALLBACK_MS, type d as CheckpointFallbackEvent, type C as CheckpointRegistry, type e as CheckpointRegistryOptions, type f as CheckpointRegistryStats, type g as CheckpointWatermark, type E as ExecutorOutboxSinkOptions, type L as LunoraCollectionConfig, type a as LunoraCollectionOptions, O as OUTBOX_MUTATION_FN_NAME, type h as OutboxExecutor, type i as OutboxMutationMetadata, type S as SyncWriter, c as createCheckpointRegistry, j as createExecutorOutboxSink, k as createOptimisticOnlineDetector, m as getShardCheckpoints, l as lunoraCollectionOptions, n as makeDiffEmit, r as releaseShardCheckpoints, o as runOutboxMutation, s as shardCheckpointStats, t as toMap } from "./packem_shared/collection-options.d-B_2IXvdU.js";
4
4
  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-DAFPHFxr.js";
5
- export { type B as BindMutatorsContext, type a as BoundMutators, type C as ClientMutatorContext, type b as ClientMutatorDef, D as DIRECT_TRANSACTION_METADATA_KEY, type M as MutatorReference, c as bindMutators, d as defineMutator } from "./packem_shared/define-mutators.d-C_E1q1_B.js";
5
+ export { type B as BindMutatorsContext, type a as BoundMutatorApi, type b as BoundMutators, type C as ClientMutatorContext, type c as ClientMutatorDef, type d as CollectionMap, D as DIRECT_TRANSACTION_METADATA_KEY, type g as MutatorReference, type M as MutatorRejectedEvent, e as bindMutators, f as defineMutator, i as initMutators } from "./packem_shared/define-mutators.d-DLP_vYu9.js";
6
6
  import '@lunora/client';
7
7
  import '@tanstack/offline-transactions';
8
8
  /** One row to insert. `_id` may be pre-minted client-side so the optimistic row keys match the persisted one. */
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{applyPlanToCollections as r,applyPlanToDb as i}from"./packem_shared/applyPlanToCollections-C29s6OoX.mjs";import{CHECKPOINT_FALLBACK_MS as a,createCheckpointRegistry as p,getShardCheckpoints as c,lunoraCollectionOptions as l,releaseShardCheckpoints as s,shardCheckpointStats as C}from"./packem_shared/CHECKPOINT_FALLBACK_MS-vBJLOBjX.mjs";import{defineCollections as T}from"./packem_shared/defineCollections-BCgkk3A6.mjs";import{DIRECT_TRANSACTION_METADATA_KEY as x,bindMutators as A,defineMutator as m}from"./packem_shared/DIRECT_TRANSACTION_METADATA_KEY-BxF71ve7.mjs";import{OUTBOX_MUTATION_FN_NAME as E,createExecutorOutboxSink as M,createOptimisticOnlineDetector as _,makeDiffEmit as h,runOutboxMutation as N,toMap as S}from"./packem_shared/OUTBOX_MUTATION_FN_NAME-CebgkYw2.mjs";const t="0.0.0";export{a as CHECKPOINT_FALLBACK_MS,x as DIRECT_TRANSACTION_METADATA_KEY,E as OUTBOX_MUTATION_FN_NAME,t as VERSION,r as applyPlanToCollections,i as applyPlanToDb,A as bindMutators,p as createCheckpointRegistry,M as createExecutorOutboxSink,_ as createOptimisticOnlineDetector,T as defineCollections,m as defineMutator,c as getShardCheckpoints,l as lunoraCollectionOptions,h as makeDiffEmit,s as releaseShardCheckpoints,N as runOutboxMutation,C as shardCheckpointStats,S as toMap};
1
+ import{applyPlanToCollections as r,applyPlanToDb as i}from"./packem_shared/applyPlanToCollections-C29s6OoX.mjs";import{CHECKPOINT_FALLBACK_MS as a,createCheckpointRegistry as p,getShardCheckpoints as c,lunoraCollectionOptions as l,releaseShardCheckpoints as s,shardCheckpointStats as C}from"./packem_shared/CHECKPOINT_FALLBACK_MS-vBJLOBjX.mjs";import{defineCollections as T}from"./packem_shared/defineCollections-BCgkk3A6.mjs";import{DIRECT_TRANSACTION_METADATA_KEY as u,bindMutators as x,defineMutator as A,initMutators as M}from"./packem_shared/DIRECT_TRANSACTION_METADATA_KEY-DDE7m9p9.mjs";import{OUTBOX_MUTATION_FN_NAME as E,createExecutorOutboxSink as _,createOptimisticOnlineDetector as h,makeDiffEmit as N,runOutboxMutation as S,toMap as d}from"./packem_shared/OUTBOX_MUTATION_FN_NAME-CebgkYw2.mjs";const t="0.0.0";export{a as CHECKPOINT_FALLBACK_MS,u as DIRECT_TRANSACTION_METADATA_KEY,E as OUTBOX_MUTATION_FN_NAME,t as VERSION,r as applyPlanToCollections,i as applyPlanToDb,x as bindMutators,p as createCheckpointRegistry,_ as createExecutorOutboxSink,h as createOptimisticOnlineDetector,T as defineCollections,A as defineMutator,c as getShardCheckpoints,M as initMutators,l as lunoraCollectionOptions,N as makeDiffEmit,s as releaseShardCheckpoints,S as runOutboxMutation,C as shardCheckpointStats,d as toMap};
@@ -1,4 +1,4 @@
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--WcIPEJa.mjs";
1
+ export { type B as BindMutatorsContext, type a as BoundMutatorApi, type b as BoundMutators, type C as ClientMutatorContext, type c as ClientMutatorDef, type d as CollectionMap, type M as MutatorRejectedEvent, e as bindMutators, f as defineMutator, i as initMutators } from "../packem_shared/define-mutators.d-BXQN7fju.mjs";
2
2
  import '@lunora/client';
3
3
  import '@tanstack/db';
4
4
  import "../packem_shared/collection-options.d-B_2IXvdU.mjs";
@@ -1,4 +1,4 @@
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-C_E1q1_B.js";
1
+ export { type B as BindMutatorsContext, type a as BoundMutatorApi, type b as BoundMutators, type C as ClientMutatorContext, type c as ClientMutatorDef, type d as CollectionMap, type M as MutatorRejectedEvent, e as bindMutators, f as defineMutator, i as initMutators } from "../packem_shared/define-mutators.d-DLP_vYu9.js";
2
2
  import '@lunora/client';
3
3
  import '@tanstack/db';
4
4
  import "../packem_shared/collection-options.d-B_2IXvdU.js";
@@ -1 +1 @@
1
- import{bindMutators as r,defineMutator as e}from"../packem_shared/DIRECT_TRANSACTION_METADATA_KEY-BxF71ve7.mjs";export{r as bindMutators,e as defineMutator};
1
+ import{bindMutators as r,defineMutator as i,initMutators as a}from"../packem_shared/DIRECT_TRANSACTION_METADATA_KEY-DDE7m9p9.mjs";export{r as bindMutators,i as defineMutator,a as initMutators};
@@ -0,0 +1 @@
1
+ import{LunoraError as l}from"@lunora/errors";import{createTransaction as g}from"@tanstack/db";import{getShardCheckpoints as w,hasCheckpointsAttached as M}from"./CHECKPOINT_FALLBACK_MS-vBJLOBjX.mjs";import{runOutboxMutation as _}from"./OUTBOX_MUTATION_FN_NAME-CebgkYw2.mjs";const v="__tanstack_db_direct",T="defineMutator: `serverRef` must be a generated mutator reference (api.mutators.*) or a 'namespace:fn' string",k=e=>{const t=typeof e=="string"?e:e?.__lunoraRef;if(typeof t!="string"||t.length===0)throw new l("INTERNAL",T);return t},A=e=>({__lunoraClientMutator:!0,apply:e.apply,serverRef:k(e.serverRef)}),E=(e,t,p)=>{let i=0;const d=()=>(i=Math.max(i,e.confirmedMutationWatermark(t.shardKey))+1,i);let u=Promise.resolve();const h=(r,a)=>{const s=u.then(async()=>{for(let c=0;;c+=1){const o=d(),{applied:n}=await e.callMutator(r,a,{clientSeq:o,shardKey:t.shardKey});if(n)return o;if(c>=32)throw new l("INTERNAL",`lunora: custom mutator "${r}" could not claim a fresh client sequence after ${String(32)} attempts`)}});return u=s.then(()=>{},()=>{}),s},R=()=>{if(t.checkpoints===!1)return;if(t.checkpoints)return t.checkpoints;const r=w(e,t.shardKey);return M(r)?r:void 0},y=(r,a,s,c)=>{const{onWriteRejected:o}=t;o&&r.isPersisted.promise.catch(n=>{const m=n instanceof Error?n:new Error(String(n));try{o({args:c,code:m.code,error:m,mutator:a,serverRef:s})}catch{}})},f={};for(const[r,a]of Object.entries(p))f[r]=s=>{const c=g({autoCommit:!0,metadata:{[v]:!0,serverRef:a.serverRef},mutationFn:async()=>{let o=0;await _(async()=>{o=await h(a.serverRef,s)});const n=R();n&&(n.acknowledge({mutationId:o}),await n.awaitMutationId(o))}});return c.mutate(()=>{a.apply({collections:t.collections},s)}),y(c,r,a.serverRef,s),c};return f},K=()=>({bindMutators:E,defineMutator:A});export{v as DIRECT_TRANSACTION_METADATA_KEY,E as bindMutators,A as defineMutator,K as initMutators};
@@ -1,6 +1,6 @@
1
1
  import { LunoraClient } from '@lunora/client';
2
2
  import { Collection, Transaction } from '@tanstack/db';
3
- import { C as CheckpointRegistry, R as Row } from "./collection-options.d-B_2IXvdU.js";
3
+ import { C as CheckpointRegistry } from "./collection-options.d-B_2IXvdU.mjs";
4
4
  /**
5
5
  * TanStack DB's "direct transaction" marker.
6
6
  *
@@ -19,10 +19,23 @@ import { C as CheckpointRegistry, R as Row } from "./collection-options.d-B_2IXv
19
19
  * reads that module off disk and fails if the upstream value ever changes.
20
20
  */
21
21
  declare const DIRECT_TRANSACTION_METADATA_KEY = "__tanstack_db_direct";
22
+ /**
23
+ * A map of wired collections, keyed by the name the optimistic bodies address them
24
+ * by.
25
+ *
26
+ * The row type is `any` on purpose. `Collection<T, string>` is **invariant** in
27
+ * `T` (its methods both consume and produce rows), so a concrete
28
+ * `Collection<Doc<"nodes"> & Row>` is NOT assignable to `Collection<Row>` — which
29
+ * is why the previous `Record<string, Collection<Row, string>>` forced an
30
+ * `as never` cast on every entry, and left `context.collections.<name>` too
31
+ * untyped to be worth using. Projects recover the concrete types by binding them
32
+ * once through {@link initMutators}.
33
+ */
34
+ type CollectionMap = Record<string, Collection<any, string>>;
22
35
  /** The local store a client mutator's optimistic body writes against. */
23
- interface ClientMutatorContext {
36
+ interface ClientMutatorContext<TCollections extends CollectionMap = CollectionMap> {
24
37
  /** The wired collections, keyed by name — apply optimistic inserts/updates/deletes here. */
25
- collections: Record<string, Collection<Row, string>>;
38
+ collections: TCollections;
26
39
  }
27
40
  /**
28
41
  * A generated mutator reference (`api.mutators.sendMessage`), accepted by
@@ -41,14 +54,12 @@ interface MutatorReference<TArgs = unknown> {
41
54
  };
42
55
  readonly __lunoraRef: string;
43
56
  }
44
- /** Args type carried by a {@link MutatorReference}. */
45
- type ArgsOfReference<R> = R extends MutatorReference<infer A> ? A : never;
46
57
  /** A client-side custom mutator: an optimistic body plus the path of its authoritative server impl. */
47
- interface ClientMutatorDef<TArgs> {
58
+ interface ClientMutatorDef<TArgs, TCollections extends CollectionMap = CollectionMap> {
48
59
  /** Brand so codegen / `bindMutators` can recognize a mutator definition. */
49
60
  __lunoraClientMutator: true;
50
61
  /** The optimistic update applied to the local collections before the server confirms. */
51
- apply: (context: ClientMutatorContext, args: TArgs) => void;
62
+ apply: (context: ClientMutatorContext<TCollections>, args: TArgs) => void;
52
63
  /** The Lunora function path of the server-authoritative mutator (`defineMutator` on the server). */
53
64
  serverRef: string;
54
65
  }
@@ -75,22 +86,47 @@ interface ClientMutatorDef<TArgs> {
75
86
  * // restate the args yourself.
76
87
  * defineMutator<{ text: string }>({ apply, serverRef: "mutators:sendMessage" });
77
88
  * ```
89
+ *
90
+ * `context.collections` is typed by whatever map it is bound to — which for this
91
+ * standalone form is the widest one. Bind the concrete collections once with
92
+ * {@link initMutators} to get `collections.&lt;name>` typed inside `apply`.
78
93
  */
79
94
  declare const defineMutator: {
80
95
  <TArgs = Record<string, unknown>>(definition: {
81
96
  apply: (context: ClientMutatorContext, args: TArgs) => void;
82
97
  serverRef: string;
83
98
  }): ClientMutatorDef<TArgs>;
84
- <R extends MutatorReference<never>>(definition: {
85
- apply: (context: ClientMutatorContext, args: ArgsOfReference<R>) => void;
86
- serverRef: R;
87
- }): ClientMutatorDef<ArgsOfReference<R>>;
99
+ <TArgs>(definition: {
100
+ apply: (context: ClientMutatorContext, args: TArgs) => void;
101
+ serverRef: MutatorReference<TArgs>;
102
+ }): ClientMutatorDef<TArgs>;
88
103
  };
89
- type AnyMutatorMap = Record<string, ClientMutatorDef<any>>;
90
- /** Args type of a mutator definition. */
91
- type ArgsOf<M> = M extends ClientMutatorDef<infer A> ? A : never;
92
- /** Inputs `bindMutators` needs to run a mutator: the local store + how the overlay drops. */
93
- interface BindMutatorsContext {
104
+ /**
105
+ * A mutator map whose defs are bound to `TCollections`, arg types erased — the
106
+ * `TCollections`-pinned counterpart of {@link AnyMutatorMap}, and `any` for the
107
+ * same variance reason.
108
+ */
109
+ type MutatorMapFor<TCollections extends CollectionMap> = Record<string, ClientMutatorDef<any, TCollections>>;
110
+ type AnyMutatorMap = Record<string, ClientMutatorDef<any, any>>;
111
+ /** Args type of a mutator definition, whatever collections map it was bound to. */
112
+ type ArgsOf<M> = M extends ClientMutatorDef<infer A, infer _C> ? A : never;
113
+ /** A mutator write that was permanently rejected, passed to {@link BindMutatorsContext.onWriteRejected}. */
114
+ interface MutatorRejectedEvent {
115
+ /** The args the rejected call was made with. */
116
+ args: unknown;
117
+ /**
118
+ * The machine-readable reason when the server supplied one (e.g. `CONFLICT`,
119
+ * `FORBIDDEN`). Mirrors {@link import("./define-collections").WriteRejectedEvent.code}.
120
+ */
121
+ code?: string;
122
+ /** The error that rejected the write. */
123
+ error: Error;
124
+ /** The bound mutator's key in the map passed to `bindMutators`. */
125
+ mutator: string;
126
+ /** The `namespace:fn` path the push targeted. */
127
+ serverRef: string;
128
+ }
129
+ interface BindMutatorsContext<TCollections extends CollectionMap = CollectionMap> {
94
130
  /**
95
131
  * Resolves the optimistic-overlay drop against confirmed server watermarks: a
96
132
  * mutation's overlay is held until the sync stream echoes
@@ -107,7 +143,24 @@ interface BindMutatorsContext {
107
143
  */
108
144
  checkpoints?: CheckpointRegistry | false;
109
145
  /** The wired collections the optimistic bodies write against. */
110
- collections: Record<string, Collection<Row, string>>;
146
+ collections: TCollections;
147
+ /**
148
+ * Called when a mutator's server push is permanently rejected — the aggregate
149
+ * failure channel for writes, symmetric with
150
+ * {@link import("./define-collections").DefineCollectionsOptions.onWriteRejected}
151
+ * on the outbox path.
152
+ *
153
+ * Supplying it also makes a **fire-and-forget** call safe: a bound handle
154
+ * returns a `Transaction` whose `isPersisted` promise rejects on failure, so a
155
+ * caller that neither awaits it nor attaches a `.catch` leaves an unhandled
156
+ * rejection. With this hook set, `bindMutators` consumes that rejection itself
157
+ * after reporting it — a caller that DOES await still sees the rejection, so
158
+ * per-call handling is unaffected.
159
+ *
160
+ * A throwing listener is swallowed: reporting a failure must not manufacture a
161
+ * second one.
162
+ */
163
+ onWriteRejected?: (event: MutatorRejectedEvent) => void;
111
164
  /** Optional shard key the mutator's server push is routed to. */
112
165
  shardKey?: string;
113
166
  }
@@ -144,5 +197,39 @@ type BoundMutators<M extends AnyMutatorMap> = { [K in keyof M]: (args: ArgsOf<M[
144
197
  * a permanently-rejected predecessor can't wedge the chain: the next push simply
145
198
  * reclaims the same `watermark + 1` instead of leaving a hole the DO waits on.
146
199
  */
147
- declare const bindMutators: <M extends AnyMutatorMap>(client: LunoraClient, context: BindMutatorsContext, mutators: M) => BoundMutators<M>;
148
- export { BindMutatorsContext as B, ClientMutatorContext as C, DIRECT_TRANSACTION_METADATA_KEY as D, MutatorReference as M, BoundMutators as a, ClientMutatorDef as b, bindMutators as c, defineMutator as d };
200
+ declare const bindMutators: <M extends AnyMutatorMap, TCollections extends CollectionMap = CollectionMap>(client: LunoraClient, context: BindMutatorsContext<TCollections>, mutators: M) => BoundMutators<M>;
201
+ /** `defineMutator` + `bindMutators`, both bound to one project's collections map. */
202
+ interface BoundMutatorApi<TCollections extends CollectionMap> {
203
+ /** {@link bindMutators}, with `collections` pinned to `TCollections`. */
204
+ bindMutators: <M extends MutatorMapFor<TCollections>>(client: LunoraClient, context: BindMutatorsContext<TCollections>, mutators: M) => BoundMutators<M>;
205
+ /** {@link defineMutator}, with `context.collections` typed as `TCollections`. */
206
+ defineMutator: {
207
+ <TArgs = Record<string, unknown>>(definition: {
208
+ apply: (context: ClientMutatorContext<TCollections>, args: TArgs) => void;
209
+ serverRef: string;
210
+ }): ClientMutatorDef<TArgs, TCollections>;
211
+ <TArgs>(definition: {
212
+ apply: (context: ClientMutatorContext<TCollections>, args: TArgs) => void;
213
+ serverRef: MutatorReference<TArgs>;
214
+ }): ClientMutatorDef<TArgs, TCollections>;
215
+ };
216
+ }
217
+ /**
218
+ * Bind the mutator surface to **this project's** collections map, once.
219
+ *
220
+ * `Collection` is invariant in its row type, so a shared
221
+ * `Record&lt;string, Collection&lt;Row, string>>` could neither accept a generated
222
+ * collection without an `as never` cast nor hand a usable type back to `apply` —
223
+ * which is why optimistic bodies tended to ignore `context.collections` and close
224
+ * over module-scope collection variables instead. Declaring the map once here
225
+ * fixes both ends: `bindMutators` takes the concrete collections cast-free, and
226
+ * `apply` reads `collections.&lt;name>` at its real row type.
227
+ *
228
+ * Runtime-identical to the standalone {@link defineMutator} / {@link bindMutators}
229
+ * (this returns those very functions); only the types narrow.
230
+ * @example
231
+ * const { bindMutators, defineMutator } = initMutators&lt;{ nodes: typeof wholeOutlineCollection }>();
232
+ * const setText = defineMutator({ apply: ({ collections }, args) => collections.nodes.update(args.id, setter), serverRef: api.mutators.setText });
233
+ */
234
+ declare const initMutators: <TCollections extends CollectionMap>() => BoundMutatorApi<TCollections>;
235
+ export { BindMutatorsContext as B, ClientMutatorContext as C, DIRECT_TRANSACTION_METADATA_KEY as D, MutatorRejectedEvent as M, BoundMutatorApi as a, BoundMutators as b, ClientMutatorDef as c, CollectionMap as d, bindMutators as e, defineMutator as f, MutatorReference as g, initMutators as i };
@@ -1,6 +1,6 @@
1
1
  import { LunoraClient } from '@lunora/client';
2
2
  import { Collection, Transaction } from '@tanstack/db';
3
- import { C as CheckpointRegistry, R as Row } from "./collection-options.d-B_2IXvdU.mjs";
3
+ import { C as CheckpointRegistry } from "./collection-options.d-B_2IXvdU.js";
4
4
  /**
5
5
  * TanStack DB's "direct transaction" marker.
6
6
  *
@@ -19,10 +19,23 @@ import { C as CheckpointRegistry, R as Row } from "./collection-options.d-B_2IXv
19
19
  * reads that module off disk and fails if the upstream value ever changes.
20
20
  */
21
21
  declare const DIRECT_TRANSACTION_METADATA_KEY = "__tanstack_db_direct";
22
+ /**
23
+ * A map of wired collections, keyed by the name the optimistic bodies address them
24
+ * by.
25
+ *
26
+ * The row type is `any` on purpose. `Collection&lt;T, string>` is **invariant** in
27
+ * `T` (its methods both consume and produce rows), so a concrete
28
+ * `Collection&lt;Doc&lt;"nodes"> & Row>` is NOT assignable to `Collection&lt;Row>` — which
29
+ * is why the previous `Record&lt;string, Collection&lt;Row, string>>` forced an
30
+ * `as never` cast on every entry, and left `context.collections.&lt;name>` too
31
+ * untyped to be worth using. Projects recover the concrete types by binding them
32
+ * once through {@link initMutators}.
33
+ */
34
+ type CollectionMap = Record<string, Collection<any, string>>;
22
35
  /** The local store a client mutator's optimistic body writes against. */
23
- interface ClientMutatorContext {
36
+ interface ClientMutatorContext<TCollections extends CollectionMap = CollectionMap> {
24
37
  /** The wired collections, keyed by name — apply optimistic inserts/updates/deletes here. */
25
- collections: Record<string, Collection<Row, string>>;
38
+ collections: TCollections;
26
39
  }
27
40
  /**
28
41
  * A generated mutator reference (`api.mutators.sendMessage`), accepted by
@@ -41,14 +54,12 @@ interface MutatorReference<TArgs = unknown> {
41
54
  };
42
55
  readonly __lunoraRef: string;
43
56
  }
44
- /** Args type carried by a {@link MutatorReference}. */
45
- type ArgsOfReference<R> = R extends MutatorReference<infer A> ? A : never;
46
57
  /** A client-side custom mutator: an optimistic body plus the path of its authoritative server impl. */
47
- interface ClientMutatorDef<TArgs> {
58
+ interface ClientMutatorDef<TArgs, TCollections extends CollectionMap = CollectionMap> {
48
59
  /** Brand so codegen / `bindMutators` can recognize a mutator definition. */
49
60
  __lunoraClientMutator: true;
50
61
  /** The optimistic update applied to the local collections before the server confirms. */
51
- apply: (context: ClientMutatorContext, args: TArgs) => void;
62
+ apply: (context: ClientMutatorContext<TCollections>, args: TArgs) => void;
52
63
  /** The Lunora function path of the server-authoritative mutator (`defineMutator` on the server). */
53
64
  serverRef: string;
54
65
  }
@@ -75,22 +86,47 @@ interface ClientMutatorDef<TArgs> {
75
86
  * // restate the args yourself.
76
87
  * defineMutator<{ text: string }>({ apply, serverRef: "mutators:sendMessage" });
77
88
  * ```
89
+ *
90
+ * `context.collections` is typed by whatever map it is bound to — which for this
91
+ * standalone form is the widest one. Bind the concrete collections once with
92
+ * {@link initMutators} to get `collections.&lt;name>` typed inside `apply`.
78
93
  */
79
94
  declare const defineMutator: {
80
95
  <TArgs = Record<string, unknown>>(definition: {
81
96
  apply: (context: ClientMutatorContext, args: TArgs) => void;
82
97
  serverRef: string;
83
98
  }): ClientMutatorDef<TArgs>;
84
- <R extends MutatorReference<never>>(definition: {
85
- apply: (context: ClientMutatorContext, args: ArgsOfReference<R>) => void;
86
- serverRef: R;
87
- }): ClientMutatorDef<ArgsOfReference<R>>;
99
+ <TArgs>(definition: {
100
+ apply: (context: ClientMutatorContext, args: TArgs) => void;
101
+ serverRef: MutatorReference<TArgs>;
102
+ }): ClientMutatorDef<TArgs>;
88
103
  };
89
- type AnyMutatorMap = Record<string, ClientMutatorDef<any>>;
90
- /** Args type of a mutator definition. */
91
- type ArgsOf<M> = M extends ClientMutatorDef<infer A> ? A : never;
92
- /** Inputs `bindMutators` needs to run a mutator: the local store + how the overlay drops. */
93
- interface BindMutatorsContext {
104
+ /**
105
+ * A mutator map whose defs are bound to `TCollections`, arg types erased — the
106
+ * `TCollections`-pinned counterpart of {@link AnyMutatorMap}, and `any` for the
107
+ * same variance reason.
108
+ */
109
+ type MutatorMapFor<TCollections extends CollectionMap> = Record<string, ClientMutatorDef<any, TCollections>>;
110
+ type AnyMutatorMap = Record<string, ClientMutatorDef<any, any>>;
111
+ /** Args type of a mutator definition, whatever collections map it was bound to. */
112
+ type ArgsOf<M> = M extends ClientMutatorDef<infer A, infer _C> ? A : never;
113
+ /** A mutator write that was permanently rejected, passed to {@link BindMutatorsContext.onWriteRejected}. */
114
+ interface MutatorRejectedEvent {
115
+ /** The args the rejected call was made with. */
116
+ args: unknown;
117
+ /**
118
+ * The machine-readable reason when the server supplied one (e.g. `CONFLICT`,
119
+ * `FORBIDDEN`). Mirrors {@link import("./define-collections").WriteRejectedEvent.code}.
120
+ */
121
+ code?: string;
122
+ /** The error that rejected the write. */
123
+ error: Error;
124
+ /** The bound mutator's key in the map passed to `bindMutators`. */
125
+ mutator: string;
126
+ /** The `namespace:fn` path the push targeted. */
127
+ serverRef: string;
128
+ }
129
+ interface BindMutatorsContext<TCollections extends CollectionMap = CollectionMap> {
94
130
  /**
95
131
  * Resolves the optimistic-overlay drop against confirmed server watermarks: a
96
132
  * mutation's overlay is held until the sync stream echoes
@@ -107,7 +143,24 @@ interface BindMutatorsContext {
107
143
  */
108
144
  checkpoints?: CheckpointRegistry | false;
109
145
  /** The wired collections the optimistic bodies write against. */
110
- collections: Record<string, Collection<Row, string>>;
146
+ collections: TCollections;
147
+ /**
148
+ * Called when a mutator's server push is permanently rejected — the aggregate
149
+ * failure channel for writes, symmetric with
150
+ * {@link import("./define-collections").DefineCollectionsOptions.onWriteRejected}
151
+ * on the outbox path.
152
+ *
153
+ * Supplying it also makes a **fire-and-forget** call safe: a bound handle
154
+ * returns a `Transaction` whose `isPersisted` promise rejects on failure, so a
155
+ * caller that neither awaits it nor attaches a `.catch` leaves an unhandled
156
+ * rejection. With this hook set, `bindMutators` consumes that rejection itself
157
+ * after reporting it — a caller that DOES await still sees the rejection, so
158
+ * per-call handling is unaffected.
159
+ *
160
+ * A throwing listener is swallowed: reporting a failure must not manufacture a
161
+ * second one.
162
+ */
163
+ onWriteRejected?: (event: MutatorRejectedEvent) => void;
111
164
  /** Optional shard key the mutator's server push is routed to. */
112
165
  shardKey?: string;
113
166
  }
@@ -144,5 +197,39 @@ type BoundMutators<M extends AnyMutatorMap> = { [K in keyof M]: (args: ArgsOf<M[
144
197
  * a permanently-rejected predecessor can't wedge the chain: the next push simply
145
198
  * reclaims the same `watermark + 1` instead of leaving a hole the DO waits on.
146
199
  */
147
- declare const bindMutators: <M extends AnyMutatorMap>(client: LunoraClient, context: BindMutatorsContext, mutators: M) => BoundMutators<M>;
148
- export { BindMutatorsContext as B, ClientMutatorContext as C, DIRECT_TRANSACTION_METADATA_KEY as D, MutatorReference as M, BoundMutators as a, ClientMutatorDef as b, bindMutators as c, defineMutator as d };
200
+ declare const bindMutators: <M extends AnyMutatorMap, TCollections extends CollectionMap = CollectionMap>(client: LunoraClient, context: BindMutatorsContext<TCollections>, mutators: M) => BoundMutators<M>;
201
+ /** `defineMutator` + `bindMutators`, both bound to one project's collections map. */
202
+ interface BoundMutatorApi<TCollections extends CollectionMap> {
203
+ /** {@link bindMutators}, with `collections` pinned to `TCollections`. */
204
+ bindMutators: <M extends MutatorMapFor<TCollections>>(client: LunoraClient, context: BindMutatorsContext<TCollections>, mutators: M) => BoundMutators<M>;
205
+ /** {@link defineMutator}, with `context.collections` typed as `TCollections`. */
206
+ defineMutator: {
207
+ <TArgs = Record<string, unknown>>(definition: {
208
+ apply: (context: ClientMutatorContext<TCollections>, args: TArgs) => void;
209
+ serverRef: string;
210
+ }): ClientMutatorDef<TArgs, TCollections>;
211
+ <TArgs>(definition: {
212
+ apply: (context: ClientMutatorContext<TCollections>, args: TArgs) => void;
213
+ serverRef: MutatorReference<TArgs>;
214
+ }): ClientMutatorDef<TArgs, TCollections>;
215
+ };
216
+ }
217
+ /**
218
+ * Bind the mutator surface to **this project's** collections map, once.
219
+ *
220
+ * `Collection` is invariant in its row type, so a shared
221
+ * `Record&lt;string, Collection&lt;Row, string>>` could neither accept a generated
222
+ * collection without an `as never` cast nor hand a usable type back to `apply` —
223
+ * which is why optimistic bodies tended to ignore `context.collections` and close
224
+ * over module-scope collection variables instead. Declaring the map once here
225
+ * fixes both ends: `bindMutators` takes the concrete collections cast-free, and
226
+ * `apply` reads `collections.&lt;name>` at its real row type.
227
+ *
228
+ * Runtime-identical to the standalone {@link defineMutator} / {@link bindMutators}
229
+ * (this returns those very functions); only the types narrow.
230
+ * @example
231
+ * const { bindMutators, defineMutator } = initMutators&lt;{ nodes: typeof wholeOutlineCollection }>();
232
+ * const setText = defineMutator({ apply: ({ collections }, args) => collections.nodes.update(args.id, setter), serverRef: api.mutators.setText });
233
+ */
234
+ declare const initMutators: <TCollections extends CollectionMap>() => BoundMutatorApi<TCollections>;
235
+ export { BindMutatorsContext as B, ClientMutatorContext as C, DIRECT_TRANSACTION_METADATA_KEY as D, MutatorRejectedEvent as M, BoundMutatorApi as a, BoundMutators as b, ClientMutatorDef as c, CollectionMap as d, bindMutators as e, defineMutator as f, MutatorReference as g, initMutators as i };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lunora/db",
3
- "version": "1.0.0-alpha.31",
3
+ "version": "1.0.0-alpha.33",
4
4
  "description": "TanStack DB binding: typed, live-synced collections and a durable offline outbox over the Lunora client",
5
5
  "keywords": [
6
6
  "cloudflare",
@@ -54,8 +54,8 @@
54
54
  "access": "public"
55
55
  },
56
56
  "dependencies": {
57
- "@lunora/client": "1.0.0-alpha.31",
58
- "@lunora/errors": "1.0.0-alpha.8"
57
+ "@lunora/client": "1.0.0-alpha.32",
58
+ "@lunora/errors": "1.0.0-alpha.9"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "@tanstack/db": "^0.6.0",
@@ -1 +0,0 @@
1
- import{LunoraError as l}from"@lunora/errors";import{createTransaction as y}from"@tanstack/db";import{getShardCheckpoints as R,hasCheckpointsAttached as M}from"./CHECKPOINT_FALLBACK_MS-vBJLOBjX.mjs";import{runOutboxMutation as _}from"./OUTBOX_MUTATION_FN_NAME-CebgkYw2.mjs";const w="__tanstack_db_direct",A="defineMutator: `serverRef` must be a generated mutator reference (api.mutators.*) or a 'namespace:fn' string",g=e=>{const t=typeof e=="string"?e:e?.__lunoraRef;if(typeof t!="string"||t.length===0)throw new l("INTERNAL",A);return t},E=e=>({__lunoraClientMutator:!0,apply:e.apply,serverRef:g(e.serverRef)}),I=(e,t,m)=>{let c=0;const p=()=>(c=Math.max(c,e.confirmedMutationWatermark(t.shardKey))+1,c);let u=Promise.resolve();const d=(r,o)=>{const a=u.then(async()=>{for(let s=0;;s+=1){const n=p(),{applied:i}=await e.callMutator(r,o,{clientSeq:n,shardKey:t.shardKey});if(i)return n;if(s>=32)throw new l("INTERNAL",`lunora: custom mutator "${r}" could not claim a fresh client sequence after ${String(32)} attempts`)}});return u=a.then(()=>{},()=>{}),a},h=()=>{if(t.checkpoints===!1)return;if(t.checkpoints)return t.checkpoints;const r=R(e,t.shardKey);return M(r)?r:void 0},f={};for(const[r,o]of Object.entries(m))f[r]=a=>{const s=y({autoCommit:!0,metadata:{[w]:!0,serverRef:o.serverRef},mutationFn:async()=>{let n=0;await _(async()=>{n=await d(o.serverRef,a)});const i=h();i&&(i.acknowledge({mutationId:n}),await i.awaitMutationId(n))}});return s.mutate(()=>{o.apply({collections:t.collections},a)}),s};return f};export{w as DIRECT_TRANSACTION_METADATA_KEY,I as bindMutators,E as defineMutator};