@lobb-js/core 0.18.0 → 0.20.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@lobb-js/core",
3
3
  "license": "UNLICENSED",
4
- "version": "0.18.0",
4
+ "version": "0.20.0",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -226,11 +226,15 @@ export class CollectionStore {
226
226
  transaction: client,
227
227
  },
228
228
  );
229
- data = await this.dbDriver.createOne(
230
- collectionName,
231
- this.filterPayload(collectionName, result.data),
232
- client,
233
- );
229
+
230
+ if (!Lobb.instance.configManager.isCollectionVirtual(collectionName)) {
231
+ data = await this.dbDriver.createOne(
232
+ collectionName,
233
+ this.filterPayload(collectionName, result.data),
234
+ client,
235
+ );
236
+ }
237
+
234
238
  data = (await Lobb.instance.eventSystem.emit(
235
239
  `core.store.createOne`,
236
240
  {
@@ -8,6 +8,7 @@ export function getCollectionDocumentSchema(
8
8
  const collection = Lobb.instance.configManager.getCollection(
9
9
  collectionName,
10
10
  );
11
+ if (collection.virtual) return z.object({}).passthrough();
11
12
  const fieldNames = Object.keys(collection.fields);
12
13
 
13
14
  const properties: Record<string, any> = {};
@@ -64,7 +65,7 @@ export function getColFieldPropSchema(
64
65
  );
65
66
  }
66
67
 
67
- const requiredField = Boolean(field.validators?.required);
68
+ const requiredField = Boolean("validators" in field && field.validators?.required);
68
69
  if (!requiredField) {
69
70
  fieldSchema = fieldSchema.optional();
70
71
  }
@@ -40,6 +40,10 @@ export class MetaService {
40
40
  collections[collectionName].singleton = Lobb.instance.configManager
41
41
  .isCollectionSingleton(collectionName);
42
42
 
43
+ // is collection virtual
44
+ collections[collectionName].virtual = Lobb.instance.configManager
45
+ .isCollectionVirtual(collectionName);
46
+
43
47
  // filling the extension property
44
48
  collections[collectionName].category = collection.category;
45
49
  collections[collectionName].owner = Lobb.instance.utils
@@ -64,12 +68,15 @@ export class MetaService {
64
68
  field.label = fieldName;
65
69
  field.key = fieldName;
66
70
  field.enum = "enum" in fieldConfig ? fieldConfig.enum : undefined;
67
- field.validators = fieldConfig.validators;
68
- field.pre_processors = fieldConfig.pre_processors;
71
+ field.validators = "validators" in fieldConfig ? fieldConfig.validators : undefined;
72
+ field.pre_processors = "pre_processors" in fieldConfig ? fieldConfig.pre_processors : undefined;
69
73
  field.ui = fieldConfig.ui;
70
74
 
71
75
  collections[collectionName].fields[fieldName] = field;
72
76
  }
77
+
78
+ // filling the collection ui
79
+ collections[collectionName].ui = collection.ui;
73
80
  }
74
81
 
75
82
  return collections;
@@ -3,6 +3,7 @@ import {
3
3
  type CollectionConfig,
4
4
  CollectionConfigSchema,
5
5
  type CollectionsConfig,
6
+ type NormalCollectionConfig,
6
7
  } from "../types/index.ts";
7
8
  import type { CollectionField } from "../types/index.ts";
8
9
  import type { Extension } from "../types/index.ts";
@@ -47,16 +48,17 @@ export class ConfigManager {
47
48
  this.config.collections,
48
49
  )
49
50
  ) {
51
+ if (collectionValue.virtual) continue;
50
52
  for (
51
53
  const [fieldName, fieldValue] of Object.entries(
52
- this.config.collections[collectionName].fields,
54
+ (this.config.collections[collectionName] as any).fields,
53
55
  )
54
56
  ) {
55
57
  if (fieldValue.type === "integer" && fieldValue.references) {
56
58
  if (!this.config.collections[fieldValue.references.collection]) {
57
59
  throw new LobbError({
58
60
  code: "INTERNAL_SERVER_ERROR",
59
- message: `Field (${collectionName}.${fieldName}) references collection (${fieldValue.references.collection}) which does not exist`,
61
+ message: `"${collectionName}.${fieldName}" references "${fieldValue.references.collection}" which is not defined in your collections.`,
60
62
  });
61
63
  }
62
64
  if (!this.config.relations) {
@@ -82,6 +84,12 @@ export class ConfigManager {
82
84
 
83
85
  // Iterate over the collections and map each field to only include the 'type' property
84
86
  for (const collectionName in collections) {
87
+ // Virtual collections have no DB table — skip them entirely
88
+ if (collections[collectionName].virtual) {
89
+ delete collections[collectionName];
90
+ continue;
91
+ }
92
+
85
93
  // Keep only the 'indexes' and 'fields' properties in a collection
86
94
  collections[collectionName] = {
87
95
  indexes: collections[collectionName].indexes,
@@ -131,19 +139,30 @@ export class ConfigManager {
131
139
  return collection;
132
140
  }
133
141
 
142
+ public getNormalCollection(collectionName: string): NormalCollectionConfig {
143
+ const collection = this.getCollection(collectionName);
144
+ if (collection.virtual) {
145
+ throw new LobbError({
146
+ code: "INTERNAL_SERVER_ERROR",
147
+ message: `The (${collectionName}) collection is virtual and has no fields`,
148
+ });
149
+ }
150
+ return collection;
151
+ }
152
+
134
153
  public getFieldNames(collectionName: string) {
135
- return Object.keys(
136
- this.getCollection(collectionName).fields,
137
- );
154
+ const collection = this.getCollection(collectionName);
155
+ if (collection.virtual) return [];
156
+ return Object.keys(collection.fields);
138
157
  }
139
158
 
140
159
  public fieldExists(
141
160
  fieldName: string,
142
161
  collectionName: string,
143
162
  ): boolean {
144
- return Boolean(
145
- this.getCollection(collectionName).fields[fieldName],
146
- );
163
+ const collection = this.getCollection(collectionName);
164
+ if (collection.virtual) return false;
165
+ return Boolean(collection.fields[fieldName]);
147
166
  }
148
167
 
149
168
  public collectionExists(collectionName: string): boolean {
@@ -154,7 +173,8 @@ export class ConfigManager {
154
173
  fieldName: string,
155
174
  collectionName: string,
156
175
  ): CollectionField {
157
- const field = this.getCollection(collectionName).fields[fieldName];
176
+ const collection = this.getCollection(collectionName);
177
+ const field = collection.virtual ? undefined : collection.fields[fieldName];
158
178
  if (!field) {
159
179
  throw new LobbError({
160
180
  code: "BAD_REQUEST",
@@ -205,7 +225,11 @@ export class ConfigManager {
205
225
  const virtualCollections: CollectionsConfig = {};
206
226
  const collections = this.getCollections();
207
227
  for (const [collectionName, collection] of Object.entries(collections)) {
208
- const collectionFieldsNames = Object.keys(collection.fields);
228
+ if (collection.virtual) {
229
+ virtualCollections[collectionName] = collection;
230
+ continue;
231
+ }
232
+ const collectionFieldsNames = Object.keys(collection.fields ?? {});
209
233
  if (collectionFieldsNames.length <= 1) {
210
234
  virtualCollections[collectionName] = collection;
211
235
  }
@@ -253,6 +277,12 @@ export class ConfigManager {
253
277
  }
254
278
 
255
279
  public isCollectionSingleton(collectionName: string): boolean {
256
- return Boolean(this.config.collections[collectionName]?.singleton);
280
+ const collection = this.config.collections[collectionName];
281
+ if (!collection || collection.virtual) return false;
282
+ return Boolean(collection.singleton);
283
+ }
284
+
285
+ public isCollectionVirtual(collectionName: string): boolean {
286
+ return Boolean(this.config.collections[collectionName]?.virtual);
257
287
  }
258
288
  }
@@ -37,7 +37,7 @@ function validateDBNamesAreSnakeCase(
37
37
  );
38
38
  }
39
39
  for (
40
- const [fieldName, _] of Object.entries(collectionValue.fields)
40
+ const [fieldName, _] of Object.entries(collectionValue.virtual ? {} : collectionValue.fields)
41
41
  ) {
42
42
  if (!isSnakeCase(fieldName)) {
43
43
  throw new Error(
@@ -36,21 +36,6 @@ export class DatabaseSyncManager {
36
36
  ) {
37
37
  if (!dbSchemaDiff.length) return;
38
38
 
39
- if (!forceSync) {
40
- console.error(
41
- "These are the differences between the config schema and the database schema",
42
- );
43
- console.error(
44
- "You should add migrations to match the collection schema with the database schema",
45
- );
46
- console.error(dbSchemaDiff);
47
- throw new LobbError({
48
- code: "INTERNAL_SERVER_ERROR",
49
- message:
50
- "The schema of the configuration collection does not align with the actual schema of the connected database.",
51
- });
52
- }
53
-
54
39
  // just-diff may produce deep paths (length > 3) for sub-field changes within
55
40
  // an existing index (e.g. renaming a column the index covers). Convert those
56
41
  // into explicit drop+recreate (replace) ops, deduplicating by index name.
@@ -84,6 +69,8 @@ export class DatabaseSyncManager {
84
69
  };
85
70
  normalizedDiff.sort((a, b) => opOrder(a) - opOrder(b));
86
71
 
72
+ await this.assertNoDataLoss(normalizedDiff, forceSync);
73
+
87
74
  for (let index = 0; index < normalizedDiff.length; index++) {
88
75
  const change = normalizedDiff[index];
89
76
  if (change.path.length === 1 && change.op === "add") {
@@ -114,7 +101,7 @@ export class DatabaseSyncManager {
114
101
  } else if (change.path[1] === "fields" && change.op === "replace") {
115
102
  const collectionName = change.path[0] as string;
116
103
  const fieldName = change.path[2] as string;
117
- const fieldConfig = Lobb.instance.configManager.getCollection(collectionName).fields[fieldName];
104
+ const fieldConfig = Lobb.instance.configManager.getNormalCollection(collectionName).fields[fieldName];
118
105
  await this.dbDriver.alterField(collectionName, fieldName, fieldConfig);
119
106
  } else if (change.path[1] === "indexes" && change.op === "add") {
120
107
  const collectionName = change.path[0] as string;
@@ -131,10 +118,10 @@ export class DatabaseSyncManager {
131
118
  } else if (change.path[1] === "indexes" && change.op === "replace") {
132
119
  const collectionName = change.path[0] as string;
133
120
  const indexName = change.path[2] as string;
134
- const collectionConfig = Lobb.instance.configManager.getCollection(
121
+ const collectionConfig = Lobb.instance.configManager.getNormalCollection(
135
122
  collectionName,
136
123
  );
137
- const indexes = collectionConfig.indexes;
124
+ const indexes = collectionConfig.indexes ?? {};
138
125
  const index = indexes[indexName];
139
126
  await this.dbDriver.dropIndex(collectionName, indexName);
140
127
  await this.dbDriver.createIndex(
@@ -152,6 +139,59 @@ export class DatabaseSyncManager {
152
139
  }
153
140
  }
154
141
 
142
+ private async assertNoDataLoss(
143
+ diff: Array<{ op: string; path: (string | number)[]; value: unknown }>,
144
+ forceSync: boolean,
145
+ ) {
146
+ if (forceSync) return;
147
+ const destructiveOps = diff.filter(
148
+ (change) =>
149
+ (change.path.length === 1 && change.op === "remove") ||
150
+ (change.path[1] === "fields" && change.op === "remove"),
151
+ );
152
+
153
+ if (destructiveOps.length === 0) return;
154
+
155
+ const blocked: string[] = [];
156
+
157
+ for (const change of destructiveOps) {
158
+ const collectionName = change.path[0] as string;
159
+
160
+ if (change.path.length === 1 && change.op === "remove") {
161
+ const { meta } = await this.dbDriver.findAll(collectionName, { limit: 1, offset: 0, fields: "id" });
162
+ if (meta.totalCount > 0) {
163
+ blocked.push(
164
+ `Table "${collectionName}" has ${meta.totalCount} row(s) and cannot be dropped automatically. Write a migration to handle the data first.`,
165
+ );
166
+ }
167
+ } else if (change.path[1] === "fields" && change.op === "remove") {
168
+ const fieldName = change.path[2] as string;
169
+ const { meta } = await this.dbDriver.findAll(collectionName, {
170
+ limit: 1,
171
+ offset: 0,
172
+ fields: "id",
173
+ filter: { [fieldName]: { $ne: null } },
174
+ });
175
+ if (meta.totalCount > 0) {
176
+ blocked.push(
177
+ `Column "${collectionName}.${fieldName}" has ${meta.totalCount} non-null row(s) and cannot be dropped automatically. Write a migration to handle the data first.`,
178
+ );
179
+ }
180
+ }
181
+ }
182
+
183
+ if (blocked.length > 0) {
184
+ console.error("Blocked destructive schema changes detected:");
185
+ for (const msg of blocked) console.error(` • ${msg}`);
186
+ throw new LobbError({
187
+ code: "INTERNAL_SERVER_ERROR",
188
+ message:
189
+ "Refusing to apply destructive schema changes on tables/columns that contain data. " +
190
+ "Write explicit migrations to handle the data, then re-run.",
191
+ });
192
+ }
193
+ }
194
+
155
195
  private async getDbDifferences(specificColleciton?: string) {
156
196
  let dbSchema = await this.dbDriver.getDbSchema();
157
197
  let configDbSchema = Lobb.instance.configManager.getDbSchema();
@@ -1,7 +1,7 @@
1
1
  import format from "pg-format";
2
2
  import type { FindAllParamsOutput } from "../../../types/index.ts";
3
3
  import type {
4
- CollectionConfig,
4
+ NormalCollectionConfig,
5
5
  CollectionIndex,
6
6
  CollectionIndexes,
7
7
  CollectionsConfig,
@@ -109,7 +109,7 @@ export class PGDriver extends DatabaseDriver {
109
109
 
110
110
  public async createCollection(
111
111
  collectionName: string,
112
- collectionConfig: CollectionConfig,
112
+ collectionConfig: NormalCollectionConfig,
113
113
  ) {
114
114
  const collectionEntries = Object.entries(collectionConfig.fields);
115
115
  const createTableBodySql: string = collectionEntries.map(
@@ -127,7 +127,7 @@ export class PGDriver extends DatabaseDriver {
127
127
  await this.runQuery(client, query);
128
128
 
129
129
  // creating the indexes
130
- const indexes = collectionConfig.indexes;
130
+ const indexes = collectionConfig.indexes ?? {};
131
131
  for (
132
132
  const [indexName, value] of Object.entries(indexes)
133
133
  ) {
@@ -423,7 +423,9 @@ export class PGDriver extends DatabaseDriver {
423
423
  }
424
424
 
425
425
  private stripVirtualFields(collectionName: string, data: any): any {
426
- const fields = Lobb.instance.configManager.getCollection(collectionName).fields;
426
+ const collection = Lobb.instance.configManager.getCollection(collectionName);
427
+ if (collection.virtual) return data;
428
+ const fields = collection.fields;
427
429
  const result = { ...data };
428
430
  for (const key of Object.keys(result)) {
429
431
  if (fields[key]?.virtual) delete result[key];
@@ -82,7 +82,7 @@ export class QueryBuilder {
82
82
  const mainFields = fields.filter((f) => !f.includes("."));
83
83
  mainFields.forEach((field) => {
84
84
  if (field === "*") {
85
- const collectionFields = Lobb.instance.configManager.getCollection(this.collectionName).fields;
85
+ const collectionFields = Lobb.instance.configManager.getNormalCollection(this.collectionName).fields;
86
86
  const currentCollectionFields = Object.entries(collectionFields)
87
87
  .filter(([, fieldConfig]) => !fieldConfig.virtual)
88
88
  .map(([fieldName]) => fieldName);
@@ -130,7 +130,7 @@ export class QueryBuilder {
130
130
  : foreignFieldCollection;
131
131
 
132
132
  // Exclude virtual fields — they have no DB column in the related table
133
- const foreignCollectionFields = Lobb.instance.configManager.getCollection(foreignFieldCollection).fields;
133
+ const foreignCollectionFields = Lobb.instance.configManager.getNormalCollection(foreignFieldCollection).fields;
134
134
  const nonVirtualNestedFields = nestedFields.filter((f) => !foreignCollectionFields[f]?.virtual);
135
135
 
136
136
  // Build JSON safely: only if related row exists
@@ -41,15 +41,40 @@ export const CollectionFieldsSchema = z.intersection(
41
41
  z.record(CollectionFieldSchema),
42
42
  );
43
43
 
44
- // CollectionConfig
45
- export const CollectionConfigSchema = z.object({
44
+ const CollectionTabSchema = z.object({
45
+ id: z.string(),
46
+ label: z.string(),
47
+ filter: z.record(z.unknown()).optional(),
48
+ default: z.boolean().optional(),
49
+ });
50
+
51
+ const CollectionUiSchema = z.object({
52
+ tabs: z.array(CollectionTabSchema).optional(),
53
+ }).optional();
54
+
55
+ // Virtual collection — no DB table, no fields, just an API endpoint for workflows to intercept
56
+ export const VirtualCollectionConfigSchema = z.object({
57
+ virtual: z.literal(true),
58
+ category: z.string().optional(),
59
+ ui: CollectionUiSchema,
60
+ });
61
+
62
+ // Normal collection — has fields (required) and optional indexes
63
+ export const NormalCollectionConfigSchema = z.object({
64
+ virtual: z.literal(false).optional(),
46
65
  category: z.string().optional(),
47
66
  singleton: z.boolean().optional(),
48
67
  hooks: CollectionHooksSchema,
49
- indexes: CollectionIndexesSchema,
68
+ indexes: CollectionIndexesSchema.optional(),
50
69
  fields: CollectionFieldsSchema,
70
+ ui: CollectionUiSchema,
51
71
  });
52
72
 
73
+ export const CollectionConfigSchema = z.union([
74
+ VirtualCollectionConfigSchema,
75
+ NormalCollectionConfigSchema,
76
+ ]);
77
+
53
78
  export const CollectionsConfigSchema = z.record(CollectionConfigSchema);
54
79
 
55
80
  // Inferred Types
@@ -59,5 +84,7 @@ export type CollectionIndex = z.infer<typeof CollectionIndexSchema>;
59
84
  export type CollectionIndexes = z.infer<typeof CollectionIndexesSchema>;
60
85
  export type CollectionFields = z.infer<typeof CollectionFieldsSchema>;
61
86
  export type CollectionFieldsWithoutId = Omit<CollectionFields, "id">;
87
+ export type VirtualCollectionConfig = z.infer<typeof VirtualCollectionConfigSchema>;
88
+ export type NormalCollectionConfig = z.infer<typeof NormalCollectionConfigSchema>;
62
89
  export type CollectionConfig = z.infer<typeof CollectionConfigSchema>;
63
90
  export type CollectionsConfig = z.infer<typeof CollectionsConfigSchema>;
@@ -5,6 +5,7 @@ export type { Config, WebConfig } from "./config/config.ts";
5
5
  export type { RelationsConfig } from "./config/relations.ts";
6
6
  export type {
7
7
  CollectionConfig,
8
+ NormalCollectionConfig,
8
9
  CollectionIndex,
9
10
  CollectionIndexes,
10
11
  CollectionsConfig,
@@ -2,7 +2,8 @@ import type { Workflow } from "../../WorkflowSystem.ts";
2
2
  import { Lobb } from "../../../Lobb.ts";
3
3
 
4
4
  async function applyDefaults(input: any) {
5
- const fields = Lobb.instance.configManager.getCollection(input.collectionName).fields;
5
+ if (Lobb.instance.configManager.isCollectionVirtual(input.collectionName)) return input;
6
+ const fields = Lobb.instance.configManager.getNormalCollection(input.collectionName).fields;
6
7
  const data = input.data;
7
8
 
8
9
  for (const [fieldName, fieldConfig] of Object.entries(fields) as [string, any][]) {
@@ -3,7 +3,8 @@ import { Lobb } from "../../../Lobb.ts";
3
3
  import { LobbError } from "../../../LobbError.ts";
4
4
 
5
5
  async function runEnumCheck(input: any, processAllFields: boolean) {
6
- const fields = Lobb.instance.configManager.getCollection(input.collectionName).fields;
6
+ if (Lobb.instance.configManager.isCollectionVirtual(input.collectionName)) return input;
7
+ const fields = Lobb.instance.configManager.getNormalCollection(input.collectionName).fields;
7
8
  const data = input.data;
8
9
  const errors: Record<string, string[]> = {};
9
10
 
@@ -5,9 +5,10 @@ async function runFieldHooks(
5
5
  hookName: "beforeCreate" | "beforeUpdate" | "afterCreate" | "afterUpdate",
6
6
  input: any,
7
7
  ) {
8
- const fields = Lobb.instance.configManager.getCollection(input.collectionName).fields;
8
+ if (Lobb.instance.configManager.isCollectionVirtual(input.collectionName)) return;
9
+ const fields = Lobb.instance.configManager.getNormalCollection(input.collectionName).fields;
9
10
  for (const [fieldName, fieldConfig] of Object.entries(fields)) {
10
- const hook = fieldConfig.hooks?.[hookName];
11
+ const hook = "hooks" in fieldConfig ? fieldConfig.hooks?.[hookName] : undefined;
11
12
  if (!hook) continue;
12
13
  const result = await hook({ data: input.data, context: input.context });
13
14
  if (result !== undefined) {
@@ -21,7 +22,8 @@ export const hooksWorkflows: Workflow[] = [
21
22
  name: "core_hooksBeforeCreate",
22
23
  eventName: "core.store.preCreateOne",
23
24
  handler: async (input) => {
24
- const hooks = Lobb.instance.configManager.getCollection(input.collectionName).hooks;
25
+ if (Lobb.instance.configManager.isCollectionVirtual(input.collectionName)) return input;
26
+ const hooks = Lobb.instance.configManager.getNormalCollection(input.collectionName).hooks;
25
27
  await hooks?.beforeCreate?.({ data: input.data, context: input.context });
26
28
  await runFieldHooks("beforeCreate", input);
27
29
  return input;
@@ -31,7 +33,8 @@ export const hooksWorkflows: Workflow[] = [
31
33
  name: "core_hooksBeforeUpdate",
32
34
  eventName: "core.store.preUpdateOne",
33
35
  handler: async (input) => {
34
- const hooks = Lobb.instance.configManager.getCollection(input.collectionName).hooks;
36
+ if (Lobb.instance.configManager.isCollectionVirtual(input.collectionName)) return input;
37
+ const hooks = Lobb.instance.configManager.getNormalCollection(input.collectionName).hooks;
35
38
  await hooks?.beforeUpdate?.({ data: input.data, context: input.context });
36
39
  await runFieldHooks("beforeUpdate", input);
37
40
  return input;
@@ -41,7 +44,8 @@ export const hooksWorkflows: Workflow[] = [
41
44
  name: "core_hooksAfterCreate",
42
45
  eventName: "core.store.createOne",
43
46
  handler: async (input) => {
44
- const hooks = Lobb.instance.configManager.getCollection(input.collectionName).hooks;
47
+ if (Lobb.instance.configManager.isCollectionVirtual(input.collectionName)) return input;
48
+ const hooks = Lobb.instance.configManager.getNormalCollection(input.collectionName).hooks;
45
49
  await hooks?.afterCreate?.({ data: input.data, context: input.context });
46
50
  await runFieldHooks("afterCreate", input);
47
51
  return input;
@@ -51,7 +55,8 @@ export const hooksWorkflows: Workflow[] = [
51
55
  name: "core_hooksAfterUpdate",
52
56
  eventName: "core.store.updateOne",
53
57
  handler: async (input) => {
54
- const hooks = Lobb.instance.configManager.getCollection(input.collectionName).hooks;
58
+ if (Lobb.instance.configManager.isCollectionVirtual(input.collectionName)) return input;
59
+ const hooks = Lobb.instance.configManager.getNormalCollection(input.collectionName).hooks;
55
60
  await hooks?.afterUpdate?.({ data: input.data, context: input.context });
56
61
  await runFieldHooks("afterUpdate", input);
57
62
  return input;
@@ -9,7 +9,7 @@ export async function processor(
9
9
  processAllFields: boolean,
10
10
  ) {
11
11
  const fieldsSchema =
12
- Lobb.instance.configManager.getCollection(input.collectionName).fields;
12
+ Lobb.instance.configManager.isCollectionVirtual(input.collectionName) ? {} as any : Lobb.instance.configManager.getNormalCollection(input.collectionName).fields;
13
13
 
14
14
  input.data = await processPayloadWithSchema(
15
15
  type,
@@ -3,7 +3,8 @@ import { Lobb } from "../../../Lobb.ts";
3
3
  import { LobbError } from "../../../LobbError.ts";
4
4
 
5
5
  async function runRequiredCheck(input: any, processAllFields: boolean) {
6
- const fields = Lobb.instance.configManager.getCollection(input.collectionName).fields;
6
+ if (Lobb.instance.configManager.isCollectionVirtual(input.collectionName)) return input;
7
+ const fields = Lobb.instance.configManager.getNormalCollection(input.collectionName).fields;
7
8
  const data = input.data;
8
9
  const errors: Record<string, string[]> = {};
9
10
 
@@ -11,7 +12,7 @@ async function runRequiredCheck(input: any, processAllFields: boolean) {
11
12
 
12
13
  for (const fieldName of fieldNames) {
13
14
  const fieldConfig = fields[fieldName];
14
- if (!fieldConfig?.required) continue;
15
+ if (!fieldConfig || !("required" in fieldConfig) || !fieldConfig.required) continue;
15
16
 
16
17
  const value = data[fieldName];
17
18
  if (value === undefined || value === null || value === "") {
@@ -6,7 +6,7 @@ export async function validator(
6
6
  processAllFields: boolean = false,
7
7
  ) {
8
8
  const fieldsSchema =
9
- Lobb.instance.configManager.getCollection(input.collectionName).fields;
9
+ Lobb.instance.configManager.isCollectionVirtual(input.collectionName) ? {} as any : Lobb.instance.configManager.getNormalCollection(input.collectionName).fields;
10
10
 
11
11
  await validatePayloadWithSchema(
12
12
  input.data,
@@ -3,7 +3,8 @@ import { Lobb } from "../../../Lobb.ts";
3
3
  import { LobbError } from "../../../LobbError.ts";
4
4
 
5
5
  async function runFieldValidators(input: any, processAllFields: boolean) {
6
- const fields = Lobb.instance.configManager.getCollection(input.collectionName).fields;
6
+ if (Lobb.instance.configManager.isCollectionVirtual(input.collectionName)) return input;
7
+ const fields = Lobb.instance.configManager.getNormalCollection(input.collectionName).fields;
7
8
  const data = input.data;
8
9
  const errors: Record<string, string[]> = {};
9
10
 
@@ -11,7 +12,7 @@ async function runFieldValidators(input: any, processAllFields: boolean) {
11
12
 
12
13
  for (const fieldName of fieldNames) {
13
14
  const fieldConfig = fields[fieldName];
14
- if (!fieldConfig?.validator) continue;
15
+ if (!fieldConfig || !("validator" in fieldConfig) || !fieldConfig.validator) continue;
15
16
 
16
17
  const error = await fieldConfig.validator({
17
18
  value: data[fieldName],