@dwk/rdf 0.1.0-beta.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/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 David W. Keith
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,125 @@
1
+ # `@dwk/rdf`
2
+
3
+ > Thin Turtle/JSON-LD parse and serialize layer over N3.js. Cross-standard reusable.
4
+
5
+ Part of the [`@dwk` IndieWeb + Solid cohort](../../README.md). See the
6
+ [package specification](../../spec/packages/rdf.md) for the full requirements.
7
+
8
+ This package is **cross-standard reusable**: it takes plain-data inputs only,
9
+ has no Workers-runtime dependency, and unit-tests in isolation (Node, no
10
+ `workerd`).
11
+
12
+ ## API
13
+
14
+ ### Turtle family (over N3.js)
15
+
16
+ ```ts
17
+ import { parseTurtle, writeTurtle } from "@dwk/rdf";
18
+
19
+ const quads = parseTurtle(turtleString, { baseIRI });
20
+ const out = await writeTurtle(quads, { format: "N-Triples" });
21
+ ```
22
+
23
+ `format` accepts the N3.js identifiers `"Turtle"`, `"TriG"`, `"N-Triples"`,
24
+ `"N-Quads"`.
25
+
26
+ ### JSON-LD
27
+
28
+ ```ts
29
+ import { parseJsonLd, writeJsonLd } from "@dwk/rdf";
30
+
31
+ const quads = await parseJsonLd(jsonLdStringOrObject, { base });
32
+ const out = await writeJsonLd(quads); // expanded / flattened form
33
+ ```
34
+
35
+ ### Content negotiation
36
+
37
+ `parse` / `serialize` dispatch by media type — the entry points
38
+ `@dwk/solid-pod` uses for content negotiation:
39
+
40
+ ```ts
41
+ import { parse, serialize, formatForMediaType } from "@dwk/rdf";
42
+
43
+ const quads = await parse(body, request.headers.get("content-type")!);
44
+ const body = await serialize(quads, "text/turtle");
45
+ ```
46
+
47
+ Recognized media types: `text/turtle`, `application/trig`,
48
+ `application/n-triples`, `application/n-quads`, `application/ld+json`.
49
+ Media-type parameters (e.g. `; charset=utf-8`, `; profile=…`) and casing are
50
+ ignored. Note `application/json` is **not** treated as RDF — JSON-LD's media
51
+ type is `application/ld+json`, and auto-parsing arbitrary `application/json`
52
+ bodies as a graph is a correctness/security hazard on write. (A read-only
53
+ `application/json` → JSON-LD convenience can be opted into at the negotiation
54
+ layer; `@dwk/solid-pod` does this on read.)
55
+
56
+ ### Triple ↔ store helpers
57
+
58
+ `termToStored` / `storedToTerm` / `quadToStored` / `storedToQuad` convert
59
+ between RDF-JS terms/quads and a flat, JSON-serializable `StoredQuad` shape that
60
+ maps onto the DO-SQLite columns in [`@dwk/store`](../store) and survives a
61
+ structured-clone boundary.
62
+
63
+ ```ts
64
+ import { quadToStored, storedToQuad } from "@dwk/rdf";
65
+
66
+ const row = quadToStored(quad); // { subject, predicate, object, graph }
67
+ const quad = storedToQuad(row);
68
+ ```
69
+
70
+ ## JSON-LD: the chosen approach and supported subset
71
+
72
+ N3.js does not handle JSON-LD, and `jsonld.js` is too large for the Worker
73
+ script-size budget (see
74
+ [non-functional-requirements.md](../../spec/non-functional-requirements.md#runtime-budget)).
75
+ This package therefore ships a **dependency-free JSON-LD ⇄ RDF converter** (zero
76
+ added bytes beyond N3.js) implementing a pragmatic subset of JSON-LD 1.0
77
+ toRDF/fromRDF — the decision that resolves
78
+ [open-questions.md §4](../../spec/open-questions.md).
79
+
80
+ **Parse (JSON-LD → quads) supports:**
81
+
82
+ - Inline `@context` (object, or array of objects); context arrays; resetting
83
+ with `null`.
84
+ - Context features: term → IRI mappings, prefix/CURIE expansion (`prefix:term`),
85
+ `@vocab`, `@base`, default `@language`, and expanded term definitions with
86
+ `@id`, `@type` (datatype IRI, `@id`, or `@vocab`), `@language`, and
87
+ `@container: @list`.
88
+ - Node objects with `@id` (IRIs and `_:` blank nodes), `@type`, nested node
89
+ objects, and node references.
90
+ - Value objects (`@value` + `@type` / `@language`) and native scalars typed per
91
+ JSON-LD rules (string → `xsd:string` or a language string, integer →
92
+ `xsd:integer`, fractional → `xsd:double` in canonical lexical form, boolean →
93
+ `xsd:boolean`). A `@value` of `null` produces no triple. Relative `@id`/IRIs
94
+ that no `@base`/`base` resolves to an absolute IRI are dropped rather than
95
+ emitted as invalid terms.
96
+ - Lists (`@container: @list` and inline `@list`) → `rdf:first`/`rdf:rest`/
97
+ `rdf:nil`.
98
+ - `@graph`: top-level wrapper (default graph) and named graphs (a node with
99
+ `@id` + `@graph`).
100
+ - `@reverse` properties.
101
+
102
+ **Serialize (quads → JSON-LD)** emits **expanded / flattened** form (node
103
+ objects keyed by full IRIs, no `@context`), reconstructing well-formed
104
+ `rdf:first`/`rdf:rest`/`rdf:nil` chains back into `@list`. This form round-trips
105
+ through `parseJsonLd` at the RDF (quad) level. One inherent caveat: an empty
106
+ `@list` becomes `rdf:nil`, which the JSON-LD data model cannot distinguish from
107
+ a property whose value is literally `rdf:nil`, so empty lists serialize as an
108
+ `rdf:nil` reference.
109
+
110
+ **Out of scope for v1** (documented limitations; `JsonLdError` is thrown where
111
+ detectable):
112
+
113
+ - **Remote / URL contexts** — contexts must be inlined. A string `@context`
114
+ throws.
115
+ - Framing and `@reverse` containers, `@index` / `@included` maps, `@nest`,
116
+ scoped contexts, type-scoped contexts, and JSON-LD 1.1 `@json` / `@direction`.
117
+ - Compaction to a supplied context on serialize — output is always expanded.
118
+
119
+ These are sufficient for Solid/IndieWeb content negotiation, where documents are
120
+ served with controlled, inlinable contexts. The subset can be widened later
121
+ without changing the public API.
122
+
123
+ ## License
124
+
125
+ [ISC](../../LICENSE)
@@ -0,0 +1,36 @@
1
+ import type { Quad } from "n3";
2
+ /**
3
+ * `@dwk/rdf` — thin Turtle/JSON-LD parse and serialize layer over N3.js.
4
+ *
5
+ * Cross-standard reusable: plain-data inputs only, no Workers runtime
6
+ * dependency.
7
+ *
8
+ * @see spec/packages/rdf.md
9
+ */
10
+ export { parseTurtle, writeTurtle, type ParseTurtleOptions, type WriteTurtleOptions, } from "./turtle";
11
+ export { parseJsonLd, writeJsonLd, JsonLdError, type ParseJsonLdOptions, type WriteJsonLdOptions, type JsonValue, type JsonObject, } from "./jsonld";
12
+ export { formatForMediaType, MEDIA_TYPE_FORMATS, type RdfFormat, } from "./media-types";
13
+ export { termToStored, storedToTerm, quadToStored, storedToQuad, type StoredTerm, type StoredQuad, type StoredTermType, } from "./store";
14
+ export type { Quad };
15
+ /** Options for {@link parse} / {@link serialize}. */
16
+ export interface NegotiationOptions {
17
+ /** Base IRI used to resolve relative IRIs (Turtle family + JSON-LD). */
18
+ readonly baseIRI?: string;
19
+ /** Prefixes to declare when serializing the Turtle family. */
20
+ readonly prefixes?: Record<string, string>;
21
+ }
22
+ /**
23
+ * Parse an RDF document by media type — the content-negotiation entry point for
24
+ * `@dwk/solid-pod`. Dispatches to the Turtle family or JSON-LD code path.
25
+ *
26
+ * @throws if the media type is not a supported RDF serialization.
27
+ */
28
+ export declare function parse(input: string, mediaType: string, options?: NegotiationOptions): Promise<Quad[]>;
29
+ /**
30
+ * Serialize quads by media type — the content-negotiation entry point for
31
+ * `@dwk/solid-pod`. Dispatches to the Turtle family or JSON-LD code path.
32
+ *
33
+ * @throws if the media type is not a supported RDF serialization.
34
+ */
35
+ export declare function serialize(quads: Quad[], mediaType: string, options?: NegotiationOptions): Promise<string>;
36
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAE/B;;;;;;;GAOG;AAEH,OAAO,EACL,WAAW,EACX,WAAW,EACX,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,GACxB,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,WAAW,EACX,WAAW,EACX,WAAW,EACX,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,KAAK,UAAU,GAChB,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,KAAK,SAAS,GACf,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,cAAc,GACpB,MAAM,SAAS,CAAC;AAEjB,YAAY,EAAE,IAAI,EAAE,CAAC;AAErB,qDAAqD;AACrD,MAAM,WAAW,kBAAkB;IACjC,wEAAwE;IACxE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5C;AAED;;;;;GAKG;AACH,wBAAsB,KAAK,CACzB,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,IAAI,EAAE,CAAC,CASjB;AAED;;;;;GAKG;AACH,wBAAsB,SAAS,CAC7B,KAAK,EAAE,IAAI,EAAE,EACb,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,MAAM,CAAC,CASjB"}
package/dist/index.js ADDED
@@ -0,0 +1,48 @@
1
+ import { formatForMediaType } from "./media-types";
2
+ import { parseJsonLd, writeJsonLd } from "./jsonld";
3
+ import { parseTurtle, writeTurtle } from "./turtle";
4
+ /**
5
+ * `@dwk/rdf` — thin Turtle/JSON-LD parse and serialize layer over N3.js.
6
+ *
7
+ * Cross-standard reusable: plain-data inputs only, no Workers runtime
8
+ * dependency.
9
+ *
10
+ * @see spec/packages/rdf.md
11
+ */
12
+ export { parseTurtle, writeTurtle, } from "./turtle";
13
+ export { parseJsonLd, writeJsonLd, JsonLdError, } from "./jsonld";
14
+ export { formatForMediaType, MEDIA_TYPE_FORMATS, } from "./media-types";
15
+ export { termToStored, storedToTerm, quadToStored, storedToQuad, } from "./store";
16
+ /**
17
+ * Parse an RDF document by media type — the content-negotiation entry point for
18
+ * `@dwk/solid-pod`. Dispatches to the Turtle family or JSON-LD code path.
19
+ *
20
+ * @throws if the media type is not a supported RDF serialization.
21
+ */
22
+ export async function parse(input, mediaType, options) {
23
+ const format = formatForMediaType(mediaType);
24
+ if (!format) {
25
+ throw new Error(`@dwk/rdf: unsupported media type "${mediaType}"`);
26
+ }
27
+ if (format === "JSON-LD") {
28
+ return parseJsonLd(input, { base: options?.baseIRI });
29
+ }
30
+ return parseTurtle(input, { format, baseIRI: options?.baseIRI });
31
+ }
32
+ /**
33
+ * Serialize quads by media type — the content-negotiation entry point for
34
+ * `@dwk/solid-pod`. Dispatches to the Turtle family or JSON-LD code path.
35
+ *
36
+ * @throws if the media type is not a supported RDF serialization.
37
+ */
38
+ export async function serialize(quads, mediaType, options) {
39
+ const format = formatForMediaType(mediaType);
40
+ if (!format) {
41
+ throw new Error(`@dwk/rdf: unsupported media type "${mediaType}"`);
42
+ }
43
+ if (format === "JSON-LD") {
44
+ return writeJsonLd(quads);
45
+ }
46
+ return writeTurtle(quads, { format, prefixes: options?.prefixes });
47
+ }
48
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAGpD;;;;;;;GAOG;AAEH,OAAO,EACL,WAAW,EACX,WAAW,GAGZ,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,WAAW,EACX,WAAW,EACX,WAAW,GAKZ,MAAM,UAAU,CAAC;AAElB,OAAO,EACL,kBAAkB,EAClB,kBAAkB,GAEnB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,YAAY,GAIb,MAAM,SAAS,CAAC;AAYjB;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CACzB,KAAa,EACb,SAAiB,EACjB,OAA4B;IAE5B,MAAM,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;IAC7C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,qCAAqC,SAAS,GAAG,CAAC,CAAC;IACrE,CAAC;IACD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,WAAW,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,WAAW,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;AACnE,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,KAAa,EACb,SAAiB,EACjB,OAA4B;IAE5B,MAAM,MAAM,GAAG,kBAAkB,CAAC,SAAS,CAAC,CAAC;IAC7C,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,qCAAqC,SAAS,GAAG,CAAC,CAAC;IACrE,CAAC;IACD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,WAAW,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;AACrE,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { Quad } from "n3";
2
+ /**
3
+ * JSON-LD ⇄ RDF for the edge.
4
+ *
5
+ * N3.js does not handle JSON-LD, and `jsonld.js` is too large for the Worker
6
+ * script-size budget. This is a **dependency-free** JSON-LD ↔ quads converter
7
+ * covering the subset `@dwk/solid-pod` content negotiation needs. See
8
+ * `spec/open-questions.md` §4 and the README for the exact supported subset and
9
+ * its known limitations.
10
+ */
11
+ /** A parsed JSON value. */
12
+ export type JsonValue = string | number | boolean | null | JsonValue[] | {
13
+ [key: string]: JsonValue;
14
+ };
15
+ /** A JSON object. */
16
+ export type JsonObject = {
17
+ [key: string]: JsonValue;
18
+ };
19
+ /** Error raised for malformed or unsupported JSON-LD input. */
20
+ export declare class JsonLdError extends Error {
21
+ constructor(message: string);
22
+ }
23
+ /** Options for {@link parseJsonLd}. */
24
+ export interface ParseJsonLdOptions {
25
+ /** Base IRI used to resolve relative `@id` / `@base` references. */
26
+ readonly base?: string;
27
+ }
28
+ /**
29
+ * Parse a JSON-LD document (string or already-parsed value) into quads.
30
+ *
31
+ * Supports the subset documented in the package README: inline contexts only —
32
+ * no remote (URL) contexts.
33
+ */
34
+ export declare function parseJsonLd(input: string | JsonValue, options?: ParseJsonLdOptions): Promise<Quad[]>;
35
+ /** Options for {@link writeJsonLd}. */
36
+ export interface WriteJsonLdOptions {
37
+ /** `JSON.stringify` indentation width (default `2`). */
38
+ readonly space?: number;
39
+ }
40
+ /**
41
+ * Serialize quads into a JSON-LD document string (expanded / flattened form).
42
+ */
43
+ export declare function writeJsonLd(quads: Quad[], options?: WriteJsonLdOptions): Promise<string>;
44
+ //# sourceMappingURL=jsonld.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonld.d.ts","sourceRoot":"","sources":["../src/jsonld.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAIV,IAAI,EAIL,MAAM,IAAI,CAAC;AAEZ;;;;;;;;GAQG;AAEH,2BAA2B;AAC3B,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,MAAM,GACN,OAAO,GACP,IAAI,GACJ,SAAS,EAAE,GACX;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAEjC,qBAAqB;AACrB,MAAM,MAAM,UAAU,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAA;CAAE,CAAC;AAEtD,+DAA+D;AAC/D,qBAAa,WAAY,SAAQ,KAAK;gBACxB,OAAO,EAAE,MAAM;CAI5B;AA+hBD,uCAAuC;AACvC,MAAM,WAAW,kBAAkB;IACjC,oEAAoE;IACpE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,IAAI,EAAE,CAAC,CAcjB;AA2LD,uCAAuC;AACvC,MAAM,WAAW,kBAAkB;IACjC,wDAAwD;IACxD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,IAAI,EAAE,EACb,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,MAAM,CAAC,CAEjB"}