@aventara/core 1.0.0-rc.1 → 1.0.0-rc.3

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.
Files changed (97) hide show
  1. package/CHANGELOG.md +341 -1
  2. package/ERRORS.md +142 -0
  3. package/README.md +24 -1
  4. package/dist/adapters/adapter-discovery-diagnostic.guidance.d.ts +6 -0
  5. package/dist/adapters/adapter-discovery-diagnostic.guidance.js +7 -0
  6. package/dist/adapters/adapter-field.d.ts +14 -0
  7. package/dist/adapters/adapter-model.d.ts +9 -0
  8. package/dist/adapters/adapter.interface.d.ts +10 -0
  9. package/dist/compiler/contract-compiler-diagnostic.guidance.d.ts +6 -0
  10. package/dist/compiler/contract-compiler-diagnostic.guidance.js +97 -0
  11. package/dist/compiler/contract.compiler.js +94 -6
  12. package/dist/config/computed-fields.d.ts +62 -0
  13. package/dist/config/computed-fields.js +80 -0
  14. package/dist/config/config-diagnostic.guidance.d.ts +6 -0
  15. package/dist/config/config-diagnostic.guidance.js +32 -0
  16. package/dist/config/define-resource.d.ts +1 -1
  17. package/dist/config/exact-framework-config.d.ts +50 -20
  18. package/dist/config/field-compute.resolver.d.ts +2 -0
  19. package/dist/config/field-compute.resolver.js +8 -0
  20. package/dist/config/framework-config.d.ts +7 -6
  21. package/dist/config/framework-resources-config.d.ts +18 -12
  22. package/dist/config/pipeline-config.d.ts +4 -4
  23. package/dist/contracts/capability.d.ts +10 -1
  24. package/dist/contracts/capability.js +2 -0
  25. package/dist/contracts/client-contract-structure.validator.d.ts +65 -35
  26. package/dist/contracts/client-contract-structure.validator.js +4 -2
  27. package/dist/contracts/contract-canonicalizer.d.ts +7 -0
  28. package/dist/contracts/contract-canonicalizer.js +18 -4
  29. package/dist/contracts/field.d.ts +30 -1
  30. package/dist/framework/contract-view.d.ts +4 -4
  31. package/dist/framework/framework-construction.error.d.ts +12 -3
  32. package/dist/framework/framework-construction.error.js +5 -1
  33. package/dist/framework/framework.factory.d.ts +1 -1
  34. package/dist/framework/framework.factory.js +9 -2
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.js +1 -0
  37. package/dist/internal.d.ts +2 -0
  38. package/dist/internal.js +1 -0
  39. package/dist/operations/database-refusal.d.ts +51 -0
  40. package/dist/operations/database-refusal.js +45 -0
  41. package/dist/operations/operation-arguments.d.ts +102 -31
  42. package/dist/operations/operation-validator-core.js +145 -36
  43. package/dist/operations/validation-code.guidance.d.ts +6 -0
  44. package/dist/operations/validation-code.guidance.js +90 -0
  45. package/dist/operations/write-scope.resolver.d.ts +4 -0
  46. package/dist/operations/write-scope.resolver.js +2 -0
  47. package/dist/protocol/protocol.handler.js +55 -16
  48. package/dist/runtime/caller-data.copier.d.ts +17 -0
  49. package/dist/runtime/caller-data.copier.js +18 -1
  50. package/dist/runtime/caller-data.door.d.ts +11 -5
  51. package/dist/runtime/computed-read-runtime.js +15 -2
  52. package/dist/runtime/computed-value.error.d.ts +3 -2
  53. package/dist/runtime/computed-value.error.js +3 -2
  54. package/dist/runtime/create-requirement.d.ts +41 -5
  55. package/dist/runtime/create-requirement.js +137 -20
  56. package/dist/runtime/diagnostics-reporter.d.ts +2 -1
  57. package/dist/runtime/error-boundary.js +4 -0
  58. package/dist/runtime/error-guidance.d.ts +12 -0
  59. package/dist/runtime/error-guidance.js +1 -0
  60. package/dist/runtime/error.catalog.d.ts +49 -0
  61. package/dist/runtime/error.catalog.js +49 -0
  62. package/dist/runtime/field-stage.runtime.d.ts +2 -0
  63. package/dist/runtime/field-stage.runtime.js +8 -4
  64. package/dist/runtime/field-validator.runtime.d.ts +18 -1
  65. package/dist/runtime/field-validator.runtime.js +47 -8
  66. package/dist/runtime/json-value.d.ts +10 -5
  67. package/dist/runtime/level-lifecycle.runtime.d.ts +2 -2
  68. package/dist/runtime/level-lifecycle.runtime.js +2 -5
  69. package/dist/runtime/mutation-planner.js +3 -14
  70. package/dist/runtime/operation-code.guidance.d.ts +6 -0
  71. package/dist/runtime/operation-code.guidance.js +167 -0
  72. package/dist/runtime/operation-runtime.d.ts +20 -6
  73. package/dist/runtime/operation-runtime.js +82 -36
  74. package/dist/runtime/owned-execution.registry.d.ts +12 -2
  75. package/dist/runtime/pipe-output.validator.d.ts +10 -0
  76. package/dist/runtime/pipe-output.validator.js +4 -2
  77. package/dist/runtime/pipeline.d.ts +35 -8
  78. package/dist/runtime/readonly-value.d.ts +14 -3
  79. package/dist/runtime/readonly-value.js +10 -1
  80. package/dist/runtime/scalar-runtime.d.ts +5 -1
  81. package/dist/runtime/scalar-runtime.js +86 -16
  82. package/dist/runtime/unsent-value.normalizer.d.ts +15 -0
  83. package/dist/runtime/unsent-value.normalizer.js +128 -0
  84. package/dist/transactions/operation-fingerprint.js +2 -2
  85. package/dist/transactions/sql-statement.d.ts +8 -0
  86. package/dist/transactions/sql-statement.validator.js +36 -3
  87. package/dist/transactions/transaction-plan-assembler.d.ts +1 -1
  88. package/dist/transactions/transaction-plan-assembler.js +33 -10
  89. package/dist/transactions/transaction-plan-validator.d.ts +16 -0
  90. package/dist/transactions/transaction-plan-validator.js +27 -1
  91. package/dist/transactions/transaction-reference-validator.d.ts +42 -0
  92. package/dist/transactions/transaction-reference-validator.js +82 -56
  93. package/dist/transactions/transaction-reference.js +5 -0
  94. package/dist/transactions/transaction-runtime.js +47 -22
  95. package/dist/transactions/transaction-stage.runtime.d.ts +2 -1
  96. package/dist/transactions/transaction-stage.runtime.js +7 -4
  97. package/package.json +4 -3
