@rebasepro/server 0.25.0 → 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 (66) hide show
  1. package/dist/{GCSStorageController-C7ARrDAp.js → GCSStorageController-CRd9bFrc.js} +3 -3
  2. package/dist/{GCSStorageController-C7ARrDAp.js.map → GCSStorageController-CRd9bFrc.js.map} +1 -1
  3. package/dist/{S3StorageController-qevJ0pVE.js → S3StorageController-D00EFa5P.js} +3 -3
  4. package/dist/{S3StorageController-qevJ0pVE.js.map → S3StorageController-D00EFa5P.js.map} +1 -1
  5. package/dist/api/closed-database.d.ts +25 -0
  6. package/dist/{auth-CteGWxsL.js → auth-Dkvo2tTJ.js} +26 -18
  7. package/dist/auth-Dkvo2tTJ.js.map +1 -0
  8. package/dist/backend/boot-announcements.d.ts +13 -0
  9. package/dist/backend/create-backend.d.ts +4 -0
  10. package/dist/{backup-Dq0lyJ_D.js → backup-B9m3cb3X.js} +7 -7
  11. package/dist/{backup-Dq0lyJ_D.js.map → backup-B9m3cb3X.js.map} +1 -1
  12. package/dist/{contract-routes-DiFwiLg2.js → contract-routes-CsFXW7j4.js} +2 -2
  13. package/dist/{contract-routes-DiFwiLg2.js.map → contract-routes-CsFXW7j4.js.map} +1 -1
  14. package/dist/{cron-loader-NHqRfKF0.js → cron-loader-Dj6lA4HQ.js} +2 -2
  15. package/dist/{cron-loader-NHqRfKF0.js.map → cron-loader-Dj6lA4HQ.js.map} +1 -1
  16. package/dist/{cron-routes-Dhf82HzT.js → cron-routes-CwbLZguO.js} +5 -5
  17. package/dist/{cron-routes-Dhf82HzT.js.map → cron-routes-CwbLZguO.js.map} +1 -1
  18. package/dist/{cron-scheduler-D1DT0nw5.js → cron-scheduler-B04ACoUE.js} +2 -2
  19. package/dist/{cron-scheduler-D1DT0nw5.js.map → cron-scheduler-B04ACoUE.js.map} +1 -1
  20. package/dist/{cron-store-DEpVstUU.js → cron-store-DkyfuLOH.js} +3 -3
  21. package/dist/{cron-store-DEpVstUU.js.map → cron-store-DkyfuLOH.js.map} +1 -1
  22. package/dist/{ddl-bootstrap-BNqi2NZe.js → ddl-bootstrap-Ck20GSIX.js} +2 -2
  23. package/dist/{ddl-bootstrap-BNqi2NZe.js.map → ddl-bootstrap-Ck20GSIX.js.map} +1 -1
  24. package/dist/{errors-_ytoGi8A.js → errors-OMWkKSH-.js} +66 -5
  25. package/dist/errors-OMWkKSH-.js.map +1 -0
  26. package/dist/{function-loader-DbCSoNJj.js → function-loader-KKDqTcIr.js} +2 -2
  27. package/dist/{function-loader-DbCSoNJj.js.map → function-loader-KKDqTcIr.js.map} +1 -1
  28. package/dist/{function-routes-DNV4RUF0.js → function-routes-RxrCHNuM.js} +2 -2
  29. package/dist/{function-routes-DNV4RUF0.js.map → function-routes-RxrCHNuM.js.map} +1 -1
  30. package/dist/functions/index.js +18 -1
  31. package/dist/functions/index.js.map +1 -1
  32. package/dist/{history-recorder-RZ1Ow4YY.js → history-recorder-B2rkuhuy.js} +2 -2
  33. package/dist/{history-recorder-RZ1Ow4YY.js.map → history-recorder-B2rkuhuy.js.map} +1 -1
  34. package/dist/index.es.js +529 -113
  35. package/dist/index.es.js.map +1 -1
  36. package/dist/{jobs-8G-_agld.js → jobs-h1WRq3cp.js} +3 -3
  37. package/dist/{jobs-8G-_agld.js.map → jobs-h1WRq3cp.js.map} +1 -1
  38. package/dist/{jwt-YhciR93H.js → jwt-D3oFauZI.js} +2 -2
  39. package/dist/{jwt-YhciR93H.js.map → jwt-D3oFauZI.js.map} +1 -1
  40. package/dist/{logger-Bt32sHFw.js → logger-CW8pmTS6.js} +35 -3
  41. package/dist/logger-CW8pmTS6.js.map +1 -0
  42. package/dist/{logs-routes-CelTiS7C.js → logs-routes-CBt7mMIL.js} +3 -3
  43. package/dist/{logs-routes-CelTiS7C.js.map → logs-routes-CBt7mMIL.js.map} +1 -1
  44. package/dist/{openapi-generator-BamY6-ga.js → openapi-generator-BjHr6upl.js} +2 -2
  45. package/dist/{openapi-generator-BamY6-ga.js.map → openapi-generator-BjHr6upl.js.map} +1 -1
  46. package/dist/{proxy-BEKbsil6.js → proxy-CH8Jsovg.js} +2 -2
  47. package/dist/{proxy-BEKbsil6.js.map → proxy-CH8Jsovg.js.map} +1 -1
  48. package/dist/{query-parser-CGohjyWI.js → query-parser-otFZqJsC.js} +3 -3
  49. package/dist/{query-parser-CGohjyWI.js.map → query-parser-otFZqJsC.js.map} +1 -1
  50. package/dist/{request-timeout-CRny5KD0.js → request-timeout-59crS-9u.js} +3 -3
  51. package/dist/{request-timeout-CRny5KD0.js.map → request-timeout-59crS-9u.js.map} +1 -1
  52. package/dist/{schema-editor-routes-CnNGCayU.js → schema-editor-routes-Bsut0bCC.js} +2 -2
  53. package/dist/{schema-editor-routes-CnNGCayU.js.map → schema-editor-routes-Bsut0bCC.js.map} +1 -1
  54. package/dist/{soft-delete-params-BAgpmBvp.js → soft-delete-params-BmlVwecC.js} +2 -2
  55. package/dist/{soft-delete-params-BAgpmBvp.js.map → soft-delete-params-BmlVwecC.js.map} +1 -1
  56. package/dist/{sql-export-routes-BgTJ7K3S.js → sql-export-routes-DmB_uZ4l.js} +3 -3
  57. package/dist/{sql-export-routes-BgTJ7K3S.js.map → sql-export-routes-DmB_uZ4l.js.map} +1 -1
  58. package/dist/{upload-validation-I9cCUBIW.js → upload-validation-Bgo626uY.js} +2 -2
  59. package/dist/{upload-validation-I9cCUBIW.js.map → upload-validation-Bgo626uY.js.map} +1 -1
  60. package/dist/utils/dev-dependency-command.d.ts +15 -0
  61. package/dist/utils/dev-port.d.ts +8 -0
  62. package/dist/utils/logger.d.ts +24 -0
  63. package/package.json +5 -5
  64. package/dist/auth-CteGWxsL.js.map +0 -1
  65. package/dist/errors-_ytoGi8A.js.map +0 -1
  66. 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-BamY6-ga.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-CteGWxsL.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-BAgpmBvp.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-CGohjyWI.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-_ytoGi8A.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-CelTiS7C.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-I9cCUBIW.js";
