@simtlix/simfinity-core 3.5.2 → 3.5.5
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 +11 -4
- package/package.json +2 -1
- package/src/auth/expressions.js +77 -16
- package/src/auth/index.js +44 -8
- package/src/auth/rules.js +54 -10
- package/src/auth/thenable.js +17 -0
- package/src/index.js +1 -0
- package/src/mutation-limits.js +24 -0
- package/src/query-plan.js +14 -6
- package/src/relation-owners.js +5 -0
- package/src/relation-storage.js +58 -0
- package/src/runtime.js +523 -127
- package/src/scalars/factory.js +30 -10
- package/src/scalars/pattern.js +16 -0
- package/src/scalars.js +3 -2
- package/src/validators.js +4 -4
- package/types/index.d.ts +179 -7
package/src/scalars/factory.js
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
import { GraphQLScalarType, GraphQLString, GraphQLInt, GraphQLFloat, GraphQLBoolean, GraphQLID, Kind } from 'graphql';
|
|
2
2
|
|
|
3
|
+
// The base each validated scalar delegates parsing to. A base exists before its scalar, so these links cannot cycle.
|
|
4
|
+
const validatedBases = new WeakMap();
|
|
5
|
+
|
|
6
|
+
// Follow validated-scalar chains to the scalar that defines the literal syntax. Any other scalar is the root, even
|
|
7
|
+
// with a hand-set `baseScalarType` storage hint, because its own parseLiteral decides which literals it takes.
|
|
8
|
+
const findRootScalar = (scalarType) => {
|
|
9
|
+
let root = scalarType;
|
|
10
|
+
while (validatedBases.has(root)) {
|
|
11
|
+
root = validatedBases.get(root);
|
|
12
|
+
}
|
|
13
|
+
return root;
|
|
14
|
+
};
|
|
15
|
+
|
|
3
16
|
export function createValidatedScalar(name, description, baseScalarType, validate) {
|
|
4
17
|
if (!baseScalarType) {
|
|
5
18
|
throw new Error('baseScalarType is required');
|
|
@@ -16,16 +29,21 @@ export function createValidatedScalar(name, description, baseScalarType, validat
|
|
|
16
29
|
throw new Error('baseScalarType must be a standard GraphQL scalar type or a custom scalar with a valid name');
|
|
17
30
|
}
|
|
18
31
|
|
|
32
|
+
// Float takes integer literals too, as GraphQLFloat does, so an integral default printed as `5` stays valid.
|
|
19
33
|
const kindMap = {
|
|
20
|
-
String: Kind.STRING,
|
|
21
|
-
Int: Kind.INT,
|
|
22
|
-
Float: Kind.FLOAT,
|
|
23
|
-
Boolean: Kind.BOOLEAN,
|
|
24
|
-
ID: Kind.STRING,
|
|
34
|
+
String: [Kind.STRING],
|
|
35
|
+
Int: [Kind.INT],
|
|
36
|
+
Float: [Kind.FLOAT, Kind.INT],
|
|
37
|
+
Boolean: [Kind.BOOLEAN],
|
|
38
|
+
ID: [Kind.STRING],
|
|
25
39
|
};
|
|
26
40
|
|
|
27
|
-
|
|
41
|
+
// Custom root scalars define their own literal kinds, so their parseLiteral decides.
|
|
42
|
+
const rootScalarType = findRootScalar(baseScalarType);
|
|
43
|
+
const baseKinds = Object.hasOwn(kindMap, rootScalarType.name) ? kindMap[rootScalarType.name] : undefined;
|
|
28
44
|
|
|
45
|
+
// Input is parsed by the base first, so validators judge the same internal value for variables and literals.
|
|
46
|
+
// An undefined result is the base rejecting the input; GraphQL reports it without running the validator.
|
|
29
47
|
const scalar = new GraphQLScalarType({
|
|
30
48
|
name: `${name}_${baseScalarType.name}`,
|
|
31
49
|
description,
|
|
@@ -34,19 +52,21 @@ export function createValidatedScalar(name, description, baseScalarType, validat
|
|
|
34
52
|
return baseScalarType.serialize(value);
|
|
35
53
|
},
|
|
36
54
|
parseValue(value) {
|
|
37
|
-
|
|
38
|
-
|
|
55
|
+
const parsed = baseScalarType.parseValue(value);
|
|
56
|
+
if (parsed !== undefined) validate(parsed);
|
|
57
|
+
return parsed;
|
|
39
58
|
},
|
|
40
59
|
parseLiteral(ast, variables) {
|
|
41
|
-
if (ast.kind
|
|
60
|
+
if (baseKinds !== undefined && !baseKinds.includes(ast.kind)) {
|
|
42
61
|
throw new Error(`${name}_${baseScalarType.name} must be a ${baseScalarType.name}`);
|
|
43
62
|
}
|
|
44
63
|
const value = baseScalarType.parseLiteral(ast, variables);
|
|
45
|
-
validate(value);
|
|
64
|
+
if (value !== undefined) validate(value);
|
|
46
65
|
return value;
|
|
47
66
|
},
|
|
48
67
|
});
|
|
49
68
|
|
|
50
69
|
scalar.baseScalarType = baseScalarType;
|
|
70
|
+
validatedBases.set(scalar, baseScalarType);
|
|
51
71
|
return scalar;
|
|
52
72
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// A cross-realm RegExp fails `instanceof RegExp` but keeps this tag.
|
|
2
|
+
const isRegExp = (value) => Object.prototype.toString.call(value) === '[object RegExp]';
|
|
3
|
+
|
|
4
|
+
/** Private RegExp copy shared by `validators.pattern()` and `createPatternStringScalar()`; not a published subpath. */
|
|
5
|
+
export const compilePattern = (pattern) => {
|
|
6
|
+
if (typeof pattern !== 'string' && !isRegExp(pattern)) {
|
|
7
|
+
throw new TypeError('pattern must be a RegExp or a string');
|
|
8
|
+
}
|
|
9
|
+
return new RegExp(pattern);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/** Tests each value from its first character, so `g` and `y` keep no state between values. */
|
|
13
|
+
export const matchesPattern = (regex, value) => {
|
|
14
|
+
regex.lastIndex = 0;
|
|
15
|
+
return regex.test(value);
|
|
16
|
+
};
|
package/src/scalars.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
} from 'graphql';
|
|
4
4
|
import { createValidatedScalar } from './scalars/factory.js';
|
|
5
5
|
import { isEmailFormat } from './scalars/email.js';
|
|
6
|
+
import { compilePattern, matchesPattern } from './scalars/pattern.js';
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Email scalar - validates email format
|
|
@@ -152,7 +153,7 @@ export const createBoundedFloatScalar = (name, min, max) => {
|
|
|
152
153
|
* @returns {GraphQLScalarType} A scalar type with pattern validation
|
|
153
154
|
*/
|
|
154
155
|
export const createPatternStringScalar = (name, pattern, message) => {
|
|
155
|
-
const regex =
|
|
156
|
+
const regex = compilePattern(pattern);
|
|
156
157
|
const errorMessage = message || 'Value does not match required pattern';
|
|
157
158
|
|
|
158
159
|
return createValidatedScalar(
|
|
@@ -163,7 +164,7 @@ export const createPatternStringScalar = (name, pattern, message) => {
|
|
|
163
164
|
if (typeof value !== 'string') {
|
|
164
165
|
throw new Error('Value must be a string');
|
|
165
166
|
}
|
|
166
|
-
if (!regex
|
|
167
|
+
if (!matchesPattern(regex, value)) {
|
|
167
168
|
throw new Error(errorMessage);
|
|
168
169
|
}
|
|
169
170
|
},
|
package/src/validators.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import SimfinityError from './errors/simfinity.error.js';
|
|
2
2
|
import { isEmailFormat } from './scalars/email.js';
|
|
3
|
+
import { compilePattern, matchesPattern } from './scalars/pattern.js';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Creates a validation object that works for both 'save' (CREATE) and 'update' (UPDATE) operations.
|
|
@@ -74,7 +75,7 @@ export const maxLength = (name, max) => {
|
|
|
74
75
|
};
|
|
75
76
|
|
|
76
77
|
export const pattern = (name, regex, message) => {
|
|
77
|
-
const regexObj =
|
|
78
|
+
const regexObj = compilePattern(regex);
|
|
78
79
|
const errorMessage = message || `${name} format is invalid`;
|
|
79
80
|
|
|
80
81
|
return createValidator(async (typeName, fieldName, value) => {
|
|
@@ -82,7 +83,7 @@ export const pattern = (name, regex, message) => {
|
|
|
82
83
|
throw new SimfinityError(`${name} must be a string`, 'VALIDATION_ERROR', 400);
|
|
83
84
|
}
|
|
84
85
|
|
|
85
|
-
if (!regexObj
|
|
86
|
+
if (!matchesPattern(regexObj, value)) {
|
|
86
87
|
throw new SimfinityError(errorMessage, 'VALIDATION_ERROR', 400);
|
|
87
88
|
}
|
|
88
89
|
}, false); // Optional
|
|
@@ -109,8 +110,7 @@ export const url = () => {
|
|
|
109
110
|
try {
|
|
110
111
|
// Use URL constructor for better validation
|
|
111
112
|
new URL(value);
|
|
112
|
-
} catch
|
|
113
|
-
console.log('Invalid URL format', e);
|
|
113
|
+
} catch {
|
|
114
114
|
throw new SimfinityError('Invalid URL format', 'VALIDATION_ERROR', 400);
|
|
115
115
|
}
|
|
116
116
|
}, false); // Optional
|
package/types/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
GraphQLEnumType,
|
|
3
|
+
GraphQLError,
|
|
3
4
|
GraphQLInputObjectType,
|
|
4
5
|
GraphQLObjectType,
|
|
5
6
|
GraphQLOutputType,
|
|
@@ -43,6 +44,16 @@ export interface ModelDescription {
|
|
|
43
44
|
}>;
|
|
44
45
|
}
|
|
45
46
|
export function describeModels(registrations: ModelRegistration[]): ModelDescription;
|
|
47
|
+
/**
|
|
48
|
+
* Creates the scalar `${name}_${baseScalarType.name}`. `validate` throws to reject a value and
|
|
49
|
+
* receives the base scalar's internal value: the base's `parseValue` result for variables, the
|
|
50
|
+
* base's `parseLiteral` result for inline literals, and the resolver's value before serialization
|
|
51
|
+
* for output. Inline literal kinds are checked against the root of the chain of
|
|
52
|
+
* `createValidatedScalar` scalars when that root is String, ID, Int, Float or Boolean; a Float
|
|
53
|
+
* root accepts integer and float literals. Any other scalar is the root, even with a hand-set
|
|
54
|
+
* `baseScalarType` storage hint, and its own `parseLiteral` decides. `validate` is not called
|
|
55
|
+
* when the base rejects input by returning `undefined`.
|
|
56
|
+
*/
|
|
46
57
|
export function createValidatedScalar<TInternal, TExternal>(
|
|
47
58
|
name: string,
|
|
48
59
|
description: string,
|
|
@@ -55,6 +66,10 @@ export class SimfinityError extends Error {
|
|
|
55
66
|
getCode(): string | undefined;
|
|
56
67
|
getStatus(): number | undefined;
|
|
57
68
|
getTimestamp(): string;
|
|
69
|
+
/** Set on errors that wrap a driver error, such as MongoDB's TRANSACTION_RETRY_EXCEEDED. */
|
|
70
|
+
cause?: unknown;
|
|
71
|
+
/** Set on errors that wrap a driver error, such as MongoDB's TRANSACTION_RETRY_EXCEEDED; returns `cause`. */
|
|
72
|
+
getCause?(): unknown;
|
|
58
73
|
}
|
|
59
74
|
|
|
60
75
|
export class InternalServerError extends SimfinityError {
|
|
@@ -62,7 +77,38 @@ export class InternalServerError extends SimfinityError {
|
|
|
62
77
|
cause?: unknown;
|
|
63
78
|
getCause(): unknown;
|
|
64
79
|
}
|
|
65
|
-
|
|
80
|
+
/**
|
|
81
|
+
* Creates an error formatter for a GraphQL server's error hook. A field error is a `GraphQLError`
|
|
82
|
+
* with a `path`: for the one graphql-js creates around a value raised while resolving a field, such
|
|
83
|
+
* as one a resolver threw, it classifies that value, and a raised `GraphQLError` that already had a
|
|
84
|
+
* path is classified as a field error itself. A path-less `GraphQLError` is a request error, even
|
|
85
|
+
* when it wraps a plain Error with the same message. An explicit `InternalServerError` cause,
|
|
86
|
+
* followed through GraphQLErrors only, is kept for masking; mark unexpected subscription source
|
|
87
|
+
* failures with it because a path-less wrapper cannot distinguish them from scalar input errors.
|
|
88
|
+
* Any other input, such as a syntax, validation or variable error, is classified as is:
|
|
89
|
+
* - a `SimfinityError` is kept;
|
|
90
|
+
* - a request `GraphQLError`, or one raised while resolving a field that has its own string
|
|
91
|
+
* `extensions.code` or a `SimfinityError` cause, becomes a `SimfinityError` with its own message
|
|
92
|
+
* and extensions (including ones the server added, such as Yoga's `http`). Its code is that of a
|
|
93
|
+
* `SimfinityError` reached through `GraphQLError` causes only, else its string `extensions.code`
|
|
94
|
+
* (so a code the server set, such as Yoga's `GRAPHQL_PARSE_FAILED`, is kept), else
|
|
95
|
+
* `BAD_REQUEST`; its status is that error's, else its integer `extensions.status`, else 500 for
|
|
96
|
+
* `INTERNAL_SERVER_ERROR` and 400 otherwise. Its `originalError` is never exposed, so input
|
|
97
|
+
* rejected by Simfinity's validated scalars is `BAD_REQUEST` (400);
|
|
98
|
+
* - any other `GraphQLError` raised while resolving a field, including the ones graphql-js raises
|
|
99
|
+
* when it cannot complete a resolved value (scalar or enum serialization, `isTypeOf`, abstract
|
|
100
|
+
* type resolution, a non-iterable list), becomes `InternalServerError` with that message and the
|
|
101
|
+
* error as its cause;
|
|
102
|
+
* - any other `Error` becomes `InternalServerError` with that error as its cause;
|
|
103
|
+
* - a non-Error value becomes `InternalServerError('Unexpected error value')` with no cause. Servers
|
|
104
|
+
* whose executor first turns it into an `Error`, such as Yoga, pass the value's text instead.
|
|
105
|
+
*
|
|
106
|
+
* `callback` receives the classified error; check `error instanceof InternalServerError` to mask
|
|
107
|
+
* unexpected errors. A returned error replaces it, and a returned `GraphQLError` is returned
|
|
108
|
+
* unchanged. The result is a `GraphQLError` that keeps the input's locations and path, has the
|
|
109
|
+
* chosen error as `originalError`, and copies its `extensions`.
|
|
110
|
+
*/
|
|
111
|
+
export function buildErrorFormatter(callback?: (error: SimfinityError) => Error | void): (error: unknown) => GraphQLError;
|
|
66
112
|
|
|
67
113
|
export interface EntityController<Session = any> {
|
|
68
114
|
onSaving?(record: any, args: any, session: Session | undefined, context: any): void | Promise<void>;
|
|
@@ -87,6 +133,14 @@ export interface RuntimeRegistration<Model = any, Session = any> extends ModelRe
|
|
|
87
133
|
controller?: EntityController<Session> | null;
|
|
88
134
|
onModelCreated?: ((model: Model) => void) | null;
|
|
89
135
|
stateMachine?: StateMachine<Session> | null;
|
|
136
|
+
/**
|
|
137
|
+
* Set by `createSchema()` before `adapter.prepare()`: true for entity types, those registered
|
|
138
|
+
* with `connect()` and the targets of non-embedded relations, whose `id` resolves to the stored
|
|
139
|
+
* `_id`, falling back to `id`. False for types this runtime uses only as embedded values. The `id`
|
|
140
|
+
* rule follows the type object, so a type that another runtime sharing it treats as an entity
|
|
141
|
+
* resolves `id` the same way here.
|
|
142
|
+
*/
|
|
143
|
+
storedIdentity?: boolean;
|
|
90
144
|
}
|
|
91
145
|
export interface MiddlewareContext {
|
|
92
146
|
args: any;
|
|
@@ -96,16 +150,86 @@ export interface MiddlewareContext {
|
|
|
96
150
|
context?: any;
|
|
97
151
|
[key: string]: any;
|
|
98
152
|
}
|
|
153
|
+
/** Operations that a type's `extensions.scope` can restrict. */
|
|
154
|
+
export type ScopeOperation = 'find' | 'get_by_id' | 'aggregate';
|
|
155
|
+
/** Argument of a scope function. Mutate `args` in place; the return value is ignored. */
|
|
156
|
+
export interface ScopeParams<Model = any, Session = any> {
|
|
157
|
+
type: RuntimeRegistration<Model, Session>;
|
|
158
|
+
args: Record<string, any>;
|
|
159
|
+
operation: ScopeOperation;
|
|
160
|
+
context: any;
|
|
161
|
+
}
|
|
162
|
+
export type ScopeFunction<Model = any, Session = any> = (params: ScopeParams<Model, Session>) => unknown;
|
|
163
|
+
/**
|
|
164
|
+
* Value of a type's `extensions.scope`: a plain object whose keys are scope operations and whose
|
|
165
|
+
* values are functions. Omit a key to leave that operation unscoped. A present key must hold a
|
|
166
|
+
* function: `{ find: undefined }` type-checks but throws `INVALID_SCOPE` (500) at startup, so
|
|
167
|
+
* leave the key out instead. Every own string key is checked, including non-enumerable ones. Any
|
|
168
|
+
* other shape throws `INVALID_SCOPE` at registration, at `createSchema()`, or on a read after it
|
|
169
|
+
* was changed.
|
|
170
|
+
*/
|
|
171
|
+
export type TypeScopes<Model = any, Session = any> = Partial<Record<ScopeOperation, ScopeFunction<Model, Session>>>;
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Process-wide limits for generated add, update and state-action mutations. Any other option,
|
|
175
|
+
* such as a misspelled `maxNestedOperation`, throws `INVALID_MUTATION_LIMITS` (400).
|
|
176
|
+
*/
|
|
177
|
+
export interface MutationLimitsOptions {
|
|
178
|
+
/**
|
|
179
|
+
* Maximum number of `added`, `updated` and `deleted` entries across every nested level of
|
|
180
|
+
* non-embedded collection fields in one generated mutation. A non-negative safe integer;
|
|
181
|
+
* `null` or omitted means unlimited (the default).
|
|
182
|
+
*/
|
|
183
|
+
maxNestedOperations?: number | null;
|
|
184
|
+
}
|
|
185
|
+
|
|
99
186
|
export interface Runtime<Model = any, Session = any> {
|
|
100
187
|
configureQueryLimits(options?: { maxPageSize?: number }): void;
|
|
188
|
+
/**
|
|
189
|
+
* Process-wide; calling it without options restores the unlimited default. Invalid options,
|
|
190
|
+
* including unknown or misspelled option keys, throw `INVALID_MUTATION_LIMITS` (400) and keep
|
|
191
|
+
* the current limit. A generated mutation over the limit fails with
|
|
192
|
+
* `NESTED_OPERATIONS_EXCEEDED` (400) before its transaction starts.
|
|
193
|
+
*/
|
|
194
|
+
configureMutationLimits(options?: MutationLimitsOptions): void;
|
|
195
|
+
/**
|
|
196
|
+
* Throws `INVALID_SCOPE` (500) for an invalid `extensions.scope`, and
|
|
197
|
+
* `TYPE_BOUND_TO_OTHER_RUNTIME` (409) when another runtime bound the type by generating its
|
|
198
|
+
* relation resolvers, or reserved it because its schema reached the type with an unresolved
|
|
199
|
+
* relation field. A rejected type is not registered.
|
|
200
|
+
*/
|
|
101
201
|
connect(model: Model | null, type: GraphQLObjectType, singular: string, plural: string, controller?: EntityController<Session> | null, onModelCreated?: ((model: Model) => void) | null, stateMachine?: StateMachine<Session> | null): void;
|
|
202
|
+
/** Throws like `connect` for an invalid scope or a type bound to another runtime. */
|
|
102
203
|
addNoEndpointType(type: GraphQLObjectType): void;
|
|
204
|
+
/**
|
|
205
|
+
* Validates every registered scope (`INVALID_SCOPE`) before building models, and rejects
|
|
206
|
+
* (`TYPE_BOUND_TO_OTHER_RUNTIME`) any reachable type that another runtime bound or reserved, or
|
|
207
|
+
* whose field was copied with `toConfig()` after another runtime generated its relation
|
|
208
|
+
* resolver, keeping the generated resolver or its extensions. A list or embedded relation to an unregistered type throws
|
|
209
|
+
* `UNREGISTERED_RELATION_TARGET` (500). A writable non-embedded list relation without a
|
|
210
|
+
* non-empty child `connectionField`, whose resolver this runtime would generate, throws
|
|
211
|
+
* `INVALID_MODEL` (400) before `adapter.prepare()`; one with an application resolver or
|
|
212
|
+
* `readOnly` only logs a `Configuration issue` warning, and its nested writes fail with
|
|
213
|
+
* `INVALID_MODEL` (500). Fields named like generated query arguments also log a warning. The
|
|
214
|
+
* first schema binds the types whose relation resolvers it generates to this runtime and
|
|
215
|
+
* reserves reachable types that still have a
|
|
216
|
+
* non-embedded relation field without a resolver, such as an unregistered custom mutation result.
|
|
217
|
+
* Every entity type resolves `id` to `_id ?? id`, whatever the allowlists include.
|
|
218
|
+
*/
|
|
103
219
|
createSchema(includedQueryTypes?: GraphQLObjectType[] | null, includedMutationTypes?: GraphQLObjectType[] | null, includedCustomMutations?: string[] | null): GraphQLSchema;
|
|
104
220
|
getModel(type: GraphQLObjectType | { name: string }): Model | null | undefined;
|
|
105
221
|
getType(name: string | { name: string }): GraphQLObjectType | null | undefined;
|
|
106
222
|
/** Available after createSchema has built input types. */
|
|
107
223
|
getInputType(type: GraphQLObjectType | { name: string }): GraphQLInputObjectType | undefined;
|
|
108
224
|
getRegistrations(): RuntimeRegistration<Model, Session>[];
|
|
225
|
+
/**
|
|
226
|
+
* Middleware runs in registration order before the operation. Throws `INVALID_MIDDLEWARE`
|
|
227
|
+
* (500) for a non-function. The rest of the chain runs at most once and is awaited even when a
|
|
228
|
+
* middleware does not await `next()`; its errors cancel the operation, even if a middleware
|
|
229
|
+
* catches them. Call `next()` before the middleware returns or its promise settles: a later
|
|
230
|
+
* call, such as `setTimeout(next)`, does nothing. Omitting `next()` skips the remaining
|
|
231
|
+
* middleware only.
|
|
232
|
+
*/
|
|
109
233
|
use(middleware: (params: MiddlewareContext, next: () => Promise<void>) => void | Promise<void>): void;
|
|
110
234
|
registerMutation(name: string, description: string, input: GraphQLInputObjectType | null | undefined, output: GraphQLOutputType, callback: (args: any, session: Session, context: any) => any): void;
|
|
111
235
|
/** Owns the full workflow transaction unless an active caller session is supplied. */
|
|
@@ -119,12 +243,29 @@ export interface DatabaseAdapter<Model = any, Session = any> {
|
|
|
119
243
|
validateRegistration?(registration: RuntimeRegistration<Model, Session>): void;
|
|
120
244
|
prepare?(registrations: RuntimeRegistration<Model, Session>[], options: { createCollection: boolean }): void;
|
|
121
245
|
createModel(type: GraphQLObjectType, callback: ((model: Model) => void) | null | undefined, options: { createCollection: boolean }): Model;
|
|
246
|
+
/**
|
|
247
|
+
* Casts a reference or batch key to the stored identifier type. Throw a `SimfinityError` with
|
|
248
|
+
* code `NOT_VALID_ID` (400) for a malformed value; never create a new identifier. A batched
|
|
249
|
+
* reference read whose ID throws or is normalized reads that ID alone with `getById`. When
|
|
250
|
+
* `getById` rejects a stored reference with that error, the generated reference field fails with
|
|
251
|
+
* an `InternalServerError` whose cause is that error, since the client did not send the value.
|
|
252
|
+
*/
|
|
122
253
|
castId(value: any): any;
|
|
123
254
|
stateValue?(state: { name: string; value: any }): any;
|
|
124
255
|
withTransaction<T>(session: Session | null | undefined, callback: (session: Session) => Promise<T> | T, model?: Model): Promise<T>;
|
|
125
256
|
newRecord(model: Model, data: any, session?: Session): any;
|
|
126
257
|
saveRecord(model: Model, record: any, session?: Session): any;
|
|
127
258
|
toObject(record: any): any;
|
|
259
|
+
/**
|
|
260
|
+
* Optional. When defined, every nullable, singular embedded object field without a resolver
|
|
261
|
+
* passes its value through `readEmbeddedValue(value)`. The field is read as graphql's default
|
|
262
|
+
* resolver reads it: a method on the parent is called with (args, context, info), and the hook
|
|
263
|
+
* receives its result once a returned promise settles. Return null for a value that a hydrated
|
|
264
|
+
* record renders as an object although the stored value is an explicit null; return any other
|
|
265
|
+
* value unchanged. It must depend only on the value: these resolvers read no data and do not bind
|
|
266
|
+
* their types, so a runtime that reaches a shared type may read through another runtime's hook.
|
|
267
|
+
*/
|
|
268
|
+
readEmbeddedValue?(value: any): any;
|
|
128
269
|
getById(model: Model, id: any, session?: Session | null, options?: { projection?: Record<string, number>; plain?: boolean; lock?: boolean; requiredId?: any; context?: any }): any;
|
|
129
270
|
/** Optional batch read: the records found for `ids`, in getById shape and any order. After a failure, the runtime reads each ID with getById. */
|
|
130
271
|
getByIds?(model: Model, ids: any[], options?: { context?: any }): any;
|
|
@@ -151,6 +292,11 @@ export function createQueryPlan(models: ModelDescription, entityName: string, in
|
|
|
151
292
|
export function resolveModelPath(models: ModelDescription, entityName: string, path: string | string[]): FieldDescription[];
|
|
152
293
|
|
|
153
294
|
export function configureQueryLimits(options?: { maxPageSize?: number }): void;
|
|
295
|
+
/**
|
|
296
|
+
* Same process-wide setting as `Runtime.configureMutationLimits`. Unknown or misspelled options
|
|
297
|
+
* throw `INVALID_MUTATION_LIMITS` (400) and keep the current limit.
|
|
298
|
+
*/
|
|
299
|
+
export function configureMutationLimits(options?: MutationLimitsOptions): void;
|
|
154
300
|
export function paginationStages(pagination: { page: number; size: number } | null | undefined, withDefault: boolean): Array<{ $skip: number } | { $limit: number }>;
|
|
155
301
|
|
|
156
302
|
/** Envelop-style schema plugin used by the shared authorization helpers. */
|
|
@@ -181,6 +327,11 @@ export interface FieldValidations {
|
|
|
181
327
|
export const validators: {
|
|
182
328
|
stringLength(name: string, min?: number, max?: number): FieldValidations;
|
|
183
329
|
maxLength(name: string, max: number): FieldValidations;
|
|
330
|
+
/**
|
|
331
|
+
* Copies the pattern when the helper is created and tests each value from its first character,
|
|
332
|
+
* so `g` and `y` keep no state between values. Throws `TypeError` unless the pattern is a
|
|
333
|
+
* `RegExp` or a string.
|
|
334
|
+
*/
|
|
184
335
|
pattern(name: string, regex: RegExp | string, message?: string): FieldValidations;
|
|
185
336
|
email(): FieldValidations;
|
|
186
337
|
url(): FieldValidations;
|
|
@@ -204,6 +355,7 @@ export const scalars: {
|
|
|
204
355
|
createBoundedStringScalar(name: string, min?: number, max?: number): GraphQLScalarType;
|
|
205
356
|
createBoundedIntScalar(name: string, min?: number, max?: number): GraphQLScalarType;
|
|
206
357
|
createBoundedFloatScalar(name: string, min?: number, max?: number): GraphQLScalarType;
|
|
358
|
+
/** Same pattern rules as `validators.pattern`: a private copy, tested from the first character. */
|
|
207
359
|
createPatternStringScalar(name: string, pattern: RegExp | string, message?: string): GraphQLScalarType;
|
|
208
360
|
};
|
|
209
361
|
|
|
@@ -248,7 +400,11 @@ declare class ForbiddenError extends SimfinityError {
|
|
|
248
400
|
|
|
249
401
|
/** Authorization utilities (RBAC/ABAC rules, plugin factories and auth errors). */
|
|
250
402
|
export const auth: {
|
|
251
|
-
/**
|
|
403
|
+
/**
|
|
404
|
+
* Wraps schema resolvers in-place. Throws TypeError for invalid rules, maps, or defaultPolicy.
|
|
405
|
+
* `onSchemaChange` throws TypeError, before wrapping any field, when a schema type was created
|
|
406
|
+
* by another copy of the graphql module.
|
|
407
|
+
*/
|
|
252
408
|
createAuthPlugin(permissions: PermissionSchema, options?: AuthPluginOptions): EnvelopSchemaPlugin;
|
|
253
409
|
/** @deprecated Use createAuthPlugin instead. graphql-middleware compatible middleware. */
|
|
254
410
|
createAuthMiddleware(
|
|
@@ -264,24 +420,40 @@ export const auth: {
|
|
|
264
420
|
options?: AuthPluginOptions,
|
|
265
421
|
): (resolve: any, parent: any, args: any, ctx: any, info: any) => Promise<any>;
|
|
266
422
|
resolvePath(obj: any, pathOrFn: string | ((obj: any) => any)): any;
|
|
267
|
-
|
|
423
|
+
/**
|
|
424
|
+
* Paths in the helpers below are dotted strings or synchronous extractors. An `async`
|
|
425
|
+
* extractor throws TypeError when the helper is created; a path that yields a promise or other
|
|
426
|
+
* thenable denies with TypeError.
|
|
427
|
+
*/
|
|
428
|
+
requireAuth(userPath?: string | ((ctx: any) => unknown)): AuthRuleFunction;
|
|
268
429
|
/** Required roles must be nonempty strings; invalid configuration throws TypeError. */
|
|
269
|
-
requireRole(
|
|
430
|
+
requireRole(
|
|
431
|
+
role: string | string[],
|
|
432
|
+
options?: { userPath?: string | ((ctx: any) => unknown); rolePath?: string | ((user: any) => unknown) },
|
|
433
|
+
): AuthRuleFunction;
|
|
270
434
|
/** Exact array membership; only a standalone '*' claim grants all permissions. */
|
|
271
435
|
requirePermission(
|
|
272
436
|
permission: string | string[],
|
|
273
|
-
options?: { userPath?: string; permissionsPath?: string },
|
|
437
|
+
options?: { userPath?: string | ((ctx: any) => unknown); permissionsPath?: string | ((user: any) => unknown) },
|
|
274
438
|
): AuthRuleFunction;
|
|
275
439
|
/** Requires at least one rule function; throws TypeError otherwise. */
|
|
276
440
|
composeRules(...rules: AuthRuleFunction[]): AuthRuleFunction;
|
|
277
441
|
/** Requires at least one rule function; throws TypeError otherwise. */
|
|
278
442
|
anyRule(...rules: AuthRuleFunction[]): AuthRuleFunction;
|
|
279
443
|
/** Compares nonempty string, finite number, or MongoDB ObjectId identities; missing IDs deny. */
|
|
280
|
-
isOwner(
|
|
444
|
+
isOwner(
|
|
445
|
+
ownerField?: string | ((parent: any) => unknown),
|
|
446
|
+
userIdField?: string | ((user: any) => unknown),
|
|
447
|
+
options?: { userPath?: string | ((ctx: any) => unknown) },
|
|
448
|
+
): AuthRuleFunction;
|
|
281
449
|
createRule(predicate: AuthRuleFunction, errorMessage?: string, errorCode?: string): AuthRuleFunction;
|
|
282
450
|
allow(): AuthRuleFunction;
|
|
283
451
|
deny(message?: string): AuthRuleFunction;
|
|
284
|
-
/**
|
|
452
|
+
/**
|
|
453
|
+
* Invalid ASTs and unresolved comparisons deny, including under negation. Dates compare by
|
|
454
|
+
* time. Promise operands are always invalid; NaN and Date/non-Date comparisons are invalid
|
|
455
|
+
* except against `null`, which stays comparable with any value (the comparison is false).
|
|
456
|
+
*/
|
|
285
457
|
evaluateExpression(expression: unknown, context: any): boolean;
|
|
286
458
|
isPolicyExpression(value: unknown): boolean;
|
|
287
459
|
/** Throws TypeError for a malformed expression. */
|