package/dist/internal.js CHANGED
@@ -11,6 +11,7 @@ export { LIFECYCLE_TRAITS } from "./contracts/lifecycle-trait.js";
11
11
  export { isOperationVariantAvailable, projectionScopeOf, writeScopeOf, } from "./contracts/resource-operations.js";
12
12
  export { BUILT_IN_SCALARS } from "./contracts/scalar.js";
13
13
  export { TRANSACTION_CAPABILITIES } from "./contracts/transaction-capability.js";
14
+ export { DATABASE_REFUSALS } from "./operations/database-refusal.js";
14
15
  export { VALIDATION_CODES } from "./operations/validation-code.js";
15
16
  export { mcpFailureReporterOf } from "./runtime/diagnostics-reporter.js";
16
17
  export { FRAMEWORK_ERROR_CLASS_OF } from "./runtime/framework-error-class.table.js";
@@ -0,0 +1,51 @@
1
+ import type { AvValidationCode } from "./validation-code.js";
2
+ /**
3
+ * Why the database refused a caller's value, each with the validation code and the
4
+ * issue message an adapter answers it with.
5
+ */
6
+ export declare const DATABASE_REFUSALS: {
7
+ readonly tooLong: {
8
+ readonly code: "V1004";
9
+ readonly message: "The database refused this value: it is longer than the database can hold for its column.";
10
+ };
11
+ readonly outOfRange: {
12
+ readonly code: "V1004";
13
+ readonly message: "The database refused this value: it is outside the range its column can hold.";
14
+ };
15
+ readonly datetimeOutOfRange: {
16
+ readonly code: "V1004";
17
+ readonly message: "The database refused this value: the date and time is outside the range the database can hold.";
18
+ };
19
+ readonly divisionByZero: {
20
+ readonly code: "V1004";
21
+ readonly message: "The database refused this value: it divides by zero.";
22
+ };
23
+ readonly computedNull: {
24
+ readonly code: "V1004";
25
+ readonly message: "The database refused this value: the write computes no value for a column that requires one, as a division by zero does.";
26
+ };
27
+ readonly invalidInput: {
28
+ readonly code: "V1001";
29
+ readonly message: "The database refused this value: it is not valid input for its column's type.";
30
+ };
31
+ readonly nulInText: {
32
+ readonly code: "V1001";
33
+ readonly message: "The database refused this value: its text cannot contain the NUL character (U+0000).";
34
+ };
35
+ readonly nulInJson: {
36
+ readonly code: "V1001";
37
+ readonly message: "The database refused this value: its JSON cannot contain the NUL character (U+0000).";
38
+ };
39
+ readonly trailingEscape: {
40
+ readonly code: "V1001";
41
+ readonly message: "The database refused this value: a text match cannot end with the escape character \\.";
42
+ };
43
+ readonly backReference: {
44
+ readonly code: "V1012";
45
+ readonly message: "The data source refused this nested write: a record written through a relation cannot write that relation or its reference fields back.";
46
+ };
47
+ };
48
+ /** A reason the database refused a caller's value: a key of {@link DATABASE_REFUSALS}. */
49
+ export type AvDatabaseRefusal = keyof typeof DATABASE_REFUSALS;
50
+ /** Whether `message` is the message of a database refusal answered under `code`. */
51
+ export declare function isDatabaseRefusalMessage(code: AvValidationCode, message: string): boolean;
@@ -0,0 +1,45 @@
1
+ export const DATABASE_REFUSALS = {
2
+ tooLong: {
3
+ code: "V1004",
4
+ message: "The database refused this value: it is longer than the database can hold for its column.",
5
+ },
6
+ outOfRange: {
7
+ code: "V1004",
8
+ message: "The database refused this value: it is outside the range its column can hold.",
9
+ },
10
+ datetimeOutOfRange: {
11
+ code: "V1004",
12
+ message: "The database refused this value: the date and time is outside the range the database can hold.",
13
+ },
14
+ divisionByZero: {
15
+ code: "V1004",
16
+ message: "The database refused this value: it divides by zero.",
17
+ },
18
+ computedNull: {
19
+ code: "V1004",
20
+ message: "The database refused this value: the write computes no value for a column that requires one, as a division by zero does.",
21
+ },
22
+ invalidInput: {
23
+ code: "V1001",
24
+ message: "The database refused this value: it is not valid input for its column's type.",
25
+ },
26
+ nulInText: {
27
+ code: "V1001",
28
+ message: "The database refused this value: its text cannot contain the NUL character (U+0000).",
29
+ },
30
+ nulInJson: {
31
+ code: "V1001",
32
+ message: "The database refused this value: its JSON cannot contain the NUL character (U+0000).",
33
+ },
34
+ trailingEscape: {
35
+ code: "V1001",
36
+ message: "The database refused this value: a text match cannot end with the escape character \\.",
37
+ },
38
+ backReference: {
39
+ code: "V1012",
40
+ message: "The data source refused this nested write: a record written through a relation cannot write that relation or its reference fields back.",
41
+ },
42
+ };
43
+ export function isDatabaseRefusalMessage(code, message) {
44
+ return Object.values(DATABASE_REFUSALS).some((refusal) => refusal.code === code && refusal.message === message);
45
+ }
@@ -1,4 +1,4 @@
1
- import type { AvProjectionScope, AvRelationMutationDirective, AvScalarFilterOperator, AvScalarMutationDirective, AvWriteScope } from "../contracts/capability.js";
1
+ import type { AvProjectionScope, AvRelationMutationDirective, AvScalarFilterOperator, AvScalarMutationDirective, AvWriteScope, ScalarFilterMode } from "../contracts/capability.js";
2
2
  import type { AvContract } from "../contracts/contract.js";
