@spooky-sync/core 0.0.1-canary.21 → 0.0.1-canary.210

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 (163) hide show
  1. package/AGENTS.md +57 -0
  2. package/dist/index.d.ts +2514 -58
  3. package/dist/index.js +12561 -2449
  4. package/dist/otel/index.d.ts +2 -2
  5. package/dist/otel/index.js +6 -6
  6. package/dist/sqlite-open.js +303 -0
  7. package/dist/sqlite-worker.d.ts +1 -0
  8. package/dist/sqlite-worker.js +439 -0
  9. package/dist/tabs-broker-worker.d.ts +8 -0
  10. package/dist/tabs-broker-worker.js +472 -0
  11. package/dist/types.d.ts +751 -11
  12. package/package.json +11 -7
  13. package/scripts/check-broker-bundle.mjs +33 -0
  14. package/skills/{spooky-core → sp00ky-core}/SKILL.md +12 -12
  15. package/skills/{spooky-core → sp00ky-core}/references/auth.md +1 -1
  16. package/skills/{spooky-core → sp00ky-core}/references/config.md +2 -2
  17. package/src/bucket-blurhash.test.ts +148 -0
  18. package/src/build-globals.d.ts +12 -0
  19. package/src/events/events.test.ts +2 -1
  20. package/src/events/index.ts +3 -0
  21. package/src/index.ts +36 -2
  22. package/src/modules/app-release/index.test.ts +125 -0
  23. package/src/modules/app-release/index.ts +201 -0
  24. package/src/modules/auth/auth.local-first.test.ts +101 -0
  25. package/src/modules/auth/events/index.ts +2 -1
  26. package/src/modules/auth/index.ts +127 -24
  27. package/src/modules/cache/cache.relay.test.ts +95 -0
  28. package/src/modules/cache/index.ts +163 -43
  29. package/src/modules/cache/types.ts +2 -2
  30. package/src/modules/crdt/crdt-field.ts +294 -0
  31. package/src/modules/crdt/crdt-hydration.test.ts +210 -0
  32. package/src/modules/crdt/crdt-reconnect.test.ts +195 -0
  33. package/src/modules/crdt/index.ts +463 -0
  34. package/src/modules/crdt/loro-loader.ts +25 -0
  35. package/src/modules/data/data.hydration.test.ts +142 -0
  36. package/src/modules/data/data.membership.test.ts +523 -0
  37. package/src/modules/data/data.notify-table.test.ts +41 -0
  38. package/src/modules/data/data.pending-ids.test.ts +199 -0
  39. package/src/modules/data/data.rebind.test.ts +170 -0
  40. package/src/modules/data/data.rematerialize.test.ts +114 -0
  41. package/src/modules/data/data.run.test.ts +113 -0
  42. package/src/modules/data/data.settled-writes.test.ts +206 -0
  43. package/src/modules/data/data.status.test.ts +249 -0
  44. package/src/modules/data/id-set-plan.test.ts +122 -0
  45. package/src/modules/data/index.ts +1815 -151
  46. package/src/modules/data/mutation-id.test.ts +25 -0
  47. package/src/modules/data/mutation-id.ts +35 -0
  48. package/src/modules/data/window-query.test.ts +52 -0
  49. package/src/modules/data/window-query.ts +194 -0
  50. package/src/modules/devtools/flags.ts +349 -0
  51. package/src/modules/devtools/index.ts +450 -46
  52. package/src/modules/devtools/notify-throttle.test.ts +154 -0
  53. package/src/modules/devtools/state-shape.test.ts +146 -0
  54. package/src/modules/devtools/storage-info.test.ts +79 -0
  55. package/src/modules/devtools/storage-info.ts +168 -0
  56. package/src/modules/devtools/versions.test.ts +74 -0
  57. package/src/modules/devtools/versions.ts +110 -0
  58. package/src/modules/feature-flag/index.test.ts +251 -0
  59. package/src/modules/feature-flag/index.ts +308 -0
  60. package/src/modules/ref-tables.test.ts +91 -0
  61. package/src/modules/ref-tables.ts +88 -0
  62. package/src/modules/sync/engine.ts +164 -82
  63. package/src/modules/sync/events/index.ts +9 -2
  64. package/src/modules/sync/queue/queue-down.test.ts +180 -0
  65. package/src/modules/sync/queue/queue-down.ts +80 -13
  66. package/src/modules/sync/queue/queue-up.forwarded.test.ts +164 -0
  67. package/src/modules/sync/queue/queue-up.ts +241 -57
  68. package/src/modules/sync/scheduler.pause.test.ts +109 -0
  69. package/src/modules/sync/scheduler.retry.test.ts +237 -0
  70. package/src/modules/sync/scheduler.ts +215 -13
  71. package/src/modules/sync/sync.cleanup.test.ts +116 -0
  72. package/src/modules/sync/sync.health.test.ts +149 -0
  73. package/src/modules/sync/sync.heartbeat.test.ts +80 -0
  74. package/src/modules/sync/sync.live-removal.test.ts +175 -0
  75. package/src/modules/sync/sync.reconnect.test.ts +145 -0
  76. package/src/modules/sync/sync.subquery.test.ts +82 -0
  77. package/src/modules/sync/sync.tabs.test.ts +249 -0
  78. package/src/modules/sync/sync.ts +1726 -99
  79. package/src/modules/sync/utils.test.ts +269 -2
  80. package/src/modules/sync/utils.ts +201 -17
  81. package/src/otel/index.ts +13 -10
  82. package/src/services/blobs/blob-cache.test.ts +359 -0
  83. package/src/services/blobs/blob-cache.ts +603 -0
  84. package/src/services/blobs/blob-manifest.ts +227 -0
  85. package/src/services/blobs/blob-store.test.ts +77 -0
  86. package/src/services/blobs/blob-store.ts +359 -0
  87. package/src/services/blobs/blob.fixture.ts +90 -0
  88. package/src/services/blobs/index.ts +70 -0
  89. package/src/services/database/cache-engine.ts +193 -0
  90. package/src/services/database/connection-supervisor.test.ts +289 -0
  91. package/src/services/database/connection-supervisor.ts +415 -0
  92. package/src/services/database/database.query-timeout.test.ts +83 -0
  93. package/src/services/database/database.ts +41 -12
  94. package/src/services/database/engine-factory.ts +33 -0
  95. package/src/services/database/errors.ts +34 -0
  96. package/src/services/database/events/index.ts +2 -1
  97. package/src/services/database/index.ts +7 -0
  98. package/src/services/database/local-migrator.ts +30 -27
  99. package/src/services/database/local.test.ts +64 -0
  100. package/src/services/database/local.ts +484 -67
  101. package/src/services/database/plan-render.test.ts +159 -0
  102. package/src/services/database/plan-render.ts +108 -0
  103. package/src/services/database/relation-resolver.test.ts +413 -0
  104. package/src/services/database/relation-resolver.ts +0 -0
  105. package/src/services/database/remote.ts +110 -14
  106. package/src/services/database/sqlite-cache-engine.test.ts +616 -0
  107. package/src/services/database/sqlite-cache-engine.timeout.test.ts +61 -0
  108. package/src/services/database/sqlite-cache-engine.ts +1358 -0
  109. package/src/services/database/sqlite-devtools-queries.integration.test.ts +143 -0
  110. package/src/services/database/sqlite-devtools-queries.test.ts +154 -0
  111. package/src/services/database/sqlite-lock-verify.test.ts +33 -0
  112. package/src/services/database/sqlite-lock-verify.ts +45 -0
  113. package/src/services/database/sqlite-open.test.ts +150 -0
  114. package/src/services/database/sqlite-open.ts +164 -0
  115. package/src/services/database/sqlite-plan-sql.test.ts +104 -0
  116. package/src/services/database/sqlite-plan-sql.ts +138 -0
  117. package/src/services/database/sqlite-projection.test.ts +99 -0
  118. package/src/services/database/sqlite-select.integration.test.ts +185 -0
  119. package/src/services/database/sqlite-select.test.ts +246 -0
  120. package/src/services/database/sqlite-select.ts +131 -0
  121. package/src/services/database/sqlite-transport.fixture.ts +30 -0
  122. package/src/services/database/sqlite-transport.ts +224 -0
  123. package/src/services/database/sqlite-worker.ts +437 -0
  124. package/src/services/database/surql-translate.ts +416 -0
  125. package/src/services/database/surreal-cache-engine.ts +161 -0
  126. package/src/services/logger/index.ts +3 -2
  127. package/src/services/persistence/localstorage.ts +2 -2
  128. package/src/services/persistence/resilient.ts +11 -4
  129. package/src/services/persistence/surrealdb.ts +10 -10
  130. package/src/services/stream-processor/index.ts +796 -84
  131. package/src/services/stream-processor/permissions.test.ts +47 -0
  132. package/src/services/stream-processor/permissions.ts +53 -0
  133. package/src/services/stream-processor/stream-processor.batch.test.ts +186 -0
  134. package/src/services/stream-processor/stream-processor.prime.test.ts +198 -0
  135. package/src/services/stream-processor/stream-processor.reset.test.ts +226 -0
  136. package/src/services/stream-processor/stream-processor.test.ts +1 -1
  137. package/src/services/stream-processor/wasm-types.ts +59 -3
  138. package/src/services/tabs/broker-client.ts +283 -0
  139. package/src/services/tabs/broker.test.ts +327 -0
  140. package/src/services/tabs/coordinator.test.ts +365 -0
  141. package/src/services/tabs/coordinator.ts +633 -0
  142. package/src/services/tabs/fake-ports.fixture.ts +112 -0
  143. package/src/services/tabs/leader-locks.ts +75 -0
  144. package/src/services/tabs/protocol.ts +258 -0
  145. package/src/services/tabs/support.ts +36 -0
  146. package/src/services/tabs/tabs-broker-worker.ts +640 -0
  147. package/src/sp00ky.auth-order.test.ts +92 -0
  148. package/src/sp00ky.init-query.test.ts +183 -0
  149. package/src/sp00ky.local-first.test.ts +60 -0
  150. package/src/sp00ky.ts +1693 -0
  151. package/src/types.ts +528 -13
  152. package/src/utils/blurhash.ts +90 -0
  153. package/src/utils/error-classification.test.ts +44 -0
  154. package/src/utils/error-classification.ts +7 -0
  155. package/src/utils/index.ts +79 -13
  156. package/src/utils/parser.test.ts +49 -120
  157. package/src/utils/parser.ts +32 -2
  158. package/src/utils/semver.test.ts +32 -0
  159. package/src/utils/semver.ts +30 -0
  160. package/src/utils/surql.ts +30 -18
  161. package/src/utils/withRetry.test.ts +1 -1
  162. package/tsdown.config.ts +86 -1
  163. package/src/spooky.ts +0 -395
