@lunora/db 1.0.0-alpha.3 → 1.0.0-alpha.5
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/README.md +12 -0
- package/dist/collections/index.d.mts +5 -0
- package/dist/collections/index.d.ts +5 -0
- package/dist/collections/index.mjs +2 -0
- package/dist/index.d.mts +7 -141
- package/dist/index.d.ts +7 -141
- package/dist/index.mjs +4 -2
- package/dist/mutators/index.d.mts +5 -0
- package/dist/mutators/index.d.ts +5 -0
- package/dist/mutators/index.mjs +1 -0
- package/dist/packem_shared/{createOptimisticOnlineDetector-CRulWqZ7.mjs → OUTBOX_MUTATION_FN_NAME-3cxtwP39.mjs} +34 -1
- package/dist/packem_shared/bindMutators-DWGJhyt3.mjs +66 -0
- package/dist/packem_shared/collection-options.d-B3YaP49K.d.mts +196 -0
- package/dist/packem_shared/collection-options.d-B3YaP49K.d.ts +196 -0
- package/dist/packem_shared/createCheckpointRegistry-CR_q270X.mjs +125 -0
- package/dist/packem_shared/define-collections.d-DKc3vPUv.d.ts +85 -0
- package/dist/packem_shared/define-collections.d-L5DVcLnj.d.mts +85 -0
- package/dist/packem_shared/define-mutators.d-Bqd-o1Zp.d.mts +80 -0
- package/dist/packem_shared/define-mutators.d-BqsA6_pd.d.ts +80 -0
- package/dist/packem_shared/defineCollections-it3wN-49.mjs +74 -0
- package/package.json +10 -2
- package/dist/packem_shared/defineCollections-BAmslSrF.mjs +0 -109
package/README.md
CHANGED
|
@@ -85,6 +85,18 @@ export const createCollections = (client: LunoraClient) =>
|
|
|
85
85
|
|
|
86
86
|
> `vis generate lunora-collections` scaffolds this from your `schema.ts` + functions.
|
|
87
87
|
|
|
88
|
+
### Local-first sync engine
|
|
89
|
+
|
|
90
|
+
Beyond whole-table collections, the `@lunora/db/collections` and
|
|
91
|
+
`@lunora/db/mutators` subpaths expose the local-first sync engine:
|
|
92
|
+
`lunoraCollectionOptions({ shape })` syncs a **partial replication shape** (only
|
|
93
|
+
the rows a client needs, scoped by a server-resolved predicate) over the poke
|
|
94
|
+
diff protocol, and `defineMutator` + `bindMutators` run **optimistic custom
|
|
95
|
+
mutators** (a local body first, a server-authoritative impl second, rebased on
|
|
96
|
+
every sync tick). The framework adapters add a `useMutator` / `createMutator` /
|
|
97
|
+
`mutator` hook over a bound handle. See the
|
|
98
|
+
**[local-first guide](https://lunora.sh/docs/concepts/local-first)**.
|
|
99
|
+
|
|
88
100
|
> This README covers the basics. For the full API, options, and guides, see the **[documentation](https://lunora.sh/docs/addons/db)**.
|
|
89
101
|
|
|
90
102
|
## Related
|
|
@@ -0,0 +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-B3YaP49K.mjs";
|
|
2
|
+
export { type C as CollectionDef, type I as InsertBinding, type L as LunoraDb, d as defineCollections } from "../packem_shared/define-collections.d-L5DVcLnj.mjs";
|
|
3
|
+
import '@lunora/client';
|
|
4
|
+
import '@tanstack/db';
|
|
5
|
+
import '@tanstack/offline-transactions';
|
|
@@ -0,0 +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-B3YaP49K.js";
|
|
2
|
+
export { type C as CollectionDef, type I as InsertBinding, type L as LunoraDb, d as defineCollections } from "../packem_shared/define-collections.d-DKc3vPUv.js";
|
|
3
|
+
import '@lunora/client';
|
|
4
|
+
import '@tanstack/db';
|
|
5
|
+
import '@tanstack/offline-transactions';
|
package/dist/index.d.mts
CHANGED
|
@@ -1,142 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
};
|
|
8
|
-
/** The subset of a TanStack DB sync write channel that {@link makeDiffEmit} drives. */
|
|
9
|
-
interface SyncWriter<T extends object> {
|
|
10
|
-
begin: () => void;
|
|
11
|
-
commit: () => void;
|
|
12
|
-
write: (message: {
|
|
13
|
-
type: "insert" | "update";
|
|
14
|
-
value: T;
|
|
15
|
-
} | {
|
|
16
|
-
key: string;
|
|
17
|
-
type: "delete";
|
|
18
|
-
}) => void;
|
|
19
|
-
}
|
|
20
|
-
/** Index a row list into a keyed map. */
|
|
21
|
-
declare const toMap: <T extends object>(rows: ReadonlyArray<T>, getKey: (row: T) => string) => Map<string, T>;
|
|
22
|
-
/**
|
|
23
|
-
* Build an `emit(next)` that diffs a desired keyed snapshot into a collection's
|
|
24
|
-
* sync channel — only changed rows are written, so a reconnect snapshot or a
|
|
25
|
-
* scope change never churns the synced view out from under a pending optimistic
|
|
26
|
-
* row. The last-synced base is tracked in `synced`.
|
|
27
|
-
*
|
|
28
|
-
* Change detection compares rows by `JSON.stringify`, which is key-order
|
|
29
|
-
* sensitive — safe here because `synced` only ever holds server snapshots, whose
|
|
30
|
-
* column order is stable across reconnects (same query projection). A sync source
|
|
31
|
-
* with unstable key ordering would need a structural compare instead.
|
|
32
|
-
*/
|
|
33
|
-
declare const makeDiffEmit: <T extends object>(synced: Map<string, T>, writer: SyncWriter<T>) => (next: Map<string, T>) => void;
|
|
34
|
-
/**
|
|
35
|
-
* Run a Lunora mutation under the outbox's retry policy.
|
|
36
|
-
*
|
|
37
|
-
* The retryable/permanent split keys on whether the failure carries a server
|
|
38
|
-
* application error `code` (set by `@lunora/client`'s rpc when the server returns
|
|
39
|
-
* a `{ error: { code, … } }` envelope — validation, conflict, etc.). A coded
|
|
40
|
-
* error is a definite verdict: surface it as a `NonRetriableError` so the executor
|
|
41
|
-
* stops and TanStack DB rolls the optimistic insert back. Everything without a
|
|
42
|
-
* code is transient — a `fetch` network failure (`TypeError`) or an HTTP/infra
|
|
43
|
-
* blip the rpc surfaces as a code-less `Error` (a 5xx gateway page, a non-JSON
|
|
44
|
-
* body) — so it's rethrown as-is and the durable outbox replays it. Keying on
|
|
45
|
-
* `error instanceof TypeError` alone would wrongly drop the latter.
|
|
46
|
-
*/
|
|
47
|
-
declare const runOutboxMutation: (mutate: () => Promise<unknown>) => Promise<void>;
|
|
48
|
-
/**
|
|
49
|
-
* An "always attempt" online detector. We deliberately don't trust
|
|
50
|
-
* `navigator.onLine`: some environments (and Playwright's `setOffline` under
|
|
51
|
-
* Firefox) leave it stuck, which would freeze the outbox. Instead the executor
|
|
52
|
-
* always tries the send and {@link runOutboxMutation}'s transient-error retry
|
|
53
|
-
* handles real offline; the periodic tick nudges the executor to drain the outbox
|
|
54
|
-
* so a queued write replays promptly once connectivity returns.
|
|
55
|
-
*
|
|
56
|
-
* `isOnline` is therefore intentionally always `true` — it gates the executor's
|
|
57
|
-
* attempts, not a UI signal. A consumer that wants to show real connectivity
|
|
58
|
-
* should read `navigator.onLine` itself, separately from this detector.
|
|
59
|
-
*/
|
|
60
|
-
declare const createOptimisticOnlineDetector: () => OnlineDetector;
|
|
61
|
-
/** Element type of an array (the row type a `list` query returns). */
|
|
62
|
-
type Element<T> = T extends ReadonlyArray<infer E> ? E : never;
|
|
63
|
-
/** `true` for the `any` type, `false` otherwise. */
|
|
64
|
-
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
65
|
-
/**
|
|
66
|
-
* The row type a `list` query syncs. For `TList = any` (the heterogeneous-map
|
|
67
|
-
* constraint) it resolves to the permissive {@link Row}, not `never` — otherwise
|
|
68
|
-
* the constraint would force every `optimistic` to return `never`. For a concrete
|
|
69
|
-
* `FunctionReference` it's the element type of the query's array return.
|
|
70
|
-
*/
|
|
71
|
-
type RowOfList<TList> = IsAny<TList> extends true ? Row : TList extends FunctionReference<infer _K, infer _A, infer R> ? Element<R> & Row : never;
|
|
72
|
-
/** Maps a write through the durable outbox: optimistic insert + a retried mutation. */
|
|
73
|
-
interface InsertBinding<TRow extends Row, TInput> {
|
|
74
|
-
/** The Lunora mutation that persists the row. */
|
|
75
|
-
mutation: FunctionReference;
|
|
76
|
-
/** Build the optimistic row to insert from the action input + the generated client id. */
|
|
77
|
-
optimistic: (input: TInput, id: string) => TRow;
|
|
78
|
-
/** Build the mutation args from the persisted optimistic row (forward `_id` as the `clientId`). */
|
|
79
|
-
toArgs: (row: TRow) => Record<string, unknown>;
|
|
80
|
-
}
|
|
81
|
-
/** Declarative binding of a Lunora table to a live collection (+ optional write action). */
|
|
82
|
-
interface CollectionDef<TList extends FunctionReference, TInput = never> {
|
|
83
|
-
/** Row key extractor — defaults to `row._id`. */
|
|
84
|
-
getKey?: (row: RowOfList<TList>) => string;
|
|
85
|
-
/** Optional write binding — present iff this collection is written through the outbox. */
|
|
86
|
-
insert?: InsertBinding<RowOfList<TList>, TInput>;
|
|
87
|
-
/** The Lunora query that lists the rows (the sync source). */
|
|
88
|
-
list: TList;
|
|
89
|
-
/**
|
|
90
|
-
* Notified when the underlying `list` subscription errors (e.g. the server
|
|
91
|
-
* rejects it). Without this the error would be swallowed and the collection
|
|
92
|
-
* could hang in `loading`; the binding always moves the collection out of
|
|
93
|
-
* `loading` on error, and forwards the error here if supplied.
|
|
94
|
-
*/
|
|
95
|
-
onError?: (error: SubscriptionError) => void;
|
|
96
|
-
/** A field that scopes the list (e.g. a shard key); makes the collection re-pointable via `scope`. */
|
|
97
|
-
scopeBy?: string;
|
|
98
|
-
}
|
|
99
|
-
type AnyDef = CollectionDef<any, any>;
|
|
100
|
-
/**
|
|
101
|
-
* The public row type a collection exposes — the element type of its `list`
|
|
102
|
-
* query's return, with no `& Row`: a `Collection<T>` is invariant in `T`, so the
|
|
103
|
-
* exposed type must be exactly the document type, not a subtype.
|
|
104
|
-
*/
|
|
105
|
-
type RowOf<C extends AnyDef> = C["list"] extends FunctionReference<infer _K, infer _A, infer R> ? Element<R> : never;
|
|
106
|
-
/** The action input type, inferred structurally from the def's optimistic insert. */
|
|
107
|
-
type InputOf<C> = C extends {
|
|
108
|
-
insert: {
|
|
109
|
-
optimistic: (input: infer I, id: string) => unknown;
|
|
110
|
-
};
|
|
111
|
-
} ? I : never;
|
|
112
|
-
/** The wired data layer `defineCollections` returns. */
|
|
113
|
-
interface LunoraDb<D extends Record<string, AnyDef>> {
|
|
114
|
-
/** Optimistic, durable, retried write actions — present for `insert` collections. */
|
|
115
|
-
actions: { [K in keyof D]: D[K] extends {
|
|
116
|
-
insert: object;
|
|
117
|
-
} ? (input: InputOf<D[K]>) => {
|
|
118
|
-
id: string;
|
|
119
|
-
transaction: Transaction;
|
|
120
|
-
} : never };
|
|
121
|
-
/** The live, synced collections — feed these to `useLiveQuery`. */
|
|
122
|
-
collections: { [K in keyof D]: Collection<RowOf<D[K]>, string> };
|
|
123
|
-
/** The shared offline executor (the outbox). */
|
|
124
|
-
executor: OfflineExecutor;
|
|
125
|
-
/** Re-point a `scopeBy` collection's subscription (omit `args` to detach) — present for scoped collections. */
|
|
126
|
-
scope: { [K in keyof D]: D[K] extends {
|
|
127
|
-
scopeBy: string;
|
|
128
|
-
} ? (args?: Record<string, unknown>) => void : never };
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Wire a set of Lunora tables into a TanStack DB data layer in one declaration:
|
|
132
|
-
* each entry becomes a live, auto-indexed collection synced from its `list` query,
|
|
133
|
-
* and `insert` entries get an optimistic write action backed by the
|
|
134
|
-
* offline-transactions outbox (durable, retried, client-id-keyed). Scoped
|
|
135
|
-
* (`scopeBy`) collections are re-pointable for sharded queries.
|
|
136
|
-
*
|
|
137
|
-
* This is the hand-written form; `@lunora/codegen` can emit a fully-typed call to
|
|
138
|
-
* it from `schema.ts`, so an app writes nothing.
|
|
139
|
-
*/
|
|
140
|
-
declare const defineCollections: <D extends Record<string, AnyDef>>(client: LunoraClient, defs: D) => LunoraDb<D>;
|
|
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-B3YaP49K.mjs";
|
|
2
|
+
export { type C as CollectionDef, type I as InsertBinding, type L as LunoraDb, d as defineCollections } from "./packem_shared/define-collections.d-L5DVcLnj.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-Bqd-o1Zp.mjs";
|
|
4
|
+
import '@lunora/client';
|
|
5
|
+
import '@tanstack/db';
|
|
6
|
+
import '@tanstack/offline-transactions';
|
|
141
7
|
declare const VERSION = "0.0.0";
|
|
142
|
-
export {
|
|
8
|
+
export { VERSION };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,142 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
};
|
|
8
|
-
/** The subset of a TanStack DB sync write channel that {@link makeDiffEmit} drives. */
|
|
9
|
-
interface SyncWriter<T extends object> {
|
|
10
|
-
begin: () => void;
|
|
11
|
-
commit: () => void;
|
|
12
|
-
write: (message: {
|
|
13
|
-
type: "insert" | "update";
|
|
14
|
-
value: T;
|
|
15
|
-
} | {
|
|
16
|
-
key: string;
|
|
17
|
-
type: "delete";
|
|
18
|
-
}) => void;
|
|
19
|
-
}
|
|
20
|
-
/** Index a row list into a keyed map. */
|
|
21
|
-
declare const toMap: <T extends object>(rows: ReadonlyArray<T>, getKey: (row: T) => string) => Map<string, T>;
|
|
22
|
-
/**
|
|
23
|
-
* Build an `emit(next)` that diffs a desired keyed snapshot into a collection's
|
|
24
|
-
* sync channel — only changed rows are written, so a reconnect snapshot or a
|
|
25
|
-
* scope change never churns the synced view out from under a pending optimistic
|
|
26
|
-
* row. The last-synced base is tracked in `synced`.
|
|
27
|
-
*
|
|
28
|
-
* Change detection compares rows by `JSON.stringify`, which is key-order
|
|
29
|
-
* sensitive — safe here because `synced` only ever holds server snapshots, whose
|
|
30
|
-
* column order is stable across reconnects (same query projection). A sync source
|
|
31
|
-
* with unstable key ordering would need a structural compare instead.
|
|
32
|
-
*/
|
|
33
|
-
declare const makeDiffEmit: <T extends object>(synced: Map<string, T>, writer: SyncWriter<T>) => (next: Map<string, T>) => void;
|
|
34
|
-
/**
|
|
35
|
-
* Run a Lunora mutation under the outbox's retry policy.
|
|
36
|
-
*
|
|
37
|
-
* The retryable/permanent split keys on whether the failure carries a server
|
|
38
|
-
* application error `code` (set by `@lunora/client`'s rpc when the server returns
|
|
39
|
-
* a `{ error: { code, … } }` envelope — validation, conflict, etc.). A coded
|
|
40
|
-
* error is a definite verdict: surface it as a `NonRetriableError` so the executor
|
|
41
|
-
* stops and TanStack DB rolls the optimistic insert back. Everything without a
|
|
42
|
-
* code is transient — a `fetch` network failure (`TypeError`) or an HTTP/infra
|
|
43
|
-
* blip the rpc surfaces as a code-less `Error` (a 5xx gateway page, a non-JSON
|
|
44
|
-
* body) — so it's rethrown as-is and the durable outbox replays it. Keying on
|
|
45
|
-
* `error instanceof TypeError` alone would wrongly drop the latter.
|
|
46
|
-
*/
|
|
47
|
-
declare const runOutboxMutation: (mutate: () => Promise<unknown>) => Promise<void>;
|
|
48
|
-
/**
|
|
49
|
-
* An "always attempt" online detector. We deliberately don't trust
|
|
50
|
-
* `navigator.onLine`: some environments (and Playwright's `setOffline` under
|
|
51
|
-
* Firefox) leave it stuck, which would freeze the outbox. Instead the executor
|
|
52
|
-
* always tries the send and {@link runOutboxMutation}'s transient-error retry
|
|
53
|
-
* handles real offline; the periodic tick nudges the executor to drain the outbox
|
|
54
|
-
* so a queued write replays promptly once connectivity returns.
|
|
55
|
-
*
|
|
56
|
-
* `isOnline` is therefore intentionally always `true` — it gates the executor's
|
|
57
|
-
* attempts, not a UI signal. A consumer that wants to show real connectivity
|
|
58
|
-
* should read `navigator.onLine` itself, separately from this detector.
|
|
59
|
-
*/
|
|
60
|
-
declare const createOptimisticOnlineDetector: () => OnlineDetector;
|
|
61
|
-
/** Element type of an array (the row type a `list` query returns). */
|
|
62
|
-
type Element<T> = T extends ReadonlyArray<infer E> ? E : never;
|
|
63
|
-
/** `true` for the `any` type, `false` otherwise. */
|
|
64
|
-
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
65
|
-
/**
|
|
66
|
-
* The row type a `list` query syncs. For `TList = any` (the heterogeneous-map
|
|
67
|
-
* constraint) it resolves to the permissive {@link Row}, not `never` — otherwise
|
|
68
|
-
* the constraint would force every `optimistic` to return `never`. For a concrete
|
|
69
|
-
* `FunctionReference` it's the element type of the query's array return.
|
|
70
|
-
*/
|
|
71
|
-
type RowOfList<TList> = IsAny<TList> extends true ? Row : TList extends FunctionReference<infer _K, infer _A, infer R> ? Element<R> & Row : never;
|
|
72
|
-
/** Maps a write through the durable outbox: optimistic insert + a retried mutation. */
|
|
73
|
-
interface InsertBinding<TRow extends Row, TInput> {
|
|
74
|
-
/** The Lunora mutation that persists the row. */
|
|
75
|
-
mutation: FunctionReference;
|
|
76
|
-
/** Build the optimistic row to insert from the action input + the generated client id. */
|
|
77
|
-
optimistic: (input: TInput, id: string) => TRow;
|
|
78
|
-
/** Build the mutation args from the persisted optimistic row (forward `_id` as the `clientId`). */
|
|
79
|
-
toArgs: (row: TRow) => Record<string, unknown>;
|
|
80
|
-
}
|
|
81
|
-
/** Declarative binding of a Lunora table to a live collection (+ optional write action). */
|
|
82
|
-
interface CollectionDef<TList extends FunctionReference, TInput = never> {
|
|
83
|
-
/** Row key extractor — defaults to `row._id`. */
|
|
84
|
-
getKey?: (row: RowOfList<TList>) => string;
|
|
85
|
-
/** Optional write binding — present iff this collection is written through the outbox. */
|
|
86
|
-
insert?: InsertBinding<RowOfList<TList>, TInput>;
|
|
87
|
-
/** The Lunora query that lists the rows (the sync source). */
|
|
88
|
-
list: TList;
|
|
89
|
-
/**
|
|
90
|
-
* Notified when the underlying `list` subscription errors (e.g. the server
|
|
91
|
-
* rejects it). Without this the error would be swallowed and the collection
|
|
92
|
-
* could hang in `loading`; the binding always moves the collection out of
|
|
93
|
-
* `loading` on error, and forwards the error here if supplied.
|
|
94
|
-
*/
|
|
95
|
-
onError?: (error: SubscriptionError) => void;
|
|
96
|
-
/** A field that scopes the list (e.g. a shard key); makes the collection re-pointable via `scope`. */
|
|
97
|
-
scopeBy?: string;
|
|
98
|
-
}
|
|
99
|
-
type AnyDef = CollectionDef<any, any>;
|
|
100
|
-
/**
|
|
101
|
-
* The public row type a collection exposes — the element type of its `list`
|
|
102
|
-
* query's return, with no `& Row`: a `Collection<T>` is invariant in `T`, so the
|
|
103
|
-
* exposed type must be exactly the document type, not a subtype.
|
|
104
|
-
*/
|
|
105
|
-
type RowOf<C extends AnyDef> = C["list"] extends FunctionReference<infer _K, infer _A, infer R> ? Element<R> : never;
|
|
106
|
-
/** The action input type, inferred structurally from the def's optimistic insert. */
|
|
107
|
-
type InputOf<C> = C extends {
|
|
108
|
-
insert: {
|
|
109
|
-
optimistic: (input: infer I, id: string) => unknown;
|
|
110
|
-
};
|
|
111
|
-
} ? I : never;
|
|
112
|
-
/** The wired data layer `defineCollections` returns. */
|
|
113
|
-
interface LunoraDb<D extends Record<string, AnyDef>> {
|
|
114
|
-
/** Optimistic, durable, retried write actions — present for `insert` collections. */
|
|
115
|
-
actions: { [K in keyof D]: D[K] extends {
|
|
116
|
-
insert: object;
|
|
117
|
-
} ? (input: InputOf<D[K]>) => {
|
|
118
|
-
id: string;
|
|
119
|
-
transaction: Transaction;
|
|
120
|
-
} : never };
|
|
121
|
-
/** The live, synced collections — feed these to `useLiveQuery`. */
|
|
122
|
-
collections: { [K in keyof D]: Collection<RowOf<D[K]>, string> };
|
|
123
|
-
/** The shared offline executor (the outbox). */
|
|
124
|
-
executor: OfflineExecutor;
|
|
125
|
-
/** Re-point a `scopeBy` collection's subscription (omit `args` to detach) — present for scoped collections. */
|
|
126
|
-
scope: { [K in keyof D]: D[K] extends {
|
|
127
|
-
scopeBy: string;
|
|
128
|
-
} ? (args?: Record<string, unknown>) => void : never };
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* Wire a set of Lunora tables into a TanStack DB data layer in one declaration:
|
|
132
|
-
* each entry becomes a live, auto-indexed collection synced from its `list` query,
|
|
133
|
-
* and `insert` entries get an optimistic write action backed by the
|
|
134
|
-
* offline-transactions outbox (durable, retried, client-id-keyed). Scoped
|
|
135
|
-
* (`scopeBy`) collections are re-pointable for sharded queries.
|
|
136
|
-
*
|
|
137
|
-
* This is the hand-written form; `@lunora/codegen` can emit a fully-typed call to
|
|
138
|
-
* it from `schema.ts`, so an app writes nothing.
|
|
139
|
-
*/
|
|
140
|
-
declare const defineCollections: <D extends Record<string, AnyDef>>(client: LunoraClient, defs: D) => LunoraDb<D>;
|
|
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-B3YaP49K.js";
|
|
2
|
+
export { type C as CollectionDef, type I as InsertBinding, type L as LunoraDb, d as defineCollections } from "./packem_shared/define-collections.d-DKc3vPUv.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-BqsA6_pd.js";
|
|
4
|
+
import '@lunora/client';
|
|
5
|
+
import '@tanstack/db';
|
|
6
|
+
import '@tanstack/offline-transactions';
|
|
141
7
|
declare const VERSION = "0.0.0";
|
|
142
|
-
export {
|
|
8
|
+
export { VERSION };
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export {
|
|
1
|
+
export { createCheckpointRegistry, lunoraCollectionOptions } from './packem_shared/createCheckpointRegistry-CR_q270X.mjs';
|
|
2
|
+
export { defineCollections } from './packem_shared/defineCollections-it3wN-49.mjs';
|
|
3
|
+
export { bindMutators, defineMutator } from './packem_shared/bindMutators-DWGJhyt3.mjs';
|
|
4
|
+
export { OUTBOX_MUTATION_FN_NAME, createExecutorOutboxSink, createOptimisticOnlineDetector, makeDiffEmit, runOutboxMutation, toMap } from './packem_shared/OUTBOX_MUTATION_FN_NAME-3cxtwP39.mjs';
|
|
3
5
|
|
|
4
6
|
const VERSION = "0.0.0";
|
|
5
7
|
|
|
@@ -0,0 +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-Bqd-o1Zp.mjs";
|
|
2
|
+
import '@lunora/client';
|
|
3
|
+
import '@tanstack/db';
|
|
4
|
+
import "../packem_shared/collection-options.d-B3YaP49K.mjs";
|
|
5
|
+
import '@tanstack/offline-transactions';
|
|
@@ -0,0 +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-BqsA6_pd.js";
|
|
2
|
+
import '@lunora/client';
|
|
3
|
+
import '@tanstack/db';
|
|
4
|
+
import "../packem_shared/collection-options.d-B3YaP49K.js";
|
|
5
|
+
import '@tanstack/offline-transactions';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { bindMutators, defineMutator } from '../packem_shared/bindMutators-DWGJhyt3.mjs';
|
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
import { NonRetriableError } from '@tanstack/offline-transactions';
|
|
2
2
|
|
|
3
3
|
const OUTBOX_DRAIN_INTERVAL_MS = 1e3;
|
|
4
|
+
const OUTBOX_MUTATION_FN_NAME = "__lunora_outbox__";
|
|
5
|
+
const createExecutorOutboxSink = (executor, options = {}) => {
|
|
6
|
+
const maxItems = options.maxItems ?? 1e3;
|
|
7
|
+
const mutationFunctionName = options.mutationFnName ?? OUTBOX_MUTATION_FN_NAME;
|
|
8
|
+
return {
|
|
9
|
+
enqueue(mutation) {
|
|
10
|
+
if (executor.getPendingCount() >= maxItems) {
|
|
11
|
+
const error = new Error("offline outbox is full");
|
|
12
|
+
error.code = "OFFLINE_QUEUE_OVERFLOW";
|
|
13
|
+
return Promise.reject(error);
|
|
14
|
+
}
|
|
15
|
+
const metadata = {
|
|
16
|
+
args: mutation.args,
|
|
17
|
+
clientId: mutation.clientId,
|
|
18
|
+
functionPath: mutation.functionPath,
|
|
19
|
+
// Persist the stable replay key so a committed-but-unacked retry
|
|
20
|
+
// resends the same `x-lunora-mutation-id` and the server dedups it.
|
|
21
|
+
idempotencyKey: mutation.idempotencyKey,
|
|
22
|
+
identity: mutation.identity,
|
|
23
|
+
mutationId: mutation.mutationId,
|
|
24
|
+
shardKey: mutation.shardKey
|
|
25
|
+
};
|
|
26
|
+
const transaction = executor.createOfflineTransaction({
|
|
27
|
+
autoCommit: true,
|
|
28
|
+
idempotencyKey: mutation.idempotencyKey,
|
|
29
|
+
metadata,
|
|
30
|
+
mutationFnName: mutationFunctionName
|
|
31
|
+
});
|
|
32
|
+
transaction.mutate(() => void 0);
|
|
33
|
+
return Promise.resolve();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
};
|
|
4
37
|
const toMap = (rows, getKey) => {
|
|
5
38
|
const map = /* @__PURE__ */ new Map();
|
|
6
39
|
for (const row of rows) {
|
|
@@ -62,4 +95,4 @@ const createOptimisticOnlineDetector = () => {
|
|
|
62
95
|
};
|
|
63
96
|
};
|
|
64
97
|
|
|
65
|
-
export { createOptimisticOnlineDetector, makeDiffEmit, runOutboxMutation, toMap };
|
|
98
|
+
export { OUTBOX_MUTATION_FN_NAME, createExecutorOutboxSink, createOptimisticOnlineDetector, makeDiffEmit, runOutboxMutation, toMap };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { createTransaction } from '@tanstack/db';
|
|
2
|
+
import { runOutboxMutation } from './OUTBOX_MUTATION_FN_NAME-3cxtwP39.mjs';
|
|
3
|
+
|
|
4
|
+
const defineMutator = (definition) => {
|
|
5
|
+
return {
|
|
6
|
+
__lunoraClientMutator: true,
|
|
7
|
+
apply: definition.apply,
|
|
8
|
+
serverRef: definition.serverRef
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
const bindMutators = (client, context, mutators) => {
|
|
12
|
+
const maxReissues = 32;
|
|
13
|
+
let counter = 0;
|
|
14
|
+
const nextClientSeq = () => {
|
|
15
|
+
counter = Math.max(counter, client.confirmedMutationWatermark(context.shardKey)) + 1;
|
|
16
|
+
return counter;
|
|
17
|
+
};
|
|
18
|
+
let pushChain = Promise.resolve();
|
|
19
|
+
const pushSerialized = (serverRef, args) => {
|
|
20
|
+
const run = pushChain.then(async () => {
|
|
21
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
22
|
+
const clientSeq = nextClientSeq();
|
|
23
|
+
const { applied } = await client.callMutator(serverRef, args, {
|
|
24
|
+
clientSeq,
|
|
25
|
+
shardKey: context.shardKey
|
|
26
|
+
});
|
|
27
|
+
if (applied) {
|
|
28
|
+
return clientSeq;
|
|
29
|
+
}
|
|
30
|
+
if (attempt >= maxReissues) {
|
|
31
|
+
throw new Error(`lunora: custom mutator "${serverRef}" could not claim a fresh client sequence after ${String(maxReissues)} attempts`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
pushChain = run.then(
|
|
36
|
+
() => void 0,
|
|
37
|
+
() => void 0
|
|
38
|
+
);
|
|
39
|
+
return run;
|
|
40
|
+
};
|
|
41
|
+
const bound = {};
|
|
42
|
+
for (const [name, mutator] of Object.entries(mutators)) {
|
|
43
|
+
bound[name] = (args) => {
|
|
44
|
+
const transaction = createTransaction({
|
|
45
|
+
autoCommit: true,
|
|
46
|
+
metadata: { serverRef: mutator.serverRef },
|
|
47
|
+
mutationFn: async () => {
|
|
48
|
+
let appliedSeq = 0;
|
|
49
|
+
await runOutboxMutation(async () => {
|
|
50
|
+
appliedSeq = await pushSerialized(mutator.serverRef, args);
|
|
51
|
+
});
|
|
52
|
+
if (context.checkpoints) {
|
|
53
|
+
await context.checkpoints.awaitMutationId(appliedSeq);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
transaction.mutate(() => {
|
|
58
|
+
mutator.apply({ collections: context.collections }, args);
|
|
59
|
+
});
|
|
60
|
+
return transaction;
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return bound;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export { bindMutators, defineMutator };
|