deepline 0.1.150 → 0.1.151
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +157 -140
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/csv-rows.ts +2 -19
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/row-isolation.ts +5 -53
- package/dist/bundling-sources/sdk/src/config.ts +2 -2
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +100 -158
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durability-store.ts +54 -0
- package/dist/bundling-sources/shared_libs/play-runtime/map-row-outcome.ts +167 -0
- package/dist/bundling-sources/shared_libs/play-runtime/pacing.ts +79 -0
- package/dist/bundling-sources/shared_libs/play-runtime/row-isolation.ts +39 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +19 -86
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-row-transition.ts +90 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-session.ts +43 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +142 -11
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +3 -2
- package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +20 -23
- package/dist/cli/index.js +35 -3
- package/dist/cli/index.mjs +35 -3
- package/dist/index.js +3 -3
- package/dist/index.mjs +3 -3
- package/dist/plays/bundle-play-file.mjs +22 -19
- package/package.json +1 -1
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { MapRowOutcome, MapRowOutcomeStatus } from './durability-store';
|
|
2
|
+
|
|
3
|
+
export const MAP_ROW_OUTCOME_RUNTIME_FIELDS = {
|
|
4
|
+
rowKey: '__deeplineRowKey',
|
|
5
|
+
cellMetaPatch: '__deeplineCellMetaPatch',
|
|
6
|
+
rowStatus: '__deeplineRowStatus',
|
|
7
|
+
rowError: '__deeplineRowError',
|
|
8
|
+
} as const;
|
|
9
|
+
|
|
10
|
+
const MAP_ROW_OUTCOME_RUNTIME_FIELD_SET = new Set<string>(
|
|
11
|
+
Object.values(MAP_ROW_OUTCOME_RUNTIME_FIELDS),
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
export type MapRowOutcomeRuntimeRow = Record<string, unknown>;
|
|
15
|
+
|
|
16
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
17
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function resolveMapRowOutcomeKey(
|
|
21
|
+
row: MapRowOutcomeRuntimeRow,
|
|
22
|
+
): string | null {
|
|
23
|
+
const key = row[MAP_ROW_OUTCOME_RUNTIME_FIELDS.rowKey];
|
|
24
|
+
return typeof key === 'string' && key.length > 0 ? key : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function mapRowOutcomeRuntimeFields(input: {
|
|
28
|
+
key: string;
|
|
29
|
+
cellMetaPatch?: Record<string, unknown> | null;
|
|
30
|
+
status?: MapRowOutcomeStatus | null;
|
|
31
|
+
error?: string | null;
|
|
32
|
+
}): Record<string, unknown> {
|
|
33
|
+
return {
|
|
34
|
+
[MAP_ROW_OUTCOME_RUNTIME_FIELDS.rowKey]: input.key,
|
|
35
|
+
...(input.cellMetaPatch
|
|
36
|
+
? { [MAP_ROW_OUTCOME_RUNTIME_FIELDS.cellMetaPatch]: input.cellMetaPatch }
|
|
37
|
+
: {}),
|
|
38
|
+
...(input.status
|
|
39
|
+
? { [MAP_ROW_OUTCOME_RUNTIME_FIELDS.rowStatus]: input.status }
|
|
40
|
+
: {}),
|
|
41
|
+
...(input.error !== undefined
|
|
42
|
+
? { [MAP_ROW_OUTCOME_RUNTIME_FIELDS.rowError]: input.error }
|
|
43
|
+
: {}),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function completedMapRowOutcome(input: {
|
|
48
|
+
key: string;
|
|
49
|
+
inputIndex?: number | null;
|
|
50
|
+
data: Record<string, unknown>;
|
|
51
|
+
cellMetaPatch?: Record<string, unknown> | null;
|
|
52
|
+
}): MapRowOutcome {
|
|
53
|
+
return {
|
|
54
|
+
key: input.key,
|
|
55
|
+
...(input.inputIndex !== undefined ? { inputIndex: input.inputIndex } : {}),
|
|
56
|
+
data: input.data,
|
|
57
|
+
...(input.cellMetaPatch ? { cellMetaPatch: input.cellMetaPatch } : {}),
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function failedMapRowOutcome(input: {
|
|
62
|
+
key: string;
|
|
63
|
+
inputIndex?: number | null;
|
|
64
|
+
data: Record<string, unknown>;
|
|
65
|
+
cellMetaPatch?: Record<string, unknown> | null;
|
|
66
|
+
error?: string | null;
|
|
67
|
+
}): MapRowOutcome {
|
|
68
|
+
return {
|
|
69
|
+
...completedMapRowOutcome(input),
|
|
70
|
+
status: 'failed',
|
|
71
|
+
error: input.error ?? null,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function mapRowOutcomeRuntimeRow(
|
|
76
|
+
outcome: MapRowOutcome,
|
|
77
|
+
): Record<string, unknown> {
|
|
78
|
+
return {
|
|
79
|
+
...outcome.data,
|
|
80
|
+
...mapRowOutcomeRuntimeFields({
|
|
81
|
+
key: outcome.key,
|
|
82
|
+
cellMetaPatch: outcome.cellMetaPatch,
|
|
83
|
+
status: outcome.status,
|
|
84
|
+
error: outcome.error,
|
|
85
|
+
}),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function copyMapRowOutcomeRuntimeFields<
|
|
90
|
+
TTarget extends MapRowOutcomeRuntimeRow,
|
|
91
|
+
>(target: TTarget, row: MapRowOutcomeRuntimeRow): TTarget {
|
|
92
|
+
const writableTarget = target as MapRowOutcomeRuntimeRow;
|
|
93
|
+
for (const runtimeField of MAP_ROW_OUTCOME_RUNTIME_FIELD_SET) {
|
|
94
|
+
if (runtimeField in row) {
|
|
95
|
+
writableTarget[runtimeField] = row[runtimeField];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return target;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function stripMapRowOutcomeRuntimeFields<
|
|
102
|
+
TRow extends MapRowOutcomeRuntimeRow,
|
|
103
|
+
>(row: TRow): TRow {
|
|
104
|
+
const data: MapRowOutcomeRuntimeRow = {};
|
|
105
|
+
for (const fieldName of Reflect.ownKeys(row)) {
|
|
106
|
+
if (
|
|
107
|
+
typeof fieldName === 'string' &&
|
|
108
|
+
MAP_ROW_OUTCOME_RUNTIME_FIELD_SET.has(fieldName)
|
|
109
|
+
) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const descriptor = Object.getOwnPropertyDescriptor(row, fieldName);
|
|
113
|
+
if (!descriptor) continue;
|
|
114
|
+
Object.defineProperty(data, fieldName, descriptor);
|
|
115
|
+
}
|
|
116
|
+
return data as TRow;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function mapRowOutcomeFromRuntimeRow(
|
|
120
|
+
row: MapRowOutcomeRuntimeRow,
|
|
121
|
+
): MapRowOutcome | null {
|
|
122
|
+
if (typeof row.key === 'string' && isRecord(row.data)) {
|
|
123
|
+
return {
|
|
124
|
+
key: row.key,
|
|
125
|
+
data: row.data,
|
|
126
|
+
...(isRecord(row.cellMetaPatch)
|
|
127
|
+
? { cellMetaPatch: row.cellMetaPatch }
|
|
128
|
+
: {}),
|
|
129
|
+
...(row.status === 'failed'
|
|
130
|
+
? {
|
|
131
|
+
status: 'failed',
|
|
132
|
+
error: typeof row.error === 'string' ? row.error : null,
|
|
133
|
+
}
|
|
134
|
+
: {}),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const key = resolveMapRowOutcomeKey(row);
|
|
139
|
+
if (!key) return null;
|
|
140
|
+
const cellMetaPatch = row[MAP_ROW_OUTCOME_RUNTIME_FIELDS.cellMetaPatch];
|
|
141
|
+
const rowStatus = row[MAP_ROW_OUTCOME_RUNTIME_FIELDS.rowStatus];
|
|
142
|
+
const rowError = row[MAP_ROW_OUTCOME_RUNTIME_FIELDS.rowError];
|
|
143
|
+
return {
|
|
144
|
+
key,
|
|
145
|
+
data: stripMapRowOutcomeRuntimeFields(row),
|
|
146
|
+
...(isRecord(cellMetaPatch) ? { cellMetaPatch } : {}),
|
|
147
|
+
...(rowStatus === 'failed'
|
|
148
|
+
? {
|
|
149
|
+
status: 'failed',
|
|
150
|
+
error: typeof rowError === 'string' ? rowError : null,
|
|
151
|
+
}
|
|
152
|
+
: {}),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function requireMapRowOutcomeFromRuntimeRow(
|
|
157
|
+
row: MapRowOutcomeRuntimeRow,
|
|
158
|
+
input: { tableNamespace: string },
|
|
159
|
+
): MapRowOutcome {
|
|
160
|
+
const outcome = mapRowOutcomeFromRuntimeRow(row);
|
|
161
|
+
if (!outcome) {
|
|
162
|
+
throw new Error(
|
|
163
|
+
`persistCompletedMapRows received a row without a key for tableNamespace=${input.tableNamespace}.`,
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
return outcome;
|
|
167
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { resolveBuiltinPacing } from './builtin-pacing';
|
|
2
|
+
import type { PacingRule, PlayQueueHint } from './governor/rate-state-backend';
|
|
3
|
+
|
|
4
|
+
export type ResolvedPacingPolicy = {
|
|
5
|
+
provider: string;
|
|
6
|
+
rules: PacingRule[];
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type PacingPolicyQueueHint = Pick<
|
|
10
|
+
PlayQueueHint,
|
|
11
|
+
'provider' | 'ruleId' | 'requestsPerWindow' | 'windowMs' | 'maxConcurrency'
|
|
12
|
+
>;
|
|
13
|
+
|
|
14
|
+
function isFinitePositiveNumber(value: unknown): value is number {
|
|
15
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function pacingPolicyFromQueueHints(
|
|
19
|
+
hints: readonly PacingPolicyQueueHint[],
|
|
20
|
+
): ResolvedPacingPolicy | null {
|
|
21
|
+
if (hints.length === 0) return null;
|
|
22
|
+
const provider = hints[0]?.provider?.trim();
|
|
23
|
+
if (!provider) return null;
|
|
24
|
+
const rules = hints.flatMap((hint) => {
|
|
25
|
+
if (
|
|
26
|
+
typeof hint.ruleId !== 'string' ||
|
|
27
|
+
!hint.ruleId.trim() ||
|
|
28
|
+
!isFinitePositiveNumber(hint.requestsPerWindow) ||
|
|
29
|
+
!isFinitePositiveNumber(hint.windowMs)
|
|
30
|
+
) {
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
return [
|
|
34
|
+
{
|
|
35
|
+
ruleId: hint.ruleId,
|
|
36
|
+
requestsPerWindow: hint.requestsPerWindow,
|
|
37
|
+
windowMs: hint.windowMs,
|
|
38
|
+
maxConcurrency:
|
|
39
|
+
typeof hint.maxConcurrency === 'number' &&
|
|
40
|
+
Number.isFinite(hint.maxConcurrency) &&
|
|
41
|
+
hint.maxConcurrency > 0
|
|
42
|
+
? hint.maxConcurrency
|
|
43
|
+
: null,
|
|
44
|
+
} satisfies PacingRule,
|
|
45
|
+
];
|
|
46
|
+
});
|
|
47
|
+
return rules.length > 0 ? { provider, rules } : null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function pacingPolicyFromUnknownQueueHints(
|
|
51
|
+
value: unknown,
|
|
52
|
+
): ResolvedPacingPolicy | null {
|
|
53
|
+
if (!Array.isArray(value)) return null;
|
|
54
|
+
return pacingPolicyFromQueueHints(
|
|
55
|
+
value
|
|
56
|
+
.filter((hint): hint is Record<string, unknown> =>
|
|
57
|
+
Boolean(hint && typeof hint === 'object' && !Array.isArray(hint)),
|
|
58
|
+
)
|
|
59
|
+
.map((hint) => ({
|
|
60
|
+
provider: typeof hint.provider === 'string' ? hint.provider : '',
|
|
61
|
+
ruleId: typeof hint.ruleId === 'string' ? hint.ruleId : '',
|
|
62
|
+
requestsPerWindow:
|
|
63
|
+
typeof hint.requestsPerWindow === 'number'
|
|
64
|
+
? hint.requestsPerWindow
|
|
65
|
+
: Number.NaN,
|
|
66
|
+
windowMs:
|
|
67
|
+
typeof hint.windowMs === 'number' ? hint.windowMs : Number.NaN,
|
|
68
|
+
maxConcurrency:
|
|
69
|
+
typeof hint.maxConcurrency === 'number' ? hint.maxConcurrency : null,
|
|
70
|
+
})),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function pacingPolicyForTool(
|
|
75
|
+
toolId: string,
|
|
76
|
+
hints: readonly PlayQueueHint[],
|
|
77
|
+
): ResolvedPacingPolicy | null {
|
|
78
|
+
return resolveBuiltinPacing(toolId) ?? pacingPolicyFromQueueHints(hints);
|
|
79
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { isHardBillingToolHttpError } from './tool-http-errors';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Thrown by runner Adapters when a Play Run is externally cancelled. Row
|
|
5
|
+
* isolation must let this escape so in-flight user code stops cooperatively.
|
|
6
|
+
*/
|
|
7
|
+
export class WorkflowAbortError extends Error {
|
|
8
|
+
override readonly name = 'WorkflowAbort';
|
|
9
|
+
constructor(message = 'Play run cancelled.') {
|
|
10
|
+
super(message);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function isAbortLikeError(error: unknown): boolean {
|
|
15
|
+
if (!error) return false;
|
|
16
|
+
if (error instanceof WorkflowAbortError) return true;
|
|
17
|
+
if (error instanceof Error) {
|
|
18
|
+
if (error.name === 'WorkflowAbort' || error.name === 'AbortError') {
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
return /\b(cancell?ed|aborted|terminate[d]?)\b/i.test(error.message);
|
|
22
|
+
}
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Errors that must stay run-fatal even under default map row failure isolation.
|
|
28
|
+
*
|
|
29
|
+
* Provider/tool HTTP failures, including exhausted 429/5xx retries, are row
|
|
30
|
+
* outcomes after the tool-call Adapter spends its local retry budget. Abort,
|
|
31
|
+
* Governor budget exhaustion, and hard billing failures escape row isolation.
|
|
32
|
+
*/
|
|
33
|
+
export function isRowIsolationExemptError(error: unknown): boolean {
|
|
34
|
+
if (isAbortLikeError(error)) return true;
|
|
35
|
+
if (error instanceof Error && error.name === 'GovernorBudgetError') {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return isHardBillingToolHttpError(error);
|
|
39
|
+
}
|
|
@@ -60,6 +60,15 @@ import {
|
|
|
60
60
|
import { stringifyPostgresJson } from './postgres-json';
|
|
61
61
|
import { RECEIPT_STATUS_CODE, receiptStatusFromCode } from './receipt-status';
|
|
62
62
|
import type { MapRowOutcome } from './durability-store';
|
|
63
|
+
import {
|
|
64
|
+
prepareRuntimeSheetRowTransitions,
|
|
65
|
+
type RuntimePreparedCompletedRow,
|
|
66
|
+
type RuntimePreparedFailedRow,
|
|
67
|
+
} from './runtime-sheet-row-transition';
|
|
68
|
+
import {
|
|
69
|
+
mapRowOutcomeRuntimeFields,
|
|
70
|
+
resolveMapRowOutcomeKey,
|
|
71
|
+
} from './map-row-outcome';
|
|
63
72
|
import {
|
|
64
73
|
DEEPLINE_CELL_META_FIELD,
|
|
65
74
|
cellPolicyFields,
|
|
@@ -92,18 +101,6 @@ type RuntimeDatasetRowEntry = {
|
|
|
92
101
|
row: Record<string, unknown>;
|
|
93
102
|
inputIndex: number;
|
|
94
103
|
};
|
|
95
|
-
type RuntimePreparedCompletedRow = {
|
|
96
|
-
key: string;
|
|
97
|
-
input_index: number | null;
|
|
98
|
-
data_patch: Record<string, unknown>;
|
|
99
|
-
cell_meta_patch: Record<string, unknown>;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
type RuntimePreparedFailedRow = RuntimePreparedCompletedRow & {
|
|
103
|
-
/** Row-level error persisted to `_error`; never empty. */
|
|
104
|
-
error: string;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
104
|
const dbSessionCache = new Map<string, DbSessionCacheEntry>();
|
|
108
105
|
const dbSessionInFlight = new Map<string, Promise<CreateDbSessionResponse>>();
|
|
109
106
|
const postgresPools = new Map<string, RuntimePool>();
|
|
@@ -1680,35 +1677,6 @@ function cachedRuntimeCellMetaPatch(runId: string): Record<string, unknown> {
|
|
|
1680
1677
|
};
|
|
1681
1678
|
}
|
|
1682
1679
|
|
|
1683
|
-
function completedRuntimeCellMetaPatch(input: {
|
|
1684
|
-
runId: string;
|
|
1685
|
-
outputFields: readonly string[];
|
|
1686
|
-
rowPatch?: Record<string, unknown>;
|
|
1687
|
-
}): Record<string, unknown> {
|
|
1688
|
-
const patch: Record<string, unknown> = {};
|
|
1689
|
-
const completedAt = Date.now();
|
|
1690
|
-
for (const field of input.outputFields) {
|
|
1691
|
-
const existing =
|
|
1692
|
-
input.rowPatch?.[field] &&
|
|
1693
|
-
typeof input.rowPatch[field] === 'object' &&
|
|
1694
|
-
!Array.isArray(input.rowPatch[field])
|
|
1695
|
-
? (input.rowPatch[field] as Record<string, unknown>)
|
|
1696
|
-
: {};
|
|
1697
|
-
patch[field] = {
|
|
1698
|
-
status: 'completed',
|
|
1699
|
-
runId: input.runId,
|
|
1700
|
-
completedAt,
|
|
1701
|
-
...existing,
|
|
1702
|
-
};
|
|
1703
|
-
}
|
|
1704
|
-
for (const [field, meta] of Object.entries(input.rowPatch ?? {})) {
|
|
1705
|
-
if (!Object.hasOwn(patch, field)) {
|
|
1706
|
-
patch[field] = meta;
|
|
1707
|
-
}
|
|
1708
|
-
}
|
|
1709
|
-
return patch;
|
|
1710
|
-
}
|
|
1711
|
-
|
|
1712
1680
|
function cachedRuntimeCellMetaUpdateSql(
|
|
1713
1681
|
tableAlias: string,
|
|
1714
1682
|
outputFields: readonly string[],
|
|
@@ -2554,7 +2522,7 @@ async function buildRuntimeSheetDatasetStartResult(
|
|
|
2554
2522
|
),
|
|
2555
2523
|
sheetContract: input.sheetContract,
|
|
2556
2524
|
}),
|
|
2557
|
-
|
|
2525
|
+
...mapRowOutcomeRuntimeFields({ key: entry.key }),
|
|
2558
2526
|
})),
|
|
2559
2527
|
tableNamespace: input.tableNamespace,
|
|
2560
2528
|
};
|
|
@@ -2671,7 +2639,7 @@ async function buildRuntimeSheetDatasetStartResult(
|
|
|
2671
2639
|
),
|
|
2672
2640
|
sheetContract: input.sheetContract,
|
|
2673
2641
|
}),
|
|
2674
|
-
|
|
2642
|
+
...mapRowOutcomeRuntimeFields({ key: entry.key }),
|
|
2675
2643
|
}));
|
|
2676
2644
|
return {
|
|
2677
2645
|
inserted: input.inserted,
|
|
@@ -2683,7 +2651,7 @@ async function buildRuntimeSheetDatasetStartResult(
|
|
|
2683
2651
|
completedData: existingPendingRowsByKey.get(entry.key) ?? {},
|
|
2684
2652
|
sheetContract: input.sheetContract,
|
|
2685
2653
|
}),
|
|
2686
|
-
|
|
2654
|
+
...mapRowOutcomeRuntimeFields({ key: entry.key }),
|
|
2687
2655
|
})),
|
|
2688
2656
|
completedRows,
|
|
2689
2657
|
tableNamespace: input.tableNamespace,
|
|
@@ -3061,9 +3029,8 @@ export async function startRuntimeSheetDataset(
|
|
|
3061
3029
|
// non-enumerable alias fields on the JSON payload boundary.
|
|
3062
3030
|
const cleanedRow = toSerializableCsvAliasedRow(row);
|
|
3063
3031
|
const key =
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
: derivePlayRowIdentity(cleanedRow, input.tableNamespace);
|
|
3032
|
+
resolveMapRowOutcomeKey(row) ??
|
|
3033
|
+
derivePlayRowIdentity(cleanedRow, input.tableNamespace);
|
|
3067
3034
|
if (key && !uniqueRows.has(key)) {
|
|
3068
3035
|
uniqueRows.set(key, cleanedRow);
|
|
3069
3036
|
}
|
|
@@ -3517,14 +3484,6 @@ async function failRuntimeMapRowChunks(
|
|
|
3517
3484
|
return { updated };
|
|
3518
3485
|
}
|
|
3519
3486
|
|
|
3520
|
-
function normalizeRuntimeMapInputIndex(value: unknown): number | null {
|
|
3521
|
-
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
3522
|
-
return null;
|
|
3523
|
-
}
|
|
3524
|
-
const normalized = Math.floor(value);
|
|
3525
|
-
return normalized >= 0 ? normalized : null;
|
|
3526
|
-
}
|
|
3527
|
-
|
|
3528
3487
|
/**
|
|
3529
3488
|
* Mark map rows terminal in the per-run scoped Postgres sheet table by key.
|
|
3530
3489
|
* Mirrors server-side `store.completeSheetRows` semantics: UPDATE-by-key with
|
|
@@ -3603,37 +3562,11 @@ export async function completeRuntimeMapRows(
|
|
|
3603
3562
|
.join(',\n ')}`
|
|
3604
3563
|
: '';
|
|
3605
3564
|
|
|
3606
|
-
const completedRows
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
// sibling cells + the failed cell). Defaulting every output field to
|
|
3612
|
-
// 'completed' here would lie about the failed cell and break re-run
|
|
3613
|
-
// recompute.
|
|
3614
|
-
failedRows.push({
|
|
3615
|
-
key,
|
|
3616
|
-
input_index: normalizeRuntimeMapInputIndex(row.inputIndex),
|
|
3617
|
-
data_patch: row.data,
|
|
3618
|
-
cell_meta_patch: row.cellMetaPatch ?? {},
|
|
3619
|
-
error:
|
|
3620
|
-
typeof row.error === 'string' && row.error.trim()
|
|
3621
|
-
? row.error
|
|
3622
|
-
: 'Row execution failed.',
|
|
3623
|
-
});
|
|
3624
|
-
continue;
|
|
3625
|
-
}
|
|
3626
|
-
completedRows.push({
|
|
3627
|
-
key,
|
|
3628
|
-
input_index: normalizeRuntimeMapInputIndex(row.inputIndex),
|
|
3629
|
-
data_patch: row.data,
|
|
3630
|
-
cell_meta_patch: completedRuntimeCellMetaPatch({
|
|
3631
|
-
runId: input.runId,
|
|
3632
|
-
outputFields: input.outputFields ?? [],
|
|
3633
|
-
rowPatch: row.cellMetaPatch,
|
|
3634
|
-
}),
|
|
3635
|
-
});
|
|
3636
|
-
}
|
|
3565
|
+
const { completedRows, failedRows } = prepareRuntimeSheetRowTransitions({
|
|
3566
|
+
rows: uniqueRows.values(),
|
|
3567
|
+
runId: input.runId,
|
|
3568
|
+
outputFields: input.outputFields ?? [],
|
|
3569
|
+
});
|
|
3637
3570
|
const chunks = chunkValues(completedRows, DIRECT_POSTGRES_BATCH_SIZE);
|
|
3638
3571
|
const failedChunks = chunkValues(failedRows, DIRECT_POSTGRES_BATCH_SIZE);
|
|
3639
3572
|
const needsTransaction = chunks.length + failedChunks.length > 1;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { MapRowOutcome } from './durability-store';
|
|
2
|
+
|
|
3
|
+
export type RuntimePreparedCompletedRow = {
|
|
4
|
+
key: string;
|
|
5
|
+
input_index: number | null;
|
|
6
|
+
data_patch: Record<string, unknown>;
|
|
7
|
+
cell_meta_patch: Record<string, unknown>;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type RuntimePreparedFailedRow = RuntimePreparedCompletedRow & {
|
|
11
|
+
/** Row-level error persisted to `_error`; never empty. */
|
|
12
|
+
error: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function normalizeRuntimeMapInputIndex(value: unknown): number | null {
|
|
16
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
const normalized = Math.floor(value);
|
|
20
|
+
return normalized >= 0 ? normalized : null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function completedRuntimeCellMetaPatch(input: {
|
|
24
|
+
runId: string;
|
|
25
|
+
outputFields: readonly string[];
|
|
26
|
+
rowPatch?: Record<string, unknown>;
|
|
27
|
+
nowMs?: number;
|
|
28
|
+
}): Record<string, unknown> {
|
|
29
|
+
const patch: Record<string, unknown> = {};
|
|
30
|
+
const completedAt = input.nowMs ?? Date.now();
|
|
31
|
+
for (const field of input.outputFields) {
|
|
32
|
+
const existing =
|
|
33
|
+
input.rowPatch?.[field] &&
|
|
34
|
+
typeof input.rowPatch[field] === 'object' &&
|
|
35
|
+
!Array.isArray(input.rowPatch[field])
|
|
36
|
+
? (input.rowPatch[field] as Record<string, unknown>)
|
|
37
|
+
: {};
|
|
38
|
+
patch[field] = {
|
|
39
|
+
status: 'completed',
|
|
40
|
+
runId: input.runId,
|
|
41
|
+
completedAt,
|
|
42
|
+
...existing,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
for (const [field, meta] of Object.entries(input.rowPatch ?? {})) {
|
|
46
|
+
if (!Object.hasOwn(patch, field)) {
|
|
47
|
+
patch[field] = meta;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return patch;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function prepareRuntimeSheetRowTransitions(input: {
|
|
54
|
+
rows: Iterable<MapRowOutcome>;
|
|
55
|
+
runId: string;
|
|
56
|
+
outputFields: readonly string[];
|
|
57
|
+
}): {
|
|
58
|
+
completedRows: RuntimePreparedCompletedRow[];
|
|
59
|
+
failedRows: RuntimePreparedFailedRow[];
|
|
60
|
+
} {
|
|
61
|
+
const completedRows: RuntimePreparedCompletedRow[] = [];
|
|
62
|
+
const failedRows: RuntimePreparedFailedRow[] = [];
|
|
63
|
+
for (const row of input.rows) {
|
|
64
|
+
if (!row.key) continue;
|
|
65
|
+
if (row.status === 'failed') {
|
|
66
|
+
failedRows.push({
|
|
67
|
+
key: row.key,
|
|
68
|
+
input_index: normalizeRuntimeMapInputIndex(row.inputIndex),
|
|
69
|
+
data_patch: row.data,
|
|
70
|
+
cell_meta_patch: row.cellMetaPatch ?? {},
|
|
71
|
+
error:
|
|
72
|
+
typeof row.error === 'string' && row.error.trim()
|
|
73
|
+
? row.error
|
|
74
|
+
: 'Row execution failed.',
|
|
75
|
+
});
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
completedRows.push({
|
|
79
|
+
key: row.key,
|
|
80
|
+
input_index: normalizeRuntimeMapInputIndex(row.inputIndex),
|
|
81
|
+
data_patch: row.data,
|
|
82
|
+
cell_meta_patch: completedRuntimeCellMetaPatch({
|
|
83
|
+
runId: input.runId,
|
|
84
|
+
outputFields: input.outputFields,
|
|
85
|
+
rowPatch: row.cellMetaPatch,
|
|
86
|
+
}),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return { completedRows, failedRows };
|
|
90
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export type RuntimeSheetSessionScope<TPreloadedSession = unknown> = {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
executorToken: string;
|
|
4
|
+
orgId: string;
|
|
5
|
+
preloadedDbSessions?: TPreloadedSession[] | null;
|
|
6
|
+
playName: string;
|
|
7
|
+
runId: string;
|
|
8
|
+
userEmail?: string | null;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
function requireRuntimeSheetSessionField(
|
|
12
|
+
fieldName: string,
|
|
13
|
+
value: string,
|
|
14
|
+
): string {
|
|
15
|
+
const trimmed = value.trim();
|
|
16
|
+
if (!trimmed) {
|
|
17
|
+
throw new Error(`Runtime Sheet Session requires ${fieldName}.`);
|
|
18
|
+
}
|
|
19
|
+
return trimmed;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function runtimeSheetSessionScope<TPreloadedSession = unknown>(input: {
|
|
23
|
+
baseUrl: string;
|
|
24
|
+
executorToken: string;
|
|
25
|
+
orgId: string;
|
|
26
|
+
preloadedDbSessions?: TPreloadedSession[] | null;
|
|
27
|
+
playName: string;
|
|
28
|
+
runId: string;
|
|
29
|
+
userEmail?: string | null;
|
|
30
|
+
}): RuntimeSheetSessionScope<TPreloadedSession> {
|
|
31
|
+
return {
|
|
32
|
+
baseUrl: requireRuntimeSheetSessionField('baseUrl', input.baseUrl),
|
|
33
|
+
executorToken: requireRuntimeSheetSessionField(
|
|
34
|
+
'executorToken',
|
|
35
|
+
input.executorToken,
|
|
36
|
+
),
|
|
37
|
+
orgId: requireRuntimeSheetSessionField('orgId', input.orgId),
|
|
38
|
+
preloadedDbSessions: input.preloadedDbSessions ?? null,
|
|
39
|
+
playName: requireRuntimeSheetSessionField('playName', input.playName),
|
|
40
|
+
runId: requireRuntimeSheetSessionField('runId', input.runId),
|
|
41
|
+
userEmail: input.userEmail ?? null,
|
|
42
|
+
};
|
|
43
|
+
}
|