velocious 1.0.661 → 1.0.663
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/README.md +12 -1
- package/build/application.js +1 -1
- package/build/background-jobs/adapter.js +9 -0
- package/build/background-jobs/job-runner.js +3 -1
- package/build/background-jobs/local-adapter.js +7 -0
- package/build/background-jobs/local-store.js +113 -3
- package/build/background-jobs/main.js +35 -1
- package/build/background-jobs/pooled-runner-child.js +38 -0
- package/build/background-jobs/status-reporter.js +97 -1
- package/build/background-jobs/store.js +130 -4
- package/build/background-jobs/types.js +6 -1
- package/build/background-jobs/web/controller.js +4 -0
- package/build/background-jobs/worker.js +59 -0
- package/build/configuration-types.js +3 -1
- package/build/configuration.js +33 -0
- package/build/http-server/client/errors.js +27 -0
- package/build/http-server/client/index.js +26 -1
- package/build/http-server/client/request-buffer/index.js +38 -6
- package/build/http-server/client/request-runner.js +8 -1
- package/build/http-server/client/response.js +11 -0
- package/build/routes/resolver.js +1 -1
- package/build/src/application.js +2 -2
- package/build/src/background-jobs/adapter.d.ts +17 -0
- package/build/src/background-jobs/adapter.d.ts.map +1 -1
- package/build/src/background-jobs/adapter.js +9 -1
- package/build/src/background-jobs/job-runner.d.ts +3 -1
- package/build/src/background-jobs/job-runner.d.ts.map +1 -1
- package/build/src/background-jobs/job-runner.js +5 -2
- package/build/src/background-jobs/local-adapter.d.ts +13 -0
- package/build/src/background-jobs/local-adapter.d.ts.map +1 -1
- package/build/src/background-jobs/local-adapter.js +7 -1
- package/build/src/background-jobs/local-store.d.ts +38 -0
- package/build/src/background-jobs/local-store.d.ts.map +1 -1
- package/build/src/background-jobs/local-store.js +117 -4
- package/build/src/background-jobs/main.d.ts +14 -0
- package/build/src/background-jobs/main.d.ts.map +1 -1
- package/build/src/background-jobs/main.js +35 -2
- package/build/src/background-jobs/pooled-runner-child.js +38 -1
- package/build/src/background-jobs/status-reporter.d.ts +51 -1
- package/build/src/background-jobs/status-reporter.d.ts.map +1 -1
- package/build/src/background-jobs/status-reporter.js +89 -2
- package/build/src/background-jobs/store.d.ts +48 -0
- package/build/src/background-jobs/store.d.ts.map +1 -1
- package/build/src/background-jobs/store.js +128 -5
- package/build/src/background-jobs/types.d.ts +34 -2
- package/build/src/background-jobs/types.d.ts.map +1 -1
- package/build/src/background-jobs/types.js +7 -2
- package/build/src/background-jobs/web/controller.d.ts.map +1 -1
- package/build/src/background-jobs/web/controller.js +5 -1
- package/build/src/background-jobs/worker.d.ts +20 -0
- package/build/src/background-jobs/worker.d.ts.map +1 -1
- package/build/src/background-jobs/worker.js +58 -1
- package/build/src/configuration-types.d.ts +13 -3
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +4 -2
- package/build/src/configuration.d.ts +14 -2
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +31 -1
- package/build/src/http-server/client/errors.d.ts +25 -0
- package/build/src/http-server/client/errors.d.ts.map +1 -0
- package/build/src/http-server/client/errors.js +26 -0
- package/build/src/http-server/client/index.d.ts +5 -0
- package/build/src/http-server/client/index.d.ts.map +1 -1
- package/build/src/http-server/client/index.js +26 -2
- package/build/src/http-server/client/request-buffer/index.d.ts +13 -0
- package/build/src/http-server/client/request-buffer/index.d.ts.map +1 -1
- package/build/src/http-server/client/request-buffer/index.js +34 -8
- package/build/src/http-server/client/request-runner.d.ts.map +1 -1
- package/build/src/http-server/client/request-runner.js +10 -2
- package/build/src/http-server/client/response.d.ts.map +1 -1
- package/build/src/http-server/client/response.js +8 -1
- package/build/src/routes/resolver.js +2 -2
- package/package.json +1 -1
- package/src/application.js +1 -1
- package/src/background-jobs/adapter.js +9 -0
- package/src/background-jobs/job-runner.js +3 -1
- package/src/background-jobs/local-adapter.js +7 -0
- package/src/background-jobs/local-store.js +113 -3
- package/src/background-jobs/main.js +35 -1
- package/src/background-jobs/pooled-runner-child.js +38 -0
- package/src/background-jobs/status-reporter.js +97 -1
- package/src/background-jobs/store.js +130 -4
- package/src/background-jobs/types.js +6 -1
- package/src/background-jobs/web/controller.js +4 -0
- package/src/background-jobs/worker.js +59 -0
- package/src/configuration-types.js +3 -1
- package/src/configuration.js +33 -0
- package/src/http-server/client/errors.js +27 -0
- package/src/http-server/client/index.js +26 -1
- package/src/http-server/client/request-buffer/index.js +38 -6
- package/src/http-server/client/request-runner.js +8 -1
- package/src/http-server/client/response.js +11 -0
- package/src/routes/resolver.js +1 -1
|
@@ -1255,7 +1255,8 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1255
1255
|
status: "handed_off",
|
|
1256
1256
|
handed_off_at_ms: handedOffAtMs,
|
|
1257
1257
|
handoff_id: handoffId,
|
|
1258
|
-
worker_id: workerId || null
|
|
1258
|
+
worker_id: workerId || null,
|
|
1259
|
+
...this._clearedChildAcceptanceData()
|
|
1259
1260
|
},
|
|
1260
1261
|
conditions: {concurrency_key: queuedJob.concurrencyKey, id: jobId, status: "queued"}
|
|
1261
1262
|
})
|
|
@@ -1269,6 +1270,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1269
1270
|
/** @type {import("./types.js").BackgroundJobRow} */
|
|
1270
1271
|
const handedOffJob = {
|
|
1271
1272
|
...queuedJob,
|
|
1273
|
+
...this._clearedChildAcceptanceRow(),
|
|
1272
1274
|
handedOffAtMs,
|
|
1273
1275
|
handoffId,
|
|
1274
1276
|
status: "handed_off",
|
|
@@ -1315,6 +1317,67 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1315
1317
|
})
|
|
1316
1318
|
}
|
|
1317
1319
|
|
|
1320
|
+
/**
|
|
1321
|
+
* Records pooled-child acceptance evidence for an active handoff: when the
|
|
1322
|
+
* executing runner child received and/or started the job, plus that child's
|
|
1323
|
+
* stable identity and pid. Only the fields supplied are written, so a
|
|
1324
|
+
* received-then-started observation lands as two fenced partial updates. The
|
|
1325
|
+
* update is fenced by the exact active handoff lease, so a report for a
|
|
1326
|
+
* reclaimed or re-handed-off job is dropped instead of stamping the wrong
|
|
1327
|
+
* attempt.
|
|
1328
|
+
* @param {object} args - Options.
|
|
1329
|
+
* @param {string} args.jobId - Job id.
|
|
1330
|
+
* @param {string} [args.handoffId] - Handoff lease id.
|
|
1331
|
+
* @param {string} [args.workerId] - Worker id.
|
|
1332
|
+
* @param {number} [args.handedOffAtMs] - Handed off timestamp.
|
|
1333
|
+
* @param {number} [args.receivedAtMs] - Epoch ms the runner child received the job.
|
|
1334
|
+
* @param {number} [args.startedAtMs] - Epoch ms the job's perform started in the child.
|
|
1335
|
+
* @param {string} [args.childInstanceId] - Stable pooled child identity.
|
|
1336
|
+
* @param {number} [args.childPid] - Pooled child OS pid.
|
|
1337
|
+
* @returns {Promise<boolean>} - Whether the fenced report was accepted.
|
|
1338
|
+
*/
|
|
1339
|
+
async markChildAccepted({jobId, handoffId, workerId, handedOffAtMs, receivedAtMs, startedAtMs, childInstanceId, childPid}) {
|
|
1340
|
+
await this.ensureReady()
|
|
1341
|
+
|
|
1342
|
+
return await this._serializedConnectionMutation(async (db) => {
|
|
1343
|
+
const job = await this._getJobRowById(db, jobId)
|
|
1344
|
+
|
|
1345
|
+
if (!job) return false
|
|
1346
|
+
if (!this._shouldAcceptReport({job, handoffId, workerId, handedOffAtMs})) return false
|
|
1347
|
+
|
|
1348
|
+
const data = {}
|
|
1349
|
+
if (typeof receivedAtMs === "number") data.child_received_at_ms = receivedAtMs
|
|
1350
|
+
if (typeof startedAtMs === "number") data.child_started_at_ms = startedAtMs
|
|
1351
|
+
if (typeof childInstanceId === "string") data.child_instance_id = childInstanceId
|
|
1352
|
+
if (typeof childPid === "number") data.child_pid = childPid
|
|
1353
|
+
if (Object.keys(data).length === 0) return false
|
|
1354
|
+
|
|
1355
|
+
const affectedRows = await this._updateAffectedRows(db, {
|
|
1356
|
+
tableName: JOBS_TABLE,
|
|
1357
|
+
data,
|
|
1358
|
+
conditions: this._activeHandoffConditions(job)
|
|
1359
|
+
})
|
|
1360
|
+
|
|
1361
|
+
return affectedRows === 1
|
|
1362
|
+
})
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
/**
|
|
1366
|
+
* Returns the database data that clears pooled-child acceptance evidence.
|
|
1367
|
+
* @returns {Record<string, ReturnType<typeof JSON.parse>>} - Cleared acceptance columns.
|
|
1368
|
+
*/
|
|
1369
|
+
_clearedChildAcceptanceData() {
|
|
1370
|
+
return {child_instance_id: null, child_pid: null, child_received_at_ms: null, child_started_at_ms: null}
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
/**
|
|
1374
|
+
* Returns the row-shape counterpart of the cleared acceptance columns.
|
|
1375
|
+
* @returns {Pick<import("./types.js").BackgroundJobRow, "childInstanceId" | "childPid" | "childReceivedAtMs" | "childStartedAtMs">} - Cleared acceptance fields.
|
|
1376
|
+
*/
|
|
1377
|
+
_clearedChildAcceptanceRow() {
|
|
1378
|
+
return {childInstanceId: null, childPid: null, childReceivedAtMs: null, childStartedAtMs: null}
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1318
1381
|
/**
|
|
1319
1382
|
* Returns an active handoff to the queue at a caller-requested future time.
|
|
1320
1383
|
* This is normal job control flow: it preserves failure attempts and metadata.
|
|
@@ -1345,7 +1408,8 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1345
1408
|
scheduled_at_ms: scheduledAtMs,
|
|
1346
1409
|
handed_off_at_ms: null,
|
|
1347
1410
|
handoff_id: null,
|
|
1348
|
-
worker_id: null
|
|
1411
|
+
worker_id: null,
|
|
1412
|
+
...this._clearedChildAcceptanceData()
|
|
1349
1413
|
},
|
|
1350
1414
|
conditions: this._activeHandoffConditions(job)
|
|
1351
1415
|
})
|
|
@@ -1378,7 +1442,8 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
1378
1442
|
scheduled_at_ms: this.clock.now(),
|
|
1379
1443
|
handed_off_at_ms: null,
|
|
1380
1444
|
handoff_id: null,
|
|
1381
|
-
worker_id: null
|
|
1445
|
+
worker_id: null,
|
|
1446
|
+
...this._clearedChildAcceptanceData()
|
|
1382
1447
|
},
|
|
1383
1448
|
conditions: {handoff_id: handoffId, id: jobId, status: "handed_off"}
|
|
1384
1449
|
})
|
|
@@ -2063,6 +2128,10 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2063
2128
|
table.string("concurrency_key", {null: true, index: true})
|
|
2064
2129
|
table.integer("max_concurrency", {null: true})
|
|
2065
2130
|
table.bigint("timeout_ms", {null: true})
|
|
2131
|
+
table.bigint("child_received_at_ms", {null: true})
|
|
2132
|
+
table.bigint("child_started_at_ms", {null: true})
|
|
2133
|
+
table.string("child_instance_id", {null: true})
|
|
2134
|
+
table.integer("child_pid", {null: true})
|
|
2066
2135
|
|
|
2067
2136
|
await db.createTable(table)
|
|
2068
2137
|
}
|
|
@@ -2156,9 +2225,56 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2156
2225
|
await this._ensureQueueColumn(db)
|
|
2157
2226
|
await this._ensureScheduleKeyColumn(db)
|
|
2158
2227
|
await this._ensureJobTimeoutColumn(db)
|
|
2228
|
+
await this._ensureChildAcceptanceColumns(db)
|
|
2159
2229
|
await this._ensureJobsTableIndexesOnce(db)
|
|
2160
2230
|
}
|
|
2161
2231
|
|
|
2232
|
+
/**
|
|
2233
|
+
* Idempotently adds the pooled-child acceptance evidence columns to existing
|
|
2234
|
+
* job tables. They record when the executing runner child received and
|
|
2235
|
+
* started a job plus that child's identity, so a handed-off job can be told
|
|
2236
|
+
* apart from one whose runner never picked it up.
|
|
2237
|
+
* @param {import("../database/drivers/base.js").default} db - Database connection.
|
|
2238
|
+
* @returns {Promise<void>} - Resolves when ensured.
|
|
2239
|
+
*/
|
|
2240
|
+
async _ensureChildAcceptanceColumns(db) {
|
|
2241
|
+
const lockName = `${MIGRATION_SCOPE}:child_acceptance_columns`
|
|
2242
|
+
const acquired = await db.acquireAdvisoryLock(lockName)
|
|
2243
|
+
|
|
2244
|
+
if (!acquired) throw new Error("Failed to acquire background jobs child-acceptance schema lock")
|
|
2245
|
+
|
|
2246
|
+
try {
|
|
2247
|
+
db.clearSchemaCache()
|
|
2248
|
+
const table = await db.getTableByNameOrFail(JOBS_TABLE)
|
|
2249
|
+
const tableData = new TableData(JOBS_TABLE)
|
|
2250
|
+
let added = false
|
|
2251
|
+
|
|
2252
|
+
if (!(await table.getColumnByName("child_received_at_ms"))) {
|
|
2253
|
+
tableData.bigint("child_received_at_ms", {null: true})
|
|
2254
|
+
added = true
|
|
2255
|
+
}
|
|
2256
|
+
if (!(await table.getColumnByName("child_started_at_ms"))) {
|
|
2257
|
+
tableData.bigint("child_started_at_ms", {null: true})
|
|
2258
|
+
added = true
|
|
2259
|
+
}
|
|
2260
|
+
if (!(await table.getColumnByName("child_instance_id"))) {
|
|
2261
|
+
tableData.string("child_instance_id", {null: true})
|
|
2262
|
+
added = true
|
|
2263
|
+
}
|
|
2264
|
+
if (!(await table.getColumnByName("child_pid"))) {
|
|
2265
|
+
tableData.integer("child_pid", {null: true})
|
|
2266
|
+
added = true
|
|
2267
|
+
}
|
|
2268
|
+
|
|
2269
|
+
if (added) {
|
|
2270
|
+
for (const sql of await db.alterTableSQLs(tableData)) await db.query(sql)
|
|
2271
|
+
db.clearSchemaCache()
|
|
2272
|
+
}
|
|
2273
|
+
} finally {
|
|
2274
|
+
await db.releaseAdvisoryLock(lockName)
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2162
2278
|
/**
|
|
2163
2279
|
* Repairs secondary indexes that older add-column upgrades declared but did
|
|
2164
2280
|
* not create on every SQL driver. The migration ledger keeps routine store
|
|
@@ -2514,6 +2630,7 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2514
2630
|
/** @type {import("./types.js").BackgroundJobRow} */
|
|
2515
2631
|
const transitionedJob = {
|
|
2516
2632
|
...job,
|
|
2633
|
+
...(shouldRetry ? this._clearedChildAcceptanceRow() : {}),
|
|
2517
2634
|
attempts: nextAttempt,
|
|
2518
2635
|
handedOffAtMs: null,
|
|
2519
2636
|
lastError: failureMessage,
|
|
@@ -2553,6 +2670,11 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2553
2670
|
last_error: failureMessage
|
|
2554
2671
|
}
|
|
2555
2672
|
|
|
2673
|
+
// A retry starts a fresh handoff with a possibly different runner, so the
|
|
2674
|
+
// previous child's acceptance evidence must not leak into the next attempt.
|
|
2675
|
+
// Terminal failures keep it as historical evidence for the lost attempt.
|
|
2676
|
+
if (shouldRetry) Object.assign(update, this._clearedChildAcceptanceData())
|
|
2677
|
+
|
|
2556
2678
|
this._applyOrphanedFailureUpdate({markOrphaned, now, update})
|
|
2557
2679
|
this._applyFailureStatusUpdate({markOrphaned, now, scheduledAt, shouldRetry, update})
|
|
2558
2680
|
|
|
@@ -2630,7 +2752,11 @@ export default class BackgroundJobsStore extends BackgroundJobsAdapter {
|
|
|
2630
2752
|
lastError: row.last_error ? String(row.last_error) : null,
|
|
2631
2753
|
concurrencyKey: row.concurrency_key ? String(row.concurrency_key) : null,
|
|
2632
2754
|
maxConcurrency: this._normalizeNumber(row.max_concurrency),
|
|
2633
|
-
timeoutMs: this._normalizeNumber(row.timeout_ms)
|
|
2755
|
+
timeoutMs: this._normalizeNumber(row.timeout_ms),
|
|
2756
|
+
childReceivedAtMs: this._normalizeNumber(row.child_received_at_ms),
|
|
2757
|
+
childStartedAtMs: this._normalizeNumber(row.child_started_at_ms),
|
|
2758
|
+
childInstanceId: row.child_instance_id ? String(row.child_instance_id) : null,
|
|
2759
|
+
childPid: this._normalizeNumber(row.child_pid)
|
|
2634
2760
|
}
|
|
2635
2761
|
}
|
|
2636
2762
|
|
|
@@ -169,6 +169,10 @@
|
|
|
169
169
|
* @property {string | null} concurrencyKey - Durable concurrency key.
|
|
170
170
|
* @property {number | null} maxConcurrency - Durable per-key cap.
|
|
171
171
|
* @property {number | null} timeoutMs - Per-job wall-clock timeout override, or null when omitted.
|
|
172
|
+
* @property {number | null} childReceivedAtMs - Epoch ms when the executing pooled child's event loop processed the job message, or null when no runner accepted it yet.
|
|
173
|
+
* @property {number | null} childStartedAtMs - Epoch ms when the job's perform started in the pooled child, or null when it never started.
|
|
174
|
+
* @property {string | null} childInstanceId - Stable identity of the pooled child process that accepted the job, or null.
|
|
175
|
+
* @property {number | null} childPid - OS pid of the pooled child process that accepted the job, or null.
|
|
172
176
|
*/
|
|
173
177
|
/**
|
|
174
178
|
* @typedef {"queued" | "handed_off" | null} BackgroundJobReplacementPreviousStatus
|
|
@@ -219,6 +223,7 @@
|
|
|
219
223
|
* @typedef {{type: "schedule-cancelled", jobId: string | null, outcome: BackgroundJobCancellationOutcome}} BackgroundJobScheduleCancelledMessage
|
|
220
224
|
* @typedef {{type: "cancel-scheduled-error", error?: string}} BackgroundJobCancelScheduledErrorMessage
|
|
221
225
|
* @typedef {{type: "job", payload: BackgroundJobPayload}} BackgroundJobJobMessage
|
|
226
|
+
* @typedef {{type: "job-accepted", jobId: string, handoffId?: string, workerId?: string, handedOffAtMs?: number, receivedAtMs?: number, startedAtMs?: number, childInstanceId?: string, childPid?: number}} BackgroundJobAcceptedMessage
|
|
222
227
|
* @typedef {{type: "job-complete", jobId: string, handoffId?: string, workerId?: string, handedOffAtMs?: number}} BackgroundJobCompleteMessage
|
|
223
228
|
* @typedef {{type: "job-failed", jobId: string, error?: ReturnType<typeof JSON.parse>, handoffId?: string, workerId?: string, handedOffAtMs?: number, runnerFailure?: PooledRunnerFailure}} BackgroundJobFailedMessage
|
|
224
229
|
* @typedef {{type: "job-reschedule", jobId: string, delayMs: number, handoffId?: string, workerId?: string, handedOffAtMs?: number}} BackgroundJobRescheduleMessage
|
|
@@ -226,7 +231,7 @@
|
|
|
226
231
|
* @typedef {{type: "job-update-error", jobId: string, error?: string}} BackgroundJobUpdateErrorMessage
|
|
227
232
|
*/
|
|
228
233
|
/**
|
|
229
|
-
* @typedef {BackgroundJobHelloMessage | BackgroundJobGenerationAcceptedMessage | BackgroundJobGenerationRejectedMessage | BackgroundJobReadyMessage | BackgroundJobDrainingMessage | BackgroundJobHeartbeatMessage | BackgroundJobEnqueueMessage | BackgroundJobEnqueuedMessage | BackgroundJobEnqueueErrorMessage | BackgroundJobReplaceScheduledMessage | BackgroundJobScheduleReplacedMessage | BackgroundJobReplaceScheduledErrorMessage | BackgroundJobCancelScheduledMessage | BackgroundJobScheduleCancelledMessage | BackgroundJobCancelScheduledErrorMessage | BackgroundJobJobMessage | BackgroundJobCompleteMessage | BackgroundJobFailedMessage | BackgroundJobRescheduleMessage | BackgroundJobUpdatedMessage | BackgroundJobUpdateErrorMessage} BackgroundJobSocketMessage
|
|
234
|
+
* @typedef {BackgroundJobHelloMessage | BackgroundJobGenerationAcceptedMessage | BackgroundJobGenerationRejectedMessage | BackgroundJobReadyMessage | BackgroundJobDrainingMessage | BackgroundJobHeartbeatMessage | BackgroundJobEnqueueMessage | BackgroundJobEnqueuedMessage | BackgroundJobEnqueueErrorMessage | BackgroundJobReplaceScheduledMessage | BackgroundJobScheduleReplacedMessage | BackgroundJobReplaceScheduledErrorMessage | BackgroundJobCancelScheduledMessage | BackgroundJobScheduleCancelledMessage | BackgroundJobCancelScheduledErrorMessage | BackgroundJobJobMessage | BackgroundJobAcceptedMessage | BackgroundJobCompleteMessage | BackgroundJobFailedMessage | BackgroundJobRescheduleMessage | BackgroundJobUpdatedMessage | BackgroundJobUpdateErrorMessage} BackgroundJobSocketMessage
|
|
230
235
|
*/
|
|
231
236
|
|
|
232
237
|
export const nothing = {}
|
|
@@ -192,6 +192,10 @@ export default class VelociousBackgroundJobsWebController extends Controller {
|
|
|
192
192
|
args: redactArgs ? undefined : job.args,
|
|
193
193
|
argsRedacted: redactArgs,
|
|
194
194
|
attempts: job.attempts,
|
|
195
|
+
childInstanceId: job.childInstanceId,
|
|
196
|
+
childPid: job.childPid,
|
|
197
|
+
childReceivedAtMs: job.childReceivedAtMs,
|
|
198
|
+
childStartedAtMs: job.childStartedAtMs,
|
|
195
199
|
completedAtMs: job.completedAtMs,
|
|
196
200
|
createdAtMs: job.createdAtMs,
|
|
197
201
|
executionMode: job.executionMode,
|
|
@@ -56,6 +56,12 @@ const FORKED_RUNNER_ENTRY_PATH = fileURLToPath(new URL("./forked-runner-child.js
|
|
|
56
56
|
const POOLED_RUNNER_ENTRY_PATH = fileURLToPath(new URL("./pooled-runner-child.js", import.meta.url))
|
|
57
57
|
/** How often the worker sends a liveness heartbeat to the main. */
|
|
58
58
|
const HEARTBEAT_INTERVAL_MS = 15000
|
|
59
|
+
/**
|
|
60
|
+
* Max time the worker spends retrying one pooled child's acceptance report
|
|
61
|
+
* before dropping it. Acceptance evidence is diagnostic — a persistent
|
|
62
|
+
* main/DB outage must not hold runner capacity hostage.
|
|
63
|
+
*/
|
|
64
|
+
const CHILD_ACCEPTANCE_REPORT_MAX_DURATION_MS = 10000
|
|
59
65
|
/** TCP keepalive so a half-open connection to the main surfaces as a close. */
|
|
60
66
|
const SOCKET_KEEPALIVE_MS = 10000
|
|
61
67
|
/**
|
|
@@ -72,6 +78,28 @@ function positiveInteger(value) {
|
|
|
72
78
|
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined
|
|
73
79
|
}
|
|
74
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Checks whether an IPC value is a pooled child's acceptance observation for
|
|
83
|
+
* one job. The child carries its exact handoff lease so the worker can forward
|
|
84
|
+
* the report without depending on its in-flight entry still existing.
|
|
85
|
+
* @param {ReturnType<typeof JSON.parse>} message - IPC message.
|
|
86
|
+
* @returns {message is {type: "job-received" | "job-started", jobId: string, handoffId?: string, workerId?: string, handedOffAtMs?: number, receivedAtMs?: number, startedAtMs?: number, childInstanceId?: string, childPid?: number}} - Whether this is a valid acceptance message.
|
|
87
|
+
*/
|
|
88
|
+
function isChildAcceptanceMessage(message) {
|
|
89
|
+
if (!message || typeof message !== "object") return false
|
|
90
|
+
const record = /** @type {Record<string, ReturnType<typeof JSON.parse>>} */ (message)
|
|
91
|
+
|
|
92
|
+
return (record.type === "job-received" || record.type === "job-started")
|
|
93
|
+
&& typeof record.jobId === "string"
|
|
94
|
+
&& (record.handoffId === undefined || typeof record.handoffId === "string")
|
|
95
|
+
&& (record.workerId === undefined || typeof record.workerId === "string")
|
|
96
|
+
&& (record.handedOffAtMs === undefined || typeof record.handedOffAtMs === "number")
|
|
97
|
+
&& (record.receivedAtMs === undefined || typeof record.receivedAtMs === "number")
|
|
98
|
+
&& (record.startedAtMs === undefined || typeof record.startedAtMs === "number")
|
|
99
|
+
&& (record.childInstanceId === undefined || typeof record.childInstanceId === "string")
|
|
100
|
+
&& (record.childPid === undefined || Number.isInteger(record.childPid))
|
|
101
|
+
}
|
|
102
|
+
|
|
75
103
|
/**
|
|
76
104
|
* Normalizes a candidate pooled-runner resource limit.
|
|
77
105
|
* @param {number | undefined} value - Candidate positive number.
|
|
@@ -1225,6 +1253,10 @@ export default class BackgroundJobsWorker {
|
|
|
1225
1253
|
if (state) state.started = true
|
|
1226
1254
|
return
|
|
1227
1255
|
}
|
|
1256
|
+
if (isChildAcceptanceMessage(message)) {
|
|
1257
|
+
this._reportChildAccepted(message)
|
|
1258
|
+
return
|
|
1259
|
+
}
|
|
1228
1260
|
if (record.type !== "job-outcome" || !state || state.settling || typeof record.jobId !== "string") return
|
|
1229
1261
|
state.started = true
|
|
1230
1262
|
const entry = state.inflight.get(record.jobId)
|
|
@@ -1258,6 +1290,33 @@ export default class BackgroundJobsWorker {
|
|
|
1258
1290
|
this._terminateIfDrained(child)
|
|
1259
1291
|
}
|
|
1260
1292
|
|
|
1293
|
+
/**
|
|
1294
|
+
* Forwards one pooled child's acceptance observation to main as a bounded
|
|
1295
|
+
* diagnostic report. The child carries its exact handoff lease, so a timeout
|
|
1296
|
+
* or outcome that already settled the worker's in-flight entry cannot lose
|
|
1297
|
+
* the fencing. A report that never lands degrades phase diagnostics for that
|
|
1298
|
+
* job only — it must never block or fail the job itself.
|
|
1299
|
+
* @param {{type: "job-received" | "job-started", jobId: string, handoffId?: string, workerId?: string, handedOffAtMs?: number, receivedAtMs?: number, startedAtMs?: number, childInstanceId?: string, childPid?: number}} message - Validated child acceptance message.
|
|
1300
|
+
* @returns {void}
|
|
1301
|
+
*/
|
|
1302
|
+
_reportChildAccepted(message) {
|
|
1303
|
+
if (!this.statusReporter) return
|
|
1304
|
+
|
|
1305
|
+
void this.statusReporter.reportChildAcceptedWithRetry({
|
|
1306
|
+
jobId: message.jobId,
|
|
1307
|
+
handoffId: message.handoffId,
|
|
1308
|
+
workerId: message.workerId,
|
|
1309
|
+
handedOffAtMs: message.handedOffAtMs,
|
|
1310
|
+
receivedAtMs: message.receivedAtMs,
|
|
1311
|
+
startedAtMs: message.startedAtMs,
|
|
1312
|
+
childInstanceId: message.childInstanceId,
|
|
1313
|
+
childPid: message.childPid,
|
|
1314
|
+
maxDurationMs: CHILD_ACCEPTANCE_REPORT_MAX_DURATION_MS
|
|
1315
|
+
}).catch((error) => {
|
|
1316
|
+
console.error("Background job child-acceptance reporting failed:", error)
|
|
1317
|
+
})
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1261
1320
|
/**
|
|
1262
1321
|
* Marks a pooled child for retirement and eagerly spawns a single replacement
|
|
1263
1322
|
* (1-for-1) so its capacity is restored immediately without waiting for it to
|
|
@@ -293,6 +293,8 @@
|
|
|
293
293
|
* @property {boolean | HttpCompressionConfiguration} [compression] - Buffered response compression. Enabled with documented defaults when absent; false or {enabled: false} disables it globally.
|
|
294
294
|
* @property {string} [host] - Hostname to bind the HTTP server to.
|
|
295
295
|
* @property {boolean} [inProcess] - Run HTTP handlers in the main thread instead of worker threads.
|
|
296
|
+
* @property {number} [maxBufferedResponseBodyBytes] - Maximum UTF-8/binary byte length accepted by buffered response bodies. Unbounded when absent; streamed file responses are unaffected.
|
|
297
|
+
* @property {number} [maxRequestBodyBytes] - Maximum decoded request-body bytes accepted before routing. Unbounded when absent.
|
|
296
298
|
* @property {number} [maxWorkers] - Backward-compatible alias for workers.
|
|
297
299
|
* @property {number} [port] - Port to bind the HTTP server to.
|
|
298
300
|
* @property {{maxPendingBytes?: number, maxPendingMessages?: number}} [websocketInboundQueue] - Per-session retained inbound WebSocket message limits.
|
|
@@ -735,7 +737,7 @@
|
|
|
735
737
|
* @property {AttachmentsConfiguration} [attachments] - Attachment storage configuration.
|
|
736
738
|
* @property {BackendProjectConfiguration[]} [backendProjects] - Backend project definitions used for frontend model generation.
|
|
737
739
|
* @property {VelociousPackageConfiguration[]} [packages] - External Velocious packages that contribute models, frontend-model resources and migrations.
|
|
738
|
-
* @property {{[key: string]: {[key: string]: DatabaseConfigurationType}}} database - Database configurations keyed by environment and identifier.
|
|
740
|
+
* @property {false | {[key: string]: {[key: string]: DatabaseConfigurationType}}} [database] - Database configurations keyed by environment and identifier, or false for an explicitly database-free application.
|
|
739
741
|
* @property {boolean} [debug] - Enable debug logging.
|
|
740
742
|
* @property {boolean | DebugEndpointConfiguration} [debugEndpoint] - Enable the built-in debug endpoint. Defaults to false.
|
|
741
743
|
* @property {boolean | ApiManifestConfiguration} [apiManifest] - Enable the built-in API manifest endpoint. Defaults to false.
|
package/build/configuration.js
CHANGED
|
@@ -155,6 +155,21 @@ function positiveSafeInteger(value, name, defaultValue) {
|
|
|
155
155
|
return value
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Validates an optional positive safe integer configuration value.
|
|
160
|
+
* @param {ReturnType<typeof JSON.parse>} value - Configured positive safe integer.
|
|
161
|
+
* @param {string} name - Configuration key.
|
|
162
|
+
* @returns {number | undefined} - Validated configured value.
|
|
163
|
+
*/
|
|
164
|
+
function optionalPositiveSafeInteger(value, name) {
|
|
165
|
+
if (value === undefined) return undefined
|
|
166
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) {
|
|
167
|
+
throw new TypeError(`${name} must be a positive safe integer`)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return value
|
|
171
|
+
}
|
|
172
|
+
|
|
158
173
|
/**
|
|
159
174
|
* Validates an integer configuration value inside an inclusive range.
|
|
160
175
|
* @param {ReturnType<typeof JSON.parse>} value - Configured integer.
|
|
@@ -347,6 +362,8 @@ export default class VelociousConfiguration {
|
|
|
347
362
|
this.httpServer = {
|
|
348
363
|
...(httpServer || {}),
|
|
349
364
|
compression: normalizeHttpCompression(httpServer?.compression),
|
|
365
|
+
maxBufferedResponseBodyBytes: optionalPositiveSafeInteger(httpServer?.maxBufferedResponseBodyBytes, "httpServer.maxBufferedResponseBodyBytes"),
|
|
366
|
+
maxRequestBodyBytes: optionalPositiveSafeInteger(httpServer?.maxRequestBodyBytes, "httpServer.maxRequestBodyBytes"),
|
|
350
367
|
websocketInboundQueue: {
|
|
351
368
|
maxPendingBytes: positiveSafeInteger(websocketInboundQueue?.maxPendingBytes, "httpServer.websocketInboundQueue.maxPendingBytes", DEFAULT_WEBSOCKET_INBOUND_MAX_PENDING_BYTES),
|
|
352
369
|
maxPendingMessages: positiveSafeInteger(websocketInboundQueue?.maxPendingMessages, "httpServer.websocketInboundQueue.maxPendingMessages", DEFAULT_WEBSOCKET_INBOUND_MAX_PENDING_MESSAGES)
|
|
@@ -644,6 +661,22 @@ export default class VelociousConfiguration {
|
|
|
644
661
|
return this.httpServer.compression
|
|
645
662
|
}
|
|
646
663
|
|
|
664
|
+
/**
|
|
665
|
+
* Runs get maximum buffered response body bytes.
|
|
666
|
+
* @returns {number | undefined} - Configured byte limit, or undefined when unbounded.
|
|
667
|
+
*/
|
|
668
|
+
getHttpServerMaxBufferedResponseBodyBytes() {
|
|
669
|
+
return this.httpServer.maxBufferedResponseBodyBytes
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Runs get maximum request body bytes.
|
|
674
|
+
* @returns {number | undefined} - Configured byte limit, or undefined when unbounded.
|
|
675
|
+
*/
|
|
676
|
+
getHttpServerMaxRequestBodyBytes() {
|
|
677
|
+
return this.httpServer.maxRequestBodyBytes
|
|
678
|
+
}
|
|
679
|
+
|
|
647
680
|
/**
|
|
648
681
|
* Runs get cookie secret.
|
|
649
682
|
* @returns {string | undefined} - Cookie secret.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
export class HttpRequestBodyTooLargeError extends Error {
|
|
4
|
+
/**
|
|
5
|
+
* Creates a request-body limit error.
|
|
6
|
+
* @param {object} args - Size details.
|
|
7
|
+
* @param {number} args.actualBytes - Declared or accumulated body size.
|
|
8
|
+
* @param {number} args.maxBytes - Configured maximum body size.
|
|
9
|
+
*/
|
|
10
|
+
constructor({actualBytes, maxBytes}) {
|
|
11
|
+
super(`HTTP request body exceeds ${maxBytes} bytes (received or declared ${actualBytes})`)
|
|
12
|
+
this.name = "HttpRequestBodyTooLargeError"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class HttpResponseBodyTooLargeError extends Error {
|
|
17
|
+
/**
|
|
18
|
+
* Creates a buffered-response limit error.
|
|
19
|
+
* @param {object} args - Size details.
|
|
20
|
+
* @param {number} args.actualBytes - Buffered response body size.
|
|
21
|
+
* @param {number} args.maxBytes - Configured maximum body size.
|
|
22
|
+
*/
|
|
23
|
+
constructor({actualBytes, maxBytes}) {
|
|
24
|
+
super(`Buffered HTTP response body exceeds ${maxBytes} bytes (received ${actualBytes})`)
|
|
25
|
+
this.name = "HttpResponseBodyTooLargeError"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -5,6 +5,7 @@ import fs from "node:fs/promises"
|
|
|
5
5
|
import {digg} from "diggerize"
|
|
6
6
|
import {ensureError} from "typanic"
|
|
7
7
|
import EventEmitter from "../../utils/event-emitter.js"
|
|
8
|
+
import {HttpRequestBodyTooLargeError} from "./errors.js"
|
|
8
9
|
import Logger from "../../logger.js"
|
|
9
10
|
import Request from "./request.js"
|
|
10
11
|
import RequestRunner from "./request-runner.js"
|
|
@@ -104,6 +105,26 @@ export default class VeoliciousHttpServerClient {
|
|
|
104
105
|
this.events.emit("close")
|
|
105
106
|
}
|
|
106
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Sends a deterministic request-body limit response and closes the connection.
|
|
110
|
+
* @returns {void} - No return value.
|
|
111
|
+
*/
|
|
112
|
+
_sendPayloadTooLargeResponse() {
|
|
113
|
+
const httpVersion = this.currentRequest?.httpVersion() || "1.1"
|
|
114
|
+
const body = "Payload Too Large\n"
|
|
115
|
+
const headers = [
|
|
116
|
+
`HTTP/${httpVersion} 413 Payload Too Large`,
|
|
117
|
+
"Connection: Close",
|
|
118
|
+
"Content-Type: text/plain; charset=UTF-8",
|
|
119
|
+
`Content-Length: ${Buffer.byteLength(body, "utf8")}`,
|
|
120
|
+
"",
|
|
121
|
+
body
|
|
122
|
+
].join("\r\n")
|
|
123
|
+
|
|
124
|
+
this.events.emit("output", headers)
|
|
125
|
+
this.events.emit("close")
|
|
126
|
+
}
|
|
127
|
+
|
|
107
128
|
/**
|
|
108
129
|
* Runs handle bad request.
|
|
109
130
|
* @param {Error} error - Error instance.
|
|
@@ -121,7 +142,11 @@ export default class VeoliciousHttpServerClient {
|
|
|
121
142
|
this.currentRequest = undefined
|
|
122
143
|
this.state = "initial"
|
|
123
144
|
|
|
124
|
-
|
|
145
|
+
if (error instanceof HttpRequestBodyTooLargeError) {
|
|
146
|
+
this._sendPayloadTooLargeResponse()
|
|
147
|
+
} else {
|
|
148
|
+
this._sendBadRequestResponse("Bad Request")
|
|
149
|
+
}
|
|
125
150
|
}
|
|
126
151
|
|
|
127
152
|
executeCurrentRequest = () => {
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import EventEmitter from "../../../utils/event-emitter.js"
|
|
4
4
|
import FormDataPart from "./form-data-part.js"
|
|
5
5
|
import Header from "./header.js"
|
|
6
|
+
import {HttpRequestBodyTooLargeError} from "../errors.js"
|
|
6
7
|
import {incorporate} from "incorporator"
|
|
7
8
|
import Logger from "../../../logger.js"
|
|
8
9
|
import ParamsToObject from "../params-to-object.js"
|
|
@@ -66,6 +67,31 @@ export default class RequestBuffer {
|
|
|
66
67
|
this.logger = new Logger(this, {debug: false})
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Raises before buffering a request body beyond the configured bound.
|
|
72
|
+
* @param {number} actualBytes - Declared or accumulated decoded body bytes.
|
|
73
|
+
* @returns {void}
|
|
74
|
+
*/
|
|
75
|
+
assertRequestBodySize(actualBytes) {
|
|
76
|
+
const maxBytes = this.configuration.getHttpServerMaxRequestBodyBytes()
|
|
77
|
+
|
|
78
|
+
if (maxBytes !== undefined && actualBytes > maxBytes) {
|
|
79
|
+
throw new HttpRequestBodyTooLargeError({actualBytes, maxBytes})
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Records bytes consumed after request headers. Multipart parsing can accept
|
|
85
|
+
* an unframed body, so enforce its configured bound during accumulation.
|
|
86
|
+
* @param {number} bytes - Newly consumed body bytes.
|
|
87
|
+
* @returns {void}
|
|
88
|
+
*/
|
|
89
|
+
recordBodyBytes(bytes) {
|
|
90
|
+
this.bodyLength += bytes
|
|
91
|
+
|
|
92
|
+
if (this.multiPartyFormData) this.assertRequestBodySize(this.bodyLength)
|
|
93
|
+
}
|
|
94
|
+
|
|
69
95
|
destroy() {
|
|
70
96
|
// Do nothing for now...
|
|
71
97
|
}
|
|
@@ -113,7 +139,7 @@ export default class RequestBuffer {
|
|
|
113
139
|
const newlineIndex = data.indexOf(10, index)
|
|
114
140
|
|
|
115
141
|
if (newlineIndex === -1) {
|
|
116
|
-
if (this.readingBody) this.
|
|
142
|
+
if (this.readingBody) this.recordBodyBytes(data.length - index)
|
|
117
143
|
|
|
118
144
|
for (let dataIndex = index; dataIndex < data.length; dataIndex += 1) {
|
|
119
145
|
this.data.push(data[dataIndex])
|
|
@@ -122,7 +148,7 @@ export default class RequestBuffer {
|
|
|
122
148
|
return data.length
|
|
123
149
|
}
|
|
124
150
|
|
|
125
|
-
if (this.readingBody) this.
|
|
151
|
+
if (this.readingBody) this.recordBodyBytes(newlineIndex + 1 - index)
|
|
126
152
|
|
|
127
153
|
let line
|
|
128
154
|
|
|
@@ -157,7 +183,7 @@ export default class RequestBuffer {
|
|
|
157
183
|
const endIndex = Math.min(data.length, index + remainingBodyBytes)
|
|
158
184
|
|
|
159
185
|
this.postBodyBuffers.push(data.subarray(index, endIndex))
|
|
160
|
-
this.
|
|
186
|
+
this.recordBodyBytes(endIndex - index)
|
|
161
187
|
|
|
162
188
|
if (this.contentLength && this.bodyLength >= this.contentLength) {
|
|
163
189
|
this.postRequestDone()
|
|
@@ -175,7 +201,7 @@ export default class RequestBuffer {
|
|
|
175
201
|
feedByte(data, index) {
|
|
176
202
|
const char = data[index]
|
|
177
203
|
|
|
178
|
-
if (this.readingBody) this.
|
|
204
|
+
if (this.readingBody) this.recordBodyBytes(1)
|
|
179
205
|
|
|
180
206
|
switch(this.state) {
|
|
181
207
|
case "chunked-data": {
|
|
@@ -414,6 +440,12 @@ export default class RequestBuffer {
|
|
|
414
440
|
this.readingBody = true
|
|
415
441
|
this.bodyLength = 0
|
|
416
442
|
|
|
443
|
+
if (this.contentLength !== undefined) {
|
|
444
|
+
if (Number.isNaN(this.contentLength)) throw new Error("Content length is invalid")
|
|
445
|
+
|
|
446
|
+
this.assertRequestBodySize(this.contentLength)
|
|
447
|
+
}
|
|
448
|
+
|
|
417
449
|
const match = this.getHeader("content-type")?.value?.match(/^multipart\/form-data;\s*boundary=(.+)$/i)
|
|
418
450
|
|
|
419
451
|
if (match) {
|
|
@@ -425,8 +457,6 @@ export default class RequestBuffer {
|
|
|
425
457
|
this.setState("multi-part-form-data")
|
|
426
458
|
} else if (this.contentLength === 0 || this.contentLength === undefined) {
|
|
427
459
|
this.completeRequest()
|
|
428
|
-
} else if (Number.isNaN(this.contentLength)) {
|
|
429
|
-
throw new Error("Content length is invalid")
|
|
430
460
|
} else {
|
|
431
461
|
/**
|
|
432
462
|
* Narrows the runtime value to the documented type.
|
|
@@ -521,6 +551,8 @@ export default class RequestBuffer {
|
|
|
521
551
|
return
|
|
522
552
|
}
|
|
523
553
|
|
|
554
|
+
this.assertRequestBodySize((this.chunkedBodyChars?.length || 0) + size)
|
|
555
|
+
|
|
524
556
|
this.currentChunkSize = size
|
|
525
557
|
this.currentChunkBytesRead = 0
|
|
526
558
|
this.setState("chunked-data")
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import {ensureError} from "typanic"
|
|
4
4
|
import BacktraceCleaner from "../../utils/backtrace-cleaner-node.js"
|
|
5
5
|
import EventEmitter from "../../utils/event-emitter.js"
|
|
6
|
+
import {HttpResponseBodyTooLargeError} from "./errors.js"
|
|
6
7
|
import Logger from "../../logger.js"
|
|
7
8
|
import RequestTiming from "./request-timing.js"
|
|
8
9
|
import Response from "./response.js"
|
|
@@ -304,7 +305,13 @@ export default class VelociousHttpServerClientRequestRunner {
|
|
|
304
305
|
})
|
|
305
306
|
|
|
306
307
|
response.setStatus(500)
|
|
307
|
-
|
|
308
|
+
try {
|
|
309
|
+
response.setErrorBody(error)
|
|
310
|
+
} catch (responseError) {
|
|
311
|
+
if (!(responseError instanceof HttpResponseBodyTooLargeError)) throw responseError
|
|
312
|
+
|
|
313
|
+
response.setBody("")
|
|
314
|
+
}
|
|
308
315
|
}
|
|
309
316
|
|
|
310
317
|
await this.logger.debug(() => ["Request runner done", {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
+
import {HttpResponseBodyTooLargeError} from "./errors.js"
|
|
4
|
+
|
|
3
5
|
/**
|
|
4
6
|
* Named status aliases.
|
|
5
7
|
* @type {Record<string, number>} */
|
|
@@ -226,6 +228,15 @@ export default class VelociousHttpServerClientResponse {
|
|
|
226
228
|
setBody(value) {
|
|
227
229
|
this.filePath = null
|
|
228
230
|
this.fileOnFinished = null
|
|
231
|
+
|
|
232
|
+
const actualBytes = typeof value === "string" ? Buffer.byteLength(value, "utf8") : value.byteLength
|
|
233
|
+
const maxBytes = this.configuration.getHttpServerMaxBufferedResponseBodyBytes()
|
|
234
|
+
|
|
235
|
+
if (maxBytes !== undefined && actualBytes > maxBytes) {
|
|
236
|
+
this.body = ""
|
|
237
|
+
throw new HttpResponseBodyTooLargeError({actualBytes, maxBytes})
|
|
238
|
+
}
|
|
239
|
+
|
|
229
240
|
this.body = value
|
|
230
241
|
}
|
|
231
242
|
|
package/build/routes/resolver.js
CHANGED
|
@@ -92,7 +92,7 @@ export default class VelociousRoutesResolver {
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
const routeResolverHookMatch = await this.resolveRouteResolverHooks(currentPath, {hasMatchingCustomRoute})
|
|
95
|
-
let skipControllerConnections = routeResolverHookMatch?.skipControllerConnections === true
|
|
95
|
+
let skipControllerConnections = this.configuration.database === false || routeResolverHookMatch?.skipControllerConnections === true
|
|
96
96
|
let skipAbilityResolution = routeResolverHookMatch?.skipAbilityResolution === true
|
|
97
97
|
let skipTenantResolution = routeResolverHookMatch?.skipTenantResolution === true
|
|
98
98
|
const matchResult = routeResolverHookMatch || !currentRoute ? undefined : this.matchPathWithRoutes(currentRoute, currentPath)
|