@dwk/solid-pod 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 +15 -0
- package/README.md +108 -0
- package/dist/auth.d.ts +33 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +160 -0
- package/dist/auth.js.map +1 -0
- package/dist/config.d.ts +181 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +74 -0
- package/dist/config.js.map +1 -0
- package/dist/encoding.d.ts +13 -0
- package/dist/encoding.d.ts.map +1 -0
- package/dist/encoding.js +31 -0
- package/dist/encoding.js.map +1 -0
- package/dist/gc.d.ts +22 -0
- package/dist/gc.d.ts.map +1 -0
- package/dist/gc.js +33 -0
- package/dist/gc.js.map +1 -0
- package/dist/handler.d.ts +20 -0
- package/dist/handler.d.ts.map +1 -0
- package/dist/handler.js +155 -0
- package/dist/handler.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/jwt.d.ts +36 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +120 -0
- package/dist/jwt.js.map +1 -0
- package/dist/ldp.d.ts +37 -0
- package/dist/ldp.d.ts.map +1 -0
- package/dist/ldp.js +85 -0
- package/dist/ldp.js.map +1 -0
- package/dist/log.d.ts +55 -0
- package/dist/log.d.ts.map +1 -0
- package/dist/log.js +51 -0
- package/dist/log.js.map +1 -0
- package/dist/negotiation.d.ts +23 -0
- package/dist/negotiation.d.ts.map +1 -0
- package/dist/negotiation.js +80 -0
- package/dist/negotiation.js.map +1 -0
- package/dist/patch.d.ts +80 -0
- package/dist/patch.d.ts.map +1 -0
- package/dist/patch.js +425 -0
- package/dist/patch.js.map +1 -0
- package/dist/pod.d.ts +20 -0
- package/dist/pod.d.ts.map +1 -0
- package/dist/pod.js +860 -0
- package/dist/pod.js.map +1 -0
- package/dist/wac.d.ts +33 -0
- package/dist/wac.d.ts.map +1 -0
- package/dist/wac.js +84 -0
- package/dist/wac.js.map +1 -0
- package/package.json +55 -0
- package/src/auth.ts +203 -0
- package/src/config.ts +254 -0
- package/src/encoding.ts +32 -0
- package/src/gc.ts +47 -0
- package/src/handler.ts +199 -0
- package/src/index.ts +32 -0
- package/src/jwt.ts +166 -0
- package/src/ldp.ts +99 -0
- package/src/log.ts +59 -0
- package/src/negotiation.ts +97 -0
- package/src/patch.ts +539 -0
- package/src/pod.ts +1195 -0
- package/src/wac.ts +119 -0
package/dist/log.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@dwk/solid-pod` — structured observability event taxonomy.
|
|
3
|
+
*
|
|
4
|
+
* A Solid Pod authenticates DPoP-bound tokens at the edge and enforces WAC in
|
|
5
|
+
* the Durable Object; an auth/authz decision that is silently swallowed is an
|
|
6
|
+
* operational blind spot. Logging and metrics are opt-in via an injected
|
|
7
|
+
* {@link Logger} and {@link Metrics} (see `@dwk/log`) wired **once at the
|
|
8
|
+
* composition boundary** — the stateless front door. They **share this one
|
|
9
|
+
* vocabulary**: the same dotted event name is passed to the logger and the
|
|
10
|
+
* metrics sink so a log line and its counter line up.
|
|
11
|
+
*
|
|
12
|
+
* Because the Durable Object cannot receive injected functions across the
|
|
13
|
+
* isolate boundary, it signals its authorization outcomes back to the front door
|
|
14
|
+
* via an internal response header ({@link INTERNAL_HEADERS.outcome}); the front
|
|
15
|
+
* door — which holds the injected seams — emits the events and strips the header.
|
|
16
|
+
* This keeps the DO free of any logger and honors the composition contract.
|
|
17
|
+
*
|
|
18
|
+
* Fields follow the redaction policy: only machine-readable reason codes, HTTP
|
|
19
|
+
* method/status, and a sanitized agent host (`hostFromUrl`) — never tokens,
|
|
20
|
+
* proofs, resource paths, or bodies. See `spec/observability.md`.
|
|
21
|
+
*
|
|
22
|
+
* @packageDocumentation
|
|
23
|
+
*/
|
|
24
|
+
/** Stable event names emitted by `@dwk/solid-pod`. */
|
|
25
|
+
export declare const SolidPodLogEvent: {
|
|
26
|
+
/**
|
|
27
|
+
* A presented credential failed edge authentication. Field: `reason` (an
|
|
28
|
+
* {@link AuthFailureReason}, e.g. `signature_invalid`, `dpop_invalid`).
|
|
29
|
+
*/
|
|
30
|
+
readonly AuthRejected: "solid.auth.rejected";
|
|
31
|
+
/** A request authenticated successfully at the edge. Field: `agentHost`. */
|
|
32
|
+
readonly AuthAccepted: "solid.auth.accepted";
|
|
33
|
+
/** WAC refused the request in the Durable Object. Fields: `method`, `status` (401/403). */
|
|
34
|
+
readonly AccessDenied: "solid.wac.denied";
|
|
35
|
+
/** A proof-less write was refused (DPoP required). Field: `method`. */
|
|
36
|
+
readonly AnonymousWriteRefused: "solid.write.anonymous_refused";
|
|
37
|
+
/** A write's DPoP `jti` was replayed and the write was refused. Field: `method`. */
|
|
38
|
+
readonly ReplayRejected: "solid.dpop.replay_rejected";
|
|
39
|
+
};
|
|
40
|
+
/** Union of the event-name string literals in {@link SolidPodLogEvent}. */
|
|
41
|
+
export type SolidPodLogEvent = (typeof SolidPodLogEvent)[keyof typeof SolidPodLogEvent];
|
|
42
|
+
/**
|
|
43
|
+
* Machine-readable outcome values the Durable Object sets on
|
|
44
|
+
* {@link INTERNAL_HEADERS.outcome} so the front door can emit the matching
|
|
45
|
+
* {@link SolidPodLogEvent}. Internal to the DO↔front-door contract; stripped
|
|
46
|
+
* before the response reaches the client.
|
|
47
|
+
*/
|
|
48
|
+
export declare const PodOutcome: {
|
|
49
|
+
readonly WacDenied: "wac_denied";
|
|
50
|
+
readonly AnonymousWriteRefused: "anon_write_refused";
|
|
51
|
+
readonly Replay: "replay";
|
|
52
|
+
};
|
|
53
|
+
/** Union of the outcome string literals in {@link PodOutcome}. */
|
|
54
|
+
export type PodOutcome = (typeof PodOutcome)[keyof typeof PodOutcome];
|
|
55
|
+
//# sourceMappingURL=log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.d.ts","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,sDAAsD;AACtD,eAAO,MAAM,gBAAgB;IAC3B;;;OAGG;;IAEH,4EAA4E;;IAE5E,2FAA2F;;IAE3F,uEAAuE;;IAEvE,oFAAoF;;CAE5E,CAAC;AAEX,2EAA2E;AAC3E,MAAM,MAAM,gBAAgB,GAC1B,CAAC,OAAO,gBAAgB,CAAC,CAAC,MAAM,OAAO,gBAAgB,CAAC,CAAC;AAE3D;;;;;GAKG;AACH,eAAO,MAAM,UAAU;;;;CAIb,CAAC;AAEX,kEAAkE;AAClE,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,OAAO,UAAU,CAAC,CAAC"}
|
package/dist/log.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@dwk/solid-pod` — structured observability event taxonomy.
|
|
3
|
+
*
|
|
4
|
+
* A Solid Pod authenticates DPoP-bound tokens at the edge and enforces WAC in
|
|
5
|
+
* the Durable Object; an auth/authz decision that is silently swallowed is an
|
|
6
|
+
* operational blind spot. Logging and metrics are opt-in via an injected
|
|
7
|
+
* {@link Logger} and {@link Metrics} (see `@dwk/log`) wired **once at the
|
|
8
|
+
* composition boundary** — the stateless front door. They **share this one
|
|
9
|
+
* vocabulary**: the same dotted event name is passed to the logger and the
|
|
10
|
+
* metrics sink so a log line and its counter line up.
|
|
11
|
+
*
|
|
12
|
+
* Because the Durable Object cannot receive injected functions across the
|
|
13
|
+
* isolate boundary, it signals its authorization outcomes back to the front door
|
|
14
|
+
* via an internal response header ({@link INTERNAL_HEADERS.outcome}); the front
|
|
15
|
+
* door — which holds the injected seams — emits the events and strips the header.
|
|
16
|
+
* This keeps the DO free of any logger and honors the composition contract.
|
|
17
|
+
*
|
|
18
|
+
* Fields follow the redaction policy: only machine-readable reason codes, HTTP
|
|
19
|
+
* method/status, and a sanitized agent host (`hostFromUrl`) — never tokens,
|
|
20
|
+
* proofs, resource paths, or bodies. See `spec/observability.md`.
|
|
21
|
+
*
|
|
22
|
+
* @packageDocumentation
|
|
23
|
+
*/
|
|
24
|
+
/** Stable event names emitted by `@dwk/solid-pod`. */
|
|
25
|
+
export const SolidPodLogEvent = {
|
|
26
|
+
/**
|
|
27
|
+
* A presented credential failed edge authentication. Field: `reason` (an
|
|
28
|
+
* {@link AuthFailureReason}, e.g. `signature_invalid`, `dpop_invalid`).
|
|
29
|
+
*/
|
|
30
|
+
AuthRejected: "solid.auth.rejected",
|
|
31
|
+
/** A request authenticated successfully at the edge. Field: `agentHost`. */
|
|
32
|
+
AuthAccepted: "solid.auth.accepted",
|
|
33
|
+
/** WAC refused the request in the Durable Object. Fields: `method`, `status` (401/403). */
|
|
34
|
+
AccessDenied: "solid.wac.denied",
|
|
35
|
+
/** A proof-less write was refused (DPoP required). Field: `method`. */
|
|
36
|
+
AnonymousWriteRefused: "solid.write.anonymous_refused",
|
|
37
|
+
/** A write's DPoP `jti` was replayed and the write was refused. Field: `method`. */
|
|
38
|
+
ReplayRejected: "solid.dpop.replay_rejected",
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Machine-readable outcome values the Durable Object sets on
|
|
42
|
+
* {@link INTERNAL_HEADERS.outcome} so the front door can emit the matching
|
|
43
|
+
* {@link SolidPodLogEvent}. Internal to the DO↔front-door contract; stripped
|
|
44
|
+
* before the response reaches the client.
|
|
45
|
+
*/
|
|
46
|
+
export const PodOutcome = {
|
|
47
|
+
WacDenied: "wac_denied",
|
|
48
|
+
AnonymousWriteRefused: "anon_write_refused",
|
|
49
|
+
Replay: "replay",
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=log.js.map
|
package/dist/log.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,sDAAsD;AACtD,MAAM,CAAC,MAAM,gBAAgB,GAAG;IAC9B;;;OAGG;IACH,YAAY,EAAE,qBAAqB;IACnC,4EAA4E;IAC5E,YAAY,EAAE,qBAAqB;IACnC,2FAA2F;IAC3F,YAAY,EAAE,kBAAkB;IAChC,uEAAuE;IACvE,qBAAqB,EAAE,+BAA+B;IACtD,oFAAoF;IACpF,cAAc,EAAE,4BAA4B;CACpC,CAAC;AAMX;;;;;GAKG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,SAAS,EAAE,YAAY;IACvB,qBAAqB,EAAE,oBAAoB;IAC3C,MAAM,EAAE,QAAQ;CACR,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RDF content negotiation for reads.
|
|
3
|
+
*
|
|
4
|
+
* Maps an `Accept` header to a concrete RDF serialization `@dwk/rdf` can write,
|
|
5
|
+
* preferring the client's highest-q acceptable type and falling back to Turtle
|
|
6
|
+
* (the Solid default). Turtle and JSON-LD are the guaranteed minimum; the other
|
|
7
|
+
* Turtle-family types `@dwk/rdf` supports are offered too.
|
|
8
|
+
*/
|
|
9
|
+
import { type RdfFormat } from "@dwk/rdf";
|
|
10
|
+
/** A negotiated serialization: the concrete media type and `@dwk/rdf` format. */
|
|
11
|
+
export interface Negotiated {
|
|
12
|
+
readonly mediaType: string;
|
|
13
|
+
readonly format: RdfFormat;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Choose the RDF serialization to write for an `Accept` header, or `null` when
|
|
17
|
+
* the header is present but lists nothing this server can serve (the caller MUST
|
|
18
|
+
* then answer `406 Not Acceptable`). An absent/empty `Accept` or a `*` `/` `*`
|
|
19
|
+
* wildcard yields the Turtle default; concrete RDF media types and family
|
|
20
|
+
* wildcards (`text/*`, `application/*`) negotiate to a supported type.
|
|
21
|
+
*/
|
|
22
|
+
export declare function negotiateMediaType(accept: string | null): Negotiated | null;
|
|
23
|
+
//# sourceMappingURL=negotiation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"negotiation.d.ts","sourceRoot":"","sources":["../src/negotiation.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AAE9D,iFAAiF;AACjF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;CAC5B;AAsCD;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,UAAU,GAAG,IAAI,CAoC3E"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RDF content negotiation for reads.
|
|
3
|
+
*
|
|
4
|
+
* Maps an `Accept` header to a concrete RDF serialization `@dwk/rdf` can write,
|
|
5
|
+
* preferring the client's highest-q acceptable type and falling back to Turtle
|
|
6
|
+
* (the Solid default). Turtle and JSON-LD are the guaranteed minimum; the other
|
|
7
|
+
* Turtle-family types `@dwk/rdf` supports are offered too.
|
|
8
|
+
*/
|
|
9
|
+
import { formatForMediaType } from "@dwk/rdf";
|
|
10
|
+
/** Canonical media types offered on read, in server-preference order. */
|
|
11
|
+
const OFFERED = [
|
|
12
|
+
"text/turtle",
|
|
13
|
+
"application/ld+json",
|
|
14
|
+
"application/n-triples",
|
|
15
|
+
"application/trig",
|
|
16
|
+
"application/n-quads",
|
|
17
|
+
];
|
|
18
|
+
const DEFAULT = { mediaType: "text/turtle", format: "Turtle" };
|
|
19
|
+
/** Parse an `Accept` header into entries sorted by descending q-value. */
|
|
20
|
+
function parseAccept(accept) {
|
|
21
|
+
return accept
|
|
22
|
+
.split(",")
|
|
23
|
+
.map((part) => {
|
|
24
|
+
const [type, ...params] = part.split(";").map((s) => s.trim());
|
|
25
|
+
let q = 1;
|
|
26
|
+
for (const param of params) {
|
|
27
|
+
const m = /^q=([0-9.]+)$/.exec(param);
|
|
28
|
+
if (m)
|
|
29
|
+
q = Number.parseFloat(m[1]);
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
type: (type ?? "").toLowerCase(),
|
|
33
|
+
q: Number.isFinite(q) ? q : 0,
|
|
34
|
+
};
|
|
35
|
+
})
|
|
36
|
+
.filter((e) => e.type.length > 0)
|
|
37
|
+
.sort((a, b) => b.q - a.q);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Choose the RDF serialization to write for an `Accept` header, or `null` when
|
|
41
|
+
* the header is present but lists nothing this server can serve (the caller MUST
|
|
42
|
+
* then answer `406 Not Acceptable`). An absent/empty `Accept` or a `*` `/` `*`
|
|
43
|
+
* wildcard yields the Turtle default; concrete RDF media types and family
|
|
44
|
+
* wildcards (`text/*`, `application/*`) negotiate to a supported type.
|
|
45
|
+
*/
|
|
46
|
+
export function negotiateMediaType(accept) {
|
|
47
|
+
if (!accept || accept.trim() === "")
|
|
48
|
+
return DEFAULT;
|
|
49
|
+
for (const entry of parseAccept(accept)) {
|
|
50
|
+
if (entry.q === 0)
|
|
51
|
+
continue;
|
|
52
|
+
if (entry.type === "*/*")
|
|
53
|
+
return DEFAULT;
|
|
54
|
+
const essence = entry.type.split(";")[0]?.trim() ?? "";
|
|
55
|
+
// Read-only opt-in: a client asking for `application/json` gets JSON-LD.
|
|
56
|
+
// This alias lives here, not in `@dwk/rdf`'s RDF media-type registry, so an
|
|
57
|
+
// incoming `application/json` *request body* is never misparsed as RDF.
|
|
58
|
+
if (essence === "application/json") {
|
|
59
|
+
return { mediaType: "application/ld+json", format: "JSON-LD" };
|
|
60
|
+
}
|
|
61
|
+
const format = formatForMediaType(entry.type);
|
|
62
|
+
if (format) {
|
|
63
|
+
return { mediaType: essence || "text/turtle", format };
|
|
64
|
+
}
|
|
65
|
+
// Wildcard subtypes (`text/*`, `application/*`): pick the first offered
|
|
66
|
+
// type in that family.
|
|
67
|
+
const slash = entry.type.indexOf("/");
|
|
68
|
+
if (slash > 0 && entry.type.endsWith("/*")) {
|
|
69
|
+
const family = entry.type.slice(0, slash);
|
|
70
|
+
const offered = OFFERED.find((m) => m.startsWith(`${family}/`));
|
|
71
|
+
if (offered) {
|
|
72
|
+
return { mediaType: offered, format: formatForMediaType(offered) };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// An `Accept` was supplied but nothing in it is acceptable: signal 406 rather
|
|
77
|
+
// than silently serving Turtle the client said it could not handle.
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=negotiation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"negotiation.js","sourceRoot":"","sources":["../src/negotiation.ts"],"names":[],"mappings":"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"}
|
package/dist/patch.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* N3 Patch (`text/n3`) and a minimal `application/sparql-update` parser, plus
|
|
3
|
+
* the deliberately-small `solid:where` matcher.
|
|
4
|
+
*
|
|
5
|
+
* This is **not** a SPARQL engine. `solid:where` is a conjunctive basic graph
|
|
6
|
+
* pattern matched against the resource's current triples; the patch applies
|
|
7
|
+
* only when the pattern binds to **exactly one** solution (per the Solid
|
|
8
|
+
* Protocol's N3 Patch rules). Variables in `solid:deletes` / `solid:inserts`
|
|
9
|
+
* are then instantiated from that single binding. Anything richer (`OPTIONAL`,
|
|
10
|
+
* `FILTER`, property paths, multiple solutions) is out of scope and surfaces as
|
|
11
|
+
* a `409`.
|
|
12
|
+
*/
|
|
13
|
+
import { type StoredQuad, type StoredTerm } from "@dwk/rdf";
|
|
14
|
+
/** A term in a patch template/pattern: a variable, or a concrete RDF term. */
|
|
15
|
+
type PatchTerm = {
|
|
16
|
+
readonly kind: "var";
|
|
17
|
+
readonly name: string;
|
|
18
|
+
} | {
|
|
19
|
+
readonly kind: "term";
|
|
20
|
+
readonly term: StoredTerm;
|
|
21
|
+
};
|
|
22
|
+
/** A triple pattern (subject/predicate/object) from a patch graph. */
|
|
23
|
+
interface PatchTriple {
|
|
24
|
+
readonly subject: PatchTerm;
|
|
25
|
+
readonly predicate: PatchTerm;
|
|
26
|
+
readonly object: PatchTerm;
|
|
27
|
+
}
|
|
28
|
+
/** A parsed patch: the `where` pattern and the `deletes`/`inserts` templates. */
|
|
29
|
+
export interface Patch {
|
|
30
|
+
readonly where: readonly PatchTriple[];
|
|
31
|
+
readonly deletes: readonly PatchTriple[];
|
|
32
|
+
readonly inserts: readonly PatchTriple[];
|
|
33
|
+
}
|
|
34
|
+
/** A concrete delete/insert pair ready to hand to `@dwk/store`. */
|
|
35
|
+
export interface ResolvedPatch {
|
|
36
|
+
readonly deletes: readonly StoredQuad[];
|
|
37
|
+
readonly inserts: readonly StoredQuad[];
|
|
38
|
+
/** True when the patch only inserts (authorizable with `acl:Append`). */
|
|
39
|
+
readonly insertOnly: boolean;
|
|
40
|
+
}
|
|
41
|
+
/** A stable failure code from parsing or applying a patch. */
|
|
42
|
+
export type PatchError = "parse_error" | "unsupported_media_type" | "no_match" | "ambiguous_match" | "delete_not_found" | "where_too_complex";
|
|
43
|
+
export declare class PatchProblem extends Error {
|
|
44
|
+
readonly code: PatchError;
|
|
45
|
+
constructor(code: PatchError);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A stable failure code for a violation of the N3 Patch *document constraints*
|
|
49
|
+
* (Solid Protocol §5.3.1) — as distinct from a binding/state outcome. These all
|
|
50
|
+
* mean the patch document is itself malformed or ill-formed per the spec, and
|
|
51
|
+
* MUST be answered with `422 Unprocessable Entity` (`#server-patch-n3-invalid`).
|
|
52
|
+
*/
|
|
53
|
+
export type PatchConstraint = "parse_error" | "missing_type" | "duplicate_predicate" | "blank_node_in_template" | "unbound_template_variable";
|
|
54
|
+
/**
|
|
55
|
+
* Thrown when a patch document does not satisfy the N3 Patch document
|
|
56
|
+
* constraints. Kept distinct from {@link PatchProblem} so the handler can map it
|
|
57
|
+
* to `422` while binding/state failures stay `409`.
|
|
58
|
+
*/
|
|
59
|
+
export declare class PatchConstraintError extends Error {
|
|
60
|
+
readonly code: PatchConstraint;
|
|
61
|
+
constructor(code: PatchConstraint);
|
|
62
|
+
}
|
|
63
|
+
/** Parse a patch body by its `Content-Type`. */
|
|
64
|
+
export declare function parsePatch(body: string, contentType: string, baseIRI: string): Patch;
|
|
65
|
+
/**
|
|
66
|
+
* Resolve a parsed {@link Patch} against the resource's `current` quads into a
|
|
67
|
+
* concrete delete/insert pair.
|
|
68
|
+
*
|
|
69
|
+
* @throws {PatchProblem}
|
|
70
|
+
* - `no_match` when `where` binds to no solution;
|
|
71
|
+
* - `ambiguous_match` when it binds to more than one;
|
|
72
|
+
* - `delete_not_found` when a resolved delete triple is absent;
|
|
73
|
+
* - `where_too_complex` when the `where` pattern exceeds the solver's bounds.
|
|
74
|
+
* @throws {PatchConstraintError}
|
|
75
|
+
* - `unbound_template_variable` when a template uses an unbound variable
|
|
76
|
+
* (backstop for the SPARQL path; N3 patches are checked at parse time).
|
|
77
|
+
*/
|
|
78
|
+
export declare function resolvePatch(patch: Patch, current: readonly StoredQuad[]): ResolvedPatch;
|
|
79
|
+
export {};
|
|
80
|
+
//# sourceMappingURL=patch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patch.d.ts","sourceRoot":"","sources":["../src/patch.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAGL,KAAK,UAAU,EACf,KAAK,UAAU,EAChB,MAAM,UAAU,CAAC;AAQlB,8EAA8E;AAC9E,KAAK,SAAS,GACV;IAAE,QAAQ,CAAC,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAEzD,sEAAsE;AACtE,UAAU,WAAW;IACnB,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;CAC5B;AAED,iFAAiF;AACjF,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;CAC1C;AAED,mEAAmE;AACnE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IACxC,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IACxC,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED,8DAA8D;AAC9D,MAAM,MAAM,UAAU,GAClB,aAAa,GACb,wBAAwB,GACxB,UAAU,GACV,iBAAiB,GACjB,kBAAkB,GAClB,mBAAmB,CAAC;AAExB,qBAAa,YAAa,SAAQ,KAAK;IACzB,QAAQ,CAAC,IAAI,EAAE,UAAU;gBAAhB,IAAI,EAAE,UAAU;CAItC;AAED;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GACvB,aAAa,GACb,cAAc,GACd,qBAAqB,GACrB,wBAAwB,GACxB,2BAA2B,CAAC;AAEhC;;;;GAIG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IACjC,QAAQ,CAAC,IAAI,EAAE,eAAe;gBAArB,IAAI,EAAE,eAAe;CAI3C;AAsPD,gDAAgD;AAChD,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,GACd,KAAK,CAYP;AAyJD;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,SAAS,UAAU,EAAE,GAC7B,aAAa,CAcf"}
|