pecunia-cli 0.1.5 → 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-DVhIY0TJ.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 };
@@ -30,89 +30,84 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
30
30
  schema: tables,
31
31
  usePlural: adapter.options?.adapterConfig?.usePlural
32
32
  });
33
+ const tableNameMap = /* @__PURE__ */ new Map();
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 = [];
33
96
  for (const tableKey in tables) {
34
97
  const table = tables[tableKey];
35
98
  const modelName = getModelName(tableKey);
36
99
  const fields = table.fields;
37
- function getType(name, field) {
38
- name = convertToSnakeCase(name, adapter.options?.camelCase);
39
- if (field.references?.field === "id") {
40
- if (databaseType === "mysql") return `varchar('${name}', { length: 36 })`;
41
- return `text('${name}')`;
42
- }
43
- const type = field.type;
44
- if (typeof type !== "string") {
45
- if (Array.isArray(type) && type.every((x) => typeof x === "string")) return {
46
- sqlite: `text({ enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
47
- pg: `text('${name}', { enum: [${type.map((x) => `'${x}'`).join(", ")}] })`,
48
- mysql: `mysqlEnum([${type.map((x) => `'${x}'`).join(", ")}])`
49
- }[databaseType];
50
- throw new TypeError(`Invalid field type for field ${name} in model ${modelName}`);
51
- }
52
- const dbTypeMap = {
53
- string: {
54
- sqlite: `text('${name}')`,
55
- pg: `text('${name}')`,
56
- 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}')`
57
- },
58
- boolean: {
59
- sqlite: `integer('${name}', { mode: 'boolean' })`,
60
- pg: `boolean('${name}')`,
61
- mysql: `boolean('${name}')`
62
- },
63
- number: {
64
- sqlite: `integer('${name}')`,
65
- pg: field.bigint ? `bigint('${name}', { mode: 'number' })` : `integer('${name}')`,
66
- mysql: field.bigint ? `bigint('${name}', { mode: 'number' })` : `int('${name}')`
67
- },
68
- date: {
69
- sqlite: `integer('${name}', { mode: 'timestamp_ms' })`,
70
- pg: `timestamp('${name}')`,
71
- mysql: `timestamp('${name}', { fsp: 3 })`
72
- },
73
- "number[]": {
74
- sqlite: `text('${name}', { mode: "json" })`,
75
- pg: field.bigint ? `bigint('${name}', { mode: 'number' }).array()` : `integer('${name}').array()`,
76
- mysql: `text('${name}', { mode: 'json' })`
77
- },
78
- "string[]": {
79
- sqlite: `text('${name}', { mode: "json" })`,
80
- pg: `text('${name}').array()`,
81
- mysql: `text('${name}', { mode: "json" })`
82
- },
83
- json: {
84
- sqlite: `text('${name}', { mode: "json" })`,
85
- pg: `jsonb('${name}')`,
86
- mysql: `json('${name}', { mode: "json" })`
87
- },
88
- uuid: {
89
- sqlite: `text('${name}')`,
90
- pg: `uuid('${name}')`,
91
- mysql: `varchar('${name}', { length: 36 })`
92
- }
93
- }[type];
94
- if (!dbTypeMap) throw new Error(`Unsupported field type '${field.type}' for field '${name}'.`);
95
- return dbTypeMap[databaseType];
96
- }
97
100
  const idFieldType = table.fields.id?.type;
98
101
  let id;
99
102
  if (databaseType === "pg" && idFieldType === "uuid") id = `uuid('id').primaryKey()`;
100
103
  else if (databaseType === "mysql") id = `varchar('id', { length: 36 }).primaryKey()`;
101
104
  else id = `text('id').primaryKey()`;
102
105
  const indexes = [];
103
- const assignIndexes = (indexesToAssign) => {
104
- if (!indexesToAssign.length) return "";
105
- const parts = [`, (table) => [`];
106
- for (const index of indexesToAssign) parts.push(` ${index.type}("${index.name}").on(table.${index.on}),`);
107
- parts.push(`]`);
108
- return parts.join("\n");
109
- };
110
- const schema = `export const ${modelName} = ${databaseType}Table("${convertToSnakeCase(modelName, adapter.options?.camelCase)}", {
111
- id: ${id},
112
- ${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;
113
109
  const attr = fields[field];
114
110
  const fieldName = attr.fieldName || field;
115
- let type = getType(fieldName, attr);
116
111
  if (attr.index && !attr.unique) indexes.push({
117
112
  type: "index",
118
113
  name: `${modelName}_${fieldName}_idx`,
@@ -123,6 +118,63 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
123
118
  name: `${modelName}_${fieldName}_uidx`,
124
119
  on: fieldName
125
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 = "";
126
178
  if (attr.defaultValue !== null && typeof attr.defaultValue !== "undefined") if (typeof attr.defaultValue === "function") {
127
179
  if (attr.type === "date" && attr.defaultValue.toString().includes("new Date()")) if (databaseType === "sqlite") type += `.default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)`;
128
180
  else type += `.defaultNow()`;
@@ -131,11 +183,22 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
131
183
  if (attr.onUpdate && attr.type === "date") {
132
184
  if (typeof attr.onUpdate === "function") type += `.$onUpdate(${attr.onUpdate})`;
133
185
  }
134
- return `${fieldName}: ${type}${attr.required ? ".notNull()" : ""}${attr.unique ? ".unique()" : ""}${attr.references ? `.references(()=> ${getModelName(attr.references.model)}.${getFieldName({
135
- model: attr.references.model,
136
- field: attr.references.field
137
- })}, { onDelete: '${attr.references.onDelete || "cascade"}' })` : ""}`;
138
- }).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 ")}
139
202
  }${assignIndexes(indexes)});`;
