@spooky-sync/core 0.0.1-canary.108 → 0.0.1-canary.109

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.
Files changed (33) hide show
  1. package/dist/index.d.ts +10 -114
  2. package/dist/index.js +1070 -46
  3. package/dist/sqlite-worker.d.ts +1 -0
  4. package/dist/sqlite-worker.js +107 -0
  5. package/dist/types.d.ts +157 -2
  6. package/package.json +4 -3
  7. package/src/modules/cache/index.ts +2 -2
  8. package/src/modules/crdt/crdt-field.ts +3 -3
  9. package/src/modules/crdt/crdt-hydration.test.ts +5 -5
  10. package/src/modules/crdt/index.ts +2 -2
  11. package/src/modules/data/index.ts +45 -17
  12. package/src/modules/data/window-query.ts +24 -0
  13. package/src/modules/devtools/index.ts +2 -2
  14. package/src/modules/sync/queue/queue-down.ts +2 -2
  15. package/src/modules/sync/queue/queue-up.ts +2 -2
  16. package/src/modules/sync/sync.ts +2 -2
  17. package/src/services/database/cache-engine.ts +143 -0
  18. package/src/services/database/engine-factory.ts +32 -0
  19. package/src/services/database/index.ts +6 -0
  20. package/src/services/database/local-migrator.ts +2 -2
  21. package/src/services/database/plan-render.test.ts +85 -0
  22. package/src/services/database/plan-render.ts +86 -0
  23. package/src/services/database/relation-resolver.test.ts +413 -0
  24. package/src/services/database/relation-resolver.ts +0 -0
  25. package/src/services/database/sqlite-cache-engine.ts +631 -0
  26. package/src/services/database/sqlite-worker.ts +116 -0
  27. package/src/services/database/surql-translate.ts +291 -0
  28. package/src/services/database/surreal-cache-engine.ts +122 -0
  29. package/src/services/persistence/surrealdb.ts +2 -2
  30. package/src/services/stream-processor/index.ts +2 -2
  31. package/src/sp00ky.ts +24 -7
  32. package/src/types.ts +17 -1
  33. package/tsdown.config.ts +10 -1
package/dist/index.d.ts CHANGED
@@ -1,35 +1,10 @@
1
- import { A as SyncHealthStatus, C as RunOptions, D as StoreType, E as Sp00kyQueryResultPromise, F as SyncEventSystem, I as EventDefinition, L as EventSystem, M as UpdateOptions, N as UpEvent, O as SyncHealth, P as Logger$1, S as RegistrationTimings, T as Sp00kyQueryResult, _ as QueryTimeToLive, a as MutationCallback, b as RecordVersionArray, c as PersistenceClient, d as QueryConfig, f as QueryConfigRecord, g as QueryStatusCallback, h as QueryStatus, i as MATERIALIZATION_SAMPLE_WINDOW, j as TimingPhase, k as SyncHealthConfig, l as PhaseStat, m as QueryState, n as EventSubscriptionOptions, o as MutationEvent, p as QueryHash, r as Level, s as MutationEventType, t as DebounceOptions, u as PinoTransmit, v as QueryTimings, w as Sp00kyConfig, x as RecordVersionDiff, y as QueryUpdateCallback } from "./types.js";
1
+ import { A as SyncHealthStatus, B as EventDefinition, C as RunOptions, D as StoreType, E as Sp00kyQueryResultPromise, F as SealedQuery, I as DatabaseEventSystem, L as DatabaseEventTypes, M as UpdateOptions, N as UpEvent, O as SyncHealth, P as LocalStore, R as Logger$1, S as RegistrationTimings, T as Sp00kyQueryResult, V as EventSystem, _ as QueryTimeToLive, a as MutationCallback, b as RecordVersionArray, c as PersistenceClient, d as QueryConfig, f as QueryConfigRecord, g as QueryStatusCallback, h as QueryStatus, i as MATERIALIZATION_SAMPLE_WINDOW, j as TimingPhase, k as SyncHealthConfig, l as PhaseStat, m as QueryState, n as EventSubscriptionOptions, o as MutationEvent, p as QueryHash, r as Level, s as MutationEventType, t as DebounceOptions, u as PinoTransmit, v as QueryTimings, w as Sp00kyConfig, x as RecordVersionDiff, y as QueryUpdateCallback, z as SyncEventSystem } from "./types.js";
2
2
  import * as surrealdb0 from "surrealdb";
