@factoidal/core 0.4.0 → 0.5.1

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 CHANGED
@@ -1,5 +1,144 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.1 — 2026-09-04
4
+
5
+ **A store can be opened once and queried many times.** `storeQuery` is
6
+ stateless: it re-reads, re-verifies and re-decodes the block on every
7
+ call, so a chat bot or a server paid the full cost for every question.
8
+ The new handle pays it once.
9
+
10
+ Measured on a 141-graph store, `skos:prefLabel` block of 5,571,302 bytes
11
+ and 45,806 rows, a DIFFERENT search string every query, load 10:
12
+
13
+ | | stateless | handle |
14
+ |---|---|---|
15
+ | first query (open + query) | 1,376 ms | 1,363 ms |
16
+ | second query, different string | 1,376 ms | **95 ms** |
17
+ | tenth query, all different | 1,382 ms | 103 ms |
18
+ | ten queries, total | 13,962 ms | **2,277 ms** |
19
+
20
+ ```js
21
+ import { openStoreHandle } from '@factoidal/core/store'
22
+ import { loadEngine } from '@factoidal/core/engine'
23
+ const engine = await loadEngine()
24
+ const store = await openStoreHandle(engine, '/path/to/store')
25
+ const a = store.query('PREFIX skos: … SELECT … ') // 95 ms
26
+ const b = store.query('PREFIX skos: … SELECT … ') // 95 ms
27
+ store.close()
28
+ ```
29
+
30
+ **What it buys**: the per-query SHA-256 verification, block decode,
31
+ dataset build and index build all happen once. **What it does not buy**:
32
+ `CONTAINS` still scans every retained row, so cost stays proportional to
33
+ row count. This is not a search fix. There is still no inverted index.
34
+
35
+ ⚠️ **Memory.** A held-open handle is about 170 MiB resident, of which
36
+ roughly 94 MiB is the decoded form of that 5.5 MB packed block — about
37
+ 17 times the packed size. Query evaluation peaks at 346 MiB. A process
38
+ holding several stores should watch this; `storeHandleList` reports each
39
+ handle's retained bytes and rows.
40
+
41
+ **For a long-lived process**: several stores may be open at once, keyed
42
+ independently. `storeOpen` REFUSES at its cap and never evicts another
43
+ caller's handle — an eviction policy is a host decision and is not
44
+ implemented. The WebAssembly module is single-threaded, so a host must
45
+ queue overlapping calls; use one module instance per worker thread for
46
+ concurrency. A server would still need a re-open path when a generation
47
+ is replaced on disk, and delta-log overlay support: this path serves the
48
+ manifest's committed artifacts only.
49
+
50
+ Handle answers are gated against the stateless path by comparing ROWS,
51
+ not row counts, in `tools/wasm-store-query-smoke.sh` and
52
+ `Wasm/native-smoke.sh` (85 pass, 0 fail, out of 85; was 79).
53
+
54
+ ## 0.5.0 — 2026-09-04
55
+
56
+ **Queries against a persisted store are about six times faster.** Measured
57
+ end to end the way a caller runs them — process start, engine load,
58
+ digest verification, block decode and scan — on a 141-graph store with a
59
+ 5,571,302-byte `skos:prefLabel` block of 45,806 rows, at the same machine
60
+ load:
61
+
62
+ | query | 0.4.0 | 0.5.0 |
63
+ |---|---|---|
64
+ | `CONTAINS` over labels, `LIMIT 8` | 12.03 s | **2.06 s** |
65
+ | the same for a second word | 12.18 s | **2.04 s** |
66
+ | the same for a third | 11.96 s | **2.02 s** |
67
+
68
+ Two causes, both fixed.
69
+
70
+ - **A quadratic byte copy in SHA-256.** `Crypto.processBlocks256` copied
71
+ the whole remaining message once per 64-byte block, so verifying an
72
+ artifact was quadratic in its size. Fitting `t = c*n^k` to the
73
+ admission step gave k = 2.08 before and **k = 0.98 after**. On the
74
+ three blocks of that store, admission went 1,179 / 2,150 / 11,069 ms to
75
+ 303 / 413 / 872 ms. Decode and evaluation were linear throughout.
76
+ - **`LIMIT` was not pushed down through `GRAPH`.** A `LIMIT 8` cost what
77
+ a full count cost, because `GRAPH ?g { ... }` fell through to the
78
+ reference evaluator over the whole materialised dataset. The push-down
79
+ now takes one `GRAPH` layer with a constant IRI or a variable.
80
+ `ORDER BY`, `OFFSET`, `DISTINCT`, `REDUCED`, `GROUP BY`, `HAVING`,
81
+ `VALUES` and aggregates still reject, two of them pinned by theorems.
82
+ - A `RangeError: Maximum call stack size exceeded` on `SELECT ... LIMIT 8`
83
+ is gone with it, because the query no longer materialises 45,806 rows
84
+ to return eight.
85
+
86
+ **A correctness fix found while measuring.** The pre-existing bare-BGP
87
+ `LIMIT` push-down could answer SHORT: a repeated variable (`?x ?p ?x`) or
88
+ an RDF-star triple term let the backend stop early on rows the match then
89
+ rejected. It now refuses both shapes.
90
+
91
+ **New exports.** `@factoidal/core/store`, `/pack` and `/engine`. A caller
92
+ can drive the store in process instead of spawning the command:
93
+
94
+ ```js
95
+ import { openStore, queryStore } from '@factoidal/core/store'
96
+ import { loadEngine } from '@factoidal/core/engine'
97
+ const engine = await loadEngine()
98
+ const store = openStore('/path/to/store', null)
99
+ const { result } = queryStore(engine, store, 'PREFIX skos: ... SELECT ...')
100
+ ```
101
+
102
+ **The engine carries a day of OWL work**: OWL RL 1,181 pass, 266 fail
103
+ (out of 1,457) and OWL DL about 1,326 pass, 121 fail (out of 1,457), both
104
+ against a conclusion check corrected to require one functional blank-node
105
+ mapping (RDF 1.1 Semantics §1.5 and the interpolation lemma). A false
106
+ clash was removed — the materialiser had minted one existential witness
107
+ for several obligations, so the engine denied three consistent
108
+ ontologies; ConsistencyTest went 758 pass, 4 fail to 761 pass, 1 fail.
109
+
110
+ **Named graphs now pack at scale.** The IBK4 quad path read the whole
111
+ source file and a 553 MB, 194-graph corpus could not be packed at all. It
112
+ streams now, and a quadratic term that only named graphs paid — a hash
113
+ map copied per quad in `addQuadFast` — is gone. Peak memory per source
114
+ byte fell from 37 and 20 to between 7.4 and 11.4; a 316,816,934-byte,
115
+ 194-graph input that used to fail now packs in 750 s at 2.34 GB. Byte
116
+ identity with the previous packer holds by theorem, not only by diff.
117
+
118
+ **Documentation corrected.** The GeoSPARQL section named functions that
119
+ do not exist. Six topological functions are implemented — `geof:sfEquals`,
120
+ `sfDisjoint`, `sfIntersects`, `sfTouches`, `sfWithin`, `sfContains` — and
121
+ they work against a persisted store, verified. There is no
122
+ `geof:distance`, `buffer`, `envelope`, `boundary`, `convexHull`, no
123
+ `relate` with a DE-9IM matrix, no CRS handling beyond the WKT literal and
124
+ no GML. Full text is SPARQL 1.1's own `CONTAINS` / `STRSTARTS` / `REGEX`,
125
+ evaluated per row after a block decodes: **there is no inverted index**.
126
+
127
+ Known limits, measured:
128
+
129
+ - A query is still O(rows) per search string, and nothing is retained
130
+ between queries: `storeQuery` re-reads, re-verifies and re-decodes the
131
+ block every call. A store handle that decodes once is the next step.
132
+ - `ORDER BY ... LIMIT n` still overflows the call stack above about
133
+ 14,576 materialised rows.
134
+ https://github.com/danbri/factoidal/issues/653
135
+ - `update` and `compact` still exit 3.
136
+ https://github.com/danbri/factoidal/issues/641
137
+ - A query plan is refused above 64 artifacts, 8,388,608 blob bytes or
138
+ 100,000 rows. https://github.com/danbri/factoidal/issues/648
139
+ - The two SHA-256 folds are checked equal by the FIPS 180-4 build-time
140
+ guards and the HACL* differential, not proved.
141
+
3
142
  ## 0.4.0 — 2026-09-04
