bunderstack 0.25.2 → 1.0.0-beta.1

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.
Files changed (164) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/dist/auth-password.d.ts +27 -0
  3. package/dist/auth-password.d.ts.map +1 -0
  4. package/dist/auth-password.js +60 -0
  5. package/dist/auth-password.js.map +1 -0
  6. package/dist/auth.d.ts +1 -1
  7. package/dist/auth.d.ts.map +1 -1
  8. package/dist/auth.js +1 -0
  9. package/dist/auth.js.map +1 -1
  10. package/dist/backend.d.ts +3 -0
  11. package/dist/backend.d.ts.map +1 -1
  12. package/dist/backend.js +3 -1
  13. package/dist/backend.js.map +1 -1
  14. package/dist/cli.d.ts +11 -1
  15. package/dist/cli.d.ts.map +1 -1
  16. package/dist/cli.js +97 -2
  17. package/dist/cli.js.map +1 -1
  18. package/dist/config.d.ts +0 -8
  19. package/dist/config.d.ts.map +1 -1
  20. package/dist/config.js +0 -18
  21. package/dist/config.js.map +1 -1
  22. package/dist/crud-operations.d.ts.map +1 -1
  23. package/dist/crud-operations.js +19 -3
  24. package/dist/crud-operations.js.map +1 -1
  25. package/dist/dev/binaries.d.ts +22 -0
  26. package/dist/dev/binaries.d.ts.map +1 -0
  27. package/dist/dev/binaries.js +119 -0
  28. package/dist/dev/binaries.js.map +1 -0
  29. package/dist/dev/env.d.ts +11 -0
  30. package/dist/dev/env.d.ts.map +1 -0
  31. package/dist/dev/env.js +71 -0
  32. package/dist/dev/env.js.map +1 -0
  33. package/dist/dev/index.d.ts +45 -0
  34. package/dist/dev/index.d.ts.map +1 -0
  35. package/dist/dev/index.js +287 -0
  36. package/dist/dev/index.js.map +1 -0
  37. package/dist/dev/processes.d.ts +37 -0
  38. package/dist/dev/processes.d.ts.map +1 -0
  39. package/dist/dev/processes.js +104 -0
  40. package/dist/dev/processes.js.map +1 -0
  41. package/dist/dev/push.d.ts +2 -0
  42. package/dist/dev/push.d.ts.map +1 -0
  43. package/dist/dev/push.js +25 -0
  44. package/dist/dev/push.js.map +1 -0
  45. package/dist/env-probe.d.ts.map +1 -1
  46. package/dist/env-probe.js +0 -1
  47. package/dist/env-probe.js.map +1 -1
  48. package/dist/env.d.ts +0 -3
  49. package/dist/env.d.ts.map +1 -1
  50. package/dist/env.js +0 -7
  51. package/dist/env.js.map +1 -1
  52. package/dist/handler.d.ts +2 -0
  53. package/dist/handler.d.ts.map +1 -1
  54. package/dist/handler.js +1 -2
  55. package/dist/handler.js.map +1 -1
  56. package/dist/hosted-contract.d.ts.map +1 -1
  57. package/dist/hosted-contract.js +7 -3
  58. package/dist/hosted-contract.js.map +1 -1
  59. package/dist/index.d.ts +4 -2
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +1 -0
  62. package/dist/index.js.map +1 -1
  63. package/dist/jobs/define.d.ts +5 -0
  64. package/dist/jobs/define.d.ts.map +1 -1
  65. package/dist/jobs/define.js.map +1 -1
  66. package/dist/jobs/index.d.ts +2 -4
  67. package/dist/jobs/index.d.ts.map +1 -1
  68. package/dist/jobs/index.js +2 -3
  69. package/dist/jobs/index.js.map +1 -1
  70. package/dist/jobs/queue.d.ts +1 -0
  71. package/dist/jobs/queue.d.ts.map +1 -1
  72. package/dist/jobs/queue.js +6 -3
  73. package/dist/jobs/queue.js.map +1 -1
  74. package/dist/jobs/slots.d.ts +5 -0
  75. package/dist/jobs/slots.d.ts.map +1 -1
  76. package/dist/jobs/slots.js +12 -0
  77. package/dist/jobs/slots.js.map +1 -1
  78. package/dist/jobs/worker.d.ts +6 -0
  79. package/dist/jobs/worker.d.ts.map +1 -1
  80. package/dist/jobs/worker.js +32 -1
  81. package/dist/jobs/worker.js.map +1 -1
  82. package/dist/platform.d.ts +25 -0
  83. package/dist/platform.d.ts.map +1 -0
  84. package/dist/platform.js +24 -0
  85. package/dist/platform.js.map +1 -0
  86. package/dist/rate-limit.d.ts +2 -1
  87. package/dist/rate-limit.d.ts.map +1 -1
  88. package/dist/rate-limit.js +19 -22
  89. package/dist/rate-limit.js.map +1 -1
  90. package/dist/realtime/facade.d.ts +1 -1
  91. package/dist/realtime/facade.d.ts.map +1 -1
  92. package/dist/realtime/facade.js.map +1 -1
  93. package/dist/realtime/publisher.d.ts +0 -4
  94. package/dist/realtime/publisher.d.ts.map +1 -1
  95. package/dist/realtime/publisher.js +0 -9
  96. package/dist/realtime/publisher.js.map +1 -1
  97. package/dist/runtime.d.ts +5 -19
  98. package/dist/runtime.d.ts.map +1 -1
  99. package/dist/runtime.js +28 -102
  100. package/dist/runtime.js.map +1 -1
  101. package/dist/storage/registry.d.ts +2 -1
  102. package/dist/storage/registry.d.ts.map +1 -1
  103. package/dist/storage/registry.js +2 -2
  104. package/dist/storage/registry.js.map +1 -1
  105. package/dist/storage/s3.d.ts +11 -4
  106. package/dist/storage/s3.d.ts.map +1 -1
  107. package/dist/storage/s3.js +88 -34
  108. package/dist/storage/s3.js.map +1 -1
  109. package/dist/storage/sigv4.d.ts +24 -0
  110. package/dist/storage/sigv4.d.ts.map +1 -0
  111. package/dist/storage/sigv4.js +107 -0
  112. package/dist/storage/sigv4.js.map +1 -0
  113. package/dist/testing/fixture.d.ts.map +1 -1
  114. package/dist/testing/fixture.js +0 -3
  115. package/dist/testing/fixture.js.map +1 -1
  116. package/dist/testing/workers-fakes.d.ts +17 -0
  117. package/dist/testing/workers-fakes.d.ts.map +1 -0
  118. package/dist/testing/workers-fakes.js +98 -0
  119. package/dist/testing/workers-fakes.js.map +1 -0
  120. package/dist/typeid.d.ts +1 -0
  121. package/dist/typeid.d.ts.map +1 -1
  122. package/dist/typeid.js +34 -4
  123. package/dist/typeid.js.map +1 -1
  124. package/dist/vite.d.ts +30 -0
  125. package/dist/vite.d.ts.map +1 -0
  126. package/dist/vite.js +37 -0
  127. package/dist/vite.js.map +1 -0
  128. package/dist/workers/app.d.ts +19 -0
  129. package/dist/workers/app.d.ts.map +1 -0
  130. package/dist/workers/app.js +56 -0
  131. package/dist/workers/app.js.map +1 -0
  132. package/dist/workers/index.d.ts +21 -0
  133. package/dist/workers/index.d.ts.map +1 -0
  134. package/dist/workers/index.js +33 -0
  135. package/dist/workers/index.js.map +1 -0
  136. package/dist/workers/r2.d.ts +23 -0
  137. package/dist/workers/r2.d.ts.map +1 -0
  138. package/dist/workers/r2.js +77 -0
  139. package/dist/workers/r2.js.map +1 -0
  140. package/dist/workers/rate-limiter.d.ts +9 -0
  141. package/dist/workers/rate-limiter.d.ts.map +1 -0
  142. package/dist/workers/rate-limiter.js +22 -0
  143. package/dist/workers/rate-limiter.js.map +1 -0
  144. package/dist/workers/realtime-hub.d.ts +20 -0
  145. package/dist/workers/realtime-hub.d.ts.map +1 -0
  146. package/dist/workers/realtime-hub.js +117 -0
  147. package/dist/workers/realtime-hub.js.map +1 -0
  148. package/dist/workers/scheduler.d.ts +13 -0
  149. package/dist/workers/scheduler.d.ts.map +1 -0
  150. package/dist/workers/scheduler.js +71 -0
  151. package/dist/workers/scheduler.js.map +1 -0
  152. package/dist/workers/types.d.ts +55 -0
  153. package/dist/workers/types.d.ts.map +1 -0
  154. package/dist/workers/types.js +4 -0
  155. package/dist/workers/types.js.map +1 -0
  156. package/dist/workers/wrangler.d.ts +64 -0
  157. package/dist/workers/wrangler.d.ts.map +1 -0
  158. package/dist/workers/wrangler.js +115 -0
  159. package/dist/workers/wrangler.js.map +1 -0
  160. package/package.json +11 -3
  161. package/dist/jobs/runtime.d.ts +0 -17
  162. package/dist/jobs/runtime.d.ts.map +0 -1
  163. package/dist/jobs/runtime.js +0 -56
  164. package/dist/jobs/runtime.js.map +0 -1
@@ -8,7 +8,7 @@ import { validateStandardSchema } from '../standard-schema.js';
8
8
  import { parseCron } from './cron.js';
9
9
  import { backoffMs, DEFAULT_RETRIES, leaseDurationFor } from './define.js';
