@vortex-rdf/vortex-rdf-store 0.1.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 ADDED
@@ -0,0 +1,216 @@
1
+ # Vortex-RDF for JavaScript
2
+
3
+ JavaScript bindings of [`vortex-rdf-core`](https://crates.io/crates/vortex-rdf-core) compiled via WebAssembly.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @vortex-rdf/vortex-rdf-store
9
+ ```
10
+
11
+ The package is ESM-only (no CommonJS `require`) and works identically in Node.js and browsers — the same `import` statement resolves to a Node-specific entry point or a browser one depending on where it runs, so there's no environment-specific code to write:
12
+
13
+ ```javascript
14
+ import { VortexRdfStore } from '@vortex-rdf/vortex-rdf-store';
15
+
16
+ const store = await VortexRdfStore.fromString(ttlData, 'turtle');
17
+ ```
18
+
19
+ Under the hood, a single `wasm-pack --target web` build backs both: the Node entry point reads the `.wasm` file straight off disk (`node:fs`), and the browser entry point uses the standard `fetch`-based loading that bundlers and browsers already understand via `import.meta.url`. Both call the WASM module's async initialization for you, so there's no `init()` to await yourself.
20
+
21
+ > **Bundler note:** both entry points use top-level `await`. Vite and Rollup
22
+ > support this by default; webpack 5 needs
23
+ > `experiments: { topLevelAwait: true }` enabled in its config.
24
+
25
+ ## Usage
26
+
27
+ ### Loading data
28
+
29
+ ```typescript
30
+ import { VortexRdfStore } from '@vortex-rdf/vortex-rdf-store';
31
+ import { Readable } from 'node:stream';
32
+ import type { Quad, Stream } from '@rdfjs/types';
33
+
34
+ // From a Turtle/N-Quads/... string
35
+ // Supported formats: `ntriples`, `nquads`, `turtle`, `trig`, `n3`, `rdfxml`, `jsonld`
36
+ // (plus the short aliases `nt`, `nq`, `ttl`, `rdf`, `xml`).
37
+ const store = await VortexRdfStore.fromString(ttlData, 'turtle');
38
+
39
+ // From RDF-JS quads — an array
40
+ const quads: Quad[] = [...];
41
+ const store = await VortexRdfStore.fromQuads(quads);
42
+
43
+ // Or from any RDF/JS Stream<Quad>
44
+ const quadStream: Stream<Quad> = Readable.from(quads, { objectMode: true });
45
+ const store = await VortexRdfStore.fromQuads(quadStream);
46
+
47
+ // From Vortex binary data (e.g, fetched from a remote server)
48
+ const store = await VortexRdfStore.fromBytes(vortexBytes);
49
+
50
+ // Or create a new empty store
51
+ const store = VortexRdfStore.empty();
52
+ ```
53
+
54
+ ### Querying
55
+
56
+ `match` implements the RDF/JS [`Source.match`](https://rdf.js.org/stream-spec/#source-interface) contract. It takes a `(subject, predicate, object, graph)` pattern — pass
57
+ `null`/`undefined` for a variable position — and returns **synchronously** an RDF/JS `Stream<Quad>`:
58
+
59
+ ```javascript
60
+ store.match(null, myPredicate, null, null)
61
+ .on('data', (quad) => console.log(`${quad.subject.value} -> ${quad.object.value}`))
62
+ .on('end', () => console.log('done'));
63
+ ```
64
+
65
+ The returned stream also implements `Symbol.asyncIterator` as a convenience, so it can be consumed with `for await` (in TypeScript, cast to `AsyncIterable<Quad>` since the declared type is `Stream<Quad>`):
66
+
67
+ ```javascript
68
+ for await (const quad of store.match(null, myPredicate, null, null)) {
69
+ console.log(quad.object.value);
70
+ }
71
+ ```
72
+
73
+ When you just want the matches as an array, `getQuads` is the array-returning counterpart (`async`, because resolving the match crosses the WebAssembly boundary):
74
+
75
+ ```javascript
76
+ const quads = await store.getQuads(null, myPredicate, null, null);
77
+ console.log(`Found ${quads.length} results`);
78
+ ```
79
+
80
+ **Quads are lazy and zero-copy.** `match`/`getQuads` don't build eager term objects — they hand back quads backed by the store's columnar data. A term's string is decoded only when you read `.value`/`.termType`, and then interned, so iterating, counting, filtering, and `.equals` never materialize strings you don't use. Under the default `Dictionary` layout, `.equals` between terms of the same store is an **integer code compare** (no decoding at all).
81
+
82
+ The quads implement the RDF/JS `Quad`/`Term` interface (`.subject.value`, `.equals`, …) and interoperate with foreign RDF/JS terms via `.equals` in both directions. (They're lazy views into the producing store, so — unlike a plain data object — don't `structuredClone`
83
+ them or rely on enumerating own properties.)
84
+
85
+ Test membership of a single quad with `has` (an exact four-component lookup):
86
+
87
+ ```javascript
88
+ if (await store.has(myQuad)) {
89
+ console.log('present');
90
+ }
91
+ ```
92
+
93
+ ### Mutation
94
+
95
+ ```javascript
96
+ await store.addQuad(myQuad);
97
+ await store.addQuads([quadA, quadB]);
98
+ await store.deleteQuad(existingQuad);
99
+ ```
100
+
101
+ Mutations follow RDF/JS dataset semantics: adding a quad already present is a no-op, and deleting never rewrites the columnar data (rows are tombstoned).
102
+
103
+ Added quads accumulate in a small in-memory tail beside the immutable base, so the store's indexes keep working across edits; when the tail outgrows the base (a tenth of its rows, or 100K rows) the store compacts itself back into one sorted, indexed array.
104
+
105
+ Prefer `addQuads` over a loop of `addQuad` calls; for bulk loading, build once with `fromString`/`fromQuads`.
106
+
107
+ ### Serializing
108
+
109
+ ```javascript
110
+ // Back to RDF text, in any supported format
111
+ const turtle = await store.toRdf('turtle');
112
+
113
+ // To Vortex binary data; read back with VortexRdfStore.fromBytes
114
+ const bytes = await store.toBytes();
115
+ ```
116
+
117
+ ### Build options
118
+
119
+ Ingestion accepts an optional `BuildOptions` object that trades build cost against query speed and size. All fields are optional.
120
+
121
+ ```javascript
122
+ const store = await VortexRdfStore.fromString(data, 'nquads', {
123
+ builder: 'Sorted', // 'Unsorted' (default) | 'Sorted'
124
+ layout: 'Dictionary', // 'Dictionary' (default) | 'Default' | 'TypedObject'
125
+ indexes: ['SecondaryByReference'], // default: []
126
+ });
127
+ ```
128
+
129
+ **`builder`** — how quads are ordered while building:
130
+
131
+ | Value | Build cost | Effect on queries |
132
+ | --- | --- | --- |
133
+ | `'Unsorted'` (default) | Cheapest; natural insertion order | Every `match` is a full column scan |
134
+ | `'Sorted'` | Global in-memory sort by subject → predicate → object → graph | Subject lookups use a binary search |
135
+
136
+ **`layout`** — how terms are encoded into columns:
137
+
138
+ | Value | Notes |
139
+ | --- | --- |
140
+ | `'Dictionary'` (default) | Terms replaced by codes into a sorted term dictionary. Most compact and fastest to query; backs the integer `.equals` fast path on lazy quads; added quads live in an in-memory string tail until serialized |
141
+ | `'Default'` | All four terms as N-Triples strings |
142
+ | `'TypedObject'` | Object split into kind/value/datatype/language columns |
143
+
144
+ **`indexes`**
145
+
146
+ - `'SecondaryByReference'` adds sorted predicate/object columns plus row-id back-references, so predicate-only and object-only patterns use a binary search instead of a full scan.
147
+ - `'SecondaryByCopy'` embeds two complete extra copies of the quad columns — one sorted by `(p, o, s, g)`, one by `(o, s, p, g)` — giving predicate- and object-bound patterns (including combined predicate+object lookups, resolved in one prefix search) the same sorted access path subjects have, at roughly 2× the storage. Both cost extra space and are only effective alongside `builder: 'Sorted'`.
148
+
149
+ A good query-optimized configuration is
150
+
151
+ ```javascript
152
+ {
153
+ builder: 'Sorted',
154
+ layout: 'Dictionary',
155
+ indexes: ['SecondaryByReference']
156
+ };
157
+ ```
158
+
159
+
160
+ The default `{ builder: 'Unsorted', layout: 'Dictionary' }` already gives compact, code-based lazy reads and is cheap to build.
161
+
162
+ ### Helper functions
163
+
164
+ For one-shot conversions without holding a store:
165
+
166
+ ```javascript
167
+ import { rdf_to_vortex, vortex_to_rdf, nquads_to_vortex, vortex_to_nquads } from '@vortex-rdf/vortex-rdf-store';
168
+
169
+ const bytes = await rdf_to_vortex(turtleText, 'turtle', { builder: 'Sorted' });
170
+ const text = await vortex_to_rdf(bytes, 'nquads');
171
+
172
+ // N-Quads shorthands
173
+ const bytes2 = await nquads_to_vortex(nquadsText);
174
+ const text2 = await vortex_to_nquads(bytes2);
175
+ ```
176
+
177
+ ### TypeScript support
178
+
179
+ The package ships typings generated from the Rust bindings, using RDF-JS types.
180
+
181
+ ```typescript
182
+ import { VortexRdfStore, type BuildOptions } from '@vortex-rdf/vortex-rdf-store';
183
+ import { DataFactory } from 'rdf-data-factory';
184
+
185
+ const df = new DataFactory();
186
+
187
+ const options: BuildOptions = { builder: 'Sorted', layout: 'Dictionary' };
188
+ const store = await VortexRdfStore.fromString(data, 'nquads', options);
189
+
190
+ console.log(store.layout()); // 'Dictionary'
191
+
192
+ const quads = await store.getQuads(null, df.namedNode('http://schema.org/name'), null, null);
193
+ for (const quad of quads) {
194
+ console.log(quad.subject.value);
195
+ }
196
+ ```
197
+
198
+ ## Building
199
+
200
+ This package is built using [wasm-pack](https://rustwasm.github.io/wasm-pack/), targeting `web` — the same wasm build is shared by both environments:
201
+
202
+ ```bash
203
+ # Build the wasm module (writes to pkg/web/)
204
+ npm run build
205
+
206
+ # Run the test suite (requires a build first)
207
+ npm test
208
+ ```
209
+
210
+ `entry/node.js` and `entry/browser.js` are small, hand-written wrappers around that single build — they differ only in how they supply the `.wasm` bytes to the generated `init()` (a direct file read vs. the default `fetch`-based path), and `package.json`'s `exports` map picks the right one per environment.
211
+
212
+ There's no separate Node-targeted wasm build to maintain.
213
+
214
+ ## License
215
+
216
+ MIT
@@ -0,0 +1,18 @@
1
+ // Types for the browser/bundler entry point: identical public surface to the
2
+ // Node entry (they differ only in how the wasm bytes are loaded at runtime).
3
+ export {
4
+ VortexRdfStore,
5
+ init_panic_hook,
6
+ rdf_to_vortex,
7
+ vortex_to_rdf,
8
+ nquads_to_vortex,
9
+ vortex_to_nquads,
10
+ } from '../pkg/web/vortex_rdf.js';
11
+ export type {
12
+ BuildOptions,
13
+ BuildOptionsInput,
14
+ BuilderStrategy,
15
+ LayoutStrategy,
16
+ IndexType,
17
+ RdfFormatName,
18
+ } from '../pkg/web/vortex_rdf.js';
@@ -0,0 +1,17 @@
1
+ // Browser/bundler ESM entry point. `init()` with no argument resolves the
2
+ // wasm file relative to its own module URL and `fetch`es it — the standard
3
+ // wasm-bindgen `web`-target path, understood natively by modern bundlers
4
+ // (Vite, webpack 5+, Rollup) via `new URL(..., import.meta.url)` and by a
5
+ // bare `<script type="module">` served from a CDN.
6
+ import init from '../pkg/web/vortex_rdf.js';
7
+
8
+ await init();
9
+
10
+ export {
11
+ VortexRdfStore,
12
+ init_panic_hook,
13
+ rdf_to_vortex,
14
+ vortex_to_rdf,
15
+ nquads_to_vortex,
16
+ vortex_to_nquads,
17
+ } from '../pkg/web/vortex_rdf.js';
@@ -0,0 +1,19 @@
1
+ // Types for the Node entry point: the same public surface as the generated
2
+ // wasm bindings, minus the low-level `init`/`initSync` (the entry calls init
3
+ // for the caller). Kept in sync with entry/node.js's re-exports.
4
+ export {
5
+ VortexRdfStore,
6
+ init_panic_hook,
7
+ rdf_to_vortex,
8
+ vortex_to_rdf,
9
+ nquads_to_vortex,
10
+ vortex_to_nquads,
11
+ } from '../pkg/web/vortex_rdf.js';
12
+ export type {
13
+ BuildOptions,
14
+ BuildOptionsInput,
15
+ BuilderStrategy,
16
+ LayoutStrategy,
17
+ IndexType,
18
+ RdfFormatName,
19
+ } from '../pkg/web/vortex_rdf.js';
package/entry/node.js ADDED
@@ -0,0 +1,19 @@
1
+ // Node ESM entry point. Loads the wasm bytes directly via `node:fs` rather
2
+ // than through the generated `init()`'s default `fetch(...)` path — Node's
3
+ // `fetch` does not support `file:` URLs, so the bundled default would fail
4
+ // here even though it works for a real browser or bundler.
5
+ import { readFile } from 'node:fs/promises';
6
+ import { fileURLToPath } from 'node:url';
7
+ import init from '../pkg/web/vortex_rdf.js';
8
+
9
+ const wasmPath = fileURLToPath(new URL('../pkg/web/vortex_rdf_bg.wasm', import.meta.url));
10
+ await init({ module_or_path: await readFile(wasmPath) });
11
+
12
+ export {
13
+ VortexRdfStore,
14
+ init_panic_hook,
15
+ rdf_to_vortex,
16
+ vortex_to_rdf,
17
+ nquads_to_vortex,
18
+ vortex_to_nquads,
19
+ } from '../pkg/web/vortex_rdf.js';
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@vortex-rdf/vortex-rdf-store",
3
+ "version": "0.1.0",
4
+ "description": "High-performance, zero-copy columnar RDF serialization format",
5
+ "type": "module",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "main": "./entry/browser.js",
10
+ "types": "./pkg/web/vortex_rdf.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./pkg/web/vortex_rdf.d.ts",
14
+ "node": "./entry/node.js",
15
+ "browser": "./entry/browser.js",
16
+ "default": "./entry/browser.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "entry/node.js",
21
+ "entry/node.d.ts",
22
+ "entry/browser.js",
23
+ "entry/browser.d.ts",
24
+ "pkg/web/vortex_rdf.js",
25
+ "pkg/web/vortex_rdf.d.ts",
26
+ "pkg/web/vortex_rdf_bg.wasm",
27
+ "pkg/web/vortex_rdf_bg.wasm.d.ts",
28
+ "pkg/web/snippets/**"
29
+ ],
30
+ "scripts": {
31
+ "build:wasm": "wasm-pack build --target web --out-dir pkg/web --no-pack",
32
+ "build:clean-manifest": "node -e \"require('fs').rmSync('pkg/web/package.json',{force:true})\"",
33
+ "build": "npm run build:wasm && npm run build:clean-manifest",
34
+ "prepublishOnly": "npm run build",
35
+ "test": "vitest run"
36
+ },
37
+ "keywords": [
38
+ "rdf",
39
+ "vortex",
40
+ "columnar",
41
+ "semantic-web",
42
+ "wasm",
43
+ "zero-copy"
44
+ ],
45
+ "author": "Julián Rojas",
46
+ "license": "MIT",
47
+ "devDependencies": {
48
+ "@types/node": "^26.1.1",
49
+ "rdf-data-factory": "^2.0.2",
50
+ "typescript": "^7.0.2",
51
+ "vitest": "^4.1.10",
52
+ "wasm-pack": "^0.15.0"
53
+ },
54
+ "dependencies": {
55
+ "@rdfjs/types": "^2.0.1"
56
+ }
57
+ }