@loradb/lora-graphql 0.21.0 → 0.22.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 +500 -77
- package/dist/analyze/access.d.ts +45 -2
- package/dist/analyze/statistics.d.ts +1 -0
- package/dist/cli.js +386 -323
- package/dist/cli.js.map +1 -1
- package/dist/compile/auth.d.ts +49 -0
- package/dist/compile/cache.d.ts +11 -1
- package/dist/compile/context.d.ts +20 -0
- package/dist/compile/cost.d.ts +36 -0
- package/dist/compile/read.d.ts +7 -0
- package/dist/{diff-CcRU-3_p.js → diff-B9iDdELF.js} +13 -12
- package/dist/{diff-CcRU-3_p.js.map → diff-B9iDdELF.js.map} +1 -1
- package/dist/driver-C5dSoSD8.js +13905 -0
- package/dist/driver-C5dSoSD8.js.map +1 -0
- package/dist/driver.d.ts +2 -0
- package/dist/errors.d.ts +29 -1
- package/dist/execute/budget.d.ts +22 -0
- package/dist/execute/changes.d.ts +2 -0
- package/dist/execute/cypher-mutation.d.ts +4 -1
- package/dist/execute/mutate.d.ts +29 -1
- package/dist/guards.d.ts +19 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +19 -17
- package/dist/lora-graphql.d.ts +120 -13
- package/dist/model/desugar.d.ts +10 -0
- package/dist/model/directives.d.ts +1 -1
- package/dist/model/inputs.d.ts +9 -0
- package/dist/model/types.d.ts +46 -3
- package/dist/model/unique-together.d.ts +3 -0
- package/dist/schema/build.d.ts +6 -1
- package/dist/testing.js +1 -1
- package/package.json +2 -2
- package/dist/driver-DAtBEMWj.js +0 -11845
- package/dist/driver-DAtBEMWj.js.map +0 -1
package/dist/driver.d.ts
CHANGED
|
@@ -52,6 +52,8 @@ export interface LoraDriver {
|
|
|
52
52
|
* Open an interactive transaction. Needed for mutations, which check
|
|
53
53
|
* their writes (connect targets, cardinality, authorization) before
|
|
54
54
|
* committing. Optional: the WASM binding has none, so it serves reads.
|
|
55
|
+
* A write transaction waits for the writer lock: the wait must honour
|
|
56
|
+
* `timeoutMs` and `signal` (`loraDriver` bounds it).
|
|
55
57
|
*/
|
|
56
58
|
begin?(options: RunOptions): Promise<DriverTransaction>;
|
|
57
59
|
/** Plan a statement without running it. Optional: the WASM binding has no `explain()`. */
|
package/dist/errors.d.ts
CHANGED
|
@@ -15,6 +15,34 @@ export declare class ModelError extends Error {
|
|
|
15
15
|
constructor(problems: ModelProblem[]);
|
|
16
16
|
}
|
|
17
17
|
export declare function formatProblem(p: ModelProblem): string;
|
|
18
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Every `extensions.code` a request-time error of this library carries, in
|
|
20
|
+
* a stable order. Use it to exhaustively map codes (HTTP status, client
|
|
21
|
+
* copy) or to test membership at runtime.
|
|
22
|
+
*/
|
|
23
|
+
export declare const LORA_GRAPHQL_ERROR_CODES: readonly ["BAD_USER_INPUT", "INVALID_CURSOR", "LIMIT_EXCEEDED", "COST_EXCEEDED", "TIMEOUT", "UNAUTHENTICATED", "FORBIDDEN", "NOT_FOUND", "CONSTRAINT_VIOLATION", "DATABASE_ERROR", "PERSISTED_QUERY_ONLY", "WRONG_OPERATION_TYPE"];
|
|
24
|
+
export type LoraGraphQLErrorCode = (typeof LORA_GRAPHQL_ERROR_CODES)[number];
|
|
25
|
+
/**
|
|
26
|
+
* Whether `err` is a request-time error raised by this library: a
|
|
27
|
+
* GraphQL error (or a plain `{ extensions: { code } }` from a serialized
|
|
28
|
+
* response) whose `extensions.code` is one of `LORA_GRAPHQL_ERROR_CODES`.
|
|
29
|
+
* Duck-typed, so it also recognizes errors built by another copy of
|
|
30
|
+
* `graphql`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function isLoraGraphQLError(err: unknown): err is {
|
|
33
|
+
message: string;
|
|
34
|
+
extensions: {
|
|
35
|
+
code: LoraGraphQLErrorCode;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
19
38
|
/** A request-time error with a stable `extensions.code`. */
|
|
20
39
|
export declare function requestError(code: LoraGraphQLErrorCode, message: string, cause?: unknown, extensions?: Record<string, unknown>): GraphQLError;
|
|
40
|
+
/**
|
|
41
|
+
* One error per field path pattern: errors with the same message and code
|
|
42
|
+
* at paths that differ only in list indices (a field every row of a list
|
|
43
|
+
* refuses, such as an @authentication field read anonymously) become one
|
|
44
|
+
* error at the first path, with `extensions.count` and
|
|
45
|
+
* `extensions.pathPattern` (indices as `"*"`). A page of 100 x 100 rows
|
|
46
|
+
* otherwise answers with 10 000 copies of one error.
|
|
47
|
+
*/
|
|
48
|
+
export declare function collapseErrors(errors: readonly GraphQLError[]): GraphQLError[];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/** Default `maxConcurrentStatements`. */
|
|
2
|
+
export declare const MAX_CONCURRENT_STATEMENTS = 2;
|
|
3
|
+
/** Default `operationTimeoutMs`, as a multiple of `timeoutMs`. */
|
|
4
|
+
export declare const OPERATION_TIMEOUT_FACTOR = 2;
|
|
5
|
+
export declare class OperationBudget {
|
|
6
|
+
#private;
|
|
7
|
+
/** Root fields of the operation in flight. */
|
|
8
|
+
active: number;
|
|
9
|
+
constructor(timeoutMs: number, maxConcurrent: number);
|
|
10
|
+
get signal(): AbortSignal;
|
|
11
|
+
/** Milliseconds left before the deadline (Infinity without one). */
|
|
12
|
+
remaining(): number;
|
|
13
|
+
/**
|
|
14
|
+
* Run `fn` once a statement slot is free, failing with TIMEOUT when the
|
|
15
|
+
* deadline passes first (the statement keeps its own abort signal).
|
|
16
|
+
*/
|
|
17
|
+
run<T>(fn: (signal: AbortSignal) => Promise<T>): Promise<T>;
|
|
18
|
+
/** Stop the deadline timer: the operation is over. */
|
|
19
|
+
dispose(): void;
|
|
20
|
+
}
|
|
21
|
+
/** One signal aborted when either is; `b` alone when `a` is absent. */
|
|
22
|
+
export declare function anySignal(a: AbortSignal | undefined, b: AbortSignal): AbortSignal;
|
|
@@ -53,6 +53,8 @@ export interface WriteChange {
|
|
|
53
53
|
export interface ReadSetLike {
|
|
54
54
|
labels: readonly string[];
|
|
55
55
|
relationships: readonly string[];
|
|
56
|
+
/** Reads nobody can name (a `@cypher` statement): every change affects it. */
|
|
57
|
+
opaque?: boolean | undefined;
|
|
56
58
|
}
|
|
57
59
|
/**
|
|
58
60
|
* Whether a read with this read-set may observe the change. Label-level:
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { FieldNode } from 'graphql';
|
|
2
|
+
import { Statement } from '../driver.js';
|
|
2
3
|
import { CypherField } from '../model/types.js';
|
|
3
4
|
import { MutationEnv } from './mutate.js';
|
|
4
|
-
export declare function executeCypherMutation(env: MutationEnv, field: CypherField, args: Record<string, unknown>, fieldNodes: readonly FieldNode[]
|
|
5
|
+
export declare function executeCypherMutation(env: MutationEnv, field: CypherField, args: Record<string, unknown>, fieldNodes: readonly FieldNode[],
|
|
6
|
+
/** The field's `viewer` rules, checked in the transaction first. */
|
|
7
|
+
guard?: Statement): Promise<unknown>;
|
package/dist/execute/mutate.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FieldNode } from 'graphql';
|
|
2
|
+
import { Statistics } from '../analyze/statistics.js';
|
|
2
3
|
import { SelectionContext } from '../compile/selection.js';
|
|
3
4
|
import { DriverTransaction, LoraDriver, QueryResult, Statement } from '../driver.js';
|
|
4
5
|
import { GraphModel, NodeType } from '../model/types.js';
|
|
@@ -28,8 +29,21 @@ export interface MutationEnv {
|
|
|
28
29
|
timeoutMs: number;
|
|
29
30
|
signal: AbortSignal | undefined;
|
|
30
31
|
degrees: ReadonlyMap<string, number>;
|
|
31
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* Most nodes one mutation may create, update or delete, nested ones
|
|
34
|
+
* included; relationships written, ten times as many.
|
|
35
|
+
*/
|
|
32
36
|
maxBatch: number;
|
|
37
|
+
/** Most items a @cypher list argument takes without `@size(max:)`. */
|
|
38
|
+
maxListArgument?: number | undefined;
|
|
39
|
+
/** Relationship levels one `where` may nest. */
|
|
40
|
+
maxFilterDepth?: number | undefined;
|
|
41
|
+
/** Items an `in` filter operand may hold. */
|
|
42
|
+
maxListFilter?: number | undefined;
|
|
43
|
+
/** Characters a string filter operand may hold. */
|
|
44
|
+
maxStringFilter?: number | undefined;
|
|
45
|
+
/** Statistics from `analyze()`, for filter costs. */
|
|
46
|
+
statistics?: Statistics | undefined;
|
|
33
47
|
callbacks: Readonly<Record<string, PopulatedByCallback>>;
|
|
34
48
|
/**
|
|
35
49
|
* A caller-owned transaction: run inside it and leave the commit to the
|
|
@@ -40,6 +54,15 @@ export interface MutationEnv {
|
|
|
40
54
|
onStatement?: ((statement: Statement) => void) | undefined;
|
|
41
55
|
/** Runs a statement under timing, tracing and metrics, when configured. */
|
|
42
56
|
observe?: ((statement: Statement, run: () => Promise<QueryResult>) => Promise<QueryResult>) | undefined;
|
|
57
|
+
/**
|
|
58
|
+
* Called inside the transaction once a delete knows every node it
|
|
59
|
+
* removes, before any is removed: subscribers' checks of the nodes as
|
|
60
|
+
* they were (a deleted node cannot be checked after the commit).
|
|
61
|
+
*/
|
|
62
|
+
beforeDelete?: ((change: WriteChange, doomed: ReadonlyArray<{
|
|
63
|
+
node: NodeType;
|
|
64
|
+
keys: unknown[];
|
|
65
|
+
}>, run: (statement: Statement) => Promise<QueryResult>) => Promise<void>) | undefined;
|
|
43
66
|
}
|
|
44
67
|
/** Run `statement` in `tx`, reporting it before and observing it during. */
|
|
45
68
|
export declare function runStatement(env: MutationEnv, tx: DriverTransaction, statement: Statement): Promise<QueryResult>;
|
|
@@ -50,6 +73,11 @@ export interface MutationInfo {
|
|
|
50
73
|
relationshipsCreated: number;
|
|
51
74
|
relationshipsDeleted: number;
|
|
52
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* `@uniqueTogether` after a write whose write-set is unknown (a `@cypher`
|
|
78
|
+
* mutation): every node of each type in `nodes` is compared, in `tx`.
|
|
79
|
+
*/
|
|
80
|
+
export declare function checkUniqueTogetherOf(env: MutationEnv, tx: DriverTransaction, nodes: readonly NodeType[]): Promise<void>;
|
|
53
81
|
export interface MutationResult {
|
|
54
82
|
payload: unknown;
|
|
55
83
|
change: WriteChange;
|
package/dist/guards.d.ts
CHANGED
|
@@ -41,8 +41,15 @@ export declare function parseOptions(guards?: DocumentGuards): ParseOptions;
|
|
|
41
41
|
* The guards as an Envelop plugin, for GraphQL Yoga and other Envelop
|
|
42
42
|
* servers: the token limit wraps `parse`, the rest are validation rules.
|
|
43
43
|
* Typed structurally, so the package does not depend on Envelop.
|
|
44
|
+
*
|
|
45
|
+
* It also checks, once, that the server runs the same `graphql` copy as
|
|
46
|
+
* this library (see `graphqlRealmProblem`), and reports a mismatch
|
|
47
|
+
* through `onRealmMismatch` (default: `console.error`).
|
|
44
48
|
*/
|
|
45
|
-
export declare function envelopPlugin(guards?: DocumentGuards): {
|
|
49
|
+
export declare function envelopPlugin(guards?: DocumentGuards, onRealmMismatch?: (message: string) => void): {
|
|
50
|
+
onSchemaChange({ schema }: {
|
|
51
|
+
schema: unknown;
|
|
52
|
+
}): void;
|
|
46
53
|
onParse({ parseFn, setParseFn, }: {
|
|
47
54
|
parseFn: (source: unknown, options?: ParseOptions) => unknown;
|
|
48
55
|
setParseFn: (fn: (source: unknown, options?: ParseOptions) => unknown) => void;
|
|
@@ -51,4 +58,15 @@ export declare function envelopPlugin(guards?: DocumentGuards): {
|
|
|
51
58
|
addValidationRule: (rule: ValidationRule) => void;
|
|
52
59
|
}): void;
|
|
53
60
|
};
|
|
61
|
+
type RealmProbe = "schema" | "validation";
|
|
62
|
+
/**
|
|
63
|
+
* When `value` (a schema, or the context a validation rule receives)
|
|
64
|
+
* comes from a different `graphql` module than the one this library
|
|
65
|
+
* imports, the message explaining it; otherwise undefined. Two copies
|
|
66
|
+
* break `instanceof GraphQLError`, so servers such as Yoga mask the
|
|
67
|
+
* library's errors (FORBIDDEN, BAD_USER_INPUT, ...) as "Unexpected
|
|
68
|
+
* error".
|
|
69
|
+
*/
|
|
70
|
+
export declare function graphqlRealmProblem(value: unknown, what: RealmProbe): string | undefined;
|
|
54
71
|
export declare function nodeEnv(): string | undefined;
|
|
72
|
+
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { LoraGraphQL, type AssertSchemaOptions, type CheckOptions, type CheckReport, type CostEvent, type DatabaseErrorEvent, type ExecuteArgs, type ExecutionTiming, type LoraGraphQLContext, type LoraGraphQLOptions, type SchemaAssertion, type StatementEvent, } from './lora-graphql.js';
|
|
1
|
+
export { LoraGraphQL, type AssertSchemaOptions, type CheckOptions, type CheckReport, type CostEvent, type DatabaseErrorEvent, type ExecuteArgs, type ExecutionTiming, type LoraGraphQLContext, type LoraGraphQLOptions, type LoraExecutionResult, type SchemaAssertion, type StatementEvent, } from './lora-graphql.js';
|
|
2
2
|
export { loraDriver, type DriverTransaction, type LoraDatabaseLike, type LoraDriver, type QueryPlan, type QueryResult, type RunOptions, type Statement, } from './driver.js';
|
|
3
3
|
export { directiveTypeDefs } from './model/directives.js';
|
|
4
4
|
export { buildModel, type ModelOptions } from './model/build.js';
|
|
@@ -6,7 +6,7 @@ export type * from './model/types.js';
|
|
|
6
6
|
export { inferRequirements, requirementDdl, type SchemaRequirement, } from './analyze/indexes.js';
|
|
7
7
|
export { checkPlans, scanExpands, type PlanFinding, type PlanReport, } from './analyze/plans.js';
|
|
8
8
|
export type { DegreeStats, Statistics } from './analyze/statistics.js';
|
|
9
|
-
export type { AccessEntry, AccessVerdict } from './analyze/access.js';
|
|
9
|
+
export type { AccessEntry, AccessVerdict, OperationAccess, RootFieldAccess, } from './analyze/access.js';
|
|
10
10
|
export type { CypherFinding } from './analyze/cypher-check.js';
|
|
11
11
|
export { diffSchemas, type ApiChange, type SchemaDiff, } from './analyze/diff.js';
|
|
12
12
|
export type { CompiledRead, ReadSet, SeekExpectation } from './compile/read.js';
|
|
@@ -15,7 +15,7 @@ export type { MutationInfo, PopulatedByCallback } from './execute/mutate.js';
|
|
|
15
15
|
export type { MutationKind } from './schema/mutations.js';
|
|
16
16
|
export type { ChangeEvent } from './schema/build.js';
|
|
17
17
|
export { LoraTransaction } from './execute/transaction.js';
|
|
18
|
-
export { ModelError, formatProblem, type LoraGraphQLErrorCode, type ModelProblem, } from './errors.js';
|
|
18
|
+
export { ModelError, formatProblem, isLoraGraphQLError, LORA_GRAPHQL_ERROR_CODES, type LoraGraphQLErrorCode, type ModelProblem, } from './errors.js';
|
|
19
19
|
export { toGlobalId, fromGlobalId } from './schema/global-id.js';
|
|
20
20
|
export type { Attributes, MetricsLike, ObservabilityOptions, SpanLike, StatementEndEvent, TracerLike, } from './observe.js';
|
|
21
21
|
export { schemaHash, type ManifestOperation, type OperationManifest, } from './codegen.js';
|
package/dist/index.js
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
import { D as
|
|
2
|
-
import { d as
|
|
1
|
+
import { D as r, L as e, a as o, b as l, M as i, c as d, d as n, e as t, f as p, g as f, h as m, i as L, j as c, l as R, p as h, r as D, s as E, k as G, t as b, v as u } from "./driver-C5dSoSD8.js";
|
|
2
|
+
import { d as A } from "./diff-B9iDdELF.js";
|
|
3
3
|
export {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
o as
|
|
7
|
-
l as
|
|
8
|
-
i as
|
|
9
|
-
d as
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
t as
|
|
4
|
+
r as DEFAULT_GUARDS,
|
|
5
|
+
e as LORA_GRAPHQL_ERROR_CODES,
|
|
6
|
+
o as LoraGraphQL,
|
|
7
|
+
l as LoraTransaction,
|
|
8
|
+
i as ModelError,
|
|
9
|
+
d as buildModel,
|
|
10
|
+
n as checkPlans,
|
|
11
|
+
A as diffSchemas,
|
|
12
|
+
t as directiveTypeDefs,
|
|
13
|
+
p as envelopPlugin,
|
|
13
14
|
f as formatProblem,
|
|
14
15
|
m as fromGlobalId,
|
|
15
|
-
|
|
16
|
-
c as
|
|
16
|
+
L as inferRequirements,
|
|
17
|
+
c as isLoraGraphQLError,
|
|
18
|
+
R as loraDriver,
|
|
17
19
|
h as parseOptions,
|
|
18
20
|
D as requirementDdl,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
E as scanExpands,
|
|
22
|
+
G as schemaHash,
|
|
23
|
+
b as toGlobalId,
|
|
24
|
+
u as validationRules
|
|
23
25
|
};
|
|
24
26
|
//# sourceMappingURL=index.js.map
|
package/dist/lora-graphql.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { DocumentNode, ExecutionResult, GraphQLSchema, GraphQLFieldResolver, GraphQLScalarType, ValidationRule } from 'graphql';
|
|
1
|
+
import { ExecutionArgs, DocumentNode, ExecutionResult, GraphQLSchema, GraphQLFieldResolver, GraphQLScalarType, ValidationRule } from 'graphql';
|
|
2
2
|
import { CypherFinding } from './analyze/cypher-check.js';
|
|
3
3
|
import { SchemaRequirement } from './analyze/indexes.js';
|
|
4
4
|
import { PlanReport } from './analyze/plans.js';
|
|
5
|
-
import { AccessEntry } from './analyze/access.js';
|
|
5
|
+
import { AccessEntry, OperationAccess } from './analyze/access.js';
|
|
6
6
|
import { Statistics } from './analyze/statistics.js';
|
|
7
7
|
import { CompiledRead, ReadSet } from './compile/read.js';
|
|
8
8
|
import { LoraDriver, Statement } from './driver.js';
|
|
@@ -20,10 +20,34 @@ export interface LoraGraphQLOptions extends ModelOptions, ObservabilityOptions {
|
|
|
20
20
|
driver: LoraDriver;
|
|
21
21
|
/** Timeout for every statement, in milliseconds. Default 10 000; 0 disables. */
|
|
22
22
|
timeoutMs?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Time budget for all root fields of one query, in milliseconds: past
|
|
25
|
+
* it, the operation's statements are aborted and its unfinished fields
|
|
26
|
+
* fail with TIMEOUT. Default twice `timeoutMs` (20 000); 0 disables.
|
|
27
|
+
* Subscriptions are not bounded by it; mutations keep `timeoutMs` per
|
|
28
|
+
* statement.
|
|
29
|
+
*/
|
|
30
|
+
operationTimeoutMs?: number;
|
|
31
|
+
/**
|
|
32
|
+
* Statements one operation runs at once: aliased root fields queue for
|
|
33
|
+
* a slot instead of taking every libuv worker, so one request cannot
|
|
34
|
+
* starve the others. Default 2.
|
|
35
|
+
*/
|
|
36
|
+
maxConcurrentStatements?: number;
|
|
37
|
+
/**
|
|
38
|
+
* Approximate bytes the compile cache may hold, and again the parsed
|
|
39
|
+
* document cache: entries are evicted oldest first past it, besides the
|
|
40
|
+
* entry caps. Requests with more than 16 KiB of variables a field uses
|
|
41
|
+
* (long `in:` lists, embedding vectors) are compiled but not cached.
|
|
42
|
+
* Default 64 MiB.
|
|
43
|
+
*/
|
|
44
|
+
compileCacheBytes?: number;
|
|
23
45
|
/**
|
|
24
46
|
* Reject a root field whose estimated rows touched exceed this, before
|
|
25
47
|
* it runs. The estimate multiplies page sizes through nested lists,
|
|
26
|
-
* capped by relationship degrees from `analyze()` or `@cardinality
|
|
48
|
+
* capped by relationship degrees from `analyze()` or `@cardinality`,
|
|
49
|
+
* and charges each filter the rows it examines: a label scan when no
|
|
50
|
+
* index answers it, the related nodes of every relationship it follows.
|
|
27
51
|
* Default 50 000; `Infinity` disables.
|
|
28
52
|
*/
|
|
29
53
|
maxCost?: number;
|
|
@@ -41,16 +65,61 @@ export interface LoraGraphQLOptions extends ModelOptions, ObservabilityOptions {
|
|
|
41
65
|
*/
|
|
42
66
|
jwt?: (context: unknown) => Record<string, unknown> | undefined;
|
|
43
67
|
/**
|
|
44
|
-
* Most nodes one mutation may create or delete, nested ones
|
|
45
|
-
* Default 1000: larger imports belong in a Cypher load, not a GraphQL
|
|
68
|
+
* Most nodes one mutation may create, update or delete, nested ones
|
|
69
|
+
* included (and ten times as many relationships written). Default 1000: larger imports belong in a Cypher load, not a GraphQL
|
|
46
70
|
* request. Also the default `limit` of bulk updates and deletes.
|
|
47
71
|
*/
|
|
48
72
|
maxBatch?: number;
|
|
73
|
+
/**
|
|
74
|
+
* Most items a list argument of a `@cypher` field takes, unless its
|
|
75
|
+
* `@size(max:)` says otherwise. More is BAD_USER_INPUT before the
|
|
76
|
+
* statement runs. Default 1000.
|
|
77
|
+
*/
|
|
78
|
+
maxListArgument?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Relationship levels one `where` may nest: quantifiers (`some`,
|
|
81
|
+
* `none`, `all`, `single`, `count`, `aggregate`), connection filters,
|
|
82
|
+
* `<field>Exists` and filters through a single relationship each count
|
|
83
|
+
* one. Deeper is BAD_USER_INPUT before anything runs: each level
|
|
84
|
+
* multiplies the work by the relationship's degree. Default 2.
|
|
85
|
+
*/
|
|
86
|
+
maxFilterDepth?: number;
|
|
87
|
+
/**
|
|
88
|
+
* Items an `in` filter operand may hold. More is BAD_USER_INPUT before
|
|
89
|
+
* anything runs. Default 1000.
|
|
90
|
+
*/
|
|
91
|
+
maxListFilter?: number;
|
|
92
|
+
/**
|
|
93
|
+
* Characters a string filter operand (`eq`, `contains`, an `in` item, …)
|
|
94
|
+
* may hold. More is BAD_USER_INPUT before anything runs. Default 10 000.
|
|
95
|
+
*/
|
|
96
|
+
maxStringFilter?: number;
|
|
49
97
|
/**
|
|
50
98
|
* Changes a `changes()` consumer or subscriber may fall behind before it
|
|
51
99
|
* is ended with an error. Default 1000.
|
|
52
100
|
*/
|
|
53
101
|
maxQueuedChanges?: number;
|
|
102
|
+
/**
|
|
103
|
+
* Live subscriptions one scope (see `subscriptionScope`) may hold; one
|
|
104
|
+
* more is LIMIT_EXCEEDED. Default 100.
|
|
105
|
+
*/
|
|
106
|
+
maxSubscriptions?: number;
|
|
107
|
+
/**
|
|
108
|
+
* What `maxSubscriptions` counts per: by default the context object, so
|
|
109
|
+
* a server that reuses one context per connection (graphql-ws) limits
|
|
110
|
+
* each connection. Return the connection (or user) otherwise.
|
|
111
|
+
*/
|
|
112
|
+
subscriptionScope?: (context: unknown) => object | undefined;
|
|
113
|
+
/**
|
|
114
|
+
* How deep relationship filters may nest in a subscription's `where`,
|
|
115
|
+
* which runs on every change. Deeper is LIMIT_EXCEEDED. Default 1.
|
|
116
|
+
*/
|
|
117
|
+
maxSubscriptionFilterDepth?: number;
|
|
118
|
+
/**
|
|
119
|
+
* Per statement a subscription runs to check a change (visibility,
|
|
120
|
+
* `where`, related nodes). Default 2000, or `timeoutMs` when lower.
|
|
121
|
+
*/
|
|
122
|
+
subscriptionTimeoutMs?: number;
|
|
54
123
|
/** Named callbacks for `@populatedBy(callback:)`. */
|
|
55
124
|
callbacks?: Record<string, PopulatedByCallback>;
|
|
56
125
|
/**
|
|
@@ -99,9 +168,11 @@ export interface LoraGraphQLOptions extends ModelOptions, ObservabilityOptions {
|
|
|
99
168
|
* later failure leaves the earlier ones committed. `"operation"`: every
|
|
100
169
|
* root field in one transaction, committed only when the operation
|
|
101
170
|
* reports no error, rolled back (with `data: null`) otherwise. A
|
|
102
|
-
* `transaction` in the context takes precedence.
|
|
103
|
-
*
|
|
104
|
-
*
|
|
171
|
+
* `transaction` in the context takes precedence. Envelop / Yoga servers
|
|
172
|
+
* on `getSchema()` get the same with `lora.envelopPlugin()`; other
|
|
173
|
+
* servers calling graphql-js directly put a `lora.begin()` transaction
|
|
174
|
+
* in the context (without one, a one-time warning says each root field
|
|
175
|
+
* committed on its own).
|
|
105
176
|
*/
|
|
106
177
|
mutationTransaction?: "field" | "operation";
|
|
107
178
|
/**
|
|
@@ -244,15 +315,41 @@ export interface ExecuteArgs {
|
|
|
244
315
|
variables?: Record<string, unknown>;
|
|
245
316
|
operationName?: string;
|
|
246
317
|
context?: unknown;
|
|
318
|
+
/**
|
|
319
|
+
* Attach the operation's read-set to the result as a non-enumerable
|
|
320
|
+
* `readSet` property (never serialized to the client), for a response
|
|
321
|
+
* cache to invalidate with `lora.affects(result.readSet, change)`.
|
|
322
|
+
*/
|
|
323
|
+
readSet?: boolean;
|
|
247
324
|
}
|
|
325
|
+
/** `execute()`'s result; `readSet` is there when `ExecuteArgs.readSet` asked. */
|
|
326
|
+
export type LoraExecutionResult = ExecutionResult & {
|
|
327
|
+
readonly readSet?: ReadSet;
|
|
328
|
+
};
|
|
248
329
|
export declare class LoraGraphQL {
|
|
249
330
|
#private;
|
|
250
331
|
readonly model: GraphModel;
|
|
251
332
|
constructor(options: LoraGraphQLOptions);
|
|
252
333
|
/** The configured document guards as validation rules. */
|
|
253
334
|
validationRules(): ValidationRule[];
|
|
254
|
-
/**
|
|
255
|
-
|
|
335
|
+
/**
|
|
336
|
+
* The configured document guards as an Envelop / Yoga plugin. With
|
|
337
|
+
* `mutationTransaction: "operation"` it also runs a mutation with
|
|
338
|
+
* several root fields in one transaction, and it collapses identical
|
|
339
|
+
* errors across list indices, as `execute()` does.
|
|
340
|
+
*/
|
|
341
|
+
envelopPlugin(): ReturnType<typeof envelopPlugin> & {
|
|
342
|
+
onExecute(payload: {
|
|
343
|
+
args: ExecutionArgs;
|
|
344
|
+
executeFn: (args: ExecutionArgs) => unknown;
|
|
345
|
+
setExecuteFn: (fn: (args: ExecutionArgs) => unknown) => void;
|
|
346
|
+
}): {
|
|
347
|
+
onExecuteDone(payload: {
|
|
348
|
+
result: unknown;
|
|
349
|
+
setResult: (result: ExecutionResult) => void;
|
|
350
|
+
}): void;
|
|
351
|
+
};
|
|
352
|
+
};
|
|
256
353
|
/** The executable schema, for any graphql-js server. */
|
|
257
354
|
getSchema(): GraphQLSchema;
|
|
258
355
|
/** The client-facing SDL: generated types only, no model directives. */
|
|
@@ -266,8 +363,10 @@ export declare class LoraGraphQL {
|
|
|
266
363
|
*/
|
|
267
364
|
assertSchema(options?: AssertSchemaOptions): Promise<SchemaAssertion>;
|
|
268
365
|
/**
|
|
269
|
-
*
|
|
270
|
-
* use each relationship's
|
|
366
|
+
* Count nodes and measure relationship degrees (S6). Cost estimates then
|
|
367
|
+
* use each relationship's maximum degree, capped by its page limit,
|
|
368
|
+
* instead of the page limit alone; filters use node counts and mean
|
|
369
|
+
* degrees (see `src/compile/cost.ts`).
|
|
271
370
|
*/
|
|
272
371
|
analyze(options?: {
|
|
273
372
|
sample?: number;
|
|
@@ -286,6 +385,14 @@ export declare class LoraGraphQL {
|
|
|
286
385
|
* model; stable, so it can be snapshotted and reviewed as a diff.
|
|
287
386
|
*/
|
|
288
387
|
accessMatrix(): AccessEntry[];
|
|
388
|
+
/**
|
|
389
|
+
* Who may run an operation: per root field, the verdict for each kind
|
|
390
|
+
* of caller, from the same rules as `accessMatrix()`, plus the most
|
|
391
|
+
* restrictive verdict per caller. Takes a document (source or parsed)
|
|
392
|
+
* or the id of a persisted operation; `operationName` picks one of
|
|
393
|
+
* several operations.
|
|
394
|
+
*/
|
|
395
|
+
operationAccess(document: string | DocumentNode, operationName?: string): OperationAccess;
|
|
289
396
|
check(options?: CheckOptions): Promise<CheckReport>;
|
|
290
397
|
/**
|
|
291
398
|
* Compile a query without running it: one entry per root field, with its
|
|
@@ -332,7 +439,7 @@ export declare class LoraGraphQL {
|
|
|
332
439
|
* subscription runs with {@link LoraGraphQL.subscribe}; given one, this
|
|
333
440
|
* returns a `WRONG_OPERATION_TYPE` error.
|
|
334
441
|
*/
|
|
335
|
-
execute(args: ExecuteArgs): Promise<
|
|
442
|
+
execute(args: ExecuteArgs): Promise<LoraExecutionResult>;
|
|
336
443
|
/**
|
|
337
444
|
* Run a subscription: an async iterable of results, one per event, or a
|
|
338
445
|
* single result with the errors when it cannot start (an unknown id, a
|
package/dist/model/desugar.d.ts
CHANGED
|
@@ -41,3 +41,13 @@ export declare function desugarRule(ctx: DesugarContext, owner: NodeType | undef
|
|
|
41
41
|
export declare function desugarNode(ctx: DesugarContext, node: NodeType, where: unknown): unknown;
|
|
42
42
|
/** Model problems for a `@viewer` mapping, once the node types are built. */
|
|
43
43
|
export declare function checkViewer(viewer: ViewerMapping, nodes: ReadonlyMap<string, NodeType>, jwtType: string, problems: ModelProblem[]): void;
|
|
44
|
+
/**
|
|
45
|
+
* The parts a rule tests at its top level, through AND, OR and NOT:
|
|
46
|
+
* `node`, `jwt`, `viewer`, `source`, `target`, `edge`.
|
|
47
|
+
*/
|
|
48
|
+
export declare function ruleParts(where: unknown, out?: Set<string>): Set<string>;
|
|
49
|
+
/**
|
|
50
|
+
* A relationship property's rule that tests the relationship's ends or
|
|
51
|
+
* edge (`source`, `target`, `edge`): decided per relationship.
|
|
52
|
+
*/
|
|
53
|
+
export declare function testsRelationshipEnds(where: unknown): boolean;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const directiveTypeDefs = "\n \"A node label set. Defaults to the type name.\"\n directive @node(labels: [String!], plural: String) on OBJECT\n\n \"Natural key: required, unique and immutable; the tie-breaker of every sort and the anchor of cursors, global ids, updates and deletes. With generate: true, creates fill it with a UUID when the input leaves it out.\"\n directive @key(\n generate: Boolean = false\n \"VIEWER: a created key must start with the caller's @viewer claim and the separator (lou:tomorrowland).\"\n scope: KeyScope\n separator: String = \":\"\n ) on FIELD_DEFINITION\n\n enum KeyScope {\n VIEWER\n }\n\n \"On an interface field: every implementation declares this relationship (with @relationship, possibly of different types or directions), so clients can select it on the interface.\"\n directive @declareRelationship on FIELD_DEFINITION\n\n \"Offer this field as a grouping key of <plural>Grouped (needs @query(aggregate: true)).\"\n directive @groupBy on FIELD_DEFINITION\n\n \"A field computed in JavaScript by the resolver passed in the resolvers option. requires is a selection on this type (for example name capacity) fetched in the same statement, so the resolver reads it from its source.\"\n directive @customResolver(requires: String) on FIELD_DEFINITION\n\n \"On a custom scalar: how its values are stored. Filters, sorts and indexes follow the storage type; the scalar is passed through unless the scalars option supplies an implementation.\"\n directive @storedAs(type: StorageType!) on SCALAR\n\n enum StorageType {\n STRING\n INT\n FLOAT\n BOOLEAN\n DATETIME\n DATE\n }\n\n \"A uniqueness constraint.\"\n directive @unique on FIELD_DEFINITION\n\n \"An explicit index. Usually unnecessary: inferred from @filterable and @sortable.\"\n directive @index(kind: IndexKind!) on FIELD_DEFINITION\n\n \"An edge. The target type is the field's type.\"\n directive @relationship(\n type: String!\n direction: RelationshipDirection!\n properties: String\n \"UNDIRECTED reads follow the relationship both ways; writes use direction.\"\n queryDirection: QueryDirection = DIRECTED\n \"What deleting this node does to nodes reached through the field.\"\n onDelete: OnDelete = DETACH\n \"Which nested writes mutation inputs offer for the field.\"\n nestedOperations: [NestedOperation!] = [\n CREATE\n CONNECT\n DISCONNECT\n UPDATE\n DELETE\n ]\n \"false: no aggregate on the field's connection and no aggregate filter.\"\n aggregate: Boolean = true\n ) on FIELD_DEFINITION\n\n enum NestedOperation {\n CREATE\n CONNECT\n DISCONNECT\n UPDATE\n DELETE\n }\n\n enum QueryDirection {\n DIRECTED\n UNDIRECTED\n }\n\n \"DETACH removes the relationships; CASCADE deletes the related nodes too; RESTRICT refuses while any exist.\"\n enum OnDelete {\n DETACH\n CASCADE\n RESTRICT\n }\n\n \"Which mutations may set the field. @readonly is onCreate: false, onUpdate: false.\"\n directive @settable(\n onCreate: Boolean = true\n onUpdate: Boolean = true\n ) on FIELD_DEFINITION\n\n \"Whether the field can be read (onRead: false makes it write-only) or aggregated.\"\n directive @selectable(\n onRead: Boolean = true\n onAggregate: Boolean = true\n ) on FIELD_DEFINITION\n\n \"Set by a callback passed to LoraGraphQL({ callbacks }) on these operations; never client-settable.\"\n directive @populatedBy(\n callback: String!\n operations: [TimestampOperation!]! = [CREATE, UPDATE]\n ) on FIELD_DEFINITION\n\n \"The shape of the request's claims. Rules may only test declared claims.\"\n directive @jwt on OBJECT\n\n \"Where a declared claim lives in the token, e.g. app_metadata.roles.\"\n directive @jwtClaim(path: String!) on FIELD_DEFINITION\n\n \"The claim that identifies the caller: the node of `type` whose `field` (a @key or @unique field) equals it. Enables isViewer and viewer in rules.\"\n directive @viewer(type: String!, field: String!) on FIELD_DEFINITION\n\n \"Properties carried by a relationship type.\"\n directive @relationshipProperties on OBJECT\n\n \"API name differs from the stored property.\"\n directive @alias(property: String!) on FIELD_DEFINITION\n\n \"Stored, never exposed.\"\n directive @private on FIELD_DEFINITION\n\n \"Declared upper bound on a relationship's fan-out, used by cost estimates.\"\n directive @cardinality(max: Int!) on FIELD_DEFINITION\n\n \"Generated read operations for a node type. Reads are on by default.\"\n directive @query(\n read: Boolean = true\n aggregate: Boolean = false\n ) on OBJECT | INTERFACE | UNION\n\n \"The plural of an interface or union, for its root field.\"\n directive @plural(value: String!) on INTERFACE | UNION\n\n \"Filter operators for a field. Without arguments: EQ and IN. On a relationship field: enables relationship filters.\"\n directive @filterable(byValue: [FilterOperator!]) on FIELD_DEFINITION\n\n \"Sort and keyset-paginate on this field.\"\n directive @sortable on FIELD_DEFINITION\n\n \"Page size bounds for lists of this type.\"\n directive @limit(\n default: Int\n max: Int\n ) on OBJECT | INTERFACE | UNION | FIELD_DEFINITION\n\n \"Expose an opaque global id derived from this @key field.\"\n directive @relayId on FIELD_DEFINITION\n\n \"Full-text search over String fields: a FULLTEXT index and a search root field per entry.\"\n directive @fulltext(indexes: [FulltextIndex!]!) on OBJECT\n\n \"A vector embedding ([Float!]): a VECTOR index and a similarity root field.\"\n directive @vector(\n dimensions: Int!\n similarity: VectorSimilarity = COSINE\n queryName: String\n ) on FIELD_DEFINITION\n\n input FulltextIndex {\n \"Index name. Default: <label>_search for the first, required after.\"\n name: String\n fields: [String!]!\n analyzer: FulltextAnalyzer = STANDARD\n \"Root field name. Default: search<Plural>, or search<Plural>By<Name>.\"\n queryName: String\n }\n\n enum FulltextAnalyzer {\n STANDARD\n SIMPLE\n }\n\n enum VectorSimilarity {\n COSINE\n EUCLIDEAN\n }\n\n \"Generated subscriptions to changes made through the library. None without this directive.\"\n directive @subscription(\n operations: [MutationOperation!]! = [CREATE, UPDATE, DELETE]\n \"Also send CONNECT and DISCONNECT events for the type's relationships.\"\n relationships: Boolean = false\n \"Send the stored values before an update or delete (one extra read per write).\"\n previousState: Boolean = false\n ) on OBJECT\n\n \"Generated mutations for a node type. None without this directive.\"\n directive @mutation(\n operations: [MutationOperation!]! = [CREATE, UPDATE, DELETE]\n ) on OBJECT\n\n \"Value stored on create when the input leaves the field out.\"\n directive @default(value: DefaultValue!) on FIELD_DEFINITION\n\n \"Set to the current time by the listed operations; never client-settable.\"\n directive @timestamp(\n operations: [TimestampOperation!]! = [CREATE, UPDATE]\n ) on FIELD_DEFINITION\n\n \"Readable but never client-settable.\"\n directive @readonly on FIELD_DEFINITION\n\n \"A field backed by a Cypher statement. `this` is the parent node; arguments are $parameters, and $jwt holds the request's claims.\"\n directive @cypher(statement: String!, columnName: String) on FIELD_DEFINITION\n\n \"Require an authenticated request (a jwt in the context) for these operations.\"\n directive @authentication(\n operations: [AuthOperation!]! = [\n READ\n CREATE\n UPDATE\n DELETE\n CREATE_RELATIONSHIP\n DELETE_RELATIONSHIP\n SUBSCRIBE\n ]\n \"Claims the request must also satisfy, such as a role in roles.\"\n jwt: AuthorizationWhere\n ) on OBJECT | FIELD_DEFINITION\n\n \"Row-level rules over the node and the request's claims, compiled into the statements.\"\n directive @authorization(\n filter: [AuthorizationFilterRule!]\n validate: [AuthorizationValidateRule!]\n \"On a type: false keeps the schema's bypass (see @authorizationDefaults) from skipping this type's rules.\"\n bypass: Boolean\n \"On a @mutation type: operations deliberately open to every caller, so check() does not report them as unguarded.\"\n public: [AuthOperation!]\n \"On a scalar field: a row failing unless reads the field as value (null when left out) instead of failing the request.\"\n mask: [AuthorizationMask!]\n ) on OBJECT | FIELD_DEFINITION\n\n \"Named rules over claims, usable in any rule as { rule: name }, on `extend schema`.\"\n directive @authorizationRules(\n rules: [AuthorizationRuleDefinition!]!\n ) on SCHEMA\n\n input AuthorizationRuleDefinition {\n name: String!\n where: AuthorizationWhere!\n }\n\n \"A named rule of this type, usable in its rules as { rule: name } and through a relationship to it as { rel: { rule: name } }.\"\n directive @authorizationRule(\n name: String!\n where: AuthorizationWhere!\n ) repeatable on OBJECT\n\n \"Schema-wide authorization settings, on `extend schema`.\"\n directive @authorizationDefaults(\n \"A claims-only test (jwt, AND, OR, NOT): a request passing it skips every filter and validate rule (not @authentication).\"\n bypass: AuthorizationWhere\n \"The write rule of every @mutation type that declares no CREATE, UPDATE or DELETE rule of its own.\"\n mutations: AuthorizationWhere\n ) on SCHEMA\n\n input AuthorizationMask {\n unless: AuthorizationWhere!\n value: DefaultValue\n }\n\n input AuthorizationFilterRule {\n operations: [AuthOperation!]! = [READ, UPDATE, DELETE]\n \"Default true: without a token the rule denies.\"\n requireAuthentication: Boolean\n where: AuthorizationWhere!\n }\n\n input AuthorizationValidateRule {\n operations: [AuthOperation!]! = [READ, CREATE, UPDATE, DELETE]\n when: [AuthorizationWhen!]! = [BEFORE, AFTER]\n \"Default true: without a token the rule denies.\"\n requireAuthentication: Boolean\n where: AuthorizationWhere!\n }\n\n enum MutationOperation {\n CREATE\n UPDATE\n DELETE\n }\n\n enum TimestampOperation {\n CREATE\n UPDATE\n }\n\n enum AuthOperation {\n READ\n CREATE\n UPDATE\n DELETE\n CREATE_RELATIONSHIP\n DELETE_RELATIONSHIP\n SUBSCRIBE\n \"On a relationship field: creating one of its relationships (connect, nested create).\"\n CONNECT\n \"On a relationship field: removing one of its relationships (disconnect).\"\n DISCONNECT\n \"On a relationship field: setting properties on an existing relationship (edge update, re-connect).\"\n UPDATE_EDGE\n \"On a relationship field: reading, filtering, sorting or aggregating its properties.\"\n READ_EDGE\n }\n\n enum AuthorizationWhen {\n BEFORE\n AFTER\n }\n\n \"Any literal.\"\n scalar DefaultValue\n\n \"{ node: <Type>Where-shaped filter, jwt: claim filter, AND, OR, NOT }. String values starting with $jwt. are replaced by claims.\"\n scalar AuthorizationWhere\n\n enum IndexKind {\n RANGE\n TEXT\n POINT\n }\n\n enum RelationshipDirection {\n IN\n OUT\n }\n\n enum FilterOperator {\n EQ\n IN\n LT\n LTE\n GT\n GTE\n CONTAINS\n STARTS_WITH\n ENDS_WITH\n WITHIN_BBOX\n DISTANCE\n INCLUDES\n IS_NULL\n CASE_INSENSITIVE\n }\n\n scalar BigInt\n scalar Date\n scalar Time\n scalar LocalTime\n scalar DateTime\n scalar LocalDateTime\n scalar Duration\n\n type Point {\n longitude: Float!\n latitude: Float!\n height: Float\n srid: Int!\n crs: String!\n }\n\n type CartesianPoint {\n x: Float!\n y: Float!\n z: Float\n srid: Int!\n crs: String!\n }\n";
|
|
1
|
+
export declare const directiveTypeDefs = "\n \"A node label set. Defaults to the type name.\"\n directive @node(labels: [String!], plural: String) on OBJECT\n\n \"Natural key: required, unique and immutable; the tie-breaker of every sort and the anchor of cursors, global ids, updates and deletes. With generate: true, creates fill it with a UUID when the input leaves it out.\"\n directive @key(\n generate: Boolean = false\n \"VIEWER: a created key must start with the caller's @viewer claim and the separator (lou:tomorrowland).\"\n scope: KeyScope\n separator: String = \":\"\n ) on FIELD_DEFINITION\n\n enum KeyScope {\n VIEWER\n }\n\n \"On an interface field: every implementation declares this relationship (with @relationship, possibly of different types or directions), so clients can select it on the interface.\"\n directive @declareRelationship on FIELD_DEFINITION\n\n \"Offer this field as a grouping key of <plural>Grouped (needs @query(aggregate: true)).\"\n directive @groupBy on FIELD_DEFINITION\n\n \"A field computed in JavaScript by the resolver passed in the resolvers option. requires is a selection on this type (for example name capacity) fetched in the same statement, so the resolver reads it from its source.\"\n directive @customResolver(requires: String) on FIELD_DEFINITION\n\n \"On a custom scalar: how its values are stored. Filters, sorts and indexes follow the storage type; the scalar is passed through unless the scalars option supplies an implementation.\"\n directive @storedAs(type: StorageType!) on SCALAR\n\n enum StorageType {\n STRING\n INT\n FLOAT\n BOOLEAN\n DATETIME\n DATE\n }\n\n \"A uniqueness constraint.\"\n directive @unique on FIELD_DEFINITION\n\n \"No two nodes of this type (among those matching where) share the values of fields: scalar fields, single relationships (by the target's @key) and at most one list relationship (by the set of target keys). Enforced by every generated mutation, for every caller.\"\n directive @uniqueTogether(\n fields: [String!]!\n where: NodeWhere\n ) repeatable on OBJECT\n\n \"An explicit index. Usually unnecessary: inferred from @filterable and @sortable.\"\n directive @index(kind: IndexKind!) on FIELD_DEFINITION\n\n \"An edge. The target type is the field's type.\"\n directive @relationship(\n type: String!\n direction: RelationshipDirection!\n properties: String\n \"UNDIRECTED reads follow the relationship both ways; writes use direction.\"\n queryDirection: QueryDirection = DIRECTED\n \"What deleting this node does to nodes reached through the field.\"\n onDelete: OnDelete = DETACH\n \"Which nested writes mutation inputs offer for the field.\"\n nestedOperations: [NestedOperation!] = [\n CREATE\n CONNECT\n DISCONNECT\n UPDATE\n DELETE\n ]\n \"false: no aggregate on the field's connection and no aggregate filter.\"\n aggregate: Boolean = true\n ) on FIELD_DEFINITION\n\n \"UPDATE changes a connected node and its relationship properties in place; UPDATE_EDGE only the properties.\"\n enum NestedOperation {\n CREATE\n CONNECT\n DISCONNECT\n UPDATE\n UPDATE_EDGE\n DELETE\n }\n\n enum QueryDirection {\n DIRECTED\n UNDIRECTED\n }\n\n \"DETACH removes the relationships; CASCADE deletes the related nodes too; RESTRICT refuses while any exist.\"\n enum OnDelete {\n DETACH\n CASCADE\n RESTRICT\n }\n\n \"Which mutations may set the field. @readonly is onCreate: false, onUpdate: false.\"\n directive @settable(\n onCreate: Boolean = true\n onUpdate: Boolean = true\n ) on FIELD_DEFINITION\n\n \"Whether the field can be read (onRead: false makes it write-only) or aggregated.\"\n directive @selectable(\n onRead: Boolean = true\n onAggregate: Boolean = true\n ) on FIELD_DEFINITION\n\n \"Set by a callback passed to LoraGraphQL({ callbacks }) on these operations. Client-settable only with an explicit @settable(onCreate: true) or (onUpdate: true) and a field-level @authorization(validate:) rule for that operation.\"\n directive @populatedBy(\n callback: String!\n operations: [TimestampOperation!]! = [CREATE, UPDATE]\n ) on FIELD_DEFINITION\n\n \"The shape of the request's claims. Rules may only test declared claims.\"\n directive @jwt on OBJECT\n\n \"Where a declared claim lives in the token, e.g. app_metadata.roles.\"\n directive @jwtClaim(path: String!) on FIELD_DEFINITION\n\n \"The claim that identifies the caller: the node of `type` whose `field` (a @key or @unique field) equals it. Enables isViewer and viewer in rules.\"\n directive @viewer(type: String!, field: String!) on FIELD_DEFINITION\n\n \"Properties carried by a relationship type.\"\n directive @relationshipProperties on OBJECT\n\n \"API name differs from the stored property.\"\n directive @alias(property: String!) on FIELD_DEFINITION\n\n \"Stored, never exposed.\"\n directive @private on FIELD_DEFINITION\n\n \"Declared upper bound on a relationship's fan-out, used by cost estimates.\"\n directive @cardinality(max: Int!) on FIELD_DEFINITION\n\n \"The most items a list argument of a @cypher field takes; more is BAD_USER_INPUT before the statement runs. Without it, maxListArgument applies.\"\n directive @size(max: Int!) on ARGUMENT_DEFINITION\n\n \"Bounds of an Int or Float argument of a @cypher field (each item, for a list); a value outside them is BAD_USER_INPUT before the statement runs.\"\n directive @range(min: Float, max: Float) on ARGUMENT_DEFINITION\n\n \"Generated read operations for a node type. Reads are on by default.\"\n directive @query(\n read: Boolean = true\n aggregate: Boolean = false\n ) on OBJECT | INTERFACE | UNION\n\n \"The plural of an interface or union, for its root field.\"\n directive @plural(value: String!) on INTERFACE | UNION\n\n \"Filter operators for a field. Without arguments: EQ and IN. On a relationship field: enables relationship filters.\"\n directive @filterable(byValue: [FilterOperator!]) on FIELD_DEFINITION\n\n \"Sort and keyset-paginate on this field.\"\n directive @sortable on FIELD_DEFINITION\n\n \"Page size bounds for lists of this type.\"\n directive @limit(\n default: Int\n max: Int\n ) on OBJECT | INTERFACE | UNION | FIELD_DEFINITION\n\n \"Expose an opaque global id derived from this @key field.\"\n directive @relayId on FIELD_DEFINITION\n\n \"Full-text search over String fields: a FULLTEXT index and a search root field per entry.\"\n directive @fulltext(indexes: [FulltextIndex!]!) on OBJECT\n\n \"A vector embedding ([Float!]): a VECTOR index and a similarity root field.\"\n directive @vector(\n dimensions: Int!\n similarity: VectorSimilarity = COSINE\n queryName: String\n ) on FIELD_DEFINITION\n\n input FulltextIndex {\n \"Index name. Default: <label>_search for the first, required after.\"\n name: String\n fields: [String!]!\n analyzer: FulltextAnalyzer = STANDARD\n \"Root field name. Default: search<Plural>, or search<Plural>By<Name>.\"\n queryName: String\n }\n\n enum FulltextAnalyzer {\n STANDARD\n SIMPLE\n }\n\n enum VectorSimilarity {\n COSINE\n EUCLIDEAN\n }\n\n \"Generated subscriptions to changes made through the library. None without this directive.\"\n directive @subscription(\n operations: [MutationOperation!]! = [CREATE, UPDATE, DELETE]\n \"Also send CONNECT and DISCONNECT events for the type's relationships.\"\n relationships: Boolean = false\n \"Send the stored values before an update or delete (one extra read per write).\"\n previousState: Boolean = false\n ) on OBJECT\n\n \"Generated mutations for a node type. None without this directive.\"\n directive @mutation(\n operations: [MutationOperation!]! = [CREATE, UPDATE, DELETE]\n ) on OBJECT\n\n \"Value stored on create when the input leaves the field out.\"\n directive @default(value: DefaultValue!) on FIELD_DEFINITION\n\n \"Set to the current time by the listed operations. Client-settable only with an explicit @settable(onCreate: true) or (onUpdate: true) and a field-level @authorization(validate:) rule for that operation.\"\n directive @timestamp(\n operations: [TimestampOperation!]! = [CREATE, UPDATE]\n ) on FIELD_DEFINITION\n\n \"Readable but never client-settable.\"\n directive @readonly on FIELD_DEFINITION\n\n \"A field backed by a Cypher statement. `this` is the parent node; arguments are $parameters, $jwt holds the request's claims, and $viewer the caller's @viewer node key.\"\n directive @cypher(statement: String!, columnName: String) on FIELD_DEFINITION\n\n \"Require an authenticated request (a jwt in the context) for these operations.\"\n directive @authentication(\n operations: [AuthOperation!]! = [\n READ\n CREATE\n UPDATE\n DELETE\n CREATE_RELATIONSHIP\n DELETE_RELATIONSHIP\n SUBSCRIBE\n ]\n \"Claims the request must also satisfy, such as a role in roles.\"\n jwt: AuthorizationWhere\n ) on OBJECT | FIELD_DEFINITION\n\n \"Row-level rules over the node and the request's claims, compiled into the statements.\"\n directive @authorization(\n filter: [AuthorizationFilterRule!]\n validate: [AuthorizationValidateRule!]\n \"On a type: false keeps the schema's bypass (see @authorizationDefaults) from skipping this type's rules; true lets it skip them, as without the argument, and tells check() that was meant.\"\n bypass: Boolean\n \"On a @mutation type: operations deliberately open to every caller, so check() does not report them as unguarded.\"\n public: [AuthOperation!]\n \"On a scalar field: a row failing unless reads the field as value (null when left out) instead of failing the request.\"\n mask: [AuthorizationMask!]\n ) on OBJECT | FIELD_DEFINITION\n\n \"Named rules over claims, usable in any rule as { rule: name }, on `extend schema`.\"\n directive @authorizationRules(\n rules: [AuthorizationRuleDefinition!]!\n ) on SCHEMA\n\n input AuthorizationRuleDefinition {\n name: String!\n where: AuthorizationWhere!\n }\n\n \"A named rule of this type, usable in its rules as { rule: name } and through a relationship to it as { rel: { rule: name } }.\"\n directive @authorizationRule(\n name: String!\n where: AuthorizationWhere!\n ) repeatable on OBJECT\n\n \"Schema-wide authorization settings, on `extend schema`.\"\n directive @authorizationDefaults(\n \"A claims-only test (jwt, AND, OR, NOT): a request passing it skips every filter and validate rule (not @authentication).\"\n bypass: AuthorizationWhere\n \"The write rule of every @mutation type that declares no CREATE, UPDATE or DELETE rule of its own.\"\n mutations: AuthorizationWhere\n ) on SCHEMA\n\n input AuthorizationMask {\n unless: AuthorizationWhere!\n value: DefaultValue\n }\n\n input AuthorizationFilterRule {\n operations: [AuthOperation!]! = [READ, UPDATE, DELETE]\n \"Default true: without a token the rule denies.\"\n requireAuthentication: Boolean\n where: AuthorizationWhere!\n }\n\n input AuthorizationValidateRule {\n operations: [AuthOperation!]! = [READ, CREATE, UPDATE, DELETE]\n when: [AuthorizationWhen!]! = [BEFORE, AFTER]\n \"Default true: without a token the rule denies.\"\n requireAuthentication: Boolean\n where: AuthorizationWhere!\n }\n\n enum MutationOperation {\n CREATE\n UPDATE\n DELETE\n }\n\n enum TimestampOperation {\n CREATE\n UPDATE\n }\n\n enum AuthOperation {\n READ\n CREATE\n UPDATE\n DELETE\n CREATE_RELATIONSHIP\n DELETE_RELATIONSHIP\n SUBSCRIBE\n \"On a relationship field: creating one of its relationships (connect, nested create).\"\n CONNECT\n \"On a relationship field: removing one of its relationships (disconnect).\"\n DISCONNECT\n \"On a relationship field: setting properties on an existing relationship (edge update, re-connect).\"\n UPDATE_EDGE\n \"On a relationship field: reading, filtering, sorting or aggregating its properties.\"\n READ_EDGE\n }\n\n enum AuthorizationWhen {\n BEFORE\n AFTER\n }\n\n \"Any literal.\"\n scalar DefaultValue\n\n \"{ node: <Type>Where-shaped filter, jwt: claim filter, AND, OR, NOT }. String values starting with $jwt. are replaced by claims.\"\n scalar AuthorizationWhere\n\n \"A <Type>Where-shaped filter over the type the directive is on.\"\n scalar NodeWhere\n\n enum IndexKind {\n RANGE\n TEXT\n POINT\n }\n\n enum RelationshipDirection {\n IN\n OUT\n }\n\n enum FilterOperator {\n EQ\n IN\n LT\n LTE\n GT\n GTE\n CONTAINS\n STARTS_WITH\n ENDS_WITH\n WITHIN_BBOX\n DISTANCE\n INCLUDES\n IS_NULL\n CASE_INSENSITIVE\n }\n\n scalar BigInt\n scalar Date\n scalar Time\n scalar LocalTime\n scalar DateTime\n scalar LocalDateTime\n scalar Duration\n\n type Point {\n longitude: Float!\n latitude: Float!\n height: Float\n srid: Int!\n crs: String!\n }\n\n type CartesianPoint {\n x: Float!\n y: Float!\n z: Float\n srid: Int!\n crs: String!\n }\n";
|
|
2
2
|
/** Names defined by the prelude; never treated as user types. */
|
|
3
3
|
export declare const PRELUDE_TYPES: Set<string>;
|
package/dist/model/inputs.d.ts
CHANGED
|
@@ -10,3 +10,12 @@ export declare function isUpdatable(model: GraphModel, node: NodeType, seen?: Se
|
|
|
10
10
|
export declare function offersNested(model: GraphModel, rel: RelationshipField, update: boolean, seen?: Set<string>): boolean;
|
|
11
11
|
/** Whether any property of a @relationshipProperties type is settable on `op`. */
|
|
12
12
|
export declare function hasSettable(props: RelationshipPropertiesType, op: "CREATE" | "UPDATE"): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* The first value in `value` (each item, for a list) outside `range`, or
|
|
15
|
+
* undefined. Null and absent values are not checked: nullability is the
|
|
16
|
+
* type's business.
|
|
17
|
+
*/
|
|
18
|
+
export declare function outOfRange(value: unknown, range: {
|
|
19
|
+
min?: number | undefined;
|
|
20
|
+
max?: number | undefined;
|
|
21
|
+
}): number | undefined;
|
package/dist/model/types.d.ts
CHANGED
|
@@ -84,7 +84,11 @@ export interface ScalarField extends FieldBase {
|
|
|
84
84
|
} | undefined;
|
|
85
85
|
description: string | undefined;
|
|
86
86
|
}
|
|
87
|
-
export type NestedOperation = "CREATE" | "CONNECT" | "DISCONNECT"
|
|
87
|
+
export type NestedOperation = "CREATE" | "CONNECT" | "DISCONNECT"
|
|
88
|
+
/** `update: [{ key, edge, node }]`: the connected node and the properties. */
|
|
89
|
+
| "UPDATE"
|
|
90
|
+
/** `update: [{ key, edge }]`: the relationship's properties only. */
|
|
91
|
+
| "UPDATE_EDGE" | "DELETE";
|
|
88
92
|
export interface RelationshipField extends FieldBase {
|
|
89
93
|
kind: "relationship";
|
|
90
94
|
name: string;
|
|
@@ -140,6 +144,13 @@ export interface CypherArgument {
|
|
|
140
144
|
type: TypeShape;
|
|
141
145
|
defaultValue: unknown;
|
|
142
146
|
description: string | undefined;
|
|
147
|
+
/** `@size(max:)`: the most items a list argument takes. */
|
|
148
|
+
maxItems?: number | undefined;
|
|
149
|
+
/** `@range(min:, max:)`: bounds of an Int or Float argument. */
|
|
150
|
+
range?: {
|
|
151
|
+
min?: number | undefined;
|
|
152
|
+
max?: number | undefined;
|
|
153
|
+
} | undefined;
|
|
143
154
|
}
|
|
144
155
|
export interface CypherField extends FieldBase {
|
|
145
156
|
kind: "cypher";
|
|
@@ -180,8 +191,15 @@ export interface CustomField extends FieldBase {
|
|
|
180
191
|
description: string | undefined;
|
|
181
192
|
}
|
|
182
193
|
export type Field = ScalarField | RelationshipField | CypherField | CustomField;
|
|
183
|
-
/**
|
|
194
|
+
/**
|
|
195
|
+
* A placeholder inside a rule string: `${jwt.path}`, `${context.path}`,
|
|
196
|
+
* `${viewer.field}`, or a value of the node the rule is about:
|
|
197
|
+
* `${node.path}` in a type's rules, `${source.path}` / `${target.path}` /
|
|
198
|
+
* `${edge.property}` in a relationship field's rules.
|
|
199
|
+
*/
|
|
184
200
|
export declare const PLACEHOLDER: RegExp;
|
|
201
|
+
/** The placeholder sources that name a node (or edge) of the rule itself. */
|
|
202
|
+
export declare const RULE_REFERENCES: Set<string>;
|
|
185
203
|
/** `{ node, jwt, AND, OR, NOT }`, as written in `@authorization`. */
|
|
186
204
|
export type AuthorizationWhere = Record<string, unknown>;
|
|
187
205
|
export interface AuthorizationFilterRule {
|
|
@@ -202,7 +220,10 @@ export interface AuthorizationValidateRule {
|
|
|
202
220
|
export interface Authorization {
|
|
203
221
|
filter: readonly AuthorizationFilterRule[];
|
|
204
222
|
validate: readonly AuthorizationValidateRule[];
|
|
205
|
-
/**
|
|
223
|
+
/**
|
|
224
|
+
* `bypass: false`: the schema's bypass does not skip these rules.
|
|
225
|
+
* `true`: it does, as when left out, and `check()` does not note it.
|
|
226
|
+
*/
|
|
206
227
|
bypass?: boolean;
|
|
207
228
|
/** `public:` operations deliberately open to every caller. */
|
|
208
229
|
public?: ReadonlySet<AuthOperation>;
|
|
@@ -248,8 +269,28 @@ export interface NodeType {
|
|
|
248
269
|
search: readonly SearchIndex[];
|
|
249
270
|
/** Interfaces the type implements. */
|
|
250
271
|
interfaces: readonly string[];
|
|
272
|
+
/** `@uniqueTogether`: combinations no two nodes of the type may share. */
|
|
273
|
+
uniqueTogether: readonly UniqueTogether[];
|
|
251
274
|
description: string | undefined;
|
|
252
275
|
}
|
|
276
|
+
/**
|
|
277
|
+
* `@uniqueTogether(fields:, where:)`: no two nodes of the type (among those
|
|
278
|
+
* matching `where`) hold the same value of every field. A combination
|
|
279
|
+
* with a null scalar, a missing single relationship or an empty set is
|
|
280
|
+
* exempt, as a null is in a unique index.
|
|
281
|
+
*/
|
|
282
|
+
export interface UniqueTogether {
|
|
283
|
+
/** The fields as declared, for messages. */
|
|
284
|
+
fields: readonly string[];
|
|
285
|
+
/** Scalar fields, compared by stored value. */
|
|
286
|
+
scalars: readonly ScalarField[];
|
|
287
|
+
/** Single relationships, compared by the target's @key. */
|
|
288
|
+
singles: readonly RelationshipField[];
|
|
289
|
+
/** At most one list relationship, compared as the set of target keys. */
|
|
290
|
+
set: RelationshipField | undefined;
|
|
291
|
+
/** A `<Type>Where`-shaped filter: only nodes matching it are compared. */
|
|
292
|
+
where: Record<string, unknown> | undefined;
|
|
293
|
+
}
|
|
253
294
|
export type SearchIndex = {
|
|
254
295
|
kind: "fulltext";
|
|
255
296
|
name: string;
|
|
@@ -346,6 +387,8 @@ export interface GraphModel {
|
|
|
346
387
|
} | undefined;
|
|
347
388
|
/** Secret cursors are signed with, when configured. */
|
|
348
389
|
cursorSecret: string | undefined;
|
|
390
|
+
/** The global page-size cap (`maxLimit`); also caps `nodes(ids:)`. */
|
|
391
|
+
maxLimit?: number | undefined;
|
|
349
392
|
}
|
|
350
393
|
export interface ModelWarning {
|
|
351
394
|
type: string;
|