4
143
 
5
144
  The package builds a store of its own. `pack` and `activate` join
package/README.md CHANGED
@@ -488,6 +488,94 @@ store, `SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }`, whole process
488
488
  including start-up: 220 ms through this command, 33 ms through the
489
489
  native `l4block-id-v3-query`.
490
490
 
491
+ ### Holding a store open: verify once, answer many
492
+
493
+ `queryStore` is stateless. Every call transfers the artifacts again, and
494
+ the engine hashes, decodes and indexes them again. A process that answers
495
+ many questions against one generation — a chat bot, an MCP server, a
496
+ SPARQL endpoint — pays all of that for every question, and none of it
497
+ depends on the question.
498
+
499
+ A **store handle** holds that work. `openStoreHandle` verifies each
500
+ artifact against the SHA-256 the manifest commits, decodes each block and
501
+ indexes the rows, once. `handle.query()` then answers from what it
502
+ retained. Several stores can be open at the same time.
503
+
504
+ ```js
505
+ import { loadEngine } from '@factoidal/core/bin/engine.mjs'
506
+ import { openStore, openStoreHandle, listStoreHandles } from '@factoidal/core/store'
507
+
508
+ const engine = await loadEngine()
509
+
510
+ // Two stores, open at once, held for the life of the process.
511
+ const skos = openStoreHandle(engine, openStore('./skos-store'))
512
+ const docs = openStoreHandle(engine, openStore('./docs-store'))
513
+
514
+ console.log(listStoreHandles(engine))
515
+ // { ok: true, handles: [ {handle:'s1', bytes:…, rows:…}, {handle:'s2', …} ],
516
+ // bytes: …, rows: …, handleCap: 8, bytesCap: 67108864 }
517
+
518
+ const PREFIX = 'PREFIX skos: <http://www.w3.org/2004/02/skos/core#>'
519
+
520
+ function labelsMatching (handle, needle) {
521
+ const answer = handle.query(`${PREFIX}
522
+ SELECT ?c ?l WHERE {
523
+ GRAPH ?g { ?c skos:prefLabel ?l }
524
+ FILTER(CONTAINS(LCASE(STR(?l)), "${needle.toLowerCase()}"))
525
+ } LIMIT 10`)
526
+ return answer.srj.results.bindings
527
+ }
528
+
529
+ // Many questions, each a new search string. None of them re-reads a block.
530
+ for (const needle of ['water', 'forest', 'railway', 'volcano']) {
531
+ console.log(needle, labelsMatching(skos, needle).length)
532
+ }
533
+ console.log(docs.query(`${PREFIX} SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }`))
534
+
535
+ skos.close()
536
+ docs.close()
537
+ ```
538
+
539
+ `openStoreHandle(engine, store, options)` takes:
540
+
541
+ | option | effect |
542
+ |---|---|
543
+ | (none) | open every artifact the manifest declares |
544
+ | `{ sparql }` | open only the artifacts that query's plan names |
545
+ | `{ keys }` | open exactly these artifact keys |
546
+
547
+ `queryStoreHandle(engine, handle, sparql)`, `closeStoreHandle(engine,
548
+ handle)` and `listStoreHandles(engine)` are the same operations for a
549
+ caller that holds only the handle id.
550
+
551
+ **Measured 2026-09-04**, macOS arm64, Node 22, the 141-graph SKOS store,
552
+ `skos:prefLabel` block of 5,571,302 bytes and 45,806 rows, one
553
+ `CONTAINS` query per row, a DIFFERENT search string every time:
554
+
555
+ | | stateless `queryStore` | handle |
556
+ |---|---|---|
557
+ | first query (open + query) | 1376 ms | 1363 ms |
558
+ | second query, different search string | 1376 ms | 95 ms |
559
+ | tenth query, all different | 1382 ms | 103 ms |
560
+ | ten queries, total | 13962 ms | 2277 ms |
561
+
562
+ **What a handle buys and what it does not.** It removes the per-query
563
+ digest check, block decode and index build. It does NOT make search
564
+ sub-linear: `CONTAINS` still scans every retained row, so a query still
565
+ costs time proportional to the row count. A text index is separate work.
566
+
567
+ **Residency.** Retaining a decoded block costs memory. Measured on the
568
+ same store: 76 MiB resident with the engine loaded and no handle, 170 MiB
569
+ with the handle open — about 94 MiB for a 5.5 MB packed block, and
570
+ evaluation peaks higher again (346 MiB during the queries above). The
571
+ caps are on ARTIFACT bytes, which is what the manifest declares: 8 open
572
+ handles, and 67108864 retained artifact bytes across all of them. A cap
573
+ is a refusal naming the cap; no handle is ever evicted to make room for
574
+ another. `listStoreHandles` is how a server sees its own residency.
575
+
576
+ **One call at a time.** The WebAssembly module is single-threaded. Two
577
+ `query()` calls cannot overlap; a server queues them.
578
+
491
579
  ## API (draft)
