@spooky-sync/core 0.0.1-canary.35 → 0.0.1-canary.37

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 (38) hide show
  1. package/dist/index.d.ts +6 -6
  2. package/dist/index.js +11 -10
  3. package/dist/otel/index.js +3 -3
  4. package/dist/types.d.ts +3 -1
  5. package/package.json +1 -1
  6. package/src/events/events.test.ts +2 -1
  7. package/src/modules/auth/events/index.ts +2 -1
  8. package/src/modules/auth/index.ts +5 -8
  9. package/src/modules/cache/index.ts +5 -5
  10. package/src/modules/cache/types.ts +2 -2
  11. package/src/modules/data/index.ts +15 -12
  12. package/src/modules/devtools/index.ts +6 -6
  13. package/src/modules/sync/engine.ts +6 -6
  14. package/src/modules/sync/events/index.ts +3 -2
  15. package/src/modules/sync/queue/queue-down.ts +4 -3
  16. package/src/modules/sync/queue/queue-up.ts +6 -5
  17. package/src/modules/sync/scheduler.ts +2 -2
  18. package/src/modules/sync/sync.ts +10 -9
  19. package/src/modules/sync/utils.test.ts +2 -2
  20. package/src/modules/sync/utils.ts +7 -5
  21. package/src/otel/index.ts +10 -7
  22. package/src/services/database/database.ts +7 -7
  23. package/src/services/database/events/index.ts +2 -1
  24. package/src/services/database/local-migrator.ts +8 -8
  25. package/src/services/database/local.ts +5 -4
  26. package/src/services/database/remote.ts +4 -4
  27. package/src/services/logger/index.ts +3 -2
  28. package/src/services/persistence/localstorage.ts +2 -2
  29. package/src/services/persistence/resilient.ts +2 -2
  30. package/src/services/persistence/surrealdb.ts +3 -3
  31. package/src/services/stream-processor/index.ts +7 -6
  32. package/src/services/stream-processor/stream-processor.test.ts +1 -1
  33. package/src/services/stream-processor/wasm-types.ts +1 -1
  34. package/src/sp00ky.ts +8 -9
  35. package/src/types.ts +7 -5
  36. package/src/utils/index.ts +6 -6
  37. package/src/utils/parser.ts +2 -1
  38. package/src/utils/surql.ts +15 -15
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { C as Logger$1, S as UpdateOptions, T as EventSystem, _ as RunOptions, a as MutationEvent, b as Sp00kyQueryResultPromise, c as PinoTransmit, d as QueryHash, f as QueryState, g as RecordVersionDiff, h as RecordVersionArray, i as MutationCallback, l as QueryConfig, m as QueryUpdateCallback, n as EventSubscriptionOptions, o as MutationEventType, p as QueryTimeToLive, r as Level, s as PersistenceClient, t as DebounceOptions, u as QueryConfigRecord, v as Sp00kyConfig, w as EventDefinition, x as StoreType, y as Sp00kyQueryResult } from "./types.js";
2
2
  import * as surrealdb0 from "surrealdb";
3
- import { Duration, RecordId, Surreal, SurrealTransaction } from "surrealdb";
3
+ import { Duration, RecordId, Surreal as Surreal$1, SurrealTransaction } from "surrealdb";
4
4
  import { AccessDefinition, BackendNames, BackendRoutes, BucketNames, ColumnSchema, GetTable, QueryBuilder, QueryOptions, RoutePayload, SchemaStructure, TableModel, TableNames, TypeNameToTypeMap } from "@spooky-sync/query-builder";
5
5
  import { Logger } from "pino";
6
6
 