3
3
  import { Duration, RecordId, Surreal as Surreal$1, SurrealTransaction } from "surrealdb";
4
- import { AccessDefinition, BackendNames, BackendRoutes, BucketNames, ColumnSchema, GetTable, QueryBuilder, QueryOptions, RoutePayload, SchemaStructure, TableModel, TableNames, TypeNameToTypeMap } from "@spooky-sync/query-builder";
4
+ import { AccessDefinition, BackendNames, BackendRoutes, BucketNames, ColumnSchema, GetTable, QueryBuilder, QueryOptions, QueryPlan, RoutePayload, SchemaStructure, TableModel, TableNames, TypeNameToTypeMap } from "@spooky-sync/query-builder";
5
5
  import { Logger } from "pino";
6
6
  import { LoroDoc } from "loro-crdt";
7
7
 
8
- //#region src/services/database/events/index.d.ts
9
- declare const DatabaseEventTypes: {
10
- readonly LocalQuery: "DATABASE_LOCAL_QUERY";
11
- readonly RemoteQuery: "DATABASE_REMOTE_QUERY";
12
- };
13
- interface DatabaseQueryEventPayload {
14
- query: string;
15
- vars?: Record<string, unknown>;
16
- duration: number;
17
- success: boolean;
18
- error?: string;
19
- timestamp: number;
20
- }
21
- type DatabaseEventTypeMap = {
22
- [DatabaseEventTypes.LocalQuery]: EventDefinition<typeof DatabaseEventTypes.LocalQuery, DatabaseQueryEventPayload>;
23
- [DatabaseEventTypes.RemoteQuery]: EventDefinition<typeof DatabaseEventTypes.RemoteQuery, DatabaseQueryEventPayload>;
24
- };
25
- type DatabaseEventSystem = EventSystem<DatabaseEventTypeMap>;
26
- //#endregion
27
- //#region src/utils/surql.d.ts
28
- interface SealedQuery<T = void> {
29
- readonly sql: string;
30
- readonly extract: (results: unknown[]) => T;
31
- }
32
- //#endregion
33
8
  //#region src/services/database/database.d.ts
