@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:
|
|
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:
|
|
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:
|
|
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:
|
|
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.
|
|
5804
|
-
const WASM_VERSION = "0.0.1-canary.
|
|
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.
|
|
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.
|
|
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.
|
|
64
|
-
"@spooky-sync/ssp-wasm": "0.0.1-canary.
|
|
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
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
369
|
+
mutation_id: parseStoredRecordId(r.id),
|
|
351
370
|
record_id: parseRecordIdString(r.recordId),
|
|
352
371
|
};
|
|
353
372
|
default:
|