@lunora/db 1.0.0-alpha.1 → 1.0.0-alpha.100
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/LICENSE.md +6 -0
- package/README.md +19 -1
- package/__assets__/package-og.svg +1 -1
- package/dist/collections/index.d.mts +5 -0
- package/dist/collections/index.d.ts +5 -0
- package/dist/collections/index.mjs +1 -0
- package/dist/index.d.mts +140 -135
- package/dist/index.d.ts +140 -135
- package/dist/index.mjs +1 -6
- 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/CHECKPOINT_FALLBACK_MS-DVkxHO2j.mjs +1 -0
- package/dist/packem_shared/DIRECT_TRANSACTION_METADATA_KEY-O-hmqO42.mjs +1 -0
- package/dist/packem_shared/OUTBOX_MUTATION_FN_NAME-BfFCughR.mjs +1 -0
- package/dist/packem_shared/applyPlanToCollections-C_eyqNF9.mjs +1 -0
- package/dist/packem_shared/collection-options.d-bmH3BTGq.d.mts +364 -0
- package/dist/packem_shared/collection-options.d-bmH3BTGq.d.ts +364 -0
- package/dist/packem_shared/defineCollections-Cz4-m1Mp.mjs +1 -0
- package/dist/packem_shared/index.d-BzXEOiP5.d.mts +174 -0
- package/dist/packem_shared/index.d-DQsjGcjP.d.ts +235 -0
- package/dist/packem_shared/index.d-DlfHbDas.d.ts +174 -0
- package/dist/packem_shared/index.d-DrpzHIoj.d.mts +235 -0
- package/package.json +12 -3
- package/dist/packem_shared/defineCollections-Cqtf9ffi.mjs +0 -109
- package/dist/packem_shared/toMap-CRulWqZ7.mjs +0 -65
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { type b as CheckpointRegistry, type L as LunoraCollectionConfig, type f as LunoraCollectionOptions, i as createCheckpointRegistry, m as lunoraCollectionOptions } from "../packem_shared/collection-options.d-bmH3BTGq.mjs";
|
|
2
|
+
export { type C as CollectionDef, type I as InsertBinding, type L as LunoraDb, d as defineCollections } from "../packem_shared/index.d-BzXEOiP5.mjs";
|
|
3
|
+
import '@lunora/client';
|
|
4
|
+
import '@tanstack/db';
|
|
5
|
+
import '@tanstack/offline-transactions';
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { type b as CheckpointRegistry, type L as LunoraCollectionConfig, type f as LunoraCollectionOptions, i as createCheckpointRegistry, m as lunoraCollectionOptions } from "../packem_shared/collection-options.d-bmH3BTGq.js";
|
|
2
|
+
export { type C as CollectionDef, type I as InsertBinding, type L as LunoraDb, d as defineCollections } from "../packem_shared/index.d-DlfHbDas.js";
|
|
3
|
+
import '@lunora/client';
|
|
4
|
+
import '@tanstack/db';
|
|
5
|
+
import '@tanstack/offline-transactions';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createCheckpointRegistry as t,lunoraCollectionOptions as r}from"../packem_shared/CHECKPOINT_FALLBACK_MS-DVkxHO2j.mjs";import{defineCollections as n}from"../packem_shared/defineCollections-Cz4-m1Mp.mjs";export{t as createCheckpointRegistry,n as defineCollections,r as lunoraCollectionOptions};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,142 +1,147 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
type
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
interface
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} | {
|
|
16
|
-
key: string;
|
|
17
|
-
type: "delete";
|
|
18
|
-
}) => void;
|
|
1
|
+
import { Collection } from '@tanstack/db';
|
|
2
|
+
import { R as Row } from "./packem_shared/collection-options.d-bmH3BTGq.mjs";
|
|
3
|
+
export { C as CHECKPOINT_FALLBACK_MS, type a as CheckpointFallbackEvent, type b as CheckpointRegistry, type c as CheckpointRegistryOptions, type d as CheckpointRegistryStats, type e as CheckpointWatermark, type E as ExecutorOutboxSinkOptions, type L as LunoraCollectionConfig, type f as LunoraCollectionOptions, O as OUTBOX_MUTATION_FN_NAME, type g as OutboxExecutor, type h as OutboxMutationMetadata, type S as SyncWriter, i as createCheckpointRegistry, j as createExecutorOutboxSink, k as createOptimisticOnlineDetector, l as getShardCheckpoints, m as lunoraCollectionOptions, n as makeDiffEmit, r as releaseShardCheckpoints, o as runOutboxMutation, t as toMap } from "./packem_shared/collection-options.d-bmH3BTGq.mjs";
|
|
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/index.d-BzXEOiP5.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 M as MutatorReference, type e as MutatorRejectedEvent, f as bindMutators, g as defineMutator, i as initMutators } from "./packem_shared/index.d-DrpzHIoj.mjs";
|
|
6
|
+
import '@lunora/client';
|
|
7
|
+
import '@tanstack/offline-transactions';
|
|
8
|
+
/** One row to insert. `_id` may be pre-minted client-side so the optimistic row keys match the persisted one. */
|
|
9
|
+
interface PlanInsert {
|
|
10
|
+
/** Row body. Include `_id` to key the row yourself (the server honors it as the `clientId`). */
|
|
11
|
+
row: Record<string, unknown> & {
|
|
12
|
+
_id?: string;
|
|
13
|
+
};
|
|
14
|
+
table: string;
|
|
19
15
|
}
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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>;
|
|
16
|
+
/** One row to patch, by id. */
|
|
17
|
+
interface PlanPatch {
|
|
18
|
+
fields: Record<string, unknown>;
|
|
19
|
+
id: string;
|
|
20
|
+
table: string;
|
|
80
21
|
}
|
|
81
|
-
/**
|
|
82
|
-
interface
|
|
83
|
-
|
|
84
|
-
|
|
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;
|
|
22
|
+
/** One row to delete, by id. */
|
|
23
|
+
interface PlanDelete {
|
|
24
|
+
id: string;
|
|
25
|
+
table: string;
|
|
98
26
|
}
|
|
99
|
-
type AnyDef = CollectionDef<any, any>;
|
|
100
27
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
28
|
+
* A change plan: what a mutator intends to write.
|
|
29
|
+
*
|
|
30
|
+
* Applied in a fixed order — **deletes, then patches, then inserts** — by both
|
|
31
|
+
* appliers. The order is part of the contract, not an implementation detail: a plan
|
|
32
|
+
* that deletes a row and inserts its replacement under the same natural key only
|
|
33
|
+
* behaves the same on both sides if both sides agree which happens first.
|
|
34
|
+
*/
|
|
35
|
+
interface ChangePlan {
|
|
36
|
+
deletes?: ReadonlyArray<PlanDelete>;
|
|
37
|
+
inserts?: ReadonlyArray<PlanInsert>;
|
|
38
|
+
patches?: ReadonlyArray<PlanPatch>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The `ctx.db` methods {@link applyPlanToDb} needs — structural, so any writer
|
|
42
|
+
* satisfies it.
|
|
43
|
+
*
|
|
44
|
+
* The `never` parameter positions are deliberate, not laziness. `ctx.db.delete` is
|
|
45
|
+
* `<T extends string>(id: Id<T>) => …` over the **branded** `Id<T>`, and under
|
|
46
|
+
* `strictFunctionTypes` a parameter is contravariant — so declaring `id: string` here
|
|
47
|
+
* would make the real `ctx.db` un-assignable to `PlanWriter` (`string` is not
|
|
48
|
+
* assignable to `string & { __table }`), and every caller would need a cast at the
|
|
49
|
+
* call site instead. `never` accepts any branded id, which keeps `applyPlanToDb(ctx.db,
|
|
50
|
+
* plan)` cast-free for the caller and confines the two `as never` casts to this module.
|
|
51
|
+
*
|
|
52
|
+
* The trade-off is real: argument checking inside `applyPlanToDb` is erased, so a plan
|
|
53
|
+
* naming a table the schema doesn't have is a runtime error. Use `ctx.db.asId(table,
|
|
54
|
+
* id)` when building the plan to catch a malformed id at the boundary.
|
|
55
|
+
*/
|
|
56
|
+
interface PlanWriter {
|
|
57
|
+
delete: (id: never) => Promise<void>;
|
|
58
|
+
insert: (tableName: never, document: Record<string, unknown>, options?: {
|
|
59
|
+
clientId?: string;
|
|
60
|
+
}) => Promise<unknown>;
|
|
61
|
+
patch: (id: never, patch: Record<string, unknown>) => Promise<void>;
|
|
129
62
|
}
|
|
130
63
|
/**
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
64
|
+
* Apply `plan` to a map of TanStack collections — the client (optimistic) half.
|
|
65
|
+
*
|
|
66
|
+
* A plan naming a table with no wired collection is **skipped, not an error**: an app
|
|
67
|
+
* legitimately syncs a subset of the tables its mutators write (a server-only audit
|
|
68
|
+
* row has no client collection), and throwing would make the optimistic body fail on
|
|
69
|
+
* a write the server handles fine.
|
|
70
|
+
*/
|
|
71
|
+
declare const applyPlanToCollections: (collections: Record<string, Collection<Row, string>>, plan: ChangePlan) => void;
|
|
72
|
+
/**
|
|
73
|
+
* Apply `plan` to a `ctx.db` writer — the server (authoritative) half.
|
|
74
|
+
*
|
|
75
|
+
* Sequential by design: the shard's SQLite is single-threaded and a mutation runs
|
|
76
|
+
* inside one BEGIN/COMMIT span, so ordering is observable and a mid-plan failure
|
|
77
|
+
* rolls the whole plan back. An insert carrying an `_id` forwards it as `clientId`,
|
|
78
|
+
* which is how a client-minted key becomes the persisted primary key.
|
|
79
|
+
*/
|
|
80
|
+
declare const applyPlanToDb: (db: PlanWriter, plan: ChangePlan) => Promise<void>;
|
|
141
81
|
declare const VERSION = "0.0.0";
|
|
142
|
-
export {
|
|
82
|
+
export {
|
|
83
|
+
/**
|
|
84
|
+
* `@lunora/db` — a TanStack DB binding for the Lunora client.
|
|
85
|
+
*
|
|
86
|
+
* `defineCollections(client, { … })` wires a set of Lunora tables into live,
|
|
87
|
+
* auto-indexed TanStack DB collections (reads) plus a durable, retried
|
|
88
|
+
* offline-transactions outbox (writes) in one declaration. The lower-level
|
|
89
|
+
* helpers are exported for testing and advanced composition.
|
|
90
|
+
*
|
|
91
|
+
* Peer-depends on `@tanstack/db` and `@tanstack/offline-transactions`; the
|
|
92
|
+
* consuming app pins their versions and supplies React bindings
|
|
93
|
+
* (`@tanstack/react-db`) itself.
|
|
94
|
+
*/
|
|
95
|
+
type ChangePlan,
|
|
96
|
+
/**
|
|
97
|
+
* `@lunora/db` — a TanStack DB binding for the Lunora client.
|
|
98
|
+
*
|
|
99
|
+
* `defineCollections(client, { … })` wires a set of Lunora tables into live,
|
|
100
|
+
* auto-indexed TanStack DB collections (reads) plus a durable, retried
|
|
101
|
+
* offline-transactions outbox (writes) in one declaration. The lower-level
|
|
102
|
+
* helpers are exported for testing and advanced composition.
|
|
103
|
+
*
|
|
104
|
+
* Peer-depends on `@tanstack/db` and `@tanstack/offline-transactions`; the
|
|
105
|
+
* consuming app pins their versions and supplies React bindings
|
|
106
|
+
* (`@tanstack/react-db`) itself.
|
|
107
|
+
*/
|
|
108
|
+
type PlanDelete,
|
|
109
|
+
/**
|
|
110
|
+
* `@lunora/db` — a TanStack DB binding for the Lunora client.
|
|
111
|
+
*
|
|
112
|
+
* `defineCollections(client, { … })` wires a set of Lunora tables into live,
|
|
113
|
+
* auto-indexed TanStack DB collections (reads) plus a durable, retried
|
|
114
|
+
* offline-transactions outbox (writes) in one declaration. The lower-level
|
|
115
|
+
* helpers are exported for testing and advanced composition.
|
|
116
|
+
*
|
|
117
|
+
* Peer-depends on `@tanstack/db` and `@tanstack/offline-transactions`; the
|
|
118
|
+
* consuming app pins their versions and supplies React bindings
|
|
119
|
+
* (`@tanstack/react-db`) itself.
|
|
120
|
+
*/
|
|
121
|
+
type PlanInsert,
|
|
122
|
+
/**
|
|
123
|
+
* `@lunora/db` — a TanStack DB binding for the Lunora client.
|
|
124
|
+
*
|
|
125
|
+
* `defineCollections(client, { … })` wires a set of Lunora tables into live,
|
|
126
|
+
* auto-indexed TanStack DB collections (reads) plus a durable, retried
|
|
127
|
+
* offline-transactions outbox (writes) in one declaration. The lower-level
|
|
128
|
+
* helpers are exported for testing and advanced composition.
|
|
129
|
+
*
|
|
130
|
+
* Peer-depends on `@tanstack/db` and `@tanstack/offline-transactions`; the
|
|
131
|
+
* consuming app pins their versions and supplies React bindings
|
|
132
|
+
* (`@tanstack/react-db`) itself.
|
|
133
|
+
*/
|
|
134
|
+
type PlanPatch,
|
|
135
|
+
/**
|
|
136
|
+
* `@lunora/db` — a TanStack DB binding for the Lunora client.
|
|
137
|
+
*
|
|
138
|
+
* `defineCollections(client, { … })` wires a set of Lunora tables into live,
|
|
139
|
+
* auto-indexed TanStack DB collections (reads) plus a durable, retried
|
|
140
|
+
* offline-transactions outbox (writes) in one declaration. The lower-level
|
|
141
|
+
* helpers are exported for testing and advanced composition.
|
|
142
|
+
*
|
|
143
|
+
* Peer-depends on `@tanstack/db` and `@tanstack/offline-transactions`; the
|
|
144
|
+
* consuming app pins their versions and supplies React bindings
|
|
145
|
+
* (`@tanstack/react-db`) itself.
|
|
146
|
+
*/
|
|
147
|
+
type PlanWriter, type Row, VERSION, applyPlanToCollections, applyPlanToDb };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,142 +1,147 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
type
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
interface
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} | {
|
|
16
|
-
key: string;
|
|
17
|
-
type: "delete";
|
|
18
|
-
}) => void;
|
|
1
|
+
import { Collection } from '@tanstack/db';
|
|
2
|
+
import { R as Row } from "./packem_shared/collection-options.d-bmH3BTGq.js";
|
|
3
|
+
export { C as CHECKPOINT_FALLBACK_MS, type a as CheckpointFallbackEvent, type b as CheckpointRegistry, type c as CheckpointRegistryOptions, type d as CheckpointRegistryStats, type e as CheckpointWatermark, type E as ExecutorOutboxSinkOptions, type L as LunoraCollectionConfig, type f as LunoraCollectionOptions, O as OUTBOX_MUTATION_FN_NAME, type g as OutboxExecutor, type h as OutboxMutationMetadata, type S as SyncWriter, i as createCheckpointRegistry, j as createExecutorOutboxSink, k as createOptimisticOnlineDetector, l as getShardCheckpoints, m as lunoraCollectionOptions, n as makeDiffEmit, r as releaseShardCheckpoints, o as runOutboxMutation, t as toMap } from "./packem_shared/collection-options.d-bmH3BTGq.js";
|
|
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/index.d-DlfHbDas.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 M as MutatorReference, type e as MutatorRejectedEvent, f as bindMutators, g as defineMutator, i as initMutators } from "./packem_shared/index.d-DQsjGcjP.js";
|
|
6
|
+
import '@lunora/client';
|
|
7
|
+
import '@tanstack/offline-transactions';
|
|
8
|
+
/** One row to insert. `_id` may be pre-minted client-side so the optimistic row keys match the persisted one. */
|
|
9
|
+
interface PlanInsert {
|
|
10
|
+
/** Row body. Include `_id` to key the row yourself (the server honors it as the `clientId`). */
|
|
11
|
+
row: Record<string, unknown> & {
|
|
12
|
+
_id?: string;
|
|
13
|
+
};
|
|
14
|
+
table: string;
|
|
19
15
|
}
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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>;
|
|
16
|
+
/** One row to patch, by id. */
|
|
17
|
+
interface PlanPatch {
|
|
18
|
+
fields: Record<string, unknown>;
|
|
19
|
+
id: string;
|
|
20
|
+
table: string;
|
|
80
21
|
}
|
|
81
|
-
/**
|
|
82
|
-
interface
|
|
83
|
-
|
|
84
|
-
|
|
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;
|
|
22
|
+
/** One row to delete, by id. */
|
|
23
|
+
interface PlanDelete {
|
|
24
|
+
id: string;
|
|
25
|
+
table: string;
|
|
98
26
|
}
|
|
99
|
-
type AnyDef = CollectionDef<any, any>;
|
|
100
27
|
/**
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
28
|
+
* A change plan: what a mutator intends to write.
|
|
29
|
+
*
|
|
30
|
+
* Applied in a fixed order — **deletes, then patches, then inserts** — by both
|
|
31
|
+
* appliers. The order is part of the contract, not an implementation detail: a plan
|
|
32
|
+
* that deletes a row and inserts its replacement under the same natural key only
|
|
33
|
+
* behaves the same on both sides if both sides agree which happens first.
|
|
34
|
+
*/
|
|
35
|
+
interface ChangePlan {
|
|
36
|
+
deletes?: ReadonlyArray<PlanDelete>;
|
|
37
|
+
inserts?: ReadonlyArray<PlanInsert>;
|
|
38
|
+
patches?: ReadonlyArray<PlanPatch>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The `ctx.db` methods {@link applyPlanToDb} needs — structural, so any writer
|
|
42
|
+
* satisfies it.
|
|
43
|
+
*
|
|
44
|
+
* The `never` parameter positions are deliberate, not laziness. `ctx.db.delete` is
|
|
45
|
+
* `<T extends string>(id: Id<T>) => …` over the **branded** `Id<T>`, and under
|
|
46
|
+
* `strictFunctionTypes` a parameter is contravariant — so declaring `id: string` here
|
|
47
|
+
* would make the real `ctx.db` un-assignable to `PlanWriter` (`string` is not
|
|
48
|
+
* assignable to `string & { __table }`), and every caller would need a cast at the
|
|
49
|
+
* call site instead. `never` accepts any branded id, which keeps `applyPlanToDb(ctx.db,
|
|
50
|
+
* plan)` cast-free for the caller and confines the two `as never` casts to this module.
|
|
51
|
+
*
|
|
52
|
+
* The trade-off is real: argument checking inside `applyPlanToDb` is erased, so a plan
|
|
53
|
+
* naming a table the schema doesn't have is a runtime error. Use `ctx.db.asId(table,
|
|
54
|
+
* id)` when building the plan to catch a malformed id at the boundary.
|
|
55
|
+
*/
|
|
56
|
+
interface PlanWriter {
|
|
57
|
+
delete: (id: never) => Promise<void>;
|
|
58
|
+
insert: (tableName: never, document: Record<string, unknown>, options?: {
|
|
59
|
+
clientId?: string;
|
|
60
|
+
}) => Promise<unknown>;
|
|
61
|
+
patch: (id: never, patch: Record<string, unknown>) => Promise<void>;
|
|
129
62
|
}
|
|
130
63
|
/**
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
64
|
+
* Apply `plan` to a map of TanStack collections — the client (optimistic) half.
|
|
65
|
+
*
|
|
66
|
+
* A plan naming a table with no wired collection is **skipped, not an error**: an app
|
|
67
|
+
* legitimately syncs a subset of the tables its mutators write (a server-only audit
|
|
68
|
+
* row has no client collection), and throwing would make the optimistic body fail on
|
|
69
|
+
* a write the server handles fine.
|
|
70
|
+
*/
|
|
71
|
+
declare const applyPlanToCollections: (collections: Record<string, Collection<Row, string>>, plan: ChangePlan) => void;
|
|
72
|
+
/**
|
|
73
|
+
* Apply `plan` to a `ctx.db` writer — the server (authoritative) half.
|
|
74
|
+
*
|
|
75
|
+
* Sequential by design: the shard's SQLite is single-threaded and a mutation runs
|
|
76
|
+
* inside one BEGIN/COMMIT span, so ordering is observable and a mid-plan failure
|
|
77
|
+
* rolls the whole plan back. An insert carrying an `_id` forwards it as `clientId`,
|
|
78
|
+
* which is how a client-minted key becomes the persisted primary key.
|
|
79
|
+
*/
|
|
80
|
+
declare const applyPlanToDb: (db: PlanWriter, plan: ChangePlan) => Promise<void>;
|
|
141
81
|
declare const VERSION = "0.0.0";
|
|
142
|
-
export {
|
|
82
|
+
export {
|
|
83
|
+
/**
|
|
84
|
+
* `@lunora/db` — a TanStack DB binding for the Lunora client.
|
|
85
|
+
*
|
|
86
|
+
* `defineCollections(client, { … })` wires a set of Lunora tables into live,
|
|
87
|
+
* auto-indexed TanStack DB collections (reads) plus a durable, retried
|
|
88
|
+
* offline-transactions outbox (writes) in one declaration. The lower-level
|
|
89
|
+
* helpers are exported for testing and advanced composition.
|
|
90
|
+
*
|
|
91
|
+
* Peer-depends on `@tanstack/db` and `@tanstack/offline-transactions`; the
|
|
92
|
+
* consuming app pins their versions and supplies React bindings
|
|
93
|
+
* (`@tanstack/react-db`) itself.
|
|
94
|
+
*/
|
|
95
|
+
type ChangePlan,
|
|
96
|
+
/**
|
|
97
|
+
* `@lunora/db` — a TanStack DB binding for the Lunora client.
|
|
98
|
+
*
|
|
99
|
+
* `defineCollections(client, { … })` wires a set of Lunora tables into live,
|
|
100
|
+
* auto-indexed TanStack DB collections (reads) plus a durable, retried
|
|
101
|
+
* offline-transactions outbox (writes) in one declaration. The lower-level
|
|
102
|
+
* helpers are exported for testing and advanced composition.
|
|
103
|
+
*
|
|
104
|
+
* Peer-depends on `@tanstack/db` and `@tanstack/offline-transactions`; the
|
|
105
|
+
* consuming app pins their versions and supplies React bindings
|
|
106
|
+
* (`@tanstack/react-db`) itself.
|
|
107
|
+
*/
|
|
108
|
+
type PlanDelete,
|
|
109
|
+
/**
|
|
110
|
+
* `@lunora/db` — a TanStack DB binding for the Lunora client.
|
|
111
|
+
*
|
|
112
|
+
* `defineCollections(client, { … })` wires a set of Lunora tables into live,
|
|
113
|
+
* auto-indexed TanStack DB collections (reads) plus a durable, retried
|
|
114
|
+
* offline-transactions outbox (writes) in one declaration. The lower-level
|
|
115
|
+
* helpers are exported for testing and advanced composition.
|
|
116
|
+
*
|
|
117
|
+
* Peer-depends on `@tanstack/db` and `@tanstack/offline-transactions`; the
|
|
118
|
+
* consuming app pins their versions and supplies React bindings
|
|
119
|
+
* (`@tanstack/react-db`) itself.
|
|
120
|
+
*/
|
|
121
|
+
type PlanInsert,
|
|
122
|
+
/**
|
|
123
|
+
* `@lunora/db` — a TanStack DB binding for the Lunora client.
|
|
124
|
+
*
|
|
125
|
+
* `defineCollections(client, { … })` wires a set of Lunora tables into live,
|
|
126
|
+
* auto-indexed TanStack DB collections (reads) plus a durable, retried
|
|
127
|
+
* offline-transactions outbox (writes) in one declaration. The lower-level
|
|
128
|
+
* helpers are exported for testing and advanced composition.
|
|
129
|
+
*
|
|
130
|
+
* Peer-depends on `@tanstack/db` and `@tanstack/offline-transactions`; the
|
|
131
|
+
* consuming app pins their versions and supplies React bindings
|
|
132
|
+
* (`@tanstack/react-db`) itself.
|
|
133
|
+
*/
|
|
134
|
+
type PlanPatch,
|
|
135
|
+
/**
|
|
136
|
+
* `@lunora/db` — a TanStack DB binding for the Lunora client.
|
|
137
|
+
*
|
|
138
|
+
* `defineCollections(client, { … })` wires a set of Lunora tables into live,
|
|
139
|
+
* auto-indexed TanStack DB collections (reads) plus a durable, retried
|
|
140
|
+
* offline-transactions outbox (writes) in one declaration. The lower-level
|
|
141
|
+
* helpers are exported for testing and advanced composition.
|
|
142
|
+
*
|
|
143
|
+
* Peer-depends on `@tanstack/db` and `@tanstack/offline-transactions`; the
|
|
144
|
+
* consuming app pins their versions and supplies React bindings
|
|
145
|
+
* (`@tanstack/react-db`) itself.
|
|
146
|
+
*/
|
|
147
|
+
type PlanWriter, type Row, VERSION, applyPlanToCollections, applyPlanToDb };
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { createOptimisticOnlineDetector, makeDiffEmit, runOutboxMutation, toMap } from './packem_shared/toMap-CRulWqZ7.mjs';
|
|
3
|
-
|
|
4
|
-
const VERSION = "0.0.0";
|
|
5
|
-
|
|
6
|
-
export { VERSION };
|
|
1
|
+
import{applyPlanToCollections as r,applyPlanToDb as i}from"./packem_shared/applyPlanToCollections-C_eyqNF9.mjs";import{CHECKPOINT_FALLBACK_MS as a,createCheckpointRegistry as p,getShardCheckpoints as c,lunoraCollectionOptions as l,releaseShardCheckpoints as s}from"./packem_shared/CHECKPOINT_FALLBACK_MS-DVkxHO2j.mjs";import{defineCollections as O}from"./packem_shared/defineCollections-Cz4-m1Mp.mjs";import{DIRECT_TRANSACTION_METADATA_KEY as f,bindMutators as u,defineMutator as x,initMutators as A}from"./packem_shared/DIRECT_TRANSACTION_METADATA_KEY-O-hmqO42.mjs";import{OUTBOX_MUTATION_FN_NAME as m,createExecutorOutboxSink as E,createOptimisticOnlineDetector as _,makeDiffEmit as N,runOutboxMutation as S,toMap as d}from"./packem_shared/OUTBOX_MUTATION_FN_NAME-BfFCughR.mjs";const t="0.0.0";export{a as CHECKPOINT_FALLBACK_MS,f as DIRECT_TRANSACTION_METADATA_KEY,m as OUTBOX_MUTATION_FN_NAME,t as VERSION,r as applyPlanToCollections,i as applyPlanToDb,u as bindMutators,p as createCheckpointRegistry,E as createExecutorOutboxSink,_ as createOptimisticOnlineDetector,O as defineCollections,x as defineMutator,c as getShardCheckpoints,A as initMutators,l as lunoraCollectionOptions,N as makeDiffEmit,s as releaseShardCheckpoints,S as runOutboxMutation,d as toMap};
|
|
@@ -0,0 +1,5 @@
|
|
|
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 e as MutatorRejectedEvent, f as bindMutators, g as defineMutator, i as initMutators } from "../packem_shared/index.d-DrpzHIoj.mjs";
|
|
2
|
+
import '@lunora/client';
|
|
3
|
+
import '@tanstack/db';
|
|
4
|
+
import "../packem_shared/collection-options.d-bmH3BTGq.mjs";
|
|
5
|
+
import '@tanstack/offline-transactions';
|
|
@@ -0,0 +1,5 @@
|
|
|
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 e as MutatorRejectedEvent, f as bindMutators, g as defineMutator, i as initMutators } from "../packem_shared/index.d-DQsjGcjP.js";
|
|
2
|
+
import '@lunora/client';
|
|
3
|
+
import '@tanstack/db';
|
|
4
|
+
import "../packem_shared/collection-options.d-bmH3BTGq.js";
|
|
5
|
+
import '@tanstack/offline-transactions';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{bindMutators as r,defineMutator as i,initMutators as a}from"../packem_shared/DIRECT_TRANSACTION_METADATA_KEY-O-hmqO42.mjs";export{r as bindMutators,i as defineMutator,a as initMutators};
|