@lobb-js/core 0.13.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.
Files changed (86) hide show
  1. package/package.json +48 -0
  2. package/src/Lobb.ts +150 -0
  3. package/src/LobbError.ts +105 -0
  4. package/src/TypesGenerator.ts +11 -0
  5. package/src/api/WebServer.ts +126 -0
  6. package/src/api/collections/CollectionControllers.ts +485 -0
  7. package/src/api/collections/CollectionService.ts +162 -0
  8. package/src/api/collections/collectionRoutes.ts +105 -0
  9. package/src/api/collections/collectionStore.ts +647 -0
  10. package/src/api/collections/transactions.ts +166 -0
  11. package/src/api/collections/utils.ts +73 -0
  12. package/src/api/errorHandler.ts +73 -0
  13. package/src/api/events/index.ts +129 -0
  14. package/src/api/meta/route.ts +66 -0
  15. package/src/api/meta/service.ts +163 -0
  16. package/src/api/middlewares.ts +71 -0
  17. package/src/api/openApiRoute.ts +1017 -0
  18. package/src/api/schema/SchemaService.ts +71 -0
  19. package/src/api/schema/schemaRoutes.ts +13 -0
  20. package/src/config/ConfigManager.ts +252 -0
  21. package/src/config/validations.ts +49 -0
  22. package/src/coreCollections/collectionsCollection.ts +56 -0
  23. package/src/coreCollections/index.ts +14 -0
  24. package/src/coreCollections/migrationsCollection.ts +36 -0
  25. package/src/coreCollections/queryCollection.ts +26 -0
  26. package/src/coreCollections/workflowsCollection.ts +73 -0
  27. package/src/coreDbSetup/index.ts +72 -0
  28. package/src/coreMigrations/index.ts +3 -0
  29. package/src/database/DatabaseService.ts +44 -0
  30. package/src/database/DatabaseSyncManager.ts +173 -0
  31. package/src/database/MigrationsManager.ts +95 -0
  32. package/src/database/drivers/MongoDriver.ts +750 -0
  33. package/src/database/drivers/pgDriver/PGDriver.ts +655 -0
  34. package/src/database/drivers/pgDriver/QueryBuilder.ts +474 -0
  35. package/src/database/drivers/pgDriver/utils.ts +6 -0
  36. package/src/events/EventSystem.ts +191 -0
  37. package/src/events/coreEvents/index.ts +218 -0
  38. package/src/events/studioEvents/index.ts +32 -0
  39. package/src/extension/ExtensionSystem.ts +236 -0
  40. package/src/extension/dashboardRoute.ts +35 -0
  41. package/src/fields/ArrayField.ts +33 -0
  42. package/src/fields/BoolField.ts +34 -0
  43. package/src/fields/DateField.ts +13 -0
  44. package/src/fields/DateTimeField.ts +13 -0
  45. package/src/fields/DecimalField.ts +13 -0
  46. package/src/fields/FieldUtils.ts +56 -0
  47. package/src/fields/FloatField.ts +13 -0
  48. package/src/fields/IntegerField.ts +13 -0
  49. package/src/fields/LongField.ts +13 -0
  50. package/src/fields/ObjectField.ts +15 -0
  51. package/src/fields/StringField.ts +13 -0
  52. package/src/fields/TextField.ts +13 -0
  53. package/src/fields/TimeField.ts +13 -0
  54. package/src/index.ts +53 -0
  55. package/src/studio/Studio.ts +108 -0
  56. package/src/types/CollectionControllers.ts +15 -0
  57. package/src/types/DatabaseDriver.ts +115 -0
  58. package/src/types/Extension.ts +46 -0
  59. package/src/types/Field.ts +29 -0
  60. package/src/types/apiSchema.ts +12 -0
  61. package/src/types/collectionServiceSchema.ts +18 -0
  62. package/src/types/config/collectionFields.ts +85 -0
  63. package/src/types/config/collectionsConfig.ts +50 -0
  64. package/src/types/config/config.ts +66 -0
  65. package/src/types/config/relations.ts +17 -0
  66. package/src/types/filterSchema.ts +88 -0
  67. package/src/types/index.ts +38 -0
  68. package/src/types/migrations.ts +12 -0
  69. package/src/types/websockets.ts +34 -0
  70. package/src/types/workflows/processors.ts +1 -0
  71. package/src/utils/lockCollectionToObject.ts +204 -0
  72. package/src/utils/utils.ts +310 -0
  73. package/src/workflows/WorkflowSystem.ts +182 -0
  74. package/src/workflows/coreWorkflows/collectionsTable/index.ts +118 -0
  75. package/src/workflows/coreWorkflows/index.ts +18 -0
  76. package/src/workflows/coreWorkflows/processors/postOperationsWorkflows.ts +46 -0
  77. package/src/workflows/coreWorkflows/processors/preOperationsWorkflows.ts +27 -0
  78. package/src/workflows/coreWorkflows/processors/processorForDB.ts +13 -0
  79. package/src/workflows/coreWorkflows/processors/processors/processor.ts +23 -0
  80. package/src/workflows/coreWorkflows/processors/processors/processorsFunctions.ts +47 -0
  81. package/src/workflows/coreWorkflows/processors/utils.ts +102 -0
  82. package/src/workflows/coreWorkflows/processors/validator/validator.ts +19 -0
  83. package/src/workflows/coreWorkflows/processors/validator/validatorsFunction.ts +52 -0
  84. package/src/workflows/coreWorkflows/queryCoreWorkflows.ts +31 -0
  85. package/src/workflows/coreWorkflows/utilsCoreWorkflows.ts +40 -0
  86. package/src/workflows/coreWorkflows/workflowsCollection/workflowsCollectionWorkflows.ts +101 -0
