@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 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/core/session/pi-parser.ts"],"sourcesContent":["import type {SessionViewRow} from '@shipfox/api-logs-dto';\nimport {parseSessionEntry, sessionMessageEntrySchema} from './entry-schema.js';\nimport {toJson} from './object.js';\nimport {expandMessageEntry} from './pi/message-rows.js';\nimport {\n branchSummaryDetail,\n branchSummaryMeta,\n compactionDetail,\n compactionMeta,\n customEntryLabel,\n customEntryMeta,\n customEntryText,\n entryDetail,\n labelDetail,\n labelMeta,\n modelChangeDetail,\n sessionDetail,\n} from './pi/metadata.js';\nimport {lifecycleRow, messageRow, rawRecordRow} from './rows.js';\nimport type {AgentSessionRecord} from './session-record.js';\n\nexport type {AgentSessionRecord} from './session-record.js';\n\nexport function parsePiSessionRecord(record: AgentSessionRecord): readonly SessionViewRow[] {\n const parsed = parseSessionEntry(record.data);\n if (!parsed.ok) {\n return [\n rawRecordRow(\n record,\n parsed.reason === 'invalid-json' ? 'Malformed session entry' : 'Unsupported entry',\n ),\n ];\n }\n\n const entry = parsed.entry;\n switch (entry.type) {\n case 'message': {\n const parsedEntry = sessionMessageEntrySchema.safeParse(entry);\n if (!parsedEntry.success) {\n return [\n {kind: 'raw', timestamp: record.ts, label: 'Unsupported message entry', raw: record.data},\n ];\n }\n return expandMessageEntry(record.ts, parsedEntry.data.message);\n }\n case 'session':\n return [lifecycleRow(record.ts, 'Session started', sessionDetail(entry), 'default', false)];\n case 'session_info':\n return [lifecycleRow(record.ts, 'Session info', entryDetail(entry), 'default', false)];\n case 'thinking_level_change':\n return [\n lifecycleRow(record.ts, 'Thinking level changed', entryDetail(entry), 'default', false),\n ];\n case 'model_change':\n return [lifecycleRow(record.ts, 'Model changed', modelChangeDetail(entry), 'default', false)];\n case 'compaction':\n return [\n lifecycleRow(\n record.ts,\n 'Context compacted',\n compactionDetail(entry),\n 'default',\n false,\n compactionMeta(entry),\n ),\n ];\n case 'branch_summary':\n return [\n messageRow(\n record.ts,\n 'system',\n 'branch summary',\n branchSummaryDetail(entry) ?? toJson(entry),\n false,\n branchSummaryMeta(entry),\n ),\n ];\n case 'custom':\n return [\n messageRow(\n record.ts,\n 'custom',\n customEntryLabel('custom'),\n customEntryText(entry) ?? toJson(entry),\n false,\n customEntryMeta(entry),\n ),\n ];\n case 'custom_message':\n return [\n messageRow(\n record.ts,\n 'custom',\n customEntryLabel('custom message'),\n customEntryText(entry) ?? toJson(entry),\n false,\n customEntryMeta(entry),\n ),\n ];\n case 'label':\n return [\n lifecycleRow(record.ts, 'Label', labelDetail(entry), 'default', false, labelMeta(entry)),\n ];\n default:\n return [rawRecordRow(record, `Unknown session entry: ${entry.type}`)];\n }\n}\n"],"names":["parseSessionEntry","sessionMessageEntrySchema","toJson","expandMessageEntry","branchSummaryDetail","branchSummaryMeta","compactionDetail","compactionMeta","customEntryLabel","customEntryMeta","customEntryText","entryDetail","labelDetail","labelMeta","modelChangeDetail","sessionDetail","lifecycleRow","messageRow","rawRecordRow","parsePiSessionRecord","record","parsed","data","ok","reason","entry","type","parsedEntry","safeParse","success","kind","timestamp","ts","label","raw","message"],"mappings":"AACA,SAAQA,iBAAiB,EAAEC,yBAAyB,QAAO,oBAAoB;AAC/E,SAAQC,MAAM,QAAO,cAAc;AACnC,SAAQC,kBAAkB,QAAO,uBAAuB;AACxD,SACEC,mBAAmB,EACnBC,iBAAiB,EACjBC,gBAAgB,EAChBC,cAAc,EACdC,gBAAgB,EAChBC,eAAe,EACfC,eAAe,EACfC,WAAW,EACXC,WAAW,EACXC,SAAS,EACTC,iBAAiB,EACjBC,aAAa,QACR,mBAAmB;AAC1B,SAAQC,YAAY,EAAEC,UAAU,EAAEC,YAAY,QAAO,YAAY;AAKjE,OAAO,SAASC,qBAAqBC,MAA0B;IAC7D,MAAMC,SAASrB,kBAAkBoB,OAAOE,IAAI;IAC5C,IAAI,CAACD,OAAOE,EAAE,EAAE;QACd,OAAO;YACLL,aACEE,QACAC,OAAOG,MAAM,KAAK,iBAAiB,4BAA4B;SAElE;IACH;IAEA,MAAMC,QAAQJ,OAAOI,KAAK;IAC1B,OAAQA,MAAMC,IAAI;QAChB,KAAK;YAAW;gBACd,MAAMC,cAAc1B,0BAA0B2B,SAAS,CAACH;gBACxD,IAAI,CAACE,YAAYE,OAAO,EAAE;oBACxB,OAAO;wBACL;4BAACC,MAAM;4BAAOC,WAAWX,OAAOY,EAAE;4BAAEC,OAAO;4BAA6BC,KAAKd,OAAOE,IAAI;wBAAA;qBACzF;gBACH;gBACA,OAAOnB,mBAAmBiB,OAAOY,EAAE,EAAEL,YAAYL,IAAI,CAACa,OAAO;YAC/D;QACA,KAAK;YACH,OAAO;gBAACnB,aAAaI,OAAOY,EAAE,EAAE,mBAAmBjB,cAAcU,QAAQ,WAAW;aAAO;QAC7F,KAAK;YACH,OAAO;gBAACT,aAAaI,OAAOY,EAAE,EAAE,gBAAgBrB,YAAYc,QAAQ,WAAW;aAAO;QACxF,KAAK;YACH,OAAO;gBACLT,aAAaI,OAAOY,EAAE,EAAE,0BAA0BrB,YAAYc,QAAQ,WAAW;aAClF;QACH,KAAK;YACH,OAAO;gBAACT,aAAaI,OAAOY,EAAE,EAAE,iBAAiBlB,kBAAkBW,QAAQ,WAAW;aAAO;QAC/F,KAAK;YACH,OAAO;gBACLT,aACEI,OAAOY,EAAE,EACT,qBACA1B,iBAAiBmB,QACjB,WACA,OACAlB,eAAekB;aAElB;QACH,KAAK;YACH,OAAO;gBACLR,WACEG,OAAOY,EAAE,EACT,UACA,kBACA5B,oBAAoBqB,UAAUvB,OAAOuB,QACrC,OACApB,kBAAkBoB;aAErB;QACH,KAAK;YACH,OAAO;gBACLR,WACEG,OAAOY,EAAE,EACT,UACAxB,iBAAiB,WACjBE,gBAAgBe,UAAUvB,OAAOuB,QACjC,OACAhB,gBAAgBgB;aAEnB;QACH,KAAK;YACH,OAAO;gBACLR,WACEG,OAAOY,EAAE,EACT,UACAxB,iBAAiB,mBACjBE,gBAAgBe,UAAUvB,OAAOuB,QACjC,OACAhB,gBAAgBgB;aAEnB;QACH,KAAK;YACH,OAAO;gBACLT,aAAaI,OAAOY,EAAE,EAAE,SAASpB,YAAYa,QAAQ,WAAW,OAAOZ,UAAUY;aAClF;QACH;YACE,OAAO;gBAACP,aAAaE,QAAQ,CAAC,uBAAuB,EAAEK,MAAMC,IAAI,EAAE;aAAE;IACzE;AACF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { SessionViewLifecycleRow, SessionViewMessageRow, SessionViewRow, SessionViewRowMeta, SessionViewThinkingRow } from '@shipfox/api-logs-dto';
|
|
2
|
+
import type { AgentSessionRecord } from './session-record.js';
|
|
3
|
+
export declare function messageRow(timestamp: number, role: string, label: string, text: string, terminalFailure: boolean, meta?: readonly SessionViewRowMeta[]): SessionViewMessageRow;
|
|
4
|
+
export declare function thinkingRow(timestamp: number, text: string): SessionViewThinkingRow;
|
|
5
|
+
export declare function lifecycleRow(timestamp: number, label: string, detail: string | null, tone: SessionViewLifecycleRow['tone'], terminalFailure: boolean, meta?: readonly SessionViewRowMeta[]): SessionViewLifecycleRow;
|
|
6
|
+
export declare function rawRecordRow(record: AgentSessionRecord, label: string): SessionViewRow;
|
|
7
|
+
//# sourceMappingURL=rows.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rows.d.ts","sourceRoot":"","sources":["../../../src/core/session/rows.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,qBAAqB,EACrB,cAAc,EACd,kBAAkB,EAClB,sBAAsB,EACvB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,qBAAqB,CAAC;AAE5D,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,eAAe,EAAE,OAAO,EACxB,IAAI,GAAE,SAAS,kBAAkB,EAAO,GACvC,qBAAqB,CAEvB;AAED,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,sBAAsB,CAEnF;AAED,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,IAAI,EAAE,uBAAuB,CAAC,MAAM,CAAC,EACrC,eAAe,EAAE,OAAO,EACxB,IAAI,GAAE,SAAS,kBAAkB,EAAO,GACvC,uBAAuB,CAEzB;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,kBAAkB,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc,CAEtF"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function messageRow(timestamp, role, label, text, terminalFailure, meta = []) {
|
|
2
|
+
return {
|
|
3
|
+
kind: 'message',
|
|
4
|
+
timestamp,
|
|
5
|
+
role,
|
|
6
|
+
label,
|
|
7
|
+
meta,
|
|
8
|
+
text,
|
|
9
|
+
terminalFailure
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function thinkingRow(timestamp, text) {
|
|
13
|
+
return {
|
|
14
|
+
kind: 'thinking',
|
|
15
|
+
timestamp,
|
|
16
|
+
text
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function lifecycleRow(timestamp, label, detail, tone, terminalFailure, meta = []) {
|
|
20
|
+
return {
|
|
21
|
+
kind: 'lifecycle',
|
|
22
|
+
timestamp,
|
|
23
|
+
label,
|
|
24
|
+
detail,
|
|
25
|
+
meta,
|
|
26
|
+
tone,
|
|
27
|
+
terminalFailure
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function rawRecordRow(record, label) {
|
|
31
|
+
return {
|
|
32
|
+
kind: 'raw',
|
|
33
|
+
timestamp: record.ts,
|
|
34
|
+
label,
|
|
35
|
+
raw: record.data
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
//# sourceMappingURL=rows.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/core/session/rows.ts"],"sourcesContent":["import type {\n SessionViewLifecycleRow,\n SessionViewMessageRow,\n SessionViewRow,\n SessionViewRowMeta,\n SessionViewThinkingRow,\n} from '@shipfox/api-logs-dto';\nimport type {AgentSessionRecord} from './session-record.js';\n\nexport function messageRow(\n timestamp: number,\n role: string,\n label: string,\n text: string,\n terminalFailure: boolean,\n meta: readonly SessionViewRowMeta[] = [],\n): SessionViewMessageRow {\n return {kind: 'message', timestamp, role, label, meta, text, terminalFailure};\n}\n\nexport function thinkingRow(timestamp: number, text: string): SessionViewThinkingRow {\n return {kind: 'thinking', timestamp, text};\n}\n\nexport function lifecycleRow(\n timestamp: number,\n label: string,\n detail: string | null,\n tone: SessionViewLifecycleRow['tone'],\n terminalFailure: boolean,\n meta: readonly SessionViewRowMeta[] = [],\n): SessionViewLifecycleRow {\n return {kind: 'lifecycle', timestamp, label, detail, meta, tone, terminalFailure};\n}\n\nexport function rawRecordRow(record: AgentSessionRecord, label: string): SessionViewRow {\n return {kind: 'raw', timestamp: record.ts, label, raw: record.data};\n}\n"],"names":["messageRow","timestamp","role","label","text","terminalFailure","meta","kind","thinkingRow","lifecycleRow","detail","tone","rawRecordRow","record","ts","raw","data"],"mappings":"AASA,OAAO,SAASA,WACdC,SAAiB,EACjBC,IAAY,EACZC,KAAa,EACbC,IAAY,EACZC,eAAwB,EACxBC,OAAsC,EAAE;IAExC,OAAO;QAACC,MAAM;QAAWN;QAAWC;QAAMC;QAAOG;QAAMF;QAAMC;IAAe;AAC9E;AAEA,OAAO,SAASG,YAAYP,SAAiB,EAAEG,IAAY;IACzD,OAAO;QAACG,MAAM;QAAYN;QAAWG;IAAI;AAC3C;AAEA,OAAO,SAASK,aACdR,SAAiB,EACjBE,KAAa,EACbO,MAAqB,EACrBC,IAAqC,EACrCN,eAAwB,EACxBC,OAAsC,EAAE;IAExC,OAAO;QAACC,MAAM;QAAaN;QAAWE;QAAOO;QAAQJ;QAAMK;QAAMN;IAAe;AAClF;AAEA,OAAO,SAASO,aAAaC,MAA0B,EAAEV,KAAa;IACpE,OAAO;QAACI,MAAM;QAAON,WAAWY,OAAOC,EAAE;QAAEX;QAAOY,KAAKF,OAAOG,IAAI;IAAA;AACpE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-record.d.ts","sourceRoot":"","sources":["../../../src/core/session/session-record.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/core/session/session-record.ts"],"sourcesContent":["export interface AgentSessionRecord {\n data: string;\n ts: number;\n}\n"],"names":[],"mappings":"AAAA,WAGC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Transaction } from './db.js';
|
|
2
|
+
/** Creates the per-job accounting row on first append; a no-op once it exists. */
|
|
3
|
+
export declare function ensureJobAccounting(tx: Transaction, params: {
|
|
4
|
+
jobId: string;
|
|
5
|
+
workspaceId: string;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
export interface AccrualResult {
|
|
8
|
+
used: number;
|
|
9
|
+
startedAt: Date;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Atomically adds `delta` stored bytes to the job's accrual, returning the new
|
|
13
|
+
* total and the budget clock origin. Returns null when the job is already capped
|
|
14
|
+
* (the `capped_at IS NULL` guard matched no row), so the caller drops the append.
|
|
15
|
+
*/
|
|
16
|
+
export declare function accrueStoredBytes(tx: Transaction, params: {
|
|
17
|
+
jobId: string;
|
|
18
|
+
delta: number;
|
|
19
|
+
}): Promise<AccrualResult | null>;
|
|
20
|
+
/** Claims the cap for a job. Returns true for the single caller that wins the race. */
|
|
21
|
+
export declare function claimCap(tx: Transaction, jobId: string): Promise<boolean>;
|
|
22
|
+
export declare function isJobCapped(tx: Transaction, jobId: string): Promise<boolean>;
|
|
23
|
+
/**
|
|
24
|
+
* Prunes accounting only after the job has no recent budget activity. A live job re-touches
|
|
25
|
+
* `updated_at` on append, so this guard keeps retention from resetting its cap state.
|
|
26
|
+
*/
|
|
27
|
+
export declare function deleteJobAccounting(tx: Transaction, params: {
|
|
28
|
+
jobId: string;
|
|
29
|
+
retentionDays: number;
|
|
30
|
+
}): Promise<{
|
|
31
|
+
deleted: boolean;
|
|
32
|
+
}>;
|
|
33
|
+
//# sourceMappingURL=accounting.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"accounting.d.ts","sourceRoot":"","sources":["../../src/db/accounting.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,SAAS,CAAC;AAGzC,kFAAkF;AAClF,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,WAAW,EACf,MAAM,EAAE;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAC,GAC3C,OAAO,CAAC,IAAI,CAAC,CAKf;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAsB,iBAAiB,CACrC,EAAE,EAAE,WAAW,EACf,MAAM,EAAE;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAC,GACrC,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAW/B;AAED,uFAAuF;AACvF,wBAAsB,QAAQ,CAAC,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAQ/E;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOlF;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,WAAW,EACf,MAAM,EAAE;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAC,GAC7C,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAC,CAAC,CAY7B"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { and, eq, isNull, lt, sql } from 'drizzle-orm';
|
|
2
|
+
import { jobAccounting } from './schema/job-accounting.js';
|
|
3
|
+
/** Creates the per-job accounting row on first append; a no-op once it exists. */ export async function ensureJobAccounting(tx, params) {
|
|
4
|
+
await tx.insert(jobAccounting).values({
|
|
5
|
+
jobId: params.jobId,
|
|
6
|
+
workspaceId: params.workspaceId
|
|
7
|
+
}).onConflictDoNothing({
|
|
8
|
+
target: jobAccounting.jobId
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Atomically adds `delta` stored bytes to the job's accrual, returning the new
|
|
13
|
+
* total and the budget clock origin. Returns null when the job is already capped
|
|
14
|
+
* (the `capped_at IS NULL` guard matched no row), so the caller drops the append.
|
|
15
|
+
*/ export async function accrueStoredBytes(tx, params) {
|
|
16
|
+
const [row] = await tx.update(jobAccounting).set({
|
|
17
|
+
storedBytesUsed: sql`${jobAccounting.storedBytesUsed} + ${params.delta}`,
|
|
18
|
+
updatedAt: sql`now()`
|
|
19
|
+
}).where(and(eq(jobAccounting.jobId, params.jobId), isNull(jobAccounting.cappedAt))).returning({
|
|
20
|
+
used: jobAccounting.storedBytesUsed,
|
|
21
|
+
startedAt: jobAccounting.startedAt
|
|
22
|
+
});
|
|
23
|
+
return row ?? null;
|
|
24
|
+
}
|
|
25
|
+
/** Claims the cap for a job. Returns true for the single caller that wins the race. */ export async function claimCap(tx, jobId) {
|
|
26
|
+
const [row] = await tx.update(jobAccounting).set({
|
|
27
|
+
cappedAt: sql`now()`,
|
|
28
|
+
updatedAt: sql`now()`
|
|
29
|
+
}).where(and(eq(jobAccounting.jobId, jobId), isNull(jobAccounting.cappedAt))).returning({
|
|
30
|
+
jobId: jobAccounting.jobId
|
|
31
|
+
});
|
|
32
|
+
return Boolean(row);
|
|
33
|
+
}
|
|
34
|
+
export async function isJobCapped(tx, jobId) {
|
|
35
|
+
const [row] = await tx.select({
|
|
36
|
+
cappedAt: jobAccounting.cappedAt
|
|
37
|
+
}).from(jobAccounting).where(eq(jobAccounting.jobId, jobId));
|
|
38
|
+
return Boolean(row?.cappedAt);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Prunes accounting only after the job has no recent budget activity. A live job re-touches
|
|
42
|
+
* `updated_at` on append, so this guard keeps retention from resetting its cap state.
|
|
43
|
+
*/ export async function deleteJobAccounting(tx, params) {
|
|
44
|
+
const [row] = await tx.delete(jobAccounting).where(and(eq(jobAccounting.jobId, params.jobId), lt(jobAccounting.updatedAt, sql`now() - make_interval(days => ${params.retentionDays})`))).returning({
|
|
45
|
+
jobId: jobAccounting.jobId
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
deleted: Boolean(row)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//# sourceMappingURL=accounting.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/db/accounting.ts"],"sourcesContent":["import {and, eq, isNull, lt, sql} from 'drizzle-orm';\nimport type {Transaction} from './db.js';\nimport {jobAccounting} from './schema/job-accounting.js';\n\n/** Creates the per-job accounting row on first append; a no-op once it exists. */\nexport async function ensureJobAccounting(\n tx: Transaction,\n params: {jobId: string; workspaceId: string},\n): Promise<void> {\n await tx\n .insert(jobAccounting)\n .values({jobId: params.jobId, workspaceId: params.workspaceId})\n .onConflictDoNothing({target: jobAccounting.jobId});\n}\n\nexport interface AccrualResult {\n used: number;\n startedAt: Date;\n}\n\n/**\n * Atomically adds `delta` stored bytes to the job's accrual, returning the new\n * total and the budget clock origin. Returns null when the job is already capped\n * (the `capped_at IS NULL` guard matched no row), so the caller drops the append.\n */\nexport async function accrueStoredBytes(\n tx: Transaction,\n params: {jobId: string; delta: number},\n): Promise<AccrualResult | null> {\n const [row] = await tx\n .update(jobAccounting)\n .set({\n storedBytesUsed: sql`${jobAccounting.storedBytesUsed} + ${params.delta}`,\n updatedAt: sql`now()`,\n })\n .where(and(eq(jobAccounting.jobId, params.jobId), isNull(jobAccounting.cappedAt)))\n .returning({used: jobAccounting.storedBytesUsed, startedAt: jobAccounting.startedAt});\n\n return row ?? null;\n}\n\n/** Claims the cap for a job. Returns true for the single caller that wins the race. */\nexport async function claimCap(tx: Transaction, jobId: string): Promise<boolean> {\n const [row] = await tx\n .update(jobAccounting)\n .set({cappedAt: sql`now()`, updatedAt: sql`now()`})\n .where(and(eq(jobAccounting.jobId, jobId), isNull(jobAccounting.cappedAt)))\n .returning({jobId: jobAccounting.jobId});\n\n return Boolean(row);\n}\n\nexport async function isJobCapped(tx: Transaction, jobId: string): Promise<boolean> {\n const [row] = await tx\n .select({cappedAt: jobAccounting.cappedAt})\n .from(jobAccounting)\n .where(eq(jobAccounting.jobId, jobId));\n\n return Boolean(row?.cappedAt);\n}\n\n/**\n * Prunes accounting only after the job has no recent budget activity. A live job re-touches\n * `updated_at` on append, so this guard keeps retention from resetting its cap state.\n */\nexport async function deleteJobAccounting(\n tx: Transaction,\n params: {jobId: string; retentionDays: number},\n): Promise<{deleted: boolean}> {\n const [row] = await tx\n .delete(jobAccounting)\n .where(\n and(\n eq(jobAccounting.jobId, params.jobId),\n lt(jobAccounting.updatedAt, sql`now() - make_interval(days => ${params.retentionDays})`),\n ),\n )\n .returning({jobId: jobAccounting.jobId});\n\n return {deleted: Boolean(row)};\n}\n"],"names":["and","eq","isNull","lt","sql","jobAccounting","ensureJobAccounting","tx","params","insert","values","jobId","workspaceId","onConflictDoNothing","target","accrueStoredBytes","row","update","set","storedBytesUsed","delta","updatedAt","where","cappedAt","returning","used","startedAt","claimCap","Boolean","isJobCapped","select","from","deleteJobAccounting","delete","retentionDays","deleted"],"mappings":"AAAA,SAAQA,GAAG,EAAEC,EAAE,EAAEC,MAAM,EAAEC,EAAE,EAAEC,GAAG,QAAO,cAAc;AAErD,SAAQC,aAAa,QAAO,6BAA6B;AAEzD,gFAAgF,GAChF,OAAO,eAAeC,oBACpBC,EAAe,EACfC,MAA4C;IAE5C,MAAMD,GACHE,MAAM,CAACJ,eACPK,MAAM,CAAC;QAACC,OAAOH,OAAOG,KAAK;QAAEC,aAAaJ,OAAOI,WAAW;IAAA,GAC5DC,mBAAmB,CAAC;QAACC,QAAQT,cAAcM,KAAK;IAAA;AACrD;AAOA;;;;CAIC,GACD,OAAO,eAAeI,kBACpBR,EAAe,EACfC,MAAsC;IAEtC,MAAM,CAACQ,IAAI,GAAG,MAAMT,GACjBU,MAAM,CAACZ,eACPa,GAAG,CAAC;QACHC,iBAAiBf,GAAG,CAAC,EAAEC,cAAcc,eAAe,CAAC,GAAG,EAAEX,OAAOY,KAAK,CAAC,CAAC;QACxEC,WAAWjB,GAAG,CAAC,KAAK,CAAC;IACvB,GACCkB,KAAK,CAACtB,IAAIC,GAAGI,cAAcM,KAAK,EAAEH,OAAOG,KAAK,GAAGT,OAAOG,cAAckB,QAAQ,IAC9EC,SAAS,CAAC;QAACC,MAAMpB,cAAcc,eAAe;QAAEO,WAAWrB,cAAcqB,SAAS;IAAA;IAErF,OAAOV,OAAO;AAChB;AAEA,qFAAqF,GACrF,OAAO,eAAeW,SAASpB,EAAe,EAAEI,KAAa;IAC3D,MAAM,CAACK,IAAI,GAAG,MAAMT,GACjBU,MAAM,CAACZ,eACPa,GAAG,CAAC;QAACK,UAAUnB,GAAG,CAAC,KAAK,CAAC;QAAEiB,WAAWjB,GAAG,CAAC,KAAK,CAAC;IAAA,GAChDkB,KAAK,CAACtB,IAAIC,GAAGI,cAAcM,KAAK,EAAEA,QAAQT,OAAOG,cAAckB,QAAQ,IACvEC,SAAS,CAAC;QAACb,OAAON,cAAcM,KAAK;IAAA;IAExC,OAAOiB,QAAQZ;AACjB;AAEA,OAAO,eAAea,YAAYtB,EAAe,EAAEI,KAAa;IAC9D,MAAM,CAACK,IAAI,GAAG,MAAMT,GACjBuB,MAAM,CAAC;QAACP,UAAUlB,cAAckB,QAAQ;IAAA,GACxCQ,IAAI,CAAC1B,eACLiB,KAAK,CAACrB,GAAGI,cAAcM,KAAK,EAAEA;IAEjC,OAAOiB,QAAQZ,KAAKO;AACtB;AAEA;;;CAGC,GACD,OAAO,eAAeS,oBACpBzB,EAAe,EACfC,MAA8C;IAE9C,MAAM,CAACQ,IAAI,GAAG,MAAMT,GACjB0B,MAAM,CAAC5B,eACPiB,KAAK,CACJtB,IACEC,GAAGI,cAAcM,KAAK,EAAEH,OAAOG,KAAK,GACpCR,GAAGE,cAAcgB,SAAS,EAAEjB,GAAG,CAAC,8BAA8B,EAAEI,OAAO0B,aAAa,CAAC,CAAC,CAAC,IAG1FV,SAAS,CAAC;QAACb,OAAON,cAAcM,KAAK;IAAA;IAExC,OAAO;QAACwB,SAASP,QAAQZ;IAAI;AAC/B"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { type Transaction } from './db.js';
|
|
3
|
+
import { type ChunkOrigin } from './schema/chunks.js';
|
|
4
|
+
export interface InsertChunkParams {
|
|
5
|
+
streamId: string;
|
|
6
|
+
streamOffset: number;
|
|
7
|
+
byteLen: number;
|
|
8
|
+
data: Buffer;
|
|
9
|
+
origin: ChunkOrigin;
|
|
10
|
+
}
|
|
11
|
+
export declare function insertChunk(tx: Transaction, params: InsertChunkParams): Promise<void>;
|
|
12
|
+
export interface ChunkPageRow {
|
|
13
|
+
seq: number;
|
|
14
|
+
data: Buffer;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* One keyset page of a stream's chunk bytes in `seq` order. Compaction walks pages
|
|
18
|
+
* with `afterSeq = lastSeqOfPreviousPage`, so memory stays flat regardless of stream
|
|
19
|
+
* size, and reads run outside any transaction (no tx is pinned for the long upload).
|
|
20
|
+
* Safe against late writers because a closed stream never accepts another chunk.
|
|
21
|
+
*/
|
|
22
|
+
export declare function readChunksKeyset(params: {
|
|
23
|
+
streamId: string;
|
|
24
|
+
afterSeq: number;
|
|
25
|
+
limit: number;
|
|
26
|
+
}): Promise<ChunkPageRow[]>;
|
|
27
|
+
export interface ChunkPage {
|
|
28
|
+
/** Concatenated chunk bytes for the page, in `seq` order: ready-to-serve NDJSON. */
|
|
29
|
+
data: Buffer;
|
|
30
|
+
/** The last `seq` included; the next read passes this as `afterSeq`. */
|
|
31
|
+
nextSeq: number;
|
|
32
|
+
/** Whether at least one more chunk exists past `nextSeq`. */
|
|
33
|
+
hasMore: boolean;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* A byte-bounded page of a stream's chunk bytes in `seq` order, for the inline read path.
|
|
37
|
+
* Walking by `seq` (not the runner byte offset) is what makes server-injected control tombstones
|
|
38
|
+
* interleave with normalized runner records exactly as compaction concatenates them, so the inline
|
|
39
|
+
* NDJSON is byte-identical to the compacted object.
|
|
40
|
+
*
|
|
41
|
+
* Returns at most ~`maxBytes`, but always at least one whole chunk so the cursor advances
|
|
42
|
+
* even when a single chunk exceeds `maxBytes`. The cheap `(seq, byte_len)` scan picks the
|
|
43
|
+
* prefix first, so the heavy `data` column is only materialized for the chunks actually
|
|
44
|
+
* returned.
|
|
45
|
+
*/
|
|
46
|
+
export declare function readChunkPageBySeq(params: {
|
|
47
|
+
streamId: string;
|
|
48
|
+
afterSeq: number;
|
|
49
|
+
maxBytes: number;
|
|
50
|
+
}): Promise<ChunkPage>;
|
|
51
|
+
export interface ChunkStats {
|
|
52
|
+
count: number;
|
|
53
|
+
maxSeq: number;
|
|
54
|
+
uncompressedBytes: number;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Aggregate of a stream's chunks: row count, highest `seq`, and total stored bytes.
|
|
58
|
+
* Compaction reads this once before uploading, both to stamp object metadata and as the
|
|
59
|
+
* baseline its streamed totals are checked against before the chunk rows are deleted.
|
|
60
|
+
*/
|
|
61
|
+
export declare function chunkStats(streamId: string): Promise<ChunkStats>;
|
|
62
|
+
//# sourceMappingURL=chunks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunks.d.ts","sourceRoot":"","sources":["../../src/db/chunks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,MAAM,EAAC,MAAM,aAAa,CAAC;AAEnC,OAAO,EAAK,KAAK,WAAW,EAAC,MAAM,SAAS,CAAC;AAC7C,OAAO,EAAC,KAAK,WAAW,EAAY,MAAM,oBAAoB,CAAC;AAE/D,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;CACrB;AAED,wBAAsB,WAAW,CAAC,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAQ3F;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CAAC,MAAM,EAAE;IAC7C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAQ1B;AAED,MAAM,WAAW,SAAS;IACxB,oFAAoF;IACpF,IAAI,EAAE,MAAM,CAAC;IACb,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;IAChB,6DAA6D;IAC7D,OAAO,EAAE,OAAO,CAAC;CAClB;AAMD;;;;;;;;;;GAUG;AACH,wBAAsB,kBAAkB,CAAC,MAAM,EAAE;IAC/C,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,GAAG,OAAO,CAAC,SAAS,CAAC,CAsCrB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAetE"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { and, asc, eq, gt, lte, sql } from 'drizzle-orm';
|
|
3
|
+
import { db } from './db.js';
|
|
4
|
+
import { logChunks } from './schema/chunks.js';
|
|
5
|
+
export async function insertChunk(tx, params) {
|
|
6
|
+
await tx.insert(logChunks).values({
|
|
7
|
+
streamId: params.streamId,
|
|
8
|
+
streamOffset: params.streamOffset,
|
|
9
|
+
byteLen: params.byteLen,
|
|
10
|
+
data: params.data,
|
|
11
|
+
origin: params.origin
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* One keyset page of a stream's chunk bytes in `seq` order. Compaction walks pages
|
|
16
|
+
* with `afterSeq = lastSeqOfPreviousPage`, so memory stays flat regardless of stream
|
|
17
|
+
* size, and reads run outside any transaction (no tx is pinned for the long upload).
|
|
18
|
+
* Safe against late writers because a closed stream never accepts another chunk.
|
|
19
|
+
*/ export async function readChunksKeyset(params) {
|
|
20
|
+
const rows = await db().select({
|
|
21
|
+
seq: logChunks.seq,
|
|
22
|
+
data: logChunks.data
|
|
23
|
+
}).from(logChunks).where(and(eq(logChunks.streamId, params.streamId), gt(logChunks.seq, params.afterSeq))).orderBy(asc(logChunks.seq)).limit(params.limit);
|
|
24
|
+
return rows;
|
|
25
|
+
}
|
|
26
|
+
// Caps the per-read metadata scan so a stream with pathologically many tiny chunks
|
|
27
|
+
// can't make one read walk the whole stream; the client re-polls (hasMore) past it.
|
|
28
|
+
const CHUNK_PAGE_SCAN_CAP = 4096;
|
|
29
|
+
/**
|
|
30
|
+
* A byte-bounded page of a stream's chunk bytes in `seq` order, for the inline read path.
|
|
31
|
+
* Walking by `seq` (not the runner byte offset) is what makes server-injected control tombstones
|
|
32
|
+
* interleave with normalized runner records exactly as compaction concatenates them, so the inline
|
|
33
|
+
* NDJSON is byte-identical to the compacted object.
|
|
34
|
+
*
|
|
35
|
+
* Returns at most ~`maxBytes`, but always at least one whole chunk so the cursor advances
|
|
36
|
+
* even when a single chunk exceeds `maxBytes`. The cheap `(seq, byte_len)` scan picks the
|
|
37
|
+
* prefix first, so the heavy `data` column is only materialized for the chunks actually
|
|
38
|
+
* returned.
|
|
39
|
+
*/ export async function readChunkPageBySeq(params) {
|
|
40
|
+
const meta = await db().select({
|
|
41
|
+
seq: logChunks.seq,
|
|
42
|
+
byteLen: logChunks.byteLen
|
|
43
|
+
}).from(logChunks).where(and(eq(logChunks.streamId, params.streamId), gt(logChunks.seq, params.afterSeq))).orderBy(asc(logChunks.seq)).limit(CHUNK_PAGE_SCAN_CAP + 1);
|
|
44
|
+
if (meta.length === 0) {
|
|
45
|
+
return {
|
|
46
|
+
data: Buffer.alloc(0),
|
|
47
|
+
nextSeq: params.afterSeq,
|
|
48
|
+
hasMore: false
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
let accumulatedBytes = 0;
|
|
52
|
+
let includedCount = 0;
|
|
53
|
+
let nextSeq = params.afterSeq;
|
|
54
|
+
for (const row of meta){
|
|
55
|
+
if (includedCount > 0 && accumulatedBytes + row.byteLen > params.maxBytes) break;
|
|
56
|
+
accumulatedBytes += row.byteLen;
|
|
57
|
+
nextSeq = row.seq;
|
|
58
|
+
includedCount += 1;
|
|
59
|
+
if (includedCount >= CHUNK_PAGE_SCAN_CAP) break;
|
|
60
|
+
}
|
|
61
|
+
const hasMore = includedCount < meta.length;
|
|
62
|
+
const rows = await db().select({
|
|
63
|
+
data: logChunks.data
|
|
64
|
+
}).from(logChunks).where(and(eq(logChunks.streamId, params.streamId), gt(logChunks.seq, params.afterSeq), lte(logChunks.seq, nextSeq))).orderBy(asc(logChunks.seq));
|
|
65
|
+
return {
|
|
66
|
+
data: Buffer.concat(rows.map((row)=>row.data)),
|
|
67
|
+
nextSeq,
|
|
68
|
+
hasMore
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Aggregate of a stream's chunks: row count, highest `seq`, and total stored bytes.
|
|
73
|
+
* Compaction reads this once before uploading, both to stamp object metadata and as the
|
|
74
|
+
* baseline its streamed totals are checked against before the chunk rows are deleted.
|
|
75
|
+
*/ export async function chunkStats(streamId) {
|
|
76
|
+
const [row] = await db().select({
|
|
77
|
+
count: sql`count(*)::int`,
|
|
78
|
+
maxSeq: sql`coalesce(max(${logChunks.seq}), 0)::int`,
|
|
79
|
+
uncompressedBytes: sql`coalesce(sum(${logChunks.byteLen}), 0)::text`
|
|
80
|
+
}).from(logChunks).where(eq(logChunks.streamId, streamId));
|
|
81
|
+
return {
|
|
82
|
+
count: row?.count ?? 0,
|
|
83
|
+
maxSeq: row?.maxSeq ?? 0,
|
|
84
|
+
uncompressedBytes: Number(row?.uncompressedBytes ?? '0')
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
//# sourceMappingURL=chunks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/db/chunks.ts"],"sourcesContent":["import {Buffer} from 'node:buffer';\nimport {and, asc, eq, gt, lte, sql} from 'drizzle-orm';\nimport {db, type Transaction} from './db.js';\nimport {type ChunkOrigin, logChunks} from './schema/chunks.js';\n\nexport interface InsertChunkParams {\n streamId: string;\n streamOffset: number;\n byteLen: number;\n data: Buffer;\n origin: ChunkOrigin;\n}\n\nexport async function insertChunk(tx: Transaction, params: InsertChunkParams): Promise<void> {\n await tx.insert(logChunks).values({\n streamId: params.streamId,\n streamOffset: params.streamOffset,\n byteLen: params.byteLen,\n data: params.data,\n origin: params.origin,\n });\n}\n\nexport interface ChunkPageRow {\n seq: number;\n data: Buffer;\n}\n\n/**\n * One keyset page of a stream's chunk bytes in `seq` order. Compaction walks pages\n * with `afterSeq = lastSeqOfPreviousPage`, so memory stays flat regardless of stream\n * size, and reads run outside any transaction (no tx is pinned for the long upload).\n * Safe against late writers because a closed stream never accepts another chunk.\n */\nexport async function readChunksKeyset(params: {\n streamId: string;\n afterSeq: number;\n limit: number;\n}): Promise<ChunkPageRow[]> {\n const rows = await db()\n .select({seq: logChunks.seq, data: logChunks.data})\n .from(logChunks)\n .where(and(eq(logChunks.streamId, params.streamId), gt(logChunks.seq, params.afterSeq)))\n .orderBy(asc(logChunks.seq))\n .limit(params.limit);\n return rows;\n}\n\nexport interface ChunkPage {\n /** Concatenated chunk bytes for the page, in `seq` order: ready-to-serve NDJSON. */\n data: Buffer;\n /** The last `seq` included; the next read passes this as `afterSeq`. */\n nextSeq: number;\n /** Whether at least one more chunk exists past `nextSeq`. */\n hasMore: boolean;\n}\n\n// Caps the per-read metadata scan so a stream with pathologically many tiny chunks\n// can't make one read walk the whole stream; the client re-polls (hasMore) past it.\nconst CHUNK_PAGE_SCAN_CAP = 4096;\n\n/**\n * A byte-bounded page of a stream's chunk bytes in `seq` order, for the inline read path.\n * Walking by `seq` (not the runner byte offset) is what makes server-injected control tombstones\n * interleave with normalized runner records exactly as compaction concatenates them, so the inline\n * NDJSON is byte-identical to the compacted object.\n *\n * Returns at most ~`maxBytes`, but always at least one whole chunk so the cursor advances\n * even when a single chunk exceeds `maxBytes`. The cheap `(seq, byte_len)` scan picks the\n * prefix first, so the heavy `data` column is only materialized for the chunks actually\n * returned.\n */\nexport async function readChunkPageBySeq(params: {\n streamId: string;\n afterSeq: number;\n maxBytes: number;\n}): Promise<ChunkPage> {\n const meta = await db()\n .select({seq: logChunks.seq, byteLen: logChunks.byteLen})\n .from(logChunks)\n .where(and(eq(logChunks.streamId, params.streamId), gt(logChunks.seq, params.afterSeq)))\n .orderBy(asc(logChunks.seq))\n .limit(CHUNK_PAGE_SCAN_CAP + 1);\n\n if (meta.length === 0) {\n return {data: Buffer.alloc(0), nextSeq: params.afterSeq, hasMore: false};\n }\n\n let accumulatedBytes = 0;\n let includedCount = 0;\n let nextSeq = params.afterSeq;\n for (const row of meta) {\n if (includedCount > 0 && accumulatedBytes + row.byteLen > params.maxBytes) break;\n accumulatedBytes += row.byteLen;\n nextSeq = row.seq;\n includedCount += 1;\n if (includedCount >= CHUNK_PAGE_SCAN_CAP) break;\n }\n\n const hasMore = includedCount < meta.length;\n\n const rows = await db()\n .select({data: logChunks.data})\n .from(logChunks)\n .where(\n and(\n eq(logChunks.streamId, params.streamId),\n gt(logChunks.seq, params.afterSeq),\n lte(logChunks.seq, nextSeq),\n ),\n )\n .orderBy(asc(logChunks.seq));\n\n return {data: Buffer.concat(rows.map((row) => row.data)), nextSeq, hasMore};\n}\n\nexport interface ChunkStats {\n count: number;\n maxSeq: number;\n uncompressedBytes: number;\n}\n\n/**\n * Aggregate of a stream's chunks: row count, highest `seq`, and total stored bytes.\n * Compaction reads this once before uploading, both to stamp object metadata and as the\n * baseline its streamed totals are checked against before the chunk rows are deleted.\n */\nexport async function chunkStats(streamId: string): Promise<ChunkStats> {\n const [row] = await db()\n .select({\n count: sql<number>`count(*)::int`,\n maxSeq: sql<number>`coalesce(max(${logChunks.seq}), 0)::int`,\n uncompressedBytes: sql<string>`coalesce(sum(${logChunks.byteLen}), 0)::text`,\n })\n .from(logChunks)\n .where(eq(logChunks.streamId, streamId));\n\n return {\n count: row?.count ?? 0,\n maxSeq: row?.maxSeq ?? 0,\n uncompressedBytes: Number(row?.uncompressedBytes ?? '0'),\n };\n}\n"],"names":["Buffer","and","asc","eq","gt","lte","sql","db","logChunks","insertChunk","tx","params","insert","values","streamId","streamOffset","byteLen","data","origin","readChunksKeyset","rows","select","seq","from","where","afterSeq","orderBy","limit","CHUNK_PAGE_SCAN_CAP","readChunkPageBySeq","meta","length","alloc","nextSeq","hasMore","accumulatedBytes","includedCount","row","maxBytes","concat","map","chunkStats","count","maxSeq","uncompressedBytes","Number"],"mappings":"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"}
|