@rebasepro/server 0.25.0-canary.ge06257e → 0.25.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/{GCSStorageController-CAKa_EFW.js → GCSStorageController-CRd9bFrc.js} +3 -3
  2. package/dist/{GCSStorageController-CAKa_EFW.js.map → GCSStorageController-CRd9bFrc.js.map} +1 -1
  3. package/dist/{S3StorageController-Kt-LJpf3.js → S3StorageController-D00EFa5P.js} +3 -3
  4. package/dist/{S3StorageController-Kt-LJpf3.js.map → S3StorageController-D00EFa5P.js.map} +1 -1
  5. package/dist/api/closed-database.d.ts +25 -0
  6. package/dist/api/errors.d.ts +6 -10
  7. package/dist/{auth-xk37vrHO.js → auth-Dkvo2tTJ.js} +43 -35
  8. package/dist/auth-Dkvo2tTJ.js.map +1 -0
  9. package/dist/backend/boot-announcements.d.ts +13 -0
  10. package/dist/backend/create-backend.d.ts +4 -0
  11. package/dist/{backup-CpaJWXSc.js → backup-B9m3cb3X.js} +7 -7
  12. package/dist/{backup-CpaJWXSc.js.map → backup-B9m3cb3X.js.map} +1 -1
  13. package/dist/boot/boot.d.ts +10 -0
  14. package/dist/{contract-routes-DiFwiLg2.js → contract-routes-CsFXW7j4.js} +2 -2
  15. package/dist/{contract-routes-DiFwiLg2.js.map → contract-routes-CsFXW7j4.js.map} +1 -1
  16. package/dist/{cron-loader-NHqRfKF0.js → cron-loader-Dj6lA4HQ.js} +2 -2
  17. package/dist/{cron-loader-NHqRfKF0.js.map → cron-loader-Dj6lA4HQ.js.map} +1 -1
  18. package/dist/{cron-routes-CPIhdasM.js → cron-routes-CwbLZguO.js} +5 -5
  19. package/dist/{cron-routes-CPIhdasM.js.map → cron-routes-CwbLZguO.js.map} +1 -1
  20. package/dist/{cron-scheduler-D1DT0nw5.js → cron-scheduler-B04ACoUE.js} +2 -2
  21. package/dist/{cron-scheduler-D1DT0nw5.js.map → cron-scheduler-B04ACoUE.js.map} +1 -1
  22. package/dist/{cron-store-DEpVstUU.js → cron-store-DkyfuLOH.js} +3 -3
  23. package/dist/{cron-store-DEpVstUU.js.map → cron-store-DkyfuLOH.js.map} +1 -1
  24. package/dist/{ddl-bootstrap-BNqi2NZe.js → ddl-bootstrap-Ck20GSIX.js} +2 -2
  25. package/dist/{ddl-bootstrap-BNqi2NZe.js.map → ddl-bootstrap-Ck20GSIX.js.map} +1 -1
  26. package/dist/{errors-CHSJYpoy.js → errors-OMWkKSH-.js} +76 -22
  27. package/dist/errors-OMWkKSH-.js.map +1 -0
  28. package/dist/{function-loader-DbCSoNJj.js → function-loader-KKDqTcIr.js} +2 -2
  29. package/dist/{function-loader-DbCSoNJj.js.map → function-loader-KKDqTcIr.js.map} +1 -1
  30. package/dist/{function-routes-C0emkHtX.js → function-routes-RxrCHNuM.js} +2 -2
  31. package/dist/{function-routes-C0emkHtX.js.map → function-routes-RxrCHNuM.js.map} +1 -1
  32. package/dist/functions/index.js +18 -1
  33. package/dist/functions/index.js.map +1 -1
  34. package/dist/{history-recorder-RZ1Ow4YY.js → history-recorder-B2rkuhuy.js} +2 -2
  35. package/dist/{history-recorder-RZ1Ow4YY.js.map → history-recorder-B2rkuhuy.js.map} +1 -1
  36. package/dist/index.es.js +632 -129
  37. package/dist/index.es.js.map +1 -1
  38. package/dist/{jobs-8G-_agld.js → jobs-h1WRq3cp.js} +3 -3
  39. package/dist/{jobs-8G-_agld.js.map → jobs-h1WRq3cp.js.map} +1 -1
  40. package/dist/{jwt-YhciR93H.js → jwt-D3oFauZI.js} +2 -2
  41. package/dist/{jwt-YhciR93H.js.map → jwt-D3oFauZI.js.map} +1 -1
  42. package/dist/{logger-Bt32sHFw.js → logger-CW8pmTS6.js} +35 -3
  43. package/dist/logger-CW8pmTS6.js.map +1 -0
  44. package/dist/{logs-routes-BM2cDory.js → logs-routes-CBt7mMIL.js} +3 -3
  45. package/dist/{logs-routes-BM2cDory.js.map → logs-routes-CBt7mMIL.js.map} +1 -1
  46. package/dist/{openapi-generator-IKwDAQGO.js → openapi-generator-BjHr6upl.js} +2 -2
  47. package/dist/{openapi-generator-IKwDAQGO.js.map → openapi-generator-BjHr6upl.js.map} +1 -1
  48. package/dist/{proxy-BEKbsil6.js → proxy-CH8Jsovg.js} +2 -2
  49. package/dist/{proxy-BEKbsil6.js.map → proxy-CH8Jsovg.js.map} +1 -1
  50. package/dist/{query-parser-BnhugNwZ.js → query-parser-otFZqJsC.js} +3 -3
  51. package/dist/{query-parser-BnhugNwZ.js.map → query-parser-otFZqJsC.js.map} +1 -1
  52. package/dist/{request-timeout-LiGpCCP1.js → request-timeout-59crS-9u.js} +3 -3
  53. package/dist/{request-timeout-LiGpCCP1.js.map → request-timeout-59crS-9u.js.map} +1 -1
  54. package/dist/{schema-editor-routes-zjVKreVQ.js → schema-editor-routes-Bsut0bCC.js} +2 -2
  55. package/dist/{schema-editor-routes-zjVKreVQ.js.map → schema-editor-routes-Bsut0bCC.js.map} +1 -1
  56. package/dist/{soft-delete-params-uHxJoETb.js → soft-delete-params-BmlVwecC.js} +2 -2
  57. package/dist/{soft-delete-params-uHxJoETb.js.map → soft-delete-params-BmlVwecC.js.map} +1 -1
  58. package/dist/{sql-export-routes-D86u7N9i.js → sql-export-routes-DmB_uZ4l.js} +3 -3
  59. package/dist/{sql-export-routes-D86u7N9i.js.map → sql-export-routes-DmB_uZ4l.js.map} +1 -1
  60. package/dist/{upload-validation-pXcDy_EQ.js → upload-validation-Bgo626uY.js} +2 -2
  61. package/dist/{upload-validation-pXcDy_EQ.js.map → upload-validation-Bgo626uY.js.map} +1 -1
  62. package/dist/utils/dev-dependency-command.d.ts +15 -0
  63. package/dist/utils/dev-port.d.ts +8 -0
  64. package/dist/utils/logger.d.ts +24 -0
  65. package/package.json +5 -5
  66. package/dist/auth-xk37vrHO.js.map +0 -1
  67. package/dist/errors-CHSJYpoy.js.map +0 -1
  68. package/dist/logger-Bt32sHFw.js.map +0 -1
