@rebasepro/server 0.16.0 → 0.16.1-canary.g701140a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/dist/api/contract-routes.d.ts +1 -1
  2. package/dist/api/errors.d.ts +1 -1
  3. package/dist/api/index.d.ts +3 -3
  4. package/dist/api/logs-routes.d.ts +1 -1
  5. package/dist/api/rest/api-generator.d.ts +2 -2
  6. package/dist/api/rest/index.d.ts +1 -1
  7. package/dist/api/rest/query-parser.d.ts +1 -1
  8. package/dist/api/schema-editor-routes.d.ts +1 -1
  9. package/dist/api/types.d.ts +3 -4
  10. package/dist/auth/adapter-middleware.d.ts +2 -2
  11. package/dist/auth/admin-roles-route.d.ts +2 -2
  12. package/dist/auth/admin-user-ops.d.ts +3 -3
  13. package/dist/auth/admin-users-route.d.ts +4 -4
  14. package/dist/auth/api-keys/api-key-middleware.d.ts +2 -2
  15. package/dist/auth/api-keys/api-key-permission-guard.d.ts +1 -1
  16. package/dist/auth/api-keys/api-key-routes.d.ts +2 -2
  17. package/dist/auth/api-keys/api-key-store.d.ts +1 -1
  18. package/dist/auth/api-keys/index.d.ts +9 -9
  19. package/dist/auth/apple-oauth.d.ts +2 -2
  20. package/dist/auth/auth-hooks.d.ts +3 -3
  21. package/dist/auth/bitbucket-oauth.d.ts +2 -2
  22. package/dist/auth/builtin-auth-adapter.d.ts +7 -4
  23. package/dist/auth/captcha.d.ts +86 -0
  24. package/dist/auth/cookie-utils.d.ts +2 -2
  25. package/dist/auth/discord-oauth.d.ts +2 -2
  26. package/dist/auth/facebook-oauth.d.ts +2 -2
  27. package/dist/auth/github-oauth.d.ts +2 -2
  28. package/dist/auth/gitlab-oauth.d.ts +2 -2
  29. package/dist/auth/google-oauth.d.ts +1 -1
  30. package/dist/auth/index.d.ts +56 -54
  31. package/dist/auth/jwks-routes.d.ts +1 -1
  32. package/dist/auth/jwt.d.ts +1 -1
  33. package/dist/auth/linkedin-oauth.d.ts +2 -2
  34. package/dist/auth/magic-link-routes.d.ts +10 -3
  35. package/dist/auth/mfa-gate.d.ts +1 -1
  36. package/dist/auth/mfa-routes.d.ts +3 -3
  37. package/dist/auth/microsoft-oauth.d.ts +2 -2
  38. package/dist/auth/middleware.d.ts +5 -5
  39. package/dist/auth/rate-limiter.d.ts +2 -2
  40. package/dist/auth/require-auth.d.ts +1 -1
  41. package/dist/auth/reset-password-admin.d.ts +4 -4
  42. package/dist/auth/routes.d.ts +13 -4
  43. package/dist/auth/session-routes.d.ts +3 -3
  44. package/dist/auth/slack-oauth.d.ts +2 -2
  45. package/dist/auth/spotify-oauth.d.ts +2 -2
  46. package/dist/auth/token-revocation.d.ts +2 -2
  47. package/dist/auth/twitter-oauth.d.ts +2 -2
  48. package/dist/{auth-5Et5mnUA.js → auth-B0IV-irB.js} +250 -15
  49. package/dist/auth-B0IV-irB.js.map +1 -0
  50. package/dist/backup/backup-common.d.ts +1 -1
  51. package/dist/backup/backup-routes.d.ts +3 -3
  52. package/dist/backup/index.d.ts +3 -3
  53. package/dist/{backup-C6ljYVTp.js → backup-BJ86ah4T.js} +2 -2
  54. package/dist/{backup-C6ljYVTp.js.map → backup-BJ86ah4T.js.map} +1 -1
  55. package/dist/boot/boot.d.ts +5 -5
  56. package/dist/boot/bundle.d.ts +1 -1
  57. package/dist/boot/driver.d.ts +1 -1
  58. package/dist/boot/env.d.ts +13 -1
  59. package/dist/boot/options.d.ts +30 -6
  60. package/dist/boot/role.d.ts +3 -2
  61. package/dist/boot/sources.d.ts +1 -1
  62. package/dist/collections/BackendCollectionRegistry.d.ts +1 -1
  63. package/dist/collections/loader.d.ts +1 -1
  64. package/dist/{contract-routes-DZ-LBpSL.js → contract-routes-BEq7euZg.js} +2 -2
  65. package/dist/contract-routes-BEq7euZg.js.map +1 -0
  66. package/dist/cron/cron-routes.d.ts +2 -2
  67. package/dist/cron/cron-scheduler.d.ts +2 -2
  68. package/dist/cron/index.d.ts +8 -8
  69. package/dist/{cron-loader-YhhQeVBM.js → cron-loader-BMvtW6-J.js} +2 -2
  70. package/dist/{cron-loader-YhhQeVBM.js.map → cron-loader-BMvtW6-J.js.map} +1 -1
  71. package/dist/{cron-routes-maM_RlUu.js → cron-routes-BvYk-Kmi.js} +2 -2
  72. package/dist/{cron-routes-maM_RlUu.js.map → cron-routes-BvYk-Kmi.js.map} +1 -1
  73. package/dist/{cron-scheduler-DIpYBmZP.js → cron-scheduler-Buf-uVam.js} +2 -2
  74. package/dist/{cron-scheduler-DIpYBmZP.js.map → cron-scheduler-Buf-uVam.js.map} +1 -1
  75. package/dist/{cron-store-DfH_4Cd9.js → cron-store-CpfttP4f.js} +3 -3
  76. package/dist/{cron-store-DfH_4Cd9.js.map → cron-store-CpfttP4f.js.map} +1 -1
  77. package/dist/{ddl-bootstrap-Cywoj8Ta.js → ddl-bootstrap-CFLhcvfQ.js} +2 -2
  78. package/dist/{ddl-bootstrap-Cywoj8Ta.js.map → ddl-bootstrap-CFLhcvfQ.js.map} +1 -1
  79. package/dist/dev-secrets.d.ts +52 -0
  80. package/dist/email/dev-sink.d.ts +67 -0
  81. package/dist/email/index.d.ts +9 -7
  82. package/dist/email/link-base.d.ts +1 -1
  83. package/dist/email/smtp-email-service.d.ts +1 -1
  84. package/dist/env.d.ts +1 -1
  85. package/dist/{errors-EBYiaJ2E.js → errors-D_LwNKRM.js} +5 -5
  86. package/dist/errors-D_LwNKRM.js.map +1 -0
  87. package/dist/{function-loader-DDS1v7YX.js → function-loader-BrLmC_-y.js} +3 -3
  88. package/dist/{function-loader-DDS1v7YX.js.map → function-loader-BrLmC_-y.js.map} +1 -1
  89. package/dist/functions/context.d.ts +141 -0
  90. package/dist/functions/define-function.d.ts +1 -1
  91. package/dist/functions/function-routes.d.ts +2 -2
  92. package/dist/functions/guards.d.ts +76 -0
  93. package/dist/functions/index.d.ts +96 -5
  94. package/dist/functions/index.js +919 -0
  95. package/dist/functions/index.js.map +1 -0
  96. package/dist/functions/internal.d.ts +26 -0
  97. package/dist/functions/proxy.d.ts +1 -1
  98. package/dist/functions/request-timeout.d.ts +9 -1
  99. package/dist/functions/runtime-env.d.ts +92 -0
  100. package/dist/functions/wait-until.d.ts +74 -0
  101. package/dist/history/history-routes.d.ts +2 -2
  102. package/dist/history/index.d.ts +1 -1
  103. package/dist/index.d.ts +63 -54
  104. package/dist/index.es.js +1126 -53
  105. package/dist/index.es.js.map +1 -1
  106. package/dist/init/docs.d.ts +1 -1
  107. package/dist/init/middlewares.d.ts +1 -1
  108. package/dist/init/shutdown.d.ts +4 -0
  109. package/dist/init/storage.d.ts +1 -1
  110. package/dist/init/surfaces.d.ts +10 -0
  111. package/dist/init.d.ts +72 -18
  112. package/dist/jobs/index.d.ts +5 -5
  113. package/dist/{jobs-CyOKXXlu.js → jobs-CSlBZ8dn.js} +3 -3
  114. package/dist/{jobs-CyOKXXlu.js.map → jobs-CSlBZ8dn.js.map} +1 -1
  115. package/dist/{jwt-DxH9fLPt.js → jwt-BbJi0TR0.js} +2 -2
  116. package/dist/{jwt-DxH9fLPt.js.map → jwt-BbJi0TR0.js.map} +1 -1
  117. package/dist/{logger-DfvF_8r-.js → logger-TdvXIGqR.js} +101 -8
  118. package/dist/logger-TdvXIGqR.js.map +1 -0
  119. package/dist/metrics/index.d.ts +1 -1
  120. package/dist/{proxy-Bj5DVllb.js → proxy-QJKSS-CV.js} +5 -3
  121. package/dist/{proxy-Bj5DVllb.js.map → proxy-QJKSS-CV.js.map} +1 -1
  122. package/dist/{request-timeout-BuFoEKwT.js → request-timeout-OofPCHQT.js} +17 -3
  123. package/dist/request-timeout-OofPCHQT.js.map +1 -0
  124. package/dist/rls-audit/index.d.ts +111 -0
  125. package/dist/{schema-editor-routes-CV9k0w3G.js → schema-editor-routes-Cax2XTRj.js} +2 -2
  126. package/dist/{schema-editor-routes-CV9k0w3G.js.map → schema-editor-routes-Cax2XTRj.js.map} +1 -1
  127. package/dist/services/webhook-service.d.ts +1 -1
  128. package/dist/singleton.d.ts +7 -0
  129. package/dist/storage/GCSStorageController.d.ts +1 -1
  130. package/dist/storage/LocalStorageController.d.ts +1 -1
  131. package/dist/storage/S3StorageController.d.ts +1 -1
  132. package/dist/storage/cache-headers.d.ts +87 -0
  133. package/dist/storage/index.d.ts +11 -11
  134. package/dist/storage/policies.d.ts +88 -0
  135. package/dist/storage/range.d.ts +63 -0
  136. package/dist/storage/routes.d.ts +3 -3
  137. package/dist/storage/storage-registry.d.ts +1 -1
  138. package/dist/storage/tus-handler.d.ts +2 -2
  139. package/dist/utils/host.d.ts +58 -0
  140. package/dist/utils/logger.d.ts +0 -15
  141. package/dist/utils/request-id.d.ts +1 -1
  142. package/functions/package.json +24 -0
  143. package/package.json +13 -7
  144. package/dist/auth-5Et5mnUA.js.map +0 -1
  145. package/dist/contract-routes-DZ-LBpSL.js.map +0 -1
  146. package/dist/errors-EBYiaJ2E.js.map +0 -1
  147. package/dist/logger-DfvF_8r-.js.map +0 -1
  148. package/dist/request-timeout-BuFoEKwT.js.map +0 -1
