@simtlix/simfinity-core 3.15.0 → 3.16.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/types/index.d.ts CHANGED
@@ -56,11 +56,18 @@ export const MAX_RECORD_VERSION: number;
56
56
  * missing; constructs that middleware and scopes add read stored data. With any other adapter they
57
57
  * fail with `UNSUPPORTED_QUERY`. A term or group condition without a path tests the field itself, except that every term
58
58
  * of a quantified relation filter needs a non-empty element-relative path.
59
+ *
60
+ * `IIN` is `IN` ignoring case: an array of non-null strings without U+0000, possibly empty, on a `String` field or
61
+ * `String` list by its effective scalar; it matches where `IEQ` matches one of them, and `[]` matches nothing.
62
+ * `CONTAINS_ALL` takes a non-empty array of non-null scalars, cast like `IN` elements; it applies to a scalar list or a
63
+ * field reached through embedded lists, where it holds when every value is among the values of all items, and fails
64
+ * with `INVALID_FILTER_OPERATOR` on other leaves and with `INVALID_FILTER_PATH` through a collection or a reference
65
+ * inside an embedded list. Neither has a negated form.
59
66
  */
60
67
  export type QueryOperator =
61
68
  | 'EQ' | 'NE' | 'LT' | 'LTE' | 'GT' | 'GTE' | 'BTW' | 'IN' | 'NIN' | 'LIKE'
62
69
  | 'ILIKE' | 'STARTS_WITH' | 'ISTARTS_WITH' | 'ENDS_WITH' | 'IENDS_WITH' | 'IEQ'
63
- | 'IS_EMPTY' | 'IS_NOT_EMPTY';
70
+ | 'IS_EMPTY' | 'IS_NOT_EMPTY' | 'CONTAINS_ALL' | 'IIN';
64
71
 
65
72
  /** The quantifier applied to the elements visible in a collection or reference frame. */
66
73
  export type QueryQuantifier = 'ANY' | 'ALL' | 'NONE';
@@ -427,16 +434,24 @@ export interface ConcurrencyDeclaration {
427
434
  }
428
435
 
429
436
  /**
430
- * Process-wide limits for generated add, update and state-action mutations. Any other option,
431
- * such as a misspelled `maxNestedOperation`, throws `INVALID_MUTATION_LIMITS` (400).
437
+ * Process-wide limits for generated mutations. Each call replaces both values: an omitted or
438
+ * `undefined` option restores its default. Any other option, such as a misspelled
439
+ * `maxNestedOperation`, throws `INVALID_MUTATION_LIMITS` (400) and keeps the current limits.
432
440
  */
433
441
  export interface MutationLimitsOptions {
434
442
  /**
435
443
  * Maximum number of `added`, `updated` and `deleted` entries across every nested level of
436
444
  * non-embedded collection fields in one generated mutation. A non-negative safe integer;
437
- * `null` or omitted means unlimited (the default).
445
+ * `null` or omitted means unlimited (the default). The helpers `updateObject` and
446
+ * `transitionObject` apply it to their `input` as the generated mutations do.
438
447
  */
439
448
  maxNestedOperations?: number | null;
449
+ /**
450
+ * Maximum number of operations in one generated transaction mutation. A positive safe integer;
451
+ * omitted means 100 (the default). `null`, `0`, a negative number or a non-integer throws
452
+ * `INVALID_MUTATION_LIMITS` (400).
453
+ */
454
+ maxTransactionOperations?: number;
440
455
  }
441
456
 
442
457
  /**
@@ -449,6 +464,21 @@ export interface CreateSchemaOptions {
449
464
  * `[]` includes none, and names that are not registered are ignored.
450
465
  */
451
466
  includedCustomQueries?: string[] | null;