140
203
  code += `\n${schema}\n`;
141
204
  }
@@ -209,7 +272,7 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
209
272
  for (const relation of duplicateRelations) {
210
273
  if (!relation.reference) continue;
211
274
  const fieldName = relation.reference.fieldName;
212
- const tableRelation = `export const ${`${modelName}${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)}Relations`} = relations(${getModelName(table.modelName)}, ({ one }) => ({
275
+ const tableRelation = `export const ${`${modelName}${fieldName.charAt(0).toUpperCase() + fieldName.slice(1)}Relations`} = relations(${modelName}, ({ one }) => ({
213
276
  ${relation.key}: one(${relation.model}, {
214
277
  fields: [${relation.reference.field}],
215
278
  references: [${relation.reference.references}],
@@ -220,7 +283,7 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
220
283
  const hasOne = singleRelations.length > 0;
221
284
  const hasMany = manyRelations.length > 0;
222
285
  if (hasOne && hasMany) {
223
- const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ one, many }) => ({
286
+ const tableRelation = `export const ${modelName}Relations = relations(${modelName}, ({ one, many }) => ({
224
287
  ${singleRelations.map((relation) => relation.reference ? ` ${relation.key}: one(${relation.model}, {
225
288
  fields: [${relation.reference.field}],
226
289
  references: [${relation.reference.references}],
@@ -229,7 +292,7 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
229
292
  }))`;
230
293
  relationsString += `\n${tableRelation}\n`;
231
294
  } else if (hasOne) {
232
- const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ one }) => ({
295
+ const tableRelation = `export const ${modelName}Relations = relations(${modelName}, ({ one }) => ({
233
296
  ${singleRelations.map((relation) => relation.reference ? ` ${relation.key}: one(${relation.model}, {
234
297
  fields: [${relation.reference.field}],
235
298
  references: [${relation.reference.references}],
@@ -237,7 +300,7 @@ const generateDrizzleSchema = async ({ options, file, adapter }) => {
237
300
  }))`;
238
301
  relationsString += `\n${tableRelation}\n`;
239
302
  } else if (hasMany) {
240
- const tableRelation = `export const ${modelName}Relations = relations(${getModelName(table.modelName)}, ({ many }) => ({
303
+ const tableRelation = `export const ${modelName}Relations = relations(${modelName}, ({ many }) => ({
241
304
  ${manyRelations.map(({ key, model }) => ` ${key}: many(${model})`).join(",\n ")}
242
305
  }))`;
243
306
  relationsString += `\n${tableRelation}\n`;
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-DVhIY0TJ.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.5",
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.8",
68
- "pecunia-root": "^0.1.6",
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",