@oxilite/d1 0.3.0 → 0.5.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/README.md +21 -2
- package/bin/oxilite-d1.mjs +42 -3
- package/dist/driver.d.ts +90 -1
- package/dist/driver.js +124 -3
- package/dist/index.d.ts +14 -0
- package/dist/index.js +21 -2
- package/dist/node.d.ts +13 -0
- package/dist/node.js +17 -2
- package/package.json +2 -2
- package/wasm/node/oxilite_wasm.d.ts +90 -3
- package/wasm/node/oxilite_wasm.js +392 -1
- package/wasm/node/oxilite_wasm_bg.wasm +0 -0
- package/wasm/node/oxilite_wasm_bg.wasm.d.ts +20 -0
- package/wasm/web/oxilite_wasm.d.ts +110 -3
- package/wasm/web/oxilite_wasm.js +392 -1
- package/wasm/web/oxilite_wasm_bg.wasm +0 -0
- package/wasm/web/oxilite_wasm_bg.wasm.d.ts +20 -0
package/README.md
CHANGED
|
@@ -98,13 +98,32 @@ The same store runs on a Durable Object's embedded SQLite through a small adapte
|
|
|
98
98
|
|
|
99
99
|
## API
|
|
100
100
|
|
|
101
|
-
`D1Store` mirrors Oxigraph's JavaScript `Store`, asynchronously: `query`, `queryJson`, `update`, `load`, `bulkLoad`, `dump`, `add`, `delete`, `has`, `match`, `size`, `clear`, plus `cypher`, `explain`, `explainUpdate`, `explainCypher`, `optimize`, `materialize` (OWL 2 RL as SQL rules), `clearInferences`, `jsonld(options)` and `credentials(options)`. Query options add `reasoning: "rdfs" | "owl-ql"` and `
|
|
101
|
+
`D1Store` mirrors Oxigraph's JavaScript `Store`, asynchronously: `query`, `queryJson`, `update`, `load`, `bulkLoad`, `dump`, `add`, `delete`, `has`, `match`, `size`, `clear`, plus `cypher`, `explain`, `explainUpdate`, `explainCypher`, `optimize`, `materialize` (OWL 2 RL as SQL rules), `clearInferences`, `jsonld(options)` and `credentials(options)`. The [schema registry](https://github.com/Volland/oxilite/blob/main/docs/schema-registry.md) is there too: `registerSchemaGraph(graph, role, { appliesTo })`, `schemaGraphs()`, `setSchemaGraphActive`, `unregisterSchemaGraph`, `dropSchemaGraph`, `shapeIndex()`; registrations are RDF in `<oxilite:schema>`, written with the same portable SPARQL as on Oxigraph. `D1Store.open(db, { systemGraphs: true })` (or `npx oxilite-d1 schema --system-graphs`) starts a blank database with the `oxl:` vocabulary in `<oxilite:vocabulary>`; `installSystemGraphs()` adds it to an existing one. Query options add `reasoning: "rdfs" | "owl-ql"`, `include_inferred` and `include_schema_graphs`; create the store with `textIndex: true` for FTS5 search with `oxl:textMatch`.
|
|
102
102
|
|
|
103
103
|
| Import | Use |
|
|
104
104
|
|---|---|
|
|
105
105
|
| `@oxilite/d1` | Workers: pass the wasm module (`@oxilite/d1/oxilite.wasm`) to `D1Store.open` |
|
|
106
106
|
| `@oxilite/d1/node` | Node.js, tests and Miniflare: the wasm core loads itself |
|
|
107
|
-
| `npx oxilite-d1 schema [--jsonld]` | Print the schema as a D1 migration (with the JSON-LD tables) |
|
|
107
|
+
| `npx oxilite-d1 schema [--jsonld] [--versioning log]` | Print the schema as a D1 migration (with the JSON-LD tables, with versioning) |
|
|
108
|
+
| `npx oxilite-d1 versioning-migration --from off --to log` | Print the migration that changes an existing database's versioning level |
|
|
109
|
+
|
|
110
|
+
### Versioning
|
|
111
|
+
|
|
112
|
+
Open or create the store with `versioning: "stamped"` (a store clock) or `"log"` (an immutable change log). Every D1 batch is then one commit:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const store = await D1Store.open(env.DB, { wasm, versioning: "log" });
|
|
116
|
+
await store.withCommit({ author: "ada", message: "close t1" }, (s) => s.update(closeTicket));
|
|
117
|
+
const before = await store.query(sparql, { as_of: "HEAD~1" }); // or "#42", "@2026-09-01T12:00:00Z"
|
|
118
|
+
const diff = await store.diff("HEAD~1"); // [{ tick, added, quad }]
|
|
119
|
+
const log = await store.history(20); // [{ tick, time, kind, author, message, added, removed }]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`versioning()`, `setVersioning(level, { allowLoss })`, `setCommitInfo`, `changes(after)`, `resolveVersion` and `purge(pattern, reason)` complete the API. `SERVICE <oxilite:version/HEAD~1> { … }` compares versions inside one query, `GRAPH <oxilite:history>` reads commits and changes as RDF, and `cypher(q, {}, { asOf: "HEAD~1" })` matches the past. Measured on D1: `stamped` writes 4.82 rows per triple against 4.81 for a plain store, `log` 6.82 and `log` with `asOfIndex` 8.82. A store keeps its level: change an existing database with a migration.
|
|
123
|
+
|
|
124
|
+
### Upgrading a 0.4 database
|
|
125
|
+
|
|
126
|
+
0.5 moves the schema registry into the RDF graph `<oxilite:schema>` and adds a scope column to the reasoning cache. `D1Store.open(env.DB, { wasm })` (without `migrated: true`) upgrades a 0.4 database in one batch: registrations become triples, the `schema_graphs` table goes, and the cache is rebuilt. Do it once (for example from a one-off script or a deploy step), then keep opening with `migrated: true`; `openExisting` on a database that was not upgraded fails with a message saying so. New databases: regenerate `migrations/0001_oxilite.sql` with `npx oxilite-d1 schema`.
|
|
108
127
|
|
|
109
128
|
## Tips
|
|
110
129
|
|
package/bin/oxilite-d1.mjs
CHANGED
|
@@ -1,16 +1,55 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// Usage:
|
|
2
|
+
// Usage:
|
|
3
|
+
// npx oxilite-d1 schema [--no-graph-index] [--jsonld] [--no-metadata-indexes]
|
|
4
|
+
// [--versioning off|stamped|log] [--as-of-index] [--stamp-index]
|
|
5
|
+
// [--system-graphs] > migrations/0001_oxilite.sql
|
|
6
|
+
// npx oxilite-d1 versioning-migration --from off --to log [--as-of-index] [--stamp-index]
|
|
7
|
+
// [--allow-loss] [--stamp-column] [--frozen-history] > migrations/0002_versioning.sql
|
|
3
8
|
import { D1Store } from "../dist/node.js";
|
|
4
9
|
|
|
5
10
|
const [command, ...args] = process.argv.slice(2);
|
|
11
|
+
const value = (name) => {
|
|
12
|
+
const i = args.indexOf(name);
|
|
13
|
+
return i >= 0 ? args[i + 1] : undefined;
|
|
14
|
+
};
|
|
15
|
+
const usage = `usage:
|
|
16
|
+
oxilite-d1 schema [--no-graph-index] [--jsonld] [--no-metadata-indexes] [--versioning off|stamped|log] [--as-of-index] [--stamp-index] [--system-graphs]
|
|
17
|
+
oxilite-d1 versioning-migration --from LEVEL --to LEVEL [--as-of-index] [--stamp-index] [--allow-loss] [--stamp-column] [--frozen-history]`;
|
|
18
|
+
|
|
6
19
|
if (command === "schema") {
|
|
7
20
|
const jsonld = args.includes("--jsonld")
|
|
8
21
|
? args.includes("--no-metadata-indexes")
|
|
9
22
|
? { issuer: false, subject: false, validUntil: false }
|
|
10
23
|
: true
|
|
11
24
|
: false;
|
|
12
|
-
process.stdout.write(
|
|
25
|
+
process.stdout.write(
|
|
26
|
+
D1Store.schemaSql({
|
|
27
|
+
graphIndex: !args.includes("--no-graph-index"),
|
|
28
|
+
jsonld,
|
|
29
|
+
versioning: value("--versioning") ?? "off",
|
|
30
|
+
asOfIndex: args.includes("--as-of-index"),
|
|
31
|
+
stampIndex: args.includes("--stamp-index"),
|
|
32
|
+
systemGraphs: args.includes("--system-graphs"),
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
} else if (command === "versioning-migration") {
|
|
36
|
+
const from = value("--from");
|
|
37
|
+
const to = value("--to");
|
|
38
|
+
if (!from || !to) {
|
|
39
|
+
console.error(usage);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
process.stdout.write(`-- oxilite: versioning ${from} -> ${to} (generated by oxilite-d1 versioning-migration)\n`);
|
|
43
|
+
process.stdout.write(
|
|
44
|
+
D1Store.levelChangeSql(from, to, {
|
|
45
|
+
asOfIndex: args.includes("--as-of-index") || undefined,
|
|
46
|
+
stampIndex: args.includes("--stamp-index") || undefined,
|
|
47
|
+
allowLoss: args.includes("--allow-loss"),
|
|
48
|
+
stampColumn: args.includes("--stamp-column"),
|
|
49
|
+
history: args.includes("--frozen-history") ? "frozen" : "none",
|
|
50
|
+
}),
|
|
51
|
+
);
|
|
13
52
|
} else {
|
|
14
|
-
console.error(
|
|
53
|
+
console.error(usage);
|
|
15
54
|
process.exit(1);
|
|
16
55
|
}
|
package/dist/driver.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CypherOptions, type DatalogMaterializeResult, type DatalogOptions, type DatalogResult, type CypherResult, type CypherValue, type CredentialOptions, type DocumentFilter, type Drift, type JsonLdOptions, type PresentationKeys, type StoredDocument, type Term, type DumpOptions, type LoadData, type LoadOptions, type Quad, type QueryOptions, type QueryResult, type TermLike } from "@oxilite/common";
|
|
1
|
+
import { type CypherOptions, type DatalogMaterializeResult, type DatalogOptions, type DatalogResult, type CypherResult, type CypherValue, type CredentialOptions, type DocumentFilter, type Drift, type JsonLdOptions, type PresentationKeys, type StoredDocument, type Term, type DumpOptions, type LoadData, type LoadOptions, type Quad, type QueryOptions, type QueryResult, type TermLike, type Change, type CommitInfo, type CommitRecord, type LevelChange, type Versioning, type VersionStatus, type GraphArg, type PropertyShapeEntry, type SchemaGraphEntry, type SchemaRegistration, type SchemaRole } from "@oxilite/common";
|
|
2
2
|
/** The subset of the Cloudflare D1 binding API used by oxilite. */
|
|
3
3
|
export interface D1PreparedStatementLike {
|
|
4
4
|
raw(): Promise<unknown[][]>;
|
|
@@ -43,7 +43,27 @@ export interface WasmEngine {
|
|
|
43
43
|
clear(): WasmJob;
|
|
44
44
|
materialize(): WasmJob;
|
|
45
45
|
clearInferences(): WasmJob;
|
|
46
|
+
registerSchemaGraphSparql(graph: string, role: string, registration?: string | null): string;
|
|
47
|
+
unregisterSchemaGraphSparql(graph: string): string;
|
|
48
|
+
setSchemaGraphActiveSparql(graph: string, active: boolean): string;
|
|
49
|
+
dropSchemaGraphSparql(graph: string): string;
|
|
50
|
+
schemaGraphRegisteredSparql(graph: string): string;
|
|
51
|
+
graphSizeSparql(graph: string): string;
|
|
52
|
+
schemaGraphsSparql(): string;
|
|
53
|
+
schemaGraphsFromOutput(output: string): string;
|
|
54
|
+
systemGraphsSparql(): string;
|
|
55
|
+
systemGraphsReadySparql(): string;
|
|
56
|
+
shapeIndex(): WasmJob;
|
|
46
57
|
schemaSql(): string;
|
|
58
|
+
versioning(): WasmJob;
|
|
59
|
+
setVersioning(level: string, change?: string | null): WasmJob;
|
|
60
|
+
levelChangeSql(from: string, to: string, change?: string | null, state?: string | null): string;
|
|
61
|
+
setCommitInfo(info?: string | null): void;
|
|
62
|
+
history(limit: number): WasmJob;
|
|
63
|
+
resolveVersion(version: string): WasmJob;
|
|
64
|
+
changes(after: number, until?: number | null): WasmJob;
|
|
65
|
+
diff(from: string, to: string): WasmJob;
|
|
66
|
+
purge(pattern: string, reason?: string | null): WasmJob;
|
|
47
67
|
jsonld(op: string, args: string, options?: string | null): WasmJob;
|
|
48
68
|
jsonldSchemaSql(indexes?: string | null): string;
|
|
49
69
|
}
|
|
@@ -58,6 +78,22 @@ export interface D1StoreOptions {
|
|
|
58
78
|
migrated?: boolean;
|
|
59
79
|
/** Create the FTS5 full-text index over string literals (for `oxl:textMatch`). */
|
|
60
80
|
textIndex?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* Versioning of a new store: `"off"` (default), `"stamped"` (a store clock and the tick that
|
|
83
|
+
* added each quad) or `"log"` (an immutable change log: history and time travel). An existing
|
|
84
|
+
* store keeps its level: change it with `setVersioning` or a migration.
|
|
85
|
+
*/
|
|
86
|
+
versioning?: Versioning;
|
|
87
|
+
/** With `"log"`: index the change log by predicate and object (faster as-of queries). */
|
|
88
|
+
asOfIndex?: boolean;
|
|
89
|
+
/** With `"stamped"` or `"log"`: index the tick that added each quad. */
|
|
90
|
+
stampIndex?: boolean;
|
|
91
|
+
/**
|
|
92
|
+
* Install the system graphs in a blank store: the oxilite vocabulary in `<oxilite:vocabulary>`
|
|
93
|
+
* and the schema registry's own description in `<oxilite:schema>` (default false, so a new
|
|
94
|
+
* store is empty as in Oxigraph).
|
|
95
|
+
*/
|
|
96
|
+
systemGraphs?: boolean;
|
|
61
97
|
}
|
|
62
98
|
/** An oxilite RDF store on a Cloudflare D1 database. */
|
|
63
99
|
export declare class D1Store {
|
|
@@ -88,6 +124,8 @@ export declare class D1Store {
|
|
|
88
124
|
* writing statement reads, then applies its changes as one D1 batch.
|
|
89
125
|
*/
|
|
90
126
|
cypher(query: string, params?: Record<string, CypherValue>, options?: CypherOptions): Promise<CypherResult>;
|
|
127
|
+
/** The tick a version reference (`"HEAD~2"`, `"#42"`, `"@2026-09-01T00:00:00Z"`) designates. */
|
|
128
|
+
resolveVersion(version: string): Promise<number>;
|
|
91
129
|
/**
|
|
92
130
|
* A Datalog program over the same quads: recursive rules with stratified negation,
|
|
93
131
|
* constraints and aggregation. A program whose recursion is linear is one SQL statement, so
|
|
@@ -116,6 +154,35 @@ export declare class D1Store {
|
|
|
116
154
|
queryJson(query: string): Promise<string>;
|
|
117
155
|
/** The SQL a query compiles to, with join orders and warnings. */
|
|
118
156
|
explain(query: string): string;
|
|
157
|
+
/** The versioning level of the store and where its clock and history stand. */
|
|
158
|
+
versioning(): Promise<VersionStatus>;
|
|
159
|
+
/**
|
|
160
|
+
* Changes the versioning level. Upgrades keep every quad (the upgrade to `"log"` records the
|
|
161
|
+
* whole store as its genesis commit); a downgrade freezes the history and stops the clock,
|
|
162
|
+
* and deletes them only with `allowLoss`. On a production D1 database prefer a migration
|
|
163
|
+
* (`npx oxilite-d1 versioning-migration`).
|
|
164
|
+
*/
|
|
165
|
+
setVersioning(level: Versioning, change?: LevelChange): Promise<VersionStatus>;
|
|
166
|
+
/** Author and message recorded on the commits of the following writes (until changed). */
|
|
167
|
+
setCommitInfo(info?: CommitInfo): void;
|
|
168
|
+
/** Runs `f` with `info` recorded on its writes. */
|
|
169
|
+
withCommit<T>(info: CommitInfo, f: (store: this) => Promise<T>): Promise<T>;
|
|
170
|
+
/** The latest commits and level changes, newest first. */
|
|
171
|
+
history(limit?: number): Promise<CommitRecord[]>;
|
|
172
|
+
/** The changes after tick `after` (up to `until`), in order. */
|
|
173
|
+
changes(after?: number, until?: number): Promise<Change[]>;
|
|
174
|
+
/** The net difference between two versions (`"HEAD~1"`, `"#42"`, `"@2026-09-01T00:00:00Z"`). */
|
|
175
|
+
diff(from: string, to?: string): Promise<Change[]>;
|
|
176
|
+
/**
|
|
177
|
+
* Removes the quads matching the pattern from the store and from its whole history, for
|
|
178
|
+
* erasure requests; the purge is recorded without the removed content.
|
|
179
|
+
*/
|
|
180
|
+
purge(pattern: {
|
|
181
|
+
subject?: TermLike;
|
|
182
|
+
predicate?: TermLike;
|
|
183
|
+
object?: TermLike;
|
|
184
|
+
graph?: TermLike;
|
|
185
|
+
}, reason?: string): Promise<void>;
|
|
119
186
|
/** SPARQL update, applied atomically in one D1 batch. */
|
|
120
187
|
update(update: string, options?: {
|
|
121
188
|
base_iri?: string;
|
|
@@ -144,6 +211,28 @@ export declare class D1Store {
|
|
|
144
211
|
materialize(): Promise<number>;
|
|
145
212
|
/** Removes every materialized inference. */
|
|
146
213
|
clearInferences(): Promise<void>;
|
|
214
|
+
/**
|
|
215
|
+
* Declares a graph to hold an ontology, SHACL shapes or a ShEx schema, and (`appliesTo`) the
|
|
216
|
+
* graphs it describes. One atomic batch that also rebuilds the reasoning closure and the
|
|
217
|
+
* shape index; the graph's triples stay where they are.
|
|
218
|
+
*/
|
|
219
|
+
registerSchemaGraph(graph: GraphArg, role: SchemaRole, registration?: SchemaRegistration): Promise<void>;
|
|
220
|
+
/** The schema registry, ordered by role and graph. */
|
|
221
|
+
schemaGraphs(): Promise<SchemaGraphEntry[]>;
|
|
222
|
+
private registered;
|
|
223
|
+
/** Activates or deactivates a registration; returns whether one was found. */
|
|
224
|
+
setSchemaGraphActive(graph: GraphArg, active: boolean): Promise<boolean>;
|
|
225
|
+
/** Removes a registration, keeping the graph's triples; returns whether one was found. */
|
|
226
|
+
unregisterSchemaGraph(graph: GraphArg): Promise<boolean>;
|
|
227
|
+
/** Removes a registration and every quad of its graph; returns how many quads it held. */
|
|
228
|
+
dropSchemaGraph(graph: GraphArg): Promise<number>;
|
|
229
|
+
/**
|
|
230
|
+
* Installs or refreshes the system graphs (`<oxilite:vocabulary>`, `<oxilite:schema>`'s own
|
|
231
|
+
* description) in an existing database; returns `false` when they were already current.
|
|
232
|
+
*/
|
|
233
|
+
installSystemGraphs(): Promise<boolean>;
|
|
234
|
+
/** The compiled SHACL property shapes of the registered shapes graphs. */
|
|
235
|
+
shapeIndex(): Promise<PropertyShapeEntry[]>;
|
|
147
236
|
/** Refreshes planner statistics (run after large imports). */
|
|
148
237
|
optimize(): Promise<void>;
|
|
149
238
|
clear(): Promise<void>;
|
package/dist/driver.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// The core (WebAssembly) never touches the database. Each operation is a job: the driver
|
|
4
4
|
// feeds it the response of every request it asks for, until it is done. Atomic requests
|
|
5
5
|
// become one `db.batch()` — D1's only transaction.
|
|
6
|
-
import { JsonLdError, cypherResult, datalogResult, documentText, filterJson, fromJson, jsonLdError, storedDocument, loadDataToString, outputToResult, toJson, } from "@oxilite/common";
|
|
6
|
+
import { JsonLdError, cypherResult, datalogResult, documentText, filterJson, fromJson, jsonLdError, storedDocument, graphJson, registrationJson, toSchemaGraphs, toShapeIndex, toChanges, loadDataToString, outputToResult, toJson, } from "@oxilite/common";
|
|
7
7
|
/** A term hash collision (two different terms with the same 59-bit id). */
|
|
8
8
|
export class OxiliteCollisionError extends Error {
|
|
9
9
|
}
|
|
@@ -11,6 +11,8 @@ function mapError(e) {
|
|
|
11
11
|
const message = e instanceof Error ? e.message : String(e);
|
|
12
12
|
if (message.includes("oxilite: term hash collision"))
|
|
13
13
|
return new OxiliteCollisionError(message);
|
|
14
|
+
if (message.includes("no such column: scope"))
|
|
15
|
+
return new Error("this D1 database has the oxilite 0.4 schema: open it once with D1Store.open(db) (without `migrated`) to upgrade it, then regenerate your migration with `npx oxilite-d1 schema`");
|
|
14
16
|
if (message.includes("graph_does_not_exist"))
|
|
15
17
|
return new Error("the graph does not exist");
|
|
16
18
|
if (message.includes("graph_already_exists"))
|
|
@@ -32,7 +34,14 @@ export class D1Store {
|
|
|
32
34
|
this.engine = engine;
|
|
33
35
|
}
|
|
34
36
|
static async openWith(Engine, db, options = {}) {
|
|
35
|
-
const engine = new Engine(null, JSON.stringify({
|
|
37
|
+
const engine = new Engine(null, JSON.stringify({
|
|
38
|
+
graphIndex: options.graphIndex ?? true,
|
|
39
|
+
textIndex: options.textIndex ?? false,
|
|
40
|
+
versioning: options.versioning ?? "off",
|
|
41
|
+
asOfIndex: options.asOfIndex ?? false,
|
|
42
|
+
stampIndex: options.stampIndex ?? false,
|
|
43
|
+
systemGraphs: options.systemGraphs ?? false,
|
|
44
|
+
}));
|
|
36
45
|
const store = new D1Store(db, engine);
|
|
37
46
|
await store.run(options.migrated ? engine.openExisting() : engine.open());
|
|
38
47
|
return store;
|
|
@@ -107,9 +116,18 @@ export class D1Store {
|
|
|
107
116
|
* writing statement reads, then applies its changes as one D1 batch.
|
|
108
117
|
*/
|
|
109
118
|
async cypher(query, params = {}, options = {}) {
|
|
110
|
-
|
|
119
|
+
// A version is resolved once, so every read of the statement sees the same tick.
|
|
120
|
+
const opts = { ...options };
|
|
121
|
+
if (options.asOf !== undefined) {
|
|
122
|
+
opts.asOfTick = await this.resolveVersion(options.asOf);
|
|
123
|
+
}
|
|
124
|
+
const out = (await this.run(this.engine.cypher(query, JSON.stringify(params), JSON.stringify(opts))));
|
|
111
125
|
return cypherResult(out);
|
|
112
126
|
}
|
|
127
|
+
/** The tick a version reference (`"HEAD~2"`, `"#42"`, `"@2026-09-01T00:00:00Z"`) designates. */
|
|
128
|
+
async resolveVersion(version) {
|
|
129
|
+
return (await this.run(this.engine.resolveVersion(version))).value;
|
|
130
|
+
}
|
|
113
131
|
/**
|
|
114
132
|
* A Datalog program over the same quads: recursive rules with stratified negation,
|
|
115
133
|
* constraints and aggregation. A program whose recursion is linear is one SQL statement, so
|
|
@@ -170,6 +188,57 @@ export class D1Store {
|
|
|
170
188
|
explain(query) {
|
|
171
189
|
return this.engine.explain(query);
|
|
172
190
|
}
|
|
191
|
+
// ---------------------------------------------------------------------------- versioning
|
|
192
|
+
/** The versioning level of the store and where its clock and history stand. */
|
|
193
|
+
async versioning() {
|
|
194
|
+
return (await this.run(this.engine.versioning()));
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Changes the versioning level. Upgrades keep every quad (the upgrade to `"log"` records the
|
|
198
|
+
* whole store as its genesis commit); a downgrade freezes the history and stops the clock,
|
|
199
|
+
* and deletes them only with `allowLoss`. On a production D1 database prefer a migration
|
|
200
|
+
* (`npx oxilite-d1 versioning-migration`).
|
|
201
|
+
*/
|
|
202
|
+
async setVersioning(level, change = {}) {
|
|
203
|
+
return (await this.run(this.engine.setVersioning(level, JSON.stringify(change))));
|
|
204
|
+
}
|
|
205
|
+
/** Author and message recorded on the commits of the following writes (until changed). */
|
|
206
|
+
setCommitInfo(info = {}) {
|
|
207
|
+
this.engine.setCommitInfo(JSON.stringify(info));
|
|
208
|
+
}
|
|
209
|
+
/** Runs `f` with `info` recorded on its writes. */
|
|
210
|
+
async withCommit(info, f) {
|
|
211
|
+
this.setCommitInfo(info);
|
|
212
|
+
try {
|
|
213
|
+
return await f(this);
|
|
214
|
+
}
|
|
215
|
+
finally {
|
|
216
|
+
this.setCommitInfo({});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/** The latest commits and level changes, newest first. */
|
|
220
|
+
async history(limit = 20) {
|
|
221
|
+
return (await this.run(this.engine.history(limit)));
|
|
222
|
+
}
|
|
223
|
+
/** The changes after tick `after` (up to `until`), in order. */
|
|
224
|
+
async changes(after = 0, until) {
|
|
225
|
+
return toChanges(await this.run(this.engine.changes(after, until ?? null)));
|
|
226
|
+
}
|
|
227
|
+
/** The net difference between two versions (`"HEAD~1"`, `"#42"`, `"@2026-09-01T00:00:00Z"`). */
|
|
228
|
+
async diff(from, to = "HEAD") {
|
|
229
|
+
return toChanges(await this.run(this.engine.diff(from, to)));
|
|
230
|
+
}
|
|
231
|
+
/**
|
|
232
|
+
* Removes the quads matching the pattern from the store and from its whole history, for
|
|
233
|
+
* erasure requests; the purge is recorded without the removed content.
|
|
234
|
+
*/
|
|
235
|
+
async purge(pattern, reason) {
|
|
236
|
+
const json = {};
|
|
237
|
+
for (const [k, v] of Object.entries(pattern))
|
|
238
|
+
if (v)
|
|
239
|
+
json[k] = toJson(v);
|
|
240
|
+
await this.run(this.engine.purge(JSON.stringify(json), reason ?? null));
|
|
241
|
+
}
|
|
173
242
|
/** SPARQL update, applied atomically in one D1 batch. */
|
|
174
243
|
async update(update, options = {}) {
|
|
175
244
|
await this.run(this.engine.update(update, options.base_iri ?? null));
|
|
@@ -229,6 +298,58 @@ export class D1Store {
|
|
|
229
298
|
async clearInferences() {
|
|
230
299
|
await this.run(this.engine.clearInferences());
|
|
231
300
|
}
|
|
301
|
+
// The schema registry is RDF in <oxilite:schema>: the core builds the portable SPARQL, and
|
|
302
|
+
// this driver runs it like any other query or update.
|
|
303
|
+
/**
|
|
304
|
+
* Declares a graph to hold an ontology, SHACL shapes or a ShEx schema, and (`appliesTo`) the
|
|
305
|
+
* graphs it describes. One atomic batch that also rebuilds the reasoning closure and the
|
|
306
|
+
* shape index; the graph's triples stay where they are.
|
|
307
|
+
*/
|
|
308
|
+
async registerSchemaGraph(graph, role, registration = {}) {
|
|
309
|
+
await this.update(this.engine.registerSchemaGraphSparql(graphJson(graph), role, registrationJson(registration)));
|
|
310
|
+
}
|
|
311
|
+
/** The schema registry, ordered by role and graph. */
|
|
312
|
+
async schemaGraphs() {
|
|
313
|
+
const out = await this.run(this.engine.query(this.engine.schemaGraphsSparql()));
|
|
314
|
+
return toSchemaGraphs(JSON.parse(this.engine.schemaGraphsFromOutput(JSON.stringify(out))));
|
|
315
|
+
}
|
|
316
|
+
async registered(graph) {
|
|
317
|
+
return (await this.query(this.engine.schemaGraphRegisteredSparql(graphJson(graph)))) === true;
|
|
318
|
+
}
|
|
319
|
+
/** Activates or deactivates a registration; returns whether one was found. */
|
|
320
|
+
async setSchemaGraphActive(graph, active) {
|
|
321
|
+
if (!(await this.registered(graph)))
|
|
322
|
+
return false;
|
|
323
|
+
await this.update(this.engine.setSchemaGraphActiveSparql(graphJson(graph), active));
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
/** Removes a registration, keeping the graph's triples; returns whether one was found. */
|
|
327
|
+
async unregisterSchemaGraph(graph) {
|
|
328
|
+
if (!(await this.registered(graph)))
|
|
329
|
+
return false;
|
|
330
|
+
await this.update(this.engine.unregisterSchemaGraphSparql(graphJson(graph)));
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
/** Removes a registration and every quad of its graph; returns how many quads it held. */
|
|
334
|
+
async dropSchemaGraph(graph) {
|
|
335
|
+
const rows = (await this.query(this.engine.graphSizeSparql(graphJson(graph))));
|
|
336
|
+
await this.update(this.engine.dropSchemaGraphSparql(graphJson(graph)));
|
|
337
|
+
return Number(rows[0]?.get("n")?.value ?? 0);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Installs or refreshes the system graphs (`<oxilite:vocabulary>`, `<oxilite:schema>`'s own
|
|
341
|
+
* description) in an existing database; returns `false` when they were already current.
|
|
342
|
+
*/
|
|
343
|
+
async installSystemGraphs() {
|
|
344
|
+
if ((await this.query(this.engine.systemGraphsReadySparql())) === true)
|
|
345
|
+
return false;
|
|
346
|
+
await this.update(this.engine.systemGraphsSparql());
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
/** The compiled SHACL property shapes of the registered shapes graphs. */
|
|
350
|
+
async shapeIndex() {
|
|
351
|
+
return toShapeIndex(await this.run(this.engine.shapeIndex()));
|
|
352
|
+
}
|
|
232
353
|
/** Refreshes planner statistics (run after large imports). */
|
|
233
354
|
async optimize() {
|
|
234
355
|
await this.run(this.engine.optimize());
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { D1Store as Base, type D1DatabaseLike, type D1StoreOptions } from "./driver.js";
|
|
2
|
+
import type { LevelChange, Versioning } from "@oxilite/common";
|
|
2
3
|
export * from "@oxilite/common";
|
|
3
4
|
export { OxiliteCollisionError, D1Credentials, D1JsonLdDocuments, type D1DatabaseLike, type D1StoreOptions, } from "./driver.js";
|
|
4
5
|
/** Initializes the WebAssembly core (a `WebAssembly.Module`, bytes, or a URL/Response). */
|
|
@@ -19,6 +20,19 @@ export declare class D1Store {
|
|
|
19
20
|
subject?: boolean;
|
|
20
21
|
validUntil?: boolean;
|
|
21
22
|
};
|
|
23
|
+
versioning?: Versioning;
|
|
24
|
+
asOfIndex?: boolean;
|
|
25
|
+
stampIndex?: boolean;
|
|
26
|
+
systemGraphs?: boolean;
|
|
27
|
+
wasm?: WebAssembly.Module | BufferSource;
|
|
28
|
+
}): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* The SQL that changes the versioning level of an existing D1 database (for
|
|
31
|
+
* `wrangler d1 migrations`). `state` describes the database when it was lowered before.
|
|
32
|
+
*/
|
|
33
|
+
static levelChangeSql(from: Versioning, to: Versioning, options?: LevelChange & {
|
|
34
|
+
stampColumn?: boolean;
|
|
35
|
+
history?: "none" | "frozen";
|
|
22
36
|
wasm?: WebAssembly.Module | BufferSource;
|
|
23
37
|
}): Promise<string>;
|
|
24
38
|
}
|
package/dist/index.js
CHANGED
|
@@ -4,10 +4,19 @@
|
|
|
4
4
|
// import wasm from "@oxilite/d1/oxilite.wasm";
|
|
5
5
|
// const store = await D1Store.open(env.DB, { wasm });
|
|
6
6
|
import init, { Engine, initSync } from "../wasm/web/oxilite_wasm.js";
|
|
7
|
-
import { D1Store as Base } from "./driver.js";
|
|
7
|
+
import { D1Store as Base, } from "./driver.js";
|
|
8
8
|
export * from "@oxilite/common";
|
|
9
9
|
export { OxiliteCollisionError, D1Credentials, D1JsonLdDocuments, } from "./driver.js";
|
|
10
10
|
let ready = false;
|
|
11
|
+
function engineOptions(o) {
|
|
12
|
+
return JSON.stringify({
|
|
13
|
+
graphIndex: o.graphIndex ?? true,
|
|
14
|
+
versioning: o.versioning ?? "off",
|
|
15
|
+
asOfIndex: o.asOfIndex ?? false,
|
|
16
|
+
stampIndex: o.stampIndex ?? false,
|
|
17
|
+
systemGraphs: o.systemGraphs ?? false,
|
|
18
|
+
});
|
|
19
|
+
}
|
|
11
20
|
/** Initializes the WebAssembly core (a `WebAssembly.Module`, bytes, or a URL/Response). */
|
|
12
21
|
export async function initOxilite(wasm) {
|
|
13
22
|
if (ready)
|
|
@@ -32,9 +41,19 @@ export class D1Store {
|
|
|
32
41
|
*/
|
|
33
42
|
static async schemaSql(options = {}) {
|
|
34
43
|
await initOxilite(options.wasm);
|
|
35
|
-
const e = new Engine(null,
|
|
44
|
+
const e = new Engine(null, engineOptions(options));
|
|
36
45
|
if (options.jsonld)
|
|
37
46
|
return e.jsonldSchemaSql(JSON.stringify(options.jsonld === true ? {} : options.jsonld));
|
|
38
47
|
return e.schemaSql();
|
|
39
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* The SQL that changes the versioning level of an existing D1 database (for
|
|
51
|
+
* `wrangler d1 migrations`). `state` describes the database when it was lowered before.
|
|
52
|
+
*/
|
|
53
|
+
static async levelChangeSql(from, to, options = {}) {
|
|
54
|
+
await initOxilite(options.wasm);
|
|
55
|
+
const { stampColumn, history, wasm: _wasm, ...change } = options;
|
|
56
|
+
const e = new Engine(null, null);
|
|
57
|
+
return e.levelChangeSql(from, to, JSON.stringify(change), JSON.stringify({ stampColumn, history }));
|
|
58
|
+
}
|
|
40
59
|
}
|
package/dist/node.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { D1Store as Base, type D1DatabaseLike, type D1StoreOptions } from "./driver.js";
|
|
2
|
+
import type { LevelChange, Versioning } from "@oxilite/common";
|
|
2
3
|
export * from "@oxilite/common";
|
|
3
4
|
export { OxiliteCollisionError, D1Credentials, D1JsonLdDocuments, type D1DatabaseLike, type D1StoreOptions, } from "./driver.js";
|
|
4
5
|
export declare class D1Store {
|
|
@@ -11,5 +12,17 @@ export declare class D1Store {
|
|
|
11
12
|
subject?: boolean;
|
|
12
13
|
validUntil?: boolean;
|
|
13
14
|
};
|
|
15
|
+
versioning?: Versioning;
|
|
16
|
+
asOfIndex?: boolean;
|
|
17
|
+
stampIndex?: boolean;
|
|
18
|
+
systemGraphs?: boolean;
|
|
19
|
+
}): string;
|
|
20
|
+
/**
|
|
21
|
+
* The SQL that changes the versioning level of an existing D1 database (for
|
|
22
|
+
* `wrangler d1 migrations`). `stampColumn` / `history` describe a database lowered before.
|
|
23
|
+
*/
|
|
24
|
+
static levelChangeSql(from: Versioning, to: Versioning, options?: LevelChange & {
|
|
25
|
+
stampColumn?: boolean;
|
|
26
|
+
history?: "none" | "frozen";
|
|
14
27
|
}): string;
|
|
15
28
|
}
|
package/dist/node.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @oxilite/d1 for Node.js (tests, scripts, Miniflare): the WebAssembly core loads itself.
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
|
-
import { D1Store as Base } from "./driver.js";
|
|
3
|
+
import { D1Store as Base, } from "./driver.js";
|
|
4
4
|
export * from "@oxilite/common";
|
|
5
5
|
export { OxiliteCollisionError, D1Credentials, D1JsonLdDocuments, } from "./driver.js";
|
|
6
6
|
const require = createRequire(import.meta.url);
|
|
@@ -11,9 +11,24 @@ export class D1Store {
|
|
|
11
11
|
}
|
|
12
12
|
/** The schema as SQL; `jsonld` adds the JSON-LD document tables. */
|
|
13
13
|
static schemaSql(options = {}) {
|
|
14
|
-
const e = new Engine(null, JSON.stringify({
|
|
14
|
+
const e = new Engine(null, JSON.stringify({
|
|
15
|
+
graphIndex: options.graphIndex ?? true,
|
|
16
|
+
versioning: options.versioning ?? "off",
|
|
17
|
+
asOfIndex: options.asOfIndex ?? false,
|
|
18
|
+
stampIndex: options.stampIndex ?? false,
|
|
19
|
+
systemGraphs: options.systemGraphs ?? false,
|
|
20
|
+
}));
|
|
15
21
|
if (options.jsonld)
|
|
16
22
|
return e.jsonldSchemaSql(JSON.stringify(options.jsonld === true ? {} : options.jsonld));
|
|
17
23
|
return e.schemaSql();
|
|
18
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* The SQL that changes the versioning level of an existing D1 database (for
|
|
27
|
+
* `wrangler d1 migrations`). `stampColumn` / `history` describe a database lowered before.
|
|
28
|
+
*/
|
|
29
|
+
static levelChangeSql(from, to, options = {}) {
|
|
30
|
+
const { stampColumn, history, ...change } = options;
|
|
31
|
+
const e = new Engine(null, null);
|
|
32
|
+
return e.levelChangeSql(from, to, JSON.stringify(change), JSON.stringify({ stampColumn, history }));
|
|
33
|
+
}
|
|
19
34
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oxilite/d1",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "oxilite on Cloudflare D1 and Durable Objects: an Oxigraph-compatible SPARQL 1.1 and openCypher store (WebAssembly core)",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"test": "vitest run"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@oxilite/common": "0.
|
|
35
|
+
"@oxilite/common": "0.5.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"miniflare": "^4"
|