@an5/orm 1.0.7 → 1.0.9

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 (46) hide show
  1. package/README.md +97 -83
  2. package/dist/an5Metadata.d.ts +10 -0
  3. package/dist/an5Metadata.js +93 -0
  4. package/dist/an5Orm.d.ts +95 -3
  5. package/dist/an5Orm.js +808 -512
  6. package/dist/config.d.ts +2 -0
  7. package/dist/config.js +24 -0
  8. package/dist/errors.d.ts +14 -0
  9. package/dist/errors.js +16 -0
  10. package/dist/generator/src/code-generator.d.ts +1 -0
  11. package/dist/generator/src/code-generator.js +44 -20
  12. package/dist/generator/src/dotnet-generator.d.ts +2 -0
  13. package/dist/generator/src/dotnet-generator.js +332 -6
  14. package/dist/generator/src/index.js +18 -2
  15. package/dist/generator/src/metadata-generator.d.ts +4 -1
  16. package/dist/generator/src/metadata-generator.js +22 -8
  17. package/dist/generator/src/python-generator.d.ts +10 -0
  18. package/dist/generator/src/python-generator.js +242 -2
  19. package/dist/index.d.ts +6 -2
  20. package/dist/index.js +23 -8
  21. package/dist/logger.d.ts +53 -0
  22. package/dist/logger.js +117 -0
  23. package/dist/metadata.d.ts +24 -0
  24. package/dist/metadata.js +15 -0
  25. package/dist/migration-core.d.ts +87 -0
  26. package/dist/migration-core.js +692 -0
  27. package/dist/sql-utils.d.ts +31 -0
  28. package/dist/sql-utils.js +290 -0
  29. package/generator/src/code-generator.ts +45 -20
  30. package/generator/src/dotnet-generator.ts +344 -6
  31. package/generator/src/golang-generator.ts +1242 -0
  32. package/generator/src/index.ts +8 -1
  33. package/generator/src/metadata-generator.ts +29 -12
  34. package/generator/src/python-generator.ts +314 -4
  35. package/package.json +12 -10
  36. package/dist/cleanup.d.ts +0 -2
  37. package/dist/cleanup.js +0 -120
  38. package/dist/cli.js +0 -64
  39. package/dist/migrate.d.ts +0 -11
  40. package/dist/migrate.js +0 -301
  41. package/dist/pull.d.ts +0 -2
  42. package/dist/pull.js +0 -339
  43. package/dist/push.d.ts +0 -2
  44. package/dist/push.js +0 -281
  45. package/dist/seed.d.ts +0 -2
  46. package/dist/seed.js +0 -401