492
580
 
493
581
  The `factoidal` CLI (`bin/factoidal-cli/factoidal_cli.ml`, built to
@@ -653,14 +741,42 @@ value transforms:
653
741
  API; the `_*` functions (e.g. `_deltaLogCorruptLastForTest`) are
654
742
  test-only and intentionally left untyped.
655
743
 
656
- ### GeoSPARQL
744
+ ### GeoSPARQL — six topological functions
745
+
746
+ The `geof:` functions below are built into the SPARQL engine and need no
747
+ import. They work through `query()` / `fn.query()` AND against a
748
+ persisted store through `factoidal query`, because both paths evaluate
749
+ in the same environment.
750
+
751
+ geof:sfEquals geof:sfDisjoint geof:sfIntersects
752
+ geof:sfTouches geof:sfWithin geof:sfContains
753
+
754
+ ```sparql
755
+ PREFIX geof: <http://www.opengis.net/def/function/geosparql/>
756
+ PREFIX geo: <http://www.opengis.net/ont/geosparql#>
757
+ SELECT ?a WHERE {
758
+ ?a :footprint ?w
759
+ FILTER(geof:sfWithin(?w, "POLYGON((0 0,0 2,2 2,2 0,0 0))"^^geo:wktLiteral))
760
+ }
761
+ ```
657
762
 
658
- There is no separate GeoSPARQL function: the `geof:` functions
659
- (`geof:sfWithin`, `geof:sfDisjoint`, `geof:distance`, `geof:envelope`,
660
- …) are built into the SPARQL engine and work through ordinary
661
- `query()` / `fn.query()` e.g.
662
- `query(data, 'PREFIX geof: <http://www.opengis.net/def/function/geosparql/> SELECT ?a ?b WHERE { FILTER(geof:sfWithin(?a, ?b)) }')`.
663
- Nothing to import; nothing "missing".
763
+ **What is NOT there**, stated so nobody plans around it: no
764
+ `geof:distance`, `geof:buffer`, `geof:envelope`, `geof:boundary`,
765
+ `geof:convexHull` or any other non-topological measure; no
766
+ `geof:relate` with a DE-9IM matrix; no coordinate reference system
767
+ handling beyond what the WKT literal carries; no GML literals. Geometry
768
+ comes from a WKT parser, so a shapefile, GeoJSON or GML source must be
769
+ converted to `geo:wktLiteral` before it is loaded.
770
+
771
+ ### Full text: SPARQL's own functions, no index
772
+
773
+ `CONTAINS`, `STRSTARTS`, `STRENDS` and `REGEX` (SPARQL 1.1 §17.4.3) are
774
+ implemented and are the way to search text. They are evaluated per row
775
+ after a block is decoded — **there is no inverted index and no
776
+ `text:query`-style extension**. Measured 2026-09-04: a `CONTAINS` over
777
+ 45,806 `skos:prefLabel` values in one block answers in about 6 seconds.
778
+ That is fine for a vocabulary and will not scale to a large literal
779
+ corpus.
664
780
 
665
781
  ## Limits (deliberate, documented)
666
782
 
package/bin/store.mjs CHANGED
@@ -236,3 +236,160 @@ export function turtleOfNQuads (engine, nquads) {
236
236
  throw asStoreError(error)
237
237
  }
238
238
  }
