@spooky-sync/core 0.0.1-canary.15 → 0.0.1-canary.151

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 (101) hide show
  1. package/AGENTS.md +56 -0
  2. package/dist/index.d.ts +1192 -372
  3. package/dist/index.js +5772 -1270
  4. package/dist/otel/index.d.ts +21 -0
  5. package/dist/otel/index.js +86 -0
  6. package/dist/sqlite-plan-sql.js +194 -0
  7. package/dist/sqlite-worker.d.ts +1 -0
  8. package/dist/sqlite-worker.js +202 -0
  9. package/dist/types.d.ts +755 -0
  10. package/package.json +40 -8
  11. package/skills/sp00ky-core/SKILL.md +258 -0
  12. package/skills/sp00ky-core/references/auth.md +98 -0
  13. package/skills/sp00ky-core/references/config.md +76 -0
  14. package/src/build-globals.d.ts +12 -0
  15. package/src/events/events.test.ts +2 -1
  16. package/src/events/index.ts +3 -0
  17. package/src/index.ts +16 -2
  18. package/src/modules/app-release/index.test.ts +125 -0
  19. package/src/modules/app-release/index.ts +201 -0
  20. package/src/modules/auth/events/index.ts +2 -1
  21. package/src/modules/auth/index.ts +59 -20
  22. package/src/modules/cache/index.ts +77 -32
  23. package/src/modules/cache/types.ts +2 -2
  24. package/src/modules/crdt/crdt-field.ts +294 -0
  25. package/src/modules/crdt/crdt-hydration.test.ts +206 -0
  26. package/src/modules/crdt/index.ts +361 -0
  27. package/src/modules/crdt/loro-loader.ts +25 -0
  28. package/src/modules/data/data.hydration.test.ts +142 -0
  29. package/src/modules/data/data.rebind.test.ts +147 -0
  30. package/src/modules/data/data.run.test.ts +113 -0
  31. package/src/modules/data/data.status.test.ts +249 -0
  32. package/src/modules/data/index.ts +1134 -128
  33. package/src/modules/data/window-query.test.ts +52 -0
  34. package/src/modules/data/window-query.ts +154 -0
  35. package/src/modules/devtools/index.ts +191 -30
  36. package/src/modules/devtools/versions.test.ts +74 -0
  37. package/src/modules/devtools/versions.ts +81 -0
  38. package/src/modules/feature-flag/index.test.ts +120 -0
  39. package/src/modules/feature-flag/index.ts +209 -0
  40. package/src/modules/ref-tables.test.ts +91 -0
  41. package/src/modules/ref-tables.ts +88 -0
  42. package/src/modules/sync/engine.ts +101 -37
  43. package/src/modules/sync/events/index.ts +9 -2
  44. package/src/modules/sync/queue/queue-down.ts +12 -5
  45. package/src/modules/sync/queue/queue-up.ts +29 -14
  46. package/src/modules/sync/scheduler.pause.test.ts +109 -0
  47. package/src/modules/sync/scheduler.ts +73 -7
  48. package/src/modules/sync/sync.health.test.ts +149 -0
  49. package/src/modules/sync/sync.subquery.test.ts +82 -0
  50. package/src/modules/sync/sync.ts +1017 -62
  51. package/src/modules/sync/utils.test.ts +269 -2
  52. package/src/modules/sync/utils.ts +182 -17
  53. package/src/otel/index.ts +127 -0
  54. package/src/services/database/cache-engine.ts +143 -0
  55. package/src/services/database/database.ts +11 -11
  56. package/src/services/database/engine-factory.ts +32 -0
  57. package/src/services/database/events/index.ts +2 -1
  58. package/src/services/database/index.ts +6 -0
  59. package/src/services/database/local-migrator.ts +28 -27
  60. package/src/services/database/local.test.ts +64 -0
  61. package/src/services/database/local.ts +478 -67
  62. package/src/services/database/plan-render.test.ts +159 -0
  63. package/src/services/database/plan-render.ts +108 -0
  64. package/src/services/database/relation-resolver.test.ts +413 -0
  65. package/src/services/database/relation-resolver.ts +0 -0
  66. package/src/services/database/remote.ts +13 -13
  67. package/src/services/database/sqlite-cache-engine.test.ts +215 -0
  68. package/src/services/database/sqlite-cache-engine.ts +829 -0
  69. package/src/services/database/sqlite-plan-sql.test.ts +104 -0
  70. package/src/services/database/sqlite-plan-sql.ts +106 -0
  71. package/src/services/database/sqlite-select.integration.test.ts +185 -0
  72. package/src/services/database/sqlite-select.test.ts +279 -0
  73. package/src/services/database/sqlite-select.ts +113 -0
  74. package/src/services/database/sqlite-worker.ts +160 -0
  75. package/src/services/database/surql-translate.ts +291 -0
  76. package/src/services/database/surreal-cache-engine.ts +139 -0
  77. package/src/services/logger/index.ts +6 -101
  78. package/src/services/persistence/localstorage.ts +2 -2
  79. package/src/services/persistence/resilient.ts +11 -4
  80. package/src/services/persistence/surrealdb.ts +10 -10
  81. package/src/services/stream-processor/index.ts +295 -38
  82. package/src/services/stream-processor/permissions.test.ts +47 -0
  83. package/src/services/stream-processor/permissions.ts +53 -0
  84. package/src/services/stream-processor/stream-processor.batch.test.ts +136 -0
  85. package/src/services/stream-processor/stream-processor.reset.test.ts +104 -0
  86. package/src/services/stream-processor/stream-processor.test.ts +1 -1
  87. package/src/services/stream-processor/wasm-types.ts +18 -2
  88. package/src/sp00ky.auth-order.test.ts +92 -0
  89. package/src/sp00ky.init-query.test.ts +183 -0
  90. package/src/sp00ky.ts +1000 -0
  91. package/src/types.ts +267 -15
  92. package/src/utils/error-classification.test.ts +44 -0
  93. package/src/utils/error-classification.ts +7 -0
  94. package/src/utils/index.ts +35 -13
  95. package/src/utils/parser.ts +3 -2
  96. package/src/utils/semver.test.ts +32 -0
  97. package/src/utils/semver.ts +30 -0
  98. package/src/utils/surql.ts +24 -15
  99. package/src/utils/withRetry.test.ts +1 -1
  100. package/tsdown.config.ts +77 -1
  101. package/src/spooky.ts +0 -395
