@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
package/dist/index.es.js CHANGED
@@ -4,25 +4,27 @@ __createRequire(import.meta.url);
4
4
  import { A as GeoPoint, M as RebaseApiError, N as RebaseClientError, O as EntityReference, S as toSnakeCase, _ as fieldKeyForColumn, b as buildCompositeId, c as collectAllPages, d as normalizeOrderBy, f as serializeOrderBy, g as getEffectiveSecurityRules, h as resolveDataSource, j as Vector, k as EntityRelation, l as paginateFind, m as createDataSourceRegistry, n as buildSdkData, o as serializeFilter, p as CollectionRegistry, s as serializeLogicalCondition, t as buildRoutedRebaseData, u as resolveFindWindow, v as findRelation, x as resolvePrimaryKeys, y as resolveCollectionRelations } from "./src-BPfYOeN4.js";
5
5
  import { c as DEFAULT_DATA_SOURCE_KEY, g as toCanonicalOp, i as getCollectionDataPath, m as parseRelationAggregateSort, n as RLS_ROLES_SQL, o as isPostgresCollectionConfig, r as RLS_UID_SQL, t as RLS_JWT_SQL } from "./src-CrCxd8km.js";
6
6
  import { n as ADMIN_PROPERTY_KEYS, t as ADMIN_COLLECTION_KEYS } from "./admin_block-DLILvzle.js";
7
- import { a as isDuplicateObjectRace, i as isConcurrentDdlRace, n as createDdlBootstrapper, o as revokeInternalTableSql, s as isSQLAdmin, t as CONCURRENT_DDL_SQLSTATES } from "./ddl-bootstrap-Cywoj8Ta.js";
8
- import { C as canonicalStorageKey, D as findStorageSuffixCollision, E as DEFAULT_STORAGE_SOURCE_KEY, O as normalizeStorageSources, S as canonicalStorageId, b as InvalidStorageKeyError, c as getJwks, d as hasAsymmetricSigningKey, i as generateDownloadToken, k as storageEnvSuffix, n as configureJwt, p as isJwtConfigured, v as normalizePemFromEnv, x as canonicalStorageBucket, y as InvalidStorageBucketError } from "./jwt-DxH9fLPt.js";
9
- import { n as createContractRoutes, r as computeSchemaVersion } from "./contract-routes-DZ-LBpSL.js";
10
- import { $ as createAdapterAuthMiddleware, A as _coercedNumber, B as validatePasswordStrength, C as providerVerifiedEmail, Ct as parseQueryOptions, D as _enum, E as ZodNumber, Et as isPublicStoragePath, F as createEmailService, G as getPasswordResetTemplate, H as generateSecurePassword, I as assertEmailLinkBases, J as resolveEmailBranding, K as getUserInvitationTemplate, L as resolveEmailLinkBase, M as createDataRateLimiter, N as MemoryRateLimitStore, O as object, P as SMTPEmailService, Q as raw, R as resolveAuthHooks, S as pkceTokenParams, St as parseGroupBy, T as createBuiltinAuthAdapter, Tt as PUBLIC_STORAGE_PREFIX, U as getEmailVerificationTemplate, V as verifyPassword, W as getMagicLinkTemplate, X as escapeHtml, Y as RawHtml, Z as html, _ as verifyOidcIdToken, _t as scopeDataDriver, a as resolveRateLimitStoreKind, at as publicObjectAuth, b as createGoogleProvider, bt as orderByEntriesToTuples, c as createSlackProvider, ct as requireAuth, d as createDiscordProvider, dt as createStorageApiKeyGuard, et as createAuthMiddleware, f as createTwitterProvider, ft as isApiKeyToken, g as tryVerifyOidcIdToken, gt as SERVICE_IDENTITY, h as createMicrosoftProvider, ht as safeCompare, i as createApiKeyStore, it as optionalAuth, j as DEFAULT_FUNCTIONS_ANONYMOUS_LIMIT, k as string, l as createBitbucketProvider, lt as createApiKeyPreAuth, m as createAppleProvider, mt as extractBearerToken, n as createCustomAuthAdapter, nt as extractUserFromToken, o as createSqlRateLimitStore, ot as queryTokenAuth, p as createFacebookProvider, pt as validateApiKey, q as getWelcomeEmailTemplate, r as createApiKeyRoutes, rt as fileTokenAuth, s as createSpotifyProvider, st as requireAdmin, tt as createRequireAuth, u as createGitLabProvider, ut as createFunctionApiKeyGuard, v as createGitHubProvider, vt as httpMethodToOperation, w as createJwksRoutes, wt as resolveListLimitParam, x as oauthCodeFlowSchema, xt as parseAggregateSelect, y as createLinkedinProvider, yt as isOperationAllowed, z as hashPassword } from "./auth-5Et5mnUA.js";
11
- import { t as logger } from "./logger-DfvF_8r-.js";
12
- import { n as errorHandler, t as ApiError } from "./errors-EBYiaJ2E.js";
7
+ import { a as isDuplicateObjectRace, i as isConcurrentDdlRace, n as createDdlBootstrapper, o as revokeInternalTableSql, s as isSQLAdmin, t as CONCURRENT_DDL_SQLSTATES } from "./ddl-bootstrap-CFLhcvfQ.js";
8
+ import { C as canonicalStorageKey, D as findStorageSuffixCollision, E as DEFAULT_STORAGE_SOURCE_KEY, O as normalizeStorageSources, S as canonicalStorageId, b as InvalidStorageKeyError, c as getJwks, d as hasAsymmetricSigningKey, i as generateDownloadToken, k as storageEnvSuffix, n as configureJwt, p as isJwtConfigured, v as normalizePemFromEnv, x as canonicalStorageBucket, y as InvalidStorageBucketError } from "./jwt-BbJi0TR0.js";
9
+ import { n as createContractRoutes, r as computeSchemaVersion } from "./contract-routes-BEq7euZg.js";
10
+ import { $ as html, A as _coercedNumber, B as resolveAuthHooks, C as providerVerifiedEmail, Ct as orderByEntriesToTuples, D as _enum, Dt as resolveListLimitParam, E as ZodNumber, Et as parseQueryOptions, F as extractLinks, G as getEmailVerificationTemplate, H as validatePasswordStrength, I as SMTPEmailService, J as getUserInvitationTemplate, K as getMagicLinkTemplate, L as createEmailService, M as createDataRateLimiter, N as MemoryRateLimitStore, O as object, Ot as PUBLIC_STORAGE_PREFIX, P as createDevEmailSink, Q as escapeHtml, R as assertEmailLinkBases, S as pkceTokenParams, St as isOperationAllowed, T as createBuiltinAuthAdapter, Tt as parseGroupBy, U as verifyPassword, V as hashPassword, W as generateSecurePassword, X as resolveEmailBranding, Y as getWelcomeEmailTemplate, Z as RawHtml, _ as verifyOidcIdToken, _t as safeCompare, a as resolveRateLimitStoreKind, at as fileTokenAuth, b as createGoogleProvider, bt as hasAdministrativeRole, c as createSlackProvider, ct as queryTokenAuth, d as createDiscordProvider, dt as createApiKeyPreAuth, et as raw, f as createTwitterProvider, ft as createFunctionApiKeyGuard, g as tryVerifyOidcIdToken, gt as extractBearerToken, h as createMicrosoftProvider, ht as validateApiKey, i as createApiKeyStore, it as extractUserFromToken, j as DEFAULT_FUNCTIONS_ANONYMOUS_LIMIT, k as string, kt as isPublicStoragePath, l as createBitbucketProvider, lt as requireAdmin, m as createAppleProvider, mt as isApiKeyToken, n as createCustomAuthAdapter, nt as createAuthMiddleware, o as createSqlRateLimitStore, ot as optionalAuth, p as createFacebookProvider, pt as createStorageApiKeyGuard, q as getPasswordResetTemplate, r as createApiKeyRoutes, rt as createRequireAuth, s as createSpotifyProvider, st as publicObjectAuth, tt as createAdapterAuthMiddleware, u as createGitLabProvider, ut as requireAuth, v as createGitHubProvider, vt as SERVICE_IDENTITY, w as createJwksRoutes, wt as parseAggregateSelect, x as oauthCodeFlowSchema, xt as httpMethodToOperation, y as createLinkedinProvider, yt as scopeDataDriver, z as resolveEmailLinkBase } from "./auth-B0IV-irB.js";
11
+ import { r as hostEnv, t as logger } from "./logger-TdvXIGqR.js";
12
+ import { n as errorHandler, t as ApiError } from "./errors-D_LwNKRM.js";
13
13
  import { t as logMiddleware } from "./logs-routes-CWBLQj2l.js";
14
14
  import { n as DOCUMENT_MIME_TYPES, r as IMAGE_MIME_TYPES, t as DEFAULT_MAX_FILE_SIZE } from "./types-DSnOC4mF.js";
15
15
  import { t as S3StorageController } from "./S3StorageController-B2EDXXMH.js";
16
16
  import { t as GCSStorageController } from "./GCSStorageController-BEmDYFKc.js";
17
- import { n as loadFunctionsFromDirectory } from "./function-loader-DDS1v7YX.js";
17
+ import { n as loadFunctionsFromDirectory, r as loadFunctionsWithDiagnostics } from "./function-loader-BrLmC_-y.js";
18
18
  import { t as createFunctionRoutes } from "./function-routes-Btcez1T-.js";
19
- import { n as loadCronJobsFromDirectory, r as loadCronJobsWithDiagnostics } from "./cron-loader-YhhQeVBM.js";
20
- import { r as validateCronExpression, t as CronScheduler } from "./cron-scheduler-DIpYBmZP.js";
21
- import { t as createCronRoutes } from "./cron-routes-maM_RlUu.js";
22
- import { t as createCronStore } from "./cron-store-DfH_4Cd9.js";
23
- import { a as parseBackupTimestamp, i as parseBackupDestination, n as createBackupRoutes, o as readBackupBytes, r as listBackupObjects } from "./backup-C6ljYVTp.js";
24
- import { i as createJobStore, n as createJobQueue, r as defaultBackoff } from "./jobs-CyOKXXlu.js";
19
+ import "./proxy-QJKSS-CV.js";
20
+ import "./request-timeout-OofPCHQT.js";
25
21
  import { t as FunctionSelectionError } from "./selection-_z6TM1DB.js";
22
+ import { n as loadCronJobsFromDirectory, r as loadCronJobsWithDiagnostics } from "./cron-loader-BMvtW6-J.js";
23
+ import { r as validateCronExpression, t as CronScheduler } from "./cron-scheduler-Buf-uVam.js";
24
+ import { t as createCronRoutes } from "./cron-routes-BvYk-Kmi.js";
25
+ import { t as createCronStore } from "./cron-store-CpfttP4f.js";
26
+ import { a as parseBackupTimestamp, i as parseBackupDestination, n as createBackupRoutes, o as readBackupBytes, r as listBackupObjects } from "./backup-BJ86ah4T.js";
27
+ import { i as createJobStore, n as createJobQueue, r as defaultBackoff } from "./jobs-CSlBZ8dn.js";
26
28
  import { createHash, randomBytes, randomUUID } from "node:crypto";
27
29
  import * as fs$2 from "fs";
28
30
  import fs, { existsSync } from "fs";
@@ -41,6 +43,7 @@ import fs$1 from "node:fs";
41
43
  import fsp from "node:fs/promises";
42
44
  import os from "node:os";
43
45
  import { mkdir, open, unlink, writeFile } from "fs/promises";
46
+ import { env, getRuntimeKey } from "hono/adapter";
44
47
  import { sql } from "drizzle-orm";
45
48
  import { lookup } from "node:dns/promises";
46
49
  import { isIP } from "node:net";
@@ -161,7 +164,7 @@ var PROPERTY_KEYS_BY_TYPE = {
161
164
  ],
162
165
  geopoint: [],
163
166
  binary: [],