@@ -1,7 +1,100 @@
1
1
  import { createRequire as __createRequire } from "module";
2
- import process from "process";
2
+ import "process";
3
3
  __createRequire(import.meta.url);
4
+ //#region src/utils/host.ts
5
+ /**
6
+ * The host globals this package reads, behind functions that do not assume Node.
7
+ *
8
+ * `process` is not defined on workerd, on Deno Deploy without the compat flag,
9
+ * or in a browser. That matters here for one specific reason: the portable
10
+ * authoring surface (`@rebasepro/server/functions`) reaches the logger and the
11
+ * error handler, and a bare `process.env.NODE_ENV` inside either of them turns
12
+ * the first log line of a request into a `ReferenceError` on a runtime that has
13
+ * no `process` — a failure that reads as "the framework crashed" rather than
14
+ * "this runtime has no process object".
15
+ *
16
+ * Nothing here throws and nothing here is async. A runtime that cannot answer
17
+ * gets the empty answer, because every caller in this file's blast radius is
18
+ * choosing a log level or a format, and the safe default for both is the
19
+ * development one.
20
+ *
21
+ * @module
22
+ */
23
+ /**
24
+ * Where an adapter with no `process` can publish the environment.
25
+ *
26
+ * Cloudflare Workers hand the environment to the *request*, not to the module,
27
+ * so there is no global to read at import time. An edge adapter that has
28
+ * already seen a request can stash the bag here and every contextless reader in
29
+ * the framework — the logger, chiefly — starts answering correctly.
30
+ *
31
+ * `Symbol.for` rather than a module-local for the same reason the singleton
32
+ * uses it: more than one copy of this module can be loaded into one process,
33
+ * and a module-local would leave every copy but the writer's blind. See
34
+ * `../singleton.ts`.
35
+ *
36
+ * Request-scoped code should NOT read this. Use `getEnv(c)` from
37
+ * `@rebasepro/server/functions`, which reads the binding attached to the
38
+ * request it is serving — the only correct source on a runtime where two
39
+ * concurrent requests can carry different bindings.
40
+ */
41
+ var ENV_SLOT = Symbol.for("@rebasepro/server:host-env");
42
+ /**
43
+ * The process environment, or the closest thing this runtime has to one.
44
+ *
45
+ * Order: a bag published by {@link setHostEnv} first, because an adapter that
46
+ * set one knows more than the ambient globals do; then `process.env`; then
47
+ * nothing.
48
+ */
49
+ function hostEnv() {
50
+ const global = globalThis;
51
+ return global[ENV_SLOT] ?? global.process?.env ?? {};
52
+ }
53
+ /**
54
+ * Write one already-formatted line to the process's output.
55
+ *
56
+ * `process.stdout.write` is preferred where it exists because it is the only
57
+ * one of the two that does not append its own formatting to a line that is
58
+ * already a complete JSON document — `console.log` on Node is
59
+ * `process.stdout.write` plus `util.format`, and `util.format` will happily
60
+ * reinterpret a `%s` that appeared inside a user's log message.
61
+ *
62
+ * Where it does not exist, `console` is the runtime's log sink and is what its
63
+ * platform collects.
64
+ */
65
+ function writeLine(stream, line) {
66
+ const proc = globalThis.process;
67
+ const sink = stream === "err" ? proc?.stderr : proc?.stdout;
68
+ if (typeof sink?.write === "function") {
69
+ sink.write(line + "\n");
70
+ return;
71
+ }
72
+ if (stream === "err") console.error(line);
73
+ else console.log(line);
74
+ }
75
+ //#endregion
4
76
  //#region src/utils/logger.ts