@@ -1,4 +1,4 @@
1
- import { MutationEventType } from '../types';
1
+ import type { MutationEventType } from '../types';
2
2
 
3
3
  // ==================== TYPES ====================
4
4
 
@@ -34,6 +34,7 @@ export interface SurqlHelper {
34
34
  keyDataVars: ({ key: string; variable: string } | { statement: string } | string)[]
35
35
  ): string;
36
36
  upsert(idVar: string, dataVar: string): string;
37
+ upsertMerge(idVar: string, dataVar: string): string;
37
38
  updateMerge(idVar: string, dataVar: string): string;
38
39
  updateSet(
39
40
  idVar: string,
@@ -90,16 +91,16 @@ export const surql: SurqlHelper = {
90
91
  returnValues: ({ field: string; alias: string } | string)[]
91
92
  ) {
92
93
  return `SELECT ${returnValues
93
- .map((returnValues) =>
94
- typeof returnValues === 'string'
95
- ? returnValues
96
- : `${returnValues.field} as ${returnValues.alias}`
94
+ .map((rv) =>
95
+ typeof rv === 'string'
96
+ ? rv
97
+ : `${rv.field} as ${rv.alias}`
97
98
  )
98
99
  .join(',')} FROM ${table} WHERE ${whereVar
99
- .map((whereVar) =>
100
- typeof whereVar === 'string'
101
- ? `${whereVar} = $${whereVar}`
102
- : `${whereVar.field} = $${whereVar.variable}`
100
+ .map((wv) =>
101
+ typeof wv === 'string'
102
+ ? `${wv} = $${wv}`
103
+ : `${wv.field} = $${wv.variable}`
103
104
  )
104
105
  .join(' AND ')}`;
105
106
  },
@@ -127,6 +128,14 @@ export const surql: SurqlHelper = {
127
128
  return `UPSERT ONLY $${idVar} REPLACE $${dataVar}`;
128
129
  },
129
130
 
131
+ // Use this for sync-down ingestion: the remote payload omits local-only
132
+ // fields injected by the CLI's local-schema setup (`_00_crdt`,
133
+ // `_00_cursor`), and REPLACE would wipe them on every round-trip and
134
+ // break offline reload of CRDT state.
135
+ upsertMerge(idVar: string, dataVar: string) {
136
+ return `UPSERT ONLY $${idVar} MERGE $${dataVar}`;
137
+ },
138
+
130
139
  updateMerge(idVar: string, dataVar: string) {
131
140
  return `UPDATE ONLY $${idVar} MERGE $${dataVar}`;
132
141
  },
@@ -136,12 +145,12 @@ export const surql: SurqlHelper = {
136
145
  keyDataVar: ({ key: string; variable: string } | { statement: string } | string)[]
137
146
  ) {
138
147
  return `UPDATE $${idVar} SET ${keyDataVar
139
- .map((keyDataVar) =>
140
- typeof keyDataVar === 'string'
141
- ? `${keyDataVar} = $${keyDataVar}`
142
- : 'statement' in keyDataVar
143
- ? keyDataVar.statement
144
- : `${keyDataVar.key} = $${keyDataVar.variable}`
148
+ .map((kdv) =>
149
+ typeof kdv === 'string'
150
+ ? `${kdv} = $${kdv}`
151
+ : 'statement' in kdv
152
+ ? kdv.statement
153
+ : `${kdv.key} = $${kdv.variable}`
145
154
  )
146
155
  .join(', ')}`;
147
156
  },
@@ -145,7 +145,7 @@ describe('withRetry', () => {
145
145
  attempt: 1,
146
146
  retries: 3,
147
147
  error: 'Can not open transaction',
148
- Category: 'spooky-client::utils::withRetry',
148
+ Category: 'sp00ky-client::utils::withRetry',
149
149
  }),
150
150
  'Retrying DB operation'
151
151
  );
