@factoidal/core 0.7.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/CHANGELOG.md +510 -1
  2. package/NOTICE +2 -21
  3. package/README.md +117 -993
  4. package/api.d.ts +53 -0
  5. package/api.js +5 -0
  6. package/api.mjs +56 -0
  7. package/bin/engine.mjs +38 -19
  8. package/bin/factoidal.mjs +513 -67
  9. package/bin/pack.mjs +37 -0
  10. package/bin/serve-worker.mjs +32 -0
  11. package/bin/serve.mjs +423 -0
  12. package/bin/store-worker-host.mjs +1 -1
  13. package/bin/store.mjs +1 -1
  14. package/browser.d.ts +110 -507
  15. package/browser.js +151 -2257
  16. package/docs/fuseki-replacement.md +108 -0
  17. package/fn.d.ts +24 -95
  18. package/fn.js +104 -150
  19. package/index.d.ts +598 -259
  20. package/index.js +4 -234
  21. package/index.mjs +176 -84
  22. package/l4-assets/l4factoidal.js +47 -7
  23. package/l4-assets/l4factoidal.mjs +1 -1
  24. package/l4-assets/l4factoidal.wasm +0 -0
  25. package/l4-assets/version.json +11 -7
  26. package/lib/api.mjs +2300 -0
  27. package/lib/browser-engine.mjs +69 -0
  28. package/lib/dataset.mjs +377 -0
  29. package/lib/delta-log-hosts.mjs +191 -0
  30. package/lib/lean-entry.mjs +204 -0
  31. package/lib/pure.mjs +98 -0
  32. package/lib/store.mjs +115 -0
  33. package/lib/version.mjs +5 -0
  34. package/lws/README.md +97 -0
  35. package/lws/server.mjs +331 -0
  36. package/package.json +59 -61
  37. package/rdfjs.js +5 -536
  38. package/rdfjs.mjs +594 -0
  39. package/sample-store.d.ts +8 -16
  40. package/sample-store.mjs +39 -29
  41. package/solid/client/README.md +100 -0
  42. package/solid/client/index.mjs +277 -0
  43. package/solid/server/README.md +96 -0
  44. package/solid/server/index.mjs +317 -0
  45. package/store-host/deno.mjs +8 -0
  46. package/store-host/index.mjs +12 -0
  47. package/store-host/node.mjs +8 -0
  48. package/xmpp/README.md +107 -0
  49. package/xmpp/index.mjs +229 -0
  50. package/browser-wasm.js +0 -872
  51. package/factoidal-npm-entry.js +0 -15477
  52. package/factoidal-npm-entry.wasm.assets/code-5a7fc68f2ab1323718b8.wasm +0 -0
  53. package/factoidal-npm-entry.wasm.js +0 -455
  54. package/factoidal.js +0 -13118
  55. package/factoidal.wasm.assets/code-bbe6099bfb5b10c4c3ab.wasm +0 -0
  56. package/factoidal.wasm.js +0 -457
  57. package/hacl-init.js +0 -92
  58. package/hacl-wasm/FStar.wasm +0 -0
  59. package/hacl-wasm/Hacl_Bignum.wasm +0 -0
  60. package/hacl-wasm/Hacl_Bignum25519_51.wasm +0 -0
  61. package/hacl-wasm/Hacl_Bignum_Base.wasm +0 -0
  62. package/hacl-wasm/Hacl_Curve25519_51.wasm +0 -0
  63. package/hacl-wasm/Hacl_Ed25519.wasm +0 -0
  64. package/hacl-wasm/Hacl_Ed25519_PrecompTable.wasm +0 -0
  65. package/hacl-wasm/Hacl_Hash_Base.wasm +0 -0
  66. package/hacl-wasm/Hacl_Hash_SHA2.wasm +0 -0
  67. package/hacl-wasm/Hacl_IntTypes_Intrinsics.wasm +0 -0
  68. package/hacl-wasm/LowStar_Endianness.wasm +0 -0
  69. package/hacl-wasm/WasmSupport.wasm +0 -0
  70. package/hacl-wasm/api.js +0 -775
  71. package/hacl-wasm/api.json +0 -3787
  72. package/hacl-wasm/layouts.json +0 -1
  73. package/hacl-wasm/loader.js +0 -568
  74. package/hacl-wasm/shell.js +0 -12
  75. package/l4-core.d.ts +0 -159
  76. package/l4-core.js +0 -188
  77. package/l4.d.ts +0 -50
  78. package/l4.js +0 -104
  79. package/lib/api.js +0 -2300
  80. package/lib/engine-js.js +0 -165
  81. package/lib/engine-wasm.js +0 -300
  82. package/sample-store/CURRENT +0 -1
  83. package/sample-store/gen-1/manifest.sbm2 +0 -0
  84. package/sample-store/gen-1/manifest.tsv +0 -14
  85. package/sample-store/gen-1/predicate-0.ibk3 +0 -0
  86. package/sample-store/gen-1/predicate-0.ibk3.merkle +0 -1
  87. package/sample-store/gen-1/predicate-0.ibk3.oli2 +0 -0
  88. package/sample-store/gen-1/predicate-0.ibk3.oli2.merkle +0 -1
  89. package/sample-store/gen-1/predicate-0.ibk3.sri2 +0 -0
  90. package/sample-store/gen-1/predicate-0.ibk3.sri2.merkle +0 -1
  91. package/sample-store/gen-1/predicate-0.ibk3.tli1 +0 -0
  92. package/sample-store/gen-1/predicate-0.ibk3.tli1.merkle +0 -0
  93. package/sample-store/gen-1/predicate-1.ibk3 +0 -0
  94. package/sample-store/gen-1/predicate-1.ibk3.merkle +0 -0
  95. package/sample-store/gen-1/predicate-1.ibk3.oli2 +0 -0
  96. package/sample-store/gen-1/predicate-1.ibk3.oli2.merkle +0 -0
  97. package/sample-store/gen-1/predicate-1.ibk3.sri2 +0 -0
  98. package/sample-store/gen-1/predicate-1.ibk3.sri2.merkle +0 -1
  99. package/sample-store/gen-1/predicate-1.ibk3.tli1 +0 -0
  100. package/sample-store/gen-1/predicate-1.ibk3.tli1.merkle +0 -1
  101. package/sample-store/gen-1/predicate-10.ibk3 +0 -0
  102. package/sample-store/gen-1/predicate-10.ibk3.merkle +0 -1
  103. package/sample-store/gen-1/predicate-10.ibk3.oli2 +0 -0
  104. package/sample-store/gen-1/predicate-10.ibk3.oli2.merkle +0 -0
  105. package/sample-store/gen-1/predicate-10.ibk3.sri2 +0 -0
  106. package/sample-store/gen-1/predicate-10.ibk3.sri2.merkle +0 -1
  107. package/sample-store/gen-1/predicate-10.ibk3.tli1 +0 -0
  108. package/sample-store/gen-1/predicate-10.ibk3.tli1.merkle +0 -1
  109. package/sample-store/gen-1/predicate-11.ibk3 +0 -0
  110. package/sample-store/gen-1/predicate-11.ibk3.merkle +0 -0
  111. package/sample-store/gen-1/predicate-11.ibk3.oli2 +0 -0
  112. package/sample-store/gen-1/predicate-11.ibk3.oli2.merkle +0 -1
  113. package/sample-store/gen-1/predicate-11.ibk3.sri2 +0 -0
  114. package/sample-store/gen-1/predicate-11.ibk3.sri2.merkle +0 -1
  115. package/sample-store/gen-1/predicate-11.ibk3.tli1 +0 -0
  116. package/sample-store/gen-1/predicate-11.ibk3.tli1.merkle +0 -1
  117. package/sample-store/gen-1/predicate-12.ibk3 +0 -0
  118. package/sample-store/gen-1/predicate-12.ibk3.merkle +0 -1
  119. package/sample-store/gen-1/predicate-12.ibk3.oli2 +0 -0
  120. package/sample-store/gen-1/predicate-12.ibk3.oli2.merkle +0 -1
  121. package/sample-store/gen-1/predicate-12.ibk3.sri2 +0 -0
  122. package/sample-store/gen-1/predicate-12.ibk3.sri2.merkle +0 -1
  123. package/sample-store/gen-1/predicate-12.ibk3.tli1 +0 -0
  124. package/sample-store/gen-1/predicate-12.ibk3.tli1.merkle +0 -1
  125. package/sample-store/gen-1/predicate-2.ibk3 +0 -0
  126. package/sample-store/gen-1/predicate-2.ibk3.merkle +0 -1
  127. package/sample-store/gen-1/predicate-2.ibk3.oli2 +0 -0
  128. package/sample-store/gen-1/predicate-2.ibk3.oli2.merkle +0 -1
  129. package/sample-store/gen-1/predicate-2.ibk3.sri2 +0 -0
  130. package/sample-store/gen-1/predicate-2.ibk3.sri2.merkle +0 -0
  131. package/sample-store/gen-1/predicate-2.ibk3.tli1 +0 -0
  132. package/sample-store/gen-1/predicate-2.ibk3.tli1.merkle +0 -0
  133. package/sample-store/gen-1/predicate-3.ibk3 +0 -0
  134. package/sample-store/gen-1/predicate-3.ibk3.merkle +0 -1
  135. package/sample-store/gen-1/predicate-3.ibk3.oli2 +0 -0
  136. package/sample-store/gen-1/predicate-3.ibk3.oli2.merkle +0 -1
  137. package/sample-store/gen-1/predicate-3.ibk3.sri2 +0 -0
  138. package/sample-store/gen-1/predicate-3.ibk3.sri2.merkle +0 -1
  139. package/sample-store/gen-1/predicate-3.ibk3.tli1 +0 -0
  140. package/sample-store/gen-1/predicate-3.ibk3.tli1.merkle +0 -1
  141. package/sample-store/gen-1/predicate-4.ibk3 +0 -0
  142. package/sample-store/gen-1/predicate-4.ibk3.merkle +0 -0
  143. package/sample-store/gen-1/predicate-4.ibk3.oli2 +0 -0
  144. package/sample-store/gen-1/predicate-4.ibk3.oli2.merkle +0 -1
  145. package/sample-store/gen-1/predicate-4.ibk3.sri2 +0 -0
  146. package/sample-store/gen-1/predicate-4.ibk3.sri2.merkle +0 -1
  147. package/sample-store/gen-1/predicate-4.ibk3.tli1 +0 -0
  148. package/sample-store/gen-1/predicate-4.ibk3.tli1.merkle +0 -2
  149. package/sample-store/gen-1/predicate-5.ibk3 +0 -0
  150. package/sample-store/gen-1/predicate-5.ibk3.merkle +0 -1
  151. package/sample-store/gen-1/predicate-5.ibk3.oli2 +0 -0
  152. package/sample-store/gen-1/predicate-5.ibk3.oli2.merkle +0 -1
  153. package/sample-store/gen-1/predicate-5.ibk3.sri2 +0 -0
  154. package/sample-store/gen-1/predicate-5.ibk3.sri2.merkle +0 -1
  155. package/sample-store/gen-1/predicate-5.ibk3.tli1 +0 -0
  156. package/sample-store/gen-1/predicate-5.ibk3.tli1.merkle +0 -1
  157. package/sample-store/gen-1/predicate-6.ibk3 +0 -0
  158. package/sample-store/gen-1/predicate-6.ibk3.merkle +0 -1
  159. package/sample-store/gen-1/predicate-6.ibk3.oli2 +0 -0
  160. package/sample-store/gen-1/predicate-6.ibk3.oli2.merkle +0 -1
  161. package/sample-store/gen-1/predicate-6.ibk3.sri2 +0 -0
  162. package/sample-store/gen-1/predicate-6.ibk3.sri2.merkle +0 -1
  163. package/sample-store/gen-1/predicate-6.ibk3.tli1 +0 -0
  164. package/sample-store/gen-1/predicate-6.ibk3.tli1.merkle +0 -0
  165. package/sample-store/gen-1/predicate-7.ibk3 +0 -0
  166. package/sample-store/gen-1/predicate-7.ibk3.merkle +0 -0
  167. package/sample-store/gen-1/predicate-7.ibk3.oli2 +0 -0
  168. package/sample-store/gen-1/predicate-7.ibk3.oli2.merkle +0 -2
  169. package/sample-store/gen-1/predicate-7.ibk3.sri2 +0 -0
  170. package/sample-store/gen-1/predicate-7.ibk3.sri2.merkle +0 -1
  171. package/sample-store/gen-1/predicate-7.ibk3.tli1 +0 -0
  172. package/sample-store/gen-1/predicate-7.ibk3.tli1.merkle +0 -1
  173. package/sample-store/gen-1/predicate-8.ibk3 +0 -0
  174. package/sample-store/gen-1/predicate-8.ibk3.merkle +0 -1
  175. package/sample-store/gen-1/predicate-8.ibk3.oli2 +0 -0
  176. package/sample-store/gen-1/predicate-8.ibk3.oli2.merkle +0 -1
  177. package/sample-store/gen-1/predicate-8.ibk3.sri2 +0 -0
  178. package/sample-store/gen-1/predicate-8.ibk3.sri2.merkle +0 -1
  179. package/sample-store/gen-1/predicate-8.ibk3.tli1 +0 -0
  180. package/sample-store/gen-1/predicate-8.ibk3.tli1.merkle +0 -1
  181. package/sample-store/gen-1/predicate-9.ibk3 +0 -0
  182. package/sample-store/gen-1/predicate-9.ibk3.merkle +0 -1
  183. package/sample-store/gen-1/predicate-9.ibk3.oli2 +0 -0
  184. package/sample-store/gen-1/predicate-9.ibk3.oli2.merkle +0 -1
  185. package/sample-store/gen-1/predicate-9.ibk3.sri2 +0 -0
  186. package/sample-store/gen-1/predicate-9.ibk3.sri2.merkle +0 -3
  187. package/sample-store/gen-1/predicate-9.ibk3.tli1 +0 -0
  188. package/sample-store/gen-1/predicate-9.ibk3.tli1.merkle +0 -2
  189. package/select.d.ts +0 -116
  190. package/select.js +0 -492
  191. package/version.json +0 -102
  192. package/wasm.d.ts +0 -130
  193. package/wasm.js +0 -158
