@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 +1 -1
- package/src/api/collections/collectionStore.ts +9 -5
- package/src/api/collections/utils.ts +2 -1
- package/src/api/meta/service.ts +9 -2
- package/src/config/ConfigManager.ts +41 -11
- package/src/config/validations.ts +1 -1
- package/src/database/DatabaseSyncManager.ts +58 -18
- package/src/database/drivers/pgDriver/PGDriver.ts +6 -4
- package/src/database/drivers/pgDriver/QueryBuilder.ts +2 -2
- package/src/types/config/collectionsConfig.ts +30 -3
- package/src/types/index.ts +1 -0
- package/src/workflows/coreWorkflows/processors/defaultWorkflows.ts +2 -1
- package/src/workflows/coreWorkflows/processors/enumWorkflows.ts +2 -1
- package/src/workflows/coreWorkflows/processors/hooksWorkflows.ts +11 -6
- package/src/workflows/coreWorkflows/processors/processors/processor.ts +1 -1
- package/src/workflows/coreWorkflows/processors/requiredWorkflows.ts +3 -2
- package/src/workflows/coreWorkflows/processors/validator/validator.ts +1 -1
- package/src/workflows/coreWorkflows/processors/validatorWorkflows.ts +3 -2
package/package.json
CHANGED
|
@@ -226,11 +226,15 @@ export class CollectionStore {
|
|
|
226
226
|
transaction: client,
|
|
227
227
|
},
|
|
228
228
|
);
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
this.
|
|
232
|
-
|
|
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
|
}
|
package/src/api/meta/service.ts
CHANGED
|
@@ -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: `
|
|
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
|
-
|
|
136
|
-
|
|
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
|
-
|
|
145
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
45
|
-
|
|
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>;
|
package/src/types/index.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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
|
|
15
|
+
if (!fieldConfig || !("validator" in fieldConfig) || !fieldConfig.validator) continue;
|
|
15
16
|
|
|
16
17
|
const error = await fieldConfig.validator({
|
|
17
18
|
value: data[fieldName],
|