@autobe/utils 0.28.1 → 0.29.1

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 (33) hide show
  1. package/lib/ArrayUtil.d.ts +4 -0
  2. package/lib/ArrayUtil.js +38 -0
  3. package/lib/ArrayUtil.js.map +1 -0
  4. package/lib/aggregate/AutoBeFunctionCallingMetricFactory.d.ts +7 -0
  5. package/lib/aggregate/AutoBeFunctionCallingMetricFactory.js +35 -0
  6. package/lib/aggregate/AutoBeFunctionCallingMetricFactory.js.map +1 -0
  7. package/lib/aggregate/AutoBeProcessAggregateFactory.d.ts +13 -0
  8. package/lib/aggregate/AutoBeProcessAggregateFactory.js +111 -0
  9. package/lib/aggregate/AutoBeProcessAggregateFactory.js.map +1 -0
  10. package/lib/aggregate/TokenUsageComputer.d.ts +6 -0
  11. package/lib/aggregate/TokenUsageComputer.js +42 -0
  12. package/lib/aggregate/TokenUsageComputer.js.map +1 -0
  13. package/lib/aggregate/index.d.ts +3 -0
  14. package/lib/aggregate/index.js +20 -0
  15. package/lib/aggregate/index.js.map +1 -0
  16. package/lib/index.d.ts +3 -0
  17. package/lib/index.js +3 -0
  18. package/lib/index.js.map +1 -1
  19. package/lib/prisma/index.d.ts +1 -0
  20. package/lib/prisma/index.js +18 -0
  21. package/lib/prisma/index.js.map +1 -0
  22. package/lib/prisma/writePrismaApplication.d.ts +5 -0
  23. package/lib/prisma/writePrismaApplication.js +342 -0
  24. package/lib/prisma/writePrismaApplication.js.map +1 -0
  25. package/package.json +3 -2
  26. package/src/ArrayUtil.ts +21 -0
  27. package/src/aggregate/AutoBeFunctionCallingMetricFactory.ts +44 -0
  28. package/src/aggregate/AutoBeProcessAggregateFactory.ts +152 -0
  29. package/src/aggregate/TokenUsageComputer.ts +49 -0
  30. package/src/aggregate/index.ts +3 -0
  31. package/src/index.ts +4 -0
  32. package/src/prisma/index.ts +1 -0
  33. package/src/prisma/writePrismaApplication.ts +439 -0
