@prisma-next/sql-runtime 0.5.0-dev.7 → 0.5.0-dev.70

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 (48) hide show
  1. package/README.md +31 -22
  2. package/dist/exports-CXtbKm5q.mjs +1516 -0
  3. package/dist/exports-CXtbKm5q.mjs.map +1 -0
  4. package/dist/{index-yb51L_1h.d.mts → index-C4Dz0JKE.d.mts} +116 -45
  5. package/dist/index-C4Dz0JKE.d.mts.map +1 -0
  6. package/dist/index.d.mts +2 -2
  7. package/dist/index.mjs +2 -3
  8. package/dist/test/utils.d.mts +38 -33
  9. package/dist/test/utils.d.mts.map +1 -1
  10. package/dist/test/utils.mjs +107 -56
  11. package/dist/test/utils.mjs.map +1 -1
  12. package/package.json +18 -19
  13. package/src/codecs/alias-resolver.ts +34 -0
  14. package/src/codecs/decoding.ts +263 -176
  15. package/src/codecs/encoding.ts +151 -38
  16. package/src/codecs/validation.ts +4 -4
  17. package/src/content-hash.ts +44 -0
  18. package/src/exports/index.ts +13 -7
  19. package/src/fingerprint.ts +22 -0
  20. package/src/guardrails/raw.ts +165 -0
  21. package/src/lower-sql-plan.ts +3 -3
  22. package/src/marker.ts +75 -0
  23. package/src/middleware/before-compile-chain.ts +1 -0
  24. package/src/middleware/budgets.ts +36 -120
  25. package/src/middleware/lints.ts +3 -3
  26. package/src/middleware/sql-middleware.ts +6 -5
  27. package/src/runtime-spi.ts +44 -0
  28. package/src/sql-context.ts +315 -105
  29. package/src/sql-family-adapter.ts +3 -2
  30. package/src/sql-marker.ts +89 -51
  31. package/src/sql-runtime.ts +305 -144
  32. package/dist/exports-BQZSVXXt.mjs +0 -981
  33. package/dist/exports-BQZSVXXt.mjs.map +0 -1
  34. package/dist/index-yb51L_1h.d.mts.map +0 -1
  35. package/src/codecs/json-schema-validation.ts +0 -61
  36. package/test/async-iterable-result.test.ts +0 -141
  37. package/test/before-compile-chain.test.ts +0 -223
  38. package/test/budgets.test.ts +0 -431
  39. package/test/context.types.test-d.ts +0 -68
  40. package/test/execution-stack.test.ts +0 -161
  41. package/test/json-schema-validation.test.ts +0 -571
  42. package/test/lints.test.ts +0 -160
  43. package/test/mutation-default-generators.test.ts +0 -254
  44. package/test/parameterized-types.test.ts +0 -529
  45. package/test/sql-context.test.ts +0 -384
  46. package/test/sql-family-adapter.test.ts +0 -103
  47. package/test/sql-runtime.test.ts +0 -792
  48. package/test/utils.ts +0 -297
@@ -1,221 +1,308 @@
1
- import type { ExecutionPlan } from '@prisma-next/contract/types';
2
- import type { Codec, CodecRegistry } from '@prisma-next/sql-relational-core/ast';
3
- import type { JsonSchemaValidatorRegistry } from '@prisma-next/sql-relational-core/query-lane-context';
4
- import { validateJsonValue } from './json-schema-validation';
1
+ import {
2
+ checkAborted,
3
+ isRuntimeError,
4
+ raceAgainstAbort,
5
+ runtimeError,
6
+ } from '@prisma-next/framework-components/runtime';
7
+ import type {
8
+ AnyQueryAst,
9
+ Codec,
10
+ ContractCodecRegistry,
11
+ ProjectionItem,
12
+ SqlCodecCallContext,
13
+ } from '@prisma-next/sql-relational-core/ast';
14
+ import type { SqlExecutionPlan } from '@prisma-next/sql-relational-core/plan';
15
+ import { makeAliasResolver } from './alias-resolver';
5
16
 
