@autobe/compiler 0.3.24 → 0.4.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.
@@ -3,8 +3,6 @@ import {
3
3
  IAutoBeCompiler,
4
4
  IAutoBeInterfaceCompiler,
5
5
  IAutoBePrismaCompiler,
6
- IAutoBePrismaCompilerProps,
7
- IAutoBePrismaCompilerResult,
8
6
  IAutoBeTypeScriptCompiler,
9
7
  IAutoBeTypeScriptCompilerProps,
10
8
  IAutoBeTypeScriptCompilerResult,
@@ -15,27 +13,23 @@ import { AutoBePrismaCompiler } from "./AutoBePrismaCompiler";
15
13
  import { AutoBeTypeScriptCompiler } from "./AutoBeTypeScriptCompiler";
16
14
 
17
15
  export class AutoBeCompiler implements IAutoBeCompiler {
18
- private readonly prisma_: IAutoBePrismaCompiler = new AutoBePrismaCompiler();
19
- private readonly interface_: IAutoBeInterfaceCompiler =
20
- new AutoBeInterfaceCompiler();
21
- private readonly typescript_: IAutoBeTypeScriptCompiler =
22
- new AutoBeTypeScriptCompiler();
23
-
24
- public prisma(
25
- props: IAutoBePrismaCompilerProps,
26
- ): Promise<IAutoBePrismaCompilerResult> {
27
- return this.prisma_.compile(props);
28
- }
16
+ public readonly prisma: IAutoBePrismaCompiler = new AutoBePrismaCompiler();
29
17
 
30
18
  public interface(
31
19
  document: AutoBeOpenApi.IDocument,
32
20
  ): Promise<Record<string, string>> {
33
- return this.interface_.compile(document);
21
+ return this.interface_compiler_.compile(document);
34
22
  }
35
23
 
36
24
  public typescript(
37
25
  props: IAutoBeTypeScriptCompilerProps,
38
26
  ): Promise<IAutoBeTypeScriptCompilerResult> {
39
- return this.typescript_.compile(props);
27
+ return this.typescript_compiler_.compile(props);
40
28
  }
29
+
30
+ private readonly interface_compiler_: IAutoBeInterfaceCompiler =
31
+ new AutoBeInterfaceCompiler();
32
+
33
+ private readonly typescript_compiler_: IAutoBeTypeScriptCompiler =
34
+ new AutoBeTypeScriptCompiler();
41
35
  }
@@ -1,10 +1,15 @@
1
1
  import {
2
+ AutoBePrisma,
2
3
  IAutoBePrismaCompiler,
3
4
  IAutoBePrismaCompilerProps,
4
5
  IAutoBePrismaCompilerResult,
6
+ IAutoBePrismaValidation,
5
7
  } from "@autobe/interface";
6
8
  import { EmbedPrisma } from "embed-prisma";
7
9
 
10
+ import { validatePrismaApplication } from "./prisma/validatePrismaApplication";
11
+ import { writePrismaApplication } from "./prisma/writePrismaApplication";
12
+
8
13
  export class AutoBePrismaCompiler implements IAutoBePrismaCompiler {
9
14
  public async compile(
10
15
  props: IAutoBePrismaCompilerProps,
@@ -12,4 +17,16 @@ export class AutoBePrismaCompiler implements IAutoBePrismaCompiler {
12
17
  const compiler: EmbedPrisma = new EmbedPrisma();
13
18
  return compiler.compile(props.files);
14
19
  }
20
+
21
+ public async validate(
22
+ app: AutoBePrisma.IApplication,
23
+ ): Promise<IAutoBePrismaValidation> {
24
+ return validatePrismaApplication(app);
25
+ }
26
+
27
+ public async write(
28
+ app: AutoBePrisma.IApplication,
29
+ ): Promise<Record<string, string>> {
30
+ return writePrismaApplication(app);
31
+ }
15
32
  }
@@ -0,0 +1,331 @@
1
+ import { AutoBePrisma, IAutoBePrismaValidation } from "@autobe/interface";
2
+ import { HashMap, hash } from "tstl";
3
+
4
+ import { MapUtil } from "../utils/MapUtil";
5
+
6
+ export function validatePrismaApplication(
7
+ application: AutoBePrisma.IApplication,
8
+ ): IAutoBePrismaValidation {
9
+ const dict: Map<string, AutoBePrisma.IModel> = new Map(
10
+ application.files
11
+ .map((file) => file.models)
12
+ .flat()
13
+ .map((model) => [model.name, model]),
14
+ );
15
+ const errors: IAutoBePrismaValidation.IError[] = [
16
+ ...validateDuplicatedFiles(application),
17
+ ...validateDuplicatedModels(application),
18
+ ...application.files
19
+ .map((file, fi) =>
20
+ file.models.map((model, mi) => {
21
+ const accessor: string = `application.files[${fi}].models[${mi}]`;
22
+ return [
23
+ ...validateDuplicatedFields(model, accessor),
24
+ ...validateDuplicatedIndexes(model, accessor),
25
+ ...validateIndexes(model, accessor),
26
+ ...validateReferences(model, accessor, dict),
27
+ ];
28
+ }),
29
+ )
30
+ .flat(2),
31
+ ];
32
+ return errors.length === 0
33
+ ? { success: true, data: application }
34
+ : { success: false, data: application, errors };
35
+ }
36
+
37
+ /* -----------------------------------------------------------
38
+ DUPLICATES
39
+ ----------------------------------------------------------- */
40
+ function validateDuplicatedFiles(
41
+ app: AutoBePrisma.IApplication,
42
+ ): IAutoBePrismaValidation.IError[] {
43
+ interface IFileContainer {
44
+ file: AutoBePrisma.IFile;
45
+ index: number;
46
+ }
47
+ const group: Map<string, IFileContainer[]> = new Map();
48
+ app.files.forEach((file, fileIndex) => {
49
+ const container: IFileContainer = { file, index: fileIndex };
50
+ MapUtil.take(group, file.filename, () => []).push(container);
51
+ });
52
+
53
+ const errors: IAutoBePrismaValidation.IError[] = [];
54
+ for (const array of group.values())
55
+ if (array.length !== 1)
56
+ array.forEach((container, i) => {
57
+ errors.push({
58
+ path: `application.files[${container.index}]`,
59
+ table: null,
60
+ field: null,
61
+ message: [
62
+ `File ${container.file.filename} is duplicated.`,
63
+ "",
64
+ "Accessors of the other duplicated files are:",
65
+ "",
66
+ ...array
67
+ .filter((_oppo, j) => i !== j)
68
+ .map((oppo) => `- application.files[${oppo.index}]`),
69
+ ].join("\n"),
70
+ });
71
+ });
72
+ return errors;
73
+ }
74
+
75
+ function validateDuplicatedModels(
76
+ app: AutoBePrisma.IApplication,
77
+ ): IAutoBePrismaValidation.IError[] {
78
+ interface IModelContainer {
79
+ file: AutoBePrisma.IFile;
80
+ model: AutoBePrisma.IModel;
81
+ fileIndex: number;
82
+ modelIndex: number;
83
+ }
84
+ const modelContainers: Map<string, IModelContainer[]> = new Map();
85
+ app.files.forEach((file, fileIndex) => {
86
+ file.models.forEach((model, modelIndex) => {
87
+ const container: IModelContainer = {
88
+ file,
89
+ model,
90
+ fileIndex,
91
+ modelIndex,
92
+ };
93
+ MapUtil.take(modelContainers, model.name, () => []).push(container);
94
+ });
95
+ });
96
+
97
+ const errors: IAutoBePrismaValidation.IError[] = [];
98
+ for (const array of modelContainers.values())
99
+ if (array.length !== 1)
100
+ array.forEach((container, i) => {
101
+ errors.push({
102
+ path: `application.files[${container.fileIndex}].models[${container.modelIndex}]`,
103
+ table: container.model.name,
104
+ field: null,
105
+ message: [
106
+ `Model ${container.model.name} is duplicated.`,
107
+ "",
108
+ "Accessors of the other duplicated models are:",
109
+ "",
110
+ ...array
111
+ .filter((_oppo, j) => i !== j)
112
+ .map(
113
+ (oppo) =>
114
+ `- application.files[${oppo.fileIndex}].models[${oppo.modelIndex}]`,
115
+ ),
116
+ ].join("\n"),
117
+ });
118
+ });
119
+ return errors;
120
+ }
121
+
122
+ function validateDuplicatedFields(
123
+ model: AutoBePrisma.IModel,
124
+ accessor: string,
125
+ ): IAutoBePrismaValidation.IError[] {
126
+ const group: Map<string, string[]> = new Map();
127
+ MapUtil.take(group, model.primaryField.name, () => []).push(
128
+ `${accessor}.primaryField.name`,
129
+ );
130
+ model.foreignFields.forEach((field, i) =>
131
+ MapUtil.take(group, field.name, () => []).push(
132
+ `${accessor}.foreignFields[${i}].name`,
133
+ ),
134
+ );
135
+ model.plainFields.forEach((field, i) =>
136
+ MapUtil.take(group, field.name, () => []).push(
137
+ `${accessor}.plainFields[${i}].name`,
138
+ ),
139
+ );
140
+
141
+ const errors: IAutoBePrismaValidation.IError[] = [];
142
+ for (const [field, array] of group)
143
+ if (array.length !== 1)
144
+ array.forEach((path, i) => {
145
+ errors.push({
146
+ path,
147
+ table: model.name,
148
+ field,
149
+ message: [
150
+ `Field ${field} is duplicated.`,
151
+ "",
152
+ "Accessors of the other duplicated fields are:",
153
+ "",
154
+ ...array.filter((_oppo, j) => i !== j).map((a) => `- ${a}`),
155
+ ].join("\n"),
156
+ });
157
+ });
158
+ return errors;
159
+ }
160
+
161
+ function validateDuplicatedIndexes(
162
+ model: AutoBePrisma.IModel,
163
+ accessor: string,
164
+ ): IAutoBePrismaValidation.IError[] {
165
+ const group: HashMap<string[], string[]> = new HashMap(
166
+ (x) => hash(...x),
167
+ (x, y) => x.length === y.length && x.every((v, i) => v === y[i]),
168
+ );
169
+ model.uniqueIndexes.forEach((unique, i) =>
170
+ group
171
+ .take(unique.fieldNames, () => [])
172
+ .push(`${accessor}.uniqueIndexes[${i}].fieldNames`),
173
+ );
174
+ model.plainIndexes.forEach((plain, i) =>
175
+ group
176
+ .take(plain.fieldNames, () => [])
177
+ .push(`${accessor}.plainIndexes[${i}].fieldNames`),
178
+ );
179
+
180
+ const errors: IAutoBePrismaValidation.IError[] = [];
181
+ for (const { first: fieldNames, second: array } of group)
182
+ if (array.length !== 1)
183
+ array.forEach((path, i) => {
184
+ errors.push({
185
+ path,
186
+ table: model.name,
187
+ field: null,
188
+ message: [
189
+ `Duplicated index found (${fieldNames.join(", ")})`,
190
+ "",
191
+ "Accessors of the other duplicated indexes are:",
192
+ "",
193
+ ...array.filter((_oppo, j) => i !== j).map((a) => `- ${a}`),
194
+ ].join("\n"),
195
+ });
196
+ });
197
+
198
+ if (
199
+ model.ginIndexes.length !==
200
+ new Set(model.ginIndexes.map((g) => g.fieldName)).size
201
+ )
202
+ errors.push({
203
+ path: `${accessor}.ginIndexes[].fieldName`,
204
+ table: model.name,
205
+ field: null,
206
+ message: [
207
+ "Duplicated GIN index found.",
208
+ "",
209
+ "GIN index can only be used once per field.",
210
+ "Please remove the duplicated GIN indexes.",
211
+ ].join("\n"),
212
+ });
213
+
214
+ return errors;
215
+ }
216
+
217
+ /* -----------------------------------------------------------
218
+ VALIDATIONS
219
+ ----------------------------------------------------------- */
220
+ function validateIndexes(
221
+ model: AutoBePrisma.IModel,
222
+ accessor: string,
223
+ ): IAutoBePrismaValidation.IError[] {
224
+ // EMENSION
225
+ model.uniqueIndexes = model.uniqueIndexes.filter(
226
+ (unique) =>
227
+ unique.fieldNames.length !== 0 &&
228
+ unique.fieldNames[0] !== model.primaryField.name,
229
+ );
230
+ model.plainIndexes = model.plainIndexes.filter(
231
+ (plain) =>
232
+ plain.fieldNames.length !== 0 &&
233
+ plain.fieldNames[0] !== model.primaryField.name,
234
+ );
235
+
236
+ const errors: IAutoBePrismaValidation.IError[] = [];
237
+ const columnNames: Set<string> = new Set([
238
+ model.primaryField.name,
239
+ ...model.foreignFields.map((field) => field.name),
240
+ ...model.plainFields.map((field) => field.name),
241
+ ]);
242
+
243
+ // COLUMN LEVEL VALIDATION
244
+ const validate = <T>(props: {
245
+ title: string;
246
+ indexes: T[];
247
+ fieldNames: (idx: T) => string[];
248
+ accessor: (i: number, j: number) => string;
249
+ additional?: (idx: T, i: number) => void;
250
+ }) => {
251
+ props.indexes.forEach((idx, i) => {
252
+ // FIND TARGET FIELD
253
+ props.fieldNames(idx).forEach((name, j) => {
254
+ if (!columnNames.has(name))
255
+ errors.push({
256
+ path: `${accessor}.${props.accessor(i, j)}`,
257
+ table: model.name,
258
+ field: null,
259
+ message: `Field ${name} does not exist in model ${model.name}.`,
260
+ });
261
+ });
262
+ });
263
+ };
264
+ validate({
265
+ title: "unique index",
266
+ indexes: model.uniqueIndexes,
267
+ fieldNames: (idx) => idx.fieldNames,
268
+ accessor: (i, j) => `uniqueIndexes[${i}].fieldNames[${j}]`,
269
+ });
270
+ validate({
271
+ title: "index",
272
+ indexes: model.plainIndexes,
273
+ fieldNames: (idx) => idx.fieldNames,
274
+ accessor: (i, j) => `plainIndexes[${i}].fieldNames[${j}]`,
275
+ });
276
+ validate({
277
+ title: "index",
278
+ indexes: model.ginIndexes,
279
+ fieldNames: (idx) => [idx.fieldName],
280
+ accessor: (i) => `ginIndexes[${i}].fieldName`,
281
+ additional: (gin, i) => {
282
+ const pIndex: number = model.plainFields.findIndex(
283
+ (plain) => plain.name === gin.fieldName,
284
+ );
285
+ if (pIndex === -1)
286
+ errors.push({
287
+ path: `${accessor}.ginIndexes[${i}].fieldName`,
288
+ table: model.name,
289
+ field: null,
290
+ message: [
291
+ "GIN index can only be used on string typed field.",
292
+ `However, the target field ${gin.fieldName} does not exist",
293
+ "in the {@link plainFields}.`,
294
+ ].join("\n"),
295
+ });
296
+ else if (model.plainFields[pIndex].type !== "string")
297
+ errors.push({
298
+ path: `${accessor}.ginIndexes[${i}].fieldName`,
299
+ table: model.name,
300
+ field: model.plainFields[pIndex].name,
301
+ message: [
302
+ "GIN index can only be used on string typed field.",
303
+ `However, the target field ${gin.fieldName} is not string,`,
304
+ `but ${model.plainFields[pIndex].type}.`,
305
+ "",
306
+ `- accessor of the wrong typed field: ${`${accessor}.plainFields[${pIndex}].type`}`,
307
+ ].join("\n"),
308
+ });
309
+ },
310
+ });
311
+ return errors;
312
+ }
313
+
314
+ function validateReferences(
315
+ model: AutoBePrisma.IModel,
316
+ accessor: string,
317
+ dict: Map<string, AutoBePrisma.IModel>,
318
+ ): IAutoBePrismaValidation.IError[] {
319
+ const errors: IAutoBePrismaValidation.IError[] = [];
320
+ model.foreignFields.forEach((field, i) => {
321
+ const target = dict.get(field.relation.targetModel);
322
+ if (target === undefined)
323
+ errors.push({
324
+ path: `${accessor}.foreignFields[${i}].relation.targetModel`,
325
+ table: model.name,
326
+ field: field.name,
327
+ message: `Target model ${field.relation.targetModel} does not exist.`,
328
+ });
329
+ });
330
+ return errors;
331
+ }
@@ -0,0 +1,250 @@
1
+ import { AutoBePrisma } from "@autobe/interface";
2
+
3
+ import { ArrayUtil } from "../utils/ArrayUtil";
4
+ import { MapUtil } from "../utils/MapUtil";
5
+
6
+ export function writePrismaApplication(
7
+ app: AutoBePrisma.IApplication,
8
+ ): Record<string, string> {
9
+ for (const file of app.files)
10
+ for (const model of file.models) fillMappingName(model);
11
+ return {
12
+ ...Object.fromEntries(
13
+ app.files
14
+ .filter((file) => file.filename !== "main.prisma")
15
+ .map((file) => [file.filename, writeFile(app, file)]),
16
+ ),
17
+ "main.prisma": MAIN_FILE,
18
+ };
19
+ }
20
+
21
+ function writeFile(
22
+ app: AutoBePrisma.IApplication,
23
+ file: AutoBePrisma.IFile,
24
+ ): string {
25
+ return file.models.map((model) => writeModel(app, file, model)).join("\n\n");
26
+ }
27
+
28
+ function writeModel(
29
+ app: AutoBePrisma.IApplication,
30
+ file: AutoBePrisma.IFile,
31
+ model: AutoBePrisma.IModel,
32
+ ): string {
33
+ return [
34
+ writeComment(
35
+ [
36
+ model.description,
37
+ "",
38
+ ...(model.material ? [] : [`@namespace ${file.namespace}`]),
39
+ "@author AutoBE - https://github.com/wrtnlabs/autobe",
40
+ ].join("\n"),
41
+ ),
42
+ `model ${model.name} {`,
43
+ indent(
44
+ ArrayUtil.paddle([writeColumns(model), writeRelations(app, model)]).join(
45
+ "\n",
46
+ ),
47
+ ),
48
+ "}",
49
+ ].join("\n");
50
+ }
51
+
52
+ function fillMappingName(model: AutoBePrisma.IModel): void {
53
+ const group: Map<string, AutoBePrisma.IForeignField[]> = new Map();
54
+ for (const ff of model.foreignFields) {
55
+ MapUtil.take(group, ff.relation.targetModel, () => []).push(ff);
56
+ if (ff.relation.targetModel == model.name)
57
+ ff.relation.mappingName = "recursive";
58
+ }
59
+ for (const array of group.values())
60
+ if (array.length !== 1)
61
+ for (const ff of array) {
62
+ ff.relation.mappingName = `${model.name}_of_${ff.name}`;
63
+ }
64
+ }
65
+
66
+ /* -----------------------------------------------------------
67
+ COLUMNS
68
+ ----------------------------------------------------------- */
69
+ function writeColumns(model: AutoBePrisma.IModel): string[] {
70
+ return [
71
+ "//----",
72
+ "// COLUMNS",
73
+ "//----",
74
+ writePrimary(model.primaryField),
75
+ ...model.foreignFields.map((x) => ["", writeField(x)]).flat(),
76
+ ...model.plainFields.map((x) => ["", writeField(x)]).flat(),
77
+ ];
78
+ }
79
+
80
+ function writePrimary(field: AutoBePrisma.IPrimaryField): string {
81
+ return [
82
+ writeComment(field.description),
83
+ `${field.name} String @id @db.Uuid`,
84
+ ].join("\n");
85
+ }
86
+
87
+ function writeField(field: AutoBePrisma.IPlainField): string {
88
+ const logical: string = LOGICAL_TYPES[field.type];
89
+ const physical: string | undefined =
90
+ PHYSICAL_TYPES[field.type as keyof typeof PHYSICAL_TYPES];
91
+ return [
92
+ writeComment(field.description),
93
+ [
94
+ field.name,
95
+ `${logical}${field.nullable ? "?" : ""}`,
96
+ ...(physical ? [physical] : []),
97
+ ].join(" "),
98
+ ].join("\n");
99
+ }
100
+
101
+ /* -----------------------------------------------------------
102
+ RELATIONS
103
+ ----------------------------------------------------------- */
104
+ function writeRelations(
105
+ app: AutoBePrisma.IApplication,
106
+ model: AutoBePrisma.IModel,
107
+ ): string[] {
108
+ interface IHasRelationship {
109
+ modelName: string;
110
+ unique: boolean;
111
+ mappingName?: string;
112
+ }
113
+ const hasRelationships: IHasRelationship[] = app.files
114
+ .map((otherFile) =>
115
+ otherFile.models.map((otherModel) =>
116
+ otherModel.foreignFields
117
+ .filter(
118
+ (otherForeign) => otherForeign.relation.targetModel === model.name,
119
+ )
120
+ .map((otherForeign) => ({
121
+ modelName: otherModel.name,
122
+ unique: otherForeign.unique,
123
+ mappingName: otherForeign.relation.mappingName,
124
+ })),
125
+ ),
126
+ )
127
+ .flat(2);
128
+ const foreignIndexes: AutoBePrisma.IForeignField[] =
129
+ model.foreignFields.filter(
130
+ (f) =>
131
+ model.uniqueIndexes.every((u) => u.fieldNames[0] !== f.name) &&
132
+ model.plainIndexes.every((p) => p.fieldNames[0] !== f.name),
133
+ );
134
+ const contents: string[][] = [
135
+ model.foreignFields.map(writeConstraint),
136
+ hasRelationships.map((r) =>
137
+ [
138
+ r.mappingName ?? r.modelName,
139
+ `${r.modelName}${r.unique ? "?" : "[]"}`,
140
+ ...(r.mappingName ? [`@relation("${r.mappingName}")`] : []),
141
+ ].join(" "),
142
+ ),
143
+ foreignIndexes.map(writeForeignIndex),
144
+ [
145
+ ...model.uniqueIndexes.map(writeUniqueIndex),
146
+ ...model.plainIndexes.map(writePlainIndex),
147
+ ...model.ginIndexes.map(writeGinIndex),
148
+ ],
149
+ ];
150
+ if (contents.every((c) => c.length === 0)) return [];
151
+ return [
152
+ "//----",
153
+ "// RELATIONS",
154
+ "//----",
155
+ // paddled content
156
+ ...ArrayUtil.paddle(contents),
157
+ ];
158
+ }
159
+
160
+ function writeConstraint(field: AutoBePrisma.IForeignField): string {
161
+ return [
162
+ field.relation.name,
163
+ `${field.relation.targetModel}${field.nullable ? "?" : ""}`,
164
+ `@relation(${[
165
+ ...(field.relation.mappingName
166
+ ? [`"${field.relation.mappingName}"`]
167
+ : []),
168
+ `fields: [${field.name}]`,
169
+ `references: [id]`,
170
+ `onDelete: Cascade`,
171
+ ].join(", ")})`,
172
+ ].join(" ");
173
+ }
174
+
175
+ function writeForeignIndex(field: AutoBePrisma.IForeignField): string {
176
+ return `@@${field.unique ? "unique" : "index"}([${field.name}])`;
177
+ }
178
+
179
+ function writeUniqueIndex(field: AutoBePrisma.IUniqueIndex): string {
180
+ return `@@unique([${field.fieldNames.join(", ")}])`;
181
+ }
182
+
183
+ function writePlainIndex(field: AutoBePrisma.IPlainIndex): string {
184
+ return `@@index([${field.fieldNames.join(", ")}])`;
185
+ }
186
+
187
+ function writeGinIndex(field: AutoBePrisma.IGinIndex): string {
188
+ return `@@index([${field.fieldName}(ops: raw("gin_trgm_ops"))], type: Gin)`;
189
+ }
190
+
191
+ /* -----------------------------------------------------------
192
+ BACKGROUND
193
+ ----------------------------------------------------------- */
194
+ function writeComment(content: string): string {
195
+ return content
196
+ .split("\r\n")
197
+ .join("\n")
198
+ .split("\n")
199
+ .map((str) => `///${str.length ? ` ${str}` : ""}`)
200
+ .join("\n")
201
+ .trim();
202
+ }
203
+
204
+ function indent(content: string): string {
205
+ return content
206
+ .split("\r\n")
207
+ .join("\n")
208
+ .split("\n")
209
+ .map((str) => ` ${str}`)
210
+ .join("\n");
211
+ }
212
+
213
+ const LOGICAL_TYPES = {
214
+ // native types
215
+ boolean: "Boolean",
216
+ int: "Int",
217
+ double: "Float",
218
+ string: "String",
219
+ // formats
220
+ datetime: "DateTime",
221
+ uuid: "String",
222
+ uri: "String",
223
+ };
224
+
225
+ const PHYSICAL_TYPES = {
226
+ int: "@db.Integer",
227
+ double: "@db.DoublePrecision",
228
+ uuid: "@db.Uuid",
229
+ datetime: "@db.Timestamptz",
230
+ uri: "@db.VarChar(80000)",
231
+ };
232
+
233
+ const MAIN_FILE = `
234
+ generator client {
235
+ provider = "prisma-client-js"
236
+ previewFeatures = ["postgresqlExtensions", "views"]
237
+ binaryTargets = ["native"]
238
+ }
239
+
240
+ datasource db {
241
+ provider = "postgresql"
242
+ url = env("DATABASE_URL")
243
+ extensions = []
244
+ }
245
+
246
+ generator markdown {
247
+ provider = "prisma-markdown"
248
+ output = "../docs/ERD.md"
249
+ }
250
+ `.trim();
@@ -8,4 +8,14 @@ export namespace ArrayUtil {
8
8
  result[i] = await callback(array[i], i, array);
9
9
  return result;
10
10
  }
11
+
12
+ export function paddle(contents: string[][]): string[] {
13
+ const output: string[] = [];
14
+ contents.forEach((c) => {
15
+ if (c.length === 0) return;
16
+ else if (output.length === 0) output.push(...c);
17
+ else output.push("", ...c);
18
+ });
19
+ return output;
20
+ }
11
21
  }
@@ -0,0 +1,10 @@
1
+ export namespace MapUtil {
2
+ export function take<K, V>(map: Map<K, V>, key: K, value: () => V): V {
3
+ if (map.has(key)) {
4
+ return map.get(key) as V;
5
+ }
6
+ const newValue = value();
7
+ map.set(key, newValue);
8
+ return newValue;
9
+ }
10
+ }