@@ -31,13 +31,13 @@ interface SealedQuery<T = void> {
31
31
  //#endregion
32
32
  //#region src/services/database/database.d.ts
33
33
  declare abstract class AbstractDatabaseService {
34
- protected client: Surreal;
34
+ protected client: Surreal$1;
35
35
  protected logger: Logger$1;
36
36
  protected events: DatabaseEventSystem;
37
37
  protected abstract eventType: typeof DatabaseEventTypes.LocalQuery | typeof DatabaseEventTypes.RemoteQuery;
38
- constructor(client: Surreal, logger: Logger$1, events: DatabaseEventSystem);
38
+ constructor(client: Surreal$1, logger: Logger$1, events: DatabaseEventSystem);
39
39
  abstract connect(): Promise<void>;
40
- getClient(): Surreal;
40
+ getClient(): Surreal$1;
41
41
  getEvents(): DatabaseEventSystem;
42
42
  tx(): Promise<SurrealTransaction>;
43
43
  private queryQueue;
@@ -221,8 +221,8 @@ declare class Sp00kyClient<S extends SchemaStructure> {
221
221
  private logger;
222
222
  auth: AuthService<S>;
223
223
  streamProcessor: StreamProcessorService;
224
- get remoteClient(): Surreal;
225
- get localClient(): Surreal;
224
+ get remoteClient(): surrealdb0.Surreal;
225
+ get localClient(): surrealdb0.Surreal;
226
226
  get pendingMutationCount(): number;
227
227
  subscribeToPendingMutations(cb: (count: number) => void): () => void;
228
228
  constructor(config: Sp00kyConfig<S>);
package/dist/index.js CHANGED
@@ -28,7 +28,7 @@ const surql = {
28
28
  return `SELECT ${returnValues.join(",")} FROM ONLY $${idVar}`;
29
29
  },
30
30
  selectByFieldsAnd(table, whereVar, returnValues) {
31
- return `SELECT ${returnValues.map((returnValues) => typeof returnValues === "string" ? returnValues : `${returnValues.field} as ${returnValues.alias}`).join(",")} FROM ${table} WHERE ${whereVar.map((whereVar) => typeof whereVar === "string" ? `${whereVar} = $${whereVar}` : `${whereVar.field} = $${whereVar.variable}`).join(" AND ")}`;
31
+ return `SELECT ${returnValues.map((rv) => typeof rv === "string" ? rv : `${rv.field} as ${rv.alias}`).join(",")} FROM ${table} WHERE ${whereVar.map((wv) => typeof wv === "string" ? `${wv} = $${wv}` : `${wv.field} = $${wv.variable}`).join(" AND ")}`;
32
32
  },
33
33
  create(idVar, dataVar) {
34
34
  return `CREATE ONLY $${idVar} CONTENT $${dataVar}`;
@@ -43,7 +43,7 @@ const surql = {
43
43
  return `UPDATE ONLY $${idVar} MERGE $${dataVar}`;
44
44
  },
45
45
  updateSet(idVar, keyDataVar) {
46
- return `UPDATE $${idVar} SET ${keyDataVar.map((keyDataVar) => typeof keyDataVar === "string" ? `${keyDataVar} = $${keyDataVar}` : "statement" in keyDataVar ? keyDataVar.statement : `${keyDataVar.key} = $${keyDataVar.variable}`).join(", ")}`;
46
+ return `UPDATE $${idVar} SET ${keyDataVar.map((kdv) => typeof kdv === "string" ? `${kdv} = $${kdv}` : "statement" in kdv ? kdv.statement : `${kdv.key} = $${kdv.variable}`).join(", ")}`;
47
47
  },
48
48
  delete(idVar) {
49
49
  return `DELETE $${idVar}`;
@@ -157,7 +157,7 @@ function parseDuration(duration) {
157
157
  if (typeof duration !== "string") return 6e5;
158
158
  const match = duration.match(/^(\d+)([smh])$/);
159
159
  if (!match) return 6e5;
160
- const val = parseInt(match[1], 10);
160
+ const val = Number.parseInt(match[1], 10);
161
161
  switch (match[2]) {
162
162
  case "s": return val * 1e3;
163
163
  case "h": return val * 36e5;
@@ -426,6 +426,7 @@ var DataModule = class {
426
426
  max_retries: options?.max_retries ?? 3,
427
427
  retry_strategy: options?.retry_strategy ?? "linear"
428
428
  };
429
+ if (options?.timeout != null) record.timeout = options.timeout;
429
430
  if (options?.assignedTo) record.assigned_to = options.assignedTo;
430
431
  const recordId = `${tableName}:${generateId()}`;
431
432
  await this.create(recordId, record);
@@ -742,7 +743,7 @@ function parseUpdateOptions(id, data, options) {
742
743
  let pushEventOptions = {};
743
744
  if (options?.debounced) pushEventOptions = { debounced: {
744
745
  delay: options.debounced !== true ? options.debounced?.delay ?? 200 : 200,
745
- key: (options.debounced !== true ? options.debounced?.key ?? id : id) === "recordId_x_fields" ? `${id}::${Object.keys(data).sort().join("#")}` : id
746
+ key: (options.debounced !== true ? options.debounced?.key ?? id : id) === "recordId_x_fields" ? `${id}::${Object.keys(data).toSorted().join("#")}` : id
746
747
  } };
747
748
  return pushEventOptions;
748
749
  }
@@ -1173,20 +1174,20 @@ var LocalMigrator = class {
1173
1174
  try {
1174
1175
  const [lastSchemaRecord] = await this.localDb.query(`SELECT hash, created_at FROM ONLY _00_schema ORDER BY created_at DESC LIMIT 1;`);
1175
1176
  return lastSchemaRecord?.hash === hash;
1176
- } catch (error) {
1177
+ } catch (_error) {
1177
1178
  return false;
1178
1179
  }
1179
1180
  }
1180
1181
  async recreateDatabase(database) {
1181
1182
  try {
1182
1183
  await this.localDb.query(`DEFINE DATABASE _00_temp;`);
1183
- } catch (e) {}
1184
+ } catch (_e) {}
1184
1185
  try {
1185
1186
  await this.localDb.query(`
1186
1187
  USE DB _00_temp;
1187
1188
  REMOVE DATABASE ${database};
1188
1189
  `);
1189
- } catch (e) {}
1190
+ } catch (_e) {}
1190
1191
  await this.localDb.query(`
1191
1192
  DEFINE DATABASE ${database};
1192
1193
  USE DB ${database};
@@ -1480,8 +1481,8 @@ var ArraySyncer = class {
1480
1481
  remoteArray;
1481
1482
  needsSort = false;
1482
1483
  constructor(localArray, remoteArray) {
1483
- this.remoteArray = remoteArray.sort((a, b) => a[0].localeCompare(b[0]));
1484
- this.localArray = localArray.sort((a, b) => a[0].localeCompare(b[0]));
1484
+ this.remoteArray = remoteArray.toSorted((a, b) => a[0].localeCompare(b[0]));
1485
+ this.localArray = localArray.toSorted((a, b) => a[0].localeCompare(b[0]));
1485
1486
  }
1486
1487
  /**
1487
1488
  * Inserts an item into the local array
@@ -2401,7 +2402,7 @@ var AuthService = class {
2401
2402
  await this.persistenceClient.remove("sp00ky_auth_token");
2402
2403
  try {
2403
2404
  await this.remote.getClient().invalidate();
2404
- } catch (e) {}
2405
+ } catch (_e) {}
2405
2406
  this.notifyListeners();
2406
2407
  }
2407
2408
  async setSession(token, user) {
@@ -50,9 +50,9 @@ function createOtelTransmit(endpoint, level = "info") {
50
50
  const messages = [...logEvent.messages];
51
51
  const severityNumber = mapLevelToSeverityNumber(levelLabel);
52
52
  let body = "";
53
- const msg = messages.pop();
54
- if (typeof msg === "string") body = msg;
55
- else if (msg) body = JSON.stringify(msg);
53
+ const lastMsg = messages.pop();
54
+ if (typeof lastMsg === "string") body = lastMsg;
55
+ else if (lastMsg) body = JSON.stringify(lastMsg);
56
56
  let category = "sp00ky-client::unknown";
57
57
  const attributes = {};
58
58
  for (const msg of messages) if (typeof msg === "object") {
package/dist/types.d.ts CHANGED
@@ -228,7 +228,7 @@ interface Sp00kyConfig<S extends SchemaStructure> {
228
228
  /** The compiled SURQL schema string. */
229
229
  schemaSurql: string;
230
230
  /** Logging level. */
231
- logLevel: Level;
231
+ logLevel: Level$1;
232
232
  /**
233
233
  * Persistence client to use.
234
234
  * Can be a custom implementation, 'surrealdb' (default), or 'localstorage'.
@@ -331,6 +331,8 @@ interface RunOptions {
331
331
  assignedTo?: string;
332
332
  max_retries?: number;
333
333
  retry_strategy?: 'linear' | 'exponential';
334
+ /** Timeout in seconds for the backend HTTP call. Only used if the backend allows timeout override. */
335
+ timeout?: number;
334
336
  }
335
337
  /**
336
338
  * Options for update operations.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spooky-sync/core",
3
- "version": "0.0.1-canary.35",
3
+ "version": "0.0.1-canary.37",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -1,5 +1,6 @@
1
1
  import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { EventSystem, EventDefinition } from './index';
2
+ import type { EventDefinition } from './index';
3
+ import { EventSystem } from './index';
3
4
 
4
5
  // Define test event types
5
6
  type TestEvents = {
@@ -1,4 +1,5 @@
1
- import { createEventSystem, EventDefinition, EventSystem } from '../../../events/index';
1
+ import type { EventDefinition, EventSystem } from '../../../events/index';
2
+ import { createEventSystem } from '../../../events/index';
2
3
 
3
4
  export const AuthEventTypes = {
4
5
  AuthStateChanged: 'AUTH_STATE_CHANGED',
@@ -1,17 +1,14 @@
1
- import { RemoteDatabaseService } from '../../services/database/remote';
2
- import { LocalDatabaseService } from '../../services/database/local';
3
- import { DataModule } from '../data/index';
4
- import {
1
+ import type { RemoteDatabaseService } from '../../services/database/remote';
2
+ import type {
5
3
  SchemaStructure,
6
4
  AccessDefinition,
7
5
  ColumnSchema,
8
6
  TypeNameToTypeMap,
9
7
  } from '@spooky-sync/query-builder';
10
- import { Logger } from '../../services/logger/index';
11
- import { encodeRecordId } from '../../utils/index';
8
+ import type { Logger } from '../../services/logger/index';
12
9
  export * from './events/index';
13
10
  import { AuthEventTypes, createAuthEventSystem } from './events/index';
14
- import { PersistenceClient } from '../../types';
11
+ import type { PersistenceClient } from '../../types';
15
12
 
16
13
  // Helper to pretty print types
17
14
  type Prettify<T> = {
@@ -175,7 +172,7 @@ export class AuthService<S extends SchemaStructure> {
175
172
 
176
173
  try {
177
174
  await this.remote.getClient().invalidate();
178
- } catch (e) {
175
+ } catch (_e) {
179
176
  // Ignore invalidation errors
180
177
  }
181
178
 
@@ -1,13 +1,13 @@
1
- import { LocalDatabaseService } from '../../services/database/index';
2
- import {
1
+ import type { LocalDatabaseService } from '../../services/database/index';
2
+ import type {
3
3
  StreamProcessorService,
4
4
  StreamUpdate,
5
5
  StreamUpdateReceiver,
6
6
  } from '../../services/stream-processor/index';
7
- import { Logger } from '../../services/logger/index';
7
+ import type { Logger } from '../../services/logger/index';
8
8
  import { parseRecordIdString, encodeRecordId, surql } from '../../utils/index';
9
- import { CacheRecord, QueryConfig } from './types';
10
- import { RecordVersionArray } from '../../types';
9
+ import type { CacheRecord, QueryConfig } from './types';
10
+ import type { RecordVersionArray } from '../../types';
11
11
 
12
12
  export * from './types';
13
13
 
@@ -1,5 +1,5 @@
1
- import { RecordId, Duration } from 'surrealdb';
2
- import { QueryTimeToLive, RecordVersionArray } from '../../types';
1
+ import type { RecordId, Duration } from 'surrealdb';
2
+ import type { QueryTimeToLive } from '../../types';
3
3
 
4
4
  export type RecordWithId = Record<string, any> & { id: RecordId<string> };
5
5
 
@@ -1,17 +1,16 @@
1
1
  import { RecordId, Duration } from 'surrealdb';
2
- import {
2
+ import type {
3
3
  SchemaStructure,
4
4
  TableNames,
5
5
  BackendNames,
6
6
  BackendRoutes,
7
7
  RoutePayload,
8
8
  } from '@spooky-sync/query-builder';
9
- import { LocalDatabaseService } from '../../services/database/index';
10
- import { CacheModule, RecordWithId } from '../cache/index';
11
- import { Logger } from '../../services/logger/index';
12
- import { StreamUpdate } from '../../services/stream-processor/index';
13
- import {
14
- MutationEvent,
9
+ import type { LocalDatabaseService } from '../../services/database/index';
10
+ import type { CacheModule, RecordWithId } from '../cache/index';
11
+ import type { Logger } from '../../services/logger/index';
12
+ import type { StreamUpdate } from '../../services/stream-processor/index';
13
+ import type {
15
14
  QueryConfig,
16
15
  QueryHash,
17
16
  QueryState,
@@ -21,8 +20,7 @@ import {
21
20
  RecordVersionArray,
22
21
  QueryConfigRecord,
23
22
  UpdateOptions,
24
- RunOptions,
25
- } from '../../types';
23
+ RunOptions} from '../../types';
26
24
  import {
27
25
  parseRecordIdString,
28
26
  extractIdPart,
@@ -34,8 +32,8 @@ import {
34
32
  extractTablePart,
35
33
  generateId,
36
34
  } from '../../utils/index';
37
- import { CreateEvent, DeleteEvent, UpdateEvent } from '../sync/index';
38
- import { PushEventOptions } from '../../events/index';
35
+ import type { CreateEvent, DeleteEvent, UpdateEvent } from '../sync/index';
36
+ import type { PushEventOptions } from '../../events/index';
39
37
 
40
38
  /**
41
39
  * DataModule - Unified query and mutation management
@@ -173,6 +171,7 @@ export class DataModule<S extends SchemaStructure> {
173
171
  if (op === 'UPDATE') {
174
172
  // Clear existing timer if any
175
173
  if (this.debounceTimers.has(queryHash)) {
174
+ // oxlint-disable-next-line no-non-null-assertion -- guarded by .has() check above
176
175
  clearTimeout(this.debounceTimers.get(queryHash)!);
177
176
  }
178
177
 
@@ -370,6 +369,10 @@ export class DataModule<S extends SchemaStructure> {
370
369
  retry_strategy: options?.retry_strategy ?? 'linear',
371
370
  };
372
371
 
372
+ if (options?.timeout != null) {
373
+ record.timeout = options.timeout;
374
+ }
375
+
373
376
  if (options?.assignedTo) {
374
377
  record.assigned_to = options.assignedTo;
375
378
  }
@@ -866,7 +869,7 @@ export function parseUpdateOptions(
866
869
  const delay = options.debounced !== true ? (options.debounced?.delay ?? 200) : 200;
867
870
  const keyType = options.debounced !== true ? (options.debounced?.key ?? id) : id;
868
871
  const key =
869
- keyType === 'recordId_x_fields' ? `${id}::${Object.keys(data).sort().join('#')}` : id;
872
+ keyType === 'recordId_x_fields' ? `${id}::${Object.keys(data).toSorted().join('#')}` : id;
870
873
 
871
874
  pushEventOptions = {
872
875
  debounced: {
@@ -1,8 +1,8 @@
1
- import { LocalDatabaseService, RemoteDatabaseService } from '../../services/database/index';
2
- import { Logger } from '../../services/logger/index';
3
- import { SchemaStructure } from '@spooky-sync/query-builder';
1
+ import type { LocalDatabaseService, RemoteDatabaseService } from '../../services/database/index';
2
+ import type { Logger } from '../../services/logger/index';
3
+ import type { SchemaStructure } from '@spooky-sync/query-builder';
4
4
  import { RecordId } from 'surrealdb';
5
- import { StreamUpdate, StreamUpdateReceiver } from '../../services/stream-processor/index';
5
+ import type { StreamUpdate, StreamUpdateReceiver } from '../../services/stream-processor/index';
6
6
  import { encodeRecordId } from '../../utils/index';
7
7
 
8
8
  // DevTools interfaces (matching extension expectations)
@@ -13,8 +13,8 @@ export interface DevToolsEvent {
13
13
  payload: any;
14
14
  }
15
15
 
16
- import { DataModule } from '../data/index';
17
- import { AuthService } from '../auth/index';
16
+ import type { DataModule } from '../data/index';
17
+ import type { AuthService } from '../auth/index';
18
18
  import { AuthEventTypes } from '../auth/events/index';
19
19
 
20
20
  export class DevToolsService implements StreamUpdateReceiver {
@@ -1,9 +1,9 @@
1
- import { RecordId } from 'surrealdb';
2
- import { SchemaStructure } from '@spooky-sync/query-builder';
3
- import { RemoteDatabaseService } from '../../services/database/index';
4
- import { CacheModule, CacheRecord, RecordWithId } from '../cache/index';
5
- import { RecordVersionDiff } from '../../types';
6
- import { Logger } from '../../services/logger/index';
1
+ import type { RecordId } from 'surrealdb';
2
+ import type { SchemaStructure } from '@spooky-sync/query-builder';
3
+ import type { RemoteDatabaseService } from '../../services/database/index';
4
+ import type { CacheModule, CacheRecord, RecordWithId } from '../cache/index';
5
+ import type { RecordVersionDiff } from '../../types';
6
+ import type { Logger } from '../../services/logger/index';
7
7
  import { SyncEventTypes, createSyncEventSystem } from './events/index';
8
8
  import { encodeRecordId } from '../../utils/index';
9
9
  import { cleanRecord } from '../../utils/parser';
@@ -1,5 +1,6 @@
1
- import { createEventSystem, EventDefinition, EventSystem } from '../../../events/index';
2
- import { RecordVersionArray } from '../../../types';
1
+ import type { EventDefinition, EventSystem } from '../../../events/index';
2
+ import { createEventSystem } from '../../../events/index';
3
+ import type { RecordVersionArray } from '../../../types';
3
4
 
4
5
  export const SyncQueueEventTypes = {
5
6
  MutationEnqueued: 'MUTATION_ENQUEUED',
@@ -1,10 +1,11 @@
1
- import { LocalDatabaseService } from '../../../services/database/index';
1
+ import type { LocalDatabaseService } from '../../../services/database/index';
2
+ import type {
3
+ SyncQueueEventSystem} from '../events/index';
2
4
  import {
3
5
  createSyncQueueEventSystem,
4
- SyncQueueEventSystem,
5
6
  SyncQueueEventTypes,
6
7
  } from '../events/index';
7
- import { Logger } from '../../../services/logger/index';
8
+ import type { Logger } from '../../../services/logger/index';
8
9
 
9
10
  export type RegisterEvent = {
10
11
  type: 'register';
@@ -1,13 +1,14 @@
1
- import { RecordId } from 'surrealdb';
2
- import { LocalDatabaseService } from '../../../services/database/index';
1
+ import type { RecordId } from 'surrealdb';
2
+ import type { LocalDatabaseService } from '../../../services/database/index';
3
+ import type {
4
+ SyncQueueEventSystem} from '../events/index';
3
5
  import {
4
6
  createSyncQueueEventSystem,
5
- SyncQueueEventSystem,
6
7
  SyncQueueEventTypes,
7
8
  } from '../events/index';
8
9
  import { parseRecordIdString, extractTablePart, classifySyncError } from '../../../utils/index';
9
- import { Logger } from '../../../services/logger/index';
10
- import { PushEventOptions } from '../../../events/index';
10
+ import type { Logger } from '../../../services/logger/index';
11
+ import type { PushEventOptions } from '../../../events/index';
11
12
 
12
13
  export type CreateEvent = {
13
14
  type: 'create';
@@ -1,5 +1,5 @@
1
- import { Logger } from '../../services/logger/index';
2
- import { UpQueue, DownQueue, DownEvent, UpEvent, RollbackCallback } from './queue/index';
1
+ import type { Logger } from '../../services/logger/index';
2
+ import type { UpQueue, DownQueue, DownEvent, UpEvent, RollbackCallback } from './queue/index';
3
3
  import { SyncQueueEventTypes } from './events/index';
4
4
 
5
5
  /**
@@ -1,16 +1,17 @@
1
- import { LocalDatabaseService, RemoteDatabaseService } from '../../services/database/index';
2
- import { MutationEvent, RecordVersionArray } from '../../types';
1
+ import type { LocalDatabaseService, RemoteDatabaseService } from '../../services/database/index';
2
+ import type { RecordVersionArray } from '../../types';
3
3
  import { createSyncEventSystem, SyncEventTypes, SyncQueueEventTypes } from './events/index';
4
- import { Logger } from '../../services/logger/index';
5
- import { DownEvent, DownQueue, UpEvent, UpQueue } from './queue/index';
6
- import { RecordId, Uuid } from 'surrealdb';
4
+ import type { Logger } from '../../services/logger/index';
5
+ import type { DownEvent, UpEvent} from './queue/index';
6
+ import { DownQueue, UpQueue } from './queue/index';
7
+ import type { RecordId, Uuid } from 'surrealdb';
7
8
  import { ArraySyncer, createDiffFromDbOp } from './utils';
8
9
  import { SyncEngine } from './engine';
9
10
  import { SyncScheduler } from './scheduler';
10
- import { SchemaStructure } from '@spooky-sync/query-builder';
11
- import { CacheModule } from '../cache/index';
12
- import { DataModule } from '../data/index';
13
- import { encodeRecordId, extractTablePart, parseDuration, surql } from '../../utils/index';
11
+ import type { SchemaStructure } from '@spooky-sync/query-builder';
12
+ import type { CacheModule } from '../cache/index';
13
+ import type { DataModule } from '../data/index';
14
+ import { encodeRecordId, extractTablePart, surql } from '../../utils/index';
14
15
 
15
16
  /**
16
17
  * The main synchronization engine for Sp00ky.
@@ -6,7 +6,7 @@ import {
6
6
  createDiffFromDbOp,
7
7
  ArraySyncer,
8
8
  } from './utils';
9
- import { RecordVersionArray, RecordVersionDiff } from '../../types';
9
+ import type { RecordVersionArray, RecordVersionDiff } from '../../types';
10
10
  import { encodeRecordId } from '../../utils/index';
11
11
 
12
12
  function rid(table: string, id: string): RecordId<string> {
@@ -305,7 +305,7 @@ describe('ArraySyncer', () => {
305
305
  expect(diff!.removed).toHaveLength(3);
306
306
  // Check they come in sorted order
307
307
  const removedIds = diff!.removed.map((r) => encodeRecordId(r));
308
- const sorted = [...removedIds].sort();
308
+ const sorted = [...removedIds].toSorted();
309
309
  expect(removedIds).toEqual(sorted);
310
310
  });
311
311
  });
@@ -1,5 +1,5 @@
1
- import { RecordId } from 'surrealdb';
2
- import { RecordVersionArray, RecordVersionDiff } from '../../types';
1
+ import type { RecordId } from 'surrealdb';
2
+ import type { RecordVersionArray, RecordVersionDiff } from '../../types';
3
3
  import { parseRecordIdString, encodeRecordId } from '../../utils/index';
4
4
 
5
5
  export class ArraySyncer {
@@ -8,8 +8,8 @@ export class ArraySyncer {
8
8
  private needsSort = false;
9
9
 
10
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]));
11
+ this.remoteArray = remoteArray.toSorted((a, b) => a[0].localeCompare(b[0]));
12
+ this.localArray = localArray.toSorted((a, b) => a[0].localeCompare(b[0]));
13
13
  }
14
14
 
15
15
  /**
@@ -92,10 +92,12 @@ export function diffRecordVersionArray(
92
92
  return {
93
93
  added: added.map((id) => ({
94
94
  id: parseRecordIdString(id),
95
+ // oxlint-disable-next-line no-non-null-assertion
95
96
  version: remoteMap.get(id)!,
96
97
  })),
97
98
  updated: updated.map((id) => ({
98
99
  id: parseRecordIdString(id),
100
+ // oxlint-disable-next-line no-non-null-assertion
99
101
  version: remoteMap.get(id)!,
100
102
  })),
101
103
  removed: removed.map(parseRecordIdString),
@@ -126,7 +128,7 @@ export function applyRecordVersionDiff(
126
128
  currentMap.set(encodeRecordId(item.id), item.version);
127
129
  }
128
130
 
129
- return Array.from(currentMap).sort((a, b) => a[0].localeCompare(b[0]));
131
+ return Array.from(currentMap).toSorted((a, b) => a[0].localeCompare(b[0]));
130
132
  }
131
133
 
132
134
  export function createDiffFromDbOp(
package/src/otel/index.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { Level } from 'pino';
2
- import { PinoTransmit } from '../types';
1
+ import type { Level } from 'pino';
2
+ import type { PinoTransmit } from '../types';
3
3
 
4
4
  // Map pino levels to OTEL severity numbers
5
5
  // https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/logs/data-model.md#severity-fields
@@ -74,6 +74,7 @@ export function createOtelTransmit(endpoint: string, level: Level = 'info'): Pin
74
74
  return {
75
75
  level: level,
76
76
  send: (levelLabel: string, logEvent: any) => {
77
+ // oxlint-disable-next-line promise/always-return
77
78
  otelReady.then((getOtelLogger) => {
78
79
  try {
79
80
  const messages = [...logEvent.messages];
@@ -81,12 +82,12 @@ export function createOtelTransmit(endpoint: string, level: Level = 'info'): Pin
81
82
 
82
83
  // Construct the message body
83
84
  let body = '';
84
- const msg = messages.pop();
85
+ const lastMsg = messages.pop();
85
86
 
86
- if (typeof msg === 'string') {
87
- body = msg;
88
- } else if (msg) {
89
- body = JSON.stringify(msg);
87
+ if (typeof lastMsg === 'string') {
88
+ body = lastMsg;
89
+ } else if (lastMsg) {
90
+ body = JSON.stringify(lastMsg);
90
91
  }
91
92
 
92
93
  let category = 'sp00ky-client::unknown';
@@ -114,9 +115,11 @@ export function createOtelTransmit(endpoint: string, level: Level = 'info'): Pin
114
115
  timestamp: new Date(logEvent.ts),
115
116
  });
116
117
  } catch (e) {
118
+ // oxlint-disable-next-line no-console
117
119
  console.warn('Failed to transmit log to OTEL endpoint', e);
118
120
  }
119
121
  }).catch((e) => {
122
+ // oxlint-disable-next-line no-console
120
123
  console.warn('Failed to load OpenTelemetry modules', e);
121
124
  });
122
125
  },
@@ -1,11 +1,9 @@
1
- import { Surreal, SurrealTransaction } from 'surrealdb';
2
- import { createLogger, Logger } from '../logger/index';
3
- import {
1
+ import type { Surreal, SurrealTransaction } from 'surrealdb';
2
+ import type { Logger } from '../logger/index';
3
+ import type {
4
4
  DatabaseEventSystem,
5
- DatabaseEventTypes,
6
- DatabaseQueryEventPayload,
7
- } from './events/index';
8
- import { SealedQuery } from '../../utils/surql';
5
+ DatabaseEventTypes} from './events/index';
6
+ import type { SealedQuery } from '../../utils/surql';
9
7
 
10
8
  export abstract class AbstractDatabaseService {
11
9
  protected client: Surreal;
@@ -43,6 +41,7 @@ export abstract class AbstractDatabaseService {
43
41
  async query<T extends unknown[]>(query: string, vars?: Record<string, unknown>): Promise<T> {
44
42
  return new Promise((resolve, reject) => {
45
43
  this.queryQueue = this.queryQueue
44
+ // oxlint-disable-next-line promise/always-return
46
45
  .then(async () => {
47
46
  const startTime = performance.now();
48
47
  try {
@@ -87,6 +86,7 @@ export abstract class AbstractDatabaseService {
87
86
  { query, vars, err, Category: 'sp00ky-client::Database::query' },
88
87
  'Query execution failed'
89
88
  );
89
+ // oxlint-disable-next-line no-multiple-resolved -- resolve/reject are in try/catch, mutually exclusive
90
90
  reject(err);
91
91
  }
92
92
  })
@@ -1,4 +1,5 @@
1
- import { createEventSystem, EventDefinition, EventSystem } from '../../../events/index';
1
+ import type { EventDefinition, EventSystem } from '../../../events/index';
2
+ import { createEventSystem } from '../../../events/index';
2
3
 
3
4
  export const DatabaseEventTypes = {
4
5
  LocalQuery: 'DATABASE_LOCAL_QUERY',
@@ -1,6 +1,6 @@
1
- import type { Surreal } from 'surrealdb';
2
- import { Logger, createLogger } from '../logger/index';
3
- import { LocalDatabaseService } from './local';
1
+ import type { Logger} from '../logger/index';
2
+ import { createLogger } from '../logger/index';
3
+ import type { LocalDatabaseService } from './local';
4
4
 
5
5
  export interface SchemaRecord {
6
6
  hash: string;
@@ -23,8 +23,8 @@ export class LocalMigrator {
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> {
@@ -94,7 +94,7 @@ export class LocalMigrator {
94
94
  `SELECT hash, created_at FROM ONLY _00_schema ORDER BY created_at DESC LIMIT 1;`
95
95
  );
96
96
  return lastSchemaRecord?.hash === hash;
97
- } catch (error) {
97
+ } catch (_error) {
98
98
  return false;
99
99
  }
100
100
  }
@@ -102,7 +102,7 @@ export class LocalMigrator {
102
102
  private async recreateDatabase(database: string) {
103
103
  try {
104
104
  await this.localDb.query(`DEFINE DATABASE _00_temp;`);
105
- } catch (e) {
105
+ } catch (_e) {
106
106
  // Ignore if exists
107
107
  }
108
108
 
@@ -111,7 +111,7 @@ export class LocalMigrator {
111
111
  USE DB _00_temp;
112
112
  REMOVE DATABASE ${database};
113
113
  `);
114
- } catch (e) {
114
+ } catch (_e) {
115
115
  // Ignore error if database doesn't exist
116
116
  }
117
117
 
@@ -1,10 +1,11 @@
1
- import { applyDiagnostics, DateTime, Diagnostic, RecordId, Surreal } from 'surrealdb';
1
+ import type { Diagnostic} from 'surrealdb';
2
+ import { applyDiagnostics, DateTime, RecordId, Surreal } from 'surrealdb';
2
3
  import { createWasmWorkerEngines } from '@surrealdb/wasm';
3
- import { Sp00kyConfig } from '../../types';
4
- import { Logger } from '../logger/index';
4
+ import type { Sp00kyConfig } from '../../types';
5
+ import type { Logger } from '../logger/index';
5
6
  import { AbstractDatabaseService } from './database';
6
7
  import { createDatabaseEventSystem, DatabaseEventTypes } from './events/index';
7
- import { encodeRecordId, parseRecordIdString, surql } from '../../utils/index';
8
+ import { encodeRecordId } from '../../utils/index';
8
9
 
9
10
  export class LocalDatabaseService extends AbstractDatabaseService {
10
11
  private config: Sp00kyConfig<any>['database'];
@@ -1,12 +1,12 @@
1
+ import type {
2
+ Diagnostic} from 'surrealdb';
1
3
  import {
2
4
  applyDiagnostics,
3
5
  createRemoteEngines,
4
- Diagnostic,
5
6
  Surreal,
6
- SurrealTransaction,
7
7
  } from 'surrealdb';
8
- import { Sp00kyConfig } from '../../types';
9
- import { Logger } from '../logger/index';
8
+ import type { Sp00kyConfig } from '../../types';
9
+ import type { Logger } from '../logger/index';
10
10
  import { AbstractDatabaseService } from './database';
11
11
  import { createDatabaseEventSystem, DatabaseEventTypes } from './events/index';
12
12
 
@@ -1,5 +1,5 @@
1
- import pino, { Level, type Logger as PinoLogger, type LoggerOptions } from 'pino';
2
- import { PinoTransmit } from '../../types';
1
+ import pino, { type Level, type Logger as PinoLogger, type LoggerOptions } from 'pino';
2
+ import type { PinoTransmit } from '../../types';
3
3
 
4
4
  export type Logger = PinoLogger;
5
5
 
@@ -7,6 +7,7 @@ export function createLogger(level: Level = 'info', transmit?: PinoTransmit): Lo
7
7
  const browserConfig: LoggerOptions['browser'] = {
8
8
  asObject: true,
9
9
  write: (o: any) => {
10
+ // oxlint-disable-next-line no-console
10
11
  console.log(JSON.stringify(o));
11
12
  },
12
13
  };
@@ -1,5 +1,5 @@
1
- import { Logger } from 'pino';
2
- import { PersistenceClient } from '../../types';
1
+ import type { Logger } from 'pino';
2
+ import type { PersistenceClient } from '../../types';
3
3
 
4
4
  export class LocalStoragePersistenceClient implements PersistenceClient {
5
5
  private logger: Logger;
@@ -1,5 +1,5 @@
1
- import { Logger } from 'pino';
2
- import { PersistenceClient } from '../../types';
1
+ import type { Logger } from 'pino';
2
+ import type { PersistenceClient } from '../../types';
3
3
 
4
4
  export class ResilientPersistenceClient implements PersistenceClient {
5
5
  private logger: Logger;
@@ -1,7 +1,7 @@
1
- import { PersistenceClient } from '../../types';
1
+ import type { PersistenceClient } from '../../types';
2
2
  import { parseRecordIdString, surql } from '../../utils/index';
3
- import { Logger } from 'pino';
4
- import { AbstractDatabaseService } from '../database/database';
3
+ import type { Logger } from 'pino';
4
+ import type { AbstractDatabaseService } from '../database/database';
5
5
 
6
6
  export class SurrealDBPersistenceClient implements PersistenceClient {
7
7
  private logger: Logger;
@@ -1,10 +1,11 @@
1
+ // oxlint-disable-next-line no-named-as-default -- WASM module default export convention
1
2
  import init, { Sp00kyProcessor } from '@spooky-sync/ssp-wasm';
2
- import { EventDefinition, EventSystem } from '../../events/index';
3
- import { Logger } from 'pino';
4
- import { LocalDatabaseService } from '../database/index';
5
- import { WasmProcessor, WasmStreamUpdate } from './wasm-types';
6
- import { Duration } from 'surrealdb';
7
- import { PersistenceClient, QueryTimeToLive, RecordVersionArray } from '../../types';
3
+ import type { EventDefinition, EventSystem } from '../../events/index';
4
+ import type { Logger } from 'pino';
5
+ import type { LocalDatabaseService } from '../database/index';
6
+ import type { WasmProcessor, WasmStreamUpdate } from './wasm-types';
7
+ import type { Duration } from 'surrealdb';
8
+ import type { PersistenceClient, QueryTimeToLive, RecordVersionArray } from '../../types';
8
9
 
9
10
  // Simple interface for query plan registration (replaces Incantation class)
10
11
  interface QueryPlanConfig {
@@ -127,7 +127,7 @@ describe('StreamProcessor Ingest Behavior', () => {
127
127
  const params = { id: new MockRecordId('user', '2dng4ngbicbl0scod87i') };
128
128
  const normalizedParams = normalizeValue(params);
129
129
 
130
- const ingestedRecord = {
130
+ const _ingestedRecord = {
131
131
  id: 'user:2dng4ngbicbl0scod87i',
132
132
  username: 'sara',
133
133
  };
@@ -1,4 +1,4 @@
1
- import { RecordVersionArray } from '../../types';
1
+ import type { RecordVersionArray } from '../../types';
2
2
 
3
3
  export interface WasmStreamUpdate {
4
4
  query_id: string;
package/src/sp00ky.ts CHANGED
@@ -1,24 +1,21 @@
1
1
  import { DataModule } from './modules/data/index';
2
- import {
2
+ import type {
3
3
  Sp00kyConfig,
4
4
  QueryTimeToLive,
5
5
  Sp00kyQueryResultPromise,
6
6
  PersistenceClient,
7
- MutationEvent,
8
7
  UpdateOptions,
9
- RunOptions,
10
- } from './types';
8
+ RunOptions} from './types';
11
9
  import {
12
10
  LocalDatabaseService,
13
11
  LocalMigrator,
14
12
  RemoteDatabaseService,
15
13
  } from './services/database/index';
16
- import { Surreal } from 'surrealdb';
17
- import { Sp00kySync, UpEvent } from './modules/sync/index';
18
- import {
14
+ import type { UpEvent } from './modules/sync/index';
15
+ import { Sp00kySync } from './modules/sync/index';
16
+ import type {
19
17
  GetTable,
20
18
  InnerQuery,
21
- QueryBuilder,
22
19
  QueryOptions,
23
20
  SchemaStructure,
24
21
  TableModel,
@@ -26,7 +23,9 @@ import {
26
23
  BucketNames,
27
24
  BackendNames,
28
25
  BackendRoutes,
29
- RoutePayload,
26
+ RoutePayload} from '@spooky-sync/query-builder';
27
+ import {
28
+ QueryBuilder
30
29
  } from '@spooky-sync/query-builder';
31
30
 
32
31
  import { DevToolsService } from './modules/devtools/index';
package/src/types.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { RecordId, SchemaStructure } from '@spooky-sync/query-builder';
2
- import { Level, type LoggerOptions } from 'pino';
3
- import { PushEventOptions } from './events/index';
4
- import { UpEvent } from './modules/sync/index';
1
+ import type { RecordId, SchemaStructure } from '@spooky-sync/query-builder';
2
+ import type { Level, LoggerOptions } from 'pino';
3
+ import type { PushEventOptions } from './events/index';
4
+ import type { UpEvent } from './modules/sync/index';
5
5
 
6
- export type { Level } from 'pino';
6
+ export type { Level };
7
7
 
8
8
  /**
9
9
  * A pino browser transmit object for forwarding logs to an external sink (e.g. OpenTelemetry).
@@ -214,6 +214,8 @@ export interface RunOptions {
214
214
  assignedTo?: string;
215
215
  max_retries?: number;
216
216
  retry_strategy?: 'linear' | 'exponential';
217
+ /** Timeout in seconds for the backend HTTP call. Only used if the backend allows timeout override. */
218
+ timeout?: number;
217
219
  }
218
220
 
219
221
  /**
@@ -1,7 +1,7 @@
1
- import { GetTable, SchemaStructure, TableModel, TableNames } from '@spooky-sync/query-builder';
1
+ import type { GetTable, SchemaStructure, TableModel, TableNames } from '@spooky-sync/query-builder';
2
2
  import { Uuid, RecordId, Duration } from 'surrealdb';
3
- import { Logger } from '../services/logger/index';
4
- import { QueryTimeToLive } from '../types';
3
+ import type { Logger } from '../services/logger/index';
4
+ import type { QueryTimeToLive } from '../types';
5
5
 
6
6
  export * from './surql';
7
7
  export * from './parser';
@@ -74,12 +74,12 @@ export function decodeFromSp00ky<S extends SchemaStructure, T extends TableNames
74
74
  for (const field of Object.keys(table.columns)) {
75
75
  const column = table.columns[field] as any;
76
76
  const relation = schema.relationships.find((r) => r.from === tableName && r.field === field);
77
- if ((column.recordId || relation) && encoded[field] != null) {
77
+ if ((column.recordId || relation) && encoded[field] !== null && encoded[field] !== undefined) {
78
78
  if (encoded[field] instanceof RecordId) {
79
79
  encoded[field] = `${encoded[field].table.toString()}:${encoded[field].id}`;
80
80
  } else if (
81
81
  relation &&
82
- (encoded[field] instanceof Object || encoded[field] instanceof Array)
82
+ (encoded[field] instanceof Object || Array.isArray(encoded[field]))
83
83
  ) {
84
84
  if (Array.isArray(encoded[field])) {
85
85
  encoded[field] = encoded[field].map((item) =>
@@ -117,7 +117,7 @@ export function parseDuration(duration: QueryTimeToLive | Duration): number {
117
117
 
118
118
  const match = duration.match(/^(\d+)([smh])$/);
119
119
  if (!match) return 600000;
120
- const val = parseInt(match[1], 10);
120
+ const val = Number.parseInt(match[1], 10);
121
121
  const unit = match[2];
122
122
  switch (unit) {
123
123
  case 's':
@@ -1,4 +1,5 @@
1
- import { ColumnSchema, RecordId } from '@spooky-sync/query-builder';
1
+ import type { ColumnSchema} from '@spooky-sync/query-builder';
2
+ import { RecordId } from '@spooky-sync/query-builder';
2
3
  import { parseRecordIdString } from './index';
3
4
  import { DateTime } from 'surrealdb';
4
5
 
@@ -1,4 +1,4 @@
1
- import { MutationEventType } from '../types';
1
+ import type { MutationEventType } from '../types';
2
2
 
3
3
  // ==================== TYPES ====================
4
4
 
@@ -90,16 +90,16 @@ export const surql: SurqlHelper = {
90
90
  returnValues: ({ field: string; alias: string } | string)[]
91
91
  ) {
92
92
  return `SELECT ${returnValues
93
- .map((returnValues) =>
94
- typeof returnValues === 'string'
95
- ? returnValues
96
- : `${returnValues.field} as ${returnValues.alias}`
93
+ .map((rv) =>
94
+ typeof rv === 'string'
95
+ ? rv
96
+ : `${rv.field} as ${rv.alias}`
97
97
  )
98
98
  .join(',')} FROM ${table} WHERE ${whereVar
99
- .map((whereVar) =>
100
- typeof whereVar === 'string'
101
- ? `${whereVar} = $${whereVar}`
102
- : `${whereVar.field} = $${whereVar.variable}`
99
+ .map((wv) =>
100
+ typeof wv === 'string'
101
+ ? `${wv} = $${wv}`
102
+ : `${wv.field} = $${wv.variable}`
103
103
  )
104
104
  .join(' AND ')}`;
105
105
  },
@@ -136,12 +136,12 @@ export const surql: SurqlHelper = {
136
136
  keyDataVar: ({ key: string; variable: string } | { statement: string } | string)[]
137
137
  ) {
138
138
  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}`
139
+ .map((kdv) =>
140
+ typeof kdv === 'string'
141
+ ? `${kdv} = $${kdv}`
142
+ : 'statement' in kdv
143
+ ? kdv.statement
144
+ : `${kdv.key} = $${kdv.variable}`
145
145
  )
146
146
  .join(', ')}`;
147
147
  },