@technicity/data-service-generator 0.23.0-next.1 → 0.23.0-next.3

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.
@@ -44,6 +44,7 @@ const isNotNullOrUndefined_1 = require("../lib/isNotNullOrUndefined");
44
44
  const pg_1 = require("pg");
45
45
  const MySQL_1 = require("../runtime/lib/MySQL");
46
46
  const capitalizeFirstLetter_1 = require("../lib/capitalizeFirstLetter");
47
+ const pg2sqliteSchema_1 = require("./pg2sqliteSchema");
47
48
  const ctxStorage = new node_async_hooks_1.AsyncLocalStorage();
48
49
  function getCtx() {
49
50
  const c = ctxStorage.getStore();
@@ -68,7 +69,8 @@ async function generate(input) {
68
69
  const ctx = {
69
70
  runId: node_crypto_1.default.randomUUID(),
70
71
  dialect: input.dialect,
71
- query: undefined
72
+ query: undefined,
73
+ pool: undefined
72
74
  };
73
75
  return ctxStorage.run(ctx, async () => {
74
76
  init(input);
@@ -174,36 +176,46 @@ async function generate(input) {
174
176
  fs.writeFileSync(path.join(tmpBuildOutputPath, "IRuntime.d.ts"), fs.existsSync(path.join(__dirname, "../runtime", "IRuntime.d.ts"))
175
177
  ? fs.readFileSync(path.join(__dirname, "../runtime", "IRuntime.d.ts"), "utf-8")
176
178
  : 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) {
179
+ if (input.outputSqliteSchema) {
180
+ let schemaSqlite = null;
181
+ if (getCtx().dialect === "mysql") {
182
+ // Since mysql2sqlite outputs a malformed string if a column
183
+ // has the name `enum`, temporarily change the name to something else,
184
+ // then change it back.
185
+ const enumMarker = "`" + node_crypto_1.default.randomUUID() + "`";
186
+ const schemaMySql = Object.values(artifacts)
187
+ .reduce((acc, x) => {
188
+ let d = x.dump?.schema;
189
+ if (!d) {
190
+ return acc;
191
+ }
192
+ d = d.replace(/`enum`/g, enumMarker);
193
+ d += ";";
194
+ acc.push(d);
186
195
  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);
196
+ }, [])
197
+ .join("\n\n");
198
+ const mysql2SqliteSrc = getMysql2sqliteSrc();
199
+ const mysql2SqlitePath = path.join(tmpDirPath, "mysql2sqlite");
200
+ fs.writeFileSync(mysql2SqlitePath, mysql2SqliteSrc);
201
+ fs.chmodSync(mysql2SqlitePath, 0o755);
202
+ const tmpMySqlSchemaFilename = "tmp.sql";
203
+ const tmpMySqlSchemaPath = path.join(tmpDirPath, tmpMySqlSchemaFilename);
204
+ fs.writeFileSync(tmpMySqlSchemaPath, schemaMySql);
205
+ schemaSqlite = child_process
206
+ .execFileSync(mysql2SqlitePath, [tmpMySqlSchemaFilename], {
207
+ cwd: tmpDirPath
208
+ })
209
+ .toString();
210
+ schemaSqlite = schemaSqlite.replace(new RegExp(enumMarker, "g"), "`enum`");
211
+ }
212
+ else if (getCtx().dialect === "postgresql") {
213
+ schemaSqlite = await (0, pg2sqliteSchema_1.convertPgSchemaToSqlite)(getCtx().pool);
214
+ }
215
+ if (schemaSqlite) {
216
+ const src = prettier.format(`module.exports = { schema: \`${schemaSqlite.replace(/`/g, "\\`")}\` }`, { parser: "babel" });
217
+ fs.writeFileSync(path.join(tmpBuildOutputPath, "artifacts.sqlite.js"), src);
218
+ }
207
219
  }
208
220
  if (!fs.existsSync(outdir)) {
209
221
  fse.mkdirpSync(outdir);
@@ -217,24 +229,28 @@ function init(input) {
217
229
  const ctx = getCtx();
218
230
  const { database, user, password, host, port, server } = input;
219
231
  if (ctx.dialect === "mysql") {
220
- const mysql = new MySQL_1.MySQL({
232
+ const connectionOpts = {
221
233
  user,
222
234
  password,
223
- host,
224
- port,
235
+ host: host ?? "localhost",
236
+ port: port ?? 3306,
225
237
  database
226
- });
238
+ };
239
+ const mysql = new MySQL_1.MySQL(connectionOpts);
227
240
  ctx.query = mysql.query.bind(mysql);
241
+ ctx.pool = mysql;
228
242
  }
229
243
  if (ctx.dialect === "postgresql") {
230
- const pool = new pg_1.Pool({
244
+ const connectionOpts = {
231
245
  host: host ?? "localhost",
232
246
  port: port ?? 5432,
233
247
  user,
234
248
  password,
235
249
  database
236
- });
250
+ };
251
+ const pool = new pg_1.Pool(connectionOpts);
237
252
  ctx.query = (q, values) => pool.query(q, values ?? []).then((r) => r.rows);
253
+ ctx.pool = pool;
238
254
  }
239
255
  }
240
256
  // It's a bit awkward to put __whereNeedsProcessing, __prepareWhere on the class,
@@ -278,7 +294,7 @@ async function getSDKSource(input, specialCaseUuidColumn, supplementClientOpts,
278
294
  runtime: any;
279
295
  clientOpts: { [k: string]: any; },
280
296
  otherOpts?: { [k: string]: any; },
281
- passBeforeValueToAfterCallback: boolean,
297
+ passBeforeValueToAfterCallback?: boolean,
282
298
  }) {
283
299
  let otherOpts = opts.otherOpts ?? {};
284
300
  if (opts.clientOpts.filename === ":memory:") {
@@ -291,7 +307,7 @@ async function getSDKSource(input, specialCaseUuidColumn, supplementClientOpts,
291
307
  : "otherOpts"}, artifacts);
292
308
  this.onHandlerMap = new Map();
293
309
  this.eventTarget = new EventTarget();
294
- this.passBeforeValueToAfterCallback = opts.passBeforeValueToAfterCallback;
310
+ this.passBeforeValueToAfterCallback = opts.passBeforeValueToAfterCallback ?? false;
295
311
  }
296
312
 
297
313
  $use(middleware: TMiddleware) {
@@ -1622,7 +1638,7 @@ const getRelationInfo = (0, memoize_1.default)(async function getRelationInfo(ta
1622
1638
  return acc;
1623
1639
  }, []);
1624
1640
  out = out.concat(relationsManyToMany);
1625
- out = _.sortBy((x) => x.table, out);
1641
+ out = _.sortBy([(x) => x.table, (x) => x.name], out);
1626
1642
  return out;
1627
1643
  }, (table) => getCtx().runId + ":" + table);
1628
1644
  function getRelationManyToOneFieldName(x) {
@@ -1784,6 +1800,32 @@ const getPgEnumDefinition = (0, memoize_1.default)(async function getPgEnumDefin
1784
1800
  const labels = rows.map((r) => String(r.enumlabel).replace(/'/g, "''"));
1785
1801
  return "enum('" + labels.join("', '") + "')";
1786
1802
  }, (udtName) => getCtx().runId + ":" + udtName);
1803
+ /** List enum type names in the public schema (for building CREATE TYPE DDL). */
1804
+ async function getPublicEnumTypeNames() {
1805
+ const { dialect, query } = getCtx();
1806
+ if (dialect !== "postgresql")
1807
+ return [];
1808
+ const rows = await query(`SELECT t.typname FROM pg_type t
1809
+ JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid
1810
+ WHERE n.nspname = 'public' AND t.typtype = 'e'
1811
+ ORDER BY t.typname`);
1812
+ return rows.map((r) => String(r.typname));
1813
+ }
1814
+ const getPgCreateTypeEnumStatement = (0, memoize_1.default)(async function getPgCreateTypeEnumStatement(typname) {
1815
+ const { dialect, query } = getCtx();
1816
+ if (dialect !== "postgresql")
1817
+ return null;
1818
+ const rows = await query(`SELECT e.enumlabel FROM pg_enum e
1819
+ JOIN pg_type t ON e.enumtypid = t.oid
1820
+ JOIN pg_catalog.pg_namespace n ON t.typnamespace = n.oid
1821
+ WHERE t.typname = $1 AND n.nspname = 'public'
1822
+ ORDER BY e.enumsortorder`, [typname]);
1823
+ if (rows.length === 0)
1824
+ return null;
1825
+ const quoted = typname.replace(/"/g, '""');
1826
+ const labels = rows.map((r) => "'" + String(r.enumlabel).replace(/'/g, "''") + "'");
1827
+ return `CREATE TYPE "${quoted}" AS ENUM (${labels.join(", ")});`;
1828
+ }, (typname) => getCtx().runId + ":" + typname);
1787
1829
  const getTableMeta = (0, memoize_1.default)(async function getTableMeta(table) {
1788
1830
  const { dialect, query } = getCtx();
1789
1831
  if (dialect === "mysql") {
@@ -1839,7 +1881,10 @@ const getTableMeta = (0, memoize_1.default)(async function getTableMeta(table) {
1839
1881
  Type: type,
1840
1882
  Null: c.is_nullable === "YES" ? "YES" : "NO",
1841
1883
  Key: keyMap.get(c.Field) ?? "",
1842
- Default: c.Default ?? ""
1884
+ Default: c.Default ?? "",
1885
+ ...(c.data_type === "USER-DEFINED" && c.udt_name != null
1886
+ ? { PgType: c.udt_name }
1887
+ : {})
1843
1888
  };
1844
1889
  });
1845
1890
  }
@@ -1859,7 +1904,21 @@ async function getShowCreateTable(table) {
1859
1904
  ]);
1860
1905
  const refByFk = new Map(relations.map((r) => [r.foreignKey, r]));
1861
1906
  const columnDefs = tableMeta.map((c) => {
1862
- let def = `"${c.Field.replace(/"/g, '""')}" ${c.Type} ${c.Null === "YES" ? "NULL" : "NOT NULL"}`;
1907
+ const isSerialPk = c.Key === "PRI" &&
1908
+ c.Default != null &&
1909
+ c.Default !== "" &&
1910
+ /nextval\s*\(/i.test(c.Default);
1911
+ if (isSerialPk) {
1912
+ const baseType = (c.PgType ?? c.Type).toLowerCase();
1913
+ const serialType = baseType === "bigint" || baseType === "int8"
1914
+ ? "BIGSERIAL"
1915
+ : baseType === "smallint" || baseType === "int2"
1916
+ ? "SMALLSERIAL"
1917
+ : "SERIAL";
1918
+ return `"${c.Field.replace(/"/g, '""')}" ${serialType} PRIMARY KEY`;
1919
+ }
1920
+ const pgType = c.PgType ?? c.Type;
1921
+ let def = `"${c.Field.replace(/"/g, '""')}" ${pgType} ${c.Null === "YES" ? "NULL" : "NOT NULL"}`;
1863
1922
  if (c.Default != null && c.Default !== "") {
1864
1923
  def += ` DEFAULT ${c.Default}`;
1865
1924
  }
@@ -1873,7 +1932,7 @@ async function getShowCreateTable(table) {
1873
1932
  }
1874
1933
  return def;
1875
1934
  });
1876
- return `CREATE TABLE "${table.replace(/"/g, '""')}" (\n ${columnDefs.join(",\n ")}\n)`;
1935
+ return `CREATE TABLE "${table.replace(/"/g, '""')}" (\n ${columnDefs.join(",\n ")}\n);`;
1877
1936
  }
1878
1937
  return Promise.resolve(null);
1879
1938
  }
@@ -0,0 +1,6 @@
1
+ import { Pool } from "pg";
2
+ /**
3
+ * Reflects the PostgreSQL schema from the given connection and returns
4
+ * SQLite-compatible CREATE TABLE statements as a string.
5
+ */
6
+ export declare function convertPgSchemaToSqlite(pool: Pool): Promise<string>;
@@ -0,0 +1,121 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.convertPgSchemaToSqlite = convertPgSchemaToSqlite;
4
+ /**
5
+ * Reflects the PostgreSQL schema from the given connection and returns
6
+ * SQLite-compatible CREATE TABLE statements as a string.
7
+ */
8
+ async function convertPgSchemaToSqlite(pool) {
9
+ const columnsResult = await pool.query(`
10
+ SELECT table_schema, table_name, column_name, ordinal_position,
11
+ data_type, character_maximum_length, is_nullable, column_default
12
+ FROM information_schema.columns
13
+ WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
14
+ ORDER BY table_schema, table_name, ordinal_position
15
+ `);
16
+ const pkResult = await pool.query(`
17
+ SELECT tc.table_schema, tc.table_name, kcu.column_name
18
+ FROM information_schema.table_constraints tc
19
+ JOIN information_schema.key_column_usage kcu
20
+ ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema
21
+ WHERE tc.constraint_type = 'PRIMARY KEY'
22
+ `);
23
+ const primaryKeys = new Set(pkResult.rows.map((r) => `${r.table_schema}.${r.table_name}.${r.column_name}`));
24
+ const byTable = {};
25
+ for (const c of columnsResult.rows) {
26
+ const key = `${c.table_schema}.${c.table_name}`;
27
+ if (!byTable[key])
28
+ byTable[key] = [];
29
+ byTable[key].push(c);
30
+ }
31
+ const statements = [];
32
+ for (const [key, cols] of Object.entries(byTable)) {
33
+ const tableName = key.split(".").slice(-1)[0];
34
+ const parts = cols.map((c) => {
35
+ const sqliteType = toSqliteType(c.data_type);
36
+ const pk = primaryKeys.has(`${c.table_schema}.${c.table_name}.${c.column_name}`);
37
+ const notNull = c.is_nullable === "NO" && !(c.column_default?.includes("nextval") ?? false);
38
+ const defaultVal = pgDefaultToSqlite(c.column_default);
39
+ const pkClause = pk ? " PRIMARY KEY" : "";
40
+ const nullClause = notNull && !pk ? " NOT NULL" : "";
41
+ const defaultClause = defaultVal != null ? ` DEFAULT ${defaultVal}` : "";
42
+ return ` "${c.column_name}" ${sqliteType}${pkClause}${nullClause}${defaultClause}`;
43
+ });
44
+ statements.push(`CREATE TABLE "${tableName}" (\n${parts.join(",\n")}\n);`);
45
+ }
46
+ return statements.join("\n\n") + "\n";
47
+ }
48
+ const PG_TO_SQLITE_TYPE = {
49
+ smallint: "INTEGER",
50
+ integer: "INTEGER",
51
+ bigint: "INTEGER",
52
+ serial: "INTEGER",
53
+ bigserial: "INTEGER",
54
+ smallserial: "INTEGER",
55
+ real: "REAL",
56
+ "double precision": "REAL",
57
+ numeric: "REAL",
58
+ decimal: "REAL",
59
+ boolean: "INTEGER",
60
+ character: "TEXT",
61
+ "character varying": "TEXT",
62
+ varchar: "TEXT",
63
+ char: "TEXT",
64
+ text: "TEXT",
65
+ timestamp: "TEXT",
66
+ "timestamp with time zone": "TEXT",
67
+ "timestamp without time zone": "TEXT",
68
+ date: "TEXT",
69
+ time: "TEXT",
70
+ json: "TEXT",
71
+ jsonb: "TEXT",
72
+ uuid: "TEXT",
73
+ bytea: "BLOB"
74
+ };
75
+ function toSqliteType(pgType) {
76
+ const base = (pgType ?? "")
77
+ .toLowerCase()
78
+ .replace(/^(.*?)(\s+with.*|\s+without.*)$/, "$1")
79
+ .trim();
80
+ return PG_TO_SQLITE_TYPE[base] ?? "TEXT";
81
+ }
82
+ /**
83
+ * Converts a PostgreSQL column_default expression to a SQLite DEFAULT clause
84
+ * value (without the "DEFAULT" keyword). Returns null if we should not emit a default.
85
+ */
86
+ function pgDefaultToSqlite(columnDefault) {
87
+ if (columnDefault == null || columnDefault.trim() === "")
88
+ return null;
89
+ const raw = columnDefault.trim();
90
+ // Skip sequence-based defaults (serial/identity); SQLite uses INTEGER PRIMARY KEY
91
+ if (raw.toLowerCase().includes("nextval"))
92
+ return null;
93
+ // Timestamp / time
94
+ if (/^(now\s*\(\s*\)|current_timestamp)$/i.test(raw))
95
+ return "CURRENT_TIMESTAMP";
96
+ if (/^current_date$/i.test(raw))
97
+ return "CURRENT_DATE";
98
+ if (/^current_time$/i.test(raw))
99
+ return "CURRENT_TIME";
100
+ // Booleans
101
+ if (/^\s*true\s*$/i.test(raw))
102
+ return "1";
103
+ if (/^\s*false\s*$/i.test(raw))
104
+ return "0";
105
+ // Numeric literal (optionally with ::type)
106
+ const numericMatch = raw.match(/^(-?\d+(?:\.\d+)?)\s*(?:::\s*\w+(?:\s+\w+)*)?\s*$/);
107
+ if (numericMatch)
108
+ return numericMatch[1];
109
+ // String literal: '...' with optional ::type
110
+ const stringMatch = raw.match(/^'(.*)'\s*(?:::\s*\w+(?:\s+\w+)*)?\s*$/s);
111
+ if (stringMatch) {
112
+ const inner = stringMatch[1].replace(/'/g, "''");
113
+ return `'${inner}'`;
114
+ }
115
+ // Unknown expression: strip ::type and emit in parens if it looks safe
116
+ const withoutCast = raw.replace(/\s*::\s*[\w\s]+$/, "").trim();
117
+ if (withoutCast && /^[\w().\s+-]+$/i.test(withoutCast)) {
118
+ return `(${withoutCast})`;
119
+ }
120
+ return null;
121
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@technicity/data-service-generator",
3
- "version": "0.23.0-next.1",
3
+ "version": "0.23.0-next.3",
4
4
  "main": "./dist/index.js",
5
5
  "files": [
6
6
  "dist"