@factoidal/core 0.7.1 → 0.11.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/CHANGELOG.md +616 -1
- package/NOTICE +2 -21
- package/README.md +123 -993
- package/api.d.ts +53 -0
- package/api.js +5 -0
- package/api.mjs +56 -0
- package/bin/engine.mjs +38 -19
- package/bin/ext.mjs +16 -20
- package/bin/factoidal.mjs +667 -118
- package/bin/pack.mjs +37 -0
- package/bin/serve-worker.mjs +32 -0
- package/bin/serve.mjs +423 -0
- package/bin/store-worker-host.mjs +1 -1
- package/bin/store.mjs +1 -1
- package/browser.d.ts +114 -507
- package/browser.js +155 -2257
- package/docs/fuseki-replacement.md +108 -0
- package/fn.d.ts +25 -96
- package/fn.js +131 -240
- package/index.d.ts +620 -260
- package/index.js +4 -234
- package/index.mjs +180 -84
- package/l4-assets/l4factoidal.js +47 -7
- package/l4-assets/l4factoidal.mjs +1 -1
- package/l4-assets/l4factoidal.wasm +0 -0
- package/l4-assets/version.json +11 -7
- package/lib/api.mjs +2337 -0
- package/lib/browser-engine.mjs +69 -0
- package/lib/dataset.mjs +471 -0
- package/lib/delta-log-hosts.mjs +191 -0
- package/lib/lean-entry.mjs +213 -0
- package/lib/pure.mjs +98 -0
- package/lib/store.mjs +115 -0
- package/lib/version.mjs +5 -0
- package/lws/README.md +137 -0
- package/lws/persist.mjs +78 -0
- package/lws/server.mjs +416 -0
- package/package.json +59 -61
- package/rdfjs.js +5 -536
- package/rdfjs.mjs +517 -0
- package/sample-store.d.ts +8 -16
- package/sample-store.mjs +39 -29
- package/solid/client/README.md +100 -0
- package/solid/client/index.mjs +293 -0
- package/solid/server/README.md +96 -0
- package/solid/server/index.mjs +395 -0
- package/store-host/deno.mjs +8 -0
- package/store-host/index.mjs +12 -0
- package/store-host/node.mjs +8 -0
- package/xmpp/README.md +107 -0
- package/xmpp/index.mjs +229 -0
- package/browser-wasm.js +0 -872
- package/factoidal-npm-entry.js +0 -15477
- package/factoidal-npm-entry.wasm.assets/code-5a7fc68f2ab1323718b8.wasm +0 -0
- package/factoidal-npm-entry.wasm.js +0 -455
- package/factoidal.js +0 -13118
- package/factoidal.wasm.assets/code-bbe6099bfb5b10c4c3ab.wasm +0 -0
- package/factoidal.wasm.js +0 -457
- package/hacl-init.js +0 -92
- package/hacl-wasm/FStar.wasm +0 -0
- package/hacl-wasm/Hacl_Bignum.wasm +0 -0
- package/hacl-wasm/Hacl_Bignum25519_51.wasm +0 -0
- package/hacl-wasm/Hacl_Bignum_Base.wasm +0 -0
- package/hacl-wasm/Hacl_Curve25519_51.wasm +0 -0
- package/hacl-wasm/Hacl_Ed25519.wasm +0 -0
- package/hacl-wasm/Hacl_Ed25519_PrecompTable.wasm +0 -0
- package/hacl-wasm/Hacl_Hash_Base.wasm +0 -0
- package/hacl-wasm/Hacl_Hash_SHA2.wasm +0 -0
- package/hacl-wasm/Hacl_IntTypes_Intrinsics.wasm +0 -0
- package/hacl-wasm/LowStar_Endianness.wasm +0 -0
- package/hacl-wasm/WasmSupport.wasm +0 -0
- package/hacl-wasm/api.js +0 -775
- package/hacl-wasm/api.json +0 -3787
- package/hacl-wasm/layouts.json +0 -1
- package/hacl-wasm/loader.js +0 -568
- package/hacl-wasm/shell.js +0 -12
- package/l4-core.d.ts +0 -159
- package/l4-core.js +0 -188
- package/l4.d.ts +0 -50
- package/l4.js +0 -104
- package/lib/api.js +0 -2300
- package/lib/engine-js.js +0 -165
- package/lib/engine-wasm.js +0 -300
- package/sample-store/CURRENT +0 -1
- package/sample-store/gen-1/manifest.sbm2 +0 -0
- package/sample-store/gen-1/manifest.tsv +0 -14
- package/sample-store/gen-1/predicate-0.ibk3 +0 -0
- package/sample-store/gen-1/predicate-0.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-0.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-0.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-0.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-0.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-0.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-0.ibk3.tli1.merkle +0 -0
- package/sample-store/gen-1/predicate-1.ibk3 +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.merkle +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.oli2.merkle +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-1.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-10.ibk3 +0 -0
- package/sample-store/gen-1/predicate-10.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-10.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-10.ibk3.oli2.merkle +0 -0
- package/sample-store/gen-1/predicate-10.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-10.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-10.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-10.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-11.ibk3 +0 -0
- package/sample-store/gen-1/predicate-11.ibk3.merkle +0 -0
- package/sample-store/gen-1/predicate-11.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-11.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-11.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-11.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-11.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-11.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-12.ibk3 +0 -0
- package/sample-store/gen-1/predicate-12.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-12.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-12.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-12.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-12.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-12.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-12.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-2.ibk3 +0 -0
- package/sample-store/gen-1/predicate-2.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-2.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-2.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-2.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-2.ibk3.sri2.merkle +0 -0
- package/sample-store/gen-1/predicate-2.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-2.ibk3.tli1.merkle +0 -0
- package/sample-store/gen-1/predicate-3.ibk3 +0 -0
- package/sample-store/gen-1/predicate-3.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-3.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-3.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-3.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-3.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-3.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-3.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-4.ibk3 +0 -0
- package/sample-store/gen-1/predicate-4.ibk3.merkle +0 -0
- package/sample-store/gen-1/predicate-4.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-4.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-4.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-4.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-4.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-4.ibk3.tli1.merkle +0 -2
- package/sample-store/gen-1/predicate-5.ibk3 +0 -0
- package/sample-store/gen-1/predicate-5.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-5.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-5.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-5.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-5.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-5.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-5.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-6.ibk3 +0 -0
- package/sample-store/gen-1/predicate-6.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-6.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-6.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-6.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-6.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-6.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-6.ibk3.tli1.merkle +0 -0
- package/sample-store/gen-1/predicate-7.ibk3 +0 -0
- package/sample-store/gen-1/predicate-7.ibk3.merkle +0 -0
- package/sample-store/gen-1/predicate-7.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-7.ibk3.oli2.merkle +0 -2
- package/sample-store/gen-1/predicate-7.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-7.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-7.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-7.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-8.ibk3 +0 -0
- package/sample-store/gen-1/predicate-8.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-8.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-8.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-8.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-8.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-8.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-8.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-9.ibk3 +0 -0
- package/sample-store/gen-1/predicate-9.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-9.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-9.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-9.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-9.ibk3.sri2.merkle +0 -3
- package/sample-store/gen-1/predicate-9.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-9.ibk3.tli1.merkle +0 -2
- package/select.d.ts +0 -116
- package/select.js +0 -492
- package/version.json +0 -102
- package/wasm.d.ts +0 -130
- package/wasm.js +0 -158
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
// Host stores for the durable update log (deltaLog* in lib/api.mjs; hub
|
|
2
|
+
// post 18; https://github.com/danbri/factoidal/issues/728).
|
|
3
|
+
//
|
|
4
|
+
// A host store keeps, for each log name, the records in commit order. A
|
|
5
|
+
// record is { seq, epoch, hex }: `hex` is the hexadecimal text of one DLB1
|
|
6
|
+
// record as the engine wrote it (op deltaLogEncode,
|
|
7
|
+
// formal/lean4/Wasm/Ops/DeltaLog.lean). The store does not read the hex.
|
|
8
|
+
// The byte format, the checksums, the torn-record rule and the merge are in
|
|
9
|
+
// Lean (formal/lean4/L4Factoidal/Storage/BrowserLog.lean); a host store
|
|
10
|
+
// only stores and loads text.
|
|
11
|
+
//
|
|
12
|
+
// open(name) create the log if it is absent
|
|
13
|
+
// append(name, makeRecord) seq := the number of records; makeRecord(seq)
|
|
14
|
+
// (synchronous) gives {hex, epoch}; the record
|
|
15
|
+
// is committed with that seq, atomically
|
|
16
|
+
// readAll(name) the hex texts, in seq order
|
|
17
|
+
// destroy(name) remove the log
|
|
18
|
+
// corruptLast(name) TEST HOOK: cut the last 8 hex digits (4
|
|
19
|
+
// bytes) of the last record, as a torn write
|
|
20
|
+
// would; false when the log is empty
|
|
21
|
+
//
|
|
22
|
+
// No Node built-ins: the browser imports this file as it is.
|
|
23
|
+
|
|
24
|
+
const DELTA_STORE = 'deltaBatches';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The records in the memory of this process. The default store of the Node
|
|
28
|
+
* entry: the records live as long as the process. Pass another store to
|
|
29
|
+
* buildApi() (driver.deltaLogStore) to keep them elsewhere.
|
|
30
|
+
*/
|
|
31
|
+
export function memoryDeltaLogStore() {
|
|
32
|
+
const logs = new Map();
|
|
33
|
+
const recordsOf = (name) => {
|
|
34
|
+
if (!logs.has(name)) logs.set(name, []);
|
|
35
|
+
return logs.get(name);
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
kind: 'memory',
|
|
39
|
+
async open(name) {
|
|
40
|
+
recordsOf(name);
|
|
41
|
+
},
|
|
42
|
+
async append(name, makeRecord) {
|
|
43
|
+
const records = recordsOf(name);
|
|
44
|
+
const seq = records.length;
|
|
45
|
+
const rec = makeRecord(seq);
|
|
46
|
+
records.push({ seq, epoch: rec.epoch, hex: rec.hex });
|
|
47
|
+
return { seq };
|
|
48
|
+
},
|
|
49
|
+
async readAll(name) {
|
|
50
|
+
return (logs.get(name) || []).map((r) => r.hex);
|
|
51
|
+
},
|
|
52
|
+
async destroy(name) {
|
|
53
|
+
logs.delete(name);
|
|
54
|
+
},
|
|
55
|
+
async corruptLast(name) {
|
|
56
|
+
const records = logs.get(name);
|
|
57
|
+
if (!records || records.length === 0) return false;
|
|
58
|
+
const last = records[records.length - 1];
|
|
59
|
+
last.hex = last.hex.slice(0, Math.max(0, last.hex.length - 8));
|
|
60
|
+
return true;
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function indexedDbFactory() {
|
|
66
|
+
const factory = globalThis.indexedDB;
|
|
67
|
+
if (!factory) {
|
|
68
|
+
throw new Error('deltaLog: this runtime has no indexedDB; the browser entry keeps the log in IndexedDB');
|
|
69
|
+
}
|
|
70
|
+
return factory;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function idbOpen(dbName) {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
const req = indexedDbFactory().open(dbName, 1);
|
|
76
|
+
req.onupgradeneeded = () => {
|
|
77
|
+
const db = req.result;
|
|
78
|
+
if (!db.objectStoreNames.contains(DELTA_STORE)) {
|
|
79
|
+
db.createObjectStore(DELTA_STORE, { keyPath: 'seq' });
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
req.onsuccess = () => resolve(req.result);
|
|
83
|
+
req.onerror = () => reject(req.error || new Error('indexedDB.open failed'));
|
|
84
|
+
req.onblocked = () => reject(new Error('indexedDB.open blocked (another tab holds an open connection at an older version)'));
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function requestDone(req) {
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
req.onsuccess = () => resolve(req.result);
|
|
91
|
+
req.onerror = () => reject(req.error || new Error('IndexedDB request failed'));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function transactionDone(tx) {
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
tx.oncomplete = () => resolve();
|
|
98
|
+
tx.onerror = () => reject(tx.error || new Error('IndexedDB transaction failed'));
|
|
99
|
+
tx.onabort = () => reject(tx.error || new Error('IndexedDB transaction aborted'));
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The records in IndexedDB: one database for each log name, one object
|
|
105
|
+
* store 'deltaBatches' keyed by seq, one object { seq, epoch, hex } for each
|
|
106
|
+
* committed update. The default store of the browser entries.
|
|
107
|
+
*
|
|
108
|
+
* append() counts the records, calls makeRecord and adds the record in ONE
|
|
109
|
+
* readwrite transaction. IndexedDB runs the readwrite transactions of one
|
|
110
|
+
* object store one after another, so two tabs that append at the same time
|
|
111
|
+
* get different seq values; `add` (not `put`) refuses a seq that exists. The
|
|
112
|
+
* transaction asks for `durability: 'strict'`: Chrome 121 and later default
|
|
113
|
+
* to 'relaxed', where `complete` can fire before the data is on disk.
|
|
114
|
+
*/
|
|
115
|
+
export function indexedDbDeltaLogStore() {
|
|
116
|
+
return {
|
|
117
|
+
kind: 'indexedDB',
|
|
118
|
+
async open(name) {
|
|
119
|
+
const db = await idbOpen(name);
|
|
120
|
+
db.close();
|
|
121
|
+
},
|
|
122
|
+
async append(name, makeRecord) {
|
|
123
|
+
const db = await idbOpen(name);
|
|
124
|
+
try {
|
|
125
|
+
const tx = db.transaction(DELTA_STORE, 'readwrite', { durability: 'strict' });
|
|
126
|
+
const done = transactionDone(tx);
|
|
127
|
+
const store = tx.objectStore(DELTA_STORE);
|
|
128
|
+
let seq;
|
|
129
|
+
let failure = null;
|
|
130
|
+
const countReq = store.count();
|
|
131
|
+
countReq.onsuccess = () => {
|
|
132
|
+
seq = countReq.result;
|
|
133
|
+
try {
|
|
134
|
+
// Synchronous: the transaction stays active inside this callback.
|
|
135
|
+
const rec = makeRecord(seq);
|
|
136
|
+
store.add({ seq, epoch: rec.epoch, hex: rec.hex });
|
|
137
|
+
} catch (err) {
|
|
138
|
+
failure = err;
|
|
139
|
+
tx.abort();
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
try {
|
|
143
|
+
await done;
|
|
144
|
+
} catch (err) {
|
|
145
|
+
throw failure || err;
|
|
146
|
+
}
|
|
147
|
+
return { seq };
|
|
148
|
+
} finally {
|
|
149
|
+
db.close();
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
async readAll(name) {
|
|
153
|
+
const db = await idbOpen(name);
|
|
154
|
+
try {
|
|
155
|
+
const all = await requestDone(
|
|
156
|
+
db.transaction(DELTA_STORE, 'readonly').objectStore(DELTA_STORE).getAll());
|
|
157
|
+
all.sort((a, b) => a.seq - b.seq);
|
|
158
|
+
return all.map((r) => r.hex);
|
|
159
|
+
} finally {
|
|
160
|
+
db.close();
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
async destroy(name) {
|
|
164
|
+
await new Promise((resolve, reject) => {
|
|
165
|
+
const req = indexedDbFactory().deleteDatabase(name);
|
|
166
|
+
req.onsuccess = () => resolve();
|
|
167
|
+
req.onerror = () => reject(req.error || new Error('indexedDB.deleteDatabase failed'));
|
|
168
|
+
req.onblocked = () => reject(new Error('indexedDB.deleteDatabase blocked (another open connection)'));
|
|
169
|
+
});
|
|
170
|
+
},
|
|
171
|
+
async corruptLast(name) {
|
|
172
|
+
const db = await idbOpen(name);
|
|
173
|
+
try {
|
|
174
|
+
const all = await requestDone(
|
|
175
|
+
db.transaction(DELTA_STORE, 'readonly').objectStore(DELTA_STORE).getAll());
|
|
176
|
+
if (all.length === 0) return false;
|
|
177
|
+
all.sort((a, b) => a.seq - b.seq);
|
|
178
|
+
const last = all[all.length - 1];
|
|
179
|
+
const tx = db.transaction(DELTA_STORE, 'readwrite');
|
|
180
|
+
const done = transactionDone(tx);
|
|
181
|
+
tx.objectStore(DELTA_STORE).put({
|
|
182
|
+
seq: last.seq, epoch: last.epoch, hex: last.hex.slice(0, Math.max(0, last.hex.length - 8)),
|
|
183
|
+
});
|
|
184
|
+
await done;
|
|
185
|
+
return true;
|
|
186
|
+
} finally {
|
|
187
|
+
db.close();
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
// The entry object over a loaded Lean engine handle.
|
|
2
|
+
//
|
|
3
|
+
// A Lean engine handle is what `loadL4()` of l4-assets/l4factoidal.js
|
|
4
|
+
// answers: `call(op, args)`, `callBlob(op, args, bytes)`,
|
|
5
|
+
// `callBlobIO(op, args, bytes)` over the dispatch ABI of
|
|
6
|
+
// formal/lean4/Wasm/Dispatch.lean. Those methods parse the envelope and
|
|
7
|
+
// THROW on {"ok":false,...}. lib/api.mjs wants the other shape: one method
|
|
8
|
+
// per op, string arguments, the envelope as a JSON string, errors as
|
|
9
|
+
// {"ok":false} envelopes. entryFromEngine() builds that object.
|
|
10
|
+
//
|
|
11
|
+
// Which ops exist is the engine's own answer: the `ops` reflection op
|
|
12
|
+
// lists them, and the entry object has a method for each name in that list
|
|
13
|
+
// and for no other. capabilities() in lib/api.mjs probes the methods, so a
|
|
14
|
+
// wasm module older than this package reports what it serves.
|
|
15
|
+
//
|
|
16
|
+
// Four ops need more than strings in and an envelope out, and get their
|
|
17
|
+
// own adapters below:
|
|
18
|
+
// - queryHdt and storeOpen read a byte region (the dispatch ABI's blob);
|
|
19
|
+
// - registerExtensionFunction / unregisterExtensionFunction /
|
|
20
|
+
// clearExtensionFunctions install the JavaScript call-out of SPARQL 1.1
|
|
21
|
+
// section 17.6 (the ops extRegister, extUnregister, extClear).
|
|
22
|
+
// None of them makes a semantic decision: which IRIs reach the host, what
|
|
23
|
+
// an absent answer means, which artifact a query reads, are decided in Lean.
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
// The two npm entries and the engine each loads (issue 713 stage 4). The
|
|
27
|
+
// core engine's `ops` reflection lists what only the full engine serves
|
|
28
|
+
// (`fullOnlyOps`, `fullOnlyFormats`); a call to one of those names the
|
|
29
|
+
// entry that has it.
|
|
30
|
+
import { appTermToJson, termToJson } from '../rdfjs.mjs';
|
|
31
|
+
export const FULL_ENTRY = '@factoidal/core';
|
|
32
|
+
// The core profile is built for the hub pages only; the package ships the
|
|
33
|
+
// full engine (owner, 2026-10-20).
|
|
34
|
+
export const CORE_ENTRY = 'l4factoidal-core.js (the hub build of the core profile)';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The message of a refusal by the core engine of something only the full
|
|
38
|
+
* engine serves.
|
|
39
|
+
* @param {string} what the function, op or format refused
|
|
40
|
+
*/
|
|
41
|
+
export function fullEngineMessage(what) {
|
|
42
|
+
return `${what} is not in the core engine (${CORE_ENTRY}, ` +
|
|
43
|
+
`l4factoidal-core.wasm); import '${FULL_ENTRY}', whose full engine ` +
|
|
44
|
+
'(l4factoidal.wasm) serves it';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// The ops whose second argument is a format tag of a document they read.
|
|
48
|
+
const DOCUMENT_FORMAT_OPS = new Set(['parseToDatasetJson', 'datasetOpen', 'parseDocument']);
|
|
49
|
+
const EXT_PENDING_MARKER = '__FACTOIDAL_EXT_PENDING__';
|
|
50
|
+
|
|
51
|
+
function okEnvelope(extra) {
|
|
52
|
+
return JSON.stringify({ ok: true, ...(extra || {}) });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// An engine refusal arrives as an Error whose `.envelope` is the whole
|
|
56
|
+
// {"ok":false,...} document (the loader keeps it, so a parse failure's
|
|
57
|
+
// line, column, offset and format reach lib/api.mjs's ParseError). The
|
|
58
|
+
// message stays the loader's own, "l4factoidal: <error>".
|
|
59
|
+
function failEnvelope(err) {
|
|
60
|
+
const env = err && err.envelope;
|
|
61
|
+
if (env && typeof env === 'object' && env.ok === false) {
|
|
62
|
+
return JSON.stringify({ ...env, error: String(err.message) });
|
|
63
|
+
}
|
|
64
|
+
return JSON.stringify({ ok: false, error: String((err && err.message) || err) });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// One answer for Wasm/Ops/ExtFns.lean: the JavaScript value tagged with its
|
|
68
|
+
// JavaScript type ({js, v}; a number as its Number::toString text), or a term
|
|
69
|
+
// object. The engine chooses the datatype and the lexical form
|
|
70
|
+
// (L4Factoidal.XSD.JsValue.jsLiteral, issue 894 item 3). '' means "no
|
|
71
|
+
// value" (the section 17.6 error).
|
|
72
|
+
function encodeExtAnswer(out) {
|
|
73
|
+
if (out === null || out === undefined || out === EXT_PENDING_MARKER) return '';
|
|
74
|
+
switch (typeof out) {
|
|
75
|
+
case 'boolean': return JSON.stringify({ js: 'boolean', v: out });
|
|
76
|
+
case 'number': return JSON.stringify({ js: 'number', v: Object.is(out, -0) ? '-0' : String(out) });
|
|
77
|
+
case 'bigint': return JSON.stringify({ js: 'bigint', v: out.toString() });
|
|
78
|
+
case 'string': return JSON.stringify({ js: 'string', v: out });
|
|
79
|
+
case 'object':
|
|
80
|
+
try { return JSON.stringify(appTermToJson(out)); } catch (_error) { return ''; }
|
|
81
|
+
default: return '';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// The Lean module asks its host through ONE global,
|
|
86
|
+
// `globalThis.__factoidalExtCall(iri, argsJson)` (Wasm/ExtHost.lean). The
|
|
87
|
+
// bridges lib/api.mjs registers are keyed by IRI here.
|
|
88
|
+
function installExtensionBridge(eng, entry) {
|
|
89
|
+
const bridges = new Map();
|
|
90
|
+
const hostCall = (iri, argsJson) => {
|
|
91
|
+
const bridge = bridges.get(String(iri));
|
|
92
|
+
if (!bridge) return '';
|
|
93
|
+
try {
|
|
94
|
+
return encodeExtAnswer(bridge(String(iri), String(argsJson)));
|
|
95
|
+
} catch (_err) {
|
|
96
|
+
return '';
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
entry.registerExtensionFunction = (iri, bridge) => {
|
|
100
|
+
try {
|
|
101
|
+
eng.call('extRegister', [String(iri)]);
|
|
102
|
+
bridges.set(String(iri), bridge);
|
|
103
|
+
globalThis.__factoidalExtCall = hostCall;
|
|
104
|
+
return okEnvelope();
|
|
105
|
+
} catch (err) {
|
|
106
|
+
return failEnvelope(err);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
entry.unregisterExtensionFunction = (iri) => {
|
|
110
|
+
try {
|
|
111
|
+
eng.call('extUnregister', [String(iri)]);
|
|
112
|
+
bridges.delete(String(iri));
|
|
113
|
+
return okEnvelope();
|
|
114
|
+
} catch (err) {
|
|
115
|
+
return failEnvelope(err);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
entry.clearExtensionFunctions = () => {
|
|
119
|
+
try {
|
|
120
|
+
eng.call('extClear', []);
|
|
121
|
+
bridges.clear();
|
|
122
|
+
return okEnvelope();
|
|
123
|
+
} catch (err) {
|
|
124
|
+
return failEnvelope(err);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Build the entry object lib/api.mjs drives, from a loaded engine handle.
|
|
131
|
+
*
|
|
132
|
+
* @param {{call: Function, callBlob?: Function, callBlobIO?: Function}} eng
|
|
133
|
+
* @returns {Record<string, Function> & {abiVersion: string, engine: object}}
|
|
134
|
+
*/
|
|
135
|
+
export function entryFromEngine(eng) {
|
|
136
|
+
if (!eng || typeof eng.call !== 'function') {
|
|
137
|
+
throw new TypeError(
|
|
138
|
+
'factoidal: expected a Lean engine handle with a call(op, args) method ' +
|
|
139
|
+
'(the value loadL4() of l4-assets/l4factoidal.js resolves to)');
|
|
140
|
+
}
|
|
141
|
+
const reflection = eng.call('ops', []);
|
|
142
|
+
// `engine` is "core" for l4factoidal-core.wasm and "full" for
|
|
143
|
+
// l4factoidal.wasm; a module older than issue 713 stage 4 has no member and
|
|
144
|
+
// is the full engine.
|
|
145
|
+
const profile = reflection.engine === 'core' ? 'core' : 'full';
|
|
146
|
+
const entry = {
|
|
147
|
+
abiVersion: String(reflection.abiVersion),
|
|
148
|
+
engine: eng,
|
|
149
|
+
profile,
|
|
150
|
+
fullOnlyOps: Array.isArray(reflection.fullOnlyOps) ? reflection.fullOnlyOps.map(String) : [],
|
|
151
|
+
fullOnlyFormats: Array.isArray(reflection.fullOnlyFormats)
|
|
152
|
+
? reflection.fullOnlyFormats.map((f) => String(f).toLowerCase()) : [],
|
|
153
|
+
};
|
|
154
|
+
const ops = Array.isArray(reflection.ops) ? reflection.ops : [];
|
|
155
|
+
for (const op of ops) {
|
|
156
|
+
if (op === 'ops') continue;
|
|
157
|
+
const formatChecked = DOCUMENT_FORMAT_OPS.has(op) && entry.fullOnlyFormats.length > 0;
|
|
158
|
+
entry[op] = (...args) => {
|
|
159
|
+
if (formatChecked && entry.fullOnlyFormats.includes(String(args[1] ?? '').toLowerCase())) {
|
|
160
|
+
return JSON.stringify({
|
|
161
|
+
ok: false, servedBy: 'full',
|
|
162
|
+
error: fullEngineMessage(`the format '${args[1]}'`),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
return JSON.stringify(eng.call(op, args.map(String)));
|
|
167
|
+
} catch (err) {
|
|
168
|
+
return failEnvelope(err);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
if (ops.includes('extRegister')) installExtensionBridge(eng, entry);
|
|
173
|
+
// The byte-region ops. `bytes` is a Uint8Array; it crosses into the wasm
|
|
174
|
+
// heap with no encoding.
|
|
175
|
+
// queryHdt goes through the IO form of the blob entry when the engine has it:
|
|
176
|
+
// that entry reads the system clock once for the query, so NOW() (SPARQL 1.1
|
|
177
|
+
// section 17.4.5.1) is bound. The pure `callBlob` has no clock. An engine
|
|
178
|
+
// without `callBlobIO` serves the op through `callBlob`, as before
|
|
179
|
+
// (https://github.com/danbri/factoidal/issues/743).
|
|
180
|
+
if ((typeof eng.callBlobIO === 'function' || typeof eng.callBlob === 'function') &&
|
|
181
|
+
ops.includes('queryHdt')) {
|
|
182
|
+
entry.queryHdt = (bytes, sparql) => {
|
|
183
|
+
try {
|
|
184
|
+
const args = [String(sparql)];
|
|
185
|
+
return JSON.stringify(typeof eng.callBlobIO === 'function'
|
|
186
|
+
? eng.callBlobIO('queryHdt', args, bytes).envelope
|
|
187
|
+
: eng.callBlob('queryHdt', args, bytes));
|
|
188
|
+
} catch (err) {
|
|
189
|
+
return failEnvelope(err);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (typeof eng.callBlobIO === 'function' && ops.includes('storeOpen')) {
|
|
194
|
+
entry.storeOpen = (manifestHex, artifactsJson, bytes) => {
|
|
195
|
+
try {
|
|
196
|
+
return JSON.stringify(
|
|
197
|
+
eng.callBlobIO('storeOpen', [String(manifestHex), String(artifactsJson)], bytes).envelope);
|
|
198
|
+
} catch (err) {
|
|
199
|
+
return failEnvelope(err);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
// datasetQuadsBin answers a byte region (formal/lean4/L4Factoidal/RDF/QuadsBin.lean):
|
|
204
|
+
// this method answers {envelope, bytes} and throws on a refusal.
|
|
205
|
+
const blobIoOps = Array.isArray(reflection.blobIoOps) ? reflection.blobIoOps : [];
|
|
206
|
+
if (typeof eng.callBlobIO === 'function' && blobIoOps.includes('datasetQuadsBin')) {
|
|
207
|
+
entry.datasetQuadsBin = (handle, optsJson) =>
|
|
208
|
+
eng.callBlobIO('datasetQuadsBin', [String(handle), String(optsJson ?? '')], new Uint8Array(0));
|
|
209
|
+
} else {
|
|
210
|
+
delete entry.datasetQuadsBin;
|
|
211
|
+
}
|
|
212
|
+
return entry;
|
|
213
|
+
}
|
package/lib/pure.mjs
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Pure functions over engine ops that had no JavaScript function before
|
|
2
|
+
// 0.10.0. Each one takes values and returns a value: no handle, no hidden
|
|
3
|
+
// state. Every decision is made by the Lean op it calls
|
|
4
|
+
// (formal/lean4/Wasm/Ops/Crypto.lean, Wasm/Ops/Proof.lean,
|
|
5
|
+
// Wasm/CoreDispatch.lean). This file converts bytes to hex and back.
|
|
6
|
+
// No Node built-ins: the browser imports it as it is.
|
|
7
|
+
|
|
8
|
+
function hexOf(bytes) {
|
|
9
|
+
if (!(bytes instanceof Uint8Array)) throw new TypeError('expected a Uint8Array');
|
|
10
|
+
let s = '';
|
|
11
|
+
for (const b of bytes) s += b.toString(16).padStart(2, '0');
|
|
12
|
+
return s;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function bytesOf(hex) {
|
|
16
|
+
const out = new Uint8Array(hex.length / 2);
|
|
17
|
+
for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.substr(2 * i, 2), 16);
|
|
18
|
+
return out;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function optHex(v) {
|
|
22
|
+
return v === undefined ? undefined : hexOf(v);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function str(name, v) {
|
|
26
|
+
if (typeof v !== 'string') throw new TypeError(`${name}: argument must be a string`);
|
|
27
|
+
return v;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {() => Promise<object>} loadEngine resolves to an engine handle
|
|
32
|
+
* with `call(op, args)` (bin/engine.mjs, lib/browser-engine.mjs).
|
|
33
|
+
*/
|
|
34
|
+
export function buildPure(loadEngine) {
|
|
35
|
+
const call = async (op, args) => (await loadEngine()).call(op, args);
|
|
36
|
+
|
|
37
|
+
/** ChaCha20-Poly1305 seal (RFC 8439). key 32 bytes, nonce 12 bytes. */
|
|
38
|
+
async function aeadSeal({ key, nonce, plaintext, aad }) {
|
|
39
|
+
const r = await call('aeadSeal', [JSON.stringify({
|
|
40
|
+
alg: 'chacha20-poly1305', key: hexOf(key), nonce: hexOf(nonce),
|
|
41
|
+
aad: optHex(aad), plaintext: optHex(plaintext),
|
|
42
|
+
})]);
|
|
43
|
+
return bytesOf(r.ciphertext);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** ChaCha20-Poly1305 open. Rejects when the tag does not authenticate. */
|
|
47
|
+
async function aeadOpen({ key, nonce, ciphertext, aad }) {
|
|
48
|
+
const r = await call('aeadOpen', [JSON.stringify({
|
|
49
|
+
alg: 'chacha20-poly1305', key: hexOf(key), nonce: hexOf(nonce),
|
|
50
|
+
aad: optHex(aad), ciphertext: hexOf(ciphertext),
|
|
51
|
+
})]);
|
|
52
|
+
return bytesOf(r.plaintext);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** HPKE base mode seal, RFC 9180 DHKEM(X25519)/HKDF-SHA256/ChaCha20-Poly1305. */
|
|
56
|
+
async function hpkeSeal({ skE, pkR, plaintext, info, aad }) {
|
|
57
|
+
const r = await call('hpkeSeal', [JSON.stringify({
|
|
58
|
+
skE: hexOf(skE), pkR: hexOf(pkR), info: optHex(info),
|
|
59
|
+
aad: optHex(aad), plaintext: optHex(plaintext),
|
|
60
|
+
})]);
|
|
61
|
+
return { message: bytesOf(r.message), enc: bytesOf(r.enc), ciphertext: bytesOf(r.ciphertext) };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** HPKE base mode open over the `message` that hpkeSeal returned. */
|
|
65
|
+
async function hpkeOpen({ skR, message, info, aad }) {
|
|
66
|
+
const r = await call('hpkeOpen', [JSON.stringify({
|
|
67
|
+
skR: hexOf(skR), info: optHex(info), aad: optHex(aad), message: hexOf(message),
|
|
68
|
+
})]);
|
|
69
|
+
return bytesOf(r.plaintext);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Check an FPP0 proof bundle (JSON text). Returns the engine's report. */
|
|
73
|
+
async function proofCheck(bundleJson) {
|
|
74
|
+
const { ok, ...rest } = await call('proofCheck', [str('proofCheck', bundleJson)]);
|
|
75
|
+
return rest;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Describe the shape of an FPP0 proof bundle without checking it. */
|
|
79
|
+
async function proofInspect(bundleJson) {
|
|
80
|
+
const { ok, ...rest } = await call('proofInspect', [str('proofInspect', bundleJson)]);
|
|
81
|
+
return rest;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** RDFC-1.0 over N-Quads text, N-Quads text out. */
|
|
85
|
+
async function canonicalizeNQuads(nquads) {
|
|
86
|
+
return (await call('canonicalizeToNQuads', [str('canonicalizeNQuads', nquads)])).nquads;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Parse N-Quads (or N-Triples) text and write it back as N-Quads. */
|
|
90
|
+
async function normalizeNQuads(nquads) {
|
|
91
|
+
return (await call('serializeNQuads', [str('normalizeNQuads', nquads)])).nquads;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
aeadSeal, aeadOpen, hpkeSeal, hpkeOpen,
|
|
96
|
+
proofCheck, proofInspect, canonicalizeNQuads, normalizeNQuads,
|
|
97
|
+
};
|
|
98
|
+
}
|
package/lib/store.mjs
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// Host plumbing for the Lean persisted store (Shardborough IBK3/IBK4/IBK5
|
|
2
|
+
// generations), shared by Node and the browser.
|
|
3
|
+
//
|
|
4
|
+
// A store is read through a READER: a function from a path relative to the
|
|
5
|
+
// collection root ('CURRENT', 'gen-1/manifest.sbm2',
|
|
6
|
+
// 'gen-1/predicate-0.ibk3', ...) to the bytes of that file (a Uint8Array or
|
|
7
|
+
// an ArrayBuffer, or a Promise of either). index.mjs builds one over the
|
|
8
|
+
// file system; a browser page builds one over fetch().
|
|
9
|
+
//
|
|
10
|
+
// WHAT THIS FILE IS ALLOWED TO DO
|
|
11
|
+
// Read CURRENT, read a manifest by name, read the artifact files the
|
|
12
|
+
// engine named, concatenate their bytes and say where each one starts.
|
|
13
|
+
// It never parses a manifest, verifies a digest, decodes a block or
|
|
14
|
+
// chooses an artifact: the ops storeManifestInspect, storeQueryPlan and
|
|
15
|
+
// storeOpen (formal/lean4/Wasm/Ops/Store.lean, StoreHandles.lean) decide
|
|
16
|
+
// all of that. bin/store.mjs is the same plumbing over store-host/ for the
|
|
17
|
+
// `factoidal` command.
|
|
18
|
+
|
|
19
|
+
import { requireChildName } from '../store-host/paths.mjs';
|
|
20
|
+
|
|
21
|
+
/** The manifest file names a generation directory can carry, in the order
|
|
22
|
+
* the native tools try them. */
|
|
23
|
+
export const MANIFEST_NAMES = ['manifest.sbm2', 'manifest.sbm1'];
|
|
24
|
+
|
|
25
|
+
async function readBytes(reader, path) {
|
|
26
|
+
const out = await reader(path);
|
|
27
|
+
if (out instanceof Uint8Array) return out;
|
|
28
|
+
if (out instanceof ArrayBuffer) return new Uint8Array(out);
|
|
29
|
+
if (ArrayBuffer.isView(out)) return new Uint8Array(out.buffer, out.byteOffset, out.byteLength);
|
|
30
|
+
throw new TypeError(
|
|
31
|
+
`store reader: '${path}' answered ${out === null ? 'null' : typeof out}, ` +
|
|
32
|
+
'expected a Uint8Array or an ArrayBuffer');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The generation to open and its manifest bytes. With no `generation`,
|
|
37
|
+
* CURRENT names it (an activated collection); with one, that generation is
|
|
38
|
+
* opened directly, activated or not.
|
|
39
|
+
*/
|
|
40
|
+
export async function openGeneration(reader, generation) {
|
|
41
|
+
let name = generation;
|
|
42
|
+
if (name == null) {
|
|
43
|
+
let pointer;
|
|
44
|
+
try {
|
|
45
|
+
pointer = await readBytes(reader, 'CURRENT');
|
|
46
|
+
} catch (cause) {
|
|
47
|
+
const err = new Error(
|
|
48
|
+
`openStore: the collection root has no readable CURRENT pointer (${cause && cause.message})`);
|
|
49
|
+
err.cause = cause;
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
// CURRENT holds a UTF-8 generation name (docs/shardborough-storage-spec.md
|
|
53
|
+
// section 6.4); trailing ASCII whitespace is tolerated, as in
|
|
54
|
+
// store-host/index.mjs.
|
|
55
|
+
name = new TextDecoder('utf-8', { fatal: true }).decode(pointer).replace(/[\r\n\t ]+$/, '');
|
|
56
|
+
requireChildName(name, 'CURRENT');
|
|
57
|
+
} else {
|
|
58
|
+
requireChildName(String(name), 'generation');
|
|
59
|
+
name = String(name);
|
|
60
|
+
}
|
|
61
|
+
for (const manifestName of MANIFEST_NAMES) {
|
|
62
|
+
let manifest;
|
|
63
|
+
try {
|
|
64
|
+
manifest = await readBytes(reader, `${name}/${manifestName}`);
|
|
65
|
+
} catch (_err) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
return { generation: name, manifestName, manifest, activated: generation == null };
|
|
69
|
+
}
|
|
70
|
+
throw new Error(`openStore: generation '${name}' has none of ${MANIFEST_NAMES.join(', ')}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Read the named artifacts of one generation into one region, with a
|
|
74
|
+
* {key, offset, len} window per artifact. */
|
|
75
|
+
export async function readRegion(reader, generation, keys) {
|
|
76
|
+
const chunks = [];
|
|
77
|
+
for (const key of keys) chunks.push(await readBytes(reader, `${generation}/${key}`));
|
|
78
|
+
let total = 0;
|
|
79
|
+
for (const c of chunks) total += c.length;
|
|
80
|
+
const blob = new Uint8Array(total);
|
|
81
|
+
const artifacts = [];
|
|
82
|
+
let offset = 0;
|
|
83
|
+
for (let i = 0; i < chunks.length; i += 1) {
|
|
84
|
+
blob.set(chunks[i], offset);
|
|
85
|
+
artifacts.push({ key: keys[i], offset, len: chunks[i].length });
|
|
86
|
+
offset += chunks[i].length;
|
|
87
|
+
}
|
|
88
|
+
return { blob, artifacts, total };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The artifact keys a manifest declares, as storeManifestInspect lists
|
|
92
|
+
* them: every block, every index sidecar, every out-of-line literal. */
|
|
93
|
+
export function manifestKeys(inspectEnvelope) {
|
|
94
|
+
const keys = [];
|
|
95
|
+
for (const entry of inspectEnvelope.entries || []) {
|
|
96
|
+
keys.push(entry.key);
|
|
97
|
+
for (const key of Object.values(entry.sidecars || {})) keys.push(key);
|
|
98
|
+
}
|
|
99
|
+
for (const blob of inspectEnvelope.blobs || []) keys.push(blob.key);
|
|
100
|
+
return keys;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const HEX = (() => {
|
|
104
|
+
const t = new Array(256);
|
|
105
|
+
for (let b = 0; b < 256; b += 1) t[b] = b.toString(16).padStart(2, '0');
|
|
106
|
+
return t;
|
|
107
|
+
})();
|
|
108
|
+
|
|
109
|
+
/** Lowercase hexadecimal: the transport the manifest argument of the store
|
|
110
|
+
* ops takes. Artifact bytes never take this path. */
|
|
111
|
+
export function hexOfBytes(bytes) {
|
|
112
|
+
const parts = new Array(bytes.length);
|
|
113
|
+
for (let i = 0; i < bytes.length; i += 1) parts[i] = HEX[bytes[i]];
|
|
114
|
+
return parts.join('');
|
|
115
|
+
}
|
package/lib/version.mjs
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// The package version, as a module the browser can import (a JSON import
|
|
2
|
+
// needs import attributes, which not every runtime this package targets
|
|
3
|
+
// reads). test/dts-drift.test.js checks that it equals package.json's
|
|
4
|
+
// `version`.
|
|
5
|
+
export const version = '0.11.0';
|