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

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 (228) hide show
  1. package/CHANGELOG.md +943 -16
  2. package/ERRORS.md +142 -0
  3. package/README.md +274 -16
  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 +22 -0
  7. package/dist/adapters/adapter-identifier.d.ts +9 -0
  8. package/dist/adapters/adapter-identifier.js +6 -1
  9. package/dist/adapters/adapter-model.d.ts +9 -0
  10. package/dist/adapters/adapter-operation.d.ts +18 -3
  11. package/dist/adapters/adapter-resource.d.ts +5 -0
  12. package/dist/adapters/adapter.interface.d.ts +10 -0
  13. package/dist/avz/avz.d.ts +1 -1
  14. package/dist/avz/json-shape.registry.d.ts +2 -1
  15. package/dist/compiler/contract-compiler-diagnostic.guidance.d.ts +6 -0
  16. package/dist/compiler/contract-compiler-diagnostic.guidance.js +97 -0
  17. package/dist/compiler/contract.compiler.d.ts +6 -0
  18. package/dist/compiler/contract.compiler.js +227 -24
  19. package/dist/compiler/managed-elsewhere.projector.d.ts +52 -0
  20. package/dist/compiler/managed-elsewhere.projector.js +179 -0
  21. package/dist/compiler/managed-elsewhere.validator.d.ts +9 -0
  22. package/dist/compiler/managed-elsewhere.validator.js +74 -0
  23. package/dist/config/computed-fields.d.ts +62 -0
  24. package/dist/config/computed-fields.js +73 -0
  25. package/dist/config/config-diagnostic.guidance.d.ts +6 -0
  26. package/dist/config/config-diagnostic.guidance.js +32 -0
  27. package/dist/config/config.resolver.d.ts +24 -0
  28. package/dist/config/config.resolver.js +65 -0
  29. package/dist/config/config.validator.js +222 -8
  30. package/dist/config/define-resource.d.ts +1 -5
  31. package/dist/config/entrypoint.normalizer.d.ts +3 -2
  32. package/dist/config/exact-framework-config.d.ts +67 -22
  33. package/dist/config/field-compute.resolver.d.ts +2 -0
  34. package/dist/config/field-compute.resolver.js +8 -0
  35. package/dist/config/framework-config.d.ts +53 -15
  36. package/dist/config/framework-resources-config.d.ts +25 -14
  37. package/dist/config/pipeline-config.d.ts +4 -4
  38. package/dist/config/pipeline-tree.resolver.d.ts +2 -2
  39. package/dist/contracts/access-route.rules.d.ts +10 -0
  40. package/dist/contracts/access-route.rules.js +45 -0
  41. package/dist/contracts/capability.d.ts +26 -1
  42. package/dist/contracts/capability.js +20 -0
  43. package/dist/contracts/client-contract-structure.validator.d.ts +301 -89
  44. package/dist/contracts/client-contract-structure.validator.js +37 -2
  45. package/dist/contracts/contract-access.d.ts +29 -0
  46. package/dist/contracts/contract-access.js +1 -0
  47. package/dist/contracts/contract-canonicalizer.d.ts +9 -1
  48. package/dist/contracts/contract-canonicalizer.js +30 -4
  49. package/dist/contracts/contract-hash.d.ts +0 -1
  50. package/dist/contracts/contract-hash.js +1 -1
  51. package/dist/contracts/contract.d.ts +27 -1
  52. package/dist/contracts/field.d.ts +58 -1
  53. package/dist/contracts/json-schema.projector.d.ts +11 -0
  54. package/dist/contracts/json-schema.projector.js +78 -0
  55. package/dist/contracts/scalar-value-type.d.ts +3 -1
  56. package/dist/framework/access-facade.d.ts +116 -0
  57. package/dist/framework/access-facade.js +120 -0
  58. package/dist/framework/contract-view.d.ts +120 -16
  59. package/dist/framework/framework-construction.error.d.ts +14 -4
  60. package/dist/framework/framework-construction.error.js +18 -2
  61. package/dist/framework/framework.d.ts +26 -5
  62. package/dist/framework/framework.factory.d.ts +1 -1
  63. package/dist/framework/framework.factory.js +115 -23
  64. package/dist/framework/operation-facade.d.ts +80 -13
  65. package/dist/framework/operation-facade.js +2 -1
  66. package/dist/index.d.ts +17 -5
  67. package/dist/index.js +1 -0
  68. package/dist/internal.d.ts +7 -2
  69. package/dist/internal.js +6 -0
  70. package/dist/operations/database-refusal.d.ts +51 -0
  71. package/dist/operations/database-refusal.js +45 -0
  72. package/dist/operations/operation-arguments.d.ts +148 -60
  73. package/dist/operations/operation-call.d.ts +9 -5
  74. package/dist/operations/operation-result.d.ts +6 -4
  75. package/dist/operations/operation-validator-core.js +159 -36
  76. package/dist/operations/operation-validator.d.ts +1 -1
  77. package/dist/operations/structural-limits.walker.d.ts +1 -1
  78. package/dist/operations/validation-code.guidance.d.ts +6 -0
  79. package/dist/operations/validation-code.guidance.js +90 -0
  80. package/dist/operations/write-scope.resolver.d.ts +4 -0
  81. package/dist/operations/write-scope.resolver.js +2 -0
  82. package/dist/policy/access-provider.interface.d.ts +91 -0
  83. package/dist/policy/access-provider.interface.js +1 -0
  84. package/dist/policy/access-provider.registry.d.ts +17 -0
  85. package/dist/policy/access-provider.registry.js +15 -0
  86. package/dist/policy/access-question.d.ts +20 -0
  87. package/dist/policy/access-question.js +1 -0
  88. package/dist/policy/access-question.registry.d.ts +5 -0
  89. package/dist/policy/access-question.registry.js +19 -0
  90. package/dist/policy/access-question.runtime.d.ts +36 -0
  91. package/dist/policy/access-question.runtime.js +297 -0
  92. package/dist/policy/access-verb.table.d.ts +56 -0
  93. package/dist/policy/access-verb.table.js +21 -0
  94. package/dist/policy/access-verbs.d.ts +46 -0
  95. package/dist/policy/access-verbs.js +1 -0
  96. package/dist/policy/policy-admission.d.ts +20 -0
  97. package/dist/policy/policy-admission.js +409 -0
  98. package/dist/policy/policy-after-update.runtime.d.ts +79 -0
  99. package/dist/policy/policy-after-update.runtime.js +208 -0
  100. package/dist/policy/policy-condition.d.ts +15 -0
  101. package/dist/policy/policy-condition.js +36 -0
  102. package/dist/policy/policy-engine.d.ts +205 -0
  103. package/dist/policy/policy-engine.js +1 -0
  104. package/dist/policy/policy-named-rows.d.ts +18 -0
  105. package/dist/policy/policy-named-rows.js +51 -0
  106. package/dist/policy/policy-nested-write.runtime.d.ts +44 -0
  107. package/dist/policy/policy-nested-write.runtime.js +545 -0
  108. package/dist/policy/policy-read.runtime.d.ts +81 -0
  109. package/dist/policy/policy-read.runtime.js +503 -0
  110. package/dist/policy/policy-reference-write.runtime.d.ts +51 -0
  111. package/dist/policy/policy-reference-write.runtime.js +345 -0
  112. package/dist/policy/policy-request.d.ts +41 -0
  113. package/dist/policy/policy-request.js +63 -0
  114. package/dist/policy/policy-session.d.ts +106 -0
  115. package/dist/policy/policy-session.js +295 -0
  116. package/dist/policy/policy-startup.validator.d.ts +10 -0
  117. package/dist/policy/policy-startup.validator.js +112 -0
  118. package/dist/policy/policy-write.runtime.d.ts +39 -0
  119. package/dist/policy/policy-write.runtime.js +532 -0
  120. package/dist/prebuilt/core-version.generated.d.ts +2 -0
  121. package/dist/prebuilt/core-version.generated.js +1 -0
  122. package/dist/prebuilt/prebuilt-changes.d.ts +29 -0
  123. package/dist/prebuilt/prebuilt-changes.js +65 -0
  124. package/dist/prebuilt/prebuilt-contracts.d.ts +40 -0
  125. package/dist/prebuilt/prebuilt-contracts.emitter.d.ts +16 -0
  126. package/dist/prebuilt/prebuilt-contracts.emitter.js +50 -0
  127. package/dist/prebuilt/prebuilt-contracts.js +1 -0
  128. package/dist/prebuilt/prebuilt-contracts.loader.d.ts +19 -0
  129. package/dist/prebuilt/prebuilt-contracts.loader.js +77 -0
  130. package/dist/prebuilt/prebuilt-inputs.fingerprint.d.ts +16 -0
  131. package/dist/prebuilt/prebuilt-inputs.fingerprint.js +229 -0
  132. package/dist/protocol/absent-route.responder.d.ts +9 -27
  133. package/dist/protocol/absent-route.responder.js +36 -3
  134. package/dist/protocol/contract-document.responder.d.ts +2 -1
  135. package/dist/protocol/http-request.decoder.d.ts +19 -3
  136. package/dist/protocol/http-request.decoder.js +24 -1
  137. package/dist/protocol/http-request.reader.d.ts +6 -3
  138. package/dist/protocol/http-request.reader.js +1 -0
  139. package/dist/protocol/http-response.encoder.js +2 -2
  140. package/dist/protocol/protocol-headers.d.ts +3 -3
  141. package/dist/protocol/protocol-surface.d.ts +6 -1
  142. package/dist/protocol/protocol-surface.js +7 -0
  143. package/dist/protocol/protocol.binder.d.ts +16 -1
  144. package/dist/protocol/protocol.binder.js +4 -1
  145. package/dist/protocol/protocol.handler.d.ts +12 -1
  146. package/dist/protocol/protocol.handler.js +86 -17
  147. package/dist/runtime/caller-data.copier.d.ts +18 -3
  148. package/dist/runtime/caller-data.copier.js +21 -8
  149. package/dist/runtime/caller-data.door.d.ts +13 -6
  150. package/dist/runtime/computed-read-runtime.d.ts +6 -1
  151. package/dist/runtime/computed-read-runtime.js +232 -45
  152. package/dist/runtime/computed-value.error.d.ts +3 -2
  153. package/dist/runtime/computed-value.error.js +3 -2
  154. package/dist/runtime/context-executor.runtime.d.ts +4 -3
  155. package/dist/runtime/context-executor.runtime.js +4 -2
  156. package/dist/runtime/core-class.brand.d.ts +10 -0
  157. package/dist/runtime/core-class.brand.js +21 -0
  158. package/dist/runtime/create-requirement.d.ts +31 -5
  159. package/dist/runtime/create-requirement.js +129 -21
  160. package/dist/runtime/decimal.d.ts +14 -0
  161. package/dist/runtime/decimal.js +21 -0
  162. package/dist/runtime/diagnostics-reporter.d.ts +44 -9
  163. package/dist/runtime/diagnostics-reporter.js +13 -1
  164. package/dist/runtime/error-boundary.d.ts +9 -2
  165. package/dist/runtime/error-boundary.js +12 -0
  166. package/dist/runtime/error-guidance.d.ts +12 -0
  167. package/dist/runtime/error-guidance.js +1 -0
  168. package/dist/runtime/error.catalog.d.ts +49 -0
  169. package/dist/runtime/error.catalog.js +49 -0
  170. package/dist/runtime/execution-options.d.ts +8 -0
  171. package/dist/runtime/field-check.evaluator.d.ts +0 -2
  172. package/dist/runtime/field-check.evaluator.js +1 -1
  173. package/dist/runtime/field-compute.d.ts +11 -10
  174. package/dist/runtime/field-stage.runtime.d.ts +3 -1
  175. package/dist/runtime/field-stage.runtime.js +8 -4
  176. package/dist/runtime/field-validator.runtime.d.ts +19 -1
  177. package/dist/runtime/field-validator.runtime.js +47 -8
  178. package/dist/runtime/framework-error.js +9 -0
  179. package/dist/runtime/framework-warning.emitter.d.ts +6 -0
  180. package/dist/runtime/framework-warning.emitter.js +8 -0
  181. package/dist/runtime/json-value.d.ts +10 -5
  182. package/dist/runtime/level-lifecycle.runtime.d.ts +4 -3
  183. package/dist/runtime/level-lifecycle.runtime.js +3 -5
  184. package/dist/runtime/mutation-planner.d.ts +1 -6
  185. package/dist/runtime/mutation-planner.js +3 -14
  186. package/dist/runtime/operation-code.guidance.d.ts +6 -0
  187. package/dist/runtime/operation-code.guidance.js +167 -0
  188. package/dist/runtime/operation-request.d.ts +16 -9
  189. package/dist/runtime/operation-runtime.d.ts +35 -17
  190. package/dist/runtime/operation-runtime.js +220 -49
  191. package/dist/runtime/outcome-mapper.d.ts +2 -1
  192. package/dist/runtime/output-validator.d.ts +1 -1
  193. package/dist/runtime/owned-execution.registry.d.ts +13 -3
  194. package/dist/runtime/pipe-output.validator.d.ts +10 -0
  195. package/dist/runtime/pipe-output.validator.js +4 -2
  196. package/dist/runtime/pipeline.d.ts +47 -22
  197. package/dist/runtime/readonly-value.d.ts +20 -5
  198. package/dist/runtime/readonly-value.js +74 -26
  199. package/dist/runtime/referential-action.d.ts +1 -1
  200. package/dist/runtime/scalar-runtime.d.ts +4 -11
  201. package/dist/runtime/scalar-runtime.js +91 -22
  202. package/dist/runtime/unsent-value.normalizer.d.ts +15 -0
  203. package/dist/runtime/unsent-value.normalizer.js +128 -0
  204. package/dist/transactions/operation-arguments-encoder.js +8 -3
  205. package/dist/transactions/operation-fingerprint.js +2 -2
  206. package/dist/transactions/sql-statement.d.ts +10 -2
  207. package/dist/transactions/sql-statement.validator.d.ts +2 -2
  208. package/dist/transactions/sql-statement.validator.js +38 -5
  209. package/dist/transactions/transaction-envelope.d.ts +5 -4
  210. package/dist/transactions/transaction-integrity-error.js +2 -0
  211. package/dist/transactions/transaction-plan-assembler.d.ts +12 -5
  212. package/dist/transactions/transaction-plan-assembler.js +48 -10
  213. package/dist/transactions/transaction-plan-validator.d.ts +23 -7
  214. package/dist/transactions/transaction-plan-validator.js +27 -1
  215. package/dist/transactions/transaction-reference-resolver.d.ts +3 -2
  216. package/dist/transactions/transaction-reference-validator.d.ts +43 -3
  217. package/dist/transactions/transaction-reference-validator.js +83 -57
  218. package/dist/transactions/transaction-reference.d.ts +1 -1
  219. package/dist/transactions/transaction-reference.js +5 -0
  220. package/dist/transactions/transaction-request.d.ts +8 -8
  221. package/dist/transactions/transaction-runtime.d.ts +5 -2
  222. package/dist/transactions/transaction-runtime.js +79 -27
  223. package/dist/transactions/transaction-settlement.runtime.d.ts +4 -3
  224. package/dist/transactions/transaction-slot-path-error.js +2 -0
  225. package/dist/transactions/transaction-stage.runtime.d.ts +5 -3
  226. package/dist/transactions/transaction-stage.runtime.js +7 -4
  227. package/package.json +6 -5
  228. package/src/runtime/field-check.evaluator.ts +1 -1
