@factoidal/core 0.18.0 → 0.19.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 +11 -0
- package/README.md +16 -1
- package/authz/server.mjs +5 -0
- package/bin/factoidal.mjs +47 -8
- package/index.d.ts +4 -1
- package/l4-assets/l4factoidal.js +1 -1
- package/l4-assets/l4factoidal.wasm +0 -0
- package/l4-assets/version.json +6 -6
- package/lib/version.mjs +1 -1
- package/package.json +2 -2
- package/rdfjs.mjs +10 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.19.0 (2026-10-27)
|
|
6
|
+
|
|
7
|
+
- Notice (owner decision, 2026-10-27): "EXPERIMENTAL UNREVIEWED CODE - USE WITH CARE - DO NOT CREATE PUBLIC SERVICES USING THIS WITHOUT CAREFUL INDEPENDENT EXPERT REVIEW." It is at the top of both READMEs and the hub, in the package description, in every `factoidal` help text, and on standard error when `serve`, `lws-serve`, `solid-serve`, `solid-client`, `lws-client`, `authz-serve`, `authz-keys`, `xmpp-serve` or `webdav-serve` starts.
|
|
8
|
+
- Authorization server (https://github.com/danbri/factoidal-core/pull/150): SAML 2.0 subject tokens (lws10-authn-saml, token type `urn:ietf:params:oauth:token-type:saml2`) from the identity providers the configuration trusts: `authz-serve --saml-idp FILE` (repeatable; also `lws-serve --authz`), config member `samlIdps` (`[{ entityId, jwk }]`, an RSA public JWK of 2048 bits or more). The assertion needs one enveloped XML Signature in the SAML Core §5 profile (Exclusive XML Canonicalization 1.0, rsa-sha256, SHA-256 digest, the reference `#` + the assertion's `ID`), verified under the configured key, never a key from `ds:KeyInfo`; a document type declaration, comments, processing instructions, a second signature or a wrapped assertion are refused. `saml:NameID` is the subject, the bearer `Recipient` the client; `Conditions` times and every `AudienceRestriction` must name this server. Encrypted assertions are refused (https://github.com/danbri/factoidal/issues/1073). The token type is advertised only when a provider is configured (https://github.com/danbri/factoidal/issues/926). Lean: `XML/ExcC14n.lean`, `XML/DSig.lean`, `SAML/Assertion.lean`; record `docs/designissues/2026-10-27-saml-xml-signature.md`.
|
|
9
|
+
|
|
10
|
+
Fixes for defects found by the echo test ports (https://github.com/danbri/echochamber: Jena-, RDFLib-, N3.js- and RDF4J-style test suites run against this package).
|
|
11
|
+
|
|
12
|
+
- SPARQL (https://github.com/danbri/factoidal-core/pull/146): blank nodes written by SPARQL Update get valid N-Triples labels, so CONSTRUCT, DESCRIBE and `shaclValidate` on that Dataset no longer fail (https://github.com/danbri/factoidal/issues/1074); IN and NOT IN raise an error when a comparison errs and no other element decides, so `2 IN (3, 1/0)` is an error, not false (§17.4.1.9, https://github.com/danbri/factoidal/issues/1049); ORDER BY an aggregate (`ORDER BY DESC(COUNT(?s))`) orders the groups of a grouped query (https://github.com/danbri/factoidal/issues/1050); SUBSTR takes start and length of types derived from xsd:integer, such as xsd:int (https://github.com/danbri/factoidal/issues/1060); no XSD constructor function casts a language-tagged literal: `xsd:string("foo"@en)` and `xsd:byte("1"@en)` are errors, use `STR` (§17.5, https://github.com/danbri/factoidal/issues/1062); `true.` and `false.` read as the keyword and the triple terminator (https://github.com/danbri/factoidal/issues/1075); a variable used only inside MINUS is not in scope, so `(1 AS ?z)` is allowed (§18.2.1, https://github.com/danbri/factoidal/issues/1069).
|
|
13
|
+
- Readers and writers (https://github.com/danbri/factoidal-core/pull/147): N-Triples 1.2 and N-Quads 1.2 read the VERSION directive (https://github.com/danbri/factoidal/issues/1055); TriG refuses a top-level triple with no final '.' (https://github.com/danbri/factoidal/issues/1056); Turtle and TriG read white space or a comment after `^^`, and a prefix named `true` or `false` (https://github.com/danbri/factoidal/issues/1063); one RFC 3987 IRI check: the readers, `fromQuads`/`Dataset.add`, the SPARQL parser and the writers refuse a string that is not an IRI, such as `<a_b:c>`, `<http://www:example.org/>` or `<http://a/%zz>`, with `term.invalid-iri`, so no writer writes text that no reader reads (https://github.com/danbri/factoidal/issues/1057); RDF/XML writer: CR in a literal is written `
` (https://github.com/danbri/factoidal/issues/1047), a predicate such as `urn:example:p` is written with namespace `urn:example:` (https://github.com/danbri/factoidal/issues/1048); RDF/JS: `dataFactory.literal` gives the language tag in lowercase, and the `Literal` type has `direction` (https://github.com/danbri/factoidal/issues/1054); JSON-LD: a `"@foo"` key in a value object is ignored with or without a base (https://github.com/danbri/factoidal/issues/1064); `parse()` with no format: JSON-LD only for JSON text, N-Quads when a statement names a graph; the rules are in API.md (https://github.com/danbri/factoidal/issues/1058).
|
|
14
|
+
- Cost: the IRI check makes parsing slower: 100,000 triples, median of 3, 0.18 → 0.19: N-Triples 581 → 646 ms (+11%), Turtle 606 → 718 ms (+19%), N-Quads 1.2 616 → 681 ms (+10%). Work to recover it is in progress.
|
|
15
|
+
|
|
5
16
|
## 0.18.0 (2026-10-27)
|
|
6
17
|
|
|
7
18
|
- XSD constructor functions for datatypes outside the SPARQL 1.1 §17.5 table (`xsd:byte`, `xsd:int`, `xsd:unsignedInt`, `xsd:date`, `xsd:time`, `xsd:gYear` and the other `xsd:g*` types, the duration types, `xsd:token`, …) now cast as XPath F&O 3.1 §19 says. Before, they changed only the datatype and gave ill-typed literals such as `"HaHa"^^xsd:byte`. An invalid value or a cast that F&O does not allow is now an error (the variable is unbound) (https://github.com/danbri/factoidal/issues/1041, https://github.com/danbri/factoidal-core/pull/142).
|
package/README.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @factoidal/core
|
|
2
2
|
|
|
3
|
+
> ⚠️ **EXPERIMENTAL UNREVIEWED CODE - USE WITH CARE - DO NOT CREATE PUBLIC SERVICES USING THIS WITHOUT CAREFUL INDEPENDENT EXPERT REVIEW.**
|
|
4
|
+
>
|
|
5
|
+
> This covers the whole core library and every command line tool, and in
|
|
6
|
+
> particular all protocol, authentication and security code (the LWS, Solid,
|
|
7
|
+
> SPARQL Protocol, WebDAV and XMPP servers and clients, the authorization
|
|
8
|
+
> server, JOSE, XML Signature and SAML, the crypto bindings). Owner decision,
|
|
9
|
+
> 2026-10-27.
|
|
10
|
+
|
|
3
11
|
RDF, SPARQL, reasoning, validation and Linked Data servers in one package.
|
|
4
12
|
Runs in Node 22.12+, Deno 2 and browsers. No dependencies.
|
|
5
13
|
|
|
@@ -43,7 +51,13 @@ RDF 1.2 input uses `turtle12`, `ntriples12`, `nquads12` or `trig12`.
|
|
|
43
51
|
- Canonicalization: RDFC-1.0; Verifiable Credentials Data Integrity (eddsa-rdfc-2022), did:key
|
|
44
52
|
- XML: XML 1.0, XPath 1.0, XSLT 1.0, XForms
|
|
45
53
|
- Servers: SPARQL 1.1 Protocol and Graph Store (Fuseki-compatible paths,
|
|
46
|
-
Jena TDB2 read only), Linked Web Storage 1.0, Solid
|
|
54
|
+
Jena TDB2 read only), Linked Web Storage 1.0, Solid, an OAuth 2.0
|
|
55
|
+
authorization server (token exchange for OpenID Connect ID tokens,
|
|
56
|
+
self-issued CID credentials and SAML 2.0 assertions)
|
|
57
|
+
- Security: JWS/JWT (ES256, RS256, EdDSA), XML Signature verification in the
|
|
58
|
+
SAML Core profile (Exclusive XML Canonicalization 1.0, rsa-sha256), with
|
|
59
|
+
HACL\* primitives. XML Encryption and JWE are not implemented yet:
|
|
60
|
+
https://github.com/danbri/factoidal/issues/1081
|
|
47
61
|
- A persisted store with a delta log for updates (early stage)
|
|
48
62
|
- Terms: a JavaScript extension function can answer with an exact term
|
|
49
63
|
(`{value: "007", datatype: XSD + "integer"}`, an RDF/JS term, a triple
|
|
@@ -189,6 +203,7 @@ $ npx factoidal query store 'SELECT * WHERE { ?s ?p ?o } LIMIT 5'
|
|
|
189
203
|
$ npx factoidal query "$(npx factoidal sample-store)" 'SELECT (COUNT(*) AS ?n) WHERE { ?s ?p ?o }'
|
|
190
204
|
$ npx factoidal lws-serve DIR --issuer IRI # Linked Web Storage
|
|
191
205
|
$ npx factoidal solid-serve DIR # Solid Protocol
|
|
206
|
+
$ npx factoidal authz-serve DIR --resource IRI --saml-idp idp.json # OAuth 2.0 authorization server
|
|
192
207
|
```
|
|
193
208
|
|
|
194
209
|
`npx factoidal --help` lists every command. Replacing Apache Jena Fuseki:
|
package/authz/server.mjs
CHANGED
|
@@ -169,6 +169,8 @@ async function fetchFrom (url, accept, address) {
|
|
|
169
169
|
* @param {string|null} [options.storageKey] the key text; default FACTOIDAL_LWS_KEY; null reads none
|
|
170
170
|
* @param {boolean|string[]} [options.cors] false for no CORS fields, or the origins to answer;
|
|
171
171
|
* default: echo any valid request Origin (issue 923)
|
|
172
|
+
* @param {{entityId: string, jwk: object}[]} [options.samlIdps] the SAML 2.0 identity providers
|
|
173
|
+
* whose signed assertions are accepted, each with its RSA public JWK (issue 926)
|
|
172
174
|
* @param {object} [options.engine] an engine to reuse
|
|
173
175
|
*/
|
|
174
176
|
export async function openAuthorizationServer (options = {}) {
|
|
@@ -192,6 +194,9 @@ export async function openAuthorizationServer (options = {}) {
|
|
|
192
194
|
// CORS (issue 923): false or a list of origins, passed unread; absent, the
|
|
193
195
|
// engine echoes the request Origin (L4Factoidal/Authz/Server.lean stepCors).
|
|
194
196
|
if (options.cors !== undefined) config.cors = options.cors
|
|
197
|
+
// SAML 2.0 identity providers (issue 926): [{ entityId, jwk }], passed
|
|
198
|
+
// unread; the engine checks each (Wasm/Ops/Authz.lean samlIdpsOf?).
|
|
199
|
+
if (options.samlIdps !== undefined) config.samlIdps = options.samlIdps
|
|
195
200
|
const key = storageKeyText(options.storageKey)
|
|
196
201
|
if (key !== undefined) config.storageKey = key
|
|
197
202
|
if (dir !== null) {
|
package/bin/factoidal.mjs
CHANGED
|
@@ -90,6 +90,15 @@ function err (line) { console.error(line) }
|
|
|
90
90
|
|
|
91
91
|
// ---------------------------------------------------------------- usage
|
|
92
92
|
|
|
93
|
+
// Owner, 2026-10-27: every protocol, authentication and security component,
|
|
94
|
+
// the CLIs and the core library carry this notice. It is printed with every
|
|
95
|
+
// help text and on standard error when a server, client or key command starts.
|
|
96
|
+
const REVIEW_WARNING = 'EXPERIMENTAL UNREVIEWED CODE - USE WITH CARE - DO NOT CREATE PUBLIC SERVICES USING THIS WITHOUT CAREFUL INDEPENDENT EXPERT REVIEW.'
|
|
97
|
+
|
|
98
|
+
/** The commands that serve, speak a protocol, or handle keys and tokens. */
|
|
99
|
+
const WARNED_COMMANDS = new Set(['serve', 'lws-serve', 'solid-serve', 'solid-client', 'lws-client',
|
|
100
|
+
'authz-serve', 'authz-keys', 'xmpp-serve', 'webdav-serve'])
|
|
101
|
+
|
|
93
102
|
const USAGE = `factoidal - the Factoidal persisted store, from Node or Deno
|
|
94
103
|
|
|
95
104
|
usage: factoidal <command> [options]
|
|
@@ -355,6 +364,7 @@ usage: factoidal authz-serve DIR --resource IRI[,IRI...] [--issuer IRI]
|
|
|
355
364
|
[--token-lifetime SECONDS] [--rotate-after SECONDS]
|
|
356
365
|
[--allow-http] [--allow-private-fetch]
|
|
357
366
|
[--cors-origin ORIGIN]... [--no-cors]
|
|
367
|
+
[--saml-idp FILE]...
|
|
358
368
|
|
|
359
369
|
An authorization server as a component (https://github.com/danbri/factoidal/issues/916),
|
|
360
370
|
for lws-serve and any resource server that reads RFC 8414 metadata:
|
|
@@ -363,8 +373,17 @@ for lws-serve and any resource server that reads RFC 8414 metadata:
|
|
|
363
373
|
POST /token RFC 8693 token exchange for an RFC 9068
|
|
364
374
|
access token whose aud is the resource
|
|
365
375
|
Subject tokens: OpenID Connect ID tokens (lws10-authn-openid) and
|
|
366
|
-
self-issued JWTs for https and did:key subjects (lws10-authn-ssi-cid)
|
|
367
|
-
2.0
|
|
376
|
+
self-issued JWTs for https and did:key subjects (lws10-authn-ssi-cid), and
|
|
377
|
+
SAML 2.0 assertions (lws10-authn-saml) from the identity providers --saml-idp
|
|
378
|
+
names (https://github.com/danbri/factoidal/issues/926).
|
|
379
|
+
|
|
380
|
+
--saml-idp FILE (repeatable) trusts one SAML 2.0 identity provider. FILE is
|
|
381
|
+
JSON: {"entityId": "<the saml:Issuer value>", "jwk": <its RSA public JWK>}.
|
|
382
|
+
An assertion must carry one enveloped XML Signature (exclusive
|
|
383
|
+
canonicalization, rsa-sha256) that verifies under that key, an audience that
|
|
384
|
+
names the issuer of this server, and bearer subject confirmation; its NameID
|
|
385
|
+
is the subject and its Recipient the client. Encrypted assertions are
|
|
386
|
+
refused. Without --saml-idp the token type is refused and not advertised.
|
|
368
387
|
|
|
369
388
|
--resource names the resource IRIs (for lws-serve: BASE.well-known/lws-storage)
|
|
370
389
|
tokens may be issued for; any other is refused with invalid_target.
|
|
@@ -520,7 +539,8 @@ is required):
|
|
|
520
539
|
BASE.well-known/lws-token, the JWK Set at
|
|
521
540
|
BASE.well-known/lws-jwks, signing keys in DIR (as
|
|
522
541
|
authz-serve; --alg, --token-lifetime, --rotate-after,
|
|
523
|
-
--allow-http
|
|
542
|
+
--allow-http, --allow-private-fetch and --saml-idp as
|
|
543
|
+
there). The
|
|
524
544
|
storage trusts it, and any --issuer too. Each request
|
|
525
545
|
is the authorization server's when the engine says so
|
|
526
546
|
(L4Factoidal/Authz/Mount.lean); every other is the
|
|
@@ -807,7 +827,7 @@ function parseArguments (args, valueOptions) {
|
|
|
807
827
|
class UsageError extends Error {}
|
|
808
828
|
|
|
809
829
|
/** Options that may be given more than once; their value is the list. */
|
|
810
|
-
const REPEATABLE_OPTIONS = new Set(['cors-origin'])
|
|
830
|
+
const REPEATABLE_OPTIONS = new Set(['cors-origin', 'saml-idp'])
|
|
811
831
|
|
|
812
832
|
const VALUE_OPTIONS = {
|
|
813
833
|
version: new Set([]),
|
|
@@ -821,7 +841,8 @@ const VALUE_OPTIONS = {
|
|
|
821
841
|
serve: new Set(['port', 'host', 'query-path', 'gsp-path', 'file', 'layout', 'tdb2']),
|
|
822
842
|
'lws-serve': new Set(['port', 'host', 'base', 'issuer', 'auth-config', 'owner', 'root-acl',
|
|
823
843
|
'audience', 'page-size', 'moves', 'controller-inbox', 'delivery-limit', 'delivery-window',
|
|
824
|
-
'redirects-max', 'storage', 'cors-origin', 'authz', 'alg', 'token-lifetime', 'rotate-after'
|
|
844
|
+
'redirects-max', 'storage', 'cors-origin', 'authz', 'alg', 'token-lifetime', 'rotate-after',
|
|
845
|
+
'saml-idp']),
|
|
825
846
|
'solid-serve': new Set(['port', 'host', 'base', 'owner', 'issuer', 'auth-config']),
|
|
826
847
|
'solid-client': new Set(['body', 'file', 'content-type', 'interpret']),
|
|
827
848
|
'lws-client': new Set(['body', 'file', 'content-type', 'slug', 'type', 'link', 'if-match',
|
|
@@ -829,7 +850,7 @@ const VALUE_OPTIONS = {
|
|
|
829
850
|
'target', 'subject-token-file', 'subject-token-type', 'access-token', 'storage', 'root',
|
|
830
851
|
'inbox']),
|
|
831
852
|
'authz-serve': new Set(['port', 'host', 'issuer', 'resource', 'alg', 'token-lifetime', 'rotate-after',
|
|
832
|
-
'cors-origin']),
|
|
853
|
+
'cors-origin', 'saml-idp']),
|
|
833
854
|
'authz-keys': new Set([]),
|
|
834
855
|
'xmpp-serve': new Set(['port', 'host', 'domain', 'accounts', 'cert', 'key']),
|
|
835
856
|
'webdav-serve': new Set(['port', 'host'])
|
|
@@ -1744,6 +1765,16 @@ function corsConfig (options) {
|
|
|
1744
1765
|
return {}
|
|
1745
1766
|
}
|
|
1746
1767
|
|
|
1768
|
+
// --saml-idp FILE (repeatable): a SAML 2.0 identity provider whose signed
|
|
1769
|
+
// assertions the authorization server accepts, as the config member
|
|
1770
|
+
// `samlIdps`. FILE is JSON {"entityId": ID, "jwk": RSA public JWK}; passed
|
|
1771
|
+
// unread, the engine checks it (Wasm/Ops/Authz.lean, issue 926).
|
|
1772
|
+
function samlConfig (options) {
|
|
1773
|
+
const files = options['saml-idp']
|
|
1774
|
+
if (!Array.isArray(files)) return {}
|
|
1775
|
+
return { samlIdps: files.map((f) => readJsonFile(f)) }
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1747
1778
|
function serveHost (options) {
|
|
1748
1779
|
if (options.host === undefined) return '127.0.0.1'
|
|
1749
1780
|
if (typeof options.host !== 'string' || options.host.length === 0) {
|
|
@@ -1877,7 +1908,8 @@ async function commandLwsServe (positional, options) {
|
|
|
1877
1908
|
...(options['rotate-after'] !== undefined ? { rotateAfter: natOption('rotate-after', options['rotate-after']) } : {}),
|
|
1878
1909
|
...(options['allow-http'] === true ? { allowHttp: true } : {}),
|
|
1879
1910
|
...(options['allow-private-fetch'] === true ? { allowPrivateFetch: true } : {}),
|
|
1880
|
-
...corsConfig(options)
|
|
1911
|
+
...corsConfig(options),
|
|
1912
|
+
...samlConfig(options)
|
|
1881
1913
|
}
|
|
1882
1914
|
let auth
|
|
1883
1915
|
if (authFile !== null) {
|
|
@@ -1964,7 +1996,8 @@ async function commandAuthzServe (positional, options) {
|
|
|
1964
1996
|
...(options['rotate-after'] !== undefined ? { rotateAfter: natOption('rotate-after', options['rotate-after']) } : {}),
|
|
1965
1997
|
...(options['allow-http'] === true ? { allowHttp: true } : {}),
|
|
1966
1998
|
...(options['allow-private-fetch'] === true ? { allowPrivateFetch: true } : {}),
|
|
1967
|
-
...corsConfig(options)
|
|
1999
|
+
...corsConfig(options),
|
|
2000
|
+
...samlConfig(options)
|
|
1968
2001
|
})
|
|
1969
2002
|
} catch (error) {
|
|
1970
2003
|
if (error !== null && typeof error === 'object' && error.envelope !== undefined) {
|
|
@@ -2287,12 +2320,14 @@ const COMMANDS = {
|
|
|
2287
2320
|
|
|
2288
2321
|
async function main () {
|
|
2289
2322
|
if (argv.length === 0) {
|
|
2323
|
+
out(REVIEW_WARNING + '\n')
|
|
2290
2324
|
out(USAGE)
|
|
2291
2325
|
return EXIT_USAGE
|
|
2292
2326
|
}
|
|
2293
2327
|
const command = argv[0]
|
|
2294
2328
|
if (command === '--help' || command === '-h' || command === 'help') {
|
|
2295
2329
|
const topic = argv[1]
|
|
2330
|
+
out(REVIEW_WARNING + '\n')
|
|
2296
2331
|
if (typeof topic === 'string' && Object.prototype.hasOwnProperty.call(COMMAND_USAGE, topic)) {
|
|
2297
2332
|
out(COMMAND_USAGE[topic])
|
|
2298
2333
|
} else {
|
|
@@ -2317,9 +2352,13 @@ async function main () {
|
|
|
2317
2352
|
throw error
|
|
2318
2353
|
}
|
|
2319
2354
|
if (parsed.options.help) {
|
|
2355
|
+
out(REVIEW_WARNING + '\n')
|
|
2320
2356
|
out(COMMAND_USAGE[command])
|
|
2321
2357
|
return EXIT_OK
|
|
2322
2358
|
}
|
|
2359
|
+
if (WARNED_COMMANDS.has(command)) {
|
|
2360
|
+
err(`factoidal ${command}: WARNING: ${REVIEW_WARNING}`)
|
|
2361
|
+
}
|
|
2323
2362
|
try {
|
|
2324
2363
|
return await COMMANDS[command](parsed.positional, parsed.options)
|
|
2325
2364
|
} catch (error) {
|
package/index.d.ts
CHANGED
|
@@ -27,8 +27,11 @@ export interface BlankNode {
|
|
|
27
27
|
export interface Literal {
|
|
28
28
|
termType: 'Literal';
|
|
29
29
|
value: string;
|
|
30
|
-
/** Language tag ('' when none). */
|
|
30
|
+
/** Language tag, lowercase ('' when none). */
|
|
31
31
|
language: string;
|
|
32
|
+
/** Base direction of a directional language-tagged string (RDF 1.2):
|
|
33
|
+
* 'ltr' or 'rtl'; '' when none. RDF/JS Data Model, Literal.direction. */
|
|
34
|
+
direction: '' | 'ltr' | 'rtl';
|
|
32
35
|
/** Datatype IRI term (xsd:string for plain literals). */
|
|
33
36
|
datatype: NamedNode;
|
|
34
37
|
equals(other: Term | null | undefined): boolean;
|
package/l4-assets/l4factoidal.js
CHANGED
|
Binary file
|
package/l4-assets/version.json
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
"engine": "lean4",
|
|
3
3
|
"note": "The Lean 4 (L4Factoidal) engine build provenance and claims: the only engine of @factoidal/core since 0.9.0 (issue 713). This committed file is a placeholder. tools/fetch-builds.sh --set web (through tools/stamp-l4-version.mjs) writes the provenance members from the danbri/factoidal-builds release that built the wasm; formal/lean4/Wasm/build-wasm.sh step 9 writes them for a local build. Do not commit a stamped copy.",
|
|
4
4
|
"stamped": true,
|
|
5
|
-
"release": "danbri/factoidal-builds build-
|
|
6
|
-
"releaseRun": "https://github.com/danbri/factoidal-builds/actions/runs/
|
|
7
|
-
"gitSha": "
|
|
8
|
-
"builtAt": "2026-10-
|
|
5
|
+
"release": "danbri/factoidal-builds build-b8c6b64b8dc1-web",
|
|
6
|
+
"releaseRun": "https://github.com/danbri/factoidal-builds/actions/runs/40525043015",
|
|
7
|
+
"gitSha": "b8c6b64b8dc18a6daa2151a61e28dbb9a4707ade",
|
|
8
|
+
"builtAt": "2026-10-27T15:28:08Z",
|
|
9
9
|
"leanToolchain": "leanprover/lean4:v4.33.1",
|
|
10
10
|
"emscripten": "6.0.8",
|
|
11
11
|
"abiVersion": "1",
|
|
12
12
|
"engineVersion": "l4factoidal-wasm 0.2.0 (dispatch: RDF parse/serialize, SPARQL query+update, RDFS/OWL-RL closures, RDFC-1.0, CL/IKL)",
|
|
13
13
|
"version": "l4factoidal-wasm 0.2.0 (dispatch: RDF parse/serialize, SPARQL query+update, RDFS/OWL-RL closures, RDFC-1.0, CL/IKL)",
|
|
14
|
-
"wasmSha256": "
|
|
15
|
-
"wasmBytes":
|
|
14
|
+
"wasmSha256": "6dbf38660cb9966ae7c65ecdb68a38e83d09c1283415c1486b618f203f3f2f39",
|
|
15
|
+
"wasmBytes": 17694204,
|
|
16
16
|
"claims": {
|
|
17
17
|
"source": "formal/lean4 (L4Factoidal): no sorry, no user axioms, no native_decide; W3C behaviour pinned by build-time #guard",
|
|
18
18
|
"suitesAtBuildSha": "see docs/test-results and formal/lean4/PORT_NOTES.md at gitSha"
|
package/lib/version.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@factoidal/core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "RDF parsing and writing (Turtle, TriG, N-Triples, N-Quads, RDF/XML, JSON-LD), SPARQL 1.1 and 1.2, RDFS and OWL reasoning, SHACL and ShEx validation, CSVW and RML mapping, RDFC-1.0, and SPARQL, LWS and Solid servers, for Node, Deno and browsers.",
|
|
3
|
+
"version": "0.19.0",
|
|
4
|
+
"description": "EXPERIMENTAL UNREVIEWED CODE: do not run public services on it without careful independent expert review. RDF parsing and writing (Turtle, TriG, N-Triples, N-Quads, RDF/XML, JSON-LD), SPARQL 1.1 and 1.2, RDFS and OWL reasoning, SHACL and ShEx validation, CSVW and RML mapping, RDFC-1.0, and SPARQL, LWS and Solid servers, for Node, Deno and browsers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"sparql",
|
|
7
7
|
"rdf",
|
package/rdfjs.mjs
CHANGED
|
@@ -52,11 +52,20 @@ class BlankNode {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
// RDF/JS Data Model, interface Literal: "language the language as lowercase
|
|
56
|
+
// [BCP47] string (examples: "en", "en-gb")". A BCP 47 tag is ASCII (RFC 5646
|
|
57
|
+
// section 2.1), and the engine's readers lowercase its ASCII letters (Lean
|
|
58
|
+
// String.toLower), so a factory term equals the same term read from text
|
|
59
|
+
// (https://github.com/danbri/factoidal/issues/1054).
|
|
60
|
+
function lowerAscii(s) {
|
|
61
|
+
return s.replace(/[A-Z]+/g, (m) => m.toLowerCase());
|
|
62
|
+
}
|
|
63
|
+
|
|
55
64
|
class Literal {
|
|
56
65
|
constructor(value, language, datatype, direction) {
|
|
57
66
|
this.termType = 'Literal';
|
|
58
67
|
this.value = value;
|
|
59
|
-
this.language = language
|
|
68
|
+
this.language = language ? lowerAscii(String(language)) : '';
|
|
60
69
|
// RDF/JS Data Model DataFactory.literal: no datatype argument means
|
|
61
70
|
// xsd:string, a language means rdf:langString, a language and a
|
|
62
71
|
// direction mean rdf:dirLangString.
|