Skip to content
This repository was archived by the owner on Mar 1, 2026. It is now read-only.

Commit e489042

Browse files
committed
fix: address PR comments
1 parent 0c244e0 commit e489042

9 files changed

Lines changed: 60 additions & 33 deletions

File tree

packages/cli/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
"dependencies": {
3939
"@dotenvx/dotenvx": "^1.51.0",
4040
"@zenstackhq/common-helpers": "workspace:*",
41-
"@zenstackhq/language": "workspace:*",
4241
"@zenstackhq/schema": "workspace:*",
4342
"@zenstackhq/language": "workspace:*",
4443
"@zenstackhq/orm": "workspace:*",

packages/cli/src/actions/db.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ async function runPull(options: PullOptions) {
149149
}
150150
// sync relation fields
151151
for (const relation of resolvedRelations) {
152-
const simmilarRelations = resolvedRelations.filter((rr) => {
152+
const similarRelations = resolvedRelations.filter((rr) => {
153153
return (
154154
rr !== relation &&
155155
((rr.schema === relation.schema &&
@@ -170,7 +170,7 @@ async function runPull(options: PullOptions) {
170170
services,
171171
options,
172172
selfRelation,
173-
simmilarRelations,
173+
similarRelations: similarRelations,
174174
});
175175
}
176176

@@ -390,8 +390,8 @@ async function runPull(options: PullOptions) {
390390
deletedFields.forEach((msg) => console.log(msg));
391391
}
392392

393-
if (options.out && !fs.lstatSync(options.out).isFile()) {
394-
throw new Error(`Output path ${options.out} is not a file`);
393+
if (options.out && fs.existsSync(options.out) && !fs.lstatSync(options.out).isFile()) {
394+
throw new Error(`Output path ${options.out} exists but is not a file`);
395395
}
396396

397397
const generator = new ZModelCodeGenerator({

packages/cli/src/actions/pull/index.ts

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,28 @@ export function syncEnums({
7676
.filter((d) => isEnum(d))
7777
.forEach((d) => {
7878
const factory = new EnumFactory().setName(d.name);
79+
// Copy enum-level comments
80+
if (d.comments?.length) {
81+
factory.update({ comments: [...d.comments] });
82+
}
83+
// Copy enum-level attributes (@@map, @@schema, etc.)
84+
if (d.attributes?.length) {
85+
factory.update({ attributes: [...d.attributes] });
86+
}
87+
// Copy fields with their attributes and comments
7988
d.fields.forEach((v) => {
80-
factory.addField((builder) => builder.setName(v.name));
89+
factory.addField((builder) => {
90+
builder.setName(v.name);
91+
// Copy field-level comments
92+
if (v.comments?.length) {
93+
v.comments.forEach((c) => builder.addComment(c));
94+
}
95+
// Copy field-level attributes (@map, etc.)
96+
if (v.attributes?.length) {
97+
builder.update({ attributes: [...v.attributes] });
98+
}
99+
return builder;
100+
});
81101
});
82102
model.declarations.push(factory.get({ $container: model }));
83103
});
@@ -322,8 +342,10 @@ export function syncTable({
322342
);
323343
}
324344

325-
const uniqueColumns = table.columns.filter((c) => c.unique || c.pk);
326-
if(uniqueColumns.length === 0) {
345+
const hasUniqueConstraint =
346+
table.columns.some((c) => c.unique || c.pk) ||
347+
table.indexes.some((i) => i.unique);
348+
if (!hasUniqueConstraint) {
327349
modelFactory.addAttribute((a) => a.setDecl(getAttributeRef('@@ignore', services)));
328350
modelFactory.comments.push(
329351
'/// The underlying table does not contain a valid unique identifier and can therefore currently not be handled by Zenstack Client.',
@@ -415,14 +437,14 @@ export function syncRelation({
415437
services,
416438
options,
417439
selfRelation,
418-
simmilarRelations,
440+
similarRelations,
419441
}: {
420442
model: Model;
421443
relation: Relation;
422444
services: ZModelServices;
423445
options: PullOptions;
424446
//self included
425-
simmilarRelations: number;
447+
similarRelations: number;
426448
selfRelation: boolean;
427449
}) {
428450
const idAttribute = getAttributeRef('@id', services);
@@ -431,7 +453,7 @@ export function syncRelation({
431453
const fieldMapAttribute = getAttributeRef('@map', services);
432454
const tableMapAttribute = getAttributeRef('@@map', services);
433455

434-
const includeRelationName = selfRelation || simmilarRelations > 0;
456+
const includeRelationName = selfRelation || similarRelations > 0;
435457

436458
if (!idAttribute || !uniqueAttribute || !relationAttribute || !fieldMapAttribute || !tableMapAttribute) {
437459
throw new Error('Cannot find required attributes in the model.');
@@ -456,15 +478,15 @@ export function syncRelation({
456478

457479
const fieldPrefix = /[0-9]/g.test(sourceModel.name.charAt(0)) ? '_' : '';
458480

459-
const relationName = `${relation.table}${simmilarRelations > 0 ? `_${relation.column}` : ''}To${relation.references.table}`;
481+
const relationName = `${relation.table}${similarRelations > 0 ? `_${relation.column}` : ''}To${relation.references.table}`;
460482

461483
const sourceNameFromReference = sourceField.name.toLowerCase().endsWith('id') ? `${resolveNameCasing("camel", sourceField.name.slice(0, -2)).name}${relation.type === 'many'? 's' : ''}` : undefined;
462484

463485
const sourceFieldFromReference = sourceModel.fields.find((f) => f.name === sourceNameFromReference);
464486

465487
let { name: sourceFieldName } = resolveNameCasing(
466488
options.fieldCasing,
467-
simmilarRelations > 0
489+
similarRelations > 0
468490
? `${fieldPrefix}${sourceModel.name.charAt(0).toLowerCase()}${sourceModel.name.slice(1)}_${relation.column}`
469491
: `${(!sourceFieldFromReference? sourceNameFromReference : undefined) || resolveNameCasing("camel", targetModel.name).name}${relation.type === 'many'? 's' : ''}`,
470492
);
@@ -523,7 +545,7 @@ export function syncRelation({
523545
const oppositeFieldPrefix = /[0-9]/g.test(targetModel.name.charAt(0)) ? '_' : '';
524546
const { name: oppositeFieldName } = resolveNameCasing(
525547
options.fieldCasing,
526-
simmilarRelations > 0
548+
similarRelations > 0
527549
? `${oppositeFieldPrefix}${sourceModel.name.charAt(0).toLowerCase()}${sourceModel.name.slice(1)}_${relation.column}`
528550
: `${resolveNameCasing("camel", sourceModel.name).name}${relation.references.type === 'many'? 's' : ''}`,
529551
);

packages/cli/src/actions/pull/provider/mysql.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ export const mysql: IntrospectionProvider = {
9595
getDefaultDatabaseType(type: BuiltinType) {
9696
switch (type) {
9797
case 'String':
98-
return { type: 'varchar', precisition: 191 };
98+
return { type: 'varchar', precision: 191 };
9999
case 'Boolean':
100100
// Boolean maps to 'boolean' (our synthetic type from tinyint(1))
101101
// No precision needed since we handle the mapping in the query
@@ -107,9 +107,9 @@ export const mysql: IntrospectionProvider = {
107107
case 'Float':
108108
return { type: 'double' };
109109
case 'Decimal':
110-
return { type: 'decimal', precisition: 65 };
110+
return { type: 'decimal', precision: 65 };
111111
case 'DateTime':
112-
return { type: 'datetime', precisition: 3 };
112+
return { type: 'datetime', precision: 3 };
113113
case 'Json':
114114
return { type: 'json' };
115115
case 'Bytes':
@@ -338,8 +338,8 @@ export const mysql: IntrospectionProvider = {
338338
dbAttr &&
339339
defaultDatabaseType &&
340340
(defaultDatabaseType.type !== datatype ||
341-
(defaultDatabaseType.precisition &&
342-
defaultDatabaseType.precisition !== (length || precision)))
341+
(defaultDatabaseType.precision &&
342+
defaultDatabaseType.precision !== (length || precision)))
343343
) {
344344
const dbAttrFactory = new DataFieldAttributeFactory().setDecl(dbAttr);
345345
if (length || precision) {

packages/cli/src/actions/pull/provider/postgresql.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ export const postgresql: IntrospectionProvider = {
103103
case 'Decimal':
104104
return { type: 'decimal' };
105105
case 'DateTime':
106-
return { type: 'timestamp', precisition: 3 };
106+
return { type: 'timestamp', precision: 3 };
107107
case 'Json':
108108
return { type: 'jsonb' };
109109
case 'Bytes':
@@ -246,8 +246,8 @@ export const postgresql: IntrospectionProvider = {
246246
dbAttr &&
247247
defaultDatabaseType &&
248248
(defaultDatabaseType.type !== datatype ||
249-
(defaultDatabaseType.precisition &&
250-
defaultDatabaseType.precisition !== (length || precision)))
249+
(defaultDatabaseType.precision &&
250+
defaultDatabaseType.precision !== (length || precision)))
251251
) {
252252
const dbAttrFactory = new DataFieldAttributeFactory().setDecl(dbAttr);
253253
if (length || precision) {

packages/cli/src/actions/pull/provider/provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export interface IntrospectionProvider {
6666
type: BuiltinType | 'Unsupported';
6767
isArray: boolean;
6868
};
69-
getDefaultDatabaseType(type: BuiltinType): { precisition?: number; type: string } | undefined;
69+
getDefaultDatabaseType(type: BuiltinType): { precision?: number; type: string } | undefined;
7070
/**
7171
* Get the expression builder callback for a field's @default attribute value.
7272
* Returns null if no @default attribute should be added.

packages/cli/src/actions/pull/provider/sqlite.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export const sqlite: IntrospectionProvider = {
115115

116116
// Unique columns detection via unique indexes with single column
117117
const uniqueSingleColumn = new Set<string>();
118-
const uniqueIndexRows = idxList.filter((r) => r.unique === 1);
118+
const uniqueIndexRows = idxList.filter((r) => r.unique === 1 && r.partial !== 1);
119119
for (const idx of uniqueIndexRows) {
120120
const idxCols = all<{ name: string }>(`PRAGMA index_info('${idx.name.replace(/'/g, "''")}')`);
121121
if (idxCols.length === 1 && idxCols[0]?.name) {
@@ -134,7 +134,7 @@ export const sqlite: IntrospectionProvider = {
134134
valid: true, // SQLite does not expose index validity
135135
ready: true, // SQLite does not expose index readiness
136136
partial: idx.partial === 1,
137-
predicate: null, // SQLite does not expose index predicate
137+
predicate: idx.partial === 1 ? '[partial]' : null, // SQLite does not expose index predicate
138138
columns: idxCols.map((col) => ({
139139
name: col.name,
140140
expression: null,
@@ -363,8 +363,8 @@ export const sqlite: IntrospectionProvider = {
363363
dbAttr &&
364364
defaultDatabaseType &&
365365
(defaultDatabaseType.type !== datatype ||
366-
(defaultDatabaseType.precisition &&
367-
defaultDatabaseType.precisition !== (length || precision)))
366+
(defaultDatabaseType.precision &&
367+
defaultDatabaseType.precision !== (length || precision)))
368368
) {
369369
const dbAttrFactory = new DataFieldAttributeFactory().setDecl(dbAttr);
370370
if (length || precision) {

packages/cli/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,12 @@ function createProgram() {
150150
.addOption(noVersionCheckOption)
151151
.addOption(new Option('-o, --out <path>', 'add custom output path for the introspected schema'))
152152
.addOption(
153-
new Option('--model-casing <pascal|camel|snake|kebab>', 'set the casing of generated models').default(
153+
new Option('--model-casing <pascal|camel|snake|kebab|none>', 'set the casing of generated models').default(
154154
'none',
155155
),
156156
)
157157
.addOption(
158-
new Option('--field-casing <pascal|camel|snake|kebab>', 'set the casing of generated fields').default(
158+
new Option('--field-casing <pascal|camel|snake|kebab|none>', 'set the casing of generated fields').default(
159159
'none',
160160
),
161161
)

packages/language/src/factory/attribute.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,12 @@ export class DataFieldAttributeFactory extends AstFactory<DataFieldAttribute> {
2121
super({ type: DataFieldAttribute, node: { args: [] } });
2222
}
2323
setDecl(decl: Attribute) {
24+
if (!decl) {
25+
throw new Error('Attribute declaration is required');
26+
}
2427
this.decl = {
25-
$refText: decl?.name ?? '',
26-
ref: decl!,
28+
$refText: decl.name,
29+
ref: decl,
2730
};
2831
this.update({
2932
decl: this.decl,
@@ -50,9 +53,12 @@ export class DataModelAttributeFactory extends AstFactory<DataModelAttribute> {
5053
super({ type: DataModelAttribute, node: { args: [] } });
5154
}
5255
setDecl(decl: Attribute) {
56+
if (!decl) {
57+
throw new Error('Attribute declaration is required');
58+
}
5359
this.decl = {
54-
$refText: decl?.name ?? '',
55-
ref: decl!,
60+
$refText: decl.name,
61+
ref: decl,
5662
};
5763
this.update({
5864
decl: this.decl,

0 commit comments

Comments
 (0)