@open-mercato/core 0.10.1-develop.7707.1.61845a3ef7 → 0.10.1-develop.7709.1.046124599c
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/modules/customers/api/interactions/conflicts/route.js +15 -1
- package/dist/modules/customers/api/interactions/conflicts/route.js.map +2 -2
- package/dist/modules/workflows/lib/activity-executor.js +78 -51
- package/dist/modules/workflows/lib/activity-executor.js.map +2 -2
- package/dist/modules/workflows/lib/activity-registry.js.map +2 -2
- package/dist/modules/workflows/lib/activity-types.js +4 -1
- package/dist/modules/workflows/lib/activity-types.js.map +2 -2
- package/dist/modules/workflows/lib/activity-worker-handler.js +1 -1
- package/dist/modules/workflows/lib/activity-worker-handler.js.map +2 -2
- package/package.json +7 -7
- package/src/modules/customers/api/interactions/conflicts/route.ts +16 -1
- package/src/modules/workflows/lib/activity-executor.ts +89 -58
- package/src/modules/workflows/lib/activity-registry.ts +1 -0
- package/src/modules/workflows/lib/activity-types.ts +4 -1
- package/src/modules/workflows/lib/activity-worker-handler.ts +2 -2
|
@@ -382,7 +382,7 @@ export async function enqueueActivity(
|
|
|
382
382
|
workflowContext,
|
|
383
383
|
stepContext,
|
|
384
384
|
retryPolicy: activity.retryPolicy,
|
|
385
|
-
timeoutMs: activity
|
|
385
|
+
timeoutMs: resolveActivityTimeoutMs(activity),
|
|
386
386
|
tenantId: workflowInstance.tenantId,
|
|
387
387
|
organizationId: workflowInstance.organizationId,
|
|
388
388
|
userId: context.userId,
|
|
@@ -851,6 +851,7 @@ async function executeActivityByType(
|
|
|
851
851
|
em: em as PostgreSqlEntityManager,
|
|
852
852
|
container,
|
|
853
853
|
signal,
|
|
854
|
+
timeoutMs: resolveActivityTimeoutMs(activity),
|
|
854
855
|
})
|
|
855
856
|
}
|
|
856
857
|
|
|
@@ -1215,6 +1216,38 @@ export type CallWebhookDeps = {
|
|
|
1215
1216
|
allowPrivate?: boolean
|
|
1216
1217
|
fetchImpl?: typeof fetch
|
|
1217
1218
|
signal?: AbortSignal
|
|
1219
|
+
timeoutMs?: number
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
const WEBHOOK_TIMEOUT_MS = 30_000
|
|
1223
|
+
const WEBHOOK_MAX_RESPONSE_BYTES = 1024 * 1024
|
|
1224
|
+
|
|
1225
|
+
async function readWebhookResponse(response: Response, signal: AbortSignal): Promise<string> {
|
|
1226
|
+
const declaredLength = Number(response.headers.get('content-length'))
|
|
1227
|
+
if (declaredLength > WEBHOOK_MAX_RESPONSE_BYTES) {
|
|
1228
|
+
throw new Error(`[internal] CALL_WEBHOOK response exceeds ${WEBHOOK_MAX_RESPONSE_BYTES} bytes`)
|
|
1229
|
+
}
|
|
1230
|
+
if (!response.body) return ''
|
|
1231
|
+
|
|
1232
|
+
const reader = response.body.getReader()
|
|
1233
|
+
const chunks: Uint8Array[] = []
|
|
1234
|
+
let totalBytes = 0
|
|
1235
|
+
try {
|
|
1236
|
+
for (;;) {
|
|
1237
|
+
throwIfAborted(signal)
|
|
1238
|
+
const { done, value } = await raceAbortable(reader.read(), signal)
|
|
1239
|
+
if (done) break
|
|
1240
|
+
totalBytes += value.byteLength
|
|
1241
|
+
if (totalBytes > WEBHOOK_MAX_RESPONSE_BYTES) {
|
|
1242
|
+
throw new Error(`[internal] CALL_WEBHOOK response exceeds ${WEBHOOK_MAX_RESPONSE_BYTES} bytes`)
|
|
1243
|
+
}
|
|
1244
|
+
chunks.push(value)
|
|
1245
|
+
}
|
|
1246
|
+
return new TextDecoder().decode(Buffer.concat(chunks, totalBytes))
|
|
1247
|
+
} finally {
|
|
1248
|
+
void reader.cancel().catch(() => undefined)
|
|
1249
|
+
reader.releaseLock()
|
|
1250
|
+
}
|
|
1218
1251
|
}
|
|
1219
1252
|
|
|
1220
1253
|
export async function executeCallWebhook(
|
|
@@ -1234,67 +1267,65 @@ export async function executeCallWebhook(
|
|
|
1234
1267
|
|
|
1235
1268
|
const allowPrivate = deps.allowPrivate ?? isAllowPrivateWorkflowWebhookUrlsEnabled()
|
|
1236
1269
|
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
method,
|
|
1243
|
-
headers: {
|
|
1244
|
-
'Content-Type': 'application/json',
|
|
1245
|
-
...headers,
|
|
1246
|
-
},
|
|
1247
|
-
body: body !== undefined && body !== null ? JSON.stringify(body) : undefined,
|
|
1248
|
-
redirect: 'manual',
|
|
1249
|
-
signal: deps.signal,
|
|
1250
|
-
},
|
|
1251
|
-
{
|
|
1252
|
-
subject: 'Workflow webhook URL',
|
|
1253
|
-
allowPrivate,
|
|
1254
|
-
lookupHost: deps.lookupHost,
|
|
1255
|
-
fetchImpl: deps.fetchImpl,
|
|
1256
|
-
},
|
|
1257
|
-
)
|
|
1258
|
-
} catch (error) {
|
|
1259
|
-
if (error instanceof UnsafeOutboundUrlError) {
|
|
1260
|
-
throw new Error(
|
|
1261
|
-
`CALL_WEBHOOK rejected unsafe URL (reason=${error.reason}): ${error.message}`
|
|
1262
|
-
)
|
|
1263
|
-
}
|
|
1264
|
-
throw error
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
if (response.status >= 300 && response.status < 400) {
|
|
1268
|
-
const location = response.headers.get('location')
|
|
1269
|
-
throw new Error(
|
|
1270
|
-
`CALL_WEBHOOK refused to follow redirect ${response.status} to ${
|
|
1271
|
-
location ?? '(no Location header)'
|
|
1272
|
-
}`
|
|
1273
|
-
)
|
|
1274
|
-
}
|
|
1270
|
+
return executeWithTimeout(async (deadlineSignal) => {
|
|
1271
|
+
const signal = deps.signal
|
|
1272
|
+
? AbortSignal.any([deadlineSignal, deps.signal])
|
|
1273
|
+
: deadlineSignal
|
|
1274
|
+
throwIfAborted(signal)
|
|
1275
1275
|
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1276
|
+
let response: Response | undefined
|
|
1277
|
+
try {
|
|
1278
|
+
response = await raceAbortable(safeOutboundFetch(
|
|
1279
|
+
url,
|
|
1280
|
+
{
|
|
1281
|
+
method,
|
|
1282
|
+
headers: {
|
|
1283
|
+
'Content-Type': 'application/json',
|
|
1284
|
+
...headers,
|
|
1285
|
+
},
|
|
1286
|
+
body: body !== undefined && body !== null ? JSON.stringify(body) : undefined,
|
|
1287
|
+
redirect: 'manual',
|
|
1288
|
+
signal,
|
|
1289
|
+
},
|
|
1290
|
+
{
|
|
1291
|
+
subject: 'Workflow webhook URL',
|
|
1292
|
+
allowPrivate,
|
|
1293
|
+
lookupHost: deps.lookupHost,
|
|
1294
|
+
fetchImpl: deps.fetchImpl,
|
|
1295
|
+
},
|
|
1296
|
+
), signal)
|
|
1297
|
+
|
|
1298
|
+
if (response.status >= 300 && response.status < 400) {
|
|
1299
|
+
const location = response.headers.get('location')
|
|
1300
|
+
throw new Error(
|
|
1301
|
+
`CALL_WEBHOOK refused to follow redirect ${response.status} to ${
|
|
1302
|
+
location ?? '(no Location header)'
|
|
1303
|
+
}`
|
|
1304
|
+
)
|
|
1305
|
+
}
|
|
1279
1306
|
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
result = await response.text()
|
|
1284
|
-
}
|
|
1307
|
+
const text = await readWebhookResponse(response, signal)
|
|
1308
|
+
const contentType = response.headers.get('content-type')
|
|
1309
|
+
const result: unknown = contentType?.includes('application/json') ? JSON.parse(text) : text
|
|
1285
1310
|
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
}
|
|
1311
|
+
if (!response.ok) {
|
|
1312
|
+
throw new Error(
|
|
1313
|
+
`Webhook request failed with status ${response.status}: ${JSON.stringify(result)}`
|
|
1314
|
+
)
|
|
1315
|
+
}
|
|
1292
1316
|
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1317
|
+
return { status: response.status, statusText: response.statusText, result }
|
|
1318
|
+
} catch (error) {
|
|
1319
|
+
if (error instanceof UnsafeOutboundUrlError) {
|
|
1320
|
+
throw new Error(
|
|
1321
|
+
`CALL_WEBHOOK rejected unsafe URL (reason=${error.reason}): ${error.message}`
|
|
1322
|
+
)
|
|
1323
|
+
}
|
|
1324
|
+
throw error
|
|
1325
|
+
} finally {
|
|
1326
|
+
void response?.body?.cancel().catch(() => undefined)
|
|
1327
|
+
}
|
|
1328
|
+
}, deps.timeoutMs && deps.timeoutMs > 0 ? deps.timeoutMs : WEBHOOK_TIMEOUT_MS)
|
|
1298
1329
|
}
|
|
1299
1330
|
|
|
1300
1331
|
/**
|
|
@@ -301,7 +301,10 @@ export function registerBuiltinActivityTypes(): void {
|
|
|
301
301
|
{ id: 'headers', component: 'keyValue' },
|
|
302
302
|
{ id: 'body', component: 'json' },
|
|
303
303
|
],
|
|
304
|
-
execute: async (config, ctx, deps) => (await loadExecutor()).executeCallWebhook(config, ctx, {
|
|
304
|
+
execute: async (config, ctx, deps) => (await loadExecutor()).executeCallWebhook(config, ctx, {
|
|
305
|
+
signal: deps.signal,
|
|
306
|
+
timeoutMs: deps.timeoutMs,
|
|
307
|
+
}),
|
|
305
308
|
async: { capable: true },
|
|
306
309
|
mock: (config) => {
|
|
307
310
|
const record = asConfigRecord(config)
|
|
@@ -47,7 +47,7 @@ const INVOKE_AGENT_QUEUE_MAX_ATTEMPTS = 3
|
|
|
47
47
|
* place, so callers never need their own side-effect imports.
|
|
48
48
|
*/
|
|
49
49
|
export async function executeRegistryActivity(
|
|
50
|
-
payload: Pick<WorkflowActivityJobActivity, 'activityType' | 'activityConfig'>,
|
|
50
|
+
payload: Pick<WorkflowActivityJobActivity, 'activityType' | 'activityConfig' | 'timeoutMs'>,
|
|
51
51
|
activityContext: ActivityContext,
|
|
52
52
|
deps: ActivityExecuteDeps
|
|
53
53
|
): Promise<unknown> {
|
|
@@ -61,7 +61,7 @@ export async function executeRegistryActivity(
|
|
|
61
61
|
)
|
|
62
62
|
}
|
|
63
63
|
const runActivity = entry.executeAsync ?? entry.execute
|
|
64
|
-
return await runActivity(payload.activityConfig, activityContext, deps)
|
|
64
|
+
return await runActivity(payload.activityConfig, activityContext, { ...deps, timeoutMs: payload.timeoutMs })
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/**
|