@@ -19,7 +19,7 @@ function serverContract(core, active) {
19
19
  return { ...core, resources };
20
20
  }
21
21
  const SERVER_VIEWS = new WeakMap();
22
- function serverContractFor(core, active) {
22
+ export function serverContractFor(core, active) {
23
23
  let view = SERVER_VIEWS.get(active);
24
24
  if (view === undefined) {
25
25
  view = serverContract(core, active);
@@ -64,7 +64,9 @@ export function refusePipeOutput(contracts, request, args, pipe, index, options)
64
64
  return refuseStageOutput(contracts, request, args, `Pipe ${pipeLabel(pipe, index)}`, options);
65
65
  }
66
66
  export function refuseStageOutput(contracts, request, args, subject, options) {
67
- const issues = serverIssues(serverContractFor(contracts.core, contracts.active), request, args, options);
67
+ return refusedStageOutput(request, serverIssues(serverContractFor(contracts.core, contracts.active), request, args, options), subject);
68
+ }
69
+ export function refusedStageOutput(request, issues, subject) {
68
70
  const [first] = issues;
69
71
  if (first === undefined) {
70
72
  return undefined;
@@ -1,16 +1,17 @@
1
1
  import type { AvAdapterModel } from "../adapters/adapter-model.js";
2
2
  import type { AvAdapterSqlValue } from "../adapters/adapter-sql-statement.js";
3
+ import type { AvPreComputeLift } from "../config/computed-fields.js";
3
4
  import type { AvRequestOrigin } from "../config/config-layer.js";
4
5
  import type { AvConfigResourceKey } from "../config/framework-field-definition.js";
5
6
  import type { AvContract } from "../contracts/contract.js";
6
7
  import type { AvApplicationScalarForms } from "../contracts/scalar-value-type.js";
7
- import type { AvContractApplicationView } from "../framework/contract-view.js";
8
+ import type { AvContractApplicationView, RequirementsLessComputed } from "../framework/contract-view.js";
8
9
  import type { AvContextOperations } from "../framework/operation-facade.js";
9
10
  import type { AvOperationArgumentsFor, AvResourceKey } from "../operations/operation-arguments.js";
10
11
  import type { AvOperationFamily, AvOperationIdentity } from "../operations/operation-identity.js";
11
12
  import type { AvTransactionReference } from "../transactions/transaction-reference.js";
12
13
  import type { AvError } from "./framework-error.js";
13
- import type { AvDeepReadonly, AvDeepReadonlyLeaf } from "./readonly-value.js";
14
+ import type { ArrayForm, AvDeepReadonly, AvDeepReadonlyLeaf } from "./readonly-value.js";
14
15
  import type { AvState } from "./request-state.js";
15
16
  /**
16
17
  * What a pipeline is told about the HTTP request a client-origin operation arrived on.
@@ -36,24 +37,38 @@ type AvContextFacts = {
36
37
  */
37
38
  readonly $state: AvState;
38
39
  };
39
- /**
40
- * `ctx.$sql`: a developer SQL statement, written only as a tagged template —
41
- * ``await ctx.$sql<Row>`insert into audit (actor) values (${actorId})` `` — run at
42
- * once. Every interpolated value is bound as a parameter, never spliced into the
43
- * text. It runs in the current transaction; outside one, as a plain statement,
44
- * with no transaction opened around it. Resolves the rows the statement returned.
45
- */
46
- export type AvContextSql = <Row = unknown>(strings: TemplateStringsArray, ...values: readonly AvAdapterSqlValue[]) => Promise<Row[]>;
47
40
  /**
48
41
  * The arguments a variant-level stage is told: the operation's arguments under
49
42
  * the model's Application Contract view, in the application forms, a reference
50
43
  * position admitting an unresolved `$ref`. `unknown` for a model whose Resource
51
44
  * names are not literal.
52
45
  */
53
- export type AvStageArguments<M extends AvAdapterModel, R extends string, F extends AvOperationFamily, V extends string> = string extends R ? unknown : R extends AvResourceKey<AvContractApplicationView<M>> ? AvOperationArgumentsFor<AvContractApplicationView<M>, R, Extract<AvOperationIdentity, {
46
+ export type AvStageArguments<M extends AvAdapterModel, R extends string, F extends AvOperationFamily, V extends string> = StageArgumentsOf<AvContractApplicationView<M>, R, F, V>;
47
+ /** The arguments of operation `F`.`V` on `R` under the Contract view `C`. */
48
+ type StageArgumentsOf<C extends AvContract, R extends string, F extends AvOperationFamily, V extends string> = string extends R ? unknown : R extends AvResourceKey<C> ? AvOperationArgumentsFor<C, R, Extract<AvOperationIdentity, {
54
49
  readonly family: F;
55
50
  readonly variant: V;
56
51
  }>, AvApplicationScalarForms, AvApplicationScalarForms> : unknown;
52
+ /**
53
+ * The arguments a variant's guards, pipes and filters are told: the operation's
54
+ * arguments before write compute has run, so a create need not hold a field
55
+ * `compute.create` fills (`Lift`, as `"<Resource>.<field>"`; `string`: any).
56
+ */
57
+ export type AvPreComputeStageArguments<M extends AvAdapterModel, R extends string, F extends AvOperationFamily, V extends string, Lift extends string = AvPreComputeLift<M, "root">> = F extends "find" | "delete" ? AvStageArguments<M, R, F, V> : [Lift] extends [never] ? AvStageArguments<M, R, F, V> : StageArgumentsOf<PreComputeView<AvContractApplicationView<M>, Lift>, R, F, V>;
58
+ /** A Contract view with the create requirements `Lift` names lifted. */
59
+ type PreComputeView<C, Lift extends string> = Extract<{
60
+ readonly [P in keyof C]: P extends "resources" ? string extends Lift ? {
61
+ readonly [R in keyof C[P]]: Omit<C[P][R], "createRequirements">;
62
+ } : {
63
+ readonly [R in keyof C[P]]: LiftedResource<C[P][R], LiftedFieldOf<Lift, R>>;
64
+ } : C[P];
65
+ }, AvContract>;
66
+ /** The fields of Resource `R` that `Lift` names. */
67
+ type LiftedFieldOf<Lift extends string, R> = Lift extends `${R & string}.${infer Field}` ? Field : never;
68
+ /** One Resource with the create requirements on `Fields` lifted; itself when none. */
69
+ type LiftedResource<Resource, Fields> = [Fields] extends [never] ? Resource : {
70
+ readonly [P in keyof Resource]: P extends "createRequirements" ? RequirementsLessComputed<Exclude<Resource[P], undefined>, Fields> : Resource[P];
71
+ };
57
72
  /**
58
73
  * What a pipeline callback is told about the operation it is wrapping, and the
59
74
  * framework's executor: `ctx.<Resource>.<family>.<variant>(args)`, `ctx.$sql` and
@@ -83,11 +98,20 @@ export type AvContext<M extends AvAdapterModel = AvAdapterModel, R extends strin
83
98
  * returned them in a pipe, and the validated arguments the operation executes in
84
99
  * an interceptor, a hook or a filter. Only a pipe changes them, by returning new
85
100
  * ones; writing to them throws a `TypeError`. At a variant, typed as that
86
- * operation's arguments ({@link AvStageArguments}), deep read-only; above it,
87
- * `unknown`.
101
+ * operation's arguments ({@link AvStageArguments}), deep read-only; in a guard or
102
+ * a filter a create may lack the fields `compute.create` fills ({@link
103
+ * AvPreComputeStageArguments}: write compute has not run); above it, `unknown`.
88
104
  */
89
105
  readonly $args: AvDeepReadonly<A>;
90
- readonly $sql: AvContextSql;
106
+ /**
107
+ * A developer SQL statement, written only as a tagged template —
108
+ * ``await ctx.$sql<Row>`insert into audit (actor) values (${actorId})` ``
109
+ * — run at once. Every interpolated value is bound as a parameter, never
110
+ * spliced into the text. It runs in the current transaction; outside one,
111
+ * as a plain statement, with no transaction opened around it. Resolves
112
+ * the rows the statement returned.
113
+ */
114
+ readonly $sql: <Row = unknown>(strings: TemplateStringsArray, ...values: readonly AvAdapterSqlValue[]) => Promise<Row[]>;
91
115
  /**
92
116
  * Runs `work` in a transaction, `tx` being this ctx bound to it, and resolves
93
117
  * what `work` resolved; a throw rolls it back. Inside an open transaction it
@@ -103,22 +127,22 @@ export type AvGuard<M extends AvAdapterModel = AvAdapterModel, R extends string
103
127
  * Transforms the arguments before the validators judge them: the only stage
104
128
  * that changes them. `args` is deep-frozen, so a pipe returns new arguments;
105
129
  * returning `undefined` keeps them as they are. At a variant, `args` and the
106
- * return are typed as that operation's arguments ({@link AvStageArguments}),
107
- * `args` deep read-only.
130
+ * return are typed as that operation's arguments before write compute runs
131
+ * ({@link AvPreComputeStageArguments}: a create may lack the fields
132
+ * `compute.create` fills), `args` deep read-only.
108
133
  */
109
134
  export type AvPipe<M extends AvAdapterModel = AvAdapterModel, R extends string = AvConfigResourceKey<M>, F extends AvOperationFamily = AvOperationFamily, V extends string = string, A = unknown> = {
110
- pipe(args: PipeArguments<A>, context: AvContext<M, R, F, V, A>): PipeResult<PipeArguments<A>>;
135
+ pipe(args: PipeArguments<A>, context: AvContext<M, R, F, V, A>): unknown extends PipeArguments<A> ? any : PipeArguments<A> | undefined | void | Promise<PipeArguments<A> | undefined | void>;
111
136
  }["pipe"];
112
137
  /**
113
138
  * A variant pipe's arguments and return: the operation's arguments, deep
114
139
  * read-only, each member closed to inference.
115
140
  */
116
- type PipeArguments<T> = T extends AvDeepReadonlyLeaf | AvTransactionReference ? T : T extends object ? {
141
+ type PipeArguments<T> = T extends AvDeepReadonlyLeaf | AvTransactionReference ? T : T extends readonly unknown[] ? ArrayForm<T> extends "array" ? readonly NoInfer<PipeArguments<Exclude<T[number], undefined>>>[] : {
142
+ readonly [K in keyof T]: NoInfer<PipeArguments<Exclude<T[K], undefined>>>;
143
+ } : T extends object ? {
117
144
  readonly [K in keyof T]: NoInfer<PipeArguments<Exclude<T[K], undefined>>>;
118
145
  } : T;
119
- /** What a pipe returns: arguments of type `A`, or `undefined` to keep them. */
120
- type PipeResult<A> = unknown extends A ? any : // biome-ignore lint/suspicious/noConfusingVoidType: a pipe written as `() => void sideEffect()` keeps the arguments, as `undefined` does
121
- A | undefined | void | Promise<A | undefined | void>;
122
146
  /** Runs just before the operation executes, inside every interceptor; `ctx.$args` is deep-frozen. */
123
147
  export type AvBeforeHook<M extends AvAdapterModel = AvAdapterModel, R extends string = AvConfigResourceKey<M>, F extends AvOperationFamily = AvOperationFamily, V extends string = string, A = unknown> = {
124
148
  before(context: AvContext<M, R, F, V, A>): void | Promise<void>;
@@ -180,7 +204,8 @@ export type AvTransactionStep<M extends AvAdapterModel = AvAdapterModel> = {
180
204
  export type AvTransactionContext<M extends AvAdapterModel = AvAdapterModel> = AvContextOperations<M> & AvContextFacts & {
181
205
  /** The plan, in step order, `$ref`s unresolved, deep-frozen. */
182
206
  readonly $args: readonly AvTransactionStep<M>[];
183
- readonly $sql: AvContextSql;
207
+ /** As a stage's `ctx.$sql`: a developer SQL statement, run in this transaction. */
208
+ readonly $sql: <Row = unknown>(strings: TemplateStringsArray, ...values: readonly AvAdapterSqlValue[]) => Promise<Row[]>;
184
209
  /** Runs `work` inside this transaction (it opens nothing): `tx` is this ctx. */
185
210
  $transaction<T>(work: (tx: AvTransactionContext<M>) => Promise<T>): Promise<T>;
186
211
  };
@@ -1,12 +1,23 @@
1
- import { AvDecimal } from "./decimal.js";
1
+ import { type AvDecimal } from "./decimal.js";
2
2
  /** A value `AvDeepReadonly` leaves as it is: a primitive, a function, or a scalar instance. */
3
3
  export type AvDeepReadonlyLeaf = string | number | boolean | bigint | symbol | null | undefined | Date | Uint8Array | AvDecimal | ((...args: never[]) => unknown);
4
+ /**
5
+ * Whether array type `T` is an open array (every length: `"array"`) or a tuple
6
+ * (`"tuple"`). A tuple whose only fixed positions are optional ones before a rest
7
+ * element (`[a?, ...b[]]`) has every length too and reads as an array.
8
+ */
9
+ export type ArrayForm<T extends readonly unknown[]> = T extends readonly [] | readonly [unknown, ...unknown[]] | readonly [...unknown[], unknown] ? "tuple" : T extends {
10
+ readonly length: infer Length;
11
+ } ? number extends Length ? "array" : "tuple" : "tuple";
4
12
  /**
5
13
  * `T` with every property, element and nested property read-only: what a stage
6
- * that only reads is told. Arrays become read-only arrays; a `Date`, a
7
- * `Uint8Array`, an `AvDecimal` and functions are left as they are.
14
+ * that only reads is told. Arrays become read-only arrays, tuples read-only
15
+ * tuples; a `Date`, a `Uint8Array`, an `AvDecimal` and functions are left as
16
+ * they are.
8
17
  */
9
- export type AvDeepReadonly<T> = T extends AvDeepReadonlyLeaf ? T : T extends object ? {
18
+ export type AvDeepReadonly<T> = T extends AvDeepReadonlyLeaf ? T : T extends readonly unknown[] ? ArrayForm<T> extends "array" ? readonly AvDeepReadonly<T[number]>[] : {
19
+ readonly [K in keyof T]: AvDeepReadonly<T[K]>;
20
+ } : T extends object ? {
10
21
  readonly [K in keyof T]: AvDeepReadonly<T[K]>;
11
22
  } : T;
12
23
  /**
@@ -21,8 +32,12 @@ export declare function deepFreeze<T>(value: T): T;
21
32
  * only reads is told — the validated arguments after validation, a result in an
22
33
  * after hook — so neither what is written nor the result the caller receives can
23
34
  * be changed through it.
35
+ *
36
+ * With `snapshot`, a copied container found to hold no `Date` or `Uint8Array`
37
+ * is remembered as such at once, so a later `frozenCopy` of the copy shares it
38
+ * without walking it: for a copy that is itself copied again for each stage.
24
39
  */
25
- export declare function frozenCopy<T>(value: T): AvDeepReadonly<T>;
40
+ export declare function frozenCopy<T>(value: T, snapshot?: boolean): AvDeepReadonly<T>;
26
41
  /**
27
42
  * A read-only view of `value`: reads see the live value, nested plain objects
28
43
  * and arrays as views too, and any write, definition or deletion throws a
@@ -1,4 +1,4 @@
1
- import { AvDecimal } from "./decimal.js";
1
+ import { isAvDecimal } from "./decimal.js";
2
2
  function isContainer(value) {
3
3
  if (value === null || typeof value !== "object") {
4
4
  return false;
@@ -6,7 +6,7 @@ function isContainer(value) {
6
6
  if (Array.isArray(value)) {
7
7
  return true;
8
8
  }
9
- if (value instanceof AvDecimal) {
9
+ if (isAvDecimal(value)) {
10
10
  return false;
11
11
  }
12
12
  const prototype = Object.getPrototypeOf(value);
@@ -36,51 +36,99 @@ function scalarCopy(value) {
36
36
  }
37
37
  return undefined;
38
38
  }
39
- export function frozenCopy(value) {
39
+ const SCALAR_FREE = new WeakSet();
40
+ function scalarFree(value) {
41
+ return Object.isFrozen(value) && SCALAR_FREE.has(value);
42
+ }
43
+ const WALKING = Symbol("walking");
44
+ function place(target, key, value, enumerable) {
45
+ if (enumerable && typeof key === "string" && !(key in target)) {
46
+ target[key] = value;
47
+ return;
48
+ }
49
+ Object.defineProperty(target, key, {
50
+ value,
51
+ enumerable,
52
+ writable: true,
53
+ configurable: true,
54
+ });
55
+ }
56
+ export function frozenCopy(value, snapshot = false) {
57
+ const scalar = scalarCopy(value);
58
+ if (scalar !== undefined) {
59
+ return scalar;
60
+ }
61
+ if (!isContainer(value) || scalarFree(value)) {
62
+ return value;
63
+ }
40
64
  const copies = new Map();
65
+ let cycled = false;
66
+ let holdsScalar = false;
41
67
  const copy = (current) => {
68
+ if (current === null || typeof current !== "object") {
69
+ return current;
70
+ }
42
71
  const scalar = scalarCopy(current);
43
72
  if (scalar !== undefined) {
73
+ holdsScalar = true;
44
74
  return scalar;
45
75
  }
46
- if (!isContainer(current)) {
76
+ if (!isContainer(current) || scalarFree(current)) {
47
77
  return current;
48
78
  }
49
- if (copies.has(current)) {
50
- return copies.get(current);
79
+ const known = copies.get(current);
80
+ if (known === WALKING) {
81
+ cycled = true;
82
+ return current;
83
+ }
84
+ if (known !== undefined) {
85
+ holdsScalar ||= !scalarFree(known);
86
+ return known;
51
87
  }
52
- copies.set(current, current);
53
- const entries = [];
54
- let changed = !DEEP_FROZEN.has(current);
88
+ copies.set(current, WALKING);
89
+ const outer = holdsScalar;
90
+ holdsScalar = false;
91
+ let changed = !(Object.isFrozen(current) && DEEP_FROZEN.has(current));
92
+ const array = Array.isArray(current);
93
+ const prototype = Object.getPrototypeOf(current);
94
+ const target = array
95
+ ? new Array(current.length)
96
+ : prototype === Object.prototype
97
+ ? {}
98
+ : Object.create(prototype);
55
99
  for (const key of Reflect.ownKeys(current)) {
100
+ if (array && key === "length") {
101
+ continue;
102
+ }
56
103
  const descriptor = Object.getOwnPropertyDescriptor(current, key);
57
- if (descriptor === undefined ||
58
- !("value" in descriptor) ||
59
- (Array.isArray(current) && key === "length")) {
104
+ if (descriptor === undefined || !("value" in descriptor)) {
60
105
  continue;
61
106
  }
62
107
  const next = copy(descriptor.value);
63
108
  changed ||= next !== descriptor.value;
64
- entries.push([key, descriptor, next]);
109
+ place(target, key, next, descriptor.enumerable === true);
65
110
  }
111
+ const held = holdsScalar;
112
+ holdsScalar = outer || held;
66
113
  if (!changed) {
67
114
  copies.set(current, current);
115
+ if (!cycled) {
116
+ SCALAR_FREE.add(current);
117
+ }
68
118
  return current;
69
119
  }
70
- const target = Array.isArray(current)
71
- ? new Array(current.length)
72
- : Object.create(Object.getPrototypeOf(current));
73
- for (const [key, descriptor, next] of entries) {
74
- Object.defineProperty(target, key, {
75
- value: next,
76
- enumerable: descriptor.enumerable ?? false,
77
- writable: true,
78
- configurable: true,
79
- });
120
+ Object.freeze(target);
121
+ DEEP_FROZEN.add(target);
122
+ if (cycled) {
123
+ for (const key of Reflect.ownKeys(target)) {
124
+ deepFreeze(target[key]);
125
+ }
126
+ }
127
+ if (snapshot && !held && !cycled) {
128
+ SCALAR_FREE.add(target);
80
129
  }
81
- const frozen = deepFreeze(target);
82
- copies.set(current, frozen);
83
- return frozen;
130
+ copies.set(current, target);
131
+ return target;
84
132
  };
85
133
  return copy(value);
86
134
  }
@@ -29,7 +29,7 @@ export type ReferentialEdge = {
29
29
  * relation `field` and any of these names the reference twice and is refused;
30
30
  * a required relation may be written through all of them instead.
31
31
  */
32
- readonly references: readonly string[];
32
+ readonly referenceFields: readonly string[];
33
33
  /** What deleting a `target` row does to rows of the ENCLOSING Resource. */
34
34
  readonly onDelete: ReferentialDeleteEffect;
35
35
  };
@@ -1,19 +1,18 @@
1
1
  import type { AvRequestOrigin } from "../config/config-layer.js";
2
- import type { AvScalarFilterOperator } from "../contracts/capability.js";
3
2
  import type { AvContract } from "../contracts/contract.js";
4
3
  import type { AvScalarField } from "../contracts/field.js";
5
4
  import type { AvOperationIdentity } from "../operations/operation-identity.js";
6
5
  import type { OperationValidationOptions, SemanticOperationArguments } from "../operations/operation-validator.js";
7
6
  import type { AvValidationIssue } from "../operations/validation-code.js";
8
7
  import { type TransactionSlotShape } from "../transactions/transaction-reference.js";
9
- export type ScalarNormalizationResult = {
8
+ type ScalarNormalizationResult = {
10
9
  readonly ok: true;
11
10
  readonly arguments: SemanticOperationArguments;
12
11
  } | {
13
12
  readonly ok: false;
14
13
  readonly issues: readonly AvValidationIssue[];
15
14
  };
16
- export type ScalarValueNormalizationResult = {
15
+ type ScalarValueNormalizationResult = {
17
16
  readonly ok: true;
18
17
  readonly value: unknown;
19
18
  } | {
@@ -31,6 +30,8 @@ type Path = readonly (string | number)[];
31
30
  * one reader and a value to another.
32
31
  */
33
32
  export declare function isPlainRecord(value: unknown): value is Readonly<Record<string, unknown>>;
33
+ /** The `V1001` of an `undefined` the request reads with. */
34
+ export declare function unsentReadValueIssue(path: Path): AvValidationIssue;
34
35
  /**
35
36
  * An operation argument's JSON value is bounded by `maxJsonDepth`, which the
36
37
  * structural walk enforces before this runs; a `$sql` step's parameter is not
@@ -71,14 +72,6 @@ export declare function jsonShapeIssues(field: AvScalarField<AvContract>, value:
71
72
  * its copy.
72
73
  */
73
74
  export declare function copyScalarFieldValue(field: AvScalarField<AvContract>, value: unknown): unknown;
74
- /**
75
- * The operators of a step's own filter whose operand is a reference position, and
76
- * the shape each holds — the one table the normalization below and the reference
77
- * collector (`transaction-reference-collector.ts`) both read. `not` also takes a
78
- * nested operator object. Not here, so never a reference: `mode`, `isEmpty`, `path`
79
- * and a `json` field's `string*`/`array*` operators, which take no field value.
80
- */
81
- export declare const REFERENCE_OPERANDS: Readonly<Partial<Record<AvScalarFilterOperator, TransactionSlotShape>>>;
82
75
  /** The shape of `operator`'s operand when it is a reference position. */
83
76
  export declare function referenceOperandShape(operator: string): TransactionSlotShape | undefined;
84
77
  /**
@@ -1,13 +1,20 @@
1
1
  import { shapeOfField } from "../avz/json-shape.registry.js";
2
+ import { AVZ_FORMATS } from "../config/framework-field-definition.js";
3
+ import { FILTER_MODES, } from "../contracts/capability.js";
2
4
  import { lookupField, lookupOwn, lookupResource, } from "../contracts/contract-lookup.js";
3
5
  import { decodeBase64, WIRE_SCALAR_PATTERNS, } from "../contracts/wire-scalar-format.js";
4
6
  import { routeTransactionReferenceCandidate, } from "../transactions/transaction-reference.js";
5
- import { AvDecimal } from "./decimal.js";
7
+ import { AvDecimal, isAvDecimal } from "./decimal.js";
8
+ import { fieldCheckIssues } from "./field-check.evaluator.js";
6
9
  const INT_MIN = -(2 ** 31);
7
10
  const INT_MAX = 2 ** 31 - 1;
8
11
  const BIGINT_MIN = -(2n ** 63n);
9
12
  const BIGINT_MAX = 2n ** 63n - 1n;
10
13
  const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
14
+ const NAMED_FORMATS = new Set(AVZ_FORMATS);
15
+ function isNamedFormat(format) {
16
+ return NAMED_FORMATS.has(format);
17
+ }
11
18
  function isRecord(value) {
12
19
  return value !== null && typeof value === "object" && !Array.isArray(value);
13
20
  }
@@ -24,6 +31,10 @@ function issue(code, path, message) {
24
31
  function typeIssue(path, expected) {
25
32
  return issue("V1001", path, `Value must be ${expected}.`);
26
33
  }
34
+ const UNSENT_READ_VALUE_MESSAGE = "Value must not be undefined: it would be left out, and the request would reach more rows.";
35
+ export function unsentReadValueIssue(path) {
36
+ return issue("V1001", path, UNSENT_READ_VALUE_MESSAGE);
37
+ }
27
38
  function formatIssue(path, expected) {
28
39
  return issue("V1003", path, `Value must use ${expected}.`);
29
40
  }
@@ -83,18 +94,14 @@ export function normalizeJsonValue(value, path, seen) {
83
94
  const normalized = [];
84
95
  task.emit(normalized);
85
96
  stack.push({ kind: "leave", container: current });
86
- const entries = [];
87
- current.forEach((entry, index) => {
88
- entries.push([index, entry]);
89
- });
90
- for (let cursor = entries.length - 1; cursor >= 0; cursor -= 1) {
91
- const entry = entries[cursor];
97
+ for (let index = current.length - 1; index >= 0; index -= 1) {
98
+ const entry = current[index];
92
99
  stack.push({
93
100
  kind: "visit",
94
- value: entry[1],
95
- link: { parent: task.link, segment: entry[0] },
101
+ value: entry === undefined ? null : entry,
102
+ link: { parent: task.link, segment: index },
96
103
  emit: (child) => {
97
- normalized.push(child);
104
+ normalized[index] = child;
98
105
  },
99
106
  });
100
107
  }
@@ -112,7 +119,7 @@ export function normalizeJsonValue(value, path, seen) {
112
119
  const normalized = {};
113
120
  task.emit(normalized);
114
121
  stack.push({ kind: "leave", container: current });
115
- const entries = Object.entries(current);
122
+ const entries = Object.entries(current).filter((entry) => entry[1] !== undefined);
116
123
  for (let cursor = entries.length - 1; cursor >= 0; cursor -= 1) {
117
124
  const entry = entries[cursor];
118
125
  stack.push({
@@ -231,10 +238,9 @@ function normalizeScalarAtom(contract, field, value, origin, path, allowNull) {
231
238
  break;
232
239
  case "decimal":
233
240
  if (origin === "application") {
234
- normalized =
235
- value instanceof AvDecimal
236
- ? { ok: true, value }
237
- : { ok: false, issues: [typeIssue(path, "an Aventara Decimal")] };
241
+ normalized = isAvDecimal(value)
242
+ ? { ok: true, value }
243
+ : { ok: false, issues: [typeIssue(path, "an Aventara Decimal")] };
238
244
  }
239
245
  else if (typeof value !== "string") {
240
246
  normalized = {
@@ -359,9 +365,21 @@ export function normalizeScalarFieldValue(contract, field, value, origin, path)
359
365
  });
360
366
  return issues.length > 0
361
367
  ? { ok: false, issues }
362
- : { ok: true, value: normalized };
368
+ : heldToFormat(field, { ok: true, value: normalized }, origin, path);
363
369
  }
364
- return normalizeScalarAtom(contract, field, value, origin === "runtime" ? "application" : origin, path, field.nullable);
370
+ return heldToFormat(field, normalizeScalarAtom(contract, field, value, origin === "runtime" ? "application" : origin, path, field.nullable), origin, path);
371
+ }
372
+ function heldToFormat(field, normalized, origin, path) {
373
+ const format = field.format;
374
+ if (origin !== "runtime" ||
375
+ !normalized.ok ||
376
+ format === undefined ||
377
+ format === "uuid" ||
378
+ !isNamedFormat(format)) {
379
+ return normalized;
380
+ }
381
+ const issues = fieldCheckIssues([{ check: "string_format", format }], normalized.value, "string", path, field.list);
382
+ return issues.length === 0 ? normalized : { ok: false, issues };
365
383
  }
366
384
  export function jsonShapeIssues(field, value, path, position = "value") {
367
385
  if (field.json === undefined || value === null || value === undefined) {
@@ -427,6 +445,11 @@ export function copyScalarFieldValue(field, value) {
427
445
  function normalizeListElement(contract, field, value, origin, path) {
428
446
  return normalizeScalarAtom(contract, field, value, origin, path, false);
429
447
  }
448
+ function normalizeFilterEntry(contract, field, value, origin, path) {
449
+ return value === undefined
450
+ ? { ok: false, issues: [unsentReadValueIssue(path)] }
451
+ : normalizeListElement(contract, field, value, origin, path);
452
+ }
430
453
  function mergeResult(result, issues) {
431
454
  if (result.ok) {
432
455
  return result.value;
@@ -434,7 +457,7 @@ function mergeResult(result, issues) {
434
457
  issues.push(...result.issues);
435
458
  return undefined;
436
459
  }
437
- export const REFERENCE_OPERANDS = Object.freeze({
460
+ const REFERENCE_OPERANDS = Object.freeze({
438
461
  equals: "value",
439
462
  not: "value",
440
463
  lt: "nonNullValue",
@@ -463,10 +486,40 @@ const NULL_OPERAND_OPERATORS = new Set([
463
486
  "arrayStartsWith",
464
487
  "arrayEndsWith",
465
488
  ]);
489
+ const KEYWORD_OPERANDS = Object.freeze({
490
+ mode: (operand, path, issues) => {
491
+ if (!FILTER_MODES.includes(operand)) {
492
+ issues.push(typeIssue(path, '"default", "sensitive" or "insensitive"'));
493
+ }
494
+ },
495
+ isEmpty: (operand, path, issues) => {
496
+ if (typeof operand !== "boolean") {
497
+ issues.push(typeIssue(path, "a boolean"));
498
+ }
499
+ },
500
+ path: (operand, path, issues) => {
501
+ if (!Array.isArray(operand)) {
502
+ issues.push(typeIssue(path, "an array of strings"));
503
+ return;
504
+ }
505
+ for (let index = 0; index < operand.length; index += 1) {
506
+ if (operand[index] === undefined) {
507
+ issues.push(unsentReadValueIssue([...path, index]));
508
+ }
509
+ else if (typeof operand[index] !== "string") {
510
+ issues.push(typeIssue([...path, index], "a string"));
511
+ }
512
+ }
513
+ },
514
+ });
466
515
  function normalizeScalarFilter(contract, field, value, origin, path, issues, references = false) {
467
516
  if (isKeptReference(references, value)) {
468
517
  return value;
469
518
  }
519
+ if (value === undefined) {
520
+ issues.push(unsentReadValueIssue(path));
521
+ return value;
522
+ }
470
523
  if (!isPlainRecord(value)) {
471
524
  return mergeResult(normalizeScalarFieldValue(contract, field, value, origin, path), issues);
472
525
  }
@@ -477,11 +530,17 @@ function normalizeScalarFilter(contract, field, value, origin, path, issues, ref
477
530
  const normalized = cloneRecord(value);
478
531
  for (const [operator, operand] of Object.entries(value)) {
479
532
  const operandPath = [...path, operator];
533
+ if (operand === undefined) {
534
+ issues.push(unsentReadValueIssue(operandPath));
535
+ continue;
536
+ }
480
537
  if (operand === null && !acceptsNullOperand(operator)) {
481
538
  issues.push(typeIssue(operandPath, "non-null"));
482
539
  continue;
483
540
  }
484
- if (operator === "mode" || operator === "isEmpty" || operator === "path") {
541
+ const keyword = lookupOwn(KEYWORD_OPERANDS, operator);
542
+ if (keyword !== undefined) {
543
+ keyword(operand, operandPath, issues);
485
544
  continue;
486
545
  }
487
546
  if (referenceOperandShape(operator) !== undefined &&
@@ -497,14 +556,14 @@ function normalizeScalarFilter(contract, field, value, origin, path, issues, ref
497
556
  issues.push(typeIssue(operandPath, "an array"));
498
557
  continue;
499
558
  }
500
- normalized[operator] = operand.map((entry, index) => mergeResult(normalizeListElement(contract, field, entry, origin, [
559
+ normalized[operator] = operand.map((entry, index) => mergeResult(normalizeFilterEntry(contract, field, entry, origin, [
501
560
  ...operandPath,
502
561
  index,
503
562
  ]), issues));
504
563
  continue;
505
564
  }
506
565
  if (operator === "has") {
507
- normalized[operator] = mergeResult(normalizeListElement(contract, field, operand, origin, operandPath), issues);
566
+ normalized[operator] = mergeResult(normalizeFilterEntry(contract, field, operand, origin, operandPath), issues);
508
567
  continue;
509
568
  }
510
569
  if (operator === "hasEvery" || operator === "hasSome") {
@@ -512,7 +571,7 @@ function normalizeScalarFilter(contract, field, value, origin, path, issues, ref
512
571
  issues.push(typeIssue(operandPath, "an array"));
513
572
  continue;
514
573
  }
515
- normalized[operator] = operand.map((entry, index) => mergeResult(normalizeListElement(contract, field, entry, origin, [
574
+ normalized[operator] = operand.map((entry, index) => mergeResult(normalizeFilterEntry(contract, field, entry, origin, [
516
575
  ...operandPath,
517
576
  index,
518
577
  ]), issues));
@@ -521,6 +580,12 @@ function normalizeScalarFilter(contract, field, value, origin, path, issues, ref
521
580
  if ("scalar" in field.type &&
522
581
  field.type.scalar === "json" &&
523
582
  (operator.startsWith("string") || operator.startsWith("array"))) {
583
+ if (operator.startsWith("string")) {
584
+ if (typeof operand !== "string") {
585
+ issues.push(typeIssue(operandPath, "a string"));
586
+ }
587
+ continue;
588
+ }
524
589
  const jsonResult = normalizeJsonValue(operand, operandPath, new Set());
525
590
  normalized[operator] = mergeResult(jsonResult, issues);
526
591
  continue;
@@ -585,6 +650,10 @@ function normalizeUniqueSelector(contract, resourceName, selector, origin, path,
585
650
  const normalized = cloneRecord(selector);
586
651
  for (const [fieldName, value] of Object.entries(selector)) {
587
652
  const field = lookupField(resource, fieldName);
653
+ if (field?.kind === "scalar" && value === undefined) {
654
+ issues.push(unsentReadValueIssue([...path, fieldName]));
655
+ continue;
656
+ }
588
657
  if (field?.kind === "scalar" && !isKeptReference(references, value)) {
589
658
  normalized[fieldName] = mergeResult(normalizeScalarFieldValue(contract, field, value, origin, [
590
659
  ...path,
@@ -0,0 +1,15 @@
1
+ import type { AvContract } from "../contracts/contract.js";
2
+ import type { AvOperationFamily } from "../operations/operation-identity.js";
3
+ /**
4
+ * One operation's arguments with every write record's unsent fields removed — a
5
+ * field whose value is `undefined`, at any depth of nested write data the
6
+ * Contract's `maxNestingDepth` admits — and
7
+ * nothing else changed (see the module note): `where`, selectors and every other
8
+ * member are left as they are. The same object when nothing was unsent; otherwise
9
+ * a copy of each container on the way, never a write into `args`. Total over
10
+ * malformed input.
11
+ */
12
+ export declare function withoutUnsentWriteValues(contract: AvContract, request: {
13
+ readonly resource: string;
14
+ readonly family: AvOperationFamily;
15
+ }, args: unknown): unknown;