@rebasepro/server 0.13.1-canary.gf57a27e → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/dist/{admin_block-CLeUMG8G.js → admin_block-H86dCPsj.js} +60 -2
  2. package/dist/admin_block-H86dCPsj.js.map +1 -0
  3. package/dist/api/ast-schema-editor.d.ts +74 -6
  4. package/dist/api/errors.d.ts +12 -0
  5. package/dist/api/rest/api-generator.d.ts +26 -1
  6. package/dist/api/rest/idempotency.d.ts +25 -4
  7. package/dist/api/rest/query-parser.d.ts +15 -1
  8. package/dist/api/rest/write-validation.d.ts +40 -4
  9. package/dist/auth/adapter-middleware.d.ts +3 -1
  10. package/dist/auth/admin-roles.d.ts +24 -0
  11. package/dist/auth/apple-oauth.d.ts +16 -11
  12. package/dist/auth/bitbucket-oauth.d.ts +2 -4
  13. package/dist/auth/builtin-auth-adapter.d.ts +11 -0
  14. package/dist/auth/discord-oauth.d.ts +5 -6
  15. package/dist/auth/facebook-oauth.d.ts +2 -4
  16. package/dist/auth/github-oauth.d.ts +2 -4
  17. package/dist/auth/gitlab-oauth.d.ts +6 -4
  18. package/dist/auth/google-oauth.d.ts +1 -0
  19. package/dist/auth/index.d.ts +7 -0
  20. package/dist/auth/interfaces.d.ts +81 -1
  21. package/dist/auth/jwt.d.ts +40 -0
  22. package/dist/auth/linkedin-oauth.d.ts +2 -4
  23. package/dist/auth/mfa-crypto.d.ts +54 -3
  24. package/dist/auth/mfa-gate.d.ts +42 -0
  25. package/dist/auth/mfa-routes.d.ts +26 -1
  26. package/dist/auth/mfa.d.ts +16 -0
  27. package/dist/auth/microsoft-oauth.d.ts +19 -4
  28. package/dist/auth/middleware.d.ts +26 -0
  29. package/dist/auth/oauth-code-flow.d.ts +66 -0
  30. package/dist/auth/oauth-signin-policy.d.ts +61 -0
  31. package/dist/auth/oidc-id-token.d.ts +61 -0
  32. package/dist/auth/rate-limiter.d.ts +62 -5
  33. package/dist/auth/registration-policy.d.ts +31 -0
  34. package/dist/auth/rls-scope.d.ts +25 -0
  35. package/dist/auth/routes.d.ts +15 -0
  36. package/dist/auth/slack-oauth.d.ts +2 -4
  37. package/dist/auth/spotify-oauth.d.ts +2 -4
  38. package/dist/auth/token-revocation.d.ts +38 -0
  39. package/dist/auth/twitter-oauth.d.ts +2 -5
  40. package/dist/{auth-BANhRhN9.js → auth-CYoPVf-E.js} +1814 -354
  41. package/dist/auth-CYoPVf-E.js.map +1 -0
  42. package/dist/{backup-BJiY_mM-.js → backup-C6ljYVTp.js} +2 -2
  43. package/dist/{backup-BJiY_mM-.js.map → backup-C6ljYVTp.js.map} +1 -1
  44. package/dist/{contract-routes-FSt0Nyfk.js → contract-routes-Bet-eCNJ.js} +3 -3
  45. package/dist/{contract-routes-FSt0Nyfk.js.map → contract-routes-Bet-eCNJ.js.map} +1 -1
  46. package/dist/cron/cron-loader.d.ts +26 -1
  47. package/dist/cron/cron-routes.d.ts +1 -1
  48. package/dist/cron/cron-scheduler.d.ts +5 -0
  49. package/dist/cron/define-cron.d.ts +14 -2
  50. package/dist/cron/index.d.ts +2 -2
  51. package/dist/{cron-loader-B1S2MCSl.js → cron-loader-YhhQeVBM.js} +43 -8
  52. package/dist/cron-loader-YhhQeVBM.js.map +1 -0
  53. package/dist/{cron-routes-D5a9v1HY.js → cron-routes-maM_RlUu.js} +10 -4
  54. package/dist/cron-routes-maM_RlUu.js.map +1 -0
  55. package/dist/{cron-scheduler-CsQtYLMq.js → cron-scheduler-DIpYBmZP.js} +13 -7
  56. package/dist/cron-scheduler-DIpYBmZP.js.map +1 -0
  57. package/dist/{cron-store-ZbjfuvMb.js → cron-store-Dvr4Y1sZ.js} +5 -5
  58. package/dist/{cron-store-ZbjfuvMb.js.map → cron-store-Dvr4Y1sZ.js.map} +1 -1
  59. package/dist/{ddl-bootstrap-Bp3mr6Td.js → ddl-bootstrap-BhXbTnBl.js} +2 -2
  60. package/dist/{ddl-bootstrap-Bp3mr6Td.js.map → ddl-bootstrap-BhXbTnBl.js.map} +1 -1
  61. package/dist/email/html.d.ts +54 -0
  62. package/dist/email/index.d.ts +3 -0
  63. package/dist/email/link-base.d.ts +39 -0
  64. package/dist/email/smtp-email-service.d.ts +7 -1
  65. package/dist/email/templates.d.ts +9 -1
  66. package/dist/email/types.d.ts +11 -1
  67. package/dist/env.d.ts +5 -0
  68. package/dist/{errors-BpudWAVU.js → errors-EBYiaJ2E.js} +14 -2
  69. package/dist/errors-EBYiaJ2E.js.map +1 -0
  70. package/dist/function-loader-DDS1v7YX.js +139 -0
  71. package/dist/function-loader-DDS1v7YX.js.map +1 -0
  72. package/dist/{function-routes-C0cLIy3N.js → function-routes-Btcez1T-.js} +18 -6
  73. package/dist/function-routes-Btcez1T-.js.map +1 -0
  74. package/dist/functions/define-function.d.ts +24 -5
  75. package/dist/functions/function-loader.d.ts +23 -0
  76. package/dist/functions/function-routes.d.ts +7 -1
  77. package/dist/functions/request-timeout.d.ts +34 -0
  78. package/dist/index.d.ts +3 -2
  79. package/dist/index.es.js +1437 -694
  80. package/dist/index.es.js.map +1 -1
  81. package/dist/init/docs.d.ts +10 -1
  82. package/dist/init/process-safety.d.ts +25 -0
  83. package/dist/init.d.ts +38 -0
  84. package/dist/{jwt-CzeviDcB.js → jwt-_IFqfTOg.js} +57 -9
  85. package/dist/jwt-_IFqfTOg.js.map +1 -0
  86. package/dist/logger-DfvF_8r-.js +190 -0
  87. package/dist/logger-DfvF_8r-.js.map +1 -0
  88. package/dist/{openapi-generator-VB0MUH8h.js → openapi-generator-DPKtUC9X.js} +87 -31
  89. package/dist/openapi-generator-DPKtUC9X.js.map +1 -0
  90. package/dist/request-timeout-RivJsME0.js +65 -0
  91. package/dist/request-timeout-RivJsME0.js.map +1 -0
  92. package/dist/schema-editor-routes-CRcS3ArS.js +437 -0
  93. package/dist/schema-editor-routes-CRcS3ArS.js.map +1 -0
  94. package/dist/serve-spa.d.ts +5 -4
  95. package/dist/services/outbound-url-guard.d.ts +53 -0
  96. package/dist/services/routed-realtime-service.d.ts +10 -2
  97. package/dist/services/webhook-service.d.ts +76 -1
  98. package/dist/singleton.d.ts +25 -8
  99. package/dist/{src-DVb5a0bp.js → src-C7rkDGxA.js} +536 -61
  100. package/dist/src-C7rkDGxA.js.map +1 -0
  101. package/dist/{src-Ca6NhxKs.js → src-Cz9nMgUR.js} +16 -3
  102. package/dist/src-Cz9nMgUR.js.map +1 -0
  103. package/dist/storage/LocalStorageController.d.ts +12 -1
  104. package/dist/storage/image-transform.d.ts +57 -0
  105. package/dist/storage/keys.d.ts +34 -1
  106. package/dist/storage/routes.d.ts +11 -0
  107. package/dist/storage/tus-handler.d.ts +22 -2
  108. package/dist/utils/logger.d.ts +18 -0
  109. package/package.json +6 -5
  110. package/dist/admin_block-CLeUMG8G.js.map +0 -1
  111. package/dist/auth-BANhRhN9.js.map +0 -1
  112. package/dist/cron-loader-B1S2MCSl.js.map +0 -1
  113. package/dist/cron-routes-D5a9v1HY.js.map +0 -1
  114. package/dist/cron-scheduler-CsQtYLMq.js.map +0 -1
  115. package/dist/errors-BpudWAVU.js.map +0 -1
  116. package/dist/function-loader-B_1fYfUY.js +0 -86
  117. package/dist/function-loader-B_1fYfUY.js.map +0 -1
  118. package/dist/function-routes-C0cLIy3N.js.map +0 -1
  119. package/dist/jwt-CzeviDcB.js.map +0 -1
  120. package/dist/logger-BYU66ENZ.js +0 -94
  121. package/dist/logger-BYU66ENZ.js.map +0 -1
  122. package/dist/openapi-generator-VB0MUH8h.js.map +0 -1
  123. package/dist/schema-editor-routes-BqjzvvWU.js +0 -248
  124. package/dist/schema-editor-routes-BqjzvvWU.js.map +0 -1
  125. package/dist/src-Ca6NhxKs.js.map +0 -1
  126. package/dist/src-DVb5a0bp.js.map +0 -1
