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.
@@ -0,0 +1,1393 @@
1
+ /**-------------------------------------------------------------------
2
+ *
3
+ * [ Schema Converter ]
4
+ *
5
+ * - Handles conversion between internal DSL schema and Prisma schema
6
+ * - Supports bidirectional conversion (DSL to Prisma and Prisma to DSL)
7
+ *
8
+ * -------------------------------------------------------------------*/
9
+ import { plural } from "pluralize";
10
+ import { QUERY } from "./queries";
11
+
12
+ /**
13
+ * Converts internal DSL schema to Prisma schema format
14
+ */
15
+ class DSLToPrismaConverter {
16
+ constructor() {}
17
+
18
+ /**
19
+ * Convert internal DSL schema to Prisma schema
20
+ */
21
+ convertToPrisma(
22
+ dslSchema: DBConfigs,
23
+ options: ConversionOptions = {}
24
+ ): string {
25
+ const {
26
+ provider = "postgresql",
27
+ connectionStringEnv = "DATABASE_URL",
28
+ includeComments = true,
29
+ } = options;
30
+
31
+ // Start with header and generator
32
+ let prismaSchema = this.generateHeader(includeComments);
33
+ prismaSchema += QUERY.SCHEMA_CONVERSION.PRISMA_GENERATOR;
34
+
35
+ // Add datasource
36
+ const datasource = QUERY.SCHEMA_CONVERSION.PRISMA_DATASOURCE.replace(
37
+ "{provider}",
38
+ provider
39
+ ).replace("{connectionStringEnv}", connectionStringEnv);
40
+ prismaSchema += `\n${datasource}\n`;
41
+
42
+ // Process each schema in the DSL
43
+ const dbName = Object.keys(dslSchema)[0] as string;
44
+ const schemas = dslSchema[dbName]?.schemas || [];
45
+
46
+ // Track relationships to avoid duplicates
47
+ const processedRelations = new Set<string>();
48
+
49
+ // Convert each model
50
+ for (const schema of schemas) {
51
+ prismaSchema += this.convertModelToPrisma(
52
+ schema,
53
+ schemas,
54
+ processedRelations,
55
+ includeComments
56
+ );
57
+ }
58
+
59
+ return prismaSchema;
60
+ }
61
+
62
+ /**
63
+ * Generate schema header with comments
64
+ */
65
+ private generateHeader(includeComments: boolean): string {
66
+ if (!includeComments) return "";
67
+
68
+ return `// This is your Prisma schema file,
69
+ // learn more about it in the docs: https://pris.ly/d/prisma-schema
70
+
71
+ `;
72
+ }
73
+
74
+ /**
75
+ * Convert a single model to Prisma format
76
+ */
77
+ private convertModelToPrisma(
78
+ schema: Schema,
79
+ allSchemas: Schema[],
80
+ processedRelations: Set<string>,
81
+ includeComments: boolean
82
+ ): string {
83
+ const modelName = schema.model;
84
+ let modelDefinition = includeComments
85
+ ? `// ${schema.tableName} table\n`
86
+ : "";
87
+
88
+ modelDefinition += `model ${modelName} {\n`;
89
+
90
+ // Add fields
91
+ for (const field of schema.fields) {
92
+ modelDefinition += this.convertFieldToPrisma(field, schema.tableName);
93
+ }
94
+
95
+ // Add relations
96
+ if (schema.relations) {
97
+ for (const [relationName, relation] of Object.entries(schema.relations)) {
98
+ const relationDef = this.convertRelationToPrisma(
99
+ relationName,
100
+ relation,
101
+ schema,
102
+ allSchemas,
103
+ processedRelations
104
+ );
105
+ if (relationDef) {
106
+ modelDefinition += relationDef;
107
+ }
108
+ }
109
+ }
110
+
111
+ // Add table mapping if table name differs from model name
112
+ if (schema.tableName.toLowerCase() !== modelName.toLowerCase()) {
113
+ modelDefinition += ` @@map("${schema.tableName}")\n`;
114
+ }
115
+
116
+ modelDefinition += `}\n\n`;
117
+ return modelDefinition;
118
+ }
119
+
120
+ /**
121
+ * Convert a field to Prisma format
122
+ */
123
+ private convertFieldToPrisma(field: SchemaField, tableName: string): string {
124
+ const { name, type } = field;
125
+ const prismaType = this.mapTypeToPrisma(type, field);
126
+
127
+ let fieldDefinition = ` ${name} ${prismaType}`;
128
+
129
+ // Handle nullable
130
+ if (!field.nullable) {
131
+ fieldDefinition += " ";
132
+ } else {
133
+ fieldDefinition += "? ";
134
+ }
135
+
136
+ // Handle primary key
137
+ if (field.primaryKey) {
138
+ fieldDefinition += "@id ";
139
+ }
140
+
141
+ // Handle default values
142
+ if (field.default !== undefined || field.autoIncrement) {
143
+ fieldDefinition += this.generateDefaultAttribute(field);
144
+ }
145
+
146
+ // Handle unique constraint
147
+ if (field.unique && !field.primaryKey) {
148
+ fieldDefinition += "@unique ";
149
+ }
150
+
151
+ // Handle created at
152
+ if (
153
+ name.toLowerCase() === "createdat" &&
154
+ type.toLowerCase().includes("time")
155
+ ) {
156
+ fieldDefinition += "@default(now()) ";
157
+ }
158
+
159
+ // Handle updated at
160
+ if (
161
+ name.toLowerCase() === "updatedat" &&
162
+ type.toLowerCase().includes("time")
163
+ ) {
164
+ fieldDefinition += "@updatedAt ";
165
+ }
166
+
167
+ // Add column mapping if field name differs from column name in database
168
+ // This is a placeholder - in a real implementation you'd check if the field name
169
+ // differs from the column name in the database
170
+ // if (name !== databaseColumnName) {
171
+ // fieldDefinition += `@map("${databaseColumnName}") `;
172
+ // }
173
+
174
+ return `${fieldDefinition}\n`;
175
+ }
176
+
177
+ /**
178
+ * Generate default attribute for a field
179
+ */
180
+ private generateDefaultAttribute(field: SchemaField): string {
181
+ if (field.autoIncrement) {
182
+ return "@default(autoincrement()) ";
183
+ }
184
+
185
+ if (
186
+ field.type.toLowerCase() === "uuid" ||
187
+ field.name.toLowerCase() === "id"
188
+ ) {
189
+ return "@default(uuid()) ";
190
+ }
191
+
192
+ // Handle different types of defaults
193
+ if (field.default !== undefined) {
194
+ if (typeof field.default === "string") {
195
+ return `@default("${field.default}") `;
196
+ } else if (typeof field.default === "boolean") {
197
+ return `@default(${field.default}) `;
198
+ } else if (typeof field.default === "number") {
199
+ return `@default(${field.default}) `;
200
+ } else if (field.default === null) {
201
+ return "";
202
+ }
203
+ }
204
+
205
+ return "";
206
+ }
207
+
208
+ /**
209
+ * Convert a relation to Prisma format
210
+ */
211
+ private convertRelationToPrisma(
212
+ relationName: string,
213
+ relation: SchemaRelation,
214
+ sourceSchema: Schema,
215
+ allSchemas: Schema[],
216
+ processedRelations: Set<string>
217
+ ): string {
218
+ const { relatedTo, relationType, foreignKey } = relation;
219
+
220
+ // Find the related schema
221
+ const relatedSchema = allSchemas.find((s) => s.model === relatedTo);
222
+ if (!relatedSchema) return "";
223
+
224
+ // Create a unique identifier for this relation to avoid duplicates
225
+ const relationId = `${sourceSchema.model}:${relatedSchema.model}:${relationName}`;
226
+ const reverseRelationId = `${relatedSchema.model}:${sourceSchema.model}:${relationName}`;
227
+
228
+ // Skip if we've already processed this relation
229
+ if (
230
+ processedRelations.has(relationId) ||
231
+ processedRelations.has(reverseRelationId)
232
+ ) {
233
+ return "";
234
+ }
235
+
236
+ processedRelations.add(relationId);
237
+
238
+ // Handle different relation types
239
+ switch (relationType) {
240
+ case "OneToOne":
241
+ return this.generateOneToOneRelation(
242
+ sourceSchema,
243
+ relatedSchema,
244
+ relation,
245
+ foreignKey
246
+ );
247
+
248
+ case "OneToMany":
249
+ return this.generateOneToManyRelation(
250
+ sourceSchema,
251
+ relatedSchema,
252
+ relation,
253
+ foreignKey
254
+ );
255
+
256
+ case "ManyToOne":
257
+ return this.generateManyToOneRelation(
258
+ sourceSchema,
259
+ relatedSchema,
260
+ relation,
261
+ foreignKey
262
+ );
263
+
264
+ case "ManyToMany":
265
+ return this.generateManyToManyRelation(
266
+ sourceSchema,
267
+ relatedSchema,
268
+ relation,
269
+ relationName
270
+ );
271
+
272
+ default:
273
+ return "";
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Generate a one-to-one relation
279
+ */
280
+ private generateOneToOneRelation(
281
+ sourceSchema: Schema,
282
+ relatedSchema: Schema,
283
+ relation: SchemaRelation,
284
+ foreignKey: string
285
+ ): string {
286
+ const relatedModelName = relatedSchema.model;
287
+ const relationField = this.camelCase(relatedModelName);
288
+
289
+ return (
290
+ ` ${relationField} ${relatedModelName}? @relation(fields: [${foreignKey}], references: [id])\n` +
291
+ ` ${foreignKey} String? @unique\n`
292
+ );
293
+ }
294
+
295
+ /**
296
+ * Generate a one-to-many relation
297
+ */
298
+ private generateOneToManyRelation(
299
+ sourceSchema: Schema,
300
+ relatedSchema: Schema,
301
+ relation: SchemaRelation,
302
+ foreignKey: string
303
+ ): string {
304
+ const relatedModelName = relatedSchema.model;
305
+ const relationField = this.camelCase(plural(relatedModelName));
306
+
307
+ return ` ${relationField} ${relatedModelName}[]\n`;
308
+ }
309
+
310
+ /**
311
+ * Generate a many-to-one relation
312
+ */
313
+ private generateManyToOneRelation(
314
+ sourceSchema: Schema,
315
+ relatedSchema: Schema,
316
+ relation: SchemaRelation,
317
+ foreignKey: string
318
+ ): string {
319
+ const relatedModelName = relatedSchema.model;
320
+ const relationField = this.camelCase(relatedModelName);
321
+
322
+ return (
323
+ ` ${relationField} ${relatedModelName} @relation(fields: [${foreignKey}], references: [id])\n` +
324
+ ` ${foreignKey} String\n`
325
+ );
326
+ }
327
+
328
+ /**
329
+ * Generate a many-to-many relation
330
+ */
331
+ private generateManyToManyRelation(
332
+ sourceSchema: Schema,
333
+ relatedSchema: Schema,
334
+ relation: SchemaRelation,
335
+ relationName: string
336
+ ): string {
337
+ const relatedModelName = relatedSchema.model;
338
+ const relationField = this.camelCase(plural(relatedModelName));
339
+
340
+ return ` ${relationField} ${relatedModelName}[] @relation("${relationName}")\n`;
341
+ }
342
+
343
+ /**
344
+ * Map internal type to Prisma type
345
+ */
346
+ private mapTypeToPrisma(type: string, field: SchemaField): string {
347
+ const typeMap: Record<string, string> = {
348
+ string: "String",
349
+ number: "Int",
350
+ bigint: "BigInt",
351
+ boolean: "Boolean",
352
+ Date: "DateTime",
353
+ DateTime: "DateTime",
354
+ json: "Json",
355
+ jsonb: "Json",
356
+ uuid: "String",
357
+ text: "String",
358
+ };
359
+
360
+ return typeMap[type.toLowerCase()] || "String";
361
+ }
362
+
363
+ /**
364
+ * Convert string to camelCase
365
+ */
366
+ private camelCase(str: string): string {
367
+ return str.charAt(0).toLowerCase() + str.slice(1);
368
+ }
369
+ }
370
+
371
+ /**
372
+ * Converts Prisma schema to internal DSL schema format
373
+ */
374
+ class PrismaToDSLConverter {
375
+ constructor() {}
376
+
377
+ /**
378
+ * Convert Prisma schema to internal DSL format
379
+ */
380
+ convertToDSL(prismaSchema: string, dbName: string): DBConfigs {
381
+ // Parse the Prisma schema
382
+ const models = this.parsePrismaSchema(prismaSchema);
383
+
384
+ // Convert models to schemas
385
+ const schemas: Schema[] = models.map((model) =>
386
+ this.convertModelToDSL(model)
387
+ );
388
+
389
+ // Create the DBConfigs object
390
+ const dbConfigs: DBConfigs = {
391
+ [dbName]: {
392
+ database: {
393
+ name: dbName,
394
+ type: this.detectDatabaseType(prismaSchema) as
395
+ | "postgres"
396
+ | "sqlite"
397
+ | "indexeddb"
398
+ | "memory"
399
+ | "localstorage"
400
+ | "valkey",
401
+ enabled: true,
402
+ connectionStringEnv: this.extractConnectionStringEnv(prismaSchema),
403
+ },
404
+ schemas,
405
+ },
406
+ };
407
+
408
+ return dbConfigs;
409
+ }
410
+
411
+ /**
412
+ * Parse Prisma schema into model objects
413
+ */
414
+ private parsePrismaSchema(prismaSchema: string): PrismaModel[] {
415
+ const models: PrismaModel[] = [];
416
+ const modelRegex = /model\s+(\w+)\s+{([^}]*)}/g;
417
+ let match;
418
+
419
+ while ((match = modelRegex.exec(prismaSchema)) !== null) {
420
+ const modelName = match[1];
421
+ const modelBody = match[2];
422
+
423
+ // Parse fields
424
+ const fields: PrismaField[] = [];
425
+ const relations: PrismaRelation[] = [];
426
+
427
+ const lines = modelBody
428
+ ?.split("\n")
429
+ .map((line) => line.trim())
430
+ .filter((line) => line);
431
+
432
+ for (const line of lines || []) {
433
+ // Skip comments and directives
434
+ if (line.startsWith("//") || line.startsWith("@@")) continue;
435
+
436
+ // Parse field
437
+ const fieldMatch = line.match(/(\w+)\s+(\w+)(\??)(\[\])?(\s+(.*))?/);
438
+ if (fieldMatch) {
439
+ const [_, fieldName, fieldType, nullable, isArray, __, attributes] =
440
+ fieldMatch;
441
+
442
+ // Check if it's a relation
443
+ if (attributes && attributes.includes("@relation")) {
444
+ const relationMatch = attributes.match(
445
+ /@relation\(fields:\s*\[(\w+)\],\s*references:\s*\[(\w+)\]\)/
446
+ );
447
+ if (relationMatch) {
448
+ const [_, foreignKey, referencedField] = relationMatch;
449
+
450
+ if (foreignKey && referencedField && fieldName && fieldType) {
451
+ relations.push({
452
+ name: fieldName,
453
+ type: fieldType,
454
+ foreignKey,
455
+ referencedField,
456
+ isArray: !!isArray,
457
+ isRequired: !nullable,
458
+ });
459
+ }
460
+ } else {
461
+ // Many-to-many relation
462
+ const manyToManyMatch = attributes.match(/@relation\("(\w+)"\)/);
463
+ if (manyToManyMatch) {
464
+ const [_, relationName] = manyToManyMatch;
465
+
466
+ if (relationName && fieldName && fieldType) {
467
+ relations.push({
468
+ name: fieldName,
469
+ type: fieldType,
470
+ relationName,
471
+ isArray: !!isArray,
472
+ isRequired: !nullable,
473
+ });
474
+ }
475
+ }
476
+ }
477
+ } else {
478
+ // Regular field
479
+ if (fieldName && fieldType) {
480
+ fields.push({
481
+ name: fieldName,
482
+ type: fieldType,
483
+ isArray: !!isArray,
484
+ isRequired: !nullable,
485
+ attributes: attributes || "",
486
+ });
487
+ }
488
+ }
489
+ }
490
+ }
491
+
492
+ // Extract table name from @@map directive
493
+ let tableName = modelName;
494
+ const mapMatch = modelBody?.match(/@@map\("([^"]+)"\)/);
495
+ if (mapMatch) {
496
+ tableName = mapMatch[1];
497
+ }
498
+
499
+ if (!tableName) {
500
+ throw new Error(`Table name not found for model ${modelName}`);
501
+ }
502
+
503
+ if (!modelName) {
504
+ throw new Error(`Model name not found for model ${modelName}`);
505
+ }
506
+
507
+ models.push({
508
+ name: modelName,
509
+ tableName,
510
+ fields,
511
+ relations,
512
+ });
513
+ }
514
+
515
+ return models;
516
+ }
517
+
518
+ /**
519
+ * Convert a Prisma model to internal DSL schema
520
+ */
521
+ private convertModelToDSL(model: PrismaModel): Schema {
522
+ const fields: SchemaField[] = model.fields.map((field) =>
523
+ this.convertFieldToDSL(field)
524
+ );
525
+ const relations: SchemaRelation[] = [];
526
+
527
+ // Convert relations
528
+ for (const relation of model.relations) {
529
+ const relationKey = `${relation.name}Relation`;
530
+ relations.push(this.convertRelationToDSL(relation));
531
+ }
532
+
533
+ return {
534
+ model: model.name,
535
+ tableName: model.tableName,
536
+ fields,
537
+ relations:
538
+ Object.keys(relations).length > 0
539
+ ? relations
540
+ : ([] as unknown as SchemaRelation[]),
541
+ };
542
+ }
543
+
544
+ /**
545
+ * Convert a Prisma field to internal DSL field
546
+ */
547
+ private convertFieldToDSL(field: PrismaField): SchemaField {
548
+ const schemaField: SchemaField = {
549
+ name: field.name,
550
+ type: this.mapPrismaTypeToInternal(field.type),
551
+ nullable: !field.isRequired,
552
+ };
553
+
554
+ // Handle attributes
555
+ if (field.attributes) {
556
+ // Primary key
557
+ if (field.attributes.includes("@id")) {
558
+ schemaField.primaryKey = true;
559
+ }
560
+
561
+ // Unique constraint
562
+ if (field.attributes.includes("@unique")) {
563
+ schemaField.unique = true;
564
+ }
565
+
566
+ // Default values
567
+ const defaultMatch = field.attributes.match(/@default\(([^)]+)\)/);
568
+ if (defaultMatch) {
569
+ const defaultValue = defaultMatch[1];
570
+
571
+ if (defaultValue === "autoincrement()") {
572
+ schemaField.autoIncrement = true;
573
+ } else if (defaultValue === "uuid()" || defaultValue === "cuid()") {
574
+ // Handle UUID/CUID default
575
+ schemaField.default = "uuid";
576
+ } else if (defaultValue === "now()") {
577
+ // Handle DateTime default
578
+ schemaField.DateTime = new Date();
579
+ } else if (defaultValue === "true" || defaultValue === "false") {
580
+ // Handle boolean default
581
+ schemaField.default = defaultValue === "true";
582
+ } else if (!isNaN(Number(defaultValue))) {
583
+ // Handle numeric default
584
+ schemaField.default = Number(defaultValue);
585
+ } else {
586
+ // Handle string default (remove quotes)
587
+ schemaField.default = defaultValue?.replace(/^"(.*)"$/, "$1");
588
+ }
589
+ }
590
+
591
+ // Updated at
592
+ // if (field.attributes.includes("@updatedAt")) {
593
+ // schemaField.updatedAt = true;
594
+ // }
595
+ }
596
+
597
+ return schemaField;
598
+ }
599
+
600
+ /**
601
+ * Convert a Prisma relation to internal DSL relation
602
+ */
603
+ private convertRelationToDSL(relation: PrismaRelation): SchemaRelation {
604
+ let relationType: "OneToOne" | "OneToMany" | "ManyToOne" | "ManyToMany";
605
+
606
+ if (relation.isArray) {
607
+ relationType = relation.relationName ? "ManyToMany" : "OneToMany";
608
+ } else {
609
+ relationType =
610
+ relation.foreignKey && relation.attributes?.includes("@unique")
611
+ ? "OneToOne"
612
+ : "ManyToOne";
613
+ }
614
+
615
+ return {
616
+ relatedTo: relation.type,
617
+ relationType,
618
+ foreignKey: relation.foreignKey || "",
619
+ onDelete: "CASCADE", // Default value, could be extracted from attributes
620
+ };
621
+ }
622
+
623
+ /**
624
+ * Map Prisma type to internal type
625
+ */
626
+ private mapPrismaTypeToInternal(prismaType: string): string {
627
+ const typeMap: Record<string, string> = {
628
+ String: "string",
629
+ Int: "number",
630
+ BigInt: "bigint",
631
+ Float: "number",
632
+ Decimal: "number",
633
+ Boolean: "boolean",
634
+ DateTime: "DateTime",
635
+ Date: "Date",
636
+ Json: "json",
637
+ Bytes: "string",
638
+ };
639
+
640
+ return typeMap[prismaType] || "string";
641
+ }
642
+
643
+ /**
644
+ * Detect database type from Prisma schema
645
+ */
646
+ private detectDatabaseType(prismaSchema: string): string {
647
+ const providerMatch = prismaSchema.match(/provider\s*=\s*"([^"]+)"/);
648
+ if (providerMatch && providerMatch[1]) {
649
+ const provider = providerMatch[1];
650
+
651
+ switch (provider) {
652
+ case "postgresql":
653
+ return "postgres";
654
+ case "mysql":
655
+ return "mysql";
656
+ case "sqlite":
657
+ return "sqlite";
658
+ case "sqlserver":
659
+ return "sqlserver";
660
+ default:
661
+ return provider;
662
+ }
663
+ }
664
+
665
+ return "postgres"; // Default
666
+ }
667
+
668
+ /**
669
+ * Extract connection string environment variable from Prisma schema
670
+ */
671
+ private extractConnectionStringEnv(prismaSchema: string): string {
672
+ const urlMatch = prismaSchema.match(/url\s*=\s*env\("([^"]+)"\)/);
673
+ return urlMatch?.[1] ?? "DATABASE_URL";
674
+ }
675
+ }
676
+
677
+ /**
678
+ * Schema Converter Factory
679
+ */
680
+ class SchemaConverter {
681
+ private dslToPrismaConverter: DSLToPrismaConverter;
682
+ private prismaToDSLConverter: PrismaToDSLConverter;
683
+
684
+ constructor() {
685
+ this.dslToPrismaConverter = new DSLToPrismaConverter();
686
+ this.prismaToDSLConverter = new PrismaToDSLConverter();
687
+ }
688
+
689
+ /**
690
+ * Convert DSL schema to Prisma schema
691
+ */
692
+ dslToPrisma(dslSchema: DBConfigs, options: ConversionOptions = {}): string {
693
+ return this.dslToPrismaConverter.convertToPrisma(dslSchema, options);
694
+ }
695
+
696
+ /**
697
+ * Convert Prisma schema to DSL schema
698
+ */
699
+ prismaToDSL(prismaSchema: string, dbName: string): DBConfigs {
700
+ return this.prismaToDSLConverter.convertToDSL(prismaSchema, dbName);
701
+ }
702
+ }
703
+
704
+ /**
705
+ * Types
706
+ */
707
+ interface ConversionOptions {
708
+ provider?: string;
709
+ connectionStringEnv?: string;
710
+ includeComments?: boolean;
711
+ }
712
+
713
+ interface PrismaModel {
714
+ name: string;
715
+ tableName: string;
716
+ fields: PrismaField[];
717
+ relations: PrismaRelation[];
718
+ }
719
+
720
+ interface PrismaField {
721
+ name: string;
722
+ type: string;
723
+ isArray: boolean;
724
+ isRequired: boolean;
725
+ attributes?: string;
726
+ }
727
+
728
+ interface PrismaRelation {
729
+ name: string;
730
+ type: string;
731
+ foreignKey?: string;
732
+ referencedField?: string;
733
+ relationName?: string;
734
+ isArray: boolean;
735
+ isRequired: boolean;
736
+ attributes?: string;
737
+ }
738
+
739
+ export { DSLToPrismaConverter, PrismaToDSLConverter, SchemaConverter };
740
+
741
+ /**
742
+ * Test
743
+ */
744
+
745
+ const DSLSchema: DBConfigs = {
746
+ uwazi_dev: {
747
+ database: {
748
+ name: "uwazi_dev",
749
+ type: "postgres",
750
+ enabled: true,
751
+ connectionStringEnv: "UWAZI_SPRING_DATABASE_URL",
752
+ },
753
+ schemas: [
754
+ {
755
+ model: "AuditTrail",
756
+ tableName: "audit_trails",
757
+ fields: [
758
+ {
759
+ name: "log_ref",
760
+ type: "String",
761
+ length: 50,
762
+ nullable: false,
763
+ },
764
+ {
765
+ name: "user_name",
766
+ type: "String",
767
+ length: 50,
768
+ },
769
+ {
770
+ name: "active_page",
771
+ type: "String",
772
+ length: 255,
773
+ },
774
+ {
775
+ name: "activity_done",
776
+ type: "String",
777
+ length: 255,
778
+ },
779
+ {
780
+ name: "system_module",
781
+ type: "String",
782
+ length: 100,
783
+ },
784
+ {
785
+ name: "audit_date",
786
+ type: "DateTime",
787
+ default: "now",
788
+ },
789
+ {
790
+ name: "ip_address",
791
+ type: "String",
792
+ length: 50,
793
+ },
794
+ ],
795
+ },
796
+ {
797
+ model: "Claim",
798
+ tableName: "claims",
799
+ fields: [
800
+ {
801
+ name: "claim_reference",
802
+ type: "String",
803
+ length: 50,
804
+ nullable: false,
805
+ index: true,
806
+ },
807
+ {
808
+ name: "invoice_number",
809
+ type: "String",
810
+ length: 50,
811
+ unique: true,
812
+ nullable: false,
813
+ index: true,
814
+ },
815
+ {
816
+ name: "policy_number",
817
+ type: "String",
818
+ length: 50,
819
+ },
820
+ {
821
+ name: "invoice_amount",
822
+ type: "Decimal",
823
+ nullable: false,
824
+ length: 15,
825
+ precision: 2,
826
+ },
827
+ {
828
+ name: "min_cost",
829
+ type: "Decimal",
830
+ length: 15,
831
+ precision: 2,
832
+ },
833
+ {
834
+ name: "maximum_cost",
835
+ type: "Decimal",
836
+ length: 15,
837
+ precision: 2,
838
+ },
839
+ {
840
+ name: "risk_classification",
841
+ type: "String",
842
+ length: 50,
843
+ },
844
+ {
845
+ name: "claim_narration",
846
+ type: "String",
847
+ length: 500,
848
+ },
849
+ {
850
+ name: "created_by",
851
+ type: "String",
852
+ length: 100,
853
+ },
854
+ {
855
+ name: "approved_by",
856
+ type: "String",
857
+ length: 100,
858
+ },
859
+ {
860
+ name: "created_at",
861
+ type: "DateTime",
862
+ default: "now",
863
+ },
864
+ {
865
+ name: "date_approved",
866
+ type: "DateTime",
867
+ },
868
+ {
869
+ name: "approval_remarks",
870
+ type: "String",
871
+ length: 255,
872
+ },
873
+ {
874
+ name: "status_code",
875
+ type: "String",
876
+ length: 50,
877
+ },
878
+ {
879
+ name: "status_description",
880
+ type: "Text",
881
+ },
882
+ ],
883
+ relations: [
884
+ {
885
+ foreignKey: "treatment_id",
886
+ relatedTo: "Treatment",
887
+ relationType: "ManyToOne",
888
+ onDelete: "CASCADE",
889
+ },
890
+ {
891
+ foreignKey: "hospital_id",
892
+ relatedTo: "Organisation",
893
+ relationType: "ManyToOne",
894
+ onDelete: "CASCADE",
895
+ },
896
+ {
897
+ foreignKey: "insured_id",
898
+ relatedTo: "Organisation",
899
+ relationType: "ManyToOne",
900
+ onDelete: "CASCADE",
901
+ },
902
+ ],
903
+ },
904
+ {
905
+ model: "Organisation",
906
+ tableName: "organisations",
907
+ fields: [
908
+ {
909
+ name: "code",
910
+ type: "String",
911
+ length: 50,
912
+ unique: true,
913
+ nullable: false,
914
+ index: true,
915
+ },
916
+ {
917
+ name: "name",
918
+ type: "String",
919
+ length: 255,
920
+ nullable: false,
921
+ },
922
+ {
923
+ name: "type",
924
+ type: "String",
925
+ length: 50,
926
+ nullable: false,
927
+ },
928
+ {
929
+ name: "kra_pin",
930
+ type: "String",
931
+ length: 50,
932
+ nullable: true,
933
+ },
934
+ {
935
+ name: "head_quarter_location",
936
+ type: "String",
937
+ nullable: true,
938
+ },
939
+ {
940
+ name: "email_address",
941
+ type: "String",
942
+ length: 255,
943
+ unique: true,
944
+ nullable: true,
945
+ },
946
+ {
947
+ name: "mobile_number",
948
+ type: "String",
949
+ length: 20,
950
+ nullable: true,
951
+ },
952
+ {
953
+ name: "hospital_category",
954
+ type: "String",
955
+ length: 50,
956
+ nullable: true,
957
+ },
958
+ {
959
+ name: "created_at",
960
+ type: "DateTime",
961
+ default: "now",
962
+ },
963
+ {
964
+ name: "updated_at",
965
+ type: "DateTime",
966
+ },
967
+ {
968
+ name: "created_by",
969
+ type: "String",
970
+ length: 100,
971
+ },
972
+ {
973
+ name: "approved_by",
974
+ type: "String",
975
+ length: 100,
976
+ },
977
+ {
978
+ name: "approved_at",
979
+ type: "DateTime",
980
+ default: "now",
981
+ },
982
+ {
983
+ name: "status_code",
984
+ type: "String",
985
+ length: 50,
986
+ nullable: false,
987
+ },
988
+ {
989
+ name: "status_description",
990
+ type: "Text",
991
+ nullable: true,
992
+ },
993
+ ],
994
+ },
995
+ {
996
+ model: "Policy",
997
+ tableName: "policies",
998
+ fields: [
999
+ {
1000
+ name: "policy_number",
1001
+ type: "String",
1002
+ length: 50,
1003
+ unique: true,
1004
+ nullable: false,
1005
+ index: true,
1006
+ },
1007
+ {
1008
+ name: "policy_start_date",
1009
+ type: "Date",
1010
+ nullable: false,
1011
+ default: "now",
1012
+ },
1013
+ {
1014
+ name: "policy_end_date",
1015
+ type: "Date",
1016
+ nullable: false,
1017
+ default: "now",
1018
+ },
1019
+ {
1020
+ name: "premium_amount",
1021
+ type: "Decimal",
1022
+ length: 15,
1023
+ precision: 2,
1024
+ nullable: false,
1025
+ },
1026
+ {
1027
+ name: "remaining_limit",
1028
+ type: "Decimal",
1029
+ nullable: false,
1030
+ length: 15,
1031
+ precision: 2,
1032
+ },
1033
+ {
1034
+ name: "created_at",
1035
+ type: "DateTime",
1036
+ default: "now",
1037
+ },
1038
+ {
1039
+ name: "updated_at",
1040
+ type: "DateTime",
1041
+ },
1042
+ ],
1043
+ relations: [
1044
+ {
1045
+ foreignKey: "user_id",
1046
+ relatedTo: "User",
1047
+ relationType: "ManyToOne",
1048
+ onDelete: "CASCADE",
1049
+ },
1050
+ ],
1051
+ },
1052
+ {
1053
+ model: "Role",
1054
+ tableName: "roles",
1055
+ fields: [
1056
+ {
1057
+ name: "name",
1058
+ type: "String",
1059
+ length: 50,
1060
+ nullable: false,
1061
+ },
1062
+ {
1063
+ name: "description",
1064
+ type: "String",
1065
+ length: 255,
1066
+ nullable: false,
1067
+ },
1068
+ {
1069
+ name: "created_by",
1070
+ type: "String",
1071
+ },
1072
+ {
1073
+ name: "created_at",
1074
+ type: "DateTime",
1075
+ default: "now",
1076
+ },
1077
+ ],
1078
+ },
1079
+ {
1080
+ model: "Token",
1081
+ tableName: "tokens",
1082
+ fields: [
1083
+ {
1084
+ name: "token",
1085
+ type: "Text",
1086
+ nullable: false,
1087
+ },
1088
+ {
1089
+ name: "token_type",
1090
+ type: "String",
1091
+ length: 20,
1092
+ nullable: false,
1093
+ },
1094
+ {
1095
+ name: "expires_at",
1096
+ type: "DateTime",
1097
+ nullable: false,
1098
+ },
1099
+ {
1100
+ name: "revoked",
1101
+ type: "Boolean",
1102
+ default: false,
1103
+ },
1104
+ {
1105
+ name: "created_at",
1106
+ type: "DateTime",
1107
+ default: "now",
1108
+ },
1109
+ ],
1110
+ relations: [
1111
+ {
1112
+ foreignKey: "user_id",
1113
+ relatedTo: "User",
1114
+ relationType: "ManyToOne",
1115
+ onDelete: "CASCADE",
1116
+ },
1117
+ ],
1118
+ },
1119
+ {
1120
+ model: "TreatmentCost",
1121
+ tableName: "treatment_costs",
1122
+ fields: [
1123
+ {
1124
+ name: "hospital_category",
1125
+ type: "String",
1126
+ length: 50,
1127
+ nullable: false,
1128
+ },
1129
+ {
1130
+ name: "min_cost",
1131
+ type: "Decimal",
1132
+ length: 15,
1133
+ precision: 2,
1134
+ nullable: false,
1135
+ },
1136
+ {
1137
+ name: "maximum_cost",
1138
+ type: "Decimal",
1139
+ length: 15,
1140
+ precision: 2,
1141
+ nullable: false,
1142
+ },
1143
+ {
1144
+ name: "created_by",
1145
+ type: "String",
1146
+ length: 100,
1147
+ },
1148
+ {
1149
+ name: "created_at",
1150
+ type: "DateTime",
1151
+ default: "now",
1152
+ },
1153
+ {
1154
+ name: "approved_by",
1155
+ type: "String",
1156
+ length: 100,
1157
+ },
1158
+ {
1159
+ name: "approved_at",
1160
+ type: "DateTime",
1161
+ },
1162
+ {
1163
+ name: "updated_by",
1164
+ type: "String",
1165
+ length: 100,
1166
+ },
1167
+ {
1168
+ name: "updated_at",
1169
+ type: "DateTime",
1170
+ },
1171
+ {
1172
+ name: "status_code",
1173
+ type: "String",
1174
+ length: 50,
1175
+ nullable: false,
1176
+ },
1177
+ {
1178
+ name: "status_description",
1179
+ type: "Text",
1180
+ },
1181
+ ],
1182
+ relations: [
1183
+ {
1184
+ foreignKey: "treatment_id",
1185
+ relatedTo: "Treatment",
1186
+ relationType: "ManyToOne",
1187
+ onDelete: "CASCADE",
1188
+ },
1189
+ ],
1190
+ },
1191
+ {
1192
+ model: "Treatment",
1193
+ tableName: "treatments",
1194
+ fields: [
1195
+ {
1196
+ name: "code",
1197
+ type: "String",
1198
+ length: 50,
1199
+ unique: true,
1200
+ nullable: false,
1201
+ index: true,
1202
+ },
1203
+ {
1204
+ name: "name",
1205
+ type: "String",
1206
+ length: 255,
1207
+ },
1208
+ {
1209
+ name: "description",
1210
+ type: "String",
1211
+ length: 500,
1212
+ },
1213
+ {
1214
+ name: "created_by",
1215
+ type: "String",
1216
+ length: 100,
1217
+ },
1218
+ {
1219
+ name: "created_at",
1220
+ type: "DateTime",
1221
+ default: "now",
1222
+ },
1223
+ {
1224
+ name: "approved_by",
1225
+ type: "String",
1226
+ length: 100,
1227
+ },
1228
+ {
1229
+ name: "approved_at",
1230
+ type: "DateTime",
1231
+ default: "now",
1232
+ },
1233
+ {
1234
+ name: "status_code",
1235
+ type: "String",
1236
+ length: 50,
1237
+ nullable: false,
1238
+ },
1239
+ {
1240
+ name: "status_description",
1241
+ type: "Text",
1242
+ },
1243
+ ],
1244
+ },
1245
+ {
1246
+ model: "User",
1247
+ tableName: "users",
1248
+ fields: [
1249
+ {
1250
+ name: "user_name",
1251
+ type: "String",
1252
+ length: 100,
1253
+ unique: true,
1254
+ nullable: false,
1255
+ index: true,
1256
+ },
1257
+ {
1258
+ name: "first_name",
1259
+ type: "String",
1260
+ length: 100,
1261
+ nullable: false,
1262
+ },
1263
+ {
1264
+ name: "second_name",
1265
+ type: "String",
1266
+ length: 100,
1267
+ nullable: true,
1268
+ },
1269
+ {
1270
+ name: "last_name",
1271
+ type: "String",
1272
+ length: 100,
1273
+ nullable: false,
1274
+ },
1275
+ {
1276
+ name: "national_id",
1277
+ type: "String",
1278
+ length: 50,
1279
+ nullable: true,
1280
+ unique: true,
1281
+ },
1282
+ {
1283
+ name: "gender",
1284
+ type: "String",
1285
+ length: 10,
1286
+ nullable: true,
1287
+ },
1288
+ {
1289
+ name: "dob",
1290
+ type: "Date",
1291
+ nullable: true,
1292
+ },
1293
+ {
1294
+ name: "mobile_number",
1295
+ type: "String",
1296
+ length: 20,
1297
+ unique: true,
1298
+ nullable: true,
1299
+ },
1300
+ {
1301
+ name: "email",
1302
+ type: "String",
1303
+ length: 255,
1304
+ unique: true,
1305
+ nullable: false,
1306
+ index: true,
1307
+ },
1308
+ {
1309
+ name: "password_hash",
1310
+ type: "String",
1311
+ length: 255,
1312
+ nullable: false,
1313
+ },
1314
+ {
1315
+ name: "last_login_date",
1316
+ type: "DateTime",
1317
+ nullable: true,
1318
+ },
1319
+ {
1320
+ name: "login_trials",
1321
+ type: "Int",
1322
+ default: 3,
1323
+ nullable: true,
1324
+ },
1325
+ {
1326
+ name: "login_ip",
1327
+ type: "String",
1328
+ length: 50,
1329
+ nullable: true,
1330
+ },
1331
+ {
1332
+ name: "created_by",
1333
+ type: "String",
1334
+ length: 100,
1335
+ nullable: false,
1336
+ },
1337
+ {
1338
+ name: "created_at",
1339
+ type: "DateTime",
1340
+ default: "now",
1341
+ nullable: true,
1342
+ },
1343
+ {
1344
+ name: "approved_by",
1345
+ type: "String",
1346
+ length: 100,
1347
+ nullable: true,
1348
+ },
1349
+ {
1350
+ name: "approved_at",
1351
+ type: "DateTime",
1352
+ default: "now",
1353
+ nullable: true,
1354
+ },
1355
+ {
1356
+ name: "updated_at",
1357
+ type: "DateTime",
1358
+ nullable: true,
1359
+ },
1360
+ {
1361
+ name: "status_code",
1362
+ type: "String",
1363
+ length: 50,
1364
+ nullable: false,
1365
+ },
1366
+ {
1367
+ name: "status_description",
1368
+ type: "Text",
1369
+ nullable: true,
1370
+ },
1371
+ ],
1372
+ relations: [
1373
+ {
1374
+ foreignKey: "role_id",
1375
+ relatedTo: "Role",
1376
+ relationType: "ManyToOne",
1377
+ },
1378
+ {
1379
+ foreignKey: "organisation_id",
1380
+ relatedTo: "Organisation",
1381
+ relationType: "ManyToOne",
1382
+ },
1383
+ ],
1384
+ },
1385
+ ],
1386
+ },
1387
+ };
1388
+
1389
+ const schemaConverter = new SchemaConverter();
1390
+ const prismaSchema = schemaConverter.dslToPrisma(DSLSchema);
1391
+ const dslSchema = schemaConverter.prismaToDSL(prismaSchema, "uwazi_dev");
1392
+ await Bun.write("schema.prisma", prismaSchema);
1393
+ await Bun.write("dsl-schema.json", JSON.stringify(dslSchema, null, 2));