frogql 0.2.3 → 0.2.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +50 -6
  3. package/package.json +6 -6
package/CHANGELOG.md CHANGED
@@ -8,6 +8,35 @@ Released in lock-step with the [PyPI `frogql` package](https://pypi.org/project/
8
8
 
9
9
  ## [Unreleased]
10
10
 
11
+ ### Documentation
12
+
13
+ - README: expanded the "Insert, modify, persist" section into "Incremental writes" with explicit upsert and delete-by-id snippets, and a notes block covering auto-commit, LTJ cache invalidation per mutation, per-statement transaction granularity, no `MERGE`, no multi-DML chains, and `importJson` not being incremental.
14
+ - README: corrected the `EdgeRef.props` paragraph — props is always populated since `0.2.3`, not just on top-level `RETURN e`.
15
+
16
+ ## [0.2.3] — 2026-05-13
17
+
18
+ ### Added
19
+
20
+ - Parser accepts `elementPropertySpecification` without `isLabelExpression` per ISO/IEC 39075:2024 §16. Now valid: `({k: v})`, `(x {k: v})`, `-[{k: v}]->`, `-[e {k: v}]->`. Previously these were rejected with `expected path pattern, got LBrace`; the colonised forms (`(:Label {k: v})`, `(: {k: v})`) already worked and behave identically.
21
+
22
+ ## [0.2.2] — 2026-05-13
23
+
24
+ ### Fixed
25
+
26
+ - Edges expose `props` symmetrically with nodes — both via `RETURN e` and inside `_paths`. `EdgeRef.props` in `index.d.ts` is no longer optional. Pre-`0.2.2` versions silently dropped edge properties in both code paths despite the `EdgeRef` doc-comment claiming `RETURN e` would include them.
27
+
28
+ ## [0.2.1] — 2026-05-12
29
+
30
+ ### Changed
31
+
32
+ - Re-release of `0.2.0` after the lock-step PyPI + npm pipeline reached steady state; no surface or on-disk format changes.
33
+
34
+ ## [0.2.0] — 2026-05-12
35
+
36
+ ### Added
37
+
38
+ - First stable release. Surface, on-disk format, and the multi-package npm publish flow validated end-to-end through the `-rc.x` series.
39
+
11
40
  ## [0.2.0-rc.3] — 2026-05-12
12
41
 
13
42
  ### Fixed
package/README.md CHANGED
@@ -97,7 +97,7 @@ import type {
97
97
  | `SHOW INDEXES` | `IndexSummary[]` |
98
98
  | `INSERT / SET / REMOVE / DELETE / DETACH DELETE` | `DmCounters` |
99
99
 
100
- Node and edge references inside row values use `NodeRef` / `EdgeRef`. The `props` field on `EdgeRef` is optional: present when returned via `RETURN e` at the top level, omitted in path-internal context to keep payloads small.
100
+ Node and edge references inside row values use `NodeRef` / `EdgeRef`. Both shapes are symmetric — `kind`, `id`, `labels`, and `props` are always populated, whether the element arrives via `RETURN x` at the top level or inside a `_paths` entry. (Pre-`0.2.3` versions omitted `props` on edges in `_paths`; that gap is closed.)
101
101
 
102
102
  ## Examples
103
103
 
@@ -144,21 +144,65 @@ const rows = conn.execute(
144
144
 
145
145
  Left-outer join with bind-pushdown: per outer row, pin shared variables and pin-execute the inner. SQLite-style nested-loop. ~93× speedup vs the global-evaluate-and-join baseline on LDBC IS5.
146
146
 
147
- ### Insert, modify, persist
147
+ ### Incremental writes (insert / upsert / delete)
148
+
149
+ The full ISO §13 DML surface is reachable through `execute()` — there is no dedicated `connection.insert()` / `connection.delete()` method, the same way SQLite doesn't have one. Writes go in as GQL statements.
148
150
 
149
151
  ```ts
150
152
  import type { DmCounters } from "frogql";
151
153
 
152
- const a = conn.execute("INSERT (:Person {name: 'Alice', age: 30})") as DmCounters;
154
+ // Insert.
155
+ const a = conn.execute("INSERT (:Person {id: 'u-42', name: 'Alice', age: 30})") as DmCounters;
153
156
  console.log(a.nodesInserted); // 1
154
157
 
155
- conn.execute("MATCH (p:Person {name: 'Alice'}) SET p.age = 31");
156
- conn.execute("MATCH (p:Person {name: 'Alice'}) REMOVE p.age");
158
+ // Update a property.
159
+ conn.execute("MATCH (p:Person {id: 'u-42'}) SET p.age = 31");
160
+
161
+ // Remove a property without dropping the node.
162
+ conn.execute("MATCH (p:Person {id: 'u-42'}) REMOVE p.age");
157
163
 
158
- // Until you save(), mutations live in the in-memory overlay only.
164
+ // Delete by id. DETACH first removes incident edges; NODETACH errors out
165
+ // if the node still has neighbours.
166
+ conn.execute("MATCH (p:Person {id: 'u-42'}) DETACH DELETE p");
167
+
168
+ // Mutations live in an in-memory overlay until you call save().
159
169
  conn.save();
160
170
  ```
161
171
 
172
+ #### Upsert pattern
173
+
174
+ There is no `MERGE` yet (deferred per the ISO §13 MVP). Upsert is a two-step probe + branch:
175
+
176
+ ```ts
177
+ function upsertPerson(conn: Connection, id: string, name: string) {
178
+ const hits = conn.execute(
179
+ `MATCH (p:Person {id: '${id}'}) RETURN p.id AS id LIMIT 1`,
180
+ 1
181
+ ) as Array<{ id: string }>;
182
+ if (hits.length === 0) {
183
+ conn.execute(`INSERT (:Person {id: '${id}', name: '${name}'})`);
184
+ } else {
185
+ conn.execute(`MATCH (p:Person {id: '${id}'}) SET p.name = '${name}'`);
186
+ }
187
+ }
188
+ ```
189
+
190
+ A unique secondary index on `(Person, id)` makes the probe a point lookup:
191
+
192
+ ```ts
193
+ conn.execute("CREATE INDEX ON :Person(id) USING HASH");
194
+ ```
195
+
196
+ `execute()` takes a raw query string — there is no parameter-binding API. **Escape user input yourself** (`String#replace(/'/g, "\\'")` at minimum) or you'll have a query-injection vector.
197
+
198
+ #### Operational notes for sync / streaming workloads
199
+
200
+ - **Auto-commit is off.** Mutations stay in the RAM overlay until `conn.save()` writes a fresh `.gdb` atomically. A crashed process loses the unsaved overlay. Batch writes and save on a cadence (every N statements / every T seconds) rather than per-mutation.
201
+ - **LTJ cache invalidation per mutation.** Each successful DML drops the cached six-ordering TripleIndex; the next read rebuilds it (~670 ms on an LDBC SF0.1-shaped graph). Tight write-read loops pay this per mutation. If your workload is write-heavy with occasional reads, that's fine; if it interleaves writes and reads, batch reads after a batch of writes.
202
+ - **Transactions are per-statement.** A failed statement rolls back its own overlay delta. There's no multi-statement transaction boundary yet (deferred until WAL).
203
+ - **No `MERGE`, no multi-DML chains** (`MATCH … INSERT … SET …` in one statement). One DML op per `execute()` call.
204
+ - **`importJson` is not incremental.** It builds a fresh `.gdb` from scratch and overwrites the destination. Use `execute("INSERT …")` against an open `Connection` for incremental ingest.
205
+
162
206
  ### Schema introspection
163
207
 
164
208
  ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frogql",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "froGQL — embedded GQL graph database with ISO GQL path patterns (Rust core, Node.js bindings via napi-rs)",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -65,10 +65,10 @@
65
65
  "provenance": true
66
66
  },
67
67
  "optionalDependencies": {
68
- "frogql-darwin-x64": "0.2.3",
69
- "frogql-darwin-arm64": "0.2.3",
70
- "frogql-linux-x64-gnu": "0.2.3",
71
- "frogql-linux-arm64-gnu": "0.2.3",
72
- "frogql-win32-x64-msvc": "0.2.3"
68
+ "frogql-darwin-x64": "0.2.5",
69
+ "frogql-darwin-arm64": "0.2.5",
70
+ "frogql-linux-x64-gnu": "0.2.5",
71
+ "frogql-linux-arm64-gnu": "0.2.5",
72
+ "frogql-win32-x64-msvc": "0.2.5"
73
73
  }
74
74
  }