@@ -1,5 +1,12 @@
1
1
  export * from './database';
2
2
  export * from './local';
3
3
  export * from './remote';
4
+ export * from './connection-supervisor';
4
5
  export * from './local-migrator';
5
6
  export * from './events/index';
7
+ export * from './cache-engine';
8
+ export * from './surreal-cache-engine';
9
+ export * from './sqlite-cache-engine';
10
+ export * from './relation-resolver';
11
+ export * from './plan-render';
12
+ export { createLocalEngine } from './engine-factory';
@@ -1,6 +1,6 @@
1
- import type { Surreal } from 'surrealdb';
2
- import { Logger, createLogger } from '../logger/index';
3
- import { LocalDatabaseService } from './local';
1
+ import type { Logger} from '../logger/index';
2
+ import { createLogger } from '../logger/index';
3
+ import type { LocalStore } from './cache-engine';
4
4
 
5
5
  export interface SchemaRecord {
6
6
  hash: string;
@@ -19,12 +19,12 @@ export class LocalMigrator {
19
19
  private logger: Logger;
20
20
 
21
21
  constructor(
22
- private localDb: LocalDatabaseService,
22
+ private localDb: LocalStore,
23
23
  logger: Logger
24
24
  ) {
25
25
  this.logger = logger.child({ service: 'LocalMigrator' });
26
- logger?.child({ service: 'LocalMigrator' }) ??
27
- createLogger('info').child({ service: 'LocalMigrator' });
26
+ void (logger?.child({ service: 'LocalMigrator' }) ??
27
+ createLogger('info').child({ service: 'LocalMigrator' }));
28
28
  }
29
29
 
30
30
  async provision(schemaSurql: string): Promise<void> {
@@ -34,7 +34,7 @@ export class LocalMigrator {
34
34
 
35
35
  if (await this.isSchemaUpToDate(hash)) {
36
36
  this.logger.info(
37
- { Category: 'spooky-client::LocalMigrator::provision' },
37
+ { Category: 'sp00ky-client::LocalMigrator::provision' },
38
38
  '[Provisioning] Schema is up to date, skipping migration'
39
39
  );
40
40
  return;
@@ -43,10 +43,13 @@ export class LocalMigrator {
43
43
  await this.recreateDatabase(database);
44
44
 
45
45
  const systemSchema = `
46
- DEFINE TABLE IF NOT EXISTS _spooky_stream_processor_state SCHEMALESS PERMISSIONS FOR select, create, update, delete WHERE true;
47
- DEFINE TABLE IF NOT EXISTS _spooky_query SCHEMALESS PERMISSIONS FOR select, create, update, delete WHERE true;
48
- DEFINE TABLE IF NOT EXISTS _spooky_schema SCHEMALESS PERMISSIONS FOR select, create, update, delete WHERE true;
49
- DEFINE TABLE IF NOT EXISTS _spooky_pending_mutations SCHEMALESS PERMISSIONS FOR select, create, update, delete WHERE true;
46
+ DEFINE TABLE IF NOT EXISTS _00_stream_processor_state SCHEMALESS PERMISSIONS FOR select, create, update, delete WHERE true;
47
+ DEFINE TABLE IF NOT EXISTS _00_query SCHEMALESS PERMISSIONS FOR select, create, update, delete WHERE true;
48
+ DEFINE TABLE IF NOT EXISTS _00_preload SCHEMALESS PERMISSIONS FOR select, create, update, delete WHERE true;
49
+ DEFINE TABLE IF NOT EXISTS _00_window SCHEMALESS PERMISSIONS FOR select, create, update, delete WHERE true;
50
+ DEFINE TABLE IF NOT EXISTS _00_schema SCHEMALESS PERMISSIONS FOR select, create, update, delete WHERE true;
51
+ DEFINE TABLE IF NOT EXISTS _00_pending_mutations SCHEMALESS PERMISSIONS FOR select, create, update, delete WHERE true;
52
+ DEFINE TABLE IF NOT EXISTS _00_blob SCHEMALESS PERMISSIONS FOR select, create, update, delete WHERE true;
50
53
  `;
51
54
  const fullSchema = schemaSurql + '\n' + systemSchema;
52
55
 
@@ -60,7 +63,7 @@ export class LocalMigrator {
60
63
  // SKIP INDEXES: WASM engine hangs on DEFINE INDEX (confirmed)
61
64
  if (cleanStatement.toUpperCase().startsWith('DEFINE INDEX')) {
62
65
  this.logger.warn(
63
- { Category: 'spooky-client::LocalMigrator::provision' },
66
+ { Category: 'sp00ky-client::LocalMigrator::provision' },
64
67
  `[Provisioning] Skipping index definition (WASM hang avoidance): ${cleanStatement.substring(0, 50)}...`
65
68
  );
66
69
  continue;
@@ -68,17 +71,17 @@ export class LocalMigrator {
68
71
 
69
72
  try {
70
73
  this.logger.info(
71
- { Category: 'spooky-client::LocalMigrator::provision' },
74
+ { Category: 'sp00ky-client::LocalMigrator::provision' },
72
75
  `[Provisioning] (${i + 1}/${statements.length}) Executing: ${statement.substring(0, 50)}...`
73
76
  );
74
- await this.localDb.query(statement);
77
+ await this.localDb.queryUngated(statement);
75
78
  this.logger.info(
76
- { Category: 'spooky-client::LocalMigrator::provision' },
79
+ { Category: 'sp00ky-client::LocalMigrator::provision' },
77
80
  `[Provisioning] (${i + 1}/${statements.length}) Done`
78
81
  );
79
82
  } catch (e) {
80
83
  this.logger.error(
81
- { Category: 'spooky-client::LocalMigrator::provision' },
84
+ { Category: 'sp00ky-client::LocalMigrator::provision' },
82
85
  `[Provisioning] (${i + 1}/${statements.length}) Error executing statement: ${statement}`
83
86
  );
84
87
  throw e;
@@ -90,32 +93,32 @@ export class LocalMigrator {
90
93
 
91
94
  private async isSchemaUpToDate(hash: string): Promise<boolean> {
92
95
  try {
93
- const [lastSchemaRecord] = await this.localDb.query<any>(
94
- `SELECT hash, created_at FROM ONLY _spooky_schema ORDER BY created_at DESC LIMIT 1;`
96
+ const [lastSchemaRecord] = await this.localDb.queryUngated<any>(
97
+ `SELECT hash, created_at FROM ONLY _00_schema ORDER BY created_at DESC LIMIT 1;`
95
98
  );
96
99
  return lastSchemaRecord?.hash === hash;
97
- } catch (error) {
100
+ } catch (_error) {
98
101
  return false;
99
102
  }
100
103
  }
101
104
 
102
105
  private async recreateDatabase(database: string) {
103
106
  try {
104
- await this.localDb.query(`DEFINE DATABASE _spooky_temp;`);
105
- } catch (e) {
107
+ await this.localDb.queryUngated(`DEFINE DATABASE _00_temp;`);
108
+ } catch (_e) {
106
109
  // Ignore if exists
107
110
  }
108
111
 
109
112
  try {
110
- await this.localDb.query(`
111
- USE DB _spooky_temp;
113
+ await this.localDb.queryUngated(`
114
+ USE DB _00_temp;
112
115
  REMOVE DATABASE ${database};
113
116
  `);
114
- } catch (e) {
117
+ } catch (_e) {
115
118
  // Ignore error if database doesn't exist
116
119
  }
117
120
 
118
- await this.localDb.query(`
121
+ await this.localDb.queryUngated(`
119
122
  DEFINE DATABASE ${database};
120
123
  USE DB ${database};
121
124
  `);
@@ -195,8 +198,8 @@ export class LocalMigrator {
195
198
  }
196
199
 
197
200
  private async createHashRecord(hash: string) {
198
- await this.localDb.query(
199
- `UPSERT _spooky_schema SET hash = $hash, created_at = time::now() WHERE hash = $hash;`,
201
+ await this.localDb.queryUngated(
202
+ `UPSERT _00_schema SET hash = $hash, created_at = time::now() WHERE hash = $hash;`,
200
203
  { hash }
201
204
  );
202
205
  }
@@ -0,0 +1,64 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { isLocalStoreOpenError } from './local';
3
+
4
+ // `LocalDatabaseService.connect` recovers (drops the store + reconnects, falling
5
+ // back to in-memory) only when the failure is the SurrealDB-WASM IndexedDB
6
+ // open/transaction error — NOT for unrelated errors. This pins the message match
7
+ // against the real error the engine throws so a recovery path doesn't silently
8
+ // stop triggering (or start swallowing unrelated failures).
9
+ describe('isLocalStoreOpenError', () => {
10
+ it('matches the real SurrealDB-WASM IndexedDB key-value store error', () => {
11
+ const real = new Error(
12
+ 'There was a problem with the key-value store: There was a problem with a ' +
13
+ 'transaction: An IndexedDB error occured: idb error'
14
+ );
15
+ expect(isLocalStoreOpenError(real)).toBe(true);
16
+ });
17
+
18
+ it('matches on the individual signals (indexeddb / idb error / key-value store)', () => {
19
+ expect(isLocalStoreOpenError(new Error('IndexedDB is not available'))).toBe(true);
20
+ expect(isLocalStoreOpenError(new Error('idb error'))).toBe(true);
21
+ expect(isLocalStoreOpenError(new Error('problem with the key-value store'))).toBe(true);
22
+ // Non-Error inputs are stringified.
23
+ expect(isLocalStoreOpenError('An IndexedDB error occured')).toBe(true);
24
+ });
25
+
26
+ it('does NOT match unrelated errors (so we never clear the store spuriously)', () => {
27
+ expect(isLocalStoreOpenError(new Error('WebSocket connection refused'))).toBe(false);
28
+ expect(isLocalStoreOpenError(new Error('Parse error: unexpected token'))).toBe(false);
29
+ expect(isLocalStoreOpenError(new Error('permission denied'))).toBe(false);
30
+ expect(isLocalStoreOpenError(null)).toBe(false);
31
+ expect(isLocalStoreOpenError(undefined)).toBe(false);
32
+ });
33
+ });
34
+
35
+ // Per-user local buckets: URL/name derivation and the SCOPE of the tier-2
36
+ // corruption drop. The drop must only ever match the failing bucket's store —
37
+ // a substring wipe would take every user's cache AND their un-pushed mutation
38
+ // outboxes down with one corrupt store.
39
+ import { bucketStoreName, bucketStoreUrl, matchesBucketStore } from './local';
40
+
41
+ describe('bucket store naming', () => {
42
+ it('derives the store url/name from the bucket id', () => {
43
+ expect(bucketStoreUrl('abc')).toBe('indxdb://sp00ky-abc');
44
+ expect(bucketStoreName('abc')).toBe('sp00ky-abc');
45
+ expect(bucketStoreUrl('anon')).toBe('indxdb://sp00ky-anon');
46
+ });
47
+ });
48
+
49
+ describe('matchesBucketStore', () => {
50
+ it('matches the exact store and derived names', () => {
51
+ expect(matchesBucketStore('sp00ky-abc', 'sp00ky-abc')).toBe(true);
52
+ expect(matchesBucketStore('sp00ky-abc-wal', 'sp00ky-abc')).toBe(true);
53
+ expect(matchesBucketStore('surrealdb/sp00ky-abc', 'sp00ky-abc')).toBe(true);
54
+ expect(matchesBucketStore('SP00KY-ABC', 'sp00ky-abc')).toBe(true);
55
+ });
56
+
57
+ it("never matches another bucket's store", () => {
58
+ expect(matchesBucketStore('sp00ky-abcdef', 'sp00ky-abc')).toBe(false);
59
+ expect(matchesBucketStore('sp00ky-anon', 'sp00ky-abc')).toBe(false);
60
+ expect(matchesBucketStore('sp00ky', 'sp00ky-abc')).toBe(false);
61
+ // The legacy shared store is not any bucket's store.
62
+ expect(matchesBucketStore('sp00ky', 'sp00ky-anon')).toBe(false);
63
+ });
64
+ });