@rebasepro/server 0.17.3 → 0.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -1
- package/README.md +5 -1
- package/bin/rebase-server.js +16 -1
- package/dist/{GCSStorageController-Bl7nmhdv.js → GCSStorageController-ZXPoNqW3.js} +21 -10
- package/dist/GCSStorageController-ZXPoNqW3.js.map +1 -0
- package/dist/{S3StorageController-CWvvrRpF.js → S3StorageController-5pAXyv31.js} +24 -10
- package/dist/S3StorageController-5pAXyv31.js.map +1 -0
- package/dist/admin-roles-vYdp_Pil.js +36 -0
- package/dist/admin-roles-vYdp_Pil.js.map +1 -0
- package/dist/{admin_block-BX6YULvJ.js → admin_block-0Xu0r6eZ.js} +4 -1
- package/dist/admin_block-0Xu0r6eZ.js.map +1 -0
- package/dist/api/errors.d.ts +37 -0
- package/dist/api/live-schema-routes.d.ts +14 -0
- package/dist/api/logs-routes.d.ts +8 -0
- package/dist/api/rest/api-generator.d.ts +33 -0
- package/dist/api/root-error-handler.d.ts +62 -0
- package/dist/api/schema-drift.d.ts +39 -0
- package/dist/api/types.d.ts +55 -0
- package/dist/{ast-schema-editor-CvkYOvri.js → ast-schema-editor-CNgFJ3NF.js} +23 -7
- package/dist/ast-schema-editor-CNgFJ3NF.js.map +1 -0
- package/dist/auth/api-keys/api-key-routes.d.ts +12 -0
- package/dist/auth/auth-hooks.d.ts +13 -2
- package/dist/auth/exclude-auth-secrets.d.ts +14 -0
- package/dist/auth/jwt.d.ts +22 -1
- package/dist/auth/mfa-routes.d.ts +10 -1
- package/dist/auth/middleware.d.ts +22 -0
- package/dist/auth/rate-limiter.d.ts +50 -0
- package/dist/auth/registration-policy.d.ts +27 -0
- package/dist/auth/rls-scope.d.ts +9 -1
- package/dist/auth/routes.d.ts +8 -1
- package/dist/auth/seed-admin.d.ts +73 -0
- package/dist/auth/session-routes.d.ts +10 -0
- package/dist/{auth-DkbzMZUS.js → auth-BRiOuyq8.js} +283 -198
- package/dist/auth-BRiOuyq8.js.map +1 -0
- package/dist/{backup-CRdZkA6c.js → backup-C8P6Cl3G.js} +2 -2
- package/dist/{backup-CRdZkA6c.js.map → backup-C8P6Cl3G.js.map} +1 -1
- package/dist/boot/boot.d.ts +18 -0
- package/dist/boot/bundle.d.ts +29 -6
- package/dist/boot/ddl-bootstrap.d.ts +2 -0
- package/dist/boot/env.d.ts +30 -1
- package/dist/boot/fetch-bundle.d.ts +24 -1
- package/dist/boot/options.d.ts +0 -8
- package/dist/boot/provision.d.ts +58 -18
- package/dist/boot/resource-adapters.d.ts +11 -1
- package/dist/boot/resource-resolvers.d.ts +96 -0
- package/dist/boot/sources.d.ts +27 -20
- package/dist/collections/validate-config.d.ts +29 -0
- package/dist/{contract-routes-CHPVKTZW.js → contract-routes-CusnEB5h.js} +24 -9
- package/dist/{contract-routes-CHPVKTZW.js.map → contract-routes-CusnEB5h.js.map} +1 -1
- package/dist/cron/cron-loader.d.ts +7 -1
- package/dist/cron/cron-scheduler.d.ts +48 -2
- package/dist/{cron-loader-d9WMFENB.js → cron-loader-DnmIePn_.js} +23 -4
- package/dist/cron-loader-DnmIePn_.js.map +1 -0
- package/dist/{cron-routes-DSC-kZXI.js → cron-routes-D3x2ydMa.js} +9 -6
- package/dist/cron-routes-D3x2ydMa.js.map +1 -0
- package/dist/{cron-scheduler-BB82dWuU.js → cron-scheduler-BpPKpl4i.js} +148 -28
- package/dist/cron-scheduler-BpPKpl4i.js.map +1 -0
- package/dist/{cron-store-KeN8rN63.js → cron-store-CCQXwgVL.js} +5 -5
- package/dist/{cron-store-KeN8rN63.js.map → cron-store-CCQXwgVL.js.map} +1 -1
- package/dist/{ddl-bootstrap-5YZCZ8qk.js → ddl-bootstrap-CfNvxMuK.js} +26 -2
- package/dist/{ddl-bootstrap-5YZCZ8qk.js.map → ddl-bootstrap-CfNvxMuK.js.map} +1 -1
- package/dist/email/index.d.ts +1 -0
- package/dist/email/unconfigured.d.ts +20 -0
- package/dist/env.d.ts +25 -0
- package/dist/errors-HjfaPlvY.js +385 -0
- package/dist/errors-HjfaPlvY.js.map +1 -0
- package/dist/{function-loader-LLdmBFoL.js → function-loader-xnbDAPfa.js} +9 -3
- package/dist/{function-loader-LLdmBFoL.js.map → function-loader-xnbDAPfa.js.map} +1 -1
- package/dist/function-routes-gQ0EShVG.js +267 -0
- package/dist/function-routes-gQ0EShVG.js.map +1 -0
- package/dist/functions/define-function.d.ts +6 -3
- package/dist/functions/function-routes.d.ts +8 -4
- package/dist/functions/index.js +128 -23
- package/dist/functions/index.js.map +1 -1
- package/dist/{history-recorder-B2pL3X8b.js → history-recorder-B1FwXx9J.js} +3 -3
- package/dist/{history-recorder-B2pL3X8b.js.map → history-recorder-B1FwXx9J.js.map} +1 -1
- package/dist/{history-store-BUJ-37Sm.js → history-store-LHXaQywp.js} +2 -2
- package/dist/{history-store-BUJ-37Sm.js.map → history-store-LHXaQywp.js.map} +1 -1
- package/dist/index.d.ts +16 -4
- package/dist/index.es.js +16229 -7676
- package/dist/index.es.js.map +1 -1
- package/dist/init.d.ts +33 -13
- package/dist/{jobs-XXXcZrmj.js → jobs-DkkD9mPV.js} +5 -5
- package/dist/{jobs-XXXcZrmj.js.map → jobs-DkkD9mPV.js.map} +1 -1
- package/dist/{jwt-DNeOn39r.js → jwt-DkhXwMzR.js} +9 -314
- package/dist/jwt-DkhXwMzR.js.map +1 -0
- package/dist/keys-g8lbVC_o.js +230 -0
- package/dist/keys-g8lbVC_o.js.map +1 -0
- package/dist/{logger-DS03e908.js → logger-DO2PZc4i.js} +197 -18
- package/dist/logger-DO2PZc4i.js.map +1 -0
- package/dist/{logs-routes-DB72iQSr.js → logs-routes-CbsTpozn.js} +74 -6
- package/dist/logs-routes-CbsTpozn.js.map +1 -0
- package/dist/{openapi-generator-CpZp2Fai.js → openapi-generator-BIBbO1Tq.js} +221 -38
- package/dist/openapi-generator-BIBbO1Tq.js.map +1 -0
- package/dist/{proxy-CMymhnwG.js → proxy-Czngl3p9.js} +2 -2
- package/dist/{proxy-CMymhnwG.js.map → proxy-Czngl3p9.js.map} +1 -1
- package/dist/{query-parser-DqXNaBIH.js → query-parser-C68Q9EX4.js} +35 -6
- package/dist/{query-parser-DqXNaBIH.js.map → query-parser-C68Q9EX4.js.map} +1 -1
- package/dist/queues/runtime.d.ts +56 -0
- package/dist/{request-timeout-C8gkc-j7.js → request-timeout-DESvlfrS.js} +4 -6
- package/dist/request-timeout-DESvlfrS.js.map +1 -0
- package/dist/schema-edit/project-root.d.ts +4 -4
- package/dist/{schema-editor-routes-DNV_nBhW.js → schema-editor-routes-CcZKh50q.js} +4 -4
- package/dist/{schema-editor-routes-DNV_nBhW.js.map → schema-editor-routes-CcZKh50q.js.map} +1 -1
- package/dist/{schemas-DBxgjM9A.js → schemas-C3234HWE.js} +2746 -72
- package/dist/schemas-C3234HWE.js.map +1 -0
- package/dist/singleton.d.ts +6 -4
- package/dist/src-DHK4fHkw.js +1002 -0
- package/dist/src-DHK4fHkw.js.map +1 -0
- package/dist/{src-C9gKj23J.js → src-Dq-I3Ybx.js} +507 -54
- package/dist/src-Dq-I3Ybx.js.map +1 -0
- package/dist/storage/GCSStorageController.d.ts +5 -0
- package/dist/storage/LocalStorageController.d.ts +14 -0
- package/dist/storage/S3StorageController.d.ts +8 -0
- package/dist/storage/image-transform.d.ts +35 -0
- package/dist/storage/keys.d.ts +23 -0
- package/dist/storage/policies.d.ts +22 -0
- package/dist/storage/routes.d.ts +9 -0
- package/dist/storage/tus-handler.d.ts +43 -1
- package/dist/storage/types.d.ts +28 -0
- package/dist/types-BfKcm9do.js.map +1 -1
- package/dist/utils/dev-port.d.ts +18 -0
- package/dist/utils/logger.d.ts +60 -0
- package/dist/utils/request-logger.d.ts +2 -1
- package/package.json +30 -24
- package/dist/GCSStorageController-Bl7nmhdv.js.map +0 -1
- package/dist/S3StorageController-CWvvrRpF.js.map +0 -1
- package/dist/admin_block-BX6YULvJ.js.map +0 -1
- package/dist/ast-schema-editor-CvkYOvri.js.map +0 -1
- package/dist/auth-DkbzMZUS.js.map +0 -1
- package/dist/cron-loader-d9WMFENB.js.map +0 -1
- package/dist/cron-routes-DSC-kZXI.js.map +0 -1
- package/dist/cron-scheduler-BB82dWuU.js.map +0 -1
- package/dist/errors-DBwpj9N8.js +0 -231
- package/dist/errors-DBwpj9N8.js.map +0 -1
- package/dist/function-routes-ClT6UQpD.js +0 -41
- package/dist/function-routes-ClT6UQpD.js.map +0 -1
- package/dist/jwt-DNeOn39r.js.map +0 -1
- package/dist/logger-DS03e908.js.map +0 -1
- package/dist/logs-routes-DB72iQSr.js.map +0 -1
- package/dist/openapi-generator-CpZp2Fai.js.map +0 -1
- package/dist/request-timeout-C8gkc-j7.js.map +0 -1
- package/dist/schemas-DBxgjM9A.js.map +0 -1
- package/dist/src-C9gKj23J.js.map +0 -1
- package/dist/src-CcOl05Oq.js +0 -552
- package/dist/src-CcOl05Oq.js.map +0 -1
- package/dist/utils/logging.d.ts +0 -5
|
@@ -0,0 +1,1002 @@
|
|
|
1
|
+
import { createRequire as __rebaseCreateRequire } from "module";
|
|
2
|
+
import __rebaseProcess from "process";
|
|
3
|
+
globalThis.process ??= __rebaseProcess;
|
|
4
|
+
__rebaseCreateRequire(import.meta.url);
|
|
5
|
+
//#region ../types/src/types/filter-operators.ts
|
|
6
|
+
/** The wire spelling of a {@link RelationAggregateSort}: `min(applications.created_at)`. */
|
|
7
|
+
var RELATION_AGGREGATE_SORT_PATTERN = /^(min|max|count|sum|avg)\(([^().]+)(?:\.([^()]+))?\)$/;
|
|
8
|
+
/**
|
|
9
|
+
* A {@link RelationAggregateSort} as a single string — `min(applications.created_at)`,
|
|
10
|
+
* `count(applications)`.
|
|
11
|
+
*
|
|
12
|
+
* The wire form is a string because every layer below the call site already is
|
|
13
|
+
* one: `OrderByTuple` is `[string, direction]`, the REST parameter is
|
|
14
|
+
* `?orderBy=key:direction`, the driver contract takes `orderBy?: string |
|
|
15
|
+
* OrderByTuple[]`, and a cursor names its keys by string. `_score` established
|
|
16
|
+
* the same pattern — a sort key that is not a column, spelled as one — and this
|
|
17
|
+
* reuses it rather than widening five signatures to carry an object that would
|
|
18
|
+
* be flattened at the end anyway.
|
|
19
|
+
*
|
|
20
|
+
* SQL's own spelling, so the key reads as what it compiles to. Neither `:` nor
|
|
21
|
+
* `,` appears in it, which is what keeps it safe in the colon-delimited wire
|
|
22
|
+
* shorthand.
|
|
23
|
+
*
|
|
24
|
+
* @group Models
|
|
25
|
+
*/
|
|
26
|
+
function encodeRelationAggregateSort(sort) {
|
|
27
|
+
return `${sort.agg}(${sort.relation}${sort.field ? `.${sort.field}` : ""})`;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Read the string spelling back, or `undefined` if it is not one.
|
|
31
|
+
*
|
|
32
|
+
* `undefined` rather than a throw: this is asked of *every* sort key to find
|
|
33
|
+
* out which kind it is, and an ordinary column name is not an error.
|
|
34
|
+
*
|
|
35
|
+
* @group Models
|
|
36
|
+
*/
|
|
37
|
+
function parseRelationAggregateSort(key) {
|
|
38
|
+
const match = RELATION_AGGREGATE_SORT_PATTERN.exec(key);
|
|
39
|
+
if (!match) return void 0;
|
|
40
|
+
const [, agg, relation, field] = match;
|
|
41
|
+
if (!field && agg !== "count") return void 0;
|
|
42
|
+
return {
|
|
43
|
+
agg,
|
|
44
|
+
relation,
|
|
45
|
+
...field && { field }
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Is this sort key the object form rather than a field name? */
|
|
49
|
+
function isRelationAggregateSort(key) {
|
|
50
|
+
return typeof key === "object" && key !== null && typeof key.relation === "string" && typeof key.agg === "string";
|
|
51
|
+
}
|
|
52
|
+
/** A sort key in the single-string form every layer below the call site speaks. */
|
|
53
|
+
function sortKeyToString(key) {
|
|
54
|
+
return isRelationAggregateSort(key) ? encodeRelationAggregateSort(key) : key;
|
|
55
|
+
}
|
|
56
|
+
/** Maps canonical operators to their REST short-code equivalents. */
|
|
57
|
+
var CANONICAL_TO_REST = {
|
|
58
|
+
"==": "eq",
|
|
59
|
+
"!=": "neq",
|
|
60
|
+
">": "gt",
|
|
61
|
+
">=": "gte",
|
|
62
|
+
"<": "lt",
|
|
63
|
+
"<=": "lte",
|
|
64
|
+
"in": "in",
|
|
65
|
+
"not-in": "nin",
|
|
66
|
+
"array-contains": "cs",
|
|
67
|
+
"array-contains-any": "csa",
|
|
68
|
+
"like": "like",
|
|
69
|
+
"ilike": "ilike",
|
|
70
|
+
"not-like": "nlike",
|
|
71
|
+
"not-ilike": "nilike",
|
|
72
|
+
"is-null": "isnull",
|
|
73
|
+
"is-not-null": "notnull"
|
|
74
|
+
};
|
|
75
|
+
/** Maps REST short-code operators to their canonical equivalents. */
|
|
76
|
+
var REST_TO_CANONICAL = {
|
|
77
|
+
"eq": "==",
|
|
78
|
+
"neq": "!=",
|
|
79
|
+
"gt": ">",
|
|
80
|
+
"gte": ">=",
|
|
81
|
+
"lt": "<",
|
|
82
|
+
"lte": "<=",
|
|
83
|
+
"in": "in",
|
|
84
|
+
"nin": "not-in",
|
|
85
|
+
"cs": "array-contains",
|
|
86
|
+
"csa": "array-contains-any",
|
|
87
|
+
"like": "like",
|
|
88
|
+
"ilike": "ilike",
|
|
89
|
+
"nlike": "not-like",
|
|
90
|
+
"nilike": "not-ilike",
|
|
91
|
+
"isnull": "is-null",
|
|
92
|
+
"notnull": "is-not-null"
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Operators that test for null/not-null and therefore ignore their value.
|
|
96
|
+
* Codecs normalize the value of these conditions to `null`.
|
|
97
|
+
*/
|
|
98
|
+
var NULL_OPS = /* @__PURE__ */ new Set(["is-null", "is-not-null"]);
|
|
99
|
+
/**
|
|
100
|
+
* Every canonical operator, in a stable order. Useful for engine capability
|
|
101
|
+
* declarations ({@link DataSourceCapabilities.filterOperators}) and for
|
|
102
|
+
* building operator subsets.
|
|
103
|
+
* @group Models
|
|
104
|
+
*/
|
|
105
|
+
var ALL_WHERE_FILTER_OPS = [
|
|
106
|
+
"<",
|
|
107
|
+
"<=",
|
|
108
|
+
"==",
|
|
109
|
+
"!=",
|
|
110
|
+
">=",
|
|
111
|
+
">",
|
|
112
|
+
"in",
|
|
113
|
+
"not-in",
|
|
114
|
+
"array-contains",
|
|
115
|
+
"array-contains-any",
|
|
116
|
+
"like",
|
|
117
|
+
"ilike",
|
|
118
|
+
"not-like",
|
|
119
|
+
"not-ilike",
|
|
120
|
+
"is-null",
|
|
121
|
+
"is-not-null"
|
|
122
|
+
];
|
|
123
|
+
/** All canonical operator strings for runtime validation. */
|
|
124
|
+
var CANONICAL_OPS = new Set(ALL_WHERE_FILTER_OPS);
|
|
125
|
+
/**
|
|
126
|
+
* The REST table as a `Map`, because the key `toCanonicalOp` is handed comes
|
|
127
|
+
* off the wire.
|
|
128
|
+
*
|
|
129
|
+
* Indexed as a plain object, every `Object.prototype` member answered:
|
|
130
|
+
* `toCanonicalOp("valueOf")` returned the inherited *function* as though it
|
|
131
|
+
* were a `WhereFilterOp`, and every caller here treats a defined result as
|
|
132
|
+
* "known operator". Same defect the REST codec's own lookup tables were
|
|
133
|
+
* converted away from in `filter-dialect.ts`; this is the copy that survived
|
|
134
|
+
* one package over, and it now sits under the operator validation the REST
|
|
135
|
+
* parser does, which would otherwise have admitted `["constructor", x]`.
|
|
136
|
+
*/
|
|
137
|
+
var REST_OP_LOOKUP = new Map(Object.entries(REST_TO_CANONICAL));
|
|
138
|
+
/**
|
|
139
|
+
* Resolve any operator string (canonical or REST short-code) to its
|
|
140
|
+
* canonical `WhereFilterOp` form. Returns `undefined` for unknown operators.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* toCanonicalOp("==") // "=="
|
|
144
|
+
* toCanonicalOp("eq") // "=="
|
|
145
|
+
* toCanonicalOp("cs") // "array-contains"
|
|
146
|
+
* toCanonicalOp("xyz") // undefined
|
|
147
|
+
*/
|
|
148
|
+
function toCanonicalOp(op) {
|
|
149
|
+
if (CANONICAL_OPS.has(op)) return op;
|
|
150
|
+
return REST_OP_LOOKUP.get(op);
|
|
151
|
+
}
|
|
152
|
+
//#endregion
|
|
153
|
+
//#region ../types/src/types/data_source.ts
|
|
154
|
+
/**
|
|
155
|
+
* The default data-source key, used when a collection does not name a
|
|
156
|
+
* `dataSource`. Shared by the frontend router and the backend driver
|
|
157
|
+
* registry so both agree on "the default database".
|
|
158
|
+
* @group Models
|
|
159
|
+
*/
|
|
160
|
+
var DEFAULT_DATA_SOURCE_KEY = "(default)";
|
|
161
|
+
/**
|
|
162
|
+
* Relation kinds assumed filterable when a driver does not say.
|
|
163
|
+
*
|
|
164
|
+
* `belongsTo` alone: its filter is a comparison on a column of the row being
|
|
165
|
+
* filtered, the one shape that needs no query construction a driver might not
|
|
166
|
+
* have. Everything else is a correlated subquery over another table.
|
|
167
|
+
*
|
|
168
|
+
* @group Models
|
|
169
|
+
*/
|
|
170
|
+
var DEFAULT_FILTERABLE_RELATION_KINDS = ["belongsTo"];
|
|
171
|
+
/** @group Models */
|
|
172
|
+
var POSTGRES_CAPABILITIES = {
|
|
173
|
+
key: "postgres",
|
|
174
|
+
label: "PostgreSQL",
|
|
175
|
+
supportsRelations: true,
|
|
176
|
+
supportsSubcollections: false,
|
|
177
|
+
supportsRLS: true,
|
|
178
|
+
supportsReferences: false,
|
|
179
|
+
supportsColumnTypes: true,
|
|
180
|
+
supportsRealtime: true,
|
|
181
|
+
supportsVectors: true,
|
|
182
|
+
filterOperators: ALL_WHERE_FILTER_OPS,
|
|
183
|
+
filterableRelationKinds: [
|
|
184
|
+
"belongsTo",
|
|
185
|
+
"manyToMany",
|
|
186
|
+
"hasMany",
|
|
187
|
+
"hasOne"
|
|
188
|
+
],
|
|
189
|
+
supportsRelationFieldFilters: true,
|
|
190
|
+
relationAggregateSorts: true,
|
|
191
|
+
supportsSQLAdmin: true,
|
|
192
|
+
supportsDocumentAdmin: false,
|
|
193
|
+
supportsSchemaAdmin: true
|
|
194
|
+
};
|
|
195
|
+
/** @group Models */
|
|
196
|
+
var FIREBASE_CAPABILITIES = {
|
|
197
|
+
key: "firestore",
|
|
198
|
+
label: "Firebase / Firestore",
|
|
199
|
+
supportsRelations: false,
|
|
200
|
+
supportsSubcollections: true,
|
|
201
|
+
supportsRLS: false,
|
|
202
|
+
supportsReferences: true,
|
|
203
|
+
supportsColumnTypes: false,
|
|
204
|
+
supportsRealtime: true,
|
|
205
|
+
supportsVectors: false,
|
|
206
|
+
filterOperators: ALL_WHERE_FILTER_OPS.filter((op) => op !== "like" && op !== "ilike" && op !== "not-like" && op !== "not-ilike"),
|
|
207
|
+
filterableRelationKinds: [],
|
|
208
|
+
supportsRelationFieldFilters: false,
|
|
209
|
+
relationAggregateSorts: false,
|
|
210
|
+
supportsSQLAdmin: false,
|
|
211
|
+
supportsDocumentAdmin: false,
|
|
212
|
+
supportsSchemaAdmin: false
|
|
213
|
+
};
|
|
214
|
+
/** @group Models */
|
|
215
|
+
var MONGODB_CAPABILITIES = {
|
|
216
|
+
key: "mongodb",
|
|
217
|
+
label: "MongoDB",
|
|
218
|
+
supportsRelations: false,
|
|
219
|
+
supportsSubcollections: true,
|
|
220
|
+
supportsRLS: false,
|
|
221
|
+
supportsReferences: true,
|
|
222
|
+
supportsColumnTypes: false,
|
|
223
|
+
supportsRealtime: false,
|
|
224
|
+
supportsVectors: false,
|
|
225
|
+
filterOperators: ALL_WHERE_FILTER_OPS,
|
|
226
|
+
filterableRelationKinds: [],
|
|
227
|
+
supportsRelationFieldFilters: false,
|
|
228
|
+
relationAggregateSorts: false,
|
|
229
|
+
supportsSQLAdmin: false,
|
|
230
|
+
supportsDocumentAdmin: true,
|
|
231
|
+
supportsSchemaAdmin: true
|
|
232
|
+
};
|
|
233
|
+
/**
|
|
234
|
+
* Fallback capabilities when the driver is unknown.
|
|
235
|
+
* Enables everything so nothing is hidden unexpectedly.
|
|
236
|
+
* @group Models
|
|
237
|
+
*/
|
|
238
|
+
var DEFAULT_CAPABILITIES = {
|
|
239
|
+
key: "(default)",
|
|
240
|
+
label: "Default",
|
|
241
|
+
supportsRelations: true,
|
|
242
|
+
supportsSubcollections: true,
|
|
243
|
+
supportsRLS: true,
|
|
244
|
+
supportsReferences: true,
|
|
245
|
+
supportsColumnTypes: true,
|
|
246
|
+
supportsRealtime: true,
|
|
247
|
+
supportsVectors: true,
|
|
248
|
+
filterOperators: ALL_WHERE_FILTER_OPS,
|
|
249
|
+
filterableRelationKinds: DEFAULT_FILTERABLE_RELATION_KINDS,
|
|
250
|
+
supportsRelationFieldFilters: false,
|
|
251
|
+
relationAggregateSorts: false,
|
|
252
|
+
supportsSQLAdmin: true,
|
|
253
|
+
supportsDocumentAdmin: true,
|
|
254
|
+
supportsSchemaAdmin: true
|
|
255
|
+
};
|
|
256
|
+
var CAPABILITIES_REGISTRY = {
|
|
257
|
+
postgres: POSTGRES_CAPABILITIES,
|
|
258
|
+
firestore: FIREBASE_CAPABILITIES,
|
|
259
|
+
mongodb: MONGODB_CAPABILITIES,
|
|
260
|
+
"(default)": DEFAULT_CAPABILITIES
|
|
261
|
+
};
|
|
262
|
+
/**
|
|
263
|
+
* Look up capabilities for a given engine key.
|
|
264
|
+
* If `engine` is undefined or not found, returns `DEFAULT_CAPABILITIES`.
|
|
265
|
+
* @group Models
|
|
266
|
+
*/
|
|
267
|
+
function getDataSourceCapabilities(engine) {
|
|
268
|
+
if (!engine) return POSTGRES_CAPABILITIES;
|
|
269
|
+
return CAPABILITIES_REGISTRY[engine] ?? DEFAULT_CAPABILITIES;
|
|
270
|
+
}
|
|
271
|
+
//#endregion
|
|
272
|
+
//#region ../types/src/types/collections.ts
|
|
273
|
+
/**
|
|
274
|
+
* Type guard for PostgreSQL collections.
|
|
275
|
+
* Returns true if the collection uses the Postgres engine (or the default engine).
|
|
276
|
+
*
|
|
277
|
+
* Generic over the *input* type, and narrows by intersection rather than
|
|
278
|
+
* replacement. Narrowing to a bare `PostgresCollectionConfig` discarded whatever
|
|
279
|
+
* the caller actually had — most visibly the admin panel's view model, whose
|
|
280
|
+
* flattened presentation fields vanished the moment a collection passed through
|
|
281
|
+
* one of these guards.
|
|
282
|
+
*
|
|
283
|
+
* @group Models
|
|
284
|
+
*/
|
|
285
|
+
function isPostgresCollectionConfig(collection) {
|
|
286
|
+
return !collection.engine || collection.engine === "postgres";
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Narrows to the SQL collection fields — `table`, `relations`,
|
|
290
|
+
* `disableDefaultPolicies` — by asking the engine's declared capabilities
|
|
291
|
+
* rather than by naming Postgres.
|
|
292
|
+
*
|
|
293
|
+
* The two halves of this already existed and were never joined. The engine
|
|
294
|
+
* split (`PostgresCollectionConfig` / `FirebaseCollectionConfig` /
|
|
295
|
+
* `MongoDBCollectionConfig`) said which fields belong to which engine at the
|
|
296
|
+
* type level; {@link DataSourceCapabilities} said the same thing at runtime,
|
|
297
|
+
* down to a `supportsRelations` flag. So call sites guarded on the capability
|
|
298
|
+
* and then read a field the base type had to declare for them — which is why
|
|
299
|
+
* those fields were on the base, and why a MongoDB collection could be written
|
|
300
|
+
* with a `table`.
|
|
301
|
+
*
|
|
302
|
+
* Prefer this over {@link isPostgresCollectionConfig} wherever the question is
|
|
303
|
+
* "does this collection live in a SQL table", so a custom SQL engine
|
|
304
|
+
* registered through `registerDataSourceCapabilities` is included.
|
|
305
|
+
*
|
|
306
|
+
* @group Models
|
|
307
|
+
*/
|
|
308
|
+
function isRelationalCollectionConfig(collection) {
|
|
309
|
+
return getDataSourceCapabilities(collection.engine).supportsRelations;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Type guard for Firebase / Firestore collections.
|
|
313
|
+
* @group Models
|
|
314
|
+
*/
|
|
315
|
+
function isFirebaseCollectionConfig(collection) {
|
|
316
|
+
return collection.engine === "firestore";
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Type guard for MongoDB collections.
|
|
320
|
+
* @group Models
|
|
321
|
+
*/
|
|
322
|
+
function isMongoDBCollectionConfig(collection) {
|
|
323
|
+
return collection.engine === "mongodb";
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Returns the data path for a collection.
|
|
327
|
+
* For Firestore or MongoDB collections with a `path`, returns that value;
|
|
328
|
+
* otherwise falls back to `slug`.
|
|
329
|
+
*/
|
|
330
|
+
function getCollectionDataPath(collection) {
|
|
331
|
+
if (isFirebaseCollectionConfig(collection) && collection.path) return collection.path;
|
|
332
|
+
if (isMongoDBCollectionConfig(collection) && collection.path) return collection.path;
|
|
333
|
+
return collection.slug;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Reads a collection's driver-declared subcollections thunk (the `subcollections`
|
|
337
|
+
* field) independent of engine identity, so engine-agnostic code doesn't have to
|
|
338
|
+
* type-guard against a specific driver. Returns `undefined` when the collection
|
|
339
|
+
* declares none.
|
|
340
|
+
*
|
|
341
|
+
* Pair with `getDataSourceCapabilities(engine).supportsSubcollections` to decide
|
|
342
|
+
* whether the engine honours subcollections at all before reading them.
|
|
343
|
+
* @group Models
|
|
344
|
+
*/
|
|
345
|
+
function getDeclaredSubcollections(collection) {
|
|
346
|
+
return collection.subcollections;
|
|
347
|
+
}
|
|
348
|
+
//#endregion
|
|
349
|
+
//#region ../types/src/types/rls-functions.ts
|
|
350
|
+
/**
|
|
351
|
+
* The SQL helper functions RLS policies call, and the schema they live in.
|
|
352
|
+
*
|
|
353
|
+
* ## One schema, and it is ours
|
|
354
|
+
*
|
|
355
|
+
* Rebase creates exactly one schema in a project's database: `rebase`. These
|
|
356
|
+
* three functions live in it alongside the framework's own tables, and that is
|
|
357
|
+
* the whole contract — a reader can look at a database and know precisely which
|
|
358
|
+
* namespace belongs to the framework and that nothing else was touched.
|
|
359
|
+
*
|
|
360
|
+
* It used to be two. `uid()`, `jwt()` and `roles()` sat in a schema called
|
|
361
|
+
* `auth`, which is Supabase's name, chosen so that a developer who had written
|
|
362
|
+
* Supabase RLS would recognise `auth.uid()`. The familiarity was real but the
|
|
363
|
+
* name was not Rebase's to take, and taking it had a concrete cost: pointing
|
|
364
|
+
* Rebase at a database that already had a Supabase `auth` schema meant
|
|
365
|
+
* `CREATE OR REPLACE FUNCTION auth.uid() RETURNS text` against Supabase's
|
|
366
|
+
* `RETURNS uuid`, which Postgres rejects outright —
|
|
367
|
+
*
|
|
368
|
+
* ERROR: cannot change return type of existing function
|
|
369
|
+
* HINT: Use DROP FUNCTION auth.uid() first.
|
|
370
|
+
*
|
|
371
|
+
* — and the failure landed inside a catch-all that logged a warning and carried
|
|
372
|
+
* on, leaving a database with auth tables, no helper functions, and policies
|
|
373
|
+
* calling functions that did not exist. Under `rebase db migrate` the same
|
|
374
|
+
* statements aborted the migration instead.
|
|
375
|
+
*
|
|
376
|
+
* `rebase.uid()` collides with nobody. A Supabase database keeps its `auth`
|
|
377
|
+
* schema untouched and gains a `rebase` one, which is what a gradual migration
|
|
378
|
+
* needs.
|
|
379
|
+
*
|
|
380
|
+
* ## Why functions at all, rather than inlining `current_setting`
|
|
381
|
+
*
|
|
382
|
+
* Because the indirection has already been spent once. `uid()` resolves
|
|
383
|
+
* `app.uid` and falls back to the pre-rename `app.user_id`, so that during a
|
|
384
|
+
* rolling deploy — old and new pods serving one database — both eras resolve
|
|
385
|
+
* the principal. That was a single `CREATE OR REPLACE`. Inlined into policy
|
|
386
|
+
* bodies it would have been a rewrite of every policy on every table.
|
|
387
|
+
*
|
|
388
|
+
* ## Why the name is not configurable
|
|
389
|
+
*
|
|
390
|
+
* A policy body is stored SQL: Postgres parses `USING (…)` once and keeps it, so
|
|
391
|
+
* these strings are written into every policy in every database Rebase has
|
|
392
|
+
* provisioned. Everything that reads policies back — the SQL-to-policy parser
|
|
393
|
+
* behind the admin UI, the drift checker, `rls-check` — would have to know the
|
|
394
|
+
* configured value to recognise its own output. One frozen name is the feature.
|
|
395
|
+
*/
|
|
396
|
+
/** The schema Rebase owns. The only schema Rebase creates. */
|
|
397
|
+
var REBASE_SCHEMA = "rebase";
|
|
398
|
+
/**
|
|
399
|
+
* The principal of the current request, as text, or NULL in the server context.
|
|
400
|
+
*
|
|
401
|
+
* Never NULL for a user request — an anonymous one carries
|
|
402
|
+
* {@link ANONYMOUS_USER_ID} — which is what makes `IS NULL` a reliable test for
|
|
403
|
+
* the trusted server plane and `IS NOT NULL` a tautology.
|
|
404
|
+
*/
|
|
405
|
+
var RLS_UID_SQL = `${REBASE_SCHEMA}.uid()`;
|
|
406
|
+
/** The request's roles as a comma-separated string, for `string_to_array`. */
|
|
407
|
+
var RLS_ROLES_SQL = `${REBASE_SCHEMA}.roles()`;
|
|
408
|
+
`${REBASE_SCHEMA}`;
|
|
409
|
+
/** The request's JWT claims as `jsonb`, or `{}`. */
|
|
410
|
+
var RLS_JWT_SQL = `${REBASE_SCHEMA}.jwt()`;
|
|
411
|
+
/**
|
|
412
|
+
* The pre-1.0 spellings, for recognising policies and hand-written SQL that
|
|
413
|
+
* predate the move.
|
|
414
|
+
*
|
|
415
|
+
* Kept because policies outlive the server that wrote them: a database migrated
|
|
416
|
+
* by an older release still holds `auth.uid()` in its policy bodies until the
|
|
417
|
+
* next push or boot recompiles them, and anything that reads policies back has
|
|
418
|
+
* to recognise both eras or report the framework's own output as foreign drift.
|
|
419
|
+
* Also used to give a project whose `securityRules` contain raw `auth.uid()` a
|
|
420
|
+
* message naming the replacement, instead of a parse failure.
|
|
421
|
+
*/
|
|
422
|
+
var LEGACY_RLS_SCHEMA = "auth";
|
|
423
|
+
`${LEGACY_RLS_SCHEMA}`;
|
|
424
|
+
`${LEGACY_RLS_SCHEMA}`;
|
|
425
|
+
`${LEGACY_RLS_SCHEMA}`;
|
|
426
|
+
//#endregion
|
|
427
|
+
//#region ../types/src/types/resources.ts
|
|
428
|
+
/** The key a resource takes when a project declares only one of its kind. */
|
|
429
|
+
var DEFAULT_RESOURCE_KEY = "(default)";
|
|
430
|
+
var BRAND = Symbol.for("@rebasepro/types.resource");
|
|
431
|
+
/** Whether a value is a resource handle rather than a plain string key. */
|
|
432
|
+
function isResourceHandle(value) {
|
|
433
|
+
return typeof value === "object" && value !== null && BRAND in value;
|
|
434
|
+
}
|
|
435
|
+
/** The key a resource reference names, whether it is a handle or already a key. */
|
|
436
|
+
function resourceKeyOf(ref) {
|
|
437
|
+
return isResourceHandle(ref) ? ref.key : ref;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Replace every resource handle inside a value with its key, deeply.
|
|
441
|
+
*
|
|
442
|
+
* Applied where authored config becomes data: `defineCollection`, the
|
|
443
|
+
* collection loaders, the derive step. Past that point a collection is plain
|
|
444
|
+
* data that serialises, compares with `===` and reaches the admin UI over the
|
|
445
|
+
* wire, so a handle must not survive into it. Plain objects and arrays are
|
|
446
|
+
* walked; anything else — a function, a Date, a class instance — is a leaf and
|
|
447
|
+
* is returned as it is, which is what keeps callbacks and validators intact.
|
|
448
|
+
*/
|
|
449
|
+
function resolveResourceRefs(value) {
|
|
450
|
+
if (isResourceHandle(value)) return value.key;
|
|
451
|
+
if (Array.isArray(value)) {
|
|
452
|
+
let changed = false;
|
|
453
|
+
const out = value.map((item) => {
|
|
454
|
+
const next = resolveResourceRefs(item);
|
|
455
|
+
if (next !== item) changed = true;
|
|
456
|
+
return next;
|
|
457
|
+
});
|
|
458
|
+
return changed ? out : value;
|
|
459
|
+
}
|
|
460
|
+
if (value !== null && typeof value === "object") {
|
|
461
|
+
const proto = Object.getPrototypeOf(value);
|
|
462
|
+
if (proto === Object.prototype || proto === null) {
|
|
463
|
+
let changed = false;
|
|
464
|
+
const out = {};
|
|
465
|
+
for (const [k, v] of Object.entries(value)) {
|
|
466
|
+
const next = resolveResourceRefs(v);
|
|
467
|
+
if (next !== v) changed = true;
|
|
468
|
+
out[k] = next;
|
|
469
|
+
}
|
|
470
|
+
return changed ? out : value;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return value;
|
|
474
|
+
}
|
|
475
|
+
var GLOBAL_KEY = Symbol.for("@rebasepro/types.resourceRegistry");
|
|
476
|
+
/**
|
|
477
|
+
* Where kinds live, versioned — and why the version is in the symbol.
|
|
478
|
+
*
|
|
479
|
+
* Sharing one kinds map across copies means the copy that registers SECOND is
|
|
480
|
+
* the one that runs the comparison. That copy is whatever the bundle happens to
|
|
481
|
+
* carry, which for a driver is a build of this package frozen at its release —
|
|
482
|
+
* so the rule enforced is the rule that shipped THEN, not the one written here.
|
|
483
|
+
*
|
|
484
|
+
* `revision` (390bb03cd, applied to `database` in 346df48e2) was supposed to
|
|
485
|
+
* settle a disagreement between two copies, and it settles it only when the
|
|
486
|
+
* copy doing the arithmetic knows what `revision` is. 0.17.0–0.17.3 do not:
|
|
487
|
+
* they deep-equal the spec and throw. The runtime registers at import and a
|
|
488
|
+
* driver is imported after it, so the old copy is always second, always the
|
|
489
|
+
* judge, and always throws — verified by the bundle corpus on 2026-09-07,
|
|
490
|
+
* which reported v0.17.3's message verbatim ("Two packages cannot define the
|
|
491
|
+
* same kind.", no revision clause) while the runtime it ran on was 0.18.
|
|
492
|
+
*
|
|
493
|
+
* So copies that understand `revision` keep their kinds here, under a symbol
|
|
494
|
+
* no released copy looks at, and the legacy map is left to whoever still wants
|
|
495
|
+
* it. An old copy then registers into a map nobody contests, finds no existing
|
|
496
|
+
* entry, and cannot throw — in any load order, which is what the previous fix
|
|
497
|
+
* only claimed. Bumping this suffix again is how a future change to the
|
|
498
|
+
* REGISTRATION PROTOCOL is made; a change to a kind's own definition is still
|
|
499
|
+
* `revision`, among peers that share this map.
|
|
500
|
+
*/
|
|
501
|
+
var KINDS_KEY = Symbol.for("@rebasepro/types.resourceKinds.v2");
|
|
502
|
+
function registry() {
|
|
503
|
+
const g = globalThis;
|
|
504
|
+
let shared = g[GLOBAL_KEY];
|
|
505
|
+
if (!shared) {
|
|
506
|
+
shared = {
|
|
507
|
+
kinds: /* @__PURE__ */ new Map(),
|
|
508
|
+
declarations: /* @__PURE__ */ new Map()
|
|
509
|
+
};
|
|
510
|
+
g[GLOBAL_KEY] = shared;
|
|
511
|
+
}
|
|
512
|
+
let kinds = g[KINDS_KEY];
|
|
513
|
+
if (!kinds) {
|
|
514
|
+
kinds = /* @__PURE__ */ new Map();
|
|
515
|
+
g[KINDS_KEY] = kinds;
|
|
516
|
+
}
|
|
517
|
+
return {
|
|
518
|
+
kinds,
|
|
519
|
+
legacyKinds: shared.kinds,
|
|
520
|
+
declarations: shared.declarations
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Every kind visible to this copy: the versioned map, plus anything only a
|
|
525
|
+
* legacy copy registered.
|
|
526
|
+
*
|
|
527
|
+
* The fallback is not for Rebase's own kinds — this copy defines all of those —
|
|
528
|
+
* but for a third-party driver built against an older `@rebasepro/types` that
|
|
529
|
+
* registers a kind of its own. Dropping it would make that kind invisible and
|
|
530
|
+
* turn `declareResource` into "unknown resource kind" for something genuinely
|
|
531
|
+
* registered.
|
|
532
|
+
*/
|
|
533
|
+
function visibleKinds() {
|
|
534
|
+
const { kinds, legacyKinds } = registry();
|
|
535
|
+
if (legacyKinds.size === 0) return kinds;
|
|
536
|
+
const merged = new Map(legacyKinds);
|
|
537
|
+
for (const [k, v] of kinds) merged.set(k, v);
|
|
538
|
+
return merged;
|
|
539
|
+
}
|
|
540
|
+
var amendments = /* @__PURE__ */ new Map();
|
|
541
|
+
/**
|
|
542
|
+
* Correct a registered kind without touching its registered literal.
|
|
543
|
+
*
|
|
544
|
+
* Use this, never an edit to the literal, for a kind that has shipped in a
|
|
545
|
+
* published package. The amendment applies to reads through this copy only.
|
|
546
|
+
*/
|
|
547
|
+
function amendResourceKind(kind, amendment) {
|
|
548
|
+
amendments.set(kind, {
|
|
549
|
+
...amendments.get(kind),
|
|
550
|
+
...amendment
|
|
551
|
+
});
|
|
552
|
+
}
|
|
553
|
+
/** A registered kind as this copy sees it: the shared literal plus this copy's amendments. */
|
|
554
|
+
function effectiveKind(spec) {
|
|
555
|
+
const amendment = amendments.get(spec.kind);
|
|
556
|
+
return amendment ? {
|
|
557
|
+
...spec,
|
|
558
|
+
...amendment
|
|
559
|
+
} : spec;
|
|
560
|
+
}
|
|
561
|
+
/** `kind:key`, the graph's primary key. */
|
|
562
|
+
function declarationId(kind, key) {
|
|
563
|
+
return `${kind}:${key}`;
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* Register a resource kind.
|
|
567
|
+
*
|
|
568
|
+
* Idempotent for an identical spec. When a spec for the same kind is already
|
|
569
|
+
* registered and differs, the `revision` decides: the higher one is kept and
|
|
570
|
+
* the other copy is warned about, in either load order. Two different specs at
|
|
571
|
+
* the SAME revision are a genuine conflict — two packages defining one kind, or
|
|
572
|
+
* a change that forgot to bump — and still throw.
|
|
573
|
+
*
|
|
574
|
+
* Both copies in that comparison are peers on `KINDS_KEY`, which is what makes
|
|
575
|
+
* the rule enforceable: a copy old enough not to know `revision` writes to the
|
|
576
|
+
* legacy map instead and never reaches this function's arithmetic. Registering
|
|
577
|
+
* a kind an older copy already put in the legacy map is therefore not a
|
|
578
|
+
* conflict — it is the ordinary case, and `visibleKinds` prefers this one.
|
|
579
|
+
*/
|
|
580
|
+
function registerResourceKind(spec) {
|
|
581
|
+
const kinds = registry().kinds;
|
|
582
|
+
const existing = kinds.get(spec.kind);
|
|
583
|
+
if (!existing) {
|
|
584
|
+
kinds.set(spec.kind, spec);
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
if (JSON.stringify(existing) === JSON.stringify(spec)) return;
|
|
588
|
+
const have = existing.revision ?? 0;
|
|
589
|
+
const incoming = spec.revision ?? 0;
|
|
590
|
+
if (have === incoming) throw new Error(`Resource kind "${spec.kind}" is already registered with a different definition at revision ${have}. Two packages cannot define the same kind; a newer definition of the same kind must carry a higher \`revision\`.`);
|
|
591
|
+
const [kept, dropped] = incoming > have ? [spec, existing] : [existing, spec];
|
|
592
|
+
if (kept === spec) kinds.set(spec.kind, spec);
|
|
593
|
+
console.warn(`[resources] Resource kind "${spec.kind}" is registered twice, at revisions ${dropped.revision ?? 0} and ${kept.revision ?? 0}; keeping revision ${kept.revision ?? 0}. The older copy is usually @rebasepro/types inlined in a driver built before the kind changed — rebuild the project with a current driver to remove it.`);
|
|
594
|
+
}
|
|
595
|
+
/** Every registered kind, for validators and for `rebase doctor`. */
|
|
596
|
+
function resourceKinds() {
|
|
597
|
+
return [...visibleKinds().values()].map(effectiveKind);
|
|
598
|
+
}
|
|
599
|
+
/** One registered kind, or undefined. */
|
|
600
|
+
function resourceKind(kind) {
|
|
601
|
+
const spec = visibleKinds().get(kind);
|
|
602
|
+
return spec && effectiveKind(spec);
|
|
603
|
+
}
|
|
604
|
+
var COMMON_OPTION_KEYS = [
|
|
605
|
+
"engine",
|
|
606
|
+
"transport",
|
|
607
|
+
"label"
|
|
608
|
+
];
|
|
609
|
+
/** Whether an engine is one the kind knows, or an explicit `custom:` opt-out. */
|
|
610
|
+
function isValidEngine(spec, engine) {
|
|
611
|
+
return engine.startsWith("custom:") || spec.engines.includes(engine);
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Declare a resource. The primitive every kind's constructor is built from.
|
|
615
|
+
*
|
|
616
|
+
* Redeclaring the same `kind:key` with a *different* shape throws rather than
|
|
617
|
+
* merging. Merging is what the old storage path did, and it silently discarded
|
|
618
|
+
* one of the two engines — a declaration accepted and then ignored, which is
|
|
619
|
+
* the failure this whole model exists to remove. Redeclaring it identically is
|
|
620
|
+
* fine: a config module evaluated twice must not be an error.
|
|
621
|
+
*/
|
|
622
|
+
function declareResource(kind, key = DEFAULT_RESOURCE_KEY, options = {}) {
|
|
623
|
+
const spec = resourceKind(kind);
|
|
624
|
+
if (!spec) {
|
|
625
|
+
const known = [...visibleKinds().keys()].sort().join(", ") || "none";
|
|
626
|
+
throw new Error(`Unknown resource kind "${kind}". Registered kinds: ${known}. Call registerResourceKind() before declaring one.`);
|
|
627
|
+
}
|
|
628
|
+
if (!key || typeof key !== "string" || key.trim() === "") throw new Error(`A ${kind} needs a non-empty key.`);
|
|
629
|
+
const engine = options.engine ?? spec.defaultEngine;
|
|
630
|
+
if (!isValidEngine(spec, engine)) throw new Error(`Unknown ${kind} engine "${engine}" for "${key}". Known engines: ${spec.engines.join(", ")}. An engine this build does not ship is spelled "custom:${engine}", which says so at the call site rather than failing later.`);
|
|
631
|
+
const allowed = /* @__PURE__ */ new Set([...COMMON_OPTION_KEYS, ...spec.optionKeys ?? []]);
|
|
632
|
+
const unknown = Object.keys(options).filter((k) => !allowed.has(k));
|
|
633
|
+
if (unknown.length > 0) throw new Error(`Unknown option(s) on ${kind} "${key}": ${unknown.join(", ")}. A ${kind} accepts: ${[...allowed].sort().join(", ")}.`);
|
|
634
|
+
const extra = {};
|
|
635
|
+
for (const k of spec.optionKeys ?? []) if (options[k] !== void 0) extra[k] = options[k];
|
|
636
|
+
const declaration = {
|
|
637
|
+
kind,
|
|
638
|
+
key,
|
|
639
|
+
engine,
|
|
640
|
+
transport: options.transport ?? "server",
|
|
641
|
+
...options.label !== void 0 ? { label: options.label } : {},
|
|
642
|
+
options: Object.freeze(extra)
|
|
643
|
+
};
|
|
644
|
+
const id = declarationId(kind, key);
|
|
645
|
+
const previous = registry().declarations.get(id);
|
|
646
|
+
if (previous) {
|
|
647
|
+
if (JSON.stringify(previous) !== JSON.stringify(declaration)) throw new Error(`${kind} "${key}" is declared twice with different configuration. Declare it once and export it — two declarations of one resource is the ambiguity this model exists to remove, so it is refused rather than merged.`);
|
|
648
|
+
} else registry().declarations.set(id, declaration);
|
|
649
|
+
return {
|
|
650
|
+
...declaration,
|
|
651
|
+
toString() {
|
|
652
|
+
return key;
|
|
653
|
+
},
|
|
654
|
+
[BRAND]: true
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
/** Every declared resource, in declaration order, optionally filtered by kind. */
|
|
658
|
+
function declaredResources(kind) {
|
|
659
|
+
const all = [...registry().declarations.values()];
|
|
660
|
+
return kind ? all.filter((r) => r.kind === kind) : all;
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* The env-var suffix a resource's bindings use: `__ANALYTICS` for `analytics`,
|
|
664
|
+
* and nothing at all for the default-keyed one.
|
|
665
|
+
*
|
|
666
|
+
* The default takes no suffix so that a project with one database configured
|
|
667
|
+
* through plain `DATABASE_URL` keeps working having declared nothing — the
|
|
668
|
+
* overwhelmingly common project must not have to say so.
|
|
669
|
+
*/
|
|
670
|
+
function resourceEnvSuffix(key) {
|
|
671
|
+
if (key === "(default)") return "";
|
|
672
|
+
return `__${key.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "")}`;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Build a graph from the current declarations, sorted for a stable diff.
|
|
676
|
+
*
|
|
677
|
+
* `usedBy` maps a `kind:key` id to the things that reach it. The derive step
|
|
678
|
+
* supplies it after evaluating collections; a runtime building the graph at
|
|
679
|
+
* boot passes nothing and gets declarations alone, which is all it binds from.
|
|
680
|
+
*/
|
|
681
|
+
function buildResourceGraph(usedBy) {
|
|
682
|
+
return {
|
|
683
|
+
version: 1,
|
|
684
|
+
resources: declaredResources().slice().sort((a, b) => a.kind.localeCompare(b.kind) || a.key.localeCompare(b.key)).map((r) => {
|
|
685
|
+
const users = usedBy?.get(declarationId(r.kind, r.key));
|
|
686
|
+
return users && users.length > 0 ? {
|
|
687
|
+
...r,
|
|
688
|
+
usedBy: [...users].sort()
|
|
689
|
+
} : r;
|
|
690
|
+
})
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
//#endregion
|
|
694
|
+
//#region ../types/src/types/storage_source.ts
|
|
695
|
+
/**
|
|
696
|
+
* Describes a named storage backend — a place files live.
|
|
697
|
+
*
|
|
698
|
+
* Declared once and shared front + back: the frontend uses it to decide
|
|
699
|
+
* transport (HTTP proxy vs direct SDK), the backend uses the same `key`
|
|
700
|
+
* to resolve a StorageController, and collection properties reference
|
|
701
|
+
* a definition by its `key` via `StorageConfig.storageSource`.
|
|
702
|
+
*
|
|
703
|
+
* This mirrors the {@link DataSourceDefinition} pattern used for databases.
|
|
704
|
+
*
|
|
705
|
+
* @group Models
|
|
706
|
+
*/
|
|
707
|
+
/**
|
|
708
|
+
* The default storage source key, used when a property does not specify
|
|
709
|
+
* a `storageSource`. Shared by the frontend and backend registries so
|
|
710
|
+
* both agree on "the default storage backend".
|
|
711
|
+
* @group Models
|
|
712
|
+
*/
|
|
713
|
+
var DEFAULT_STORAGE_SOURCE_KEY = "(default)";
|
|
714
|
+
/**
|
|
715
|
+
* The environment-variable suffix for a storage or data source key.
|
|
716
|
+
*
|
|
717
|
+
* `""` for the default source — so a single-bucket project keeps configuring
|
|
718
|
+
* plain `S3_BUCKET` — and `__<KEY>` for every named one, uppercased with
|
|
719
|
+
* non-alphanumerics collapsed to underscores: `media-cdn` → `S3_BUCKET__MEDIA_CDN`.
|
|
720
|
+
*
|
|
721
|
+
* The rule derives the variable name from the declared key rather than
|
|
722
|
+
* discovering keys by scanning the environment. Scanning would have to guess how
|
|
723
|
+
* `S3_BUCKET__MEDIA_CDN` splits into a key; deriving cannot be ambiguous, and a
|
|
724
|
+
* typo surfaces as a missing source at boot instead of a silently ignored
|
|
725
|
+
* variable.
|
|
726
|
+
*
|
|
727
|
+
* It lives in this package, with no dependencies, because four things must agree
|
|
728
|
+
* on it exactly: the CLI (validating a build), the runtime (reading its own
|
|
729
|
+
* environment), the control plane (writing a tenant's Secret), and the docs. A
|
|
730
|
+
* second implementation of a naming convention is a second chance to disagree.
|
|
731
|
+
*
|
|
732
|
+
* @group Models
|
|
733
|
+
*/
|
|
734
|
+
function storageEnvSuffix(key, defaultKey = DEFAULT_STORAGE_SOURCE_KEY) {
|
|
735
|
+
if (!key || key === defaultKey) return "";
|
|
736
|
+
const normalized = key.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
737
|
+
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.`);
|
|
738
|
+
return `__${normalized}`;
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Two distinct keys that collapse onto the same variable name, or `null`.
|
|
742
|
+
*
|
|
743
|
+
* `media-cdn` and `media_cdn` are different source keys but the same suffix, so
|
|
744
|
+
* without this one of them silently reads the other's configuration. Returns the
|
|
745
|
+
* offending pair rather than throwing, so each caller can raise it in its own
|
|
746
|
+
* idiom — a `BundleError` at boot, a build failure in the CLI, a rejected deploy
|
|
747
|
+
* in a control plane.
|
|
748
|
+
*
|
|
749
|
+
* @group Models
|
|
750
|
+
*/
|
|
751
|
+
function findStorageSuffixCollision(keys, defaultKey = DEFAULT_STORAGE_SOURCE_KEY) {
|
|
752
|
+
const seen = /* @__PURE__ */ new Map();
|
|
753
|
+
for (const key of keys) {
|
|
754
|
+
const suffix = storageEnvSuffix(key, defaultKey);
|
|
755
|
+
const existing = seen.get(suffix);
|
|
756
|
+
if (existing !== void 0 && existing !== key) return {
|
|
757
|
+
a: existing,
|
|
758
|
+
b: key,
|
|
759
|
+
suffix
|
|
760
|
+
};
|
|
761
|
+
seen.set(suffix, key);
|
|
762
|
+
}
|
|
763
|
+
return null;
|
|
764
|
+
}
|
|
765
|
+
//#endregion
|
|
766
|
+
//#region ../types/src/types/resource_kinds.ts
|
|
767
|
+
/**
|
|
768
|
+
* The kinds Rebase ships, and the constructors a project declares them with.
|
|
769
|
+
*
|
|
770
|
+
* Each kind is registered rather than hardcoded, so a fourth one arrives
|
|
771
|
+
* without editing a manifest schema, a validator and a switch statement. That
|
|
772
|
+
* cost is precisely why databases and buckets ended up declared in different
|
|
773
|
+
* files with different rules — the cheapest thing to do was always to bolt the
|
|
774
|
+
* new kind onto whichever home was nearest.
|
|
775
|
+
*
|
|
776
|
+
* A kind owns its engine list. `custom:<id>` is always accepted, so a build
|
|
777
|
+
* that ships an engine this package has never heard of says so at the call site
|
|
778
|
+
* instead of looking like a typo of one that exists.
|
|
779
|
+
*/
|
|
780
|
+
registerResourceKind({
|
|
781
|
+
revision: 1,
|
|
782
|
+
kind: "database",
|
|
783
|
+
engines: [
|
|
784
|
+
"postgres",
|
|
785
|
+
"mongodb",
|
|
786
|
+
"firestore",
|
|
787
|
+
"sqlite"
|
|
788
|
+
],
|
|
789
|
+
defaultEngine: "postgres",
|
|
790
|
+
envBases: [
|
|
791
|
+
"DATABASE_URL",
|
|
792
|
+
"REBASE_DRIVER",
|
|
793
|
+
"REBASE_DB_POOL_MAX"
|
|
794
|
+
],
|
|
795
|
+
optionKeys: [
|
|
796
|
+
"databaseId",
|
|
797
|
+
"migrations",
|
|
798
|
+
"extensions"
|
|
799
|
+
],
|
|
800
|
+
implicitDefault: true
|
|
801
|
+
});
|
|
802
|
+
amendResourceKind("database", { envBases: [
|
|
803
|
+
"DATABASE_URL",
|
|
804
|
+
"DATABASE_READ_URL",
|
|
805
|
+
"ADMIN_CONNECTION_STRING",
|
|
806
|
+
"REBASE_DRIVER",
|
|
807
|
+
"DB_POOL_MAX",
|
|
808
|
+
"DB_POOL_IDLE_TIMEOUT",
|
|
809
|
+
"DB_POOL_CONNECT_TIMEOUT"
|
|
810
|
+
] });
|
|
811
|
+
registerResourceKind({
|
|
812
|
+
kind: "bucket",
|
|
813
|
+
engines: [
|
|
814
|
+
"local",
|
|
815
|
+
"s3",
|
|
816
|
+
"gcs",
|
|
817
|
+
"azure",
|
|
818
|
+
"firebase"
|
|
819
|
+
],
|
|
820
|
+
defaultEngine: "local",
|
|
821
|
+
envBases: [
|
|
822
|
+
"S3_BUCKET",
|
|
823
|
+
"GCS_BUCKET",
|
|
824
|
+
"STORAGE_BUCKET",
|
|
825
|
+
"STORAGE_PUBLIC_URL"
|
|
826
|
+
],
|
|
827
|
+
envBasesByEngine: {
|
|
828
|
+
local: ["STORAGE_BUCKET"],
|
|
829
|
+
s3: [
|
|
830
|
+
"S3_BUCKET",
|
|
831
|
+
"STORAGE_ENDPOINT",
|
|
832
|
+
"STORAGE_REGION",
|
|
833
|
+
"STORAGE_PUBLIC_URL"
|
|
834
|
+
],
|
|
835
|
+
gcs: ["GCS_BUCKET", "STORAGE_PUBLIC_URL"],
|
|
836
|
+
azure: ["STORAGE_BUCKET", "STORAGE_PUBLIC_URL"],
|
|
837
|
+
firebase: ["STORAGE_BUCKET", "STORAGE_PUBLIC_URL"]
|
|
838
|
+
},
|
|
839
|
+
optionKeys: [
|
|
840
|
+
"publicRead",
|
|
841
|
+
"prefix",
|
|
842
|
+
"account"
|
|
843
|
+
],
|
|
844
|
+
implicitDefault: false
|
|
845
|
+
});
|
|
846
|
+
amendResourceKind("bucket", {
|
|
847
|
+
optionKeys: [
|
|
848
|
+
"publicRead",
|
|
849
|
+
"prefix",
|
|
850
|
+
"account",
|
|
851
|
+
"default"
|
|
852
|
+
],
|
|
853
|
+
envBases: [
|
|
854
|
+
"STORAGE_TYPE",
|
|
855
|
+
"STORAGE_PATH",
|
|
856
|
+
"S3_BUCKET",
|
|
857
|
+
"S3_REGION",
|
|
858
|
+
"S3_ACCESS_KEY_ID",
|
|
859
|
+
"S3_SECRET_ACCESS_KEY",
|
|
860
|
+
"S3_ENDPOINT",
|
|
861
|
+
"S3_FORCE_PATH_STYLE",
|
|
862
|
+
"GCS_BUCKET",
|
|
863
|
+
"GCS_PROJECT_ID",
|
|
864
|
+
"GCS_KEY_FILENAME"
|
|
865
|
+
],
|
|
866
|
+
envBasesByEngine: {
|
|
867
|
+
local: ["STORAGE_TYPE", "STORAGE_PATH"],
|
|
868
|
+
s3: [
|
|
869
|
+
"STORAGE_TYPE",
|
|
870
|
+
"S3_BUCKET",
|
|
871
|
+
"S3_REGION",
|
|
872
|
+
"S3_ACCESS_KEY_ID",
|
|
873
|
+
"S3_SECRET_ACCESS_KEY",
|
|
874
|
+
"S3_ENDPOINT",
|
|
875
|
+
"S3_FORCE_PATH_STYLE"
|
|
876
|
+
],
|
|
877
|
+
gcs: [
|
|
878
|
+
"STORAGE_TYPE",
|
|
879
|
+
"GCS_BUCKET",
|
|
880
|
+
"GCS_PROJECT_ID",
|
|
881
|
+
"GCS_KEY_FILENAME"
|
|
882
|
+
],
|
|
883
|
+
azure: [],
|
|
884
|
+
firebase: []
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
registerResourceKind({
|
|
888
|
+
kind: "topic",
|
|
889
|
+
engines: ["jobs"],
|
|
890
|
+
defaultEngine: "jobs",
|
|
891
|
+
envBases: ["REBASE_TOPIC_URL"],
|
|
892
|
+
optionKeys: ["delivery", "maxAttempts"],
|
|
893
|
+
implicitDefault: false
|
|
894
|
+
});
|
|
895
|
+
amendResourceKind("topic", { envBases: [] });
|
|
896
|
+
var runtimeHolder = { current: null };
|
|
897
|
+
/** Install the transport topics publish through. Called by the server at boot. */
|
|
898
|
+
function setTopicRuntime(runtime) {
|
|
899
|
+
runtimeHolder.current = runtime;
|
|
900
|
+
}
|
|
901
|
+
var subscriptions = [];
|
|
902
|
+
/** Every declared subscription, for the worker to wire and the graph to record. */
|
|
903
|
+
function declaredSubscriptions(topic) {
|
|
904
|
+
return topic ? subscriptions.filter((s) => s.topic === topic) : subscriptions.slice();
|
|
905
|
+
}
|
|
906
|
+
registerResourceKind({
|
|
907
|
+
kind: "cron",
|
|
908
|
+
engines: ["scheduler"],
|
|
909
|
+
defaultEngine: "scheduler",
|
|
910
|
+
envBases: [],
|
|
911
|
+
optionKeys: [
|
|
912
|
+
"schedule",
|
|
913
|
+
"timezone",
|
|
914
|
+
"description",
|
|
915
|
+
"enabled",
|
|
916
|
+
"timeoutSeconds",
|
|
917
|
+
"catchUpWindowSeconds"
|
|
918
|
+
],
|
|
919
|
+
implicitDefault: false
|
|
920
|
+
});
|
|
921
|
+
/**
|
|
922
|
+
* Declare a cron, as the scheduler's `defineCron` does on its way through.
|
|
923
|
+
*
|
|
924
|
+
* Projects do not call this: `defineCron` in `@rebasepro/server` does, so a
|
|
925
|
+
* cron file is both the handler and the declaration — one file, one name, and
|
|
926
|
+
* the graph derived from it says what a host needs to know without evaluating
|
|
927
|
+
* the handler. Exported so the derive step and the scheduler spell the
|
|
928
|
+
* declaration identically.
|
|
929
|
+
*/
|
|
930
|
+
function declareCron(name, options) {
|
|
931
|
+
if (typeof options.schedule !== "string" || options.schedule.trim() === "") throw new Error(`Cron "${name}" needs a schedule — a five-field cron expression such as "0 3 * * *".`);
|
|
932
|
+
return declareResource("cron", name, options);
|
|
933
|
+
}
|
|
934
|
+
registerResourceKind({
|
|
935
|
+
kind: "function",
|
|
936
|
+
engines: ["http"],
|
|
937
|
+
defaultEngine: "http",
|
|
938
|
+
envBases: [],
|
|
939
|
+
optionKeys: [
|
|
940
|
+
"portable",
|
|
941
|
+
"requires",
|
|
942
|
+
"file"
|
|
943
|
+
],
|
|
944
|
+
implicitDefault: false
|
|
945
|
+
});
|
|
946
|
+
registerResourceKind({
|
|
947
|
+
kind: "queue",
|
|
948
|
+
engines: ["jobs"],
|
|
949
|
+
defaultEngine: "jobs",
|
|
950
|
+
envBases: [],
|
|
951
|
+
optionKeys: ["maxAttempts"],
|
|
952
|
+
implicitDefault: false
|
|
953
|
+
});
|
|
954
|
+
var queueRuntimeHolder = { current: null };
|
|
955
|
+
/** Install the transport queues enqueue through. Called by the server at boot. */
|
|
956
|
+
function setQueueRuntime(runtime) {
|
|
957
|
+
queueRuntimeHolder.current = runtime;
|
|
958
|
+
}
|
|
959
|
+
var queueConsumers = /* @__PURE__ */ new Map();
|
|
960
|
+
/** Every declared queue handler, for the worker to wire. */
|
|
961
|
+
function declaredQueueConsumers() {
|
|
962
|
+
return [...queueConsumers.values()];
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* One declaration, as the data layer's definition.
|
|
966
|
+
*
|
|
967
|
+
* There is exactly one of these per kind, and everything that needs a
|
|
968
|
+
* definition goes through it — the frontend, the managed runtime's boot path,
|
|
969
|
+
* and an ejected project's own entrypoint. That is not tidiness: the mapping
|
|
970
|
+
* used to exist twice, once here and once in `@rebasepro/server`'s
|
|
971
|
+
* `graphToStorageSources`, and the two disagreed. The server's copy carried a
|
|
972
|
+
* bucket's `account`; this one dropped it, so a bucket declared with shared
|
|
973
|
+
* credentials resolved them on the managed runtime and resolved *nothing* in an
|
|
974
|
+
* ejected backend — the source was skipped and every upload to it answered 501.
|
|
975
|
+
*
|
|
976
|
+
* A field-by-field map is one line away from that failure at all times, so
|
|
977
|
+
* there is now one line to keep right instead of two to keep equal.
|
|
978
|
+
*/
|
|
979
|
+
function resourceToDataSource(declaration) {
|
|
980
|
+
return {
|
|
981
|
+
key: declaration.key === "(default)" ? DEFAULT_DATA_SOURCE_KEY : declaration.key,
|
|
982
|
+
engine: declaration.engine,
|
|
983
|
+
transport: declaration.transport,
|
|
984
|
+
...typeof declaration.options.databaseId === "string" ? { databaseId: declaration.options.databaseId } : {},
|
|
985
|
+
...declaration.label !== void 0 ? { label: declaration.label } : {}
|
|
986
|
+
};
|
|
987
|
+
}
|
|
988
|
+
/** One declaration, as the storage layer's definition. See {@link resourceToDataSource}. */
|
|
989
|
+
function resourceToStorageSource(declaration) {
|
|
990
|
+
return {
|
|
991
|
+
key: declaration.key === "(default)" ? DEFAULT_STORAGE_SOURCE_KEY : declaration.key,
|
|
992
|
+
engine: declaration.engine,
|
|
993
|
+
transport: declaration.transport,
|
|
994
|
+
...typeof declaration.options.account === "string" ? { account: declaration.options.account } : {},
|
|
995
|
+
...declaration.options.default === true ? { default: true } : {},
|
|
996
|
+
...declaration.label !== void 0 ? { label: declaration.label } : {}
|
|
997
|
+
};
|
|
998
|
+
}
|
|
999
|
+
//#endregion
|
|
1000
|
+
export { REST_TO_CANONICAL as A, isPostgresCollectionConfig as C, ALL_WHERE_FILTER_OPS as D, getDataSourceCapabilities as E, parseRelationAggregateSort as M, sortKeyToString as N, CANONICAL_TO_REST as O, toCanonicalOp as P, getDeclaredSubcollections as S, DEFAULT_DATA_SOURCE_KEY as T, resourceKinds as _, resourceToStorageSource as a, RLS_UID_SQL as b, DEFAULT_STORAGE_SOURCE_KEY as c, DEFAULT_RESOURCE_KEY as d, buildResourceGraph as f, resourceKind as g, resourceKeyOf as h, resourceToDataSource as i, isRelationAggregateSort as j, NULL_OPS as k, findStorageSuffixCollision as l, resourceEnvSuffix as m, declaredQueueConsumers as n, setQueueRuntime as o, resolveResourceRefs as p, declaredSubscriptions as r, setTopicRuntime as s, declareCron as t, storageEnvSuffix as u, RLS_JWT_SQL as v, isRelationalCollectionConfig as w, getCollectionDataPath as x, RLS_ROLES_SQL as y };
|
|
1001
|
+
|
|
1002
|
+
//# sourceMappingURL=src-DHK4fHkw.js.map
|