@lobb-js/core 0.22.1 → 0.24.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/Lobb.ts +2 -0
- package/src/api/collections/CollectionControllers.ts +17 -0
- package/src/api/collections/collectionStore.ts +682 -24
- package/src/api/meta/service.ts +1 -0
- package/src/config/ConfigManager.ts +154 -6
- package/src/config/validations.ts +13 -5
- package/src/database/drivers/pgDriver/QueryBuilder.ts +3 -141
- package/src/index.ts +1 -1
- package/src/types/collectionServiceSchema.ts +61 -14
- package/src/types/config/collectionFields.ts +19 -0
- package/src/types/config/collectionsConfig.ts +5 -0
- package/src/types/config/relations.ts +22 -3
- package/src/types/index.ts +7 -3
- package/src/workflows/coreWorkflows/index.ts +2 -0
- package/src/workflows/coreWorkflows/processors/coerceAndValidateWorkflows.ts +108 -0
- package/src/workflows/coreWorkflows/processors/hooksWorkflows.ts +29 -5
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { Workflow } from "../../WorkflowSystem.ts";
|
|
2
|
+
import { Lobb } from "../../../Lobb.ts";
|
|
3
|
+
import { LobbError } from "../../../LobbError.ts";
|
|
4
|
+
|
|
5
|
+
async function coerceAndValidate(input: any) {
|
|
6
|
+
if (Lobb.instance.configManager.isCollectionVirtual(input.collectionName)) return input;
|
|
7
|
+
const fields = Lobb.instance.configManager.getNormalCollection(input.collectionName).fields;
|
|
8
|
+
const data = input.data;
|
|
9
|
+
const errors: Record<string, string[]> = {};
|
|
10
|
+
|
|
11
|
+
for (const [fieldName, fieldConfig] of Object.entries(fields) as [string, any][]) {
|
|
12
|
+
if (!(fieldName in data)) continue;
|
|
13
|
+
const value = data[fieldName];
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
if (value === null || value === undefined) continue;
|
|
17
|
+
|
|
18
|
+
if (fieldConfig.type === "string") {
|
|
19
|
+
if (typeof value !== "string") {
|
|
20
|
+
data[fieldName] = String(value);
|
|
21
|
+
}
|
|
22
|
+
} else if (fieldConfig.type === "text") {
|
|
23
|
+
if (typeof value !== "string") {
|
|
24
|
+
data[fieldName] = String(value);
|
|
25
|
+
}
|
|
26
|
+
} else if (fieldConfig.type === "integer") {
|
|
27
|
+
if (value === "") {
|
|
28
|
+
data[fieldName] = null;
|
|
29
|
+
} else {
|
|
30
|
+
const int = Number(value);
|
|
31
|
+
if (!Number.isFinite(int)) throw new Error(`${fieldName} must be a valid integer`);
|
|
32
|
+
data[fieldName] = Math.trunc(int);
|
|
33
|
+
}
|
|
34
|
+
} else if (fieldConfig.type === "long") {
|
|
35
|
+
if (value === "") {
|
|
36
|
+
data[fieldName] = null;
|
|
37
|
+
} else if (typeof value === "string" && !/^-?\d+$/.test(value.trim())) {
|
|
38
|
+
throw new Error(`${fieldName} must be a valid integer`);
|
|
39
|
+
}
|
|
40
|
+
} else if (fieldConfig.type === "float") {
|
|
41
|
+
if (value === "") {
|
|
42
|
+
data[fieldName] = null;
|
|
43
|
+
} else {
|
|
44
|
+
const float = Number(value);
|
|
45
|
+
if (!Number.isFinite(float)) throw new Error(`${fieldName} must be a valid number`);
|
|
46
|
+
data[fieldName] = float;
|
|
47
|
+
}
|
|
48
|
+
} else if (fieldConfig.type === "decimal") {
|
|
49
|
+
if (value === "") {
|
|
50
|
+
data[fieldName] = null;
|
|
51
|
+
} else if (typeof value === "string" && isNaN(Number(value))) {
|
|
52
|
+
throw new Error(`${fieldName} must be a valid decimal number`);
|
|
53
|
+
}
|
|
54
|
+
} else if (fieldConfig.type === "bool") {
|
|
55
|
+
if (value === "") {
|
|
56
|
+
data[fieldName] = null;
|
|
57
|
+
} else if (typeof value === "boolean") {
|
|
58
|
+
// already correct, nothing to do
|
|
59
|
+
} else if (typeof value === "string" && value.toLowerCase() === "true" || value === "1" || value === 1) {
|
|
60
|
+
data[fieldName] = true;
|
|
61
|
+
} else if (typeof value === "string" && value.toLowerCase() === "false" || value === "0" || value === 0) {
|
|
62
|
+
data[fieldName] = false;
|
|
63
|
+
} else {
|
|
64
|
+
throw new Error(`${fieldName} must be a valid boolean`);
|
|
65
|
+
}
|
|
66
|
+
} else if (fieldConfig.type === "date") {
|
|
67
|
+
if (value === "") {
|
|
68
|
+
data[fieldName] = null;
|
|
69
|
+
}
|
|
70
|
+
} else if (fieldConfig.type === "datetime") {
|
|
71
|
+
if (value === "") {
|
|
72
|
+
data[fieldName] = null;
|
|
73
|
+
}
|
|
74
|
+
} else if (fieldConfig.type === "time") {
|
|
75
|
+
if (value === "") {
|
|
76
|
+
data[fieldName] = null;
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
throw new Error(`coerceAndValidate: type "${fieldConfig.type}" is not implemented`);
|
|
80
|
+
}
|
|
81
|
+
} catch (e: any) {
|
|
82
|
+
errors[fieldName] = [e.message];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (Object.keys(errors).length) {
|
|
87
|
+
throw new LobbError({
|
|
88
|
+
code: "BAD_REQUEST",
|
|
89
|
+
message: "Validation failed. Please check your inputs and try again.",
|
|
90
|
+
details: errors,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return input;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export const coerceAndValidateWorkflows: Workflow[] = [
|
|
98
|
+
{
|
|
99
|
+
name: "core_coerceAndValidateOnCreate",
|
|
100
|
+
eventName: "core.store.preCreateOne",
|
|
101
|
+
handler: async (input) => coerceAndValidate(input),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "core_coerceAndValidateOnUpdate",
|
|
105
|
+
eventName: "core.store.preUpdateOne",
|
|
106
|
+
handler: async (input) => coerceAndValidate(input),
|
|
107
|
+
},
|
|
108
|
+
];
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
},
|