@loradb/lora-graphql 0.34.0 → 0.34.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 CHANGED
@@ -162,7 +162,7 @@ API:
162
162
  | `@mutation(operations: [CREATE, UPDATE, DELETE])` | type | Generated mutations; none without it |
163
163
  | `@subscription(operations: [CREATE, UPDATE, DELETE])` | type | Generated subscriptions; none without it |
164
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: { ... } }]`) |
165
+ | `@sortable` | field | Sort and paginate by this field (on a relationship property: `sort: [{ edge: { ... } }]`); `nulls: FIRST \| LAST` places a nullable field's nulls whichever the direction |
166
166
  | `@groupBy` | field | A grouping key of `<plural>Grouped(by:)` (needs `@query(aggregate: true)`) |
167
167
  | `@limit(default:, max:)` | type, interface, union, list relationship | Page size bounds |
168
168
  | `@length(min:, max:)` | String / ID field | Its length in code points (each item of a list) |
@@ -288,7 +288,12 @@ false`: festivals without a venue); as in every relationship filter, a
288
288
  error. With `cursorSecret` it is also signed (HMAC-SHA-256), and any
289
289
  cursor the server did not issue is rejected. Nested lists without a sort come in `@key` order.
290
290
  - Nulls sort last ascending and first descending, as in Cypher, and keyset
