@rebasepro/common 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +178 -110
- package/dist/index.es.js +71 -9
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +72 -9
- package/dist/index.umd.js.map +1 -1
- package/dist/util/entities.d.ts +2 -2
- package/dist/util/relations.d.ts +1 -1
- package/package.json +12 -15
- package/src/collections/CollectionRegistry.d.ts +56 -0
- package/src/collections/CollectionRegistry.ts +17 -42
- package/src/collections/index.d.ts +1 -0
- package/src/data/buildRebaseData.d.ts +14 -0
- package/src/index.d.ts +3 -0
- package/src/types/json-logic-js.d.ts +1 -1
- package/src/util/builders.d.ts +57 -0
- package/src/util/builders.ts +1 -0
- package/src/util/callbacks.d.ts +6 -0
- package/src/util/collections.d.ts +11 -0
- package/src/util/common.d.ts +2 -0
- package/src/util/conditions.d.ts +26 -0
- package/src/util/entities.d.ts +58 -0
- package/src/util/entities.ts +6 -2
- package/src/util/enums.d.ts +3 -0
- package/src/util/index.d.ts +16 -0
- package/src/util/navigation_from_path.d.ts +34 -0
- package/src/util/navigation_utils.d.ts +20 -0
- package/src/util/parent_references_from_path.d.ts +6 -0
- package/src/util/paths.d.ts +14 -0
- package/src/util/permissions.d.ts +5 -0
- package/src/util/references.d.ts +2 -0
- package/src/util/relations.d.ts +22 -0
- package/src/util/relations.ts +69 -8
- package/src/util/resolutions.d.ts +72 -0
- package/src/util/resolutions.ts +3 -3
- package/src/util/storage.d.ts +24 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { EntityCollection, Property, Relation } from "@rebasepro/types";
|
|
2
|
+
export declare function sanitizeRelation(relation: Partial<Relation>, sourceCollection: EntityCollection): Relation;
|
|
3
|
+
export declare function resolveCollectionRelations(collection: EntityCollection): Record<string, Relation>;
|
|
4
|
+
export declare function resolvePropertyRelation({ propertyKey, property, sourceCollection }: {
|
|
5
|
+
propertyKey: string;
|
|
6
|
+
property: Property;
|
|
7
|
+
sourceCollection: EntityCollection;
|
|
8
|
+
}): Relation | undefined;
|
|
9
|
+
export declare function getTableName(collection: EntityCollection): string;
|
|
10
|
+
export declare function getTableVarName(tableName: string): string;
|
|
11
|
+
export declare function getEnumVarName(tableName: string, propName: string): string;
|
|
12
|
+
export declare function getColumnName(fullColumn: string): string;
|
|
13
|
+
/**
|
|
14
|
+
* Look up a relation by key with forgiving normalization.
|
|
15
|
+
*
|
|
16
|
+
* `resolveCollectionRelations` stores each relation under a single canonical
|
|
17
|
+
* key (no aliases). This helper tries the given key as-is, then falls back to
|
|
18
|
+
* slug form (underscores → hyphens) and snake_case form (hyphens → underscores)
|
|
19
|
+
* so that callers that receive a key from external input (URL path segments,
|
|
20
|
+
* user-provided config, etc.) can still find the right entry.
|
|
21
|
+
*/
|
|
22
|
+
export declare function findRelation(resolvedRelations: Record<string, Relation>, key: string): Relation | undefined;
|
package/src/util/relations.ts
CHANGED
|
@@ -2,14 +2,58 @@ import { CollectionWithRelations, EntityCollection, getDataSourceCapabilities, P
|
|
|
2
2
|
import { toSnakeCase } from "@rebasepro/utils";
|
|
3
3
|
import { generateForeignKeyName } from "@rebasepro/utils";
|
|
4
4
|
|
|
5
|
-
export function sanitizeRelation(
|
|
5
|
+
export function sanitizeRelation(
|
|
6
|
+
relation: Partial<Relation>,
|
|
7
|
+
sourceCollection: EntityCollection,
|
|
8
|
+
resolveCollection?: (slugOrTable: string) => EntityCollection | undefined
|
|
9
|
+
): Relation {
|
|
6
10
|
if (!relation.target) {
|
|
7
11
|
throw new Error("Relation is missing a `target` collection.");
|
|
8
12
|
}
|
|
9
|
-
|
|
13
|
+
|
|
14
|
+
const rawTarget = relation.target;
|
|
15
|
+
let targetCollection: EntityCollection | undefined;
|
|
16
|
+
|
|
17
|
+
if (typeof rawTarget === "string") {
|
|
18
|
+
if (resolveCollection) {
|
|
19
|
+
targetCollection = resolveCollection(rawTarget);
|
|
20
|
+
}
|
|
21
|
+
if (!targetCollection) {
|
|
22
|
+
targetCollection = { slug: rawTarget, name: rawTarget } as EntityCollection;
|
|
23
|
+
}
|
|
24
|
+
} else if (typeof rawTarget === "function") {
|
|
25
|
+
const evaluated = rawTarget();
|
|
26
|
+
if (typeof evaluated === "string") {
|
|
27
|
+
if (resolveCollection) {
|
|
28
|
+
targetCollection = resolveCollection(evaluated);
|
|
29
|
+
}
|
|
30
|
+
if (!targetCollection) {
|
|
31
|
+
targetCollection = { slug: evaluated, name: evaluated } as EntityCollection;
|
|
32
|
+
}
|
|
33
|
+
} else {
|
|
34
|
+
targetCollection = evaluated;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!targetCollection) {
|
|
39
|
+
throw new Error("Relation is missing a valid `target` collection.");
|
|
40
|
+
}
|
|
10
41
|
|
|
11
42
|
const newRelation: Partial<Relation> = { ...relation };
|
|
12
43
|
|
|
44
|
+
newRelation.target = () => {
|
|
45
|
+
if (typeof rawTarget === "string") {
|
|
46
|
+
return (resolveCollection && resolveCollection(rawTarget)) || targetCollection!;
|
|
47
|
+
} else if (typeof rawTarget === "function") {
|
|
48
|
+
const evaluated = rawTarget();
|
|
49
|
+
if (typeof evaluated === "string") {
|
|
50
|
+
return (resolveCollection && resolveCollection(evaluated)) || targetCollection!;
|
|
51
|
+
}
|
|
52
|
+
return evaluated;
|
|
53
|
+
}
|
|
54
|
+
return targetCollection!;
|
|
55
|
+
};
|
|
56
|
+
|
|
13
57
|
// 1. Default relationName from target collection slug
|
|
14
58
|
if (!newRelation.relationName) {
|
|
15
59
|
newRelation.relationName = toSnakeCase(targetCollection.slug);
|
|
@@ -41,7 +85,7 @@ export function sanitizeRelation(relation: Partial<Relation>, sourceCollection:
|
|
|
41
85
|
|
|
42
86
|
try {
|
|
43
87
|
// Look for an owning relation on the target that points back to this collection
|
|
44
|
-
const targetRelations = getDataSourceCapabilities(targetCollection.driver).supportsRelations ? (((targetCollection as
|
|
88
|
+
const targetRelations = getDataSourceCapabilities(targetCollection.driver).supportsRelations ? (((targetCollection as CollectionWithRelations).relations) || []) : [];
|
|
45
89
|
for (const targetRel of targetRelations) {
|
|
46
90
|
if (targetRel.direction === "owning" &&
|
|
47
91
|
targetRel.cardinality === "one" &&
|
|
@@ -85,16 +129,33 @@ export function sanitizeRelation(relation: Partial<Relation>, sourceCollection:
|
|
|
85
129
|
// Note: we intentionally do NOT require `through` here because the raw (unsanitized)
|
|
86
130
|
// relations won't have `through` populated yet — sanitizeRelation fills it in later.
|
|
87
131
|
// `cardinality: "many" + direction: "owning"` is sufficient to identify owning M2M.
|
|
88
|
-
|
|
132
|
+
|
|
133
|
+
// 1. Check the explicit relations[] array
|
|
134
|
+
const targetRelations = getDataSourceCapabilities(targetCollection.driver).supportsRelations ? (((targetCollection as CollectionWithRelations).relations) || []) : [];
|
|
89
135
|
for (const targetRel of targetRelations) {
|
|
90
136
|
if (targetRel.cardinality === "many" &&
|
|
91
137
|
(targetRel.direction === "owning" || !targetRel.direction) &&
|
|
92
138
|
(targetRel.relationName === newRelation.inverseRelationName)) {
|
|
93
|
-
// Found a corresponding owning many-to-many relation
|
|
94
139
|
isManyToManyInverse = true;
|
|
95
140
|
break;
|
|
96
141
|
}
|
|
97
142
|
}
|
|
143
|
+
|
|
144
|
+
// 2. Also check the target's properties for inline relation definitions
|
|
145
|
+
// (e.g. posts.properties.tags = { type: "relation", cardinality: "many", direction: "owning" })
|
|
146
|
+
if (!isManyToManyInverse && targetCollection.properties) {
|
|
147
|
+
for (const [propKey, prop] of Object.entries(targetCollection.properties)) {
|
|
148
|
+
if ((prop as Property).type !== "relation") continue;
|
|
149
|
+
const relProp = prop as RelationProperty;
|
|
150
|
+
const relName = relProp.relationName || propKey;
|
|
151
|
+
if (relName === newRelation.inverseRelationName &&
|
|
152
|
+
relProp.cardinality === "many" &&
|
|
153
|
+
(relProp.direction === "owning" || !relProp.direction)) {
|
|
154
|
+
isManyToManyInverse = true;
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
98
159
|
} catch (e) {
|
|
99
160
|
// If we can't inspect the target collection, assume one-to-many
|
|
100
161
|
}
|
|
@@ -152,8 +213,8 @@ export function resolveCollectionRelations(
|
|
|
152
213
|
|
|
153
214
|
// 1. Process explicit relations from the `relations` field.
|
|
154
215
|
// Each relation is stored once under its canonical relationName key.
|
|
155
|
-
if (
|
|
156
|
-
|
|
216
|
+
if (relCollection.relations) {
|
|
217
|
+
relCollection.relations.forEach((relation: Relation) => {
|
|
157
218
|
const normalizedRelation = sanitizeRelation(relation, collection);
|
|
158
219
|
const relationKey = normalizedRelation.relationName;
|
|
159
220
|
if (relationKey) {
|
|
@@ -232,7 +293,7 @@ export function resolvePropertyRelation({
|
|
|
232
293
|
}
|
|
233
294
|
|
|
234
295
|
// 2. Fall back to lookup from collection.relations[] (backward compat)
|
|
235
|
-
const relation = (((sourceCollection as
|
|
296
|
+
const relation = (((sourceCollection as CollectionWithRelations).relations) ?? []).find((rel: Relation) => rel.relationName === relProp.relationName)
|
|
236
297
|
if (!relation) {
|
|
237
298
|
console.warn(`Unrecognized relation format for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
|
|
238
299
|
return undefined;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { ArrayProperty, AuthController, EntityCollection, EnumValueConfig, EnumValues, NumberProperty, Properties, Property, Relation, RelationProperty, StringProperty } from "@rebasepro/types";
|
|
2
|
+
type PropertyConfig = {
|
|
3
|
+
property: unknown;
|
|
4
|
+
[key: string]: unknown;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Resolve property builders, enums and arrays.
|
|
8
|
+
*/
|
|
9
|
+
export type ResolvePropertyProps<M extends Record<string, unknown> = Record<string, unknown>> = {
|
|
10
|
+
property: Property;
|
|
11
|
+
propertyKey?: string;
|
|
12
|
+
values?: Partial<M>;
|
|
13
|
+
previousValues?: Partial<M>;
|
|
14
|
+
path?: string;
|
|
15
|
+
entityId?: string | number;
|
|
16
|
+
index?: number;
|
|
17
|
+
propertyConfigs?: Record<string, PropertyConfig>;
|
|
18
|
+
ignoreMissingFields?: boolean;
|
|
19
|
+
authController: AuthController;
|
|
20
|
+
};
|
|
21
|
+
export declare function resolveProperty<M extends Record<string, unknown> = Record<string, unknown>>(props: ResolvePropertyProps<M>): Property | null;
|
|
22
|
+
export declare function resolveRelationProperty(property: RelationProperty, relations: Relation[], propertyKey?: string): RelationProperty;
|
|
23
|
+
/**
|
|
24
|
+
* Resolve enum aliases for a string or number property
|
|
25
|
+
* @param property
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolvePropertyEnum(property: StringProperty | NumberProperty): StringProperty | NumberProperty;
|
|
28
|
+
/**
|
|
29
|
+
* Resolve enums and arrays for properties
|
|
30
|
+
* @param properties
|
|
31
|
+
* @param value
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveProperties<M extends Record<string, unknown>>({ propertyKey, properties, ignoreMissingFields, ...props }: {
|
|
34
|
+
propertyKey?: string;
|
|
35
|
+
properties: Properties;
|
|
36
|
+
values?: Partial<M>;
|
|
37
|
+
previousValues?: Partial<M>;
|
|
38
|
+
path?: string;
|
|
39
|
+
entityId?: string | number;
|
|
40
|
+
index?: number;
|
|
41
|
+
propertyConfigs?: Record<string, PropertyConfig>;
|
|
42
|
+
ignoreMissingFields?: boolean;
|
|
43
|
+
authController: AuthController;
|
|
44
|
+
}): Properties;
|
|
45
|
+
export declare function resolveArrayProperties<M>({ propertyKey, property, ignoreMissingFields, ...props }: {
|
|
46
|
+
propertyKey?: string;
|
|
47
|
+
property: ArrayProperty;
|
|
48
|
+
values?: Partial<M>;
|
|
49
|
+
previousValues?: Partial<M>;
|
|
50
|
+
path?: string;
|
|
51
|
+
entityId?: string | number;
|
|
52
|
+
index?: number;
|
|
53
|
+
propertyConfigs?: Record<string, PropertyConfig>;
|
|
54
|
+
ignoreMissingFields?: boolean;
|
|
55
|
+
authController: AuthController;
|
|
56
|
+
}): Property[];
|
|
57
|
+
export declare function getArrayResolvedProperties({ propertyKey, propertyValue, property, ...props }: {
|
|
58
|
+
propertyValue: unknown;
|
|
59
|
+
propertyKey?: string;
|
|
60
|
+
property: ArrayProperty;
|
|
61
|
+
ignoreMissingFields: boolean;
|
|
62
|
+
values?: object;
|
|
63
|
+
previousValues?: object;
|
|
64
|
+
path?: string;
|
|
65
|
+
entityId?: string | number;
|
|
66
|
+
index?: number;
|
|
67
|
+
propertyConfigs?: Record<string, PropertyConfig>;
|
|
68
|
+
authController: AuthController;
|
|
69
|
+
}): Property[];
|
|
70
|
+
export declare function resolveEnumValues(input: EnumValues): EnumValueConfig[] | undefined;
|
|
71
|
+
export declare function getSubcollections<M extends Record<string, unknown> = Record<string, unknown>>(collection: EntityCollection<M>): EntityCollection<Record<string, unknown>>[];
|
|
72
|
+
export {};
|
package/src/util/resolutions.ts
CHANGED
|
@@ -350,8 +350,8 @@ export function getSubcollections<M extends Record<string, unknown> = Record<str
|
|
|
350
350
|
return (collection as CollectionWithSubcollections).subcollections!() ?? [];
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
-
if (getDataSourceCapabilities(collection.driver).supportsRelations && ((collection as
|
|
354
|
-
const manyRelations = ((collection as
|
|
353
|
+
if (getDataSourceCapabilities(collection.driver).supportsRelations && ((collection as CollectionWithRelations).relations)) {
|
|
354
|
+
const manyRelations = ((collection as CollectionWithRelations).relations)!.filter((r: Relation) => r.cardinality === "many");
|
|
355
355
|
return manyRelations.map((r: Relation) => {
|
|
356
356
|
const target = r.target();
|
|
357
357
|
if (!target) return undefined;
|
|
@@ -376,7 +376,7 @@ export function getSubcollections<M extends Record<string, unknown> = Record<str
|
|
|
376
376
|
|
|
377
377
|
const targetWithOverrides = { ...target, ...baseOverrides };
|
|
378
378
|
return (r.overrides ? mergeDeep(targetWithOverrides, r.overrides) : targetWithOverrides) as EntityCollection<Record<string, unknown>>;
|
|
379
|
-
}).filter((c:
|
|
379
|
+
}).filter((c: EntityCollection<Record<string, unknown>> | undefined): c is EntityCollection<Record<string, unknown>> => Boolean(c));
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
return [];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ArrayProperty, EntityValues, StorageConfig, StringProperty, UploadedFileContext } from "@rebasepro/types";
|
|
2
|
+
interface ResolveFilenameStringParams<M extends Record<string, unknown>> {
|
|
3
|
+
input: string | ((context: UploadedFileContext) => (Promise<string> | string));
|
|
4
|
+
storage: StorageConfig;
|
|
5
|
+
values: EntityValues<M>;
|
|
6
|
+
entityId?: string | number;
|
|
7
|
+
path?: string;
|
|
8
|
+
property: StringProperty | ArrayProperty;
|
|
9
|
+
file: File;
|
|
10
|
+
propertyKey: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function resolveStorageFilenameString<M extends Record<string, unknown>>({ input, storage, values, entityId, path, property, file, propertyKey }: ResolveFilenameStringParams<M>): Promise<string>;
|
|
13
|
+
interface ResolveStoragePathStringParams<M extends Record<string, unknown>> {
|
|
14
|
+
input: string | ((context: UploadedFileContext) => string);
|
|
15
|
+
storage: StorageConfig;
|
|
16
|
+
values: EntityValues<M>;
|
|
17
|
+
entityId?: string | number;
|
|
18
|
+
path?: string;
|
|
19
|
+
property: StringProperty | ArrayProperty;
|
|
20
|
+
file: File;
|
|
21
|
+
propertyKey: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function resolveStoragePathString<M extends Record<string, unknown>>({ input, storage, values, entityId, path, property, file, propertyKey }: ResolveStoragePathStringParams<M>): string;
|
|
24
|
+
export {};
|