@oxilite/d1 0.3.0 → 0.7.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 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 `include_inferred`; create the store with `textIndex: true` for FTS5 search with `oxl:textMatch`.
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
 
@@ -1,16 +1,55 @@
1
1
  #!/usr/bin/env node
2
- // Usage: npx oxilite-d1 schema [--no-graph-index] [--jsonld] [--no-metadata-indexes] > migrations/0001_oxilite.sql
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(D1Store.schemaSql({ graphIndex: !args.includes("--no-graph-index"), jsonld }));
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("usage: oxilite-d1 schema [--no-graph-index] [--jsonld] [--no-metadata-indexes]");
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({ graphIndex: options.graphIndex ?? true, textIndex: options.textIndex ?? false }));
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
- const out = (await this.run(this.engine.cypher(query, JSON.stringify(params), JSON.stringify(options))));
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, JSON.stringify({ graphIndex: options.graphIndex ?? true }));
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({ graphIndex: options.graphIndex ?? true }));
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.0",
3
+ "version": "0.7.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.3.0"
35
+ "@oxilite/common": "0.7.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "miniflare": "^4"