@loradb/lora-graphql 0.24.0 → 0.25.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/README.md +185 -42
- package/dist/analyze/access.d.ts +11 -3
- package/dist/analyze/input-schemas.d.ts +11 -0
- package/dist/analyze/plans.d.ts +8 -0
- package/dist/analyze/stored.d.ts +29 -0
- package/dist/analyze/verdicts.d.ts +6 -0
- package/dist/cli.js +263 -244
- package/dist/cli.js.map +1 -1
- package/dist/compile/cache.d.ts +9 -1
- package/dist/compile/context.d.ts +3 -0
- package/dist/compile/read/common.d.ts +2 -0
- package/dist/compile/read/root.d.ts +7 -0
- package/dist/compile/selection.d.ts +5 -0
- package/dist/diff-CdZZG-N1.js +134 -0
- package/dist/diff-CdZZG-N1.js.map +1 -0
- package/dist/driver.d.ts +36 -1
- package/dist/errors.d.ts +21 -1
- package/dist/events/outbox.d.ts +14 -0
- package/dist/events/worker.d.ts +67 -0
- package/dist/execute/mutate/checks.d.ts +16 -0
- package/dist/execute/mutate/env.d.ts +50 -0
- package/dist/execute/mutate/errors.d.ts +6 -0
- package/dist/execute/mutate/links.d.ts +38 -0
- package/dist/execute/mutate/plan.d.ts +5 -0
- package/dist/execute/mutate/runner.d.ts +101 -25
- package/dist/execute/mutate/single-call.d.ts +92 -0
- package/dist/execute/mutate/triggers.d.ts +21 -0
- package/dist/execute/mutate/unique-together.d.ts +5 -1
- package/dist/execute/mutate/validate.d.ts +11 -0
- package/dist/execute/transaction.d.ts +13 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +16 -15
- package/dist/lora-graphql-D11QFUkf.js +17222 -0
- package/dist/lora-graphql-D11QFUkf.js.map +1 -0
- package/dist/lora-graphql.d.ts +36 -3
- package/dist/model/build/authorization.d.ts +11 -1
- package/dist/model/build/constraints.d.ts +22 -0
- package/dist/model/build/rule-checks.d.ts +6 -1
- package/dist/model/build/trigger.d.ts +9 -0
- package/dist/model/constraints.d.ts +112 -0
- package/dist/model/desugar.d.ts +7 -0
- package/dist/model/directives.d.ts +1 -1
- package/dist/model/types.d.ts +34 -0
- package/dist/options.d.ts +76 -3
- package/dist/results.d.ts +12 -1
- package/dist/schema/scalars.d.ts +8 -1
- package/dist/standard-schema.d.ts +37 -0
- package/dist/testing.js +1 -1
- package/package.json +2 -2
- package/dist/diff-aDivqYqr.js +0 -100
- package/dist/diff-aDivqYqr.js.map +0 -1
- package/dist/driver-DSnytFsU.js +0 -14323
- package/dist/driver-DSnytFsU.js.map +0 -1
package/README.md
CHANGED
|
@@ -65,6 +65,7 @@ const yoga = createYoga({
|
|
|
65
65
|
- [The smart layer](#the-smart-layer)
|
|
66
66
|
- [Change tracking](#change-tracking)
|
|
67
67
|
- [Subscriptions](#subscriptions)
|
|
68
|
+
- [Triggers and workers](#triggers-and-workers)
|
|
68
69
|
- [Transactions](#transactions)
|
|
69
70
|
- [CLI](#cli)
|
|
70
71
|
- [Drivers, limits and errors](#drivers-limits-and-errors)
|
|
@@ -97,6 +98,13 @@ Relationship fields take `where`, `sort` and `limit`; list relationships
|
|
|
97
98
|
also get `…Connection`, whose edges carry the relationship properties and
|
|
98
99
|
filter on them (`where: { node, edge }`).
|
|
99
100
|
|
|
101
|
+
`@query(aggregate: true)` on a type also gives every relationship
|
|
102
|
+
connection that leads to it types of its own (`CityVenuesConnection` in
|
|
103
|
+
place of `VenueConnection`), since its `aggregate` covers that
|
|
104
|
+
relationship: turning it on renames types in the schema. A count does not
|
|
105
|
+
need it: `{ venuesConnection { totalCount } }` selects no page and reads
|
|
106
|
+
none.
|
|
107
|
+
|
|
100
108
|
The surface is restrictive: a field is filterable only with `@filterable`,
|
|
101
109
|
and only by the operators listed; sortable only with `@sortable`;
|
|
102
110
|
`printPublicSchema()` prints exactly what clients see, with no directives.
|
|
@@ -157,8 +165,12 @@ API:
|
|
|
157
165
|
| `@sortable` | field | Sort and paginate by this field (on a relationship property: `sort: [{ edge: { ... } }]`) |
|
|
158
166
|
| `@groupBy` | field | A grouping key of `<plural>Grouped(by:)` (needs `@query(aggregate: true)`) |
|
|
159
167
|
| `@limit(default:, max:)` | type, interface, union, list relationship | Page size bounds |
|
|
160
|
-
| `@
|
|
161
|
-
| `@
|
|
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` | How many items it holds; on an argument, `max` (default `maxListArgument`) |
|
|
162
174
|
| `@relayId` | `@key` field | Adds a global `id` and the `Node` interface |
|
|
163
175
|
| `@authentication(operations:, jwt:)` | type, field | Needs an authenticated request, whose claims satisfy `jwt` |
|
|
164
176
|
| `@authorization(filter:, validate:)` | type (filter and validate), field (validate) | Row-level rules, compiled into statements |
|
|
@@ -178,6 +190,29 @@ string), `Date`, `Time`, `LocalTime`, `DateTime`, `LocalDateTime`,
|
|
|
178
190
|
`Duration` (ISO-8601 strings), `Point` and `CartesianPoint` (objects; set with
|
|
179
191
|
`PointInput` / `CartesianPointInput`), and non-null lists of these.
|
|
180
192
|
|
|
193
|
+
The six rows from `@length` to `@size` are validation rules. On a field
|
|
194
|
+
they are checked wherever the field is set (create, update, upsert, nested
|
|
195
|
+
creates, relationship properties), in JavaScript before the transaction
|
|
196
|
+
opens, and a mutation that breaks any is `BAD_USER_INPUT` with every
|
|
197
|
+
violation in `extensions.violations` (`path`, `coordinate`, `rule`,
|
|
198
|
+
`params`, `message`; never the value). `null` and absent values pass; a
|
|
199
|
+
pattern must match the whole value; values computed during the write (an
|
|
200
|
+
`adjust`, a `@populatedBy` result) are checked in the transaction and fail
|
|
201
|
+
with `CONSTRAINT_VIOLATION`. Raw Cypher and `@cypher` mutations are not
|
|
202
|
+
checked. Each takes `message:`. `@normalize(trim:, case:, unicode:,
|
|
203
|
+
emptyToNull:)` changes a string before it is validated and stored (it is
|
|
204
|
+
what makes a `@unique` email treat `Ada@X.com ` and `ada@x.com` as one
|
|
205
|
+
value). The rules and `@normalize` may sit on a custom scalar (`scalar
|
|
206
|
+
Email @storedAs(type: STRING) @format(as: "email")`), for every field of
|
|
207
|
+
that type. For a rule across fields, the `validators` option takes a
|
|
208
|
+
Standard Schema validator per node type (zod, valibot, arktype, yup),
|
|
209
|
+
run on each input after the field rules. The same directives go on
|
|
210
|
+
`@cypher` arguments. Each input field says its rules in its description,
|
|
211
|
+
`lora.inputSchemas()` (CLI: `json-schema`) exports them as JSON Schema
|
|
212
|
+
for clients, `diff` reports a tightened rule as breaking, and `check`
|
|
213
|
+
lists stored values that no longer pass. See the
|
|
214
|
+
[directive reference](https://loradb.com/docs/graphql/directives#validation).
|
|
215
|
+
|
|
181
216
|
## Queries
|
|
182
217
|
|
|
183
218
|
```graphql
|
|
@@ -809,12 +844,18 @@ type Post
|
|
|
809
844
|
- **Anonymous callers.** Without a token every rule denies, unless the
|
|
810
845
|
schema says `@authorizationDefaults(requireAuthentication: false)`. Then
|
|
811
846
|
a rule with a branch that reads no claims decides that branch for
|
|
812
|
-
anonymous callers too (the published posts above)
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
847
|
+
anonymous callers too (the published posts above). A rule that needs
|
|
848
|
+
claims still denies them, and how depends on the kind of rule: a
|
|
849
|
+
validate rule asks for a token (`UNAUTHENTICATED`, not `FORBIDDEN`),
|
|
850
|
+
while a READ filter admits no row, so a signed-out caller reads an
|
|
851
|
+
empty list and the access matrix calls it `denied`.
|
|
852
|
+
It is one setting for the schema; a rule has no such field, and it
|
|
853
|
+
reaches every rule: a CREATE, UPDATE, DELETE or CONNECT rule that
|
|
854
|
+
reads no claims decides for signed-out callers as well. `check` names
|
|
855
|
+
each such write rule that no `@authentication` covers. To keep a
|
|
856
|
+
claim-free rule or branch for signed-in callers, test a claim beside it
|
|
816
857
|
(`{ jwt: { sub: { exists: true } } }`, as the tenant rule above does),
|
|
817
|
-
or put `@authentication` on the type.
|
|
858
|
+
or put `@authentication` on the type or the relationship field.
|
|
818
859
|
- **The caller's own node.** Mark the claim that identifies the caller
|
|
819
860
|
with `@viewer(type: "Person", field: "subject")` (the field is `@key` or
|
|
820
861
|
`@unique`), so the key can stay a slug while the subject is an identity
|
|
@@ -1363,6 +1404,85 @@ replay earlier writes. `onWrite` still reports
|
|
|
1363
1404
|
this instance's mutations, and `previousState` needs them: the feed carries
|
|
1364
1405
|
the state after the write. Call `lora.close()` to stop the feed.
|
|
1365
1406
|
|
|
1407
|
+
## Triggers and workers
|
|
1408
|
+
|
|
1409
|
+
Three things react to a write, at three moments:
|
|
1410
|
+
|
|
1411
|
+
- a **trigger** runs inside the mutation's transaction, before the
|
|
1412
|
+
commit: derived data that must never be out of step, or a refusal;
|
|
1413
|
+
- an **event** (`onWrite`, `changes()`, a subscription) reports what
|
|
1414
|
+
committed;
|
|
1415
|
+
- a **worker** delivers what a transaction cannot hold (a push, an
|
|
1416
|
+
e-mail, a webhook) after the commit, with retries.
|
|
1417
|
+
|
|
1418
|
+
```graphql
|
|
1419
|
+
type Comment
|
|
1420
|
+
@node
|
|
1421
|
+
@mutation
|
|
1422
|
+
@authentication
|
|
1423
|
+
@trigger(
|
|
1424
|
+
on: [CREATE]
|
|
1425
|
+
statement: """
|
|
1426
|
+
MATCH (this)-[:ON]->(post:Post)<-[:WROTE]-(author:Person)
|
|
1427
|
+
CREATE (author)<-[:FOR]-(n:Notification {
|
|
1428
|
+
key: 'comment:' + this.key, text: 'New comment on ' + post.title
|
|
1429
|
+
})
|
|
1430
|
+
RETURN n AS created
|
|
1431
|
+
"""
|
|
1432
|
+
) {
|
|
1433
|
+
key: String! @key
|
|
1434
|
+
text: String!
|
|
1435
|
+
post: Post! @relationship(type: "ON", direction: OUT)
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
type Notification @node @outbox @subscription(operations: [CREATE]) {
|
|
1439
|
+
key: String! @key
|
|
1440
|
+
text: String!
|
|
1441
|
+
}
|
|
1442
|
+
```
|
|
1443
|
+
|
|
1444
|
+
```ts
|
|
1445
|
+
const lora = new LoraGraphQL({
|
|
1446
|
+
typeDefs,
|
|
1447
|
+
driver: loraDriver(db),
|
|
1448
|
+
// What Cypher cannot say; throwing rolls the mutation back.
|
|
1449
|
+
triggers: {
|
|
1450
|
+
Comment: {
|
|
1451
|
+
create: async ({ keys, transaction }) => {
|
|
1452
|
+
/* transaction.execute(cypher, params) */
|
|
1453
|
+
},
|
|
1454
|
+
},
|
|
1455
|
+
},
|
|
1456
|
+
});
|
|
1457
|
+
|
|
1458
|
+
// After the commit, at least once, with retries: make it idempotent.
|
|
1459
|
+
lora.worker("Notification", {
|
|
1460
|
+
handle: async (notification, { key, attempt, signal }) => {
|
|
1461
|
+
await push.send(key, notification.text, { signal });
|
|
1462
|
+
},
|
|
1463
|
+
});
|
|
1464
|
+
```
|
|
1465
|
+
|
|
1466
|
+
- A `@trigger` statement runs once per mutation with `this` bound to
|
|
1467
|
+
every node of the type the mutation wrote; `$jwt`, `$viewer` and
|
|
1468
|
+
`$operation` are bound. `fields:` narrows an `UPDATE` trigger to
|
|
1469
|
+
updates that named one of them.
|
|
1470
|
+
- Nodes a statement returns as `created` or `updated` join the
|
|
1471
|
+
mutation's write-set, so `onWrite` and subscriptions see them without
|
|
1472
|
+
`changeFeed`.
|
|
1473
|
+
- Triggers run only for a write every rule has passed, and what a
|
|
1474
|
+
trigger writes fires no trigger.
|
|
1475
|
+
- **Only generated mutations fire triggers.** Raw Cypher and `@cypher`
|
|
1476
|
+
mutations fire none.
|
|
1477
|
+
- A trigger function holds the writer lock, bounded by
|
|
1478
|
+
`triggerTimeoutMs` (default 1000). Network calls belong in a worker.
|
|
1479
|
+
- `@outbox` makes each node of a type work for `lora.worker()`. The
|
|
1480
|
+
delivery state lives on the node, outside the API, so the work is as
|
|
1481
|
+
durable as the data and is found again after a restart.
|
|
1482
|
+
|
|
1483
|
+
The full guide, with a tested example and every option, is on the
|
|
1484
|
+
[site](https://loradb.com/docs/graphql/triggers).
|
|
1485
|
+
|
|
1366
1486
|
## Transactions
|
|
1367
1487
|
|
|
1368
1488
|
```ts
|
|
@@ -1445,18 +1565,29 @@ the rules test, such as `roles:admin`), with the verdict (`allowed`,
|
|
|
1445
1565
|
rules that decide it. When rules name the caller's node (`isViewer`),
|
|
1446
1566
|
`viewer` is the caller on nodes that are theirs: every rule part that
|
|
1447
1567
|
only names them passes, so it shows the most a related caller gets,
|
|
1448
|
-
where `authenticated` shows what any signed-in caller gets. A
|
|
1568
|
+
where `authenticated` shows what any signed-in caller gets. A part under
|
|
1569
|
+
`NOT` stays as written and is decided per row, so `viewer` never reads
|
|
1570
|
+
below `authenticated`. A relationship field has a CONNECT row when a
|
|
1571
|
+
rule or `@authentication(operations: [CREATE_RELATIONSHIP])` guards it,
|
|
1572
|
+
and a DISCONNECT row for a rule or `DELETE_RELATIONSHIP`. A
|
|
1449
1573
|
`@key(scope: VIEWER)` type's CREATE reads
|
|
1450
1574
|
`unauthenticated` for anonymous callers and `validated` by `key scope`
|
|
1451
1575
|
for the rest.
|
|
1452
1576
|
|
|
1453
|
-
`lora.operationAccess(document | persistedId, operationName?)`
|
|
1454
|
-
the same question for one operation: each root field (fragments
|
|
1577
|
+
`lora.operationAccess(document | persistedId, operationName?, variables?)`
|
|
1578
|
+
answers the same question for one operation: each root field (fragments
|
|
1455
1579
|
followed) with its type, the operations it needs (an upsert needs
|
|
1456
1580
|
`CREATE` and `UPDATE`) and the verdict per caller, plus the most
|
|
1457
1581
|
restrictive verdict per caller over all root fields. A field over
|
|
1458
1582
|
several types (`node`, a union) reports the most restrictive member.
|
|
1459
|
-
|
|
1583
|
+
A mutation's inputs are read for what they write through relationship
|
|
1584
|
+
fields: a nested connect or create counts as that field's CONNECT (and
|
|
1585
|
+
the target's CREATE), a disconnect or nested delete as its DISCONNECT, an
|
|
1586
|
+
`update: { edge }` as UPDATE_EDGE, each named in `by`
|
|
1587
|
+
(`Room.chan CONNECT: @authentication`). Pass `variables` for inputs the
|
|
1588
|
+
document takes as variables; a variable without a value is not read and
|
|
1589
|
+
is listed in the field's `unresolved`. Selections below a root field
|
|
1590
|
+
answer to their own types' READ rules.
|
|
1460
1591
|
To keep admin-only operations out of client bundles:
|
|
1461
1592
|
|
|
1462
1593
|
````ts
|
|
@@ -1470,9 +1601,15 @@ order is stable, so a snapshot in CI turns access changes into diffs.
|
|
|
1470
1601
|
`check` lints authorization too:
|
|
1471
1602
|
|
|
1472
1603
|
- a filter rule every signed-in caller passes;
|
|
1473
|
-
- a rule
|
|
1604
|
+
- a rule that needs no claims, as a whole or in one `OR` branch, on a
|
|
1605
|
+
type or a field, in a schema that leaves
|
|
1474
1606
|
`@authorizationDefaults(requireAuthentication:)` unset: anonymous
|
|
1475
|
-
callers are refused
|
|
1607
|
+
callers are refused it by default, so say which was meant;
|
|
1608
|
+
- once that setting is `false`, each write rule (CREATE, UPDATE, DELETE,
|
|
1609
|
+
CONNECT, DISCONNECT, UPDATE_EDGE) a signed-out caller can pass and no
|
|
1610
|
+
`@authentication` covers. READ rules and operations the type lists in
|
|
1611
|
+
`public` are left alone: those are what the setting and the list are
|
|
1612
|
+
for;
|
|
1476
1613
|
- field rules the schema's bypass skips, on a type that says neither
|
|
1477
1614
|
`@authorization(bypass: false)` nor `bypass: true`, and one line naming
|
|
1478
1615
|
every type with rules the bypass reaches that way (plus relationship
|
|
@@ -1585,37 +1722,43 @@ transaction. Mutations need interactive transactions, which only the Node
|
|
|
1585
1722
|
binding has, so the WASM binding serves reads.
|
|
1586
1723
|
`explain()`, and so plan checks, need the Node binding too.
|
|
1587
1724
|
|
|
1588
|
-
| Option | Default | Meaning
|
|
1589
|
-
| ---------------------------- | ------------- |
|
|
1590
|
-
| `timeoutMs` | 10 000 | Per statement and lock wait; a `signal` cancels
|
|
1591
|
-
| `operationTimeoutMs` | 2 × timeout | All root fields of one query; then `TIMEOUT`
|
|
1592
|
-
| `maxConcurrentStatements` | 2 | Statements one operation runs at once
|
|
1593
|
-
| `maxCost` | 50 000 | Estimated rows per operation
|
|
1594
|
-
| `maxBatch` | 1000 | Nodes written per mutation; bulk `limit` by default
|
|
1595
|
-
| `compileCacheBytes` | 64 MiB | Compile cache and document cache, each
|
|
1596
|
-
| `maxQueuedChanges` | 1000 | How far a change consumer may fall behind
|
|
1597
|
-
| `maxSubscriptions` | 100 | Live subscriptions per `subscriptionScope`
|
|
1598
|
-
| `
|
|
1599
|
-
| `
|
|
1600
|
-
| `
|
|
1601
|
-
| `
|
|
1602
|
-
| `
|
|
1603
|
-
| `
|
|
1604
|
-
| `
|
|
1605
|
-
| `
|
|
1606
|
-
| `
|
|
1607
|
-
| `
|
|
1608
|
-
| `
|
|
1609
|
-
| `
|
|
1610
|
-
| `
|
|
1611
|
-
| `
|
|
1612
|
-
| `
|
|
1613
|
-
| `
|
|
1614
|
-
| `
|
|
1615
|
-
| `
|
|
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) |
|
|
1616
1757
|
|
|
1617
1758
|
Errors carry `extensions.code`: `BAD_USER_INPUT`, `INVALID_CURSOR`,
|
|
1618
|
-
`LIMIT_EXCEEDED`, `COST_EXCEEDED`, `
|
|
1759
|
+
`LIMIT_EXCEEDED`, `COST_EXCEEDED`, `OVERLOADED` (more than
|
|
1760
|
+
`maxQueuedWrites` mutations are waiting for the database: answer 503 and
|
|
1761
|
+
let the client back off), `TIMEOUT` (with `operationTimeoutMs`),
|
|
1619
1762
|
`UNAUTHENTICATED`, `FORBIDDEN`,
|
|
1620
1763
|
`NOT_FOUND`, `CONSTRAINT_VIOLATION` (with `type` and `field`),
|
|
1621
1764
|
`DATABASE_ERROR` (with an `id`, also given to `onError`),
|
package/dist/analyze/access.d.ts
CHANGED
|
@@ -23,6 +23,11 @@ export interface RootFieldAccess {
|
|
|
23
23
|
verdict: AccessVerdict;
|
|
24
24
|
by: string[];
|
|
25
25
|
}>;
|
|
26
|
+
/**
|
|
27
|
+
* Variables in the field's arguments that were given no value: what
|
|
28
|
+
* they would write through relationship fields is not in `access`.
|
|
29
|
+
*/
|
|
30
|
+
unresolved?: string[];
|
|
26
31
|
}
|
|
27
32
|
export interface OperationAccess {
|
|
28
33
|
operation: "query" | "mutation" | "subscription";
|
|
@@ -39,9 +44,12 @@ export interface OperationAccess {
|
|
|
39
44
|
}
|
|
40
45
|
/**
|
|
41
46
|
* Who may run `operation`: each root field's verdict per principal, read
|
|
42
|
-
* off the same rules as `accessMatrix`.
|
|
43
|
-
*
|
|
47
|
+
* off the same rules as `accessMatrix`. A mutation's inputs are read for
|
|
48
|
+
* the relationship writes they make, each a part of its root field's
|
|
49
|
+
* verdict; an input given as a variable is read when `variables` has its
|
|
50
|
+
* value and listed in `unresolved` when it does not. Selections below a
|
|
51
|
+
* root field answer to their own types' READ rules (see the matrix).
|
|
44
52
|
*/
|
|
45
|
-
export declare function operationAccess(model: GraphModel, schema: GraphQLSchema, operation: OperationDefinitionNode, fragments: ReadonlyMap<string, FragmentDefinitionNode
|
|
53
|
+
export declare function operationAccess(model: GraphModel, schema: GraphQLSchema, operation: OperationDefinitionNode, fragments: ReadonlyMap<string, FragmentDefinitionNode>, variables?: Readonly<Record<string, unknown>>): OperationAccess;
|
|
46
54
|
/** The access matrix of `model`, in a stable order. */
|
|
47
55
|
export declare function accessMatrix(model: GraphModel, schema: GraphQLSchema): AccessEntry[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GraphModel } from '../model/types.js';
|
|
2
|
+
export type JsonSchema = Record<string, unknown>;
|
|
3
|
+
/**
|
|
4
|
+
* A JSON Schema per generated create and update input, by input type
|
|
5
|
+
* name (`PersonCreateInput`), describing the scalar fields a client may
|
|
6
|
+
* set and the rules on them. A pattern is anchored, since here it must
|
|
7
|
+
* match the whole value; a format is given both by name and, where it is
|
|
8
|
+
* an expression, as a pattern, since validators treat `format` as a
|
|
9
|
+
* remark by default.
|
|
10
|
+
*/
|
|
11
|
+
export declare function inputSchemas(model: GraphModel): Record<string, JsonSchema>;
|
package/dist/analyze/plans.d.ts
CHANGED
|
@@ -35,3 +35,11 @@ export declare function checkPlans(driver: LoraDriver, compiled: CompiledRead, o
|
|
|
35
35
|
* that start from keys (every mutation statement) must have none.
|
|
36
36
|
*/
|
|
37
37
|
export declare function scanExpands(statement: Statement, plan: QueryPlan): PlanFinding[];
|
|
38
|
+
/**
|
|
39
|
+
* Plan the statements a mutation ran. Every one starts from keys, so none
|
|
40
|
+
* may expand from a full scan (`scanExpands`); the engine's row estimate
|
|
41
|
+
* is held to `rowBudget` when one is given.
|
|
42
|
+
*/
|
|
43
|
+
export declare function checkStatements(driver: LoraDriver, statements: readonly Statement[], options?: {
|
|
44
|
+
rowBudget?: number;
|
|
45
|
+
}): Promise<PlanReport[]>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { LoraDriver } from '../driver.js';
|
|
2
|
+
import { GraphModel } from '../model/types.js';
|
|
3
|
+
/** A stored value that breaks a rule of its field. */
|
|
4
|
+
export interface StoredViolation {
|
|
5
|
+
type: string;
|
|
6
|
+
/** The node's `@key`; for a relationship property, the declaring node's. */
|
|
7
|
+
key: unknown;
|
|
8
|
+
field: string;
|
|
9
|
+
/**
|
|
10
|
+
* The rule, as in a request's `violations`; `normalize` for a value not
|
|
11
|
+
* in its stored form; `fulltext` for one a full-text index skips.
|
|
12
|
+
*/
|
|
13
|
+
rule: string;
|
|
14
|
+
message: string;
|
|
15
|
+
}
|
|
16
|
+
export interface StoredReport {
|
|
17
|
+
violations: StoredViolation[];
|
|
18
|
+
/**
|
|
19
|
+
* Nodes read per type, and relationships per `Owner.field` whose
|
|
20
|
+
* properties have rules; one with more than `limit` is read in part.
|
|
21
|
+
*/
|
|
22
|
+
scanned: Record<string, number>;
|
|
23
|
+
/** Types with more nodes than were read. */
|
|
24
|
+
partial: string[];
|
|
25
|
+
}
|
|
26
|
+
export declare function scanStored(driver: LoraDriver, model: GraphModel, options?: {
|
|
27
|
+
limit?: number;
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
}): Promise<StoredReport>;
|
|
@@ -42,4 +42,10 @@ export declare function fieldVerdict(ctx: CompileContext, node: NodeType, field:
|
|
|
42
42
|
kind: string;
|
|
43
43
|
authorization?: NodeType["authorization"];
|
|
44
44
|
}): Pick<AccessEntry, "verdict" | "by">;
|
|
45
|
+
/**
|
|
46
|
+
* The `@authentication` operation a relationship operation answers to: a
|
|
47
|
+
* connect is checked as CREATE_RELATIONSHIP on the field, a disconnect as
|
|
48
|
+
* DELETE_RELATIONSHIP. Edge reads and updates have none of their own.
|
|
49
|
+
*/
|
|
50
|
+
export declare function relationshipAuthentication(op: RelationshipOperation): AuthOperation | undefined;
|
|
45
51
|
export declare function relationshipVerdict(ctx: CompileContext, node: NodeType, field: Parameters<typeof relationshipRules>[1], op: RelationshipOperation): Pick<AccessEntry, "verdict" | "by">;
|