@rebasepro/common 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.
@@ -51,12 +51,6 @@ export declare class CollectionRegistry {
51
51
  register(collection: CollectionConfig, rawCollection?: CollectionConfig): void;
52
52
  private _registerRecursively;
53
53
  normalizeCollection(collection: CollectionConfig): CollectionConfig;
54
- /**
55
- * Extract Relation[] from properties that have inline relation config (i.e. `target` is set).
56
- * This allows developers to define relations directly on properties without a separate
57
- * `relations[]` entry on the collection.
58
- */
59
- private extractRelationsFromProperties;
60
54
  private normalizeProperties;
61
55
  private normalizeProperty;
62
56
  get(path: string): CollectionConfig | undefined;
package/dist/index.es.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ANONYMOUS_USER_ID, CANONICAL_TO_REST, DEFAULT_DATA_SOURCE_KEY, EntityReference, EntityRelation, NULL_OPS, REST_TO_CANONICAL, getDataSourceCapabilities, getDeclaredSubcollections, isPostgresCollectionConfig, policy, toCanonicalOp } from "@rebasepro/types";
1
+ import { ANONYMOUS_USER_ID, CANONICAL_TO_REST, DEFAULT_DATA_SOURCE_KEY, EntityReference, EntityRelation, NULL_OPS, REST_TO_CANONICAL, getDataSourceCapabilities, getDeclaredSubcollections, isManyToMany, isPostgresCollectionConfig, policy, toCanonicalOp } from "@rebasepro/types";
2
2
  import { deepClone, generateForeignKeyName, getIn, getPolicyOperations, isDefaultFieldConfigId, mergeDeep, randomString, removeFunctions, toSnakeCase } from "@rebasepro/utils";
3
3
  import jsonLogic from "json-logic-js";
4
4
  import { deepEqual } from "fast-equals";
@@ -372,164 +372,143 @@ function fullPathToCollectionSegments(path) {
372
372
  return path.split("/").filter((e, i) => i % 2 === 0);
373
373
  }
374
374
  //#endregion
