frogql-wasm 0.5.3 → 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 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 in-memory graph plus the caches that keep query latency flat.
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
- * `{ node_labels, edge_labels, node_count, edge_count }`, mirroring
20
- * the Python/Node `schema()` summary.
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 in-memory graph plus the caches that keep query latency flat.
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
@@ -149,6 +282,10 @@ function __wbg_get_imports() {
149
282
  const ret = new Array();
150
283
  return ret;
151
284
  },
285
+ __wbg_now_190933fa139cc119: function() {
286
+ const ret = Date.now();
287
+ return ret;
288
+ },
152
289
  __wbg_set_52b1e1eb5bed906a: function(arg0, arg1, arg2) {
153
290
  const ret = arg0.set(arg1, arg2);
154
291
  return ret;
@@ -230,6 +367,13 @@ function isLikeNone(x) {
230
367
  return x === undefined || x === null;
231
368
  }
232
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
+
233
377
  function passStringToWasm0(arg, malloc, realloc) {
234
378
  if (realloc === undefined) {
235
379
  const buf = cachedTextEncoder.encode(arg);
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.3",
5
+ "version": "0.5.5",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",