morio_bridge 0.1.0 → 0.1.2
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.
- checksums.yaml +4 -4
- data/bin/post-install.sh +1 -1
- data/morio_bridge.gemspec +4 -3
- data/server/bun.lock +116 -0
- data/server/db/pg-dev/client/client.ts +47 -0
- data/server/db/pg-dev/client/commonInputTypes.ts +141 -0
- data/server/db/pg-dev/client/enums.ts +13 -0
- data/server/db/pg-dev/client/index.ts +5 -0
- data/server/db/pg-dev/client/internal/class.ts +242 -0
- data/server/db/pg-dev/client/internal/prismaNamespace.ts +760 -0
- data/server/db/pg-dev/client/models/User.ts +1151 -0
- data/server/db/pg-dev/client/models.ts +11 -0
- data/server/db/pg-dev/schema.prisma +24 -0
- data/server/examples/commands.ts +95 -0
- data/server/examples/create-test.ts +21 -0
- data/server/index.ts +349 -0
- data/server/package.json +45 -0
- data/server/plugins/index.ts +26 -0
- data/server/plugins/lib/correct.schema.prisma +131 -0
- data/server/plugins/lib/schema-converter.ts +1393 -0
- data/server/plugins/lib/schema.ts +469 -0
- data/server/plugins/lib/util.ts +51 -0
- data/server/plugins/orm.ts +32 -0
- data/server/plugins/validator.ts +191 -0
- data/server/tsconfig.json +28 -0
- metadata +23 -1
@@ -0,0 +1,11 @@
|
|
1
|
+
|
2
|
+
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
|
3
|
+
/* eslint-disable */
|
4
|
+
// @ts-nocheck
|
5
|
+
/**
|
6
|
+
* This is a barrel export file for all models and their related types.
|
7
|
+
*
|
8
|
+
* 🟢 You can import this file directly.
|
9
|
+
*/
|
10
|
+
export type * from './models/User.ts'
|
11
|
+
export type * from './commonInputTypes.ts'
|
@@ -0,0 +1,24 @@
|
|
1
|
+
generator client {
|
2
|
+
provider = "prisma-client"
|
3
|
+
previewFeatures = ["queryCompiler", "driverAdapters"]
|
4
|
+
output = "./client"
|
5
|
+
runtime = "bun"
|
6
|
+
moduleFormat = "esm"
|
7
|
+
}
|
8
|
+
|
9
|
+
datasource db {
|
10
|
+
provider = "postgresql"
|
11
|
+
url = env("DATABASE_URL")
|
12
|
+
}
|
13
|
+
|
14
|
+
model User {
|
15
|
+
id String @id @default(uuid())
|
16
|
+
email String
|
17
|
+
password String
|
18
|
+
createdAt DateTime @default(now())
|
19
|
+
updatedAt DateTime @updatedAt
|
20
|
+
|
21
|
+
@@map("users")
|
22
|
+
}
|
23
|
+
|
24
|
+
|
@@ -0,0 +1,95 @@
|
|
1
|
+
import { spawn } from "child_process";
|
2
|
+
import kleur from "kleur";
|
3
|
+
import path from "path";
|
4
|
+
|
5
|
+
const commands = [
|
6
|
+
{
|
7
|
+
command: "prisma migrate reset --schema=./db/pg-dev/schema.prisma --force",
|
8
|
+
verbose: false,
|
9
|
+
label: "orm",
|
10
|
+
description: "resetting database",
|
11
|
+
},
|
12
|
+
{
|
13
|
+
command:
|
14
|
+
"prisma migrate dev --schema=./db/pg-dev/schema.prisma --name=init",
|
15
|
+
verbose: false,
|
16
|
+
label: "orm",
|
17
|
+
description: "running migrations",
|
18
|
+
},
|
19
|
+
];
|
20
|
+
|
21
|
+
const runCommand = ({
|
22
|
+
command,
|
23
|
+
verbose = true,
|
24
|
+
label = "cmd",
|
25
|
+
description = "",
|
26
|
+
}: {
|
27
|
+
command: string;
|
28
|
+
verbose?: boolean;
|
29
|
+
label?: string;
|
30
|
+
description?: string;
|
31
|
+
}) => {
|
32
|
+
return new Promise((resolve, reject) => {
|
33
|
+
const commandSplit = command.split(" ");
|
34
|
+
const base = commandSplit[0];
|
35
|
+
const args = commandSplit.slice(1) || [];
|
36
|
+
|
37
|
+
if (!base) {
|
38
|
+
throw new Error("Invalid command");
|
39
|
+
}
|
40
|
+
|
41
|
+
const basePath = path.basename(base);
|
42
|
+
|
43
|
+
// can i catch the error if the command is not found?
|
44
|
+
|
45
|
+
const proc = spawn(base, args, {
|
46
|
+
env: {
|
47
|
+
...process.env,
|
48
|
+
},
|
49
|
+
});
|
50
|
+
|
51
|
+
if (label && verbose) {
|
52
|
+
const message = `[${label}] ${basePath} ${args.join(" ")}\n`;
|
53
|
+
process.stdout.write(kleur.gray(message));
|
54
|
+
}
|
55
|
+
|
56
|
+
if (label && !verbose && description) {
|
57
|
+
const message = `[${label}] ${description}\n`;
|
58
|
+
process.stdout.write(kleur.gray(message));
|
59
|
+
}
|
60
|
+
|
61
|
+
proc.on("error", (error) => {
|
62
|
+
const message = `[${label}] ${error.message}\n`;
|
63
|
+
process.stderr.write(kleur.red(message));
|
64
|
+
});
|
65
|
+
|
66
|
+
proc.stdout.on("data", (data) => {
|
67
|
+
if (verbose) {
|
68
|
+
process.stdout.write(kleur.gray(data));
|
69
|
+
}
|
70
|
+
});
|
71
|
+
|
72
|
+
proc.stderr.on("data", (data) => {
|
73
|
+
process.stderr.write(kleur.red(data));
|
74
|
+
});
|
75
|
+
|
76
|
+
proc.on("close", (code) => {
|
77
|
+
if (code === 0) {
|
78
|
+
if (verbose) {
|
79
|
+
const message = `[${label}] exited with code ${code}\n`;
|
80
|
+
process.stdout.write(kleur.gray(message));
|
81
|
+
}
|
82
|
+
resolve(true);
|
83
|
+
} else {
|
84
|
+
const message = `[${label}] exited with code ${code}\n`;
|
85
|
+
process.stderr.write(kleur.red(message));
|
86
|
+
// reject(new Error(`Command failed with code ${code}\n`));
|
87
|
+
process.exit(code);
|
88
|
+
}
|
89
|
+
});
|
90
|
+
});
|
91
|
+
};
|
92
|
+
|
93
|
+
for (const command of commands) {
|
94
|
+
await runCommand(command);
|
95
|
+
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
import { plugins } from "../plugins";
|
2
|
+
|
3
|
+
const request = {
|
4
|
+
command: "database",
|
5
|
+
model: "user",
|
6
|
+
action: "create",
|
7
|
+
payload: {
|
8
|
+
data: {
|
9
|
+
email: "johanambuguah@gmail.com",
|
10
|
+
password: "jmm",
|
11
|
+
createdAt: new Date().toISOString(),
|
12
|
+
updatedAt: new Date().toISOString(),
|
13
|
+
},
|
14
|
+
},
|
15
|
+
};
|
16
|
+
|
17
|
+
const response = await plugins
|
18
|
+
.find((plugin: any) => plugin.command && plugin.command === request?.command)
|
19
|
+
?.run(request);
|
20
|
+
|
21
|
+
console.log({ response });
|
data/server/index.ts
ADDED
@@ -0,0 +1,349 @@
|
|
1
|
+
import { existsSync, unlinkSync } from "fs";
|
2
|
+
import { createServer, Server, Socket } from "net";
|
3
|
+
import { plugins } from "./plugins";
|
4
|
+
|
5
|
+
interface ParsedRequest {
|
6
|
+
method: string;
|
7
|
+
path: string;
|
8
|
+
body: string;
|
9
|
+
}
|
10
|
+
|
11
|
+
enum REPLY_TYPE {
|
12
|
+
Health = "health",
|
13
|
+
Invalid = "invalid_request",
|
14
|
+
Error = "internal_server_error",
|
15
|
+
Success = "success",
|
16
|
+
}
|
17
|
+
|
18
|
+
// Sweet spot server settings( keepalive probe delay and nagle algorithm disabling for lower latency )
|
19
|
+
const KEEPALIVE_PROBE_DELAY_MS = 50;
|
20
|
+
const DISABLE_NAGLE_ALGORITHM = true;
|
21
|
+
|
22
|
+
class UnixSocket {
|
23
|
+
private server: Server | null = null;
|
24
|
+
private bufferSize = 65536; // bytes
|
25
|
+
private isReady: boolean = false;
|
26
|
+
|
27
|
+
constructor(
|
28
|
+
private readonly socketPath: string,
|
29
|
+
private messageHandler: (data: string) => Promise<any>
|
30
|
+
) {}
|
31
|
+
|
32
|
+
/**
|
33
|
+
* [ Lifecycle]
|
34
|
+
*/
|
35
|
+
public async start(): Promise<void> {
|
36
|
+
const maxConnectionAttempts = 20;
|
37
|
+
const retryDelayMs = 50;
|
38
|
+
let connectionAttempts = 0;
|
39
|
+
|
40
|
+
this.createServer();
|
41
|
+
|
42
|
+
while (connectionAttempts < maxConnectionAttempts) {
|
43
|
+
this.deleteSocket(this.socketPath);
|
44
|
+
|
45
|
+
if (connectionAttempts > 0) {
|
46
|
+
const socketName = this.socketPath.split("/").pop();
|
47
|
+
console.log({
|
48
|
+
message: `connecting to ${socketName}`,
|
49
|
+
attempts: connectionAttempts,
|
50
|
+
});
|
51
|
+
}
|
52
|
+
|
53
|
+
try {
|
54
|
+
await new Promise<void>((resolve, reject) => {
|
55
|
+
// server not initialized - retry connection
|
56
|
+
if (!this.server) {
|
57
|
+
reject(new Error("Server not initialized"));
|
58
|
+
return;
|
59
|
+
}
|
60
|
+
|
61
|
+
// server is initialized - listen for connections
|
62
|
+
this.server.listen(this.socketPath, () => {
|
63
|
+
this.isReady = true;
|
64
|
+
resolve();
|
65
|
+
});
|
66
|
+
|
67
|
+
// register error handler - check if the socket file exists
|
68
|
+
// and delete it if it does then retry connection
|
69
|
+
this.server.once("error", (e: any) => {
|
70
|
+
const existsError = e.code === "EEXIST";
|
71
|
+
const socketFileExists = existsSync(this.socketPath);
|
72
|
+
|
73
|
+
if (existsError && socketFileExists) unlinkSync(this.socketPath);
|
74
|
+
reject(e);
|
75
|
+
});
|
76
|
+
});
|
77
|
+
return;
|
78
|
+
} catch (e: any) {
|
79
|
+
if (connectionAttempts < maxConnectionAttempts - 1) {
|
80
|
+
connectionAttempts++;
|
81
|
+
await this.sleep(retryDelayMs);
|
82
|
+
continue;
|
83
|
+
}
|
84
|
+
throw new Error(
|
85
|
+
`Failed to listen on ${this.socketPath} after ${maxConnectionAttempts} attempts: ${e.message}`
|
86
|
+
);
|
87
|
+
}
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
public stop(): void {
|
92
|
+
this.server?.close();
|
93
|
+
this.isReady = false;
|
94
|
+
this.deleteSocket(this.socketPath);
|
95
|
+
}
|
96
|
+
|
97
|
+
/**
|
98
|
+
* [ message handler ]
|
99
|
+
*/
|
100
|
+
private async onMessage(
|
101
|
+
socket: Socket,
|
102
|
+
method: string,
|
103
|
+
path: string,
|
104
|
+
body: string
|
105
|
+
) {
|
106
|
+
try {
|
107
|
+
const healthCheck = method === "GET" && path === "/health";
|
108
|
+
const invalidRequest = method !== "POST" || path !== "/prisma/0/call";
|
109
|
+
|
110
|
+
switch (true) {
|
111
|
+
case healthCheck:
|
112
|
+
await this.reply({ socket, type: REPLY_TYPE.Health });
|
113
|
+
break;
|
114
|
+
case invalidRequest:
|
115
|
+
await this.reply({ socket, type: REPLY_TYPE.Invalid });
|
116
|
+
break;
|
117
|
+
default:
|
118
|
+
// Success callbacks can be handled here
|
119
|
+
const response = await this.messageHandler(body);
|
120
|
+
await this.reply({
|
121
|
+
socket,
|
122
|
+
type: REPLY_TYPE.Success,
|
123
|
+
body: response,
|
124
|
+
});
|
125
|
+
}
|
126
|
+
} catch (e) {
|
127
|
+
// Error callbacks can be handled here
|
128
|
+
await this.reply({
|
129
|
+
socket,
|
130
|
+
type: REPLY_TYPE.Error,
|
131
|
+
error: e instanceof Error ? e.message : "Unknown error",
|
132
|
+
});
|
133
|
+
}
|
134
|
+
}
|
135
|
+
|
136
|
+
/**
|
137
|
+
* [ unix server socket helpers ]
|
138
|
+
*/
|
139
|
+
private createServer() {
|
140
|
+
this.server = createServer(
|
141
|
+
{
|
142
|
+
highWaterMark: this.bufferSize,
|
143
|
+
},
|
144
|
+
(socket: Socket) => {
|
145
|
+
// socket options
|
146
|
+
socket.setEncoding("utf8");
|
147
|
+
socket.setKeepAlive(true, KEEPALIVE_PROBE_DELAY_MS);
|
148
|
+
socket.setNoDelay(DISABLE_NAGLE_ALGORITHM);
|
149
|
+
|
150
|
+
this.setupSocketListeners(socket);
|
151
|
+
}
|
152
|
+
);
|
153
|
+
}
|
154
|
+
|
155
|
+
private async setupSocketListeners(socket: Socket): Promise<void> {
|
156
|
+
// buffer incoming data
|
157
|
+
let buffer = "";
|
158
|
+
|
159
|
+
socket.on("data", async (data: string) => {
|
160
|
+
buffer += data;
|
161
|
+
|
162
|
+
const done = buffer.includes("\r\n\r\n");
|
163
|
+
if (!done) return;
|
164
|
+
const { method, path, body } = await this.fromSocketFormat(buffer);
|
165
|
+
|
166
|
+
await this.onMessage(socket, method, path, body);
|
167
|
+
|
168
|
+
// reset buffer
|
169
|
+
buffer = "";
|
170
|
+
});
|
171
|
+
|
172
|
+
socket.on("error", (e: Error) => {
|
173
|
+
console.error(
|
174
|
+
`Socket error on ${this.socketPath}: ${e.message}, stack: ${e.stack}`
|
175
|
+
);
|
176
|
+
});
|
177
|
+
|
178
|
+
socket.on("drain", () => {
|
179
|
+
socket.resume();
|
180
|
+
});
|
181
|
+
|
182
|
+
socket.on("close", () => {});
|
183
|
+
}
|
184
|
+
|
185
|
+
private deleteSocket(socketPath: string) {
|
186
|
+
// delete the socket if it exists because the server might have crashed
|
187
|
+
// and left the socket file behind
|
188
|
+
|
189
|
+
try {
|
190
|
+
const socketExists = existsSync(socketPath);
|
191
|
+
if (socketExists) unlinkSync(socketPath);
|
192
|
+
} catch (e: any) {
|
193
|
+
if (e.code !== "ENOENT")
|
194
|
+
console.warn(`Failed to delete socket ${socketPath}:`, e);
|
195
|
+
}
|
196
|
+
}
|
197
|
+
|
198
|
+
private sleep(ms: number) {
|
199
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
200
|
+
}
|
201
|
+
|
202
|
+
/**
|
203
|
+
* [ request/reply helpers ]
|
204
|
+
*/
|
205
|
+
private async writeToSocket({
|
206
|
+
socket,
|
207
|
+
code,
|
208
|
+
status,
|
209
|
+
body,
|
210
|
+
}: {
|
211
|
+
socket: Socket;
|
212
|
+
code: number;
|
213
|
+
status: string;
|
214
|
+
body: any;
|
215
|
+
}): Promise<void> {
|
216
|
+
const data = await this.toSocketFormat(code, status, body);
|
217
|
+
|
218
|
+
return new Promise((resolve) => {
|
219
|
+
const isWritable = socket.write(data);
|
220
|
+
|
221
|
+
if (!isWritable) {
|
222
|
+
socket.once("drain", () => {
|
223
|
+
socket.resume();
|
224
|
+
resolve();
|
225
|
+
});
|
226
|
+
} else resolve();
|
227
|
+
});
|
228
|
+
}
|
229
|
+
|
230
|
+
private async reply({
|
231
|
+
socket,
|
232
|
+
type,
|
233
|
+
body,
|
234
|
+
error,
|
235
|
+
}: {
|
236
|
+
socket: Socket;
|
237
|
+
type: REPLY_TYPE;
|
238
|
+
body?: any;
|
239
|
+
error?: string;
|
240
|
+
}) {
|
241
|
+
switch (type) {
|
242
|
+
case REPLY_TYPE.Health:
|
243
|
+
await this.writeToSocket({
|
244
|
+
socket,
|
245
|
+
code: 200,
|
246
|
+
status: "OK",
|
247
|
+
body: { status: "healthy" },
|
248
|
+
});
|
249
|
+
break;
|
250
|
+
case REPLY_TYPE.Invalid:
|
251
|
+
await this.writeToSocket({
|
252
|
+
socket,
|
253
|
+
code: 400,
|
254
|
+
status: "Bad Request",
|
255
|
+
body: { error: "Invalid request" },
|
256
|
+
});
|
257
|
+
break;
|
258
|
+
case REPLY_TYPE.Error:
|
259
|
+
await this.writeToSocket({
|
260
|
+
socket,
|
261
|
+
code: 500,
|
262
|
+
status: "Internal Server Error",
|
263
|
+
body: { error },
|
264
|
+
});
|
265
|
+
break;
|
266
|
+
case REPLY_TYPE.Success:
|
267
|
+
await this.writeToSocket({
|
268
|
+
socket,
|
269
|
+
code: 200,
|
270
|
+
status: "OK",
|
271
|
+
body,
|
272
|
+
});
|
273
|
+
break;
|
274
|
+
}
|
275
|
+
}
|
276
|
+
|
277
|
+
/**
|
278
|
+
* [ socket format helpers ]
|
279
|
+
*/
|
280
|
+
private async fromSocketFormat(data: string): Promise<ParsedRequest> {
|
281
|
+
const headerEnd = data.indexOf("\r\n\r\n");
|
282
|
+
if (headerEnd === -1) {
|
283
|
+
throw new Error("Invalid request: no header-body separator");
|
284
|
+
}
|
285
|
+
|
286
|
+
const headers = data.slice(0, headerEnd).split("\r\n");
|
287
|
+
const [method, path] = headers[0]?.split(" ") ?? ["", ""];
|
288
|
+
const body = data.slice(headerEnd + 4);
|
289
|
+
return { method: method ?? "", path: path ?? "", body };
|
290
|
+
}
|
291
|
+
|
292
|
+
private async toSocketFormat(
|
293
|
+
statusCode: number,
|
294
|
+
statusText: string,
|
295
|
+
body: any
|
296
|
+
): Promise<string> {
|
297
|
+
const bodyStr = await JSON.stringify(body);
|
298
|
+
return `HTTP/1.1 ${statusCode} ${statusText}\r\nContent-Type: application/json\r\nContent-Length: ${bodyStr.length}\r\n\r\n${bodyStr}`;
|
299
|
+
}
|
300
|
+
}
|
301
|
+
|
302
|
+
class SocketCluster {
|
303
|
+
private servers: UnixSocket[] = [];
|
304
|
+
|
305
|
+
constructor(
|
306
|
+
private readonly config: {
|
307
|
+
socketPaths: string;
|
308
|
+
messageHandler: (body: string) => Promise<any>;
|
309
|
+
}
|
310
|
+
) {
|
311
|
+
this.servers = this.config.socketPaths
|
312
|
+
.split(",")
|
313
|
+
.map((path) => new UnixSocket(path, this.config.messageHandler));
|
314
|
+
}
|
315
|
+
|
316
|
+
public async init() {
|
317
|
+
for (const server of this.servers) {
|
318
|
+
await server.start();
|
319
|
+
}
|
320
|
+
|
321
|
+
this.servers.forEach((server) => {
|
322
|
+
process.on("SIGTERM", () => {
|
323
|
+
server.stop();
|
324
|
+
process.exit(0);
|
325
|
+
});
|
326
|
+
});
|
327
|
+
}
|
328
|
+
}
|
329
|
+
|
330
|
+
new SocketCluster({
|
331
|
+
socketPaths: process.env.MORIO_RB_BUN_SOCKETS || "/tmp/prisma.sock",
|
332
|
+
messageHandler: async (body: string) => {
|
333
|
+
const request = await JSON.parse(body);
|
334
|
+
|
335
|
+
const response = plugins
|
336
|
+
.find(
|
337
|
+
(plugin: any) => plugin.command && plugin.command === request?.command
|
338
|
+
)
|
339
|
+
?.run(request);
|
340
|
+
|
341
|
+
if (response) return response;
|
342
|
+
|
343
|
+
return {
|
344
|
+
from: "bun-server-xxx",
|
345
|
+
request,
|
346
|
+
response: request,
|
347
|
+
};
|
348
|
+
},
|
349
|
+
}).init();
|
data/server/package.json
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
{
|
2
|
+
"name": "unix",
|
3
|
+
"private": true,
|
4
|
+
"type": "module",
|
5
|
+
"scripts": {
|
6
|
+
"start": "bun run index.ts",
|
7
|
+
"ptest": "tsx test.ts",
|
8
|
+
"prisma:generate": "prisma generate --schema=./db/pg-dev/schema.prisma",
|
9
|
+
"prisma:studio": "prisma studio",
|
10
|
+
"prisma:migrate:dev": "prisma migrate dev --schema=./db/pg-dev/schema.prisma --name=init",
|
11
|
+
"prisma:migrate:reset": "prisma migrate reset --schema=./db/pg-dev/schema.prisma --force",
|
12
|
+
"prisma:db:pull": "prisma db pull --schema=./db/pg-dev/schema.prisma",
|
13
|
+
"prisma:db:push": "prisma db push --schema=./db/pg-dev/schema.prisma",
|
14
|
+
"prisma:validate": "prisma validate --schema=./db/pg-dev/schema.prisma",
|
15
|
+
"prisma:format": "prisma format --schema=./db/pg-dev/schema.prisma",
|
16
|
+
"prisma:version": "prisma version",
|
17
|
+
"prisma:debug": "prisma debug",
|
18
|
+
"prisma:help": "prisma --help",
|
19
|
+
"to-exe": "bun build --compile --outfile ../unix_server index.ts"
|
20
|
+
},
|
21
|
+
"devDependencies": {
|
22
|
+
"@types/bun": "latest",
|
23
|
+
"@types/pg": "8.15.2"
|
24
|
+
},
|
25
|
+
"peerDependencies": {
|
26
|
+
"typescript": "^5"
|
27
|
+
},
|
28
|
+
"dependencies": {
|
29
|
+
"@prisma/adapter-pg": "6.8.2",
|
30
|
+
"@prisma/client": "6.8.2",
|
31
|
+
"fastest-validator": "1.19.1",
|
32
|
+
"kleur": "^4.1.5",
|
33
|
+
"pg": "8.16.0",
|
34
|
+
"prisma": "6.8.2",
|
35
|
+
"yaml": "^2.8.0"
|
36
|
+
},
|
37
|
+
"trustedDependencies": [
|
38
|
+
"better-sqlite3",
|
39
|
+
"fastest-validator",
|
40
|
+
"pg",
|
41
|
+
"prisma",
|
42
|
+
"@prisma/client",
|
43
|
+
"@prisma/adapter-pg"
|
44
|
+
]
|
45
|
+
}
|
@@ -0,0 +1,26 @@
|
|
1
|
+
// import { MorioOrm } from "./orm";
|
2
|
+
import { parseConfigs } from "./lib/util";
|
3
|
+
import { MorioValidator } from "./validator";
|
4
|
+
|
5
|
+
const configs = await parseConfigs();
|
6
|
+
|
7
|
+
console.log("Hi, my name is Morio");
|
8
|
+
console.log(JSON.stringify({ configs }));
|
9
|
+
|
10
|
+
const validator = new MorioValidator();
|
11
|
+
// const orm = await new MorioOrm().init();
|
12
|
+
|
13
|
+
export const plugins = [
|
14
|
+
{
|
15
|
+
name: "validator",
|
16
|
+
command: "validate",
|
17
|
+
description: "A validator plugin",
|
18
|
+
run: (options: any) => validator.run(options),
|
19
|
+
},
|
20
|
+
// {
|
21
|
+
// name: "orm",
|
22
|
+
// command: "database",
|
23
|
+
// description: "A orm plugin",
|
24
|
+
// run: (options: any) => orm.run(options),
|
25
|
+
// },
|
26
|
+
];
|
@@ -0,0 +1,131 @@
|
|
1
|
+
datasource db {
|
2
|
+
provider = "postgres"
|
3
|
+
url = env("UWAZI_SPRING_DATABASE_URL")
|
4
|
+
}
|
5
|
+
|
6
|
+
generator client {
|
7
|
+
provider = "prisma-client-js"
|
8
|
+
}
|
9
|
+
|
10
|
+
model AuditTrail {
|
11
|
+
id Int @id @default(autoincrement())
|
12
|
+
log_ref String @db.VarChar(50)
|
13
|
+
user_name String @db.VarChar(50)
|
14
|
+
active_page String @db.VarChar(255)
|
15
|
+
activity_done String @db.VarChar(255)
|
16
|
+
system_module String @db.VarChar(100)
|
17
|
+
audit_date DateTime @default(now())
|
18
|
+
ip_address String @db.VarChar(50)
|
19
|
+
@@map("audit_trails")
|
20
|
+
}
|
21
|
+
|
22
|
+
model Treatment {
|
23
|
+
id Int @id @default(autoincrement())
|
24
|
+
claims Claim[]
|
25
|
+
// Add other Treatment fields as needed
|
26
|
+
@@map("treatments")
|
27
|
+
}
|
28
|
+
|
29
|
+
model Role {
|
30
|
+
id Int @id @default(autoincrement())
|
31
|
+
users User[]
|
32
|
+
// Add other Role fields as needed
|
33
|
+
@@map("roles")
|
34
|
+
}
|
35
|
+
model Claim {
|
36
|
+
id Int @id @default(autoincrement())
|
37
|
+
claim_reference String @db.VarChar(50)
|
38
|
+
@@index([claim_reference])
|
39
|
+
invoice_number String @unique @db.VarChar(50)
|
40
|
+
@@index([invoice_number])
|
41
|
+
policy_number String @db.VarChar(50)
|
42
|
+
invoice_amount Decimal @db.Decimal(15, 2)
|
43
|
+
min_cost Decimal @db.Decimal(15, 2)
|
44
|
+
maximum_cost Decimal @db.Decimal(15, 2)
|
45
|
+
risk_classification String @db.VarChar(50)
|
46
|
+
claim_narration String @db.VarChar(500)
|
47
|
+
created_by String @db.VarChar(100)
|
48
|
+
approved_by String @db.VarChar(100)
|
49
|
+
created_at DateTime @default(now())
|
50
|
+
date_approved DateTime
|
51
|
+
approval_remarks String @db.VarChar(255)
|
52
|
+
status_code String @db.VarChar(50)
|
53
|
+
status_description String
|
54
|
+
treatment_id Int
|
55
|
+
treatment Treatment @relation(fields: [treatment_id], references: [id], onDelete: Cascade)
|
56
|
+
hospital_id Int
|
57
|
+
hospital_claims Organisation @relation("HospitalClaims", fields: [hospital_id], references: [id], onDelete: Cascade)
|
58
|
+
insured_id Int
|
59
|
+
insurer_claims Organisation @relation("InsuredClaims", fields: [insured_id], references: [id], onDelete: Cascade)
|
60
|
+
@@map("claims")
|
61
|
+
}
|
62
|
+
|
63
|
+
model Organisation {
|
64
|
+
id Int @id @default(autoincrement())
|
65
|
+
code String @unique @db.VarChar(50)
|
66
|
+
@@index([code])
|
67
|
+
name String @db.VarChar(255)
|
68
|
+
type String @db.VarChar(50)
|
69
|
+
kra_pin String? @db.VarChar(50)
|
70
|
+
head_quarter_location String?
|
71
|
+
email_address String? @unique @db.VarChar(255)
|
72
|
+
mobile_number String? @db.VarChar(20)
|
73
|
+
hospital_category String? @db.VarChar(50)
|
74
|
+
created_at DateTime @default(now())
|
75
|
+
updated_at DateTime
|
76
|
+
created_by String @db.VarChar(100)
|
77
|
+
approved_by String @db.VarChar(100)
|
78
|
+
approved_at DateTime @default(now())
|
79
|
+
status_code String @db.VarChar(50)
|
80
|
+
status_description String?
|
81
|
+
hospital_claims Claim[] @relation("HospitalClaims")
|
82
|
+
insured_claims Claim[] @relation("InsuredClaims")
|
83
|
+
users User[] @relation("OrganisationUsers")
|
84
|
+
@@map("organisations")
|
85
|
+
}
|
86
|
+
|
87
|
+
model User {
|
88
|
+
id Int @id @default(autoincrement())
|
89
|
+
user_name String @unique @db.VarChar(100)
|
90
|
+
@@index([user_name])
|
91
|
+
first_name String @db.VarChar(100)
|
92
|
+
second_name String? @db.VarChar(100)
|
93
|
+
last_name String @db.VarChar(100)
|
94
|
+
national_id String? @unique @db.VarChar(50)
|
95
|
+
gender String? @db.VarChar(10)
|
96
|
+
dob DateTime?
|
97
|
+
mobile_number String? @unique @db.VarChar(20)
|
98
|
+
email String @unique @db.VarChar(255)
|
99
|
+
@@index([email])
|
100
|
+
password_hash String @db.VarChar(255)
|
101
|
+
last_login_date DateTime?
|
102
|
+
login_trials Int? @default(3)
|
103
|
+
login_ip String? @db.VarChar(50)
|
104
|
+
created_by String @db.VarChar(100)
|
105
|
+
created_at DateTime? @default(now())
|
106
|
+
approved_by String? @db.VarChar(100)
|
107
|
+
approved_at DateTime? @default(now())
|
108
|
+
updated_at DateTime?
|
109
|
+
status_code String @db.VarChar(50)
|
110
|
+
status_description String?
|
111
|
+
|
112
|
+
// relations
|
113
|
+
role_id Int
|
114
|
+
role Role @relation(fields: [role_id], references: [id])
|
115
|
+
|
116
|
+
organisation_id Int
|
117
|
+
organisation Organisation @relation("OrganisationUsers", fields: [organisation_id], references: [id])
|
118
|
+
|
119
|
+
// indexes
|
120
|
+
@@index([user_name, email], name: "user_name_email_idx")
|
121
|
+
@@unique([user_name, email])
|
122
|
+
|
123
|
+
|
124
|
+
@@index([mobile_number, national_id], name: "mobile_national_id_idx")
|
125
|
+
|
126
|
+
@@index([last_login_date, status_code], name: "login_perf_idx")
|
127
|
+
|
128
|
+
// mapped
|
129
|
+
@@map("users")
|
130
|
+
}
|
131
|
+
|