@factoidal/core 0.10.0 → 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 +114 -1
- package/README.md +7 -1
- package/bin/ext.mjs +16 -20
- package/bin/factoidal.mjs +172 -69
- package/browser.d.ts +4 -0
- package/browser.js +4 -0
- package/fn.d.ts +1 -1
- package/fn.js +34 -97
- package/index.d.ts +22 -1
- package/index.mjs +4 -0
- package/l4-assets/l4factoidal.js +1 -1
- package/l4-assets/l4factoidal.mjs +1 -1
- package/l4-assets/l4factoidal.wasm +0 -0
- package/l4-assets/version.json +6 -6
- package/lib/api.mjs +155 -118
- package/lib/dataset.mjs +128 -34
- package/lib/lean-entry.mjs +23 -14
- package/lib/version.mjs +1 -1
- package/lws/README.md +46 -6
- package/lws/persist.mjs +78 -0
- package/lws/server.mjs +101 -16
- package/package.json +1 -1
- package/rdfjs.mjs +149 -226
- package/solid/client/index.mjs +25 -9
- package/solid/server/index.mjs +93 -15
package/solid/server/index.mjs
CHANGED
|
@@ -23,6 +23,16 @@
|
|
|
23
23
|
// solidOpen [configJson] -> { ok, handle, root, storage }
|
|
24
24
|
// solidStep [handle, requestJson] -> { ok, response }
|
|
25
25
|
// solidClose [handle] -> { ok }
|
|
26
|
+
// solidFetched [handle, documentJson] -> { ok, fetch? | installed? | refused? }
|
|
27
|
+
//
|
|
28
|
+
// Solid-OIDC (issue 899): with `auth` in the configuration the engine
|
|
29
|
+
// verifies the DPoP-bound access token itself
|
|
30
|
+
// (formal/lean4/L4Factoidal/Solid/Server/Oidc.lean). When it needs a
|
|
31
|
+
// document it does not hold -- an issuer's OpenID configuration, its JWK
|
|
32
|
+
// Set, or a WebID profile -- `solidStep` answers `{ fetch: {kind, key,
|
|
33
|
+
// url} }`. This host fetches exactly that URL, hands status and body to
|
|
34
|
+
// `solidFetched` unread, follows any `fetch` that answers, and steps the
|
|
35
|
+
// request again. It chooses no URL and reads no document.
|
|
26
36
|
//
|
|
27
37
|
// request { method, target, headers: [[name, value]], body }
|
|
28
38
|
// response { status, headers: [[name, value]], body }
|
|
@@ -37,19 +47,39 @@
|
|
|
37
47
|
// ephemeral port the origin is not known until the socket is bound, so
|
|
38
48
|
// the storage handle is opened after binding rather than before.
|
|
39
49
|
//
|
|
40
|
-
// `agent` is the WebID of the requesting agent.
|
|
41
|
-
//
|
|
42
|
-
// engine
|
|
43
|
-
// is an unauthenticated agent and that is what these tests exercise.
|
|
50
|
+
// `agent` is the WebID of the requesting agent. It stands only when the
|
|
51
|
+
// configuration has no `auth` (or has `insecureNoAuth`); with `auth` the
|
|
52
|
+
// engine decides the WebID from the credential.
|
|
44
53
|
//
|
|
45
54
|
// This file shares no code with `../client/index.mjs` beyond the engine
|
|
46
55
|
// loader, by the design record's rule that the two conformance classes
|
|
47
56
|
// stay distinct.
|
|
48
57
|
|
|
49
58
|
import { loadEngine } from '../../bin/engine.mjs'
|
|
59
|
+
import { readSnapshot, persistIfChanged } from '../../lws/persist.mjs'
|
|
50
60
|
|
|
51
61
|
/** The ops this host needs from the engine. */
|
|
52
|
-
export const SOLID_SERVER_OPS = ['solidOpen', 'solidStep', 'solidClose'
|
|
62
|
+
export const SOLID_SERVER_OPS = ['solidOpen', 'solidStep', 'solidClose', 'solidSnapshot',
|
|
63
|
+
'solidFetched']
|
|
64
|
+
|
|
65
|
+
/** The most documents one request may make this host fetch. The engine
|
|
66
|
+
* names at most three per request (issuer metadata, key set, profile);
|
|
67
|
+
* this bound only stops a defective module from looping the host. */
|
|
68
|
+
const MAX_FETCHES = 8
|
|
69
|
+
|
|
70
|
+
/** Fetch one document the engine named. Pure I/O; a network failure is
|
|
71
|
+
* status 0, which the engine refuses like any failed fetch. */
|
|
72
|
+
async function fetchDocument (url) {
|
|
73
|
+
try {
|
|
74
|
+
const answer = await fetch(url, {
|
|
75
|
+
redirect: 'error',
|
|
76
|
+
headers: { accept: 'text/turtle, application/json;q=0.9, */*;q=0.1' }
|
|
77
|
+
})
|
|
78
|
+
return { status: answer.status, body: await answer.text() }
|
|
79
|
+
} catch (error) {
|
|
80
|
+
return { status: 0, body: String(error && error.message ? error.message : error) }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
53
83
|
|
|
54
84
|
/** An error raised by the Solid server host. `unknownOp` is set when the
|
|
55
85
|
* loaded WebAssembly module does not carry the operation at all, which
|
|
@@ -103,7 +133,7 @@ function handleOf (envelope) {
|
|
|
103
133
|
return null
|
|
104
134
|
}
|
|
105
135
|
|
|
106
|
-
function responseOf (envelope) {
|
|
136
|
+
function responseOf (envelope, bytes) {
|
|
107
137
|
if (envelope === null || typeof envelope !== 'object') {
|
|
108
138
|
throw new SolidServerHostError('solidStep answered with no envelope')
|
|
109
139
|
}
|
|
@@ -117,10 +147,19 @@ function responseOf (envelope) {
|
|
|
117
147
|
return {
|
|
118
148
|
status: record.status,
|
|
119
149
|
headers: Array.isArray(record.headers) ? record.headers : [],
|
|
120
|
-
body:
|
|
150
|
+
body: bytes instanceof Uint8Array ? bytes : new Uint8Array(0)
|
|
121
151
|
}
|
|
122
152
|
}
|
|
123
153
|
|
|
154
|
+
/** The request content as bytes: a byte array is passed as it is, and a
|
|
155
|
+
* string (a JavaScript caller's convenience) as its UTF-8 bytes. Bodies
|
|
156
|
+
* are bytes end to end (https://github.com/danbri/factoidal/issues/889). */
|
|
157
|
+
function contentBytes (request) {
|
|
158
|
+
if (request.body instanceof Uint8Array) return request.body
|
|
159
|
+
if (typeof request.body === 'string') return new TextEncoder().encode(request.body)
|
|
160
|
+
return new Uint8Array(0)
|
|
161
|
+
}
|
|
162
|
+
|
|
124
163
|
/**
|
|
125
164
|
* Open one Solid storage on the engine.
|
|
126
165
|
*
|
|
@@ -141,6 +180,19 @@ export async function openSolidStorage (options = {}) {
|
|
|
141
180
|
if (typeof options.now === 'number') config.now = options.now
|
|
142
181
|
if (typeof options.owner === 'string') config.owner = options.owner
|
|
143
182
|
if (typeof options.agent === 'string') config.agent = options.agent
|
|
183
|
+
// Solid-OIDC: `auth` is passed through unread; `insecureNoAuth` is the
|
|
184
|
+
// explicit development mode.
|
|
185
|
+
if (options.auth !== undefined && options.auth !== null) config.auth = options.auth
|
|
186
|
+
if (options.insecureNoAuth === true) config.insecureNoAuth = true
|
|
187
|
+
if (typeof options.enforceWac === 'boolean') config.enforceWac = options.enforceWac
|
|
188
|
+
if (typeof options.rootAcl === 'string') config.rootAcl = options.rootAcl
|
|
189
|
+
// `dir` names the storage directory. The snapshot found there, if any,
|
|
190
|
+
// is the storage to continue from; the engine checks it.
|
|
191
|
+
const dir = typeof options.dir === 'string' ? options.dir : null
|
|
192
|
+
if (dir !== null) {
|
|
193
|
+
const snapshot = readSnapshot(dir)
|
|
194
|
+
if (snapshot !== null) config.snapshot = snapshot
|
|
195
|
+
}
|
|
144
196
|
let opened
|
|
145
197
|
try {
|
|
146
198
|
opened = engine.call('solidOpen', [JSON.stringify(config)])
|
|
@@ -163,14 +215,40 @@ export async function openSolidStorage (options = {}) {
|
|
|
163
215
|
/**
|
|
164
216
|
* One protocol step.
|
|
165
217
|
* @param {{method: string, target: string,
|
|
166
|
-
* headers: Array<[string, string]>, body: string}} request
|
|
167
|
-
* @returns {{status: number, headers: Array<[string, string]>, body:
|
|
218
|
+
* headers: Array<[string, string]>, body: Uint8Array|string}} request
|
|
219
|
+
* @returns {{status: number, headers: Array<[string, string]>, body: Uint8Array}}
|
|
168
220
|
*/
|
|
169
|
-
step (request) {
|
|
221
|
+
async step (request) {
|
|
222
|
+
const content = contentBytes(request)
|
|
223
|
+
const { body: _body, ...fields } = request
|
|
170
224
|
const record = options.realClock === true
|
|
171
|
-
? { ...
|
|
172
|
-
:
|
|
173
|
-
|
|
225
|
+
? { ...fields, now: Math.floor(Date.now() / 1000) }
|
|
226
|
+
: fields
|
|
227
|
+
// The byte-region entry: request content in, response content out.
|
|
228
|
+
const callStep = () =>
|
|
229
|
+
engine.callBlobIO('solidStep', [handle, JSON.stringify(record)], content)
|
|
230
|
+
let { envelope, bytes } = callStep()
|
|
231
|
+
let fetches = 0
|
|
232
|
+
while (envelope !== null && typeof envelope === 'object' &&
|
|
233
|
+
envelope.fetch !== undefined && envelope.fetch !== null) {
|
|
234
|
+
let wanted = envelope.fetch
|
|
235
|
+
while (wanted !== undefined && wanted !== null) {
|
|
236
|
+
fetches += 1
|
|
237
|
+
if (fetches > MAX_FETCHES) {
|
|
238
|
+
throw new SolidServerHostError('the engine asked for more documents than one request may fetch')
|
|
239
|
+
}
|
|
240
|
+
const got = await fetchDocument(wanted.url)
|
|
241
|
+
const told = engine.call('solidFetched', [handle, JSON.stringify({
|
|
242
|
+
kind: wanted.kind, key: wanted.key, url: wanted.url, status: got.status, body: got.body,
|
|
243
|
+
...(typeof record.now === 'number' ? { now: record.now } : {})
|
|
244
|
+
})])
|
|
245
|
+
wanted = told !== null && typeof told === 'object' ? told.fetch : null
|
|
246
|
+
}
|
|
247
|
+
({ envelope, bytes } = callStep())
|
|
248
|
+
}
|
|
249
|
+
const answer = responseOf(envelope, bytes)
|
|
250
|
+
if (dir !== null) persistIfChanged(engine, 'solidSnapshot', handle, envelope, dir)
|
|
251
|
+
return answer
|
|
174
252
|
},
|
|
175
253
|
close () {
|
|
176
254
|
try { engine.call('solidClose', [handle]) } catch (_error) { /* already gone */ }
|
|
@@ -183,7 +261,7 @@ function readBody (request) {
|
|
|
183
261
|
const chunks = []
|
|
184
262
|
request.on('data', (chunk) => chunks.push(chunk))
|
|
185
263
|
request.on('error', reject)
|
|
186
|
-
request.on('end', () => resolve(Buffer.concat(chunks)
|
|
264
|
+
request.on('end', () => resolve(new Uint8Array(Buffer.concat(chunks))))
|
|
187
265
|
})
|
|
188
266
|
}
|
|
189
267
|
|
|
@@ -240,7 +318,7 @@ export async function createSolidServer (options = {}) {
|
|
|
240
318
|
}
|
|
241
319
|
let answer
|
|
242
320
|
try {
|
|
243
|
-
answer = (await storage()).step(record)
|
|
321
|
+
answer = await (await storage()).step(record)
|
|
244
322
|
} catch (error) {
|
|
245
323
|
onError(error)
|
|
246
324
|
response.writeHead(500, { 'content-type': 'text/plain' })
|