@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.
- package/dist/collections/CollectionRegistry.d.ts +0 -6
- package/dist/index.es.js +150 -256
- package/dist/index.es.js.map +1 -1
- package/dist/util/index.d.ts +1 -0
- package/dist/util/relations.d.ts +27 -17
- package/dist/util/resolutions.d.ts +10 -2
- package/dist/util/resolve-relation.d.ts +19 -0
- package/package.json +3 -3
- package/src/collections/CollectionRegistry.ts +38 -99
- package/src/data/buildRebaseData.ts +12 -12
- package/src/data/query_builder.ts +7 -2
- package/src/util/index.ts +1 -0
- package/src/util/junction-policies.ts +5 -3
- package/src/util/relations.ts +43 -295
- package/src/util/resolutions.ts +27 -67
- package/src/util/resolve-relation.ts +134 -0
|
@@ -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/
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
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
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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
|
|
468
|
-
*
|
|
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
|
-
*
|
|
471
|
-
*
|
|
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
|
|
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
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
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
|
|
1722
|
-
const targetCollection =
|
|
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
|
-
|
|
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],
|
|
2121
|
+
for (const key in properties) newProperties[key] = this.normalizeProperty(key, properties[key], collection);
|
|
2230
2122
|
return newProperties;
|
|
2231
2123
|
}
|
|
2232
|
-
normalizeProperty(key, property,
|
|
2124
|
+
normalizeProperty(key, property, collection) {
|
|
2233
2125
|
const newProperty = { ...property };
|
|
2234
|
-
if (newProperty.type === "map" && newProperty.properties) newProperty.properties = this.normalizeProperties(newProperty.properties,
|
|
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,
|
|
2238
|
-
else arrayProp.of = this.normalizeProperty(`${key}.of`, arrayProp.of,
|
|
2239
|
-
else if (arrayProp.oneOf && arrayProp.oneOf.properties) arrayProp.oneOf.properties = this.normalizeProperties(arrayProp.oneOf.properties,
|
|
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
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
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,
|
|
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
|