@spooky-sync/core 0.0.1-canary.21 → 0.0.1-canary.211
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,20 +1,47 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Diagnostic,
|
|
3
|
+
SurrealEvents} from 'surrealdb';
|
|
1
4
|
import {
|
|
2
5
|
applyDiagnostics,
|
|
3
6
|
createRemoteEngines,
|
|
4
|
-
Diagnostic,
|
|
5
7
|
Surreal,
|
|
6
|
-
SurrealTransaction,
|
|
7
8
|
} from 'surrealdb';
|
|
8
|
-
import {
|
|
9
|
-
import { Logger } from '../logger/index';
|
|
9
|
+
import type { ConnectionState, ReconnectConfig, Sp00kyConfig } from '../../types';
|
|
10
|
+
import type { Logger } from '../logger/index';
|
|
10
11
|
import { AbstractDatabaseService } from './database';
|
|
11
12
|
import { createDatabaseEventSystem, DatabaseEventTypes } from './events/index';
|
|
12
13
|
|
|
14
|
+
/** Defaults for {@link ReconnectConfig}. See that type for the rationale. */
|
|
15
|
+
export const RECONNECT_DEFAULTS = {
|
|
16
|
+
attempts: -1,
|
|
17
|
+
retryDelayMax: 15_000,
|
|
18
|
+
heartbeatIntervalMs: 20_000,
|
|
19
|
+
heartbeatTimeoutMs: 10_000,
|
|
20
|
+
superviseRetryDelayMaxMs: 15_000,
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
/** Fill in {@link RECONNECT_DEFAULTS} for every field the caller omitted. */
|
|
24
|
+
export function resolveReconnectConfig(
|
|
25
|
+
input?: ReconnectConfig
|
|
26
|
+
): Required<ReconnectConfig> {
|
|
27
|
+
return { ...RECONNECT_DEFAULTS, ...input };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Transport events the SDK publishes, mapped 1:1 to {@link ConnectionState}. */
|
|
31
|
+
export type RemoteConnectionEvent = ConnectionState | 'error';
|
|
32
|
+
|
|
13
33
|
export class RemoteDatabaseService extends AbstractDatabaseService {
|
|
14
|
-
private config:
|
|
34
|
+
private config: Sp00kyConfig<any>['database'];
|
|
15
35
|
protected eventType = DatabaseEventTypes.RemoteQuery;
|
|
36
|
+
private readonly reconnectConfig: Required<ReconnectConfig>;
|
|
37
|
+
/**
|
|
38
|
+
* In-flight `connect()`, so concurrent callers (boot + supervisor revive +
|
|
39
|
+
* an `online` event landing at the same moment) share one attempt instead of
|
|
40
|
+
* racing two sockets. Cleared on settle, so a later call always reconnects.
|
|
41
|
+
*/
|
|
42
|
+
private connecting: Promise<void> | null = null;
|
|
16
43
|
|
|
17
|
-
constructor(config:
|
|
44
|
+
constructor(config: Sp00kyConfig<any>['database'], logger: Logger) {
|
|
18
45
|
const events = createDatabaseEventSystem();
|
|
19
46
|
super(
|
|
20
47
|
new Surreal({
|
|
@@ -29,7 +56,7 @@ export class RemoteDatabaseService extends AbstractDatabaseService {
|
|
|
29
56
|
type,
|
|
30
57
|
phase,
|
|
31
58
|
service: 'surrealdb:remote',
|
|
32
|
-
Category: '
|
|
59
|
+
Category: 'sp00ky-client::RemoteDatabaseService::diagnostics',
|
|
33
60
|
},
|
|
34
61
|
`Remote SurrealDB diagnostics captured ${type}:${phase}`
|
|
35
62
|
);
|
|
@@ -41,13 +68,68 @@ export class RemoteDatabaseService extends AbstractDatabaseService {
|
|
|
41
68
|
events
|
|
42
69
|
);
|
|
43
70
|
this.config = config;
|
|
71
|
+
this.reconnectConfig = resolveReconnectConfig(config.reconnect);
|
|
72
|
+
this.queryTimeoutMs = Math.max(0, config.queryTimeoutMs ?? 60_000);
|
|
44
73
|
}
|
|
45
74
|
|
|
46
|
-
getConfig():
|
|
75
|
+
getConfig(): Sp00kyConfig<any>['database'] {
|
|
47
76
|
return this.config;
|
|
48
77
|
}
|
|
49
78
|
|
|
79
|
+
/** Resolved reconnect tunables; the supervisor reads its own knobs here. */
|
|
80
|
+
getReconnectConfig(): Required<ReconnectConfig> {
|
|
81
|
+
return this.reconnectConfig;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Current transport state as reported by the SDK. */
|
|
85
|
+
getStatus(): ConnectionState {
|
|
86
|
+
return this.client.status as ConnectionState;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Observe transport events. Thin passthrough so callers (the supervisor,
|
|
91
|
+
* sync, CRDT) don't have to reach through `getClient()`.
|
|
92
|
+
*/
|
|
93
|
+
subscribeConnection<K extends RemoteConnectionEvent>(
|
|
94
|
+
event: K,
|
|
95
|
+
cb: (...payload: SurrealEvents[K]) => void
|
|
96
|
+
): () => void {
|
|
97
|
+
return this.client.subscribe(event, cb);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Tear the socket down on purpose. Used by the heartbeat watchdog when a
|
|
102
|
+
* socket stops answering but never closes: `close()` makes the SDK publish
|
|
103
|
+
* `disconnected`, which is what drives the supervisor's revive loop.
|
|
104
|
+
*/
|
|
105
|
+
async forceClose(): Promise<void> {
|
|
106
|
+
try {
|
|
107
|
+
await this.client.close();
|
|
108
|
+
} catch (err) {
|
|
109
|
+
this.logger.debug(
|
|
110
|
+
{ err, Category: 'sp00ky-client::RemoteDatabaseService::forceClose' },
|
|
111
|
+
'forceClose failed; treating the socket as gone anyway'
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Open (or re-open) the remote connection.
|
|
118
|
+
*
|
|
119
|
+
* Safe to call repeatedly: concurrent calls share the in-flight attempt, and
|
|
120
|
+
* a call after a `disconnected` builds a fresh socket. `use()` and
|
|
121
|
+
* `authenticate()` are re-applied here for the cold path; the SDK also
|
|
122
|
+
* replays them itself on its own internal reconnects.
|
|
123
|
+
*/
|
|
50
124
|
async connect(): Promise<void> {
|
|
125
|
+
if (this.connecting) return this.connecting;
|
|
126
|
+
this.connecting = this.doConnect().finally(() => {
|
|
127
|
+
this.connecting = null;
|
|
128
|
+
});
|
|
129
|
+
return this.connecting;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private async doConnect(): Promise<void> {
|
|
51
133
|
const { endpoint, token, namespace, database } = this.getConfig();
|
|
52
134
|
if (endpoint) {
|
|
53
135
|
this.logger.info(
|
|
@@ -55,12 +137,26 @@ export class RemoteDatabaseService extends AbstractDatabaseService {
|
|
|
55
137
|
endpoint,
|
|
56
138
|
namespace,
|
|
57
139
|
database,
|
|
58
|
-
Category: '
|
|
140
|
+
Category: 'sp00ky-client::RemoteDatabaseService::connect',
|
|
59
141
|
},
|
|
60
142
|
'Connecting to remote database'
|
|
61
143
|
);
|
|
62
144
|
try {
|
|
63
|
-
|
|
145
|
+
// Without explicit options the SDK caps itself at 5 attempts (~62s of
|
|
146
|
+
// outage) and then gives up permanently — nothing would re-open the
|
|
147
|
+
// socket after that. `attempts: -1` keeps it trying; the supervisor
|
|
148
|
+
// still covers the case where the SDK terminates the engine because its
|
|
149
|
+
// post-reconnect handshake threw.
|
|
150
|
+
await this.client.connect(endpoint, {
|
|
151
|
+
reconnect: {
|
|
152
|
+
enabled: true,
|
|
153
|
+
attempts: this.reconnectConfig.attempts,
|
|
154
|
+
retryDelay: 1_000,
|
|
155
|
+
retryDelayMax: this.reconnectConfig.retryDelayMax,
|
|
156
|
+
retryDelayMultiplier: 2,
|
|
157
|
+
retryDelayJitter: 0.1,
|
|
158
|
+
},
|
|
159
|
+
});
|
|
64
160
|
await this.client.use({
|
|
65
161
|
namespace,
|
|
66
162
|
database,
|
|
@@ -68,25 +164,25 @@ export class RemoteDatabaseService extends AbstractDatabaseService {
|
|
|
68
164
|
|
|
69
165
|
if (token) {
|
|
70
166
|
this.logger.debug(
|
|
71
|
-
{ Category: '
|
|
167
|
+
{ Category: 'sp00ky-client::RemoteDatabaseService::connect' },
|
|
72
168
|
'Authenticating with token'
|
|
73
169
|
);
|
|
74
170
|
await this.client.authenticate(token);
|
|
75
171
|
}
|
|
76
172
|
this.logger.info(
|
|
77
|
-
{ Category: '
|
|
173
|
+
{ Category: 'sp00ky-client::RemoteDatabaseService::connect' },
|
|
78
174
|
'Connected to remote database'
|
|
79
175
|
);
|
|
80
176
|
} catch (err) {
|
|
81
177
|
this.logger.error(
|
|
82
|
-
{ err, Category: '
|
|
178
|
+
{ err, Category: 'sp00ky-client::RemoteDatabaseService::connect' },
|
|
83
179
|
'Failed to connect to remote database'
|
|
84
180
|
);
|
|
85
181
|
throw err;
|
|
86
182
|
}
|
|
87
183
|
} else {
|
|
88
184
|
this.logger.warn(
|
|
89
|
-
{ Category: '
|
|
185
|
+
{ Category: 'sp00ky-client::RemoteDatabaseService::connect' },
|
|
90
186
|
'No endpoint configured for remote database'
|
|
91
187
|
);
|
|
92
188
|
}
|