@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.
Files changed (163) hide show
  1. package/AGENTS.md +57 -0
  2. package/dist/index.d.ts +2514 -58
  3. package/dist/index.js +12561 -2449
  4. package/dist/otel/index.d.ts +2 -2
  5. package/dist/otel/index.js +6 -6
  6. package/dist/sqlite-open.js +303 -0
  7. package/dist/sqlite-worker.d.ts +1 -0
  8. package/dist/sqlite-worker.js +439 -0
  9. package/dist/tabs-broker-worker.d.ts +8 -0
  10. package/dist/tabs-broker-worker.js +472 -0
  11. package/dist/types.d.ts +751 -11
  12. package/package.json +11 -7
  13. package/scripts/check-broker-bundle.mjs +33 -0
  14. package/skills/{spooky-core → sp00ky-core}/SKILL.md +12 -12
  15. package/skills/{spooky-core → sp00ky-core}/references/auth.md +1 -1
  16. package/skills/{spooky-core → sp00ky-core}/references/config.md +2 -2
  17. package/src/bucket-blurhash.test.ts +148 -0
  18. package/src/build-globals.d.ts +12 -0
  19. package/src/events/events.test.ts +2 -1
  20. package/src/events/index.ts +3 -0
  21. package/src/index.ts +36 -2
  22. package/src/modules/app-release/index.test.ts +125 -0
  23. package/src/modules/app-release/index.ts +201 -0
  24. package/src/modules/auth/auth.local-first.test.ts +101 -0
  25. package/src/modules/auth/events/index.ts +2 -1
  26. package/src/modules/auth/index.ts +127 -24
  27. package/src/modules/cache/cache.relay.test.ts +95 -0
  28. package/src/modules/cache/index.ts +163 -43
  29. package/src/modules/cache/types.ts +2 -2
  30. package/src/modules/crdt/crdt-field.ts +294 -0
  31. package/src/modules/crdt/crdt-hydration.test.ts +210 -0
  32. package/src/modules/crdt/crdt-reconnect.test.ts +195 -0
  33. package/src/modules/crdt/index.ts +463 -0
  34. package/src/modules/crdt/loro-loader.ts +25 -0
  35. package/src/modules/data/data.hydration.test.ts +142 -0
  36. package/src/modules/data/data.membership.test.ts +523 -0
  37. package/src/modules/data/data.notify-table.test.ts +41 -0
  38. package/src/modules/data/data.pending-ids.test.ts +199 -0
  39. package/src/modules/data/data.rebind.test.ts +170 -0
  40. package/src/modules/data/data.rematerialize.test.ts +114 -0
  41. package/src/modules/data/data.run.test.ts +113 -0
  42. package/src/modules/data/data.settled-writes.test.ts +206 -0
  43. package/src/modules/data/data.status.test.ts +249 -0
  44. package/src/modules/data/id-set-plan.test.ts +122 -0
  45. package/src/modules/data/index.ts +1815 -151
  46. package/src/modules/data/mutation-id.test.ts +25 -0
  47. package/src/modules/data/mutation-id.ts +35 -0
  48. package/src/modules/data/window-query.test.ts +52 -0
  49. package/src/modules/data/window-query.ts +194 -0
  50. package/src/modules/devtools/flags.ts +349 -0
  51. package/src/modules/devtools/index.ts +450 -46
  52. package/src/modules/devtools/notify-throttle.test.ts +154 -0
  53. package/src/modules/devtools/state-shape.test.ts +146 -0
  54. package/src/modules/devtools/storage-info.test.ts +79 -0
  55. package/src/modules/devtools/storage-info.ts +168 -0
  56. package/src/modules/devtools/versions.test.ts +74 -0
  57. package/src/modules/devtools/versions.ts +110 -0
  58. package/src/modules/feature-flag/index.test.ts +251 -0
  59. package/src/modules/feature-flag/index.ts +308 -0
  60. package/src/modules/ref-tables.test.ts +91 -0
  61. package/src/modules/ref-tables.ts +88 -0
  62. package/src/modules/sync/engine.ts +164 -82
  63. package/src/modules/sync/events/index.ts +9 -2
  64. package/src/modules/sync/queue/queue-down.test.ts +180 -0
  65. package/src/modules/sync/queue/queue-down.ts +80 -13
  66. package/src/modules/sync/queue/queue-up.forwarded.test.ts +164 -0
  67. package/src/modules/sync/queue/queue-up.ts +241 -57
  68. package/src/modules/sync/scheduler.pause.test.ts +109 -0
  69. package/src/modules/sync/scheduler.retry.test.ts +237 -0
  70. package/src/modules/sync/scheduler.ts +215 -13
  71. package/src/modules/sync/sync.cleanup.test.ts +116 -0
  72. package/src/modules/sync/sync.health.test.ts +149 -0
  73. package/src/modules/sync/sync.heartbeat.test.ts +80 -0
  74. package/src/modules/sync/sync.live-removal.test.ts +175 -0
  75. package/src/modules/sync/sync.reconnect.test.ts +145 -0
  76. package/src/modules/sync/sync.subquery.test.ts +82 -0
  77. package/src/modules/sync/sync.tabs.test.ts +249 -0
  78. package/src/modules/sync/sync.ts +1726 -99
  79. package/src/modules/sync/utils.test.ts +269 -2
  80. package/src/modules/sync/utils.ts +201 -17
  81. package/src/otel/index.ts +13 -10
  82. package/src/services/blobs/blob-cache.test.ts +359 -0
  83. package/src/services/blobs/blob-cache.ts +603 -0
  84. package/src/services/blobs/blob-manifest.ts +227 -0
  85. package/src/services/blobs/blob-store.test.ts +77 -0
  86. package/src/services/blobs/blob-store.ts +359 -0
  87. package/src/services/blobs/blob.fixture.ts +90 -0
  88. package/src/services/blobs/index.ts +70 -0
  89. package/src/services/database/cache-engine.ts +193 -0
  90. package/src/services/database/connection-supervisor.test.ts +289 -0
  91. package/src/services/database/connection-supervisor.ts +415 -0
  92. package/src/services/database/database.query-timeout.test.ts +83 -0
  93. package/src/services/database/database.ts +41 -12
  94. package/src/services/database/engine-factory.ts +33 -0
  95. package/src/services/database/errors.ts +34 -0
  96. package/src/services/database/events/index.ts +2 -1
  97. package/src/services/database/index.ts +7 -0
  98. package/src/services/database/local-migrator.ts +30 -27
  99. package/src/services/database/local.test.ts +64 -0
  100. package/src/services/database/local.ts +484 -67
  101. package/src/services/database/plan-render.test.ts +159 -0
  102. package/src/services/database/plan-render.ts +108 -0
  103. package/src/services/database/relation-resolver.test.ts +413 -0
  104. package/src/services/database/relation-resolver.ts +0 -0
  105. package/src/services/database/remote.ts +110 -14
  106. package/src/services/database/sqlite-cache-engine.test.ts +616 -0
  107. package/src/services/database/sqlite-cache-engine.timeout.test.ts +61 -0
  108. package/src/services/database/sqlite-cache-engine.ts +1358 -0
  109. package/src/services/database/sqlite-devtools-queries.integration.test.ts +143 -0
  110. package/src/services/database/sqlite-devtools-queries.test.ts +154 -0
  111. package/src/services/database/sqlite-lock-verify.test.ts +33 -0
  112. package/src/services/database/sqlite-lock-verify.ts +45 -0
  113. package/src/services/database/sqlite-open.test.ts +150 -0
  114. package/src/services/database/sqlite-open.ts +164 -0
  115. package/src/services/database/sqlite-plan-sql.test.ts +104 -0
  116. package/src/services/database/sqlite-plan-sql.ts +138 -0
  117. package/src/services/database/sqlite-projection.test.ts +99 -0
  118. package/src/services/database/sqlite-select.integration.test.ts +185 -0
  119. package/src/services/database/sqlite-select.test.ts +246 -0
  120. package/src/services/database/sqlite-select.ts +131 -0
  121. package/src/services/database/sqlite-transport.fixture.ts +30 -0
  122. package/src/services/database/sqlite-transport.ts +224 -0
  123. package/src/services/database/sqlite-worker.ts +437 -0
  124. package/src/services/database/surql-translate.ts +416 -0
  125. package/src/services/database/surreal-cache-engine.ts +161 -0
  126. package/src/services/logger/index.ts +3 -2
  127. package/src/services/persistence/localstorage.ts +2 -2
  128. package/src/services/persistence/resilient.ts +11 -4
  129. package/src/services/persistence/surrealdb.ts +10 -10
  130. package/src/services/stream-processor/index.ts +796 -84
  131. package/src/services/stream-processor/permissions.test.ts +47 -0
  132. package/src/services/stream-processor/permissions.ts +53 -0
  133. package/src/services/stream-processor/stream-processor.batch.test.ts +186 -0
  134. package/src/services/stream-processor/stream-processor.prime.test.ts +198 -0
  135. package/src/services/stream-processor/stream-processor.reset.test.ts +226 -0
  136. package/src/services/stream-processor/stream-processor.test.ts +1 -1
  137. package/src/services/stream-processor/wasm-types.ts +59 -3
  138. package/src/services/tabs/broker-client.ts +283 -0
  139. package/src/services/tabs/broker.test.ts +327 -0
  140. package/src/services/tabs/coordinator.test.ts +365 -0
  141. package/src/services/tabs/coordinator.ts +633 -0
  142. package/src/services/tabs/fake-ports.fixture.ts +112 -0
  143. package/src/services/tabs/leader-locks.ts +75 -0
  144. package/src/services/tabs/protocol.ts +258 -0
  145. package/src/services/tabs/support.ts +36 -0
  146. package/src/services/tabs/tabs-broker-worker.ts +640 -0
  147. package/src/sp00ky.auth-order.test.ts +92 -0
  148. package/src/sp00ky.init-query.test.ts +183 -0
  149. package/src/sp00ky.local-first.test.ts +60 -0
  150. package/src/sp00ky.ts +1693 -0
  151. package/src/types.ts +528 -13
  152. package/src/utils/blurhash.ts +90 -0
  153. package/src/utils/error-classification.test.ts +44 -0
  154. package/src/utils/error-classification.ts +7 -0
  155. package/src/utils/index.ts +79 -13
  156. package/src/utils/parser.test.ts +49 -120
  157. package/src/utils/parser.ts +32 -2
  158. package/src/utils/semver.test.ts +32 -0
  159. package/src/utils/semver.ts +30 -0
  160. package/src/utils/surql.ts +30 -18
  161. package/src/utils/withRetry.test.ts +1 -1
  162. package/tsdown.config.ts +86 -1
  163. 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 { SpookyConfig } from '../../types';
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: SpookyConfig<any>['database'];
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: SpookyConfig<any>['database'], logger: Logger) {
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: 'spooky-client::RemoteDatabaseService::diagnostics',
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(): SpookyConfig<any>['database'] {
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: 'spooky-client::RemoteDatabaseService::connect',
140
+ Category: 'sp00ky-client::RemoteDatabaseService::connect',
59
141
  },
60
142
  'Connecting to remote database'
61
143
  );
62
144
  try {
63
- await this.client.connect(endpoint);
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: 'spooky-client::RemoteDatabaseService::connect' },
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: 'spooky-client::RemoteDatabaseService::connect' },
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: 'spooky-client::RemoteDatabaseService::connect' },
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: 'spooky-client::RemoteDatabaseService::connect' },
185
+ { Category: 'sp00ky-client::RemoteDatabaseService::connect' },
90
186
  'No endpoint configured for remote database'
91
187
  );
92
188
  }