package/dist/index.es.js CHANGED
@@ -7,38 +7,38 @@ import { a as MAX_LIST_LIMIT, c as RebaseClientError, l as isUnsupported, n as s
7
7
  import { C as isFieldOperation, T as EntityRelation, _ as normalizeOrderBy, b as BATCH_REF_KEY, c as collectAllPages, g as OrderBySpecError, l as paginateFind, m as normalizeInclude, p as mergeIncludeSpecs, r as deserializeFilter, t as RESERVED_QUERY_KEYS, u as resolveFindWindow, v as serializeOrderBy, x as FIELD_OPERATORS, y as withRelationReferencesOnly } from "./filter-dialect-O1HK6Nvr.js";
8
8
  import { A as DEFAULT_RESOURCE_KEY, C as declaredSubscriptions, D as setTopicRuntime, E as setQueueRuntime, F as resourceKind, G as DEFAULT_DATA_SOURCE_KEY, H as getDeclaredSubcollections, I as resourceKinds, K as getDataSourceCapabilities, L as RLS_JWT_SQL, M as resolveResourceRefs, N as resourceEnvSuffix, O as DEFAULT_STORAGE_SOURCE_KEY, P as resourceKeyOf, R as RLS_ROLES_SQL, S as declaredQueueConsumers, T as resourceToStorageSource, U as isPostgresCollectionConfig, V as getCollectionDataPath, _ as scopesForRoles, c as intersectScopes, et as sortKeyToString, h as scopeGrantsAny, j as buildResourceGraph, k as storageEnvSuffix, m as scopeGrants, p as parseScope, q as ALL_WHERE_FILTER_OPS, s as hasAdminRole, w as resourceToDataSource, y as summarizeScopes, z as RLS_UID_SQL } from "./src-D5Y3N-S4.js";
9
9
  import { a as nestCollectionPatchPaths, c as ADMIN_PROPERTY_KEYS, i as isCollectionPatch, n as collectionPatchProblems, r as diffCollections, s as ADMIN_COLLECTION_KEYS, t as applyCollectionPatch } from "./collection_patch-BRu-BvDv.js";
10
- import { a as softDeleteFieldOf, c as JUNCTION_PIVOT_KEY, i as restoresSoftDeletedRow, l as hasForeignKeyOnTarget, n as buildCollectionSchema, o as buildTenantSecurityRule, s as INCLUDE_COUNT_KEY, t as buildCollectionInputSchema, u as isManyToMany } from "./openapi-generator-IKwDAQGO.js";
10
+ import { a as softDeleteFieldOf, c as JUNCTION_PIVOT_KEY, i as restoresSoftDeletedRow, l as hasForeignKeyOnTarget, n as buildCollectionSchema, o as buildTenantSecurityRule, s as INCLUDE_COUNT_KEY, t as buildCollectionInputSchema, u as isManyToMany } from "./openapi-generator-BjHr6upl.js";
11
11
  import { n as isAnonymousUid, r as policy } from "./policy-PdzX-Opd.js";
12
12
  import { n as isSchemaEditingAdmin, t as isSQLAdmin } from "./backend-DTAOsLQc.js";
13
- import { a as parseEnvBoolean, i as validateCronExpression, t as CronScheduler } from "./cron-scheduler-D1DT0nw5.js";
13
+ import { a as parseEnvBoolean, i as validateCronExpression, t as CronScheduler } from "./cron-scheduler-B04ACoUE.js";
14
14
  import { n as isValidAppHost, r as reservedPrefixFor, t as SCHEMA_VERSION_HEADER } from "./project_manifest-R6y_KHms.js";
15
- import { n as createContractRoutes, r as computeSchemaVersion } from "./contract-routes-DiFwiLg2.js";
16
- import { $ as optionalAuth, $t as raw, A as createAuthLimiters, At as setDefaultAccessControl, B as registerDevEmailSink, Bt as resolveEmailLinkBase, C as createPersonalKeyRoutes, Ct as configureAccess, D as configureUserPicking, Dt as processAccessControl, E as createBuiltinAuthAdapter, Et as keyOwnerResolverFor, F as MemoryRateLimitStore, Ft as supportsRlsScoping, G as validatePasswordStrength, Gt as getMagicLinkTemplate, H as createEmailService, Ht as getEmailChangeTemplate, I as activeDevEmailSink, It as isAccessTokenRevoked, J as createAdapterAuthMiddleware, Jt as getWelcomeEmailTemplate, K as verifyPassword, Kt as getPasswordResetTemplate, L as clearActiveDevEmailSink, Lt as completeUserCreation, M as createDataRateLimiter, Mt as safeCompare, N as createRateLimiter, Nt as SERVICE_IDENTITY, O as isBootstrapWindowOpen, Ot as requireScope, P as setSharedRateLimitStore, Pt as scopeDataDriver, Q as fileTokenAuth, Qt as html, R as createDevEmailSink, Rt as generateSecurePassword, S as createApiKeyRoutes, St as callerScopes, T as createJwksRoutes, Tt as hasScope, U as resolveAuthHooks, Ut as getEmailOtpTemplate, V as SMTPEmailService, Vt as getEmailChangeNoticeTemplate, W as hashPassword, Wt as getEmailVerificationTemplate, X as createRequireAuth, Xt as RawHtml, Y as createAuthMiddleware, Yt as resolveEmailBranding, Z as extractUserFromToken, Zt as escapeHtml, _ as oauthCodeFlowSchema, _t as isApiKeyToken, a as createBitbucketProvider, an as storageObjectPath, at as userFromToken, b as resolveRateLimitStoreKind, bt as accessModelFromCollections, c as createTwitterProvider, ct as isPublicObjectGrant, d as createMicrosoftProvider, dt as storageRequestWildcard, en as httpMethodToOperation, et as publicObjectAuth, f as tryVerifyOidcIdToken, ft as decideImpersonation, g as createGoogleProvider, gt as createTusScopeGuard, h as createLinkedinProvider, ht as createFunctionScopeGuard, i as createSlackProvider, in as isPublicStoragePath, it as requireAuth, j as createDataRateLimitCheck, jt as extractBearerToken, k as DEFAULT_FUNCTIONS_ANONYMOUS_LIMIT, kt as requireScopeByMethod, l as createFacebookProvider, lt as requestedStorageObject, m as createGitHubProvider, mt as createApiKeyPreAuth, n as createCustomAuthAdapter, nn as PUBLIC_STORAGE_PREFIX, nt as refuse$1, o as createGitLabProvider, ot as PUBLIC_OBJECT_PRINCIPAL, p as verifyOidcIdToken, pt as refuseUnhonouredImpersonation, q as adapterRefusal, qt as getUserInvitationTemplate, r as createSpotifyProvider, rn as isPublicStorageKey, rt as requireAdmin, s as createDiscordProvider, st as isDownloadTokenGrant, tn as securityRuleToConditions, tt as queryTokenAuth, u as createAppleProvider, ut as storageObjectOfPath, v as pkceTokenParams, vt as resolveApiKey, w as createApiKeyStore, wt as getAccessModel, x as createSqlRateLimitStore, xt as accessOf, y as providerVerifiedEmail, yt as validateApiKey, z as extractLinks, zt as assertEmailLinkBases } from "./auth-xk37vrHO.js";
17
- import { a as redactSensitiveText, i as rawQueryLoggingEnabled, l as hostEnv, n as describeCauseChain, o as setLogLevel, r as logger, s as setLogOutput } from "./logger-Bt32sHFw.js";
18
- import { a as apiHiddenFieldNames, c as fieldKeyForColumn, d as isJunctionBackedRelation, g as toWireKey, h as resolveRelation, l as findRelation, m as resolveCollectionRelations, n as HARD_DELETE_QUERY_PARAM, p as relationDeclaringProperty, r as parseHardDelete, s as restrictedFieldNames, u as getTableName } from "./soft-delete-params-uHxJoETb.js";
15
+ import { n as createContractRoutes, r as computeSchemaVersion } from "./contract-routes-CsFXW7j4.js";
16
+ import { $ as optionalAuth, $t as raw, A as createAuthLimiters, At as setDefaultAccessControl, B as registerDevEmailSink, Bt as resolveEmailLinkBase, C as createPersonalKeyRoutes, Ct as configureAccess, D as configureUserPicking, Dt as processAccessControl, E as createBuiltinAuthAdapter, Et as keyOwnerResolverFor, F as MemoryRateLimitStore, Ft as supportsRlsScoping, G as validatePasswordStrength, Gt as getMagicLinkTemplate, H as createEmailService, Ht as getEmailChangeTemplate, I as activeDevEmailSink, It as isAccessTokenRevoked, J as createAdapterAuthMiddleware, Jt as getWelcomeEmailTemplate, K as verifyPassword, Kt as getPasswordResetTemplate, L as clearActiveDevEmailSink, Lt as completeUserCreation, M as createDataRateLimiter, Mt as safeCompare, N as createRateLimiter, Nt as SERVICE_IDENTITY, O as isBootstrapWindowOpen, Ot as requireScope, P as setSharedRateLimitStore, Pt as scopeDataDriver, Q as fileTokenAuth, Qt as html, R as createDevEmailSink, Rt as generateSecurePassword, S as createApiKeyRoutes, St as callerScopes, T as createJwksRoutes, Tt as hasScope, U as resolveAuthHooks, Ut as getEmailOtpTemplate, V as SMTPEmailService, Vt as getEmailChangeNoticeTemplate, W as hashPassword, Wt as getEmailVerificationTemplate, X as createRequireAuth, Xt as RawHtml, Y as createAuthMiddleware, Yt as resolveEmailBranding, Z as extractUserFromToken, Zt as escapeHtml, _ as oauthCodeFlowSchema, _t as isApiKeyToken, a as createBitbucketProvider, an as storageObjectPath, at as userFromToken, b as resolveRateLimitStoreKind, bt as accessModelFromCollections, c as createTwitterProvider, ct as isPublicObjectGrant, d as createMicrosoftProvider, dt as storageRequestWildcard, en as httpMethodToOperation, et as publicObjectAuth, f as tryVerifyOidcIdToken, ft as decideImpersonation, g as createGoogleProvider, gt as createTusScopeGuard, h as createLinkedinProvider, ht as createFunctionScopeGuard, i as createSlackProvider, in as isPublicStoragePath, it as requireAuth, j as createDataRateLimitCheck, jt as extractBearerToken, k as DEFAULT_FUNCTIONS_ANONYMOUS_LIMIT, kt as requireScopeByMethod, l as createFacebookProvider, lt as requestedStorageObject, m as createGitHubProvider, mt as createApiKeyPreAuth, n as createCustomAuthAdapter, nn as PUBLIC_STORAGE_PREFIX, nt as refuse$1, o as createGitLabProvider, ot as PUBLIC_OBJECT_PRINCIPAL, p as verifyOidcIdToken, pt as refuseUnhonouredImpersonation, q as adapterRefusal, qt as getUserInvitationTemplate, r as createSpotifyProvider, rn as isPublicStorageKey, rt as requireAdmin, s as createDiscordProvider, st as isDownloadTokenGrant, tn as securityRuleToConditions, tt as queryTokenAuth, u as createAppleProvider, ut as storageObjectOfPath, v as pkceTokenParams, vt as resolveApiKey, w as createApiKeyStore, wt as getAccessModel, x as createSqlRateLimitStore, xt as accessOf, y as providerVerifiedEmail, yt as validateApiKey, z as extractLinks, zt as assertEmailLinkBases } from "./auth-Dkvo2tTJ.js";
17
+ import { a as logger, c as setLogLevel, d as hostEnv, i as logLevelEnabled, l as setLogOutput, o as rawQueryLoggingEnabled, r as describeCauseChain, s as redactSensitiveText, t as _setLogLineHook } from "./logger-CW8pmTS6.js";
18
+ import { a as apiHiddenFieldNames, c as fieldKeyForColumn, d as isJunctionBackedRelation, g as toWireKey, h as resolveRelation, l as findRelation, m as resolveCollectionRelations, n as HARD_DELETE_QUERY_PARAM, p as relationDeclaringProperty, r as parseHardDelete, s as restrictedFieldNames, u as getTableName } from "./soft-delete-params-BmlVwecC.js";
19
19
  import { c as createTransport, d as parseRebaseJson, h as unref, l as defaultCodeForStatus, m as withETag, o as createCollectionClient, p as etagOf, r as SDKQueryBuilder, u as invalidBulkBody } from "./offline-store-BhYXViha-BW5j1We_.js";
20
20
  import "./resumable-upload-threshold-CKv-Ry0l.js";
21
21
  import { n as revokeInternalTableSql } from "./internal-tables-L2t39RdT.js";
22
22
  import { i as warnDeprecated } from "./load-failure-L5hvfDoG.js";
23
- import { a as resolveListLimitParam, c as assertReadableFields, d as decodeCursor$1, f as reconcileCursorOrder, i as parseQueryOptions, l as requestViewer, n as parseAggregateSelect, o as assertNoHiddenAggregateNames, r as parseGroupBy, s as assertQueryFieldsReadable, t as orderByEntriesToTuples, u as cursorToStartAfter } from "./query-parser-BnhugNwZ.js";
24
- import { a as isDataException, i as errorHandler, n as DATABASE_UNAVAILABLE_MESSAGE, o as isDatabaseUnreachable, r as declaredErrorAnswer, s as schemaDriftRemedy, t as ApiError } from "./errors-CHSJYpoy.js";
25
- import { C as randomHex, E as sha256Hex, S as constantTimeEqual, T as sha256Bytes, b as normalizePemFromEnv, d as hasAsymmetricSigningKey, g as setDefaultJwtService, h as processJwtService, l as getJwks, m as jwtOf, n as configureJwt, r as createJwtService, y as verifyJwt } from "./jwt-YhciR93H.js";
23
+ import { a as resolveListLimitParam, c as assertReadableFields, d as decodeCursor$1, f as reconcileCursorOrder, i as parseQueryOptions, l as requestViewer, n as parseAggregateSelect, o as assertNoHiddenAggregateNames, r as parseGroupBy, s as assertQueryFieldsReadable, t as orderByEntriesToTuples, u as cursorToStartAfter } from "./query-parser-otFZqJsC.js";
24
+ import { a as isDataException, i as errorHandler, n as DATABASE_UNAVAILABLE_MESSAGE, o as isDatabaseUnreachable, r as declaredErrorAnswer, s as schemaDriftRemedy, t as ApiError } from "./errors-OMWkKSH-.js";
25
+ import { C as randomHex, E as sha256Hex, S as constantTimeEqual, T as sha256Bytes, b as normalizePemFromEnv, d as hasAsymmetricSigningKey, g as setDefaultJwtService, h as processJwtService, l as getJwks, m as jwtOf, n as configureJwt, r as createJwtService, y as verifyJwt } from "./jwt-D3oFauZI.js";
26
26
  import { a as canonicalStorageId, i as canonicalStorageBucket, n as InvalidStorageBucketError, o as canonicalStorageKey, r as InvalidStorageKeyError, t as InvalidListOptionsError } from "./keys-COzQ2aE8.js";
27
- import { t as logMiddleware } from "./logs-routes-BM2cDory.js";
28
- import { a as readStreamFully, c as DOCUMENT_MIME_TYPES, i as fileWebStream, l as IMAGE_MIME_TYPES, r as bytesView, s as DEFAULT_MAX_FILE_SIZE, t as validateUploadedFile } from "./upload-validation-pXcDy_EQ.js";
29
- import { t as S3StorageController } from "./S3StorageController-Kt-LJpf3.js";
30
- import { t as GCSStorageController } from "./GCSStorageController-CAKa_EFW.js";
31
- import { a as isDuplicateObjectRace, i as isConcurrentDdlRace, n as createDdlBootstrapper, t as CONCURRENT_DDL_SQLSTATES } from "./ddl-bootstrap-BNqi2NZe.js";
32
- import { t as createCronStore } from "./cron-store-DEpVstUU.js";
33
- import { i as loadFunctionsWithDiagnostics, n as loadFunctions, r as loadFunctionsFromDirectory } from "./function-loader-DbCSoNJj.js";
34
- import { a as getDriver, c as getUser, d as identityResolved, f as isAdmin, i as getApiKey, l as getUserId, m as requireDriver, o as getRequestId, p as isAuthenticated, r as requireRole, s as getRoles, t as createFunctionRoutes, u as hasRole } from "./function-routes-C0emkHtX.js";
35
- import "./proxy-BEKbsil6.js";
36
- import "./request-timeout-LiGpCCP1.js";
27
+ import { t as logMiddleware } from "./logs-routes-CBt7mMIL.js";
28
+ import { a as readStreamFully, c as DOCUMENT_MIME_TYPES, i as fileWebStream, l as IMAGE_MIME_TYPES, r as bytesView, s as DEFAULT_MAX_FILE_SIZE, t as validateUploadedFile } from "./upload-validation-Bgo626uY.js";
29
+ import { t as S3StorageController } from "./S3StorageController-D00EFa5P.js";
30
+ import { t as GCSStorageController } from "./GCSStorageController-CRd9bFrc.js";
31
+ import { a as isDuplicateObjectRace, i as isConcurrentDdlRace, n as createDdlBootstrapper, t as CONCURRENT_DDL_SQLSTATES } from "./ddl-bootstrap-Ck20GSIX.js";
32
+ import { t as createCronStore } from "./cron-store-DkyfuLOH.js";
33
+ import { i as loadFunctionsWithDiagnostics, n as loadFunctions, r as loadFunctionsFromDirectory } from "./function-loader-KKDqTcIr.js";
34
+ import { a as getDriver, c as getUser, d as identityResolved, f as isAdmin, i as getApiKey, l as getUserId, m as requireDriver, o as getRequestId, p as isAuthenticated, r as requireRole, s as getRoles, t as createFunctionRoutes, u as hasRole } from "./function-routes-RxrCHNuM.js";
35
+ import "./proxy-CH8Jsovg.js";
36
+ import "./request-timeout-59crS-9u.js";
37
37
  import { t as FunctionSelectionError } from "./selection-CRpqKUbt.js";
38
- import { a as loadCrons, i as loadCronJobsWithDiagnostics, n as declareCrons, r as loadCronJobsFromDirectory } from "./cron-loader-NHqRfKF0.js";
39
- import { t as createCronRoutes } from "./cron-routes-CPIhdasM.js";
40
- import { a as listBackupObjects, c as readBackupBytes, i as backupStorageFromEnv, n as createBackupLinkRoutes, o as parseBackupDestination, r as createBackupRoutes, s as parseBackupTimestamp } from "./backup-CpaJWXSc.js";
41
- import { a as defaultBackoff, i as createJobQueue, n as createJobStore, o as currentAmbientTransaction, r as PermanentJobError } from "./jobs-8G-_agld.js";
38
+ import { a as loadCrons, i as loadCronJobsWithDiagnostics, n as declareCrons, r as loadCronJobsFromDirectory } from "./cron-loader-Dj6lA4HQ.js";
39
+ import { t as createCronRoutes } from "./cron-routes-CwbLZguO.js";
40
+ import { a as listBackupObjects, c as readBackupBytes, i as backupStorageFromEnv, n as createBackupLinkRoutes, o as parseBackupDestination, r as createBackupRoutes, s as parseBackupTimestamp } from "./backup-B9m3cb3X.js";
41
+ import { a as defaultBackoff, i as createJobQueue, n as createJobStore, o as currentAmbientTransaction, r as PermanentJobError } from "./jobs-h1WRq3cp.js";
42
42
  import { t as METRIC_SERIES } from "./history-store-DPofGVDp.js";
43
43
  import { createHash, createSign, randomBytes } from "node:crypto";
44
44
  import * as path$2 from "node:path";
@@ -57,17 +57,17 @@ import { createHmac, randomUUID as randomUUID$1 } from "crypto";
57
57
  import { createMiddleware } from "hono/factory";
58
58
  import { z, z as z$1 } from "zod";
59
59
  import { routePath } from "hono/route";
60
+ import { AsyncLocalStorage } from "node:async_hooks";
61
+ import fs$1, { createWriteStream } from "node:fs";
60
62
  import { csrf } from "hono/csrf";
61
63
  import { COMPRESSIBLE_CONTENT_TYPE_REGEX, compress } from "hono/compress";
62
64
  import { pipeline } from "stream/promises";
63
- import fs$1, { createWriteStream } from "node:fs";
64
65
  import * as fs$2 from "node:fs/promises";
65
66
  import fsp from "node:fs/promises";
66
67
  import os from "node:os";
67
68
  import { mkdir, open, readFile, readdir, stat, unlink, writeFile } from "fs/promises";
68
69
  import { execFile } from "node:child_process";
69
70
  import { promisify as promisify$1 } from "node:util";
70
- import { AsyncLocalStorage } from "node:async_hooks";
71
71
  import { env, getRuntimeKey } from "hono/adapter";
72
72
  import { sql } from "drizzle-orm";
73
73
  import net from "net";
@@ -92,6 +92,41 @@ import { pipeline as pipeline$1 } from "node:stream/promises";
92
92
  */
93
93
  var DEFAULT_COMMIT_PATHS = { schemaFile: "backend/src/schema.generated.ts" };
94
94
  //#endregion
95
+ //#region ../types/src/types/content_hash.ts
96
+ /**
97
+ * Whether a static build's file name carries a content hash: the files the
98
+ * runtime serves `immutable` for a year, and the ones `rebase build` writes
99
+ * `.br` and `.gz` beside. One rule for both, so a file is never precompressed
100
+ * and revalidated, or cached for a year and compressed on every request.
101
+ *
102
+ * Two shapes count:
103
+ *
104
+ * - **Vite's own**, `[name]-[hash]`: the last eight characters of the name,
105
+ * after a `-`. They are base64url, an alphabet with `-` and `_` in it, so
106
+ * about one hash in eight holds a `-` (`index-C-WpENHZ.js`,
107
+ * `page10-D6Dq--fD.js`), and the last `-` is not where it starts.
108
+ * - **A longer one** with no `-` in it, eight characters or more after the last
109
+ * `-` (`[hash:12]`, another bundler's names).
110
+ *
111
+ * Either must hold a digit or a capital. That is the evidence of a hash: a
112
+ * hand-written `vendor-analytics.js` is not taken for one, and gets revalidated
113
+ * rather than pinned in every visitor's cache with no way to recall it.
114
+ */
115
+ function isContentHashedFileName(fileName) {
116
+ const dot = fileName.lastIndexOf(".");
117
+ if (dot <= 0) return false;
118
+ const stem = fileName.slice(0, dot);
119
+ if (stem.length > 9 && stem[stem.length - 9] === "-" && looksLikeHash(stem.slice(-8))) return true;
120
+ const dash = stem.lastIndexOf("-");
121
+ if (dash < 0) return false;
122
+ const suffix = stem.slice(dash + 1);
123
+ return suffix.length >= 8 && looksLikeHash(suffix);
124
+ }
125
+ /** Base64url throughout, with a digit or a capital somewhere in it. */
126
+ function looksLikeHash(text) {
127
+ return /^[A-Za-z0-9_-]+$/.test(text) && /[0-9A-Z]/.test(text);
128
+ }
129
+ //#endregion
95
130
  //#region ../types/src/controllers/storage-references.ts
96
131
  /**
97
132
  * The scheme of a **storage reference**: a stored object named inside text.
@@ -6582,6 +6617,37 @@ function realtimeLostError() {
6582
6617
  function realtimeDebugOn() {
6583
6618
  return Reflect.get(globalThis, "__REBASE_DEBUG_REALTIME__") === true;
6584
6619
  }
6620
+ /**
6621
+ * Connection chatter: the socket opened, signed in, closed, is redialling.
6622
+ *
6623
+ * `console.debug` where that is a debug level — a browser's console hides it
6624
+ * unless Verbose is asked for. Not in Node, where `console.debug` is
6625
+ * `console.log`: a script's `listen()` opened its stdout with "Connected to
6626
+ * PostgreSQL backend" and "WebSocket auto-authenticated", ahead of what the
6627
+ * script prints. There it is written only while realtime debugging is on.
6628
+ */
6629
+ function connectionDebug(...args) {
6630
+ if (!(typeof window !== "undefined" || typeof document !== "undefined") && !realtimeDebugOn()) return;
6631
+ console.debug(...args);
6632
+ }
6633
+ /**
6634
+ * The codes an `AUTHENTICATE` is refused with when the server will not honour
6635
+ * the session: the token does not verify or has expired, the session was
6636
+ * revoked or its account is gone (`SESSION_REVOKED`), the account is disabled
6637
+ * (`ACCOUNT_DISABLED`) — the codes the HTTP routes answer the same token with.
6638
+ * An answer, not a failure: asking again with the same token gets the same one.
6639
+ */
6640
+ var SESSION_REFUSAL_CODES = /* @__PURE__ */ new Set([
6641
+ "INVALID_TOKEN",
6642
+ "TOKEN_EXPIRED",
6643
+ "JWT_EXPIRED",
6644
+ "SESSION_REVOKED",
6645
+ "ACCOUNT_DISABLED",
6646
+ "SESSION_ENDED"
6647
+ ]);
6648
+ function isSessionRefusal(error) {
6649
+ return error instanceof RebaseApiError && typeof error.code === "string" && SESSION_REFUSAL_CODES.has(error.code);
6650
+ }
6585
6651
  /** The ceiling of the reconnect backoff. */
6586
6652
  var MAX_RECONNECT_DELAY_MS = 3e4;
6587
6653
  /**
@@ -6927,7 +6993,7 @@ var RebaseWebSocketClient = class {
6927
6993
  if (this.closedByCaller) return;
6928
6994
  if (this.connectionState !== "reconnecting" && this.connectionState !== "disconnected") return;
6929
6995
  if (this.ws) return;
6930
- console.debug(`${reason} — retrying the realtime connection`);
6996
+ connectionDebug(`${reason} — retrying the realtime connection`);
6931
6997
  if (this.reconnectTimeout) {
6932
6998
  clearTimeout(this.reconnectTimeout);
6933
6999
  this.reconnectTimeout = null;
@@ -6959,6 +7025,24 @@ var RebaseWebSocketClient = class {
6959
7025
  }
6960
7026
  onlineListener = null;
6961
7027
  visibilityListener = null;
7028
+ /** The session this socket's server refused, and why. Cleared when a socket opens. */
7029
+ refusedSession = null;
7030
+ /**
7031
+ * Whether `error` is the server refusing the session `token` signs in as —
7032
+ * revoked, its account gone or disabled, a token that does not verify. Said
7033
+ * once per socket and token, as a warning that names the code.
7034
+ */
7035
+ noteSessionRefused(token, error) {
7036
+ if (!isSessionRefusal(error)) return false;
7037
+ if (this.refusedSession?.token !== token) {
7038
+ this.refusedSession = {
7039
+ token,
7040
+ error
7041
+ };
7042
+ console.warn(`[Rebase] Realtime: the server refused this session (${error.code}: ${error.message}). The socket is connected but not signed in, and what needs an account is refused with that code.`);
7043
+ }
7044
+ return true;
7045
+ }
6962
7046
  /**
6963
7047
  * Authenticate the WebSocket connection
6964
7048
  */
@@ -7007,10 +7091,10 @@ var RebaseWebSocketClient = class {
7007
7091
  if (this.isConnected && !this.isAuthenticated && !this.authPromise) this.getAuthToken().then((token) => {
7008
7092
  if (!this.ws) return;
7009
7093
  if (token) this.authenticate(token).catch((e) => {
7010
- if (this.ws) console.debug("WebSocket auto-auth skipped:", e?.message || e);
7094
+ if (this.ws) connectionDebug("WebSocket auto-auth skipped:", e?.message || e);
7011
7095
  });
7012
7096
  }).catch((e) => {
7013
- if (this.ws) console.debug("WebSocket auto-auth skipped:", e?.message || e);
7097
+ if (this.ws) connectionDebug("WebSocket auto-auth skipped:", e?.message || e);
7014
7098
  });
7015
7099
  }
7016
7100
  /**
@@ -7026,6 +7110,7 @@ var RebaseWebSocketClient = class {
7026
7110
  if (permanent) this.removeNetworkListeners();
7027
7111
  this.isAuthenticated = false;
7028
7112
  this.authPromise = null;
7113
+ this.refusedSession = null;
7029
7114
  if (this.reconnectTimeout) {
7030
7115
  clearTimeout(this.reconnectTimeout);
7031
7116
  this.reconnectTimeout = null;
@@ -7060,7 +7145,7 @@ var RebaseWebSocketClient = class {
7060
7145
  const socket = new this.WebSocketConstructor(withOptIns(this.websocketUrl));
7061
7146
  this.ws = socket;
7062
7147
  this.ws.onopen = async () => {
7063
- console.debug("Connected to PostgreSQL backend");
7148
+ connectionDebug("Connected to PostgreSQL backend");
7064
7149
  const wasReconnect = this.hadConnection;
7065
7150
  this.hadConnection = true;
7066
7151
  this.isConnected = true;
@@ -7069,14 +7154,18 @@ var RebaseWebSocketClient = class {
7069
7154
  this.outageReported = false;
7070
7155
  this.gaveUp = false;
7071
7156
  this.setState("connected");
7072
- if (this.getAuthToken && !this.isAuthenticated) try {
7073
- const token = await this.getAuthToken();
7074
- if (token) {
7075
- await this.authenticate(token);
7076
- console.debug("WebSocket auto-authenticated");
7157
+ this.refusedSession = null;
7158
+ if (this.getAuthToken && !this.isAuthenticated) {
7159
+ let token = null;
7160
+ try {
7161
+ token = await this.getAuthToken();
7162
+ if (token) {
7163
+ await this.authenticate(token);
7164
+ connectionDebug("WebSocket auto-authenticated");
7165
+ }
7166
+ } catch (error) {
7167
+ if (!(token && this.noteSessionRefused(token, error))) connectionDebug("WebSocket connected, not signed in yet:", error?.message || error);
7077
7168
  }
7078
- } catch (error) {
7079
- console.debug("WebSocket connected without auth:", error?.message || error);
7080
7169
  }
7081
7170
  this.emit(wasReconnect ? "reconnect" : "connect");
7082
7171
  this.processMessageQueue(wasReconnect);
@@ -7093,7 +7182,7 @@ var RebaseWebSocketClient = class {
7093
7182
  }
7094
7183
  };
7095
7184
  const onClosed = () => {
7096
- console.debug("Disconnected from PostgreSQL backend");
7185
+ connectionDebug("Disconnected from PostgreSQL backend");
7097
7186
  this.stopLivenessWatch();
7098
7187
  this.forgetServerOptIns();
7099
7188
  if (this.ws === socket) this.ws = null;
@@ -7223,7 +7312,7 @@ var RebaseWebSocketClient = class {
7223
7312
  this.reconnectAttempts = Math.min(this.reconnectAttempts + 1, 30);
7224
7313
  const ceiling = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), MAX_RECONNECT_DELAY_MS);
7225
7314
  const delay = MIN_RECONNECT_DELAY_MS + Math.floor(Math.random() * (ceiling - MIN_RECONNECT_DELAY_MS));
7226
- console.debug(`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts})`);
7315
+ connectionDebug(`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts})`);
7227
7316
  if (this.reconnectTimeout) clearTimeout(this.reconnectTimeout);
7228
7317
  this.reconnectTimeout = setTimeout(() => {
7229
7318
  this.reconnectTimeout = null;
@@ -7492,31 +7581,36 @@ var RebaseWebSocketClient = class {
7492
7581
  }
7493
7582
  async runAuthentication(retryCount) {
7494
7583
  let lastError = null;
7495
- for (let attempt = 0; attempt < retryCount; attempt++) try {
7496
- const token = await this.getAuthToken();
7497
- if (!token) throw new Error("user not logged in");
7498
- await this.authenticate(token);
7499
- console.debug("WebSocket authenticated on demand");
7500
- return;
7501
- } catch (error) {
7502
- lastError = error;
7503
- const errMsg = error instanceof Error ? error.message : String(error);
7504
- if (errMsg.includes("not logged in") || errMsg.includes("Session expired")) {
7505
- console.warn("WebSocket auth failed: user not logged in");
7506
- throw error;
7507
- }
7508
- if (errMsg.includes("still loading")) {
7584
+ for (let attempt = 0; attempt < retryCount; attempt++) {
7585
+ let token = null;
7586
+ try {
7587
+ token = await this.getAuthToken();
7588
+ if (!token) throw new Error("user not logged in");
7589
+ if (this.refusedSession?.token === token) throw this.refusedSession.error;
7590
+ await this.authenticate(token);
7591
+ connectionDebug("WebSocket authenticated on demand");
7592
+ return;
7593
+ } catch (error) {
7594
+ lastError = error;
7595
+ if (token && this.noteSessionRefused(token, error)) throw error;
7596
+ const errMsg = error instanceof Error ? error.message : String(error);
7597
+ if (errMsg.includes("not logged in") || errMsg.includes("Session expired")) {
7598
+ console.warn("WebSocket auth failed: user not logged in");
7599
+ throw error;
7600
+ }
7601
+ if (errMsg.includes("still loading")) {
7602
+ if (attempt < retryCount - 1) {
7603
+ const delay = Math.min(500 * (attempt + 1), 2e3);
7604
+ await new Promise((resolve) => setTimeout(resolve, delay));
7605
+ continue;
7606
+ }
7607
+ }
7509
7608
  if (attempt < retryCount - 1) {
7510
- const delay = Math.min(500 * (attempt + 1), 2e3);
7609
+ const delay = Math.min(1e3 * (attempt + 1), 3e3);
7610
+ connectionDebug(`WebSocket auth attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
7511
7611
  await new Promise((resolve) => setTimeout(resolve, delay));
7512
- continue;
7513
7612
  }
7514
7613
  }
7515
- if (attempt < retryCount - 1) {
7516
- const delay = Math.min(1e3 * (attempt + 1), 3e3);
7517
- console.debug(`WebSocket auth attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
7518
- await new Promise((resolve) => setTimeout(resolve, delay));
7519
- }
7520
7614
  }
7521
7615
  console.warn("WebSocket on-demand auth failed after retries:", lastError);
7522
7616
  throw lastError;
@@ -7528,7 +7622,7 @@ var RebaseWebSocketClient = class {
7528
7622
  const token = await this.getAuthToken();
7529
7623
  if (!token) throw new Error("user not logged in");
7530
7624
  await this.authenticate(token);
7531
- console.debug("WebSocket reauthenticated successfully");
7625
+ connectionDebug("WebSocket reauthenticated successfully");
7532
7626
  } catch (error) {
7533
7627
  console.error("WebSocket reauthentication failed:", error);
7534
7628
  throw error;
@@ -7602,6 +7696,10 @@ var RebaseWebSocketClient = class {
7602
7696
  await this.ensureAuthenticated();
7603
7697
  } catch (error) {
7604
7698
  if (!(isSubscriptionFrame && isNotSignedIn(error))) {
7699
+ if (error instanceof RebaseApiError) {
7700
+ reject(error);
7701
+ return;
7702
+ }
7605
7703
  reject(new RebaseApiError(error instanceof Error ? error.message : "Authentication required"));
7606
7704
  return;
7607
7705
  }
@@ -8214,7 +8312,7 @@ var RebaseWebSocketClient = class {
8214
8312
  * we need to re-register everything to resume receiving updates.
8215
8313
  */
8216
8314
  resubscribeAll() {
8217
- console.debug(`[WS] Re-subscribing: ${this.collectionSubscriptions.size} collection(s), ${this.singleSubscriptions.size} row(ies)`);
8315
+ connectionDebug(`[WS] Re-subscribing: ${this.collectionSubscriptions.size} collection(s), ${this.singleSubscriptions.size} row(ies)`);
8218
8316
  for (const [key, sub] of this.collectionSubscriptions.entries()) {
8219
8317
  if (sub.subscribeInFlight) continue;
8220
8318
  const oldBackendId = sub.backendSubscriptionId;
@@ -14767,6 +14865,99 @@ function recordMountPath() {
14767
14865
  };
14768
14866
  }
14769
14867
  //#endregion
14868
+ //#region src/backend/boot-announcements.ts
14869
+ /**
14870
+ * What a backend says while it is built, held until it serves.
14871
+ *
14872
+ * `createRebaseBackend` builds everything and mounts nothing, and its build
14873
+ * logs the boot: the connection's posture, change capture, the per-boot key,
14874
+ * which process owns the schema. Those lines are the server's to print — a
14875
+ * server mounts the backend a moment later and they are its boot log. A
14876
+ * script builds a backend to call `backend.rebase` or `backend.fetch` and
14877
+ * never mounts it, and there the same lines were the first thing on its
14878
+ * stdout, ahead of the JSON it exists to print (`tsx script.ts > out.json`).
14879
+ *
14880
+ * So the info lines said inside the build — by this package or a driver, in
14881
+ * the build's async context — are held, and written when the backend first
14882
+ * serves: `mount()`, a route group, `attachRealtime()` or `start()`. They come
14883
+ * out in the order they were said, with the time they were said, before
14884
+ * anything the mount says itself; a server's boot reads as it did. A backend
14885
+ * that never serves never writes them, and what it logs at info afterwards —
14886
+ * the request log of `backend.fetch`, its shutdown — is not written either:
14887
+ * that is a library talking to itself.
14888
+ *
14889
+ * Not held, ever:
14890
+ * - warnings and errors, which say something is wrong and are written at once;
14891
+ * - anything at `LOG_LEVEL=debug`, which asks for every line as it happens;
14892
+ * - a line from anywhere outside the build — the application's own, another
14893
+ * backend's, a request a served backend answers.
14894
+ *
14895
+ * A build that throws writes what it held: the lines before a failure are the
14896
+ * failure's context.
14897
+ *
14898
+ * Host-side: the build's context is an `AsyncLocalStorage`, so this module is
14899
+ * never reachable from `@rebasepro/server/functions`. The logger asks it
14900
+ * through `LogLineHook`, which needs nothing from Node.
14901
+ *
14902
+ * @module
14903
+ */
14904
+ /**
14905
+ * The most lines one build holds. A build says a few dozen; past this they are
14906
+ * written as they come rather than kept, so a pathological build cannot grow
14907
+ * the buffer without bound.
14908
+ */
14909
+ var MAX_HELD_LINES = 500;
14910
+ var context = new AsyncLocalStorage();
14911
+ var installed = false;
14912
+ /**
14913
+ * Point the logger at the build contexts. Once per copy of this module; a hook
14914
+ * another copy installed first is asked about every line that is not ours.
14915
+ */
14916
+ function installHook() {
14917
+ if (installed) return;
14918
+ installed = true;
14919
+ const previous = _setLogLineHook((level, write) => {
14920
+ const own = context.getStore();
14921
+ if (own) return own.offer(level, write);
14922
+ return previous ? previous(level, write) : false;
14923
+ });
14924
+ }
14925
+ var Announcements = class {
14926
+ phase = "building";
14927
+ held = [];
14928
+ run(fn) {
14929
+ installHook();
14930
+ return context.run(this, fn);
14931
+ }
14932
+ /** Whether this backend takes the line: holds it, or does not write it. */
14933
+ offer(level, write) {
14934
+ if (this.phase === "serving") return false;
14935
+ if (level === "warn" || level === "error") return false;
14936
+ if (logLevelEnabled("debug")) return false;
14937
+ if (this.phase === "built") return true;
14938
+ if (this.held.length >= MAX_HELD_LINES) return false;
14939
+ this.held.push(write);
14940
+ return true;
14941
+ }
14942
+ built() {
14943
+ if (this.phase === "building") this.phase = "built";
14944
+ }
14945
+ failed() {
14946
+ this.serve();
14947
+ }
14948
+ serve() {
14949
+ if (this.phase === "serving") return;
14950
+ this.phase = "serving";
14951
+ const held = this.held;
14952
+ this.held = [];
14953
+ for (const write of held) write();
14954
+ }
14955
+ };
14956
+ /** Announcements for one backend about to be built. */
14957
+ function createBootAnnouncements() {
14958
+ return new Announcements();
14959
+ }
14960
+ //#endregion
14770
14961
  //#region src/utils/byte-size.ts
14771
14962
  /**
14772
14963
  * A byte count as an operator reads it in an error message: `10MB`, `1.5MB`,
@@ -14790,6 +14981,93 @@ function trim(value) {
14790
14981
  return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
14791
14982
  }
14792
14983
  //#endregion
14984
+ //#region src/utils/dev-dependency-command.ts
14985
+ /**
14986
+ * "Install this optional dependency", spelled so it works where the reader
14987
+ * types it: from the project root, in the project's own package manager.
14988
+ *
14989
+ * A scaffolded project is a workspace whose backend is the member `backend`,
14990
+ * and the backend runs there. A bare `pnpm add -D <pkg>` typed at the root —
14991
+ * where a reader is, reading `rebase dev`'s output or the admin panel —
14992
+ * refuses with `ERR_PNPM_ADDING_TO_ROOT`, so the command names the member.
14993
+ *
14994
+ * The same rules as the driver's `describeDevAddCommand`
14995
+ * (`@rebasepro/server-postgres`, `cli-helpers.ts`), which the CLI prints for
14996
+ * atlas. That package depends on this one, not the other way round, so the
14997
+ * server keeps its own copy. Host-side: it reads the file system.
14998
+ *
14999
+ * @module
15000
+ */
15001
+ var LOCKFILES = [
15002
+ ["pnpm-lock.yaml", "pnpm"],
15003
+ ["bun.lock", "bun"],
15004
+ ["bun.lockb", "bun"],
15005
+ ["yarn.lock", "yarn"],
15006
+ ["package-lock.json", "npm"]
15007
+ ];
15008
+ /** Which package manager built the project, read off the nearest lockfile at or above `startDir`. pnpm when none. */
15009
+ function projectPackageManager(startDir) {
15010
+ for (let dir = path.resolve(startDir);; dir = path.dirname(dir)) {
15011
+ for (const [file, manager] of LOCKFILES) if (fs$1.existsSync(path.join(dir, file))) return manager;
15012
+ if (path.dirname(dir) === dir) return "pnpm";
15013
+ }
15014
+ }
15015
+ function isWorkspaceRoot(dir) {
15016
+ if (fs$1.existsSync(path.join(dir, "pnpm-workspace.yaml"))) return true;
15017
+ try {
15018
+ const manifest = JSON.parse(fs$1.readFileSync(path.join(dir, "package.json"), "utf8"));
15019
+ return typeof manifest === "object" && manifest !== null && "workspaces" in manifest;
15020
+ } catch {
15021
+ return false;
15022
+ }
15023
+ }
15024
+ /**
15025
+ * The workspace member `startDir` is in, relative to the workspace root —
15026
+ * `backend` in a scaffold — or null at that root and outside any workspace.
15027
+ */
15028
+ function workspaceMember(startDir) {
15029
+ let member = path.resolve(startDir);
15030
+ while (!fs$1.existsSync(path.join(member, "package.json"))) {
15031
+ const parent = path.dirname(member);
15032
+ if (parent === member) return null;
15033
+ member = parent;
15034
+ }
15035
+ for (let dir = path.dirname(member);; dir = path.dirname(dir)) {
15036
+ if (isWorkspaceRoot(dir)) return path.relative(dir, member).split(path.sep).join("/");
15037
+ if (path.dirname(dir) === dir) return null;
15038
+ }
15039
+ }
15040
+ /** A path as one shell word: as it is when it needs no quoting, single-quoted otherwise. */
15041
+ var shellWord = (value) => /^[\w./-]+$/.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
15042
+ /**
15043
+ * `add -D <spec>` for the package the backend in `startDir` runs from, typed
15044
+ * at the project root: `pnpm --dir backend add -D ts-morph@28.0.0` in a
15045
+ * scaffold, `pnpm add -D ts-morph@28.0.0` where the backend is the project.
15046
+ */
15047
+ function devDependencyInstallCommand(spec, startDir = process.cwd()) {
15048
+ const member = workspaceMember(startDir);
15049
+ const dir = member === null ? null : shellWord(member);
15050
+ switch (projectPackageManager(startDir)) {
15051
+ case "npm": return dir ? `npm install -D ${spec} -w ${dir}` : `npm install -D ${spec}`;
15052
+ case "yarn": return dir ? `yarn --cwd ${dir} add -D ${spec}` : `yarn add -D ${spec}`;
15053
+ case "bun": return dir ? `bun add -d ${spec} --cwd ${dir}` : `bun add -d ${spec}`;
15054
+ default: return dir ? `pnpm --dir ${dir} add -D ${spec}` : `pnpm add -D ${spec}`;
15055
+ }
15056
+ }
15057
+ /** The `ts-morph` the schema editor is built against: the server's optional peer. */
15058
+ var TS_MORPH = "ts-morph@28.0.0";
15059
+ /**
15060
+ * How to turn on what needs `ts-morph`, as the end of a sentence that has
15061
+ * said it is missing: "Run `pnpm --dir backend add -D ts-morph@28.0.0` from
15062
+ * the project root, then restart the backend, to enable it."
15063
+ *
15064
+ * `pnpm`, never `npm`, in a pnpm project: running npm inside one rewrites
15065
+ * node_modules into a hoisted layout pnpm then disagrees with (docs/bug-classes.md §5).
15066
+ */
15067
+ function tsMorphInstallSentence(startDir = process.cwd()) {
15068
+ return `Run \`${devDependencyInstallCommand(TS_MORPH, startDir)}\`${workspaceMember(startDir) === null ? "" : " from the project root"}, then restart the backend, to enable it.`;
15069
+ }
15070
+ //#endregion
14793
15071
  //#region src/utils/compression.ts
14794
15072
  /**
14795
15073
  * Below this many bytes a body is sent as it is. Hono's own default: gzip's
@@ -18407,11 +18685,11 @@ async function createStorageController(config) {
18407
18685
  switch (config.type) {
18408
18686
  case "local": return new LocalStorageController(config);
18409
18687
  case "s3": {
18410
- const { S3StorageController } = await import("./S3StorageController-Kt-LJpf3.js").then((n) => n.n);
18688
+ const { S3StorageController } = await import("./S3StorageController-D00EFa5P.js").then((n) => n.n);
18411
18689
  return new S3StorageController(config);
18412
18690
  }
18413
18691
  case "gcs": {
18414
- const { GCSStorageController } = await import("./GCSStorageController-CAKa_EFW.js").then((n) => n.n);
18692
+ const { GCSStorageController } = await import("./GCSStorageController-CRd9bFrc.js").then((n) => n.n);
18415
18693
  return new GCSStorageController(config);
18416
18694
  }
18417
18695
  default: throw new Error(`Unknown storage type: ${config.type}. Built-in types: local, s3, gcs. For other providers, implement the StorageController interface directly.`);
@@ -18650,7 +18928,7 @@ function noCollectionsCause(unprotected) {
18650
18928
  * routes wherever and as often as the application asks.
18651
18929
  */
18652
18930
  async function loadOpenApiGenerator() {
18653
- const { generateOpenApiSpec } = await import("./openapi-generator-IKwDAQGO.js").then((n) => n.r);
18931
+ const { generateOpenApiSpec } = await import("./openapi-generator-BjHr6upl.js").then((n) => n.r);
18654
18932
  return generateOpenApiSpec;
18655
18933
  }
18656
18934
  /**
@@ -19743,7 +20021,7 @@ function createLiveSchemaRoutes(config) {
19743
20021
  enabled: false,
19744
20022
  canPlan: true,
19745
20023
  code: off?.code ?? "SCHEMA_EDITOR_MISSING_DEPENDENCY",
19746
- reason: off?.message ?? "Rewriting collection source needs `ts-morph`, which is not installed on this server. Run `pnpm add -D ts-morph@28.0.0` to enable it."
20024
+ reason: off?.message ?? `Rewriting collection source needs \`ts-morph\`, which is not installed on this server. ${tsMorphInstallSentence()}`
19747
20025
  });
19748
20026
  }
19749
20027
  if (!repository) return c.json({
@@ -25207,8 +25485,14 @@ function assertNoSharedLocation(resolved, env, options) {
25207
25485
  if (other) {
25208
25486
  const [moved, kept] = key === "(default)" ? [other.key, key] : [key, other.key];
25209
25487
  const movedLocation = moved === key ? location : other.location;
25488
+ const keptLocation = moved === key ? other.location : location;
25210
25489
  const variable = `${movedLocation.variable}${envSuffixForKey(moved, DEFAULT_STORAGE_SOURCE_KEY)}`;
25211
- throw new BundleError(`Storage sources "${moved}" and "${kept}" would both store files in ${movedLocation.describe}, so each would list, serve and delete the other's files.`, `Give "${moved}" a ${movedLocation.local !== void 0 ? "directory" : "bucket"} of its own with ${variable}.` + (kept === "(default)" ? ` If uploads that name no \`storageSource\` belong in "${moved}", mark it instead — bucket("${moved}", { default: true }).` : ""));
25490
+ const alias = kept === "(default)" ? ` If uploads that name no \`storageSource\` belong in "${moved}", mark it instead — bucket("${moved}", { default: true }).` : "";
25491
+ if (movedLocation.local !== void 0 && keptLocation.local !== void 0 && movedLocation.local !== keptLocation.local) {
25492
+ const inside = movedLocation.local.startsWith(keptLocation.local + path$1.sep);
25493
+ throw new BundleError(`Storage source "${moved}" would store its files in ${movedLocation.local}, which ${inside ? "is inside" : "holds"} ${keptLocation.local}, where "${kept}" stores its own, so each would list, serve and delete the other's files.`, `Give "${moved}" a directory ${inside ? "outside" : "apart from"} ${keptLocation.local} with ${variable} (it is ${movedLocation.local} now).${alias}`);
25494
+ }
25495
+ throw new BundleError(`Storage sources "${moved}" and "${kept}" would both store files in ${movedLocation.describe}, so each would list, serve and delete the other's files.`, `Give "${moved}" a ${movedLocation.local !== void 0 ? "directory" : "bucket"} of its own with ${variable}.${alias}`);
25212
25496
  }
25213
25497
  seen.push({
25214
25498
  key,
@@ -25948,8 +26232,23 @@ function resolveProvision(provision) {
25948
26232
  * which {@link setDefaultBackend} sets and `initializeRebaseBackend` sets for
25949
26233
  * its backend. The one process-wide setting it touches is the log level, and
25950
26234
  * only when `logging.level` is given: the logger is the process's.
26235
+ *
26236
+ * What the build logs at info is written when the backend first serves — see
26237
+ * `boot-announcements.ts` — so a script that builds one and never mounts it
26238
+ * prints nothing of the boot's on its stdout.
25951
26239
  */
25952
26240
  async function createRebaseBackend(options) {
26241
+ const announcements = createBootAnnouncements();
26242
+ try {
26243
+ const backend = await announcements.run(() => buildRebaseBackend(options, announcements));
26244
+ announcements.built();
26245
+ return backend;
26246
+ } catch (error) {
26247
+ announcements.failed();
26248
+ throw error;
26249
+ }
26250
+ }
26251
+ async function buildRebaseBackend(options, announcements) {
25953
26252
  const config = options;
25954
26253
  const provision = resolveProvision(config.provision);
25955
26254
  if (config.logging?.level !== void 0) setLogLevel(config.logging.level);
@@ -26229,7 +26528,7 @@ async function createRebaseBackend(options) {
26229
26528
  const platformTokenPreAuth = createPlatformTokenPreAuth(await platformTokensFromEnv(process.env));
26230
26529
  let backupLinkRouter;
26231
26530
  if (surfaces.admin) {
26232
- const { createBackupLinkRoutes, backupDestinationFromEnv } = await import("./backup-CpaJWXSc.js").then((n) => n.t);
26531
+ const { createBackupLinkRoutes, backupDestinationFromEnv } = await import("./backup-B9m3cb3X.js").then((n) => n.t);
26233
26532
  backupLinkRouter = createBackupLinkRoutes({ getDestination: () => backupDestinationFromEnv(process.env) });
26234
26533
  }
26235
26534
  const apiKeyRouter = apiKeyStore && surfaces.admin ? createApiKeyRoutes({
@@ -26374,7 +26673,7 @@ async function createRebaseBackend(options) {
26374
26673
  ]) {
26375
26674
  const providerConfig = safeAuthConfig[key];
26376
26675
  if (providerConfig && requiredFields.every((f) => Boolean(providerConfig[f]))) {
26377
- const createFn = (await import("./auth-xk37vrHO.js").then((n) => n.t))[factory];
26676
+ const createFn = (await import("./auth-Dkvo2tTJ.js").then((n) => n.t))[factory];
26378
26677
  oauthProviders.push(createFn(providerConfig));
26379
26678
  }
26380
26679
  }
@@ -26486,6 +26785,21 @@ async function createRebaseBackend(options) {
26486
26785
  }));
26487
26786
  if (announce) logger.info(`MCP endpoint mounted at ${publicUrl}${mcpPath} (OAuth at ${oauthBasePath})`);
26488
26787
  };
26788
+ const mountWarnings = [];
26789
+ const warnOnMount = (group, message) => {
26790
+ mountWarnings.push({
26791
+ group,
26792
+ message,
26793
+ said: false
26794
+ });
26795
+ };
26796
+ const sayMountWarnings = (groups) => {
26797
+ for (const warning of mountWarnings) {
26798
+ if (warning.said || !groups.has(warning.group)) continue;
26799
+ warning.said = true;
26800
+ logger.warn(warning.message);
26801
+ }
26802
+ };
26489
26803
  const adminSurfacesGated = !!authAdapter && (isAuthAdapter(config.auth) || !!config.auth.jwtSecret);
26490
26804
  const adminGate = (scopes) => [
26491
26805
  provideServices,
@@ -26497,9 +26811,9 @@ async function createRebaseBackend(options) {
26497
26811
  }),
26498
26812
  typeof scopes === "string" ? requireScope(scopes) : requireScopeByMethod(scopes)
26499
26813
  ];
26500
- const applyAdminGate = (router, surface, scopes) => {
26814
+ const applyAdminGate = (router, surface, scopes, mountedWith = "admin") => {
26501
26815
  if (!adminSurfacesGated) {
26502
- logger.warn(`${surface} routes are mounted but DISABLED: no authentication is configured (no auth adapter and no auth.jwtSecret), so there is no way to tell an admin from an anonymous caller. They answer 501 until auth is configured.`);
26816
+ warnOnMount(mountedWith, `${surface} routes are mounted but DISABLED: no authentication is configured (no auth adapter and no auth.jwtSecret), so there is no way to tell an admin from an anonymous caller. They answer 501 until auth is configured.`);
26503
26817
  router.use("/*", async (c) => c.json({ error: {
26504
26818
  code: "ADMIN_SURFACE_UNAVAILABLE",
26505
26819
  message: `${surface} is admin-only, and this server has no authentication configured to identify an admin with. Set auth.jwtSecret (or pass an AuthAdapter) to enable it.`
@@ -26550,18 +26864,31 @@ async function createRebaseBackend(options) {
26550
26864
  let schemaEditorRoutes;
26551
26865
  let schemaSourceEditor;
26552
26866
  if (!schemaEditorOff && config.collectionsDir) try {
26553
- const editorModule = await import("./schema-editor-routes-zjVKreVQ.js");
26867
+ const editorModule = await import("./schema-editor-routes-Bsut0bCC.js");
26554
26868
  schemaSourceEditor = editorModule.createSchemaSourceEditor(config.collectionsDir);
26555
26869
  schemaEditorRoutes = editorModule.createSchemaEditorRoutes(schemaSourceEditor);
26556
26870
  } catch (err) {
26557
26871
  if (err?.code === "ERR_MODULE_NOT_FOUND") {
26558
26872
  schemaEditorOff = {
26559
26873
  code: "SCHEMA_EDITOR_MISSING_DEPENDENCY",
26560
- message: "The schema editor needs `ts-morph`, which is not installed on this server. Run `pnpm add -D ts-morph@28.0.0` to enable it."
26874
+ message: "The schema editor needs `ts-morph`, which is not installed on this server. " + tsMorphInstallSentence()
26561
26875
  };
26562
- logger.warn(`Schema Editor disabled: ${schemaEditorOff.message}`);
26876
+ logger.debug(`Schema Editor disabled: ${schemaEditorOff.message}`);
26563
26877
  } else throw err;
26564
26878
  }
26879
+ /**
26880
+ * The missing `ts-morph`, said the first time the editor is asked for —
26881
+ * the admin panel's collection editor asks both schema surfaces for their
26882
+ * status — rather than at boot. Once per backend.
26883
+ */
26884
+ let toldEditorNeedsTsMorph = false;
26885
+ const sayEditorNeedsTsMorph = async (_c, next) => {
26886
+ if (!toldEditorNeedsTsMorph && schemaEditorOff?.code === "SCHEMA_EDITOR_MISSING_DEPENDENCY") {
26887
+ toldEditorNeedsTsMorph = true;
26888
+ logger.warn(`Schema Editor disabled: ${schemaEditorOff.message}`);
26889
+ }
26890
+ await next();
26891
+ };
26565
26892
  let liveSchemaSurface;
26566
26893
  let schemaEditorSurface;
26567
26894
  if (surfaces.admin) {
@@ -26570,6 +26897,7 @@ async function createRebaseBackend(options) {
26570
26897
  read: "schema:read",
26571
26898
  write: "schema:write"
26572
26899
  });
26900
+ schemaEditorRouter.use("/*", sayEditorNeedsTsMorph);
26573
26901
  schemaEditorRouter.get("/status", (c) => c.json(schemaEditorOff ? {
26574
26902
  enabled: false,
26575
26903
  reason: schemaEditorOff.message,
@@ -26591,7 +26919,7 @@ async function createRebaseBackend(options) {
26591
26919
  if (err?.code !== "ERR_MODULE_NOT_FOUND") throw err;
26592
26920
  remoteEditingOff = {
26593
26921
  code: "SCHEMA_EDITOR_MISSING_DEPENDENCY",
26594
- message: "Live schema editing rewrites collection source with `ts-morph`, which is not installed on this server. Run `pnpm add -D ts-morph@28.0.0` to enable it."
26922
+ message: `Live schema editing rewrites collection source with \`ts-morph\`, which is not installed on this server. ${tsMorphInstallSentence()}`
26595
26923
  };
26596
26924
  logger.warn(`Live schema editing disabled: ${remoteEditingOff.message}`);
26597
26925
  }
@@ -26612,6 +26940,7 @@ async function createRebaseBackend(options) {
26612
26940
  const liveSchemaRouter = new Hono();
26613
26941
  let undeployedCommit;
26614
26942
  applyAdminGate(liveSchemaRouter, "Live schema editing", "schema:read");
26943
+ liveSchemaRouter.use("/*", sayEditorNeedsTsMorph);
26615
26944
  const commitPaths = remoteRepo ? commitPathsFor(path.join("/", remoteRepo.collectionsPath ?? "config/collections"), "/") : collectionsDir && repositoryRoot ? commitPathsFor(collectionsDir, repositoryRoot) : void 0;
26616
26945
  if (commitPaths) logger.debug("Live schema editing commit paths relocated", { commitPaths });
26617
26946
  liveSchemaRouter.route("/", createLiveSchemaRoutes({
@@ -26828,7 +27157,7 @@ async function createRebaseBackend(options) {
26828
27157
  realtime: false,
26829
27158
  token: internalServiceKey,
26830
27159
  fetch: async (input, init) => {
26831
- return await inProcessApp().request(input, init);
27160
+ return await announcements.run(() => inProcessApp().request(input, init));
26832
27161
  }
26833
27162
  });
26834
27163
  const serviceIdentity = SERVICE_IDENTITY;
@@ -26889,9 +27218,9 @@ async function createRebaseBackend(options) {
26889
27218
  injectCallbackClient(driverResults.values(), serverClient);
26890
27219
  let functionsSurface;
26891
27220
  if ((config.functionsDir || config.functions) && surfaces.functions) {
26892
- const { functionList, readFunctionsDirectory } = await import("./function-loader-DbCSoNJj.js").then((n) => n.t);
26893
- const { createFunctionRoutes } = await import("./function-routes-C0emkHtX.js").then((n) => n.n);
26894
- const { createFunctionsRequestTimeout, resolveFunctionsTimeoutMs } = await import("./request-timeout-LiGpCCP1.js").then((n) => n.t);
27221
+ const { functionList, readFunctionsDirectory } = await import("./function-loader-KKDqTcIr.js").then((n) => n.t);
27222
+ const { createFunctionRoutes } = await import("./function-routes-RxrCHNuM.js").then((n) => n.n);
27223
+ const { createFunctionsRequestTimeout, resolveFunctionsTimeoutMs } = await import("./request-timeout-59crS-9u.js").then((n) => n.t);
26895
27224
  const { functions: functionRecord, problems } = config.functions ? {
26896
27225
  functions: config.functions,
26897
27226
  problems: []
@@ -26941,7 +27270,7 @@ async function createRebaseBackend(options) {
26941
27270
  else logger.warn(`Function routes are served, but no functions loaded from ${config.functionsDir ?? "the `functions` option"}. ` + (problems.length > 0 ? `${problems.length} file(s) were skipped — see the messages above.` : "The directory holds no .ts/.js function files."));
26942
27271
  }
26943
27272
  if (config.functionsUpstream && !surfaces.functions) {
26944
- const { createFunctionsProxy } = await import("./proxy-BEKbsil6.js").then((n) => n.t);
27273
+ const { createFunctionsProxy } = await import("./proxy-CH8Jsovg.js").then((n) => n.t);
26945
27274
  functionsSurface = createFunctionsProxy({ upstream: config.functionsUpstream });
26946
27275
  logger.info("Forwarding custom functions to another process", { upstream: config.functionsUpstream });
26947
27276
  }
@@ -26950,10 +27279,10 @@ async function createRebaseBackend(options) {
26950
27279
  /** How many cron jobs `start()` schedules in this process. */
26951
27280
  let cronJobsToSchedule = 0;
26952
27281
  if (surfaces.cron || (config.cronsDir || config.crons) && ownership.cronScheduler) {
26953
- const { cronJobsFrom, readCronsDirectory } = await import("./cron-loader-NHqRfKF0.js").then((n) => n.t);
26954
- const { CronScheduler } = await import("./cron-scheduler-D1DT0nw5.js").then((n) => n.n);
26955
- const { createCronRoutes } = await import("./cron-routes-CPIhdasM.js").then((n) => n.n);
26956
- const { createCronStore } = await import("./cron-store-DEpVstUU.js").then((n) => n.n);
27282
+ const { cronJobsFrom, readCronsDirectory } = await import("./cron-loader-Dj6lA4HQ.js").then((n) => n.t);
27283
+ const { CronScheduler } = await import("./cron-scheduler-B04ACoUE.js").then((n) => n.n);
27284
+ const { createCronRoutes } = await import("./cron-routes-CwbLZguO.js").then((n) => n.n);
27285
+ const { createCronStore } = await import("./cron-store-DkyfuLOH.js").then((n) => n.n);
26957
27286
  const { crons: cronRecord, problems: cronProblems } = config.crons ? {
26958
27287
  crons: config.crons,
26959
27288
  problems: []
@@ -26982,7 +27311,7 @@ async function createRebaseBackend(options) {
26982
27311
  applyAdminGate(cronRouter, "Cron", {
26983
27312
  read: "cron:read",
26984
27313
  write: "cron:write"
26985
- });
27314
+ }, "cron");
26986
27315
  cronRouter.route("/", createCronRoutes(cronScheduler, cronProblems.length));
26987
27316
  cronSurface = cronRouter;
26988
27317
  if (loadedCronJobs.length > 0 && !ownership.cronScheduler) logger.info("Cron jobs registered but NOT scheduled in this process", {
@@ -27014,7 +27343,7 @@ async function createRebaseBackend(options) {
27014
27343
  }
27015
27344
  let metricsHistory;
27016
27345
  try {
27017
- const { createMetricsHistory } = await import("./history-recorder-RZ1Ow4YY.js");
27346
+ const { createMetricsHistory } = await import("./history-recorder-B2rkuhuy.js");
27018
27347
  metricsHistory = createMetricsHistory(defaultDriver);
27019
27348
  if (metricsHistory && provision.database) await metricsHistory.ensure();
27020
27349
  } catch (err) {
@@ -27023,7 +27352,7 @@ async function createRebaseBackend(options) {
27023
27352
  }
27024
27353
  let jobQueue;
27025
27354
  if (jobsOptions?.enabled) {
27026
- const { createJobStore, createJobQueue } = await import("./jobs-8G-_agld.js").then((n) => n.t);
27355
+ const { createJobStore, createJobQueue } = await import("./jobs-h1WRq3cp.js").then((n) => n.t);
27027
27356
  const store = createJobStore(defaultDriver);
27028
27357
  if (store) {
27029
27358
  if (provision.database) await store.ensureTable();
@@ -27063,7 +27392,7 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27063
27392
  let logsSurface;
27064
27393
  let metaSurface;
27065
27394
  if (surfaces.admin) {
27066
- const { createBackupRoutes, backupDestinationFromEnv, readBackupSchedule } = await import("./backup-CpaJWXSc.js").then((n) => n.t);
27395
+ const { createBackupRoutes, backupDestinationFromEnv, readBackupSchedule } = await import("./backup-B9m3cb3X.js").then((n) => n.t);
27067
27396
  const backupRouter = new Hono();
27068
27397
  applyAdminGate(backupRouter, "Backup", "backups:read");
27069
27398
  const scheduler = cronScheduler;
@@ -27084,7 +27413,7 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27084
27413
  rlsAuditSurface = rlsAuditRouter;
27085
27414
  }
27086
27415
  if (surfaces.admin) {
27087
- const { createSqlExportRoutes } = await import("./sql-export-routes-D86u7N9i.js");
27416
+ const { createSqlExportRoutes } = await import("./sql-export-routes-DmB_uZ4l.js");
27088
27417
  const sqlExportRouter = new Hono();
27089
27418
  applyAdminGate(sqlExportRouter, "SQL export", "database:write");
27090
27419
  sqlExportRouter.route("/", createSqlExportRoutes({ getAdmin: () => defaultDriver.admin }));
@@ -27114,14 +27443,14 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27114
27443
  devMailSurface = devMailRouter;
27115
27444
  }
27116
27445
  if (surfaces.admin) {
27117
- const { createLogsRoutes } = await import("./logs-routes-BM2cDory.js").then((n) => n.n);
27446
+ const { createLogsRoutes } = await import("./logs-routes-CBt7mMIL.js").then((n) => n.n);
27118
27447
  const logsRouter = new Hono();
27119
27448
  applyAdminGate(logsRouter, "Logs", "logs:read");
27120
27449
  logsRouter.route("/", createLogsRoutes({}, { closeSignal: longLivedResponses.signal }));
27121
27450
  logsSurface = logsRouter;
27122
27451
  }
27123
27452
  if (surfaces.meta) {
27124
- const { createContractRoutes } = await import("./contract-routes-DiFwiLg2.js").then((n) => n.t);
27453
+ const { createContractRoutes } = await import("./contract-routes-CsFXW7j4.js").then((n) => n.t);
27125
27454
  const contractRouter = new Hono();
27126
27455
  if (adminSurfacesGated) contractRouter.use("/contract", ...adminGate("schema:read"));
27127
27456
  else {
@@ -27129,7 +27458,7 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27129
27458
  code: "CONTRACT_UNAVAILABLE",
27130
27459
  message: "The project contract is only served when authentication is configured, because it describes every table and relation in the project."
27131
27460
  } }, 404));
27132
- logger.warn("Contract endpoint disabled: no auth is configured (no adapter or no jwtSecret), and it would otherwise expose the full collection schema to anyone. `/api/meta/schema-version` is still served.");
27461
+ warnOnMount("meta", "Contract endpoint disabled: no auth is configured (no adapter or no jwtSecret), and it would otherwise expose the full collection schema to anyone. `/api/meta/schema-version` is still served.");
27133
27462
  }
27134
27463
  contractRouter.route("/", createContractRoutes({
27135
27464
  collectionRegistry,
@@ -27190,7 +27519,8 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27190
27519
  if (surfaces[surface]) return;
27191
27520
  throw new Error(`routes.${surface}(): this backend was created with \`surfaces: { ${surface}: false }\`, so it built no ${surface} routes. Create it with the ${surface} surface on to mount them.`);
27192
27521
  };
27193
- const routes = {
27522
+ /** The groups as they are built. `mountInto` and the public {@link routes} below compose from these. */
27523
+ const groups = {
27194
27524
  auth: () => {
27195
27525
  requireSurface("auth");
27196
27526
  const authGroup = group();
@@ -27239,6 +27569,25 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27239
27569
  return group(metaSurface);
27240
27570
  }
27241
27571
  };
27572
+ /**
27573
+ * The groups an application mounts one by one. Asking for one is serving:
27574
+ * what the build said is written, and the group's own warnings with it.
27575
+ */
27576
+ const servedGroup = (name) => () => {
27577
+ const router = groups[name]();
27578
+ announcements.serve();
27579
+ sayMountWarnings(/* @__PURE__ */ new Set([name]));
27580
+ return router;
27581
+ };
27582
+ const routes = {
27583
+ auth: servedGroup("auth"),
27584
+ data: servedGroup("data"),
27585
+ storage: servedGroup("storage"),
27586
+ functions: servedGroup("functions"),
27587
+ admin: servedGroup("admin"),
27588
+ cron: servedGroup("cron"),
27589
+ meta: servedGroup("meta")
27590
+ };
27242
27591
  /** The base path the application last mounted at — where the SDK's socket dials by default. */
27243
27592
  let mountedBasePath;
27244
27593
  /**
@@ -27252,6 +27601,14 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27252
27601
  */
27253
27602
  const mountInto = (app, basePathOption, announce) => {
27254
27603
  const basePath = basePathOption || "/api";
27604
+ if (announce) {
27605
+ announcements.serve();
27606
+ const mounted = /* @__PURE__ */ new Set();
27607
+ if (surfaces.admin) mounted.add("admin");
27608
+ if (surfaces.cron && cronSurface) mounted.add("cron");
27609
+ if (surfaces.meta && metaSurface) mounted.add("meta");
27610
+ sayMountWarnings(mounted);
27611
+ }
27255
27612
  installRootErrorHandler(app);
27256
27613
  installUnmatchedApiEnvelope(app, basePath);
27257
27614
  const middlewares = configureMiddlewares(app, basePath, isProduction, announce ? config : {
@@ -27259,8 +27616,8 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27259
27616
  corsHandled: true
27260
27617
  });
27261
27618
  app.use(`${basePath}/*`, refuseUnhonouredImpersonation([`${basePath}/data`, `${basePath}/functions`]));
27262
- if (surfaces.admin) app.route(`${basePath}/admin`, routes.admin());
27263
- if (surfaces.auth) app.route(`${basePath}/auth`, routes.auth());
27619
+ if (surfaces.admin) app.route(`${basePath}/admin`, groups.admin());
27620
+ if (surfaces.auth) app.route(`${basePath}/auth`, groups.auth());
27264
27621
  if (jwt.isConfigured() && surfaces.auth) {
27265
27622
  app.route("/.well-known", createJwksRoutes(jwt));
27266
27623
  if (announce && jwt.hasAsymmetricSigningKey()) logger.info("Access tokens are signed asymmetrically; public keys at /.well-known/jwks.json");
@@ -27268,12 +27625,12 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27268
27625
  mountMcp(app, basePath, announce);
27269
27626
  if (schemaEditorSurface) app.route(`${basePath}/schema-editor`, deprecatedAlias(schemaEditorSurface, `${basePath}/admin/schema-editor`));
27270
27627
  if (surfaces.storage) {
27271
- app.route(`${basePath}/storage`, routes.storage());
27628
+ app.route(`${basePath}/storage`, groups.storage());
27272
27629
  if (storageCarriesUploadLimit) middlewares.exemptFromBodyLimit(`${basePath}/storage${STORAGE_UPLOAD_PATH}`);
27273
27630
  else if (announce) logger.info("Storage not configured — /storage/sources lists none, every other /storage route returns 501 STORAGE_NOT_CONFIGURED");
27274
27631
  }
27275
27632
  if (surfaces.data) {
27276
- app.route(`${basePath}/data`, routes.data());
27633
+ app.route(`${basePath}/data`, groups.data());
27277
27634
  if (announce && activeCollections.length === 0) logger.info("No collections to serve — /data returns 404 NO_COLLECTIONS");
27278
27635
  mountOpenApiDocs(app, basePath, {
27279
27636
  enableSwagger: config.enableSwagger,
@@ -27286,15 +27643,15 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27286
27643
  });
27287
27644
  }
27288
27645
  if (functionsSurface) {
27289
- app.route(`${basePath}/functions`, routes.functions());
27646
+ app.route(`${basePath}/functions`, groups.functions());
27290
27647
  if (announce) logger.debug("Function routes mounted", { path: `${basePath}/functions` });
27291
27648
  }
27292
27649
  if (surfaces.cron && cronSurface) {
27293
- app.route(`${basePath}/admin/cron`, routes.cron());
27650
+ app.route(`${basePath}/admin/cron`, groups.cron());
27294
27651
  app.route(`${basePath}/cron`, deprecatedAlias(cronSurface, `${basePath}/admin/cron`));
27295
27652
  }
27296
27653
  if (logsSurface) app.route(`${basePath}/logs`, deprecatedAlias(logsSurface, `${basePath}/admin/logs`));
27297
- if (surfaces.meta && metaSurface) app.route(`${basePath}/meta`, routes.meta());
27654
+ if (surfaces.meta && metaSurface) app.route(`${basePath}/meta`, groups.meta());
27298
27655
  if (announce) logger.debug("Rebase backend mounted", { basePath });
27299
27656
  };
27300
27657
  let inProcess;
@@ -27307,6 +27664,7 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27307
27664
  };
27308
27665
  let realtimeAttached = false;
27309
27666
  const attachRealtime = async (server, attachOptions = {}) => {
27667
+ announcements.serve();
27310
27668
  if (!surfaces.realtime || !defaultBootstrapper.initializeWebsockets || !effectiveRealtimeService) return;
27311
27669
  if (realtimeAttached) throw new Error("attachRealtime(): this backend's websocket is already attached to a server.");
27312
27670
  realtimeAttached = true;
@@ -27324,6 +27682,7 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27324
27682
  let authTokenSweep;
27325
27683
  let historyRetentionSweep;
27326
27684
  const start = () => {
27685
+ announcements.serve();
27327
27686
  if (started) return;
27328
27687
  started = true;
27329
27688
  runInBackendScope(ownScope, startTimers);
@@ -27365,7 +27724,7 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27365
27724
  persistClaims: config.cronPersistence !== false
27366
27725
  });
27367
27726
  };
27368
- const shutdown = (timeoutMs, shutdownOptions = {}) => createShutdown({
27727
+ const shutdown = (timeoutMs, shutdownOptions = {}) => announcements.run(() => createShutdown({
27369
27728
  server: shutdownOptions.server,
27370
27729
  cronScheduler,
27371
27730
  jobQueue,
@@ -27376,7 +27735,7 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27376
27735
  realtimeServices,
27377
27736
  closeLongLivedResponses: () => longLivedResponses.abort(),
27378
27737
  emailService
27379
- })(timeoutMs);
27738
+ })(timeoutMs));
27380
27739
  logger.debug("Rebase Backend Initialized");
27381
27740
  return {
27382
27741
  rebase: serverSingleton,
@@ -27395,7 +27754,7 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27395
27754
  cronScheduler,
27396
27755
  jobQueue,
27397
27756
  routes,
27398
- fetch: async (request) => await inProcessApp().fetch(request),
27757
+ fetch: async (request) => await announcements.run(() => inProcessApp().fetch(request)),
27399
27758
  mount: (app, mountOptions = {}) => {
27400
27759
  mountInto(app, mountOptions.basePath, true);
27401
27760
  mountedBasePath = mountOptions.basePath || "/api";
@@ -29080,14 +29439,14 @@ function listenWithPortRetry(server, startPort, options) {
29080
29439
  const onListening = () => {
29081
29440
  cleanup();
29082
29441
  const bound = boundPort(server) ?? port;
29083
- if (portFileDir) {
29084
- try {
29085
- const portFile = path$1.join(portFileDir, DEV_PORT_FILENAME);
29086
- fs.writeFileSync(portFile, `${bound} ${startPort}`, "utf-8");
29087
- } catch {}
29088
- writeStateFile(portFileDir, bound, options?.serviceKey);
29442
+ if (!portFileDir) {
29443
+ resolve(bound);
29444
+ return;
29089
29445
  }
29090
- resolve(bound);
29446
+ heldByAnotherServer(portFileDir, bound).catch(() => false).then((held) => {
29447
+ if (!held) writeDevFiles(portFileDir, bound, startPort, options?.serviceKey);
29448
+ resolve(bound);
29449
+ });
29091
29450
  };
29092
29451
  /**
29093
29452
  * Say which port was refused and which one is next.
@@ -29133,18 +29492,116 @@ function listenWithPortRetry(server, startPort, options) {
29133
29492
  });
29134
29493
  }
29135
29494
  /**
29495
+ * What this process wrote into each project's `.rebase-dev-port`, by
29496
+ * directory, so that stopping removes the file only while it still says that.
29497
+ */
29498
+ var writtenPortFiles = /* @__PURE__ */ new Map();
29499
+ function stateFilePath(dir) {
29500
+ return path$1.join(dir, ".rebase", "state.json");
29501
+ }
29502
+ /** The pid and port `.rebase/state.json` names, or `null` when there is none to read. */
29503
+ function readStateOwner(dir) {
29504
+ let raw;
29505
+ try {
29506
+ raw = fs.readFileSync(stateFilePath(dir), "utf-8");
29507
+ } catch {
29508
+ return null;
29509
+ }
29510
+ try {
29511
+ const parsed = JSON.parse(raw);
29512
+ if (typeof parsed !== "object" || parsed === null) return {
29513
+ pid: null,
29514
+ port: null
29515
+ };
29516
+ return {
29517
+ pid: "pid" in parsed && typeof parsed.pid === "number" && Number.isInteger(parsed.pid) && parsed.pid > 0 ? parsed.pid : null,
29518
+ port: "port" in parsed && typeof parsed.port === "number" && Number.isInteger(parsed.port) ? parsed.port : null
29519
+ };
29520
+ } catch {
29521
+ return {
29522
+ pid: null,
29523
+ port: null
29524
+ };
29525
+ }
29526
+ }
29527
+ /** Is *some* process with this pid running? EPERM means it is, as someone else. */
29528
+ function processAlive(pid) {
29529
+ try {
29530
+ process.kill(pid, 0);
29531
+ return true;
29532
+ } catch (error) {
29533
+ return error.code === "EPERM";
29534
+ }
29535
+ }
29536
+ /**
29537
+ * Whether `rebase dev` started this server.
29538
+ *
29539
+ * It states `REBASE_DEV_PORT_EXPLICIT`, true or false, on every backend it
29540
+ * spawns (see `options.explicit` above), so the variable reading as either at
29541
+ * all is the mark. The dev files are named for that server and describe it
29542
+ * first.
29543
+ */
29544
+ function startedByRebaseDev() {
29545
+ return parseEnvBoolean(process.env.REBASE_DEV_PORT_EXPLICIT) !== void 0;
29546
+ }
29547
+ /**
29548
+ * Whether another server in this project holds the dev files, so this one must
29549
+ * leave them alone.
29550
+ *
29551
+ * `rebase start` beside `rebase dev` is the documented local check, and both
29552
+ * bind through {@link listenWithPortRetry}. The files used to belong to
29553
+ * whoever bound last: `start` took them over while it ran, so `rebase
29554
+ * api-keys`, `generate-sdk` and MCP discovery all reached it rather than dev,
29555
+ * and when it stopped it deleted them under a dev that was still serving —
29556
+ * `api-keys list` then answered "This project's server is not running".
29557
+ *
29558
+ * So the files belong to the server `state.json` names while that server is
29559
+ * still there: its pid alive, and its port answering, because a pid alone can
29560
+ * be a stranger's after a crash and a reboot. A server on the very port it
29561
+ * names is not "another" one — that is a `tsx watch` restart, or a crash this
29562
+ * server has replaced. A server `rebase dev` started takes the files over in
29563
+ * every case: they are its own.
29564
+ */
29565
+ async function heldByAnotherServer(dir, bound) {
29566
+ if (startedByRebaseDev()) return false;
29567
+ const owner = readStateOwner(dir);
29568
+ if (!owner || owner.pid === null || owner.pid === process.pid) return false;
29569
+ if (owner.port === null || owner.port === bound) return false;
29570
+ if (!processAlive(owner.pid)) return false;
29571
+ return loopbackInUse(owner.port);
29572
+ }
29573
+ /** Write `.rebase-dev-port` and `.rebase/state.json` for a server this process just bound. */
29574
+ function writeDevFiles(dir, bound, requested, serviceKey) {
29575
+ try {
29576
+ const contents = `${bound} ${requested}`;
29577
+ fs.writeFileSync(path$1.join(dir, DEV_PORT_FILENAME), contents, "utf-8");
29578
+ writtenPortFiles.set(path$1.resolve(dir), contents);
29579
+ } catch {}
29580
+ writeStateFile(dir, bound, serviceKey);
29581
+ }
29582
+ /**
29136
29583
  * Clean up the dev port file and state file (call on graceful shutdown).
29137
29584
  *
29585
+ * Only this process's own: `state.json` while it names this pid, and
29586
+ * `.rebase-dev-port` while it holds what this process wrote and no other
29587
+ * server has taken `state.json` over. A `rebase start` that stopped beside a
29588
+ * running `rebase dev` used to delete dev's files — see `heldByAnotherServer`.
29589
+ *
29138
29590
  * @internal Not part of the stable public API. See {@link listenWithPortRetry}.
29139
29591
  */
29140
29592
  function cleanupDevPortFile(dir) {
29141
- try {
29142
- const portFile = path$1.join(dir, DEV_PORT_FILENAME);
29143
- if (fs.existsSync(portFile)) fs.unlinkSync(portFile);
29593
+ const owner = readStateOwner(dir);
29594
+ const ownsState = owner?.pid === process.pid;
29595
+ if (ownsState) try {
29596
+ fs.unlinkSync(stateFilePath(dir));
29144
29597
  } catch {}
29598
+ const key = path$1.resolve(dir);
29599
+ const wrote = writtenPortFiles.get(key);
29600
+ if (wrote === void 0 || !(ownsState || owner === null)) return;
29145
29601
  try {
29146
- const stateFile = path$1.join(dir, ".rebase", "state.json");
29147
- if (fs.existsSync(stateFile)) fs.unlinkSync(stateFile);
29602
+ const portFile = path$1.join(dir, DEV_PORT_FILENAME);
29603
+ if (fs.readFileSync(portFile, "utf-8") === wrote) fs.unlinkSync(portFile);
29604
+ writtenPortFiles.delete(key);
29148
29605
  } catch {}
29149
29606
  }
29150
29607
  /**
@@ -29587,26 +30044,17 @@ function isAssetRequest(requestPath, destination) {
29587
30044
  * Erring toward "not hashed" is therefore the safe direction: the cost is a
29588
30045
  * revalidation that usually answers 304, against serving a year-stale file with
29589
30046
  * no way to recall it. So this demands positive evidence of a hash rather than
29590
- * assuming one — a segment of 8+ characters after the last `-` that is not
29591
- * ordinary lowercase prose. Vite and Rollup emit base64url digests, which
29592
- * essentially always carry a digit or a capital; a hand-written
29593
- * `vendor-analytics.js` does not, and gets revalidated.
30047
+ * assuming one, by the rule `rebase build` precompresses with
30048
+ * (`isContentHashedFileName`): Vite's eight base64url characters after a `-`,
30049
+ * or 8+ after the last `-`, with a digit or a capital in them. A hand-written
30050
+ * `vendor-analytics.js` has neither, and gets revalidated.
29594
30051
  *
29595
30052
  * Deliberately not "everything under /assets/": that is a Vite convention, not a
29596
30053
  * guarantee, and a build that drops an unhashed file there would have it pinned
29597
30054
  * in every visitor's cache for a year with no way to correct it.
29598
30055
  */
29599
30056
  function isContentHashed(requestPath) {
29600
- const lastSegment = requestPath.slice(requestPath.lastIndexOf("/") + 1);
29601
- const dot = lastSegment.lastIndexOf(".");
29602
- if (dot <= 0) return false;
29603
- const stem = lastSegment.slice(0, dot);
29604
- const dash = stem.lastIndexOf("-");
29605
- if (dash < 0) return false;
29606
- const suffix = stem.slice(dash + 1);
29607
- if (suffix.length < 8) return false;
29608
- if (!/^[A-Za-z0-9_-]+$/.test(suffix)) return false;
29609
- return /[0-9A-Z]/.test(suffix);
30057
+ return isContentHashedFileName(requestPath.slice(requestPath.lastIndexOf("/") + 1));
29610
30058
  }
29611
30059
  /** A year, and never revalidated: the URL changes when the bytes do. */
29612
30060
  var IMMUTABLE_CACHE = "public, max-age=31536000, immutable";
@@ -32134,7 +32582,7 @@ async function bootFromBundle(options = {}) {
32134
32582
  }
32135
32583
  const server = createRuntimeHttpServer(getRequestListener(app.fetch));
32136
32584
  const usersCollection = await loadUsersCollection(bundle);
32137
- const storage = resolveStorageSources(process.env, storageSourceDefs, path$1.join(bundle.dir, "uploads"), { production: isProduction });
32585
+ const storage = resolveBootStorage(bundle, process.env, storageSourceDefs, isProduction);
32138
32586
  warnOnUnusableBundleShape(bundle);
32139
32587
  installUnhandledRejectionHandler();
32140
32588
  const createdBackend = await createRebaseBackend({
@@ -32524,6 +32972,61 @@ function warnOnDriverSkew(dataSources, runtimeVersion) {
32524
32972
  }
32525
32973
  }
32526
32974
  /**
32975
+ * The storage step of {@link bootFromBundle}: every storage source this process
32976
+ * serves, from the declarations and the environment.
32977
+ *
32978
+ * Local storage that `STORAGE_PATH` does not place keeps its files in
32979
+ * {@link defaultStorageDirectory}.
32980
+ */
32981
+ function resolveBootStorage(bundle, env, definitions, production) {
32982
+ const directory = defaultStorageDirectory(bundle);
32983
+ const storage = resolveStorageSources(env, definitions, directory, { production });
32984
+ if (!env.STORAGE_PATH?.trim()) warnOnUploadsLeftInBundle(bundle, directory);
32985
+ return storage;
32986
+ }
32987
+ /**
32988
+ * Where local storage keeps its files when `STORAGE_PATH` names no directory:
32989
+ * `<project>/uploads`, where `rebase dev` and the server `rebase eject` writes
32990
+ * keep them too.
32991
+ *
32992
+ * A built bundle that sits inside a project — `rebase start` boots
32993
+ * `<project>/dist-bundle` — stores there too, found by the project's
32994
+ * `rebase.json`, so `rebase start` serves what `rebase dev` stored. Not inside
32995
+ * the bundle: the next `rebase build` deletes that directory and writes it
32996
+ * again, and every file in it would go while the rows naming them stay.
32997
+ *
32998
+ * A bundle with no project around it stores inside the bundle: the runtime
32999
+ * image's `/bundle`, a bundle the runtime fetched. A container names its volume
33000
+ * with `STORAGE_PATH`, which wins over this either way.
33001
+ */
33002
+ function defaultStorageDirectory(bundle) {
33003
+ const root = bundle.isSource ? bundle.dir : findProjectRoot(path$1.dirname(bundle.dir)) ?? bundle.dir;
33004
+ return path$1.join(root, "uploads");
33005
+ }
33006
+ /**
33007
+ * Name the uploads inside a bundle whose local storage is in the project:
33008
+ * nothing serves them, and the next `rebase build` deletes them. Silent for a
33009
+ * bundle that stores inside itself, and for one that holds none.
33010
+ */
33011
+ function warnOnUploadsLeftInBundle(bundle, directory) {
33012
+ if (path$1.dirname(directory) === bundle.dir) return;
33013
+ let entries;
33014
+ try {
33015
+ entries = fs.readdirSync(bundle.dir, { withFileTypes: true });
33016
+ } catch {
33017
+ return;
33018
+ }
33019
+ const left = entries.filter((entry) => entry.isDirectory() && /^uploads(__.+)?$/.test(entry.name)).map((entry) => path$1.join(bundle.dir, entry.name)).filter((dir) => {
33020
+ try {
33021
+ return fs.readdirSync(dir).length > 0;
33022
+ } catch {
33023
+ return false;
33024
+ }
33025
+ });
33026
+ if (left.length === 0) return;
33027
+ logger.warn(`Uploads an earlier version stored inside the bundle are no longer served: ${left.join(", ")}. Local storage keeps its files in ${directory} now, where \`rebase dev\` keeps them, and the next \`rebase build\` deletes the bundle's copy. Move them there (a named bucket's \`uploads__<key>\` goes beside it), or set STORAGE_PATH=${path$1.join(bundle.dir, "uploads")} to keep serving them where they are.`);
33028
+ }
33029
+ /**
32527
33030
  * Warn about a bundle whose shape makes provisioning impossible.
32528
33031
  *
32529
33032
  * `createRebaseBackend` provisions the collection schema for every boot