@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.
- package/README.md +10 -3
- package/dist/context.d.ts +2 -1
- package/dist/index-bun.d.ts +2 -0
- package/dist/index-bun.js +2 -0
- package/dist/index-node.d.ts +2 -0
- package/dist/index-node.js +2 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -6
- package/dist/pull.js +1 -1
- package/dist/sqlite-blob-store.d.ts +4 -9
- package/dist/sqlite-blob-store.js +5 -10
- package/dist/sqlite-bun-driver.d.ts +11 -0
- package/dist/sqlite-bun-driver.js +27 -0
- package/dist/sqlite-bun.d.ts +24 -0
- package/dist/sqlite-bun.js +40 -0
- package/dist/sqlite-dialect.d.ts +7 -7
- package/dist/sqlite-dialect.js +2 -2
- package/dist/sqlite-driver.d.ts +26 -0
- package/dist/sqlite-driver.js +8 -0
- package/dist/sqlite-image.d.ts +7 -9
- package/dist/sqlite-image.js +26 -28
- package/dist/sqlite-lease-store.d.ts +4 -9
- package/dist/sqlite-lease-store.js +5 -10
- package/dist/sqlite-node-driver.d.ts +10 -0
- package/dist/sqlite-node-driver.js +30 -0
- package/dist/sqlite-node.d.ts +24 -0
- package/dist/sqlite-node.js +50 -0
- package/dist/sqlite-segment-store.d.ts +4 -10
- package/dist/sqlite-segment-store.js +6 -9
- package/dist/sqlite-storage.d.ts +3 -11
- package/dist/sqlite-storage.js +8 -5
- package/dist/storage-errors.js +4 -1
- package/package.json +18 -3
- package/src/context.ts +2 -1
- package/src/index-bun.ts +9 -0
- package/src/index-node.ts +9 -0
- package/src/index.ts +8 -6
- package/src/pull.ts +1 -1
- package/src/sqlite-blob-store.ts +11 -10
- package/src/sqlite-bun-driver.ts +42 -0
- package/src/sqlite-bun.ts +53 -0
- package/src/sqlite-dialect.ts +7 -7
- package/src/sqlite-driver.ts +44 -0
- package/src/sqlite-image.ts +44 -49
- package/src/sqlite-lease-store.ts +11 -10
- package/src/sqlite-node-driver.ts +46 -0
- package/src/sqlite-node.ts +62 -0
- package/src/sqlite-segment-store.ts +11 -11
- package/src/sqlite-storage.ts +13 -7
- 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
|
|
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:
|
|
19
|
+
readonly db: SqliteDatabase;
|
|
23
20
|
#ttlMs: number;
|
|
24
21
|
|
|
25
22
|
constructor(
|
|
26
|
-
db:
|
|
23
|
+
db: SqliteDatabase | string = ':memory:',
|
|
27
24
|
options?: { ttlMs?: number },
|
|
28
25
|
) {
|
|
29
|
-
|
|
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(
|
package/src/sqlite-storage.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* SQLite storage
|
|
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
|
|
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:
|
|
383
|
-
/** One
|
|
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:
|
|
404
|
-
|
|
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
|
|
package/src/storage-errors.ts
CHANGED
|
@@ -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
|
-
|
|
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. */
|