@shipfox/api-logs 2.0.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 +2 -0
- package/.turbo/turbo-type$colon$emit.log +1 -0
- package/.turbo/turbo-type.log +1 -0
- package/CHANGELOG.md +179 -0
- package/LICENSE +21 -0
- package/README.md +151 -0
- package/dist/api/object-storage.d.ts +69 -0
- package/dist/api/object-storage.d.ts.map +1 -0
- package/dist/api/object-storage.js +199 -0
- package/dist/api/object-storage.js.map +1 -0
- package/dist/config.d.ts +20 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +108 -0
- package/dist/config.js.map +1 -0
- package/dist/core/append-logs.d.ts +28 -0
- package/dist/core/append-logs.d.ts.map +1 -0
- package/dist/core/append-logs.js +318 -0
- package/dist/core/append-logs.js.map +1 -0
- package/dist/core/budget.d.ts +12 -0
- package/dist/core/budget.d.ts.map +1 -0
- package/dist/core/budget.js +9 -0
- package/dist/core/budget.js.map +1 -0
- package/dist/core/close-stream.d.ts +27 -0
- package/dist/core/close-stream.d.ts.map +1 -0
- package/dist/core/close-stream.js +59 -0
- package/dist/core/close-stream.js.map +1 -0
- package/dist/core/compaction.d.ts +28 -0
- package/dist/core/compaction.d.ts.map +1 -0
- package/dist/core/compaction.js +49 -0
- package/dist/core/compaction.js.map +1 -0
- package/dist/core/entities/attempt-stream.d.ts +37 -0
- package/dist/core/entities/attempt-stream.d.ts.map +1 -0
- package/dist/core/entities/attempt-stream.js +15 -0
- package/dist/core/entities/attempt-stream.js.map +1 -0
- package/dist/core/entities/job-accounting.d.ts +12 -0
- package/dist/core/entities/job-accounting.d.ts.map +1 -0
- package/dist/core/entities/job-accounting.js +6 -0
- package/dist/core/entities/job-accounting.js.map +1 -0
- package/dist/core/entities/log-object.d.ts +13 -0
- package/dist/core/entities/log-object.d.ts.map +1 -0
- package/dist/core/entities/log-object.js +9 -0
- package/dist/core/entities/log-object.js.map +1 -0
- package/dist/core/errors.d.ts +26 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +31 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/finalize-attempt-stream.d.ts +22 -0
- package/dist/core/finalize-attempt-stream.d.ts.map +1 -0
- package/dist/core/finalize-attempt-stream.js +48 -0
- package/dist/core/finalize-attempt-stream.js.map +1 -0
- package/dist/core/read-logs.d.ts +33 -0
- package/dist/core/read-logs.d.ts.map +1 -0
- package/dist/core/read-logs.js +49 -0
- package/dist/core/read-logs.js.map +1 -0
- package/dist/core/reap-stale-open-streams.d.ts +27 -0
- package/dist/core/reap-stale-open-streams.d.ts.map +1 -0
- package/dist/core/reap-stale-open-streams.js +50 -0
- package/dist/core/reap-stale-open-streams.js.map +1 -0
- package/dist/core/retention.d.ts +32 -0
- package/dist/core/retention.d.ts.map +1 -0
- package/dist/core/retention.js +148 -0
- package/dist/core/retention.js.map +1 -0
- package/dist/core/session/claude/rows.d.ts +6 -0
- package/dist/core/session/claude/rows.d.ts.map +1 -0
- package/dist/core/session/claude/rows.js +145 -0
- package/dist/core/session/claude/rows.js.map +1 -0
- package/dist/core/session/claude-parser.d.ts +4 -0
- package/dist/core/session/claude-parser.d.ts.map +1 -0
- package/dist/core/session/claude-parser.js +42 -0
- package/dist/core/session/claude-parser.js.map +1 -0
- package/dist/core/session/entry-schema.d.ts +51 -0
- package/dist/core/session/entry-schema.d.ts.map +1 -0
- package/dist/core/session/entry-schema.js +61 -0
- package/dist/core/session/entry-schema.js.map +1 -0
- package/dist/core/session/object.d.ts +12 -0
- package/dist/core/session/object.d.ts.map +1 -0
- package/dist/core/session/object.js +50 -0
- package/dist/core/session/object.js.map +1 -0
- package/dist/core/session/parse-session.d.ts +5 -0
- package/dist/core/session/parse-session.d.ts.map +1 -0
- package/dist/core/session/parse-session.js +18 -0
- package/dist/core/session/parse-session.js.map +1 -0
- package/dist/core/session/pi/message-rows.d.ts +4 -0
- package/dist/core/session/pi/message-rows.d.ts.map +1 -0
- package/dist/core/session/pi/message-rows.js +113 -0
- package/dist/core/session/pi/message-rows.js.map +1 -0
- package/dist/core/session/pi/metadata.d.ts +18 -0
- package/dist/core/session/pi/metadata.d.ts.map +1 -0
- package/dist/core/session/pi/metadata.js +120 -0
- package/dist/core/session/pi/metadata.js.map +1 -0
- package/dist/core/session/pi/text.d.ts +12 -0
- package/dist/core/session/pi/text.d.ts.map +1 -0
- package/dist/core/session/pi/text.js +59 -0
- package/dist/core/session/pi/text.js.map +1 -0
- package/dist/core/session/pi-parser.d.ts +5 -0
- package/dist/core/session/pi-parser.d.ts.map +1 -0
- package/dist/core/session/pi-parser.js +73 -0
- package/dist/core/session/pi-parser.js.map +1 -0
- package/dist/core/session/rows.d.ts +7 -0
- package/dist/core/session/rows.d.ts.map +1 -0
- package/dist/core/session/rows.js +39 -0
- package/dist/core/session/rows.js.map +1 -0
- package/dist/core/session/session-record.d.ts +5 -0
- package/dist/core/session/session-record.d.ts.map +1 -0
- package/dist/core/session/session-record.js +3 -0
- package/dist/core/session/session-record.js.map +1 -0
- package/dist/db/accounting.d.ts +33 -0
- package/dist/db/accounting.d.ts.map +1 -0
- package/dist/db/accounting.js +52 -0
- package/dist/db/accounting.js.map +1 -0
- package/dist/db/chunks.d.ts +62 -0
- package/dist/db/chunks.d.ts.map +1 -0
- package/dist/db/chunks.js +88 -0
- package/dist/db/chunks.js.map +1 -0
- package/dist/db/db.d.ts +752 -0
- package/dist/db/db.d.ts.map +1 -0
- package/dist/db/db.js +24 -0
- package/dist/db/db.js.map +1 -0
- package/dist/db/index.d.ts +2 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +5 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema/attempt-streams.d.ts +304 -0
- package/dist/db/schema/attempt-streams.d.ts.map +1 -0
- package/dist/db/schema/attempt-streams.js +100 -0
- package/dist/db/schema/attempt-streams.js.map +1 -0
- package/dist/db/schema/chunks.d.ts +166 -0
- package/dist/db/schema/chunks.d.ts.map +1 -0
- package/dist/db/schema/chunks.js +47 -0
- package/dist/db/schema/chunks.js.map +1 -0
- package/dist/db/schema/common.d.ts +29 -0
- package/dist/db/schema/common.d.ts.map +1 -0
- package/dist/db/schema/common.js +11 -0
- package/dist/db/schema/common.js.map +1 -0
- package/dist/db/schema/job-accounting.d.ts +143 -0
- package/dist/db/schema/job-accounting.d.ts.map +1 -0
- package/dist/db/schema/job-accounting.js +43 -0
- package/dist/db/schema/job-accounting.js.map +1 -0
- package/dist/db/schema/outbox.d.ts +195 -0
- package/dist/db/schema/outbox.d.ts.map +1 -0
- package/dist/db/schema/outbox.js +5 -0
- package/dist/db/schema/outbox.js.map +1 -0
- package/dist/db/streams.d.ts +141 -0
- package/dist/db/streams.d.ts.map +1 -0
- package/dist/db/streams.js +197 -0
- package/dist/db/streams.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +81 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics/index.d.ts +2 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +3 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/instance.d.ts +14 -0
- package/dist/metrics/instance.d.ts.map +1 -0
- package/dist/metrics/instance.js +16 -0
- package/dist/metrics/instance.js.map +1 -0
- package/dist/metrics/service.d.ts +2 -0
- package/dist/metrics/service.d.ts.map +1 -0
- package/dist/metrics/service.js +20 -0
- package/dist/metrics/service.js.map +1 -0
- package/dist/presentation/dto/read-logs.d.ts +4 -0
- package/dist/presentation/dto/read-logs.d.ts.map +1 -0
- package/dist/presentation/dto/read-logs.js +22 -0
- package/dist/presentation/dto/read-logs.js.map +1 -0
- package/dist/presentation/routes/append-logs.d.ts +2 -0
- package/dist/presentation/routes/append-logs.d.ts.map +1 -0
- package/dist/presentation/routes/append-logs.js +71 -0
- package/dist/presentation/routes/append-logs.js.map +1 -0
- package/dist/presentation/routes/index.d.ts +3 -0
- package/dist/presentation/routes/index.d.ts.map +1 -0
- package/dist/presentation/routes/index.js +32 -0
- package/dist/presentation/routes/index.js.map +1 -0
- package/dist/presentation/routes/read-logs.d.ts +2 -0
- package/dist/presentation/routes/read-logs.d.ts.map +1 -0
- package/dist/presentation/routes/read-logs.js +41 -0
- package/dist/presentation/routes/read-logs.js.map +1 -0
- package/dist/presentation/subscribers/on-job-terminated.d.ts +8 -0
- package/dist/presentation/subscribers/on-job-terminated.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-job-terminated.js +32 -0
- package/dist/presentation/subscribers/on-job-terminated.js.map +1 -0
- package/dist/presentation/subscribers/on-log-stream-closed.d.ts +9 -0
- package/dist/presentation/subscribers/on-log-stream-closed.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-log-stream-closed.js +31 -0
- package/dist/presentation/subscribers/on-log-stream-closed.js.map +1 -0
- package/dist/presentation/subscribers/on-step-attempt-terminated.d.ts +3 -0
- package/dist/presentation/subscribers/on-step-attempt-terminated.d.ts.map +1 -0
- package/dist/presentation/subscribers/on-step-attempt-terminated.js +14 -0
- package/dist/presentation/subscribers/on-step-attempt-terminated.js.map +1 -0
- package/dist/temporal/activities/close-abandoned-streams.d.ts +12 -0
- package/dist/temporal/activities/close-abandoned-streams.d.ts.map +1 -0
- package/dist/temporal/activities/close-abandoned-streams.js +33 -0
- package/dist/temporal/activities/close-abandoned-streams.js.map +1 -0
- package/dist/temporal/activities/compact-stream.d.ts +33 -0
- package/dist/temporal/activities/compact-stream.d.ts.map +1 -0
- package/dist/temporal/activities/compact-stream.js +95 -0
- package/dist/temporal/activities/compact-stream.js.map +1 -0
- package/dist/temporal/activities/compaction-reconcile.d.ts +16 -0
- package/dist/temporal/activities/compaction-reconcile.d.ts.map +1 -0
- package/dist/temporal/activities/compaction-reconcile.js +52 -0
- package/dist/temporal/activities/compaction-reconcile.js.map +1 -0
- package/dist/temporal/activities/index.d.ts +14 -0
- package/dist/temporal/activities/index.d.ts.map +1 -0
- package/dist/temporal/activities/index.js +16 -0
- package/dist/temporal/activities/index.js.map +1 -0
- package/dist/temporal/activities/reap-stale-open-streams.d.ts +4 -0
- package/dist/temporal/activities/reap-stale-open-streams.d.ts.map +1 -0
- package/dist/temporal/activities/reap-stale-open-streams.js +11 -0
- package/dist/temporal/activities/reap-stale-open-streams.js.map +1 -0
- package/dist/temporal/activities/retention-sweep.d.ts +7 -0
- package/dist/temporal/activities/retention-sweep.d.ts.map +1 -0
- package/dist/temporal/activities/retention-sweep.js +19 -0
- package/dist/temporal/activities/retention-sweep.js.map +1 -0
- package/dist/temporal/constants.d.ts +7 -0
- package/dist/temporal/constants.d.ts.map +1 -0
- package/dist/temporal/constants.js +13 -0
- package/dist/temporal/constants.js.map +1 -0
- package/dist/temporal/workflows/close-abandoned-streams.d.ts +11 -0
- package/dist/temporal/workflows/close-abandoned-streams.d.ts.map +1 -0
- package/dist/temporal/workflows/close-abandoned-streams.js +22 -0
- package/dist/temporal/workflows/close-abandoned-streams.js.map +1 -0
- package/dist/temporal/workflows/compact-stream.d.ts +6 -0
- package/dist/temporal/workflows/compact-stream.d.ts.map +1 -0
- package/dist/temporal/workflows/compact-stream.js +25 -0
- package/dist/temporal/workflows/compact-stream.js.map +1 -0
- package/dist/temporal/workflows/compaction-reconcile-cron.d.ts +3 -0
- package/dist/temporal/workflows/compaction-reconcile-cron.d.ts.map +1 -0
- package/dist/temporal/workflows/compaction-reconcile-cron.js +15 -0
- package/dist/temporal/workflows/compaction-reconcile-cron.js.map +1 -0
- package/dist/temporal/workflows/index.d.ts +6 -0
- package/dist/temporal/workflows/index.d.ts.map +1 -0
- package/dist/temporal/workflows/index.js +7 -0
- package/dist/temporal/workflows/index.js.map +1 -0
- package/dist/temporal/workflows/reap-stale-open-streams-cron.d.ts +2 -0
- package/dist/temporal/workflows/reap-stale-open-streams-cron.d.ts.map +1 -0
- package/dist/temporal/workflows/reap-stale-open-streams-cron.js +15 -0
- package/dist/temporal/workflows/reap-stale-open-streams-cron.js.map +1 -0
- package/dist/temporal/workflows/retention-sweep-cron.d.ts +2 -0
- package/dist/temporal/workflows/retention-sweep-cron.d.ts.map +1 -0
- package/dist/temporal/workflows/retention-sweep-cron.js +23 -0
- package/dist/temporal/workflows/retention-sweep-cron.js.map +1 -0
- package/dist/tsconfig.test.tsbuildinfo +1 -0
- package/drizzle/0000_initial.sql +65 -0
- package/drizzle/meta/0000_snapshot.json +529 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +7 -0
- package/package.json +84 -0
- package/src/api/object-storage-credentials.test.ts +70 -0
- package/src/api/object-storage.test.ts +64 -0
- package/src/api/object-storage.ts +226 -0
- package/src/config.test.ts +118 -0
- package/src/config.ts +130 -0
- package/src/core/append-logs.test.ts +563 -0
- package/src/core/append-logs.ts +384 -0
- package/src/core/budget.test.ts +30 -0
- package/src/core/budget.ts +19 -0
- package/src/core/close-stream.ts +72 -0
- package/src/core/compaction.ts +68 -0
- package/src/core/entities/attempt-stream.ts +38 -0
- package/src/core/entities/job-accounting.ts +11 -0
- package/src/core/entities/log-object.test.ts +25 -0
- package/src/core/entities/log-object.ts +18 -0
- package/src/core/errors.ts +37 -0
- package/src/core/finalize-attempt-stream.test.ts +90 -0
- package/src/core/finalize-attempt-stream.ts +59 -0
- package/src/core/read-logs.ts +77 -0
- package/src/core/reap-stale-open-streams.test.ts +137 -0
- package/src/core/reap-stale-open-streams.ts +60 -0
- package/src/core/retention.test.ts +388 -0
- package/src/core/retention.ts +184 -0
- package/src/core/session/claude/rows.ts +223 -0
- package/src/core/session/claude-parser.test.ts +199 -0
- package/src/core/session/claude-parser.ts +38 -0
- package/src/core/session/entry-schema.ts +82 -0
- package/src/core/session/object.ts +57 -0
- package/src/core/session/parse-session.ts +23 -0
- package/src/core/session/pi/message-rows.ts +211 -0
- package/src/core/session/pi/metadata.ts +148 -0
- package/src/core/session/pi/text.ts +83 -0
- package/src/core/session/pi-parser.test.ts +451 -0
- package/src/core/session/pi-parser.ts +107 -0
- package/src/core/session/rows.ts +38 -0
- package/src/core/session/session-record.ts +4 -0
- package/src/db/accounting.ts +81 -0
- package/src/db/chunks.ts +143 -0
- package/src/db/db.ts +22 -0
- package/src/db/index.ts +4 -0
- package/src/db/schema/attempt-streams.ts +106 -0
- package/src/db/schema/chunks.ts +43 -0
- package/src/db/schema/common.ts +12 -0
- package/src/db/schema/job-accounting.ts +38 -0
- package/src/db/schema/outbox.ts +4 -0
- package/src/db/streams.test.ts +47 -0
- package/src/db/streams.ts +368 -0
- package/src/index.ts +77 -0
- package/src/metrics/index.ts +1 -0
- package/src/metrics/instance.ts +33 -0
- package/src/metrics/service.ts +23 -0
- package/src/presentation/dto/read-logs.ts +24 -0
- package/src/presentation/routes/append-logs.test.ts +279 -0
- package/src/presentation/routes/append-logs.ts +66 -0
- package/src/presentation/routes/index.ts +27 -0
- package/src/presentation/routes/read-logs.test.ts +504 -0
- package/src/presentation/routes/read-logs.ts +38 -0
- package/src/presentation/subscribers/on-job-terminated.test.ts +64 -0
- package/src/presentation/subscribers/on-job-terminated.ts +26 -0
- package/src/presentation/subscribers/on-log-stream-closed.test.ts +63 -0
- package/src/presentation/subscribers/on-log-stream-closed.ts +26 -0
- package/src/presentation/subscribers/on-step-attempt-terminated.ts +16 -0
- package/src/temporal/activities/close-abandoned-streams.test.ts +84 -0
- package/src/temporal/activities/close-abandoned-streams.ts +30 -0
- package/src/temporal/activities/compact-stream.test.ts +221 -0
- package/src/temporal/activities/compact-stream.ts +123 -0
- package/src/temporal/activities/compaction-reconcile.test.ts +143 -0
- package/src/temporal/activities/compaction-reconcile.ts +48 -0
- package/src/temporal/activities/index.ts +15 -0
- package/src/temporal/activities/reap-stale-open-streams.test.ts +48 -0
- package/src/temporal/activities/reap-stale-open-streams.ts +14 -0
- package/src/temporal/activities/retention-sweep.test.ts +38 -0
- package/src/temporal/activities/retention-sweep.ts +23 -0
- package/src/temporal/constants.ts +14 -0
- package/src/temporal/workflows/close-abandoned-streams.ts +25 -0
- package/src/temporal/workflows/compact-stream.ts +26 -0
- package/src/temporal/workflows/compaction-reconcile-cron.ts +14 -0
- package/src/temporal/workflows/index.ts +5 -0
- package/src/temporal/workflows/reap-stale-open-streams-cron.ts +13 -0
- package/src/temporal/workflows/retention-sweep-cron.ts +23 -0
- package/test/env.ts +37 -0
- package/test/factories/job-accounting.ts +38 -0
- package/test/fixtures/closed-stream.ts +53 -0
- package/test/fixtures/lease-token.ts +42 -0
- package/test/fixtures/ndjson.ts +34 -0
- package/test/globalSetup.ts +44 -0
- package/test/index.ts +9 -0
- package/test/queries.ts +54 -0
- package/test/setup.ts +17 -0
- package/tsconfig.build.json +9 -0
- package/tsconfig.build.tsbuildinfo +1 -0
- package/tsconfig.json +3 -0
- package/tsconfig.test.json +8 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { and, asc, eq, getTableColumns, isNull, lt, notInArray, sql } from 'drizzle-orm';
|
|
2
|
+
import { LeaseStreamMismatchError } from '#core/errors.js';
|
|
3
|
+
import { db } from './db.js';
|
|
4
|
+
import { attemptStreams, toAttemptStream } from './schema/attempt-streams.js';
|
|
5
|
+
import { logChunks } from './schema/chunks.js';
|
|
6
|
+
/**
|
|
7
|
+
* Reads the stream for `(job, step, attempt)` without creating it. Used by the
|
|
8
|
+
* empty-body heartbeat path so a runner cannot mint rows with empty appends.
|
|
9
|
+
*/ export async function getAttemptStream(tx, lookup) {
|
|
10
|
+
const [row] = await tx.select().from(attemptStreams).where(and(eq(attemptStreams.jobId, lookup.jobId), eq(attemptStreams.stepId, lookup.stepId), eq(attemptStreams.attempt, lookup.attempt)));
|
|
11
|
+
return row ? toAttemptStream(row) : null;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Reads the stream for `(step, attempt)` for the session read path, which holds no lease
|
|
15
|
+
* and so has no `jobId`. `stepId` is a globally unique uuidv7, so `(stepId, attempt)`
|
|
16
|
+
* selects at most one row even though stream identity is `(jobId, stepId, attempt)`. The
|
|
17
|
+
* caller authorizes against the row's denormalized `workspaceId`.
|
|
18
|
+
*/ export async function getStreamByStepAttempt(lookup) {
|
|
19
|
+
const [row] = await db().select().from(attemptStreams).where(and(eq(attemptStreams.stepId, lookup.stepId), eq(attemptStreams.attempt, lookup.attempt)));
|
|
20
|
+
return row ? toAttemptStream(row) : null;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Loads the stream for `(job, step, attempt)`, creating it on first append.
|
|
24
|
+
* Scoped by `jobId` from the lease, so a lease never reaches another job's stream.
|
|
25
|
+
* The create path reports whether the row was inserted so callers can record
|
|
26
|
+
* first-open side effects only after their enclosing transaction commits.
|
|
27
|
+
*/ export async function getOrCreateAttemptStreamWithStatus(tx, identity) {
|
|
28
|
+
const [row] = await tx.insert(attemptStreams).values(identity).onConflictDoUpdate({
|
|
29
|
+
target: [
|
|
30
|
+
attemptStreams.jobId,
|
|
31
|
+
attemptStreams.stepId,
|
|
32
|
+
attemptStreams.attempt
|
|
33
|
+
],
|
|
34
|
+
set: {
|
|
35
|
+
updatedAt: sql`now()`
|
|
36
|
+
}
|
|
37
|
+
})// Postgres sets `xmax` only on the conflict-update path, so this keeps
|
|
38
|
+
// first-open detection in the single upsert round trip.
|
|
39
|
+
.returning({
|
|
40
|
+
...getTableColumns(attemptStreams),
|
|
41
|
+
created: sql`xmax = 0`
|
|
42
|
+
});
|
|
43
|
+
if (!row) throw new Error('attempt stream missing after get-or-create');
|
|
44
|
+
if (row.workspaceId !== identity.workspaceId || row.projectId !== identity.projectId || row.workflowRunAttemptId !== identity.workflowRunAttemptId) {
|
|
45
|
+
throw new LeaseStreamMismatchError();
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
stream: toAttemptStream(row),
|
|
49
|
+
created: row.created
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export async function getOrCreateAttemptStream(tx, identity) {
|
|
53
|
+
const result = await getOrCreateAttemptStreamWithStatus(tx, identity);
|
|
54
|
+
return result.stream;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Offset-CAS extend: advances `committed_length` by `byteLen` iff it currently
|
|
58
|
+
* equals `offset`. On mismatch, re-reads the committed length and classifies the
|
|
59
|
+
* caller's chunk: a retry (fully already applied) only when `offset + byteLen`
|
|
60
|
+
* does not run past the committed length; otherwise a gap. A chunk that straddles
|
|
61
|
+
* the committed point (offset behind it, but extending past it) is a gap too, so
|
|
62
|
+
* the runner rewinds rather than getting a 200 that silently drops its tail.
|
|
63
|
+
*/ export async function casExtendCommittedLength(tx, params) {
|
|
64
|
+
const extended = await tx.update(attemptStreams).set({
|
|
65
|
+
committedLength: sql`${attemptStreams.committedLength} + ${params.byteLen}`,
|
|
66
|
+
updatedAt: sql`now()`
|
|
67
|
+
}).where(and(eq(attemptStreams.id, params.streamId), eq(attemptStreams.committedLength, params.offset))).returning({
|
|
68
|
+
committedLength: attemptStreams.committedLength
|
|
69
|
+
});
|
|
70
|
+
if (extended[0]) return {
|
|
71
|
+
outcome: 'extended',
|
|
72
|
+
committedLength: extended[0].committedLength
|
|
73
|
+
};
|
|
74
|
+
const [current] = await tx.select({
|
|
75
|
+
committedLength: attemptStreams.committedLength
|
|
76
|
+
}).from(attemptStreams).where(eq(attemptStreams.id, params.streamId));
|
|
77
|
+
const committedLength = current?.committedLength ?? 0;
|
|
78
|
+
const fullyApplied = params.offset + params.byteLen <= committedLength;
|
|
79
|
+
return {
|
|
80
|
+
outcome: fullyApplied ? 'retry' : 'gap',
|
|
81
|
+
committedLength
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export async function setDeclaredTotalBytes(tx, params) {
|
|
85
|
+
await tx.update(attemptStreams).set({
|
|
86
|
+
declaredTotalBytes: params.declaredTotalBytes,
|
|
87
|
+
updatedAt: sql`now()`
|
|
88
|
+
}).where(eq(attemptStreams.id, params.streamId));
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Guarded close: flips `open → closed` and stamps `closed_at` iff the stream is
|
|
92
|
+
* still open, returning the closed row (or null when another path already closed
|
|
93
|
+
* it). The `WHERE state='open'` predicate is both the row lock and the
|
|
94
|
+
* exactly-once gate. `committed_length` is left untouched; it stays equal to the
|
|
95
|
+
* runner spool bytes. `truncated` is set only on the timeout path.
|
|
96
|
+
*/ export async function markStreamClosed(tx, params) {
|
|
97
|
+
const [row] = await tx.update(attemptStreams).set({
|
|
98
|
+
state: 'closed',
|
|
99
|
+
closeReason: params.reason,
|
|
100
|
+
closedAt: sql`now()`,
|
|
101
|
+
updatedAt: sql`now()`,
|
|
102
|
+
...params.markTruncated ? {
|
|
103
|
+
truncated: true
|
|
104
|
+
} : {}
|
|
105
|
+
}).where(and(eq(attemptStreams.id, params.streamId), eq(attemptStreams.state, 'open'))).returning();
|
|
106
|
+
return row ? toAttemptStream(row) : null;
|
|
107
|
+
}
|
|
108
|
+
/** Open streams of a job, for the timeout sweep to force-close after the grace period. */ export async function listOpenStreamsByJob(jobId) {
|
|
109
|
+
const rows = await db().select().from(attemptStreams).where(and(eq(attemptStreams.jobId, jobId), eq(attemptStreams.state, 'open')));
|
|
110
|
+
return rows.map(toAttemptStream);
|
|
111
|
+
}
|
|
112
|
+
/** For compaction, which is driven by the closed event that carries the stream id. */ export async function getAttemptStreamById(streamId) {
|
|
113
|
+
const [row] = await db().select().from(attemptStreams).where(eq(attemptStreams.id, streamId));
|
|
114
|
+
return row ? toAttemptStream(row) : null;
|
|
115
|
+
}
|
|
116
|
+
export async function getAttemptStreamByIdInTransaction(tx, streamId) {
|
|
117
|
+
const [row] = await tx.select().from(attemptStreams).where(eq(attemptStreams.id, streamId));
|
|
118
|
+
return row ? toAttemptStream(row) : null;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Final compaction step: records the object key and deletes the now-cold chunk rows in one
|
|
122
|
+
* transaction. The guard is `state='closed' AND object_key IS NULL`, so the publish is a
|
|
123
|
+
* single winner even when two compaction attempts (e.g. a heartbeat-timed-out run and its
|
|
124
|
+
* retry) race: each uploads to its own key, and only the first to land here writes a key and
|
|
125
|
+
* drops the chunks. A 0-row result means the row was either hard-deleted by retention or
|
|
126
|
+
* already published by another attempt; the caller re-reads to tell those apart and deletes
|
|
127
|
+
* its own now-orphaned upload either way. Returns whether this attempt won the publish.
|
|
128
|
+
*/ export async function setObjectKeyAndDeleteChunks(tx, params) {
|
|
129
|
+
const updated = await tx.update(attemptStreams).set({
|
|
130
|
+
objectKey: params.objectKey,
|
|
131
|
+
updatedAt: sql`now()`
|
|
132
|
+
}).where(and(eq(attemptStreams.id, params.streamId), eq(attemptStreams.state, 'closed'), isNull(attemptStreams.objectKey))).returning({
|
|
133
|
+
id: attemptStreams.id
|
|
134
|
+
});
|
|
135
|
+
if (updated.length === 0) return {
|
|
136
|
+
updated: false
|
|
137
|
+
};
|
|
138
|
+
await tx.delete(logChunks).where(eq(logChunks.streamId, params.streamId));
|
|
139
|
+
return {
|
|
140
|
+
updated: true
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Closed streams that never got an object key and have sat that way past the stale
|
|
145
|
+
* window: the reconcile cron re-drives compaction for these. Hits the partial index
|
|
146
|
+
* `logs_attempt_streams_uncompacted_idx`; ordered by `closed_at` so the oldest backlog
|
|
147
|
+
* drains first, bounded by `limit` per tick.
|
|
148
|
+
*/ export async function listStaleUncompactedStreams(params) {
|
|
149
|
+
const rows = await db().select().from(attemptStreams).where(and(eq(attemptStreams.state, 'closed'), isNull(attemptStreams.objectKey), lt(attemptStreams.closedAt, sql`now() - make_interval(secs => ${params.olderThanSeconds})`))).orderBy(asc(attemptStreams.closedAt)).limit(params.limit);
|
|
150
|
+
return rows.map(toAttemptStream);
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Lists open streams old enough to force-close. The age check is safe only when
|
|
154
|
+
* `olderThanSeconds` exceeds the lease TTL: leases are minted before a stream can
|
|
155
|
+
* be created, so no valid append can arrive after `created_at + leaseTTL`.
|
|
156
|
+
*/ export async function listStaleOpenStreams(params) {
|
|
157
|
+
const rows = await db().select().from(attemptStreams).where(and(eq(attemptStreams.state, 'open'), lt(attemptStreams.createdAt, sql`now() - make_interval(secs => ${params.olderThanSeconds})`))).orderBy(asc(attemptStreams.createdAt)).limit(params.limit);
|
|
158
|
+
return rows.map(toAttemptStream);
|
|
159
|
+
}
|
|
160
|
+
export async function getOpenStreamCount() {
|
|
161
|
+
const [row] = await db().select({
|
|
162
|
+
value: sql`count(*)`.mapWith(BigInt)
|
|
163
|
+
}).from(attemptStreams).where(eq(attemptStreams.state, 'open'));
|
|
164
|
+
return row?.value ?? 0n;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Lists expired closed streams for retention. `excludeIds` keeps failed or raced rows from
|
|
168
|
+
* starving younger rows in the same run, and avoids cursoring on `closed_at`, whose microsecond
|
|
169
|
+
* precision would be lost through JS `Date`.
|
|
170
|
+
*
|
|
171
|
+
* No `object_key` filter: retention deletes compacted and never-compacted streams alike.
|
|
172
|
+
*/ export async function listExpiredClosedStreams(params) {
|
|
173
|
+
const rows = await db().select().from(attemptStreams).where(and(eq(attemptStreams.state, 'closed'), lt(attemptStreams.closedAt, sql`now() - make_interval(days => ${params.retentionDays})`), params.excludeIds && params.excludeIds.length > 0 ? notInArray(attemptStreams.id, params.excludeIds) : undefined)).orderBy(asc(attemptStreams.closedAt), asc(attemptStreams.id)).limit(params.limit).for('update', {
|
|
174
|
+
skipLocked: true
|
|
175
|
+
});
|
|
176
|
+
return rows.map(toAttemptStream);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Hard-deletes one expired stream row, guarded on the observed `object_key` so a racing
|
|
180
|
+
* compaction publish leaves the row for the next sweep.
|
|
181
|
+
*/ export async function deleteExpiredStream(tx, params) {
|
|
182
|
+
const [row] = await tx.delete(attemptStreams).where(and(eq(attemptStreams.id, params.streamId), params.observedObjectKey === null ? isNull(attemptStreams.objectKey) : eq(attemptStreams.objectKey, params.observedObjectKey))).returning({
|
|
183
|
+
jobId: attemptStreams.jobId
|
|
184
|
+
});
|
|
185
|
+
return {
|
|
186
|
+
deleted: Boolean(row),
|
|
187
|
+
jobId: row?.jobId ?? null
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
export async function accountingHasNoStreams(tx, jobId) {
|
|
191
|
+
const [row] = await tx.select({
|
|
192
|
+
id: attemptStreams.id
|
|
193
|
+
}).from(attemptStreams).where(eq(attemptStreams.jobId, jobId)).limit(1);
|
|
194
|
+
return !row;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
//# sourceMappingURL=streams.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/db/streams.ts"],"sourcesContent":["import {and, asc, eq, getTableColumns, isNull, lt, notInArray, sql} from 'drizzle-orm';\nimport type {AttemptStream, StreamCloseReason} from '#core/entities/attempt-stream.js';\nimport {LeaseStreamMismatchError} from '#core/errors.js';\nimport {db, type Transaction} from './db.js';\nimport {attemptStreams, toAttemptStream} from './schema/attempt-streams.js';\nimport {logChunks} from './schema/chunks.js';\n\nexport interface AttemptStreamIdentity {\n jobId: string;\n stepId: string;\n attempt: number;\n workspaceId: string;\n projectId: string;\n workflowRunAttemptId: string;\n}\n\nexport interface AttemptStreamLookup {\n jobId: string;\n stepId: string;\n attempt: number;\n}\n\n/**\n * Reads the stream for `(job, step, attempt)` without creating it. Used by the\n * empty-body heartbeat path so a runner cannot mint rows with empty appends.\n */\nexport async function getAttemptStream(\n tx: Transaction,\n lookup: AttemptStreamLookup,\n): Promise<AttemptStream | null> {\n const [row] = await tx\n .select()\n .from(attemptStreams)\n .where(\n and(\n eq(attemptStreams.jobId, lookup.jobId),\n eq(attemptStreams.stepId, lookup.stepId),\n eq(attemptStreams.attempt, lookup.attempt),\n ),\n );\n return row ? toAttemptStream(row) : null;\n}\n\n/**\n * Reads the stream for `(step, attempt)` for the session read path, which holds no lease\n * and so has no `jobId`. `stepId` is a globally unique uuidv7, so `(stepId, attempt)`\n * selects at most one row even though stream identity is `(jobId, stepId, attempt)`. The\n * caller authorizes against the row's denormalized `workspaceId`.\n */\nexport async function getStreamByStepAttempt(lookup: {\n stepId: string;\n attempt: number;\n}): Promise<AttemptStream | null> {\n const [row] = await db()\n .select()\n .from(attemptStreams)\n .where(\n and(eq(attemptStreams.stepId, lookup.stepId), eq(attemptStreams.attempt, lookup.attempt)),\n );\n return row ? toAttemptStream(row) : null;\n}\n\nexport interface GetOrCreateAttemptStreamResult {\n stream: AttemptStream;\n created: boolean;\n}\n\n/**\n * Loads the stream for `(job, step, attempt)`, creating it on first append.\n * Scoped by `jobId` from the lease, so a lease never reaches another job's stream.\n * The create path reports whether the row was inserted so callers can record\n * first-open side effects only after their enclosing transaction commits.\n */\nexport async function getOrCreateAttemptStreamWithStatus(\n tx: Transaction,\n identity: AttemptStreamIdentity,\n): Promise<GetOrCreateAttemptStreamResult> {\n const [row] = await tx\n .insert(attemptStreams)\n .values(identity)\n .onConflictDoUpdate({\n target: [attemptStreams.jobId, attemptStreams.stepId, attemptStreams.attempt],\n set: {updatedAt: sql`now()`},\n })\n // Postgres sets `xmax` only on the conflict-update path, so this keeps\n // first-open detection in the single upsert round trip.\n .returning({...getTableColumns(attemptStreams), created: sql<boolean>`xmax = 0`});\n\n if (!row) throw new Error('attempt stream missing after get-or-create');\n if (\n row.workspaceId !== identity.workspaceId ||\n row.projectId !== identity.projectId ||\n row.workflowRunAttemptId !== identity.workflowRunAttemptId\n ) {\n throw new LeaseStreamMismatchError();\n }\n return {stream: toAttemptStream(row), created: row.created};\n}\n\nexport async function getOrCreateAttemptStream(\n tx: Transaction,\n identity: AttemptStreamIdentity,\n): Promise<AttemptStream> {\n const result = await getOrCreateAttemptStreamWithStatus(tx, identity);\n return result.stream;\n}\n\nexport type CasOutcome = 'extended' | 'retry' | 'gap';\n\nexport interface CasResult {\n outcome: CasOutcome;\n committedLength: number;\n}\n\n/**\n * Offset-CAS extend: advances `committed_length` by `byteLen` iff it currently\n * equals `offset`. On mismatch, re-reads the committed length and classifies the\n * caller's chunk: a retry (fully already applied) only when `offset + byteLen`\n * does not run past the committed length; otherwise a gap. A chunk that straddles\n * the committed point (offset behind it, but extending past it) is a gap too, so\n * the runner rewinds rather than getting a 200 that silently drops its tail.\n */\nexport async function casExtendCommittedLength(\n tx: Transaction,\n params: {streamId: string; offset: number; byteLen: number},\n): Promise<CasResult> {\n const extended = await tx\n .update(attemptStreams)\n .set({\n committedLength: sql`${attemptStreams.committedLength} + ${params.byteLen}`,\n updatedAt: sql`now()`,\n })\n .where(\n and(\n eq(attemptStreams.id, params.streamId),\n eq(attemptStreams.committedLength, params.offset),\n ),\n )\n .returning({committedLength: attemptStreams.committedLength});\n\n if (extended[0]) return {outcome: 'extended', committedLength: extended[0].committedLength};\n\n const [current] = await tx\n .select({committedLength: attemptStreams.committedLength})\n .from(attemptStreams)\n .where(eq(attemptStreams.id, params.streamId));\n const committedLength = current?.committedLength ?? 0;\n\n const fullyApplied = params.offset + params.byteLen <= committedLength;\n return {outcome: fullyApplied ? 'retry' : 'gap', committedLength};\n}\n\nexport async function setDeclaredTotalBytes(\n tx: Transaction,\n params: {streamId: string; declaredTotalBytes: number},\n): Promise<void> {\n await tx\n .update(attemptStreams)\n .set({declaredTotalBytes: params.declaredTotalBytes, updatedAt: sql`now()`})\n .where(eq(attemptStreams.id, params.streamId));\n}\n\n/**\n * Guarded close: flips `open → closed` and stamps `closed_at` iff the stream is\n * still open, returning the closed row (or null when another path already closed\n * it). The `WHERE state='open'` predicate is both the row lock and the\n * exactly-once gate. `committed_length` is left untouched; it stays equal to the\n * runner spool bytes. `truncated` is set only on the timeout path.\n */\nexport async function markStreamClosed(\n tx: Transaction,\n params: {streamId: string; reason: StreamCloseReason; markTruncated: boolean},\n): Promise<AttemptStream | null> {\n const [row] = await tx\n .update(attemptStreams)\n .set({\n state: 'closed',\n closeReason: params.reason,\n closedAt: sql`now()`,\n updatedAt: sql`now()`,\n ...(params.markTruncated ? {truncated: true} : {}),\n })\n .where(and(eq(attemptStreams.id, params.streamId), eq(attemptStreams.state, 'open')))\n .returning();\n\n return row ? toAttemptStream(row) : null;\n}\n\n/** Open streams of a job, for the timeout sweep to force-close after the grace period. */\nexport async function listOpenStreamsByJob(jobId: string): Promise<AttemptStream[]> {\n const rows = await db()\n .select()\n .from(attemptStreams)\n .where(and(eq(attemptStreams.jobId, jobId), eq(attemptStreams.state, 'open')));\n\n return rows.map(toAttemptStream);\n}\n\n/** For compaction, which is driven by the closed event that carries the stream id. */\nexport async function getAttemptStreamById(streamId: string): Promise<AttemptStream | null> {\n const [row] = await db().select().from(attemptStreams).where(eq(attemptStreams.id, streamId));\n return row ? toAttemptStream(row) : null;\n}\n\nexport async function getAttemptStreamByIdInTransaction(\n tx: Transaction,\n streamId: string,\n): Promise<AttemptStream | null> {\n const [row] = await tx.select().from(attemptStreams).where(eq(attemptStreams.id, streamId));\n return row ? toAttemptStream(row) : null;\n}\n\n/**\n * Final compaction step: records the object key and deletes the now-cold chunk rows in one\n * transaction. The guard is `state='closed' AND object_key IS NULL`, so the publish is a\n * single winner even when two compaction attempts (e.g. a heartbeat-timed-out run and its\n * retry) race: each uploads to its own key, and only the first to land here writes a key and\n * drops the chunks. A 0-row result means the row was either hard-deleted by retention or\n * already published by another attempt; the caller re-reads to tell those apart and deletes\n * its own now-orphaned upload either way. Returns whether this attempt won the publish.\n */\nexport async function setObjectKeyAndDeleteChunks(\n tx: Transaction,\n params: {streamId: string; objectKey: string},\n): Promise<{updated: boolean}> {\n const updated = await tx\n .update(attemptStreams)\n .set({objectKey: params.objectKey, updatedAt: sql`now()`})\n .where(\n and(\n eq(attemptStreams.id, params.streamId),\n eq(attemptStreams.state, 'closed'),\n isNull(attemptStreams.objectKey),\n ),\n )\n .returning({id: attemptStreams.id});\n\n if (updated.length === 0) return {updated: false};\n\n await tx.delete(logChunks).where(eq(logChunks.streamId, params.streamId));\n return {updated: true};\n}\n\n/**\n * Closed streams that never got an object key and have sat that way past the stale\n * window: the reconcile cron re-drives compaction for these. Hits the partial index\n * `logs_attempt_streams_uncompacted_idx`; ordered by `closed_at` so the oldest backlog\n * drains first, bounded by `limit` per tick.\n */\nexport async function listStaleUncompactedStreams(params: {\n olderThanSeconds: number;\n limit: number;\n}): Promise<AttemptStream[]> {\n const rows = await db()\n .select()\n .from(attemptStreams)\n .where(\n and(\n eq(attemptStreams.state, 'closed'),\n isNull(attemptStreams.objectKey),\n lt(attemptStreams.closedAt, sql`now() - make_interval(secs => ${params.olderThanSeconds})`),\n ),\n )\n .orderBy(asc(attemptStreams.closedAt))\n .limit(params.limit);\n\n return rows.map(toAttemptStream);\n}\n\n/**\n * Lists open streams old enough to force-close. The age check is safe only when\n * `olderThanSeconds` exceeds the lease TTL: leases are minted before a stream can\n * be created, so no valid append can arrive after `created_at + leaseTTL`.\n */\nexport async function listStaleOpenStreams(params: {\n olderThanSeconds: number;\n limit: number;\n}): Promise<AttemptStream[]> {\n const rows = await db()\n .select()\n .from(attemptStreams)\n .where(\n and(\n eq(attemptStreams.state, 'open'),\n lt(\n attemptStreams.createdAt,\n sql`now() - make_interval(secs => ${params.olderThanSeconds})`,\n ),\n ),\n )\n .orderBy(asc(attemptStreams.createdAt))\n .limit(params.limit);\n\n return rows.map(toAttemptStream);\n}\n\nexport async function getOpenStreamCount(): Promise<bigint> {\n const [row] = await db()\n .select({value: sql<bigint>`count(*)`.mapWith(BigInt)})\n .from(attemptStreams)\n .where(eq(attemptStreams.state, 'open'));\n\n return row?.value ?? 0n;\n}\n\n/**\n * Lists expired closed streams for retention. `excludeIds` keeps failed or raced rows from\n * starving younger rows in the same run, and avoids cursoring on `closed_at`, whose microsecond\n * precision would be lost through JS `Date`.\n *\n * No `object_key` filter: retention deletes compacted and never-compacted streams alike.\n */\nexport async function listExpiredClosedStreams(params: {\n retentionDays: number;\n limit: number;\n excludeIds?: string[] | undefined;\n}): Promise<AttemptStream[]> {\n const rows = await db()\n .select()\n .from(attemptStreams)\n .where(\n and(\n eq(attemptStreams.state, 'closed'),\n lt(attemptStreams.closedAt, sql`now() - make_interval(days => ${params.retentionDays})`),\n params.excludeIds && params.excludeIds.length > 0\n ? notInArray(attemptStreams.id, params.excludeIds)\n : undefined,\n ),\n )\n .orderBy(asc(attemptStreams.closedAt), asc(attemptStreams.id))\n .limit(params.limit)\n .for('update', {skipLocked: true});\n\n return rows.map(toAttemptStream);\n}\n\n/**\n * Hard-deletes one expired stream row, guarded on the observed `object_key` so a racing\n * compaction publish leaves the row for the next sweep.\n */\nexport async function deleteExpiredStream(\n tx: Transaction,\n params: {streamId: string; observedObjectKey: string | null},\n): Promise<{deleted: boolean; jobId: string | null}> {\n const [row] = await tx\n .delete(attemptStreams)\n .where(\n and(\n eq(attemptStreams.id, params.streamId),\n params.observedObjectKey === null\n ? isNull(attemptStreams.objectKey)\n : eq(attemptStreams.objectKey, params.observedObjectKey),\n ),\n )\n .returning({jobId: attemptStreams.jobId});\n\n return {deleted: Boolean(row), jobId: row?.jobId ?? null};\n}\n\nexport async function accountingHasNoStreams(tx: Transaction, jobId: string): Promise<boolean> {\n const [row] = await tx\n .select({id: attemptStreams.id})\n .from(attemptStreams)\n .where(eq(attemptStreams.jobId, jobId))\n .limit(1);\n\n return !row;\n}\n"],"names":["and","asc","eq","getTableColumns","isNull","lt","notInArray","sql","LeaseStreamMismatchError","db","attemptStreams","toAttemptStream","logChunks","getAttemptStream","tx","lookup","row","select","from","where","jobId","stepId","attempt","getStreamByStepAttempt","getOrCreateAttemptStreamWithStatus","identity","insert","values","onConflictDoUpdate","target","set","updatedAt","returning","created","Error","workspaceId","projectId","workflowRunAttemptId","stream","getOrCreateAttemptStream","result","casExtendCommittedLength","params","extended","update","committedLength","byteLen","id","streamId","offset","outcome","current","fullyApplied","setDeclaredTotalBytes","declaredTotalBytes","markStreamClosed","state","closeReason","reason","closedAt","markTruncated","truncated","listOpenStreamsByJob","rows","map","getAttemptStreamById","getAttemptStreamByIdInTransaction","setObjectKeyAndDeleteChunks","updated","objectKey","length","delete","listStaleUncompactedStreams","olderThanSeconds","orderBy","limit","listStaleOpenStreams","createdAt","getOpenStreamCount","value","mapWith","BigInt","listExpiredClosedStreams","retentionDays","excludeIds","undefined","for","skipLocked","deleteExpiredStream","observedObjectKey","deleted","Boolean","accountingHasNoStreams"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAC,KAAK,aAAa,EAAoB,MAAM,sBAAsB,CAAC;AAY3E,OAAO,EAAC,oBAAoB,EAAC,MAAM,wBAAwB,CAAC;AAO5D,eAAO,MAAM,UAAU,EAAE,aAiDxB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { dirname, resolve } from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { LOG_STREAM_CLOSED, logsEventSchemas } from '@shipfox/api-logs-dto';
|
|
4
|
+
import { WORKFLOWS_JOB_TERMINATED, WORKFLOWS_STEP_ATTEMPT_TERMINATED } from '@shipfox/api-workflows-dto';
|
|
5
|
+
import { subscriberFactory } from '@shipfox/node-module';
|
|
6
|
+
import { db } from '#db/db.js';
|
|
7
|
+
import { migrationsPath } from '#db/index.js';
|
|
8
|
+
import { logsOutbox } from '#db/schema/outbox.js';
|
|
9
|
+
import { registerLogsServiceMetrics } from '#metrics/service.js';
|
|
10
|
+
import { logsRoutes } from '#presentation/routes/index.js';
|
|
11
|
+
import { onJobTerminated } from '#presentation/subscribers/on-job-terminated.js';
|
|
12
|
+
import { onLogStreamClosed } from '#presentation/subscribers/on-log-stream-closed.js';
|
|
13
|
+
import { onStepAttemptTerminated } from '#presentation/subscribers/on-step-attempt-terminated.js';
|
|
14
|
+
import { createLogsActivities } from '#temporal/activities/index.js';
|
|
15
|
+
import { LOGS_COMPACTION_TASK_QUEUE, LOGS_LIFECYCLE_TASK_QUEUE } from '#temporal/constants.js';
|
|
16
|
+
export { checkBucketReachable } from '#api/object-storage.js';
|
|
17
|
+
const packageRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
18
|
+
const workflowsPath = resolve(packageRoot, 'dist/temporal/workflows/index.js');
|
|
19
|
+
const subscriber = subscriberFactory();
|
|
20
|
+
export const logsModule = {
|
|
21
|
+
name: 'logs',
|
|
22
|
+
database: {
|
|
23
|
+
db,
|
|
24
|
+
migrationsPath
|
|
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,
|
|
34
|
+
db,
|
|
35
|
+
eventSchemas: logsEventSchemas
|
|
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
|
+
]
|
|
64
|
+
},
|
|
65
|
+
// Compaction runs on its own queue so long uploads cannot starve the lifecycle sweep.
|
|
66
|
+
{
|
|
67
|
+
taskQueue: LOGS_COMPACTION_TASK_QUEUE,
|
|
68
|
+
workflowsPath,
|
|
69
|
+
activities: createLogsActivities,
|
|
70
|
+
workflows: [
|
|
71
|
+
{
|
|
72
|
+
name: 'compactionReconcileCron',
|
|
73
|
+
id: 'logs-compaction-reconcile',
|
|
74
|
+
cronSchedule: '*/10 * * * *'
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
]
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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 {logsRoutes} 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 const logsModule: ShipfoxModule = {\n name: 'logs',\n database: {db, migrationsPath},\n routes: logsRoutes,\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"],"names":["dirname","resolve","fileURLToPath","LOG_STREAM_CLOSED","logsEventSchemas","WORKFLOWS_JOB_TERMINATED","WORKFLOWS_STEP_ATTEMPT_TERMINATED","subscriberFactory","db","migrationsPath","logsOutbox","registerLogsServiceMetrics","logsRoutes","onJobTerminated","onLogStreamClosed","onStepAttemptTerminated","createLogsActivities","LOGS_COMPACTION_TASK_QUEUE","LOGS_LIFECYCLE_TASK_QUEUE","checkBucketReachable","packageRoot","url","workflowsPath","subscriber","logsModule","name","database","routes","metrics","publishers","table","eventSchemas","subscribers","workers","taskQueue","activities","workflows","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;AACpC,SAA4BC,iBAAiB,QAAO,uBAAuB;AAC3E,SAAQC,EAAE,QAAO,YAAY;AAC7B,SAAQC,cAAc,QAAO,eAAe;AAC5C,SAAQC,UAAU,QAAO,uBAAuB;AAChD,SAAQC,0BAA0B,QAAO,sBAAsB;AAC/D,SAAQC,UAAU,QAAO,gCAAgC;AACzD,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,cAAcnB,QAAQD,QAAQE,cAAc,YAAYmB,GAAG,IAAI;AACrE,MAAMC,gBAAgBrB,QAAQmB,aAAa;AAE3C,MAAMG,aAAahB;AAEnB,OAAO,MAAMiB,aAA4B;IACvCC,MAAM;IACNC,UAAU;QAAClB;QAAIC;IAAc;IAC7BkB,QAAQf;IACRgB,SAASjB;IACT,oFAAoF;IACpF,uFAAuF;IACvFkB,YAAY;QAAC;YAACJ,MAAM;YAAQK,OAAOpB;YAAYF;YAAIuB,cAAc3B;QAAgB;KAAE;IACnF4B,aAAa;QACXT,WAAWjB,mCAAmCS;QAC9CQ,WAAWlB,0BAA0BQ;QACrCU,WAAWpB,mBAAmBW;KAC/B;IACDmB,SAAS;QACP;YACEC,WAAWhB;YACXI;YACAa,YAAYnB;YACZ,yFAAyF;YACzF,2FAA2F;YAC3FoB,WAAW;gBACT;oBACEX,MAAM;oBACNY,IAAI;oBACJC,cAAc;gBAChB;gBACA,6EAA6E;gBAC7E,iCAAiC;gBACjC;oBACEb,MAAM;oBACNY,IAAI;oBACJC,cAAc;gBAChB;aACD;QACH;QACA,sFAAsF;QACtF;YACEJ,WAAWjB;YACXK;YACAa,YAAYnB;YACZoB,WAAW;gBACT;oBACEX,MAAM;oBACNY,IAAI;oBACJC,cAAc;gBAChB;aACD;QACH;KACD;AACH,EAAE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/metrics/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,0BAA0B,EAAC,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/metrics/index.ts"],"sourcesContent":["export {registerLogsServiceMetrics} from './service.js';\n"],"names":["registerLogsServiceMetrics"],"mappings":"AAAA,SAAQA,0BAA0B,QAAO,eAAe"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { LogRecord } from '@shipfox/api-logs-dto';
|
|
2
|
+
export type LogRecordMetricKind = LogRecord['type'];
|
|
3
|
+
export declare const recordAppendedCount: import("@shipfox/node-opentelemetry").Counter<{
|
|
4
|
+
kind: LogRecordMetricKind;
|
|
5
|
+
}>;
|
|
6
|
+
export declare const streamOpenedCount: import("@shipfox/node-opentelemetry").Counter<Record<string, never>>;
|
|
7
|
+
export declare const streamClosedCount: import("@shipfox/node-opentelemetry").Counter<{
|
|
8
|
+
reason: "declared" | "timeout";
|
|
9
|
+
}>;
|
|
10
|
+
export type CompactionMetricOutcome = 'already-compacted' | 'compacted' | 'failed' | 'gone' | 'retention-raced' | 'superseded';
|
|
11
|
+
export declare const compactionCount: import("@shipfox/node-opentelemetry").Counter<{
|
|
12
|
+
outcome: CompactionMetricOutcome;
|
|
13
|
+
}>;
|
|
14
|
+
//# sourceMappingURL=instance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instance.d.ts","sourceRoot":"","sources":["../../src/metrics/instance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,uBAAuB,CAAC;AAKrD,MAAM,MAAM,mBAAmB,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;AAEpD,eAAO,MAAM,mBAAmB;UAA8B,mBAAmB;EAGhF,CAAC;AAEF,eAAO,MAAM,iBAAiB,sEAE5B,CAAC;AAEH,eAAO,MAAM,iBAAiB;YAAgC,UAAU,GAAG,SAAS;EAGnF,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAC/B,mBAAmB,GACnB,WAAW,GACX,QAAQ,GACR,MAAM,GACN,iBAAiB,GACjB,YAAY,CAAC;AAEjB,eAAO,MAAM,eAAe;aAAiC,uBAAuB;EAGnF,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { instanceMetrics } from '@shipfox/node-opentelemetry';
|
|
2
|
+
const meter = instanceMetrics.getMeter('logs');
|
|
3
|
+
export const recordAppendedCount = meter.createCounter('logs_record_appended', {
|
|
4
|
+
description: 'Log records appended to durable stream storage by record type'
|
|
5
|
+
});
|
|
6
|
+
export const streamOpenedCount = meter.createCounter('logs_stream_opened', {
|
|
7
|
+
description: 'Log streams opened by first append'
|
|
8
|
+
});
|
|
9
|
+
export const streamClosedCount = meter.createCounter('logs_stream_closed', {
|
|
10
|
+
description: 'Log streams closed by reason'
|
|
11
|
+
});
|
|
12
|
+
export const compactionCount = meter.createCounter('logs_compaction', {
|
|
13
|
+
description: 'Log stream compaction attempts by outcome'
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
//# sourceMappingURL=instance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/metrics/instance.ts"],"sourcesContent":["import type {LogRecord} from '@shipfox/api-logs-dto';\nimport {instanceMetrics} from '@shipfox/node-opentelemetry';\n\nconst meter = instanceMetrics.getMeter('logs');\n\nexport type LogRecordMetricKind = LogRecord['type'];\n\nexport const recordAppendedCount = meter.createCounter<{kind: LogRecordMetricKind}>(\n 'logs_record_appended',\n {description: 'Log records appended to durable stream storage by record type'},\n);\n\nexport const streamOpenedCount = meter.createCounter<Record<string, never>>('logs_stream_opened', {\n description: 'Log streams opened by first append',\n});\n\nexport const streamClosedCount = meter.createCounter<{reason: 'declared' | 'timeout'}>(\n 'logs_stream_closed',\n {description: 'Log streams closed by reason'},\n);\n\nexport type CompactionMetricOutcome =\n | 'already-compacted'\n | 'compacted'\n | 'failed'\n | 'gone'\n | 'retention-raced'\n | 'superseded';\n\nexport const compactionCount = meter.createCounter<{outcome: CompactionMetricOutcome}>(\n 'logs_compaction',\n {description: 'Log stream compaction attempts by outcome'},\n);\n"],"names":["instanceMetrics","meter","getMeter","recordAppendedCount","createCounter","description","streamOpenedCount","streamClosedCount","compactionCount"],"mappings":"AACA,SAAQA,eAAe,QAAO,8BAA8B;AAE5D,MAAMC,QAAQD,gBAAgBE,QAAQ,CAAC;AAIvC,OAAO,MAAMC,sBAAsBF,MAAMG,aAAa,CACpD,wBACA;IAACC,aAAa;AAA+D,GAC7E;AAEF,OAAO,MAAMC,oBAAoBL,MAAMG,aAAa,CAAwB,sBAAsB;IAChGC,aAAa;AACf,GAAG;AAEH,OAAO,MAAME,oBAAoBN,MAAMG,aAAa,CAClD,sBACA;IAACC,aAAa;AAA8B,GAC5C;AAUF,OAAO,MAAMG,kBAAkBP,MAAMG,aAAa,CAChD,mBACA;IAACC,aAAa;AAA2C,GACzD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/metrics/service.ts"],"names":[],"mappings":"AAGA,wBAAgB,0BAA0B,IAAI,IAAI,CAajD"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getServiceMetricsProvider } from '@shipfox/node-opentelemetry';
|
|
2
|
+
import { getOpenStreamCount } from '#db/streams.js';
|
|
3
|
+
export function registerLogsServiceMetrics() {
|
|
4
|
+
const meter = getServiceMetricsProvider().getMeter('logs');
|
|
5
|
+
const openStreams = meter.createObservableGauge('logs_open_streams', {
|
|
6
|
+
description: 'Log streams currently open for appends'
|
|
7
|
+
});
|
|
8
|
+
meter.addBatchObservableCallback(async (observer)=>{
|
|
9
|
+
observer.observe(openStreams, toSafeGaugeNumber(await getOpenStreamCount()));
|
|
10
|
+
}, [
|
|
11
|
+
openStreams
|
|
12
|
+
]);
|
|
13
|
+
}
|
|
14
|
+
function toSafeGaugeNumber(value) {
|
|
15
|
+
// OpenTelemetry gauges accept numbers; clamp unrepresentable DB counts rather than round them.
|
|
16
|
+
if (value <= BigInt(Number.MAX_SAFE_INTEGER)) return Number(value);
|
|
17
|
+
return Number.MAX_SAFE_INTEGER;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/metrics/service.ts"],"sourcesContent":["import {getServiceMetricsProvider} from '@shipfox/node-opentelemetry';\nimport {getOpenStreamCount} from '#db/streams.js';\n\nexport function registerLogsServiceMetrics(): void {\n const meter = getServiceMetricsProvider().getMeter('logs');\n\n const openStreams = meter.createObservableGauge('logs_open_streams', {\n description: 'Log streams currently open for appends',\n });\n\n meter.addBatchObservableCallback(\n async (observer) => {\n observer.observe(openStreams, toSafeGaugeNumber(await getOpenStreamCount()));\n },\n [openStreams],\n );\n}\n\nfunction toSafeGaugeNumber(value: bigint): number {\n // OpenTelemetry gauges accept numbers; clamp unrepresentable DB counts rather than round them.\n if (value <= BigInt(Number.MAX_SAFE_INTEGER)) return Number(value);\n return Number.MAX_SAFE_INTEGER;\n}\n"],"names":["getServiceMetricsProvider","getOpenStreamCount","registerLogsServiceMetrics","meter","getMeter","openStreams","createObservableGauge","description","addBatchObservableCallback","observer","observe","toSafeGaugeNumber","value","BigInt","Number","MAX_SAFE_INTEGER"],"mappings":"AAAA,SAAQA,yBAAyB,QAAO,8BAA8B;AACtE,SAAQC,kBAAkB,QAAO,iBAAiB;AAElD,OAAO,SAASC;IACd,MAAMC,QAAQH,4BAA4BI,QAAQ,CAAC;IAEnD,MAAMC,cAAcF,MAAMG,qBAAqB,CAAC,qBAAqB;QACnEC,aAAa;IACf;IAEAJ,MAAMK,0BAA0B,CAC9B,OAAOC;QACLA,SAASC,OAAO,CAACL,aAAaM,kBAAkB,MAAMV;IACxD,GACA;QAACI;KAAY;AAEjB;AAEA,SAASM,kBAAkBC,KAAa;IACtC,+FAA+F;IAC/F,IAAIA,SAASC,OAAOC,OAAOC,gBAAgB,GAAG,OAAOD,OAAOF;IAC5D,OAAOE,OAAOC,gBAAgB;AAChC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-logs.d.ts","sourceRoot":"","sources":["../../../src/presentation/dto/read-logs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,uBAAuB,CAAC;AAC/D,OAAO,KAAK,EAAC,aAAa,EAAC,MAAM,oBAAoB,CAAC;AAEtD,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,mBAAmB,CAoBxE"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export function toReadLogsDto(result) {
|
|
2
|
+
if (result.mode === 'presigned') {
|
|
3
|
+
return {
|
|
4
|
+
mode: 'presigned',
|
|
5
|
+
url: result.url,
|
|
6
|
+
state: result.state,
|
|
7
|
+
expires_at: result.expiresAt.toISOString(),
|
|
8
|
+
total_bytes: result.totalBytes,
|
|
9
|
+
truncated: result.truncated
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
mode: 'inline',
|
|
14
|
+
ndjson: result.ndjson,
|
|
15
|
+
next_cursor: result.nextCursor,
|
|
16
|
+
has_more: result.hasMore,
|
|
17
|
+
state: result.state,
|
|
18
|
+
truncated: result.truncated
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
//# sourceMappingURL=read-logs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/presentation/dto/read-logs.ts"],"sourcesContent":["import type {ReadLogsResponseDto} from '@shipfox/api-logs-dto';\nimport type {LogReadResult} from '#core/read-logs.js';\n\nexport function toReadLogsDto(result: LogReadResult): ReadLogsResponseDto {\n if (result.mode === 'presigned') {\n return {\n mode: 'presigned',\n url: result.url,\n state: result.state,\n expires_at: result.expiresAt.toISOString(),\n total_bytes: result.totalBytes,\n truncated: result.truncated,\n };\n }\n\n return {\n mode: 'inline',\n ndjson: result.ndjson,\n next_cursor: result.nextCursor,\n has_more: result.hasMore,\n state: result.state,\n truncated: result.truncated,\n };\n}\n"],"names":["toReadLogsDto","result","mode","url","state","expires_at","expiresAt","toISOString","total_bytes","totalBytes","truncated","ndjson","next_cursor","nextCursor","has_more","hasMore"],"mappings":"AAGA,OAAO,SAASA,cAAcC,MAAqB;IACjD,IAAIA,OAAOC,IAAI,KAAK,aAAa;QAC/B,OAAO;YACLA,MAAM;YACNC,KAAKF,OAAOE,GAAG;YACfC,OAAOH,OAAOG,KAAK;YACnBC,YAAYJ,OAAOK,SAAS,CAACC,WAAW;YACxCC,aAAaP,OAAOQ,UAAU;YAC9BC,WAAWT,OAAOS,SAAS;QAC7B;IACF;IAEA,OAAO;QACLR,MAAM;QACNS,QAAQV,OAAOU,MAAM;QACrBC,aAAaX,OAAOY,UAAU;QAC9BC,UAAUb,OAAOc,OAAO;QACxBX,OAAOH,OAAOG,KAAK;QACnBM,WAAWT,OAAOS,SAAS;IAC7B;AACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"append-logs.d.ts","sourceRoot":"","sources":["../../../src/presentation/routes/append-logs.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,eAAe,iDAqD1B,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { requireLeasedJobContext } from '@shipfox/api-auth-context';
|
|
3
|
+
import { appendLogsQuerySchema, appendLogsResponseSchema, offsetGapResponseSchema } from '@shipfox/api-logs-dto';
|
|
4
|
+
import { ClientError, defineRoute } from '@shipfox/node-fastify';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import { appendLogs } from '#core/append-logs.js';
|
|
7
|
+
import { LeaseStreamMismatchError, MalformedLogChunkError, OffsetGapError } from '#core/errors.js';
|
|
8
|
+
export const appendLogsRoute = defineRoute({
|
|
9
|
+
method: 'POST',
|
|
10
|
+
path: '/steps/:stepId/logs',
|
|
11
|
+
description: 'Append a chunk of logs for a step attempt of the leased job.',
|
|
12
|
+
schema: {
|
|
13
|
+
params: z.object({
|
|
14
|
+
stepId: z.string().uuid()
|
|
15
|
+
}),
|
|
16
|
+
querystring: appendLogsQuerySchema,
|
|
17
|
+
response: {
|
|
18
|
+
200: appendLogsResponseSchema,
|
|
19
|
+
409: offsetGapResponseSchema
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
errorHandler: (error)=>{
|
|
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
|
+
});
|
|
70
|
+
|
|
71
|
+
//# sourceMappingURL=append-logs.js.map
|
|
@@ -0,0 +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 const appendLogsRoute = 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 // The scoped lease proves membership in the dispatched step attempt; active-step\n // completion remains governed by workflow/report state, not the log append route.\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 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\n return {committed_length: result.committedLength, capped: result.capped};\n },\n});\n"],"names":["Buffer","requireLeasedJobContext","appendLogsQuerySchema","appendLogsResponseSchema","offsetGapResponseSchema","ClientError","defineRoute","z","appendLogs","LeaseStreamMismatchError","MalformedLogChunkError","OffsetGapError","appendLogsRoute","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;AAC/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,MAAMC,kBAAkBN,YAAY;IACzCO,QAAQ;IACRC,MAAM;IACNC,aAAa;IACbC,QAAQ;QACNC,QAAQV,EAAEW,MAAM,CAAC;YAACC,QAAQZ,EAAEa,MAAM,GAAGC,IAAI;QAAE;QAC3CC,aAAapB;QACbqB,UAAU;YACR,KAAKpB;YACL,KAAKC;QACP;IACF;IACAoB,cAAc,CAACC;QACb,IAAIA,iBAAiBd,gBAAgB;YACnC,MAAM,IAAIN,YAAY,kDAAkD,cAAc;gBACpFqB,QAAQ;gBACRC,SAAS;oBAACC,kBAAkBH,MAAMI,eAAe;gBAAA;YACnD;QACF;QACA,IAAIJ,iBAAiBf,wBAAwB;YAC3C,MAAM,IAAIL,YAAYoB,MAAMK,OAAO,EAAE,uBAAuB;gBAACJ,QAAQ;YAAG;QAC1E;QACA,IAAID,iBAAiBhB,0BAA0B;YAC7C,MAAM,IAAIJ,YAAYoB,MAAMK,OAAO,EAAE,yBAAyB;gBAACJ,QAAQ;YAAG;QAC5E;QACA,MAAMD;IACR;IACAM,SAAS,OAAOC;QACd,MAAMC,YAAYhC,wBAAwB+B;QAC1C,MAAM,EAACb,MAAM,EAAC,GAAGa,QAAQf,MAAM;QAC/B,MAAM,EAACiB,OAAO,EAAEC,MAAM,EAAC,GAAGH,QAAQI,KAAK;QAEvC,iFAAiF;QACjF,kFAAkF;QAClF,IAAIH,UAAUI,aAAa,KAAKlB,UAAUc,UAAUK,kBAAkB,KAAKJ,SAAS;YAClF,MAAM,IAAI7B,YAAY,2CAA2C,kBAAkB;gBACjFqB,QAAQ;YACV;QACF;QAEA,MAAMa,SAAS,MAAM/B,WAAW;YAC9BgC,OAAOP,UAAUO,KAAK;YACtBC,aAAaR,UAAUQ,WAAW;YAClCC,WAAWT,UAAUS,SAAS;YAC9BC,sBAAsBV,UAAUU,oBAAoB;YACpDxB;YACAe;YACAC;YACAS,MAAM,AAACZ,QAAQY,IAAI,IAA2B5C,OAAO6C,KAAK,CAAC;QAC7D;QAEA,OAAO;YAACjB,kBAAkBW,OAAOV,eAAe;YAAEiB,QAAQP,OAAOO,MAAM;QAAA;IACzE;AACF,GAAG"}
|
|
@@ -0,0 +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,eAAO,MAAM,UAAU,EAAE,UAAU,EAiBlC,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { AUTH_LEASED_JOB, AUTH_USER } from '@shipfox/api-auth-context';
|
|
2
|
+
import { createRawBodyPlugin } from '@shipfox/node-fastify';
|
|
3
|
+
import { config } from '#config.js';
|
|
4
|
+
import { appendLogsRoute } from './append-logs.js';
|
|
5
|
+
import { readLogsRoute } from './read-logs.js';
|
|
6
|
+
// Keep the lease-authed append in its own Fastify scope so the raw NDJSON parser does not
|
|
7
|
+
// disturb the JSON read route (or workflow routes). The body limit also bounds one-append
|
|
8
|
+
// budget overshoot. The read route is session-authed and workspace-scoped via the row.
|
|
9
|
+
export const logsRoutes = [
|
|
10
|
+
{
|
|
11
|
+
prefix: '/runs/jobs/current',
|
|
12
|
+
auth: AUTH_LEASED_JOB,
|
|
13
|
+
plugins: [
|
|
14
|
+
createRawBodyPlugin({
|
|
15
|
+
contentType: 'application/x-ndjson',
|
|
16
|
+
bodyLimit: config.LOG_APPEND_BODY_LIMIT_BYTES
|
|
17
|
+
})
|
|
18
|
+
],
|
|
19
|
+
routes: [
|
|
20
|
+
appendLogsRoute
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
prefix: '/steps',
|
|
25
|
+
auth: AUTH_USER,
|
|
26
|
+
routes: [
|
|
27
|
+
readLogsRoute
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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 {appendLogsRoute} 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 const logsRoutes: RouteGroup[] = [\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: [appendLogsRoute],\n },\n {\n prefix: '/steps',\n auth: AUTH_USER,\n routes: [readLogsRoute],\n },\n];\n"],"names":["AUTH_LEASED_JOB","AUTH_USER","createRawBodyPlugin","config","appendLogsRoute","readLogsRoute","logsRoutes","prefix","auth","plugins","contentType","bodyLimit","LOG_APPEND_BODY_LIMIT_BYTES","routes"],"mappings":"AAAA,SAAQA,eAAe,EAAEC,SAAS,QAAO,4BAA4B;AACrE,SAAQC,mBAAmB,QAAwB,wBAAwB;AAC3E,SAAQC,MAAM,QAAO,aAAa;AAClC,SAAQC,eAAe,QAAO,mBAAmB;AACjD,SAAQC,aAAa,QAAO,iBAAiB;AAE7C,0FAA0F;AAC1F,0FAA0F;AAC1F,uFAAuF;AACvF,OAAO,MAAMC,aAA2B;IACtC;QACEC,QAAQ;QACRC,MAAMR;QACNS,SAAS;YACPP,oBAAoB;gBAClBQ,aAAa;gBACbC,WAAWR,OAAOS,2BAA2B;YAC/C;SACD;QACDC,QAAQ;YAACT;SAAgB;IAC3B;IACA;QACEG,QAAQ;QACRC,MAAMP;QACNY,QAAQ;YAACR;SAAc;IACzB;CACD,CAAC"}
|