bunderstack 0.22.0 → 0.23.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/CHANGELOG.md +45 -0
- package/README.md +2 -1
- package/dist/api/catalog.d.ts +8 -0
- package/dist/api/catalog.d.ts.map +1 -0
- package/dist/api/catalog.js +56 -0
- package/dist/api/catalog.js.map +1 -0
- package/dist/api/context.d.ts +3 -0
- package/dist/api/context.d.ts.map +1 -1
- package/dist/api/context.js +2 -0
- package/dist/api/context.js.map +1 -1
- package/dist/api/registry.d.ts.map +1 -1
- package/dist/api/registry.js +1 -0
- package/dist/api/registry.js.map +1 -1
- package/dist/api/router.d.ts +3 -0
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +7 -2
- package/dist/api/router.js.map +1 -1
- package/dist/backend-internals.d.ts +11 -0
- package/dist/backend-internals.d.ts.map +1 -1
- package/dist/backend-internals.js.map +1 -1
- package/dist/backend.d.ts +2 -2
- package/dist/backend.d.ts.map +1 -1
- package/dist/backend.js +18 -5
- package/dist/backend.js.map +1 -1
- package/dist/blueprint.d.ts +21 -1
- package/dist/blueprint.d.ts.map +1 -1
- package/dist/blueprint.js +45 -17
- package/dist/blueprint.js.map +1 -1
- package/dist/env.d.ts +18 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +1 -0
- package/dist/env.js.map +1 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -2
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/jobs/worker.d.ts +12 -0
- package/dist/jobs/worker.d.ts.map +1 -1
- package/dist/jobs/worker.js +32 -1
- package/dist/jobs/worker.js.map +1 -1
- package/dist/logging.d.ts +8 -0
- package/dist/logging.d.ts.map +1 -0
- package/dist/logging.js +6 -0
- package/dist/logging.js.map +1 -0
- package/dist/manifest.d.ts +20 -0
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +68 -6
- package/dist/manifest.js.map +1 -1
- package/dist/readiness-probes.d.ts +4 -0
- package/dist/readiness-probes.d.ts.map +1 -0
- package/dist/readiness-probes.js +23 -0
- package/dist/readiness-probes.js.map +1 -0
- package/dist/readiness.d.ts +39 -0
- package/dist/readiness.d.ts.map +1 -0
- package/dist/readiness.js +72 -0
- package/dist/readiness.js.map +1 -0
- package/dist/runtime.d.ts +4 -0
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +32 -4
- package/dist/runtime.js.map +1 -1
- package/dist/testing/auth.d.ts +30 -2
- package/dist/testing/auth.d.ts.map +1 -1
- package/dist/testing/auth.js +103 -14
- package/dist/testing/auth.js.map +1 -1
- package/dist/testing/fixture.d.ts +18 -0
- package/dist/testing/fixture.d.ts.map +1 -1
- package/dist/testing/fixture.js +55 -2
- package/dist/testing/fixture.js.map +1 -1
- package/dist/testing/jobs.d.ts +9 -1
- package/dist/testing/jobs.d.ts.map +1 -1
- package/dist/testing/jobs.js +9 -0
- package/dist/testing/jobs.js.map +1 -1
- package/dist/testing/logs.d.ts +18 -0
- package/dist/testing/logs.d.ts.map +1 -0
- package/dist/testing/logs.js +53 -0
- package/dist/testing/logs.js.map +1 -0
- package/dist/testing.d.ts +5 -4
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +1 -1
- package/dist/testing.js.map +1 -1
- package/llms.txt +20 -13
- package/package.json +1 -1
package/dist/jobs/worker.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/jobs/worker.ts"],"names":[],"mappings":"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","sourcesContent":["// src/jobs/worker.ts — the queue worker. One `tick()` is a full cycle:\n// recover expired leases → reap old succeeded rows → claim and run queue jobs.\nimport { and, eq, inArray, is, isNotNull, lt, lte, max, sql } from 'drizzle-orm'\nimport { PgDatabase } from 'drizzle-orm/pg-core'\n\nimport type { AnyDb } from '../dialect'\nimport type {\n AnyBackgroundDefinition,\n JobsDefs,\n JobsRuntimeFacade,\n TickResult,\n} from './define'\n\nimport { jobsTableFor } from '../internal-tables'\nimport { validateStandardSchema } from '../standard-schema'\nimport { parseCron } from './cron'\nimport { backoffMs, DEFAULT_RETRIES, DEFAULT_TIMEOUT_MS } from './define'\nimport { enqueueJob } from './queue'\nimport { CRON_PREFIX, floorSlot, slotsDue, SLOT_MS } from './slots'\n\nconst CLAIM_BATCH = 10\nconst SUCCEEDED_RETENTION_MS = 24 * 60 * 60 * 1000\nconst REAP_INTERVAL_MS = 60 * 60_000\n\ntype JobRow = {\n id: string\n type: string\n payloadJson: string\n attempts: number\n runAt: number\n}\n\nfunction toError(err: unknown): Error {\n return err instanceof Error ? err : new Error(String(err))\n}\n\nfunction maxAttempts(def: AnyBackgroundDefinition): number {\n return 1 + (def.retries ?? DEFAULT_RETRIES)\n}\n\n/** Resolve a stored row type back to its definition. Cron rows carry the\n * reserved prefix; queue rows use the definition key verbatim. */\nfunction definitionFor(\n defs: JobsDefs,\n type: string,\n): AnyBackgroundDefinition | undefined {\n if (type.startsWith(CRON_PREFIX)) {\n const def = defs[type.slice(CRON_PREFIX.length)]\n return def?.kind === 'cron' ? def : undefined\n }\n const def = defs[type]\n return def?.kind === 'job' ? def : undefined\n}\n\n/** Terminal queue rows release their dedupe key. */\nfunction terminalPatch() {\n return { dedupeKey: null }\n}\n\nexport function createJobRunner(deps: {\n db: AnyDb\n defs: JobsDefs\n /** Handler ctx WITHOUT `jobs`; the facade is injected via setJobsFacade. */\n ctx: Record<string, unknown>\n}) {\n const { db, defs } = deps\n const t = jobsTableFor(db)\n const ctx = { ...deps.ctx } as Record<string, unknown>\n let lastReapAt = 0\n\n /** Cron rows carry no payload — their handler input is the slot itself. */\n function resolveInput(def: AnyBackgroundDefinition, row: JobRow): unknown {\n if (def.kind === 'cron') {\n return { scheduledFor: new Date(Number(row.runAt)) }\n }\n const raw = JSON.parse(row.payloadJson)\n return def.input\n ? validateStandardSchema(def.input, raw, 'job payload')\n : undefined\n }\n\n /**\n * The watermark is the newest slot we already stored for this cron. When no\n * rows exist — a newly declared cron, or one whose rows were reaped — anchor\n * one slot before now so the current minute is eligible and nothing older is.\n */\n async function cronWatermark(type: string, now: number): Promise<number> {\n const rows = await db\n .select({ latest: max(t.runAt) })\n .from(t)\n .where(eq(t.type, type))\n const latest = rows[0]?.latest\n return latest == null ? floorSlot(now) - SLOT_MS : Number(latest)\n }\n\n /** Enqueue a row per due slot. The unique(type, dedupeKey) constraint makes\n * this safe to run concurrently in any number of processes. */\n async function materializeCronSlots(now: number) {\n for (const [name, def] of Object.entries(defs)) {\n if (def.kind !== 'cron') continue\n const type = `${CRON_PREFIX}${name}`\n const from = await cronWatermark(type, now)\n const slots = slotsDue({\n cron: parseCron(def.schedule),\n from,\n to: now,\n catchUp: def.catchUp,\n catchUpWindowMs: def.catchUpWindow,\n })\n for (const slot of slots) {\n await enqueueJob(db, defs, name, null, {\n runAt: slot,\n dedupeKey: String(slot),\n })\n }\n }\n }\n\n async function fireOnFailed(\n def: AnyBackgroundDefinition,\n input: unknown,\n error: Error,\n ) {\n if (!def.onFailed) return\n try {\n await (def.onFailed as (i: unknown, e: Error, c: unknown) => unknown)(\n input,\n error,\n ctx,\n )\n } catch (hookErr) {\n console.error('[bunderstack] onFailed hook threw:', hookErr)\n }\n }\n\n /** running rows whose lease expired → pending (or failed when exhausted). */\n async function recoverExpiredLeases(now: number) {\n const expired: (JobRow & { lastError: string | null })[] = await db\n .select({\n id: t.id,\n type: t.type,\n payloadJson: t.payloadJson,\n attempts: t.attempts,\n lastError: t.lastError,\n runAt: t.runAt,\n })\n .from(t)\n .where(\n and(\n eq(t.status, 'running'),\n isNotNull(t.lockedUntil),\n lt(t.lockedUntil, now),\n ),\n )\n for (const row of expired) {\n const def = definitionFor(defs, row.type)\n const error = new Error('lease expired (worker crashed or timed out)')\n if (!def) {\n await db\n .update(t)\n .set({\n status: 'failed',\n finishedAt: now,\n lockedUntil: null,\n lastError: `unknown job type \"${row.type}\"`,\n dedupeKey: null,\n })\n .where(eq(t.id, row.id))\n continue\n }\n if (Number(row.attempts) >= maxAttempts(def)) {\n await db\n .update(t)\n .set({\n status: 'failed',\n finishedAt: now,\n lockedUntil: null,\n lastError: error.message,\n ...terminalPatch(),\n })\n .where(eq(t.id, row.id))\n await fireOnFailed(def, resolveInput(def, row), error)\n } else {\n await db\n .update(t)\n .set({\n status: 'pending',\n lockedUntil: null,\n runAt: now + backoffMs(def, Number(row.attempts)),\n lastError: error.message,\n })\n .where(eq(t.id, row.id))\n }\n }\n }\n\n async function reapSucceeded(now: number) {\n await db\n .delete(t)\n .where(\n and(\n eq(t.status, 'succeeded'),\n lt(t.finishedAt, now - SUCCEEDED_RETENTION_MS),\n ),\n )\n }\n\n /** Atomically claim up to `limit` runnable jobs of one type. */\n async function claim(\n type: string,\n limit: number,\n now: number,\n leaseUntil: number,\n ): Promise<JobRow[]> {\n const pendingIds = db\n .select({ id: t.id })\n .from(t)\n .where(and(eq(t.type, type), eq(t.status, 'pending'), lte(t.runAt, now)))\n .orderBy(t.runAt)\n .limit(limit)\n // PG: lock the selected rows so concurrent replicas skip them. SQLite's\n // single-writer model makes the one-statement UPDATE atomic on its own.\n const sub = is(db, PgDatabase)\n ? (\n pendingIds as unknown as {\n for: (m: string, o: object) => typeof pendingIds\n }\n ).for('update', { skipLocked: true })\n : pendingIds\n const rows: JobRow[] = await db\n .update(t)\n .set({\n status: 'running',\n lockedUntil: leaseUntil,\n attempts: sql`${t.attempts} + 1`,\n })\n .where(and(inArray(t.id, sub), eq(t.status, 'pending')))\n .returning({\n id: t.id,\n type: t.type,\n payloadJson: t.payloadJson,\n attempts: t.attempts,\n runAt: t.runAt,\n })\n return rows\n }\n\n // `now` is the tick's injected clock: retry runAt math uses it so tests can\n // drive backoff deterministically. finishedAt uses the real clock (a handler\n // may run long past the tick's start).\n async function runJob(\n row: JobRow,\n def: AnyBackgroundDefinition,\n now: number,\n leaseUntil: number,\n ): Promise<'ran' | 'failed' | 'lost'> {\n let input: unknown\n try {\n input = resolveInput(def, row)\n } catch (err) {\n // Stored payload no longer parses (schema drift): retrying can't help.\n const e = toError(err)\n const updated = await db\n .update(t)\n .set({\n status: 'failed',\n finishedAt: Date.now(),\n lockedUntil: null,\n lastError: e.message,\n ...terminalPatch(),\n })\n .where(and(eq(t.id, row.id), eq(t.lockedUntil, leaseUntil)))\n .returning({ id: t.id })\n if (!updated[0]) return 'lost'\n await fireOnFailed(def, undefined, e)\n return 'failed'\n }\n try {\n await (def.handler as (i: unknown, c: unknown) => unknown)(input, ctx)\n const updated = await db\n .update(t)\n .set({\n status: 'succeeded',\n finishedAt: Date.now(),\n lockedUntil: null,\n ...terminalPatch(),\n })\n .where(and(eq(t.id, row.id), eq(t.lockedUntil, leaseUntil)))\n .returning({ id: t.id })\n if (!updated[0]) return 'lost'\n return 'ran'\n } catch (err) {\n const e = toError(err)\n if (Number(row.attempts) < maxAttempts(def)) {\n const updated = await db\n .update(t)\n .set({\n status: 'pending',\n lockedUntil: null,\n runAt: now + backoffMs(def, Number(row.attempts)),\n lastError: e.message,\n })\n .where(and(eq(t.id, row.id), eq(t.lockedUntil, leaseUntil)))\n .returning({ id: t.id })\n if (!updated[0]) return 'lost'\n } else {\n const updated = await db\n .update(t)\n .set({\n status: 'failed',\n finishedAt: Date.now(),\n lockedUntil: null,\n lastError: e.message,\n ...terminalPatch(),\n })\n .where(and(eq(t.id, row.id), eq(t.lockedUntil, leaseUntil)))\n .returning({ id: t.id })\n if (!updated[0]) return 'lost'\n await fireOnFailed(def, input, e)\n }\n return 'failed'\n }\n }\n\n async function runClaimable(now: number): Promise<TickResult> {\n const claimedWork: Array<{\n row: JobRow\n def: AnyBackgroundDefinition\n leaseUntil: number\n }> = []\n let totalClaimed = 0\n for (const [name, def] of Object.entries(defs)) {\n const type = def.kind === 'cron' ? `${CRON_PREFIX}${name}` : name\n let limit = CLAIM_BATCH\n if (def.kind === 'job' && def.concurrency !== undefined) {\n const runningRows = await db\n .select({ id: t.id })\n .from(t)\n .where(and(eq(t.type, type), eq(t.status, 'running')))\n const capacity = def.concurrency - runningRows.length\n if (capacity <= 0) continue\n limit = Math.min(limit, capacity)\n }\n const leaseUntil = now + (def.timeout ?? DEFAULT_TIMEOUT_MS)\n const claimed = await claim(type, limit, now, leaseUntil)\n totalClaimed += claimed.length\n for (const row of claimed) claimedWork.push({ row, def, leaseUntil })\n }\n // Claim the whole tick snapshot before starting any handler. Work enqueued\n // by a handler therefore belongs to the next tick, regardless of the\n // declaration order of its target job type.\n const outcomes = await Promise.all(\n claimedWork.map(({ row, def, leaseUntil }) =>\n runJob(row, def, now, leaseUntil),\n ),\n )\n let ran = 0\n let failed = 0\n for (const outcome of outcomes) {\n if (outcome === 'ran') ran++\n else if (outcome === 'failed') failed++\n }\n return { claimed: totalClaimed, ran, failed }\n }\n\n return {\n async tick(now: number = Date.now()): Promise<TickResult> {\n await materializeCronSlots(now)\n await recoverExpiredLeases(now)\n if (now - lastReapAt >= REAP_INTERVAL_MS) {\n lastReapAt = now\n await reapSucceeded(now)\n }\n return runClaimable(now)\n },\n async inspect(now: number) {\n const runnableRows = await db\n .select({ id: t.id })\n .from(t)\n .where(and(eq(t.status, 'pending'), lte(t.runAt, now)))\n const failed: Array<{\n id: string\n type: string\n attempts: number\n lastError: string | null\n }> = await db\n .select({\n id: t.id,\n type: t.type,\n attempts: t.attempts,\n lastError: t.lastError,\n })\n .from(t)\n .where(eq(t.status, 'failed'))\n return {\n runnable: runnableRows.length,\n failed: failed.map((row) => ({\n id: String(row.id),\n name: row.type.startsWith(CRON_PREFIX)\n ? row.type.slice(CRON_PREFIX.length)\n : row.type,\n attempts: Number(row.attempts),\n lastError: row.lastError,\n })),\n }\n },\n setJobsFacade(f: JobsRuntimeFacade) {\n ctx.jobs = f\n },\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"worker.js","sourceRoot":"","sources":["../../src/jobs/worker.ts"],"names":[],"mappings":"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","sourcesContent":["// src/jobs/worker.ts — the queue worker. One `tick()` is a full cycle:\n// recover expired leases → reap old succeeded rows → claim and run queue jobs.\nimport { and, eq, inArray, is, isNotNull, lt, lte, max, sql } from 'drizzle-orm'\nimport { PgDatabase } from 'drizzle-orm/pg-core'\n\nimport type { AnyDb } from '../dialect'\nimport type { BunderstackLogger } from '../logging'\nimport type {\n AnyBackgroundDefinition,\n JobsDefs,\n JobsRuntimeFacade,\n TickResult,\n} from './define'\n\nimport { jobsTableFor } from '../internal-tables'\nimport { consoleLogger } from '../logging'\nimport { validateStandardSchema } from '../standard-schema'\nimport { parseCron } from './cron'\nimport { backoffMs, DEFAULT_RETRIES, DEFAULT_TIMEOUT_MS } from './define'\nimport { enqueueJob } from './queue'\nimport { CRON_PREFIX, floorSlot, slotsDue, SLOT_MS } from './slots'\n\nconst CLAIM_BATCH = 10\nconst SUCCEEDED_RETENTION_MS = 24 * 60 * 60 * 1000\nconst REAP_INTERVAL_MS = 60 * 60_000\n\ntype JobRow = {\n id: string\n type: string\n payloadJson: string\n attempts: number\n runAt: number\n}\n\nfunction toError(err: unknown): Error {\n return err instanceof Error ? err : new Error(String(err))\n}\n\nfunction maxAttempts(def: AnyBackgroundDefinition): number {\n return 1 + (def.retries ?? DEFAULT_RETRIES)\n}\n\n/** Resolve a stored row type back to its definition. Cron rows carry the\n * reserved prefix; queue rows use the definition key verbatim. */\nfunction definitionFor(\n defs: JobsDefs,\n type: string,\n): AnyBackgroundDefinition | undefined {\n if (type.startsWith(CRON_PREFIX)) {\n const def = defs[type.slice(CRON_PREFIX.length)]\n return def?.kind === 'cron' ? def : undefined\n }\n const def = defs[type]\n return def?.kind === 'job' ? def : undefined\n}\n\n/** Terminal queue rows release their dedupe key. */\nfunction terminalPatch() {\n return { dedupeKey: null }\n}\n\nexport function createJobRunner(deps: {\n db: AnyDb\n defs: JobsDefs\n /** Handler ctx WITHOUT `jobs`; the facade is injected via setJobsFacade. */\n ctx: Record<string, unknown>\n logger?: BunderstackLogger\n}) {\n const { db, defs } = deps\n const logger = deps.logger ?? consoleLogger\n const t = jobsTableFor(db)\n const ctx = { ...deps.ctx } as Record<string, unknown>\n let lastReapAt = 0\n\n /** Cron rows carry no payload — their handler input is the slot itself. */\n function resolveInput(def: AnyBackgroundDefinition, row: JobRow): unknown {\n if (def.kind === 'cron') {\n return { scheduledFor: new Date(Number(row.runAt)) }\n }\n const raw = JSON.parse(row.payloadJson)\n return def.input\n ? validateStandardSchema(def.input, raw, 'job payload')\n : undefined\n }\n\n /**\n * The watermark is the newest slot we already stored for this cron. When no\n * rows exist — a newly declared cron, or one whose rows were reaped — anchor\n * one slot before now so the current minute is eligible and nothing older is.\n */\n async function cronWatermark(type: string, now: number): Promise<number> {\n const rows = await db\n .select({ latest: max(t.runAt) })\n .from(t)\n .where(eq(t.type, type))\n const latest = rows[0]?.latest\n return latest == null ? floorSlot(now) - SLOT_MS : Number(latest)\n }\n\n /** Enqueue a row per due slot. The unique(type, dedupeKey) constraint makes\n * this safe to run concurrently in any number of processes. */\n async function materializeCronSlots(now: number) {\n for (const [name, def] of Object.entries(defs)) {\n if (def.kind !== 'cron') continue\n const type = `${CRON_PREFIX}${name}`\n const from = await cronWatermark(type, now)\n const slots = slotsDue({\n cron: parseCron(def.schedule),\n from,\n to: now,\n catchUp: def.catchUp,\n catchUpWindowMs: def.catchUpWindow,\n })\n for (const slot of slots) {\n await enqueueJob(db, defs, name, null, {\n runAt: slot,\n dedupeKey: String(slot),\n })\n }\n }\n }\n\n async function fireOnFailed(\n def: AnyBackgroundDefinition,\n input: unknown,\n error: Error,\n ) {\n if (!def.onFailed) return\n try {\n await (def.onFailed as (i: unknown, e: Error, c: unknown) => unknown)(\n input,\n error,\n ctx,\n )\n } catch (hookErr) {\n logger.error('[bunderstack] onFailed hook threw:', hookErr)\n }\n }\n\n /** running rows whose lease expired → pending (or failed when exhausted). */\n async function recoverExpiredLeases(now: number) {\n const expired: (JobRow & { lastError: string | null })[] = await db\n .select({\n id: t.id,\n type: t.type,\n payloadJson: t.payloadJson,\n attempts: t.attempts,\n lastError: t.lastError,\n runAt: t.runAt,\n })\n .from(t)\n .where(\n and(\n eq(t.status, 'running'),\n isNotNull(t.lockedUntil),\n lt(t.lockedUntil, now),\n ),\n )\n for (const row of expired) {\n const def = definitionFor(defs, row.type)\n const error = new Error('lease expired (worker crashed or timed out)')\n if (!def) {\n await db\n .update(t)\n .set({\n status: 'failed',\n finishedAt: now,\n lockedUntil: null,\n lastError: `unknown job type \"${row.type}\"`,\n dedupeKey: null,\n })\n .where(eq(t.id, row.id))\n continue\n }\n if (Number(row.attempts) >= maxAttempts(def)) {\n await db\n .update(t)\n .set({\n status: 'failed',\n finishedAt: now,\n lockedUntil: null,\n lastError: error.message,\n ...terminalPatch(),\n })\n .where(eq(t.id, row.id))\n await fireOnFailed(def, resolveInput(def, row), error)\n } else {\n await db\n .update(t)\n .set({\n status: 'pending',\n lockedUntil: null,\n runAt: now + backoffMs(def, Number(row.attempts)),\n lastError: error.message,\n })\n .where(eq(t.id, row.id))\n }\n }\n }\n\n async function reapSucceeded(now: number) {\n await db\n .delete(t)\n .where(\n and(\n eq(t.status, 'succeeded'),\n lt(t.finishedAt, now - SUCCEEDED_RETENTION_MS),\n ),\n )\n }\n\n /** Atomically claim up to `limit` runnable jobs of one type. */\n async function claim(\n type: string,\n limit: number,\n now: number,\n leaseUntil: number,\n ): Promise<JobRow[]> {\n const pendingIds = db\n .select({ id: t.id })\n .from(t)\n .where(and(eq(t.type, type), eq(t.status, 'pending'), lte(t.runAt, now)))\n .orderBy(t.runAt)\n .limit(limit)\n // PG: lock the selected rows so concurrent replicas skip them. SQLite's\n // single-writer model makes the one-statement UPDATE atomic on its own.\n const sub = is(db, PgDatabase)\n ? (\n pendingIds as unknown as {\n for: (m: string, o: object) => typeof pendingIds\n }\n ).for('update', { skipLocked: true })\n : pendingIds\n const rows: JobRow[] = await db\n .update(t)\n .set({\n status: 'running',\n lockedUntil: leaseUntil,\n attempts: sql`${t.attempts} + 1`,\n })\n .where(and(inArray(t.id, sub), eq(t.status, 'pending')))\n .returning({\n id: t.id,\n type: t.type,\n payloadJson: t.payloadJson,\n attempts: t.attempts,\n runAt: t.runAt,\n })\n return rows\n }\n\n // `now` is the tick's injected clock: retry runAt math uses it so tests can\n // drive backoff deterministically. finishedAt uses the real clock (a handler\n // may run long past the tick's start).\n async function runJob(\n row: JobRow,\n def: AnyBackgroundDefinition,\n now: number,\n leaseUntil: number,\n ): Promise<'ran' | 'failed' | 'lost'> {\n let input: unknown\n try {\n input = resolveInput(def, row)\n } catch (err) {\n // Stored payload no longer parses (schema drift): retrying can't help.\n const e = toError(err)\n const updated = await db\n .update(t)\n .set({\n status: 'failed',\n finishedAt: Date.now(),\n lockedUntil: null,\n lastError: e.message,\n ...terminalPatch(),\n })\n .where(and(eq(t.id, row.id), eq(t.lockedUntil, leaseUntil)))\n .returning({ id: t.id })\n if (!updated[0]) return 'lost'\n await fireOnFailed(def, undefined, e)\n return 'failed'\n }\n try {\n await (def.handler as (i: unknown, c: unknown) => unknown)(input, ctx)\n const updated = await db\n .update(t)\n .set({\n status: 'succeeded',\n finishedAt: Date.now(),\n lockedUntil: null,\n ...terminalPatch(),\n })\n .where(and(eq(t.id, row.id), eq(t.lockedUntil, leaseUntil)))\n .returning({ id: t.id })\n if (!updated[0]) return 'lost'\n return 'ran'\n } catch (err) {\n const e = toError(err)\n if (Number(row.attempts) < maxAttempts(def)) {\n const updated = await db\n .update(t)\n .set({\n status: 'pending',\n lockedUntil: null,\n runAt: now + backoffMs(def, Number(row.attempts)),\n lastError: e.message,\n })\n .where(and(eq(t.id, row.id), eq(t.lockedUntil, leaseUntil)))\n .returning({ id: t.id })\n if (!updated[0]) return 'lost'\n } else {\n const updated = await db\n .update(t)\n .set({\n status: 'failed',\n finishedAt: Date.now(),\n lockedUntil: null,\n lastError: e.message,\n ...terminalPatch(),\n })\n .where(and(eq(t.id, row.id), eq(t.lockedUntil, leaseUntil)))\n .returning({ id: t.id })\n if (!updated[0]) return 'lost'\n await fireOnFailed(def, input, e)\n }\n return 'failed'\n }\n }\n\n async function runClaimable(now: number): Promise<TickResult> {\n const claimedWork: Array<{\n row: JobRow\n def: AnyBackgroundDefinition\n leaseUntil: number\n }> = []\n let totalClaimed = 0\n for (const [name, def] of Object.entries(defs)) {\n const type = def.kind === 'cron' ? `${CRON_PREFIX}${name}` : name\n let limit = CLAIM_BATCH\n if (def.kind === 'job' && def.concurrency !== undefined) {\n const runningRows = await db\n .select({ id: t.id })\n .from(t)\n .where(and(eq(t.type, type), eq(t.status, 'running')))\n const capacity = def.concurrency - runningRows.length\n if (capacity <= 0) continue\n limit = Math.min(limit, capacity)\n }\n const leaseUntil = now + (def.timeout ?? DEFAULT_TIMEOUT_MS)\n const claimed = await claim(type, limit, now, leaseUntil)\n totalClaimed += claimed.length\n for (const row of claimed) claimedWork.push({ row, def, leaseUntil })\n }\n // Claim the whole tick snapshot before starting any handler. Work enqueued\n // by a handler therefore belongs to the next tick, regardless of the\n // declaration order of its target job type.\n const outcomes = await Promise.all(\n claimedWork.map(({ row, def, leaseUntil }) =>\n runJob(row, def, now, leaseUntil),\n ),\n )\n let ran = 0\n let failed = 0\n for (const outcome of outcomes) {\n if (outcome === 'ran') ran++\n else if (outcome === 'failed') failed++\n }\n return { claimed: totalClaimed, ran, failed }\n }\n\n return {\n async tick(now: number = Date.now()): Promise<TickResult> {\n await materializeCronSlots(now)\n await recoverExpiredLeases(now)\n if (now - lastReapAt >= REAP_INTERVAL_MS) {\n lastReapAt = now\n await reapSucceeded(now)\n }\n return runClaimable(now)\n },\n async inspect(now: number) {\n const runnableRows = await db\n .select({ id: t.id })\n .from(t)\n .where(and(eq(t.status, 'pending'), lte(t.runAt, now)))\n const failed: Array<{\n id: string\n type: string\n attempts: number\n lastError: string | null\n }> = await db\n .select({\n id: t.id,\n type: t.type,\n attempts: t.attempts,\n lastError: t.lastError,\n })\n .from(t)\n .where(eq(t.status, 'failed'))\n const rows: Array<{\n id: string\n type: string\n status: string\n attempts: number\n runAt: number\n dedupeKey: string | null\n lastError: string | null\n createdAt: number\n }> = await db\n .select({\n id: t.id,\n type: t.type,\n status: t.status,\n attempts: t.attempts,\n runAt: t.runAt,\n dedupeKey: t.dedupeKey,\n lastError: t.lastError,\n createdAt: t.createdAt,\n })\n .from(t)\n return {\n runnable: runnableRows.length,\n failed: failed.map((row) => ({\n id: String(row.id),\n name: row.type.startsWith(CRON_PREFIX)\n ? row.type.slice(CRON_PREFIX.length)\n : row.type,\n attempts: Number(row.attempts),\n lastError: row.lastError,\n })),\n jobs: rows\n .sort(\n (left, right) =>\n Number(left.createdAt) - Number(right.createdAt) ||\n String(left.id).localeCompare(String(right.id)),\n )\n .map((row) => ({\n id: String(row.id),\n name: row.type.startsWith(CRON_PREFIX)\n ? row.type.slice(CRON_PREFIX.length)\n : row.type,\n kind: row.type.startsWith(CRON_PREFIX)\n ? ('cron' as const)\n : ('job' as const),\n status: row.status as\n | 'pending'\n | 'running'\n | 'succeeded'\n | 'failed',\n attempts: Number(row.attempts),\n runAt: Number(row.runAt),\n dedupeKey: row.dedupeKey,\n lastError: row.lastError,\n })),\n }\n },\n setJobsFacade(f: JobsRuntimeFacade) {\n ctx.jobs = f\n },\n }\n}\n"]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type BunderstackLogLevel = 'info' | 'warn' | 'error';
|
|
2
|
+
export type BunderstackLogger = {
|
|
3
|
+
info(...args: unknown[]): void;
|
|
4
|
+
warn(...args: unknown[]): void;
|
|
5
|
+
error(...args: unknown[]): void;
|
|
6
|
+
};
|
|
7
|
+
export declare const consoleLogger: BunderstackLogger;
|
|
8
|
+
//# sourceMappingURL=logging.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logging.d.ts","sourceRoot":"","sources":["../src/logging.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;AAE3D,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAC9B,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;IAC9B,KAAK,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI,CAAA;CAChC,CAAA;AAED,eAAO,MAAM,aAAa,EAAE,iBAI3B,CAAA"}
|
package/dist/logging.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logging.js","sourceRoot":"","sources":["../src/logging.ts"],"names":[],"mappings":"AAQA,MAAM,CAAC,MAAM,aAAa,GAAsB;IAC9C,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACxC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACxC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;CAC3C,CAAA","sourcesContent":["export type BunderstackLogLevel = 'info' | 'warn' | 'error'\n\nexport type BunderstackLogger = {\n info(...args: unknown[]): void\n warn(...args: unknown[]): void\n error(...args: unknown[]): void\n}\n\nexport const consoleLogger: BunderstackLogger = {\n info: (...args) => console.info(...args),\n warn: (...args) => console.warn(...args),\n error: (...args) => console.error(...args),\n}\n"]}
|
package/dist/manifest.d.ts
CHANGED
|
@@ -6,6 +6,22 @@ export type ManifestEnvVar = {
|
|
|
6
6
|
key: string;
|
|
7
7
|
required: boolean;
|
|
8
8
|
scope: 'server' | 'client';
|
|
9
|
+
sensitive: boolean;
|
|
10
|
+
description?: string;
|
|
11
|
+
};
|
|
12
|
+
export type ApiOperationEffect = 'read' | 'mutation' | 'unknown';
|
|
13
|
+
/**
|
|
14
|
+
* One application-declared procedure. `effect` is derived from the declared
|
|
15
|
+
* HTTP method; `unknown` means the application declared no route, and a host
|
|
16
|
+
* must treat it as at least as dangerous as a mutation.
|
|
17
|
+
*/
|
|
18
|
+
export type ApiOperation = {
|
|
19
|
+
handle: string;
|
|
20
|
+
operationId: string;
|
|
21
|
+
effect: ApiOperationEffect;
|
|
22
|
+
method?: string;
|
|
23
|
+
path?: string;
|
|
24
|
+
summary?: string;
|
|
9
25
|
};
|
|
10
26
|
export type BunderstackManifest = {
|
|
11
27
|
version: 3;
|
|
@@ -29,6 +45,9 @@ export type BunderstackManifest = {
|
|
|
29
45
|
required: boolean;
|
|
30
46
|
};
|
|
31
47
|
environment: ManifestEnvVar[];
|
|
48
|
+
api: {
|
|
49
|
+
operations: ApiOperation[];
|
|
50
|
+
};
|
|
32
51
|
background: {
|
|
33
52
|
jobs: {
|
|
34
53
|
name: string;
|
|
@@ -55,5 +74,6 @@ export declare function buildManifest(args: {
|
|
|
55
74
|
emailProvider: string | undefined;
|
|
56
75
|
realtime: boolean;
|
|
57
76
|
jobs: JobsDefs | undefined;
|
|
77
|
+
api: ApiOperation[];
|
|
58
78
|
}): BunderstackManifest;
|
|
59
79
|
//# sourceMappingURL=manifest.d.ts.map
|
package/dist/manifest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,OAAO,CAAA;AACvD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAe/E,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,QAAQ,EAAE,OAAO,CAAA;IACjB,KAAK,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC1B,SAAS,EAAE,OAAO,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,CAAA;AAEhE;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,kBAAkB,CAAA;IAC1B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,CAAC,CAAA;IACV,QAAQ,EAAE;QACR,OAAO,EAAE,OAAO,CAAA;QAChB,mBAAmB,EAAE,MAAM,CAAA;QAC3B,MAAM,EAAE;YAAE,UAAU,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,OAAO,CAAA;SAAE,EAAE,CAAA;KACxE,CAAA;IACD,OAAO,EAAE;QACP,aAAa,EAAE,MAAM,CAAA;QACrB,OAAO,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,cAAc,CAAC,YAAY,CAAC,CAAA;SAAE,EAAE,CAAA;KACtE,CAAA;IACD,QAAQ,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAA;IAC/B,WAAW,EAAE,cAAc,EAAE,CAAA;IAC7B,GAAG,EAAE;QAAE,UAAU,EAAE,YAAY,EAAE,CAAA;KAAE,CAAA;IACnC,UAAU,EAAE;QACV,IAAI,EAAE;YAAE,IAAI,EAAE,MAAM,CAAA;SAAE,EAAE,CAAA;QACxB,IAAI,EAAE;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,KAAK,CAAA;SAAE,EAAE,CAAA;QAC3D,WAAW,EAAE;YACX,IAAI,EAAE,eAAe,CAAA;YACrB,QAAQ,EAAE,MAAM,CAAA;YAChB,QAAQ,EAAE,KAAK,CAAA;SAChB,EAAE,CAAA;KACJ,CAAA;CACF,CAAA;AAkMD,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,mBAAmB,CAuCjE;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE;IAClC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,OAAO,EAAE,OAAO,CAAA;IAChB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,OAAO,EAAE,sBAAsB,CAAA;IAC/B,SAAS,EAAE,cAAc,GAAG,SAAS,CAAA;IACrC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAA;IACjC,QAAQ,EAAE,OAAO,CAAA;IACjB,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAA;IAC1B,GAAG,EAAE,YAAY,EAAE,CAAA;CACpB,GAAG,mBAAmB,CA+FtB"}
|
package/dist/manifest.js
CHANGED
|
@@ -3,6 +3,7 @@ import * as v from 'valibot';
|
|
|
3
3
|
import { bunderstackEmailEvents, bunderstackEmails, bunderstackFiles, bunderstackIdempotency, bunderstackJobs, } from './internal-tables.js';
|
|
4
4
|
import { parseCron } from './jobs/cron.js';
|
|
5
5
|
import { StandardSchemaValidationError, validateStandardSchema, } from './standard-schema.js';
|
|
6
|
+
const MAX_ENV_DESCRIPTION = 200;
|
|
6
7
|
const nonEmpty = v.pipe(v.string(), v.minLength(1));
|
|
7
8
|
const migrationDirectory = v.pipe(nonEmpty, v.check((value) => value.startsWith('/') ||
|
|
8
9
|
(!value.includes('\\') &&
|
|
@@ -39,7 +40,19 @@ const manifestSchema = v.strictObject({
|
|
|
39
40
|
key: nonEmpty,
|
|
40
41
|
required: v.boolean(),
|
|
41
42
|
scope: v.picklist(['server', 'client']),
|
|
43
|
+
sensitive: v.boolean(),
|
|
44
|
+
description: v.optional(v.pipe(nonEmpty, v.maxLength(MAX_ENV_DESCRIPTION))),
|
|
42
45
|
})),
|
|
46
|
+
api: v.strictObject({
|
|
47
|
+
operations: v.array(v.strictObject({
|
|
48
|
+
handle: nonEmpty,
|
|
49
|
+
operationId: nonEmpty,
|
|
50
|
+
effect: v.picklist(['read', 'mutation', 'unknown']),
|
|
51
|
+
method: v.optional(nonEmpty),
|
|
52
|
+
path: v.optional(nonEmpty),
|
|
53
|
+
summary: v.optional(v.pipe(nonEmpty, v.maxLength(200))),
|
|
54
|
+
})),
|
|
55
|
+
}),
|
|
43
56
|
background: v.strictObject({
|
|
44
57
|
jobs: v.array(v.strictObject({ name: nonEmpty })),
|
|
45
58
|
cron: v.array(v.strictObject({
|
|
@@ -71,7 +84,15 @@ function describeTables(schema) {
|
|
|
71
84
|
? [{ exportName, physicalName: getTableName(value), system: false }]
|
|
72
85
|
: []), (entry) => entry.physicalName);
|
|
73
86
|
}
|
|
74
|
-
function
|
|
87
|
+
function envDescription(key, value) {
|
|
88
|
+
const description = value.trim().replace(/\s+/g, ' ');
|
|
89
|
+
if (!description)
|
|
90
|
+
throw new Error(`[bunderstack] env.meta.${key}.description must not be empty`);
|
|
91
|
+
if (description.length > MAX_ENV_DESCRIPTION)
|
|
92
|
+
throw new Error(`[bunderstack] env.meta.${key}.description must be at most ${MAX_ENV_DESCRIPTION} characters`);
|
|
93
|
+
return description;
|
|
94
|
+
}
|
|
95
|
+
function describeSection(section, scope, meta) {
|
|
75
96
|
return Object.entries(section ?? {}).map(([key, schema]) => {
|
|
76
97
|
let required = false;
|
|
77
98
|
try {
|
|
@@ -82,7 +103,20 @@ function describeSection(section, scope) {
|
|
|
82
103
|
throw error;
|
|
83
104
|
required = true;
|
|
84
105
|
}
|
|
85
|
-
|
|
106
|
+
const entry = meta?.[key];
|
|
107
|
+
const sensitive = entry?.sensitive ?? scope === 'server';
|
|
108
|
+
if (scope === 'client' && sensitive) {
|
|
109
|
+
throw new Error(`[bunderstack] env.meta.${key}.sensitive must be false: client values ship to the browser`);
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
key,
|
|
113
|
+
required,
|
|
114
|
+
scope,
|
|
115
|
+
sensitive,
|
|
116
|
+
...(entry?.description === undefined
|
|
117
|
+
? {}
|
|
118
|
+
: { description: envDescription(key, entry.description) }),
|
|
119
|
+
};
|
|
86
120
|
});
|
|
87
121
|
}
|
|
88
122
|
function systemTables() {
|
|
@@ -120,20 +154,45 @@ export function parseManifest(value) {
|
|
|
120
154
|
rejectDuplicates('database export table', manifest.database.tables.map((entry) => entry.exportName));
|
|
121
155
|
rejectDuplicates('storage bucket', manifest.storage.buckets.map((entry) => entry.name));
|
|
122
156
|
rejectDuplicates('environment key', manifest.environment.map((entry) => entry.key));
|
|
157
|
+
rejectDuplicates('api operation', manifest.api.operations.map((entry) => entry.handle));
|
|
123
158
|
rejectDuplicates('background job', manifest.background.jobs.map((entry) => entry.name));
|
|
124
159
|
rejectDuplicates('background cron', manifest.background.cron.map((entry) => entry.name));
|
|
125
160
|
rejectDuplicates('background maintenance', manifest.background.maintenance.map((entry) => entry.name));
|
|
126
161
|
return manifest;
|
|
127
162
|
}
|
|
128
163
|
export function buildManifest(args) {
|
|
164
|
+
const declaredKeys = new Set([
|
|
165
|
+
...Object.keys(args.envConfig?.server ?? {}),
|
|
166
|
+
...Object.keys(args.envConfig?.client ?? {}),
|
|
167
|
+
]);
|
|
168
|
+
for (const key of Object.keys(args.envConfig?.meta ?? {})) {
|
|
169
|
+
if (!declaredKeys.has(key))
|
|
170
|
+
throw new Error(`[bunderstack] env.meta references undeclared key "${key}"`);
|
|
171
|
+
}
|
|
129
172
|
const environment = [
|
|
130
|
-
...describeSection(args.envConfig?.server, 'server'),
|
|
131
|
-
...describeSection(args.envConfig?.client, 'client'),
|
|
173
|
+
...describeSection(args.envConfig?.server, 'server', args.envConfig?.meta),
|
|
174
|
+
...describeSection(args.envConfig?.client, 'client', args.envConfig?.meta),
|
|
132
175
|
...(args.emailProvider === 'resend'
|
|
133
|
-
? [
|
|
176
|
+
? [
|
|
177
|
+
{
|
|
178
|
+
key: 'RESEND_API_KEY',
|
|
179
|
+
required: true,
|
|
180
|
+
scope: 'server',
|
|
181
|
+
sensitive: true,
|
|
182
|
+
description: 'Resend API key used to send transactional email',
|
|
183
|
+
},
|
|
184
|
+
]
|
|
134
185
|
: []),
|
|
135
186
|
...(args.emailProvider === 'smtp'
|
|
136
|
-
? [
|
|
187
|
+
? [
|
|
188
|
+
{
|
|
189
|
+
key: 'SMTP_URL',
|
|
190
|
+
required: true,
|
|
191
|
+
scope: 'server',
|
|
192
|
+
sensitive: true,
|
|
193
|
+
description: 'SMTP connection URL used to send transactional email',
|
|
194
|
+
},
|
|
195
|
+
]
|
|
137
196
|
: []),
|
|
138
197
|
];
|
|
139
198
|
rejectDuplicates('environment key', environment.map((entry) => entry.key));
|
|
@@ -153,6 +212,9 @@ export function buildManifest(args) {
|
|
|
153
212
|
},
|
|
154
213
|
realtime: { required: args.realtime },
|
|
155
214
|
environment: sortBy(environment, (entry) => entry.key),
|
|
215
|
+
api: {
|
|
216
|
+
operations: [...args.api].sort((left, right) => left.handle.localeCompare(right.handle)),
|
|
217
|
+
},
|
|
156
218
|
background: {
|
|
157
219
|
jobs: sortBy(Object.entries(args.jobs ?? {})
|
|
158
220
|
.filter(([, def]) => def.kind === 'job')
|
package/dist/manifest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AACnD,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAO5B,OAAO,EACL,sBAAsB,EACtB,iBAAiB,EACjB,gBAAgB,EAChB,sBAAsB,EACtB,eAAe,GAChB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EACL,6BAA6B,EAC7B,sBAAsB,GACvB,MAAM,mBAAmB,CAAA;AAgC1B,MAAM,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAA;AACnD,MAAM,kBAAkB,GAAG,CAAC,CAAC,IAAI,CAC/B,QAAQ,EACR,CAAC,CAAC,KAAK,CACL,CAAC,KAAK,EAAE,EAAE,CACR,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC;IACrB,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QACpB,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC,EACnE,mFAAmF,CACpF,CACF,CAAA;AACD,MAAM,YAAY,GAAG,CAAC,CAAC,IAAI,CACzB,QAAQ,EACR,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IAChB,IAAI,CAAC;QACH,SAAS,CAAC,KAAK,CAAC,CAAA;QAChB,OAAO,IAAI,CAAA;IACb,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC,EAAE,uBAAuB,CAAC,CAC5B,CAAA;AAED,MAAM,cAAc,GAAG,CAAC,CAAC,YAAY,CAAC;IACpC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACrB,QAAQ,EAAE,CAAC,CAAC,YAAY,CAAC;QACvB,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QACrC,mBAAmB,EAAE,kBAAkB;QACvC,MAAM,EAAE,CAAC,CAAC,KAAK,CACb,CAAC,CAAC,YAAY,CAAC;YACb,UAAU,EAAE,QAAQ;YACpB,YAAY,EAAE,QAAQ;YACtB,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE;SACpB,CAAC,CACH;KACF,CAAC;IACF,OAAO,EAAE,CAAC,CAAC,YAAY,CAAC;QACtB,aAAa,EAAE,QAAQ;QACvB,OAAO,EAAE,CAAC,CAAC,KAAK,CACd,CAAC,CAAC,YAAY,CAAC;YACb,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;SAC9C,CAAC,CACH;KACF,CAAC;IACF,QAAQ,EAAE,CAAC,CAAC,YAAY,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;IACnD,WAAW,EAAE,CAAC,CAAC,KAAK,CAClB,CAAC,CAAC,YAAY,CAAC;QACb,GAAG,EAAE,QAAQ;QACb,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;QACrB,KAAK,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;KACxC,CAAC,CACH;IACD,UAAU,EAAE,CAAC,CAAC,YAAY,CAAC;QACzB,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;QACjD,IAAI,EAAE,CAAC,CAAC,KAAK,CACX,CAAC,CAAC,YAAY,CAAC;YACb,IAAI,EAAE,QAAQ;YACd,QAAQ,EAAE,YAAY;YACtB,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;SAC3B,CAAC,CACH;QACD,WAAW,EAAE,CAAC,CAAC,KAAK,CAClB,CAAC,CAAC,YAAY,CAAC;YACb,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC;YAChC,QAAQ,EAAE,YAAY;YACtB,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC;SAC3B,CAAC,CACH;KACF,CAAC;CACH,CAAC,CAAA;AAEF,SAAS,MAAM,CAAI,OAAqB,EAAE,GAAyB;IACjE,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAChF,CAAC;AAED,SAAS,gBAAgB,CAAC,UAAkB,EAAE,MAAyB;IACrE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC;YACjB,MAAM,IAAI,KAAK,CAAC,2BAA2B,UAAU,KAAK,KAAK,GAAG,CAAC,CAAA;QACrE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;IACjB,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,MAA+B;IACrD,MAAM,WAAW,GAAG,IAAI,GAAG,CACzB,YAAY,EAAE,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,CAClD,CAAA;IACD,OAAO,MAAM,CACX,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,UAAU,EAAE,KAAK,CAAC,EAAE,EAAE,CACrD,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QACpE,CAAC,CAAC,EAAE,CACP,EACD,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,YAAY,CAC9B,CAAA;AACH,CAAC;AAED,SAAS,eAAe,CACtB,OAAqD,EACrD,KAA8B;IAE9B,OAAO,MAAM,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,EAAE;QACzD,IAAI,QAAQ,GAAG,KAAK,CAAA;QACpB,IAAI,CAAC;YACH,sBAAsB,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,CAAA;QAClD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,CAAC,KAAK,YAAY,6BAA6B,CAAC;gBAAE,MAAM,KAAK,CAAA;YAClE,QAAQ,GAAG,IAAI,CAAA;QACjB,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;IACjC,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,SAAS,YAAY;IACnB,OAAO;QACL;YACE,UAAU,EAAE,qBAAqB;YACjC,YAAY,EAAE,YAAY,CAAC,sBAAsB,CAAC;YAClD,MAAM,EAAE,IAAI;SACb;QACD;YACE,UAAU,EAAE,gBAAgB;YAC5B,YAAY,EAAE,YAAY,CAAC,iBAAiB,CAAC;YAC7C,MAAM,EAAE,IAAI;SACb;QACD;YACE,UAAU,EAAE,eAAe;YAC3B,YAAY,EAAE,YAAY,CAAC,gBAAgB,CAAC;YAC5C,MAAM,EAAE,IAAI;SACb;QACD;YACE,UAAU,EAAE,qBAAqB;YACjC,YAAY,EAAE,YAAY,CAAC,sBAAsB,CAAC;YAClD,MAAM,EAAE,IAAI;SACb;QACD;YACE,UAAU,EAAE,cAAc;YAC1B,YAAY,EAAE,YAAY,CAAC,eAAe,CAAC;YAC3C,MAAM,EAAE,IAAI;SACb;KACF,CAAA;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,MAAM,QAAQ,GAAG,sBAAsB,CACrC,cAAc,EACd,KAAK,EACL,UAAU,CACY,CAAA;IACxB,gBAAgB,CACd,yBAAyB,EACzB,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,CAC5D,CAAA;IACD,gBAAgB,CACd,uBAAuB,EACvB,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,CAC1D,CAAA;IACD,gBAAgB,CACd,gBAAgB,EAChB,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CACpD,CAAA;IACD,gBAAgB,CACd,iBAAiB,EACjB,QAAQ,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAC/C,CAAA;IACD,gBAAgB,CACd,gBAAgB,EAChB,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CACpD,CAAA;IACD,gBAAgB,CACd,iBAAiB,EACjB,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CACpD,CAAA;IACD,gBAAgB,CACd,wBAAwB,EACxB,QAAQ,CAAC,UAAU,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAC3D,CAAA;IACD,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAS7B;IACC,MAAM,WAAW,GAAG;QAClB,GAAG,eAAe,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC;QACpD,GAAG,eAAe,CAAC,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC;QACpD,GAAG,CAAC,IAAI,CAAC,aAAa,KAAK,QAAQ;YACjC,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,gBAAgB,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAiB,EAAE,CAAC;YACvE,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,IAAI,CAAC,aAAa,KAAK,MAAM;YAC/B,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,UAAU,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAiB,EAAE,CAAC;YACjE,CAAC,CAAC,EAAE,CAAC;KACR,CAAA;IACD,gBAAgB,CACd,iBAAiB,EACjB,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CACtC,CAAA;IAED,OAAO,aAAa,CAAC;QACnB,OAAO,EAAE,CAAC;QACV,QAAQ,EAAE;YACR,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,mBAAmB,EAAE,IAAI,CAAC,mBAAmB;YAC7C,MAAM,EAAE,MAAM,CACZ,CAAC,GAAG,YAAY,EAAE,EAAE,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,EACnD,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,YAAY,CAC9B;SACF;QACD,OAAO,EAAE;YACP,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa;YACzC,OAAO,EAAE,MAAM,CACb,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;gBAClD,IAAI,EAAE,MAAM,CAAC,IAAI;gBACjB,UAAU,EAAE,MAAM,CAAC,UAAU;aAC9B,CAAC,CAAC,EACH,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CACxB;SACF;QACD,QAAQ,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE;QACrC,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC;QACtD,UAAU,EAAE;YACV,IAAI,EAAE,MAAM,CACV,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;iBAC5B,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,KAAK,CAAC;iBACvC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,EAC9B,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CACtB;YACD,IAAI,EAAE,MAAM,CACV,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;iBAC5B,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC;iBACxC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC;gBACrB,IAAI;gBACJ,QAAQ,EAAE,GAAG,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;gBACjD,QAAQ,EAAE,KAAc;aACzB,CAAC,CAAC,EACL,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CACtB;YACD,WAAW,EAAE;gBACX;oBACE,IAAI,EAAE,eAAe;oBACrB,QAAQ,EAAE,WAAW;oBACrB,QAAQ,EAAE,KAAc;iBACzB;aACF;SACF;KACF,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import type { StandardSchemaV1 } from '@standard-schema/spec'\n\nimport { getTableName, isTable } from 'drizzle-orm'\nimport * as v from 'valibot'\n\nimport type { Dialect } from './dialect'\nimport type { EnvConfigInput } from './env'\nimport type { JobsDefs } from './jobs/define'\nimport type { ResolvedBucket, ResolvedStorageBuckets } from './storage/buckets'\n\nimport {\n bunderstackEmailEvents,\n bunderstackEmails,\n bunderstackFiles,\n bunderstackIdempotency,\n bunderstackJobs,\n} from './internal-tables'\nimport { parseCron } from './jobs/cron'\nimport {\n StandardSchemaValidationError,\n validateStandardSchema,\n} from './standard-schema'\n\nexport type ManifestEnvVar = {\n key: string\n required: boolean\n scope: 'server' | 'client'\n}\n\nexport type BunderstackManifest = {\n version: 3\n database: {\n dialect: Dialect\n migrationsDirectory: string\n tables: { exportName: string; physicalName: string; system: boolean }[]\n }\n storage: {\n defaultBucket: string\n buckets: { name: string; visibility: ResolvedBucket['visibility'] }[]\n }\n realtime: { required: boolean }\n environment: ManifestEnvVar[]\n background: {\n jobs: { name: string }[]\n cron: { name: string; schedule: string; timezone: 'UTC' }[]\n maintenance: {\n name: 'storage-sweep'\n schedule: string\n timezone: 'UTC'\n }[]\n }\n}\n\nconst nonEmpty = v.pipe(v.string(), v.minLength(1))\nconst migrationDirectory = v.pipe(\n nonEmpty,\n v.check(\n (value) =>\n value.startsWith('/') ||\n (!value.includes('\\\\') &&\n value.split('/').every((part) => part !== '..' && part !== '')),\n 'migrationsDirectory must be an absolute path or a relative path without traversal',\n ),\n)\nconst cronSchedule = v.pipe(\n nonEmpty,\n v.check((value) => {\n try {\n parseCron(value)\n return true\n } catch {\n return false\n }\n }, 'invalid cron schedule'),\n)\n\nconst manifestSchema = v.strictObject({\n version: v.literal(3),\n database: v.strictObject({\n dialect: v.picklist(['sqlite', 'pg']),\n migrationsDirectory: migrationDirectory,\n tables: v.array(\n v.strictObject({\n exportName: nonEmpty,\n physicalName: nonEmpty,\n system: v.boolean(),\n }),\n ),\n }),\n storage: v.strictObject({\n defaultBucket: nonEmpty,\n buckets: v.array(\n v.strictObject({\n name: nonEmpty,\n visibility: v.picklist(['public', 'private']),\n }),\n ),\n }),\n realtime: v.strictObject({ required: v.boolean() }),\n environment: v.array(\n v.strictObject({\n key: nonEmpty,\n required: v.boolean(),\n scope: v.picklist(['server', 'client']),\n }),\n ),\n background: v.strictObject({\n jobs: v.array(v.strictObject({ name: nonEmpty })),\n cron: v.array(\n v.strictObject({\n name: nonEmpty,\n schedule: cronSchedule,\n timezone: v.literal('UTC'),\n }),\n ),\n maintenance: v.array(\n v.strictObject({\n name: v.literal('storage-sweep'),\n schedule: cronSchedule,\n timezone: v.literal('UTC'),\n }),\n ),\n }),\n})\n\nfunction sortBy<T>(entries: readonly T[], key: (entry: T) => string): T[] {\n return [...entries].sort((left, right) => key(left).localeCompare(key(right)))\n}\n\nfunction rejectDuplicates(collection: string, values: readonly string[]): void {\n const seen = new Set<string>()\n for (const value of values) {\n if (seen.has(value))\n throw new Error(`[bunderstack] duplicate ${collection} \"${value}\"`)\n seen.add(value)\n }\n}\n\nfunction describeTables(schema: Record<string, unknown>) {\n const systemNames = new Set<string>(\n systemTables().map((table) => table.physicalName),\n )\n return sortBy(\n Object.entries(schema).flatMap(([exportName, value]) =>\n isTable(value) && !systemNames.has(getTableName(value))\n ? [{ exportName, physicalName: getTableName(value), system: false }]\n : [],\n ),\n (entry) => entry.physicalName,\n )\n}\n\nfunction describeSection(\n section: Record<string, StandardSchemaV1> | undefined,\n scope: ManifestEnvVar['scope'],\n): ManifestEnvVar[] {\n return Object.entries(section ?? {}).map(([key, schema]) => {\n let required = false\n try {\n validateStandardSchema(schema, undefined, 'env')\n } catch (error) {\n if (!(error instanceof StandardSchemaValidationError)) throw error\n required = true\n }\n return { key, required, scope }\n })\n}\n\nfunction systemTables() {\n return [\n {\n exportName: '_system.emailEvents',\n physicalName: getTableName(bunderstackEmailEvents),\n system: true,\n },\n {\n exportName: '_system.emails',\n physicalName: getTableName(bunderstackEmails),\n system: true,\n },\n {\n exportName: '_system.files',\n physicalName: getTableName(bunderstackFiles),\n system: true,\n },\n {\n exportName: '_system.idempotency',\n physicalName: getTableName(bunderstackIdempotency),\n system: true,\n },\n {\n exportName: '_system.jobs',\n physicalName: getTableName(bunderstackJobs),\n system: true,\n },\n ]\n}\n\nexport function parseManifest(value: unknown): BunderstackManifest {\n const manifest = validateStandardSchema(\n manifestSchema,\n value,\n 'manifest',\n ) as BunderstackManifest\n rejectDuplicates(\n 'database physical table',\n manifest.database.tables.map((entry) => entry.physicalName),\n )\n rejectDuplicates(\n 'database export table',\n manifest.database.tables.map((entry) => entry.exportName),\n )\n rejectDuplicates(\n 'storage bucket',\n manifest.storage.buckets.map((entry) => entry.name),\n )\n rejectDuplicates(\n 'environment key',\n manifest.environment.map((entry) => entry.key),\n )\n rejectDuplicates(\n 'background job',\n manifest.background.jobs.map((entry) => entry.name),\n )\n rejectDuplicates(\n 'background cron',\n manifest.background.cron.map((entry) => entry.name),\n )\n rejectDuplicates(\n 'background maintenance',\n manifest.background.maintenance.map((entry) => entry.name),\n )\n return manifest\n}\n\nexport function buildManifest(args: {\n schema: Record<string, unknown>\n dialect: Dialect\n migrationsDirectory: string\n storage: ResolvedStorageBuckets\n envConfig: EnvConfigInput | undefined\n emailProvider: string | undefined\n realtime: boolean\n jobs: JobsDefs | undefined\n}): BunderstackManifest {\n const environment = [\n ...describeSection(args.envConfig?.server, 'server'),\n ...describeSection(args.envConfig?.client, 'client'),\n ...(args.emailProvider === 'resend'\n ? [{ key: 'RESEND_API_KEY', required: true, scope: 'server' as const }]\n : []),\n ...(args.emailProvider === 'smtp'\n ? [{ key: 'SMTP_URL', required: true, scope: 'server' as const }]\n : []),\n ]\n rejectDuplicates(\n 'environment key',\n environment.map((entry) => entry.key),\n )\n\n return parseManifest({\n version: 3,\n database: {\n dialect: args.dialect,\n migrationsDirectory: args.migrationsDirectory,\n tables: sortBy(\n [...systemTables(), ...describeTables(args.schema)],\n (entry) => entry.physicalName,\n ),\n },\n storage: {\n defaultBucket: args.storage.defaultBucket,\n buckets: sortBy(\n [...args.storage.buckets.values()].map((bucket) => ({\n name: bucket.name,\n visibility: bucket.visibility,\n })),\n (bucket) => bucket.name,\n ),\n },\n realtime: { required: args.realtime },\n environment: sortBy(environment, (entry) => entry.key),\n background: {\n jobs: sortBy(\n Object.entries(args.jobs ?? {})\n .filter(([, def]) => def.kind === 'job')\n .map(([name]) => ({ name })),\n (entry) => entry.name,\n ),\n cron: sortBy(\n Object.entries(args.jobs ?? {})\n .filter(([, def]) => def.kind === 'cron')\n .map(([name, def]) => ({\n name,\n schedule: def.kind === 'cron' ? def.schedule : '',\n timezone: 'UTC' as const,\n })),\n (entry) => entry.name,\n ),\n maintenance: [\n {\n name: 'storage-sweep',\n schedule: '0 4 * * *',\n timezone: 'UTC' as const,\n },\n ],\n },\n })\n}\n"]}
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../src/manifest.ts"],"names":[],"mappings":"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","sourcesContent":["import type { StandardSchemaV1 } from '@standard-schema/spec'\n\nimport { getTableName, isTable } from 'drizzle-orm'\nimport * as v from 'valibot'\n\nimport type { Dialect } from './dialect'\nimport type { EnvConfigInput, EnvVarMeta } from './env'\nimport type { JobsDefs } from './jobs/define'\nimport type { ResolvedBucket, ResolvedStorageBuckets } from './storage/buckets'\n\nimport {\n bunderstackEmailEvents,\n bunderstackEmails,\n bunderstackFiles,\n bunderstackIdempotency,\n bunderstackJobs,\n} from './internal-tables'\nimport { parseCron } from './jobs/cron'\nimport {\n StandardSchemaValidationError,\n validateStandardSchema,\n} from './standard-schema'\n\nexport type ManifestEnvVar = {\n key: string\n required: boolean\n scope: 'server' | 'client'\n sensitive: boolean\n description?: string\n}\n\nexport type ApiOperationEffect = 'read' | 'mutation' | 'unknown'\n\n/**\n * One application-declared procedure. `effect` is derived from the declared\n * HTTP method; `unknown` means the application declared no route, and a host\n * must treat it as at least as dangerous as a mutation.\n */\nexport type ApiOperation = {\n handle: string\n operationId: string\n effect: ApiOperationEffect\n method?: string\n path?: string\n summary?: string\n}\n\nexport type BunderstackManifest = {\n version: 3\n database: {\n dialect: Dialect\n migrationsDirectory: string\n tables: { exportName: string; physicalName: string; system: boolean }[]\n }\n storage: {\n defaultBucket: string\n buckets: { name: string; visibility: ResolvedBucket['visibility'] }[]\n }\n realtime: { required: boolean }\n environment: ManifestEnvVar[]\n api: { operations: ApiOperation[] }\n background: {\n jobs: { name: string }[]\n cron: { name: string; schedule: string; timezone: 'UTC' }[]\n maintenance: {\n name: 'storage-sweep'\n schedule: string\n timezone: 'UTC'\n }[]\n }\n}\n\nconst MAX_ENV_DESCRIPTION = 200\n\nconst nonEmpty = v.pipe(v.string(), v.minLength(1))\nconst migrationDirectory = v.pipe(\n nonEmpty,\n v.check(\n (value) =>\n value.startsWith('/') ||\n (!value.includes('\\\\') &&\n value.split('/').every((part) => part !== '..' && part !== '')),\n 'migrationsDirectory must be an absolute path or a relative path without traversal',\n ),\n)\nconst cronSchedule = v.pipe(\n nonEmpty,\n v.check((value) => {\n try {\n parseCron(value)\n return true\n } catch {\n return false\n }\n }, 'invalid cron schedule'),\n)\n\nconst manifestSchema = v.strictObject({\n version: v.literal(3),\n database: v.strictObject({\n dialect: v.picklist(['sqlite', 'pg']),\n migrationsDirectory: migrationDirectory,\n tables: v.array(\n v.strictObject({\n exportName: nonEmpty,\n physicalName: nonEmpty,\n system: v.boolean(),\n }),\n ),\n }),\n storage: v.strictObject({\n defaultBucket: nonEmpty,\n buckets: v.array(\n v.strictObject({\n name: nonEmpty,\n visibility: v.picklist(['public', 'private']),\n }),\n ),\n }),\n realtime: v.strictObject({ required: v.boolean() }),\n environment: v.array(\n v.strictObject({\n key: nonEmpty,\n required: v.boolean(),\n scope: v.picklist(['server', 'client']),\n sensitive: v.boolean(),\n description: v.optional(\n v.pipe(nonEmpty, v.maxLength(MAX_ENV_DESCRIPTION)),\n ),\n }),\n ),\n api: v.strictObject({\n operations: v.array(\n v.strictObject({\n handle: nonEmpty,\n operationId: nonEmpty,\n effect: v.picklist(['read', 'mutation', 'unknown']),\n method: v.optional(nonEmpty),\n path: v.optional(nonEmpty),\n summary: v.optional(v.pipe(nonEmpty, v.maxLength(200))),\n }),\n ),\n }),\n background: v.strictObject({\n jobs: v.array(v.strictObject({ name: nonEmpty })),\n cron: v.array(\n v.strictObject({\n name: nonEmpty,\n schedule: cronSchedule,\n timezone: v.literal('UTC'),\n }),\n ),\n maintenance: v.array(\n v.strictObject({\n name: v.literal('storage-sweep'),\n schedule: cronSchedule,\n timezone: v.literal('UTC'),\n }),\n ),\n }),\n})\n\nfunction sortBy<T>(entries: readonly T[], key: (entry: T) => string): T[] {\n return [...entries].sort((left, right) => key(left).localeCompare(key(right)))\n}\n\nfunction rejectDuplicates(collection: string, values: readonly string[]): void {\n const seen = new Set<string>()\n for (const value of values) {\n if (seen.has(value))\n throw new Error(`[bunderstack] duplicate ${collection} \"${value}\"`)\n seen.add(value)\n }\n}\n\nfunction describeTables(schema: Record<string, unknown>) {\n const systemNames = new Set<string>(\n systemTables().map((table) => table.physicalName),\n )\n return sortBy(\n Object.entries(schema).flatMap(([exportName, value]) =>\n isTable(value) && !systemNames.has(getTableName(value))\n ? [{ exportName, physicalName: getTableName(value), system: false }]\n : [],\n ),\n (entry) => entry.physicalName,\n )\n}\n\nfunction envDescription(key: string, value: string): string {\n const description = value.trim().replace(/\\s+/g, ' ')\n if (!description)\n throw new Error(\n `[bunderstack] env.meta.${key}.description must not be empty`,\n )\n if (description.length > MAX_ENV_DESCRIPTION)\n throw new Error(\n `[bunderstack] env.meta.${key}.description must be at most ${MAX_ENV_DESCRIPTION} characters`,\n )\n return description\n}\n\nfunction describeSection(\n section: Record<string, StandardSchemaV1> | undefined,\n scope: ManifestEnvVar['scope'],\n meta: Record<string, EnvVarMeta> | undefined,\n): ManifestEnvVar[] {\n return Object.entries(section ?? {}).map(([key, schema]) => {\n let required = false\n try {\n validateStandardSchema(schema, undefined, 'env')\n } catch (error) {\n if (!(error instanceof StandardSchemaValidationError)) throw error\n required = true\n }\n const entry = meta?.[key]\n const sensitive = entry?.sensitive ?? scope === 'server'\n if (scope === 'client' && sensitive) {\n throw new Error(\n `[bunderstack] env.meta.${key}.sensitive must be false: client values ship to the browser`,\n )\n }\n return {\n key,\n required,\n scope,\n sensitive,\n ...(entry?.description === undefined\n ? {}\n : { description: envDescription(key, entry.description) }),\n }\n })\n}\n\nfunction systemTables() {\n return [\n {\n exportName: '_system.emailEvents',\n physicalName: getTableName(bunderstackEmailEvents),\n system: true,\n },\n {\n exportName: '_system.emails',\n physicalName: getTableName(bunderstackEmails),\n system: true,\n },\n {\n exportName: '_system.files',\n physicalName: getTableName(bunderstackFiles),\n system: true,\n },\n {\n exportName: '_system.idempotency',\n physicalName: getTableName(bunderstackIdempotency),\n system: true,\n },\n {\n exportName: '_system.jobs',\n physicalName: getTableName(bunderstackJobs),\n system: true,\n },\n ]\n}\n\nexport function parseManifest(value: unknown): BunderstackManifest {\n const manifest = validateStandardSchema(\n manifestSchema,\n value,\n 'manifest',\n ) as BunderstackManifest\n rejectDuplicates(\n 'database physical table',\n manifest.database.tables.map((entry) => entry.physicalName),\n )\n rejectDuplicates(\n 'database export table',\n manifest.database.tables.map((entry) => entry.exportName),\n )\n rejectDuplicates(\n 'storage bucket',\n manifest.storage.buckets.map((entry) => entry.name),\n )\n rejectDuplicates(\n 'environment key',\n manifest.environment.map((entry) => entry.key),\n )\n rejectDuplicates(\n 'api operation',\n manifest.api.operations.map((entry) => entry.handle),\n )\n rejectDuplicates(\n 'background job',\n manifest.background.jobs.map((entry) => entry.name),\n )\n rejectDuplicates(\n 'background cron',\n manifest.background.cron.map((entry) => entry.name),\n )\n rejectDuplicates(\n 'background maintenance',\n manifest.background.maintenance.map((entry) => entry.name),\n )\n return manifest\n}\n\nexport function buildManifest(args: {\n schema: Record<string, unknown>\n dialect: Dialect\n migrationsDirectory: string\n storage: ResolvedStorageBuckets\n envConfig: EnvConfigInput | undefined\n emailProvider: string | undefined\n realtime: boolean\n jobs: JobsDefs | undefined\n api: ApiOperation[]\n}): BunderstackManifest {\n const declaredKeys = new Set([\n ...Object.keys(args.envConfig?.server ?? {}),\n ...Object.keys(args.envConfig?.client ?? {}),\n ])\n for (const key of Object.keys(args.envConfig?.meta ?? {})) {\n if (!declaredKeys.has(key))\n throw new Error(\n `[bunderstack] env.meta references undeclared key \"${key}\"`,\n )\n }\n const environment = [\n ...describeSection(args.envConfig?.server, 'server', args.envConfig?.meta),\n ...describeSection(args.envConfig?.client, 'client', args.envConfig?.meta),\n ...(args.emailProvider === 'resend'\n ? [\n {\n key: 'RESEND_API_KEY',\n required: true,\n scope: 'server' as const,\n sensitive: true,\n description: 'Resend API key used to send transactional email',\n },\n ]\n : []),\n ...(args.emailProvider === 'smtp'\n ? [\n {\n key: 'SMTP_URL',\n required: true,\n scope: 'server' as const,\n sensitive: true,\n description: 'SMTP connection URL used to send transactional email',\n },\n ]\n : []),\n ]\n rejectDuplicates(\n 'environment key',\n environment.map((entry) => entry.key),\n )\n\n return parseManifest({\n version: 3,\n database: {\n dialect: args.dialect,\n migrationsDirectory: args.migrationsDirectory,\n tables: sortBy(\n [...systemTables(), ...describeTables(args.schema)],\n (entry) => entry.physicalName,\n ),\n },\n storage: {\n defaultBucket: args.storage.defaultBucket,\n buckets: sortBy(\n [...args.storage.buckets.values()].map((bucket) => ({\n name: bucket.name,\n visibility: bucket.visibility,\n })),\n (bucket) => bucket.name,\n ),\n },\n realtime: { required: args.realtime },\n environment: sortBy(environment, (entry) => entry.key),\n api: {\n operations: [...args.api].sort((left, right) =>\n left.handle.localeCompare(right.handle),\n ),\n },\n background: {\n jobs: sortBy(\n Object.entries(args.jobs ?? {})\n .filter(([, def]) => def.kind === 'job')\n .map(([name]) => ({ name })),\n (entry) => entry.name,\n ),\n cron: sortBy(\n Object.entries(args.jobs ?? {})\n .filter(([, def]) => def.kind === 'cron')\n .map(([name, def]) => ({\n name,\n schedule: def.kind === 'cron' ? def.schedule : '',\n timezone: 'UTC' as const,\n })),\n (entry) => entry.name,\n ),\n maintenance: [\n {\n name: 'storage-sweep',\n schedule: '0 4 * * *',\n timezone: 'UTC' as const,\n },\n ],\n },\n })\n}\n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { AnyDb } from './dialect.js';
|
|
2
|
+
import type { ReadinessProbes } from './readiness.js';
|
|
3
|
+
export declare function createReadinessProbes(db: AnyDb, now?: () => number): Pick<ReadinessProbes, 'probeDatabase' | 'countOverdueJobs'>;
|
|
4
|
+
//# sourceMappingURL=readiness-probes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readiness-probes.d.ts","sourceRoot":"","sources":["../src/readiness-probes.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,WAAW,CAAA;AACtC,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAOlD,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,KAAK,EACT,GAAG,GAAE,MAAM,MAAiB,GAC3B,IAAI,CAAC,eAAe,EAAE,eAAe,GAAG,kBAAkB,CAAC,CAe7D"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// src/readiness-probes.ts — the internal jobs table doubles as the liveness
|
|
2
|
+
// query: reaching it proves the connection, and a missing-relation error proves
|
|
3
|
+
// the schema was never provisioned.
|
|
4
|
+
import { and, count, eq, lt } from 'drizzle-orm';
|
|
5
|
+
import { jobsTableFor } from './internal-tables.js';
|
|
6
|
+
/** A pending job this far past its `runAt` means nothing is draining the queue. */
|
|
7
|
+
const OVERDUE_MS = 60_000;
|
|
8
|
+
export function createReadinessProbes(db, now = Date.now) {
|
|
9
|
+
const t = jobsTableFor(db);
|
|
10
|
+
return {
|
|
11
|
+
probeDatabase: async () => {
|
|
12
|
+
await db.select({ id: t.id }).from(t).limit(1);
|
|
13
|
+
},
|
|
14
|
+
countOverdueJobs: async () => {
|
|
15
|
+
const rows = await db
|
|
16
|
+
.select({ overdue: count() })
|
|
17
|
+
.from(t)
|
|
18
|
+
.where(and(eq(t.status, 'pending'), lt(t.runAt, now() - OVERDUE_MS)));
|
|
19
|
+
return Number(rows[0]?.overdue ?? 0);
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=readiness-probes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readiness-probes.js","sourceRoot":"","sources":["../src/readiness-probes.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,gFAAgF;AAChF,oCAAoC;AACpC,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA;AAKhD,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAEhD,mFAAmF;AACnF,MAAM,UAAU,GAAG,MAAM,CAAA;AAEzB,MAAM,UAAU,qBAAqB,CACnC,EAAS,EACT,MAAoB,IAAI,CAAC,GAAG;IAE5B,MAAM,CAAC,GAAG,YAAY,CAAC,EAAE,CAAC,CAAA;IAE1B,OAAO;QACL,aAAa,EAAE,KAAK,IAAI,EAAE;YACxB,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAChD,CAAC;QACD,gBAAgB,EAAE,KAAK,IAAI,EAAE;YAC3B,MAAM,IAAI,GAAG,MAAM,EAAE;iBAClB,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC;iBAC5B,IAAI,CAAC,CAAC,CAAC;iBACP,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,EAAE,SAAS,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA;YACvE,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,OAAO,IAAI,CAAC,CAAC,CAAA;QACtC,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["// src/readiness-probes.ts — the internal jobs table doubles as the liveness\n// query: reaching it proves the connection, and a missing-relation error proves\n// the schema was never provisioned.\nimport { and, count, eq, lt } from 'drizzle-orm'\n\nimport type { AnyDb } from './dialect'\nimport type { ReadinessProbes } from './readiness'\n\nimport { jobsTableFor } from './internal-tables'\n\n/** A pending job this far past its `runAt` means nothing is draining the queue. */\nconst OVERDUE_MS = 60_000\n\nexport function createReadinessProbes(\n db: AnyDb,\n now: () => number = Date.now,\n): Pick<ReadinessProbes, 'probeDatabase' | 'countOverdueJobs'> {\n const t = jobsTableFor(db)\n\n return {\n probeDatabase: async () => {\n await db.select({ id: t.id }).from(t).limit(1)\n },\n countOverdueJobs: async () => {\n const rows = await db\n .select({ overdue: count() })\n .from(t)\n .where(and(eq(t.status, 'pending'), lt(t.runAt, now() - OVERDUE_MS)))\n return Number(rows[0]?.overdue ?? 0)\n },\n }\n}\n"]}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as v from 'valibot';
|
|
2
|
+
export type ReadinessStatus = 'ok' | 'degraded' | 'error' | 'skipped';
|
|
3
|
+
export type ReadinessCode = 'unreachable' | 'not_provisioned' | 'backlog';
|
|
4
|
+
export type ReadinessCheckName = 'database' | 'schema' | 'background';
|
|
5
|
+
export type ReadinessCheck = {
|
|
6
|
+
name: ReadinessCheckName;
|
|
7
|
+
status: ReadinessStatus;
|
|
8
|
+
code?: ReadinessCode;
|
|
9
|
+
overdue?: number;
|
|
10
|
+
};
|
|
11
|
+
export type ReadinessReport = {
|
|
12
|
+
status: 'ok' | 'degraded' | 'error';
|
|
13
|
+
revision?: string;
|
|
14
|
+
checks: ReadinessCheck[];
|
|
15
|
+
};
|
|
16
|
+
export type ReadinessProbes = {
|
|
17
|
+
/** Deployed commit, when the platform injects `BUNDERSTACK_REVISION`. */
|
|
18
|
+
revision?: string;
|
|
19
|
+
queueJobsDeclared: boolean;
|
|
20
|
+
probeDatabase: () => Promise<void>;
|
|
21
|
+
countOverdueJobs: () => Promise<number>;
|
|
22
|
+
};
|
|
23
|
+
export declare const readinessReportSchema: v.StrictObjectSchema<{
|
|
24
|
+
readonly status: v.PicklistSchema<["ok", "degraded", "error"], undefined>;
|
|
25
|
+
readonly revision: v.OptionalSchema<v.StringSchema<undefined>, undefined>;
|
|
26
|
+
readonly checks: v.ArraySchema<v.StrictObjectSchema<{
|
|
27
|
+
readonly name: v.PicklistSchema<["database", "schema", "background"], undefined>;
|
|
28
|
+
readonly status: v.PicklistSchema<["ok", "degraded", "error", "skipped"], undefined>;
|
|
29
|
+
readonly code: v.OptionalSchema<v.PicklistSchema<["unreachable", "not_provisioned", "backlog"], undefined>, undefined>;
|
|
30
|
+
readonly overdue: v.OptionalSchema<v.NumberSchema<undefined>, undefined>;
|
|
31
|
+
}, undefined>, undefined>;
|
|
32
|
+
}, undefined>;
|
|
33
|
+
/**
|
|
34
|
+
* A query that fails this way reached the database and found no schema.
|
|
35
|
+
* Drizzle wraps the driver error, so the relation name lives on `cause`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isMissingRelationError(error: unknown): boolean;
|
|
38
|
+
export declare function buildReadinessReport(probes: ReadinessProbes): Promise<ReadinessReport>;
|
|
39
|
+
//# sourceMappingURL=readiness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readiness.d.ts","sourceRoot":"","sources":["../src/readiness.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,CAAC,MAAM,SAAS,CAAA;AAE5B,MAAM,MAAM,eAAe,GAAG,IAAI,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,CAAA;AACrE,MAAM,MAAM,aAAa,GAAG,aAAa,GAAG,iBAAiB,GAAG,SAAS,CAAA;AACzE,MAAM,MAAM,kBAAkB,GAAG,UAAU,GAAG,QAAQ,GAAG,YAAY,CAAA;AAErE,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,kBAAkB,CAAA;IACxB,MAAM,EAAE,eAAe,CAAA;IACvB,IAAI,CAAC,EAAE,aAAa,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,IAAI,GAAG,UAAU,GAAG,OAAO,CAAA;IACnC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,cAAc,EAAE,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,iBAAiB,EAAE,OAAO,CAAA;IAC1B,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC,gBAAgB,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;CACxC,CAAA;AAED,eAAO,MAAM,qBAAqB;;;;;;;;;aAahC,CAAA;AAKF;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAa9D;AAED,wBAAsB,oBAAoB,CACxC,MAAM,EAAE,eAAe,GACtB,OAAO,CAAC,eAAe,CAAC,CAgD1B"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
// src/readiness.ts — machine-readable "did this release come up" report.
|
|
2
|
+
// Public endpoint: results carry a closed set of codes and never a driver
|
|
3
|
+
// message, a connection string, or a stack trace.
|
|
4
|
+
import * as v from 'valibot';
|
|
5
|
+
export const readinessReportSchema = v.strictObject({
|
|
6
|
+
status: v.picklist(['ok', 'degraded', 'error']),
|
|
7
|
+
revision: v.optional(v.string()),
|
|
8
|
+
checks: v.array(v.strictObject({
|
|
9
|
+
name: v.picklist(['database', 'schema', 'background']),
|
|
10
|
+
status: v.picklist(['ok', 'degraded', 'error', 'skipped']),
|
|
11
|
+
code: v.optional(v.picklist(['unreachable', 'not_provisioned', 'backlog'])),
|
|
12
|
+
overdue: v.optional(v.number()),
|
|
13
|
+
})),
|
|
14
|
+
});
|
|
15
|
+
const MISSING_RELATION = /no such table|does not exist|undefined table|unknown table/i;
|
|
16
|
+
/**
|
|
17
|
+
* A query that fails this way reached the database and found no schema.
|
|
18
|
+
* Drizzle wraps the driver error, so the relation name lives on `cause`.
|
|
19
|
+
*/
|
|
20
|
+
export function isMissingRelationError(error) {
|
|
21
|
+
let current = error;
|
|
22
|
+
for (let depth = 0; current !== undefined && current !== null && depth < 8; depth += 1) {
|
|
23
|
+
const message = current instanceof Error ? current.message : String(current);
|
|
24
|
+
if (MISSING_RELATION.test(message))
|
|
25
|
+
return true;
|
|
26
|
+
if (!(current instanceof Error))
|
|
27
|
+
return false;
|
|
28
|
+
current = current.cause;
|
|
29
|
+
}
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
export async function buildReadinessReport(probes) {
|
|
33
|
+
const checks = [];
|
|
34
|
+
let queryable = false;
|
|
35
|
+
try {
|
|
36
|
+
await probes.probeDatabase();
|
|
37
|
+
checks.push({ name: 'database', status: 'ok' }, { name: 'schema', status: 'ok' });
|
|
38
|
+
queryable = true;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
checks.push(isMissingRelationError(error)
|
|
42
|
+
? { name: 'database', status: 'ok' }
|
|
43
|
+
: { name: 'database', status: 'error', code: 'unreachable' }, isMissingRelationError(error)
|
|
44
|
+
? { name: 'schema', status: 'error', code: 'not_provisioned' }
|
|
45
|
+
: { name: 'schema', status: 'skipped' });
|
|
46
|
+
}
|
|
47
|
+
if (!queryable || !probes.queueJobsDeclared) {
|
|
48
|
+
checks.push({ name: 'background', status: 'skipped' });
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
try {
|
|
52
|
+
const overdue = await probes.countOverdueJobs();
|
|
53
|
+
checks.push(overdue > 0
|
|
54
|
+
? { name: 'background', status: 'degraded', code: 'backlog', overdue }
|
|
55
|
+
: { name: 'background', status: 'ok' });
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
checks.push({ name: 'background', status: 'skipped' });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const status = checks.some((check) => check.status === 'error')
|
|
62
|
+
? 'error'
|
|
63
|
+
: checks.some((check) => check.status === 'degraded')
|
|
64
|
+
? 'degraded'
|
|
65
|
+
: 'ok';
|
|
66
|
+
return {
|
|
67
|
+
status,
|
|
68
|
+
...(probes.revision === undefined ? {} : { revision: probes.revision }),
|
|
69
|
+
checks,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=readiness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readiness.js","sourceRoot":"","sources":["../src/readiness.ts"],"names":[],"mappings":"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","sourcesContent":["// src/readiness.ts — machine-readable \"did this release come up\" report.\n// Public endpoint: results carry a closed set of codes and never a driver\n// message, a connection string, or a stack trace.\nimport * as v from 'valibot'\n\nexport type ReadinessStatus = 'ok' | 'degraded' | 'error' | 'skipped'\nexport type ReadinessCode = 'unreachable' | 'not_provisioned' | 'backlog'\nexport type ReadinessCheckName = 'database' | 'schema' | 'background'\n\nexport type ReadinessCheck = {\n name: ReadinessCheckName\n status: ReadinessStatus\n code?: ReadinessCode\n overdue?: number\n}\n\nexport type ReadinessReport = {\n status: 'ok' | 'degraded' | 'error'\n revision?: string\n checks: ReadinessCheck[]\n}\n\nexport type ReadinessProbes = {\n /** Deployed commit, when the platform injects `BUNDERSTACK_REVISION`. */\n revision?: string\n queueJobsDeclared: boolean\n probeDatabase: () => Promise<void>\n countOverdueJobs: () => Promise<number>\n}\n\nexport const readinessReportSchema = v.strictObject({\n status: v.picklist(['ok', 'degraded', 'error']),\n revision: v.optional(v.string()),\n checks: v.array(\n v.strictObject({\n name: v.picklist(['database', 'schema', 'background']),\n status: v.picklist(['ok', 'degraded', 'error', 'skipped']),\n code: v.optional(\n v.picklist(['unreachable', 'not_provisioned', 'backlog']),\n ),\n overdue: v.optional(v.number()),\n }),\n ),\n})\n\nconst MISSING_RELATION =\n /no such table|does not exist|undefined table|unknown table/i\n\n/**\n * A query that fails this way reached the database and found no schema.\n * Drizzle wraps the driver error, so the relation name lives on `cause`.\n */\nexport function isMissingRelationError(error: unknown): boolean {\n let current: unknown = error\n for (\n let depth = 0;\n current !== undefined && current !== null && depth < 8;\n depth += 1\n ) {\n const message = current instanceof Error ? current.message : String(current)\n if (MISSING_RELATION.test(message)) return true\n if (!(current instanceof Error)) return false\n current = current.cause\n }\n return false\n}\n\nexport async function buildReadinessReport(\n probes: ReadinessProbes,\n): Promise<ReadinessReport> {\n const checks: ReadinessCheck[] = []\n let queryable = false\n\n try {\n await probes.probeDatabase()\n checks.push(\n { name: 'database', status: 'ok' },\n { name: 'schema', status: 'ok' },\n )\n queryable = true\n } catch (error) {\n checks.push(\n isMissingRelationError(error)\n ? { name: 'database', status: 'ok' }\n : { name: 'database', status: 'error', code: 'unreachable' },\n isMissingRelationError(error)\n ? { name: 'schema', status: 'error', code: 'not_provisioned' }\n : { name: 'schema', status: 'skipped' },\n )\n }\n\n if (!queryable || !probes.queueJobsDeclared) {\n checks.push({ name: 'background', status: 'skipped' })\n } else {\n try {\n const overdue = await probes.countOverdueJobs()\n checks.push(\n overdue > 0\n ? { name: 'background', status: 'degraded', code: 'backlog', overdue }\n : { name: 'background', status: 'ok' },\n )\n } catch {\n checks.push({ name: 'background', status: 'skipped' })\n }\n }\n\n const status = checks.some((check) => check.status === 'error')\n ? 'error'\n : checks.some((check) => check.status === 'degraded')\n ? 'degraded'\n : 'ok'\n\n return {\n status,\n ...(probes.revision === undefined ? {} : { revision: probes.revision }),\n checks,\n }\n}\n"]}
|