@technicity/data-service-generator 0.22.2 → 0.23.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/dist/src/generation/generate.d.ts +21 -0
  2. package/dist/src/generation/generate.js +2349 -0
  3. package/dist/src/index.d.ts +4 -0
  4. package/dist/src/index.js +11 -0
  5. package/dist/src/lib/CustomError.d.ts +3 -0
  6. package/dist/src/lib/CustomError.js +10 -0
  7. package/dist/src/lib/capitalizeFirstLetter.d.ts +1 -0
  8. package/dist/src/lib/capitalizeFirstLetter.js +6 -0
  9. package/dist/src/lib/getDuplicates.d.ts +1 -0
  10. package/dist/src/lib/getDuplicates.js +9 -0
  11. package/dist/src/lib/isNotNullOrUndefined.d.ts +1 -0
  12. package/dist/src/lib/isNotNullOrUndefined.js +7 -0
  13. package/dist/src/runtime/Cache.d.ts +28 -0
  14. package/dist/src/runtime/Cache.js +142 -0
  15. package/dist/src/runtime/IRuntime.d.ts +209 -0
  16. package/dist/src/runtime/IRuntime.js +12 -0
  17. package/dist/src/runtime/RuntimeMySQL.d.ts +26 -0
  18. package/dist/src/runtime/RuntimeMySQL.js +132 -0
  19. package/dist/src/runtime/RuntimePostgreSQL.d.ts +30 -0
  20. package/dist/src/runtime/RuntimePostgreSQL.js +73 -0
  21. package/dist/src/runtime/RuntimeSQLite.d.ts +42 -0
  22. package/dist/src/runtime/RuntimeSQLite.js +150 -0
  23. package/dist/src/runtime/Stats.d.ts +8 -0
  24. package/dist/src/runtime/Stats.js +31 -0
  25. package/dist/src/runtime/lib/MySQL.d.ts +13 -0
  26. package/dist/src/runtime/lib/MySQL.js +116 -0
  27. package/dist/src/runtime/lib/PostgreSQL.d.ts +14 -0
  28. package/dist/src/runtime/lib/PostgreSQL.js +110 -0
  29. package/dist/src/runtime/lib/SDKBadWhereError.d.ts +4 -0
  30. package/dist/src/runtime/lib/SDKBadWhereError.js +10 -0
  31. package/dist/src/runtime/lib/SDKNotFoundError.d.ts +4 -0
  32. package/dist/src/runtime/lib/SDKNotFoundError.js +10 -0
  33. package/dist/src/runtime/lib/addNullFallbacks.d.ts +1 -0
  34. package/dist/src/runtime/lib/addNullFallbacks.js +32 -0
  35. package/dist/src/runtime/lib/addNullFallbacks.test.d.ts +1 -0
  36. package/dist/src/runtime/lib/addNullFallbacks.test.js +206 -0
  37. package/dist/src/runtime/lib/cursor.d.ts +2 -0
  38. package/dist/src/runtime/lib/cursor.js +10 -0
  39. package/dist/src/runtime/lib/getDateTimeStringMySQL.d.ts +1 -0
  40. package/dist/src/runtime/lib/getDateTimeStringMySQL.js +7 -0
  41. package/dist/src/runtime/lib/getOrderBy.d.ts +5 -0
  42. package/dist/src/runtime/lib/getOrderBy.js +52 -0
  43. package/dist/src/runtime/lib/getSqlAst.d.ts +2 -0
  44. package/dist/src/runtime/lib/getSqlAst.js +245 -0
  45. package/dist/src/runtime/lib/getWhere.d.ts +2 -0
  46. package/dist/src/runtime/lib/getWhere.js +20 -0
  47. package/dist/src/runtime/lib/shared.d.ts +13 -0
  48. package/dist/src/runtime/lib/shared.js +1118 -0
  49. package/dist/src/runtime/lib/stringifyWhere.d.ts +18 -0
  50. package/dist/src/runtime/lib/stringifyWhere.js +257 -0
  51. package/dist/src/runtime/lib/stringifyWhere.test.d.ts +1 -0
  52. package/dist/src/runtime/lib/stringifyWhere.test.js +245 -0
  53. package/dist/src/runtime/lib/utility.d.ts +5 -0
  54. package/dist/src/runtime/lib/utility.js +14 -0
  55. package/dist/src/traverseFieldArgs.d.ts +2 -0
  56. package/dist/src/traverseFieldArgs.js +17 -0
  57. package/dist/src/traverseFieldArgs.test.d.ts +1 -0
  58. package/dist/src/traverseFieldArgs.test.js +56 -0
  59. package/dist/test/addWhereValidTrue.d.ts +1 -0
  60. package/dist/test/addWhereValidTrue.js +39 -0
  61. package/dist/test/globalSetup.d.ts +13 -0
  62. package/dist/test/globalSetup.js +436 -0
  63. package/dist/test/postgres/__generated__/sdk-ts/artifacts.d.ts +8425 -0
  64. package/dist/test/postgres/__generated__/sdk-ts/artifacts.js +10469 -0
  65. package/dist/test/postgres/__generated__/sdk-ts/index.js +12162 -0
  66. package/package.json +5 -1