package/tsdown.config.ts CHANGED
@@ -1,9 +1,85 @@
1
1
  import { defineConfig } from 'tsdown';
2
+ import { createRequire } from 'node:module';
3
+
4
+ const require = createRequire(import.meta.url);
5
+
6
+ // Resolve real package versions at build time so the DevTools can report the
7
+ // actual bundled frontend versions (used to detect frontend/backend drift).
8
+ const corePkg = require('./package.json');
9
+ const coreVersion: string = corePkg.version;
10
+ // ssp-wasm has no `exports` map; resolve its package.json directly, falling
11
+ // back to the relative workspace path if module resolution can't find it.
12
+ let wasmVersion: string;
13
+ try {
14
+ wasmVersion = require('@spooky-sync/ssp-wasm/package.json').version;
15
+ } catch {
16
+ wasmVersion = require('../ssp-wasm/package.json').version;
17
+ }
18
+ // Report the SurrealDB WASM ENGINE version (`@surrealdb/wasm`) — the engine the
19
+ // in-browser local DB actually runs — NOT the `surrealdb` JS client library
20
+ // version. The WASM engine is the one that must stay compatible with the server
21
+ // engine, so it's the meaningful number to compare in DevTools. Prefer the
22
+ // actually-installed version; fall back to our pinned range if its `exports`
23
+ // map blocks importing its package.json.
24
+ let surrealVersion: string;
25
+ try {
26
+ surrealVersion = require('@surrealdb/wasm/package.json').version;
27
+ } catch {
28
+ surrealVersion = String(corePkg.dependencies?.['@surrealdb/wasm'] ?? 'unknown').replace(
29
+ /^[\^~]/,
30
+ ''
31
+ );
32
+ }
33
+
34
+ // tsdown 0.12.x forwards a top-level `define` to rolldown's inputOptions, which
35
+ // rejects it — so we do the substitution ourselves with a tiny transform
36
+ // plugin. Only our own modules reference the `__SP00KY_*__` identifiers, and
37
+ // the early `includes` check skips everything else.
38
+ const replacements: Record<string, string> = {
39
+ __SP00KY_CORE_VERSION__: JSON.stringify(coreVersion),
40
+ __SP00KY_WASM_VERSION__: JSON.stringify(wasmVersion),
41
+ __SP00KY_SURREAL_VERSION__: JSON.stringify(surrealVersion),
42
+ };
43
+
44
+ const versionDefinePlugin = {
45
+ name: 'sp00ky-version-define',
46
+ transform(code: string) {
47
+ if (!code.includes('__SP00KY_')) return null;
48
+ let out = code;
49
+ for (const [token, value] of Object.entries(replacements)) {
50
+ out = out.split(token).join(value);
51
+ }
52
+ return out;
53
+ },
54
+ };
55
+
56
+ // The SQLite worker URL is written as `./sqlite-worker.ts` in source so Vite's
57
+ // src-bundling consumers (the example app aliases core to `src`) resolve it.
58
+ // In the published build the worker is emitted at `dist/sqlite-worker.js`, so
59
+ // rewrite the reference to `.js` here — otherwise the flat `dist/index.js`
60
+ // carries a dangling `.ts` URL that a consumer's bundler can't resolve.
61
+ const sqliteWorkerUrlPlugin = {
62
+ name: 'sp00ky-sqlite-worker-url',
63
+ transform(code: string) {
64
+ if (!code.includes('sqlite-worker.ts')) return null;
65
+ return code.split('sqlite-worker.ts').join('sqlite-worker.js');
66
+ },
67
+ };
2
68
 