3
3
  import type { AvProjectionScopes, AvScalarField } from "../contracts/field.js";
4
4
  import type { AvScalarValueType, FieldAtom, ScalarForms } from "../contracts/scalar-value-type.js";
@@ -82,7 +82,7 @@ type SlotReference<C extends AvContract, K extends AvResourceKey<C>, F extends A
82
82
  * read a JSON object as the operators, so their value forms leave it out.
83
83
  */
84
84
  type JsonObject = Extract<AvJsonValue, {
85
- readonly [key: string]: AvJsonValue;
85
+ readonly [key: string]: AvJsonValue | undefined;
86
86
  }>;
87
87
  /**
88
88
  * The field-independent shape of one scalar filter: any value, or an object of the
@@ -106,14 +106,17 @@ type FieldSlotReferences<C extends AvContract, K extends AvResourceKey<C>, F ext
106
106
  * whole value; `in`, `notIn`, `hasEvery` and `hasSome` take a list of entries;
107
107
  * `has` one entry; a `json` field's `string*`/`array*` operand a
108
108
  * {@link AvJsonValue}; `not` a nested operator object of the same operators, or
109
- * a value where `equals` is one of them. `mode`, `isEmpty` and `path` are passed to the adapter as given.
110
- * Only `equals`, `not` and the `json` `array*` operands take `null`. Inside a
111
- * transaction plan every operand but `mode`, `isEmpty`, `path` and the `json`
112
- * ones may be a `$ref` of the shape it holds (`References`).
109
+ * a value where `equals` is one of them. `mode` takes one of the modes the
110
+ * field offers at this level (`LevelMode`), `isEmpty` a boolean, `path` a list of
111
+ * JSON keys and a `json` field's `string*` operand a string: none of these is a
112
+ * value of the field. Only `equals`, `not` and the `json` `array*` operands take
113
+ * `null`. Inside a transaction plan every
114
+ * operand but `mode`, `isEmpty`, `path` and the `json` ones may be a `$ref` of
115
+ * the shape it holds (`References`).
113
116
  */
