@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.
- package/package.json +48 -0
- package/src/Lobb.ts +150 -0
- package/src/LobbError.ts +105 -0
- package/src/TypesGenerator.ts +11 -0
- package/src/api/WebServer.ts +126 -0
- package/src/api/collections/CollectionControllers.ts +485 -0
- package/src/api/collections/CollectionService.ts +162 -0
- package/src/api/collections/collectionRoutes.ts +105 -0
- package/src/api/collections/collectionStore.ts +647 -0
- package/src/api/collections/transactions.ts +166 -0
- package/src/api/collections/utils.ts +73 -0
- package/src/api/errorHandler.ts +73 -0
- package/src/api/events/index.ts +129 -0
- package/src/api/meta/route.ts +66 -0
- package/src/api/meta/service.ts +163 -0
- package/src/api/middlewares.ts +71 -0
- package/src/api/openApiRoute.ts +1017 -0
- package/src/api/schema/SchemaService.ts +71 -0
- package/src/api/schema/schemaRoutes.ts +13 -0
- package/src/config/ConfigManager.ts +252 -0
- package/src/config/validations.ts +49 -0
- package/src/coreCollections/collectionsCollection.ts +56 -0
- package/src/coreCollections/index.ts +14 -0
- package/src/coreCollections/migrationsCollection.ts +36 -0
- package/src/coreCollections/queryCollection.ts +26 -0
- package/src/coreCollections/workflowsCollection.ts +73 -0
- package/src/coreDbSetup/index.ts +72 -0
- package/src/coreMigrations/index.ts +3 -0
- package/src/database/DatabaseService.ts +44 -0
- package/src/database/DatabaseSyncManager.ts +173 -0
- package/src/database/MigrationsManager.ts +95 -0
- package/src/database/drivers/MongoDriver.ts +750 -0
- package/src/database/drivers/pgDriver/PGDriver.ts +655 -0
- package/src/database/drivers/pgDriver/QueryBuilder.ts +474 -0
- package/src/database/drivers/pgDriver/utils.ts +6 -0
- package/src/events/EventSystem.ts +191 -0
- package/src/events/coreEvents/index.ts +218 -0
- package/src/events/studioEvents/index.ts +32 -0
- package/src/extension/ExtensionSystem.ts +236 -0
- package/src/extension/dashboardRoute.ts +35 -0
- package/src/fields/ArrayField.ts +33 -0
- package/src/fields/BoolField.ts +34 -0
- package/src/fields/DateField.ts +13 -0
- package/src/fields/DateTimeField.ts +13 -0
- package/src/fields/DecimalField.ts +13 -0
- package/src/fields/FieldUtils.ts +56 -0
- package/src/fields/FloatField.ts +13 -0
- package/src/fields/IntegerField.ts +13 -0
- package/src/fields/LongField.ts +13 -0
- package/src/fields/ObjectField.ts +15 -0
- package/src/fields/StringField.ts +13 -0
- package/src/fields/TextField.ts +13 -0
- package/src/fields/TimeField.ts +13 -0
- package/src/index.ts +53 -0
- package/src/studio/Studio.ts +108 -0
- package/src/types/CollectionControllers.ts +15 -0
- package/src/types/DatabaseDriver.ts +115 -0
- package/src/types/Extension.ts +46 -0
- package/src/types/Field.ts +29 -0
- package/src/types/apiSchema.ts +12 -0
- package/src/types/collectionServiceSchema.ts +18 -0
- package/src/types/config/collectionFields.ts +85 -0
- package/src/types/config/collectionsConfig.ts +50 -0
- package/src/types/config/config.ts +66 -0
- package/src/types/config/relations.ts +17 -0
- package/src/types/filterSchema.ts +88 -0
- package/src/types/index.ts +38 -0
- package/src/types/migrations.ts +12 -0
- package/src/types/websockets.ts +34 -0
- package/src/types/workflows/processors.ts +1 -0
- package/src/utils/lockCollectionToObject.ts +204 -0
- package/src/utils/utils.ts +310 -0
- package/src/workflows/WorkflowSystem.ts +182 -0
- package/src/workflows/coreWorkflows/collectionsTable/index.ts +118 -0
- package/src/workflows/coreWorkflows/index.ts +18 -0
- package/src/workflows/coreWorkflows/processors/postOperationsWorkflows.ts +46 -0
- package/src/workflows/coreWorkflows/processors/preOperationsWorkflows.ts +27 -0
- package/src/workflows/coreWorkflows/processors/processorForDB.ts +13 -0
- package/src/workflows/coreWorkflows/processors/processors/processor.ts +23 -0
- package/src/workflows/coreWorkflows/processors/processors/processorsFunctions.ts +47 -0
- package/src/workflows/coreWorkflows/processors/utils.ts +102 -0
- package/src/workflows/coreWorkflows/processors/validator/validator.ts +19 -0
- package/src/workflows/coreWorkflows/processors/validator/validatorsFunction.ts +52 -0
- package/src/workflows/coreWorkflows/queryCoreWorkflows.ts +31 -0
- package/src/workflows/coreWorkflows/utilsCoreWorkflows.ts +40 -0
- 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
|
+
}
|