@lunora/db 1.0.0-alpha.28 → 1.0.0-alpha.29
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/collections/index.d.mts +2 -2
- package/dist/collections/index.d.ts +2 -2
- package/dist/collections/index.mjs +1 -2
- package/dist/index.d.mts +79 -5
- package/dist/index.d.ts +79 -5
- package/dist/index.mjs +1 -8
- package/dist/mutators/index.d.mts +2 -2
- package/dist/mutators/index.d.ts +2 -2
- package/dist/mutators/index.mjs +1 -1
- package/dist/packem_shared/CHECKPOINT_FALLBACK_MS-CtPxAU9J.mjs +1 -0
- package/dist/packem_shared/DIRECT_TRANSACTION_METADATA_KEY-Cvm6vs5S.mjs +1 -0
- package/dist/packem_shared/OUTBOX_MUTATION_FN_NAME-CebgkYw2.mjs +1 -0
- package/dist/packem_shared/applyPlanToCollections-C29s6OoX.mjs +1 -0
- package/dist/packem_shared/{collection-options.d-BPVkj5FW.d.mts → collection-options.d-C03Rjjxn.d.mts} +120 -10
- package/dist/packem_shared/{collection-options.d-BPVkj5FW.d.ts → collection-options.d-C03Rjjxn.d.ts} +120 -10
- package/dist/packem_shared/{define-collections.d-Dkzt5xPY.d.ts → define-collections.d-Ds-5C0M9.d.ts} +1 -1
- package/dist/packem_shared/{define-collections.d-C9paBiXl.d.mts → define-collections.d-Y9AaX0yt.d.mts} +1 -1
- package/dist/packem_shared/{define-mutators.d-DTU85KL6.d.mts → define-mutators.d-DqkWcDks.d.ts} +80 -12
- package/dist/packem_shared/{define-mutators.d-DukVT0Y3.d.ts → define-mutators.d-tPb72T4I.d.mts} +80 -12
- package/dist/packem_shared/defineCollections-CnFdW1Ff.mjs +1 -0
- package/package.json +2 -2
- package/dist/packem_shared/OUTBOX_MUTATION_FN_NAME-9fqK7rND.mjs +0 -131
- package/dist/packem_shared/bindMutators-B_RaNgel.mjs +0 -70
- package/dist/packem_shared/createCheckpointRegistry-BtIEm2Kh.mjs +0 -136
- package/dist/packem_shared/defineCollections-C_F-42rS.mjs +0 -109
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { type C as CheckpointRegistry, type L as LunoraCollectionConfig, type a as LunoraCollectionOptions, c as createCheckpointRegistry, l as lunoraCollectionOptions } from "../packem_shared/collection-options.d-
|
|
2
|
-
export { type C as CollectionDef, type I as InsertBinding, type L as LunoraDb, d as defineCollections } from "../packem_shared/define-collections.d-
|
|
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-C03Rjjxn.mjs";
|
|
2
|
+
export { type C as CollectionDef, type I as InsertBinding, type L as LunoraDb, d as defineCollections } from "../packem_shared/define-collections.d-Y9AaX0yt.mjs";
|
|
3
3
|
import '@lunora/client';
|
|
4
4
|
import '@tanstack/db';
|
|
5
5
|
import '@tanstack/offline-transactions';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { type C as CheckpointRegistry, type L as LunoraCollectionConfig, type a as LunoraCollectionOptions, c as createCheckpointRegistry, l as lunoraCollectionOptions } from "../packem_shared/collection-options.d-
|
|
2
|
-
export { type C as CollectionDef, type I as InsertBinding, type L as LunoraDb, d as defineCollections } from "../packem_shared/define-collections.d-
|
|
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-C03Rjjxn.js";
|
|
2
|
+
export { type C as CollectionDef, type I as InsertBinding, type L as LunoraDb, d as defineCollections } from "../packem_shared/define-collections.d-Ds-5C0M9.js";
|
|
3
3
|
import '@lunora/client';
|
|
4
4
|
import '@tanstack/db';
|
|
5
5
|
import '@tanstack/offline-transactions';
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { defineCollections } from '../packem_shared/defineCollections-C_F-42rS.mjs';
|
|
1
|
+
import{createCheckpointRegistry as t,lunoraCollectionOptions as r}from"../packem_shared/CHECKPOINT_FALLBACK_MS-CtPxAU9J.mjs";import{defineCollections as n}from"../packem_shared/defineCollections-CnFdW1Ff.mjs";export{t as createCheckpointRegistry,n as defineCollections,r as lunoraCollectionOptions};
|
package/dist/index.d.mts
CHANGED
|
@@ -1,8 +1,82 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export { type
|
|
1
|
+
import { Collection } from '@tanstack/db';
|
|
2
|
+
import { R as Row } from "./packem_shared/collection-options.d-C03Rjjxn.mjs";
|
|
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-C03Rjjxn.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/define-collections.d-Y9AaX0yt.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-tPb72T4I.mjs";
|
|
4
6
|
import '@lunora/client';
|
|
5
|
-
import '@tanstack/db';
|
|
6
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;
|
|
15
|
+
}
|
|
16
|
+
/** One row to patch, by id. */
|
|
17
|
+
interface PlanPatch {
|
|
18
|
+
fields: Record<string, unknown>;
|
|
19
|
+
id: string;
|
|
20
|
+
table: string;
|
|
21
|
+
}
|
|
22
|
+
/** One row to delete, by id. */
|
|
23
|
+
interface PlanDelete {
|
|
24
|
+
id: string;
|
|
25
|
+
table: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
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>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
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>;
|
|
7
81
|
declare const VERSION = "0.0.0";
|
|
8
|
-
export { VERSION };
|
|
82
|
+
export { type ChangePlan, type PlanDelete, type PlanInsert, type PlanPatch, type PlanWriter, type Row, VERSION, applyPlanToCollections, applyPlanToDb };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,82 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export { type
|
|
1
|
+
import { Collection } from '@tanstack/db';
|
|
2
|
+
import { R as Row } from "./packem_shared/collection-options.d-C03Rjjxn.js";
|
|
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-C03Rjjxn.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/define-collections.d-Ds-5C0M9.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-DqkWcDks.js";
|
|
4
6
|
import '@lunora/client';
|
|
5
|
-
import '@tanstack/db';
|
|
6
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;
|
|
15
|
+
}
|
|
16
|
+
/** One row to patch, by id. */
|
|
17
|
+
interface PlanPatch {
|
|
18
|
+
fields: Record<string, unknown>;
|
|
19
|
+
id: string;
|
|
20
|
+
table: string;
|
|
21
|
+
}
|
|
22
|
+
/** One row to delete, by id. */
|
|
23
|
+
interface PlanDelete {
|
|
24
|
+
id: string;
|
|
25
|
+
table: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
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>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
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>;
|
|
7
81
|
declare const VERSION = "0.0.0";
|
|
8
|
-
export { VERSION };
|
|
82
|
+
export { type ChangePlan, type PlanDelete, type PlanInsert, type PlanPatch, type PlanWriter, type Row, VERSION, applyPlanToCollections, applyPlanToDb };
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export { defineCollections } from './packem_shared/defineCollections-C_F-42rS.mjs';
|
|
3
|
-
export { bindMutators, defineMutator } from './packem_shared/bindMutators-B_RaNgel.mjs';
|
|
4
|
-
export { OUTBOX_MUTATION_FN_NAME, createExecutorOutboxSink, createOptimisticOnlineDetector, makeDiffEmit, runOutboxMutation, toMap } from './packem_shared/OUTBOX_MUTATION_FN_NAME-9fqK7rND.mjs';
|
|
5
|
-
|
|
6
|
-
const VERSION = "0.0.0";
|
|
7
|
-
|
|
8
|
-
export { VERSION };
|
|
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-CtPxAU9J.mjs";import{defineCollections as T}from"./packem_shared/defineCollections-CnFdW1Ff.mjs";import{DIRECT_TRANSACTION_METADATA_KEY as x,bindMutators as A,defineMutator as m}from"./packem_shared/DIRECT_TRANSACTION_METADATA_KEY-Cvm6vs5S.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,5 +1,5 @@
|
|
|
1
|
-
export { type B as BindMutatorsContext, type a as BoundMutators, type C as ClientMutatorContext, type b as ClientMutatorDef, c as bindMutators, d as defineMutator } from "../packem_shared/define-mutators.d-
|
|
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-tPb72T4I.mjs";
|
|
2
2
|
import '@lunora/client';
|
|
3
3
|
import '@tanstack/db';
|
|
4
|
-
import "../packem_shared/collection-options.d-
|
|
4
|
+
import "../packem_shared/collection-options.d-C03Rjjxn.mjs";
|
|
5
5
|
import '@tanstack/offline-transactions';
|
package/dist/mutators/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { type B as BindMutatorsContext, type a as BoundMutators, type C as ClientMutatorContext, type b as ClientMutatorDef, c as bindMutators, d as defineMutator } from "../packem_shared/define-mutators.d-
|
|
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-DqkWcDks.js";
|
|
2
2
|
import '@lunora/client';
|
|
3
3
|
import '@tanstack/db';
|
|
4
|
-
import "../packem_shared/collection-options.d-
|
|
4
|
+
import "../packem_shared/collection-options.d-C03Rjjxn.js";
|
|
5
5
|
import '@tanstack/offline-transactions';
|
package/dist/mutators/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
import{bindMutators as r,defineMutator as e}from"../packem_shared/DIRECT_TRANSACTION_METADATA_KEY-Cvm6vs5S.mjs";export{r as bindMutators,e as defineMutator};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{LunoraError as y}from"@lunora/errors";import{BTreeIndex as w}from"@tanstack/db";import{toMap as b,makeDiffEmit as C}from"./OUTBOX_MUTATION_FN_NAME-CebgkYw2.mjs";const v=()=>{let e=Number.NEGATIVE_INFINITY;const a=[];return{advance:n=>{if(!(n<=e)){e=n;for(let o=a.length-1;o>=0;o-=1){const r=a[o];r&&r.threshold<=e&&(r.resolve(),a.splice(o,1))}}},await:n=>n<=e?Promise.resolve():new Promise(o=>{a.push({resolve:o,threshold:n})}),passed:n=>n<=e,waiting:()=>a.length}},M=3e3;let I=!1;const N=e=>{I||(I=!0,console.warn(`[@lunora/db] released an optimistic overlay via the ${String(e.waitedMs)}ms checkpoint fallback: the server confirmed ${e.kind} ${String(e.watermark)} but no sync frame ever echoed it. A dropped shape poke or \`settled\` frame is the usual cause — inspect the subscription rather than raising \`fallbackMs\`. (Reported once per process.)`))},S=(e={})=>{const a=e.fallbackMs??M,n=e.onFallback??N,o=v(),r=v();let s=0;const c=new Set,h=(i,t)=>{for(const d of c)d.kind===i&&d.threshold<=t&&(clearTimeout(d.handle),c.delete(d))},l=(i,t)=>{if(a<=0)return;const d=i==="checkpoint"?o:r;if(d.passed(t))return;for(const u of c)if(u.kind===i&&u.threshold>=t)return;const p={handle:setTimeout(()=>{if(c.delete(p),!d.passed(t)){s+=1,d.advance(t);try{n({kind:i,waitedMs:a,watermark:t})}catch{}}},a),kind:i,threshold:t};c.add(p)};return{acknowledge:({checkpoint:i,mutationId:t})=>{i!==void 0&&l("checkpoint",i),t!==void 0&&l("mutationId",t)},awaitCheckpoint:i=>o.await(i),awaitMutationId:i=>r.await(i),resolve:({checkpoint:i,mutationId:t})=>{i!==void 0&&(o.advance(i),h("checkpoint",i)),t!==void 0&&(r.advance(t),h("mutationId",t))},stats:()=>({fallbacks:s,pendingCheckpointWaiters:o.waiting(),pendingMutationWaiters:r.waiting()})}},m=new WeakMap,T=(e,a,n)=>{let o=m.get(e);o||(o=new Map,m.set(e,o));const r=a??"",s=o.get(r);if(s)return s;const c=S(n);return o.set(r,c),c},g=new WeakSet,E=e=>{g.add(e)},R=e=>g.has(e),x=e=>{const a=m.get(e);if(a){for(const n of a.values())n.resolve({checkpoint:Number.POSITIVE_INFINITY,mutationId:Number.POSITIVE_INFINITY});m.delete(e)}},F=e=>{const a=m.get(e),n={};if(a)for(const[o,r]of a)n[o]=r.stats();return n},O=e=>{if(e.list===void 0==(e.shape===void 0))throw new y("INTERNAL","lunoraCollectionOptions: pass exactly one of `list` or `shape`");const a=e.getKey??(t=>t._id),n=e.checkpoints??T(e.client,e.shape?.shardKey??e.shardKey);E(n);const o=new Map;let r,s,c,h;const l=(t,d)=>{const p=k=>{r?.(b(k,a)),d?.(),e.shape===void 0&&n.resolve({mutationId:e.client.confirmedMutationWatermark(e.shardKey)})},u=k=>c?.(k),f=k=>{n.resolve(k)};return e.shape!==void 0?e.client.subscribeShape({args:t,name:e.shape.name},p,{onCheckpoint:f,onError:u,shardKey:e.shape.shardKey}):e.client.subscribe(e.list,t,p,{onCheckpoint:f,onError:u,shardKey:e.shardKey})},i={autoIndex:"eager",defaultIndexType:w,getKey:a,id:e.id??e.list?.__lunoraRef??`shape:${e.shape?.name??""}`,...e.load==="eager"?{startSync:!0}:{},sync:{sync:t=>(r=C(o,t),c=d=>{t.markReady(),e.onError?.(d)},e.scopeBy===void 0?s=l(e.shape?.args??{},()=>{t.markReady()}):(t.markReady(),h!==void 0&&(s=l(h,void 0))),()=>{r=void 0,c=void 0,s?.(),s=void 0,o.clear()})}};return{checkpoints:n,config:i,scope:t=>{e.scopeBy!==void 0&&(h=t,s?.(),s=void 0,r?.(new Map),t!==void 0&&r!==void 0&&(s=l(t,void 0)))}}};export{M as CHECKPOINT_FALLBACK_MS,S as createCheckpointRegistry,T as getShardCheckpoints,R as hasCheckpointsAttached,O as lunoraCollectionOptions,E as markCheckpointsAttached,x as releaseShardCheckpoints,F as shardCheckpointStats};
|
|
@@ -0,0 +1 @@
|
|
|
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-CtPxAU9J.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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createCollection as f,safeRandomUUID as m}from"@tanstack/db";import{NonRetriableError as y}from"@tanstack/offline-transactions";const p=1e3,d="__lunora_outbox__",l=new WeakMap,w=()=>f({getKey:e=>e._id,id:`${d}:${m()}`,startSync:!0,sync:{sync:e=>(e.markReady(),()=>{})}}),x=(e,t)=>{l.set(e,t)};let u=!1;const g=(e,t={})=>{const r=t.maxItems??1e3,n=t.mutationFnName??d;return{enqueue(o){if(e.getPendingCount()>=r){const c=new Error("offline outbox is full");return c.code="OFFLINE_QUEUE_OVERFLOW",Promise.reject(c)}const i={args:o.args,clientId:o.clientId,functionPath:o.functionPath,idempotencyKey:o.idempotencyKey,identity:o.identity,mutationId:o.mutationId,shardKey:o.shardKey},a=l.get(e);!a&&!u&&(u=!0,console.warn("[@lunora/db] createExecutorOutboxSink: no outbox carrier is registered for this executor. On a TanStack OfflineExecutor a zero-mutation transaction is silently dropped, so offline writes may be lost — wire the sink to the executor returned by defineCollections()."));const s=e.createOfflineTransaction({autoCommit:!1,idempotencyKey:o.idempotencyKey,metadata:i,mutationFnName:n});return s.mutate(()=>{a?.insert({_id:o.idempotencyKey})}),s.commit?.().catch(()=>{}),Promise.resolve()}}},h=(e,t)=>{const r=new Map;for(const n of e)r.set(t(n),n);return r},E=(e,t)=>r=>{t.begin();const n=new Map;for(const[o,i]of r){const a=JSON.stringify(i);n.set(o,a),e.has(o)?e.get(o)!==a&&t.write({type:"update",value:i}):t.write({type:"insert",value:i})}for(const o of e.keys())r.has(o)||t.write({key:o,type:"delete"});t.commit(),e.clear();for(const[o,i]of n)e.set(o,i)},_=async e=>{try{await e()}catch(t){if(typeof t.code=="string"){const r=new y(t instanceof Error?t.message:String(t));throw r.code=t.code,r}throw t}},I=()=>{const e=new Set;return{dispose:()=>{for(const t of e)clearInterval(t);e.clear()},isOnline:()=>!0,notifyOnline:()=>{},subscribe:t=>{const r=setInterval(t,p);return e.add(r),()=>{clearInterval(r),e.delete(r)}}}};export{d as OUTBOX_MUTATION_FN_NAME,g as createExecutorOutboxSink,I as createOptimisticOnlineDetector,w as createOutboxCarrier,E as makeDiffEmit,x as registerOutboxCarrier,_ as runOutboxMutation,h as toMap};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const l=t=>({deletes:t.deletes??[],inserts:t.inserts??[],patches:t.patches??[]}),r=(t,o)=>{const{deletes:n,inserts:i,patches:a}=l(o);for(const e of n)t[e.table]?.delete(e.id);for(const e of a){const s=t[e.table];s&&s.update(e.id,c=>{Object.assign(c,e.fields)})}for(const e of i){const s=t[e.table];if(s){if(typeof e.row._id!="string")throw new TypeError(`applyPlanToCollections: insert into "${e.table}" needs an "_id" — mint it client-side so the optimistic row keys match the persisted one`);s.insert(e.row)}}},d=async(t,o)=>{const{deletes:n,inserts:i,patches:a}=l(o);for(const e of n)await t.delete(e.id);for(const e of a)await t.patch(e.id,e.fields);for(const e of i){const{_id:s,...c}=e.row;await t.insert(e.table,c,...typeof s=="string"?[{clientId:s}]:[])}};export{r as applyPlanToCollections,d as applyPlanToDb};
|
|
@@ -130,6 +130,48 @@ declare const runOutboxMutation: (mutate: () => Promise<unknown>) => Promise<voi
|
|
|
130
130
|
* should read `navigator.onLine` itself, separately from this detector.
|
|
131
131
|
*/
|
|
132
132
|
declare const createOptimisticOnlineDetector: () => OnlineDetector;
|
|
133
|
+
/** A watermark pair — the two monotonic lines a checkpoint registry gates on. */
|
|
134
|
+
interface CheckpointWatermark {
|
|
135
|
+
/** Op-log cursor the server has durably applied. */
|
|
136
|
+
checkpoint?: number;
|
|
137
|
+
/** Highest `clientSeq` the server has echoed back for this client. */
|
|
138
|
+
mutationId?: number;
|
|
139
|
+
}
|
|
140
|
+
/** Reported when the fallback releases an overlay the sync stream never confirmed. */
|
|
141
|
+
interface CheckpointFallbackEvent {
|
|
142
|
+
/** Which gate released. */
|
|
143
|
+
kind: "checkpoint" | "mutationId";
|
|
144
|
+
/** How long the release waited past the server acknowledgement, in ms. */
|
|
145
|
+
waitedMs: number;
|
|
146
|
+
/** The watermark that was acknowledged but never confirmed by a sync frame. */
|
|
147
|
+
watermark: number;
|
|
148
|
+
}
|
|
149
|
+
/** Counters for {@link CheckpointRegistry.stats} — feeds a debug/diagnostics surface. */
|
|
150
|
+
interface CheckpointRegistryStats {
|
|
151
|
+
/** How many times the fallback timer released an overlay (a non-zero value means sync frames are being lost). */
|
|
152
|
+
fallbacks: number;
|
|
153
|
+
/** Overlays currently waiting on a checkpoint cursor. */
|
|
154
|
+
pendingCheckpointWaiters: number;
|
|
155
|
+
/** Overlays currently waiting on a mutation id. */
|
|
156
|
+
pendingMutationWaiters: number;
|
|
157
|
+
}
|
|
158
|
+
/** Tuning for {@link createCheckpointRegistry}. */
|
|
159
|
+
interface CheckpointRegistryOptions {
|
|
160
|
+
/**
|
|
161
|
+
* How long an {@link CheckpointRegistry.acknowledge}d watermark waits for the
|
|
162
|
+
* authoritative sync frame before the overlay is released anyway. Default 3000.
|
|
163
|
+
* `0` disables the fallback (an overlay then waits forever for the frame — the
|
|
164
|
+
* pre-fallback behavior, which hangs on a dropped poke).
|
|
165
|
+
*/
|
|
166
|
+
fallbackMs?: number;
|
|
167
|
+
/**
|
|
168
|
+
* Notified each time the fallback fires. A fallback is never *correct* — it
|
|
169
|
+
* means a poke or `settled` frame that should have confirmed the write never
|
|
170
|
+
* arrived — so this is the hook for a warning or a metric. Defaults to a
|
|
171
|
+
* one-shot `console.warn`.
|
|
172
|
+
*/
|
|
173
|
+
onFallback?: (event: CheckpointFallbackEvent) => void;
|
|
174
|
+
}
|
|
133
175
|
/**
|
|
134
176
|
* Resolves the TanStack optimistic-overlay drop against the server's confirmed
|
|
135
177
|
* watermarks. A mutator's optimistic transaction returns `awaitMutationId(id)`
|
|
@@ -137,22 +179,83 @@ declare const createOptimisticOnlineDetector: () => OnlineDetector;
|
|
|
137
179
|
* settles, so the row de-duplicates exactly as the synced server value lands — no
|
|
138
180
|
* flash of the optimistic row disappearing then reappearing.
|
|
139
181
|
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
182
|
+
* Two inputs, deliberately distinct:
|
|
183
|
+
*
|
|
184
|
+
* - {@link resolve} is the **authoritative** advance, called by whoever owns the
|
|
185
|
+
* watermark stream — a `data`/`delta` frame's `lastMutationId`, or a shape poke's
|
|
186
|
+
* `checkpoint`. The synced rows have landed, so gates open immediately.
|
|
187
|
+
* - {@link acknowledge} is the **provisional** advance, called when the server has
|
|
188
|
+
* accepted the write (the mutator RPC ack) but the matching rows have not
|
|
189
|
+
* necessarily been delivered yet. Releasing here would drop the overlay before
|
|
190
|
+
* the synced row exists — a visible flicker — so instead it arms a bounded
|
|
191
|
+
* fallback. If the authoritative frame lands first the fallback is cancelled;
|
|
192
|
+
* if it never lands, the overlay is released after `fallbackMs` and the event is
|
|
193
|
+
* reported rather than hanging forever.
|
|
194
|
+
*
|
|
195
|
+
* That pairing is why a lost poke degrades to a late overlay drop instead of a
|
|
196
|
+
* permanently stuck `isPersisted` promise.
|
|
142
197
|
*/
|
|
143
198
|
interface CheckpointRegistry {
|
|
199
|
+
/**
|
|
200
|
+
* Record a server-accepted watermark whose rows may not have synced yet: arms
|
|
201
|
+
* the bounded fallback described on {@link CheckpointRegistry}. Safe to call
|
|
202
|
+
* repeatedly; a watermark already passed is a no-op.
|
|
203
|
+
*/
|
|
204
|
+
acknowledge: (watermark: CheckpointWatermark) => void;
|
|
144
205
|
/** Resolve once the server has acknowledged the op-log `cursor`. */
|
|
145
206
|
awaitCheckpoint: (cursor: number) => Promise<void>;
|
|
146
207
|
/** Resolve once the server has echoed a `lastMutationId >= id` for this client. */
|
|
147
208
|
awaitMutationId: (id: number) => Promise<void>;
|
|
148
|
-
/** Advance the gates from a frame's watermark; later callers past the mark settle immediately. */
|
|
149
|
-
resolve: (watermark:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}) => void;
|
|
209
|
+
/** Advance the gates from a sync frame's watermark; later callers past the mark settle immediately. */
|
|
210
|
+
resolve: (watermark: CheckpointWatermark) => void;
|
|
211
|
+
/** Diagnostics counters — notably how often the fallback had to fire. */
|
|
212
|
+
stats: () => CheckpointRegistryStats;
|
|
153
213
|
}
|
|
154
|
-
/**
|
|
155
|
-
declare const
|
|
214
|
+
/** Default fallback window: long enough that a slow-but-arriving poke wins, short enough that a UI isn't visibly stuck. */
|
|
215
|
+
declare const CHECKPOINT_FALLBACK_MS = 3e3;
|
|
216
|
+
/**
|
|
217
|
+
* A standalone checkpoint/mutation-id registry. Prefer {@link getShardCheckpoints}
|
|
218
|
+
* unless you are wiring a bespoke watermark stream — a registry must be shared by
|
|
219
|
+
* every collection on a shard (see that function for why).
|
|
220
|
+
*/
|
|
221
|
+
declare const createCheckpointRegistry: (options?: CheckpointRegistryOptions) => CheckpointRegistry;
|
|
222
|
+
/**
|
|
223
|
+
* The shared checkpoint registry for `client` + `shardKey` — created on first use.
|
|
224
|
+
* This is the registry {@link lunoraCollectionOptions} and
|
|
225
|
+
* {@link import("./define-mutators").bindMutators} default to, which is what makes
|
|
226
|
+
* a multi-collection shard work without the caller relaying pokes between
|
|
227
|
+
* registries by hand.
|
|
228
|
+
*
|
|
229
|
+
* `options` applies **only when the registry is created**. Because the point is that
|
|
230
|
+
* every collection and mutator on a shard shares one gate, a later call cannot
|
|
231
|
+
* retune an existing registry — it returns the existing one and `options` is ignored.
|
|
232
|
+
* To control `fallbackMs` / `onFallback`, build the registry yourself with
|
|
233
|
+
* {@link createCheckpointRegistry} and pass it explicitly to every
|
|
234
|
+
* `lunoraCollectionOptions` and `bindMutators` call for that shard.
|
|
235
|
+
*/
|
|
236
|
+
declare const getShardCheckpoints: (client: LunoraClient, shardKey?: string, options?: CheckpointRegistryOptions) => CheckpointRegistry;
|
|
237
|
+
/**
|
|
238
|
+
* Release every pending overlay gate for `client` and drop its shard registries.
|
|
239
|
+
*
|
|
240
|
+
* The hot-reload / teardown escape hatch. When a module that owns collections and
|
|
241
|
+
* mutators is replaced — a Vite HMR update, a sign-out that rebuilds the data layer
|
|
242
|
+
* — the *old* bindings may still have transactions parked in `awaitMutationId`. The
|
|
243
|
+
* subscriptions that would have resolved them are gone with the old module, so
|
|
244
|
+
* without this those promises never settle and every one of their
|
|
245
|
+
* `transaction.isPersisted` waiters hangs forever.
|
|
246
|
+
*
|
|
247
|
+
* Resolving to `Infinity` settles the parked waiters (the writes were already sent;
|
|
248
|
+
* the server is authoritative regardless), and dropping the registries means the
|
|
249
|
+
* replacement module's bindings start from a clean per-shard gate.
|
|
250
|
+
*
|
|
251
|
+
* ```ts
|
|
252
|
+
* // In the module that owns the data layer:
|
|
253
|
+
* import.meta.hot?.dispose(() => releaseShardCheckpoints(client));
|
|
254
|
+
* ```
|
|
255
|
+
*/
|
|
256
|
+
declare const releaseShardCheckpoints: (client: LunoraClient) => void;
|
|
257
|
+
/** Every live registry for `client`, keyed by shard (`""` = unsharded) — for a debug surface. */
|
|
258
|
+
declare const shardCheckpointStats: (client: LunoraClient) => Record<string, CheckpointRegistryStats>;
|
|
156
259
|
/**
|
|
157
260
|
* A replication-shape sync source (the local-first partial-replication path).
|
|
158
261
|
* Mutually exclusive with {@link LunoraCollectionConfig.list}: the collection
|
|
@@ -169,6 +272,13 @@ interface ShapeSource {
|
|
|
169
272
|
}
|
|
170
273
|
/** Declarative inputs for {@link lunoraCollectionOptions}. */
|
|
171
274
|
interface LunoraCollectionConfig<TRow extends Row> {
|
|
275
|
+
/**
|
|
276
|
+
* The registry optimistic overlays are gated on. Defaults to the shared
|
|
277
|
+
* per-shard registry ({@link getShardCheckpoints}), which is what a
|
|
278
|
+
* multi-collection shard needs — pass one explicitly only to isolate a
|
|
279
|
+
* collection's gate (tests) or to supply custom {@link CheckpointRegistryOptions}.
|
|
280
|
+
*/
|
|
281
|
+
checkpoints?: CheckpointRegistry;
|
|
172
282
|
/** The Lunora client to subscribe through. */
|
|
173
283
|
client: LunoraClient;
|
|
174
284
|
/** Row key extractor — defaults to `row._id`. */
|
|
@@ -224,4 +334,4 @@ interface LunoraCollectionOptions<TRow extends Row> {
|
|
|
224
334
|
* overlays against confirmed server watermarks (see {@link CheckpointRegistry}).
|
|
225
335
|
*/
|
|
226
336
|
declare const lunoraCollectionOptions: <TRow extends Row>(options: LunoraCollectionConfig<TRow>) => LunoraCollectionOptions<TRow>;
|
|
227
|
-
export { CheckpointRegistry as C, ExecutorOutboxSinkOptions as E, LunoraCollectionConfig as L, OUTBOX_MUTATION_FN_NAME as O, Row as R, SyncWriter as S, LunoraCollectionOptions as a,
|
|
337
|
+
export { CheckpointRegistry as C, ExecutorOutboxSinkOptions as E, LunoraCollectionConfig as L, OUTBOX_MUTATION_FN_NAME as O, Row as R, SyncWriter as S, LunoraCollectionOptions as a, CHECKPOINT_FALLBACK_MS as b, createCheckpointRegistry as c, CheckpointFallbackEvent as d, CheckpointRegistryOptions as e, CheckpointRegistryStats as f, CheckpointWatermark as g, OutboxExecutor as h, OutboxMutationMetadata as i, createExecutorOutboxSink as j, createOptimisticOnlineDetector as k, lunoraCollectionOptions as l, getShardCheckpoints as m, makeDiffEmit as n, runOutboxMutation as o, releaseShardCheckpoints as r, shardCheckpointStats as s, toMap as t };
|
package/dist/packem_shared/{collection-options.d-BPVkj5FW.d.ts → collection-options.d-C03Rjjxn.d.ts}
RENAMED
|
@@ -130,6 +130,48 @@ declare const runOutboxMutation: (mutate: () => Promise<unknown>) => Promise<voi
|
|
|
130
130
|
* should read `navigator.onLine` itself, separately from this detector.
|
|
131
131
|
*/
|
|
132
132
|
declare const createOptimisticOnlineDetector: () => OnlineDetector;
|
|
133
|
+
/** A watermark pair — the two monotonic lines a checkpoint registry gates on. */
|
|
134
|
+
interface CheckpointWatermark {
|
|
135
|
+
/** Op-log cursor the server has durably applied. */
|
|
136
|
+
checkpoint?: number;
|
|
137
|
+
/** Highest `clientSeq` the server has echoed back for this client. */
|
|
138
|
+
mutationId?: number;
|
|
139
|
+
}
|
|
140
|
+
/** Reported when the fallback releases an overlay the sync stream never confirmed. */
|
|
141
|
+
interface CheckpointFallbackEvent {
|
|
142
|
+
/** Which gate released. */
|
|
143
|
+
kind: "checkpoint" | "mutationId";
|
|
144
|
+
/** How long the release waited past the server acknowledgement, in ms. */
|
|
145
|
+
waitedMs: number;
|
|
146
|
+
/** The watermark that was acknowledged but never confirmed by a sync frame. */
|
|
147
|
+
watermark: number;
|
|
148
|
+
}
|
|
149
|
+
/** Counters for {@link CheckpointRegistry.stats} — feeds a debug/diagnostics surface. */
|
|
150
|
+
interface CheckpointRegistryStats {
|
|
151
|
+
/** How many times the fallback timer released an overlay (a non-zero value means sync frames are being lost). */
|
|
152
|
+
fallbacks: number;
|
|
153
|
+
/** Overlays currently waiting on a checkpoint cursor. */
|
|
154
|
+
pendingCheckpointWaiters: number;
|
|
155
|
+
/** Overlays currently waiting on a mutation id. */
|
|
156
|
+
pendingMutationWaiters: number;
|
|
157
|
+
}
|
|
158
|
+
/** Tuning for {@link createCheckpointRegistry}. */
|
|
159
|
+
interface CheckpointRegistryOptions {
|
|
160
|
+
/**
|
|
161
|
+
* How long an {@link CheckpointRegistry.acknowledge}d watermark waits for the
|
|
162
|
+
* authoritative sync frame before the overlay is released anyway. Default 3000.
|
|
163
|
+
* `0` disables the fallback (an overlay then waits forever for the frame — the
|
|
164
|
+
* pre-fallback behavior, which hangs on a dropped poke).
|
|
165
|
+
*/
|
|
166
|
+
fallbackMs?: number;
|
|
167
|
+
/**
|
|
168
|
+
* Notified each time the fallback fires. A fallback is never *correct* — it
|
|
169
|
+
* means a poke or `settled` frame that should have confirmed the write never
|
|
170
|
+
* arrived — so this is the hook for a warning or a metric. Defaults to a
|
|
171
|
+
* one-shot `console.warn`.
|
|
172
|
+
*/
|
|
173
|
+
onFallback?: (event: CheckpointFallbackEvent) => void;
|
|
174
|
+
}
|
|
133
175
|
/**
|
|
134
176
|
* Resolves the TanStack optimistic-overlay drop against the server's confirmed
|
|
135
177
|
* watermarks. A mutator's optimistic transaction returns `awaitMutationId(id)`
|
|
@@ -137,22 +179,83 @@ declare const createOptimisticOnlineDetector: () => OnlineDetector;
|
|
|
137
179
|
* settles, so the row de-duplicates exactly as the synced server value lands — no
|
|
138
180
|
* flash of the optimistic row disappearing then reappearing.
|
|
139
181
|
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
182
|
+
* Two inputs, deliberately distinct:
|
|
183
|
+
*
|
|
184
|
+
* - {@link resolve} is the **authoritative** advance, called by whoever owns the
|
|
185
|
+
* watermark stream — a `data`/`delta` frame's `lastMutationId`, or a shape poke's
|
|
186
|
+
* `checkpoint`. The synced rows have landed, so gates open immediately.
|
|
187
|
+
* - {@link acknowledge} is the **provisional** advance, called when the server has
|
|
188
|
+
* accepted the write (the mutator RPC ack) but the matching rows have not
|
|
189
|
+
* necessarily been delivered yet. Releasing here would drop the overlay before
|
|
190
|
+
* the synced row exists — a visible flicker — so instead it arms a bounded
|
|
191
|
+
* fallback. If the authoritative frame lands first the fallback is cancelled;
|
|
192
|
+
* if it never lands, the overlay is released after `fallbackMs` and the event is
|
|
193
|
+
* reported rather than hanging forever.
|
|
194
|
+
*
|
|
195
|
+
* That pairing is why a lost poke degrades to a late overlay drop instead of a
|
|
196
|
+
* permanently stuck `isPersisted` promise.
|
|
142
197
|
*/
|
|
143
198
|
interface CheckpointRegistry {
|
|
199
|
+
/**
|
|
200
|
+
* Record a server-accepted watermark whose rows may not have synced yet: arms
|
|
201
|
+
* the bounded fallback described on {@link CheckpointRegistry}. Safe to call
|
|
202
|
+
* repeatedly; a watermark already passed is a no-op.
|
|
203
|
+
*/
|
|
204
|
+
acknowledge: (watermark: CheckpointWatermark) => void;
|
|
144
205
|
/** Resolve once the server has acknowledged the op-log `cursor`. */
|
|
145
206
|
awaitCheckpoint: (cursor: number) => Promise<void>;
|
|
146
207
|
/** Resolve once the server has echoed a `lastMutationId >= id` for this client. */
|
|
147
208
|
awaitMutationId: (id: number) => Promise<void>;
|
|
148
|
-
/** Advance the gates from a frame's watermark; later callers past the mark settle immediately. */
|
|
149
|
-
resolve: (watermark:
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}) => void;
|
|
209
|
+
/** Advance the gates from a sync frame's watermark; later callers past the mark settle immediately. */
|
|
210
|
+
resolve: (watermark: CheckpointWatermark) => void;
|
|
211
|
+
/** Diagnostics counters — notably how often the fallback had to fire. */
|
|
212
|
+
stats: () => CheckpointRegistryStats;
|
|
153
213
|
}
|
|
154
|
-
/**
|
|
155
|
-
declare const
|
|
214
|
+
/** Default fallback window: long enough that a slow-but-arriving poke wins, short enough that a UI isn't visibly stuck. */
|
|
215
|
+
declare const CHECKPOINT_FALLBACK_MS = 3e3;
|
|
216
|
+
/**
|
|
217
|
+
* A standalone checkpoint/mutation-id registry. Prefer {@link getShardCheckpoints}
|
|
218
|
+
* unless you are wiring a bespoke watermark stream — a registry must be shared by
|
|
219
|
+
* every collection on a shard (see that function for why).
|
|
220
|
+
*/
|
|
221
|
+
declare const createCheckpointRegistry: (options?: CheckpointRegistryOptions) => CheckpointRegistry;
|
|
222
|
+
/**
|
|
223
|
+
* The shared checkpoint registry for `client` + `shardKey` — created on first use.
|
|
224
|
+
* This is the registry {@link lunoraCollectionOptions} and
|
|
225
|
+
* {@link import("./define-mutators").bindMutators} default to, which is what makes
|
|
226
|
+
* a multi-collection shard work without the caller relaying pokes between
|
|
227
|
+
* registries by hand.
|
|
228
|
+
*
|
|
229
|
+
* `options` applies **only when the registry is created**. Because the point is that
|
|
230
|
+
* every collection and mutator on a shard shares one gate, a later call cannot
|
|
231
|
+
* retune an existing registry — it returns the existing one and `options` is ignored.
|
|
232
|
+
* To control `fallbackMs` / `onFallback`, build the registry yourself with
|
|
233
|
+
* {@link createCheckpointRegistry} and pass it explicitly to every
|
|
234
|
+
* `lunoraCollectionOptions` and `bindMutators` call for that shard.
|
|
235
|
+
*/
|
|
236
|
+
declare const getShardCheckpoints: (client: LunoraClient, shardKey?: string, options?: CheckpointRegistryOptions) => CheckpointRegistry;
|
|
237
|
+
/**
|
|
238
|
+
* Release every pending overlay gate for `client` and drop its shard registries.
|
|
239
|
+
*
|
|
240
|
+
* The hot-reload / teardown escape hatch. When a module that owns collections and
|
|
241
|
+
* mutators is replaced — a Vite HMR update, a sign-out that rebuilds the data layer
|
|
242
|
+
* — the *old* bindings may still have transactions parked in `awaitMutationId`. The
|
|
243
|
+
* subscriptions that would have resolved them are gone with the old module, so
|
|
244
|
+
* without this those promises never settle and every one of their
|
|
245
|
+
* `transaction.isPersisted` waiters hangs forever.
|
|
246
|
+
*
|
|
247
|
+
* Resolving to `Infinity` settles the parked waiters (the writes were already sent;
|
|
248
|
+
* the server is authoritative regardless), and dropping the registries means the
|
|
249
|
+
* replacement module's bindings start from a clean per-shard gate.
|
|
250
|
+
*
|
|
251
|
+
* ```ts
|
|
252
|
+
* // In the module that owns the data layer:
|
|
253
|
+
* import.meta.hot?.dispose(() => releaseShardCheckpoints(client));
|
|
254
|
+
* ```
|
|
255
|
+
*/
|
|
256
|
+
declare const releaseShardCheckpoints: (client: LunoraClient) => void;
|
|
257
|
+
/** Every live registry for `client`, keyed by shard (`""` = unsharded) — for a debug surface. */
|
|
258
|
+
declare const shardCheckpointStats: (client: LunoraClient) => Record<string, CheckpointRegistryStats>;
|
|
156
259
|
/**
|
|
157
260
|
* A replication-shape sync source (the local-first partial-replication path).
|
|
158
261
|
* Mutually exclusive with {@link LunoraCollectionConfig.list}: the collection
|
|
@@ -169,6 +272,13 @@ interface ShapeSource {
|
|
|
169
272
|
}
|
|
170
273
|
/** Declarative inputs for {@link lunoraCollectionOptions}. */
|
|
171
274
|
interface LunoraCollectionConfig<TRow extends Row> {
|
|
275
|
+
/**
|
|
276
|
+
* The registry optimistic overlays are gated on. Defaults to the shared
|
|
277
|
+
* per-shard registry ({@link getShardCheckpoints}), which is what a
|
|
278
|
+
* multi-collection shard needs — pass one explicitly only to isolate a
|
|
279
|
+
* collection's gate (tests) or to supply custom {@link CheckpointRegistryOptions}.
|
|
280
|
+
*/
|
|
281
|
+
checkpoints?: CheckpointRegistry;
|
|
172
282
|
/** The Lunora client to subscribe through. */
|
|
173
283
|
client: LunoraClient;
|
|
174
284
|
/** Row key extractor — defaults to `row._id`. */
|
|
@@ -224,4 +334,4 @@ interface LunoraCollectionOptions<TRow extends Row> {
|
|
|
224
334
|
* overlays against confirmed server watermarks (see {@link CheckpointRegistry}).
|
|
225
335
|
*/
|
|
226
336
|
declare const lunoraCollectionOptions: <TRow extends Row>(options: LunoraCollectionConfig<TRow>) => LunoraCollectionOptions<TRow>;
|
|
227
|
-
export { CheckpointRegistry as C, ExecutorOutboxSinkOptions as E, LunoraCollectionConfig as L, OUTBOX_MUTATION_FN_NAME as O, Row as R, SyncWriter as S, LunoraCollectionOptions as a,
|
|
337
|
+
export { CheckpointRegistry as C, ExecutorOutboxSinkOptions as E, LunoraCollectionConfig as L, OUTBOX_MUTATION_FN_NAME as O, Row as R, SyncWriter as S, LunoraCollectionOptions as a, CHECKPOINT_FALLBACK_MS as b, createCheckpointRegistry as c, CheckpointFallbackEvent as d, CheckpointRegistryOptions as e, CheckpointRegistryStats as f, CheckpointWatermark as g, OutboxExecutor as h, OutboxMutationMetadata as i, createExecutorOutboxSink as j, createOptimisticOnlineDetector as k, lunoraCollectionOptions as l, getShardCheckpoints as m, makeDiffEmit as n, runOutboxMutation as o, releaseShardCheckpoints as r, shardCheckpointStats as s, toMap as t };
|
package/dist/packem_shared/{define-collections.d-Dkzt5xPY.d.ts → define-collections.d-Ds-5C0M9.d.ts}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FunctionReference, SubscriptionError, LunoraClient } from '@lunora/client';
|
|
2
2
|
import { Transaction, Collection } from '@tanstack/db';
|
|
3
3
|
import { OfflineExecutor, StorageDiagnostic } from '@tanstack/offline-transactions';
|
|
4
|
-
import { R as Row } from "./collection-options.d-
|
|
4
|
+
import { R as Row } from "./collection-options.d-C03Rjjxn.js";
|
|
5
5
|
/** Element type of an array (the row type a `list` query returns). */
|
|
6
6
|
type Element<T> = T extends ReadonlyArray<infer E> ? E : never;
|
|
7
7
|
/** `true` for the `any` type, `false` otherwise. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { FunctionReference, SubscriptionError, LunoraClient } from '@lunora/client';
|
|
2
2
|
import { Transaction, Collection } from '@tanstack/db';
|
|
3
3
|
import { OfflineExecutor, StorageDiagnostic } from '@tanstack/offline-transactions';
|
|
4
|
-
import { R as Row } from "./collection-options.d-
|
|
4
|
+
import { R as Row } from "./collection-options.d-C03Rjjxn.mjs";
|
|
5
5
|
/** Element type of an array (the row type a `list` query returns). */
|
|
6
6
|
type Element<T> = T extends ReadonlyArray<infer E> ? E : never;
|
|
7
7
|
/** `true` for the `any` type, `false` otherwise. */
|