@@ -0,0 +1,39 @@
1
+ import type { EmailConfig } from "./types";
2
+ /**
3
+ * Resolution — and boot-time validation — of the base URL every emailed link is
4
+ * built from.
5
+ *
6
+ * Each link kind used to read one config field and fall back to `""`, which
7
+ * produces `href="/verify-email?token=…"`. A mail client has no base document to
8
+ * resolve that against, so the link is inert: the route still answers
9
+ * `{ success: true }`, the token is still minted and stored, and nothing in the
10
+ * logs says anything is wrong. `verifyEmailUrl` in particular is set by no boot
11
+ * path, so that was the *default* behaviour of email verification.
12
+ *
13
+ * Two changes: the fallback chains live here rather than being re-spelled at
14
+ * each call site (only the magic-link route had one), and
15
+ * {@link assertEmailLinkBases} refuses at boot when no absolute base can be
16
+ * resolved. A configuration error that only ever shows up as "the link in the
17
+ * email does nothing" is worth a failed start.
18
+ */
19
+ /** Which link a base URL is being resolved for. */
20
+ export type EmailLinkKind = "resetPassword" | "verifyEmail" | "magicLink";
21
+ /**
22
+ * The absolute base URL for a link kind, or `""` when the config has none.
23
+ *
24
+ * Callers append their path to the result. `""` is only reachable on a config
25
+ * that never went through {@link assertEmailLinkBases} — a hand-constructed
26
+ * `SMTPEmailService` in a test, say — and is kept rather than thrown so that a
27
+ * misconfiguration cannot turn a password-reset request into a 500 that
28
+ * distinguishes existing accounts from missing ones.
29
+ */
30
+ export declare function resolveEmailLinkBase(config: EmailConfig | undefined, kind: EmailLinkKind): string;
31
+ /**
32
+ * Throw when an email configuration cannot produce a followable link.
33
+ *
34
+ * Called from `createEmailService`, i.e. from every boot path that wires email
35
+ * up (the managed runtime, both driver bootstrappers, and any app that passes
36
+ * `auth.email`). A base that is set but relative is reported separately from one
37
+ * that is missing, because the two have different fixes.
38
+ */
39
+ export declare function assertEmailLinkBases(config: EmailConfig): void;
@@ -25,6 +25,12 @@ export declare class SMTPEmailService implements EmailService {
25
25
  verifyConnection(): Promise<boolean>;
26
26
  }