@@ -0,0 +1,647 @@
1
+ import type { FindAllParamsInput } from "../../types/index.ts";
2
+ import type { DatabaseDriver } from "../../types/index.ts";
3
+ import type { MassReturn } from "../../types/index.ts";
4
+ import type { PoolClient } from "pg";
5
+ import type { Context as HonoContext } from "hono";
6
+
7
+ import { findAllParamsSchema } from "../../types/index.ts";
8
+ import { ZodError } from "zod";
9
+ import { LobbError } from "../../LobbError.ts";
10
+ import { Lobb } from "../../Lobb.ts";
11
+ import { beginTransaction } from "./transactions.ts";
12
+
13
+ export interface ExposedServiceOutput {
14
+ data: any;
15
+ meta?: any;
16
+ }
17
+
18
+ export interface ExposedManyServiceOutput {
19
+ data: any[];
20
+ meta?: any;
21
+ }
22
+
23
+ export type TriggeredBy = "INTERNAL" | "API";
24
+
25
+ export class CollectionStore {
26
+ private get dbDriver(): DatabaseDriver {
27
+ return Lobb.instance.databaseService.getDriver();
28
+ }
29
+
30
+ public async findAll({
31
+ collectionName,
32
+ params,
33
+ triggeredBy = "INTERNAL",
34
+ context,
35
+ client,
36
+ }: {
37
+ collectionName: string;
38
+ params?: FindAllParamsInput;
39
+ triggeredBy?: TriggeredBy;
40
+ context?: HonoContext;
41
+ client?: PoolClient;
42
+ }): Promise<ExposedManyServiceOutput> {
43
+ return await beginTransaction(
44
+ async (client) => {
45
+ const eventResult = await Lobb.instance.eventSystem.emit(
46
+ `core.store.preFindAll`,
47
+ {
48
+ collectionName: collectionName,
49
+ params: params,
50
+ context: context,
51
+ triggeredBy: triggeredBy,
52
+ transaction: client,
53
+ },
54
+ );
55
+
56
+ if (eventResult.filter) {
57
+ if (!params) {
58
+ params = {};
59
+ }
60
+ if (params.filter) {
61
+ params.filter = {
62
+ $and: [
63
+ params.filter,
64
+ eventResult.filter,
65
+ ],
66
+ };
67
+ } else {
68
+ params.filter = eventResult.filter;
69
+ }
70
+ }
71
+
72
+ if (params?.filter) {
73
+ params.filter = {
74
+ $and: [params.filter],
75
+ };
76
+
77
+ params.filter = Lobb.instance.utils.renderTemplateDeep(
78
+ params.filter,
79
+ {
80
+ ctx: context,
81
+ },
82
+ );
83
+ }
84
+
85
+ const parsedParams = findAllParamsSchema.parse(params);
86
+
87
+ try {
88
+ const result = await this.dbDriver.findAll(
89
+ collectionName,
90
+ parsedParams,
91
+ client,
92
+ );
93
+
94
+ result.data = (await Lobb.instance.eventSystem.emit(
95
+ `core.store.findAll`,
96
+ {
97
+ data: result.data,
98
+ params: params,
99
+ collectionName: collectionName,
100
+ context: context,
101
+ triggeredBy: triggeredBy,
102
+ transaction: client,
103
+ },
104
+ )).data;
105
+
106
+ return result;
107
+ } catch (error: any) {
108
+ if (error instanceof ZodError) {
109
+ throw new LobbError({
110
+ code: "BAD_REQUEST",
111
+ message: "Invalid query schema",
112
+ details: error.errors[0],
113
+ });
114
+ }
115
+
116
+ throw error;
117
+ }
118
+ },
119
+ client,
120
+ );
121
+ }
122
+
123
+ public async findOne({
124
+ collectionName,
125
+ id,
126
+ triggeredBy = "INTERNAL",
127
+ context,
128
+ client,
129
+ }: {
130
+ collectionName: string;
131
+ id: string;
132
+ triggeredBy?: TriggeredBy;
133
+ context?: HonoContext;
134
+ client?: PoolClient;
135
+ }): Promise<ExposedServiceOutput> {
136
+ return await beginTransaction(
137
+ async (client) => {
138
+ const eventResult: any = await Lobb.instance.eventSystem.emit(
139
+ `core.store.preFindOne`,
140
+ {
141
+ collectionName: collectionName,
142
+ id: id,
143
+ context: context,
144
+ triggeredBy: triggeredBy,
145
+ transaction: client,
146
+ },
147
+ );
148
+
149
+ const params: FindAllParamsInput = {};
150
+
151
+ params.filter = {
152
+ id: eventResult.id,
153
+ };
154
+
155
+ if (eventResult.filter) {
156
+ if (params.filter) {
157
+ params.filter = {
158
+ $and: [
159
+ params.filter,
160
+ eventResult.filter,
161
+ ],
162
+ };
163
+ } else {
164
+ params.filter = eventResult.filter;
165
+ }
166
+ }
167
+
168
+ const parsedParams = findAllParamsSchema.parse(params);
169
+
170
+ let data = (await this.dbDriver.findAll(
171
+ collectionName,
172
+ parsedParams,
173
+ client,
174
+ )).data[0];
175
+
176
+ if (!data) {
177
+ throw new LobbError({
178
+ code: "NOT_FOUND",
179
+ message: "The entry you are looking for does not exist.",
180
+ });
181
+ }
182
+
183
+ data = (await Lobb.instance.eventSystem.emit(
184
+ `core.store.findOne`,
185
+ {
186
+ data,
187
+ collectionName: collectionName,
188
+ context: context,
189
+ triggeredBy: triggeredBy,
190
+ transaction: client,
191
+ },
192
+ )).data;
193
+
194
+ return {
195
+ data,
196
+ };
197
+ },
198
+ client,
199
+ );
200
+ }
201
+
202
+ public async createOne({
203
+ collectionName,
204
+ data,
205
+ triggeredBy = "INTERNAL",
206
+ context,
207
+ client,
208
+ }: {
209
+ collectionName: string;
210
+ data: Record<string, any>;
211
+ triggeredBy?: TriggeredBy;
212
+ context?: HonoContext;
213
+ client?: PoolClient;
214
+ }): Promise<ExposedServiceOutput> {
215
+ return await beginTransaction(
216
+ async (client) => {
217
+ await this.handleSingletonCollection(collectionName, context, client);
218
+
219
+ const result = await Lobb.instance.eventSystem.emit(
220
+ `core.store.preCreateOne`,
221
+ {
222
+ data,
223
+ collectionName: collectionName,
224
+ context: context,
225
+ triggeredBy: triggeredBy,
226
+ transaction: client,
227
+ },
228
+ );
229
+ data = await this.dbDriver.createOne(
230
+ collectionName,
231
+ this.filterPayload(collectionName, result.data),
232
+ client,
233
+ );
234
+ data = (await Lobb.instance.eventSystem.emit(
235
+ `core.store.createOne`,
236
+ {
237
+ data,
238
+ collectionName: collectionName,
239
+ context: context,
240
+ triggeredBy: triggeredBy,
241
+ transaction: client,
242
+ },
243
+ )).data;
244
+
245
+ return {
246
+ data,
247
+ };
248
+ },
249
+ client,
250
+ );
251
+ }
252
+
253
+ public async updateOne({
254
+ collectionName,
255
+ id,
256
+ data,
257
+ triggeredBy = "INTERNAL",
258
+ context,
259
+ client,
260
+ }: {
261
+ collectionName: string;
262
+ id: string;
263
+ data: Record<string, any> | null;
264
+ triggeredBy?: TriggeredBy;
265
+ context?: HonoContext;
266
+ client?: PoolClient;
267
+ }): Promise<ExposedServiceOutput> {
268
+ return await beginTransaction(
269
+ async (client) => {
270
+ const result = await Lobb.instance.eventSystem.emit(
271
+ `core.store.preUpdateOne`,
272
+ {
273
+ id,
274
+ data,
275
+ collectionName: collectionName,
276
+ context: context,
277
+ triggeredBy: triggeredBy,
278
+ transaction: client,
279
+ },
280
+ );
281
+ data = await this.dbDriver.updateOne(
282
+ collectionName,
283
+ result.id,
284
+ this.filterPayload(collectionName, result.data),
285
+ client,
286
+ );
287
+ data = (await Lobb.instance.eventSystem.emit(
288
+ `core.store.updateOne`,
289
+ {
290
+ id,
291
+ data,
292
+ collectionName: collectionName,
293
+ context: context,
294
+ triggeredBy: triggeredBy,
295
+ transaction: client,
296
+ },
297
+ )).data;
298
+
299
+ return {
300
+ data,
301
+ };
302
+ },
303
+ client,
304
+ );
305
+ }
306
+
307
+ public async deleteOne(
308
+ {
309
+ collectionName,
310
+ id,
311
+ force = false,
312
+ triggeredBy = "INTERNAL",
313
+ context,
314
+ client,
315
+ }: {
316
+ collectionName: string;
317
+ id: string;
318
+ force?: boolean;
319
+ triggeredBy?: TriggeredBy;
320
+ context?: HonoContext;
321
+ client?: PoolClient;
322
+ },
323
+ ): Promise<ExposedServiceOutput> {
324
+ return await beginTransaction(
325
+ async (client) => {
326
+ const result: any = await Lobb.instance.eventSystem.emit(
327
+ `core.store.preDeleteOne`,
328
+ {
329
+ id,
330
+ collectionName: collectionName,
331
+ context: context,
332
+ triggeredBy: triggeredBy,
333
+ transaction: client,
334
+ },
335
+ );
336
+
337
+ // check if the record has children
338
+ const childRelations = Lobb.instance.configManager.getChildRelations(
339
+ collectionName,
340
+ );
341
+ for (let index = 0; index < childRelations.length; index++) {
342
+ const childRelation = childRelations[index];
343
+ const childCollection = childRelation.from.collection;
344
+ const childField = childRelation.from.field;
345
+ const ids = await this.dbDriver.getIdsByFilter(
346
+ childCollection,
347
+ {
348
+ [childField]: result.id,
349
+ },
350
+ client,
351
+ );
352
+
353
+ if (ids.length) {
354
+ if (force) {
355
+ for (let index = 0; index < ids.length; index++) {
356
+ const id = ids[index];
357
+ await Lobb.instance.collectionService.deleteOne({
358
+ collectionName: childCollection,
359
+ context,
360
+ client,
361
+ id,
362
+ force,
363
+ });
364
+ }
365
+ } else {
366
+ throw new LobbError({
367
+ code: "BAD_REQUEST",
368
+ message:
369
+ `Record has children of the (${childCollection}) collection and cannot be deleted`,
370
+ });
371
+ }
372
+ }
373
+ }
374
+
375
+ // delete the record
376
+ let data = await this.dbDriver.deleteOne(
377
+ collectionName,
378
+ result.id,
379
+ client,
380
+ );
381
+ data = (await Lobb.instance.eventSystem.emit(
382
+ `core.store.deleteOne`,
383
+ {
384
+ id,
385
+ data,
386
+ collectionName: collectionName,
387
+ context: context,
388
+ triggeredBy: triggeredBy,
389
+ transaction: client,
390
+ },
391
+ )).data;
392
+
393
+ return {
394
+ data,
395
+ };
396
+ },
397
+ client,
398
+ );
399
+ }
400
+
401
+ public async readSingleton({
402
+ collectionName,
403
+ triggeredBy = "INTERNAL",
404
+ context,
405
+ client,
406
+ }: {
407
+ collectionName: string;
408
+ triggeredBy?: TriggeredBy;
409
+ context?: HonoContext;
410
+ client?: PoolClient;
411
+ }): Promise<ExposedServiceOutput> {
412
+ return await beginTransaction(
413
+ async (client) => {
414
+ const singletonId = (await this.dbDriver.getIdsByFilter(
415
+ collectionName,
416
+ {},
417
+ client,
418
+ ))[0] as string | undefined;
419
+
420
+ if (!singletonId) {
421
+ throw new LobbError({
422
+ code: "NOT_FOUND",
423
+ message:
424
+ "The the singleton you are trying to read is not created yet",
425
+ });
426
+ }
427
+
428
+ const data = await this.findOne({
429
+ collectionName,
430
+ id: singletonId,
431
+ triggeredBy,
432
+ context,
433
+ client,
434
+ });
435
+
436
+ return data;
437
+ },
438
+ client,
439
+ );
440
+ }
441
+
442
+ public async updateSingleton({
443
+ collectionName,
444
+ data,
445
+ triggeredBy = "INTERNAL",
446
+ context,
447
+ client,
448
+ }: {
449
+ collectionName: string;
450
+ data: Record<string, any>;
451
+ triggeredBy?: TriggeredBy;
452
+ context?: HonoContext;
453
+ client?: PoolClient;
454
+ }): Promise<ExposedServiceOutput> {
455
+ return await beginTransaction(
456
+ async (client) => {
457
+ const singletonId = (await this.dbDriver.getIdsByFilter(
458
+ collectionName,
459
+ {},
460
+ client,
461
+ ))[0] as string | undefined;
462
+
463
+ if (!singletonId) {
464
+ return await this.createOne({
465
+ collectionName,
466
+ data: data,
467
+ triggeredBy,
468
+ context,
469
+ client,
470
+ });
471
+ }
472
+
473
+ return await this.updateOne({
474
+ collectionName,
475
+ id: singletonId,
476
+ data: data,
477
+ triggeredBy,
478
+ context,
479
+ client,
480
+ });
481
+ },
482
+ client,
483
+ );
484
+ }
485
+
486
+ // many operations
487
+ public async deleteMany(
488
+ {
489
+ collectionName,
490
+ filter,
491
+ force = false,
492
+ triggeredBy = "INTERNAL",
493
+ context,
494
+ client,
495
+ }: {
496
+ collectionName: string;
497
+ filter?: any;
498
+ force?: boolean;
499
+ triggeredBy?: TriggeredBy;
500
+ context?: HonoContext;
501
+ client?: PoolClient;
502
+ },
503
+ ): Promise<MassReturn> {
504
+ return await beginTransaction(
505
+ async (client) => {
506
+ const ids = await this.dbDriver.getIdsByFilter(
507
+ collectionName,
508
+ filter,
509
+ client,
510
+ );
511
+
512
+ for (let index = 0; index < ids.length; index++) {
513
+ const id = ids[index];
514
+ await this.deleteOne({
515
+ collectionName,
516
+ id,
517
+ force,
518
+ triggeredBy,
519
+ context,
520
+ client,
521
+ });
522
+ }
523
+
524
+ return {
525
+ affectedCount: ids.length,
526
+ };
527
+ },
528
+ client,
529
+ );
530
+ }
531
+
532
+ public async updateMany({
533
+ collectionName,
534
+ filter,
535
+ data,
536
+ triggeredBy = "INTERNAL",
537
+ context,
538
+ client,
539
+ }: {
540
+ collectionName: string;
541
+ data: Record<string, any>;
542
+ filter?: any;
543
+ triggeredBy?: TriggeredBy;
544
+ context?: HonoContext;
545
+ client?: PoolClient;
546
+ }): Promise<MassReturn> {
547
+ return await beginTransaction(
548
+ async (client) => {
549
+ const ids = await this.dbDriver.getIdsByFilter(
550
+ collectionName,
551
+ filter,
552
+ client,
553
+ );
554
+
555
+ for (let index = 0; index < ids.length; index++) {
556
+ const id = ids[index];
557
+ await this.updateOne({
558
+ collectionName,
559
+ id,
560
+ data,
561
+ triggeredBy,
562
+ context,
563
+ client,
564
+ });
565
+ }
566
+
567
+ return {
568
+ affectedCount: ids.length,
569
+ };
570
+ },
571
+ client,
572
+ );
573
+ }
574
+
575
+ public async createMany({
576
+ collectionName,
577
+ data,
578
+ triggeredBy = "INTERNAL",
579
+ context,
580
+ client,
581
+ }: {
582
+ collectionName: string;
583
+ data: Record<string, any>[];
584
+ triggeredBy?: TriggeredBy;
585
+ context?: HonoContext;
586
+ client?: PoolClient;
587
+ }): Promise<MassReturn> {
588
+ return await beginTransaction(
589
+ async (client) => {
590
+ for (let index = 0; index < data.length; index++) {
591
+ const entry = data[index];
592
+ await this.createOne({
593
+ collectionName,
594
+ data: entry,
595
+ triggeredBy,
596
+ context,
597
+ client,
598
+ });
599
+ }
600
+
601
+ return {
602
+ affectedCount: data.length,
603
+ };
604
+ },
605
+ client,
606
+ );
607
+ }
608
+
609
+ // utils
610
+ public filterPayload(
611
+ collectionName: string,
612
+ payload: Record<string, any>,
613
+ ): Record<string, any> {
614
+ // Get the list of valid field names for this collection
615
+ const fieldNames = Lobb.instance.configManager.getFieldNames(
616
+ collectionName,
617
+ );
618
+
619
+ // Filter the payload
620
+ const filteredPayload: Record<string, any> = {};
621
+ for (const key of Object.keys(payload)) {
622
+ if (fieldNames.includes(key)) {
623
+ filteredPayload[key] = payload[key];
624
+ }
625
+ }
626
+
627
+ return filteredPayload;
628
+ }
629
+
630
+ private async handleSingletonCollection(
631
+ collectionName: string,
632
+ context?: HonoContext,
633
+ client?: PoolClient,
634
+ ) {
635
+ if (
636
+ Lobb.instance.configManager.isCollectionSingleton(collectionName)
637
+ ) {
638
+ const result = await this.findAll({ collectionName, context, client });
639
+ if (result.meta.totalCount > 0) {
640
+ throw new LobbError({
641
+ code: "BAD_REQUEST",
642
+ message: "Only one record is allowed in this singleton collection.",
643
+ });
644
+ }
645
+ }
646
+ }
647
+ }