@rebasepro/server 0.14.0 → 0.14.1-canary.g7e666eb
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/rest/query-parser.d.ts +15 -1
- package/dist/api/rest/write-validation.d.ts +26 -0
- package/dist/auth/interfaces.d.ts +14 -1
- package/dist/auth/jwt.d.ts +30 -2
- package/dist/{auth-CYoPVf-E.js → auth-BQcdhMBL.js} +64 -165
- package/dist/auth-BQcdhMBL.js.map +1 -0
- package/dist/{cron-store-Dvr4Y1sZ.js → cron-store-D5dUNviq.js} +2 -2
- package/dist/{cron-store-Dvr4Y1sZ.js.map → cron-store-D5dUNviq.js.map} +1 -1
- package/dist/ddl-bootstrap-BhXbTnBl.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +267 -279
- package/dist/index.es.js.map +1 -1
- package/dist/{jwt-_IFqfTOg.js → jwt-CYGFT0ih.js} +297 -7
- package/dist/jwt-CYGFT0ih.js.map +1 -0
- package/dist/{openapi-generator-DPKtUC9X.js → openapi-generator-BWL8F2La.js} +24 -3
- package/dist/openapi-generator-BWL8F2La.js.map +1 -0
- package/dist/{request-timeout-RivJsME0.js → request-timeout-BuFoEKwT.js} +6 -3
- package/dist/request-timeout-BuFoEKwT.js.map +1 -0
- package/dist/{src-C7rkDGxA.js → src-8XDWyDfR.js} +84 -13
- package/dist/src-8XDWyDfR.js.map +1 -0
- package/dist/src-Cz9nMgUR.js.map +1 -1
- package/dist/storage/keys.d.ts +17 -0
- package/dist/storage/routes.d.ts +1 -1
- package/dist/storage/storage-registry.d.ts +46 -4
- package/dist/storage/tus-handler.d.ts +1 -1
- package/package.json +5 -5
- package/dist/auth-CYoPVf-E.js.map +0 -1
- package/dist/jwt-_IFqfTOg.js.map +0 -1
- package/dist/openapi-generator-DPKtUC9X.js.map +0 -1
- package/dist/request-timeout-RivJsME0.js.map +0 -1
- package/dist/src-C7rkDGxA.js.map +0 -1
package/dist/index.es.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
import process from "process";
|
|
3
3
|
__createRequire(import.meta.url);
|
|
4
|
-
import { A as RebaseApiError,
|
|
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-8XDWyDfR.js";
|
|
5
5
|
import { c as DEFAULT_DATA_SOURCE_KEY, i as getCollectionDataPath, m as toCanonicalOp, n as RLS_ROLES_SQL, o as isPostgresCollectionConfig, r as RLS_UID_SQL, t as RLS_JWT_SQL } from "./src-Cz9nMgUR.js";
|
|
6
6
|
import { n as ADMIN_PROPERTY_KEYS, t as ADMIN_COLLECTION_KEYS } from "./admin_block-H86dCPsj.js";
|
|
7
7
|
import { i as isSQLAdmin, r as revokeInternalTableSql } from "./ddl-bootstrap-BhXbTnBl.js";
|
|
8
|
+
import { C as DEFAULT_STORAGE_SOURCE_KEY, E as storageEnvSuffix, T as normalizeStorageSources, _ as InvalidStorageKeyError, b as canonicalStorageKey, g as InvalidStorageBucketError, i as generateDownloadToken, n as configureJwt, v as canonicalStorageBucket, w as findStorageSuffixCollision, y as canonicalStorageId } from "./jwt-CYGFT0ih.js";
|
|
8
9
|
import { n as createContractRoutes } from "./contract-routes-Bet-eCNJ.js";
|
|
9
|
-
import { $ as fileTokenAuth, A as MemoryRateLimitStore, B as getEmailVerificationTemplate, C as ZodNumber,
|
|
10
|
+
import { $ as fileTokenAuth, A as MemoryRateLimitStore, B as getEmailVerificationTemplate, C as ZodNumber, D as _coercedNumber, E as string, F as resolveAuthHooks, G as RawHtml, H as getPasswordResetTemplate, I as hashPassword, J as raw, K as escapeHtml, L as validatePasswordStrength, M as createEmailService, N as assertEmailLinkBases, O as DEFAULT_FUNCTIONS_ANONYMOUS_LIMIT, P as resolveEmailLinkBase, Q as extractUserFromToken, R as verifyPassword, S as createBuiltinAuthAdapter, T as object, U as getUserInvitationTemplate, V as getMagicLinkTemplate, W as getWelcomeEmailTemplate, X as createAuthMiddleware, Y as createAdapterAuthMiddleware, Z as createRequireAuth, _ as createLinkedinProvider, _t as parseQueryOptions, a as createSpotifyProvider, at as createApiKeyPreAuth, b as pkceTokenParams, bt as isPublicStoragePath, c as createGitLabProvider, ct as isApiKeyToken, d as createFacebookProvider, dt as safeCompare, et as optionalAuth, f as createAppleProvider, ft as SERVICE_IDENTITY, g as createGitHubProvider, gt as orderByEntriesToTuples, h as verifyOidcIdToken, ht as isOperationAllowed, i as createApiKeyStore, it as requireAuth, j as SMTPEmailService, k as createDataRateLimiter, l as createDiscordProvider, lt as validateApiKey, m as tryVerifyOidcIdToken, mt as httpMethodToOperation, n as createCustomAuthAdapter, nt as queryTokenAuth, o as createSlackProvider, ot as createFunctionApiKeyGuard, p as createMicrosoftProvider, pt as scopeDataDriver, q as html, r as createApiKeyRoutes, rt as requireAdmin, s as createBitbucketProvider, st as createStorageApiKeyGuard, tt as publicObjectAuth, u as createTwitterProvider, ut as extractBearerToken, v as createGoogleProvider, vt as resolveListLimitParam, w as _enum, x as providerVerifiedEmail, y as oauthCodeFlowSchema, yt as PUBLIC_STORAGE_PREFIX, z as generateSecurePassword } from "./auth-BQcdhMBL.js";
|
|
10
11
|
import { t as logger } from "./logger-DfvF_8r-.js";
|
|
11
12
|
import { n as errorHandler, t as ApiError } from "./errors-EBYiaJ2E.js";
|
|
12
|
-
import { i as generateDownloadToken, n as configureJwt } from "./jwt-_IFqfTOg.js";
|
|
13
13
|
import { t as logMiddleware } from "./logs-routes-BYA72C_C.js";
|
|
14
14
|
import { n as DOCUMENT_MIME_TYPES, r as IMAGE_MIME_TYPES, t as DEFAULT_MAX_FILE_SIZE } from "./types-DSnOC4mF.js";
|
|
15
15
|
import { t as S3StorageController } from "./S3StorageController-B2EDXXMH.js";
|
|
@@ -19,7 +19,7 @@ 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-D5dUNviq.js";
|
|
23
23
|
import { a as parseBackupTimestamp, i as parseBackupDestination, n as createBackupRoutes, o as readBackupBytes, r as listBackupObjects } from "./backup-C6ljYVTp.js";
|
|
24
24
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
25
25
|
import * as fs$2 from "fs";
|
|
@@ -49,153 +49,6 @@ import { secureHeaders } from "hono/secure-headers";
|
|
|
49
49
|
import { getRequestListener } from "@hono/node-server";
|
|
50
50
|
import { execFile } from "node:child_process";
|
|
51
51
|
import { promisify as promisify$1 } from "node:util";
|
|
52
|
-
//#region ../types/src/types/storage_source.ts
|
|
53
|
-
/**
|
|
54
|
-
* Describes a named storage backend — a place files live.
|
|
55
|
-
*
|
|
56
|
-
* Declared once and shared front + back: the frontend uses it to decide
|
|
57
|
-
* transport (HTTP proxy vs direct SDK), the backend uses the same `key`
|
|
58
|
-
* to resolve a StorageController, and collection properties reference
|
|
59
|
-
* a definition by its `key` via `StorageConfig.storageSource`.
|
|
60
|
-
*
|
|
61
|
-
* This mirrors the {@link DataSourceDefinition} pattern used for databases.
|
|
62
|
-
*
|
|
63
|
-
* @group Models
|
|
64
|
-
*/
|
|
65
|
-
/**
|
|
66
|
-
* The default storage source key, used when a property does not specify
|
|
67
|
-
* a `storageSource`. Shared by the frontend and backend registries so
|
|
68
|
-
* both agree on "the default storage backend".
|
|
69
|
-
* @group Models
|
|
70
|
-
*/
|
|
71
|
-
var DEFAULT_STORAGE_SOURCE_KEY = "(default)";
|
|
72
|
-
/**
|
|
73
|
-
* The environment-variable suffix for a storage or data source key.
|
|
74
|
-
*
|
|
75
|
-
* `""` for the default source — so a single-bucket project keeps configuring
|
|
76
|
-
* plain `S3_BUCKET` — and `__<KEY>` for every named one, uppercased with
|
|
77
|
-
* non-alphanumerics collapsed to underscores: `media-cdn` → `S3_BUCKET__MEDIA_CDN`.
|
|
78
|
-
*
|
|
79
|
-
* The rule derives the variable name from the declared key rather than
|
|
80
|
-
* discovering keys by scanning the environment. Scanning would have to guess how
|
|
81
|
-
* `S3_BUCKET__MEDIA_CDN` splits into a key; deriving cannot be ambiguous, and a
|
|
82
|
-
* typo surfaces as a missing source at boot instead of a silently ignored
|
|
83
|
-
* variable.
|
|
84
|
-
*
|
|
85
|
-
* It lives in this package, with no dependencies, because four things must agree
|
|
86
|
-
* on it exactly: the CLI (validating a build), the runtime (reading its own
|
|
87
|
-
* environment), the control plane (writing a tenant's Secret), and the docs. A
|
|
88
|
-
* second implementation of a naming convention is a second chance to disagree.
|
|
89
|
-
*
|
|
90
|
-
* @group Models
|
|
91
|
-
*/
|
|
92
|
-
function storageEnvSuffix(key, defaultKey = DEFAULT_STORAGE_SOURCE_KEY) {
|
|
93
|
-
if (!key || key === defaultKey) return "";
|
|
94
|
-
const normalized = key.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
95
|
-
if (!normalized) throw new Error(`Source key "${key}" cannot be turned into an environment variable name. Use a key containing at least one letter or digit.`);
|
|
96
|
-
return `__${normalized}`;
|
|
97
|
-
}
|
|
98
|
-
/**
|
|
99
|
-
* Two distinct keys that collapse onto the same variable name, or `null`.
|
|
100
|
-
*
|
|
101
|
-
* `media-cdn` and `media_cdn` are different source keys but the same suffix, so
|
|
102
|
-
* without this one of them silently reads the other's configuration. Returns the
|
|
103
|
-
* offending pair rather than throwing, so each caller can raise it in its own
|
|
104
|
-
* idiom — a `BundleError` at boot, a build failure in the CLI, a rejected deploy
|
|
105
|
-
* in a control plane.
|
|
106
|
-
*
|
|
107
|
-
* @group Models
|
|
108
|
-
*/
|
|
109
|
-
function findStorageSuffixCollision(keys, defaultKey = DEFAULT_STORAGE_SOURCE_KEY) {
|
|
110
|
-
const seen = /* @__PURE__ */ new Map();
|
|
111
|
-
for (const key of keys) {
|
|
112
|
-
const suffix = storageEnvSuffix(key, defaultKey);
|
|
113
|
-
const existing = seen.get(suffix);
|
|
114
|
-
if (existing !== void 0 && existing !== key) return {
|
|
115
|
-
a: existing,
|
|
116
|
-
b: key,
|
|
117
|
-
suffix
|
|
118
|
-
};
|
|
119
|
-
seen.set(suffix, key);
|
|
120
|
-
}
|
|
121
|
-
return null;
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Merge the two places a project may declare storage sources into one list.
|
|
125
|
-
*
|
|
126
|
-
* `rebase.json` is authoritative for every field it states. Config code may add
|
|
127
|
-
* sources it does not mention and fill in fields it left out, but may not
|
|
128
|
-
* contradict it: the manifest is what a host reads to decide which buckets need
|
|
129
|
-
* configuring, and a runtime that quietly disagreed with it would put the
|
|
130
|
-
* console back to describing a topology the tenant does not have — the exact
|
|
131
|
-
* failure this whole mechanism exists to end.
|
|
132
|
-
*
|
|
133
|
-
* Note what is *not* here: no default source is invented when both inputs are
|
|
134
|
-
* empty. That decision belongs to the resolver, which knows whether declaring
|
|
135
|
-
* nothing means "one plain bucket" (it does) or "no storage at all".
|
|
136
|
-
*
|
|
137
|
-
* @group Models
|
|
138
|
-
*/
|
|
139
|
-
function normalizeStorageSources(declared, exported) {
|
|
140
|
-
const merged = /* @__PURE__ */ new Map();
|
|
141
|
-
const declaredEntries = Array.isArray(declared) ? declared.filter((d) => d?.key).map((d) => [d.key, d]) : Object.entries(declared ?? {});
|
|
142
|
-
for (const [key, config] of declaredEntries) merged.set(key, {
|
|
143
|
-
key,
|
|
144
|
-
engine: config.engine,
|
|
145
|
-
transport: config.transport ?? "server",
|
|
146
|
-
...config.label !== void 0 ? { label: config.label } : {}
|
|
147
|
-
});
|
|
148
|
-
for (const definition of exported ?? []) {
|
|
149
|
-
if (!definition?.key) continue;
|
|
150
|
-
const existing = merged.get(definition.key);
|
|
151
|
-
if (!existing) {
|
|
152
|
-
merged.set(definition.key, {
|
|
153
|
-
key: definition.key,
|
|
154
|
-
engine: definition.engine,
|
|
155
|
-
transport: definition.transport ?? "server",
|
|
156
|
-
...definition.label !== void 0 ? { label: definition.label } : {}
|
|
157
|
-
});
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
if (existing.label === void 0 && definition.label !== void 0) existing.label = definition.label;
|
|
161
|
-
}
|
|
162
|
-
return Array.from(merged.values());
|
|
163
|
-
}
|
|
164
|
-
//#endregion
|
|
165
|
-
//#region ../common/src/data/sort-dialect.ts
|
|
166
|
-
/**
|
|
167
|
-
* Sort-order wire codec.
|
|
168
|
-
*
|
|
169
|
-
* This is the ONLY module that knows about the colon-delimited wire format
|
|
170
|
-
* (`"field:direction"`) used in HTTP query parameters.
|
|
171
|
-
* Everything else speaks {@link OrderByTuple} exclusively.
|
|
172
|
-
*
|
|
173
|
-
* Mirrors the filter architecture in `filter-dialect.ts`.
|
|
174
|
-
*
|
|
175
|
-
* @module
|
|
176
|
-
*/
|
|
177
|
-
/**
|
|
178
|
-
* Serialize an {@link OrderByTuple} to the wire format `"field:direction"`.
|
|
179
|
-
*
|
|
180
|
-
* **Runtime tolerance:** if the input is already a well-formed wire string
|
|
181
|
-
* (from an untyped JS caller), it is returned unchanged.
|
|
182
|
-
* This is undocumented tolerance, not public API — don't rely on it.
|
|
183
|
-
*
|
|
184
|
-
* @param orderBy - A canonical `[field, direction]` tuple, or at runtime
|
|
185
|
-
* possibly a pre-serialized string (undocumented tolerance).
|
|
186
|
-
* @returns The wire-format string, or `undefined` if the input is falsy.
|
|
187
|
-
*
|
|
188
|
-
* @remarks
|
|
189
|
-
* Field names containing `:` are representable in the tuple form but
|
|
190
|
-
* **not** on the wire — this is an inherent limitation of the colon-delimited
|
|
191
|
-
* encoding and is not resolved here.
|
|
192
|
-
*/
|
|
193
|
-
function serializeOrderBy(orderBy) {
|
|
194
|
-
if (!orderBy) return void 0;
|
|
195
|
-
if (typeof orderBy === "string") return orderBy;
|
|
196
|
-
return `${orderBy[0]}:${orderBy[1]}`;
|
|
197
|
-
}
|
|
198
|
-
//#endregion
|
|
199
52
|
//#region src/collections/BackendCollectionRegistry.ts
|
|
200
53
|
/**
|
|
201
54
|
* Backend-agnostic collection registry.
|
|
@@ -1083,6 +936,35 @@ function projectResponseFields(rows, fields, collection, options) {
|
|
|
1083
936
|
return projected;
|
|
1084
937
|
});
|
|
1085
938
|
}
|
|
939
|
+
/**
|
|
940
|
+
* Both write checks, as one call, for a transport that is not the REST router.
|
|
941
|
+
*
|
|
942
|
+
* The REST routes run `assertKnownWriteFields` and `assertWriteValuesValid`
|
|
943
|
+
* back to back on the caller's body, seven times over — and they were the only
|
|
944
|
+
* place either ran. The WebSocket `SAVE` handler took a client payload straight
|
|
945
|
+
* to `driver.save`, so the same write arrived validated through one door and
|
|
946
|
+
* unvalidated through the other: `PATCH /api/data/users/1 { age: 999 }` was a
|
|
947
|
+
* 400, and the socket wrote it.
|
|
948
|
+
*
|
|
949
|
+
* Exported for the sockets in `@rebasepro/server-postgres` and
|
|
950
|
+
* `@rebasepro/server-mongo`, which are the other request boundaries. Not the
|
|
951
|
+
* route builder — `index.ts` keeps that internal, and this is a rule rather
|
|
952
|
+
* than wiring.
|
|
953
|
+
*
|
|
954
|
+
* It stays at the boundary rather than moving into the driver deliberately: it
|
|
955
|
+
* validates what a *caller sent*, before `beforeSave` gets a chance to fill in
|
|
956
|
+
* or rewrite anything. In-process writes through `rebase.data` are trusted
|
|
957
|
+
* server code and are not run through it, which is the placement the REST layer
|
|
958
|
+
* already chose.
|
|
959
|
+
*
|
|
960
|
+
* @param values the caller's payload, exactly as it arrived
|
|
961
|
+
* @param collection resolved from the registry by path — never the copy the
|
|
962
|
+
* client sent, or the rules would be the caller's to pick
|
|
963
|
+
*/
|
|
964
|
+
function assertWriteRequestValid(values, collection) {
|
|
965
|
+
assertKnownWriteFields(values, collection);
|
|
966
|
+
assertWriteValuesValid(values, collection);
|
|
967
|
+
}
|
|
1086
968
|
//#endregion
|
|
1087
969
|
//#region src/api/rest/idempotency.ts
|
|
1088
970
|
/**
|
|
@@ -1495,12 +1377,11 @@ var RestApiGenerator = class {
|
|
|
1495
1377
|
logical: queryOptions.logical,
|
|
1496
1378
|
limit: queryOptions.limit,
|
|
1497
1379
|
offset: queryOptions.offset,
|
|
1498
|
-
orderBy: queryOptions.orderBy
|
|
1499
|
-
order: queryOptions.orderBy?.[0]?.direction === "desc" ? "desc" : "asc",
|
|
1380
|
+
orderBy: orderByEntriesToTuples(queryOptions.orderBy),
|
|
1500
1381
|
searchString,
|
|
1501
1382
|
searchExplain,
|
|
1502
1383
|
vectorSearch: queryOptions.vectorSearch
|
|
1503
|
-
}, queryOptions.include) : await this.fetchRawCollection(driver, resolvedCollection, queryOptions, searchString);
|
|
1384
|
+
}, queryOptions.include) : await this.fetchRawCollection(driver, resolvedCollection, queryOptions, searchString, searchExplain);
|
|
1504
1385
|
const total = await this.countRawEntities(driver, resolvedCollection, queryOptions, searchString);
|
|
1505
1386
|
return c.json({
|
|
1506
1387
|
data: projectResponseFields(entities, queryOptions.fields, resolvedCollection, { include: queryOptions.include }),
|
|
@@ -1829,7 +1710,8 @@ var RestApiGenerator = class {
|
|
|
1829
1710
|
path: parsed.collectionPath,
|
|
1830
1711
|
filter: queryOptions.where,
|
|
1831
1712
|
logical: queryOptions.logical,
|
|
1832
|
-
searchString
|
|
1713
|
+
searchString,
|
|
1714
|
+
vectorSearch: queryOptions.vectorSearch
|
|
1833
1715
|
}) : 0;
|
|
1834
1716
|
return c.json({ count: total });
|
|
1835
1717
|
} else if (parsed.id) {
|
|
@@ -1853,9 +1735,9 @@ var RestApiGenerator = class {
|
|
|
1853
1735
|
logical: queryOptions.logical,
|
|
1854
1736
|
limit: queryOptions.limit,
|
|
1855
1737
|
offset: queryOptions.offset,
|
|
1856
|
-
orderBy: queryOptions.orderBy
|
|
1857
|
-
|
|
1858
|
-
|
|
1738
|
+
orderBy: orderByEntriesToTuples(queryOptions.orderBy),
|
|
1739
|
+
searchString,
|
|
1740
|
+
vectorSearch: queryOptions.vectorSearch
|
|
1859
1741
|
};
|
|
1860
1742
|
const entities = fetchService ? await fetchService.fetchCollectionForRest(parsed.collectionPath, listOptions, queryOptions.include) : await driver.fetchCollection({
|
|
1861
1743
|
path: parsed.collectionPath,
|
|
@@ -1865,7 +1747,8 @@ var RestApiGenerator = class {
|
|
|
1865
1747
|
path: parsed.collectionPath,
|
|
1866
1748
|
filter: queryOptions.where,
|
|
1867
1749
|
logical: queryOptions.logical,
|
|
1868
|
-
searchString
|
|
1750
|
+
searchString,
|
|
1751
|
+
vectorSearch: queryOptions.vectorSearch
|
|
1869
1752
|
}) : entities.length;
|
|
1870
1753
|
const listCollection = this.resolveNestedWriteCollection(parsed.collectionPath);
|
|
1871
1754
|
return c.json({
|
|
@@ -1960,17 +1843,17 @@ var RestApiGenerator = class {
|
|
|
1960
1843
|
/**
|
|
1961
1844
|
* Fetch raw collection data without Entity wrapper (fallback for non-Postgres)
|
|
1962
1845
|
*/
|
|
1963
|
-
async fetchRawCollection(driver, collection, queryOptions, searchString) {
|
|
1846
|
+
async fetchRawCollection(driver, collection, queryOptions, searchString, searchExplain) {
|
|
1964
1847
|
return await driver.fetchCollection({
|
|
1965
1848
|
path: getCollectionDataPath(collection),
|
|
1966
1849
|
collection,
|
|
1967
1850
|
filter: queryOptions.where,
|
|
1968
1851
|
logical: queryOptions.logical,
|
|
1969
1852
|
limit: queryOptions.limit,
|
|
1970
|
-
orderBy: queryOptions.orderBy
|
|
1971
|
-
|
|
1972
|
-
startAfter: queryOptions.offset ? String(queryOptions.offset) : void 0,
|
|
1853
|
+
orderBy: orderByEntriesToTuples(queryOptions.orderBy),
|
|
1854
|
+
offset: queryOptions.offset,
|
|
1973
1855
|
searchString,
|
|
1856
|
+
searchExplain,
|
|
1974
1857
|
vectorSearch: queryOptions.vectorSearch
|
|
1975
1858
|
});
|
|
1976
1859
|
}
|
|
@@ -1983,7 +1866,8 @@ var RestApiGenerator = class {
|
|
|
1983
1866
|
collection,
|
|
1984
1867
|
filter: queryOptions.where,
|
|
1985
1868
|
logical: queryOptions.logical,
|
|
1986
|
-
searchString
|
|
1869
|
+
searchString,
|
|
1870
|
+
vectorSearch: queryOptions.vectorSearch
|
|
1987
1871
|
}) : 0;
|
|
1988
1872
|
}
|
|
1989
1873
|
/**
|
|
@@ -2439,6 +2323,129 @@ var LocalStorageController = class {
|
|
|
2439
2323
|
}
|
|
2440
2324
|
};
|
|
2441
2325
|
//#endregion
|
|
2326
|
+
//#region src/storage/storage-registry.ts
|
|
2327
|
+
/**
|
|
2328
|
+
* Storage Registry
|
|
2329
|
+
*
|
|
2330
|
+
* Manages multiple storage controllers for Rebase backend.
|
|
2331
|
+
* Allows different storage backends for different use cases.
|
|
2332
|
+
*
|
|
2333
|
+
* Usage:
|
|
2334
|
+
* - Single storage: Pass a single StorageController → maps to "(default)"
|
|
2335
|
+
* - Multiple storages: Pass a map of { storageId: StorageController }
|
|
2336
|
+
* - String properties use `storageId` in their config to specify which storage to use
|
|
2337
|
+
* - Properties without `storageId` fallback to "(default)"
|
|
2338
|
+
*/
|
|
2339
|
+
/**
|
|
2340
|
+
* The default storage identifier used when:
|
|
2341
|
+
* - A single storage controller is provided (not a map)
|
|
2342
|
+
* - A property doesn't specify a storageId
|
|
2343
|
+
*
|
|
2344
|
+
* An alias rather than a second `"(default)"` literal: the same string is the
|
|
2345
|
+
* registry key here, the source key a project declares in `rebase.json`, and
|
|
2346
|
+
* the scope baked into a download token. Two literals that must stay equal are
|
|
2347
|
+
* a divergence waiting to happen — and this one would silently unscope tokens.
|
|
2348
|
+
*/
|
|
2349
|
+
var DEFAULT_STORAGE_ID = DEFAULT_STORAGE_SOURCE_KEY;
|
|
2350
|
+
/**
|
|
2351
|
+
* A request named a storage source that is not registered.
|
|
2352
|
+
*
|
|
2353
|
+
* This used to be a `logger.warn` and a fallback to the default source, which
|
|
2354
|
+
* is the wrong answer in a way that is hard to notice: the caller asks for
|
|
2355
|
+
* `media`, the `storageAuthorize` hook is asked about `media`, and the bytes
|
|
2356
|
+
* are read from — or written to — `(default)`. A hook that widens access for
|
|
2357
|
+
* one named source therefore widens it for the default one, and the object the
|
|
2358
|
+
* hook approved is not the object the request touched. That is the same shape
|
|
2359
|
+
* as the non-canonical-key bypass: the check and the read disagree about which
|
|
2360
|
+
* object is in play.
|
|
2361
|
+
*
|
|
2362
|
+
* The failure is silent by construction. Both sources hold the *same key*,
|
|
2363
|
+
* because that is what makes a caller reach for a second source in the first
|
|
2364
|
+
* place, so the fallback returns plausible bytes rather than an error.
|
|
2365
|
+
*
|
|
2366
|
+
* It is also reachable without an attacker: a source declared in `rebase.json`
|
|
2367
|
+
* but not configured by the environment is skipped at boot and never
|
|
2368
|
+
* registered, while `GET /sources` still advertises it. The client asks for a
|
|
2369
|
+
* source it was told exists and silently gets a different bucket's contents.
|
|
2370
|
+
*
|
|
2371
|
+
* Carries `knownKeys` so the route layer can tell a typo from a source that was
|
|
2372
|
+
* declared but never configured — two different answers to the caller. The
|
|
2373
|
+
* registry itself does not know what was *declared*, only what was registered,
|
|
2374
|
+
* so it does not make that decision.
|
|
2375
|
+
*/
|
|
2376
|
+
var UnknownStorageSourceError = class extends Error {
|
|
2377
|
+
storageId;
|
|
2378
|
+
knownKeys;
|
|
2379
|
+
constructor(storageId, knownKeys) {
|
|
2380
|
+
super(`[StorageRegistry] Storage source "${storageId}" is not registered. Registered sources: ${knownKeys.length > 0 ? knownKeys.map((k) => `"${k}"`).join(", ") : "(none)"}.`);
|
|
2381
|
+
this.storageId = storageId;
|
|
2382
|
+
this.knownKeys = knownKeys;
|
|
2383
|
+
this.name = "UnknownStorageSourceError";
|
|
2384
|
+
}
|
|
2385
|
+
};
|
|
2386
|
+
/**
|
|
2387
|
+
* Default implementation of StorageRegistry
|
|
2388
|
+
*/
|
|
2389
|
+
var DefaultStorageRegistry = class DefaultStorageRegistry {
|
|
2390
|
+
controllers = /* @__PURE__ */ new Map();
|
|
2391
|
+
/**
|
|
2392
|
+
* Create a StorageRegistry from either a single controller or a map
|
|
2393
|
+
* @param input - Single StorageController (maps to "(default)") or Record<string, StorageController>
|
|
2394
|
+
*/
|
|
2395
|
+
static create(input) {
|
|
2396
|
+
const registry = new DefaultStorageRegistry();
|
|
2397
|
+
if (isStorageController(input)) registry.register(DEFAULT_STORAGE_ID, input);
|
|
2398
|
+
else {
|
|
2399
|
+
for (const [id, controller] of Object.entries(input)) if (isStorageController(controller)) registry.register(id, controller);
|
|
2400
|
+
if (!registry.has(DEFAULT_STORAGE_ID) && registry.size() > 0) {
|
|
2401
|
+
const firstId = Object.keys(input).find((k) => isStorageController(input[k]));
|
|
2402
|
+
if (firstId) {
|
|
2403
|
+
logger.warn(`[StorageRegistry] No "${DEFAULT_STORAGE_ID}" storage provided. Using "${firstId}" as the default.`);
|
|
2404
|
+
registry.register(DEFAULT_STORAGE_ID, input[firstId]);
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
return registry;
|
|
2409
|
+
}
|
|
2410
|
+
register(id, controller) {
|
|
2411
|
+
if (this.controllers.has(id)) logger.warn(`[StorageRegistry] Overwriting storage with id "${id}"`);
|
|
2412
|
+
this.controllers.set(id, controller);
|
|
2413
|
+
}
|
|
2414
|
+
getDefault() {
|
|
2415
|
+
const controller = this.controllers.get(DEFAULT_STORAGE_ID);
|
|
2416
|
+
if (!controller) throw new Error(`[StorageRegistry] No default storage registered. Register one with id "${DEFAULT_STORAGE_ID}" or pass a single StorageController.`);
|
|
2417
|
+
return controller;
|
|
2418
|
+
}
|
|
2419
|
+
get(id) {
|
|
2420
|
+
return this.controllers.get(canonicalStorageId(id));
|
|
2421
|
+
}
|
|
2422
|
+
getOrDefault(id) {
|
|
2423
|
+
const key = canonicalStorageId(id);
|
|
2424
|
+
if (key === DEFAULT_STORAGE_ID) return this.getDefault();
|
|
2425
|
+
const controller = this.controllers.get(key);
|
|
2426
|
+
if (controller) return controller;
|
|
2427
|
+
throw new UnknownStorageSourceError(key, this.list());
|
|
2428
|
+
}
|
|
2429
|
+
has(id) {
|
|
2430
|
+
return this.controllers.has(canonicalStorageId(id));
|
|
2431
|
+
}
|
|
2432
|
+
list() {
|
|
2433
|
+
return Array.from(this.controllers.keys());
|
|
2434
|
+
}
|
|
2435
|
+
size() {
|
|
2436
|
+
return this.controllers.size;
|
|
2437
|
+
}
|
|
2438
|
+
};
|
|
2439
|
+
/**
|
|
2440
|
+
* Type guard to check if an object is a StorageController
|
|
2441
|
+
* vs a Record<string, StorageController> (multiple storages)
|
|
2442
|
+
*/
|
|
2443
|
+
function isStorageController(obj) {
|
|
2444
|
+
if (typeof obj !== "object" || obj === null) return false;
|
|
2445
|
+
const controller = obj;
|
|
2446
|
+
return typeof controller.putObject === "function" && typeof controller.getSignedUrl === "function" && typeof controller.deleteObject === "function" && typeof controller.listObjects === "function" && typeof controller.getType === "function";
|
|
2447
|
+
}
|
|
2448
|
+
//#endregion
|
|
2442
2449
|
//#region src/storage/image-transform.ts
|
|
2443
2450
|
/**
|
|
2444
2451
|
* Image Transformation Service
|
|
@@ -2810,6 +2817,7 @@ var TusHandler = class {
|
|
|
2810
2817
|
throw new ApiError(400, "INVALID_STORAGE_BUCKET", err instanceof InvalidStorageBucketError ? err.message : "Invalid storage bucket");
|
|
2811
2818
|
}
|
|
2812
2819
|
const storageId = metadata.storageId || c.req.query("storageId") || void 0;
|
|
2820
|
+
if (storageId !== void 0 && this.storageRegistry && !this.storageRegistry.has(canonicalStorageId(storageId))) throw new ApiError(400, "UNKNOWN_STORAGE_SOURCE", `Unknown storage source "${storageId}". Available: ${this.storageRegistry.list().map((k) => `"${k}"`).join(", ") || "(none)"}.`, void 0, true);
|
|
2813
2821
|
if (this.authorizeUpload) await this.authorizeUpload(c, key, bucket || "default", storageId);
|
|
2814
2822
|
const filePath = join(this.tusDir, id);
|
|
2815
2823
|
await writeFile(filePath, Buffer.alloc(0));
|
|
@@ -2912,7 +2920,12 @@ var TusHandler = class {
|
|
|
2912
2920
|
async finalize(upload) {
|
|
2913
2921
|
const storageId = upload.storageId;
|
|
2914
2922
|
let targetController = this.storageController;
|
|
2915
|
-
if (this.storageRegistry)
|
|
2923
|
+
if (this.storageRegistry) try {
|
|
2924
|
+
targetController = storageId ? this.storageRegistry.getOrDefault(storageId) : this.storageRegistry.getDefault();
|
|
2925
|
+
} catch (err) {
|
|
2926
|
+
if (!(err instanceof UnknownStorageSourceError)) throw err;
|
|
2927
|
+
targetController = void 0;
|
|
2928
|
+
}
|
|
2916
2929
|
if (!targetController) {
|
|
2917
2930
|
logger.error("[TUS] Upload received but no StorageController is configured — nothing can be written", {
|
|
2918
2931
|
uploadId: upload.id,
|
|
@@ -3074,8 +3087,8 @@ function canonicalKeyOrBadRequest(key) {
|
|
|
3074
3087
|
* The bucket's counterpart to {@link canonicalKeyOrBadRequest}, and it exists
|
|
3075
3088
|
* for the same reason: the value routes a write, so it has to be checked where
|
|
3076
3089
|
* it enters rather than where it is used. Applied at every entry point a bucket
|
|
3077
|
-
* has — this route's multipart body, the
|
|
3078
|
-
* `Upload-Metadata` header.
|
|
3090
|
+
* has — this route's multipart body, the folder route's JSON body, the
|
|
3091
|
+
* `?bucket=` query, and the TUS `Upload-Metadata` header.
|
|
3079
3092
|
*/
|
|
3080
3093
|
function canonicalBucketOrBadRequest(bucket) {
|
|
3081
3094
|
try {
|
|
@@ -3175,13 +3188,46 @@ function createStorageRoutes(config) {
|
|
|
3175
3188
|
if (!allowed) throw ApiError.forbidden("Not authorized for this object");
|
|
3176
3189
|
};
|
|
3177
3190
|
/**
|
|
3178
|
-
* Resolve the storage controller for a request.
|
|
3179
|
-
*
|
|
3180
|
-
*
|
|
3191
|
+
* Resolve the storage controller for a request, or refuse the request.
|
|
3192
|
+
*
|
|
3193
|
+
* A `storageId` that names no registered source used to fall back to the
|
|
3194
|
+
* default one. That is silently wrong: the `storageAuthorize` hook is asked
|
|
3195
|
+
* about the source the caller named while the bytes come from `(default)`,
|
|
3196
|
+
* so the object the hook approved is not the object the request touched —
|
|
3197
|
+
* and because the two sources hold the same key, the fallback returns
|
|
3198
|
+
* plausible bytes instead of an error.
|
|
3199
|
+
*
|
|
3200
|
+
* The caller gets one of two answers, because they are two different
|
|
3201
|
+
* problems with two different fixes:
|
|
3202
|
+
*
|
|
3203
|
+
* - **501** if the source *is* declared in `rebase.json` but has no
|
|
3204
|
+
* credentials in this environment. It was skipped at boot rather than
|
|
3205
|
+
* crash-looping the backend, but `GET /sources` still advertises it, so a
|
|
3206
|
+
* client asking for it is not confused — the deployment is incomplete.
|
|
3207
|
+
* 501 (not 503) for the same reason the whole-storage stub uses it: this
|
|
3208
|
+
* is permanent until someone configures it, and the client's offline
|
|
3209
|
+
* queue retries 503 forever.
|
|
3210
|
+
* - **400** otherwise. The id names nothing this deployment has ever heard
|
|
3211
|
+
* of, which is a caller mistake. `expected`, so one client holding a
|
|
3212
|
+
* stale source name does not write a warning per request forever.
|
|
3181
3213
|
*/
|
|
3214
|
+
const declaredKeys = new Set((declaredSources ?? []).map((s) => s.key));
|
|
3215
|
+
const refuseUnknownSource = (storageId, knownKeys) => {
|
|
3216
|
+
if (declaredKeys.has(storageId)) throw new ApiError(501, "STORAGE_SOURCE_NOT_CONFIGURED", `Storage source "${storageId}" is declared but not configured on this deployment, so it cannot be read from or written to. Set its credentials (the ${storageId} suffixed environment variables) and redeploy.`);
|
|
3217
|
+
throw new ApiError(400, "UNKNOWN_STORAGE_SOURCE", `Unknown storage source "${storageId}". Available: ${knownKeys.length > 0 ? knownKeys.map((k) => `"${k}"`).join(", ") : "(none)"}.`, void 0, true);
|
|
3218
|
+
};
|
|
3182
3219
|
const resolveController = (storageId) => {
|
|
3183
|
-
if (registry)
|
|
3184
|
-
|
|
3220
|
+
if (registry) try {
|
|
3221
|
+
return registry.getOrDefault(storageId);
|
|
3222
|
+
} catch (err) {
|
|
3223
|
+
if (err instanceof UnknownStorageSourceError) return refuseUnknownSource(err.storageId, err.knownKeys);
|
|
3224
|
+
throw err;
|
|
3225
|
+
}
|
|
3226
|
+
if (controller) {
|
|
3227
|
+
const requested = canonicalStorageId(storageId);
|
|
3228
|
+
if (requested !== "(default)") return refuseUnknownSource(requested, [DEFAULT_STORAGE_SOURCE_KEY]);
|
|
3229
|
+
return controller;
|
|
3230
|
+
}
|
|
3185
3231
|
throw new Error("No storage controller or registry available");
|
|
3186
3232
|
};
|
|
3187
3233
|
/** Get the default controller (used for TUS and base-path derivation). */
|
|
@@ -3263,7 +3309,7 @@ function createStorageRoutes(config) {
|
|
|
3263
3309
|
const transformKeyPrefix = `${storageId || "(default)"}/${bucket}/${resolvedPath}`;
|
|
3264
3310
|
const transformOpts = transformOptionsOrBadRequest(c.req.query());
|
|
3265
3311
|
if (resolved.getType() === "local") {
|
|
3266
|
-
const absolutePath = resolved.getAbsolutePath(resolvedPath, bucket);
|
|
3312
|
+
const absolutePath = resolved.getAbsolutePath(resolvedPath, bucket ?? "default");
|
|
3267
3313
|
try {
|
|
3268
3314
|
await fsp.access(absolutePath);
|
|
3269
3315
|
} catch {
|
|
@@ -3324,7 +3370,7 @@ function createStorageRoutes(config) {
|
|
|
3324
3370
|
const scopedPath = `${bucket}/${resolvedPath}`;
|
|
3325
3371
|
if (isPublicStoragePath(scopedPath)) downloadConfig.metadata.public = true;
|
|
3326
3372
|
else {
|
|
3327
|
-
downloadConfig.metadata.token = generateDownloadToken(scopedPath, 300);
|
|
3373
|
+
downloadConfig.metadata.token = generateDownloadToken(scopedPath, 300, storageId);
|
|
3328
3374
|
downloadConfig.metadata.tokenExpiresIn = 300;
|
|
3329
3375
|
}
|
|
3330
3376
|
}
|
|
@@ -3384,9 +3430,10 @@ function createStorageRoutes(config) {
|
|
|
3384
3430
|
const storageId = typeof body.storageId === "string" ? body.storageId : c.req.query("storageId");
|
|
3385
3431
|
if (!folderPath || typeof folderPath !== "string") throw ApiError.badRequest("Folder path is required");
|
|
3386
3432
|
const resolved = resolveController(storageId);
|
|
3387
|
-
const {
|
|
3433
|
+
const { resolvedPath } = parseBucketAndPath(folderPath);
|
|
3434
|
+
const bucket = canonicalBucketOrBadRequest(typeof body.bucket === "string" ? body.bucket : void 0);
|
|
3388
3435
|
if (!resolvedPath || resolvedPath.trim() === "") throw ApiError.badRequest("Invalid folder path");
|
|
3389
|
-
await checkAuthorized(c, "write", resolvedPath, bucket, storageId);
|
|
3436
|
+
await checkAuthorized(c, "write", resolvedPath, bucket ?? "default", storageId);
|
|
3390
3437
|
if (resolved.getType() === "local") {
|
|
3391
3438
|
const absolutePath = resolved.getAbsolutePath(resolvedPath, bucket);
|
|
3392
3439
|
fs$1.mkdirSync(absolutePath, { recursive: true });
|
|
@@ -3395,7 +3442,8 @@ function createStorageRoutes(config) {
|
|
|
3395
3442
|
const emptyFile = new File([], key, { type: "application/x-directory" });
|
|
3396
3443
|
await resolved.putObject({
|
|
3397
3444
|
file: emptyFile,
|
|
3398
|
-
key
|
|
3445
|
+
key,
|
|
3446
|
+
bucket
|
|
3399
3447
|
});
|
|
3400
3448
|
}
|
|
3401
3449
|
return c.json({
|
|
@@ -3446,77 +3494,6 @@ function createStorageRoutes(config) {
|
|
|
3446
3494
|
return router;
|
|
3447
3495
|
}
|
|
3448
3496
|
//#endregion
|
|
3449
|
-
//#region src/storage/storage-registry.ts
|
|
3450
|
-
/**
|
|
3451
|
-
* The default storage identifier used when:
|
|
3452
|
-
* - A single storage controller is provided (not a map)
|
|
3453
|
-
* - A property doesn't specify a storageId
|
|
3454
|
-
*/
|
|
3455
|
-
var DEFAULT_STORAGE_ID = "(default)";
|
|
3456
|
-
/**
|
|
3457
|
-
* Default implementation of StorageRegistry
|
|
3458
|
-
*/
|
|
3459
|
-
var DefaultStorageRegistry = class DefaultStorageRegistry {
|
|
3460
|
-
controllers = /* @__PURE__ */ new Map();
|
|
3461
|
-
/**
|
|
3462
|
-
* Create a StorageRegistry from either a single controller or a map
|
|
3463
|
-
* @param input - Single StorageController (maps to "(default)") or Record<string, StorageController>
|
|
3464
|
-
*/
|
|
3465
|
-
static create(input) {
|
|
3466
|
-
const registry = new DefaultStorageRegistry();
|
|
3467
|
-
if (isStorageController(input)) registry.register(DEFAULT_STORAGE_ID, input);
|
|
3468
|
-
else {
|
|
3469
|
-
for (const [id, controller] of Object.entries(input)) if (isStorageController(controller)) registry.register(id, controller);
|
|
3470
|
-
if (!registry.has("(default)") && registry.size() > 0) {
|
|
3471
|
-
const firstId = Object.keys(input).find((k) => isStorageController(input[k]));
|
|
3472
|
-
if (firstId) {
|
|
3473
|
-
logger.warn(`[StorageRegistry] No "${DEFAULT_STORAGE_ID}" storage provided. Using "${firstId}" as the default.`);
|
|
3474
|
-
registry.register(DEFAULT_STORAGE_ID, input[firstId]);
|
|
3475
|
-
}
|
|
3476
|
-
}
|
|
3477
|
-
}
|
|
3478
|
-
return registry;
|
|
3479
|
-
}
|
|
3480
|
-
register(id, controller) {
|
|
3481
|
-
if (this.controllers.has(id)) logger.warn(`[StorageRegistry] Overwriting storage with id "${id}"`);
|
|
3482
|
-
this.controllers.set(id, controller);
|
|
3483
|
-
}
|
|
3484
|
-
getDefault() {
|
|
3485
|
-
const controller = this.controllers.get(DEFAULT_STORAGE_ID);
|
|
3486
|
-
if (!controller) throw new Error(`[StorageRegistry] No default storage registered. Register one with id "${DEFAULT_STORAGE_ID}" or pass a single StorageController.`);
|
|
3487
|
-
return controller;
|
|
3488
|
-
}
|
|
3489
|
-
get(id) {
|
|
3490
|
-
if (id === void 0 || id === null) return this.controllers.get(DEFAULT_STORAGE_ID);
|
|
3491
|
-
return this.controllers.get(id);
|
|
3492
|
-
}
|
|
3493
|
-
getOrDefault(id) {
|
|
3494
|
-
if (id === void 0 || id === null) return this.getDefault();
|
|
3495
|
-
const controller = this.controllers.get(id);
|
|
3496
|
-
if (controller) return controller;
|
|
3497
|
-
logger.warn(`[StorageRegistry] Storage "${id}" not found, falling back to "${DEFAULT_STORAGE_ID}"`);
|
|
3498
|
-
return this.getDefault();
|
|
3499
|
-
}
|
|
3500
|
-
has(id) {
|
|
3501
|
-
return this.controllers.has(id);
|
|
3502
|
-
}
|
|
3503
|
-
list() {
|
|
3504
|
-
return Array.from(this.controllers.keys());
|
|
3505
|
-
}
|
|
3506
|
-
size() {
|
|
3507
|
-
return this.controllers.size;
|
|
3508
|
-
}
|
|
3509
|
-
};
|
|
3510
|
-
/**
|
|
3511
|
-
* Type guard to check if an object is a StorageController
|
|
3512
|
-
* vs a Record<string, StorageController> (multiple storages)
|
|
3513
|
-
*/
|
|
3514
|
-
function isStorageController(obj) {
|
|
3515
|
-
if (typeof obj !== "object" || obj === null) return false;
|
|
3516
|
-
const controller = obj;
|
|
3517
|
-
return typeof controller.putObject === "function" && typeof controller.getSignedUrl === "function" && typeof controller.deleteObject === "function" && typeof controller.listObjects === "function" && typeof controller.getType === "function";
|
|
3518
|
-
}
|
|
3519
|
-
//#endregion
|
|
3520
3497
|
//#region src/storage/index.ts
|
|
3521
3498
|
/**
|
|
3522
3499
|
* Create a storage controller from a config object.
|
|
@@ -3610,7 +3587,7 @@ function assertStorageAccessControlConfigured(state, isProduction) {
|
|
|
3610
3587
|
*/
|
|
3611
3588
|
async function mountOpenApiDocs(app, basePath, enableSwagger, serverCollections, requireAuth) {
|
|
3612
3589
|
if (enableSwagger === false || serverCollections.length === 0) return;
|
|
3613
|
-
const { generateOpenApiSpec } = await import("./openapi-generator-
|
|
3590
|
+
const { generateOpenApiSpec } = await import("./openapi-generator-BWL8F2La.js");
|
|
3614
3591
|
app.get(`${basePath}/docs`, (c) => {
|
|
3615
3592
|
const spec = generateOpenApiSpec(serverCollections, {
|
|
3616
3593
|
basePath,
|
|
@@ -5092,9 +5069,15 @@ var SDKQueryBuilder = class {
|
|
|
5092
5069
|
}
|
|
5093
5070
|
/**
|
|
5094
5071
|
* Order the results by a specific column.
|
|
5072
|
+
*
|
|
5073
|
+
* Call it again to add a tie-breaker rather than replace the sort: keys
|
|
5074
|
+
* apply in the order they were added, so
|
|
5075
|
+
* `.orderBy("roles").orderBy("created_at", "desc")` sorts by role and
|
|
5076
|
+
* shows the newest first within each one.
|
|
5095
5077
|
*/
|
|
5096
5078
|
orderBy(column, direction = "asc") {
|
|
5097
|
-
this.params.orderBy
|
|
5079
|
+
const existing = normalizeOrderBy(this.params.orderBy) ?? [];
|
|
5080
|
+
this.params.orderBy = [...existing, [column, direction]];
|
|
5098
5081
|
return this;
|
|
5099
5082
|
}
|
|
5100
5083
|
/**
|
|
@@ -5409,8 +5392,7 @@ function createCollectionClient(transport, slug, ws) {
|
|
|
5409
5392
|
logical: params?.logical,
|
|
5410
5393
|
limit: params?.limit,
|
|
5411
5394
|
offset: window.driverOffset,
|
|
5412
|
-
orderBy: params?.orderBy
|
|
5413
|
-
order: params?.orderBy?.[1],
|
|
5395
|
+
orderBy: normalizeOrderBy(params?.orderBy),
|
|
5414
5396
|
searchString: params?.searchString,
|
|
5415
5397
|
searchExplain: params?.searchExplain
|
|
5416
5398
|
}, (incomingRows) => {
|
|
@@ -7896,23 +7878,30 @@ function matchesParams(row, params) {
|
|
|
7896
7878
|
* not shuffle rows between pages.
|
|
7897
7879
|
*/
|
|
7898
7880
|
function sortRows(rows, orderBy) {
|
|
7899
|
-
|
|
7900
|
-
|
|
7901
|
-
const sign = direction === "desc" ? -1 : 1;
|
|
7881
|
+
const keys = normalizeOrderBy(orderBy);
|
|
7882
|
+
if (!keys) return rows;
|
|
7902
7883
|
return rows.sort((a, b) => {
|
|
7903
|
-
const
|
|
7904
|
-
|
|
7905
|
-
|
|
7906
|
-
|
|
7907
|
-
|
|
7908
|
-
if (aNull && bNull) return tiebreak(a, b);
|
|
7909
|
-
return (aNull ? 1 : -1) * (direction === "desc" ? -1 : 1);
|
|
7910
|
-
}
|
|
7911
|
-
const cmp = compareValues(av, bv);
|
|
7912
|
-
if (cmp === void 0 || cmp === 0) return tiebreak(a, b);
|
|
7913
|
-
return cmp * sign;
|
|
7884
|
+
for (const [field, direction = "asc"] of keys) {
|
|
7885
|
+
const cmp = compareOnKey(a, b, field, direction);
|
|
7886
|
+
if (cmp !== 0) return cmp;
|
|
7887
|
+
}
|
|
7888
|
+
return tiebreak(a, b);
|
|
7914
7889
|
});
|
|
7915
7890
|
}
|
|
7891
|
+
/** One key's verdict: negative, positive, or 0 for "these two are equal here". */
|
|
7892
|
+
function compareOnKey(a, b, field, direction) {
|
|
7893
|
+
const av = a[field];
|
|
7894
|
+
const bv = b[field];
|
|
7895
|
+
const aNull = isNullish(toComparable(av));
|
|
7896
|
+
const bNull = isNullish(toComparable(bv));
|
|
7897
|
+
if (aNull || bNull) {
|
|
7898
|
+
if (aNull && bNull) return 0;
|
|
7899
|
+
return (aNull ? 1 : -1) * (direction === "desc" ? -1 : 1);
|
|
7900
|
+
}
|
|
7901
|
+
const cmp = compareValues(av, bv);
|
|
7902
|
+
if (cmp === void 0 || cmp === 0) return 0;
|
|
7903
|
+
return cmp * (direction === "desc" ? -1 : 1);
|
|
7904
|
+
}
|
|
7916
7905
|
function tiebreak(a, b) {
|
|
7917
7906
|
return compareValues(a.id, b.id) ?? 0;
|
|
7918
7907
|
}
|
|
@@ -8009,17 +7998,16 @@ function isExactlyEvaluable(params) {
|
|
|
8009
7998
|
* comparator.
|
|
8010
7999
|
*/
|
|
8011
8000
|
function isLocallySortable(rows, orderBy) {
|
|
8012
|
-
|
|
8013
|
-
|
|
8014
|
-
|
|
8001
|
+
const keys = normalizeOrderBy(orderBy);
|
|
8002
|
+
if (!keys) return true;
|
|
8003
|
+
return keys.every(([field]) => rows.every((row) => {
|
|
8015
8004
|
const value = toComparable(row[field]);
|
|
8016
|
-
if (isNullish(value))
|
|
8017
|
-
if (typeof value === "number" || typeof value === "boolean")
|
|
8018
|
-
if (typeof value === "bigint")
|
|
8019
|
-
if (typeof value === "string" && value.trim() !== "" && !Number.isNaN(Number(value)))
|
|
8005
|
+
if (isNullish(value)) return true;
|
|
8006
|
+
if (typeof value === "number" || typeof value === "boolean") return true;
|
|
8007
|
+
if (typeof value === "bigint") return true;
|
|
8008
|
+
if (typeof value === "string" && value.trim() !== "" && !Number.isNaN(Number(value))) return true;
|
|
8020
8009
|
return false;
|
|
8021
|
-
}
|
|
8022
|
-
return true;
|
|
8010
|
+
}));
|
|
8023
8011
|
}
|
|
8024
8012
|
/** Run a full query — filter, sort, paginate — over a set of rows. */
|
|
8025
8013
|
function runLocalQuery(rows, params) {
|
|
@@ -10185,7 +10173,7 @@ async function _initializeRebaseBackend(config) {
|
|
|
10185
10173
|
]) {
|
|
10186
10174
|
const providerConfig = safeAuthConfig[key];
|
|
10187
10175
|
if (providerConfig && requiredFields.every((f) => Boolean(providerConfig[f]))) {
|
|
10188
|
-
const createFn = (await import("./auth-
|
|
10176
|
+
const createFn = (await import("./auth-BQcdhMBL.js").then((n) => n.t))[factory];
|
|
10189
10177
|
oauthProviders.push(createFn(providerConfig));
|
|
10190
10178
|
}
|
|
10191
10179
|
}
|
|
@@ -10463,7 +10451,7 @@ async function _initializeRebaseBackend(config) {
|
|
|
10463
10451
|
if (config.functionsDir) {
|
|
10464
10452
|
const { loadFunctionsWithDiagnostics } = await import("./function-loader-DDS1v7YX.js").then((n) => n.t);
|
|
10465
10453
|
const { createFunctionRoutes } = await import("./function-routes-Btcez1T-.js").then((n) => n.n);
|
|
10466
|
-
const { createFunctionsRequestTimeout, resolveFunctionsTimeoutMs } = await import("./request-timeout-
|
|
10454
|
+
const { createFunctionsRequestTimeout, resolveFunctionsTimeoutMs } = await import("./request-timeout-BuFoEKwT.js");
|
|
10467
10455
|
const { functions: loadedFunctions, problems } = await loadFunctionsWithDiagnostics(config.functionsDir);
|
|
10468
10456
|
const functionsRouter = new Hono();
|
|
10469
10457
|
functionsRouter.onError(errorHandler);
|
|
@@ -10502,7 +10490,7 @@ async function _initializeRebaseBackend(config) {
|
|
|
10502
10490
|
const { loadCronJobsWithDiagnostics } = await import("./cron-loader-YhhQeVBM.js").then((n) => n.t);
|
|
10503
10491
|
const { CronScheduler } = await import("./cron-scheduler-DIpYBmZP.js").then((n) => n.n);
|
|
10504
10492
|
const { createCronRoutes } = await import("./cron-routes-maM_RlUu.js").then((n) => n.n);
|
|
10505
|
-
const { createCronStore } = await import("./cron-store-
|
|
10493
|
+
const { createCronStore } = await import("./cron-store-D5dUNviq.js").then((n) => n.n);
|
|
10506
10494
|
const { jobs: loadedCronJobs, problems: cronProblems } = await loadCronJobsWithDiagnostics(config.cronsDir);
|
|
10507
10495
|
cronScheduler = new CronScheduler();
|
|
10508
10496
|
cronScheduler.setClient(serverSingleton);
|
|
@@ -13411,6 +13399,6 @@ async function ensureCollectionPolicies(bundle, dataSources, env) {
|
|
|
13411
13399
|
else logger.debug("RLS policies are up to date.");
|
|
13412
13400
|
}
|
|
13413
13401
|
//#endregion
|
|
13414
|
-
export { ApiError, BlockedUrlError, BundleError, 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, WebhookDispatcher, _resetRebaseMock, _setRebaseMock, applyCollectionDefaults, assertAllowedOutboundUrl, assertCollectionConfigs, assertDistinctSuffixes, assertEmailLinkBases, authJwt, authRoles, authUid, blockedAddressReason, bootFromBundle, classifySurface, cleanupDevPortFile, createAppleProvider, createBackupRoutes, createBitbucketProvider, createBuiltinAuthAdapter, createContractRoutes, createCronRoutes, createCronStore, createCustomAuthAdapter, createDiscordProvider, createEmailService, createFacebookProvider, createFunctionRoutes, createGitHubProvider, createGitLabProvider, createGoogleProvider, createHistoryRoutes, createLinkedinProvider, createMetricsMiddleware, createMetricsRoutes, createMicrosoftProvider, createSlackProvider, createSourceBundle, createSpotifyProvider, createStorageController, createStorageRoutes, createTwitterProvider, defineCron, defineFunction, envSuffixForKey, errorHandler, escapeHtml, extractUserFromToken, fileTokenAuth, findCollectionConfigProblems, generateSecurePassword, getEmailVerificationTemplate, getMagicLinkTemplate, getPasswordResetTemplate, getUserInvitationTemplate, getWelcomeEmailTemplate, hashPassword, html, httpMethodToOperation, initializeDataSource, initializeDataSources, initializeRebaseBackend, installShutdownHandlers, isApiKeyToken, isAuthAdapter, isDatabaseAdapter, 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 };
|
|
13402
|
+
export { ApiError, BlockedUrlError, BundleError, 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, WebhookDispatcher, _resetRebaseMock, _setRebaseMock, applyCollectionDefaults, assertAllowedOutboundUrl, assertCollectionConfigs, assertDistinctSuffixes, assertEmailLinkBases, assertWriteRequestValid, authJwt, authRoles, authUid, blockedAddressReason, bootFromBundle, classifySurface, cleanupDevPortFile, createAppleProvider, createBackupRoutes, createBitbucketProvider, createBuiltinAuthAdapter, createContractRoutes, createCronRoutes, createCronStore, createCustomAuthAdapter, createDiscordProvider, createEmailService, createFacebookProvider, createFunctionRoutes, createGitHubProvider, createGitLabProvider, createGoogleProvider, createHistoryRoutes, createLinkedinProvider, createMetricsMiddleware, createMetricsRoutes, createMicrosoftProvider, createSlackProvider, createSourceBundle, createSpotifyProvider, createStorageController, createStorageRoutes, createTwitterProvider, defineCron, defineFunction, envSuffixForKey, errorHandler, escapeHtml, extractUserFromToken, fileTokenAuth, findCollectionConfigProblems, generateSecurePassword, getEmailVerificationTemplate, getMagicLinkTemplate, getPasswordResetTemplate, getUserInvitationTemplate, getWelcomeEmailTemplate, hashPassword, html, httpMethodToOperation, initializeDataSource, initializeDataSources, initializeRebaseBackend, installShutdownHandlers, isApiKeyToken, isAuthAdapter, isDatabaseAdapter, 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 };
|
|
13415
13403
|
|
|
13416
13404
|
//# sourceMappingURL=index.es.js.map
|