114
- type ScalarFilterOperands<Value, Atom, Json, References extends Readonly<Record<TransactionSlotShape, unknown>>, Operator extends AvScalarFilterOperator> = {
117
+ type ScalarFilterOperands<Value, Atom, Json, References extends Readonly<Record<TransactionSlotShape, unknown>>, Operator extends AvScalarFilterOperator, Mode extends ScalarFilterMode, PathMode extends ScalarFilterMode, LevelMode extends ScalarFilterMode> = {
115
118
  equals: Value | References["value"];
116
- not: ("equals" extends Operator ? Exclude<Value, JsonObject> | References["value"] : never) | ScalarFilterOperators<Value, Atom, Json, References, Operator>;
119
+ not: NegatedValue<Value, References, Operator> | ScalarFilterOperators<Value, Atom, Json, References, Operator, Mode, PathMode>;
117
120
  in: readonly Atom[] | References["entries"];
118
121
  notIn: readonly Atom[] | References["entries"];
119
122
  lt: Exclude<Value, null> | References["nonNullValue"];
@@ -123,26 +126,67 @@ type ScalarFilterOperands<Value, Atom, Json, References extends Readonly<Record<
123
126
  contains: Exclude<Value, null> | References["nonNullValue"];
124
127
  startsWith: Exclude<Value, null> | References["nonNullValue"];
125
128
  endsWith: Exclude<Value, null> | References["nonNullValue"];
126
- mode: unknown;
129
+ mode: LevelMode;
127
130
  has: Atom | References["entry"];
128
131
  hasEvery: readonly Atom[] | References["entries"];
129
132
  hasSome: readonly Atom[] | References["entries"];
130
- isEmpty: unknown;
131
- path: unknown;
132
- stringContains: Exclude<Json, null>;
133
- stringStartsWith: Exclude<Json, null>;
134
- stringEndsWith: Exclude<Json, null>;
133
+ isEmpty: boolean;
134
+ path: readonly string[];
135
+ stringContains: string;
136
+ stringStartsWith: string;
137
+ stringEndsWith: string;
135
138
  arrayContains: Json;
136
139
  arrayStartsWith: Json;
137
140
  arrayEndsWith: Json;
138
141
  };
139
142
  /**
140
143
  * A scalar filter's operator object: the operators `Operator` names — the ones
141
- * the field's `filter` member advertises — each with its operand.
144
+ * the field's `filter` member advertises — each with its operand. Where `path`
145
+ * is one of them, an object that names it takes `PathMode`, and one that does
146
+ * not takes `Mode`. An object that names `mode` or `path` names a comparing
147
+ * operator beside it.
142
148
  */
143
- type ScalarFilterOperators<Value, Atom, Json, References extends Readonly<Record<TransactionSlotShape, unknown>>, Operator extends AvScalarFilterOperator> = Readonly<Partial<Pick<ScalarFilterOperands<Value, Atom, Json, References, Operator>, Operator>>>;
149
+ type ScalarFilterOperators<Value, Atom, Json, References extends Readonly<Record<TransactionSlotShape, unknown>>, Operator extends AvScalarFilterOperator, Mode extends ScalarFilterMode, PathMode extends ScalarFilterMode> = ScalarFilterLevel<ReadonlyOperands<Value, Atom, Json, References, Operator, Mode, PathMode, Mode>, Operator, Mode, NegatedValue<Value, References, Operator>, "path" extends Operator ? {
150
+ readonly path?: never;
151
+ } : unknown> | ("path" extends Operator ? ScalarFilterOperatorsAtPath<ReadonlyOperands<Value, Atom, Json, References, Operator, Mode, PathMode, PathMode>, Operator, PathMode, NegatedValue<Value, References, Operator>> : never);
152
+ /** {@link ScalarFilterOperands}, each operand read-only. */
153
+ type ReadonlyOperands<Value, Atom, Json, References extends Readonly<Record<TransactionSlotShape, unknown>>, Operator extends AvScalarFilterOperator, Mode extends ScalarFilterMode, PathMode extends ScalarFilterMode, LevelMode extends ScalarFilterMode> = Readonly<ScalarFilterOperands<Value, Atom, Json, References, Operator, Mode, PathMode, LevelMode>>;
154
+ /** The operators of a level that compare something: all but `mode` and `path`. */
155
+ type ComparingOperator<Operator extends AvScalarFilterOperator> = Exclude<Operator, "mode" | "path">;
156
+ /** `not`'s value arm: equality negated, offered only where `equals` is. */
157
+ type NegatedValue<Value, References extends Readonly<Record<TransactionSlotShape, unknown>>, Operator extends AvScalarFilterOperator> = "equals" extends Operator ? Exclude<Value, JsonObject> | References["value"] : never;
158
+ /** A level's comparing operators, each optional. */
159
+ type ComparingOperators<Operands extends Readonly<Record<AvScalarFilterOperator, unknown>>, Operator extends AvScalarFilterOperator> = Partial<Pick<Operands, ComparingOperator<Operator>>>;
160
+ /**
161
+ * A level's comparing operators with at least one of them present; `not` is
162
+ * one only with a value (an object under `not` compares at its own level).
163
+ */
164
+ type SomeComparingOperator<Operands extends Readonly<Record<AvScalarFilterOperator, unknown>>, Operator extends AvScalarFilterOperator, Negated> = ({
165
+ [Key in Exclude<ComparingOperator<Operator>, "not">]-?: Pick<Operands, Key>;
166
+ }[Exclude<ComparingOperator<Operator>, "not">] & ComparingOperators<Operands, Operator>) | ("not" extends Operator ? {
167
+ readonly not: Negated;
168
+ } & Partial<Pick<Operands, Exclude<ComparingOperator<Operator>, "not">>> : never);
169
+ /**
170
+ * One operator object that names no `path`: without `mode`, any of its comparing
171
+ * operators; with `mode` (one of `Mode`), at least one of them.
172
+ */
173
+ type ScalarFilterLevel<Operands extends Readonly<Record<AvScalarFilterOperator, unknown>>, Operator extends AvScalarFilterOperator, Mode extends ScalarFilterMode, Negated, PathKey> = ("mode" extends Operator ? (ComparingOperators<Operands, Operator> & {
174
+ readonly mode?: never;
175
+ }) | (SomeComparingOperator<Operands, Operator, Negated> & {
176
+ readonly mode?: Mode;
177
+ }) : ComparingOperators<Operands, Operator>) & PathKey;
178
+ /** An operator object that names `path`: `PathMode`, and a comparing operator. */
179
+ type ScalarFilterOperatorsAtPath<Operands extends Readonly<Record<AvScalarFilterOperator, unknown>>, Operator extends AvScalarFilterOperator, PathMode extends ScalarFilterMode, Negated> = SomeComparingOperator<Operands, Operator, Negated> & {
180
+ readonly path: readonly string[];
181
+ } & ("mode" extends Operator ? {
182
+ readonly mode?: PathMode;
183
+ } : unknown);
144
184
  /** The filter operators field `F` advertises: `never` when it advertises none. */
145
185
  type ScalarFilterOperatorAt<C extends AvContract, K extends AvResourceKey<C>, F extends AvFieldKey<C, K>> = Extract<CapabilityTokens<C, K, F, "filter", UnrestrictedProjection>, AvScalarFilterOperator>;
186
+ /** The `mode` operands field `F` takes in an operator object without `path`. */
187
+ type ScalarFilterModeAt<C extends AvContract, K extends AvResourceKey<C>, F extends AvFieldKey<C, K>> = "default" | Extract<CapabilityTokens<C, K, F, "filterModes", UnrestrictedProjection>, ScalarFilterMode>;
188
+ /** The `mode` operands field `F` takes in an operator object that names `path`. */
189
+ type ScalarPathFilterModeAt<C extends AvContract, K extends AvResourceKey<C>, F extends AvFieldKey<C, K>> = "pathFilterModes" extends keyof CapabilitiesOf<C, K, F> ? "default" | Extract<CapabilityTokens<C, K, F, "pathFilterModes", UnrestrictedProjection>, ScalarFilterMode> : ScalarFilterModeAt<C, K, F>;
146
190
  /**
147
191
  * One scalar field's filter under `Forms`: a shorthand value where the field
148
192
  * advertises `equals`, or an object of the operators it advertises. Inside a
@@ -153,7 +197,7 @@ type ScalarFilterOperatorAt<C extends AvContract, K extends AvResourceKey<C>, F
153
197
  */
154
198
  type ScalarFilterFor<C extends AvContract, K extends AvResourceKey<C>, F extends AvFieldKey<C, K>, Forms extends ScalarForms, ReferenceForms extends ScalarForms> = ("equals" extends ScalarFilterOperatorAt<C, K, F> ? ScalarFilterShorthand<ScalarValueAt<C, K, F, Forms>> | SlotReference<C, K, F, ReferenceForms, "value"> : never) | ScalarFilterOperators<ScalarValueAt<C, K, F, Forms>, ScalarAtomAt<C, K, F, Forms>, ScalarFieldAt<C, K, F>["type"] extends {
155
199
  readonly scalar: "json";
156
- } ? AvJsonValue : ScalarValueAt<C, K, F, Forms>, FieldSlotReferences<C, K, F, ReferenceForms>, ScalarFilterOperatorAt<C, K, F>>;
200
+ } ? AvJsonValue : ScalarValueAt<C, K, F, Forms>, FieldSlotReferences<C, K, F, ReferenceForms>, ScalarFilterOperatorAt<C, K, F>, ScalarFilterModeAt<C, K, F>, ScalarPathFilterModeAt<C, K, F>>;
157
201
  /** The scalar fields of `K` a `where` can name: those advertising an operator. */