6
- function resolveRowCodec(
7
- alias: string,
8
- plan: ExecutionPlan,
9
- registry: CodecRegistry,
10
- ): Codec | null {
11
- const planCodecId = plan.meta.annotations?.codecs?.[alias] as string | undefined;
12
- if (planCodecId) {
13
- const codec = registry.get(planCodecId);
14
- if (codec) {
15
- return codec;
16
- }
17
- }
17
+ type ColumnRef = { table: string; column: string };
18
18
 
19
- if (plan.meta.projectionTypes) {
20
- const typeId = plan.meta.projectionTypes[alias];
21
- if (typeId) {
22
- const codec = registry.get(typeId);
23
- if (codec) {
24
- return codec;
25
- }
26
- }
27
- }
19
+ interface DecodeContext {
20
+ readonly aliases: ReadonlyArray<string> | undefined;
21
+ readonly codecs: ReadonlyMap<string, Codec>;
22
+ readonly columnRefs: ReadonlyMap<string, ColumnRef>;
23
+ readonly includeAliases: ReadonlySet<string>;
24
+ }
25
+
26
+ const WIRE_PREVIEW_LIMIT = 100;
27
+ const EMPTY_INCLUDE_ALIASES: ReadonlySet<string> = new Set<string>();
28
28
 
29
- return null;
29
+ function isAstBackedPlan(
30
+ plan: SqlExecutionPlan,
31
+ ): plan is SqlExecutionPlan & { readonly ast: AnyQueryAst } {
32
+ return plan.ast !== undefined;
30
33
  }
31
34
 
32
- type ColumnRefIndex = Map<string, { table: string; column: string }>;
35
+ function projectionListFromAst(ast: AnyQueryAst): ReadonlyArray<ProjectionItem> | undefined {
36
+ if (ast.kind === 'select') {
37
+ return ast.projection;
38
+ }
39
+ return ast.returning;
40
+ }
33
41
 
34
42
  /**
35
- * Builds a lookup index from column name → { table, column } ref.
36
- * Called once per decodeRow invocation to avoid O(aliases × refs) linear scans.
43
+ * Resolve the per-cell codec for a projection item.
44
+ *
45
+ * When a `(table, column)` ref is available — either implicit on a `column-ref` expression or carried explicitly via `item.refs` for column-bound non-`column-ref` projections — prefer `contractCodecs.forColumn(table, column)`: that returns the per-instance codec materialized from the descriptor's factory for that column, encoding any per-instance state (typeParams like vector length, schema validators, etc.).
46
+ *
47
+ * The wrong-instance risk for parameterized codecs is closed off structurally:
48
+ *
49
+ * 1. `buildContractCodecRegistry` pre-populates `byCodecId` with one canonical instance per non-parameterized descriptor; parameterized descriptors are intentionally absent. 2. `forCodecId` rejects ambiguous parameterized fallbacks (`ambiguousCodecIds`). 3. The non-ambiguous parameterized case stores the column-correct per-instance codec under `byCodecId`, so the fall-through still resolves to the right instance.
50
+ *
51
+ * The `forCodecId` fallback otherwise covers projections that are *not* column-bound (computed projections, raw SQL aliases) but still carry a `codecId` (ADR 205 stamps every `ProjectionItem` with the producer's codec id).
52
+ *
53
+ * Codec-registry-unification spec § AC-4 / AC-5.
37
54
  */
38
- function buildColumnRefIndex(plan: ExecutionPlan): ColumnRefIndex | null {
39
- const columns = plan.meta.refs?.columns;
40
- if (!columns) return null;
41
-
42
- const index: ColumnRefIndex = new Map();
43
- for (const ref of columns) {
44
- index.set(ref.column, ref);
55
+ function resolveProjectionCodec(
56
+ item: ProjectionItem,
57
+ contractCodecs: ContractCodecRegistry | undefined,
58
+ aliasResolver: (alias: string) => string,
59
+ ): Codec | undefined {
60
+ if (contractCodecs) {
61
+ if (item.expr.kind === 'column-ref') {
62
+ const byColumn = contractCodecs.forColumn(aliasResolver(item.expr.table), item.expr.column);
63
+ // Only honour `byColumn` when its codec id agrees with `item.codecId`. They can legitimately disagree when an `OperationExpr`-shaped projection carries a single inner column-ref but transforms the value's codec (e.g. `cosineDistance(col, x)` projects `pg/float8@1` while the inner column-ref points at a `pg/vector@1` column).
64
+ if (byColumn && (item.codecId === undefined || byColumn.id === item.codecId)) return byColumn;
65
+ } else if (item.refs) {
66
+ const byColumn = contractCodecs.forColumn(aliasResolver(item.refs.table), item.refs.column);
67
+ if (byColumn && (item.codecId === undefined || byColumn.id === item.codecId)) return byColumn;
68
+ }
45
69
  }
46
- return index;
70
+ if (item.codecId) {
71
+ return contractCodecs?.forCodecId(item.codecId);
72
+ }
73
+ return undefined;
47
74
  }
48
75
 
49
- function parseProjectionRef(value: string): { table: string; column: string } | null {
50
- if (value.startsWith('include:') || value.startsWith('operation:')) {
51
- return null;
76
+ function buildDecodeContext(
77
+ plan: SqlExecutionPlan,
78
+ contractCodecs: ContractCodecRegistry | undefined,
79
+ ): DecodeContext {
80
+ if (!isAstBackedPlan(plan)) {
81
+ return {
82
+ aliases: undefined,
83
+ codecs: new Map(),
84
+ columnRefs: new Map(),
85
+ includeAliases: EMPTY_INCLUDE_ALIASES,
86
+ };
52
87
  }
53
88
 
54
- const separatorIndex = value.indexOf('.');
55
- if (separatorIndex <= 0 || separatorIndex === value.length - 1) {
56
- return null;
89
+ const projection = projectionListFromAst(plan.ast);
90
+ if (!projection) {
91
+ return {
92
+ aliases: undefined,
93
+ codecs: new Map(),
94
+ columnRefs: new Map(),
95
+ includeAliases: EMPTY_INCLUDE_ALIASES,
96
+ };
57
97
  }
58
98
 
59
- return {
60
- table: value.slice(0, separatorIndex),
61
- column: value.slice(separatorIndex + 1),
62
- };
99
+ const aliases: string[] = [];
100
+ const codecs = new Map<string, Codec>();
101
+ const columnRefs = new Map<string, ColumnRef>();
102
+ const includeAliases = new Set<string>();
103
+ const aliasResolver = makeAliasResolver(plan.ast);
104
+
105
+ for (const item of projection) {
106
+ aliases.push(item.alias);
107
+
108
+ const codec = resolveProjectionCodec(item, contractCodecs, aliasResolver);
109
+ if (codec) {
110
+ codecs.set(item.alias, codec);
111
+ }
112
+
113
+ if (item.expr.kind === 'column-ref') {
114
+ columnRefs.set(item.alias, {
115
+ table: aliasResolver(item.expr.table),
116
+ column: item.expr.column,
117
+ });
118
+ } else if (item.refs) {
119
+ columnRefs.set(item.alias, {
120
+ table: aliasResolver(item.refs.table),
121
+ column: item.refs.column,
122
+ });
123
+ } else if (item.expr.kind === 'subquery' || item.expr.kind === 'json-array-agg') {
124
+ includeAliases.add(item.alias);
125
+ }
126
+ }
127
+
128
+ return { aliases, codecs, columnRefs, includeAliases };
129
+ }
130
+
131
+ function previewWireValue(wireValue: unknown): string {
132
+ if (typeof wireValue === 'string') {
133
+ return wireValue.length > WIRE_PREVIEW_LIMIT
134
+ ? `${wireValue.substring(0, WIRE_PREVIEW_LIMIT)}...`
135
+ : wireValue;
136
+ }
137
+ return String(wireValue).substring(0, WIRE_PREVIEW_LIMIT);
63
138
  }
64
139
 
65
- function resolveColumnRefForAlias(
140
+ function wrapDecodeFailure(
141
+ error: unknown,
66
142
  alias: string,
67
- projection: ExecutionPlan['meta']['projection'],
68
- fallbackColumnRefIndex: ColumnRefIndex | null,
69
- ): { table: string; column: string } | undefined {
70
- if (projection && !Array.isArray(projection)) {
71
- const mappedRef = (projection as Record<string, string>)[alias];
72
- if (typeof mappedRef !== 'string') {
73
- return undefined;
74
- }
75
- return parseProjectionRef(mappedRef) ?? undefined;
143
+ ref: ColumnRef | undefined,
144
+ codec: Codec,
145
+ wireValue: unknown,
146
+ ): never {
147
+ const message = error instanceof Error ? error.message : String(error);
148
+ const target = ref ? `${ref.table}.${ref.column}` : alias;
149
+ const wrapped = runtimeError(
150
+ 'RUNTIME.DECODE_FAILED',
151
+ `Failed to decode column ${target} with codec '${codec.id}': ${message}`,
152
+ {
153
+ ...(ref ? { table: ref.table, column: ref.column } : { alias }),
154
+ codec: codec.id,
155
+ wirePreview: previewWireValue(wireValue),
156
+ },
157
+ );
158
+ wrapped.cause = error;
159
+ throw wrapped;
160
+ }
161
+
162
+ function wrapIncludeAggregateFailure(error: unknown, alias: string, wireValue: unknown): never {
163
+ const message = error instanceof Error ? error.message : String(error);
164
+ const wrapped = runtimeError(
165
+ 'RUNTIME.DECODE_FAILED',
166
+ `Failed to parse JSON array for include alias '${alias}': ${message}`,
167
+ {
168
+ alias,
169
+ wirePreview: previewWireValue(wireValue),
170
+ },
171
+ );
172
+ wrapped.cause = error;
173
+ throw wrapped;
174
+ }
175
+
176
+ function decodeIncludeAggregate(alias: string, wireValue: unknown): unknown {
177
+ if (wireValue === null || wireValue === undefined) {
178
+ return [];
76
179
  }
77
180
 
78
- return fallbackColumnRefIndex?.get(alias);
181
+ try {
182
+ let parsed: unknown;
183
+ if (typeof wireValue === 'string') {
184
+ parsed = JSON.parse(wireValue);
185
+ } else if (Array.isArray(wireValue)) {
186
+ parsed = wireValue;
187
+ } else {
188
+ parsed = JSON.parse(String(wireValue));
189
+ }
190
+
191
+ if (!Array.isArray(parsed)) {
192
+ throw new Error(`Expected array for include alias '${alias}', got ${typeof parsed}`);
193
+ }
194
+
195
+ return parsed;
196
+ } catch (error) {
197
+ wrapIncludeAggregateFailure(error, alias, wireValue);
198
+ }
79
199
  }
80
200
 
81
- export function decodeRow(
82
- row: Record<string, unknown>,
83
- plan: ExecutionPlan,
84
- registry: CodecRegistry,
85
- jsonValidators?: JsonSchemaValidatorRegistry,
86
- ): Record<string, unknown> {
87
- const decoded: Record<string, unknown> = {};
88
- const projection = plan.meta.projection;
201
+ /**
202
+ * Decodes a single field. Single-armed: every cell takes the same path — `codec.decode → await → return plain value` — so sync- and async-authored codecs are indistinguishable to callers. JSON-Schema validation, when required, lives inside the resolved codec's `decode` body (e.g. `arktype-json` validates against its rehydrated schema and throws `RUNTIME.JSON_SCHEMA_VALIDATION_FAILED` from `decode` directly); there is
203
+ * no separate validator-registry pass.
204
+ *
205
+ * The row-level `rowCtx` is repackaged into a per-cell `SqlCodecCallContext` whose `column = { table, name }` is a structural projection of the per-cell `ColumnRef = { table, column }` resolved from the AST-backed `DecodeContext` (the same resolution `wrapDecodeFailure` uses for envelope construction — one resolution per cell, two consumers). Cells the runtime cannot resolve to a single underlying column (aggregate
206
+ * aliases, computed projections without a simple ref) get `column: undefined`, matching the spec contract that the runtime never silently defaults this field.
207
+ */
208
+ async function decodeField(
209
+ alias: string,
210
+ wireValue: unknown,
211
+ decodeCtx: DecodeContext,
212
+ rowCtx: SqlCodecCallContext,
213
+ ): Promise<unknown> {
214
+ if (wireValue === null) {
215
+ return null;
216
+ }
217
+
218
+ const codec = decodeCtx.codecs.get(alias);
219
+ if (!codec) {
220
+ return wireValue;
221
+ }
89
222
 
90
- // Fallback for plans that do not provide projection alias -> table.column mapping.
91
- const fallbackColumnRefIndex =
92
- jsonValidators && (!projection || Array.isArray(projection)) ? buildColumnRefIndex(plan) : null;
223
+ const ref = decodeCtx.columnRefs.get(alias);
93
224
 
94
- let aliases: readonly string[];
95
- if (projection && !Array.isArray(projection)) {
96
- aliases = Object.keys(projection);
97
- } else if (projection && Array.isArray(projection)) {
98
- aliases = projection;
225
+ // Per-cell ctx: the cell-level `column` is a `SqlColumnRef = { table, name }` projection of the resolved `ColumnRef = { table, column }` (same resolution `wrapDecodeFailure` uses below — no double work). Cells the runtime cannot resolve (aggregate aliases, computed projections without a simple ref) drop the `column` field entirely — explicitly cleared so a previously-populated `rowCtx.column` cannot leak through to
226
+ // unrelated cells. Destructuring (rather than `column: undefined`) is required because `SqlCodecCallContext.column` is declared `column?: SqlColumnRef` under `exactOptionalPropertyTypes`.
227
+ let cellCtx: SqlCodecCallContext;
228
+ if (ref) {
229
+ cellCtx = { ...rowCtx, column: { table: ref.table, name: ref.column } };
99
230
  } else {
100
- aliases = Object.keys(row);
231
+ const { column: _drop, ...rowCtxWithoutColumn } = rowCtx;
232
+ cellCtx = rowCtxWithoutColumn;
101
233
  }
102
234
 
103
- for (const alias of aliases) {
104
- const wireValue = row[alias];
235
+ try {
236
+ return await codec.decode(wireValue, cellCtx);
237
+ } catch (error) {
238
+ // Codec-authored runtime envelopes (e.g. `RUNTIME.DECODE_FAILED` thrown from inside the codec body, or `RUNTIME.ABORTED` raised via `CodecCallContext.signal` per ADR 207) carry their own per-codec context — wrapping them again would erase that context and coerce the abort intent into a generic decode failure. Pass them through unchanged; only foreign errors get the `wrapDecodeFailure` envelope.
239
+ if (isRuntimeError(error)) {
240
+ throw error;
241
+ }
242
+ wrapDecodeFailure(error, alias, ref, codec, wireValue);
243
+ }
244
+ }
105
245
 
106
- const projectionValue =
107
- projection && typeof projection === 'object' && !Array.isArray(projection)
108
- ? (projection as Record<string, string>)[alias]
109
- : undefined;
246
+ /**
247
+ * Decodes a row by dispatching all per-cell codec calls concurrently via `Promise.all`. Each cell follows the single-armed `decodeField` path. Failures are wrapped in `RUNTIME.DECODE_FAILED` with `{ table, column, codec }` (or `{ alias, codec }` when no column ref is resolvable) and the original error attached on `cause`.
248
+ *
249
+ * When `rowCtx.signal` is provided:
250
+ *
251
+ * - **Already-aborted at entry** short-circuits with `RUNTIME.ABORTED` (`{ phase: 'decode' }`) before any `codec.decode` call is made.
252
+ * - **Mid-flight aborts** race the per-cell `Promise.all` against the signal so the runtime returns promptly even when codec bodies ignore it. In-flight bodies that ignore the signal complete in the background (cooperative cancellation).
253
+ * - Existing `RUNTIME.DECODE_FAILED` envelopes from codec bodies pass through unchanged (no double wrap).
254
+ */
255
+ export async function decodeRow(
256
+ row: Record<string, unknown>,
257
+ plan: SqlExecutionPlan,
258
+ rowCtx: SqlCodecCallContext,
259
+ contractCodecs?: ContractCodecRegistry,
260
+ ): Promise<Record<string, unknown>> {
261
+ checkAborted(rowCtx, 'decode');
262
+ const signal = rowCtx.signal;
110
263
 
111
- if (typeof projectionValue === 'string' && projectionValue.startsWith('include:')) {
112
- if (wireValue === null || wireValue === undefined) {
113
- decoded[alias] = [];
114
- continue;
115
- }
264
+ const decodeCtx = buildDecodeContext(plan, contractCodecs);
116
265
 
117
- try {
118
- let parsed: unknown;
119
- if (typeof wireValue === 'string') {
120
- parsed = JSON.parse(wireValue);
121
- } else if (Array.isArray(wireValue)) {
122
- parsed = wireValue;
123
- } else {
124
- parsed = JSON.parse(String(wireValue));
125
- }
126
-
127
- if (!Array.isArray(parsed)) {
128
- throw new Error(`Expected array for include alias '${alias}', got ${typeof parsed}`);
129
- }
130
-
131
- decoded[alias] = parsed;
132
- } catch (error) {
133
- const decodeError = new Error(
134
- `Failed to parse JSON array for include alias '${alias}': ${error instanceof Error ? error.message : String(error)}`,
135
- ) as Error & {
136
- code: string;
137
- category: string;
138
- severity: string;
139
- details?: Record<string, unknown>;
140
- };
141
- decodeError.code = 'RUNTIME.DECODE_FAILED';
142
- decodeError.category = 'RUNTIME';
143
- decodeError.severity = 'error';
144
- decodeError.details = {
266
+ const aliases = decodeCtx.aliases ?? Object.keys(row);
267
+
268
+ if (decodeCtx.aliases !== undefined) {
269
+ for (const alias of decodeCtx.aliases) {
270
+ if (!Object.hasOwn(row, alias)) {
271
+ throw runtimeError('RUNTIME.DECODE_FAILED', `Row missing projection alias "${alias}"`, {
145
272
  alias,
146
- wirePreview:
147
- typeof wireValue === 'string' && wireValue.length > 100
148
- ? `${wireValue.substring(0, 100)}...`
149
- : String(wireValue).substring(0, 100),
150
- };
151
- throw decodeError;
273
+ expectedAliases: decodeCtx.aliases,
274
+ presentKeys: Object.keys(row),
275
+ });
152
276
  }
153
- continue;
154
277
  }
278
+ }
155
279
 
156
- if (wireValue === null || wireValue === undefined) {
157
- decoded[alias] = wireValue;
158
- continue;
159
- }
280
+ const tasks: Promise<unknown>[] = [];
281
+ const includeIndices: { index: number; alias: string; value: unknown }[] = [];
160
282
 
161
- const codec = resolveRowCodec(alias, plan, registry);
283
+ for (let i = 0; i < aliases.length; i++) {
284
+ const alias = aliases[i] as string;
285
+ const wireValue = row[alias];
162
286
 
163
- if (!codec) {
164
- decoded[alias] = wireValue;
287
+ if (decodeCtx.includeAliases.has(alias)) {
288
+ includeIndices.push({ index: i, alias, value: wireValue });
289
+ tasks.push(Promise.resolve(undefined));
165
290
  continue;
166
291
  }
167
292
 
168
- try {
169
- const decodedValue = codec.decode(wireValue);
170
-
171
- // Validate decoded JSON value against schema
172
- if (jsonValidators) {
173
- const ref = resolveColumnRefForAlias(alias, projection, fallbackColumnRefIndex);
174
- if (ref) {
175
- validateJsonValue(
176
- jsonValidators,
177
- ref.table,
178
- ref.column,
179
- decodedValue,
180
- 'decode',
181
- codec.id,
182
- );
183
- }
184
- }
293
+ tasks.push(decodeField(alias, wireValue, decodeCtx, rowCtx));
294
+ }
185
295
 
186
- decoded[alias] = decodedValue;
187
- } catch (error) {
188
- // Re-throw JSON schema validation errors as-is
189
- if (
190
- error instanceof Error &&
191
- 'code' in error &&
192
- (error as Error & { code: string }).code === 'RUNTIME.JSON_SCHEMA_VALIDATION_FAILED'
193
- ) {
194
- throw error;
195
- }
296
+ const settled = await raceAgainstAbort(Promise.all(tasks), signal, 'decode');
196
297
 
197
- const decodeError = new Error(
198
- `Failed to decode row alias '${alias}' with codec '${codec.id}': ${error instanceof Error ? error.message : String(error)}`,
199
- ) as Error & {
200
- code: string;
201
- category: string;
202
- severity: string;
203
- details?: Record<string, unknown>;
204
- };
205
- decodeError.code = 'RUNTIME.DECODE_FAILED';
206
- decodeError.category = 'RUNTIME';
207
- decodeError.severity = 'error';
208
- decodeError.details = {
209
- alias,
210
- codec: codec.id,
211
- wirePreview:
212
- typeof wireValue === 'string' && wireValue.length > 100
213
- ? `${wireValue.substring(0, 100)}...`
214
- : String(wireValue).substring(0, 100),
215
- };
216
- throw decodeError;
217
- }
298
+ // Include aggregates are decoded synchronously after concurrent codec dispatch settles, so any decode failures upstream propagate first.
299
+ for (const entry of includeIndices) {
300
+ settled[entry.index] = decodeIncludeAggregate(entry.alias, entry.value);
218
301
  }
219
302
 
303
+ const decoded: Record<string, unknown> = {};
304
+ for (let i = 0; i < aliases.length; i++) {
305
+ decoded[aliases[i] as string] = settled[i];
306
+ }
220
307
  return decoded;
221
308
  }