@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.
- package/AGENTS.md +57 -0
- package/dist/index.d.ts +2514 -58
- package/dist/index.js +12561 -2449
- package/dist/otel/index.d.ts +2 -2
- package/dist/otel/index.js +6 -6
- package/dist/sqlite-open.js +303 -0
- package/dist/sqlite-worker.d.ts +1 -0
- package/dist/sqlite-worker.js +439 -0
- package/dist/tabs-broker-worker.d.ts +8 -0
- package/dist/tabs-broker-worker.js +472 -0
- package/dist/types.d.ts +751 -11
- package/package.json +11 -7
- package/scripts/check-broker-bundle.mjs +33 -0
- package/skills/{spooky-core → sp00ky-core}/SKILL.md +12 -12
- package/skills/{spooky-core → sp00ky-core}/references/auth.md +1 -1
- package/skills/{spooky-core → sp00ky-core}/references/config.md +2 -2
- package/src/bucket-blurhash.test.ts +148 -0
- package/src/build-globals.d.ts +12 -0
- package/src/events/events.test.ts +2 -1
- package/src/events/index.ts +3 -0
- package/src/index.ts +36 -2
- package/src/modules/app-release/index.test.ts +125 -0
- package/src/modules/app-release/index.ts +201 -0
- package/src/modules/auth/auth.local-first.test.ts +101 -0
- package/src/modules/auth/events/index.ts +2 -1
- package/src/modules/auth/index.ts +127 -24
- package/src/modules/cache/cache.relay.test.ts +95 -0
- package/src/modules/cache/index.ts +163 -43
- package/src/modules/cache/types.ts +2 -2
- package/src/modules/crdt/crdt-field.ts +294 -0
- package/src/modules/crdt/crdt-hydration.test.ts +210 -0
- package/src/modules/crdt/crdt-reconnect.test.ts +195 -0
- package/src/modules/crdt/index.ts +463 -0
- package/src/modules/crdt/loro-loader.ts +25 -0
- package/src/modules/data/data.hydration.test.ts +142 -0
- package/src/modules/data/data.membership.test.ts +523 -0
- package/src/modules/data/data.notify-table.test.ts +41 -0
- package/src/modules/data/data.pending-ids.test.ts +199 -0
- package/src/modules/data/data.rebind.test.ts +170 -0
- package/src/modules/data/data.rematerialize.test.ts +114 -0
- package/src/modules/data/data.run.test.ts +113 -0
- package/src/modules/data/data.settled-writes.test.ts +206 -0
- package/src/modules/data/data.status.test.ts +249 -0
- package/src/modules/data/id-set-plan.test.ts +122 -0
- package/src/modules/data/index.ts +1815 -151
- package/src/modules/data/mutation-id.test.ts +25 -0
- package/src/modules/data/mutation-id.ts +35 -0
- package/src/modules/data/window-query.test.ts +52 -0
- package/src/modules/data/window-query.ts +194 -0
- package/src/modules/devtools/flags.ts +349 -0
- package/src/modules/devtools/index.ts +450 -46
- package/src/modules/devtools/notify-throttle.test.ts +154 -0
- package/src/modules/devtools/state-shape.test.ts +146 -0
- package/src/modules/devtools/storage-info.test.ts +79 -0
- package/src/modules/devtools/storage-info.ts +168 -0
- package/src/modules/devtools/versions.test.ts +74 -0
- package/src/modules/devtools/versions.ts +110 -0
- package/src/modules/feature-flag/index.test.ts +251 -0
- package/src/modules/feature-flag/index.ts +308 -0
- package/src/modules/ref-tables.test.ts +91 -0
- package/src/modules/ref-tables.ts +88 -0
- package/src/modules/sync/engine.ts +164 -82
- package/src/modules/sync/events/index.ts +9 -2
- package/src/modules/sync/queue/queue-down.test.ts +180 -0
- package/src/modules/sync/queue/queue-down.ts +80 -13
- package/src/modules/sync/queue/queue-up.forwarded.test.ts +164 -0
- package/src/modules/sync/queue/queue-up.ts +241 -57
- package/src/modules/sync/scheduler.pause.test.ts +109 -0
- package/src/modules/sync/scheduler.retry.test.ts +237 -0
- package/src/modules/sync/scheduler.ts +215 -13
- package/src/modules/sync/sync.cleanup.test.ts +116 -0
- package/src/modules/sync/sync.health.test.ts +149 -0
- package/src/modules/sync/sync.heartbeat.test.ts +80 -0
- package/src/modules/sync/sync.live-removal.test.ts +175 -0
- package/src/modules/sync/sync.reconnect.test.ts +145 -0
- package/src/modules/sync/sync.subquery.test.ts +82 -0
- package/src/modules/sync/sync.tabs.test.ts +249 -0
- package/src/modules/sync/sync.ts +1726 -99
- package/src/modules/sync/utils.test.ts +269 -2
- package/src/modules/sync/utils.ts +201 -17
- package/src/otel/index.ts +13 -10
- package/src/services/blobs/blob-cache.test.ts +359 -0
- package/src/services/blobs/blob-cache.ts +603 -0
- package/src/services/blobs/blob-manifest.ts +227 -0
- package/src/services/blobs/blob-store.test.ts +77 -0
- package/src/services/blobs/blob-store.ts +359 -0
- package/src/services/blobs/blob.fixture.ts +90 -0
- package/src/services/blobs/index.ts +70 -0
- package/src/services/database/cache-engine.ts +193 -0
- package/src/services/database/connection-supervisor.test.ts +289 -0
- package/src/services/database/connection-supervisor.ts +415 -0
- package/src/services/database/database.query-timeout.test.ts +83 -0
- package/src/services/database/database.ts +41 -12
- package/src/services/database/engine-factory.ts +33 -0
- package/src/services/database/errors.ts +34 -0
- package/src/services/database/events/index.ts +2 -1
- package/src/services/database/index.ts +7 -0
- package/src/services/database/local-migrator.ts +30 -27
- package/src/services/database/local.test.ts +64 -0
- package/src/services/database/local.ts +484 -67
- package/src/services/database/plan-render.test.ts +159 -0
- package/src/services/database/plan-render.ts +108 -0
- package/src/services/database/relation-resolver.test.ts +413 -0
- package/src/services/database/relation-resolver.ts +0 -0
- package/src/services/database/remote.ts +110 -14
- package/src/services/database/sqlite-cache-engine.test.ts +616 -0
- package/src/services/database/sqlite-cache-engine.timeout.test.ts +61 -0
- package/src/services/database/sqlite-cache-engine.ts +1358 -0
- package/src/services/database/sqlite-devtools-queries.integration.test.ts +143 -0
- package/src/services/database/sqlite-devtools-queries.test.ts +154 -0
- package/src/services/database/sqlite-lock-verify.test.ts +33 -0
- package/src/services/database/sqlite-lock-verify.ts +45 -0
- package/src/services/database/sqlite-open.test.ts +150 -0
- package/src/services/database/sqlite-open.ts +164 -0
- package/src/services/database/sqlite-plan-sql.test.ts +104 -0
- package/src/services/database/sqlite-plan-sql.ts +138 -0
- package/src/services/database/sqlite-projection.test.ts +99 -0
- package/src/services/database/sqlite-select.integration.test.ts +185 -0
- package/src/services/database/sqlite-select.test.ts +246 -0
- package/src/services/database/sqlite-select.ts +131 -0
- package/src/services/database/sqlite-transport.fixture.ts +30 -0
- package/src/services/database/sqlite-transport.ts +224 -0
- package/src/services/database/sqlite-worker.ts +437 -0
- package/src/services/database/surql-translate.ts +416 -0
- package/src/services/database/surreal-cache-engine.ts +161 -0
- package/src/services/logger/index.ts +3 -2
- package/src/services/persistence/localstorage.ts +2 -2
- package/src/services/persistence/resilient.ts +11 -4
- package/src/services/persistence/surrealdb.ts +10 -10
- package/src/services/stream-processor/index.ts +796 -84
- package/src/services/stream-processor/permissions.test.ts +47 -0
- package/src/services/stream-processor/permissions.ts +53 -0
- package/src/services/stream-processor/stream-processor.batch.test.ts +186 -0
- package/src/services/stream-processor/stream-processor.prime.test.ts +198 -0
- package/src/services/stream-processor/stream-processor.reset.test.ts +226 -0
- package/src/services/stream-processor/stream-processor.test.ts +1 -1
- package/src/services/stream-processor/wasm-types.ts +59 -3
- package/src/services/tabs/broker-client.ts +283 -0
- package/src/services/tabs/broker.test.ts +327 -0
- package/src/services/tabs/coordinator.test.ts +365 -0
- package/src/services/tabs/coordinator.ts +633 -0
- package/src/services/tabs/fake-ports.fixture.ts +112 -0
- package/src/services/tabs/leader-locks.ts +75 -0
- package/src/services/tabs/protocol.ts +258 -0
- package/src/services/tabs/support.ts +36 -0
- package/src/services/tabs/tabs-broker-worker.ts +640 -0
- package/src/sp00ky.auth-order.test.ts +92 -0
- package/src/sp00ky.init-query.test.ts +183 -0
- package/src/sp00ky.local-first.test.ts +60 -0
- package/src/sp00ky.ts +1693 -0
- package/src/types.ts +528 -13
- package/src/utils/blurhash.ts +90 -0
- package/src/utils/error-classification.test.ts +44 -0
- package/src/utils/error-classification.ts +7 -0
- package/src/utils/index.ts +79 -13
- package/src/utils/parser.test.ts +49 -120
- package/src/utils/parser.ts +32 -2
- package/src/utils/semver.test.ts +32 -0
- package/src/utils/semver.ts +30 -0
- package/src/utils/surql.ts +30 -18
- package/src/utils/withRetry.test.ts +1 -1
- package/tsdown.config.ts +86 -1
- 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 {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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:
|
|
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: '
|
|
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
|
|
47
|
-
DEFINE TABLE IF NOT EXISTS
|
|
48
|
-
DEFINE TABLE IF NOT EXISTS
|
|
49
|
-
DEFINE TABLE IF NOT EXISTS
|
|
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: '
|
|
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: '
|
|
74
|
+
{ Category: 'sp00ky-client::LocalMigrator::provision' },
|
|
72
75
|
`[Provisioning] (${i + 1}/${statements.length}) Executing: ${statement.substring(0, 50)}...`
|
|
73
76
|
);
|
|
74
|
-
await this.localDb.
|
|
77
|
+
await this.localDb.queryUngated(statement);
|
|
75
78
|
this.logger.info(
|
|
76
|
-
{ Category: '
|
|
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: '
|
|
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.
|
|
94
|
-
`SELECT hash, created_at FROM ONLY
|
|
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 (
|
|
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.
|
|
105
|
-
} catch (
|
|
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.
|
|
111
|
-
USE DB
|
|
113
|
+
await this.localDb.queryUngated(`
|
|
114
|
+
USE DB _00_temp;
|
|
112
115
|
REMOVE DATABASE ${database};
|
|
113
116
|
`);
|
|
114
|
-
} catch (
|
|
117
|
+
} catch (_e) {
|
|
115
118
|
// Ignore error if database doesn't exist
|
|
116
119
|
}
|
|
117
120
|
|
|
118
|
-
await this.localDb.
|
|
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.
|
|
199
|
-
`UPSERT
|
|
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
|
+
});
|