package/bin/factoidal.mjs CHANGED
@@ -24,10 +24,10 @@ import {
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'
@@ -91,7 +91,8 @@ function exit (code) {
91
91
  else process.exitCode = code
92
92
  }
93
93
 
94
- function out (line) { console.log(line) }
94
+ let outSilenced = false
95
+ function out (line) { if (!outSilenced) console.log(line) }
95
96
  function err (line) { console.error(line) }
96
97
 
97
98
  // ---------------------------------------------------------------- usage
@@ -102,13 +103,18 @@ usage: factoidal <command> [options]
102
103
 
103
104
  commands:
104
105
  version print the package and engine versions
105
- sample-store print the path of the bundled sample store
106
+ sample-store [DIR] pack a small built-in store and print its path
106
107
  inspect STORE report what the activated manifest commits
107
108
  query STORE [QUERY] evaluate a SPARQL query against a store
108
109
  pack INPUT OUTPUT build one immutable generation from an RDF file
109
110
  activate STORE GENERATION make one generation the activated generation
110
111
  update STORE [UPDATE] apply a SPARQL Update through the delta log
111
112
  compact STORE GENERATION fold the delta log into a new generation
113
+ serve STORE serve SPARQL 1.1 Protocol, Graph Store and
114
+ Service Description over HTTP
115
+ lws-serve DIR serve Linked Web Storage 1.0 over HTTP
116
+ solid-serve DIR serve the Solid Protocol over HTTP
117
+ solid-client OP URL run one Solid client operation
112
118
 
113
119
  global options:
114
120
  -h, --help print this text, or a command's own help
@@ -118,33 +124,30 @@ global options:
118
124
  exit codes:
119
125
  0 success 1 failure 2 usage error 3 not yet wired (${ISSUE})
120
126
 
121
- STORE is a collection root: the directory that holds CURRENT. This package
122
- carries one, so the first query needs no other download:
127
+ STORE is a collection root: the directory that holds CURRENT. Make one from
128
+ an RDF file with pack and activate, or try a small built-in one:
123
129
 
124
130
  factoidal query "$(factoidal sample-store)" 'SELECT * WHERE { ?s ?p ?o } LIMIT 5'`
125
131
 
126
132
  const COMMAND_USAGE = {
127
- version: `factoidal version - print the package and engine versions
133
+ 'sample-store': `factoidal sample-store - pack a small built-in store, print its path
128
134
 
129
- usage: factoidal version [--json]
135
+ usage: factoidal sample-store [DIR] [--json]
130
136
 
131
- Prints the npm package version, the Lean engine's WebAssembly digest as
132
- recorded by its build, and which host-I/O implementation is loaded.`,
137
+ Packs the few triples held in sample-store.mjs (no data file ships) into a
138
+ store under DIR, or under the OS temporary directory, activates it, and
139
+ prints the collection root. A DIR that already holds CURRENT is reused.
133
140
 
134
- 'sample-store': `factoidal sample-store - print the bundled store's path
141
+ factoidal query "$(factoidal sample-store)" \\
142
+ 'SELECT ?c ?l WHERE { ?c <http://www.w3.org/2004/02/skos/core#prefLabel> ?l }'`,
135
143
 
136
- usage: factoidal sample-store [--json]
144
+ version: `factoidal version - print the package and engine versions
137
145
 
138
- Prints the collection root of the Shardborough store this package
139
- carries, so a fresh install can query something at once:
146
+ usage: factoidal version [--json]
140
147
 
141
- factoidal inspect "$(factoidal sample-store)"
142
- factoidal query "$(factoidal sample-store)" \\
143
- 'SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }'
148
+ Prints the npm package version, the Lean engine's WebAssembly digest as
149
+ recorded by its build, and which host-I/O implementation is loaded.`,
144
150
 
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
151
 
149
152
  inspect: `factoidal inspect - report what a store's manifest commits
150
153
 
@@ -200,14 +203,16 @@ every later query from what it retained.
200
203
 
201
204
  formats:
202
205
  table a human display of the engine's SPARQL Query Results JSON;
203
- ASK prints true or false, CONSTRUCT prints its N-Triples
206
+ ASK prints true or false, CONSTRUCT and DESCRIBE print their
207
+ N-Triples
204
208
  json SELECT prints the engine's SPARQL 1.1 Query Results JSON
205
- document; ASK and CONSTRUCT print the operation's envelope,
206
- because the operation answers those two with a boolean and a
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 own Turtle
210
- writer, which flattens named graphs into the default graph
209
+ document; ASK, CONSTRUCT and DESCRIBE print the operation's
210
+ envelope, because the operation answers those with a boolean
211
+ or a serialized graph rather than with a results document
212
+ nquads CONSTRUCT and DESCRIBE only: the graph the engine serialized
213
+ turtle CONSTRUCT and DESCRIBE only: that graph through the engine's
214
+ own Turtle writer, which flattens named graphs into the default
215
+ graph
211
216
 
212
217
  not available, and why:
213
218
  --base IRI the store query operation takes no base argument;
@@ -215,8 +220,6 @@ not available, and why:
215
220
  xml, csv, tsv the engine has no operation that writes the SPARQL
216
221
  Results XML, CSV or TSV documents, and writing one
217
222
  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
223
 
221
224
  A store carrying a non-empty delta log is not served by this path: the
222
225
  operation reads the manifest's committed artifacts only. Use the native
@@ -279,7 +282,137 @@ compacted epoch. It does not activate unless --activate is given.
279
282
 
280
283
  options:
281
284
  --activate activate the new generation when it verifies
282
- --json emit one JSON object`
285
+ --json emit one JSON object`,
286
+
287
+ serve: `factoidal serve - a SPARQL 1.1 endpoint over an activated store
288
+
289
+ usage: factoidal serve STORE [--port N] [--host ADDRESS] [options]
290
+ factoidal serve --file DATA /DATASET [--port N] [options]
291
+ factoidal serve --tdb2 DIR /DATASET [--port N] [options]
292
+
293
+ The second form matches fuseki-server --file DATA /DATASET: DATA is packed
294
+ into a temporary collection (layout ibk5 unless --layout is given), served
295
+ at /DATASET/sparql (also /DATASET/query and /DATASET/update) and
296
+ /DATASET/data (also /DATASET/get, read only). Updates are lost on exit.
297
+
298
+ The third form matches fuseki-server --loc DIR /DATASET for an Apache Jena
299
+ TDB2 database: DIR is read in place, read only. SPARQL query and Graph Store
300
+ GET work; an update or a Graph Store write answers 501 with a message. The
301
+ current Data-NNNN generation is served; a database whose journal.jrnl is not
302
+ empty (Jena has not replayed it) is refused. The Lean decoder reads the
303
+ indexes and node table (formal/lean4/L4Factoidal/Tdb2/Read.lean); every
304
+ quad is held in memory while the server runs.
305
+
306
+ Serves the activated generation of STORE, with its delta log, over HTTP:
307
+
308
+ GET/POST /sparql SPARQL 1.1 Protocol query and update
309
+ (GET /sparql with no query string: the SPARQL 1.1
310
+ Service Description of the endpoint)
311
+ /data, /data/... SPARQL 1.1 Graph Store HTTP Protocol (?default,
312
+ ?graph=IRI, or a path under /data naming the graph)
313
+
314
+ options:
315
+ --port N the port to bind; 0 takes an ephemeral one (default 3030)
316
+ --host ADDRESS the address to bind (default 127.0.0.1)
317
+ --query-path PATH the query and update endpoint (default /sparql)
318
+ --gsp-path PATH the Graph Store endpoint (default /data)
319
+ --file DATA pack and serve an RDF file (see the second form)
320
+ --tdb2 DIR serve a Jena TDB2 database read only (third form)
321
+ --layout LAYOUT with --file: ibk3, ibk4 or ibk5 (default ibk5)
322
+ --no-worker serve on this thread instead of a worker thread with a
323
+ raised call stack (large collections need the worker)
324
+ --quiet print the origin only
325
+
326
+ An update, or a Graph Store PUT, POST or DELETE, is appended to the
327
+ activated generation's delta log as one batch before the response is sent,
328
+ and is visible to the next request. An IBK3 (triples) generation accepts
329
+ changes to the default graph only, because its delta log is replayed and
330
+ compacted for that graph only; a change to a named graph answers 501. A
331
+ quad generation (ibk4, ibk5) accepts every graph. Fold the log into a new
332
+ generation with l4block-shard-compact, activate it, and restart the server.
333
+
334
+ The server makes no protocol decision: the request framing, routing,
335
+ status codes, content negotiation, evaluation and the delta-log batch are
336
+ decided by formal/lean4/L4Factoidal/HTTP/Wire.lean and
337
+ formal/lean4/L4Factoidal/SPARQL/StoreEndpoint.lean through the
338
+ storeServe operations. A WebAssembly module built before those operations
339
+ landed makes this command exit 3 and say so.
340
+
341
+ Specifications: https://www.w3.org/TR/sparql11-protocol/
342
+ https://www.w3.org/TR/sparql11-http-rdf-update/
343
+ https://www.w3.org/TR/sparql11-service-description/`,
344
+
345
+ 'lws-serve': `factoidal lws-serve - serve Linked Web Storage 1.0 over HTTP
346
+
347
+ usage: factoidal lws-serve DIR [--port N] [--host ADDRESS]
348
+
349
+ Starts a Node HTTP server that answers every request through the engine's
350
+ lwsStep operation.
351
+
352
+ DIR names the storage directory. The first slice keeps the resource tree
353
+ in the engine handle, so DIR is not read or written yet; it is the place
354
+ a later snapshot goes (design record, section 2).
355
+
356
+ --port N the port to bind; 0 takes an ephemeral one (default 3000)
357
+ --host ADDRESS the address to bind (default 127.0.0.1)
358
+ --base IRI the absolute IRI the storage root maps to
359
+ (default: the bound origin)
360
+
361
+ The server makes no protocol decision: status codes, Link relations,
362
+ Last-Modified and the PATCH blank-node refusal are all answered by
363
+ formal/lean4/L4Factoidal/LWS/. A WebAssembly module built before those
364
+ operations landed makes this command exit 3 and say so.
365
+
366
+ Specification: https://w3c.github.io/lws-protocol/lws10-core/`,
367
+
368
+ 'solid-serve': `factoidal solid-serve - serve the Solid Protocol over HTTP
369
+
370
+ usage: factoidal solid-serve DIR [--port N] [--host ADDRESS]
371
+
372
+ Starts a Node HTTP server that answers every request through the engine's
373
+ solidStep operation.
374
+
375
+ DIR names the storage directory. The first slice keeps the resource tree
376
+ in the engine handle, so DIR is not read or written yet; it is the place
377
+ a later snapshot goes (design record, section 2).
378
+
379
+ --port N the port to bind; 0 takes an ephemeral one (default 3000)
380
+ --host ADDRESS the address to bind (default 127.0.0.1)
381
+ --base IRI the absolute IRI the storage root maps to
382
+ (default: the bound origin)
383
+ --owner WEBID the WebID the storage advertises as its owner
384
+
385
+ The server makes no protocol decision. Storage discovery, containment
386
+ triples, slash semantics, the Allow and Accept-* headers, the PUT/POST/
387
+ PATCH/DELETE rules, the acl and describedby lifecycle, N3 Patch, Web
388
+ Access Control, CORS and the LDN inbox are answered by
389
+ formal/lean4/L4Factoidal/Solid/Server/. A WebAssembly module built before
390
+ those operations landed makes this command exit 3 and say so.
391
+
392
+ Specification: https://solidproject.org/TR/protocol`,
393
+
394
+ 'solid-client': `factoidal solid-client - run one Solid client operation
395
+
396
+ usage: factoidal solid-client <get|put|post|delete|discover> URL [options]
397
+
398
+ Every request is built by the engine's solidClientRequest operation and
399
+ every response is read by solidClientResponse. This command supplies a
400
+ socket and prints what the engine answered.
401
+
402
+ The five verbs are aliases for the operation kinds of the wasm ABI; the
403
+ kind names are accepted directly as well.
404
+
405
+ get read put replace post create
406
+ delete delete discover discoverStorage
407
+ (also: patch, readProfile)
408
+
409
+ --body TEXT the request body for put, post and patch
410
+ --file PATH read the request body from a file
411
+ --content-type M the body's media type
412
+ --interpret KIND one of storage, containment, auxiliaries, profile,
413
+ wacAllow, used when the engine names none
414
+
415
+ Specification: https://solidproject.org/TR/protocol`
283
416
  }
