@rebasepro/server-postgres 0.10.1-canary.ed8caed → 0.10.1-canary.g4db1bb1

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.
Files changed (42) hide show
  1. package/dist/auth/schema-version.d.ts +106 -0
  2. package/dist/collections/validate-relations.d.ts +53 -0
  3. package/dist/data-transformer.d.ts +3 -3
  4. package/dist/{ensure-collection-tables-BVvtkkRm.js → ensure-collection-tables-DGMYK0fr.js} +5 -5
  5. package/dist/ensure-collection-tables-DGMYK0fr.js.map +1 -0
  6. package/dist/index.es.js +610 -313
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/services/RelationService.d.ts +9 -8
  9. package/dist/services/cdc/junction-tables.d.ts +38 -0
  10. package/dist/services/nested-path.d.ts +3 -3
  11. package/dist/services/realtimeService.d.ts +19 -0
  12. package/dist/services/row-pipeline.d.ts +2 -2
  13. package/dist/{src-lcfUP4xg.js → src-3VmUJ8Xn.js} +135 -247
  14. package/dist/src-3VmUJ8Xn.js.map +1 -0
  15. package/dist/{src-CBgtrPhJ.js → src-D5xBTl32.js} +12 -2
  16. package/dist/src-D5xBTl32.js.map +1 -0
  17. package/dist/utils/drizzle-conditions.d.ts +35 -23
  18. package/package.json +6 -6
  19. package/src/PostgresBootstrapper.ts +15 -2
  20. package/src/auth/ensure-tables.ts +23 -0
  21. package/src/auth/schema-version.ts +260 -0
  22. package/src/cli-errors.ts +1 -1
  23. package/src/cli-helpers.ts +4 -3
  24. package/src/collections/PostgresCollectionRegistry.ts +9 -4
  25. package/src/collections/buildRegistry.ts +7 -0
  26. package/src/collections/validate-relations.ts +280 -0
  27. package/src/data-transformer.ts +28 -38
  28. package/src/schema/doctor.ts +8 -8
  29. package/src/schema/generate-drizzle-schema-logic.ts +49 -101
  30. package/src/schema/generate-postgres-ddl-logic.ts +9 -9
  31. package/src/schema/introspect-db-logic.ts +19 -23
  32. package/src/services/FetchService.ts +12 -12
  33. package/src/services/PersistService.ts +23 -27
  34. package/src/services/RelationService.ts +65 -83
  35. package/src/services/cdc/junction-tables.ts +91 -0
  36. package/src/services/nested-path.ts +24 -9
  37. package/src/services/realtimeService.ts +60 -0
  38. package/src/services/row-pipeline.ts +5 -6
  39. package/src/utils/drizzle-conditions.ts +177 -382
  40. package/dist/ensure-collection-tables-BVvtkkRm.js.map +0 -1
  41. package/dist/src-CBgtrPhJ.js.map +0 -1
  42. package/dist/src-lcfUP4xg.js.map +0 -1
@@ -2,7 +2,7 @@ import { createRequire as __createRequire } from "module";
2
2
  import "process";
3
3
  __createRequire(import.meta.url);
4
4
  import { r as __require, t as __commonJSMin } from "./chunk-DSJWtz9O.js";
5
- import { c as getDeclaredSubcollections, d as REST_TO_CANONICAL, f as toCanonicalOp, l as isPostgresCollectionConfig, n as getDataSourceCapabilities, o as ANONYMOUS_USER_ID, p as EntityRelation, s as policy, u as NULL_OPS } from "./src-CBgtrPhJ.js";
5
+ import { d as isPostgresCollectionConfig, f as NULL_OPS, h as EntityRelation, l as isManyToMany, m as toCanonicalOp, n as getDataSourceCapabilities, o as ANONYMOUS_USER_ID, p as REST_TO_CANONICAL, s as policy, u as getDeclaredSubcollections } from "./src-D5xBTl32.js";
6
6
  //#region ../common/src/util/common.ts
