@loradb/lora-graphql 0.34.1 → 0.35.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
@@ -377,6 +377,76 @@ out. Mutations connect, create and disconnect per member:
377
377
  `events: { connect: { Concert: [{ key: "c1" }] } }`. A single relationship
378
378
  to a union holds one node across all members.
379
379
 
380
+ ### Interface and union connections
381
+
382
+ `<plural>Connection` pages an interface or union with cursors, like a
383
+ `@node` type's connection. It takes `where`, `sort`, `first`/`after` and
384
+ `last`/`before`, and `totalCount` counts every match across the members:
385
+
386
+ ```graphql
387
+ {
388
+ eventsConnection(sort: [{ starts: ASC }], first: 2) {
389
+ totalCount
390
+ pageInfo {
391
+ hasNextPage
392
+ endCursor
393
+ }
394
+ edges {
395
+ node {
396
+ __typename
397
+ key
398
+ title
399
+ ... on Concert {
400
+ band
401
+ }
402
+ ... on Exhibition {
403
+ artist
404
+ }
405
+ }
406
+ }
407
+ }
408
+ }
409
+ ```
410
+
411
+ Over two concerts and two exhibitions:
412
+
413
+ ```json
414
+ {
415
+ "eventsConnection": {
416
+ "totalCount": 4,
417
+ "pageInfo": {
418
+ "hasNextPage": true,
419
+ "endCursor": "eyJzIjoiRXZlbnQ6LHN0YXJ0czpBU0MiLCJ2IjpbMTEsIkNvbmNlcnQiLCJjMiJdfQ"
420
+ },
421
+ "edges": [
422
+ {
423
+ "node": {
424
+ "__typename": "Exhibition",
425
+ "key": "e1",
426
+ "title": "Modern art",
427
+ "artist": "Ann"
428
+ }
429
+ },
430
+ {
431
+ "node": {
432
+ "__typename": "Concert",
433
+ "key": "c2",
434
+ "title": "Jazz brunch",
435
+ "band": "Trio"
436
+ }
437
+ }
438
+ ]
439
+ }
440
+ }
441
+ ```
442
+
443
+ The order is the list root's: the requested fields, then the type name,
444
+ then the key. A cursor holds those values, so `after: endCursor` continues
445
+ with `e2` and `c1`. Each member reads its own page past the cursor, sorted
446
+ and limited, with its own indexes; the pages are merged and cut to
447
+ `first`. `totalCount` adds up one count per member, and `maxTotalCount`
448
+ caps the sum.
449
+
380
450
  ### Interface relationships
381
451
 
382
452
  An interface field under `@declareRelationship` is a relationship every
@@ -1360,9 +1430,14 @@ lora.onWrite((change) => {
1360
1430
  });
1361
1431
  ```
1362
1432
 
1363
- A read-set is label-level, and over-approximates: a `@cypher` statement
1364
- can read anything, so a read that ran one (and any mutation) carries
1365
- `opaque: true`, which every change affects. `@customResolver` fields
1433
+ A read-set is label-level, and over-approximates. A read-only `@cypher`
1434
+ field adds the labels and relationship types its statement's patterns
1435
+ name, its parent's label and the type it returns:
1436
+ `MATCH (a:ImageAsset {source: this.image}) RETURN a` reads `ImageAsset`.
1437
+ Where the text cannot tell (a procedure `CALL`, `MATCH (n)` with no label,
1438
+ `-->` with no type), `@cypher(reads: ["Festival", "PLAYS_AT"])` names
1439
+ type names, labels or relationship types; without it such a read, and
1440
+ any mutation, carries `opaque: true`, which every change affects. `@customResolver` fields
1366
1441
  are not seen; key the cache so they do not need to be.
1367
1442
 
1368
1443
  ## Subscriptions
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-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";
6
+ import { d as Te } from "./diff-Dqy1Lad3.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-Bxrt6tWk.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",
@@ -5,6 +5,15 @@ import { Clause, Expr } from '../cypher.js';
5
5
  import { CompiledRead, Args, Projection } from './types.js';
6
6
  /** `events(where, sort, limit)` over an interface's implementations or a union's members. */
7
7
  export declare function compileAbstractRoot(ctx: CompileContext, abstract: AbstractType, args: Args, fieldNodes: readonly FieldNode[]): CompiledRead;
8
+ /**
9
+ * `eventsConnection(first, after, last, before, where, sort)` over an
10
+ * interface's implementations or a union's members: each member's page
11
+ * (filtered, keyset-bounded, sorted and limited on its own, so each can
12
+ * use its indexes) merged in the list root's order (requested fields,
13
+ * then type name, then key), and `totalCount` summed across members.
14
+ * A cursor holds the sort values, the type name and the key.
15
+ */
16
+ export declare function compileAbstractConnection(ctx: CompileContext, abstract: AbstractType, args: Args, fieldNodes: readonly FieldNode[]): CompiledRead;
8
17
  /** A relationship field whose target is an interface or union. */
9
18
  export declare function projectPolymorphic(ctx: CompileContext, parent: string, rel: RelationshipField, key: string, args: Args, sets: SelectionSetNode[], rows: number): Projection;
10
19
  /**
@@ -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-CplYvRoG.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-Bxrt6tWk.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-BnwuROV5.js.map
134
+ //# sourceMappingURL=diff-Dqy1Lad3.js.map
@@ -1 +1 @@
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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"}
1
+ {"version":3,"file":"diff-Dqy1Lad3.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-CplYvRoG.js";
2
- import { d as O } from "./diff-BnwuROV5.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-Bxrt6tWk.js";
2
+ import { d as O } from "./diff-Dqy1Lad3.js";
3
3
  export {
4
4
  r as DEFAULT_GUARDS,
5
5
  e as LORA_GRAPHQL_ERROR_CODES,