@shipfox/api-logs 5.0.0 → 7.1.0
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/.turbo/turbo-build.log +14 -14
- package/CHANGELOG.md +46 -0
- package/dist/api/object-storage.d.ts.map +1 -1
- package/dist/api/object-storage.js +28 -2
- package/dist/api/object-storage.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +5 -10
- package/dist/config.js.map +1 -1
- package/dist/core/append-logs.d.ts +2 -6
- package/dist/core/append-logs.d.ts.map +1 -1
- package/dist/core/append-logs.js +7 -16
- package/dist/core/append-logs.js.map +1 -1
- package/dist/core/reap-stale-open-streams.d.ts.map +1 -1
- package/dist/core/reap-stale-open-streams.js +7 -0
- package/dist/core/reap-stale-open-streams.js.map +1 -1
- package/dist/core/retention.d.ts.map +1 -1
- package/dist/core/retention.js +25 -0
- package/dist/core/retention.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +63 -59
- package/dist/index.js.map +1 -1
- package/dist/presentation/routes/append-logs.d.ts +2 -1
- package/dist/presentation/routes/append-logs.d.ts.map +1 -1
- package/dist/presentation/routes/append-logs.js +61 -61
- package/dist/presentation/routes/append-logs.js.map +1 -1
- package/dist/presentation/routes/index.d.ts +2 -1
- package/dist/presentation/routes/index.d.ts.map +1 -1
- package/dist/presentation/routes/index.js +25 -23
- package/dist/presentation/routes/index.js.map +1 -1
- package/dist/temporal/activities/compact-stream.d.ts.map +1 -1
- package/dist/temporal/activities/compact-stream.js +17 -1
- package/dist/temporal/activities/compact-stream.js.map +1 -1
- package/dist/temporal/activities/compaction-reconcile.d.ts.map +1 -1
- package/dist/temporal/activities/compaction-reconcile.js +7 -0
- package/dist/temporal/activities/compaction-reconcile.js.map +1 -1
- package/dist/temporal/workflows/index.bundle.js +17164 -3306
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +12 -30
- package/src/api/object-storage.ts +21 -2
- package/src/config.test.ts +8 -32
- package/src/config.ts +10 -15
- package/src/core/append-logs.test.ts +13 -19
- package/src/core/append-logs.ts +12 -20
- package/src/core/reap-stale-open-streams.ts +2 -0
- package/src/core/retention.ts +5 -0
- package/src/index.ts +63 -51
- package/src/presentation/routes/append-logs.test.ts +3 -2
- package/src/presentation/routes/append-logs.ts +54 -50
- package/src/presentation/routes/index.ts +22 -19
- package/src/presentation/routes/read-logs.test.ts +3 -2
- package/src/temporal/activities/compact-stream.ts +13 -1
- package/src/temporal/activities/compaction-reconcile.ts +2 -0
- package/test/env.ts +1 -3
- package/test/fixtures/lease-token.ts +2 -1
- package/test/fixtures/workflows-client.ts +17 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/dist/core/retention.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { reportError } from '@shipfox/node-error-monitoring';
|
|
1
2
|
import { logger } from '@shipfox/node-opentelemetry';
|
|
2
3
|
import { deleteObjectsByPrefix } from '#api/object-storage.js';
|
|
3
4
|
import { config } from '#config.js';
|
|
@@ -81,6 +82,12 @@ function deleteExpiredStreamRow(params) {
|
|
|
81
82
|
err: error,
|
|
82
83
|
streamId: stream.id
|
|
83
84
|
}, 'Failed to delete expired log stream objects');
|
|
85
|
+
reportError(error, {
|
|
86
|
+
boundary: 'logs.maintenance',
|
|
87
|
+
extra: {
|
|
88
|
+
streamId: stream.id
|
|
89
|
+
}
|
|
90
|
+
});
|
|
84
91
|
continue;
|
|
85
92
|
}
|
|
86
93
|
let outcome;
|
|
@@ -96,6 +103,12 @@ function deleteExpiredStreamRow(params) {
|
|
|
96
103
|
err: error,
|
|
97
104
|
streamId: stream.id
|
|
98
105
|
}, 'Failed to delete expired log stream row');
|
|
106
|
+
reportError(error, {
|
|
107
|
+
boundary: 'logs.maintenance',
|
|
108
|
+
extra: {
|
|
109
|
+
streamId: stream.id
|
|
110
|
+
}
|
|
111
|
+
});
|
|
99
112
|
continue;
|
|
100
113
|
}
|
|
101
114
|
if (!outcome.deleted) {
|
|
@@ -109,6 +122,12 @@ function deleteExpiredStreamRow(params) {
|
|
|
109
122
|
err: error,
|
|
110
123
|
streamId: stream.id
|
|
111
124
|
}, 'Failed to reload raced expired log stream');
|
|
125
|
+
reportError(error, {
|
|
126
|
+
boundary: 'logs.maintenance',
|
|
127
|
+
extra: {
|
|
128
|
+
streamId: stream.id
|
|
129
|
+
}
|
|
130
|
+
});
|
|
112
131
|
continue;
|
|
113
132
|
}
|
|
114
133
|
if (current) {
|
|
@@ -125,6 +144,12 @@ function deleteExpiredStreamRow(params) {
|
|
|
125
144
|
err: error,
|
|
126
145
|
streamId: stream.id
|
|
127
146
|
}, 'Failed to delete raced expired log stream');
|
|
147
|
+
reportError(error, {
|
|
148
|
+
boundary: 'logs.maintenance',
|
|
149
|
+
extra: {
|
|
150
|
+
streamId: stream.id
|
|
151
|
+
}
|
|
152
|
+
});
|
|
128
153
|
continue;
|
|
129
154
|
}
|
|
130
155
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/core/retention.ts"],"sourcesContent":["import {logger} from '@shipfox/node-opentelemetry';\nimport {deleteObjectsByPrefix} from '#api/object-storage.js';\nimport {config} from '#config.js';\nimport type {AttemptStream} from '#core/entities/attempt-stream.js';\nimport {logObjectKey} from '#core/entities/log-object.js';\nimport {deleteJobAccounting} from '#db/accounting.js';\nimport {db} from '#db/db.js';\nimport {\n accountingHasNoStreams,\n deleteExpiredStream,\n getAttemptStreamById,\n listExpiredClosedStreams,\n} from '#db/streams.js';\n\nexport interface RetentionSweepResult {\n /** Streams whose objects and row were deleted. */\n deleted: number;\n /** Streams skipped because compaction changed `object_key` after we read it (retried next run). */\n raced: number;\n /** Streams whose object cleanup or guarded row delete threw; logged, skipped, and retried next run. */\n failed: number;\n accountingPruned: number;\n iterations: number;\n /** True when the sweep stopped on its wall-clock budget with backlog likely remaining. */\n timedOut: boolean;\n}\n\nexport interface RunRetentionSweepParams {\n retentionDays: number;\n batchLimit: number;\n timeBudgetMs: number;\n maxIterations: number;\n /** Wall clock; injectable so tests can drive the time budget deterministically. */\n now?: () => number;\n /** Liveness signal (e.g. the activity heartbeat); invoked once per processed stream. */\n onProgress?: () => void;\n}\n\nfunction deleteExpiredStreamObjects(stream: AttemptStream): Promise<void> {\n return deleteObjectsByPrefix(`${logObjectKey(config.LOG_STORAGE_S3_PREFIX, stream)}/`);\n}\n\nfunction deleteExpiredStreamRow(params: {\n stream: AttemptStream;\n retentionDays: number;\n}): Promise<{deleted: boolean; prunedAccounting: boolean}> {\n return db().transaction(async (tx) => {\n const {deleted, jobId} = await deleteExpiredStream(tx, {\n streamId: params.stream.id,\n observedObjectKey: params.stream.objectKey,\n });\n if (deleted && jobId && (await accountingHasNoStreams(tx, jobId))) {\n const pruned = await deleteJobAccounting(tx, {\n jobId,\n retentionDays: params.retentionDays,\n });\n return {deleted, prunedAccounting: pruned.deleted};\n }\n return {deleted, prunedAccounting: false};\n });\n}\n\n/**\n * Deletes expired closed streams and prunes accounting for emptied jobs.\n *\n * The loop self-bounds because a Temporal `startToCloseTimeout` marks the activity failed but\n * does not stop already-running JS. Objects are deleted before rows so a cleanup failure leaves\n * the row discoverable for the next sweep; the row delete stays guarded on the observed\n * `object_key`, so a racing compaction publish is re-read before the row is removed.\n */\nexport async function runRetentionSweep(\n params: RunRetentionSweepParams,\n): Promise<RetentionSweepResult> {\n const now = params.now ?? Date.now;\n const deadline = now() + params.timeBudgetMs;\n const result: RetentionSweepResult = {\n deleted: 0,\n raced: 0,\n failed: 0,\n accountingPruned: 0,\n iterations: 0,\n timedOut: false,\n };\n\n // Failed or raced rows retry next run; skipping them here keeps the rest of the backlog moving.\n const skip = new Set<string>();\n while (result.iterations < params.maxIterations) {\n if (now() >= deadline) {\n result.timedOut = true;\n break;\n }\n\n const batch = await listExpiredClosedStreams({\n retentionDays: params.retentionDays,\n limit: params.batchLimit,\n excludeIds: skip.size > 0 ? [...skip] : undefined,\n });\n if (batch.length === 0) break;\n\n let timedOutMidBatch = false;\n for (const stream of batch) {\n // Per-stream checks keep long batches within the heartbeat and wall-clock budgets.\n params.onProgress?.();\n if (now() >= deadline) {\n result.timedOut = true;\n timedOutMidBatch = true;\n break;\n }\n\n try {\n await deleteExpiredStreamObjects(stream);\n } catch (error) {\n result.failed += 1;\n skip.add(stream.id);\n logger().error(\n {err: error, streamId: stream.id},\n 'Failed to delete expired log stream objects',\n );\n continue;\n }\n\n let outcome: {deleted: boolean; prunedAccounting: boolean};\n try {\n outcome = await deleteExpiredStreamRow({stream, retentionDays: params.retentionDays});\n } catch (error) {\n result.failed += 1;\n skip.add(stream.id);\n logger().error(\n {err: error, streamId: stream.id},\n 'Failed to delete expired log stream row',\n );\n continue;\n }\n\n if (!outcome.deleted) {\n let current: AttemptStream | null;\n try {\n current = await getAttemptStreamById(stream.id);\n } catch (error) {\n result.failed += 1;\n skip.add(stream.id);\n logger().error(\n {err: error, streamId: stream.id},\n 'Failed to reload raced expired log stream',\n );\n continue;\n }\n if (current) {\n try {\n await deleteExpiredStreamObjects(current);\n outcome = await deleteExpiredStreamRow({\n stream: current,\n retentionDays: params.retentionDays,\n });\n } catch (error) {\n result.failed += 1;\n skip.add(stream.id);\n logger().error(\n {err: error, streamId: stream.id},\n 'Failed to delete raced expired log stream',\n );\n continue;\n }\n }\n if (!current || !outcome.deleted) {\n // `object_key` changed again or the row disappeared; the next sweep will re-read it.\n result.raced += 1;\n skip.add(stream.id);\n continue;\n }\n }\n\n result.deleted += 1;\n if (outcome.prunedAccounting) result.accountingPruned += 1;\n }\n\n if (timedOutMidBatch) break;\n\n result.iterations += 1;\n if (batch.length < params.batchLimit) break;\n }\n\n return result;\n}\n"],"names":["logger","deleteObjectsByPrefix","config","logObjectKey","deleteJobAccounting","db","accountingHasNoStreams","deleteExpiredStream","getAttemptStreamById","listExpiredClosedStreams","deleteExpiredStreamObjects","stream","LOG_STORAGE_S3_PREFIX","deleteExpiredStreamRow","params","transaction","tx","deleted","jobId","streamId","id","observedObjectKey","objectKey","pruned","retentionDays","prunedAccounting","runRetentionSweep","now","Date","deadline","timeBudgetMs","result","raced","failed","accountingPruned","iterations","timedOut","skip","Set","maxIterations","batch","limit","batchLimit","excludeIds","size","undefined","length","timedOutMidBatch","onProgress","error","add","err","outcome","current"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/core/retention.ts"],"sourcesContent":["import {reportError} from '@shipfox/node-error-monitoring';\nimport {logger} from '@shipfox/node-opentelemetry';\nimport {deleteObjectsByPrefix} from '#api/object-storage.js';\nimport {config} from '#config.js';\nimport type {AttemptStream} from '#core/entities/attempt-stream.js';\nimport {logObjectKey} from '#core/entities/log-object.js';\nimport {deleteJobAccounting} from '#db/accounting.js';\nimport {db} from '#db/db.js';\nimport {\n accountingHasNoStreams,\n deleteExpiredStream,\n getAttemptStreamById,\n listExpiredClosedStreams,\n} from '#db/streams.js';\n\nexport interface RetentionSweepResult {\n /** Streams whose objects and row were deleted. */\n deleted: number;\n /** Streams skipped because compaction changed `object_key` after we read it (retried next run). */\n raced: number;\n /** Streams whose object cleanup or guarded row delete threw; logged, skipped, and retried next run. */\n failed: number;\n accountingPruned: number;\n iterations: number;\n /** True when the sweep stopped on its wall-clock budget with backlog likely remaining. */\n timedOut: boolean;\n}\n\nexport interface RunRetentionSweepParams {\n retentionDays: number;\n batchLimit: number;\n timeBudgetMs: number;\n maxIterations: number;\n /** Wall clock; injectable so tests can drive the time budget deterministically. */\n now?: () => number;\n /** Liveness signal (e.g. the activity heartbeat); invoked once per processed stream. */\n onProgress?: () => void;\n}\n\nfunction deleteExpiredStreamObjects(stream: AttemptStream): Promise<void> {\n return deleteObjectsByPrefix(`${logObjectKey(config.LOG_STORAGE_S3_PREFIX, stream)}/`);\n}\n\nfunction deleteExpiredStreamRow(params: {\n stream: AttemptStream;\n retentionDays: number;\n}): Promise<{deleted: boolean; prunedAccounting: boolean}> {\n return db().transaction(async (tx) => {\n const {deleted, jobId} = await deleteExpiredStream(tx, {\n streamId: params.stream.id,\n observedObjectKey: params.stream.objectKey,\n });\n if (deleted && jobId && (await accountingHasNoStreams(tx, jobId))) {\n const pruned = await deleteJobAccounting(tx, {\n jobId,\n retentionDays: params.retentionDays,\n });\n return {deleted, prunedAccounting: pruned.deleted};\n }\n return {deleted, prunedAccounting: false};\n });\n}\n\n/**\n * Deletes expired closed streams and prunes accounting for emptied jobs.\n *\n * The loop self-bounds because a Temporal `startToCloseTimeout` marks the activity failed but\n * does not stop already-running JS. Objects are deleted before rows so a cleanup failure leaves\n * the row discoverable for the next sweep; the row delete stays guarded on the observed\n * `object_key`, so a racing compaction publish is re-read before the row is removed.\n */\nexport async function runRetentionSweep(\n params: RunRetentionSweepParams,\n): Promise<RetentionSweepResult> {\n const now = params.now ?? Date.now;\n const deadline = now() + params.timeBudgetMs;\n const result: RetentionSweepResult = {\n deleted: 0,\n raced: 0,\n failed: 0,\n accountingPruned: 0,\n iterations: 0,\n timedOut: false,\n };\n\n // Failed or raced rows retry next run; skipping them here keeps the rest of the backlog moving.\n const skip = new Set<string>();\n while (result.iterations < params.maxIterations) {\n if (now() >= deadline) {\n result.timedOut = true;\n break;\n }\n\n const batch = await listExpiredClosedStreams({\n retentionDays: params.retentionDays,\n limit: params.batchLimit,\n excludeIds: skip.size > 0 ? [...skip] : undefined,\n });\n if (batch.length === 0) break;\n\n let timedOutMidBatch = false;\n for (const stream of batch) {\n // Per-stream checks keep long batches within the heartbeat and wall-clock budgets.\n params.onProgress?.();\n if (now() >= deadline) {\n result.timedOut = true;\n timedOutMidBatch = true;\n break;\n }\n\n try {\n await deleteExpiredStreamObjects(stream);\n } catch (error) {\n result.failed += 1;\n skip.add(stream.id);\n logger().error(\n {err: error, streamId: stream.id},\n 'Failed to delete expired log stream objects',\n );\n reportError(error, {boundary: 'logs.maintenance', extra: {streamId: stream.id}});\n continue;\n }\n\n let outcome: {deleted: boolean; prunedAccounting: boolean};\n try {\n outcome = await deleteExpiredStreamRow({stream, retentionDays: params.retentionDays});\n } catch (error) {\n result.failed += 1;\n skip.add(stream.id);\n logger().error(\n {err: error, streamId: stream.id},\n 'Failed to delete expired log stream row',\n );\n reportError(error, {boundary: 'logs.maintenance', extra: {streamId: stream.id}});\n continue;\n }\n\n if (!outcome.deleted) {\n let current: AttemptStream | null;\n try {\n current = await getAttemptStreamById(stream.id);\n } catch (error) {\n result.failed += 1;\n skip.add(stream.id);\n logger().error(\n {err: error, streamId: stream.id},\n 'Failed to reload raced expired log stream',\n );\n reportError(error, {boundary: 'logs.maintenance', extra: {streamId: stream.id}});\n continue;\n }\n if (current) {\n try {\n await deleteExpiredStreamObjects(current);\n outcome = await deleteExpiredStreamRow({\n stream: current,\n retentionDays: params.retentionDays,\n });\n } catch (error) {\n result.failed += 1;\n skip.add(stream.id);\n logger().error(\n {err: error, streamId: stream.id},\n 'Failed to delete raced expired log stream',\n );\n reportError(error, {boundary: 'logs.maintenance', extra: {streamId: stream.id}});\n continue;\n }\n }\n if (!current || !outcome.deleted) {\n // `object_key` changed again or the row disappeared; the next sweep will re-read it.\n result.raced += 1;\n skip.add(stream.id);\n continue;\n }\n }\n\n result.deleted += 1;\n if (outcome.prunedAccounting) result.accountingPruned += 1;\n }\n\n if (timedOutMidBatch) break;\n\n result.iterations += 1;\n if (batch.length < params.batchLimit) break;\n }\n\n return result;\n}\n"],"names":["reportError","logger","deleteObjectsByPrefix","config","logObjectKey","deleteJobAccounting","db","accountingHasNoStreams","deleteExpiredStream","getAttemptStreamById","listExpiredClosedStreams","deleteExpiredStreamObjects","stream","LOG_STORAGE_S3_PREFIX","deleteExpiredStreamRow","params","transaction","tx","deleted","jobId","streamId","id","observedObjectKey","objectKey","pruned","retentionDays","prunedAccounting","runRetentionSweep","now","Date","deadline","timeBudgetMs","result","raced","failed","accountingPruned","iterations","timedOut","skip","Set","maxIterations","batch","limit","batchLimit","excludeIds","size","undefined","length","timedOutMidBatch","onProgress","error","add","err","boundary","extra","outcome","current"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
import type { WorkflowsModuleClient } from '@shipfox/api-workflows-dto/inter-module';
|
|
1
2
|
import { type ShipfoxModule } from '@shipfox/node-module';
|
|
2
3
|
export { checkBucketReachable } from '#api/object-storage.js';
|
|
3
|
-
export declare
|
|
4
|
+
export declare function createLogsModule({ workflows, jobLeaseTokenTtlSeconds, }: {
|
|
5
|
+
workflows: WorkflowsModuleClient;
|
|
6
|
+
jobLeaseTokenTtlSeconds: number;
|
|
7
|
+
}): ShipfoxModule;
|
|
4
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAC,KAAK,aAAa,EAAoB,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,yCAAyC,CAAC;AACnF,OAAO,EAAC,KAAK,aAAa,EAAoB,MAAM,sBAAsB,CAAC;AAa3E,OAAO,EAAC,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AAO5D,wBAAgB,gBAAgB,CAAC,EAC/B,SAAS,EACT,uBAAuB,GACxB,EAAE;IACD,SAAS,EAAE,qBAAqB,CAAC;IACjC,uBAAuB,EAAE,MAAM,CAAC;CACjC,GAAG,aAAa,CAqDhB"}
|
package/dist/index.js
CHANGED
|
@@ -3,11 +3,12 @@ import { fileURLToPath } from 'node:url';
|
|
|
3
3
|
import { LOG_STREAM_CLOSED, logsEventSchemas } from '@shipfox/api-logs-dto';
|
|
4
4
|
import { WORKFLOWS_JOB_TERMINATED, WORKFLOWS_STEP_ATTEMPT_TERMINATED } from '@shipfox/api-workflows-dto';
|
|
5
5
|
import { subscriberFactory } from '@shipfox/node-module';
|
|
6
|
+
import { validateLogStreamReapAfterSeconds } from '#config.js';
|
|
6
7
|
import { db } from '#db/db.js';
|
|
7
8
|
import { migrationsPath } from '#db/index.js';
|
|
8
9
|
import { logsOutbox } from '#db/schema/outbox.js';
|
|
9
10
|
import { registerLogsServiceMetrics } from '#metrics/service.js';
|
|
10
|
-
import {
|
|
11
|
+
import { createLogsRoutes } from '#presentation/routes/index.js';
|
|
11
12
|
import { onJobTerminated } from '#presentation/subscribers/on-job-terminated.js';
|
|
12
13
|
import { onLogStreamClosed } from '#presentation/subscribers/on-log-stream-closed.js';
|
|
13
14
|
import { onStepAttemptTerminated } from '#presentation/subscribers/on-step-attempt-terminated.js';
|
|
@@ -17,65 +18,68 @@ export { checkBucketReachable } from '#api/object-storage.js';
|
|
|
17
18
|
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
18
19
|
const workflowsPath = resolve(packageRoot, 'dist/temporal/workflows/index.js');
|
|
19
20
|
const subscriber = subscriberFactory();
|
|
20
|
-
export
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
},
|
|
26
|
-
routes: logsRoutes,
|
|
27
|
-
metrics: registerLogsServiceMetrics,
|
|
28
|
-
// `logs.stream.closed` is written by the close paths: the job-terminated subscriber
|
|
29
|
-
// force-closes streams the runner never ended, and the closed event drives compaction.
|
|
30
|
-
publishers: [
|
|
31
|
-
{
|
|
32
|
-
name: 'logs',
|
|
33
|
-
table: logsOutbox,
|
|
21
|
+
export function createLogsModule({ workflows, jobLeaseTokenTtlSeconds }) {
|
|
22
|
+
validateLogStreamReapAfterSeconds(jobLeaseTokenTtlSeconds);
|
|
23
|
+
return {
|
|
24
|
+
name: 'logs',
|
|
25
|
+
database: {
|
|
34
26
|
db,
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
],
|
|
38
|
-
subscribers: [
|
|
39
|
-
subscriber(WORKFLOWS_STEP_ATTEMPT_TERMINATED, onStepAttemptTerminated),
|
|
40
|
-
subscriber(WORKFLOWS_JOB_TERMINATED, onJobTerminated),
|
|
41
|
-
subscriber(LOG_STREAM_CLOSED, onLogStreamClosed)
|
|
42
|
-
],
|
|
43
|
-
workers: [
|
|
44
|
-
{
|
|
45
|
-
taskQueue: LOGS_LIFECYCLE_TASK_QUEUE,
|
|
46
|
-
workflowsPath,
|
|
47
|
-
activities: createLogsActivities,
|
|
48
|
-
// Temporal cron schedules are fixed after creation, so making cadence configurable would
|
|
49
|
-
// look adjustable while keeping the old schedule. Tune LOG_RETENTION_DAYS for the horizon.
|
|
50
|
-
workflows: [
|
|
51
|
-
{
|
|
52
|
-
name: 'retentionSweepCron',
|
|
53
|
-
id: 'logs-retention-sweep',
|
|
54
|
-
cronSchedule: '0 * * * *'
|
|
55
|
-
},
|
|
56
|
-
// Offset from retention's top-of-hour sweep; stale-stream age is governed by
|
|
57
|
-
// LOG_STREAM_REAP_AFTER_SECONDS.
|
|
58
|
-
{
|
|
59
|
-
name: 'reapStaleOpenStreamsCron',
|
|
60
|
-
id: 'logs-reap-stale-open-streams',
|
|
61
|
-
cronSchedule: '5,15,25,35,45,55 * * * *'
|
|
62
|
-
}
|
|
63
|
-
]
|
|
27
|
+
migrationsPath
|
|
64
28
|
},
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
29
|
+
routes: createLogsRoutes(workflows),
|
|
30
|
+
metrics: registerLogsServiceMetrics,
|
|
31
|
+
// `logs.stream.closed` is written by the close paths: the job-terminated subscriber
|
|
32
|
+
// force-closes streams the runner never ended, and the closed event drives compaction.
|
|
33
|
+
publishers: [
|
|
34
|
+
{
|
|
35
|
+
name: 'logs',
|
|
36
|
+
table: logsOutbox,
|
|
37
|
+
db,
|
|
38
|
+
eventSchemas: logsEventSchemas
|
|
39
|
+
}
|
|
40
|
+
],
|
|
41
|
+
subscribers: [
|
|
42
|
+
subscriber(WORKFLOWS_STEP_ATTEMPT_TERMINATED, onStepAttemptTerminated),
|
|
43
|
+
subscriber(WORKFLOWS_JOB_TERMINATED, onJobTerminated),
|
|
44
|
+
subscriber(LOG_STREAM_CLOSED, onLogStreamClosed)
|
|
45
|
+
],
|
|
46
|
+
workers: [
|
|
47
|
+
{
|
|
48
|
+
taskQueue: LOGS_LIFECYCLE_TASK_QUEUE,
|
|
49
|
+
workflowsPath,
|
|
50
|
+
activities: createLogsActivities,
|
|
51
|
+
// Temporal cron schedules are fixed after creation, so making cadence configurable would
|
|
52
|
+
// look adjustable while keeping the old schedule. Tune LOG_RETENTION_DAYS for the horizon.
|
|
53
|
+
workflows: [
|
|
54
|
+
{
|
|
55
|
+
name: 'retentionSweepCron',
|
|
56
|
+
id: 'logs-retention-sweep',
|
|
57
|
+
cronSchedule: '0 * * * *'
|
|
58
|
+
},
|
|
59
|
+
// Offset from retention's top-of-hour sweep; stale-stream age is governed by
|
|
60
|
+
// LOG_STREAM_REAP_AFTER_SECONDS.
|
|
61
|
+
{
|
|
62
|
+
name: 'reapStaleOpenStreamsCron',
|
|
63
|
+
id: 'logs-reap-stale-open-streams',
|
|
64
|
+
cronSchedule: '5,15,25,35,45,55 * * * *'
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
// Compaction runs on its own queue so long uploads cannot starve the lifecycle sweep.
|
|
69
|
+
{
|
|
70
|
+
taskQueue: LOGS_COMPACTION_TASK_QUEUE,
|
|
71
|
+
workflowsPath,
|
|
72
|
+
activities: createLogsActivities,
|
|
73
|
+
workflows: [
|
|
74
|
+
{
|
|
75
|
+
name: 'compactionReconcileCron',
|
|
76
|
+
id: 'logs-compaction-reconcile',
|
|
77
|
+
cronSchedule: '*/10 * * * *'
|
|
78
|
+
}
|
|
79
|
+
]
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
};
|
|
83
|
+
}
|
|
80
84
|
|
|
81
85
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {dirname, resolve} from 'node:path';\nimport {fileURLToPath} from 'node:url';\nimport {LOG_STREAM_CLOSED, type LogsEventMap, logsEventSchemas} from '@shipfox/api-logs-dto';\nimport {\n WORKFLOWS_JOB_TERMINATED,\n WORKFLOWS_STEP_ATTEMPT_TERMINATED,\n type WorkflowsEventMapDto,\n} from '@shipfox/api-workflows-dto';\nimport {type ShipfoxModule, subscriberFactory} from '@shipfox/node-module';\nimport {db} from '#db/db.js';\nimport {migrationsPath} from '#db/index.js';\nimport {logsOutbox} from '#db/schema/outbox.js';\nimport {registerLogsServiceMetrics} from '#metrics/service.js';\nimport {
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {dirname, resolve} from 'node:path';\nimport {fileURLToPath} from 'node:url';\nimport {LOG_STREAM_CLOSED, type LogsEventMap, logsEventSchemas} from '@shipfox/api-logs-dto';\nimport {\n WORKFLOWS_JOB_TERMINATED,\n WORKFLOWS_STEP_ATTEMPT_TERMINATED,\n type WorkflowsEventMapDto,\n} from '@shipfox/api-workflows-dto';\nimport type {WorkflowsModuleClient} from '@shipfox/api-workflows-dto/inter-module';\nimport {type ShipfoxModule, subscriberFactory} from '@shipfox/node-module';\nimport {validateLogStreamReapAfterSeconds} from '#config.js';\nimport {db} from '#db/db.js';\nimport {migrationsPath} from '#db/index.js';\nimport {logsOutbox} from '#db/schema/outbox.js';\nimport {registerLogsServiceMetrics} from '#metrics/service.js';\nimport {createLogsRoutes} from '#presentation/routes/index.js';\nimport {onJobTerminated} from '#presentation/subscribers/on-job-terminated.js';\nimport {onLogStreamClosed} from '#presentation/subscribers/on-log-stream-closed.js';\nimport {onStepAttemptTerminated} from '#presentation/subscribers/on-step-attempt-terminated.js';\nimport {createLogsActivities} from '#temporal/activities/index.js';\nimport {LOGS_COMPACTION_TASK_QUEUE, LOGS_LIFECYCLE_TASK_QUEUE} from '#temporal/constants.js';\n\nexport {checkBucketReachable} from '#api/object-storage.js';\n\nconst packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');\nconst workflowsPath = resolve(packageRoot, 'dist/temporal/workflows/index.js');\n\nconst subscriber = subscriberFactory<WorkflowsEventMapDto & LogsEventMap>();\n\nexport function createLogsModule({\n workflows,\n jobLeaseTokenTtlSeconds,\n}: {\n workflows: WorkflowsModuleClient;\n jobLeaseTokenTtlSeconds: number;\n}): ShipfoxModule {\n validateLogStreamReapAfterSeconds(jobLeaseTokenTtlSeconds);\n\n return {\n name: 'logs',\n database: {db, migrationsPath},\n routes: createLogsRoutes(workflows),\n metrics: registerLogsServiceMetrics,\n // `logs.stream.closed` is written by the close paths: the job-terminated subscriber\n // force-closes streams the runner never ended, and the closed event drives compaction.\n publishers: [{name: 'logs', table: logsOutbox, db, eventSchemas: logsEventSchemas}],\n subscribers: [\n subscriber(WORKFLOWS_STEP_ATTEMPT_TERMINATED, onStepAttemptTerminated),\n subscriber(WORKFLOWS_JOB_TERMINATED, onJobTerminated),\n subscriber(LOG_STREAM_CLOSED, onLogStreamClosed),\n ],\n workers: [\n {\n taskQueue: LOGS_LIFECYCLE_TASK_QUEUE,\n workflowsPath,\n activities: createLogsActivities,\n // Temporal cron schedules are fixed after creation, so making cadence configurable would\n // look adjustable while keeping the old schedule. Tune LOG_RETENTION_DAYS for the horizon.\n workflows: [\n {\n name: 'retentionSweepCron',\n id: 'logs-retention-sweep',\n cronSchedule: '0 * * * *',\n },\n // Offset from retention's top-of-hour sweep; stale-stream age is governed by\n // LOG_STREAM_REAP_AFTER_SECONDS.\n {\n name: 'reapStaleOpenStreamsCron',\n id: 'logs-reap-stale-open-streams',\n cronSchedule: '5,15,25,35,45,55 * * * *',\n },\n ],\n },\n // Compaction runs on its own queue so long uploads cannot starve the lifecycle sweep.\n {\n taskQueue: LOGS_COMPACTION_TASK_QUEUE,\n workflowsPath,\n activities: createLogsActivities,\n workflows: [\n {\n name: 'compactionReconcileCron',\n id: 'logs-compaction-reconcile',\n cronSchedule: '*/10 * * * *',\n },\n ],\n },\n ],\n };\n}\n"],"names":["dirname","resolve","fileURLToPath","LOG_STREAM_CLOSED","logsEventSchemas","WORKFLOWS_JOB_TERMINATED","WORKFLOWS_STEP_ATTEMPT_TERMINATED","subscriberFactory","validateLogStreamReapAfterSeconds","db","migrationsPath","logsOutbox","registerLogsServiceMetrics","createLogsRoutes","onJobTerminated","onLogStreamClosed","onStepAttemptTerminated","createLogsActivities","LOGS_COMPACTION_TASK_QUEUE","LOGS_LIFECYCLE_TASK_QUEUE","checkBucketReachable","packageRoot","url","workflowsPath","subscriber","createLogsModule","workflows","jobLeaseTokenTtlSeconds","name","database","routes","metrics","publishers","table","eventSchemas","subscribers","workers","taskQueue","activities","id","cronSchedule"],"mappings":"AAAA,SAAQA,OAAO,EAAEC,OAAO,QAAO,YAAY;AAC3C,SAAQC,aAAa,QAAO,WAAW;AACvC,SAAQC,iBAAiB,EAAqBC,gBAAgB,QAAO,wBAAwB;AAC7F,SACEC,wBAAwB,EACxBC,iCAAiC,QAE5B,6BAA6B;AAEpC,SAA4BC,iBAAiB,QAAO,uBAAuB;AAC3E,SAAQC,iCAAiC,QAAO,aAAa;AAC7D,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,cAAc,QAAO,eAAe;AAC5C,SAAQC,UAAU,QAAO,uBAAuB;AAChD,SAAQC,0BAA0B,QAAO,sBAAsB;AAC/D,SAAQC,gBAAgB,QAAO,gCAAgC;AAC/D,SAAQC,eAAe,QAAO,iDAAiD;AAC/E,SAAQC,iBAAiB,QAAO,oDAAoD;AACpF,SAAQC,uBAAuB,QAAO,0DAA0D;AAChG,SAAQC,oBAAoB,QAAO,gCAAgC;AACnE,SAAQC,0BAA0B,EAAEC,yBAAyB,QAAO,yBAAyB;AAE7F,SAAQC,oBAAoB,QAAO,yBAAyB;AAE5D,MAAMC,cAAcpB,QAAQD,QAAQE,cAAc,YAAYoB,GAAG,IAAI;AACrE,MAAMC,gBAAgBtB,QAAQoB,aAAa;AAE3C,MAAMG,aAAajB;AAEnB,OAAO,SAASkB,iBAAiB,EAC/BC,SAAS,EACTC,uBAAuB,EAIxB;IACCnB,kCAAkCmB;IAElC,OAAO;QACLC,MAAM;QACNC,UAAU;YAACpB;YAAIC;QAAc;QAC7BoB,QAAQjB,iBAAiBa;QACzBK,SAASnB;QACT,oFAAoF;QACpF,uFAAuF;QACvFoB,YAAY;YAAC;gBAACJ,MAAM;gBAAQK,OAAOtB;gBAAYF;gBAAIyB,cAAc9B;YAAgB;SAAE;QACnF+B,aAAa;YACXX,WAAWlB,mCAAmCU;YAC9CQ,WAAWnB,0BAA0BS;YACrCU,WAAWrB,mBAAmBY;SAC/B;QACDqB,SAAS;YACP;gBACEC,WAAWlB;gBACXI;gBACAe,YAAYrB;gBACZ,yFAAyF;gBACzF,2FAA2F;gBAC3FS,WAAW;oBACT;wBACEE,MAAM;wBACNW,IAAI;wBACJC,cAAc;oBAChB;oBACA,6EAA6E;oBAC7E,iCAAiC;oBACjC;wBACEZ,MAAM;wBACNW,IAAI;wBACJC,cAAc;oBAChB;iBACD;YACH;YACA,sFAAsF;YACtF;gBACEH,WAAWnB;gBACXK;gBACAe,YAAYrB;gBACZS,WAAW;oBACT;wBACEE,MAAM;wBACNW,IAAI;wBACJC,cAAc;oBAChB;iBACD;YACH;SACD;IACH;AACF"}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
|
|
1
|
+
import type { WorkflowsModuleClient } from '@shipfox/api-workflows-dto/inter-module';
|
|
2
|
+
export declare function createAppendLogsRoute(workflows: WorkflowsModuleClient): import("@shipfox/node-fastify").RouteDefinition;
|
|
2
3
|
//# sourceMappingURL=append-logs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"append-logs.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/append-logs.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"append-logs.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/append-logs.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,yCAAyC,CAAC;AAMnF,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,qBAAqB,mDAwDrE"}
|
|
@@ -5,67 +5,67 @@ import { ClientError, defineRoute } from '@shipfox/node-fastify';
|
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
import { appendLogs } from '#core/append-logs.js';
|
|
7
7
|
import { LeaseStreamMismatchError, MalformedLogChunkError, OffsetGapError } from '#core/errors.js';
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
export function createAppendLogsRoute(workflows) {
|
|
9
|
+
return defineRoute({
|
|
10
|
+
method: 'POST',
|
|
11
|
+
path: '/steps/:stepId/logs',
|
|
12
|
+
description: 'Append a chunk of logs for a step attempt of the leased job.',
|
|
13
|
+
schema: {
|
|
14
|
+
params: z.object({
|
|
15
|
+
stepId: z.string().uuid()
|
|
16
|
+
}),
|
|
17
|
+
querystring: appendLogsQuerySchema,
|
|
18
|
+
response: {
|
|
19
|
+
200: appendLogsResponseSchema,
|
|
20
|
+
409: offsetGapResponseSchema
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
errorHandler: (error)=>{
|
|
24
|
+
if (error instanceof OffsetGapError) {
|
|
25
|
+
throw new ClientError('Append offset is ahead of the committed length', 'offset-gap', {
|
|
26
|
+
status: 409,
|
|
27
|
+
details: {
|
|
28
|
+
committed_length: error.committedLength
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
if (error instanceof MalformedLogChunkError) {
|
|
33
|
+
throw new ClientError(error.message, 'malformed-log-chunk', {
|
|
34
|
+
status: 400
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (error instanceof LeaseStreamMismatchError) {
|
|
38
|
+
throw new ClientError(error.message, 'lease-stream-mismatch', {
|
|
39
|
+
status: 403
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
},
|
|
44
|
+
handler: async (request)=>{
|
|
45
|
+
const leasedJob = requireLeasedJobContext(request);
|
|
46
|
+
const { stepId } = request.params;
|
|
47
|
+
const { attempt, offset } = request.query;
|
|
48
|
+
if (leasedJob.currentStepId !== stepId || leasedJob.currentStepAttempt !== attempt) {
|
|
49
|
+
throw new ClientError('Step not found for leased job execution', 'step-not-found', {
|
|
50
|
+
status: 404
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
const result = await appendLogs({
|
|
54
|
+
jobId: leasedJob.jobId,
|
|
55
|
+
workspaceId: leasedJob.workspaceId,
|
|
56
|
+
projectId: leasedJob.projectId,
|
|
57
|
+
workflowRunAttemptId: leasedJob.workflowRunAttemptId,
|
|
58
|
+
stepId,
|
|
59
|
+
attempt,
|
|
60
|
+
offset,
|
|
61
|
+
body: request.body ?? Buffer.alloc(0)
|
|
62
|
+
}, workflows);
|
|
63
|
+
return {
|
|
64
|
+
committed_length: result.committedLength,
|
|
65
|
+
capped: result.capped
|
|
66
|
+
};
|
|
20
67
|
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (error instanceof OffsetGapError) {
|
|
24
|
-
throw new ClientError('Append offset is ahead of the committed length', 'offset-gap', {
|
|
25
|
-
status: 409,
|
|
26
|
-
details: {
|
|
27
|
-
committed_length: error.committedLength
|
|
28
|
-
}
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
if (error instanceof MalformedLogChunkError) {
|
|
32
|
-
throw new ClientError(error.message, 'malformed-log-chunk', {
|
|
33
|
-
status: 400
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
if (error instanceof LeaseStreamMismatchError) {
|
|
37
|
-
throw new ClientError(error.message, 'lease-stream-mismatch', {
|
|
38
|
-
status: 403
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
throw error;
|
|
42
|
-
},
|
|
43
|
-
handler: async (request)=>{
|
|
44
|
-
const leasedJob = requireLeasedJobContext(request);
|
|
45
|
-
const { stepId } = request.params;
|
|
46
|
-
const { attempt, offset } = request.query;
|
|
47
|
-
// The scoped lease proves membership in the dispatched step attempt; active-step
|
|
48
|
-
// completion remains governed by workflow/report state, not the log append route.
|
|
49
|
-
if (leasedJob.currentStepId !== stepId || leasedJob.currentStepAttempt !== attempt) {
|
|
50
|
-
throw new ClientError('Step not found for leased job execution', 'step-not-found', {
|
|
51
|
-
status: 404
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
const result = await appendLogs({
|
|
55
|
-
jobId: leasedJob.jobId,
|
|
56
|
-
workspaceId: leasedJob.workspaceId,
|
|
57
|
-
projectId: leasedJob.projectId,
|
|
58
|
-
workflowRunAttemptId: leasedJob.workflowRunAttemptId,
|
|
59
|
-
stepId,
|
|
60
|
-
attempt,
|
|
61
|
-
offset,
|
|
62
|
-
body: request.body ?? Buffer.alloc(0)
|
|
63
|
-
});
|
|
64
|
-
return {
|
|
65
|
-
committed_length: result.committedLength,
|
|
66
|
-
capped: result.capped
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
});
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
70
|
|
|
71
71
|
//# sourceMappingURL=append-logs.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/routes/append-logs.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport {requireLeasedJobContext} from '@shipfox/api-auth-context';\nimport {\n appendLogsQuerySchema,\n appendLogsResponseSchema,\n offsetGapResponseSchema,\n} from '@shipfox/api-logs-dto';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport {appendLogs} from '#core/append-logs.js';\nimport {LeaseStreamMismatchError, MalformedLogChunkError, OffsetGapError} from '#core/errors.js';\n\nexport
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/append-logs.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport {requireLeasedJobContext} from '@shipfox/api-auth-context';\nimport {\n appendLogsQuerySchema,\n appendLogsResponseSchema,\n offsetGapResponseSchema,\n} from '@shipfox/api-logs-dto';\nimport type {WorkflowsModuleClient} from '@shipfox/api-workflows-dto/inter-module';\nimport {ClientError, defineRoute} from '@shipfox/node-fastify';\nimport {z} from 'zod';\nimport {appendLogs} from '#core/append-logs.js';\nimport {LeaseStreamMismatchError, MalformedLogChunkError, OffsetGapError} from '#core/errors.js';\n\nexport function createAppendLogsRoute(workflows: WorkflowsModuleClient) {\n return defineRoute({\n method: 'POST',\n path: '/steps/:stepId/logs',\n description: 'Append a chunk of logs for a step attempt of the leased job.',\n schema: {\n params: z.object({stepId: z.string().uuid()}),\n querystring: appendLogsQuerySchema,\n response: {\n 200: appendLogsResponseSchema,\n 409: offsetGapResponseSchema,\n },\n },\n errorHandler: (error) => {\n if (error instanceof OffsetGapError) {\n throw new ClientError('Append offset is ahead of the committed length', 'offset-gap', {\n status: 409,\n details: {committed_length: error.committedLength},\n });\n }\n if (error instanceof MalformedLogChunkError) {\n throw new ClientError(error.message, 'malformed-log-chunk', {status: 400});\n }\n if (error instanceof LeaseStreamMismatchError) {\n throw new ClientError(error.message, 'lease-stream-mismatch', {status: 403});\n }\n throw error;\n },\n handler: async (request) => {\n const leasedJob = requireLeasedJobContext(request);\n const {stepId} = request.params;\n const {attempt, offset} = request.query;\n\n if (leasedJob.currentStepId !== stepId || leasedJob.currentStepAttempt !== attempt) {\n throw new ClientError('Step not found for leased job execution', 'step-not-found', {\n status: 404,\n });\n }\n\n const result = await appendLogs(\n {\n jobId: leasedJob.jobId,\n workspaceId: leasedJob.workspaceId,\n projectId: leasedJob.projectId,\n workflowRunAttemptId: leasedJob.workflowRunAttemptId,\n stepId,\n attempt,\n offset,\n body: (request.body as Buffer | undefined) ?? Buffer.alloc(0),\n },\n workflows,\n );\n\n return {committed_length: result.committedLength, capped: result.capped};\n },\n });\n}\n"],"names":["Buffer","requireLeasedJobContext","appendLogsQuerySchema","appendLogsResponseSchema","offsetGapResponseSchema","ClientError","defineRoute","z","appendLogs","LeaseStreamMismatchError","MalformedLogChunkError","OffsetGapError","createAppendLogsRoute","workflows","method","path","description","schema","params","object","stepId","string","uuid","querystring","response","errorHandler","error","status","details","committed_length","committedLength","message","handler","request","leasedJob","attempt","offset","query","currentStepId","currentStepAttempt","result","jobId","workspaceId","projectId","workflowRunAttemptId","body","alloc","capped"],"mappings":"AAAA,SAAQA,MAAM,QAAO,cAAc;AACnC,SAAQC,uBAAuB,QAAO,4BAA4B;AAClE,SACEC,qBAAqB,EACrBC,wBAAwB,EACxBC,uBAAuB,QAClB,wBAAwB;AAE/B,SAAQC,WAAW,EAAEC,WAAW,QAAO,wBAAwB;AAC/D,SAAQC,CAAC,QAAO,MAAM;AACtB,SAAQC,UAAU,QAAO,uBAAuB;AAChD,SAAQC,wBAAwB,EAAEC,sBAAsB,EAAEC,cAAc,QAAO,kBAAkB;AAEjG,OAAO,SAASC,sBAAsBC,SAAgC;IACpE,OAAOP,YAAY;QACjBQ,QAAQ;QACRC,MAAM;QACNC,aAAa;QACbC,QAAQ;YACNC,QAAQX,EAAEY,MAAM,CAAC;gBAACC,QAAQb,EAAEc,MAAM,GAAGC,IAAI;YAAE;YAC3CC,aAAarB;YACbsB,UAAU;gBACR,KAAKrB;gBACL,KAAKC;YACP;QACF;QACAqB,cAAc,CAACC;YACb,IAAIA,iBAAiBf,gBAAgB;gBACnC,MAAM,IAAIN,YAAY,kDAAkD,cAAc;oBACpFsB,QAAQ;oBACRC,SAAS;wBAACC,kBAAkBH,MAAMI,eAAe;oBAAA;gBACnD;YACF;YACA,IAAIJ,iBAAiBhB,wBAAwB;gBAC3C,MAAM,IAAIL,YAAYqB,MAAMK,OAAO,EAAE,uBAAuB;oBAACJ,QAAQ;gBAAG;YAC1E;YACA,IAAID,iBAAiBjB,0BAA0B;gBAC7C,MAAM,IAAIJ,YAAYqB,MAAMK,OAAO,EAAE,yBAAyB;oBAACJ,QAAQ;gBAAG;YAC5E;YACA,MAAMD;QACR;QACAM,SAAS,OAAOC;YACd,MAAMC,YAAYjC,wBAAwBgC;YAC1C,MAAM,EAACb,MAAM,EAAC,GAAGa,QAAQf,MAAM;YAC/B,MAAM,EAACiB,OAAO,EAAEC,MAAM,EAAC,GAAGH,QAAQI,KAAK;YAEvC,IAAIH,UAAUI,aAAa,KAAKlB,UAAUc,UAAUK,kBAAkB,KAAKJ,SAAS;gBAClF,MAAM,IAAI9B,YAAY,2CAA2C,kBAAkB;oBACjFsB,QAAQ;gBACV;YACF;YAEA,MAAMa,SAAS,MAAMhC,WACnB;gBACEiC,OAAOP,UAAUO,KAAK;gBACtBC,aAAaR,UAAUQ,WAAW;gBAClCC,WAAWT,UAAUS,SAAS;gBAC9BC,sBAAsBV,UAAUU,oBAAoB;gBACpDxB;gBACAe;gBACAC;gBACAS,MAAM,AAACZ,QAAQY,IAAI,IAA2B7C,OAAO8C,KAAK,CAAC;YAC7D,GACAjC;YAGF,OAAO;gBAACgB,kBAAkBW,OAAOV,eAAe;gBAAEiB,QAAQP,OAAOO,MAAM;YAAA;QACzE;IACF;AACF"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WorkflowsModuleClient } from '@shipfox/api-workflows-dto/inter-module';
|
|
1
2
|
import { type RouteGroup } from '@shipfox/node-fastify';
|
|
2
|
-
export declare
|
|
3
|
+
export declare function createLogsRoutes(workflows: WorkflowsModuleClient): RouteGroup[];
|
|
3
4
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,KAAK,UAAU,EAAC,MAAM,uBAAuB,CAAC;AAQ3E,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,qBAAqB,EAAC,MAAM,yCAAyC,CAAC;AACnF,OAAO,EAAsB,KAAK,UAAU,EAAC,MAAM,uBAAuB,CAAC;AAQ3E,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,qBAAqB,GAAG,UAAU,EAAE,CAmB/E"}
|
|
@@ -1,32 +1,34 @@
|
|
|
1
1
|
import { AUTH_LEASED_JOB, AUTH_USER } from '@shipfox/api-auth-context';
|
|
2
2
|
import { createRawBodyPlugin } from '@shipfox/node-fastify';
|
|
3
3
|
import { config } from '#config.js';
|
|
4
|
-
import {
|
|
4
|
+
import { createAppendLogsRoute } from './append-logs.js';
|
|
5
5
|
import { readLogsRoute } from './read-logs.js';
|
|
6
6
|
// Keep the lease-authed append in its own Fastify scope so the raw NDJSON parser does not
|
|
7
7
|
// disturb the JSON read route (or workflow routes). The body limit also bounds one-append
|
|
8
8
|
// budget overshoot. The read route is session-authed and workspace-scoped via the row.
|
|
9
|
-
export
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
9
|
+
export function createLogsRoutes(workflows) {
|
|
10
|
+
return [
|
|
11
|
+
{
|
|
12
|
+
prefix: '/runs/jobs/current',
|
|
13
|
+
auth: AUTH_LEASED_JOB,
|
|
14
|
+
plugins: [
|
|
15
|
+
createRawBodyPlugin({
|
|
16
|
+
contentType: 'application/x-ndjson',
|
|
17
|
+
bodyLimit: config.LOG_APPEND_BODY_LIMIT_BYTES
|
|
18
|
+
})
|
|
19
|
+
],
|
|
20
|
+
routes: [
|
|
21
|
+
createAppendLogsRoute(workflows)
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
prefix: '/steps',
|
|
26
|
+
auth: AUTH_USER,
|
|
27
|
+
routes: [
|
|
28
|
+
readLogsRoute
|
|
29
|
+
]
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
}
|
|
31
33
|
|
|
32
34
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/presentation/routes/index.ts"],"sourcesContent":["import {AUTH_LEASED_JOB, AUTH_USER} from '@shipfox/api-auth-context';\nimport {createRawBodyPlugin, type RouteGroup} from '@shipfox/node-fastify';\nimport {config} from '#config.js';\nimport {
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/routes/index.ts"],"sourcesContent":["import {AUTH_LEASED_JOB, AUTH_USER} from '@shipfox/api-auth-context';\nimport type {WorkflowsModuleClient} from '@shipfox/api-workflows-dto/inter-module';\nimport {createRawBodyPlugin, type RouteGroup} from '@shipfox/node-fastify';\nimport {config} from '#config.js';\nimport {createAppendLogsRoute} from './append-logs.js';\nimport {readLogsRoute} from './read-logs.js';\n\n// Keep the lease-authed append in its own Fastify scope so the raw NDJSON parser does not\n// disturb the JSON read route (or workflow routes). The body limit also bounds one-append\n// budget overshoot. The read route is session-authed and workspace-scoped via the row.\nexport function createLogsRoutes(workflows: WorkflowsModuleClient): RouteGroup[] {\n return [\n {\n prefix: '/runs/jobs/current',\n auth: AUTH_LEASED_JOB,\n plugins: [\n createRawBodyPlugin({\n contentType: 'application/x-ndjson',\n bodyLimit: config.LOG_APPEND_BODY_LIMIT_BYTES,\n }),\n ],\n routes: [createAppendLogsRoute(workflows)],\n },\n {\n prefix: '/steps',\n auth: AUTH_USER,\n routes: [readLogsRoute],\n },\n ];\n}\n"],"names":["AUTH_LEASED_JOB","AUTH_USER","createRawBodyPlugin","config","createAppendLogsRoute","readLogsRoute","createLogsRoutes","workflows","prefix","auth","plugins","contentType","bodyLimit","LOG_APPEND_BODY_LIMIT_BYTES","routes"],"mappings":"AAAA,SAAQA,eAAe,EAAEC,SAAS,QAAO,4BAA4B;AAErE,SAAQC,mBAAmB,QAAwB,wBAAwB;AAC3E,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAQC,qBAAqB,QAAO,mBAAmB;AACvD,SAAQC,aAAa,QAAO,iBAAiB;AAE7C,0FAA0F;AAC1F,0FAA0F;AAC1F,uFAAuF;AACvF,OAAO,SAASC,iBAAiBC,SAAgC;IAC/D,OAAO;QACL;YACEC,QAAQ;YACRC,MAAMT;YACNU,SAAS;gBACPR,oBAAoB;oBAClBS,aAAa;oBACbC,WAAWT,OAAOU,2BAA2B;gBAC/C;aACD;YACDC,QAAQ;gBAACV,sBAAsBG;aAAW;QAC5C;QACA;YACEC,QAAQ;YACRC,MAAMR;YACNa,QAAQ;gBAACT;aAAc;QACzB;KACD;AACH"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compact-stream.d.ts","sourceRoot":"","sources":["../../../src/temporal/activities/compact-stream.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"compact-stream.d.ts","sourceRoot":"","sources":["../../../src/temporal/activities/compact-stream.ts"],"names":[],"mappings":"AAIA,OAAO,EAAC,mBAAmB,EAAC,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAK,KAAK,WAAW,EAAC,MAAM,WAAW,CAAC;AAI/C,MAAM,MAAM,mBAAmB,GAC3B;IAAC,OAAO,EAAE,MAAM,CAAA;CAAC,GACjB;IAAC,OAAO,EAAE,mBAAmB,CAAA;CAAC,GAC9B;IAAC,OAAO,EAAE,YAAY,CAAA;CAAC,GACvB;IAAC,OAAO,EAAE,iBAAiB,CAAA;CAAC,GAC5B;IAAC,OAAO,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,iBAAiB,EAAE,MAAM,CAAA;CAAC,CAAC;AAE7F,UAAU,yBAAyB;IACjC,mBAAmB,EAAE,OAAO,mBAAmB,CAAC;IAChD,2BAA2B,EAAE,CAC3B,EAAE,EAAE,WAAW,EACf,MAAM,EAAE;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAC,KAC1C,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAC,CAAC,CAAC;CAClC;AAgGD,wBAAgB,2BAA2B,CACzC,YAAY,GAAE,yBAA+C,GAC5D,CAAC,MAAM,EAAE;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAC,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAW9D;AAED,eAAO,MAAM,qBAAqB,WAbtB;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAC,KAAK,OAAO,CAAC,mBAAmB,CAaI,CAAC"}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { reportError } from '@shipfox/node-error-monitoring';
|
|
2
|
+
import { logger } from '@shipfox/node-opentelemetry';
|
|
1
3
|
import { Context } from '@temporalio/activity';
|
|
2
4
|
import { compactedObjectKey, deleteObject, putCompactedObject } from '#api/object-storage.js';
|
|
3
5
|
import { compactedGzipStream } from '#core/compaction.js';
|
|
@@ -55,7 +57,21 @@ const defaultDependencies = {
|
|
|
55
57
|
}
|
|
56
58
|
});
|
|
57
59
|
if (stats.chunkCount !== expected.count || stats.lastSeq !== expected.maxSeq || stats.uncompressedBytes !== expected.uncompressedBytes) {
|
|
58
|
-
await deleteObject(uploadKey).catch(()=>
|
|
60
|
+
await deleteObject(uploadKey).catch((error)=>{
|
|
61
|
+
logger().error({
|
|
62
|
+
err: error,
|
|
63
|
+
streamId: stream.id,
|
|
64
|
+
objectKey: uploadKey
|
|
65
|
+
}, 'Failed to delete invalid compacted log object');
|
|
66
|
+
reportError(error, {
|
|
67
|
+
boundary: 'logs.cleanup',
|
|
68
|
+
operation: 'delete-invalid-compaction-object',
|
|
69
|
+
extra: {
|
|
70
|
+
streamId: stream.id,
|
|
71
|
+
objectKey: uploadKey
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
59
75
|
throw new Error(`Compaction integrity check failed for stream ${stream.id}: streamed ${stats.chunkCount} chunks / ${stats.uncompressedBytes} bytes up to seq ${stats.lastSeq}, table holds ${expected.count} / ${expected.uncompressedBytes} bytes up to seq ${expected.maxSeq}`);
|
|
60
76
|
}
|
|
61
77
|
const { updated } = await db().transaction((tx)=>dependencies.setObjectKeyAndDeleteChunks(tx, {
|