@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.
Files changed (195) hide show
  1. package/CHANGELOG.md +616 -1
  2. package/NOTICE +2 -21
  3. package/README.md +123 -993
  4. package/api.d.ts +53 -0
  5. package/api.js +5 -0
  6. package/api.mjs +56 -0
  7. package/bin/engine.mjs +38 -19
  8. package/bin/ext.mjs +16 -20
  9. package/bin/factoidal.mjs +667 -118
  10. package/bin/pack.mjs +37 -0
  11. package/bin/serve-worker.mjs +32 -0
  12. package/bin/serve.mjs +423 -0
  13. package/bin/store-worker-host.mjs +1 -1
  14. package/bin/store.mjs +1 -1
  15. package/browser.d.ts +114 -507
  16. package/browser.js +155 -2257
  17. package/docs/fuseki-replacement.md +108 -0
  18. package/fn.d.ts +25 -96
  19. package/fn.js +131 -240
  20. package/index.d.ts +620 -260
  21. package/index.js +4 -234
  22. package/index.mjs +180 -84
  23. package/l4-assets/l4factoidal.js +47 -7
  24. package/l4-assets/l4factoidal.mjs +1 -1
  25. package/l4-assets/l4factoidal.wasm +0 -0
  26. package/l4-assets/version.json +11 -7
  27. package/lib/api.mjs +2337 -0
  28. package/lib/browser-engine.mjs +69 -0
  29. package/lib/dataset.mjs +471 -0
  30. package/lib/delta-log-hosts.mjs +191 -0
  31. package/lib/lean-entry.mjs +213 -0
  32. package/lib/pure.mjs +98 -0
  33. package/lib/store.mjs +115 -0
  34. package/lib/version.mjs +5 -0
  35. package/lws/README.md +137 -0
  36. package/lws/persist.mjs +78 -0
  37. package/lws/server.mjs +416 -0
  38. package/package.json +59 -61
  39. package/rdfjs.js +5 -536
  40. package/rdfjs.mjs +517 -0
  41. package/sample-store.d.ts +8 -16
  42. package/sample-store.mjs +39 -29
  43. package/solid/client/README.md +100 -0
  44. package/solid/client/index.mjs +293 -0
  45. package/solid/server/README.md +96 -0
  46. package/solid/server/index.mjs +395 -0
  47. package/store-host/deno.mjs +8 -0
  48. package/store-host/index.mjs +12 -0
  49. package/store-host/node.mjs +8 -0
  50. package/xmpp/README.md +107 -0
  51. package/xmpp/index.mjs +229 -0
  52. package/browser-wasm.js +0 -872
  53. package/factoidal-npm-entry.js +0 -15477
  54. package/factoidal-npm-entry.wasm.assets/code-5a7fc68f2ab1323718b8.wasm +0 -0
  55. package/factoidal-npm-entry.wasm.js +0 -455
  56. package/factoidal.js +0 -13118
  57. package/factoidal.wasm.assets/code-bbe6099bfb5b10c4c3ab.wasm +0 -0
  58. package/factoidal.wasm.js +0 -457
  59. package/hacl-init.js +0 -92
  60. package/hacl-wasm/FStar.wasm +0 -0
  61. package/hacl-wasm/Hacl_Bignum.wasm +0 -0
  62. package/hacl-wasm/Hacl_Bignum25519_51.wasm +0 -0
  63. package/hacl-wasm/Hacl_Bignum_Base.wasm +0 -0
  64. package/hacl-wasm/Hacl_Curve25519_51.wasm +0 -0
  65. package/hacl-wasm/Hacl_Ed25519.wasm +0 -0
  66. package/hacl-wasm/Hacl_Ed25519_PrecompTable.wasm +0 -0
  67. package/hacl-wasm/Hacl_Hash_Base.wasm +0 -0
  68. package/hacl-wasm/Hacl_Hash_SHA2.wasm +0 -0
  69. package/hacl-wasm/Hacl_IntTypes_Intrinsics.wasm +0 -0
  70. package/hacl-wasm/LowStar_Endianness.wasm +0 -0
  71. package/hacl-wasm/WasmSupport.wasm +0 -0
  72. package/hacl-wasm/api.js +0 -775
  73. package/hacl-wasm/api.json +0 -3787
  74. package/hacl-wasm/layouts.json +0 -1
  75. package/hacl-wasm/loader.js +0 -568
  76. package/hacl-wasm/shell.js +0 -12
  77. package/l4-core.d.ts +0 -159
  78. package/l4-core.js +0 -188
  79. package/l4.d.ts +0 -50
  80. package/l4.js +0 -104
  81. package/lib/api.js +0 -2300
  82. package/lib/engine-js.js +0 -165
  83. package/lib/engine-wasm.js +0 -300
  84. package/sample-store/CURRENT +0 -1
  85. package/sample-store/gen-1/manifest.sbm2 +0 -0
  86. package/sample-store/gen-1/manifest.tsv +0 -14
  87. package/sample-store/gen-1/predicate-0.ibk3 +0 -0
  88. package/sample-store/gen-1/predicate-0.ibk3.merkle +0 -1
  89. package/sample-store/gen-1/predicate-0.ibk3.oli2 +0 -0
  90. package/sample-store/gen-1/predicate-0.ibk3.oli2.merkle +0 -1
  91. package/sample-store/gen-1/predicate-0.ibk3.sri2 +0 -0
  92. package/sample-store/gen-1/predicate-0.ibk3.sri2.merkle +0 -1
  93. package/sample-store/gen-1/predicate-0.ibk3.tli1 +0 -0
  94. package/sample-store/gen-1/predicate-0.ibk3.tli1.merkle +0 -0
  95. package/sample-store/gen-1/predicate-1.ibk3 +0 -0
  96. package/sample-store/gen-1/predicate-1.ibk3.merkle +0 -0
  97. package/sample-store/gen-1/predicate-1.ibk3.oli2 +0 -0
  98. package/sample-store/gen-1/predicate-1.ibk3.oli2.merkle +0 -0
  99. package/sample-store/gen-1/predicate-1.ibk3.sri2 +0 -0
  100. package/sample-store/gen-1/predicate-1.ibk3.sri2.merkle +0 -1
  101. package/sample-store/gen-1/predicate-1.ibk3.tli1 +0 -0
  102. package/sample-store/gen-1/predicate-1.ibk3.tli1.merkle +0 -1
  103. package/sample-store/gen-1/predicate-10.ibk3 +0 -0
  104. package/sample-store/gen-1/predicate-10.ibk3.merkle +0 -1
  105. package/sample-store/gen-1/predicate-10.ibk3.oli2 +0 -0
  106. package/sample-store/gen-1/predicate-10.ibk3.oli2.merkle +0 -0
  107. package/sample-store/gen-1/predicate-10.ibk3.sri2 +0 -0
  108. package/sample-store/gen-1/predicate-10.ibk3.sri2.merkle +0 -1
  109. package/sample-store/gen-1/predicate-10.ibk3.tli1 +0 -0
  110. package/sample-store/gen-1/predicate-10.ibk3.tli1.merkle +0 -1
  111. package/sample-store/gen-1/predicate-11.ibk3 +0 -0
  112. package/sample-store/gen-1/predicate-11.ibk3.merkle +0 -0
  113. package/sample-store/gen-1/predicate-11.ibk3.oli2 +0 -0
  114. package/sample-store/gen-1/predicate-11.ibk3.oli2.merkle +0 -1
  115. package/sample-store/gen-1/predicate-11.ibk3.sri2 +0 -0
  116. package/sample-store/gen-1/predicate-11.ibk3.sri2.merkle +0 -1
  117. package/sample-store/gen-1/predicate-11.ibk3.tli1 +0 -0
  118. package/sample-store/gen-1/predicate-11.ibk3.tli1.merkle +0 -1
  119. package/sample-store/gen-1/predicate-12.ibk3 +0 -0
  120. package/sample-store/gen-1/predicate-12.ibk3.merkle +0 -1
  121. package/sample-store/gen-1/predicate-12.ibk3.oli2 +0 -0
  122. package/sample-store/gen-1/predicate-12.ibk3.oli2.merkle +0 -1
  123. package/sample-store/gen-1/predicate-12.ibk3.sri2 +0 -0
  124. package/sample-store/gen-1/predicate-12.ibk3.sri2.merkle +0 -1
  125. package/sample-store/gen-1/predicate-12.ibk3.tli1 +0 -0
  126. package/sample-store/gen-1/predicate-12.ibk3.tli1.merkle +0 -1
  127. package/sample-store/gen-1/predicate-2.ibk3 +0 -0
  128. package/sample-store/gen-1/predicate-2.ibk3.merkle +0 -1
  129. package/sample-store/gen-1/predicate-2.ibk3.oli2 +0 -0
  130. package/sample-store/gen-1/predicate-2.ibk3.oli2.merkle +0 -1
  131. package/sample-store/gen-1/predicate-2.ibk3.sri2 +0 -0
  132. package/sample-store/gen-1/predicate-2.ibk3.sri2.merkle +0 -0
  133. package/sample-store/gen-1/predicate-2.ibk3.tli1 +0 -0
  134. package/sample-store/gen-1/predicate-2.ibk3.tli1.merkle +0 -0
  135. package/sample-store/gen-1/predicate-3.ibk3 +0 -0
  136. package/sample-store/gen-1/predicate-3.ibk3.merkle +0 -1
  137. package/sample-store/gen-1/predicate-3.ibk3.oli2 +0 -0
  138. package/sample-store/gen-1/predicate-3.ibk3.oli2.merkle +0 -1
  139. package/sample-store/gen-1/predicate-3.ibk3.sri2 +0 -0
  140. package/sample-store/gen-1/predicate-3.ibk3.sri2.merkle +0 -1
  141. package/sample-store/gen-1/predicate-3.ibk3.tli1 +0 -0
  142. package/sample-store/gen-1/predicate-3.ibk3.tli1.merkle +0 -1
  143. package/sample-store/gen-1/predicate-4.ibk3 +0 -0
  144. package/sample-store/gen-1/predicate-4.ibk3.merkle +0 -0
  145. package/sample-store/gen-1/predicate-4.ibk3.oli2 +0 -0
  146. package/sample-store/gen-1/predicate-4.ibk3.oli2.merkle +0 -1
  147. package/sample-store/gen-1/predicate-4.ibk3.sri2 +0 -0
  148. package/sample-store/gen-1/predicate-4.ibk3.sri2.merkle +0 -1
  149. package/sample-store/gen-1/predicate-4.ibk3.tli1 +0 -0
  150. package/sample-store/gen-1/predicate-4.ibk3.tli1.merkle +0 -2
  151. package/sample-store/gen-1/predicate-5.ibk3 +0 -0
  152. package/sample-store/gen-1/predicate-5.ibk3.merkle +0 -1
  153. package/sample-store/gen-1/predicate-5.ibk3.oli2 +0 -0
  154. package/sample-store/gen-1/predicate-5.ibk3.oli2.merkle +0 -1
  155. package/sample-store/gen-1/predicate-5.ibk3.sri2 +0 -0
  156. package/sample-store/gen-1/predicate-5.ibk3.sri2.merkle +0 -1
  157. package/sample-store/gen-1/predicate-5.ibk3.tli1 +0 -0
  158. package/sample-store/gen-1/predicate-5.ibk3.tli1.merkle +0 -1
  159. package/sample-store/gen-1/predicate-6.ibk3 +0 -0
  160. package/sample-store/gen-1/predicate-6.ibk3.merkle +0 -1
  161. package/sample-store/gen-1/predicate-6.ibk3.oli2 +0 -0
  162. package/sample-store/gen-1/predicate-6.ibk3.oli2.merkle +0 -1
  163. package/sample-store/gen-1/predicate-6.ibk3.sri2 +0 -0
  164. package/sample-store/gen-1/predicate-6.ibk3.sri2.merkle +0 -1
  165. package/sample-store/gen-1/predicate-6.ibk3.tli1 +0 -0
  166. package/sample-store/gen-1/predicate-6.ibk3.tli1.merkle +0 -0
  167. package/sample-store/gen-1/predicate-7.ibk3 +0 -0
  168. package/sample-store/gen-1/predicate-7.ibk3.merkle +0 -0
  169. package/sample-store/gen-1/predicate-7.ibk3.oli2 +0 -0
  170. package/sample-store/gen-1/predicate-7.ibk3.oli2.merkle +0 -2
  171. package/sample-store/gen-1/predicate-7.ibk3.sri2 +0 -0
  172. package/sample-store/gen-1/predicate-7.ibk3.sri2.merkle +0 -1
  173. package/sample-store/gen-1/predicate-7.ibk3.tli1 +0 -0
  174. package/sample-store/gen-1/predicate-7.ibk3.tli1.merkle +0 -1
  175. package/sample-store/gen-1/predicate-8.ibk3 +0 -0
  176. package/sample-store/gen-1/predicate-8.ibk3.merkle +0 -1
  177. package/sample-store/gen-1/predicate-8.ibk3.oli2 +0 -0
  178. package/sample-store/gen-1/predicate-8.ibk3.oli2.merkle +0 -1
  179. package/sample-store/gen-1/predicate-8.ibk3.sri2 +0 -0
  180. package/sample-store/gen-1/predicate-8.ibk3.sri2.merkle +0 -1
  181. package/sample-store/gen-1/predicate-8.ibk3.tli1 +0 -0
  182. package/sample-store/gen-1/predicate-8.ibk3.tli1.merkle +0 -1
  183. package/sample-store/gen-1/predicate-9.ibk3 +0 -0
  184. package/sample-store/gen-1/predicate-9.ibk3.merkle +0 -1
  185. package/sample-store/gen-1/predicate-9.ibk3.oli2 +0 -0
  186. package/sample-store/gen-1/predicate-9.ibk3.oli2.merkle +0 -1
  187. package/sample-store/gen-1/predicate-9.ibk3.sri2 +0 -0
  188. package/sample-store/gen-1/predicate-9.ibk3.sri2.merkle +0 -3
  189. package/sample-store/gen-1/predicate-9.ibk3.tli1 +0 -0
  190. package/sample-store/gen-1/predicate-9.ibk3.tli1.merkle +0 -2
  191. package/select.d.ts +0 -116
  192. package/select.js +0 -492
  193. package/version.json +0 -102
  194. package/wasm.d.ts +0 -130
  195. 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
+ }
@@ -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';