77
+ /**
78
+ * Structured Logger for Rebase Backend
79
+ *
80
+ * Outputs JSON lines when `NODE_ENV=production`, human-readable prefixed
81
+ * lines otherwise. Designed to work with Google Cloud Logging severity levels.
82
+ *
83
+ * Every line — message and data, at any depth — passes through the redaction
84
+ * below, which strips Drizzle's `Failed query: … / params: …` wrapper and the
85
+ * values of secret-looking keys. See the block above `serialiseError`.
86
+ *
87
+ * Usage:
88
+ * import { logger } from "./utils/logger";
89
+ * logger.info("Server started", { port: 3001 });
90
+ * logger.error("Request failed", { path: "/api/test", error: err });
91
+ *
92
+ * Every host global goes through `./host`, and that is load-bearing rather than
93
+ * tidy: this module is reachable from `@rebasepro/server/functions`, the
94
+ * authoring surface that has to import cleanly on a runtime with no `process`.
95
+ * A bare `process.env.NODE_ENV` here would make the first log line of the first
96
+ * request on workerd a `ReferenceError`.
97
+ */
5
98
  /** Google Cloud Logging severity strings. */
6
99
  var GCP_SEVERITY = {
7
100
  debug: "DEBUG",
@@ -16,10 +109,10 @@ var LOG_PRIORITY = {
16
109
  error: 3
17
110
  };
18
111
  function isProduction() {
19
- return process.env.NODE_ENV === "production";
112
+ return hostEnv().NODE_ENV === "production";
20
113
  }
21
114
  function getMinLevel() {
22
- const env = (process.env.LOG_LEVEL || "info").toLowerCase();
115
+ const env = (hostEnv().LOG_LEVEL || "info").toLowerCase();
23
116
  if (env in LOG_PRIORITY) return env;
24
117
  return "info";
25
118
  }
@@ -57,7 +150,7 @@ function isSensitiveKey(key) {
57
150
  * it, and it never re-enables the key deny-list.
58
151
  */
59
152
  function rawQueriesAllowed() {
60
- return process.env.NODE_ENV !== "production" && process.env.REBASE_LOG_RAW_QUERIES === "true";
153
+ return hostEnv().NODE_ENV !== "production" && hostEnv().REBASE_LOG_RAW_QUERIES === "true";
61
154
  }
62
155
  /**
63
156
  * Strip every `Failed query: … / params: …` span out of a message or stack,
@@ -154,8 +247,8 @@ function createLogger(rawDefaultFields = {}) {
154
247
  ...merged
155
248
  };
156
249
  const line = JSON.stringify(entry);
157
- if (level === "error") process.stderr.write(line + "\n");
158
- else process.stdout.write(line + "\n");
250
+ if (level === "error") writeLine("err", line);
251
+ else writeLine("out", line);
159
252
  } else {
160
253
  const prefix = level === "error" ? "❌" : level === "warn" ? "⚠️" : level === "info" ? "ℹ️" : "🐛";
161
254
  const extra = Object.keys(merged).length > 0 ? ` ${JSON.stringify(merged)}` : "";
@@ -185,6 +278,6 @@ function createLogger(rawDefaultFields = {}) {
185
278
  */
186
279
  var logger = createLogger();
187
280
  //#endregion
188
- export { redactSensitiveText as n, logger as t };
281
+ export { redactSensitiveText as n, hostEnv as r, logger as t };
189
282
 
190
- //# sourceMappingURL=logger-DfvF_8r-.js.map
283
+ //# sourceMappingURL=logger-TdvXIGqR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger-TdvXIGqR.js","names":[],"sources":["../src/utils/host.ts","../src/utils/logger.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","/**\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"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { Hono } from "hono";
2
2
  import type { MiddlewareHandler } from "hono";
3
- import type { HonoEnv } from "../api/types";
3
+ import type { HonoEnv } from "../api/types.js";
4
4
  /**
5
5
  * Runtime metrics, in Prometheus text format.
6
6
  *
@@ -1,7 +1,8 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  import "process";
3
3
  __createRequire(import.meta.url);
4
- import { t as logger } from "./logger-DfvF_8r-.js";
4
+ import { n as __exportAll } from "./rolldown-runtime-DSJWtz9O.js";
5
+ import { t as logger } from "./logger-TdvXIGqR.js";
5
6
  import { Hono } from "hono";
6
7
  import { getConnInfo } from "@hono/node-server/conninfo";
7
8
  //#region src/functions/proxy.ts
@@ -19,6 +20,7 @@ import { getConnInfo } from "@hono/node-server/conninfo";
19
20
  * `JWT_SECRET` and must see the original `Authorization` header, so re-deciding
20
21
  * anything on the way through would put two answers where the system has one.
21
22
  */
23
+ var proxy_exports = /* @__PURE__ */ __exportAll({ createFunctionsProxy: () => createFunctionsProxy });
22
24
  /**
23
25
  * Headers that describe *this* connection rather than the request, and so must
24
26
  * not be copied onto a new one.
@@ -134,6 +136,6 @@ function socketAddress(c) {
134
136
  }
135
137
  }
136
138
  //#endregion
137
- export { createFunctionsProxy };
139
+ export { proxy_exports as t };
138
140
 
139
- //# sourceMappingURL=proxy-Bj5DVllb.js.map
141
+ //# sourceMappingURL=proxy-QJKSS-CV.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"proxy-Bj5DVllb.js","names":[],"sources":["../src/functions/proxy.ts"],"sourcesContent":["/**\n * Forwarding `/api/functions/*` to the process that serves it.\n *\n * A split deployment normally puts a reverse proxy in front and routes the path\n * there. This exists so it does not have to: set `REBASE_FUNCTIONS_UPSTREAM` on\n * the API process and the two-container topology presents the identical URL\n * surface as the one-container one, with nothing new to install and nothing for\n * a client or an SDK to know about.\n *\n * It is a transport hop, not a trust boundary. Nothing is authenticated or\n * authorised here: the upstream runs the same auth middleware against the same\n * `JWT_SECRET` and must see the original `Authorization` header, so re-deciding\n * anything on the way through would put two answers where the system has one.\n */\nimport { Hono, type Context } from \"hono\";\nimport { getConnInfo } from \"@hono/node-server/conninfo\";\nimport type { HonoEnv } from \"../api/types\";\nimport { logger } from \"../utils/logger\";\n\n/**\n * Headers that describe *this* connection rather than the request, and so must\n * not be copied onto a new one.\n *\n * `host` is the important one: forwarded unchanged it names the API process, and\n * an upstream doing any virtual-host routing would answer for the wrong service.\n * `content-length` and `transfer-encoding` are recomputed by `fetch` from the\n * body it is actually given, and a stale value is a hang or a truncation.\n */\nconst HOP_BY_HOP = new Set([\n \"connection\",\n \"keep-alive\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n \"te\",\n \"trailer\",\n \"transfer-encoding\",\n \"upgrade\",\n \"host\",\n \"content-length\"\n]);\n\n/**\n * Additionally dropped from the *response*, because `fetch` has already acted on\n * them by the time we see the body.\n *\n * `Content-Encoding` is the one that matters and the one that is easy to miss:\n * undici decompresses a `gzip` response transparently, but leaves the header on\n * the `Response` object. Copy it onto the body we hand back and the client is\n * told to gunzip bytes that are already plain — which does not fail cleanly. It\n * hangs, or it surfaces as a decode error several layers away from the proxy\n * that caused it. (This is not hypothetical: the runtime compresses its own\n * responses, so *every* forwarded response carries the header.)\n */\nconst RESPONSE_ONLY_DROP = new Set([\"content-encoding\", \"content-length\"]);\n\nexport interface FunctionsProxyOptions {\n /** Base URL of the process serving the functions, e.g. `http://functions:8080`. */\n upstream: string;\n /** The mount path being forwarded, e.g. `/api/functions`. */\n basePath: string;\n}\n\n/**\n * A router that forwards everything under its mount point to `upstream`.\n *\n * Method, path, query string, body and headers travel verbatim, with two\n * deliberate exceptions:\n *\n * 1. Hop-by-hop headers are dropped ({@link HOP_BY_HOP}).\n * 2. `X-Forwarded-For` gains the address this process saw the request come from.\n * This is not decoration — the upstream's rate limiters and the auth routes'\n * IP logging read that chain, and without the append every forwarded request\n * arrives from one address: the API pod's. Every caller would share a single\n * rate-limit bucket and every login would be logged from the same IP.\n *\n * **The upstream must therefore count one more proxy hop than the API process\n * does.** `TRUSTED_PROXY_HOPS` on the functions process is the API's value\n * plus one. Left unset it defaults to 0, and the upstream falls back to the\n * socket address — again, the API pod's — which is safe but useless.\n */\nexport function createFunctionsProxy(options: FunctionsProxyOptions): Hono<HonoEnv> {\n const router = new Hono<HonoEnv>();\n // Trailing slash trimmed once, here: joining it per request is how a proxy\n // ends up asking for `//functions/x`, which some servers route and some 404.\n const upstream = options.upstream.replace(/\\/+$/, \"\");\n\n router.all(\"/*\", async (c) => {\n const incoming = new URL(c.req.url);\n // The path *below* the mount point. Hono gives the full request path, so\n // taking the suffix is what keeps the upstream's own mount identical:\n // `/api/functions/send-invoice` here becomes `/api/functions/send-invoice`\n // there, not `/api/functions/api/functions/send-invoice`.\n const suffix = incoming.pathname.startsWith(options.basePath)\n ? incoming.pathname.slice(options.basePath.length)\n : incoming.pathname;\n const target = `${upstream}${options.basePath}${suffix}${incoming.search}`;\n\n const headers = new Headers();\n for (const [name, value] of c.req.raw.headers) {\n if (!HOP_BY_HOP.has(name.toLowerCase())) headers.set(name, value);\n }\n\n const seenFrom = socketAddress(c);\n if (seenFrom) {\n const chain = c.req.header(\"x-forwarded-for\");\n headers.set(\"x-forwarded-for\", chain ? `${chain}, ${seenFrom}` : seenFrom);\n }\n\n try {\n const response = await fetch(target, {\n method: c.req.method,\n headers,\n // GET and HEAD may not carry one; everything else streams\n // through rather than being buffered, so a large upload does not\n // have to fit in this process's memory on its way past.\n body: c.req.method === \"GET\" || c.req.method === \"HEAD\" ? undefined : c.req.raw.body,\n // Required by undici whenever the body is a stream. Without it\n // the fetch rejects with a message about duplex that says\n // nothing about what to do.\n duplex: \"half\",\n redirect: \"manual\"\n } as RequestInit);\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: responseHeaders(response.headers)\n });\n } catch (err) {\n // The upstream being down is an infrastructure fact, not a bug in\n // the caller's request, and 502 is the one status that says so.\n // Named explicitly in the body because the alternative — a generic\n // 500 from the API process — sends people reading the wrong logs.\n logger.error(\"Functions upstream unreachable\", {\n upstream,\n path: incoming.pathname,\n error: err instanceof Error ? err : new Error(String(err))\n });\n return c.json({\n error: {\n code: \"FUNCTIONS_UPSTREAM_UNREACHABLE\",\n message: \"The process serving custom functions could not be reached from this one.\"\n }\n }, 502);\n }\n });\n\n return router;\n}\n\n/**\n * Response headers, minus the ones that describe the upstream's connection and\n * the ones `fetch` has already consumed on our behalf.\n */\nfunction responseHeaders(headers: Headers): Headers {\n const out = new Headers();\n for (const [name, value] of headers) {\n const lower = name.toLowerCase();\n if (HOP_BY_HOP.has(lower) || RESPONSE_ONLY_DROP.has(lower)) continue;\n out.set(name, value);\n }\n return out;\n}\n\n/** The address this process saw, or undefined on a runtime that cannot say. */\nfunction socketAddress(c: Context<HonoEnv>): string | undefined {\n try {\n return getConnInfo(c).remote.address;\n } catch {\n return undefined;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,IAAM,6BAAa,IAAI,IAAI;CACvB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ,CAAC;;;;;;;;;;;;;AAcD,IAAM,qCAAqB,IAAI,IAAI,CAAC,oBAAoB,gBAAgB,CAAC;;;;;;;;;;;;;;;;;;;AA2BzE,SAAgB,qBAAqB,SAA+C;CAChF,MAAM,SAAS,IAAI,KAAc;CAGjC,MAAM,WAAW,QAAQ,SAAS,QAAQ,QAAQ,EAAE;CAEpD,OAAO,IAAI,MAAM,OAAO,MAAM;EAC1B,MAAM,WAAW,IAAI,IAAI,EAAE,IAAI,GAAG;EAKlC,MAAM,SAAS,SAAS,SAAS,WAAW,QAAQ,QAAQ,IACtD,SAAS,SAAS,MAAM,QAAQ,SAAS,MAAM,IAC/C,SAAS;EACf,MAAM,SAAS,GAAG,WAAW,QAAQ,WAAW,SAAS,SAAS;EAElE,MAAM,UAAU,IAAI,QAAQ;EAC5B,KAAK,MAAM,CAAC,MAAM,UAAU,EAAE,IAAI,IAAI,SAClC,IAAI,CAAC,WAAW,IAAI,KAAK,YAAY,CAAC,GAAG,QAAQ,IAAI,MAAM,KAAK;EAGpE,MAAM,WAAW,cAAc,CAAC;EAChC,IAAI,UAAU;GACV,MAAM,QAAQ,EAAE,IAAI,OAAO,iBAAiB;GAC5C,QAAQ,IAAI,mBAAmB,QAAQ,GAAG,MAAM,IAAI,aAAa,QAAQ;EAC7E;EAEA,IAAI;GACA,MAAM,WAAW,MAAM,MAAM,QAAQ;IACjC,QAAQ,EAAE,IAAI;IACd;IAIA,MAAM,EAAE,IAAI,WAAW,SAAS,EAAE,IAAI,WAAW,SAAS,KAAA,IAAY,EAAE,IAAI,IAAI;IAIhF,QAAQ;IACR,UAAU;GACd,CAAgB;GAEhB,OAAO,IAAI,SAAS,SAAS,MAAM;IAC/B,QAAQ,SAAS;IACjB,YAAY,SAAS;IACrB,SAAS,gBAAgB,SAAS,OAAO;GAC7C,CAAC;EACL,SAAS,KAAK;GAKV,OAAO,MAAM,kCAAkC;IAC3C;IACA,MAAM,SAAS;IACf,OAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;GAC7D,CAAC;GACD,OAAO,EAAE,KAAK,EACV,OAAO;IACH,MAAM;IACN,SAAS;GACb,EACJ,GAAG,GAAG;EACV;CACJ,CAAC;CAED,OAAO;AACX;;;;;AAMA,SAAS,gBAAgB,SAA2B;CAChD,MAAM,MAAM,IAAI,QAAQ;CACxB,KAAK,MAAM,CAAC,MAAM,UAAU,SAAS;EACjC,MAAM,QAAQ,KAAK,YAAY;EAC/B,IAAI,WAAW,IAAI,KAAK,KAAK,mBAAmB,IAAI,KAAK,GAAG;EAC5D,IAAI,IAAI,MAAM,KAAK;CACvB;CACA,OAAO;AACX;;AAGA,SAAS,cAAc,GAAyC;CAC5D,IAAI;EACA,OAAO,YAAY,CAAC,CAAC,CAAC,OAAO;CACjC,QAAQ;EACJ;CACJ;AACJ"}
1
+ {"version":3,"file":"proxy-QJKSS-CV.js","names":[],"sources":["../src/functions/proxy.ts"],"sourcesContent":["/**\n * Forwarding `/api/functions/*` to the process that serves it.\n *\n * A split deployment normally puts a reverse proxy in front and routes the path\n * there. This exists so it does not have to: set `REBASE_FUNCTIONS_UPSTREAM` on\n * the API process and the two-container topology presents the identical URL\n * surface as the one-container one, with nothing new to install and nothing for\n * a client or an SDK to know about.\n *\n * It is a transport hop, not a trust boundary. Nothing is authenticated or\n * authorised here: the upstream runs the same auth middleware against the same\n * `JWT_SECRET` and must see the original `Authorization` header, so re-deciding\n * anything on the way through would put two answers where the system has one.\n */\nimport { Hono, type Context } from \"hono\";\nimport { getConnInfo } from \"@hono/node-server/conninfo\";\nimport type { HonoEnv } from \"../api/types\";\nimport { logger } from \"../utils/logger\";\n\n/**\n * Headers that describe *this* connection rather than the request, and so must\n * not be copied onto a new one.\n *\n * `host` is the important one: forwarded unchanged it names the API process, and\n * an upstream doing any virtual-host routing would answer for the wrong service.\n * `content-length` and `transfer-encoding` are recomputed by `fetch` from the\n * body it is actually given, and a stale value is a hang or a truncation.\n */\nconst HOP_BY_HOP = new Set([\n \"connection\",\n \"keep-alive\",\n \"proxy-authenticate\",\n \"proxy-authorization\",\n \"te\",\n \"trailer\",\n \"transfer-encoding\",\n \"upgrade\",\n \"host\",\n \"content-length\"\n]);\n\n/**\n * Additionally dropped from the *response*, because `fetch` has already acted on\n * them by the time we see the body.\n *\n * `Content-Encoding` is the one that matters and the one that is easy to miss:\n * undici decompresses a `gzip` response transparently, but leaves the header on\n * the `Response` object. Copy it onto the body we hand back and the client is\n * told to gunzip bytes that are already plain — which does not fail cleanly. It\n * hangs, or it surfaces as a decode error several layers away from the proxy\n * that caused it. (This is not hypothetical: the runtime compresses its own\n * responses, so *every* forwarded response carries the header.)\n */\nconst RESPONSE_ONLY_DROP = new Set([\"content-encoding\", \"content-length\"]);\n\nexport interface FunctionsProxyOptions {\n /** Base URL of the process serving the functions, e.g. `http://functions:8080`. */\n upstream: string;\n /** The mount path being forwarded, e.g. `/api/functions`. */\n basePath: string;\n}\n\n/**\n * A router that forwards everything under its mount point to `upstream`.\n *\n * Method, path, query string, body and headers travel verbatim, with two\n * deliberate exceptions:\n *\n * 1. Hop-by-hop headers are dropped ({@link HOP_BY_HOP}).\n * 2. `X-Forwarded-For` gains the address this process saw the request come from.\n * This is not decoration — the upstream's rate limiters and the auth routes'\n * IP logging read that chain, and without the append every forwarded request\n * arrives from one address: the API pod's. Every caller would share a single\n * rate-limit bucket and every login would be logged from the same IP.\n *\n * **The upstream must therefore count one more proxy hop than the API process\n * does.** `TRUSTED_PROXY_HOPS` on the functions process is the API's value\n * plus one. Left unset it defaults to 0, and the upstream falls back to the\n * socket address — again, the API pod's — which is safe but useless.\n */\nexport function createFunctionsProxy(options: FunctionsProxyOptions): Hono<HonoEnv> {\n const router = new Hono<HonoEnv>();\n // Trailing slash trimmed once, here: joining it per request is how a proxy\n // ends up asking for `//functions/x`, which some servers route and some 404.\n const upstream = options.upstream.replace(/\\/+$/, \"\");\n\n router.all(\"/*\", async (c) => {\n const incoming = new URL(c.req.url);\n // The path *below* the mount point. Hono gives the full request path, so\n // taking the suffix is what keeps the upstream's own mount identical:\n // `/api/functions/send-invoice` here becomes `/api/functions/send-invoice`\n // there, not `/api/functions/api/functions/send-invoice`.\n const suffix = incoming.pathname.startsWith(options.basePath)\n ? incoming.pathname.slice(options.basePath.length)\n : incoming.pathname;\n const target = `${upstream}${options.basePath}${suffix}${incoming.search}`;\n\n const headers = new Headers();\n for (const [name, value] of c.req.raw.headers) {\n if (!HOP_BY_HOP.has(name.toLowerCase())) headers.set(name, value);\n }\n\n const seenFrom = socketAddress(c);\n if (seenFrom) {\n const chain = c.req.header(\"x-forwarded-for\");\n headers.set(\"x-forwarded-for\", chain ? `${chain}, ${seenFrom}` : seenFrom);\n }\n\n try {\n const response = await fetch(target, {\n method: c.req.method,\n headers,\n // GET and HEAD may not carry one; everything else streams\n // through rather than being buffered, so a large upload does not\n // have to fit in this process's memory on its way past.\n body: c.req.method === \"GET\" || c.req.method === \"HEAD\" ? undefined : c.req.raw.body,\n // Required by undici whenever the body is a stream. Without it\n // the fetch rejects with a message about duplex that says\n // nothing about what to do.\n duplex: \"half\",\n redirect: \"manual\"\n } as RequestInit);\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: responseHeaders(response.headers)\n });\n } catch (err) {\n // The upstream being down is an infrastructure fact, not a bug in\n // the caller's request, and 502 is the one status that says so.\n // Named explicitly in the body because the alternative — a generic\n // 500 from the API process — sends people reading the wrong logs.\n logger.error(\"Functions upstream unreachable\", {\n upstream,\n path: incoming.pathname,\n error: err instanceof Error ? err : new Error(String(err))\n });\n return c.json({\n error: {\n code: \"FUNCTIONS_UPSTREAM_UNREACHABLE\",\n message: \"The process serving custom functions could not be reached from this one.\"\n }\n }, 502);\n }\n });\n\n return router;\n}\n\n/**\n * Response headers, minus the ones that describe the upstream's connection and\n * the ones `fetch` has already consumed on our behalf.\n */\nfunction responseHeaders(headers: Headers): Headers {\n const out = new Headers();\n for (const [name, value] of headers) {\n const lower = name.toLowerCase();\n if (HOP_BY_HOP.has(lower) || RESPONSE_ONLY_DROP.has(lower)) continue;\n out.set(name, value);\n }\n return out;\n}\n\n/** The address this process saw, or undefined on a runtime that cannot say. */\nfunction socketAddress(c: Context<HonoEnv>): string | undefined {\n try {\n return getConnInfo(c).remote.address;\n } catch {\n return undefined;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,IAAM,6BAAa,IAAI,IAAI;CACvB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACJ,CAAC;;;;;;;;;;;;;AAcD,IAAM,qCAAqB,IAAI,IAAI,CAAC,oBAAoB,gBAAgB,CAAC;;;;;;;;;;;;;;;;;;;AA2BzE,SAAgB,qBAAqB,SAA+C;CAChF,MAAM,SAAS,IAAI,KAAc;CAGjC,MAAM,WAAW,QAAQ,SAAS,QAAQ,QAAQ,EAAE;CAEpD,OAAO,IAAI,MAAM,OAAO,MAAM;EAC1B,MAAM,WAAW,IAAI,IAAI,EAAE,IAAI,GAAG;EAKlC,MAAM,SAAS,SAAS,SAAS,WAAW,QAAQ,QAAQ,IACtD,SAAS,SAAS,MAAM,QAAQ,SAAS,MAAM,IAC/C,SAAS;EACf,MAAM,SAAS,GAAG,WAAW,QAAQ,WAAW,SAAS,SAAS;EAElE,MAAM,UAAU,IAAI,QAAQ;EAC5B,KAAK,MAAM,CAAC,MAAM,UAAU,EAAE,IAAI,IAAI,SAClC,IAAI,CAAC,WAAW,IAAI,KAAK,YAAY,CAAC,GAAG,QAAQ,IAAI,MAAM,KAAK;EAGpE,MAAM,WAAW,cAAc,CAAC;EAChC,IAAI,UAAU;GACV,MAAM,QAAQ,EAAE,IAAI,OAAO,iBAAiB;GAC5C,QAAQ,IAAI,mBAAmB,QAAQ,GAAG,MAAM,IAAI,aAAa,QAAQ;EAC7E;EAEA,IAAI;GACA,MAAM,WAAW,MAAM,MAAM,QAAQ;IACjC,QAAQ,EAAE,IAAI;IACd;IAIA,MAAM,EAAE,IAAI,WAAW,SAAS,EAAE,IAAI,WAAW,SAAS,KAAA,IAAY,EAAE,IAAI,IAAI;IAIhF,QAAQ;IACR,UAAU;GACd,CAAgB;GAEhB,OAAO,IAAI,SAAS,SAAS,MAAM;IAC/B,QAAQ,SAAS;IACjB,YAAY,SAAS;IACrB,SAAS,gBAAgB,SAAS,OAAO;GAC7C,CAAC;EACL,SAAS,KAAK;GAKV,OAAO,MAAM,kCAAkC;IAC3C;IACA,MAAM,SAAS;IACf,OAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,GAAG,CAAC;GAC7D,CAAC;GACD,OAAO,EAAE,KAAK,EACV,OAAO;IACH,MAAM;IACN,SAAS;GACb,EACJ,GAAG,GAAG;EACV;CACJ,CAAC;CAED,OAAO;AACX;;;;;AAMA,SAAS,gBAAgB,SAA2B;CAChD,MAAM,MAAM,IAAI,QAAQ;CACxB,KAAK,MAAM,CAAC,MAAM,UAAU,SAAS;EACjC,MAAM,QAAQ,KAAK,YAAY;EAC/B,IAAI,WAAW,IAAI,KAAK,KAAK,mBAAmB,IAAI,KAAK,GAAG;EAC5D,IAAI,IAAI,MAAM,KAAK;CACvB;CACA,OAAO;AACX;;AAGA,SAAS,cAAc,GAAyC;CAC5D,IAAI;EACA,OAAO,YAAY,CAAC,CAAC,CAAC,OAAO;CACjC,QAAQ;EACJ;CACJ;AACJ"}
@@ -1,8 +1,14 @@
1
1
  import { createRequire as __createRequire } from "module";
2
2
  import process from "process";
3
3
  __createRequire(import.meta.url);
4
- import { t as logger } from "./logger-DfvF_8r-.js";
4
+ import { n as __exportAll } from "./rolldown-runtime-DSJWtz9O.js";
5
+ import { t as logger } from "./logger-TdvXIGqR.js";
5
6
  //#region src/functions/request-timeout.ts
7
+ var request_timeout_exports = /* @__PURE__ */ __exportAll({
8
+ DEFAULT_FUNCTIONS_TIMEOUT_MS: () => DEFAULT_FUNCTIONS_TIMEOUT_MS,
9
+ createFunctionsRequestTimeout: () => createFunctionsRequestTimeout,
10
+ resolveFunctionsTimeoutMs: () => resolveFunctionsTimeoutMs
11
+ });
6
12
  /** Default ceiling for a custom function request, in milliseconds. */
7
13
  var DEFAULT_FUNCTIONS_TIMEOUT_MS = 3e4;
8
14
  /**
@@ -39,6 +45,14 @@ function resolveFunctionsTimeoutMs(configured) {
39
45
  * eventual result is dropped. It is a ceiling, not a kill switch, and the exact
40
46
  * number matters far less than its existence.
41
47
  *
48
+ * "The handler keeps running" is a **Node** guarantee, not a property of the
49
+ * contract. It follows from the process outliving the request, which is not
50
+ * true on an isolate-based host: there, work still in flight when the response
51
+ * resolves is terminated rather than orphaned. So a handler must not depend on
52
+ * finishing after its 504 — anything that has to complete belongs in
53
+ * `waitUntil()`, which is the one construct both hosts honour. See
54
+ * `./wait-until.ts`.
55
+ *
42
56
  * Mounted in front of the auth middleware rather than behind it, so a wedged
43
57
  * driver — the failure that also hangs `withAuth()` — is covered too.
44
58
  */
@@ -63,6 +77,6 @@ function createFunctionsRequestTimeout(ms) {
63
77
  };
64
78
  }
65
79
  //#endregion
66
- export { createFunctionsRequestTimeout, resolveFunctionsTimeoutMs };
80
+ export { request_timeout_exports as t };
67
81
 
68
- //# sourceMappingURL=request-timeout-BuFoEKwT.js.map
82
+ //# sourceMappingURL=request-timeout-OofPCHQT.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-timeout-OofPCHQT.js","names":[],"sources":["../src/functions/request-timeout.ts"],"sourcesContent":["import type { MiddlewareHandler } from \"hono\";\nimport type { HonoEnv } from \"../api/types\";\nimport { logger } from \"../utils/logger\";\n\n/** Default ceiling for a custom function request, in milliseconds. */\nexport const DEFAULT_FUNCTIONS_TIMEOUT_MS = 30_000;\n\n/**\n * Resolve the functions request timeout from config, then env, then the default.\n *\n * `0` (or any non-positive number) disables the ceiling — for a deployment\n * whose proxy already imposes one, or a function that legitimately streams for\n * minutes.\n */\nexport function resolveFunctionsTimeoutMs(configured?: number): number {\n if (typeof configured === \"number\" && Number.isFinite(configured)) {\n return Math.max(0, Math.floor(configured));\n }\n // Blank means unset, not zero. `Number(\"\")` and `Number(\" \")` are both 0,\n // and 0 here means \"no ceiling\" — so a compose file with\n // `REBASE_FUNCTIONS_TIMEOUT_MS=${SOMETHING}` and `SOMETHING` undefined, or\n // a `.env` line with the name and no value, silently switched off the one\n // bound on how long code the framework did not write may hold a socket.\n // Declaring a variable without setting it is the ordinary way to write\n // both of those files, and the failure is invisible: nothing logs, and the\n // deployment behaves exactly as it did before the ceiling existed.\n const raw = process.env.REBASE_FUNCTIONS_TIMEOUT_MS?.trim();\n if (raw) {\n const fromEnv = Number(raw);\n if (Number.isFinite(fromEnv) && fromEnv >= 0) {\n return Math.floor(fromEnv);\n }\n }\n return DEFAULT_FUNCTIONS_TIMEOUT_MS;\n}\n\n/**\n * A per-request ceiling for the custom functions router.\n *\n * Custom functions are the one router that runs code the framework did not\n * write, and nothing else in the stack bounds how long that code takes: the\n * Node server is constructed without `requestTimeout`/`headersTimeout`, so a\n * handler awaiting a promise that never settles — a `fetch` to an unreachable\n * third party with no `AbortSignal`, a query on a wedged connection — holds its\n * socket and its request object until the client gives up. On the managed\n * runtime the process is shared between tenants, so that is not only the slow\n * caller's problem.\n *\n * The handler is **not** cancelled — it cannot be, there is no cancellation\n * token to hand user code. What is bounded is the client-visible request: after\n * `ms` the caller gets a 504 and the socket is released, and the handler's\n * eventual result is dropped. It is a ceiling, not a kill switch, and the exact\n * number matters far less than its existence.\n *\n * \"The handler keeps running\" is a **Node** guarantee, not a property of the\n * contract. It follows from the process outliving the request, which is not\n * true on an isolate-based host: there, work still in flight when the response\n * resolves is terminated rather than orphaned. So a handler must not depend on\n * finishing after its 504 — anything that has to complete belongs in\n * `waitUntil()`, which is the one construct both hosts honour. See\n * `./wait-until.ts`.\n *\n * Mounted in front of the auth middleware rather than behind it, so a wedged\n * driver — the failure that also hangs `withAuth()` — is covered too.\n */\nexport function createFunctionsRequestTimeout(ms: number): MiddlewareHandler<HonoEnv> {\n return async (c, next) => {\n if (ms <= 0) return next();\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timedOut = new Promise<\"timeout\">((resolve) => {\n timer = setTimeout(() => resolve(\"timeout\"), ms);\n });\n\n try {\n const outcome = await Promise.race([next().then(() => \"done\" as const), timedOut]);\n if (outcome === \"timeout\") {\n logger.warn(\n `[functions] ${c.req.method} ${c.req.path} exceeded the ${ms}ms request timeout — ` +\n \"answering 504. The handler is still running; it cannot be cancelled from here. \" +\n \"Give outbound calls an AbortSignal, or raise `functionsTimeoutMs` / REBASE_FUNCTIONS_TIMEOUT_MS.\"\n );\n return c.json({\n error: {\n message: \"Function timed out\",\n code: \"FUNCTION_TIMEOUT\"\n }\n }, 504);\n }\n } finally {\n if (timer) clearTimeout(timer);\n }\n return undefined;\n };\n}\n"],"mappings":";;;;;;;;;;;;AAKA,IAAa,+BAA+B;;;;;;;;AAS5C,SAAgB,0BAA0B,YAA6B;CACnE,IAAI,OAAO,eAAe,YAAY,OAAO,SAAS,UAAU,GAC5D,OAAO,KAAK,IAAI,GAAG,KAAK,MAAM,UAAU,CAAC;CAU7C,MAAM,MAAM,QAAQ,IAAI,6BAA6B,KAAK;CAC1D,IAAI,KAAK;EACL,MAAM,UAAU,OAAO,GAAG;EAC1B,IAAI,OAAO,SAAS,OAAO,KAAK,WAAW,GACvC,OAAO,KAAK,MAAM,OAAO;CAEjC;CACA,OAAO;AACX;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,SAAgB,8BAA8B,IAAwC;CAClF,OAAO,OAAO,GAAG,SAAS;EACtB,IAAI,MAAM,GAAG,OAAO,KAAK;EAEzB,IAAI;EACJ,MAAM,WAAW,IAAI,SAAoB,YAAY;GACjD,QAAQ,iBAAiB,QAAQ,SAAS,GAAG,EAAE;EACnD,CAAC;EAED,IAAI;GAEA,IAAI,MADkB,QAAQ,KAAK,CAAC,KAAK,CAAC,CAAC,WAAW,MAAe,GAAG,QAAQ,CAAC,MACjE,WAAW;IACvB,OAAO,KACH,eAAe,EAAE,IAAI,OAAO,GAAG,EAAE,IAAI,KAAK,gBAAgB,GAAG,uMAGjE;IACA,OAAO,EAAE,KAAK,EACV,OAAO;KACH,SAAS;KACT,MAAM;IACV,EACJ,GAAG,GAAG;GACV;EACJ,UAAU;GACN,IAAI,OAAO,aAAa,KAAK;EACjC;CAEJ;AACJ"}
@@ -0,0 +1,111 @@
1
+ /**
2
+ * The shape `@rebasepro/rls-check` returns. Declared structurally rather than
3
+ * imported as a type, so this module carries no dependency on it at all — see
4
+ * the module comment.
5
+ */
6
+ export interface RlsScanFinding {
7
+ id: string;
8
+ severity: "info" | "low" | "medium" | "high" | "critical";
9
+ title: string;
10
+ target: {
11
+ schema: string;
12
+ table?: string;
13
+ policy?: string;
14
+ };
15
+ }
16
+ export interface RlsScanResult {
17
+ scannedAt: string;
18
+ /** Host and database name only — the checker never returns credentials. */
19
+ database: {
20
+ host: string;
21
+ name: string;
22
+ };
23
+ stats: {
24
+ schemas: number;
25
+ tables: number;
26
+ policies: number;
27
+ tablesWithoutRls: number;
28
+ checksRun: number;
29
+ };
30
+ findings: RlsScanFinding[];
31
+ }
32
+ declare const SEVERITY_ORDER: readonly ["info", "low", "medium", "high", "critical"];
33
+ export type RlsSeverity = (typeof SEVERITY_ORDER)[number];
34
+ export interface RlsAuditConfig {
35
+ /** Off unless set. See the module comment for why. */
36
+ enabled?: boolean;
37
+ /**
38
+ * How often to run, in milliseconds. Default 24 hours.
39
+ *
40
+ * The thing being watched is a schema, which changes on deploys, not on
41
+ * traffic — so this is a daily safety net, not a monitor.
42
+ */
43
+ intervalMs?: number;
44
+ /** Run once at startup as well as on the interval. Default true. */
45
+ runOnBoot?: boolean;
46
+ /** Restrict the scan to these schemas. Default: every user schema. */
47
+ schemas?: string[];
48
+ /**
49
+ * Where to connect. Defaults to `DATABASE_URL`.
50
+ *
51
+ * Taken as a string rather than borrowed from the driver because the checker
52
+ * connects on its own terms — its own session, its own statement timeout —
53
+ * and threading it through a driver would make the audit's behaviour depend
54
+ * on the pool it borrowed from.
55
+ */
56
+ connectionString?: string;
57
+ /**
58
+ * Severity at or above which a completed run logs a warning rather than an
59
+ * info line. Default `high`.
60
+ */
61
+ warnAtSeverity?: RlsSeverity;
62
+ /** Statement timeout for the scan's own queries. Default 30s. */
63
+ statementTimeoutMs?: number;
64
+ /**
65
+ * The scanner. Pass `scan` from `@rebasepro/rls-check`:
66
+ *
67
+ * ```ts
68
+ * import { scan } from "@rebasepro/rls-check";
69
+ * rlsAudit: { enabled: true, scan }
70
+ * ```
71
+ *
72
+ * Required when `enabled`. See the module comment for why it is not
73
+ * imported here.
74
+ */
75
+ scan?: RlsScanner;
76
+ }
77
+ /** The one function this module needs from `@rebasepro/rls-check`. */
78
+ export type RlsScanner = (options: {
79
+ connectionString: string;
80
+ schemas?: string[];
81
+ statementTimeoutMs?: number;
82
+ }) => Promise<RlsScanResult>;
83
+ export interface RlsAuditStatus {
84
+ enabled: boolean;
85
+ /** Why it is not running, when it is not. */
86
+ reason?: string;
87
+ lastRunAt?: string;
88
+ lastError?: string;
89
+ result?: RlsScanResult;
90
+ }
91
+ export interface RlsAudit {
92
+ start(): void;
93
+ stop(): void;
94
+ /** Run now, outside the schedule. Resolves when the run finishes. */
95
+ runNow(): Promise<void>;
96
+ status(): RlsAuditStatus;
97
+ }
98
+ /** One line naming what the run found, at the right level to be noticed. */
99
+ export declare function summarize(result: RlsScanResult, warnAt: RlsSeverity): {
100
+ level: "warn" | "info";
101
+ message: string;
102
+ };
103
+ /**
104
+ * Create the audit. Nothing runs until {@link RlsAudit.start}.
105
+ *
106
+ * Because the scanner is supplied by the caller, the schedule, the summary and
107
+ * the failure handling are all testable without a database and without
108
+ * `@rebasepro/rls-check` present.
109
+ */
110
+ export declare function createRlsAudit(config: RlsAuditConfig): RlsAudit;
111
+ export {};
@@ -3,7 +3,7 @@ import "process";
3
3
  __createRequire(import.meta.url);
4
4
  import "./src-CrCxd8km.js";
5
5
  import { i as nestAdminPropertyKeys, r as nestAdminCollectionKeys } from "./admin_block-DLILvzle.js";
6
- import { n as errorHandler } from "./errors-EBYiaJ2E.js";
6
+ import { n as errorHandler } from "./errors-D_LwNKRM.js";
7
7
  import * as fs$1 from "fs";
8
8
  import * as path$1 from "path";
9
9
  import { Hono } from "hono";
@@ -434,4 +434,4 @@ function createSchemaEditorRoutes(collectionsDir) {
434
434
  //#endregion
435
435
  export { createSchemaEditorRoutes };
436
436
 
437
- //# sourceMappingURL=schema-editor-routes-CV9k0w3G.js.map
437
+ //# sourceMappingURL=schema-editor-routes-Cax2XTRj.js.map