@@ -0,0 +1,2349 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || function (mod) {
19
+ if (mod && mod.__esModule) return mod;
20
+ var result = {};
21
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
+ __setModuleDefault(result, mod);
23
+ return result;
24
+ };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
28
+ Object.defineProperty(exports, "__esModule", { value: true });
29
+ exports.generate = generate;
30
+ const path = __importStar(require("node:path"));
31
+ const fs = __importStar(require("node:fs"));
32
+ const os = __importStar(require("node:os"));
33
+ const node_async_hooks_1 = require("node:async_hooks");
34
+ const child_process = __importStar(require("node:child_process"));
35
+ const node_crypto_1 = __importDefault(require("node:crypto"));
36
+ const prettier = __importStar(require("prettier"));
37
+ const changeCase = __importStar(require("change-case"));
38
+ const fse = __importStar(require("fs-extra"));
39
+ const _ = __importStar(require("lodash/fp"));
40
+ const memoize_1 = __importDefault(require("lodash/memoize"));
41
+ const json_schema_to_typescript_1 = require("json-schema-to-typescript");
42
+ const getDuplicates_1 = require("../lib/getDuplicates");
43
+ const isNotNullOrUndefined_1 = require("../lib/isNotNullOrUndefined");
44
+ const pg_1 = require("pg");
45
+ const MySQL_1 = require("../runtime/lib/MySQL");
46
+ const capitalizeFirstLetter_1 = require("../lib/capitalizeFirstLetter");
47
+ const ctxStorage = new node_async_hooks_1.AsyncLocalStorage();
48
+ function getCtx() {
49
+ const c = ctxStorage.getStore();
50
+ if (!c)
51
+ throw new Error("generate() context missing");
52
+ return c;
53
+ }
54
+ const json2TsOpts = {
55
+ bannerComment: ""
56
+ };
57
+ const keyFields = "$fields";
58
+ async function generate(input) {
59
+ if (input.tables != null && input.excludeTables != null) {
60
+ throw new Error("Must specify either `tables` or `excludeTables`, not both");
61
+ }
62
+ if (input.dialect == null) {
63
+ throw new Error("Must specify `dialect`");
64
+ }
65
+ const specialCaseUuidColumn = input.specialCaseUuidColumn ?? true;
66
+ const includeMappedFields = input.includeMappedFields ?? true;
67
+ const supplementClientOpts = input.supplementClientOpts ?? true;
68
+ const ctx = {
69
+ runId: node_crypto_1.default.randomUUID(),
70
+ dialect: input.dialect,
71
+ query: undefined
72
+ };
73
+ return ctxStorage.run(ctx, async () => {
74
+ init(input);
75
+ let tables = await getTableNames();
76
+ if (tables.length === 0) {
77
+ throw new Error("No tables found");
78
+ }
79
+ if (input.tables != null) {
80
+ tables = tables.filter((x) => input.tables?.includes(x));
81
+ }
82
+ if (input.excludeTables != null) {
83
+ tables = tables.filter((x) => !input.excludeTables?.includes(x));
84
+ }
85
+ const data = await Promise.all(tables.flatMap((x) => [
86
+ getGetOneData(x, includeMappedFields),
87
+ getGetListData(x),
88
+ getGetListPaginatedData(x),
89
+ getPostOneData(x, specialCaseUuidColumn, includeMappedFields),
90
+ getPatchOneData(x, specialCaseUuidColumn, includeMappedFields),
91
+ getPatchListData(x),
92
+ getDeleteOneData(x),
93
+ getDeleteListData(x)
94
+ ]));
95
+ const artifacts = await getArtifacts(tables, includeMappedFields, specialCaseUuidColumn);
96
+ const artifactsSource = getArtifactsSource(artifacts);
97
+ const sdkSource = await getSDKSource(data, specialCaseUuidColumn, supplementClientOpts, artifacts, input.outputSqliteSchema);
98
+ const sdkFilename = "index.ts";
99
+ const sourceIRuntimeFilePath = fs.existsSync(path.join(__dirname, "../runtime", "IRuntime.ts"))
100
+ ? path.join(__dirname, "../runtime", "IRuntime.ts")
101
+ : path.join(__dirname, "../runtime", "IRuntime.js");
102
+ const IRuntimeFilename = path.basename(sourceIRuntimeFilePath);
103
+ const artifactsFilename = "artifacts.ts";
104
+ const tsConfigJSON = {
105
+ compilerOptions: {
106
+ module: "commonjs",
107
+ moduleResolution: "node",
108
+ target: "es2020",
109
+ declaration: true,
110
+ outDir: "./sdk-ts"
111
+ },
112
+ include: [sdkFilename, artifactsFilename, IRuntimeFilename]
113
+ };
114
+ const packageJSON = {
115
+ name: "temp",
116
+ version: "1.0.0",
117
+ // Deps need to be included so that they're inlined by ncc
118
+ dependencies: require("../../package.json").dependencies,
119
+ devDependencies: {
120
+ "@types/node": require("../../package.json").devDependencies["@types/node"],
121
+ typescript: require("../../package.json").devDependencies.typescript
122
+ },
123
+ // Not `resolutions` because npm used for install
124
+ overrides: {
125
+ // Fix for: `Cannot find type definition file for 'glob'`
126
+ glob: ">9.0.0"
127
+ }
128
+ };
129
+ const tmpDirPath = path.join(os.tmpdir(),
130
+ // _ because - in filename is not supported by mysql2sqlite
131
+ `dsg_${node_crypto_1.default.randomUUID()}`.replace(/-/g, "_"));
132
+ fse.mkdirpSync(tmpDirPath);
133
+ fs.writeFileSync(path.join(tmpDirPath, sdkFilename), sdkSource);
134
+ fs.writeFileSync(path.join(tmpDirPath, artifactsFilename), artifactsSource);
135
+ fse.copyFileSync(sourceIRuntimeFilePath, path.join(tmpDirPath, IRuntimeFilename));
136
+ const typesDirPath = path.join(tmpDirPath, "types");
137
+ fse.mkdirpSync(typesDirPath);
138
+ fs.writeFileSync(path.join(typesDirPath, "_shared.ts"), getTypeShared());
139
+ for (let x of data) {
140
+ if (x.kind === "getOne") {
141
+ fs.writeFileSync(path.join(typesDirPath, x.typeFieldsName + ".ts"), x.typeFields);
142
+ fs.writeFileSync(path.join(typesDirPath, x.typeReturnBaseName + ".ts"), x.typeReturnBase);
143
+ }
144
+ if (x.kind === "getList") {
145
+ fs.writeFileSync(path.join(typesDirPath, x.typeWhereName + ".ts"), x.typeWhere);
146
+ fs.writeFileSync(path.join(typesDirPath, x.typeOrderByName + ".ts"), x.typeOrderBy);
147
+ }
148
+ if (x.kind === "postOne") {
149
+ fs.writeFileSync(path.join(typesDirPath, x.typeDataName + ".ts"), x.typeData);
150
+ }
151
+ if (x.kind === "patchOne") {
152
+ fs.writeFileSync(path.join(typesDirPath, x.typeDataName + ".ts"), x.typeData);
153
+ }
154
+ }
155
+ fs.writeFileSync(path.join(typesDirPath, "index.ts"), getTypeTypesIndex(data));
156
+ fs.writeFileSync(path.join(tmpDirPath, "package.json"), JSON.stringify(packageJSON, null, 2));
157
+ fs.writeFileSync(path.join(tmpDirPath, "tsconfig.json"), JSON.stringify(tsConfigJSON, null, 2));
158
+ fse.copySync(__dirname, path.join(tmpDirPath, "src"));
159
+ const tmpBuildOutputPath = path.join(tmpDirPath, "sdk-ts");
160
+ const outdir = path.resolve(input.outdir);
161
+ const sdkOutputPath = path.join(outdir, "sdk-ts");
162
+ const nccVersion = "^0.33.0";
163
+ child_process.execSync("npm i", { cwd: tmpDirPath, stdio: "inherit" });
164
+ child_process.execSync(`npm_config_yes=true npx -p @vercel/ncc@${nccVersion} ncc build ./${sdkFilename} -o ${tmpBuildOutputPath} -e ./artifacts`, { cwd: tmpDirPath, stdio: "inherit" });
165
+ // TODO: workaround for artifacts.js not being output by ncc
166
+ fs.writeFileSync(path.join(tmpBuildOutputPath, "artifacts.js"), artifactsSource
167
+ .replace("export const artifacts: IArtifacts = ", "module.exports.artifacts = ")
168
+ .split("\n")
169
+ // Remove import
170
+ .slice(2)
171
+ .join("\n"));
172
+ // TODO: workaround for IRuntime.d.ts not being included
173
+ // copyFileSync hangs for some reason, so use writeFileSync + readFileSync instead
174
+ fs.writeFileSync(path.join(tmpBuildOutputPath, "IRuntime.d.ts"), fs.existsSync(path.join(__dirname, "../runtime", "IRuntime.d.ts"))
175
+ ? fs.readFileSync(path.join(__dirname, "../runtime", "IRuntime.d.ts"), "utf-8")
176
+ : fs.readFileSync(sourceIRuntimeFilePath, "utf-8"));
177
+ if (getCtx().dialect === "mysql" && input.outputSqliteSchema) {
178
+ // Since mysql2sqlite outputs a malformed string if a column
179
+ // has the name `enum`, temporarily change the name to something else,
180
+ // then change it back.
181
+ const enumMarker = "`" + node_crypto_1.default.randomUUID() + "`";
182
+ const schemaMySql = Object.values(artifacts)
183
+ .reduce((acc, x) => {
184
+ let d = x.dump?.schema;
185
+ if (!d) {
186
+ return acc;
187
+ }
188
+ d = d.replace(/`enum`/g, enumMarker);
189
+ d += ";";
190
+ acc.push(d);
191
+ return acc;
192
+ }, [])
193
+ .join("\n\n");
194
+ const mysql2SqliteSrc = getMysql2sqliteSrc();
195
+ const mysql2SqlitePath = path.join(tmpDirPath, "mysql2sqlite");
196
+ fs.writeFileSync(mysql2SqlitePath, mysql2SqliteSrc);
197
+ fs.chmodSync(mysql2SqlitePath, 0o755);
198
+ const tmpMySqlSchemaFilename = "tmp.sql";
199
+ const tmpMySqlSchemaPath = path.join(tmpDirPath, tmpMySqlSchemaFilename);
200
+ fs.writeFileSync(tmpMySqlSchemaPath, schemaMySql);
201
+ let schemaSqlite = child_process
202
+ .execFileSync(mysql2SqlitePath, [tmpMySqlSchemaFilename], { cwd: tmpDirPath })
203
+ .toString();
204
+ schemaSqlite = schemaSqlite.replace(new RegExp(enumMarker, "g"), "`enum`");
205
+ const src = prettier.format(`module.exports = { schema: \`${schemaSqlite.replace(/`/g, "\\`")}\` }`, { parser: "babel" });
206
+ fs.writeFileSync(path.join(tmpBuildOutputPath, "artifacts.sqlite.js"), src);
207
+ }
208
+ if (!fs.existsSync(outdir)) {
209
+ fse.mkdirpSync(outdir);
210
+ }
211
+ fse.emptyDirSync(sdkOutputPath);
212
+ fse.copySync(tmpBuildOutputPath, sdkOutputPath);
213
+ fse.removeSync(tmpDirPath);
214
+ });
215
+ }
216
+ function init(input) {
217
+ const ctx = getCtx();
218
+ const { database, user, password, host, port, server } = input;
219
+ if (ctx.dialect === "mysql") {
220
+ const mysql = new MySQL_1.MySQL({
221
+ user,
222
+ password,
223
+ host,
224
+ port,
225
+ database
226
+ });
227
+ ctx.query = mysql.query.bind(mysql);
228
+ }
229
+ if (ctx.dialect === "postgresql") {
230
+ const pool = new pg_1.Pool({
231
+ host: host ?? "localhost",
232
+ port: port ?? 5432,
233
+ user,
234
+ password,
235
+ database
236
+ });
237
+ ctx.query = (q, values) => pool.query(q, values ?? []).then((r) => r.rows);
238
+ }
239
+ }
240
+ // It's a bit awkward to put __whereNeedsProcessing, __prepareWhere on the class,
241
+ // but it allows us to share the same database pool, clientOpts, etc.
242
+ async function getSDKSource(input, specialCaseUuidColumn, supplementClientOpts, artifacts, outputSqliteSchema) {
243
+ function getTypeImports() {
244
+ let set = new Set();
245
+ for (let d of input) {
246
+ for (let k of [
247
+ "typeFieldsName",
248
+ "typeReturnBaseName",
249
+ "typeWhereName",
250
+ "typeOrderByName",
251
+ "typeDataName"
252
+ ]) {
253
+ const str = d[k];
254
+ if (str) {
255
+ set.add(str);
256
+ }
257
+ }
258
+ }
259
+ return `import type { ${["Paginate", "ListPaginated"]
260
+ .concat(Array.from(set).sort())
261
+ .join(",\n")} } from "./types";`;
262
+ }
263
+ const src = `import { AsyncLocalStorage } from "node:async_hooks";
264
+ import type { IRuntime, TMiddleware, TContext, TOnHandler, EventOnHandlerError } from "./IRuntime"
265
+ import { artifacts } from "./artifacts";
266
+
267
+ ${getTypeImports()}
268
+
269
+ const asyncLocalStorage = new AsyncLocalStorage<{ isInOnHandler: boolean }>();
270
+
271
+ export class SDK {
272
+ runtime: IRuntime;
273
+ onHandlerMap: Map<string, TOnHandler>;
274
+ eventTarget: EventTarget;
275
+ passBeforeValueToAfterCallback: boolean;
276
+
277
+ constructor(opts: {
278
+ runtime: any;
279
+ clientOpts: { [k: string]: any; },
280
+ otherOpts?: { [k: string]: any; },
281
+ passBeforeValueToAfterCallback: boolean,
282
+ }) {
283
+ let otherOpts = opts.otherOpts ?? {};
284
+ if (opts.clientOpts.filename === ":memory:") {
285
+ ${outputSqliteSchema === true
286
+ ? `otherOpts = { ...otherOpts, createTablesString: require("./artifacts.sqlite").schema }`
287
+ : `throw new Error("SQLite artifacts not available. Please regenerate the SDK with \`outputSqliteSchema: true\`.")`}
288
+ }
289
+ this.runtime = new opts.runtime(opts.clientOpts, ${supplementClientOpts === true
290
+ ? "{ supplementClientOpts: true, ...otherOpts }"
291
+ : "otherOpts"}, artifacts);
292
+ this.onHandlerMap = new Map();
293
+ this.eventTarget = new EventTarget();
294
+ this.passBeforeValueToAfterCallback = opts.passBeforeValueToAfterCallback;
295
+ }
296
+
297
+ $use(middleware: TMiddleware) {
298
+ return this.runtime.$use(middleware);
299
+ }
300
+
301
+ $whereNeedsProcessing(where: any) {
302
+ return this.runtime.$whereNeedsProcessing(where);
303
+ }
304
+
305
+ async $prepareWhere(
306
+ table: string,
307
+ where: any
308
+ ) {
309
+ return this.runtime.$prepareWhere(artifacts, table, where);
310
+ }
311
+
312
+ async $queryRaw(sql: string, values?: any[]) {
313
+ return this.runtime.$queryRaw(sql, values);
314
+ }
315
+
316
+ async $shutdown() {
317
+ return this.runtime.$shutdown();
318
+ }
319
+
320
+ async $startTransaction(input?: {
321
+ isolationLevel?:
322
+ | "READ UNCOMMITTED"
323
+ | "READ COMMITTED"
324
+ | "REPEATABLE READ"
325
+ | "SERIALIZABLE"
326
+ }) {
327
+ const { dbCall, queryRaw, commit, rollback } = await this.runtime.$startTransaction(input);
328
+ const runtime = this.runtime;
329
+ const eventTarget = this.eventTarget;
330
+ const onHandlerMap = this.onHandlerMap;
331
+ const passBeforeValueToAfterCallback = this.passBeforeValueToAfterCallback;
332
+ return {
333
+ $queryRaw: queryRaw,
334
+ $commit: commit,
335
+ $rollback: rollback,
336
+ ${(await Promise.all(input.flatMap(async (x) => {
337
+ if (x.kind === "getOne") {
338
+ const findOnes = await getFindOnes(x, specialCaseUuidColumn);
339
+ return getMethodSourceGetOne(x, findOnes, true);
340
+ }
341
+ if (x.kind === "getList") {
342
+ return getMethodSourceGetList(x, true);
343
+ }
344
+ if (x.kind === "getListPaginated") {
345
+ return getMethodSourceGetListPaginated(x, true);
346
+ }
347
+ if (x.kind === "postOne") {
348
+ return getMethodSourcePostOne(x, specialCaseUuidColumn, true);
349
+ }
350
+ if (x.kind === "patchOne") {
351
+ const findOnes = await getFindOnes(x, specialCaseUuidColumn);
352
+ return getMethodSourcePatchOne(x, findOnes, true);
353
+ }
354
+ if (x.kind === "patchList") {
355
+ return getMethodSourcePatchList(x, true);
356
+ }
357
+ if (x.kind === "deleteOne") {
358
+ const findOnes = await getFindOnes(x, specialCaseUuidColumn);
359
+ return getMethodSourceDeleteOne(x, findOnes, true);
360
+ }
361
+ if (x.kind === "deleteList") {
362
+ return getMethodSourceDeleteList(x, true);
363
+ }
364
+ }))).join(",\n")}
365
+ }
366
+ }
367
+
368
+ onOnHandlerError(cb: (event: EventOnHandlerError<unknown>) => void) {
369
+ this.eventTarget.addEventListener("error", cb);
370
+ }
371
+
372
+ ${(await Promise.all(input.flatMap(async (x) => {
373
+ if (x.kind === "getOne") {
374
+ const findOnes = await getFindOnes(x, specialCaseUuidColumn);
375
+ return getMethodSourceGetOne(x, findOnes, false);
376
+ }
377
+ if (x.kind === "getList") {
378
+ return getMethodSourceGetList(x, false);
379
+ }
380
+ if (x.kind === "getListPaginated") {
381
+ return getMethodSourceGetListPaginated(x, false);
382
+ }
383
+ if (x.kind === "postOne") {
384
+ return getMethodSourcePostOne(x, specialCaseUuidColumn, false);
385
+ }
386
+ if (x.kind === "patchOne") {
387
+ const findOnes = await getFindOnes(x, specialCaseUuidColumn);
388
+ return getMethodSourcePatchOne(x, findOnes, false);
389
+ }
390
+ if (x.kind === "patchList") {
391
+ return getMethodSourcePatchList(x, false);
392
+ }
393
+ if (x.kind === "deleteOne") {
394
+ const findOnes = await getFindOnes(x, specialCaseUuidColumn);
395
+ return getMethodSourceDeleteOne(x, findOnes, false);
396
+ }
397
+ if (x.kind === "deleteList") {
398
+ return getMethodSourceDeleteList(x, false);
399
+ }
400
+ }))).join("\n\n")}
401
+
402
+ ${(await Promise.all(input.flatMap(async (x) => {
403
+ if (x.kind === "postOne") {
404
+ return getMethodSourceOnHandlerPostOne(x);
405
+ }
406
+ if (x.kind === "patchOne") {
407
+ const findOnes = await getFindOnes(x, specialCaseUuidColumn);
408
+ return getMethodSourceOnHandlerPatchOne(x, findOnes);
409
+ }
410
+ if (x.kind === "patchList") {
411
+ return getMethodSourceOnHandlerPatchList(x);
412
+ }
413
+ if (x.kind === "deleteOne") {
414
+ const findOnes = await getFindOnes(x, specialCaseUuidColumn);
415
+ return getMethodSourceOnHandlerDeleteOne(x, findOnes);
416
+ }
417
+ if (x.kind === "deleteList") {
418
+ return getMethodSourceOnHandlerDeleteList(x);
419
+ }
420
+ })))
421
+ .filter(Boolean)
422
+ .join("\n\n")}
423
+ }
424
+
425
+ ${await Promise.all(Object.entries(artifacts).map(async ([table, tableArtifacts]) => {
426
+ const jsonSchema = {
427
+ type: "object",
428
+ properties: tableArtifacts.fields.reduce((acc, x) => {
429
+ if (x.kind === "object") {
430
+ return acc;
431
+ }
432
+ if (x.kind === "scalar" && x.mapped) {
433
+ return acc;
434
+ }
435
+ acc[x.name] =
436
+ x.kind === "enum"
437
+ ? { enum: x.values }
438
+ : { type: getJSONTypes(x.type, x.nullable) };
439
+ return acc;
440
+ }, {}),
441
+ required: tableArtifacts.fields
442
+ .filter((x) => x.kind === "scalar" || x.kind === "enum")
443
+ .map((x) => x.name),
444
+ additionalProperties: false
445
+ };
446
+ return (0, json_schema_to_typescript_1.compile)(jsonSchema, getTypeReturnName(table), json2TsOpts);
447
+ })).then((xs) => xs.join(";\n\n"))}
448
+
449
+ ${Object.entries(artifacts)
450
+ .map(([table, tableArtifacts]) => {
451
+ // https://pkerschbaum.com/blog/how-prisma-adapts-result-types-based-on-the-actual-arguments-given
452
+ // https://github.com/pkerschbaum/how-prisma-adapts-result-types-based-on-the-actual-arguments-given_example/blob/main/prisma/lib/prisma-client/index.d.ts
453
+ return `export type ${getTypeGetPayloadName(table)}<S extends { ${keyFields}?: ${getTypeFieldsName(table)} } | boolean, U = keyof S> = S extends true ? ${getTypeReturnName(table)} : S extends { ${keyFields}?: ${getTypeFieldsName(table)}, [k: string]: any } ? "${keyFields}" extends U ? S["${keyFields}"] extends undefined ? ${getTypeReturnName(table)} : { [P in TrueKeys<S["${keyFields}"]>]: ${tableArtifacts.fields
454
+ .reduce((acc, x) => {
455
+ if (x.kind !== "object") {
456
+ return acc;
457
+ }
458
+ let str = `P extends "${x.name}" ? ${getTypeGetPayloadName(x.type)}<S["${keyFields}"][P]>`;
459
+ if (x.isList) {
460
+ str += "[]";
461
+ }
462
+ if (x.nullable) {
463
+ str += " | null";
464
+ }
465
+ str += " : ";
466
+ acc.push(str);
467
+ return acc;
468
+ }, [])
469
+ .join("")} ${tableArtifacts.fields
470
+ .reduce((acc, x) => {
471
+ if (x.kind === "scalar" && x.mapped) {
472
+ let type = x.type;
473
+ if (x.nullable) {
474
+ type += " | null";
475
+ }
476
+ const str = `P extends "${x.name}" ? ${type} : `;
477
+ acc.push(str);
478
+ }
479
+ return acc;
480
+ }, [])
481
+ .join("")} P extends keyof ${getTypeReturnName(table)} ? ${getTypeReturnName(table)}[P] : never } : ${getTypeReturnName(table)} : ${getTypeReturnName(table)}`;
482
+ })
483
+ .join(";\n\n")}
484
+
485
+ // Utils
486
+
487
+ type Pick2<T, K extends keyof T> = { [P in K]: T[P]; };
488
+
489
+ type RequiredKeys<T> = {
490
+ [K in keyof T]-?: {} extends Pick2<T, K> ? never : K;
491
+ }[keyof T];
492
+
493
+ type TruthyKeys<T> = {
494
+ [key in keyof T]: T[key] extends false | undefined | null ? never : key;
495
+ }[keyof T];
496
+
497
+ type TrueKeys<T> = TruthyKeys<Pick2<T, RequiredKeys<T>>>;
498
+
499
+ type SelectSubset<T, U> = {
500
+ [key in keyof T]: key extends keyof U ? T[key] : never;
501
+ }
502
+
503
+ type HasSelect = { ${keyFields}: any; };
504
+
505
+ type CheckSelect<T, S, U> = T extends HasSelect ? U : S;
506
+ `;
507
+ return prettier.format(src, { parser: "typescript" });
508
+ }
509
+ async function getFindOnes(x, specialCaseUuidColumn) {
510
+ let findOnes = [];
511
+ const primaryColumn = await getPrimaryColumn(x.table);
512
+ const uniqueColumns = await getUniqueColumns(x.table, specialCaseUuidColumn);
513
+ findOnes = findOnes
514
+ .concat([primaryColumn])
515
+ .concat(uniqueColumns)
516
+ .map((x) => ({
517
+ ...x,
518
+ type: x.type === "integer" ? "number" : x.type
519
+ }));
520
+ return findOnes;
521
+ }
522
+ function getMethodSourceGetOne(x, findOnes, isTransaction) {
523
+ const param2 = `{ ${keyFields}?: ${x.typeFieldsName}, correlationId?: string, skipCache?: boolean, context?: TContext }`;
524
+ return `async ${x.methodName}<T extends ${param2}>(
525
+ param1: ${findOnes
526
+ .map((findOne) => `{ ${findOne.name}: ${findOne.type}${findOne.nullable ? " | null" : ""} }`)
527
+ .join(" | ")},
528
+ param2?: SelectSubset<T, ${param2}>
529
+ ): Promise<CheckSelect<T, ${getTypeReturnName(x.table)}, ${getTypeGetPayloadName(x.table)}<T>>> {
530
+ return ${isTransaction ? "runtime" : "this.runtime"}.resolve(
531
+ {
532
+ resource: "${x.table}",
533
+ action: "${mapKindToAction(x.kind)}",
534
+ args: { $where: param1 },
535
+ fields: param2?.$fields as any,
536
+ artifacts,
537
+ context: param2?.context,
538
+ skipCache: param2?.skipCache,
539
+ eventTarget: ${isTransaction ? "eventTarget" : "this.eventTarget"},
540
+ ${isTransaction ? "dbCall," : ""}
541
+ passBeforeValueToAfterCallback: ${isTransaction
542
+ ? "passBeforeValueToAfterCallback"
543
+ : "this.passBeforeValueToAfterCallback"},
544
+ }
545
+ );
546
+ }`;
547
+ }
548
+ function getMethodSourceGetList(x, isTransaction) {
549
+ const param2 = `{ ${keyFields}?: ${x.typeFieldsName}, correlationId?: string, skipCache?: boolean, context?: TContext }`;
550
+ return `async ${x.methodName}<T extends ${param2}>(
551
+ param1: { $where?: ${x.typeWhereName}, $orderBy?: ${x.typeOrderByName}, $limit?: number },
552
+ param2?: SelectSubset<T, ${param2}>
553
+ ): Promise<Array<CheckSelect<T, ${getTypeReturnName(x.table)}, ${getTypeGetPayloadName(x.table)}<T>>>> {
554
+ return ${isTransaction ? "runtime" : "this.runtime"}.resolve(
555
+ {
556
+ resource: "${x.table}",
557
+ action: "${mapKindToAction(x.kind)}",
558
+ args: param1,
559
+ fields: param2?.$fields as any,
560
+ artifacts,
561
+ context: param2?.context,
562
+ skipCache: param2?.skipCache,
563
+ eventTarget: ${isTransaction ? "eventTarget" : "this.eventTarget"},
564
+ ${isTransaction ? "dbCall," : ""}
565
+ passBeforeValueToAfterCallback: ${isTransaction
566
+ ? "passBeforeValueToAfterCallback"
567
+ : "this.passBeforeValueToAfterCallback"},
568
+ }
569
+ );
570
+ }`;
571
+ }
572
+ function getMethodSourceGetListPaginated(x, isTransaction) {
573
+ const param2 = `{ ${keyFields}?: ${x.typeFieldsName}, correlationId?: string, skipCache?: boolean, context?: TContext }`;
574
+ return `async ${x.methodName}<T extends ${param2}>(
575
+ param1: { $where?: ${x.typeWhereName}, $orderBy?: ${x.typeOrderByName}, $paginate: Paginate },
576
+ param2?: SelectSubset<T, ${param2}>
577
+ ): Promise<ListPaginated<CheckSelect<T, ${getTypeReturnName(x.table)}, ${getTypeGetPayloadName(x.table)}<T>>>> {
578
+ return ${isTransaction ? "runtime" : "this.runtime"}.resolve(
579
+ {
580
+ resource: "${x.table}",
581
+ action: "${mapKindToAction(x.kind)}",
582
+ args: param1,
583
+ fields: param2?.$fields as any,
584
+ artifacts,
585
+ context: param2?.context,
586
+ skipCache: param2?.skipCache,
587
+ eventTarget: ${isTransaction ? "eventTarget" : "this.eventTarget"},
588
+ ${isTransaction ? "dbCall," : ""}
589
+ passBeforeValueToAfterCallback: ${isTransaction
590
+ ? "passBeforeValueToAfterCallback"
591
+ : "this.passBeforeValueToAfterCallback"},
592
+ }
593
+ );
594
+ }`;
595
+ }
596
+ function getMethodSourcePostOne(x, specialCaseUuidColumn, isTransaction) {
597
+ const param2 = `{ ${keyFields}?: ${x.typeFieldsName}, correlationId?: string, context?: TContext }`;
598
+ return `async ${x.methodName}<T extends ${param2}>(
599
+ data: ${x.typeDataName},
600
+ param2?: SelectSubset<T, ${param2}>
601
+ ): Promise<CheckSelect<T, ${getTypeReturnName(x.table)}, ${getTypeGetPayloadName(x.table)}<T>>> {
602
+ return ${isTransaction ? "runtime" : "this.runtime"}.resolve({
603
+ resource: "${x.table}",
604
+ action: "${mapKindToAction(x.kind)}",
605
+ data,
606
+ artifacts,
607
+ fields: param2?.$fields as any,
608
+ context: {...param2?.context, specialCaseUuidColumn: ${JSON.stringify(specialCaseUuidColumn)}},
609
+ eventTarget: ${isTransaction ? "eventTarget" : "this.eventTarget"},
610
+ onHandler: ${isTransaction ? "onHandlerMap" : "this.onHandlerMap"}.get("${mapKindToAction(x.kind)}-${x.table}"),
611
+ asyncLocalStorage,
612
+ sdk: this,
613
+ ${isTransaction ? "dbCall," : ""}
614
+ passBeforeValueToAfterCallback: ${isTransaction
615
+ ? "passBeforeValueToAfterCallback"
616
+ : "this.passBeforeValueToAfterCallback"},
617
+ });
618
+ }`;
619
+ }
620
+ function getMethodSourceOnHandlerPostOne(x) {
621
+ return `on${(0, capitalizeFirstLetter_1.capitalizeFirstLetter)(x.methodName)}(handler:
622
+ (sdk: InstanceType<typeof SDK>, input: { data: ${x.typeDataName} },
623
+ output: Partial<${getTypeReturnName(x.table)}>,
624
+ context: TContext,
625
+ ) => Promise<void>
626
+ ): void {
627
+ this.onHandlerMap.set("${mapKindToAction(x.kind)}-${x.table}", handler);
628
+ }`;
629
+ }
630
+ function getMethodSourcePatchOne(x, findOnes, isTransaction) {
631
+ const param2 = `{ ${keyFields}?: ${x.typeFieldsName}, correlationId?: string, context?: TContext }`;
632
+ return `async ${x.methodName}<T extends ${param2}>(
633
+ param1: ${findOnes
634
+ .map((findOne) => `{ ${findOne.name}: ${findOne.type}${findOne.nullable ? " | null" : ""} }`)
635
+ .join(" | ")},
636
+ data: ${x.typeDataName},
637
+ param2?: SelectSubset<T, ${param2}>
638
+ ): Promise<CheckSelect<T, ${getTypeReturnName(x.table)}, ${getTypeGetPayloadName(x.table)}<T>>> {
639
+ return ${isTransaction ? "runtime" : "this.runtime"}.resolve({
640
+ resource: "${x.table}",
641
+ action: "${mapKindToAction(x.kind)}",
642
+ args: { $where: param1 },
643
+ data,
644
+ artifacts,
645
+ fields: param2?.$fields as any,
646
+ context: param2?.context,
647
+ eventTarget: ${isTransaction ? "eventTarget" : "this.eventTarget"},
648
+ onHandler: ${isTransaction ? "onHandlerMap" : "this.onHandlerMap"}.get("${mapKindToAction(x.kind)}-${x.table}"),
649
+ asyncLocalStorage,
650
+ sdk: this,
651
+ ${isTransaction ? "dbCall," : ""}
652
+ passBeforeValueToAfterCallback: ${isTransaction
653
+ ? "passBeforeValueToAfterCallback"
654
+ : "this.passBeforeValueToAfterCallback"},
655
+ });
656
+ }`;
657
+ }
658
+ function getMethodSourceOnHandlerPatchOne(x, findOnes) {
659
+ return `on${(0, capitalizeFirstLetter_1.capitalizeFirstLetter)(x.methodName)}(handler:
660
+ (sdk: InstanceType<typeof SDK>, input: { $where: ${findOnes
661
+ .map((findOne) => `{ ${findOne.name}: ${findOne.type}${findOne.nullable ? " | null" : ""} }`)
662
+ .join(" | ")}, data: ${x.typeDataName} },
663
+ output: Partial<${getTypeReturnName(x.table)}>,
664
+ before: ${getTypeReturnName(x.table)} | null,
665
+ context: TContext,
666
+ ) => Promise<void>
667
+ ): void {
668
+ this.onHandlerMap.set("${mapKindToAction(x.kind)}-${x.table}", handler);
669
+ }`;
670
+ }
671
+ function getMethodSourcePatchList(x, isTransaction) {
672
+ const param2 = `{ ${keyFields}?: ${x.typeFieldsName}, correlationId?: string, context?: TContext }`;
673
+ return `async ${x.methodName}<T extends ${param2}>(
674
+ param1: { $where?: ${x.typeWhereName}, $orderBy?: ${x.typeOrderByName} },
675
+ data: ${x.typeDataName},
676
+ param2?: SelectSubset<T, ${param2}>
677
+ ): Promise<Array<CheckSelect<T, ${getTypeReturnName(x.table)}, ${getTypeGetPayloadName(x.table)}<T>>>> {
678
+ return ${isTransaction ? "runtime" : "this.runtime"}.resolve({
679
+ resource: "${x.table}",
680
+ action: "${mapKindToAction(x.kind)}",
681
+ args: param1,
682
+ data,
683
+ artifacts,
684
+ fields: param2?.$fields as any,
685
+ context: param2?.context,
686
+ eventTarget: ${isTransaction ? "eventTarget" : "this.eventTarget"},
687
+ onHandler: ${isTransaction ? "onHandlerMap" : "this.onHandlerMap"}.get("${mapKindToAction(x.kind)}-${x.table}"),
688
+ asyncLocalStorage,
689
+ sdk: this,
690
+ ${isTransaction ? "dbCall," : ""}
691
+ passBeforeValueToAfterCallback: ${isTransaction
692
+ ? "passBeforeValueToAfterCallback"
693
+ : "this.passBeforeValueToAfterCallback"},
694
+ });
695
+ }`;
696
+ }
697
+ function getMethodSourceOnHandlerPatchList(x) {
698
+ return `on${(0, capitalizeFirstLetter_1.capitalizeFirstLetter)(x.methodName)}(handler:
699
+ (sdk: InstanceType<typeof SDK>, input: { $where?: ${x.typeWhereName}, $orderBy?: ${x.typeOrderByName}, data: ${x.typeDataName} },
700
+ output: Partial<${getTypeReturnName(x.table)}>[],
701
+ before: ${getTypeReturnName(x.table)}[] | null,
702
+ context: TContext,
703
+ ) => Promise<void>
704
+ ): void {
705
+ this.onHandlerMap.set("${mapKindToAction(x.kind)}-${x.table}", handler);
706
+ }`;
707
+ }
708
+ function getMethodSourceDeleteOne(x, findOnes, isTransaction) {
709
+ return `async ${x.methodName}(
710
+ param1: ${findOnes
711
+ .map((findOne) => `{ ${findOne.name}: ${findOne.type}${findOne.nullable ? " | null" : ""} }`)
712
+ .join(" | ")},
713
+ param2?: { correlationId?: string, context?: TContext }
714
+ ): Promise<void> {
715
+ await ${isTransaction ? "runtime" : "this.runtime"}.resolve({
716
+ resource: "${x.table}",
717
+ action: "${mapKindToAction(x.kind)}",
718
+ args: { $where: param1 },
719
+ artifacts,
720
+ context: param2?.context,
721
+ eventTarget: ${isTransaction ? "eventTarget" : "this.eventTarget"},
722
+ onHandler: ${isTransaction ? "onHandlerMap" : "this.onHandlerMap"}.get("${mapKindToAction(x.kind)}-${x.table}"),
723
+ asyncLocalStorage,
724
+ sdk: this,
725
+ ${isTransaction ? "dbCall," : ""}
726
+ passBeforeValueToAfterCallback: ${isTransaction
727
+ ? "passBeforeValueToAfterCallback"
728
+ : "this.passBeforeValueToAfterCallback"},
729
+ });
730
+ }`;
731
+ }
732
+ function getMethodSourceOnHandlerDeleteOne(x, findOnes) {
733
+ return `on${(0, capitalizeFirstLetter_1.capitalizeFirstLetter)(x.methodName)}(handler:
734
+ (sdk: InstanceType<typeof SDK>, input: { $where: ${findOnes
735
+ .map((findOne) => `{ ${findOne.name}: ${findOne.type}${findOne.nullable ? " | null" : ""} }`)
736
+ .join(" | ")}, },
737
+ output: void,
738
+ context: TContext,
739
+ ) => Promise<void>
740
+ ): void {
741
+ this.onHandlerMap.set("${mapKindToAction(x.kind)}-${x.table}", handler);
742
+ }`;
743
+ }
744
+ function getMethodSourceDeleteList(x, isTransaction) {
745
+ return `async ${x.methodName}(
746
+ param1: { $where?: ${x.typeWhereName} },
747
+ param2?: { correlationId?: string, context?: TContext }
748
+ ): Promise<void> {
749
+ await ${isTransaction ? "runtime" : "this.runtime"}.resolve({
750
+ resource: "${x.table}",
751
+ action: "${mapKindToAction(x.kind)}",
752
+ args: param1,
753
+ artifacts,
754
+ context: param2?.context,
755
+ eventTarget: ${isTransaction ? "eventTarget" : "this.eventTarget"},
756
+ onHandler: ${isTransaction ? "onHandlerMap" : "this.onHandlerMap"}.get("${mapKindToAction(x.kind)}-${x.table}"),
757
+ asyncLocalStorage,
758
+ sdk: this,
759
+ ${isTransaction ? "dbCall," : ""}
760
+ passBeforeValueToAfterCallback: ${isTransaction
761
+ ? "passBeforeValueToAfterCallback"
762
+ : "this.passBeforeValueToAfterCallback"},
763
+ });
764
+ }`;
765
+ }
766
+ function getMethodSourceOnHandlerDeleteList(x) {
767
+ return `on${(0, capitalizeFirstLetter_1.capitalizeFirstLetter)(x.methodName)}(handler:
768
+ (sdk: InstanceType<typeof SDK>, input: { $where?: ${x.typeWhereName} },
769
+ output: void,
770
+ before: ${getTypeReturnName(x.table)}[] | null,
771
+ context: TContext,
772
+ ) => Promise<void>
773
+ ): void {
774
+ this.onHandlerMap.set("${mapKindToAction(x.kind)}-${x.table}", handler);
775
+ }`;
776
+ }
777
+ function mapKindToAction(kind) {
778
+ if (kind === "getOne") {
779
+ return "findUnique";
780
+ }
781
+ if (kind === "getList") {
782
+ return "findMany";
783
+ }
784
+ if (kind === "getListPaginated") {
785
+ return "findManyPaginated";
786
+ }
787
+ if (kind === "postOne") {
788
+ return "create";
789
+ }
790
+ if (kind === "patchOne") {
791
+ return "update";
792
+ }
793
+ if (kind === "patchList") {
794
+ return "updateMany";
795
+ }
796
+ if (kind === "deleteOne") {
797
+ return "delete";
798
+ }
799
+ if (kind === "deleteList") {
800
+ return "deleteMany";
801
+ }
802
+ throw new Error(`Unhandled kind: ${kind}`);
803
+ }
804
+ function getTypeGetPayloadName(table) {
805
+ return "GetPayload" + changeCase.pascalCase(table);
806
+ }
807
+ function getTypeReturnName(table) {
808
+ return changeCase.pascalCase(table);
809
+ }
810
+ function getTypeReturnBaseName(table) {
811
+ return "ReturnBase" + changeCase.pascalCase(table);
812
+ }
813
+ function getTypeFieldsName(table) {
814
+ return "Fields" + changeCase.pascalCase(table);
815
+ }
816
+ function getTypeWhereName(table) {
817
+ return "Where" + changeCase.pascalCase(table);
818
+ }
819
+ function getTypeOrderByName(table) {
820
+ return "OrderBy" + changeCase.pascalCase(table);
821
+ }
822
+ function getTypeDataPostName(table) {
823
+ return "DataPost" + changeCase.pascalCase(table);
824
+ }
825
+ async function getGetOneData(table, includeMappedFields) {
826
+ const typeFieldsName = getTypeFieldsName(table);
827
+ const typeReturnBaseName = getTypeReturnBaseName(table);
828
+ return {
829
+ kind: "getOne",
830
+ table,
831
+ methodName: "get" + changeCase.pascalCase(table),
832
+ typeFields: await getTypeFields(table, typeFieldsName, includeMappedFields),
833
+ typeFieldsName,
834
+ typeReturnBase: await getTypeReturnBase(table, typeReturnBaseName, includeMappedFields),
835
+ typeReturnBaseName
836
+ };
837
+ }
838
+ async function getGetListData(table) {
839
+ const typeFieldsName = getTypeFieldsName(table);
840
+ const typeReturnBaseName = getTypeReturnBaseName(table);
841
+ const typeWhereName = getTypeWhereName(table);
842
+ const typeWhere = await getTypeWhere(table, typeWhereName);
843
+ const typeOrderByName = getTypeOrderByName(table);
844
+ const typeOrderBy = await getTypeOrderBy(table, typeOrderByName);
845
+ return {
846
+ kind: "getList",
847
+ table,
848
+ methodName: "get" + changeCase.pascalCase(table) + "List",
849
+ typeFieldsName,
850
+ typeReturnBaseName,
851
+ typeWhere,
852
+ typeWhereName,
853
+ typeOrderBy,
854
+ typeOrderByName
855
+ };
856
+ }
857
+ async function getGetListPaginatedData(table) {
858
+ const typeFieldsName = getTypeFieldsName(table);
859
+ const typeReturnBaseName = getTypeReturnBaseName(table);
860
+ const typeWhereName = getTypeWhereName(table);
861
+ const typeOrderByName = getTypeOrderByName(table);
862
+ return {
863
+ kind: "getListPaginated",
864
+ table,
865
+ methodName: "get" + changeCase.pascalCase(table) + "ListPaginated",
866
+ typeFieldsName,
867
+ typeReturnBaseName,
868
+ typeWhereName,
869
+ typeOrderByName
870
+ };
871
+ }
872
+ async function getPostOneData(table, specialCaseUuidColumn, includeMappedFields) {
873
+ const typeFieldsName = getTypeFieldsName(table);
874
+ const typeReturnBaseName = getTypeReturnBaseName(table);
875
+ const typeDataName = getTypeDataPostName(table);
876
+ return {
877
+ kind: "postOne",
878
+ table,
879
+ methodName: "post" + changeCase.pascalCase(table),
880
+ typeFieldsName,
881
+ typeReturnBaseName,
882
+ typeData: await getTypeDataPost(table, typeDataName, specialCaseUuidColumn, includeMappedFields),
883
+ typeDataName
884
+ };
885
+ }
886
+ async function getPatchOneData(table, specialCaseUuidColumn, includeMappedFields) {
887
+ const typeFieldsName = getTypeFieldsName(table);
888
+ const typeReturnBaseName = getTypeReturnBaseName(table);
889
+ const typeDataName = "DataPatch" + changeCase.pascalCase(table);
890
+ return {
891
+ kind: "patchOne",
892
+ table,
893
+ methodName: "patch" + changeCase.pascalCase(table),
894
+ typeFieldsName,
895
+ typeReturnBaseName,
896
+ typeData: await getTypeDataPatch(table, typeDataName, specialCaseUuidColumn, includeMappedFields),
897
+ typeDataName
898
+ };
899
+ }
900
+ async function getPatchListData(table) {
901
+ const typeFieldsName = getTypeFieldsName(table);
902
+ const typeReturnBaseName = getTypeReturnBaseName(table);
903
+ const typeWhereName = getTypeWhereName(table);
904
+ const typeDataName = "DataPatch" + changeCase.pascalCase(table);
905
+ const typeOrderByName = getTypeOrderByName(table);
906
+ return {
907
+ kind: "patchList",
908
+ table,
909
+ methodName: "patch" + changeCase.pascalCase(table) + "List",
910
+ typeFieldsName,
911
+ typeReturnBaseName,
912
+ typeWhereName,
913
+ typeDataName,
914
+ typeOrderByName
915
+ };
916
+ }
917
+ function getDeleteOneData(table) {
918
+ return {
919
+ kind: "deleteOne",
920
+ table,
921
+ methodName: "delete" + changeCase.pascalCase(table)
922
+ };
923
+ }
924
+ function getDeleteListData(table) {
925
+ const typeWhereName = getTypeWhereName(table);
926
+ return {
927
+ kind: "deleteList",
928
+ table,
929
+ methodName: "delete" + changeCase.pascalCase(table) + "List",
930
+ typeWhereName
931
+ };
932
+ }
933
+ async function getTypeWhere(table, name) {
934
+ const jsonSchemaWhere = await getJSONSchemaWhere(table);
935
+ return (0, json_schema_to_typescript_1.compile)(jsonSchemaWhere, name, json2TsOpts);
936
+ }
937
+ async function getTypeDataPost(table, name, specialCaseUuidColumn, includeMappedFields) {
938
+ const primaryColumn = await getPrimaryColumn(table);
939
+ const tableMeta = (await getTableMeta(table)).filter((x) => x.Field !== primaryColumn.name);
940
+ const nullable = tableMeta.reduce((acc, m) => ({
941
+ ...acc,
942
+ [m.Field]: m.Null === "YES" ? true : false
943
+ }), {});
944
+ const hasDefault = tableMeta.reduce((acc, m) => ({
945
+ ...acc,
946
+ [m.Field]: m.Default == null ? false : true
947
+ }), {});
948
+ let properties = getJSONSchemaObjProperties(tableMeta);
949
+ let notRequiredList = [];
950
+ const oneToManyRelations = (await getRelationInfo(table)).filter((x) => x.type === "one-to-many__many-to-one" && x.kind === "one-to-many");
951
+ const mappedFields = includeMappedFields ? await getMappedFields(table) : [];
952
+ let mappedFieldsMap = new Map();
953
+ if (includeMappedFields) {
954
+ properties = {
955
+ ...properties,
956
+ ...mappedFields.reduce((acc, v) => {
957
+ acc[v.as] = { type: getJSONTypes(v.type, v.nullable) };
958
+ return acc;
959
+ }, {})
960
+ };
961
+ notRequiredList = mappedFields.flatMap((x) => [x.as, x.foreignKey]);
962
+ for (let r of oneToManyRelations) {
963
+ mappedFieldsMap.set(r.table, await getMappedFields(r.table));
964
+ }
965
+ }
966
+ const uuidColumn = await getUuidColumn(table);
967
+ const jsonSchema = {
968
+ type: "object",
969
+ properties: {
970
+ ...properties,
971
+ ...oneToManyRelations.reduce((acc, v) => {
972
+ let tsType = getTypeDataPostName(v.table);
973
+ const mappedFields = mappedFieldsMap.get(v.table);
974
+ if (includeMappedFields && mappedFields != null && mappedFields.length > 0) {
975
+ tsType = `Omit<${tsType}, ${mappedFields
976
+ .map((x) => JSON.stringify(x.as))
977
+ .join(" | ")}>`;
978
+ }
979
+ tsType = `{$create: ${tsType}[]}`;
980
+ acc[v.name] = { tsType };
981
+ return acc;
982
+ }, {})
983
+ },
984
+ additionalProperties: false,
985
+ required: Object.keys(properties)
986
+ .filter(
987
+ // `uuid` should not be required
988
+ (x) => !specialCaseUuidColumn || uuidColumn == null ? true : x !== uuidColumn.name)
989
+ .filter(
990
+ // Required if column is non-nullable and has no default.
991
+ (x) => !nullable[x] && !hasDefault[x])
992
+ // Instead of doing a union with all possible permutations of UUID and IDs,
993
+ // for simplicity, just make both not required for now.
994
+ .filter((x) => !notRequiredList.includes(x))
995
+ };
996
+ let type = await (0, json_schema_to_typescript_1.compile)(jsonSchema, name, json2TsOpts);
997
+ const imports = _.uniq(oneToManyRelations
998
+ .filter((r) => r.table !== table)
999
+ .map((x) => getTypeDataPostName(x.table)))
1000
+ .map((x) => `import type { ${x} } from "./${x}";`)
1001
+ .join("\n");
1002
+ if (imports) {
1003
+ type = imports + "\n\n" + type;
1004
+ }
1005
+ return type;
1006
+ }
1007
+ async function getTypeDataPatch(table, name, specialCaseUuidColumn, includeMappedFields) {
1008
+ const primaryColumn = await getPrimaryColumn(table);
1009
+ let tableMeta = (await getTableMeta(table)).filter((x) => x.Field !== primaryColumn.name);
1010
+ if (specialCaseUuidColumn) {
1011
+ const uuidColumn = await getUuidColumn(table);
1012
+ if (uuidColumn != null) {
1013
+ tableMeta = tableMeta.filter((x) => x.Field !== uuidColumn.name);
1014
+ }
1015
+ }
1016
+ let properties = getJSONSchemaObjProperties(tableMeta);
1017
+ let mustImportTUpdateOperationsString = false;
1018
+ let mustImportTUpdateOperationsNumber = false;
1019
+ for (let key in properties) {
1020
+ const type = unwrapJSONType(properties[key].type);
1021
+ if (type === "string") {
1022
+ properties[key] = {
1023
+ oneOf: [properties[key], { tsType: "TUpdateOperationsString" }]
1024
+ };
1025
+ mustImportTUpdateOperationsString = true;
1026
+ }
1027
+ else if (type === "number" || type === "integer") {
1028
+ properties[key] = {
1029
+ oneOf: [properties[key], { tsType: "TUpdateOperationsNumber" }]
1030
+ };
1031
+ mustImportTUpdateOperationsNumber = true;
1032
+ }
1033
+ }
1034
+ if (includeMappedFields) {
1035
+ const mappedFields = await getMappedFields(table);
1036
+ properties = {
1037
+ ...properties,
1038
+ ...mappedFields.reduce((acc, v) => {
1039
+ acc[v.as] = { type: getJSONTypes(v.type, v.nullable) };
1040
+ return acc;
1041
+ }, {})
1042
+ };
1043
+ }
1044
+ const jsonSchema = {
1045
+ type: "object",
1046
+ properties,
1047
+ additionalProperties: false,
1048
+ required: []
1049
+ };
1050
+ let type = await (0, json_schema_to_typescript_1.compile)(jsonSchema, name, json2TsOpts);
1051
+ if (mustImportTUpdateOperationsString || mustImportTUpdateOperationsNumber) {
1052
+ const imports = [];
1053
+ if (mustImportTUpdateOperationsString) {
1054
+ imports.push("TUpdateOperationsString");
1055
+ }
1056
+ if (mustImportTUpdateOperationsNumber) {
1057
+ imports.push("TUpdateOperationsNumber");
1058
+ }
1059
+ const importsStr = imports
1060
+ .map((x) => `import type { ${x} } from "./_shared";`)
1061
+ .join("\n");
1062
+ type = importsStr + "\n\n" + type;
1063
+ }
1064
+ return type;
1065
+ }
1066
+ function unwrapJSONType(type) {
1067
+ if (Array.isArray(type)) {
1068
+ // Gets first non-null type
1069
+ return type.filter((x) => x !== "null")[0];
1070
+ }
1071
+ return type;
1072
+ }
1073
+ async function getMappedFields(table) {
1074
+ const relationsManyToOne = await getRelationsManyToOne(table).then((xs) => xs.filter((x) => x.foreignKey.endsWith("Id")));
1075
+ let out = [];
1076
+ for (let x of relationsManyToOne) {
1077
+ const tableMeta = await getTableMeta(x.referencedTable);
1078
+ const uuidColumn = tableMeta.find((x) => x.Field === "uuid");
1079
+ const idColumn = tableMeta.find((x) => x.Field === "id");
1080
+ if (uuidColumn == null) {
1081
+ throw new Error("No `uuid` column found for table " + x.referencedTable);
1082
+ }
1083
+ if (idColumn == null) {
1084
+ throw new Error("No `id` column found for table " + x.referencedTable);
1085
+ }
1086
+ out.push({
1087
+ ...x,
1088
+ name: "uuid",
1089
+ // Replace `Id` with `Uuid`
1090
+ as: x.foreignKey.slice(0, -2) + "Uuid",
1091
+ type: getBaseJSONType(uuidColumn.Type, getCtx().dialect)
1092
+ });
1093
+ }
1094
+ return out;
1095
+ }
1096
+ async function getJSONSchemaWhere(table) {
1097
+ const whereSchemaName = `_Where${changeCase.pascalCase(table)}`;
1098
+ const defWhere = {
1099
+ oneOf: [
1100
+ {
1101
+ type: "object",
1102
+ properties: Object.entries(getJSONSchemaObjProperties(await getTableMeta(table))).reduce((acc, [k, v]) => ({
1103
+ ...acc,
1104
+ [k]: {
1105
+ oneOf: [
1106
+ v,
1107
+ {
1108
+ type: "object",
1109
+ properties: { $eq: v },
1110
+ additionalProperties: false
1111
+ },
1112
+ {
1113
+ type: "object",
1114
+ properties: { $neq: v },
1115
+ additionalProperties: false
1116
+ },
1117
+ {
1118
+ type: "object",
1119
+ properties: { $gt: v },
1120
+ additionalProperties: false
1121
+ },
1122
+ {
1123
+ type: "object",
1124
+ properties: { $gte: v },
1125
+ additionalProperties: false
1126
+ },
1127
+ {
1128
+ type: "object",
1129
+ properties: { $lt: v },
1130
+ additionalProperties: false
1131
+ },
1132
+ {
1133
+ type: "object",
1134
+ properties: { $lte: v },
1135
+ additionalProperties: false
1136
+ },
1137
+ {
1138
+ type: "object",
1139
+ properties: { $like: { type: "string", minLength: 1 } },
1140
+ additionalProperties: false
1141
+ },
1142
+ {
1143
+ type: "object",
1144
+ properties: { $nlike: { type: "string", minLength: 1 } },
1145
+ additionalProperties: false
1146
+ },
1147
+ {
1148
+ type: "object",
1149
+ properties: { $in: { type: "array", items: v } },
1150
+ additionalProperties: false
1151
+ },
1152
+ {
1153
+ type: "object",
1154
+ properties: { $nin: { type: "array", items: v } },
1155
+ additionalProperties: false
1156
+ },
1157
+ {
1158
+ type: "object",
1159
+ properties: {
1160
+ $btwn: {
1161
+ type: "array",
1162
+ items: v,
1163
+ minItems: 2,
1164
+ maxItems: 2,
1165
+ uniqueItems: true
1166
+ }
1167
+ },
1168
+ additionalProperties: false
1169
+ },
1170
+ {
1171
+ type: "object",
1172
+ properties: {
1173
+ $nbtwn: {
1174
+ type: "array",
1175
+ items: v,
1176
+ minItems: 2,
1177
+ maxItems: 2,
1178
+ uniqueItems: true
1179
+ }
1180
+ },
1181
+ additionalProperties: false
1182
+ }
1183
+ ]
1184
+ }
1185
+ }), {}),
1186
+ additionalProperties: false
1187
+ },
1188
+ {
1189
+ type: "object",
1190
+ properties: {
1191
+ $and: {
1192
+ type: "array",
1193
+ items: {
1194
+ $ref: `#/definitions/${whereSchemaName}`
1195
+ },
1196
+ // While it makes sense conceptually for $and to have
1197
+ // at least 2 items, in practice, $and could be
1198
+ // generated dynamically and could end up having
1199
+ // less than 2 items, so don't enforce minItems.
1200
+ // minItems: 2,
1201
+ additionalProperties: false
1202
+ }
1203
+ },
1204
+ additionalProperties: false
1205
+ },
1206
+ {
1207
+ type: "object",
1208
+ properties: {
1209
+ $or: {
1210
+ type: "array",
1211
+ items: {
1212
+ $ref: `#/definitions/${whereSchemaName}`
1213
+ },
1214
+ // While it makes sense conceptually for $and to have
1215
+ // at least 2 items, in practice, $and could be
1216
+ // generated dynamically and could end up having
1217
+ // less than 2 items, so don't enforce minItems.
1218
+ // minItems: 2,
1219
+ additionalProperties: false
1220
+ }
1221
+ },
1222
+ additionalProperties: false
1223
+ }
1224
+ ]
1225
+ };
1226
+ return {
1227
+ definitions: { [whereSchemaName]: defWhere },
1228
+ anyOf: [
1229
+ { $ref: `#/definitions/${whereSchemaName}` },
1230
+ ...["$and", "$or"].map((x) => ({
1231
+ type: "object",
1232
+ properties: {
1233
+ [x]: {
1234
+ type: "array",
1235
+ items: { $ref: `#/definitions/${whereSchemaName}` },
1236
+ // While it makes sense conceptually for $and/$or to have
1237
+ // at least 2 items, in practice, they could be
1238
+ // generated dynamically and could end up having
1239
+ // less than 2 items, so don't enforce minItems.
1240
+ // minItems: 2,
1241
+ additionalProperties: false
1242
+ }
1243
+ },
1244
+ additionalProperties: false
1245
+ }))
1246
+ ]
1247
+ };
1248
+ }
1249
+ async function getTypeOrderBy(table, name) {
1250
+ return (0, json_schema_to_typescript_1.compile)((await getJSONSchemaOrderBy(table, name)), name, json2TsOpts);
1251
+ }
1252
+ async function getJSONSchemaOrderBy(table, name) {
1253
+ const fieldNames = await getTableMeta(table).then((xs) => xs.map((x) => x.Field));
1254
+ const def = {
1255
+ oneOf: fieldNames.map((k) => ({
1256
+ type: "object",
1257
+ properties: { [k]: { enum: ["asc", "desc"] } },
1258
+ required: [k],
1259
+ additionalProperties: false
1260
+ }))
1261
+ };
1262
+ const defName = `_${name}`;
1263
+ const _schema = { $ref: `#/definitions/${defName}` };
1264
+ return {
1265
+ definitions: { [defName]: def },
1266
+ oneOf: [_schema, { type: "array", items: _schema }]
1267
+ };
1268
+ }
1269
+ function getTypeTypesIndex(data) {
1270
+ function getExport(name) {
1271
+ return `export type { ${name} } from "./${name}";`;
1272
+ }
1273
+ let set = new Set();
1274
+ for (let d of data) {
1275
+ for (let k of [
1276
+ "typeFieldsName",
1277
+ "typeReturnBaseName",
1278
+ "typeWhereName",
1279
+ "typeOrderByName",
1280
+ "typeDataName"
1281
+ ]) {
1282
+ const str = d[k];
1283
+ if (str) {
1284
+ set.add(str);
1285
+ }
1286
+ }
1287
+ }
1288
+ let src = `export type { ${[
1289
+ "Paginate",
1290
+ "ListPaginated",
1291
+ "TUpdateOperationsString",
1292
+ "TUpdateOperationsNumber"
1293
+ ].join(",")} } from "./_shared";\n\n`;
1294
+ let arr = Array.from(set).sort();
1295
+ for (let x of arr) {
1296
+ src += getExport(x);
1297
+ }
1298
+ return prettier.format(src, { parser: "typescript" });
1299
+ }
1300
+ function getTypeShared() {
1301
+ const src = `export type Paginate = {
1302
+ first: number;
1303
+ after?: string;
1304
+ before?: string;
1305
+ } | {
1306
+ last: number;
1307
+ after?: string;
1308
+ before?: string;
1309
+ } | {
1310
+ limit: number;
1311
+ offset?: number;
1312
+ }
1313
+
1314
+ export type ListPaginated<T> = {
1315
+ paginationInfo: {
1316
+ hasPreviousPage?: boolean,
1317
+ hasNextPage?: boolean,
1318
+ startCursor?: string,
1319
+ endCursor?: string,
1320
+ totalCount: number,
1321
+ },
1322
+ results: Array<T>,
1323
+ }
1324
+
1325
+ export type TUpdateOperationsString = {$prepend: string} | {$append: string}
1326
+
1327
+ export type TUpdateOperationsNumber = {$increment: number} | {$decrement: number}
1328
+ `;
1329
+ return prettier.format(src, { parser: "typescript" });
1330
+ }
1331
+ async function getTypeFields(table, name, includeMappedFields) {
1332
+ const scalarKeys = Object.keys(getJSONSchemaObjProperties(await getTableMeta(table)));
1333
+ const relations = await getRelationInfo(table);
1334
+ const mappedFields = includeMappedFields ? await getMappedFields(table) : [];
1335
+ const keyWhere = "$where";
1336
+ const keyOrderBy = "$orderBy";
1337
+ let properties = {};
1338
+ for (let x of scalarKeys) {
1339
+ properties[x] = { type: "boolean" };
1340
+ }
1341
+ for (let x of mappedFields) {
1342
+ properties[x.as] = { type: "boolean" };
1343
+ }
1344
+ for (let x of relations) {
1345
+ const argsProperties = x.type === "many-to-many"
1346
+ ? {
1347
+ [keyWhere]: {
1348
+ type: "object",
1349
+ properties: {
1350
+ [x.table]: { tsType: getTypeWhereName(x.table) },
1351
+ [x.junctionTable]: {
1352
+ tsType: getTypeWhereName(x.junctionTable)
1353
+ }
1354
+ },
1355
+ additionalProperties: false
1356
+ }
1357
+ }
1358
+ : { [keyWhere]: { tsType: getTypeWhereName(x.table) } };
1359
+ // $orderBy only makes sense for a list
1360
+ if (x.grabMany) {
1361
+ argsProperties[keyOrderBy] = {
1362
+ tsType: getTypeOrderByName(x.table)
1363
+ };
1364
+ }
1365
+ argsProperties[keyFields] = { tsType: getTypeFieldsName(x.table) };
1366
+ const jsonSchemaArgs = {
1367
+ type: "object",
1368
+ properties: argsProperties,
1369
+ additionalProperties: false
1370
+ };
1371
+ properties[x.name] = { enum: [{ type: "boolean" }, jsonSchemaArgs] };
1372
+ }
1373
+ const jsonSchemaFields = {
1374
+ type: "object",
1375
+ additionalProperties: false,
1376
+ properties
1377
+ };
1378
+ let type = await (0, json_schema_to_typescript_1.compile)(jsonSchemaFields, name, json2TsOpts);
1379
+ const fieldImports = _.uniq(relations.filter((x) => x.table !== table).map((x) => getTypeFieldsName(x.table)));
1380
+ const whereImports = _.uniq(relations.flatMap((x) => x.type === "many-to-many"
1381
+ ? [getTypeWhereName(x.table), getTypeWhereName(x.junctionTable)]
1382
+ : getTypeWhereName(x.table)));
1383
+ const orderByImports = _.uniq(relations
1384
+ // $orderBy only makes sense for a list
1385
+ .filter((x) => x.grabMany)
1386
+ .map((x) => getTypeOrderByName(x.table)));
1387
+ const imports = _.uniq(fieldImports.concat(whereImports).concat(orderByImports))
1388
+ .map((x) => `import type { ${x} } from "./${x}";`)
1389
+ .join("\n");
1390
+ type = imports + "\n\n" + type;
1391
+ return type;
1392
+ }
1393
+ async function getTypeReturnBase(table, name, includeMappedFields) {
1394
+ const tableMeta = await getTableMeta(table);
1395
+ const scalarProperties = getJSONSchemaObjProperties(tableMeta);
1396
+ const relations = await getRelationInfo(table);
1397
+ const mappedFields = includeMappedFields ? await getMappedFields(table) : [];
1398
+ const jsonSchemaReturn = {
1399
+ type: "object",
1400
+ properties: {
1401
+ ...scalarProperties,
1402
+ ...mappedFields.reduce((acc, x) => {
1403
+ acc[x.as] = { type: getJSONTypes(x.type, x.nullable) };
1404
+ return acc;
1405
+ }, {}),
1406
+ ...relations.reduce((acc, x) => {
1407
+ const key = x.name;
1408
+ const typeName = getTypeReturnBaseName(x.table);
1409
+ const tsType = x.type === "one-to-many__many-to-one"
1410
+ ? getMaybeNullableTsType(typeName, x.nullable)
1411
+ : typeName;
1412
+ if (x.grabMany) {
1413
+ acc[key] = { type: "array", items: { tsType } };
1414
+ }
1415
+ else {
1416
+ acc[key] = { tsType };
1417
+ }
1418
+ return acc;
1419
+ }, {})
1420
+ },
1421
+ // Because of aliases
1422
+ additionalProperties: true
1423
+ };
1424
+ let type = await (0, json_schema_to_typescript_1.compile)(jsonSchemaReturn, name, json2TsOpts);
1425
+ const imports = _.uniq(relations.map((x) => getTypeReturnBaseName(x.table)))
1426
+ .filter((x) => x !== name)
1427
+ .map((x) => `import type { ${x} } from "./${x}";`)
1428
+ .join("\n");
1429
+ type = imports + "\n\n" + type;
1430
+ return type;
1431
+ }
1432
+ function getMaybeNullableTsType(type, nullable) {
1433
+ if (nullable) {
1434
+ return `${type} | null`;
1435
+ }
1436
+ return type;
1437
+ }
1438
+ function getArtifactsSource(artifacts) {
1439
+ const src = `
1440
+ import type {IArtifacts} from "./IRuntime";
1441
+
1442
+ export const artifacts: IArtifacts = ${JSON.stringify(artifacts)};
1443
+ `;
1444
+ return prettier.format(src, { parser: "typescript" });
1445
+ }
1446
+ async function getArtifacts(tables, includeMappedFields, specialCaseUuidColumn) {
1447
+ const tableMetaList = await Promise.all(tables.map(async (table) => {
1448
+ const [tableMeta, primaryKey, dumpSchema] = await Promise.all([
1449
+ getTableMeta(table),
1450
+ getPrimaryColumn(table).then((x) => x.name),
1451
+ getShowCreateTable(table)
1452
+ ]);
1453
+ const scalarFields = tableMeta.map((x) => x.Field);
1454
+ const relationInfo = await getRelationInfo(table);
1455
+ const relationFields = relationInfo.reduce((acc, x) => {
1456
+ if (x.type === "one-to-many__many-to-one") {
1457
+ acc[x.name] = {
1458
+ name: x.name,
1459
+ type: x.type,
1460
+ kind: x.kind,
1461
+ table: x.table,
1462
+ grabMany: x.grabMany,
1463
+ nullable: x.nullable,
1464
+ relation: x.relation
1465
+ };
1466
+ }
1467
+ else {
1468
+ acc[x.name] = {
1469
+ name: x.name,
1470
+ type: x.type,
1471
+ table: x.table,
1472
+ junctionTable: x.junctionTable,
1473
+ grabMany: x.grabMany,
1474
+ relations: x.relations
1475
+ };
1476
+ }
1477
+ return acc;
1478
+ }, {});
1479
+ const _mappedFields = includeMappedFields ? await getMappedFields(table) : [];
1480
+ const mappedFields = _mappedFields.length === 0
1481
+ ? null
1482
+ : _mappedFields.reduce((acc, v) => {
1483
+ acc[v.as] = v;
1484
+ return acc;
1485
+ }, {});
1486
+ const dateTimeFields = Object.entries(getJSONSchemaObjProperties(tableMeta)).reduce((acc, x) => {
1487
+ const k = x[0];
1488
+ const v = x[1];
1489
+ if (v.format != null && ["date-time"].includes(v.format)) {
1490
+ acc[k] = true;
1491
+ }
1492
+ return acc;
1493
+ }, {});
1494
+ const uniqueFields = await getUniqueColumns(table, specialCaseUuidColumn);
1495
+ let fields = tableMeta.map((t) => {
1496
+ const nullable = t.Null === "YES";
1497
+ const isEnum = t.Type.startsWith("enum");
1498
+ if (isEnum) {
1499
+ const values = getPropertyEnum(t.Type);
1500
+ if (values && nullable) {
1501
+ values.push(null);
1502
+ }
1503
+ return {
1504
+ kind: "enum",
1505
+ values,
1506
+ name: t.Field,
1507
+ nullable
1508
+ };
1509
+ }
1510
+ return {
1511
+ kind: "scalar",
1512
+ type: getBaseJSONType(t.Type, getCtx().dialect),
1513
+ name: t.Field,
1514
+ nullable,
1515
+ hasDefaultValue: !!t.Default
1516
+ };
1517
+ });
1518
+ for (let x of _mappedFields) {
1519
+ fields.push({
1520
+ kind: "scalar",
1521
+ type: x.type,
1522
+ name: x.as,
1523
+ nullable: x.nullable,
1524
+ // TODO
1525
+ hasDefaultValue: false,
1526
+ mapped: true
1527
+ });
1528
+ }
1529
+ for (let x of relationInfo) {
1530
+ fields.push({
1531
+ kind: "object",
1532
+ type: x.table,
1533
+ name: x.name,
1534
+ isList: x.grabMany,
1535
+ nullable: x.type === "one-to-many__many-to-one" ? x.nullable : false
1536
+ });
1537
+ }
1538
+ return {
1539
+ table,
1540
+ primaryKey,
1541
+ scalarFields,
1542
+ relationFields,
1543
+ mappedFields,
1544
+ uniqueFields,
1545
+ dateTimeFields,
1546
+ dateTimeFieldsCount: Object.keys(dateTimeFields).length,
1547
+ fields,
1548
+ dump: dumpSchema == null ? null : { schema: dumpSchema }
1549
+ };
1550
+ }));
1551
+ const artifacts = tableMetaList.reduce((acc, x) => {
1552
+ acc[x.table] = x;
1553
+ return acc;
1554
+ }, {});
1555
+ return artifacts;
1556
+ }
1557
+ const getRelationInfo = (0, memoize_1.default)(async function getRelationInfo(table) {
1558
+ const relationsManyToOne = await getRelationsManyToOne(table);
1559
+ const relationsOneToMany = await getRelationsOneToMany(table);
1560
+ let out = [];
1561
+ out = out.concat(relationsManyToOne.reduce((acc, x) => {
1562
+ if (!x.foreignKey.endsWith("Id")) {
1563
+ return acc;
1564
+ }
1565
+ const name = getRelationManyToOneFieldName(x);
1566
+ acc.push({
1567
+ type: "one-to-many__many-to-one",
1568
+ kind: "many-to-one",
1569
+ grabMany: false,
1570
+ table: x.referencedTable,
1571
+ name,
1572
+ relation: x,
1573
+ nullable: x.nullable
1574
+ });
1575
+ return acc;
1576
+ }, []));
1577
+ const relationsOneToManyDuplicates = (0, getDuplicates_1.getDuplicates)(relationsOneToMany.map((x) => x.referencedTable));
1578
+ out = out.concat(relationsOneToMany.reduce((acc, x) => {
1579
+ if (!x.referencedKey.endsWith("Id")) {
1580
+ return acc;
1581
+ }
1582
+ let name = changeCase.camelCase(x.referencedTable) + "List";
1583
+ if (relationsOneToManyDuplicates.includes(x.referencedTable)) {
1584
+ // Examples:
1585
+ // `User` would end up with:
1586
+ // * `Invite` + `fromUserId` = `inviteFromUserList`
1587
+ // * `Invite` + `toUserId` = `inviteToUserList`
1588
+ name =
1589
+ changeCase.camelCase(x.referencedTable) +
1590
+ changeCase.pascalCase(x.referencedKey.replace(new RegExp(x.foreignKey + "$", "i"), "")) +
1591
+ "List";
1592
+ }
1593
+ acc.push({
1594
+ type: "one-to-many__many-to-one",
1595
+ kind: "one-to-many",
1596
+ grabMany: true,
1597
+ table: x.referencedTable,
1598
+ name,
1599
+ relation: x,
1600
+ nullable: x.nullable
1601
+ });
1602
+ return acc;
1603
+ }, []));
1604
+ const relationsManyToMany = (await getJunctionTables()).reduce((acc, x) => {
1605
+ const dataForParentTable = x.relations.find((r) => r.referencedTable === table);
1606
+ if (dataForParentTable == null) {
1607
+ return acc;
1608
+ }
1609
+ const dataForChildTable = x.relations.find((r) => r.referencedTable !== dataForParentTable.referencedTable);
1610
+ if (dataForChildTable == null) {
1611
+ throw new Error("Null dataForChildTable for table: " + table);
1612
+ }
1613
+ acc.push({
1614
+ type: "many-to-many",
1615
+ table: dataForChildTable.referencedTable,
1616
+ junctionTable: x.table,
1617
+ name: changeCase.camelCase(dataForChildTable.referencedTable) + "List",
1618
+ // Ensure parent comes before child
1619
+ relations: [dataForParentTable, dataForChildTable],
1620
+ grabMany: true
1621
+ });
1622
+ return acc;
1623
+ }, []);
1624
+ out = out.concat(relationsManyToMany);
1625
+ out = _.sortBy((x) => x.table, out);
1626
+ return out;
1627
+ }, (table) => getCtx().runId + ":" + table);
1628
+ function getRelationManyToOneFieldName(x) {
1629
+ return changeCase.camelCase(x.foreignKey.replace(new RegExp(x.referencedKey + "$", "i"), ""));
1630
+ }
1631
+ // TODO: not sure if this logic is correct
1632
+ async function getJunctionTables() {
1633
+ const tables = await getTableNames();
1634
+ return (await Promise.all(tables.map(async (table) => {
1635
+ const relations = await getRelationsManyToOne(table);
1636
+ if (relations.length === 2 &&
1637
+ relations[0].referencedTable !== relations[1].referencedTable &&
1638
+ // e.g. junction of Foo, Bar must be FooBar or BarFoo
1639
+ [
1640
+ relations[0].referencedTable + relations[1].referencedTable,
1641
+ relations[1].referencedTable + relations[0].referencedTable
1642
+ ].includes(table)) {
1643
+ return { table, relations };
1644
+ }
1645
+ return null;
1646
+ }))).filter(isNotNullOrUndefined_1.isNotNullOrUndefined);
1647
+ }
1648
+ // `from` relations
1649
+ // https://stackoverflow.com/a/54732547
1650
+ const getRelationsManyToOne = (0, memoize_1.default)(async function getRelationsManyToOne(table) {
1651
+ const { dialect, query } = getCtx();
1652
+ const tableMeta = await getTableMeta(table);
1653
+ let rs;
1654
+ if (dialect === "mysql") {
1655
+ const sql = `
1656
+ SELECT
1657
+ TABLE_SCHEMA as db,
1658
+ TABLE_NAME as t1,
1659
+ COLUMN_NAME as t1Field,
1660
+ REFERENCED_TABLE_SCHEMA as db2,
1661
+ REFERENCED_TABLE_NAME as t2,
1662
+ REFERENCED_COLUMN_NAME as t2Field
1663
+ FROM
1664
+ INFORMATION_SCHEMA.KEY_COLUMN_USAGE
1665
+ WHERE
1666
+ TABLE_SCHEMA = SCHEMA()
1667
+ AND REFERENCED_TABLE_NAME IS NOT NULL
1668
+ AND (TABLE_NAME = ?);
1669
+ `;
1670
+ rs = await query(sql, [table]);
1671
+ }
1672
+ else if (dialect === "postgresql") {
1673
+ rs = await query(`SELECT kcu.column_name AS "t1Field", ccu.table_name AS t2, ccu.column_name AS "t2Field"
1674
+ FROM information_schema.key_column_usage kcu
1675
+ JOIN information_schema.referential_constraints rc ON kcu.constraint_name = rc.constraint_name AND kcu.table_schema = rc.constraint_schema
1676
+ JOIN information_schema.constraint_column_usage ccu ON rc.unique_constraint_name = ccu.constraint_name AND rc.unique_constraint_schema = ccu.table_schema
1677
+ WHERE kcu.table_schema = 'public' AND kcu.table_name = $1`, [table]);
1678
+ }
1679
+ else {
1680
+ throw new Error("Unsupported dialect: " + dialect);
1681
+ }
1682
+ const xs = await Promise.all(_.uniqWith(_.isEqual, rs.map(async (v) => {
1683
+ return {
1684
+ table: table,
1685
+ foreignKey: v.t1Field,
1686
+ referencedTable: v.t2,
1687
+ referencedKey: v.t2Field,
1688
+ nullable: tableMeta.find((m) => m.Field === v.t1Field)?.Null === "YES"
1689
+ };
1690
+ })));
1691
+ return _.sortBy((x) => x.referencedTable, xs);
1692
+ }, (table) => getCtx().runId + ":" + table);
1693
+ // `to` relations
1694
+ const getRelationsOneToMany = (0, memoize_1.default)(async function getRelationsOneToMany(table) {
1695
+ const { dialect, query } = getCtx();
1696
+ let rs;
1697
+ if (dialect === "mysql") {
1698
+ const sql = `
1699
+ SELECT
1700
+ TABLE_SCHEMA as db,
1701
+ TABLE_NAME as t1,
1702
+ COLUMN_NAME as t1Field,
1703
+ REFERENCED_TABLE_SCHEMA as db2,
1704
+ REFERENCED_TABLE_NAME as t2,
1705
+ REFERENCED_COLUMN_NAME as t2Field
1706
+ FROM
1707
+ INFORMATION_SCHEMA.KEY_COLUMN_USAGE
1708
+ WHERE
1709
+ TABLE_SCHEMA = SCHEMA()
1710
+ AND REFERENCED_TABLE_NAME IS NOT NULL
1711
+ AND (REFERENCED_TABLE_NAME = ?);
1712
+ `;
1713
+ rs = await query(sql, [table]);
1714
+ }
1715
+ else if (dialect === "postgresql") {
1716
+ rs = await query(`SELECT kcu.table_name AS t1, kcu.column_name AS "t1Field", ccu.column_name AS "t2Field"
1717
+ FROM information_schema.key_column_usage kcu
1718
+ JOIN information_schema.referential_constraints rc ON kcu.constraint_name = rc.constraint_name AND kcu.table_schema = rc.constraint_schema
1719
+ JOIN information_schema.constraint_column_usage ccu ON rc.unique_constraint_name = ccu.constraint_name AND rc.unique_constraint_schema = ccu.table_schema
1720
+ WHERE kcu.table_schema = 'public' AND ccu.table_name = $1`, [table]);
1721
+ }
1722
+ else {
1723
+ throw new Error("Unsupported dialect: " + dialect);
1724
+ }
1725
+ const xs = await Promise.all(_.uniqWith(_.isEqual, rs.map(async (v) => {
1726
+ return {
1727
+ table: table,
1728
+ foreignKey: v.t2Field,
1729
+ referencedTable: v.t1,
1730
+ referencedKey: v.t1Field,
1731
+ nullable: false
1732
+ };
1733
+ })));
1734
+ return _.sortBy((x) => x.referencedKey, _.sortBy((x) => x.referencedTable, xs));
1735
+ }, (table) => getCtx().runId + ":" + table);
1736
+ async function getPrimaryColumn(table) {
1737
+ const tableMeta = await getTableMeta(table);
1738
+ const columns = tableMeta.filter((x) => x.Key === "PRI");
1739
+ if (columns.length !== 1) {
1740
+ throw new Error("Expected to find 1 primary key for table: " + table);
1741
+ }
1742
+ const column = columns[0];
1743
+ return {
1744
+ name: column.Field,
1745
+ type: getBaseJSONType(column.Type, getCtx().dialect),
1746
+ nullable: column.Null === "YES"
1747
+ };
1748
+ }
1749
+ async function getUniqueColumns(table, specialCaseUuidColumn) {
1750
+ const tableMeta = await getTableMeta(table);
1751
+ return tableMeta
1752
+ .filter((x) => x.Key === "UNI" ||
1753
+ // Because with the _Base pattern, Key isn't UNI for some reason
1754
+ (specialCaseUuidColumn && x.Field === "uuid"))
1755
+ .map((x) => ({
1756
+ name: x.Field,
1757
+ type: getBaseJSONType(x.Type, getCtx().dialect),
1758
+ nullable: x.Null === "YES"
1759
+ }));
1760
+ }
1761
+ async function getUuidColumn(table) {
1762
+ const tableMeta = await getTableMeta(table);
1763
+ const column = tableMeta.find((x) => x.Field === "uuid");
1764
+ if (column == null) {
1765
+ return undefined;
1766
+ }
1767
+ return {
1768
+ name: column.Field,
1769
+ type: column.Type,
1770
+ nullable: column.Null === "YES"
1771
+ };
1772
+ }
1773
+ const getPgEnumDefinition = (0, memoize_1.default)(async function getPgEnumDefinition(udtName) {
1774
+ const { dialect, query } = getCtx();
1775
+ if (dialect !== "postgresql")
1776
+ return null;
1777
+ const rows = await query(`SELECT e.enumlabel FROM pg_enum e
1778
+ JOIN pg_type t ON e.enumtypid = t.oid
1779
+ JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid
1780
+ WHERE t.typname = $1 AND n.nspname = 'public'
1781
+ ORDER BY e.enumsortorder`, [udtName]);
1782
+ if (rows.length === 0)
1783
+ return null;
1784
+ const labels = rows.map((r) => String(r.enumlabel).replace(/'/g, "''"));
1785
+ return "enum('" + labels.join("', '") + "')";
1786
+ }, (udtName) => getCtx().runId + ":" + udtName);
1787
+ const getTableMeta = (0, memoize_1.default)(async function getTableMeta(table) {
1788
+ const { dialect, query } = getCtx();
1789
+ if (dialect === "mysql") {
1790
+ return query("DESCRIBE ??", [table]).then((xs) => _.sortBy((x) => x.Field, xs));
1791
+ }
1792
+ if (dialect === "postgresql") {
1793
+ const columns = await query(`SELECT column_name AS "Field", data_type, udt_name, character_maximum_length AS char_max, is_nullable, column_default AS "Default"
1794
+ FROM information_schema.columns
1795
+ WHERE table_schema = 'public' AND table_name = $1
1796
+ ORDER BY ordinal_position`, [table]);
1797
+ const keyInfo = await query(`SELECT a.attname AS col, 'PRI' AS key_type
1798
+ FROM pg_index i
1799
+ JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) AND NOT a.attisdropped AND a.attnum > 0
1800
+ JOIN pg_class c ON c.oid = i.indrelid
1801
+ JOIN pg_namespace n ON n.oid = c.relnamespace
1802
+ WHERE n.nspname = 'public' AND c.relname = $1 AND i.indisprimary
1803
+ UNION ALL
1804
+ SELECT kcu.column_name AS col, 'UNI' AS key_type
1805
+ FROM information_schema.table_constraints tc
1806
+ JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
1807
+ WHERE tc.table_schema = 'public' AND tc.table_name = $1 AND tc.constraint_type = 'UNIQUE'
1808
+ UNION ALL
1809
+ SELECT kcu.column_name AS col, 'MUL' AS key_type
1810
+ FROM information_schema.table_constraints tc
1811
+ JOIN information_schema.key_column_usage kcu ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
1812
+ WHERE tc.table_schema = 'public' AND tc.table_name = $1 AND tc.constraint_type = 'FOREIGN KEY'`, [table]);
1813
+ const keyMap = new Map();
1814
+ for (const k of keyInfo) {
1815
+ if (!keyMap.has(k.col) || k.key_type === "PRI")
1816
+ keyMap.set(k.col, k.key_type);
1817
+ }
1818
+ const udtNames = [
1819
+ ...new Set(columns
1820
+ .filter((c) => c.data_type === "USER-DEFINED" && c.udt_name != null)
1821
+ .map((c) => c.udt_name))
1822
+ ];
1823
+ const enumDefs = await Promise.all(udtNames.map((udt) => getPgEnumDefinition(udt)));
1824
+ const enumMap = new Map(udtNames.map((udt, i) => [udt, enumDefs[i] ?? "varchar(255)"]));
1825
+ return columns.map((c) => {
1826
+ let type;
1827
+ if (c.data_type === "USER-DEFINED" && c.udt_name != null) {
1828
+ type = enumMap.get(c.udt_name) ?? "character varying(255)";
1829
+ }
1830
+ else {
1831
+ type = c.data_type;
1832
+ if ((c.data_type === "character varying" || c.data_type === "character") &&
1833
+ c.char_max != null) {
1834
+ type += "(" + c.char_max + ")";
1835
+ }
1836
+ }
1837
+ return {
1838
+ Field: c.Field,
1839
+ Type: type,
1840
+ Null: c.is_nullable === "YES" ? "YES" : "NO",
1841
+ Key: keyMap.get(c.Field) ?? "",
1842
+ Default: c.Default ?? ""
1843
+ };
1844
+ });
1845
+ }
1846
+ throw new Error("Unsupported dialect: " + dialect);
1847
+ }, (table) => getCtx().runId + ":" + table);
1848
+ async function getShowCreateTable(table) {
1849
+ const { dialect, query } = getCtx();
1850
+ if (dialect === "mysql") {
1851
+ return query("SHOW CREATE TABLE ??", [table]).then((xs) => xs[0]["Create Table"]
1852
+ // https://github.com/bradzacher/mysqldump/blob/66839a57e572a07c046b0ba98753f30a7026cbd8/src/getSchemaDump.ts#L65
1853
+ .replace(/AUTO_INCREMENT\s*=\s*\d+ /g, ""));
1854
+ }
1855
+ if (dialect === "postgresql") {
1856
+ const [tableMeta, relations] = await Promise.all([
1857
+ getTableMeta(table),
1858
+ getRelationsManyToOne(table)
1859
+ ]);
1860
+ const refByFk = new Map(relations.map((r) => [r.foreignKey, r]));
1861
+ const columnDefs = tableMeta.map((c) => {
1862
+ let def = `"${c.Field.replace(/"/g, '""')}" ${c.Type} ${c.Null === "YES" ? "NULL" : "NOT NULL"}`;
1863
+ if (c.Default != null && c.Default !== "") {
1864
+ def += ` DEFAULT ${c.Default}`;
1865
+ }
1866
+ if (c.Key === "PRI")
1867
+ def += " PRIMARY KEY";
1868
+ if (c.Key === "UNI")
1869
+ def += " UNIQUE";
1870
+ const ref = refByFk.get(c.Field);
1871
+ if (ref != null) {
1872
+ def += ` REFERENCES "${ref.referencedTable.replace(/"/g, '""')}" ("${ref.referencedKey.replace(/"/g, '""')}")`;
1873
+ }
1874
+ return def;
1875
+ });
1876
+ return `CREATE TABLE "${table.replace(/"/g, '""')}" (\n ${columnDefs.join(",\n ")}\n)`;
1877
+ }
1878
+ return Promise.resolve(null);
1879
+ }
1880
+ function getJSONSchemaObjProperties(tableMeta) {
1881
+ return tableMeta.reduce((acc, m) => {
1882
+ const baseType = getBaseJSONType(m.Type, getCtx().dialect);
1883
+ const format = getPropertyFormat(m.Type);
1884
+ const nullable = m.Null === "YES";
1885
+ const isEnum = m.Type.startsWith("enum");
1886
+ const _enum = isEnum
1887
+ ? getPropertyEnum(m.Type)
1888
+ : undefined;
1889
+ if (_enum && nullable) {
1890
+ _enum.push(null);
1891
+ }
1892
+ acc[m.Field] = {
1893
+ type: getJSONTypes(baseType, nullable)
1894
+ // maxLength:
1895
+ // baseType === "string" && format == null && isEnum == null
1896
+ // ? getPropertyMaxLength(m.Type)
1897
+ // : undefined,
1898
+ };
1899
+ if (_enum !== void 0) {
1900
+ acc[m.Field].enum = _enum;
1901
+ }
1902
+ if (format !== void 0) {
1903
+ acc[m.Field].format = format;
1904
+ }
1905
+ return acc;
1906
+ }, {});
1907
+ }
1908
+ function getJSONTypes(baseType, nullable) {
1909
+ if (nullable) {
1910
+ if (Array.isArray(baseType)) {
1911
+ return baseType.concat("null");
1912
+ }
1913
+ return [baseType, "null"];
1914
+ }
1915
+ return baseType;
1916
+ }
1917
+ // https://github.com/mysqljs/mysql#type-casting
1918
+ function getBaseJSONType(sqlType, dialect) {
1919
+ if (dialect === "postgresql") {
1920
+ if (sqlType === "boolean" || sqlType === "bool")
1921
+ return "boolean";
1922
+ if (["smallint", "int2", "integer", "int4", "bigint", "int8"].includes(sqlType)) {
1923
+ return "integer";
1924
+ }
1925
+ if (["real", "float4", "double precision", "float8"].includes(sqlType) ||
1926
+ sqlType.startsWith("numeric") ||
1927
+ sqlType.startsWith("decimal")) {
1928
+ return "number";
1929
+ }
1930
+ if (["text", "uuid", "json", "jsonb"].includes(sqlType) ||
1931
+ sqlType === "date" ||
1932
+ sqlType === "time" ||
1933
+ sqlType.startsWith("timestamp") ||
1934
+ sqlType.startsWith("character varying") ||
1935
+ sqlType.startsWith("character") ||
1936
+ sqlType.startsWith("enum")) {
1937
+ return "string";
1938
+ }
1939
+ throw new Error("Unable to map to JSON type: " + sqlType);
1940
+ }
1941
+ if (
1942
+ // TODO?
1943
+ sqlType === "tinyint(1)" ||
1944
+ sqlType === "boolean" ||
1945
+ sqlType === "bool" ||
1946
+ sqlType === "bit") {
1947
+ return "boolean";
1948
+ }
1949
+ // TODO?
1950
+ if (sqlType === "timestamp" ||
1951
+ sqlType === "datetime" ||
1952
+ sqlType === "datetime2" ||
1953
+ sqlType === "date") {
1954
+ return "string";
1955
+ }
1956
+ if (sqlType.startsWith("char") ||
1957
+ sqlType.startsWith("varchar") ||
1958
+ sqlType.startsWith("tinytext") ||
1959
+ sqlType.startsWith("mediumtext") ||
1960
+ sqlType.startsWith("longtext") ||
1961
+ sqlType.startsWith("text") ||
1962
+ sqlType.startsWith("enum") ||
1963
+ sqlType.startsWith("set") ||
1964
+ sqlType.startsWith("time") ||
1965
+ sqlType.startsWith("geometry") ||
1966
+ // TODO?
1967
+ // https://github.com/mysqljs/mysql#buffer
1968
+ sqlType.startsWith("tinyblob") ||
1969
+ sqlType.startsWith("mediumblob") ||
1970
+ sqlType.startsWith("longblob") ||
1971
+ sqlType.startsWith("blob") ||
1972
+ sqlType.startsWith("binary") ||
1973
+ sqlType.startsWith("varbinary") ||
1974
+ sqlType === "uniqueidentifier"
1975
+ // sqlType.startsWith("bit")
1976
+ ) {
1977
+ return "string";
1978
+ }
1979
+ if (sqlType.startsWith("tinyint") ||
1980
+ sqlType.startsWith("smallint") ||
1981
+ sqlType.startsWith("mediumint") ||
1982
+ sqlType.startsWith("int") ||
1983
+ sqlType.startsWith("year")) {
1984
+ return "integer";
1985
+ }
1986
+ if (sqlType.startsWith("bigint")) {
1987
+ // TODO: will be string if `bigNumberStrings` is used
1988
+ // https://github.com/mysqljs/mysql#connection-options
1989
+ return "integer";
1990
+ }
1991
+ if (sqlType.startsWith("float") || sqlType.startsWith("double")) {
1992
+ return "number";
1993
+ }
1994
+ if (sqlType.startsWith("decimal")) {
1995
+ // TODO: will be string if `bigNumberStrings` is used
1996
+ // https://github.com/mysqljs/mysql#connection-options
1997
+ return "number";
1998
+ }
1999
+ if (sqlType === "json" || sqlType === "nvarchar") {
2000
+ // TODO
2001
+ return "string";
2002
+ // return ["array", "object"];
2003
+ }
2004
+ throw new Error("Unable to map to JSON type: " + sqlType);
2005
+ }
2006
+ // function getPropertyMaxLength(sqlType: string) {
2007
+ // const a = sqlType.includes("(");
2008
+ // if (a) {
2009
+ // const tmp = sqlType.split("(");
2010
+ // const b = tmp[tmp.length - 1];
2011
+ // return parseInt(b.slice(0, b.length - 1));
2012
+ // }
2013
+ // return undefined;
2014
+ // }
2015
+ function getPropertyEnum(sqlType) {
2016
+ const a = sqlType.split("enum(").filter(Boolean)[0];
2017
+ const b = a.slice(0, a.length - 1);
2018
+ const c = b
2019
+ .split(",")
2020
+ .map((x) => x.trim())
2021
+ .map((x) => x.replace(/['"]/g, ""));
2022
+ return c;
2023
+ }
2024
+ function getPropertyFormat(sqlType) {
2025
+ if (sqlType === "datetime" ||
2026
+ sqlType === "datetime2" ||
2027
+ sqlType === "timestamp" ||
2028
+ sqlType.startsWith("timestamp")) {
2029
+ // TODO: not sure this is correct for `timestamp`
2030
+ return "date-time";
2031
+ }
2032
+ if (sqlType === "date") {
2033
+ // https://json-schema.org/understanding-json-schema/reference/string.html#built-in-formats
2034
+ // https://github.com/epoberezkin/ajv#formats
2035
+ // `date` isn't a built-in format, but it's supported by `Ajv`.
2036
+ return "date";
2037
+ }
2038
+ if (sqlType === "time") {
2039
+ // https://json-schema.org/understanding-json-schema/reference/string.html#built-in-formats
2040
+ // https://github.com/epoberezkin/ajv#formats
2041
+ // `time` isn't a built-in format, but it's supported by `Ajv`.
2042
+ return "time";
2043
+ }
2044
+ return undefined;
2045
+ }
2046
+ async function getTableNames() {
2047
+ const { dialect, query } = getCtx();
2048
+ if (dialect === "mysql") {
2049
+ return query("SHOW TABLES").then((xs) => xs.flatMap((x) => Object.values(x)).sort());
2050
+ }
2051
+ if (dialect === "postgresql") {
2052
+ return query(`SELECT table_name FROM information_schema.tables
2053
+ WHERE table_schema = 'public' AND table_type = 'BASE TABLE'
2054
+ ORDER BY table_name`).then((rows) => rows.map((r) => r.table_name));
2055
+ }
2056
+ throw new Error("Unsupported dialect: " + dialect);
2057
+ }
2058
+ function getMysql2sqliteSrc() {
2059
+ return `#!/usr/bin/awk -f
2060
+
2061
+ # Authors: @esperlu, @artemyk, @gkuenning, @dumblob
2062
+
2063
+ # FIXME detect empty input file and issue a warning
2064
+
2065
+ function printerr( s ){ print s | "cat >&2" }
2066
+
2067
+ BEGIN {
2068
+ if( ARGC != 2 ){
2069
+ printerr( \\
2070
+ "USAGE:\\n"\\
2071
+ " mysql2sqlite dump_mysql.sql > dump_sqlite3.sql\\n" \\
2072
+ " OR\\n" \\
2073
+ " mysql2sqlite dump_mysql.sql | sqlite3 sqlite.db\\n" \\
2074
+ "\\n" \\
2075
+ "NOTES:\\n" \\
2076
+ " Dash in filename is not supported, because dash (-) means stdin." )
2077
+ no_END = 1
2078
+ exit 1
2079
+ }
2080
+
2081
+ # Find INT_MAX supported by both this AWK (usually an ISO C signed int)
2082
+ # and SQlite.
2083
+ # On non-8bit-based architectures, the additional bits are safely ignored.
2084
+
2085
+ # 8bit (lower precision should not exist)
2086
+ s="127"
2087
+ # "63" + 0 avoids potential parser misbehavior
2088
+ if( (s + 0) "" == s ){ INT_MAX_HALF = "63" + 0 }
2089
+ # 16bit
2090
+ s="32767"
2091
+ if( (s + 0) "" == s ){ INT_MAX_HALF = "16383" + 0 }
2092
+ # 32bit
2093
+ s="2147483647"
2094
+ if( (s + 0) "" == s ){ INT_MAX_HALF = "1073741823" + 0 }
2095
+ # 64bit (as INTEGER in SQlite3)
2096
+ s="9223372036854775807"
2097
+ if( (s + 0) "" == s ){ INT_MAX_HALF = "4611686018427387904" + 0 }
2098
+ # # 128bit
2099
+ # s="170141183460469231731687303715884105728"
2100
+ # if( (s + 0) "" == s ){ INT_MAX_HALF = "85070591730234615865843651857942052864" + 0 }
2101
+ # # 256bit
2102
+ # s="57896044618658097711785492504343953926634992332820282019728792003956564819968"
2103
+ # if( (s + 0) "" == s ){ INT_MAX_HALF = "28948022309329048855892746252171976963317496166410141009864396001978282409984" + 0 }
2104
+ # # 512bit
2105
+ # s="6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042048"
2106
+ # if( (s + 0) "" == s ){ INT_MAX_HALF = "3351951982485649274893506249551461531869841455148098344430890360930441007518386744200468574541725856922507964546621512713438470702986642486608412251521024" + 0 }
2107
+ # # 1024bit
2108
+ # s="89884656743115795386465259539451236680898848947115328636715040578866337902750481566354238661203768010560056939935696678829394884407208311246423715319737062188883946712432742638151109800623047059726541476042502884419075341171231440736956555270413618581675255342293149119973622969239858152417678164812112068608"
2109
+ # if( (s + 0) "" == s ){ INT_MAX_HALF = "44942328371557897693232629769725618340449424473557664318357520289433168951375240783177119330601884005280028469967848339414697442203604155623211857659868531094441973356216371319075554900311523529863270738021251442209537670585615720368478277635206809290837627671146574559986811484619929076208839082406056034304" + 0 }
2110
+ # # higher precision probably not needed
2111
+
2112
+ FS=",$"
2113
+ print "PRAGMA synchronous = OFF;"
2114
+ print "PRAGMA journal_mode = MEMORY;"
2115
+ print "BEGIN TRANSACTION;"
2116
+ }
2117
+
2118
+ # historically 3 spaces separate non-argument local variables
2119
+ function bit_to_int( str_bit, powtwo, i, res, bit, overflow ){
2120
+ powtwo = 1
2121
+ overflow = 0
2122
+ # 011101 = 1*2^0 + 0*2^1 + 1*2^2 ...
2123
+ for( i = length( str_bit ); i > 0; --i ){
2124
+ bit = substr( str_bit, i, 1 )
2125
+ if( overflow || ( bit == 1 && res > INT_MAX_HALF ) ){
2126
+ printerr( \\
2127
+ NR ": WARN Bit field overflow, number truncated (LSBs saved, MSBs ignored)." )
2128
+ break
2129
+ }
2130
+ res = res + bit * powtwo
2131
+ # no warning here as it might be the last iteration
2132
+ if( powtwo > INT_MAX_HALF ){ overflow = 1; continue }
2133
+ powtwo = powtwo * 2
2134
+ }
2135
+ return res
2136
+ }
2137
+
2138
+ # CREATE TRIGGER statements have funny commenting. Remember we are in trigger.
2139
+ /^\\/\\*.*(CREATE.*TRIGGER|create.*trigger)/ {
2140
+ gsub( /^.*(TRIGGER|trigger)/, "CREATE TRIGGER" )
2141
+ print
2142
+ inTrigger = 1
2143
+ next
2144
+ }
2145
+ # The end of CREATE TRIGGER has a stray comment terminator
2146
+ /(END|end) \\*\\/;;/ { gsub( /\\*\\//, "" ); print; inTrigger = 0; next }
2147
+ # The rest of triggers just get passed through
2148
+ inTrigger != 0 { print; next }
2149
+
2150
+ # CREATE VIEW looks like a TABLE in comments
2151
+ /^\\/\\*.*(CREATE.*TABLE|create.*table)/ {
2152
+ inView = 1
2153
+ next
2154
+ }
2155
+ # end of CREATE VIEW
2156
+ /^(\\).*(ENGINE|engine).*\\*\\/;)/ {
2157
+ inView = 0
2158
+ next
2159
+ }
2160
+ # content of CREATE VIEW
2161
+ inView != 0 { next }
2162
+
2163
+ # skip comments
2164
+ /^\\/\\*/ { next }
2165
+
2166
+ # skip PARTITION statements
2167
+ /^ *[(]?(PARTITION|partition) +[^ ]+/ { next }
2168
+
2169
+ # print all INSERT lines
2170
+ ( /^ *\\(/ && /\\) *[,;] *$/ ) || /^(INSERT|insert|REPLACE|replace)/ {
2171
+ prev = ""
2172
+
2173
+ # first replace \\\\ by \\_ that mysqldump never generates to deal with
2174
+ # sequnces like \\\\n that should be translated into \\n, not \\<LF>.
2175
+ # After we convert all escapes we replace \\_ by backslashes.
2176
+ gsub( /\\\\\\\\/, "\\\\_" )
2177
+
2178
+ # single quotes are escaped by another single quote
2179
+ gsub( /\\\\'/, "''" )
2180
+ gsub( /\\\\n/, "\\n" )
2181
+ gsub( /\\\\r/, "\\r" )
2182
+ gsub( /\\\\"/, "\\"" )
2183
+ gsub( /\\\\\\032/, "\\032" ) # substitute char
2184
+
2185
+ gsub( /\\\\_/, "\\\\" )
2186
+
2187
+ # sqlite3 is limited to 16 significant digits of precision
2188
+ while( match( $0, /0x[0-9a-fA-F]{17}/ ) ){
2189
+ hexIssue = 1
2190
+ sub( /0x[0-9a-fA-F]+/, substr( $0, RSTART, RLENGTH-1 ), $0 )
2191
+ }
2192
+ if( hexIssue ){
2193
+ printerr( \\
2194
+ NR ": WARN Hex number trimmed (length longer than 16 chars)." )
2195
+ hexIssue = 0
2196
+ }
2197
+ print
2198
+ next
2199
+ }
2200
+
2201
+ # CREATE DATABASE is not supported
2202
+ /^(CREATE DATABASE|create database)/ { next }
2203
+
2204
+ # print the CREATE line as is and capture the table name
2205
+ /^(CREATE|create)/ {
2206
+ if( $0 ~ /IF NOT EXISTS|if not exists/ || $0 ~ /TEMPORARY|temporary/ ){
2207
+ caseIssue = 1
2208
+ printerr( \\
2209
+ NR ": WARN Potential case sensitivity issues with table/column naming\\n" \\
2210
+ " (see INFO at the end)." )
2211
+ }
2212
+ if( match( $0, /\`[^\`]+/ ) ){
2213
+ tableName = substr( $0, RSTART+1, RLENGTH-1 )
2214
+ }
2215
+ aInc = 0
2216
+ prev = ""
2217
+ firstInTable = 1
2218
+ print
2219
+ next
2220
+ }
2221
+
2222
+ # Replace \`FULLTEXT KEY\` (probably other \`XXXXX KEY\`)
2223
+ /^ (FULLTEXT KEY|fulltext key)/ { gsub( /[A-Za-z ]+(KEY|key)/, " KEY" ) }
2224
+
2225
+ # Get rid of field lengths in KEY lines
2226
+ / (PRIMARY |primary )?(KEY|key)/ { gsub( /\\([0-9]+\\)/, "" ) }
2227
+
2228
+ aInc == 1 && /PRIMARY KEY|primary key/ { next }
2229
+
2230
+ # Replace COLLATE xxx_xxxx_xx statements with COLLATE BINARY
2231
+ / (COLLATE|collate) [a-z0-9_]*/ { gsub( /(COLLATE|collate) [a-z0-9_]*/, "COLLATE BINARY" ) }
2232
+
2233
+ # Print all fields definition lines except the \`KEY\` lines.
2234
+ /^ / && !/^( (KEY|key)|\\);)/ {
2235
+ if( match( $0, /[^"\`]AUTO_INCREMENT|auto_increment[^"\`]/) ){
2236
+ aInc = 1
2237
+ gsub( /AUTO_INCREMENT|auto_increment/, "PRIMARY KEY AUTOINCREMENT" )
2238
+ }
2239
+ gsub( /(UNIQUE KEY|unique key) (\`.*\`|".*") /, "UNIQUE " )
2240
+ gsub( /(CHARACTER SET|character set) [^ ]+[ ,]/, "" )
2241
+ # FIXME
2242
+ # CREATE TRIGGER [UpdateLastTime]
2243
+ # AFTER UPDATE
2244
+ # ON Package
2245
+ # FOR EACH ROW
2246
+ # BEGIN
2247
+ # UPDATE Package SET LastUpdate = CURRENT_TIMESTAMP WHERE ActionId = old.ActionId;
2248
+ # END
2249
+ gsub( /(ON|on) (UPDATE|update) (CURRENT_TIMESTAMP|current_timestamp)(\\(\\))?/, "" )
2250
+ gsub( /(DEFAULT|default) (CURRENT_TIMESTAMP|current_timestamp)(\\(\\))?/, "DEFAULT current_timestamp")
2251
+ gsub( /(COLLATE|collate) [^ ]+ /, "" )
2252
+ gsub( /(ENUM|enum)[^)]+\\)/, "text " )
2253
+ gsub( /(SET|set)\\([^)]+\\)/, "text " )
2254
+ gsub( /UNSIGNED|unsigned/, "" )
2255
+ gsub( /_utf8mb3/, "" )
2256
+ gsub( /\` [^ ]*(INT|int|BIT|bit)[^ ]*/, "\` integer" )
2257
+ gsub( /" [^ ]*(INT|int|BIT|bit)[^ ]*/, "\\" integer" )
2258
+ ere_bit_field = "[bB]'[10]+'"
2259
+ if( match($0, ere_bit_field) ){
2260
+ sub( ere_bit_field, bit_to_int( substr( $0, RSTART +2, RLENGTH -2 -1 ) ) )
2261
+ }
2262
+
2263
+ # remove USING BTREE and other suffixes for USING, for example: "UNIQUE KEY
2264
+ # \`hostname_domain\` (\`hostname\`,\`domain\`) USING BTREE,"
2265
+ gsub( / USING [^, ]+/, "" )
2266
+
2267
+ # field comments are not supported
2268
+ gsub( / (COMMENT|comment).+$/, "" )
2269
+ # Get commas off end of line
2270
+ gsub( /,.?$/, "" )
2271
+ if( prev ){
2272
+ if( firstInTable ){
2273
+ print prev
2274
+ firstInTable = 0
2275
+ }
2276
+ else {
2277
+ print "," prev
2278
+ }
2279
+ }
2280
+ else {
2281
+ # FIXME check if this is correct in all cases
2282
+ if( match( $1,
2283
+ /(CONSTRAINT|constraint) ["].*["] (FOREIGN KEY|foreign key)/ ) ){
2284
+ print ","
2285
+ }
2286
+ }
2287
+ prev = $1
2288
+ }
2289
+
2290
+ / ENGINE| engine/ {
2291
+ if( prev ){
2292
+ if( firstInTable ){
2293
+ print prev
2294
+ firstInTable = 0
2295
+ }
2296
+ else {
2297
+ print "," prev
2298
+ }
2299
+ }
2300
+ prev=""
2301
+ print ");"
2302
+ next
2303
+ }
2304
+ # \`KEY\` lines are extracted from the \`CREATE\` block and stored in array for later print
2305
+ # in a separate \`CREATE KEY\` command. The index name is prefixed by the table name to
2306
+ # avoid a sqlite error for duplicate index name.
2307
+ /^( (KEY|key)|\\);)/ {
2308
+ if( prev ){
2309
+ if( firstInTable ){
2310
+ print prev
2311
+ firstInTable = 0
2312
+ }
2313
+ else {
2314
+ print "," prev
2315
+ }
2316
+ }
2317
+ prev = ""
2318
+ if( $0 == ");" ){
2319
+ print
2320
+ }
2321
+ else {
2322
+ if( match( $0, /\`[^\`]+/ ) ){
2323
+ indexName = substr( $0, RSTART+1, RLENGTH-1 )
2324
+ }
2325
+ if( match( $0, /\\([^()]+/ ) ){
2326
+ indexKey = substr( $0, RSTART+1, RLENGTH-1 )
2327
+ }
2328
+ # idx_ prefix to avoid name clashes (they really happen!)
2329
+ key[tableName] = key[tableName] "CREATE INDEX \\"idx_" \\
2330
+ tableName "_" indexName "\\" ON \\"" tableName "\\" (" indexKey ");\\n"
2331
+ }
2332
+ }
2333
+
2334
+ END {
2335
+ if( no_END ){ exit 1}
2336
+ # print all KEY creation lines.
2337
+ for( table in key ){ printf key[table] }
2338
+
2339
+ print "END TRANSACTION;"
2340
+
2341
+ if( caseIssue ){
2342
+ printerr( \\
2343
+ "INFO Pure sqlite identifiers are case insensitive (even if quoted\\n" \\
2344
+ " or if ASCII) and doesnt cross-check TABLE and TEMPORARY TABLE\\n" \\
2345
+ " identifiers. Thus expect errors like \\"table T has no column named F\\".")
2346
+ }
2347
+ }
2348
+ `;
2349
+ }