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.
- package/CHANGELOG.md +29 -0
- package/README.md +50 -6
- 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`.
|
|
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
|
-
###
|
|
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
|
-
|
|
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
|
-
|
|
156
|
-
conn.execute("MATCH (p:Person {
|
|
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
|
-
//
|
|
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
|
+
"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.
|
|
69
|
-
"frogql-darwin-arm64": "0.2.
|
|
70
|
-
"frogql-linux-x64-gnu": "0.2.
|
|
71
|
-
"frogql-linux-arm64-gnu": "0.2.
|
|
72
|
-
"frogql-win32-x64-msvc": "0.2.
|
|
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
|
}
|