467
+ /**
468
+ * Adds one mutation field that runs a list of generated mutations in order in one database
469
+ * transaction. `null` or omission adds none. `name` defaults to `transaction` and must be a GraphQL
470
+ * name that does not start with `__`; it names the field and its types (for `transaction`:
471
+ * `TransactionOperation`, a `@oneOf` input keyed by operation, `TransactionResult` and, for deletes
472
+ * of types without optimistic concurrency, `TransactionDeleteInput`). The operations are the
473
+ * generated add, update, delete and state machine action mutations of `types` that this schema
474
+ * includes; registered mutations never are. `types` is a non-empty array of distinct types registered
475
+ * in this runtime. An invalid option throws `INVALID_SCHEMA_OPTIONS` (500) before models are prepared,
476
+ * or once the generated mutations are built when the field name or a derived type name is taken or
477
+ * no operation remains; a type bound to another runtime throws `TYPE_BOUND_TO_OTHER_RUNTIME` (409),
478
+ * and an adapter without `assertTransactionModels` throws `TRANSACTION_UNSUPPORTED` (500). A failed
479
+ * `createSchema()` keeps no transaction field or type.
480
+ */
481
+ transaction?: { name?: string; types: GraphQLObjectType[] } | null;
452
482
  }
453
483
 
454
484
  export interface QueryLimitsOptions {
@@ -510,10 +540,12 @@ export interface Runtime<Model = any, Session = any> {
510
540
  /** The same process-wide setting as the root `configureQueryLimits`. */
511
541
  configureQueryLimits(options?: QueryLimitsOptions): void;
512
542
  /**
513
- * Process-wide; calling it without options restores the unlimited default. Invalid options,
514
- * including unknown or misspelled option keys, throw `INVALID_MUTATION_LIMITS` (400) and keep
515
- * the current limit. A generated mutation over the limit fails with
516
- * `NESTED_OPERATIONS_EXCEEDED` (400) before its transaction starts.
543
+ * Process-wide; calling it without options restores both defaults (`maxNestedOperations`
544
+ * unlimited, `maxTransactionOperations` 100). Invalid options, including unknown or misspelled
545
+ * option keys, throw `INVALID_MUTATION_LIMITS` (400) and keep the current limits. A generated
546
+ * mutation over the nested limit fails with `NESTED_OPERATIONS_EXCEEDED` (400) before its
547
+ * transaction starts. `updateObject` and `transitionObject` apply `maxNestedOperations` to their
548
+ * `input` in the same way (`null` = no limit).
517
549
  */
518
550
  configureMutationLimits(options?: MutationLimitsOptions): void;
519
551
  /**
@@ -561,7 +593,8 @@ export interface Runtime<Model = any, Session = any> {
561
593
  *
562
594
  * `options.includedCustomQueries` selects custom queries by name (see {@link CreateSchemaOptions});
563
595
  * an included custom query named like an included generated query throws `QUERY_NAME_CONFLICT`
564
- * (500). A schema without mutation fields has no `Mutation` root.
596
+ * (500). `options.transaction` adds the generated transaction field (see {@link CreateSchemaOptions}).
597
+ * A schema without mutation fields has no `Mutation` root.
565
598
  */
566
599
  createSchema(includedQueryTypes?: GraphQLObjectType[] | null, includedMutationTypes?: GraphQLObjectType[] | null, includedCustomMutations?: string[] | null, options?: CreateSchemaOptions): GraphQLSchema;
567
600
  getModel(type: GraphQLObjectType | { name: string }): Model | null | undefined;
@@ -602,6 +635,64 @@ export interface Runtime<Model = any, Session = any> {
602
635
  registerView(name: string, options: ViewOptions): void;
603
636
  /** Owns the full workflow transaction unless an active caller session is supplied. */
604
637
  saveObject(typeName: string, args: Record<string, any>, session?: Session, context?: any): Promise<any>;
638
+ /**
639
+ * Runs the lifecycle of the generated update mutation from code: validators, controller hooks,
640
+ * optimistic concurrency and the nested `added`, `updated` and `deleted` entries, in one transaction.
641
+ * `typeName` names a type registered in this runtime that stores records and has its update input
642
+ * prepared by `createSchema()`, with an `id` field. Whether a schema exposes the mutation does not
643
+ * matter: the helper is not an authorization boundary. Any other `typeName` rejects with
644
+ * `INVALID_OPERATION_TARGET` (500), before the input is read.
645
+ *
646
+ * `input` has the shape of the type's generated update input, `version` included on a type with
647
+ * optimistic concurrency. It is checked structurally and copied before anything runs, and the
648
+ * caller's object is never written to. It is not coerced like a GraphQL input: enum fields take
649
+ * their internal values, scalars are not parsed, a reference is its record key as a string, such as
650
+ * `{ id: String(record._id) }`, and the `state` of a type with a state machine changes only through
651
+ * `transitionObject`. A scalar field takes a primitive, a `Date` or plain data (objects and arrays,
652
+ * copied deeply, at most 256 levels deep); class instances such as `ObjectId`, `Buffer` or `Map` are
653
+ * rejected with `INVALID_INPUT`. An own `undefined` counts as omitted, and a bare value is never
654
+ * wrapped into a list. A missing `input`, `id` or value for a non-null field rejects with
655
+ * `REQUIRED_VALUE` (400); any other violation, such as an unknown field, an object that is not plain
656
+ * or a list that is not dense, rejects with `INVALID_INPUT` (400). The message names the path, such
657
+ * as `input.lines.added[1].price`, and never a value. More nested entries than `maxNestedOperations`
658
+ * from `configureMutationLimits()` (`null` = no limit) reject with `NESTED_OPERATIONS_EXCEEDED` (400)
659
+ * before the transaction starts.
660
+ *
661
+ * Without `session`, the adapter owns the transaction and its retries. A supplied `session` must have
662
+ * an active transaction, and the helper itself never commits, retries, aborts or cleans up a supplied
663
+ * session; an adapter may still abort it on a write error (for example MongoDB with transactional
664
+ * reference integrity). Like `saveObject`, it skips the global middleware of the record itself; nested entries keep theirs and
665
+ * receive `context`, which may be `undefined`.
666
+ *
667
+ * Resolves to the updated record, as the generated mutation returns it. An `id` that has no record
668
+ * rejects with `NOT_VALID_ID` (404), and a stale `version` with `VERSION_CONFLICT` (409).
669
+ */
670
+ updateObject(typeName: string, input: Record<string, any>, session?: Session, context?: any): Promise<any>;
671
+ /**
672
+ * Runs the lifecycle of the generated delete mutation from code. `target` is `{ id }`, the record key
673
+ * as a string, plus `version`, the version read, on a type with optimistic concurrency; on any other
674
+ * type `version` is an unknown field. The type is checked as in {@link Runtime.updateObject}, except
675
+ * that it needs no update input of its own; the type must still be a stored record type, which only
676
+ * `createSchema()` establishes. The target is checked before anything runs: a missing
677
+ * target, `id` or required `version` rejects with `REQUIRED_VALUE` (400), and any other violation
678
+ * with `INVALID_INPUT` (400). A stale `version` rejects with `VERSION_CONFLICT` (409). Sessions,
679
+ * `context` and middleware work as in `updateObject`.
680
+ *
681
+ * Resolves to the deleted record, or to `null` when no record has that ID.
682
+ */
683
+ deleteObject(typeName: string, target: { id: string; version?: number }, session?: Session, context?: any): Promise<any>;
684
+ /**
685
+ * Runs the state machine action `actionName` of `typeName` from code, with the lifecycle of the
686
+ * generated action mutation: the check of the current state, the action's callback, the update and the
687
+ * controller hooks, in one transaction. An `actionName` that is not an own action of the type's state
688
+ * machine, or a type without one, rejects with `UNKNOWN_ACTION` (500). The type, the checks of `input`,
689
+ * the session, `context`, the middleware and the errors are as in {@link Runtime.updateObject}. `input` has
690
+ * the shape of the update input and does not carry `state`. An action that does not start from the
691
+ * record's state rejects with `BAD_REQUEST` (400).
692
+ *
693
+ * Resolves to the updated record, with `state` set to the action's target state.
694
+ */
695
+ transitionObject(typeName: string, actionName: string, input: Record<string, any>, session?: Session, context?: any): Promise<any>;
605
696
  preventCreatingCollection(prevent: boolean): void;
606
697
  }
607
698
 
@@ -616,10 +707,17 @@ export interface DatabaseAdapter<Model = any, Session = any> {
616
707
  * by-ID read through a `get_by_id` scope, whose arguments use a `QLOperator` value the adapter
617
708
  * does not support fails with `UNSUPPORTED_QUERY` (400), before middleware runs for the client's
618
709
  * arguments and before `find`, `count`, `aggregate`, `findChildren` or `findChildrenBatch` for
619
- * conditions that middleware, scopes and joined scopes added. Operators outside `QLOperator` still
620
- * reach the adapter, which reports them as before. Declaring `IS_EMPTY` and `IS_NOT_EMPTY` means
621
- * compiling their semantics on the record's own fields; emptiness through references and
622
- * collections also needs `referenceEmptiness: true`, and fails with `UNSUPPORTED_QUERY` without it.
710
+ * conditions that middleware, scopes and joined scopes added. Operators outside `QLOperator`, which
711
+ * only trusted code can write, fail with `INVALID_FILTER_OPERATOR` (400) before the adapter is
712
+ * called, except in the forms the compilers ignore: an operator beside the `terms` of an object
713
+ * filter, and the `terms` of a scalar, scalar-list, interface or union field's filter. Those reach
714
+ * the adapter, which ignores them. Declaring `IS_EMPTY` and `IS_NOT_EMPTY` means compiling their
715
+ * semantics on the record's own fields; emptiness through references and collections also needs
716
+ * `referenceEmptiness: true`, and fails with `UNSUPPORTED_QUERY` without it.
717
+ * Declaring `CONTAINS_ALL` means compiling set containment over the leaf's values, including the
718
+ * values of every item of embedded lists, two-valued under negation; declaring `IIN` means
719
+ * compiling case-insensitive membership under the case-folding contract of the text operators.
720
+ * Names that an older version ignored are honored once this version knows them.
623
721
  */
624
722
  queryFeatures?: QueryFeatures | null | undefined;
625
723
  /**
@@ -631,6 +729,16 @@ export interface DatabaseAdapter<Model = any, Session = any> {
631
729
  * contract of `update` and `delete` and to the requirements on `find` below.
632
730
  */
633
731
  writeFeatures?: { optimisticConcurrency?: boolean } | null | undefined;
732
+ /**
733
+ * Optional, and required to enable the generated transaction field (see
734
+ * `CreateSchemaOptions.transaction`). Receives the models one transaction of that field may
735
+ * write: those of the types that own its operations and of the referenced collection items their
736
+ * inputs declare, at any depth, without duplicates. Core calls it before each transaction of the
737
+ * field opens, never in `createSchema()`, so a schema can be created before the connection opens.
738
+ * It must be synchronous and never write, and it throws when the models cannot share one session; a
739
+ * returned promise fails the transaction with `TRANSACTION_UNSUPPORTED` (500).
740
+ */
741
+ assertTransactionModels?(models: Model[]): void;
634
742
  bind?(runtime: Pick<Runtime<Model, Session>, 'getModel' | 'getType' | 'getRegistrations'>): void;
635
743
  validateRegistration?(registration: RuntimeRegistration<Model, Session>): void;
636
744
  /**
@@ -791,6 +899,8 @@ export type QueryJoins = Record<string, QueryPredicate | null>;
791
899
 
792
900
  /**
793
901
  * `operator` is a {@link QueryOperator}; the value of a text operator is a validated string. For
902
+ * `CONTAINS_ALL` the value is a non-empty array of non-null scalars, validated and not cast; the
903
+ * compiler casts each element as for `IN`. For `IIN` it is an array of strings, possibly empty. For
794
904
  * `IS_EMPTY` and `IS_NOT_EMPTY` the value is null, and `path` may end at an embedded object, an
795
905
  * embedded list or a scalar list as well as at a scalar; `IS_NOT_EMPTY` is the exact complement,
796
906
  * which the compiler builds. With `referenceEmptiness: true`, their `path` may also cross single
@@ -849,6 +959,22 @@ export interface QueryPlan {
849
959
  * object fails with `INVALID_FILTER_PATH`, as on MongoDB; a bare object field keeps
850
960
  * `MISSING_FILTER_PATH`.
851
961
  *
962
+ * `CONTAINS_ALL` and `IIN` are checked in this order: the path (`AMBIGUOUS_FILTER_FIELD`,
963
+ * `INVALID_FILTER_PATH`, `INVALID_FILTER_FIELD`) and the object-end rule above, then an unknown name
964
+ * (`INVALID_FILTER_OPERATOR`) and `operators` (`UNSUPPORTED_QUERY`), then, for `CONTAINS_ALL` only,
965
+ * `INVALID_FILTER_PATH` through a collection, aggregate join prefixes included
966
+ * (`The shorthand excludes them; use an explicit group`), `INVALID_FILTER_PATH` through a reference
967
+ * inside an embedded list, and `INVALID_FILTER_OPERATOR` on a leaf that is neither a scalar list nor
968
+ * a scalar reached through an embedded list, and the values last, with `INVALID_FILTER_VALUE`.
969
+ * `CONTAINS_ALL` takes a non-empty array of non-null scalars, none of them an array or a plain
970
+ * object; `IIN` takes an array of strings without U+0000, possibly empty, on a `String` field or list
971
+ * by its effective scalar. A hole of a sparse array fails for both, while `IN`, `NIN` and `BTW` keep
972
+ * skipping holes.
973
+ * Both plan `{ kind: 'predicate', path, operator, value }` with the array as validated, neither cast
974
+ * nor deduplicated. `IIN` through a collection plans as `IN` does: an implicit `ANY` frame with
975
+ * `quantifiers`, a row filter on an aggregate's join prefix, a legacy join otherwise. `CONTAINS_ALL`
976
+ * is never cut into a frame.
977
+ *
852
978
  * `referenceEmptiness` defaults to true when `operators` is omitted and to false when it is given.
853
979
  * When it is false, an emptiness condition that crosses or ends at a reference or a collection, or
854
980
  * reads a reference or collection member, fails with `UNSUPPORTED_QUERY` where the order above has
@@ -857,8 +983,17 @@ export interface QueryPlan {
857
983
  * becomes the predicate's `scopes`. `quantifiers` has the same default rule and requires reference
858
984
  * emptiness; enabled collection filters emit quantified nodes, with per-group/prefix correlation,
859
985
  * element-relative scopes/joins and aggregate anchors beyond legacy J joins. `__scope`, `__implicit`
860
- * and `__anchor` are reserved internal input metadata. A marker not filled for every path fails
861
- * with `DETACHED_CLIENT_FILTER` (500).
986
+ * and `__anchor` are reserved internal input metadata. A group that trusted code writes with
987
+ * `__implicit: true` must, after its `__anchor`, end at a collection, or at a reference without
988
+ * crossing a list, and its `__anchor`, when present, must have exactly the shape the collection
989
+ * shorthand writes: in an aggregate, on a group at the root, a non-empty array of field names without
990
+ * dots that crosses at least one collection, each a join prefix, followed by a `path` of one
991
+ * collection. Otherwise the group fails with `INVALID_FILTER_PATH` (400), whatever operators it holds
992
+ * and before its conditions are checked, with the message
993
+ * `An implicit quantified path must end at a collection, or at a reference without crossing a list` or
994
+ * `An implicit quantified anchor must name the aggregate's join prefixes`; the anchor's form is
995
+ * checked before the path rule, the rest after it.
996
+ * A marker not filled for every path fails with `DETACHED_CLIENT_FILTER` (500).
862
997
  */
863
998
  export function createQueryPlan(models: ModelDescription, entityName: string, input?: Record<string, any>, options?: { mode?: QueryPlan['mode']; operators?: Iterable<string>; referenceEmptiness?: boolean; quantifiers?: boolean }): QueryPlan;
864
999
  export function resolveModelPath(models: ModelDescription, entityName: string, path: string | string[]): FieldDescription[];
@@ -874,7 +1009,7 @@ export function resolveModelPath(models: ModelDescription, entityName: string, p
874
1009
  export function configureQueryLimits(options?: QueryLimitsOptions): void;
875
1010
  /**
876
1011
  * Same process-wide setting as `Runtime.configureMutationLimits`. Unknown or misspelled options
877
- * throw `INVALID_MUTATION_LIMITS` (400) and keep the current limit.
1012
+ * throw `INVALID_MUTATION_LIMITS` (400) and keep the current limits.
878
1013
  */
879
1014
  export function configureMutationLimits(options?: MutationLimitsOptions): void;
880
1015
  export function paginationStages(pagination: { page: number; size: number } | null | undefined, withDefault: boolean): Array<{ $skip: number } | { $limit: number }>;