@loradb/lora-graphql 0.25.0 → 0.25.1
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 +89 -58
- package/dist/cli.js +2 -2
- package/dist/{diff-CdZZG-N1.js → diff-BZZNZs76.js} +2 -2
- package/dist/{diff-CdZZG-N1.js.map → diff-BZZNZs76.js.map} +1 -1
- package/dist/execute/changes.d.ts +8 -0
- package/dist/execute/cypher-mutation.d.ts +5 -1
- package/dist/execute/mutate/reported.d.ts +26 -0
- package/dist/execute/mutate/runner.d.ts +6 -0
- package/dist/execute/mutate/triggers.d.ts +8 -2
- package/dist/index.js +2 -2
- package/dist/{lora-graphql-D11QFUkf.js → lora-graphql-RvZQdHNA.js} +6959 -6770
- package/dist/lora-graphql-RvZQdHNA.js.map +1 -0
- package/dist/model/build/trigger.d.ts +7 -0
- package/dist/model/directives.d.ts +1 -1
- package/dist/model/types.d.ts +22 -1
- package/dist/testing.js +1 -1
- package/package.json +2 -2
- package/dist/lora-graphql-D11QFUkf.js.map +0 -1
package/README.md
CHANGED
|
@@ -156,26 +156,26 @@ Model:
|
|
|
156
156
|
|
|
157
157
|
API:
|
|
158
158
|
|
|
159
|
-
| Directive | On
|
|
160
|
-
| ----------------------------------------------------- |
|
|
161
|
-
| `@query(read:, aggregate:)` | type, interface, union
|
|
162
|
-
| `@mutation(operations: [CREATE, UPDATE, DELETE])` | type
|
|
163
|
-
| `@subscription(operations: [CREATE, UPDATE, DELETE])` | type
|
|
164
|
-
| `@filterable(byValue: [...])` | field
|
|
165
|
-
| `@sortable` | field
|
|
166
|
-
| `@groupBy` | field
|
|
167
|
-
| `@limit(default:, max:)` | type, interface, union, list relationship
|
|
168
|
-
| `@length(min:, max:)` | String / ID field
|
|
169
|
-
| `@pattern(regex:)` | String / ID field
|
|
170
|
-
| `@format(as:)` | String / ID field
|
|
171
|
-
| `@range(min:, max:, exclusiveMin:, exclusiveMax:)` | number field; argument of a `@cypher` field
|
|
172
|
-
| `@multipleOf(value:)` | Int / Float field
|
|
173
|
-
| `@size(min:, max:, unique:)` | list field; list argument of `@cypher
|
|
174
|
-
| `@relayId` | `@key` field
|
|
175
|
-
| `@authentication(operations:, jwt:)` | type, field
|
|
176
|
-
| `@authorization(filter:, validate:)` | type (filter and validate), field (validate)
|
|
177
|
-
| `@jwt`, `@jwtClaim(path:)` | type, field
|
|
178
|
-
| `@viewer(type:, field:)` | `@jwt` claim
|
|
159
|
+
| Directive | On | Meaning |
|
|
160
|
+
| ----------------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
161
|
+
| `@query(read:, aggregate:)` | type, interface, union | Generated reads |
|
|
162
|
+
| `@mutation(operations: [CREATE, UPDATE, DELETE])` | type | Generated mutations; none without it |
|
|
163
|
+
| `@subscription(operations: [CREATE, UPDATE, DELETE])` | type | Generated subscriptions; none without it |
|
|
164
|
+
| `@filterable(byValue: [...])` | field | Filter operators. Bare: `EQ` and `IN` (lists: `INCLUDES`). On a relationship: enables relationship filters |
|
|
165
|
+
| `@sortable` | field | Sort and paginate by this field (on a relationship property: `sort: [{ edge: { ... } }]`) |
|
|
166
|
+
| `@groupBy` | field | A grouping key of `<plural>Grouped(by:)` (needs `@query(aggregate: true)`) |
|
|
167
|
+
| `@limit(default:, max:)` | type, interface, union, list relationship | Page size bounds |
|
|
168
|
+
| `@length(min:, max:)` | String / ID field | Its length in code points (each item of a list) |
|
|
169
|
+
| `@pattern(regex:)` | String / ID field | An expression the whole value must match; needs `@length(max:)` |
|
|
170
|
+
| `@format(as:)` | String / ID field | `email`, `email-strict`, `url`, `uuid`, `hostname`, `ipv4`, `ipv6`, `slug`, `hex-color`, `ulid`, `e164` |
|
|
171
|
+
| `@range(min:, max:, exclusiveMin:, exclusiveMax:)` | number field; argument of a `@cypher` field | Bounds of its value (each item of a list); on an argument, `min` and `max` |
|
|
172
|
+
| `@multipleOf(value:)` | Int / Float field | A whole multiple of `value` |
|
|
173
|
+
| `@size(min:, max:, unique:)` | list field; list argument of `@cypher`; list relationship | How many items it holds; on an argument, `max` (default `maxListArgument`); on a relationship, `max` items per nested `connect`, `create`, `disconnect` and `update` list |
|
|
174
|
+
| `@relayId` | `@key` field | Adds a global `id` and the `Node` interface |
|
|
175
|
+
| `@authentication(operations:, jwt:)` | type, field | Needs an authenticated request, whose claims satisfy `jwt` |
|
|
176
|
+
| `@authorization(filter:, validate:)` | type (filter and validate), field (validate) | Row-level rules, compiled into statements |
|
|
177
|
+
| `@jwt`, `@jwtClaim(path:)` | type, field | The claims shape; rules may only use declared claims |
|
|
178
|
+
| `@viewer(type:, field:)` | `@jwt` claim | The claim naming the caller's node (by a `@key` or `@unique` field): enables `isViewer` and `viewer` in rules |
|
|
179
179
|
|
|
180
180
|
`directiveTypeDefs` (or `lora-graphql directives`) prints these as SDL for
|
|
181
181
|
editors and codegen.
|
|
@@ -207,7 +207,8 @@ Email @storedAs(type: STRING) @format(as: "email")`), for every field of
|
|
|
207
207
|
that type. For a rule across fields, the `validators` option takes a
|
|
208
208
|
Standard Schema validator per node type (zod, valibot, arktype, yup),
|
|
209
209
|
run on each input after the field rules. The same directives go on
|
|
210
|
-
`@cypher` arguments
|
|
210
|
+
`@cypher` arguments, `@normalize` included: it changes the argument
|
|
211
|
+
before its rules are checked and before the statement sees it. Each input field says its rules in its description,
|
|
211
212
|
`lora.inputSchemas()` (CLI: `json-schema`) exports them as JSON Schema
|
|
212
213
|
for clients, `diff` reports a tightened rule as breaking, and `check`
|
|
213
214
|
lists stored values that no longer pass. See the
|
|
@@ -549,7 +550,11 @@ entry), and a delete reaches at most `maxBatch` nodes through
|
|
|
549
550
|
`onDelete: CASCADE`. Relationships written (connects, each key of a
|
|
550
551
|
`disconnect` list, nested `update` entries) count up to ten times
|
|
551
552
|
`maxBatch`. Going over is `LIMIT_EXCEEDED`, before the writes that
|
|
552
|
-
would exceed it. `@
|
|
553
|
+
would exceed it. For a bound per field, `@size(max:)` on a list
|
|
554
|
+
relationship (`following: [Person!]! @relationship(...) @size(max: 50)`)
|
|
555
|
+
caps each nested `connect`, `create`, `disconnect` and `update` list of
|
|
556
|
+
one input; a longer list is `BAD_USER_INPUT` before the transaction
|
|
557
|
+
opens. `@key` is not updatable.
|
|
553
558
|
`info` reports `nodesCreated`, `nodesUpdated`, `nodesDeleted`,
|
|
554
559
|
`relationshipsCreated` and `relationshipsDeleted`.
|
|
555
560
|
|
|
@@ -671,6 +676,22 @@ inferred when the statement's last top-level `RETURN` has one item,
|
|
|
671
676
|
Fields returning `@node` types are projected with the selection like any
|
|
672
677
|
other node, and read filters apply to them.
|
|
673
678
|
|
|
679
|
+
A `Mutation` statement says what it wrote in two more columns, read as a
|
|
680
|
+
trigger statement's are: the nodes it returns as `created` and `updated`
|
|
681
|
+
are its write-set, so its change is not `broad` and subscriptions on
|
|
682
|
+
their types fire. Without either column the change is `broad`, and
|
|
683
|
+
`check()` lints the mutation.
|
|
684
|
+
|
|
685
|
+
```graphql
|
|
686
|
+
type Mutation {
|
|
687
|
+
addGenre(key: String!, name: String!): Genre
|
|
688
|
+
@cypher(
|
|
689
|
+
statement: "CREATE (g:Genre {key: $key, name: $name}) RETURN g, g AS created"
|
|
690
|
+
columnName: "g"
|
|
691
|
+
)
|
|
692
|
+
}
|
|
693
|
+
```
|
|
694
|
+
|
|
674
695
|
Statements are checked, not trusted:
|
|
675
696
|
|
|
676
697
|
- at startup: every `$parameter` must be an argument, `$jwt` or
|
|
@@ -752,7 +773,10 @@ createPost(key: String!, hashtags: [String!] = [] @size(max: 30)): Post
|
|
|
752
773
|
Int and Float arguments take bounds the same way: `@range(min:, max:)`
|
|
753
774
|
(either or both, inclusive) checks the value, or each item of a list,
|
|
754
775
|
before the statement runs, and a default outside the bounds is a model
|
|
755
|
-
error. Null is not checked; that is the type's business.
|
|
776
|
+
error. Null is not checked; that is the type's business. A String or ID
|
|
777
|
+
argument takes `@normalize` (`text: String! @normalize(trim: true)
|
|
778
|
+
@length(min: 1, max: 280)`): the trimmed value is what `@length` counts
|
|
779
|
+
and what the statement gets.
|
|
756
780
|
|
|
757
781
|
```graphql
|
|
758
782
|
nearby(lat: Float! @range(min: -90, max: 90), km: Int = 10 @range(min: 1, max: 500)): [Festival!]!
|
|
@@ -1280,8 +1304,8 @@ reported as an API break with no data migration.
|
|
|
1280
1304
|
```ts
|
|
1281
1305
|
lora.onWrite((change) => {
|
|
1282
1306
|
// Per type: lists, counts and connections of these types may have
|
|
1283
|
-
// changed, not only the entities. A broad change (a @cypher mutation
|
|
1284
|
-
//
|
|
1307
|
+
// changed, not only the entities. A broad change (a @cypher mutation
|
|
1308
|
+
// that does not say what it wrote) names nothing: every type goes.
|
|
1285
1309
|
const types = change.broad ? allNodeTypes : change.types;
|
|
1286
1310
|
cache.invalidate(types.map((typename) => ({ typename })));
|
|
1287
1311
|
});
|
|
@@ -1295,7 +1319,8 @@ relationships `connected` and `disconnected` (by field and both keys),
|
|
|
1295
1319
|
included), and the touched `types` and `relationshipTypes`. Every compiled
|
|
1296
1320
|
read carries a read-set (labels and relationship types);
|
|
1297
1321
|
`lora.affects(reads, change)` tells whether a cached read may be stale.
|
|
1298
|
-
`@cypher`
|
|
1322
|
+
A `@cypher` mutation's write-set is the nodes its statement returns as
|
|
1323
|
+
`created` and `updated`; one that returns neither is reported with
|
|
1299
1324
|
`broad: true`. Only writes made through the library are seen. A consumer
|
|
1300
1325
|
that falls `maxQueuedChanges` (default 1000) behind is ended with an error
|
|
1301
1326
|
rather than buffering without bound.
|
|
@@ -1467,6 +1492,12 @@ lora.worker("Notification", {
|
|
|
1467
1492
|
every node of the type the mutation wrote; `$jwt`, `$viewer` and
|
|
1468
1493
|
`$operation` are bound. `fields:` narrows an `UPDATE` trigger to
|
|
1469
1494
|
updates that named one of them.
|
|
1495
|
+
- `@trigger(on: [CONNECT], field: "following", statement: ...)` runs
|
|
1496
|
+
once per relationship the mutation created through that field, from
|
|
1497
|
+
either side, with `this` and `target` bound: "tell a person who
|
|
1498
|
+
followed them" without reading any other relationship. `DISCONNECT`
|
|
1499
|
+
does the same for a relationship removed. A trigger function reads
|
|
1500
|
+
them in `change.connected` and `change.disconnected`.
|
|
1470
1501
|
- Nodes a statement returns as `created` or `updated` join the
|
|
1471
1502
|
mutation's write-set, so `onWrite` and subscriptions see them without
|
|
1472
1503
|
`changeFeed`.
|
|
@@ -1722,38 +1753,38 @@ transaction. Mutations need interactive transactions, which only the Node
|
|
|
1722
1753
|
binding has, so the WASM binding serves reads.
|
|
1723
1754
|
`explain()`, and so plan checks, need the Node binding too.
|
|
1724
1755
|
|
|
1725
|
-
| Option | Default | Meaning
|
|
1726
|
-
| ---------------------------- | ------------- |
|
|
1727
|
-
| `timeoutMs` | 10 000 | Per statement and lock wait; a `signal` cancels
|
|
1728
|
-
| `operationTimeoutMs` | 2 × timeout | All root fields of one query; then `TIMEOUT`
|
|
1729
|
-
| `maxConcurrentStatements` | 2 | Statements one operation runs at once
|
|
1730
|
-
| `maxCost` | 50 000 | Estimated rows per operation
|
|
1731
|
-
| `maxBatch` | 1000 | Nodes written per mutation; bulk `limit` by default
|
|
1732
|
-
| `compileCacheBytes` | 64 MiB | Compile cache and document cache, each
|
|
1733
|
-
| `maxQueuedChanges` | 1000 | How far a change consumer may fall behind
|
|
1734
|
-
| `maxSubscriptions` | 100 | Live subscriptions per `subscriptionScope`
|
|
1735
|
-
| `maxQueuedWrites` | 512 | Writes waiting for the writer lock; more is `OVERLOADED`
|
|
1736
|
-
| `maxTotalCount` | none | Where a root connection's `totalCount` stops counting
|
|
1737
|
-
| `triggerTimeoutMs` | 1000 | Per trigger function; past it the mutation fails
|
|
1738
|
-
| `singleCallMutations` | true | A create is one call that begins, runs, commits
|
|
1739
|
-
| `subscriptionScope` | the context | What `maxSubscriptions` counts per
|
|
1740
|
-
| `maxSubscriptionFilterDepth` | 1 | Relationship filter nesting in a subscription where
|
|
1741
|
-
| `subscriptionTimeoutMs` | 2000 | Per statement checking a change for a subscriber
|
|
1742
|
-
| `defaultLimit` / `maxLimit` | 25 / 100 | Global page sizes; `@limit` may only lower `max`
|
|
1743
|
-
| `callbacks` | | Named callbacks for `@populatedBy`
|
|
1744
|
-
| `jwt` | `context.jwt` | Where the claims are
|
|
1745
|
-
| `onStatement` | | Observe every statement
|
|
1746
|
-
| `cursorSecret` | | Sign cursors; reject unsigned or forged ones
|
|
1747
|
-
| `maskErrors` | production | Clients get `DATABASE_ERROR` and an `id` only
|
|
1748
|
-
| `onError` | | Receives each database error's detail and `id`
|
|
1749
|
-
| `guards` | see below | Document limits; `false` turns them off
|
|
1750
|
-
| `persistedOnly` | false | `execute()`, `subscribe()` run persisted ops only
|
|
1751
|
-
| `budget` | | Cost limit per request, from the context
|
|
1752
|
-
| `onCost` | | Each root field's estimate, total and limit
|
|
1753
|
-
| `onStatementEnd` | | Duration, rows and error of every statement call
|
|
1754
|
-
| `timing` | false | `extensions.timing` in `execute()` results
|
|
1755
|
-
| `tracer` / `traceStatements` | | OpenTelemetry-style spans; Cypher text on request
|
|
1756
|
-
| `metrics` | | Counters and histograms (see below)
|
|
1756
|
+
| Option | Default | Meaning |
|
|
1757
|
+
| ---------------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
1758
|
+
| `timeoutMs` | 10 000 | Per statement and lock wait; a `signal` cancels |
|
|
1759
|
+
| `operationTimeoutMs` | 2 × timeout | All root fields of one query; then `TIMEOUT` |
|
|
1760
|
+
| `maxConcurrentStatements` | 2 | Statements one operation runs at once |
|
|
1761
|
+
| `maxCost` | 50 000 | Estimated rows per operation |
|
|
1762
|
+
| `maxBatch` | 1000 | Nodes written per mutation, and ten times as many relationships; bulk `limit` by default. A nested `connect` list is bounded per field by `@size(max:)` on the relationship |
|
|
1763
|
+
| `compileCacheBytes` | 64 MiB | Compile cache and document cache, each |
|
|
1764
|
+
| `maxQueuedChanges` | 1000 | How far a change consumer may fall behind |
|
|
1765
|
+
| `maxSubscriptions` | 100 | Live subscriptions per `subscriptionScope` |
|
|
1766
|
+
| `maxQueuedWrites` | 512 | Writes waiting for the writer lock; more is `OVERLOADED` |
|
|
1767
|
+
| `maxTotalCount` | none | Where a root connection's `totalCount` stops counting |
|
|
1768
|
+
| `triggerTimeoutMs` | 1000 | Per trigger function; past it the mutation fails |
|
|
1769
|
+
| `singleCallMutations` | true | A create is one call that begins, runs, commits |
|
|
1770
|
+
| `subscriptionScope` | the context | What `maxSubscriptions` counts per |
|
|
1771
|
+
| `maxSubscriptionFilterDepth` | 1 | Relationship filter nesting in a subscription where |
|
|
1772
|
+
| `subscriptionTimeoutMs` | 2000 | Per statement checking a change for a subscriber |
|
|
1773
|
+
| `defaultLimit` / `maxLimit` | 25 / 100 | Global page sizes; `@limit` may only lower `max` |
|
|
1774
|
+
| `callbacks` | | Named callbacks for `@populatedBy` |
|
|
1775
|
+
| `jwt` | `context.jwt` | Where the claims are |
|
|
1776
|
+
| `onStatement` | | Observe every statement |
|
|
1777
|
+
| `cursorSecret` | | Sign cursors; reject unsigned or forged ones |
|
|
1778
|
+
| `maskErrors` | production | Clients get `DATABASE_ERROR` and an `id` only |
|
|
1779
|
+
| `onError` | | Receives each database error's detail and `id` |
|
|
1780
|
+
| `guards` | see below | Document limits; `false` turns them off |
|
|
1781
|
+
| `persistedOnly` | false | `execute()`, `subscribe()` run persisted ops only |
|
|
1782
|
+
| `budget` | | Cost limit per request, from the context |
|
|
1783
|
+
| `onCost` | | Each root field's estimate, total and limit |
|
|
1784
|
+
| `onStatementEnd` | | Duration, rows and error of every statement call |
|
|
1785
|
+
| `timing` | false | `extensions.timing` in `execute()` results |
|
|
1786
|
+
| `tracer` / `traceStatements` | | OpenTelemetry-style spans; Cypher text on request |
|
|
1787
|
+
| `metrics` | | Counters and histograms (see below) |
|
|
1757
1788
|
|
|
1758
1789
|
Errors carry `extensions.code`: `BAD_USER_INPUT`, `INVALID_CURSOR`,
|
|
1759
1790
|
`LIMIT_EXCEEDED`, `COST_EXCEEDED`, `OVERLOADED` (more than
|
package/dist/cli.js
CHANGED
|
@@ -3,8 +3,8 @@ import { execFileSync as ue } from "node:child_process";
|
|
|
3
3
|
import { stat as F, readFile as le, readdir as fe, mkdir as de, writeFile as M } from "node:fs/promises";
|
|
4
4
|
import { dirname as pe, basename as me, join as k, relative as he } from "node:path";
|
|
5
5
|
import { parse as x, Kind as p, visit as Z, print as K, typeFromAST as ge, isNonNullType as W, isListType as ve, getNamedType as we, isEnumType as be, isInputObjectType as Ee } from "graphql";
|
|
6
|
-
import { d as Te } from "./diff-
|
|
7
|
-
import { w as X, j as ye, c as ee, e as Ne, M as Se, a as P, l as te, g as C, x as ne, i as $e, r as Ie, y as ke } from "./lora-graphql-
|
|
6
|
+
import { d as Te } from "./diff-BZZNZs76.js";
|
|
7
|
+
import { w as X, j as ye, c as ee, e as Ne, M as Se, a as P, l as te, g as C, x as ne, i as $e, r as Ie, y as ke } from "./lora-graphql-RvZQdHNA.js";
|
|
8
8
|
const Oe = /* @__PURE__ */ new Set(["Query", "Mutation", "Subscription"]), z = {
|
|
9
9
|
coalesce: "@coalesce is not supported: it hides predicates from indexes; store a value instead",
|
|
10
10
|
subscriptionsAuthorization: "@subscriptionsAuthorization is not supported: SUBSCRIBE rules go in @authorization",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as b from "graphql";
|
|
2
|
-
import { c as $, i as w, r as E, n as C, o as h, q as N, u as k } from "./lora-graphql-
|
|
2
|
+
import { c as $, i as w, r as E, n as C, o as h, q as N, u as k } from "./lora-graphql-RvZQdHNA.js";
|
|
3
3
|
function q(i, n, o = {}) {
|
|
4
4
|
const c = $(i, o), t = $(n, o), f = C, d = new Map(w(c).map((e) => [f(e), e])), s = new Map(w(t).map((e) => [f(e), e])), r = [...s].filter(([e]) => !d.has(e)).map(([, e]) => e), l = [...d].filter(([e]) => !s.has(e)).map(([, e]) => e), a = [], u = [];
|
|
5
5
|
R(c, t, a, u);
|
|
@@ -131,4 +131,4 @@ function S(i, n) {
|
|
|
131
131
|
export {
|
|
132
132
|
q as d
|
|
133
133
|
};
|
|
134
|
-
//# sourceMappingURL=diff-
|
|
134
|
+
//# sourceMappingURL=diff-BZZNZs76.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff-CdZZG-N1.js","sources":["../src/analyze/diff.ts"],"sourcesContent":["// S7: one SDL, two diffs. A change to the annotated SDL changes both the\n// database (constraints, indexes, labels, property names) and the public\n// API; `diffSchemas` reports both from the same two documents.\n\nimport * as graphql from \"graphql\";\nimport type { DocumentNode, GraphQLSchema } from \"graphql\";\nimport { name } from \"../compile/cypher.js\";\nimport { compareConstraints } from \"../model/constraints.js\";\nimport { buildModel } from \"../model/build.js\";\nimport { type ModelOptions } from \"../model/build/limits.js\";\nimport type { Field, GraphModel, ScalarField } from \"../model/types.js\";\nimport { buildSchema } from \"../schema/build.js\";\nimport {\n inferRequirements,\n requirementDdl,\n requirementId,\n type SchemaRequirement,\n} from \"./indexes.js\";\n\nexport interface ApiChange {\n /** graphql-js change type, e.g. `FIELD_REMOVED`. */\n type: string;\n description: string;\n}\n\nexport interface SchemaDiff {\n database: {\n create: SchemaRequirement[];\n drop: SchemaRequirement[];\n /**\n * Statements in the order to run them. Drops and data migrations are\n * destructive: review them before running.\n */\n statements: Array<{ text: string; destructive: boolean; reason: string }>;\n };\n api: {\n /** Changes that break existing clients. */\n breaking: ApiChange[];\n /** Changes that may change behaviour for existing clients. */\n dangerous: ApiChange[];\n };\n /** Consequences worth a human look, e.g. a rename kept by @alias. */\n notes: string[];\n}\n\nexport function diffSchemas(\n before: string | DocumentNode,\n after: string | DocumentNode,\n options: ModelOptions = {},\n): SchemaDiff {\n const oldModel = buildModel(before, options);\n const newModel = buildModel(after, options);\n\n const id = requirementId;\n const oldReqs = new Map(inferRequirements(oldModel).map((r) => [id(r), r]));\n const newReqs = new Map(inferRequirements(newModel).map((r) => [id(r), r]));\n const create = [...newReqs]\n .filter(([k]) => !oldReqs.has(k))\n .map(([, r]) => r);\n const drop = [...oldReqs].filter(([k]) => !newReqs.has(k)).map(([, r]) => r);\n\n const statements: SchemaDiff[\"database\"][\"statements\"] = [];\n const notes: string[] = [];\n migrations(oldModel, newModel, statements, notes);\n for (const r of drop) {\n statements.push({\n text: `DROP ${r.kind === \"constraint\" ? \"CONSTRAINT\" : \"INDEX\"} \\`${r.name.replace(/`/g, \"``\")}\\` IF EXISTS`,\n destructive: true,\n reason: `no longer needed: ${r.reason}`,\n });\n }\n for (const r of create) {\n statements.push({\n text: requirementDdl(r),\n destructive: false,\n reason: r.reason,\n });\n }\n\n const api = apiChanges(publicSchema(oldModel), publicSchema(newModel));\n ruleChanges(oldModel, newModel, api, notes);\n return { database: { create, drop, statements }, api, notes };\n}\n\n/** Relabelled types and renamed properties need their data moved. */\nfunction migrations(\n before: GraphModel,\n after: GraphModel,\n statements: SchemaDiff[\"database\"][\"statements\"],\n notes: string[],\n) {\n for (const [typeName, oldNode] of before.nodes) {\n const newNode = after.nodes.get(typeName);\n if (!newNode) {\n notes.push(\n `${typeName} was removed; its :${oldNode.labels[0]} nodes stay in the database`,\n );\n continue;\n }\n const oldLabel = oldNode.labels[0]!;\n const newLabel = newNode.labels[0]!;\n if (oldLabel !== newLabel) {\n statements.push({\n text: `MATCH (n:${name(oldLabel)}) SET n:${name(newLabel)} REMOVE n:${name(oldLabel)}`,\n destructive: true,\n reason: `${typeName}'s label changed from ${oldLabel} to ${newLabel}`,\n });\n }\n const oldFields = scalars(oldNode.fields);\n const newFields = scalars(newNode.fields);\n for (const [fieldName, oldField] of oldFields) {\n const newField = newFields.get(fieldName);\n if (newField && newField.property !== oldField.property) {\n statements.push({\n text: `MATCH (n:${name(newLabel)}) WHERE n.${name(oldField.property)} IS NOT NULL SET n.${name(newField.property)} = n.${name(oldField.property)} REMOVE n.${name(oldField.property)}`,\n destructive: true,\n reason: `${typeName}.${fieldName} now stores \\`${newField.property}\\` instead of \\`${oldField.property}\\``,\n });\n }\n if (!newField) {\n const renamed = [...newFields.values()].find(\n (f) => f.property === oldField.property && !oldFields.has(f.name),\n );\n if (renamed) {\n notes.push(\n `${typeName}.${fieldName} is now ${typeName}.${renamed.name} over the same property \\`${oldField.property}\\`: an API break with no data migration`,\n );\n }\n }\n }\n }\n}\n\n/**\n * Validation rules are not part of the GraphQL type system, so graphql-js\n * sees no change when one is tightened: a `String` is still a `String`.\n * Compared here, on the model: a rule that now refuses input it accepted\n * breaks clients that send it.\n */\nfunction ruleChanges(\n before: GraphModel,\n after: GraphModel,\n api: SchemaDiff[\"api\"],\n notes: string[],\n) {\n const owners = (model: GraphModel) =>\n new Map<string, ReadonlyMap<string, Field | ScalarField>>([\n ...[...model.nodes].map(([n, t]) => [n, t.fields] as const),\n ...[...model.relationshipProperties].map(\n ([n, t]) => [n, t.fields] as const,\n ),\n ]);\n const old = owners(before);\n let stricter = 0;\n for (const [owner, fields] of owners(after)) {\n const oldFields = old.get(owner);\n if (!oldFields) continue;\n for (const f of fields.values()) {\n const was = oldFields.get(f.name);\n if (f.kind !== \"scalar\" || was?.kind !== \"scalar\") continue;\n const { tightened, changed } = compareConstraints(\n was.constraints,\n f.constraints,\n );\n for (const what of tightened) {\n stricter++;\n api.breaking.push({\n type: \"RULE_TIGHTENED\",\n description: `${owner}.${f.name}: ${what}. Input the old rule accepted is now refused.`,\n });\n }\n for (const what of changed) {\n stricter++;\n api.dangerous.push({\n type: \"RULE_CHANGED\",\n description: `${owner}.${f.name}: ${what}.`,\n });\n }\n }\n }\n if (stricter > 0) {\n notes.push(\n `${stricter === 1 ? \"A validation rule was\" : `${stricter} validation rules were`} added, tightened or changed: values already stored are not revisited. \\`lora-graphql check --database\\` lists the ones that no longer pass.`,\n );\n }\n}\n\nfunction scalars(fields: ReadonlyMap<string, Field>): Map<string, ScalarField> {\n const out = new Map<string, ScalarField>();\n for (const f of fields.values()) if (f.kind === \"scalar\") out.set(f.name, f);\n return out;\n}\n\nfunction publicSchema(model: GraphModel): GraphQLSchema {\n const never = () => Promise.reject(new Error(\"not connected\"));\n return buildSchema(model, {\n customResolver: () => () => null,\n resolveSearch: never,\n resolveAbstract: never,\n subscribe: () => {\n throw new Error(\"not connected\");\n },\n resolveChangedNode: never,\n previousValue: () => null,\n resolveRoot: never,\n resolveNode: never,\n resolveCypher: never,\n resolveMutation: never,\n });\n}\n\ntype ChangeFinder = (\n a: GraphQLSchema,\n b: GraphQLSchema,\n) => Array<{ type: string; description: string }>;\n\nfunction apiChanges(\n before: GraphQLSchema,\n after: GraphQLSchema,\n): SchemaDiff[\"api\"] {\n // graphql-js 16 ships these; 17 removed them.\n const lib = graphql as unknown as {\n findBreakingChanges?: ChangeFinder;\n findDangerousChanges?: ChangeFinder;\n };\n if (!lib.findBreakingChanges || !lib.findDangerousChanges) {\n return {\n breaking: [\n {\n type: \"UNSUPPORTED\",\n description:\n \"this graphql version has no findBreakingChanges; compare printPublicSchema() output with @graphql-inspector/core\",\n },\n ],\n dangerous: [],\n };\n }\n const pick = (c: { type: string; description: string }) => ({\n type: c.type,\n description: c.description,\n });\n return {\n breaking: lib.findBreakingChanges(before, after).map(pick),\n dangerous: lib.findDangerousChanges(before, after).map(pick),\n };\n}\n"],"names":["diffSchemas","before","after","options","oldModel","buildModel","newModel","id","requirementId","oldReqs","inferRequirements","r","newReqs","create","k","drop","statements","notes","migrations","requirementDdl","api","apiChanges","publicSchema","ruleChanges","typeName","oldNode","newNode","oldLabel","newLabel","name","oldFields","scalars","newFields","fieldName","oldField","newField","renamed","f","owners","model","n","t","old","stricter","owner","fields","was","tightened","changed","compareConstraints","what","out","never","buildSchema","lib","graphql","pick","c"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"diff-BZZNZs76.js","sources":["../src/analyze/diff.ts"],"sourcesContent":["// S7: one SDL, two diffs. A change to the annotated SDL changes both the\n// database (constraints, indexes, labels, property names) and the public\n// API; `diffSchemas` reports both from the same two documents.\n\nimport * as graphql from \"graphql\";\nimport type { DocumentNode, GraphQLSchema } from \"graphql\";\nimport { name } from \"../compile/cypher.js\";\nimport { compareConstraints } from \"../model/constraints.js\";\nimport { buildModel } from \"../model/build.js\";\nimport { type ModelOptions } from \"../model/build/limits.js\";\nimport type { Field, GraphModel, ScalarField } from \"../model/types.js\";\nimport { buildSchema } from \"../schema/build.js\";\nimport {\n inferRequirements,\n requirementDdl,\n requirementId,\n type SchemaRequirement,\n} from \"./indexes.js\";\n\nexport interface ApiChange {\n /** graphql-js change type, e.g. `FIELD_REMOVED`. */\n type: string;\n description: string;\n}\n\nexport interface SchemaDiff {\n database: {\n create: SchemaRequirement[];\n drop: SchemaRequirement[];\n /**\n * Statements in the order to run them. Drops and data migrations are\n * destructive: review them before running.\n */\n statements: Array<{ text: string; destructive: boolean; reason: string }>;\n };\n api: {\n /** Changes that break existing clients. */\n breaking: ApiChange[];\n /** Changes that may change behaviour for existing clients. */\n dangerous: ApiChange[];\n };\n /** Consequences worth a human look, e.g. a rename kept by @alias. */\n notes: string[];\n}\n\nexport function diffSchemas(\n before: string | DocumentNode,\n after: string | DocumentNode,\n options: ModelOptions = {},\n): SchemaDiff {\n const oldModel = buildModel(before, options);\n const newModel = buildModel(after, options);\n\n const id = requirementId;\n const oldReqs = new Map(inferRequirements(oldModel).map((r) => [id(r), r]));\n const newReqs = new Map(inferRequirements(newModel).map((r) => [id(r), r]));\n const create = [...newReqs]\n .filter(([k]) => !oldReqs.has(k))\n .map(([, r]) => r);\n const drop = [...oldReqs].filter(([k]) => !newReqs.has(k)).map(([, r]) => r);\n\n const statements: SchemaDiff[\"database\"][\"statements\"] = [];\n const notes: string[] = [];\n migrations(oldModel, newModel, statements, notes);\n for (const r of drop) {\n statements.push({\n text: `DROP ${r.kind === \"constraint\" ? \"CONSTRAINT\" : \"INDEX\"} \\`${r.name.replace(/`/g, \"``\")}\\` IF EXISTS`,\n destructive: true,\n reason: `no longer needed: ${r.reason}`,\n });\n }\n for (const r of create) {\n statements.push({\n text: requirementDdl(r),\n destructive: false,\n reason: r.reason,\n });\n }\n\n const api = apiChanges(publicSchema(oldModel), publicSchema(newModel));\n ruleChanges(oldModel, newModel, api, notes);\n return { database: { create, drop, statements }, api, notes };\n}\n\n/** Relabelled types and renamed properties need their data moved. */\nfunction migrations(\n before: GraphModel,\n after: GraphModel,\n statements: SchemaDiff[\"database\"][\"statements\"],\n notes: string[],\n) {\n for (const [typeName, oldNode] of before.nodes) {\n const newNode = after.nodes.get(typeName);\n if (!newNode) {\n notes.push(\n `${typeName} was removed; its :${oldNode.labels[0]} nodes stay in the database`,\n );\n continue;\n }\n const oldLabel = oldNode.labels[0]!;\n const newLabel = newNode.labels[0]!;\n if (oldLabel !== newLabel) {\n statements.push({\n text: `MATCH (n:${name(oldLabel)}) SET n:${name(newLabel)} REMOVE n:${name(oldLabel)}`,\n destructive: true,\n reason: `${typeName}'s label changed from ${oldLabel} to ${newLabel}`,\n });\n }\n const oldFields = scalars(oldNode.fields);\n const newFields = scalars(newNode.fields);\n for (const [fieldName, oldField] of oldFields) {\n const newField = newFields.get(fieldName);\n if (newField && newField.property !== oldField.property) {\n statements.push({\n text: `MATCH (n:${name(newLabel)}) WHERE n.${name(oldField.property)} IS NOT NULL SET n.${name(newField.property)} = n.${name(oldField.property)} REMOVE n.${name(oldField.property)}`,\n destructive: true,\n reason: `${typeName}.${fieldName} now stores \\`${newField.property}\\` instead of \\`${oldField.property}\\``,\n });\n }\n if (!newField) {\n const renamed = [...newFields.values()].find(\n (f) => f.property === oldField.property && !oldFields.has(f.name),\n );\n if (renamed) {\n notes.push(\n `${typeName}.${fieldName} is now ${typeName}.${renamed.name} over the same property \\`${oldField.property}\\`: an API break with no data migration`,\n );\n }\n }\n }\n }\n}\n\n/**\n * Validation rules are not part of the GraphQL type system, so graphql-js\n * sees no change when one is tightened: a `String` is still a `String`.\n * Compared here, on the model: a rule that now refuses input it accepted\n * breaks clients that send it.\n */\nfunction ruleChanges(\n before: GraphModel,\n after: GraphModel,\n api: SchemaDiff[\"api\"],\n notes: string[],\n) {\n const owners = (model: GraphModel) =>\n new Map<string, ReadonlyMap<string, Field | ScalarField>>([\n ...[...model.nodes].map(([n, t]) => [n, t.fields] as const),\n ...[...model.relationshipProperties].map(\n ([n, t]) => [n, t.fields] as const,\n ),\n ]);\n const old = owners(before);\n let stricter = 0;\n for (const [owner, fields] of owners(after)) {\n const oldFields = old.get(owner);\n if (!oldFields) continue;\n for (const f of fields.values()) {\n const was = oldFields.get(f.name);\n if (f.kind !== \"scalar\" || was?.kind !== \"scalar\") continue;\n const { tightened, changed } = compareConstraints(\n was.constraints,\n f.constraints,\n );\n for (const what of tightened) {\n stricter++;\n api.breaking.push({\n type: \"RULE_TIGHTENED\",\n description: `${owner}.${f.name}: ${what}. Input the old rule accepted is now refused.`,\n });\n }\n for (const what of changed) {\n stricter++;\n api.dangerous.push({\n type: \"RULE_CHANGED\",\n description: `${owner}.${f.name}: ${what}.`,\n });\n }\n }\n }\n if (stricter > 0) {\n notes.push(\n `${stricter === 1 ? \"A validation rule was\" : `${stricter} validation rules were`} added, tightened or changed: values already stored are not revisited. \\`lora-graphql check --database\\` lists the ones that no longer pass.`,\n );\n }\n}\n\nfunction scalars(fields: ReadonlyMap<string, Field>): Map<string, ScalarField> {\n const out = new Map<string, ScalarField>();\n for (const f of fields.values()) if (f.kind === \"scalar\") out.set(f.name, f);\n return out;\n}\n\nfunction publicSchema(model: GraphModel): GraphQLSchema {\n const never = () => Promise.reject(new Error(\"not connected\"));\n return buildSchema(model, {\n customResolver: () => () => null,\n resolveSearch: never,\n resolveAbstract: never,\n subscribe: () => {\n throw new Error(\"not connected\");\n },\n resolveChangedNode: never,\n previousValue: () => null,\n resolveRoot: never,\n resolveNode: never,\n resolveCypher: never,\n resolveMutation: never,\n });\n}\n\ntype ChangeFinder = (\n a: GraphQLSchema,\n b: GraphQLSchema,\n) => Array<{ type: string; description: string }>;\n\nfunction apiChanges(\n before: GraphQLSchema,\n after: GraphQLSchema,\n): SchemaDiff[\"api\"] {\n // graphql-js 16 ships these; 17 removed them.\n const lib = graphql as unknown as {\n findBreakingChanges?: ChangeFinder;\n findDangerousChanges?: ChangeFinder;\n };\n if (!lib.findBreakingChanges || !lib.findDangerousChanges) {\n return {\n breaking: [\n {\n type: \"UNSUPPORTED\",\n description:\n \"this graphql version has no findBreakingChanges; compare printPublicSchema() output with @graphql-inspector/core\",\n },\n ],\n dangerous: [],\n };\n }\n const pick = (c: { type: string; description: string }) => ({\n type: c.type,\n description: c.description,\n });\n return {\n breaking: lib.findBreakingChanges(before, after).map(pick),\n dangerous: lib.findDangerousChanges(before, after).map(pick),\n };\n}\n"],"names":["diffSchemas","before","after","options","oldModel","buildModel","newModel","id","requirementId","oldReqs","inferRequirements","r","newReqs","create","k","drop","statements","notes","migrations","requirementDdl","api","apiChanges","publicSchema","ruleChanges","typeName","oldNode","newNode","oldLabel","newLabel","name","oldFields","scalars","newFields","fieldName","oldField","newField","renamed","f","owners","model","n","t","old","stricter","owner","fields","was","tightened","changed","compareConstraints","what","out","never","buildSchema","lib","graphql","pick","c"],"mappings":"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"}
|
|
@@ -50,6 +50,14 @@ export interface WriteChange {
|
|
|
50
50
|
*/
|
|
51
51
|
broad: boolean;
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Complete `change` with the relationships a mutation wrote: both lists,
|
|
55
|
+
* and the entities and types that follow from everything in it.
|
|
56
|
+
*/
|
|
57
|
+
export declare function fillChange(change: WriteChange, wrote: {
|
|
58
|
+
connected: readonly RelationshipRef[];
|
|
59
|
+
disconnected: readonly RelationshipRef[];
|
|
60
|
+
}): void;
|
|
53
61
|
export interface ReadSetLike {
|
|
54
62
|
labels: readonly string[];
|
|
55
63
|
relationships: readonly string[];
|
|
@@ -2,6 +2,10 @@ import { FieldNode } from 'graphql';
|
|
|
2
2
|
import { Statement } from '../driver.js';
|
|
3
3
|
import { CypherField } from '../model/types.js';
|
|
4
4
|
import { MutationEnv } from './mutate/env.js';
|
|
5
|
+
import { Reported } from './mutate/reported.js';
|
|
5
6
|
export declare function executeCypherMutation(env: MutationEnv, field: CypherField, args: Record<string, unknown>, fieldNodes: readonly FieldNode[],
|
|
6
7
|
/** The field's `viewer` rules, checked in the transaction first. */
|
|
7
|
-
guard?: Statement): Promise<
|
|
8
|
+
guard?: Statement): Promise<{
|
|
9
|
+
value: unknown;
|
|
10
|
+
written: Reported;
|
|
11
|
+
}>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { CompileContext } from '../../compile/context.js';
|
|
2
|
+
import { QueryResult, Statement } from '../../driver.js';
|
|
3
|
+
import { GraphModel } from '../../model/types.js';
|
|
4
|
+
import { EntityRef } from '../changes.js';
|
|
5
|
+
export interface Reported {
|
|
6
|
+
created: EntityRef[];
|
|
7
|
+
updated: EntityRef[];
|
|
8
|
+
/** The statement wrote something listeners cannot be told about. */
|
|
9
|
+
broad: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const refId: (e: EntityRef) => string;
|
|
12
|
+
/**
|
|
13
|
+
* The nodes `result` returns as `created` or `updated`, each once (a node
|
|
14
|
+
* in both is created). A statement that writes and names neither is
|
|
15
|
+
* broad, and so is one that returns something that is not a node of the
|
|
16
|
+
* model (a key, a node of an unknown label).
|
|
17
|
+
*/
|
|
18
|
+
export declare function reportedWrites(model: GraphModel, statement: {
|
|
19
|
+
writes: boolean;
|
|
20
|
+
reports: boolean;
|
|
21
|
+
}, result: QueryResult): Reported;
|
|
22
|
+
/**
|
|
23
|
+
* A statement's CREATE knows nothing of @outbox: the nodes it reports as
|
|
24
|
+
* created become work with these, in the same transaction.
|
|
25
|
+
*/
|
|
26
|
+
export declare function outboxStatements(model: GraphModel, created: readonly EntityRef[], ctx: () => CompileContext): Statement[];
|
|
@@ -14,6 +14,12 @@ export declare class Runner {
|
|
|
14
14
|
info: MutationInfo;
|
|
15
15
|
connected: RelationshipRef[];
|
|
16
16
|
disconnected: RelationshipRef[];
|
|
17
|
+
/**
|
|
18
|
+
* The relationships of `connected` that were not there before, for
|
|
19
|
+
* CONNECT triggers. Filled in a transaction, which is where a mutation
|
|
20
|
+
* of a schema with triggers runs.
|
|
21
|
+
*/
|
|
22
|
+
linked: RelationshipRef[];
|
|
17
23
|
/** Nodes updated per type, for AFTER validation. */
|
|
18
24
|
updated: Map<NodeType, {
|
|
19
25
|
keys: unknown[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CompileContext } from '../../compile/context.js';
|
|
2
2
|
import { Statement } from '../../driver.js';
|
|
3
|
-
import {
|
|
3
|
+
import { NodeType, Trigger, TriggerOperation } from '../../model/types.js';
|
|
4
4
|
import { MutationEnv } from './env.js';
|
|
5
5
|
import { Runner } from './runner.js';
|
|
6
6
|
/** Whether the mutation may fire a trigger: a statement or a function. */
|
|
@@ -8,7 +8,13 @@ export declare function hasTriggers(env: MutationEnv): boolean;
|
|
|
8
8
|
/** Whether the mutation may call a trigger function. */
|
|
9
9
|
export declare function hasTriggerFunctions(env: MutationEnv): boolean;
|
|
10
10
|
/** The trigger statement for `keys`, with `this` bound to each node. */
|
|
11
|
-
export declare function triggerStatement(ctx: CompileContext, node: NodeType, trigger: Trigger, operation:
|
|
11
|
+
export declare function triggerStatement(ctx: CompileContext, node: NodeType, trigger: Trigger, operation: TriggerOperation, keys: unknown[]): Statement;
|
|
12
|
+
/**
|
|
13
|
+
* The text ahead of a CONNECT or DISCONNECT trigger's statement: one row
|
|
14
|
+
* per pair of `pairs` (a list of `[this key, target key]`), with `this`
|
|
15
|
+
* and `target` bound.
|
|
16
|
+
*/
|
|
17
|
+
export declare function relationshipTriggerPrefix(node: NodeType, target: NodeType, pairs: string): string;
|
|
12
18
|
/**
|
|
13
19
|
* Run the CREATE and UPDATE triggers for what the mutation wrote so far.
|
|
14
20
|
* A node the mutation created and then updated fires CREATE only.
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as r, L as e, a as o, b as i, M as l, c as n, d, e as t, f as m, g as p, h as c, i as f, j as L, k as h, l as R, p as D, r as u, s as E, m as G, t as b, v } from "./lora-graphql-
|
|
2
|
-
import { d as O } from "./diff-
|
|
1
|
+
import { D as r, L as e, a as o, b as i, M as l, c as n, d, e as t, f as m, g as p, h as c, i as f, j as L, k as h, l as R, p as D, r as u, s as E, m as G, t as b, v } from "./lora-graphql-RvZQdHNA.js";
|
|
2
|
+
import { d as O } from "./diff-BZZNZs76.js";
|
|
3
3
|
export {
|
|
4
4
|
r as DEFAULT_GUARDS,
|
|
5
5
|
e as LORA_GRAPHQL_ERROR_CODES,
|