@lobb-js/core 0.23.0 → 0.25.0

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.
@@ -40,6 +40,7 @@ export class MetaService {
40
40
  collections[collectionName].singleton = Lobb.instance.configManager
41
41
  .isCollectionSingleton(collectionName);
42
42
 
43
+ // filling the collection properties
43
44
  // is collection virtual
44
45
  collections[collectionName].virtual = Lobb.instance.configManager
45
46
  .isCollectionVirtual(collectionName);
@@ -7,7 +7,7 @@ import {
7
7
  } from "../types/index.ts";
8
8
  import type { CollectionField } from "../types/index.ts";
9
9
  import type { Extension } from "../types/index.ts";
10
- import type { RelationsConfig } from "../types/index.ts";
10
+ import type { RelationsConfig, RegularRelation } from "../types/index.ts";
11
11
 
12
12
  import _ from "lodash";
13
13
  import { coreCollections } from "../coreCollections/index.ts";
@@ -42,6 +42,39 @@ export class ConfigManager {
42
42
  };
43
43
  }
44
44
 
45
+ public expandPolymorphicFields() {
46
+ for (const [collectionName, collection] of Object.entries(this.config.collections)) {
47
+ if (!collection.fields) continue;
48
+ for (const [fieldName, fieldValue] of Object.entries(collection.fields)) {
49
+ if (fieldValue.type !== "polymorphic") continue;
50
+
51
+ const { collection_field, id_field, references } = fieldValue as any;
52
+
53
+ // Create the two real DB columns (hidden in UI)
54
+ collection.fields[collection_field] = {
55
+ type: "string",
56
+ length: 50,
57
+ ui: { hidden: true },
58
+ } as any;
59
+ collection.fields[id_field] = {
60
+ type: "integer",
61
+ ui: { hidden: true },
62
+ } as any;
63
+
64
+ // Convert the original field to virtual (drives the UI picker)
65
+ collection.fields[fieldName] = { virtual: true, ui: { icon: "Shuffle" } } as any;
66
+
67
+ // Auto-register the polymorphic relation
68
+ if (!this.config.relations) this.config.relations = [];
69
+ this.config.relations.push({
70
+ type: "polymorphic",
71
+ from: { collection: collectionName, virtual_field: fieldName, collection_field, id_field },
72
+ to: references,
73
+ } as any);
74
+ }
75
+ }
76
+ }
77
+
45
78
  public addUniqueIndexesFromFields() {
46
79
  for (
47
80
  const [collectionName, collectionValue] of Object.entries(
@@ -54,7 +87,7 @@ export class ConfigManager {
54
87
  (collectionValue as any).fields,
55
88
  )
56
89
  ) {
57
- if (fieldValue.unique) {
90
+ if ((fieldValue as any).unique) {
58
91
  const indexName = `${collectionName}_${fieldName}_unique_index`;
59
92
  (collectionValue as any).indexes[indexName] = {
60
93
  unique: true,
@@ -290,12 +323,103 @@ export class ConfigManager {
290
323
  delete this.config.collections[collectionName];
291
324
  }
292
325
 
293
- public getChildRelations(collectionName: string): RelationsConfig {
326
+ public getParentRelation(
327
+ collectionName: string,
328
+ fieldName: string,
329
+ ): RegularRelation | null {
330
+ return (
331
+ this.config.relations?.find((rel): rel is RegularRelation => {
332
+ if ("type" in rel && rel.type === "polymorphic") return false;
333
+ return (
334
+ (rel as RegularRelation).from.collection === collectionName &&
335
+ (rel as RegularRelation).from.field === fieldName
336
+ );
337
+ }) ?? null
338
+ );
339
+ }
340
+
341
+ public getPolymorphicRelationByVirtualField(
342
+ collectionName: string,
343
+ virtualField: string,
344
+ ): { collectionField: string; idField: string; allowedCollections: string[] } | null {
345
+ const rel = this.config.relations?.find((r) => {
346
+ if (!("type" in r) || r.type !== "polymorphic") return false;
347
+ return (r as any).from.collection === collectionName &&
348
+ (r as any).from.virtual_field === virtualField;
349
+ }) as any | undefined;
350
+
351
+ if (!rel) return null;
352
+ return {
353
+ collectionField: rel.from.collection_field,
354
+ idField: rel.from.id_field,
355
+ allowedCollections: rel.to,
356
+ };
357
+ }
358
+
359
+ public getPolymorphicChildRelation(
360
+ parentCollection: string,
361
+ childCollection: string,
362
+ ): { collectionField: string; idField: string } | null {
363
+ const rel = this.config.relations?.find((r) => {
364
+ if (!("type" in r) || r.type !== "polymorphic") return false;
365
+ return (r as any).from.collection === childCollection &&
366
+ (r as any).to.includes(parentCollection);
367
+ }) as any | undefined;
368
+
369
+ if (!rel) return null;
370
+ return {
371
+ collectionField: rel.from.collection_field,
372
+ idField: rel.from.id_field,
373
+ };
374
+ }
375
+
376
+ public getM2MJunction(
377
+ fromCollection: string,
378
+ toCollection: string,
379
+ ): { junctionCollection: string; parentFKField: string; targetFKField: string } | null {
380
+ const fromChildRelations = this.getChildRelations(fromCollection);
381
+
382
+ for (const fromRelation of fromChildRelations) {
383
+ const junctionName = fromRelation.from.collection;
384
+ if (!this.config.collections[junctionName]?.junction) continue;
385
+
386
+ const toRelation = this.config.relations?.find((rel): rel is RegularRelation => {
387
+ if ("type" in rel && rel.type === "polymorphic") return false;
388
+ return (
389
+ (rel as RegularRelation).from.collection === junctionName &&
390
+ (rel as RegularRelation).to.collection === toCollection &&
391
+ (rel as RegularRelation).from.field !== fromRelation.from.field
392
+ );
393
+ });
394
+
395
+ if (toRelation) {
396
+ return {
397
+ junctionCollection: junctionName,
398
+ parentFKField: fromRelation.from.field,
399
+ targetFKField: toRelation.from.field,
400
+ };
401
+ }
402
+ }
403
+
404
+ return null;
405
+ }
406
+
407
+ public getCollectionFKFields(collectionName: string): string[] {
408
+ if (!this.config.relations) return [];
409
+ return this.config.relations
410
+ .filter((rel): rel is RegularRelation => {
411
+ if ("type" in rel && rel.type === "polymorphic") return false;
412
+ return (rel as RegularRelation).from.collection === collectionName;
413
+ })
414
+ .map((rel) => rel.from.field);
415
+ }
416
+
417
+ public getChildRelations(collectionName: string): RegularRelation[] {
294
418
  if (this.config.relations) {
295
- const childrenRelations = this.config.relations.filter((relation) =>
296
- relation.to.collection === collectionName
297
- );
298
- return childrenRelations;
419
+ return this.config.relations.filter((relation): relation is RegularRelation => {
420
+ if ("type" in relation && relation.type === "polymorphic") return false;
421
+ return (relation as RegularRelation).to.collection === collectionName;
422
+ });
299
423
  }
300
424
 
301
425
  return [];
@@ -10,11 +10,19 @@ function validateRelations(config: Config, configManager: ConfigManager) {
10
10
  if (config.relations) {
11
11
  for (let index = 0; index < config.relations.length; index++) {
12
12
  const relation = config.relations[index];
13
- configManager.getCollection(relation.from.collection);
14
- configManager.getField(relation.from.field, relation.from.collection);
15
- if (!Array.isArray(relation.to)) {
16
- configManager.getCollection(relation.to.collection);
17
- configManager.getField(relation.to.field, relation.to.collection);
13
+ if ("type" in relation && relation.type === "polymorphic") {
14
+ configManager.getCollection(relation.from.collection);
15
+ configManager.fieldExists(relation.from.collection_field, relation.from.collection);
16
+ configManager.fieldExists(relation.from.id_field, relation.from.collection);
17
+ for (const targetCollection of relation.to) {
18
+ configManager.getCollection(targetCollection);
19
+ }
20
+ } else {
21
+ configManager.getCollection(relation.from.collection);
22
+ configManager.getField((relation.from as any).field, relation.from.collection);
23
+ const to = relation.to as { collection: string; field: string };
24
+ configManager.getCollection(to.collection);
25
+ configManager.getField(to.field, to.collection);
18
26
  }
19
27
  }
20
28
  }
@@ -1,5 +1,4 @@
1
1
  import type { FindAllParamsOutput } from "../../../types/index.ts";
2
- import { Lobb } from "../../../Lobb.ts";
3
2
  import format from "pg-format";
4
3
  import _ from "lodash";
5
4
  import { LobbError } from "../../../LobbError.ts";
@@ -74,128 +73,14 @@ export class QueryBuilder {
74
73
  }
75
74
 
76
75
  private getSelectFields(): string {
77
- // TODO: SANITIZE ALL THE FIELDS IN HERE THAT COMES FROM THE USER USING THE FORMAT LIBRARY
78
76
  const columns: Set<string> = new Set([`${this.collectionName}.id`]);
79
- const fields = this.params.fields.split(",").map((f) => f.trim());
80
-
81
- // Separate main fields (no dot) from foreign fields
82
- const mainFields = fields.filter((f) => !f.includes("."));
83
- mainFields.forEach((field) => {
84
- if (field === "*") {
85
- const collectionFields = Lobb.instance.configManager.getNormalCollection(this.collectionName).fields;
86
- const currentCollectionFields = Object.entries(collectionFields)
87
- .filter(([, fieldConfig]) => !fieldConfig.virtual)
88
- .map(([fieldName]) => fieldName);
89
- currentCollectionFields.forEach((item) => {
90
- columns.add(`${this.collectionName}.${item}`);
91
- });
92
- } else {
93
- columns.add(`${this.collectionName}.${field}`);
94
- }
95
- });
96
-
97
- // Process foreign fields (nested relations)
98
- if (fields.some((f) => f.includes("."))) {
99
- const foreignFields: Record<string, string[]> = {};
100
-
101
- // Group foreign fields by their parent
102
- for (const field of fields) {
103
- if (field.includes(".")) {
104
- const foreignField = field.split(".")[0];
105
- if (!foreignFields[foreignField]) foreignFields[foreignField] = [];
106
- }
107
- }
108
-
109
- for (const foreignField of Object.keys(foreignFields)) {
110
- foreignFields[foreignField] = fields
111
- .filter((f) => f.startsWith(`${foreignField}.`))
112
- .map((f) => f.slice(foreignField.length + 1));
113
- }
114
-
115
- // Add JSON build objects for each foreign field
116
- for (
117
- const [foreignField, nestedFields] of Object.entries(foreignFields)
118
- ) {
119
- const foreignFieldCollection = this.getForeignFieldCollection(
120
- this.collectionName,
121
- foreignField,
122
- );
123
-
124
- // Remove raw foreign key column
125
- columns.delete(`${this.collectionName}.${foreignField}`);
126
-
127
- // Determine alias (handle self-reference)
128
- const alias = foreignFieldCollection === this.collectionName
129
- ? `${foreignFieldCollection}_self_${foreignField}`
130
- : foreignFieldCollection;
131
-
132
- // Exclude virtual fields — they have no DB column in the related table
133
- const foreignCollectionFields = Lobb.instance.configManager.getNormalCollection(foreignFieldCollection).fields;
134
- const nonVirtualNestedFields = nestedFields.filter((f) => !foreignCollectionFields[f]?.virtual);
135
-
136
- // Build JSON safely: only if related row exists
137
- const jsonField = `
138
- CASE
139
- WHEN ${alias}.id IS NULL THEN NULL
140
- ELSE json_build_object(
141
- ${nonVirtualNestedFields.map((f) => `'${f}', ${alias}.${f}`).join(", ")}
142
- )
143
- END AS ${foreignField}
144
- `;
145
-
146
- columns.add(jsonField.trim());
147
- }
148
- }
149
-
77
+ const fields = this.params.fields.split(",").map((f) => f.trim()).filter(Boolean);
78
+ fields.forEach((field) => columns.add(`${this.collectionName}.${field}`));
150
79
  return Array.from(columns).join(", ");
151
80
  }
152
81
 
153
- private getJsonBuildObject(
154
- collectionName: string,
155
- fieldName: string,
156
- columns: string[],
157
- ): string {
158
- return `json_build_object(
159
- ${
160
- columns.map((column) => `'${column}', ${collectionName}.${column}`)
161
- .join(",")
162
- }
163
- ) AS ${fieldName}`;
164
- }
165
-
166
82
  private getJoins(): string {
167
- const joins: string[] = [];
168
- const fields = this.params.fields.split(",").map((field) => field.trim());
169
- const foreignFields: Set<string> = new Set();
170
-
171
- // Collect all foreign fields from the selected fields
172
- for (const field of fields) {
173
- if (field.includes(".")) {
174
- const foreignKeyField = field.split(".")[0];
175
- foreignFields.add(foreignKeyField);
176
- }
177
- }
178
-
179
- const collectionName = this.collectionName;
180
-
181
- // Build LEFT JOIN statements
182
- for (const foreignField of Array.from(foreignFields)) {
183
- const foreignKeyCollection = this.getForeignFieldCollection(
184
- collectionName,
185
- foreignField,
186
- );
187
-
188
- // If self-referencing, generate a unique alias
189
- const alias = foreignKeyCollection === collectionName
190
- ? `${foreignKeyCollection}_self_${foreignField}`
191
- : foreignKeyCollection;
192
-
193
- joins.push(
194
- `LEFT JOIN ${foreignKeyCollection} AS ${alias} ON ${alias}.id = ${collectionName}.${foreignField}`,
195
- );
196
- }
197
-
198
- return joins.join(" ");
83
+ return "";
199
84
  }
200
85
 
201
86
  private getGroupBy(): string {
@@ -453,27 +338,4 @@ export class QueryBuilder {
453
338
  return whereClause.trim();
454
339
  }
455
340
 
456
- getForeignFieldCollection(collection: string, foreignField: string) {
457
- const relations = Lobb.instance.configManager.config.relations;
458
- if (!relations) {
459
- throw new LobbError({
460
- code: "BAD_REQUEST",
461
- message:
462
- `Can't use dot (".") operator in (fields) findAll property because there is not relations`,
463
- });
464
- }
465
- const foreignFieldRelation = relations.find((relation) =>
466
- relation.from.collection === collection &&
467
- relation.from.field === foreignField
468
- );
469
- if (!foreignFieldRelation) {
470
- throw new LobbError({
471
- code: "BAD_REQUEST",
472
- message:
473
- `Couldn't find the relation object of the (${this.collectionName}.${foreignField}) field`,
474
- });
475
- }
476
-
477
- return foreignFieldRelation.to.collection;
478
- }
479
341
  }
package/src/index.ts CHANGED
@@ -40,7 +40,7 @@ export {
40
40
  filterSchema,
41
41
  } from "./types/filterSchema.ts";
42
42
  export type {
43
- FindAllParamsInput,
43
+ FindAllParams,
44
44
  FindAllParamsOutput,
45
45
  } from "./types/collectionServiceSchema.ts";
46
46
  export { findAllParamsSchema } from "./types/collectionServiceSchema.ts";
@@ -1,18 +1,65 @@
1
1
  import { z } from "zod";
2
2
 
3
- export const findAllParamsSchema = z
4
- .object({
3
+ export type ChildParams = {
4
+ fields?: string | string[];
5
+ sort?: string;
6
+ limit?: number;
7
+ offset?: number;
8
+ page?: number;
9
+ filter?: any;
10
+ children?: Record<string, ChildParams>;
11
+ };
12
+
13
+ export const childParamsSchema: z.ZodType<ChildParams> = z.lazy(() =>
14
+ z.object({
15
+ fields: z.union([z.string(), z.array(z.string())]).optional(),
5
16
  sort: z.string().optional(),
6
- limit: z.coerce.number().default(100),
7
- offset: z.coerce.number().default(0),
8
- page: z.coerce.number().optional(),
9
- fields: z.string().default("*"),
17
+ limit: z.number().optional(),
18
+ offset: z.number().optional(),
19
+ page: z.number().optional(),
10
20
  filter: z.any(),
11
- }).strict()
12
- .default({
13
- fields: "*",
14
- limit: 100,
15
- offset: 0,
16
- });
17
- export type FindAllParamsInput = z.input<typeof findAllParamsSchema>;
18
- export type FindAllParamsOutput = z.output<typeof findAllParamsSchema>;
21
+ children: z.record(childParamsSchema).optional(),
22
+ })
23
+ );
24
+
25
+ const baseParamsSchema = z.object({
26
+ fields: z.union([z.string(), z.array(z.string())]).optional(),
27
+ children: z.record(childParamsSchema).optional(),
28
+ });
29
+
30
+ export const findOneParamsSchema = baseParamsSchema;
31
+ export type FindOneParams = z.infer<typeof findOneParamsSchema>;
32
+
33
+ export const findAllParamsSchema = baseParamsSchema
34
+ .extend({
35
+ sort: z.string().optional(),
36
+ limit: z.number().optional(),
37
+ offset: z.number().optional(),
38
+ page: z.number().optional(),
39
+ filter: z.any(),
40
+ })
41
+ .strict();
42
+ export type FindAllParams = z.infer<typeof findAllParamsSchema>;
43
+
44
+ export type CreateChildren = Record<string, {
45
+ create?: Array<Record<string, any> & { children?: CreateChildren }>;
46
+ link?: (string | number)[];
47
+ }>;
48
+
49
+ export type UpdateChildren = Record<string, {
50
+ create?: Array<Record<string, any> & { children?: UpdateChildren }>;
51
+ link?: (string | number)[];
52
+ unlink?: (string | number)[];
53
+ delete?: (string | number)[];
54
+ set?: (string | number)[];
55
+ update?: Array<{ id: string | number } & Record<string, any>>;
56
+ }>;
57
+
58
+ export type FindAllParamsOutput = {
59
+ sort?: string;
60
+ limit: number;
61
+ offset: number;
62
+ page?: number;
63
+ fields: string;
64
+ filter?: any;
65
+ };
@@ -1,6 +1,9 @@
1
1
  import { RelationCollectionFieldSchema } from "./relations.ts";
2
2
 
3
3
  import { z } from "zod";
4
+ import type { icons } from "lucide-svelte";
5
+
6
+ type LucideIconName = keyof typeof icons;
4
7
 
5
8
  export type EnumColor =
6
9
  | "red" | "rose" | "pink" | "fuchsia" | "purple"
@@ -28,8 +31,10 @@ const UiInputSchema = z.object({
28
31
  // Defining the ui property schema
29
32
  const UiSchema = z.object({
30
33
  disabled: z.boolean().optional(),
34
+ hidden: z.boolean().optional(),
31
35
  placeholder: z.string().optional(),
32
36
  input: UiInputSchema.optional(),
37
+ icon: z.custom<LucideIconName>((val) => typeof val === "string").optional(),
33
38
  });
34
39
 
35
40
  export type FieldHookFn = (ctx: {
@@ -164,6 +169,18 @@ export const CollectionTimeFieldSchema = z.union([
164
169
  VirtualFieldSchema.extend({ type: z.literal("time") }),
165
170
  ]);
166
171
 
172
+ // Polymorphic field — expands during config init into:
173
+ // - {collection_field}: real string column (hidden in UI)
174
+ // - {id_field}: real integer column (hidden in UI)
175
+ // - the field itself becomes virtual (drives the compound picker widget)
176
+ // A polymorphic relation is also auto-registered into config.relations.
177
+ export const CollectionPolymorphicFieldSchema = CollectionFieldBaseSchema.extend({
178
+ type: z.literal("polymorphic"),
179
+ collection_field: z.string(),
180
+ id_field: z.string(),
181
+ references: z.array(z.string()),
182
+ });
183
+
167
184
  // Union type if you want a single schema for all
168
185
  export const CollectionFieldSchema = z.union([
169
186
  CollectionBoolFieldSchema,
@@ -176,6 +193,7 @@ export const CollectionFieldSchema = z.union([
176
193
  CollectionStringFieldSchema,
177
194
  CollectionTextFieldSchema,
178
195
  CollectionTimeFieldSchema,
196
+ CollectionPolymorphicFieldSchema,
179
197
  ]);
180
198
 
181
199
  export type CollectionField = z.infer<typeof CollectionFieldSchema>;
@@ -3,6 +3,9 @@ import {
3
3
  CollectionFieldSchema,
4
4
  CollectionIntegerFieldSchema,
5
5
  } from "./collectionFields.ts";
6
+ import type { icons } from "lucide-svelte";
7
+
8
+ type LucideIconName = keyof typeof icons;
6
9
 
7
10
  export type CollectionHookFn = (ctx: {
8
11
  data: Record<string, any>;
@@ -49,6 +52,7 @@ const CollectionTabSchema = z.object({
49
52
  });
50
53
 
51
54
  const CollectionUiSchema = z.object({
55
+ icon: z.custom<LucideIconName>((val) => typeof val === "string").optional(),
52
56
  tabs: z.array(CollectionTabSchema).optional(),
53
57
  }).optional();
54
58
 
@@ -64,6 +68,7 @@ export const NormalCollectionConfigSchema = z.object({
64
68
  virtual: z.literal(false).optional(),
65
69
  category: z.string().optional(),
66
70
  singleton: z.boolean().optional(),
71
+ junction: z.boolean().optional(),
67
72
  hooks: CollectionHooksSchema,
68
73
  indexes: CollectionIndexesSchema.optional(),
69
74
  fields: CollectionFieldsSchema,
@@ -6,12 +6,31 @@ export const RelationCollectionFieldSchema = z.object({
6
6
  field: z.string(),
7
7
  });
8
8
 
9
- // Regular relation schema
10
- const RelationSchema = z.object({
9
+ // Regular (FK) relation schema
10
+ export const RegularRelationSchema = z.object({
11
11
  from: RelationCollectionFieldSchema,
12
12
  to: RelationCollectionFieldSchema,
13
13
  });
14
14
 
15
+ export type RegularRelation = z.infer<typeof RegularRelationSchema>;
16
+
17
+ // Polymorphic relation schema
18
+ // from.collection_field holds the target collection name per row
19
+ // from.id_field holds the target record id per row
20
+ // to lists the allowed target collections
21
+ const PolymorphicRelationSchema = z.object({
22
+ type: z.literal("polymorphic"),
23
+ from: z.object({
24
+ collection: z.string(),
25
+ virtual_field: z.string(),
26
+ collection_field: z.string(),
27
+ id_field: z.string(),
28
+ }),
29
+ to: z.array(z.string()),
30
+ });
31
+
15
32
  // Union of all relation types
16
- export const RelationsConfigSchema = z.array(RelationSchema);
33
+ export const RelationsConfigSchema = z.array(
34
+ z.union([RegularRelationSchema, PolymorphicRelationSchema]),
35
+ );
17
36
  export type RelationsConfig = z.infer<typeof RelationsConfigSchema>;
@@ -2,7 +2,7 @@ export { DatabaseDriver } from "./DatabaseDriver.ts";
2
2
  export type { FindAllResult, MassReturn } from "./DatabaseDriver.ts";
3
3
  export type { ApiCollectionAction } from "./apiSchema.ts";
4
4
  export type { Config, WebConfig } from "./config/config.ts";
5
- export type { RelationsConfig } from "./config/relations.ts";
5
+ export type { RelationsConfig, RegularRelation } from "./config/relations.ts";
6
6
  export type {
7
7
  CollectionConfig,
8
8
  NormalCollectionConfig,
@@ -24,10 +24,14 @@ export {
24
24
  filterSchema,
25
25
  } from "./filterSchema.ts";
26
26
  export type {
27
- FindAllParamsInput,
27
+ FindAllParams,
28
28
  FindAllParamsOutput,
29
+ FindOneParams,
30
+ ChildParams,
31
+ CreateChildren,
32
+ UpdateChildren,
29
33
  } from "./collectionServiceSchema.ts";
30
- export { findAllParamsSchema } from "./collectionServiceSchema.ts";
34
+ export { findAllParamsSchema, findOneParamsSchema, childParamsSchema } from "./collectionServiceSchema.ts";
31
35
  export { CollectionConfigSchema } from "./config/collectionsConfig.ts";
32
36
  export type { CollectionControllers } from "./CollectionControllers.ts";
33
37
  export type {
@@ -1,5 +1,17 @@
1
1
  import type { Workflow } from "../../WorkflowSystem.ts";
2
2
  import { Lobb } from "../../../Lobb.ts";
3
+ import { LobbError } from "../../../LobbError.ts";
4
+
5
+ async function runHook(hook: Function, args: any, label: string) {
6
+ try {
7
+ return await hook(args);
8
+ } catch (err: any) {
9
+ throw new LobbError({
10
+ code: "BAD_REQUEST",
11
+ message: `Hook "${label}" threw an error: ${err?.message ?? String(err)}`,
12
+ });
13
+ }
14
+ }
3
15
 
4
16
  async function runFieldHooks(
5
17
  hookName: "beforeCreate" | "beforeUpdate" | "afterCreate" | "afterUpdate",
@@ -10,7 +22,11 @@ async function runFieldHooks(
10
22
  for (const [fieldName, fieldConfig] of Object.entries(fields)) {
11
23
  const hook = "hooks" in fieldConfig ? fieldConfig.hooks?.[hookName] : undefined;
12
24
  if (!hook) continue;
13
- const result = await hook({ data: input.data, context: input.context });
25
+ const result = await runHook(
26
+ hook,
27
+ { data: input.data, context: input.context },
28
+ `${input.collectionName}.${fieldName}.${hookName}`,
29
+ );
14
30
  if (result !== undefined) {
15
31
  input.data[fieldName] = result;
16
32
  }
@@ -24,7 +40,9 @@ export const hooksWorkflows: Workflow[] = [
24
40
  handler: async (input) => {
25
41
  if (Lobb.instance.configManager.isCollectionVirtual(input.collectionName)) return input;
26
42
  const hooks = Lobb.instance.configManager.getNormalCollection(input.collectionName).hooks;
27
- await hooks?.beforeCreate?.({ data: input.data, context: input.context });
43
+ if (hooks?.beforeCreate) {
44
+ await runHook(hooks.beforeCreate, { data: input.data, context: input.context }, `${input.collectionName}.beforeCreate`);
45
+ }
28
46
  await runFieldHooks("beforeCreate", input);
29
47
  return input;
30
48
  },
@@ -35,7 +53,9 @@ export const hooksWorkflows: Workflow[] = [
35
53
  handler: async (input) => {
36
54
  if (Lobb.instance.configManager.isCollectionVirtual(input.collectionName)) return input;
37
55
  const hooks = Lobb.instance.configManager.getNormalCollection(input.collectionName).hooks;
38
- await hooks?.beforeUpdate?.({ data: input.data, context: input.context });
56
+ if (hooks?.beforeUpdate) {
57
+ await runHook(hooks.beforeUpdate, { data: input.data, context: input.context }, `${input.collectionName}.beforeUpdate`);
58
+ }
39
59
  await runFieldHooks("beforeUpdate", input);
40
60
  return input;
41
61
  },
@@ -46,7 +66,9 @@ export const hooksWorkflows: Workflow[] = [
46
66
  handler: async (input) => {
47
67
  if (Lobb.instance.configManager.isCollectionVirtual(input.collectionName)) return input;
48
68
  const hooks = Lobb.instance.configManager.getNormalCollection(input.collectionName).hooks;
49
- await hooks?.afterCreate?.({ data: input.data, context: input.context });
69
+ if (hooks?.afterCreate) {
70
+ await runHook(hooks.afterCreate, { data: input.data, context: input.context }, `${input.collectionName}.afterCreate`);
71
+ }
50
72
  await runFieldHooks("afterCreate", input);
51
73
  return input;
52
74
  },
@@ -57,7 +79,9 @@ export const hooksWorkflows: Workflow[] = [
57
79
  handler: async (input) => {
58
80
  if (Lobb.instance.configManager.isCollectionVirtual(input.collectionName)) return input;
59
81
  const hooks = Lobb.instance.configManager.getNormalCollection(input.collectionName).hooks;
60
- await hooks?.afterUpdate?.({ data: input.data, context: input.context });
82
+ if (hooks?.afterUpdate) {
83
+ await runHook(hooks.afterUpdate, { data: input.data, context: input.context }, `${input.collectionName}.afterUpdate`);
84
+ }
61
85
  await runFieldHooks("afterUpdate", input);
62
86
  return input;
63
87
  },