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

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
@@ -3912,7 +3912,7 @@ var UpQueue = class {
3912
3912
  Category: "sp00ky-client::UpQueue::discardUnreplayable"
3913
3913
  }, "Discarding an unsendable pending mutation");
3914
3914
  try {
3915
- await this.local.query(`DELETE $mutation_id`, { mutation_id: parseRecordIdString(mutationId) });
3915
+ await this.local.query(`DELETE $mutation_id`, { mutation_id: parseStoredRecordId(mutationId) });
3916
3916
  } catch (error) {
3917
3917
  this.logger.error({
3918
3918
  error,
@@ -4090,6 +4090,24 @@ var UpQueue = class {
4090
4090
  function encodeUpEventId(event) {
4091
4091
  return encodeRecordId(event.mutation_id);
4092
4092
  }
4093
+ /**
4094
+ * Parse a record id READ BACK FROM THE STORE, stripping SurrealDB's `⟨⟩`
4095
+ * escaping.
4096
+ *
4097
+ * Outbox ids contain `_` and `-`, so the store hands them back in escaped
4098
+ * display form (`_00_pending_mutations:⟨1785…_0005_c960…⟩`). `parseRecordIdString`
4099
+ * keeps that verbatim, and re-encoding then escapes the brackets AGAIN, so a
4100
+ * `DELETE $mutation_id` built from a stored id targets
4101
+ * `_00_pending_mutations:⟨⟨1785…\⟩⟩` and matches nothing. Every mutation
4102
+ * replayed from the store was therefore un-deletable: its row survived a
4103
+ * SUCCESSFUL push and got re-sent on the next boot.
4104
+ */
4105
+ function parseStoredRecordId(id) {
4106
+ const [table, ...rest] = id.split(":");
4107
+ let raw = rest.join(":");
4108
+ if (raw.startsWith("⟨") && raw.endsWith("⟩")) raw = raw.slice(1, -1);
4109
+ return new RecordId(table, raw);
4110
+ }
4093
4111
  /** Materialize one `_00_pending_mutations` row into an UpEvent. */
4094
4112
  function rowToUpEvent(r, logger) {
4095
4113
  switch (r.mutationType) {
@@ -4097,21 +4115,21 @@ function rowToUpEvent(r, logger) {
4097
4115
  if (r.data === void 0 || r.data === null) return null;
4098
4116
  return {
4099
4117
  type: "create",
4100
- mutation_id: parseRecordIdString(r.id),
4118
+ mutation_id: parseStoredRecordId(r.id),
4101
4119
  record_id: parseRecordIdString(r.recordId),
4102
4120
  data: r.data,
4103
4121
  tableName: extractTablePart(r.recordId)
4104
4122
  };
4105
4123
  case "update": return {
4106
4124
  type: "update",
4107
- mutation_id: parseRecordIdString(r.id),
4125
+ mutation_id: parseStoredRecordId(r.id),
4108
4126
  record_id: parseRecordIdString(r.recordId),
4109
4127
  data: r.data,
4110
4128
  beforeRecord: r.beforeRecord
4111
4129
  };
4112
4130
  case "delete": return {
4113
4131
  type: "delete",
4114
- mutation_id: parseRecordIdString(r.id),
4132
+ mutation_id: parseStoredRecordId(r.id),
4115
4133
  record_id: parseRecordIdString(r.recordId)
4116
4134
  };
4117
4135
  default:
@@ -5800,8 +5818,8 @@ async function walkOpfs(maxEntries = 2e3, maxDepth = 8) {
5800
5818
 
5801
5819
  //#endregion
5802
5820
  //#region src/modules/devtools/index.ts
5803
- const CORE_VERSION = "0.0.1-canary.160";
5804
- const WASM_VERSION = "0.0.1-canary.160";
5821
+ const CORE_VERSION = "0.0.1-canary.161";
5822
+ const WASM_VERSION = "0.0.1-canary.161";
5805
5823
  const SURREAL_VERSION = "3.0.3";
5806
5824
  var DevToolsService = class DevToolsService {
5807
5825
  eventsHistory = [];
@@ -9045,7 +9063,7 @@ var Sp00kyClient = class {
9045
9063
  return new TabsCoordinator({
9046
9064
  tabId,
9047
9065
  fingerprint: computeTabsFingerprint({
9048
- coreVersion: "0.0.1-canary.160",
9066
+ coreVersion: "0.0.1-canary.161",
9049
9067
  schemaHash: hash53(this.config.schemaSurql),
9050
9068
  endpoint: this.config.database.endpoint ?? "",
9051
9069
  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.160",
3
+ "version": "0.0.1-canary.161",
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.160",
64
- "@spooky-sync/ssp-wasm": "0.0.1-canary.160",
63
+ "@spooky-sync/query-builder": "0.0.1-canary.161",
64
+ "@spooky-sync/ssp-wasm": "0.0.1-canary.161",
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",
@@ -114,6 +114,33 @@ describe('UpQueue.enqueueFromDatabase', () => {
114
114
  expect(stmt).toContain('data = $data');
115
115
  });
116
116
 
117
+ it('deletes the row using the STORED id, not its escaped display form', async () => {
118
+ // Outbox ids contain `_` and `-`, so the store returns them escaped:
119
+ // `_00_pending_mutations:⟨1785…_0005_c960…⟩`. Parsing that verbatim and
120
+ // re-encoding escapes the brackets AGAIN, so the DELETE targeted
121
+ // `⟨⟨…\⟩⟩` and matched nothing: a mutation replayed from the store could
122
+ // never have its row removed, even after a SUCCESSFUL push, so it was
123
+ // re-sent on every boot.
124
+ const storedId = '_00_pending_mutations:⟨1785540216679_0005_c960958c-d6ed⟩';
125
+ const deletes: any[] = [];
126
+ const query = vi.fn(async (sql: string, vars?: Record<string, unknown>) => {
127
+ if (sql.startsWith('DELETE')) {
128
+ deletes.push(vars?.mutation_id);
129
+ return [[]];
130
+ }
131
+ return [[{ id: storedId, mutationType: 'create', recordId: 'comment:c1' }]];
132
+ });
133
+ const queue = new UpQueue({ query } as any, makeLogger(), () => {});
134
+
135
+ await queue.enqueueFromDatabase(storedId);
136
+
137
+ expect(deletes).toHaveLength(1);
138
+ const rid: any = deletes[0];
139
+ expect(String(rid.table)).toBe('_00_pending_mutations');
140
+ expect(rid.id).toBe('1785540216679_0005_c960958c-d6ed');
141
+ expect(String(rid.id)).not.toContain('⟨');
142
+ });
143
+
117
144
  it('a push timeout classifies as network, so it retries instead of rolling back', () => {
118
145
  const err = new Error('Mutation push timed out after 30000ms (create)');
119
146
  expect(classifySyncError(err)).toBe('network');
@@ -1,4 +1,4 @@
1
- import type { RecordId } from 'surrealdb';
1
+ import { RecordId } from 'surrealdb';
2
2
  import type { LocalStore } from '../../../services/database/index';
3
3
  import type { SyncQueueEventSystem } from '../events/index';
4
4
  import { createSyncQueueEventSystem, SyncQueueEventTypes } from '../events/index';
@@ -100,7 +100,7 @@ export class UpQueue {
100
100
  );
101
101
  try {
102
102
  await this.local.query(`DELETE $mutation_id`, {
103
- mutation_id: parseRecordIdString(mutationId),
103
+ mutation_id: parseStoredRecordId(mutationId),
104
104
  });
105
105
  } catch (error) {
106
106
  this.logger.error(
@@ -319,6 +319,25 @@ function encodeUpEventId(event: UpEvent): string {
319
319
  return encodeRecordId(event.mutation_id);
320
320
  }
321
321
 
322
+ /**
323
+ * Parse a record id READ BACK FROM THE STORE, stripping SurrealDB's `⟨⟩`
324
+ * escaping.
325
+ *
326
+ * Outbox ids contain `_` and `-`, so the store hands them back in escaped
327
+ * display form (`_00_pending_mutations:⟨1785…_0005_c960…⟩`). `parseRecordIdString`
328
+ * keeps that verbatim, and re-encoding then escapes the brackets AGAIN, so a
329
+ * `DELETE $mutation_id` built from a stored id targets
330
+ * `_00_pending_mutations:⟨⟨1785…\⟩⟩` and matches nothing. Every mutation
331
+ * replayed from the store was therefore un-deletable: its row survived a
332
+ * SUCCESSFUL push and got re-sent on the next boot.
333
+ */
334
+ function parseStoredRecordId(id: string): RecordId<string> {
335
+ const [table, ...rest] = id.split(':');
336
+ let raw = rest.join(':');
337
+ if (raw.startsWith('⟨') && raw.endsWith('⟩')) raw = raw.slice(1, -1);
338
+ return new RecordId(table, raw);
339
+ }
340
+
322
341
  /** Materialize one `_00_pending_mutations` row into an UpEvent. */
323
342
  function rowToUpEvent(r: any, logger: Logger): UpEvent | null {
324
343
  switch (r.mutationType) {
@@ -331,7 +350,7 @@ function rowToUpEvent(r: any, logger: Logger): UpEvent | null {
331
350
  if (r.data === undefined || r.data === null) return null;
332
351
  return {
333
352
  type: 'create',
334
- mutation_id: parseRecordIdString(r.id),
353
+ mutation_id: parseStoredRecordId(r.id),
335
354
  record_id: parseRecordIdString(r.recordId),
336
355
  data: r.data,
337
356
  tableName: extractTablePart(r.recordId),
@@ -339,7 +358,7 @@ function rowToUpEvent(r: any, logger: Logger): UpEvent | null {
339
358
  case 'update':
340
359
  return {
341
360
  type: 'update',
342
- mutation_id: parseRecordIdString(r.id),
361
+ mutation_id: parseStoredRecordId(r.id),
343
362
  record_id: parseRecordIdString(r.recordId),
344
363
  data: r.data,
345
364
  beforeRecord: r.beforeRecord,
@@ -347,7 +366,7 @@ function rowToUpEvent(r: any, logger: Logger): UpEvent | null {
347
366
  case 'delete':
348
367
  return {
349
368
  type: 'delete',
350
- mutation_id: parseRecordIdString(r.id),
369
+ mutation_id: parseStoredRecordId(r.id),
351
370
  record_id: parseRecordIdString(r.recordId),
352
371
  };
353
372
  default: