@simtlix/simfinity-core 3.10.0 → 3.12.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
@@ -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,
@@ -27,17 +29,41 @@ export const QLValue: GraphQLScalarType<unknown, unknown>;
27
29
  * missing, null or `""` String (`" "` is not empty), a missing or null value of any other scalar or
28
30
  * enum, a missing, null or `[]` list (`[null]` and `[""]` are not empty), and a missing or null
29
31
  * embedded object or one whose stored members are all empty by these rules; the record identity is
30
- * never empty. They apply to the queried record's own fields: a path that continues inside an
31
- * embedded list or a collection fails with `INVALID_FILTER_PATH`, and a path that crosses or ends
32
- * at a reference, a path that ends at a collection, and an embedded object with a reference or
33
- * collection member fail with `UNSUPPORTED_QUERY` until reference emptiness arrives. A term or
34
- * group condition without a path tests the field itself.
32
+ * never empty. The target's `id` right after a reference is empty exactly when the reference is,
33
+ * including when the target is missing or hidden by its `find` scope. A path that continues inside an
34
+ * embedded list or a collection fails with `INVALID_FILTER_PATH`. With an adapter that declares
35
+ * `queryFeatures.referenceEmptiness: true`, a path may also cross single references and end at a
36
+ * reference or a collection, and an embedded object may have reference and collection members: a
37
+ * reference is empty when no record it points to exists that the target's `find` scope admits for
38
+ * the caller, a collection when no such child exists, and a hidden record on the way reads as
39
+ * missing; constructs that middleware and scopes add read stored data. With any other adapter they
40
+ * fail with `UNSUPPORTED_QUERY`. A term or group condition without a path tests the field itself.
35
41
  */
36
42
  export type QueryOperator =
37
43
  | 'EQ' | 'NE' | 'LT' | 'LTE' | 'GT' | 'GTE' | 'BTW' | 'IN' | 'NIN' | 'LIKE'
38
44
  | 'ILIKE' | 'STARTS_WITH' | 'ISTARTS_WITH' | 'ENDS_WITH' | 'IENDS_WITH' | 'IEQ'
39
45
  | 'IS_EMPTY' | 'IS_NOT_EMPTY';
40
46
 
47
+ /**
48
+ * A condition of a {@link FilterGroup}, as the `QLFilterCondition` input takes it: the field it
49
+ * tests, its {@link QueryOperator} (`EQ` when omitted) and its value. On an object field, `path`
50
+ * names the member it tests; `IS_EMPTY` and `IS_NOT_EMPTY` take no value and may omit `path` to test
51
+ * the field itself.
52
+ */
53
+ export interface FilterCondition {
54
+ field: string;
55
+ operator?: QueryOperator | (string & {});
56
+ value?: any;
57
+ path?: string;
58
+ }
59
+
60
+ /** A `QLFilterGroup` value: flat conditions plus nested `AND` and `OR` groups. */
61
+ export interface FilterGroup {
62
+ AND?: FilterGroup[];
63
+ OR?: FilterGroup[];
64
+ conditions?: FilterCondition[];
65
+ }
66
+
41
67
  /**
42
68
  * The query language features a {@link DatabaseAdapter} compiles. Declare it so that the runtime
43
69
  * sends the adapter only conditions it compiles; later releases add members for later features.
@@ -48,6 +74,15 @@ export interface QueryFeatures {
48
74
  * version of Simfinity does not know are ignored.
49
75
  */
50
76
  operators: readonly (QueryOperator | (string & {}))[];
77
+ /**
78
+ * Whether the adapter compiles `IS_EMPTY` and `IS_NOT_EMPTY` through single references and at
79
+ * references and collections, including reference and collection members of embedded objects.
80
+ * Undefined or null means false; any other value that is not a boolean fails `createRuntime` with
81
+ * `INVALID_QUERY_FEATURES` (500). An adapter that declares it receives client constructs that carry
82
+ * a filled client scope marker, `__scope: { id, scopes }`, on their term or group condition, and
83
+ * must apply each target scope in `scopes`; constructs without a marker read stored data.
84
+ */
85
+ referenceEmptiness?: boolean | null | undefined;
51
86
  }
52
87
 
