@loradb/lora-graphql 0.23.0 → 0.24.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.
Files changed (85) hide show
  1. package/README.md +68 -13
  2. package/dist/analyze/access-lint.d.ts +7 -0
  3. package/dist/analyze/access.d.ts +13 -33
  4. package/dist/analyze/assert-schema.d.ts +19 -0
  5. package/dist/analyze/diff.d.ts +1 -1
  6. package/dist/analyze/plans.d.ts +1 -1
  7. package/dist/analyze/principals.d.ts +25 -0
  8. package/dist/analyze/verdicts.d.ts +51 -0
  9. package/dist/cli.js +2 -2
  10. package/dist/cli.js.map +1 -1
  11. package/dist/compile/cache.d.ts +1 -1
  12. package/dist/compile/read/abstract.d.ts +19 -0
  13. package/dist/compile/read/aggregates.d.ts +34 -0
  14. package/dist/compile/read/by-keys.d.ts +10 -0
  15. package/dist/compile/read/common.d.ts +30 -0
  16. package/dist/compile/read/connection.d.ts +26 -0
  17. package/dist/compile/read/cypher-field.d.ts +21 -0
  18. package/dist/compile/read/match.d.ts +11 -0
  19. package/dist/compile/read/project.d.ts +15 -0
  20. package/dist/compile/read/root.d.ts +5 -0
  21. package/dist/compile/read/search.d.ts +16 -0
  22. package/dist/compile/read/sort.d.ts +42 -0
  23. package/dist/compile/read/types.d.ts +61 -0
  24. package/dist/{diff-B9iDdELF.js → diff-K8-GSeKH.js} +2 -2
  25. package/dist/diff-K8-GSeKH.js.map +1 -0
  26. package/dist/{driver-C5dSoSD8.js → driver-CXKPLfkz.js} +10796 -10254
  27. package/dist/driver-CXKPLfkz.js.map +1 -0
  28. package/dist/execute/cypher-mutation.d.ts +1 -1
  29. package/dist/execute/mutate/adjust.d.ts +5 -0
  30. package/dist/execute/mutate/checks.d.ts +21 -0
  31. package/dist/execute/mutate/delete.d.ts +7 -0
  32. package/dist/execute/mutate/env.d.ts +73 -0
  33. package/dist/execute/mutate/errors.d.ts +7 -0
  34. package/dist/execute/mutate/fragments.d.ts +37 -0
  35. package/dist/execute/mutate/plan.d.ts +95 -0
  36. package/dist/execute/mutate/runner.d.ts +129 -0
  37. package/dist/execute/mutate/unique-together.d.ts +18 -0
  38. package/dist/execute/mutate.d.ts +2 -79
  39. package/dist/index.d.ts +10 -6
  40. package/dist/index.js +2 -2
  41. package/dist/lora-graphql.d.ts +18 -328
  42. package/dist/model/build/authorization.d.ts +31 -0
  43. package/dist/model/build/cypher-field.d.ts +11 -0
  44. package/dist/model/build/directive-args.d.ts +16 -0
  45. package/dist/model/build/limits.d.ts +18 -0
  46. package/dist/model/build/relationship-field.d.ts +11 -0
  47. package/dist/model/build/rule-checks.d.ts +23 -0
  48. package/dist/model/build/rule-references.d.ts +28 -0
  49. package/dist/model/build/scalar-field.d.ts +7 -0
  50. package/dist/model/build/search.d.ts +4 -0
  51. package/dist/model/build/shapes.d.ts +18 -0
  52. package/dist/model/build/unique-together.d.ts +5 -0
  53. package/dist/model/build.d.ts +3 -20
  54. package/dist/model/directives.d.ts +1 -1
  55. package/dist/model/types.d.ts +12 -2
  56. package/dist/options.d.ts +255 -0
  57. package/dist/results.d.ts +70 -0
  58. package/dist/runtime/custom-requires.d.ts +7 -0
  59. package/dist/runtime/database-error.d.ts +10 -0
  60. package/dist/runtime/documents.d.ts +35 -0
  61. package/dist/runtime/limits.d.ts +37 -0
  62. package/dist/runtime/request.d.ts +33 -0
  63. package/dist/runtime/timing.d.ts +13 -0
  64. package/dist/schema/aggregates.d.ts +17 -0
  65. package/dist/schema/base.d.ts +17 -0
  66. package/dist/schema/build.d.ts +3 -42
  67. package/dist/schema/filters.d.ts +10 -0
  68. package/dist/schema/hooks.d.ts +42 -0
  69. package/dist/schema/objects.d.ts +36 -0
  70. package/dist/schema/query.d.ts +8 -0
  71. package/dist/schema/resolvers.d.ts +13 -0
  72. package/dist/schema/sorts.d.ts +5 -0
  73. package/dist/schema/subscriptions.d.ts +7 -0
  74. package/dist/subscriptions/change-hub.d.ts +50 -0
  75. package/dist/subscriptions/compiles.d.ts +23 -0
  76. package/dist/subscriptions/events.d.ts +9 -0
  77. package/dist/subscriptions/filter-cost.d.ts +15 -0
  78. package/dist/subscriptions/subscriptions.d.ts +73 -0
  79. package/dist/testing.d.ts +4 -1
  80. package/dist/testing.js +34 -29
  81. package/dist/testing.js.map +1 -1
  82. package/package.json +2 -2
  83. package/dist/compile/read.d.ts +0 -102
  84. package/dist/diff-B9iDdELF.js.map +0 -1
  85. package/dist/driver-C5dSoSD8.js.map +0 -1
