@origintrail-official/dkg-storage 10.0.5 → 10.0.7
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/README.md +41 -0
- package/dist/adapters/blazegraph-nquads.d.ts +36 -0
- package/dist/adapters/blazegraph-nquads.d.ts.map +1 -0
- package/dist/adapters/blazegraph-nquads.js +79 -0
- package/dist/adapters/blazegraph-nquads.js.map +1 -0
- package/dist/adapters/blazegraph.d.ts +11 -3
- package/dist/adapters/blazegraph.d.ts.map +1 -1
- package/dist/adapters/blazegraph.js +241 -84
- package/dist/adapters/blazegraph.js.map +1 -1
- package/dist/adapters/graph-enumeration-query.d.ts +33 -0
- package/dist/adapters/graph-enumeration-query.d.ts.map +1 -0
- package/dist/adapters/graph-enumeration-query.js +33 -0
- package/dist/adapters/graph-enumeration-query.js.map +1 -0
- package/dist/adapters/oxigraph-worker.d.ts +7 -2
- package/dist/adapters/oxigraph-worker.d.ts.map +1 -1
- package/dist/adapters/oxigraph-worker.js +49 -6
- package/dist/adapters/oxigraph-worker.js.map +1 -1
- package/dist/adapters/oxigraph.d.ts +7 -2
- package/dist/adapters/oxigraph.d.ts.map +1 -1
- package/dist/adapters/oxigraph.js +107 -101
- package/dist/adapters/oxigraph.js.map +1 -1
- package/dist/adapters/sparql-content-types.d.ts +16 -0
- package/dist/adapters/sparql-content-types.d.ts.map +1 -0
- package/dist/adapters/sparql-content-types.js +16 -0
- package/dist/adapters/sparql-content-types.js.map +1 -0
- package/dist/adapters/sparql-http.d.ts +17 -2
- package/dist/adapters/sparql-http.d.ts.map +1 -1
- package/dist/adapters/sparql-http.js +155 -77
- package/dist/adapters/sparql-http.js.map +1 -1
- package/dist/adapters/sparql-json-results.d.ts +32 -0
- package/dist/adapters/sparql-json-results.d.ts.map +1 -0
- package/dist/adapters/sparql-json-results.js +37 -0
- package/dist/adapters/sparql-json-results.js.map +1 -0
- package/dist/atomic-graph-replace.d.ts +33 -0
- package/dist/atomic-graph-replace.d.ts.map +1 -0
- package/dist/atomic-graph-replace.js +137 -0
- package/dist/atomic-graph-replace.js.map +1 -0
- package/dist/bounded-rdf.d.ts +45 -0
- package/dist/bounded-rdf.d.ts.map +1 -0
- package/dist/bounded-rdf.js +291 -0
- package/dist/bounded-rdf.js.map +1 -0
- package/dist/changelog-store.d.ts +242 -0
- package/dist/changelog-store.d.ts.map +1 -0
- package/dist/changelog-store.js +761 -0
- package/dist/changelog-store.js.map +1 -0
- package/dist/graph-knowledge-asset-metadata-loader.d.ts +20 -0
- package/dist/graph-knowledge-asset-metadata-loader.d.ts.map +1 -0
- package/dist/graph-knowledge-asset-metadata-loader.js +33 -0
- package/dist/graph-knowledge-asset-metadata-loader.js.map +1 -0
- package/dist/graph-manager.d.ts +170 -0
- package/dist/graph-manager.d.ts.map +1 -1
- package/dist/graph-manager.js +330 -23
- package/dist/graph-manager.js.map +1 -1
- package/dist/graph-set-index-store.d.ts +26 -7
- package/dist/graph-set-index-store.d.ts.map +1 -1
- package/dist/graph-set-index-store.js +280 -53
- package/dist/graph-set-index-store.js.map +1 -1
- package/dist/graph-write-gen.d.ts +52 -0
- package/dist/graph-write-gen.d.ts.map +1 -0
- package/dist/graph-write-gen.js +63 -0
- package/dist/graph-write-gen.js.map +1 -0
- package/dist/http-response-limit.d.ts +9 -0
- package/dist/http-response-limit.d.ts.map +1 -0
- package/dist/http-response-limit.js +53 -0
- package/dist/http-response-limit.js.map +1 -0
- package/dist/index.d.ts +14 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -6
- package/dist/index.js.map +1 -1
- package/dist/local-trusted-controls.d.ts +6 -0
- package/dist/local-trusted-controls.d.ts.map +1 -0
- package/dist/local-trusted-controls.js +6 -0
- package/dist/local-trusted-controls.js.map +1 -0
- package/dist/private-store.d.ts +39 -1
- package/dist/private-store.d.ts.map +1 -1
- package/dist/private-store.js +88 -1
- package/dist/private-store.js.map +1 -1
- package/dist/shared-memory-literal-blob-store.d.ts +4 -2
- package/dist/shared-memory-literal-blob-store.d.ts.map +1 -1
- package/dist/shared-memory-literal-blob-store.js +21 -2
- package/dist/shared-memory-literal-blob-store.js.map +1 -1
- package/dist/store-priority-scheduler.d.ts +49 -9
- package/dist/store-priority-scheduler.d.ts.map +1 -1
- package/dist/store-priority-scheduler.js +203 -40
- package/dist/store-priority-scheduler.js.map +1 -1
- package/dist/triple-store.d.ts +78 -3
- package/dist/triple-store.d.ts.map +1 -1
- package/dist/triple-store.js +90 -2
- package/dist/triple-store.js.map +1 -1
- package/dist/unsupported-capability-error.d.ts +28 -0
- package/dist/unsupported-capability-error.d.ts.map +1 -0
- package/dist/unsupported-capability-error.js +34 -0
- package/dist/unsupported-capability-error.js.map +1 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -66,6 +66,47 @@ For heavy / production workloads, prefer an out-of-process SPARQL server
|
|
|
66
66
|
(`sparql-http` or `blazegraph`), which handles reads and writes concurrently
|
|
67
67
|
and keeps the daemon responsive under load.
|
|
68
68
|
|
|
69
|
+
## External-store admission and deadlines
|
|
70
|
+
|
|
71
|
+
All external adapters share a process-wide priority scheduler. Waiting work is
|
|
72
|
+
bounded independently for `ack`, `normal`, and `background` traffic, and an
|
|
73
|
+
operation rejected before dispatch receives `StoreSchedulerBusyError` with
|
|
74
|
+
`code: STORE_SCHEDULER_BUSY`, `retryable: true`, and a reason of either
|
|
75
|
+
`queue_full` or `queue_wait_timeout`. Because this error is only created before
|
|
76
|
+
the operation closure starts, retrying it cannot duplicate a write that might
|
|
77
|
+
already have reached the store.
|
|
78
|
+
|
|
79
|
+
| Environment variable | Default | Purpose |
|
|
80
|
+
|---|---:|---|
|
|
81
|
+
| `DKG_STORE_MAX_CONCURRENT` | `8` | Maximum external-store operations in flight. |
|
|
82
|
+
| `DKG_STORE_ACK_RESERVED_SLOTS` | `1` | In-flight capacity reserved for ACK work. |
|
|
83
|
+
| `DKG_STORE_NORMAL_RESERVED_SLOTS` | `1` | Non-ACK capacity kept available for normal work while background operations are in flight. |
|
|
84
|
+
| `DKG_STORE_BACKGROUND_RESERVED_SLOTS` | `1` | Non-ACK capacity reserved for background progress. |
|
|
85
|
+
| `DKG_STORE_QUEUE_LIMIT` | `64` | Maximum waiting operations in each priority queue. |
|
|
86
|
+
| `DKG_STORE_ACK_QUEUE_LIMIT` | common limit | Optional ACK queue override. |
|
|
87
|
+
| `DKG_STORE_NORMAL_QUEUE_LIMIT` | common limit | Optional normal queue override. |
|
|
88
|
+
| `DKG_STORE_BACKGROUND_QUEUE_LIMIT` | common limit | Optional background queue override. |
|
|
89
|
+
| `DKG_STORE_QUEUE_WAIT_TIMEOUT_MS` | `10000` | Maximum pre-dispatch wait before a retryable busy rejection. |
|
|
90
|
+
| `DKG_BLAZEGRAPH_OPERATION_TIMEOUT_MS` | `30000` | Blazegraph end-to-end operation deadline, including scheduler wait, HTTP, response decoding, and mapping. |
|
|
91
|
+
|
|
92
|
+
Normal and background reserves are normalized against the available non-ACK
|
|
93
|
+
capacity. At least one background slot remains available, and the background
|
|
94
|
+
progress floor is capped to its admission ceiling so conflicting custom values
|
|
95
|
+
cannot leave usable capacity idle.
|
|
96
|
+
|
|
97
|
+
Blazegraph's deadline can also be set per store, which takes precedence over
|
|
98
|
+
the environment default:
|
|
99
|
+
|
|
100
|
+
```jsonc
|
|
101
|
+
"store": {
|
|
102
|
+
"backend": "blazegraph",
|
|
103
|
+
"options": {
|
|
104
|
+
"url": "http://127.0.0.1:9999/blazegraph/namespace/dkg/sparql",
|
|
105
|
+
"timeout": 30000
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
69
110
|
## Internal Dependencies
|
|
70
111
|
|
|
71
112
|
- `@origintrail-official/dkg-core` — configuration types, logging, constants
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blazegraph N-Quads wire encoder.
|
|
3
|
+
*
|
|
4
|
+
* A focused boundary for the one structural quirk of Blazegraph's bulk-insert
|
|
5
|
+
* endpoint: its N-Quads parser reads the request body BYTE-wise as ASCII (the
|
|
6
|
+
* Content-Type charset is ignored), so any non-ASCII byte is replaced with
|
|
7
|
+
* U+FFFD before parsing — silent stored-value corruption. Downstream, a
|
|
8
|
+
* published KA carrying such a literal fails its storage-ACK merkle
|
|
9
|
+
* recomputation (MERKLE_MISMATCH_IN_SWM) and the publish dies.
|
|
10
|
+
*
|
|
11
|
+
* Verified against the stock `lyrasis/blazegraph:2.1.5` image (image CI + the
|
|
12
|
+
* devnet run). Kept here, separate from the adapter's transport/query/mutation
|
|
13
|
+
* orchestration, so the wire-format invariant is a single testable unit.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Make an assembled N-Quads line ASCII-safe for Blazegraph's bulk-insert
|
|
17
|
+
* endpoint:
|
|
18
|
+
*
|
|
19
|
+
* - Raw non-ASCII UTF-8 (a 2-byte `é` as much as a 4-byte astral emoji) →
|
|
20
|
+
* `\uXXXX` per UTF-16 code unit (astral chars as their surrogate pair) — the
|
|
21
|
+
* exact Java-String form Blazegraph itself emits on CONSTRUCT read-back and
|
|
22
|
+
* the one encoding that round-trips every scalar, including the
|
|
23
|
+
* supplementary plane.
|
|
24
|
+
* - In-range `\UXXXXXXXX` escapes (which Blazegraph truncates to their low 16
|
|
25
|
+
* bits on parse, e.g. `\U0001F600` 😀 → U+F600) → equivalent `\uXXXX`
|
|
26
|
+
* escape(s). Out-of-range `\U` (> U+10FFFF, unrepresentable) passes through.
|
|
27
|
+
*
|
|
28
|
+
* `\uXXXX` is a valid UCHAR in both IRIREF and STRING_LITERAL_QUOTED, so the
|
|
29
|
+
* transform is safe to apply to the whole line. Existing escape sequences are
|
|
30
|
+
* consumed pairwise (backslash parity), so a literal backslash followed by
|
|
31
|
+
* `U…` text (`\\U0001F600`) is never mis-rewritten. The transform is line-level
|
|
32
|
+
* by design — the `\U` normalization and the backslash-parity guard are
|
|
33
|
+
* properties of the rendered line, not of an individual term.
|
|
34
|
+
*/
|
|
35
|
+
export declare function toBlazegraphAsciiSafeNQuads(line: string): string;
|
|
36
|
+
//# sourceMappingURL=blazegraph-nquads.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blazegraph-nquads.d.ts","sourceRoot":"","sources":["../../src/adapters/blazegraph-nquads.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAmChE"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blazegraph N-Quads wire encoder.
|
|
3
|
+
*
|
|
4
|
+
* A focused boundary for the one structural quirk of Blazegraph's bulk-insert
|
|
5
|
+
* endpoint: its N-Quads parser reads the request body BYTE-wise as ASCII (the
|
|
6
|
+
* Content-Type charset is ignored), so any non-ASCII byte is replaced with
|
|
7
|
+
* U+FFFD before parsing — silent stored-value corruption. Downstream, a
|
|
8
|
+
* published KA carrying such a literal fails its storage-ACK merkle
|
|
9
|
+
* recomputation (MERKLE_MISMATCH_IN_SWM) and the publish dies.
|
|
10
|
+
*
|
|
11
|
+
* Verified against the stock `lyrasis/blazegraph:2.1.5` image (image CI + the
|
|
12
|
+
* devnet run). Kept here, separate from the adapter's transport/query/mutation
|
|
13
|
+
* orchestration, so the wire-format invariant is a single testable unit.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Make an assembled N-Quads line ASCII-safe for Blazegraph's bulk-insert
|
|
17
|
+
* endpoint:
|
|
18
|
+
*
|
|
19
|
+
* - Raw non-ASCII UTF-8 (a 2-byte `é` as much as a 4-byte astral emoji) →
|
|
20
|
+
* `\uXXXX` per UTF-16 code unit (astral chars as their surrogate pair) — the
|
|
21
|
+
* exact Java-String form Blazegraph itself emits on CONSTRUCT read-back and
|
|
22
|
+
* the one encoding that round-trips every scalar, including the
|
|
23
|
+
* supplementary plane.
|
|
24
|
+
* - In-range `\UXXXXXXXX` escapes (which Blazegraph truncates to their low 16
|
|
25
|
+
* bits on parse, e.g. `\U0001F600` 😀 → U+F600) → equivalent `\uXXXX`
|
|
26
|
+
* escape(s). Out-of-range `\U` (> U+10FFFF, unrepresentable) passes through.
|
|
27
|
+
*
|
|
28
|
+
* `\uXXXX` is a valid UCHAR in both IRIREF and STRING_LITERAL_QUOTED, so the
|
|
29
|
+
* transform is safe to apply to the whole line. Existing escape sequences are
|
|
30
|
+
* consumed pairwise (backslash parity), so a literal backslash followed by
|
|
31
|
+
* `U…` text (`\\U0001F600`) is never mis-rewritten. The transform is line-level
|
|
32
|
+
* by design — the `\U` normalization and the backslash-parity guard are
|
|
33
|
+
* properties of the rendered line, not of an individual term.
|
|
34
|
+
*/
|
|
35
|
+
export function toBlazegraphAsciiSafeNQuads(line) {
|
|
36
|
+
let out = '';
|
|
37
|
+
for (let i = 0; i < line.length; i++) {
|
|
38
|
+
const code = line.charCodeAt(i);
|
|
39
|
+
if (code === 0x5c /* backslash */) {
|
|
40
|
+
// In-range \UXXXXXXXX → equivalent \uXXXX escape(s) Blazegraph parses
|
|
41
|
+
// correctly. Out-of-range \U (> U+10FFFF, unrepresentable) passes through.
|
|
42
|
+
if (line[i + 1] === 'U') {
|
|
43
|
+
const hex = line.slice(i + 2, i + 10);
|
|
44
|
+
if (/^[0-9A-Fa-f]{8}$/.test(hex)) {
|
|
45
|
+
const cp = parseInt(hex, 16);
|
|
46
|
+
if (cp <= 0x10ffff) {
|
|
47
|
+
out += escapeUtf16CodeUnits(String.fromCodePoint(cp));
|
|
48
|
+
i += 9;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// Any other escape: copy the pair verbatim so the escaped char is never
|
|
54
|
+
// re-inspected (preserves backslash parity for the \U check above).
|
|
55
|
+
out += line[i];
|
|
56
|
+
if (i + 1 < line.length) {
|
|
57
|
+
const nextCode = line.charCodeAt(i + 1);
|
|
58
|
+
out += nextCode >= 0x7f ? escapeUtf16CodeUnits(line[i + 1]) : line[i + 1];
|
|
59
|
+
i++;
|
|
60
|
+
}
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
if (code >= 0x7f) {
|
|
64
|
+
out += escapeUtf16CodeUnits(line[i]);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
out += line[i];
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
/** `\uXXXX` escape per UTF-16 code unit (surrogate halves stay split). */
|
|
72
|
+
function escapeUtf16CodeUnits(s) {
|
|
73
|
+
let out = '';
|
|
74
|
+
for (let i = 0; i < s.length; i++) {
|
|
75
|
+
out += `\\u${s.charCodeAt(i).toString(16).toUpperCase().padStart(4, '0')}`;
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=blazegraph-nquads.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blazegraph-nquads.js","sourceRoot":"","sources":["../../src/adapters/blazegraph-nquads.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,2BAA2B,CAAC,IAAY;IACtD,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAChC,IAAI,IAAI,KAAK,IAAI,CAAC,eAAe,EAAE,CAAC;YAClC,sEAAsE;YACtE,2EAA2E;YAC3E,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,EAAE,CAAC;gBACxB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;gBACtC,IAAI,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;oBACjC,MAAM,EAAE,GAAG,QAAQ,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;oBAC7B,IAAI,EAAE,IAAI,QAAQ,EAAE,CAAC;wBACnB,GAAG,IAAI,oBAAoB,CAAC,MAAM,CAAC,aAAa,CAAC,EAAE,CAAC,CAAC,CAAC;wBACtD,CAAC,IAAI,CAAC,CAAC;wBACP,SAAS;oBACX,CAAC;gBACH,CAAC;YACH,CAAC;YACD,wEAAwE;YACxE,oEAAoE;YACpE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;YACf,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC;gBACxB,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;gBACxC,GAAG,IAAI,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,oBAAoB,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;gBAC1E,CAAC,EAAE,CAAC;YACN,CAAC;YACD,SAAS;QACX,CAAC;QACD,IAAI,IAAI,IAAI,IAAI,EAAE,CAAC;YACjB,GAAG,IAAI,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YACrC,SAAS;QACX,CAAC;QACD,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,0EAA0E;AAC1E,SAAS,oBAAoB,CAAC,CAAS;IACrC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,GAAG,IAAI,MAAM,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;IAC7E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type { TripleStore, Quad as DKGQuad, QueryOptions, StorePressureSnapshot, TripleStoreQueryOptions, QueryResult } from '../triple-store.js';
|
|
1
|
+
import type { TripleStore, Quad as DKGQuad, QueryOptions, UpdateOptions, StorePressureSnapshot, TripleStoreQueryOptions, QueryResult } from '../triple-store.js';
|
|
2
|
+
export declare const DEFAULT_BLAZEGRAPH_OPERATION_TIMEOUT_MS = 30000;
|
|
3
|
+
export interface BlazegraphStoreOptions {
|
|
4
|
+
/** End-to-end timeout including scheduler wait, HTTP work, and response decoding. */
|
|
5
|
+
timeout?: number;
|
|
6
|
+
}
|
|
2
7
|
/**
|
|
3
8
|
* BlazegraphStore — TripleStore adapter backed by a remote Blazegraph
|
|
4
9
|
* SPARQL endpoint over HTTP. Works with any Blazegraph 2.x instance
|
|
@@ -10,7 +15,8 @@ import type { TripleStore, Quad as DKGQuad, QueryOptions, StorePressureSnapshot,
|
|
|
10
15
|
export declare class BlazegraphStore implements TripleStore {
|
|
11
16
|
readonly queryCancellation: "interruptible";
|
|
12
17
|
private readonly url;
|
|
13
|
-
|
|
18
|
+
private readonly operationTimeoutMs;
|
|
19
|
+
constructor(url: string, options?: BlazegraphStoreOptions);
|
|
14
20
|
private runStoreWork;
|
|
15
21
|
getPressureSnapshot(): StorePressureSnapshot;
|
|
16
22
|
insert(quads: DKGQuad[], options?: QueryOptions): Promise<void>;
|
|
@@ -27,7 +33,9 @@ export declare class BlazegraphStore implements TripleStore {
|
|
|
27
33
|
* delete with two full-graph `countQuads` scans (see above), which is
|
|
28
34
|
* prohibitive on a CPU-pegged core.
|
|
29
35
|
*/
|
|
30
|
-
update(sparql: string, options?:
|
|
36
|
+
update(sparql: string, options?: UpdateOptions): Promise<void>;
|
|
37
|
+
replaceGraph(graphUri: string, quads: DKGQuad[], options?: QueryOptions): Promise<void>;
|
|
38
|
+
replaceGraphAndSubject(graphUri: string, graphQuads: DKGQuad[], metaGraphUri: string, metadataSubject: string, metadataQuads: DKGQuad[], options?: QueryOptions): Promise<void>;
|
|
31
39
|
query(sparql: string, options?: TripleStoreQueryOptions): Promise<QueryResult>;
|
|
32
40
|
private queryConstruct;
|
|
33
41
|
hasGraph(graphUri: string, options?: QueryOptions): Promise<boolean>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blazegraph.d.ts","sourceRoot":"","sources":["../../src/adapters/blazegraph.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"blazegraph.d.ts","sourceRoot":"","sources":["../../src/adapters/blazegraph.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,WAAW,EACX,IAAI,IAAI,OAAO,EACf,YAAY,EACZ,aAAa,EACb,qBAAqB,EACrB,uBAAuB,EACvB,WAAW,EAIZ,MAAM,oBAAoB,CAAC;AAuB5B,eAAO,MAAM,uCAAuC,QAAS,CAAC;AAE9D,MAAM,WAAW,sBAAsB;IACrC,qFAAqF;IACrF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAmHD;;;;;;;GAOG;AACH,qBAAa,eAAgB,YAAW,WAAW;IACjD,QAAQ,CAAC,iBAAiB,EAAG,eAAe,CAAU;IAEtD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAS;gBAEhC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,sBAA2B;IAK7D,OAAO,CAAC,YAAY;IAmCpB,mBAAmB,IAAI,qBAAqB;IAQtC,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IA4B/D,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAa/D,eAAe,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IA+BnF,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAiBtG;;;;;;;;;OASG;IACG,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ9D,YAAY,CAChB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,EAAE,EAChB,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,IAAI,CAAC;IAwBV,sBAAsB,CAC1B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,OAAO,EAAE,EACrB,YAAY,EAAE,MAAM,EACpB,eAAe,EAAE,MAAM,EACvB,aAAa,EAAE,OAAO,EAAE,EACxB,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,IAAI,CAAC;IAgCV,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,WAAW,CAAC;YAqDtE,cAAc;IAiCtB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC;IAQpE,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI7C,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAQlE,UAAU,CAAC,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAehE,UAAU,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC;IAoBtE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAQd,YAAY;CA2B3B"}
|
|
@@ -1,7 +1,111 @@
|
|
|
1
|
+
import { performance } from 'node:perf_hooks';
|
|
1
2
|
import { registerTripleStoreAdapter } from '../triple-store.js';
|
|
2
3
|
import { buildBlankNodeSafeDelete } from './sparql-http.js';
|
|
3
|
-
import {
|
|
4
|
+
import { formatSparqlJsonBindings, } from './sparql-json-results.js';
|
|
5
|
+
import { toBlazegraphAsciiSafeNQuads } from './blazegraph-nquads.js';
|
|
6
|
+
import { SPARQL_QUERY_CONTENT_TYPE, SPARQL_UPDATE_CONTENT_TYPE } from './sparql-content-types.js';
|
|
7
|
+
import { assertQuadLiteralsMutf8Safe, getMetrics, JAVA_WRITE_UTF_MAX_BYTES, } from '@origintrail-official/dkg-core';
|
|
4
8
|
import { externalStorePriorityScheduler } from '../store-priority-scheduler.js';
|
|
9
|
+
import { buildAtomicGraphAndSubjectReplaceUpdate, buildAtomicGraphReplaceUpdate, isAtomicGraphReplaceStagingGraph, } from '../atomic-graph-replace.js';
|
|
10
|
+
import { quadToNQuad } from '../bounded-rdf.js';
|
|
11
|
+
import { readResponseTextBounded } from '../http-response-limit.js';
|
|
12
|
+
export const DEFAULT_BLAZEGRAPH_OPERATION_TIMEOUT_MS = 30_000;
|
|
13
|
+
function parsePositiveIntegerEnv(name, fallback) {
|
|
14
|
+
const raw = process.env[name]?.trim();
|
|
15
|
+
if (!raw)
|
|
16
|
+
return fallback;
|
|
17
|
+
const parsed = Number(raw);
|
|
18
|
+
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
19
|
+
}
|
|
20
|
+
function resolveOperationTimeout(configured) {
|
|
21
|
+
if (configured === undefined) {
|
|
22
|
+
return parsePositiveIntegerEnv('DKG_BLAZEGRAPH_OPERATION_TIMEOUT_MS', DEFAULT_BLAZEGRAPH_OPERATION_TIMEOUT_MS);
|
|
23
|
+
}
|
|
24
|
+
if (!Number.isInteger(configured) || configured <= 0) {
|
|
25
|
+
throw new Error('BlazegraphStore timeout must be a positive integer in milliseconds');
|
|
26
|
+
}
|
|
27
|
+
return configured;
|
|
28
|
+
}
|
|
29
|
+
function abortError(signal) {
|
|
30
|
+
const reason = signal.reason;
|
|
31
|
+
return reason instanceof Error ? reason : new Error(String(reason ?? 'aborted'));
|
|
32
|
+
}
|
|
33
|
+
function createStoreOperationDeadline(timeoutMs, callerSignal) {
|
|
34
|
+
const controller = new AbortController();
|
|
35
|
+
const deadlineAt = performance.now() + timeoutMs;
|
|
36
|
+
let timer;
|
|
37
|
+
let callerAttached = false;
|
|
38
|
+
const timeoutError = () => new DOMException(`Blazegraph operation exceeded its ${timeoutMs}ms deadline`, 'TimeoutError');
|
|
39
|
+
const detachCaller = () => {
|
|
40
|
+
if (!callerAttached)
|
|
41
|
+
return;
|
|
42
|
+
callerAttached = false;
|
|
43
|
+
callerSignal?.removeEventListener('abort', onCallerAbort);
|
|
44
|
+
};
|
|
45
|
+
const onCallerAbort = () => {
|
|
46
|
+
detachCaller();
|
|
47
|
+
controller.abort(callerSignal?.reason);
|
|
48
|
+
};
|
|
49
|
+
if (callerSignal?.aborted) {
|
|
50
|
+
controller.abort(callerSignal.reason);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
if (callerSignal) {
|
|
54
|
+
callerAttached = true;
|
|
55
|
+
callerSignal.addEventListener('abort', onCallerAbort, { once: true });
|
|
56
|
+
}
|
|
57
|
+
timer = setTimeout(() => {
|
|
58
|
+
timer = undefined;
|
|
59
|
+
detachCaller();
|
|
60
|
+
controller.abort(timeoutError());
|
|
61
|
+
}, timeoutMs);
|
|
62
|
+
if (typeof timer.unref === 'function')
|
|
63
|
+
timer.unref();
|
|
64
|
+
}
|
|
65
|
+
const check = () => {
|
|
66
|
+
if (controller.signal.aborted)
|
|
67
|
+
throw abortError(controller.signal);
|
|
68
|
+
if (performance.now() < deadlineAt)
|
|
69
|
+
return;
|
|
70
|
+
const error = timeoutError();
|
|
71
|
+
detachCaller();
|
|
72
|
+
controller.abort(error);
|
|
73
|
+
throw error;
|
|
74
|
+
};
|
|
75
|
+
const waitFor = async (work) => {
|
|
76
|
+
let result;
|
|
77
|
+
try {
|
|
78
|
+
// Do not race the operation against the abort signal here. The signal is
|
|
79
|
+
// already passed to fetch (and therefore to its response body), so the
|
|
80
|
+
// underlying promise settles only after the HTTP stack has processed the
|
|
81
|
+
// cancellation. Racing would reject the scheduler task immediately and
|
|
82
|
+
// release its admission slot while the prior Blazegraph request could
|
|
83
|
+
// still be unwinding server-side, allowing a retry to overlap it.
|
|
84
|
+
result = await work;
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
// The timer callback may be delayed by synchronous response processing.
|
|
88
|
+
// Re-check the monotonic deadline before preserving an underlying error
|
|
89
|
+
// so elapsed operations still surface the deadline's TimeoutError.
|
|
90
|
+
check();
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
check();
|
|
94
|
+
return result;
|
|
95
|
+
};
|
|
96
|
+
return {
|
|
97
|
+
signal: controller.signal,
|
|
98
|
+
waitFor,
|
|
99
|
+
check,
|
|
100
|
+
dispose: () => {
|
|
101
|
+
if (timer) {
|
|
102
|
+
clearTimeout(timer);
|
|
103
|
+
timer = undefined;
|
|
104
|
+
}
|
|
105
|
+
detachCaller();
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
}
|
|
5
109
|
/**
|
|
6
110
|
* BlazegraphStore — TripleStore adapter backed by a remote Blazegraph
|
|
7
111
|
* SPARQL endpoint over HTTP. Works with any Blazegraph 2.x instance
|
|
@@ -13,11 +117,35 @@ import { externalStorePriorityScheduler } from '../store-priority-scheduler.js';
|
|
|
13
117
|
export class BlazegraphStore {
|
|
14
118
|
queryCancellation = 'interruptible';
|
|
15
119
|
url;
|
|
16
|
-
|
|
120
|
+
operationTimeoutMs;
|
|
121
|
+
constructor(url, options = {}) {
|
|
17
122
|
this.url = url.replace(/\/$/, '');
|
|
123
|
+
this.operationTimeoutMs = resolveOperationTimeout(options.timeout);
|
|
18
124
|
}
|
|
19
125
|
runStoreWork(operation, options, work) {
|
|
20
|
-
|
|
126
|
+
const deadline = createStoreOperationDeadline(this.operationTimeoutMs, options?.signal);
|
|
127
|
+
const source = options?.source ?? `blazegraph.${operation}`;
|
|
128
|
+
let admitted = false;
|
|
129
|
+
const scheduled = externalStorePriorityScheduler.run(options?.priority, source, async () => {
|
|
130
|
+
admitted = true;
|
|
131
|
+
deadline.check();
|
|
132
|
+
const result = await work(deadline);
|
|
133
|
+
deadline.check();
|
|
134
|
+
return result;
|
|
135
|
+
}, deadline.signal);
|
|
136
|
+
return scheduled
|
|
137
|
+
.catch((error) => {
|
|
138
|
+
// This runs only after the admitted work promise has settled. Combined
|
|
139
|
+
// with StoreOperationDeadline.waitFor awaiting the real fetch/body
|
|
140
|
+
// promise, the metric means transport cleanup completed before the
|
|
141
|
+
// caller can launch a retry. Queued work that expired before admission
|
|
142
|
+
// is intentionally excluded.
|
|
143
|
+
if (admitted && deadline.signal.aborted) {
|
|
144
|
+
getMetrics().storeCancellationCompletedTotal.add(1, { operation, source });
|
|
145
|
+
}
|
|
146
|
+
throw error;
|
|
147
|
+
})
|
|
148
|
+
.finally(deadline.dispose);
|
|
21
149
|
}
|
|
22
150
|
getPressureSnapshot() {
|
|
23
151
|
return externalStorePriorityScheduler.snapshot;
|
|
@@ -28,24 +156,30 @@ export class BlazegraphStore {
|
|
|
28
156
|
async insert(quads, options) {
|
|
29
157
|
if (quads.length === 0)
|
|
30
158
|
return;
|
|
31
|
-
|
|
32
|
-
maxBytes: JAVA_WRITE_UTF_MAX_BYTES,
|
|
33
|
-
label: 'BlazegraphStore.insert',
|
|
34
|
-
});
|
|
35
|
-
const nquads = quads.map(quadToNQuad).join('\n') + '\n';
|
|
36
|
-
const res = await this.runStoreWork('insert', {
|
|
159
|
+
await this.runStoreWork('insert', {
|
|
37
160
|
...options,
|
|
38
161
|
source: options?.source ?? 'blazegraph.insert',
|
|
39
|
-
}, async () =>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
162
|
+
}, async (deadline) => {
|
|
163
|
+
assertQuadLiteralsMutf8Safe(quads, {
|
|
164
|
+
maxBytes: JAVA_WRITE_UTF_MAX_BYTES,
|
|
165
|
+
label: 'BlazegraphStore.insert',
|
|
166
|
+
});
|
|
167
|
+
// Serialize only after scheduler admission. A blocked store therefore
|
|
168
|
+
// retains the caller's quad array, not an additional potentially-large
|
|
169
|
+
// N-Quads copy for every waiting insert.
|
|
170
|
+
const nquads = quads.map(quadToBlazegraphNQuad).join('\n') + '\n';
|
|
171
|
+
deadline.check();
|
|
172
|
+
const res = await deadline.waitFor(fetch(this.url, {
|
|
173
|
+
method: 'POST',
|
|
174
|
+
headers: { 'Content-Type': 'text/x-nquads' },
|
|
175
|
+
body: nquads,
|
|
176
|
+
signal: deadline.signal,
|
|
177
|
+
}));
|
|
178
|
+
if (!res.ok) {
|
|
179
|
+
const text = await deadline.waitFor(res.text().catch(() => ''));
|
|
180
|
+
throw new Error(`Blazegraph insert failed (${res.status}): ${text.slice(0, 200)}`);
|
|
181
|
+
}
|
|
182
|
+
});
|
|
49
183
|
}
|
|
50
184
|
async delete(quads, options) {
|
|
51
185
|
if (quads.length === 0)
|
|
@@ -109,21 +243,47 @@ export class BlazegraphStore {
|
|
|
109
243
|
async update(sparql, options) {
|
|
110
244
|
await this.sparqlUpdate(sparql, { ...options, source: options?.source ?? 'blazegraph.update' }, 'update');
|
|
111
245
|
}
|
|
246
|
+
async replaceGraph(graphUri, quads, options) {
|
|
247
|
+
assertQuadLiteralsMutf8Safe(quads, {
|
|
248
|
+
maxBytes: JAVA_WRITE_UTF_MAX_BYTES,
|
|
249
|
+
label: 'BlazegraphStore.replaceGraph',
|
|
250
|
+
});
|
|
251
|
+
const plan = buildAtomicGraphReplaceUpdate(graphUri, quads);
|
|
252
|
+
try {
|
|
253
|
+
await this.sparqlUpdate(plan.update, { ...options, source: options?.source ?? 'blazegraph.replaceGraph' }, 'replaceGraph');
|
|
254
|
+
}
|
|
255
|
+
catch (error) {
|
|
256
|
+
if (plan.cleanup) {
|
|
257
|
+
await this.sparqlUpdate(plan.cleanup, { ...options, source: 'blazegraph.replaceGraph.cleanup' }, 'replaceGraphCleanup').catch(() => undefined);
|
|
258
|
+
}
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
async replaceGraphAndSubject(graphUri, graphQuads, metaGraphUri, metadataSubject, metadataQuads, options) {
|
|
263
|
+
assertQuadLiteralsMutf8Safe([...graphQuads, ...metadataQuads], {
|
|
264
|
+
maxBytes: JAVA_WRITE_UTF_MAX_BYTES,
|
|
265
|
+
label: 'BlazegraphStore.replaceGraphAndSubject',
|
|
266
|
+
});
|
|
267
|
+
const plan = buildAtomicGraphAndSubjectReplaceUpdate(graphUri, graphQuads, metaGraphUri, metadataSubject, metadataQuads);
|
|
268
|
+
try {
|
|
269
|
+
await this.sparqlUpdate(plan.update, { ...options, source: options?.source ?? 'blazegraph.replaceGraphAndSubject' }, 'replaceGraphAndSubject');
|
|
270
|
+
}
|
|
271
|
+
catch (error) {
|
|
272
|
+
await this.sparqlUpdate(plan.cleanup, { ...options, source: 'blazegraph.replaceGraphAndSubject.cleanup' }, 'replaceGraphAndSubjectCleanup').catch(() => undefined);
|
|
273
|
+
throw error;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
112
276
|
// -------------------------------------------------------------------
|
|
113
277
|
// Queries
|
|
114
278
|
// -------------------------------------------------------------------
|
|
115
279
|
async query(sparql, options) {
|
|
116
|
-
return this.runStoreWork('query', options, async () => {
|
|
117
|
-
if (options?.signal?.aborted) {
|
|
118
|
-
const reason = options.signal.reason;
|
|
119
|
-
throw reason instanceof Error ? reason : new Error(String(reason ?? 'aborted'));
|
|
120
|
-
}
|
|
280
|
+
return this.runStoreWork('query', options, async (deadline) => {
|
|
121
281
|
const trimmed = sparql.trim();
|
|
122
282
|
const upper = trimmed.toUpperCase();
|
|
123
283
|
const isAsk = upper.startsWith('ASK');
|
|
124
284
|
const isConstruct = upper.startsWith('CONSTRUCT') || upper.startsWith('DESCRIBE');
|
|
125
285
|
if (isConstruct) {
|
|
126
|
-
return this.queryConstruct(trimmed, options);
|
|
286
|
+
return this.queryConstruct(trimmed, deadline, options);
|
|
127
287
|
}
|
|
128
288
|
// Direct POST (W3C SPARQL 1.1 Protocol): send the query as the raw
|
|
129
289
|
// request body with `application/sparql-query` rather than URL-encoded
|
|
@@ -132,53 +292,53 @@ export class BlazegraphStore {
|
|
|
132
292
|
// on stock Blazegraph) and rejects larger payloads with HTTP 400
|
|
133
293
|
// "Unable to parse form content". The direct-POST body is not form
|
|
134
294
|
// parsed, so large queries (e.g. CONSTRUCT/VALUES) are not capped.
|
|
135
|
-
|
|
295
|
+
// SPARQL_QUERY_CONTENT_TYPE carries charset=utf-8 (see sparql-content-types.ts).
|
|
296
|
+
const res = await deadline.waitFor(fetch(this.url, {
|
|
136
297
|
method: 'POST',
|
|
137
298
|
headers: {
|
|
138
|
-
'Content-Type':
|
|
299
|
+
'Content-Type': SPARQL_QUERY_CONTENT_TYPE,
|
|
139
300
|
Accept: 'application/sparql-results+json',
|
|
140
301
|
},
|
|
141
302
|
body: trimmed,
|
|
142
|
-
signal:
|
|
143
|
-
});
|
|
303
|
+
signal: deadline.signal,
|
|
304
|
+
}));
|
|
144
305
|
if (!res.ok) {
|
|
145
|
-
const text = await
|
|
306
|
+
const text = await deadline.waitFor((options?.maxResponseBytes === undefined
|
|
307
|
+
? res.text()
|
|
308
|
+
: readResponseTextBounded(res, options.maxResponseBytes)).catch(() => ''));
|
|
146
309
|
throw new Error(`Blazegraph query failed (${res.status}): ${text.slice(0, 300)}`);
|
|
147
310
|
}
|
|
148
|
-
const json =
|
|
311
|
+
const json = options?.maxResponseBytes === undefined
|
|
312
|
+
? await deadline.waitFor(res.json())
|
|
313
|
+
: JSON.parse(await deadline.waitFor(readResponseTextBounded(res, options.maxResponseBytes)));
|
|
149
314
|
if (isAsk || 'boolean' in json) {
|
|
150
315
|
return { type: 'boolean', value: json.boolean };
|
|
151
316
|
}
|
|
152
|
-
const
|
|
153
|
-
const vars = sr.head?.vars ?? [];
|
|
154
|
-
const bindings = (sr.results?.bindings ?? []).map((row) => {
|
|
155
|
-
const obj = {};
|
|
156
|
-
for (const v of vars) {
|
|
157
|
-
const cell = row[v];
|
|
158
|
-
if (cell)
|
|
159
|
-
obj[v] = blazeTermToString(cell);
|
|
160
|
-
}
|
|
161
|
-
return obj;
|
|
162
|
-
});
|
|
317
|
+
const bindings = formatSparqlJsonBindings(json);
|
|
163
318
|
return { type: 'bindings', bindings };
|
|
164
319
|
});
|
|
165
320
|
}
|
|
166
|
-
async queryConstruct(sparql, options) {
|
|
167
|
-
const res = await fetch(this.url, {
|
|
321
|
+
async queryConstruct(sparql, deadline, options) {
|
|
322
|
+
const res = await deadline.waitFor(fetch(this.url, {
|
|
168
323
|
method: 'POST',
|
|
169
324
|
headers: {
|
|
170
|
-
'Content-Type':
|
|
325
|
+
'Content-Type': SPARQL_QUERY_CONTENT_TYPE,
|
|
171
326
|
Accept: 'text/x-nquads, application/n-quads',
|
|
172
327
|
},
|
|
173
328
|
body: sparql,
|
|
174
|
-
signal:
|
|
175
|
-
});
|
|
329
|
+
signal: deadline.signal,
|
|
330
|
+
}));
|
|
176
331
|
if (!res.ok) {
|
|
177
|
-
const text = await
|
|
332
|
+
const text = await deadline.waitFor((options?.maxResponseBytes === undefined
|
|
333
|
+
? res.text()
|
|
334
|
+
: readResponseTextBounded(res, options.maxResponseBytes)).catch(() => ''));
|
|
178
335
|
throw new Error(`Blazegraph construct failed (${res.status}): ${text.slice(0, 300)}`);
|
|
179
336
|
}
|
|
180
|
-
const text = await
|
|
337
|
+
const text = await deadline.waitFor(options?.maxResponseBytes === undefined
|
|
338
|
+
? res.text()
|
|
339
|
+
: readResponseTextBounded(res, options.maxResponseBytes));
|
|
181
340
|
const quads = parseNQuadsText(text);
|
|
341
|
+
deadline.check();
|
|
182
342
|
return { type: 'quads', quads };
|
|
183
343
|
}
|
|
184
344
|
// -------------------------------------------------------------------
|
|
@@ -198,7 +358,9 @@ export class BlazegraphStore {
|
|
|
198
358
|
const r = await this.query('SELECT DISTINCT ?g WHERE { GRAPH ?g { ?s ?p ?o } }', options);
|
|
199
359
|
if (r.type !== 'bindings')
|
|
200
360
|
return [];
|
|
201
|
-
return r.bindings
|
|
361
|
+
return r.bindings
|
|
362
|
+
.map((b) => b.g)
|
|
363
|
+
.filter((graph) => Boolean(graph) && !isAtomicGraphReplaceStagingGraph(graph));
|
|
202
364
|
}
|
|
203
365
|
// -------------------------------------------------------------------
|
|
204
366
|
// Counts
|
|
@@ -235,44 +397,38 @@ export class BlazegraphStore {
|
|
|
235
397
|
// HTTP 400 "Unable to parse form content" — which broke large publishes
|
|
236
398
|
// (a publish issues a DELETE DATA / INSERT over the full quad set). The
|
|
237
399
|
// raw body is not form parsed, so large updates succeed.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
}
|
|
253
|
-
function blazeTermToString(t) {
|
|
254
|
-
if (t.type === 'bnode')
|
|
255
|
-
return `_:${t.value}`;
|
|
256
|
-
if (t.type === 'literal' || t.type === 'typed-literal') {
|
|
257
|
-
const escaped = escapeNQuadsLiteral(t.value);
|
|
258
|
-
if (t['xml:lang'])
|
|
259
|
-
return `"${escaped}"@${t['xml:lang']}`;
|
|
260
|
-
if (t.datatype && t.datatype !== 'http://www.w3.org/2001/XMLSchema#string') {
|
|
261
|
-
return `"${escaped}"^^<${t.datatype}>`;
|
|
262
|
-
}
|
|
263
|
-
return `"${escaped}"`;
|
|
400
|
+
//
|
|
401
|
+
// SPARQL_UPDATE_CONTENT_TYPE carries charset=utf-8 (see sparql-content-types.ts).
|
|
402
|
+
await this.runStoreWork(operation, options, async (deadline) => {
|
|
403
|
+
const res = await deadline.waitFor(fetch(this.url, {
|
|
404
|
+
method: 'POST',
|
|
405
|
+
headers: { 'Content-Type': SPARQL_UPDATE_CONTENT_TYPE },
|
|
406
|
+
body: update,
|
|
407
|
+
signal: deadline.signal,
|
|
408
|
+
}));
|
|
409
|
+
if (!res.ok) {
|
|
410
|
+
const text = await deadline.waitFor(res.text().catch(() => ''));
|
|
411
|
+
throw new Error(`Blazegraph update failed (${res.status}): ${text.slice(0, 300)}`);
|
|
412
|
+
}
|
|
413
|
+
});
|
|
264
414
|
}
|
|
265
|
-
return t.value;
|
|
266
415
|
}
|
|
267
416
|
// =====================================================================
|
|
268
417
|
// N-Quad serialisation / parsing helpers (shared with oxigraph adapter)
|
|
269
418
|
// =====================================================================
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
419
|
+
/**
|
|
420
|
+
* N-Quads serializer for Blazegraph's bulk-insert wire format: a standard
|
|
421
|
+
* N-Quads line ({@link quadToNQuad}) made ASCII-safe
|
|
422
|
+
* ({@link toBlazegraphAsciiSafeNQuads}). This is the single serialization
|
|
423
|
+
* entry point for the adapter's write path, so the Blazegraph wire-format
|
|
424
|
+
* invariant lives at the serialization boundary rather than as a separate
|
|
425
|
+
* post-processing pass a caller must remember to apply. The ASCII-safe pass
|
|
426
|
+
* operates on the assembled line by design: it normalizes `\U` escapes and
|
|
427
|
+
* relies on backslash parity across the whole line, both of which are
|
|
428
|
+
* line-level properties of the rendered N-Quads, not of an individual term.
|
|
429
|
+
*/
|
|
430
|
+
function quadToBlazegraphNQuad(q) {
|
|
431
|
+
return toBlazegraphAsciiSafeNQuads(quadToNQuad(q));
|
|
276
432
|
}
|
|
277
433
|
function formatTerm(term) {
|
|
278
434
|
if (term.startsWith('"')) {
|
|
@@ -394,6 +550,7 @@ registerTripleStoreAdapter('blazegraph', async (opts) => {
|
|
|
394
550
|
const url = opts?.url;
|
|
395
551
|
if (!url)
|
|
396
552
|
throw new Error('blazegraph adapter requires options.url (SPARQL endpoint)');
|
|
397
|
-
|
|
553
|
+
const timeout = opts?.timeout;
|
|
554
|
+
return new BlazegraphStore(url, { timeout });
|
|
398
555
|
});
|
|
399
556
|
//# sourceMappingURL=blazegraph.js.map
|