@loradb/lora-graphql 0.17.0 → 0.18.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 +21 -7
- package/dist/cli.js +2 -2
- package/dist/{diff-8nsBSSNP.js → diff-fpWH5KUa.js} +2 -2
- package/dist/{diff-8nsBSSNP.js.map → diff-fpWH5KUa.js.map} +1 -1
- package/dist/{driver-BmnRtkpd.js → driver-CQj9gkvl.js} +1111 -1020
- package/dist/driver-CQj9gkvl.js.map +1 -0
- package/dist/index.js +2 -2
- package/dist/lora-graphql.d.ts +16 -5
- package/dist/testing.js +1 -1
- package/package.json +2 -2
- package/dist/driver-BmnRtkpd.js.map +0 -1
package/README.md
CHANGED
|
@@ -389,7 +389,14 @@ mutation {
|
|
|
389
389
|
is an error that writes nothing, and an empty `where` is refused.
|
|
390
390
|
- **`upsertFestivals`** creates the inputs whose key is new and updates the
|
|
391
391
|
rest; fields required on create are required only for new keys. A key the
|
|
392
|
-
caller may not see
|
|
392
|
+
caller may not see is never updated, and never reported as taken: it gets
|
|
393
|
+
the answer a free key would (see below).
|
|
394
|
+
- **Creating under a hidden key** (`createFestivals`, `upsertFestivals`, a
|
|
395
|
+
nested `create`) answers as if the key were free: whatever error the free
|
|
396
|
+
key would get, and where it would be created, `FORBIDDEN` ("not allowed to
|
|
397
|
+
create"), never a `CONSTRAINT_VIOLATION` that would confirm the key
|
|
398
|
+
exists. A key held by a node the caller can read is still reported as
|
|
399
|
+
taken. See `docs/design/graphql-threat-model.md`.
|
|
393
400
|
- **Deletes** follow `onDelete`: `DETACH` (default) removes the
|
|
394
401
|
relationships, `CASCADE` deletes what the field reaches (checking the
|
|
395
402
|
caller may delete each node), `RESTRICT` refuses while related nodes
|
|
@@ -620,8 +627,12 @@ type Post
|
|
|
620
627
|
- Field-level `@authorization(validate:)` guards one field: reading it on a
|
|
621
628
|
row that fails is `FORBIDDEN`, filtering by it only matches rows that
|
|
622
629
|
pass, sorting or aggregating by it is refused, and writing it checks the
|
|
623
|
-
rule.
|
|
624
|
-
|
|
630
|
+
rule. A write is what the input sets: a create that leaves the field out
|
|
631
|
+
is not checked against it, even when `@default` or `@populatedBy` fills
|
|
632
|
+
it, so a CREATE rule can keep a `verified: Boolean! @default(value: false)`
|
|
633
|
+
settable by admins only while anyone creates the node. Field-level
|
|
634
|
+
`@authentication` also guards filtering, sorting and aggregating on the
|
|
635
|
+
field.
|
|
625
636
|
- Reading a guarded field is checked per row, token or not: without the
|
|
626
637
|
token a rule needs, each row reads the field as `UNAUTHENTICATED`. The
|
|
627
638
|
statement has the same shape either way, so `compile()`, `explain()`,
|
|
@@ -693,7 +704,10 @@ operations; the CLI does it in CI.
|
|
|
693
704
|
**S3: compile once.** `lora.persist({ id: source })` parses and validates
|
|
694
705
|
persisted operations at startup, and `lora.execute({ id, variables, context })`
|
|
695
706
|
runs them with no parsing or validation. Ad hoc documents passed to
|
|
696
|
-
`execute({ source })` are cached too.
|
|
707
|
+
`execute({ source })` are cached too. A subscription runs the same way with
|
|
708
|
+
`lora.subscribe({ id | source, variables, context })`, which returns an
|
|
709
|
+
async iterable of results (end it with `context.signal`); `execute()` answers
|
|
710
|
+
a subscription with an error pointing there. Translation itself takes about 0.13 ms
|
|
697
711
|
for a nested page, and statement text depends only on the shape of the
|
|
698
712
|
input, so LoraDB's own plan cache is hit for every repeat.
|
|
699
713
|
|
|
@@ -920,7 +934,7 @@ which only the Node binding has, so the WASM binding serves reads.
|
|
|
920
934
|
| `maskErrors` | production | Clients get `DATABASE_ERROR` and an `id` only |
|
|
921
935
|
| `onError` | | Receives each database error's detail and `id` |
|
|
922
936
|
| `guards` | see below | Document limits; `false` turns them off |
|
|
923
|
-
| `persistedOnly` | false | `execute()`
|
|
937
|
+
| `persistedOnly` | false | `execute()`, `subscribe()` run persisted ops only |
|
|
924
938
|
| `budget` | | Cost limit per request, from the context |
|
|
925
939
|
| `onCost` | | Each root field's estimate, total and limit |
|
|
926
940
|
| `onStatementEnd` | | Duration, rows and error of every statement call |
|
|
@@ -931,14 +945,14 @@ Errors carry `extensions.code`: `BAD_USER_INPUT`, `INVALID_CURSOR`,
|
|
|
931
945
|
`LIMIT_EXCEEDED`, `COST_EXCEEDED`, `UNAUTHENTICATED`, `FORBIDDEN`,
|
|
932
946
|
`NOT_FOUND`, `CONSTRAINT_VIOLATION` (with `type` and `field`),
|
|
933
947
|
`DATABASE_ERROR` (with an `id`, also given to `onError`) and
|
|
934
|
-
`PERSISTED_QUERY_ONLY` (`execute()` got a document under `persistedOnly`). An invalid SDL
|
|
948
|
+
`PERSISTED_QUERY_ONLY` (`execute()` or `subscribe()` got a document under `persistedOnly`). An invalid SDL
|
|
935
949
|
throws one `ModelError` listing every problem, each located by type and
|
|
936
950
|
field.
|
|
937
951
|
|
|
938
952
|
### Security defaults
|
|
939
953
|
|
|
940
954
|
`maxCost` bounds the rows an operation touches; the document guards bound
|
|
941
|
-
the document before that. `execute()` and `persist()` apply them, and
|
|
955
|
+
the document before that. `execute()`, `subscribe()` and `persist()` apply them, and
|
|
942
956
|
`lora.validationRules()` / `lora.envelopPlugin()` bring them to any other
|
|
943
957
|
server (GraphQL Yoga takes the plugin as is):
|
|
944
958
|
|
package/dist/cli.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { stat as z, readdir as oe, readFile as ie, mkdir as re, writeFile as _ } from "node:fs/promises";
|
|
3
3
|
import { join as $, relative as ce } from "node:path";
|
|
4
4
|
import { parse as q, Kind as p, visit as H, print as Q, typeFromAST as ue, isNonNullType as F, isListType as le, getNamedType as fe, isEnumType as de, isInputObjectType as pe } from "graphql";
|
|
5
|
-
import { d as me } from "./diff-
|
|
6
|
-
import { m as V, d as he, M as ge, L as M, l as Y, f as J, o as Z, b as ve, i as we, r as Te, u as Ee } from "./driver-
|
|
5
|
+
import { d as me } from "./diff-fpWH5KUa.js";
|
|
6
|
+
import { m as V, d as he, M as ge, L as M, l as Y, f as J, o as Z, b as ve, i as we, r as Te, u as Ee } from "./driver-CQj9gkvl.js";
|
|
7
7
|
const be = /* @__PURE__ */ new Set(["Query", "Mutation", "Subscription"]), W = {
|
|
8
8
|
coalesce: "@coalesce is not supported: it hides predicates from indexes; store a value instead",
|
|
9
9
|
subscriptionsAuthorization: "@subscriptionsAuthorization is not supported: SUBSCRIBE rules go in @authorization",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as w from "graphql";
|
|
2
|
-
import { b as $, i as b, r as E, j as N, n as i, k as S } from "./driver-
|
|
2
|
+
import { b as $, i as b, r as E, j as N, n as i, k as S } from "./driver-CQj9gkvl.js";
|
|
3
3
|
function k(t, n, o = {}) {
|
|
4
4
|
const s = $(t, o), r = $(n, o), l = N, u = new Map(b(s).map((e) => [l(e), e])), c = new Map(b(r).map((e) => [l(e), e])), p = [...c].filter(([e]) => !u.has(e)).map(([, e]) => e), h = [...u].filter(([e]) => !c.has(e)).map(([, e]) => e), d = [], f = [];
|
|
5
5
|
C(s, r, d, f);
|
|
@@ -96,4 +96,4 @@ function M(t, n) {
|
|
|
96
96
|
export {
|
|
97
97
|
k as d
|
|
98
98
|
};
|
|
99
|
-
//# sourceMappingURL=diff-
|
|
99
|
+
//# sourceMappingURL=diff-fpWH5KUa.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff-8nsBSSNP.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 { buildModel, type ModelOptions } from \"../model/build.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 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\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 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","typeName","oldNode","newNode","oldLabel","newLabel","name","oldFields","scalars","newFields","fieldName","oldField","newField","renamed","f","fields","out","model","never","buildSchema","lib","graphql","pick","c"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"diff-fpWH5KUa.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 { buildModel, type ModelOptions } from \"../model/build.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 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\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 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","typeName","oldNode","newNode","oldLabel","newLabel","name","oldFields","scalars","newFields","fieldName","oldField","newField","renamed","f","fields","out","model","never","buildSchema","lib","graphql","pick","c"],"mappings":"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"}
|