@@ -0,0 +1,31 @@
1
+ import type { RelationDef } from "./metadata";
2
+ /**
3
+ * SQL Server identifier quoting.
4
+ *
5
+ * Wraps a bare identifier in square brackets and escapes any embedded
6
+ * closing bracket (`]` → `]]`), which is the only character that can break
7
+ * out of a bracketed identifier in T-SQL. This blocks SQL injection through
8
+ * user-supplied column / alias names.
9
+ */
10
+ export declare function quoteIdentifier(name: string): string;
11
+ /** Quotes one-part or multipart SQL Server table identifiers safely. */
12
+ export declare function quoteTableIdentifier(name: string): string;
13
+ /** Sanitizes a parameter name to a safe T-SQL identifier fragment. */
14
+ export declare function sanitizeParamName(name: string): string;
15
+ /** Validates a direction token, defaulting to ASC. */
16
+ export declare function normalizeSortDirection(dir: unknown): "ASC" | "DESC";
17
+ /** Coerces a value to a non-negative integer, or returns the fallback. */
18
+ export declare function toNonNegativeInt(value: unknown, fallback?: number): number;
19
+ export interface QueryContext {
20
+ relationMap?: Record<string, Record<string, RelationDef>>;
21
+ modelToTable?: Record<string, string>;
22
+ }
23
+ export declare function buildOrderBy(orderBy: any): string;
24
+ /**
25
+ * Recursively builds a WHERE clause from a structured filter object.
26
+ *
27
+ * Values are always bound as parameters (`@name`); only identifiers are
28
+ * interpolated directly, and they are run through {@link quoteIdentifier}.
29
+ */
30
+ export declare function parseWhere(modelName: string, where: any, params: Record<string, any>, prefix?: string, ctx?: QueryContext): string;
31
+ //# sourceMappingURL=sql-utils.d.ts.map
@@ -0,0 +1,290 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.quoteIdentifier = quoteIdentifier;
4
+ exports.quoteTableIdentifier = quoteTableIdentifier;
5
+ exports.sanitizeParamName = sanitizeParamName;
6
+ exports.normalizeSortDirection = normalizeSortDirection;
7
+ exports.toNonNegativeInt = toNonNegativeInt;
8
+ exports.buildOrderBy = buildOrderBy;
9
+ exports.parseWhere = parseWhere;
10
+ /**
11
+ * SQL Server identifier quoting.
12
+ *
13
+ * Wraps a bare identifier in square brackets and escapes any embedded
14
+ * closing bracket (`]` → `]]`), which is the only character that can break
15
+ * out of a bracketed identifier in T-SQL. This blocks SQL injection through
16
+ * user-supplied column / alias names.
17
+ */
18
+ function quoteIdentifier(name) {
19
+ const s = String(name);
20
+ return `[${s.replace(/\]/g, "]]")}]`;
21
+ }
22
+ function unquoteBracketIdentifier(part) {
23
+ const trimmed = String(part).trim();
24
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
25
+ return trimmed.slice(1, -1).replace(/\]\]/g, "]");
26
+ }
27
+ return trimmed;
28
+ }
29
+ function splitMultipartIdentifier(name) {
30
+ const parts = [];
31
+ let current = "";
32
+ let inBracket = false;
33
+ const input = String(name).trim();
34
+ for (let i = 0; i < input.length; i += 1) {
35
+ const ch = input[i];
36
+ if (ch === "[")
37
+ inBracket = true;
38
+ if (ch === "]") {
39
+ if (input[i + 1] === "]") {
40
+ current += "]]";
41
+ i += 1;
42
+ continue;
43
+ }
44
+ inBracket = false;
45
+ }
46
+ if (ch === "." && !inBracket) {
47
+ if (current.trim())
48
+ parts.push(current.trim());
49
+ current = "";
50
+ continue;
51
+ }
52
+ current += ch;
53
+ }
54
+ if (current.trim())
55
+ parts.push(current.trim());
56
+ return parts.length > 0 ? parts : [input];
57
+ }
58
+ /** Quotes one-part or multipart SQL Server table identifiers safely. */
59
+ function quoteTableIdentifier(name) {
60
+ return splitMultipartIdentifier(name)
61
+ .map(part => quoteIdentifier(unquoteBracketIdentifier(part)))
62
+ .join(".");
63
+ }
64
+ /** Sanitizes a parameter name to a safe T-SQL identifier fragment. */
65
+ function sanitizeParamName(name) {
66
+ const s = String(name);
67
+ const cleaned = s.replace(/[^A-Za-z0-9_]/g, "_");
68
+ return /^[A-Za-z]/.test(cleaned) ? cleaned : `p_${cleaned}`;
69
+ }
70
+ /** Validates a direction token, defaulting to ASC. */
71
+ function normalizeSortDirection(dir) {
72
+ const upper = typeof dir === "string" ? dir.toUpperCase() : "";
73
+ return upper === "DESC" ? "DESC" : "ASC";
74
+ }
75
+ /** Coerces a value to a non-negative integer, or returns the fallback. */
76
+ function toNonNegativeInt(value, fallback = 0) {
77
+ const n = Number.parseInt(String(value), 10);
78
+ return Number.isFinite(n) && n >= 0 ? n : fallback;
79
+ }
80
+ const OPERATOR_KEYS = ["equals", "in", "notIn", "contains", "startsWith", "endsWith", "not", "gte", "lte", "gt", "lt"];
81
+ function isOperatorValue(value) {
82
+ if (!value || typeof value !== "object" || value instanceof Date)
83
+ return false;
84
+ return OPERATOR_KEYS.some((op) => op in value);
85
+ }
86
+ function buildOrderBy(orderBy) {
87
+ if (!orderBy)
88
+ return "";
89
+ const orderClauses = [];
90
+ const orderByArr = Array.isArray(orderBy) ? orderBy : [orderBy];
91
+ for (const orderObj of orderByArr) {
92
+ if (orderObj && typeof orderObj === "object") {
93
+ for (const [k, dir] of Object.entries(orderObj)) {
94
+ orderClauses.push(`${quoteIdentifier(k)} ${normalizeSortDirection(dir)}`);
95
+ }
96
+ }
97
+ }
98
+ return orderClauses.length > 0 ? ` ORDER BY ${orderClauses.join(", ")}` : "";
99
+ }
100
+ /**
101
+ * Recursively builds a WHERE clause from a structured filter object.
102
+ *
103
+ * Values are always bound as parameters (`@name`); only identifiers are
104
+ * interpolated directly, and they are run through {@link quoteIdentifier}.
105
+ */
106
+ function parseWhere(modelName, where, params, prefix = "", ctx = {}) {
107
+ if (!where)
108
+ return "";
109
+ const conditions = [];
110
+ const relationMap = ctx.relationMap || {};
111
+ const modelToTable = ctx.modelToTable || {};
112
+ const cleanWhere = {};
113
+ for (const [key, value] of Object.entries(where)) {
114
+ if (key.includes("_") && value && typeof value === "object" && !(value instanceof Date) && !isOperatorValue(value)) {
115
+ Object.assign(cleanWhere, value);
116
+ }
117
+ else {
118
+ cleanWhere[key] = value;
119
+ }
120
+ }
121
+ for (const [key, value] of Object.entries(cleanWhere)) {
122
+ if (key === "OR" && Array.isArray(value)) {
123
+ const orConditions = value.map((subWhere, idx) => parseWhere(modelName, subWhere, params, `${prefix}or_${idx}_`, ctx));
124
+ const filtered = orConditions.filter(Boolean);
125
+ if (filtered.length > 0) {
126
+ conditions.push(`(${filtered.join(" OR ")})`);
127
+ }
128
+ }
129
+ else if (key === "AND") {
130
+ const andItems = Array.isArray(value) ? value : [value];
131
+ const andConditions = andItems.map((subWhere, idx) => parseWhere(modelName, subWhere, params, `${prefix}and_${idx}_`, ctx));
132
+ const filtered = andConditions.filter(Boolean);
133
+ if (filtered.length > 0) {
134
+ conditions.push(`(${filtered.join(" AND ")})`);
135
+ }
136
+ }
137
+ else if (key === "NOT") {
138
+ const notItems = Array.isArray(value) ? value : [value];
139
+ const notConditions = notItems.map((subWhere, idx) => parseWhere(modelName, subWhere, params, `${prefix}not_${idx}_`, ctx));
140
+ const filtered = notConditions.filter(Boolean);
141
+ if (filtered.length > 0) {
142
+ conditions.push(`NOT (${filtered.join(" AND ")})`);
143
+ }
144
+ }
145
+ else {
146
+ const modelRelations = relationMap[modelName];
147
+ const relation = modelRelations?.[key];
148
+ if (relation) {
149
+ const relationTable = quoteTableIdentifier(modelToTable[relation.modelName] || relation.modelName);
150
+ const subParams = {};
151
+ let subWhere = value;
152
+ let op = "some";
153
+ if (value && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date)) {
154
+ if (value.some) {
155
+ subWhere = value.some;
156
+ op = "some";
157
+ }
158
+ else if (value.none) {
159
+ subWhere = value.none;
160
+ op = "none";
161
+ }
162
+ else if (value.every) {
163
+ subWhere = value.every;
164
+ op = "every";
165
+ }
166
+ }
167
+ const subWhereSql = parseWhere(relation.modelName, subWhere, subParams, `${prefix}${key}_`, ctx);
168
+ Object.assign(params, subParams);
169
+ const fk = quoteIdentifier(relation.foreignKey);
170
+ const lk = quoteIdentifier(relation.localKey);
171
+ const outerKey = relation.relationType === "one" ? fk : lk;
172
+ const innerKey = relation.relationType === "one" ? lk : fk;
173
+ if (op === "every") {
174
+ if (subWhereSql) {
175
+ conditions.push(`${outerKey} NOT IN (SELECT ${innerKey} FROM ${relationTable} WITH (NOLOCK) WHERE NOT (${subWhereSql}))`);
176
+ }
177
+ else {
178
+ conditions.push("1 = 1");
179
+ }
180
+ }
181
+ else {
182
+ const relationPredicate = subWhereSql ? ` WHERE ${subWhereSql}` : "";
183
+ const operator = op === "none" ? "NOT IN" : "IN";
184
+ conditions.push(`${outerKey} ${operator} (SELECT ${innerKey} FROM ${relationTable} WITH (NOLOCK)${relationPredicate})`);
185
+ }
186
+ }
187
+ else {
188
+ const col = quoteIdentifier(key);
189
+ const paramName = sanitizeParamName(`${prefix}${key}`);
190
+ if (value && typeof value === "object" && !(value instanceof Date)) {
191
+ const ops = Object.entries(value);
192
+ for (const [op, opVal] of ops) {
193
+ if (op === "equals") {
194
+ if (opVal === null) {
195
+ conditions.push(`${col} IS NULL`);
196
+ }
197
+ else {
198
+ conditions.push(`${col} = @${paramName}_equals`);
199
+ params[`${paramName}_equals`] = opVal;
200
+ }
201
+ }
202
+ else if (op === "in" && Array.isArray(opVal)) {
203
+ if (opVal.length === 0) {
204
+ conditions.push("1 = 0");
205
+ }
206
+ else {
207
+ const inParams = [];
208
+ opVal.forEach((item, idx) => {
209
+ const inParamName = `${paramName}_in_${idx}`;
210
+ inParams.push(`@${inParamName}`);
211
+ params[inParamName] = item;
212
+ });
213
+ conditions.push(`${col} IN (${inParams.join(", ")})`);
214
+ }
215
+ }
216
+ else if (op === "notIn" && Array.isArray(opVal)) {
217
+ if (opVal.length === 0) {
218
+ conditions.push("1 = 1");
219
+ }
220
+ else {
221
+ const inParams = [];
222
+ opVal.forEach((item, idx) => {
223
+ const inParamName = `${paramName}_notin_${idx}`;
224
+ inParams.push(`@${inParamName}`);
225
+ params[inParamName] = item;
226
+ });
227
+ conditions.push(`${col} NOT IN (${inParams.join(", ")})`);
228
+ }
229
+ }
230
+ else if (op === "contains") {
231
+ conditions.push(`${col} LIKE @${paramName}_contains`);
232
+ params[`${paramName}_contains`] = `%${opVal}%`;
233
+ }
234
+ else if (op === "startsWith") {
235
+ conditions.push(`${col} LIKE @${paramName}_startsWith`);
236
+ params[`${paramName}_startsWith`] = `${opVal}%`;
237
+ }
238
+ else if (op === "endsWith") {
239
+ conditions.push(`${col} LIKE @${paramName}_endsWith`);
240
+ params[`${paramName}_endsWith`] = `%${opVal}`;
241
+ }
242
+ else if (op === "not") {
243
+ if (opVal === null) {
244
+ conditions.push(`${col} IS NOT NULL`);
245
+ }
246
+ else if (opVal && typeof opVal === "object" && !(opVal instanceof Date) && !Array.isArray(opVal)) {
247
+ const nestedParams = {};
248
+ const nestedSql = parseWhere(modelName, { [key]: opVal }, nestedParams, `${prefix}${key}_not_`, ctx);
249
+ Object.assign(params, nestedParams);
250
+ if (nestedSql)
251
+ conditions.push(`NOT (${nestedSql})`);
252
+ }
253
+ else {
254
+ conditions.push(`${col} <> @${paramName}_not`);
255
+ params[`${paramName}_not`] = opVal;
256
+ }
257
+ }
258
+ else if (op === "gte") {
259
+ conditions.push(`${col} >= @${paramName}_gte`);
260
+ params[`${paramName}_gte`] = opVal;
261
+ }
262
+ else if (op === "lte") {
263
+ conditions.push(`${col} <= @${paramName}_lte`);
264
+ params[`${paramName}_lte`] = opVal;
265
+ }
266
+ else if (op === "gt") {
267
+ conditions.push(`${col} > @${paramName}_gt`);
268
+ params[`${paramName}_gt`] = opVal;
269
+ }
270
+ else if (op === "lt") {
271
+ conditions.push(`${col} < @${paramName}_lt`);
272
+ params[`${paramName}_lt`] = opVal;
273
+ }
274
+ }
275
+ }
276
+ else {
277
+ if (value === null) {
278
+ conditions.push(`${col} IS NULL`);
279
+ }
280
+ else {
281
+ conditions.push(`${col} = @${paramName}`);
282
+ params[paramName] = value;
283
+ }
284
+ }
285
+ }
286
+ }
287
+ }
288
+ return conditions.join(" AND ");
289
+ }
290
+ //# sourceMappingURL=sql-utils.js.map
@@ -19,15 +19,11 @@ export class CodeGenerator {
19
19
 
20
20
  private generateBaseTs() {
21
21
  const baseTs = `
22
+ import { An5ClientKnownRequestError as An5KnownError } from "@an5/orm";
23
+
22
24
  export namespace An5 {
23
- export class An5ClientKnownRequestError extends Error {
24
- code!: string;
25
- meta?: any;
26
- constructor(message: string, { code, clientVersion }: { code: string; clientVersion: string }) {
27
- super(message);
28
- this.code = code;
29
- }
30
- }
25
+ export const An5ClientKnownRequestError: typeof An5KnownError = An5KnownError;
26
+ export type An5ClientKnownRequestError = An5KnownError;
31
27
  export type SortOrder = 'asc' | 'desc';
32
28
  export type StringFilter = { equals?: string; in?: string[]; notIn?: string[]; lt?: string; lte?: string; gt?: string; gte?: string; contains?: string; startsWith?: string; endsWith?: string; not?: string | StringFilter; };
33
29
  export type StringNullableFilter = { equals?: string | null; in?: (string | null)[]; notIn?: (string | null)[]; lt?: string; lte?: string; gt?: string; gte?: string; contains?: string; startsWith?: string; endsWith?: string; not?: string | StringNullableFilter | null; };
@@ -37,11 +33,11 @@ export namespace An5 {
37
33
  export type NumberNullableFilter = { equals?: number | null; in?: (number | null)[]; notIn?: (number | null)[]; lt?: number; lte?: number; gt?: number; gte?: number; not?: number | NumberNullableFilter | null; };
38
34
  export type BooleanFilter = { equals?: boolean; not?: boolean | BooleanFilter; };
39
35
  export type BooleanNullableFilter = { equals?: boolean | null; not?: boolean | BooleanNullableFilter | null; };
40
- export type DateTimeFilter = { equals?: Date; in?: Date[]; notIn?: Date[]; lt?: Date; lte?: Date; gt?: Date; gte?: Date; not?: Date | DateTimeFilter; };
41
- export type DateTimeNullableFilter = { equals?: Date | null; in?: (Date | null)[]; notIn?: (Date | null)[]; lt?: Date; lte?: Date; gt?: Date; gte?: Date; not?: Date | DateTimeNullableFilter | null; };
36
+ export type DateTimeFilter = { equals?: Date | string; in?: (Date | string)[]; notIn?: (Date | string)[]; lt?: Date | string; lte?: Date | string; gt?: Date | string; gte?: Date | string; not?: Date | string | DateTimeFilter; };
37
+ export type DateTimeNullableFilter = { equals?: Date | string | null; in?: ((Date | string) | null)[]; notIn?: ((Date | string) | null)[]; lt?: Date | string; lte?: Date | string; gt?: Date | string; gte?: Date | string; not?: Date | string | DateTimeNullableFilter | null; };
42
38
  }
43
39
 
44
- export interface TableClient<T, WhereInput = any, Select = any, Include = any, CreateInput = any, UpdateInput = any, FindManyArgs = any, FindFirstArgs = any, FindUniqueArgs = any, CreateArgs = any, UpdateArgs = any, UpsertArgs = any, DeleteArgs = any> {
40
+ export interface TableClient<T, WhereInput = any, _Select = any, Include = any, CreateInput = any, UpdateInput = any, FindManyArgs = any, FindFirstArgs = any, FindUniqueArgs = any, CreateArgs = any, UpdateArgs = any, UpsertArgs = any, DeleteArgs = any, AggregateArgs = any, GroupByArgs = any> {
45
41
  findMany(args?: FindManyArgs): Promise<T[]>;
46
42
  vectorSearch(args: { vector: number[]; take?: number; where?: WhereInput; include?: Include; vectorField?: string; distanceMetric?: 'cosine' | 'euclidean' | 'dot'; }): Promise<(T & { distance: number })[]>;
47
43
  findFirst(args?: FindFirstArgs): Promise<T | null>;
@@ -53,8 +49,8 @@ export interface TableClient<T, WhereInput = any, Select = any, Include = any, C
53
49
  updateMany(args: { where?: WhereInput; data: UpdateInput; }): Promise<{ count: number }>;
54
50
  delete(args: DeleteArgs): Promise<T>;
55
51
  deleteMany(args?: { where?: WhereInput; }): Promise<{ count: number }>;
56
- aggregate(args: any): Promise<any>;
57
- groupBy(args: any): Promise<any[]>;
52
+ aggregate(args: AggregateArgs): Promise<any>;
53
+ groupBy(args: GroupByArgs): Promise<any[]>;
58
54
  upsert(args: UpsertArgs): Promise<T>;
59
55
  }
60
56
  `;
@@ -88,7 +84,7 @@ export interface TableClient<T, WhereInput = any, Select = any, Include = any, C
88
84
  content += ` NOT?: ${model.name}WhereInput | ${model.name}WhereInput[];\n`;
89
85
  for (const field of model.fields) {
90
86
  const filter = this.getFieldFilterType(field);
91
- content += ` ${field.name}?: ${field.type} | ${filter}${field.isOptional ? ' | null' : ''};\n`;
87
+ content += ` ${field.name}?: ${this.inputType(field.type)} | ${filter}${field.isOptional ? ' | null' : ''};\n`;
92
88
  }
93
89
  for (const rel of model.relations) {
94
90
  if (rel.isArray) {
@@ -118,18 +114,25 @@ export interface TableClient<T, WhereInput = any, Select = any, Include = any, C
118
114
  }
119
115
  content += `export type ${model.name}Include = { ${includeRels.join('; ')}${includeRels.length > 0 ? ';' : ''} };\n`;
120
116
 
121
- content += `export type ${model.name}CreateInput = { ${model.fields.map(f => `${f.name}${f.isOptional || f.hasDefault ? '?' : ''}: ${f.type}${f.isOptional ? ' | null' : ''}`).join('; ')}; ${model.relations.map(r => `${r.name}?: { create?: ${r.type}CreateInput | ${r.type}CreateInput[]; connect?: ${r.type}WhereInput | ${r.type}WhereInput[]; }`).join('; ')} };\n`;
122
- content += `export type ${model.name}UpdateInput = { ${model.fields.filter(f => !f.isId).map(f => `${f.name}?: ${f.type === 'number' ? 'number | An5.IntFieldUpdateOperationsInput' : f.type}${f.isOptional ? ' | null' : ''}`).join('; ')}; ${model.relations.map(r => `${r.name}?: { create?: ${r.type}CreateInput | ${r.type}CreateInput[]; connect?: ${r.type}WhereInput | ${r.type}WhereInput[]; set?: ${r.type}WhereInput | ${r.type}WhereInput[]; disconnect?: ${r.type}WhereInput | ${r.type}WhereInput[]; delete?: ${r.type}WhereInput | ${r.type}WhereInput[]; update?: { where: ${r.type}WhereInput; data: ${r.type}UpdateInput; } | { where: ${r.type}WhereInput; data: ${r.type}UpdateInput; }[]; upsert?: { where: ${r.type}WhereInput; create: ${r.type}CreateInput; update: ${r.type}UpdateInput; } | { where: ${r.type}WhereInput; create: ${r.type}CreateInput; update: ${r.type}UpdateInput; }[]; }`).join('; ')} };\n`;
117
+ content += `export type ${model.name}CreateInput = { ${model.fields.map(f => `${f.name}${f.isOptional || f.hasDefault ? '?' : ''}: ${this.inputType(f.type)}${f.isOptional ? ' | null' : ''}`).join('; ')}; ${model.relations.map(r => `${r.name}?: { create?: ${r.type}CreateInput | ${r.type}CreateInput[]; connect?: ${r.type}WhereInput | ${r.type}WhereInput[]; }`).join('; ')} };\n`;
118
+ content += `export type ${model.name}UpdateInput = { ${model.fields.filter(f => !f.isId).map(f => `${f.name}?: ${f.type === 'number' ? 'number | An5.IntFieldUpdateOperationsInput' : this.inputType(f.type)}${f.isOptional ? ' | null' : ''}`).join('; ')}; ${model.relations.map(r => `${r.name}?: { create?: ${r.type}CreateInput | ${r.type}CreateInput[]; connect?: ${r.type}WhereInput | ${r.type}WhereInput[]; set?: ${r.type}WhereInput | ${r.type}WhereInput[]; disconnect?: ${r.type}WhereInput | ${r.type}WhereInput[]; delete?: ${r.type}WhereInput | ${r.type}WhereInput[]; update?: { where: ${r.type}WhereInput; data: ${r.type}UpdateInput; } | { where: ${r.type}WhereInput; data: ${r.type}UpdateInput; }[]; upsert?: { where: ${r.type}WhereInput; create: ${r.type}CreateInput; update: ${r.type}UpdateInput; } | { where: ${r.type}WhereInput; create: ${r.type}CreateInput; update: ${r.type}UpdateInput; }[]; }`).join('; ')} };\n`;
123
119
 
124
- content += `export type ${model.name}FindManyArgs = { where?: ${model.name}WhereInput; orderBy?: any; take?: number; skip?: number; include?: ${model.name}Include; select?: ${model.name}Select; };\n`;
125
- content += `export type ${model.name}FindFirstArgs = { where?: ${model.name}WhereInput; orderBy?: any; include?: ${model.name}Include; select?: ${model.name}Select; };\n`;
120
+ content += `export type ${model.name}OrderByInput = { ${model.fields.map(f => `${f.name}?: An5.SortOrder`).join('; ')} };\n`;
121
+ content += `export type ${model.name}FindManyArgs = { where?: ${model.name}WhereInput; orderBy?: ${model.name}OrderByInput | ${model.name}OrderByInput[]; take?: number; skip?: number; include?: ${model.name}Include; select?: ${model.name}Select; };\n`;
122
+ content += `export type ${model.name}FindFirstArgs = { where?: ${model.name}WhereInput; orderBy?: ${model.name}OrderByInput | ${model.name}OrderByInput[]; include?: ${model.name}Include; select?: ${model.name}Select; };\n`;
126
123
  content += `export type ${model.name}FindUniqueArgs = { where?: ${model.name}WhereInput; include?: ${model.name}Include; select?: ${model.name}Select; };\n`;
127
124
  content += `export type ${model.name}CreateArgs = { data: ${model.name}CreateInput; include?: ${model.name}Include; select?: ${model.name}Select; };\n`;
128
125
  content += `export type ${model.name}UpdateArgs = { where: ${model.name}WhereInput; data: ${model.name}UpdateInput; include?: ${model.name}Include; select?: ${model.name}Select; };\n`;
129
126
  content += `export type ${model.name}UpsertArgs = { where: ${model.name}WhereInput; create: ${model.name}CreateInput; update: ${model.name}UpdateInput; include?: ${model.name}Include; select?: ${model.name}Select; };\n`;
130
127
  content += `export type ${model.name}DeleteArgs = { where: ${model.name}WhereInput; include?: ${model.name}Include; select?: ${model.name}Select; };\n`;
128
+ content += `export type ${model.name}ScalarFieldEnum = ${model.fields.map(f => `'${f.name}'`).join(' | ') || 'never'};\n`;
129
+ content += `export type ${model.name}AggregateArgs = { where?: ${model.name}WhereInput; _count?: true | { _all?: true; ${model.fields.map(f => `${f.name}?: true`).join('; ')} }; _sum?: { ${model.fields.filter(f => this.normalizeType(f.type) === 'number').map(f => `${f.name}?: true`).join('; ')} }; _avg?: { ${model.fields.filter(f => this.normalizeType(f.type) === 'number').map(f => `${f.name}?: true`).join('; ')} }; _min?: { ${model.fields.map(f => `${f.name}?: true`).join('; ')} }; _max?: { ${model.fields.map(f => `${f.name}?: true`).join('; ')} }; };\n`;
130
+ const numericHavingFields = model.fields.filter(f => this.normalizeType(f.type) === 'number').map(f => `${f.name}?: An5.NumberFilter | number`).join('; ');
131
+ const scalarHavingFields = model.fields.map(f => `${f.name}?: any`).join('; ');
132
+ content += `export type ${model.name}AggregateHavingInput = { _count?: { _all?: An5.NumberFilter | number; ${model.fields.map(f => `${f.name}?: An5.NumberFilter | number`).join('; ')} }; _sum?: { ${numericHavingFields} }; _avg?: { ${numericHavingFields} }; _min?: { ${scalarHavingFields} }; _max?: { ${scalarHavingFields} }; };\n`;
133
+ content += `export type ${model.name}GroupByArgs = { by: ${model.name}ScalarFieldEnum | ${model.name}ScalarFieldEnum[]; where?: ${model.name}WhereInput; having?: ${model.name}AggregateHavingInput; orderBy?: ${model.name}OrderByInput | ${model.name}OrderByInput[]; skip?: number; take?: number; _count?: true | { _all?: true; ${model.fields.map(f => `${f.name}?: true`).join('; ')} }; _sum?: { ${model.fields.filter(f => this.normalizeType(f.type) === 'number').map(f => `${f.name}?: true`).join('; ')} }; _avg?: { ${model.fields.filter(f => this.normalizeType(f.type) === 'number').map(f => `${f.name}?: true`).join('; ')} }; _min?: { ${model.fields.map(f => `${f.name}?: true`).join('; ')} }; _max?: { ${model.fields.map(f => `${f.name}?: true`).join('; ')} }; };\n`;
131
134
 
132
- content += `export type ${model.name}TableClient = TableClient<\n ${model.name},\n ${model.name}WhereInput,\n ${model.name}Select,\n ${model.name}Include,\n ${model.name}CreateInput,\n ${model.name}UpdateInput,\n ${model.name}FindManyArgs,\n ${model.name}FindFirstArgs,\n ${model.name}FindUniqueArgs,\n ${model.name}CreateArgs,\n ${model.name}UpdateArgs,\n ${model.name}UpsertArgs,\n ${model.name}DeleteArgs\n>;\n`;
135
+ content += `export type ${model.name}TableClient = TableClient<\n ${model.name},\n ${model.name}WhereInput,\n ${model.name}Select,\n ${model.name}Include,\n ${model.name}CreateInput,\n ${model.name}UpdateInput,\n ${model.name}FindManyArgs,\n ${model.name}FindFirstArgs,\n ${model.name}FindUniqueArgs,\n ${model.name}CreateArgs,\n ${model.name}UpdateArgs,\n ${model.name}UpsertArgs,\n ${model.name}DeleteArgs,\n ${model.name}AggregateArgs,\n ${model.name}GroupByArgs\n>;\n`;
133
136
 
134
137
  fs.writeFileSync(path.join(this.outputDir, `${model.name}.ts`), content);
135
138
  }
@@ -165,10 +168,22 @@ export interface TableClient<T, WhereInput = any, Select = any, Include = any, C
165
168
  content += ` export type ${model.name}Include = ${model.name}Types.${model.name}Include;\n`;
166
169
  content += ` export type ${model.name}CreateInput = ${model.name}Types.${model.name}CreateInput;\n`;
167
170
  content += ` export type ${model.name}UpdateInput = ${model.name}Types.${model.name}UpdateInput;\n`;
171
+ content += ` export type ${model.name}OrderByInput = ${model.name}Types.${model.name}OrderByInput;\n`;
172
+ content += ` export type ${model.name}FindManyArgs = ${model.name}Types.${model.name}FindManyArgs;\n`;
173
+ content += ` export type ${model.name}FindFirstArgs = ${model.name}Types.${model.name}FindFirstArgs;\n`;
174
+ content += ` export type ${model.name}FindUniqueArgs = ${model.name}Types.${model.name}FindUniqueArgs;\n`;
175
+ content += ` export type ${model.name}CreateArgs = ${model.name}Types.${model.name}CreateArgs;\n`;
176
+ content += ` export type ${model.name}UpdateArgs = ${model.name}Types.${model.name}UpdateArgs;\n`;
177
+ content += ` export type ${model.name}UpsertArgs = ${model.name}Types.${model.name}UpsertArgs;\n`;
178
+ content += ` export type ${model.name}DeleteArgs = ${model.name}Types.${model.name}DeleteArgs;\n`;
179
+ content += ` export type ${model.name}TableClient = ${model.name}Types.${model.name}TableClient;\n`;
180
+ content += ` export type ${model.name}ScalarFieldEnum = ${model.name}Types.${model.name}ScalarFieldEnum;\n`;
181
+ content += ` export type ${model.name}AggregateArgs = ${model.name}Types.${model.name}AggregateArgs;\n`;
182
+ content += ` export type ${model.name}GroupByArgs = ${model.name}Types.${model.name}GroupByArgs;\n`;
168
183
  }
169
184
  content += `}\n`;
170
185
 
171
- content += `\nexport class An5Client {\n $connect(): Promise<void> { return Promise.resolve(); }\n $disconnect(): Promise<void> { return Promise.resolve(); }\n $transaction<R>(fn: (tx: An5Client) => Promise<R>, options?: { timeout?: number }): Promise<R>;\n $transaction<R>(list: Promise<R>[]): Promise<R[]>;\n $transaction(fn: any, options?: any): Promise<any> { return typeof fn === 'function' ? fn(this) : Promise.all(fn); }\n $queryRaw<T = any>(queryParts: TemplateStringsArray | string, ...values: any[]): Promise<T> { return Promise.resolve([] as any); }\n $queryRawUnsafe<R = any>(query: string, ...values: any[]): Promise<R> { return Promise.resolve([] as any); }\n $executeRaw<T = any>(queryParts: TemplateStringsArray | string, ...values: any[]): Promise<any> { return Promise.resolve(0 as any); }\n $executeRawUnsafe(query: string, ...values: any[]): Promise<number> { return Promise.resolve(0); }\n`;
186
+ content += `\nexport class An5Client {\n $connect(): Promise<void> { return Promise.resolve(); }\n $disconnect(): Promise<void> { return Promise.resolve(); }\n $transaction<R>(fn: (tx: An5Client) => Promise<R>, options?: { timeout?: number }): Promise<R>;\n $transaction<R>(list: Promise<R>[]): Promise<R[]>;\n $transaction(fn: any, _options?: any): Promise<any> { return typeof fn === 'function' ? fn(this) : Promise.all(fn); }\n $begin(): Promise<An5Client> { return Promise.resolve(this); }\n $commit(): Promise<void> { return Promise.resolve(); }\n $rollback(): Promise<void> { return Promise.resolve(); }\n $queryRaw<T = any>(_queryParts: TemplateStringsArray | string, ..._values: any[]): Promise<T> { return Promise.resolve([] as any); }\n $queryRawUnsafe<R = any>(_query: string, ..._values: any[]): Promise<R> { return Promise.resolve([] as any); }\n $executeRaw<_T = any>(_queryParts: TemplateStringsArray | string, ..._values: any[]): Promise<any> { return Promise.resolve(0 as any); }\n $executeRawUnsafe(_query: string, ..._values: any[]): Promise<number> { return Promise.resolve(0); }\n`;
172
187
 
173
188
  const addedProps = new Set<string>();
174
189
  for (const model of models) {
@@ -187,12 +202,16 @@ export interface TableClient<T, WhereInput = any, Select = any, Include = any, C
187
202
 
188
203
  private getAllPropertyVariations(modelName: string): string[] {
189
204
  const variations = new Set<string>();
205
+ variations.add(modelName);
190
206
  variations.add(this.toCamelCase(modelName));
207
+ variations.add(modelName + 's');
208
+ variations.add(this.toCamelCase(modelName) + 's');
191
209
  const acronyms = ['LLM', 'AI', 'MCP', 'IT', 'QC', 'HR', 'MR', 'WH', 'SSIS', 'API', 'URL', 'ID', 'JSON'];
192
210
  for (const acronym of acronyms) {
193
211
  if (modelName.startsWith(acronym)) {
194
212
  variations.add(acronym.toLowerCase() + modelName.slice(acronym.length));
195
213
  variations.add(acronym[0].toLowerCase() + acronym.slice(1) + modelName.slice(acronym.length));
214
+ variations.add(acronym.toLowerCase() + modelName.slice(acronym.length) + 's');
196
215
  }
197
216
  }
198
217
  return Array.from(variations);
@@ -207,6 +226,12 @@ export interface TableClient<T, WhereInput = any, Select = any, Include = any, C
207
226
  return 'any';
208
227
  }
209
228
 
229
+ // Input positions (create/update/where scalars) accept ISO strings as well
230
+ // as Date objects — Prisma parity for DateTime fields.
231
+ private inputType(tsType: string): string {
232
+ return tsType === 'Date' ? 'Date | string' : tsType;
233
+ }
234
+
210
235
  private normalizeType(type: string): string {
211
236
  const lowerType = type.toLowerCase();
212
237
  if (['string', 'varchar', 'nvarchar', 'char', 'nchar', 'text', 'ntext', 'xml', 'guid', 'uuid', 'uniqueidentifier', 'sysname', 'hierarchyid', 'geography', 'geometry', 'sql_variant', 'sqlvariant', 'rowversion', 'variant'].includes(lowerType)) {