@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
package/src/api/meta/service.ts
CHANGED
|
@@ -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,64 @@ 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
|
+
|
|
78
|
+
public addUniqueIndexesFromFields() {
|
|
79
|
+
for (
|
|
80
|
+
const [collectionName, collectionValue] of Object.entries(
|
|
81
|
+
this.config.collections,
|
|
82
|
+
)
|
|
83
|
+
) {
|
|
84
|
+
if (collectionValue.virtual) continue;
|
|
85
|
+
for (
|
|
86
|
+
const [fieldName, fieldValue] of Object.entries(
|
|
87
|
+
(collectionValue as any).fields,
|
|
88
|
+
)
|
|
89
|
+
) {
|
|
90
|
+
if ((fieldValue as any).unique) {
|
|
91
|
+
const indexName = `${collectionName}_${fieldName}_unique_index`;
|
|
92
|
+
(collectionValue as any).indexes[indexName] = {
|
|
93
|
+
unique: true,
|
|
94
|
+
fields: {
|
|
95
|
+
[fieldName]: { order: "asc" },
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
45
103
|
public addRelationsFromIntegerRefrences() {
|
|
46
104
|
for (
|
|
47
105
|
const [collectionName, collectionValue] of Object.entries(
|
|
@@ -265,12 +323,102 @@ export class ConfigManager {
|
|
|
265
323
|
delete this.config.collections[collectionName];
|
|
266
324
|
}
|
|
267
325
|
|
|
268
|
-
public
|
|
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
|
+
);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
if (toRelation) {
|
|
395
|
+
return {
|
|
396
|
+
junctionCollection: junctionName,
|
|
397
|
+
parentFKField: fromRelation.from.field,
|
|
398
|
+
targetFKField: toRelation.from.field,
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
public getCollectionFKFields(collectionName: string): string[] {
|
|
407
|
+
if (!this.config.relations) return [];
|
|
408
|
+
return this.config.relations
|
|
409
|
+
.filter((rel): rel is RegularRelation => {
|
|
410
|
+
if ("type" in rel && rel.type === "polymorphic") return false;
|
|
411
|
+
return (rel as RegularRelation).from.collection === collectionName;
|
|
412
|
+
})
|
|
413
|
+
.map((rel) => rel.from.field);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
public getChildRelations(collectionName: string): RegularRelation[] {
|
|
269
417
|
if (this.config.relations) {
|
|
270
|
-
|
|
271
|
-
relation.
|
|
272
|
-
|
|
273
|
-
|
|
418
|
+
return this.config.relations.filter((relation): relation is RegularRelation => {
|
|
419
|
+
if ("type" in relation && relation.type === "polymorphic") return false;
|
|
420
|
+
return (relation as RegularRelation).to.collection === collectionName;
|
|
421
|
+
});
|
|
274
422
|
}
|
|
275
423
|
|
|
276
424
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
configManager.
|
|
17
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
4
|
-
|
|
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.
|
|
7
|
-
offset: z.
|
|
8
|
-
page: z.
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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: {
|
|
@@ -54,6 +59,7 @@ const FieldHooksSchema = z.object({
|
|
|
54
59
|
export const CollectionFieldBaseSchema = z.object({
|
|
55
60
|
virtual: z.never().optional(),
|
|
56
61
|
required: z.boolean().optional(),
|
|
62
|
+
unique: z.boolean().optional(),
|
|
57
63
|
hooks: FieldHooksSchema,
|
|
58
64
|
validator: z.custom<FieldValidatorFn>((val) => typeof val === "function").optional(),
|
|
59
65
|
ui: UiSchema.optional(),
|
|
@@ -163,6 +169,18 @@ export const CollectionTimeFieldSchema = z.union([
|
|
|
163
169
|
VirtualFieldSchema.extend({ type: z.literal("time") }),
|
|
164
170
|
]);
|
|
165
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
|
+
|
|
166
184
|
// Union type if you want a single schema for all
|
|
167
185
|
export const CollectionFieldSchema = z.union([
|
|
168
186
|
CollectionBoolFieldSchema,
|
|
@@ -175,6 +193,7 @@ export const CollectionFieldSchema = z.union([
|
|
|
175
193
|
CollectionStringFieldSchema,
|
|
176
194
|
CollectionTextFieldSchema,
|
|
177
195
|
CollectionTimeFieldSchema,
|
|
196
|
+
CollectionPolymorphicFieldSchema,
|
|
178
197
|
]);
|
|
179
198
|
|
|
180
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
|
|
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(
|
|
33
|
+
export const RelationsConfigSchema = z.array(
|
|
34
|
+
z.union([RegularRelationSchema, PolymorphicRelationSchema]),
|
|
35
|
+
);
|
|
17
36
|
export type RelationsConfig = z.infer<typeof RelationsConfigSchema>;
|
package/src/types/index.ts
CHANGED
|
@@ -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
|
-
|
|
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 {
|
|
@@ -6,11 +6,13 @@ import { validatorWorkflows } from "./processors/validatorWorkflows.ts";
|
|
|
6
6
|
import { requiredWorkflows } from "./processors/requiredWorkflows.ts";
|
|
7
7
|
import { defaultWorkflows } from "./processors/defaultWorkflows.ts";
|
|
8
8
|
import { enumWorkflows } from "./processors/enumWorkflows.ts";
|
|
9
|
+
import { coerceAndValidateWorkflows } from "./processors/coerceAndValidateWorkflows.ts";
|
|
9
10
|
import { coreWorkflowsCollectionWorkflows } from "./workflowsCollection/workflowsCollectionWorkflows.ts";
|
|
10
11
|
import { getQueryCoreWorkflows } from "./queryCoreWorkflows.ts";
|
|
11
12
|
|
|
12
13
|
export function getCoreWorkflows(): Array<Workflow> {
|
|
13
14
|
return [
|
|
15
|
+
...coerceAndValidateWorkflows,
|
|
14
16
|
...hooksWorkflows.filter((w) => w.name.startsWith("core_hooksBefore")),
|
|
15
17
|
...defaultWorkflows,
|
|
16
18
|
...requiredWorkflows,
|