@rebasepro/server 0.16.0 → 0.16.1-canary.g2d1aec8
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/dist/api/contract-routes.d.ts +1 -1
- package/dist/api/errors.d.ts +1 -1
- package/dist/api/index.d.ts +3 -3
- package/dist/api/live-schema-routes.d.ts +111 -0
- package/dist/api/logs-routes.d.ts +1 -1
- package/dist/api/mount.d.ts +41 -0
- package/dist/api/rest/api-generator.d.ts +2 -2
- package/dist/api/rest/index.d.ts +1 -1
- package/dist/api/rest/query-parser.d.ts +1 -1
- package/dist/api/schema-editor-routes.d.ts +1 -1
- package/dist/api/types.d.ts +3 -4
- package/dist/{schema-editor-routes-CV9k0w3G.js → ast-schema-editor-BpFYpT38.js} +2 -39
- package/dist/ast-schema-editor-BpFYpT38.js.map +1 -0
- package/dist/auth/adapter-middleware.d.ts +2 -2
- package/dist/auth/admin-roles-route.d.ts +2 -2
- package/dist/auth/admin-user-ops.d.ts +3 -3
- package/dist/auth/admin-users-route.d.ts +4 -4
- package/dist/auth/api-keys/api-key-middleware.d.ts +2 -2
- package/dist/auth/api-keys/api-key-permission-guard.d.ts +1 -1
- package/dist/auth/api-keys/api-key-routes.d.ts +2 -2
- package/dist/auth/api-keys/api-key-store.d.ts +1 -1
- package/dist/auth/api-keys/index.d.ts +9 -9
- package/dist/auth/apple-oauth.d.ts +2 -2
- package/dist/auth/auth-hooks.d.ts +3 -3
- package/dist/auth/bitbucket-oauth.d.ts +2 -2
- package/dist/auth/builtin-auth-adapter.d.ts +7 -4
- package/dist/auth/captcha.d.ts +86 -0
- package/dist/auth/cookie-utils.d.ts +2 -2
- package/dist/auth/discord-oauth.d.ts +2 -2
- package/dist/auth/facebook-oauth.d.ts +2 -2
- package/dist/auth/github-oauth.d.ts +2 -2
- package/dist/auth/gitlab-oauth.d.ts +2 -2
- package/dist/auth/google-oauth.d.ts +1 -1
- package/dist/auth/index.d.ts +56 -54
- package/dist/auth/jwks-routes.d.ts +1 -1
- package/dist/auth/jwt.d.ts +1 -1
- package/dist/auth/linkedin-oauth.d.ts +2 -2
- package/dist/auth/magic-link-routes.d.ts +10 -3
- package/dist/auth/mfa-gate.d.ts +1 -1
- package/dist/auth/mfa-routes.d.ts +3 -3
- package/dist/auth/microsoft-oauth.d.ts +2 -2
- package/dist/auth/middleware.d.ts +5 -5
- package/dist/auth/rate-limiter.d.ts +2 -2
- package/dist/auth/require-auth.d.ts +1 -1
- package/dist/auth/reset-password-admin.d.ts +4 -4
- package/dist/auth/routes.d.ts +13 -4
- package/dist/auth/session-routes.d.ts +3 -3
- package/dist/auth/slack-oauth.d.ts +2 -2
- package/dist/auth/spotify-oauth.d.ts +2 -2
- package/dist/auth/token-revocation.d.ts +2 -2
- package/dist/auth/twitter-oauth.d.ts +2 -2
- package/dist/{auth-5Et5mnUA.js → auth-C74dqR2B.js} +250 -15
- package/dist/auth-C74dqR2B.js.map +1 -0
- package/dist/backup/backup-common.d.ts +1 -1
- package/dist/backup/backup-routes.d.ts +3 -3
- package/dist/backup/index.d.ts +3 -3
- package/dist/{backup-C6ljYVTp.js → backup-BJ86ah4T.js} +2 -2
- package/dist/{backup-C6ljYVTp.js.map → backup-BJ86ah4T.js.map} +1 -1
- package/dist/boot/boot.d.ts +5 -5
- package/dist/boot/bundle.d.ts +1 -1
- package/dist/boot/driver.d.ts +1 -1
- package/dist/boot/env.d.ts +13 -1
- package/dist/boot/options.d.ts +30 -6
- package/dist/boot/role.d.ts +3 -2
- package/dist/boot/sources.d.ts +1 -1
- package/dist/collections/BackendCollectionRegistry.d.ts +1 -1
- package/dist/collections/loader.d.ts +1 -1
- package/dist/{contract-routes-DZ-LBpSL.js → contract-routes-BEq7euZg.js} +2 -2
- package/dist/contract-routes-BEq7euZg.js.map +1 -0
- package/dist/cron/cron-routes.d.ts +2 -2
- package/dist/cron/cron-scheduler.d.ts +2 -2
- package/dist/cron/index.d.ts +8 -8
- package/dist/{cron-loader-YhhQeVBM.js → cron-loader-BMvtW6-J.js} +2 -2
- package/dist/{cron-loader-YhhQeVBM.js.map → cron-loader-BMvtW6-J.js.map} +1 -1
- package/dist/{cron-routes-maM_RlUu.js → cron-routes-BvYk-Kmi.js} +2 -2
- package/dist/{cron-routes-maM_RlUu.js.map → cron-routes-BvYk-Kmi.js.map} +1 -1
- package/dist/{cron-scheduler-DIpYBmZP.js → cron-scheduler-Buf-uVam.js} +2 -2
- package/dist/{cron-scheduler-DIpYBmZP.js.map → cron-scheduler-Buf-uVam.js.map} +1 -1
- package/dist/{cron-store-DfH_4Cd9.js → cron-store-CyF-oInV.js} +3 -3
- package/dist/{cron-store-DfH_4Cd9.js.map → cron-store-CyF-oInV.js.map} +1 -1
- package/dist/{ddl-bootstrap-Cywoj8Ta.js → ddl-bootstrap-DgSnERYN.js} +17 -3
- package/dist/ddl-bootstrap-DgSnERYN.js.map +1 -0
- package/dist/dev-secrets.d.ts +52 -0
- package/dist/email/dev-sink.d.ts +67 -0
- package/dist/email/index.d.ts +9 -7
- package/dist/email/link-base.d.ts +1 -1
- package/dist/email/smtp-email-service.d.ts +1 -1
- package/dist/env.d.ts +1 -1
- package/dist/{errors-EBYiaJ2E.js → errors-D_LwNKRM.js} +5 -5
- package/dist/errors-D_LwNKRM.js.map +1 -0
- package/dist/{function-loader-DDS1v7YX.js → function-loader-BrLmC_-y.js} +3 -3
- package/dist/{function-loader-DDS1v7YX.js.map → function-loader-BrLmC_-y.js.map} +1 -1
- package/dist/functions/context.d.ts +141 -0
- package/dist/functions/define-function.d.ts +1 -1
- package/dist/functions/function-routes.d.ts +2 -2
- package/dist/functions/guards.d.ts +76 -0
- package/dist/functions/index.d.ts +96 -5
- package/dist/functions/index.js +919 -0
- package/dist/functions/index.js.map +1 -0
- package/dist/functions/internal.d.ts +26 -0
- package/dist/functions/proxy.d.ts +1 -1
- package/dist/functions/request-timeout.d.ts +9 -1
- package/dist/functions/runtime-env.d.ts +92 -0
- package/dist/functions/wait-until.d.ts +74 -0
- package/dist/history/history-routes.d.ts +2 -2
- package/dist/history/index.d.ts +1 -1
- package/dist/index.d.ts +63 -54
- package/dist/index.es.js +2183 -164
- package/dist/index.es.js.map +1 -1
- package/dist/init/docs.d.ts +1 -1
- package/dist/init/middlewares.d.ts +1 -1
- package/dist/init/shutdown.d.ts +4 -0
- package/dist/init/storage.d.ts +1 -1
- package/dist/init/surfaces.d.ts +10 -0
- package/dist/init.d.ts +104 -18
- package/dist/jobs/index.d.ts +5 -5
- package/dist/{jobs-CyOKXXlu.js → jobs-Bn6nhY8J.js} +3 -3
- package/dist/{jobs-CyOKXXlu.js.map → jobs-Bn6nhY8J.js.map} +1 -1
- package/dist/{jwt-DxH9fLPt.js → jwt-BbJi0TR0.js} +2 -2
- package/dist/{jwt-DxH9fLPt.js.map → jwt-BbJi0TR0.js.map} +1 -1
- package/dist/{logger-DfvF_8r-.js → logger-TdvXIGqR.js} +101 -8
- package/dist/logger-TdvXIGqR.js.map +1 -0
- package/dist/metrics/index.d.ts +1 -1
- package/dist/{proxy-Bj5DVllb.js → proxy-QJKSS-CV.js} +5 -3
- package/dist/{proxy-Bj5DVllb.js.map → proxy-QJKSS-CV.js.map} +1 -1
- package/dist/{request-timeout-BuFoEKwT.js → request-timeout-OofPCHQT.js} +17 -3
- package/dist/request-timeout-OofPCHQT.js.map +1 -0
- package/dist/rls-audit/index.d.ts +111 -0
- package/dist/schema-edit/apply-schema-change.d.ts +141 -0
- package/dist/schema-edit/github-repository.d.ts +67 -0
- package/dist/schema-edit/local-git-repository.d.ts +28 -0
- package/dist/schema-edit/project-root.d.ts +29 -0
- package/dist/schema-edit/remote-source.d.ts +25 -0
- package/dist/schema-edit/schema-edit-permissions.d.ts +129 -0
- package/dist/schema-editor-routes-yh2cQcdJ.js +44 -0
- package/dist/schema-editor-routes-yh2cQcdJ.js.map +1 -0
- package/dist/services/webhook-service.d.ts +1 -1
- package/dist/singleton.d.ts +7 -0
- package/dist/storage/GCSStorageController.d.ts +1 -1
- package/dist/storage/LocalStorageController.d.ts +1 -1
- package/dist/storage/S3StorageController.d.ts +1 -1
- package/dist/storage/cache-headers.d.ts +87 -0
- package/dist/storage/index.d.ts +11 -11
- package/dist/storage/policies.d.ts +88 -0
- package/dist/storage/range.d.ts +63 -0
- package/dist/storage/routes.d.ts +3 -3
- package/dist/storage/storage-registry.d.ts +1 -1
- package/dist/storage/tus-handler.d.ts +2 -2
- package/dist/utils/host.d.ts +58 -0
- package/dist/utils/logger.d.ts +0 -15
- package/dist/utils/request-id.d.ts +1 -1
- package/functions/package.json +24 -0
- package/package.json +13 -7
- package/dist/auth-5Et5mnUA.js.map +0 -1
- package/dist/contract-routes-DZ-LBpSL.js.map +0 -1
- package/dist/ddl-bootstrap-Cywoj8Ta.js.map +0 -1
- package/dist/errors-EBYiaJ2E.js.map +0 -1
- package/dist/logger-DfvF_8r-.js.map +0 -1
- package/dist/request-timeout-BuFoEKwT.js.map +0 -1
- package/dist/schema-editor-routes-CV9k0w3G.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/utils/host.ts","../../src/singleton.ts","../../src/functions/define-function.ts","../../src/auth/admin-roles.ts","../../src/functions/context.ts","../../src/functions/guards.ts","../../src/functions/runtime-env.ts","../../src/utils/logger.ts","../../src/functions/wait-until.ts","../../src/api/errors.ts"],"sourcesContent":["/**\n * The host globals this package reads, behind functions that do not assume Node.\n *\n * `process` is not defined on workerd, on Deno Deploy without the compat flag,\n * or in a browser. That matters here for one specific reason: the portable\n * authoring surface (`@rebasepro/server/functions`) reaches the logger and the\n * error handler, and a bare `process.env.NODE_ENV` inside either of them turns\n * the first log line of a request into a `ReferenceError` on a runtime that has\n * no `process` — a failure that reads as \"the framework crashed\" rather than\n * \"this runtime has no process object\".\n *\n * Nothing here throws and nothing here is async. A runtime that cannot answer\n * gets the empty answer, because every caller in this file's blast radius is\n * choosing a log level or a format, and the safe default for both is the\n * development one.\n *\n * @module\n */\n\n/**\n * Where an adapter with no `process` can publish the environment.\n *\n * Cloudflare Workers hand the environment to the *request*, not to the module,\n * so there is no global to read at import time. An edge adapter that has\n * already seen a request can stash the bag here and every contextless reader in\n * the framework — the logger, chiefly — starts answering correctly.\n *\n * `Symbol.for` rather than a module-local for the same reason the singleton\n * uses it: more than one copy of this module can be loaded into one process,\n * and a module-local would leave every copy but the writer's blind. See\n * `../singleton.ts`.\n *\n * Request-scoped code should NOT read this. Use `getEnv(c)` from\n * `@rebasepro/server/functions`, which reads the binding attached to the\n * request it is serving — the only correct source on a runtime where two\n * concurrent requests can carry different bindings.\n */\nconst ENV_SLOT = Symbol.for(\"@rebasepro/server:host-env\");\n\ntype GlobalWithEnv = typeof globalThis & {\n [ENV_SLOT]?: Record<string, string | undefined>;\n process?: { env?: Record<string, string | undefined> };\n};\n\n/**\n * The process environment, or the closest thing this runtime has to one.\n *\n * Order: a bag published by {@link setHostEnv} first, because an adapter that\n * set one knows more than the ambient globals do; then `process.env`; then\n * nothing.\n */\nexport function hostEnv(): Record<string, string | undefined> {\n const global = globalThis as GlobalWithEnv;\n return global[ENV_SLOT] ?? global.process?.env ?? {};\n}\n\n/**\n * Read one environment variable without touching `process` directly.\n *\n * Trimmed, and blank is treated as absent — a variable declared with no value\n * is the ordinary way to write a compose file or a `.env` line, and every\n * caller in this package means \"unset\" by it. See `resolveFunctionsTimeoutMs`,\n * which learned that the hard way.\n */\nexport function hostEnvVar(name: string): string | undefined {\n const raw = hostEnv()[name];\n if (typeof raw !== \"string\") return undefined;\n const trimmed = raw.trim();\n return trimmed === \"\" ? undefined : trimmed;\n}\n\n/**\n * Publish an environment bag for contextless readers.\n *\n * Called by an adapter for a runtime whose environment is not ambient. Merges\n * rather than replaces, so two adapters (or an adapter plus a test) do not\n * silently erase each other's variables.\n */\nexport function setHostEnv(env: Record<string, string | undefined>): void {\n const global = globalThis as GlobalWithEnv;\n global[ENV_SLOT] = {\n ...(global[ENV_SLOT] ?? {}),\n ...env\n };\n}\n\n/** @internal Test seam — drops anything {@link setHostEnv} published. */\nexport function _clearHostEnv(): void {\n delete (globalThis as GlobalWithEnv)[ENV_SLOT];\n}\n\ntype GlobalWithStdio = typeof globalThis & {\n process?: {\n stdout?: { write?: (chunk: string) => unknown };\n stderr?: { write?: (chunk: string) => unknown };\n };\n};\n\n/**\n * Write one already-formatted line to the process's output.\n *\n * `process.stdout.write` is preferred where it exists because it is the only\n * one of the two that does not append its own formatting to a line that is\n * already a complete JSON document — `console.log` on Node is\n * `process.stdout.write` plus `util.format`, and `util.format` will happily\n * reinterpret a `%s` that appeared inside a user's log message.\n *\n * Where it does not exist, `console` is the runtime's log sink and is what its\n * platform collects.\n */\nexport function writeLine(stream: \"out\" | \"err\", line: string): void {\n const proc = (globalThis as GlobalWithStdio).process;\n const sink = stream === \"err\" ? proc?.stderr : proc?.stdout;\n if (typeof sink?.write === \"function\") {\n sink.write(line + \"\\n\");\n return;\n }\n if (stream === \"err\") console.error(line);\n else console.log(line);\n}\n","import type { RebaseServerClient } from \"@rebasepro/types\";\nimport { hostEnv } from \"./utils/host\";\n\n/**\n * The backing instance lives on a process-global slot, NOT in a module-local\n * variable — because more than one copy of this module can be loaded into one\n * process, and a module-local would leave every copy but the booting one dead.\n *\n * That is the normal layout under the managed runtime, not an edge case: the\n * image ships the framework at `/app/node_modules`, while a project's bundle\n * installs its own dependencies into `/bundle/node_modules` — and every custom\n * function imports `defineFunction` from `@rebasepro/server`, which resolves to\n * the bundle's transitively-installed copy. `initializeRebaseBackend()` then ran\n * against `/app`'s copy while every function held `/bundle`'s, so `rebase.data`,\n * `rebase.storage` and `rebase.dataAsAdmin` threw \"server not initialized yet\"\n * on EVERY request, forever, in an otherwise healthy process.\n *\n * `Symbol.for` is the fix because its registry is per-process rather than\n * per-module: whichever copy boots publishes here, and every other copy — same\n * version or not — reads the same live client.\n */\nconst INSTANCE_SLOT = Symbol.for(\"@rebasepro/server:singleton-instance\");\n\n/**\n * A *function* that answers \"which client is this call for\", for hosts where\n * one answer per process is the wrong shape.\n *\n * A Node server boots once and serves every request from the same client, so\n * {@link INSTANCE_SLOT} is the whole story. An isolate-based host is different\n * in a way that matters: the environment arrives attached to the **request**,\n * not to the module, so there is nothing to publish at import time, and an\n * isolate can be reused across requests that must not share state. Such a host\n * registers a resolver — typically reading an `AsyncLocalStorage` populated per\n * request — and it is consulted first.\n *\n * This exists now, before there is a host that needs it, because of what it\n * protects: `rebase` is a lazy Proxy, so every property access already goes\n * through `getInstance()`. Backing that lookup with a resolver is therefore the\n * entire porting story for the singleton — **no function anyone has already\n * written changes**. Take the resolver away and the only alternative is asking\n * users to thread a client through their handlers, which is a rewrite of every\n * function file in existence.\n */\nconst RESOLVER_SLOT = Symbol.for(\"@rebasepro/server:singleton-resolver\");\n\ntype GlobalWithInstance = typeof globalThis & {\n [INSTANCE_SLOT]?: RebaseServerClient | null;\n [RESOLVER_SLOT]?: (() => RebaseServerClient | null) | null;\n};\n\nfunction getInstance(): RebaseServerClient | null {\n const global = globalThis as GlobalWithInstance;\n // Resolver first: a host that registered one knows something per-request\n // that a process-wide slot cannot express, and a host that boots normally\n // never registers one.\n const resolver = global[RESOLVER_SLOT];\n if (resolver) {\n const resolved = resolver();\n if (resolved) return resolved;\n }\n return global[INSTANCE_SLOT] ?? null;\n}\n\nfunction setInstance(client: RebaseServerClient | null): void {\n (globalThis as GlobalWithInstance)[INSTANCE_SLOT] = client;\n}\n\n/**\n * @internal Register the per-call resolver described on {@link RESOLVER_SLOT}.\n *\n * For runtime adapters, not for application code. Pass `null` to unregister.\n * Returns the previous resolver so an adapter can restore it.\n */\nexport function _setRebaseResolver(\n resolve: (() => RebaseServerClient | null) | null\n): (() => RebaseServerClient | null) | null {\n const global = globalThis as GlobalWithInstance;\n const previous = global[RESOLVER_SLOT] ?? null;\n global[RESOLVER_SLOT] = resolve;\n return previous;\n}\n\n/**\n * @internal Called once during server initialization to set the backing instance.\n * This is invoked by `initializeRebaseBackend()` — never call it manually.\n */\nexport function _initRebase(client: RebaseServerClient): void {\n setInstance(client);\n}\n\n/**\n * @internal Allows overriding the underlying instance for unit testing.\n * Throws an error if used in a non-test environment to prevent production abuse.\n */\nexport function _setRebaseMock(mockInstance: Partial<RebaseServerClient>): void {\n if (hostEnv().NODE_ENV !== \"test\") {\n throw new Error(\"_setRebaseMock can only be called in a test environment (NODE_ENV=test).\");\n }\n setInstance({ ...(getInstance() || {} as RebaseServerClient),\n...mockInstance } as RebaseServerClient);\n}\n\n/**\n * @internal Resets the singleton instance, useful for afterEach() in test suites.\n */\nexport function _resetRebaseMock(): void {\n if (hostEnv().NODE_ENV !== \"test\") {\n throw new Error(\"_resetRebaseMock can only be called in a test environment.\");\n }\n setInstance(null);\n}\n\n/**\n * The server-side Rebase singleton.\n *\n * Initialized automatically during server startup. Provides access to all\n * app-scoped services: **data**, **auth**, **storage**, and **email**.\n *\n * **Admin data plane** (`rebase.dataAsAdmin`):\n * Backed by the native DataDriver — calls go directly to the database without\n * JSON serialization, HTTP dispatch, or middleware overhead. The driver is\n * scoped once as `{ uid: \"service\", roles: [\"admin\"] }` (`SERVICE_IDENTITY`),\n * which makes it **admin-scoped, not RLS-bypassing**: every read and write runs\n * in a transaction that has done `SET LOCAL ROLE rebase_user` with\n * `app.uid = 'service'`, and policies are evaluated against that. No\n * `REBASE_SERVICE_KEY` is required.\n *\n * It clears the default policies through their `rolesOverlap(['admin'])` arm —\n * which is why the difference rarely shows. It shows when you write your own:\n *\n * - `policy.serverContext()` compiles to `rebase.uid() IS NULL` and is therefore\n * **false** for this accessor. A collection with `disableDefaultPolicies:\n * true` whose rule is `serverContext()` denies these writes (`42501`) and\n * returns zero rows — HTTP 200, empty — for these reads.\n * - Its reach equals an `admin`-roled application user's reach. It is not a\n * private door.\n *\n * `rebase.sql()` *is* an unconditional bypass — it runs on the owner connection\n * and never goes through `withAuth`. Of the two accessors on this object, the\n * quieter one is the more privileged.\n *\n * ⚠️ `rebase.dataAsAdmin` is for trusted background work (cron jobs,\n * migrations, service tasks) — **not** for serving user-facing data. Inside a\n * request handler, run user-scoped queries through the request-scoped driver\n * (`c.var.driver`), which carries the caller's identity.\n *\n * `rebase.data` is **gone from the type**: `RebaseServerClient` omits it, so the\n * admin-scoped accessor has exactly one name and the privilege is visible at the\n * call site. The property still exists at runtime, aliasing `dataAsAdmin`, so an\n * untyped JavaScript caller keeps working rather than failing on `undefined`.\n *\n * **Control plane** (`rebase.auth`, `rebase.admin`, `rebase.storage`, etc.):\n * Routes through the Hono app's internal request handler. An internal per-boot\n * credential is generated automatically when `REBASE_SERVICE_KEY` is not set,\n * so control-plane calls always authenticate.\n *\n * @example\n * ```typescript\n * import { rebase } from \"@rebasepro/server\";\n *\n * // In a cron job, hook, or trusted service file (admin scope, RLS evaluated\n * // as `{ uid: \"service\", roles: [\"admin\"] }`):\n * await rebase.email.send({ to: \"admin@co.com\", subject: \"Alert\", html: \"<p>Hi</p>\" });\n * const jobs = await rebase.dataAsAdmin.jobs.find({ limit: 10 });\n * ```\n */\nexport const rebase: RebaseServerClient = new Proxy({} as RebaseServerClient, {\n get(_, prop) {\n const instance = getInstance();\n if (!instance) {\n throw new Error(\n `rebase.${String(prop)}: server not initialized yet. ` +\n \"The singleton is available after Rebase starts — don't call it at import time.\"\n );\n }\n return instance[prop as keyof RebaseServerClient];\n },\n set(_, prop) {\n throw new Error(\n `Cannot set rebase.${String(prop)} directly. ` +\n \"The singleton is read-only. Use _initRebase() during server startup.\"\n );\n }\n});\n","import { Hono } from \"hono\";\nimport type { RebaseServerClient } from \"@rebasepro/types\";\nimport type { HonoEnv } from \"../api/types\";\nimport { rebase } from \"../singleton\";\n\n/**\n * Typed context injected into a function authored with {@link defineFunction}.\n *\n * Surfaces the app-scoped Rebase singleton so handlers don't need to reach\n * for the global `rebase` import. Request-scoped values (the authenticated\n * `user`, the RLS-scoped `driver`, the `apiKey`, the `requestId`) are typed\n * on the Hono context via {@link HonoEnv} — read them with `c.get(\"user\")`\n * / `c.var.driver` inside a handler.\n */\nexport interface RebaseFunctionContext {\n /**\n * The server-side Rebase singleton (`dataAsAdmin`, `auth`, `storage`,\n * `email`, `sql`).\n *\n * `rebase.dataAsAdmin` runs as the service identity\n * `{ uid: \"service\", roles: [\"admin\"] }` — **admin-scoped, not an RLS\n * bypass**. Policies are still evaluated; it passes the default ones\n * through their `rolesOverlap(['admin'])` arm, the same arm an application\n * user holding the `admin` role passes. Two things follow:\n *\n * - `policy.serverContext()` (`rebase.uid() IS NULL`) is **false** here. A\n * collection with `disableDefaultPolicies: true` whose write rule is\n * `serverContext()` will refuse these writes with `42501`, and reads\n * against a hand-written admin policy that does not name the `admin` role\n * return zero rows with a 200.\n * - Do not read it as \"nobody else can reach these rows\". Whatever an\n * `admin`-roled user can reach, this can, and vice versa.\n *\n * `rebase.sql()` is the true bypass: it runs on the owner connection and\n * never goes through `withAuth`.\n *\n * For user-scoped queries inside a handler, use the request `driver`\n * (`c.var.driver`), which carries the caller's identity. (`rebase.data` no\n * longer exists on this type — `dataAsAdmin` is the only name for the\n * admin-scoped accessor.)\n */\n rebase: RebaseServerClient;\n}\n\n/**\n * Typed authoring contract for a custom backend function.\n *\n * A custom function is a file in the `functionsDir` that default-exports a\n * Hono app; the loader mounts it at `/<filename>`. `defineFunction` is the\n * typed opt-in for that contract: it hands you a pre-typed `Hono<HonoEnv>`\n * app (so `c.var.user` / `c.var.driver` are typed) plus a\n * {@link RebaseFunctionContext}, and returns exactly the Hono app the loader\n * already accepts — so it is fully interchangeable with a plain\n * `export default new Hono()`.\n *\n * @example\n * ```ts\n * import { defineFunction, requireAuth } from \"@rebasepro/server\";\n *\n * export default defineFunction((app, { rebase }) => {\n * app.use(\"/*\", requireAuth);\n * app.get(\"/home\", async (c) => {\n * // `rebase.sql` runs on the owner connection: no RLS, no policies,\n * // every row. It is the most privileged thing in this context —\n * // more so than `dataAsAdmin`, which is merely admin-scoped.\n * const [stats] = await rebase.sql(`SELECT count(*) AS n FROM orders`);\n * return c.json({ orders: Number(stats.n) });\n * });\n * });\n * ```\n *\n * @param definition Receives the function's Hono app and the typed context.\n * Register routes on the provided `app` and return nothing, or return your\n * own `Hono<HonoEnv>` app to use instead.\n * @returns The Hono app to default-export from the function file.\n */\nexport function defineFunction(\n definition: (app: Hono<HonoEnv>, ctx: RebaseFunctionContext) => void | Hono<HonoEnv>\n): Hono<HonoEnv> {\n const app = new Hono<HonoEnv>();\n const returned = definition(app, { rebase });\n return returned instanceof Hono ? returned : app;\n}\n","/**\n * Which role names carry administrative privilege.\n *\n * One definition, because there used to be several and they disagreed.\n * `requireAdmin` accepted `admin` **or** `schema-admin`; the guard that refuses\n * a dangerous `defaultRole` compared against `admin` alone. So\n * `AUTH_DEFAULT_ROLE=schema-admin` passed the guard and made every public\n * registrant an administrator — and, since a `schema-admin` may edit users, one\n * of them could then grant themselves real `admin` and keep it.\n *\n * The guard and the check have to read the same list or the gap comes back, so\n * neither of them owns it.\n *\n * `schema-admin` is here deliberately: it can reach the schema editor and the\n * SQL surfaces, which is administrative by any definition that matters. If a\n * genuinely lesser role is added later, it does not belong in this list — it\n * belongs in a capability check of its own.\n */\nexport const ADMINISTRATIVE_ROLES = [\"admin\", \"schema-admin\"] as const;\n\nexport type AdministrativeRole = (typeof ADMINISTRATIVE_ROLES)[number];\n\n/** Does this single role name carry administrative privilege? */\nexport function isAdministrativeRole(role: string): role is AdministrativeRole {\n return (ADMINISTRATIVE_ROLES as readonly string[]).includes(role);\n}\n\n/** Does any role in this list carry administrative privilege? */\nexport function hasAdministrativeRole(roles: readonly string[] | null | undefined): boolean {\n return !!roles?.some(isAdministrativeRole);\n}\n","/**\n * Reading the request context from inside a custom function.\n *\n * The functions router resolves the caller's identity before any handler runs\n * and leaves the result on the Hono context. Getting it back out used to be the\n * user's problem, and the shape made that worse than it sounds: `HonoEnv`\n * types `user` as `AuthResult`, a union that includes `boolean`, `null` and an\n * index signature, because the same slot is filled by four different middlewares\n * — JWT, service key, API key, and a user-supplied validator that may return\n * `true`. Every example in the documentation therefore opened with\n *\n * const user = c.get(\"user\") as { uid: string; roles?: string[] } | undefined;\n *\n * and an assertion in a security-relevant position is exactly the kind of line\n * that gets copied once and then never re-examined. It is also wrong in one\n * case that occurs in practice: a custom validator returning `true` stores\n * `{ uid: \"default\", roles: [] }`, which the assertion above types as having a\n * `uid` — true here, but nothing checks it.\n *\n * These accessors do the narrowing once, in the framework, where it can be\n * tested. They are also **runtime-neutral by construction** — no crypto, no\n * token parsing, no I/O, nothing but property reads on an object another\n * middleware already populated. That is what lets them live in\n * `@rebasepro/server/functions` and run unchanged on a host that has no Node\n * built-ins.\n *\n * @module\n */\nimport type { Context } from \"hono\";\nimport type { DataDriver } from \"@rebasepro/types\";\nimport type { HonoEnv } from \"../api/types\";\nimport type { ApiKeyMasked } from \"../auth/api-keys/api-key-types\";\nimport { hasAdministrativeRole } from \"../auth/admin-roles\";\n\n/**\n * The caller, as a custom function sees them.\n *\n * A narrowed view of whatever the auth middleware resolved: `uid` and `roles`\n * are guaranteed, and the index signature keeps any extra claims the token or\n * the adapter carried (`email`, `org_id`, anything a custom validator added)\n * reachable without a cast.\n */\nexport interface FunctionUser {\n /** Stable id of the caller. `\"service\"` for service-key and API-key callers. */\n uid: string;\n /** Roles as resolved for this request. Never `undefined` — an empty array instead. */\n roles: string[];\n /** Present when the identity carried one. Not every auth method does. */\n email?: string;\n /** Any further claim the token, adapter or validator supplied. */\n [claim: string]: unknown;\n}\n\n/** Anything with a Hono-style `.get`, so these work on any `Context` shape. */\ntype CtxLike = Context<HonoEnv> | Context;\n\nfunction read<K extends keyof HonoEnv[\"Variables\"]>(\n c: CtxLike,\n key: K\n): HonoEnv[\"Variables\"][K] | undefined {\n // `c.get` is typed against the app's own Env, which a handler mounted\n // through `app.route()` may have declared more loosely. The cast is\n // confined to this one function rather than repeated at every call site.\n return (c as Context<HonoEnv>).get(key);\n}\n\n/**\n * The authenticated caller, or `undefined` for an anonymous request.\n *\n * **`undefined` is not a permission decision.** The functions router mounts its\n * auth middleware with `requireAuth: false` on purpose — a webhook receiver has\n * no token to send — so an anonymous caller reaches the handler and reads\n * `undefined` here while the handler runs on regardless. Use {@link requireAuth}\n * (or a `!user` branch that returns 401) to make it a decision.\n *\n * A caller who presented a *bad* token never gets this far: both auth\n * middlewares reject an unverifiable token with 401 before the router is\n * reached, precisely so an expired session cannot be silently downgraded to an\n * anonymous one.\n */\nexport function getUser(c: CtxLike): FunctionUser | undefined {\n const raw = read(c, \"user\");\n if (!raw || typeof raw !== \"object\") return undefined;\n\n const record = raw as Record<string, unknown>;\n // `userId` is the older spelling, still accepted from custom validators —\n // `createAuthMiddleware` normalises it on the way in, but a handler mounted\n // outside that path may not have been through it.\n const uid = typeof record.uid === \"string\"\n ? record.uid\n : typeof record.userId === \"string\" ? record.userId : undefined;\n if (uid === undefined) return undefined;\n\n const roles = Array.isArray(record.roles)\n ? record.roles.filter((role): role is string => typeof role === \"string\")\n : [];\n\n return { ...record,\n uid,\n roles } as FunctionUser;\n}\n\n/** The caller's id, or `undefined` when nobody is signed in. */\nexport function getUserId(c: CtxLike): string | undefined {\n return getUser(c)?.uid;\n}\n\n/** The caller's roles. Empty for an anonymous request — never `undefined`. */\nexport function getRoles(c: CtxLike): string[] {\n return getUser(c)?.roles ?? [];\n}\n\n/**\n * Whether the caller holds **any** of the named roles.\n *\n * Any rather than all, because that is what a route guard means by a list of\n * roles; require several by calling this more than once.\n */\nexport function hasRole(c: CtxLike, ...roles: string[]): boolean {\n if (roles.length === 0) return false;\n const held = new Set(getRoles(c));\n return roles.some(role => held.has(role));\n}\n\n/**\n * Whether the caller holds an administrative role.\n *\n * Delegates to the single definition in `auth/admin-roles.ts` — which is\n * `admin` **or** `schema-admin` — rather than comparing against `\"admin\"`.\n * Those two lists disagreed once, and the gap made every public registrant an\n * administrator; see that file.\n */\nexport function isAdmin(c: CtxLike): boolean {\n return hasAdministrativeRole(getRoles(c));\n}\n\n/** Whether the request carries an identity at all. */\nexport function isAuthenticated(c: CtxLike): boolean {\n return getUser(c) !== undefined;\n}\n\n/**\n * The request-scoped data driver: reads and writes run as **the caller**, with\n * your row-level security policies evaluated against their identity.\n *\n * This is the accessor to reach for when a function serves user-facing data.\n * `rebase.dataAsAdmin` is the other one, and it is not the same thing — it runs\n * as `{ uid: \"service\", roles: [\"admin\"] }` for every caller alike, which is\n * correct for trusted background work and wrong for a request.\n *\n * `undefined` only when no Rebase auth middleware ran (see\n * {@link identityResolved}); inside a function mounted by the framework it is\n * always present, anonymous requests included — they get an anon-scoped driver\n * so policies still have an identity to evaluate.\n */\nexport function getDriver(c: CtxLike): DataDriver | undefined {\n return read(c, \"driver\");\n}\n\n/**\n * {@link getDriver}, but throws instead of handing back `undefined`.\n *\n * For the common case where a handler cannot proceed without it and would\n * otherwise write `c.get(\"driver\")!` — an assertion that turns a wiring problem\n * into `Cannot read properties of undefined (reading 'fetchCollection')` twenty\n * lines away from the cause.\n */\nexport function requireDriver(c: CtxLike): DataDriver {\n const driver = getDriver(c);\n if (!driver) {\n throw new Error(\n \"No request-scoped driver on this context. A Rebase auth middleware \" +\n \"populates it before any custom function runs, so this means the handler \" +\n \"was mounted outside the functions router — e.g. added to your own Hono \" +\n \"app directly. Mount it from the functions directory, or use \" +\n \"`rebase.dataAsAdmin` if the work is genuinely service-scoped.\"\n );\n }\n return driver;\n}\n\n/**\n * The API key this request authenticated with, masked, or `undefined` when it\n * did not use one.\n *\n * Useful for attribution and for per-key behaviour. The permission check itself\n * has already happened — reaching a handler means the key was allowed to.\n */\nexport function getApiKey(c: CtxLike): ApiKeyMasked | undefined {\n return read(c, \"apiKey\");\n}\n\n/**\n * The correlation id for this request — generated, or taken from an inbound\n * `X-Request-ID`.\n *\n * Log it. It is the only thing that ties a line written inside a function to\n * the framework's own lines for the same request.\n */\nexport function getRequestId(c: CtxLike): string | undefined {\n return read(c, \"requestId\");\n}\n\n/**\n * Whether a Rebase auth middleware has run on this request.\n *\n * Both middlewares populate `driver` for *every* outcome, anonymous included,\n * and populate `user` whenever there is one. So \"neither is set\" does not mean\n * \"anonymous\" — it means nothing resolved the identity, and treating that as\n * anonymous is the dangerous reading. The guards use this to tell a genuinely\n * anonymous caller (401) from a misconfigured mount (500), because answering\n * 401 to the second sends whoever is debugging it to look at the token.\n */\nexport function identityResolved(c: CtxLike): boolean {\n return read(c, \"user\") !== undefined || read(c, \"driver\") !== undefined;\n}\n","/**\n * Route guards for custom functions.\n *\n * These decide access from the identity the platform already resolved. They do\n * **not** verify tokens, and that division is the point rather than a\n * limitation:\n *\n * - Verifying a token needs a signing key, constant-time comparison and a\n * revocation lookup. That is host work, it belongs to the process that holds\n * the secret, and it is the part of the stack that cannot be made\n * runtime-neutral without rewriting it against WebCrypto.\n * - Deciding whether *this* caller may call *this* route is application work.\n * It needs nothing but the resolved identity, so it costs nothing to make it\n * portable — and it is the half that lives in user code.\n *\n * Splitting there is what lets a function file compile and run unchanged on a\n * host with no Node built-ins, and it is why these live in\n * `@rebasepro/server/functions` while `verifyAccessToken` does not.\n *\n * **Inside the functions router these are equivalent to the guards exported\n * from the package root.** Both auth middlewares resolve the identity before\n * any handler runs: a valid credential populates `user`, an invalid one is\n * rejected with 401 by the middleware itself, and a missing one leaves `user`\n * unset. So the root `requireAuth`'s token-parsing branch is unreachable from a\n * function, and removing it changes no outcome. The one difference is a handler\n * mounted **outside** the framework's router, where no middleware ran: the root\n * guard would parse the `Authorization` header itself, and these refuse the\n * request with a 500 that names the wiring problem. Fail-closed, and legible.\n *\n * @module\n */\nimport type { MiddlewareHandler } from \"hono\";\nimport type { HonoEnv } from \"../api/types\";\nimport { getUser, isAdmin, getRoles, identityResolved } from \"./context\";\n\n/**\n * The answer to \"a guard ran, but no middleware had resolved anything\".\n *\n * Deliberately a 500 and not a 401. A 401 tells the caller their credential is\n * the problem, and here the caller's credential was never looked at — sending\n * them to check their token is sending them to the one place the answer is not.\n */\nfunction unresolvedIdentity(): { error: { message: string; code: string } } {\n return {\n error: {\n message:\n \"This route's identity was never resolved: no Rebase auth middleware ran \" +\n \"before the guard. A function loaded from the functions directory always \" +\n \"has one. This usually means the Hono app was mounted onto your own \" +\n \"server directly, bypassing the functions router.\",\n code: \"AUTH_MIDDLEWARE_MISSING\"\n }\n };\n}\n\n/**\n * Reject anonymous callers with 401.\n *\n * Put it in the route's own middleware slot rather than `app.use(\"/*\", …)`:\n * `use()` covers only the routes declared *below* it, so a route appended later\n * — by you, months from now, at the bottom of the file — is silently\n * unprotected. The per-route form cannot drift that way.\n *\n * @example\n * ```ts\n * app.post(\"/\", requireAuth, async (c) => {\n * const user = getUser(c)!; // guaranteed by the guard\n * return c.json({ uid: user.uid });\n * });\n * ```\n */\nexport const requireAuth: MiddlewareHandler<HonoEnv> = async (c, next) => {\n if (getUser(c)) return next();\n if (!identityResolved(c)) return c.json(unresolvedIdentity(), 500);\n\n return c.json({\n error: {\n message: \"Authentication required\",\n code: \"UNAUTHORIZED\"\n }\n }, 401);\n};\n\n/**\n * Reject callers without an administrative role with 403.\n *\n * Must come **after** {@link requireAuth}: on its own it answers 401 for an\n * anonymous caller, which is right, but pairing them keeps the two failures\n * distinguishable — 401 \"who are you\", 403 \"not you\".\n *\n * Administrative means `admin` or `schema-admin`, from the single list in\n * `auth/admin-roles.ts`. Do not compare against `\"admin\"` by hand; that is the\n * divergence that list exists to prevent.\n */\nexport const requireAdmin: MiddlewareHandler<HonoEnv> = async (c, next) => {\n const user = getUser(c);\n if (!user) {\n if (!identityResolved(c)) return c.json(unresolvedIdentity(), 500);\n return c.json({\n error: {\n message: \"Authentication required\",\n code: \"UNAUTHORIZED\"\n }\n }, 401);\n }\n\n if (!isAdmin(c)) {\n return c.json({\n error: {\n message: \"Admin privileges required for this operation\",\n code: \"FORBIDDEN\"\n }\n }, 403);\n }\n\n return next();\n};\n\n/**\n * Reject callers holding none of the named roles with 403.\n *\n * Any of them, not all — require several by chaining the guard twice. Naming no\n * role at all is a programming error and throws at module load rather than at\n * request time, because `requireRole()` with an empty list would otherwise read\n * as a guard while admitting everyone.\n *\n * @example\n * ```ts\n * app.post(\"/publish\", requireAuth, requireRole(\"editor\", \"admin\"), handler);\n * ```\n */\nexport function requireRole(...roles: string[]): MiddlewareHandler<HonoEnv> {\n if (roles.length === 0) {\n throw new Error(\n \"requireRole() needs at least one role. An empty list would admit every \" +\n \"signed-in caller while reading as a restriction.\"\n );\n }\n\n const allowed = new Set(roles);\n return async (c, next) => {\n const user = getUser(c);\n if (!user) {\n if (!identityResolved(c)) return c.json(unresolvedIdentity(), 500);\n return c.json({\n error: {\n message: \"Authentication required\",\n code: \"UNAUTHORIZED\"\n }\n }, 401);\n }\n\n if (!getRoles(c).some(role => allowed.has(role))) {\n return c.json({\n error: {\n message: `This operation requires one of these roles: ${roles.join(\", \")}`,\n code: \"FORBIDDEN\"\n }\n }, 403);\n }\n\n return next();\n };\n}\n","/**\n * Reading configuration from inside a custom function.\n *\n * `process.env.STRIPE_SECRET_KEY` at the top of a function file is the second\n * of the two places the contract silently depends on Node, and it is the one\n * people write without thinking, because on Node it is correct.\n *\n * On an isolate-based host it is wrong twice over:\n *\n * 1. **There is no `process` during module evaluation.** Bindings are attached\n * to the *request*, so at import time — which is when a module-scope\n * `new Stripe(process.env.KEY!)` runs — there is nothing to read. The module\n * throws before a single request is served.\n * 2. **Two concurrent requests in one isolate can carry different bindings.**\n * A value captured once at module scope is then the wrong value for\n * somebody, silently, and only under concurrency.\n *\n * Both disappear if configuration is read from the request. {@link getEnv}\n * does that on every host: `c.env` where the host puts bindings there,\n * `process.env` where it does not. {@link lazyResource} covers the reason the\n * module-scope version was attractive in the first place — building an\n * expensive client exactly once.\n *\n * @module\n */\nimport type { Context } from \"hono\";\nimport { env as hostBindings, getRuntimeKey } from \"hono/adapter\";\n\n/**\n * Every environment variable visible to this request.\n *\n * `c.env` on workerd, `Deno.env` on Deno, `process.env` on Node, Bun and\n * Vercel's edge runtime. Reading it through here rather than through `process`\n * is the whole of what makes a function's configuration portable.\n */\nexport function getEnv(c: Context): Record<string, string | undefined> {\n return hostBindings<Record<string, string | undefined>>(c);\n}\n\n/**\n * One environment variable, or `undefined`.\n *\n * Trimmed, and a blank value counts as absent — declaring a variable and\n * leaving it empty is the ordinary way to write a compose file or a `.env`\n * line, and nobody has ever meant `\"\"` by it.\n */\nexport function env(c: Context, name: string): string | undefined {\n const raw = getEnv(c)[name];\n if (typeof raw !== \"string\") return undefined;\n const trimmed = raw.trim();\n return trimmed === \"\" ? undefined : trimmed;\n}\n\n/**\n * One environment variable, or a thrown error naming it.\n *\n * For configuration a handler cannot run without. Throwing here — inside the\n * request — is deliberately better than the module-scope `process.env.KEY!`\n * it replaces: that one takes the whole *file* down at load time, and the\n * loader reports it as \"this function could not be imported\", which names the\n * file but not the variable. This fails one request, with the name in the\n * message, while every other route in the file keeps serving.\n */\nexport function requireEnv(c: Context, name: string): string {\n const value = env(c, name);\n if (value === undefined) {\n throw new Error(\n `Missing required environment variable ${name}. Set it on the process ` +\n \"(or as a binding, on a host that has them) — and read it inside the \" +\n \"handler, not at module scope, so the same file works on both.\"\n );\n }\n return value;\n}\n\n/**\n * Which host this is: `\"node\"`, `\"workerd\"`, `\"deno\"`, `\"bun\"`, `\"edge-light\"`,\n * `\"fastly\"` or `\"other\"`.\n *\n * Use it to *degrade*, not to branch a whole implementation — a function that\n * needs two implementations is two functions. It is honest about one thing in\n * particular: a Node-only capability, such as `rebase.sql()`, can check this\n * and say so, instead of failing at the call.\n */\nexport function runtimeKey(): string {\n return getRuntimeKey();\n}\n\n/** Whether this is a Node-like host — Node itself, or Bun's Node compatibility. */\nexport function isNodeRuntime(): boolean {\n const key = getRuntimeKey();\n return key === \"node\" || key === \"bun\";\n}\n\n/**\n * Build something expensive once per environment, on first use, from inside a\n * request.\n *\n * This is the sanctioned replacement for the module-scope client:\n *\n * ```ts\n * // Don't: runs at import time, before bindings exist on some hosts.\n * const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);\n *\n * // Do: runs on first request, reads that request's configuration.\n * const stripe = lazyResource(env => new Stripe(env.STRIPE_SECRET_KEY!));\n * app.post(\"/\", async (c) => { await stripe(c).charges.list(); });\n * ```\n *\n * The factory runs once per distinct environment object and the result is\n * cached against it. On Node that is once per process, because `process.env` is\n * one object for the life of the process — identical to the module-scope\n * version in cost, and unlike it, deferred until configuration exists. On a\n * host that hands each request a fresh binding object it is once per object,\n * which is the only correct answer there: caching across bindings would serve\n * one tenant's client to another.\n *\n * A {@link WeakMap} holds the cache, so an environment object the host has\n * finished with takes the resource with it.\n */\nexport function lazyResource<T>(\n factory: (env: Record<string, string | undefined>) => T\n): (c: Context) => T {\n const cache = new WeakMap<object, T>();\n\n return (c: Context): T => {\n const bindings = getEnv(c);\n // A host that returns a primitive-ish or null bag (Fastly returns `{}`\n // freshly each call) still works — it just rebuilds, which is correct\n // and rare.\n if (!bindings || typeof bindings !== \"object\") return factory(bindings ?? {});\n\n const existing = cache.get(bindings);\n if (existing !== undefined) return existing;\n\n const created = factory(bindings);\n cache.set(bindings, created);\n return created;\n };\n}\n","/**\n * Structured Logger for Rebase Backend\n *\n * Outputs JSON lines when `NODE_ENV=production`, human-readable prefixed\n * lines otherwise. Designed to work with Google Cloud Logging severity levels.\n *\n * Every line — message and data, at any depth — passes through the redaction\n * below, which strips Drizzle's `Failed query: … / params: …` wrapper and the\n * values of secret-looking keys. See the block above `serialiseError`.\n *\n * Usage:\n * import { logger } from \"./utils/logger\";\n * logger.info(\"Server started\", { port: 3001 });\n * logger.error(\"Request failed\", { path: \"/api/test\", error: err });\n *\n * Every host global goes through `./host`, and that is load-bearing rather than\n * tidy: this module is reachable from `@rebasepro/server/functions`, the\n * authoring surface that has to import cleanly on a runtime with no `process`.\n * A bare `process.env.NODE_ENV` here would make the first log line of the first\n * request on workerd a `ReferenceError`.\n */\nimport { hostEnv, writeLine } from \"./host\";\n\nexport type LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\n/** Google Cloud Logging severity strings. */\nconst GCP_SEVERITY: Record<LogLevel, string> = {\n debug: \"DEBUG\",\n info: \"INFO\",\n warn: \"WARNING\",\n error: \"ERROR\"\n};\n\nconst LOG_PRIORITY: Record<LogLevel, number> = {\n debug: 0,\n info: 1,\n warn: 2,\n error: 3\n};\n\nexport interface LogEntry {\n severity: string;\n message: string;\n timestamp: string;\n [key: string]: unknown;\n}\n\nexport interface Logger {\n debug(message: string, data?: Record<string, unknown>): void;\n info(message: string, data?: Record<string, unknown>): void;\n warn(message: string, data?: Record<string, unknown>): void;\n error(message: string, data?: Record<string, unknown>): void;\n child(defaultFields: Record<string, unknown>): Logger;\n}\n\nfunction isProduction(): boolean {\n return hostEnv().NODE_ENV === \"production\";\n}\n\nfunction getMinLevel(): LogLevel {\n const env = (hostEnv().LOG_LEVEL || \"info\").toLowerCase();\n if (env in LOG_PRIORITY) return env as LogLevel;\n return \"info\";\n}\n\n// ── Redaction ───────────────────────────────────────────────────────\n//\n// Drizzle builds every query failure as\n// `Failed query: ${query}\\nparams: ${params}` (drizzle-orm/errors.js), so the\n// statement *and* every bound value ride along in `.message` and `.stack` of\n// whatever a driver rethrows — an email and a bcrypt hash reach stdout the\n// moment a registration hits a unique violation. The redaction lives here, in\n// the one function every log line passes through, rather than at the ~124\n// `{ error: … }` call sites: a per-site rule is what produced the leak (one\n// file suppressed the stack, four others did not), and the next caller would\n// reintroduce it. Nothing above this line needs to know about it.\n\nconst FAILED_QUERY_MARKER = \"Failed query:\";\nconst REDACTED_QUERY = \"Failed query: [redacted]\";\nconst REDACTED_VALUE = \"[redacted]\";\n\n/**\n * Key fragments whose values are never safe to publish. Compared against the\n * key with separators and case removed, so `api_key`, `apiKey` and `API-KEY`\n * all match `apikey`.\n */\nconst SENSITIVE_KEY_FRAGMENTS = [\n \"password\",\n \"passwd\",\n \"passphrase\",\n \"secret\",\n \"token\",\n \"apikey\",\n \"authorization\",\n \"credential\",\n \"cookie\",\n \"privatekey\",\n \"sessionid\"\n];\n\n/** Longest structure the redactor will walk before giving up. */\nconst MAX_REDACT_DEPTH = 8;\n\nfunction isSensitiveKey(key: string): boolean {\n const normalised = key.toLowerCase().replace(/[^a-z0-9]/g, \"\");\n return SENSITIVE_KEY_FRAGMENTS.some(fragment => normalised.includes(fragment));\n}\n\n/**\n * Local escape hatch for the `Failed query:` strip only — the statement is the\n * fastest way to diagnose a failing query on a developer machine. Ignored in\n * production, so a runtime that inherits the variable cannot leak because of\n * it, and it never re-enables the key deny-list.\n */\nfunction rawQueriesAllowed(): boolean {\n return hostEnv().NODE_ENV !== \"production\"\n && hostEnv().REBASE_LOG_RAW_QUERIES === \"true\";\n}\n\n/**\n * Strip every `Failed query: … / params: …` span out of a message or stack,\n * keeping the surrounding text (including stack frames, which carry no user\n * data). When no `params:` line follows the marker the rest of the string is\n * dropped: a statement of unknown extent is treated as sensitive rather than\n * guessed at.\n *\n * Idempotent, and it has to be: an already-redacted span still starts with the\n * marker but has no `params:` line, so a second pass over it would fall into\n * the drop-the-rest branch and eat the stack frames behind it. Redaction runs\n * more than once on the same string in practice — the cron scheduler redacts\n * before persisting and then logs the result.\n */\nexport function redactSensitiveText(text: string): string {\n if (!text.includes(FAILED_QUERY_MARKER) || rawQueriesAllowed()) return text;\n\n let out = text;\n let idx = out.indexOf(FAILED_QUERY_MARKER);\n while (idx !== -1) {\n if (out.startsWith(REDACTED_QUERY, idx)) {\n idx = out.indexOf(FAILED_QUERY_MARKER, idx + REDACTED_QUERY.length);\n continue;\n }\n const paramsIdx = out.indexOf(\"\\nparams:\", idx);\n let end: number;\n if (paramsIdx === -1) {\n end = out.length;\n } else {\n const eol = out.indexOf(\"\\n\", paramsIdx + 1);\n end = eol === -1 ? out.length : eol;\n }\n out = out.slice(0, idx) + REDACTED_QUERY + out.slice(end);\n idx = out.indexOf(FAILED_QUERY_MARKER, idx + REDACTED_QUERY.length);\n }\n return out;\n}\n\n/**\n * Serialise an Error into a plain object (stack + message), with the query\n * text redacted out of both. `query`/`params` own-properties — which\n * `DrizzleQueryError` carries beside the message — are deliberately not\n * copied.\n * Handles non-Error values gracefully.\n */\nfunction serialiseError(value: unknown): Record<string, unknown> {\n if (value instanceof Error) {\n return {\n name: value.name,\n message: redactSensitiveText(value.message),\n stack: value.stack ? redactSensitiveText(value.stack) : undefined\n };\n }\n return { value: redactSensitiveText(String(value)) };\n}\n\n/**\n * Redact one logged value: errors are serialised, strings are stripped of\n * query text, objects and arrays are walked. Cycles and over-deep structures\n * collapse to a marker rather than throwing — a logger that can fail is worse\n * than one that logs less. (An object referenced twice in one payload is\n * reported as `[circular]` the second time; bounding the walk matters more\n * than rendering a shared reference twice.)\n */\nfunction redactValue(value: unknown, depth: number, seen: WeakSet<object>): unknown {\n // `serialiseError` returns only already-redacted strings, so it is the\n // terminal step — walking its output again would redact twice.\n if (value instanceof Error) return serialiseError(value);\n if (typeof value === \"string\") return redactSensitiveText(value);\n if (value === null || typeof value !== \"object\") return value;\n if (depth >= MAX_REDACT_DEPTH) return \"[truncated]\";\n if (seen.has(value)) return \"[circular]\";\n seen.add(value);\n\n if (Array.isArray(value)) {\n return value.map(item => redactValue(item, depth + 1, seen));\n }\n if (value instanceof Date) return value;\n\n const out: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(value as Record<string, unknown>)) {\n out[key] = isSensitiveKey(key) ? REDACTED_VALUE : redactValue(val, depth + 1, seen);\n }\n return out;\n}\n\nfunction formatData(data?: Record<string, unknown>): Record<string, unknown> | undefined {\n if (!data) return undefined;\n const seen = new WeakSet<object>();\n const out: Record<string, unknown> = {};\n for (const [key, val] of Object.entries(data)) {\n out[key] = isSensitiveKey(key) ? REDACTED_VALUE : redactValue(val, 0, seen);\n }\n return out;\n}\n\nfunction createLogger(rawDefaultFields: Record<string, unknown> = {}): Logger {\n const minLevel = getMinLevel();\n // Child fields go through the same pass as per-call data — they are merged\n // into every line this logger emits, so leaving them raw would be a hole\n // the moment `child()` gets its first caller.\n const defaultFields = formatData(rawDefaultFields) ?? {};\n\n function emit(level: LogLevel, message: string, data?: Record<string, unknown>): void {\n if (LOG_PRIORITY[level] < LOG_PRIORITY[minLevel]) return;\n\n // The message is redacted too, not just the data: several call sites\n // interpolate `error.message` straight into the line they log.\n const safeMessage = redactSensitiveText(message);\n const merged = { ...defaultFields,\n...formatData(data) };\n\n if (isProduction()) {\n // Structured JSON for Cloud Logging\n const entry: LogEntry = {\n severity: GCP_SEVERITY[level],\n message: safeMessage,\n timestamp: new Date().toISOString(),\n ...merged\n };\n const line = JSON.stringify(entry);\n\n if (level === \"error\") {\n writeLine(\"err\", line);\n } else {\n writeLine(\"out\", line);\n }\n } else {\n // Human-readable for development\n const prefix = level === \"error\" ? \"❌\"\n : level === \"warn\" ? \"⚠️\"\n : level === \"info\" ? \"ℹ️\"\n : \"🐛\";\n const extra = Object.keys(merged).length > 0 ? ` ${JSON.stringify(merged)}` : \"\";\n const out = `${prefix} [${level.toUpperCase()}] ${safeMessage}${extra}`;\n\n if (level === \"error\") {\n console.error(out);\n } else if (level === \"warn\") {\n console.warn(out);\n } else {\n console.log(out);\n }\n }\n }\n\n return {\n debug: (msg, data) => emit(\"debug\", msg, data),\n info: (msg, data) => emit(\"info\", msg, data),\n warn: (msg, data) => emit(\"warn\", msg, data),\n error: (msg, data) => emit(\"error\", msg, data),\n child(fields: Record<string, unknown>): Logger {\n return createLogger({ ...defaultFields,\n...fields });\n }\n };\n}\n\n/**\n * Singleton logger instance.\n * In production: emits JSON lines with `severity`, `message`, `timestamp`.\n * In development: emits human-readable prefixed lines.\n */\nexport const logger: Logger = createLogger();\n","/**\n * Work that outlives the response.\n *\n * Sending a webhook, writing an audit row, warming a cache — work whose result\n * the caller does not wait for. Today, on Node, the way to do that is to not\n * await the promise, and it works: the process is long-lived, so a floating\n * promise settles eventually. The runtime even backstops it — an unhandled\n * rejection inside a function is logged rather than fatal, precisely so one\n * fire-and-forget call cannot end a shared process.\n *\n * That is a Node guarantee, and it is one of exactly two places where the\n * custom-functions contract silently depends on the host. On an isolate-based\n * host the isolate is eligible for termination the moment the response\n * resolves, so an un-awaited promise is not slow — it is **cancelled, usually\n * before it starts**, with no error anywhere. A function that has always sent\n * its webhook simply stops sending it, on a runtime whose logs show a clean\n * 200.\n *\n * There is no way to detect that from inside the function, and no way to fix it\n * afterwards except by rewriting every function that ever relied on it. So the\n * primitive exists now, before there is a host that needs it, and the\n * documentation names it as the only supported way to do post-response work.\n *\n * On Node it is not a no-op either. Registering the promise here is what lets\n * shutdown *wait* for it: a floating promise at SIGTERM is dropped mid-flight,\n * which is the same lost webhook arriving by a different route.\n *\n * @module\n */\nimport type { Context } from \"hono\";\nimport { logger } from \"../utils/logger\";\n\n/**\n * Pending background work, on a process-global slot for the reason given in\n * `../singleton.ts`: two copies of this module in one process must not each\n * hold half the work, or shutdown drains one half and drops the other.\n */\nconst PENDING_SLOT = Symbol.for(\"@rebasepro/server:pending-background-work\");\n\ntype GlobalWithPending = typeof globalThis & {\n [PENDING_SLOT]?: Set<Promise<unknown>>;\n};\n\nfunction pending(): Set<Promise<unknown>> {\n const global = globalThis as GlobalWithPending;\n if (!global[PENDING_SLOT]) global[PENDING_SLOT] = new Set();\n return global[PENDING_SLOT];\n}\n\n/** A context that may or may not carry the host's execution context. */\ntype MaybeExecutionCtx = {\n executionCtx?: { waitUntil?: (promise: Promise<unknown>) => void };\n};\n\n/**\n * Keep `work` alive past the response, on any runtime.\n *\n * Hand it a promise or a function returning one. Nothing is awaited: the\n * response goes out immediately, exactly as an un-awaited call would.\n *\n * What it buys, per host:\n *\n * - **Isolate hosts** (Workers, Deno Deploy, Vercel Edge): the host is told to\n * keep the isolate alive until the promise settles. Without this the work is\n * dropped when the response resolves.\n * - **Node**: the promise is tracked so {@link drainBackgroundWork} can wait for\n * it during graceful shutdown instead of the process exiting out from under\n * it.\n *\n * On every host a rejection is logged rather than left to the unhandled-rejection\n * handler, so the failure names the function it came from.\n *\n * @example\n * ```ts\n * app.post(\"/orders\", requireAuth, async (c) => {\n * const order = await createOrder(c);\n * waitUntil(c, notifyWarehouse(order)); // caller does not wait for this\n * return c.json({ id: order.id });\n * });\n * ```\n */\nexport function waitUntil(\n c: Context,\n work: Promise<unknown> | (() => Promise<unknown> | unknown)\n): void {\n let promise: Promise<unknown>;\n try {\n // A thunk is accepted because the natural way to write this —\n // `waitUntil(c, () => doThing())` — should not be a silent no-op, and\n // an accidentally-passed function is otherwise never invoked.\n promise = typeof work === \"function\" ? Promise.resolve(work()) : work;\n } catch (error) {\n // A thunk that threw synchronously. Its work never started, and the\n // response is still fine, so this is a log rather than a throw.\n logBackgroundFailure(c, error);\n return;\n }\n\n const tracked = promise.catch((error: unknown) => {\n logBackgroundFailure(c, error);\n });\n\n const set = pending();\n set.add(tracked);\n void tracked.finally(() => set.delete(tracked));\n\n // `c.executionCtx` is a getter that *throws* on a host that has none —\n // notably `@hono/node-server` — so this cannot be an `if`.\n try {\n const ctx = (c as unknown as MaybeExecutionCtx).executionCtx;\n ctx?.waitUntil?.(tracked);\n } catch {\n // No execution context: this is Node (or a test), where the process\n // outlives the response on its own and `drainBackgroundWork` covers\n // shutdown. Nothing to report — it is the expected path there.\n }\n}\n\nfunction logBackgroundFailure(c: Context, error: unknown): void {\n logger.error(\"[functions] Background work failed after the response was sent\", {\n method: c.req?.method,\n path: c.req?.path,\n error: error instanceof Error ? error : new Error(String(error))\n });\n}\n\n/** How many background promises are still in flight. Diagnostics and tests. */\nexport function pendingBackgroundWork(): number {\n return pending().size;\n}\n\n/**\n * Wait for tracked background work to finish, up to `timeoutMs`.\n *\n * Called by the shutdown path after the server stops accepting connections. It\n * resolves to the number of promises still outstanding when it returned — `0`\n * for a clean drain, more than that for a timeout, which is worth a log line\n * because it means work was dropped.\n *\n * Rejections do not propagate: every tracked promise has already had a `catch`\n * attached by {@link waitUntil}.\n */\nexport async function drainBackgroundWork(timeoutMs = 5_000): Promise<number> {\n const set = pending();\n if (set.size === 0) return 0;\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n const expired = new Promise<\"timeout\">(resolve => {\n timer = setTimeout(() => resolve(\"timeout\"), timeoutMs);\n // Do not hold the event loop open just to observe a deadline.\n (timer as unknown as { unref?: () => void }).unref?.();\n });\n\n try {\n await Promise.race([Promise.allSettled([...set]), expired]);\n } finally {\n if (timer) clearTimeout(timer);\n }\n\n return set.size;\n}\n\n/** @internal Test seam — forgets tracked work without waiting for it. */\nexport function _resetBackgroundWork(): void {\n pending().clear();\n}\n","import type { ErrorHandler } from \"hono\";\nimport type { ContentfulStatusCode } from \"hono/utils/http-status\";\nimport type { HonoEnv } from \"./types\";\nimport { logger } from \"../utils/logger\";\nimport { hostEnv } from \"../utils/host\";\n\n/** Tracks whether we've already shown the doctor hint (once per process). */\nlet _schemaDriftHinted = false;\n\n/** Shape of Postgres / network errors with diagnostic codes */\ninterface PgLikeError {\n code?: string;\n address?: string;\n port?: number;\n message?: string;\n table?: string;\n column?: string;\n schema?: string;\n detail?: string;\n hint?: string;\n constraint?: string;\n}\n\n/** 5-character SQLSTATE, e.g. `42501`, `23505`. */\nconst SQLSTATE_RE = /^[0-9A-Z]{5}$/;\n\n/**\n * Walk the cause chain for the underlying database error, identified by a\n * 5-char SQLSTATE `code`. Drizzle wraps the pg error in `.cause`, and route\n * code sometimes wraps drizzle again, so the real error may sit several\n * levels down.\n */\nfunction extractDbError(error: unknown, depth = 0): PgLikeError | null {\n if (!error || typeof error !== \"object\" || depth > 8) return null;\n const e = error as PgLikeError & { cause?: unknown };\n if (typeof e.code === \"string\" && SQLSTATE_RE.test(e.code)) return e;\n if (e.cause && typeof e.cause === \"object\") return extractDbError(e.cause, depth + 1);\n return null;\n}\n\n/**\n * Extract the missing table or column name from a PG error.\n * PG 42P01 messages look like: 'relation \"my_table\" does not exist'\n * PG 42703 messages look like: 'column \"my_col\" does not exist' or 'column my_table.my_col does not exist'\n */\nfunction extractMissingIdentifier(pgMessage?: string): string | null {\n if (!pgMessage) return null;\n // Match quoted identifier: relation \"xxx\" / column \"xxx\"\n const quoted = pgMessage.match(/(?:relation|column|table)\\s+\"([^\"]+)\"/i);\n if (quoted) return quoted[1];\n // Match unquoted: column table.col does not exist\n const unquoted = pgMessage.match(/(?:relation|column|table)\\s+([\\w.]+)\\s+does not exist/i);\n if (unquoted) return unquoted[1];\n return null;\n}\n\n/**\n * Standardized API error class.\n * Throw this from any route handler — the errorHandler middleware\n * will format it into `{ error: { message, code, details? } }`.\n */\nexport class ApiError extends Error {\n public readonly statusCode: number;\n public readonly code: string;\n public readonly details?: unknown;\n /**\n * Whether this outcome is a routine part of normal operation rather than\n * something an operator should look at. Expected errors log at debug; every\n * other operational error logs at warn.\n *\n * The motivating case is `POST /auth/refresh` with no session: clients\n * refresh on page load before they know whether one exists, so every\n * anonymous page view is a 401 — correct, and not worth a warning line.\n *\n * The other class is a caller-caused 4xx that never reached the database: a\n * mistyped filter operator, sort direction or limit, a request for a\n * collection that does not exist. Nothing on this server is wrong, and the\n * response body has already told the caller what to fix — while one client\n * holding a stale name would otherwise write a warning per request, forever,\n * until the level means nothing. See `api/rest/query-parser.ts`.\n *\n * What stays at warn is anything that says something about the *server*:\n * a schema that has drifted from the code, a permission the database\n * refused, a dependency that failed. Those are 4xx too, and they are still\n * incidents.\n */\n public readonly expected: boolean;\n\n constructor(statusCode: number, code: string, message: string, details?: unknown, expected = false) {\n super(message);\n this.name = \"ApiError\";\n this.statusCode = statusCode;\n this.code = code;\n this.details = details;\n this.expected = expected;\n }\n\n // ── Factory methods ──────────────────────────────────────────────\n\n static badRequest(message: string, code = \"BAD_REQUEST\", details?: unknown): ApiError {\n return new ApiError(400, code, message, details);\n }\n\n static unauthorized(message: string, code = \"UNAUTHORIZED\"): ApiError {\n return new ApiError(401, code, message);\n }\n\n /**\n * A 401 that is a normal outcome, not an incident — logged at debug.\n * See {@link ApiError.expected}.\n */\n static unauthenticated(message: string, code = \"UNAUTHORIZED\"): ApiError {\n return new ApiError(401, code, message, undefined, true);\n }\n\n static forbidden(message: string, code = \"FORBIDDEN\"): ApiError {\n return new ApiError(403, code, message);\n }\n\n static notFound(message: string, code = \"NOT_FOUND\"): ApiError {\n return new ApiError(404, code, message);\n }\n\n static conflict(message: string, code = \"CONFLICT\"): ApiError {\n return new ApiError(409, code, message);\n }\n\n static internal(message: string, code = \"INTERNAL_ERROR\"): ApiError {\n return new ApiError(500, code, message);\n }\n\n static serviceUnavailable(message: string, code = \"SERVICE_UNAVAILABLE\"): ApiError {\n return new ApiError(503, code, message);\n }\n}\n\n/**\n * Canonical error response shape:\n * `{ error: { message: string, code: string, details?: unknown } }`\n */\nexport interface ErrorResponse {\n error: {\n message: string;\n code: string;\n details?: unknown;\n /** Request correlation ID for tracing (echoes X-Request-ID). */\n requestId?: string;\n };\n}\n\n/**\n * General shape of errors that flow through the API error handler.\n * Extends Error with optional HTTP status, error code, and details.\n */\nexport interface RebaseApiError extends Error {\n statusCode?: number;\n code?: string;\n details?: unknown;\n}\n\n// `isRebaseApiError` was here. It read `return error instanceof Error`, so it\n// answered yes to every error while being named and used as though it\n// discriminated — the create and update handlers guarded a \"classify this as\n// BAD_REQUEST\" branch on it, and an unreachable database was therefore reported\n// to callers as a bad request. Deleted rather than repaired: the shape it\n// claimed to test is not decidable from an `Error`, and the layer that does\n// know — the driver, which holds the SQLSTATE — raises a real `ApiError`.\n\n/**\n * Hono error-handling middleware (`app.onError`).\n * Converts any error into the canonical `{ error: { message, code } }` shape.\n */\nexport const errorHandler: ErrorHandler<HonoEnv> = (err, c) => {\n // Typecast custom error properties\n const error: RebaseApiError = err;\n const reqId = typeof c.get === \"function\" ? c.get(\"requestId\") : undefined;\n\n if (error instanceof ApiError || error.name === \"ApiError\") {\n // Operational errors — log at warn, unless the error declares itself a\n // routine outcome (see ApiError.expected), which would otherwise put a\n // warning in the log for every anonymous page view.\n const expected = error instanceof ApiError && error.expected;\n const line = `[API] ${c.req.method} ${c.req.path} → ${error.statusCode} ${error.code}: ${error.message}` +\n (reqId ? ` [${reqId}]` : \"\");\n if (expected) {\n logger.debug(line);\n } else {\n logger.warn(`⚠️ ${line}`);\n }\n return c.json({\n error: {\n message: error.message,\n code: error.code || \"INTERNAL_ERROR\",\n ...(error.details !== undefined && { details: error.details }),\n ...(reqId && { requestId: reqId })\n }\n } satisfies ErrorResponse, (error.statusCode || 500) as ContentfulStatusCode);\n }\n\n const statusCode = error.statusCode || codeToStatus(error.code) || 500;\n let code = error.code || \"INTERNAL_ERROR\";\n\n // Handle DB connection and specific system errors for better logging\n let logMessage = error.message;\n\n // Resolve the actual cause — Node's net module wraps dual-stack failures\n // in an AggregateError whose inner errors carry the real address/port.\n let resolvedCause: PgLikeError | undefined;\n if (error.cause && typeof error.cause === \"object\" && error.cause !== null && \"code\" in error.cause) {\n const cause = error.cause as PgLikeError & { errors?: PgLikeError[] };\n if (cause.code === \"ECONNREFUSED\" && !cause.address && Array.isArray(cause.errors)) {\n // AggregateError — pick the first inner error that has address info\n resolvedCause = cause.errors.find(e => e.address) || cause;\n } else {\n resolvedCause = cause;\n }\n }\n\n // The real database error may sit several levels down the cause chain.\n // Losing it turns a precise failure (e.g. an RLS denial) into an opaque\n // \"Failed query: …\" 500 that is undiagnosable without direct DB access.\n const dbError = extractDbError(error);\n\n if (resolvedCause && (resolvedCause.code === \"ENETUNREACH\" || resolvedCause.code === \"ECONNREFUSED\")) {\n const cause = resolvedCause;\n if (cause.code === \"ENETUNREACH\") {\n logMessage = `Network unreachable. Cannot connect to database at ${cause.address}:${cause.port}.`;\n } else {\n logMessage = `Connection refused to database at ${cause.address}:${cause.port}. Is PostgreSQL running?`;\n }\n } else if (\"code\" in error && error.code === \"ENETUNREACH\") {\n const netErr = error as PgLikeError;\n logMessage = `Network unreachable. Cannot connect to service at ${netErr.address}:${netErr.port}.`;\n } else if (dbError && (dbError.code === \"42703\" || dbError.code === \"42P01\")) {\n code = \"SCHEMA_DRIFT\";\n const issue = dbError.code === \"42703\" ? \"column\" : \"table\";\n const identifier = dbError.table || dbError.column || extractMissingIdentifier(dbError.message) || \"unknown\";\n logMessage = `Schema drift: ${issue} \"${identifier}\" does not exist in the database. Run \\`pnpm db:push\\` to sync your schema, or \\`pnpm db:migrate\\` to apply pending migrations.`;\n } else if (dbError) {\n const parts = [`[PG ${dbError.code}] ${dbError.message}`];\n if (dbError.detail) parts.push(`Detail: ${dbError.detail}`);\n if (dbError.hint) parts.push(`Hint: ${dbError.hint}`);\n if (dbError.table) parts.push(`Table: ${dbError.table}`);\n if (dbError.column) parts.push(`Column: ${dbError.column}`);\n if (dbError.constraint) parts.push(`Constraint: ${dbError.constraint}`);\n if (dbError.code === \"42501\") {\n code = \"DB_PERMISSION_DENIED\";\n parts.push(\n \"The database rejected the statement for lack of privilege — usually a row-level \" +\n `security policy${dbError.table ? ` on \"${dbError.table}\"` : \"\"} denying this role, ` +\n \"or a stale FORCE ROW LEVEL SECURITY flag binding the owner connection.\"\n );\n }\n logMessage = parts.join(\". \");\n }\n\n const isDbSchemaMismatch = code === \"SCHEMA_DRIFT\";\n\n if (isDbSchemaMismatch) {\n // Database schema mismatch is logged as a warning instead of a fatal error\n logger.warn(\n `⚠️ [API] ${c.req.method} ${c.req.path} → ${statusCode} ${code}: ${logMessage}` +\n (reqId ? ` [${reqId}]` : \"\")\n );\n // In dev mode, show a one-time hint to run `rebase doctor`\n if (!_schemaDriftHinted && hostEnv().NODE_ENV !== \"production\") {\n _schemaDriftHinted = true;\n logger.warn([\n \"\",\n \"┌──────────────────────────────────────────────────────────────┐\",\n \"│ 💡 TIP: Run `rebase doctor` for full schema diagnostics │\",\n \"│ │\",\n \"│ Quick fixes (local dev, against DATABASE_URL): │\",\n \"│ pnpm db:push sync schema to database (dev) │\",\n \"│ pnpm db:migrate generate + apply migration (prod) │\",\n \"│ rebase doctor full 3-way drift report │\",\n \"│ │\",\n \"│ Managed cloud: the runtime applies schema + RLS at boot │\",\n \"│ (REBASE_MIGRATE_ON_BOOT); redeploy rather than db:push, │\",\n \"│ which cannot reach the tenant database. │\",\n \"└──────────────────────────────────────────────────────────────┘\",\n \"\"\n ].join(\"\\n\"));\n }\n } else {\n // Unexpected errors — log at error level\n logger.error(\n `❌ [API] ${c.req.method} ${c.req.path} → ${statusCode} ${code}: ${logMessage}` +\n (reqId ? ` [${reqId}]` : \"\")\n );\n }\n\n // Suppress the huge stack trace for known DB errors: it is noisy, and the\n // extracted [PG …] line above carries the signal. The SQL and the bound\n // params it used to leak are no longer this branch's problem — `logger`\n // strips Drizzle's `Failed query: … / params: …` wrapper out of every\n // message and stack it emits, so the fallbacks below (a connection dropped\n // mid-statement carries no SQLSTATE, so `dbError` is null and the stack is\n // logged) are covered too.\n const suppressStack = isDbSchemaMismatch || dbError !== null || (statusCode < 500 && code === \"BAD_REQUEST\");\n if (!suppressStack) {\n logger.error(String(error.stack || error));\n }\n\n // Sanitize the message for the client to prevent leaking sensitive details\n // like SQL queries or internal IP addresses.\n let clientMessage = \"An unexpected error occurred\";\n if (statusCode < 500 && error.message) {\n // If it's a 4xx error (e.g. from validation), it's generally safe to send the message\n clientMessage = error.message;\n } else if (error instanceof ApiError || error.name === \"ApiError\") {\n // We already handled ApiError above, but just in case\n clientMessage = error.message;\n } else if (code === \"SCHEMA_DRIFT\") {\n const pgErr = dbError || (error as PgLikeError);\n const issue = pgErr.code === \"42703\" ? \"column\" : \"table\";\n const identifier = pgErr.table || pgErr.column || extractMissingIdentifier(pgErr.message || error.message) || \"unknown\";\n clientMessage = `Schema drift: ${issue} \"${identifier}\" does not exist. Run \\`pnpm db:push\\` to sync your schema.`;\n } else if (code === \"DB_PERMISSION_DENIED\") {\n clientMessage = `Permission denied by the database${dbError?.table ? ` on \"${dbError.table}\"` : \"\"} (row-level security). Check the RLS policies for this table.`;\n } else if (code === \"INTERNAL_ERROR\") {\n clientMessage = \"Internal Server Error\";\n }\n\n // Database diagnostics for the envelope: the SQLSTATE is always safe to\n // return; message/detail/hint can reference schema internals, so only\n // outside production.\n const dbDetails = dbError ? {\n dbCode: dbError.code,\n ...(hostEnv().NODE_ENV !== \"production\" && {\n dbMessage: dbError.message,\n ...(dbError.detail && { detail: dbError.detail }),\n ...(dbError.hint && { hint: dbError.hint })\n })\n } : undefined;\n\n return c.json({\n error: {\n message: clientMessage,\n code,\n ...(error.details !== undefined\n ? { details: error.details }\n : dbDetails !== undefined ? { details: dbDetails } : {}),\n ...(reqId && { requestId: reqId })\n }\n } satisfies ErrorResponse, statusCode as ContentfulStatusCode);\n};\n\n/**\n * Map known error codes to HTTP status codes.\n */\nfunction codeToStatus(code?: string): number | undefined {\n if (!code) return undefined;\n const map: Record<string, number> = {\n BAD_REQUEST: 400,\n INVALID_INPUT: 400,\n WEAK_PASSWORD: 400,\n UNAUTHORIZED: 401,\n INVALID_CREDENTIALS: 401,\n INVALID_TOKEN: 401,\n FORBIDDEN: 403,\n NOT_FOUND: 404,\n CONFLICT: 409,\n EMAIL_EXISTS: 409,\n ROLE_EXISTS: 409,\n SCHEMA_DRIFT: 500,\n DB_PERMISSION_DENIED: 500,\n INTERNAL_ERROR: 500,\n NOT_CONFIGURED: 503,\n SERVICE_UNAVAILABLE: 503\n };\n return map[code];\n}\n\n\n"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host-side machinery that serves custom functions.
|
|
3
|
+
*
|
|
4
|
+
* Kept apart from `./index.ts` on purpose. That file is the package's
|
|
5
|
+
* `@rebasepro/server/functions` entry point — the surface a user's function
|
|
6
|
+
* file imports — and it must stay importable on a runtime with no Node
|
|
7
|
+
* built-ins. This one reads directories, spawns proxy requests and holds
|
|
8
|
+
* timers, so it is imported by the package root and by `init.ts`, never by
|
|
9
|
+
* application code.
|
|
10
|
+
*
|
|
11
|
+
* The split is enforced, not merely intended: `portability.test.ts` walks the
|
|
12
|
+
* import graph of `./index.ts` and fails on the first module that reaches a
|
|
13
|
+
* Node built-in. Re-export anything from this file there and that test goes red
|
|
14
|
+
* immediately, which is the point — the boundary is worth more than any single
|
|
15
|
+
* convenience export placed across it.
|
|
16
|
+
*
|
|
17
|
+
* @module
|
|
18
|
+
*/
|
|
19
|
+
export { loadFunctionsFromDirectory, loadFunctionsWithDiagnostics } from "./function-loader.js";
|
|
20
|
+
export type { LoadedFunction, LoadedFunctions } from "./function-loader.js";
|
|
21
|
+
export { createFunctionRoutes } from "./function-routes.js";
|
|
22
|
+
export { createFunctionsProxy } from "./proxy.js";
|
|
23
|
+
export type { FunctionsProxyOptions } from "./proxy.js";
|
|
24
|
+
export { createFunctionsRequestTimeout, resolveFunctionsTimeoutMs, DEFAULT_FUNCTIONS_TIMEOUT_MS } from "./request-timeout.js";
|
|
25
|
+
export { selectFunctions, FunctionSelectionError } from "./selection.js";
|
|
26
|
+
export type { FunctionSelection } from "./selection.js";
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* anything on the way through would put two answers where the system has one.
|
|
14
14
|
*/
|
|
15
15
|
import { Hono } from "hono";
|
|
16
|
-
import type { HonoEnv } from "../api/types";
|
|
16
|
+
import type { HonoEnv } from "../api/types.js";
|
|
17
17
|
export interface FunctionsProxyOptions {
|
|
18
18
|
/** Base URL of the process serving the functions, e.g. `http://functions:8080`. */
|
|
19
19
|
upstream: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MiddlewareHandler } from "hono";
|
|
2
|
-
import type { HonoEnv } from "../api/types";
|
|
2
|
+
import type { HonoEnv } from "../api/types.js";
|
|
3
3
|
/** Default ceiling for a custom function request, in milliseconds. */
|
|
4
4
|
export declare const DEFAULT_FUNCTIONS_TIMEOUT_MS = 30000;
|
|
5
5
|
/**
|
|
@@ -28,6 +28,14 @@ export declare function resolveFunctionsTimeoutMs(configured?: number): number;
|
|
|
28
28
|
* eventual result is dropped. It is a ceiling, not a kill switch, and the exact
|
|
29
29
|
* number matters far less than its existence.
|
|
30
30
|
*
|
|
31
|
+
* "The handler keeps running" is a **Node** guarantee, not a property of the
|
|
32
|
+
* contract. It follows from the process outliving the request, which is not
|
|
33
|
+
* true on an isolate-based host: there, work still in flight when the response
|
|
34
|
+
* resolves is terminated rather than orphaned. So a handler must not depend on
|
|
35
|
+
* finishing after its 504 — anything that has to complete belongs in
|
|
36
|
+
* `waitUntil()`, which is the one construct both hosts honour. See
|
|
37
|
+
* `./wait-until.ts`.
|
|
38
|
+
*
|
|
31
39
|
* Mounted in front of the auth middleware rather than behind it, so a wedged
|
|
32
40
|
* driver — the failure that also hangs `withAuth()` — is covered too.
|
|
33
41
|
*/
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading configuration from inside a custom function.
|
|
3
|
+
*
|
|
4
|
+
* `process.env.STRIPE_SECRET_KEY` at the top of a function file is the second
|
|
5
|
+
* of the two places the contract silently depends on Node, and it is the one
|
|
6
|
+
* people write without thinking, because on Node it is correct.
|
|
7
|
+
*
|
|
8
|
+
* On an isolate-based host it is wrong twice over:
|
|
9
|
+
*
|
|
10
|
+
* 1. **There is no `process` during module evaluation.** Bindings are attached
|
|
11
|
+
* to the *request*, so at import time — which is when a module-scope
|
|
12
|
+
* `new Stripe(process.env.KEY!)` runs — there is nothing to read. The module
|
|
13
|
+
* throws before a single request is served.
|
|
14
|
+
* 2. **Two concurrent requests in one isolate can carry different bindings.**
|
|
15
|
+
* A value captured once at module scope is then the wrong value for
|
|
16
|
+
* somebody, silently, and only under concurrency.
|
|
17
|
+
*
|
|
18
|
+
* Both disappear if configuration is read from the request. {@link getEnv}
|
|
19
|
+
* does that on every host: `c.env` where the host puts bindings there,
|
|
20
|
+
* `process.env` where it does not. {@link lazyResource} covers the reason the
|
|
21
|
+
* module-scope version was attractive in the first place — building an
|
|
22
|
+
* expensive client exactly once.
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
import type { Context } from "hono";
|
|
27
|
+
/**
|
|
28
|
+
* Every environment variable visible to this request.
|
|
29
|
+
*
|
|
30
|
+
* `c.env` on workerd, `Deno.env` on Deno, `process.env` on Node, Bun and
|
|
31
|
+
* Vercel's edge runtime. Reading it through here rather than through `process`
|
|
32
|
+
* is the whole of what makes a function's configuration portable.
|
|
33
|
+
*/
|
|
34
|
+
export declare function getEnv(c: Context): Record<string, string | undefined>;
|
|
35
|
+
/**
|
|
36
|
+
* One environment variable, or `undefined`.
|
|
37
|
+
*
|
|
38
|
+
* Trimmed, and a blank value counts as absent — declaring a variable and
|
|
39
|
+
* leaving it empty is the ordinary way to write a compose file or a `.env`
|
|
40
|
+
* line, and nobody has ever meant `""` by it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function env(c: Context, name: string): string | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* One environment variable, or a thrown error naming it.
|
|
45
|
+
*
|
|
46
|
+
* For configuration a handler cannot run without. Throwing here — inside the
|
|
47
|
+
* request — is deliberately better than the module-scope `process.env.KEY!`
|
|
48
|
+
* it replaces: that one takes the whole *file* down at load time, and the
|
|
49
|
+
* loader reports it as "this function could not be imported", which names the
|
|
50
|
+
* file but not the variable. This fails one request, with the name in the
|
|
51
|
+
* message, while every other route in the file keeps serving.
|
|
52
|
+
*/
|
|
53
|
+
export declare function requireEnv(c: Context, name: string): string;
|
|
54
|
+
/**
|
|
55
|
+
* Which host this is: `"node"`, `"workerd"`, `"deno"`, `"bun"`, `"edge-light"`,
|
|
56
|
+
* `"fastly"` or `"other"`.
|
|
57
|
+
*
|
|
58
|
+
* Use it to *degrade*, not to branch a whole implementation — a function that
|
|
59
|
+
* needs two implementations is two functions. It is honest about one thing in
|
|
60
|
+
* particular: a Node-only capability, such as `rebase.sql()`, can check this
|
|
61
|
+
* and say so, instead of failing at the call.
|
|
62
|
+
*/
|
|
63
|
+
export declare function runtimeKey(): string;
|
|
64
|
+
/** Whether this is a Node-like host — Node itself, or Bun's Node compatibility. */
|
|
65
|
+
export declare function isNodeRuntime(): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* Build something expensive once per environment, on first use, from inside a
|
|
68
|
+
* request.
|
|
69
|
+
*
|
|
70
|
+
* This is the sanctioned replacement for the module-scope client:
|
|
71
|
+
*
|
|
72
|
+
* ```ts
|
|
73
|
+
* // Don't: runs at import time, before bindings exist on some hosts.
|
|
74
|
+
* const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
|
|
75
|
+
*
|
|
76
|
+
* // Do: runs on first request, reads that request's configuration.
|
|
77
|
+
* const stripe = lazyResource(env => new Stripe(env.STRIPE_SECRET_KEY!));
|
|
78
|
+
* app.post("/", async (c) => { await stripe(c).charges.list(); });
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* The factory runs once per distinct environment object and the result is
|
|
82
|
+
* cached against it. On Node that is once per process, because `process.env` is
|
|
83
|
+
* one object for the life of the process — identical to the module-scope
|
|
84
|
+
* version in cost, and unlike it, deferred until configuration exists. On a
|
|
85
|
+
* host that hands each request a fresh binding object it is once per object,
|
|
86
|
+
* which is the only correct answer there: caching across bindings would serve
|
|
87
|
+
* one tenant's client to another.
|
|
88
|
+
*
|
|
89
|
+
* A {@link WeakMap} holds the cache, so an environment object the host has
|
|
90
|
+
* finished with takes the resource with it.
|
|
91
|
+
*/
|
|
92
|
+
export declare function lazyResource<T>(factory: (env: Record<string, string | undefined>) => T): (c: Context) => T;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Work that outlives the response.
|
|
3
|
+
*
|
|
4
|
+
* Sending a webhook, writing an audit row, warming a cache — work whose result
|
|
5
|
+
* the caller does not wait for. Today, on Node, the way to do that is to not
|
|
6
|
+
* await the promise, and it works: the process is long-lived, so a floating
|
|
7
|
+
* promise settles eventually. The runtime even backstops it — an unhandled
|
|
8
|
+
* rejection inside a function is logged rather than fatal, precisely so one
|
|
9
|
+
* fire-and-forget call cannot end a shared process.
|
|
10
|
+
*
|
|
11
|
+
* That is a Node guarantee, and it is one of exactly two places where the
|
|
12
|
+
* custom-functions contract silently depends on the host. On an isolate-based
|
|
13
|
+
* host the isolate is eligible for termination the moment the response
|
|
14
|
+
* resolves, so an un-awaited promise is not slow — it is **cancelled, usually
|
|
15
|
+
* before it starts**, with no error anywhere. A function that has always sent
|
|
16
|
+
* its webhook simply stops sending it, on a runtime whose logs show a clean
|
|
17
|
+
* 200.
|
|
18
|
+
*
|
|
19
|
+
* There is no way to detect that from inside the function, and no way to fix it
|
|
20
|
+
* afterwards except by rewriting every function that ever relied on it. So the
|
|
21
|
+
* primitive exists now, before there is a host that needs it, and the
|
|
22
|
+
* documentation names it as the only supported way to do post-response work.
|
|
23
|
+
*
|
|
24
|
+
* On Node it is not a no-op either. Registering the promise here is what lets
|
|
25
|
+
* shutdown *wait* for it: a floating promise at SIGTERM is dropped mid-flight,
|
|
26
|
+
* which is the same lost webhook arriving by a different route.
|
|
27
|
+
*
|
|
28
|
+
* @module
|
|
29
|
+
*/
|
|
30
|
+
import type { Context } from "hono";
|
|
31
|
+
/**
|
|
32
|
+
* Keep `work` alive past the response, on any runtime.
|
|
33
|
+
*
|
|
34
|
+
* Hand it a promise or a function returning one. Nothing is awaited: the
|
|
35
|
+
* response goes out immediately, exactly as an un-awaited call would.
|
|
36
|
+
*
|
|
37
|
+
* What it buys, per host:
|
|
38
|
+
*
|
|
39
|
+
* - **Isolate hosts** (Workers, Deno Deploy, Vercel Edge): the host is told to
|
|
40
|
+
* keep the isolate alive until the promise settles. Without this the work is
|
|
41
|
+
* dropped when the response resolves.
|
|
42
|
+
* - **Node**: the promise is tracked so {@link drainBackgroundWork} can wait for
|
|
43
|
+
* it during graceful shutdown instead of the process exiting out from under
|
|
44
|
+
* it.
|
|
45
|
+
*
|
|
46
|
+
* On every host a rejection is logged rather than left to the unhandled-rejection
|
|
47
|
+
* handler, so the failure names the function it came from.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* app.post("/orders", requireAuth, async (c) => {
|
|
52
|
+
* const order = await createOrder(c);
|
|
53
|
+
* waitUntil(c, notifyWarehouse(order)); // caller does not wait for this
|
|
54
|
+
* return c.json({ id: order.id });
|
|
55
|
+
* });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare function waitUntil(c: Context, work: Promise<unknown> | (() => Promise<unknown> | unknown)): void;
|
|
59
|
+
/** How many background promises are still in flight. Diagnostics and tests. */
|
|
60
|
+
export declare function pendingBackgroundWork(): number;
|
|
61
|
+
/**
|
|
62
|
+
* Wait for tracked background work to finish, up to `timeoutMs`.
|
|
63
|
+
*
|
|
64
|
+
* Called by the shutdown path after the server stops accepting connections. It
|
|
65
|
+
* resolves to the number of promises still outstanding when it returned — `0`
|
|
66
|
+
* for a clean drain, more than that for a timeout, which is worth a log line
|
|
67
|
+
* because it means work was dropped.
|
|
68
|
+
*
|
|
69
|
+
* Rejections do not propagate: every tracked promise has already had a `catch`
|
|
70
|
+
* attached by {@link waitUntil}.
|
|
71
|
+
*/
|
|
72
|
+
export declare function drainBackgroundWork(timeoutMs?: number): Promise<number>;
|
|
73
|
+
/** @internal Test seam — forgets tracked work without waiting for it. */
|
|
74
|
+
export declare function _resetBackgroundWork(): void;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Hono } from "hono";
|
|
2
|
-
import { HonoEnv } from "../api/types";
|
|
3
|
-
import { BackendCollectionRegistry } from "../collections/BackendCollectionRegistry";
|
|
2
|
+
import { HonoEnv } from "../api/types.js";
|
|
3
|
+
import { BackendCollectionRegistry } from "../collections/BackendCollectionRegistry.js";
|
|
4
4
|
import { DataDriver } from "@rebasepro/types";
|
|
5
5
|
/**
|
|
6
6
|
* Create Hono routes for entity history.
|
package/dist/history/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { createHistoryRoutes } from "./history-routes";
|
|
1
|
+
export { createHistoryRoutes } from "./history-routes.js";
|
package/dist/index.d.ts
CHANGED
|
@@ -6,57 +6,66 @@
|
|
|
6
6
|
* and API layers. Database implementations (e.g., PostgreSQL) are provided
|
|
7
7
|
* by specialized driver packages like `@rebasepro/server-postgres`.
|
|
8
8
|
*/
|
|
9
|
-
export { initializeRebaseBackend, isAuthAdapter, isDatabaseAdapter } from "./init";
|
|
10
|
-
export { resolveRequireAuth } from "./auth/require-auth";
|
|
11
|
-
export type { RebaseBackendConfig, RebaseBackendInstance, RebaseAuthConfig, BaasOptions } from "./init";
|
|
12
|
-
export { rebase, _setRebaseMock, _resetRebaseMock } from "./singleton";
|
|
13
|
-
export { loadCollectionsFromDirectory, applyCollectionDefaults, type CollectionDefaults } from "./collections/loader";
|
|
14
|
-
export { assertCollectionConfigs, findCollectionConfigProblems, unknownKeyPolicyFromEnv, type ConfigProblem, type UnknownKeyPolicy, type ValidateCollectionConfigOptions } from "./collections/validate-config";
|
|
15
|
-
export * from "./db/interfaces";
|
|
16
|
-
export * from "./auth/interfaces";
|
|
17
|
-
export { requireAuth, requireAdmin, optionalAuth, queryTokenAuth, fileTokenAuth, extractUserFromToken, hashPassword, verifyPassword, validatePasswordStrength, generateSecurePassword, resolveAuthHooks, createBuiltinAuthAdapter, createCustomAuthAdapter, createGoogleProvider, createLinkedinProvider, createGitHubProvider, createMicrosoftProvider, createAppleProvider, createFacebookProvider, createTwitterProvider, createDiscordProvider, createGitLabProvider, createBitbucketProvider, createSlackProvider, createSpotifyProvider, oauthCodeFlowSchema, pkceTokenParams, providerVerifiedEmail, verifyOidcIdToken, tryVerifyOidcIdToken, isApiKeyToken, validateApiKey, httpMethodToOperation, isOperationAllowed, safeCompare, getJwks, hasAsymmetricSigningKey, createJwksRoutes } from "./auth";
|
|
18
|
-
export type { JwtSigningKeyConfig, JwtSigningAlgorithm, PublicJwk, AccessTokenPayload, PasswordValidationResult, AuthHooks, AuthMethod, ResolvedAuthHooks, BuiltinAuthAdapterConfig, GoogleProviderConfig, AuthMiddlewareOptions, AuthResult, ApiKey, ApiKeyMasked, ApiKeyPermission, ApiKeyWithSecret, CreateApiKeyRequest, UpdateApiKeyRequest, ApiKeyStore, ApiKeyOperation } from "./auth";
|
|
19
|
-
export * from "./api/types";
|
|
20
|
-
export * from "./api/errors";
|
|
21
|
-
export { assertWriteRequestValid } from "./api/rest/write-validation";
|
|
22
|
-
export * from "./email";
|
|
23
|
-
export * from "./storage";
|
|
24
|
-
export {
|
|
25
|
-
export {
|
|
26
|
-
export
|
|
27
|
-
export {
|
|
28
|
-
export
|
|
29
|
-
export {
|
|
30
|
-
export type {
|
|
31
|
-
export {
|
|
32
|
-
export {
|
|
33
|
-
export
|
|
34
|
-
export {
|
|
35
|
-
export
|
|
36
|
-
export
|
|
37
|
-
export
|
|
38
|
-
export
|
|
39
|
-
export
|
|
40
|
-
export {
|
|
41
|
-
export
|
|
42
|
-
export {
|
|
43
|
-
export {
|
|
44
|
-
export {
|
|
45
|
-
export
|
|
46
|
-
export
|
|
47
|
-
export
|
|
48
|
-
export
|
|
49
|
-
export
|
|
50
|
-
export {
|
|
51
|
-
export
|
|
52
|
-
export {
|
|
53
|
-
export
|
|
54
|
-
export {
|
|
55
|
-
export {
|
|
56
|
-
export type {
|
|
57
|
-
export {
|
|
58
|
-
export type {
|
|
59
|
-
export {
|
|
60
|
-
export type {
|
|
61
|
-
export {
|
|
62
|
-
export type {
|
|
9
|
+
export { initializeRebaseBackend, isAuthAdapter, isDatabaseAdapter } from "./init.js";
|
|
10
|
+
export { resolveRequireAuth } from "./auth/require-auth.js";
|
|
11
|
+
export type { RebaseBackendConfig, RebaseBackendInstance, RebaseAuthConfig, BaasOptions } from "./init.js";
|
|
12
|
+
export { rebase, _setRebaseMock, _resetRebaseMock } from "./singleton.js";
|
|
13
|
+
export { loadCollectionsFromDirectory, applyCollectionDefaults, type CollectionDefaults } from "./collections/loader.js";
|
|
14
|
+
export { assertCollectionConfigs, findCollectionConfigProblems, unknownKeyPolicyFromEnv, type ConfigProblem, type UnknownKeyPolicy, type ValidateCollectionConfigOptions } from "./collections/validate-config.js";
|
|
15
|
+
export * from "./db/interfaces.js";
|
|
16
|
+
export * from "./auth/interfaces.js";
|
|
17
|
+
export { requireAuth, requireAdmin, optionalAuth, queryTokenAuth, fileTokenAuth, extractUserFromToken, hashPassword, verifyPassword, validatePasswordStrength, generateSecurePassword, resolveAuthHooks, createBuiltinAuthAdapter, createCustomAuthAdapter, createGoogleProvider, createLinkedinProvider, createGitHubProvider, createMicrosoftProvider, createAppleProvider, createFacebookProvider, createTwitterProvider, createDiscordProvider, createGitLabProvider, createBitbucketProvider, createSlackProvider, createSpotifyProvider, oauthCodeFlowSchema, pkceTokenParams, providerVerifiedEmail, verifyOidcIdToken, tryVerifyOidcIdToken, isApiKeyToken, validateApiKey, httpMethodToOperation, isOperationAllowed, safeCompare, getJwks, hasAsymmetricSigningKey, createJwksRoutes } from "./auth/index.js";
|
|
18
|
+
export type { JwtSigningKeyConfig, JwtSigningAlgorithm, PublicJwk, AccessTokenPayload, PasswordValidationResult, AuthHooks, AuthMethod, ResolvedAuthHooks, BuiltinAuthAdapterConfig, GoogleProviderConfig, AuthMiddlewareOptions, AuthResult, ApiKey, ApiKeyMasked, ApiKeyPermission, ApiKeyWithSecret, CreateApiKeyRequest, UpdateApiKeyRequest, ApiKeyStore, ApiKeyOperation } from "./auth/index.js";
|
|
19
|
+
export * from "./api/types.js";
|
|
20
|
+
export * from "./api/errors.js";
|
|
21
|
+
export { assertWriteRequestValid } from "./api/rest/write-validation.js";
|
|
22
|
+
export * from "./email/index.js";
|
|
23
|
+
export * from "./storage/index.js";
|
|
24
|
+
export { compileStoragePolicies, resolveStorageAccessControl, StoragePolicyError } from "./storage/policies.js";
|
|
25
|
+
export type { StoragePolicy, StoragePolicyContext, StoragePolicyPredicate } from "./storage/policies.js";
|
|
26
|
+
export { createRlsAudit, summarize as summarizeRlsScan } from "./rls-audit/index.js";
|
|
27
|
+
export type { RlsAudit, RlsAuditConfig, RlsAuditStatus, RlsScanner, RlsScanResult, RlsScanFinding, RlsSeverity } from "./rls-audit/index.js";
|
|
28
|
+
export { createHistoryRoutes } from "./history/index.js";
|
|
29
|
+
export { loadFunctionsFromDirectory, loadFunctionsWithDiagnostics, createFunctionRoutes } from "./functions/internal.js";
|
|
30
|
+
export type { LoadedFunction, LoadedFunctions } from "./functions/internal.js";
|
|
31
|
+
export { defineFunction } from "./functions/index.js";
|
|
32
|
+
export type { RebaseFunctionContext } from "./functions/index.js";
|
|
33
|
+
export { getUser, getUserId, getRoles, hasRole, isAdmin, isAuthenticated, getDriver, requireDriver, getApiKey, getRequestId, identityResolved, requireRole, getEnv, requireEnv, runtimeKey, isNodeRuntime, lazyResource, waitUntil } from "./functions/index.js";
|
|
34
|
+
export type { FunctionUser } from "./functions/index.js";
|
|
35
|
+
export { drainBackgroundWork, pendingBackgroundWork } from "./functions/wait-until.js";
|
|
36
|
+
export { loadCronJobsFromDirectory, loadCronJobsWithDiagnostics, CronScheduler, validateCronExpression, createCronRoutes, createCronStore, defineCron } from "./cron/index.js";
|
|
37
|
+
export type { LoadedCronJob, CronStore } from "./cron/index.js";
|
|
38
|
+
export { createBackupRoutes, parseBackupDestination, parseBackupTimestamp, listBackupObjects, readBackupBytes } from "./backup/index.js";
|
|
39
|
+
export type { BackupRoutesConfig, BackupDestination } from "./backup/index.js";
|
|
40
|
+
export { authUid, authRoles, authJwt } from "./utils/sql.js";
|
|
41
|
+
export { logger } from "./utils/logger.js";
|
|
42
|
+
export type { Logger } from "./utils/logger.js";
|
|
43
|
+
export { loadEnv } from "./env.js";
|
|
44
|
+
export type { RebaseEnv } from "./env.js";
|
|
45
|
+
export * from "./types/index.js";
|
|
46
|
+
export * from "./services/driver-registry.js";
|
|
47
|
+
export * from "./services/webhook-service.js";
|
|
48
|
+
export * from "./services/outbound-url-guard.js";
|
|
49
|
+
export { createJobQueue, createJobStore, defaultBackoff } from "./jobs/index.js";
|
|
50
|
+
export type { JobQueue, JobStore, JobContext, JobHandler, JobRecord, JobStatus, JobQueueClient, JobQueueOptions, EnqueueOptions } from "./jobs/index.js";
|
|
51
|
+
export { cleanupDevPortFile, listenWithPortRetry } from "./utils/dev-port.js";
|
|
52
|
+
export { serveSPA } from "./serve-spa.js";
|
|
53
|
+
export { installShutdownHandlers } from "./init/shutdown.js";
|
|
54
|
+
export type { ShutdownHandlerOptions } from "./init/shutdown.js";
|
|
55
|
+
export { bootFromBundle, runFromBundle } from "./boot/boot.js";
|
|
56
|
+
export type { BootedRuntime, BootOptions, SchemaProvisioningOptions } from "./boot/boot.js";
|
|
57
|
+
export { createDdlBootstrapper, isConcurrentDdlRace, isDuplicateObjectRace, CONCURRENT_DDL_SQLSTATES } from "./boot/ddl-bootstrap.js";
|
|
58
|
+
export type { DdlBootstrapper, SqlExec } from "./boot/ddl-bootstrap.js";
|
|
59
|
+
export { BundleError, loadBundle, readBundleManifest, loadBundleConfigExports, createSourceBundle, loadBundleSchema, loadUsersCollection } from "./boot/bundle.js";
|
|
60
|
+
export type { LoadedBundle, BundleConfigExports } from "./boot/bundle.js";
|
|
61
|
+
export { loadBootEnv, resolveCorsOrigin, isLocalhostOrigin } from "./boot/env.js";
|
|
62
|
+
export type { RebaseBootEnv, CorsOriginResolver } from "./boot/env.js";
|
|
63
|
+
export { resolveAuthOptions, resolveEmailOptions } from "./boot/options.js";
|
|
64
|
+
export { envSuffixForKey, assertDistinctSuffixes, loadDeclaredStorageSources, resolveDataSources, resolveStorageSources, resolveStorageBackend } from "./boot/sources.js";
|
|
65
|
+
export type { ResolvedDataSourceConfig, EnvBag } from "./boot/sources.js";
|
|
66
|
+
export { initializeDataSource, initializeDataSources } from "./boot/driver.js";
|
|
67
|
+
export type { InitializedDataSource, DriverConnection, BundleSchema } from "./boot/driver.js";
|
|
68
|
+
export { MetricsRegistry, createMetricsMiddleware, createMetricsRoutes, classifySurface } from "./metrics/index.js";
|
|
69
|
+
export type { MetricSurface, MetricsHandle } from "./metrics/index.js";
|
|
70
|
+
export { createContractRoutes } from "./api/contract-routes.js";
|
|
71
|
+
export type { ContractRoutesConfig } from "./api/contract-routes.js";
|