291
- pages handle them.
291
+ pages handle them. `@sortable(nulls: LAST)` (or `FIRST`) puts a nullable
292
+ field's nulls there whichever the direction, so `popularityScore: DESC`
293
+ starts with the biggest score. Such a page walks the field's RANGE index
294
+ and then the nulls, reading about one page of rows on any label size
295
+ (see the worked example under
296
+ [`@sortable`](https://loradb.com/docs/graphql/directives#sortable-nulls)).
292
297
  - Every list is bounded: `limit` / `first` default to `@limit(default:)`
293
298
  (global 25) and asking for more than `max` (global 100) is a
294
299
  `LIMIT_EXCEEDED` error, not a silent clamp.
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-B6CwL0qT.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-DVs4_koK.js";
6
+ import { d as Te } from "./diff-BnwuROV5.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-CplYvRoG.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",
@@ -122,6 +122,8 @@ export interface Pattern {
122
122
  export interface SortItem {
123
123
  expr: Expr;
124
124
  direction: "ASC" | "DESC";
125
+ /** `NULLS FIRST` / `NULLS LAST`; unset, Cypher's default for the direction. */
126
+ nulls?: "FIRST" | "LAST" | undefined;
125
127
  }
126
128
  export interface ReturnItem {
127
129
  expr: Expr;
@@ -5,11 +5,18 @@ import { Args } from './types.js';
5
5
  export interface SortKey {
6
6
  field: ScalarField;
7
7
  direction: "ASC" | "DESC";
8
+ /**
9
+ * Where nulls go (`@sortable(nulls:)`), flipped with the direction on a
10
+ * backward page. Unset: Cypher's order, last ascending, first descending.
11
+ */
12
+ nulls?: "FIRST" | "LAST" | undefined;
8
13
  /** A relationship property (`sort: [{ edge: { ... } }]`), not a node field. */
9
14
  edge?: boolean;
10
15
  /** For a computed (`@cypher`) field: the variable holding its value. */
11
16
  computed?: string;
12
17
  }
18
+ /** Whether `k`'s nulls come after every value in its order. */
19
+ export declare function nullsLast(k: SortKey): boolean;
13
20
  export declare function resolveLimit(value: unknown, limit: PageLimit, argument: string): number;
14
21
  /**
15
22
  * The sort keys for `value`. Connections (`tieBreak`) always end on a
@@ -37,7 +44,12 @@ export declare function cursorValues(variable: string, sort: SortKey[], edge?: s
37
44
  export declare function leadBound(k: SortKey): boolean;
38
45
  /**
39
46
  * Rows strictly after `values` in `sort` order, written out term by term
40
- * (`[a, b] > $list` silently matches nothing in LoraDB). Null ordering
41
- * follows Cypher: nulls last ascending, first descending.
47
+ * (`[a, b] > $list` silently matches nothing in LoraDB). Nulls go where
48
+ * each key puts them (`nullsLast`).
49
+ *
50
+ * A non-null lead value also bounds the lead (`p <= $v AND (...)`) so the
51
+ * planner can seek it. When a nullable lead's nulls come last they are a
52
+ * disjunct of their own, `(p <= $v AND (...)) OR p IS NULL`, which LoraDB
53
+ * walks as the range index and then the label's nulls (G-77).
42
54
  */
43
55
  export declare function keysetPredicate(ctx: CompileContext, variable: string, sort: SortKey[], values: unknown[], edge?: string): Expr | undefined;
@@ -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-DVs4_koK.js";
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-CplYvRoG.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-B6CwL0qT.js.map
134
+ //# sourceMappingURL=diff-BnwuROV5.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"diff-B6CwL0qT.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":";;AA6CO,SAASA,EACdC,GACAC,GACAC,IAAwB,CAAA,GACZ;AACZ,QAAMC,IAAWC,EAAWJ,GAAQE,CAAO,GACrCG,IAAWD,EAAWH,GAAOC,CAAO,GAEpCI,IAAKC,GACLC,IAAU,IAAI,IAAIC,EAAkBN,CAAQ,EAAE,IAAI,CAACO,MAAM,CAACJ,EAAGI,CAAC,GAAGA,CAAC,CAAC,CAAC,GACpEC,IAAU,IAAI,IAAIF,EAAkBJ,CAAQ,EAAE,IAAI,CAACK,MAAM,CAACJ,EAAGI,CAAC,GAAGA,CAAC,CAAC,CAAC,GACpEE,IAAS,CAAC,GAAGD,CAAO,EACvB,OAAO,CAAC,CAACE,CAAC,MAAM,CAACL,EAAQ,IAAIK,CAAC,CAAC,EAC/B,IAAI,CAAC,CAAA,EAAGH,CAAC,MAAMA,CAAC,GACbI,IAAO,CAAC,GAAGN,CAAO,EAAE,OAAO,CAAC,CAACK,CAAC,MAAM,CAACF,EAAQ,IAAIE,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA,EAAGH,CAAC,MAAMA,CAAC,GAErEK,IAAmD,CAAA,GACnDC,IAAkB,CAAA;AACxB,EAAAC,EAAWd,GAAUE,GAAUU,GAAYC,CAAK;AAChD,aAAWN,KAAKI;AACd,IAAAC,EAAW,KAAK;AAAA,MACd,MAAM,QAAQL,EAAE,SAAS,eAAe,eAAe,OAAO,MAAMA,EAAE,KAAK,QAAQ,MAAM,IAAI,CAAC;AAAA,MAC9F,aAAa;AAAA,MACb,QAAQ,qBAAqBA,EAAE,MAAM;AAAA,IAAA,CACtC;AAEH,aAAWA,KAAKE;AACd,IAAAG,EAAW,KAAK;AAAA,MACd,MAAMG,EAAeR,CAAC;AAAA,MACtB,aAAa;AAAA,MACb,QAAQA,EAAE;AAAA,IAAA,CACX;AAGH,QAAMS,IAAMC,EAAWC,EAAalB,CAAQ,GAAGkB,EAAahB,CAAQ,CAAC;AACrE,SAAAiB,EAAYnB,GAAUE,GAAUc,GAAKH,CAAK,GACnC,EAAE,UAAU,EAAE,QAAAJ,GAAQ,MAAAE,GAAM,YAAAC,EAAA,GAAc,KAAAI,GAAK,OAAAH,EAAA;AACxD;AAGA,SAASC,EACPjB,GACAC,GACAc,GACAC,GACA;AACA,aAAW,CAACO,GAAUC,CAAO,KAAKxB,EAAO,OAAO;AAC9C,UAAMyB,IAAUxB,EAAM,MAAM,IAAIsB,CAAQ;AACxC,QAAI,CAACE,GAAS;AACZ,MAAAT,EAAM;AAAA,QACJ,GAAGO,CAAQ,sBAAsBC,EAAQ,OAAO,CAAC,CAAC;AAAA,MAAA;AAEpD;AAAA,IACF;AACA,UAAME,IAAWF,EAAQ,OAAO,CAAC,GAC3BG,IAAWF,EAAQ,OAAO,CAAC;AACjC,IAAIC,MAAaC,KACfZ,EAAW,KAAK;AAAA,MACd,MAAM,YAAYa,EAAKF,CAAQ,CAAC,WAAWE,EAAKD,CAAQ,CAAC,aAAaC,EAAKF,CAAQ,CAAC;AAAA,MACpF,aAAa;AAAA,MACb,QAAQ,GAAGH,CAAQ,yBAAyBG,CAAQ,OAAOC,CAAQ;AAAA,IAAA,CACpE;AAEH,UAAME,IAAYC,EAAQN,EAAQ,MAAM,GAClCO,IAAYD,EAAQL,EAAQ,MAAM;AACxC,eAAW,CAACO,GAAWC,CAAQ,KAAKJ,GAAW;AAC7C,YAAMK,IAAWH,EAAU,IAAIC,CAAS;AAQxC,UAPIE,KAAYA,EAAS,aAAaD,EAAS,YAC7ClB,EAAW,KAAK;AAAA,QACd,MAAM,YAAYa,EAAKD,CAAQ,CAAC,aAAaC,EAAKK,EAAS,QAAQ,CAAC,sBAAsBL,EAAKM,EAAS,QAAQ,CAAC,QAAQN,EAAKK,EAAS,QAAQ,CAAC,aAAaL,EAAKK,EAAS,QAAQ,CAAC;AAAA,QACpL,aAAa;AAAA,QACb,QAAQ,GAAGV,CAAQ,IAAIS,CAAS,iBAAiBE,EAAS,QAAQ,mBAAmBD,EAAS,QAAQ;AAAA,MAAA,CACvG,GAEC,CAACC,GAAU;AACb,cAAMC,IAAU,CAAC,GAAGJ,EAAU,OAAA,CAAQ,EAAE;AAAA,UACtC,CAACK,MAAMA,EAAE,aAAaH,EAAS,YAAY,CAACJ,EAAU,IAAIO,EAAE,IAAI;AAAA,QAAA;AAElE,QAAID,KACFnB,EAAM;AAAA,UACJ,GAAGO,CAAQ,IAAIS,CAAS,WAAWT,CAAQ,IAAIY,EAAQ,IAAI,6BAA6BF,EAAS,QAAQ;AAAA,QAAA;AAAA,MAG/G;AAAA,IACF;AAAA,EACF;AACF;AAQA,SAASX,EACPtB,GACAC,GACAkB,GACAH,GACA;AACA,QAAMqB,IAAS,CAACC,MACd,IAAI,IAAsD;AAAA,IACxD,GAAG,CAAC,GAAGA,EAAM,KAAK,EAAE,IAAI,CAAC,CAACC,GAAGC,CAAC,MAAM,CAACD,GAAGC,EAAE,MAAM,CAAU;AAAA,IAC1D,GAAG,CAAC,GAAGF,EAAM,sBAAsB,EAAE;AAAA,MACnC,CAAC,CAACC,GAAGC,CAAC,MAAM,CAACD,GAAGC,EAAE,MAAM;AAAA,IAAA;AAAA,EAC1B,CACD,GACGC,IAAMJ,EAAOrC,CAAM;AACzB,MAAI0C,IAAW;AACf,aAAW,CAACC,GAAOC,CAAM,KAAKP,EAAOpC,CAAK,GAAG;AAC3C,UAAM4B,IAAYY,EAAI,IAAIE,CAAK;AAC/B,QAAKd;AACL,iBAAWO,KAAKQ,EAAO,UAAU;AAC/B,cAAMC,IAAMhB,EAAU,IAAIO,EAAE,IAAI;AAChC,YAAIA,EAAE,SAAS,YAAYS,GAAK,SAAS,SAAU;AACnD,cAAM,EAAE,WAAAC,GAAW,SAAAC,EAAA,IAAYC;AAAA,UAC7BH,EAAI;AAAA,UACJT,EAAE;AAAA,QAAA;AAEJ,mBAAWa,KAAQH;AACjB,UAAAJ,KACAvB,EAAI,SAAS,KAAK;AAAA,YAChB,MAAM;AAAA,YACN,aAAa,GAAGwB,CAAK,IAAIP,EAAE,IAAI,KAAKa,CAAI;AAAA,UAAA,CACzC;AAEH,mBAAWA,KAAQF;AACjB,UAAAL,KACAvB,EAAI,UAAU,KAAK;AAAA,YACjB,MAAM;AAAA,YACN,aAAa,GAAGwB,CAAK,IAAIP,EAAE,IAAI,KAAKa,CAAI;AAAA,UAAA,CACzC;AAAA,MAEL;AAAA,EACF;AACA,EAAIP,IAAW,KACb1B,EAAM;AAAA,IACJ,GAAG0B,MAAa,IAAI,0BAA0B,GAAGA,CAAQ,wBAAwB;AAAA,EAAA;AAGvF;AAEA,SAASZ,EAAQc,GAA8D;AAC7E,QAAMM,wBAAU,IAAA;AAChB,aAAWd,KAAKQ,EAAO,OAAA,EAAU,CAAIR,EAAE,SAAS,YAAUc,EAAI,IAAId,EAAE,MAAMA,CAAC;AAC3E,SAAOc;AACT;AAEA,SAAS7B,EAAaiB,GAAkC;AACtD,QAAMa,IAAQ,MAAM,QAAQ,OAAO,IAAI,MAAM,eAAe,CAAC;AAC7D,SAAOC,EAAYd,GAAO;AAAA,IACxB,gBAAgB,MAAM,MAAM;AAAA,IAC5B,eAAea;AAAA,IACf,iBAAiBA;AAAA,IACjB,WAAW,MAAM;AACf,YAAM,IAAI,MAAM,eAAe;AAAA,IACjC;AAAA,IACA,oBAAoBA;AAAA,IACpB,eAAe,MAAM;AAAA,IACrB,aAAaA;AAAA,IACb,aAAaA;AAAA,IACb,eAAeA;AAAA,IACf,iBAAiBA;AAAA,EAAA,CAClB;AACH;AAOA,SAAS/B,EACPpB,GACAC,GACmB;AAEnB,QAAMoD,IAAMC;AAIZ,MAAI,CAACD,EAAI,uBAAuB,CAACA,EAAI;AACnC,WAAO;AAAA,MACL,UAAU;AAAA,QACR;AAAA,UACE,MAAM;AAAA,UACN,aACE;AAAA,QAAA;AAAA,MACJ;AAAA,MAEF,WAAW,CAAA;AAAA,IAAC;AAGhB,QAAME,IAAO,CAACC,OAA8C;AAAA,IAC1D,MAAMA,EAAE;AAAA,IACR,aAAaA,EAAE;AAAA,EAAA;AAEjB,SAAO;AAAA,IACL,UAAUH,EAAI,oBAAoBrD,GAAQC,CAAK,EAAE,IAAIsD,CAAI;AAAA,IACzD,WAAWF,EAAI,qBAAqBrD,GAAQC,CAAK,EAAE,IAAIsD,CAAI;AAAA,EAAA;AAE/D;"}
1
+ {"version":3,"file":"diff-BnwuROV5.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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"}
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-DVs4_koK.js";
2
- import { d as O } from "./diff-B6CwL0qT.js";
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-CplYvRoG.js";
2
+ import { d as O } from "./diff-BnwuROV5.js";
3
3
  export {
4
4
  r as DEFAULT_GUARDS,
5
5
  e as LORA_GRAPHQL_ERROR_CODES,