7
7
  var DEFAULT_ONE_OF_TYPE = "type";
8
8
  var DEFAULT_ONE_OF_VALUE = "value";
@@ -1405,164 +1405,143 @@ function enumToObjectEntries(enumValues) {
1405
1405
  });
1406
1406
  }
1407
1407
  //#endregion
1408
- //#region ../common/src/util/relations.ts
1409
- function sanitizeRelation(relation, sourceCollection, resolveCollection) {
1410
- if (!relation.target) throw new Error("Relation is missing a `target` collection.");
1411
- const rawTarget = relation.target;
1412
- let targetCollection;
1413
- if (typeof rawTarget === "string") {
1414
- if (resolveCollection) targetCollection = resolveCollection(rawTarget);
1415
- if (!targetCollection) targetCollection = {
1416
- slug: rawTarget,
1417
- name: rawTarget
1418
- };
1419
- } else if (typeof rawTarget === "function") {
1420
- const evaluated = rawTarget();
1421
- if (typeof evaluated === "string") {
1422
- if (resolveCollection) targetCollection = resolveCollection(evaluated);
1423
- if (!targetCollection) targetCollection = {
1424
- slug: evaluated,
1425
- name: evaluated
1426
- };
1427
- } else targetCollection = evaluated;
1428
- } else if (rawTarget && typeof rawTarget === "object") targetCollection = rawTarget;
1429
- if (!targetCollection) throw new Error("Relation is missing a valid `target` collection.");
1430
- const newRelation = { ...relation };
1431
- newRelation.target = () => {
1432
- if (typeof rawTarget === "string") return resolveCollection && resolveCollection(rawTarget) || targetCollection;
1433
- else if (typeof rawTarget === "function") {
1434
- const evaluated = rawTarget();
1435
- if (typeof evaluated === "string") return resolveCollection && resolveCollection(evaluated) || targetCollection;
1436
- return evaluated;
1437
- }
1438
- return targetCollection;
1408
+ //#region ../common/src/util/resolve-relation.ts
1409
+ /**
1410
+ * Fill in a relation's defaults.
1411
+ *
1412
+ * This replaces `sanitizeRelation`, which had to work out *which kind of link
1413
+ * you meant* from whichever optional fields happened to be set — 194 lines of
1414
+ * it, including a pass that inspected the target collection's own relations to
1415
+ * decide whether a `many`/`inverse` pair was a one-to-many or the far side of a
1416
+ * many-to-many, wrapped in a `try/catch` that fell through to the wrong answer
1417
+ * when it could not tell. Two consumers running that logic at different moments
1418
+ * could reach different conclusions about the same relation.
1419
+ *
1420
+ * With the kind declared there is nothing to work out. What remains is
1421
+ * defaulting — a table name, a column name — which is deterministic, depends
1422
+ * only on the relation and its two endpoints, and cannot fail. That is why this
1423
+ * function returns rather than throws, and why it needs no cache to be
1424
+ * consistent.
1425
+ */
1426
+ function resolveRelation(relation, sourceCollection, propertyKey) {
1427
+ const target = relation.target;
1428
+ 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.`);
1431
+ const relationName = relation.relationName ?? propertyKey ?? toSnakeCase(targetCollection.slug);
1432
+ const shared = {
1433
+ relationName,
1434
+ target,
1435
+ targetSlug: targetCollection.slug,
1436
+ onUpdate: relation.onUpdate,
1437
+ onDelete: relation.onDelete,
1438
+ overrides: relation.overrides,
1439
+ validation: relation.validation
1439
1440
  };
1440
- if (!newRelation.relationName) newRelation.relationName = toSnakeCase(targetCollection.slug);
1441
- if (!newRelation.direction) if (newRelation.foreignKeyOnTarget) newRelation.direction = "inverse";
1442
- else if (newRelation.through) newRelation.direction = "owning";
1443
- else if (newRelation.cardinality === "many") newRelation.direction = "inverse";
1444
- else newRelation.direction = "owning";
1445
- if (!newRelation.joinPath) {
1446
- const sourceName = toSnakeCase(sourceCollection.slug ?? sourceCollection.name);
1447
- if (newRelation.cardinality === "one" && newRelation.direction === "owning") {
1448
- if (!newRelation.localKey) newRelation.localKey = generateForeignKeyName(newRelation.relationName);
1449
- } else if (newRelation.cardinality === "one" && newRelation.direction === "inverse") {
1450
- if (!newRelation.foreignKeyOnTarget) {
1451
- let foundForeignKey = false;
1452
- try {
1453
- const targetRelations = getDataSourceCapabilities(targetCollection.engine).supportsRelations ? targetCollection.relations || [] : [];
1454
- for (const targetRel of targetRelations) if (targetRel.direction === "owning" && targetRel.cardinality === "one" && targetRel.localKey) try {
1455
- if (targetRel.target().slug === sourceCollection.slug) {
1456
- newRelation.foreignKeyOnTarget = targetRel.localKey;
1457
- foundForeignKey = true;
1458
- break;
1459
- }
1460
- } catch (e) {
1461
- continue;
1462
- }
1463
- } catch (e) {}
1464
- if (!foundForeignKey) newRelation.foreignKeyOnTarget = generateForeignKeyName(newRelation.inverseRelationName ? toSnakeCase(newRelation.inverseRelationName) : sourceName);
1465
- }
1466
- } else if (newRelation.cardinality === "many" && newRelation.direction === "inverse") {
1467
- let isManyToManyInverse = false;
1468
- if (newRelation.inverseRelationName && !newRelation.foreignKeyOnTarget) try {
1469
- const targetRelations = getDataSourceCapabilities(targetCollection.engine).supportsRelations ? targetCollection.relations || [] : [];
1470
- for (const targetRel of targetRelations) if (targetRel.cardinality === "many" && (targetRel.direction === "owning" || !targetRel.direction) && targetRel.relationName === newRelation.inverseRelationName) {
1471
- isManyToManyInverse = true;
1472
- break;
1473
- }
1474
- if (!isManyToManyInverse && targetCollection.properties) for (const [propKey, prop] of Object.entries(targetCollection.properties)) {
1475
- if (prop.type !== "relation") continue;
1476
- const relProp = prop;
1477
- if ((relProp.relationName || propKey) === newRelation.inverseRelationName && relProp.cardinality === "many" && (relProp.direction === "owning" || !relProp.direction)) {
1478
- isManyToManyInverse = true;
1479
- break;
1480
- }
1441
+ const sourceName = toSnakeCase(sourceCollection.slug ?? sourceCollection.name);
1442
+ switch (relation.kind) {
1443
+ case "belongsTo": return {
1444
+ ...shared,
1445
+ kind: "belongsTo",
1446
+ cardinality: "one",
1447
+ writable: true,
1448
+ shared: false,
1449
+ localKey: relation.localKey ?? generateForeignKeyName(relationName)
1450
+ };
1451
+ case "hasOne": return {
1452
+ ...shared,
1453
+ kind: "hasOne",
1454
+ cardinality: "one",
1455
+ writable: true,
1456
+ shared: false,
1457
+ foreignKeyOnTarget: relation.foreignKeyOnTarget ?? generateForeignKeyName(sourceName)
1458
+ };
1459
+ case "hasMany": return {
1460
+ ...shared,
1461
+ kind: "hasMany",
1462
+ cardinality: "many",
1463
+ writable: true,
1464
+ shared: false,
1465
+ foreignKeyOnTarget: relation.foreignKeyOnTarget ?? generateForeignKeyName(sourceName)
1466
+ };
1467
+ case "manyToMany": {
1468
+ const sourceTable = getTableName(sourceCollection);
1469
+ const targetTable = getTableName(targetCollection);
1470
+ return {
1471
+ ...shared,
1472
+ kind: "manyToMany",
1473
+ cardinality: "many",
1474
+ writable: true,
1475
+ shared: true,
1476
+ through: {
1477
+ table: relation.through?.table ?? [sourceTable, targetTable].sort().join("_"),
1478
+ sourceColumn: relation.through?.sourceColumn ?? generateForeignKeyName(sourceName),
1479
+ targetColumn: relation.through?.targetColumn ?? generateForeignKeyName(relationName)
1481
1480
  }
1482
- } catch (e) {}
1483
- if (!isManyToManyInverse && !newRelation.foreignKeyOnTarget) newRelation.foreignKeyOnTarget = generateForeignKeyName(sourceName);
1484
- } else if (newRelation.cardinality === "many" && newRelation.direction === "owning") {
1485
- const sourceTableName = getTableName(sourceCollection);
1486
- const targetTableName = getTableName(targetCollection);
1487
- newRelation.through = {
1488
- table: newRelation.through?.table ?? [sourceTableName, targetTableName].sort().join("_"),
1489
- sourceColumn: newRelation.through?.sourceColumn ?? generateForeignKeyName(sourceName),
1490
- targetColumn: newRelation.through?.targetColumn ?? generateForeignKeyName(newRelation.relationName)
1491
1481
  };
1492
1482
  }
1483
+ case "via": return {
1484
+ ...shared,
1485
+ kind: "via",
1486
+ cardinality: relation.cardinality,
1487
+ writable: false,
1488
+ shared: true,
1489
+ joinPath: relation.joinPath
1490
+ };
1491
+ default: throw new Error(`Unknown relation kind: ${JSON.stringify(relation)}`);
1493
1492
  }
1494
- if (newRelation.cardinality === "one" && newRelation.direction === "owning" && !newRelation.localKey && !newRelation.joinPath) throw new Error(`Configuration Error in relation from '${sourceCollection.name}': An 'owning' one-to-one relation requires a 'localKey'. Check the relation config for '${newRelation.relationName}'`);
1495
- if (newRelation.cardinality === "one" && newRelation.direction === "inverse" && !newRelation.foreignKeyOnTarget && !newRelation.joinPath) throw new Error(`Configuration Error in relation from '${sourceCollection.name}': An 'inverse' one-to-one relation requires a 'foreignKeyOnTarget'. Check the relation config for '${newRelation.relationName}'`);
1496
- if (newRelation.cardinality === "many" && newRelation.direction === "inverse" && !newRelation.foreignKeyOnTarget && !newRelation.joinPath && !newRelation.inverseRelationName) throw new Error(`Configuration Error in relation from '${sourceCollection.name}': An 'inverse' one-to-many relation requires a 'foreignKeyOnTarget'. Check the relation config for '${newRelation.relationName}'`);
1497
- return newRelation;
1498
1493
  }
1494
+ //#endregion
1495
+ //#region ../common/src/util/relations.ts
1499
1496
  /**
1500
- * Whether the target rows of this relation are reached through a junction — a
1501
- * many-to-many, or a multi-hop `joinPath`.
1497
+ * Whether the target rows are shared with other parents — a many-to-many, or a
1498
+ * multi-hop `via` chain.
1502
1499
  *
1503
- * The distinction decides what a write "through" the relation may touch: a
1504
- * junction-backed target is shared with other parents, so the parent owns the
1505
- * *link* and not the row. The backend enforces that (an unlink rather than a
1506
- * delete) and the admin renders it (remove-from-parent rather than delete), so
1507
- * the predicate lives here rather than once per side.
1500
+ * Decides what a write "through" the relation may touch: a shared target
1501
+ * belongs to every parent that links it, so the parent owns the *link* and not
1502
+ * the row. The backend enforces that (an unlink rather than a delete) and the
1503
+ * admin renders it (remove-from-parent rather than delete).
1504
+ *
1505
+ * Now a field on the resolved relation rather than a re-derivation, so both
1506
+ * sides read the same answer instead of each computing one.
1508
1507
  */
1509
1508
  function isJunctionBackedRelation(relation) {
1510
- return Boolean(relation.through) || Boolean(relation.joinPath && relation.joinPath.length > 1);
1509
+ return relation.shared;
1511
1510
  }
1512
1511
  /** WeakMap cache — same collection instance always yields the same relation map. */
1513
1512
  var _resolvedRelationsCache = /* @__PURE__ */ new WeakMap();
1513
+ /**
1514
+ * Every relation a collection declares, keyed by the name it is addressed by.
1515
+ *
1516
+ * A relation reaches the map from either of two places — the collection's
1517
+ * `relations` array, or a `relation` property that declares one inline — and is
1518
+ * keyed by its resolved `relationName`, which is what a nested path segment,
1519
+ * an `include` key and an admin tab all match against.
1520
+ *
1521
+ * Resolution no longer swallows failures. It used to wrap each relation in a
1522
+ * `try/catch` that dropped anything it could not work out, so a
1523
+ * mis-declared relation silently vanished instead of being reported; with the
1524
+ * kind declared, the only remaining failure is a `target` that does not resolve,
1525
+ * which is worth hearing about.
1526
+ */
1514
1527
  function resolveCollectionRelations(collection) {
1515
1528
  const cached = _resolvedRelationsCache.get(collection);
1516
1529
  if (cached) return cached;
1517
1530
  if (!getDataSourceCapabilities(collection.engine).supportsRelations) return {};
1518
1531
  const relations = {};
1519
- const registeredRelationNames = /* @__PURE__ */ new Set();
1520
- if (collection.relations) collection.relations.forEach((relation) => {
1521
- try {
1522
- const normalizedRelation = sanitizeRelation(relation, collection);
1523
- const relationKey = normalizedRelation.relationName;
1524
- if (relationKey) {
1525
- relations[relationKey] = normalizedRelation;
1526
- registeredRelationNames.add(relationKey);
1527
- }
1528
- } catch (e) {}
1529
- });
1530
- if (collection.properties) Object.entries(collection.properties).forEach(([propKey, prop]) => {
1531
- const relation = resolvePropertyRelation({
1532
- propertyKey: propKey,
1533
- property: prop,
1534
- sourceCollection: collection
1535
- });
1536
- if (relation) {
1537
- if (relations[propKey]) return;
1538
- if (!relation.relationName) relation.relationName = propKey;
1539
- const normalizedRelation = sanitizeRelation(relation, collection);
1540
- relations[propKey] = normalizedRelation;
1541
- registeredRelationNames.add(normalizedRelation.relationName ?? propKey);
1542
- }
1543
- });
1532
+ for (const relation of collection.relations ?? []) {
1533
+ const resolved = resolveRelation(relation, collection);
1534
+ relations[resolved.relationName] = resolved;
1535
+ }
1536
+ for (const [propertyKey, property] of Object.entries(collection.properties ?? {})) {
1537
+ if (property?.type !== "relation") continue;
1538
+ const declared = property.relation;
1539
+ if (!declared || relations[propertyKey]) continue;
1540
+ relations[propertyKey] = resolveRelation(declared, collection, propertyKey);
1541
+ }
1544
1542
  _resolvedRelationsCache.set(collection, relations);
1545
1543
  return relations;
1546
1544
  }
1547
- function resolvePropertyRelation({ propertyKey, property, sourceCollection }) {
1548
- if (property.type !== "relation") return void 0;
1549
- const relProp = property;
1550
- if (relProp.target) return {
1551
- relationName: relProp.relationName || propertyKey,
1552
- target: relProp.target,
1553
- cardinality: relProp.cardinality || "one",
1554
- direction: relProp.direction || "owning",
1555
- inverseRelationName: relProp.inverseRelationName,
1556
- localKey: relProp.localKey,
1557
- foreignKeyOnTarget: relProp.foreignKeyOnTarget,
1558
- through: relProp.through,
1559
- joinPath: relProp.joinPath,
1560
- onUpdate: relProp.onUpdate,
1561
- onDelete: relProp.onDelete,
1562
- overrides: relProp.overrides
1563
- };
1564
- console.warn(`Unrecognized or missing relation target for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
1565
- }
1566
1545
  function getTableName(collection) {
1567
1546
  if (getDataSourceCapabilities(collection.engine).supportsRelations) return collection.table ?? toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
1568
1547
  return toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
@@ -1595,40 +1574,6 @@ function findRelation(resolvedRelations, key) {
1595
1574
  //#endregion
1596
1575
  //#region ../common/src/util/resolutions.ts
1597
1576
  /**
1598
- * Relation identities that only ever appear *below* the top level of
1599
- * `properties` — inside a `map`, an `array`, or a `oneOf` branch.
1600
- *
1601
- * A relation declared at the top level is a list hanging off the record and
1602
- * earns a tab. One declared inside a map is a field of that map. Both end up in
1603
- * the collection's flat `relations` array after normalization, so the shape of
1604
- * `properties` is the only remaining evidence of which is which.
1605
- *
1606
- * An identity declared in both places is *not* nested-only, and keeps its tab.
1607
- */
1608
- function getNestedRelationIdentities(properties) {
1609
- const topLevel = /* @__PURE__ */ new Set();
1610
- const nested = /* @__PURE__ */ new Set();
1611
- const walk = (props, depth) => {
1612
- if (!props) return;
1613
- for (const [key, property] of Object.entries(props)) {
1614
- if (!property) continue;
1615
- if (property.type === "relation") {
1616
- const relProp = property;
1617
- (depth === 0 ? topLevel : nested).add(relProp.relationName ?? key);
1618
- } else if (property.type === "map") walk(property.properties, depth + 1);
1619
- else if (property.type === "array") {
1620
- const arrayProp = property;
1621
- if (Array.isArray(arrayProp.of)) arrayProp.of.forEach((p) => walk({ entry: p }, depth + 1));
1622
- else if (arrayProp.of) walk({ entry: arrayProp.of }, depth + 1);
1623
- if (arrayProp.oneOf?.properties) walk(arrayProp.oneOf.properties, depth + 1);
1624
- }
1625
- }
1626
- };
1627
- walk(properties, 0);
1628
- for (const identity of topLevel) nested.delete(identity);
1629
- return nested;
1630
- }
1631
- /**
1632
1577
  * The lists rendered inside an entity view of `collection` — its tabs.
1633
1578
  *
1634
1579
  * The single derivation. There used to be two that disagreed: this one, and a
@@ -1655,14 +1600,12 @@ function getEntityChildViews(collection) {
1655
1600
  if (capabilities.supportsSubcollections && declaredSubcollections) return asSubcollections(declaredSubcollections() ?? []);
1656
1601
  if (!capabilities.supportsRelations) return [];
1657
1602
  const resolvedRelations = resolveCollectionRelations(collection);
1658
- const nestedOnly = getNestedRelationIdentities(collection.properties);
1659
1603
  const views = [];
1660
1604
  const seen = /* @__PURE__ */ new Set();
1661
1605
  for (const [relationKey, relation] of Object.entries(resolvedRelations)) {
1662
1606
  if (relation.cardinality !== "many") continue;
1663
1607
  const identity = relation.relationName ?? relationKey;
1664
1608
  if (seen.has(identity)) continue;
1665
- if (nestedOnly.has(identity)) continue;
1666
1609
  let target;
1667
1610
  try {
1668
1611
  target = relation.target();
@@ -1671,7 +1614,7 @@ function getEntityChildViews(collection) {
1671
1614
  }
1672
1615
  if (!target) continue;
1673
1616
  seen.add(identity);
1674
- const customName = Object.entries(collection.properties ?? {}).find(([propKey, p]) => p.type === "relation" && (p.relationName ?? propKey) === identity)?.[1]?.name;
1617
+ const customName = Object.entries(collection.properties ?? {}).find(([propKey, p]) => p.type === "relation" && (p.relation?.relationName ?? propKey) === identity)?.[1]?.name;
1675
1618
  const base = {
1676
1619
  ...target,
1677
1620
  slug: relationKey,
@@ -2254,8 +2197,8 @@ function resolveJunctionSpecs(collections) {
2254
2197
  for (const collection of collections) {
2255
2198
  const resolved = resolveCollectionRelations(collection);
2256
2199
  for (const relation of Object.values(resolved)) {
2257
- if (!relation.through) continue;
2258
- const targetCollection = typeof relation.target === "function" ? relation.target() : void 0;
2200
+ if (!isManyToMany(relation)) continue;
2201
+ const targetCollection = relation.target();
2259
2202
  if (!targetCollection) continue;
2260
2203
  const rawName = relation.through.table;
2261
2204
  const table = rawName.includes(".") ? rawName.split(".").pop() : rawName;
@@ -3297,88 +3240,33 @@ var CollectionRegistry = class {
3297
3240
  if (!result.dataSource) result.dataSource = resolved.key;
3298
3241
  if (!result.engine) result.engine = resolved.engine;
3299
3242
  }
3300
- const extractedRelations = this.extractRelationsFromProperties(result.properties);
3301
- const relResult = result;
3302
- const manualRelations = getDataSourceCapabilities(result.engine).supportsRelations ? relResult.relations ?? [] : [];
3303
- const mergedRelationsRaw = [...extractedRelations];
3304
- for (const manual of manualRelations) {
3305
- const name = manual.relationName;
3306
- if (!name) mergedRelationsRaw.push(manual);
3307
- else {
3308
- const existingIndex = mergedRelationsRaw.findIndex((r) => r.relationName === name);
3309
- if (existingIndex === -1) mergedRelationsRaw.push(manual);
3310
- else mergedRelationsRaw[existingIndex] = {
3311
- ...manual,
3312
- ...mergedRelationsRaw[existingIndex]
3313
- };
3314
- }
3315
- }
3316
- let mergedRelations = mergedRelationsRaw;
3317
- if (getDataSourceCapabilities(result.engine).supportsRelations) {
3318
- mergedRelations = mergedRelationsRaw.map((r) => {
3319
- try {
3320
- return sanitizeRelation(r, result, (slug) => this.get(slug));
3321
- } catch {
3322
- return r;
3323
- }
3324
- });
3325
- relResult.relations = mergedRelations;
3326
- }
3327
- result.properties = this.normalizeProperties(result.properties, mergedRelations);
3243
+ result.properties = this.normalizeProperties(result.properties, result);
3328
3244
  return result;
3329
3245
  }
3330
- /**
3331
- * Extract Relation[] from properties that have inline relation config (i.e. `target` is set).
3332
- * This allows developers to define relations directly on properties without a separate
3333
- * `relations[]` entry on the collection.
3334
- */
3335
- extractRelationsFromProperties(properties) {
3336
- const relations = [];
3337
- for (const [key, property] of Object.entries(properties)) if (property.type === "relation") {
3338
- const relProp = property;
3339
- const target = relProp.target ?? relProp.relation?.target;
3340
- if (target) {
3341
- const relationName = relProp.relationName ?? relProp.relation?.relationName ?? key;
3342
- relations.push({
3343
- relationName,
3344
- target,
3345
- cardinality: relProp.cardinality ?? relProp.relation?.cardinality ?? "one",
3346
- direction: relProp.direction ?? relProp.relation?.direction ?? "owning",
3347
- inverseRelationName: relProp.inverseRelationName ?? relProp.relation?.inverseRelationName,
3348
- localKey: relProp.localKey ?? relProp.relation?.localKey,
3349
- foreignKeyOnTarget: relProp.foreignKeyOnTarget ?? relProp.relation?.foreignKeyOnTarget,
3350
- through: relProp.through ?? relProp.relation?.through,
3351
- joinPath: relProp.joinPath ?? relProp.relation?.joinPath,
3352
- onUpdate: relProp.onUpdate ?? relProp.relation?.onUpdate,
3353
- onDelete: relProp.onDelete ?? relProp.relation?.onDelete,
3354
- overrides: relProp.overrides ?? relProp.relation?.overrides
3355
- });
3356
- }
3357
- } else if (property.type === "map" && property.properties) relations.push(...this.extractRelationsFromProperties(property.properties));
3358
- return relations;
3359
- }
3360
- normalizeProperties(properties, relations) {
3246
+ normalizeProperties(properties, collection) {
3361
3247
  const newProperties = {};
3362
- for (const key in properties) newProperties[key] = this.normalizeProperty(key, properties[key], relations);
3248
+ for (const key in properties) newProperties[key] = this.normalizeProperty(key, properties[key], collection);
3363
3249
  return newProperties;
3364
3250
  }
3365
- normalizeProperty(key, property, relations) {
3251
+ normalizeProperty(key, property, collection) {
3366
3252
  const newProperty = { ...property };
3367
- if (newProperty.type === "map" && newProperty.properties) newProperty.properties = this.normalizeProperties(newProperty.properties, relations);
3253
+ if (newProperty.type === "map" && newProperty.properties) newProperty.properties = this.normalizeProperties(newProperty.properties, collection);
3368
3254
  else if (newProperty.type === "array") {
3369
3255
  const arrayProp = newProperty;
3370
- if (arrayProp.of) if (Array.isArray(arrayProp.of)) arrayProp.of = arrayProp.of.map((p, i) => this.normalizeProperty(`${key}[${i}]`, p, relations));
3371
- else arrayProp.of = this.normalizeProperty(`${key}.of`, arrayProp.of, relations);
3372
- else if (arrayProp.oneOf && arrayProp.oneOf.properties) arrayProp.oneOf.properties = this.normalizeProperties(arrayProp.oneOf.properties, relations);
3256
+ if (arrayProp.of) if (Array.isArray(arrayProp.of)) arrayProp.of = arrayProp.of.map((p, i) => this.normalizeProperty(`${key}[${i}]`, p, collection));
3257
+ else arrayProp.of = this.normalizeProperty(`${key}.of`, arrayProp.of, collection);
3258
+ else if (arrayProp.oneOf && arrayProp.oneOf.properties) arrayProp.oneOf.properties = this.normalizeProperties(arrayProp.oneOf.properties, collection);
3373
3259
  } else if ((newProperty.type === "string" || newProperty.type === "number") && newProperty.enum) {
3374
3260
  const stringOrNumberProperty = newProperty;
3375
3261
  if (typeof stringOrNumberProperty.enum === "object" && !Array.isArray(stringOrNumberProperty.enum)) stringOrNumberProperty.enum = enumToObjectEntries(stringOrNumberProperty.enum)?.filter((value) => value && (value.id || value.id === 0) && value.label) ?? [];
3376
3262
  } else if (newProperty.type === "relation") {
3377
3263
  const relationProperty = newProperty;
3378
- const name = relationProperty.relationName || key;
3379
- const relation = relations.find((r) => r.relationName === name);
3380
- if (relation) relationProperty.relation = relation;
3381
- else console.warn(`Could not find relation for property '${key}' with relationName: ${name}`);
3264
+ if (relationProperty.relation) relationProperty.resolvedRelation = resolveRelation(relationProperty.relation, collection, key);
3265
+ else {
3266
+ const declared = resolveCollectionRelations(collection)[key];
3267
+ if (declared) relationProperty.resolvedRelation = declared;
3268
+ else console.warn(`Relation property '${key}' on '${collection.slug}' declares no \`relation\`, and the collection has no relation of that name.`);
3269
+ }
3382
3270
  }
3383
3271
  return newProperty;
3384
3272
  }
@@ -4103,4 +3991,4 @@ async function detectJunctionTables(executeSql) {
4103
3991
  //#endregion
4104
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 };
4105
3993
 
4106
- //# sourceMappingURL=src-lcfUP4xg.js.map
3994
+ //# sourceMappingURL=src-3VmUJ8Xn.js.map