@syncular/server 0.15.46 → 0.15.47

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 (50) hide show
  1. package/README.md +10 -3
  2. package/dist/context.d.ts +2 -1
  3. package/dist/index-bun.d.ts +2 -0
  4. package/dist/index-bun.js +2 -0
  5. package/dist/index-node.d.ts +2 -0
  6. package/dist/index-node.js +2 -0
  7. package/dist/index.d.ts +3 -2
  8. package/dist/index.js +3 -6
  9. package/dist/pull.js +1 -1
  10. package/dist/sqlite-blob-store.d.ts +4 -9
  11. package/dist/sqlite-blob-store.js +5 -10
  12. package/dist/sqlite-bun-driver.d.ts +11 -0
  13. package/dist/sqlite-bun-driver.js +27 -0
  14. package/dist/sqlite-bun.d.ts +24 -0
  15. package/dist/sqlite-bun.js +40 -0
  16. package/dist/sqlite-dialect.d.ts +7 -7
  17. package/dist/sqlite-dialect.js +2 -2
  18. package/dist/sqlite-driver.d.ts +26 -0
  19. package/dist/sqlite-driver.js +8 -0
  20. package/dist/sqlite-image.d.ts +7 -9
  21. package/dist/sqlite-image.js +26 -28
  22. package/dist/sqlite-lease-store.d.ts +4 -9
  23. package/dist/sqlite-lease-store.js +5 -10
  24. package/dist/sqlite-node-driver.d.ts +10 -0
  25. package/dist/sqlite-node-driver.js +30 -0
  26. package/dist/sqlite-node.d.ts +24 -0
  27. package/dist/sqlite-node.js +50 -0
  28. package/dist/sqlite-segment-store.d.ts +4 -10
  29. package/dist/sqlite-segment-store.js +6 -9
  30. package/dist/sqlite-storage.d.ts +3 -11
  31. package/dist/sqlite-storage.js +8 -5
  32. package/dist/storage-errors.js +4 -1
  33. package/package.json +18 -3
  34. package/src/context.ts +2 -1
  35. package/src/index-bun.ts +9 -0
  36. package/src/index-node.ts +9 -0
  37. package/src/index.ts +8 -6
  38. package/src/pull.ts +1 -1
  39. package/src/sqlite-blob-store.ts +11 -10
  40. package/src/sqlite-bun-driver.ts +42 -0
  41. package/src/sqlite-bun.ts +53 -0
  42. package/src/sqlite-dialect.ts +7 -7
  43. package/src/sqlite-driver.ts +44 -0
  44. package/src/sqlite-image.ts +44 -49
  45. package/src/sqlite-lease-store.ts +11 -10
  46. package/src/sqlite-node-driver.ts +46 -0
  47. package/src/sqlite-node.ts +62 -0
  48. package/src/sqlite-segment-store.ts +11 -11
  49. package/src/sqlite-storage.ts +13 -7
  50. package/src/storage-errors.ts +4 -1
