@spooky-sync/core 0.0.0-canary.1 → 0.0.1-alpha.2
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 +77 -0
- package/README.md +56 -16
- package/dist/index.d.ts +2179 -411
- package/dist/index.js +12509 -2475
- package/dist/otel/index.d.ts +21 -0
- package/dist/otel/index.js +86 -0
- package/dist/sqlite-open.js +322 -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 +1060 -0
- package/package.json +49 -12
- package/scripts/check-broker-bundle.mjs +33 -0
- package/skills/sp00ky-core/SKILL.md +276 -0
- package/skills/sp00ky-core/references/auth.md +98 -0
- package/skills/sp00ky-core/references/config.md +76 -0
- package/src/boot/auth-flip.saga.test.ts +82 -0
- package/src/boot/auth-flip.saga.ts +62 -0
- package/src/boot/boot.saga.test.ts +151 -0
- package/src/boot/boot.saga.ts +128 -0
- package/src/boot/bucket-switch.saga.test.ts +158 -0
- package/src/boot/bucket-switch.saga.ts +115 -0
- package/src/boot/preload.saga.test.ts +51 -0
- package/src/boot/preload.saga.ts +31 -0
- package/src/bucket-blurhash.test.ts +148 -0
- package/src/build-globals.d.ts +12 -0
- package/src/client/bucket-handle.test.ts +117 -0
- package/src/client/bucket-handle.ts +227 -0
- package/src/client/router.test.ts +56 -0
- package/src/client/router.ts +94 -0
- package/src/client/runtime.test.ts +192 -0
- package/src/client/runtime.ts +296 -0
- package/src/client/services.ts +349 -0
- package/src/client/sp00ky-client.test.ts +241 -0
- package/src/client/sp00ky-client.ts +482 -0
- package/src/events/events.test.ts +2 -1
- package/src/events/index.ts +3 -0
- package/src/index.ts +37 -1
- package/src/kernel/constants.test.ts +19 -0
- package/src/kernel/constants.ts +54 -0
- package/src/kernel/effects.test.ts +40 -0
- package/src/kernel/effects.ts +159 -0
- package/src/kernel/events.ts +55 -0
- package/src/kernel/interpreter.test.ts +116 -0
- package/src/kernel/interpreter.ts +143 -0
- package/src/kernel/saga.test.ts +89 -0
- package/src/kernel/saga.ts +73 -0
- package/src/modules/app-release/index.test.ts +125 -0
- package/src/modules/app-release/index.ts +209 -0
- package/src/modules/auth/auth.local-first.test.ts +141 -0
- package/src/modules/auth/events/index.ts +2 -1
- package/src/modules/auth/index.ts +138 -25
- 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/devtools/flags.ts +349 -0
- package/src/modules/devtools/index.ts +459 -47
- package/src/modules/devtools/notify-throttle.test.ts +154 -0
- package/src/modules/devtools/state-shape.test.ts +149 -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 +316 -0
- package/src/modules/ref-tables.test.ts +91 -0
- package/src/modules/ref-tables.ts +88 -0
- package/src/mutation/jobs.test.ts +25 -0
- package/src/mutation/jobs.ts +41 -0
- package/src/mutation/mutation-id.test.ts +37 -0
- package/src/mutation/mutation-id.ts +35 -0
- package/src/mutation/push.saga.test.ts +285 -0
- package/src/mutation/push.saga.ts +215 -0
- package/src/mutation/rows.test.ts +201 -0
- package/src/mutation/rows.ts +377 -0
- package/src/mutation/tray.saga.test.ts +63 -0
- package/src/mutation/tray.saga.ts +48 -0
- package/src/mutation/write.saga.test.ts +163 -0
- package/src/mutation/write.saga.ts +166 -0
- package/src/otel/index.ts +127 -0
- package/src/query/env.test.ts +23 -0
- package/src/query/env.ts +56 -0
- package/src/query/fetch.saga.test.ts +156 -0
- package/src/query/fetch.saga.ts +109 -0
- package/src/query/hash.test.ts +14 -0
- package/src/query/hash.ts +18 -0
- package/src/query/id-set-plan.test.ts +132 -0
- package/src/query/lifecycle.saga.test.ts +122 -0
- package/src/query/lifecycle.saga.ts +101 -0
- package/src/query/materialize.saga.test.ts +82 -0
- package/src/query/materialize.saga.ts +75 -0
- package/src/query/materialize.test.ts +50 -0
- package/src/query/materialize.ts +44 -0
- package/src/query/membership.saga.test.ts +246 -0
- package/src/query/membership.saga.ts +208 -0
- package/src/query/membership.test.ts +118 -0
- package/src/query/membership.ts +158 -0
- package/src/query/register.saga.test.ts +251 -0
- package/src/query/register.saga.ts +190 -0
- package/src/query/render-set.test.ts +36 -0
- package/src/query/render-set.ts +60 -0
- package/src/query/sql.test.ts +62 -0
- package/src/query/sql.ts +79 -0
- package/src/query/window-query.test.ts +70 -0
- package/src/query/window-query.ts +195 -0
- 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 +318 -0
- package/src/services/database/connection-supervisor.ts +422 -0
- package/src/services/database/database.query-timeout.test.ts +140 -0
- package/src/services/database/database.responses.test.ts +43 -0
- package/src/services/database/database.ts +145 -62
- 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 +467 -0
- package/src/services/database/relation-resolver.ts +0 -0
- package/src/services/database/remote.ts +222 -15
- package/src/services/database/sqlite-cache-engine.test.ts +617 -0
- package/src/services/database/sqlite-cache-engine.timeout.test.ts +61 -0
- package/src/services/database/sqlite-cache-engine.ts +1349 -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 +6 -101
- package/src/services/persistence/localstorage.ts +2 -2
- package/src/services/persistence/resilient.ts +41 -0
- 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 +262 -0
- package/src/services/tabs/support.ts +36 -0
- package/src/services/tabs/tabs-broker-worker.ts +640 -0
- package/src/sp00ky.ts +5 -0
- package/src/state/client-state.ts +184 -0
- package/src/state/lifecycle.test.ts +118 -0
- package/src/state/lifecycle.ts +86 -0
- package/src/state/reducers.test.ts +280 -0
- package/src/state/reducers.ts +476 -0
- package/src/state/selectors.test.ts +149 -0
- package/src/state/selectors.ts +184 -0
- package/src/sync/connection.saga.test.ts +86 -0
- package/src/sync/connection.saga.ts +85 -0
- package/src/sync/live.saga.test.ts +69 -0
- package/src/sync/live.saga.ts +49 -0
- package/src/sync/policy.health.test.ts +56 -0
- package/src/sync/policy.test.ts +659 -0
- package/src/sync/policy.ts +502 -0
- package/src/sync/poll.saga.test.ts +51 -0
- package/src/sync/poll.saga.ts +36 -0
- package/src/sync/tabs.saga.test.ts +74 -0
- package/src/sync/tabs.saga.ts +92 -0
- package/src/testing/adapters.ts +133 -0
- package/src/testing/build.ts +61 -0
- package/src/testing/fake-services.ts +34 -0
- package/src/testing/run-pure.test.ts +92 -0
- package/src/testing/run-pure.ts +128 -0
- package/src/testing/services.ts +17 -0
- package/src/types.ts +557 -16
- package/src/utils/blurhash.ts +90 -0
- package/src/utils/error-classification.test.ts +54 -0
- package/src/utils/error-classification.ts +25 -0
- package/src/utils/index.ts +86 -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/utils.test.ts +2 -1
- package/src/utils/withRetry.test.ts +1 -1
- package/tsdown.config.ts +86 -1
- package/vitest.config.ts +48 -0
- package/src/modules/cache/index.ts +0 -241
- package/src/modules/cache/types.ts +0 -19
- package/src/modules/data/data.test.ts +0 -58
- package/src/modules/data/index.ts +0 -777
- package/src/modules/sync/engine.ts +0 -163
- package/src/modules/sync/events/index.ts +0 -77
- package/src/modules/sync/index.ts +0 -3
- package/src/modules/sync/queue/index.ts +0 -2
- package/src/modules/sync/queue/queue-down.ts +0 -89
- package/src/modules/sync/queue/queue-up.ts +0 -223
- package/src/modules/sync/scheduler.ts +0 -84
- package/src/modules/sync/sync.ts +0 -407
- package/src/modules/sync/utils.test.ts +0 -311
- package/src/modules/sync/utils.ts +0 -171
- package/src/spooky.ts +0 -346
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
import { RecordId } from 'surrealdb';
|
|
2
|
-
import { RecordVersionArray, RecordVersionDiff } from '../../types';
|
|
3
|
-
import { parseRecordIdString, encodeRecordId } from '../../utils/index';
|
|
4
|
-
|
|
5
|
-
export class ArraySyncer {
|
|
6
|
-
private localArray: RecordVersionArray;
|
|
7
|
-
private remoteArray: RecordVersionArray;
|
|
8
|
-
private needsSort = false;
|
|
9
|
-
|
|
10
|
-
constructor(localArray: RecordVersionArray, remoteArray: RecordVersionArray) {
|
|
11
|
-
this.remoteArray = remoteArray.sort((a, b) => a[0].localeCompare(b[0]));
|
|
12
|
-
this.localArray = localArray.sort((a, b) => a[0].localeCompare(b[0]));
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Inserts an item into the local array
|
|
17
|
-
*/
|
|
18
|
-
insert(recordId: string, version: number) {
|
|
19
|
-
this.localArray.push([recordId, version]);
|
|
20
|
-
this.needsSort = true;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Updates the current local RecordVersionArray state.
|
|
25
|
-
*/
|
|
26
|
-
update(recordId: string, version: number) {
|
|
27
|
-
this.localArray = this.localArray.map((record) => {
|
|
28
|
-
if (record[0] === recordId) {
|
|
29
|
-
this.needsSort = true;
|
|
30
|
-
return [recordId, version];
|
|
31
|
-
}
|
|
32
|
-
return record;
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Deletes an item from the local array
|
|
38
|
-
*/
|
|
39
|
-
delete(recordId: string) {
|
|
40
|
-
this.localArray = this.localArray.filter((record) => record[0] !== recordId);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Returns the difference between the local and remote arrays.
|
|
45
|
-
* Includes sets of added, updated, and removed records.
|
|
46
|
-
*/
|
|
47
|
-
nextSet(): RecordVersionDiff | null {
|
|
48
|
-
if (this.needsSort) {
|
|
49
|
-
this.localArray.sort((a, b) => a[0].localeCompare(b[0]));
|
|
50
|
-
this.needsSort = false;
|
|
51
|
-
}
|
|
52
|
-
console.log('xxxx555', this.localArray, this.remoteArray);
|
|
53
|
-
const diff = diffRecordVersionArray(this.localArray, this.remoteArray);
|
|
54
|
-
return diff;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function diffRecordVersionArray(
|
|
59
|
-
local: RecordVersionArray | null,
|
|
60
|
-
remote: RecordVersionArray | null
|
|
61
|
-
): RecordVersionDiff {
|
|
62
|
-
const localArray = local || [];
|
|
63
|
-
const remoteArray = remote || [];
|
|
64
|
-
|
|
65
|
-
// Convert arrays to Maps for O(1) lookup
|
|
66
|
-
const localMap = new Map<string, number>(localArray);
|
|
67
|
-
const remoteMap = new Map<string, number>(remoteArray);
|
|
68
|
-
|
|
69
|
-
const added: string[] = [];
|
|
70
|
-
const updated: string[] = [];
|
|
71
|
-
const removed: string[] = [];
|
|
72
|
-
|
|
73
|
-
// Find added and updated records
|
|
74
|
-
for (const [recordId, remoteVersion] of remoteMap) {
|
|
75
|
-
const localVersion = localMap.get(recordId);
|
|
76
|
-
|
|
77
|
-
if (localVersion === undefined) {
|
|
78
|
-
// Record exists in remote but not in local
|
|
79
|
-
added.push(recordId);
|
|
80
|
-
} else if (localVersion < remoteVersion) {
|
|
81
|
-
// Record exists in both but remote has newer version
|
|
82
|
-
updated.push(recordId);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Find removed records
|
|
87
|
-
for (const [recordId] of localMap) {
|
|
88
|
-
if (!remoteMap.has(recordId)) {
|
|
89
|
-
removed.push(recordId);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return {
|
|
94
|
-
added: added.map((id) => ({
|
|
95
|
-
id: parseRecordIdString(id),
|
|
96
|
-
version: remoteMap.get(id)!,
|
|
97
|
-
})),
|
|
98
|
-
updated: updated.map((id) => ({
|
|
99
|
-
id: parseRecordIdString(id),
|
|
100
|
-
version: remoteMap.get(id)!,
|
|
101
|
-
})),
|
|
102
|
-
removed: removed.map(parseRecordIdString),
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Applies a RecordVersionDiff to a RecordVersionArray and returns a new sorted array.
|
|
108
|
-
*/
|
|
109
|
-
export function applyRecordVersionDiff(
|
|
110
|
-
current: RecordVersionArray,
|
|
111
|
-
diff: RecordVersionDiff
|
|
112
|
-
): RecordVersionArray {
|
|
113
|
-
const currentMap = new Map(current);
|
|
114
|
-
|
|
115
|
-
// Apply removals
|
|
116
|
-
for (const id of diff.removed) {
|
|
117
|
-
currentMap.delete(encodeRecordId(id));
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Apply additions
|
|
121
|
-
for (const item of diff.added) {
|
|
122
|
-
currentMap.set(encodeRecordId(item.id), item.version);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Apply updates
|
|
126
|
-
for (const item of diff.updated) {
|
|
127
|
-
currentMap.set(encodeRecordId(item.id), item.version);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return Array.from(currentMap).sort((a, b) => a[0].localeCompare(b[0]));
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export function createDiffFromDbOp(
|
|
134
|
-
op: 'CREATE' | 'UPDATE' | 'DELETE',
|
|
135
|
-
recordId: RecordId,
|
|
136
|
-
version: number,
|
|
137
|
-
versions?: RecordVersionArray
|
|
138
|
-
): RecordVersionDiff {
|
|
139
|
-
// Version guard: skip stale CREATE/UPDATE, but always process DELETE
|
|
140
|
-
if (op !== 'DELETE') {
|
|
141
|
-
const old = versions?.find((record) => record[0] === encodeRecordId(recordId));
|
|
142
|
-
|
|
143
|
-
if (old && old[1] >= version) {
|
|
144
|
-
return {
|
|
145
|
-
added: [],
|
|
146
|
-
updated: [],
|
|
147
|
-
removed: [],
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (op === 'CREATE') {
|
|
153
|
-
return {
|
|
154
|
-
added: [{ id: recordId, version }],
|
|
155
|
-
updated: [],
|
|
156
|
-
removed: [],
|
|
157
|
-
};
|
|
158
|
-
} else if (op === 'UPDATE') {
|
|
159
|
-
return {
|
|
160
|
-
added: [],
|
|
161
|
-
updated: [{ id: recordId, version }],
|
|
162
|
-
removed: [],
|
|
163
|
-
};
|
|
164
|
-
} else {
|
|
165
|
-
return {
|
|
166
|
-
added: [],
|
|
167
|
-
updated: [],
|
|
168
|
-
removed: [recordId],
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
}
|
package/src/spooky.ts
DELETED
|
@@ -1,346 +0,0 @@
|
|
|
1
|
-
import { DataModule } from './modules/data/index';
|
|
2
|
-
import {
|
|
3
|
-
SpookyConfig,
|
|
4
|
-
QueryTimeToLive,
|
|
5
|
-
SpookyQueryResultPromise,
|
|
6
|
-
PersistenceClient,
|
|
7
|
-
MutationEvent,
|
|
8
|
-
UpdateOptions,
|
|
9
|
-
RunOptions,
|
|
10
|
-
} from './types';
|
|
11
|
-
import {
|
|
12
|
-
LocalDatabaseService,
|
|
13
|
-
LocalMigrator,
|
|
14
|
-
RemoteDatabaseService,
|
|
15
|
-
} from './services/database/index';
|
|
16
|
-
import { Surreal } from 'surrealdb';
|
|
17
|
-
import { SpookySync, UpEvent } from './modules/sync/index';
|
|
18
|
-
import {
|
|
19
|
-
GetTable,
|
|
20
|
-
InnerQuery,
|
|
21
|
-
QueryBuilder,
|
|
22
|
-
QueryOptions,
|
|
23
|
-
SchemaStructure,
|
|
24
|
-
TableModel,
|
|
25
|
-
TableNames,
|
|
26
|
-
BackendNames,
|
|
27
|
-
BackendRoutes,
|
|
28
|
-
RoutePayload,
|
|
29
|
-
} from '@spooky-sync/query-builder';
|
|
30
|
-
|
|
31
|
-
import { DevToolsService } from './modules/devtools/index';
|
|
32
|
-
import { createLogger } from './services/logger/index';
|
|
33
|
-
import { AuthService } from './modules/auth/index';
|
|
34
|
-
import { StreamProcessorService } from './services/stream-processor/index';
|
|
35
|
-
import { EventSystem } from './events/index';
|
|
36
|
-
import { CacheModule } from './modules/cache/index';
|
|
37
|
-
import { LocalStoragePersistenceClient } from './services/persistence/localstorage';
|
|
38
|
-
import { generateId, parseParams } from './utils/index';
|
|
39
|
-
import { SurrealDBPersistenceClient } from './services/persistence/surrealdb';
|
|
40
|
-
|
|
41
|
-
export class SpookyClient<S extends SchemaStructure> {
|
|
42
|
-
private local: LocalDatabaseService;
|
|
43
|
-
private remote: RemoteDatabaseService;
|
|
44
|
-
private persistenceClient: PersistenceClient;
|
|
45
|
-
|
|
46
|
-
private migrator: LocalMigrator;
|
|
47
|
-
private cache: CacheModule;
|
|
48
|
-
private dataModule: DataModule<S>;
|
|
49
|
-
private sync: SpookySync<S>;
|
|
50
|
-
private devTools: DevToolsService;
|
|
51
|
-
|
|
52
|
-
private logger: ReturnType<typeof createLogger>;
|
|
53
|
-
public auth: AuthService<S>;
|
|
54
|
-
public streamProcessor: StreamProcessorService;
|
|
55
|
-
|
|
56
|
-
get remoteClient() {
|
|
57
|
-
return this.remote.getClient();
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
get localClient() {
|
|
61
|
-
return this.local.getClient();
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
get pendingMutationCount(): number {
|
|
65
|
-
return this.sync.pendingMutationCount;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
subscribeToPendingMutations(cb: (count: number) => void): () => void {
|
|
69
|
-
return this.sync.subscribeToPendingMutations(cb);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
constructor(private config: SpookyConfig<S>) {
|
|
73
|
-
const logger = createLogger(config.logLevel ?? 'info', config.otelEndpoint);
|
|
74
|
-
this.logger = logger.child({ service: 'SpookyClient' });
|
|
75
|
-
|
|
76
|
-
this.logger.info(
|
|
77
|
-
{
|
|
78
|
-
config: { ...config, schema: '[SchemaStructure]' },
|
|
79
|
-
Category: 'spooky-client::SpookyClient::constructor',
|
|
80
|
-
},
|
|
81
|
-
'SpookyClient initialized'
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
this.local = new LocalDatabaseService(this.config.database, logger);
|
|
85
|
-
this.remote = new RemoteDatabaseService(this.config.database, logger);
|
|
86
|
-
|
|
87
|
-
if (config.persistenceClient === 'surrealdb') {
|
|
88
|
-
this.persistenceClient = new SurrealDBPersistenceClient(this.local, logger);
|
|
89
|
-
} else if (config.persistenceClient === 'localstorage' || !config.persistenceClient) {
|
|
90
|
-
this.persistenceClient = new LocalStoragePersistenceClient(logger);
|
|
91
|
-
} else {
|
|
92
|
-
this.persistenceClient = config.persistenceClient;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
this.streamProcessor = new StreamProcessorService(
|
|
96
|
-
new EventSystem(['stream_update']),
|
|
97
|
-
this.local,
|
|
98
|
-
this.persistenceClient,
|
|
99
|
-
logger
|
|
100
|
-
);
|
|
101
|
-
this.migrator = new LocalMigrator(this.local, logger);
|
|
102
|
-
|
|
103
|
-
this.cache = new CacheModule(
|
|
104
|
-
this.local,
|
|
105
|
-
this.streamProcessor,
|
|
106
|
-
(update) => {
|
|
107
|
-
// Direct callback from cache to data module
|
|
108
|
-
this.dataModule.onStreamUpdate(update);
|
|
109
|
-
},
|
|
110
|
-
logger
|
|
111
|
-
);
|
|
112
|
-
|
|
113
|
-
this.dataModule = new DataModule(
|
|
114
|
-
this.cache,
|
|
115
|
-
this.local,
|
|
116
|
-
this.config.schema,
|
|
117
|
-
logger,
|
|
118
|
-
this.config.streamDebounceTime
|
|
119
|
-
);
|
|
120
|
-
|
|
121
|
-
// Initialize Auth
|
|
122
|
-
this.auth = new AuthService(this.config.schema, this.remote, this.persistenceClient, logger);
|
|
123
|
-
|
|
124
|
-
// Initialize Sync
|
|
125
|
-
this.sync = new SpookySync(this.local, this.remote, this.cache, this.dataModule, this.config.schema, this.logger);
|
|
126
|
-
|
|
127
|
-
// Initialize DevTools
|
|
128
|
-
this.devTools = new DevToolsService(
|
|
129
|
-
this.local,
|
|
130
|
-
this.remote,
|
|
131
|
-
logger,
|
|
132
|
-
this.config.schema,
|
|
133
|
-
this.auth,
|
|
134
|
-
this.dataModule
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
// Register DevTools as a receiver for stream updates
|
|
138
|
-
this.streamProcessor.addReceiver(this.devTools);
|
|
139
|
-
|
|
140
|
-
// Wire up callbacks instead of events
|
|
141
|
-
this.setupCallbacks();
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Setup direct callbacks instead of event subscriptions
|
|
146
|
-
*/
|
|
147
|
-
private setupCallbacks() {
|
|
148
|
-
// Mutation callback for sync
|
|
149
|
-
this.dataModule.onMutation((mutations: UpEvent[]) => {
|
|
150
|
-
// Notify DevTools
|
|
151
|
-
this.devTools.onMutation(mutations);
|
|
152
|
-
|
|
153
|
-
// Enqueue in Sync
|
|
154
|
-
if (mutations.length > 0) {
|
|
155
|
-
this.sync.enqueueMutation(mutations);
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
// Sync events for incoming updates
|
|
160
|
-
this.sync.events.subscribe('SYNC_QUERY_UPDATED', (event: any) => {
|
|
161
|
-
this.devTools.logEvent('SYNC_QUERY_UPDATED', event.payload);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
// Database events for DevTools
|
|
165
|
-
this.local.getEvents().subscribe('DATABASE_LOCAL_QUERY', (event: any) => {
|
|
166
|
-
this.devTools.logEvent('LOCAL_QUERY', event.payload);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
this.remote.getEvents().subscribe('DATABASE_REMOTE_QUERY', (event: any) => {
|
|
170
|
-
this.devTools.logEvent('REMOTE_QUERY', event.payload);
|
|
171
|
-
});
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async init() {
|
|
175
|
-
this.logger.info(
|
|
176
|
-
{ Category: 'spooky-client::SpookyClient::init' },
|
|
177
|
-
'SpookyClient initialization started'
|
|
178
|
-
);
|
|
179
|
-
try {
|
|
180
|
-
const clientId = this.config.clientId ?? (await this.loadOrGenerateClientId());
|
|
181
|
-
this.persistClientId(clientId);
|
|
182
|
-
this.logger.debug(
|
|
183
|
-
{ clientId, Category: 'spooky-client::SpookyClient::init' },
|
|
184
|
-
'Client ID loaded'
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
await this.local.connect();
|
|
188
|
-
this.logger.debug(
|
|
189
|
-
{ Category: 'spooky-client::SpookyClient::init' },
|
|
190
|
-
'Local database connected'
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
await this.migrator.provision(this.config.schemaSurql);
|
|
194
|
-
this.logger.debug({ Category: 'spooky-client::SpookyClient::init' }, 'Schema provisioned');
|
|
195
|
-
|
|
196
|
-
await this.remote.connect();
|
|
197
|
-
this.logger.debug(
|
|
198
|
-
{ Category: 'spooky-client::SpookyClient::init' },
|
|
199
|
-
'Remote database connected'
|
|
200
|
-
);
|
|
201
|
-
|
|
202
|
-
await this.streamProcessor.init();
|
|
203
|
-
this.logger.debug(
|
|
204
|
-
{ Category: 'spooky-client::SpookyClient::init' },
|
|
205
|
-
'StreamProcessor initialized'
|
|
206
|
-
);
|
|
207
|
-
|
|
208
|
-
await this.auth.init();
|
|
209
|
-
this.logger.debug({ Category: 'spooky-client::SpookyClient::init' }, 'Auth initialized');
|
|
210
|
-
|
|
211
|
-
await this.dataModule.init();
|
|
212
|
-
this.logger.debug(
|
|
213
|
-
{ Category: 'spooky-client::SpookyClient::init' },
|
|
214
|
-
'DataModule initialized'
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
await this.sync.init(clientId);
|
|
218
|
-
this.logger.debug({ Category: 'spooky-client::SpookyClient::init' }, 'Sync initialized');
|
|
219
|
-
|
|
220
|
-
this.logger.info(
|
|
221
|
-
{ Category: 'spooky-client::SpookyClient::init' },
|
|
222
|
-
'SpookyClient initialization completed successfully'
|
|
223
|
-
);
|
|
224
|
-
} catch (e) {
|
|
225
|
-
this.logger.error(
|
|
226
|
-
{ error: e, Category: 'spooky-client::SpookyClient::init' },
|
|
227
|
-
'SpookyClient initialization failed'
|
|
228
|
-
);
|
|
229
|
-
throw e;
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
async close() {
|
|
234
|
-
await this.local.close();
|
|
235
|
-
await this.remote.close();
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
authenticate(token: string) {
|
|
239
|
-
return this.remote.getClient().authenticate(token);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
deauthenticate() {
|
|
243
|
-
return this.remote.getClient().invalidate();
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
query<Table extends TableNames<S>>(
|
|
247
|
-
table: Table,
|
|
248
|
-
options: QueryOptions<TableModel<GetTable<S, Table>>, false>,
|
|
249
|
-
ttl: QueryTimeToLive = '10m'
|
|
250
|
-
): QueryBuilder<S, Table, SpookyQueryResultPromise> {
|
|
251
|
-
return new QueryBuilder<S, Table, SpookyQueryResultPromise>(
|
|
252
|
-
this.config.schema,
|
|
253
|
-
table,
|
|
254
|
-
async (q) => ({
|
|
255
|
-
hash: await this.initQuery(table, q, ttl),
|
|
256
|
-
}),
|
|
257
|
-
options
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
private async initQuery<Table extends TableNames<S>>(
|
|
262
|
-
table: Table,
|
|
263
|
-
q: InnerQuery<any, any, any>,
|
|
264
|
-
ttl: QueryTimeToLive
|
|
265
|
-
) {
|
|
266
|
-
const tableSchema = this.config.schema.tables.find((t) => t.name === table);
|
|
267
|
-
if (!tableSchema) {
|
|
268
|
-
throw new Error(`Table ${table} not found`);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
const hash = await this.dataModule.query(
|
|
272
|
-
table,
|
|
273
|
-
q.selectQuery.query,
|
|
274
|
-
parseParams(tableSchema.columns, q.selectQuery.vars ?? {}),
|
|
275
|
-
ttl
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
await this.sync.enqueueDownEvent({
|
|
279
|
-
type: 'register',
|
|
280
|
-
payload: {
|
|
281
|
-
hash,
|
|
282
|
-
},
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
return hash;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
async queryRaw(sql: string, params: Record<string, any>, ttl: QueryTimeToLive) {
|
|
289
|
-
const tableName = sql.split('FROM ')[1].split(' ')[0];
|
|
290
|
-
return this.dataModule.query(tableName, sql, params, ttl);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
async subscribe(
|
|
294
|
-
queryHash: string,
|
|
295
|
-
callback: (records: Record<string, any>[]) => void,
|
|
296
|
-
options?: { immediate?: boolean }
|
|
297
|
-
): Promise<() => void> {
|
|
298
|
-
return this.dataModule.subscribe(queryHash, callback, options);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
run<
|
|
302
|
-
B extends BackendNames<S>,
|
|
303
|
-
R extends BackendRoutes<S, B>,
|
|
304
|
-
>(backend: B, path: R, payload: RoutePayload<S, B, R>, options?: RunOptions) {
|
|
305
|
-
return this.dataModule.run(backend, path, payload, options);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
create(id: string, data: Record<string, unknown>) {
|
|
309
|
-
return this.dataModule.create(id, data);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
update(table: string, id: string, data: Record<string, unknown>, options?: UpdateOptions) {
|
|
313
|
-
return this.dataModule.update(table, id, data, options);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
delete(table: string, id: string) {
|
|
317
|
-
return this.dataModule.delete(table, id);
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
async useRemote<T>(fn: (client: Surreal) => Promise<T> | T): Promise<T> {
|
|
321
|
-
return fn(this.remote.getClient());
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
private persistClientId(id: string) {
|
|
325
|
-
try {
|
|
326
|
-
this.persistenceClient.set('spooky_client_id', id);
|
|
327
|
-
} catch (e) {
|
|
328
|
-
this.logger.warn(
|
|
329
|
-
{ error: e, Category: 'spooky-client::SpookyClient::persistClientId' },
|
|
330
|
-
'Failed to persist client ID'
|
|
331
|
-
);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
private async loadOrGenerateClientId(): Promise<string> {
|
|
336
|
-
const clientId = await this.persistenceClient.get<string>('spooky_client_id');
|
|
337
|
-
|
|
338
|
-
if (clientId) {
|
|
339
|
-
return clientId;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
const newId = generateId();
|
|
343
|
-
await this.persistClientId(newId);
|
|
344
|
-
return newId;
|
|
345
|
-
}
|
|
346
|
-
}
|