@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 +216 -0
- package/entry/browser.d.ts +18 -0
- package/entry/browser.js +17 -0
- package/entry/node.d.ts +19 -0
- package/entry/node.js +19 -0
- package/package.json +57 -0
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';
|
package/entry/browser.js
ADDED
|
@@ -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';
|
package/entry/node.d.ts
ADDED
|
@@ -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
|
+
}
|