@spooky-sync/core 0.0.1-canary.154 → 0.0.1-canary.155
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +151 -0
- package/dist/index.js +1509 -120
- package/dist/{sqlite-plan-sql.js → sqlite-open.js} +83 -1
- package/dist/sqlite-worker.js +268 -108
- package/dist/tabs-broker-worker.d.ts +8 -0
- package/dist/tabs-broker-worker.js +434 -0
- package/dist/types.d.ts +25 -0
- package/package.json +4 -4
- package/scripts/check-broker-bundle.mjs +33 -0
- package/src/modules/cache/index.ts +35 -0
- package/src/modules/data/index.ts +13 -3
- package/src/modules/data/mutation-id.test.ts +25 -0
- package/src/modules/data/mutation-id.ts +35 -0
- package/src/modules/devtools/index.ts +9 -0
- package/src/modules/sync/queue/queue-up.forwarded.test.ts +51 -0
- package/src/modules/sync/queue/queue-up.ts +74 -38
- package/src/modules/sync/scheduler.ts +4 -2
- package/src/modules/sync/sync.ts +186 -8
- package/src/services/database/engine-factory.ts +3 -2
- package/src/services/database/sqlite-cache-engine.test.ts +160 -103
- package/src/services/database/sqlite-cache-engine.ts +285 -72
- package/src/services/database/sqlite-open.ts +36 -1
- package/src/services/database/sqlite-select.test.ts +13 -46
- package/src/services/database/sqlite-transport.fixture.ts +30 -0
- package/src/services/database/sqlite-transport.ts +219 -0
- package/src/services/database/sqlite-worker.ts +340 -55
- package/src/services/stream-processor/index.ts +11 -2
- package/src/services/tabs/broker-client.ts +283 -0
- package/src/services/tabs/broker.test.ts +278 -0
- package/src/services/tabs/coordinator.test.ts +192 -0
- package/src/services/tabs/coordinator.ts +567 -0
- package/src/services/tabs/fake-ports.fixture.ts +70 -0
- package/src/services/tabs/leader-locks.ts +75 -0
- package/src/services/tabs/protocol.ts +239 -0
- package/src/services/tabs/support.ts +36 -0
- package/src/services/tabs/tabs-broker-worker.ts +581 -0
- package/src/sp00ky.ts +164 -6
- package/src/types.ts +25 -0
- package/tsdown.config.ts +19 -10
package/dist/index.d.ts
CHANGED
|
@@ -139,6 +139,7 @@ declare class StreamProcessorService {
|
|
|
139
139
|
private sessionAuth;
|
|
140
140
|
private stateKeySuffix;
|
|
141
141
|
private stateGeneration;
|
|
142
|
+
private persistState;
|
|
142
143
|
constructor(events: EventSystem<StreamProcessorEvents>, db: LocalStore, persistenceClient: PersistenceClient, logger: Logger);
|
|
143
144
|
/**
|
|
144
145
|
* Add a receiver for stream updates.
|
|
@@ -199,6 +200,8 @@ declare class StreamProcessorService {
|
|
|
199
200
|
* afterwards (a fresh circuit default-denies every table).
|
|
200
201
|
*/
|
|
201
202
|
reset(): Promise<void>;
|
|
203
|
+
/** Toggle circuit-state persistence (shared-tabs follower/leader role). */
|
|
204
|
+
setPersistenceEnabled(enabled: boolean): void;
|
|
202
205
|
loadState(): Promise<void>;
|
|
203
206
|
/**
|
|
204
207
|
* Seed per-table `select` permission predicates ({ [table]: whereText }).
|
|
@@ -261,18 +264,39 @@ interface CacheRecord {
|
|
|
261
264
|
* Single responsibility: Handle all local storage operations and DBSP ingestion.
|
|
262
265
|
* This module acts as the bridge between data operations and persistence.
|
|
263
266
|
*/
|
|
267
|
+
/** One ingested change, in exactly the shape `ingestMany` consumes. Shared
|
|
268
|
+
* with the tabs protocol so a leader can relay its ingests to followers. */
|
|
269
|
+
interface CacheIngestTuple {
|
|
270
|
+
table: string;
|
|
271
|
+
op: 'CREATE' | 'UPDATE' | 'DELETE';
|
|
272
|
+
id: string;
|
|
273
|
+
record: Record<string, unknown>;
|
|
274
|
+
}
|
|
264
275
|
declare class CacheModule implements StreamUpdateReceiver {
|
|
265
276
|
private local;
|
|
266
277
|
private streamProcessor;
|
|
267
278
|
private logger;
|
|
268
279
|
private streamUpdateCallback;
|
|
269
280
|
private versionLookups;
|
|
281
|
+
/** Shared-tabs leader: fan every committed ingest out to follower circuits.
|
|
282
|
+
* Fired AFTER the local tx (the rows are already in the shared store, so a
|
|
283
|
+
* follower only needs the circuit feed). Never set on followers. */
|
|
284
|
+
private ingestRelay;
|
|
270
285
|
constructor(local: LocalStore, streamProcessor: StreamProcessorService, streamUpdateCallback: (update: StreamUpdate) => void, logger: Logger$1);
|
|
271
286
|
/**
|
|
272
287
|
* Implements StreamUpdateReceiver interface
|
|
273
288
|
* Called directly by StreamProcessor when views change
|
|
274
289
|
*/
|
|
275
290
|
onStreamUpdate(update: StreamUpdate): void;
|
|
291
|
+
setIngestRelay(cb: ((tuples: CacheIngestTuple[]) => void) | null): void;
|
|
292
|
+
/**
|
|
293
|
+
* Shared-tabs follower: feed relayed tuples into THIS tab's circuit only.
|
|
294
|
+
* The rows are already in the shared store (the leader wrote them), so no
|
|
295
|
+
* local write happens here; the normal chain then runs: SSP -> stream update
|
|
296
|
+
* -> DataModule debounce -> materializeRecords (re-reads via the port
|
|
297
|
+
* transport) -> this tab's subscriptions fire with this tab's hashes.
|
|
298
|
+
*/
|
|
299
|
+
applyRelayedIngest(tuples: CacheIngestTuple[]): void;
|
|
276
300
|
lookup(recordId: string): number;
|
|
277
301
|
/** Drop the version cache on a bucket switch — a stale version would make
|
|
278
302
|
* the sync diff skip fetching a body the new bucket legitimately needs. */
|
|
@@ -322,6 +346,9 @@ declare class DataModule<S extends SchemaStructure> {
|
|
|
322
346
|
private local;
|
|
323
347
|
private schema;
|
|
324
348
|
private streamDebounceTime;
|
|
349
|
+
/** Tab identity baked into mutation ids (shared-tabs rollback routing);
|
|
350
|
+
* undefined in solo mode, where mutation-id falls back to a session id. */
|
|
351
|
+
private tabId;
|
|
325
352
|
private activeQueries;
|
|
326
353
|
private pendingQueries;
|
|
327
354
|
private subscriptions;
|
|
@@ -367,6 +394,9 @@ declare class DataModule<S extends SchemaStructure> {
|
|
|
367
394
|
* registered queries will get fresh, session-scoped IDs.
|
|
368
395
|
*/
|
|
369
396
|
setSessionId(sessionId: string): void;
|
|
397
|
+
/** Shared-tabs: bake this tab's identity into mutation ids so a rollback of
|
|
398
|
+
* a follower's mutation routes back to the tab that made it. */
|
|
399
|
+
setTabId(tabId: string): void;
|
|
370
400
|
/**
|
|
371
401
|
* Update the authenticated user record id. Pass `null` on sign-out.
|
|
372
402
|
* Read by `Sp00kySync.listRefTable()` so the LIVE subscription and
|
|
@@ -625,6 +655,98 @@ declare class DataModule<S extends SchemaStructure> {
|
|
|
625
655
|
* Parse update options to generate push event options
|
|
626
656
|
*/
|
|
627
657
|
//#endregion
|
|
658
|
+
//#region src/services/tabs/protocol.d.ts
|
|
659
|
+
type TabId = string;
|
|
660
|
+
type TabRole = 'solo' | 'leader' | 'follower';
|
|
661
|
+
/** Broker pings every tab at this cadence. */
|
|
662
|
+
|
|
663
|
+
/** Matches `CacheIngestTuple` (modules/cache): exactly what `ingestMany`
|
|
664
|
+
* consumes, so relayed batches feed follower circuits without reshaping. */
|
|
665
|
+
interface IngestTuple {
|
|
666
|
+
table: string;
|
|
667
|
+
op: 'CREATE' | 'UPDATE' | 'DELETE';
|
|
668
|
+
id: string;
|
|
669
|
+
record: Record<string, unknown>;
|
|
670
|
+
}
|
|
671
|
+
type FollowerToLeaderMessage = {
|
|
672
|
+
type: 'sync-hello';
|
|
673
|
+
tabId: TabId;
|
|
674
|
+
}
|
|
675
|
+
/** The follower committed an outbox row (through the shared store) and the
|
|
676
|
+
* leader should drain it. Idempotent; a new leader's loadFromDatabase is
|
|
677
|
+
* the backstop for a notify lost in a failover window. */ | {
|
|
678
|
+
type: 'mutation-enqueued';
|
|
679
|
+
mutationId: string;
|
|
680
|
+
} | {
|
|
681
|
+
type: 'request-poll';
|
|
682
|
+
};
|
|
683
|
+
type LeaderToFollowerMessage = {
|
|
684
|
+
type: 'db-ready';
|
|
685
|
+
leadershipId: number;
|
|
686
|
+
bucketId: string;
|
|
687
|
+
storageHealth: StorageHealth;
|
|
688
|
+
}
|
|
689
|
+
/** Every ingest the leader's CacheModule committed, so follower circuits
|
|
690
|
+
* stay live without their own fetch. seq detects gaps. */ | {
|
|
691
|
+
type: 'ingest-relay';
|
|
692
|
+
tuples: IngestTuple[];
|
|
693
|
+
leadershipId: number;
|
|
694
|
+
seq: number;
|
|
695
|
+
}
|
|
696
|
+
/** A `_00_list_ref` LIVE event, relayed verbatim. Each follower resolves the
|
|
697
|
+
* queryId against its own DataModule and ignores foreign queries. */ | {
|
|
698
|
+
type: 'list-ref-change';
|
|
699
|
+
action: 'CREATE' | 'UPDATE' | 'DELETE';
|
|
700
|
+
queryId: string;
|
|
701
|
+
recordId: string;
|
|
702
|
+
version: number;
|
|
703
|
+
parent: boolean;
|
|
704
|
+
}
|
|
705
|
+
/** The leader's drain rolled back a mutation owned by this tab. */ | {
|
|
706
|
+
type: 'mutation-rolled-back';
|
|
707
|
+
mutationId: string;
|
|
708
|
+
recordId: string;
|
|
709
|
+
eventType: 'create' | 'update' | 'delete';
|
|
710
|
+
error: string;
|
|
711
|
+
};
|
|
712
|
+
//#endregion
|
|
713
|
+
//#region src/services/tabs/coordinator.d.ts
|
|
714
|
+
/** Leader-side fan-out surface handed to the sync layer. The sync router
|
|
715
|
+
* (modules/sync/tab-router.ts) registers itself as the message handler. */
|
|
716
|
+
declare class LeaderSyncHub {
|
|
717
|
+
readonly leadershipId: number;
|
|
718
|
+
private logger;
|
|
719
|
+
private followers;
|
|
720
|
+
private seq;
|
|
721
|
+
onFollowerMessage: ((tabId: TabId, msg: FollowerToLeaderMessage) => void) | null;
|
|
722
|
+
onFollowerDetached: ((tabId: TabId) => void) | null;
|
|
723
|
+
constructor(leadershipId: number, logger: Logger$1);
|
|
724
|
+
attach(tabId: TabId, port: MessagePort): void;
|
|
725
|
+
detach(tabId: TabId): void;
|
|
726
|
+
detachAll(): void;
|
|
727
|
+
sendTo(tabId: TabId, msg: LeaderToFollowerMessage): void;
|
|
728
|
+
broadcast(msg: LeaderToFollowerMessage, exceptTabId?: TabId): void;
|
|
729
|
+
/** Stamped ingest relay; seq lets followers detect gaps. */
|
|
730
|
+
relayIngest(tuples: IngestTuple[], exceptTabId?: TabId): void;
|
|
731
|
+
get followerCount(): number;
|
|
732
|
+
get relayedBatches(): number;
|
|
733
|
+
}
|
|
734
|
+
/** Follower half of the syncPort. Queues while detached (leaderless window)
|
|
735
|
+
* and flushes on rebind; a lost-in-flight mutation notify is additionally
|
|
736
|
+
* backstopped by the new leader reloading the shared outbox from the store. */
|
|
737
|
+
declare class SyncForwarder {
|
|
738
|
+
private tabId;
|
|
739
|
+
private port;
|
|
740
|
+
private queued;
|
|
741
|
+
onLeaderMessage: ((msg: LeaderToFollowerMessage) => void) | null;
|
|
742
|
+
constructor(tabId: TabId);
|
|
743
|
+
rebind(port: MessagePort): void;
|
|
744
|
+
unbind(): void;
|
|
745
|
+
private post;
|
|
746
|
+
mutationEnqueued(mutationId: string): void;
|
|
747
|
+
requestPoll(): void;
|
|
748
|
+
}
|
|
749
|
+
//#endregion
|
|
628
750
|
//#region src/modules/sync/sync.d.ts
|
|
629
751
|
/**
|
|
630
752
|
* Tunables for `Sp00kySync` construction.
|
|
@@ -675,6 +797,10 @@ declare class Sp00kySync<S extends SchemaStructure> {
|
|
|
675
797
|
private wasDisconnected;
|
|
676
798
|
events: SyncEventSystem;
|
|
677
799
|
private currentUserId;
|
|
800
|
+
private tabRole;
|
|
801
|
+
private tabId;
|
|
802
|
+
private hub;
|
|
803
|
+
private forwarder;
|
|
678
804
|
private refMode;
|
|
679
805
|
private readonly anonLiveEnabled;
|
|
680
806
|
private currentLiveQueryUuid;
|
|
@@ -732,6 +858,25 @@ declare class Sp00kySync<S extends SchemaStructure> {
|
|
|
732
858
|
* @throws Error if already initialized.
|
|
733
859
|
*/
|
|
734
860
|
init(): Promise<void>;
|
|
861
|
+
/** Set BEFORE init(): shapes what init boots (a follower loads no outbox and
|
|
862
|
+
* never starts LIVE; its own registration/poll paths stay untouched). */
|
|
863
|
+
setTabContext(role: 'solo' | 'leader' | 'follower', tabId: string | null): void;
|
|
864
|
+
/** Leader duties: drain the shared outbox, own the single list_ref LIVE,
|
|
865
|
+
* relay LIVE events and rollbacks to followers via `hub`. Idempotent for a
|
|
866
|
+
* boot-time leader; a runtime promotion (failover) reloads the outbox,
|
|
867
|
+
* which now holds EVERY tab's rows, and restarts LIVE under this session. */
|
|
868
|
+
promoteToLeader(hub: LeaderSyncHub): Promise<void>;
|
|
869
|
+
/** Follower duties: no outbox drain, no LIVE. Mutations forward to the
|
|
870
|
+
* leader; everything else (registration, per-query sync, poll) runs
|
|
871
|
+
* against this tab's own remote session as usual. */
|
|
872
|
+
demoteToFollower(forwarder: SyncForwarder): void;
|
|
873
|
+
/** A forwarded outbox row from a follower: load + drain it. Idempotent. */
|
|
874
|
+
enqueueForwardedMutation(mutationId: string): Promise<void>;
|
|
875
|
+
/** A relayed `_00_list_ref` LIVE event: resolve against THIS tab's queries
|
|
876
|
+
* and run the exact same handling the LIVE subscription would have. */
|
|
877
|
+
private applyRelayedListRefChange;
|
|
878
|
+
/** One immediate poll cycle (failover convergence). */
|
|
879
|
+
forcePollRound(): Promise<void>;
|
|
735
880
|
/**
|
|
736
881
|
* Quiesce all sync activity ahead of a local-bucket switch. After this
|
|
737
882
|
* resolves, nothing in the sync module writes to the local store: the poll
|
|
@@ -1248,6 +1393,10 @@ declare class Sp00kyClient<S extends SchemaStructure> {
|
|
|
1248
1393
|
private logger;
|
|
1249
1394
|
auth: AuthService<S>;
|
|
1250
1395
|
streamProcessor: StreamProcessorService;
|
|
1396
|
+
private tabsCoordinator;
|
|
1397
|
+
private sharedActive;
|
|
1398
|
+
/** Current shared-tabs role, or null when the feature is off/fell back. */
|
|
1399
|
+
get tabRole(): TabRole | null;
|
|
1251
1400
|
get remoteClient(): surrealdb0.Surreal;
|
|
1252
1401
|
get localClient(): unknown;
|
|
1253
1402
|
get pendingMutationCount(): number;
|
|
@@ -1275,6 +1424,8 @@ declare class Sp00kyClient<S extends SchemaStructure> {
|
|
|
1275
1424
|
*/
|
|
1276
1425
|
subscribeToStorageHealth(cb: (health: StorageHealth) => void): () => void;
|
|
1277
1426
|
constructor(config: Sp00kyConfig<S>);
|
|
1427
|
+
/** The shared-tabs role machinery, wired to this client's modules. */
|
|
1428
|
+
private buildTabsCoordinator;
|
|
1278
1429
|
/**
|
|
1279
1430
|
* Setup direct callbacks instead of event subscriptions
|
|
1280
1431
|
*/
|