@loradb/lora-graphql 0.24.1 → 0.25.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 +151 -31
- package/dist/analyze/input-schemas.d.ts +11 -0
- package/dist/analyze/plans.d.ts +8 -0
- package/dist/analyze/stored.d.ts +29 -0
- package/dist/cli.js +263 -244
- package/dist/cli.js.map +1 -1
- package/dist/compile/cache.d.ts +9 -1
- package/dist/compile/context.d.ts +3 -0
- package/dist/compile/read/common.d.ts +2 -0
- package/dist/compile/read/root.d.ts +7 -0
- package/dist/compile/selection.d.ts +5 -0
- package/dist/diff-CdZZG-N1.js +134 -0
- package/dist/diff-CdZZG-N1.js.map +1 -0
- package/dist/driver.d.ts +36 -1
- package/dist/errors.d.ts +21 -1
- package/dist/events/outbox.d.ts +14 -0
- package/dist/events/worker.d.ts +67 -0
- package/dist/execute/mutate/checks.d.ts +16 -0
- package/dist/execute/mutate/env.d.ts +50 -0
- package/dist/execute/mutate/errors.d.ts +6 -0
- package/dist/execute/mutate/links.d.ts +38 -0
- package/dist/execute/mutate/plan.d.ts +5 -0
- package/dist/execute/mutate/runner.d.ts +101 -25
- package/dist/execute/mutate/single-call.d.ts +92 -0
- package/dist/execute/mutate/triggers.d.ts +21 -0
- package/dist/execute/mutate/unique-together.d.ts +5 -1
- package/dist/execute/mutate/validate.d.ts +11 -0
- package/dist/execute/transaction.d.ts +13 -2
- package/dist/index.d.ts +7 -2
- package/dist/index.js +16 -15
- package/dist/lora-graphql-D11QFUkf.js +17222 -0
- package/dist/lora-graphql-D11QFUkf.js.map +1 -0
- package/dist/lora-graphql.d.ts +30 -1
- package/dist/model/build/authorization.d.ts +11 -1
- package/dist/model/build/constraints.d.ts +22 -0
- package/dist/model/build/rule-checks.d.ts +6 -1
- package/dist/model/build/trigger.d.ts +9 -0
- package/dist/model/constraints.d.ts +112 -0
- package/dist/model/desugar.d.ts +7 -0
- package/dist/model/directives.d.ts +1 -1
- package/dist/model/types.d.ts +34 -0
- package/dist/options.d.ts +76 -3
- package/dist/results.d.ts +12 -1
- package/dist/schema/scalars.d.ts +8 -1
- package/dist/standard-schema.d.ts +37 -0
- package/dist/testing.js +1 -1
- package/package.json +2 -2
- package/dist/diff-K8-GSeKH.js +0 -100
- package/dist/diff-K8-GSeKH.js.map +0 -1
- package/dist/driver-CXKPLfkz.js +0 -14447
- package/dist/driver-CXKPLfkz.js.map +0 -1
package/dist/driver.d.ts
CHANGED
|
@@ -36,10 +36,22 @@ export interface RunOptions {
|
|
|
36
36
|
* only stored properties. Cheap enough to run synchronously.
|
|
37
37
|
*/
|
|
38
38
|
bounded?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* A mutation sent as one write batch: counted against
|
|
41
|
+
* `maxQueuedWrites`, as a transaction waiting for the lock is.
|
|
42
|
+
*/
|
|
43
|
+
queued?: boolean;
|
|
39
44
|
}
|
|
40
45
|
/** An open interactive transaction: statements see earlier writes. */
|
|
41
46
|
export interface DriverTransaction {
|
|
42
47
|
execute(statement: Statement): Promise<QueryResult>;
|
|
48
|
+
/**
|
|
49
|
+
* Run several statements in one call, in order, each seeing the writes
|
|
50
|
+
* of those before it. The first failure rolls the transaction back and
|
|
51
|
+
* rejects with that statement's error. Optional: without it the
|
|
52
|
+
* statements run one `execute()` at a time.
|
|
53
|
+
*/
|
|
54
|
+
executeMany?(statements: Statement[]): Promise<QueryResult[]>;
|
|
43
55
|
commit(): Promise<void>;
|
|
44
56
|
rollback(): Promise<void>;
|
|
45
57
|
/** False once committed, rolled back, or failed (a failure rolls back). */
|
|
@@ -75,6 +87,12 @@ export interface DriverChange {
|
|
|
75
87
|
startId?: number;
|
|
76
88
|
endId?: number;
|
|
77
89
|
properties?: Record<string, unknown>;
|
|
90
|
+
/** For an update: the properties the transaction set and removed. */
|
|
91
|
+
setKeys?: string[];
|
|
92
|
+
removedKeys?: string[];
|
|
93
|
+
/** For a node update: the labels it added and removed. */
|
|
94
|
+
addedLabels?: string[];
|
|
95
|
+
removedLabels?: string[];
|
|
78
96
|
}
|
|
79
97
|
export interface DriverChangeBatch {
|
|
80
98
|
lsn: number;
|
|
@@ -107,11 +125,21 @@ export interface LoraDatabaseLike {
|
|
|
107
125
|
}): AsyncIterable<DriverChangeBatch> & {
|
|
108
126
|
ready: Promise<void>;
|
|
109
127
|
};
|
|
110
|
-
begin?(mode?: "read_write" | "read_only"
|
|
128
|
+
begin?(mode?: "read_write" | "read_only", options?: {
|
|
129
|
+
timeoutMs?: number;
|
|
130
|
+
signal?: AbortSignal;
|
|
131
|
+
}): Promise<{
|
|
111
132
|
execute(query: string, params?: never, options?: {
|
|
112
133
|
timeoutMs?: number;
|
|
113
134
|
signal?: AbortSignal;
|
|
114
135
|
}): Promise<QueryResult>;
|
|
136
|
+
executeMany?(statements: Array<{
|
|
137
|
+
query: string;
|
|
138
|
+
params?: never;
|
|
139
|
+
}>, options?: {
|
|
140
|
+
timeoutMs?: number;
|
|
141
|
+
signal?: AbortSignal;
|
|
142
|
+
}): Promise<QueryResult[]>;
|
|
115
143
|
commit(): Promise<void>;
|
|
116
144
|
rollback(): Promise<void>;
|
|
117
145
|
readonly isOpen: boolean;
|
|
@@ -126,3 +154,10 @@ export interface LoraDatabaseLike {
|
|
|
126
154
|
* ```
|
|
127
155
|
*/
|
|
128
156
|
export declare function loraDriver(db: LoraDatabaseLike): LoraDriver;
|
|
157
|
+
/**
|
|
158
|
+
* `driver`, refusing a write transaction when `max` are already waiting
|
|
159
|
+
* for the writer lock: one writer runs at a time, so past saturation a
|
|
160
|
+
* queue only adds latency. `onFull` builds the error to throw. Reads are
|
|
161
|
+
* not counted: they do not wait for the lock.
|
|
162
|
+
*/
|
|
163
|
+
export declare function boundedWrites(driver: LoraDriver, max: number, onFull: (waiting: number) => Error): LoraDriver;
|
package/dist/errors.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export declare function formatProblem(p: ModelProblem): string;
|
|
|
20
20
|
* a stable order. Use it to exhaustively map codes (HTTP status, client
|
|
21
21
|
* copy) or to test membership at runtime.
|
|
22
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"];
|
|
23
|
+
export declare const LORA_GRAPHQL_ERROR_CODES: readonly ["BAD_USER_INPUT", "INVALID_CURSOR", "LIMIT_EXCEEDED", "COST_EXCEEDED", "OVERLOADED", "TIMEOUT", "UNAUTHENTICATED", "FORBIDDEN", "NOT_FOUND", "CONSTRAINT_VIOLATION", "DATABASE_ERROR", "PERSISTED_QUERY_ONLY", "WRONG_OPERATION_TYPE"];
|
|
24
24
|
export type LoraGraphQLErrorCode = (typeof LORA_GRAPHQL_ERROR_CODES)[number];
|
|
25
25
|
/**
|
|
26
26
|
* Whether `err` is a request-time error raised by this library: a
|
|
@@ -37,6 +37,26 @@ export declare function isLoraGraphQLError(err: unknown): err is {
|
|
|
37
37
|
};
|
|
38
38
|
/** A request-time error with a stable `extensions.code`. */
|
|
39
39
|
export declare function requestError(code: LoraGraphQLErrorCode, message: string, cause?: unknown, extensions?: Record<string, unknown>): GraphQLError;
|
|
40
|
+
/** One value a mutation was given, or computed, that breaks a field's rule. */
|
|
41
|
+
export interface Violation {
|
|
42
|
+
/** Where the value is in the mutation's arguments: keys and list indices. */
|
|
43
|
+
path: Array<string | number>;
|
|
44
|
+
/** The model field whose rule it is, as `Type.field`. */
|
|
45
|
+
coordinate: string;
|
|
46
|
+
/** The rule, named as its JSON Schema keyword (`maxLength`, `format`, ...). */
|
|
47
|
+
rule: string;
|
|
48
|
+
/** The rule's own numbers and names, enough to localise the message. */
|
|
49
|
+
params: Record<string, unknown>;
|
|
50
|
+
message: string;
|
|
51
|
+
}
|
|
52
|
+
/** The most violations one error lists; a batch can hold thousands. */
|
|
53
|
+
export declare const MAX_VIOLATIONS = 100;
|
|
54
|
+
/**
|
|
55
|
+
* Every value a mutation got wrong, in one error: `extensions.violations`
|
|
56
|
+
* lists each with its path, rule and parameters. The values themselves
|
|
57
|
+
* are never included: a rule is often on a password or an address.
|
|
58
|
+
*/
|
|
59
|
+
export declare function validationError(code: "BAD_USER_INPUT" | "CONSTRAINT_VIOLATION", violations: readonly Violation[], total?: number): GraphQLError;
|
|
40
60
|
/**
|
|
41
61
|
* One error per field path pattern: errors with the same message and code
|
|
42
62
|
* at paths that differ only in list indices (a field every row of a list
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const OUTBOX: {
|
|
2
|
+
/** `PENDING`, `CLAIMED`, `DONE` or `FAILED`. */
|
|
3
|
+
readonly state: "_outbox";
|
|
4
|
+
/** Handler calls that failed so far. */
|
|
5
|
+
readonly attempts: "_outboxAttempts";
|
|
6
|
+
/** Not before this time (epoch ms): the backoff after a failure. */
|
|
7
|
+
readonly at: "_outboxAt";
|
|
8
|
+
/** A claim is void past this time (epoch ms): its worker died. */
|
|
9
|
+
readonly lease: "_outboxLease";
|
|
10
|
+
/** The message of the last failure. */
|
|
11
|
+
readonly error: "_outboxError";
|
|
12
|
+
};
|
|
13
|
+
/** Whether `property` is one of the delivery-state properties. */
|
|
14
|
+
export declare function isOutboxProperty(property: string): boolean;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { LoraDriver } from '../driver.js';
|
|
2
|
+
import { WriteChange } from '../execute/changes.js';
|
|
3
|
+
import { NodeType } from '../model/types.js';
|
|
4
|
+
import { DatabaseErrorEvent } from '../options.js';
|
|
5
|
+
export interface WorkerOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Called with the node's stored scalar fields, by field name, read
|
|
8
|
+
* without read rules. Resolving marks the node done; throwing (or
|
|
9
|
+
* rejecting) schedules it again, up to `maxAttempts`.
|
|
10
|
+
*/
|
|
11
|
+
handle: (node: Record<string, unknown>, info: {
|
|
12
|
+
/** The node's @key value: the idempotency key of the work. */
|
|
13
|
+
key: unknown;
|
|
14
|
+
/** 1 for the first call, 2 for the first retry, ... */
|
|
15
|
+
attempt: number;
|
|
16
|
+
/** Aborted when the worker stops. */
|
|
17
|
+
signal: AbortSignal;
|
|
18
|
+
}) => unknown;
|
|
19
|
+
/** Handlers running at once. Default 4. */
|
|
20
|
+
concurrency?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Calls before a node is given up as `FAILED` and reported to
|
|
23
|
+
* `onError`. A call that never returned (its worker died, or it
|
|
24
|
+
* outlived `leaseMs`) counts. Default 8.
|
|
25
|
+
*/
|
|
26
|
+
maxAttempts?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Milliseconds to wait before attempt `attempt + 1`. Default: one
|
|
29
|
+
* second doubling per attempt, at most five minutes.
|
|
30
|
+
*/
|
|
31
|
+
backoffMs?: (attempt: number) => number;
|
|
32
|
+
/**
|
|
33
|
+
* How often the worker looks for pending nodes it was not told about:
|
|
34
|
+
* work left by a restart, retries that are due, claims whose worker
|
|
35
|
+
* died. Milliseconds, default 30 000.
|
|
36
|
+
*/
|
|
37
|
+
sweepMs?: number;
|
|
38
|
+
/**
|
|
39
|
+
* How long a claim holds, in milliseconds. Past it another worker may
|
|
40
|
+
* take the node, and what the first handler then returns is ignored:
|
|
41
|
+
* give a handler a timeout shorter than this. Default 60 000.
|
|
42
|
+
*/
|
|
43
|
+
leaseMs?: number;
|
|
44
|
+
}
|
|
45
|
+
export interface Worker {
|
|
46
|
+
/**
|
|
47
|
+
* Resolves once nothing is pending: after a look at the database, with
|
|
48
|
+
* no handler running and no node due. For tests and for shutdown.
|
|
49
|
+
*/
|
|
50
|
+
idle(): Promise<void>;
|
|
51
|
+
/** Stop taking work; resolves when the running handlers have ended. */
|
|
52
|
+
stop(): Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
export interface WorkerHost {
|
|
55
|
+
driver: LoraDriver;
|
|
56
|
+
node: NodeType;
|
|
57
|
+
timeoutMs: number;
|
|
58
|
+
/** Committed writes, from the change hub. */
|
|
59
|
+
listen: (listener: (change: WriteChange) => void) => () => unknown;
|
|
60
|
+
onError: ((event: DatabaseErrorEvent) => void) | undefined;
|
|
61
|
+
}
|
|
62
|
+
export declare class OutboxWorker implements Worker {
|
|
63
|
+
#private;
|
|
64
|
+
constructor(host: WorkerHost, options: WorkerOptions);
|
|
65
|
+
idle(): Promise<void>;
|
|
66
|
+
stop(): Promise<void>;
|
|
67
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NodeType } from '../../model/types.js';
|
|
1
2
|
import { WritePlan } from './plan.js';
|
|
2
3
|
import { Runner } from './runner.js';
|
|
3
4
|
/**
|
|
@@ -5,6 +6,12 @@ import { Runner } from './runner.js';
|
|
|
5
6
|
* the relationship was created from. Check every node that gained one.
|
|
6
7
|
*/
|
|
7
8
|
export declare function checkCardinality(runner: Runner, plan: WritePlan): Promise<void>;
|
|
9
|
+
/**
|
|
10
|
+
* {@link checkCardinality} for a single call: each check is a statement
|
|
11
|
+
* that fails on the first node holding more than one, naming the node and
|
|
12
|
+
* its counts in the message. Nothing is awaited.
|
|
13
|
+
*/
|
|
14
|
+
export declare function assertCardinality(runner: Runner, plan: WritePlan): void;
|
|
8
15
|
/**
|
|
9
16
|
* `@uniqueTogether`: after every write of the mutation, no two nodes of
|
|
10
17
|
* a constrained type share a combination. Checks the nodes it created
|
|
@@ -14,6 +21,15 @@ export declare function checkCardinality(runner: Runner, plan: WritePlan): Promi
|
|
|
14
21
|
* rule, so the bypass does not skip it.
|
|
15
22
|
*/
|
|
16
23
|
export declare function checkUniqueTogether(runner: Runner): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* `@uniqueTogether` for a single call, on the nodes with `keys` of each
|
|
26
|
+
* type: every check is issued before this first waits, as a statement
|
|
27
|
+
* that fails on the node sharing its combination.
|
|
28
|
+
*/
|
|
29
|
+
export declare function assertUniqueTogether(runner: Runner, touched: Array<{
|
|
30
|
+
node: NodeType;
|
|
31
|
+
keys: unknown[];
|
|
32
|
+
}>): Promise<void>;
|
|
17
33
|
/**
|
|
18
34
|
* A required single relationship stays set: disconnecting from the other
|
|
19
35
|
* side must not leave the owner without one.
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { TypeValidator } from '../../standard-schema.js';
|
|
1
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';
|
|
5
6
|
import { WriteChange } from '../changes.js';
|
|
7
|
+
import { LoraTransaction } from '../transaction.js';
|
|
6
8
|
/** Computes a `@populatedBy` field. */
|
|
7
9
|
export type PopulatedByCallback = (args: {
|
|
8
10
|
operation: "CREATE" | "UPDATE";
|
|
@@ -15,6 +17,33 @@ export type PopulatedByCallback = (args: {
|
|
|
15
17
|
/** The GraphQL context. */
|
|
16
18
|
context: unknown;
|
|
17
19
|
}) => unknown;
|
|
20
|
+
/** What a JavaScript trigger is called with. */
|
|
21
|
+
export interface TriggerEvent {
|
|
22
|
+
operation: "CREATE" | "UPDATE" | "DELETE";
|
|
23
|
+
/** The node type whose nodes were written. */
|
|
24
|
+
type: string;
|
|
25
|
+
/** Their @key values. For DELETE the nodes still exist. */
|
|
26
|
+
keys: unknown[];
|
|
27
|
+
/** The mutation's write-set so far. */
|
|
28
|
+
change: WriteChange;
|
|
29
|
+
/**
|
|
30
|
+
* The mutation's transaction: `execute()` runs Cypher in it, and
|
|
31
|
+
* `lora.execute({ context: { ...context, transaction } })` runs a
|
|
32
|
+
* generated mutation in it, whose writes join the write-set. The
|
|
33
|
+
* mutation commits it; throw to roll everything back.
|
|
34
|
+
*/
|
|
35
|
+
transaction: LoraTransaction;
|
|
36
|
+
/** The GraphQL context of the request. */
|
|
37
|
+
context: unknown;
|
|
38
|
+
}
|
|
39
|
+
/** A trigger function; it may return a promise. Throwing vetoes the write. */
|
|
40
|
+
export type TriggerFunction = (event: TriggerEvent) => unknown;
|
|
41
|
+
/** The trigger functions of one node type. */
|
|
42
|
+
export interface TriggerFunctions {
|
|
43
|
+
create?: TriggerFunction;
|
|
44
|
+
update?: TriggerFunction;
|
|
45
|
+
delete?: TriggerFunction;
|
|
46
|
+
}
|
|
18
47
|
export interface MutationEnv {
|
|
19
48
|
model: GraphModel;
|
|
20
49
|
driver: LoraDriver;
|
|
@@ -43,6 +72,27 @@ export interface MutationEnv {
|
|
|
43
72
|
/** Statistics from `analyze()`, for filter costs. */
|
|
44
73
|
statistics?: Statistics | undefined;
|
|
45
74
|
callbacks: Readonly<Record<string, PopulatedByCallback>>;
|
|
75
|
+
/** The `singleCallMutations` option; false keeps every mutation in a transaction. */
|
|
76
|
+
singleCall?: boolean | undefined;
|
|
77
|
+
/** The `triggers` option: functions per node type and operation. */
|
|
78
|
+
triggers?: Readonly<Record<string, TriggerFunctions>> | undefined;
|
|
79
|
+
/** The `triggerTimeoutMs` option, defaulted. */
|
|
80
|
+
triggerTimeoutMs?: number | undefined;
|
|
81
|
+
/**
|
|
82
|
+
* The mutation runs inside a trigger's transaction: it fires no trigger
|
|
83
|
+
* itself, so what a trigger writes never cascades.
|
|
84
|
+
*/
|
|
85
|
+
inTrigger?: boolean | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* `@populatedBy` callbacks already started, by the input they compute
|
|
88
|
+
* for and the field's name: those of the mutation's own inputs start
|
|
89
|
+
* before the transaction opens, all at once, so they do not run under
|
|
90
|
+
* the writer lock one after another. Each is awaited where it would
|
|
91
|
+
* have been called, so a failure surfaces at the same point.
|
|
92
|
+
*/
|
|
93
|
+
computed?: WeakMap<object, Map<string, Promise<unknown>>> | undefined;
|
|
94
|
+
/** The `validators` option: a Standard Schema per node type. */
|
|
95
|
+
validators?: Readonly<Record<string, TypeValidator>> | undefined;
|
|
46
96
|
/**
|
|
47
97
|
* A caller-owned transaction: run inside it and leave the commit to the
|
|
48
98
|
* caller. A failed mutation rolls it back, so it is never half-applied.
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { GraphModel, NodeType, RelationshipField } from '../../model/types.js';
|
|
2
2
|
export declare function notConnected(owner: NodeType, rel: RelationshipField, target: NodeType, to: unknown): import('graphql').GraphQLError;
|
|
3
|
+
/** The error of a connect whose targets are not there (or not visible). */
|
|
4
|
+
export declare function noTarget(owner: NodeType, rel: RelationshipField, target: NodeType, missing: unknown[]): import('graphql').GraphQLError;
|
|
5
|
+
/** The error of a relationship field's rules refusing `op`. */
|
|
6
|
+
export declare function refusedRule(op: "CONNECT" | "DISCONNECT" | "UPDATE_EDGE", rel: RelationshipField): import('graphql').GraphQLError;
|
|
7
|
+
/** The error of a single relationship field that would hold `count` nodes. */
|
|
8
|
+
export declare function tooMany(field: RelationshipField, key: unknown, count: number, seen: number): import('graphql').GraphQLError;
|
|
3
9
|
/** In place of the key of a node the caller cannot read, in messages. */
|
|
4
10
|
export declare const HIDDEN: unique symbol;
|
|
5
11
|
export declare function requiredMissing(owner: NodeType, field: RelationshipField, target: string, key: unknown): import('graphql').GraphQLError;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { NodeType, RelationshipField } from '../../model/types.js';
|
|
2
|
+
import { WritePlan } from './plan.js';
|
|
3
|
+
import { Runner } from './runner.js';
|
|
4
|
+
/**
|
|
5
|
+
* The links of a single call: what {@link applyLinks} does, with
|
|
6
|
+
* nothing awaited. The rules on both ends and on the relationship field
|
|
7
|
+
* are assertions, and so is every target being there: the link reports
|
|
8
|
+
* the rows it connected in the message of the assertion that fails
|
|
9
|
+
* when one is missing.
|
|
10
|
+
*/
|
|
11
|
+
export declare function linkInOne(runner: Runner, plan: WritePlan): void;
|
|
12
|
+
/**
|
|
13
|
+
* Connect `plan`'s links in a transaction, a relationship field at a
|
|
14
|
+
* time: the rules on both ends, the MERGE, then the rules on what it
|
|
15
|
+
* created or found.
|
|
16
|
+
*/
|
|
17
|
+
export declare function applyLinks(runner: Runner, plan: WritePlan): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Rules on the relationship field (and on a field declaring the same
|
|
20
|
+
* relationship from the other side) for `op`, on each pair's
|
|
21
|
+
* relationship as it stands: after the write for CONNECT and
|
|
22
|
+
* UPDATE_EDGE, before it for DISCONNECT. FORBIDDEN when any fails; the
|
|
23
|
+
* mutation rolls back.
|
|
24
|
+
*/
|
|
25
|
+
export declare function checkRelationshipRules(runner: Runner, op: "CONNECT" | "DISCONNECT" | "UPDATE_EDGE", rel: RelationshipField, pairs: Array<{
|
|
26
|
+
from: unknown;
|
|
27
|
+
to: unknown;
|
|
28
|
+
}>): Promise<void>;
|
|
29
|
+
/** Whether either end has validate rules for `op` at `when`. */
|
|
30
|
+
export declare function endRules(owner: NodeType, target: NodeType, op: "CREATE_RELATIONSHIP" | "DELETE_RELATIONSHIP", when: "BEFORE" | "AFTER"): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Validate rules for connecting or disconnecting, on both ends of each
|
|
33
|
+
* pair: FORBIDDEN when either end fails them.
|
|
34
|
+
*/
|
|
35
|
+
export declare function checkEnds(runner: Runner, op: "CREATE_RELATIONSHIP" | "DELETE_RELATIONSHIP", owner: NodeType, target: NodeType, pairs: Array<{
|
|
36
|
+
from: unknown;
|
|
37
|
+
to: unknown;
|
|
38
|
+
}>, when: "BEFORE" | "AFTER"): Promise<void>;
|
|
@@ -74,6 +74,11 @@ export declare class WritePlan {
|
|
|
74
74
|
}>;
|
|
75
75
|
/** `@populatedBy` callbacks for planned creates. */
|
|
76
76
|
pending: Array<() => Promise<void>>;
|
|
77
|
+
/**
|
|
78
|
+
* The plan is an update's: a link may find its relationship there
|
|
79
|
+
* already, which a single call must then tell in the database.
|
|
80
|
+
*/
|
|
81
|
+
reconnects: boolean;
|
|
77
82
|
constructor(env: MutationEnv, ctx: CompileContext);
|
|
78
83
|
get model(): GraphModel;
|
|
79
84
|
/**
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { SelectionSetNode } from 'graphql';
|
|
2
2
|
import { CompileContext } from '../../compile/context.js';
|
|
3
|
-
import { DriverTransaction } from '../../driver.js';
|
|
4
|
-
import { NodeType,
|
|
3
|
+
import { DriverTransaction, QueryResult, Statement } from '../../driver.js';
|
|
4
|
+
import { NodeType, ScalarField } from '../../model/types.js';
|
|
5
5
|
import { RelationshipRef, WriteChange } from '../changes.js';
|
|
6
6
|
import { MutationEnv, MutationInfo } from './env.js';
|
|
7
|
+
import { Assertions } from './single-call.js';
|
|
7
8
|
import { WritePlan, Input } from './plan.js';
|
|
8
9
|
export declare class Runner {
|
|
10
|
+
#private;
|
|
9
11
|
readonly env: MutationEnv;
|
|
10
12
|
readonly tx: DriverTransaction;
|
|
11
13
|
readonly change: WriteChange;
|
|
@@ -17,6 +19,12 @@ export declare class Runner {
|
|
|
17
19
|
keys: unknown[];
|
|
18
20
|
fields: Set<string>;
|
|
19
21
|
}>;
|
|
22
|
+
/** Whether a trigger function was called: it may have closed the transaction. */
|
|
23
|
+
triggerFunctionRan: boolean;
|
|
24
|
+
/** Nodes a trigger added to the write-set: they fire no trigger. */
|
|
25
|
+
triggered: Set<string>;
|
|
26
|
+
/** The fields each update set, per type and key: `@trigger(fields:)`. */
|
|
27
|
+
updatedFields: Map<NodeType, Map<string, Set<string>>>;
|
|
20
28
|
/**
|
|
21
29
|
* A type this mutation creates under a key held by a node the caller
|
|
22
30
|
* cannot read; see {@link Runner.moveHiddenKeys}.
|
|
@@ -30,12 +38,100 @@ export declare class Runner {
|
|
|
30
38
|
node: NodeType;
|
|
31
39
|
field: ScalarField;
|
|
32
40
|
} | undefined;
|
|
41
|
+
/** What the assertions of a single call stand for. */
|
|
42
|
+
readonly assertions: Assertions;
|
|
33
43
|
constructor(env: MutationEnv, tx: DriverTransaction, change: WriteChange);
|
|
44
|
+
/** Whether the mutation is one call that commits as it returns. */
|
|
45
|
+
get single(): boolean;
|
|
46
|
+
/** The mutation needs more than one round trip: open its transaction. */
|
|
47
|
+
open(): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* In a single call, what JavaScript would decide after the mutation's
|
|
50
|
+
* statements is their last statement instead: an assertion that no node
|
|
51
|
+
* sat under a key or a unique value this create moved aside. It is last,
|
|
52
|
+
* so any other error is still the one a free key gets.
|
|
53
|
+
*/
|
|
54
|
+
assertNothingMoved(): void;
|
|
55
|
+
/**
|
|
56
|
+
* Put a statement together and issue it. A single call puts every
|
|
57
|
+
* statement together before the first is sent, where a transaction
|
|
58
|
+
* does so once the one before it answered. An error raised meanwhile (a
|
|
59
|
+
* rule the claims settle) must not outrank what an earlier statement
|
|
60
|
+
* raises in the database: it becomes a statement that fails with it, in
|
|
61
|
+
* its place. Before the links' first statement it is thrown, as a
|
|
62
|
+
* transaction throws it: nothing was sent yet there either.
|
|
63
|
+
*/
|
|
64
|
+
inPlace(issue: () => void): void;
|
|
65
|
+
/**
|
|
66
|
+
* Run `statement` in the transaction. Statements issued in the same
|
|
67
|
+
* tick, without awaiting one before issuing the next, go to the
|
|
68
|
+
* database in one call (`executeMany`), in the order issued: one round
|
|
69
|
+
* trip under the writer lock instead of one each. When a statement of
|
|
70
|
+
* such a batch fails, every statement of the batch rejects with its
|
|
71
|
+
* error, so a batch holds writes whose results are not checked,
|
|
72
|
+
* followed by reads.
|
|
73
|
+
*/
|
|
74
|
+
send(statement: Statement): Promise<QueryResult>;
|
|
75
|
+
/**
|
|
76
|
+
* Issue a write whose result nothing checks, and leave it for the next
|
|
77
|
+
* statements to share its round trip: it is sent with whatever is
|
|
78
|
+
* issued before the event loop turns. `settle()` awaits it.
|
|
79
|
+
*/
|
|
80
|
+
later(statement: Statement): void;
|
|
81
|
+
/**
|
|
82
|
+
* Leave `pending` (a statement issued, with what follows from its
|
|
83
|
+
* rows) for `settle()` to await.
|
|
84
|
+
*/
|
|
85
|
+
defer(pending: Promise<unknown>): void;
|
|
86
|
+
/** The links of a single call begin; see {@link Runner.inPlace}. */
|
|
87
|
+
linking(): void;
|
|
88
|
+
/** Wait for the writes issued with `later`; throws the first failure. */
|
|
89
|
+
settle(): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* `pending`, safe to leave unawaited, and the mutation abandoned the
|
|
92
|
+
* moment it fails: before a batch still waiting to be sent goes out.
|
|
93
|
+
*/
|
|
94
|
+
watch<T>(pending: Promise<T>): Promise<T>;
|
|
95
|
+
/**
|
|
96
|
+
* The mutation failed: nothing queued is sent. A single call commits
|
|
97
|
+
* with its first batch, so a batch must not go out after the error that
|
|
98
|
+
* should have stopped it.
|
|
99
|
+
*/
|
|
100
|
+
abandon(err: unknown): void;
|
|
34
101
|
ctx(): CompileContext;
|
|
102
|
+
/**
|
|
103
|
+
* An update of one node by key as one statement, for a single call:
|
|
104
|
+
* the match under the caller's filter rules, the BEFORE rules, the
|
|
105
|
+
* write and the AFTER rules, each rule an assertion. Which nodes it
|
|
106
|
+
* touched is then decided in the database, not between two round
|
|
107
|
+
* trips. Returns nothing when the update is not that simple (it writes
|
|
108
|
+
* relationships, adjusts a field with a constraint, or cannot be
|
|
109
|
+
* prepared without knowing whether the node is there): the caller opens
|
|
110
|
+
* a transaction instead.
|
|
111
|
+
*/
|
|
112
|
+
updateInOne(plan: WritePlan, node: NodeType, key: unknown, input: Input, adjust: Input | undefined, tail: () => void): Promise<{
|
|
113
|
+
done: Promise<unknown[]>;
|
|
114
|
+
} | undefined>;
|
|
115
|
+
/**
|
|
116
|
+
* The single call found no node to update and rolled back: nothing of
|
|
117
|
+
* what was planned happened.
|
|
118
|
+
*/
|
|
119
|
+
nothingWritten(): void;
|
|
120
|
+
/**
|
|
121
|
+
* `send()` for a statement's rows. The promise is safe to leave
|
|
122
|
+
* unawaited: a batch fails as a whole, so when one statement of several
|
|
123
|
+
* issued together is refused, the others reject too, and whoever
|
|
124
|
+
* stopped at the first must not leave the rest unhandled.
|
|
125
|
+
*/
|
|
35
126
|
run(text: string, ctx: CompileContext): Promise<Array<Record<string, unknown>>>;
|
|
36
127
|
plan(): WritePlan;
|
|
37
128
|
/** Every planned write, in order, with the checks that follow them. */
|
|
38
|
-
|
|
129
|
+
/**
|
|
130
|
+
* `tail` is called once the plan's last statements are issued, without
|
|
131
|
+
* waiting for them: what it issues (the payload read, the caller's own
|
|
132
|
+
* checks) shares their round trip.
|
|
133
|
+
*/
|
|
134
|
+
apply(plan: WritePlan, tail?: () => void): Promise<void>;
|
|
39
135
|
/**
|
|
40
136
|
* `maxBatch` over the whole mutation: nodes created and updated so far
|
|
41
137
|
* plus those `plan` creates and updates (nested `update` entries
|
|
@@ -69,28 +165,6 @@ export declare class Runner {
|
|
|
69
165
|
moveTakenUniques(plan: WritePlan): Promise<void>;
|
|
70
166
|
applyCreates(plan: WritePlan): Promise<void>;
|
|
71
167
|
applyDisconnects(plan: WritePlan): Promise<void>;
|
|
72
|
-
applyLinks(plan: WritePlan): Promise<void>;
|
|
73
|
-
/**
|
|
74
|
-
* Rules on the relationship field (and on a field declaring the same
|
|
75
|
-
* relationship from the other side) for `op`, on each pair's
|
|
76
|
-
* relationship as it stands: after the write for CONNECT and
|
|
77
|
-
* UPDATE_EDGE, before it for DISCONNECT. FORBIDDEN when any fails; the
|
|
78
|
-
* mutation rolls back.
|
|
79
|
-
*/
|
|
80
|
-
checkRelationshipRules(op: "CONNECT" | "DISCONNECT" | "UPDATE_EDGE", rel: RelationshipField, pairs: Array<{
|
|
81
|
-
from: unknown;
|
|
82
|
-
to: unknown;
|
|
83
|
-
}>): Promise<void>;
|
|
84
|
-
/** Whether either end has validate rules for `op` at `when`. */
|
|
85
|
-
endRules(owner: NodeType, target: NodeType, op: "CREATE_RELATIONSHIP" | "DELETE_RELATIONSHIP", when: "BEFORE" | "AFTER"): boolean;
|
|
86
|
-
/**
|
|
87
|
-
* Validate rules for connecting or disconnecting, on both ends of each
|
|
88
|
-
* pair: FORBIDDEN when either end fails them.
|
|
89
|
-
*/
|
|
90
|
-
checkEnds(op: "CREATE_RELATIONSHIP" | "DELETE_RELATIONSHIP", owner: NodeType, target: NodeType, pairs: Array<{
|
|
91
|
-
from: unknown;
|
|
92
|
-
to: unknown;
|
|
93
|
-
}>, when: "BEFORE" | "AFTER"): Promise<void>;
|
|
94
168
|
/** `update: [{ key, edge }]`: properties of connected pairs, in place. */
|
|
95
169
|
applyEdgeUpdates(plan: WritePlan): Promise<void>;
|
|
96
170
|
/**
|
|
@@ -127,3 +201,5 @@ export declare class Runner {
|
|
|
127
201
|
*/
|
|
128
202
|
resolveKeys(node: NodeType, where: Input, op: "UPDATE" | "DELETE", limit: number): Promise<unknown[]>;
|
|
129
203
|
}
|
|
204
|
+
/** `pending`, safe to leave unawaited when an earlier check throws. */
|
|
205
|
+
export declare function held<T>(pending: Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { DriverTransaction, LoraDriver, QueryResult, RunOptions, Statement } from '../../driver.js';
|
|
2
|
+
import { GraphModel } from '../../model/types.js';
|
|
3
|
+
import { WritePlan } from './plan.js';
|
|
4
|
+
/**
|
|
5
|
+
* A transaction that is one `driver.run()` call until `open()` makes it a
|
|
6
|
+
* real one. In its single form the first batch is the whole mutation: it
|
|
7
|
+
* is committed when the call returns, so `commit()` has nothing left to
|
|
8
|
+
* do, and a failure has already rolled everything back.
|
|
9
|
+
*
|
|
10
|
+
* With `single` false it is never one call, only late: the transaction
|
|
11
|
+
* begins with its first statement, so what a mutation works out before
|
|
12
|
+
* that (its plan, its `@populatedBy` callbacks) does not hold the lock.
|
|
13
|
+
*/
|
|
14
|
+
export declare class LazyTransaction implements DriverTransaction {
|
|
15
|
+
#private;
|
|
16
|
+
constructor(driver: LoraDriver, options: RunOptions, single?: boolean);
|
|
17
|
+
/**
|
|
18
|
+
* Whether the single call ran and committed: an error after that can
|
|
19
|
+
* no longer undo the write.
|
|
20
|
+
*/
|
|
21
|
+
get committed(): boolean;
|
|
22
|
+
/** Whether the mutation is still one call. */
|
|
23
|
+
get single(): boolean;
|
|
24
|
+
/** The mutation needs more than one round trip: begin a transaction. */
|
|
25
|
+
open(): Promise<void>;
|
|
26
|
+
execute(statement: Statement): Promise<QueryResult>;
|
|
27
|
+
executeMany(statements: Statement[]): Promise<QueryResult[]>;
|
|
28
|
+
commit(): Promise<void>;
|
|
29
|
+
rollback(): Promise<void>;
|
|
30
|
+
get isOpen(): boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* `err` without the position the binding appends to the error of a
|
|
34
|
+
* failing statement of a batch (`(statement 2 of 3)`): callers see the
|
|
35
|
+
* error a lone statement gives. An error whose message is read-only (the
|
|
36
|
+
* DOMException of an aborted request) is passed on as it is.
|
|
37
|
+
*/
|
|
38
|
+
export declare function withoutPosition(err: unknown): unknown;
|
|
39
|
+
/**
|
|
40
|
+
* Whether the database has `assert()`: asked once per driver. Without it
|
|
41
|
+
* every mutation keeps its transaction and decides in JavaScript.
|
|
42
|
+
*/
|
|
43
|
+
export declare function supportsAssert(driver: LoraDriver): Promise<boolean>;
|
|
44
|
+
/**
|
|
45
|
+
* Whether reading any node can be refused by a READ validate rule: a
|
|
46
|
+
* payload read would then need a decision after the write, which a single
|
|
47
|
+
* call cannot make.
|
|
48
|
+
*/
|
|
49
|
+
export declare function readCanBeRefused(model: GraphModel): boolean;
|
|
50
|
+
/**
|
|
51
|
+
* What the guard of a single-call update that writes relationships stands
|
|
52
|
+
* for: the node is not there (or not the caller's to update), so the call
|
|
53
|
+
* rolled back and the mutation answers null, as a transaction would after
|
|
54
|
+
* finding nothing.
|
|
55
|
+
*/
|
|
56
|
+
export declare const NOT_THERE: unique symbol;
|
|
57
|
+
/**
|
|
58
|
+
* Whether `plan`'s links can go out in a single call: when each is
|
|
59
|
+
* known to create its relationship. A transaction asks the database
|
|
60
|
+
* whether the pair was connected already, and applies other rules to a
|
|
61
|
+
* re-connect; here it must be certain beforehand. It is when one end is
|
|
62
|
+
* a node this plan creates (it has no relationships yet) and no two
|
|
63
|
+
* links name the same relationship (one from each side's field). An
|
|
64
|
+
* update's links ask the database instead (`plan.reconnects`). A
|
|
65
|
+
* property the rules refuse depends on what the link found, in
|
|
66
|
+
* JavaScript.
|
|
67
|
+
*/
|
|
68
|
+
export declare function linksInOne(plan: WritePlan): boolean;
|
|
69
|
+
/** The message of an `assert()` that stands for a refused rule. */
|
|
70
|
+
export declare function refusal(kind: "forbidden" | "hidden" | "taken", type: string, detail: string): string;
|
|
71
|
+
/**
|
|
72
|
+
* The request error an assertion stood for, or `err` when it is not one
|
|
73
|
+
* of ours.
|
|
74
|
+
*/
|
|
75
|
+
export declare function refused(model: GraphModel, err: unknown): unknown;
|
|
76
|
+
/**
|
|
77
|
+
* The assertions of one single call that stand for an error only this
|
|
78
|
+
* mutation can name (the row a check failed on, a target that is not
|
|
79
|
+
* there): each is numbered, and its number is its message.
|
|
80
|
+
*/
|
|
81
|
+
export declare class Assertions {
|
|
82
|
+
#private;
|
|
83
|
+
/**
|
|
84
|
+
* The message, a Cypher expression, of an assertion that stands for
|
|
85
|
+
* `error`: {@link Assertions.refusedBy} gives the error back. `detail`
|
|
86
|
+
* is an expression appended to it, for an error that names what the
|
|
87
|
+
* rows held.
|
|
88
|
+
*/
|
|
89
|
+
failsAs(error: (detail: string) => unknown, detail?: string): string;
|
|
90
|
+
/** The error an assertion of {@link Assertions.failsAs} stood for, if `err` is one. */
|
|
91
|
+
refusedBy(err: unknown): unknown;
|
|
92
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { CompileContext } from '../../compile/context.js';
|
|
2
|
+
import { Statement } from '../../driver.js';
|
|
3
|
+
import { MutationOperation, NodeType, Trigger } from '../../model/types.js';
|
|
4
|
+
import { MutationEnv } from './env.js';
|
|
5
|
+
import { Runner } from './runner.js';
|
|
6
|
+
/** Whether the mutation may fire a trigger: a statement or a function. */
|
|
7
|
+
export declare function hasTriggers(env: MutationEnv): boolean;
|
|
8
|
+
/** Whether the mutation may call a trigger function. */
|
|
9
|
+
export declare function hasTriggerFunctions(env: MutationEnv): boolean;
|
|
10
|
+
/** The trigger statement for `keys`, with `this` bound to each node. */
|
|
11
|
+
export declare function triggerStatement(ctx: CompileContext, node: NodeType, trigger: Trigger, operation: MutationOperation, keys: unknown[]): Statement;
|
|
12
|
+
/**
|
|
13
|
+
* Run the CREATE and UPDATE triggers for what the mutation wrote so far.
|
|
14
|
+
* A node the mutation created and then updated fires CREATE only.
|
|
15
|
+
*/
|
|
16
|
+
export declare function runWriteTriggers(runner: Runner): Promise<void>;
|
|
17
|
+
/** Run the DELETE triggers of `doomed`, while the nodes still exist. */
|
|
18
|
+
export declare function runDeleteTriggers(runner: Runner, doomed: ReadonlyArray<{
|
|
19
|
+
node: NodeType;
|
|
20
|
+
keys: unknown[];
|
|
21
|
+
}>): Promise<void>;
|
|
@@ -14,5 +14,9 @@ export declare function checkUniqueTogetherOf(env: MutationEnv, tx: DriverTransa
|
|
|
14
14
|
* compares it with the nodes that share its first relationship end (or,
|
|
15
15
|
* with scalars only, its first scalar's value) — never a scan of the type.
|
|
16
16
|
* Without `keys`, every node of the type is compared (a scan).
|
|
17
|
+
*
|
|
18
|
+
* With `failing`, the statement fails instead of returning that node:
|
|
19
|
+
* `failing` gives the assertion's message, from an expression for the
|
|
20
|
+
* node's place in `keys`.
|
|
17
21
|
*/
|
|
18
|
-
export declare function uniqueTogetherStatement(ctx: CompileContext, model: GraphModel, node: NodeType, u: UniqueTogether, keys: unknown[] | undefined): string;
|
|
22
|
+
export declare function uniqueTogetherStatement(ctx: CompileContext, model: GraphModel, node: NodeType, u: UniqueTogether, keys: unknown[] | undefined, failing?: (index: string) => string): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { GraphModel, NodeType } from '../../model/types.js';
|
|
2
|
+
import { MutationKind } from '../../schema/mutations.js';
|
|
3
|
+
import { TypeValidator } from '../../standard-schema.js';
|
|
4
|
+
export type Validators = Readonly<Record<string, TypeValidator>>;
|
|
5
|
+
/**
|
|
6
|
+
* Refuse a mutation whose input breaks a field's rule or its type's
|
|
7
|
+
* validator, with every violation in one `BAD_USER_INPUT`. Values
|
|
8
|
+
* computed during the write (an `adjust`, a `@populatedBy` callback) are
|
|
9
|
+
* checked where they appear.
|
|
10
|
+
*/
|
|
11
|
+
export declare function validateInput(model: GraphModel, validators: Validators, op: MutationKind, node: NodeType, args: Record<string, unknown>): Promise<void>;
|