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

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 (110) hide show
  1. package/CHANGELOG.md +79 -0
  2. package/ERRORS.md +82 -82
  3. package/README.md +57 -1
  4. package/dist/compiler/contract-compiler-diagnostic.guidance.js +1 -1
  5. package/dist/compiler/contract.compiler.js +56 -9
  6. package/dist/compiler/create-requirements.projector.js +1 -3
  7. package/dist/compiler/field-validator.compiler.js +1 -3
  8. package/dist/compiler/managed-elsewhere.projector.js +5 -5
  9. package/dist/config/cache-stage.factory.d.ts +24 -0
  10. package/dist/config/cache-stage.factory.js +3 -0
  11. package/dist/config/config.resolver.js +1 -7
  12. package/dist/config/config.validator.js +61 -13
  13. package/dist/config/exact-framework-config.d.ts +6 -0
  14. package/dist/config/field-compute.resolver.js +1 -3
  15. package/dist/config/pipeline-config.d.ts +16 -0
  16. package/dist/config/pipeline-tree.resolver.js +2 -3
  17. package/dist/contracts/canonical-json.writer.d.ts +7 -0
  18. package/dist/contracts/canonical-json.writer.js +55 -0
  19. package/dist/contracts/client-contract-structure.validator.d.ts +64 -0
  20. package/dist/contracts/client-contract-structure.validator.js +12 -0
  21. package/dist/contracts/contract-canonicalizer.js +5 -0
  22. package/dist/contracts/contract-limits.d.ts +6 -0
  23. package/dist/contracts/contract-limits.js +2 -0
  24. package/dist/contracts/contract.d.ts +15 -0
  25. package/dist/contracts/json-schema.projector.js +1 -3
  26. package/dist/framework/access-facade.js +1 -3
  27. package/dist/framework/framework.factory.js +42 -13
  28. package/dist/framework/operation-facade.d.ts +67 -6
  29. package/dist/framework/operation-facade.js +7 -0
  30. package/dist/framework/operation-set.runner.d.ts +47 -0
  31. package/dist/framework/operation-set.runner.js +168 -0
  32. package/dist/index.d.ts +2 -0
  33. package/dist/index.js +2 -0
  34. package/dist/internal.d.ts +2 -0
  35. package/dist/internal.js +2 -0
  36. package/dist/operations/operation-validator-core.js +2 -4
  37. package/dist/operations/structural-limits.walker.js +1 -3
  38. package/dist/policy/access-question.runtime.js +1 -3
  39. package/dist/policy/policy-admission.js +1 -3
  40. package/dist/policy/policy-after-update.runtime.js +1 -3
  41. package/dist/policy/policy-nested-write.runtime.js +3 -12
  42. package/dist/policy/policy-read.runtime.js +1 -3
  43. package/dist/policy/policy-reference-write.runtime.js +2 -11
  44. package/dist/policy/policy-request.d.ts +31 -0
  45. package/dist/policy/policy-request.js +69 -17
  46. package/dist/policy/policy-session.js +1 -3
  47. package/dist/policy/policy-write.runtime.js +1 -3
  48. package/dist/prebuilt/core-version.generated.d.ts +1 -1
  49. package/dist/prebuilt/core-version.generated.js +1 -1
  50. package/dist/prebuilt/prebuilt-changes.js +1 -3
  51. package/dist/prebuilt/prebuilt-contracts.loader.js +1 -3
  52. package/dist/prebuilt/prebuilt-inputs.fingerprint.js +1 -3
  53. package/dist/protocol/absent-route.responder.d.ts +69 -16
  54. package/dist/protocol/absent-route.responder.js +75 -31
  55. package/dist/protocol/contract-document.responder.d.ts +7 -0
  56. package/dist/protocol/contract-document.responder.js +3 -2
  57. package/dist/protocol/http-request.decoder.d.ts +33 -0
  58. package/dist/protocol/http-request.decoder.js +23 -7
  59. package/dist/protocol/http-request.reader.d.ts +4 -0
  60. package/dist/protocol/http-request.reader.js +11 -2
  61. package/dist/protocol/http-response.encoder.d.ts +12 -0
  62. package/dist/protocol/http-response.encoder.js +15 -1
  63. package/dist/protocol/operation-response.guard.js +1 -3
  64. package/dist/protocol/operation-set.decoder.d.ts +48 -0
  65. package/dist/protocol/operation-set.decoder.js +185 -0
  66. package/dist/protocol/operation-set.handler.d.ts +27 -0
  67. package/dist/protocol/operation-set.handler.js +179 -0
  68. package/dist/protocol/protocol-headers.d.ts +9 -0
  69. package/dist/protocol/protocol-headers.js +12 -5
  70. package/dist/protocol/protocol-surface.d.ts +12 -4
  71. package/dist/protocol/protocol-surface.js +34 -1
  72. package/dist/protocol/protocol.binder.d.ts +37 -1
  73. package/dist/protocol/protocol.binder.js +2 -0
  74. package/dist/protocol/protocol.handler.d.ts +35 -2
  75. package/dist/protocol/protocol.handler.js +84 -8
  76. package/dist/protocol/read-cache.responder.d.ts +26 -0
  77. package/dist/protocol/read-cache.responder.js +47 -0
  78. package/dist/protocol/read-query.codec.d.ts +34 -0
  79. package/dist/protocol/read-query.codec.js +125 -0
  80. package/dist/runtime/computed-read-runtime.js +2 -8
  81. package/dist/runtime/create-requirement.js +1 -3
  82. package/dist/runtime/diagnostics-reporter.d.ts +6 -1
  83. package/dist/runtime/diagnostics-reporter.js +3 -8
  84. package/dist/runtime/error.catalog.d.ts +2 -2
  85. package/dist/runtime/error.catalog.js +1 -1
  86. package/dist/runtime/field-stage.runtime.d.ts +0 -2
  87. package/dist/runtime/field-stage.runtime.js +1 -7
  88. package/dist/runtime/field-validator.runtime.js +2 -1
  89. package/dist/runtime/mutation-planner.js +1 -3
  90. package/dist/runtime/nested-write.walker.js +2 -1
  91. package/dist/runtime/output-validator.js +1 -3
  92. package/dist/runtime/owned-execution.registry.d.ts +22 -2
  93. package/dist/runtime/pipeline.d.ts +5 -1
  94. package/dist/runtime/record.guard.d.ts +14 -0
  95. package/dist/runtime/record.guard.js +10 -0
  96. package/dist/runtime/request-envelope.validator.js +13 -10
  97. package/dist/runtime/scalar-runtime.d.ts +0 -10
  98. package/dist/runtime/scalar-runtime.js +1 -10
  99. package/dist/runtime/thenable.guard.d.ts +7 -0
  100. package/dist/runtime/thenable.guard.js +5 -0
  101. package/dist/runtime/unsent-value.normalizer.js +2 -1
  102. package/dist/transactions/operation-arguments-encoder.js +1 -7
  103. package/dist/transactions/sql-statement.validator.js +2 -1
  104. package/dist/transactions/transaction-plan-assembler.js +1 -7
  105. package/dist/transactions/transaction-plan-validator.js +4 -6
  106. package/dist/transactions/transaction-reference-collector.js +8 -10
  107. package/dist/transactions/transaction-reference-resolver.js +1 -3
  108. package/dist/transactions/transaction-runtime.js +31 -2
  109. package/dist/transactions/transaction-stage.runtime.js +1 -3
  110. package/package.json +1 -1
