pecunia-cli 0.1.6 → 0.1.7

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.
package/dist/api.mjs CHANGED
@@ -1,3 +1,3 @@
1
- import { a as generateKyselySchema, n as generateSchema, o as generateDrizzleSchema, r as generatePrismaSchema, t as adapters } from "./generators-BSLjSGA9.mjs";
1
+ import { a as generateKyselySchema, n as generateSchema, o as generateDrizzleSchema, r as generatePrismaSchema, t as adapters } from "./generators-BhGuoXaD.mjs";
2
2
 
3
3
  export { adapters, generateDrizzleSchema, generateKyselySchema, generatePrismaSchema, generateSchema };
@@ -32,89 +32,82 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
32
32
  });
33
33
  const tableNameMap = /* @__PURE__ */ new Map();
34
34
  for (const tableKey in tables) tableNameMap.set(tableKey, getModelName(tableKey));
35
+ function getType(name, field, databaseType$1) {
36
+ name = convertToSnakeCase(name, adapter.options?.camelCase);
37
+ if (field.references?.field === "id") {
38
+ if (databaseType$1 === "mysql") return `varchar('${name}', { length: 36 })`;
39
+ return `text('${name}')`;
40
+ }
41
+ const type = field.type;
42
+ if (typeof type !== "string") {
43
+ if (Array.isArray(type) && type.every((x) => typeof x === "string")) return {
44
+ sqlite: `text({ enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
45
+ pg: `text('${name}', { enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
46
+ mysql: `mysqlEnum([${type.map((x) => `'${x}'`).join(", ")}])`
47
+ }[databaseType$1];
48
+ throw new TypeError(`Invalid field type for field ${name}`);
49
+ }
50
+ const dbTypeMap = {
51
+ string: {
52
+ sqlite: `text('${name}')`,
53
+ pg: `text('${name}')`,
54
+ mysql: field.unique ? `varchar('${name}', { length: 255 })` : field.references ? `varchar('${name}', { length: 36 })` : field.sortable ? `varchar('${name}', { length: 255 })` : field.index ? `varchar('${name}', { length: 255 })` : `text('${name}')`
55
+ },
56
+ boolean: {
57
+ sqlite: `integer('${name}', { mode: 'boolean' })`,
58
+ pg: `boolean('${name}')`,
59
+ mysql: `boolean('${name}')`
60
+ },
61
+ number: {
62
+ sqlite: `integer('${name}')`,
63
+ pg: field.bigint ? `bigint('${name}', { mode: 'number' })` : `integer('${name}')`,
64
+ mysql: field.bigint ? `bigint('${name}', { mode: 'number' })` : `int('${name}')`
65
+ },
66
+ date: {
67
+ sqlite: `integer('${name}', { mode: 'timestamp_ms' })`,
68
+ pg: `timestamp('${name}')`,
69
+ mysql: `timestamp('${name}', { fsp: 3 })`
70
+ },
71
+ "number[]": {
72
+ sqlite: `text('${name}', { mode: "json" })`,
73
+ pg: field.bigint ? `bigint('${name}', { mode: 'number' }).array()` : `integer('${name}').array()`,
74
+ mysql: `text('${name}', { mode: 'json' })`
75
+ },
76
+ "string[]": {
77
+ sqlite: `text('${name}', { mode: "json" })`,
78
+ pg: `text('${name}').array()`,
79
+ mysql: `text('${name}', { mode: "json" })`
80
+ },
81
+ json: {
82
+ sqlite: `text('${name}', { mode: "json" })`,
83
+ pg: `jsonb('${name}')`,
84
+ mysql: `json('${name}', { mode: "json" })`
85
+ },
86
+ uuid: {
87
+ sqlite: `text('${name}')`,
88
+ pg: `uuid('${name}')`,
89
+ mysql: `varchar('${name}', { length: 36 })`
90
+ }
91
+ }[type];
92
+ if (!dbTypeMap) throw new Error(`Unsupported field type '${field.type}' for field '${name}'.`);
93
+ return dbTypeMap[databaseType$1];
94
+ }
95
+ const tableDefinitions = [];
35
96
  for (const tableKey in tables) {
36
97
  const table = tables[tableKey];
37
98
  const modelName = getModelName(tableKey);
38
99
  const fields = table.fields;
39
- function getType(name, field) {
40
- name = convertToSnakeCase(name, adapter.options?.camelCase);
41
- if (field.references?.field === "id") {
42
- if (databaseType === "mysql") return `varchar('${name}', { length: 36 })`;
43
- return `text('${name}')`;
44
- }
45
- const type = field.type;
46
- if (typeof type !== "string") {
47
- if (Array.isArray(type) && type.every((x) => typeof x === "string")) return {
48
- sqlite: `text({ enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
49
- pg: `text('${name}', { enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
50
- mysql: `mysqlEnum([${type.map((x) => `'${x}'`).join(", ")}])`
51
- }[databaseType];
52
- throw new TypeError(`Invalid field type for field ${name} in model ${modelName}`);
53
- }
54
- const dbTypeMap = {
55
- string: {
56
- sqlite: `text('${name}')`,
57
- pg: `text('${name}')`,
58
- mysql: field.unique ? `varchar('${name}', { length: 255 })` : field.references ? `varchar('${name}', { length: 36 })` : field.sortable ? `varchar('${name}', { length: 255 })` : field.index ? `varchar('${name}', { length: 255 })` : `text('${name}')`
59
- },
60
- boolean: {
61
- sqlite: `integer('${name}', { mode: 'boolean' })`,
62
- pg: `boolean('${name}')`,
63
- mysql: `boolean('${name}')`
64
- },
65
- number: {
66
- sqlite: `integer('${name}')`,
67
- pg: field.bigint ? `bigint('${name}', { mode: 'number' })` : `integer('${name}')`,
68
- mysql: field.bigint ? `bigint('${name}', { mode: 'number' })` : `int('${name}')`
69
- },
70
- date: {
71
- sqlite: `integer('${name}', { mode: 'timestamp_ms' })`,
72
- pg: `timestamp('${name}')`,
73
- mysql: `timestamp('${name}', { fsp: 3 })`
74
- },
75
- "number[]": {
76
- sqlite: `text('${name}', { mode: "json" })`,
77
- pg: field.bigint ? `bigint('${name}', { mode: 'number' }).array()` : `integer('${name}').array()`,
78
- mysql: `text('${name}', { mode: 'json' })`
79
- },
80
- "string[]": {
81
- sqlite: `text('${name}', { mode: "json" })`,
82
- pg: `text('${name}').array()`,
83
- mysql: `text('${name}', { mode: "json" })`
84
- },
85
- json: {
86
- sqlite: `text('${name}', { mode: "json" })`,
87
- pg: `jsonb('${name}')`,
88
- mysql: `json('${name}', { mode: "json" })`
89
- },
90
- uuid: {
91
- sqlite: `text('${name}')`,
92
- pg: `uuid('${name}')`,
93
- mysql: `varchar('${name}', { length: 36 })`
94
- }
95
- }[type];
96
- if (!dbTypeMap) throw new Error(`Unsupported field type '${field.type}' for field '${name}'.`);
97
- return dbTypeMap[databaseType];
98
- }
99
100
  const idFieldType = table.fields.id?.type;
100
101
  let id;
101
102
  if (databaseType === "pg" && idFieldType === "uuid") id = `uuid('id').primaryKey()`;
102
103
  else if (databaseType === "mysql") id = `varchar('id', { length: 36 }).primaryKey()`;
103
104
  else id = `text('id').primaryKey()`;
104
105
  const indexes = [];
105
- const assignIndexes = (indexesToAssign) => {
106
- if (!indexesToAssign.length) return "";
107
- const parts = [`, (table) => [`];
108
- for (const index of indexesToAssign) parts.push(` ${index.type}("${index.name}").on(table.${index.on}),`);
109
- parts.push(`]`);
110
- return parts.join("\n");
111
- };
112
- const schema = `export const ${modelName} = ${databaseType}Table("${convertToSnakeCase(modelName, adapter.options?.camelCase)}", {
113
- id: ${id},
114
- ${Object.keys(fields).filter((field) => field !== "id").map((field) => {
106
+ const references = [];
107
+ for (const field of Object.keys(fields)) {
108
+ if (field === "id") continue;
115
109
  const attr = fields[field];
116
110
  const fieldName = attr.fieldName || field;
117
- let type = getType(fieldName, attr);
118
111
  if (attr.index && !attr.unique) indexes.push({
119
112
  type: "index",
120
113
  name: `${modelName}_${fieldName}_idx`,
@@ -125,6 +118,63 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
125
118
  name: `${modelName}_${fieldName}_uidx`,
126
119
  on: fieldName
127
120
  });
121
+ if (attr.references) {
122
+ const referencedModelName = tableNameMap.get(attr.references.model) || getModelName(attr.references.model);
123
+ references.push({
124
+ fieldName,
125
+ referencedTable: referencedModelName,
126
+ referencedField: getFieldName({
127
+ model: attr.references.model,
128
+ field: attr.references.field
129
+ }),
130
+ onDelete: attr.references.onDelete || "cascade",
131
+ required: attr.required || false,
132
+ originalModel: attr.references.model
133
+ });
134
+ }
135
+ }
136
+ tableDefinitions.push({
137
+ modelName,
138
+ tableKey,
139
+ fields,
140
+ id,
141
+ indexes,
142
+ references
143
+ });
144
+ }
145
+ const referenceGraph = /* @__PURE__ */ new Map();
146
+ for (const tableDef of tableDefinitions) for (const ref of tableDef.references) {
147
+ const key = `${tableDef.tableKey}->${ref.originalModel}`;
148
+ referenceGraph.set(key, {
149
+ ...ref,
150
+ sourceTable: tableDef.tableKey,
151
+ sourceModelName: tableDef.modelName
152
+ });
153
+ }
154
+ const skipReferences = /* @__PURE__ */ new Set();
155
+ for (const tableDef of tableDefinitions) for (const ref of tableDef.references) {
156
+ const reverseKey = `${ref.originalModel}->${tableDef.tableKey}`;
157
+ const reverseRef = referenceGraph.get(reverseKey);
158
+ if (reverseRef) {
159
+ if (!ref.required && ref.onDelete === "set null" && (reverseRef.required || reverseRef.onDelete !== "set null")) skipReferences.add(`${tableDef.tableKey}.${ref.fieldName}`);
160
+ }
161
+ }
162
+ for (const tableDef of tableDefinitions) {
163
+ const { modelName, fields, id, indexes, references } = tableDef;
164
+ const assignIndexes = (indexesToAssign) => {
165
+ if (!indexesToAssign.length) return "";
166
+ const parts = [`, (table) => [`];
167
+ for (const index of indexesToAssign) parts.push(` ${index.type}("${index.name}").on(table.${index.on}),`);
168
+ parts.push(`]`);
169
+ return parts.join("\n");
170
+ };
171
+ const referenceMap = /* @__PURE__ */ new Map();
172
+ for (const ref of references) referenceMap.set(ref.fieldName, ref);
173
+ const fieldDefinitions = Object.keys(fields).filter((field) => field !== "id").map((field) => {
174
+ const attr = fields[field];
175
+ const fieldName = attr.fieldName || field;
176
+ let type = getType(fieldName, attr, databaseType);
177
+ let comment = "";
128
178
  if (attr.defaultValue !== null && typeof attr.defaultValue !== "undefined") if (typeof attr.defaultValue === "function") {
129
179
  if (attr.type === "date" && attr.defaultValue.toString().includes("new Date()")) if (databaseType === "sqlite") type += `.default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)`;
130
180
  else type += `.defaultNow()`;
@@ -133,11 +183,22 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
133
183
  if (attr.onUpdate && attr.type === "date") {
134
184
  if (typeof attr.onUpdate === "function") type += `.$onUpdate(${attr.onUpdate})`;
135
185
  }
136
- return `${fieldName}: ${type}${attr.required ? ".notNull()" : ""}${attr.unique ? ".unique()" : ""}${attr.references ? `.references(()=> ${tableNameMap.get(attr.references.model) || getModelName(attr.references.model)}.${getFieldName({
137
- model: attr.references.model,
138
- field: attr.references.field
139
- })}, { onDelete: '${attr.references.onDelete || "cascade"}' })` : ""}`;
140
- }).join(",\n ")}
186
+ const ref = referenceMap.get(fieldName);
187
+ const shouldSkipReference = skipReferences.has(`${tableDef.tableKey}.${fieldName}`);
188
+ let referenceChain = "";
189
+ if (ref && !shouldSkipReference) referenceChain = `.references(() => ${ref.referencedTable}.${ref.referencedField}, { onDelete: '${ref.onDelete}' })`;
190
+ else if (ref && shouldSkipReference) {
191
+ const reverseKey = `${ref.originalModel}->${tableDef.tableKey}`;
192
+ const reverseRef = referenceGraph.get(reverseKey);
193
+ if (reverseRef) comment = `\n // FK constraint removed to break circular dependency with ${ref.referencedTable}\n // Primary FK: ${reverseRef.sourceModelName}.${reverseRef.fieldName} -> ${modelName}.${fieldName}\n // This field still maintains referential integrity via application logic and Drizzle relations`;
194
+ else comment = `\n // FK constraint removed to break circular dependency with ${ref.referencedTable}\n // This field still maintains referential integrity via application logic and Drizzle relations`;
195
+ }
196
+ const fieldDef = `${fieldName}: ${type}${attr.required ? ".notNull()" : ""}${attr.unique ? ".unique()" : ""}${referenceChain}`;
197
+ return comment ? `${comment}\n ${fieldDef}` : fieldDef;
198
+ });
199
+ const schema = `export const ${modelName} = ${databaseType}Table("${convertToSnakeCase(modelName, adapter.options?.camelCase)}", {
200
+ id: ${id},
201
+ ${fieldDefinitions.join(",\n ")}
141
202
  }${assignIndexes(indexes)});`;
142
203
  code += `\n${schema}\n`;
143
204
  }
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { i as getPackageInfo, n as generateSchema } from "./generators-BSLjSGA9.mjs";
2
+ import { i as getPackageInfo, n as generateSchema } from "./generators-BhGuoXaD.mjs";
3
3
  import { Command } from "commander";
4
4
  import fs, { existsSync, readFileSync } from "node:fs";
5
5
  import fs$1 from "node:fs/promises";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pecunia-cli",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "type": "module",
5
5
  "module": "dist/index.mjs",
6
6
  "main": "./dist/index.mjs",
@@ -64,8 +64,8 @@
64
64
  "dotenv": "^17.2.2",
65
65
  "drizzle-orm": "^0.33.0",
66
66
  "open": "^10.2.0",
67
- "pecunia-core": "^0.0.9",
68
- "pecunia-root": "^0.1.7",
67
+ "pecunia-core": "^0.1.0",
68
+ "pecunia-root": "^0.1.8",
69
69
  "pg": "^8.16.3",
70
70
  "prettier": "^3.6.2",
71
71
  "prompts": "^2.4.2",