29
- import { t as S3StorageController } from "./S3StorageController-qevJ0pVE.js";
30
- import { t as GCSStorageController } from "./GCSStorageController-C7ARrDAp.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-DNV4RUF0.js";
35
- import "./proxy-BEKbsil6.js";
36
- import "./request-timeout-CRny5KD0.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-Dhf82HzT.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-Dq0lyJ_D.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";
@@ -6617,6 +6617,37 @@ function realtimeLostError() {
6617
6617
  function realtimeDebugOn() {
6618
6618
  return Reflect.get(globalThis, "__REBASE_DEBUG_REALTIME__") === true;
6619
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
+ }
6620
6651
  /** The ceiling of the reconnect backoff. */
6621
6652
  var MAX_RECONNECT_DELAY_MS = 3e4;
6622
6653
  /**
@@ -6962,7 +6993,7 @@ var RebaseWebSocketClient = class {
6962
6993
  if (this.closedByCaller) return;
6963
6994
  if (this.connectionState !== "reconnecting" && this.connectionState !== "disconnected") return;
6964
6995
  if (this.ws) return;
6965
- console.debug(`${reason} — retrying the realtime connection`);
6996
+ connectionDebug(`${reason} — retrying the realtime connection`);
6966
6997
  if (this.reconnectTimeout) {
6967
6998
  clearTimeout(this.reconnectTimeout);
6968
6999
  this.reconnectTimeout = null;
@@ -6994,6 +7025,24 @@ var RebaseWebSocketClient = class {
6994
7025
  }
6995
7026
  onlineListener = null;
6996
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
+ }
6997
7046
  /**
6998
7047
  * Authenticate the WebSocket connection
6999
7048
  */
@@ -7042,10 +7091,10 @@ var RebaseWebSocketClient = class {
7042
7091
  if (this.isConnected && !this.isAuthenticated && !this.authPromise) this.getAuthToken().then((token) => {
7043
7092
  if (!this.ws) return;
7044
7093
  if (token) this.authenticate(token).catch((e) => {
7045
- if (this.ws) console.debug("WebSocket auto-auth skipped:", e?.message || e);
7094
+ if (this.ws) connectionDebug("WebSocket auto-auth skipped:", e?.message || e);
7046
7095
  });
7047
7096
  }).catch((e) => {
7048
- if (this.ws) console.debug("WebSocket auto-auth skipped:", e?.message || e);
7097
+ if (this.ws) connectionDebug("WebSocket auto-auth skipped:", e?.message || e);
7049
7098
  });
7050
7099
  }
7051
7100
  /**
@@ -7061,6 +7110,7 @@ var RebaseWebSocketClient = class {
7061
7110
  if (permanent) this.removeNetworkListeners();
7062
7111
  this.isAuthenticated = false;
7063
7112
  this.authPromise = null;
7113
+ this.refusedSession = null;
7064
7114
  if (this.reconnectTimeout) {
7065
7115
  clearTimeout(this.reconnectTimeout);
7066
7116
  this.reconnectTimeout = null;
@@ -7095,7 +7145,7 @@ var RebaseWebSocketClient = class {
7095
7145
  const socket = new this.WebSocketConstructor(withOptIns(this.websocketUrl));
7096
7146
  this.ws = socket;
7097
7147
  this.ws.onopen = async () => {
7098
- console.debug("Connected to PostgreSQL backend");
7148
+ connectionDebug("Connected to PostgreSQL backend");
7099
7149
  const wasReconnect = this.hadConnection;
7100
7150
  this.hadConnection = true;
7101
7151
  this.isConnected = true;
@@ -7104,14 +7154,18 @@ var RebaseWebSocketClient = class {
7104
7154
  this.outageReported = false;
7105
7155
  this.gaveUp = false;
7106
7156
  this.setState("connected");
7107
- if (this.getAuthToken && !this.isAuthenticated) try {
7108
- const token = await this.getAuthToken();
7109
- if (token) {
7110
- await this.authenticate(token);
7111
- 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);
7112
7168
  }
7113
- } catch (error) {
7114
- console.debug("WebSocket connected without auth:", error?.message || error);
7115
7169
  }
7116
7170
  this.emit(wasReconnect ? "reconnect" : "connect");
7117
7171
  this.processMessageQueue(wasReconnect);
@@ -7128,7 +7182,7 @@ var RebaseWebSocketClient = class {
7128
7182
  }
7129
7183
  };
7130
7184
  const onClosed = () => {
7131
- console.debug("Disconnected from PostgreSQL backend");
7185
+ connectionDebug("Disconnected from PostgreSQL backend");
7132
7186
  this.stopLivenessWatch();
7133
7187
  this.forgetServerOptIns();
7134
7188
  if (this.ws === socket) this.ws = null;
@@ -7258,7 +7312,7 @@ var RebaseWebSocketClient = class {
7258
7312
  this.reconnectAttempts = Math.min(this.reconnectAttempts + 1, 30);
7259
7313
  const ceiling = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), MAX_RECONNECT_DELAY_MS);
7260
7314
  const delay = MIN_RECONNECT_DELAY_MS + Math.floor(Math.random() * (ceiling - MIN_RECONNECT_DELAY_MS));
7261
- console.debug(`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts})`);
7315
+ connectionDebug(`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts})`);
7262
7316
  if (this.reconnectTimeout) clearTimeout(this.reconnectTimeout);
7263
7317
  this.reconnectTimeout = setTimeout(() => {
7264
7318
  this.reconnectTimeout = null;
@@ -7527,31 +7581,36 @@ var RebaseWebSocketClient = class {
7527
7581
  }
7528
7582
  async runAuthentication(retryCount) {
7529
7583
  let lastError = null;
7530
- for (let attempt = 0; attempt < retryCount; attempt++) try {
7531
- const token = await this.getAuthToken();
7532
- if (!token) throw new Error("user not logged in");
7533
- await this.authenticate(token);
7534
- console.debug("WebSocket authenticated on demand");
7535
- return;
7536
- } catch (error) {
7537
- lastError = error;
7538
- const errMsg = error instanceof Error ? error.message : String(error);
7539
- if (errMsg.includes("not logged in") || errMsg.includes("Session expired")) {
7540
- console.warn("WebSocket auth failed: user not logged in");
7541
- throw error;
7542
- }
7543
- 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
+ }
7544
7608
  if (attempt < retryCount - 1) {
7545
- 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...`);
7546
7611
  await new Promise((resolve) => setTimeout(resolve, delay));
7547
- continue;
7548
7612
  }
7549
7613
  }
7550
- if (attempt < retryCount - 1) {
7551
- const delay = Math.min(1e3 * (attempt + 1), 3e3);
7552
- console.debug(`WebSocket auth attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
7553
- await new Promise((resolve) => setTimeout(resolve, delay));
7554
- }
7555
7614
  }
7556
7615
  console.warn("WebSocket on-demand auth failed after retries:", lastError);
7557
7616
  throw lastError;
@@ -7563,7 +7622,7 @@ var RebaseWebSocketClient = class {
7563
7622
  const token = await this.getAuthToken();
7564
7623
  if (!token) throw new Error("user not logged in");
7565
7624
  await this.authenticate(token);
7566
- console.debug("WebSocket reauthenticated successfully");
7625
+ connectionDebug("WebSocket reauthenticated successfully");
7567
7626
  } catch (error) {
7568
7627
  console.error("WebSocket reauthentication failed:", error);
7569
7628
  throw error;
@@ -7637,6 +7696,10 @@ var RebaseWebSocketClient = class {
7637
7696
  await this.ensureAuthenticated();
7638
7697
  } catch (error) {
7639
7698
  if (!(isSubscriptionFrame && isNotSignedIn(error))) {
7699
+ if (error instanceof RebaseApiError) {
7700
+ reject(error);
7701
+ return;
7702
+ }
7640
7703
  reject(new RebaseApiError(error instanceof Error ? error.message : "Authentication required"));
7641
7704
  return;
7642
7705
  }
@@ -8249,7 +8312,7 @@ var RebaseWebSocketClient = class {
8249
8312
  * we need to re-register everything to resume receiving updates.
8250
8313
  */
8251
8314
  resubscribeAll() {
8252
- 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)`);
8253
8316
  for (const [key, sub] of this.collectionSubscriptions.entries()) {
8254
8317
  if (sub.subscribeInFlight) continue;
8255
8318
  const oldBackendId = sub.backendSubscriptionId;
@@ -14802,6 +14865,99 @@ function recordMountPath() {
14802
14865
  };
14803
14866
  }
14804
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
14805
14961
  //#region src/utils/byte-size.ts
14806
14962
  /**
14807
14963
  * A byte count as an operator reads it in an error message: `10MB`, `1.5MB`,
@@ -14825,6 +14981,93 @@ function trim(value) {
14825
14981
  return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
14826
14982
  }
14827
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
14828
15071
  //#region src/utils/compression.ts
14829
15072
  /**
14830
15073
  * Below this many bytes a body is sent as it is. Hono's own default: gzip's
@@ -18442,11 +18685,11 @@ async function createStorageController(config) {
18442
18685
  switch (config.type) {
18443
18686
  case "local": return new LocalStorageController(config);
18444
18687
  case "s3": {
18445
- const { S3StorageController } = await import("./S3StorageController-qevJ0pVE.js").then((n) => n.n);
18688
+ const { S3StorageController } = await import("./S3StorageController-D00EFa5P.js").then((n) => n.n);
18446
18689
  return new S3StorageController(config);
18447
18690
  }
18448
18691
  case "gcs": {
18449
- const { GCSStorageController } = await import("./GCSStorageController-C7ARrDAp.js").then((n) => n.n);
18692
+ const { GCSStorageController } = await import("./GCSStorageController-CRd9bFrc.js").then((n) => n.n);
18450
18693
  return new GCSStorageController(config);
18451
18694
  }
18452
18695
  default: throw new Error(`Unknown storage type: ${config.type}. Built-in types: local, s3, gcs. For other providers, implement the StorageController interface directly.`);
@@ -18685,7 +18928,7 @@ function noCollectionsCause(unprotected) {
18685
18928
  * routes wherever and as often as the application asks.
18686
18929
  */
18687
18930
  async function loadOpenApiGenerator() {
18688
- const { generateOpenApiSpec } = await import("./openapi-generator-BamY6-ga.js").then((n) => n.r);
18931
+ const { generateOpenApiSpec } = await import("./openapi-generator-BjHr6upl.js").then((n) => n.r);
18689
18932
  return generateOpenApiSpec;
18690
18933
  }
18691
18934
  /**
@@ -19778,7 +20021,7 @@ function createLiveSchemaRoutes(config) {
19778
20021
  enabled: false,
19779
20022
  canPlan: true,
19780
20023
  code: off?.code ?? "SCHEMA_EDITOR_MISSING_DEPENDENCY",
19781
- 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()}`
19782
20025
  });