3
69
  export default defineConfig({
4
- entry: ['src/index.ts'],
70
+ // `sqlite-worker` is emitted at `dist/sqlite-worker.js` (top level, NOT under
71
+ // services/database) so the `new URL('./sqlite-worker.js', import.meta.url)`
72
+ // in `SqliteCacheEngine` — which gets bundled into the flat `dist/index.js` —
73
+ // resolves correctly. It (and `@sqlite.org/sqlite-wasm`) load lazily in the
74
+ // Worker; never pulled into the main bundle unless `localEngine: 'sqlite'`.
75
+ entry: {
76
+ index: 'src/index.ts',
77
+ 'otel/index': 'src/otel/index.ts',
78
+ 'sqlite-worker': 'src/services/database/sqlite-worker.ts',
79
+ },
5
80
  format: ['esm'],
6
81
  dts: true,
7
82
  clean: true,
8
83
  hash: false,
84
+ plugins: [versionDefinePlugin, sqliteWorkerUrlPlugin],
9
85
  });
package/src/spooky.ts DELETED
@@ -1,395 +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
- BucketNames,
27
- BackendNames,
28
- BackendRoutes,
29
- RoutePayload,
30
- } from '@spooky-sync/query-builder';
31
-
32
- import { DevToolsService } from './modules/devtools/index';
33
- import { createLogger } from './services/logger/index';
34
- import { AuthService } from './modules/auth/index';
35
- import { StreamProcessorService } from './services/stream-processor/index';
36
- import { EventSystem } from './events/index';
37
- import { CacheModule } from './modules/cache/index';
38
- import { LocalStoragePersistenceClient } from './services/persistence/localstorage';
39
- import { generateId, parseParams } from './utils/index';
40
- import { SurrealDBPersistenceClient } from './services/persistence/surrealdb';
41
- import { ResilientPersistenceClient } from './services/persistence/resilient';
42
-
43
- export class BucketHandle {
44
- constructor(private bucketName: string, private remote: RemoteDatabaseService) {}
45
-
46
- async put(path: string, content: string | Uint8Array | Blob): Promise<void> {
47
- await this.remote.query(`RETURN f"${this.bucketName}:/${path}".put($content);`, { content });
48
- }
49
-
50
- async get(path: string): Promise<unknown> {
51
- const [result] = await this.remote.query<[unknown]>(`RETURN f"${this.bucketName}:/${path}".get();`);
52
- return result;
53
- }
54
-
55
- async delete(path: string): Promise<void> {
56
- await this.remote.query(`RETURN f"${this.bucketName}:/${path}".delete();`);
57
- }
58
-
59
- async exists(path: string): Promise<boolean> {
60
- const [result] = await this.remote.query<[boolean]>(`RETURN f"${this.bucketName}:/${path}".exists();`);
61
- return result;
62
- }
63
-
64
- async head(path: string): Promise<Record<string, unknown>> {
65
- const [result] = await this.remote.query<[Record<string, unknown>]>(`RETURN f"${this.bucketName}:/${path}".head();`);
66
- return result;
67
- }
68
-
69
- async copy(sourcePath: string, targetPath: string): Promise<void> {
70
- await this.remote.query(`RETURN f"${this.bucketName}:/${sourcePath}".copy($target);`, { target: targetPath });
71
- }
72
-
73
- async rename(sourcePath: string, targetPath: string): Promise<void> {
74
- await this.remote.query(`RETURN f"${this.bucketName}:/${sourcePath}".rename($target);`, { target: targetPath });
75
- }
76
-
77
- async list(prefix?: string): Promise<string[]> {
78
- const p = prefix ?? '';
79
- const [result] = await this.remote.query<[string[]]>(`RETURN f"${this.bucketName}:/${p}".list();`);
80
- return result;
81
- }
82
- }
83
-
84
- export class SpookyClient<S extends SchemaStructure> {
85
- private local: LocalDatabaseService;
86
- private remote: RemoteDatabaseService;
87
- private persistenceClient: PersistenceClient;
88
-
89
- private migrator: LocalMigrator;
90
- private cache: CacheModule;
91
- private dataModule: DataModule<S>;
92
- private sync: SpookySync<S>;
93
- private devTools: DevToolsService;
94
-
95
- private logger: ReturnType<typeof createLogger>;
96
- public auth: AuthService<S>;
97
- public streamProcessor: StreamProcessorService;
98
-
99
- get remoteClient() {
100
- return this.remote.getClient();
101
- }
102
-
103
- get localClient() {
104
- return this.local.getClient();
105
- }
106
-
107
- get pendingMutationCount(): number {
108
- return this.sync.pendingMutationCount;
109
- }
110
-
111
- subscribeToPendingMutations(cb: (count: number) => void): () => void {
112
- return this.sync.subscribeToPendingMutations(cb);
113
- }
114
-
115
- constructor(private config: SpookyConfig<S>) {
116
- const logger = createLogger(config.logLevel ?? 'info', config.otelEndpoint);
117
- this.logger = logger.child({ service: 'SpookyClient' });
118
-
119
- this.logger.info(
120
- {
121
- config: { ...config, schema: '[SchemaStructure]' },
122
- Category: 'spooky-client::SpookyClient::constructor',
123
- },
124
- 'SpookyClient initialized'
125
- );
126
-
127
- this.local = new LocalDatabaseService(this.config.database, logger);
128
- this.remote = new RemoteDatabaseService(this.config.database, logger);
129
-
130
- if (config.persistenceClient === 'surrealdb') {
131
- this.persistenceClient = new SurrealDBPersistenceClient(this.local, logger);
132
- } else if (config.persistenceClient === 'localstorage' || !config.persistenceClient) {
133
- this.persistenceClient = new LocalStoragePersistenceClient(logger);
134
- } else {
135
- this.persistenceClient = config.persistenceClient;
136
- }
137
-
138
- this.persistenceClient = new ResilientPersistenceClient(this.persistenceClient, logger);
139
-
140
- this.streamProcessor = new StreamProcessorService(
141
- new EventSystem(['stream_update']),
142
- this.local,
143
- this.persistenceClient,
144
- logger
145
- );
146
- this.migrator = new LocalMigrator(this.local, logger);
147
-
148
- this.cache = new CacheModule(
149
- this.local,
150
- this.streamProcessor,
151
- (update) => {
152
- // Direct callback from cache to data module
153
- this.dataModule.onStreamUpdate(update);
154
- },
155
- logger
156
- );
157
-
158
- this.dataModule = new DataModule(
159
- this.cache,
160
- this.local,
161
- this.config.schema,
162
- logger,
163
- this.config.streamDebounceTime
164
- );
165
-
166
- // Initialize Auth
167
- this.auth = new AuthService(this.config.schema, this.remote, this.persistenceClient, logger);
168
-
169
- // Initialize Sync
170
- this.sync = new SpookySync(this.local, this.remote, this.cache, this.dataModule, this.config.schema, this.logger);
171
-
172
- // Initialize DevTools
173
- this.devTools = new DevToolsService(
174
- this.local,
175
- this.remote,
176
- logger,
177
- this.config.schema,
178
- this.auth,
179
- this.dataModule
180
- );
181
-
182
- // Register DevTools as a receiver for stream updates
183
- this.streamProcessor.addReceiver(this.devTools);
184
-
185
- // Wire up callbacks instead of events
186
- this.setupCallbacks();
187
- }
188
-
189
- /**
190
- * Setup direct callbacks instead of event subscriptions
191
- */
192
- private setupCallbacks() {
193
- // Mutation callback for sync
194
- this.dataModule.onMutation((mutations: UpEvent[]) => {
195
- // Notify DevTools
196
- this.devTools.onMutation(mutations);
197
-
198
- // Enqueue in Sync
199
- if (mutations.length > 0) {
200
- this.sync.enqueueMutation(mutations);
201
- }
202
- });
203
-
204
- // Sync events for incoming updates
205
- this.sync.events.subscribe('SYNC_QUERY_UPDATED', (event: any) => {
206
- this.devTools.logEvent('SYNC_QUERY_UPDATED', event.payload);
207
- });
208
-
209
- // Database events for DevTools
210
- this.local.getEvents().subscribe('DATABASE_LOCAL_QUERY', (event: any) => {
211
- this.devTools.logEvent('LOCAL_QUERY', event.payload);
212
- });
213
-
214
- this.remote.getEvents().subscribe('DATABASE_REMOTE_QUERY', (event: any) => {
215
- this.devTools.logEvent('REMOTE_QUERY', event.payload);
216
- });
217
- }
218
-
219
- async init() {
220
- this.logger.info(
221
- { Category: 'spooky-client::SpookyClient::init' },
222
- 'SpookyClient initialization started'
223
- );
224
- try {
225
- const clientId = this.config.clientId ?? (await this.loadOrGenerateClientId());
226
- this.persistClientId(clientId);
227
- this.logger.debug(
228
- { clientId, Category: 'spooky-client::SpookyClient::init' },
229
- 'Client ID loaded'
230
- );
231
-
232
- await this.local.connect();
233
- this.logger.debug(
234
- { Category: 'spooky-client::SpookyClient::init' },
235
- 'Local database connected'
236
- );
237
-
238
- await this.migrator.provision(this.config.schemaSurql);
239
- this.logger.debug({ Category: 'spooky-client::SpookyClient::init' }, 'Schema provisioned');
240
-
241
- await this.remote.connect();
242
- this.logger.debug(
243
- { Category: 'spooky-client::SpookyClient::init' },
244
- 'Remote database connected'
245
- );
246
-
247
- await this.streamProcessor.init();
248
- this.logger.debug(
249
- { Category: 'spooky-client::SpookyClient::init' },
250
- 'StreamProcessor initialized'
251
- );
252
-
253
- await this.auth.init();
254
- this.logger.debug({ Category: 'spooky-client::SpookyClient::init' }, 'Auth initialized');
255
-
256
- await this.dataModule.init();
257
- this.logger.debug(
258
- { Category: 'spooky-client::SpookyClient::init' },
259
- 'DataModule initialized'
260
- );
261
-
262
- await this.sync.init(clientId);
263
- this.logger.debug({ Category: 'spooky-client::SpookyClient::init' }, 'Sync initialized');
264
-
265
- this.logger.info(
266
- { Category: 'spooky-client::SpookyClient::init' },
267
- 'SpookyClient initialization completed successfully'
268
- );
269
- } catch (e) {
270
- this.logger.error(
271
- { error: e, Category: 'spooky-client::SpookyClient::init' },
272
- 'SpookyClient initialization failed'
273
- );
274
- throw e;
275
- }
276
- }
277
-
278
- async close() {
279
- await this.local.close();
280
- await this.remote.close();
281
- }
282
-
283
- authenticate(token: string) {
284
- return this.remote.getClient().authenticate(token);
285
- }
286
-
287
- deauthenticate() {
288
- return this.remote.getClient().invalidate();
289
- }
290
-
291
- query<Table extends TableNames<S>>(
292
- table: Table,
293
- options: QueryOptions<TableModel<GetTable<S, Table>>, false>,
294
- ttl: QueryTimeToLive = '10m'
295
- ): QueryBuilder<S, Table, SpookyQueryResultPromise> {
296
- return new QueryBuilder<S, Table, SpookyQueryResultPromise>(
297
- this.config.schema,
298
- table,
299
- async (q) => ({
300
- hash: await this.initQuery(table, q, ttl),
301
- }),
302
- options
303
- );
304
- }
305
-
306
- private async initQuery<Table extends TableNames<S>>(
307
- table: Table,
308
- q: InnerQuery<any, any, any>,
309
- ttl: QueryTimeToLive
310
- ) {
311
- const tableSchema = this.config.schema.tables.find((t) => t.name === table);
312
- if (!tableSchema) {
313
- throw new Error(`Table ${table} not found`);
314
- }
315
-
316
- const hash = await this.dataModule.query(
317
- table,
318
- q.selectQuery.query,
319
- parseParams(tableSchema.columns, q.selectQuery.vars ?? {}),
320
- ttl
321
- );
322
-
323
- await this.sync.enqueueDownEvent({
324
- type: 'register',
325
- payload: {
326
- hash,
327
- },
328
- });
329
-
330
- return hash;
331
- }
332
-
333
- async queryRaw(sql: string, params: Record<string, any>, ttl: QueryTimeToLive) {
334
- const tableName = sql.split('FROM ')[1].split(' ')[0];
335
- return this.dataModule.query(tableName, sql, params, ttl);
336
- }
337
-
338
- async subscribe(
339
- queryHash: string,
340
- callback: (records: Record<string, any>[]) => void,
341
- options?: { immediate?: boolean }
342
- ): Promise<() => void> {
343
- return this.dataModule.subscribe(queryHash, callback, options);
344
- }
345
-
346
- run<
347
- B extends BackendNames<S>,
348
- R extends BackendRoutes<S, B>,
349
- >(backend: B, path: R, payload: RoutePayload<S, B, R>, options?: RunOptions) {
350
- return this.dataModule.run(backend, path, payload, options);
351
- }
352
-
353
- bucket<B extends BucketNames<S>>(name: B): BucketHandle {
354
- return new BucketHandle(name, this.remote);
355
- }
356
-
357
- create(id: string, data: Record<string, unknown>) {
358
- return this.dataModule.create(id, data);
359
- }
360
-
361
- update(table: string, id: string, data: Record<string, unknown>, options?: UpdateOptions) {
362
- return this.dataModule.update(table, id, data, options);
363
- }
364
-
365
- delete(table: string, id: string) {
366
- return this.dataModule.delete(table, id);
367
- }
368
-
369
- async useRemote<T>(fn: (client: Surreal) => Promise<T> | T): Promise<T> {
370
- return fn(this.remote.getClient());
371
- }
372
-
373
- private persistClientId(id: string) {
374
- try {
375
- this.persistenceClient.set('spooky_client_id', id);
376
- } catch (e) {
377
- this.logger.warn(
378
- { error: e, Category: 'spooky-client::SpookyClient::persistClientId' },
379
- 'Failed to persist client ID'
380
- );
381
- }
382
- }
383
-
384
- private async loadOrGenerateClientId(): Promise<string> {
385
- const clientId = await this.persistenceClient.get<string>('spooky_client_id');
386
-
387
- if (clientId) {
388
- return clientId;
389
- }
390
-
391
- const newId = generateId();
392
- await this.persistClientId(newId);
393
- return newId;
394
- }
395
- }