@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.
|
|
5803
|
-
const WASM_VERSION = "0.0.1-canary.
|
|
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.
|
|
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.
|
|
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.
|
|
64
|
-
"@spooky-sync/ssp-wasm": "0.0.1-canary.
|
|
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
|
|
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>(
|
|
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.
|
|
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({
|
|
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
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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
|
-
|
|
292
|
-
|
|
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
|
});
|