@@ -0,0 +1,46 @@
1
+ import { DatabaseSync, type SQLInputValue } from 'node:sqlite';
2
+ import type {
3
+ SqliteDatabase,
4
+ SqliteRunResult,
5
+ SqliteStatement,
6
+ SqliteValue,
7
+ } from './sqlite-driver';
8
+
9
+ function bind(value: SqliteValue): SQLInputValue {
10
+ return typeof value === 'boolean' ? (value ? 1 : 0) : value;
11
+ }
12
+
13
+ function row<Row>(value: Record<string, unknown> | undefined): Row | null {
14
+ return (value ?? null) as Row | null;
15
+ }
16
+
17
+ export class NodeSqliteDatabase implements SqliteDatabase {
18
+ readonly native: DatabaseSync;
19
+
20
+ constructor(path = ':memory:') {
21
+ this.native = new DatabaseSync(path);
22
+ }
23
+
24
+ exec(sql: string): void {
25
+ this.native.exec(sql);
26
+ }
27
+
28
+ run(sql: string, bindings: readonly SqliteValue[] = []): SqliteRunResult {
29
+ return this.native.prepare(sql).run(...bindings.map(bind));
30
+ }
31
+
32
+ query<Row, Params extends readonly SqliteValue[]>(
33
+ sql: string,
34
+ ): SqliteStatement<Row, Params> {
35
+ const statement = this.native.prepare(sql);
36
+ return {
37
+ run: (...params) => statement.run(...params.map(bind)),
38
+ get: (...params) => row<Row>(statement.get(...params.map(bind))),
39
+ all: (...params) => statement.all(...params.map(bind)) as Row[],
40
+ };
41
+ }
42
+
43
+ close(): void {
44
+ this.native.close();
45
+ }
46
+ }
@@ -0,0 +1,62 @@
1
+ import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { SqliteBlobStore as SharedSqliteBlobStore } from './sqlite-blob-store';
5
+ import type { SqliteDatabase } from './sqlite-driver';
6
+ import { type SqliteImageBuilder, writeSqliteImage } from './sqlite-image';
7
+ import { SqliteLeaseStore as SharedSqliteLeaseStore } from './sqlite-lease-store';
8
+ import { NodeSqliteDatabase } from './sqlite-node-driver';
9
+ import { SqliteSegmentStore as SharedSqliteSegmentStore } from './sqlite-segment-store';
10
+ import { SqliteServerStorage as SharedSqliteServerStorage } from './sqlite-storage';
11
+
12
+ function database(value: SqliteDatabase | string): SqliteDatabase {
13
+ return typeof value === 'string' ? new NodeSqliteDatabase(value) : value;
14
+ }
15
+
16
+ export class SqliteServerStorage extends SharedSqliteServerStorage {
17
+ constructor(value: SqliteDatabase | string = ':memory:') {
18
+ super(database(value));
19
+ }
20
+ }
21
+
22
+ export class SqliteSegmentStore extends SharedSqliteSegmentStore {
23
+ constructor(
24
+ value: SqliteDatabase | string = ':memory:',
25
+ options?: { ttlMs?: number },
26
+ ) {
27
+ super(database(value), options);
28
+ }
29
+ }
30
+
31
+ export class SqliteBlobStore extends SharedSqliteBlobStore {
32
+ constructor(value: SqliteDatabase | string = ':memory:') {
33
+ super(database(value));
34
+ }
35
+ }
36
+
37
+ export class SqliteLeaseStore extends SharedSqliteLeaseStore {
38
+ constructor(
39
+ value: SqliteDatabase | string = ':memory:',
40
+ options?: { readonly leaseId?: () => string },
41
+ ) {
42
+ super(database(value), options);
43
+ }
44
+ }
45
+
46
+ export const buildSqliteImage: SqliteImageBuilder = (input) => {
47
+ const directory = mkdtempSync(join(tmpdir(), 'syncular-server-image-'));
48
+ const path = join(directory, 'segment.db');
49
+ const db = new NodeSqliteDatabase(path);
50
+ try {
51
+ try {
52
+ writeSqliteImage(db, input);
53
+ } finally {
54
+ db.close();
55
+ }
56
+ return new Uint8Array(readFileSync(path));
57
+ } finally {
58
+ rmSync(directory, { recursive: true, force: true });
59
+ }
60
+ };
61
+
62
+ export { NodeSqliteDatabase } from './sqlite-node-driver';
@@ -1,13 +1,6 @@
1
1
  /**
2
- * SQLite-backed segment store via `bun:sqlite` (dev/bench convenience,
3
- * dependency-free). Bun-specific by design: it imports `bun:sqlite` at the
4
- * top level, so it lives in its own module — importing it opts into the Bun
5
- * runtime. The runtime-neutral `SegmentStore` interface, `MemorySegmentStore`,
6
- * and `segmentIdFor` stay in `segment-store.ts` so the Workers/edge core can
7
- * import them without pulling in `bun:sqlite` (runtime neutrality is enforced
8
- * by `test/runtime-neutrality.test.ts`).
2
+ * SQLite-backed segment store over the shared synchronous driver.
9
3
  */
