@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.
- package/dist/{GCSStorageController-CAKa_EFW.js → GCSStorageController-CRd9bFrc.js} +3 -3
- package/dist/{GCSStorageController-CAKa_EFW.js.map → GCSStorageController-CRd9bFrc.js.map} +1 -1
- package/dist/{S3StorageController-Kt-LJpf3.js → S3StorageController-D00EFa5P.js} +3 -3
- package/dist/{S3StorageController-Kt-LJpf3.js.map → S3StorageController-D00EFa5P.js.map} +1 -1
- package/dist/api/closed-database.d.ts +25 -0
- package/dist/api/errors.d.ts +6 -10
- package/dist/{auth-xk37vrHO.js → auth-Dkvo2tTJ.js} +43 -35
- package/dist/auth-Dkvo2tTJ.js.map +1 -0
- package/dist/backend/boot-announcements.d.ts +13 -0
- package/dist/backend/create-backend.d.ts +4 -0
- package/dist/{backup-CpaJWXSc.js → backup-B9m3cb3X.js} +7 -7
- package/dist/{backup-CpaJWXSc.js.map → backup-B9m3cb3X.js.map} +1 -1
- package/dist/boot/boot.d.ts +10 -0
- package/dist/{contract-routes-DiFwiLg2.js → contract-routes-CsFXW7j4.js} +2 -2
- package/dist/{contract-routes-DiFwiLg2.js.map → contract-routes-CsFXW7j4.js.map} +1 -1
- package/dist/{cron-loader-NHqRfKF0.js → cron-loader-Dj6lA4HQ.js} +2 -2
- package/dist/{cron-loader-NHqRfKF0.js.map → cron-loader-Dj6lA4HQ.js.map} +1 -1
- package/dist/{cron-routes-CPIhdasM.js → cron-routes-CwbLZguO.js} +5 -5
- package/dist/{cron-routes-CPIhdasM.js.map → cron-routes-CwbLZguO.js.map} +1 -1
- package/dist/{cron-scheduler-D1DT0nw5.js → cron-scheduler-B04ACoUE.js} +2 -2
- package/dist/{cron-scheduler-D1DT0nw5.js.map → cron-scheduler-B04ACoUE.js.map} +1 -1
- package/dist/{cron-store-DEpVstUU.js → cron-store-DkyfuLOH.js} +3 -3
- package/dist/{cron-store-DEpVstUU.js.map → cron-store-DkyfuLOH.js.map} +1 -1
- package/dist/{ddl-bootstrap-BNqi2NZe.js → ddl-bootstrap-Ck20GSIX.js} +2 -2
- package/dist/{ddl-bootstrap-BNqi2NZe.js.map → ddl-bootstrap-Ck20GSIX.js.map} +1 -1
- package/dist/{errors-CHSJYpoy.js → errors-OMWkKSH-.js} +76 -22
- package/dist/errors-OMWkKSH-.js.map +1 -0
- package/dist/{function-loader-DbCSoNJj.js → function-loader-KKDqTcIr.js} +2 -2
- package/dist/{function-loader-DbCSoNJj.js.map → function-loader-KKDqTcIr.js.map} +1 -1
- package/dist/{function-routes-C0emkHtX.js → function-routes-RxrCHNuM.js} +2 -2
- package/dist/{function-routes-C0emkHtX.js.map → function-routes-RxrCHNuM.js.map} +1 -1
- package/dist/functions/index.js +18 -1
- package/dist/functions/index.js.map +1 -1
- package/dist/{history-recorder-RZ1Ow4YY.js → history-recorder-B2rkuhuy.js} +2 -2
- package/dist/{history-recorder-RZ1Ow4YY.js.map → history-recorder-B2rkuhuy.js.map} +1 -1
- package/dist/index.es.js +632 -129
- package/dist/index.es.js.map +1 -1
- package/dist/{jobs-8G-_agld.js → jobs-h1WRq3cp.js} +3 -3
- package/dist/{jobs-8G-_agld.js.map → jobs-h1WRq3cp.js.map} +1 -1
- package/dist/{jwt-YhciR93H.js → jwt-D3oFauZI.js} +2 -2
- package/dist/{jwt-YhciR93H.js.map → jwt-D3oFauZI.js.map} +1 -1
- package/dist/{logger-Bt32sHFw.js → logger-CW8pmTS6.js} +35 -3
- package/dist/logger-CW8pmTS6.js.map +1 -0
- package/dist/{logs-routes-BM2cDory.js → logs-routes-CBt7mMIL.js} +3 -3
- package/dist/{logs-routes-BM2cDory.js.map → logs-routes-CBt7mMIL.js.map} +1 -1
- package/dist/{openapi-generator-IKwDAQGO.js → openapi-generator-BjHr6upl.js} +2 -2
- package/dist/{openapi-generator-IKwDAQGO.js.map → openapi-generator-BjHr6upl.js.map} +1 -1
- package/dist/{proxy-BEKbsil6.js → proxy-CH8Jsovg.js} +2 -2
- package/dist/{proxy-BEKbsil6.js.map → proxy-CH8Jsovg.js.map} +1 -1
- package/dist/{query-parser-BnhugNwZ.js → query-parser-otFZqJsC.js} +3 -3
- package/dist/{query-parser-BnhugNwZ.js.map → query-parser-otFZqJsC.js.map} +1 -1
- package/dist/{request-timeout-LiGpCCP1.js → request-timeout-59crS-9u.js} +3 -3
- package/dist/{request-timeout-LiGpCCP1.js.map → request-timeout-59crS-9u.js.map} +1 -1
- package/dist/{schema-editor-routes-zjVKreVQ.js → schema-editor-routes-Bsut0bCC.js} +2 -2
- package/dist/{schema-editor-routes-zjVKreVQ.js.map → schema-editor-routes-Bsut0bCC.js.map} +1 -1
- package/dist/{soft-delete-params-uHxJoETb.js → soft-delete-params-BmlVwecC.js} +2 -2
- package/dist/{soft-delete-params-uHxJoETb.js.map → soft-delete-params-BmlVwecC.js.map} +1 -1
- package/dist/{sql-export-routes-D86u7N9i.js → sql-export-routes-DmB_uZ4l.js} +3 -3
- package/dist/{sql-export-routes-D86u7N9i.js.map → sql-export-routes-DmB_uZ4l.js.map} +1 -1
- package/dist/{upload-validation-pXcDy_EQ.js → upload-validation-Bgo626uY.js} +2 -2
- package/dist/{upload-validation-pXcDy_EQ.js.map → upload-validation-Bgo626uY.js.map} +1 -1
- package/dist/utils/dev-dependency-command.d.ts +15 -0
- package/dist/utils/dev-port.d.ts +8 -0
- package/dist/utils/logger.d.ts +24 -0
- package/package.json +5 -5
- package/dist/auth-xk37vrHO.js.map +0 -1
- package/dist/errors-CHSJYpoy.js.map +0 -1
- 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-
|
|
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-
|
|
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-
|
|
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-
|
|
17
|
-
import { a as
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
29
|
-
import { t as S3StorageController } from "./S3StorageController-
|
|
30
|
-
import { t as GCSStorageController } from "./GCSStorageController-
|
|
31
|
-
import { a as isDuplicateObjectRace, i as isConcurrentDdlRace, n as createDdlBootstrapper, t as CONCURRENT_DDL_SQLSTATES } from "./ddl-bootstrap-
|
|
32
|
-
import { t as createCronStore } from "./cron-store-
|
|
33
|
-
import { i as loadFunctionsWithDiagnostics, n as loadFunctions, r as loadFunctionsFromDirectory } from "./function-loader-
|
|
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-
|
|
35
|
-
import "./proxy-
|
|
36
|
-
import "./request-timeout-
|
|
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-
|
|
39
|
-
import { t as createCronRoutes } from "./cron-routes-
|
|
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-
|
|
41
|
-
import { a as defaultBackoff, i as createJobQueue, n as createJobStore, o as currentAmbientTransaction, r as PermanentJobError } from "./jobs-
|
|
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
|
-
|
|
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)
|
|
7094
|
+
if (this.ws) connectionDebug("WebSocket auto-auth skipped:", e?.message || e);
|
|
7011
7095
|
});
|
|
7012
7096
|
}).catch((e) => {
|
|
7013
|
-
if (this.ws)
|
|
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
|
-
|
|
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
|
-
|
|
7073
|
-
|
|
7074
|
-
|
|
7075
|
-
|
|
7076
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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++)
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
throw error;
|
|
7507
|
-
|
|
7508
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
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 ??
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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-
|
|
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.
|
|
26874
|
+
message: "The schema editor needs `ts-morph`, which is not installed on this server. " + tsMorphInstallSentence()
|
|
26561
26875
|
};
|
|
26562
|
-
logger.
|
|
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:
|
|
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-
|
|
26893
|
-
const { createFunctionRoutes } = await import("./function-routes-
|
|
26894
|
-
const { createFunctionsRequestTimeout, resolveFunctionsTimeoutMs } = await import("./request-timeout-
|
|
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-
|
|
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-
|
|
26954
|
-
const { CronScheduler } = await import("./cron-scheduler-
|
|
26955
|
-
const { createCronRoutes } = await import("./cron-routes-
|
|
26956
|
-
const { createCronStore } = await import("./cron-store-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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`,
|
|
27263
|
-
if (surfaces.auth) app.route(`${basePath}/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`,
|
|
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`,
|
|
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`,
|
|
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`,
|
|
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`,
|
|
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
|
-
|
|
29085
|
-
|
|
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
|
-
|
|
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
|
-
|
|
29142
|
-
|
|
29143
|
-
|
|
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
|
|
29147
|
-
if (fs.
|
|
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
|
|
29591
|
-
*
|
|
29592
|
-
*
|
|
29593
|
-
* `vendor-analytics.js`
|
|
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
|
-
|
|
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 =
|
|
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
|