@@ -0,0 +1,439 @@
1
+ import { AutoBePrisma } from "@autobe/interface";
2
+ import crypto from "crypto";
3
+
4
+ import { ArrayUtil } from "../ArrayUtil";
5
+ import { MapUtil } from "../MapUtil";
6
+ import { StringUtil } from "../StringUtil";
7
+
8
+ export function writePrismaApplication(props: {
9
+ dbms: "postgres" | "sqlite";
10
+ application: AutoBePrisma.IApplication;
11
+ }): Record<string, string> {
12
+ for (const file of props.application.files)
13
+ for (const model of file.models) fillMappingName(model);
14
+ return {
15
+ ...Object.fromEntries(
16
+ props.application.files
17
+ .filter((file) => file.filename !== "main.prisma")
18
+ .map((file) => [
19
+ file.filename,
20
+ writeFile({
21
+ ...props,
22
+ file,
23
+ }),
24
+ ]),
25
+ ),
26
+ "main.prisma":
27
+ props.dbms === "postgres" ? POSTGRES_MAIN_FILE : SQLITE_MAIN_FILE,
28
+ };
29
+ }
30
+
31
+ function writeFile(props: {
32
+ dbms: "postgres" | "sqlite";
33
+ application: AutoBePrisma.IApplication;
34
+ file: AutoBePrisma.IFile;
35
+ }): string {
36
+ return props.file.models
37
+ .map((model) =>
38
+ writeModel({
39
+ ...props,
40
+ model,
41
+ }),
42
+ )
43
+ .join("\n\n");
44
+ }
45
+
46
+ function writeModel(props: {
47
+ dbms: "postgres" | "sqlite";
48
+ application: AutoBePrisma.IApplication;
49
+ file: AutoBePrisma.IFile;
50
+ model: AutoBePrisma.IModel;
51
+ }): string {
52
+ return [
53
+ writeComment(
54
+ [
55
+ props.model.description,
56
+ "",
57
+ ...(props.model.material ? [] : [`@namespace ${props.file.namespace}`]),
58
+ "@author AutoBE - https://github.com/wrtnlabs/autobe",
59
+ ].join("\n"),
60
+ 80,
61
+ ),
62
+ `model ${props.model.name} {`,
63
+ addIndent(
64
+ ArrayUtil.paddle([writeColumns(props), writeRelations(props)]).join("\n"),
65
+ ),
66
+ "}",
67
+ ].join("\n");
68
+ }
69
+
70
+ function fillMappingName(model: AutoBePrisma.IModel): void {
71
+ const group: Map<string, AutoBePrisma.IForeignField[]> = new Map();
72
+ for (const ff of model.foreignFields) {
73
+ MapUtil.take(group, ff.relation.targetModel, () => []).push(ff);
74
+ if (ff.relation.targetModel == model.name)
75
+ ff.relation.mappingName = "recursive";
76
+ }
77
+ for (const array of group.values())
78
+ if (array.length !== 1)
79
+ for (const ff of array)
80
+ ff.relation.mappingName = shortName(`${model.name}_of_${ff.name}`);
81
+ }
82
+
83
+ /* -----------------------------------------------------------
84
+ COLUMNS
85
+ ----------------------------------------------------------- */
86
+ function writeColumns(props: {
87
+ dbms: "postgres" | "sqlite";
88
+ model: AutoBePrisma.IModel;
89
+ }): string[] {
90
+ return [
91
+ "//----",
92
+ "// COLUMNS",
93
+ "//----",
94
+ writePrimary({
95
+ dbms: props.dbms,
96
+ model: props.model,
97
+ field: props.model.primaryField,
98
+ }),
99
+ ...props.model.foreignFields
100
+ .map((x) => [
101
+ "",
102
+ writeField({
103
+ dbms: props.dbms,
104
+ field: x,
105
+ }),
106
+ ])
107
+ .flat(),
108
+ ...props.model.plainFields
109
+ .map((x) => [
110
+ "",
111
+ writeField({
112
+ dbms: props.dbms,
113
+ field: x,
114
+ }),
115
+ ])
116
+ .flat(),
117
+ ];
118
+ }
119
+
120
+ function writePrimary(props: {
121
+ dbms: "postgres" | "sqlite";
122
+ model: AutoBePrisma.IModel;
123
+ field: AutoBePrisma.IPrimaryField;
124
+ }): string {
125
+ const type: string | undefined =
126
+ props.dbms === "postgres" ? POSTGRES_PHYSICAL_TYPES.uuid : undefined;
127
+ const pkeyName: string = `${props.model.name}__pkey`;
128
+ const signature: string =
129
+ pkeyName.length <= MAX_IDENTIFIER_LENGTH
130
+ ? "@id"
131
+ : `@id(map: "${shortName(pkeyName)}")`;
132
+ return [
133
+ writeComment(props.field.description, 78),
134
+ `${props.field.name} String ${signature}${type ? ` ${type}` : ""}`,
135
+ ].join("\n");
136
+ }
137
+
138
+ function writeField(props: {
139
+ dbms: "postgres" | "sqlite";
140
+ field: AutoBePrisma.IPlainField;
141
+ }): string {
142
+ const logical: string = LOGICAL_TYPES[props.field.type];
143
+ const physical: string | undefined =
144
+ props.dbms === "postgres"
145
+ ? POSTGRES_PHYSICAL_TYPES[
146
+ props.field.type as keyof typeof POSTGRES_PHYSICAL_TYPES
147
+ ]
148
+ : undefined;
149
+ return [
150
+ writeComment(props.field.description, 78),
151
+ [
152
+ props.field.name,
153
+ `${logical}${props.field.nullable ? "?" : ""}`,
154
+ ...(physical ? [physical] : []),
155
+ ].join(" "),
156
+ ].join("\n");
157
+ }
158
+
159
+ /* -----------------------------------------------------------
160
+ RELATIONS
161
+ ----------------------------------------------------------- */
162
+ function writeRelations(props: {
163
+ dbms: "postgres" | "sqlite";
164
+ application: AutoBePrisma.IApplication;
165
+ model: AutoBePrisma.IModel;
166
+ }): string[] {
167
+ interface IHasRelationship {
168
+ modelName: string;
169
+ unique: boolean;
170
+ mappingName?: string;
171
+ }
172
+ const hasRelationships: IHasRelationship[] = props.application.files
173
+ .map((otherFile) =>
174
+ otherFile.models.map((otherModel) =>
175
+ otherModel.foreignFields
176
+ .filter(
177
+ (otherForeign) =>
178
+ otherForeign.relation.targetModel === props.model.name,
179
+ )
180
+ .map((otherForeign) => ({
181
+ modelName: otherModel.name,
182
+ unique: otherForeign.unique,
183
+ mappingName: otherForeign.relation.mappingName,
184
+ })),
185
+ ),
186
+ )
187
+ .flat(2);
188
+ const foreignIndexes: AutoBePrisma.IForeignField[] =
189
+ props.model.foreignFields.filter((f) => {
190
+ if (f.unique === true)
191
+ return props.model.uniqueIndexes.every(
192
+ (u) => u.fieldNames.length !== 1 || u.fieldNames[0] !== f.name,
193
+ );
194
+ return (
195
+ props.model.uniqueIndexes.every((u) => u.fieldNames[0] !== f.name) &&
196
+ props.model.plainIndexes.every((p) => p.fieldNames[0] !== f.name)
197
+ );
198
+ });
199
+ const contents: string[][] = [
200
+ props.model.foreignFields.map((foreign) =>
201
+ writeConstraint({
202
+ dbms: props.dbms,
203
+ model: props.model,
204
+ foreign,
205
+ }),
206
+ ),
207
+ hasRelationships.map((r) =>
208
+ [
209
+ r.mappingName ?? r.modelName,
210
+ `${r.modelName}${r.unique ? "?" : "[]"}`,
211
+ ...(r.mappingName ? [`@relation("${r.mappingName}")`] : []),
212
+ ].join(" "),
213
+ ),
214
+ foreignIndexes.map((field) =>
215
+ writeForeignIndex({
216
+ model: props.model,
217
+ field,
218
+ }),
219
+ ),
220
+ [
221
+ ...props.model.uniqueIndexes.map((unique) =>
222
+ writeUniqueIndex({
223
+ model: props.model,
224
+ unique,
225
+ }),
226
+ ),
227
+ ...props.model.plainIndexes.map((plain) =>
228
+ writePlainIndex({
229
+ model: props.model,
230
+ plain,
231
+ }),
232
+ ),
233
+ ...(props.dbms === "postgres"
234
+ ? props.model.ginIndexes.map((gin) =>
235
+ writeGinIndex({
236
+ model: props.model,
237
+ gin,
238
+ }),
239
+ )
240
+ : []),
241
+ ],
242
+ ];
243
+ if (contents.every((c) => c.length === 0)) return [];
244
+ return [
245
+ "//----",
246
+ "// RELATIONS",
247
+ "//----",
248
+ // paddled content
249
+ ...ArrayUtil.paddle(contents),
250
+ ];
251
+ }
252
+
253
+ function writeConstraint(props: {
254
+ dbms: "postgres" | "sqlite";
255
+ model: AutoBePrisma.IModel;
256
+ foreign: AutoBePrisma.IForeignField;
257
+ }): string {
258
+ // spellchecker:ignore-next-line
259
+ const name: string = `${props.model.name}_${props.foreign.name}_rela`;
260
+ const tooMuchLong: boolean =
261
+ props.dbms === "postgres" && name.length > MAX_IDENTIFIER_LENGTH;
262
+ const body: string = [
263
+ props.foreign.relation.name,
264
+ `${props.foreign.relation.targetModel}${props.foreign.nullable ? "?" : ""}`,
265
+ `@relation(${[
266
+ ...(props.foreign.relation.mappingName
267
+ ? [`"${props.foreign.relation.mappingName}"`]
268
+ : []),
269
+ `fields: [${props.foreign.name}]`,
270
+ `references: [id]`,
271
+ `onDelete: Cascade`,
272
+ ...(tooMuchLong ? [`map: "${shortName(name)}"`] : []),
273
+ ].join(", ")})`,
274
+ ].join(" ");
275
+ return tooMuchLong
276
+ ? StringUtil.trim`
277
+ // spellchecker: ignore-next-line
278
+ ${body}
279
+ `
280
+ : body;
281
+ }
282
+
283
+ function writeForeignIndex(props: {
284
+ model: AutoBePrisma.IModel;
285
+ field: AutoBePrisma.IForeignField;
286
+ }): string {
287
+ const name: string = `${props.model.name}_${props.field.name}_fkey`;
288
+ const prefix: string = `@@${props.field.unique === true ? "unique" : "index"}([${props.field.name}]`;
289
+ if (name.length <= MAX_IDENTIFIER_LENGTH) return `${prefix})`;
290
+ return StringUtil.trim`
291
+ // spellchecker: ignore-next-line
292
+ ${prefix}, map: "${shortName(name)}")
293
+ `;
294
+ }
295
+
296
+ function writeUniqueIndex(props: {
297
+ model: AutoBePrisma.IModel;
298
+ unique: AutoBePrisma.IUniqueIndex;
299
+ }): string {
300
+ const name: string = `${props.model.name}_${props.unique.fieldNames.join("_")}_key`;
301
+ const prefix: string = `@@unique([${props.unique.fieldNames.join(", ")}]`;
302
+ if (name.length <= MAX_IDENTIFIER_LENGTH) return `${prefix})`;
303
+ return StringUtil.trim`
304
+ // spellchecker: ignore-next-line
305
+ ${prefix}, map: "${shortName(name)}")
306
+ `;
307
+ }
308
+
309
+ function writePlainIndex(props: {
310
+ model: AutoBePrisma.IModel;
311
+ plain: AutoBePrisma.IPlainIndex;
312
+ }): string {
313
+ const name: string = `${props.model.name}_${props.plain.fieldNames.join("_")}_idx`;
314
+ const prefix: string = `@@index([${props.plain.fieldNames.join(", ")}]`;
315
+ if (name.length <= MAX_IDENTIFIER_LENGTH) return `${prefix})`;
316
+ return StringUtil.trim`
317
+ // spellchecker: ignore-next-line
318
+ ${prefix}, map: "${shortName(name)}")
319
+ `;
320
+ }
321
+
322
+ function writeGinIndex(props: {
323
+ model: AutoBePrisma.IModel;
324
+ gin: AutoBePrisma.IGinIndex;
325
+ }): string {
326
+ const name: string = `${props.model.name}_${props.gin.fieldName}_idx`;
327
+ const prefix: string = `@@index([${props.gin.fieldName}(ops: raw("gin_trgm_ops"))], type: Gin`;
328
+ if (name.length <= MAX_IDENTIFIER_LENGTH) return `${prefix})`;
329
+ return StringUtil.trim`
330
+ // spellchecker: ignore-next-line
331
+ ${prefix}, map: "${shortName(name)}")
332
+ `;
333
+ }
334
+
335
+ /* -----------------------------------------------------------
336
+ BACKGROUND
337
+ ----------------------------------------------------------- */
338
+ function writeComment(content: string, length: number): string {
339
+ return content
340
+ .split("\r\n")
341
+ .join("\n")
342
+ .split("\n")
343
+ .map((line) => line.trim())
344
+ .map((line) => {
345
+ // 77자에서 "/// " 4자를 뺀 73자가 실제 컨텐츠 최대 길이
346
+ if (line.length <= length - 4) return [line];
347
+ const words: string[] = line.split(" ");
348
+ const result: string[] = [];
349
+ let currentLine = "";
350
+
351
+ for (const word of words) {
352
+ const potentialLine = currentLine ? `${currentLine} ${word}` : word;
353
+ if (potentialLine.length <= 73) {
354
+ currentLine = potentialLine;
355
+ } else {
356
+ if (currentLine) result.push(currentLine);
357
+ currentLine = word;
358
+ }
359
+ }
360
+
361
+ if (currentLine) result.push(currentLine);
362
+ return result;
363
+ })
364
+ .flat()
365
+ .map((str) => `///${str.length ? ` ${str}` : ""}`)
366
+ .join("\n")
367
+ .trim();
368
+ }
369
+
370
+ function addIndent(content: string): string {
371
+ return content
372
+ .split("\r\n")
373
+ .join("\n")
374
+ .split("\n")
375
+ .map((str) => ` ${str}`)
376
+ .join("\n");
377
+ }
378
+
379
+ function shortName(name: string): string {
380
+ if (name.length <= MAX_IDENTIFIER_LENGTH) return name;
381
+ const hash: string = crypto
382
+ .createHash("md5")
383
+ .update(name)
384
+ .digest("hex")
385
+ .substring(0, HASH_TRUNCATION_LENGTH);
386
+ return `${name.substring(0, MAX_IDENTIFIER_LENGTH - HASH_TRUNCATION_LENGTH - 1)}_${hash}`;
387
+ }
388
+
389
+ const LOGICAL_TYPES = {
390
+ // native types
391
+ boolean: "Boolean",
392
+ int: "Int",
393
+ double: "Float",
394
+ string: "String",
395
+ // formats
396
+ datetime: "DateTime",
397
+ uuid: "String",
398
+ uri: "String",
399
+ };
400
+ const POSTGRES_PHYSICAL_TYPES = {
401
+ int: "@db.Integer",
402
+ double: "@db.DoublePrecision",
403
+ uuid: "@db.Uuid",
404
+ datetime: "@db.Timestamptz",
405
+ uri: "@db.VarChar(80000)",
406
+ };
407
+
408
+ const POSTGRES_MAIN_FILE = StringUtil.trim`
409
+ generator client {
410
+ provider = "prisma-client-js"
411
+ engineType = "client"
412
+ previewFeatures = ["postgresqlExtensions", "views"]
413
+ }
414
+ datasource db {
415
+ provider = "postgresql"
416
+ url = env("DATABASE_URL")
417
+ extensions = [pg_trgm]
418
+ }
419
+ generator markdown {
420
+ provider = "prisma-markdown"
421
+ output = "../../docs/ERD.md"
422
+ }
423
+ `;
424
+ const SQLITE_MAIN_FILE = StringUtil.trim`
425
+ generator client {
426
+ provider = "prisma-client-js"
427
+ engineType = "client"
428
+ }
429
+ datasource db {
430
+ provider = "sqlite"
431
+ url = "file:../db.sqlite"
432
+ }
433
+ generator markdown {
434
+ provider = "prisma-markdown"
435
+ output = "../../docs/ERD.md"
436
+ }
437
+ `;
438
+ const MAX_IDENTIFIER_LENGTH = 63;
439
+ const HASH_TRUNCATION_LENGTH = 8;