@@ -0,0 +1,10 @@
1
+ import { GraphQLInputObjectType, GraphQLInputType } from 'graphql';
2
+ import { GraphModel, NodeType, RelationshipField, ScalarField } from '../model/types.js';
3
+ import { BaseTypes } from './base.js';
4
+ export declare function buildFilters(model: GraphModel, base: BaseTypes): {
5
+ inputTypeOf: (f: ScalarField) => GraphQLInputType;
6
+ whereOf: (typeName: string) => GraphQLInputObjectType;
7
+ polymorphic: (rel: RelationshipField) => boolean;
8
+ connectionWhere: (node: NodeType, rel: RelationshipField) => GraphQLInputObjectType;
9
+ };
10
+ export type Filters = ReturnType<typeof buildFilters>;
@@ -0,0 +1,42 @@
1
+ import { GraphQLFieldResolver, GraphQLResolveInfo, GraphQLScalarType } from 'graphql';
2
+ import { RootKind } from '../compile/read/types.js';
3
+ import { AbstractType, CustomField, CypherField, NodeType, ScalarField, SearchIndex } from '../model/types.js';
4
+ import { MutationResolver } from './mutations.js';
5
+ /** One event of a generated subscription. */
6
+ export interface ChangeEvent {
7
+ operation: "CREATE" | "UPDATE" | "DELETE" | "CONNECT" | "DISCONNECT";
8
+ key: unknown;
9
+ /** When the write was committed, ISO-8601. */
10
+ timestamp?: string | undefined;
11
+ /** For CONNECT / DISCONNECT: the relationship and its other end. */
12
+ relationship?: {
13
+ field: string;
14
+ type: string;
15
+ relatedType: string;
16
+ relatedKey: unknown;
17
+ } | undefined;
18
+ /** Stored properties before the write (`previousState: true`). */
19
+ previous?: Record<string, unknown> | undefined;
20
+ }
21
+ export interface SchemaHooks {
22
+ resolveAbstract: (abstract: AbstractType, info: GraphQLResolveInfo, context: unknown) => Promise<unknown>;
23
+ subscribe: (node: NodeType, args: Record<string, unknown>, context: unknown) => AsyncIterable<ChangeEvent>;
24
+ resolveChangedNode: (node: NodeType, event: ChangeEvent, info: GraphQLResolveInfo, context: unknown) => Promise<unknown>;
25
+ /**
26
+ * A `previousState` value as the subscriber may see it: throws when
27
+ * field-level @authentication refuses it, applies masks by the claims.
28
+ */
29
+ previousValue: (node: NodeType, field: ScalarField, value: unknown, context: unknown) => unknown;
30
+ /** Implementations of custom scalars, by name (the scalars option). */
31
+ scalars?: Readonly<Record<string, GraphQLScalarType>> | undefined;
32
+ /** The resolver of a `@customResolver` field (from the resolvers option). */
33
+ customResolver: (node: NodeType, field: CustomField) => GraphQLFieldResolver<Record<string, unknown>, unknown>;
34
+ resolveSearch: (node: NodeType, index: SearchIndex, info: GraphQLResolveInfo, context: unknown, connection: boolean) => Promise<unknown>;
35
+ resolveRoot: RootResolver;
36
+ resolveNode: NodeResolver;
37
+ resolveCypher: CypherRootResolver;
38
+ resolveMutation: MutationResolver;
39
+ }
40
+ export type CypherRootResolver = (field: CypherField, info: GraphQLResolveInfo, context: unknown) => Promise<unknown>;
41
+ export type RootResolver = (kind: RootKind, node: NodeType, info: GraphQLResolveInfo, context: unknown) => Promise<unknown>;
42
+ export type NodeResolver = (id: string, info: GraphQLResolveInfo, context: unknown) => Promise<unknown>;
@@ -0,0 +1,36 @@
1
+ import { GraphQLInputObjectType, GraphQLInterfaceType, GraphQLObjectType, GraphQLUnionType, GraphQLFieldConfigArgumentMap, GraphQLInputType, GraphQLOutputType } from 'graphql';
2
+ import { RawConnection } from '../compile/read/types.js';
3
+ import { CypherField, GraphModel } from '../model/types.js';
4
+ import { AggregateFieldType } from './aggregates.js';
5
+ import { BaseTypes } from './base.js';
6
+ import { Filters } from './filters.js';
7
+ import { SchemaHooks } from './hooks.js';
8
+ import { Sorts } from './sorts.js';
9
+ export declare function buildObjects(model: GraphModel, hooks: SchemaHooks, base: BaseTypes, filters: Filters, sorts: Sorts, aggregateFieldType: AggregateFieldType): {
10
+ objects: Map<string, GraphQLObjectType<any, any>>;
11
+ connections: Map<string, GraphQLObjectType<any, any>>;
12
+ abstractTypes: Map<string, GraphQLInterfaceType | GraphQLUnionType>;
13
+ plainObjects: Map<string, GraphQLObjectType<any, any>>;
14
+ connectionResolvers: {
15
+ edges: (src: RawConnection) => {
16
+ __sort: string;
17
+ node: unknown;
18
+ properties?: unknown;
19
+ __cursor: unknown[];
20
+ }[];
21
+ pageInfo: (src: RawConnection) => {
22
+ hasNextPage: boolean;
23
+ hasPreviousPage: boolean;
24
+ startCursor: string | null;
25
+ endCursor: string | null;
26
+ };
27
+ totalCount: (src: RawConnection & {
28
+ __denied?: number;
29
+ }) => number;
30
+ };
31
+ listArgs: (target: string) => GraphQLFieldConfigArgumentMap;
32
+ connectionArgs: (target: string, where?: GraphQLInputType, sort?: GraphQLInputObjectType) => GraphQLFieldConfigArgumentMap;
33
+ cypherOutput: (f: CypherField) => GraphQLOutputType;
34
+ cypherArgs: (f: CypherField) => GraphQLFieldConfigArgumentMap;
35
+ };
36
+ export type ObjectTypes = ReturnType<typeof buildObjects>;
@@ -0,0 +1,8 @@
1
+ import { GraphQLFieldConfigMap } from 'graphql';
2
+ import { GraphModel } from '../model/types.js';
3
+ import { AggregateTypes } from './aggregates.js';
4
+ import { BaseTypes } from './base.js';
5
+ import { Filters } from './filters.js';
6
+ import { SchemaHooks } from './hooks.js';
7
+ import { ObjectTypes } from './objects.js';
8
+ export declare function buildQuery(model: GraphModel, hooks: SchemaHooks, base: BaseTypes, filters: Filters, objectTypes: ObjectTypes, aggregateTypes: AggregateTypes): GraphQLFieldConfigMap<unknown, unknown>;
@@ -0,0 +1,13 @@
1
+ import { GraphQLFieldResolver } from 'graphql';
2
+ import { RawConnection, RawEdge } from '../compile/read/types.js';
3
+ export type SortedEdge = RawEdge & {
4
+ __sort: string;
5
+ };
6
+ /** The rows of a page in the requested order. */
7
+ export declare function pageOf(src: RawConnection): RawEdge[];
8
+ /** Values under response keys: the compiler projects each alias. */
9
+ export declare const byResponseKey: GraphQLFieldResolver<Record<string, unknown>, unknown>;
10
+ /** A stored VECTOR as its numbers. */
11
+ export declare const vectorByResponseKey: GraphQLFieldResolver<Record<string, unknown>, unknown>;
12
+ /** Same, for nodes: fails on nodes a READ validate rule rejects. */
13
+ export declare const nodesByResponseKey: GraphQLFieldResolver<Record<string, unknown>, unknown>;
@@ -0,0 +1,5 @@
1
+ import { GraphQLInputObjectType } from 'graphql';
2
+ import { GraphModel } from '../model/types.js';
3
+ import { BaseTypes } from './base.js';
4
+ export declare function buildSorts(model: GraphModel, base: BaseTypes): Map<string, GraphQLInputObjectType>;
5
+ export type Sorts = ReturnType<typeof buildSorts>;
@@ -0,0 +1,7 @@
1
+ import { GraphQLFieldConfigMap } from 'graphql';
2
+ import { GraphModel } from '../model/types.js';
3
+ import { BaseTypes } from './base.js';
4
+ import { Filters } from './filters.js';
5
+ import { SchemaHooks } from './hooks.js';
6
+ import { ObjectTypes } from './objects.js';
7
+ export declare function buildSubscriptions(model: GraphModel, hooks: SchemaHooks, base: BaseTypes, filters: Filters, objectTypes: ObjectTypes): GraphQLFieldConfigMap<unknown, unknown>;
@@ -0,0 +1,50 @@
1
+ import { LoraDriver } from '../driver.js';
2
+ import { WriteChange } from '../execute/changes.js';
3
+ import { GraphModel } from '../model/types.js';
4
+ import { LoraGraphQLOptions } from '../options.js';
5
+ export interface ChangeHubOptions {
6
+ driver: LoraDriver;
7
+ model: GraphModel;
8
+ /** The `changeFeed` option. */
9
+ changeFeed: boolean | undefined;
10
+ /** The `maxQueuedChanges` option, defaulted. */
11
+ maxQueued: number;
12
+ onError: LoraGraphQLOptions["onError"];
13
+ /**
14
+ * The owning instance's public `onWrite`: `changes()` and the
15
+ * subscriptions register through it, like any other listener.
16
+ */
17
+ onWrite: (listener: (change: WriteChange) => void) => () => void;
18
+ }
19
+ export declare class ChangeHub {
20
+ #private;
21
+ constructor(options: ChangeHubOptions);
22
+ /** Called after every committed mutation with its exact write-set. */
23
+ onWrite(listener: (change: WriteChange) => void): () => void;
24
+ /**
25
+ * Register a consumer of committed writes: of the engine feed when it
26
+ * is on, else of this instance's mutations. Returns its removal.
27
+ */
28
+ listen(listener: (change: WriteChange) => void): () => unknown;
29
+ /**
30
+ * Committed writes as an async iterator, e.g. for a subscription
31
+ * resolver. Only writes made through this instance are seen. `return()`
32
+ * or aborting `signal` (already aborted included) ends it at once.
33
+ */
34
+ changes(options?: {
35
+ signal?: AbortSignal;
36
+ maxQueued?: number;
37
+ }): AsyncIterableIterator<WriteChange>;
38
+ /**
39
+ * An async iterator over the items a `register`ed listener receives,
40
+ * queued up to `maxQueued`; `register` returns the unregister call.
41
+ */
42
+ queue<T>(register: (listener: (item: T) => void) => () => void, options: {
43
+ signal?: AbortSignal;
44
+ maxQueued?: number;
45
+ }): AsyncIterableIterator<T>;
46
+ /** Hand a committed mutation's write-set to the `onWrite` listeners. */
47
+ emit(change: WriteChange): void;
48
+ /** Stop the engine change feed (with `changeFeed: true`). */
49
+ close(): void;
50
+ }
@@ -0,0 +1,23 @@
1
+ import { Statement } from '../driver.js';
2
+ /**
3
+ * A statement compiled once for a subscription, reused while its claims
4
+ * and the `$context` values it read are unchanged. `param` names the
5
+ * parameter each run fills in; `share` keys the reads subscribers share.
6
+ */
7
+ export interface SubscriptionCompile<C> {
8
+ claims: string;
9
+ statistics: number;
10
+ variables: string | undefined;
11
+ contextReads: Array<[string, unknown]>;
12
+ compiled: C;
13
+ param: string;
14
+ share: string | undefined;
15
+ }
16
+ /** A cached compile still holds for these `$context` values. */
17
+ export declare function sameContextReads(reads: Array<[string, unknown]>, context: unknown): boolean;
18
+ /**
19
+ * A compile made with `slot` as one parameter's value, reusable with that
20
+ * parameter filled in per run; undefined unless exactly one parameter
21
+ * holds the slot.
22
+ */
23
+ export declare function slotCompile<C>(compiled: C, slot: unknown, claims: string | undefined, statistics: number, variables: string | undefined, contextReads: Array<[string, unknown]>, statement?: Statement): SubscriptionCompile<C> | undefined;
@@ -0,0 +1,9 @@
1
+ import { WriteChange } from '../execute/changes.js';
2
+ import { NodeType } from '../model/types.js';
3
+ import { ChangeEvent } from '../schema/hooks.js';
4
+ /** The write an event came from, for reads shared across its subscribers. */
5
+ export declare const CHANGE: unique symbol;
6
+ /** On a CONNECT / DISCONNECT event: whether its node owns the field. */
7
+ export declare const SELF_OWNS: unique symbol;
8
+ /** A write's events for one node type, one per node, most specific first. */
9
+ export declare function changeEvents(change: WriteChange, node: NodeType): ChangeEvent[];
@@ -0,0 +1,15 @@
1
+ import { GraphModel, NodeType } from '../model/types.js';
2
+ export interface FilterCostEnv {
3
+ model: GraphModel;
4
+ /** Measured maximum degrees, by `Owner.field` (see `analyze()`). */
5
+ degrees: ReadonlyMap<string, number>;
6
+ /** The `maxSubscriptionFilterDepth` option. */
7
+ maxDepth: number;
8
+ }
9
+ /**
10
+ * Estimated rows one changed node's `where` check touches: each
11
+ * relationship filter scans the relationship (its measured degree, else
12
+ * its cardinality, else a page of the target), times what it nests.
13
+ * Nesting deeper than `maxSubscriptionFilterDepth` is LIMIT_EXCEEDED.
14
+ */
15
+ export declare function filterCost(env: FilterCostEnv, node: NodeType, where: Record<string, unknown> | null | undefined, depth: number): number;
@@ -0,0 +1,73 @@
1
+ import { GraphQLResolveInfo, GraphQLSchema } from 'graphql';
2
+ import { CompileContext } from '../compile/context.js';
3
+ import { CompiledRead } from '../compile/read/types.js';
4
+ import { SelectionContext } from '../compile/selection.js';
5
+ import { LoraDriver, QueryResult, Statement } from '../driver.js';
6
+ import { WriteChange } from '../execute/changes.js';
7
+ import { GraphModel, NodeType } from '../model/types.js';
8
+ import { ChangeEvent } from '../schema/hooks.js';
9
+ import { ChangeHub } from './change-hub.js';
10
+ /**
11
+ * What subscriptions need of the `LoraGraphQL` instance that owns them:
12
+ * its limits, and the request machinery they share with queries.
13
+ */
14
+ export interface SubscriptionHost {
15
+ model: GraphModel;
16
+ driver: LoraDriver;
17
+ changes: ChangeHub;
18
+ /** The `maxSubscriptions` option, defaulted. */
19
+ maxSubscriptions: number;
20
+ /** The `subscriptionScope` option, defaulted. */
21
+ subscriptionScope: (context: unknown) => object | undefined;
22
+ /** The `maxSubscriptionFilterDepth` option, defaulted. */
23
+ maxSubscriptionFilterDepth: number;
24
+ /** The `subscriptionTimeoutMs` option, defaulted. */
25
+ subscriptionTimeoutMs: number;
26
+ /** The executable schema. */
27
+ schema(): GraphQLSchema;
28
+ /** The request's verified claims. */
29
+ jwt(context: unknown): Record<string, unknown> | undefined;
30
+ /** A compile context for the request. */
31
+ context(base: SelectionContext, context: unknown): CompileContext;
32
+ /** Measured maximum degrees, by `Owner.field`. */
33
+ degrees(): ReadonlyMap<string, number>;
34
+ /** Bumped when statistics change. */
35
+ statisticsVersion(): number;
36
+ /** Charge `cost` to the operation; COST_EXCEEDED past its limit. */
37
+ charge(field: string, cost: number, context: unknown): void;
38
+ /** Run a compiled read as the root field `field`. */
39
+ run(field: string, compiled: CompiledRead, context: unknown, info?: GraphQLResolveInfo, change?: object, shareKey?: string): Promise<unknown>;
40
+ }
41
+ export declare class Subscriptions {
42
+ #private;
43
+ constructor(host: SubscriptionHost);
44
+ /**
45
+ * Events for one node type, from the exact write-sets of mutations made
46
+ * through this instance. Nodes the subscriber may not read are skipped;
47
+ * deletions of rule-protected types are only sent to subscribers that
48
+ * follow that key and could read the node before it went (checked in
49
+ * the deleting transaction).
50
+ */
51
+ subscribe(node: NodeType, args: Record<string, unknown>, context: unknown): AsyncIterableIterator<ChangeEvent>;
52
+ /**
53
+ * Inside a deleting transaction, before anything is deleted: which of
54
+ * the doomed nodes each subscriber following one of their keys may
55
+ * read, kept for the change's DELETE events. A node cannot be checked
56
+ * once it is gone, so a deletion nobody checked reaches nobody whose
57
+ * rules depend on the node.
58
+ */
59
+ probeDeletes(change: WriteChange, doomed: ReadonlyArray<{
60
+ node: NodeType;
61
+ keys: unknown[];
62
+ }>, run: (statement: Statement) => Promise<QueryResult>): Promise<void>;
63
+ /**
64
+ * A changed node for a subscriber. The read is compiled once per
65
+ * subscription (context), field node, claims and variables, with the
66
+ * key as its only varying parameter.
67
+ */
68
+ resolveByKey(node: NodeType, key: unknown, info: GraphQLResolveInfo, context: unknown, change?: object): Promise<unknown>;
69
+ /** `run` once per change for each distinct statement set. */
70
+ shared(change: object | undefined, statements: Statement[], run: () => Promise<QueryResult[]>,
71
+ /** Precomputed key of the statements, from a reused compile. */
72
+ shareKey?: string): Promise<QueryResult[]>;
73
+ }
package/dist/testing.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { DocumentNode, ExecutionResult, GraphQLSchema } from 'graphql';
2
2
  import { LoraDatabaseLike } from './driver.js';
