@rebasepro/server 0.10.1-canary.b1e3dbf → 0.10.1-canary.ff9ccd6
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/api/ast-schema-editor.d.ts +10 -0
- package/dist/index.es.js +97 -70
- package/dist/index.es.js.map +1 -1
- package/dist/{openapi-generator-DqSIwNLV.js → openapi-generator-C3_pTcsb.js} +2 -3
- package/dist/{openapi-generator-DqSIwNLV.js.map → openapi-generator-C3_pTcsb.js.map} +1 -1
- package/dist/{schema-editor-routes-D3ef8zu1.js → schema-editor-routes-CF-g0gVN.js} +30 -2
- package/dist/schema-editor-routes-CF-g0gVN.js.map +1 -0
- package/dist/{src-BITicbgD.js → src-DRUswG_w.js} +107 -3
- package/dist/src-DRUswG_w.js.map +1 -0
- package/dist/storage/routes.d.ts +9 -1
- package/dist/storage/types.d.ts +1 -1
- package/dist/utils/dev-port.d.ts +5 -2
- package/package.json +8 -11
- package/dist/schema-editor-routes-D3ef8zu1.js.map +0 -1
- package/dist/src-BITicbgD.js.map +0 -1
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Move presentation keys into the `admin` block.
|
|
3
|
+
*
|
|
4
|
+
* `ADMIN_COLLECTION_KEYS` comes from `@rebasepro/types` rather than being spelled
|
|
5
|
+
* out here, so adding a field to `AdminCollectionOptions` cannot leave this writer
|
|
6
|
+
* behind. Any such key already inside `admin` wins over a top-level copy: the
|
|
7
|
+
* nested one is what the file said, and a flat duplicate is the view model's
|
|
8
|
+
* flattening leaking back.
|
|
9
|
+
*/
|
|
10
|
+
export declare function nestAdminKeys(collectionData: Record<string, unknown>): Record<string, unknown>;
|
|
1
11
|
export declare class AstSchemaEditor {
|
|
2
12
|
private project;
|
|
3
13
|
private collectionsDir;
|
package/dist/index.es.js
CHANGED
|
@@ -2,7 +2,7 @@ import { createRequire as __createRequire } from "module";
|
|
|
2
2
|
import process from "process";
|
|
3
3
|
__createRequire(import.meta.url);
|
|
4
4
|
import { _ as __toESM, a as generateRefreshToken, c as getRefreshTokenTtlMs, d as verifyAccessToken, f as verifyDownloadToken, g as __require, h as __exportAll, i as generateDownloadToken, l as hashRefreshToken, m as __commonJSMin, n as configureJwt, o as getAccessTokenExpiry, p as require_jsonwebtoken, r as generateAccessToken, s as getRefreshTokenExpiry, t as MAX_COOKIE_AGE_MS } from "./jwt-Dj7r7QX7.js";
|
|
5
|
-
import { _ as
|
|
5
|
+
import { S as RebaseClientError, _ as EntityReference, a as DEFAULT_DATA_SOURCE_KEY, b as Vector, c as policy, d as isPostgresCollectionConfig, g as toCanonicalOp, h as REST_TO_CANONICAL, i as DEFAULT_STORAGE_SOURCE_KEY, l as getCollectionDataPath, m as NULL_OPS, n as serializeCollections, o as getDataSourceCapabilities, p as CANONICAL_TO_REST, r as SCHEMA_VERSION_HEADER, s as isSQLAdmin, t as computeSchemaVersion, u as getDeclaredSubcollections, v as EntityRelation, x as RebaseApiError, y as GeoPoint } from "./src-DRUswG_w.js";
|
|
6
6
|
import { t as logger } from "./logger-BYU66ENZ.js";
|
|
7
7
|
import { t as nativeDynamicImport } from "./dynamic-import-Dvh-K5fl.js";
|
|
8
8
|
import { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
|
|
@@ -1689,39 +1689,9 @@ policy.or(policy.serverContext(), policy.rolesOverlap(["admin"]));
|
|
|
1689
1689
|
return jsonLogic;
|
|
1690
1690
|
});
|
|
1691
1691
|
})))();
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
* Default operators offered per property type, before engine capabilities and
|
|
1696
|
-
* per-property narrowing are applied. These mirror what the built-in filter
|
|
1697
|
-
* fields can render.
|
|
1698
|
-
*/
|
|
1699
|
-
var COMPARISON_OPS = [
|
|
1700
|
-
"==",
|
|
1701
|
-
"!=",
|
|
1702
|
-
">",
|
|
1703
|
-
">=",
|
|
1704
|
-
"<",
|
|
1705
|
-
"<="
|
|
1706
|
-
];
|
|
1707
|
-
var NULL_CHECK_OPS = ["is-null", "is-not-null"];
|
|
1708
|
-
var MEMBERSHIP_OPS = ["in", "not-in"];
|
|
1709
|
-
var PATTERN_OPS = [
|
|
1710
|
-
"like",
|
|
1711
|
-
"ilike",
|
|
1712
|
-
"not-like",
|
|
1713
|
-
"not-ilike"
|
|
1714
|
-
];
|
|
1715
|
-
[
|
|
1716
|
-
...COMPARISON_OPS,
|
|
1717
|
-
...MEMBERSHIP_OPS,
|
|
1718
|
-
...PATTERN_OPS,
|
|
1719
|
-
...NULL_CHECK_OPS
|
|
1720
|
-
], [
|
|
1721
|
-
...COMPARISON_OPS,
|
|
1722
|
-
...MEMBERSHIP_OPS,
|
|
1723
|
-
...NULL_CHECK_OPS
|
|
1724
|
-
], [...COMPARISON_OPS, ...NULL_CHECK_OPS], [...NULL_CHECK_OPS], [...MEMBERSHIP_OPS, ...NULL_CHECK_OPS], [...MEMBERSHIP_OPS, ...NULL_CHECK_OPS];
|
|
1692
|
+
/**
|
|
1693
|
+
* Apply PropertyConditions to a resolved property, evaluating all JSON Logic rules.
|
|
1694
|
+
*/
|
|
1725
1695
|
//#endregion
|
|
1726
1696
|
//#region ../../node_modules/.pnpm/fast-equals@6.0.0/node_modules/fast-equals/dist/es/index.mjs
|
|
1727
1697
|
var { getOwnPropertyNames, getOwnPropertySymbols } = Object;
|
|
@@ -12888,6 +12858,7 @@ async function verifyGoogleAccessToken(accessToken, expectedClientId) {
|
|
|
12888
12858
|
function createGoogleProvider(config) {
|
|
12889
12859
|
const clientId = typeof config === "string" ? config : config.clientId;
|
|
12890
12860
|
const clientSecret = typeof config === "string" ? void 0 : config.clientSecret;
|
|
12861
|
+
if (!clientSecret) console.warn("[Rebase] Google provider configured without a client secret. ID-token and access-token sign-in still work, but the authorization-code flow used by the admin login button will fail. Set GOOGLE_CLIENT_SECRET to enable it.");
|
|
12891
12862
|
let googleClient;
|
|
12892
12863
|
async function getClient() {
|
|
12893
12864
|
if (!googleClient) {
|
|
@@ -14580,7 +14551,7 @@ function buildAdapterAuthMiddleware(adapter, requireAuth, publicRead) {
|
|
|
14580
14551
|
function createStorageRoutes(config) {
|
|
14581
14552
|
const router = new Hono();
|
|
14582
14553
|
router.onError(errorHandler);
|
|
14583
|
-
const { controller, registry, sources: declaredSources, requireAuth: requireAuth$1 = true, publicRead = false, authAdapter, authorize } = config;
|
|
14554
|
+
const { controller, registry, sources: declaredSources, requireAuth: requireAuth$1 = true, publicRead = false, authAdapter, authorize, authorizeData } = config;
|
|
14584
14555
|
/**
|
|
14585
14556
|
* Run the per-object authorization hook, if one is configured.
|
|
14586
14557
|
*
|
|
@@ -14601,7 +14572,8 @@ function createStorageRoutes(config) {
|
|
|
14601
14572
|
bucket,
|
|
14602
14573
|
operation,
|
|
14603
14574
|
user,
|
|
14604
|
-
storageId: storageId ?? void 0
|
|
14575
|
+
storageId: storageId ?? void 0,
|
|
14576
|
+
data: authorizeData?.()
|
|
14605
14577
|
});
|
|
14606
14578
|
} catch {
|
|
14607
14579
|
allowed = false;
|
|
@@ -15043,7 +15015,7 @@ function assertStorageAccessControlConfigured(state, isProduction) {
|
|
|
15043
15015
|
//#region src/init/docs.ts
|
|
15044
15016
|
async function mountOpenApiDocs(app, basePath, enableSwagger, activeCollections, requireAuth) {
|
|
15045
15017
|
if (enableSwagger === false || activeCollections.length === 0) return;
|
|
15046
|
-
const { generateOpenApiSpec } = await import("./openapi-generator-
|
|
15018
|
+
const { generateOpenApiSpec } = await import("./openapi-generator-C3_pTcsb.js");
|
|
15047
15019
|
app.get(`${basePath}/docs`, (c) => {
|
|
15048
15020
|
const spec = generateOpenApiSpec(activeCollections, {
|
|
15049
15021
|
basePath,
|
|
@@ -15545,6 +15517,34 @@ function createAuth(transport, options) {
|
|
|
15545
15517
|
transport.setToken(null);
|
|
15546
15518
|
emit("SIGNED_OUT", null);
|
|
15547
15519
|
}
|
|
15520
|
+
/**
|
|
15521
|
+
* Recover from a 401 on an ordinary API request.
|
|
15522
|
+
*
|
|
15523
|
+
* Returns `true` when the caller should retry — we minted a fresh access
|
|
15524
|
+
* token. When the refresh is rejected *fatally* (the refresh token itself
|
|
15525
|
+
* is invalid, expired or revoked) this client can no longer act as the
|
|
15526
|
+
* user at all, so we drop the session and emit `SIGNED_OUT`. UIs gate on
|
|
15527
|
+
* that event, so they show their login screen instead of leaving the user
|
|
15528
|
+
* staring at "Invalid or expired token" on every view.
|
|
15529
|
+
*
|
|
15530
|
+
* Transient failures (offline, 5xx, backend restarting) keep the session:
|
|
15531
|
+
* the scheduled refresh backs off and retries, and the token is very
|
|
15532
|
+
* likely still good once the backend answers again.
|
|
15533
|
+
*/
|
|
15534
|
+
async function handleUnauthorized() {
|
|
15535
|
+
if (!currentSession) return false;
|
|
15536
|
+
if (authFlowMode !== "cookie" && !currentSession.refreshToken) {
|
|
15537
|
+
abandonSessionLocally();
|
|
15538
|
+
return false;
|
|
15539
|
+
}
|
|
15540
|
+
try {
|
|
15541
|
+
await refreshSession();
|
|
15542
|
+
return true;
|
|
15543
|
+
} catch (err) {
|
|
15544
|
+
if (isFatalRefreshError(err)) abandonSessionLocally();
|
|
15545
|
+
return false;
|
|
15546
|
+
}
|
|
15547
|
+
}
|
|
15548
15548
|
async function attemptScheduledRefresh(attempt) {
|
|
15549
15549
|
try {
|
|
15550
15550
|
await refreshSession();
|
|
@@ -16046,6 +16046,7 @@ function createAuth(transport, options) {
|
|
|
16046
16046
|
signInWithSpotify,
|
|
16047
16047
|
signOut,
|
|
16048
16048
|
refreshSession,
|
|
16049
|
+
handleUnauthorized,
|
|
16049
16050
|
getUser,
|
|
16050
16051
|
findUserByEmail,
|
|
16051
16052
|
updateUser,
|
|
@@ -20189,14 +20190,7 @@ function createRebaseClient(options) {
|
|
|
20189
20190
|
} catch (e) {}
|
|
20190
20191
|
return session?.accessToken || options.token || "";
|
|
20191
20192
|
},
|
|
20192
|
-
onUnauthorized: options.onUnauthorized || (
|
|
20193
|
-
try {
|
|
20194
|
-
await auth.refreshSession();
|
|
20195
|
-
return true;
|
|
20196
|
-
} catch (e) {
|
|
20197
|
-
return false;
|
|
20198
|
-
}
|
|
20199
|
-
})
|
|
20193
|
+
onUnauthorized: options.onUnauthorized || (() => auth.handleUnauthorized())
|
|
20200
20194
|
});
|
|
20201
20195
|
auth.onAuthStateChange((event, session) => {
|
|
20202
20196
|
if (!ws) return;
|
|
@@ -20206,14 +20200,7 @@ function createRebaseClient(options) {
|
|
|
20206
20200
|
}
|
|
20207
20201
|
});
|
|
20208
20202
|
}
|
|
20209
|
-
if (!options.onUnauthorized) transport.setOnUnauthorized(
|
|
20210
|
-
try {
|
|
20211
|
-
await auth.refreshSession();
|
|
20212
|
-
return true;
|
|
20213
|
-
} catch (e) {
|
|
20214
|
-
return false;
|
|
20215
|
-
}
|
|
20216
|
-
});
|
|
20203
|
+
if (!options.onUnauthorized) transport.setOnUnauthorized(() => auth.handleUnauthorized());
|
|
20217
20204
|
/**
|
|
20218
20205
|
* Suggest the closest known collection key for a mistyped accessor.
|
|
20219
20206
|
* Uses edit-distance-1 and prefix matching — no external dependency.
|
|
@@ -20951,7 +20938,7 @@ async function _initializeRebaseBackend(config) {
|
|
|
20951
20938
|
if (schemaEditorEnabled && config.collectionsDir) {
|
|
20952
20939
|
let editorModule;
|
|
20953
20940
|
try {
|
|
20954
|
-
editorModule = await import("./schema-editor-routes-
|
|
20941
|
+
editorModule = await import("./schema-editor-routes-CF-g0gVN.js");
|
|
20955
20942
|
} catch (err) {
|
|
20956
20943
|
if (err?.code === "ERR_MODULE_NOT_FOUND") logger.warn("Schema Editor disabled: its dependency ts-morph is not installed. Run `npm install ts-morph@28.0.0` to enable it.");
|
|
20957
20944
|
else throw err;
|
|
@@ -20963,6 +20950,7 @@ async function _initializeRebaseBackend(config) {
|
|
|
20963
20950
|
logger.info("Schema Editor mounted", { path: `${basePath}/schema-editor` });
|
|
20964
20951
|
}
|
|
20965
20952
|
}
|
|
20953
|
+
const storageAuthorizeData = {};
|
|
20966
20954
|
if (storageController) {
|
|
20967
20955
|
const storageMaxSize = (config.storage && typeof config.storage === "object" && "type" in config.storage ? config.storage.maxFileSize : void 0) ?? 50 * 1024 * 1024;
|
|
20968
20956
|
assertStorageAccessControlConfigured({
|
|
@@ -20977,7 +20965,8 @@ async function _initializeRebaseBackend(config) {
|
|
|
20977
20965
|
requireAuth: resolveRequireAuth(config.auth),
|
|
20978
20966
|
publicRead: config.storagePublicRead === true,
|
|
20979
20967
|
authAdapter,
|
|
20980
|
-
authorize: config.storageAuthorize
|
|
20968
|
+
authorize: config.storageAuthorize,
|
|
20969
|
+
authorizeData: () => storageAuthorizeData.current
|
|
20981
20970
|
});
|
|
20982
20971
|
const storageRouter = new Hono();
|
|
20983
20972
|
if (apiKeyPreAuth) storageRouter.use("/*", apiKeyPreAuth, createStorageApiKeyGuard());
|
|
@@ -21056,6 +21045,7 @@ async function _initializeRebaseBackend(config) {
|
|
|
21056
21045
|
roles: ["admin"]
|
|
21057
21046
|
};
|
|
21058
21047
|
const defaultData = buildSdkData(await scopeDataDriver(defaultDriver, serviceIdentity));
|
|
21048
|
+
storageAuthorizeData.current = defaultData;
|
|
21059
21049
|
const dataSourcesByKey = {};
|
|
21060
21050
|
for (const driverKey of driverRegistry.list()) {
|
|
21061
21051
|
if (driverKey === "(default)") continue;
|
|
@@ -22457,8 +22447,11 @@ var DEV_PORT_FILENAME = ".rebase-dev-port";
|
|
|
22457
22447
|
* Try to `listen` on `startPort`. If the port is busy (`EADDRINUSE`), increment
|
|
22458
22448
|
* and retry up to `maxAttempts` times.
|
|
22459
22449
|
*
|
|
22460
|
-
* When a port file
|
|
22461
|
-
*
|
|
22450
|
+
* When a port file written by a previous run exists *and that run asked for the
|
|
22451
|
+
* same `startPort`*, the port it landed on is tried first, so tsx watch restarts
|
|
22452
|
+
* keep the address the frontend was configured with. A different `startPort` means
|
|
22453
|
+
* the configuration changed and the file is ignored — an explicitly requested port
|
|
22454
|
+
* is never overridden by a stale one.
|
|
22462
22455
|
*
|
|
22463
22456
|
* Resolves with the port that was actually bound.
|
|
22464
22457
|
*
|
|
@@ -22486,8 +22479,10 @@ function listenWithPortRetry(server, startPort, options) {
|
|
|
22486
22479
|
if (portFileDir) try {
|
|
22487
22480
|
const portFile = path.join(portFileDir, DEV_PORT_FILENAME);
|
|
22488
22481
|
if (fs.existsSync(portFile)) {
|
|
22489
|
-
const
|
|
22490
|
-
|
|
22482
|
+
const [savedRaw, requestedRaw] = fs.readFileSync(portFile, "utf-8").trim().split(/\s+/);
|
|
22483
|
+
const saved = parseInt(savedRaw, 10);
|
|
22484
|
+
const requestedThen = requestedRaw === void 0 ? NaN : parseInt(requestedRaw, 10);
|
|
22485
|
+
if (saved > 0 && saved < 65536 && saved !== startPort && (Number.isNaN(requestedThen) || requestedThen === startPort)) affinityPort = saved;
|
|
22491
22486
|
}
|
|
22492
22487
|
} catch {}
|
|
22493
22488
|
return new Promise((resolve, reject) => {
|
|
@@ -22505,24 +22500,29 @@ function listenWithPortRetry(server, startPort, options) {
|
|
|
22505
22500
|
}
|
|
22506
22501
|
const port = portsToTry[index];
|
|
22507
22502
|
attempt++;
|
|
22508
|
-
const
|
|
22509
|
-
|
|
22510
|
-
server.removeListener("error", onError);
|
|
22511
|
-
tryNext(index + 1);
|
|
22512
|
-
} else reject(err);
|
|
22513
|
-
};
|
|
22514
|
-
server.once("error", onError);
|
|
22515
|
-
server.listen(port, host, () => {
|
|
22516
|
-
server.removeListener("error", onError);
|
|
22503
|
+
const onListening = () => {
|
|
22504
|
+
cleanup();
|
|
22517
22505
|
if (portFileDir) {
|
|
22518
22506
|
try {
|
|
22519
22507
|
const portFile = path.join(portFileDir, DEV_PORT_FILENAME);
|
|
22520
|
-
fs.writeFileSync(portFile,
|
|
22508
|
+
fs.writeFileSync(portFile, `${port} ${startPort}`, "utf-8");
|
|
22521
22509
|
} catch {}
|
|
22522
22510
|
writeStateFile(portFileDir, port, options?.serviceKey);
|
|
22523
22511
|
}
|
|
22524
22512
|
resolve(port);
|
|
22525
|
-
}
|
|
22513
|
+
};
|
|
22514
|
+
const onError = (err) => {
|
|
22515
|
+
cleanup();
|
|
22516
|
+
if (err.code === "EADDRINUSE") tryNext(index + 1);
|
|
22517
|
+
else reject(err);
|
|
22518
|
+
};
|
|
22519
|
+
function cleanup() {
|
|
22520
|
+
server.removeListener("listening", onListening);
|
|
22521
|
+
server.removeListener("error", onError);
|
|
22522
|
+
}
|
|
22523
|
+
server.once("error", onError);
|
|
22524
|
+
server.once("listening", onListening);
|
|
22525
|
+
server.listen(port, host);
|
|
22526
22526
|
}
|
|
22527
22527
|
tryNext(0);
|
|
22528
22528
|
});
|
|
@@ -23744,6 +23744,7 @@ async function bootFromBundle(options = {}) {
|
|
|
23744
23744
|
const server = createServer(getRequestListener(app.fetch));
|
|
23745
23745
|
const usersCollection = await loadUsersCollection(bundle);
|
|
23746
23746
|
const storage = resolveStorageSources(process.env, storageSourceDefs, path.join(bundle.dir, "uploads"));
|
|
23747
|
+
await ensureCollectionSchema(bundle, dataSources, env);
|
|
23747
23748
|
const backend = await initializeRebaseBackend({
|
|
23748
23749
|
server,
|
|
23749
23750
|
app,
|
|
@@ -23953,6 +23954,32 @@ async function runFromBundle(options = {}) {
|
|
|
23953
23954
|
process.exit(1);
|
|
23954
23955
|
}
|
|
23955
23956
|
}
|
|
23957
|
+
/**
|
|
23958
|
+
* Bring the database's collection tables up to date before serving.
|
|
23959
|
+
*
|
|
23960
|
+
* Delegates to whichever driver bootstrapped the default data source; a driver
|
|
23961
|
+
* without `ensureCollectionSchema` (a schemaless one, or an older build) simply
|
|
23962
|
+
* skips, which is why this cannot break an existing deployment.
|
|
23963
|
+
*
|
|
23964
|
+
* Failure is fatal on purpose. Booting anyway would produce exactly the state
|
|
23965
|
+
* this exists to prevent — an app that answers sign-in and 500s every data
|
|
23966
|
+
* request — and a crash-looping pod with the DDL error in its logs is a far
|
|
23967
|
+
* better signal than a running one that silently cannot serve.
|
|
23968
|
+
*/
|
|
23969
|
+
async function ensureCollectionSchema(bundle, dataSources, env) {
|
|
23970
|
+
if ((env.REBASE_MIGRATE_ON_BOOT || "ensure") === "none") {
|
|
23971
|
+
logger.info("REBASE_MIGRATE_ON_BOOT=none — leaving the database schema untouched.");
|
|
23972
|
+
return;
|
|
23973
|
+
}
|
|
23974
|
+
if ((bundle.manifest.mode ?? "cms") !== "cms") return;
|
|
23975
|
+
if (!bundle.collectionsDir) return;
|
|
23976
|
+
const primary = dataSources[0];
|
|
23977
|
+
if (!primary?.bootstrapper.ensureCollectionSchema) return;
|
|
23978
|
+
const collections = await loadCollectionsFromDirectory(bundle.collectionsDir);
|
|
23979
|
+
if (collections.length === 0) return;
|
|
23980
|
+
const { applied } = await primary.bootstrapper.ensureCollectionSchema(collections, primary.connection, (message) => logger.info(`schema: ${message}`));
|
|
23981
|
+
logger.info(applied > 0 ? `Applied ${applied} additive schema change(s) before boot.` : "Collection schema is up to date.");
|
|
23982
|
+
}
|
|
23956
23983
|
//#endregion
|
|
23957
23984
|
//#region src/api/contract-routes.ts
|
|
23958
23985
|
var contract_routes_exports = /* @__PURE__ */ __exportAll({ createContractRoutes: () => createContractRoutes });
|