239
+
240
+ // ---------------------------------------------------------------- handles
241
+ //
242
+ // `queryStore` above is stateless: every call transfers the artifacts again,
243
+ // and the engine hashes, decodes and indexes them again. A process that
244
+ // answers many questions against one generation — a chat bot, an MCP server,
245
+ // a SPARQL endpoint — pays that per question, and none of it depends on the
246
+ // question.
247
+ //
248
+ // A handle holds the verified, decoded, indexed form inside the engine.
249
+ // `storeOpen` verifies every artifact against the SHA-256 the manifest
250
+ // commits, once; `storeHandleQuery` then answers from what it retained. The
251
+ // engine refuses a query whose plan needs a block the handle does not retain,
252
+ // so a handle answer is never a partial generation.
253
+ //
254
+ // What a handle does NOT change: a query still scans every retained row, so
255
+ // its cost is still proportional to the row count. A handle removes the
256
+ // per-query decode; it is not a text index.
257
+ //
258
+ // The stateless path above stays exactly as it was. A one-shot CLI query
259
+ // should not pay to build a handle it will drop.
260
+
261
+ /** The artifact keys a manifest declares, in manifest order. */
262
+ function manifestKeys (engine, store) {
263
+ return inspectManifest(engine, store).entries.map((entry) => entry.key)
264
+ }
265
+
266
+ /** Read the named artifacts and concatenate them into one region. */
267
+ function regionOf (store, keys) {
268
+ const chunks = keys.map((key) => readWhole(joinPath(store.generationDir, key)))
269
+ let total = 0
270
+ for (const chunk of chunks) total += chunk.length
271
+ const blob = new Uint8Array(total)
272
+ const artifacts = []
273
+ let offset = 0
274
+ for (let index = 0; index < chunks.length; index += 1) {
275
+ blob.set(chunks[index], offset)
276
+ artifacts.push({ key: keys[index], offset, len: chunks[index].length })
277
+ offset += chunks[index].length
278
+ }
279
+ return { blob, artifacts }
280
+ }
281
+
282
+ /**
283
+ * An open store held inside the engine. Hold this object for as long as the
284
+ * process answers questions about the generation, then `close()` it.
285
+ *
286
+ * The wasm module is single-threaded: two `query()` calls cannot overlap. A
287
+ * server awaits one before it starts the next.
288
+ */
289
+ export class StoreHandle {
290
+ constructor (engine, store, envelope) {
291
+ this.engine = engine
292
+ this.store = store
293
+ this.handle = envelope.handle
294
+ this.identity = envelope.identity
295
+ this.layout = envelope.layout
296
+ this.wireVersion = envelope.wireVersion
297
+ this.artifacts = envelope.artifacts
298
+ this.bytes = envelope.bytes
299
+ this.rows = envelope.rows
300
+ this.closed = false
301
+ }
302
+
303
+ /**
304
+ * One SPARQL query against the retained blocks. The envelope is the one
305
+ * `queryStore` answers with — `kind`, `srj`/`boolean`/`nquads`, plus
306
+ * `shards` and `mode` — so a caller handles both paths alike.
307
+ */
308
+ query (sparql) {
309
+ if (this.closed) {
310
+ throw new StoreOperationError(`store handle ${this.handle} is closed`)
311
+ }
312
+ try {
313
+ return this.engine.call('storeHandleQuery', [this.handle, sparql])
314
+ } catch (error) {
315
+ throw asStoreError(error)
316
+ }
317
+ }
318
+
319
+ /** Drop the handle and everything it retained. Idempotent on this object. */
320
+ close () {
321
+ if (this.closed) return
322
+ this.closed = true
323
+ try {
324
+ this.engine.call('storeHandleClose', [this.handle])
325
+ } catch (error) {
326
+ throw asStoreError(error)
327
+ }
328
+ }
329
+ }
330
+
331
+ /**
332
+ * `storeOpen` — verify, decode and index a generation once.
333
+ *
334
+ * With no options every artifact the manifest declares is opened, so any
335
+ * query the generation can answer works. With `sparql` only the artifacts
336
+ * that query's plan names are opened, which is what a process asking one
337
+ * query shape wants. With `keys` exactly those artifacts are opened.
338
+ *
339
+ * @param {object} engine the loaded engine (bin/engine.mjs)
340
+ * @param {object} store the result of `openStore`
341
+ * @param {{keys?: string[], sparql?: string}} options
342
+ * @returns {StoreHandle}
343
+ */
344
+ export function openStoreHandle (engine, store, options = {}) {
345
+ let keys
346
+ if (Array.isArray(options.keys)) {
347
+ keys = options.keys
348
+ } else if (typeof options.sparql === 'string') {
349
+ keys = planQuery(engine, store, options.sparql).keys
350
+ } else {
351
+ keys = manifestKeys(engine, store)
352
+ }
353
+ const { blob, artifacts } = regionOf(store, keys)
354
+ let envelope
355
+ try {
356
+ envelope = engine.callBlobIO('storeOpen',
357
+ [store.manifestHex, JSON.stringify(artifacts)], blob).envelope
358
+ } catch (error) {
359
+ throw asStoreError(error)
360
+ }
361
+ return new StoreHandle(engine, store, envelope)
362
+ }
363
+
364
+ /** `storeHandleQuery` — the loose form, for a caller holding only the id. */
365
+ export function queryStoreHandle (engine, handle, sparql) {
366
+ const id = typeof handle === 'string' ? handle : handle.handle
367
+ try {
368
+ return engine.call('storeHandleQuery', [id, sparql])
369
+ } catch (error) {
370
+ throw asStoreError(error)
371
+ }
372
+ }
373
+
374
+ /** `storeHandleClose` — the loose form. */
375
+ export function closeStoreHandle (engine, handle) {
376
+ if (handle instanceof StoreHandle) return handle.close()
377
+ try {
378
+ return engine.call('storeHandleClose', [handle])
379
+ } catch (error) {
380
+ throw asStoreError(error)
381
+ }
382
+ }
383
+
384
+ /**
385
+ * `storeHandleList` — what this process holds open, with the retained bytes
386
+ * and rows and the two residency caps. A server that cannot see its own
387
+ * residency cannot be operated.
388
+ */
389
+ export function listStoreHandles (engine) {
390
+ try {
391
+ return engine.call('storeHandleList', [])
392
+ } catch (error) {
393
+ throw asStoreError(error)
394
+ }
395
+ }
@@ -37,7 +37,7 @@
37
37
  // bytes change.
