@syncular/client 0.21.1 → 0.23.0
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/dist/bun-database.d.ts +4 -1
- package/dist/bun-database.js +24 -1
- package/dist/client.d.ts +47 -0
- package/dist/client.js +347 -7
- package/dist/database.d.ts +27 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +5 -0
- package/dist/invalidation.d.ts +9 -0
- package/dist/node-database.d.ts +4 -1
- package/dist/node-database.js +24 -1
- package/dist/previous-version.d.ts +207 -0
- package/dist/previous-version.js +732 -0
- package/dist/query-guard.d.ts +5 -0
- package/dist/query-guard.js +5 -0
- package/dist/reactive-store.js +34 -9
- package/dist/schema.d.ts +4 -2
- package/dist/schema.js +15 -2
- package/dist/wasm-database.js +87 -35
- package/dist/worker-entry.js +15 -4
- package/dist/worker-host.d.ts +13 -1
- package/dist/worker-host.js +13 -0
- package/dist/worker-protocol.d.ts +16 -1
- package/package.json +3 -3
- package/src/bun-database.ts +26 -1
- package/src/client.ts +521 -10
- package/src/database.ts +28 -0
- package/src/index.ts +13 -0
- package/src/invalidation.ts +9 -0
- package/src/node-database.ts +26 -1
- package/src/previous-version.ts +1070 -0
- package/src/query-guard.ts +5 -0
- package/src/reactive-store.ts +36 -16
- package/src/schema.ts +19 -2
- package/src/wasm-database.ts +114 -47
- package/src/worker-entry.ts +16 -4
- package/src/worker-host.ts +40 -0
- package/src/worker-protocol.ts +19 -0
package/src/database.ts
CHANGED
|
@@ -9,6 +9,24 @@ export type SqlValue = string | number | bigint | boolean | Uint8Array | null;
|
|
|
9
9
|
|
|
10
10
|
export type SqlRow = Record<string, SqlValue>;
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* RFC 0005: a second database file in this database's own storage scope (the
|
|
14
|
+
* same on-disk directory, or the same OPFS SAH pool directory). Used to keep
|
|
15
|
+
* the previous-version container off the connection `query()` and unaware code
|
|
16
|
+
* use. The read guarantee is narrow: an older client's ordinary query
|
|
17
|
+
* connection does not attach this file, so no SQL it runs can reach the
|
|
18
|
+
* container. It is NOT protection against arbitrary same-origin access and NOT
|
|
19
|
+
* protection against native filesystem access.
|
|
20
|
+
*/
|
|
21
|
+
export interface SiblingDatabase {
|
|
22
|
+
/** The open connection; the caller closes it before removal. */
|
|
23
|
+
readonly database: ClientDatabase;
|
|
24
|
+
/** Close the handle. A live handle blocks physical removal. */
|
|
25
|
+
close(): void;
|
|
26
|
+
/** Best-effort physical removal of the backing file. Close the handle first. */
|
|
27
|
+
removeFile(): void;
|
|
28
|
+
}
|
|
29
|
+
|
|
12
30
|
export interface ClientDatabase {
|
|
13
31
|
/** Execute a single statement (no result rows). */
|
|
14
32
|
exec(sql: string, params?: readonly SqlValue[]): void;
|
|
@@ -32,6 +50,16 @@ export interface ClientDatabase {
|
|
|
32
50
|
alias: string,
|
|
33
51
|
fn: () => T | Promise<T>,
|
|
34
52
|
): Promise<T>;
|
|
53
|
+
/**
|
|
54
|
+
* RFC 0005: open (creating on demand) the named sibling database file.
|
|
55
|
+
* `undefined` means the host provides no sibling capability, which makes the
|
|
56
|
+
* previous-version feature unavailable there. The NAME is code-derived and is
|
|
57
|
+
* not persisted in the replica database (data minimisation; not a security
|
|
58
|
+
* control).
|
|
59
|
+
*/
|
|
60
|
+
openSibling?(name: string): SiblingDatabase | undefined;
|
|
61
|
+
/** RFC 0005: non-creating existence probe for {@link openSibling}. */
|
|
62
|
+
siblingExists?(name: string): boolean;
|
|
35
63
|
close(): void;
|
|
36
64
|
}
|
|
37
65
|
|
package/src/index.ts
CHANGED
|
@@ -28,6 +28,19 @@ export * from './multi-tab';
|
|
|
28
28
|
export * from './naming';
|
|
29
29
|
export * from './outbox';
|
|
30
30
|
export * from './outcomes';
|
|
31
|
+
// RFC 0005: explicit public surface. Everything else in the module is
|
|
32
|
+
// implementation detail used by `client.ts` (capture, container file, durable
|
|
33
|
+
// refusal/audit codecs), not package API. The feature is opt-in and its
|
|
34
|
+
// consumer-facing contract is exactly the read spec and the two result types.
|
|
35
|
+
export {
|
|
36
|
+
decodePreviousVersionAuditResult,
|
|
37
|
+
decodePreviousVersionDiscardResult,
|
|
38
|
+
decodePreviousVersionSnapshot,
|
|
39
|
+
type PreviousVersionAudit,
|
|
40
|
+
type PreviousVersionReadSpec,
|
|
41
|
+
type PreviousVersionReason,
|
|
42
|
+
type PreviousVersionSnapshot,
|
|
43
|
+
} from './previous-version';
|
|
31
44
|
export * from './query-guard';
|
|
32
45
|
export * from './reactive-store';
|
|
33
46
|
export * from './remote';
|
package/src/invalidation.ts
CHANGED
|
@@ -30,6 +30,15 @@ export interface SyncStatusSnapshot {
|
|
|
30
30
|
readonly leaseState: LeaseState | undefined;
|
|
31
31
|
readonly schemaFloor: SchemaFloor | undefined;
|
|
32
32
|
readonly syncNeeded: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* RFC 0005 A2: whether a previous-version cache is present, so a host's
|
|
35
|
+
* update/rollback path can require its discard. `createdAtMs` is the capture
|
|
36
|
+
* time when a readable cache exists.
|
|
37
|
+
*/
|
|
38
|
+
readonly previousVersionContext: {
|
|
39
|
+
readonly present: boolean;
|
|
40
|
+
readonly createdAtMs?: number;
|
|
41
|
+
};
|
|
33
42
|
}
|
|
34
43
|
|
|
35
44
|
export interface ClientChangeBatch {
|
package/src/node-database.ts
CHANGED
|
@@ -3,13 +3,14 @@
|
|
|
3
3
|
* adapter: synchronous queries, nested transactions, and SQLite image attach.
|
|
4
4
|
*/
|
|
5
5
|
import { DatabaseSync, type SQLInputValue } from 'node:sqlite';
|
|
6
|
-
import { mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { existsSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { tmpdir } from 'node:os';
|
|
8
8
|
import { join } from 'node:path';
|
|
9
9
|
import {
|
|
10
10
|
assertImageAlias,
|
|
11
11
|
type ClientDatabase,
|
|
12
12
|
runTransaction,
|
|
13
|
+
type SiblingDatabase,
|
|
13
14
|
type SqlRow,
|
|
14
15
|
type SqlValue,
|
|
15
16
|
} from './database';
|
|
@@ -36,9 +37,11 @@ function normalizeRow(row: Record<string, unknown>): SqlRow {
|
|
|
36
37
|
|
|
37
38
|
export class NodeClientDatabase implements ClientDatabase {
|
|
38
39
|
readonly db: DatabaseSync;
|
|
40
|
+
readonly #path: string;
|
|
39
41
|
#tx = { depth: 0 };
|
|
40
42
|
|
|
41
43
|
constructor(path = ':memory:') {
|
|
44
|
+
this.#path = path;
|
|
42
45
|
this.db = new DatabaseSync(path);
|
|
43
46
|
try {
|
|
44
47
|
this.db.exec('PRAGMA journal_mode = WAL');
|
|
@@ -58,6 +61,28 @@ export class NodeClientDatabase implements ClientDatabase {
|
|
|
58
61
|
return rows.map(normalizeRow);
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
/** RFC 0005: a sibling file beside the replica, e.g. `<path>.prev-context`. */
|
|
65
|
+
openSibling(name: string): SiblingDatabase {
|
|
66
|
+
const siblingPath =
|
|
67
|
+
this.#path === ':memory:' ? ':memory:' : `${this.#path}.${name}`;
|
|
68
|
+
const database = new NodeClientDatabase(siblingPath);
|
|
69
|
+
return {
|
|
70
|
+
database,
|
|
71
|
+
close: () => database.close(),
|
|
72
|
+
removeFile: () => {
|
|
73
|
+
if (siblingPath === ':memory:') return;
|
|
74
|
+
rmSync(siblingPath, { force: true });
|
|
75
|
+
rmSync(`${siblingPath}-wal`, { force: true });
|
|
76
|
+
rmSync(`${siblingPath}-shm`, { force: true });
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
siblingExists(name: string): boolean {
|
|
82
|
+
if (this.#path === ':memory:') return false;
|
|
83
|
+
return existsSync(`${this.#path}.${name}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
61
86
|
transaction<T>(fn: () => T): T {
|
|
62
87
|
return runTransaction(this.#tx, (sql) => this.db.exec(sql), fn);
|
|
63
88
|
}
|