@simtlix/simfinity-core 3.11.0 → 3.13.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 +10 -1
- package/package.json +2 -1
- package/src/index.js +1 -0
- package/src/metadata.js +24 -0
- package/src/record-version.js +235 -0
- package/src/runtime.js +718 -113
- package/types/index.d.ts +153 -5
package/types/index.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ import type {
|
|
|
2
2
|
GraphQLEnumType,
|
|
3
3
|
GraphQLError,
|
|
4
4
|
GraphQLInputObjectType,
|
|
5
|
+
GraphQLList,
|
|
6
|
+
GraphQLNonNull,
|
|
5
7
|
GraphQLObjectType,
|
|
6
8
|
GraphQLOutputType,
|
|
7
9
|
GraphQLScalarType,
|
|
@@ -13,6 +15,20 @@ export const QLOperator: GraphQLEnumType;
|
|
|
13
15
|
export const QLSort: GraphQLInputObjectType;
|
|
14
16
|
export const QLValue: GraphQLScalarType<unknown, unknown>;
|
|
15
17
|
|
|
18
|
+
/**
|
|
19
|
+
* The scalar of the `version` field that Simfinity adds to a type that declares optimistic
|
|
20
|
+
* concurrency (see {@link ConcurrencyDeclaration}). It holds safe integers from 1 to
|
|
21
|
+
* {@link MAX_RECORD_VERSION}, a JSON number on the wire: a string, a float, 0, a negative number and an
|
|
22
|
+
* unsafe integer fail GraphQL coercion. A client sends the version it read back unchanged in updates
|
|
23
|
+
* and deletes, and Simfinity advances it. Only the managed field may use it in a type, which
|
|
24
|
+
* `createSchema()` and `describeModels()` enforce with `INVALID_MODEL` (400); a registered mutation or
|
|
25
|
+
* custom query may use it in its own input and output types. Import it from the facade the application
|
|
26
|
+
* uses: the protection depends on one copy of core per process.
|
|
27
|
+
*/
|
|
28
|
+
export const RecordVersion: GraphQLScalarType;
|
|
29
|
+
/** The highest version, `Number.MAX_SAFE_INTEGER`. The version of a record never goes past it. */
|
|
30
|
+
export const MAX_RECORD_VERSION: number;
|
|
31
|
+
|
|
16
32
|
/**
|
|
17
33
|
* A `QLOperator` value. The operators of 3.7.0 are `EQ`, `NE`, `LT`, `LTE`, `GT`, `GTE`, `BTW`, `IN`,
|
|
18
34
|
* `NIN` and `LIKE`. The literal text operators take a non-null string without U+0000 on a `String`
|
|
@@ -42,6 +58,26 @@ export type QueryOperator =
|
|
|
42
58
|
| 'ILIKE' | 'STARTS_WITH' | 'ISTARTS_WITH' | 'ENDS_WITH' | 'IENDS_WITH' | 'IEQ'
|
|
43
59
|
| 'IS_EMPTY' | 'IS_NOT_EMPTY';
|
|
44
60
|
|
|
61
|
+
/**
|
|
62
|
+
* A condition of a {@link FilterGroup}, as the `QLFilterCondition` input takes it: the field it
|
|
63
|
+
* tests, its {@link QueryOperator} (`EQ` when omitted) and its value. On an object field, `path`
|
|
64
|
+
* names the member it tests; `IS_EMPTY` and `IS_NOT_EMPTY` take no value and may omit `path` to test
|
|
65
|
+
* the field itself.
|
|
66
|
+
*/
|
|
67
|
+
export interface FilterCondition {
|
|
68
|
+
field: string;
|
|
69
|
+
operator?: QueryOperator | (string & {});
|
|
70
|
+
value?: any;
|
|
71
|
+
path?: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** A `QLFilterGroup` value: flat conditions plus nested `AND` and `OR` groups. */
|
|
75
|
+
export interface FilterGroup {
|
|
76
|
+
AND?: FilterGroup[];
|
|
77
|
+
OR?: FilterGroup[];
|
|
78
|
+
conditions?: FilterCondition[];
|
|
79
|
+
}
|
|
80
|
+
|
|
45
81
|
/**
|
|
46
82
|
* The query language features a {@link DatabaseAdapter} compiles. Declare it so that the runtime
|
|
47
83
|
* sends the adapter only conditions it compiles; later releases add members for later features.
|
|
@@ -77,7 +113,8 @@ export interface FieldDescription {
|
|
|
77
113
|
itemRequired: boolean;
|
|
78
114
|
unique: boolean;
|
|
79
115
|
readOnly: boolean;
|
|
80
|
-
|
|
116
|
+
/** `'Version'` is the managed version field of a type that declares optimistic concurrency (`version: true`). */
|
|
117
|
+
scalar?: 'ID' | 'String' | 'Int' | 'Float' | 'Boolean' | 'DateTime' | 'Enum' | 'Version';
|
|
81
118
|
values?: string[];
|
|
82
119
|
enumValues?: { name: string; value: unknown }[];
|
|
83
120
|
target?: string;
|
|
@@ -85,6 +122,12 @@ export interface FieldDescription {
|
|
|
85
122
|
fields?: FieldDescription[];
|
|
86
123
|
private?: boolean;
|
|
87
124
|
inferred?: boolean;
|
|
125
|
+
/**
|
|
126
|
+
* Set on the managed version field of a type that declares optimistic concurrency, which
|
|
127
|
+
* `describeModels()` appends after the type's own fields: `{ name: 'version', scalar: 'Version',
|
|
128
|
+
* required: true, readOnly: true, version: true }`.
|
|
129
|
+
*/
|
|
130
|
+
version?: true;
|
|
88
131
|
}
|
|
89
132
|
export interface ModelDescription {
|
|
90
133
|
entities: Array<{
|
|
@@ -99,6 +142,13 @@ export interface ModelDescription {
|
|
|
99
142
|
* fields are left out of every entity and embedded type, and the types only they reach are not
|
|
100
143
|
* described. Throws `INVALID_MODEL` (400) for an invalid `extensions.virtual` declaration, as
|
|
101
144
|
* `createSchema()` does (see {@link VirtualFieldOptions}).
|
|
145
|
+
*
|
|
146
|
+
* A type that declares optimistic concurrency (see {@link ConcurrencyDeclaration}) is described with
|
|
147
|
+
* its version field last, before `createSchema()` has added it and after, and the call throws
|
|
148
|
+
* `INVALID_MODEL` (400) for an invalid declaration, for a field named `version` that Simfinity did not
|
|
149
|
+
* add, for a reference stored under `version` (`connectionField: 'version'`), for a collection of
|
|
150
|
+
* such items that links through `version`, for such a type used as an embedded value, and for any
|
|
151
|
+
* other field typed {@link RecordVersion}.
|
|
102
152
|
*/
|
|
103
153
|
export function describeModels(registrations: ModelRegistration[]): ModelDescription;
|
|
104
154
|
/**
|
|
@@ -290,6 +340,19 @@ export type ScopeFunction<Model = any, Session = any> = (params: ScopeParams<Mod
|
|
|
290
340
|
* was changed.
|
|
291
341
|
*/
|
|
292
342
|
export type TypeScopes<Model = any, Session = any> = Partial<Record<ScopeOperation, ScopeFunction<Model, Session>>>;
|
|
343
|
+
/**
|
|
344
|
+
* Value of a type's `extensions.concurrency`: the entity is protected by optimistic concurrency.
|
|
345
|
+
* Simfinity adds a read-only `version: RecordVersion!` field to the type, which starts at 1 and grows
|
|
346
|
+
* by 1 with each accepted write; a client sends the version it read back in updates and deletes, and a
|
|
347
|
+
* write at another version fails with `VERSION_CONFLICT` (409). The only valid value is
|
|
348
|
+
* `{ mode: 'optimistic' }`: `null`, an array, another mode or an extra key throws `INVALID_MODEL`
|
|
349
|
+
* (400). So does a type that is not an entity, one used as an embedded value, one that already has a
|
|
350
|
+
* field named `version` (or a field stored under that name), and an adapter that does not declare
|
|
351
|
+
* `writeFeatures.optimisticConcurrency`.
|
|
352
|
+
*/
|
|
353
|
+
export interface ConcurrencyDeclaration {
|
|
354
|
+
mode: 'optimistic';
|
|
355
|
+
}
|
|
293
356
|
|
|
294
357
|
/**
|
|
295
358
|
* Process-wide limits for generated add, update and state-action mutations. Any other option,
|
|
@@ -327,6 +390,42 @@ export interface QueryLimitsOptions {
|
|
|
327
390
|
maxReferenceFrames?: number;
|
|
328
391
|
}
|
|
329
392
|
|
|
393
|
+
/**
|
|
394
|
+
* The type of a view parameter: a value the client sends as a variable or a literal, so a scalar,
|
|
395
|
+
* an enum or a list of them, each possibly non-null, never an input object.
|
|
396
|
+
*/
|
|
397
|
+
export type ViewParamType =
|
|
398
|
+
| GraphQLScalarType
|
|
399
|
+
| GraphQLEnumType
|
|
400
|
+
| GraphQLList<ViewParamType>
|
|
401
|
+
| GraphQLNonNull<GraphQLScalarType | GraphQLEnumType | GraphQLList<ViewParamType>>;
|
|
402
|
+
|
|
403
|
+
/** A view parameter: an argument of the view's root field that only its `where` reads. */
|
|
404
|
+
export interface ViewParam {
|
|
405
|
+
type: ViewParamType;
|
|
406
|
+
description?: string;
|
|
407
|
+
defaultValue?: unknown;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** The declaration of a named view (see {@link Runtime.registerView}). */
|
|
411
|
+
export interface ViewOptions {
|
|
412
|
+
/** The connected endpoint type whose list the view returns. */
|
|
413
|
+
type: GraphQLObjectType;
|
|
414
|
+
/** Non-empty; the root field's description. */
|
|
415
|
+
description: string;
|
|
416
|
+
/**
|
|
417
|
+
* Arguments of the root field besides the generated list arguments, named unlike them and unlike
|
|
418
|
+
* the fields of `type`. They never reach the adapter: only `where` receives them.
|
|
419
|
+
*/
|
|
420
|
+
params?: Record<string, ViewParam>;
|
|
421
|
+
/**
|
|
422
|
+
* Called on every read with the param values and the request context. The groups it returns are
|
|
423
|
+
* appended to the client's filters after middleware, so neither the client nor middleware can drop
|
|
424
|
+
* them; their paths may name fields that are not queryable.
|
|
425
|
+
*/
|
|
426
|
+
where: (input: { params: Record<string, any>; context: any }) => FilterGroup[] | Promise<FilterGroup[]>;
|
|
427
|
+
}
|
|
428
|
+
|
|
330
429
|
export interface Runtime<Model = any, Session = any> {
|
|
331
430
|
/** The same process-wide setting as the root `configureQueryLimits`. */
|
|
332
431
|
configureQueryLimits(options?: QueryLimitsOptions): void;
|
|
@@ -410,6 +509,17 @@ export interface Runtime<Model = any, Session = any> {
|
|
|
410
509
|
* (500) for a name already registered.
|
|
411
510
|
*/
|
|
412
511
|
registerCustomQuery(name: string, description: string, input: GraphQLInputObjectType | null | undefined, output: GraphQLOutputType, callback: (input: any, context: any) => any): void;
|
|
512
|
+
/**
|
|
513
|
+
* Register a named view: a root query field that returns the list of `options.type` with the
|
|
514
|
+
* generated list arguments plus `options.params`, and appends the groups of `options.where` after
|
|
515
|
+
* middleware. A schema includes the view exactly when it includes the type. Throws `INVALID_VIEW`
|
|
516
|
+
* (500) for an invalid declaration and `QUERY_NAME_CONFLICT` (500) for a name already registered as
|
|
517
|
+
* a view, keeping the first registration. `createSchema` throws `INVALID_VIEW` when the type is not
|
|
518
|
+
* connected in this runtime or a param is named like one of its fields or a generated list
|
|
519
|
+
* argument, and `QUERY_NAME_CONFLICT` when an included view is named like an included generated or
|
|
520
|
+
* custom query.
|
|
521
|
+
*/
|
|
522
|
+
registerView(name: string, options: ViewOptions): void;
|
|
413
523
|
/** Owns the full workflow transaction unless an active caller session is supplied. */
|
|
414
524
|
saveObject(typeName: string, args: Record<string, any>, session?: Session, context?: any): Promise<any>;
|
|
415
525
|
preventCreatingCollection(prevent: boolean): void;
|
|
@@ -432,6 +542,15 @@ export interface DatabaseAdapter<Model = any, Session = any> {
|
|
|
432
542
|
* collections also needs `referenceEmptiness: true`, and fails with `UNSUPPORTED_QUERY` without it.
|
|
433
543
|
*/
|
|
434
544
|
queryFeatures?: QueryFeatures | null | undefined;
|
|
545
|
+
/**
|
|
546
|
+
* Optional. Declares the write features this adapter implements. `createRuntime` reads it once and
|
|
547
|
+
* throws `INVALID_WRITE_FEATURES` (500) unless it is undefined, null or an object whose
|
|
548
|
+
* `optimisticConcurrency`, if present, is a boolean. A schema with a type that declares
|
|
549
|
+
* `extensions.concurrency` (see {@link ConcurrencyDeclaration}) throws `INVALID_MODEL` (400) unless
|
|
550
|
+
* the adapter declares `optimisticConcurrency: true`, which commits it to the `options.expectedVersion`
|
|
551
|
+
* contract of `update` and `delete` and to the requirements on `find` below.
|
|
552
|
+
*/
|
|
553
|
+
writeFeatures?: { optimisticConcurrency?: boolean } | null | undefined;
|
|
435
554
|
bind?(runtime: Pick<Runtime<Model, Session>, 'getModel' | 'getType' | 'getRegistrations'>): void;
|
|
436
555
|
validateRegistration?(registration: RuntimeRegistration<Model, Session>): void;
|
|
437
556
|
/**
|
|
@@ -440,10 +559,16 @@ export interface DatabaseAdapter<Model = any, Session = any> {
|
|
|
440
559
|
* must never be stored: an adapter that builds storage from a type's fields skips each field for
|
|
441
560
|
* which {@link isVirtualField} returns true, on the types its embedded fields reach as well, or
|
|
442
561
|
* builds it from `describeModels(registrations)`, which leaves them out. Generated writes, filters,
|
|
443
|
-
* sorts and aggregations never name a virtual field; a controller hook can still assign one.
|
|
562
|
+
* sorts and aggregations never name a virtual field; a controller hook can still assign one. The
|
|
563
|
+
* version field of a type that declares optimistic concurrency is injected after `prepare()`, so a
|
|
564
|
+
* type here may not have it yet; `describeModels(registrations)` describes it either way.
|
|
444
565
|
*/
|
|
445
566
|
prepare?(registrations: RuntimeRegistration<Model, Session>[], options: { createCollection: boolean }): void;
|
|
446
|
-
/**
|
|
567
|
+
/**
|
|
568
|
+
* Creates the model of a registered type. Its fields include virtual fields; see `prepare()`. A type
|
|
569
|
+
* that declares optimistic concurrency already has its injected version field here, which
|
|
570
|
+
* `describeModels(registrations)` describes as well.
|
|
571
|
+
*/
|
|
447
572
|
createModel(type: GraphQLObjectType, callback: ((model: Model) => void) | null | undefined, options: { createCollection: boolean }): Model;
|
|
448
573
|
/**
|
|
449
574
|
* Casts a reference or batch key to the stored identifier type. Throw a `SimfinityError` with
|
|
@@ -505,8 +630,31 @@ export interface DatabaseAdapter<Model = any, Session = any> {
|
|
|
505
630
|
*/
|
|
506
631
|
readByIdIsInert?(model: Model): boolean;
|
|
507
632
|
prepareUpdate(set: Record<string, any>, unset: Record<string, string>): any;
|
|
508
|
-
|
|
509
|
-
|
|
633
|
+
/**
|
|
634
|
+
* With `options.expectedVersion`, one atomic write that matches the ID and a stored version equal to
|
|
635
|
+
* it (on MongoDB a missing or null stored version counts as 1), applies `changes`, sets the version
|
|
636
|
+
* to `expectedVersion + 1` and returns the updated record. When nothing matches, or when
|
|
637
|
+
* `expectedVersion >= MAX_RECORD_VERSION` and the write is skipped, it reads the record by ID in the
|
|
638
|
+
* same session: it returns null for a missing one, throws `VERSION_CONFLICT` (409) when the stored
|
|
639
|
+
* version differs, `VERSION_EXHAUSTED` (409) when stored and expected are `MAX_RECORD_VERSION`, and
|
|
640
|
+
* returns null otherwise. Without `options.expectedVersion` it behaves as before. A wrapper must
|
|
641
|
+
* forward every argument, `options` included.
|
|
642
|
+
*/
|
|
643
|
+
update(model: Model, id: any, changes: any, session?: Session, options?: { expectedVersion?: number }): any;
|
|
644
|
+
/**
|
|
645
|
+
* With `options.expectedVersion`, one atomic delete that matches the ID and the version, and returns
|
|
646
|
+
* the deleted record. When nothing matches, it decides as `update` does, without the exhausted case:
|
|
647
|
+
* a delete does not advance the version, so a record at `MAX_RECORD_VERSION` is deleted like any
|
|
648
|
+
* other. Without it, it behaves as before. A wrapper must forward every argument, `options`
|
|
649
|
+
* included.
|
|
650
|
+
*/
|
|
651
|
+
delete(model: Model, id: any, session?: Session, options?: { expectedVersion?: number }): any;
|
|
652
|
+
/**
|
|
653
|
+
* An adapter that declares `writeFeatures.optimisticConcurrency` must run `find` inside the given
|
|
654
|
+
* `session`, honor `options.requiredId` and accept `options.context`: the runtime reads through
|
|
655
|
+
* `find` with the caller's session to check, before a hook runs, whether the caller can read a
|
|
656
|
+
* record whose version is stale.
|
|
657
|
+
*/
|
|
510
658
|
find(model: Model, type: GraphQLObjectType, args: any, session?: Session | null, options?: { requiredId?: any; context?: any }): any;
|
|
511
659
|
count(model: Model, type: GraphQLObjectType, args: any, session?: Session | null): Promise<number> | number;
|
|
512
660
|
aggregate(model: Model, type: GraphQLObjectType, args: any, session?: Session | null): any;
|