375
- //#region src/util/relations.ts
376
- function sanitizeRelation(relation, sourceCollection, resolveCollection) {
377
- if (!relation.target) throw new Error("Relation is missing a `target` collection.");
378
- const rawTarget = relation.target;
379
- let targetCollection;
380
- if (typeof rawTarget === "string") {
381
- if (resolveCollection) targetCollection = resolveCollection(rawTarget);
382
- if (!targetCollection) targetCollection = {
383
- slug: rawTarget,
384
- name: rawTarget
385
- };
386
- } else if (typeof rawTarget === "function") {
387
- const evaluated = rawTarget();
388
- if (typeof evaluated === "string") {
389
- if (resolveCollection) targetCollection = resolveCollection(evaluated);
390
- if (!targetCollection) targetCollection = {
391
- slug: evaluated,
392
- name: evaluated
393
- };
394
- } else targetCollection = evaluated;
395
- } else if (rawTarget && typeof rawTarget === "object") targetCollection = rawTarget;
396
- if (!targetCollection) throw new Error("Relation is missing a valid `target` collection.");
397
- const newRelation = { ...relation };
398
- newRelation.target = () => {
399
- if (typeof rawTarget === "string") return resolveCollection && resolveCollection(rawTarget) || targetCollection;
400
- else if (typeof rawTarget === "function") {
401
- const evaluated = rawTarget();
402
- if (typeof evaluated === "string") return resolveCollection && resolveCollection(evaluated) || targetCollection;
403
- return evaluated;
404
- }
405
- return targetCollection;
375
+ //#region src/util/resolve-relation.ts
376
+ /**
377
+ * Fill in a relation's defaults.
378
+ *
379
+ * This replaces `sanitizeRelation`, which had to work out *which kind of link
380
+ * you meant* from whichever optional fields happened to be set — 194 lines of
381
+ * it, including a pass that inspected the target collection's own relations to
382
+ * decide whether a `many`/`inverse` pair was a one-to-many or the far side of a
383
+ * many-to-many, wrapped in a `try/catch` that fell through to the wrong answer
384
+ * when it could not tell. Two consumers running that logic at different moments
385
+ * could reach different conclusions about the same relation.
386
+ *
387
+ * With the kind declared there is nothing to work out. What remains is
388
+ * defaulting — a table name, a column name — which is deterministic, depends
389
+ * only on the relation and its two endpoints, and cannot fail. That is why this
390
+ * function returns rather than throws, and why it needs no cache to be
391
+ * consistent.
392
+ */
393
+ function resolveRelation(relation, sourceCollection, propertyKey) {
394
+ const target = relation.target;
395
+ if (typeof target !== "function") throw new Error(`Relation${relation.relationName ? ` '${relation.relationName}'` : ""} on '${sourceCollection.slug}' has no \`target\`. Give it a thunk: \`target: () => otherCollection\`.`);
396
+ const targetCollection = target();
397
+ if (!targetCollection?.slug) throw new Error(`Relation${relation.relationName ? ` '${relation.relationName}'` : ""} on '${sourceCollection.slug}' has a \`target\` that did not resolve to a collection.`);
398
+ const relationName = relation.relationName ?? propertyKey ?? toSnakeCase(targetCollection.slug);
399
+ const shared = {
400
+ relationName,
401
+ target,
402
+ targetSlug: targetCollection.slug,
403
+ onUpdate: relation.onUpdate,
404
+ onDelete: relation.onDelete,
405
+ overrides: relation.overrides,
406
+ validation: relation.validation
406
407
  };
407
- if (!newRelation.relationName) newRelation.relationName = toSnakeCase(targetCollection.slug);
408
- if (!newRelation.direction) if (newRelation.foreignKeyOnTarget) newRelation.direction = "inverse";
409
- else if (newRelation.through) newRelation.direction = "owning";
410
- else if (newRelation.cardinality === "many") newRelation.direction = "inverse";
411
- else newRelation.direction = "owning";
412
- if (!newRelation.joinPath) {
413
- const sourceName = toSnakeCase(sourceCollection.slug ?? sourceCollection.name);
414
- if (newRelation.cardinality === "one" && newRelation.direction === "owning") {
415
- if (!newRelation.localKey) newRelation.localKey = generateForeignKeyName(newRelation.relationName);
416
- } else if (newRelation.cardinality === "one" && newRelation.direction === "inverse") {
417
- if (!newRelation.foreignKeyOnTarget) {
418
- let foundForeignKey = false;
419
- try {
420
- const targetRelations = getDataSourceCapabilities(targetCollection.engine).supportsRelations ? targetCollection.relations || [] : [];
421
- for (const targetRel of targetRelations) if (targetRel.direction === "owning" && targetRel.cardinality === "one" && targetRel.localKey) try {
422
- if (targetRel.target().slug === sourceCollection.slug) {
423
- newRelation.foreignKeyOnTarget = targetRel.localKey;
424
- foundForeignKey = true;
425
- break;
426
- }
427
- } catch (e) {
428
- continue;
429
- }
430
- } catch (e) {}
431
- if (!foundForeignKey) newRelation.foreignKeyOnTarget = generateForeignKeyName(newRelation.inverseRelationName ? toSnakeCase(newRelation.inverseRelationName) : sourceName);
432
- }
433
- } else if (newRelation.cardinality === "many" && newRelation.direction === "inverse") {
434
- let isManyToManyInverse = false;
435
- if (newRelation.inverseRelationName && !newRelation.foreignKeyOnTarget) try {
436
- const targetRelations = getDataSourceCapabilities(targetCollection.engine).supportsRelations ? targetCollection.relations || [] : [];
437
- for (const targetRel of targetRelations) if (targetRel.cardinality === "many" && (targetRel.direction === "owning" || !targetRel.direction) && targetRel.relationName === newRelation.inverseRelationName) {
438
- isManyToManyInverse = true;
439
- break;
440
- }
441
- if (!isManyToManyInverse && targetCollection.properties) for (const [propKey, prop] of Object.entries(targetCollection.properties)) {
442
- if (prop.type !== "relation") continue;
443
- const relProp = prop;
444
- if ((relProp.relationName || propKey) === newRelation.inverseRelationName && relProp.cardinality === "many" && (relProp.direction === "owning" || !relProp.direction)) {
445
- isManyToManyInverse = true;
446
- break;
447
- }
408
+ const sourceName = toSnakeCase(sourceCollection.slug ?? sourceCollection.name);
409
+ switch (relation.kind) {
410
+ case "belongsTo": return {
411
+ ...shared,
412
+ kind: "belongsTo",
413
+ cardinality: "one",
414
+ writable: true,
415
+ shared: false,
416
+ localKey: relation.localKey ?? generateForeignKeyName(relationName)
417
+ };
418
+ case "hasOne": return {
419
+ ...shared,
420
+ kind: "hasOne",
421
+ cardinality: "one",
422
+ writable: true,
423
+ shared: false,
424
+ foreignKeyOnTarget: relation.foreignKeyOnTarget ?? generateForeignKeyName(sourceName)
425
+ };
426
+ case "hasMany": return {
427
+ ...shared,
428
+ kind: "hasMany",
429
+ cardinality: "many",
430
+ writable: true,
431
+ shared: false,
432
+ foreignKeyOnTarget: relation.foreignKeyOnTarget ?? generateForeignKeyName(sourceName)
433
+ };
434
+ case "manyToMany": {
435
+ const sourceTable = getTableName(sourceCollection);
436
+ const targetTable = getTableName(targetCollection);
437
+ return {
438
+ ...shared,
439
+ kind: "manyToMany",
440
+ cardinality: "many",
441
+ writable: true,
442
+ shared: true,
443
+ through: {
444
+ table: relation.through?.table ?? [sourceTable, targetTable].sort().join("_"),
445
+ sourceColumn: relation.through?.sourceColumn ?? generateForeignKeyName(sourceName),
446
+ targetColumn: relation.through?.targetColumn ?? generateForeignKeyName(relationName)
448
447
  }
449
- } catch (e) {}
450
- if (!isManyToManyInverse && !newRelation.foreignKeyOnTarget) newRelation.foreignKeyOnTarget = generateForeignKeyName(sourceName);
451
- } else if (newRelation.cardinality === "many" && newRelation.direction === "owning") {
452
- const sourceTableName = getTableName(sourceCollection);
453
- const targetTableName = getTableName(targetCollection);
454
- newRelation.through = {
455
- table: newRelation.through?.table ?? [sourceTableName, targetTableName].sort().join("_"),
456
- sourceColumn: newRelation.through?.sourceColumn ?? generateForeignKeyName(sourceName),
457
- targetColumn: newRelation.through?.targetColumn ?? generateForeignKeyName(newRelation.relationName)
458
448
  };
459
449
  }
450
+ case "via": return {
451
+ ...shared,
452
+ kind: "via",
453
+ cardinality: relation.cardinality,
454
+ writable: false,
455
+ shared: true,
456
+ joinPath: relation.joinPath
457
+ };
458
+ default: throw new Error(`Unknown relation kind: ${JSON.stringify(relation)}`);
460
459
  }
461
- 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}'`);
462
- 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}'`);
463
- 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}'`);
464
- return newRelation;
465
460
  }
461
+ //#endregion
462
+ //#region src/util/relations.ts
466
463
  /**
467
- * Whether the target rows of this relation are reached through a junction — a
468
- * many-to-many, or a multi-hop `joinPath`.
464
+ * Whether the target rows are shared with other parents — a many-to-many, or a
465
+ * multi-hop `via` chain.
466
+ *
467
+ * Decides what a write "through" the relation may touch: a shared target
468
+ * belongs to every parent that links it, so the parent owns the *link* and not
469
+ * the row. The backend enforces that (an unlink rather than a delete) and the
470
+ * admin renders it (remove-from-parent rather than delete).
469
471
  *
470
- * The distinction decides what a write "through" the relation may touch: a
471
- * junction-backed target is shared with other parents, so the parent owns the
472
- * *link* and not the row. The backend enforces that (an unlink rather than a
473
- * delete) and the admin renders it (remove-from-parent rather than delete), so
474
- * the predicate lives here rather than once per side.
472
+ * Now a field on the resolved relation rather than a re-derivation, so both
473
+ * sides read the same answer instead of each computing one.
475
474
  */
476
475
  function isJunctionBackedRelation(relation) {
477
- return Boolean(relation.through) || Boolean(relation.joinPath && relation.joinPath.length > 1);
476
+ return relation.shared;
478
477
  }
479
478
  /** WeakMap cache — same collection instance always yields the same relation map. */
480
479
  var _resolvedRelationsCache = /* @__PURE__ */ new WeakMap();
480
+ /**
481
+ * Every relation a collection declares, keyed by the name it is addressed by.
482
+ *
483
+ * A relation reaches the map from either of two places — the collection's
484
+ * `relations` array, or a `relation` property that declares one inline — and is
485
+ * keyed by its resolved `relationName`, which is what a nested path segment,
486
+ * an `include` key and an admin tab all match against.
487
+ *
488
+ * Resolution no longer swallows failures. It used to wrap each relation in a
489
+ * `try/catch` that dropped anything it could not work out, so a
490
+ * mis-declared relation silently vanished instead of being reported; with the
491
+ * kind declared, the only remaining failure is a `target` that does not resolve,
492
+ * which is worth hearing about.
493
+ */
481
494
  function resolveCollectionRelations(collection) {
482
495
  const cached = _resolvedRelationsCache.get(collection);
483
496
  if (cached) return cached;
484
497
  if (!getDataSourceCapabilities(collection.engine).supportsRelations) return {};
485
498
  const relations = {};
486
- const registeredRelationNames = /* @__PURE__ */ new Set();
487
- if (collection.relations) collection.relations.forEach((relation) => {
488
- try {
489
- const normalizedRelation = sanitizeRelation(relation, collection);
490
- const relationKey = normalizedRelation.relationName;
491
- if (relationKey) {
492
- relations[relationKey] = normalizedRelation;
493
- registeredRelationNames.add(relationKey);
494
- }
495
- } catch (e) {}
496
- });
497
- if (collection.properties) Object.entries(collection.properties).forEach(([propKey, prop]) => {
498
- const relation = resolvePropertyRelation({
499
- propertyKey: propKey,
500
- property: prop,
501
- sourceCollection: collection
502
- });
503
- if (relation) {
504
- if (relations[propKey]) return;
505
- if (!relation.relationName) relation.relationName = propKey;
506
- const normalizedRelation = sanitizeRelation(relation, collection);
507
- relations[propKey] = normalizedRelation;
508
- registeredRelationNames.add(normalizedRelation.relationName ?? propKey);
509
- }
510
- });
499
+ for (const relation of collection.relations ?? []) {
500
+ const resolved = resolveRelation(relation, collection);
501
+ relations[resolved.relationName] = resolved;
502
+ }
503
+ for (const [propertyKey, property] of Object.entries(collection.properties ?? {})) {
504
+ if (property?.type !== "relation") continue;
505
+ const declared = property.relation;
506
+ if (!declared || relations[propertyKey]) continue;
507
+ relations[propertyKey] = resolveRelation(declared, collection, propertyKey);
508
+ }
511
509
  _resolvedRelationsCache.set(collection, relations);
512
510
  return relations;
513
511
  }
514
- function resolvePropertyRelation({ propertyKey, property, sourceCollection }) {
515
- if (property.type !== "relation") return void 0;
516
- const relProp = property;
517
- if (relProp.target) return {
518
- relationName: relProp.relationName || propertyKey,
519
- target: relProp.target,
520
- cardinality: relProp.cardinality || "one",
521
- direction: relProp.direction || "owning",
522
- inverseRelationName: relProp.inverseRelationName,
523
- localKey: relProp.localKey,
524
- foreignKeyOnTarget: relProp.foreignKeyOnTarget,
525
- through: relProp.through,
526
- joinPath: relProp.joinPath,
527
- onUpdate: relProp.onUpdate,
528
- onDelete: relProp.onDelete,
529
- overrides: relProp.overrides
530
- };
531
- console.warn(`Unrecognized or missing relation target for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
532
- }
533
512
  function getTableName(collection) {
534
513
  if (getDataSourceCapabilities(collection.engine).supportsRelations) return collection.table ?? toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
535
514
  return toSnakeCase(collection.slug) ?? toSnakeCase(collection.name);
@@ -629,15 +608,21 @@ function resolveProperty(props) {
629
608
  }
630
609
  return resolvedProperty;
631
610
  }
632
- function resolveRelationProperty(property, relations, propertyKey) {
633
- if (property.relation) return property;
634
- const name = property.relationName || propertyKey;
635
- const relation = name ? relations.find((rel) => rel.relationName === name) : void 0;
636
- if (!relation) throw Error(`Relation ${name ?? "(unnamed)"} not found`);
637
- return {
638
- ...property,
639
- relation
640
- };
611
+ /**
612
+ * The resolved relation a relation property refers to.
613
+ *
614
+ * Normalization stamps `resolvedRelation` onto the property, so this is usually
615
+ * a field read. It falls back to resolving from the collection for properties
616
+ * that never went through the registry — a preview, or a form rendered straight
617
+ * from an authored config.
618
+ */
619
+ function resolveRelationProperty(property, collection, propertyKey) {
620
+ if (property.resolvedRelation) return property.resolvedRelation;
621
+ if (property.relation) return resolveRelation(property.relation, collection, propertyKey);
622
+ const name = propertyKey ?? "";
623
+ const declared = resolveCollectionRelations(collection)[name];
624
+ if (!declared) throw Error(`Relation property '${name || "(unnamed)"}' on '${collection.slug}' declares no \`relation\`, and the collection has no relation of that name.`);
625
+ return declared;
641
626
  }
642
627
  /**
643
628
  * Resolve enum aliases for a string or number property
@@ -735,40 +720,6 @@ function resolveEnumValues(input) {
735
720
  else return;
736
721
  }
737
722
  /**
738
- * Relation identities that only ever appear *below* the top level of
739
- * `properties` — inside a `map`, an `array`, or a `oneOf` branch.
740
- *
741
- * A relation declared at the top level is a list hanging off the record and
742
- * earns a tab. One declared inside a map is a field of that map. Both end up in
743
- * the collection's flat `relations` array after normalization, so the shape of
744
- * `properties` is the only remaining evidence of which is which.
745
- *
746
- * An identity declared in both places is *not* nested-only, and keeps its tab.
747
- */
748
- function getNestedRelationIdentities(properties) {
749
- const topLevel = /* @__PURE__ */ new Set();
750
- const nested = /* @__PURE__ */ new Set();
751
- const walk = (props, depth) => {
752
- if (!props) return;
753
- for (const [key, property] of Object.entries(props)) {
754
- if (!property) continue;
755
- if (property.type === "relation") {
756
- const relProp = property;
757
- (depth === 0 ? topLevel : nested).add(relProp.relationName ?? key);
758
- } else if (property.type === "map") walk(property.properties, depth + 1);
759
- else if (property.type === "array") {
760
- const arrayProp = property;
761
- if (Array.isArray(arrayProp.of)) arrayProp.of.forEach((p) => walk({ entry: p }, depth + 1));
762
- else if (arrayProp.of) walk({ entry: arrayProp.of }, depth + 1);
763
- if (arrayProp.oneOf?.properties) walk(arrayProp.oneOf.properties, depth + 1);
764
- }
765
- }
766
- };
767
- walk(properties, 0);
768
- for (const identity of topLevel) nested.delete(identity);
769
- return nested;
770
- }
771
- /**
772
723
  * The lists rendered inside an entity view of `collection` — its tabs.
773
724
  *
774
725
  * The single derivation. There used to be two that disagreed: this one, and a
@@ -795,14 +746,12 @@ function getEntityChildViews(collection) {
795
746
  if (capabilities.supportsSubcollections && declaredSubcollections) return asSubcollections(declaredSubcollections() ?? []);
796
747
  if (!capabilities.supportsRelations) return [];
797
748
  const resolvedRelations = resolveCollectionRelations(collection);
798
- const nestedOnly = getNestedRelationIdentities(collection.properties);
799
749
  const views = [];
800
750
  const seen = /* @__PURE__ */ new Set();
801
751
  for (const [relationKey, relation] of Object.entries(resolvedRelations)) {
802
752
  if (relation.cardinality !== "many") continue;
803
753
  const identity = relation.relationName ?? relationKey;
804
754
  if (seen.has(identity)) continue;
805
- if (nestedOnly.has(identity)) continue;
806
755
  let target;
807
756
  try {
808
757
  target = relation.target();
@@ -811,7 +760,7 @@ function getEntityChildViews(collection) {
811
760
  }
812
761
  if (!target) continue;
813
762
  seen.add(identity);
814
- const customName = Object.entries(collection.properties ?? {}).find(([propKey, p]) => p.type === "relation" && (p.relationName ?? propKey) === identity)?.[1]?.name;
763
+ const customName = Object.entries(collection.properties ?? {}).find(([propKey, p]) => p.type === "relation" && (p.relation?.relationName ?? propKey) === identity)?.[1]?.name;
815
764
  const base = {
816
765
  ...target,
817
766
  slug: relationKey,
@@ -1718,8 +1667,8 @@ function resolveJunctionSpecs(collections) {
1718
1667
  for (const collection of collections) {
1719
1668
  const resolved = resolveCollectionRelations(collection);
1720
1669
  for (const relation of Object.values(resolved)) {
1721
- if (!relation.through) continue;
1722
- const targetCollection = typeof relation.target === "function" ? relation.target() : void 0;
1670
+ if (!isManyToMany(relation)) continue;
1671
+ const targetCollection = relation.target();
1723
1672
  if (!targetCollection) continue;
1724
1673
  const rawName = relation.through.table;
1725
1674
  const table = rawName.includes(".") ? rawName.split(".").pop() : rawName;
@@ -2164,88 +2113,33 @@ var CollectionRegistry = class {
2164
2113
  if (!result.dataSource) result.dataSource = resolved.key;
2165
2114
  if (!result.engine) result.engine = resolved.engine;
2166
2115
  }
2167
- const extractedRelations = this.extractRelationsFromProperties(result.properties);
2168
- const relResult = result;
2169
- const manualRelations = getDataSourceCapabilities(result.engine).supportsRelations ? relResult.relations ?? [] : [];
2170
- const mergedRelationsRaw = [...extractedRelations];
2171
- for (const manual of manualRelations) {
2172
- const name = manual.relationName;
2173
- if (!name) mergedRelationsRaw.push(manual);
2174
- else {
2175
- const existingIndex = mergedRelationsRaw.findIndex((r) => r.relationName === name);
2176
- if (existingIndex === -1) mergedRelationsRaw.push(manual);
2177
- else mergedRelationsRaw[existingIndex] = {
2178
- ...manual,
2179
- ...mergedRelationsRaw[existingIndex]
2180
- };
2181
- }
2182
- }
2183
- let mergedRelations = mergedRelationsRaw;
2184
- if (getDataSourceCapabilities(result.engine).supportsRelations) {
2185
- mergedRelations = mergedRelationsRaw.map((r) => {
2186
- try {
2187
- return sanitizeRelation(r, result, (slug) => this.get(slug));
2188
- } catch {
2189
- return r;
2190
- }
2191
- });
2192
- relResult.relations = mergedRelations;
2193
- }
2194
- result.properties = this.normalizeProperties(result.properties, mergedRelations);
2116
+ result.properties = this.normalizeProperties(result.properties, result);
2195
2117
  return result;
2196
2118
  }
2197
- /**
2198
- * Extract Relation[] from properties that have inline relation config (i.e. `target` is set).
2199
- * This allows developers to define relations directly on properties without a separate
2200
- * `relations[]` entry on the collection.
2201
- */
2202
- extractRelationsFromProperties(properties) {
2203
- const relations = [];
2204
- for (const [key, property] of Object.entries(properties)) if (property.type === "relation") {
2205
- const relProp = property;
2206
- const target = relProp.target ?? relProp.relation?.target;
2207
- if (target) {
2208
- const relationName = relProp.relationName ?? relProp.relation?.relationName ?? key;
2209
- relations.push({
2210
- relationName,
2211
- target,
2212
- cardinality: relProp.cardinality ?? relProp.relation?.cardinality ?? "one",
2213
- direction: relProp.direction ?? relProp.relation?.direction ?? "owning",
2214
- inverseRelationName: relProp.inverseRelationName ?? relProp.relation?.inverseRelationName,
2215
- localKey: relProp.localKey ?? relProp.relation?.localKey,
2216
- foreignKeyOnTarget: relProp.foreignKeyOnTarget ?? relProp.relation?.foreignKeyOnTarget,
2217
- through: relProp.through ?? relProp.relation?.through,
2218
- joinPath: relProp.joinPath ?? relProp.relation?.joinPath,
2219
- onUpdate: relProp.onUpdate ?? relProp.relation?.onUpdate,
2220
- onDelete: relProp.onDelete ?? relProp.relation?.onDelete,
2221
- overrides: relProp.overrides ?? relProp.relation?.overrides
2222
- });
2223
- }
2224
- } else if (property.type === "map" && property.properties) relations.push(...this.extractRelationsFromProperties(property.properties));
2225
- return relations;
2226
- }
2227
- normalizeProperties(properties, relations) {
2119
+ normalizeProperties(properties, collection) {
2228
2120
  const newProperties = {};
2229
- for (const key in properties) newProperties[key] = this.normalizeProperty(key, properties[key], relations);
2121
+ for (const key in properties) newProperties[key] = this.normalizeProperty(key, properties[key], collection);
2230
2122
  return newProperties;
2231
2123
  }
2232
- normalizeProperty(key, property, relations) {
2124
+ normalizeProperty(key, property, collection) {
2233
2125
  const newProperty = { ...property };
2234
- if (newProperty.type === "map" && newProperty.properties) newProperty.properties = this.normalizeProperties(newProperty.properties, relations);
2126
+ if (newProperty.type === "map" && newProperty.properties) newProperty.properties = this.normalizeProperties(newProperty.properties, collection);
2235
2127
  else if (newProperty.type === "array") {
2236
2128
  const arrayProp = newProperty;
2237
- if (arrayProp.of) if (Array.isArray(arrayProp.of)) arrayProp.of = arrayProp.of.map((p, i) => this.normalizeProperty(`${key}[${i}]`, p, relations));
2238
- else arrayProp.of = this.normalizeProperty(`${key}.of`, arrayProp.of, relations);
2239
- else if (arrayProp.oneOf && arrayProp.oneOf.properties) arrayProp.oneOf.properties = this.normalizeProperties(arrayProp.oneOf.properties, relations);
2129
+ if (arrayProp.of) if (Array.isArray(arrayProp.of)) arrayProp.of = arrayProp.of.map((p, i) => this.normalizeProperty(`${key}[${i}]`, p, collection));
2130
+ else arrayProp.of = this.normalizeProperty(`${key}.of`, arrayProp.of, collection);
2131
+ else if (arrayProp.oneOf && arrayProp.oneOf.properties) arrayProp.oneOf.properties = this.normalizeProperties(arrayProp.oneOf.properties, collection);
2240
2132
  } else if ((newProperty.type === "string" || newProperty.type === "number") && newProperty.enum) {
2241
2133
  const stringOrNumberProperty = newProperty;
2242
2134
  if (typeof stringOrNumberProperty.enum === "object" && !Array.isArray(stringOrNumberProperty.enum)) stringOrNumberProperty.enum = enumToObjectEntries(stringOrNumberProperty.enum)?.filter((value) => value && (value.id || value.id === 0) && value.label) ?? [];
2243
2135
  } else if (newProperty.type === "relation") {
2244
2136
  const relationProperty = newProperty;
2245
- const name = relationProperty.relationName || key;
2246
- const relation = relations.find((r) => r.relationName === name);
2247
- if (relation) relationProperty.relation = relation;
2248
- else console.warn(`Could not find relation for property '${key}' with relationName: ${name}`);
2137
+ if (relationProperty.relation) relationProperty.resolvedRelation = resolveRelation(relationProperty.relation, collection, key);
2138
+ else {
2139
+ const declared = resolveCollectionRelations(collection)[key];
2140
+ if (declared) relationProperty.resolvedRelation = declared;
2141
+ else console.warn(`Relation property '${key}' on '${collection.slug}' declares no \`relation\`, and the collection has no relation of that name.`);
2142
+ }
2249
2143
  }
2250
2144
  return newProperty;
2251
2145
  }
@@ -3436,6 +3330,6 @@ async function detectJunctionTables(executeSql) {
3436
3330
  return junctionTables;
3437
3331
  }
3438
3332
  //#endregion
3439
- export { COLLECTION_PATH_SEPARATOR, COMPOSITE_ID_SEPARATOR, CollectionRegistry, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, JUNCTION_TABLES_SQL, QueryBuilder, REBASE_INTERNAL_PREFIXES, REBASE_INTERNAL_SCHEMAS, and, buildCollection, buildCompositeId, buildConditionContext, buildProperty, buildPropertyCallbacks, buildRebaseData, buildRoutedRebaseData, buildSdkData, canCreateEntity, canDeleteEntity, canEditEntity, canReadCollection, checkOperation, classifyTable, cond, createDataSourceRegistry, createRelationRef, createRelationRefWithData, defaultUsersCollection, defineCollection, deserializeFilter, deserializeLogicalCondition, deserializeOrderBy, detectJunctionTables, embedParentExpression, enumToObjectEntries, evaluateCondition, evaluatePolicy, findAnonymousGrants, findRelation, fullPathToCollectionSegments, getArrayResolvedProperties, getColumnName, getDeclaredPrimaryKeys, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEffectiveSecurityRules, getEntityChildViews, getEnumVarName, getInjectedSecurityRules, getJunctionCollectionConfig, getJunctionSecurityRules, getLabelOrConfigFrom, getPrimaryKeys, getReferenceFrom, getRelationFrom, getSubcollections, getTableName, getTableVarName, isJunctionBackedRelation, isPropertyBuilder, isRebaseInternalTable, normalizeToEntityRelation, or, parseIdValues, policyToPostgres, registerConditionOperations, resolveArrayProperties, resolveCollectionRelations, resolveDataSource, resolveEnumValues, resolveJunctionSpecs, resolvePrimaryKeys, resolveProperties, resolveProperty, resolvePropertyEnum, resolvePropertyRelation, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource, sanitizeData, sanitizeRelation, securityRuleToConditions, segmentsToStrippedPath, serializeFilter, serializeLogicalCondition, serializeOrderBy, sortProperties, sqlToPolicy, stripCollectionPath, traverseValueProperty, traverseValuesProperties, updateDateAutoValues, wrapAsEntityData, wrapAsSdkData };
3333
+ export { COLLECTION_PATH_SEPARATOR, COMPOSITE_ID_SEPARATOR, CollectionRegistry, DEFAULT_ONE_OF_TYPE, DEFAULT_ONE_OF_VALUE, JUNCTION_TABLES_SQL, QueryBuilder, REBASE_INTERNAL_PREFIXES, REBASE_INTERNAL_SCHEMAS, and, buildCollection, buildCompositeId, buildConditionContext, buildProperty, buildPropertyCallbacks, buildRebaseData, buildRoutedRebaseData, buildSdkData, canCreateEntity, canDeleteEntity, canEditEntity, canReadCollection, checkOperation, classifyTable, cond, createDataSourceRegistry, createRelationRef, createRelationRefWithData, defaultUsersCollection, defineCollection, deserializeFilter, deserializeLogicalCondition, deserializeOrderBy, detectJunctionTables, embedParentExpression, enumToObjectEntries, evaluateCondition, evaluatePolicy, findAnonymousGrants, findRelation, fullPathToCollectionSegments, getArrayResolvedProperties, getColumnName, getDeclaredPrimaryKeys, getDefaultValueFor, getDefaultValueFortype, getDefaultValuesFor, getEffectiveSecurityRules, getEntityChildViews, getEnumVarName, getInjectedSecurityRules, getJunctionCollectionConfig, getJunctionSecurityRules, getLabelOrConfigFrom, getPrimaryKeys, getReferenceFrom, getRelationFrom, getSubcollections, getTableName, getTableVarName, isJunctionBackedRelation, isPropertyBuilder, isRebaseInternalTable, normalizeToEntityRelation, or, parseIdValues, policyToPostgres, registerConditionOperations, resolveArrayProperties, resolveCollectionRelations, resolveDataSource, resolveEnumValues, resolveJunctionSpecs, resolvePrimaryKeys, resolveProperties, resolveProperty, resolvePropertyEnum, resolveRelation, resolveRelationProperty, resolveStorageFilenameString, resolveStoragePathString, resolveStorageSource, sanitizeData, securityRuleToConditions, segmentsToStrippedPath, serializeFilter, serializeLogicalCondition, serializeOrderBy, sortProperties, sqlToPolicy, stripCollectionPath, traverseValueProperty, traverseValuesProperties, updateDateAutoValues, wrapAsEntityData, wrapAsSdkData };
3440
3334
 
3441
3335
  //# sourceMappingURL=index.es.js.map