@lobb-js/core 0.28.0 → 0.30.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 +2 -1
- package/src/api/collections/CollectionControllers.ts +9 -0
- package/src/api/collections/collectionStore.ts +76 -1
- package/src/config/ConfigManager.ts +10 -4
- package/src/extension/ExtensionSystem.ts +1 -0
- package/src/types/Extension.ts +2 -0
- package/src/workflows/coreWorkflows/index.ts +2 -0
- package/src/workflows/coreWorkflows/processors/dryRunWorkflows.ts +28 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lobb-js/core",
|
|
3
3
|
"license": "UNLICENSED",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.30.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"zod-to-ts": "^1.2.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
+
"lucide-svelte": "latest",
|
|
40
41
|
"@types/lodash": "^4.17.0",
|
|
41
42
|
"@types/nunjucks": "^3.2.6",
|
|
42
43
|
"@types/pg": "^8.11.0",
|
|
@@ -46,12 +46,15 @@ export const collectionControllers: CollectionControllers = {
|
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
const dryRun = c.req.query("dry_run") !== undefined;
|
|
50
|
+
|
|
49
51
|
const result = await Lobb.instance.collectionService.createOne({
|
|
50
52
|
collectionName,
|
|
51
53
|
context: c,
|
|
52
54
|
data: body.data,
|
|
53
55
|
children: body.children,
|
|
54
56
|
triggeredBy: "API",
|
|
57
|
+
dryRun,
|
|
55
58
|
});
|
|
56
59
|
|
|
57
60
|
return c.json(result, 201);
|
|
@@ -209,6 +212,8 @@ export const collectionControllers: CollectionControllers = {
|
|
|
209
212
|
}
|
|
210
213
|
}
|
|
211
214
|
|
|
215
|
+
const dryRun = c.req.query("dry_run") !== undefined;
|
|
216
|
+
|
|
212
217
|
const result = await Lobb.instance.collectionService.updateOne({
|
|
213
218
|
collectionName,
|
|
214
219
|
context: c,
|
|
@@ -216,6 +221,7 @@ export const collectionControllers: CollectionControllers = {
|
|
|
216
221
|
data: body.data,
|
|
217
222
|
children: body.children,
|
|
218
223
|
triggeredBy: "API",
|
|
224
|
+
dryRun,
|
|
219
225
|
});
|
|
220
226
|
return c.json(result, 200);
|
|
221
227
|
},
|
|
@@ -245,12 +251,15 @@ export const collectionControllers: CollectionControllers = {
|
|
|
245
251
|
}
|
|
246
252
|
}
|
|
247
253
|
|
|
254
|
+
const dryRun = c.req.query("dry_run") !== undefined;
|
|
255
|
+
|
|
248
256
|
const result = await Lobb.instance.collectionService.deleteOne({
|
|
249
257
|
collectionName,
|
|
250
258
|
context: c,
|
|
251
259
|
id,
|
|
252
260
|
force,
|
|
253
261
|
triggeredBy: "API",
|
|
262
|
+
dryRun,
|
|
254
263
|
});
|
|
255
264
|
|
|
256
265
|
return c.json(result);
|
|
@@ -41,6 +41,16 @@ export class CollectionStore {
|
|
|
41
41
|
context?: HonoContext;
|
|
42
42
|
client?: PoolClient;
|
|
43
43
|
}): Promise<ExposedManyServiceOutput> {
|
|
44
|
+
// Resolve children_of + parent_id into a regular filter before proceeding.
|
|
45
|
+
// This allows fetching related records (fk, m2m, polymorphic) without the caller
|
|
46
|
+
// needing to know the relation type or junction details.
|
|
47
|
+
if ((params as any)?.children_of !== undefined) {
|
|
48
|
+
const { children_of, parent_id, ...rest } = params as any;
|
|
49
|
+
const childFilter = await this.resolveChildrenOf(collectionName, children_of, parent_id);
|
|
50
|
+
if (childFilter === null) return { data: [], meta: { totalCount: 0 } };
|
|
51
|
+
params = { ...rest, filter: { ...rest.filter, ...childFilter } };
|
|
52
|
+
}
|
|
53
|
+
|
|
44
54
|
return await beginTransaction(
|
|
45
55
|
async (client) => {
|
|
46
56
|
const eventResult = await Lobb.instance.eventSystem.emit(
|
|
@@ -224,6 +234,7 @@ export class CollectionStore {
|
|
|
224
234
|
triggeredBy = "INTERNAL",
|
|
225
235
|
context,
|
|
226
236
|
client,
|
|
237
|
+
dryRun = false,
|
|
227
238
|
}: {
|
|
228
239
|
collectionName: string;
|
|
229
240
|
data: Record<string, any>;
|
|
@@ -231,6 +242,7 @@ export class CollectionStore {
|
|
|
231
242
|
triggeredBy?: TriggeredBy;
|
|
232
243
|
context?: HonoContext;
|
|
233
244
|
client?: PoolClient;
|
|
245
|
+
dryRun?: boolean;
|
|
234
246
|
}): Promise<ExposedServiceOutput> {
|
|
235
247
|
return await beginTransaction(
|
|
236
248
|
async (client) => {
|
|
@@ -248,6 +260,7 @@ export class CollectionStore {
|
|
|
248
260
|
context: context,
|
|
249
261
|
triggeredBy: triggeredBy,
|
|
250
262
|
transaction: client,
|
|
263
|
+
dryRun,
|
|
251
264
|
},
|
|
252
265
|
);
|
|
253
266
|
|
|
@@ -259,7 +272,6 @@ export class CollectionStore {
|
|
|
259
272
|
);
|
|
260
273
|
}
|
|
261
274
|
|
|
262
|
-
|
|
263
275
|
data = (await Lobb.instance.eventSystem.emit(
|
|
264
276
|
`core.store.createOne`,
|
|
265
277
|
{
|
|
@@ -291,6 +303,7 @@ export class CollectionStore {
|
|
|
291
303
|
triggeredBy = "INTERNAL",
|
|
292
304
|
context,
|
|
293
305
|
client,
|
|
306
|
+
dryRun = false,
|
|
294
307
|
}: {
|
|
295
308
|
collectionName: string;
|
|
296
309
|
id: string;
|
|
@@ -299,6 +312,7 @@ export class CollectionStore {
|
|
|
299
312
|
triggeredBy?: TriggeredBy;
|
|
300
313
|
context?: HonoContext;
|
|
301
314
|
client?: PoolClient;
|
|
315
|
+
dryRun?: boolean;
|
|
302
316
|
}): Promise<ExposedServiceOutput> {
|
|
303
317
|
return await beginTransaction(
|
|
304
318
|
async (client) => {
|
|
@@ -311,8 +325,10 @@ export class CollectionStore {
|
|
|
311
325
|
context: context,
|
|
312
326
|
triggeredBy: triggeredBy,
|
|
313
327
|
transaction: client,
|
|
328
|
+
dryRun,
|
|
314
329
|
},
|
|
315
330
|
);
|
|
331
|
+
|
|
316
332
|
data = await this.dbDriver.updateOne(
|
|
317
333
|
collectionName,
|
|
318
334
|
result.id,
|
|
@@ -351,6 +367,7 @@ export class CollectionStore {
|
|
|
351
367
|
triggeredBy = "INTERNAL",
|
|
352
368
|
context,
|
|
353
369
|
client,
|
|
370
|
+
dryRun = false,
|
|
354
371
|
}: {
|
|
355
372
|
collectionName: string;
|
|
356
373
|
id: string;
|
|
@@ -358,6 +375,7 @@ export class CollectionStore {
|
|
|
358
375
|
triggeredBy?: TriggeredBy;
|
|
359
376
|
context?: HonoContext;
|
|
360
377
|
client?: PoolClient;
|
|
378
|
+
dryRun?: boolean;
|
|
361
379
|
},
|
|
362
380
|
): Promise<ExposedServiceOutput> {
|
|
363
381
|
return await beginTransaction(
|
|
@@ -370,6 +388,7 @@ export class CollectionStore {
|
|
|
370
388
|
context: context,
|
|
371
389
|
triggeredBy: triggeredBy,
|
|
372
390
|
transaction: client,
|
|
391
|
+
dryRun,
|
|
373
392
|
},
|
|
374
393
|
);
|
|
375
394
|
|
|
@@ -1222,6 +1241,62 @@ export class CollectionStore {
|
|
|
1222
1241
|
}
|
|
1223
1242
|
}
|
|
1224
1243
|
|
|
1244
|
+
private async resolveChildrenOf(
|
|
1245
|
+
collectionName: string,
|
|
1246
|
+
childrenOf: string,
|
|
1247
|
+
parentId: any,
|
|
1248
|
+
): Promise<Record<string, any> | null> {
|
|
1249
|
+
const children = Lobb.instance.configManager.getCollectionChildren(childrenOf);
|
|
1250
|
+
const child = children.find((c) => c.collection === collectionName);
|
|
1251
|
+
|
|
1252
|
+
if (!child) {
|
|
1253
|
+
throw new LobbError({
|
|
1254
|
+
code: "BAD_REQUEST",
|
|
1255
|
+
message: `"${collectionName}" is not a child of "${childrenOf}"`,
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
if (child.type === "fk") {
|
|
1260
|
+
return { [child.field!]: parentId };
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
if (child.type === "m2m") {
|
|
1264
|
+
const junction = await this.findAll({
|
|
1265
|
+
collectionName: child.junction!,
|
|
1266
|
+
params: { filter: { [child.junction_field!]: parentId } },
|
|
1267
|
+
});
|
|
1268
|
+
const ids = junction.data.map((r: any) => r[child.target_field!]);
|
|
1269
|
+
if (ids.length === 0) return null;
|
|
1270
|
+
return { id: { $in: ids } };
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
if (child.type === "polymorphic") {
|
|
1274
|
+
const relations = Lobb.instance.configManager.config.relations ?? [];
|
|
1275
|
+
const polyRelation = relations.find(
|
|
1276
|
+
(r: any) =>
|
|
1277
|
+
r.type === "polymorphic" &&
|
|
1278
|
+
r.from.collection === collectionName &&
|
|
1279
|
+
Array.isArray(r.to) &&
|
|
1280
|
+
r.to.includes(childrenOf),
|
|
1281
|
+
) as any;
|
|
1282
|
+
if (!polyRelation) {
|
|
1283
|
+
throw new LobbError({
|
|
1284
|
+
code: "BAD_REQUEST",
|
|
1285
|
+
message: `No polymorphic relation found between "${collectionName}" and "${childrenOf}"`,
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
return {
|
|
1289
|
+
[polyRelation.from.collection_field]: childrenOf,
|
|
1290
|
+
[polyRelation.from.id_field]: parentId,
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
throw new LobbError({
|
|
1295
|
+
code: "BAD_REQUEST",
|
|
1296
|
+
message: `Unknown relation type: ${child.type}`,
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1225
1300
|
private async handleSingletonCollection(
|
|
1226
1301
|
collectionName: string,
|
|
1227
1302
|
context?: HonoContext,
|
|
@@ -62,7 +62,7 @@ export class ConfigManager {
|
|
|
62
62
|
} as any;
|
|
63
63
|
|
|
64
64
|
// Convert the original field to virtual (drives the UI picker)
|
|
65
|
-
collection.fields[fieldName] = { virtual: true, ui: { icon: "
|
|
65
|
+
collection.fields[fieldName] = { virtual: true, ui: { icon: "GitFork" } } as any;
|
|
66
66
|
|
|
67
67
|
// Auto-register the polymorphic relation
|
|
68
68
|
if (!this.config.relations) this.config.relations = [];
|
|
@@ -425,10 +425,10 @@ export class ConfigManager {
|
|
|
425
425
|
return [];
|
|
426
426
|
}
|
|
427
427
|
|
|
428
|
-
public getCollectionChildren(collectionName: string): { type: string; collection: string; field?: string }[] {
|
|
428
|
+
public getCollectionChildren(collectionName: string): { type: string; collection: string; field?: string; junction?: string; junction_field?: string; target_field?: string }[] {
|
|
429
429
|
const allRelations = this.config.relations ?? [];
|
|
430
430
|
const allCollections = this.config.collections;
|
|
431
|
-
const children: { type: string; collection: string; field?: string }[] = [];
|
|
431
|
+
const children: { type: string; collection: string; field?: string; junction?: string; junction_field?: string; target_field?: string }[] = [];
|
|
432
432
|
const seenJunctions = new Set<string>();
|
|
433
433
|
|
|
434
434
|
for (const relation of allRelations) {
|
|
@@ -446,7 +446,13 @@ export class ConfigManager {
|
|
|
446
446
|
(r as RegularRelation).from.field !== reg.from.field,
|
|
447
447
|
) as RegularRelation | undefined;
|
|
448
448
|
if (otherSide) {
|
|
449
|
-
children.push({
|
|
449
|
+
children.push({
|
|
450
|
+
type: "m2m",
|
|
451
|
+
collection: otherSide.to.collection,
|
|
452
|
+
junction: childCollection,
|
|
453
|
+
junction_field: reg.from.field,
|
|
454
|
+
target_field: otherSide.from.field,
|
|
455
|
+
});
|
|
450
456
|
}
|
|
451
457
|
} else {
|
|
452
458
|
children.push({ type: "fk", collection: childCollection, field: reg.from.field });
|
package/src/types/Extension.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { OpenAPIV3_1 } from "openapi-types";
|
|
|
5
5
|
import type { RelationsConfig } from "./config/relations.ts";
|
|
6
6
|
import type { Lobb } from "../Lobb.ts";
|
|
7
7
|
import type { Workflow } from "../workflows/WorkflowSystem.ts";
|
|
8
|
+
import * as Icons from "lucide-svelte";
|
|
8
9
|
|
|
9
10
|
interface OpenApiProperty {
|
|
10
11
|
paths: OpenAPIV3_1.Document["paths"];
|
|
@@ -17,6 +18,7 @@ export interface Dashboard {
|
|
|
17
18
|
|
|
18
19
|
interface ExtensionBase {
|
|
19
20
|
name: string;
|
|
21
|
+
icon?: keyof typeof Icons;
|
|
20
22
|
workflows?: Workflow[];
|
|
21
23
|
migrations?: Migrations;
|
|
22
24
|
init?: (lobb: Lobb) => Promise<void>;
|
|
@@ -9,6 +9,7 @@ import { enumWorkflows } from "./processors/enumWorkflows.ts";
|
|
|
9
9
|
import { coerceAndValidateWorkflows } from "./processors/coerceAndValidateWorkflows.ts";
|
|
10
10
|
import { coreWorkflowsCollectionWorkflows } from "./workflowsCollection/workflowsCollectionWorkflows.ts";
|
|
11
11
|
import { getQueryCoreWorkflows } from "./queryCoreWorkflows.ts";
|
|
12
|
+
import { dryRunWorkflows } from "./processors/dryRunWorkflows.ts";
|
|
12
13
|
|
|
13
14
|
export function getCoreWorkflows(): Array<Workflow> {
|
|
14
15
|
return [
|
|
@@ -23,5 +24,6 @@ export function getCoreWorkflows(): Array<Workflow> {
|
|
|
23
24
|
...getCollectionsTableWorkflows(),
|
|
24
25
|
...getQueryCoreWorkflows(),
|
|
25
26
|
...getUtilsCoreWorkflows(),
|
|
27
|
+
...dryRunWorkflows,
|
|
26
28
|
];
|
|
27
29
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Workflow } from "../../WorkflowSystem.ts";
|
|
2
|
+
|
|
3
|
+
export const dryRunWorkflows: Workflow[] = [
|
|
4
|
+
{
|
|
5
|
+
name: "core_dryRunCreate",
|
|
6
|
+
eventName: "core.store.preCreateOne",
|
|
7
|
+
handler: async (input) => {
|
|
8
|
+
if (!input.dryRun) return input;
|
|
9
|
+
throw new Response(null, { status: 204 });
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: "core_dryRunUpdate",
|
|
14
|
+
eventName: "core.store.preUpdateOne",
|
|
15
|
+
handler: async (input) => {
|
|
16
|
+
if (!input.dryRun) return input;
|
|
17
|
+
throw new Response(null, { status: 204 });
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "core_dryRunDelete",
|
|
22
|
+
eventName: "core.store.preDeleteOne",
|
|
23
|
+
handler: async (input) => {
|
|
24
|
+
if (!input.dryRun) return input;
|
|
25
|
+
throw new Response(null, { status: 204 });
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
];
|