@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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,620 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.0 — 2026-10-21
4
+
5
+ No RDF, SPARQL, datatype or IRI decision is made in JavaScript any more
6
+ (https://github.com/danbri/factoidal/issues/894; owner, 2026-10-21: "The ja
7
+ leak fix is p0"). The engine parses, writes, escapes, compares and chooses
8
+ datatypes; the JavaScript copies term components and calls ops.
9
+ `tools/host-purity-lint.sh` group 3 scans the npm and hub JavaScript for RDF
10
+ syntax and runs in `npm test`.
11
+
12
+ ### Breaking changes
13
+
14
+ These shapes could not be kept:
15
+
16
+ - `shexValidate` and `Dataset.validateShex`: a focus or shape given as a
17
+ string is read as one RDF 1.2 N-Triples term (`'<http://…>'` or `'_:b'`);
18
+ a bare IRI string is refused. Every API that takes a term as a string
19
+ now reads it through one engine reader (`TermJson.readTermString`), so
20
+ `_:x` is always a blank node and `<…>` always an IRI.
21
+ - `graphs()` returns `[NamedNode|BlankNode, Dataset]` pairs, not
22
+ `[string, Dataset]`, so an IRI graph and a blank-node graph with the same
23
+ text are never confused.
24
+ - A literal with a language and a direction has datatype
25
+ `rdf:dirLangString` (RDF 1.2 Concepts; RDF/JS Data Model); before, the
26
+ RDF/JS default gave `rdf:langString`.
27
+
28
+ - `factoidal lws-serve` and `@factoidal/core/lws` enforce storage-server
29
+ authorization from the LWS editor's draft (401 with
30
+ `WWW-Authenticate: Bearer resource_metadata`, the RFC 9728 metadata
31
+ document, Bearer JWT access tokens checked for signature, `iss`, `aud`,
32
+ `exp`, `nbf` and `iat`, then Web Access Control). `lws-serve` needs
33
+ `--issuer IRI`, `--auth-config FILE` or, for development only,
34
+ `--insecure-no-auth`; `listen()` with neither `auth` nor
35
+ `insecureNoAuth` serves only what the ACLs give to the public. New op
36
+ `lwsFetched`. https://github.com/danbri/factoidal/issues/886
37
+
38
+ - The N-Quads text functions of `@factoidal/core/rdfjs` (`nquadsToQuads`,
39
+ `nquadsLineToQuad`, `quadsToNQuads`, `quadToNQuads`, `termToNQuads`,
40
+ `DatasetCore.fromNQuads`, `DatasetCore.prototype.toNQuads`) call the engine.
41
+ They throw until the engine is loaded: `await ready()` (new) or any other
42
+ `@factoidal/core` call first. `nquadsToQuads` now rejects a line the
43
+ N-Quads grammar rejects (before, `<http://e/a b>` was read as an IRI), and
44
+ the writers escape as the engine's canonical N-Triples form does (before,
45
+ `\b`, `\f` and other control characters were written raw).
46
+ - A JavaScript number answered by a registered extension function becomes
47
+ `xsd:integer` only for an integer within ±(2^53 − 1); every other number is
48
+ an `xsd:double` in the XSD 1.1 canonical form (`1e21` is `"1.0E21"`, before
49
+ `"1e+21"^^xsd:integer`; `0.1` is `"1.0E-1"`, before `"0.1"`). `NaN`,
50
+ `Infinity` and `-Infinity` answer `"NaN"`, `"INF"`, `"-INF"`; before they
51
+ were the §17.6 error. A `bigint` answers `xsd:integer`
52
+ (`L4Factoidal.XSD.JsValue.jsLiteral`).
53
+ - `queryHdt` answers a Dataset for CONSTRUCT and DESCRIBE; before it threw a
54
+ TypeError chosen by a regular expression over the query text.
55
+ - `Literal` has a `direction` member (`''`, `'ltr'`, `'rtl'`), and a
56
+ directional language-tagged string from the engine keeps its direction and
57
+ its `rdf:dirLangString` datatype; `equals` compares it.
58
+
59
+ ### New
60
+
61
+ - `isomorphic(a, b)`: dataset isomorphism by the engine
62
+ (`Dataset.isomorphicOutcome`, blank nodes in triple terms included).
63
+ `@factoidal/core/fn`'s `equals` uses it.
64
+ - `fromQuads(quads)`: a Dataset from RDF/JS quads, sent as term components.
65
+ - `ready()`: load the engine. `engineTables()`: the engine's format,
66
+ regime, query-output and pack-syntax name tables.
67
+ - Ops (formal/lean4/Wasm/Ops/Handles.lean): `datasetQuadsJson`,
68
+ `datasetAddQuadsJson`, `datasetDeleteQuadsJson`, `datasetFromQuadsJson`,
69
+ `datasetGraphNames`, `datasetGraphAsDefault`, `datasetIsomorphic`,
70
+ `parseQuadsJson`, `quadsToNQuads`, `termsToNTriples`, `resolveIri`,
71
+ `queryForm`, `engineTables`; `datasetMatch`/`datasetMatchCount` take term
72
+ objects. Theorems: `TermJson.decodeTerm_encodeTerm`,
73
+ `TermJson.decodeQuad_encodeQuad`, `TermJson.mem_quadList_default`,
74
+ `TermJson.mem_quadList_named`, `TermJson.toCanonicalNQuads_eq_quadList`,
75
+ `TermJson.quadPage_append`, `XSD.JsValue.doubleLexical_inLexicalSpace`,
76
+ `XSD.JsValue.integerLexical_inLexicalSpace`,
77
+ `XSD.JsValue.jsLiteral_number_double`, `XSD.JsValue.jsLiteral_number_integer`.
78
+
79
+ ### Added
80
+
81
+ - An extension function may answer an exact term: an RDF/JS term
82
+ (Literal, NamedNode, BlankNode, or a Quad as an RDF 1.2 triple term), or
83
+ `{value, datatype}`, or `{value, language, direction?}`. The lexical form
84
+ and the datatype IRI are kept as given, ill-typed literals included (RDF
85
+ 1.1 Concepts §3.3). The engine checks only what RDF requires: absolute
86
+ IRIs, a well-formed BCP 47 language tag, direction `ltr` or `rtl`; a term
87
+ that fails is the §17.6 error for that call. A plain JS number still gets
88
+ its datatype from `jsLiteral`. Theorem `TermJson.extAnswerJson_encodeTerm`:
89
+ an app-supplied term is the answer, unchanged.
90
+
91
+ ### Performance
92
+
93
+ - Dataset iteration reads the quads from the engine as one byte region (op
94
+ `datasetQuadsBin`: a term table in the delta-log v2 term codec and four
95
+ u32 indices per quad), decoded in JavaScript with `DataView` and
96
+ `TextDecoder`. 60,000 quads: 178–205 ms under load, against 214–280 ms in
97
+ 0.10.0 and about the time of the N-Quads writer op in the same run.
98
+ Theorems `decodeBin_quadsBin` and `quadsBin_binSpec`
99
+ (`formal/lean4/L4Factoidal/RDF/QuadsBinTheorems.lean`) prove the region
100
+ reads back as exactly the dataset's quads, so no run-time check is made.
101
+ https://github.com/danbri/factoidal/issues/900
102
+
103
+ ### Fixed
104
+
105
+ - `graphs()` (both APIs) no longer merges an IRI graph and a blank-node graph
106
+ whose strings are equal.
107
+ - `Dataset` iteration builds its quads from the engine's term objects; it no
108
+ longer parses N-Quads text in JavaScript.
109
+ - The Solid client resolves a relative target against `baseIri` by RFC 3986
110
+ §5.2 (op `resolveIri`); before it concatenated strings.
111
+ - `factoidal query` table cells and `factoidal inspect` graph names are the
112
+ engine's canonical N-Triples forms (escaped); `factoidal pack` reads its
113
+ suffix table from the engine and detects the syntax of a file with an
114
+ unknown suffix from its content.
115
+
116
+ ## 0.10.0 — 2026-10-21
117
+
118
+ ### Breaking changes
119
+
120
+ One wasm carries every capability (owner, 2026-10-20: "all this stuff goes
121
+ into a single mod we call Factoidal/core and a dingle wasm"):
122
+
123
+ - The duplicate smaller wasm (`l4-assets/l4factoidal-core.{wasm,mjs,js}`) and
124
+ its `./core` and `./core-browser` subpaths are gone. `l4factoidal.wasm`
125
+ serves every function of the package; import `@factoidal/core`.
126
+ `l4-assets/version.json` has no `coreWasmSha256`/`coreWasmBytes`.
127
+ - No demonstration triples ship (owner: "All the CLIs are still bundled i
128
+ just didn't want any actual demo triples cluttering the file"): the
129
+ 4,434-triple `sample-store/` directory is gone. `factoidal sample-store
130
+ [DIR]` and `sampleStorePath([dir])` now pack 15 triples held in
131
+ `sample-store.mjs` into a store under DIR or the temporary directory and
132
+ return its path; `sampleStoreFacts` describes that store.
133
+ - The demonstration functions `matrixDeterminant`, `matrixScalarProduct`,
134
+ `matrixVectorProduct`, `matrixOuterProduct`, `sigmoidPoints` and
135
+ `sigmoidFormulaMathml` (and `capabilities().matrix`/`.sigmoid`) left the
136
+ JavaScript API, `@factoidal/core/fn` and the types. The Lean ops stay and
137
+ answer through `loadEngine().call(op, args)`.
138
+
139
+ One `Dataset` class whose data stays in the engine
140
+ (https://github.com/danbri/factoidal/issues/882 item 1):
141
+
142
+ - `Dataset` is a handle into the engine's dataset store; it no longer holds
143
+ N-Quads text. `new Dataset(...)` and `Dataset.fromNQuads(...)` are gone:
144
+ make one with `parse`, `openDataset` or another producer. It still implements RDF/JS DatasetCore:
145
+ `add`, `delete`, `has` and `match` run in the engine (`match` and `has` use
146
+ its indexes; `match` answers a new Dataset). The in-memory RDF/JS DatasetCore is
147
+ `DatasetCore` in `@factoidal/core/rdfjs` (that subpath no longer exports
148
+ `Dataset`).
149
+ - `DatasetHandle` is a deprecated alias of `Dataset` (removed in 0.11.0).
150
+ `handle.close()`, `handle.closed` and `handle.toDataset()` are replaced by
151
+ `dispose()`, `disposed` and the Dataset itself. Using a disposed Dataset
152
+ throws a `TypeError`.
153
+ - `openDataset` answers a `Dataset` (a Dataset argument is cloned).
154
+ - `coreRdfsClosure`/`rhoDfClosure` and `rdfsPlusClosure` answer a `Dataset`
155
+ (before: `{ok, ntriples}` and `{ok, ntriples, rounds}`; `rounds` is gone).
156
+ `fn.coreRdfsClosure` and `fn.rdfsPlusClosure` drop `rounds` too.
157
+ - Text input with no `format` is no longer read as Turtle: the engine infers
158
+ the format (op `detectFormat`: JSON-LD, RDF/XML, TriG or Turtle, in RDF
159
+ 1.2 mode) and reports it in `dataset.format` / `dataset.formatInferred`, or
160
+ in the error message. N-Quads is never inferred; name it.
161
+ - Every function throws a `TypeError` for an unknown option key.
162
+ - `update(dataset, text)` answers a new Dataset and leaves its argument
163
+ unchanged (as before); `dataset.update(text)` changes it in place.
164
+ - `fn.toDataset` answers a DatasetCore; `fn.fromDataset` takes a `Dataset`
165
+ or a DatasetCore.
166
+
167
+ ### New
168
+
169
+ - `Dataset` methods: `query` (with `entail` and `output`: SPARQL results
170
+ JSON, XML, CSV, TSV), `update`, `validateShacl`, `validateShex`,
171
+ `closure(regime)`, `canonicalize`, `serialize({format, version})`,
172
+ `toString(format)`, `clone`, `has`, `match`, lazy RDF/JS quad iteration,
173
+ `dispose`, `[Symbol.dispose]`; a `FinalizationRegistry` releases a
174
+ collected Dataset.
175
+ - `serialize` writes every writer the engine has: N-Quads, N-Triples,
176
+ Turtle, TriG, RDF/XML, JSON-LD, RDFC-1.0 canonical N-Quads, each with
177
+ `version: '1.1'` (refuses RDF 1.2 terms) or `'1.2'`. `formats()` gives the
178
+ engine's list; the README table is generated from it. Writers the engine
179
+ lacks: https://github.com/danbri/factoidal/issues/883.
180
+ - `closure(data, regime)`.
181
+ - Engine ops (Lean, `Wasm/Ops/Handles.lean`, `Wasm/Ops/DatasetFull.lean`):
182
+ `datasetClone`, `datasetCanonicalize`, `datasetClosure`,
183
+ `datasetShaclValidate`, `datasetQueryRaw`, `datasetFormats`,
184
+ `detectFormat`, `datasetFromCsvw`, `datasetFromRml`, `datasetFromJsonLd`,
185
+ `datasetToJsonLd`, `datasetShexValidate`, `datasetRifEval`; and
186
+ `datasetSerialize` writes N-Triples, TriG, RDF/XML and RDFC-1.0.
187
+ - Every example in README.md and API.md is executed by
188
+ `test/doc-examples.test.mjs`.
189
+
190
+ ### Other changes
191
+
192
+ - The README is a short summary: what the package is, a usage example, the
193
+ JavaScript API and command list, the specifications covered with test
194
+ scores and their sources, and measured strengths and gaps. The previous
195
+ README is `API.md` in the repository (not in the tarball).
196
+ - New pure functions over engine ops that had no JavaScript function:
197
+ `aeadSeal`, `aeadOpen` (ChaCha20-Poly1305, RFC 8439), `hpkeSeal`,
198
+ `hpkeOpen` (RFC 9180 base mode), `proofCheck`, `proofInspect` (FPP0 proof
199
+ bundles), `canonicalizeNQuads` and `normalizeNQuads` (N-Quads text in, text
200
+ out). Tests: `test/pure-and-serve.test.mjs`.
201
+ - New `serveStore(root, {port, host})`: the `factoidal serve` SPARQL 1.1
202
+ Protocol server as a function, `{url, port, close()}`. Node only.
203
+ - `factoidal serve --file DATA /ds`, the `fuseki-server --file` form, and
204
+ the Fuseki service names `/ds/query`, `/ds/update` and `/ds/get` beside
205
+ `/ds/sparql` (decided in `StoreEndpoint.lean`). Record and gaps:
206
+ `docs/fuseki-replacement.md`; tests `test/fuseki-compat.test.mjs`. W3C live
207
+ protocol run: 50 pass, 0 fail (out of 50).
208
+ - The wasm is rebuilt from `main`; the package carries the Lean 4 engine only.
209
+ Package suite: 432 pass, 0 fail (out of 432); 4 of them are expected
210
+ failures naming issues 829, 830 and 831.
211
+ - Removed: the 0.9.x subpath aliases `@factoidal/core/l4` and
212
+ `@factoidal/core/l4-core` (files `l4.js`, `l4.d.ts`, `l4-core.js`,
213
+ `l4-core.d.ts`), as announced in 0.9.0. Import `@factoidal/core`; the
214
+ low-level engine handle is `loadEngine()`.
215
+ - `l4-assets/version.json` is written at release from the
216
+ `danbri/factoidal-builds` release that built the wasm
217
+ (`tools/fetch-builds.sh --set web`): source commit, build time, sha256 and
218
+ size of both wasm files, and the engine's own version. The committed file
219
+ is a placeholder. `factoidal version` prints the stamp and the loaded
220
+ engine's `version()`, and exits 1 when the stamped sha256 differs from the
221
+ installed wasm.
222
+ - RDF 1.2 format tags `turtle12`, `ntriples12`, `nquads12`, `trig12` are in
223
+ the `DataFormat` type.
224
+ - Includes the entries listed under "Unreleased" below.
225
+
226
+ ## Unreleased (included in 0.10.0)
227
+
228
+ - **The durable update log in the browser, on the Lean engine**
229
+ ([issue 728](https://github.com/danbri/factoidal/issues/728); hub post 18).
230
+ `deltaLogOpen`, `deltaLogAppend`, `deltaLogReadAllHex`, `deltaLogMerge`,
231
+ `deltaLogDestroy` and `_deltaLogCorruptLastForTest` are back, with the names
232
+ and result shapes of the F\* browser entry before 0.9.0, and
233
+ `deltaLogInspect` and the capability `deltaLog` are new. One record per
234
+ update, in the DLB1 format of the store's delta log; the engine ops
235
+ `deltaLogEncode`, `deltaLogDecode` and `deltaLogMerge` make every decision
236
+ about the bytes (checksums, a torn last record, merge on read), with
237
+ theorems in `formal/lean4/L4Factoidal/Storage/BrowserLogTheorems.lean`.
238
+ The browser entries keep the records in IndexedDB; the Node entries keep
239
+ them in the memory of the process. A record that does not verify, and
240
+ every record after it, is not applied.
241
+
242
+ - **SPARQL `SERVICE` failures inside `EXISTS` and under an outer `SILENT`**
243
+ ([issue 735](https://github.com/danbri/factoidal/issues/735); SPARQL 1.1
244
+ Federated Query §2.3 and §3.2). A failed `SERVICE` call without `SILENT`
245
+ inside `EXISTS` or `NOT EXISTS` (in `FILTER`, `BIND`, an `OPTIONAL`
246
+ condition, `HAVING`, `ORDER BY` or a `SELECT` expression) now fails the
247
+ query, as the same call in a graph pattern does. A failed `SERVICE` call
248
+ nested in the body of an outer `SERVICE SILENT` is a failure of the outer
249
+ call, so the outer call gives one solution with no bindings; without
250
+ `SILENT` on the outer call the query fails. The two expected-failure cases
251
+ in `test/l4-parity.test.js` are now assertions.
252
+
253
+ - **`factoidal serve STORE --port N`: a SPARQL 1.1 endpoint over an activated
254
+ store** ([issue 669](https://github.com/danbri/factoidal/issues/669), step
255
+ 1). SPARQL 1.1 Protocol query and update at `/sparql`, the Service
256
+ Description on a GET there with no query string, and the Graph Store HTTP
257
+ Protocol at `/data`. An update or a Graph Store write is appended to the
258
+ generation's delta log as one batch, synced, before it is answered. The
259
+ request framing, routing, status codes, negotiation and the delta-log batch
260
+ are decided by the engine (`storeServeKeys`, `storeServeOpen`,
261
+ `storeServeRequest`, `storeServeCommit`, `storeServeClose`); `bin/serve.mjs`
262
+ moves bytes. W3C protocol, graph-store and service-description tests over
263
+ a live socket: 50 pass, 0 fail (out of 50). Open gap: a quad generation's
264
+ delta log is not replayed by the native quad reader
265
+ ([issue 770](https://github.com/danbri/factoidal/issues/770)).
266
+ - **`@factoidal/core/core`: the core engine** ([issue 713](https://github.com/danbri/factoidal/issues/713),
267
+ stage 4). `l4factoidal-core.wasm` (5.68 MB, 1.07 MB with gzip; the full
268
+ engine is 10.54 MB, 2.07 MB) serves parse and serialize (Turtle, TriG,
269
+ N-Triples, N-Quads, RDF/XML, lenient), SPARQL 1.1 and 1.2 query and update
270
+ over text and dataset handles, canonicalization, the RDFS and OWL 2 RL
271
+ closures and SHACL. The entry has the same exports as `.`; a function
272
+ outside that subset rejects naming `@factoidal/core`, and
273
+ `capabilities().profile` is `'core'` (`'full'` on the other entries).
274
+ Files: `core.mjs`, `core.js`, `core-browser.js`, `core.d.ts`,
275
+ `core-browser.d.ts`, `l4-assets/l4factoidal-core.{js,mjs,wasm}`.
276
+ - A SELECT of more than about 11,000 solutions, and `canonicalize()` of more
277
+ than about 20,000 blank-node quads, no longer fail under Node's default
278
+ stack with "Maximum call stack size exceeded". Four functions of the Lean
279
+ library recursed once per row or per quad (`SPARQL.numberRows`,
280
+ `SPARQL.rowsJoined`, `RDF.Canonical.collapseKeyedQQuads`, `dedupAdj`);
281
+ each now compiles to an accumulator form proved equal to it. The Results
282
+ JSON writer had also copied the rest of the document once per row.
283
+ - `NOW()` is bound ([issue 743](https://github.com/danbri/factoidal/issues/743)).
284
+ Through `query()`, `update()`, dataset handles, the entailment path,
285
+ `queryHdt()` and the store queries, SPARQL `NOW()` answered an error and left
286
+ its variable unbound (W3C `functions/now01.rq` answered `false`). The engine
287
+ now reads the system clock once per query or update and gives every `NOW()` of
288
+ that execution the same `xsd:dateTime` value in UTC (SPARQL 1.1 §17.4.5.1).
289
+ The wasm modules grow by about 82 KB (full) and 89 KB (core).
290
+ - The pure dispatch entries take the instant of `NOW()` as an optional last
291
+ argument ([issue 748](https://github.com/danbri/factoidal/issues/748)).
292
+ `call` and `callBlob` read no clock, so a host that uses them got an error
293
+ for `NOW()`. `queryDataset` and `updateDataset` (fourth, after the base IRI),
294
+ `queryRaw` and `storeQuery` (fourth) and `queryHdt` (second) now accept an
295
+ `xsd:dateTime` lexical form such as `2026-10-10T12:34:56Z`; an empty string
296
+ means none, and a text that is not a dateTime lexical form is an error. Hosts
297
+ that pass fewer arguments do not change. `callIO` and `callBlobIO` still read
298
+ the clock themselves.
299
+ - `Dataset` (`./rdfjs`) adds and looks up a quad in constant time. It
300
+ scanned every quad it held, so `parse()` of 12,000 quads spent 3.1 s
301
+ building the result; it now takes 0.35 s.
302
+
303
+ ## 0.9.0 — 2026-10-10
304
+
305
+ **One engine: the Lean 4 build.** ([issue 713](https://github.com/danbri/factoidal/issues/713),
306
+ stage 3.) Owner, 2026-10-10: "We should make the main module Lean4-based.
307
+ Private builds might compare this with F\* (or indeed different wasms from
308
+ different toolchains)." The main entry (`.`: `index.js`, `index.mjs`),
309
+ `./api`, `./fn` and `./browser` now load the Lean 4 library `L4Factoidal`
310
+ compiled to WebAssembly (`l4-assets/`). Every function is a dispatch op of
311
+ that module behind the typed wrapper the F\* engine had, with the same
312
+ signatures and result shapes except where the list below says otherwise.
313
+
314
+ Removed, with no replacement shim (there are no users):
315
+
316
+ - the F\* bundles (`factoidal.js`, `factoidal.wasm.js`,
317
+ `factoidal-npm-entry*.js` and their wasm assets) and the entry points
318
+ that loaded them: `./wasm`, `./lite`, `./browser-wasm`, `./select`, and the
319
+ `./factoidal*.js` subpaths. The F\* bundles stay build artifacts of
320
+ `danbri/factoidal-builds`, for private comparison builds:
321
+ `tests/engine-compare/run.sh PATH/TO/factoidal-npm-entry.js`.
322
+ - `hacl-wasm/` and `hacl-init.js`: Ed25519 is HACL\*'s C code compiled into
323
+ the Lean wasm module, SHA-256 is the Lean hash, and there is no
324
+ initialisation step.
325
+ - the COTTAS functions `openCottas`, `queryCottas`, `closeCottas` and
326
+ `toCottas` (on the main entry and on `./fn`), and the browser delta log
327
+ (`deltaLogOpen` ... `deltaLogDestroy`), which wrote COTTAS batches. The
328
+ owner ruled the F\* COTTAS store essentially unused
329
+ ([issue 566](https://github.com/danbri/factoidal/issues/566)). The
330
+ Lean persisted store replaces them (below); its update path in JavaScript
331
+ is [issue 641](https://github.com/danbri/factoidal/issues/641).
332
+ - the F\* `version.json` with its claims block. `l4-assets/version.json` is
333
+ the engine's provenance.
334
+
335
+ New:
336
+
337
+ - **The persisted store on the main entry**: `openStore(source, options)`
338
+ answers a `Store` (a store handle inside the engine: every artifact
339
+ verified against its manifest's SHA-256 once, then queried from the
340
+ retained blocks), `queryStore(source, sparql)`, `listStores()`, and
341
+ `sampleStorePath()` / `sampleStoreFacts`. `source` is a collection root
342
+ path (Node) or a reader function `(path) => bytes`, which is how a page
343
+ reads a store over `fetch()`.
344
+ - `shaclValidate` and `shexValidate` on the Lean engine (ops of
345
+ `formal/lean4/Wasm/Ops/Validate.lean`): SHACL Core, SHACL-SPARQL and SHACL
346
+ 1.2 node expressions; ShExJ or ShExC schemas. `shexValidate` answers a
347
+ boolean (the F\* engine answered `null` outside its decidable fragment).
348
+ - SPARQL 1.2 queries: `query(data, q, {sparql12: true})` and
349
+ `handle.query(q, {sparql12: true})` (ops `queryDataset12`,
350
+ `datasetQuery12`): triple-term patterns, reified triples, annotations,
351
+ `TRIPLE`/`isTRIPLE`/`SUBJECT`/`PREDICATE`/`OBJECT`.
352
+ - `query(data, q, {entail})` with `RDFS`, `OWL-RL`, `x-rdfscore` or
353
+ `x-rdfsplus` runs in the engine (op `queryRaw`): the default graph is
354
+ closed under the regime, then the query is answered. SELECT, ASK,
355
+ CONSTRUCT and DESCRIBE.
356
+ - `loadEngine()`: the low-level engine handle (`call(op, args)`), for the
357
+ dispatch ops the typed layer does not wrap.
358
+ - Common Logic: `clParse`, `clSerialize`, `clAlphaNorm`, `clNormalize` on the
359
+ main entry (they were on `./l4-core` only).
360
+ - `browser.js` loads the wasm with `fetch` and
361
+ `WebAssembly.instantiateStreaming`; `setAssetsUrl()` names another
362
+ directory for the engine's three files.
363
+ - `parse(text, {lenient: true})` (op `parseDocument`,
364
+ [issue 716](https://github.com/danbri/factoidal/issues/716)): the Turtle,
365
+ TriG, N-Triples and N-Quads parsers skip each bad statement, keep the good
366
+ ones and list one diagnostic per skipped statement in
367
+ `dataset.diagnostics`: `message`, `line`, `column`, `offset` (a UTF-16
368
+ index, as `ParseError.offset`) and a stable `class` (for example
369
+ `undefined-prefix`, `bad-escape`, `unexpected-token`). The new option
370
+ `maxErrors` stops the parse after that many diagnostics with one
371
+ `too-many-errors` diagnostic. `capabilities().parseDiagnostics` is `true`.
372
+ A document with no error gives the strict result and no diagnostics (a
373
+ theorem in the Lean source). RDF/XML and JSON-LD parse strictly.
374
+
375
+ Changed:
376
+
377
+ - **`ParseError.offset` is a UTF-16 code-unit index into the input string**,
378
+ so `text.slice(err.offset)` starts at the error. The F\* engine counted
379
+ UTF-8 bytes; the Lean engine counts Unicode scalar values and the wrapper
380
+ converts. (`line` is 1-based; `column` counts Unicode scalar values.) This
381
+ unit is a coordinator default, recorded in
382
+ `docs/designissues/2026-10-10-npm-lean-only.md`.
383
+ - Each SELECT solution `Map` iterates in the order of the SELECT clause
384
+ (`head.vars`); it followed the member order of the engine's JSON binding
385
+ objects, which carries no meaning.
386
+ - DESCRIBE answers the Concise Bounded Description of each named resource
387
+ (the F\* engine answered the empty graph,
388
+ [issue 702](https://github.com/danbri/factoidal/issues/702)).
389
+ - `serialize(data, {format: 'ntriples'})` answers the engine's N-Quads text
390
+ and refuses data with a named graph (N-Triples has no graph term).
391
+ - `xmlWellformed` reads a DOCTYPE with an internal subset (the F\* parser
392
+ answered `false` for any DOCTYPE); `xpathEval` is the full XPath 1.0
393
+ evaluator and its node kinds include `root` and `namespace`;
394
+ `mathmlEval` also evaluates `vector` and `matrix`; `rifEval`'s envelope
395
+ has no `rounds`/`engineMs`; `tableauMaterialise` adds `budgetHit`;
396
+ `rmlMap` reads the iteration format from the mapping's
397
+ `rml:referenceFormulation`. Record:
398
+ `docs/designissues/2026-10-10-npm-lean-only.md` section 6.
399
+ - `capabilities()` reports what the loaded wasm serves: new members
400
+ `entailment`, `hdt`, `sparql12`, `store`; `cottasBytesStore` is gone.
401
+ - `engines.node` is `>=22.12`: `index.js` hands the ES module `index.mjs`
402
+ to `require()`, so CommonJS and ES module callers share one engine and
403
+ one `Dataset` class. (Node 20 reached end of life on 2026-04-30.)
404
+
405
+ Open, each with an expected-failure test:
406
+
407
+ - A failed `SERVICE` call without `SILENT` inside `EXISTS`, and a failed
408
+ nested `SERVICE` under an outer `SILENT`
409
+ ([issue 735](https://github.com/danbri/factoidal/issues/735)). A failed
410
+ `SERVICE` call without `SILENT` elsewhere now fails the query
411
+ ([issue 717](https://github.com/danbri/factoidal/issues/717), fixed).
412
+
413
+ Deprecated: `./l4` and `./l4-core` are aliases of `.` in 0.9.x and go in
414
+ 0.10.0. (`./l4` was the low-level loader; `loadEngine()` replaces it.)
415
+
416
+ ### Earlier in 0.9.0 (before the switch; `./l4-core` was the Lean entry then)
417
+
418
+ **Lean engine (`factoidal/l4-core`): 26 more functions of the main
419
+ entry** ([issue 713](https://github.com/danbri/factoidal/issues/713),
420
+ stage 1 of making the package Lean-only). `jsonldToRdf`,
421
+ `jsonldFromRdf`, `rmlMap`, `csvwToRdf`, `xmlWellformed`, `xpathEval`,
422
+ `xsltTransform`, `schematronValidate`, `jsonSchemaValidate`,
423
+ `mathmlEval`, `xformsRecalc`, `rifEval`, `queryHdt`,
424
+ `tableauMaterialise`, `tableauDlInconsistent`, `queryRaw`, the three
425
+ extension-function and two SERVICE-endpoint registry functions, and the
426
+ five `toan*` functions now run over the Lean wasm. Signatures and
427
+ result shapes are the F\* engine's. `parse(text, {format: 'jsonld'})`
428
+ now works on this engine. `tableauMaterialise` also returns `budgetHit`
429
+ on the Lean engine. The main entry (`index.js`) is unchanged.
430
+
431
+ **`factoidal/l4-core` serves the maths, VC and DID functions, and matches the
432
+ F\* engine's envelopes** (stage 2 of
433
+ [issue 713](https://github.com/danbri/factoidal/issues/713)). New on the Lean
434
+ engine, with the signatures and result shapes `index.d.ts` declares:
435
+ `matrixDeterminant`, `matrixScalarProduct`, `matrixVectorProduct`,
436
+ `matrixOuterProduct`, `sigmoidPoints`, `sigmoidFormulaMathml`, `vcSha256Hex`,
437
+ `vcEd25519SecretToPublic`, `vcEd25519Sign`, `vcEd25519Verify`,
438
+ `vcEddsaCreateFromCanonical`, `vcEddsaVerifyFromCanonical`, `vcCheckCredential`,
439
+ `vcCheckCredentialSubject`, `vcCheckNoDataLoss`,
440
+ `vcCheckRelatedResourceDigests` and `didKeyResolve`. The Ed25519 operations run
441
+ HACL\* compiled into the wasm module; they need no `initHacl()` call. Envelope
442
+ parity ([issue 685](https://github.com/danbri/factoidal/issues/685)): a Turtle,
443
+ TriG, N-Triples or N-Quads syntax error rejects with a `ParseError` carrying
444
+ `line`, `column`, `offset` and `format` (the Lean `offset` counts Unicode scalar
445
+ values, the F\* `offset` counts UTF-8 bytes); `parse()` and `openDataset()`
446
+ record the prefixes of a Turtle or TriG document; Turtle output accepts the
447
+ `prefixes` and `literalShorthand` options and prints bare numeric and boolean
448
+ literals by default; and each document that is parsed gets its own blank-node
449
+ scope, so merging two documents that both write `_:b0` keeps two nodes. The
450
+ Lean engine still has no `{lenient: true}` parse and no SPARQL 1.2 handle
451
+ query.
452
+
453
+ **Store format: the literal index is renamed LGI3 and LGI4; re-pack older
454
+ generations.** The literal search index folds each lexical form before it takes
455
+ character 3-grams. On 2026-10-10 the fold changed from ASCII-only to the Unicode
456
+ case mapping that SPARQL `LCASE` uses
457
+ ([issue 706](https://github.com/danbri/factoidal/issues/706)), so the grams of a
458
+ non-ASCII literal differ and an index written under the old fold lacks rows the
459
+ Unicode `LCASE` keeps. The two layouts are now named LGI3 (was LGI1, sidecar
460
+ `.lgi3`) and LGI4 (was LGI2, sidecar `.lgi4`), and the manifest layout labels
461
+ changed with them. A generation packed earlier names LGI1 or LGI2; `storeOpen`,
462
+ `storeManifestInspect`, `storeQueryPlan`, `storeQuery`, `l4block-shard-activate`
463
+ and `l4block-quad-query` refuse it with the cause and ask for a re-pack.
464
+ `storeOpen` and `storeHandleList` also report `literalIndexes` and
465
+ `geoIndexes`, the number of retained blocks that hold a decoded index of each
466
+ kind. Record: `docs/designissues/2026-10-10-literal-index-lgi3.md`.
467
+
468
+ ## 0.8.0 — 2026-09-16
469
+
470
+ **Behaviour change: parsing is strict by default.** `parse()` (and
471
+ every function that parses text input) now rejects on a syntax error,
472
+ an undeclared prefix, or a relative IRI with no `baseIRI` in effect,
473
+ instead of silently dropping the affected statements. The error is a
474
+ new `class ParseError extends Error` (`name`, `line`, `column`,
475
+ `offset`, `format` — position fields present for Turtle/TriG/
476
+ N-Triples/N-Quads, absent for RDF/XML and JSON-LD, which carry a
477
+ message only), exported from every entry point (`index.js`/`.mjs`,
478
+ `wasm.js`, `l4-core.js`, `api.js`/`.mjs`, `lite.js`/`.mjs`, and
479
+ `browser.js`, which declares its own copy since it has no imports).
480
+ Pass `{lenient: true}` to `parse()` to recover instead: the returned
481
+ Dataset holds whatever the parser recovered, and the new
482
+ `dataset.diagnostics` array lists what was skipped (needs the
483
+ npm-entry engine bundle's `parseDocument` export). Issues
484
+ [344](https://github.com/danbri/factoidal/issues/344),
485
+ [680](https://github.com/danbri/factoidal/issues/680),
486
+ [681](https://github.com/danbri/factoidal/issues/681).
487
+
488
+ **Dataset handles.** `openDataset(data, {format?, baseIRI?})` parses
489
+ once and keeps the result indexed in the engine, so repeated
490
+ `query()`/`update()`/`serialize()` calls against the returned
491
+ `DatasetHandle` skip both the re-parse and the SPARQL-index rebuild a
492
+ fresh `query(ds, ...)` call pays every time (measured ~2x faster per
493
+ call on a 2,000-triple two-pattern join in this session — see
494
+ `tests/perf/npm_handle_vs_stateless.mjs`). `query(data, sparql,
495
+ options)`, `serialize(data, options)` and `canonicalize(data)` also
496
+ accept a `DatasetHandle` directly; `update(handle, ...)` rejects with
497
+ a `TypeError`, since `update()` always returns a fresh Dataset while
498
+ `handle.update()` mutates the handle in place. `select.js` (the F*/
499
+ Lean backend selector) deliberately does not route `openDataset()` —
500
+ a handle is opened against one engine's own entry object, so there is
501
+ no "same handle on the other engine" to fall through to.
502
+
503
+ **Prefixes and Turtle literal shorthand.** `Dataset` gains `.prefixes`
504
+ (the declared map from parsing, `{}` for N-Triples/N-Quads) and
505
+ `.diagnostics` (own, non-enumerable properties, so `JSON.stringify()`/
506
+ `toArray()` are unaffected either way). `serialize(data, {format:
507
+ 'turtle', prefixes?, literalShorthand?})` uses caller prefix pairs
508
+ (dropping unused caller namespaces, skipping caller labels when
509
+ auto-generating `nsN:` ones) when given, or automatically when `data`
510
+ is a Dataset with non-empty `.prefixes`; `literalShorthand` (default
511
+ `true`) controls whether `xsd:integer`/`decimal`/`double`/`boolean`
512
+ literals with a Turtle-grammar-matching lexical form print bare.
513
+ `DatasetHandle.serialize()` has the same options.
514
+
515
+ **New entry point `@factoidal/core/lite`** (`lite.js`/`.mjs`/`.d.ts`):
516
+ a smaller npm-entry ABI bundle (`factoidal-npm-entry-lite.js`, about
517
+ half the gzip size of the full bundle — see README.md's "Choosing a
518
+ bundle: full or lite") implementing the core surface (`parse`,
519
+ `query`, `update`, `serialize`, `canonicalize`, `graphs`,
520
+ `canonicalHash`, `openDataset`/`DatasetHandle`, the extension-function
521
+ and SERVICE-endpoint registration functions, `capabilities`) with no
522
+ SHACL, ShEx, OWL closure, JSON-LD, RDF/XML, CSVW, RML, RIF, XML,
523
+ XPath, VC crypto, COTTAS, or CLI bundle (no entailment regimes, no
524
+ `queryHdt`). `capabilities()` gains `profile` (`'full'` | `'lite'`),
525
+ `abiVersion`, `datasetHandles`, `parseDiagnostics`, `turtlePrefixes`.
526
+ Issue [684](https://github.com/danbri/factoidal/issues/684).
527
+
528
+ **`browser.js`: `query()` and `toRdf()` route through the npm-entry
529
+ ABI.** Whenever the persistent ABI bundle is available (already on
530
+ `globalThis`, or fetched by `loadNpmEntry()`), `query()` with `entail:
531
+ 'none'` and `output: 'json'` (both defaults), and every `toRdf()`
532
+ call, now go through it instead of the eval-per-call CLI bundle —
533
+ falling back to the CLI bundle only when loading the ABI fails. This
534
+ makes the classic-`<script>` route (README.md's "Bundlers and Content
535
+ Security Policy" §b) CSP-safe for parsing and SELECT/ASK too, not only
536
+ for the operations already ABI-routed. New `openDataset(text,
537
+ {format?, baseIRI?})` on `browser.js` returns a handle with `query`/
538
+ `update`/`serialize`/`close` over the same ABI. Issue
539
+ [682](https://github.com/danbri/factoidal/issues/682) (second half).
540
+
541
+ Three new entry points, for two storage protocols:
542
+
543
+ * `@factoidal/core/lws` — a Node `http` server for the
544
+ [Linked Web Storage Protocol 1.0 core](https://w3c.github.io/lws-protocol/lws10-core/);
545
+ * `@factoidal/core/solid/server` — the same shape for the
546
+ [Solid Protocol v0.11.0](https://solidproject.org/TR/protocol) server
547
+ conformance class, including the CORS preflight and the storage root;
548
+ * `@factoidal/core/solid/client` — the client conformance class over
549
+ `fetch`, whose every request is built by the engine and whose every
550
+ response is read by it.
551
+
552
+ Three commands: `factoidal lws-serve DIR`, `factoidal solid-serve DIR`
553
+ and `factoidal solid-client <get|put|post|delete|discover> URL`.
554
+
555
+ Every protocol decision is the Lean engine's; the hosts move bytes. The
556
+ operations are `lwsOpen`/`lwsStep`/`lwsClose`,
557
+ `solidOpen`/`solidStep`/`solidClose` and
558
+ `solidClientRequest`/`solidClientResponse` on the WebAssembly dispatch
559
+ ABI. **A module built before they landed answers `unknown op`**: the
560
+ entry points report it through their `…OpsAvailable(engine)` probe and
561
+ the commands exit 3. `DIR` is not read or written yet — the resource tree
562
+ lives in the engine handle — and Solid-OIDC token verification is not
563
+ built, so the first slice serves public resources and unauthenticated
564
+ writes. Issue
565
+ [659](https://github.com/danbri/factoidal/issues/659).
566
+
567
+ New entry point `@factoidal/core/api`: `createApi(entry, options?)`
568
+ wires the typed `parse`/`query`/`serialize`/... surface around an
569
+ already-loaded npm-entry ABI object, with no `fetch` and no `new
570
+ Function(src)` eval — the route for a bundler (esbuild, webpack,
571
+ Rollup) or a page under `Content-Security-Policy: script-src 'self'`.
572
+ `entry` accepts the `factoidalNpmEntry` object itself, a Promise of
573
+ it, or a zero-argument function returning either. `browser.js`'s
574
+ `loadNpmEntry()` now checks `globalThis.factoidalNpmEntry` before
575
+ fetching anything, so a classic `<script src="factoidal-npm-entry.js">`
576
+ tag (or the new `setNpmEntry(abi)`) makes every `browser.js` operation
577
+ already routed through that ABI CSP-safe too. `package.json`'s
578
+ `exports` map gains `./api` and direct subpaths for
579
+ `factoidal-npm-entry.js`/`.wasm.js`/`.wasm.assets/*` (already shipped
580
+ in `files`, not previously importable as package subpaths). Every
581
+ typed entry point (`factoidal`, `factoidal/wasm`, `factoidal/api`,
582
+ `factoidal/l4-core`) now reports `version` and `engine`. See
583
+ README.md's "Bundlers and Content Security Policy" section. Proved
584
+ against a real headless-Chromium page under that CSP header
585
+ (`tests/web-demos/bundler_csp_smoke.sh`) and against esbuild's bundler
586
+ (`test/bundler-esbuild.test.mjs`). Issue
587
+ [682](https://github.com/danbri/factoidal/issues/682).
588
+
589
+ README.md documents `parse()`'s output order (canonical/sorted
590
+ N-Quads, not document order) and the blank-node label scheme
591
+ (`_anonN` in document order for anonymous nodes, `p<k>_d<n>_` scope
592
+ prefixes, no RDF meaning beyond within-dataset identity), pinned by
593
+ `test/parse-order.test.js`. No behavior changed. Issue
594
+ [683](https://github.com/danbri/factoidal/issues/683).
595
+
596
+ A bare string where `parse`, `query`, `update`, `openDataset`,
597
+ `serialize`, `canonicalize` or `DatasetHandle.serialize` take an
598
+ options object now means `{ format: <string> }`, so
599
+ `serialize(ds, 'turtle')` is `serialize(ds, { format: 'turtle' })`.
600
+ Until now the string was read as an empty options object and
601
+ `serialize(ds, 'turtle')` returned N-Quads with no error (found by the
602
+ 0.8.0 release probe). Any other non-object value throws a `TypeError`
603
+ naming the function. Pinned by `test/options-format-string.test.js`.
604
+
605
+ The shipped bundles are built from a fresh extraction of the F\* OWL
606
+ closure source. The committed OCaml for `OWL.Closure` and
607
+ `OWL.DirectMapping.Filter` had lagged the `.fst` files since the
608
+ 2026-09-07 OWL soundness audit (commit 709f3c4; the "Check F\* Extraction"
609
+ workflow was red on `claude/main` from that date, runs 200 and 201),
610
+ so 0.7.1 was built from the pre-audit rules. Observable in this
611
+ release: the rule that derived `owl:sameAs` between named classes
612
+ from `owl:equivalentClass` is removed, datatype-range clashes are
613
+ decided by XSD value-space disjointness (`xsd_value_spaces_disjoint`),
614
+ and the direct-mapping filter also excludes the built-in OWL annotation
615
+ properties, not only declared ones. The npm-publish workflow re-extracts and refuses drift,
616
+ so this correction is a precondition of the release.
617
+
3
618
  ## 0.7.1 — 2026-09-06
4
619
 
5
620
  `factoidal pack --batch-bytes N` was documented in 0.7.0 and refused by the
@@ -567,7 +1182,7 @@ Known limits, measured:
567
1182
  format-compatibility gate rejected stores written by the current js
568
1183
  bundle (caught by the wasm/js parity test in `test/`).
569
1184
 
570
- ## Unreleased
1185
+ ## Between 0.1.0-alpha.0 and 0.1.0 — unpublished
571
1186
 
572
1187
  - Package renamed from the placeholder `@danbri/foafos` to `factoidal`
573
1188
  (issue #403). The package was never published under the old name, so
package/NOTICE CHANGED
@@ -3,27 +3,8 @@
3
3
 
4
4
  The code in this package is Apache-2.0; see LICENSE.
5
5
 
6
- sample-store/
7
- -------------
8
-
9
- The bundled sample Shardborough store holds five IPTC NewsCodes
10
- vocabularies, converted from the publisher's RDF/XML and packed into
11
- IBK3 predicate blocks:
12
-
13
- spamfstat, videoqualifier, subjectqualifier, videocodec, spct
14
-
15
- Publisher: International Press Telecommunications Council
16
- https://iptc.org/
17
- Licence: Creative Commons Attribution 4.0 International (CC BY 4.0)
18
- https://creativecommons.org/licenses/by/4.0/
19
- Obtained through: https://github.com/danbri/skosdex third_party/skos
20
-
21
- The IRIs, labels and structure are the IPTC's. The block layout, the
22
- dictionary, the indexes and the manifest are this package's encoding of
23
- them. Redistribution of the vocabulary content stays under CC BY 4.0.
24
-
25
- hacl-wasm/
26
- ----------
6
+ HACL* (compiled into l4factoidal.wasm)
7
+ --------------------------------------
27
8
 
28
9
  HACL* verified cryptographic primitives, from
29
10
  https://github.com/hacl-star/hacl-star, Apache-2.0 / MIT. See the