19783
20026
  }
19784
20027
  if (!repository) return c.json({
@@ -25989,8 +26232,23 @@ function resolveProvision(provision) {
25989
26232
  * which {@link setDefaultBackend} sets and `initializeRebaseBackend` sets for
25990
26233
  * its backend. The one process-wide setting it touches is the log level, and
25991
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.
25992
26239
  */
25993
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) {
25994
26252
  const config = options;
25995
26253
  const provision = resolveProvision(config.provision);
25996
26254
  if (config.logging?.level !== void 0) setLogLevel(config.logging.level);
@@ -26270,7 +26528,7 @@ async function createRebaseBackend(options) {
26270
26528
  const platformTokenPreAuth = createPlatformTokenPreAuth(await platformTokensFromEnv(process.env));
26271
26529
  let backupLinkRouter;
26272
26530
  if (surfaces.admin) {
26273
- const { createBackupLinkRoutes, backupDestinationFromEnv } = await import("./backup-Dq0lyJ_D.js").then((n) => n.t);
26531
+ const { createBackupLinkRoutes, backupDestinationFromEnv } = await import("./backup-B9m3cb3X.js").then((n) => n.t);
26274
26532
  backupLinkRouter = createBackupLinkRoutes({ getDestination: () => backupDestinationFromEnv(process.env) });
26275
26533
  }
26276
26534
  const apiKeyRouter = apiKeyStore && surfaces.admin ? createApiKeyRoutes({
@@ -26415,7 +26673,7 @@ async function createRebaseBackend(options) {
26415
26673
  ]) {
26416
26674
  const providerConfig = safeAuthConfig[key];
26417
26675
  if (providerConfig && requiredFields.every((f) => Boolean(providerConfig[f]))) {
26418
- const createFn = (await import("./auth-CteGWxsL.js").then((n) => n.t))[factory];
26676
+ const createFn = (await import("./auth-Dkvo2tTJ.js").then((n) => n.t))[factory];
26419
26677
  oauthProviders.push(createFn(providerConfig));
26420
26678
  }
26421
26679
  }
@@ -26527,6 +26785,21 @@ async function createRebaseBackend(options) {
26527
26785
  }));
26528
26786
  if (announce) logger.info(`MCP endpoint mounted at ${publicUrl}${mcpPath} (OAuth at ${oauthBasePath})`);
26529
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
+ };
26530
26803
  const adminSurfacesGated = !!authAdapter && (isAuthAdapter(config.auth) || !!config.auth.jwtSecret);
26531
26804
  const adminGate = (scopes) => [
26532
26805
  provideServices,
@@ -26538,9 +26811,9 @@ async function createRebaseBackend(options) {
26538
26811
  }),
26539
26812
  typeof scopes === "string" ? requireScope(scopes) : requireScopeByMethod(scopes)
26540
26813
  ];
26541
- const applyAdminGate = (router, surface, scopes) => {
26814
+ const applyAdminGate = (router, surface, scopes, mountedWith = "admin") => {
26542
26815
  if (!adminSurfacesGated) {
26543
- 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.`);
26544
26817
  router.use("/*", async (c) => c.json({ error: {
26545
26818
  code: "ADMIN_SURFACE_UNAVAILABLE",
26546
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.`
@@ -26591,18 +26864,31 @@ async function createRebaseBackend(options) {
26591
26864
  let schemaEditorRoutes;
26592
26865
  let schemaSourceEditor;
26593
26866
  if (!schemaEditorOff && config.collectionsDir) try {
26594
- const editorModule = await import("./schema-editor-routes-CnNGCayU.js");
26867
+ const editorModule = await import("./schema-editor-routes-Bsut0bCC.js");
26595
26868
  schemaSourceEditor = editorModule.createSchemaSourceEditor(config.collectionsDir);
26596
26869
  schemaEditorRoutes = editorModule.createSchemaEditorRoutes(schemaSourceEditor);
26597
26870
  } catch (err) {
26598
26871
  if (err?.code === "ERR_MODULE_NOT_FOUND") {
26599
26872
  schemaEditorOff = {
26600
26873
  code: "SCHEMA_EDITOR_MISSING_DEPENDENCY",
26601
- 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()
26602
26875
  };
26603
- logger.warn(`Schema Editor disabled: ${schemaEditorOff.message}`);
26876
+ logger.debug(`Schema Editor disabled: ${schemaEditorOff.message}`);
26604
26877
  } else throw err;
26605
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
+ };
26606
26892
  let liveSchemaSurface;
26607
26893
  let schemaEditorSurface;
26608
26894
  if (surfaces.admin) {
@@ -26611,6 +26897,7 @@ async function createRebaseBackend(options) {
26611
26897
  read: "schema:read",
26612
26898
  write: "schema:write"
26613
26899
  });
26900
+ schemaEditorRouter.use("/*", sayEditorNeedsTsMorph);
26614
26901
  schemaEditorRouter.get("/status", (c) => c.json(schemaEditorOff ? {
26615
26902
  enabled: false,
26616
26903
  reason: schemaEditorOff.message,
@@ -26632,7 +26919,7 @@ async function createRebaseBackend(options) {
26632
26919
  if (err?.code !== "ERR_MODULE_NOT_FOUND") throw err;
26633
26920
  remoteEditingOff = {
26634
26921
  code: "SCHEMA_EDITOR_MISSING_DEPENDENCY",
26635
- 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()}`
26636
26923
  };
26637
26924
  logger.warn(`Live schema editing disabled: ${remoteEditingOff.message}`);
26638
26925
  }
@@ -26653,6 +26940,7 @@ async function createRebaseBackend(options) {
26653
26940
  const liveSchemaRouter = new Hono();
26654
26941
  let undeployedCommit;
26655
26942
  applyAdminGate(liveSchemaRouter, "Live schema editing", "schema:read");
26943
+ liveSchemaRouter.use("/*", sayEditorNeedsTsMorph);
26656
26944
  const commitPaths = remoteRepo ? commitPathsFor(path.join("/", remoteRepo.collectionsPath ?? "config/collections"), "/") : collectionsDir && repositoryRoot ? commitPathsFor(collectionsDir, repositoryRoot) : void 0;
26657
26945
  if (commitPaths) logger.debug("Live schema editing commit paths relocated", { commitPaths });
26658
26946
  liveSchemaRouter.route("/", createLiveSchemaRoutes({
@@ -26869,7 +27157,7 @@ async function createRebaseBackend(options) {
26869
27157
  realtime: false,
26870
27158
  token: internalServiceKey,
26871
27159
  fetch: async (input, init) => {
26872
- return await inProcessApp().request(input, init);
27160
+ return await announcements.run(() => inProcessApp().request(input, init));
26873
27161
  }
26874
27162
  });
26875
27163
  const serviceIdentity = SERVICE_IDENTITY;
@@ -26930,9 +27218,9 @@ async function createRebaseBackend(options) {
26930
27218
  injectCallbackClient(driverResults.values(), serverClient);
26931
27219
  let functionsSurface;
26932
27220
  if ((config.functionsDir || config.functions) && surfaces.functions) {
26933
- const { functionList, readFunctionsDirectory } = await import("./function-loader-DbCSoNJj.js").then((n) => n.t);
26934
- const { createFunctionRoutes } = await import("./function-routes-DNV4RUF0.js").then((n) => n.n);
26935
- const { createFunctionsRequestTimeout, resolveFunctionsTimeoutMs } = await import("./request-timeout-CRny5KD0.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);
26936
27224
  const { functions: functionRecord, problems } = config.functions ? {
26937
27225
  functions: config.functions,
26938
27226
  problems: []
@@ -26982,7 +27270,7 @@ async function createRebaseBackend(options) {
26982
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."));
26983
27271
  }
26984
27272
  if (config.functionsUpstream && !surfaces.functions) {
26985
- const { createFunctionsProxy } = await import("./proxy-BEKbsil6.js").then((n) => n.t);
27273
+ const { createFunctionsProxy } = await import("./proxy-CH8Jsovg.js").then((n) => n.t);
26986
27274
  functionsSurface = createFunctionsProxy({ upstream: config.functionsUpstream });
26987
27275
  logger.info("Forwarding custom functions to another process", { upstream: config.functionsUpstream });
26988
27276
  }
@@ -26991,10 +27279,10 @@ async function createRebaseBackend(options) {
26991
27279
  /** How many cron jobs `start()` schedules in this process. */
26992
27280
  let cronJobsToSchedule = 0;
26993
27281
  if (surfaces.cron || (config.cronsDir || config.crons) && ownership.cronScheduler) {
26994
- const { cronJobsFrom, readCronsDirectory } = await import("./cron-loader-NHqRfKF0.js").then((n) => n.t);
26995
- const { CronScheduler } = await import("./cron-scheduler-D1DT0nw5.js").then((n) => n.n);
26996
- const { createCronRoutes } = await import("./cron-routes-Dhf82HzT.js").then((n) => n.n);
26997
- 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);
26998
27286
  const { crons: cronRecord, problems: cronProblems } = config.crons ? {
26999
27287
  crons: config.crons,
27000
27288
  problems: []
@@ -27023,7 +27311,7 @@ async function createRebaseBackend(options) {
27023
27311
  applyAdminGate(cronRouter, "Cron", {
27024
27312
  read: "cron:read",
27025
27313
  write: "cron:write"
27026
- });
27314
+ }, "cron");
27027
27315
  cronRouter.route("/", createCronRoutes(cronScheduler, cronProblems.length));
27028
27316
  cronSurface = cronRouter;
27029
27317
  if (loadedCronJobs.length > 0 && !ownership.cronScheduler) logger.info("Cron jobs registered but NOT scheduled in this process", {
@@ -27055,7 +27343,7 @@ async function createRebaseBackend(options) {
27055
27343
  }
27056
27344
  let metricsHistory;
27057
27345
  try {
27058
- const { createMetricsHistory } = await import("./history-recorder-RZ1Ow4YY.js");
27346
+ const { createMetricsHistory } = await import("./history-recorder-B2rkuhuy.js");
27059
27347
  metricsHistory = createMetricsHistory(defaultDriver);
27060
27348
  if (metricsHistory && provision.database) await metricsHistory.ensure();
27061
27349
  } catch (err) {
@@ -27064,7 +27352,7 @@ async function createRebaseBackend(options) {
27064
27352
  }
27065
27353
  let jobQueue;
27066
27354
  if (jobsOptions?.enabled) {
27067
- 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);
27068
27356
  const store = createJobStore(defaultDriver);
27069
27357
  if (store) {
27070
27358
  if (provision.database) await store.ensureTable();
@@ -27104,7 +27392,7 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27104
27392
  let logsSurface;
27105
27393
  let metaSurface;
27106
27394
  if (surfaces.admin) {
27107
- const { createBackupRoutes, backupDestinationFromEnv, readBackupSchedule } = await import("./backup-Dq0lyJ_D.js").then((n) => n.t);
27395
+ const { createBackupRoutes, backupDestinationFromEnv, readBackupSchedule } = await import("./backup-B9m3cb3X.js").then((n) => n.t);
27108
27396
  const backupRouter = new Hono();
27109
27397
  applyAdminGate(backupRouter, "Backup", "backups:read");
27110
27398
  const scheduler = cronScheduler;
@@ -27125,7 +27413,7 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27125
27413
  rlsAuditSurface = rlsAuditRouter;
27126
27414
  }
27127
27415
  if (surfaces.admin) {
27128
- const { createSqlExportRoutes } = await import("./sql-export-routes-BgTJ7K3S.js");
27416
+ const { createSqlExportRoutes } = await import("./sql-export-routes-DmB_uZ4l.js");
27129
27417
  const sqlExportRouter = new Hono();
27130
27418
  applyAdminGate(sqlExportRouter, "SQL export", "database:write");
27131
27419
  sqlExportRouter.route("/", createSqlExportRoutes({ getAdmin: () => defaultDriver.admin }));
@@ -27155,14 +27443,14 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27155
27443
  devMailSurface = devMailRouter;
27156
27444
  }
27157
27445
  if (surfaces.admin) {
27158
- const { createLogsRoutes } = await import("./logs-routes-CelTiS7C.js").then((n) => n.n);
27446
+ const { createLogsRoutes } = await import("./logs-routes-CBt7mMIL.js").then((n) => n.n);
27159
27447
  const logsRouter = new Hono();
27160
27448
  applyAdminGate(logsRouter, "Logs", "logs:read");
27161
27449
  logsRouter.route("/", createLogsRoutes({}, { closeSignal: longLivedResponses.signal }));
27162
27450
  logsSurface = logsRouter;
27163
27451
  }
27164
27452
  if (surfaces.meta) {
27165
- 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);
27166
27454
  const contractRouter = new Hono();
27167
27455
  if (adminSurfacesGated) contractRouter.use("/contract", ...adminGate("schema:read"));
27168
27456
  else {
@@ -27170,7 +27458,7 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27170
27458
  code: "CONTRACT_UNAVAILABLE",
27171
27459
  message: "The project contract is only served when authentication is configured, because it describes every table and relation in the project."
27172
27460
  } }, 404));
27173
- 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.");
27174
27462
  }
27175
27463
  contractRouter.route("/", createContractRoutes({
27176
27464
  collectionRegistry,
@@ -27231,7 +27519,8 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27231
27519
  if (surfaces[surface]) return;
27232
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.`);
27233
27521
  };
27234
- const routes = {
27522
+ /** The groups as they are built. `mountInto` and the public {@link routes} below compose from these. */
27523
+ const groups = {
27235
27524
  auth: () => {
27236
27525
  requireSurface("auth");
27237
27526
  const authGroup = group();
@@ -27280,6 +27569,25 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27280
27569
  return group(metaSurface);
27281
27570
  }
27282
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
+ };
27283
27591
  /** The base path the application last mounted at — where the SDK's socket dials by default. */
27284
27592
  let mountedBasePath;
27285
27593
  /**
@@ -27293,6 +27601,14 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27293
27601
  */
27294
27602
  const mountInto = (app, basePathOption, announce) => {
27295
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
+ }
27296
27612
  installRootErrorHandler(app);
27297
27613
  installUnmatchedApiEnvelope(app, basePath);
27298
27614
  const middlewares = configureMiddlewares(app, basePath, isProduction, announce ? config : {
@@ -27300,8 +27616,8 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27300
27616
  corsHandled: true
27301
27617
  });
27302
27618
  app.use(`${basePath}/*`, refuseUnhonouredImpersonation([`${basePath}/data`, `${basePath}/functions`]));
27303
- if (surfaces.admin) app.route(`${basePath}/admin`, routes.admin());
27304
- 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());
27305
27621
  if (jwt.isConfigured() && surfaces.auth) {
27306
27622
  app.route("/.well-known", createJwksRoutes(jwt));
27307
27623
  if (announce && jwt.hasAsymmetricSigningKey()) logger.info("Access tokens are signed asymmetrically; public keys at /.well-known/jwks.json");
@@ -27309,12 +27625,12 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27309
27625
  mountMcp(app, basePath, announce);
27310
27626
  if (schemaEditorSurface) app.route(`${basePath}/schema-editor`, deprecatedAlias(schemaEditorSurface, `${basePath}/admin/schema-editor`));
27311
27627
  if (surfaces.storage) {
27312
- app.route(`${basePath}/storage`, routes.storage());
27628
+ app.route(`${basePath}/storage`, groups.storage());
27313
27629
  if (storageCarriesUploadLimit) middlewares.exemptFromBodyLimit(`${basePath}/storage${STORAGE_UPLOAD_PATH}`);
27314
27630
  else if (announce) logger.info("Storage not configured — /storage/sources lists none, every other /storage route returns 501 STORAGE_NOT_CONFIGURED");
27315
27631
  }
27316
27632
  if (surfaces.data) {
27317
- app.route(`${basePath}/data`, routes.data());
27633
+ app.route(`${basePath}/data`, groups.data());
27318
27634
  if (announce && activeCollections.length === 0) logger.info("No collections to serve — /data returns 404 NO_COLLECTIONS");
27319
27635
  mountOpenApiDocs(app, basePath, {
27320
27636
  enableSwagger: config.enableSwagger,
@@ -27327,15 +27643,15 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27327
27643
  });
27328
27644
  }
27329
27645
  if (functionsSurface) {
27330
- app.route(`${basePath}/functions`, routes.functions());
27646
+ app.route(`${basePath}/functions`, groups.functions());
27331
27647
  if (announce) logger.debug("Function routes mounted", { path: `${basePath}/functions` });
27332
27648
  }
27333
27649
  if (surfaces.cron && cronSurface) {
27334
- app.route(`${basePath}/admin/cron`, routes.cron());
27650
+ app.route(`${basePath}/admin/cron`, groups.cron());
27335
27651
  app.route(`${basePath}/cron`, deprecatedAlias(cronSurface, `${basePath}/admin/cron`));
27336
27652
  }
27337
27653
  if (logsSurface) app.route(`${basePath}/logs`, deprecatedAlias(logsSurface, `${basePath}/admin/logs`));
27338
- if (surfaces.meta && metaSurface) app.route(`${basePath}/meta`, routes.meta());
27654
+ if (surfaces.meta && metaSurface) app.route(`${basePath}/meta`, groups.meta());
27339
27655
  if (announce) logger.debug("Rebase backend mounted", { basePath });
27340
27656
  };
27341
27657
  let inProcess;
@@ -27348,6 +27664,7 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27348
27664
  };
27349
27665
  let realtimeAttached = false;
27350
27666
  const attachRealtime = async (server, attachOptions = {}) => {
27667
+ announcements.serve();
27351
27668
  if (!surfaces.realtime || !defaultBootstrapper.initializeWebsockets || !effectiveRealtimeService) return;
27352
27669
  if (realtimeAttached) throw new Error("attachRealtime(): this backend's websocket is already attached to a server.");
27353
27670
  realtimeAttached = true;
@@ -27365,6 +27682,7 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27365
27682
  let authTokenSweep;
27366
27683
  let historyRetentionSweep;
27367
27684
  const start = () => {
27685
+ announcements.serve();
27368
27686
  if (started) return;
27369
27687
  started = true;
27370
27688
  runInBackendScope(ownScope, startTimers);
@@ -27406,7 +27724,7 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27406
27724
  persistClaims: config.cronPersistence !== false
27407
27725
  });
27408
27726
  };
27409
- const shutdown = (timeoutMs, shutdownOptions = {}) => createShutdown({
27727
+ const shutdown = (timeoutMs, shutdownOptions = {}) => announcements.run(() => createShutdown({
27410
27728
  server: shutdownOptions.server,
27411
27729
  cronScheduler,
27412
27730
  jobQueue,
@@ -27417,7 +27735,7 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27417
27735
  realtimeServices,
27418
27736
  closeLongLivedResponses: () => longLivedResponses.abort(),
27419
27737
  emailService
27420
- })(timeoutMs);
27738
+ })(timeoutMs));
27421
27739
  logger.debug("Rebase Backend Initialized");
27422
27740
  return {
27423
27741
  rebase: serverSingleton,
@@ -27436,7 +27754,7 @@ Use a driver that supports the job queue, or remove the declarations. Refusing t
27436
27754
  cronScheduler,
27437
27755
  jobQueue,
27438
27756
  routes,
27439
- fetch: async (request) => await inProcessApp().fetch(request),
27757
+ fetch: async (request) => await announcements.run(() => inProcessApp().fetch(request)),
27440
27758
  mount: (app, mountOptions = {}) => {
27441
27759
  mountInto(app, mountOptions.basePath, true);
27442
27760
  mountedBasePath = mountOptions.basePath || "/api";
@@ -29121,14 +29439,14 @@ function listenWithPortRetry(server, startPort, options) {
29121
29439
  const onListening = () => {
29122
29440
  cleanup();
29123
29441
  const bound = boundPort(server) ?? port;
29124
- if (portFileDir) {
29125
- try {
29126
- const portFile = path$1.join(portFileDir, DEV_PORT_FILENAME);
29127
- fs.writeFileSync(portFile, `${bound} ${startPort}`, "utf-8");
29128
- } catch {}
29129
- writeStateFile(portFileDir, bound, options?.serviceKey);
29442
+ if (!portFileDir) {
29443
+ resolve(bound);
29444
+ return;
29130
29445
  }
29131
- resolve(bound);
29446
+ heldByAnotherServer(portFileDir, bound).catch(() => false).then((held) => {
29447
+ if (!held) writeDevFiles(portFileDir, bound, startPort, options?.serviceKey);
29448
+ resolve(bound);
29449
+ });
29132
29450
  };
29133
29451
  /**
29134
29452
  * Say which port was refused and which one is next.
@@ -29174,18 +29492,116 @@ function listenWithPortRetry(server, startPort, options) {
29174
29492
  });
29175
29493
  }
29176
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
+ /**
29177
29583
  * Clean up the dev port file and state file (call on graceful shutdown).
29178
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
+ *
29179
29590
  * @internal Not part of the stable public API. See {@link listenWithPortRetry}.
29180
29591
  */
29181
29592
  function cleanupDevPortFile(dir) {
29182
- try {
29183
- const portFile = path$1.join(dir, DEV_PORT_FILENAME);
29184
- 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));
29185
29597
  } catch {}
29598
+ const key = path$1.resolve(dir);
29599
+ const wrote = writtenPortFiles.get(key);
29600
+ if (wrote === void 0 || !(ownsState || owner === null)) return;
29186
29601
  try {
29187
- const stateFile = path$1.join(dir, ".rebase", "state.json");
29188
- 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);
29189
29605
  } catch {}
29190
29606
  }
29191
29607
  /**