@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,44 @@
1
+ import type { DatabaseDriver } from "../types/index.ts";
2
+
3
+ import { Lobb } from "../Lobb.ts";
4
+ import { LobbError } from "../LobbError.ts";
5
+
6
+ export class DatabaseService {
7
+ private dbDriver!: DatabaseDriver;
8
+
9
+ public static async init() {
10
+ const databaseService = new DatabaseService();
11
+ await databaseService.createConnection();
12
+ return databaseService;
13
+ }
14
+
15
+ public async close() {
16
+ await this.dbDriver.close();
17
+ }
18
+
19
+ public async createConnection() {
20
+ Lobb.instance.configManager.config.database.type =
21
+ Lobb.instance.configManager.config.database.type ?? "pg";
22
+
23
+ if (Lobb.instance.configManager.config.database.type === "pg") {
24
+ const DriverClass =
25
+ (await import("./drivers/pgDriver/PGDriver.ts")).PGDriver;
26
+ this.dbDriver = new DriverClass();
27
+ } else {
28
+ throw new LobbError({
29
+ code: "INTERNAL_SERVER_ERROR",
30
+ message:
31
+ `driver of type (${Lobb.instance.configManager.config.database.type}) is not implemented`,
32
+ });
33
+ }
34
+ await this.dbDriver.createConnection();
35
+ }
36
+
37
+ public dropDatabase() {
38
+ return this.dbDriver.dropDatabase();
39
+ }
40
+
41
+ public getDriver() {
42
+ return this.dbDriver;
43
+ }
44
+ }
@@ -0,0 +1,173 @@
1
+ import * as _ from "lodash";
2
+
3
+ import { diff } from "just-diff";
4
+ import { runCoreDbSetup } from "../coreDbSetup/index.ts";
5
+ import { MigrationsManager } from "./MigrationsManager.ts";
6
+ import { LobbError } from "../LobbError.ts";
7
+ import { Lobb } from "../Lobb.ts";
8
+ import type { CollectionConfig, CollectionField, CollectionIndex } from "../types/index.ts";
9
+
10
+ export class DatabaseSyncManager {
11
+ private dbDriver = Lobb.instance.databaseService.getDriver();
12
+ private config = Lobb.instance.configManager.config;
13
+
14
+ public static async init() {
15
+ const databaseSyncManager = new DatabaseSyncManager();
16
+ await databaseSyncManager.syncDatabase();
17
+ return databaseSyncManager;
18
+ }
19
+
20
+ public async syncDatabase(specificColleciton?: string) {
21
+ const forceSync = this.config.project.force_sync ?? false;
22
+
23
+ // Startup tasks run first — applies any idempotent DB fixes before schema reading begins.
24
+ await runCoreDbSetup();
25
+
26
+ const migrationsManager = new MigrationsManager();
27
+ await migrationsManager.init();
28
+
29
+ const dbSchemaDiff = await this.getDbDifferences(specificColleciton);
30
+ await this.applyDbSchemaDiff(dbSchemaDiff, forceSync);
31
+ }
32
+
33
+ private async applyDbSchemaDiff(
34
+ dbSchemaDiff: Array<{ op: string; path: (string | number)[]; value: unknown }>,
35
+ forceSync: boolean,
36
+ ) {
37
+ if (!dbSchemaDiff.length) return;
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
+ // just-diff may produce deep paths (length > 3) for sub-field changes within
55
+ // an existing index (e.g. renaming a column the index covers). Convert those
56
+ // into explicit drop+recreate (replace) ops, deduplicating by index name.
57
+ const syntheticReplaces = new Map<string, (string | number)[]>();
58
+ const normalizedDiff = dbSchemaDiff.filter((change) => {
59
+ if (change.path[1] === "indexes" && change.path.length > 3) {
60
+ const key = `${change.path[0]}:${change.path[2]}`;
61
+ if (!syntheticReplaces.has(key)) {
62
+ syntheticReplaces.set(key, [change.path[0], "indexes", change.path[2]]);
63
+ }
64
+ return false;
65
+ }
66
+ return true;
67
+ });
68
+ for (const path of syntheticReplaces.values()) {
69
+ normalizedDiff.push({ op: "replace", path, value: undefined });
70
+ }
71
+
72
+ // Apply changes in safe dependency order:
73
+ // 1. Drop collections, 2. Drop indexes, 3. Remove fields,
74
+ // 4. Add collections, 5. Add fields, 6. Add/replace indexes
75
+ const opOrder = (change: { op: string; path: (string | number)[] }) => {
76
+ if (change.path.length === 1 && change.op === "remove") return 0; // drop collection
77
+ if (change.path[1] === "indexes" && change.op === "remove") return 1; // drop index
78
+ if (change.path[1] === "indexes" && change.op === "replace") return 2; // replace index (drop+create)
79
+ if (change.path[1] === "fields" && change.op === "remove") return 3; // remove field
80
+ if (change.path.length === 1 && change.op === "add") return 4; // add collection
81
+ if (change.path[1] === "fields" && change.op === "add") return 5; // add field
82
+ if (change.path[1] === "indexes" && change.op === "add") return 6; // add index
83
+ return 7;
84
+ };
85
+ normalizedDiff.sort((a, b) => opOrder(a) - opOrder(b));
86
+
87
+ for (let index = 0; index < normalizedDiff.length; index++) {
88
+ const change = normalizedDiff[index];
89
+ if (change.path.length === 1 && change.op === "add") {
90
+ const addedCollectionName = change.path[0] as string;
91
+ await this.dbDriver.createCollection(
92
+ addedCollectionName,
93
+ change.value as CollectionConfig,
94
+ );
95
+ } else if (change.path.length === 1 && change.op === "remove") {
96
+ const removedCollectionName = change.path[0] as string;
97
+ await this.dbDriver.dropCollection(removedCollectionName);
98
+ } else if (
99
+ change.path.length === 3 &&
100
+ change.path[1] === "fields" &&
101
+ change.op === "add"
102
+ ) {
103
+ const collectionName = change.path[0] as string;
104
+ const fieldName = change.path[2] as string;
105
+ await this.dbDriver.addField(
106
+ collectionName,
107
+ fieldName,
108
+ change.value as CollectionField,
109
+ );
110
+ } else if (change.path[1] === "fields" && change.op === "remove") {
111
+ const collectionName = change.path[0] as string;
112
+ const fieldName = change.path[2] as string;
113
+ await this.dbDriver.removeField(collectionName, fieldName);
114
+ } else if (change.path[1] === "indexes" && change.op === "add") {
115
+ const collectionName = change.path[0] as string;
116
+ const indexName = change.path[2] as string;
117
+ await this.dbDriver.createIndex(
118
+ collectionName,
119
+ indexName,
120
+ change.value as CollectionIndex,
121
+ );
122
+ } else if (change.path[1] === "indexes" && change.op === "remove") {
123
+ const collectionName = change.path[0] as string;
124
+ const indexName = change.path[2] as string;
125
+ await this.dbDriver.dropIndex(collectionName, indexName);
126
+ } else if (change.path[1] === "indexes" && change.op === "replace") {
127
+ const collectionName = change.path[0] as string;
128
+ const indexName = change.path[2] as string;
129
+ const collectionConfig = Lobb.instance.configManager.getCollection(
130
+ collectionName,
131
+ );
132
+ const indexes = collectionConfig.indexes;
133
+ const index = indexes[indexName];
134
+ await this.dbDriver.dropIndex(collectionName, indexName);
135
+ await this.dbDriver.createIndex(
136
+ collectionName,
137
+ indexName,
138
+ index,
139
+ );
140
+ } else {
141
+ console.error(change);
142
+ throw new LobbError({
143
+ code: "INTERNAL_SERVER_ERROR",
144
+ message: "the database change above is not handled",
145
+ });
146
+ }
147
+ }
148
+ }
149
+
150
+ private async getDbDifferences(specificColleciton?: string) {
151
+ let dbSchema = await this.dbDriver.getDbSchema();
152
+ let configDbSchema = Lobb.instance.configManager.getDbSchema();
153
+
154
+ if (specificColleciton) {
155
+ if (dbSchema[specificColleciton]) {
156
+ dbSchema = {
157
+ [specificColleciton]: dbSchema[specificColleciton],
158
+ };
159
+ } else {
160
+ dbSchema = {};
161
+ }
162
+ if (configDbSchema[specificColleciton]) {
163
+ configDbSchema = {
164
+ [specificColleciton]: configDbSchema[specificColleciton],
165
+ };
166
+ } else {
167
+ configDbSchema = {};
168
+ }
169
+ }
170
+
171
+ return diff(dbSchema, configDbSchema);
172
+ }
173
+ }
@@ -0,0 +1,95 @@
1
+ import type { Migration, MigrationProps, Migrations } from "../types/index.ts";
2
+
3
+ import { coreMigrations } from "../coreMigrations/index.ts";
4
+ import { LobbError } from "../LobbError.ts";
5
+ import { Lobb } from "../Lobb.ts";
6
+
7
+ export class MigrationsManager {
8
+ private dbDriver = Lobb.instance.databaseService.getDriver();
9
+
10
+ public async init() {
11
+ await this.handleMigrations();
12
+ }
13
+
14
+ private async handleMigrations(): Promise<void> {
15
+ const migrationOwners = [
16
+ ...Lobb.instance.configManager.getExtensionNames(),
17
+ "__core",
18
+ "__project",
19
+ ];
20
+
21
+ for (const migrationOwner of migrationOwners) {
22
+ const appliedNames = await this.getAppliedMigrationNamesByOwner(migrationOwner);
23
+ const migrations = await this.getMigrations(migrationOwner, appliedNames);
24
+ await this.applyMigrations(migrationOwner, migrations);
25
+ }
26
+ }
27
+
28
+ private async getAppliedMigrationNamesByOwner(owner: string): Promise<Set<string>> {
29
+ const result = await Lobb.instance.collectionService.findAll({
30
+ collectionName: "core_migrations",
31
+ params: { filter: { owner } },
32
+ });
33
+ return new Set(result.data.map((row) => row.migration_name as string));
34
+ }
35
+
36
+ private async getMigrations(
37
+ owner: string,
38
+ appliedNames: Set<string>,
39
+ ): Promise<Migrations> {
40
+ let allMigrations: Migrations = {};
41
+
42
+ if (owner === "__core") {
43
+ allMigrations = coreMigrations;
44
+ } else if (owner === "__project") {
45
+ allMigrations = Lobb.instance.configManager.config.migrations ?? {};
46
+ } else {
47
+ allMigrations = Lobb.instance.extensionSystem.getMigrations(owner);
48
+ }
49
+
50
+ return Object.fromEntries(
51
+ Object.entries(allMigrations).filter(([name]) => !appliedNames.has(name)),
52
+ );
53
+ }
54
+
55
+ private async applyMigrations(
56
+ migrationOwner: string,
57
+ migrations: Migrations,
58
+ ): Promise<void> {
59
+ for (const [migrationName, migration] of Object.entries(migrations)) {
60
+ let ownerForMessage: string;
61
+
62
+ if (migrationOwner === "__project") {
63
+ ownerForMessage = "CURRENT PROJECT";
64
+ } else if (migrationOwner === "__core") {
65
+ ownerForMessage = "LOBB CORE";
66
+ } else {
67
+ ownerForMessage = migrationOwner;
68
+ }
69
+
70
+ console.log(
71
+ `Executing the (${migrationName}) migration of (${ownerForMessage})`,
72
+ );
73
+ const migrationProps: MigrationProps = {
74
+ driver: Lobb.instance.databaseService.getDriver(),
75
+ };
76
+ try {
77
+ await (migration as Migration).up(migrationProps);
78
+ } catch (error) {
79
+ console.error(`(MIGRATIONS ERROR): ${error}`);
80
+ throw new LobbError({
81
+ code: "INTERNAL_SERVER_ERROR",
82
+ message:
83
+ `An Error happened trying to execute the (project) type at the (${migrationName}) migration`,
84
+ });
85
+ }
86
+ await Lobb.instance.collectionService.createOne({
87
+ collectionName: "core_migrations",
88
+ data: {
89
+ owner: migrationOwner,
90
+ migration_name: migrationName,
91
+ },
92
+ });
93
+ }
94
+ }
95
+ }