frogql-wasm 0.5.4 → 0.5.5
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 +34 -0
- package/frogql_wasm.d.ts +96 -3
- package/frogql_wasm.js +143 -3
- package/frogql_wasm_bg.wasm +0 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -40,6 +40,40 @@ Verified: a fresh project that `npm install`s the package and runs
|
|
|
40
40
|
`vite build` bundles the `.wasm` (≈300 kB gzip) with no plugin.
|
|
41
41
|
|
|
42
42
|
- `open_json(json)` → `Connection`. Parses `{ "nodes": [...], "edges": [...] }`.
|
|
43
|
+
- `open_bytes(gdb, ltj?)` → `Connection`. Opens a real `.gdb` image fetched
|
|
44
|
+
over the network, paged out of RAM. `ltj` is the matching
|
|
45
|
+
`<db>.gdb.ltj` sidecar, or `null` to rebuild the index at open.
|
|
46
|
+
|
|
47
|
+
```js
|
|
48
|
+
const [gdb, ltj] = await Promise.all([
|
|
49
|
+
fetch("/santiago.gdb").then(r => r.arrayBuffer()),
|
|
50
|
+
fetch("/santiago.gdb.ltj").then(r => r.arrayBuffer()),
|
|
51
|
+
]);
|
|
52
|
+
const conn = open_bytes(new Uint8Array(gdb), new Uint8Array(ltj));
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Prefer it over `open_json` for anything large: a JSON document is parsed
|
|
56
|
+
and rebuilt node by node and carries no index, while a `.gdb` is already
|
|
57
|
+
in the engine's layout and its catalog supplies the schema instead of
|
|
58
|
+
it being re-inferred. Measured on a 459 127-node / 2 244 154-edge graph
|
|
59
|
+
(309 MB `.gdb`): `open_bytes` in 3.7 s, first query 13 ms.
|
|
60
|
+
|
|
61
|
+
**Whether to fetch the sidecar depends on size.** It exists so the six
|
|
62
|
+
LTJ trie orderings are not rebuilt, which is `O(E log E)` — 252 s
|
|
63
|
+
measured on a 617 M-edge graph. But decoding it is not free either, and
|
|
64
|
+
on the 2.2 M-edge graph above the 70 MB sidecar *cost* ~0.8 s against
|
|
65
|
+
rebuilding in ~1.7 s. Fetch it for a graph big enough that the rebuild
|
|
66
|
+
hurts; skip it otherwise and save the download. A sidecar that does not
|
|
67
|
+
describe the database is refused and the index rebuilt, so a mismatched
|
|
68
|
+
pair costs time and never correctness.
|
|
69
|
+
|
|
70
|
+
Read-only as to storage: DML works through the same overlay as every
|
|
71
|
+
other backend, but there is nowhere to write pages back to, so the
|
|
72
|
+
durable copy stays whatever the server serves. `to_json()` gives a
|
|
73
|
+
snapshot of the merged view. Two things a file gives that bytes do not,
|
|
74
|
+
and which are skipped rather than faked: the legacy-format upgrade
|
|
75
|
+
(re-save such a database with a native build before serving it) and
|
|
76
|
+
vector sidecars.
|
|
43
77
|
- `Connection.execute(query, limit?)` → rows array (read queries) or a
|
|
44
78
|
counters object (INSERT / SET / REMOVE / DELETE). `limit` defaults to 100.
|
|
45
79
|
- `Connection.to_json()` → JSON string of the live merged view (base +
|
package/frogql_wasm.d.ts
CHANGED
|
@@ -2,12 +2,40 @@
|
|
|
2
2
|
/* eslint-disable */
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* A live
|
|
5
|
+
* A live graph plus the caches that keep query latency flat.
|
|
6
6
|
*/
|
|
7
7
|
export class Connection {
|
|
8
8
|
private constructor();
|
|
9
9
|
free(): void;
|
|
10
10
|
[Symbol.dispose](): void;
|
|
11
|
+
/**
|
|
12
|
+
* `{ node_labels, edge_labels, node_count, edge_count }`, mirroring
|
|
13
|
+
* the Python/Node `schema()` summary.
|
|
14
|
+
* What the typechecker knows about a query, without running it.
|
|
15
|
+
*
|
|
16
|
+
* This is the half of froGQL a REPL shows and a bare "0 rows" hides.
|
|
17
|
+
* `(n:Calle)-[e]->(m)` against a schema where every `EN_CALLE` points
|
|
18
|
+
* *into* `Calle` is not an empty answer, it is a **provably** empty
|
|
19
|
+
* one — the checker settles it before the runtime is asked, and
|
|
20
|
+
* saying "0 filas" instead sends the reader looking for missing data
|
|
21
|
+
* that was never missing.
|
|
22
|
+
*
|
|
23
|
+
* ```json
|
|
24
|
+
* { "ok": true, "empty": true, "errors": [], "warnings": [...],
|
|
25
|
+
* "vars": [{ "name": "n", "type": "(:Calle {...})" }] }
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* The pipeline is run here rather than through
|
|
29
|
+
* `compile_query_with_diagnostics_with` because that returns the
|
|
30
|
+
* compiled query and drops the `TypeEnvironment` — and the
|
|
31
|
+
* environment is the interesting part: it is what the checker
|
|
32
|
+
* *inferred*, per variable, which no amount of reading the query
|
|
33
|
+
* back tells you.
|
|
34
|
+
*
|
|
35
|
+
* Cheap enough to run on every keystroke: parse, elaborate and check,
|
|
36
|
+
* with no optimizer pass and no graph access at all.
|
|
37
|
+
*/
|
|
38
|
+
check(query: string): any;
|
|
11
39
|
/**
|
|
12
40
|
* Execute one GQL statement. Read queries return an array of row
|
|
13
41
|
* objects; data-modifying statements return a counters object.
|
|
@@ -16,9 +44,43 @@ export class Connection {
|
|
|
16
44
|
*/
|
|
17
45
|
execute(query: string, limit?: number | null): any;
|
|
18
46
|
/**
|
|
19
|
-
*
|
|
20
|
-
* the
|
|
47
|
+
* The active GRAPH TYPE, rendered the way `SHOW GRAPH TYPE DEFAULT`
|
|
48
|
+
* renders it in the REPL: one line per node type and one per edge
|
|
49
|
+
* type, with the properties each carries.
|
|
50
|
+
*
|
|
51
|
+
* `schema()` answers "which labels exist", which is what a sidebar
|
|
52
|
+
* needs and all it needs. This answers "what is in them" — the
|
|
53
|
+
* question anyone writing a query against an unfamiliar database
|
|
54
|
+
* actually has, and the one that was unanswerable in the browser
|
|
55
|
+
* because the formatter was never exposed.
|
|
56
|
+
*
|
|
57
|
+
* On a `.gdb` this reads the catalog; on a JSON graph it is inferred
|
|
58
|
+
* from the data, which is the same split `active_schema` makes.
|
|
59
|
+
*/
|
|
60
|
+
graph_type(): string;
|
|
61
|
+
/**
|
|
62
|
+
* The active GRAPH TYPE as data, so it can be **drawn**.
|
|
63
|
+
*
|
|
64
|
+
* `graph_type()` renders the same thing as text, which is what the
|
|
65
|
+
* REPL shows and what a reader skims. A schema is a graph, though —
|
|
66
|
+
* node types joined by edge types — and the shape of it is the part
|
|
67
|
+
* a text listing makes you reconstruct in your head. This returns
|
|
68
|
+
* the pieces a diagram needs and lets the page draw them.
|
|
69
|
+
*
|
|
70
|
+
* ```json
|
|
71
|
+
* { "nodes": [{ "name": "fpl", "labels": ["Fpl"],
|
|
72
|
+
* "props": [{ "key": "fplId", "type": "STRING" }] }],
|
|
73
|
+
* "edges": [{ "label": "SALE_DE", "from": "fpl", "to": "aerodromo",
|
|
74
|
+
* "directed": true, "props": [] }] }
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* Endpoints are the *names* `typing::format::NodeTypeNames` derives,
|
|
78
|
+
* so the diagram and the text call a type the same thing. An edge
|
|
79
|
+
* whose endpoint matches no declared node type gets `null` there
|
|
80
|
+
* rather than a name that would misdescribe it — the same care
|
|
81
|
+
* `format_schema` takes when it declines to borrow a name.
|
|
21
82
|
*/
|
|
83
|
+
graph_type_json(): any;
|
|
22
84
|
schema(): any;
|
|
23
85
|
/**
|
|
24
86
|
* Serialise the live merged view (base + overlay) to a JSON string —
|
|
@@ -30,6 +92,33 @@ export class Connection {
|
|
|
30
92
|
readonly node_count: number;
|
|
31
93
|
}
|
|
32
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Open a `.gdb` image fetched over the network, with its `.ltj` sidecar
|
|
97
|
+
* when the caller has it.
|
|
98
|
+
*
|
|
99
|
+
* ```js
|
|
100
|
+
* const [gdb, ltj] = await Promise.all([
|
|
101
|
+
* fetch("/santiago.gdb").then(r => r.arrayBuffer()),
|
|
102
|
+
* fetch("/santiago.gdb.ltj").then(r => r.arrayBuffer()),
|
|
103
|
+
* ]);
|
|
104
|
+
* const conn = open_bytes(new Uint8Array(gdb), new Uint8Array(ltj));
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* `ltj` is optional and is the reason to prefer this over `open_json`
|
|
108
|
+
* for anything large: without it the six LTJ trie orderings are rebuilt
|
|
109
|
+
* at open, which is `O(E log E)`. A sidecar that does not describe this
|
|
110
|
+
* database is **refused, not trusted** — the same `(graph_id,
|
|
111
|
+
* node_count, edge_count)` fingerprint a file-backed open checks — and
|
|
112
|
+
* the index is rebuilt instead, so a mismatched pair costs time and
|
|
113
|
+
* never correctness.
|
|
114
|
+
*
|
|
115
|
+
* The connection is read-mostly: DML works, through the same overlay as
|
|
116
|
+
* every other backend, but there is nowhere to write pages back to, so
|
|
117
|
+
* the durable copy is whatever the server serves. `to_json()` still
|
|
118
|
+
* gives a snapshot of the merged view.
|
|
119
|
+
*/
|
|
120
|
+
export function open_bytes(gdb: Uint8Array, ltj?: Uint8Array | null): Connection;
|
|
121
|
+
|
|
33
122
|
/**
|
|
34
123
|
* Parse a JSON graph document (`{"nodes": [...], "edges": [...]}`) and
|
|
35
124
|
* open a connection over it. Warms the LTJ index eagerly so the first
|
|
@@ -42,11 +131,15 @@ export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembl
|
|
|
42
131
|
export interface InitOutput {
|
|
43
132
|
readonly memory: WebAssembly.Memory;
|
|
44
133
|
readonly __wbg_connection_free: (a: number, b: number) => void;
|
|
134
|
+
readonly connection_check: (a: number, b: number, c: number) => [number, number, number];
|
|
45
135
|
readonly connection_edge_count: (a: number) => number;
|
|
46
136
|
readonly connection_execute: (a: number, b: number, c: number, d: number) => [number, number, number];
|
|
137
|
+
readonly connection_graph_type: (a: number) => [number, number];
|
|
138
|
+
readonly connection_graph_type_json: (a: number) => [number, number, number];
|
|
47
139
|
readonly connection_node_count: (a: number) => number;
|
|
48
140
|
readonly connection_schema: (a: number) => [number, number, number];
|
|
49
141
|
readonly connection_to_json: (a: number) => [number, number];
|
|
142
|
+
readonly open_bytes: (a: number, b: number, c: number, d: number) => [number, number, number];
|
|
50
143
|
readonly open_json: (a: number, b: number) => [number, number, number];
|
|
51
144
|
readonly __wbindgen_malloc: (a: number, b: number) => number;
|
|
52
145
|
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
|
package/frogql_wasm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* @ts-self-types="./frogql_wasm.d.ts" */
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* A live
|
|
4
|
+
* A live graph plus the caches that keep query latency flat.
|
|
5
5
|
*/
|
|
6
6
|
export class Connection {
|
|
7
7
|
static __wrap(ptr) {
|
|
@@ -20,6 +20,44 @@ export class Connection {
|
|
|
20
20
|
const ptr = this.__destroy_into_raw();
|
|
21
21
|
wasm.__wbg_connection_free(ptr, 0);
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* `{ node_labels, edge_labels, node_count, edge_count }`, mirroring
|
|
25
|
+
* the Python/Node `schema()` summary.
|
|
26
|
+
* What the typechecker knows about a query, without running it.
|
|
27
|
+
*
|
|
28
|
+
* This is the half of froGQL a REPL shows and a bare "0 rows" hides.
|
|
29
|
+
* `(n:Calle)-[e]->(m)` against a schema where every `EN_CALLE` points
|
|
30
|
+
* *into* `Calle` is not an empty answer, it is a **provably** empty
|
|
31
|
+
* one — the checker settles it before the runtime is asked, and
|
|
32
|
+
* saying "0 filas" instead sends the reader looking for missing data
|
|
33
|
+
* that was never missing.
|
|
34
|
+
*
|
|
35
|
+
* ```json
|
|
36
|
+
* { "ok": true, "empty": true, "errors": [], "warnings": [...],
|
|
37
|
+
* "vars": [{ "name": "n", "type": "(:Calle {...})" }] }
|
|
38
|
+
* ```
|
|
39
|
+
*
|
|
40
|
+
* The pipeline is run here rather than through
|
|
41
|
+
* `compile_query_with_diagnostics_with` because that returns the
|
|
42
|
+
* compiled query and drops the `TypeEnvironment` — and the
|
|
43
|
+
* environment is the interesting part: it is what the checker
|
|
44
|
+
* *inferred*, per variable, which no amount of reading the query
|
|
45
|
+
* back tells you.
|
|
46
|
+
*
|
|
47
|
+
* Cheap enough to run on every keystroke: parse, elaborate and check,
|
|
48
|
+
* with no optimizer pass and no graph access at all.
|
|
49
|
+
* @param {string} query
|
|
50
|
+
* @returns {any}
|
|
51
|
+
*/
|
|
52
|
+
check(query) {
|
|
53
|
+
const ptr0 = passStringToWasm0(query, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
|
|
54
|
+
const len0 = WASM_VECTOR_LEN;
|
|
55
|
+
const ret = wasm.connection_check(this.__wbg_ptr, ptr0, len0);
|
|
56
|
+
if (ret[2]) {
|
|
57
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
58
|
+
}
|
|
59
|
+
return takeFromExternrefTable0(ret[0]);
|
|
60
|
+
}
|
|
23
61
|
/**
|
|
24
62
|
* @returns {number}
|
|
25
63
|
*/
|
|
@@ -45,6 +83,63 @@ export class Connection {
|
|
|
45
83
|
}
|
|
46
84
|
return takeFromExternrefTable0(ret[0]);
|
|
47
85
|
}
|
|
86
|
+
/**
|
|
87
|
+
* The active GRAPH TYPE, rendered the way `SHOW GRAPH TYPE DEFAULT`
|
|
88
|
+
* renders it in the REPL: one line per node type and one per edge
|
|
89
|
+
* type, with the properties each carries.
|
|
90
|
+
*
|
|
91
|
+
* `schema()` answers "which labels exist", which is what a sidebar
|
|
92
|
+
* needs and all it needs. This answers "what is in them" — the
|
|
93
|
+
* question anyone writing a query against an unfamiliar database
|
|
94
|
+
* actually has, and the one that was unanswerable in the browser
|
|
95
|
+
* because the formatter was never exposed.
|
|
96
|
+
*
|
|
97
|
+
* On a `.gdb` this reads the catalog; on a JSON graph it is inferred
|
|
98
|
+
* from the data, which is the same split `active_schema` makes.
|
|
99
|
+
* @returns {string}
|
|
100
|
+
*/
|
|
101
|
+
graph_type() {
|
|
102
|
+
let deferred1_0;
|
|
103
|
+
let deferred1_1;
|
|
104
|
+
try {
|
|
105
|
+
const ret = wasm.connection_graph_type(this.__wbg_ptr);
|
|
106
|
+
deferred1_0 = ret[0];
|
|
107
|
+
deferred1_1 = ret[1];
|
|
108
|
+
return getStringFromWasm0(ret[0], ret[1]);
|
|
109
|
+
} finally {
|
|
110
|
+
wasm.__wbindgen_free(deferred1_0, deferred1_1, 1);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* The active GRAPH TYPE as data, so it can be **drawn**.
|
|
115
|
+
*
|
|
116
|
+
* `graph_type()` renders the same thing as text, which is what the
|
|
117
|
+
* REPL shows and what a reader skims. A schema is a graph, though —
|
|
118
|
+
* node types joined by edge types — and the shape of it is the part
|
|
119
|
+
* a text listing makes you reconstruct in your head. This returns
|
|
120
|
+
* the pieces a diagram needs and lets the page draw them.
|
|
121
|
+
*
|
|
122
|
+
* ```json
|
|
123
|
+
* { "nodes": [{ "name": "fpl", "labels": ["Fpl"],
|
|
124
|
+
* "props": [{ "key": "fplId", "type": "STRING" }] }],
|
|
125
|
+
* "edges": [{ "label": "SALE_DE", "from": "fpl", "to": "aerodromo",
|
|
126
|
+
* "directed": true, "props": [] }] }
|
|
127
|
+
* ```
|
|
128
|
+
*
|
|
129
|
+
* Endpoints are the *names* `typing::format::NodeTypeNames` derives,
|
|
130
|
+
* so the diagram and the text call a type the same thing. An edge
|
|
131
|
+
* whose endpoint matches no declared node type gets `null` there
|
|
132
|
+
* rather than a name that would misdescribe it — the same care
|
|
133
|
+
* `format_schema` takes when it declines to borrow a name.
|
|
134
|
+
* @returns {any}
|
|
135
|
+
*/
|
|
136
|
+
graph_type_json() {
|
|
137
|
+
const ret = wasm.connection_graph_type_json(this.__wbg_ptr);
|
|
138
|
+
if (ret[2]) {
|
|
139
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
140
|
+
}
|
|
141
|
+
return takeFromExternrefTable0(ret[0]);
|
|
142
|
+
}
|
|
48
143
|
/**
|
|
49
144
|
* @returns {number}
|
|
50
145
|
*/
|
|
@@ -53,8 +148,6 @@ export class Connection {
|
|
|
53
148
|
return ret >>> 0;
|
|
54
149
|
}
|
|
55
150
|
/**
|
|
56
|
-
* `{ node_labels, edge_labels, node_count, edge_count }`, mirroring
|
|
57
|
-
* the Python/Node `schema()` summary.
|
|
58
151
|
* @returns {any}
|
|
59
152
|
*/
|
|
60
153
|
schema() {
|
|
@@ -85,6 +178,46 @@ export class Connection {
|
|
|
85
178
|
}
|
|
86
179
|
if (Symbol.dispose) Connection.prototype[Symbol.dispose] = Connection.prototype.free;
|
|
87
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Open a `.gdb` image fetched over the network, with its `.ltj` sidecar
|
|
183
|
+
* when the caller has it.
|
|
184
|
+
*
|
|
185
|
+
* ```js
|
|
186
|
+
* const [gdb, ltj] = await Promise.all([
|
|
187
|
+
* fetch("/santiago.gdb").then(r => r.arrayBuffer()),
|
|
188
|
+
* fetch("/santiago.gdb.ltj").then(r => r.arrayBuffer()),
|
|
189
|
+
* ]);
|
|
190
|
+
* const conn = open_bytes(new Uint8Array(gdb), new Uint8Array(ltj));
|
|
191
|
+
* ```
|
|
192
|
+
*
|
|
193
|
+
* `ltj` is optional and is the reason to prefer this over `open_json`
|
|
194
|
+
* for anything large: without it the six LTJ trie orderings are rebuilt
|
|
195
|
+
* at open, which is `O(E log E)`. A sidecar that does not describe this
|
|
196
|
+
* database is **refused, not trusted** — the same `(graph_id,
|
|
197
|
+
* node_count, edge_count)` fingerprint a file-backed open checks — and
|
|
198
|
+
* the index is rebuilt instead, so a mismatched pair costs time and
|
|
199
|
+
* never correctness.
|
|
200
|
+
*
|
|
201
|
+
* The connection is read-mostly: DML works, through the same overlay as
|
|
202
|
+
* every other backend, but there is nowhere to write pages back to, so
|
|
203
|
+
* the durable copy is whatever the server serves. `to_json()` still
|
|
204
|
+
* gives a snapshot of the merged view.
|
|
205
|
+
* @param {Uint8Array} gdb
|
|
206
|
+
* @param {Uint8Array | null} [ltj]
|
|
207
|
+
* @returns {Connection}
|
|
208
|
+
*/
|
|
209
|
+
export function open_bytes(gdb, ltj) {
|
|
210
|
+
const ptr0 = passArray8ToWasm0(gdb, wasm.__wbindgen_malloc);
|
|
211
|
+
const len0 = WASM_VECTOR_LEN;
|
|
212
|
+
var ptr1 = isLikeNone(ltj) ? 0 : passArray8ToWasm0(ltj, wasm.__wbindgen_malloc);
|
|
213
|
+
var len1 = WASM_VECTOR_LEN;
|
|
214
|
+
const ret = wasm.open_bytes(ptr0, len0, ptr1, len1);
|
|
215
|
+
if (ret[2]) {
|
|
216
|
+
throw takeFromExternrefTable0(ret[1]);
|
|
217
|
+
}
|
|
218
|
+
return Connection.__wrap(ret[0]);
|
|
219
|
+
}
|
|
220
|
+
|
|
88
221
|
/**
|
|
89
222
|
* Parse a JSON graph document (`{"nodes": [...], "edges": [...]}`) and
|
|
90
223
|
* open a connection over it. Warms the LTJ index eagerly so the first
|
|
@@ -234,6 +367,13 @@ function isLikeNone(x) {
|
|
|
234
367
|
return x === undefined || x === null;
|
|
235
368
|
}
|
|
236
369
|
|
|
370
|
+
function passArray8ToWasm0(arg, malloc) {
|
|
371
|
+
const ptr = malloc(arg.length * 1, 1) >>> 0;
|
|
372
|
+
getUint8ArrayMemory0().set(arg, ptr / 1);
|
|
373
|
+
WASM_VECTOR_LEN = arg.length;
|
|
374
|
+
return ptr;
|
|
375
|
+
}
|
|
376
|
+
|
|
237
377
|
function passStringToWasm0(arg, malloc, realloc) {
|
|
238
378
|
if (realloc === undefined) {
|
|
239
379
|
const buf = cachedTextEncoder.encode(arg);
|
package/frogql_wasm_bg.wasm
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "frogql-wasm",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"description": "froGQL — embedded GQL graph database with ISO GQL path patterns (Rust core, WebAssembly bindings for the browser)",
|
|
5
|
-
"version": "0.5.
|
|
5
|
+
"version": "0.5.5",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|