38
38
 
39
39
  // Stamped by formal/lean4/Wasm/build-wasm.sh step 9 -- do not hand-edit.
40
- const WASM_VERSION = "125d391e0ccc";
40
+ const WASM_VERSION = "ffbb3a035705";
41
41
 
42
42
  import createModule from './l4factoidal.mjs';
43
43
 
Binary file
@@ -2,13 +2,13 @@
2
2
  "engine": "lean4",
3
3
  "note": "The Lean 4 (L4Factoidal) engine's own build provenance and claims -- distinct from ../version.json, which is the F* engine's. Do not merge the two: they come from different toolchains, different git SHAs in general, and carry different proof obligations (issue #618).",
4
4
  "version": "0.1.0",
5
- "gitSha": "6b9f12003805f5bc1136949b12dc6b1f41885d00",
6
- "builtAt": "2026-09-03T23:23:57+00:00",
5
+ "gitSha": "6613042b8acf3be78541f862f3ad0caf057cbab3",
6
+ "builtAt": "2026-09-04T20:56:42+00:00",
7
7
  "leanToolchain": "leanprover/lean4:v4.33.1",
8
8
  "emscripten": "emcc (Emscripten gcc/clang-like replacement + linker emulating GNU ld) 6.0.8-git",
9
9
  "abiVersion": "1",