284
417
 
285
418
  // ------------------------------------------------------------- arguments
@@ -331,10 +464,14 @@ const VALUE_OPTIONS = {
331
464
  'sample-store': new Set([]),
332
465
  inspect: new Set(['generation']),
333
466
  query: new Set(['query', 'file', 'format', 'limit', 'base', 'generation']),
334
- pack: new Set(['layout', 'syntax', 'chunk-bytes', 'base']),
467
+ pack: new Set(['layout', 'syntax', 'chunk-bytes', 'base', 'batch-bytes']),
335
468
  activate: new Set([]),
336
469
  update: new Set(['update', 'file']),
337
- compact: new Set([])
470
+ compact: new Set([]),
471
+ serve: new Set(['port', 'host', 'query-path', 'gsp-path', 'file', 'layout', 'tdb2']),
472
+ 'lws-serve': new Set(['port', 'host', 'base']),
473
+ 'solid-serve': new Set(['port', 'host', 'base', 'owner']),
474
+ 'solid-client': new Set(['body', 'file', 'content-type', 'interpret'])
338
475
  }
339
476
 
340
477
  // ------------------------------------------------------------- commands
@@ -348,55 +485,115 @@ function readJsonFile (path) {
348
485
  return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes))
349
486
  }
350
487
 
351
- function commandVersion (options) {
488
+ async function sha256Hex (bytes) {
489
+ const digest = await globalThis.crypto.subtle.digest('SHA-256', bytes)
490
+ return hexOfBytes(new Uint8Array(digest))
491
+ }
492
+
493
+ // `factoidal version`: what l4-assets/version.json says (the release stamp,
494
+ // written by tools/fetch-builds.sh --set web from the factoidal-builds
495
+ // release that built the wasm), what the installed wasm files hash to, and
496
+ // the loaded engine's own version(). A stamp whose sha256 differs from the
497
+ // installed wasm is an error (exit 1): the stamp then describes another
498
+ // build. A source checkout carries the unstamped placeholder.
499
+ async function commandVersion (options) {
352
500
  const root = packageDirectory()
353
501
  const own = readJsonFile(joinPath(root, 'package.json'))
354
- let engine = null
502
+ let stamp = null
355
503
  try {
356
- engine = readJsonFile(joinPath(root, 'l4-assets/version.json'))
504
+ stamp = readJsonFile(joinPath(root, 'l4-assets/version.json'))
357
505
  } catch (_error) {
358
- engine = null
506
+ stamp = null
507
+ }
508
+ const stamped = stamp !== null && stamp.stamped === true
509
+ const installed = {}
510
+ for (const [key, file] of [['wasm', 'l4factoidal.wasm']]) {
511
+ try {
512
+ const bytes = readWhole(joinPath(root, `l4-assets/${file}`))
513
+ installed[key] = { sha256: await sha256Hex(bytes), bytes: bytes.length }
514
+ } catch (_error) {
515
+ installed[key] = null
516
+ }
517
+ }
518
+ let engineVersion = null
519
+ let engineError = null
520
+ if (installed.wasm !== null) {
521
+ try {
522
+ engineVersion = (await loadEngine()).version()
523
+ } catch (error) {
524
+ engineError = String(error && error.message ? error.message : error)
525
+ }
526
+ }
527
+ const mismatches = []
528
+ if (stamped) {
529
+ for (const [key, shaKey, bytesKey] of [['wasm', 'wasmSha256', 'wasmBytes']]) {
530
+ if (installed[key] === null) continue
531
+ if (stamp[shaKey] !== installed[key].sha256 || stamp[bytesKey] !== installed[key].bytes) {
532
+ mismatches.push(`${key}: version.json says sha256 ${stamp[shaKey]} (${stamp[bytesKey]} bytes), ` +
533
+ `installed file is ${installed[key].sha256} (${installed[key].bytes} bytes)`)
534
+ }
535
+ }
536
+ if (engineVersion !== null && typeof stamp.engineVersion === 'string' &&
537
+ stamp.engineVersion !== engineVersion) {
538
+ mismatches.push(`engine: version.json says ${stamp.engineVersion}, the loaded wasm says ${engineVersion}`)
539
+ }
359
540
  }
360
541
  const report = {
361
542
  package: own.name,
362
543
  version: own.version,
363
544
  host: runtime,
364
- engine: engine === null
545
+ stamp: stamp === null
365
546
  ? null
366
- : {
367
- engine: engine.engine,
368
- version: engine.version,
369
- gitSha: engine.gitSha,
370
- leanToolchain: engine.leanToolchain,
371
- wasmSha256: engine.wasmSha256,
372
- wasmBytes: engine.wasmBytes
373
- }
547
+ : stamped
548
+ ? {
549
+ stamped: true,
550
+ engineVersion: stamp.engineVersion,
551
+ gitSha: stamp.gitSha,
552
+ builtAt: stamp.builtAt,
553
+ release: stamp.release,
554
+ leanToolchain: stamp.leanToolchain,
555
+ wasmSha256: stamp.wasmSha256,
556
+ wasmBytes: stamp.wasmBytes
557
+ }
558
+ : { stamped: false, status: stamp.status || 'stamped at release' },
559
+ installed,
560
+ engineVersion,
561
+ engineError,
562
+ mismatches
374
563
  }
375
564
  if (options.json) {
376
565
  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
566
  } else {
385
- out('engine (no l4-assets/version.json in this install)')
567
+ out(`${report.package} ${report.version}`)
568
+ out(`host-io ${report.host}`)
569
+ if (stamp === null) {
570
+ out('stamp: no l4-assets/version.json in this install')
571
+ } else if (!stamped) {
572
+ out('stamp: not stamped (l4-assets/version.json is the source placeholder; ' +
573
+ 'it is stamped at release by tools/fetch-builds.sh --set web)')
574
+ } else {
575
+ out(`stamp: built from ${stamp.gitSha} at ${stamp.builtAt}` +
576
+ (stamp.release ? ` (${stamp.release})` : ''))
577
+ out(`stamp: engine ${stamp.engineVersion} (${stamp.leanToolchain})`)
578
+ out(`stamp: wasm sha256 ${stamp.wasmSha256} (${stamp.wasmBytes} bytes)`)
579
+ }
580
+ for (const [key, label] of [['wasm', 'wasm']]) {
581
+ out(installed[key] === null
582
+ ? `installed ${label}: absent`
583
+ : `installed ${label} sha256 ${installed[key].sha256} (${installed[key].bytes} bytes)`)
584
+ }
585
+ out(engineVersion !== null
586
+ ? `engine version() ${engineVersion}`
587
+ : `engine version() unavailable${engineError ? ': ' + engineError : ''}`)
386
588
  }
387
- return EXIT_OK
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
589
+ if (mismatches.length > 0) {
590
+ for (const m of mismatches) err(`factoidal version: STAMP MISMATCH ${m}`)
591
+ return EXIT_FAILURE
395
592
  }
396
- out(path)
397
593
  return EXIT_OK
398
594
  }
399
595
 
596
+
400
597
  // ------------------------------------------------------------ rendering
401
598
 
402
599
  /**
@@ -674,7 +871,7 @@ function renderQueryResult (engine, result, format, limit, quiet) {
674
871
  return EXIT_OK
675
872
  }
676
873
  if (format !== 'table') {
677
- err(`factoidal query: --format ${format} needs a CONSTRUCT query; this one is a SELECT`)
874
+ err(`factoidal query: --format ${format} needs a CONSTRUCT or DESCRIBE query; this one is a SELECT`)
678
875
  return EXIT_FAILURE
679
876
  }
680
877
  const shown = limit === null ? bindings : bindings.slice(0, limit)
@@ -694,13 +891,13 @@ function renderQueryResult (engine, result, format, limit, quiet) {
694
891
  return EXIT_OK
695
892
  }
696
893
  if (format !== 'table') {
697
- err(`factoidal query: --format ${format} needs a CONSTRUCT query; this one is an ASK`)
894
+ err(`factoidal query: --format ${format} needs a CONSTRUCT or DESCRIBE query; this one is an ASK`)
698
895
  return EXIT_FAILURE
699
896
  }
700
897
  out(result.boolean ? 'true' : 'false')
701
898
  return EXIT_OK
702
899
  }
703
- if (result.kind === 'construct') {
900
+ if (result.kind === 'construct' || result.kind === 'describe') {
704
901
  if (format === 'json') {
705
902
  out(JSON.stringify(result, null, 2))
706
903
  return EXIT_OK
@@ -835,6 +1032,34 @@ async function commandPack (positional, options) {
835
1032
  return EXIT_OK
836
1033
  }
837
1034
 
1035
+ async function commandSampleStore (positional, options) {
1036
+ if (positional.length > 1) throw new UsageError('sample-store takes at most one DIR')
1037
+ const { mkdtempSync, writeFileSync, existsSync } = await import('node:fs')
1038
+ const { tmpdir } = await import('node:os')
1039
+ const root = positional.length === 1
1040
+ ? positional[0]
1041
+ : mkdtempSync(joinPath(tmpdir(), 'factoidal-sample-store-'))
1042
+ if (!existsSync(joinPath(root, 'CURRENT'))) {
1043
+ makeDirectory(joinPath(root, 'gen-1'))
1044
+ const source = joinPath(root, 'sample.ttl')
1045
+ writeFileSync(source, SAMPLE_TURTLE)
1046
+ outSilenced = true
1047
+ try {
1048
+ let code = await commandPack([source, joinPath(root, 'gen-1')], { quiet: true })
1049
+ if (code === EXIT_OK) code = await commandActivate([root, 'gen-1'], { quiet: true })
1050
+ if (code !== EXIT_OK) return code
1051
+ } finally {
1052
+ outSilenced = false
1053
+ }
1054
+ }
1055
+ if (options.json === true) {
1056
+ out(JSON.stringify({ path: root, ...sampleStoreFacts }, null, 2))
1057
+ } else {
1058
+ out(root)
1059
+ }
1060
+ return EXIT_OK
1061
+ }
1062
+
838
1063
  async function commandActivate (positional, options) {
839
1064
  if (positional.length !== 2) throw new UsageError('activate needs STORE and GENERATION')
840
1065
  const [root, generation] = positional
@@ -906,15 +1131,236 @@ function commandCompact (positional, _options) {
906
1131
  return notWired('compact', 'Compaction is stage 4 of the milestone.')
907
1132
  }
908
1133
 
1134
+ // ------------------------------------------------- the protocol servers
1135
+ // These commands hand a socket to a host module and print where it is
1136
+ // listening. They decide nothing about the protocol they serve; see
1137
+ // npm/factoidal/lws/README.md and npm/factoidal/solid/*/README.md.
1138
+ // https://github.com/danbri/factoidal/issues/659
1139
+
1140
+ function servePort (options, fallback) {
1141
+ if (options.port === undefined) return fallback
1142
+ const port = Number(options.port)
1143
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
1144
+ throw new UsageError(`--port must be an integer from 0 to 65535, not "${options.port}"`)
1145
+ }
1146
+ return port
1147
+ }
1148
+
1149
+ function serveHost (options) {
1150
+ if (options.host === undefined) return '127.0.0.1'
1151
+ if (typeof options.host !== 'string' || options.host.length === 0) {
1152
+ throw new UsageError('--host needs an address')
1153
+ }
1154
+ return options.host
1155
+ }
1156
+
1157
+ /** Keep the process alive until the runtime is interrupted. */
1158
+ function untilInterrupted (running, quiet) {
1159
+ return new Promise((resolve) => {
1160
+ const stop = async () => {
1161
+ if (!quiet) err('')
1162
+ await running.close()
1163
+ resolve(EXIT_OK)
1164
+ }
1165
+ if (isDeno) {
1166
+ globalThis.Deno.addSignalListener('SIGINT', stop)
1167
+ } else {
1168
+ process.on('SIGINT', stop)
1169
+ process.on('SIGTERM', stop)
1170
+ }
1171
+ })
1172
+ }
1173
+
1174
+ async function commandServe (positional, options) {
1175
+ const quiet = options.quiet === true
1176
+ // The Apache Jena Fuseki form: `serve --file DATA /ds`. The file is
1177
+ // packed into a new collection in a temporary directory (ibk5 unless
1178
+ // --layout says otherwise, so every graph accepts writes), activated, and
1179
+ // served at /ds/sparql and /ds/data; /ds/query, /ds/update and /ds/get
1180
+ // are answered too (StoreEndpoint.datasetPrefix). Updates go to that
1181
+ // temporary collection and are lost when the process ends, as with
1182
+ // fuseki-server --file. docs/fuseki-replacement.md.
1183
+ if (typeof options.file === 'string') {
1184
+ if (positional.length !== 1 || !positional[0].startsWith('/')) {
1185
+ throw new UsageError('serve --file DATA needs one dataset name, such as /ds')
1186
+ }
1187
+ const dsName = positional[0].replace(/\/+$/, '')
1188
+ const { mkdtempSync } = await import('node:fs')
1189
+ const { tmpdir } = await import('node:os')
1190
+ const { join } = await import('node:path')
1191
+ const root = mkdtempSync(join(tmpdir(), 'factoidal-serve-'))
1192
+ const packOptions = { quiet: true, layout: typeof options.layout === 'string' ? options.layout : 'ibk5' }
1193
+ let rc = await commandPack([options.file, join(root, 'gen-1')], packOptions)
1194
+ if (rc !== 0) return rc
1195
+ rc = await commandActivate([root, 'gen-1'], { quiet: true })
1196
+ if (rc !== 0) return rc
1197
+ if (!quiet) err(`serve: packed ${options.file} into ${root} (temporary; updates are lost on exit)`)
1198
+ positional = [root]
1199
+ options = { ...options, 'query-path': `${dsName}/sparql`, 'gsp-path': `${dsName}/data` }
1200
+ }
1201
+ // The Apache Jena Fuseki form: `serve --tdb2 DIR /ds`, a TDB2 database
1202
+ // read in place and read only (https://github.com/danbri/factoidal/issues/834).
1203
+ const tdb2 = typeof options.tdb2 === 'string'
1204
+ if (tdb2) {
1205
+ if (positional.length !== 1 || !positional[0].startsWith('/')) {
1206
+ throw new UsageError('serve --tdb2 DIR needs one dataset name, such as /ds')
1207
+ }
1208
+ const dsName = positional[0].replace(/\/+$/, '')
1209
+ positional = [options.tdb2]
1210
+ options = { ...options, 'query-path': `${dsName}/sparql`, 'gsp-path': `${dsName}/data` }
1211
+ }
1212
+ if (positional.length !== 1) throw new UsageError('serve needs one STORE')
1213
+ const { listen, listenOnWorker, ServeHostError } = await import('./serve.mjs')
1214
+ const serveOptions = {
1215
+ port: servePort(options, 3030),
1216
+ host: serveHost(options),
1217
+ ...(tdb2 ? { tdb2: true } : {}),
1218
+ ...(typeof options['query-path'] === 'string' ? { queryPath: options['query-path'] } : {}),
1219
+ ...(typeof options['gsp-path'] === 'string' ? { gspPath: options['gsp-path'] } : {})
1220
+ }
1221
+ const onWorker = options['no-worker'] !== true && workerRouteAvailable()
1222
+ let running
1223
+ try {
1224
+ running = onWorker
1225
+ ? await listenOnWorker(positional[0], serveOptions)
1226
+ : await listen(positional[0], serveOptions)
1227
+ } catch (error) {
1228
+ if (error instanceof ServeHostError && error.unknownOp) {
1229
+ return notWired('serve', error.message)
1230
+ }
1231
+ if (error instanceof Error && typeof error.message === 'string') {
1232
+ err(`factoidal serve: ${error.message.replace(/^l4factoidal:\s*/, '')}`)
1233
+ return EXIT_FAILURE
1234
+ }
1235
+ throw error
1236
+ }
1237
+ const info = onWorker ? running.info : running.served.info
1238
+ if (!quiet) {
1239
+ err(`serve: listening on ${running.origin} for ${positional[0]} ` +
1240
+ `(layout ${info.layout}, ${plural(info.rows, 'row')}, ` +
1241
+ `delta batches replayed: ${info.deltaBatches}, write scope ${info.writeScope})`)
1242
+ err(`serve: query ${running.origin}${info.queryPath}, graph store ${running.origin}${info.gspPath}`)
1243
+ }
1244
+ out(running.origin)
1245
+ return untilInterrupted(running, quiet)
1246
+ }
1247
+
1248
+ async function commandLwsServe (positional, options) {
1249
+ if (positional.length !== 1) throw new UsageError('lws-serve needs one DIR')
1250
+ const quiet = options.quiet === true
1251
+ const { listen, LwsHostError } = await import('../lws/server.mjs')
1252
+ let running
1253
+ try {
1254
+ running = await listen({
1255
+ port: servePort(options, 3000),
1256
+ host: serveHost(options),
1257
+ ...(typeof options.base === 'string' ? { baseIri: options.base } : {}),
1258
+ realClock: true
1259
+ })
1260
+ } catch (error) {
1261
+ if (error instanceof LwsHostError && error.unknownOp) {
1262
+ return notWired('lws-serve', error.message)
1263
+ }
1264
+ throw error
1265
+ }
1266
+ if (!quiet) err(`lws-serve: listening on ${running.origin} for ${positional[0]}`)
1267
+ out(running.origin)
1268
+ return untilInterrupted(running, quiet)
1269
+ }
1270
+
1271
+ async function commandSolidServe (positional, options) {
1272
+ if (positional.length !== 1) throw new UsageError('solid-serve needs one DIR')
1273
+ const quiet = options.quiet === true
1274
+ const { listen, SolidServerHostError } = await import('../solid/server/index.mjs')
1275
+ let running
1276
+ try {
1277
+ running = await listen({
1278
+ port: servePort(options, 3000),
1279
+ host: serveHost(options),
1280
+ ...(typeof options.base === 'string' ? { baseIri: options.base } : {}),
1281
+ ...(typeof options.owner === 'string' ? { owner: options.owner } : {}),
1282
+ realClock: true
1283
+ })
1284
+ } catch (error) {
1285
+ if (error instanceof SolidServerHostError && error.unknownOp) {
1286
+ return notWired('solid-serve', error.message)
1287
+ }
1288
+ throw error
1289
+ }
1290
+ if (!quiet) err(`solid-serve: listening on ${running.origin} for ${positional[0]}`)
1291
+ out(running.origin)
1292
+ return untilInterrupted(running, quiet)
1293
+ }
1294
+
1295
+ // The command's verbs, as aliases for the wasm ABI's request kinds. A
1296
+ // kind name is accepted directly too, so this table adds a spelling and
1297
+ // hides nothing.
1298
+ const SOLID_CLIENT_ALIASES = {
1299
+ get: 'read',
1300
+ put: 'replace',
1301
+ post: 'create',
1302
+ delete: 'delete',
1303
+ discover: 'discoverStorage'
1304
+ }
1305
+
1306
+ async function commandSolidClient (positional, options) {
1307
+ if (positional.length !== 2) {
1308
+ throw new UsageError('solid-client needs an operation and a URL')
1309
+ }
1310
+ const [verb, target] = positional
1311
+ const { createSolidClient, SolidClientHostError, SOLID_REQUEST_KINDS } =
1312
+ await import('../solid/client/index.mjs')
1313
+ const kind = Object.prototype.hasOwnProperty.call(SOLID_CLIENT_ALIASES, verb)
1314
+ ? SOLID_CLIENT_ALIASES[verb]
1315
+ : verb
1316
+ if (!SOLID_REQUEST_KINDS.includes(kind)) {
1317
+ throw new UsageError(
1318
+ `unknown operation "${verb}"; one of ` +
1319
+ `${Object.keys(SOLID_CLIENT_ALIASES).join(', ')} or ` +
1320
+ `${SOLID_REQUEST_KINDS.join(', ')}`)
1321
+ }
1322
+ const args = { target }
1323
+ if (typeof options.file === 'string') {
1324
+ args.body = new TextDecoder('utf-8', { fatal: true }).decode(readWhole(options.file))
1325
+ } else if (typeof options.body === 'string') {
1326
+ args.body = options.body
1327
+ }
1328
+ if (typeof options['content-type'] === 'string') {
1329
+ args.contentType = options['content-type']
1330
+ }
1331
+ if (['create', 'replace', 'patch'].includes(kind) && args.body === undefined) {
1332
+ throw new UsageError(`${verb} needs --body or --file`)
1333
+ }
1334
+ const extra = typeof options.interpret === 'string'
1335
+ ? { interpret: options.interpret }
1336
+ : {}
1337
+ let answer
1338
+ try {
1339
+ const client = await createSolidClient()
1340
+ answer = await client.run(kind, args, extra)
1341
+ } catch (error) {
1342
+ if (error instanceof SolidClientHostError && error.unknownOp) {
1343
+ return notWired('solid-client', error.message)
1344
+ }
1345
+ throw error
1346
+ }
1347
+ out(JSON.stringify(answer, null, 2))
1348
+ return EXIT_OK
1349
+ }
1350
+
909
1351
  const COMMANDS = {
910
1352
  version: (positional, options) => commandVersion(options),
911
- 'sample-store': (positional, options) => commandSampleStore(options),
1353
+ 'sample-store': (positional, options) => commandSampleStore(positional, options),
912
1354
  inspect: commandInspect,
913
1355
  query: commandQuery,
914
1356
  pack: commandPack,
915
1357
  activate: commandActivate,
916
1358
  update: commandUpdate,
917
- compact: commandCompact
1359
+ compact: commandCompact,
1360
+ serve: commandServe,
1361
+ 'lws-serve': commandLwsServe,
1362
+ 'solid-serve': commandSolidServe,
1363
+ 'solid-client': commandSolidClient
918
1364
  }
919
1365
 
920
1366
  // ----------------------------------------------------------------- main