3
- import { LoraGraphQL, LoraGraphQLOptions, StatementEvent } from './lora-graphql.js';
3
+ import { LoraGraphQL } from './lora-graphql.js';
4
+ import { LoraGraphQLOptions, StatementEvent } from './options.js';
4
5
  /** The node binding's database, as far as tests use it. */
5
6
  export type TestDatabase = LoraDatabaseLike & {
6
7
  execute(query: string, params?: never): Promise<unknown>;
@@ -54,6 +55,8 @@ export interface AccessExpectations {
54
55
  * - `connect Trip.members lou:tomorrowland → f1` (also `->`)
55
56
  * - `disconnect Trip.members lou:tomorrowland → f1`
56
57
  * - `update-edge Trip.members lou:tomorrowland → f1 {"rsvp": "GOING"}`
58
+ * (on a single relationship the edge is the current one, whichever
59
+ * node the entry names)
57
60
  *
58
61
  * Denied means `FORBIDDEN`, `UNAUTHENTICATED` or `NOT_FOUND` (a node the
59
62
  * caller cannot see); any other error is reported as a mismatch either way.
package/dist/testing.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { graphql as g } from "graphql";
2
- import { a as h, l as b } from "./driver-C5dSoSD8.js";
2
+ import { a as h, l as b } from "./driver-CXKPLfkz.js";
3
3
  async function T(l) {
4
4
  let u;
5
5
  try {
@@ -12,23 +12,23 @@ async function T(l) {
12
12
  const i = await u(), e = [], { seed: t, onStatement: a, ...c } = l, n = new h({
13
13
  ...c,
14
14
  driver: b(i),
15
- onStatement: (r) => {
16
- e.push(r), a?.(r);
15
+ onStatement: (s) => {
16
+ e.push(s), a?.(s);
17
17
  }
18
18
  });
19
19
  if (await n.assertSchema({ create: !0 }), typeof t == "function") await t(i);
20
20
  else
21
- for (const r of typeof t == "string" ? [t] : t ?? [])
22
- await i.execute(r);
23
- const o = n.getSchema(), s = (r, p = {}, d = {}) => g({ schema: o, source: r, variableValues: p, contextValue: d });
21
+ for (const s of typeof t == "string" ? [t] : t ?? [])
22
+ await i.execute(s);
23
+ const o = n.getSchema(), r = (s, p = {}, d = {}) => g({ schema: o, source: s, variableValues: p, contextValue: d });
24
24
  return {
25
25
  lora: n,
26
26
  db: i,
27
27
  schema: o,
28
28
  statements: e,
29
- run: s,
30
- data: async (r, p, d) => {
31
- const $ = await s(r, p, d);
29
+ run: r,
30
+ data: async (s, p, d) => {
31
+ const $ = await r(s, p, d);
32
32
  if ($.errors) throw $.errors[0];
33
33
  return $.data;
34
34
  },
@@ -82,7 +82,7 @@ async function D(l, u, i) {
82
82
  u
83
83
  ), o = await l.begin();
84
84
  try {
85
- const s = await l.execute({
85
+ const r = await l.execute({
86
86
  source: e,
87
87
  variables: t,
88
88
  context: {
@@ -90,16 +90,16 @@ async function D(l, u, i) {
90
90
  ...i.as ? { jwt: i.as } : {},
91
91
  transaction: o
92
92
  }
93
- }), r = s.errors?.[0];
94
- if (r) {
95
- const d = r.extensions?.code;
96
- return typeof d == "string" && v.has(d) ? { kind: "denied", message: `${d}: ${r.message}` } : {
93
+ }), s = r.errors?.[0];
94
+ if (s) {
95
+ const d = s.extensions?.code;
96
+ return typeof d == "string" && v.has(d) ? { kind: "denied", message: `${d}: ${s.message}` } : {
97
97
  kind: "error",
98
- message: `${String(d ?? "error")}: ${r.message}`
98
+ message: `${String(d ?? "error")}: ${s.message}`
99
99
  };
100
100
  }
101
- const p = s.data?.[c];
102
- return n && (p == null || !n(p)) ? { kind: "denied", message: "not visible, nothing written" } : a && s.data?.[c] == null ? { kind: "denied", message: "not visible" } : { kind: "allowed" };
101
+ const p = r.data?.[c];
102
+ return n && (p == null || !n(p)) ? { kind: "denied", message: "not visible, nothing written" } : a && r.data?.[c] == null ? { kind: "denied", message: "not visible" } : { kind: "allowed" };
103
103
  } finally {
104
104
  await o.rollback();
105
105
  }
@@ -112,9 +112,9 @@ function E(l, u) {
112
112
  throw new Error(
113
113
  "expected `<read|create|update|delete> Type key [json]` or `<connect|disconnect|update-edge> Type.field key → key [json]`"
114
114
  );
115
- const [, e, t, a, c, n, o] = i, s = l.model.nodes.get(t);
116
- if (!s) throw new Error(`${t} is not a @node type`);
117
- const r = o ? JSON.parse(o) : {}, p = `${s.key.customScalar ?? s.key.type}!`, d = t.charAt(0).toLowerCase() + t.slice(1), $ = s.plural.charAt(0).toUpperCase() + s.plural.slice(1), w = ["connect", "disconnect", "update-edge"].includes(
115
+ const [, e, t, a, c, n, o] = i, r = l.model.nodes.get(t);
116
+ if (!r) throw new Error(`${t} is not a @node type`);
117
+ const s = o ? JSON.parse(o) : {}, p = `${r.key.customScalar ?? r.key.type}!`, d = t.charAt(0).toLowerCase() + t.slice(1), $ = r.plural.charAt(0).toUpperCase() + r.plural.slice(1), w = ["connect", "disconnect", "update-edge"].includes(
118
118
  e
119
119
  );
120
120
  if (w !== (a !== void 0 && n !== void 0))
@@ -125,7 +125,7 @@ function E(l, u) {
125
125
  case "read":
126
126
  return {
127
127
  operation: e,
128
- source: `query($key: ${p}) { ${d}(${s.key.name}: $key) { ${s.key.name} } }`,
128
+ source: `query($key: ${p}) { ${d}(${r.key.name}: $key) { ${r.key.name} } }`,
129
129
  variables: { key: c },
130
130
  isRead: !0,
131
131
  root: d
@@ -134,15 +134,15 @@ function E(l, u) {
134
134
  return {
135
135
  operation: e,
136
136
  source: `mutation($input: [${t}CreateInput!]!) { create${$}(input: $input) { __typename } }`,
137
- variables: { input: [{ ...r, [s.key.name]: c }] },
137
+ variables: { input: [{ ...s, [r.key.name]: c }] },
138
138
  isRead: !1,
139
139
  root: `create${$}`
140
140
  };
141
141
  case "update":
142
142
  return {
143
143
  operation: e,
144
- source: `mutation($key: ${p}, $update: ${t}UpdateInput!) { update${t}(${s.key.name}: $key, update: $update) { ${d} { __typename } info { relationshipsDeleted } } }`,
145
- variables: { key: c, update: r },
144
+ source: `mutation($key: ${p}, $update: ${t}UpdateInput!) { update${t}(${r.key.name}: $key, update: $update) { ${d} { __typename } info { relationshipsDeleted } } }`,
145
+ variables: { key: c, update: s },
146
146
  isRead: !1,
147
147
  root: `update${t}`,
148
148
  wrote: (f) => f[d] != null
@@ -150,22 +150,27 @@ function E(l, u) {
150
150
  case "delete":
151
151
  return {
152
152
  operation: e,
153
- source: `mutation($key: ${p}) { delete${t}(${s.key.name}: $key) { nodesDeleted } }`,
153
+ source: `mutation($key: ${p}) { delete${t}(${r.key.name}: $key) { nodesDeleted } }`,
154
154
  variables: { key: c },
155
155
  isRead: !1,
156
156
  root: `delete${t}`,
157
157
  wrote: (f) => Number(f.nodesDeleted ?? 0) > 0
158
158
  };
159
159
  }
160
- const y = s.fields.get(a);
160
+ const y = r.fields.get(a);
161
161
  if (y?.kind !== "relationship")
162
162
  throw new Error(`${t}.${a} is not a relationship field`);
163
163
  const m = l.model.nodes.get(y.target)?.key.name ?? "key", k = e === "connect" ? {
164
- connect: y.list ? [{ [m]: n, ...o ? { edge: r } : {} }] : { [m]: n, ...o ? { edge: r } : {} }
165
- } : e === "disconnect" ? { disconnect: y.list ? [n] : !0 } : { update: [{ [m]: n, edge: r }] };
164
+ connect: y.list ? [{ [m]: n, ...o ? { edge: s } : {} }] : { [m]: n, ...o ? { edge: s } : {} }
165
+ } : e === "disconnect" ? { disconnect: y.list ? [n] : !0 } : (
166
+ // A single relationship has one edge: the update names no key.
167
+ {
168
+ update: y.list ? [{ [m]: n, edge: s }] : { edge: s }
169
+ }
170
+ );
166
171
  return {
167
172
  operation: e,
168
- source: `mutation($key: ${p}, $update: ${t}UpdateInput!) { update${t}(${s.key.name}: $key, update: $update) { ${d} { __typename } info { relationshipsDeleted } } }`,
173
+ source: `mutation($key: ${p}, $update: ${t}UpdateInput!) { update${t}(${r.key.name}: $key, update: $update) { ${d} { __typename } info { relationshipsDeleted } } }`,
169
174
  variables: { key: c, update: { [a]: k } },
170
175
  isRead: !1,
171
176
  root: `update${t}`,
@@ -1 +1 @@
1
- {"version":3,"file":"testing.js","sources":["../src/testing.ts"],"sourcesContent":["// Test helpers: an in-memory LoraDB with the schema asserted, and a plan\n// assertion. Import from `@loradb/lora-graphql/testing`; needs\n// `@loradb/lora-node`. Framework-agnostic: failures throw plain errors.\n\nimport {\n graphql,\n type DocumentNode,\n type ExecutionResult,\n type GraphQLSchema,\n} from \"graphql\";\nimport { loraDriver, type LoraDatabaseLike } from \"./driver.js\";\nimport {\n LoraGraphQL,\n type LoraGraphQLOptions,\n type StatementEvent,\n} from \"./lora-graphql.js\";\n\n/** The node binding's database, as far as tests use it. */\nexport type TestDatabase = LoraDatabaseLike & {\n execute(query: string, params?: never): Promise<unknown>;\n dispose?(): void;\n};\n\nexport interface TestLoraGraphQLOptions extends Omit<\n LoraGraphQLOptions,\n \"driver\"\n> {\n /** Cypher to run after the schema is asserted, or a function given the database. */\n seed?: string | string[] | ((db: TestDatabase) => Promise<void>);\n}\n\nexport interface TestLoraGraphQL {\n lora: LoraGraphQL;\n db: TestDatabase;\n schema: GraphQLSchema;\n /** Every statement run so far (clear with `statements.length = 0`). */\n statements: StatementEvent[];\n /** Execute a document; errors stay in the result. */\n run: (\n source: string,\n variables?: Record<string, unknown>,\n context?: Record<string, unknown>,\n ) => Promise<ExecutionResult>;\n /** Execute a document and return its data; throws on the first error. */\n data: <T = Record<string, unknown>>(\n source: string,\n variables?: Record<string, unknown>,\n context?: Record<string, unknown>,\n ) => Promise<T>;\n /** Release the database. */\n close: () => void;\n}\n\n/** A LoraGraphQL over a fresh in-memory database, schema asserted and seeded. */\nexport async function createTestLoraGraphQL(\n options: TestLoraGraphQLOptions,\n): Promise<TestLoraGraphQL> {\n let createDatabase: () => Promise<TestDatabase>;\n try {\n ({ createDatabase } = (await import(\"@loradb/lora-node\")) as unknown as {\n createDatabase: typeof createDatabase;\n });\n } catch {\n throw new Error(\n \"createTestLoraGraphQL needs @loradb/lora-node: npm install --save-dev @loradb/lora-node\",\n );\n }\n const db = await createDatabase();\n const statements: StatementEvent[] = [];\n const { seed, onStatement, ...rest } = options;\n const lora = new LoraGraphQL({\n ...rest,\n driver: loraDriver(db),\n onStatement: (event) => {\n statements.push(event);\n onStatement?.(event);\n },\n });\n await lora.assertSchema({ create: true });\n if (typeof seed === \"function\") await seed(db);\n else {\n for (const text of typeof seed === \"string\" ? [seed] : (seed ?? [])) {\n await db.execute(text);\n }\n }\n const schema = lora.getSchema();\n const run = (\n source: string,\n variables: Record<string, unknown> = {},\n contextValue: Record<string, unknown> = {},\n ) => graphql({ schema, source, variableValues: variables, contextValue });\n return {\n lora,\n db,\n schema,\n statements,\n run,\n data: async <T>(\n source: string,\n variables?: Record<string, unknown>,\n context?: Record<string, unknown>,\n ) => {\n const r = await run(source, variables, context);\n if (r.errors) throw r.errors[0];\n return r.data as T;\n },\n close: () => db.dispose?.(),\n };\n}\n\n/**\n * Assert that every statement of a query's root fields uses the index\n * access it was compiled for (no label scans where a seek was expected,\n * no mutating plans, no row-budget excess). Throws listing each finding.\n */\nexport async function expectSeeks(\n lora: LoraGraphQL,\n document: string | DocumentNode,\n variables: Record<string, unknown> = {},\n options: { rowBudget?: number; context?: unknown } = {},\n): Promise<void> {\n const fields = await lora.explain(document, variables, options);\n const findings = fields.flatMap((f) =>\n f.reports.flatMap((r) =>\n r.findings.map((x) => `${f.field}: ${x.message}\\n ${x.statement}`),\n ),\n );\n if (findings.length > 0) {\n throw new Error(\n `expected every root field to seek:\\n${findings.join(\"\\n\")}`,\n );\n }\n}\n\n/** The access `expectAccess` expects: entries allowed, and entries denied. */\nexport interface AccessExpectations {\n /** The claims to act as; undefined acts anonymously. */\n as: Record<string, unknown> | undefined;\n /** Other GraphQL context values the rules read (`$context.*`). */\n context?: Record<string, unknown>;\n allowed?: readonly string[];\n denied?: readonly string[];\n}\n\n/**\n * Assert who may do what, against the database: each entry runs as the\n * caller in a transaction that is always rolled back, and every mismatch\n * is reported at once. Entries:\n *\n * - `read Trip lou:tomorrowland` (denied: not visible, or refused)\n * - `create Trip lou:x {\"name\": \"X\"}` (other input fields as JSON)\n * - `update Trip lou:x {\"name\": \"Y\"}` and `delete Trip lou:x`\n * - `connect Trip.members lou:tomorrowland → f1` (also `->`)\n * - `disconnect Trip.members lou:tomorrowland → f1`\n * - `update-edge Trip.members lou:tomorrowland → f1 {\"rsvp\": \"GOING\"}`\n *\n * Denied means `FORBIDDEN`, `UNAUTHENTICATED` or `NOT_FOUND` (a node the\n * caller cannot see); any other error is reported as a mismatch either way.\n */\nexport async function expectAccess(\n target: LoraGraphQL | { lora: LoraGraphQL },\n expectations: AccessExpectations,\n): Promise<void> {\n const lora = target instanceof LoraGraphQL ? target : target.lora;\n const problems: string[] = [];\n const check = async (entry: string, allowed: boolean) => {\n let outcome: AccessOutcome;\n try {\n outcome = await tryAccess(lora, entry, expectations);\n } catch (err) {\n problems.push(\n `${entry}: ${err instanceof Error ? err.message : String(err)}`,\n );\n return;\n }\n if (outcome.kind === \"error\") {\n problems.push(`${entry}: failed with ${outcome.message}`);\n } else if (allowed && outcome.kind === \"denied\") {\n problems.push(\n `expected allowed, was denied: ${entry} (${outcome.message})`,\n );\n } else if (!allowed && outcome.kind === \"allowed\") {\n problems.push(`expected denied, was allowed: ${entry}`);\n }\n };\n for (const entry of expectations.allowed ?? []) await check(entry, true);\n for (const entry of expectations.denied ?? []) await check(entry, false);\n if (problems.length > 0) {\n const who = expectations.as ? JSON.stringify(expectations.as) : \"anonymous\";\n throw new Error(\n `access as ${who} differs in ${problems.length} ${problems.length === 1 ? \"entry\" : \"entries\"}:\\n ${problems.join(\"\\n \")}`,\n );\n }\n}\n\ntype AccessOutcome =\n | { kind: \"allowed\" }\n | { kind: \"denied\"; message: string }\n | { kind: \"error\"; message: string };\n\nconst DENIED = new Set([\"FORBIDDEN\", \"UNAUTHENTICATED\", \"NOT_FOUND\"]);\n\nasync function tryAccess(\n lora: LoraGraphQL,\n entry: string,\n expectations: AccessExpectations,\n): Promise<AccessOutcome> {\n const { source, variables, isRead, root, wrote } = accessOperation(\n lora,\n entry,\n );\n const tx = await lora.begin();\n try {\n const result = await lora.execute({\n source,\n variables,\n context: {\n ...expectations.context,\n ...(expectations.as ? { jwt: expectations.as } : {}),\n transaction: tx,\n },\n });\n const error = result.errors?.[0];\n if (error) {\n const code = (error.extensions as Record<string, unknown> | undefined)?.[\n \"code\"\n ];\n return typeof code === \"string\" && DENIED.has(code)\n ? { kind: \"denied\", message: `${code}: ${error.message}` }\n : {\n kind: \"error\",\n message: `${String(code ?? \"error\")}: ${error.message}`,\n };\n }\n const payload = (result.data as Record<string, unknown> | null)?.[root];\n if (\n wrote &&\n (payload == null || !wrote(payload as Record<string, unknown>))\n ) {\n return { kind: \"denied\", message: \"not visible, nothing written\" };\n }\n if (isRead) {\n const value = (result.data as Record<string, unknown> | null)?.[root];\n if (value == null) return { kind: \"denied\", message: \"not visible\" };\n }\n return { kind: \"allowed\" };\n } finally {\n await tx.rollback();\n }\n}\n\nfunction accessOperation(\n lora: LoraGraphQL,\n entry: string,\n): {\n operation: string;\n source: string;\n variables: Record<string, unknown>;\n isRead: boolean;\n root: string;\n /** How the payload says nothing was written: a hidden target answers no error. */\n wrote?: (payload: Record<string, unknown>) => boolean;\n} {\n const m =\n /^\\s*(read|create|update|delete|connect|disconnect|update-edge)\\s+([A-Za-z_][\\w]*)(?:\\.([A-Za-z_]\\w*))?\\s+(\\S+)(?:\\s*(?:→|->)\\s*(\\S+))?\\s*(\\{.*\\})?\\s*$/s.exec(\n entry,\n );\n if (!m) {\n throw new Error(\n \"expected `<read|create|update|delete> Type key [json]` or `<connect|disconnect|update-edge> Type.field key → key [json]`\",\n );\n }\n const [, operation, typeName, fieldName, key, other, json] = m as unknown as [\n string,\n string,\n string,\n string | undefined,\n string,\n string | undefined,\n string | undefined,\n ];\n const node = lora.model.nodes.get(typeName);\n if (!node) throw new Error(`${typeName} is not a @node type`);\n const input = json ? (JSON.parse(json) as Record<string, unknown>) : {};\n const keyType = `${node.key.customScalar ?? node.key.type}!`;\n const lower = typeName.charAt(0).toLowerCase() + typeName.slice(1);\n const upperPlural =\n node.plural.charAt(0).toUpperCase() + node.plural.slice(1);\n const relational = [\"connect\", \"disconnect\", \"update-edge\"].includes(\n operation,\n );\n if (relational !== (fieldName !== undefined && other !== undefined)) {\n throw new Error(\n relational\n ? `${operation} takes Type.field key → key`\n : `${operation} takes Type key`,\n );\n }\n switch (operation) {\n case \"read\":\n return {\n operation,\n source: `query($key: ${keyType}) { ${lower}(${node.key.name}: $key) { ${node.key.name} } }`,\n variables: { key },\n isRead: true,\n root: lower,\n };\n case \"create\":\n return {\n operation,\n source: `mutation($input: [${typeName}CreateInput!]!) { create${upperPlural}(input: $input) { __typename } }`,\n variables: { input: [{ ...input, [node.key.name]: key }] },\n isRead: false,\n root: `create${upperPlural}`,\n };\n case \"update\":\n return {\n operation,\n source: `mutation($key: ${keyType}, $update: ${typeName}UpdateInput!) { update${typeName}(${node.key.name}: $key, update: $update) { ${lower} { __typename } info { relationshipsDeleted } } }`,\n variables: { key, update: input },\n isRead: false,\n root: `update${typeName}`,\n wrote: (p) => p[lower] != null,\n };\n case \"delete\":\n return {\n operation,\n source: `mutation($key: ${keyType}) { delete${typeName}(${node.key.name}: $key) { nodesDeleted } }`,\n variables: { key },\n isRead: false,\n root: `delete${typeName}`,\n wrote: (p) => Number(p[\"nodesDeleted\"] ?? 0) > 0,\n };\n }\n const field = node.fields.get(fieldName!);\n if (field?.kind !== \"relationship\") {\n throw new Error(`${typeName}.${fieldName} is not a relationship field`);\n }\n const targetKey = lora.model.nodes.get(field.target)?.key.name ?? \"key\";\n const change =\n operation === \"connect\"\n ? {\n connect: field.list\n ? [{ [targetKey]: other, ...(json ? { edge: input } : {}) }]\n : { [targetKey]: other, ...(json ? { edge: input } : {}) },\n }\n : operation === \"disconnect\"\n ? { disconnect: field.list ? [other] : true }\n : { update: [{ [targetKey]: other, edge: input }] };\n return {\n operation,\n source: `mutation($key: ${keyType}, $update: ${typeName}UpdateInput!) { update${typeName}(${node.key.name}: $key, update: $update) { ${lower} { __typename } info { relationshipsDeleted } } }`,\n variables: { key, update: { [fieldName!]: change } },\n isRead: false,\n root: `update${typeName}`,\n // A hidden owner answers a null node; a disconnect of a pair the\n // caller may not touch removes nothing.\n wrote: (p) =>\n p[lower] != null &&\n (operation !== \"disconnect\" ||\n Number(\n (p[\"info\"] as Record<string, unknown> | undefined)?.[\n \"relationshipsDeleted\"\n ] ?? 0,\n ) > 0),\n };\n}\n"],"names":["createTestLoraGraphQL","options","createDatabase","db","statements","seed","onStatement","rest","lora","LoraGraphQL","loraDriver","event","text","schema","run","source","variables","contextValue","graphql","context","r","expectSeeks","document","findings","f","x","expectAccess","target","expectations","problems","check","entry","allowed","outcome","tryAccess","err","who","DENIED","isRead","root","wrote","accessOperation","tx","result","error","code","payload","m","operation","typeName","fieldName","key","other","json","node","input","keyType","lower","upperPlural","relational","p","field","targetKey","change"],"mappings":"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"}
1
+ {"version":3,"file":"testing.js","sources":["../src/testing.ts"],"sourcesContent":["// Test helpers: an in-memory LoraDB with the schema asserted, and a plan\n// assertion. Import from `@loradb/lora-graphql/testing`; needs\n// `@loradb/lora-node`. Framework-agnostic: failures throw plain errors.\n\nimport {\n graphql,\n type DocumentNode,\n type ExecutionResult,\n type GraphQLSchema,\n} from \"graphql\";\nimport { loraDriver, type LoraDatabaseLike } from \"./driver.js\";\nimport { LoraGraphQL } from \"./lora-graphql.js\";\nimport { type LoraGraphQLOptions, type StatementEvent } from \"./options.js\";\n\n/** The node binding's database, as far as tests use it. */\nexport type TestDatabase = LoraDatabaseLike & {\n execute(query: string, params?: never): Promise<unknown>;\n dispose?(): void;\n};\n\nexport interface TestLoraGraphQLOptions extends Omit<\n LoraGraphQLOptions,\n \"driver\"\n> {\n /** Cypher to run after the schema is asserted, or a function given the database. */\n seed?: string | string[] | ((db: TestDatabase) => Promise<void>);\n}\n\nexport interface TestLoraGraphQL {\n lora: LoraGraphQL;\n db: TestDatabase;\n schema: GraphQLSchema;\n /** Every statement run so far (clear with `statements.length = 0`). */\n statements: StatementEvent[];\n /** Execute a document; errors stay in the result. */\n run: (\n source: string,\n variables?: Record<string, unknown>,\n context?: Record<string, unknown>,\n ) => Promise<ExecutionResult>;\n /** Execute a document and return its data; throws on the first error. */\n data: <T = Record<string, unknown>>(\n source: string,\n variables?: Record<string, unknown>,\n context?: Record<string, unknown>,\n ) => Promise<T>;\n /** Release the database. */\n close: () => void;\n}\n\n/** A LoraGraphQL over a fresh in-memory database, schema asserted and seeded. */\nexport async function createTestLoraGraphQL(\n options: TestLoraGraphQLOptions,\n): Promise<TestLoraGraphQL> {\n let createDatabase: () => Promise<TestDatabase>;\n try {\n ({ createDatabase } = (await import(\"@loradb/lora-node\")) as unknown as {\n createDatabase: typeof createDatabase;\n });\n } catch {\n throw new Error(\n \"createTestLoraGraphQL needs @loradb/lora-node: npm install --save-dev @loradb/lora-node\",\n );\n }\n const db = await createDatabase();\n const statements: StatementEvent[] = [];\n const { seed, onStatement, ...rest } = options;\n const lora = new LoraGraphQL({\n ...rest,\n driver: loraDriver(db),\n onStatement: (event) => {\n statements.push(event);\n onStatement?.(event);\n },\n });\n await lora.assertSchema({ create: true });\n if (typeof seed === \"function\") await seed(db);\n else {\n for (const text of typeof seed === \"string\" ? [seed] : (seed ?? [])) {\n await db.execute(text);\n }\n }\n const schema = lora.getSchema();\n const run = (\n source: string,\n variables: Record<string, unknown> = {},\n contextValue: Record<string, unknown> = {},\n ) => graphql({ schema, source, variableValues: variables, contextValue });\n return {\n lora,\n db,\n schema,\n statements,\n run,\n data: async <T>(\n source: string,\n variables?: Record<string, unknown>,\n context?: Record<string, unknown>,\n ) => {\n const r = await run(source, variables, context);\n if (r.errors) throw r.errors[0];\n return r.data as T;\n },\n close: () => db.dispose?.(),\n };\n}\n\n/**\n * Assert that every statement of a query's root fields uses the index\n * access it was compiled for (no label scans where a seek was expected,\n * no mutating plans, no row-budget excess). Throws listing each finding.\n */\nexport async function expectSeeks(\n lora: LoraGraphQL,\n document: string | DocumentNode,\n variables: Record<string, unknown> = {},\n options: { rowBudget?: number; context?: unknown } = {},\n): Promise<void> {\n const fields = await lora.explain(document, variables, options);\n const findings = fields.flatMap((f) =>\n f.reports.flatMap((r) =>\n r.findings.map((x) => `${f.field}: ${x.message}\\n ${x.statement}`),\n ),\n );\n if (findings.length > 0) {\n throw new Error(\n `expected every root field to seek:\\n${findings.join(\"\\n\")}`,\n );\n }\n}\n\n/** The access `expectAccess` expects: entries allowed, and entries denied. */\nexport interface AccessExpectations {\n /** The claims to act as; undefined acts anonymously. */\n as: Record<string, unknown> | undefined;\n /** Other GraphQL context values the rules read (`$context.*`). */\n context?: Record<string, unknown>;\n allowed?: readonly string[];\n denied?: readonly string[];\n}\n\n/**\n * Assert who may do what, against the database: each entry runs as the\n * caller in a transaction that is always rolled back, and every mismatch\n * is reported at once. Entries:\n *\n * - `read Trip lou:tomorrowland` (denied: not visible, or refused)\n * - `create Trip lou:x {\"name\": \"X\"}` (other input fields as JSON)\n * - `update Trip lou:x {\"name\": \"Y\"}` and `delete Trip lou:x`\n * - `connect Trip.members lou:tomorrowland → f1` (also `->`)\n * - `disconnect Trip.members lou:tomorrowland → f1`\n * - `update-edge Trip.members lou:tomorrowland → f1 {\"rsvp\": \"GOING\"}`\n * (on a single relationship the edge is the current one, whichever\n * node the entry names)\n *\n * Denied means `FORBIDDEN`, `UNAUTHENTICATED` or `NOT_FOUND` (a node the\n * caller cannot see); any other error is reported as a mismatch either way.\n */\nexport async function expectAccess(\n target: LoraGraphQL | { lora: LoraGraphQL },\n expectations: AccessExpectations,\n): Promise<void> {\n const lora = target instanceof LoraGraphQL ? target : target.lora;\n const problems: string[] = [];\n const check = async (entry: string, allowed: boolean) => {\n let outcome: AccessOutcome;\n try {\n outcome = await tryAccess(lora, entry, expectations);\n } catch (err) {\n problems.push(\n `${entry}: ${err instanceof Error ? err.message : String(err)}`,\n );\n return;\n }\n if (outcome.kind === \"error\") {\n problems.push(`${entry}: failed with ${outcome.message}`);\n } else if (allowed && outcome.kind === \"denied\") {\n problems.push(\n `expected allowed, was denied: ${entry} (${outcome.message})`,\n );\n } else if (!allowed && outcome.kind === \"allowed\") {\n problems.push(`expected denied, was allowed: ${entry}`);\n }\n };\n for (const entry of expectations.allowed ?? []) await check(entry, true);\n for (const entry of expectations.denied ?? []) await check(entry, false);\n if (problems.length > 0) {\n const who = expectations.as ? JSON.stringify(expectations.as) : \"anonymous\";\n throw new Error(\n `access as ${who} differs in ${problems.length} ${problems.length === 1 ? \"entry\" : \"entries\"}:\\n ${problems.join(\"\\n \")}`,\n );\n }\n}\n\ntype AccessOutcome =\n | { kind: \"allowed\" }\n | { kind: \"denied\"; message: string }\n | { kind: \"error\"; message: string };\n\nconst DENIED = new Set([\"FORBIDDEN\", \"UNAUTHENTICATED\", \"NOT_FOUND\"]);\n\nasync function tryAccess(\n lora: LoraGraphQL,\n entry: string,\n expectations: AccessExpectations,\n): Promise<AccessOutcome> {\n const { source, variables, isRead, root, wrote } = accessOperation(\n lora,\n entry,\n );\n const tx = await lora.begin();\n try {\n const result = await lora.execute({\n source,\n variables,\n context: {\n ...expectations.context,\n ...(expectations.as ? { jwt: expectations.as } : {}),\n transaction: tx,\n },\n });\n const error = result.errors?.[0];\n if (error) {\n const code = (error.extensions as Record<string, unknown> | undefined)?.[\n \"code\"\n ];\n return typeof code === \"string\" && DENIED.has(code)\n ? { kind: \"denied\", message: `${code}: ${error.message}` }\n : {\n kind: \"error\",\n message: `${String(code ?? \"error\")}: ${error.message}`,\n };\n }\n const payload = (result.data as Record<string, unknown> | null)?.[root];\n if (\n wrote &&\n (payload == null || !wrote(payload as Record<string, unknown>))\n ) {\n return { kind: \"denied\", message: \"not visible, nothing written\" };\n }\n if (isRead) {\n const value = (result.data as Record<string, unknown> | null)?.[root];\n if (value == null) return { kind: \"denied\", message: \"not visible\" };\n }\n return { kind: \"allowed\" };\n } finally {\n await tx.rollback();\n }\n}\n\nfunction accessOperation(\n lora: LoraGraphQL,\n entry: string,\n): {\n operation: string;\n source: string;\n variables: Record<string, unknown>;\n isRead: boolean;\n root: string;\n /** How the payload says nothing was written: a hidden target answers no error. */\n wrote?: (payload: Record<string, unknown>) => boolean;\n} {\n const m =\n /^\\s*(read|create|update|delete|connect|disconnect|update-edge)\\s+([A-Za-z_][\\w]*)(?:\\.([A-Za-z_]\\w*))?\\s+(\\S+)(?:\\s*(?:→|->)\\s*(\\S+))?\\s*(\\{.*\\})?\\s*$/s.exec(\n entry,\n );\n if (!m) {\n throw new Error(\n \"expected `<read|create|update|delete> Type key [json]` or `<connect|disconnect|update-edge> Type.field key → key [json]`\",\n );\n }\n const [, operation, typeName, fieldName, key, other, json] = m as unknown as [\n string,\n string,\n string,\n string | undefined,\n string,\n string | undefined,\n string | undefined,\n ];\n const node = lora.model.nodes.get(typeName);\n if (!node) throw new Error(`${typeName} is not a @node type`);\n const input = json ? (JSON.parse(json) as Record<string, unknown>) : {};\n const keyType = `${node.key.customScalar ?? node.key.type}!`;\n const lower = typeName.charAt(0).toLowerCase() + typeName.slice(1);\n const upperPlural =\n node.plural.charAt(0).toUpperCase() + node.plural.slice(1);\n const relational = [\"connect\", \"disconnect\", \"update-edge\"].includes(\n operation,\n );\n if (relational !== (fieldName !== undefined && other !== undefined)) {\n throw new Error(\n relational\n ? `${operation} takes Type.field key → key`\n : `${operation} takes Type key`,\n );\n }\n switch (operation) {\n case \"read\":\n return {\n operation,\n source: `query($key: ${keyType}) { ${lower}(${node.key.name}: $key) { ${node.key.name} } }`,\n variables: { key },\n isRead: true,\n root: lower,\n };\n case \"create\":\n return {\n operation,\n source: `mutation($input: [${typeName}CreateInput!]!) { create${upperPlural}(input: $input) { __typename } }`,\n variables: { input: [{ ...input, [node.key.name]: key }] },\n isRead: false,\n root: `create${upperPlural}`,\n };\n case \"update\":\n return {\n operation,\n source: `mutation($key: ${keyType}, $update: ${typeName}UpdateInput!) { update${typeName}(${node.key.name}: $key, update: $update) { ${lower} { __typename } info { relationshipsDeleted } } }`,\n variables: { key, update: input },\n isRead: false,\n root: `update${typeName}`,\n wrote: (p) => p[lower] != null,\n };\n case \"delete\":\n return {\n operation,\n source: `mutation($key: ${keyType}) { delete${typeName}(${node.key.name}: $key) { nodesDeleted } }`,\n variables: { key },\n isRead: false,\n root: `delete${typeName}`,\n wrote: (p) => Number(p[\"nodesDeleted\"] ?? 0) > 0,\n };\n }\n const field = node.fields.get(fieldName!);\n if (field?.kind !== \"relationship\") {\n throw new Error(`${typeName}.${fieldName} is not a relationship field`);\n }\n const targetKey = lora.model.nodes.get(field.target)?.key.name ?? \"key\";\n const change =\n operation === \"connect\"\n ? {\n connect: field.list\n ? [{ [targetKey]: other, ...(json ? { edge: input } : {}) }]\n : { [targetKey]: other, ...(json ? { edge: input } : {}) },\n }\n : operation === \"disconnect\"\n ? { disconnect: field.list ? [other] : true }\n : // A single relationship has one edge: the update names no key.\n {\n update: field.list\n ? [{ [targetKey]: other, edge: input }]\n : { edge: input },\n };\n return {\n operation,\n source: `mutation($key: ${keyType}, $update: ${typeName}UpdateInput!) { update${typeName}(${node.key.name}: $key, update: $update) { ${lower} { __typename } info { relationshipsDeleted } } }`,\n variables: { key, update: { [fieldName!]: change } },\n isRead: false,\n root: `update${typeName}`,\n // A hidden owner answers a null node; a disconnect of a pair the\n // caller may not touch removes nothing.\n wrote: (p) =>\n p[lower] != null &&\n (operation !== \"disconnect\" ||\n Number(\n (p[\"info\"] as Record<string, unknown> | undefined)?.[\n \"relationshipsDeleted\"\n ] ?? 0,\n ) > 0),\n };\n}\n"],"names":["createTestLoraGraphQL","options","createDatabase","db","statements","seed","onStatement","rest","lora","LoraGraphQL","loraDriver","event","text","schema","run","source","variables","contextValue","graphql","context","r","expectSeeks","document","findings","f","x","expectAccess","target","expectations","problems","check","entry","allowed","outcome","tryAccess","err","who","DENIED","isRead","root","wrote","accessOperation","tx","result","error","code","payload","m","operation","typeName","fieldName","key","other","json","node","input","keyType","lower","upperPlural","relational","p","field","targetKey","change"],"mappings":";;AAmDA,eAAsBA,EACpBC,GAC0B;AAC1B,MAAIC;AACJ,MAAI;AACF,KAAC,EAAE,gBAAAA,EAAA,IAAoB,MAAM,OAAO,mBAAmB;AAAA,EAGzD,QAAQ;AACN,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAAA,EAEJ;AACA,QAAMC,IAAK,MAAMD,EAAA,GACXE,IAA+B,CAAA,GAC/B,EAAE,MAAAC,GAAM,aAAAC,GAAa,GAAGC,MAASN,GACjCO,IAAO,IAAIC,EAAY;AAAA,IAC3B,GAAGF;AAAA,IACH,QAAQG,EAAWP,CAAE;AAAA,IACrB,aAAa,CAACQ,MAAU;AACtB,MAAAP,EAAW,KAAKO,CAAK,GACrBL,IAAcK,CAAK;AAAA,IACrB;AAAA,EAAA,CACD;AAED,MADA,MAAMH,EAAK,aAAa,EAAE,QAAQ,IAAM,GACpC,OAAOH,KAAS,WAAY,OAAMA,EAAKF,CAAE;AAAA;AAE3C,eAAWS,KAAQ,OAAOP,KAAS,WAAW,CAACA,CAAI,IAAKA,KAAQ;AAC9D,YAAMF,EAAG,QAAQS,CAAI;AAGzB,QAAMC,IAASL,EAAK,UAAA,GACdM,IAAM,CACVC,GACAC,IAAqC,CAAA,GACrCC,IAAwC,CAAA,MACrCC,EAAQ,EAAE,QAAAL,GAAQ,QAAAE,GAAQ,gBAAgBC,GAAW,cAAAC,GAAc;AACxE,SAAO;AAAA,IACL,MAAAT;AAAA,IACA,IAAAL;AAAA,IACA,QAAAU;AAAA,IACA,YAAAT;AAAA,IACA,KAAAU;AAAA,IACA,MAAM,OACJC,GACAC,GACAG,MACG;AACH,YAAMC,IAAI,MAAMN,EAAIC,GAAQC,GAAWG,CAAO;AAC9C,UAAIC,EAAE,OAAQ,OAAMA,EAAE,OAAO,CAAC;AAC9B,aAAOA,EAAE;AAAA,IACX;AAAA,IACA,OAAO,MAAMjB,EAAG,UAAA;AAAA,EAAU;AAE9B;AAOA,eAAsBkB,EACpBb,GACAc,GACAN,IAAqC,CAAA,GACrCf,IAAqD,IACtC;AAEf,QAAMsB,KADS,MAAMf,EAAK,QAAQc,GAAUN,GAAWf,CAAO,GACtC;AAAA,IAAQ,CAACuB,MAC/BA,EAAE,QAAQ;AAAA,MAAQ,CAACJ,MACjBA,EAAE,SAAS,IAAI,CAACK,MAAM,GAAGD,EAAE,KAAK,KAAKC,EAAE,OAAO;AAAA,IAAOA,EAAE,SAAS,EAAE;AAAA,IAAA;AAAA,EACpE;AAEF,MAAIF,EAAS,SAAS;AACpB,UAAM,IAAI;AAAA,MACR;AAAA,EAAuCA,EAAS,KAAK;AAAA,CAAI,CAAC;AAAA,IAAA;AAGhE;AA6BA,eAAsBG,EACpBC,GACAC,GACe;AACf,QAAMpB,IAAOmB,aAAkBlB,IAAckB,IAASA,EAAO,MACvDE,IAAqB,CAAA,GACrBC,IAAQ,OAAOC,GAAeC,MAAqB;AACvD,QAAIC;AACJ,QAAI;AACF,MAAAA,IAAU,MAAMC,EAAU1B,GAAMuB,GAAOH,CAAY;AAAA,IACrD,SAASO,GAAK;AACZ,MAAAN,EAAS;AAAA,QACP,GAAGE,CAAK,KAAKI,aAAe,QAAQA,EAAI,UAAU,OAAOA,CAAG,CAAC;AAAA,MAAA;AAE/D;AAAA,IACF;AACA,IAAIF,EAAQ,SAAS,UACnBJ,EAAS,KAAK,GAAGE,CAAK,iBAAiBE,EAAQ,OAAO,EAAE,IAC/CD,KAAWC,EAAQ,SAAS,WACrCJ,EAAS;AAAA,MACP,iCAAiCE,CAAK,KAAKE,EAAQ,OAAO;AAAA,IAAA,IAEnD,CAACD,KAAWC,EAAQ,SAAS,aACtCJ,EAAS,KAAK,iCAAiCE,CAAK,EAAE;AAAA,EAE1D;AACA,aAAWA,KAASH,EAAa,WAAW,CAAA,EAAI,OAAME,EAAMC,GAAO,EAAI;AACvE,aAAWA,KAASH,EAAa,UAAU,CAAA,EAAI,OAAME,EAAMC,GAAO,EAAK;AACvE,MAAIF,EAAS,SAAS,GAAG;AACvB,UAAMO,IAAMR,EAAa,KAAK,KAAK,UAAUA,EAAa,EAAE,IAAI;AAChE,UAAM,IAAI;AAAA,MACR,aAAaQ,CAAG,eAAeP,EAAS,MAAM,IAAIA,EAAS,WAAW,IAAI,UAAU,SAAS;AAAA,IAAQA,EAAS,KAAK;AAAA,GAAM,CAAC;AAAA,IAAA;AAAA,EAE9H;AACF;AAOA,MAAMQ,IAAS,oBAAI,IAAI,CAAC,aAAa,mBAAmB,WAAW,CAAC;AAEpE,eAAeH,EACb1B,GACAuB,GACAH,GACwB;AACxB,QAAM,EAAE,QAAAb,GAAQ,WAAAC,GAAW,QAAAsB,GAAQ,MAAAC,GAAM,OAAAC,MAAUC;AAAA,IACjDjC;AAAA,IACAuB;AAAA,EAAA,GAEIW,IAAK,MAAMlC,EAAK,MAAA;AACtB,MAAI;AACF,UAAMmC,IAAS,MAAMnC,EAAK,QAAQ;AAAA,MAChC,QAAAO;AAAA,MACA,WAAAC;AAAA,MACA,SAAS;AAAA,QACP,GAAGY,EAAa;AAAA,QAChB,GAAIA,EAAa,KAAK,EAAE,KAAKA,EAAa,GAAA,IAAO,CAAA;AAAA,QACjD,aAAac;AAAA,MAAA;AAAA,IACf,CACD,GACKE,IAAQD,EAAO,SAAS,CAAC;AAC/B,QAAIC,GAAO;AACT,YAAMC,IAAQD,EAAM,YAClB;AAEF,aAAO,OAAOC,KAAS,YAAYR,EAAO,IAAIQ,CAAI,IAC9C,EAAE,MAAM,UAAU,SAAS,GAAGA,CAAI,KAAKD,EAAM,OAAO,OACpD;AAAA,QACE,MAAM;AAAA,QACN,SAAS,GAAG,OAAOC,KAAQ,OAAO,CAAC,KAAKD,EAAM,OAAO;AAAA,MAAA;AAAA,IAE7D;AACA,UAAME,IAAWH,EAAO,OAA0CJ,CAAI;AACtE,WACEC,MACCM,KAAW,QAAQ,CAACN,EAAMM,CAAkC,KAEtD,EAAE,MAAM,UAAU,SAAS,+BAAA,IAEhCR,KACaK,EAAO,OAA0CJ,CAAI,KACvD,OAAa,EAAE,MAAM,UAAU,SAAS,cAAA,IAEhD,EAAE,MAAM,UAAA;AAAA,EACjB,UAAA;AACE,UAAMG,EAAG,SAAA;AAAA,EACX;AACF;AAEA,SAASD,EACPjC,GACAuB,GASA;AACA,QAAMgB,IACJ,0JAA0J;AAAA,IACxJhB;AAAA,EAAA;AAEJ,MAAI,CAACgB;AACH,UAAM,IAAI;AAAA,MACR;AAAA,IAAA;AAGJ,QAAM,CAAA,EAAGC,GAAWC,GAAUC,GAAWC,GAAKC,GAAOC,CAAI,IAAIN,GASvDO,IAAO9C,EAAK,MAAM,MAAM,IAAIyC,CAAQ;AAC1C,MAAI,CAACK,EAAM,OAAM,IAAI,MAAM,GAAGL,CAAQ,sBAAsB;AAC5D,QAAMM,IAAQF,IAAQ,KAAK,MAAMA,CAAI,IAAgC,CAAA,GAC/DG,IAAU,GAAGF,EAAK,IAAI,gBAAgBA,EAAK,IAAI,IAAI,KACnDG,IAAQR,EAAS,OAAO,CAAC,EAAE,gBAAgBA,EAAS,MAAM,CAAC,GAC3DS,IACJJ,EAAK,OAAO,OAAO,CAAC,EAAE,YAAA,IAAgBA,EAAK,OAAO,MAAM,CAAC,GACrDK,IAAa,CAAC,WAAW,cAAc,aAAa,EAAE;AAAA,IAC1DX;AAAA,EAAA;AAEF,MAAIW,OAAgBT,MAAc,UAAaE,MAAU;AACvD,UAAM,IAAI;AAAA,MACRO,IACI,GAAGX,CAAS,gCACZ,GAAGA,CAAS;AAAA,IAAA;AAGpB,UAAQA,GAAA;AAAA,IACN,KAAK;AACH,aAAO;AAAA,QACL,WAAAA;AAAA,QACA,QAAQ,eAAeQ,CAAO,OAAOC,CAAK,IAAIH,EAAK,IAAI,IAAI,aAAaA,EAAK,IAAI,IAAI;AAAA,QACrF,WAAW,EAAE,KAAAH,EAAA;AAAA,QACb,QAAQ;AAAA,QACR,MAAMM;AAAA,MAAA;AAAA,IAEV,KAAK;AACH,aAAO;AAAA,QACL,WAAAT;AAAA,QACA,QAAQ,qBAAqBC,CAAQ,2BAA2BS,CAAW;AAAA,QAC3E,WAAW,EAAE,OAAO,CAAC,EAAE,GAAGH,GAAO,CAACD,EAAK,IAAI,IAAI,GAAGH,EAAA,CAAK,EAAA;AAAA,QACvD,QAAQ;AAAA,QACR,MAAM,SAASO,CAAW;AAAA,MAAA;AAAA,IAE9B,KAAK;AACH,aAAO;AAAA,QACL,WAAAV;AAAA,QACA,QAAQ,kBAAkBQ,CAAO,cAAcP,CAAQ,yBAAyBA,CAAQ,IAAIK,EAAK,IAAI,IAAI,8BAA8BG,CAAK;AAAA,QAC5I,WAAW,EAAE,KAAAN,GAAK,QAAQI,EAAA;AAAA,QAC1B,QAAQ;AAAA,QACR,MAAM,SAASN,CAAQ;AAAA,QACvB,OAAO,CAACW,MAAMA,EAAEH,CAAK,KAAK;AAAA,MAAA;AAAA,IAE9B,KAAK;AACH,aAAO;AAAA,QACL,WAAAT;AAAA,QACA,QAAQ,kBAAkBQ,CAAO,aAAaP,CAAQ,IAAIK,EAAK,IAAI,IAAI;AAAA,QACvE,WAAW,EAAE,KAAAH,EAAA;AAAA,QACb,QAAQ;AAAA,QACR,MAAM,SAASF,CAAQ;AAAA,QACvB,OAAO,CAACW,MAAM,OAAOA,EAAE,gBAAmB,CAAC,IAAI;AAAA,MAAA;AAAA,EACjD;AAEJ,QAAMC,IAAQP,EAAK,OAAO,IAAIJ,CAAU;AACxC,MAAIW,GAAO,SAAS;AAClB,UAAM,IAAI,MAAM,GAAGZ,CAAQ,IAAIC,CAAS,8BAA8B;AAExE,QAAMY,IAAYtD,EAAK,MAAM,MAAM,IAAIqD,EAAM,MAAM,GAAG,IAAI,QAAQ,OAC5DE,IACJf,MAAc,YACV;AAAA,IACE,SAASa,EAAM,OACX,CAAC,EAAE,CAACC,CAAS,GAAGV,GAAO,GAAIC,IAAO,EAAE,MAAME,EAAA,IAAU,GAAC,CAAI,IACzD,EAAE,CAACO,CAAS,GAAGV,GAAO,GAAIC,IAAO,EAAE,MAAME,EAAA,IAAU,CAAA,EAAC;AAAA,EAAG,IAE7DP,MAAc,eACZ,EAAE,YAAYa,EAAM,OAAO,CAACT,CAAK,IAAI,GAAA;AAAA;AAAA,IAErC;AAAA,MACE,QAAQS,EAAM,OACV,CAAC,EAAE,CAACC,CAAS,GAAGV,GAAO,MAAMG,EAAA,CAAO,IACpC,EAAE,MAAMA,EAAA;AAAA,IAAM;AAAA;AAE5B,SAAO;AAAA,IACL,WAAAP;AAAA,IACA,QAAQ,kBAAkBQ,CAAO,cAAcP,CAAQ,yBAAyBA,CAAQ,IAAIK,EAAK,IAAI,IAAI,8BAA8BG,CAAK;AAAA,IAC5I,WAAW,EAAE,KAAAN,GAAK,QAAQ,EAAE,CAACD,CAAU,GAAGa,IAAO;AAAA,IACjD,QAAQ;AAAA,IACR,MAAM,SAASd,CAAQ;AAAA;AAAA;AAAA,IAGvB,OAAO,CAACW,MACNA,EAAEH,CAAK,KAAK,SACXT,MAAc,gBACb;AAAA,MACGY,EAAE,MACD,wBACG;AAAA,IAAA,IACH;AAAA,EAAA;AAEZ;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loradb/lora-graphql",
3
- "version": "0.23.0",
3
+ "version": "0.24.1",
4
4
  "description": "Schema-first GraphQL for LoraDB: one annotated SDL becomes an executable, index-aware GraphQL API that compiles every operation to a single Cypher statement.",
5
5
  "license": "BUSL-1.1",
6
6
  "packageManager": "yarn@4.5.1",
@@ -65,7 +65,7 @@
65
65
  "bench:load": "node bench/load/main.mjs"
66
66
  },
67
67
  "peerDependencies": {
68
- "@loradb/lora-node": "^0.23.0",
68
+ "@loradb/lora-node": "^0.24.1",
69
69
  "graphql": "^16.8.0 || ^17.0.0"
70
70
  },
71
71
  "devDependencies": {