164
- vector: ["dimensions"],
167
+ vector: ["dimensions", "index"],
165
168
  reference: [
166
169
  "isId",
167
170
  "path",
@@ -3032,6 +3035,138 @@ function isStorageController(obj) {
3032
3035
  return typeof controller.putObject === "function" && typeof controller.getSignedUrl === "function" && typeof controller.deleteObject === "function" && typeof controller.listObjects === "function" && typeof controller.getType === "function";
3033
3036
  }
3034
3037
  //#endregion
3038
+ //#region src/storage/cache-headers.ts
3039
+ /**
3040
+ * A weak validator built from size and modification time.
3041
+ *
3042
+ * Weak (`W/`) because it is not a digest of the body: two different bodies of
3043
+ * the same length written in the same millisecond would collide. That is the
3044
+ * same trade nginx makes for static files, and the alternative — hashing every
3045
+ * object on every request — costs more than it saves for content this server is
3046
+ * already streaming from somewhere else.
3047
+ */
3048
+ function buildEntityTag(size, lastModifiedMs) {
3049
+ return `W/"${size.toString(16)}-${Math.floor(lastModifiedMs).toString(16)}"`;
3050
+ }
3051
+ function objectValidators(size, lastModifiedMs) {
3052
+ const seconds = Math.floor(lastModifiedMs / 1e3) * 1e3;
3053
+ return {
3054
+ etag: buildEntityTag(size, lastModifiedMs),
3055
+ lastModified: new Date(seconds).toUTCString(),
3056
+ lastModifiedMs: seconds
3057
+ };
3058
+ }
3059
+ function cacheControl(options) {
3060
+ const parts = [options.isPublic ? "public" : "private", `max-age=${options.maxAgeSeconds}`];
3061
+ if (options.isPublic && options.staleWhileRevalidateSeconds) parts.push(`stale-while-revalidate=${options.staleWhileRevalidateSeconds}`);
3062
+ parts.push("must-revalidate");
3063
+ return parts.join(", ");
3064
+ }
3065
+ /** Every `ETag` in an `If-None-Match` header, unquoted comparison-ready. */
3066
+ function parseIfNoneMatch(header) {
3067
+ return header.split(",").map((part) => part.trim()).filter(Boolean);
3068
+ }
3069
+ /**
3070
+ * Weak comparison, which is the only comparison `If-None-Match` on a GET is
3071
+ * allowed to use (RFC 9110 §8.8.3.2). `W/"x"` and `"x"` are the same entity for
3072
+ * this purpose, so the prefix is stripped from both sides before comparing.
3073
+ */
3074
+ function weakMatch(a, b) {
3075
+ const strip = (v) => v.startsWith("W/") ? v.slice(2) : v;
3076
+ return strip(a) === strip(b);
3077
+ }
3078
+ /**
3079
+ * Whether the request already holds this exact representation.
3080
+ *
3081
+ * `If-None-Match` wins outright when present: it is an exact validator, whereas
3082
+ * `If-Modified-Since` has second resolution and is only consulted in its
3083
+ * absence — again the order RFC 9110 requires.
3084
+ */
3085
+ function isNotModified(c, validators) {
3086
+ const ifNoneMatch = c.req.header("if-none-match");
3087
+ if (ifNoneMatch) {
3088
+ const tags = parseIfNoneMatch(ifNoneMatch);
3089
+ return tags.includes("*") || tags.some((tag) => weakMatch(tag, validators.etag));
3090
+ }
3091
+ const ifModifiedSince = c.req.header("if-modified-since");
3092
+ if (ifModifiedSince) {
3093
+ const since = Date.parse(ifModifiedSince);
3094
+ if (!Number.isNaN(since)) return validators.lastModifiedMs <= since;
3095
+ }
3096
+ return false;
3097
+ }
3098
+ /**
3099
+ * Put the validators and the cache policy on a response.
3100
+ *
3101
+ * Applied to the 304 as well as the 200: a 304 that omits them tells the client
3102
+ * nothing about how long the answer holds, so the next load revalidates again
3103
+ * immediately and the round trip repeats forever.
3104
+ */
3105
+ function applyCacheHeaders(c, validators, options) {
3106
+ c.header("ETag", validators.etag);
3107
+ c.header("Last-Modified", validators.lastModified);
3108
+ c.header("Cache-Control", cacheControl(options));
3109
+ c.header("Vary", "Authorization");
3110
+ }
3111
+ //#endregion
3112
+ //#region src/storage/range.ts
3113
+ /**
3114
+ * Parse a `Range` header against an object of `size` bytes.
3115
+ *
3116
+ * Deliberately conservative: anything malformed, multi-range, or in a unit other
3117
+ * than bytes resolves to `none`, which serves the whole object. A range request
3118
+ * answered with `200` is always legal; a range answered wrongly is a corrupted
3119
+ * download.
3120
+ */
3121
+ function parseRange(header, size) {
3122
+ if (!header) return { kind: "none" };
3123
+ const match = /^bytes=(.*)$/i.exec(header.trim());
3124
+ if (!match) return { kind: "none" };
3125
+ const spec = match[1].trim();
3126
+ if (spec.includes(",")) return { kind: "none" };
3127
+ const parts = /^(\d*)-(\d*)$/.exec(spec);
3128
+ if (!parts) return { kind: "none" };
3129
+ const [, rawStart, rawEnd] = parts;
3130
+ if (size === 0) return { kind: "unsatisfiable" };
3131
+ let start;
3132
+ let end;
3133
+ if (rawStart === "") {
3134
+ if (rawEnd === "") return { kind: "none" };
3135
+ const suffix = Number(rawEnd);
3136
+ if (!Number.isSafeInteger(suffix)) return { kind: "none" };
3137
+ if (suffix === 0) return { kind: "unsatisfiable" };
3138
+ start = Math.max(0, size - suffix);
3139
+ end = size - 1;
3140
+ } else {
3141
+ start = Number(rawStart);
3142
+ if (!Number.isSafeInteger(start)) return { kind: "none" };
3143
+ if (start >= size) return { kind: "unsatisfiable" };
3144
+ if (rawEnd === "") end = size - 1;
3145
+ else {
3146
+ end = Number(rawEnd);
3147
+ if (!Number.isSafeInteger(end)) return { kind: "none" };
3148
+ end = Math.min(end, size - 1);
3149
+ }
3150
+ if (end < start) return { kind: "unsatisfiable" };
3151
+ }
3152
+ return {
3153
+ kind: "range",
3154
+ range: {
3155
+ start,
3156
+ end,
3157
+ length: end - start + 1
3158
+ }
3159
+ };
3160
+ }
3161
+ /** The `Content-Range` for a served range. */
3162
+ function contentRange(range, size) {
3163
+ return `bytes ${range.start}-${range.end}/${size}`;
3164
+ }
3165
+ /** The `Content-Range` for a `416`, which names the size and no range. */
3166
+ function unsatisfiableContentRange(size) {
3167
+ return `bytes */${size}`;
3168
+ }
3169
+ //#endregion
3035
3170
  //#region src/storage/image-transform.ts
3036
3171
  /**
3037
3172
  * Image Transformation Service
@@ -3893,11 +4028,20 @@ function createStorageRoutes(config) {
3893
4028
  await checkAuthorized(c, "read", resolvedPath, bucket, storageId);
3894
4029
  c.header("X-Content-Type-Options", "nosniff");
3895
4030
  const transformKeyPrefix = `${storageId || "(default)"}/${bucket}/${resolvedPath}`;
4031
+ const sharedCacheable = publicRead === true || isPublicStoragePath(resolvedPath);
4032
+ const cachePolicy = (maxAgeSeconds) => ({
4033
+ isPublic: sharedCacheable,
4034
+ maxAgeSeconds,
4035
+ staleWhileRevalidateSeconds: 86400
4036
+ });
4037
+ const OBJECT_MAX_AGE = 60;
4038
+ const TRANSFORM_MAX_AGE = 3600;
3896
4039
  const transformOpts = transformOptionsOrBadRequest(c.req.query());
3897
4040
  if (resolved.getType() === "local") {
3898
4041
  const absolutePath = resolved.getAbsolutePath(resolvedPath, bucket ?? "default");
4042
+ let localStat;
3899
4043
  try {
3900
- await fsp.access(absolutePath);
4044
+ localStat = await fsp.stat(absolutePath);
3901
4045
  } catch {
3902
4046
  throw ApiError.notFound("File not found");
3903
4047
  }
@@ -3908,31 +4052,75 @@ function createStorageRoutes(config) {
3908
4052
  contentType = JSON.parse(metadataRaw).contentType || contentType;
3909
4053
  } catch {}
3910
4054
  if (transformOpts && isTransformableImage(contentType)) {
3911
- const transformed = await transformOnce(transformCache.buildKey(transformKeyPrefix, transformOpts), transformOpts, async () => Buffer.from(await fsp.readFile(absolutePath)));
4055
+ const sourceVersion = buildEntityTag(localStat.size, localStat.mtimeMs);
4056
+ const transformed = await transformOnce(transformCache.buildKey(`${transformKeyPrefix}@${sourceVersion}`, transformOpts), transformOpts, async () => Buffer.from(await fsp.readFile(absolutePath)));
4057
+ const validators = objectValidators(transformed.data.byteLength, localStat.mtimeMs);
4058
+ applyCacheHeaders(c, validators, cachePolicy(TRANSFORM_MAX_AGE));
4059
+ if (isNotModified(c, validators)) return c.body(null, 304);
3912
4060
  c.header("Content-Type", transformed.contentType);
3913
- c.header("Cache-Control", "public, max-age=31536000, immutable");
3914
4061
  return c.body(new Uint8Array(transformed.data));
3915
4062
  }
3916
- const fileContent = await fsp.readFile(absolutePath);
4063
+ const validators = objectValidators(localStat.size, localStat.mtimeMs);
4064
+ applyCacheHeaders(c, validators, cachePolicy(OBJECT_MAX_AGE));
3917
4065
  const served = resolveServedContentType(contentType);
3918
4066
  c.header("Content-Type", served.contentType);
3919
4067
  if (served.attachment) c.header("Content-Disposition", "attachment");
4068
+ c.header("Accept-Ranges", "bytes");
4069
+ if (isNotModified(c, validators)) return c.body(null, 304);
4070
+ const localRange = parseRange(c.req.header("range"), localStat.size);
4071
+ if (localRange.kind === "unsatisfiable") {
4072
+ c.header("Content-Range", unsatisfiableContentRange(localStat.size));
4073
+ return c.body(null, 416);
4074
+ }
4075
+ if (localRange.kind === "range") {
4076
+ const { start, length } = localRange.range;
4077
+ const handle = await fsp.open(absolutePath, "r");
4078
+ try {
4079
+ const buffer = Buffer.alloc(length);
4080
+ await handle.read(buffer, 0, length, start);
4081
+ c.header("Content-Range", contentRange(localRange.range, localStat.size));
4082
+ c.header("Content-Length", String(length));
4083
+ return c.body(new Uint8Array(buffer), 206);
4084
+ } finally {
4085
+ await handle.close();
4086
+ }
4087
+ }
4088
+ c.header("Content-Length", String(localStat.size));
4089
+ const fileContent = await fsp.readFile(absolutePath);
3920
4090
  return c.body(new Uint8Array(fileContent));
3921
4091
  }
3922
4092
  const fileObject = await resolved.getObject(resolvedPath, bucket);
3923
4093
  if (!fileObject) throw ApiError.notFound("File not found");
3924
4094
  const remoteContentType = fileObject.type || "application/octet-stream";
3925
4095
  if (transformOpts && isTransformableImage(remoteContentType)) {
3926
- const transformed = await transformOnce(transformCache.buildKey(transformKeyPrefix, transformOpts), transformOpts, async () => Buffer.from(await fileObject.arrayBuffer()));
4096
+ const sourceVersion = buildEntityTag(fileObject.size, fileObject.lastModified);
4097
+ const transformed = await transformOnce(transformCache.buildKey(`${transformKeyPrefix}@${sourceVersion}`, transformOpts), transformOpts, async () => Buffer.from(await fileObject.arrayBuffer()));
4098
+ const validators = objectValidators(transformed.data.byteLength, fileObject.lastModified);
4099
+ applyCacheHeaders(c, validators, cachePolicy(TRANSFORM_MAX_AGE));
4100
+ if (isNotModified(c, validators)) return c.body(null, 304);
3927
4101
  c.header("Content-Type", transformed.contentType);
3928
- c.header("Cache-Control", "public, max-age=31536000, immutable");
3929
4102
  return c.body(new Uint8Array(transformed.data));
3930
4103
  }
3931
4104
  const servedRemote = resolveServedContentType(remoteContentType);
3932
4105
  c.header("Content-Type", servedRemote.contentType);
3933
4106
  if (servedRemote.attachment) c.header("Content-Disposition", "attachment");
3934
- c.header("Cache-Control", "public, max-age=3600, immutable");
4107
+ const remoteValidators = objectValidators(fileObject.size, fileObject.lastModified);
4108
+ applyCacheHeaders(c, remoteValidators, cachePolicy(OBJECT_MAX_AGE));
4109
+ c.header("Accept-Ranges", "bytes");
4110
+ if (isNotModified(c, remoteValidators)) return c.body(null, 304);
4111
+ const remoteRange = parseRange(c.req.header("range"), fileObject.size);
4112
+ if (remoteRange.kind === "unsatisfiable") {
4113
+ c.header("Content-Range", unsatisfiableContentRange(fileObject.size));
4114
+ return c.body(null, 416);
4115
+ }
3935
4116
  const buf = await fileObject.arrayBuffer();
4117
+ if (remoteRange.kind === "range") {
4118
+ const { start, end, length } = remoteRange.range;
4119
+ c.header("Content-Range", contentRange(remoteRange.range, fileObject.size));
4120
+ c.header("Content-Length", String(length));
4121
+ return c.body(new Uint8Array(buf.slice(start, end + 1)), 206);
4122
+ }
4123
+ c.header("Content-Length", String(fileObject.size));
3936
4124
  return c.body(new Uint8Array(buf));
3937
4125
  });
3938
4126
  /**
@@ -4161,6 +4349,114 @@ function assertStorageAccessControlConfigured(state, isProduction) {
4161
4349
  logger.warn(STORAGE_NO_ACCESS_CONTROL_MESSAGE);
4162
4350
  }
4163
4351
  //#endregion
4352
+ //#region src/storage/policies.ts
4353
+ var StoragePolicyError = class extends Error {
4354
+ constructor(message) {
4355
+ super(message);
4356
+ this.name = "StoragePolicyError";
4357
+ }
4358
+ };
4359
+ var ALL_OPERATIONS = [
4360
+ "read",
4361
+ "write",
4362
+ "delete",
4363
+ "list"
4364
+ ];
4365
+ /**
4366
+ * Split a key into segments, dropping empty ones.
4367
+ *
4368
+ * Empty segments come from a leading slash or a `//`, and treating them as real
4369
+ * would let `users//x` match a pattern expecting a captured segment with the
4370
+ * empty string. Keys reaching here are already sanitized against traversal; this
4371
+ * is about matching, not safety.
4372
+ */
4373
+ function segmentsOf(value) {
4374
+ return value.split("/").filter((segment) => segment.length > 0);
4375
+ }
4376
+ function compile(policy, index) {
4377
+ const label = `storagePolicies[${index}]`;
4378
+ if (typeof policy.path !== "string" || policy.path.trim() === "") throw new StoragePolicyError(`${label}: \`path\` must be a non-empty string.`);
4379
+ const segments = segmentsOf(policy.path);
4380
+ if (segments.length === 0) throw new StoragePolicyError(`${label}: \`path\` "${policy.path}" names no segments. Use "**" to match every key.`);
4381
+ const starIndex = segments.indexOf("**");
4382
+ if (starIndex !== -1 && starIndex !== segments.length - 1) throw new StoragePolicyError(`${label}: "**" is only allowed as the last segment of \`path\`, and "${policy.path}" puts it in the middle. Use "*" for a single segment.`);
4383
+ const captures = segments.filter((s) => s.startsWith(":")).map((s) => s.slice(1));
4384
+ for (const name of captures) {
4385
+ if (name === "") throw new StoragePolicyError(`${label}: a ":" placeholder in "${policy.path}" has no name.`);
4386
+ if (captures.filter((c) => c === name).length > 1) throw new StoragePolicyError(`${label}: "${policy.path}" captures ":${name}" twice, so one would silently win.`);
4387
+ }
4388
+ if (policy.operations !== void 0) {
4389
+ if (!Array.isArray(policy.operations) || policy.operations.length === 0) throw new StoragePolicyError(`${label}: \`operations\` is empty, so this policy grants nothing. Remove it to grant all, or name the operations.`);
4390
+ for (const operation of policy.operations) if (!ALL_OPERATIONS.includes(operation)) throw new StoragePolicyError(`${label}: \`operations\` names "${operation}", which is not a storage operation. Use ${ALL_OPERATIONS.map((o) => `"${o}"`).join(", ")}.`);
4391
+ }
4392
+ if (policy.allow !== "public" && policy.allow !== "authenticated" && typeof policy.allow !== "function") throw new StoragePolicyError(`${label}: \`allow\` must be "public", "authenticated", or a function. A policy with no \`allow\` grants nothing, which is never what was meant.`);
4393
+ return {
4394
+ segments,
4395
+ trailing: starIndex !== -1,
4396
+ operations: new Set(policy.operations ?? ALL_OPERATIONS),
4397
+ allow: policy.allow,
4398
+ source: policy.path
4399
+ };
4400
+ }
4401
+ /** Match a key's segments against a compiled pattern, capturing as it goes. */
4402
+ function match(compiled, key) {
4403
+ const keySegments = segmentsOf(key);
4404
+ const pattern = compiled.trailing ? compiled.segments.slice(0, -1) : compiled.segments;
4405
+ if (compiled.trailing ? keySegments.length < pattern.length : keySegments.length !== pattern.length) return;
4406
+ const params = {};
4407
+ for (let i = 0; i < pattern.length; i++) {
4408
+ const expected = pattern[i];
4409
+ const actual = keySegments[i];
4410
+ if (expected === "*") continue;
4411
+ if (expected.startsWith(":")) {
4412
+ params[expected.slice(1)] = actual;
4413
+ continue;
4414
+ }
4415
+ if (expected !== actual) return void 0;
4416
+ }
4417
+ return params;
4418
+ }
4419
+ /**
4420
+ * Compile a policy list into a {@link StorageAuthorize}.
4421
+ *
4422
+ * Compiled once, at boot, so a malformed pattern fails the start rather than the
4423
+ * first upload. `fallback` is an explicit `storageAuthorize`, consulted only
4424
+ * when no policy matched.
4425
+ */
4426
+ function compileStoragePolicies(policies, fallback) {
4427
+ if (!Array.isArray(policies)) throw new StoragePolicyError("`storagePolicies` must be an array.");
4428
+ const compiled = policies.map(compile);
4429
+ return async (ctx) => {
4430
+ for (const policy of compiled) {
4431
+ if (!policy.operations.has(ctx.operation)) continue;
4432
+ const params = match(policy, ctx.key);
4433
+ if (!params) continue;
4434
+ if (policy.allow === "public") return true;
4435
+ if (policy.allow === "authenticated") {
4436
+ if (ctx.user?.uid) return true;
4437
+ continue;
4438
+ }
4439
+ if (await policy.allow({
4440
+ ...ctx,
4441
+ params
4442
+ })) return true;
4443
+ }
4444
+ if (fallback) return await fallback(ctx);
4445
+ return false;
4446
+ };
4447
+ }
4448
+ /**
4449
+ * The access-control function for a configuration, or `undefined` when it
4450
+ * declares none.
4451
+ *
4452
+ * Kept separate from {@link compileStoragePolicies} so `init` has one call to
4453
+ * make and one thing to reason about: policies, a hook, both, or neither.
4454
+ */
4455
+ function resolveStorageAccessControl(config) {
4456
+ if (config.storagePolicies?.length) return compileStoragePolicies(config.storagePolicies, config.storageAuthorize);
4457
+ return config.storageAuthorize;
4458
+ }
4459
+ //#endregion
4164
4460
  //#region src/init/docs.ts
4165
4461
  /**
4166
4462
  * Serve the OpenAPI document, and Swagger UI outside production.
@@ -4256,6 +4552,101 @@ function createHealthCheck(defaultDriver, authSchemaCheck) {
4256
4552
  };
4257
4553
  }
4258
4554
  //#endregion
4555
+ //#region src/functions/wait-until.ts
4556
+ /**
4557
+ * Pending background work, on a process-global slot for the reason given in
4558
+ * `../singleton.ts`: two copies of this module in one process must not each
4559
+ * hold half the work, or shutdown drains one half and drops the other.
4560
+ */
4561
+ var PENDING_SLOT = Symbol.for("@rebasepro/server:pending-background-work");
4562
+ function pending() {
4563
+ const global = globalThis;
4564
+ if (!global[PENDING_SLOT]) global[PENDING_SLOT] = /* @__PURE__ */ new Set();
4565
+ return global[PENDING_SLOT];
4566
+ }
4567
+ /**
4568
+ * Keep `work` alive past the response, on any runtime.
4569
+ *
4570
+ * Hand it a promise or a function returning one. Nothing is awaited: the
4571
+ * response goes out immediately, exactly as an un-awaited call would.
4572
+ *
4573
+ * What it buys, per host:
4574
+ *
4575
+ * - **Isolate hosts** (Workers, Deno Deploy, Vercel Edge): the host is told to
4576
+ * keep the isolate alive until the promise settles. Without this the work is
4577
+ * dropped when the response resolves.
4578
+ * - **Node**: the promise is tracked so {@link drainBackgroundWork} can wait for
4579
+ * it during graceful shutdown instead of the process exiting out from under
4580
+ * it.
4581
+ *
4582
+ * On every host a rejection is logged rather than left to the unhandled-rejection
4583
+ * handler, so the failure names the function it came from.
4584
+ *
4585
+ * @example
4586
+ * ```ts
4587
+ * app.post("/orders", requireAuth, async (c) => {
4588
+ * const order = await createOrder(c);
4589
+ * waitUntil(c, notifyWarehouse(order)); // caller does not wait for this
4590
+ * return c.json({ id: order.id });
4591
+ * });
4592
+ * ```
4593
+ */
4594
+ function waitUntil(c, work) {
4595
+ let promise;
4596
+ try {
4597
+ promise = typeof work === "function" ? Promise.resolve(work()) : work;
4598
+ } catch (error) {
4599
+ logBackgroundFailure(c, error);
4600
+ return;
4601
+ }
4602
+ const tracked = promise.catch((error) => {
4603
+ logBackgroundFailure(c, error);
4604
+ });
4605
+ const set = pending();
4606
+ set.add(tracked);
4607
+ tracked.finally(() => set.delete(tracked));
4608
+ try {
4609
+ c.executionCtx?.waitUntil?.(tracked);
4610
+ } catch {}
4611
+ }
4612
+ function logBackgroundFailure(c, error) {
4613
+ logger.error("[functions] Background work failed after the response was sent", {
4614
+ method: c.req?.method,
4615
+ path: c.req?.path,
4616
+ error: error instanceof Error ? error : new Error(String(error))
4617
+ });
4618
+ }
4619
+ /** How many background promises are still in flight. Diagnostics and tests. */
4620
+ function pendingBackgroundWork() {
4621
+ return pending().size;
4622
+ }
4623
+ /**
4624
+ * Wait for tracked background work to finish, up to `timeoutMs`.
4625
+ *
4626
+ * Called by the shutdown path after the server stops accepting connections. It
4627
+ * resolves to the number of promises still outstanding when it returned — `0`
4628
+ * for a clean drain, more than that for a timeout, which is worth a log line
4629
+ * because it means work was dropped.
4630
+ *
4631
+ * Rejections do not propagate: every tracked promise has already had a `catch`
4632
+ * attached by {@link waitUntil}.
4633
+ */
4634
+ async function drainBackgroundWork(timeoutMs = 5e3) {
4635
+ const set = pending();
4636
+ if (set.size === 0) return 0;
4637
+ let timer;
4638
+ const expired = new Promise((resolve) => {
4639
+ timer = setTimeout(() => resolve("timeout"), timeoutMs);
4640
+ timer.unref?.();
4641
+ });
4642
+ try {
4643
+ await Promise.race([Promise.allSettled([...set]), expired]);
4644
+ } finally {
4645
+ if (timer) clearTimeout(timer);
4646
+ }
4647
+ return set.size;
4648
+ }
4649
+ //#endregion
4259
4650
  //#region src/init/shutdown.ts
4260
4651
  /**
4261
4652
  * Install graceful-shutdown signal handlers for a Rebase backend.
@@ -4326,10 +4717,14 @@ function createShutdown(config) {
4326
4717
  config.cronScheduler.stop();
4327
4718
  logger.info("Cron scheduler stopped");
4328
4719
  }
4720
+ if (config.rlsAudit) config.rlsAudit.stop();
4329
4721
  if (config.jobQueue) {
4330
4722
  await config.jobQueue.stop();
4331
4723
  logger.info("Job queue stopped");
4332
4724
  }
4725
+ const drainBudget = timeoutMs > 0 ? Math.min(5e3, timeoutMs) : 5e3;
4726
+ const stillPending = await drainBackgroundWork(drainBudget);
4727
+ if (stillPending > 0) logger.warn(`${stillPending} background task(s) handed to waitUntil() did not finish within ${drainBudget}ms and are being dropped. Give them an AbortSignal, or move work this long into a cron job or the job queue, where it is restartable.`);
4333
4728
  for (const [key, rt] of Object.entries(config.realtimeServices)) try {
4334
4729
  if (typeof rt.destroy === "function") {
4335
4730
  await rt.destroy();
@@ -4354,6 +4749,147 @@ function createShutdown(config) {
4354
4749
  };
4355
4750
  }
4356
4751
  //#endregion
4752
+ //#region src/rls-audit/index.ts
4753
+ /**
4754
+ * The row-level-security audit, on a schedule.
4755
+ *
4756
+ * `@rebasepro/rls-check` is a thorough, read-only audit of a database's RLS —
4757
+ * more thorough, on security specifically, than the dashboard advisors it
4758
+ * competes with. And nobody runs it, because it is a command, and a command is
4759
+ * something you have to remember. A check that has to be remembered is a check
4760
+ * that reports a clean database right up until the day it matters.
4761
+ *
4762
+ * This runs it on a timer against the database the server is already using, and
4763
+ * makes the result readable without a terminal. Nothing else about the checker
4764
+ * changes: same package, same checks, same output.
4765
+ *
4766
+ * ## Why it is off by default
4767
+ *
4768
+ * It opens its own connection and introspects the whole catalog. That is cheap
4769
+ * and read-only, but it is not nothing, and a background query nobody asked for
4770
+ * is a poor default for a library. Enabling it is one line, and the boot log
4771
+ * says so when it is off.
4772
+ *
4773
+ * ## Why the scanner is injected rather than imported
4774
+ *
4775
+ * `@rebasepro/server` is engine-agnostic — it has no `pg` and no other database
4776
+ * driver, deliberately. `@rebasepro/rls-check` has `pg`, because it connects to
4777
+ * Postgres. Importing it here would put a Postgres driver in the install of
4778
+ * every Mongo and Firebase user, for a feature none of them can use, and an
4779
+ * audit of row-level security is a Postgres concept living in the one package
4780
+ * that is supposed to know nothing about Postgres.
4781
+ *
4782
+ * So the scan function is handed in:
4783
+ *
4784
+ * ```ts
4785
+ * import { scan } from "@rebasepro/rls-check";
4786
+ * rlsAudit: { enabled: true, scan }
4787
+ * ```
4788
+ *
4789
+ * Everything else — the schedule, the summary, the status, the admin route —
4790
+ * lives here and is engine-agnostic in fact as well as in principle.
4791
+ */
4792
+ var SEVERITY_ORDER = [
4793
+ "info",
4794
+ "low",
4795
+ "medium",
4796
+ "high",
4797
+ "critical"
4798
+ ];
4799
+ /** Rank for comparison. Unknown severities sort lowest rather than throwing. */
4800
+ var rank = (severity) => {
4801
+ const index = SEVERITY_ORDER.indexOf(severity);
4802
+ return index === -1 ? 0 : index;
4803
+ };
4804
+ var DEFAULT_INTERVAL_MS = 1440 * 60 * 1e3;
4805
+ var DEFAULT_STATEMENT_TIMEOUT_MS = 3e4;
4806
+ /** One line naming what the run found, at the right level to be noticed. */
4807
+ function summarize(result, warnAt) {
4808
+ const counts = /* @__PURE__ */ new Map();
4809
+ for (const finding of result.findings) counts.set(finding.severity, (counts.get(finding.severity) ?? 0) + 1);
4810
+ const worst = result.findings.reduce((acc, f) => rank(f.severity) > rank(acc) ? f.severity : acc, "info");
4811
+ if (result.findings.length === 0) return {
4812
+ level: "info",
4813
+ message: `RLS audit clean: ${result.stats.tables} table(s), ${result.stats.policies} polic(ies), ${result.stats.checksRun} check(s), no findings.`
4814
+ };
4815
+ const breakdown = [...SEVERITY_ORDER].reverse().filter((severity) => counts.get(severity)).map((severity) => `${counts.get(severity)} ${severity}`).join(", ");
4816
+ const message = `RLS audit found ${result.findings.length} issue(s) — ${breakdown}. ${result.stats.tablesWithoutRls} table(s) without RLS. Read the detail at GET /api/admin/rls-audit, or run \`npx @rebasepro/rls-check\` for the full report.`;
4817
+ return {
4818
+ level: rank(worst) >= rank(warnAt) ? "warn" : "info",
4819
+ message
4820
+ };
4821
+ }
4822
+ /**
4823
+ * Create the audit. Nothing runs until {@link RlsAudit.start}.
4824
+ *
4825
+ * Because the scanner is supplied by the caller, the schedule, the summary and
4826
+ * the failure handling are all testable without a database and without
4827
+ * `@rebasepro/rls-check` present.
4828
+ */
4829
+ function createRlsAudit(config) {
4830
+ const enabled = config.enabled === true;
4831
+ const intervalMs = config.intervalMs ?? DEFAULT_INTERVAL_MS;
4832
+ const warnAt = config.warnAtSeverity ?? "high";
4833
+ const connectionString = config.connectionString ?? process.env.DATABASE_URL;
4834
+ let timer;
4835
+ let status = { enabled };
4836
+ if (enabled && !config.scan) status = {
4837
+ enabled: false,
4838
+ reason: "The scheduled RLS audit is enabled but no scanner was supplied. Pass `scan` from `@rebasepro/rls-check`: `rlsAudit: { enabled: true, scan }`."
4839
+ };
4840
+ else if (enabled && !connectionString) status = {
4841
+ enabled: false,
4842
+ reason: "The scheduled RLS audit is enabled but no connection string is available. Set DATABASE_URL, or pass `rlsAudit.connectionString`."
4843
+ };
4844
+ const runnable = enabled && !!connectionString && !!config.scan;
4845
+ const run = async () => {
4846
+ if (!runnable) return;
4847
+ try {
4848
+ const result = await config.scan({
4849
+ connectionString,
4850
+ schemas: config.schemas,
4851
+ statementTimeoutMs: config.statementTimeoutMs ?? DEFAULT_STATEMENT_TIMEOUT_MS
4852
+ });
4853
+ const { level, message } = summarize(result, warnAt);
4854
+ logger[level](`[rls-audit] ${message}`);
4855
+ status = {
4856
+ enabled: true,
4857
+ lastRunAt: result.scannedAt,
4858
+ result
4859
+ };
4860
+ } catch (err) {
4861
+ const detail = err instanceof Error ? err.message : String(err);
4862
+ logger.error(`[rls-audit] Scan failed: ${detail}`);
4863
+ status = {
4864
+ ...status,
4865
+ enabled: true,
4866
+ lastRunAt: (/* @__PURE__ */ new Date()).toISOString(),
4867
+ lastError: detail
4868
+ };
4869
+ }
4870
+ };
4871
+ return {
4872
+ start() {
4873
+ if (!runnable) {
4874
+ if (status.reason) logger.warn(`[rls-audit] ${status.reason}`);
4875
+ return;
4876
+ }
4877
+ if (timer) return;
4878
+ timer = setInterval(() => {
4879
+ run();
4880
+ }, intervalMs);
4881
+ timer.unref?.();
4882
+ if (config.runOnBoot !== false) run();
4883
+ },
4884
+ stop() {
4885
+ if (timer) clearInterval(timer);
4886
+ timer = void 0;
4887
+ },
4888
+ runNow: run,
4889
+ status: () => status
4890
+ };
4891
+ }
4892
+ //#endregion
4357
4893
  //#region src/init/surfaces.ts
4358
4894
  /** Every surface, in a stable order. */
4359
4895
  var ALL_RUNTIME_SURFACES = [
@@ -4383,7 +4919,8 @@ function resolveSurfaces(options) {
4383
4919
  function resolveOwnership(options) {
4384
4920
  return {
4385
4921
  cronScheduler: options?.cronScheduler ?? true,
4386
- jobWorkers: options?.jobWorkers ?? true
4922
+ jobWorkers: options?.jobWorkers ?? true,
4923
+ rlsAudit: options?.rlsAudit ?? true
4387
4924
  };
4388
4925
  }
4389
4926
  /** The surfaces a process is not serving, for a boot log line. */
@@ -10385,8 +10922,35 @@ function createHistoryRoutes(params) {
10385
10922
  * version or not — reads the same live client.
10386
10923
  */
10387
10924
  var INSTANCE_SLOT = Symbol.for("@rebasepro/server:singleton-instance");
10925
+ /**
10926
+ * A *function* that answers "which client is this call for", for hosts where
10927
+ * one answer per process is the wrong shape.
10928
+ *
10929
+ * A Node server boots once and serves every request from the same client, so
10930
+ * {@link INSTANCE_SLOT} is the whole story. An isolate-based host is different
10931
+ * in a way that matters: the environment arrives attached to the **request**,
10932
+ * not to the module, so there is nothing to publish at import time, and an
10933
+ * isolate can be reused across requests that must not share state. Such a host
10934
+ * registers a resolver — typically reading an `AsyncLocalStorage` populated per
10935
+ * request — and it is consulted first.
10936
+ *
10937
+ * This exists now, before there is a host that needs it, because of what it
10938
+ * protects: `rebase` is a lazy Proxy, so every property access already goes
10939
+ * through `getInstance()`. Backing that lookup with a resolver is therefore the
10940
+ * entire porting story for the singleton — **no function anyone has already
10941
+ * written changes**. Take the resolver away and the only alternative is asking
10942
+ * users to thread a client through their handlers, which is a rewrite of every
10943
+ * function file in existence.
10944
+ */
10945
+ var RESOLVER_SLOT = Symbol.for("@rebasepro/server:singleton-resolver");
10388
10946
  function getInstance() {
10389
- return globalThis[INSTANCE_SLOT] ?? null;
10947
+ const global = globalThis;
10948
+ const resolver = global[RESOLVER_SLOT];
10949
+ if (resolver) {
10950
+ const resolved = resolver();
10951
+ if (resolved) return resolved;
10952
+ }
10953
+ return global[INSTANCE_SLOT] ?? null;
10390
10954
  }
10391
10955
  function setInstance(client) {
10392
10956
  globalThis[INSTANCE_SLOT] = client;
@@ -10403,7 +10967,7 @@ function _initRebase(client) {
10403
10967
  * Throws an error if used in a non-test environment to prevent production abuse.
10404
10968
  */
10405
10969
  function _setRebaseMock(mockInstance) {
10406
- if (process.env.NODE_ENV !== "test") throw new Error("_setRebaseMock can only be called in a test environment (NODE_ENV=test).");
10970
+ if (hostEnv().NODE_ENV !== "test") throw new Error("_setRebaseMock can only be called in a test environment (NODE_ENV=test).");
10407
10971
  setInstance({
10408
10972
  ...getInstance() || {},
10409
10973
  ...mockInstance
@@ -10413,7 +10977,7 @@ function _setRebaseMock(mockInstance) {
10413
10977
  * @internal Resets the singleton instance, useful for afterEach() in test suites.
10414
10978
  */
10415
10979
  function _resetRebaseMock() {
10416
- if (process.env.NODE_ENV !== "test") throw new Error("_resetRebaseMock can only be called in a test environment.");
10980
+ if (hostEnv().NODE_ENV !== "test") throw new Error("_resetRebaseMock can only be called in a test environment.");
10417
10981
  setInstance(null);
10418
10982
  }
10419
10983
  /**
@@ -10865,7 +11429,7 @@ async function _initializeRebaseBackend(config) {
10865
11429
  ]) {
10866
11430
  const providerConfig = safeAuthConfig[key];
10867
11431
  if (providerConfig && requiredFields.every((f) => Boolean(providerConfig[f]))) {
10868
- const createFn = (await import("./auth-5Et5mnUA.js").then((n) => n.t))[factory];
11432
+ const createFn = (await import("./auth-B0IV-irB.js").then((n) => n.t))[factory];
10869
11433
  oauthProviders.push(createFn(providerConfig));
10870
11434
  }
10871
11435
  }
@@ -10879,6 +11443,7 @@ async function _initializeRebaseBackend(config) {
10879
11443
  disableSelfRegistration: safeAuthConfig.disableSelfRegistration ?? false,
10880
11444
  allowAnonymous: safeAuthConfig.allowAnonymous ?? false,
10881
11445
  allowUserLookup: safeAuthConfig.allowUserLookup ?? false,
11446
+ captcha: safeAuthConfig.captcha,
10882
11447
  defaultRole: safeAuthConfig.defaultRole,
10883
11448
  oauthProviders,
10884
11449
  allowedRedirectUris: safeAuthConfig.allowedRedirectUris,
@@ -10965,7 +11530,7 @@ async function _initializeRebaseBackend(config) {
10965
11530
  let schemaEditorOff = schemaEditorUnavailable();
10966
11531
  let schemaEditorRoutes;
10967
11532
  if (!schemaEditorOff && config.collectionsDir) try {
10968
- schemaEditorRoutes = (await import("./schema-editor-routes-CV9k0w3G.js")).createSchemaEditorRoutes(config.collectionsDir);
11533
+ schemaEditorRoutes = (await import("./schema-editor-routes-Cax2XTRj.js")).createSchemaEditorRoutes(config.collectionsDir);
10969
11534
  } catch (err) {
10970
11535
  if (err?.code === "ERR_MODULE_NOT_FOUND") {
10971
11536
  schemaEditorOff = {
@@ -10998,8 +11563,12 @@ async function _initializeRebaseBackend(config) {
10998
11563
  const storageAuthorizeData = {};
10999
11564
  if (storageController) {
11000
11565
  const storageMaxSize = (config.storage && typeof config.storage === "object" && "type" in config.storage ? config.storage.maxFileSize : void 0) ?? 50 * 1024 * 1024;
11566
+ const storageAccessControl = resolveStorageAccessControl({
11567
+ storagePolicies: config.storagePolicies,
11568
+ storageAuthorize: config.storageAuthorize
11569
+ });
11001
11570
  assertStorageAccessControlConfigured({
11002
- hasAuthorize: !!config.storageAuthorize,
11571
+ hasAuthorize: !!config.storageAuthorize || !!config.storagePolicies?.length,
11003
11572
  publicRead: config.storagePublicRead === true,
11004
11573
  allowAnyAuthenticated: config.storageInsecureAllowAnyAuthenticated === true
11005
11574
  }, isProduction);
@@ -11010,7 +11579,7 @@ async function _initializeRebaseBackend(config) {
11010
11579
  requireAuth: resolveRequireAuth(config.auth),
11011
11580
  publicRead: config.storagePublicRead === true,
11012
11581
  authAdapter,
11013
- authorize: config.storageAuthorize,
11582
+ authorize: storageAccessControl,
11014
11583
  authorizeData: () => storageAuthorizeData.current
11015
11584
  });
11016
11585
  const storageRouter = new Hono();
@@ -11147,9 +11716,9 @@ async function _initializeRebaseBackend(config) {
11147
11716
  if (driver && "client" in driver) driver.client = serverClient;
11148
11717
  }
11149
11718
  if (config.functionsDir && surfaces.functions) {
11150
- const { loadFunctionsWithDiagnostics } = await import("./function-loader-DDS1v7YX.js").then((n) => n.t);
11719
+ const { loadFunctionsWithDiagnostics } = await import("./function-loader-BrLmC_-y.js").then((n) => n.t);
11151
11720
  const { createFunctionRoutes } = await import("./function-routes-Btcez1T-.js").then((n) => n.n);
11152
- const { createFunctionsRequestTimeout, resolveFunctionsTimeoutMs } = await import("./request-timeout-BuFoEKwT.js");
11721
+ const { createFunctionsRequestTimeout, resolveFunctionsTimeoutMs } = await import("./request-timeout-OofPCHQT.js").then((n) => n.t);
11153
11722
  const { functions: allFunctions, problems } = await loadFunctionsWithDiagnostics(config.functionsDir);
11154
11723
  const { selectFunctions } = await import("./selection-_z6TM1DB.js").then((n) => n.n);
11155
11724
  const loadedFunctions = selectFunctions(allFunctions, config.functionsSelection);
@@ -11190,7 +11759,7 @@ async function _initializeRebaseBackend(config) {
11190
11759
  else logger.warn(`Function routes mounted at ${basePath}/functions, but no functions loaded from ${config.functionsDir}. ` + (problems.length > 0 ? `${problems.length} file(s) were skipped — see the messages above.` : "The directory holds no .ts/.js function files."));
11191
11760
  }
11192
11761
  if (config.functionsUpstream && !surfaces.functions) {
11193
- const { createFunctionsProxy } = await import("./proxy-Bj5DVllb.js");
11762
+ const { createFunctionsProxy } = await import("./proxy-QJKSS-CV.js").then((n) => n.t);
11194
11763
  config.app.route(`${basePath}/functions`, createFunctionsProxy({
11195
11764
  upstream: config.functionsUpstream,
11196
11765
  basePath: `${basePath}/functions`
@@ -11202,10 +11771,10 @@ async function _initializeRebaseBackend(config) {
11202
11771
  }
11203
11772
  let cronScheduler;
11204
11773
  if (config.cronsDir && (surfaces.cron || ownership.cronScheduler)) {
11205
- const { loadCronJobsWithDiagnostics } = await import("./cron-loader-YhhQeVBM.js").then((n) => n.t);
11206
- const { CronScheduler } = await import("./cron-scheduler-DIpYBmZP.js").then((n) => n.n);
11207
- const { createCronRoutes } = await import("./cron-routes-maM_RlUu.js").then((n) => n.n);
11208
- const { createCronStore } = await import("./cron-store-DfH_4Cd9.js").then((n) => n.n);
11774
+ const { loadCronJobsWithDiagnostics } = await import("./cron-loader-BMvtW6-J.js").then((n) => n.t);
11775
+ const { CronScheduler } = await import("./cron-scheduler-Buf-uVam.js").then((n) => n.n);
11776
+ const { createCronRoutes } = await import("./cron-routes-BvYk-Kmi.js").then((n) => n.n);
11777
+ const { createCronStore } = await import("./cron-store-CpfttP4f.js").then((n) => n.n);
11209
11778
  const { jobs: loadedCronJobs, problems: cronProblems } = await loadCronJobsWithDiagnostics(config.cronsDir);
11210
11779
  cronScheduler = new CronScheduler();
11211
11780
  cronScheduler.setClient(serverSingleton);
@@ -11235,7 +11804,7 @@ async function _initializeRebaseBackend(config) {
11235
11804
  }
11236
11805
  let jobQueue;
11237
11806
  if (config.jobs?.enabled) {
11238
- const { createJobStore, createJobQueue } = await import("./jobs-CyOKXXlu.js").then((n) => n.t);
11807
+ const { createJobStore, createJobQueue } = await import("./jobs-CSlBZ8dn.js").then((n) => n.t);
11239
11808
  const store = createJobStore(defaultDriver);
11240
11809
  if (store) {
11241
11810
  await store.ensureTable();
@@ -11246,8 +11815,9 @@ async function _initializeRebaseBackend(config) {
11246
11815
  } else logger.info("Job queue available for enqueue, workers NOT started in this process", { tasks: Object.keys(config.jobs.tasks ?? {}).length });
11247
11816
  } else logger.warn("Job queue requested but unavailable on this driver — `rebase.jobs.enqueue` will throw, and webhook deliveries fall back to the in-memory queue.");
11248
11817
  }
11818
+ let rlsAudit;
11249
11819
  if (surfaces.admin) {
11250
- const { createBackupRoutes, parseBackupDestination } = await import("./backup-C6ljYVTp.js").then((n) => n.t);
11820
+ const { createBackupRoutes, parseBackupDestination } = await import("./backup-BJ86ah4T.js").then((n) => n.t);
11251
11821
  const backupRouter = new Hono();
11252
11822
  applyAdminGate(backupRouter, "Backup");
11253
11823
  backupRouter.route("/", createBackupRoutes({
@@ -11260,6 +11830,16 @@ async function _initializeRebaseBackend(config) {
11260
11830
  config.app.route(`${basePath}/admin/backups`, backupRouter);
11261
11831
  logger.debug("Backup admin routes mounted", { path: `${basePath}/admin/backups` });
11262
11832
  }
11833
+ if (surfaces.admin) {
11834
+ const rlsAuditRouter = new Hono();
11835
+ applyAdminGate(rlsAuditRouter, "RLS audit");
11836
+ rlsAuditRouter.get("/", (c) => c.json(rlsAudit ? rlsAudit.status() : {
11837
+ enabled: false,
11838
+ reason: config.rlsAudit?.enabled ? "This process serves the admin surface but does not own the RLS audit." : "The scheduled RLS audit is off. Set `rlsAudit.enabled` to turn it on."
11839
+ }));
11840
+ config.app.route(`${basePath}/admin/rls-audit`, rlsAuditRouter);
11841
+ logger.debug("RLS audit admin routes mounted", { path: `${basePath}/admin/rls-audit` });
11842
+ }
11263
11843
  if (surfaces.admin) {
11264
11844
  const { default: logsRoutes } = await import("./logs-routes-CWBLQj2l.js").then((n) => n.n);
11265
11845
  const logsRouter = new Hono();
@@ -11269,7 +11849,7 @@ async function _initializeRebaseBackend(config) {
11269
11849
  logger.debug("Logs routes mounted", { path: `${basePath}/logs` });
11270
11850
  }
11271
11851
  if (surfaces.meta) {
11272
- const { createContractRoutes } = await import("./contract-routes-DZ-LBpSL.js").then((n) => n.t);
11852
+ const { createContractRoutes } = await import("./contract-routes-BEq7euZg.js").then((n) => n.t);
11273
11853
  const contractRouter = new Hono();
11274
11854
  if (adminSurfacesGated) {
11275
11855
  if (apiKeyPreAuth) contractRouter.use("/contract", apiKeyPreAuth);
@@ -11298,10 +11878,15 @@ async function _initializeRebaseBackend(config) {
11298
11878
  logger.debug("Rebase Backend Initialized");
11299
11879
  const authSchemaCheck = authConfigResult?.schemaHealthCheck;
11300
11880
  const healthCheck = createHealthCheck(defaultDriver, authSchemaCheck ? () => authSchemaCheck.call(authConfigResult) : void 0);
11881
+ if (config.rlsAudit?.enabled && ownership.rlsAudit) {
11882
+ rlsAudit = createRlsAudit(config.rlsAudit);
11883
+ rlsAudit.start();
11884
+ }
11301
11885
  const shutdown = createShutdown({
11302
11886
  server: config.server,
11303
11887
  cronScheduler,
11304
11888
  jobQueue,
11889
+ rlsAudit,
11305
11890
  realtimeServices
11306
11891
  });
11307
11892
  /**
@@ -11385,6 +11970,276 @@ function defineFunction(definition) {
11385
11970
  return returned instanceof Hono ? returned : app;
11386
11971
  }
11387
11972
  //#endregion
11973
+ //#region src/functions/context.ts
11974
+ function read(c, key) {
11975
+ return c.get(key);
11976
+ }
11977
+ /**
11978
+ * The authenticated caller, or `undefined` for an anonymous request.
11979
+ *
11980
+ * **`undefined` is not a permission decision.** The functions router mounts its
11981
+ * auth middleware with `requireAuth: false` on purpose — a webhook receiver has
11982
+ * no token to send — so an anonymous caller reaches the handler and reads
11983
+ * `undefined` here while the handler runs on regardless. Use {@link requireAuth}
11984
+ * (or a `!user` branch that returns 401) to make it a decision.
11985
+ *
11986
+ * A caller who presented a *bad* token never gets this far: both auth
11987
+ * middlewares reject an unverifiable token with 401 before the router is
11988
+ * reached, precisely so an expired session cannot be silently downgraded to an
11989
+ * anonymous one.
11990
+ */
11991
+ function getUser(c) {
11992
+ const raw = read(c, "user");
11993
+ if (!raw || typeof raw !== "object") return void 0;
11994
+ const record = raw;
11995
+ const uid = typeof record.uid === "string" ? record.uid : typeof record.userId === "string" ? record.userId : void 0;
11996
+ if (uid === void 0) return void 0;
11997
+ const roles = Array.isArray(record.roles) ? record.roles.filter((role) => typeof role === "string") : [];
11998
+ return {
11999
+ ...record,
12000
+ uid,
12001
+ roles
12002
+ };
12003
+ }
12004
+ /** The caller's id, or `undefined` when nobody is signed in. */
12005
+ function getUserId(c) {
12006
+ return getUser(c)?.uid;
12007
+ }
12008
+ /** The caller's roles. Empty for an anonymous request — never `undefined`. */
12009
+ function getRoles(c) {
12010
+ return getUser(c)?.roles ?? [];
12011
+ }
12012
+ /**
12013
+ * Whether the caller holds **any** of the named roles.
12014
+ *
12015
+ * Any rather than all, because that is what a route guard means by a list of
12016
+ * roles; require several by calling this more than once.
12017
+ */
12018
+ function hasRole(c, ...roles) {
12019
+ if (roles.length === 0) return false;
12020
+ const held = new Set(getRoles(c));
12021
+ return roles.some((role) => held.has(role));
12022
+ }
12023
+ /**
12024
+ * Whether the caller holds an administrative role.
12025
+ *
12026
+ * Delegates to the single definition in `auth/admin-roles.ts` — which is
12027
+ * `admin` **or** `schema-admin` — rather than comparing against `"admin"`.
12028
+ * Those two lists disagreed once, and the gap made every public registrant an
12029
+ * administrator; see that file.
12030
+ */
12031
+ function isAdmin(c) {
12032
+ return hasAdministrativeRole(getRoles(c));
12033
+ }
12034
+ /** Whether the request carries an identity at all. */
12035
+ function isAuthenticated(c) {
12036
+ return getUser(c) !== void 0;
12037
+ }
12038
+ /**
12039
+ * The request-scoped data driver: reads and writes run as **the caller**, with
12040
+ * your row-level security policies evaluated against their identity.
12041
+ *
12042
+ * This is the accessor to reach for when a function serves user-facing data.
12043
+ * `rebase.dataAsAdmin` is the other one, and it is not the same thing — it runs
12044
+ * as `{ uid: "service", roles: ["admin"] }` for every caller alike, which is
12045
+ * correct for trusted background work and wrong for a request.
12046
+ *
12047
+ * `undefined` only when no Rebase auth middleware ran (see
12048
+ * {@link identityResolved}); inside a function mounted by the framework it is
12049
+ * always present, anonymous requests included — they get an anon-scoped driver
12050
+ * so policies still have an identity to evaluate.
12051
+ */
12052
+ function getDriver(c) {
12053
+ return read(c, "driver");
12054
+ }
12055
+ /**
12056
+ * {@link getDriver}, but throws instead of handing back `undefined`.
12057
+ *
12058
+ * For the common case where a handler cannot proceed without it and would
12059
+ * otherwise write `c.get("driver")!` — an assertion that turns a wiring problem
12060
+ * into `Cannot read properties of undefined (reading 'fetchCollection')` twenty
12061
+ * lines away from the cause.
12062
+ */
12063
+ function requireDriver(c) {
12064
+ const driver = getDriver(c);
12065
+ if (!driver) throw new Error("No request-scoped driver on this context. A Rebase auth middleware populates it before any custom function runs, so this means the handler was mounted outside the functions router — e.g. added to your own Hono app directly. Mount it from the functions directory, or use `rebase.dataAsAdmin` if the work is genuinely service-scoped.");
12066
+ return driver;
12067
+ }
12068
+ /**
12069
+ * The API key this request authenticated with, masked, or `undefined` when it
12070
+ * did not use one.
12071
+ *
12072
+ * Useful for attribution and for per-key behaviour. The permission check itself
12073
+ * has already happened — reaching a handler means the key was allowed to.
12074
+ */
12075
+ function getApiKey(c) {
12076
+ return read(c, "apiKey");
12077
+ }
12078
+ /**
12079
+ * The correlation id for this request — generated, or taken from an inbound
12080
+ * `X-Request-ID`.
12081
+ *
12082
+ * Log it. It is the only thing that ties a line written inside a function to
12083
+ * the framework's own lines for the same request.
12084
+ */
12085
+ function getRequestId(c) {
12086
+ return read(c, "requestId");
12087
+ }
12088
+ /**
12089
+ * Whether a Rebase auth middleware has run on this request.
12090
+ *
12091
+ * Both middlewares populate `driver` for *every* outcome, anonymous included,
12092
+ * and populate `user` whenever there is one. So "neither is set" does not mean
12093
+ * "anonymous" — it means nothing resolved the identity, and treating that as
12094
+ * anonymous is the dangerous reading. The guards use this to tell a genuinely
12095
+ * anonymous caller (401) from a misconfigured mount (500), because answering
12096
+ * 401 to the second sends whoever is debugging it to look at the token.
12097
+ */
12098
+ function identityResolved(c) {
12099
+ return read(c, "user") !== void 0 || read(c, "driver") !== void 0;
12100
+ }
12101
+ //#endregion
12102
+ //#region src/functions/guards.ts
12103
+ /**
12104
+ * The answer to "a guard ran, but no middleware had resolved anything".
12105
+ *
12106
+ * Deliberately a 500 and not a 401. A 401 tells the caller their credential is
12107
+ * the problem, and here the caller's credential was never looked at — sending
12108
+ * them to check their token is sending them to the one place the answer is not.
12109
+ */
12110
+ function unresolvedIdentity() {
12111
+ return { error: {
12112
+ message: "This route's identity was never resolved: no Rebase auth middleware ran before the guard. A function loaded from the functions directory always has one. This usually means the Hono app was mounted onto your own server directly, bypassing the functions router.",
12113
+ code: "AUTH_MIDDLEWARE_MISSING"
12114
+ } };
12115
+ }
12116
+ /**
12117
+ * Reject callers holding none of the named roles with 403.
12118
+ *
12119
+ * Any of them, not all — require several by chaining the guard twice. Naming no
12120
+ * role at all is a programming error and throws at module load rather than at
12121
+ * request time, because `requireRole()` with an empty list would otherwise read
12122
+ * as a guard while admitting everyone.
12123
+ *
12124
+ * @example
12125
+ * ```ts
12126
+ * app.post("/publish", requireAuth, requireRole("editor", "admin"), handler);
12127
+ * ```
12128
+ */
12129
+ function requireRole(...roles) {
12130
+ if (roles.length === 0) throw new Error("requireRole() needs at least one role. An empty list would admit every signed-in caller while reading as a restriction.");
12131
+ const allowed = new Set(roles);
12132
+ return async (c, next) => {
12133
+ if (!getUser(c)) {
12134
+ if (!identityResolved(c)) return c.json(unresolvedIdentity(), 500);
12135
+ return c.json({ error: {
12136
+ message: "Authentication required",
12137
+ code: "UNAUTHORIZED"
12138
+ } }, 401);
12139
+ }
12140
+ if (!getRoles(c).some((role) => allowed.has(role))) return c.json({ error: {
12141
+ message: `This operation requires one of these roles: ${roles.join(", ")}`,
12142
+ code: "FORBIDDEN"
12143
+ } }, 403);
12144
+ return next();
12145
+ };
12146
+ }
12147
+ //#endregion
12148
+ //#region src/functions/runtime-env.ts
12149
+ /**
12150
+ * Every environment variable visible to this request.
12151
+ *
12152
+ * `c.env` on workerd, `Deno.env` on Deno, `process.env` on Node, Bun and
12153
+ * Vercel's edge runtime. Reading it through here rather than through `process`
12154
+ * is the whole of what makes a function's configuration portable.
12155
+ */
12156
+ function getEnv(c) {
12157
+ return env(c);
12158
+ }
12159
+ /**
12160
+ * One environment variable, or `undefined`.
12161
+ *
12162
+ * Trimmed, and a blank value counts as absent — declaring a variable and
12163
+ * leaving it empty is the ordinary way to write a compose file or a `.env`
12164
+ * line, and nobody has ever meant `""` by it.
12165
+ */
12166
+ function env$1(c, name) {
12167
+ const raw = getEnv(c)[name];
12168
+ if (typeof raw !== "string") return void 0;
12169
+ const trimmed = raw.trim();
12170
+ return trimmed === "" ? void 0 : trimmed;
12171
+ }
12172
+ /**
12173
+ * One environment variable, or a thrown error naming it.
12174
+ *
12175
+ * For configuration a handler cannot run without. Throwing here — inside the
12176
+ * request — is deliberately better than the module-scope `process.env.KEY!`
12177
+ * it replaces: that one takes the whole *file* down at load time, and the
12178
+ * loader reports it as "this function could not be imported", which names the
12179
+ * file but not the variable. This fails one request, with the name in the
12180
+ * message, while every other route in the file keeps serving.
12181
+ */
12182
+ function requireEnv(c, name) {
12183
+ const value = env$1(c, name);
12184
+ if (value === void 0) throw new Error(`Missing required environment variable ${name}. Set it on the process (or as a binding, on a host that has them) — and read it inside the handler, not at module scope, so the same file works on both.`);
12185
+ return value;
12186
+ }
12187
+ /**
12188
+ * Which host this is: `"node"`, `"workerd"`, `"deno"`, `"bun"`, `"edge-light"`,
12189
+ * `"fastly"` or `"other"`.
12190
+ *
12191
+ * Use it to *degrade*, not to branch a whole implementation — a function that
12192
+ * needs two implementations is two functions. It is honest about one thing in
12193
+ * particular: a Node-only capability, such as `rebase.sql()`, can check this
12194
+ * and say so, instead of failing at the call.
12195
+ */
12196
+ function runtimeKey() {
12197
+ return getRuntimeKey();
12198
+ }
12199
+ /** Whether this is a Node-like host — Node itself, or Bun's Node compatibility. */
12200
+ function isNodeRuntime() {
12201
+ const key = getRuntimeKey();
12202
+ return key === "node" || key === "bun";
12203
+ }
12204
+ /**
12205
+ * Build something expensive once per environment, on first use, from inside a
12206
+ * request.
12207
+ *
12208
+ * This is the sanctioned replacement for the module-scope client:
12209
+ *
12210
+ * ```ts
12211
+ * // Don't: runs at import time, before bindings exist on some hosts.
12212
+ * const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
12213
+ *
12214
+ * // Do: runs on first request, reads that request's configuration.
12215
+ * const stripe = lazyResource(env => new Stripe(env.STRIPE_SECRET_KEY!));
12216
+ * app.post("/", async (c) => { await stripe(c).charges.list(); });
12217
+ * ```
12218
+ *
12219
+ * The factory runs once per distinct environment object and the result is
12220
+ * cached against it. On Node that is once per process, because `process.env` is
12221
+ * one object for the life of the process — identical to the module-scope
12222
+ * version in cost, and unlike it, deferred until configuration exists. On a
12223
+ * host that hands each request a fresh binding object it is once per object,
12224
+ * which is the only correct answer there: caching across bindings would serve
12225
+ * one tenant's client to another.
12226
+ *
12227
+ * A {@link WeakMap} holds the cache, so an environment object the host has
12228
+ * finished with takes the resource with it.
12229
+ */
12230
+ function lazyResource(factory) {
12231
+ const cache = /* @__PURE__ */ new WeakMap();
12232
+ return (c) => {
12233
+ const bindings = getEnv(c);
12234
+ if (!bindings || typeof bindings !== "object") return factory(bindings ?? {});
12235
+ const existing = cache.get(bindings);
12236
+ if (existing !== void 0) return existing;
12237
+ const created = factory(bindings);
12238
+ cache.set(bindings, created);
12239
+ return created;
12240
+ };
12241
+ }
12242
+ //#endregion
11388
12243
  //#region src/cron/define-cron.ts
11389
12244
  /**
11390
12245
  * Typed authoring helper for a cron job file. Identity at runtime —
@@ -11469,6 +12324,130 @@ var authJwt = () => {
11469
12324
  return sql.raw(RLS_JWT_SQL);
11470
12325
  };
11471
12326
  //#endregion
12327
+ //#region src/dev-secrets.ts
12328
+ /**
12329
+ * Development secrets that survive a restart.
12330
+ *
12331
+ * `loadEnv` generates `JWT_SECRET` and `REBASE_SERVICE_KEY` when they are unset
12332
+ * outside production, so a developer can start without setting anything up. It
12333
+ * generated them **freshly on every boot**, which meant every restart of the dev
12334
+ * server silently invalidated the previous run's tokens: you were logged out of
12335
+ * your own app because you edited a file, and the API key you had just made
12336
+ * stopped working.
12337
+ *
12338
+ * The CLI already worked around it for scaffolded projects by writing a stable
12339
+ * `JWT_SECRET` into `.env` at `rebase init` — the comment there says exactly
12340
+ * why. That leaves anyone who did not scaffold, and anyone running from a
12341
+ * container with no `.env`, with the logout-on-restart behaviour.
12342
+ *
12343
+ * So the generated values are cached in a file beside the other dev-state
12344
+ * files — `.rebase-dev-port` and `.rebase-dev-url` follow the same convention
12345
+ * and are gitignored in the same place.
12346
+ *
12347
+ * ## What this does not change
12348
+ *
12349
+ * Production. `loadEnv` refuses to start when a secret was auto-generated and
12350
+ * `NODE_ENV=production`, and that is untouched: this only makes the
12351
+ * already-generated development values stable. There is no path by which a
12352
+ * cached secret becomes a production one.
12353
+ *
12354
+ * ## Failing soft
12355
+ *
12356
+ * A read-only filesystem, a container without a writable working directory, a
12357
+ * permissions problem — none of these should stop a development server from
12358
+ * starting. Every failure here falls back to the previous behaviour, an
12359
+ * ephemeral secret, and says so once at debug level.
12360
+ */
12361
+ /** Beside `.rebase-dev-port` and `.rebase-dev-url`, and gitignored with them. */
12362
+ var DEV_SECRETS_FILENAME = ".rebase-dev-secrets.json";
12363
+ /** Owner read/write only. These are secrets, even development ones. */
12364
+ var FILE_MODE = 384;
12365
+ /** Whether these environment variables describe a test runner. */
12366
+ function detectTestRunner(env) {
12367
+ return Boolean(env.JEST_WORKER_ID || env.VITEST || env.VITEST_WORKER_ID || env.NODE_ENV === "test");
12368
+ }
12369
+ /**
12370
+ * Whether a test runner started this process, decided **once at import**.
12371
+ *
12372
+ * Read at call time it would be wrong, and provably so: `env.test.ts` does
12373
+ * `process.env = {}` in its `beforeEach`, which erases `NODE_ENV` and
12374
+ * `JEST_WORKER_ID` alike. A predicate consulting the live environment therefore
12375
+ * sees a bare object and concludes "development", which is how the first version
12376
+ * of this dropped a `.rebase-dev-secrets.json` into `packages/server` on every
12377
+ * test run.
12378
+ *
12379
+ * Whether a test runner is hosting the process is a fact about the process, not
12380
+ * about whatever a test has since done to `process.env`, so it is captured
12381
+ * before any test can rewrite it.
12382
+ */
12383
+ var TEST_RUNNER_DETECTED = detectTestRunner(process.env);
12384
+ /**
12385
+ * Whether generated secrets should be cached at all.
12386
+ *
12387
+ * Not in production — `loadEnv` refuses to boot there on a generated secret, so
12388
+ * there is nothing to cache and no path by which a cached one becomes a
12389
+ * production one.
12390
+ *
12391
+ * Not under a test runner: a test must not inherit a secret a previous run left
12392
+ * behind, and a library call has no business writing into a suite's working
12393
+ * directory. `inTestRunner` is a parameter only so this predicate can be tested
12394
+ * on its own; callers pass nothing.
12395
+ */
12396
+ function shouldCacheDevSecrets(env = process.env, inTestRunner = TEST_RUNNER_DETECTED) {
12397
+ if (inTestRunner) return false;
12398
+ return env.NODE_ENV !== "production";
12399
+ }
12400
+ /** `REBASE_DEV_SECRETS_FILE`, or the conventional name in the working directory. */
12401
+ function resolveDevSecretsFile(env = process.env) {
12402
+ const override = env.REBASE_DEV_SECRETS_FILE?.trim();
12403
+ if (override) return path$2.resolve(override);
12404
+ return path$2.join(process.cwd(), DEV_SECRETS_FILENAME);
12405
+ }
12406
+ /**
12407
+ * Open the cache.
12408
+ *
12409
+ * Reading a malformed file is treated as an empty cache rather than an error:
12410
+ * the file is ours, it holds regenerable values, and refusing to boot over a
12411
+ * corrupted convenience cache would be worse than rewriting it.
12412
+ */
12413
+ function openDevSecretStore(file = resolveDevSecretsFile()) {
12414
+ let cache = {};
12415
+ let usable = true;
12416
+ try {
12417
+ if (fs$2.existsSync(file)) {
12418
+ const parsed = JSON.parse(fs$2.readFileSync(file, "utf-8"));
12419
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
12420
+ for (const [key, value] of Object.entries(parsed)) if (typeof value === "string" && value.length > 0) cache[key] = value;
12421
+ }
12422
+ }
12423
+ } catch {
12424
+ cache = {};
12425
+ }
12426
+ const flush = () => {
12427
+ try {
12428
+ fs$2.mkdirSync(path$2.dirname(file), { recursive: true });
12429
+ fs$2.writeFileSync(file, `${JSON.stringify(cache, null, 2)}\n`, { mode: FILE_MODE });
12430
+ fs$2.chmodSync(file, FILE_MODE);
12431
+ } catch (err) {
12432
+ if (usable) {
12433
+ usable = false;
12434
+ logger.debug(`[dev-secrets] Could not write ${file}, so generated development secrets will not survive a restart: ${err instanceof Error ? err.message : String(err)}`);
12435
+ }
12436
+ }
12437
+ };
12438
+ return {
12439
+ file,
12440
+ get usable() {
12441
+ return usable;
12442
+ },
12443
+ get: (name) => cache[name],
12444
+ set: (name, value) => {
12445
+ cache[name] = value;
12446
+ flush();
12447
+ }
12448
+ };
12449
+ }
12450
+ //#endregion
11472
12451
  //#region src/env.ts
11473
12452
  /**
11474
12453
  * Generate a cryptographically secure random secret (hex-encoded).
@@ -11586,14 +12565,24 @@ function loadEnv(options) {
11586
12565
  const isProduction = process.env.NODE_ENV === "production";
11587
12566
  const autoGeneratedSecrets = [];
11588
12567
  if (!isProduction) {
11589
- if (!process.env.JWT_SECRET) {
11590
- process.env.JWT_SECRET = generateSecret();
11591
- autoGeneratedSecrets.push("JWT_SECRET");
11592
- }
11593
- if (!process.env.REBASE_SERVICE_KEY) {
11594
- process.env.REBASE_SERVICE_KEY = generateSecret();
11595
- autoGeneratedSecrets.push("REBASE_SERVICE_KEY");
11596
- }
12568
+ const devSecrets = shouldCacheDevSecrets() ? openDevSecretStore() : void 0;
12569
+ const reused = [];
12570
+ const ensure = (name) => {
12571
+ if (process.env[name]) return;
12572
+ const cached = devSecrets?.get(name);
12573
+ if (cached) {
12574
+ process.env[name] = cached;
12575
+ reused.push(name);
12576
+ } else {
12577
+ const generated = generateSecret();
12578
+ process.env[name] = generated;
12579
+ devSecrets?.set(name, generated);
12580
+ }
12581
+ autoGeneratedSecrets.push(name);
12582
+ };
12583
+ ensure("JWT_SECRET");
12584
+ ensure("REBASE_SERVICE_KEY");
12585
+ if (autoGeneratedSecrets.length > 0 && reused.length !== autoGeneratedSecrets.length && devSecrets?.usable) logger.info(`Generated development ${autoGeneratedSecrets.length > 1 ? "secrets" : "secret"} (${autoGeneratedSecrets.join(", ")}) and cached them in ${DEV_SECRETS_FILENAME} so a restart does not log you out. Set them explicitly for anything that is not a development machine.`);
11597
12586
  }
11598
12587
  const env = (options?.extend ? rebaseEnvSchema.merge(options.extend) : rebaseEnvSchema).superRefine((data, ctx) => {
11599
12588
  const d = data;
@@ -12774,6 +13763,18 @@ var bootEnvExtension = object({
12774
13763
  "false",
12775
13764
  ""
12776
13765
  ]).default("false").transform((v) => v === "true"),
13766
+ /**
13767
+ * Bot protection. Enabled by setting both — a provider without a secret
13768
+ * cannot verify anything, and is refused at boot rather than ignored.
13769
+ */
13770
+ CAPTCHA_PROVIDER: _enum([
13771
+ "turnstile",
13772
+ "hcaptcha",
13773
+ ""
13774
+ ]).optional(),
13775
+ CAPTCHA_SECRET: string().optional(),
13776
+ /** Comma-separated: register, login, forgotPassword, magicLink. */
13777
+ CAPTCHA_ROUTES: string().optional(),
12777
13778
  AUTH_COOKIE_SAME_SITE: _enum([
12778
13779
  "Strict",
12779
13780
  "Lax",
@@ -12874,6 +13875,19 @@ var bootEnvExtension = object({
12874
13875
  ""
12875
13876
  ]).optional().transform((v) => v === void 0 || v === "" ? void 0 : v === "true"),
12876
13877
  /**
13878
+ * Override whether this process runs the scheduled RLS audit. Unset follows
13879
+ * the role.
13880
+ *
13881
+ * Unlike cron and the job workers this is not claim-protected — every owner
13882
+ * scans on its own timer, which is redundant rather than unsafe — so a split
13883
+ * deployment sets it false everywhere but one process.
13884
+ */
13885
+ REBASE_RLS_AUDIT: _enum([
13886
+ "true",
13887
+ "false",
13888
+ ""
13889
+ ]).optional().transform((v) => v === void 0 || v === "" ? void 0 : v === "true"),
13890
+ /**
12877
13891
  * Comma-separated function names this process serves. Unset means all.
12878
13892
  *
12879
13893
  * `functions` role only. A name that is not in the bundle is a boot failure,
@@ -13023,7 +14037,8 @@ var ROLES = {
13023
14037
  },
13024
14038
  ownership: {
13025
14039
  cronScheduler: false,
13026
- jobWorkers: false
14040
+ jobWorkers: false,
14041
+ rlsAudit: false
13027
14042
  },
13028
14043
  provisionSchema: false
13029
14044
  },
@@ -13067,6 +14082,7 @@ function resolveRole(env) {
13067
14082
  const ownership = { ...base.ownership };
13068
14083
  if (env.REBASE_CRON_SCHEDULER !== void 0) ownership.cronScheduler = env.REBASE_CRON_SCHEDULER;
13069
14084
  if (env.REBASE_JOB_WORKERS !== void 0) ownership.jobWorkers = env.REBASE_JOB_WORKERS;
14085
+ if (env.REBASE_RLS_AUDIT !== void 0) ownership.rlsAudit = env.REBASE_RLS_AUDIT;
13070
14086
  return {
13071
14087
  role,
13072
14088
  surfaces: { ...base.surfaces },
@@ -13504,13 +14520,51 @@ async function initializeDataSources(sources, schema, resolveFrom = []) {
13504
14520
  //#endregion
13505
14521
  //#region src/boot/options.ts
13506
14522
  /**
13507
- * Build the email configuration, or `undefined` when no SMTP host is set.
14523
+ * True when an emailed link would be followable — i.e. when there is an
14524
+ * absolute base to build one from. Mirrors `assertEmailLinkBases`, which
14525
+ * refuses the boot otherwise.
14526
+ */
14527
+ function hasAbsoluteLinkBase(value) {
14528
+ if (!value) return false;
14529
+ try {
14530
+ const url = new URL(value);
14531
+ return url.protocol === "http:" || url.protocol === "https:";
14532
+ } catch {
14533
+ return false;
14534
+ }
14535
+ }
14536
+ /**
14537
+ * Build the email configuration.
14538
+ *
14539
+ * With `SMTP_HOST`, that is a real SMTP transport. Without it, outside
14540
+ * production, it is the development sink: mail is captured and its links are
14541
+ * printed, so `POST /auth/magic-link` and `POST /auth/forgot-password` complete
14542
+ * on a fresh project instead of answering `503 EMAIL_NOT_CONFIGURED`. The token
14543
+ * was always minted and valid; only delivery was missing.
14544
+ *
14545
+ * Three conditions, all required, none configurable:
14546
+ *
14547
+ * - no `SMTP_HOST` — a configured mail server always wins;
14548
+ * - `NODE_ENV` is not `production` — a captured reset mail carries a working
14549
+ * token, so this must not be reachable there (see `dev-sink.ts`);
14550
+ * - an absolute `FRONTEND_URL`, or the emailed link has no base and is dead on
14551
+ * arrival — which is the condition `assertEmailLinkBases` fails the boot on.
13508
14552
  *
13509
- * Without it the auth adapter still works; password-reset and verification mail
13510
- * simply has nowhere to go, which the auth routes report for themselves.
14553
+ * When any of those does not hold this returns `undefined`, exactly as before,
14554
+ * and the auth routes report the missing service for themselves.
13511
14555
  */
13512
14556
  function resolveEmailOptions(env) {
13513
- if (!env.SMTP_HOST) return void 0;
14557
+ if (!env.SMTP_HOST) {
14558
+ if (env.NODE_ENV === "production") return void 0;
14559
+ if (!hasAbsoluteLinkBase(env.FRONTEND_URL)) return void 0;
14560
+ return {
14561
+ from: env.SMTP_FROM || `${env.APP_NAME} <noreply@rebase.pro>`,
14562
+ sendEmail: createDevEmailSink().sendEmail,
14563
+ appName: env.APP_NAME,
14564
+ logoUrl: env.EMAIL_LOGO_URL,
14565
+ resetPasswordUrl: env.FRONTEND_URL
14566
+ };
14567
+ }
13514
14568
  return {
13515
14569
  from: env.SMTP_FROM || `${env.APP_NAME} <noreply@rebase.pro>`,
13516
14570
  smtp: {
@@ -13529,6 +14583,24 @@ function resolveEmailOptions(env) {
13529
14583
  };
13530
14584
  }
13531
14585
  /**
14586
+ * Bot protection from the environment, or `undefined` when it is not configured.
14587
+ *
14588
+ * Both halves are required together. A provider with no secret cannot verify
14589
+ * anything, so it is left off here and `resolveCaptchaVerifier` refuses the boot
14590
+ * if something else turns it on — the one failure this feature must not have is
14591
+ * being silently absent while the config says otherwise.
14592
+ */
14593
+ function resolveCaptchaOptions(env) {
14594
+ if (!env.CAPTCHA_PROVIDER || !env.CAPTCHA_SECRET) return void 0;
14595
+ const routes = env.CAPTCHA_ROUTES?.split(",").map((part) => part.trim()).filter(Boolean);
14596
+ return {
14597
+ enabled: true,
14598
+ provider: env.CAPTCHA_PROVIDER,
14599
+ secret: env.CAPTCHA_SECRET,
14600
+ ...routes?.length ? { routes } : {}
14601
+ };
14602
+ }
14603
+ /**
13532
14604
  * Build the auth configuration from the environment and the bundle's users
13533
14605
  * collection.
13534
14606
  *
@@ -13552,6 +14624,7 @@ function resolveAuthOptions(env, usersCollection) {
13552
14624
  disableSelfRegistration: env.DISABLE_SELF_REGISTRATION,
13553
14625
  allowAnonymous: env.ALLOW_ANONYMOUS,
13554
14626
  allowUserLookup: env.AUTH_ALLOW_USER_LOOKUP,
14627
+ captcha: resolveCaptchaOptions(env),
13555
14628
  email: resolveEmailOptions(env),
13556
14629
  cookieAuth: { sameSite: env.AUTH_COOKIE_SAME_SITE || "Lax" }
13557
14630
  };
@@ -14322,6 +15395,6 @@ function warnOnUnusableBundleShape(bundle) {
14322
15395
  logger.warn(`This bundle declares a config package at "${bundle.manifest.entry.config}", but no collections directory resolved inside it, so no collections were loaded and no tables will be created. Rebuild with \`rebase build\` and check the manifest's \`entry.collections\`.`);
14323
15396
  }
14324
15397
  //#endregion
14325
- export { ApiError, BlockedUrlError, BundleError, CONCURRENT_DDL_SQLSTATES, CronScheduler, DEFAULT_DRIVER_ID, DEFAULT_MAX_FILE_SIZE, DEFAULT_STORAGE_ID, DOCUMENT_MIME_TYPES, DefaultDriverRegistry, DefaultStorageRegistry, GCSStorageController, IMAGE_MIME_TYPES, LocalStorageController, MetricsRegistry, RawHtml, S3StorageController, SMTPEmailService, TransformCache, TusHandler, UnknownStorageSourceError, WEBHOOK_DELIVERY_TASK, WebhookDispatcher, _resetRebaseMock, _setRebaseMock, applyCollectionDefaults, assertAllowedOutboundUrl, assertCollectionConfigs, assertDistinctSuffixes, assertEmailLinkBases, assertWriteRequestValid, authJwt, authRoles, authUid, blockedAddressReason, bootFromBundle, classifySurface, cleanupDevPortFile, createAppleProvider, createBackupRoutes, createBitbucketProvider, createBuiltinAuthAdapter, createContractRoutes, createCronRoutes, createCronStore, createCustomAuthAdapter, createDdlBootstrapper, createDiscordProvider, createEmailService, createFacebookProvider, createFunctionRoutes, createGitHubProvider, createGitLabProvider, createGoogleProvider, createHistoryRoutes, createJobQueue, createJobStore, createJwksRoutes, createLinkedinProvider, createMetricsMiddleware, createMetricsRoutes, createMicrosoftProvider, createSlackProvider, createSourceBundle, createSpotifyProvider, createStorageController, createStorageRoutes, createTwitterProvider, defaultBackoff, defineCron, defineFunction, envSuffixForKey, errorHandler, escapeHtml, extractUserFromToken, fileTokenAuth, findCollectionConfigProblems, generateSecurePassword, getEmailVerificationTemplate, getJwks, getMagicLinkTemplate, getPasswordResetTemplate, getUserInvitationTemplate, getWelcomeEmailTemplate, hasAsymmetricSigningKey, hashPassword, html, httpMethodToOperation, initializeDataSource, initializeDataSources, initializeRebaseBackend, installShutdownHandlers, isApiKeyToken, isAuthAdapter, isConcurrentDdlRace, isDatabaseAdapter, isDuplicateObjectRace, isLocalhostOrigin, isOperationAllowed, isTransformableImage, listBackupObjects, listenWithPortRetry, loadBootEnv, loadBundle, loadBundleConfigExports, loadBundleSchema, loadCollectionsFromDirectory, loadCronJobsFromDirectory, loadCronJobsWithDiagnostics, loadDeclaredStorageSources, loadEnv, loadFunctionsFromDirectory, loadUsersCollection, logger, oauthCodeFlowSchema, optionalAuth, parseBackupDestination, parseBackupTimestamp, parseTransformOptions, pkceTokenParams, providerVerifiedEmail, queryTokenAuth, raw, readBackupBytes, readBundleManifest, rebase, requireAdmin, requireAuth, resolveAuthHooks, resolveAuthOptions, resolveCorsOrigin, resolveDataSources, resolveEmailBranding, resolveEmailLinkBase, resolveEmailOptions, resolveRequireAuth, resolveStorageBackend, resolveStorageSources, runFromBundle, safeCompare, serveSPA, transformImage, tryVerifyOidcIdToken, unknownKeyPolicyFromEnv, validateApiKey, validateCronExpression, validatePasswordStrength, verifyOidcIdToken, verifyPassword };
15398
+ export { ApiError, BlockedUrlError, BundleError, CONCURRENT_DDL_SQLSTATES, CronScheduler, DEFAULT_DRIVER_ID, DEFAULT_MAX_FILE_SIZE, DEFAULT_STORAGE_ID, DOCUMENT_MIME_TYPES, DefaultDriverRegistry, DefaultStorageRegistry, GCSStorageController, IMAGE_MIME_TYPES, LocalStorageController, MetricsRegistry, RawHtml, S3StorageController, SMTPEmailService, StoragePolicyError, TransformCache, TusHandler, UnknownStorageSourceError, WEBHOOK_DELIVERY_TASK, WebhookDispatcher, _resetRebaseMock, _setRebaseMock, applyCollectionDefaults, assertAllowedOutboundUrl, assertCollectionConfigs, assertDistinctSuffixes, assertEmailLinkBases, assertWriteRequestValid, authJwt, authRoles, authUid, blockedAddressReason, bootFromBundle, classifySurface, cleanupDevPortFile, compileStoragePolicies, createAppleProvider, createBackupRoutes, createBitbucketProvider, createBuiltinAuthAdapter, createContractRoutes, createCronRoutes, createCronStore, createCustomAuthAdapter, createDdlBootstrapper, createDevEmailSink, createDiscordProvider, createEmailService, createFacebookProvider, createFunctionRoutes, createGitHubProvider, createGitLabProvider, createGoogleProvider, createHistoryRoutes, createJobQueue, createJobStore, createJwksRoutes, createLinkedinProvider, createMetricsMiddleware, createMetricsRoutes, createMicrosoftProvider, createRlsAudit, createSlackProvider, createSourceBundle, createSpotifyProvider, createStorageController, createStorageRoutes, createTwitterProvider, defaultBackoff, defineCron, defineFunction, drainBackgroundWork, envSuffixForKey, errorHandler, escapeHtml, extractLinks, extractUserFromToken, fileTokenAuth, findCollectionConfigProblems, generateSecurePassword, getApiKey, getDriver, getEmailVerificationTemplate, getEnv, getJwks, getMagicLinkTemplate, getPasswordResetTemplate, getRequestId, getRoles, getUser, getUserId, getUserInvitationTemplate, getWelcomeEmailTemplate, hasAsymmetricSigningKey, hasRole, hashPassword, html, httpMethodToOperation, identityResolved, initializeDataSource, initializeDataSources, initializeRebaseBackend, installShutdownHandlers, isAdmin, isApiKeyToken, isAuthAdapter, isAuthenticated, isConcurrentDdlRace, isDatabaseAdapter, isDuplicateObjectRace, isLocalhostOrigin, isNodeRuntime, isOperationAllowed, isTransformableImage, lazyResource, listBackupObjects, listenWithPortRetry, loadBootEnv, loadBundle, loadBundleConfigExports, loadBundleSchema, loadCollectionsFromDirectory, loadCronJobsFromDirectory, loadCronJobsWithDiagnostics, loadDeclaredStorageSources, loadEnv, loadFunctionsFromDirectory, loadFunctionsWithDiagnostics, loadUsersCollection, logger, oauthCodeFlowSchema, optionalAuth, parseBackupDestination, parseBackupTimestamp, parseTransformOptions, pendingBackgroundWork, pkceTokenParams, providerVerifiedEmail, queryTokenAuth, raw, readBackupBytes, readBundleManifest, rebase, requireAdmin, requireAuth, requireDriver, requireEnv, requireRole, resolveAuthHooks, resolveAuthOptions, resolveCorsOrigin, resolveDataSources, resolveEmailBranding, resolveEmailLinkBase, resolveEmailOptions, resolveRequireAuth, resolveStorageAccessControl, resolveStorageBackend, resolveStorageSources, runFromBundle, runtimeKey, safeCompare, serveSPA, summarize as summarizeRlsScan, transformImage, tryVerifyOidcIdToken, unknownKeyPolicyFromEnv, validateApiKey, validateCronExpression, validatePasswordStrength, verifyOidcIdToken, verifyPassword, waitUntil };
14326
15399
 
14327
15400
  //# sourceMappingURL=index.es.js.map