@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.
@@ -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;
@@ -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(relation: Partial<Relation>, sourceCollection: EntityCollection): Relation {
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
- const targetCollection = relation.target();
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 any).relations) || []) : [];
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
- const targetRelations = getDataSourceCapabilities(targetCollection.driver).supportsRelations ? (((targetCollection as any).relations) || []) : [];
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 ((relCollection as any).relations) {
156
- (relCollection as any).relations.forEach((relation: Relation) => {
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 any).relations) ?? []).find((rel: Relation) => rel.relationName === relProp.relationName)
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 {};
@@ -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 any).relations)) {
354
- const manyRelations = ((collection as any).relations)!.filter((r: Relation) => r.cardinality === "many");
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: any): c is EntityCollection<Record<string, unknown>> => Boolean(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 {};