10
- import { Database } from 'bun:sqlite';
11
4
  import {
12
5
  DEFAULT_SEGMENT_TTL_MS,
13
6
  type SegmentFindKey,
@@ -17,16 +10,23 @@ import {
17
10
  type SegmentStoreStats,
18
11
  segmentIdFor,
19
12
  } from './segment-store';
13
+ import {
14
+ SqliteAdapterRequiredError,
15
+ type SqliteDatabase,
16
+ } from './sqlite-driver';
20
17
 
21
18
  export class SqliteSegmentStore implements SegmentStore {
22
- readonly db: Database;
19
+ readonly db: SqliteDatabase;
23
20
  #ttlMs: number;
24
21
 
25
22
  constructor(
26
- db: Database | string = ':memory:',
23
+ db: SqliteDatabase | string = ':memory:',
27
24
  options?: { ttlMs?: number },
28
25
  ) {
29
- this.db = typeof db === 'string' ? new Database(db) : db;
26
+ if (typeof db === 'string') {
27
+ throw new SqliteAdapterRequiredError();
28
+ }
29
+ this.db = db;
30
30
  this.#ttlMs = options?.ttlMs ?? DEFAULT_SEGMENT_TTL_MS;
31
31
  this.db.exec(`
32
32
  CREATE TABLE IF NOT EXISTS sync_segments(
@@ -1,12 +1,11 @@
1
1
  /**
2
- * SQLite storage via `bun:sqlite` (dev-speed, dependency-free).
2
+ * SQLite server storage over the shared synchronous driver.
3
3
  *
4
4
  * Scope fanout is index-first: both the commit log and the
5
5
  * current-row table carry a (table, variable, value) inverted index; reads
6
6
  * select candidates from the index and verify the full multi-variable
7
7
  * match against the stored scope map — never a log scan.
8
8
  */
9
- import { Database } from 'bun:sqlite';
10
9
  import {
11
10
  bindAuthoritativePartition,
12
11
  prepareAuthoritativeQuery,
@@ -45,6 +44,10 @@ import {
45
44
  serializePushResult,
46
45
  toStoredRow,
47
46
  } from './sqlite-dialect';
47
+ import {
48
+ SqliteAdapterRequiredError,
49
+ type SqliteDatabase,
50
+ } from './sqlite-driver';
48
51
  import type {
49
52
  AuthoritativeQueryRequest,
50
53
  AuthoritativeQueryResult,
@@ -160,7 +163,7 @@ class SqliteTransaction implements StorageTransaction {
160
163
  clientCommitId: string,
161
164
  ): Promise<StoredPushResult | undefined> {
162
165
  this.#assertOpen();
163
- // One shared bun:sqlite connection this read runs inside this
166
+ // One shared SQLite connection: this read runs inside this
164
167
  // transaction's BEGIN IMMEDIATE.
165
168
  return this.#storage.getPushResult(
166
169
  this.#partition,
@@ -379,8 +382,8 @@ class SqliteTransaction implements StorageTransaction {
379
382
  }
380
383
 
381
384
  export class SqliteServerStorage implements ServerStorage {
382
- readonly db: Database;
383
- /** One bun:sqlite connection can own only one transaction at a time. */
385
+ readonly db: SqliteDatabase;
386
+ /** One SQLite connection can own only one transaction at a time. */
384
387
  #transactionTail: Promise<void> = Promise.resolve();
385
388
  /** Set by `ensureSchema`: app-table lookup for the relational row store. */
386
389
  #tables: ReadonlyMap<string, CompiledTable> | undefined;
@@ -400,8 +403,11 @@ export class SqliteServerStorage implements ServerStorage {
400
403
  }
401
404
  }
402
405
 
403
- constructor(db: Database | string = ':memory:') {
404
- this.db = typeof db === 'string' ? new Database(db) : db;
406
+ constructor(db: SqliteDatabase | string = ':memory:') {
407
+ if (typeof db === 'string') {
408
+ throw new SqliteAdapterRequiredError();
409
+ }
410
+ this.db = db;
405
411
  this.db.exec(SQLITE_DDL);
406
412
  }
407
413
 
@@ -52,6 +52,7 @@ export class StorageQueryError extends Error {
52
52
  interface DriverError {
53
53
  readonly code?: unknown;
54
54
  readonly errno?: unknown;
55
+ readonly errcode?: unknown;
55
56
  readonly message?: unknown;
56
57
  }
57
58
 
@@ -72,7 +73,9 @@ export function isSqliteConstraintError(error: unknown): boolean {
72
73
  return true;
73
74
  }
74
75
  const errno = candidate?.errno;
75
- return typeof errno === 'number' && (errno & 0xff) === 19;
76
+ if (typeof errno === 'number' && (errno & 0xff) === 19) return true;
77
+ const errcode = candidate?.errcode;
78
+ return typeof errcode === 'number' && (errcode & 0xff) === 19;
76
79
  }
77
80
 
78
81
  /** PostgreSQL SQLSTATE class 23: integrity constraint violation. */