34
9
  declare abstract class AbstractDatabaseService {
35
10
  protected client: Surreal$1;
@@ -50,85 +25,6 @@ declare abstract class AbstractDatabaseService {
50
25
  close(): Promise<void>;
51
26
  }
52
27
  //#endregion
53
- //#region src/services/database/local.d.ts
54
- declare class LocalDatabaseService extends AbstractDatabaseService {
55
- private config;
56
- protected eventType: "DATABASE_LOCAL_QUERY";
57
- /** Bucket currently open. Set by `connect`/`switchStore`. */
58
- private bucketId;
59
- /**
60
- * Monotonic store generation. Bumped on every `switchStore`. Async chains
61
- * that read from the store, await something remote, and then write back
62
- * (sync poll, SSP stream updates) capture this at chain start and drop
63
- * their write when it no longer matches — a stale-epoch write would land
64
- * another user's data in the new bucket.
65
- */
66
- private storeEpoch;
67
- /** Gate that `query()`/`execute()` await; closed for the switch window. */
68
- private gate;
69
- /** The incoming client while a switch is in flight (for unload cleanup). */
70
- private pendingSwitchClient;
71
- constructor(config: Sp00kyConfig<any>['database'], logger: Logger$1);
72
- getConfig(): Sp00kyConfig<any>['database'];
73
- get currentBucketId(): string;
74
- get epoch(): number;
75
- /**
76
- * Close the query gate for a bucket switch. Every `query()`/`execute()`
77
- * issued after this waits until the returned release fn runs — so work
78
- * triggered mid-switch (sibling auth subscribers registering queries)
79
- * lands on the NEW bucket instead of racing the swap. The migrator uses
80
- * `queryUngated()` to provision the new bucket while the gate is closed.
81
- */
82
- beginSwitch(): () => void;
83
- query<T extends unknown[]>(query: string, vars?: Record<string, unknown>, opts?: {
84
- epoch?: number;
85
- }): Promise<T>;
86
- execute<T>(query: SealedQuery<T>, vars?: Record<string, unknown>, opts?: {
87
- epoch?: number;
88
- }): Promise<T>;
89
- /** Gate-bypassing query — ONLY for the switch path itself (schema
90
- * provisioning must run while the gate is closed, or it deadlocks). */
91
- queryUngated<T extends unknown[]>(query: string, vars?: Record<string, unknown>): Promise<T>;
92
- connect(bucketId?: string): Promise<void>;
93
- /**
94
- * Switch the local store to another user's bucket. Opens the NEW bucket on a
95
- * second client first (with the same 3-tier recovery), then atomically swaps
96
- * `this.client` and closes the old one — a failed open never leaves the
97
- * service on a dead client. Bumps the store epoch so in-flight old-bucket
98
- * async chains can detect they're stale.
99
- *
100
- * Callers own the drain/rebind choreography (close the gate, quiesce sync +
101
- * timers BEFORE calling this; re-provision + rebind AFTER).
102
- */
103
- switchStore(bucketId: string): Promise<void>;
104
- /**
105
- * Open `storeUrl` on `client` with tiered recovery:
106
- * tier 1 retries the same store (transient idb-handle races — preserves the
107
- * cache), tier 2 drops THIS bucket's IndexedDB store and reconnects fresh,
108
- * tier 3 falls back to `mem://` for the session. Only ever drops the bucket
109
- * being opened — other users' buckets hold their own caches AND un-pushed
110
- * mutation outboxes, which must survive another bucket's corruption.
111
- */
112
- private openWithRecovery;
113
- private unloadCloseRegistered;
114
- /**
115
- * Close the local DB on page unload so the SurrealDB-WASM worker releases its
116
- * IndexedDB connection cleanly. Without this, the previous page's connection
117
- * lingers; the next load's `client.connect` opens the store but the first
118
- * write transaction in `client.use` hits an "IndexedDB error" — which then
119
- * (mis)triggered the corrupt-store recovery and WIPED the cache on every
120
- * reload, making warm loads as slow as cold ones. `pagehide` is the reliable
121
- * unload signal (fires on bfcache + normal navigation); `close()` is async but
122
- * the WASM worker initiates the IndexedDB connection teardown synchronously.
123
- * Also closes a mid-switch incoming client so its fresh handle doesn't linger.
124
- */
125
- private registerUnloadClose;
126
- private openStore;
127
- }
128
- /** True for the SurrealDB-WASM error raised when its IndexedDB-backed key-value
129
- * store can't be opened (corrupt / version-incompatible / blocked). Exported
130
- * for unit testing the error-message match. */
131
- //#endregion
132
28
  //#region src/services/database/remote.d.ts
133
29
  declare class RemoteDatabaseService extends AbstractDatabaseService {
134
30
  private config;
@@ -243,7 +139,7 @@ declare class StreamProcessorService {
243
139
  private sessionAuth;
244
140
  private stateKeySuffix;
245
141
  private stateGeneration;
246
- constructor(events: EventSystem<StreamProcessorEvents>, db: LocalDatabaseService, persistenceClient: PersistenceClient, logger: Logger);
142
+ constructor(events: EventSystem<StreamProcessorEvents>, db: LocalStore, persistenceClient: PersistenceClient, logger: Logger);
247
143
  /**
248
144
  * Add a receiver for stream updates.
249
145
  * Multiple receivers can be registered (DataManager, DevTools, etc.)
@@ -371,7 +267,7 @@ declare class CacheModule implements StreamUpdateReceiver {
371
267
  private logger;
372
268
  private streamUpdateCallback;
373
269
  private versionLookups;
374
- constructor(local: LocalDatabaseService, streamProcessor: StreamProcessorService, streamUpdateCallback: (update: StreamUpdate) => void, logger: Logger$1);
270
+ constructor(local: LocalStore, streamProcessor: StreamProcessorService, streamUpdateCallback: (update: StreamUpdate) => void, logger: Logger$1);
375
271
  /**
376
272
  * Implements StreamUpdateReceiver interface
377
273
  * Called directly by StreamProcessor when views change
@@ -463,7 +359,7 @@ declare class DataModule<S extends SchemaStructure> {
463
359
  onDeregister?: (hash: QueryHash) => void;
464
360
  private sessionId;
465
361
  private currentUserId;
466
- constructor(cache: CacheModule, local: LocalDatabaseService, schema: S, logger: Logger$1, streamDebounceTime?: number);
362
+ constructor(cache: CacheModule, local: LocalStore, schema: S, logger: Logger$1, streamDebounceTime?: number);
467
363
  init(sessionId: string): Promise<void>;
468
364
  /**
469
365
  * Update the session salt used in query-id hashing. Call this when the
@@ -485,7 +381,7 @@ declare class DataModule<S extends SchemaStructure> {
485
381
  /**
486
382
  * Register a query and return its hash for subscriptions
487
383
  */
488
- query<T extends TableNames<S>>(tableName: T, surqlString: string, params: Record<string, any>, ttl: QueryTimeToLive): Promise<QueryHash>;
384
+ query<T extends TableNames<S>>(tableName: T, surqlString: string, params: Record<string, any>, ttl: QueryTimeToLive, plan?: QueryPlan): Promise<QueryHash>;
489
385
  /**
490
386
  * Subscribe to query updates
491
387
  */
@@ -781,7 +677,7 @@ declare class Sp00kySync<S extends SchemaStructure> {
781
677
  private startSelfHeal;
782
678
  private scheduleSelfHeal;
783
679
  private stopSelfHeal;
784
- constructor(local: LocalDatabaseService, remote: RemoteDatabaseService, cache: CacheModule, dataModule: DataModule<S>, schema: S, logger: Logger$1, options?: Sp00kySyncOptions);
680
+ constructor(local: LocalStore, remote: RemoteDatabaseService, cache: CacheModule, dataModule: DataModule<S>, schema: S, logger: Logger$1, options?: Sp00kySyncOptions);
785
681
  /**
786
682
  * Initializes the synchronization system.
787
683
  * Starts the scheduler and initiates the initial sync cycles.
@@ -1026,7 +922,7 @@ declare class CrdtField {
1026
922
  getDoc(): LoroDoc;
1027
923
  /** Whether the LoroDoc was loaded from saved CRDT state */
1028
924
  hasContent(): boolean;
1029
- startSync(local: LocalDatabaseService, remote: RemoteDatabaseService, recordId: string, sessionId: string, debounceMs: number): void;
925
+ startSync(local: LocalStore, remote: RemoteDatabaseService, recordId: string, sessionId: string, debounceMs: number): void;
1030
926
  /**
1031
927
  * Stop syncing this field. Flushes one final remote push by default so the
1032
928
  * last keystrokes aren't lost. Pass `{ flush: false }` on a bucket switch —
@@ -1093,7 +989,7 @@ declare class CrdtManager {
1093
989
  private pendingLive;
1094
990
  private logger;
1095
991
  private sessionId;
1096
- constructor(schema: SchemaStructure, local: LocalDatabaseService, remote: RemoteDatabaseService, logger: Logger$1, debounceMs?: number);
992
+ constructor(schema: SchemaStructure, local: LocalStore, remote: RemoteDatabaseService, logger: Logger$1, debounceMs?: number);
1097
993
  /** Set the session id that scopes this client's cursor entries. Must be
1098
994
  * called before `open()` for cursors to be pushed under a stable key.
1099
995
  * Passed in from `sp00ky.ts` at boot (it already fetches `session::id()`
@@ -1241,7 +1137,7 @@ declare class Sp00kyClient<S extends SchemaStructure> {
1241
1137
  auth: AuthService<S>;
1242
1138
  streamProcessor: StreamProcessorService;
1243
1139
  get remoteClient(): surrealdb0.Surreal;
1244
- get localClient(): surrealdb0.Surreal;
1140
+ get localClient(): unknown;
1245
1141
  get pendingMutationCount(): number;
1246
1142
  /** Number of times the initial list_ref LIVE subscription retried on
1247
1143
  * the most recent `setCurrentUserId` call. 0 when the SSP's