@freecorps/ember-offline 0.1.0 → 0.3.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/README.md +1 -1
- package/dist/backend.d.ts +38 -2
- package/dist/backend.js +4 -4
- package/dist/index.d.ts +9 -1
- package/dist/index.js +9 -1
- package/dist/indexeddb.d.ts +2 -1
- package/dist/indexeddb.js +22 -8
- package/dist/memory.d.ts +3 -1
- package/dist/memory.js +16 -3
- package/dist/mutations.d.ts +15 -0
- package/dist/mutations.js +112 -0
- package/dist/opfs-worker.d.ts +52 -0
- package/dist/opfs-worker.js +72 -0
- package/dist/opfs.d.ts +30 -0
- package/dist/opfs.js +66 -0
- package/dist/remote.d.ts +109 -0
- package/dist/remote.js +388 -0
- package/dist/sqlite-wasm.d.ts +30 -0
- package/dist/sqlite-wasm.js +77 -0
- package/dist/sqlite.d.ts +2 -1
- package/dist/sqlite.js +19 -5
- package/dist/store.d.ts +33 -3
- package/dist/store.js +430 -59
- package/dist/tabs.d.ts +79 -0
- package/dist/tabs.js +210 -0
- package/dist/tinybase.d.ts +78 -0
- package/dist/tinybase.js +294 -0
- package/package.json +34 -7
package/dist/sqlite.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type PendingMutationRecord } from "@freecorps/ember-client";
|
|
2
2
|
import { type LocalSchema, type SqlDriver } from "@freecorps/ember-server";
|
|
3
|
-
import type { BackendReader, Commit, OfflineBackend, QueryRecord } from "./backend.ts";
|
|
3
|
+
import type { BackendReader, Commit, OfflineBackend, QueryLogEntry, QueryRecord } from "./backend.ts";
|
|
4
4
|
import { type OfflineOptions, type OfflineStore } from "./store.ts";
|
|
5
5
|
export type OpenSqlite = (name: string) => SqlDriver & {
|
|
6
6
|
close?(): void;
|
|
@@ -23,6 +23,7 @@ export declare class SqliteBackend implements OfflineBackend {
|
|
|
23
23
|
open(): Promise<{
|
|
24
24
|
meta: Record<string, string>;
|
|
25
25
|
queries: QueryRecord[];
|
|
26
|
+
log: QueryLogEntry[];
|
|
26
27
|
}>;
|
|
27
28
|
/** Opens (and migrates) a shard's database, registering it in the main database. */
|
|
28
29
|
private shard;
|
package/dist/sqlite.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// `withSearchIndex` runs on FTS5 locally. A main database holds metadata, the pending queue and the shard list.
|
|
5
5
|
//
|
|
6
6
|
// <name> _ember_local_meta, _ember_local_pending, _ember_local_shards
|
|
7
|
-
// <name>-<sha(shard)> schema tables + _ember_local_queries (records
|
|
7
|
+
// <name>-<sha(shard)> schema tables + _ember_local_queries + _ember_local_log (records, their row-diff log and
|
|
8
|
+
// their rows commit atomically)
|
|
8
9
|
import { decodeJson, encodeJson } from "@freecorps/ember-client";
|
|
9
10
|
import { jsSearch, prepareLocalDatabase, sha256Hex, sqlLocalReader, sqlReplicaWriter, } from "@freecorps/ember-server";
|
|
10
11
|
import { createOfflineStorage } from "./store.js";
|
|
@@ -13,7 +14,10 @@ const MAIN_DDL = [
|
|
|
13
14
|
`CREATE TABLE IF NOT EXISTS "_ember_local_pending" ("seq" INTEGER PRIMARY KEY AUTOINCREMENT, "mid" TEXT NOT NULL UNIQUE, "record" TEXT NOT NULL) STRICT`,
|
|
14
15
|
`CREATE TABLE IF NOT EXISTS "_ember_local_shards" ("shard" TEXT PRIMARY KEY, "db" TEXT NOT NULL) STRICT`,
|
|
15
16
|
];
|
|
16
|
-
const QUERIES_DDL =
|
|
17
|
+
const QUERIES_DDL = [
|
|
18
|
+
`CREATE TABLE IF NOT EXISTS "_ember_local_queries" ("key" TEXT PRIMARY KEY, "record" TEXT NOT NULL) STRICT`,
|
|
19
|
+
`CREATE TABLE IF NOT EXISTS "_ember_local_log" ("key" TEXT NOT NULL, "seq" INTEGER NOT NULL, "entry" TEXT NOT NULL, PRIMARY KEY ("key", "seq")) STRICT`,
|
|
20
|
+
];
|
|
17
21
|
export class SqliteBackend {
|
|
18
22
|
schema;
|
|
19
23
|
openDb;
|
|
@@ -39,13 +43,16 @@ export class SqliteBackend {
|
|
|
39
43
|
for (const r of main.exec(`SELECT "k", "v" FROM "_ember_local_meta"`).rows)
|
|
40
44
|
meta[String(r.k)] = String(r.v);
|
|
41
45
|
const queries = [];
|
|
46
|
+
const log = [];
|
|
42
47
|
for (const r of main.exec(`SELECT "shard", "db" FROM "_ember_local_shards"`).rows) {
|
|
43
48
|
this.known.set(String(r.shard), String(r.db));
|
|
44
49
|
const s = this.shard(String(r.shard));
|
|
45
50
|
for (const q of s.driver.exec(`SELECT "record" FROM "_ember_local_queries"`).rows)
|
|
46
51
|
queries.push(decodeJson(String(q.record)));
|
|
52
|
+
for (const e of s.driver.exec(`SELECT "entry" FROM "_ember_local_log" ORDER BY "key", "seq"`).rows)
|
|
53
|
+
log.push(decodeJson(String(e.entry)));
|
|
47
54
|
}
|
|
48
|
-
return { meta, queries };
|
|
55
|
+
return { meta, queries, log };
|
|
49
56
|
}
|
|
50
57
|
/** Opens (and migrates) a shard's database, registering it in the main database. */
|
|
51
58
|
shard(shard) {
|
|
@@ -60,7 +67,8 @@ export class SqliteBackend {
|
|
|
60
67
|
}
|
|
61
68
|
const driver = this.openDb(dbName);
|
|
62
69
|
prepareLocalDatabase(driver, this.schema);
|
|
63
|
-
|
|
70
|
+
for (const ddl of QUERIES_DDL)
|
|
71
|
+
driver.exec(ddl);
|
|
64
72
|
s = { driver, writer: sqlReplicaWriter(driver, this.schema), reader: sqlLocalReader(driver, this.schema) };
|
|
65
73
|
this.shards.set(shard, s);
|
|
66
74
|
return s;
|
|
@@ -85,9 +93,15 @@ export class SqliteBackend {
|
|
|
85
93
|
s.driver.transaction(() => {
|
|
86
94
|
for (const r of c.put ?? []) {
|
|
87
95
|
s.driver.exec(`INSERT INTO "_ember_local_queries" ("key", "record") VALUES (?, ?) ON CONFLICT ("key") DO UPDATE SET "record" = excluded."record"`, r.key, encodeJson(r));
|
|
96
|
+
s.driver.exec(`DELETE FROM "_ember_local_log" WHERE "key" = ?`, r.key);
|
|
88
97
|
}
|
|
89
|
-
for (const k of c.drop ?? [])
|
|
98
|
+
for (const k of c.drop ?? []) {
|
|
90
99
|
s.driver.exec(`DELETE FROM "_ember_local_queries" WHERE "key" = ?`, k);
|
|
100
|
+
s.driver.exec(`DELETE FROM "_ember_local_log" WHERE "key" = ?`, k);
|
|
101
|
+
}
|
|
102
|
+
for (const e of c.append ?? []) {
|
|
103
|
+
s.driver.exec(`INSERT OR REPLACE INTO "_ember_local_log" ("key", "seq", "entry") VALUES (?, ?, ?)`, e.key, e.seq, encodeJson(e));
|
|
104
|
+
}
|
|
91
105
|
for (const { table, id } of c.delete ?? [])
|
|
92
106
|
s.writer.delete(table, id);
|
|
93
107
|
for (const { table, doc } of c.upsert ?? [])
|
package/dist/store.d.ts
CHANGED
|
@@ -16,6 +16,12 @@ export interface OfflineOptions {
|
|
|
16
16
|
maxQueries?: number;
|
|
17
17
|
/** Records not refreshed by the server for this long are dropped on load (with the rows only they own). Default 30 days. */
|
|
18
18
|
retentionMs?: number;
|
|
19
|
+
/**
|
|
20
|
+
* Store replicated documents in values as row references and persist a changed value as a row diff appended to
|
|
21
|
+
* the record's log (default true). `false` rewrites the whole value on every change (the previous format; kept
|
|
22
|
+
* for measurements and as an escape hatch).
|
|
23
|
+
*/
|
|
24
|
+
persistDiffs?: boolean;
|
|
19
25
|
now?: () => number;
|
|
20
26
|
}
|
|
21
27
|
export declare class OfflineStore implements StorageAdapter {
|
|
@@ -28,11 +34,15 @@ export declare class OfflineStore implements StorageAdapter {
|
|
|
28
34
|
private readonly records;
|
|
29
35
|
/** `${shard}\0${id}` → number of records owning the row. */
|
|
30
36
|
private readonly refs;
|
|
31
|
-
/**
|
|
32
|
-
private readonly
|
|
37
|
+
/** `${shard}\0${id}` → the row as last written / read (unknown after a restart until needed). */
|
|
38
|
+
private readonly rowDoc;
|
|
39
|
+
private readonly rowSig;
|
|
40
|
+
private readonly watches;
|
|
41
|
+
private rerunQueued;
|
|
33
42
|
private meta;
|
|
34
43
|
private chain;
|
|
35
44
|
private opened;
|
|
45
|
+
private stopExternal;
|
|
36
46
|
private readonly now;
|
|
37
47
|
constructor(backend: OfflineBackend, options: OfflineOptions);
|
|
38
48
|
/** Names of the queries that run locally. */
|
|
@@ -41,12 +51,18 @@ export declare class OfflineStore implements StorageAdapter {
|
|
|
41
51
|
identity: LocalIdentity | null;
|
|
42
52
|
}>;
|
|
43
53
|
private open;
|
|
54
|
+
/** A stored record with its log applied. A log that does not apply marks the record broken (dropped on open). */
|
|
55
|
+
private replay;
|
|
44
56
|
private ref;
|
|
45
57
|
private enqueue;
|
|
46
58
|
/** Resolves when every write handed to the store so far is committed. */
|
|
47
59
|
flush(): Promise<void>;
|
|
48
60
|
applyPatch(u: QueryUpdate): Promise<void>;
|
|
49
|
-
|
|
61
|
+
/** Reads rows by id from the backend (async readers: fetch and retry until nothing is missing). */
|
|
62
|
+
private readRows;
|
|
63
|
+
/** The full value of a record (resolving its row references from the replica). */
|
|
64
|
+
private valueOf;
|
|
65
|
+
private know;
|
|
50
66
|
private apply;
|
|
51
67
|
/** Over `maxQueries`: drop the least recently refreshed records (never the one just written). */
|
|
52
68
|
private evict;
|
|
@@ -56,8 +72,22 @@ export declare class OfflineStore implements StorageAdapter {
|
|
|
56
72
|
*/
|
|
57
73
|
private dropRecords;
|
|
58
74
|
runQueryLocally(req: LocalQueryRequest): Promise<LocalQueryResult | undefined>;
|
|
75
|
+
/** The local result of a request (registered: run over the replica; else / on failure: the cached value). */
|
|
76
|
+
private evaluate;
|
|
59
77
|
/** Runs a registered query over a shard's replica (re-running after async fetches until nothing is missing). */
|
|
60
78
|
private runLocal;
|
|
79
|
+
/**
|
|
80
|
+
* The local result of `req` now, and again whenever it may have changed: a registered query re-runs when rows
|
|
81
|
+
* inside its read set change; any request re-reads when the cached record of its args changes. Results equal to
|
|
82
|
+
* the last one sent are not re-sent.
|
|
83
|
+
*/
|
|
84
|
+
watchQueryLocally(req: LocalQueryRequest, onResult: (r: LocalQueryResult | undefined) => void, onError?: (e: Error) => void): () => void;
|
|
85
|
+
private schedule;
|
|
86
|
+
private rerun;
|
|
87
|
+
/** Rows of `shard` changed (and these records were rewritten): mark the watches they may affect. */
|
|
88
|
+
private notify;
|
|
89
|
+
/** Someone else wrote rows (a shared TinyBase store): what we knew about them is stale; re-run what reads them. */
|
|
90
|
+
private onExternal;
|
|
61
91
|
setIdentity(identity: LocalIdentity | null): Promise<void>;
|
|
62
92
|
/** Forget every cached result and replicated row (e.g. on sign-out on a shared device). Pending mutations stay. */
|
|
63
93
|
clear(): Promise<void>;
|