@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
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@lobb-js/core",
3
+ "version": "0.13.0",
4
+ "license": "AGPL-3.0-only",
5
+ "type": "module",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "files": [
10
+ "src"
11
+ ],
12
+ "exports": {
13
+ ".": "./src/index.ts"
14
+ },
15
+ "scripts": {
16
+ "test": "bun test tests",
17
+ "dev": "bun run src/index.ts"
18
+ },
19
+ "dependencies": {
20
+ "@faker-js/faker": "^9.8.0",
21
+ "@scalar/hono-api-reference": "^0.7.2",
22
+ "cron": "^4.4.0",
23
+ "cron-validator": "^1.4.0",
24
+ "hono": "^4.7.0",
25
+ "just-diff": "^6.0.2",
26
+ "lodash": "^4.17.21",
27
+ "mingo": "^6.4.15",
28
+ "nunjucks": "^3.2.4",
29
+ "openapi-types": "^12.1.3",
30
+ "pg": "^8.13.3",
31
+ "pg-format": "^1.0.4",
32
+ "qs": "^6.12.1",
33
+ "sucrase": "^3.35.0",
34
+ "validator": "^13.12.0",
35
+ "zod": "^3.24.2",
36
+ "zod-to-json-schema": "^3.24.6",
37
+ "zod-to-ts": "^1.2.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/lodash": "^4.17.0",
41
+ "@types/nunjucks": "^3.2.6",
42
+ "@types/pg": "^8.11.0",
43
+ "@types/pg-format": "^1.0.5",
44
+ "@types/qs": "^6.9.0",
45
+ "@types/validator": "^13.12.0",
46
+ "bun-types": "latest"
47
+ }
48
+ }
package/src/Lobb.ts ADDED
@@ -0,0 +1,150 @@
1
+ import type { CollectionControllers } from "./types/index.ts";
2
+ import type { Config } from "./types/index.ts";
3
+
4
+ import { ExtensionSystem } from "./extension/ExtensionSystem.ts";
5
+ import { DatabaseService } from "./database/DatabaseService.ts";
6
+ import { WorkflowSystem } from "./workflows/WorkflowSystem.ts";
7
+ import { DatabaseSyncManager } from "./database/DatabaseSyncManager.ts";
8
+ import { WebServer } from "./api/WebServer.ts";
9
+ import { collectionControllers } from "./api/collections/CollectionControllers.ts";
10
+ import { ConfigManager } from "./config/ConfigManager.ts";
11
+ import { Utils } from "./utils/utils.ts";
12
+ import { transactions } from "./api/collections/transactions.ts";
13
+ import { EventSystem } from "./events/EventSystem.ts";
14
+ import { SchemaService } from "./api/schema/SchemaService.ts";
15
+ import { TypesGenerator } from "./TypesGenerator.ts";
16
+ import { CollectionService } from "./api/collections/CollectionService.ts";
17
+ import { CollectionStore } from "./api/collections/collectionStore.ts";
18
+ import { Studio } from "./studio/Studio.ts";
19
+ import { dirname } from "node:path";
20
+
21
+ export function run(config: Config) {
22
+ return Lobb.init(config);
23
+ }
24
+
25
+ export class Lobb {
26
+ public static instance: Lobb;
27
+ public configManager!: ConfigManager;
28
+ public databaseService!: DatabaseService;
29
+ public workflowSystem!: WorkflowSystem;
30
+ public eventSystem!: EventSystem;
31
+ public databaseSyncManager!: DatabaseSyncManager;
32
+ public schemaService!: SchemaService;
33
+ public webServer!: WebServer;
34
+ public extensionSystem!: ExtensionSystem;
35
+ public utils = Utils;
36
+ public collectionControllers!: CollectionControllers;
37
+ public transactions!: typeof transactions;
38
+ public typesGenerator!: TypesGenerator;
39
+ public collectionStore!: CollectionStore;
40
+ public collectionService!: CollectionService;
41
+ private studio!: Studio;
42
+ private tempDatabase = false;
43
+
44
+ constructor() {}
45
+
46
+ public static async init(config: Config) {
47
+ const projectDir = dirname(Bun.main);
48
+
49
+ const studio = new Studio(projectDir);
50
+
51
+ const isTempDatabase = config.database.database === "*";
52
+ if (isTempDatabase) {
53
+ config = {
54
+ ...config,
55
+ database: {
56
+ ...config.database,
57
+ database: `lobb_test_${crypto.randomUUID().replace(/-/g, "")}`,
58
+ },
59
+ };
60
+ }
61
+
62
+ console.log("starting the app");
63
+ const lobb = new Lobb();
64
+ lobb.studio = studio;
65
+ lobb.tempDatabase = isTempDatabase;
66
+ Lobb.instance = lobb;
67
+ lobb.eventSystem = new EventSystem();
68
+ lobb.workflowSystem = new WorkflowSystem();
69
+ lobb.extensionSystem = ExtensionSystem.init();
70
+ lobb.initShutdownSignalListeners();
71
+ lobb.configManager = await ConfigManager.init(config);
72
+ await lobb.afterConfigInit();
73
+ lobb.databaseService = await DatabaseService.init();
74
+ lobb.collectionStore = new CollectionStore();
75
+ lobb.collectionService = new CollectionService();
76
+ await lobb.eventSystem.init();
77
+ await lobb.workflowSystem.init();
78
+ lobb.databaseSyncManager = await DatabaseSyncManager.init();
79
+ lobb.schemaService = await SchemaService.init();
80
+ const webPort = lobb.configManager.config.web_server.port ?? 3000;
81
+ const studioPort = await studio.start(webPort);
82
+ lobb.webServer = await WebServer.init(studioPort);
83
+ lobb.collectionControllers = collectionControllers;
84
+ lobb.transactions = transactions;
85
+ await lobb.extensionSystem.executeExtensionsInitMethods();
86
+
87
+ // emit the core.init event
88
+ await lobb.eventSystem.emit("core.init", {});
89
+
90
+ // generate types
91
+ lobb.typesGenerator = await TypesGenerator.init();
92
+
93
+ console.log("The app is running");
94
+
95
+ return lobb;
96
+ }
97
+
98
+ public async close() {
99
+ // execute the close methods of the extensions
100
+ await this.extensionSystem.executeExtensionsCloseMethods();
101
+
102
+ await this.workflowSystem.close();
103
+ await this.eventSystem.close();
104
+ if (this.webServer) await this.webServer.close();
105
+ if (this.databaseService) await this.databaseService.close();
106
+ if (this.tempDatabase) await this.databaseService.dropDatabase();
107
+ this.studio?.stop();
108
+
109
+ if (process.platform === "win32") {
110
+ process.off("SIGBREAK", this.onGracefulShutdown);
111
+ } else {
112
+ process.off("SIGTERM", this.onGracefulShutdown);
113
+ }
114
+ process.off("SIGINT", this.onGracefulShutdown);
115
+
116
+ // reset to the real current working directory if it was changed
117
+ // by the context property in the project section
118
+ process.chdir((globalThis as any).currentDirectory);
119
+ }
120
+
121
+ private initShutdownSignalListeners() {
122
+ if (process.platform === "win32") {
123
+ process.on("SIGBREAK", this.onGracefulShutdown);
124
+ } else {
125
+ process.on("SIGTERM", this.onGracefulShutdown);
126
+ }
127
+ process.on("SIGINT", this.onGracefulShutdown);
128
+ }
129
+
130
+ private onGracefulShutdown = async () => {
131
+ console.log("Performing graceful shutdown tasks...");
132
+
133
+ await this.close();
134
+
135
+ console.log("Done Performing graceful shutdown tasks!");
136
+
137
+ // exit so that all recources and ports are freed
138
+ process.exit(0);
139
+ };
140
+
141
+ private async afterConfigInit() {
142
+ // change the current directory
143
+ const config = Lobb.instance.configManager.config;
144
+ (globalThis as any).currentDirectory = process.cwd();
145
+ process.chdir(config.project.context ?? ".");
146
+
147
+ await Lobb.instance.extensionSystem.loadExtensionsCollections();
148
+ this.configManager.addRelationsFromIntegerRefrences();
149
+ }
150
+ }
@@ -0,0 +1,105 @@
1
+ import { z } from "zod";
2
+
3
+ export const LobbStatusCodes = {
4
+ CONTINUE: 100,
5
+ SWITCHING_PROTOCOLS: 101,
6
+ PROCESSING: 102,
7
+ EARLY_HINTS: 103,
8
+
9
+ OK: 200,
10
+ CREATED: 201,
11
+ ACCEPTED: 202,
12
+ NON_AUTHORITATIVE_INFORMATION: 203,
13
+ NO_CONTENT: 204,
14
+ RESET_CONTENT: 205,
15
+ PARTIAL_CONTENT: 206,
16
+ MULTI_STATUS: 207,
17
+ ALREADY_REPORTED: 208,
18
+ IM_USED: 226,
19
+
20
+ MULTIPLE_CHOICES: 300,
21
+ MOVED_PERMANENTLY: 301,
22
+ FOUND: 302,
23
+ SEE_OTHER: 303,
24
+ NOT_MODIFIED: 304,
25
+ USE_PROXY: 305,
26
+ SWITCH_PROXY: 306, // unused
27
+ TEMPORARY_REDIRECT: 307,
28
+ PERMANENT_REDIRECT: 308,
29
+
30
+ BAD_REQUEST: 400,
31
+ UNAUTHORIZED: 401,
32
+ PAYMENT_REQUIRED: 402,
33
+ FORBIDDEN: 403,
34
+ NOT_FOUND: 404,
35
+ METHOD_NOT_ALLOWED: 405,
36
+ NOT_ACCEPTABLE: 406,
37
+ PROXY_AUTHENTICATION_REQUIRED: 407,
38
+ REQUEST_TIMEOUT: 408,
39
+ CONFLICT: 409,
40
+ GONE: 410,
41
+ LENGTH_REQUIRED: 411,
42
+ PRECONDITION_FAILED: 412,
43
+ PAYLOAD_TOO_LARGE: 413,
44
+ URI_TOO_LONG: 414,
45
+ UNSUPPORTED_MEDIA_TYPE: 415,
46
+ RANGE_NOT_SATISFIABLE: 416,
47
+ EXPECTATION_FAILED: 417,
48
+ IM_A_TEAPOT: 418,
49
+ MISDIRECTED_REQUEST: 421,
50
+ UNPROCESSABLE_ENTITY: 422,
51
+ LOCKED: 423,
52
+ FAILED_DEPENDENCY: 424,
53
+ TOO_EARLY: 425,
54
+ UPGRADE_REQUIRED: 426,
55
+ PRECONDITION_REQUIRED: 428,
56
+ TOO_MANY_REQUESTS: 429,
57
+ REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
58
+ UNAVAILABLE_FOR_LEGAL_REASONS: 451,
59
+
60
+ INTERNAL_SERVER_ERROR: 500,
61
+ NOT_IMPLEMENTED: 501,
62
+ BAD_GATEWAY: 502,
63
+ SERVICE_UNAVAILABLE: 503,
64
+ GATEWAY_TIMEOUT: 504,
65
+ HTTP_VERSION_NOT_SUPPORTED: 505,
66
+ VARIANT_ALSO_NEGOTIATES: 506,
67
+ INSUFFICIENT_STORAGE: 507,
68
+ LOOP_DETECTED: 508,
69
+ NOT_EXTENDED: 510,
70
+ NETWORK_AUTHENTICATION_REQUIRED: 511,
71
+ };
72
+
73
+ // Reuse your ErrorCode union
74
+ const ErrorCodeSchema = z.enum(
75
+ Object.keys(LobbStatusCodes) as [
76
+ keyof typeof LobbStatusCodes,
77
+ ...Array<keyof typeof LobbStatusCodes>,
78
+ ],
79
+ );
80
+
81
+ export const LobbErrorSchema = z.object({
82
+ code: ErrorCodeSchema,
83
+ message: z.string(),
84
+ details: z.any().optional(),
85
+ });
86
+ type ErrorCode = z.infer<typeof ErrorCodeSchema>;
87
+ export type LobbErrorOptions = z.infer<typeof LobbErrorSchema>;
88
+
89
+ export class LobbError extends Error {
90
+ public status: number;
91
+ public code: ErrorCode;
92
+ public details?: any;
93
+
94
+ constructor({ code, message, details }: LobbErrorOptions) {
95
+ super(message);
96
+ this.name = "LobbError";
97
+ this.status = LobbStatusCodes[code];
98
+ this.code = code;
99
+ this.details = details;
100
+
101
+ if (Error.captureStackTrace) {
102
+ Error.captureStackTrace(this, LobbError);
103
+ }
104
+ }
105
+ }
@@ -0,0 +1,11 @@
1
+ export class TypesGenerator {
2
+ constructor() {}
3
+
4
+ public static async init() {
5
+ return new TypesGenerator();
6
+ }
7
+
8
+ public async generate() {
9
+ // TODO
10
+ }
11
+ }
@@ -0,0 +1,126 @@
1
+ import type { WebConfig } from "../types/index.ts";
2
+
3
+ import { Hono } from "hono";
4
+ import type { Context } from "hono";
5
+ import { cors } from "hono/cors";
6
+ import { proxy } from "hono/proxy";
7
+ import { getCollectionRoute } from "./collections/collectionRoutes.ts";
8
+ import { getMetaRoute } from "./meta/route.ts";
9
+ import { errorHandler } from "./errorHandler.ts";
10
+ import * as middlewares from "./middlewares.ts";
11
+ import { getOpenApiRoute } from "./openApiRoute.ts";
12
+ import { Lobb } from "../Lobb.ts";
13
+ import { getSchemaRoutes } from "./schema/schemaRoutes.ts";
14
+ import { getEventsRoute } from "./events/index.ts";
15
+
16
+ export class WebServer {
17
+ private webConfig!: WebConfig;
18
+ public app!: Hono;
19
+ public port!: number;
20
+ private server!: ReturnType<typeof Bun.serve>;
21
+
22
+ public static async init(studioPort: number | null = null) {
23
+ const webServer = new WebServer();
24
+ webServer.webConfig = Lobb.instance.configManager.config.web_server;
25
+ webServer.app = new Hono();
26
+ await webServer.start(studioPort);
27
+ return webServer;
28
+ }
29
+
30
+ public close() {
31
+ // closing all opened sockets
32
+ this.server.stop(true);
33
+ }
34
+
35
+ public async start(studioPort: number | null = null) {
36
+ this.app.onError(errorHandler);
37
+
38
+ // pre middlwares
39
+ this.app.use(middlewares.extensionContext);
40
+ this.app.use(middlewares.workflowsMiddlewares);
41
+
42
+ const corsConfig = Lobb.instance.configManager.config.web_server.cors;
43
+ if (corsConfig) {
44
+ this.app.use(cors(corsConfig));
45
+ }
46
+
47
+ // post middlwares
48
+ this.app.use(middlewares.pick);
49
+
50
+ this.app.get("/", (c) => {
51
+ return c.json({ status: "ok", message: "API is available at /api" });
52
+ });
53
+
54
+ this.app.get("/api", (c) => {
55
+ return c.json({ status: "ok", message: "Lobb API is up and running." });
56
+ });
57
+
58
+ this.app.route("/api/meta", getMetaRoute());
59
+ this.app.route("/api/schema", getSchemaRoutes());
60
+ this.app.route("/api/collections", getCollectionRoute());
61
+ this.app.route("/api/openapi", getOpenApiRoute());
62
+ this.app.route("/api/extensions", Lobb.instance.extensionSystem.routes());
63
+ this.app.route("/api/events", getEventsRoute());
64
+
65
+ if (studioPort) {
66
+ const port = studioPort;
67
+ const studioProxy = (c: Context) => {
68
+ const targetUrl = new URL(c.req.url);
69
+ targetUrl.host = `localhost:${port}`;
70
+ return proxy(targetUrl.toString(), { headers: c.req.header() });
71
+ };
72
+ this.app.all("/studio", (c) => c.redirect("/studio/", 308));
73
+ this.app.all("/studio/", studioProxy);
74
+ this.app.all("/studio/*", studioProxy);
75
+ }
76
+
77
+ const isDev = process.env.LOBB_MODE !== "prod";
78
+ const serveBase = {
79
+ port: this.webConfig.port ?? 3000,
80
+ hostname: this.webConfig.host ?? "0.0.0.0",
81
+ };
82
+
83
+ // In dev, forward WebSocket upgrade requests to the Vite dev server so that
84
+ // Vite's HMR can connect through the Lobb proxy instead of falling back to
85
+ // a direct connection (which causes a console warning).
86
+ if (isDev && studioPort) {
87
+ type WSData = { path: string; upstream?: WebSocket };
88
+ this.server = Bun.serve<WSData>({
89
+ ...serveBase,
90
+ fetch: (req, server) => {
91
+ if (req.headers.get("upgrade") === "websocket") {
92
+ const url = new URL(req.url);
93
+ if (url.pathname.startsWith("/studio")) {
94
+ const upgraded = server.upgrade(req, { data: { path: url.pathname + url.search } });
95
+ if (upgraded) return undefined;
96
+ }
97
+ }
98
+ return this.app.fetch(req);
99
+ },
100
+ websocket: {
101
+ open(ws) {
102
+ const upstream = new WebSocket(`ws://localhost:${studioPort}${ws.data.path.replace(/^\/studio/, "") || "/"}`);
103
+ ws.data.upstream = upstream;
104
+ upstream.addEventListener("message", (e) => ws.send(e.data as string | ArrayBuffer));
105
+ upstream.addEventListener("close", (e) => ws.close(e.code, e.reason));
106
+ upstream.addEventListener("error", () => ws.close());
107
+ },
108
+ message(ws, message) {
109
+ if (ws.data.upstream?.readyState === WebSocket.OPEN) {
110
+ ws.data.upstream.send(message as string | ArrayBuffer);
111
+ }
112
+ },
113
+ close(ws, code, reason) {
114
+ ws.data.upstream?.close(code, reason);
115
+ },
116
+ },
117
+ });
118
+ } else {
119
+ this.server = Bun.serve({
120
+ ...serveBase,
121
+ fetch: this.app.fetch,
122
+ });
123
+ }
124
+ this.port = this.server.port!;
125
+ }
126
+ }