@@ -12,6 +12,10 @@ export type AvContractLimits = {
12
12
  readonly maxBooleanNodes: number;
13
13
  /** How deeply a `json` field's value may nest: its arrays and objects, counted from the value. */
14
14
  readonly maxJsonDepth: number;
15
+ /** The most operations one Operation Set (`POST /_all`) may hold. */
16
+ readonly maxAll: number;
17
+ /** The most entries of one Operation Set that run at once; at least 1. */
18
+ readonly maxAllConcurrency: number;
15
19
  };
16
20
  /** The limits in force when the configuration sets none. */
17
21
  export declare const DEFAULT_CONTRACT_LIMITS: {
@@ -21,4 +25,6 @@ export declare const DEFAULT_CONTRACT_LIMITS: {
21
25
  readonly maxTransactionOperations: 20;
22
26
  readonly maxBooleanNodes: 50;
23
27
  readonly maxJsonDepth: 64;
28
+ readonly maxAll: 10;
29
+ readonly maxAllConcurrency: 4;
24
30
  };
@@ -5,4 +5,6 @@ export const DEFAULT_CONTRACT_LIMITS = {
5
5
  maxTransactionOperations: 20,
6
6
  maxBooleanNodes: 50,
7
7
  maxJsonDepth: 64,
8
+ maxAll: 10,
9
+ maxAllConcurrency: 4,
8
10
  };
@@ -56,6 +56,21 @@ export interface AvContractApplication extends AvContract {
56
56
  * identified by its hash.
57
57
  */
58
58
  export interface AvContractClient extends AvContract {
59
+ /**
60
+ * The Resources, by name. One with a cached read carries `cache`, by `find`
61
+ * variant: that read is also served as
62
+ * `GET /_resources/<resource>/find/<variant>?q=…&v=…`, which a browser
63
+ * (`for: "user"`), a CDN for anonymous callers (`"anonymous"`) or a CDN for
64
+ * everyone (`"everyone"`) may keep for `maxAge` seconds.
65
+ */
66
+ readonly resources: Readonly<Record<string, AvResource<this> & {
67
+ readonly cache?: {
68
+ readonly [variant in "first" | "unique" | "many" | "count"]?: {
69
+ readonly for: "user" | "anonymous" | "everyone";
70
+ readonly maxAge: number;
71
+ };
72
+ };
73
+ }>>;
59
74
  /** The protocol version and this Contract's hash. */
60
75
  readonly protocol: AvContractClientProtocol;
61
76
  /**
@@ -1,6 +1,4 @@
1
- function isRecord(value) {
2
- return typeof value === "object" && value !== null && !Array.isArray(value);
3
- }
1
+ import { isRecord } from "../runtime/record.guard.js";
4
2
  function memberSchema(schema) {
5
3
  if (!isRecord(schema)) {
6
4
  throw new TypeError("the schema is not one of the forms a ClientContract states");
@@ -1,6 +1,7 @@
1
1
  import { projectJsonValue } from "../contracts/json-schema.projector.js";
2
2
  import { ACCESS_VERB_NAMES, ACCESS_VERBS, providerVerb, } from "../policy/access-verb.table.js";
3
3
  import { copyAtDoor } from "../runtime/caller-data.door.js";
4
+ import { isRecord } from "../runtime/record.guard.js";
4
5
  import { requestIdOf } from "../runtime/request-id.js";
5
6
  import { stateOptionOf } from "../runtime/request-state.resolver.js";
6
7
  import { encodeOperationArgumentsForWire } from "../transactions/operation-arguments-encoder.js";
@@ -63,9 +64,6 @@ function stateOf(options) {
63
64
  const state = stateOptionOf(options);
64
65
  return state === undefined ? {} : { state };
65
66
  }
66
- function isRecord(value) {
67
- return typeof value === "object" && value !== null && !Array.isArray(value);
68
- }
69
67
  function wireQuestions(contract, questions) {
70
68
  const copy = copyAtDoor({ questions });
71
69
  if (!copy.ok) {
@@ -9,7 +9,7 @@ import { recordAccessProvider } from "../policy/access-provider.registry.js";
9
9
  import { recordAccessQuestions } from "../policy/access-question.registry.js";
10
10
  import { createAccessQuestionAnswerer } from "../policy/access-question.runtime.js";
11
11
  import { heldTargetsOf } from "../policy/policy-reference-write.runtime.js";
12
- import { callerOf } from "../policy/policy-request.js";
12
+ import { callerOf, derivePolicyCaller, } from "../policy/policy-request.js";
13
13
  import { policyStartupDiagnostics } from "../policy/policy-startup.validator.js";
14
14
  import { loadPrebuiltContracts, warnStalePrebuilt, } from "../prebuilt/prebuilt-contracts.loader.js";
15
15
  import { copyAtDoor, copyEnvelopeAtDoor, refuseCallerData, } from "../runtime/caller-data.door.js";
@@ -26,7 +26,8 @@ import { createTransactionPlanAssembler } from "../transactions/transaction-plan
26
26
  import { createTransactionRuntime } from "../transactions/transaction-runtime.js";
27
27
  import { createServerClientAccess } from "./access-facade.js";
28
28
  import { AvConstructionError } from "./framework-construction.error.js";
29
- import { createOperationFacade, createOperationTree, settleOperation, } from "./operation-facade.js";
29
+ import { createAbortableOperationFacade, createOperationFacade, createOperationTree, settleOperation, } from "./operation-facade.js";
30
+ import { runOperationSet } from "./operation-set.runner.js";
30
31
  const APPLICATION_SQL_BUILDER = "$sql";
31
32
  export async function createFramework(config) {
32
33
  const configDiagnostics = validateFrameworkConfig(config);
@@ -55,6 +56,10 @@ export async function createFramework(config) {
55
56
  if (!compilation.ok) {
56
57
  throw new AvConstructionError("contract-compilation", compilation.diagnostics);
57
58
  }
59
+ const clashes = serverClientClashes(compilation.contracts);
60
+ if (clashes.length > 0) {
61
+ throw new AvConstructionError("contract-compilation", clashes);
62
+ }
58
63
  const forcing = validateForcedOperations(config, compilation.contracts);
59
64
  if (forcing.length > 0) {
60
65
  throw new AvConstructionError("configuration", forcing);
@@ -79,7 +84,7 @@ export async function createFramework(config) {
79
84
  const executeTransaction = createTransactionRuntime(runtimeDependencies, pipelineDependencies);
80
85
  const misuse = createMisuseReporter(resolvedConfig.misuse);
81
86
  const executeOperation = (request, caller) => executeTotally(execute, request, resolvedConfig.diagnostics, "caller", caller);
82
- const executeApplicationValues = (request, caller) => executeTotally(encodingApplicationValues(execute, compilation.contracts.client), request, resolvedConfig.diagnostics, "caller", caller);
87
+ const prepareApplicationValues = (request, caller) => ((copy) => () => executeTotally(encodingApplicationValues(execute, compilation.contracts.client, copy), request, resolvedConfig.diagnostics, "caller", caller))(copyArgumentsNow(request.arguments));
83
88
  const protocol = {
84
89
  version: AVENTARA_PROTOCOL_VERSION,
85
90
  hash: compilation.contracts.client.protocol.hash,
@@ -116,7 +121,7 @@ export async function createFramework(config) {
116
121
  arguments: args,
117
122
  }), executeOperation),
118
123
  client: createOperationFacade(model, clientRequest, executeOperation),
119
- serverClient: withServerClientAccess(createOperationFacade(model, clientRequest, executeApplicationValues), accessAnswerer === undefined
124
+ serverClient: withServerClientMembers(createAbortableOperationFacade(model, clientRequest, prepareApplicationValues), (entries, options) => runOperationSet(entries, options, compilation.contracts.client.limits), accessAnswerer === undefined
120
125
  ? undefined
121
126
  : createServerClientAccess({
122
127
  contract: compilation.contracts.client,
@@ -140,7 +145,8 @@ export async function createFramework(config) {
140
145
  recordAccessQuestions(framework, accessAnswerer);
141
146
  }
142
147
  recordOwnedExecution(framework, {
143
- execute: (request, refused) => executeTotally(execute, request, resolvedConfig.diagnostics, refused === undefined ? "owned" : { refused }),
148
+ execute: (request, refused, member) => executeTotally(execute, request, resolvedConfig.diagnostics, refused === undefined ? "owned" : { refused }, member?.caller, member?.key),
149
+ caller: (request) => derivePolicyCaller(resolvedConfig.policy, request),
144
150
  executeTransaction: (request, refused) => executeTransactionTotally(executeTransaction, request, resolvedConfig.diagnostics, undefined, refused === undefined
145
151
  ? { source: "owned" }
146
152
  : { source: "owned", refused }),
@@ -169,9 +175,19 @@ async function prebuiltCompilation(prebuilt, model, config, resolved) {
169
175
  }
170
176
  return { ok: true, contracts: loaded.contracts };
171
177
  }
172
- function encodingApplicationValues(execute, contract) {
178
+ function copyArgumentsNow(args) {
179
+ try {
180
+ return copyAtDoor(args);
181
+ }
182
+ catch (thrown) {
183
+ return { thrown };
184
+ }
185
+ }
186
+ function encodingApplicationValues(execute, contract, copy) {
173
187
  return (request, report, _source, caller) => {
174
- const copy = copyAtDoor(request.arguments);
188
+ if ("thrown" in copy) {
189
+ throw copy.thrown;
190
+ }
175
191
  if (!copy.ok) {
176
192
  return execute(request, report, { refused: copy.error }, caller);
177
193
  }
@@ -181,8 +197,8 @@ function encodingApplicationValues(execute, contract) {
181
197
  }, report, "owned", caller);
182
198
  };
183
199
  }
184
- async function executeTotally(execute, request, diagnostics, source, caller) {
185
- const report = createDiagnosticsReporter(diagnostics, request);
200
+ async function executeTotally(execute, request, diagnostics, source, caller, key) {
201
+ const report = createDiagnosticsReporter(diagnostics, request, undefined, key);
186
202
  try {
187
203
  return await execute(request, report, source, caller);
188
204
  }
@@ -263,8 +279,21 @@ async function runTransactionTotally(execute, assembler, operations, requestId,
263
279
  statements: assembled.statements,
264
280
  }, caller);
265
281
  }
266
- function withServerClientAccess(facade, access) {
267
- return access === undefined
268
- ? facade
269
- : Object.freeze({ ...facade, $access: access });
282
+ function withServerClientMembers(facade, all, access) {
283
+ return Object.freeze({
284
+ ...facade,
285
+ all,
286
+ ...(access === undefined ? {} : { $access: access }),
287
+ });
288
+ }
289
+ function serverClientClashes(contracts) {
290
+ return Object.hasOwn(contracts.client.resources, "all")
291
+ ? [
292
+ {
293
+ code: "COMPILER_RESERVED_RESOURCE_NAME",
294
+ path: "adapter.model.resources.all",
295
+ message: 'resource "all" clashes with serverClient.all; rename the Resource or expose it under another name',
296
+ },
297
+ ]
298
+ : [];
270
299
  }
@@ -39,13 +39,29 @@ export type AvAppTxPlainCall<in out C extends AvContract, in out R extends AvRes
39
39
  export type AvClientTxProjectingCall<in out C extends AvContract, in out R extends AvResourceKey<C>, in out I extends AvOperationIdentity> = DeferredProjectingCall<C, R, I, DeferredArguments<C, R, I, AvWireScalarForms, AvApplicationScalarForms>, AvApplicationScalarForms, "client">;
40
40
  /** `framework.clientTx.<Resource>.<family>.<variant>` when it does not. */
41
41
  export type AvClientTxPlainCall<in out C extends AvContract, in out R extends AvResourceKey<C>, in out I extends AvOperationIdentity> = DeferredPlainCall<C, R, I, DeferredArguments<C, R, I, AvWireScalarForms, AvApplicationScalarForms>, AvApplicationScalarForms, "client">;
42
+ /** A `serverClient` call's options: {@link AvExecutionOptions}, and the call's own abort signal. */
43
+ type AvServerClientCallOptions = AvExecutionOptions & {
44
+ /**
45
+ * Aborts this call only. Aborted before the call runs, it never runs;
46
+ * while it runs, it rejects with the signal's reason at once, and the
47
+ * database finishes what it started. Any platform's `AbortSignal` is one.
48
+ */
49
+ readonly signal?: {
50
+ readonly aborted: boolean;
51
+ readonly reason: unknown;
52
+ addEventListener(type: "abort", listener: () => void, options?: {
53
+ readonly once?: boolean;
54
+ }): void;
55
+ removeEventListener(type: "abort", listener: () => void): void;
56
+ };
57
+ };
42
58
  /**
43
59
  * `framework.serverClient.<Resource>.<family>.<variant>` when the variant takes a `select` or `include`:
44
60
  * the Client Contract's view, application forms in.
45
61
  */
46
- export type AvServerClientProjectingCall<in out C extends AvContract, in out R extends AvResourceKey<C>, in out I extends AvOperationIdentity> = ProjectingCall<C, R, I, AvOperationArgumentsFor<C, R, I, AvApplicationScalarForms>, AvApplicationScalarForms, AvExecutionOptions>;
62
+ export type AvServerClientProjectingCall<in out C extends AvContract, in out R extends AvResourceKey<C>, in out I extends AvOperationIdentity> = ProjectingCall<C, R, I, AvOperationArgumentsFor<C, R, I, AvApplicationScalarForms>, AvApplicationScalarForms, AvServerClientCallOptions>;
47
63
  /** `framework.serverClient.<Resource>.<family>.<variant>` when it does not. */
48
- export type AvServerClientPlainCall<in out C extends AvContract, in out R extends AvResourceKey<C>, in out I extends AvOperationIdentity> = PlainCall<C, R, I, AvOperationArgumentsFor<C, R, I, AvApplicationScalarForms>, AvApplicationScalarForms, AvExecutionOptions>;
64
+ export type AvServerClientPlainCall<in out C extends AvContract, in out R extends AvResourceKey<C>, in out I extends AvOperationIdentity> = PlainCall<C, R, I, AvOperationArgumentsFor<C, R, I, AvApplicationScalarForms>, AvApplicationScalarForms, AvServerClientCallOptions>;
49
65
  /**
50
66
  * `framework.serverClientTx.<Resource>.<family>.<variant>` when the variant takes a `select` or `include`:
51
67
  * the Client Contract's view, application forms in, a client-origin handle out.
@@ -117,13 +133,50 @@ export type AvApplicationFacade<C extends AvContract> = {
117
133
  export type AvClientFacade<C extends AvContract> = {
118
134
  readonly [R in AvResourceKey<C>]: AvClientResourceFacade<C, R>;
119
135
  };
136
+ /** `framework.serverClient.all`: its own member beside the Resources. */
137
+ type ServerClientAll = {
138
+ /**
139
+ * Awaits an object of calls together, in process (any number of them:
140
+ * `limits.maxAll` guards the HTTP door only), and resolves one value per key. A call already runs when it is made;
141
+ * an `avLazy(() => call)` entry is started by `all`, at most
142
+ * `limits.maxAllConcurrency` at once. Strict: the first failure rejects it
143
+ * and its `avLazy` entries not yet started never start (fail-fast, not
144
+ * atomic: what already ran stays done). With `settle: true` every key settles as `{ status, value }`
145
+ * or `{ status, reason }`. Its `signal` aborts each of its calls and no other.
146
+ */
147
+ readonly all: <E extends {
148
+ readonly [key: string]: PromiseLike<unknown>;
149
+ }, S extends boolean = false>(entries: E, options?: {
150
+ /** Aborts every call of this `all`, and no other. Any platform's `AbortSignal` is one. */
151
+ readonly signal?: {
152
+ readonly aborted: boolean;
153
+ readonly reason: unknown;
154
+ addEventListener(type: "abort", listener: () => void, options?: {
155
+ readonly once?: boolean;
156
+ }): void;
157
+ removeEventListener(type: "abort", listener: () => void): void;
158
+ };
159
+ /** true settles every key on its own and never rejects for one; strict otherwise. */
160
+ readonly settle?: S;
161
+ }) => Promise<S extends true ? {
162
+ -readonly [K in keyof E]: {
163
+ readonly status: "fulfilled";
164
+ readonly value: Awaited<E[K]>;
165
+ } | {
166
+ readonly status: "rejected";
167
+ readonly reason: unknown;
168
+ };
169
+ } : {
170
+ -readonly [K in keyof E]: Awaited<E[K]>;
171
+ }>;
172
+ };
120
173
  /**
121
174
  * `framework.serverClient`: one {@link AvServerClientResourceFacade} per Resource
122
- * of `C`, the Client Contract's view, taking application forms.
175
+ * of `C`, the Client Contract's view, taking application forms, and `all`.
123
176
  */
124
177
  export type AvServerClientFacade<C extends AvContract> = {
125
178
  readonly [R in AvResourceKey<C>]: AvServerClientResourceFacade<C, R>;
126
- };
179
+ } & ServerClientAll;
127
180
  /** `framework.appTx.$sql`: the developer SQL step only an application transaction has. */
128
181
  type ApplicationSqlStep = {
129
182
  /**
@@ -156,7 +209,7 @@ export type AvServerClientTxFacade<C extends AvContract> = {
156
209
  };
157
210
  type ApplicationSurface<C> = C extends AvContract ? string extends AvResourceKey<C> ? OpaqueSurface : AvApplicationFacade<C> : never;
158
211
  type ClientSurface<C> = C extends AvContract ? string extends AvResourceKey<C> ? OpaqueSurface : AvClientFacade<C> : never;
159
- type ServerClientSurface<C> = C extends AvContract ? string extends AvResourceKey<C> ? OpaqueSurface : AvServerClientFacade<C> : never;
212
+ type ServerClientSurface<C> = C extends AvContract ? string extends AvResourceKey<C> ? OpaqueSurface & ServerClientAll : AvServerClientFacade<C> : never;
160
213
  type ApplicationDeferredSurface<C> = C extends AvContract ? string extends AvResourceKey<C> ? OpaqueSurface & ApplicationSqlStep : AvAppTxFacade<C> : never;
161
214
  type ClientDeferredSurface<C> = C extends AvContract ? string extends AvResourceKey<C> ? OpaqueSurface : AvClientTxFacade<C> : never;
162
215
  type ServerClientDeferredSurface<C> = C extends AvContract ? string extends AvResourceKey<C> ? OpaqueSurface : AvServerClientTxFacade<C> : never;
@@ -164,7 +217,7 @@ type ServerClientDeferredSurface<C> = C extends AvContract ? string extends AvRe
164
217
  export type ApplicationOperations<M extends AvAdapterModel, Cfg> = ApplicationSurface<AvContractApplicationView<M, Cfg>>;
165
218
  /** `framework.client`'s type: {@link AvClientFacade}, or opaque. */
166
219
  export type ClientOperations<M extends AvAdapterModel, Cfg> = ClientSurface<ClientContractView<M, Cfg>>;
167
- /** `framework.serverClient`'s type: {@link AvServerClientFacade}, or opaque, and `$access`. */
220
+ /** `framework.serverClient`'s type: {@link AvServerClientFacade}, or opaque with `all`, and `$access`. */
168
221
  export type ServerClientOperations<M extends AvAdapterModel, Cfg> = ServerClientSurface<ClientContractView<M, Cfg>> & ServerClientAccess<ClientContractView<M, Cfg>, AccessProviderOf<Cfg>>;
169
222
  /** `framework.appTx`'s type: {@link AvAppTxFacade}, or opaque with `$sql`. */
170
223
  export type ApplicationTransactionBuilders<M extends AvAdapterModel, Cfg> = ApplicationDeferredSurface<AvContractApplicationView<M, Cfg>>;
@@ -217,6 +270,14 @@ export declare function createOperationFacade<Facade>(model: AvAdapterModel, req
217
270
  * the response's data, or its failure thrown.
218
271
  */
219
272
  export declare function settleOperation(response: AvOperationResponse | AvTransactionResponse): unknown;
273
+ /** Prepares one call's run: its arguments are read when the call is made. */
274
+ type FacadePreparer = (request: AvOperationRequest, caller: PolicyCaller | undefined) => () => Promise<AvOperationResponse>;
275
+ /**
276
+ * {@link createOperationFacade}'s twin for `framework.serverClient`: a call
277
+ * builds its request and runs at once, and the caller's `signal` aborts it
278
+ * (already aborted, it never runs; aborted while it runs, it rejects at once).
279
+ */
280
+ export declare function createAbortableOperationFacade<Facade>(model: AvAdapterModel, request: FacadeRequest, prepare: FacadePreparer): Facade;
220
281
  /**
221
282
  * One leaf of an operation tree: the callable for one advertised variant. The
222
283
  * second parameter is a façade call's options; a builder leaf ignores it.
@@ -4,6 +4,7 @@ import { callerOf } from "../policy/policy-request.js";
4
4
  import { thrownFailure } from "../runtime/framework-error.factory.js";
5
5
  import { requestIdOf } from "../runtime/request-id.js";
6
6
  import { stateOptionOf } from "../runtime/request-state.resolver.js";
7
+ import { runAbortable, signalOptionOf } from "./operation-set.runner.js";
7
8
  export function createOperationFacade(model, request, execute) {
8
9
  return createOperationTree(model, (resource, identity) => async (args, options) => settleOperation(await execute(request(resource, identity, args === undefined ? {} : args, requestIdOf(options), stateOptionOf(options)), callerOf(options))));
9
10
  }
@@ -13,6 +14,12 @@ export function settleOperation(response) {
13
14
  }
14
15
  return response.data;
15
16
  }
17
+ export function createAbortableOperationFacade(model, request, prepare) {
18
+ return createOperationTree(model, (resource, identity) => (args, options) => {
19
+ const run = prepare(request(resource, identity, args === undefined ? {} : args, requestIdOf(options), stateOptionOf(options)), callerOf(options));
20
+ return runAbortable(async () => settleOperation(await run()), signalOptionOf(options));
21
+ });
22
+ }
16
23
  export function createOperationTree(model, leaf) {
17
24
  const resources = Object.entries(model.resources).map(([resource, { operations }]) => {
18
25
  const families = Object.entries(operations).map(([family, variants]) => [family, variantLeaves(leaf, resource, family, variants)]);
@@ -0,0 +1,47 @@
1
+ import type { AvContractLimits } from "../contracts/contract-limits.js";
2
+ /**
3
+ * What core reads of an abort signal: the platform's `AbortSignal` (DOM's,
4
+ * Node's, any runtime's) is one, structurally. Core runs in any JS runtime, so
5
+ * its declarations name no platform global (rc.11): a consumer with neither
6
+ * DOM's lib nor Node's types type-checks them. The public signatures spell this
7
+ * shape inline (`serverClient` calls, `serverClient.all`, the protocol's
8
+ * `handleAll`): no new public name.
9
+ */
10
+ export type AbortSignalShape = {
11
+ readonly aborted: boolean;
12
+ readonly reason: unknown;
13
+ addEventListener(type: "abort", listener: () => void, options?: {
14
+ readonly once?: boolean;
15
+ }): void;
16
+ removeEventListener(type: "abort", listener: () => void): void;
17
+ };
18
+ /** `options.signal` when it is an abort signal; anything else is ignored. */
19
+ export declare function signalOptionOf(options: unknown): AbortSignalShape | undefined;
20
+ /**
21
+ * Runs `run` now under `signal`: an eager call's own abort. Already aborted, it
22
+ * never runs; aborted while it runs, it rejects at once with the signal's reason.
23
+ */
24
+ export declare function runAbortable(run: () => Promise<unknown>, signal: AbortSignalShape | undefined): Promise<unknown>;
25
+ /**
26
+ * A call made later: `avLazy(() => avClient.Post.find.many(args))` makes the
27
+ * call on each `then`, `catch` or `finally`, never before the first, and
28
+ * answers what that call answers. It keeps no answer: awaited again (by another
29
+ * request, say), it makes the call again, in the context awaiting it. Given to
30
+ * `all`, it is started by `all`, at most `limits.maxAllConcurrency` at once,
31
+ * and never when `all` has already failed (strict) or been aborted.
32
+ */
33
+ export declare function avLazy<T>(make: () => PromiseLike<T>): Promise<T>;
34
+ /**
35
+ * The Contract's `limits.maxAllConcurrency`, for `all` here and for `POST /_all`
36
+ * (`protocol/operation-set.handler.ts`). A compiled Contract always holds a
37
+ * whole number of at least 1; a hand-built one that does not runs its entries
38
+ * one at a time, never none.
39
+ */
40
+ export declare function maxAllConcurrencyOf(limits: AvContractLimits): number;
41
+ /**
42
+ * `all(entries, options?)` over `limits` (the Client Contract's): see the module
43
+ * comment. Rejects with a `TypeError`, before anything runs, when `entries` is not
44
+ * an object whose every value is a promise. Any number of entries is accepted:
45
+ * `limits.maxAll` guards the HTTP door only.
46
+ */
47
+ export declare function runOperationSet(entries: unknown, options: unknown, limits: AvContractLimits): Promise<unknown>;
@@ -0,0 +1,168 @@
1
+ import { isThenable } from "../runtime/thenable.guard.js";
2
+ const LAZY_CALLS = new WeakMap();
3
+ const ignore = () => undefined;
4
+ export function signalOptionOf(options) {
5
+ if (typeof options !== "object" || options === null) {
6
+ return undefined;
7
+ }
8
+ const signal = options.signal;
9
+ return typeof signal === "object" &&
10
+ signal !== null &&
11
+ typeof signal.aborted === "boolean" &&
12
+ typeof signal.addEventListener === "function"
13
+ ? signal
14
+ : undefined;
15
+ }
16
+ function start(run, signal) {
17
+ if (signal?.aborted === true) {
18
+ return { result: Promise.reject(signal.reason), done: Promise.resolve() };
19
+ }
20
+ let running;
21
+ try {
22
+ running = run();
23
+ }
24
+ catch (error) {
25
+ running = Promise.reject(error);
26
+ }
27
+ const done = running.then(ignore, ignore);
28
+ if (signal === undefined) {
29
+ return { result: running, done };
30
+ }
31
+ const result = new Promise((resolve, reject) => {
32
+ const aborted = () => reject(signal.reason);
33
+ signal.addEventListener("abort", aborted, { once: true });
34
+ running.then((value) => {
35
+ signal.removeEventListener("abort", aborted);
36
+ resolve(value);
37
+ }, (error) => {
38
+ signal.removeEventListener("abort", aborted);
39
+ reject(error);
40
+ });
41
+ });
42
+ return { result, done };
43
+ }
44
+ export function runAbortable(run, signal) {
45
+ return start(run, signal).result;
46
+ }
47
+ export function avLazy(make) {
48
+ const run = () => Promise.resolve(make());
49
+ const record = {
50
+ begin: (signal) => start(run, signal),
51
+ };
52
+ const result = () => record.begin().result;
53
+ const lazy = Object.freeze({
54
+ then: (onFulfilled, onRejected) => result().then(onFulfilled, onRejected),
55
+ catch: (onRejected) => result().catch(onRejected),
56
+ finally: (onFinally) => result().finally(onFinally),
57
+ [Symbol.toStringTag]: "Promise",
58
+ });
59
+ LAZY_CALLS.set(lazy, record);
60
+ return lazy;
61
+ }
62
+ export function maxAllConcurrencyOf(limits) {
63
+ const limit = limits.maxAllConcurrency;
64
+ return typeof limit === "number" && Number.isSafeInteger(limit) && limit >= 1
65
+ ? limit
66
+ : 1;
67
+ }
68
+ export function runOperationSet(entries, options, limits) {
69
+ if (typeof entries !== "object" ||
70
+ entries === null ||
71
+ Array.isArray(entries)) {
72
+ return Promise.reject(new TypeError("all takes an object of calls: { key: avClient.Resource.find.many(…), … }."));
73
+ }
74
+ const keyed = [];
75
+ for (const key of Object.keys(entries)) {
76
+ const value = entries[key];
77
+ if (!isThenable(value)) {
78
+ return Promise.reject(new TypeError(`all's entry ${JSON.stringify(key)} is not a call.`));
79
+ }
80
+ keyed.push([key, value]);
81
+ }
82
+ const settle = typeof options === "object" &&
83
+ options !== null &&
84
+ options.settle === true;
85
+ const outer = signalOptionOf(options);
86
+ const group = new AbortController();
87
+ const forward = () => {
88
+ group.abort(outer?.reason);
89
+ };
90
+ if (outer?.aborted === true) {
91
+ forward();
92
+ }
93
+ else {
94
+ outer?.addEventListener("abort", forward, { once: true });
95
+ }
96
+ const list = keyed.map(([key, value]) => {
97
+ const record = LAZY_CALLS.get(value);
98
+ let resolve = ignore;
99
+ let reject = ignore;
100
+ const outcome = new Promise((onResolve, onReject) => {
101
+ resolve = onResolve;
102
+ reject = onReject;
103
+ });
104
+ return {
105
+ key,
106
+ value,
107
+ record,
108
+ outcome,
109
+ resolve,
110
+ reject,
111
+ };
112
+ });
113
+ const answer = (entry, result) => {
114
+ result.then((value) => entry.resolve(value), (reason) => {
115
+ entry.reject(reason);
116
+ if (!settle) {
117
+ group.abort();
118
+ }
119
+ });
120
+ };
121
+ const follow = (entry, result) => {
122
+ const aborted = () => entry.reject(group.signal.reason);
123
+ if (group.signal.aborted) {
124
+ aborted();
125
+ }
126
+ else {
127
+ group.signal.addEventListener("abort", aborted, { once: true });
128
+ }
129
+ answer(entry, result);
130
+ };
131
+ for (const entry of list) {
132
+ if (entry.record === undefined) {
133
+ follow(entry, entry.value);
134
+ }
135
+ }
136
+ const queued = list.filter((entry) => entry.record !== undefined);
137
+ const cap = maxAllConcurrencyOf(limits);
138
+ let next = 0;
139
+ let running = 0;
140
+ const launch = () => {
141
+ while (next < queued.length && (running < cap || group.signal.aborted)) {
142
+ const entry = queued[next];
143
+ const record = entry.record;
144
+ next += 1;
145
+ const call = record.begin(group.signal);
146
+ running += 1;
147
+ answer(entry, call.result);
148
+ void Promise.all([call.done, entry.outcome.then(ignore, ignore)]).then(() => {
149
+ running -= 1;
150
+ launch();
151
+ });
152
+ }
153
+ };
154
+ group.signal.addEventListener("abort", launch, { once: true });
155
+ launch();
156
+ const done = (result) => {
157
+ outer?.removeEventListener("abort", forward);
158
+ return result;
159
+ };
160
+ const byKey = (values) => Object.fromEntries(list.map(({ key }, index) => [key, values[index]]));
161
+ if (settle) {
162
+ return Promise.all(list.map(({ outcome }) => outcome.then((value) => ({ status: "fulfilled", value }), (reason) => ({ status: "rejected", reason })))).then((settled) => done(byKey(settled)));
163
+ }
164
+ return Promise.all(list.map(({ outcome }) => outcome)).then((values) => done(byKey(values)), (error) => {
165
+ group.abort();
166
+ throw done(error);
167
+ });
168
+ }
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export type { AvAdapterDiscoveryDiagnostic, AvAdapterDiscoveryDiagnosticCode, }
3
3
  export type { AvAdapterModel } from "./adapters/adapter-model.js";
4
4
  export type { AvContracts } from "./compiler/contract.compiler.js";
5
5
  export type { AvContractCompilerDiagnostic, AvContractCompilerDiagnosticCode, } from "./compiler/contract-compiler-diagnostic.js";
6
+ export { cache } from "./config/cache-stage.factory.js";
6
7
  export type { AvConfigComputedFields } from "./config/computed-fields.js";
7
8
  export type { AvMcpEndpoint } from "./config/config.resolver.js";
8
9
  export type { AvConfigDiagnostic, AvConfigDiagnosticCode, } from "./config/config-diagnostic.js";
@@ -34,6 +35,7 @@ export type { AvFramework } from "./framework/framework.js";
34
35
  export type { AvConstructionDiagnostic, AvConstructionStage, } from "./framework/framework-construction.error.js";
35
36
  export { AvConstructionError } from "./framework/framework-construction.error.js";
36
37
  export type { AvApplicationFacade, AvApplicationFamilyFacade, AvApplicationPlainCall, AvApplicationProjectingCall, AvApplicationResourceFacade, AvAppTxFacade, AvAppTxFamilyFacade, AvAppTxPlainCall, AvAppTxProjectingCall, AvAppTxResourceFacade, AvClientFacade, AvClientFamilyFacade, AvClientPlainCall, AvClientProjectingCall, AvClientResourceFacade, AvClientTxFacade, AvClientTxFamilyFacade, AvClientTxPlainCall, AvClientTxProjectingCall, AvClientTxResourceFacade, AvServerClientFacade, AvServerClientFamilyFacade, AvServerClientPlainCall, AvServerClientProjectingCall, AvServerClientResourceFacade, AvServerClientTxFacade, AvServerClientTxFamilyFacade, AvServerClientTxPlainCall, AvServerClientTxProjectingCall, AvServerClientTxResourceFacade, } from "./framework/operation-facade.js";
38
+ export { avLazy } from "./framework/operation-set.runner.js";
37
39
  export type { AvCreateDataInput, AvCursorInput, AvFieldKey, AvIncludeInput, AvNoAdvertisedMember, AvNullsOrder, AvOperationArgumentMap, AvOperationArgumentsFor, AvOrderByEntry, AvOrderByInput, AvProjectionEntry, AvRelationProjectionInput, AvResourceKey, AvScalarFilterOperands, AvScalarOrderInput, AvScalarWhereInput, AvSelectInput, AvSortDirection, AvUniqueWhereInput, AvUpdateDataInput, AvWhereInput, } from "./operations/operation-arguments.js";
38
40
  export type { AvDeferredOperationCall, AvOperationCall, AvOperationGrammar, AvOperationSlot, } from "./operations/operation-call.js";
39
41
  export type { AvOperationFamily, AvOperationIdentity, AvOperationVariant, } from "./operations/operation-identity.js";
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
+ export { cache } from "./config/cache-stage.factory.js";
1
2
  export { defineResource, } from "./config/define-resource.js";
2
3
  export { createFramework } from "./framework/framework.factory.js";
3
4
  export { AvConstructionError } from "./framework/framework-construction.error.js";
5
+ export { avLazy } from "./framework/operation-set.runner.js";
4
6
  export { AvDecimal } from "./runtime/decimal.js";
5
7
  export { AvErrorCatalog, } from "./runtime/error.catalog.js";
6
8
  export { AvAuthError, AvConflictError, AvContractMismatchError, AvError, AvInternalError, AvNotFoundError, AvProtocolError, AvValidationError, } from "./runtime/framework-error.js";
@@ -15,6 +15,7 @@ export type { AvAdapterSqlStatement, AvAdapterSqlValue, } from "./adapters/adapt
15
15
  export type { AvAdapterTransactionExecutor } from "./adapters/adapter-transaction-executor.interface.js";
16
16
  export { compileContracts } from "./compiler/contract.compiler.js";
17
17
  export { resolveFrameworkConfig } from "./config/config.resolver.js";
18
+ export { canonicalJson } from "./contracts/canonical-json.writer.js";
18
19
  export { PROJECTION_SCOPES, WRITE_SCOPES } from "./contracts/capability.js";
19
20
  export { validateClientContractStructure } from "./contracts/client-contract-structure.validator.js";
20
21
  export { AvCanonicalJsonError, canonicalizeContract, canonicalizeJson, } from "./contracts/contract-canonicalizer.js";
@@ -35,6 +36,7 @@ export type { SemanticOperationArguments } from "./operations/operation-validato
35
36
  export { VALIDATION_CODES } from "./operations/validation-code.js";
36
37
  export { accessProviderOf } from "./policy/access-provider.registry.js";
37
38
  export { ACCESS_VERB_NAMES, ACCESS_VERBS, providerVerb, } from "./policy/access-verb.table.js";
39
+ export { holdRequestPrincipal } from "./policy/policy-request.js";
38
40
  export { prebuiltFileChanges } from "./prebuilt/prebuilt-changes.js";
39
41
  export { emitPrebuiltContracts, prebuiltContractsOf, } from "./prebuilt/prebuilt-contracts.emitter.js";
40
42
  export { mcpFailureReporterOf } from "./runtime/diagnostics-reporter.js";
package/dist/internal.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { compileContracts } from "./compiler/contract.compiler.js";
2
2
  export { resolveFrameworkConfig } from "./config/config.resolver.js";
3
+ export { canonicalJson } from "./contracts/canonical-json.writer.js";
3
4
  export { PROJECTION_SCOPES, WRITE_SCOPES } from "./contracts/capability.js";
4
5
  export { validateClientContractStructure } from "./contracts/client-contract-structure.validator.js";
5
6
  export { AvCanonicalJsonError, canonicalizeContract, canonicalizeJson, } from "./contracts/contract-canonicalizer.js";
@@ -16,6 +17,7 @@ export { DATABASE_REFUSALS } from "./operations/database-refusal.js";
16
17
  export { VALIDATION_CODES } from "./operations/validation-code.js";
17
18
  export { accessProviderOf } from "./policy/access-provider.registry.js";
18
19
  export { ACCESS_VERB_NAMES, ACCESS_VERBS, providerVerb, } from "./policy/access-verb.table.js";
20
+ export { holdRequestPrincipal } from "./policy/policy-request.js";
19
21
  export { prebuiltFileChanges } from "./prebuilt/prebuilt-changes.js";
20
22
  export { emitPrebuiltContracts, prebuiltContractsOf, } from "./prebuilt/prebuilt-contracts.emitter.js";
21
23
  export { mcpFailureReporterOf } from "./runtime/diagnostics-reporter.js";
@@ -1,7 +1,8 @@
1
1
  import { FILTER_MODES, toOneRelinkWithRewrite, } from "../contracts/capability.js";
2
2
  import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
3
3
  import { advertisedDirectivesAt, advertisedReducers, advertisedReducersAt, holdsAt, } from "../contracts/field.js";
4
- import { isPlainRecord, unsentReadValueIssue, } from "../runtime/scalar-runtime.js";
4
+ import { isPlainRecord, isRecord } from "../runtime/record.guard.js";
5
+ import { unsentReadValueIssue } from "../runtime/scalar-runtime.js";
5
6
  import { routeTransactionReferenceCandidate } from "../transactions/transaction-reference.js";
6
7
  import { isOperationFamily, } from "./operation-identity.js";
7
8
  import { NESTED_PROJECTION_SCOPE, resolveProjectionScope, } from "./projection-scope.resolver.js";
@@ -96,9 +97,6 @@ const OPERATION_ARGUMENT_RULES = {
96
97
  projection: true,
97
98
  },
98
99
  };
99
- function isRecord(value) {
100
- return typeof value === "object" && value !== null && !Array.isArray(value);
101
- }
102
100
  function includes(values, value) {
103
101
  return values?.includes(value) ?? false;
104
102
  }
@@ -1,5 +1,6 @@
1
1
  import { lookupField, lookupResource } from "../contracts/contract-lookup.js";
2
2
  import { advertisedDirectives, holdsAt, } from "../contracts/field.js";
3
+ import { isRecord } from "../runtime/record.guard.js";
3
4
  import { NESTED_PROJECTION_SCOPE, resolveProjectionScope, } from "./projection-scope.resolver.js";
4
5
  const UNTRACKED = [];
5
6
  const RELATION_FILTER_OPERATORS = ["some", "every", "none"];
@@ -15,9 +16,6 @@ const DIRECTIVE_PAYLOADS = new Map([
15
16
  ],
16
17
  ]);
17
18
  const NO_PAYLOADS = [];
18
- function isRecord(value) {
19
- return typeof value === "object" && value !== null && !Array.isArray(value);
20
- }
21
19
  function extend(state, path, segment) {
22
20
  return state.tracking ? [...path, segment] : UNTRACKED;
23
21
  }