@factoidal/core 0.7.1 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +616 -1
- package/NOTICE +2 -21
- package/README.md +123 -993
- package/api.d.ts +53 -0
- package/api.js +5 -0
- package/api.mjs +56 -0
- package/bin/engine.mjs +38 -19
- package/bin/ext.mjs +16 -20
- package/bin/factoidal.mjs +667 -118
- package/bin/pack.mjs +37 -0
- package/bin/serve-worker.mjs +32 -0
- package/bin/serve.mjs +423 -0
- package/bin/store-worker-host.mjs +1 -1
- package/bin/store.mjs +1 -1
- package/browser.d.ts +114 -507
- package/browser.js +155 -2257
- package/docs/fuseki-replacement.md +108 -0
- package/fn.d.ts +25 -96
- package/fn.js +131 -240
- package/index.d.ts +620 -260
- package/index.js +4 -234
- package/index.mjs +180 -84
- package/l4-assets/l4factoidal.js +47 -7
- package/l4-assets/l4factoidal.mjs +1 -1
- package/l4-assets/l4factoidal.wasm +0 -0
- package/l4-assets/version.json +11 -7
- package/lib/api.mjs +2337 -0
- package/lib/browser-engine.mjs +69 -0
- package/lib/dataset.mjs +471 -0
- package/lib/delta-log-hosts.mjs +191 -0
- package/lib/lean-entry.mjs +213 -0
- package/lib/pure.mjs +98 -0
- package/lib/store.mjs +115 -0
- package/lib/version.mjs +5 -0
- package/lws/README.md +137 -0
- package/lws/persist.mjs +78 -0
- package/lws/server.mjs +416 -0
- package/package.json +59 -61
- package/rdfjs.js +5 -536
- package/rdfjs.mjs +517 -0
- package/sample-store.d.ts +8 -16
- package/sample-store.mjs +39 -29
- package/solid/client/README.md +100 -0
- package/solid/client/index.mjs +293 -0
- package/solid/server/README.md +96 -0
- package/solid/server/index.mjs +395 -0
- package/store-host/deno.mjs +8 -0
- package/store-host/index.mjs +12 -0
- package/store-host/node.mjs +8 -0
- package/xmpp/README.md +107 -0
- package/xmpp/index.mjs +229 -0
- package/browser-wasm.js +0 -872
- package/factoidal-npm-entry.js +0 -15477
- package/factoidal-npm-entry.wasm.assets/code-5a7fc68f2ab1323718b8.wasm +0 -0
- package/factoidal-npm-entry.wasm.js +0 -455
- package/factoidal.js +0 -13118
- package/factoidal.wasm.assets/code-bbe6099bfb5b10c4c3ab.wasm +0 -0
- package/factoidal.wasm.js +0 -457
- package/hacl-init.js +0 -92
- package/hacl-wasm/FStar.wasm +0 -0
- package/hacl-wasm/Hacl_Bignum.wasm +0 -0
- package/hacl-wasm/Hacl_Bignum25519_51.wasm +0 -0
- package/hacl-wasm/Hacl_Bignum_Base.wasm +0 -0
- package/hacl-wasm/Hacl_Curve25519_51.wasm +0 -0
- package/hacl-wasm/Hacl_Ed25519.wasm +0 -0
- package/hacl-wasm/Hacl_Ed25519_PrecompTable.wasm +0 -0
- package/hacl-wasm/Hacl_Hash_Base.wasm +0 -0
- package/hacl-wasm/Hacl_Hash_SHA2.wasm +0 -0
- package/hacl-wasm/Hacl_IntTypes_Intrinsics.wasm +0 -0
- package/hacl-wasm/LowStar_Endianness.wasm +0 -0
- package/hacl-wasm/WasmSupport.wasm +0 -0
- package/hacl-wasm/api.js +0 -775
- package/hacl-wasm/api.json +0 -3787
- package/hacl-wasm/layouts.json +0 -1
- package/hacl-wasm/loader.js +0 -568
- package/hacl-wasm/shell.js +0 -12
- package/l4-core.d.ts +0 -159
- package/l4-core.js +0 -188
- package/l4.d.ts +0 -50
- package/l4.js +0 -104
- package/lib/api.js +0 -2300
- package/lib/engine-js.js +0 -165
- package/lib/engine-wasm.js +0 -300
- package/sample-store/CURRENT +0 -1
- package/sample-store/gen-1/manifest.sbm2 +0 -0
- package/sample-store/gen-1/manifest.tsv +0 -14
- package/sample-store/gen-1/predicate-0.ibk3 +0 -0
- package/sample-store/gen-1/predicate-0.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-0.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-0.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-0.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-0.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-0.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-0.ibk3.tli1.merkle +0 -0
- package/sample-store/gen-1/predicate-1.ibk3 +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.merkle +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.oli2.merkle +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-1.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-1.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-10.ibk3 +0 -0
- package/sample-store/gen-1/predicate-10.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-10.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-10.ibk3.oli2.merkle +0 -0
- package/sample-store/gen-1/predicate-10.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-10.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-10.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-10.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-11.ibk3 +0 -0
- package/sample-store/gen-1/predicate-11.ibk3.merkle +0 -0
- package/sample-store/gen-1/predicate-11.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-11.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-11.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-11.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-11.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-11.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-12.ibk3 +0 -0
- package/sample-store/gen-1/predicate-12.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-12.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-12.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-12.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-12.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-12.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-12.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-2.ibk3 +0 -0
- package/sample-store/gen-1/predicate-2.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-2.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-2.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-2.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-2.ibk3.sri2.merkle +0 -0
- package/sample-store/gen-1/predicate-2.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-2.ibk3.tli1.merkle +0 -0
- package/sample-store/gen-1/predicate-3.ibk3 +0 -0
- package/sample-store/gen-1/predicate-3.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-3.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-3.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-3.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-3.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-3.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-3.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-4.ibk3 +0 -0
- package/sample-store/gen-1/predicate-4.ibk3.merkle +0 -0
- package/sample-store/gen-1/predicate-4.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-4.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-4.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-4.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-4.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-4.ibk3.tli1.merkle +0 -2
- package/sample-store/gen-1/predicate-5.ibk3 +0 -0
- package/sample-store/gen-1/predicate-5.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-5.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-5.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-5.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-5.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-5.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-5.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-6.ibk3 +0 -0
- package/sample-store/gen-1/predicate-6.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-6.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-6.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-6.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-6.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-6.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-6.ibk3.tli1.merkle +0 -0
- package/sample-store/gen-1/predicate-7.ibk3 +0 -0
- package/sample-store/gen-1/predicate-7.ibk3.merkle +0 -0
- package/sample-store/gen-1/predicate-7.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-7.ibk3.oli2.merkle +0 -2
- package/sample-store/gen-1/predicate-7.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-7.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-7.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-7.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-8.ibk3 +0 -0
- package/sample-store/gen-1/predicate-8.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-8.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-8.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-8.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-8.ibk3.sri2.merkle +0 -1
- package/sample-store/gen-1/predicate-8.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-8.ibk3.tli1.merkle +0 -1
- package/sample-store/gen-1/predicate-9.ibk3 +0 -0
- package/sample-store/gen-1/predicate-9.ibk3.merkle +0 -1
- package/sample-store/gen-1/predicate-9.ibk3.oli2 +0 -0
- package/sample-store/gen-1/predicate-9.ibk3.oli2.merkle +0 -1
- package/sample-store/gen-1/predicate-9.ibk3.sri2 +0 -0
- package/sample-store/gen-1/predicate-9.ibk3.sri2.merkle +0 -3
- package/sample-store/gen-1/predicate-9.ibk3.tli1 +0 -0
- package/sample-store/gen-1/predicate-9.ibk3.tli1.merkle +0 -2
- package/select.d.ts +0 -116
- package/select.js +0 -492
- package/version.json +0 -102
- package/wasm.d.ts +0 -130
- package/wasm.js +0 -158
package/bin/factoidal.mjs
CHANGED
|
@@ -20,14 +20,14 @@
|
|
|
20
20
|
// `--format turtle` all print documents the engine produced.
|
|
21
21
|
|
|
22
22
|
import {
|
|
23
|
-
StoreHostError, atomicReplace, listGeneration, makeDirectory, readWhole,
|
|
23
|
+
StoreHostError, atomicReplace, listGeneration, makeDirectory, readChunk, readWhole,
|
|
24
24
|
runtime
|
|
25
25
|
} from '../store-host/index.mjs'
|
|
26
26
|
import { fileUrlToPath, joinPath } from '../store-host/paths.mjs'
|
|
27
|
-
import { loadEngine } from './engine.mjs'
|
|
28
|
-
import { sampleStoreFacts, sampleStorePath } from '../sample-store.mjs'
|
|
27
|
+
import { hexOfBytes, loadEngine } from './engine.mjs'
|
|
29
28
|
import { PackError, packSupported, verifyGeneration } from './pack.mjs'
|
|
30
29
|
import { denoReexec, isStackOverflow, runPack } from './pack-host.mjs'
|
|
30
|
+
import { SAMPLE_TURTLE, sampleStoreFacts } from '../sample-store.mjs'
|
|
31
31
|
import {
|
|
32
32
|
planQueryOnWorker, queryStoreOnWorker, workerRouteAvailable
|
|
33
33
|
} from './store-worker-host.mjs'
|
|
@@ -46,23 +46,9 @@ const EXIT_NOT_WIRED = 3
|
|
|
46
46
|
const FEED_PROGRESS = 65536
|
|
47
47
|
|
|
48
48
|
const PACK_LAYOUTS = ['ibk3', 'ibk4', 'ibk5']
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
['.trig', 'trig'],
|
|
53
|
-
['.nq', 'nquads'], ['.nquads', 'nquads'],
|
|
54
|
-
['.nt', 'ntriples'], ['.ntriples', 'ntriples']
|
|
55
|
-
]
|
|
56
|
-
|
|
57
|
-
// Suffixes the engine parses elsewhere but the packer's streaming fold does
|
|
58
|
-
// not read. Naming them is better than "cannot tell the syntax from its
|
|
59
|
-
// name", which sends the reader looking for a --syntax value that does not
|
|
60
|
-
// exist.
|
|
61
|
-
const PACK_UNSUPPORTED_SUFFIXES = [
|
|
62
|
-
['.rdf', 'RDF/XML'], ['.owl', 'RDF/XML'], ['.xml', 'RDF/XML'],
|
|
63
|
-
['.jsonld', 'JSON-LD'], ['.json', 'JSON-LD'], ['.n3', 'Notation3'],
|
|
64
|
-
['.csv', 'CSV'], ['.tsv', 'TSV'], ['.hdt', 'HDT']
|
|
65
|
-
]
|
|
49
|
+
// The syntaxes the packer reads and the file-name suffixes of each are the
|
|
50
|
+
// engine's tables (op engineTables: packSyntaxes, packSuffixes,
|
|
51
|
+
// packUnsupportedSuffixes), https://github.com/danbri/factoidal/issues/894.
|
|
66
52
|
|
|
67
53
|
/** The parent of a path, and its last component. The `activate` hint
|
|
68
54
|
* printed after a pack needs both; neither is a format decision. */
|
|
@@ -91,7 +77,8 @@ function exit (code) {
|
|
|
91
77
|
else process.exitCode = code
|
|
92
78
|
}
|
|
93
79
|
|
|
94
|
-
|
|
80
|
+
let outSilenced = false
|
|
81
|
+
function out (line) { if (!outSilenced) console.log(line) }
|
|
95
82
|
function err (line) { console.error(line) }
|
|
96
83
|
|
|
97
84
|
// ---------------------------------------------------------------- usage
|
|
@@ -102,13 +89,18 @@ usage: factoidal <command> [options]
|
|
|
102
89
|
|
|
103
90
|
commands:
|
|
104
91
|
version print the package and engine versions
|
|
105
|
-
sample-store
|
|
92
|
+
sample-store [DIR] pack a small built-in store and print its path
|
|
106
93
|
inspect STORE report what the activated manifest commits
|
|
107
94
|
query STORE [QUERY] evaluate a SPARQL query against a store
|
|
108
95
|
pack INPUT OUTPUT build one immutable generation from an RDF file
|
|
109
96
|
activate STORE GENERATION make one generation the activated generation
|
|
110
97
|
update STORE [UPDATE] apply a SPARQL Update through the delta log
|
|
111
98
|
compact STORE GENERATION fold the delta log into a new generation
|
|
99
|
+
serve STORE serve SPARQL 1.1 Protocol, Graph Store and
|
|
100
|
+
Service Description over HTTP
|
|
101
|
+
lws-serve DIR serve Linked Web Storage 1.0 over HTTP
|
|
102
|
+
solid-serve DIR serve the Solid Protocol over HTTP
|
|
103
|
+
solid-client OP URL run one Solid client operation
|
|
112
104
|
|
|
113
105
|
global options:
|
|
114
106
|
-h, --help print this text, or a command's own help
|
|
@@ -118,33 +110,30 @@ global options:
|
|
|
118
110
|
exit codes:
|
|
119
111
|
0 success 1 failure 2 usage error 3 not yet wired (${ISSUE})
|
|
120
112
|
|
|
121
|
-
STORE is a collection root: the directory that holds CURRENT.
|
|
122
|
-
|
|
113
|
+
STORE is a collection root: the directory that holds CURRENT. Make one from
|
|
114
|
+
an RDF file with pack and activate, or try a small built-in one:
|
|
123
115
|
|
|
124
116
|
factoidal query "$(factoidal sample-store)" 'SELECT * WHERE { ?s ?p ?o } LIMIT 5'`
|
|
125
117
|
|
|
126
118
|
const COMMAND_USAGE = {
|
|
127
|
-
|
|
119
|
+
'sample-store': `factoidal sample-store - pack a small built-in store, print its path
|
|
128
120
|
|
|
129
|
-
usage: factoidal
|
|
121
|
+
usage: factoidal sample-store [DIR] [--json]
|
|
130
122
|
|
|
131
|
-
|
|
132
|
-
|
|
123
|
+
Packs the few triples held in sample-store.mjs (no data file ships) into a
|
|
124
|
+
store under DIR, or under the OS temporary directory, activates it, and
|
|
125
|
+
prints the collection root. A DIR that already holds CURRENT is reused.
|
|
133
126
|
|
|
134
|
-
|
|
127
|
+
factoidal query "$(factoidal sample-store)" \\
|
|
128
|
+
'SELECT ?c ?l WHERE { ?c <http://www.w3.org/2004/02/skos/core#prefLabel> ?l }'`,
|
|
135
129
|
|
|
136
|
-
|
|
130
|
+
version: `factoidal version - print the package and engine versions
|
|
137
131
|
|
|
138
|
-
|
|
139
|
-
carries, so a fresh install can query something at once:
|
|
132
|
+
usage: factoidal version [--json]
|
|
140
133
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
'SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }'
|
|
134
|
+
Prints the npm package version, the Lean engine's WebAssembly digest as
|
|
135
|
+
recorded by its build, and which host-I/O implementation is loaded.`,
|
|
144
136
|
|
|
145
|
-
The store holds five IPTC NewsCodes vocabularies (CC BY 4.0; see NOTICE)
|
|
146
|
-
packed into IBK3 predicate blocks. --json adds what was recorded when it
|
|
147
|
-
was packed.`,
|
|
148
137
|
|
|
149
138
|
inspect: `factoidal inspect - report what a store's manifest commits
|
|
150
139
|
|
|
@@ -200,14 +189,16 @@ every later query from what it retained.
|
|
|
200
189
|
|
|
201
190
|
formats:
|
|
202
191
|
table a human display of the engine's SPARQL Query Results JSON;
|
|
203
|
-
ASK prints true or false, CONSTRUCT
|
|
192
|
+
ASK prints true or false, CONSTRUCT and DESCRIBE print their
|
|
193
|
+
N-Triples
|
|
204
194
|
json SELECT prints the engine's SPARQL 1.1 Query Results JSON
|
|
205
|
-
document; ASK and
|
|
206
|
-
because the operation answers those
|
|
207
|
-
serialized graph rather than with a results document
|
|
208
|
-
nquads CONSTRUCT only: the graph the engine serialized
|
|
209
|
-
turtle CONSTRUCT only: that graph through the engine's
|
|
210
|
-
writer, which flattens named graphs into the default
|
|
195
|
+
document; ASK, CONSTRUCT and DESCRIBE print the operation's
|
|
196
|
+
envelope, because the operation answers those with a boolean
|
|
197
|
+
or a serialized graph rather than with a results document
|
|
198
|
+
nquads CONSTRUCT and DESCRIBE only: the graph the engine serialized
|
|
199
|
+
turtle CONSTRUCT and DESCRIBE only: that graph through the engine's
|
|
200
|
+
own Turtle writer, which flattens named graphs into the default
|
|
201
|
+
graph
|
|
211
202
|
|
|
212
203
|
not available, and why:
|
|
213
204
|
--base IRI the store query operation takes no base argument;
|
|
@@ -215,8 +206,6 @@ not available, and why:
|
|
|
215
206
|
xml, csv, tsv the engine has no operation that writes the SPARQL
|
|
216
207
|
Results XML, CSV or TSV documents, and writing one
|
|
217
208
|
here would be a second serializer (iron rule 7)
|
|
218
|
-
DESCRIBE the engine answers "DESCRIBE is not supported by the
|
|
219
|
-
npm entry yet"
|
|
220
209
|
|
|
221
210
|
A store carrying a non-empty delta log is not served by this path: the
|
|
222
211
|
operation reads the manifest's committed artifacts only. Use the native
|
|
@@ -279,7 +268,186 @@ compacted epoch. It does not activate unless --activate is given.
|
|
|
279
268
|
|
|
280
269
|
options:
|
|
281
270
|
--activate activate the new generation when it verifies
|
|
282
|
-
--json emit one JSON object
|
|
271
|
+
--json emit one JSON object`,
|
|
272
|
+
|
|
273
|
+
serve: `factoidal serve - a SPARQL 1.1 endpoint over an activated store
|
|
274
|
+
|
|
275
|
+
usage: factoidal serve STORE [--port N] [--host ADDRESS] [options]
|
|
276
|
+
factoidal serve --file DATA /DATASET [--port N] [options]
|
|
277
|
+
factoidal serve --tdb2 DIR /DATASET [--port N] [options]
|
|
278
|
+
|
|
279
|
+
The second form matches fuseki-server --file DATA /DATASET: DATA is packed
|
|
280
|
+
into a temporary collection (layout ibk5 unless --layout is given), served
|
|
281
|
+
at /DATASET/sparql (also /DATASET/query and /DATASET/update) and
|
|
282
|
+
/DATASET/data (also /DATASET/get, read only). Updates are lost on exit.
|
|
283
|
+
|
|
284
|
+
The third form matches fuseki-server --loc DIR /DATASET for an Apache Jena
|
|
285
|
+
TDB2 database: DIR is read in place, read only. SPARQL query and Graph Store
|
|
286
|
+
GET work; an update or a Graph Store write answers 501 with a message. The
|
|
287
|
+
current Data-NNNN generation is served; a database whose journal.jrnl is not
|
|
288
|
+
empty (Jena has not replayed it) is refused. The Lean decoder reads the
|
|
289
|
+
indexes and node table (formal/lean4/L4Factoidal/Tdb2/Read.lean); every
|
|
290
|
+
quad is held in memory while the server runs.
|
|
291
|
+
|
|
292
|
+
Serves the activated generation of STORE, with its delta log, over HTTP:
|
|
293
|
+
|
|
294
|
+
GET/POST /sparql SPARQL 1.1 Protocol query and update
|
|
295
|
+
(GET /sparql with no query string: the SPARQL 1.1
|
|
296
|
+
Service Description of the endpoint)
|
|
297
|
+
/data, /data/... SPARQL 1.1 Graph Store HTTP Protocol (?default,
|
|
298
|
+
?graph=IRI, or a path under /data naming the graph)
|
|
299
|
+
|
|
300
|
+
options:
|
|
301
|
+
--port N the port to bind; 0 takes an ephemeral one (default 3030)
|
|
302
|
+
--host ADDRESS the address to bind (default 127.0.0.1)
|
|
303
|
+
--query-path PATH the query and update endpoint (default /sparql)
|
|
304
|
+
--gsp-path PATH the Graph Store endpoint (default /data)
|
|
305
|
+
--file DATA pack and serve an RDF file (see the second form)
|
|
306
|
+
--tdb2 DIR serve a Jena TDB2 database read only (third form)
|
|
307
|
+
--layout LAYOUT with --file: ibk3, ibk4 or ibk5 (default ibk5)
|
|
308
|
+
--no-worker serve on this thread instead of a worker thread with a
|
|
309
|
+
raised call stack (large collections need the worker)
|
|
310
|
+
--quiet print the origin only
|
|
311
|
+
|
|
312
|
+
An update, or a Graph Store PUT, POST or DELETE, is appended to the
|
|
313
|
+
activated generation's delta log as one batch before the response is sent,
|
|
314
|
+
and is visible to the next request. An IBK3 (triples) generation accepts
|
|
315
|
+
changes to the default graph only, because its delta log is replayed and
|
|
316
|
+
compacted for that graph only; a change to a named graph answers 501. A
|
|
317
|
+
quad generation (ibk4, ibk5) accepts every graph. Fold the log into a new
|
|
318
|
+
generation with l4block-shard-compact, activate it, and restart the server.
|
|
319
|
+
|
|
320
|
+
The server makes no protocol decision: the request framing, routing,
|
|
321
|
+
status codes, content negotiation, evaluation and the delta-log batch are
|
|
322
|
+
decided by formal/lean4/L4Factoidal/HTTP/Wire.lean and
|
|
323
|
+
formal/lean4/L4Factoidal/SPARQL/StoreEndpoint.lean through the
|
|
324
|
+
storeServe operations. A WebAssembly module built before those operations
|
|
325
|
+
landed makes this command exit 3 and say so.
|
|
326
|
+
|
|
327
|
+
Specifications: https://www.w3.org/TR/sparql11-protocol/
|
|
328
|
+
https://www.w3.org/TR/sparql11-http-rdf-update/
|
|
329
|
+
https://www.w3.org/TR/sparql11-service-description/`,
|
|
330
|
+
|
|
331
|
+
'lws-serve': `factoidal lws-serve - serve Linked Web Storage 1.0 over HTTP
|
|
332
|
+
|
|
333
|
+
usage: factoidal lws-serve DIR (--issuer IRI | --auth-config FILE | --insecure-no-auth)
|
|
334
|
+
[--owner WEBID] [--root-acl FILE] [--audience IRI]
|
|
335
|
+
[--port N] [--host ADDRESS] [--base IRI]
|
|
336
|
+
|
|
337
|
+
Starts a Node HTTP server that answers every request through the engine's
|
|
338
|
+
lwsStep operation.
|
|
339
|
+
|
|
340
|
+
DIR names the storage directory. The storage is kept in the engine
|
|
341
|
+
handle and written to DIR/lws-state.json after every request that
|
|
342
|
+
changed it (temporary file, fsync, rename), and read back on the next
|
|
343
|
+
start. The engine decides the snapshot format and refuses a snapshot
|
|
344
|
+
path with a dot segment or other traversal form
|
|
345
|
+
(formal/lean4/L4Factoidal/LWS/Snapshot.lean).
|
|
346
|
+
|
|
347
|
+
--port N the port to bind; 0 takes an ephemeral one (default 3000)
|
|
348
|
+
--host ADDRESS the address to bind (default 127.0.0.1)
|
|
349
|
+
--base IRI the absolute IRI the storage root maps to
|
|
350
|
+
(default: the bound origin)
|
|
351
|
+
|
|
352
|
+
Authorization (LWS editor's draft, section Authorization; one of the three
|
|
353
|
+
is required):
|
|
354
|
+
--issuer IRI trust this authorization server; several may be
|
|
355
|
+
given separated by commas. Its metadata is read from
|
|
356
|
+
IRI/.well-known/lws-configuration and its keys from
|
|
357
|
+
the jwks_uri there, when a token first needs them.
|
|
358
|
+
--auth-config FILE the engine's "auth" configuration as JSON:
|
|
359
|
+
{"issuers":[{"issuer":IRI,"jwks":{...}?}],
|
|
360
|
+
"audience":IRI?, "leeway":SECONDS?,
|
|
361
|
+
"refreshInterval":SECONDS?}
|
|
362
|
+
--insecure-no-auth development only: no authorization at all; every
|
|
363
|
+
request is served. Never the default.
|
|
364
|
+
--owner WEBID the storage owner: a new storage gets a root ACL
|
|
365
|
+
that gives this agent Read, Write and Control
|
|
366
|
+
--root-acl FILE a Turtle root ACL for a new storage instead
|
|
367
|
+
--audience IRI the aud value tokens must carry (default: the
|
|
368
|
+
storage root IRI)
|
|
369
|
+
|
|
370
|
+
Requests carry an access token as "Authorization: Bearer TOKEN" (RFC 6750).
|
|
371
|
+
The engine verifies its JWS signature (ES256, RS256 or EdDSA, with the
|
|
372
|
+
vendored HACL* code), its iss, aud, exp, nbf and iat, and then decides
|
|
373
|
+
access with Web Access Control over the storage's ACL resources. A
|
|
374
|
+
request without a valid token answers 401 with
|
|
375
|
+
WWW-Authenticate: Bearer resource_metadata="..." (RFC 9728), and the
|
|
376
|
+
metadata document is served at /.well-known/oauth-protected-resource.
|
|
377
|
+
|
|
378
|
+
The server makes no protocol decision: status codes, Link relations,
|
|
379
|
+
Last-Modified, the PATCH blank-node refusal and every authorization
|
|
380
|
+
decision are answered by formal/lean4/L4Factoidal/LWS/. A WebAssembly module built before those
|
|
381
|
+
operations landed makes this command exit 3 and say so.
|
|
382
|
+
|
|
383
|
+
Specification: https://w3c.github.io/lws-protocol/lws10-core/`,
|
|
384
|
+
|
|
385
|
+
'solid-serve': `factoidal solid-serve - serve the Solid Protocol over HTTP
|
|
386
|
+
|
|
387
|
+
usage: factoidal solid-serve DIR (--issuer IRI | --auth-config FILE | --insecure-no-auth)
|
|
388
|
+
[--owner WEBID] [--port N] [--host ADDRESS] [--base IRI]
|
|
389
|
+
|
|
390
|
+
Starts a Node HTTP server that answers every request through the engine's
|
|
391
|
+
solidStep operation.
|
|
392
|
+
|
|
393
|
+
DIR names the storage directory. The storage is kept in the engine
|
|
394
|
+
handle and written to DIR/lws-state.json after every request that
|
|
395
|
+
changed it (temporary file, fsync, rename), and read back on the next
|
|
396
|
+
start. The engine decides the snapshot format and refuses a snapshot
|
|
397
|
+
path with a dot segment or other traversal form
|
|
398
|
+
(formal/lean4/L4Factoidal/LWS/Snapshot.lean).
|
|
399
|
+
|
|
400
|
+
--port N the port to bind; 0 takes an ephemeral one (default 3000)
|
|
401
|
+
--host ADDRESS the address to bind (default 127.0.0.1)
|
|
402
|
+
--base IRI the absolute IRI the storage root maps to
|
|
403
|
+
(default: the bound origin)
|
|
404
|
+
--owner WEBID the WebID the storage advertises as its owner; under
|
|
405
|
+
authentication it gets acl:Read, acl:Write and
|
|
406
|
+
acl:Control on the root (Web Access Control §3.2)
|
|
407
|
+
--issuer IRI trust this Solid-OIDC issuer (comma separated for more).
|
|
408
|
+
The engine verifies each DPoP-bound access token
|
|
409
|
+
(Solid-OIDC §8, §9.3; RFC 9449): signature under the
|
|
410
|
+
issuer's keys found by OpenID Connect Discovery, aud
|
|
411
|
+
"solid", webid, exp, cnf.jkt bound to the DPoP key,
|
|
412
|
+
htm, htu, iat and a jti replay cache, and that the
|
|
413
|
+
WebID profile lists the issuer (solid:oidcIssuer).
|
|
414
|
+
--auth-config FILE the same as JSON: {"issuers":[{"issuer":IRI,
|
|
415
|
+
"jwks":{...}?}], "leeway"?, "iatWindow"?,
|
|
416
|
+
"refreshInterval"?, "checkProfileIssuer"?}
|
|
417
|
+
--insecure-no-auth development only: no authentication; Web Access
|
|
418
|
+
Control is not enforced
|
|
419
|
+
|
|
420
|
+
The server makes no protocol decision. Storage discovery, containment
|
|
421
|
+
triples, slash semantics, the Allow and Accept-* headers, the PUT/POST/
|
|
422
|
+
PATCH/DELETE rules, the acl and describedby lifecycle, N3 Patch, Web
|
|
423
|
+
Access Control, CORS and the LDN inbox are answered by
|
|
424
|
+
formal/lean4/L4Factoidal/Solid/Server/. A WebAssembly module built before
|
|
425
|
+
those operations landed makes this command exit 3 and say so.
|
|
426
|
+
|
|
427
|
+
Specification: https://solidproject.org/TR/protocol`,
|
|
428
|
+
|
|
429
|
+
'solid-client': `factoidal solid-client - run one Solid client operation
|
|
430
|
+
|
|
431
|
+
usage: factoidal solid-client <get|put|post|delete|discover> URL [options]
|
|
432
|
+
|
|
433
|
+
Every request is built by the engine's solidClientRequest operation and
|
|
434
|
+
every response is read by solidClientResponse. This command supplies a
|
|
435
|
+
socket and prints what the engine answered.
|
|
436
|
+
|
|
437
|
+
The five verbs are aliases for the operation kinds of the wasm ABI; the
|
|
438
|
+
kind names are accepted directly as well.
|
|
439
|
+
|
|
440
|
+
get read put replace post create
|
|
441
|
+
delete delete discover discoverStorage
|
|
442
|
+
(also: patch, readProfile)
|
|
443
|
+
|
|
444
|
+
--body TEXT the request body for put, post and patch
|
|
445
|
+
--file PATH read the request body from a file
|
|
446
|
+
--content-type M the body's media type
|
|
447
|
+
--interpret KIND one of storage, containment, auxiliaries, profile,
|
|
448
|
+
wacAllow, used when the engine names none
|
|
449
|
+
|
|
450
|
+
Specification: https://solidproject.org/TR/protocol`
|
|
283
451
|
}
|
|
284
452
|
|
|
285
453
|
// ------------------------------------------------------------- arguments
|
|
@@ -334,7 +502,12 @@ const VALUE_OPTIONS = {
|
|
|
334
502
|
pack: new Set(['layout', 'syntax', 'chunk-bytes', 'base', 'batch-bytes']),
|
|
335
503
|
activate: new Set([]),
|
|
336
504
|
update: new Set(['update', 'file']),
|
|
337
|
-
compact: new Set([])
|
|
505
|
+
compact: new Set([]),
|
|
506
|
+
serve: new Set(['port', 'host', 'query-path', 'gsp-path', 'file', 'layout', 'tdb2']),
|
|
507
|
+
'lws-serve': new Set(['port', 'host', 'base', 'issuer', 'auth-config', 'owner', 'root-acl',
|
|
508
|
+
'audience']),
|
|
509
|
+
'solid-serve': new Set(['port', 'host', 'base', 'owner', 'issuer', 'auth-config']),
|
|
510
|
+
'solid-client': new Set(['body', 'file', 'content-type', 'interpret'])
|
|
338
511
|
}
|
|
339
512
|
|
|
340
513
|
// ------------------------------------------------------------- commands
|
|
@@ -348,85 +521,142 @@ function readJsonFile (path) {
|
|
|
348
521
|
return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes))
|
|
349
522
|
}
|
|
350
523
|
|
|
351
|
-
function
|
|
524
|
+
async function sha256Hex (bytes) {
|
|
525
|
+
const digest = await globalThis.crypto.subtle.digest('SHA-256', bytes)
|
|
526
|
+
return hexOfBytes(new Uint8Array(digest))
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// `factoidal version`: what l4-assets/version.json says (the release stamp,
|
|
530
|
+
// written by tools/fetch-builds.sh --set web from the factoidal-builds
|
|
531
|
+
// release that built the wasm), what the installed wasm files hash to, and
|
|
532
|
+
// the loaded engine's own version(). A stamp whose sha256 differs from the
|
|
533
|
+
// installed wasm is an error (exit 1): the stamp then describes another
|
|
534
|
+
// build. A source checkout carries the unstamped placeholder.
|
|
535
|
+
async function commandVersion (options) {
|
|
352
536
|
const root = packageDirectory()
|
|
353
537
|
const own = readJsonFile(joinPath(root, 'package.json'))
|
|
354
|
-
let
|
|
538
|
+
let stamp = null
|
|
355
539
|
try {
|
|
356
|
-
|
|
540
|
+
stamp = readJsonFile(joinPath(root, 'l4-assets/version.json'))
|
|
357
541
|
} catch (_error) {
|
|
358
|
-
|
|
542
|
+
stamp = null
|
|
543
|
+
}
|
|
544
|
+
const stamped = stamp !== null && stamp.stamped === true
|
|
545
|
+
const installed = {}
|
|
546
|
+
for (const [key, file] of [['wasm', 'l4factoidal.wasm']]) {
|
|
547
|
+
try {
|
|
548
|
+
const bytes = readWhole(joinPath(root, `l4-assets/${file}`))
|
|
549
|
+
installed[key] = { sha256: await sha256Hex(bytes), bytes: bytes.length }
|
|
550
|
+
} catch (_error) {
|
|
551
|
+
installed[key] = null
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
let engineVersion = null
|
|
555
|
+
let engineError = null
|
|
556
|
+
if (installed.wasm !== null) {
|
|
557
|
+
try {
|
|
558
|
+
engineVersion = (await loadEngine()).version()
|
|
559
|
+
} catch (error) {
|
|
560
|
+
engineError = String(error && error.message ? error.message : error)
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
const mismatches = []
|
|
564
|
+
if (stamped) {
|
|
565
|
+
for (const [key, shaKey, bytesKey] of [['wasm', 'wasmSha256', 'wasmBytes']]) {
|
|
566
|
+
if (installed[key] === null) continue
|
|
567
|
+
if (stamp[shaKey] !== installed[key].sha256 || stamp[bytesKey] !== installed[key].bytes) {
|
|
568
|
+
mismatches.push(`${key}: version.json says sha256 ${stamp[shaKey]} (${stamp[bytesKey]} bytes), ` +
|
|
569
|
+
`installed file is ${installed[key].sha256} (${installed[key].bytes} bytes)`)
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
if (engineVersion !== null && typeof stamp.engineVersion === 'string' &&
|
|
573
|
+
stamp.engineVersion !== engineVersion) {
|
|
574
|
+
mismatches.push(`engine: version.json says ${stamp.engineVersion}, the loaded wasm says ${engineVersion}`)
|
|
575
|
+
}
|
|
359
576
|
}
|
|
360
577
|
const report = {
|
|
361
578
|
package: own.name,
|
|
362
579
|
version: own.version,
|
|
363
580
|
host: runtime,
|
|
364
|
-
|
|
581
|
+
stamp: stamp === null
|
|
365
582
|
? null
|
|
366
|
-
:
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
583
|
+
: stamped
|
|
584
|
+
? {
|
|
585
|
+
stamped: true,
|
|
586
|
+
engineVersion: stamp.engineVersion,
|
|
587
|
+
gitSha: stamp.gitSha,
|
|
588
|
+
builtAt: stamp.builtAt,
|
|
589
|
+
release: stamp.release,
|
|
590
|
+
leanToolchain: stamp.leanToolchain,
|
|
591
|
+
wasmSha256: stamp.wasmSha256,
|
|
592
|
+
wasmBytes: stamp.wasmBytes
|
|
593
|
+
}
|
|
594
|
+
: { stamped: false, status: stamp.status || 'stamped at release' },
|
|
595
|
+
installed,
|
|
596
|
+
engineVersion,
|
|
597
|
+
engineError,
|
|
598
|
+
mismatches
|
|
374
599
|
}
|
|
375
600
|
if (options.json) {
|
|
376
601
|
out(JSON.stringify(report, null, 2))
|
|
377
|
-
return EXIT_OK
|
|
378
|
-
}
|
|
379
|
-
out(`${report.package} ${report.version}`)
|
|
380
|
-
out(`host-io ${report.host}`)
|
|
381
|
-
if (report.engine !== null) {
|
|
382
|
-
out(`engine ${report.engine.engine} ${report.engine.version} (${report.engine.leanToolchain})`)
|
|
383
|
-
out(`wasm sha256 ${report.engine.wasmSha256} (${report.engine.wasmBytes} bytes)`)
|
|
384
602
|
} else {
|
|
385
|
-
out(
|
|
603
|
+
out(`${report.package} ${report.version}`)
|
|
604
|
+
out(`host-io ${report.host}`)
|
|
605
|
+
if (stamp === null) {
|
|
606
|
+
out('stamp: no l4-assets/version.json in this install')
|
|
607
|
+
} else if (!stamped) {
|
|
608
|
+
out('stamp: not stamped (l4-assets/version.json is the source placeholder; ' +
|
|
609
|
+
'it is stamped at release by tools/fetch-builds.sh --set web)')
|
|
610
|
+
} else {
|
|
611
|
+
out(`stamp: built from ${stamp.gitSha} at ${stamp.builtAt}` +
|
|
612
|
+
(stamp.release ? ` (${stamp.release})` : ''))
|
|
613
|
+
out(`stamp: engine ${stamp.engineVersion} (${stamp.leanToolchain})`)
|
|
614
|
+
out(`stamp: wasm sha256 ${stamp.wasmSha256} (${stamp.wasmBytes} bytes)`)
|
|
615
|
+
}
|
|
616
|
+
for (const [key, label] of [['wasm', 'wasm']]) {
|
|
617
|
+
out(installed[key] === null
|
|
618
|
+
? `installed ${label}: absent`
|
|
619
|
+
: `installed ${label} sha256 ${installed[key].sha256} (${installed[key].bytes} bytes)`)
|
|
620
|
+
}
|
|
621
|
+
out(engineVersion !== null
|
|
622
|
+
? `engine version() ${engineVersion}`
|
|
623
|
+
: `engine version() unavailable${engineError ? ': ' + engineError : ''}`)
|
|
386
624
|
}
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function commandSampleStore (options) {
|
|
391
|
-
const path = sampleStorePath()
|
|
392
|
-
if (options.json === true) {
|
|
393
|
-
out(JSON.stringify({ path, ...sampleStoreFacts }, null, 2))
|
|
394
|
-
return EXIT_OK
|
|
625
|
+
if (mismatches.length > 0) {
|
|
626
|
+
for (const m of mismatches) err(`factoidal version: STAMP MISMATCH ${m}`)
|
|
627
|
+
return EXIT_FAILURE
|
|
395
628
|
}
|
|
396
|
-
out(path)
|
|
397
629
|
return EXIT_OK
|
|
398
630
|
}
|
|
399
631
|
|
|
632
|
+
|
|
400
633
|
// ------------------------------------------------------------ rendering
|
|
401
634
|
|
|
402
635
|
/**
|
|
403
636
|
* One graph name as the manifest reports it. `{"kind":"default"}` is the
|
|
404
637
|
* default graph; the operation marks it that way so a host never has to
|
|
405
|
-
* recognise a reserved IRI.
|
|
638
|
+
* recognise a reserved IRI. The engine writes the N-Triples form of a name
|
|
639
|
+
* (op termsToNTriples).
|
|
406
640
|
*/
|
|
407
|
-
function
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
641
|
+
function graphLabels (engine, graphs) {
|
|
642
|
+
const terms = graphs.filter((g) => g.kind !== 'default')
|
|
643
|
+
.map((g) => ({ type: g.kind === 'iri' ? 'uri' : 'bnode', value: g.value }))
|
|
644
|
+
const tokens = terms.length === 0 ? [] : engine.call('termsToNTriples', [JSON.stringify(terms)]).tokens
|
|
645
|
+
let k = 0
|
|
646
|
+
return graphs.map((g) => (g.kind === 'default' ? 'default' : tokens[k++]))
|
|
412
647
|
}
|
|
413
648
|
|
|
414
649
|
/**
|
|
415
|
-
*
|
|
416
|
-
*
|
|
417
|
-
*
|
|
650
|
+
* The SPARQL Query Results JSON terms of a table, as display cells: the
|
|
651
|
+
* engine writes each term's canonical N-Triples form (op termsToNTriples).
|
|
652
|
+
* `--format json` prints the engine's own document.
|
|
418
653
|
*/
|
|
419
|
-
function
|
|
420
|
-
|
|
421
|
-
if (
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
if (typeof lang === 'string' && lang.length > 0) return `"${term.value}"@${lang}`
|
|
426
|
-
if (typeof term.datatype === 'string') return `"${term.value}"^^<${term.datatype}>`
|
|
427
|
-
return `"${term.value}"`
|
|
428
|
-
}
|
|
429
|
-
return JSON.stringify(term)
|
|
654
|
+
function cellsOfRows (engine, rows) {
|
|
655
|
+
const terms = []
|
|
656
|
+
for (const row of rows) for (const t of row) if (t !== undefined && t !== null) terms.push(t)
|
|
657
|
+
const tokens = terms.length === 0 ? [] : engine.call('termsToNTriples', [JSON.stringify(terms)]).tokens
|
|
658
|
+
let k = 0
|
|
659
|
+
return rows.map((row) => row.map((t) => (t === undefined || t === null ? '' : tokens[k++])))
|
|
430
660
|
}
|
|
431
661
|
|
|
432
662
|
function plural (count, noun) {
|
|
@@ -502,7 +732,7 @@ async function commandInspect (positional, options) {
|
|
|
502
732
|
String(entry.rows),
|
|
503
733
|
String(entry.bytes),
|
|
504
734
|
entry.blockKind,
|
|
505
|
-
entry.graphs.length === 0 ? '-' : entry.graphs
|
|
735
|
+
entry.graphs.length === 0 ? '-' : graphLabels(engine, entry.graphs).join(' '),
|
|
506
736
|
entry.predicate
|
|
507
737
|
].concat(zoned
|
|
508
738
|
? [String((entry.blobRefs ?? []).length), zoneCell(entry.subjectZone)]
|
|
@@ -674,11 +904,11 @@ function renderQueryResult (engine, result, format, limit, quiet) {
|
|
|
674
904
|
return EXIT_OK
|
|
675
905
|
}
|
|
676
906
|
if (format !== 'table') {
|
|
677
|
-
err(`factoidal query: --format ${format} needs a CONSTRUCT query; this one is a SELECT`)
|
|
907
|
+
err(`factoidal query: --format ${format} needs a CONSTRUCT or DESCRIBE query; this one is a SELECT`)
|
|
678
908
|
return EXIT_FAILURE
|
|
679
909
|
}
|
|
680
910
|
const shown = limit === null ? bindings : bindings.slice(0, limit)
|
|
681
|
-
printTable(vars, shown.map((row) => vars.map((name) =>
|
|
911
|
+
printTable(vars, cellsOfRows(engine, shown.map((row) => vars.map((name) => row[name]))))
|
|
682
912
|
if (shown.length < bindings.length) {
|
|
683
913
|
err(`showing ${shown.length} of ${bindings.length} rows (--limit ${limit})`)
|
|
684
914
|
} else if (!quiet) {
|
|
@@ -694,13 +924,13 @@ function renderQueryResult (engine, result, format, limit, quiet) {
|
|
|
694
924
|
return EXIT_OK
|
|
695
925
|
}
|
|
696
926
|
if (format !== 'table') {
|
|
697
|
-
err(`factoidal query: --format ${format} needs a CONSTRUCT query; this one is an ASK`)
|
|
927
|
+
err(`factoidal query: --format ${format} needs a CONSTRUCT or DESCRIBE query; this one is an ASK`)
|
|
698
928
|
return EXIT_FAILURE
|
|
699
929
|
}
|
|
700
930
|
out(result.boolean ? 'true' : 'false')
|
|
701
931
|
return EXIT_OK
|
|
702
932
|
}
|
|
703
|
-
if (result.kind === 'construct') {
|
|
933
|
+
if (result.kind === 'construct' || result.kind === 'describe') {
|
|
704
934
|
if (format === 'json') {
|
|
705
935
|
out(JSON.stringify(result, null, 2))
|
|
706
936
|
return EXIT_OK
|
|
@@ -717,32 +947,51 @@ function renderQueryResult (engine, result, format, limit, quiet) {
|
|
|
717
947
|
return EXIT_FAILURE
|
|
718
948
|
}
|
|
719
949
|
|
|
720
|
-
/** The
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
950
|
+
/** The first `length` bytes of a file, as text, for content detection. */
|
|
951
|
+
function readHead (path, length) {
|
|
952
|
+
return new TextDecoder('utf-8', { fatal: false }).decode(readChunk(path, 0, length))
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
/** The syntax tag for an input: --syntax when given, else the engine's
|
|
956
|
+
* suffix table (op engineTables), else the engine's content detection
|
|
957
|
+
* (op detectFormat) on the first 64 KiB of the file. */
|
|
958
|
+
function packSyntax (engine, input, options) {
|
|
959
|
+
const tables = engine.call('engineTables', []).tables
|
|
960
|
+
const syntaxes = tables.packSyntaxes
|
|
724
961
|
if (typeof options.syntax === 'string') {
|
|
725
962
|
const syntax = options.syntax.toLowerCase()
|
|
726
|
-
if (
|
|
727
|
-
throw new UsageError(`--syntax ${options.syntax} is not one of ${
|
|
963
|
+
if (syntaxes.indexOf(syntax) >= 0) return syntax
|
|
964
|
+
throw new UsageError(`--syntax ${options.syntax} is not one of ${syntaxes.join(', ')}`)
|
|
728
965
|
}
|
|
729
966
|
const lower = input.toLowerCase()
|
|
730
|
-
for (const [suffix, syntax] of
|
|
967
|
+
for (const [suffix, syntax] of Object.entries(tables.packSuffixes)) {
|
|
731
968
|
if (lower.endsWith(suffix)) return syntax
|
|
732
969
|
}
|
|
733
|
-
for (const [suffix, name] of
|
|
970
|
+
for (const [suffix, name] of Object.entries(tables.packUnsupportedSuffixes)) {
|
|
734
971
|
if (lower.endsWith(suffix)) {
|
|
735
972
|
throw new UsageError(
|
|
736
973
|
`pack does not read ${name}. The packer's streaming fold reads ` +
|
|
737
|
-
`${
|
|
974
|
+
`${syntaxes.join(', ')} only. Convert the file first, for ` +
|
|
738
975
|
"example with: factoidal parse FILE --out nquads")
|
|
739
976
|
}
|
|
740
977
|
}
|
|
978
|
+
let head = ''
|
|
979
|
+
try {
|
|
980
|
+
head = readHead(input, 65536)
|
|
981
|
+
} catch (_error) {
|
|
982
|
+
head = ''
|
|
983
|
+
}
|
|
984
|
+
if (head !== '') {
|
|
985
|
+
const tag = engine.call('detectFormat', [head]).format
|
|
986
|
+
const syntax = tag.replace(/12$/, '')
|
|
987
|
+
if (syntaxes.indexOf(syntax) >= 0) return syntax
|
|
988
|
+
}
|
|
741
989
|
throw new UsageError(
|
|
742
|
-
`cannot tell the syntax of ${input} from its name; give --syntax ` +
|
|
743
|
-
`(${
|
|
990
|
+
`cannot tell the syntax of ${input} from its name or its content; give --syntax ` +
|
|
991
|
+
`(${syntaxes.join(', ')})`)
|
|
744
992
|
}
|
|
745
993
|
|
|
994
|
+
|
|
746
995
|
/**
|
|
747
996
|
* The base IRI relative IRIs in the source resolve against.
|
|
748
997
|
*
|
|
@@ -773,7 +1022,7 @@ function packLayout (options) {
|
|
|
773
1022
|
async function commandPack (positional, options) {
|
|
774
1023
|
if (positional.length !== 2) throw new UsageError('pack needs INPUT and OUTPUT')
|
|
775
1024
|
const [input, output] = positional
|
|
776
|
-
const syntax = packSyntax(input, options)
|
|
1025
|
+
const syntax = packSyntax(await loadEngine(), input, options)
|
|
777
1026
|
const layout = packLayout(options)
|
|
778
1027
|
// The pack fold needs a bigger call stack than either runtime gives by
|
|
779
1028
|
// default (https://github.com/danbri/factoidal/issues/649). Under Node
|
|
@@ -835,6 +1084,34 @@ async function commandPack (positional, options) {
|
|
|
835
1084
|
return EXIT_OK
|
|
836
1085
|
}
|
|
837
1086
|
|
|
1087
|
+
async function commandSampleStore (positional, options) {
|
|
1088
|
+
if (positional.length > 1) throw new UsageError('sample-store takes at most one DIR')
|
|
1089
|
+
const { mkdtempSync, writeFileSync, existsSync } = await import('node:fs')
|
|
1090
|
+
const { tmpdir } = await import('node:os')
|
|
1091
|
+
const root = positional.length === 1
|
|
1092
|
+
? positional[0]
|
|
1093
|
+
: mkdtempSync(joinPath(tmpdir(), 'factoidal-sample-store-'))
|
|
1094
|
+
if (!existsSync(joinPath(root, 'CURRENT'))) {
|
|
1095
|
+
makeDirectory(joinPath(root, 'gen-1'))
|
|
1096
|
+
const source = joinPath(root, 'sample.ttl')
|
|
1097
|
+
writeFileSync(source, SAMPLE_TURTLE)
|
|
1098
|
+
outSilenced = true
|
|
1099
|
+
try {
|
|
1100
|
+
let code = await commandPack([source, joinPath(root, 'gen-1')], { quiet: true })
|
|
1101
|
+
if (code === EXIT_OK) code = await commandActivate([root, 'gen-1'], { quiet: true })
|
|
1102
|
+
if (code !== EXIT_OK) return code
|
|
1103
|
+
} finally {
|
|
1104
|
+
outSilenced = false
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
if (options.json === true) {
|
|
1108
|
+
out(JSON.stringify({ path: root, ...sampleStoreFacts }, null, 2))
|
|
1109
|
+
} else {
|
|
1110
|
+
out(root)
|
|
1111
|
+
}
|
|
1112
|
+
return EXIT_OK
|
|
1113
|
+
}
|
|
1114
|
+
|
|
838
1115
|
async function commandActivate (positional, options) {
|
|
839
1116
|
if (positional.length !== 2) throw new UsageError('activate needs STORE and GENERATION')
|
|
840
1117
|
const [root, generation] = positional
|
|
@@ -906,15 +1183,287 @@ function commandCompact (positional, _options) {
|
|
|
906
1183
|
return notWired('compact', 'Compaction is stage 4 of the milestone.')
|
|
907
1184
|
}
|
|
908
1185
|
|
|
1186
|
+
// ------------------------------------------------- the protocol servers
|
|
1187
|
+
// These commands hand a socket to a host module and print where it is
|
|
1188
|
+
// listening. They decide nothing about the protocol they serve; see
|
|
1189
|
+
// npm/factoidal/lws/README.md and npm/factoidal/solid/*/README.md.
|
|
1190
|
+
// https://github.com/danbri/factoidal/issues/659
|
|
1191
|
+
|
|
1192
|
+
function servePort (options, fallback) {
|
|
1193
|
+
if (options.port === undefined) return fallback
|
|
1194
|
+
const port = Number(options.port)
|
|
1195
|
+
if (!Number.isInteger(port) || port < 0 || port > 65535) {
|
|
1196
|
+
throw new UsageError(`--port must be an integer from 0 to 65535, not "${options.port}"`)
|
|
1197
|
+
}
|
|
1198
|
+
return port
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
function serveHost (options) {
|
|
1202
|
+
if (options.host === undefined) return '127.0.0.1'
|
|
1203
|
+
if (typeof options.host !== 'string' || options.host.length === 0) {
|
|
1204
|
+
throw new UsageError('--host needs an address')
|
|
1205
|
+
}
|
|
1206
|
+
return options.host
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
/** Keep the process alive until the runtime is interrupted. */
|
|
1210
|
+
function untilInterrupted (running, quiet) {
|
|
1211
|
+
return new Promise((resolve) => {
|
|
1212
|
+
const stop = async () => {
|
|
1213
|
+
if (!quiet) err('')
|
|
1214
|
+
await running.close()
|
|
1215
|
+
resolve(EXIT_OK)
|
|
1216
|
+
}
|
|
1217
|
+
if (isDeno) {
|
|
1218
|
+
globalThis.Deno.addSignalListener('SIGINT', stop)
|
|
1219
|
+
} else {
|
|
1220
|
+
process.on('SIGINT', stop)
|
|
1221
|
+
process.on('SIGTERM', stop)
|
|
1222
|
+
}
|
|
1223
|
+
})
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
async function commandServe (positional, options) {
|
|
1227
|
+
const quiet = options.quiet === true
|
|
1228
|
+
// The Apache Jena Fuseki form: `serve --file DATA /ds`. The file is
|
|
1229
|
+
// packed into a new collection in a temporary directory (ibk5 unless
|
|
1230
|
+
// --layout says otherwise, so every graph accepts writes), activated, and
|
|
1231
|
+
// served at /ds/sparql and /ds/data; /ds/query, /ds/update and /ds/get
|
|
1232
|
+
// are answered too (StoreEndpoint.datasetPrefix). Updates go to that
|
|
1233
|
+
// temporary collection and are lost when the process ends, as with
|
|
1234
|
+
// fuseki-server --file. docs/fuseki-replacement.md.
|
|
1235
|
+
if (typeof options.file === 'string') {
|
|
1236
|
+
if (positional.length !== 1 || !positional[0].startsWith('/')) {
|
|
1237
|
+
throw new UsageError('serve --file DATA needs one dataset name, such as /ds')
|
|
1238
|
+
}
|
|
1239
|
+
const dsName = positional[0].replace(/\/+$/, '')
|
|
1240
|
+
const { mkdtempSync } = await import('node:fs')
|
|
1241
|
+
const { tmpdir } = await import('node:os')
|
|
1242
|
+
const { join } = await import('node:path')
|
|
1243
|
+
const root = mkdtempSync(join(tmpdir(), 'factoidal-serve-'))
|
|
1244
|
+
const packOptions = { quiet: true, layout: typeof options.layout === 'string' ? options.layout : 'ibk5' }
|
|
1245
|
+
let rc = await commandPack([options.file, join(root, 'gen-1')], packOptions)
|
|
1246
|
+
if (rc !== 0) return rc
|
|
1247
|
+
rc = await commandActivate([root, 'gen-1'], { quiet: true })
|
|
1248
|
+
if (rc !== 0) return rc
|
|
1249
|
+
if (!quiet) err(`serve: packed ${options.file} into ${root} (temporary; updates are lost on exit)`)
|
|
1250
|
+
positional = [root]
|
|
1251
|
+
options = { ...options, 'query-path': `${dsName}/sparql`, 'gsp-path': `${dsName}/data` }
|
|
1252
|
+
}
|
|
1253
|
+
// The Apache Jena Fuseki form: `serve --tdb2 DIR /ds`, a TDB2 database
|
|
1254
|
+
// read in place and read only (https://github.com/danbri/factoidal/issues/834).
|
|
1255
|
+
const tdb2 = typeof options.tdb2 === 'string'
|
|
1256
|
+
if (tdb2) {
|
|
1257
|
+
if (positional.length !== 1 || !positional[0].startsWith('/')) {
|
|
1258
|
+
throw new UsageError('serve --tdb2 DIR needs one dataset name, such as /ds')
|
|
1259
|
+
}
|
|
1260
|
+
const dsName = positional[0].replace(/\/+$/, '')
|
|
1261
|
+
positional = [options.tdb2]
|
|
1262
|
+
options = { ...options, 'query-path': `${dsName}/sparql`, 'gsp-path': `${dsName}/data` }
|
|
1263
|
+
}
|
|
1264
|
+
if (positional.length !== 1) throw new UsageError('serve needs one STORE')
|
|
1265
|
+
const { listen, listenOnWorker, ServeHostError } = await import('./serve.mjs')
|
|
1266
|
+
const serveOptions = {
|
|
1267
|
+
port: servePort(options, 3030),
|
|
1268
|
+
host: serveHost(options),
|
|
1269
|
+
...(tdb2 ? { tdb2: true } : {}),
|
|
1270
|
+
...(typeof options['query-path'] === 'string' ? { queryPath: options['query-path'] } : {}),
|
|
1271
|
+
...(typeof options['gsp-path'] === 'string' ? { gspPath: options['gsp-path'] } : {})
|
|
1272
|
+
}
|
|
1273
|
+
const onWorker = options['no-worker'] !== true && workerRouteAvailable()
|
|
1274
|
+
let running
|
|
1275
|
+
try {
|
|
1276
|
+
running = onWorker
|
|
1277
|
+
? await listenOnWorker(positional[0], serveOptions)
|
|
1278
|
+
: await listen(positional[0], serveOptions)
|
|
1279
|
+
} catch (error) {
|
|
1280
|
+
if (error instanceof ServeHostError && error.unknownOp) {
|
|
1281
|
+
return notWired('serve', error.message)
|
|
1282
|
+
}
|
|
1283
|
+
if (error instanceof Error && typeof error.message === 'string') {
|
|
1284
|
+
err(`factoidal serve: ${error.message.replace(/^l4factoidal:\s*/, '')}`)
|
|
1285
|
+
return EXIT_FAILURE
|
|
1286
|
+
}
|
|
1287
|
+
throw error
|
|
1288
|
+
}
|
|
1289
|
+
const info = onWorker ? running.info : running.served.info
|
|
1290
|
+
if (!quiet) {
|
|
1291
|
+
err(`serve: listening on ${running.origin} for ${positional[0]} ` +
|
|
1292
|
+
`(layout ${info.layout}, ${plural(info.rows, 'row')}, ` +
|
|
1293
|
+
`delta batches replayed: ${info.deltaBatches}, write scope ${info.writeScope})`)
|
|
1294
|
+
err(`serve: query ${running.origin}${info.queryPath}, graph store ${running.origin}${info.gspPath}`)
|
|
1295
|
+
}
|
|
1296
|
+
out(running.origin)
|
|
1297
|
+
return untilInterrupted(running, quiet)
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
async function commandLwsServe (positional, options) {
|
|
1301
|
+
if (positional.length !== 1) throw new UsageError('lws-serve needs one DIR')
|
|
1302
|
+
const quiet = options.quiet === true
|
|
1303
|
+
const insecure = options['insecure-no-auth'] === true
|
|
1304
|
+
const issuerOption = typeof options.issuer === 'string' ? options.issuer : null
|
|
1305
|
+
const authFile = typeof options['auth-config'] === 'string' ? options['auth-config'] : null
|
|
1306
|
+
if (!insecure && issuerOption === null && authFile === null) {
|
|
1307
|
+
throw new UsageError('lws-serve needs --issuer IRI or --auth-config FILE, ' +
|
|
1308
|
+
'or --insecure-no-auth for development')
|
|
1309
|
+
}
|
|
1310
|
+
if (insecure && (issuerOption !== null || authFile !== null)) {
|
|
1311
|
+
throw new UsageError('--insecure-no-auth cannot be combined with --issuer or --auth-config')
|
|
1312
|
+
}
|
|
1313
|
+
let auth
|
|
1314
|
+
if (authFile !== null) {
|
|
1315
|
+
auth = readJsonFile(authFile)
|
|
1316
|
+
} else if (issuerOption !== null) {
|
|
1317
|
+
auth = { issuers: issuerOption.split(',').filter((v) => v !== '').map((issuer) => ({ issuer })) }
|
|
1318
|
+
}
|
|
1319
|
+
if (auth !== undefined && typeof options.audience === 'string') {
|
|
1320
|
+
auth = { ...auth, audience: options.audience }
|
|
1321
|
+
}
|
|
1322
|
+
const { listen, LwsHostError } = await import('../lws/server.mjs')
|
|
1323
|
+
let running
|
|
1324
|
+
try {
|
|
1325
|
+
running = await listen({
|
|
1326
|
+
port: servePort(options, 3000),
|
|
1327
|
+
host: serveHost(options),
|
|
1328
|
+
...(typeof options.base === 'string' ? { baseIri: options.base } : {}),
|
|
1329
|
+
...(insecure ? { insecureNoAuth: true } : { auth }),
|
|
1330
|
+
...(typeof options.owner === 'string' ? { owner: options.owner } : {}),
|
|
1331
|
+
...(typeof options['root-acl'] === 'string'
|
|
1332
|
+
? { rootAcl: new TextDecoder('utf-8', { fatal: true }).decode(readWhole(options['root-acl'])) }
|
|
1333
|
+
: {}),
|
|
1334
|
+
realClock: true,
|
|
1335
|
+
dir: positional[0]
|
|
1336
|
+
})
|
|
1337
|
+
} catch (error) {
|
|
1338
|
+
if (error instanceof LwsHostError && error.unknownOp) {
|
|
1339
|
+
return notWired('lws-serve', error.message)
|
|
1340
|
+
}
|
|
1341
|
+
throw error
|
|
1342
|
+
}
|
|
1343
|
+
if (!quiet) {
|
|
1344
|
+
err(`lws-serve: listening on ${running.origin} for ${positional[0]}`)
|
|
1345
|
+
if (insecure) err('lws-serve: WARNING: --insecure-no-auth: every request is served without authorization')
|
|
1346
|
+
}
|
|
1347
|
+
out(running.origin)
|
|
1348
|
+
return untilInterrupted(running, quiet)
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
async function commandSolidServe (positional, options) {
|
|
1352
|
+
if (positional.length !== 1) throw new UsageError('solid-serve needs one DIR')
|
|
1353
|
+
const quiet = options.quiet === true
|
|
1354
|
+
// The same rule as lws-serve: no start without a trusted issuer unless
|
|
1355
|
+
// the development mode is asked for by name.
|
|
1356
|
+
const insecure = options['insecure-no-auth'] === true
|
|
1357
|
+
const issuerOption = typeof options.issuer === 'string' ? options.issuer : null
|
|
1358
|
+
const authFile = typeof options['auth-config'] === 'string' ? options['auth-config'] : null
|
|
1359
|
+
if (!insecure && issuerOption === null && authFile === null) {
|
|
1360
|
+
throw new UsageError('solid-serve needs --issuer IRI or --auth-config FILE, ' +
|
|
1361
|
+
'or --insecure-no-auth for development')
|
|
1362
|
+
}
|
|
1363
|
+
if (insecure && (issuerOption !== null || authFile !== null)) {
|
|
1364
|
+
throw new UsageError('--insecure-no-auth cannot be combined with --issuer or --auth-config')
|
|
1365
|
+
}
|
|
1366
|
+
let auth
|
|
1367
|
+
if (authFile !== null) {
|
|
1368
|
+
auth = readJsonFile(authFile)
|
|
1369
|
+
} else if (issuerOption !== null) {
|
|
1370
|
+
auth = { issuers: issuerOption.split(',').filter((v) => v !== '').map((issuer) => ({ issuer })) }
|
|
1371
|
+
}
|
|
1372
|
+
const { listen, SolidServerHostError } = await import('../solid/server/index.mjs')
|
|
1373
|
+
let running
|
|
1374
|
+
try {
|
|
1375
|
+
running = await listen({
|
|
1376
|
+
port: servePort(options, 3000),
|
|
1377
|
+
host: serveHost(options),
|
|
1378
|
+
...(typeof options.base === 'string' ? { baseIri: options.base } : {}),
|
|
1379
|
+
...(insecure ? { insecureNoAuth: true } : { auth }),
|
|
1380
|
+
...(typeof options.owner === 'string' ? { owner: options.owner } : {}),
|
|
1381
|
+
realClock: true,
|
|
1382
|
+
dir: positional[0]
|
|
1383
|
+
})
|
|
1384
|
+
} catch (error) {
|
|
1385
|
+
if (error instanceof SolidServerHostError && error.unknownOp) {
|
|
1386
|
+
return notWired('solid-serve', error.message)
|
|
1387
|
+
}
|
|
1388
|
+
throw error
|
|
1389
|
+
}
|
|
1390
|
+
if (!quiet) {
|
|
1391
|
+
err(`solid-serve: listening on ${running.origin} for ${positional[0]}`)
|
|
1392
|
+
if (insecure) err('solid-serve: WARNING: --insecure-no-auth: every request is served without authentication')
|
|
1393
|
+
}
|
|
1394
|
+
out(running.origin)
|
|
1395
|
+
return untilInterrupted(running, quiet)
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// The command's verbs, as aliases for the wasm ABI's request kinds. A
|
|
1399
|
+
// kind name is accepted directly too, so this table adds a spelling and
|
|
1400
|
+
// hides nothing.
|
|
1401
|
+
const SOLID_CLIENT_ALIASES = {
|
|
1402
|
+
get: 'read',
|
|
1403
|
+
put: 'replace',
|
|
1404
|
+
post: 'create',
|
|
1405
|
+
delete: 'delete',
|
|
1406
|
+
discover: 'discoverStorage'
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
async function commandSolidClient (positional, options) {
|
|
1410
|
+
if (positional.length !== 2) {
|
|
1411
|
+
throw new UsageError('solid-client needs an operation and a URL')
|
|
1412
|
+
}
|
|
1413
|
+
const [verb, target] = positional
|
|
1414
|
+
const { createSolidClient, SolidClientHostError, SOLID_REQUEST_KINDS } =
|
|
1415
|
+
await import('../solid/client/index.mjs')
|
|
1416
|
+
const kind = Object.prototype.hasOwnProperty.call(SOLID_CLIENT_ALIASES, verb)
|
|
1417
|
+
? SOLID_CLIENT_ALIASES[verb]
|
|
1418
|
+
: verb
|
|
1419
|
+
if (!SOLID_REQUEST_KINDS.includes(kind)) {
|
|
1420
|
+
throw new UsageError(
|
|
1421
|
+
`unknown operation "${verb}"; one of ` +
|
|
1422
|
+
`${Object.keys(SOLID_CLIENT_ALIASES).join(', ')} or ` +
|
|
1423
|
+
`${SOLID_REQUEST_KINDS.join(', ')}`)
|
|
1424
|
+
}
|
|
1425
|
+
const args = { target }
|
|
1426
|
+
if (typeof options.file === 'string') {
|
|
1427
|
+
args.body = new TextDecoder('utf-8', { fatal: true }).decode(readWhole(options.file))
|
|
1428
|
+
} else if (typeof options.body === 'string') {
|
|
1429
|
+
args.body = options.body
|
|
1430
|
+
}
|
|
1431
|
+
if (typeof options['content-type'] === 'string') {
|
|
1432
|
+
args.contentType = options['content-type']
|
|
1433
|
+
}
|
|
1434
|
+
if (['create', 'replace', 'patch'].includes(kind) && args.body === undefined) {
|
|
1435
|
+
throw new UsageError(`${verb} needs --body or --file`)
|
|
1436
|
+
}
|
|
1437
|
+
const extra = typeof options.interpret === 'string'
|
|
1438
|
+
? { interpret: options.interpret }
|
|
1439
|
+
: {}
|
|
1440
|
+
let answer
|
|
1441
|
+
try {
|
|
1442
|
+
const client = await createSolidClient()
|
|
1443
|
+
answer = await client.run(kind, args, extra)
|
|
1444
|
+
} catch (error) {
|
|
1445
|
+
if (error instanceof SolidClientHostError && error.unknownOp) {
|
|
1446
|
+
return notWired('solid-client', error.message)
|
|
1447
|
+
}
|
|
1448
|
+
throw error
|
|
1449
|
+
}
|
|
1450
|
+
out(JSON.stringify(answer, null, 2))
|
|
1451
|
+
return EXIT_OK
|
|
1452
|
+
}
|
|
1453
|
+
|
|
909
1454
|
const COMMANDS = {
|
|
910
1455
|
version: (positional, options) => commandVersion(options),
|
|
911
|
-
'sample-store': (positional, options) => commandSampleStore(options),
|
|
1456
|
+
'sample-store': (positional, options) => commandSampleStore(positional, options),
|
|
912
1457
|
inspect: commandInspect,
|
|
913
1458
|
query: commandQuery,
|
|
914
1459
|
pack: commandPack,
|
|
915
1460
|
activate: commandActivate,
|
|
916
1461
|
update: commandUpdate,
|
|
917
|
-
compact: commandCompact
|
|
1462
|
+
compact: commandCompact,
|
|
1463
|
+
serve: commandServe,
|
|
1464
|
+
'lws-serve': commandLwsServe,
|
|
1465
|
+
'solid-serve': commandSolidServe,
|
|
1466
|
+
'solid-client': commandSolidClient
|
|
918
1467
|
}
|
|
919
1468
|
|
|
920
1469
|
// ----------------------------------------------------------------- main
|