@rebasepro/server-postgres 0.11.1-canary.gfd39654 → 0.12.1-canary.g009ed95
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/PostgresBackendDriver.d.ts +1 -1
- package/dist/PostgresBootstrapper.d.ts +33 -1
- package/dist/auth/services.d.ts +21 -0
- package/dist/backup/backup-service.d.ts +10 -1
- package/dist/backup/pg-tools.d.ts +47 -0
- package/dist/backup-service-CD8o_1Sl.js +8999 -0
- package/dist/backup-service-CD8o_1Sl.js.map +1 -0
- package/dist/cli-helpers.d.ts +39 -0
- package/dist/collections/buildRegistry.d.ts +1 -1
- package/dist/connection-BuZ97wsr.js +250 -0
- package/dist/connection-BuZ97wsr.js.map +1 -0
- package/dist/connection.d.ts +42 -0
- package/dist/ensure-collection-policies-BrUVgjz3.js +57 -0
- package/dist/ensure-collection-policies-BrUVgjz3.js.map +1 -0
- package/dist/ensure-collection-tables-Da2oGkX2.js +650 -0
- package/dist/ensure-collection-tables-Da2oGkX2.js.map +1 -0
- package/dist/history/HistoryService.d.ts +9 -29
- package/dist/index.es.js +1234 -9753
- package/dist/index.es.js.map +1 -1
- package/dist/policy-CeA1JcxP.js +105 -0
- package/dist/policy-CeA1JcxP.js.map +1 -0
- package/dist/schema/auth-schema.d.ts +83 -144
- package/dist/schema/dynamic-tables.d.ts +1 -1
- package/dist/schema/ensure-collection-policies.d.ts +60 -0
- package/dist/schema/ensure-collection-tables.d.ts +44 -2
- package/dist/schema/generate-postgres-ddl-logic.d.ts +135 -1
- package/dist/schema/introspect-db-constraints.d.ts +57 -0
- package/dist/schema/introspect-db-logic.d.ts +94 -5
- package/dist/schema/introspect-db-queries.d.ts +119 -0
- package/dist/schema/introspect-db-structure.d.ts +263 -0
- package/dist/schema/introspect-db-types.d.ts +11 -0
- package/dist/schema/introspect-runtime.d.ts +1 -1
- package/dist/services/FetchService.d.ts +40 -2
- package/dist/services/RelationService.d.ts +24 -1
- package/dist/services/channel-bus/index.d.ts +1 -7
- package/dist/services/collection-helpers.d.ts +24 -1
- package/dist/services/dataService.d.ts +3 -1
- package/dist/services/row-pipeline.d.ts +4 -2
- package/dist/{src-3VmUJ8Xn.js → src-CzbghKwf.js} +464 -187
- package/dist/src-CzbghKwf.js.map +1 -0
- package/dist/{src-D5xBTl32.js → src-DoU9yPqq.js} +79 -189
- package/dist/src-DoU9yPqq.js.map +1 -0
- package/dist/utils/connection-string.d.ts +29 -0
- package/dist/utils/drizzle-conditions.d.ts +162 -7
- package/dist/utils/pg-error-utils.d.ts +25 -3
- package/dist/websocket-B2LsrINK.js +530 -0
- package/dist/websocket-B2LsrINK.js.map +1 -0
- package/package.json +14 -14
- package/src/PostgresAdapter.ts +21 -2
- package/src/PostgresBackendDriver.ts +4 -0
- package/src/PostgresBootstrapper.ts +212 -36
- package/src/auth/ensure-tables.ts +164 -9
- package/src/auth/services.ts +24 -2
- package/src/backup/backup-cli.ts +41 -2
- package/src/backup/backup-service.ts +38 -5
- package/src/backup/pg-tools.ts +96 -3
- package/src/cli-helpers.ts +70 -0
- package/src/cli.ts +44 -26
- package/src/collections/buildRegistry.ts +1 -1
- package/src/collections/validate-relations.ts +15 -0
- package/src/connection.ts +73 -0
- package/src/data-transformer.ts +9 -3
- package/src/databasePoolManager.ts +5 -2
- package/src/history/HistoryService.ts +13 -31
- package/src/schema/auth-schema.ts +30 -19
- package/src/schema/dynamic-tables.ts +1 -1
- package/src/schema/ensure-collection-policies.ts +105 -0
- package/src/schema/ensure-collection-tables.test.ts +105 -9
- package/src/schema/ensure-collection-tables.ts +220 -32
- package/src/schema/generate-drizzle-schema-logic.ts +33 -8
- package/src/schema/generate-postgres-ddl-logic.ts +382 -19
- package/src/schema/introspect-db-constraints.ts +385 -0
- package/src/schema/introspect-db-inference.ts +18 -8
- package/src/schema/introspect-db-logic.ts +385 -71
- package/src/schema/introspect-db-queries.ts +326 -0
- package/src/schema/introspect-db-structure.ts +670 -0
- package/src/schema/introspect-db-types.ts +56 -0
- package/src/schema/introspect-db.ts +37 -80
- package/src/schema/introspect-runtime.test.ts +56 -8
- package/src/schema/introspect-runtime.ts +32 -10
- package/src/security/policy-drift.test.ts +11 -3
- package/src/services/FetchService.ts +148 -18
- package/src/services/PersistService.ts +20 -6
- package/src/services/RelationService.ts +249 -48
- package/src/services/channel-bus/index.ts +0 -9
- package/src/services/collection-helpers.ts +40 -1
- package/src/services/dataService.ts +3 -1
- package/src/services/realtimeService.ts +3 -3
- package/src/services/row-pipeline.ts +4 -2
- package/src/utils/connection-string.ts +58 -0
- package/src/utils/drizzle-conditions.ts +539 -50
- package/src/utils/pg-error-utils.ts +98 -3
- package/src/websocket.ts +18 -9
- package/dist/chunk-DSJWtz9O.js +0 -40
- package/dist/ensure-collection-tables-DGMYK0fr.js +0 -304
- package/dist/ensure-collection-tables-DGMYK0fr.js.map +0 -1
- package/dist/src-3VmUJ8Xn.js.map +0 -1
- package/dist/src-D5xBTl32.js.map +0 -1
|
@@ -1,8 +1,61 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
import "process";
|
|
3
3
|
__createRequire(import.meta.url);
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
4
|
+
import { l as __require, s as __commonJSMin } from "./connection-BuZ97wsr.js";
|
|
5
|
+
import { a as getDataSourceCapabilities, c as toCanonicalOp, n as isPostgresCollectionConfig, o as NULL_OPS, r as isRelationalCollectionConfig, s as REST_TO_CANONICAL, t as getDeclaredSubcollections } from "./src-DoU9yPqq.js";
|
|
6
|
+
import { n as ANONYMOUS_USER_IDS, r as policy, t as ANONYMOUS_USER_ID } from "./policy-CeA1JcxP.js";
|
|
7
|
+
//#region ../types/src/types/entities.ts
|
|
8
|
+
/**
|
|
9
|
+
* Class used to create a reference to a entity in a different path
|
|
10
|
+
*/
|
|
11
|
+
var EntityRelation = class {
|
|
12
|
+
__type = "relation";
|
|
13
|
+
/**
|
|
14
|
+
* ID of the entity
|
|
15
|
+
*/
|
|
16
|
+
id;
|
|
17
|
+
/**
|
|
18
|
+
* A string representing the path of the referenced document (relative
|
|
19
|
+
* to the root of the database).
|
|
20
|
+
*/
|
|
21
|
+
path;
|
|
22
|
+
/**
|
|
23
|
+
* Pre-fetched data payload to eliminate N+1 queries.
|
|
24
|
+
* When present, clients can use this directly instead of fetching.
|
|
25
|
+
*/
|
|
26
|
+
data;
|
|
27
|
+
constructor(id, path, data) {
|
|
28
|
+
this.id = id;
|
|
29
|
+
this.path = path;
|
|
30
|
+
this.data = data;
|
|
31
|
+
}
|
|
32
|
+
get pathWithId() {
|
|
33
|
+
return `${this.path}/${this.id}`;
|
|
34
|
+
}
|
|
35
|
+
isEntityReference() {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
isEntityRelation() {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var Vector = class {
|
|
43
|
+
value;
|
|
44
|
+
constructor(value) {
|
|
45
|
+
this.value = value;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
//#endregion
|
|
49
|
+
//#region ../types/src/types/relations.ts
|
|
50
|
+
/** @group Models */
|
|
51
|
+
function hasForeignKeyOnTarget(relation) {
|
|
52
|
+
return relation.kind === "hasOne" || relation.kind === "hasMany";
|
|
53
|
+
}
|
|
54
|
+
/** @group Models */
|
|
55
|
+
function isManyToMany(relation) {
|
|
56
|
+
return relation.kind === "manyToMany";
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
6
59
|
//#region ../common/src/util/common.ts
|
|
7
60
|
var DEFAULT_ONE_OF_TYPE = "type";
|
|
8
61
|
var DEFAULT_ONE_OF_VALUE = "value";
|
|
@@ -1014,16 +1067,12 @@ function removeFunctions(o) {
|
|
|
1014
1067
|
if (o === void 0) return void 0;
|
|
1015
1068
|
if (o === null) return null;
|
|
1016
1069
|
if (typeof o === "object") {
|
|
1017
|
-
if (Array.isArray(o)) return o.map((v) => removeFunctions(v));
|
|
1070
|
+
if (Array.isArray(o)) return o.filter((v) => typeof v !== "function").map((v) => removeFunctions(v));
|
|
1018
1071
|
if (!isPlainObject(o)) return o;
|
|
1019
|
-
return Object.entries(o).filter(([_, value]) => typeof value !== "function").
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
}).reduce((a, b) => ({
|
|
1024
|
-
...a,
|
|
1025
|
-
...b
|
|
1026
|
-
}), {});
|
|
1072
|
+
return Object.entries(o).filter(([_, value]) => typeof value !== "function").reduce((acc, [key, value]) => {
|
|
1073
|
+
acc[key] = removeFunctions(value);
|
|
1074
|
+
return acc;
|
|
1075
|
+
}, {});
|
|
1027
1076
|
}
|
|
1028
1077
|
return o;
|
|
1029
1078
|
}
|
|
@@ -1162,11 +1211,101 @@ function getPolicyNamesForRule(rule, tableName) {
|
|
|
1162
1211
|
return ops.map((op, opIdx) => rule.name ? ops.length > 1 ? `${rule.name}_${op}` : rule.name : `${tableName}_${op}_${ruleHash}${ops.length > 1 ? `_${opIdx}` : ""}`);
|
|
1163
1212
|
}
|
|
1164
1213
|
//#endregion
|
|
1214
|
+
//#region ../utils/src/plurals.ts
|
|
1215
|
+
/**
|
|
1216
|
+
* Returns the singular of an English word.
|
|
1217
|
+
*
|
|
1218
|
+
* @param {string} word
|
|
1219
|
+
* @param {number} [amount]
|
|
1220
|
+
* @returns {string}
|
|
1221
|
+
*/
|
|
1222
|
+
function singular(word, amount) {
|
|
1223
|
+
if (amount !== void 0 && amount !== 1) return word;
|
|
1224
|
+
const singulars = {
|
|
1225
|
+
"(quiz)zes$": "$1",
|
|
1226
|
+
"(matr)ices$": "$1ix",
|
|
1227
|
+
"(vert|ind)ices$": "$1ex",
|
|
1228
|
+
"^(ox)en$": "$1",
|
|
1229
|
+
"(alias)es$": "$1",
|
|
1230
|
+
"(octop|vir)i$": "$1us",
|
|
1231
|
+
"(cris|ax|test)es$": "$1is",
|
|
1232
|
+
"(shoe)s$": "$1",
|
|
1233
|
+
"(o)es$": "$1",
|
|
1234
|
+
"(bus)es$": "$1",
|
|
1235
|
+
"([m|l])ice$": "$1ouse",
|
|
1236
|
+
"(x|ch|ss|sh)es$": "$1",
|
|
1237
|
+
"(m)ovies$": "$1ovie",
|
|
1238
|
+
"(s)eries$": "$1eries",
|
|
1239
|
+
"([^aeiouy]|qu)ies$": "$1y",
|
|
1240
|
+
"([lr])ves$": "$1f",
|
|
1241
|
+
"(tive)s$": "$1",
|
|
1242
|
+
"(hive)s$": "$1",
|
|
1243
|
+
"(li|wi|kni)ves$": "$1fe",
|
|
1244
|
+
"(shea|loa|lea|thie)ves$": "$1f",
|
|
1245
|
+
"(^analy)ses$": "$1sis",
|
|
1246
|
+
"((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$": "$1$2sis",
|
|
1247
|
+
"([ti])a$": "$1um",
|
|
1248
|
+
"(n)ews$": "$1ews",
|
|
1249
|
+
"(h|bl)ouses$": "$1ouse",
|
|
1250
|
+
"(corpse)s$": "$1",
|
|
1251
|
+
"(us)es$": "$1",
|
|
1252
|
+
s$: ""
|
|
1253
|
+
};
|
|
1254
|
+
const irregular = {
|
|
1255
|
+
move: "moves",
|
|
1256
|
+
foot: "feet",
|
|
1257
|
+
goose: "geese",
|
|
1258
|
+
sex: "sexes",
|
|
1259
|
+
child: "children",
|
|
1260
|
+
man: "men",
|
|
1261
|
+
tooth: "teeth",
|
|
1262
|
+
person: "people"
|
|
1263
|
+
};
|
|
1264
|
+
if ([
|
|
1265
|
+
"sheep",
|
|
1266
|
+
"fish",
|
|
1267
|
+
"deer",
|
|
1268
|
+
"moose",
|
|
1269
|
+
"series",
|
|
1270
|
+
"species",
|
|
1271
|
+
"money",
|
|
1272
|
+
"rice",
|
|
1273
|
+
"information",
|
|
1274
|
+
"equipment",
|
|
1275
|
+
"bison",
|
|
1276
|
+
"cod",
|
|
1277
|
+
"offspring",
|
|
1278
|
+
"pike",
|
|
1279
|
+
"salmon",
|
|
1280
|
+
"shrimp",
|
|
1281
|
+
"swine",
|
|
1282
|
+
"trout",
|
|
1283
|
+
"aircraft",
|
|
1284
|
+
"hovercraft",
|
|
1285
|
+
"spacecraft",
|
|
1286
|
+
"sugar",
|
|
1287
|
+
"tuna",
|
|
1288
|
+
"you",
|
|
1289
|
+
"wood"
|
|
1290
|
+
].indexOf(word.toLowerCase()) >= 0) return word;
|
|
1291
|
+
for (const w in irregular) {
|
|
1292
|
+
const pattern = new RegExp(`${irregular[w]}$`, "i");
|
|
1293
|
+
if (pattern.test(word)) return word.replace(pattern, w);
|
|
1294
|
+
}
|
|
1295
|
+
for (const reg in singulars) {
|
|
1296
|
+
const pattern = new RegExp(reg, "i");
|
|
1297
|
+
if (pattern.test(word)) return word.replace(pattern, singulars[reg]);
|
|
1298
|
+
}
|
|
1299
|
+
return word;
|
|
1300
|
+
}
|
|
1301
|
+
//#endregion
|
|
1165
1302
|
//#region ../utils/src/names.ts
|
|
1166
1303
|
/**
|
|
1167
1304
|
* Generates a foreign key column name from a given string, typically a collection slug or name.
|
|
1168
|
-
* It
|
|
1169
|
-
*
|
|
1305
|
+
* It singularizes the name, converts it to snake_case and appends '_id'.
|
|
1306
|
+
*
|
|
1307
|
+
* Singularization runs *before* snake-casing so that acronyms survive: `toSnakeCase`
|
|
1308
|
+
* splits on every capital, which turned "URLs" into "ur_ls" and then "ur_l_id".
|
|
1170
1309
|
*
|
|
1171
1310
|
* @param name The base name to convert to a foreign key.
|
|
1172
1311
|
* @returns A foreign key name in the format 'singular_name_id'.
|
|
@@ -1176,8 +1315,8 @@ function getPolicyNamesForRule(rule, tableName) {
|
|
|
1176
1315
|
* generateForeignKeyName("users")
|
|
1177
1316
|
*
|
|
1178
1317
|
* @example
|
|
1179
|
-
* // returns "
|
|
1180
|
-
* generateForeignKeyName("
|
|
1318
|
+
* // returns "category_id"
|
|
1319
|
+
* generateForeignKeyName("categories")
|
|
1181
1320
|
*
|
|
1182
1321
|
* @example
|
|
1183
1322
|
* // returns "product_id"
|
|
@@ -1185,8 +1324,41 @@ function getPolicyNamesForRule(rule, tableName) {
|
|
|
1185
1324
|
*
|
|
1186
1325
|
*/
|
|
1187
1326
|
function generateForeignKeyName(name) {
|
|
1188
|
-
|
|
1189
|
-
|
|
1327
|
+
return `${toSnakeCase(singularizeForKey(name))}_id`;
|
|
1328
|
+
}
|
|
1329
|
+
/**
|
|
1330
|
+
* `singular()` handles real English plurals, but its final catch-all rule strips
|
|
1331
|
+
* any trailing "s", which mangles words that only look plural. Guard the two
|
|
1332
|
+
* cases that produce a column name nobody would recognise:
|
|
1333
|
+
*
|
|
1334
|
+
* - a double "s" ending is never a plural marker ("address", "class", "process"),
|
|
1335
|
+
* so stripping it yields "addres";
|
|
1336
|
+
* - a name that singularizes to nothing (the literal "s") would yield "_id".
|
|
1337
|
+
*/
|
|
1338
|
+
function singularizeForKey(name) {
|
|
1339
|
+
if (/ss$/i.test(name)) return name;
|
|
1340
|
+
const result = singular(name);
|
|
1341
|
+
return result.length > 0 ? result : name;
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* What `generateForeignKeyName` returned before it learned to singularize:
|
|
1345
|
+
* snake-case the name, then chop one trailing "s".
|
|
1346
|
+
*
|
|
1347
|
+
* This is here to be *detected*, never to be generated. A database provisioned
|
|
1348
|
+
* under the old rule carries `categorie_id`, `addresse_id`, `children_id` or
|
|
1349
|
+
* `ur_l_id` where the current rule expects `category_id`, `address_id`,
|
|
1350
|
+
* `child_id` and `url_id` — and the boot-time schema ensure is additive, so it
|
|
1351
|
+
* would create the new column empty beside the populated old one and leave the
|
|
1352
|
+
* relation reading nothing. No error, no missing table: the failure is silent,
|
|
1353
|
+
* which is the only reason this function still exists.
|
|
1354
|
+
*
|
|
1355
|
+
* `ensureCollectionSchema` calls it to recognise that shape and say so.
|
|
1356
|
+
* Returns the same string as `generateForeignKeyName` for every regular plural,
|
|
1357
|
+
* so a caller can compare the two and act only when they differ.
|
|
1358
|
+
*/
|
|
1359
|
+
function legacyForeignKeyName(name) {
|
|
1360
|
+
const snake = toSnakeCase(name);
|
|
1361
|
+
return `${snake.endsWith("s") ? snake.slice(0, -1) : snake}_id`;
|
|
1190
1362
|
}
|
|
1191
1363
|
//#endregion
|
|
1192
1364
|
//#region ../common/src/util/entities.ts
|
|
@@ -1264,7 +1436,7 @@ function traverseValueProperty(inputValue, property, operation) {
|
|
|
1264
1436
|
return value;
|
|
1265
1437
|
}
|
|
1266
1438
|
/**
|
|
1267
|
-
* Create a lightweight relation stub for
|
|
1439
|
+
* Create a lightweight relation stub for admin views.
|
|
1268
1440
|
* Replaces inline `{ id, path, __type: "relation" }` object literals.
|
|
1269
1441
|
*/
|
|
1270
1442
|
function createRelationRef(id, path) {
|
|
@@ -1426,8 +1598,7 @@ function enumToObjectEntries(enumValues) {
|
|
|
1426
1598
|
function resolveRelation(relation, sourceCollection, propertyKey) {
|
|
1427
1599
|
const target = relation.target;
|
|
1428
1600
|
if (typeof target !== "function") throw new Error(`Relation${relation.relationName ? ` '${relation.relationName}'` : ""} on '${sourceCollection.slug}' has no \`target\`. Give it a thunk: \`target: () => otherCollection\`.`);
|
|
1429
|
-
const targetCollection = target
|
|
1430
|
-
if (!targetCollection?.slug) throw new Error(`Relation${relation.relationName ? ` '${relation.relationName}'` : ""} on '${sourceCollection.slug}' has a \`target\` that did not resolve to a collection.`);
|
|
1601
|
+
const targetCollection = callTarget(relation, sourceCollection, propertyKey, target);
|
|
1431
1602
|
const relationName = relation.relationName ?? propertyKey ?? toSnakeCase(targetCollection.slug);
|
|
1432
1603
|
const shared = {
|
|
1433
1604
|
relationName,
|
|
@@ -1454,7 +1625,8 @@ function resolveRelation(relation, sourceCollection, propertyKey) {
|
|
|
1454
1625
|
cardinality: "one",
|
|
1455
1626
|
writable: true,
|
|
1456
1627
|
shared: false,
|
|
1457
|
-
foreignKeyOnTarget: relation.foreignKeyOnTarget ?? generateForeignKeyName(sourceName)
|
|
1628
|
+
foreignKeyOnTarget: relation.foreignKeyOnTarget ?? generateForeignKeyName(sourceName),
|
|
1629
|
+
sourceKey: relation.sourceKey
|
|
1458
1630
|
};
|
|
1459
1631
|
case "hasMany": return {
|
|
1460
1632
|
...shared,
|
|
@@ -1462,7 +1634,8 @@ function resolveRelation(relation, sourceCollection, propertyKey) {
|
|
|
1462
1634
|
cardinality: "many",
|
|
1463
1635
|
writable: true,
|
|
1464
1636
|
shared: false,
|
|
1465
|
-
foreignKeyOnTarget: relation.foreignKeyOnTarget ?? generateForeignKeyName(sourceName)
|
|
1637
|
+
foreignKeyOnTarget: relation.foreignKeyOnTarget ?? generateForeignKeyName(sourceName),
|
|
1638
|
+
sourceKey: relation.sourceKey
|
|
1466
1639
|
};
|
|
1467
1640
|
case "manyToMany": {
|
|
1468
1641
|
const sourceTable = getTableName(sourceCollection);
|
|
@@ -1491,6 +1664,43 @@ function resolveRelation(relation, sourceCollection, propertyKey) {
|
|
|
1491
1664
|
default: throw new Error(`Unknown relation kind: ${JSON.stringify(relation)}`);
|
|
1492
1665
|
}
|
|
1493
1666
|
}
|
|
1667
|
+
/** How this relation is addressed in an error message, before it has a resolved name. */
|
|
1668
|
+
function describe(relation, sourceCollection, propertyKey) {
|
|
1669
|
+
const name = relation.relationName ?? propertyKey;
|
|
1670
|
+
return `Relation${name ? ` '${name}'` : ""} on '${sourceCollection.slug}'`;
|
|
1671
|
+
}
|
|
1672
|
+
/**
|
|
1673
|
+
* Call the `target` thunk, and translate the two ways an import cycle breaks it
|
|
1674
|
+
* into an error that names the cause.
|
|
1675
|
+
*
|
|
1676
|
+
* The thunk exists to defer the reference until every module has finished
|
|
1677
|
+
* evaluating, and for a cycle that closes at import time it does. What it cannot
|
|
1678
|
+
* defer is a cycle that leaves the binding permanently unusable, and there are
|
|
1679
|
+
* two shapes of that:
|
|
1680
|
+
*
|
|
1681
|
+
* - **ESM/TDZ.** `const` and `class` bindings in a not-yet-evaluated module are
|
|
1682
|
+
* in the temporal dead zone, so reading one throws `ReferenceError: x is not
|
|
1683
|
+
* defined`. The stack points at the thunk — a one-line arrow function that is
|
|
1684
|
+
* obviously fine — and says nothing about the cycle that made it throw.
|
|
1685
|
+
* - **CJS interop.** The half-initialised module object has no `default` yet,
|
|
1686
|
+
* the import resolves to `undefined`, and the thunk returns it without
|
|
1687
|
+
* complaint. That one used to surface here as "did not resolve to a
|
|
1688
|
+
* collection", which is true and unhelpful.
|
|
1689
|
+
*
|
|
1690
|
+
* Both mean the same thing, and the fix for both is the same: break the cycle,
|
|
1691
|
+
* or move the relation into the collection that does not close it.
|
|
1692
|
+
*/
|
|
1693
|
+
function callTarget(relation, sourceCollection, propertyKey, target) {
|
|
1694
|
+
let targetCollection;
|
|
1695
|
+
try {
|
|
1696
|
+
targetCollection = target();
|
|
1697
|
+
} catch (error) {
|
|
1698
|
+
if (error instanceof ReferenceError) throw new Error(`${describe(relation, sourceCollection, propertyKey)} targets a collection that is not initialized yet — almost always an import cycle between the two collection files. Break the cycle (move the shared piece into a third module, or import the target lazily) so the target's module finishes evaluating before the registry is built.`, { cause: error });
|
|
1699
|
+
throw error;
|
|
1700
|
+
}
|
|
1701
|
+
if (!targetCollection?.slug) throw new Error(`${describe(relation, sourceCollection, propertyKey)} has a \`target\` that resolved to ${targetCollection === void 0 ? "`undefined`" : "something that is not a collection"}. ` + (targetCollection === void 0 ? "Under CommonJS interop an import cycle resolves the default import to `undefined`, so check whether this collection and its target import each other. Otherwise the thunk is returning the wrong value — it must return the collection itself, not a promise or a module." : "The thunk must return a collection config with a `slug`."));
|
|
1702
|
+
return targetCollection;
|
|
1703
|
+
}
|
|
1494
1704
|
//#endregion
|
|
1495
1705
|
//#region ../common/src/util/relations.ts
|
|
1496
1706
|
/**
|
|
@@ -1527,7 +1737,7 @@ var _resolvedRelationsCache = /* @__PURE__ */ new WeakMap();
|
|
|
1527
1737
|
function resolveCollectionRelations(collection) {
|
|
1528
1738
|
const cached = _resolvedRelationsCache.get(collection);
|
|
1529
1739
|
if (cached) return cached;
|
|
1530
|
-
if (!
|
|
1740
|
+
if (!isRelationalCollectionConfig(collection)) return {};
|
|
1531
1741
|
const relations = {};
|
|
1532
1742
|
for (const relation of collection.relations ?? []) {
|
|
1533
1743
|
const resolved = resolveRelation(relation, collection);
|
|
@@ -1543,7 +1753,7 @@ function resolveCollectionRelations(collection) {
|
|
|
1543
1753
|
return relations;
|
|
1544
1754
|
}
|
|
1545
1755
|
function getTableName(collection) {
|
|
1546
|
-
if (
|
|
1756
|
+
if (isRelationalCollectionConfig(collection)) return collection.table ?? toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
|
|
1547
1757
|
return toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
|
|
1548
1758
|
}
|
|
1549
1759
|
function getTableVarName(tableName) {
|
|
@@ -1811,7 +2021,12 @@ var UID_NOT_NULL = /auth\.uid\(\)\s+IS\s+NOT\s+NULL/i;
|
|
|
1811
2021
|
* is how the trusted *server* context is recognised), so:
|
|
1812
2022
|
*
|
|
1813
2023
|
* - `auth.uid() IS NOT NULL` is a tautology on the user path, and
|
|
1814
|
-
* - `auth.uid() != 'anon'`
|
|
2024
|
+
* - `auth.uid() != 'anon'` excludes one spelling of anonymous and admits the
|
|
2025
|
+
* other. This one is not hypothetical and was not only a foreign habit:
|
|
2026
|
+
* rebase's own request path reported `'anon'` while everything that compiled
|
|
2027
|
+
* or checked a policy used `'anonymous'`, so whichever literal an author
|
|
2028
|
+
* picked, half the anonymous callers walked through. See
|
|
2029
|
+
* {@link ANONYMOUS_USER_IDS}.
|
|
1815
2030
|
*
|
|
1816
2031
|
* Either one turns a lockdown into a full grant, and neither looks wrong. No
|
|
1817
2032
|
* real user id is ever one of these literals, and a user-context request is
|
|
@@ -1849,7 +2064,7 @@ function findAnonymousGrants(expr) {
|
|
|
1849
2064
|
found.push({
|
|
1850
2065
|
pattern: "foreign-uid-literal",
|
|
1851
2066
|
detail: literal.value,
|
|
1852
|
-
explanation: `'${literal.value}' is a ${platform} convention. Rebase reports an anonymous request as '${ANONYMOUS_USER_ID}', so comparing against '${literal.value}' passes for every caller. Use \`condition: policy.authenticated()\` to mean "signed in".`
|
|
2067
|
+
explanation: `'${literal.value}' is a ${platform} convention. Rebase reports an anonymous request as '${ANONYMOUS_USER_ID}', so comparing against '${literal.value}' passes for every caller. Use \`condition: policy.authenticated()\` to mean "signed in" — it compiles to NOT IN (${ANONYMOUS_USER_IDS.map((v) => `'${v}'`).join(", ")}), covering every spelling rebase has reported rather than whichever one you remember.`
|
|
1853
2068
|
});
|
|
1854
2069
|
return;
|
|
1855
2070
|
}
|
|
@@ -1944,7 +2159,7 @@ function compile(expr, scope) {
|
|
|
1944
2159
|
}
|
|
1945
2160
|
case "rolesOverlap": return `string_to_array(auth.roles(), ',') && ${rolesArraySql(expr.roles)}`;
|
|
1946
2161
|
case "rolesContain": return `string_to_array(auth.roles(), ',') @> ${rolesArraySql(expr.roles)}`;
|
|
1947
|
-
case "authenticated": return `auth.uid() IS NOT NULL AND auth.uid()
|
|
2162
|
+
case "authenticated": return `auth.uid() IS NOT NULL AND auth.uid() NOT IN (${ANONYMOUS_USER_IDS.map(quoteLiteral).join(", ")})`;
|
|
1948
2163
|
case "serverContext": return "auth.uid() IS NULL";
|
|
1949
2164
|
case "existsIn": return compileExistsIn(expr, scope);
|
|
1950
2165
|
case "raw": return expr.sql.replace(/\{(\w+)\}/g, (_, col) => `${outerQualifier(scope)}${resolveColumnName(col, scope.outerCollection)}`);
|
|
@@ -2016,75 +2231,6 @@ function rolesArraySql(roles) {
|
|
|
2016
2231
|
return `ARRAY[${[...roles].sort().map((r) => `'${r}'`).join(",")}]`;
|
|
2017
2232
|
}
|
|
2018
2233
|
//#endregion
|
|
2019
|
-
//#region ../common/src/util/callbacks.ts
|
|
2020
|
-
/**
|
|
2021
|
-
* Helper function to recursively check if there are any callbacks in the properties.
|
|
2022
|
-
*/
|
|
2023
|
-
function hasPropertyCallbacks(properties, callbackName) {
|
|
2024
|
-
if (!properties) return false;
|
|
2025
|
-
for (const property of Object.values(properties)) {
|
|
2026
|
-
if (property.callbacks?.[callbackName]) return true;
|
|
2027
|
-
if (property.type === "map" && property.properties) {
|
|
2028
|
-
if (hasPropertyCallbacks(property.properties, callbackName)) return true;
|
|
2029
|
-
} else if (property.type === "array" && property.of) {
|
|
2030
|
-
const ofs = Array.isArray(property.of) ? property.of : [property.of];
|
|
2031
|
-
for (const of of ofs) {
|
|
2032
|
-
if (of.callbacks?.[callbackName]) return true;
|
|
2033
|
-
if (of.type === "map" && of.properties && hasPropertyCallbacks(of.properties, callbackName)) return true;
|
|
2034
|
-
}
|
|
2035
|
-
}
|
|
2036
|
-
}
|
|
2037
|
-
return false;
|
|
2038
|
-
}
|
|
2039
|
-
/**
|
|
2040
|
-
* Recursively process properties to apply field-level hooks.
|
|
2041
|
-
*/
|
|
2042
|
-
async function processProperties(properties, values, previousValues, propsContext, callbackName) {
|
|
2043
|
-
if (!values || typeof values !== "object") return values;
|
|
2044
|
-
const result = { ...values };
|
|
2045
|
-
for (const [key, property] of Object.entries(properties)) {
|
|
2046
|
-
if (result[key] === void 0) continue;
|
|
2047
|
-
let currentValue = result[key];
|
|
2048
|
-
const previousValue = previousValues?.[key];
|
|
2049
|
-
if (property.type === "array" && Array.isArray(currentValue)) {
|
|
2050
|
-
if (property.of && !Array.isArray(property.of)) currentValue = await Promise.all(currentValue.map(async (item, index) => {
|
|
2051
|
-
const prevItem = Array.isArray(previousValue) ? previousValue[index] : void 0;
|
|
2052
|
-
return (await processProperties({ "_tmp": property.of }, { "_tmp": item }, { "_tmp": prevItem }, propsContext, callbackName))["_tmp"];
|
|
2053
|
-
}));
|
|
2054
|
-
} else if (property.type === "map" && property.properties && typeof currentValue === "object") currentValue = await processProperties(property.properties, currentValue, previousValue ?? {}, propsContext, callbackName);
|
|
2055
|
-
if (property.callbacks?.[callbackName]) {
|
|
2056
|
-
const cbRes = await Promise.resolve(property.callbacks[callbackName]({
|
|
2057
|
-
...propsContext,
|
|
2058
|
-
value: currentValue,
|
|
2059
|
-
previousValue
|
|
2060
|
-
}));
|
|
2061
|
-
if (cbRes !== void 0) currentValue = cbRes;
|
|
2062
|
-
}
|
|
2063
|
-
result[key] = currentValue;
|
|
2064
|
-
}
|
|
2065
|
-
return result;
|
|
2066
|
-
}
|
|
2067
|
-
/**
|
|
2068
|
-
* Helper function to extract field-level PropertyCallbacks from a properties schema
|
|
2069
|
-
* and wrap them into an CollectionCallbacks object recursively.
|
|
2070
|
-
*/
|
|
2071
|
-
var buildPropertyCallbacks = (properties) => {
|
|
2072
|
-
if (!properties) return void 0;
|
|
2073
|
-
const propertyCallbacks = {};
|
|
2074
|
-
if (hasPropertyCallbacks(properties, "afterRead")) propertyCallbacks.afterRead = async (props) => {
|
|
2075
|
-
const row = props.row;
|
|
2076
|
-
const processedValues = await processProperties(properties, row, row, props, "afterRead");
|
|
2077
|
-
return {
|
|
2078
|
-
...props.row,
|
|
2079
|
-
...processedValues
|
|
2080
|
-
};
|
|
2081
|
-
};
|
|
2082
|
-
if (hasPropertyCallbacks(properties, "beforeSave")) propertyCallbacks.beforeSave = async (props) => {
|
|
2083
|
-
return await processProperties(properties, props.values, props.previousValues ?? {}, props, "beforeSave");
|
|
2084
|
-
};
|
|
2085
|
-
return Object.keys(propertyCallbacks).length > 0 ? propertyCallbacks : void 0;
|
|
2086
|
-
};
|
|
2087
|
-
//#endregion
|
|
2088
2234
|
//#region ../common/src/util/auth-default-policies.ts
|
|
2089
2235
|
/**
|
|
2090
2236
|
* Default RLS policies injected by the schema generator.
|
|
@@ -2152,8 +2298,8 @@ function getIdPropertyName$1(collection) {
|
|
|
2152
2298
|
* Collections that opt out via `disableDefaultPolicies` are returned unchanged.
|
|
2153
2299
|
*/
|
|
2154
2300
|
function getEffectiveSecurityRules(collection) {
|
|
2155
|
-
const explicit = [...
|
|
2156
|
-
if (collection.disableDefaultPolicies) return explicit;
|
|
2301
|
+
const explicit = [...collection.securityRules ?? []];
|
|
2302
|
+
if (isPostgresCollectionConfig(collection) && collection.disableDefaultPolicies) return explicit;
|
|
2157
2303
|
const tableName = getTableName(collection);
|
|
2158
2304
|
const injected = [];
|
|
2159
2305
|
injected.push({
|
|
@@ -2324,7 +2470,7 @@ function coversUpdate(rule) {
|
|
|
2324
2470
|
* stays locked (RLS is still enabled) until they write policies for it.
|
|
2325
2471
|
*/
|
|
2326
2472
|
function getJunctionSecurityRules(spec) {
|
|
2327
|
-
if (spec.declaringSides.every((side) => side.collection.disableDefaultPolicies)) return [];
|
|
2473
|
+
if (spec.declaringSides.every((side) => isPostgresCollectionConfig(side.collection) && side.collection.disableDefaultPolicies)) return [];
|
|
2328
2474
|
const rules = [];
|
|
2329
2475
|
rules.push({
|
|
2330
2476
|
name: `${spec.table}_default_admin_read`,
|
|
@@ -2659,10 +2805,27 @@ function getJunctionSecurityRules(spec) {
|
|
|
2659
2805
|
});
|
|
2660
2806
|
})))();
|
|
2661
2807
|
/**
|
|
2662
|
-
*
|
|
2808
|
+
* How wide a `varchar`/`char` column should be for a given property.
|
|
2809
|
+
*
|
|
2810
|
+
* One definition, three call sites, because they used to disagree. For the same
|
|
2811
|
+
* `columnType: "varchar"` property the DDL generator emitted `VARCHAR(255)`
|
|
2812
|
+
* while the Drizzle generator emitted a bare `varchar("col")` — which Postgres
|
|
2813
|
+
* reads as *unbounded* — so which of the two you ran decided whether the column
|
|
2814
|
+
* had a limit at all. Introspection then dropped the length entirely, so reading
|
|
2815
|
+
* an existing `character varying(500)` column back and regenerating it produced
|
|
2816
|
+
* a `VARCHAR(255)`: a silent narrowing of a column with data already in it.
|
|
2817
|
+
*
|
|
2818
|
+
* `validation.max` is the property's own statement about how long the value may
|
|
2819
|
+
* be, so it is the only sensible source for the column's width — and it keeps
|
|
2820
|
+
* the constraint the database enforces in step with the one the app enforces,
|
|
2821
|
+
* rather than inventing a second, different limit underneath it.
|
|
2663
2822
|
*/
|
|
2823
|
+
function resolveStringColumnLength(prop) {
|
|
2824
|
+
const max = prop.validation?.max;
|
|
2825
|
+
return typeof max === "number" && Number.isInteger(max) && max > 0 ? max : 255;
|
|
2826
|
+
}
|
|
2664
2827
|
//#endregion
|
|
2665
|
-
//#region ../../node_modules/.pnpm/fast-equals@6.0.
|
|
2828
|
+
//#region ../../node_modules/.pnpm/fast-equals@6.0.2/node_modules/fast-equals/dist/es/index.mjs
|
|
2666
2829
|
var { getOwnPropertyNames, getOwnPropertySymbols } = Object;
|
|
2667
2830
|
var { hasOwnProperty } = Object.prototype;
|
|
2668
2831
|
/**
|
|
@@ -2698,7 +2861,8 @@ function createIsCircular(areItemsEqual) {
|
|
|
2698
2861
|
* not enumerable and symbol properties.
|
|
2699
2862
|
*/
|
|
2700
2863
|
function getStrictProperties(object) {
|
|
2701
|
-
|
|
2864
|
+
const symbols = getOwnPropertySymbols(object);
|
|
2865
|
+
return symbols.length ? getOwnPropertyNames(object).concat(symbols) : getOwnPropertyNames(object);
|
|
2702
2866
|
}
|
|
2703
2867
|
/**
|
|
2704
2868
|
* Whether the object contains the property passed as an own property.
|
|
@@ -2771,7 +2935,7 @@ function areMapsEqual(a, b, state) {
|
|
|
2771
2935
|
const size = a.size;
|
|
2772
2936
|
if (size !== b.size) return false;
|
|
2773
2937
|
if (!size) return true;
|
|
2774
|
-
const matchedIndices = new
|
|
2938
|
+
const matchedIndices = new Uint8Array(size);
|
|
2775
2939
|
const aIterable = a.entries();
|
|
2776
2940
|
let aResult;
|
|
2777
2941
|
let bResult;
|
|
@@ -2779,7 +2943,7 @@ function areMapsEqual(a, b, state) {
|
|
|
2779
2943
|
while (aResult = aIterable.next()) {
|
|
2780
2944
|
if (aResult.done) break;
|
|
2781
2945
|
const bIterable = b.entries();
|
|
2782
|
-
let hasMatch =
|
|
2946
|
+
let hasMatch = 0;
|
|
2783
2947
|
let matchIndex = 0;
|
|
2784
2948
|
while (bResult = bIterable.next()) {
|
|
2785
2949
|
if (bResult.done) break;
|
|
@@ -2790,7 +2954,7 @@ function areMapsEqual(a, b, state) {
|
|
|
2790
2954
|
const aEntry = aResult.value;
|
|
2791
2955
|
const bEntry = bResult.value;
|
|
2792
2956
|
if (state.equals(aEntry[0], bEntry[0], index, matchIndex, a, b, state) && state.equals(aEntry[1], bEntry[1], aEntry[0], bEntry[0], a, b, state)) {
|
|
2793
|
-
hasMatch = matchedIndices[matchIndex] =
|
|
2957
|
+
hasMatch = matchedIndices[matchIndex] = 1;
|
|
2794
2958
|
break;
|
|
2795
2959
|
}
|
|
2796
2960
|
matchIndex++;
|
|
@@ -2848,19 +3012,19 @@ function areSetsEqual(a, b, state) {
|
|
|
2848
3012
|
const size = a.size;
|
|
2849
3013
|
if (size !== b.size) return false;
|
|
2850
3014
|
if (!size) return true;
|
|
2851
|
-
const matchedIndices = new
|
|
3015
|
+
const matchedIndices = new Uint8Array(size);
|
|
2852
3016
|
const aIterable = a.values();
|
|
2853
3017
|
let aResult;
|
|
2854
3018
|
let bResult;
|
|
2855
3019
|
while (aResult = aIterable.next()) {
|
|
2856
3020
|
if (aResult.done) break;
|
|
2857
3021
|
const bIterable = b.values();
|
|
2858
|
-
let hasMatch =
|
|
3022
|
+
let hasMatch = 0;
|
|
2859
3023
|
let matchIndex = 0;
|
|
2860
3024
|
while (bResult = bIterable.next()) {
|
|
2861
3025
|
if (bResult.done) break;
|
|
2862
3026
|
if (!matchedIndices[matchIndex] && state.equals(aResult.value, bResult.value, aResult.value, bResult.value, a, b, state)) {
|
|
2863
|
-
hasMatch = matchedIndices[matchIndex] =
|
|
3027
|
+
hasMatch = matchedIndices[matchIndex] = 1;
|
|
2864
3028
|
break;
|
|
2865
3029
|
}
|
|
2866
3030
|
matchIndex++;
|
|
@@ -2873,8 +3037,8 @@ function areSetsEqual(a, b, state) {
|
|
|
2873
3037
|
* Whether the TypedArray instances are equal in value.
|
|
2874
3038
|
*/
|
|
2875
3039
|
function areTypedArraysEqual(a, b) {
|
|
2876
|
-
let index = a.
|
|
2877
|
-
if (b.
|
|
3040
|
+
let index = a.length;
|
|
3041
|
+
if (b.length !== index || a.byteOffset !== b.byteOffset) return false;
|
|
2878
3042
|
while (index-- > 0) if (a[index] !== b[index]) return false;
|
|
2879
3043
|
return true;
|
|
2880
3044
|
}
|
|
@@ -3444,6 +3608,136 @@ var QueryBuilder = class {
|
|
|
3444
3608
|
return this.collection.listen(this.params, onUpdate, onError);
|
|
3445
3609
|
}
|
|
3446
3610
|
};
|
|
3611
|
+
/** Rows `findAll()` will materialise before it refuses to continue. */
|
|
3612
|
+
var DEFAULT_FIND_ALL_MAX_ROWS = 1e4;
|
|
3613
|
+
/**
|
|
3614
|
+
* Requests one walk may make before it gives up on the server ever saying
|
|
3615
|
+
* `hasMore: false`. At the default page size that is two million rows — far
|
|
3616
|
+
* past any legitimate walk, and short of running forever.
|
|
3617
|
+
*/
|
|
3618
|
+
var DEFAULT_MAX_PAGES = 1e4;
|
|
3619
|
+
/**
|
|
3620
|
+
* Thrown when a walk stops for a reason the caller needs to know about.
|
|
3621
|
+
*
|
|
3622
|
+
* Every one of these is a case where the alternative would be silent: a
|
|
3623
|
+
* truncated array that looks complete, or a loop that never returns. Check
|
|
3624
|
+
* {@link code} to tell them apart.
|
|
3625
|
+
*/
|
|
3626
|
+
var RebasePaginationError = class RebasePaginationError extends Error {
|
|
3627
|
+
code;
|
|
3628
|
+
constructor(code, message) {
|
|
3629
|
+
super(message);
|
|
3630
|
+
this.name = "RebasePaginationError";
|
|
3631
|
+
this.code = code;
|
|
3632
|
+
Object.setPrototypeOf(this, RebasePaginationError.prototype);
|
|
3633
|
+
}
|
|
3634
|
+
};
|
|
3635
|
+
function normalizePageSize(raw) {
|
|
3636
|
+
if (raw === void 0 || !Number.isFinite(raw)) return 200;
|
|
3637
|
+
return Math.max(1, Math.floor(raw));
|
|
3638
|
+
}
|
|
3639
|
+
function normalizeMaxPages(raw) {
|
|
3640
|
+
if (raw === void 0) return DEFAULT_MAX_PAGES;
|
|
3641
|
+
if (raw === Number.POSITIVE_INFINITY) return raw;
|
|
3642
|
+
if (!Number.isFinite(raw)) return DEFAULT_MAX_PAGES;
|
|
3643
|
+
return Math.max(1, Math.floor(raw));
|
|
3644
|
+
}
|
|
3645
|
+
function normalizeMaxRows(raw) {
|
|
3646
|
+
if (raw === void 0) return DEFAULT_FIND_ALL_MAX_ROWS;
|
|
3647
|
+
if (raw === Number.POSITIVE_INFINITY) return raw;
|
|
3648
|
+
if (!Number.isFinite(raw)) return DEFAULT_FIND_ALL_MAX_ROWS;
|
|
3649
|
+
return Math.max(0, Math.floor(raw));
|
|
3650
|
+
}
|
|
3651
|
+
/**
|
|
3652
|
+
* Add one condition to a `where` map without disturbing what is already there.
|
|
3653
|
+
*
|
|
3654
|
+
* The caller's own filter on the cursor column has to survive — dropping it
|
|
3655
|
+
* would widen the query, which is the silent-filter-loss failure mode — so a
|
|
3656
|
+
* second condition on the same column becomes the array-of-tuples form that
|
|
3657
|
+
* `FindParams.where` already accepts, and both are AND-ed.
|
|
3658
|
+
*/
|
|
3659
|
+
function appendCondition(where, column, condition) {
|
|
3660
|
+
const next = { ...where ?? {} };
|
|
3661
|
+
const existing = next[column];
|
|
3662
|
+
if (existing === void 0) next[column] = condition;
|
|
3663
|
+
else if (Array.isArray(existing) && existing.length > 0 && Array.isArray(existing[0])) next[column] = [...existing, condition];
|
|
3664
|
+
else next[column] = [existing, condition];
|
|
3665
|
+
return next;
|
|
3666
|
+
}
|
|
3667
|
+
function cursorEquals(a, b) {
|
|
3668
|
+
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
|
|
3669
|
+
return Object.is(a, b);
|
|
3670
|
+
}
|
|
3671
|
+
/**
|
|
3672
|
+
* Walk every row a query matches, yielding one row at a time and fetching the
|
|
3673
|
+
* next page only when the consumer asks for it.
|
|
3674
|
+
*
|
|
3675
|
+
* See {@link SDKCollectionClient.iterate} for the caller-facing contract,
|
|
3676
|
+
* including the offset-drift caveat and the `cursor` alternative.
|
|
3677
|
+
*
|
|
3678
|
+
* @param find the transport's single-page read
|
|
3679
|
+
* @param params `find()` parameters minus the window, plus the walk options
|
|
3680
|
+
* @param label the collection name, so an error says which walk failed
|
|
3681
|
+
*/
|
|
3682
|
+
async function* paginateFind(find, params, label = "collection") {
|
|
3683
|
+
const { pageSize, cursor, maxPages, ...rest } = params ?? {};
|
|
3684
|
+
const findParams = { ...rest };
|
|
3685
|
+
const size = normalizePageSize(pageSize);
|
|
3686
|
+
const pageCap = normalizeMaxPages(maxPages);
|
|
3687
|
+
const cursorField = typeof cursor === "string" ? cursor : cursor?.field;
|
|
3688
|
+
const requestedDirection = typeof cursor === "object" && cursor !== null ? cursor.direction : void 0;
|
|
3689
|
+
let direction = "asc";
|
|
3690
|
+
if (cursorField) {
|
|
3691
|
+
const orderBy = findParams.orderBy;
|
|
3692
|
+
if (orderBy && orderBy[0] !== cursorField) throw new RebasePaginationError("cursor-order-mismatch", `Cannot seek on "${cursorField}" while ordering "${label}" by "${orderBy[0]}": keyset pagination only advances along the column the query is sorted by. Order by "${cursorField}", or drop the cursor and page by offset.`);
|
|
3693
|
+
direction = requestedDirection ?? orderBy?.[1] ?? "asc";
|
|
3694
|
+
findParams.orderBy = [cursorField, direction];
|
|
3695
|
+
}
|
|
3696
|
+
const seekOp = direction === "desc" ? "<" : ">";
|
|
3697
|
+
const baseWhere = findParams.where;
|
|
3698
|
+
let offset = 0;
|
|
3699
|
+
let pages = 0;
|
|
3700
|
+
let cursorValue;
|
|
3701
|
+
let seeking = false;
|
|
3702
|
+
for (;;) {
|
|
3703
|
+
if (pages >= pageCap) throw new RebasePaginationError("max-pages", `Iterating "${label}" made ${pages} requests without the server reporting the end of the collection. Stopping rather than looping forever — raise \`maxPages\` if the walk is genuinely this long, or check that the backend sets \`meta.hasMore\`.`);
|
|
3704
|
+
const pageParams = {
|
|
3705
|
+
...findParams,
|
|
3706
|
+
limit: size
|
|
3707
|
+
};
|
|
3708
|
+
if (cursorField) {
|
|
3709
|
+
if (seeking) pageParams.where = appendCondition(baseWhere, cursorField, [seekOp, cursorValue]);
|
|
3710
|
+
} else pageParams.offset = offset;
|
|
3711
|
+
const page = await find(pageParams);
|
|
3712
|
+
pages += 1;
|
|
3713
|
+
const rows = page?.data ?? [];
|
|
3714
|
+
if (rows.length === 0) return;
|
|
3715
|
+
for (const row of rows) yield row;
|
|
3716
|
+
if (page?.meta?.hasMore !== true) return;
|
|
3717
|
+
if (cursorField) {
|
|
3718
|
+
const nextValue = rows[rows.length - 1]?.[cursorField];
|
|
3719
|
+
if (nextValue === void 0 || nextValue === null) throw new RebasePaginationError("cursor-missing", `Cannot seek past the last row of "${label}": it has no value for the cursor column "${cursorField}". Pick a column that is present and non-null on every row.`);
|
|
3720
|
+
if (seeking && cursorEquals(nextValue, cursorValue)) throw new RebasePaginationError("cursor-stalled", `Iterating "${label}" is stuck: two pages in a row ended at ${cursorField}=${String(nextValue)}. The cursor column has to be unique — a repeated value cannot be seeked past, and continuing would either loop forever or skip the duplicates. Use the primary key, or page by offset.`);
|
|
3721
|
+
cursorValue = nextValue;
|
|
3722
|
+
seeking = true;
|
|
3723
|
+
} else offset += rows.length;
|
|
3724
|
+
}
|
|
3725
|
+
}
|
|
3726
|
+
/**
|
|
3727
|
+
* {@link paginateFind}, collected into an array under a ceiling.
|
|
3728
|
+
*
|
|
3729
|
+
* See {@link SDKCollectionClient.findAll}.
|
|
3730
|
+
*/
|
|
3731
|
+
async function collectAllPages(find, params, label = "collection") {
|
|
3732
|
+
const { maxRows, ...rest } = params ?? {};
|
|
3733
|
+
const cap = normalizeMaxRows(maxRows);
|
|
3734
|
+
const out = [];
|
|
3735
|
+
for await (const row of paginateFind(find, rest, label)) {
|
|
3736
|
+
out.push(row);
|
|
3737
|
+
if (out.length > cap) throw new RebasePaginationError("max-rows", `findAll("${label}") matched more than ${cap} rows. Returning the first ${cap} would look like the whole answer and quietly not be one, so this throws instead. Raise \`maxRows\` if you meant to load them all, or stream with \`iterate()\`.`);
|
|
3738
|
+
}
|
|
3739
|
+
return out;
|
|
3740
|
+
}
|
|
3447
3741
|
//#endregion
|
|
3448
3742
|
//#region ../common/src/data/filter-dialect.ts
|
|
3449
3743
|
/**
|
|
@@ -3593,6 +3887,42 @@ function rowToEntity(row, slug, primaryKeys = []) {
|
|
|
3593
3887
|
values: row
|
|
3594
3888
|
};
|
|
3595
3889
|
}
|
|
3890
|
+
/**
|
|
3891
|
+
* The relation envelope `toFlatRow` writes where a relation was:
|
|
3892
|
+
* `{ id, path, __type: "relation", data: { id, path, values } }`. It is the
|
|
3893
|
+
* admin's view-model, and the only pipeline that produces one is postgres'.
|
|
3894
|
+
*/
|
|
3895
|
+
function isRelationEnvelope(value) {
|
|
3896
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && value.__type === "relation";
|
|
3897
|
+
}
|
|
3898
|
+
/** The target's own columns, as `toRestRow` would have inlined them. */
|
|
3899
|
+
function inlineEnvelope(envelope) {
|
|
3900
|
+
return envelope.data?.values ?? {};
|
|
3901
|
+
}
|
|
3902
|
+
/**
|
|
3903
|
+
* Replace every relation envelope on a row with the target's flat columns.
|
|
3904
|
+
*
|
|
3905
|
+
* The SDK serves one relation shape — the inlined one (see
|
|
3906
|
+
* {@link RestFetchService}) — and reads that come back through a *driver*
|
|
3907
|
+
* method rather than the REST pipeline still carry envelopes. Realtime is the
|
|
3908
|
+
* one such read left: there is no `listenForRest`, so the rows arrive shaped
|
|
3909
|
+
* for the admin and are flattened here instead.
|
|
3910
|
+
*
|
|
3911
|
+
* Only applied where the REST pipeline is the contract (see `find`); a driver
|
|
3912
|
+
* without a `restFetchService` keeps whatever it returns, so the admin's own
|
|
3913
|
+
* path through {@link buildRebaseData} is untouched.
|
|
3914
|
+
*/
|
|
3915
|
+
function inlineRelationRefs(row) {
|
|
3916
|
+
let out;
|
|
3917
|
+
for (const [key, value] of Object.entries(row)) if (isRelationEnvelope(value)) {
|
|
3918
|
+
out = out ?? { ...row };
|
|
3919
|
+
out[key] = inlineEnvelope(value);
|
|
3920
|
+
} else if (Array.isArray(value) && value.some(isRelationEnvelope)) {
|
|
3921
|
+
out = out ?? { ...row };
|
|
3922
|
+
out[key] = value.map((item) => isRelationEnvelope(item) ? inlineEnvelope(item) : item);
|
|
3923
|
+
}
|
|
3924
|
+
return out ?? row;
|
|
3925
|
+
}
|
|
3596
3926
|
function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
3597
3927
|
const accessor = {
|
|
3598
3928
|
async find(params) {
|
|
@@ -3600,14 +3930,14 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
3600
3930
|
const limit = params?.limit ?? 20;
|
|
3601
3931
|
const offset = params?.offset ?? 0;
|
|
3602
3932
|
const fetchService = driver.restFetchService;
|
|
3603
|
-
const rows = fetchService
|
|
3933
|
+
const rows = fetchService ? await fetchService.fetchCollectionForRest(slug, {
|
|
3604
3934
|
filter,
|
|
3605
3935
|
limit: params?.limit,
|
|
3606
3936
|
offset: params?.offset,
|
|
3607
3937
|
orderBy: params?.orderBy?.[0],
|
|
3608
3938
|
order: params?.orderBy?.[1],
|
|
3609
3939
|
searchString: params?.searchString
|
|
3610
|
-
}, params
|
|
3940
|
+
}, params?.include) : await driver.fetchCollection({
|
|
3611
3941
|
path: slug,
|
|
3612
3942
|
limit: params?.limit,
|
|
3613
3943
|
offset: params?.offset,
|
|
@@ -3636,7 +3966,8 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
3636
3966
|
};
|
|
3637
3967
|
},
|
|
3638
3968
|
async findById(id) {
|
|
3639
|
-
const
|
|
3969
|
+
const fetchService = driver.restFetchService;
|
|
3970
|
+
const row = fetchService ? await fetchService.fetchOneForRest(slug, id) : await driver.fetchOne({
|
|
3640
3971
|
path: slug,
|
|
3641
3972
|
id
|
|
3642
3973
|
});
|
|
@@ -3682,6 +4013,7 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
3682
4013
|
listen: driver.listenCollection ? (params, onUpdate, onError) => {
|
|
3683
4014
|
const limit = params?.limit ?? 20;
|
|
3684
4015
|
const offset = params?.offset ?? 0;
|
|
4016
|
+
const normalize = driver.restFetchService ? inlineRelationRefs : (row) => row;
|
|
3685
4017
|
return driver.listenCollection({
|
|
3686
4018
|
path: slug,
|
|
3687
4019
|
limit: params?.limit,
|
|
@@ -3692,7 +4024,7 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
3692
4024
|
searchString: params?.searchString,
|
|
3693
4025
|
onUpdate: (entities) => {
|
|
3694
4026
|
onUpdate({
|
|
3695
|
-
data: entities.map((row) => rowToEntity(row, slug, getPks())),
|
|
4027
|
+
data: entities.map((row) => rowToEntity(normalize(row), slug, getPks())),
|
|
3696
4028
|
meta: {
|
|
3697
4029
|
total: entities.length,
|
|
3698
4030
|
limit,
|
|
@@ -3705,10 +4037,11 @@ function createDriverAccessor(driver, slug, getPks = () => []) {
|
|
|
3705
4037
|
});
|
|
3706
4038
|
} : void 0,
|
|
3707
4039
|
listenById: driver.listenOne ? (id, onUpdate, onError) => {
|
|
4040
|
+
const normalize = driver.restFetchService ? inlineRelationRefs : (row) => row;
|
|
3708
4041
|
return driver.listenOne({
|
|
3709
4042
|
path: slug,
|
|
3710
4043
|
id,
|
|
3711
|
-
onUpdate: (entity) => onUpdate(entity ? rowToEntity(entity, slug, getPks()) : void 0),
|
|
4044
|
+
onUpdate: (entity) => onUpdate(entity ? rowToEntity(normalize(entity), slug, getPks()) : void 0),
|
|
3712
4045
|
onError
|
|
3713
4046
|
});
|
|
3714
4047
|
} : void 0,
|
|
@@ -3839,7 +4172,7 @@ var SdkQueryBuilder = class {
|
|
|
3839
4172
|
* {@link SDKCollectionClient}. Every returned record is unwrapped to a flat row
|
|
3840
4173
|
* so the backend SDK is byte-for-byte the same shape as the frontend client.
|
|
3841
4174
|
*/
|
|
3842
|
-
function toSdkCollectionClient(snap) {
|
|
4175
|
+
function toSdkCollectionClient(snap, slug = "collection") {
|
|
3843
4176
|
const client = {
|
|
3844
4177
|
async find(params) {
|
|
3845
4178
|
const res = await snap.find(params);
|
|
@@ -3848,6 +4181,12 @@ function toSdkCollectionClient(snap) {
|
|
|
3848
4181
|
meta: res.meta
|
|
3849
4182
|
};
|
|
3850
4183
|
},
|
|
4184
|
+
iterate(params) {
|
|
4185
|
+
return paginateFind((p) => client.find(p), params, slug);
|
|
4186
|
+
},
|
|
4187
|
+
findAll(params) {
|
|
4188
|
+
return collectAllPages((p) => client.find(p), params, slug);
|
|
4189
|
+
},
|
|
3851
4190
|
async findById(id) {
|
|
3852
4191
|
const s = await snap.findById(id);
|
|
3853
4192
|
return s ? entityToRow(s) : void 0;
|
|
@@ -3899,7 +4238,7 @@ function wrapAsSdkData(entityData) {
|
|
|
3899
4238
|
function getAccessor(slug) {
|
|
3900
4239
|
let accessor = cache.get(slug);
|
|
3901
4240
|
if (!accessor) {
|
|
3902
|
-
accessor = toSdkCollectionClient(entityData.collection(slug));
|
|
4241
|
+
accessor = toSdkCollectionClient(entityData.collection(slug), slug);
|
|
3903
4242
|
cache.set(slug, accessor);
|
|
3904
4243
|
}
|
|
3905
4244
|
return accessor;
|
|
@@ -3916,79 +4255,17 @@ function wrapAsSdkData(entityData) {
|
|
|
3916
4255
|
*
|
|
3917
4256
|
* This is the developer-facing SDK data layer used by backend framework
|
|
3918
4257
|
* callbacks & scripts (`context.data` / `rebase.data`). It returns flat rows —
|
|
3919
|
-
* identical in shape to the frontend SDK client
|
|
3920
|
-
*
|
|
4258
|
+
* identical in shape to the frontend SDK client, down to how a relation is
|
|
4259
|
+
* served: a foreign key stays a foreign key, and a relation named in `include`
|
|
4260
|
+
* arrives as the target's own columns. The `{ __type: "relation" }` envelope is
|
|
4261
|
+
* the admin's view-model and never reaches here.
|
|
4262
|
+
*
|
|
4263
|
+
* The admin uses {@link buildRebaseData} (Entity) over its own driver.
|
|
3921
4264
|
*/
|
|
3922
4265
|
function buildSdkData(driver) {
|
|
3923
4266
|
return wrapAsSdkData(buildRebaseData(driver));
|
|
3924
4267
|
}
|
|
3925
4268
|
//#endregion
|
|
3926
|
-
|
|
3927
|
-
/** Schemas that are always considered Rebase-internal. */
|
|
3928
|
-
var REBASE_INTERNAL_SCHEMAS = ["rebase", "auth"];
|
|
3929
|
-
/** Table-name prefixes that mark a table as Rebase-internal regardless of schema. */
|
|
3930
|
-
var REBASE_INTERNAL_PREFIXES = [
|
|
3931
|
-
"_rebase_",
|
|
3932
|
-
"_auth_",
|
|
3933
|
-
"drizzle_"
|
|
3934
|
-
];
|
|
3935
|
-
/**
|
|
3936
|
-
* Synchronously classify a table based on naming conventions.
|
|
3937
|
-
*
|
|
3938
|
-
* @param tableName - The unqualified name of the table.
|
|
3939
|
-
* @param schemaName - The schema the table belongs to (e.g. `"public"`, `"rebase"`).
|
|
3940
|
-
* @returns `"rebase-internal"` when the table belongs to a reserved schema or
|
|
3941
|
-
* carries a reserved prefix; `"user"` otherwise.
|
|
3942
|
-
*
|
|
3943
|
-
* @remarks
|
|
3944
|
-
* Junction-table detection requires an async database query and is therefore
|
|
3945
|
-
* **not** handled by this function. Use {@link detectJunctionTables} to obtain
|
|
3946
|
-
* the set of junction tables, then reclassify as needed.
|
|
3947
|
-
*/
|
|
3948
|
-
function classifyTable(tableName, schemaName) {
|
|
3949
|
-
if (REBASE_INTERNAL_SCHEMAS.includes(schemaName) || REBASE_INTERNAL_PREFIXES.some((prefix) => tableName.startsWith(prefix))) return "rebase-internal";
|
|
3950
|
-
return "user";
|
|
3951
|
-
}
|
|
3952
|
-
/** SQL query that detects junction tables in the `public` schema. */
|
|
3953
|
-
var JUNCTION_TABLES_SQL = `
|
|
3954
|
-
SELECT t.table_name
|
|
3955
|
-
FROM information_schema.tables t
|
|
3956
|
-
WHERE t.table_schema = 'public'
|
|
3957
|
-
AND t.table_type = 'BASE TABLE'
|
|
3958
|
-
AND NOT EXISTS (
|
|
3959
|
-
SELECT 1
|
|
3960
|
-
FROM information_schema.columns c
|
|
3961
|
-
WHERE c.table_schema = t.table_schema
|
|
3962
|
-
AND c.table_name = t.table_name
|
|
3963
|
-
AND c.column_name NOT IN (
|
|
3964
|
-
SELECT kcu.column_name
|
|
3965
|
-
FROM information_schema.key_column_usage kcu
|
|
3966
|
-
JOIN information_schema.table_constraints tc
|
|
3967
|
-
ON tc.constraint_name = kcu.constraint_name
|
|
3968
|
-
AND tc.table_schema = kcu.table_schema
|
|
3969
|
-
WHERE tc.constraint_type = 'FOREIGN KEY'
|
|
3970
|
-
AND kcu.table_schema = t.table_schema
|
|
3971
|
-
AND kcu.table_name = t.table_name
|
|
3972
|
-
)
|
|
3973
|
-
)
|
|
3974
|
-
`;
|
|
3975
|
-
/**
|
|
3976
|
-
* Asynchronously detect junction (link) tables in the `public` schema.
|
|
3977
|
-
*
|
|
3978
|
-
* A junction table is defined as a table where **every** column participates in
|
|
3979
|
-
* at least one foreign-key constraint.
|
|
3980
|
-
*
|
|
3981
|
-
* @param executeSql - A callback that executes a raw SQL string and returns the
|
|
3982
|
-
* resulting rows.
|
|
3983
|
-
* @returns A `Set` containing the names of all detected junction tables.
|
|
3984
|
-
*/
|
|
3985
|
-
async function detectJunctionTables(executeSql) {
|
|
3986
|
-
const rows = await executeSql(JUNCTION_TABLES_SQL);
|
|
3987
|
-
const junctionTables = /* @__PURE__ */ new Set();
|
|
3988
|
-
for (const row of rows) if (typeof row.table_name === "string") junctionTables.add(row.table_name);
|
|
3989
|
-
return junctionTables;
|
|
3990
|
-
}
|
|
3991
|
-
//#endregion
|
|
3992
|
-
export { DEFAULT_ONE_OF_TYPE as A, createRelationRefWithData as C, mergeDeep as D, getPolicyNamesForRule as E, camelCase as O, createRelationRef as S, updateDateAutoValues as T, getTableVarName as _, getJunctionCollectionConfig as a, getDeclaredPrimaryKeys as b, getEffectiveSecurityRules as c, securityRuleToConditions as d, findAnonymousGrants as f, getTableName as g, getEnumVarName as h, CollectionRegistry as i, DEFAULT_ONE_OF_VALUE as j, toSnakeCase as k, buildPropertyCallbacks as l, getColumnName as m, detectJunctionTables as n, getJunctionSecurityRules as o, findRelation as p, buildSdkData as r, resolveJunctionSpecs as s, classifyTable as t, policyToPostgres as u, resolveCollectionRelations as v, normalizeToEntityRelation as w, parseIdValues as x, buildCompositeId as y };
|
|
4269
|
+
export { DEFAULT_ONE_OF_TYPE as A, updateDateAutoValues as C, mergeDeep as D, getPolicyNamesForRule as E, hasForeignKeyOnTarget as M, isManyToMany as N, camelCase as O, Vector as P, normalizeToEntityRelation as S, legacyForeignKeyName as T, buildCompositeId as _, getJunctionSecurityRules as a, createRelationRef as b, policyToPostgres as c, findRelation as d, getColumnName as f, resolveCollectionRelations as g, getTableVarName as h, getJunctionCollectionConfig as i, DEFAULT_ONE_OF_VALUE as j, toSnakeCase as k, securityRuleToConditions as l, getTableName as m, CollectionRegistry as n, resolveJunctionSpecs as o, getEnumVarName as p, resolveStringColumnLength as r, getEffectiveSecurityRules as s, buildSdkData as t, findAnonymousGrants as u, getDeclaredPrimaryKeys as v, generateForeignKeyName as w, createRelationRefWithData as x, parseIdValues as y };
|
|
3993
4270
|
|
|
3994
|
-
//# sourceMappingURL=src-
|
|
4271
|
+
//# sourceMappingURL=src-CzbghKwf.js.map
|