53
88
  export interface ModelRegistration {
@@ -303,8 +338,56 @@ export interface CreateSchemaOptions {
303
338
  includedCustomQueries?: string[] | null;
304
339
  }
305
340
 
341
+ export interface QueryLimitsOptions {
342
+ /** Positive safe integer; defaults to 1000. Unpaged lists use min(100, maxPageSize). */
343
+ maxPageSize?: number;
344
+ /**
345
+ * Positive safe integer; defaults to 10. The most distinct scoped joins and frames that the
346
+ * client's reference and collection emptiness may need in one list, aggregate or collection-field
347
+ * call; more fail with `FILTER_DEPTH_EXCEEDED` (400).
348
+ */
349
+ maxReferenceFrames?: number;
350
+ }
351
+
352
+ /**
353
+ * The type of a view parameter: a value the client sends as a variable or a literal, so a scalar,
354
+ * an enum or a list of them, each possibly non-null, never an input object.
355
+ */
356
+ export type ViewParamType =
357
+ | GraphQLScalarType
358
+ | GraphQLEnumType
359
+ | GraphQLList<ViewParamType>
360
+ | GraphQLNonNull<GraphQLScalarType | GraphQLEnumType | GraphQLList<ViewParamType>>;
361
+
362
+ /** A view parameter: an argument of the view's root field that only its `where` reads. */
363
+ export interface ViewParam {
364
+ type: ViewParamType;
365
+ description?: string;
366
+ defaultValue?: unknown;
367
+ }
368
+
369
+ /** The declaration of a named view (see {@link Runtime.registerView}). */
370
+ export interface ViewOptions {
371
+ /** The connected endpoint type whose list the view returns. */
372
+ type: GraphQLObjectType;
373
+ /** Non-empty; the root field's description. */
374
+ description: string;
375
+ /**
376
+ * Arguments of the root field besides the generated list arguments, named unlike them and unlike
377
+ * the fields of `type`. They never reach the adapter: only `where` receives them.
378
+ */
379
+ params?: Record<string, ViewParam>;
380
+ /**
381
+ * Called on every read with the param values and the request context. The groups it returns are
382
+ * appended to the client's filters after middleware, so neither the client nor middleware can drop
383
+ * them; their paths may name fields that are not queryable.
384
+ */
385
+ where: (input: { params: Record<string, any>; context: any }) => FilterGroup[] | Promise<FilterGroup[]>;
386
+ }
387
+
306
388
  export interface Runtime<Model = any, Session = any> {
307
- configureQueryLimits(options?: { maxPageSize?: number }): void;
389
+ /** The same process-wide setting as the root `configureQueryLimits`. */
390
+ configureQueryLimits(options?: QueryLimitsOptions): void;
308
391
  /**
309
392
  * Process-wide; calling it without options restores the unlimited default. Invalid options,
310
393
  * including unknown or misspelled option keys, throw `INVALID_MUTATION_LIMITS` (400) and keep
@@ -385,6 +468,17 @@ export interface Runtime<Model = any, Session = any> {
385
468
  * (500) for a name already registered.
386
469
  */
387
470
  registerCustomQuery(name: string, description: string, input: GraphQLInputObjectType | null | undefined, output: GraphQLOutputType, callback: (input: any, context: any) => any): void;
471
+ /**
472
+ * Register a named view: a root query field that returns the list of `options.type` with the
473
+ * generated list arguments plus `options.params`, and appends the groups of `options.where` after
474
+ * middleware. A schema includes the view exactly when it includes the type. Throws `INVALID_VIEW`
475
+ * (500) for an invalid declaration and `QUERY_NAME_CONFLICT` (500) for a name already registered as
476
+ * a view, keeping the first registration. `createSchema` throws `INVALID_VIEW` when the type is not
477
+ * connected in this runtime or a param is named like one of its fields or a generated list
478
+ * argument, and `QUERY_NAME_CONFLICT` when an included view is named like an included generated or
479
+ * custom query.
480
+ */
481
+ registerView(name: string, options: ViewOptions): void;
388
482
  /** Owns the full workflow transaction unless an active caller session is supplied. */
389
483
  saveObject(typeName: string, args: Record<string, any>, session?: Session, context?: any): Promise<any>;
390
484
  preventCreatingCollection(prevent: boolean): void;
@@ -395,15 +489,16 @@ export interface DatabaseAdapter<Model = any, Session = any> {
395
489
  /**
396
490
  * Optional. The filter operators this adapter compiles. `createRuntime` reads it once and throws
397
491
  * `INVALID_QUERY_FEATURES` (500) unless it is undefined, null or an object whose `operators` is an
398
- * array of strings. Without it the adapter supports the operators of 3.7.0 (`EQ`, `NE`, `LT`,
399
- * `LTE`, `GT`, `GTE`, `BTW`, `IN`, `NIN` and `LIKE`). A list, aggregate or collection read, or a
492
+ * array of strings and whose `referenceEmptiness`, if present, is a boolean or null. Without it the
493
+ * adapter supports the operators of 3.7.0 (`EQ`, `NE`, `LT`, `LTE`, `GT`, `GTE`, `BTW`, `IN`, `NIN`
494
+ * and `LIKE`) and no reference emptiness. A list, aggregate or collection read, or a
400
495
  * by-ID read through a `get_by_id` scope, whose arguments use a `QLOperator` value the adapter
401
496
  * does not support fails with `UNSUPPORTED_QUERY` (400), before middleware runs for the client's
402
497
  * arguments and before `find`, `count`, `aggregate`, `findChildren` or `findChildrenBatch` for
403
498
  * conditions that middleware, scopes and joined scopes added. Operators outside `QLOperator` still
404
499
  * reach the adapter, which reports them as before. Declaring `IS_EMPTY` and `IS_NOT_EMPTY` means
405
- * compiling their semantics on the record's own fields; it does not lift the `UNSUPPORTED_QUERY`
406
- * that emptiness through references and collections gets in this release.
500
+ * compiling their semantics on the record's own fields; emptiness through references and
501
+ * collections also needs `referenceEmptiness: true`, and fails with `UNSUPPORTED_QUERY` without it.
407
502
  */
408
503
  queryFeatures?: QueryFeatures | null | undefined;
409
504
  bind?(runtime: Pick<Runtime<Model, Session>, 'getModel' | 'getType' | 'getRegistrations'>): void;
@@ -527,9 +622,21 @@ export function createRuntime<Model = any, Session = any>(adapter: DatabaseAdapt
527
622
  * `operator` is a {@link QueryOperator}; the value of a text operator is a validated string. For
528
623
  * `IS_EMPTY` and `IS_NOT_EMPTY` the value is null, and `path` may end at an embedded object, an
529
624
  * embedded list or a scalar list as well as at a scalar; `IS_NOT_EMPTY` is the exact complement,
530
- * which the compiler builds.
625
+ * which the compiler builds. With `referenceEmptiness: true`, their `path` may also cross single
626
+ * references and end at a reference or a collection.
627
+ *
628
+ * `scopes` exists only on such an emptiness predicate from a client, and only for a compiler that
629
+ * declares `referenceEmptiness: true`. It has one key per dotted path from the queried record: each
630
+ * single reference the path crosses (a scoped join, which admits the target only where the scope
631
+ * admits it), the end when it is a reference or a collection, and each reference and collection
632
+ * member of an embedded object (frames, which keep only the records the scope admits). Each value is
633
+ * the target's `find` scope as a predicate in the target's own frame, or null when the scope added
634
+ * nothing; after a scoped crossing, a null hop still correlates through that scoped join. Without
635
+ * `scopes` the predicate reads stored data, through plain joins.
531
636
  */
532
- export type QueryPredicate = { kind: 'predicate'; path: string[]; operator: string; value: any } | { kind: 'and' | 'or'; terms: QueryPredicate[] };
637
+ export type QueryPredicate =
638
+ | { kind: 'predicate'; path: string[]; operator: string; value: any; scopes?: Record<string, QueryPredicate | null> }
639
+ | { kind: 'and' | 'or'; terms: QueryPredicate[] };
533
640
  export interface QueryPlan {
534
641
  entity: string;
535
642
  where: QueryPredicate | null;
@@ -556,11 +663,26 @@ export interface QueryPlan {
556
663
  * the field itself. For other operators, a filter path of more than one segment that ends at an
557
664
  * object fails with `INVALID_FILTER_PATH`, as on MongoDB; a bare object field keeps
558
665
  * `MISSING_FILTER_PATH`.
666
+ *
667
+ * `referenceEmptiness` defaults to true when `operators` is omitted and to false when it is given.
668
+ * When it is false, an emptiness condition that crosses or ends at a reference or a collection, or
669
+ * reads a reference or collection member, fails with `UNSUPPORTED_QUERY` where the order above has
670
+ * `UNSUPPORTED_QUERY` for references; when it is true, such a condition plans with stored semantics,
671
+ * unless its term or group condition carries a client scope marker that the runtime filled, which
672
+ * becomes the predicate's `scopes`. A marker that is not filled for every path of the condition fails
673
+ * with `DETACHED_CLIENT_FILTER` (500).
559
674
  */
560
- export function createQueryPlan(models: ModelDescription, entityName: string, input?: Record<string, any>, options?: { mode?: QueryPlan['mode']; operators?: Iterable<string> }): QueryPlan;
675
+ export function createQueryPlan(models: ModelDescription, entityName: string, input?: Record<string, any>, options?: { mode?: QueryPlan['mode']; operators?: Iterable<string>; referenceEmptiness?: boolean }): QueryPlan;
561
676
  export function resolveModelPath(models: ModelDescription, entityName: string, path: string | string[]): FieldDescription[];
562
677
 
563
- export function configureQueryLimits(options?: { maxPageSize?: number }): void;
678
+ /**
679
+ * Process-wide. `maxPageSize` (default 1000) caps page sizes. `maxReferenceFrames` (default 10) caps,
680
+ * per list, aggregate or collection-field call, the distinct scoped joins and frames that a client's
681
+ * reference and collection emptiness needs; more fail with `FILTER_DEPTH_EXCEEDED` (400). Each must be
682
+ * a positive safe integer, or the call throws `INVALID_QUERY_LIMITS` (400) and keeps both limits; an
683
+ * omitted option takes its default again.
684
+ */
685
+ export function configureQueryLimits(options?: QueryLimitsOptions): void;
564
686
  /**
565
687
  * Same process-wide setting as `Runtime.configureMutationLimits`. Unknown or misspelled options
566
688
  * throw `INVALID_MUTATION_LIMITS` (400) and keep the current limit.