10
- "wasmSha256": "125d391e0ccc7be1a673d748d40b8443fd66bb5f0a6e3736223fd6ed19307d03",
11
- "wasmBytes": 5296153,
10
+ "wasmSha256": "ffbb3a0357051f78c2e9135ae629fc04a46037d540501f7fadd3d3e0e96c96b4",
11
+ "wasmBytes": 5412682,
12
12
  "claims": {
13
13
  "source": "formal/lean4 (L4Factoidal): no sorry, no user axioms, no native_decide; W3C behaviour pinned by build-time #guard",
14
14
  "suitesAtBuildSha": "see docs/test-results and formal/lean4/PORT_NOTES.md at gitSha"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@factoidal/core",
3
- "version": "0.4.0",
3
+ "version": "0.5.1",
4
4
  "description": "Factoidal: a linked information system with graph data and the Web at its heart - RDF parsing, SPARQL 1.1 query, canonicalization and entailment for Node and the browser (JS + Wasm), grounded in F* and Lean 4. Parser and algebra spec verified in F*; on-disk backend has unverified OCaml-side optimization layers being migrated back to F*.",
5
5
  "keywords": [
6
6
  "sparql",
@@ -79,6 +79,15 @@
79
79
  "./sample-store": {
80
80
  "types": "./sample-store.d.ts",
81
81
  "default": "./sample-store.mjs"
82
+ },
83
+ "./store": {
84
+ "default": "./bin/store.mjs"
85
+ },
86
+ "./pack": {
87
+ "default": "./bin/pack.mjs"
88
+ },
89
+ "./engine": {
90
+ "default": "./bin/engine.mjs"
82
91
  }
83
92
  },
84
93
  "files": [
package/version.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.4.0",
2
+ "version": "0.5.1",
3
3
  "gitSha": "49f8ca4d70bf57c12fea445611e2b01b067bf6ba",
4
4
  "builtAt": "2026-08-26T21:45:40Z",
5
5
  "claims": {