@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,71 @@
1
+ import type { CollectionConfig } from "../../types/index.ts";
2
+ import { Lobb } from "../../Lobb.ts";
3
+ import type { DatabaseDriver } from "../../types/index.ts";
4
+
5
+ export class SchemaService {
6
+ private dbDriver: DatabaseDriver;
7
+
8
+ constructor(dbDriver: DatabaseDriver) {
9
+ this.dbDriver = dbDriver;
10
+ }
11
+
12
+ public static async init() {
13
+ const driver = Lobb.instance.databaseService.getDriver();
14
+ const databaseSyncManager = new SchemaService(driver);
15
+ return databaseSyncManager;
16
+ }
17
+
18
+ public async getDiff() {
19
+ return {
20
+ configSchema: Lobb.instance.configManager.getDbSchema(),
21
+ dbSchema: await this.dbDriver.getDbSchema(),
22
+ };
23
+ }
24
+
25
+ public async createCollection(
26
+ collectionName: string,
27
+ collection: CollectionConfig,
28
+ effectDb: boolean = true,
29
+ ) {
30
+ if (effectDb) {
31
+ await this.dbDriver.createCollection(
32
+ collectionName,
33
+ collection,
34
+ );
35
+ }
36
+ Lobb.instance.configManager.addCollection(
37
+ collectionName,
38
+ collection,
39
+ );
40
+ }
41
+
42
+ public async updateCollection(
43
+ collectionName: string,
44
+ collection: CollectionConfig,
45
+ effectDb: boolean = true,
46
+ ) {
47
+ if (effectDb) {
48
+ await Lobb.instance.databaseSyncManager.syncDatabase(
49
+ collectionName,
50
+ );
51
+ }
52
+ Lobb.instance.configManager.updateCollection(
53
+ collectionName,
54
+ collection,
55
+ );
56
+ }
57
+
58
+ public async deleteCollection(
59
+ collectionName: string,
60
+ effectDb: boolean = true,
61
+ ) {
62
+ if (effectDb) {
63
+ await this.dbDriver.dropCollection(
64
+ collectionName,
65
+ );
66
+ }
67
+ Lobb.instance.configManager.removeCollection(
68
+ collectionName,
69
+ );
70
+ }
71
+ }
@@ -0,0 +1,13 @@
1
+ import { type Context, Hono } from "hono";
2
+ import { Lobb } from "@lobb-js/core";
3
+
4
+ export function getSchemaRoutes() {
5
+ const route = new Hono();
6
+
7
+ route.get("diff", async (c: Context) => {
8
+ const difference = await Lobb.instance.schemaService.getDiff();
9
+ return c.json(difference);
10
+ });
11
+
12
+ return route;
13
+ }
@@ -0,0 +1,252 @@
1
+ import type { Config } from "../types/index.ts";
2
+ import {
3
+ type CollectionConfig,
4
+ CollectionConfigSchema,
5
+ type CollectionsConfig,
6
+ } from "../types/index.ts";
7
+ import type { CollectionField } from "../types/index.ts";
8
+ import type { Extension } from "../types/index.ts";
9
+ import type { RelationsConfig } from "../types/index.ts";
10
+
11
+ import _ from "lodash";
12
+ import { coreCollections } from "../coreCollections/index.ts";
13
+ import { LobbError } from "../LobbError.ts";
14
+ import { validateConfig } from "./validations.ts";
15
+
16
+ /**
17
+ * Config class is responsible for loading the user configuration and checking if
18
+ * it's valid
19
+ */
20
+ export class ConfigManager {
21
+ public config!: Config;
22
+
23
+ constructor(config: Config) {
24
+ this.config = config;
25
+ }
26
+
27
+ /**
28
+ * Loads the data and checks if its valid
29
+ */
30
+ static async init(config: Config) {
31
+ const configManager = new ConfigManager(config);
32
+ configManager.loadCoreCollections();
33
+ validateConfig(configManager.config, configManager);
34
+ return configManager;
35
+ }
36
+
37
+ private loadCoreCollections() {
38
+ this.config.collections = {
39
+ ...this.config.collections,
40
+ ...coreCollections(),
41
+ };
42
+ }
43
+
44
+ public addRelationsFromIntegerRefrences() {
45
+ for (
46
+ const [collectionName, collectionValue] of Object.entries(
47
+ this.config.collections,
48
+ )
49
+ ) {
50
+ for (
51
+ const [fieldName, fieldValue] of Object.entries(
52
+ this.config.collections[collectionName].fields,
53
+ )
54
+ ) {
55
+ if (fieldValue.type === "integer" && fieldValue.references) {
56
+ if (!this.config.collections[fieldValue.references.collection]) {
57
+ throw new LobbError({
58
+ code: "INTERNAL_SERVER_ERROR",
59
+ message: `Field (${collectionName}.${fieldName}) references collection (${fieldValue.references.collection}) which does not exist`,
60
+ });
61
+ }
62
+ if (!this.config.relations) {
63
+ this.config.relations = [];
64
+ }
65
+ this.config.relations.push({
66
+ from: {
67
+ collection: collectionName,
68
+ field: fieldName,
69
+ },
70
+ to: {
71
+ collection: fieldValue.references.collection,
72
+ field: fieldValue.references.field,
73
+ },
74
+ });
75
+ }
76
+ }
77
+ }
78
+ }
79
+
80
+ public getDbSchema(): CollectionsConfig {
81
+ const collections = _.cloneDeep(this.config.collections);
82
+
83
+ // Iterate over the collections and map each field to only include the 'type' property
84
+ for (const collectionName in collections) {
85
+ // Keep only the 'indexes' and 'fields' properties in a collection
86
+ collections[collectionName] = {
87
+ indexes: collections[collectionName].indexes,
88
+ fields: collections[collectionName].fields,
89
+ };
90
+ const fields = collections[collectionName].fields;
91
+ for (const fieldName in fields) {
92
+ // including only the properties that are db related
93
+ const field: any = {};
94
+ field["type"] = fields[fieldName].type;
95
+ // TODO: this is a property related to only varchar fields. have a better way to
96
+ // include local field properties that are the related to the db instead of
97
+ // hardcoding them here
98
+ if (fields[fieldName].type === "string" && fields[fieldName].length) {
99
+ field["length"] = fields[fieldName].length;
100
+ }
101
+
102
+ fields[fieldName] = field;
103
+ }
104
+ }
105
+
106
+ return collections;
107
+ }
108
+
109
+ public getCollections(): CollectionsConfig {
110
+ const collection = this.config.collections;
111
+
112
+ return collection;
113
+ }
114
+
115
+ public getCollection(collectionName: string): CollectionConfig {
116
+ const collection = this.config.collections[collectionName];
117
+
118
+ if (!collection) {
119
+ throw new LobbError({
120
+ code: "INTERNAL_SERVER_ERROR",
121
+ message: `The (${collectionName}) collection doesnt exist`,
122
+ });
123
+ }
124
+
125
+ return collection;
126
+ }
127
+
128
+ public getFieldNames(collectionName: string) {
129
+ return Object.keys(
130
+ this.getCollection(collectionName).fields,
131
+ );
132
+ }
133
+
134
+ public fieldExists(
135
+ fieldName: string,
136
+ collectionName: string,
137
+ ): boolean {
138
+ return Boolean(
139
+ this.getCollection(collectionName).fields[fieldName],
140
+ );
141
+ }
142
+
143
+ public collectionExists(collectionName: string): boolean {
144
+ return Boolean(this.config.collections[collectionName]);
145
+ }
146
+
147
+ public getField(
148
+ fieldName: string,
149
+ collectionName: string,
150
+ ): CollectionField {
151
+ const field = this.getCollection(collectionName).fields[fieldName];
152
+ if (!field) {
153
+ throw new LobbError({
154
+ code: "BAD_REQUEST",
155
+ message: `The (${fieldName}) Field Doesnt Exist`,
156
+ });
157
+ }
158
+ return field;
159
+ }
160
+
161
+ public getCollectionsNames() {
162
+ if (!this.config) {
163
+ throw new LobbError({
164
+ code: "INTERNAL_SERVER_ERROR",
165
+ message: "the data object is not found",
166
+ });
167
+ }
168
+ return Object.keys(this.config.collections);
169
+ }
170
+
171
+ public getExtensions() {
172
+ return this.config.extensions;
173
+ }
174
+
175
+ public getExtension(extensionName: string): Extension | null {
176
+ const extensions = this.config.extensions;
177
+
178
+ if (extensions) {
179
+ for (let index = 0; index < extensions.length; index++) {
180
+ const extension = extensions[index];
181
+ if (extensionName === extension.name) {
182
+ return extension;
183
+ }
184
+ }
185
+ }
186
+
187
+ return null;
188
+ }
189
+
190
+ public getExtensionNames() {
191
+ const extensions = this.config.extensions;
192
+ if (extensions) {
193
+ return extensions.map((ext) => ext.name);
194
+ }
195
+ return [];
196
+ }
197
+
198
+ public getVirtualCollections(): CollectionsConfig {
199
+ const virtualCollections: CollectionsConfig = {};
200
+ const collections = this.getCollections();
201
+ for (const [collectionName, collection] of Object.entries(collections)) {
202
+ const collectionFieldsNames = Object.keys(collection.fields);
203
+ if (collectionFieldsNames.length <= 1) {
204
+ virtualCollections[collectionName] = collection;
205
+ }
206
+ }
207
+ return virtualCollections;
208
+ }
209
+
210
+ public addCollection(collectionName: string, collectionValue: unknown) {
211
+ const collectionExists = this.collectionExists(collectionName);
212
+ if (collectionExists) {
213
+ throw new LobbError({
214
+ code: "BAD_REQUEST",
215
+ message: `The (${collectionName}) collection already exists`,
216
+ });
217
+ }
218
+ const parsedCollectionValue = CollectionConfigSchema.parse(collectionValue);
219
+ this.config.collections[collectionName] = parsedCollectionValue;
220
+ }
221
+
222
+ public updateCollection(collectionName: string, collectionValue: unknown) {
223
+ const collectionExists = this.collectionExists(collectionName);
224
+ if (!collectionExists) {
225
+ throw new LobbError({
226
+ code: "BAD_REQUEST",
227
+ message: `The (${collectionName}) collection doesnt exist`,
228
+ });
229
+ }
230
+ const parsedCollectionValue = CollectionConfigSchema.parse(collectionValue);
231
+ this.config.collections[collectionName] = parsedCollectionValue;
232
+ }
233
+
234
+ public removeCollection(collectionName: string) {
235
+ delete this.config.collections[collectionName];
236
+ }
237
+
238
+ public getChildRelations(collectionName: string): RelationsConfig {
239
+ if (this.config.relations) {
240
+ const childrenRelations = this.config.relations.filter((relation) =>
241
+ relation.to.collection === collectionName
242
+ );
243
+ return childrenRelations;
244
+ }
245
+
246
+ return [];
247
+ }
248
+
249
+ public isCollectionSingleton(collectionName: string): boolean {
250
+ return Boolean(this.config.collections[collectionName]?.singleton);
251
+ }
252
+ }
@@ -0,0 +1,49 @@
1
+ import type { Config } from "../types/index.ts";
2
+ import type { ConfigManager } from "./ConfigManager.ts";
3
+
4
+ export function validateConfig(config: Config, configManager: ConfigManager) {
5
+ validateRelations(config, configManager);
6
+ validateDBNamesAreSnakeCase(config);
7
+ }
8
+
9
+ function validateRelations(config: Config, configManager: ConfigManager) {
10
+ if (config.relations) {
11
+ for (let index = 0; index < config.relations.length; index++) {
12
+ const relation = config.relations[index];
13
+ configManager.getCollection(relation.from.collection);
14
+ configManager.getField(relation.from.field, relation.from.collection);
15
+ if (!Array.isArray(relation.to)) {
16
+ configManager.getCollection(relation.to.collection);
17
+ configManager.getField(relation.to.field, relation.to.collection);
18
+ }
19
+ }
20
+ }
21
+ }
22
+
23
+ function validateDBNamesAreSnakeCase(
24
+ config: Config,
25
+ ) {
26
+ function isSnakeCase(str: string): boolean {
27
+ return /^[a-z]+(_[a-z]+)*$/.test(str);
28
+ }
29
+ for (
30
+ const [collectionName, collectionValue] of Object.entries(
31
+ config.collections,
32
+ )
33
+ ) {
34
+ if (!isSnakeCase(collectionName)) {
35
+ throw new Error(
36
+ `The name of the (${collectionName}) collection should be snake case`,
37
+ );
38
+ }
39
+ for (
40
+ const [fieldName, _] of Object.entries(collectionValue.fields)
41
+ ) {
42
+ if (!isSnakeCase(fieldName)) {
43
+ throw new Error(
44
+ `The name of the (${collectionName}.${fieldName}) field name should be snake case`,
45
+ );
46
+ }
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,56 @@
1
+ import {
2
+ type CollectionConfig,
3
+ CollectionConfigSchema,
4
+ } from "../types/index.ts";
5
+
6
+ import { convertSchemaToTypeString } from "../utils/utils.ts";
7
+
8
+ export function getCollectionsCollection(): CollectionConfig {
9
+ return {
10
+ "indexes": {
11
+ "core_collections_unique_index": {
12
+ "unique": true,
13
+ "fields": {
14
+ "name": {
15
+ "order": "asc",
16
+ },
17
+ },
18
+ },
19
+ },
20
+ "fields": {
21
+ "id": {
22
+ "type": "integer",
23
+ },
24
+ "name": {
25
+ "type": "string",
26
+ "length": 255,
27
+ "validators": {
28
+ "required": true,
29
+ },
30
+ },
31
+ "value": {
32
+ "type": "text",
33
+ "validators": {
34
+ "required": true,
35
+ },
36
+ "pre_processors": {
37
+ "default":
38
+ 'async function collection(): Promise<Collection> {\n\treturn {\n\t\tindexes: {},\n\t\tfields: {\n\t\t\tid: {\n\t\t\t\ttype: "integer",\n\t\t\t},\n\t\t},\n\t};\n}',
39
+ },
40
+ "ui": {
41
+ "input": {
42
+ "type": "code",
43
+ "args": {
44
+ "type": "typescript",
45
+ "height": 200,
46
+ "types": convertSchemaToTypeString(
47
+ "Collection",
48
+ CollectionConfigSchema,
49
+ ),
50
+ },
51
+ },
52
+ },
53
+ },
54
+ },
55
+ };
56
+ }
@@ -0,0 +1,14 @@
1
+ import type { CollectionConfig } from "../types/index.ts";
2
+ import { migrationsCollection } from "./migrationsCollection.ts";
3
+ import { workflowsCollection } from "./workflowsCollection.ts";
4
+ import { getCollectionsCollection } from "./collectionsCollection.ts";
5
+ import { getQueryCollection } from "./queryCollection.ts";
6
+
7
+ export function coreCollections(): Record<string, CollectionConfig> {
8
+ const collectionsSchemas: Record<string, CollectionConfig> = {};
9
+ collectionsSchemas["core_workflows"] = workflowsCollection;
10
+ collectionsSchemas["core_migrations"] = migrationsCollection;
11
+ collectionsSchemas["core_collections"] = getCollectionsCollection();
12
+ collectionsSchemas["core_query"] = getQueryCollection();
13
+ return collectionsSchemas;
14
+ }
@@ -0,0 +1,36 @@
1
+ import type { CollectionConfig } from "../types/index.ts";
2
+
3
+ export const migrationsCollection: CollectionConfig = {
4
+ "indexes": {
5
+ "core_migrations_unique_index": {
6
+ "unique": true,
7
+ "fields": {
8
+ "owner": {
9
+ "order": "asc",
10
+ },
11
+ "migration_name": {
12
+ "order": "asc",
13
+ },
14
+ },
15
+ },
16
+ },
17
+ "fields": {
18
+ "id": {
19
+ "type": "integer",
20
+ },
21
+ "owner": {
22
+ "type": "string",
23
+ "length": 255,
24
+ "validators": {
25
+ "required": true,
26
+ },
27
+ },
28
+ "migration_name": {
29
+ "type": "string",
30
+ "length": 255,
31
+ "validators": {
32
+ "required": true,
33
+ },
34
+ },
35
+ },
36
+ };
@@ -0,0 +1,26 @@
1
+ import type { CollectionConfig } from "../types/index.ts";
2
+
3
+ export function getQueryCollection(): CollectionConfig {
4
+ return {
5
+ "indexes": {},
6
+ "fields": {
7
+ "id": {
8
+ "type": "integer",
9
+ },
10
+ "query": {
11
+ "type": "text",
12
+ "validators": {
13
+ "required": true,
14
+ },
15
+ "ui": {
16
+ "input": {
17
+ "type": "code",
18
+ "args": {
19
+ "type": "sql",
20
+ },
21
+ },
22
+ },
23
+ },
24
+ },
25
+ };
26
+ }
@@ -0,0 +1,73 @@
1
+ import type { CollectionConfig } from "../types/index.ts";
2
+
3
+ export const workflowsCollection: CollectionConfig = {
4
+ "indexes": {},
5
+ "fields": {
6
+ "id": {
7
+ "type": "integer",
8
+ },
9
+ "name": {
10
+ "type": "string",
11
+ "length": 255,
12
+ "validators": {
13
+ "required": true,
14
+ },
15
+ },
16
+ "event_name": {
17
+ "type": "string",
18
+ "length": 255,
19
+ },
20
+ "directory": {
21
+ "type": "string",
22
+ "length": 255,
23
+ },
24
+ "input_schema": {
25
+ "type": "text",
26
+ "pre_processors": {
27
+ "default": "function schema(z) {\n\treturn z.object({});\n}",
28
+ },
29
+ "ui": {
30
+ "input": {
31
+ "type": "code",
32
+ "args": {
33
+ "type": "typescript",
34
+ "height": 200,
35
+ },
36
+ },
37
+ },
38
+ },
39
+ "output_schema": {
40
+ "type": "text",
41
+ "pre_processors": {
42
+ "default": "function schema(z) {\n\treturn z.object({});\n}",
43
+ },
44
+ "ui": {
45
+ "input": {
46
+ "type": "code",
47
+ "args": {
48
+ "type": "typescript",
49
+ "height": 200,
50
+ },
51
+ },
52
+ },
53
+ },
54
+ "handler": {
55
+ "type": "text",
56
+ "validators": {
57
+ "required": true,
58
+ },
59
+ "pre_processors": {
60
+ "default":
61
+ "async function workflow(input: Input, ctx: Context): Promise<Output> {\n\treturn input;\n}",
62
+ },
63
+ "ui": {
64
+ "input": {
65
+ "type": "code",
66
+ "args": {
67
+ "type": "typescript",
68
+ },
69
+ },
70
+ },
71
+ },
72
+ },
73
+ };
@@ -0,0 +1,72 @@
1
+ import type { DatabaseDriver } from "../types/index.ts";
2
+ import { Lobb } from "../Lobb.ts";
3
+
4
+ // DB setup tasks run on every startup, before migrations and schema sync.
5
+ // They are idempotent — safe to run multiple times — and handle DB setup
6
+ // that must be in place before anything else can proceed.
7
+ type DbSetupTask = (driver: DatabaseDriver) => Promise<void>;
8
+
9
+ const coreDbSetupTasks: DbSetupTask[] = [
10
+
11
+ // Creates the core_migrations table if it doesn't exist, and ensures its
12
+ // schema is up to date. We use raw SQL with IF NOT EXISTS / ADD COLUMN IF
13
+ // NOT EXISTS so this is safe to run against both fresh and old databases.
14
+ // We cannot use driver.createCollection here because it calls CREATE TABLE
15
+ // IF NOT EXISTS (which silently skips when the table already exists) and
16
+ // then unconditionally tries to create indexes — crashing on old databases
17
+ // that are missing the migration_name column.
18
+ // TODO: REMOVE THIS AFTER SOME TIME BECAUSE ITS NOT NEEDED
19
+ async (driver) => {
20
+ await driver.query(`
21
+ CREATE TABLE IF NOT EXISTS core_migrations (
22
+ "id" SERIAL PRIMARY KEY,
23
+ "owner" VARCHAR(255) NOT NULL,
24
+ "migration_name" VARCHAR(255) NOT NULL
25
+ )
26
+ `);
27
+
28
+ // Older versions of Lobb created this table without migration_name.
29
+ await driver.query(
30
+ `ALTER TABLE core_migrations ADD COLUMN IF NOT EXISTS "migration_name" VARCHAR(255) NOT NULL DEFAULT ''`,
31
+ );
32
+
33
+ await driver.query(`
34
+ CREATE UNIQUE INDEX IF NOT EXISTS core_migrations_unique_index
35
+ ON core_migrations ("owner" ASC, "migration_name" ASC)
36
+ `);
37
+ },
38
+
39
+ // Converts any timezone-naive timestamp/time columns to their timezone-aware
40
+ // counterparts. Lobb only works with TIMESTAMPTZ / TIMETZ — older lobb version generated databases
41
+ // that have these columns in the wrong type, which would crash schema reading.
42
+ // TODO: REMOVE THIS AFTER SOME TIME BECAUSE ITS NOT NEEDED
43
+ async (driver) => {
44
+ const columns = await driver.query<{
45
+ table_name: string;
46
+ column_name: string;
47
+ data_type: string;
48
+ }>(
49
+ `SELECT table_name, column_name, data_type
50
+ FROM information_schema.columns
51
+ WHERE table_schema = 'public'
52
+ AND data_type IN ('timestamp without time zone', 'time without time zone')`,
53
+ );
54
+
55
+ for (const col of columns) {
56
+ const newType = col.data_type === "timestamp without time zone"
57
+ ? "TIMESTAMPTZ"
58
+ : "TIMETZ";
59
+ await driver.query(
60
+ `ALTER TABLE "${col.table_name}" ALTER COLUMN "${col.column_name}" TYPE ${newType}`,
61
+ );
62
+ }
63
+ },
64
+
65
+ ];
66
+
67
+ export async function runCoreDbSetup() {
68
+ const driver = Lobb.instance.databaseService.getDriver();
69
+ for (const task of coreDbSetupTasks) {
70
+ await task(driver);
71
+ }
72
+ }
@@ -0,0 +1,3 @@
1
+ import type { Migrations } from "../types/index.ts";
2
+
3
+ export const coreMigrations: Migrations = {};