10
10
  import { enqueueJob } from './queue.js';
11
- import { CRON_PREFIX, floorSlot, slotsDue, SLOT_MS } from './slots.js';
11
+ import { CRON_PREFIX, floorSlot, nextCronSlot, slotsDue, SLOT_MS, } from './slots.js';
12
12
  const CLAIM_BATCH = 10;
13
13
  const SUCCEEDED_RETENTION_MS = 24 * 60 * 60 * 1000;
14
14
  const REAP_INTERVAL_MS = 60 * 60_000;
@@ -515,6 +515,37 @@ export function createJobRunner(deps) {
515
515
  },
516
516
  pump,
517
517
  drain,
518
+ /**
519
+ * The earliest time that has work: a pending run_at, a running lease end,
520
+ * or a cron slot not yet materialized. Never earlier than `now`; null when
521
+ * nothing is due before `until`.
522
+ */
523
+ async nextDueAt(now, until) {
524
+ const candidates = [];
525
+ const [pending] = await db
526
+ .select({ at: sql `min(${t.runAt})` })
527
+ .from(t)
528
+ .where(eq(t.status, 'pending'));
529
+ if (pending?.at != null)
530
+ candidates.push(Number(pending.at));
531
+ const [running] = await db
532
+ .select({ at: sql `min(${t.lockedUntil})` })
533
+ .from(t)
534
+ .where(eq(t.status, 'running'));
535
+ if (running?.at != null)
536
+ candidates.push(Number(running.at));
537
+ for (const [name, def] of Object.entries(defs)) {
538
+ if (def.kind !== 'cron')
539
+ continue;
540
+ const cursor = await cronCursor(`${CRON_PREFIX}${name}`, now);
541
+ const slot = nextCronSlot(parseCron(def.schedule), cursor.checkedThrough, until);
542
+ if (slot !== null)
543
+ candidates.push(slot);
544
+ }
545
+ if (candidates.length === 0)
546
+ return null;
547
+ return Math.max(now, Math.min(...candidates));
548
+ },
518
549
  async inspect(now) {
519
550
  const runnableRows = await db
520
551
  .select({ id: t.id })
@@ -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 {\n and,\n desc,\n eq,\n inArray,\n is,\n isNotNull,\n lt,\n lte,\n sql,\n} 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, leaseDurationFor } 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\nclass JobExecutionTimeoutError extends Error {\n constructor(maxRuntime: number) {\n super(`execution timed out after ${maxRuntime}ms`)\n this.name = 'JobExecutionTimeoutError'\n }\n}\n\nclass JobLeaseLostError extends Error {\n constructor() {\n super('job lease ownership was lost')\n this.name = 'JobLeaseLostError'\n }\n}\n\ntype JobRow = {\n id: string\n type: string\n payloadJson: string\n attempts: number\n runAt: number\n}\n\ntype LeaseOwner = {\n attempt: number\n lockedUntil: number\n}\n\ntype ClaimedWork = {\n row: JobRow\n def: AnyBackgroundDefinition\n owner: LeaseOwner\n}\n\ntype LeaseHeartbeat = {\n owner: LeaseOwner\n lost: Promise<void>\n stop(): Promise<void>\n}\n\ntype PumpResult = { wake?: Promise<void> }\n\ntype CronCursor = {\n checkedThrough: number\n}\n\nfunction jobEvent(\n event: string,\n row: JobRow,\n fields: Record<string, unknown> = {},\n) {\n return {\n source: 'bunderstack.jobs',\n event,\n jobId: row.id,\n jobType: row.type,\n attempt: Number(row.attempts),\n ...fields,\n }\n}\n\nfunction capacityFor(def: AnyBackgroundDefinition): number {\n return def.kind === 'job' && def.concurrency !== undefined\n ? def.concurrency\n : CLAIM_BATCH\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/** Queue jobs release terminal dedupe keys; cron slots retain durable ownership. */\nfunction terminalPatch(def: AnyBackgroundDefinition) {\n return def.kind === 'cron' ? {} : { dedupeKey: null }\n}\n\n/** `dedupeUntil: 'start'` releases the key at claim; everything else keeps it\n * until a terminal state, so the claim patch stays unchanged for them. */\nfunction claimPatch(def: AnyBackgroundDefinition) {\n return def.kind === 'job' && def.dedupeUntil === 'start'\n ? { dedupeKey: null }\n : {}\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 const active = new Map<string, Set<Promise<void>>>()\n const cronCursors = new Map<string, CronCursor>()\n\n function ownershipPredicate(id: string, owner: LeaseOwner) {\n return and(\n eq(t.id, id),\n eq(t.status, 'running'),\n eq(t.attempts, owner.attempt),\n eq(t.lockedUntil, owner.lockedUntil),\n )\n }\n\n function startLeaseHeartbeat(args: {\n row: JobRow\n def: AnyBackgroundDefinition\n owner: LeaseOwner\n onLost: () => void\n }): LeaseHeartbeat {\n const owner = args.owner\n const intervalMs = Math.max(10, Math.floor(leaseDurationFor(args.def) / 3))\n let stopped = false\n let resolveLost!: () => void\n const lost = new Promise<void>((resolve) => {\n resolveLost = resolve\n })\n let chain = Promise.resolve()\n const lose = () => {\n if (stopped) return\n stopped = true\n clearInterval(timer)\n resolveLost()\n args.onLost()\n }\n const renew = () => {\n chain = chain\n .then(async () => {\n if (stopped) return\n const nextLockedUntil = Date.now() + leaseDurationFor(args.def)\n const updated = await db\n .update(t)\n .set({ lockedUntil: nextLockedUntil })\n .where(ownershipPredicate(args.row.id, owner))\n .returning({ id: t.id })\n if (!updated[0]) return lose()\n owner.lockedUntil = nextLockedUntil\n })\n .catch(() => lose())\n }\n const timer = setInterval(renew, intervalMs)\n return {\n owner,\n lost,\n async stop() {\n stopped = true\n clearInterval(timer)\n await chain\n },\n }\n }\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 /** Hydrate each cron cursor once, then advance it in this worker's memory. */\n async function cronCursor(type: string, now: number): Promise<CronCursor> {\n const cached = cronCursors.get(type)\n if (cached) return cached\n\n const rows = await db\n .select({ runAt: t.runAt })\n .from(t)\n .where(eq(t.type, type))\n .orderBy(desc(t.runAt))\n .limit(1)\n const cursor = {\n checkedThrough:\n rows[0]?.runAt == null\n ? floorSlot(now) - SLOT_MS\n : Number(rows[0].runAt),\n }\n cronCursors.set(type, cursor)\n return cursor\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 const through = floorSlot(now)\n for (const [name, def] of Object.entries(defs)) {\n if (def.kind !== 'cron') continue\n const type = `${CRON_PREFIX}${name}`\n const cursor = await cronCursor(type, now)\n const slots = slotsDue({\n cron: parseCron(def.schedule),\n from: cursor.checkedThrough,\n to: through,\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 cursor.checkedThrough = slot\n }\n cursor.checkedThrough = Math.max(cursor.checkedThrough, through)\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 & {\n lastError: string | null\n lockedUntil: number\n })[] = 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 lockedUntil: t.lockedUntil,\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 owner = {\n attempt: Number(row.attempts),\n lockedUntil: Number(row.lockedUntil),\n }\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(ownershipPredicate(row.id, owner))\n continue\n }\n if (Number(row.attempts) >= maxAttempts(def)) {\n const updated = await db\n .update(t)\n .set({\n status: 'failed',\n finishedAt: now,\n lockedUntil: null,\n lastError: error.message,\n ...terminalPatch(def),\n })\n .where(ownershipPredicate(row.id, owner))\n .returning({ id: t.id })\n if (updated[0]) 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(ownershipPredicate(row.id, owner))\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 def: AnyBackgroundDefinition,\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 ...claimPatch(def),\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 async function claimAvailable(\n type: string,\n def: AnyBackgroundDefinition,\n now: number,\n ): Promise<ClaimedWork[]> {\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 let available = capacityFor(def) - runningRows.length\n if (available <= 0) return []\n\n const leaseUntil = now + leaseDurationFor(def)\n const work: ClaimedWork[] = []\n while (available > 0) {\n const limit = Math.min(CLAIM_BATCH, available)\n const rows = await claim(type, def, limit, now, leaseUntil)\n for (const row of rows) {\n work.push({\n row,\n def,\n owner: { attempt: Number(row.attempts), lockedUntil: leaseUntil },\n })\n }\n available -= rows.length\n if (rows.length < limit) break\n }\n return work\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 owner: LeaseOwner,\n ): Promise<'ran' | 'failed' | 'lost'> {\n const startedAt = Date.now()\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(def),\n })\n .where(ownershipPredicate(row.id, owner))\n .returning({ id: t.id })\n if (!updated[0]) return 'lost'\n await fireOnFailed(def, undefined, e)\n return 'failed'\n }\n const controller = new AbortController()\n const handlerCtx = { ...ctx, signal: controller.signal }\n logger.info(JSON.stringify(jobEvent('job.claimed', row)))\n let lost = false\n const heartbeat = startLeaseHeartbeat({\n row,\n def,\n owner,\n onLost: () => {\n lost = true\n controller.abort(new JobLeaseLostError())\n logger.warn(\n JSON.stringify(\n jobEvent('job.lease_lost', row, {\n durationMs: Date.now() - startedAt,\n }),\n ),\n )\n },\n })\n const deadline =\n def.maxRuntime === undefined\n ? undefined\n : setTimeout(() => {\n controller.abort(new JobExecutionTimeoutError(def.maxRuntime!))\n logger.warn(\n JSON.stringify(\n jobEvent('job.execution_timed_out', row, {\n durationMs: Date.now() - startedAt,\n }),\n ),\n )\n }, def.maxRuntime)\n try {\n await (def.handler as (i: unknown, c: unknown) => unknown)(\n input,\n handlerCtx,\n )\n if (controller.signal.reason instanceof JobExecutionTimeoutError) {\n throw controller.signal.reason\n }\n if (deadline) clearTimeout(deadline)\n await heartbeat.stop()\n if (lost) return 'lost'\n const updated = await db\n .update(t)\n .set({\n status: 'succeeded',\n finishedAt: Date.now(),\n lockedUntil: null,\n ...terminalPatch(def),\n })\n .where(ownershipPredicate(row.id, owner))\n .returning({ id: t.id })\n if (!updated[0]) return 'lost'\n logger.info(\n JSON.stringify(\n jobEvent('job.completed', row, {\n durationMs: Date.now() - startedAt,\n }),\n ),\n )\n return 'ran'\n } catch (err) {\n if (deadline) clearTimeout(deadline)\n const e =\n controller.signal.reason instanceof JobExecutionTimeoutError\n ? controller.signal.reason\n : toError(err)\n await heartbeat.stop()\n if (lost) return 'lost'\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(ownershipPredicate(row.id, owner))\n .returning({ id: t.id })\n if (!updated[0]) return 'lost'\n logger.warn(\n JSON.stringify(\n jobEvent('job.retrying', row, {\n durationMs: Date.now() - startedAt,\n error: e.message,\n }),\n ),\n )\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(def),\n })\n .where(ownershipPredicate(row.id, owner))\n .returning({ id: t.id })\n if (!updated[0]) return 'lost'\n logger.error(\n JSON.stringify(\n jobEvent('job.failed', row, {\n durationMs: Date.now() - startedAt,\n error: e.message,\n }),\n ),\n )\n await fireOnFailed(def, input, e)\n }\n return 'failed'\n }\n }\n\n async function runClaimable(now: number): Promise<TickResult> {\n const claimedWork: ClaimedWork[] = []\n let totalClaimed = 0\n for (const [name, def] of Object.entries(defs)) {\n const type = def.kind === 'cron' ? `${CRON_PREFIX}${name}` : name\n const claimed = await claimAvailable(type, def, now)\n totalClaimed += claimed.length\n claimedWork.push(...claimed)\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, owner }) => runJob(row, def, now, owner)),\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 async function maintain(now: number) {\n await materializeCronSlots(now)\n await recoverExpiredLeases(now)\n if (now - lastReapAt >= REAP_INTERVAL_MS) {\n lastReapAt = now\n await reapSucceeded(now)\n }\n }\n\n function startWork(\n type: string,\n work: ClaimedWork,\n claimedAt: number,\n ): Promise<void> {\n let tasks = active.get(type)\n if (!tasks) {\n tasks = new Set()\n active.set(type, tasks)\n }\n let task: Promise<void>\n task = runJob(work.row, work.def, claimedAt, work.owner)\n .then(() => undefined)\n .catch((error) => {\n logger.error('[bunderstack] background job execution failed:', error)\n })\n .finally(() => {\n tasks!.delete(task)\n if (tasks!.size === 0) active.delete(type)\n })\n tasks.add(task)\n return task\n }\n\n async function pump(now: number = Date.now()): Promise<PumpResult> {\n await maintain(now)\n for (const [name, def] of Object.entries(defs)) {\n const type = def.kind === 'cron' ? `${CRON_PREFIX}${name}` : name\n const work = await claimAvailable(type, def, now)\n for (const item of work) startWork(type, item, now)\n }\n const snapshot = [...active.values()].flatMap((tasks) => [...tasks])\n return snapshot.length === 0\n ? {}\n : { wake: Promise.race(snapshot).then(() => undefined) }\n }\n\n async function drain(): Promise<void> {\n const snapshot = [...active.values()].flatMap((tasks) => [...tasks])\n await Promise.allSettled(snapshot)\n }\n\n return {\n async tick(now: number = Date.now()): Promise<TickResult> {\n await maintain(now)\n return runClaimable(now)\n },\n pump,\n drain,\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"]}
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 {\n and,\n desc,\n eq,\n inArray,\n is,\n isNotNull,\n lt,\n lte,\n sql,\n} 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, leaseDurationFor } from './define'\nimport { enqueueJob } from './queue'\nimport {\n CRON_PREFIX,\n floorSlot,\n nextCronSlot,\n slotsDue,\n SLOT_MS,\n} from './slots'\n\nconst CLAIM_BATCH = 10\nconst SUCCEEDED_RETENTION_MS = 24 * 60 * 60 * 1000\nconst REAP_INTERVAL_MS = 60 * 60_000\n\nclass JobExecutionTimeoutError extends Error {\n constructor(maxRuntime: number) {\n super(`execution timed out after ${maxRuntime}ms`)\n this.name = 'JobExecutionTimeoutError'\n }\n}\n\nclass JobLeaseLostError extends Error {\n constructor() {\n super('job lease ownership was lost')\n this.name = 'JobLeaseLostError'\n }\n}\n\ntype JobRow = {\n id: string\n type: string\n payloadJson: string\n attempts: number\n runAt: number\n}\n\ntype LeaseOwner = {\n attempt: number\n lockedUntil: number\n}\n\ntype ClaimedWork = {\n row: JobRow\n def: AnyBackgroundDefinition\n owner: LeaseOwner\n}\n\ntype LeaseHeartbeat = {\n owner: LeaseOwner\n lost: Promise<void>\n stop(): Promise<void>\n}\n\ntype PumpResult = { wake?: Promise<void> }\n\ntype CronCursor = {\n checkedThrough: number\n}\n\nfunction jobEvent(\n event: string,\n row: JobRow,\n fields: Record<string, unknown> = {},\n) {\n return {\n source: 'bunderstack.jobs',\n event,\n jobId: row.id,\n jobType: row.type,\n attempt: Number(row.attempts),\n ...fields,\n }\n}\n\nfunction capacityFor(def: AnyBackgroundDefinition): number {\n return def.kind === 'job' && def.concurrency !== undefined\n ? def.concurrency\n : CLAIM_BATCH\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/** Queue jobs release terminal dedupe keys; cron slots retain durable ownership. */\nfunction terminalPatch(def: AnyBackgroundDefinition) {\n return def.kind === 'cron' ? {} : { dedupeKey: null }\n}\n\n/** `dedupeUntil: 'start'` releases the key at claim; everything else keeps it\n * until a terminal state, so the claim patch stays unchanged for them. */\nfunction claimPatch(def: AnyBackgroundDefinition) {\n return def.kind === 'job' && def.dedupeUntil === 'start'\n ? { dedupeKey: null }\n : {}\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 const active = new Map<string, Set<Promise<void>>>()\n const cronCursors = new Map<string, CronCursor>()\n\n function ownershipPredicate(id: string, owner: LeaseOwner) {\n return and(\n eq(t.id, id),\n eq(t.status, 'running'),\n eq(t.attempts, owner.attempt),\n eq(t.lockedUntil, owner.lockedUntil),\n )\n }\n\n function startLeaseHeartbeat(args: {\n row: JobRow\n def: AnyBackgroundDefinition\n owner: LeaseOwner\n onLost: () => void\n }): LeaseHeartbeat {\n const owner = args.owner\n const intervalMs = Math.max(10, Math.floor(leaseDurationFor(args.def) / 3))\n let stopped = false\n let resolveLost!: () => void\n const lost = new Promise<void>((resolve) => {\n resolveLost = resolve\n })\n let chain = Promise.resolve()\n const lose = () => {\n if (stopped) return\n stopped = true\n clearInterval(timer)\n resolveLost()\n args.onLost()\n }\n const renew = () => {\n chain = chain\n .then(async () => {\n if (stopped) return\n const nextLockedUntil = Date.now() + leaseDurationFor(args.def)\n const updated = await db\n .update(t)\n .set({ lockedUntil: nextLockedUntil })\n .where(ownershipPredicate(args.row.id, owner))\n .returning({ id: t.id })\n if (!updated[0]) return lose()\n owner.lockedUntil = nextLockedUntil\n })\n .catch(() => lose())\n }\n const timer = setInterval(renew, intervalMs)\n return {\n owner,\n lost,\n async stop() {\n stopped = true\n clearInterval(timer)\n await chain\n },\n }\n }\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 /** Hydrate each cron cursor once, then advance it in this worker's memory. */\n async function cronCursor(type: string, now: number): Promise<CronCursor> {\n const cached = cronCursors.get(type)\n if (cached) return cached\n\n const rows = await db\n .select({ runAt: t.runAt })\n .from(t)\n .where(eq(t.type, type))\n .orderBy(desc(t.runAt))\n .limit(1)\n const cursor = {\n checkedThrough:\n rows[0]?.runAt == null\n ? floorSlot(now) - SLOT_MS\n : Number(rows[0].runAt),\n }\n cronCursors.set(type, cursor)\n return cursor\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 const through = floorSlot(now)\n for (const [name, def] of Object.entries(defs)) {\n if (def.kind !== 'cron') continue\n const type = `${CRON_PREFIX}${name}`\n const cursor = await cronCursor(type, now)\n const slots = slotsDue({\n cron: parseCron(def.schedule),\n from: cursor.checkedThrough,\n to: through,\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 cursor.checkedThrough = slot\n }\n cursor.checkedThrough = Math.max(cursor.checkedThrough, through)\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 & {\n lastError: string | null\n lockedUntil: number\n })[] = 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 lockedUntil: t.lockedUntil,\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 owner = {\n attempt: Number(row.attempts),\n lockedUntil: Number(row.lockedUntil),\n }\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(ownershipPredicate(row.id, owner))\n continue\n }\n if (Number(row.attempts) >= maxAttempts(def)) {\n const updated = await db\n .update(t)\n .set({\n status: 'failed',\n finishedAt: now,\n lockedUntil: null,\n lastError: error.message,\n ...terminalPatch(def),\n })\n .where(ownershipPredicate(row.id, owner))\n .returning({ id: t.id })\n if (updated[0]) 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(ownershipPredicate(row.id, owner))\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 def: AnyBackgroundDefinition,\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 ...claimPatch(def),\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 async function claimAvailable(\n type: string,\n def: AnyBackgroundDefinition,\n now: number,\n ): Promise<ClaimedWork[]> {\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 let available = capacityFor(def) - runningRows.length\n if (available <= 0) return []\n\n const leaseUntil = now + leaseDurationFor(def)\n const work: ClaimedWork[] = []\n while (available > 0) {\n const limit = Math.min(CLAIM_BATCH, available)\n const rows = await claim(type, def, limit, now, leaseUntil)\n for (const row of rows) {\n work.push({\n row,\n def,\n owner: { attempt: Number(row.attempts), lockedUntil: leaseUntil },\n })\n }\n available -= rows.length\n if (rows.length < limit) break\n }\n return work\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 owner: LeaseOwner,\n ): Promise<'ran' | 'failed' | 'lost'> {\n const startedAt = Date.now()\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(def),\n })\n .where(ownershipPredicate(row.id, owner))\n .returning({ id: t.id })\n if (!updated[0]) return 'lost'\n await fireOnFailed(def, undefined, e)\n return 'failed'\n }\n const controller = new AbortController()\n const handlerCtx = { ...ctx, signal: controller.signal }\n logger.info(JSON.stringify(jobEvent('job.claimed', row)))\n let lost = false\n const heartbeat = startLeaseHeartbeat({\n row,\n def,\n owner,\n onLost: () => {\n lost = true\n controller.abort(new JobLeaseLostError())\n logger.warn(\n JSON.stringify(\n jobEvent('job.lease_lost', row, {\n durationMs: Date.now() - startedAt,\n }),\n ),\n )\n },\n })\n const deadline =\n def.maxRuntime === undefined\n ? undefined\n : setTimeout(() => {\n controller.abort(new JobExecutionTimeoutError(def.maxRuntime!))\n logger.warn(\n JSON.stringify(\n jobEvent('job.execution_timed_out', row, {\n durationMs: Date.now() - startedAt,\n }),\n ),\n )\n }, def.maxRuntime)\n try {\n await (def.handler as (i: unknown, c: unknown) => unknown)(\n input,\n handlerCtx,\n )\n if (controller.signal.reason instanceof JobExecutionTimeoutError) {\n throw controller.signal.reason\n }\n if (deadline) clearTimeout(deadline)\n await heartbeat.stop()\n if (lost) return 'lost'\n const updated = await db\n .update(t)\n .set({\n status: 'succeeded',\n finishedAt: Date.now(),\n lockedUntil: null,\n ...terminalPatch(def),\n })\n .where(ownershipPredicate(row.id, owner))\n .returning({ id: t.id })\n if (!updated[0]) return 'lost'\n logger.info(\n JSON.stringify(\n jobEvent('job.completed', row, {\n durationMs: Date.now() - startedAt,\n }),\n ),\n )\n return 'ran'\n } catch (err) {\n if (deadline) clearTimeout(deadline)\n const e =\n controller.signal.reason instanceof JobExecutionTimeoutError\n ? controller.signal.reason\n : toError(err)\n await heartbeat.stop()\n if (lost) return 'lost'\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(ownershipPredicate(row.id, owner))\n .returning({ id: t.id })\n if (!updated[0]) return 'lost'\n logger.warn(\n JSON.stringify(\n jobEvent('job.retrying', row, {\n durationMs: Date.now() - startedAt,\n error: e.message,\n }),\n ),\n )\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(def),\n })\n .where(ownershipPredicate(row.id, owner))\n .returning({ id: t.id })\n if (!updated[0]) return 'lost'\n logger.error(\n JSON.stringify(\n jobEvent('job.failed', row, {\n durationMs: Date.now() - startedAt,\n error: e.message,\n }),\n ),\n )\n await fireOnFailed(def, input, e)\n }\n return 'failed'\n }\n }\n\n async function runClaimable(now: number): Promise<TickResult> {\n const claimedWork: ClaimedWork[] = []\n let totalClaimed = 0\n for (const [name, def] of Object.entries(defs)) {\n const type = def.kind === 'cron' ? `${CRON_PREFIX}${name}` : name\n const claimed = await claimAvailable(type, def, now)\n totalClaimed += claimed.length\n claimedWork.push(...claimed)\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, owner }) => runJob(row, def, now, owner)),\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 async function maintain(now: number) {\n await materializeCronSlots(now)\n await recoverExpiredLeases(now)\n if (now - lastReapAt >= REAP_INTERVAL_MS) {\n lastReapAt = now\n await reapSucceeded(now)\n }\n }\n\n function startWork(\n type: string,\n work: ClaimedWork,\n claimedAt: number,\n ): Promise<void> {\n let tasks = active.get(type)\n if (!tasks) {\n tasks = new Set()\n active.set(type, tasks)\n }\n let task: Promise<void>\n task = runJob(work.row, work.def, claimedAt, work.owner)\n .then(() => undefined)\n .catch((error) => {\n logger.error('[bunderstack] background job execution failed:', error)\n })\n .finally(() => {\n tasks!.delete(task)\n if (tasks!.size === 0) active.delete(type)\n })\n tasks.add(task)\n return task\n }\n\n async function pump(now: number = Date.now()): Promise<PumpResult> {\n await maintain(now)\n for (const [name, def] of Object.entries(defs)) {\n const type = def.kind === 'cron' ? `${CRON_PREFIX}${name}` : name\n const work = await claimAvailable(type, def, now)\n for (const item of work) startWork(type, item, now)\n }\n const snapshot = [...active.values()].flatMap((tasks) => [...tasks])\n return snapshot.length === 0\n ? {}\n : { wake: Promise.race(snapshot).then(() => undefined) }\n }\n\n async function drain(): Promise<void> {\n const snapshot = [...active.values()].flatMap((tasks) => [...tasks])\n await Promise.allSettled(snapshot)\n }\n\n return {\n async tick(now: number = Date.now()): Promise<TickResult> {\n await maintain(now)\n return runClaimable(now)\n },\n pump,\n drain,\n /**\n * The earliest time that has work: a pending run_at, a running lease end,\n * or a cron slot not yet materialized. Never earlier than `now`; null when\n * nothing is due before `until`.\n */\n async nextDueAt(now: number, until: number): Promise<number | null> {\n const candidates: number[] = []\n const [pending] = await db\n .select({ at: sql<number | string | null>`min(${t.runAt})` })\n .from(t)\n .where(eq(t.status, 'pending'))\n if (pending?.at != null) candidates.push(Number(pending.at))\n const [running] = await db\n .select({ at: sql<number | string | null>`min(${t.lockedUntil})` })\n .from(t)\n .where(eq(t.status, 'running'))\n if (running?.at != null) candidates.push(Number(running.at))\n for (const [name, def] of Object.entries(defs)) {\n if (def.kind !== 'cron') continue\n const cursor = await cronCursor(`${CRON_PREFIX}${name}`, now)\n const slot = nextCronSlot(\n parseCron(def.schedule),\n cursor.checkedThrough,\n until,\n )\n if (slot !== null) candidates.push(slot)\n }\n if (candidates.length === 0) return null\n return Math.max(now, Math.min(...candidates))\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,25 @@
1
+ import type { RealtimePublisher } from './realtime/publisher.js';
2
+ import type { ResolvedBackend, ResolvedBucket } from './storage/buckets.js';
3
+ import type { StorageAdapter } from './storage/index.js';
4
+ export interface JobsPlatform {
5
+ /** A job became runnable at `runAt`. The host wakes a tick at that time. */
6
+ notify(runAt: number): void | Promise<void>;
7
+ }
8
+ export type RateLimitHit = {
9
+ allowed: boolean;
10
+ resetAt: number;
11
+ };
12
+ export interface RateLimitStore {
13
+ hit(key: string, windowMs: number, max: number, now?: number): Promise<RateLimitHit>;
14
+ }
15
+ export type StorageAdapterFactory = (backend: ResolvedBackend, bucket: ResolvedBucket) => StorageAdapter;
16
+ export interface Platform {
17
+ jobs: JobsPlatform;
18
+ /** Absent: the runtime uses a process-local memory publisher. */
19
+ realtime?: RealtimePublisher;
20
+ rateLimit: RateLimitStore;
21
+ storage: StorageAdapterFactory;
22
+ }
23
+ export declare function createMemoryRateLimitStore(): RateLimitStore;
24
+ export declare function resolvePlatform(partial?: Partial<Platform>): Platform;
25
+ //# sourceMappingURL=platform.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAIrD,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5C;AAED,MAAM,MAAM,YAAY,GAAG;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAA;AAEhE,MAAM,WAAW,cAAc;IAC7B,GAAG,CACD,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,MAAM,EACX,GAAG,CAAC,EAAE,MAAM,GACX,OAAO,CAAC,YAAY,CAAC,CAAA;CACzB;AAED,MAAM,MAAM,qBAAqB,GAAG,CAClC,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,cAAc,KACnB,cAAc,CAAA;AAEnB,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,YAAY,CAAA;IAClB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,iBAAiB,CAAA;IAC5B,SAAS,EAAE,cAAc,CAAA;IACzB,OAAO,EAAE,qBAAqB,CAAA;CAC/B;AAED,wBAAgB,0BAA0B,IAAI,cAAc,CAa3D;AAED,wBAAgB,eAAe,CAAC,OAAO,GAAE,OAAO,CAAC,QAAQ,CAAM,GAAG,QAAQ,CAOzE"}
@@ -0,0 +1,24 @@
1
+ import { createAdapter } from './storage/registry.js';
2
+ export function createMemoryRateLimitStore() {
3
+ const windows = new Map();
4
+ return {
5
+ async hit(key, windowMs, max, now = Date.now()) {
6
+ let window = windows.get(key);
7
+ if (!window || window.resetAt <= now) {
8
+ window = { count: 0, resetAt: now + windowMs };
9
+ windows.set(key, window);
10
+ }
11
+ window.count += 1;
12
+ return { allowed: window.count <= max, resetAt: window.resetAt };
13
+ },
14
+ };
15
+ }
16
+ export function resolvePlatform(partial = {}) {
17
+ return {
18
+ jobs: partial.jobs ?? { notify() { } },
19
+ realtime: partial.realtime,
20
+ rateLimit: partial.rateLimit ?? createMemoryRateLimitStore(),
21
+ storage: partial.storage ?? createAdapter,
22
+ };
23
+ }
24
+ //# sourceMappingURL=platform.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AA+BlD,MAAM,UAAU,0BAA0B;IACxC,MAAM,OAAO,GAAG,IAAI,GAAG,EAA8C,CAAA;IACrE,OAAO;QACL,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;YAC5C,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YAC7B,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,IAAI,GAAG,EAAE,CAAC;gBACrC,MAAM,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,QAAQ,EAAE,CAAA;gBAC9C,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;YAC1B,CAAC;YACD,MAAM,CAAC,KAAK,IAAI,CAAC,CAAA;YACjB,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,KAAK,IAAI,GAAG,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAA;QAClE,CAAC;KACF,CAAA;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,UAA6B,EAAE;IAC7D,OAAO;QACL,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,EAAE,MAAM,KAAI,CAAC,EAAE;QACrC,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,0BAA0B,EAAE;QAC5D,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,aAAa;KAC1C,CAAA;AACH,CAAC","sourcesContent":["// src/platform.ts — the services a host supplies to the core. A Worker gives\n// Durable Object backed ones; tests and Bun tooling get these defaults.\nimport type { RealtimePublisher } from './realtime/publisher'\nimport type { ResolvedBackend, ResolvedBucket } from './storage/buckets'\nimport type { StorageAdapter } from './storage/index'\n\nimport { createAdapter } from './storage/registry'\n\nexport interface JobsPlatform {\n /** A job became runnable at `runAt`. The host wakes a tick at that time. */\n notify(runAt: number): void | Promise<void>\n}\n\nexport type RateLimitHit = { allowed: boolean; resetAt: number }\n\nexport interface RateLimitStore {\n hit(\n key: string,\n windowMs: number,\n max: number,\n now?: number,\n ): Promise<RateLimitHit>\n}\n\nexport type StorageAdapterFactory = (\n backend: ResolvedBackend,\n bucket: ResolvedBucket,\n) => StorageAdapter\n\nexport interface Platform {\n jobs: JobsPlatform\n /** Absent: the runtime uses a process-local memory publisher. */\n realtime?: RealtimePublisher\n rateLimit: RateLimitStore\n storage: StorageAdapterFactory\n}\n\nexport function createMemoryRateLimitStore(): RateLimitStore {\n const windows = new Map<string, { count: number; resetAt: number }>()\n return {\n async hit(key, windowMs, max, now = Date.now()) {\n let window = windows.get(key)\n if (!window || window.resetAt <= now) {\n window = { count: 0, resetAt: now + windowMs }\n windows.set(key, window)\n }\n window.count += 1\n return { allowed: window.count <= max, resetAt: window.resetAt }\n },\n }\n}\n\nexport function resolvePlatform(partial: Partial<Platform> = {}): Platform {\n return {\n jobs: partial.jobs ?? { notify() {} },\n realtime: partial.realtime,\n rateLimit: partial.rateLimit ?? createMemoryRateLimitStore(),\n storage: partial.storage ?? createAdapter,\n }\n}\n"]}
@@ -1,7 +1,8 @@
1
+ import { type RateLimitStore } from './platform.js';
1
2
  export type RateLimitConfig = {
2
3
  windowMs?: number;
3
4
  max?: number;
4
5
  skip?: (req: Request) => boolean;
5
6
  };
6
- export declare function createRateLimiter(config: boolean | RateLimitConfig | undefined): (req: Request) => Promise<Response | null>;
7
+ export declare function createRateLimiter(config: boolean | RateLimitConfig | undefined, store?: RateLimitStore): (req: Request) => Promise<Response | null>;
7
8
  //# sourceMappingURL=rate-limit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAA;CACjC,CAAA;AAuBD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,GAC5C,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAyC5C"}
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8B,KAAK,cAAc,EAAE,MAAM,YAAY,CAAA;AAE5E,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAA;CACjC,CAAA;AAkBD,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,OAAO,GAAG,eAAe,GAAG,SAAS,EAC7C,KAAK,GAAE,cAA6C,GACnD,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAgC5C"}
@@ -1,4 +1,4 @@
1
- const buckets = new Map();
1
+ import { createMemoryRateLimitStore } from './platform.js';
2
2
  function resolveConfig(config) {
3
3
  if (!config)
4
4
  return null;
@@ -7,12 +7,15 @@ function resolveConfig(config) {
7
7
  return config;
8
8
  }
9
9
  function clientKey(req) {
10
+ const cloudflare = req.headers.get('cf-connecting-ip');
11
+ if (cloudflare)
12
+ return cloudflare;
10
13
  const forwarded = req.headers.get('x-forwarded-for');
11
14
  if (forwarded)
12
15
  return forwarded.split(',')[0].trim();
13
16
  return req.headers.get('x-real-ip') ?? 'local';
14
17
  }
15
- export function createRateLimiter(config) {
18
+ export function createRateLimiter(config, store = createMemoryRateLimitStore()) {
16
19
  const resolved = resolveConfig(config);
17
20
  if (!resolved) {
18
21
  return async (_req) => null;
@@ -24,26 +27,20 @@ export function createRateLimiter(config) {
24
27
  return null;
25
28
  const key = `${clientKey(req)}:${new URL(req.url).pathname}`;
26
29
  const now = Date.now();
27
- let bucket = buckets.get(key);
28
- if (!bucket || bucket.resetAt <= now) {
29
- bucket = { count: 0, resetAt: now + windowMs };
30
- buckets.set(key, bucket);
31
- }
32
- bucket.count += 1;
33
- if (bucket.count > max) {
34
- const retryAfter = Math.ceil((bucket.resetAt - now) / 1000);
35
- return new Response(JSON.stringify({
36
- error: 'Too many requests',
37
- code: 'TOO_MANY_REQUESTS',
38
- }), {
39
- status: 429,
40
- headers: {
41
- 'Content-Type': 'application/json',
42
- 'Retry-After': String(retryAfter),
43
- },
44
- });
45
- }
46
- return null;
30
+ const { allowed, resetAt } = await store.hit(key, windowMs, max, now);
31
+ if (allowed)
32
+ return null;
33
+ const retryAfter = Math.max(1, Math.ceil((resetAt - now) / 1000));
34
+ return new Response(JSON.stringify({
35
+ error: 'Too many requests',
36
+ code: 'TOO_MANY_REQUESTS',
37
+ }), {
38
+ status: 429,
39
+ headers: {
40
+ 'Content-Type': 'application/json',
41
+ 'Retry-After': String(retryAfter),
42
+ },
43
+ });
47
44
  };
48
45
  }
49
46
  //# sourceMappingURL=rate-limit.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAWA,MAAM,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAA;AAEzC,SAAS,aAAa,CACpB,MAA6C;IAE7C,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IACxB,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,EAAE,CAAA;IAC9B,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAS,SAAS,CAAC,GAAY;IAC7B,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;IACpD,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAA;IACrD,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,OAAO,CAAA;AAChD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,MAA6C;IAE7C,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;IACtC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,KAAK,EAAE,IAAa,EAAE,EAAE,CAAC,IAAI,CAAA;IACtC,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,IAAI,MAAM,CAAA;IAC5C,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,IAAI,GAAG,CAAA;IAE/B,OAAO,KAAK,EAAE,GAAY,EAA4B,EAAE;QACtD,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAA;QAErC,MAAM,GAAG,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAA;QAC5D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,IAAI,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAE7B,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,IAAI,GAAG,EAAE,CAAC;YACrC,MAAM,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,QAAQ,EAAE,CAAA;YAC9C,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;QAC1B,CAAC;QAED,MAAM,CAAC,KAAK,IAAI,CAAC,CAAA;QACjB,IAAI,MAAM,CAAC,KAAK,GAAG,GAAG,EAAE,CAAC;YACvB,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAA;YAC3D,OAAO,IAAI,QAAQ,CACjB,IAAI,CAAC,SAAS,CAAC;gBACb,KAAK,EAAE,mBAAmB;gBAC1B,IAAI,EAAE,mBAAmB;aAC1B,CAAC,EACF;gBACE,MAAM,EAAE,GAAG;gBACX,OAAO,EAAE;oBACP,cAAc,EAAE,kBAAkB;oBAClC,aAAa,EAAE,MAAM,CAAC,UAAU,CAAC;iBAClC;aACF,CACF,CAAA;QACH,CAAC;QAED,OAAO,IAAI,CAAA;IACb,CAAC,CAAA;AACH,CAAC","sourcesContent":["export type RateLimitConfig = {\n windowMs?: number\n max?: number\n skip?: (req: Request) => boolean\n}\n\ntype Bucket = {\n count: number\n resetAt: number\n}\n\nconst buckets = new Map<string, Bucket>()\n\nfunction resolveConfig(\n config: boolean | RateLimitConfig | undefined,\n): RateLimitConfig | null {\n if (!config) return null\n if (config === true) return {}\n return config\n}\n\nfunction clientKey(req: Request): string {\n const forwarded = req.headers.get('x-forwarded-for')\n if (forwarded) return forwarded.split(',')[0]!.trim()\n return req.headers.get('x-real-ip') ?? 'local'\n}\n\nexport function createRateLimiter(\n config: boolean | RateLimitConfig | undefined,\n): (req: Request) => Promise<Response | null> {\n const resolved = resolveConfig(config)\n if (!resolved) {\n return async (_req: Request) => null\n }\n\n const windowMs = resolved.windowMs ?? 60_000\n const max = resolved.max ?? 100\n\n return async (req: Request): Promise<Response | null> => {\n if (resolved.skip?.(req)) return null\n\n const key = `${clientKey(req)}:${new URL(req.url).pathname}`\n const now = Date.now()\n let bucket = buckets.get(key)\n\n if (!bucket || bucket.resetAt <= now) {\n bucket = { count: 0, resetAt: now + windowMs }\n buckets.set(key, bucket)\n }\n\n bucket.count += 1\n if (bucket.count > max) {\n const retryAfter = Math.ceil((bucket.resetAt - now) / 1000)\n return new Response(\n JSON.stringify({\n error: 'Too many requests',\n code: 'TOO_MANY_REQUESTS',\n }),\n {\n status: 429,\n headers: {\n 'Content-Type': 'application/json',\n 'Retry-After': String(retryAfter),\n },\n },\n )\n }\n\n return null\n }\n}\n"]}
1
+ {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,0BAA0B,EAAuB,MAAM,YAAY,CAAA;AAQ5E,SAAS,aAAa,CACpB,MAA6C;IAE7C,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IACxB,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,EAAE,CAAA;IAC9B,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAS,SAAS,CAAC,GAAY;IAC7B,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;IACtD,IAAI,UAAU;QAAE,OAAO,UAAU,CAAA;IACjC,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAA;IACpD,IAAI,SAAS;QAAE,OAAO,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAA;IACrD,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,OAAO,CAAA;AAChD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,MAA6C,EAC7C,QAAwB,0BAA0B,EAAE;IAEpD,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;IACtC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,KAAK,EAAE,IAAa,EAAE,EAAE,CAAC,IAAI,CAAA;IACtC,CAAC;IAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,IAAI,MAAM,CAAA;IAC5C,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,IAAI,GAAG,CAAA;IAE/B,OAAO,KAAK,EAAE,GAAY,EAA4B,EAAE;QACtD,IAAI,QAAQ,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC;YAAE,OAAO,IAAI,CAAA;QAErC,MAAM,GAAG,GAAG,GAAG,SAAS,CAAC,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAA;QAC5D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QACtB,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,CAAC,CAAA;QACrE,IAAI,OAAO;YAAE,OAAO,IAAI,CAAA;QAExB,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAC,CAAA;QACjE,OAAO,IAAI,QAAQ,CACjB,IAAI,CAAC,SAAS,CAAC;YACb,KAAK,EAAE,mBAAmB;YAC1B,IAAI,EAAE,mBAAmB;SAC1B,CAAC,EACF;YACE,MAAM,EAAE,GAAG;YACX,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,aAAa,EAAE,MAAM,CAAC,UAAU,CAAC;aAClC;SACF,CACF,CAAA;IACH,CAAC,CAAA;AACH,CAAC","sourcesContent":["import { createMemoryRateLimitStore, type RateLimitStore } from './platform'\n\nexport type RateLimitConfig = {\n windowMs?: number\n max?: number\n skip?: (req: Request) => boolean\n}\n\nfunction resolveConfig(\n config: boolean | RateLimitConfig | undefined,\n): RateLimitConfig | null {\n if (!config) return null\n if (config === true) return {}\n return config\n}\n\nfunction clientKey(req: Request): string {\n const cloudflare = req.headers.get('cf-connecting-ip')\n if (cloudflare) return cloudflare\n const forwarded = req.headers.get('x-forwarded-for')\n if (forwarded) return forwarded.split(',')[0]!.trim()\n return req.headers.get('x-real-ip') ?? 'local'\n}\n\nexport function createRateLimiter(\n config: boolean | RateLimitConfig | undefined,\n store: RateLimitStore = createMemoryRateLimitStore(),\n): (req: Request) => Promise<Response | null> {\n const resolved = resolveConfig(config)\n if (!resolved) {\n return async (_req: Request) => null\n }\n\n const windowMs = resolved.windowMs ?? 60_000\n const max = resolved.max ?? 100\n\n return async (req: Request): Promise<Response | null> => {\n if (resolved.skip?.(req)) return null\n\n const key = `${clientKey(req)}:${new URL(req.url).pathname}`\n const now = Date.now()\n const { allowed, resetAt } = await store.hit(key, windowMs, max, now)\n if (allowed) return null\n\n const retryAfter = Math.max(1, Math.ceil((resetAt - now) / 1000))\n return new Response(\n JSON.stringify({\n error: 'Too many requests',\n code: 'TOO_MANY_REQUESTS',\n }),\n {\n status: 429,\n headers: {\n 'Content-Type': 'application/json',\n 'Retry-After': String(retryAfter),\n },\n },\n )\n }\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { type InferSelectModel, type Table } from 'drizzle-orm';
2
2
  import type { RealtimeAction, RealtimePublisher } from './publisher.js';
3
- export type RealtimeTransport = 'disabled' | 'memory' | 'redis';
3
+ export type RealtimeTransport = 'disabled' | 'memory' | 'platform';
4
4
  export type SchemaTable<TSchema extends Record<string, unknown>> = Extract<TSchema[keyof TSchema], Table>;
5
5
  export interface RealtimeFacade<TSchema extends Record<string, unknown> = Record<string, unknown>> {
6
6
  readonly enabled: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"facade.d.ts","sourceRoot":"","sources":["../../src/realtime/facade.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,KAAK,EACX,MAAM,aAAa,CAAA;AAEpB,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEpE,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAA;AAE/D,MAAM,MAAM,WAAW,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,OAAO,CACxE,OAAO,CAAC,MAAM,OAAO,CAAC,EACtB,KAAK,CACN,CAAA;AAED,MAAM,WAAW,cAAc,CAC7B,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAEjE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAA;IAErC,OAAO,CAAC,MAAM,SAAS,WAAW,CAAC,OAAO,CAAC,EACzC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAChC,QAAQ,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAClC,OAAO,CAAC,IAAI,CAAC,CAAA;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,SAAS,CAAC,EAAE,iBAAiB,EAC7B,SAAS,GAAE,iBAAqD,EAChE,MAAM,CAAC,EAAE,OAAO,GACf,cAAc,CAAC,OAAO,CAAC,CA+BzB"}
1
+ {"version":3,"file":"facade.d.ts","sourceRoot":"","sources":["../../src/realtime/facade.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,gBAAgB,EACrB,KAAK,KAAK,EACX,MAAM,aAAa,CAAA;AAEpB,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAEpE,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,QAAQ,GAAG,UAAU,CAAA;AAElE,MAAM,MAAM,WAAW,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,OAAO,CACxE,OAAO,CAAC,MAAM,OAAO,CAAC,EACtB,KAAK,CACN,CAAA;AAED,MAAM,WAAW,cAAc,CAC7B,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAEjE,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,SAAS,EAAE,iBAAiB,CAAA;IAErC,OAAO,CAAC,MAAM,SAAS,WAAW,CAAC,OAAO,CAAC,EACzC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,cAAc,EACtB,MAAM,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAChC,QAAQ,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAClC,OAAO,CAAC,IAAI,CAAC,CAAA;CACjB;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC1E,SAAS,CAAC,EAAE,iBAAiB,EAC7B,SAAS,GAAE,iBAAqD,EAChE,MAAM,CAAC,EAAE,OAAO,GACf,cAAc,CAAC,OAAO,CAAC,CA+BzB"}
@@ -1 +1 @@
1
- {"version":3,"file":"facade.js","sourceRoot":"","sources":["../../src/realtime/facade.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,OAAO,GAGR,MAAM,aAAa,CAAA;AAyBpB;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,SAA6B,EAC7B,YAA+B,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,EAChE,MAAgB;IAEhB,IAAI,CAAC,SAAS,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CACb,kEAAkE,CACnE,CAAA;IACH,CAAC;IACD,IAAI,SAAS,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CACb,sEAAsE,CACvE,CAAA;IACH,CAAC;IAED,MAAM,cAAc,GAAG,IAAI,GAAG,EAAkB,CAAA;IAChD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;QACxD,IAAI,OAAO,CAAC,KAAK,CAAC;YAAE,cAAc,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAA;IAClE,CAAC;IAED,OAAO;QACL,OAAO,EAAE,SAAS,KAAK,SAAS;QAChC,SAAS;QACT,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ;YAC3C,IAAI,CAAC,SAAS;gBAAE,OAAM;YACtB,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,CAAA;YACrC,MAAM,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE;gBAChC,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,SAAS;gBACjD,MAAM;gBACN,MAAM,EAAE,MAA4C;gBACpD,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxE,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["import {\n getTableName,\n isTable,\n type InferSelectModel,\n type Table,\n} from 'drizzle-orm'\n\nimport type { RealtimeAction, RealtimePublisher } from './publisher'\n\nexport type RealtimeTransport = 'disabled' | 'memory' | 'redis'\n\nexport type SchemaTable<TSchema extends Record<string, unknown>> = Extract<\n TSchema[keyof TSchema],\n Table\n>\n\nexport interface RealtimeFacade<\n TSchema extends Record<string, unknown> = Record<string, unknown>,\n> {\n readonly enabled: boolean\n readonly transport: RealtimeTransport\n\n publish<TTable extends SchemaTable<TSchema>>(\n table: TTable,\n action: RealtimeAction,\n record: InferSelectModel<TTable>,\n metadata?: { operationId?: string },\n ): Promise<void>\n}\n\n/**\n * One name for a table everywhere: events, subscriptions, and the CRUD router\n * all use the schema key. Pass the schema so a key like `creditBalances` is not\n * published as its SQL name `credit_balances` — clients subscribe with the key\n * they call procedures with. Without a schema, the SQL name is the only name\n * available and is used as-is.\n */\nexport function createRealtimeFacade<TSchema extends Record<string, unknown>>(\n publisher?: RealtimePublisher,\n transport: RealtimeTransport = publisher ? 'memory' : 'disabled',\n schema?: TSchema,\n): RealtimeFacade<TSchema> {\n if (!publisher && transport !== 'disabled') {\n throw new Error(\n '[bunderstack] an enabled realtime transport requires a publisher',\n )\n }\n if (publisher && transport === 'disabled') {\n throw new Error(\n '[bunderstack] a realtime publisher cannot use the disabled transport',\n )\n }\n\n const keyByTableName = new Map<string, string>()\n for (const [key, value] of Object.entries(schema ?? {})) {\n if (isTable(value)) keyByTableName.set(getTableName(value), key)\n }\n\n return {\n enabled: publisher !== undefined,\n transport,\n async publish(table, action, record, metadata) {\n if (!publisher) return\n const tableName = getTableName(table)\n await publisher.publish('change', {\n table: keyByTableName.get(tableName) ?? tableName,\n action,\n record: record as unknown as Record<string, unknown>,\n ...(metadata?.operationId ? { operationId: metadata.operationId } : {}),\n })\n },\n }\n}\n"]}
1
+ {"version":3,"file":"facade.js","sourceRoot":"","sources":["../../src/realtime/facade.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,OAAO,GAGR,MAAM,aAAa,CAAA;AAyBpB;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAClC,SAA6B,EAC7B,YAA+B,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,UAAU,EAChE,MAAgB;IAEhB,IAAI,CAAC,SAAS,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;QAC3C,MAAM,IAAI,KAAK,CACb,kEAAkE,CACnE,CAAA;IACH,CAAC;IACD,IAAI,SAAS,IAAI,SAAS,KAAK,UAAU,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CACb,sEAAsE,CACvE,CAAA;IACH,CAAC;IAED,MAAM,cAAc,GAAG,IAAI,GAAG,EAAkB,CAAA;IAChD,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC,EAAE,CAAC;QACxD,IAAI,OAAO,CAAC,KAAK,CAAC;YAAE,cAAc,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAA;IAClE,CAAC;IAED,OAAO;QACL,OAAO,EAAE,SAAS,KAAK,SAAS;QAChC,SAAS;QACT,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ;YAC3C,IAAI,CAAC,SAAS;gBAAE,OAAM;YACtB,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,CAAA;YACrC,MAAM,SAAS,CAAC,OAAO,CAAC,QAAQ,EAAE;gBAChC,KAAK,EAAE,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,SAAS;gBACjD,MAAM;gBACN,MAAM,EAAE,MAA4C;gBACpD,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxE,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC","sourcesContent":["import {\n getTableName,\n isTable,\n type InferSelectModel,\n type Table,\n} from 'drizzle-orm'\n\nimport type { RealtimeAction, RealtimePublisher } from './publisher'\n\nexport type RealtimeTransport = 'disabled' | 'memory' | 'platform'\n\nexport type SchemaTable<TSchema extends Record<string, unknown>> = Extract<\n TSchema[keyof TSchema],\n Table\n>\n\nexport interface RealtimeFacade<\n TSchema extends Record<string, unknown> = Record<string, unknown>,\n> {\n readonly enabled: boolean\n readonly transport: RealtimeTransport\n\n publish<TTable extends SchemaTable<TSchema>>(\n table: TTable,\n action: RealtimeAction,\n record: InferSelectModel<TTable>,\n metadata?: { operationId?: string },\n ): Promise<void>\n}\n\n/**\n * One name for a table everywhere: events, subscriptions, and the CRUD router\n * all use the schema key. Pass the schema so a key like `creditBalances` is not\n * published as its SQL name `credit_balances` — clients subscribe with the key\n * they call procedures with. Without a schema, the SQL name is the only name\n * available and is used as-is.\n */\nexport function createRealtimeFacade<TSchema extends Record<string, unknown>>(\n publisher?: RealtimePublisher,\n transport: RealtimeTransport = publisher ? 'memory' : 'disabled',\n schema?: TSchema,\n): RealtimeFacade<TSchema> {\n if (!publisher && transport !== 'disabled') {\n throw new Error(\n '[bunderstack] an enabled realtime transport requires a publisher',\n )\n }\n if (publisher && transport === 'disabled') {\n throw new Error(\n '[bunderstack] a realtime publisher cannot use the disabled transport',\n )\n }\n\n const keyByTableName = new Map<string, string>()\n for (const [key, value] of Object.entries(schema ?? {})) {\n if (isTable(value)) keyByTableName.set(getTableName(value), key)\n }\n\n return {\n enabled: publisher !== undefined,\n transport,\n async publish(table, action, record, metadata) {\n if (!publisher) return\n const tableName = getTableName(table)\n await publisher.publish('change', {\n table: keyByTableName.get(tableName) ?? tableName,\n action,\n record: record as unknown as Record<string, unknown>,\n ...(metadata?.operationId ? { operationId: metadata.operationId } : {}),\n })\n },\n }\n}\n"]}
@@ -1,5 +1,4 @@
1
1
  import type { Publisher } from '@orpc/publisher';
2
- import type { RedisClient } from 'bun';
3
2
  export type RealtimeAction = 'create' | 'update' | 'delete';
4
3
  export interface RealtimeChange {
5
4
  table: string;
@@ -17,7 +16,4 @@ export interface RealtimePublisherOptions {
17
16
  resumeSeconds?: number;
18
17
  }
19
18
  export declare function createMemoryRealtimePublisher(options?: RealtimePublisherOptions): RealtimePublisher;
20
- export declare function createRedisRealtimePublisher(redis: RedisClient, subscriber: RedisClient | Promise<RedisClient>, options?: RealtimePublisherOptions & {
21
- prefix?: string;
22
- }): RealtimePublisher;
23
19
  //# sourceMappingURL=publisher.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"publisher.d.ts","sourceRoot":"","sources":["../../src/realtime/publisher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAA;AAKtC,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAE3D,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,cAAc,CAAA;IACtB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,cAAe,SAAQ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAC5D,MAAM,EAAE,cAAc,CAAA;CACvB;AAED,MAAM,MAAM,iBAAiB,GAAG,SAAS,CAAC,cAAc,CAAC,CAAA;AAEzD,MAAM,WAAW,wBAAwB;IACvC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,wBAAgB,6BAA6B,CAC3C,OAAO,GAAE,wBAA6B,GACrC,iBAAiB,CAKnB;AAED,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,WAAW,EAClB,UAAU,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,EAC9C,OAAO,GAAE,wBAAwB,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC3D,iBAAiB,CAOnB"}
1
+ {"version":3,"file":"publisher.d.ts","sourceRoot":"","sources":["../../src/realtime/publisher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAIhD,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAA;AAE3D,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,cAAc,CAAA;IACtB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,cAAe,SAAQ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAC5D,MAAM,EAAE,cAAc,CAAA;CACvB;AAED,MAAM,MAAM,iBAAiB,GAAG,SAAS,CAAC,cAAc,CAAC,CAAA;AAEzD,MAAM,WAAW,wBAAwB;IACvC,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,wBAAgB,6BAA6B,CAC3C,OAAO,GAAE,wBAA6B,GACrC,iBAAiB,CAKnB"}
@@ -1,4 +1,3 @@
1
- import { BunRedisPublisher } from '@orpc/bun';
2
1
  import { MemoryPublisher } from '@orpc/publisher/memory';
3
2
  export function createMemoryRealtimePublisher(options = {}) {
4
3
  return new MemoryPublisher({
@@ -6,12 +5,4 @@ export function createMemoryRealtimePublisher(options = {}) {
6
5
  resume: { enabled: true, seconds: options.resumeSeconds ?? 300 },
7
6
  });
8
7
  }
9
- export function createRedisRealtimePublisher(redis, subscriber, options = {}) {
10
- return new BunRedisPublisher(redis, {
11
- subscriber,
12
- prefix: options.prefix ?? 'bunderstack:',
13
- maxBufferedEvents: options.maxBufferedEvents,
14
- resume: { enabled: true, seconds: options.resumeSeconds ?? 300 },
15
- });
16
- }
17
8
  //# sourceMappingURL=publisher.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"publisher.js","sourceRoot":"","sources":["../../src/realtime/publisher.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAuBxD,MAAM,UAAU,6BAA6B,CAC3C,UAAoC,EAAE;IAEtC,OAAO,IAAI,eAAe,CAAiB;QACzC,iBAAiB,EAAE,OAAO,CAAC,iBAAiB;QAC5C,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,IAAI,GAAG,EAAE;KACjE,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,UAAU,4BAA4B,CAC1C,KAAkB,EAClB,UAA8C,EAC9C,UAA0D,EAAE;IAE5D,OAAO,IAAI,iBAAiB,CAAiB,KAAK,EAAE;QAClD,UAAU;QACV,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,cAAc;QACxC,iBAAiB,EAAE,OAAO,CAAC,iBAAiB;QAC5C,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,IAAI,GAAG,EAAE;KACjE,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import type { Publisher } from '@orpc/publisher'\nimport type { RedisClient } from 'bun'\n\nimport { BunRedisPublisher } from '@orpc/bun'\nimport { MemoryPublisher } from '@orpc/publisher/memory'\n\nexport type RealtimeAction = 'create' | 'update' | 'delete'\n\nexport interface RealtimeChange {\n table: string\n action: RealtimeAction\n record: Record<string, unknown>\n /** Correlates this confirmed write with the client operation that caused it. */\n operationId?: string\n}\n\nexport interface RealtimeEvents extends Record<string, object> {\n change: RealtimeChange\n}\n\nexport type RealtimePublisher = Publisher<RealtimeEvents>\n\nexport interface RealtimePublisherOptions {\n maxBufferedEvents?: number\n resumeSeconds?: number\n}\n\nexport function createMemoryRealtimePublisher(\n options: RealtimePublisherOptions = {},\n): RealtimePublisher {\n return new MemoryPublisher<RealtimeEvents>({\n maxBufferedEvents: options.maxBufferedEvents,\n resume: { enabled: true, seconds: options.resumeSeconds ?? 300 },\n })\n}\n\nexport function createRedisRealtimePublisher(\n redis: RedisClient,\n subscriber: RedisClient | Promise<RedisClient>,\n options: RealtimePublisherOptions & { prefix?: string } = {},\n): RealtimePublisher {\n return new BunRedisPublisher<RealtimeEvents>(redis, {\n subscriber,\n prefix: options.prefix ?? 'bunderstack:',\n maxBufferedEvents: options.maxBufferedEvents,\n resume: { enabled: true, seconds: options.resumeSeconds ?? 300 },\n })\n}\n"]}
1
+ {"version":3,"file":"publisher.js","sourceRoot":"","sources":["../../src/realtime/publisher.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAA;AAuBxD,MAAM,UAAU,6BAA6B,CAC3C,UAAoC,EAAE;IAEtC,OAAO,IAAI,eAAe,CAAiB;QACzC,iBAAiB,EAAE,OAAO,CAAC,iBAAiB;QAC5C,MAAM,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,IAAI,GAAG,EAAE;KACjE,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import type { Publisher } from '@orpc/publisher'\n\nimport { MemoryPublisher } from '@orpc/publisher/memory'\n\nexport type RealtimeAction = 'create' | 'update' | 'delete'\n\nexport interface RealtimeChange {\n table: string\n action: RealtimeAction\n record: Record<string, unknown>\n /** Correlates this confirmed write with the client operation that caused it. */\n operationId?: string\n}\n\nexport interface RealtimeEvents extends Record<string, object> {\n change: RealtimeChange\n}\n\nexport type RealtimePublisher = Publisher<RealtimeEvents>\n\nexport interface RealtimePublisherOptions {\n maxBufferedEvents?: number\n resumeSeconds?: number\n}\n\nexport function createMemoryRealtimePublisher(\n options: RealtimePublisherOptions = {},\n): RealtimePublisher {\n return new MemoryPublisher<RealtimeEvents>({\n maxBufferedEvents: options.maxBufferedEvents,\n resume: { enabled: true, seconds: options.resumeSeconds ?? 300 },\n })\n}\n"]}
package/dist/runtime.d.ts CHANGED
@@ -7,7 +7,7 @@ import type { CrudApiRouterFor, MergeApiRouterTypes, UnifiedApiRouter } from './
7
7
  import type { RuntimeTestingHandle } from './backend-internals.js';
8
8
  import type { DatabaseConnection } from './database/adapter.js';
9
9
  import type { DbFor } from './db.js';
10
- import type { BunderstackJobsBuilder, JobsDefs, JobsFacade, StartWorkerOptions, WorkerHandle } from './jobs/index.js';
10
+ import type { BunderstackJobsBuilder, JobsDefs, JobsFacade } from './jobs/index.js';
11
11
  import type { MessagingConfig, MessagingFacadesFor } from './messaging/index.js';
12
12
  import type { MessagingAdapter } from './messaging/runtime.js';
13
13
  import type { ResolvedStorageBuckets, StorageConfigInput } from './storage/buckets.js';
@@ -17,6 +17,7 @@ import { type BetterAuthConfig, type BunderstackConfig } from './config.js';
17
17
  import { type EnvConfigInput, type ValidatedEnv } from './env.js';
18
18
  import { type LifecycleStatus } from './lifecycle.js';
19
19
  import { type BunderstackLogger } from './logging.js';
20
+ import { type Platform } from './platform.js';
20
21
  import { type RealtimeFacade } from './realtime/facade.js';
21
22
  /** Base Better Auth contract used by framework internals and API context. */
22
23
  export type AuthInstance = Auth;
@@ -24,10 +25,10 @@ export type RuntimeOverrides = {
24
25
  database?: DatabaseConnection;
25
26
  resolvedStorage?: ResolvedStorageBuckets;
26
27
  messagingAdapters?: Record<string, MessagingAdapter>;
27
- forceMemoryRealtime?: boolean;
28
- backgroundAutoStart?: false;
29
28
  authResolver?: AuthSessionResolver;
30
29
  logger?: BunderstackLogger;
30
+ /** Host services; see src/platform.ts. */
31
+ platform?: Partial<Platform>;
31
32
  /** Private callback used by backend.test(); never exposed on the app. */
32
33
  captureTestingHandle?: (handle: RuntimeTestingHandle) => void;
33
34
  };
@@ -65,16 +66,6 @@ export interface StorageFacade {
65
66
  filename?: string;
66
67
  }): Promise<void>;
67
68
  }
68
- export type AppStartWorkerOptions = Omit<StartWorkerOptions, 'tick' | 'drain'>;
69
- export type AppRunWorkerOptions = AppStartWorkerOptions & {
70
- /**
71
- * Permit process-local realtime in a standalone worker.
72
- *
73
- * Use only when job handlers never call ctx.realtime.publish(). Publications
74
- * made through the memory broker cannot reach SSE clients in another process.
75
- */
76
- allowProcessLocalRealtime?: boolean;
77
- };
78
69
  /** Bucket names declared in a storage config; `string` when unknowable. */
79
70
  export type BucketNamesOf<TStorage> = TStorage extends {
80
71
  buckets: infer B extends Record<string, unknown>;
@@ -92,12 +83,7 @@ export type BunderstackApp<TSchema extends Record<string, unknown>, TAccess exte
92
83
  jobs: JobsFacade<TJobsDefs extends JobsDefs ? TJobsDefs : Record<never, never>, TSchema>;
93
84
  /** Typed custom row publication; enabled=false/no-op when realtime is off. */
94
85
  realtime: RealtimeFacade<TSchema>;
95
- startWorker(options?: AppStartWorkerOptions): Promise<WorkerHandle>;
96
- /** Run a queue worker until aborted, then close the application. */
97
- runWorker(options?: AppRunWorkerOptions): Promise<void>;
98
86
  close(): Promise<void>;
99
- /** True when this process is running the background tick loop. */
100
- readonly backgroundRunning: boolean;
101
87
  readonly status: LifecycleStatus;
102
88
  readonly signal: AbortSignal;
103
89
  /**
@@ -134,7 +120,7 @@ export { buildManifest, parseManifest } from './manifest.js';
134
120
  export type { BunderstackManifest, ManifestEnvVar } from './manifest.js';
135
121
  export type { EmailMessage, EmailAdapter } from './email.js';
136
122
  export { createJobsBuilder } from './jobs/index.js';
137
- export type { BunderstackJobContext, BunderstackJobsBuilder, BackgroundDefinition, BackgroundDefs, CronDefinition, CronInvocation, DedupeUntil, EnqueueOptions, EnqueueTransaction, JobContext, JobDefinition, JobsDefs, JobsFacade, JobsRuntimeFacade, QueueJobDefinition, QueueJobKeys, TypedEnqueueOptions, RunWorkerOptions, StartWorkerOptions, WorkerHandle, } from './jobs/index.js';
123
+ export type { BunderstackJobContext, BunderstackJobsBuilder, BackgroundDefinition, BackgroundDefs, CronDefinition, CronInvocation, DedupeUntil, EnqueueOptions, EnqueueTransaction, JobContext, JobDefinition, JobsDefs, JobsFacade, JobsRuntimeFacade, QueueJobDefinition, QueueJobKeys, TypedEnqueueOptions, } from './jobs/index.js';
138
124
  export { defineSessionUser, defineAccess, validateAndResolveAccess, checkAccess, AUTH_TABLE_NAMES, } from './access.js';
139
125
  export type { TableAccessInput, OperationRule, AccessContext, AccessUser, AccessUserBase, SessionUserConfig, SessionUserExtraOf, SessionUserSource, } from './access.js';
140
126
  export { typeid, generate as generateTypeId, parse as parseTypeId, asTypeId, } from './typeid.js';