@loradb/lora-graphql 0.18.0 → 0.19.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 CHANGED
@@ -605,7 +605,7 @@ type Post
605
605
  with their `sub` and `:`. The claim must be a string, number or boolean;
606
606
  otherwise the rule denies. Pick a separator no `sub` contains: with `-`,
607
607
  user `a` could take `a-b-…`, the key space of user `a-b`. See
608
- [docs/design/graphql-claim-interpolation.md](../../docs/design/graphql-claim-interpolation.md).
608
+ [docs/design/graphql-threat-model.md](../../docs/design/graphql-threat-model.md).
609
609
  - **Claim tests run in JavaScript at compile time**, so an admin's
610
610
  statement carries no filter at all. Statements stay specialised and
611
611
  index-friendly.
@@ -915,9 +915,12 @@ Both need `@loradb/lora-node` and work with any test runner.
915
915
  ## Drivers, limits and errors
916
916
 
917
917
  `loraDriver(db)` adapts a `Database` from `@loradb/lora-node` or
918
- `@loradb/lora-wasm`. Single-statement reads stream; multi-statement reads
919
- run in one read-only transaction; mutations need interactive transactions,
920
- which only the Node binding has, so the WASM binding serves reads.
918
+ `@loradb/lora-wasm`. A read of one statement runs with `execute()` (with
919
+ lora-node, on a libuv worker, not the JavaScript thread), except a lookup
920
+ by `@key` that selects only stored fields, which streams its single row
921
+ synchronously; a read of several statements runs in one read-only
922
+ transaction. Mutations need interactive transactions, which only the Node
923
+ binding has, so the WASM binding serves reads.
921
924
  `explain()`, and so plan checks, need the Node binding too.
922
925
 
923
926
  | Option | Default | Meaning |
@@ -944,8 +947,10 @@ which only the Node binding has, so the WASM binding serves reads.
944
947
  Errors carry `extensions.code`: `BAD_USER_INPUT`, `INVALID_CURSOR`,
945
948
  `LIMIT_EXCEEDED`, `COST_EXCEEDED`, `UNAUTHENTICATED`, `FORBIDDEN`,
946
949
  `NOT_FOUND`, `CONSTRAINT_VIOLATION` (with `type` and `field`),
947
- `DATABASE_ERROR` (with an `id`, also given to `onError`) and
948
- `PERSISTED_QUERY_ONLY` (`execute()` or `subscribe()` got a document under `persistedOnly`). An invalid SDL
950
+ `DATABASE_ERROR` (with an `id`, also given to `onError`),
951
+ `PERSISTED_QUERY_ONLY` (`execute()` or `subscribe()` got a document under
952
+ `persistedOnly`) and `WRONG_OPERATION_TYPE` (`execute()` got a
953
+ subscription, or `subscribe()` a query or mutation). An invalid SDL
949
954
  throws one `ModelError` listing every problem, each located by type and
950
955
  field.
951
956
 
@@ -959,6 +964,7 @@ server (GraphQL Yoga takes the plugin as is):
959
964
  | Guard | Default | Limit |
960
965
  | --------------- | ---------- | ---------------------------------------- |
961
966
  | `maxDepth` | 12 | Field nesting, through fragments |
967
+ | `maxIntrospectionDepth` | 20 | Nesting under `__schema` / `__type` |
962
968
  | `maxAliases` | 30 | Aliased fields per document |
963
969
  | `maxRootFields` | 20 | Root fields per operation |
964
970
  | `maxTokens` | 5000 | Lexer tokens per document, while parsing |
@@ -1017,7 +1023,7 @@ Measured on LoraDB 0.15 over 20 000 festivals and 100 000 relationships
1017
1023
 
1018
1024
  | Rule | Why |
1019
1025
  | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ |
1020
- | Relationship filters as `size([… \| 1])`; aggregates of related values with `reduce` | No `OPTIONAL MATCH`; `EXISTS { }` does not parse; aggregates nest safely |
1026
+ | Relationship filters as `size([… \| 1])`; aggregates of related values with `reduce` | No `OPTIONAL MATCH`; aggregates nest safely |
1021
1027
  | `CALL { }` for nested lists, nested connections, `@cypher` and interface members | The only way to sort, limit and aggregate per parent |
1022
1028
  | Ordered by an always-present string: `WHERE s >= ""` | The planner walks the index in order and stops at the limit: 0.03 ms instead of 7 ms |
1023
1029
  | Mutation statements seek the key in their own `MATCH`, then expand | The plan no longer depends on the optimizer finding the seek: 0.06 ms per connect |
@@ -1042,7 +1048,7 @@ Measured on LoraDB 0.15 over 20 000 festivals and 100 000 relationships
1042
1048
  | `@authorization` rules evaluated in Cypher | Claim checks folded in JavaScript; node rules compiled |
1043
1049
  | You pick indexes | Inferred from the API, and verified with `explain()` |
1044
1050
  | Unbounded lists; complexity left to you | Every list bounded; a cost limit per operation |
1045
- | Subscriptions over CDC | `@subscription`, from library-made writes |
1051
+ | Subscriptions over CDC | `@subscription`, from library-made writes or, with `changeFeed: true`, every committed write |
1046
1052
  | Interfaces and unions with `UNION` | Per-member subqueries merged by sort, each using its own indexes |
1047
1053
  | Federation | Not supported |
1048
1054
 
@@ -1062,7 +1068,7 @@ before a following `SET`, list comparison (`[a, b] > $list`) and
1062
1068
  | An aggregate nested in a call (`head(collect(x))`, `collect(x)[0..2]`) is not aggregated | `WITH collect(x) AS c RETURN head(c)`; `reduce` for per-parent aggregates |
1063
1069
  | `max`, `sum` and `avg` over durations are wrong | Duration aggregates are folded with `reduce` |
1064
1070
  | Integer division returns a float; negative list slices (`l[..-1]`) return `[]` | `toInteger(a / b)` for Int fields; `l[..size(l) - n]` |
1065
- | `COUNT { … RETURN DISTINCT x }`, `EXISTS { }` and `UNION` inside `CALL` do not parse | `reduce` for distinct counts; comprehensions; per-member subqueries |
1071
+ | `COUNT { … RETURN DISTINCT x }` and `UNION` inside `CALL` do not parse | `reduce` for distinct counts; per-member subqueries |
1066
1072
 
1067
1073
  ## Development
1068
1074
 
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-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";
5
+ import { d as me } from "./diff-BH4Na9pC.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-CuXOoYWT.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",
@@ -27,6 +27,12 @@ export interface CompiledRead {
27
27
  cost: number;
28
28
  /** `read` for queries; `write` for @cypher mutations. */
29
29
  mode: "read" | "write";
30
+ /**
31
+ * At most one row, found by an exact @key seek, projecting only stored
32
+ * properties: no relationship, @cypher field or rule traversal. Cheap
33
+ * enough for a driver to run synchronously.
34
+ */
35
+ bounded?: boolean;
30
36
  }
31
37
  type Args = Record<string, unknown>;
32
38
  /** A connection value before its resolvers turn it into edges/pageInfo. */
@@ -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-CQj9gkvl.js";
2
+ import { b as $, i as b, r as E, j as N, n as i, k as S } from "./driver-CuXOoYWT.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-fpWH5KUa.js.map
99
+ //# sourceMappingURL=diff-BH4Na9pC.js.map
@@ -1 +1 @@
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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"}
1
+ {"version":3,"file":"diff-BH4Na9pC.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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"}