@rebasepro/server 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/api-keys/api-key-middleware.d.ts +1 -1
- package/dist/auth/jwt.d.ts +1 -1
- package/dist/auth/rls-scope.d.ts +1 -1
- package/dist/{auth-DIKS1rsI.js → auth-5Et5mnUA.js} +141 -32
- package/dist/auth-5Et5mnUA.js.map +1 -0
- package/dist/boot/boot.d.ts +17 -1
- package/dist/boot/env.d.ts +1 -0
- package/dist/boot/provision.d.ts +1 -1
- package/dist/boot/schema-stamp.d.ts +173 -0
- package/dist/boot/version-skew.d.ts +27 -3
- package/dist/{contract-routes-DDNj4_J1.js → contract-routes-DZ-LBpSL.js} +2 -2
- package/dist/contract-routes-DZ-LBpSL.js.map +1 -0
- package/dist/cron/define-cron.d.ts +1 -1
- package/dist/{cron-store-BBGvOA-9.js → cron-store-DfH_4Cd9.js} +2 -2
- package/dist/{cron-store-BBGvOA-9.js.map → cron-store-DfH_4Cd9.js.map} +1 -1
- package/dist/ddl-bootstrap-Cywoj8Ta.js.map +1 -1
- package/dist/email/index.d.ts +1 -0
- package/dist/email/templates.d.ts +21 -5
- package/dist/email/types.d.ts +14 -0
- package/dist/functions/define-function.d.ts +1 -1
- package/dist/index.es.js +386 -35
- package/dist/index.es.js.map +1 -1
- package/dist/init/surfaces.d.ts +8 -1
- package/dist/{jobs-BOEOIGAm.js → jobs-CyOKXXlu.js} +2 -2
- package/dist/{jobs-BOEOIGAm.js.map → jobs-CyOKXXlu.js.map} +1 -1
- package/dist/jwt-DxH9fLPt.js.map +1 -1
- package/dist/{openapi-generator-DLiiGD9X.js → openapi-generator-BCKJRUS4.js} +2 -2
- package/dist/{openapi-generator-DLiiGD9X.js.map → openapi-generator-BCKJRUS4.js.map} +1 -1
- package/dist/singleton.d.ts +1 -1
- package/dist/{src-B-E7RjdN.js → src-BPfYOeN4.js} +3 -3
- package/dist/src-BPfYOeN4.js.map +1 -0
- package/dist/src-CrCxd8km.js.map +1 -1
- package/package.json +5 -5
- package/dist/auth-DIKS1rsI.js.map +0 -1
- package/dist/contract-routes-DDNj4_J1.js.map +0 -1
- package/dist/src-B-E7RjdN.js.map +0 -1
package/dist/index.es.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
import process from "process";
|
|
3
3
|
__createRequire(import.meta.url);
|
|
4
|
-
import { A as GeoPoint, M as RebaseApiError, N as RebaseClientError, O as EntityReference, S as toSnakeCase, _ as fieldKeyForColumn, b as buildCompositeId, c as collectAllPages, d as normalizeOrderBy, f as serializeOrderBy, g as getEffectiveSecurityRules, h as resolveDataSource, j as Vector, k as EntityRelation, l as paginateFind, m as createDataSourceRegistry, n as buildSdkData, o as serializeFilter, p as CollectionRegistry, s as serializeLogicalCondition, t as buildRoutedRebaseData, u as resolveFindWindow, v as findRelation, x as resolvePrimaryKeys, y as resolveCollectionRelations } from "./src-
|
|
4
|
+
import { A as GeoPoint, M as RebaseApiError, N as RebaseClientError, O as EntityReference, S as toSnakeCase, _ as fieldKeyForColumn, b as buildCompositeId, c as collectAllPages, d as normalizeOrderBy, f as serializeOrderBy, g as getEffectiveSecurityRules, h as resolveDataSource, j as Vector, k as EntityRelation, l as paginateFind, m as createDataSourceRegistry, n as buildSdkData, o as serializeFilter, p as CollectionRegistry, s as serializeLogicalCondition, t as buildRoutedRebaseData, u as resolveFindWindow, v as findRelation, x as resolvePrimaryKeys, y as resolveCollectionRelations } from "./src-BPfYOeN4.js";
|
|
5
5
|
import { c as DEFAULT_DATA_SOURCE_KEY, g as toCanonicalOp, i as getCollectionDataPath, m as parseRelationAggregateSort, n as RLS_ROLES_SQL, o as isPostgresCollectionConfig, r as RLS_UID_SQL, t as RLS_JWT_SQL } from "./src-CrCxd8km.js";
|
|
6
6
|
import { n as ADMIN_PROPERTY_KEYS, t as ADMIN_COLLECTION_KEYS } from "./admin_block-DLILvzle.js";
|
|
7
7
|
import { a as isDuplicateObjectRace, i as isConcurrentDdlRace, n as createDdlBootstrapper, o as revokeInternalTableSql, s as isSQLAdmin, t as CONCURRENT_DDL_SQLSTATES } from "./ddl-bootstrap-Cywoj8Ta.js";
|
|
8
8
|
import { C as canonicalStorageKey, D as findStorageSuffixCollision, E as DEFAULT_STORAGE_SOURCE_KEY, O as normalizeStorageSources, S as canonicalStorageId, b as InvalidStorageKeyError, c as getJwks, d as hasAsymmetricSigningKey, i as generateDownloadToken, k as storageEnvSuffix, n as configureJwt, p as isJwtConfigured, v as normalizePemFromEnv, x as canonicalStorageBucket, y as InvalidStorageBucketError } from "./jwt-DxH9fLPt.js";
|
|
9
|
-
import { n as createContractRoutes } from "./contract-routes-
|
|
10
|
-
import { $ as
|
|
9
|
+
import { n as createContractRoutes, r as computeSchemaVersion } from "./contract-routes-DZ-LBpSL.js";
|
|
10
|
+
import { $ as createAdapterAuthMiddleware, A as _coercedNumber, B as validatePasswordStrength, C as providerVerifiedEmail, Ct as parseQueryOptions, D as _enum, E as ZodNumber, Et as isPublicStoragePath, F as createEmailService, G as getPasswordResetTemplate, H as generateSecurePassword, I as assertEmailLinkBases, J as resolveEmailBranding, K as getUserInvitationTemplate, L as resolveEmailLinkBase, M as createDataRateLimiter, N as MemoryRateLimitStore, O as object, P as SMTPEmailService, Q as raw, R as resolveAuthHooks, S as pkceTokenParams, St as parseGroupBy, T as createBuiltinAuthAdapter, Tt as PUBLIC_STORAGE_PREFIX, U as getEmailVerificationTemplate, V as verifyPassword, W as getMagicLinkTemplate, X as escapeHtml, Y as RawHtml, Z as html, _ as verifyOidcIdToken, _t as scopeDataDriver, a as resolveRateLimitStoreKind, at as publicObjectAuth, b as createGoogleProvider, bt as orderByEntriesToTuples, c as createSlackProvider, ct as requireAuth, d as createDiscordProvider, dt as createStorageApiKeyGuard, et as createAuthMiddleware, f as createTwitterProvider, ft as isApiKeyToken, g as tryVerifyOidcIdToken, gt as SERVICE_IDENTITY, h as createMicrosoftProvider, ht as safeCompare, i as createApiKeyStore, it as optionalAuth, j as DEFAULT_FUNCTIONS_ANONYMOUS_LIMIT, k as string, l as createBitbucketProvider, lt as createApiKeyPreAuth, m as createAppleProvider, mt as extractBearerToken, n as createCustomAuthAdapter, nt as extractUserFromToken, o as createSqlRateLimitStore, ot as queryTokenAuth, p as createFacebookProvider, pt as validateApiKey, q as getWelcomeEmailTemplate, r as createApiKeyRoutes, rt as fileTokenAuth, s as createSpotifyProvider, st as requireAdmin, tt as createRequireAuth, u as createGitLabProvider, ut as createFunctionApiKeyGuard, v as createGitHubProvider, vt as httpMethodToOperation, w as createJwksRoutes, wt as resolveListLimitParam, x as oauthCodeFlowSchema, xt as parseAggregateSelect, y as createLinkedinProvider, yt as isOperationAllowed, z as hashPassword } from "./auth-5Et5mnUA.js";
|
|
11
11
|
import { t as logger } from "./logger-DfvF_8r-.js";
|
|
12
12
|
import { n as errorHandler, t as ApiError } from "./errors-EBYiaJ2E.js";
|
|
13
13
|
import { t as logMiddleware } from "./logs-routes-CWBLQj2l.js";
|
|
@@ -19,9 +19,9 @@ import { t as createFunctionRoutes } from "./function-routes-Btcez1T-.js";
|
|
|
19
19
|
import { n as loadCronJobsFromDirectory, r as loadCronJobsWithDiagnostics } from "./cron-loader-YhhQeVBM.js";
|
|
20
20
|
import { r as validateCronExpression, t as CronScheduler } from "./cron-scheduler-DIpYBmZP.js";
|
|
21
21
|
import { t as createCronRoutes } from "./cron-routes-maM_RlUu.js";
|
|
22
|
-
import { t as createCronStore } from "./cron-store-
|
|
22
|
+
import { t as createCronStore } from "./cron-store-DfH_4Cd9.js";
|
|
23
23
|
import { a as parseBackupTimestamp, i as parseBackupDestination, n as createBackupRoutes, o as readBackupBytes, r as listBackupObjects } from "./backup-C6ljYVTp.js";
|
|
24
|
-
import { i as createJobStore, n as createJobQueue, r as defaultBackoff } from "./jobs-
|
|
24
|
+
import { i as createJobStore, n as createJobQueue, r as defaultBackoff } from "./jobs-CyOKXXlu.js";
|
|
25
25
|
import { t as FunctionSelectionError } from "./selection-_z6TM1DB.js";
|
|
26
26
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
27
27
|
import * as fs$2 from "fs";
|
|
@@ -607,22 +607,56 @@ function compareVersions(a, b) {
|
|
|
607
607
|
return left.prerelease < right.prerelease ? -1 : 1;
|
|
608
608
|
}
|
|
609
609
|
/**
|
|
610
|
+
* Does `version` lead `against` by at least a minor?
|
|
611
|
+
*
|
|
612
|
+
* Compares the `(major, minor)` pair and ignores the patch, which is the whole
|
|
613
|
+
* point: it separates "pinned one fix ahead", a supported and deliberate thing,
|
|
614
|
+
* from "built against a framework this image predates". Pre-1.0 the minor is the
|
|
615
|
+
* breaking-change axis, so the same tuple comparison is right on both sides of
|
|
616
|
+
* 1.0 without a special case.
|
|
617
|
+
*
|
|
618
|
+
* `undefined` when either version is unparseable, so the caller declines to
|
|
619
|
+
* judge rather than guessing.
|
|
620
|
+
*/
|
|
621
|
+
function leadsByMinorOrMore(version, against) {
|
|
622
|
+
const left = parseVersion(version);
|
|
623
|
+
const right = parseVersion(against);
|
|
624
|
+
if (!left || !right) return void 0;
|
|
625
|
+
if (left.parts[0] !== right.parts[0]) return left.parts[0] > right.parts[0];
|
|
626
|
+
return left.parts[1] > right.parts[1];
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
610
629
|
* Describe how a driver's version relates to the runtime asking it for work.
|
|
611
630
|
*
|
|
612
|
-
*
|
|
613
|
-
*
|
|
614
|
-
*
|
|
631
|
+
* Both directions are reportable, for opposite reasons. A driver **behind** the
|
|
632
|
+
* runtime lacks capabilities the runtime will ask for, and reports their absence
|
|
633
|
+
* in language indistinguishable from a driver that never had them. A driver
|
|
634
|
+
* **ahead** by a minor carries half of a feature whose other half lives in
|
|
635
|
+
* `@rebasepro/server` — and the image supplies that half, so the bundle's code
|
|
636
|
+
* runs against a harness that does not know about it.
|
|
637
|
+
*
|
|
638
|
+
* Returns both flags false whenever it cannot tell: an unknown version, an
|
|
639
|
+
* unparseable one, or a gap too small to mean anything. Silence beats a
|
|
640
|
+
* confident wrong diagnosis, and the caller still prints its own local fact.
|
|
615
641
|
*/
|
|
616
642
|
function describeDriverSkew(driverVersion, runtimeVersion) {
|
|
617
|
-
if (!driverVersion || !runtimeVersion) return {
|
|
643
|
+
if (!driverVersion || !runtimeVersion) return {
|
|
644
|
+
stale: false,
|
|
645
|
+
ahead: false
|
|
646
|
+
};
|
|
618
647
|
const order = compareVersions(driverVersion, runtimeVersion);
|
|
619
|
-
if (order === void 0) return {
|
|
648
|
+
if (order === void 0) return {
|
|
649
|
+
stale: false,
|
|
650
|
+
ahead: false
|
|
651
|
+
};
|
|
620
652
|
if (order < 0) return {
|
|
621
653
|
stale: true,
|
|
654
|
+
ahead: false,
|
|
622
655
|
detail: `The driver is at ${driverVersion} while this runtime is ${runtimeVersion} — the driver is OLDER, so this is very likely a stale pin rather than a driver that never had the capability.`
|
|
623
656
|
};
|
|
624
657
|
return {
|
|
625
658
|
stale: false,
|
|
659
|
+
ahead: leadsByMinorOrMore(driverVersion, runtimeVersion) === true,
|
|
626
660
|
detail: `Driver ${driverVersion}, runtime ${runtimeVersion}.`
|
|
627
661
|
};
|
|
628
662
|
}
|
|
@@ -866,6 +900,198 @@ function provisionTargetFor(bootstrappers, adapter, driverResult) {
|
|
|
866
900
|
};
|
|
867
901
|
}
|
|
868
902
|
//#endregion
|
|
903
|
+
//#region src/boot/schema-stamp.ts
|
|
904
|
+
/**
|
|
905
|
+
* Does the schema in the database match the collections this process was built
|
|
906
|
+
* from?
|
|
907
|
+
*
|
|
908
|
+
* A split deployment can run several processes from one bundle, and — once a
|
|
909
|
+
* deployment can pin them to *different* bundles — from more than one. They all
|
|
910
|
+
* share one database, and only one of them provisions it. So a process can come
|
|
911
|
+
* up perfectly, pass every probe, and query a column the schema owner has not
|
|
912
|
+
* created yet, or rely on a policy it has not applied. Neither fails in a way
|
|
913
|
+
* anybody sees: the first is a SQL error on one route, the second is a 200 with
|
|
914
|
+
* zero rows.
|
|
915
|
+
*
|
|
916
|
+
* The check is a stamp. The process that provisions writes the version of the
|
|
917
|
+
* collections it just applied into the database; every process compares its own
|
|
918
|
+
* against what it finds. Same table, same shape and the same "unstamped is not
|
|
919
|
+
* an error" rule as the auth schema version, which exists for exactly this class
|
|
920
|
+
* of failure one subsystem over.
|
|
921
|
+
*
|
|
922
|
+
* ## Why the database and not the api
|
|
923
|
+
*
|
|
924
|
+
* The obvious design is an HTTP call to the api's `/api/meta/schema-version`.
|
|
925
|
+
* It is worse in three ways. It needs the api's address configured on every
|
|
926
|
+
* other process, so it adds a variable whose absence disables the check
|
|
927
|
+
* silently. It makes booting depend on another process already being up, which
|
|
928
|
+
* turns a rollout ordering problem into a boot failure. And it asks the wrong
|
|
929
|
+
* question: what breaks is the *database* not matching, and two processes can
|
|
930
|
+
* agree with each other while both disagree with it.
|
|
931
|
+
*
|
|
932
|
+
* The database is also the only answer that works for a `worker`, which has no
|
|
933
|
+
* reason to know any URL, and for a single `all` deployment scaled to three,
|
|
934
|
+
* where there is no api to ask.
|
|
935
|
+
*
|
|
936
|
+
* ## Why both sides compute rather than read
|
|
937
|
+
*
|
|
938
|
+
* Both the stamp and the comparison are computed from the collections the
|
|
939
|
+
* process actually loaded, never from the `schemaVersion` a bundle manifest
|
|
940
|
+
* declares. A declared value is not evidence: `/api/meta/schema-version` returns
|
|
941
|
+
* the manifest's own number verbatim when the bundle has one, which is why
|
|
942
|
+
* comparing that endpoint to that manifest is a check that passes on a bundle
|
|
943
|
+
* whose declared version is nonsense — verified, once, on a fixture corrupted to
|
|
944
|
+
* `v1:deadbeefdeadbeef`. Computing on both sides means the comparison is between
|
|
945
|
+
* two things that were derived, so agreeing means something.
|
|
946
|
+
*
|
|
947
|
+
* ## A driver that predates this
|
|
948
|
+
*
|
|
949
|
+
* The image supplies `@rebasepro/server`; the *driver* comes from the bundle,
|
|
950
|
+
* pinned to whatever the project's package.json asked npm for. So a current
|
|
951
|
+
* runtime routinely runs against a driver older than itself, and that driver has
|
|
952
|
+
* neither hook. Absence is treated as "this driver does not record a version" —
|
|
953
|
+
* the same answer as a driver that never will — and the check silently does
|
|
954
|
+
* nothing rather than failing a boot over a capability the deployment cannot
|
|
955
|
+
* supply. It starts working when the project's driver is next updated. See
|
|
956
|
+
* `version-skew.ts` for the general shape of this problem.
|
|
957
|
+
*/
|
|
958
|
+
/**
|
|
959
|
+
* Compare, without acting.
|
|
960
|
+
*
|
|
961
|
+
* Separated from {@link enforceSchemaStamp} because the decision is the part
|
|
962
|
+
* worth testing and the part that is wrong in a way nobody sees. Every branch
|
|
963
|
+
* here has a test.
|
|
964
|
+
*/
|
|
965
|
+
function decideSchemaStamp(input) {
|
|
966
|
+
if (input.supported === false) return {
|
|
967
|
+
status: "skipped",
|
|
968
|
+
reason: "this driver does not record a collections schema version"
|
|
969
|
+
};
|
|
970
|
+
if (input.introspecting) return {
|
|
971
|
+
status: "skipped",
|
|
972
|
+
reason: "collections are derived from the database, so they cannot disagree with it"
|
|
973
|
+
};
|
|
974
|
+
if (input.databaseVersion === null) return { status: "unstamped" };
|
|
975
|
+
if (input.databaseVersion === input.processVersion) return {
|
|
976
|
+
status: "match",
|
|
977
|
+
version: input.processVersion
|
|
978
|
+
};
|
|
979
|
+
return {
|
|
980
|
+
status: "mismatch",
|
|
981
|
+
database: input.databaseVersion,
|
|
982
|
+
process: input.processVersion
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
/**
|
|
986
|
+
* A refusal to serve against a schema this process does not match.
|
|
987
|
+
*
|
|
988
|
+
* Its own class so the boot paths can present it as a configuration problem
|
|
989
|
+
* rather than a crash, matching `RoleConfigurationError`.
|
|
990
|
+
*/
|
|
991
|
+
var SchemaVersionMismatchError = class extends Error {
|
|
992
|
+
databaseVersion;
|
|
993
|
+
processVersion;
|
|
994
|
+
constructor(databaseVersion, processVersion, message) {
|
|
995
|
+
super(message);
|
|
996
|
+
this.databaseVersion = databaseVersion;
|
|
997
|
+
this.processVersion = processVersion;
|
|
998
|
+
this.name = "SchemaVersionMismatchError";
|
|
999
|
+
}
|
|
1000
|
+
};
|
|
1001
|
+
/**
|
|
1002
|
+
* Read the policy from the environment.
|
|
1003
|
+
*
|
|
1004
|
+
* Warning is the default because refusing would fail a rollout *mid-flight*: for
|
|
1005
|
+
* the whole window between the schema owner rolling and the last unit following,
|
|
1006
|
+
* the units that have not rolled yet legitimately disagree. That window is
|
|
1007
|
+
* normal, and a deployment that turns it into a crash loop has traded a silent
|
|
1008
|
+
* problem for a loud outage.
|
|
1009
|
+
*
|
|
1010
|
+
* `refuse` is for the deployment that would rather not serve at all than serve
|
|
1011
|
+
* wrong — and for CI, where the window does not exist.
|
|
1012
|
+
*/
|
|
1013
|
+
function resolveSchemaMismatchPolicy(env = process.env) {
|
|
1014
|
+
const raw = (env.REBASE_REQUIRE_SCHEMA_MATCH ?? "").trim().toLowerCase();
|
|
1015
|
+
return raw === "true" || raw === "1" ? "refuse" : "warn";
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* The message a mismatch prints, or throws.
|
|
1019
|
+
*
|
|
1020
|
+
* One function so the warning and the refusal say exactly the same thing — an
|
|
1021
|
+
* operator who raises the policy from `warn` to `refuse` should not have to
|
|
1022
|
+
* learn a second description of the same fact.
|
|
1023
|
+
*/
|
|
1024
|
+
function describeMismatch(databaseVersion, processVersion) {
|
|
1025
|
+
return `The database was last provisioned from a different set of collections than this process was built from (database ${databaseVersion}, this process ${processVersion}).\n A process running ahead of the schema queries columns that do not exist yet, and relies on RLS policies that have not been applied —\n the first is a SQL error on one route, the second is an empty result with a 200. A process running behind is normal during a rollout.\n Roll the process that owns the schema first (REBASE_ROLE=api, or the migration Job), then the rest. Set REBASE_REQUIRE_SCHEMA_MATCH=true to refuse the boot instead of warning.`;
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Stamp, or compare and report.
|
|
1029
|
+
*
|
|
1030
|
+
* The provisioning process writes; everyone else reads. A process that both
|
|
1031
|
+
* provisions and would compare has nothing to compare against — it is the
|
|
1032
|
+
* authority — so it stamps and returns.
|
|
1033
|
+
*
|
|
1034
|
+
* Every failure to read or write is swallowed with a warning rather than
|
|
1035
|
+
* propagated. This check exists to make a silent problem visible; letting it
|
|
1036
|
+
* take down a boot it was only supposed to describe would be a strictly worse
|
|
1037
|
+
* trade, and a database that refuses one small write is already telling the
|
|
1038
|
+
* operator something through other channels.
|
|
1039
|
+
*/
|
|
1040
|
+
async function enforceSchemaStamp(options) {
|
|
1041
|
+
const { collections, read, stamp, provisioned, introspecting } = options;
|
|
1042
|
+
if (introspecting) return {
|
|
1043
|
+
status: "skipped",
|
|
1044
|
+
reason: "collections are derived from the database, so they cannot disagree with it"
|
|
1045
|
+
};
|
|
1046
|
+
if (!read && !stamp) return {
|
|
1047
|
+
status: "skipped",
|
|
1048
|
+
reason: "this driver does not record a collections schema version"
|
|
1049
|
+
};
|
|
1050
|
+
const processVersion = computeSchemaVersion(collections);
|
|
1051
|
+
if (provisioned) {
|
|
1052
|
+
if (!stamp) return {
|
|
1053
|
+
status: "skipped",
|
|
1054
|
+
reason: "this driver cannot record a collections schema version"
|
|
1055
|
+
};
|
|
1056
|
+
try {
|
|
1057
|
+
await stamp(processVersion);
|
|
1058
|
+
logger.debug(`🔖 [schema] Stamped the collections schema version: ${processVersion}`);
|
|
1059
|
+
} catch (error) {
|
|
1060
|
+
logger.warn("⚠️ [schema] Could not record the collections schema version. Other processes cannot check themselves against this database until one boots that can.", { error });
|
|
1061
|
+
}
|
|
1062
|
+
return {
|
|
1063
|
+
status: "match",
|
|
1064
|
+
version: processVersion
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
if (!read) return {
|
|
1068
|
+
status: "skipped",
|
|
1069
|
+
reason: "this driver cannot read a collections schema version"
|
|
1070
|
+
};
|
|
1071
|
+
let databaseVersion;
|
|
1072
|
+
try {
|
|
1073
|
+
databaseVersion = await read();
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
logger.warn("⚠️ [schema] Could not read the collections schema version from the database.", { error });
|
|
1076
|
+
return {
|
|
1077
|
+
status: "skipped",
|
|
1078
|
+
reason: "the stamp could not be read"
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
const verdict = decideSchemaStamp({
|
|
1082
|
+
processVersion,
|
|
1083
|
+
databaseVersion,
|
|
1084
|
+
introspecting
|
|
1085
|
+
});
|
|
1086
|
+
if (verdict.status === "unstamped") logger.debug("🔖 [schema] This database carries no collections schema version yet — it will be stamped by the process that provisions it.");
|
|
1087
|
+
else if (verdict.status === "mismatch") {
|
|
1088
|
+
const message = describeMismatch(verdict.database, verdict.process);
|
|
1089
|
+
if ((options.policy ?? "warn") === "refuse") throw new SchemaVersionMismatchError(verdict.database, verdict.process, message);
|
|
1090
|
+
logger.warn(`⚠️ [schema] ${message}`);
|
|
1091
|
+
}
|
|
1092
|
+
return verdict;
|
|
1093
|
+
}
|
|
1094
|
+
//#endregion
|
|
869
1095
|
//#region src/services/driver-registry.ts
|
|
870
1096
|
/**
|
|
871
1097
|
* The default driver identifier used when:
|
|
@@ -3947,7 +4173,7 @@ function assertStorageAccessControlConfigured(state, isProduction) {
|
|
|
3947
4173
|
*/
|
|
3948
4174
|
async function mountOpenApiDocs(app, basePath, enableSwagger, serverCollections, requireAuth) {
|
|
3949
4175
|
if (enableSwagger === false || serverCollections.length === 0) return;
|
|
3950
|
-
const { generateOpenApiSpec } = await import("./openapi-generator-
|
|
4176
|
+
const { generateOpenApiSpec } = await import("./openapi-generator-BCKJRUS4.js");
|
|
3951
4177
|
app.get(`${basePath}/docs`, (c) => {
|
|
3952
4178
|
const spec = generateOpenApiSpec(serverCollections, {
|
|
3953
4179
|
basePath,
|
|
@@ -4137,7 +4363,8 @@ var ALL_RUNTIME_SURFACES = [
|
|
|
4137
4363
|
"admin",
|
|
4138
4364
|
"functions",
|
|
4139
4365
|
"cron",
|
|
4140
|
-
"meta"
|
|
4366
|
+
"meta",
|
|
4367
|
+
"realtime"
|
|
4141
4368
|
];
|
|
4142
4369
|
/**
|
|
4143
4370
|
* Resolve a partial surface set against the default, which is everything on.
|
|
@@ -10207,7 +10434,7 @@ function _resetRebaseMock() {
|
|
|
10207
10434
|
* It clears the default policies through their `rolesOverlap(['admin'])` arm —
|
|
10208
10435
|
* which is why the difference rarely shows. It shows when you write your own:
|
|
10209
10436
|
*
|
|
10210
|
-
* - `policy.serverContext()` compiles to `
|
|
10437
|
+
* - `policy.serverContext()` compiles to `rebase.uid() IS NULL` and is therefore
|
|
10211
10438
|
* **false** for this accessor. A collection with `disableDefaultPolicies:
|
|
10212
10439
|
* true` whose rule is `serverContext()` denies these writes (`42501`) and
|
|
10213
10440
|
* returns zero rows — HTTP 200, empty — for these reads.
|
|
@@ -10342,6 +10569,8 @@ function wrapDatabaseAdapter(dbAdapter) {
|
|
|
10342
10569
|
initializeWebsockets: dbAdapter.initializeWebsockets,
|
|
10343
10570
|
ensureCollectionSchema: dbAdapter.ensureCollectionSchema ? (collections, driverResult, log) => dbAdapter.ensureCollectionSchema(collections, driverResult, log) : void 0,
|
|
10344
10571
|
ensureCollectionPolicies: dbAdapter.ensureCollectionPolicies ? (collections, driverResult, log) => dbAdapter.ensureCollectionPolicies(collections, driverResult, log) : void 0,
|
|
10572
|
+
readCollectionsSchemaVersion: dbAdapter.readCollectionsSchemaVersion ? (driverResult) => dbAdapter.readCollectionsSchemaVersion(driverResult) : void 0,
|
|
10573
|
+
stampCollectionsSchemaVersion: dbAdapter.stampCollectionsSchemaVersion ? (version, driverResult) => dbAdapter.stampCollectionsSchemaVersion(version, driverResult) : void 0,
|
|
10345
10574
|
getAdmin: dbAdapter.getAdmin,
|
|
10346
10575
|
mountRoutes: dbAdapter.mountRoutes
|
|
10347
10576
|
};
|
|
@@ -10413,6 +10642,10 @@ async function _initializeRebaseBackend(config) {
|
|
|
10413
10642
|
schemaProvisioning: {
|
|
10414
10643
|
attempted: schemaOutcome.status === "applied",
|
|
10415
10644
|
reason: schemaOutcome.status === "skipped" ? schemaOutcome.reason : void 0
|
|
10645
|
+
},
|
|
10646
|
+
realtime: {
|
|
10647
|
+
subscribe: surfaces.realtime,
|
|
10648
|
+
provision: config.provisionSchema ?? true
|
|
10416
10649
|
}
|
|
10417
10650
|
});
|
|
10418
10651
|
delegates[b.id || bootstrapper.type] = driverResult.driver;
|
|
@@ -10503,10 +10736,18 @@ async function _initializeRebaseBackend(config) {
|
|
|
10503
10736
|
logger.info("Authentication initialized");
|
|
10504
10737
|
} else logger.warn("Auth requested but default bootstrapper does not support initializeAuth");
|
|
10505
10738
|
}
|
|
10506
|
-
await provisionCollectionPolicies(provisionable, provisionTarget, {
|
|
10739
|
+
const policyOutcome = await provisionCollectionPolicies(provisionable, provisionTarget, {
|
|
10507
10740
|
introspecting: introspectCollections,
|
|
10508
10741
|
provision: config.provisionSchema ?? true
|
|
10509
10742
|
});
|
|
10743
|
+
await enforceSchemaStamp({
|
|
10744
|
+
collections: provisionable,
|
|
10745
|
+
provisioned: schemaOutcome.status === "applied" && policyOutcome.status === "applied",
|
|
10746
|
+
introspecting: introspectCollections,
|
|
10747
|
+
policy: resolveSchemaMismatchPolicy(),
|
|
10748
|
+
read: provisionTarget.bootstrapper.readCollectionsSchemaVersion ? () => provisionTarget.bootstrapper.readCollectionsSchemaVersion(provisionTarget.driverResult) : void 0,
|
|
10749
|
+
stamp: provisionTarget.bootstrapper.stampCollectionsSchemaVersion ? (version) => provisionTarget.bootstrapper.stampCollectionsSchemaVersion(version, provisionTarget.driverResult) : void 0
|
|
10750
|
+
});
|
|
10510
10751
|
let historyConfigResult = void 0;
|
|
10511
10752
|
if (config.history) if (defaultBootstrapper.initializeHistory) {
|
|
10512
10753
|
logger.debug("Bootstrapping entity history via driver protocol");
|
|
@@ -10624,7 +10865,7 @@ async function _initializeRebaseBackend(config) {
|
|
|
10624
10865
|
]) {
|
|
10625
10866
|
const providerConfig = safeAuthConfig[key];
|
|
10626
10867
|
if (providerConfig && requiredFields.every((f) => Boolean(providerConfig[f]))) {
|
|
10627
|
-
const createFn = (await import("./auth-
|
|
10868
|
+
const createFn = (await import("./auth-5Et5mnUA.js").then((n) => n.t))[factory];
|
|
10628
10869
|
oauthProviders.push(createFn(providerConfig));
|
|
10629
10870
|
}
|
|
10630
10871
|
}
|
|
@@ -10964,7 +11205,7 @@ async function _initializeRebaseBackend(config) {
|
|
|
10964
11205
|
const { loadCronJobsWithDiagnostics } = await import("./cron-loader-YhhQeVBM.js").then((n) => n.t);
|
|
10965
11206
|
const { CronScheduler } = await import("./cron-scheduler-DIpYBmZP.js").then((n) => n.n);
|
|
10966
11207
|
const { createCronRoutes } = await import("./cron-routes-maM_RlUu.js").then((n) => n.n);
|
|
10967
|
-
const { createCronStore } = await import("./cron-store-
|
|
11208
|
+
const { createCronStore } = await import("./cron-store-DfH_4Cd9.js").then((n) => n.n);
|
|
10968
11209
|
const { jobs: loadedCronJobs, problems: cronProblems } = await loadCronJobsWithDiagnostics(config.cronsDir);
|
|
10969
11210
|
cronScheduler = new CronScheduler();
|
|
10970
11211
|
cronScheduler.setClient(serverSingleton);
|
|
@@ -10994,7 +11235,7 @@ async function _initializeRebaseBackend(config) {
|
|
|
10994
11235
|
}
|
|
10995
11236
|
let jobQueue;
|
|
10996
11237
|
if (config.jobs?.enabled) {
|
|
10997
|
-
const { createJobStore, createJobQueue } = await import("./jobs-
|
|
11238
|
+
const { createJobStore, createJobQueue } = await import("./jobs-CyOKXXlu.js").then((n) => n.t);
|
|
10998
11239
|
const store = createJobStore(defaultDriver);
|
|
10999
11240
|
if (store) {
|
|
11000
11241
|
await store.ensureTable();
|
|
@@ -11028,7 +11269,7 @@ async function _initializeRebaseBackend(config) {
|
|
|
11028
11269
|
logger.debug("Logs routes mounted", { path: `${basePath}/logs` });
|
|
11029
11270
|
}
|
|
11030
11271
|
if (surfaces.meta) {
|
|
11031
|
-
const { createContractRoutes } = await import("./contract-routes-
|
|
11272
|
+
const { createContractRoutes } = await import("./contract-routes-DZ-LBpSL.js").then((n) => n.t);
|
|
11032
11273
|
const contractRouter = new Hono();
|
|
11033
11274
|
if (adminSurfacesGated) {
|
|
11034
11275
|
if (apiKeyPreAuth) contractRouter.use("/contract", apiKeyPreAuth);
|
|
@@ -11053,7 +11294,7 @@ async function _initializeRebaseBackend(config) {
|
|
|
11053
11294
|
defaultKey: defaultDriverId,
|
|
11054
11295
|
resolveKey: keyForCollectionPath
|
|
11055
11296
|
}) : defaultRealtimeService;
|
|
11056
|
-
if (defaultBootstrapper.initializeWebsockets && effectiveRealtimeService) await defaultBootstrapper.initializeWebsockets(config.server, effectiveRealtimeService, defaultDriver, config.auth, authAdapter);
|
|
11297
|
+
if (surfaces.realtime && defaultBootstrapper.initializeWebsockets && effectiveRealtimeService) await defaultBootstrapper.initializeWebsockets(config.server, effectiveRealtimeService, defaultDriver, config.auth, authAdapter);
|
|
11057
11298
|
logger.debug("Rebase Backend Initialized");
|
|
11058
11299
|
const authSchemaCheck = authConfigResult?.schemaHealthCheck;
|
|
11059
11300
|
const healthCheck = createHealthCheck(defaultDriver, authSchemaCheck ? () => authSchemaCheck.call(authConfigResult) : void 0);
|
|
@@ -11159,7 +11400,7 @@ function defineFunction(definition) {
|
|
|
11159
11400
|
* against that identity, not skipped. See that constant's docblock. It clears
|
|
11160
11401
|
* the default policies through their `rolesOverlap(['admin'])` arm, which is why
|
|
11161
11402
|
* the distinction rarely shows; it shows against hand-written rules, where
|
|
11162
|
-
* `policy.serverContext()` (`
|
|
11403
|
+
* `policy.serverContext()` (`rebase.uid() IS NULL`) is **false** for it. Use
|
|
11163
11404
|
* `rebase.sql()` when you need the genuine bypass — owner connection, no
|
|
11164
11405
|
* policies.
|
|
11165
11406
|
*
|
|
@@ -12040,6 +12281,49 @@ function isAssetRequest(requestPath, destination) {
|
|
|
12040
12281
|
return ASSET_EXTENSIONS.has(lastSegment.slice(dot + 1).toLowerCase());
|
|
12041
12282
|
}
|
|
12042
12283
|
/**
|
|
12284
|
+
* Does this filename carry a content hash, and may it therefore be cached forever?
|
|
12285
|
+
*
|
|
12286
|
+
* The two answers a build needs are opposites, and picking wrong in either
|
|
12287
|
+
* direction is a real bug. A hashed chunk (`index-vgugiqRO.js`) can be
|
|
12288
|
+
* `immutable` for a year because a new build gives it a new URL. `index.html`
|
|
12289
|
+
* cannot be cached at all: it is the document naming those hashed chunks, so a
|
|
12290
|
+
* cached copy pins a tab to the previous deploy's filenames, which the new build
|
|
12291
|
+
* no longer contains — the page dies on a 404 for a chunk, and the user has no
|
|
12292
|
+
* way to know a reload is what they need.
|
|
12293
|
+
*
|
|
12294
|
+
* Erring toward "not hashed" is therefore the safe direction: the cost is a
|
|
12295
|
+
* revalidation that usually answers 304, against serving a year-stale file with
|
|
12296
|
+
* no way to recall it. So this demands positive evidence of a hash rather than
|
|
12297
|
+
* assuming one — a segment of 8+ characters after the last `-` that is not
|
|
12298
|
+
* ordinary lowercase prose. Vite and Rollup emit base64url digests, which
|
|
12299
|
+
* essentially always carry a digit or a capital; a hand-written
|
|
12300
|
+
* `vendor-analytics.js` does not, and gets revalidated.
|
|
12301
|
+
*
|
|
12302
|
+
* Deliberately not "everything under /assets/": that is a Vite convention, not a
|
|
12303
|
+
* guarantee, and a build that drops an unhashed file there would have it pinned
|
|
12304
|
+
* in every visitor's cache for a year with no way to correct it.
|
|
12305
|
+
*/
|
|
12306
|
+
function isContentHashed(requestPath) {
|
|
12307
|
+
const lastSegment = requestPath.slice(requestPath.lastIndexOf("/") + 1);
|
|
12308
|
+
const dot = lastSegment.lastIndexOf(".");
|
|
12309
|
+
if (dot <= 0) return false;
|
|
12310
|
+
const stem = lastSegment.slice(0, dot);
|
|
12311
|
+
const dash = stem.lastIndexOf("-");
|
|
12312
|
+
if (dash < 0) return false;
|
|
12313
|
+
const suffix = stem.slice(dash + 1);
|
|
12314
|
+
if (suffix.length < 8) return false;
|
|
12315
|
+
if (!/^[A-Za-z0-9_-]+$/.test(suffix)) return false;
|
|
12316
|
+
return /[0-9A-Z]/.test(suffix);
|
|
12317
|
+
}
|
|
12318
|
+
/** A year, and never revalidated: the URL changes when the bytes do. */
|
|
12319
|
+
var IMMUTABLE_CACHE = "public, max-age=31536000, immutable";
|
|
12320
|
+
/**
|
|
12321
|
+
* Store it, but check before using it. Not `no-store`: revalidation answers 304
|
|
12322
|
+
* on an unchanged document, which is cheaper than re-sending it, and correctness
|
|
12323
|
+
* here needs freshness rather than absence.
|
|
12324
|
+
*/
|
|
12325
|
+
var REVALIDATE_CACHE = "no-cache";
|
|
12326
|
+
/**
|
|
12043
12327
|
* Serve a Single Page Application from an Hono app.
|
|
12044
12328
|
*
|
|
12045
12329
|
* @internal Not part of the stable public API. Exported only because the
|
|
@@ -12060,6 +12344,12 @@ function serveSPA(app, config) {
|
|
|
12060
12344
|
return;
|
|
12061
12345
|
}
|
|
12062
12346
|
const scope = isRoot ? "/*" : `${basePath}/*`;
|
|
12347
|
+
app.use(scope, async (c, next) => {
|
|
12348
|
+
await next();
|
|
12349
|
+
if (c.res.headers.has("cache-control")) return;
|
|
12350
|
+
if (c.res.status !== 200) return;
|
|
12351
|
+
c.header("Cache-Control", isContentHashed(c.req.path) ? IMMUTABLE_CACHE : REVALIDATE_CACHE);
|
|
12352
|
+
});
|
|
12063
12353
|
app.use(scope, responseCompression());
|
|
12064
12354
|
app.use(scope, serveStatic({
|
|
12065
12355
|
root: path$2.relative(process.cwd(), frontendPath),
|
|
@@ -12386,6 +12676,19 @@ var bootEnvExtension = object({
|
|
|
12386
12676
|
SMTP_NAME: string().optional(),
|
|
12387
12677
|
APP_NAME: string().default("Rebase"),
|
|
12388
12678
|
/**
|
|
12679
|
+
* Logo shown above the card in the default email templates, at 48x48.
|
|
12680
|
+
*
|
|
12681
|
+
* Must be a PNG or JPG on an absolute `http(s)` URL: mail clients do not
|
|
12682
|
+
* render SVG and block `data:` URIs, and the image is fetched by the
|
|
12683
|
+
* recipient's client rather than by this process. Anything else renders no
|
|
12684
|
+
* logo rather than a broken image — see `resolveEmailBranding`.
|
|
12685
|
+
*
|
|
12686
|
+
* Left unset, an app that still answers to the default `APP_NAME` gets the
|
|
12687
|
+
* Rebase mark and one that has renamed itself gets nothing. This is the only
|
|
12688
|
+
* way a managed tenant, which ships no code of its own, can set its own.
|
|
12689
|
+
*/
|
|
12690
|
+
EMAIL_LOGO_URL: string().optional(),
|
|
12691
|
+
/**
|
|
12389
12692
|
* Serve the bundle's static/admin assets from this process.
|
|
12390
12693
|
*
|
|
12391
12694
|
* Default on, because a self-hosted single-container deployment is the case
|
|
@@ -12400,13 +12703,24 @@ var bootEnvExtension = object({
|
|
|
12400
12703
|
/**
|
|
12401
12704
|
* What the runtime may do to the database schema at boot.
|
|
12402
12705
|
*
|
|
12403
|
-
* - `none`
|
|
12404
|
-
*
|
|
12405
|
-
* -
|
|
12406
|
-
*
|
|
12407
|
-
*
|
|
12408
|
-
*
|
|
12409
|
-
*
|
|
12706
|
+
* - `none` — touch nothing. The schema is provisioned by something else: a
|
|
12707
|
+
* migration step, or another process in a split deployment.
|
|
12708
|
+
* - anything else, **including unset** — run the additive provisioning pass
|
|
12709
|
+
* in `boot/provision.ts`: create missing tables, columns and enum types,
|
|
12710
|
+
* never drop or rewrite one.
|
|
12711
|
+
*
|
|
12712
|
+
* The default is `ensure` everywhere, production included — see
|
|
12713
|
+
* `provisioningDisabled`, which is the single place the value is read and
|
|
12714
|
+
* which only asks whether it is `none`.
|
|
12715
|
+
*
|
|
12716
|
+
* `push` is in the enum and is **not** distinguished from `ensure` anywhere
|
|
12717
|
+
* in the boot path; nothing implements "reconcile destructively" here. The
|
|
12718
|
+
* published image goes further and refuses to start on it
|
|
12719
|
+
* (`docker/entrypoint.mjs`), because a full push computes a diff and will
|
|
12720
|
+
* happily `DROP COLUMN` — and a container restart must never be able to
|
|
12721
|
+
* destroy a production column as a side effect of rescheduling. Reshaping
|
|
12722
|
+
* and destructive changes stay a deliberate `rebase db generate` + `rebase
|
|
12723
|
+
* db migrate`, or a reviewed `rebase db push` from a checkout.
|
|
12410
12724
|
*/
|
|
12411
12725
|
REBASE_MIGRATE_ON_BOOT: _enum([
|
|
12412
12726
|
"none",
|
|
@@ -12477,8 +12791,13 @@ var bootEnvExtension = object({
|
|
|
12477
12791
|
*
|
|
12478
12792
|
* Deliberately tri-state, and resolved against NODE_ENV by
|
|
12479
12793
|
* {@link resolveEnableSwagger} rather than defaulted here. Unset means "on
|
|
12480
|
-
* in development, off in production"
|
|
12481
|
-
*
|
|
12794
|
+
* in development, off in production"; `false` turns both off anywhere.
|
|
12795
|
+
*
|
|
12796
|
+
* `true` in production is only half a switch, and the asymmetry is in
|
|
12797
|
+
* `init/docs.ts` rather than here: the *spec* at `/api/docs` is served
|
|
12798
|
+
* whenever this is not `false`, but the Swagger *UI* at `/api/swagger` is
|
|
12799
|
+
* gated on `NODE_ENV !== "production"` independently and this variable
|
|
12800
|
+
* cannot open it.
|
|
12482
12801
|
*
|
|
12483
12802
|
* It used to default to `"false"` outright, which reads as a safe default
|
|
12484
12803
|
* and was not one: the runtime is how every scaffolded project boots, so
|
|
@@ -12673,6 +12992,13 @@ var RoleConfigurationError = class extends Error {
|
|
|
12673
12992
|
* and sets `REBASE_CRON_SCHEDULER=false` on the api. `functions` runs no timers
|
|
12674
12993
|
* at all — a function process is scaled by request load and replaced at will, so
|
|
12675
12994
|
* giving it scheduled work would make its replica count mean something.
|
|
12995
|
+
*
|
|
12996
|
+
* `realtime` is off for the same two, and it is the surface whose cost is paid
|
|
12997
|
+
* whether or not anyone calls it: a process that consumes change events holds a
|
|
12998
|
+
* dedicated `LISTEN` connection outside the pool for as long as it runs, and
|
|
12999
|
+
* only a process serving websockets has anybody to deliver to. Writes made *by*
|
|
13000
|
+
* these processes are still heard — capture is database triggers, so a change is
|
|
13001
|
+
* published by the database rather than by whichever pod made it.
|
|
12676
13002
|
*/
|
|
12677
13003
|
var ROLES = {
|
|
12678
13004
|
all: {
|
|
@@ -12692,7 +13018,8 @@ var ROLES = {
|
|
|
12692
13018
|
storage: false,
|
|
12693
13019
|
admin: false,
|
|
12694
13020
|
cron: false,
|
|
12695
|
-
meta: false
|
|
13021
|
+
meta: false,
|
|
13022
|
+
realtime: false
|
|
12696
13023
|
},
|
|
12697
13024
|
ownership: {
|
|
12698
13025
|
cronScheduler: false,
|
|
@@ -12708,7 +13035,8 @@ var ROLES = {
|
|
|
12708
13035
|
admin: false,
|
|
12709
13036
|
functions: false,
|
|
12710
13037
|
cron: false,
|
|
12711
|
-
meta: false
|
|
13038
|
+
meta: false,
|
|
13039
|
+
realtime: false
|
|
12712
13040
|
},
|
|
12713
13041
|
ownership: {},
|
|
12714
13042
|
provisionSchema: false
|
|
@@ -13116,6 +13444,8 @@ function adapterToBootstrapper(adapter, id, isDefault) {
|
|
|
13116
13444
|
initializeWebsockets: adapter.initializeWebsockets,
|
|
13117
13445
|
ensureCollectionSchema: adapter.ensureCollectionSchema ? (collections, driverResult, log) => adapter.ensureCollectionSchema(collections, driverResult, log) : void 0,
|
|
13118
13446
|
ensureCollectionPolicies: adapter.ensureCollectionPolicies ? (collections, driverResult, log) => adapter.ensureCollectionPolicies(collections, driverResult, log) : void 0,
|
|
13447
|
+
readCollectionsSchemaVersion: adapter.readCollectionsSchemaVersion ? (driverResult) => adapter.readCollectionsSchemaVersion(driverResult) : void 0,
|
|
13448
|
+
stampCollectionsSchemaVersion: adapter.stampCollectionsSchemaVersion ? (version, driverResult) => adapter.stampCollectionsSchemaVersion(version, driverResult) : void 0,
|
|
13119
13449
|
getAdmin: adapter.getAdmin,
|
|
13120
13450
|
mountRoutes: adapter.mountRoutes
|
|
13121
13451
|
};
|
|
@@ -13194,6 +13524,7 @@ function resolveEmailOptions(env) {
|
|
|
13194
13524
|
name: env.SMTP_NAME
|
|
13195
13525
|
},
|
|
13196
13526
|
appName: env.APP_NAME,
|
|
13527
|
+
logoUrl: env.EMAIL_LOGO_URL,
|
|
13197
13528
|
resetPasswordUrl: env.FRONTEND_URL
|
|
13198
13529
|
};
|
|
13199
13530
|
}
|
|
@@ -13938,17 +14269,37 @@ async function runFromBundle(options = {}) {
|
|
|
13938
14269
|
* driver. Nothing detected that before this: the halves simply disagreed in
|
|
13939
14270
|
* silence until some capability turned out to be missing three layers down.
|
|
13940
14271
|
*
|
|
13941
|
-
* A warning rather than a refusal.
|
|
14272
|
+
* A warning rather than a refusal. Either pairing can be fine — both are
|
|
13942
14273
|
* supported, and a boot that dies on version arithmetic would be a far worse
|
|
13943
14274
|
* failure than the drift it is guarding against. This only has to make the skew
|
|
13944
14275
|
* *visible*, so that the next person reading the log starts from the right
|
|
13945
14276
|
* question.
|
|
14277
|
+
*
|
|
14278
|
+
* ## Both directions, because only one of them was ever reported
|
|
14279
|
+
*
|
|
14280
|
+
* A driver *behind* the runtime was the original case: the platform ships a fix,
|
|
14281
|
+
* every tenant rolls onto the new image, and none of them run the fixed driver.
|
|
14282
|
+
*
|
|
14283
|
+
* A driver *ahead* by a minor is the mirror image and was silent until it cost
|
|
14284
|
+
* another day. It is not the deliberate pin this check used to assume — that is
|
|
14285
|
+
* a patch bump, and still says nothing. It is what a floating runtime range
|
|
14286
|
+
* produces when the image lags: a bundle built against a framework the image
|
|
14287
|
+
* predates, so a feature split across `@rebasepro/server` and the driver has
|
|
14288
|
+
* only its driver half present. Nothing errors. The half that landed reads
|
|
14289
|
+
* config the older harness never sets, defaults it, and a subsystem — realtime
|
|
14290
|
+
* channels, in the case that prompted this — is inert in a process that passes
|
|
14291
|
+
* every probe. The fix is the opposite of the stale case, which is exactly why
|
|
14292
|
+
* the two need separate sentences: bump the *image*, not the package.json.
|
|
13946
14293
|
*/
|
|
13947
14294
|
function warnOnDriverSkew(dataSources, runtimeVersion) {
|
|
13948
14295
|
if (!runtimeVersion) return;
|
|
13949
14296
|
for (const source of dataSources) {
|
|
13950
|
-
|
|
13951
|
-
|
|
14297
|
+
const skew = describeDriverSkew(source.driverVersion, runtimeVersion);
|
|
14298
|
+
if (skew.stale) {
|
|
14299
|
+
logger.warn(`Driver version skew on data source "${source.key}": "${source.driverPackage}" is at ${source.driverVersion}, this runtime is ${runtimeVersion}. A driver is installed from your bundle's dependencies, NOT supplied by the platform image, so a newer runtime does not update it. Capabilities added after ${source.driverVersion} are unavailable to this deployment — bump "${source.driverPackage}" in your project's package.json and redeploy.`);
|
|
14300
|
+
continue;
|
|
14301
|
+
}
|
|
14302
|
+
if (skew.ahead) logger.warn(`Driver version skew on data source "${source.key}": "${source.driverPackage}" is at ${source.driverVersion}, this runtime is ${runtimeVersion}. The driver is AHEAD of the platform image by a minor or more. Your bundle supplies the driver while the image supplies \`@rebasepro/server\`, so a feature spanning both packages has only its driver half running here — the other half is missing, and the usual symptom is a subsystem that is silently inert rather than one that errors. Deploy a newer platform image (or pin the runtime version), or pin "${source.driverPackage}" back to ${runtimeVersion} in your project's package.json.`);
|
|
13952
14303
|
}
|
|
13953
14304
|
}
|
|
13954
14305
|
/**
|
|
@@ -13971,6 +14322,6 @@ function warnOnUnusableBundleShape(bundle) {
|
|
|
13971
14322
|
logger.warn(`This bundle declares a config package at "${bundle.manifest.entry.config}", but no collections directory resolved inside it, so no collections were loaded and no tables will be created. Rebuild with \`rebase build\` and check the manifest's \`entry.collections\`.`);
|
|
13972
14323
|
}
|
|
13973
14324
|
//#endregion
|
|
13974
|
-
export { ApiError, BlockedUrlError, BundleError, CONCURRENT_DDL_SQLSTATES, CronScheduler, DEFAULT_DRIVER_ID, DEFAULT_MAX_FILE_SIZE, DEFAULT_STORAGE_ID, DOCUMENT_MIME_TYPES, DefaultDriverRegistry, DefaultStorageRegistry, GCSStorageController, IMAGE_MIME_TYPES, LocalStorageController, MetricsRegistry, RawHtml, S3StorageController, SMTPEmailService, TransformCache, TusHandler, UnknownStorageSourceError, WEBHOOK_DELIVERY_TASK, WebhookDispatcher, _resetRebaseMock, _setRebaseMock, applyCollectionDefaults, assertAllowedOutboundUrl, assertCollectionConfigs, assertDistinctSuffixes, assertEmailLinkBases, assertWriteRequestValid, authJwt, authRoles, authUid, blockedAddressReason, bootFromBundle, classifySurface, cleanupDevPortFile, createAppleProvider, createBackupRoutes, createBitbucketProvider, createBuiltinAuthAdapter, createContractRoutes, createCronRoutes, createCronStore, createCustomAuthAdapter, createDdlBootstrapper, createDiscordProvider, createEmailService, createFacebookProvider, createFunctionRoutes, createGitHubProvider, createGitLabProvider, createGoogleProvider, createHistoryRoutes, createJobQueue, createJobStore, createJwksRoutes, createLinkedinProvider, createMetricsMiddleware, createMetricsRoutes, createMicrosoftProvider, createSlackProvider, createSourceBundle, createSpotifyProvider, createStorageController, createStorageRoutes, createTwitterProvider, defaultBackoff, defineCron, defineFunction, envSuffixForKey, errorHandler, escapeHtml, extractUserFromToken, fileTokenAuth, findCollectionConfigProblems, generateSecurePassword, getEmailVerificationTemplate, getJwks, getMagicLinkTemplate, getPasswordResetTemplate, getUserInvitationTemplate, getWelcomeEmailTemplate, hasAsymmetricSigningKey, hashPassword, html, httpMethodToOperation, initializeDataSource, initializeDataSources, initializeRebaseBackend, installShutdownHandlers, isApiKeyToken, isAuthAdapter, isConcurrentDdlRace, isDatabaseAdapter, isDuplicateObjectRace, isLocalhostOrigin, isOperationAllowed, isTransformableImage, listBackupObjects, listenWithPortRetry, loadBootEnv, loadBundle, loadBundleConfigExports, loadBundleSchema, loadCollectionsFromDirectory, loadCronJobsFromDirectory, loadCronJobsWithDiagnostics, loadDeclaredStorageSources, loadEnv, loadFunctionsFromDirectory, loadUsersCollection, logger, oauthCodeFlowSchema, optionalAuth, parseBackupDestination, parseBackupTimestamp, parseTransformOptions, pkceTokenParams, providerVerifiedEmail, queryTokenAuth, raw, readBackupBytes, readBundleManifest, rebase, requireAdmin, requireAuth, resolveAuthHooks, resolveAuthOptions, resolveCorsOrigin, resolveDataSources, resolveEmailLinkBase, resolveEmailOptions, resolveRequireAuth, resolveStorageBackend, resolveStorageSources, runFromBundle, safeCompare, serveSPA, transformImage, tryVerifyOidcIdToken, unknownKeyPolicyFromEnv, validateApiKey, validateCronExpression, validatePasswordStrength, verifyOidcIdToken, verifyPassword };
|
|
14325
|
+
export { ApiError, BlockedUrlError, BundleError, CONCURRENT_DDL_SQLSTATES, CronScheduler, DEFAULT_DRIVER_ID, DEFAULT_MAX_FILE_SIZE, DEFAULT_STORAGE_ID, DOCUMENT_MIME_TYPES, DefaultDriverRegistry, DefaultStorageRegistry, GCSStorageController, IMAGE_MIME_TYPES, LocalStorageController, MetricsRegistry, RawHtml, S3StorageController, SMTPEmailService, TransformCache, TusHandler, UnknownStorageSourceError, WEBHOOK_DELIVERY_TASK, WebhookDispatcher, _resetRebaseMock, _setRebaseMock, applyCollectionDefaults, assertAllowedOutboundUrl, assertCollectionConfigs, assertDistinctSuffixes, assertEmailLinkBases, assertWriteRequestValid, authJwt, authRoles, authUid, blockedAddressReason, bootFromBundle, classifySurface, cleanupDevPortFile, createAppleProvider, createBackupRoutes, createBitbucketProvider, createBuiltinAuthAdapter, createContractRoutes, createCronRoutes, createCronStore, createCustomAuthAdapter, createDdlBootstrapper, createDiscordProvider, createEmailService, createFacebookProvider, createFunctionRoutes, createGitHubProvider, createGitLabProvider, createGoogleProvider, createHistoryRoutes, createJobQueue, createJobStore, createJwksRoutes, createLinkedinProvider, createMetricsMiddleware, createMetricsRoutes, createMicrosoftProvider, createSlackProvider, createSourceBundle, createSpotifyProvider, createStorageController, createStorageRoutes, createTwitterProvider, defaultBackoff, defineCron, defineFunction, envSuffixForKey, errorHandler, escapeHtml, extractUserFromToken, fileTokenAuth, findCollectionConfigProblems, generateSecurePassword, getEmailVerificationTemplate, getJwks, getMagicLinkTemplate, getPasswordResetTemplate, getUserInvitationTemplate, getWelcomeEmailTemplate, hasAsymmetricSigningKey, hashPassword, html, httpMethodToOperation, initializeDataSource, initializeDataSources, initializeRebaseBackend, installShutdownHandlers, isApiKeyToken, isAuthAdapter, isConcurrentDdlRace, isDatabaseAdapter, isDuplicateObjectRace, isLocalhostOrigin, isOperationAllowed, isTransformableImage, listBackupObjects, listenWithPortRetry, loadBootEnv, loadBundle, loadBundleConfigExports, loadBundleSchema, loadCollectionsFromDirectory, loadCronJobsFromDirectory, loadCronJobsWithDiagnostics, loadDeclaredStorageSources, loadEnv, loadFunctionsFromDirectory, loadUsersCollection, logger, oauthCodeFlowSchema, optionalAuth, parseBackupDestination, parseBackupTimestamp, parseTransformOptions, pkceTokenParams, providerVerifiedEmail, queryTokenAuth, raw, readBackupBytes, readBundleManifest, rebase, requireAdmin, requireAuth, resolveAuthHooks, resolveAuthOptions, resolveCorsOrigin, resolveDataSources, resolveEmailBranding, resolveEmailLinkBase, resolveEmailOptions, resolveRequireAuth, resolveStorageBackend, resolveStorageSources, runFromBundle, safeCompare, serveSPA, transformImage, tryVerifyOidcIdToken, unknownKeyPolicyFromEnv, validateApiKey, validateCronExpression, validatePasswordStrength, verifyOidcIdToken, verifyPassword };
|
|
13975
14326
|
|
|
13976
14327
|
//# sourceMappingURL=index.es.js.map
|