@spooky-sync/core 0.0.1-canary.159 → 0.0.1-canary.160

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.js CHANGED
@@ -3466,6 +3466,7 @@ var DataModule = class {
3466
3466
  const target = await withRetry(this.logger, () => this.local.execute(query, {
3467
3467
  id: rid,
3468
3468
  mid: mutationId,
3469
+ data: params,
3469
3470
  ...prefixedParams
3470
3471
  }));
3471
3472
  const parsedRecord = parseParams(tableSchema.columns, target);
@@ -5799,8 +5800,8 @@ async function walkOpfs(maxEntries = 2e3, maxDepth = 8) {
5799
5800
 
5800
5801
  //#endregion
5801
5802
  //#region src/modules/devtools/index.ts
5802
- const CORE_VERSION = "0.0.1-canary.159";
5803
- const WASM_VERSION = "0.0.1-canary.159";
5803
+ const CORE_VERSION = "0.0.1-canary.160";
5804
+ const WASM_VERSION = "0.0.1-canary.160";
5804
5805
  const SURREAL_VERSION = "3.0.3";
5805
5806
  var DevToolsService = class DevToolsService {
5806
5807
  eventsHistory = [];
@@ -9044,7 +9045,7 @@ var Sp00kyClient = class {
9044
9045
  return new TabsCoordinator({
9045
9046
  tabId,
9046
9047
  fingerprint: computeTabsFingerprint({
9047
- coreVersion: "0.0.1-canary.159",
9048
+ coreVersion: "0.0.1-canary.160",
9048
9049
  schemaHash: hash53(this.config.schemaSurql),
9049
9050
  endpoint: this.config.database.endpoint ?? "",
9050
9051
  namespace: this.config.database.namespace,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spooky-sync/core",
3
- "version": "0.0.1-canary.159",
3
+ "version": "0.0.1-canary.160",
4
4
  "type": "module",
5
5
  "sideEffects": false,
6
6
  "main": "./dist/index.js",
@@ -60,8 +60,8 @@
60
60
  }
61
61
  },
62
62
  "dependencies": {
63
- "@spooky-sync/query-builder": "0.0.1-canary.159",
64
- "@spooky-sync/ssp-wasm": "0.0.1-canary.159",
63
+ "@spooky-sync/query-builder": "0.0.1-canary.160",
64
+ "@spooky-sync/ssp-wasm": "0.0.1-canary.160",
65
65
  "@sqlite.org/sqlite-wasm": "3.53.0-build1",
66
66
  "@surrealdb/wasm": "^3.0.3",
67
67
  "fast-json-patch": "^3.1.1",
@@ -27,7 +27,8 @@ import type {
27
27
  QueryTimings,
28
28
  PhaseStat,
29
29
  RegistrationTimings,
30
- RunOptions} from '../../types';
30
+ RunOptions,
31
+ } from '../../types';
31
32
  import { MATERIALIZATION_SAMPLE_WINDOW } from '../../types';
32
33
  import {
33
34
  parseRecordIdString,
@@ -228,7 +229,15 @@ export class DataModule<S extends SchemaStructure> {
228
229
  );
229
230
 
230
231
  // Create the query and track the pending promise
231
- const promise = this.createAndRegisterQuery<T>(hash, recordId, surqlString, params, ttl, tableName, plan);
232
+ const promise = this.createAndRegisterQuery<T>(
233
+ hash,
234
+ recordId,
235
+ surqlString,
236
+ params,
237
+ ttl,
238
+ tableName,
239
+ plan
240
+ );
232
241
  this.pendingQueries.set(hash, promise);
233
242
  try {
234
243
  await promise;
@@ -641,7 +650,7 @@ export class DataModule<S extends SchemaStructure> {
641
650
  const idx = Math.min(sorted.length - 1, Math.floor(q * sorted.length));
642
651
  return sorted[idx]!;
643
652
  };
644
- return { p55: pick(0.55), p90: pick(0.90), p99: pick(0.99) };
653
+ return { p55: pick(0.55), p90: pick(0.9), p99: pick(0.99) };
645
654
  }
646
655
 
647
656
  /** Record a per-phase timing sample (ms) on a query's rolling window. */
@@ -836,10 +845,7 @@ export class DataModule<S extends SchemaStructure> {
836
845
  * Shared by `applyHydration` (live registration) and `persistSnapshot`
837
846
  * (preload).
838
847
  */
839
- private async buildAndSaveCacheBatch(
840
- tableName: string,
841
- rows: RecordWithId[]
842
- ): Promise<void> {
848
+ private async buildAndSaveCacheBatch(tableName: string, rows: RecordWithId[]): Promise<void> {
843
849
  const tableSchema = this.schema.tables.find((t) => t.name === tableName);
844
850
  const batch: CacheRecord[] = rows.map((record) => ({
845
851
  table: tableName,
@@ -882,9 +888,7 @@ export class DataModule<S extends SchemaStructure> {
882
888
  * data also clears the marker — a stale marker can't claim "warm" when the
883
889
  * rows are gone. Any read error is treated as cold.
884
890
  */
885
- async getPreloadMarker(
886
- hash: string
887
- ): Promise<{ fetchedAt: number; rowCount: number } | null> {
891
+ async getPreloadMarker(hash: string): Promise<{ fetchedAt: number; rowCount: number } | null> {
888
892
  try {
889
893
  // Pass a real RecordId: a bare string id hits the SurrealDB engine's
890
894
  // `FROM ONLY $__id` as a plain string, which "selects" the string itself
@@ -1274,6 +1278,12 @@ export class DataModule<S extends SchemaStructure> {
1274
1278
  this.local.execute(query, {
1275
1279
  id: rid,
1276
1280
  mid: mutationId,
1281
+ // The record itself is written with per-key `data_<field>` vars
1282
+ // (`createSet`), but the OUTBOX row needs the whole payload in one
1283
+ // `data` field so the mutation can be replayed later. Without this the
1284
+ // row is written with `data` unbound: the create becomes unsendable and
1285
+ // blocks the queue behind it.
1286
+ data: params,
1277
1287
  ...prefixedParams,
1278
1288
  })
1279
1289
  );
@@ -1485,9 +1495,7 @@ export class DataModule<S extends SchemaStructure> {
1485
1495
  const id = encodeRecordId(recordId);
1486
1496
 
1487
1497
  try {
1488
- await withRetry(this.logger, () =>
1489
- this.local.query('DELETE $id', { id: recordId })
1490
- );
1498
+ await withRetry(this.logger, () => this.local.query('DELETE $id', { id: recordId }));
1491
1499
  await this.cache.delete(tableName, id, true);
1492
1500
  this.removeRecordFromQueries(recordId);
1493
1501
 
@@ -1765,9 +1773,7 @@ export class DataModule<S extends SchemaStructure> {
1765
1773
  ? (configRecord as any).updateCount
1766
1774
  : 0;
1767
1775
  const persistedErrorCount =
1768
- typeof (configRecord as any)?.errorCount === 'number'
1769
- ? (configRecord as any).errorCount
1770
- : 0;
1776
+ typeof (configRecord as any)?.errorCount === 'number' ? (configRecord as any).errorCount : 0;
1771
1777
 
1772
1778
  return {
1773
1779
  config,
@@ -131,7 +131,10 @@ describe('SqliteCacheEngine storage health', () => {
131
131
  });
132
132
 
133
133
  it('publishes the fallback, its reason, and an error log when OPFS is lost', async () => {
134
- const { engine, logs } = makeEngine({ persisted: false, opfsError: 'NoModificationAllowedError: locked' });
134
+ const { engine, logs } = makeEngine({
135
+ persisted: false,
136
+ opfsError: 'NoModificationAllowedError: locked',
137
+ });
135
138
  await engine.connect('user:abc');
136
139
 
137
140
  expect(engine.storageHealth).toEqual({
@@ -178,11 +181,7 @@ describe('SqliteCacheEngine.getStorageDiagnostics', () => {
178
181
  logger.child = () => logger;
179
182
  const engine = new SqliteCacheEngine({ namespace: 'n', database: 'd' } as any, logger);
180
183
  stubTransport(engine, (type, payload) =>
181
- type === 'open'
182
- ? { persisted: true }
183
- : type === 'exec'
184
- ? { rows: execRows(payload.sql) }
185
- : {}
184
+ type === 'open' ? { persisted: true } : type === 'exec' ? { rows: execRows(payload.sql) } : {}
186
185
  );
187
186
  return engine;
188
187
  }
@@ -235,11 +234,10 @@ describe('SqliteCacheEngine.getStorageDiagnostics', () => {
235
234
  describe('SqliteCacheEngine role modes', () => {
236
235
  function makeSharedEngine() {
237
236
  const log: string[] = [];
238
- const engine = new SqliteCacheEngine(
239
- { namespace: 'n', database: 'd' } as any,
240
- makeLogger(),
241
- { shared: true, useOpfs: true }
242
- );
237
+ const engine = new SqliteCacheEngine({ namespace: 'n', database: 'd' } as any, makeLogger(), {
238
+ shared: true,
239
+ useOpfs: true,
240
+ });
243
241
  stubTransport(engine, lifecycleHandler(log));
244
242
  return { engine, log };
245
243
  }
@@ -287,11 +285,9 @@ describe('SqliteCacheEngine role modes', () => {
287
285
  });
288
286
 
289
287
  it('adoptAttached serves reads over the leader port and reports follower role', async () => {
290
- const engine = new SqliteCacheEngine(
291
- { namespace: 'n', database: 'd' } as any,
292
- makeLogger(),
293
- { shared: true }
294
- );
288
+ const engine = new SqliteCacheEngine({ namespace: 'n', database: 'd' } as any, makeLogger(), {
289
+ shared: true,
290
+ });
295
291
  const port = fakeLeaderPort();
296
292
  await engine.adoptAttached(
297
293
  port,
@@ -428,4 +424,41 @@ describe('create() tx result shaping (fast path parity)', () => {
428
424
  expect(created.id).toBe('connection:CONN_abc');
429
425
  expect(created.provider).toBe('chesscom');
430
426
  });
427
+
428
+ // The outbox row is the ONLY copy of a pending create once the in-memory
429
+ // UpEvent is gone (reload, or a shared-tabs follower whose row the leader
430
+ // replays). It must carry the payload: `processUpEvent` does
431
+ // `Object.keys(event.data)`, so a row written with `data` unbound produces a
432
+ // create that can never be sent AND blocks every later mutation behind it.
433
+ //
434
+ // Note the vars: the RECORD is written from per-key `data_<field>` vars, so
435
+ // it is easy to emit `data = $data` and never bind `data` at the call site.
436
+ // That is exactly the bug this asserts against.
437
+ it('the outbox row carries the whole payload, not just the record id', () => {
438
+ const payload = { provider: 'chesscom', username: 'hikaru' };
439
+ const vars = {
440
+ id: new RecordId('connection', 'CONN_abc'),
441
+ mid: new RecordId('_00_pending_mutations', '1'),
442
+ data: payload,
443
+ data_provider: 'chesscom',
444
+ data_username: 'hikaru',
445
+ };
446
+
447
+ const sealed = surql.seal(
448
+ surql.tx([
449
+ surql.createSet('id', [
450
+ { key: 'provider', variable: 'data_provider' },
451
+ { key: 'username', variable: 'data_username' },
452
+ ]),
453
+ surql.createMutation('create', 'mid', 'id', 'data'),
454
+ ]),
455
+ { resultIndex: 0 }
456
+ );
457
+
458
+ const { ops } = translateSurql(sealed.sql, vars);
459
+ const outboxRow = pureWriteOpResult(ops[1]) as Record<string, unknown>;
460
+
461
+ expect(outboxRow.mutationType).toBe('create');
462
+ expect(outboxRow.data).toEqual(payload);
463
+ });
431
464
  });