27
27
  /**
28
- * Create an email service from configuration
28
+ * Create an email service from configuration.
29
+ *
30
+ * This is the choke point every boot path goes through — the managed runtime,
31
+ * both driver bootstrappers, and any app that passes `auth.email` — so it is
32
+ * where the link bases are checked. A config that can send mail but cannot build
33
+ * an absolute link fails the start rather than delivering dead `href="/…"` links
34
+ * and reporting success; see `assertEmailLinkBases`.
29
35
  */
30
36
  export declare function createEmailService(config: EmailConfig): EmailService;
@@ -1,5 +1,13 @@
1
1
  /**
2
- * Default email templates for authentication emails
2
+ * Default email templates for authentication emails.
3
+ *
4
+ * Every HTML body is built with the `html` tag from `./html`, which escapes each
5
+ * interpolated value unless it is explicitly marked `raw`. `displayName` reaches
6
+ * these templates straight from the registration body, and the resulting mail is
7
+ * sent — signed by the sending domain — to an address the same anonymous request
8
+ * chose, so an unescaped interpolation here is a phishing primitive, not a
9
+ * rendering glitch. Escaping lives in the tag rather than at the call sites so a
10
+ * sixth template inherits it instead of having to remember it.
3
11
  */
4
12
  interface TemplateUser {
5
13
  email: string;
@@ -96,11 +96,16 @@ export interface EmailConfig {
96
96
  /**
97
97
  * Base URL for password reset links (e.g., "https://myapp.com")
98
98
  * The reset link will be: {baseUrl}/reset-password?token={token}
99
+ *
100
+ * Must be absolute: mail clients have no base document to resolve a relative
101
+ * href against. `createEmailService` refuses to start when email is
102
+ * configured and no absolute base URL can be resolved.
99
103
  */
100
104
  resetPasswordUrl?: string;
101
105
  /**
102
106
  * Base URL for email verification links (e.g., "https://myapp.com")
103
107
  * The verification link will be: {baseUrl}/verify-email?token={token}
108
+ * Falls back to `resetPasswordUrl` if not set.
104
109
  */
105
110
  verifyEmailUrl?: string;
106
111
  /**
@@ -114,7 +119,12 @@ export interface EmailConfig {
114
119
  */
115
120
  appName?: string;
116
121
  /**
117
- * Custom email templates (optional - defaults are provided)
122
+ * Custom email templates (optional - defaults are provided).
123
+ *
124
+ * A template receives `user.displayName` exactly as the account was
125
+ * registered with it — user input, markup and all. Build the HTML with the
126
+ * `html` tagged template exported from `@rebasepro/server`, which escapes
127
+ * every interpolation, rather than a plain template literal.
118
128
  */
119
129
  templates?: {
120
130
  passwordReset?: PasswordResetTemplateFunction;
package/dist/env.d.ts CHANGED
@@ -27,6 +27,11 @@ declare const rebaseEnvSchema: z.ZodObject<{
27
27
  true: "true";
28
28
  false: "false";
29
29
  }>>, z.ZodTransform<boolean, "" | "true" | "false" | undefined>>;
30
+ ALLOW_ANONYMOUS: z.ZodPipe<z.ZodOptional<z.ZodEnum<{
31
+ "": "";
32
+ true: "true";
33
+ false: "false";
34
+ }>>, z.ZodTransform<boolean, "" | "true" | "false" | undefined>>;
30
35
  ALLOW_LOCALHOST_IN_PRODUCTION: z.ZodPipe<z.ZodOptional<z.ZodEnum<{
31
36
  "": "";
32
37
  true: "true";
@@ -1,7 +1,7 @@
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-BYU66ENZ.js";
4
+ import { t as logger } from "./logger-DfvF_8r-.js";
5
5
  //#region src/api/errors.ts
6
6
  /** Tracks whether we've already shown the doctor hint (once per process). */
7
7
  var _schemaDriftHinted = false;
@@ -50,6 +50,18 @@ var ApiError = class ApiError extends Error {
50
50
  * The motivating case is `POST /auth/refresh` with no session: clients
51
51
  * refresh on page load before they know whether one exists, so every
52
52
  * anonymous page view is a 401 — correct, and not worth a warning line.
53
+ *
54
+ * The other class is a caller-caused 4xx that never reached the database: a
55
+ * mistyped filter operator, sort direction or limit, a request for a
56
+ * collection that does not exist. Nothing on this server is wrong, and the
57
+ * response body has already told the caller what to fix — while one client
58
+ * holding a stale name would otherwise write a warning per request, forever,
59
+ * until the level means nothing. See `api/rest/query-parser.ts`.
60
+ *
61
+ * What stays at warn is anything that says something about the *server*:
62
+ * a schema that has drifted from the code, a permission the database
63
+ * refused, a dependency that failed. Those are 4xx too, and they are still
64
+ * incidents.
53
65
  */
54
66
  expected;
55
67
  constructor(statusCode, code, message, details, expected = false) {
@@ -215,4 +227,4 @@ function codeToStatus(code) {
215
227
  //#endregion
216
228
  export { errorHandler as n, ApiError as t };
217
229
 
218
- //# sourceMappingURL=errors-BpudWAVU.js.map
230
+ //# sourceMappingURL=errors-EBYiaJ2E.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors-EBYiaJ2E.js","names":[],"sources":["../src/api/errors.ts"],"sourcesContent":["import type { ErrorHandler } from \"hono\";\nimport type { ContentfulStatusCode } from \"hono/utils/http-status\";\nimport type { HonoEnv } from \"./types\";\nimport { logger } from \"../utils/logger\";\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 && process.env.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 ...(process.env.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,139 @@
1
+ import { createRequire as __createRequire } from "module";
2
+ import "process";
3
+ __createRequire(import.meta.url);
4
+ import { n as __exportAll } from "./rolldown-runtime-DSJWtz9O.js";
5
+ import { t as logger } from "./logger-DfvF_8r-.js";
6
+ import { t as nativeDynamicImport } from "./dynamic-import-Dvh-K5fl.js";
7
+ import * as fs$1 from "fs";
8
+ import * as path$1 from "path";
9
+ import { pathToFileURL } from "url";
10
+ //#region src/functions/function-loader.ts
11
+ var function_loader_exports = /* @__PURE__ */ __exportAll({
12
+ loadFunctionsFromDirectory: () => loadFunctionsFromDirectory,
13
+ loadFunctionsWithDiagnostics: () => loadFunctionsWithDiagnostics
14
+ });
15
+ /**
16
+ * Extensions the bundler compiles (or a developer might reasonably write) that
17
+ * this loader cannot import. `rebase build` globs `functions/**\/*.ts`, so the
18
+ * build's idea of "a function file" is strictly wider than the runtime's — the
19
+ * worst direction for a mismatch to go. Anything listed here is reported as a
20
+ * problem instead of vanishing; non-code files (`.md`, `.json`, `.txt`) stay
21
+ * silent, because a README next to your functions is not a mistake.
22
+ */
23
+ var UNSUPPORTED_CODE_EXTENSIONS = [
24
+ ".mts",
25
+ ".cts",
26
+ ".tsx",
27
+ ".jsx",
28
+ ".mjs",
29
+ ".cjs"
30
+ ];
31
+ /**
32
+ * Auto-discover Hono route files from a directory.
33
+ *
34
+ * Each file should default-export a Hono app (or router).
35
+ * The filename (without extension) becomes the mount path:
36
+ * `functions/send-invoice.ts` → mounted at `/send-invoice`
37
+ *
38
+ * This mirrors how `loadCollectionsFromDirectory` works for collections.
39
+ *
40
+ * Returns only what loaded. Use {@link loadFunctionsWithDiagnostics} when the
41
+ * caller also needs to report what did not.
42
+ */
43
+ async function loadFunctionsFromDirectory(directory, importModule = nativeDynamicImport) {
44
+ return (await loadFunctionsWithDiagnostics(directory, importModule)).functions;
45
+ }
46
+ /**
47
+ * {@link loadFunctionsFromDirectory}, plus the list of files that were skipped.
48
+ *
49
+ * Never throws: a single malformed function must not crash server boot. The
50
+ * caller decides what to do with `problems` — `init.ts` mounts the router
51
+ * regardless and surfaces the count on the listing endpoint.
52
+ */
53
+ async function loadFunctionsWithDiagnostics(directory, importModule = nativeDynamicImport) {
54
+ const functions = [];
55
+ const problems = [];
56
+ if (!fs$1.existsSync(directory)) return {
57
+ functions,
58
+ problems
59
+ };
60
+ const entries = fs$1.readdirSync(directory, { withFileTypes: true });
61
+ for (const entry of entries) {
62
+ const file = entry.name;
63
+ if (entry.isDirectory()) {
64
+ if (file.startsWith(".") || file === "node_modules") continue;
65
+ logger.warn(`[functions] ${file}/: subdirectory ignored. Functions are loaded from the top level of ${directory} only, so nothing under ${file}/ is served. Move the file up (or flatten the name: \`admin/users.ts\` → \`admin-users.ts\`).`);
66
+ problems.push(`${file}/ (subdirectory — functions are not loaded recursively)`);
67
+ continue;
68
+ }
69
+ const extension = path$1.extname(file);
70
+ if (!file.startsWith(".") && !file.includes(".test.") && UNSUPPORTED_CODE_EXTENSIONS.includes(extension)) {
71
+ logger.warn(`[functions] ${file}: ${extension} files are not loaded. Rename it to .ts (or .js) to serve it.`);
72
+ problems.push(`${file} (unsupported extension ${extension})`);
73
+ continue;
74
+ }
75
+ if ((file.endsWith(".ts") || file.endsWith(".js")) && !file.startsWith(".") && !file.includes(".test.") && !file.endsWith(".d.ts") && file !== "index.ts" && file !== "index.js") {
76
+ const filePath = path$1.join(directory, file);
77
+ try {
78
+ const fileUrl = pathToFileURL(filePath).href;
79
+ const exported = (await importModule(fileUrl)).default;
80
+ if (!exported) {
81
+ logger.warn(`[functions] ${file}: no default export. Skipping.`);
82
+ problems.push(`${file} (no default export)`);
83
+ continue;
84
+ }
85
+ if (isHonoLike(exported)) {
86
+ const name = path$1.basename(file, path$1.extname(file));
87
+ functions.push({
88
+ name,
89
+ app: exported
90
+ });
91
+ logger.debug(`⚡ Loaded function route: ${name}`);
92
+ continue;
93
+ }
94
+ if (typeof exported === "function") {
95
+ const result = exported();
96
+ if (isHonoLike(result)) {
97
+ const name = path$1.basename(file, path$1.extname(file));
98
+ functions.push({
99
+ name,
100
+ app: result
101
+ });
102
+ logger.debug(`⚡ Loaded function route: ${name}`);
103
+ continue;
104
+ }
105
+ }
106
+ const exportType = typeof exported;
107
+ const keys = exported && typeof exported === "object" ? Object.getOwnPropertyNames(Object.getPrototypeOf(exported)).slice(0, 10).join(", ") : "N/A";
108
+ logger.warn(`[functions] ${file}: default export is not a Hono app or factory. Skipping.\n export type: ${exportType}${exported?.constructor?.name ? ` (${exported.constructor.name})` : ""}\n prototype methods: ${keys}\n Hint: ensure the function exports a Hono app created with the same hono version as the server.
109
+ Author with \`defineFunction(...)\` from @rebasepro/server for a typed, checked contract.
110
+ The loader checks for .fetch() and .routes — any Hono-compatible app will work.`);
111
+ problems.push(`${file} (not a Hono app or factory)`);
112
+ } catch (err) {
113
+ const message = err instanceof Error ? err.message : String(err);
114
+ logger.error(`[functions] Failed to load ${file}: ${message}`);
115
+ problems.push(`${file} (threw: ${message})`);
116
+ }
117
+ }
118
+ }
119
+ if (problems.length > 0) logger.warn(`[functions] ${problems.length} function file(s) were skipped and will NOT be served:\n` + problems.map((p) => ` - ${p}`).join("\n") + "\n Fix these or author them with `defineFunction(...)` for a typed, compile-checked contract.");
120
+ return {
121
+ functions,
122
+ problems
123
+ };
124
+ }
125
+ /**
126
+ * Duck-type check for Hono apps.
127
+ * We avoid `instanceof Hono` because different Hono versions
128
+ * installed in the user's project vs. our dependencies will
129
+ * not share the same prototype, causing false negatives.
130
+ */
131
+ function isHonoLike(obj) {
132
+ if (!obj || typeof obj !== "object") return false;
133
+ const record = obj;
134
+ return typeof record.fetch === "function" && Array.isArray(record.routes);
135
+ }
136
+ //#endregion
137
+ export { loadFunctionsFromDirectory as n, function_loader_exports as t };
138
+
139
+ //# sourceMappingURL=function-loader-DDS1v7YX.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"function-loader-DDS1v7YX.js","names":[],"sources":["../src/functions/function-loader.ts"],"sourcesContent":["import * as fs from \"fs\";\nimport * as path from \"path\";\nimport { pathToFileURL } from \"url\";\nimport { Hono } from \"hono\";\nimport { logger } from \"../utils/logger.js\";\nimport { nativeDynamicImport, type ModuleImporter } from \"../utils/dynamic-import.js\";\n\nexport interface LoadedFunction {\n /** Endpoint name derived from filename (e.g., \"send-invoice\") */\n name: string;\n /** The Hono sub-app to mount */\n app: Hono<import(\"hono\").Env>;\n}\n\n/** What a directory of function files produced: what mounted, and what did not. */\nexport interface LoadedFunctions {\n /** The functions that will be served. */\n functions: LoadedFunction[];\n /**\n * One entry per file the loader saw and did **not** mount, each already\n * phrased as `<name> (<reason>)`. Returned rather than only logged so the\n * running server can say what is missing — a boot log line is not reachable\n * from `GET /api/functions`.\n */\n problems: string[];\n}\n\n/**\n * Extensions the bundler compiles (or a developer might reasonably write) that\n * this loader cannot import. `rebase build` globs `functions/**\\/*.ts`, so the\n * build's idea of \"a function file\" is strictly wider than the runtime's — the\n * worst direction for a mismatch to go. Anything listed here is reported as a\n * problem instead of vanishing; non-code files (`.md`, `.json`, `.txt`) stay\n * silent, because a README next to your functions is not a mistake.\n */\nconst UNSUPPORTED_CODE_EXTENSIONS = [\".mts\", \".cts\", \".tsx\", \".jsx\", \".mjs\", \".cjs\"];\n\n/**\n * Auto-discover Hono route files from a directory.\n *\n * Each file should default-export a Hono app (or router).\n * The filename (without extension) becomes the mount path:\n * `functions/send-invoice.ts` → mounted at `/send-invoice`\n *\n * This mirrors how `loadCollectionsFromDirectory` works for collections.\n *\n * Returns only what loaded. Use {@link loadFunctionsWithDiagnostics} when the\n * caller also needs to report what did not.\n */\nexport async function loadFunctionsFromDirectory(\n directory: string,\n importModule: ModuleImporter = nativeDynamicImport\n): Promise<LoadedFunction[]> {\n return (await loadFunctionsWithDiagnostics(directory, importModule)).functions;\n}\n\n/**\n * {@link loadFunctionsFromDirectory}, plus the list of files that were skipped.\n *\n * Never throws: a single malformed function must not crash server boot. The\n * caller decides what to do with `problems` — `init.ts` mounts the router\n * regardless and surfaces the count on the listing endpoint.\n */\nexport async function loadFunctionsWithDiagnostics(\n directory: string,\n importModule: ModuleImporter = nativeDynamicImport\n): Promise<LoadedFunctions> {\n const functions: LoadedFunction[] = [];\n // Aggregate problem files so a broken function surfaces as one loud\n // summary line, not just a warning buried per-file. We still don't throw:\n // a single malformed function must not crash server boot.\n const problems: string[] = [];\n\n if (!fs.existsSync(directory)) {\n return { functions, problems };\n }\n\n // `withFileTypes` so a directory entry is a *reported* skip rather than a\n // filter miss. `readdirSync(dir)` returned bare names, and a subdirectory\n // simply failed the `.ts`/`.js` test — so `functions/admin/users.ts` was\n // compiled by `rebase build`, shipped in the bundle, and then dropped at\n // boot without a single log line.\n const entries = fs.readdirSync(directory, { withFileTypes: true });\n for (const entry of entries) {\n const file = entry.name;\n\n if (entry.isDirectory()) {\n // Dot-directories are tooling (`.git`, `.turbo`), not intent.\n if (file.startsWith(\".\") || file === \"node_modules\") continue;\n logger.warn(\n `[functions] ${file}/: subdirectory ignored. Functions are loaded from the top level of ` +\n `${directory} only, so nothing under ${file}/ is served. Move the file up (or flatten the ` +\n \"name: `admin/users.ts` → `admin-users.ts`).\"\n );\n problems.push(`${file}/ (subdirectory — functions are not loaded recursively)`);\n continue;\n }\n\n const extension = path.extname(file);\n if (\n !file.startsWith(\".\") &&\n !file.includes(\".test.\") &&\n UNSUPPORTED_CODE_EXTENSIONS.includes(extension)\n ) {\n logger.warn(\n `[functions] ${file}: ${extension} files are not loaded. Rename it to .ts (or .js) to serve it.`\n );\n problems.push(`${file} (unsupported extension ${extension})`);\n continue;\n }\n\n if (\n (file.endsWith(\".ts\") || file.endsWith(\".js\")) &&\n // Dotfiles: notably macOS bsdtar AppleDouble sidecars (`._foo.ts`),\n // binary blobs that cannot be imported.\n !file.startsWith(\".\") &&\n !file.includes(\".test.\") &&\n !file.endsWith(\".d.ts\") &&\n file !== \"index.ts\" &&\n file !== \"index.js\"\n ) {\n const filePath = path.join(directory, file);\n try {\n const fileUrl = pathToFileURL(filePath).href;\n\n const mod = await importModule(fileUrl);\n\n const exported = mod.default;\n\n if (!exported) {\n logger.warn(`[functions] ${file}: no default export. Skipping.`);\n problems.push(`${file} (no default export)`);\n continue;\n }\n\n // Accept a Hono instance — use duck-typing to handle different\n // Hono versions which may not share the same prototype.\n if (isHonoLike(exported)) {\n const name = path.basename(file, path.extname(file));\n functions.push({ name,\napp: exported as Hono });\n logger.debug(`⚡ Loaded function route: ${name}`);\n continue;\n }\n\n // Also accept a factory function that returns a Hono instance\n if (typeof exported === \"function\") {\n const result = exported();\n if (isHonoLike(result)) {\n const name = path.basename(file, path.extname(file));\n functions.push({ name,\napp: result as Hono });\n logger.debug(`⚡ Loaded function route: ${name}`);\n continue;\n }\n }\n\n // Provide actionable diagnostics\n const exportType = typeof exported;\n const keys = exported && typeof exported === \"object\"\n ? Object.getOwnPropertyNames(Object.getPrototypeOf(exported)).slice(0, 10).join(\", \")\n : \"N/A\";\n logger.warn(\n `[functions] ${file}: default export is not a Hono app or factory. Skipping.\\n` +\n ` export type: ${exportType}${exported?.constructor?.name ? ` (${exported.constructor.name})` : \"\"}\\n` +\n ` prototype methods: ${keys}\\n` +\n \" Hint: ensure the function exports a Hono app created with the same hono version as the server.\\n\" +\n \" Author with `defineFunction(...)` from @rebasepro/server for a typed, checked contract.\\n\" +\n \" The loader checks for .fetch() and .routes — any Hono-compatible app will work.\"\n );\n problems.push(`${file} (not a Hono app or factory)`);\n } catch (err: unknown) {\n const message =\n err instanceof Error ? err.message : String(err);\n logger.error(`[functions] Failed to load ${file}: ${message}`);\n problems.push(`${file} (threw: ${message})`);\n }\n }\n }\n\n if (problems.length > 0) {\n logger.warn(\n `[functions] ${problems.length} function file(s) were skipped and will NOT be served:\\n` +\n problems.map((p) => ` - ${p}`).join(\"\\n\") + \"\\n\" +\n \" Fix these or author them with `defineFunction(...)` for a typed, compile-checked contract.\"\n );\n }\n\n return { functions, problems };\n}\n\n/**\n * Duck-type check for Hono apps.\n * We avoid `instanceof Hono` because different Hono versions\n * installed in the user's project vs. our dependencies will\n * not share the same prototype, causing false negatives.\n */\nfunction isHonoLike(obj: unknown): boolean {\n if (!obj || typeof obj !== \"object\") return false;\n // Hono instances always have .fetch() and .routes\n const record = obj as Record<string, unknown>;\n return (\n typeof record.fetch === \"function\" &&\n Array.isArray(record.routes)\n );\n}\n"],"mappings":"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"}
@@ -10,14 +10,26 @@ var function_routes_exports = /* @__PURE__ */ __exportAll({ createFunctionRoutes
10
10
  *
11
11
  * Each function is mounted at `/<function-name>`, preserving
12
12
  * whatever HTTP methods and middleware the Hono sub-app defines.
13
+ *
14
+ * @param functions What loaded. May be empty — the router still mounts, so
15
+ * "no functions are served" answers 200 with an empty list instead of 404.
16
+ * @param skipped How many files the loader saw and could not serve. Reported
17
+ * as a count and a pointer to the log, not as filenames: the listing is
18
+ * reachable anonymously, and the per-file reasons carry import errors.
13
19
  */
14
- function createFunctionRoutes(functions) {
20
+ function createFunctionRoutes(functions, skipped = 0) {
15
21
  const router = new Hono();
16
22
  router.get("/", (c) => {
17
- return c.json({ functions: functions.map((fn) => ({
18
- name: fn.name,
19
- endpoint: `/functions/${fn.name}`
20
- })) });
23
+ return c.json({
24
+ functions: functions.map((fn) => ({
25
+ name: fn.name,
26
+ endpoint: `/functions/${fn.name}`
27
+ })),
28
+ ...skipped > 0 && {
29
+ skipped,
30
+ note: `${skipped} function file(s) failed to load and are NOT served — see the server log for the reason.`
31
+ }
32
+ });
21
33
  });
22
34
  for (const fn of functions) router.route(`/${fn.name}`, fn.app);
23
35
  return router;
@@ -25,4 +37,4 @@ function createFunctionRoutes(functions) {
25
37
  //#endregion
26
38
  export { function_routes_exports as n, createFunctionRoutes as t };
27
39
 
28
- //# sourceMappingURL=function-routes-C0cLIy3N.js.map
40
+ //# sourceMappingURL=function-routes-Btcez1T-.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"function-routes-Btcez1T-.js","names":[],"sources":["../src/functions/function-routes.ts"],"sourcesContent":["import { Hono } from \"hono\";\nimport { HonoEnv } from \"../api/types\";\nimport { LoadedFunction } from \"./function-loader\";\n\n/**\n * Mount all loaded function routes under a single Hono router.\n *\n * Each function is mounted at `/<function-name>`, preserving\n * whatever HTTP methods and middleware the Hono sub-app defines.\n *\n * @param functions What loaded. May be empty — the router still mounts, so\n * \"no functions are served\" answers 200 with an empty list instead of 404.\n * @param skipped How many files the loader saw and could not serve. Reported\n * as a count and a pointer to the log, not as filenames: the listing is\n * reachable anonymously, and the per-file reasons carry import errors.\n */\nexport function createFunctionRoutes(\n functions: LoadedFunction[],\n skipped = 0\n): Hono<HonoEnv> {\n const router = new Hono<HonoEnv>();\n\n // Listing endpoint: GET / → list available functions\n router.get(\"/\", (c) => {\n return c.json({\n functions: functions.map((fn) => ({\n name: fn.name,\n endpoint: `/functions/${fn.name}`\n })),\n ...(skipped > 0 && {\n skipped,\n note: `${skipped} function file(s) failed to load and are NOT served — see the server log for the reason.`\n })\n });\n });\n\n for (const fn of functions) {\n router.route(`/${fn.name}`, fn.app);\n }\n\n return router;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAgBA,SAAgB,qBACZ,WACA,UAAU,GACG;CACb,MAAM,SAAS,IAAI,KAAc;CAGjC,OAAO,IAAI,MAAM,MAAM;EACnB,OAAO,EAAE,KAAK;GACV,WAAW,UAAU,KAAK,QAAQ;IAC9B,MAAM,GAAG;IACT,UAAU,cAAc,GAAG;GAC/B,EAAE;GACF,GAAI,UAAU,KAAK;IACf;IACA,MAAM,GAAG,QAAQ;GACrB;EACJ,CAAC;CACL,CAAC;CAED,KAAK,MAAM,MAAM,WACb,OAAO,MAAM,IAAI,GAAG,QAAQ,GAAG,GAAG;CAGtC,OAAO;AACX"}
@@ -15,11 +15,27 @@ export interface RebaseFunctionContext {
15
15
  * The server-side Rebase singleton (`dataAsAdmin`, `auth`, `storage`,
16
16
  * `email`, `sql`).
17
17
  *
18
- * `rebase.dataAsAdmin` runs with **admin privileges and bypasses RLS** — use
19
- * it only for trusted admin work. For user-scoped queries inside a handler,
20
- * use the request `driver` (`c.var.driver`), which carries the caller's
21
- * identity so RLS applies. (`rebase.data` no longer exists on this type —
22
- * `dataAsAdmin` is the only name for the admin-scoped accessor.)
18
+ * `rebase.dataAsAdmin` runs as the service identity
19
+ * `{ uid: "service", roles: ["admin"] }` — **admin-scoped, not an RLS
20
+ * bypass**. Policies are still evaluated; it passes the default ones
21
+ * through their `rolesOverlap(['admin'])` arm, the same arm an application
22
+ * user holding the `admin` role passes. Two things follow:
23
+ *
24
+ * - `policy.serverContext()` (`auth.uid() IS NULL`) is **false** here. A
25
+ * collection with `disableDefaultPolicies: true` whose write rule is
26
+ * `serverContext()` will refuse these writes with `42501`, and reads
27
+ * against a hand-written admin policy that does not name the `admin` role
28
+ * return zero rows with a 200.
29
+ * - Do not read it as "nobody else can reach these rows". Whatever an
30
+ * `admin`-roled user can reach, this can, and vice versa.
31
+ *
32
+ * `rebase.sql()` is the true bypass: it runs on the owner connection and
33
+ * never goes through `withAuth`.
34
+ *
35
+ * For user-scoped queries inside a handler, use the request `driver`
36
+ * (`c.var.driver`), which carries the caller's identity. (`rebase.data` no
37
+ * longer exists on this type — `dataAsAdmin` is the only name for the
38
+ * admin-scoped accessor.)
23
39
  */
24
40
  rebase: RebaseServerClient;
25
41
  }
@@ -41,6 +57,9 @@ export interface RebaseFunctionContext {
41
57
  * export default defineFunction((app, { rebase }) => {
42
58
  * app.use("/*", requireAuth);
43
59
  * app.get("/home", async (c) => {
60
+ * // `rebase.sql` runs on the owner connection: no RLS, no policies,
61
+ * // every row. It is the most privileged thing in this context —
62
+ * // more so than `dataAsAdmin`, which is merely admin-scoped.
44
63
  * const [stats] = await rebase.sql(`SELECT count(*) AS n FROM orders`);
45
64
  * return c.json({ orders: Number(stats.n) });
46
65
  * });
@@ -6,6 +6,18 @@ export interface LoadedFunction {
6
6
  /** The Hono sub-app to mount */
7
7
  app: Hono<import("hono").Env>;
8
8
  }
9
+ /** What a directory of function files produced: what mounted, and what did not. */
10
+ export interface LoadedFunctions {
11
+ /** The functions that will be served. */
12
+ functions: LoadedFunction[];
13
+ /**
14
+ * One entry per file the loader saw and did **not** mount, each already
15
+ * phrased as `<name> (<reason>)`. Returned rather than only logged so the
16
+ * running server can say what is missing — a boot log line is not reachable
17
+ * from `GET /api/functions`.
18
+ */
19
+ problems: string[];
20
+ }
9
21
  /**
10
22
  * Auto-discover Hono route files from a directory.
11
23
  *
@@ -14,5 +26,16 @@ export interface LoadedFunction {
14
26
  * `functions/send-invoice.ts` → mounted at `/send-invoice`
15
27
  *
16
28
  * This mirrors how `loadCollectionsFromDirectory` works for collections.
29
+ *
30
+ * Returns only what loaded. Use {@link loadFunctionsWithDiagnostics} when the
31
+ * caller also needs to report what did not.
17
32
  */
18
33
  export declare function loadFunctionsFromDirectory(directory: string, importModule?: ModuleImporter): Promise<LoadedFunction[]>;
34
+ /**
35
+ * {@link loadFunctionsFromDirectory}, plus the list of files that were skipped.
36
+ *
37
+ * Never throws: a single malformed function must not crash server boot. The
38
+ * caller decides what to do with `problems` — `init.ts` mounts the router
39
+ * regardless and surfaces the count on the listing endpoint.
40
+ */
41
+ export declare function loadFunctionsWithDiagnostics(directory: string, importModule?: ModuleImporter): Promise<LoadedFunctions>;
@@ -6,5 +6,11 @@ import { LoadedFunction } from "./function-loader";
6
6
  *
7
7
  * Each function is mounted at `/<function-name>`, preserving
8
8
  * whatever HTTP methods and middleware the Hono sub-app defines.
9
+ *
10
+ * @param functions What loaded. May be empty — the router still mounts, so
11
+ * "no functions are served" answers 200 with an empty list instead of 404.
12
+ * @param skipped How many files the loader saw and could not serve. Reported
13
+ * as a count and a pointer to the log, not as filenames: the listing is
14
+ * reachable anonymously, and the per-file reasons carry import errors.
9
15
  */
10
- export declare function createFunctionRoutes(functions: LoadedFunction[]): Hono<HonoEnv>;
16
+ export declare function createFunctionRoutes(functions: LoadedFunction[], skipped?: number): Hono<HonoEnv>;
@@ -0,0 +1,34 @@
1
+ import type { MiddlewareHandler } from "hono";
2
+ import type { HonoEnv } from "../api/types";
3
+ /** Default ceiling for a custom function request, in milliseconds. */
4
+ export declare const DEFAULT_FUNCTIONS_TIMEOUT_MS = 30000;
5
+ /**
6
+ * Resolve the functions request timeout from config, then env, then the default.
7
+ *
8
+ * `0` (or any non-positive number) disables the ceiling — for a deployment
9
+ * whose proxy already imposes one, or a function that legitimately streams for
10
+ * minutes.
11
+ */
12
+ export declare function resolveFunctionsTimeoutMs(configured?: number): number;
13
+ /**
14
+ * A per-request ceiling for the custom functions router.
15
+ *
16
+ * Custom functions are the one router that runs code the framework did not
17
+ * write, and nothing else in the stack bounds how long that code takes: the
18
+ * Node server is constructed without `requestTimeout`/`headersTimeout`, so a
19
+ * handler awaiting a promise that never settles — a `fetch` to an unreachable
20
+ * third party with no `AbortSignal`, a query on a wedged connection — holds its
21
+ * socket and its request object until the client gives up. On the managed
22
+ * runtime the process is shared between tenants, so that is not only the slow
23
+ * caller's problem.
24
+ *
25
+ * The handler is **not** cancelled — it cannot be, there is no cancellation
26
+ * token to hand user code. What is bounded is the client-visible request: after
27
+ * `ms` the caller gets a 504 and the socket is released, and the handler's
28
+ * eventual result is dropped. It is a ceiling, not a kill switch, and the exact
29
+ * number matters far less than its existence.
30
+ *
31
+ * Mounted in front of the auth middleware rather than behind it, so a wedged
32
+ * driver — the failure that also hangs `withAuth()` — is covered too.
33
+ */
34
+ export declare function createFunctionsRequestTimeout(ms: number): MiddlewareHandler<HonoEnv>;
package/dist/index.d.ts CHANGED
@@ -14,7 +14,7 @@ export { loadCollectionsFromDirectory, applyCollectionDefaults, type CollectionD
14
14
  export { assertCollectionConfigs, findCollectionConfigProblems, unknownKeyPolicyFromEnv, type ConfigProblem, type UnknownKeyPolicy, type ValidateCollectionConfigOptions } from "./collections/validate-config";
15
15
  export * from "./db/interfaces";
16
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, isApiKeyToken, validateApiKey, httpMethodToOperation, isOperationAllowed, safeCompare } from "./auth";
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 } from "./auth";
18
18
  export type { AccessTokenPayload, PasswordValidationResult, AuthHooks, AuthMethod, ResolvedAuthHooks, BuiltinAuthAdapterConfig, GoogleProviderConfig, AuthMiddlewareOptions, AuthResult, ApiKey, ApiKeyMasked, ApiKeyPermission, ApiKeyWithSecret, CreateApiKeyRequest, UpdateApiKeyRequest, ApiKeyStore, ApiKeyOperation } from "./auth";
19
19
  export * from "./api/types";
20
20
  export * from "./api/errors";
@@ -23,7 +23,7 @@ export * from "./storage";
23
23
  export { createHistoryRoutes } from "./history";
24
24
  export { loadFunctionsFromDirectory, createFunctionRoutes, defineFunction } from "./functions";
25
25
  export type { LoadedFunction, RebaseFunctionContext } from "./functions";
26
- export { loadCronJobsFromDirectory, CronScheduler, validateCronExpression, createCronRoutes, createCronStore, defineCron } from "./cron";
26
+ export { loadCronJobsFromDirectory, loadCronJobsWithDiagnostics, CronScheduler, validateCronExpression, createCronRoutes, createCronStore, defineCron } from "./cron";
27
27
  export type { LoadedCronJob, CronStore } from "./cron";
28
28
  export { createBackupRoutes, parseBackupDestination, parseBackupTimestamp, listBackupObjects, readBackupBytes } from "./backup";
29
29
  export type { BackupRoutesConfig, BackupDestination } from "./backup";
@@ -35,6 +35,7 @@ export type { RebaseEnv } from "./env";
35
35
  export * from "./types";
36
36
  export * from "./services/driver-registry";
37
37
  export * from "./services/webhook-service";
38
+ export * from "./services/outbound-url-guard";
38
39
  export { cleanupDevPortFile, listenWithPortRetry } from "./utils/dev-port";
39
40
  export { serveSPA } from "./serve-spa";
40
41
  export { installShutdownHandlers } from "./init/shutdown";