@factoidal/core 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +125 -0
- package/README.md +149 -1
- package/bin/engine.mjs +124 -0
- package/bin/factoidal.mjs +658 -0
- package/bin/store.mjs +206 -0
- package/factoidal-npm-entry.js +13657 -30789
- package/factoidal-npm-entry.wasm.assets/{code-7ac046580f1bbdda8dc6.wasm → code-5a7fc68f2ab1323718b8.wasm} +0 -0
- package/factoidal-npm-entry.wasm.js +2 -2
- package/factoidal.js +11448 -25890
- package/fn.js +39 -0
- package/l4-assets/l4factoidal.js +197 -0
- package/l4-assets/l4factoidal.mjs +2 -0
- package/l4-assets/l4factoidal.wasm +0 -0
- package/l4-assets/package.json +4 -0
- package/l4-assets/version.json +16 -0
- package/l4-core.d.ts +159 -0
- package/l4-core.js +188 -0
- package/l4.d.ts +50 -0
- package/l4.js +104 -0
- package/lib/api.js +162 -2
- package/package.json +30 -3
- package/select.d.ts +116 -0
- package/select.js +492 -0
- package/store-host/deno.mjs +199 -0
- package/store-host/errors.mjs +55 -0
- package/store-host/index.mjs +208 -0
- package/store-host/node.mjs +212 -0
- package/store-host/paths.mjs +77 -0
- package/version.json +24 -23
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,130 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.0 — 2026-09-03
|
|
4
|
+
|
|
5
|
+
- The `factoidal` command answers SPARQL against a persisted
|
|
6
|
+
Shardborough store with no native binary. `factoidal inspect STORE`
|
|
7
|
+
decodes the manifest through the engine's `storeManifestInspect`
|
|
8
|
+
operation and prints the wire version, layout, blank-node profile and
|
|
9
|
+
the entry table; `factoidal query STORE 'SELECT ...'` asks the engine
|
|
10
|
+
which artifacts the query needs, reads exactly those, and hands their
|
|
11
|
+
bytes to `storeQuery` through one WebAssembly heap buffer with no
|
|
12
|
+
encoding. Every artifact is verified against the SHA-256 the manifest
|
|
13
|
+
commits before an answer is given. Formats: `table`, `json`,
|
|
14
|
+
`nquads`, `turtle`; `--explain` prints the artifact plan. Runs under
|
|
15
|
+
Node and under Deno (`--allow-read`).
|
|
16
|
+
- The three caps of the store query operation — 64 artifacts, 8388608
|
|
17
|
+
artifact bytes, 100000 rows — are reported with the cap, the value
|
|
18
|
+
and a next step, and are decided before a single file is read.
|
|
19
|
+
Nothing is truncated.
|
|
20
|
+
- `pack`, `activate`, `update` and `compact` still exit 3; they need
|
|
21
|
+
operations that do not exist yet
|
|
22
|
+
(https://github.com/danbri/factoidal/issues/641).
|
|
23
|
+
- A large piped result is no longer truncated: the command sets
|
|
24
|
+
`process.exitCode` instead of calling `process.exit()`, which dropped
|
|
25
|
+
everything past the 64 KiB pipe boundary.
|
|
26
|
+
|
|
27
|
+
## 0.3.0 — Lean block-worker preview
|
|
28
|
+
|
|
29
|
+
- The bundled Lean-derived WASM artifact exports
|
|
30
|
+
`scanIBK2Predicate(ibk2Hex, predicateIri)` through `factoidal/l4`.
|
|
31
|
+
It validates a canonical IBK2 block and executes its selective predicate
|
|
32
|
+
scan, returning N-Triples plus a row count. This is a narrow physical
|
|
33
|
+
helper for the Shardborough work, not full SPARQL execution inside a storage
|
|
34
|
+
backend and not a high-throughput buffer ABI (the current hex transport is
|
|
35
|
+
intentionally diagnostic).
|
|
36
|
+
- The regenerated Lean artifact is synchronized across the package's
|
|
37
|
+
`l4-assets/` and the Hub build, and is exercised against the music IBK2
|
|
38
|
+
fixture by `tools/wasm-ibk2-smoke.mjs` in the repository.
|
|
39
|
+
- The current-format companion
|
|
40
|
+
`scanIBK3Predicate(ibk3Hex, predicateIri, blankNodeScope)` validates and
|
|
41
|
+
scans complete predicate-local IBK3 artifacts. Its mandatory source/dataset
|
|
42
|
+
scope preserves one blank node across blocks from the same RDF import while
|
|
43
|
+
keeping same-spelled labels in unrelated inputs apart. Hub post 51 composes
|
|
44
|
+
three real IBK3 scans into an editable Lean-WASM SPARQL query.
|
|
45
|
+
|
|
46
|
+
## 0.2.0 — both engines in one package, with a backend selector
|
|
47
|
+
|
|
48
|
+
- The Lean 4 engine (`L4Factoidal`, wasm) now ships INSIDE
|
|
49
|
+
`@factoidal/core` as `l4-assets/`, alongside the F\*-extracted
|
|
50
|
+
engine. One install gets both. `require('factoidal/l4')` and
|
|
51
|
+
`factoidal/l4-core` are unchanged; the resolver checks in-package
|
|
52
|
+
assets first, then falls through to the old order (companion
|
|
53
|
+
package, `$FACTOIDAL_L4_ASSETS`, repo checkout).
|
|
54
|
+
- `@factoidal/lean` is superseded and was never published.
|
|
55
|
+
- New subpath `factoidal/select`: a backend selector, per-instance
|
|
56
|
+
with per-call override. Values `lean`, `fstar`, `lean1st`,
|
|
57
|
+
`fstar1st`, `slowcompareboth`. A request naming exactly one engine
|
|
58
|
+
never gets an answer from the other -- `lean` and `fstar` throw on a
|
|
59
|
+
function that engine does not implement; `lean1st` / `fstar1st` fall
|
|
60
|
+
through, and `lean1st` also takes a list of functions to route to
|
|
61
|
+
F\* regardless. Every result carries the answering engine.
|
|
62
|
+
`slowcompareboth` runs both and REPORTS disagreement rather than
|
|
63
|
+
throwing; comparison is RDFC-1.0 isomorphism for Dataset-shaped
|
|
64
|
+
results and bag equality (blank nodes relabelled per side) for
|
|
65
|
+
SELECT bindings.
|
|
66
|
+
- Measured on the typed API surface at this release: 15 functions both
|
|
67
|
+
engines answer, 38 F\*-only, 4 Lean-only (out of 57). This measures
|
|
68
|
+
the typed wrapper surface, not the Lean engine's total capability --
|
|
69
|
+
`l4-core.js` wires 16 of the engine's 21 dispatch ops.
|
|
70
|
+
- `clParse` (Common Logic Interchange Format text, ISO/IEC 24707:2018,
|
|
71
|
+
with the IKL `that`-operator extension) is now wired into the typed
|
|
72
|
+
API -- `l4-core.js`/`lib/api.js`/`select.js`, with `.d.ts` types and
|
|
73
|
+
tests. It reads CLIF text and reports its shape (sentence count,
|
|
74
|
+
`pureCL` dialect flag, canonical re-serialisation); it never produces
|
|
75
|
+
RDF. `pureCL` is a DIALECT flag, not a validity or quality signal:
|
|
76
|
+
true while the text stays inside ISO/IEC 24707 Common Logic, false
|
|
77
|
+
once it uses IKL's `that` operator. It is the first Lean-only entry
|
|
78
|
+
on the typed capability table -- formal/fstar has no CL/IKL parser at
|
|
79
|
+
all, so `index.js`/`wasm.js` never export it, `factoidal/select`'s
|
|
80
|
+
`backend:'fstar'` throws for it (never falls back to Lean), and
|
|
81
|
+
`backend:'slowcompareboth'` fails the capability precondition rather
|
|
82
|
+
than comparing one side against nothing.
|
|
83
|
+
- `clSerialize`, `clAlphaNorm` and `clNormalize` join `clParse` on the
|
|
84
|
+
same Lean-only typed surface (owner instruction, 2026-08-26 -- "wire
|
|
85
|
+
into js functional api"), same `.d.ts`-typed, test-covered pattern:
|
|
86
|
+
`clSerialize` reads CLIF text and writes it back in canonical
|
|
87
|
+
spacing, surfacing `roundTripProved: false` unmodified (the
|
|
88
|
+
round-trip lemma `clif_roundTrip`, `CL/ClifAdequacy.lean`, is OPEN --
|
|
89
|
+
the fragment boundary `marksLexable` is measured, not proved).
|
|
90
|
+
`clAlphaNorm` gives each sentence's bound-variable-renaming canonical
|
|
91
|
+
form (IKL GUIDE Appendix B condition (1)). `clNormalize` is Hayes's
|
|
92
|
+
satisfiability-preserving reduction of IKL to Common Logic
|
|
93
|
+
([#625](https://github.com/danbri/factoidal/issues/625)): it
|
|
94
|
+
surfaces `preserves: "satisfiability"` (not equivalence -- suited to
|
|
95
|
+
entailment/consistency testing, not to transforming data you intend
|
|
96
|
+
to keep) and `noIntrusion` (the proof hypothesis `CL.noIntrSs [] []`
|
|
97
|
+
decides, not a paraphrase of it). `clFiniteSat`, the fifth CL/IKL op,
|
|
98
|
+
is DEFERRED rather than wired: it takes a caller-supplied finite-
|
|
99
|
+
interpretation JSON encoding with no user yet, and a typed wrapper
|
|
100
|
+
would freeze that shape before it is known to be right; it stays
|
|
101
|
+
reachable through the raw dispatch ABI.
|
|
102
|
+
- The IKL-to-RDF projection ops (`clToDataset`, `queryWithIklService`)
|
|
103
|
+
are DELETED from the engine source (danbri/factoidal#626), along with
|
|
104
|
+
the content-addressed proposition graph names they minted. They were
|
|
105
|
+
never exposed through the npm API. `x-ikl-*` entailment regimes are
|
|
106
|
+
still rejected at the JS layer, and the engine no longer defines the
|
|
107
|
+
family either. The two ops remain present in the compiled wasm
|
|
108
|
+
artifact until it is rebuilt (danbri/factoidal#627) -- the artifact
|
|
109
|
+
is ahead of its source. `clParse` is a different op (it never
|
|
110
|
+
produces RDF) and is unaffected.
|
|
111
|
+
|
|
112
|
+
## 0.2.0 — Lean 4 engine subpath
|
|
113
|
+
|
|
114
|
+
- New `factoidal/l4-core` subpath: the same typed API served by the
|
|
115
|
+
Lean 4-extracted wasm engine instead of the F\*-extracted one
|
|
116
|
+
([#476](https://github.com/danbri/factoidal/issues/476)). Engine
|
|
117
|
+
assets resolve from the `@factoidal/lean` companion package, the
|
|
118
|
+
`FACTOIDAL_L4_ASSETS` environment variable, or the repository
|
|
119
|
+
checkout, in that order. `capabilities()` reports the Lean engine's
|
|
120
|
+
actual surface; `shaclValidate`/`owlIsConsistent`/`owlEntails`
|
|
121
|
+
raise pinned errors rather than returning wrong answers
|
|
122
|
+
([#586](https://github.com/danbri/factoidal/issues/586) tracks the
|
|
123
|
+
OWL verdicts).
|
|
124
|
+
- `serialize({ format: "nquads" })` now normalizes through the entry
|
|
125
|
+
ABI instead of the CLI path.
|
|
126
|
+
|
|
127
|
+
|
|
3
128
|
## 0.1.0 — First published release (as `@factoidal/core`)
|
|
4
129
|
|
|
5
130
|
- SPARQL 1.1 §17.6 extension functions
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @factoidal/core
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Release 0.3.0. The API surface is early and may
|
|
4
4
|
> change before 1.0. This package was previously developed in-tree
|
|
5
5
|
> under the placeholder names `factoidal` and `@danbri/foafos`; it was
|
|
6
6
|
> never published under those names. See [CHANGELOG.md](CHANGELOG.md).
|
|
@@ -306,6 +306,154 @@ const tabular = await fromCsvw(csvText, csvwMetadataJson, { mode: 'minimal' });
|
|
|
306
306
|
const saturated = await rif(data, rifRulesXml); // RIF Core forward chaining
|
|
307
307
|
```
|
|
308
308
|
|
|
309
|
+
## Lean engine (new in 0.2.0)
|
|
310
|
+
|
|
311
|
+
Two engines now ship in one package. `factoidal` and `factoidal/wasm`
|
|
312
|
+
are the F\*-extracted engine, unchanged. The subpaths below are the
|
|
313
|
+
Lean 4 engine (`L4Factoidal`, compiled to wasm).
|
|
314
|
+
|
|
315
|
+
`factoidal/l4` also exposes two deliberately narrow physical helpers:
|
|
316
|
+
`scanIBK2Predicate(ibk2Hex, predicateIri)` for the predecessor format and
|
|
317
|
+
`scanIBK3Predicate(ibk3Hex, predicateIri, blankNodeScope)` for the current
|
|
318
|
+
predicate-local format. They validate one canonical RDF block and scan its
|
|
319
|
+
named predicate, returning N-Triples and a row count. The IBK3 source scope
|
|
320
|
+
must be shared across blocks partitioned from one RDF import unit and differ
|
|
321
|
+
across unrelated units; this preserves document-scoped blank-node identity
|
|
322
|
+
when fragments are composed. The hexadecimal argument is a portable
|
|
323
|
+
diagnostic ABI, not the intended high-throughput buffer interface.
|
|
324
|
+
|
|
325
|
+
```js
|
|
326
|
+
const l4 = require('factoidal/l4-core'); // Lean engine, same API shape
|
|
327
|
+
const { select } = require('factoidal/select'); // choose an engine per call
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### `factoidal/l4-core`
|
|
331
|
+
|
|
332
|
+
Same call shape as the main API — `parse`, `query`, `update`,
|
|
333
|
+
`serialize`, `canonicalize`, `graphs`, `canonicalHash`, `owlClosure`,
|
|
334
|
+
`coreRdfsClosure`, `coreRdfsCheck`, `rhoDfClosure`,
|
|
335
|
+
`rhoDfFragmentCheck`, `rdfsPlusClosure`, `owlIsConsistent` — plus four
|
|
336
|
+
Common Logic / IKL operations that exist only here, because the F\* tree
|
|
337
|
+
has no CL parser.
|
|
338
|
+
|
|
339
|
+
| Function | In → out | What the answer is worth |
|
|
340
|
+
|---|---|---|
|
|
341
|
+
| `clParse(clifText)` | CLIF text → shape report | sentence count, CL-vs-IKL dialect, canonical re-serialisation. Reads CLIF; never produces RDF |
|
|
342
|
+
| `clSerialize(clifText)` | CLIF → CLIF | canonical writer. Returns `roundTripProved: false` — `clif_roundTrip` is an open lemma and the fragment boundary is measured, not proved |
|
|
343
|
+
| `clAlphaNorm(clifText)` | CLIF → CLIF | alpha-equivalence canonical form (IKL Appendix B condition 1) |
|
|
344
|
+
| `clNormalize(clifText)` | CLIF → CLIF | Hayes's IKL-to-CL reduction. Returns `preserves: "satisfiability"` — **not** equivalence — and `noIntrusion`, the proof hypothesis decided rather than assumed |
|
|
345
|
+
|
|
346
|
+
A fifth op, `clFiniteSat(interpJson, clifText)`, is reachable only
|
|
347
|
+
through the raw dispatch ABI: `l4.call('clFiniteSat', [interpJson,
|
|
348
|
+
clifText])`. It is not in the typed layer.
|
|
349
|
+
|
|
350
|
+
### `factoidal/select`
|
|
351
|
+
|
|
352
|
+
Per-instance backend choice with a per-call override.
|
|
353
|
+
|
|
354
|
+
| Value | Behaviour |
|
|
355
|
+
|---|---|
|
|
356
|
+
| `lean` / `fstar` | that engine only; **throws** if it does not implement the function |
|
|
357
|
+
| `lean1st` / `fstar1st` | prefer that engine, fall through to the other for unimplemented functions |
|
|
358
|
+
| `slowcompareboth` | run both and **report** disagreement rather than throwing |
|
|
359
|
+
|
|
360
|
+
Every result names the engine that answered. `capabilityTable()`
|
|
361
|
+
returns which functions each engine implements.
|
|
362
|
+
|
|
363
|
+
### What this surface is, honestly
|
|
364
|
+
|
|
365
|
+
Four `String → String` operations plus one through raw dispatch. The
|
|
366
|
+
Lean tree behind them is larger than that — the CL/IKL and unified
|
|
367
|
+
model-theory modules run to about 22,000 lines — but only these reach
|
|
368
|
+
JavaScript today. Everything else in the Lean tree is used through
|
|
369
|
+
`parse`/`query`/`closure`, or not exposed at all.
|
|
370
|
+
|
|
371
|
+
## The `factoidal` command: querying a persisted store
|
|
372
|
+
|
|
373
|
+
Installing this package puts a `factoidal` command on PATH. It reads a
|
|
374
|
+
**Shardborough** store — the on-disk format the Lean `l4block-*` tools
|
|
375
|
+
write — with no native binary: JavaScript reads the files and moves the
|
|
376
|
+
bytes, and the Lean engine running as WebAssembly makes every format
|
|
377
|
+
decision (parsing the manifest, choosing the blocks, verifying their
|
|
378
|
+
SHA-256, evaluating the SPARQL).
|
|
379
|
+
|
|
380
|
+
> This command is not the native F\* `factoidal` binary that the API
|
|
381
|
+
> table below refers to. That one is `bin/<platform>/factoidal` in the
|
|
382
|
+
> repository and takes subcommands such as `shex` and `compact`. This
|
|
383
|
+
> one takes `version`, `inspect` and `query`.
|
|
384
|
+
|
|
385
|
+
```console
|
|
386
|
+
$ factoidal inspect ./mystore
|
|
387
|
+
store ./mystore
|
|
388
|
+
generation gen-1 (activated through CURRENT)
|
|
389
|
+
manifest manifest.sbm2, 2372 bytes, wire version 6
|
|
390
|
+
layout predicate-ibk3-ptd1-sri2-tli1-oli2-merkle-v0
|
|
391
|
+
blank-node profile (none recorded)
|
|
392
|
+
term registry local-ibk3-ptd1-v0
|
|
393
|
+
fixed-chunk Merkle commitment yes
|
|
394
|
+
5 entries, 393775 bytes, 6455 rows
|
|
395
|
+
generation directory holds 42 files, 846592 bytes
|
|
396
|
+
|
|
397
|
+
# rows bytes kind graphs predicate
|
|
398
|
+
0 1800 110085 IBK3 - http://www.wikidata.org/prop/direct/P31
|
|
399
|
+
1 719 35535 IBK3 - http://www.wikidata.org/prop/direct/P361
|
|
400
|
+
...
|
|
401
|
+
|
|
402
|
+
$ factoidal query ./mystore 'SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }'
|
|
403
|
+
mode ibk3-paged-merkle-full-manifest(5), 5 artifacts, 393775 bytes read, plan declares 6455 block rows
|
|
404
|
+
n
|
|
405
|
+
"6455"^^<http://www.w3.org/2001/XMLSchema#integer>
|
|
406
|
+
1 row
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
`STORE` is a collection root: the directory holding `CURRENT`. The plan
|
|
410
|
+
line goes to stderr, so stdout carries only the result; `--quiet`
|
|
411
|
+
removes it.
|
|
412
|
+
|
|
413
|
+
| Option | What it does |
|
|
414
|
+
|---|---|
|
|
415
|
+
| `--format table` | default; a human display of the results |
|
|
416
|
+
| `--format json` | SELECT prints the engine's SPARQL 1.1 Query Results JSON; ASK and CONSTRUCT print the operation's envelope |
|
|
417
|
+
| `--format nquads` | CONSTRUCT only: the graph the engine serialized |
|
|
418
|
+
| `--format turtle` | CONSTRUCT only: that graph through the engine's own Turtle writer |
|
|
419
|
+
| `--explain` | print the artifacts the query needs and the open mode, and stop |
|
|
420
|
+
| `--limit N` | print at most N table rows; the total is always named |
|
|
421
|
+
| `--file PATH` | read the query text from a file |
|
|
422
|
+
| `--generation NAME` | read that generation rather than the activated one |
|
|
423
|
+
|
|
424
|
+
Under Deno, run the file directly; `inspect` and `query` need only
|
|
425
|
+
`--allow-read`:
|
|
426
|
+
|
|
427
|
+
```console
|
|
428
|
+
$ deno run --allow-read node_modules/@factoidal/core/bin/factoidal.mjs query ./mystore 'ASK { ?s ?p ?o }'
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
### What the command answers for, and what it does not
|
|
432
|
+
|
|
433
|
+
* **Every artifact is verified.** The engine refuses the whole query
|
|
434
|
+
when a block's bytes do not hash to the SHA-256 the manifest commits,
|
|
435
|
+
and names the artifact.
|
|
436
|
+
* **Three caps.** One call reads at most 64 artifacts, 8388608 artifact
|
|
437
|
+
bytes and 100000 rows. A query over any of them is refused before a
|
|
438
|
+
single file is read, with the cap and the value named. Nothing is
|
|
439
|
+
truncated.
|
|
440
|
+
* **Committed artifacts only.** A store carrying uncompacted delta-log
|
|
441
|
+
updates is not served by this path; use the native `l4block-*` tools.
|
|
442
|
+
* **`pack`, `activate`, `update` and `compact` exit 3.** They need
|
|
443
|
+
WebAssembly operations that do not exist yet
|
|
444
|
+
(https://github.com/danbri/factoidal/issues/641).
|
|
445
|
+
* **Node's WebAssembly frame budget.** Some evaluator paths recurse once
|
|
446
|
+
per row. Measured 2026-09-03 on a 6455-row store, `SELECT ?s ?p ?o
|
|
447
|
+
WHERE { ?s ?p ?o }` overflows the stack under Node's default while
|
|
448
|
+
`SELECT *`, or the same query with a `LIMIT`, does not, and Deno
|
|
449
|
+
clears all of them. The command reports it and exits 1 rather than
|
|
450
|
+
crashing; `node --stack-size=4000` clears it.
|
|
451
|
+
|
|
452
|
+
Measured 2026-09-03 on macOS arm64, the 6455-triple `sequence_variant`
|
|
453
|
+
store, `SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }`, whole process
|
|
454
|
+
including start-up: 220 ms through this command, 33 ms through the
|
|
455
|
+
native `l4block-id-v3-query`.
|
|
456
|
+
|
|
309
457
|
## API (draft)
|
|
310
458
|
|
|
311
459
|
The `factoidal` CLI (`bin/factoidal-cli/factoidal_cli.ml`, built to
|
package/bin/engine.mjs
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Loading the Lean engine (L4Factoidal compiled to WebAssembly) for the
|
|
2
|
+
// `factoidal` command, under Node and under Deno.
|
|
3
|
+
// https://github.com/danbri/factoidal/issues/641
|
|
4
|
+
//
|
|
5
|
+
// This file resolves three files that must stay together and keep their
|
|
6
|
+
// names -- l4factoidal.js, l4factoidal.mjs and l4factoidal.wasm -- and
|
|
7
|
+
// imports the loader. The Emscripten glue resolves the .wasm sidecar from
|
|
8
|
+
// its own basename, so a rename breaks the load on every runtime
|
|
9
|
+
// (skills/lean4-wasm-export, "the naming trap").
|
|
10
|
+
//
|
|
11
|
+
// `npm/factoidal/l4.js` does the same resolution for the CommonJS API. It
|
|
12
|
+
// is not reused here because it is CommonJS and this command must load
|
|
13
|
+
// under Deno without the require() compatibility path.
|
|
14
|
+
//
|
|
15
|
+
// WHAT THIS FILE IS ALLOWED TO DO
|
|
16
|
+
// Find the engine, load it, and turn bytes into the hexadecimal string
|
|
17
|
+
// two of the three store operations take as their small argument. It
|
|
18
|
+
// makes no format decision: it never parses a manifest, verifies a
|
|
19
|
+
// digest, decodes a block, or chooses an artifact.
|
|
20
|
+
|
|
21
|
+
import { readWhole } from '../store-host/index.mjs'
|
|
22
|
+
import { fileUrlToPath, joinPath } from '../store-host/paths.mjs'
|
|
23
|
+
|
|
24
|
+
/** Read one environment variable, or null where reading it is refused. */
|
|
25
|
+
function environment (name) {
|
|
26
|
+
try {
|
|
27
|
+
if (typeof globalThis.Deno !== 'undefined' && globalThis.Deno.env) {
|
|
28
|
+
const value = globalThis.Deno.env.get(name)
|
|
29
|
+
return typeof value === 'string' && value.length > 0 ? value : null
|
|
30
|
+
}
|
|
31
|
+
} catch (_error) {
|
|
32
|
+
// Deno without --allow-env. An absent override is the normal case.
|
|
33
|
+
return null
|
|
34
|
+
}
|
|
35
|
+
const value = globalThis.process && globalThis.process.env
|
|
36
|
+
? globalThis.process.env[name]
|
|
37
|
+
: undefined
|
|
38
|
+
return typeof value === 'string' && value.length > 0 ? value : null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readable (path) {
|
|
42
|
+
try {
|
|
43
|
+
readWhole(path)
|
|
44
|
+
return true
|
|
45
|
+
} catch (_error) {
|
|
46
|
+
return false
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** The package directory that holds this command. */
|
|
51
|
+
export function packageDirectory () {
|
|
52
|
+
return fileUrlToPath(new URL('..', import.meta.url).href).replace(/\/$/, '')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The loader file of the first engine source that exists, or null.
|
|
57
|
+
*
|
|
58
|
+
* Order, first hit wins:
|
|
59
|
+
* 1. this package's own l4-assets/ (what `npm install` gives);
|
|
60
|
+
* 2. $FACTOIDAL_L4_ASSETS (a custom deployment);
|
|
61
|
+
* 3. the repository checkout layout, docs/web/hub/assets/l4/.
|
|
62
|
+
*/
|
|
63
|
+
export function resolveEngine () {
|
|
64
|
+
const inPackage = joinPath(packageDirectory(), 'l4-assets/l4factoidal.js')
|
|
65
|
+
if (readable(inPackage)) return inPackage
|
|
66
|
+
const override = environment('FACTOIDAL_L4_ASSETS')
|
|
67
|
+
if (override !== null) {
|
|
68
|
+
const path = joinPath(override, 'l4factoidal.js')
|
|
69
|
+
if (readable(path)) return path
|
|
70
|
+
throw new Error(`FACTOIDAL_L4_ASSETS=${override} holds no l4factoidal.js`)
|
|
71
|
+
}
|
|
72
|
+
const inCheckout = joinPath(packageDirectory(),
|
|
73
|
+
'../../docs/web/hub/assets/l4/l4factoidal.js')
|
|
74
|
+
if (readable(inCheckout)) return inCheckout
|
|
75
|
+
return null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let enginePromise = null
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Load the engine once and return its handle: `call(op, args)` and
|
|
82
|
+
* `callBlob(op, args, bytes)`.
|
|
83
|
+
*/
|
|
84
|
+
export function loadEngine () {
|
|
85
|
+
if (enginePromise !== null) return enginePromise
|
|
86
|
+
enginePromise = (async () => {
|
|
87
|
+
const loaderPath = resolveEngine()
|
|
88
|
+
if (loaderPath === null) {
|
|
89
|
+
throw new Error(
|
|
90
|
+
'the Lean engine assets are missing. They normally ship in this ' +
|
|
91
|
+
"package's own l4-assets/ directory; if it is absent this install " +
|
|
92
|
+
'is incomplete. Otherwise set FACTOIDAL_L4_ASSETS to a directory ' +
|
|
93
|
+
'holding l4factoidal.js, l4factoidal.mjs and l4factoidal.wasm.')
|
|
94
|
+
}
|
|
95
|
+
const url = loaderPath.startsWith('file://')
|
|
96
|
+
? loaderPath
|
|
97
|
+
: 'file://' + encodeURI(loaderPath).replace(/#/g, '%23')
|
|
98
|
+
const module = await import(url)
|
|
99
|
+
return module.loadL4()
|
|
100
|
+
})()
|
|
101
|
+
return enginePromise
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const HEX = (() => {
|
|
105
|
+
const table = new Array(256)
|
|
106
|
+
for (let byte = 0; byte < 256; byte += 1) {
|
|
107
|
+
table[byte] = byte.toString(16).padStart(2, '0')
|
|
108
|
+
}
|
|
109
|
+
return table
|
|
110
|
+
})()
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Lowercase hexadecimal of a byte array. This is a transport encoding for
|
|
114
|
+
* the manifest argument of `storeManifestInspect` and `storeQueryPlan`,
|
|
115
|
+
* not an interpretation of the bytes. Artifact bytes never take this
|
|
116
|
+
* path: they cross raw through `callBlob`.
|
|
117
|
+
* @param {Uint8Array} bytes
|
|
118
|
+
* @returns {string}
|
|
119
|
+
*/
|
|
120
|
+
export function hexOfBytes (bytes) {
|
|
121
|
+
const parts = new Array(bytes.length)
|
|
122
|
+
for (let index = 0; index < bytes.length; index += 1) parts[index] = HEX[bytes[index]]
|
|
123
|
+
return parts.join('')
|
|
124
|
+
}
|