@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,2 @@
1
+ export declare const config: any;
2
+ //# sourceMappingURL=config.d.ts.map
package/dist/config.js ADDED
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.config = void 0;
7
+ /**
8
+ * Workspace Configuration for an5Orm
9
+ * Reads from an5Orm.config.js and environment variables.
10
+ */
11
+ const path_1 = __importDefault(require("path"));
12
+ let workspaceConfig = {};
13
+ try {
14
+ const configPath = path_1.default.join(__dirname, '..', 'an5Orm.config.js');
15
+ workspaceConfig = require(configPath);
16
+ }
17
+ catch { /* ignore */ }
18
+ exports.config = {
19
+ ...workspaceConfig,
20
+ llm: workspaceConfig.llm || {
21
+ providers: [],
22
+ },
23
+ };
24
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Core error types for AN5 ORM.
3
+ * Owned by @an5/orm so generated clients can re-export the same class and
4
+ * `instanceof` checks keep working across the ORM and its clients.
5
+ */
6
+ export declare class An5ClientKnownRequestError extends Error {
7
+ code: string;
8
+ meta?: any;
9
+ constructor(message: string, { code }: {
10
+ code: string;
11
+ clientVersion: string;
12
+ });
13
+ }
14
+ //# sourceMappingURL=errors.d.ts.map
package/dist/errors.js ADDED
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ /**
3
+ * Core error types for AN5 ORM.
4
+ * Owned by @an5/orm so generated clients can re-export the same class and
5
+ * `instanceof` checks keep working across the ORM and its clients.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.An5ClientKnownRequestError = void 0;
9
+ class An5ClientKnownRequestError extends Error {
10
+ constructor(message, { code }) {
11
+ super(message);
12
+ this.code = code;
13
+ }
14
+ }
15
+ exports.An5ClientKnownRequestError = An5ClientKnownRequestError;
16
+ //# sourceMappingURL=errors.js.map
@@ -8,6 +8,7 @@ export declare class CodeGenerator {
8
8
  private generateIndexTs;
9
9
  private getAllPropertyVariations;
10
10
  private getFieldFilterType;
11
+ private inputType;
11
12
  private normalizeType;
12
13
  private toCamelCase;
13
14
  }
@@ -22,15 +22,11 @@ class CodeGenerator {
22
22
  }
23
23
  generateBaseTs() {
24
24
  const baseTs = `
25
+ import { An5ClientKnownRequestError as An5KnownError } from "@an5/orm";
26
+
25
27
  export namespace An5 {
26
- export class An5ClientKnownRequestError extends Error {
27
- code!: string;
28
- meta?: any;
29
- constructor(message: string, { code, clientVersion }: { code: string; clientVersion: string }) {
30
- super(message);
31
- this.code = code;
32
- }
33
- }
28
+ export const An5ClientKnownRequestError: typeof An5KnownError = An5KnownError;
29
+ export type An5ClientKnownRequestError = An5KnownError;
34
30
  export type SortOrder = 'asc' | 'desc';
35
31
  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; };
36
32
  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; };
@@ -40,11 +36,11 @@ export namespace An5 {
40
36
  export type NumberNullableFilter = { equals?: number | null; in?: (number | null)[]; notIn?: (number | null)[]; lt?: number; lte?: number; gt?: number; gte?: number; not?: number | NumberNullableFilter | null; };
41
37
  export type BooleanFilter = { equals?: boolean; not?: boolean | BooleanFilter; };
42
38
  export type BooleanNullableFilter = { equals?: boolean | null; not?: boolean | BooleanNullableFilter | null; };
43
- export type DateTimeFilter = { equals?: Date; in?: Date[]; notIn?: Date[]; lt?: Date; lte?: Date; gt?: Date; gte?: Date; not?: Date | DateTimeFilter; };
44
- export type DateTimeNullableFilter = { equals?: Date | null; in?: (Date | null)[]; notIn?: (Date | null)[]; lt?: Date; lte?: Date; gt?: Date; gte?: Date; not?: Date | DateTimeNullableFilter | null; };
39
+ 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; };
40
+ 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; };
45
41
  }
46
42
 
47
- 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> {
43
+ 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> {
48
44
  findMany(args?: FindManyArgs): Promise<T[]>;
49
45
  vectorSearch(args: { vector: number[]; take?: number; where?: WhereInput; include?: Include; vectorField?: string; distanceMetric?: 'cosine' | 'euclidean' | 'dot'; }): Promise<(T & { distance: number })[]>;
50
46
  findFirst(args?: FindFirstArgs): Promise<T | null>;
@@ -56,8 +52,8 @@ export interface TableClient<T, WhereInput = any, Select = any, Include = any, C
56
52
  updateMany(args: { where?: WhereInput; data: UpdateInput; }): Promise<{ count: number }>;
57
53
  delete(args: DeleteArgs): Promise<T>;
58
54
  deleteMany(args?: { where?: WhereInput; }): Promise<{ count: number }>;
59
- aggregate(args: any): Promise<any>;
60
- groupBy(args: any): Promise<any[]>;
55
+ aggregate(args: AggregateArgs): Promise<any>;
56
+ groupBy(args: GroupByArgs): Promise<any[]>;
61
57
  upsert(args: UpsertArgs): Promise<T>;
62
58
  }
63
59
  `;
@@ -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) {
@@ -116,16 +112,23 @@ export interface TableClient<T, WhereInput = any, Select = any, Include = any, C
116
112
  includeRels.push(`_count?: boolean | { select?: { ${model.relations.map(r => `${r.name}?: boolean`).join('; ')} } }`);
117
113
  }
118
114
  content += `export type ${model.name}Include = { ${includeRels.join('; ')}${includeRels.length > 0 ? ';' : ''} };\n`;
119
- 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`;
120
- 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`;
121
- 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`;
122
- content += `export type ${model.name}FindFirstArgs = { where?: ${model.name}WhereInput; orderBy?: any; include?: ${model.name}Include; select?: ${model.name}Select; };\n`;
115
+ 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`;
116
+ 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`;
117
+ content += `export type ${model.name}OrderByInput = { ${model.fields.map(f => `${f.name}?: An5.SortOrder`).join('; ')} };\n`;
118
+ 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`;
119
+ 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`;
123
120
  content += `export type ${model.name}FindUniqueArgs = { where?: ${model.name}WhereInput; include?: ${model.name}Include; select?: ${model.name}Select; };\n`;
124
121
  content += `export type ${model.name}CreateArgs = { data: ${model.name}CreateInput; include?: ${model.name}Include; select?: ${model.name}Select; };\n`;
125
122
  content += `export type ${model.name}UpdateArgs = { where: ${model.name}WhereInput; data: ${model.name}UpdateInput; include?: ${model.name}Include; select?: ${model.name}Select; };\n`;
126
123
  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`;
127
124
  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}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`;
125
+ content += `export type ${model.name}ScalarFieldEnum = ${model.fields.map(f => `'${f.name}'`).join(' | ') || 'never'};\n`;
126
+ 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`;
127
+ const numericHavingFields = model.fields.filter(f => this.normalizeType(f.type) === 'number').map(f => `${f.name}?: An5.NumberFilter | number`).join('; ');
128
+ const scalarHavingFields = model.fields.map(f => `${f.name}?: any`).join('; ');
129
+ 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`;
130
+ 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
+ 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`;
129
132
  fs_1.default.writeFileSync(path_1.default.join(this.outputDir, `${model.name}.ts`), content);
130
133
  }
131
134
  generateIndexTs(models) {
@@ -157,9 +160,21 @@ export interface TableClient<T, WhereInput = any, Select = any, Include = any, C
157
160
  content += ` export type ${model.name}Include = ${model.name}Types.${model.name}Include;\n`;
158
161
  content += ` export type ${model.name}CreateInput = ${model.name}Types.${model.name}CreateInput;\n`;
159
162
  content += ` export type ${model.name}UpdateInput = ${model.name}Types.${model.name}UpdateInput;\n`;
163
+ content += ` export type ${model.name}OrderByInput = ${model.name}Types.${model.name}OrderByInput;\n`;
164
+ content += ` export type ${model.name}FindManyArgs = ${model.name}Types.${model.name}FindManyArgs;\n`;
165
+ content += ` export type ${model.name}FindFirstArgs = ${model.name}Types.${model.name}FindFirstArgs;\n`;
166
+ content += ` export type ${model.name}FindUniqueArgs = ${model.name}Types.${model.name}FindUniqueArgs;\n`;
167
+ content += ` export type ${model.name}CreateArgs = ${model.name}Types.${model.name}CreateArgs;\n`;
168
+ content += ` export type ${model.name}UpdateArgs = ${model.name}Types.${model.name}UpdateArgs;\n`;
169
+ content += ` export type ${model.name}UpsertArgs = ${model.name}Types.${model.name}UpsertArgs;\n`;
170
+ content += ` export type ${model.name}DeleteArgs = ${model.name}Types.${model.name}DeleteArgs;\n`;
171
+ content += ` export type ${model.name}TableClient = ${model.name}Types.${model.name}TableClient;\n`;
172
+ content += ` export type ${model.name}ScalarFieldEnum = ${model.name}Types.${model.name}ScalarFieldEnum;\n`;
173
+ content += ` export type ${model.name}AggregateArgs = ${model.name}Types.${model.name}AggregateArgs;\n`;
174
+ content += ` export type ${model.name}GroupByArgs = ${model.name}Types.${model.name}GroupByArgs;\n`;
160
175
  }
161
176
  content += `}\n`;
162
- 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`;
177
+ 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`;
163
178
  const addedProps = new Set();
164
179
  for (const model of models) {
165
180
  const props = this.getAllPropertyVariations(model.name);
@@ -175,12 +190,16 @@ export interface TableClient<T, WhereInput = any, Select = any, Include = any, C
175
190
  }
176
191
  getAllPropertyVariations(modelName) {
177
192
  const variations = new Set();
193
+ variations.add(modelName);
178
194
  variations.add(this.toCamelCase(modelName));
195
+ variations.add(modelName + 's');
196
+ variations.add(this.toCamelCase(modelName) + 's');
179
197
  const acronyms = ['LLM', 'AI', 'MCP', 'IT', 'QC', 'HR', 'MR', 'WH', 'SSIS', 'API', 'URL', 'ID', 'JSON'];
180
198
  for (const acronym of acronyms) {
181
199
  if (modelName.startsWith(acronym)) {
182
200
  variations.add(acronym.toLowerCase() + modelName.slice(acronym.length));
183
201
  variations.add(acronym[0].toLowerCase() + acronym.slice(1) + modelName.slice(acronym.length));
202
+ variations.add(acronym.toLowerCase() + modelName.slice(acronym.length) + 's');
184
203
  }
185
204
  }
186
205
  return Array.from(variations);
@@ -197,6 +216,11 @@ export interface TableClient<T, WhereInput = any, Select = any, Include = any, C
197
216
  return field.isOptional ? 'An5.DateTimeNullableFilter' : 'An5.DateTimeFilter';
198
217
  return 'any';
199
218
  }
219
+ // Input positions (create/update/where scalars) accept ISO strings as well
220
+ // as Date objects — Prisma parity for DateTime fields.
221
+ inputType(tsType) {
222
+ return tsType === 'Date' ? 'Date | string' : tsType;
223
+ }
200
224
  normalizeType(type) {
201
225
  const lowerType = type.toLowerCase();
202
226
  if (['string', 'varchar', 'nvarchar', 'char', 'nchar', 'text', 'ntext', 'xml', 'guid', 'uuid', 'uniqueidentifier', 'sysname', 'hierarchyid', 'geography', 'geometry', 'sql_variant', 'sqlvariant', 'rowversion', 'variant'].includes(lowerType)) {
@@ -3,6 +3,8 @@ export declare class DotnetGenerator {
3
3
  private outputDir;
4
4
  constructor(outputDir: string);
5
5
  generate(models: Model[]): void;
6
+ private getCsFilterType;
7
+ private generateOrmTypes;
6
8
  private generateConfigClass;
7
9
  private mapType;
8
10
  private generateEntityClass;
@@ -18,11 +18,173 @@ class DotnetGenerator {
18
18
  for (const model of models) {
19
19
  this.generateEntityClass(model);
20
20
  }
21
- // 2. Generate DbContext / Client metadata file
21
+ // 2. Generate per-model ORM types (WhereInput, OrderByInput, FindManyArgs, etc.)
22
+ this.generateOrmTypes(models);
23
+ // 3. Generate DbContext / Client metadata file
22
24
  this.generateDbContext(models);
23
- // 3. Generate configuration helper class
25
+ // 4. Generate configuration helper class
24
26
  this.generateConfigClass();
25
27
  }
28
+ getCsFilterType(fieldType, isOptional) {
29
+ const lower = fieldType.toLowerCase();
30
+ if (['datetime', 'datetime2', 'smalldatetime', 'date', 'datetimeoffset'].includes(lower))
31
+ return 'DateTimeFilter';
32
+ if (['bool', 'boolean', 'bit'].includes(lower))
33
+ return 'BoolFilter';
34
+ if (['int', 'integer', 'smallint', 'tinyint', 'number'].includes(lower))
35
+ return 'IntFilter';
36
+ if (['bigint', 'long', 'float', 'real', 'double', 'decimal', 'numeric', 'money', 'smallmoney'].includes(lower))
37
+ return 'NumberFilter';
38
+ return 'StringFilter';
39
+ }
40
+ generateOrmTypes(models) {
41
+ let content = `// This file is auto-generated. Do not edit directly.
42
+ using System;
43
+ using System.Collections.Generic;
44
+
45
+ namespace An5Orm
46
+ {
47
+ // ── Base Filter Types ──────────────────────────────────────────────────────
48
+
49
+ /// <summary>Type-safe filter for string fields.</summary>
50
+ public class StringFilter
51
+ {
52
+ public new string Equals { get; set; }
53
+ public string Not { get; set; }
54
+ public List<string> In { get; set; }
55
+ public List<string> NotIn { get; set; }
56
+ public string Contains { get; set; }
57
+ public string StartsWith { get; set; }
58
+ public string EndsWith { get; set; }
59
+ public string Gt { get; set; }
60
+ public string Gte { get; set; }
61
+ public string Lt { get; set; }
62
+ public string Lte { get; set; }
63
+ }
64
+
65
+ /// <summary>Type-safe filter for integer fields.</summary>
66
+ public class IntFilter
67
+ {
68
+ public new int? Equals { get; set; }
69
+ public int? Not { get; set; }
70
+ public List<int> In { get; set; }
71
+ public List<int> NotIn { get; set; }
72
+ public int? Gt { get; set; }
73
+ public int? Gte { get; set; }
74
+ public int? Lt { get; set; }
75
+ public int? Lte { get; set; }
76
+ }
77
+
78
+ /// <summary>Type-safe filter for numeric/float fields.</summary>
79
+ public class NumberFilter
80
+ {
81
+ public new double? Equals { get; set; }
82
+ public double? Not { get; set; }
83
+ public List<double> In { get; set; }
84
+ public List<double> NotIn { get; set; }
85
+ public double? Gt { get; set; }
86
+ public double? Gte { get; set; }
87
+ public double? Lt { get; set; }
88
+ public double? Lte { get; set; }
89
+ }
90
+
91
+ /// <summary>Type-safe filter for boolean fields.</summary>
92
+ public class BoolFilter
93
+ {
94
+ public new bool? Equals { get; set; }
95
+ }
96
+
97
+ /// <summary>Type-safe filter for DateTime fields.</summary>
98
+ public class DateTimeFilter
99
+ {
100
+ public new DateTime? Equals { get; set; }
101
+ public DateTime? Not { get; set; }
102
+ public List<DateTime> In { get; set; }
103
+ public List<DateTime> NotIn { get; set; }
104
+ public DateTime? Gt { get; set; }
105
+ public DateTime? Gte { get; set; }
106
+ public DateTime? Lt { get; set; }
107
+ public DateTime? Lte { get; set; }
108
+ }
109
+
110
+ `;
111
+ for (const model of models) {
112
+ const name = this.capitalize(model.name);
113
+ const csType = (f) => this.mapType(f.type, f.isOptional);
114
+ content += ` // ── ${name} ORM Types ──────────────────────────────────────────────────────\n\n`;
115
+ // WhereInput
116
+ content += ` /// <summary>Type-safe WHERE filter for ${name} queries.</summary>\n`;
117
+ content += ` public class ${name}WhereInput\n {\n`;
118
+ content += ` public List<${name}WhereInput> AND { get; set; }\n`;
119
+ content += ` public List<${name}WhereInput> OR { get; set; }\n`;
120
+ content += ` public ${name}WhereInput NOT { get; set; }\n`;
121
+ for (const f of model.fields) {
122
+ const ft = this.getCsFilterType(f.type, f.isOptional);
123
+ content += ` public ${ft} ${this.capitalize(f.name)} { get; set; }\n`;
124
+ }
125
+ content += ` }\n\n`;
126
+ // OrderByInput
127
+ content += ` /// <summary>Type-safe ORDER BY for ${name} queries.</summary>\n`;
128
+ content += ` public class ${name}OrderByInput\n {\n`;
129
+ for (const f of model.fields) {
130
+ content += ` public string ${this.capitalize(f.name)} { get; set; } // "asc" or "desc"\n`;
131
+ }
132
+ content += ` }\n\n`;
133
+ // CreateInput
134
+ content += ` /// <summary>Typed data for creating a new ${name} record.</summary>\n`;
135
+ content += ` public class ${name}CreateInput\n {\n`;
136
+ for (const f of model.fields) {
137
+ if (!f.isId) {
138
+ const ct = this.mapType(f.type, f.isOptional || f.hasDefault);
139
+ content += ` public ${ct} ${this.capitalize(f.name)} { get; set; }\n`;
140
+ }
141
+ }
142
+ content += ` }\n\n`;
143
+ // UpdateInput
144
+ content += ` /// <summary>Typed data for updating an existing ${name} record.</summary>\n`;
145
+ content += ` public class ${name}UpdateInput\n {\n`;
146
+ for (const f of model.fields) {
147
+ if (!f.isId) {
148
+ const ut = this.mapType(f.type, true); // always nullable for update
149
+ content += ` public ${ut} ${this.capitalize(f.name)} { get; set; }\n`;
150
+ }
151
+ }
152
+ content += ` }\n\n`;
153
+ // FindManyArgs
154
+ content += ` /// <summary>ORM-style args for ${name}.FindMany().</summary>\n`;
155
+ content += ` public class ${name}FindManyArgs\n {\n`;
156
+ content += ` public ${name}WhereInput Where { get; set; }\n`;
157
+ content += ` public ${name}OrderByInput OrderBy { get; set; }\n`;
158
+ content += ` public int? Take { get; set; }\n`;
159
+ content += ` public int? Skip { get; set; }\n`;
160
+ content += ` public List<string> Select { get; set; }\n`;
161
+ content += ` }\n\n`;
162
+ // FindFirstArgs
163
+ content += ` /// <summary>ORM-style args for ${name}.FindFirst().</summary>\n`;
164
+ content += ` public class ${name}FindFirstArgs\n {\n`;
165
+ content += ` public ${name}WhereInput Where { get; set; }\n`;
166
+ content += ` public ${name}OrderByInput OrderBy { get; set; }\n`;
167
+ content += ` public List<string> Select { get; set; }\n`;
168
+ content += ` }\n\n`;
169
+ // FindUniqueArgs
170
+ content += ` /// <summary>ORM-style args for ${name}.FindUnique().</summary>\n`;
171
+ content += ` public class ${name}FindUniqueArgs\n {\n`;
172
+ content += ` public ${name}WhereInput Where { get; set; }\n`;
173
+ content += ` }\n\n`;
174
+ // DeleteManyArgs
175
+ content += ` /// <summary>ORM-style args for ${name}.DeleteMany().</summary>\n`;
176
+ content += ` public class ${name}DeleteManyArgs\n {\n`;
177
+ content += ` public ${name}WhereInput Where { get; set; }\n`;
178
+ content += ` }\n\n`;
179
+ // CountArgs
180
+ content += ` /// <summary>ORM-style args for ${name}.Count().</summary>\n`;
181
+ content += ` public class ${name}CountArgs\n {\n`;
182
+ content += ` public ${name}WhereInput Where { get; set; }\n`;
183
+ content += ` }\n\n`;
184
+ }
185
+ content += `}\n`;
186
+ fs_1.default.writeFileSync(path_1.default.join(this.outputDir, 'An5OrmTypes.cs'), content);
187
+ }
26
188
  generateConfigClass() {
27
189
  const content = `// This file is auto-generated. Do not edit directly.
28
190
  using System;
@@ -119,6 +281,7 @@ using System;
119
281
  using System.Collections.Generic;
120
282
  using System.Data;
121
283
  using System.Reflection;
284
+ using System.Text.Json;
122
285
  using Microsoft.Data.SqlClient;
123
286
  using An5Orm.Entities;
124
287
 
@@ -169,7 +332,9 @@ namespace An5Orm
169
332
  // ── Tables / Repositories ──────────────────────────────────────────────
170
333
  `;
171
334
  for (const model of models) {
172
- content += ` public TableClient<${model.name}> ${this.capitalize(model.name)}s => new TableClient<${model.name}>(ConnectionString, "${model.schemaName}.${model.tableName}");\n`;
335
+ const name = this.capitalize(model.name);
336
+ content += ` public TableClient<${model.name}> ${name}s => new TableClient<${model.name}>(ConnectionString, "${model.schemaName}.${model.tableName}");\n`;
337
+ content += ` public TableClient<${model.name}> ${name} => ${name}s;\n`;
173
338
  }
174
339
  content += ` }
175
340
 
@@ -233,6 +398,51 @@ namespace An5Orm
233
398
  return cmd;
234
399
  }
235
400
 
401
+ public List<T> QueryRaw(string query, Dictionary<string, object> parameters = null)
402
+ {
403
+ var list = new List<T>();
404
+ var conn = An5DbContext.GetActiveConnection(ConnectionString, out bool isTx);
405
+ try
406
+ {
407
+ using (var cmd = CreateCommand(conn, query))
408
+ {
409
+ if (parameters != null)
410
+ {
411
+ foreach (var kvp in parameters)
412
+ {
413
+ cmd.Parameters.AddWithValue(kvp.Key.StartsWith("@") ? kvp.Key : "@" + kvp.Key, kvp.Value ?? DBNull.Value);
414
+ }
415
+ }
416
+
417
+ using (var reader = cmd.ExecuteReader())
418
+ {
419
+ var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
420
+ while (reader.Read())
421
+ {
422
+ var item = new T();
423
+ foreach (var prop in properties)
424
+ {
425
+ if (HasColumn(reader, prop.Name))
426
+ {
427
+ var val = reader[prop.Name];
428
+ if (val != DBNull.Value)
429
+ {
430
+ prop.SetValue(item, val);
431
+ }
432
+ }
433
+ }
434
+ list.Add(item);
435
+ }
436
+ }
437
+ }
438
+ }
439
+ finally
440
+ {
441
+ if (!isTx) conn.Dispose();
442
+ }
443
+ return list;
444
+ }
445
+
236
446
  public List<T> FindMany(string whereClause = null, Dictionary<string, object> parameters = null)
237
447
  {
238
448
  var list = new List<T>();
@@ -441,15 +651,132 @@ namespace An5Orm
441
651
  }
442
652
  }
443
653
 
654
+ public int Count(string whereClause = null, Dictionary<string, object> parameters = null)
655
+ {
656
+ string query = $"SELECT COUNT(*) FROM {TableName}";
657
+ if (!string.IsNullOrEmpty(whereClause)) query += $" WHERE {whereClause}";
658
+ var conn = An5DbContext.GetActiveConnection(ConnectionString, out bool isTx);
659
+ try
660
+ {
661
+ using (var cmd = CreateCommand(conn, query))
662
+ {
663
+ if (parameters != null)
664
+ {
665
+ foreach (var kvp in parameters)
666
+ cmd.Parameters.AddWithValue(kvp.Key.StartsWith("@") ? kvp.Key : "@" + kvp.Key, kvp.Value ?? DBNull.Value);
667
+ }
668
+ var res = cmd.ExecuteScalar();
669
+ return res != null && res != DBNull.Value ? Convert.ToInt32(res) : 0;
670
+ }
671
+ }
672
+ finally { if (!isTx) conn.Dispose(); }
673
+ }
674
+
675
+ public int CreateMany(IEnumerable<T> entities)
676
+ {
677
+ int count = 0;
678
+ foreach (var entity in entities)
679
+ {
680
+ Create(entity);
681
+ count++;
682
+ }
683
+ return count;
684
+ }
685
+
686
+ public int UpdateMany(string whereClause, Dictionary<string, object> updateData, Dictionary<string, object> parameters = null)
687
+ {
688
+ if (updateData == null || updateData.Count == 0) return 0;
689
+ var sets = new List<string>();
690
+ var sqlParams = new List<SqlParameter>();
691
+ int pIndex = 0;
692
+ foreach (var kvp in updateData)
693
+ {
694
+ string paramName = "@u_" + pIndex++;
695
+ sets.Add($"{kvp.Key} = {paramName}");
696
+ sqlParams.Add(new SqlParameter(paramName, kvp.Value ?? DBNull.Value));
697
+ }
698
+ string query = $"UPDATE {TableName} SET {string.Join(", ", sets)}";
699
+ if (!string.IsNullOrEmpty(whereClause)) query += $" WHERE {whereClause}";
700
+ var conn = An5DbContext.GetActiveConnection(ConnectionString, out bool isTx);
701
+ try
702
+ {
703
+ using (var cmd = CreateCommand(conn, query))
704
+ {
705
+ cmd.Parameters.AddRange(sqlParams.ToArray());
706
+ if (parameters != null)
707
+ {
708
+ foreach (var kvp in parameters)
709
+ cmd.Parameters.AddWithValue(kvp.Key.StartsWith("@") ? kvp.Key : "@" + kvp.Key, kvp.Value ?? DBNull.Value);
710
+ }
711
+ return cmd.ExecuteNonQuery();
712
+ }
713
+ }
714
+ finally { if (!isTx) conn.Dispose(); }
715
+ }
716
+
717
+ public int DeleteMany(string whereClause = null, Dictionary<string, object> parameters = null)
718
+ {
719
+ string query = $"DELETE FROM {TableName}";
720
+ if (!string.IsNullOrEmpty(whereClause)) query += $" WHERE {whereClause}";
721
+ var conn = An5DbContext.GetActiveConnection(ConnectionString, out bool isTx);
722
+ try
723
+ {
724
+ using (var cmd = CreateCommand(conn, query))
725
+ {
726
+ if (parameters != null)
727
+ {
728
+ foreach (var kvp in parameters)
729
+ cmd.Parameters.AddWithValue(kvp.Key.StartsWith("@") ? kvp.Key : "@" + kvp.Key, kvp.Value ?? DBNull.Value);
730
+ }
731
+ return cmd.ExecuteNonQuery();
732
+ }
733
+ }
734
+ finally { if (!isTx) conn.Dispose(); }
735
+ }
736
+
737
+ public T Upsert(T entity, string idColumnName = "Id")
738
+ {
739
+ var prop = typeof(T).GetProperty(idColumnName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
740
+ if (prop == null) throw new InvalidOperationException($"Property '{idColumnName}' not found on entity.");
741
+ var idVal = prop.GetValue(entity);
742
+ var existing = idVal != null ? FindUnique(idVal) : default;
743
+ if (existing != null) return Update(entity);
744
+ return Create(entity);
745
+ }
746
+
444
747
  public List<T> VectorSearch(List<double> vector, int take = 10, string whereClause = null, Dictionary<string, object> parameters = null, string vectorField = "Embedding", string distanceMetric = "cosine")
445
748
  {
749
+ // 1. Primary path: Native database SQL vector query execution (VECTOR_DISTANCE)
750
+ try
751
+ {
752
+ var dim = vector.Count;
753
+ var vecJson = JsonSerializer.Serialize(vector);
754
+ var sql = $"SELECT TOP ({take}) *, VECTOR_DISTANCE('{distanceMetric}', CAST([{vectorField}] AS VECTOR({dim}, float32)), CAST(@query_vector AS VECTOR({dim}, float32))) AS distance FROM {TableName} WITH (NOLOCK)";
755
+
756
+ var p = parameters != null ? new Dictionary<string, object>(parameters) : new Dictionary<string, object>();
757
+ p["query_vector"] = vecJson;
758
+
759
+ if (!string.IsNullOrWhiteSpace(whereClause))
760
+ sql += $" WHERE [{vectorField}] IS NOT NULL AND ({whereClause})";
761
+ else
762
+ sql += $" WHERE [{vectorField}] IS NOT NULL";
763
+ sql += " ORDER BY distance ASC";
764
+
765
+ var nativeRows = QueryRaw(sql, p);
766
+ if (nativeRows != null) return nativeRows;
767
+ }
768
+ catch
769
+ {
770
+ // Fallback to in-memory similarity computation if DB instance lacks native VECTOR_DISTANCE
771
+ }
772
+
773
+ // 2. Secondary fallback: In-memory similarity computation
446
774
  var rows = FindMany(whereClause, parameters);
447
775
  var results = new List<Tuple<T, double>>();
448
776
 
449
777
  var propInfo = typeof(T).GetProperty(vectorField, BindingFlags.Public | BindingFlags.Instance);
450
778
  if (propInfo == null)
451
779
  {
452
- // Fallback case-insensitive
453
780
  propInfo = typeof(T).GetProperty(vectorField, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
454
781
  }
455
782
  if (propInfo == null) return rows;
@@ -479,12 +806,11 @@ namespace An5Orm
479
806
  }
480
807
 
481
808
  results.Sort((a, b) => a.Item2.CompareTo(b.Item2));
482
-
809
+
483
810
  var output = new List<T>();
484
811
  int limit = Math.Min(take, results.Count);
485
812
  for (int i = 0; i < limit; i++)
486
813
  {
487
- // Set the distance property if the model has a public property named Distance
488
814
  var distanceProp = typeof(T).GetProperty("Distance", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
489
815
  if (distanceProp != null && distanceProp.PropertyType == typeof(double))
490
816
  {