158
202
  type ScalarFilterableKey<C extends AvContract, K extends AvResourceKey<C>> = {
159
203
  [F in ScalarFieldKeyOf<C, K>]: [ScalarFilterOperatorAt<C, K, F>] extends [
@@ -330,11 +374,11 @@ type ConnectOrCreateEntry<C extends AvContract, K extends AvResourceKey<C>, F ex
330
374
  }>;
331
375
  type ToOneUpdateEntry<C extends AvContract, K extends AvResourceKey<C>, F extends RelationFieldKeyOf<C, K>, Forms extends ScalarForms, ReferenceForms extends ScalarForms> = Readonly<{
332
376
  where?: NestedWriteWhereInput<C, K, F, Forms, ReferenceForms>;
333
- data: AvUpdateDataInput<C, RelationTarget<C, K, F>, UnrestrictedWrite, Forms, ReferenceForms>;
377
+ data: AvUpdateDataInput<C, RelationTarget<C, K, F>, UnrestrictedWrite, Forms, ReferenceForms, readonly [K, F]>;
334
378
  }>;
335
379
  type ToManyUpdateEntry<C extends AvContract, K extends AvResourceKey<C>, F extends RelationFieldKeyOf<C, K>, Forms extends ScalarForms, ReferenceForms extends ScalarForms> = Readonly<{
336
380
  where: NestedWriteWhereInput<C, K, F, Forms, ReferenceForms>;
337
- data: AvUpdateDataInput<C, RelationTarget<C, K, F>, UnrestrictedWrite, Forms, ReferenceForms>;
381
+ data: AvUpdateDataInput<C, RelationTarget<C, K, F>, "bulkWrite", Forms, ReferenceForms, readonly [K, F]>;
338
382
  }>;
339
383
  type ToOneDeleteEntry<C extends AvContract, K extends AvResourceKey<C>, F extends RelationFieldKeyOf<C, K>, Forms extends ScalarForms, ReferenceForms extends ScalarForms> = true | Readonly<{
340
384
  where?: NestedWriteWhereInput<C, K, F, Forms, ReferenceForms>;
@@ -345,12 +389,12 @@ type ToManyDeleteEntry<C extends AvContract, K extends AvResourceKey<C>, F exten
345
389
  type ToOneUpsertEntry<C extends AvContract, K extends AvResourceKey<C>, F extends RelationFieldKeyOf<C, K>, Forms extends ScalarForms, ReferenceForms extends ScalarForms> = Readonly<{
346
390
  where?: AvUniqueWhereInput<C, RelationTarget<C, K, F>, Forms, ReferenceForms>;
347
391
  create: AvCreateDataInput<C, RelationTarget<C, K, F>, UnrestrictedWrite, Forms, ReferenceForms, readonly [K, F]>;
348
- update: AvUpdateDataInput<C, RelationTarget<C, K, F>, UnrestrictedWrite, Forms, ReferenceForms>;
392
+ update: AvUpdateDataInput<C, RelationTarget<C, K, F>, UnrestrictedWrite, Forms, ReferenceForms, readonly [K, F]>;
349
393
  }>;
350
394
  type ToManyUpsertEntry<C extends AvContract, K extends AvResourceKey<C>, F extends RelationFieldKeyOf<C, K>, Forms extends ScalarForms, ReferenceForms extends ScalarForms> = Readonly<{
351
395
  where: AvUniqueWhereInput<C, RelationTarget<C, K, F>, Forms, ReferenceForms>;
352
396
  create: AvCreateDataInput<C, RelationTarget<C, K, F>, UnrestrictedWrite, Forms, ReferenceForms, readonly [K, F]>;
353
- update: AvUpdateDataInput<C, RelationTarget<C, K, F>, UnrestrictedWrite, Forms, ReferenceForms>;
397
+ update: AvUpdateDataInput<C, RelationTarget<C, K, F>, UnrestrictedWrite, Forms, ReferenceForms, readonly [K, F]>;
354
398
  }>;
355
399
  type RelationDirectiveValue<C extends AvContract, K extends AvResourceKey<C>, F extends RelationFieldKeyOf<C, K>, Directive extends AvRelationMutationDirective, Forms extends ScalarForms, ReferenceForms extends ScalarForms> = Directive extends "$create" ? ManyOrOne<C, K, F, AvCreateDataInput<C, RelationTarget<C, K, F>, UnrestrictedWrite, Forms, ReferenceForms, readonly [K, F]>> : Directive extends "$connect" ? ManyOrOne<C, K, F, AvUniqueWhereInput<C, RelationTarget<C, K, F>, Forms, ReferenceForms>> : Directive extends "$connectOrCreate" ? ManyOrOne<C, K, F, ConnectOrCreateEntry<C, K, F, Forms, ReferenceForms>> : Directive extends "$disconnect" ? RelationCardinality<C, K, F> extends "one" ? true : AvUniqueWhereInput<C, RelationTarget<C, K, F>, Forms, ReferenceForms> | readonly AvUniqueWhereInput<C, RelationTarget<C, K, F>, Forms, ReferenceForms>[] : Directive extends "$set" ? RelationCardinality<C, K, F> extends "one" ? AvUniqueWhereInput<C, RelationTarget<C, K, F>, Forms, ReferenceForms> | null : readonly AvUniqueWhereInput<C, RelationTarget<C, K, F>, Forms, ReferenceForms>[] : Directive extends "$update" ? RelationCardinality<C, K, F> extends "one" ? ToOneUpdateEntry<C, K, F, Forms, ReferenceForms> : ManyOrOne<C, K, F, ToManyUpdateEntry<C, K, F, Forms, ReferenceForms>> : Directive extends "$delete" ? RelationCardinality<C, K, F> extends "one" ? ToOneDeleteEntry<C, K, F, Forms, ReferenceForms> : ManyOrOne<C, K, F, ToManyDeleteEntry<C, K, F, Forms, ReferenceForms>> : Directive extends "$upsert" ? RelationCardinality<C, K, F> extends "one" ? ToOneUpsertEntry<C, K, F, Forms, ReferenceForms> : ManyOrOne<C, K, F, ToManyUpsertEntry<C, K, F, Forms, ReferenceForms>> : never;
356
400
  declare const NO_ADVERTISED_MEMBER: unique symbol;
@@ -377,22 +421,46 @@ type ScalarDirectiveOperand<C extends AvContract, K extends AvResourceKey<C>, F
377
421
  */
378
422
  type ScalarMutationActionFor<C extends AvContract, K extends AvResourceKey<C>, F extends AvFieldKey<C, K>, Stage extends "create" | "update", Scope extends AvWriteScope, Forms extends ScalarForms> = [
379
423
  Extract<CapabilityTokens<C, K, F, Stage, Scope>, AvScalarMutationDirective>
380
- ] extends [never] ? never : Readonly<{
381
- [Directive in Extract<CapabilityTokens<C, K, F, Stage, Scope>, AvScalarMutationDirective>]?: ScalarDirectiveOperand<C, K, F, Directive, Forms>;
382
- }>;
424
+ ] extends [never] ? never : OneDirective<Extract<CapabilityTokens<C, K, F, Stage, Scope>, AvScalarMutationDirective>, C, K, F, Forms>;
425
+ type OneDirective<Tokens extends AvScalarMutationDirective, C extends AvContract, K extends AvResourceKey<C>, F extends AvFieldKey<C, K>, Forms extends ScalarForms> = {
426
+ [Directive in Tokens]: Readonly<{
427
+ [Key in Directive]: ScalarDirectiveOperand<C, K, F, Key, Forms>;
428
+ } & {
429
+ [Other in Exclude<Tokens, Directive>]?: never;
430
+ }>;
431
+ }[Tokens];
383
432
  /**
384
433
  * Each writable scalar takes its value under `Forms`, or a directive object of
385
434
  * the directives it advertises at this stage — or, in a transaction plan's
386
435
  * top-level data, a `$ref` to a source of the same type.
387
436
  */
388
- type ScalarMutationFields<C extends AvContract, K extends AvResourceKey<C>, Stage extends "create" | "update", Scope extends AvWriteScope, Forms extends ScalarForms, ReferenceForms extends ScalarForms> = Readonly<{
389
- [F in WritableScalarKey<C, K, Stage, Scope>]?: ScalarValueAt<C, K, F, Forms> | ScalarMutationActionFor<C, K, F, Stage, Scope, Forms> | SlotReference<C, K, F, ReferenceForms, "value">;
437
+ type ScalarMutationFields<C extends AvContract, K extends AvResourceKey<C>, Stage extends "create" | "update", Scope extends AvWriteScope, Forms extends ScalarForms, ReferenceForms extends ScalarForms, Via extends ParentEdge = never> = Readonly<{
438
+ [F in Exclude<WritableScalarKey<C, K, Stage, Scope>, LoopbackRefused<C, K, Stage, Via>>]?: ScalarValueAt<C, K, F, Forms> | ScalarMutationActionFor<C, K, F, Stage, Scope, Forms> | SlotReference<C, K, F, ReferenceForms, "value">;
390
439
  }>;
391
- type RelationMutationFields<C extends AvContract, K extends AvResourceKey<C>, Stage extends "create" | "update", Scope extends AvWriteScope, Forms extends ScalarForms, ReferenceForms extends ScalarForms> = Readonly<{
392
- [F in WritableRelationKey<C, K, Stage, Scope>]?: RelationMutationActionFor<C, K, F & RelationFieldKeyOf<C, K>, Stage, Scope, Forms, ReferenceForms>;
440
+ type RelationMutationFields<C extends AvContract, K extends AvResourceKey<C>, Stage extends "create" | "update", Scope extends AvWriteScope, Forms extends ScalarForms, ReferenceForms extends ScalarForms, Via extends ParentEdge = never> = Readonly<{
441
+ [F in Exclude<WritableRelationKey<C, K, Stage, Scope>, LoopbackRefused<C, K, Stage, Via>>]?: RelationMutationActionFor<C, K, F & RelationFieldKeyOf<C, K>, Stage, Scope, Forms, ReferenceForms>;
393
442
  }>;
394
443
  /** A relation edge a nested create is reached through: `[parent, relation]`. */
395
444
  type ParentEdge = readonly [string, string];
445
+ /** The other end of `Via`'s relation, as the Contract pairs it; `never` when it does not. */
446
+ type LoopbackOf<C extends AvContract, Via> = Via extends readonly [
447
+ infer Parent,
448
+ infer Relation
449
+ ] ? Parent extends AvResourceKey<C> ? Relation extends keyof C["resources"][Parent]["fields"] ? C["resources"][Parent]["fields"][Relation] extends {
450
+ readonly inverse: infer Inverse extends string;
451
+ } ? Inverse : never : never : never : never;
452
+ /** The fields `K`'s relation `Relation` states hold its reference; `never` when it states none. */
453
+ type ReferenceFieldsOf<C extends AvContract, K extends AvResourceKey<C>, Relation extends string> = Relation extends keyof C["resources"][K]["fields"] ? C["resources"][K]["fields"][Relation] extends {
454
+ readonly referenceFields: readonly (infer Field extends string)[];
455
+ } ? Field : never : never;
456
+ /**
457
+ * The members a record of `K` reached through `Via` cannot write at `Stage`:
458
+ * the other end of `Via`'s relation and the fields that hold its reference,
459
+ * unless the Contract admits that relation at the `"loopback"` position.
460
+ */
461
+ type LoopbackRefused<C extends AvContract, K extends AvResourceKey<C>, Stage extends "create" | "update", Via> = [Via] extends [never] ? never : LoopbackOf<C, Via> extends infer Loopback extends string ? [
462
+ Extract<Loopback, WritableRelationKey<C, K, Stage, "loopback">>
463
+ ] extends [never] ? Loopback | ReferenceFieldsOf<C, K, Loopback> : never : never;
396
464
  /** The names a requirement list holds; `never` when the member is absent. */
397
465
  type RequirementNames<Requirements, Member extends PropertyKey> = Requirements extends {
398
466
  readonly [P in Member]?: infer List;
@@ -450,9 +518,12 @@ type CreateRequirementPresence<C extends AvContract, K extends AvResourceKey<C>,
450
518
  * reference fields — never both. `Via` is the `[parent, relation]` a nested
451
519
  * create is reached through: what that parent supplies is not required.
452
520
  */
453
- export type AvCreateDataInput<C extends AvContract, K extends AvResourceKey<C>, Scope extends AvWriteScope = UnrestrictedWrite, Forms extends ScalarForms = UnstatedForms, ReferenceForms extends ScalarForms = never, Via extends ParentEdge = never> = RefuseEveryKeyWhenEmpty<Readonly<ScalarMutationFields<C, K, "create", Scope, Forms, ReferenceForms> & RelationMutationFields<C, K, "create", Scope, Forms, ReferenceForms>>> & CreateRequirementPresence<C, K, Scope, Via>;
454
- /** An `update` data argument, at one write position. See {@link AvCreateDataInput}. */
455
- export type AvUpdateDataInput<C extends AvContract, K extends AvResourceKey<C>, Scope extends AvWriteScope = UnrestrictedWrite, Forms extends ScalarForms = UnstatedForms, ReferenceForms extends ScalarForms = never> = RefuseEveryKeyWhenEmpty<Readonly<ScalarMutationFields<C, K, "update", Scope, Forms, ReferenceForms> & RelationMutationFields<C, K, "update", Scope, Forms, ReferenceForms>>>;
521
+ export type AvCreateDataInput<C extends AvContract, K extends AvResourceKey<C>, Scope extends AvWriteScope = UnrestrictedWrite, Forms extends ScalarForms = UnstatedForms, ReferenceForms extends ScalarForms = never, Via extends ParentEdge = never> = RefuseEveryKeyWhenEmpty<Readonly<ScalarMutationFields<C, K, "create", Scope, Forms, ReferenceForms, Via> & RelationMutationFields<C, K, "create", Scope, Forms, ReferenceForms, Via>>> & CreateRequirementPresence<C, K, Scope, Via>;
522
+ /**
523
+ * An `update` data argument, at one write position. See {@link AvCreateDataInput};
524
+ * `Via` is the `[parent, relation]` a nested update is reached through.
525
+ */
526
+ export type AvUpdateDataInput<C extends AvContract, K extends AvResourceKey<C>, Scope extends AvWriteScope = UnrestrictedWrite, Forms extends ScalarForms = UnstatedForms, ReferenceForms extends ScalarForms = never, Via extends ParentEdge = never> = RefuseEveryKeyWhenEmpty<Readonly<ScalarMutationFields<C, K, "update", Scope, Forms, ReferenceForms, Via> & RelationMutationFields<C, K, "update", Scope, Forms, ReferenceForms, Via>>>;
456
527
  type QueryProjectionArgs<C extends AvContract, K extends AvResourceKey<C>, Scope extends AvProjectionScope, Forms extends ScalarForms> = ProjectionChoice<C, K, Scope, false, Forms>;
457
528
  /**
458
529
  * Every standard operation's argument object, with each argument at its