Files
baiyue-firstleg/scripts/event-outbox-operations.mjs
T

51 lines
3.1 KiB
JavaScript
Raw Normal View History

2026-09-29 18:47:37 +08:00
import { parseArgs } from 'node:util'
import { pathToFileURL } from 'node:url'
export function buildRequest(argv, env) {
const { values: args } = parseArgs({ args: argv, options: {
help: { type: 'boolean' }, service: { type: 'string' }, base: { type: 'string' },
entry: { type: 'string' }, retry: { type: 'boolean' }, 'request-id': { type: 'string' },
operator: { type: 'string' }, reason: { type: 'string' }, hash: { type: 'string' },
} })
if (args.help) return null
if (env.CHONGCHONG_ENV !== 'test') throw new Error('CHONGCHONG_ENV must be test')
if (!['firstleg', 'lastleg', 'finance'].includes(args.service)) throw new Error('Unknown producer service')
if (!/^[A-Za-z0-9-]{1,64}$/.test(args.entry ?? '')) throw new Error('Invalid entry ID')
const base = new URL(args.base)
if (!['http:', 'https:'].includes(base.protocol) || base.username || base.password || base.search || base.hash || base.pathname !== '/') {
throw new Error('Base must be an explicit service origin without credentials or path')
}
const token = env.EVENT_OPERATIONS_TOKEN
if (!token || Buffer.byteLength(token) < 32) throw new Error('EVENT_OPERATIONS_TOKEN must contain at least 32 bytes')
const url = new URL(`/internal/${args.service}/event-operations/${args.entry}${args.retry ? '/retry' : ''}`, base)
const options = { method: args.retry ? 'POST' : 'GET', redirect: 'error',
headers: { 'X-Event-Operations-Token': token, 'Content-Type': 'application/json' },
signal: AbortSignal.timeout(15000) }
if (args.retry) {
if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(args['request-id'] ?? '')) throw new Error('A stable request UUID is required')
for (const [name, limit] of [['operator', 128], ['reason', 500]]) {
if (!args[name]?.trim() || args[name].length > limit || /[\x00-\x1f\x7f-\x9f]/.test(args[name])) throw new Error(`Invalid ${name}`)
}
if (!/^sha256:[0-9a-f]{64}$/i.test(args.hash ?? '')) throw new Error('Expected payload hash is required')
options.body = JSON.stringify({ requestId: args['request-id'], operator: args.operator, reason: args.reason, expectedPayloadHash: args.hash })
}
return { url, options }
}
async function main() {
const request = buildRequest(process.argv.slice(2), process.env)
if (!request) {
console.log('Inspect: --service firstleg|lastleg|finance --base <direct-service-origin> --entry <id>\nRetry: add --retry --request-id <stable-uuid> --operator <name> --reason <reason> --hash <sha256:...>\nEnvironment: CHONGCHONG_ENV=test, EVENT_OPERATIONS_TOKEN. No automatic retry or redirects. Retry writes Outbox state and audit atomically.')
return
}
const response = await fetch(request.url, request.options)
if (!response.ok) throw new Error(`Operations endpoint returned HTTP ${response.status}`)
const result = await response.json()
if (result?.code !== '200') throw new Error(`Operations rejected: code=${String(result?.code ?? 'missing')}`)
console.log(JSON.stringify(result.data, null, 2))
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main().catch(error => { console.error(error.message); process.exitCode = 1 })
}