grafeo-ogm 1.8.6 → 1.9.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/CHANGELOG.md +100 -0
- package/README.md +131 -26
- package/dist/cjs/cli/commands/db-push.d.ts +3 -0
- package/dist/cjs/cli/commands/db-push.d.ts.map +1 -0
- package/dist/cjs/cli/commands/db-push.js +197 -0
- package/dist/cjs/cli/commands/db-push.js.map +1 -0
- package/dist/cjs/cli/commands/db-seed.d.ts +3 -0
- package/dist/cjs/cli/commands/db-seed.d.ts.map +1 -0
- package/dist/cjs/cli/commands/db-seed.js +153 -0
- package/dist/cjs/cli/commands/db-seed.js.map +1 -0
- package/dist/cjs/cli/commands/generate.d.ts +3 -0
- package/dist/cjs/cli/commands/generate.d.ts.map +1 -0
- package/dist/cjs/cli/commands/generate.js +218 -0
- package/dist/cjs/cli/commands/generate.js.map +1 -0
- package/dist/cjs/cli/config.d.ts +38 -0
- package/dist/cjs/cli/config.d.ts.map +1 -0
- package/dist/cjs/cli/config.js +173 -0
- package/dist/cjs/cli/config.js.map +1 -0
- package/dist/cjs/cli/driver.d.ts +12 -0
- package/dist/cjs/cli/driver.d.ts.map +1 -0
- package/dist/cjs/cli/driver.js +21 -0
- package/dist/cjs/cli/driver.js.map +1 -0
- package/dist/cjs/cli/errors.d.ts +16 -0
- package/dist/cjs/cli/errors.d.ts.map +1 -0
- package/dist/cjs/cli/errors.js +66 -0
- package/dist/cjs/cli/errors.js.map +1 -0
- package/dist/cjs/cli/index.d.ts +8 -0
- package/dist/cjs/cli/index.d.ts.map +1 -0
- package/dist/cjs/cli/index.js +187 -0
- package/dist/cjs/cli/index.js.map +1 -0
- package/dist/cjs/cli/introspect.d.ts +8 -0
- package/dist/cjs/cli/introspect.d.ts.map +1 -0
- package/dist/cjs/cli/introspect.js +52 -0
- package/dist/cjs/cli/introspect.js.map +1 -0
- package/dist/cjs/cli/push-planner.d.ts +52 -0
- package/dist/cjs/cli/push-planner.d.ts.map +1 -0
- package/dist/cjs/cli/push-planner.js +114 -0
- package/dist/cjs/cli/push-planner.js.map +1 -0
- package/dist/cjs/cli/types.d.ts +71 -0
- package/dist/cjs/cli/types.d.ts.map +1 -0
- package/dist/cjs/cli/types.js +8 -0
- package/dist/cjs/cli/types.js.map +1 -0
- package/dist/cjs/compilers/mutation.compiler.d.ts.map +1 -1
- package/dist/cjs/compilers/mutation.compiler.js +28 -25
- package/dist/cjs/compilers/mutation.compiler.js.map +1 -1
- package/dist/cjs/compilers/where.compiler.d.ts.map +1 -1
- package/dist/cjs/compilers/where.compiler.js +53 -16
- package/dist/cjs/compilers/where.compiler.js.map +1 -1
- package/dist/cjs/index.d.ts +2 -0
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +6 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/model.d.ts +3 -2
- package/dist/cjs/model.d.ts.map +1 -1
- package/dist/cjs/model.js +11 -4
- package/dist/cjs/model.js.map +1 -1
- package/dist/cjs/ogm.d.ts +0 -3
- package/dist/cjs/ogm.d.ts.map +1 -1
- package/dist/cjs/ogm.js +53 -67
- package/dist/cjs/ogm.js.map +1 -1
- package/dist/cjs/policy/types.d.ts +7 -0
- package/dist/cjs/policy/types.d.ts.map +1 -1
- package/dist/cjs/policy/types.js.map +1 -1
- package/dist/cjs/schema/index-statements.d.ts +50 -0
- package/dist/cjs/schema/index-statements.d.ts.map +1 -0
- package/dist/cjs/schema/index-statements.js +117 -0
- package/dist/cjs/schema/index-statements.js.map +1 -0
- package/dist/cjs/subgraph/subgraph-operations.d.ts.map +1 -1
- package/dist/cjs/subgraph/subgraph-operations.js +21 -5
- package/dist/cjs/subgraph/subgraph-operations.js.map +1 -1
- package/dist/cjs/utils/deep-freeze.d.ts +29 -0
- package/dist/cjs/utils/deep-freeze.d.ts.map +1 -0
- package/dist/cjs/utils/deep-freeze.js +56 -0
- package/dist/cjs/utils/deep-freeze.js.map +1 -0
- package/dist/cjs/utils/validation.d.ts +15 -0
- package/dist/cjs/utils/validation.d.ts.map +1 -1
- package/dist/cjs/utils/validation.js +19 -0
- package/dist/cjs/utils/validation.js.map +1 -1
- package/dist/esm/cli/commands/db-push.d.ts +3 -0
- package/dist/esm/cli/commands/db-push.d.ts.map +1 -0
- package/dist/esm/cli/commands/db-push.js +161 -0
- package/dist/esm/cli/commands/db-push.js.map +1 -0
- package/dist/esm/cli/commands/db-seed.d.ts +3 -0
- package/dist/esm/cli/commands/db-seed.d.ts.map +1 -0
- package/dist/esm/cli/commands/db-seed.js +117 -0
- package/dist/esm/cli/commands/db-seed.js.map +1 -0
- package/dist/esm/cli/commands/generate.d.ts +3 -0
- package/dist/esm/cli/commands/generate.d.ts.map +1 -0
- package/dist/esm/cli/commands/generate.js +182 -0
- package/dist/esm/cli/commands/generate.js.map +1 -0
- package/dist/esm/cli/config.d.ts +38 -0
- package/dist/esm/cli/config.d.ts.map +1 -0
- package/dist/esm/cli/config.js +134 -0
- package/dist/esm/cli/config.js.map +1 -0
- package/dist/esm/cli/driver.d.ts +12 -0
- package/dist/esm/cli/driver.d.ts.map +1 -0
- package/dist/esm/cli/driver.js +15 -0
- package/dist/esm/cli/driver.js.map +1 -0
- package/dist/esm/cli/errors.d.ts +16 -0
- package/dist/esm/cli/errors.d.ts.map +1 -0
- package/dist/esm/cli/errors.js +61 -0
- package/dist/esm/cli/errors.js.map +1 -0
- package/dist/esm/cli/index.d.ts +8 -0
- package/dist/esm/cli/index.d.ts.map +1 -0
- package/dist/esm/cli/index.js +151 -0
- package/dist/esm/cli/index.js.map +1 -0
- package/dist/esm/cli/introspect.d.ts +8 -0
- package/dist/esm/cli/introspect.d.ts.map +1 -0
- package/dist/esm/cli/introspect.js +49 -0
- package/dist/esm/cli/introspect.js.map +1 -0
- package/dist/esm/cli/push-planner.d.ts +52 -0
- package/dist/esm/cli/push-planner.d.ts.map +1 -0
- package/dist/esm/cli/push-planner.js +111 -0
- package/dist/esm/cli/push-planner.js.map +1 -0
- package/dist/esm/cli/types.d.ts +71 -0
- package/dist/esm/cli/types.d.ts.map +1 -0
- package/dist/esm/cli/types.js +5 -0
- package/dist/esm/cli/types.js.map +1 -0
- package/dist/esm/compilers/mutation.compiler.d.ts.map +1 -1
- package/dist/esm/compilers/mutation.compiler.js +29 -26
- package/dist/esm/compilers/mutation.compiler.js.map +1 -1
- package/dist/esm/compilers/where.compiler.d.ts.map +1 -1
- package/dist/esm/compilers/where.compiler.js +53 -16
- package/dist/esm/compilers/where.compiler.js.map +1 -1
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/model.d.ts +3 -2
- package/dist/esm/model.d.ts.map +1 -1
- package/dist/esm/model.js +11 -4
- package/dist/esm/model.js.map +1 -1
- package/dist/esm/ogm.d.ts +0 -3
- package/dist/esm/ogm.d.ts.map +1 -1
- package/dist/esm/ogm.js +53 -67
- package/dist/esm/ogm.js.map +1 -1
- package/dist/esm/policy/types.d.ts +7 -0
- package/dist/esm/policy/types.d.ts.map +1 -1
- package/dist/esm/policy/types.js.map +1 -1
- package/dist/esm/schema/index-statements.d.ts +50 -0
- package/dist/esm/schema/index-statements.d.ts.map +1 -0
- package/dist/esm/schema/index-statements.js +109 -0
- package/dist/esm/schema/index-statements.js.map +1 -0
- package/dist/esm/subgraph/subgraph-operations.d.ts.map +1 -1
- package/dist/esm/subgraph/subgraph-operations.js +21 -5
- package/dist/esm/subgraph/subgraph-operations.js.map +1 -1
- package/dist/esm/utils/deep-freeze.d.ts +29 -0
- package/dist/esm/utils/deep-freeze.d.ts.map +1 -0
- package/dist/esm/utils/deep-freeze.js +53 -0
- package/dist/esm/utils/deep-freeze.js.map +1 -0
- package/dist/esm/utils/validation.d.ts +15 -0
- package/dist/esm/utils/validation.d.ts.map +1 -1
- package/dist/esm/utils/validation.js +18 -0
- package/dist/esm/utils/validation.js.map +1 -1
- package/package.json +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,105 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.0 (2026-06-13) — 🚀 the `grafeo` CLI
|
|
4
|
+
|
|
5
|
+
> **The first developer-facing command-line tool.** Until now grafeo-ogm was a library you imported; the schema-to-types step, the constraint/index provisioning, and seeding were all things you wired up by hand. `1.9.0` ships `grafeo` — a thin, lazy-loaded CLI over machinery the library already had. Three commands: `grafeo generate` (codegen, including a CI staleness gate), `grafeo db push` (declarative constraint/index sync against a live database), and `grafeo db seed` (idempotent project seeding). Config lives in a typed `grafeo.config.ts`. **The CLI adds exactly one runtime dependency (`jiti`), and importing the library doesn't pull it in.** No library API surface changed; existing consumers upgrade transparently.
|
|
6
|
+
|
|
7
|
+
### ✨ `grafeo generate` — codegen with a CI staleness gate
|
|
8
|
+
|
|
9
|
+
Wraps the existing `generateTypes()` so the type-emit step is no longer bespoke per project. Three modes:
|
|
10
|
+
|
|
11
|
+
- **One-shot** (default) — read schema, emit the TypeScript file, exit.
|
|
12
|
+
- **`--watch`** — re-emit on schema change via a debounced `fs.watch`, with a `--poll <ms>` fallback for filesystems where native watch events are unreliable (network mounts, some container bind-mounts).
|
|
13
|
+
- **`--verify`** — emit into memory and byte-compare against the on-disk file. Writes **nothing**; exits `1` on drift. This is the CI gate: a build fails if a committed types file has fallen behind its schema, the same way a `git diff --exit-code` after codegen would, but without a working-tree mutation.
|
|
14
|
+
|
|
15
|
+
### ✨ `grafeo db push` — declarative constraint & index sync
|
|
16
|
+
|
|
17
|
+
Diffs the constraints and indexes **declared in your SDL** against what's actually live in the database, then reconciles them. Declarations are derived from directives: `@id`/`@unique` → unique constraints, `@fulltext` → fulltext indexes, `@vector` → vector indexes. The live state is read via `SHOW CONSTRAINTS` / `SHOW INDEXES`.
|
|
18
|
+
|
|
19
|
+
The planner sorts everything into four buckets and prints them before touching anything:
|
|
20
|
+
|
|
21
|
+
- **create** — declared in SDL, missing in the DB.
|
|
22
|
+
- **in-sync** — declared and already present.
|
|
23
|
+
- **orphans** — managed-looking artifacts present in the DB but no longer declared in SDL (candidates for removal).
|
|
24
|
+
- **unmanaged** — DB artifacts outside grafeo's naming convention, left strictly untouched (hand-rolled DBA constraints, other tools' indexes).
|
|
25
|
+
|
|
26
|
+
`push` is **additive-by-default and idempotent** — re-running it is a no-op once in sync, and it never drops anything on the happy path. Destructive orphan drops are gated behind `--force-drop` **and** `--yes` (or an interactive confirmation); `--dry-run` prints the plan and exits without executing. Orphan detection is scoped by naming convention, so DBA-created constraints never even enter the orphan bucket — they land in *unmanaged* and are left alone.
|
|
27
|
+
|
|
28
|
+
### ✨ `grafeo db seed` — idempotent project seeding
|
|
29
|
+
|
|
30
|
+
Runs your project's seed script — resolved as config `seed` → `./seed.ts` → `./seed.js` — handing it a fully constructed, **already-connected** OGM so the script doesn't re-derive connection details. The driver is **always closed**, including when the seed script throws, so a failed seed never leaks a live connection. The docs promote `upsert` for seed data specifically because it's idempotent: re-seeding a populated database converges instead of duplicating.
|
|
31
|
+
|
|
32
|
+
### ✨ Config: `grafeo.config.ts` and connection precedence
|
|
33
|
+
|
|
34
|
+
Configuration loads from `grafeo.config.ts` / `.js` / `.json`, transpiled on the fly via `jiti` (the single new runtime dependency). A `defineConfig` export gives you full type inference on the config object. Connection settings resolve by precedence: **explicit flag > config file > `NEO4J_*` environment variables**.
|
|
35
|
+
|
|
36
|
+
One deliberate omission: **the password is never accepted as a CLI flag.** A password passed on the command line leaks into process listings (`ps`), shell history, and CI logs — so it must come from config or the environment. This is a security constraint, not an oversight.
|
|
37
|
+
|
|
38
|
+
### Lazy loading — `generate` doesn't touch the driver
|
|
39
|
+
|
|
40
|
+
Commands are loaded lazily so the cheap path stays cheap. `grafeo generate` does pure codegen and **never loads `neo4j-driver`** — only `db push` / `db seed` pull in the driver, and only when invoked. Symmetrically, importing the library from application code does **not** pull in `jiti`; the config loader lives behind the CLI entry point. No bloat leaks in either direction.
|
|
41
|
+
|
|
42
|
+
### Internal — single source of truth for constraint/index Cypher (no behavior change)
|
|
43
|
+
|
|
44
|
+
The Cypher that creates constraints and indexes was previously emitted inside `OGM.assertIndexesAndConstraints()`. `db push`'s planner needs the *same* statements to diff against the live DB, so that generation was extracted into one module — `src/schema/index-statements.ts` — now shared by both `assertIndexesAndConstraints()` and the planner. The emitted statement text and ordering are **byte-identical** to 1.8.7; this is a pure refactor with no behavioral effect on existing OGM users.
|
|
45
|
+
|
|
46
|
+
### SECURITY — orphan `DROP CONSTRAINT` re-validates the introspected name
|
|
47
|
+
|
|
48
|
+
The orphan-drop path takes a constraint *name read back from the live database* and puts it into a `DROP CONSTRAINT` statement. Constraint names are normally grafeo-generated and safe, but a live database can carry a name with unsafe characters (created by another tool, or maliciously). Before any introspected name enters Cypher, it is now re-validated through `assertSafeIdentifier`. A name that fails validation is **demoted to *unmanaged*** — it is never dropped, and the destructive path never runs against an untrusted identifier. Defense in depth: the drop path treats DB-sourced names with the same suspicion as user input.
|
|
49
|
+
|
|
50
|
+
### Minor — NLS audit metadata version bump
|
|
51
|
+
|
|
52
|
+
NLS audit metadata now emits `ogmPolicySetVersion: "1.7.0"`, dropping the stale `-beta.0` pre-release suffix it had been carrying. The policy format reached GA in 1.7.0 and is unchanged — this only corrects the reported version string. If you assert on the exact audit-metadata value, update your expectation from `1.7.0-beta.0` to `1.7.0`.
|
|
53
|
+
|
|
54
|
+
### Tests
|
|
55
|
+
|
|
56
|
+
New CLI coverage for all three commands and the config loader: `generate` one-shot/`--watch`/`--verify`-drift, the four-bucket `db push` planner with additive/idempotent/`--dry-run`/`--force-drop` paths and the unsafe-name demotion, `db seed` driver-always-closed-on-failure, and connection precedence. The `index-statements.ts` extraction is pinned by byte-identical statement assertions against the prior `assertIndexesAndConstraints()` output. Full suite green; lint, format, and type-check clean.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 1.8.7 (2026-06-12) — 🚨 SECURITY + correctness
|
|
61
|
+
|
|
62
|
+
> **Three HIGH correctness bugs, two subgraph guards, and three security hardenings** from the post-1.8.6 comprehensive audit. Two of the three HIGH bugs are "fixed one site, missed the sibling" regressions of earlier fixes (v1.8.1 and v1.8.3) — sibling-site sweeps are now part of the fix checklist. Upgrade if you use `withContext`, nested relationship updates, union relationship filters, write restrictives, or subgraph operations.
|
|
63
|
+
|
|
64
|
+
### HIGH — `OGMWithContext.model(interfaceName)` reintroduced the v1.8.3 type-lying bug
|
|
65
|
+
|
|
66
|
+
v1.8.3 made `ogm.model('SomeInterface')` throw a descriptive `OGMError`. The per-request context wrapper — the entry point every NLS consumer uses — kept the pre-1.8.3 fallthrough: an interface name returned an `InterfaceModel` cast to `any`, so the first mutation call from typed code crashed with `TypeError: this.create is not a function`. The wrapper now enforces the same contract in **both** directions: `model(interfaceName)` throws and points at `interfaceModel()`, and `interfaceModel(nodeName)` throws `Unknown interface type` instead of returning a `Model` cast to `InterfaceModel`. `interfaceModel()` owns its own construction and cache now (it previously delegated through the buggy `model()` fallthrough).
|
|
67
|
+
|
|
68
|
+
### HIGH — nested `update.<rel>[].update` WHERE compiled operator suffixes as plain equality
|
|
69
|
+
|
|
70
|
+
v1.8.1 routed nested `disconnect[].where` and `connect[].where` through the connection-aware WHERE compiler — but missed the third sibling: the **nested update** path. `where: { node: { name_CONTAINS: 'x' } }` compiled to ``u.`name_CONTAINS` = $p`` — a non-existent property lookup that matches nothing — so the nested update was a **silent no-op**. `AND`/`OR`/`NOT` wrappers became bogus property predicates the same way. Now routed through `buildConnectionWhereConditions` like its siblings. Plain-equality inputs keep byte-identical param names (`update_<rel>_<i>_where_<prop>`); `edge` filters now throw loudly instead of silently never matching.
|
|
71
|
+
|
|
72
|
+
### HIGH — union relationship `_ALL` and `_SINGLE` silently behaved as `_SOME`
|
|
73
|
+
|
|
74
|
+
For union-typed relationships, `chapters_ALL: {...}` and `chapters_SINGLE: {...}` compiled to the same OR-of-EXISTS as `_SOME` — wrong rows, no error. A node with five matching chapters passed `_SINGLE`; a node with one failing chapter passed `_ALL`. Both now implement real quantifier semantics mirroring the non-union shapes:
|
|
75
|
+
|
|
76
|
+
- `_ALL` — per mentioned member, `NOT EXISTS { MATCH … WHERE NOT (inner) }`, AND-combined. Members with an empty predicate are vacuously satisfied; unmentioned union members are unconstrained (consistent with `_SOME`).
|
|
77
|
+
- `_SINGLE` — summed `size(…)` pattern comprehensions `= 1` across mentioned members. `@cypher`-projecting members (user fields or target policies) are rejected with an `OGMError`, mirroring the non-union `_SINGLE` contract.
|
|
78
|
+
|
|
79
|
+
v1.8.5's per-member target-policy stitching is preserved in both shapes.
|
|
80
|
+
|
|
81
|
+
### MEDIUM — subgraph operations: empty filter arrays meant "delete/clone EVERYTHING reachable"
|
|
82
|
+
|
|
83
|
+
`apoc.path.subgraphAll` treats an empty `relationshipFilter`/`labelFilter` string as "no constraint", so `$deleteSubgraph(rootId, { ownedLabels: [], ownedRelationships: [], … })` DETACH-DELETEd the **entire connected component** reachable from the root. `validateConfig` now rejects empty `ownedLabels`/`ownedRelationships` before any Cypher runs (both clone and delete). Validation errors also carry the real operation and rootId — they were previously hardcoded to `'clone'` with an empty rootId, misdirecting delete-path debugging.
|
|
84
|
+
|
|
85
|
+
### SECURITY — write restrictives now require explicit positive consent
|
|
86
|
+
|
|
87
|
+
`WriteRestrictivePolicy.when` verdicts were checked with `=== false`, so `undefined`/`null`/`0`/`''` returns silently **allowed** the write. The classic foot-gun — `when: (ctx, input) => ctx.canWrite && input.tenantId === ctx.tenantId` with an anonymous ctx — returns `undefined` and passed. Both evaluation sites (create-path and update-path) now require an explicit `true`; anything else throws `PolicyDeniedError`. **Behavior change:** policies returning truthy non-`true` values (e.g. a string) now deny — return a real boolean.
|
|
88
|
+
|
|
89
|
+
### SECURITY — `withContext` ctx is now a deep-frozen snapshot
|
|
90
|
+
|
|
91
|
+
`Object.freeze({ ...ctx })` was shallow: a policy callback could mutate nested ctx state (`ctx.user.roles.push('admin')`) and every subsequent policy decision in the same request — including nested-selection target policies — saw the escalated context. ctx is now a **deep-frozen clone** (`deepFreezeSnapshot`): plain objects/arrays are cloned recursively and frozen; class instances, Dates, Maps, and functions are kept by reference and left unfrozen. The caller's original objects stay mutable. **Behavior change:** policies must not rely on reference identity between nested ctx objects and external state (top-level identity was already broken by the spread).
|
|
92
|
+
|
|
93
|
+
### SECURITY — mutation property names now reject `__proto__`/`constructor`/`prototype`
|
|
94
|
+
|
|
95
|
+
The WHERE compiler has always blocked prototype-pollution names via `assertSafeKey`; the mutation compiler validated identifier shape only, so `model.create({ input: JSON.parse('{"__proto__": "evil"}') })` persisted a literal `__proto__` property to Neo4j. Reads through the OGM were safe (`Object.create(null)` in ResultMapper), but downstream consumers doing `Object.assign({}, node)` would re-trigger setter semantics. All mutation property-name sites (create/update/createMany/merge keys/edge properties/nested updates/mutation WHEREs) now route through a combined `assertSafePropertyName` guard. **Behavior change:** schemas with properties literally named `constructor`/`prototype` can no longer write them via the OGM — they already couldn't query them.
|
|
96
|
+
|
|
97
|
+
### Tests
|
|
98
|
+
|
|
99
|
+
24 new regression tests (union quantifiers, nested-update WHERE shapes, wrapper contract both directions, deep-freeze unit + integration, verdict strictness, subgraph guards, `__proto__` rejection). Full suite: 1414 tests, 63 suites, all green. Lint and type-check clean.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
3
103
|
## 1.8.6 (2026-05-07)
|
|
4
104
|
|
|
5
105
|
> **DX fix.** Re-export `CypherAssert`, `Neo4jRecordFactory`, and `SelectionSetFactory` from the main entry. Consumers on legacy module resolvers (TypeScript `moduleResolution: "node"`, older Jest configs without subpath-imports support) can now do `import { CypherAssert } from 'grafeo-ogm'` without needing a `.d.ts` shim or a `moduleNameMapper` workaround.
|
package/README.md
CHANGED
|
@@ -21,6 +21,7 @@ Define your graph model once in `.graphql` and get fully typed CRUD operations,
|
|
|
21
21
|
- [Why grafeo-ogm?](#why-grafeo-ogm)
|
|
22
22
|
- [Common Use Cases](#common-use-cases)
|
|
23
23
|
- [Quick Start](#quick-start)
|
|
24
|
+
- [CLI](#cli)
|
|
24
25
|
- [Features Overview](#features-overview)
|
|
25
26
|
- [Schema Definition](#schema-definition)
|
|
26
27
|
- [Query API](#query-api)
|
|
@@ -151,7 +152,15 @@ type Category @node {
|
|
|
151
152
|
}
|
|
152
153
|
```
|
|
153
154
|
|
|
154
|
-
### 2.
|
|
155
|
+
### 2. Generate types (recommended)
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
npx grafeo generate
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
This wraps `generateTypes()` to emit typed models from your SDL — wire them in with `new OGM<ModelMap>(...)` for full inference. See the [CLI](#cli) and [Type Generation](#type-generation) sections. You can skip this and run the OGM untyped, but you forfeit compile-time safety.
|
|
162
|
+
|
|
163
|
+
### 3. Initialize the OGM
|
|
155
164
|
|
|
156
165
|
```typescript
|
|
157
166
|
import { OGM } from 'grafeo-ogm';
|
|
@@ -167,7 +176,7 @@ const typeDefs = fs.readFileSync('./schema.graphql', 'utf-8');
|
|
|
167
176
|
const ogm = new OGM({ typeDefs, driver });
|
|
168
177
|
```
|
|
169
178
|
|
|
170
|
-
###
|
|
179
|
+
### 4. Basic CRUD
|
|
171
180
|
|
|
172
181
|
```typescript
|
|
173
182
|
const Book = ogm.model('Book');
|
|
@@ -201,7 +210,7 @@ await Book.update({
|
|
|
201
210
|
await Book.delete({ where: { isbn: '978-1491930892' } });
|
|
202
211
|
```
|
|
203
212
|
|
|
204
|
-
###
|
|
213
|
+
### 5. Transactions
|
|
205
214
|
|
|
206
215
|
```typescript
|
|
207
216
|
await ogm.$transaction(async (ctx) => {
|
|
@@ -213,21 +222,118 @@ await ogm.$transaction(async (ctx) => {
|
|
|
213
222
|
|
|
214
223
|
---
|
|
215
224
|
|
|
216
|
-
##
|
|
225
|
+
## CLI
|
|
226
|
+
|
|
227
|
+
grafeo-ogm ships a `grafeo` binary (installed with the package — no global install needed). Run it with `npx grafeo <command>`. The CLI is plumbing over the same library machinery you can call programmatically: `generate` wraps `generateTypes()`, and `db push` reuses the constraint/index logic behind `assertIndexesAndConstraints()`.
|
|
228
|
+
|
|
229
|
+
| Command | Purpose |
|
|
230
|
+
|---|---|
|
|
231
|
+
| `grafeo generate` | Generate TypeScript types from your SDL — one-shot, `--watch`, or `--verify` CI gate |
|
|
232
|
+
| `grafeo db push` | Diff SDL-declared constraints/indexes against the live database and apply them |
|
|
233
|
+
| `grafeo db seed` | Run your seed script with a constructed, connected OGM |
|
|
234
|
+
|
|
235
|
+
### Configuration
|
|
236
|
+
|
|
237
|
+
All commands read `grafeo.config.ts` (or `.js` / `.json`) from the working directory — first found wins. Command-line flags override config values. `defineConfig` gives full type inference:
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
// grafeo.config.ts
|
|
241
|
+
import { defineConfig } from 'grafeo-ogm';
|
|
242
|
+
|
|
243
|
+
export default defineConfig({
|
|
244
|
+
schema: './schema.graphql', // default: ./schema.graphql
|
|
245
|
+
out: './src/generated/ogm-types.ts', // default: ./grafeo.generated.ts
|
|
246
|
+
// generate: forwarded to generateTypes(). Set packageName ONLY if you
|
|
247
|
+
// installed grafeo-ogm under an npm alias, e.g. { packageName: '@myorg/ogm' }
|
|
248
|
+
database: {
|
|
249
|
+
uri: process.env.NEO4J_URI,
|
|
250
|
+
username: process.env.NEO4J_USERNAME,
|
|
251
|
+
// password: from config or NEO4J_PASSWORD — never a CLI flag (see below)
|
|
252
|
+
},
|
|
253
|
+
seed: './scripts/seed.ts',
|
|
254
|
+
vectorIndexes: {
|
|
255
|
+
// @vector SDL carries no dimensions; db push reads them here, keyed by index name
|
|
256
|
+
BookEmbedding: { dimensions: 1536, similarity: 'cosine' },
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Database connection** resolves per setting in precedence order: CLI flag (`--uri`, `--username`, `--database`) > config `database` block > environment (`NEO4J_URI`, `NEO4J_USERNAME`, `NEO4J_PASSWORD`, `NEO4J_DATABASE`). The **password is never accepted as a CLI flag** — argv leaks through process listings and shell history, so it must come from `NEO4J_PASSWORD` or the config file.
|
|
262
|
+
|
|
263
|
+
> **Config and seed files run as trusted code.** `grafeo.config.ts` and your seed script are executed in-process (via [jiti](https://github.com/unjs/jiti)) with your database credentials — the same trust model as Prisma/Drizzle. Only run `grafeo` in a directory whose config and seed files you control.
|
|
264
|
+
|
|
265
|
+
### `grafeo generate`
|
|
266
|
+
|
|
267
|
+
Compiles your SDL to TypeScript types (wraps `generateTypes()` — see [Type Generation](#type-generation) for the programmatic API and the full option list).
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
npx grafeo generate # one-shot
|
|
271
|
+
npx grafeo generate --watch # regenerate on schema change
|
|
272
|
+
npx grafeo generate --verify # CI gate: exit 1 if the file on disk is stale
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
| Flag | Description |
|
|
276
|
+
|---|---|
|
|
277
|
+
| `--schema <path>` | SDL path (overrides config; default `./schema.graphql`) |
|
|
278
|
+
| `--out <path>` | Output path (default `./grafeo.generated.ts`) |
|
|
279
|
+
| `--watch` | Debounced regeneration on change; a failed parse prints the error and keeps watching |
|
|
280
|
+
| `--verify` | Generate in memory, byte-compare against the file on disk, write nothing, exit 1 on drift |
|
|
281
|
+
| `--poll <ms>` | Use polling instead of `fs.watch` (fallback where native watching misfires) |
|
|
282
|
+
|
|
283
|
+
`--verify` is the CI staleness gate: commit your generated types, then run `grafeo generate --verify` in CI to fail the build if the schema changed without a regeneration.
|
|
284
|
+
|
|
285
|
+
### `grafeo db push`
|
|
286
|
+
|
|
287
|
+
Synchronizes the **constraints and indexes** your SDL declares (`@id`/`@unique` → unique constraints, `@fulltext` → fulltext indexes, `@vector` → vector indexes) with the live database. Constraints and indexes only — **not data**. (`db push` is deliberately scoped; use a dedicated migration tool for data.)
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
npx grafeo db push --dry-run # preview the plan — DO THIS FIRST
|
|
291
|
+
npx grafeo db push # apply (additive: creates only what's missing)
|
|
292
|
+
npx grafeo db push --force-drop --yes # also drop orphaned grafeo-managed items
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
`db push` introspects `SHOW CONSTRAINTS` / `SHOW INDEXES`, diffs against your SDL, and prints a plan in four buckets:
|
|
217
296
|
|
|
218
|
-
|
|
297
|
+
- **create** — declared in SDL, missing in the database
|
|
298
|
+
- **in sync** — already present
|
|
299
|
+
- **orphans** — grafeo-named (`{Label}_{prop}_unique`) constraints no longer in your SDL; **reported and kept** unless you pass `--force-drop`
|
|
300
|
+
- **unmanaged** — constraints/indexes that don't follow grafeo's naming convention (hand-created DBA indexes, etc.); **never touched, never counted as drift**
|
|
219
301
|
|
|
220
|
-
|
|
302
|
+
It is **additive by default** and **idempotent** — a second run with no schema change is a no-op. Dropping orphans is destructive and gated: `--force-drop` lists the exact drops (shown in `--dry-run` too) and requires interactive confirmation, or `--yes` in non-interactive sessions like CI.
|
|
221
303
|
|
|
222
|
-
|
|
304
|
+
| Flag | Description |
|
|
305
|
+
|---|---|
|
|
306
|
+
| `--dry-run` | Print the full plan (including the exact Cypher, drops included) and exit without writing |
|
|
307
|
+
| `--force-drop` | Permit dropping orphaned grafeo-managed constraints/indexes |
|
|
308
|
+
| `--yes` | Confirm destructive drops non-interactively (required with `--force-drop` in CI) |
|
|
309
|
+
| `--schema <path>`, `--uri`, `--username`, `--database` | Schema path and connection overrides |
|
|
310
|
+
|
|
311
|
+
> **Vector indexes** declare no dimensions in the SDL (the `@vector` directive only names the index and its embedding property), but Neo4j requires them at creation time. Supply them in `vectorIndexes` (keyed by index name) in your config. A `@vector` index without configured dimensions is reported and skipped — never silently created or dropped.
|
|
312
|
+
|
|
313
|
+
### `grafeo db seed`
|
|
314
|
+
|
|
315
|
+
Runs your seed script with a constructed, connected `OGM`. It resolves the entry point from config `seed`, then `./seed.ts`, then `./seed.js`. The module's default export receives the OGM; the CLI awaits it and **always closes the driver**, even if the seed throws.
|
|
316
|
+
|
|
317
|
+
```typescript
|
|
318
|
+
// seed.ts
|
|
319
|
+
import type { OGM } from 'grafeo-ogm';
|
|
320
|
+
|
|
321
|
+
export default async function seed(ogm: OGM) {
|
|
322
|
+
const Book = ogm.model('Book');
|
|
323
|
+
// Prefer upsert over create so repeated seeds converge instead of duplicating data.
|
|
324
|
+
await Book.upsert({
|
|
325
|
+
where: { id: '1' },
|
|
326
|
+
create: { id: '1', title: 'Dune' },
|
|
327
|
+
update: { title: 'Dune' },
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
```
|
|
223
331
|
|
|
224
332
|
```bash
|
|
225
|
-
|
|
226
|
-
# or
|
|
227
|
-
pnpm add grafeo-ogm@beta
|
|
333
|
+
npx grafeo db seed
|
|
228
334
|
```
|
|
229
335
|
|
|
230
|
-
|
|
336
|
+
grafeo does not enforce idempotency — use `upsert` so re-running the seed is safe. Connection and schema resolve exactly as for `db push`.
|
|
231
337
|
|
|
232
338
|
---
|
|
233
339
|
|
|
@@ -834,7 +940,7 @@ const deleteResult = await deleteSubgraph(
|
|
|
834
940
|
|
|
835
941
|
### Computed Fields with @cypher
|
|
836
942
|
|
|
837
|
-
The `@cypher` directive declares a field whose value is produced by a custom Cypher statement at query time.
|
|
943
|
+
The `@cypher` directive declares a field whose value is produced by a custom Cypher statement at query time. Scalar `@cypher` fields are resolved in three scopes — `select` / `selectionSet`, `where`, and `options.sort` — so you can project, filter, and order by them just like stored properties.
|
|
838
944
|
|
|
839
945
|
#### Declaration
|
|
840
946
|
|
|
@@ -938,11 +1044,9 @@ These are not OGM bugs — they are Cypher-language constraints. Pattern compreh
|
|
|
938
1044
|
|
|
939
1045
|
Each scope (`where`, `select`, sort) emits its own `CALL { WITH n; WITH n AS this; <statement> }` block per `@cypher` field reference. A query that references the same `@cypher` field in all three scopes emits three `CALL` blocks. This is functionally correct and parameter-isolated, but worth knowing if your statement is expensive (multi-hop traversals, aggregations over large fan-outs). For frequently-accessed computed values, consider materializing as a stored property updated on write instead.
|
|
940
1046
|
|
|
941
|
-
### Node-Level Security
|
|
942
|
-
|
|
943
|
-
> **Beta — install with `npm install grafeo-ogm@beta`.**
|
|
1047
|
+
### Node-Level Security
|
|
944
1048
|
|
|
945
|
-
Node-Level Security (NLS) is a per-request filter layer that compiles into the existing WHERE pipeline. Policies return `<Node>Where` partials, so every operator, quantifier, connection filter, and nested traversal already supported by `WhereCompiler` is automatically available inside policies — no new DSL.
|
|
1049
|
+
Node-Level Security (NLS) is a per-request filter layer that compiles into the existing WHERE pipeline. It is opt-in: an OGM constructed without the `policies` option emits byte-identical Cypher to a no-policy OGM. Policies return `<Node>Where` partials, so every operator, quantifier, connection filter, and nested traversal already supported by `WhereCompiler` is automatically available inside policies — no new DSL.
|
|
946
1050
|
|
|
947
1051
|
#### Declaration
|
|
948
1052
|
|
|
@@ -1083,7 +1187,7 @@ When `policies` is configured, every OGM-emitted query attaches transaction meta
|
|
|
1083
1187
|
|
|
1084
1188
|
```typescript
|
|
1085
1189
|
{
|
|
1086
|
-
ogmPolicySetVersion: '1.7.0
|
|
1190
|
+
ogmPolicySetVersion: '1.7.0',
|
|
1087
1191
|
ctxFingerprint: '<sha256 of sorted ctx KEYS — never values>',
|
|
1088
1192
|
modelType: 'Book',
|
|
1089
1193
|
operation: 'read',
|
|
@@ -1094,14 +1198,13 @@ When `policies` is configured, every OGM-emitted query attaches transaction meta
|
|
|
1094
1198
|
|
|
1095
1199
|
Disable via `policyDefaults: { auditMetadata: false }`. The fingerprint is intentionally key-only — no ctx values are leaked.
|
|
1096
1200
|
|
|
1097
|
-
####
|
|
1201
|
+
#### Limitations
|
|
1098
1202
|
|
|
1099
1203
|
- **`@cypher` scalar fields inside a policy `where`-partial throw when the policy is injected into nested-selection enforcement.** Refactor the policy to use stored properties or a relationship traversal.
|
|
1100
|
-
- **`upsert`** evaluates create- and update-side policies at the application layer (MERGE has no WHERE). Documented limit; full MERGE-aware enforcement is
|
|
1101
|
-
- **Restrictives are split into read-side and write-side flavors.** A restrictive's `operations` array determines which `when` signature applies. Read-side ops (`read|delete|aggregate|count`) → `when(ctx)` returns a where-partial or boolean. Write-side ops (`create|update`) → `when(ctx, input)` returns a boolean only. Mixed arrays (e.g. `['read', 'create']`) are rejected at construction time — split into two restrictives. Each flavor is invoked exactly once per query (read-side at compile, write-side at the application layer)
|
|
1204
|
+
- **`upsert`** evaluates create- and update-side policies at the application layer (MERGE has no WHERE). Documented limit; full MERGE-aware enforcement is not yet implemented.
|
|
1205
|
+
- **Restrictives are split into read-side and write-side flavors.** A restrictive's `operations` array determines which `when` signature applies. Read-side ops (`read|delete|aggregate|count`) → `when(ctx)` returns a where-partial or boolean. Write-side ops (`create|update`) → `when(ctx, input)` returns a boolean only. Mixed arrays (e.g. `['read', 'create']`) are rejected at construction time — split into two restrictives. Each flavor is invoked exactly once per query (read-side at compile, write-side at the application layer). Use `isReadRestrictive` / `isWriteRestrictive` if you need to inspect a policy at runtime.
|
|
1102
1206
|
- **InterfaceModel CASE-per-label fallback.** Implementers without a registered policy fall back to interface-level enforcement on their branch. The OGM emits a `logger.warn` at construction time when an interface has policies and one of its implementers does not — silence the warning by registering an explicit policy on each implementer.
|
|
1103
|
-
- **AsyncLocalStorage
|
|
1104
|
-
- **Live Neo4j integration tests are not part of the beta.** Mock-driver coverage is extensive but a real-DB suite is a `1.7.0` final blocker.
|
|
1207
|
+
- **No AsyncLocalStorage integration.** Context is supplied via explicit `withContext()` only — create one wrapper per request, discard after.
|
|
1105
1208
|
|
|
1106
1209
|
### Raw Cypher
|
|
1107
1210
|
|
|
@@ -1221,7 +1324,9 @@ type Article @node @vector(indexes: [
|
|
|
1221
1324
|
|
|
1222
1325
|
### Creating the index in Neo4j
|
|
1223
1326
|
|
|
1224
|
-
grafeo
|
|
1327
|
+
The `grafeo db push` CLI creates `@vector` indexes for you — the directive carries no dimensions, so supply them in your config's `vectorIndexes` map (keyed by index name) and `db push` issues the `CREATE VECTOR INDEX`. See the [CLI → `grafeo db push`](#grafeo-db-push) section. The runtime `assertIndexesAndConstraints()` does **not** create vector indexes (only fulltext indexes and unique constraints).
|
|
1328
|
+
|
|
1329
|
+
You can also run the Cypher yourself as part of a migration:
|
|
1225
1330
|
|
|
1226
1331
|
```cypher
|
|
1227
1332
|
CREATE VECTOR INDEX article_content_idx FOR (n:Article) ON n.embedding
|
|
@@ -1323,7 +1428,7 @@ The following were intentionally left out of this release:
|
|
|
1323
1428
|
- **`@embedded(from:, using:)` auto-write directive** — automatic embedding on create / update. Requires a pluggable embedder port and peer-dependency adapters.
|
|
1324
1429
|
- **Third-party embedder SDKs** (`grafeo-ogm/embedders/openai`, etc.) — tracked for a future release with optional peer dependencies.
|
|
1325
1430
|
- **Relationship-level `@vector` indexes** — the current Neo4j / official spec only supports node vector indexes.
|
|
1326
|
-
- **
|
|
1431
|
+
- **Vector index creation via the runtime `assertIndexesAndConstraints`** — that method covers fulltext indexes and unique constraints only. Create vector indexes with the `grafeo db push` CLI (see [CLI](#cli)) or run `CREATE VECTOR INDEX` yourself.
|
|
1327
1432
|
|
|
1328
1433
|
---
|
|
1329
1434
|
|
|
@@ -1588,7 +1693,7 @@ Pure query builders (e.g. `cypher-query-builder`) help you compose Cypher fragme
|
|
|
1588
1693
|
|
|
1589
1694
|
### Is grafeo-ogm production-ready?
|
|
1590
1695
|
|
|
1591
|
-
Yes. The library has 1,
|
|
1696
|
+
Yes. The library has over 1,400 unit tests covering compilers, mutations, fulltext, transactions, security, scalar type mapping, the CLI, and edge cases. It targets Neo4j 5.x and follows semver — see [CHANGELOG.md](CHANGELOG.md).
|
|
1592
1697
|
|
|
1593
1698
|
### Does grafeo-ogm work with Neo4j Aura?
|
|
1594
1699
|
|
|
@@ -1620,7 +1725,7 @@ Negligible. grafeo-ogm compiles each query once per shape (selection sets are ca
|
|
|
1620
1725
|
|
|
1621
1726
|
### Is there a CLI / migration tool / studio?
|
|
1622
1727
|
|
|
1623
|
-
|
|
1728
|
+
There is a CLI — the `grafeo` binary ships with the package (`grafeo generate`, `grafeo db push`, `grafeo db seed`). See the [CLI](#cli) section. There is no studio. Data migrations are intentionally out of scope: `db push` syncs SDL-declared constraints and indexes only, never data. Neo4j's labels and properties are flexible enough that you typically migrate data via Cypher scripts (`MATCH (n:OldLabel) SET n:NewLabel REMOVE n:OldLabel`) which you can run via `ogm.$executeRaw(...)`.
|
|
1624
1729
|
|
|
1625
1730
|
### How do I report a bug or request a feature?
|
|
1626
1731
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-push.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/db-push.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AA+ItC,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAgE1E"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runDbPush = runDbPush;
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const node_util_1 = require("node:util");
|
|
39
|
+
const parser_1 = require("../../schema/parser");
|
|
40
|
+
const config_1 = require("../config");
|
|
41
|
+
const driver_1 = require("../driver");
|
|
42
|
+
const errors_1 = require("../errors");
|
|
43
|
+
const introspect_1 = require("../introspect");
|
|
44
|
+
const push_planner_1 = require("../push-planner");
|
|
45
|
+
function parseDbPushFlags(argv) {
|
|
46
|
+
try {
|
|
47
|
+
const { values } = (0, node_util_1.parseArgs)({
|
|
48
|
+
args: argv,
|
|
49
|
+
options: {
|
|
50
|
+
schema: { type: 'string' },
|
|
51
|
+
uri: { type: 'string' },
|
|
52
|
+
username: { type: 'string' },
|
|
53
|
+
database: { type: 'string' },
|
|
54
|
+
'dry-run': { type: 'boolean' },
|
|
55
|
+
'force-drop': { type: 'boolean' },
|
|
56
|
+
yes: { type: 'boolean' },
|
|
57
|
+
debug: { type: 'boolean' },
|
|
58
|
+
},
|
|
59
|
+
strict: true,
|
|
60
|
+
allowPositionals: false,
|
|
61
|
+
});
|
|
62
|
+
return values;
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
66
|
+
throw new errors_1.CliError(`db push: ${message}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Print the FULL diff — including the destructive part. Under `--force-drop`
|
|
71
|
+
* the orphan section shows the exact drop Cypher; otherwise it reports the
|
|
72
|
+
* orphans as kept. Rendering the drops here (not in the confirmation step) is
|
|
73
|
+
* what makes `--dry-run --force-drop` preview the drops instead of hiding
|
|
74
|
+
* them — dry-run is exactly when you need to see what would be removed.
|
|
75
|
+
*/
|
|
76
|
+
function renderPlan(plan, io, willDrop) {
|
|
77
|
+
if (plan.create.length === 0)
|
|
78
|
+
if (plan.orphans.length === 0)
|
|
79
|
+
// Only claim a clean "in sync" when there is genuinely nothing to do —
|
|
80
|
+
// outstanding orphans mean the schema is not in sync.
|
|
81
|
+
io.out('✓ No changes — constraints and indexes are in sync');
|
|
82
|
+
else
|
|
83
|
+
io.out('No constraints or indexes to create.');
|
|
84
|
+
else {
|
|
85
|
+
io.out(`Plan — ${plan.create.length} to create, ${plan.inSync.length} in sync:`);
|
|
86
|
+
for (const statement of plan.create) {
|
|
87
|
+
io.out(` + ${statement.name}`);
|
|
88
|
+
io.out(` ${statement.cypher}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (plan.orphans.length > 0)
|
|
92
|
+
if (willDrop) {
|
|
93
|
+
// "eligible to drop" — this is the plan; consent is resolved afterward.
|
|
94
|
+
io.out(`Orphans — ${plan.orphans.length} eligible to drop (--force-drop):`);
|
|
95
|
+
for (const orphan of plan.orphans)
|
|
96
|
+
io.out(` - ${orphan.dropCypher}`);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
io.out(`Orphans — ${plan.orphans.length} kept (use --force-drop to remove):`);
|
|
100
|
+
for (const orphan of plan.orphans)
|
|
101
|
+
io.out(` - ${orphan.name}`);
|
|
102
|
+
}
|
|
103
|
+
if (plan.unmanaged.length > 0)
|
|
104
|
+
io.out(`Unmanaged — ${plan.unmanaged.length} ignored: ${plan.unmanaged.join(', ')}`);
|
|
105
|
+
}
|
|
106
|
+
async function resolveDropConsent(plan, io, yes) {
|
|
107
|
+
if (yes)
|
|
108
|
+
return 'granted';
|
|
109
|
+
if (io.interactive && io.confirm)
|
|
110
|
+
// Strict `=== true`: only an explicit boolean true consents, so a
|
|
111
|
+
// misbehaving embedder returning a truthy non-boolean can't authorize a drop.
|
|
112
|
+
return (await io.confirm(`Drop the ${plan.orphans.length} item(s) listed above?`)) === true
|
|
113
|
+
? 'granted'
|
|
114
|
+
: 'declined';
|
|
115
|
+
return 'blocked';
|
|
116
|
+
}
|
|
117
|
+
async function applyCreates(driver, database, plan, io) {
|
|
118
|
+
if (plan.create.length === 0)
|
|
119
|
+
return;
|
|
120
|
+
const session = driver.session(database ? { database } : undefined);
|
|
121
|
+
try {
|
|
122
|
+
for (const statement of plan.create) {
|
|
123
|
+
await session.run(statement.cypher);
|
|
124
|
+
io.out(`✓ created ${statement.name}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
await session.close();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async function applyDrops(driver, database, plan, io) {
|
|
132
|
+
if (plan.orphans.length === 0)
|
|
133
|
+
return;
|
|
134
|
+
const session = driver.session(database ? { database } : undefined);
|
|
135
|
+
try {
|
|
136
|
+
for (const orphan of plan.orphans) {
|
|
137
|
+
await session.run(orphan.dropCypher);
|
|
138
|
+
io.out(`✓ dropped ${orphan.name}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
await session.close();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function runDbPush(argv, io) {
|
|
146
|
+
const flags = parseDbPushFlags(argv);
|
|
147
|
+
const { config } = await (0, config_1.loadConfigFile)(io.cwd);
|
|
148
|
+
const schemaPath = (0, config_1.resolveSchemaPath)(flags, config, io.cwd);
|
|
149
|
+
const typeDefs = fs.readFileSync(schemaPath, 'utf-8');
|
|
150
|
+
const schema = (0, parser_1.parseSchema)(typeDefs);
|
|
151
|
+
const connection = (0, config_1.resolveConnection)(flags, config, io.env);
|
|
152
|
+
// GrafeoVectorIndexConfig is structurally a VectorIndexCreationConfig.
|
|
153
|
+
const vectorConfig = config.vectorIndexes;
|
|
154
|
+
const dryRun = flags['dry-run'] === true;
|
|
155
|
+
const forceDrop = flags['force-drop'] === true;
|
|
156
|
+
const driver = (0, driver_1.createDriver)(io, connection);
|
|
157
|
+
try {
|
|
158
|
+
const live = await (0, introspect_1.fetchLiveSchema)(driver, connection.database);
|
|
159
|
+
const plan = (0, push_planner_1.planSchemaSync)(schema, live, vectorConfig);
|
|
160
|
+
const willDrop = forceDrop && plan.orphans.length > 0;
|
|
161
|
+
renderPlan(plan, io, willDrop);
|
|
162
|
+
// `@vector` indexes without dimensions are reported, never silently
|
|
163
|
+
// skipped, and never block the rest of the plan.
|
|
164
|
+
for (const needs of plan.needsConfig)
|
|
165
|
+
io.err(`warning: vector index "${needs.indexName}" ` +
|
|
166
|
+
`(${needs.typeName}.${needs.embeddingProperty}) skipped — set ` +
|
|
167
|
+
`vectorIndexes['${needs.indexName}'].dimensions in grafeo.config.ts to create it`);
|
|
168
|
+
// Nothing to create and nothing to drop → no writes (idempotent re-run).
|
|
169
|
+
if (plan.create.length === 0 && !willDrop)
|
|
170
|
+
return 0;
|
|
171
|
+
if (dryRun) {
|
|
172
|
+
io.out('');
|
|
173
|
+
io.out('Dry run — no changes applied.');
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
// Resolve drop consent FIRST, so a hard block (non-interactive, no --yes)
|
|
177
|
+
// aborts cleanly without having applied anything. An interactive decline,
|
|
178
|
+
// by contrast, still lets the additive creates proceed.
|
|
179
|
+
let consent;
|
|
180
|
+
if (willDrop) {
|
|
181
|
+
consent = await resolveDropConsent(plan, io, flags.yes === true);
|
|
182
|
+
if (consent === 'blocked')
|
|
183
|
+
throw new errors_1.CliError('Destructive changes (--force-drop) require --yes in a non-interactive session.');
|
|
184
|
+
}
|
|
185
|
+
// Additive creates are never gated.
|
|
186
|
+
await applyCreates(driver, connection.database, plan, io);
|
|
187
|
+
if (consent === 'granted')
|
|
188
|
+
await applyDrops(driver, connection.database, plan, io);
|
|
189
|
+
else if (consent === 'declined')
|
|
190
|
+
io.out('Drop declined — orphans kept.');
|
|
191
|
+
return 0;
|
|
192
|
+
}
|
|
193
|
+
finally {
|
|
194
|
+
await driver.close();
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=db-push.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-push.js","sourceRoot":"","sources":["../../../../src/cli/commands/db-push.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"db-seed.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/db-seed.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,KAAK,EAAgB,MAAM,UAAU,CAAC;AAwGpD,wBAAsB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CA+B1E"}
|