@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
@@ -15,13 +15,187 @@ export class DotnetGenerator {
15
15
  this.generateEntityClass(model);
16
16
  }
17
17
 
18
- // 2. Generate DbContext / Client metadata file
18
+ // 2. Generate per-model ORM types (WhereInput, OrderByInput, FindManyArgs, etc.)
19
+ this.generateOrmTypes(models);
20
+
21
+ // 3. Generate DbContext / Client metadata file
19
22
  this.generateDbContext(models);
20
23
 
21
- // 3. Generate configuration helper class
24
+ // 4. Generate configuration helper class
22
25
  this.generateConfigClass();
23
26
  }
24
27
 
28
+ private getCsFilterType(fieldType: string, isOptional: boolean): string {
29
+ const lower = fieldType.toLowerCase();
30
+ if (['datetime', 'datetime2', 'smalldatetime', 'date', 'datetimeoffset'].includes(lower)) return 'DateTimeFilter';
31
+ if (['bool', 'boolean', 'bit'].includes(lower)) return 'BoolFilter';
32
+ if (['int', 'integer', 'smallint', 'tinyint', 'number'].includes(lower)) return 'IntFilter';
33
+ if (['bigint', 'long', 'float', 'real', 'double', 'decimal', 'numeric', 'money', 'smallmoney'].includes(lower)) return 'NumberFilter';
34
+ return 'StringFilter';
35
+ }
36
+
37
+ private generateOrmTypes(models: Model[]) {
38
+ let content = `// This file is auto-generated. Do not edit directly.
39
+ using System;
40
+ using System.Collections.Generic;
41
+
42
+ namespace An5Orm
43
+ {
44
+ // ── Base Filter Types ──────────────────────────────────────────────────────
45
+
46
+ /// <summary>Type-safe filter for string fields.</summary>
47
+ public class StringFilter
48
+ {
49
+ public new string Equals { get; set; }
50
+ public string Not { get; set; }
51
+ public List<string> In { get; set; }
52
+ public List<string> NotIn { get; set; }
53
+ public string Contains { get; set; }
54
+ public string StartsWith { get; set; }
55
+ public string EndsWith { get; set; }
56
+ public string Gt { get; set; }
57
+ public string Gte { get; set; }
58
+ public string Lt { get; set; }
59
+ public string Lte { get; set; }
60
+ }
61
+
62
+ /// <summary>Type-safe filter for integer fields.</summary>
63
+ public class IntFilter
64
+ {
65
+ public new int? Equals { get; set; }
66
+ public int? Not { get; set; }
67
+ public List<int> In { get; set; }
68
+ public List<int> NotIn { get; set; }
69
+ public int? Gt { get; set; }
70
+ public int? Gte { get; set; }
71
+ public int? Lt { get; set; }
72
+ public int? Lte { get; set; }
73
+ }
74
+
75
+ /// <summary>Type-safe filter for numeric/float fields.</summary>
76
+ public class NumberFilter
77
+ {
78
+ public new double? Equals { get; set; }
79
+ public double? Not { get; set; }
80
+ public List<double> In { get; set; }
81
+ public List<double> NotIn { get; set; }
82
+ public double? Gt { get; set; }
83
+ public double? Gte { get; set; }
84
+ public double? Lt { get; set; }
85
+ public double? Lte { get; set; }
86
+ }
87
+
88
+ /// <summary>Type-safe filter for boolean fields.</summary>
89
+ public class BoolFilter
90
+ {
91
+ public new bool? Equals { get; set; }
92
+ }
93
+
94
+ /// <summary>Type-safe filter for DateTime fields.</summary>
95
+ public class DateTimeFilter
96
+ {
97
+ public new DateTime? Equals { get; set; }
98
+ public DateTime? Not { get; set; }
99
+ public List<DateTime> In { get; set; }
100
+ public List<DateTime> NotIn { get; set; }
101
+ public DateTime? Gt { get; set; }
102
+ public DateTime? Gte { get; set; }
103
+ public DateTime? Lt { get; set; }
104
+ public DateTime? Lte { get; set; }
105
+ }
106
+
107
+ `;
108
+
109
+ for (const model of models) {
110
+ const name = this.capitalize(model.name);
111
+ const csType = (f: { type: string; isOptional: boolean }) => this.mapType(f.type, f.isOptional);
112
+
113
+ content += ` // ── ${name} ORM Types ──────────────────────────────────────────────────────\n\n`;
114
+
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
+
127
+ // OrderByInput
128
+ content += ` /// <summary>Type-safe ORDER BY for ${name} queries.</summary>\n`;
129
+ content += ` public class ${name}OrderByInput\n {\n`;
130
+ for (const f of model.fields) {
131
+ content += ` public string ${this.capitalize(f.name)} { get; set; } // "asc" or "desc"\n`;
132
+ }
133
+ content += ` }\n\n`;
134
+
135
+ // CreateInput
136
+ content += ` /// <summary>Typed data for creating a new ${name} record.</summary>\n`;
137
+ content += ` public class ${name}CreateInput\n {\n`;
138
+ for (const f of model.fields) {
139
+ if (!f.isId) {
140
+ const ct = this.mapType(f.type, f.isOptional || f.hasDefault);
141
+ content += ` public ${ct} ${this.capitalize(f.name)} { get; set; }\n`;
142
+ }
143
+ }
144
+ content += ` }\n\n`;
145
+
146
+ // UpdateInput
147
+ content += ` /// <summary>Typed data for updating an existing ${name} record.</summary>\n`;
148
+ content += ` public class ${name}UpdateInput\n {\n`;
149
+ for (const f of model.fields) {
150
+ if (!f.isId) {
151
+ const ut = this.mapType(f.type, true); // always nullable for update
152
+ content += ` public ${ut} ${this.capitalize(f.name)} { get; set; }\n`;
153
+ }
154
+ }
155
+ content += ` }\n\n`;
156
+
157
+ // FindManyArgs
158
+ content += ` /// <summary>ORM-style args for ${name}.FindMany().</summary>\n`;
159
+ content += ` public class ${name}FindManyArgs\n {\n`;
160
+ content += ` public ${name}WhereInput Where { get; set; }\n`;
161
+ content += ` public ${name}OrderByInput OrderBy { get; set; }\n`;
162
+ content += ` public int? Take { get; set; }\n`;
163
+ content += ` public int? Skip { get; set; }\n`;
164
+ content += ` public List<string> Select { get; set; }\n`;
165
+ content += ` }\n\n`;
166
+
167
+ // FindFirstArgs
168
+ content += ` /// <summary>ORM-style args for ${name}.FindFirst().</summary>\n`;
169
+ content += ` public class ${name}FindFirstArgs\n {\n`;
170
+ content += ` public ${name}WhereInput Where { get; set; }\n`;
171
+ content += ` public ${name}OrderByInput OrderBy { get; set; }\n`;
172
+ content += ` public List<string> Select { get; set; }\n`;
173
+ content += ` }\n\n`;
174
+
175
+ // FindUniqueArgs
176
+ content += ` /// <summary>ORM-style args for ${name}.FindUnique().</summary>\n`;
177
+ content += ` public class ${name}FindUniqueArgs\n {\n`;
178
+ content += ` public ${name}WhereInput Where { get; set; }\n`;
179
+ content += ` }\n\n`;
180
+
181
+ // DeleteManyArgs
182
+ content += ` /// <summary>ORM-style args for ${name}.DeleteMany().</summary>\n`;
183
+ content += ` public class ${name}DeleteManyArgs\n {\n`;
184
+ content += ` public ${name}WhereInput Where { get; set; }\n`;
185
+ content += ` }\n\n`;
186
+
187
+ // CountArgs
188
+ content += ` /// <summary>ORM-style args for ${name}.Count().</summary>\n`;
189
+ content += ` public class ${name}CountArgs\n {\n`;
190
+ content += ` public ${name}WhereInput Where { get; set; }\n`;
191
+ content += ` }\n\n`;
192
+ }
193
+
194
+ content += `}\n`;
195
+
196
+ fs.writeFileSync(path.join(this.outputDir, 'An5OrmTypes.cs'), content);
197
+ }
198
+
25
199
  private generateConfigClass() {
26
200
  const content = `// This file is auto-generated. Do not edit directly.
27
201
  using System;
@@ -115,6 +289,7 @@ using System;
115
289
  using System.Collections.Generic;
116
290
  using System.Data;
117
291
  using System.Reflection;
292
+ using System.Text.Json;
118
293
  using Microsoft.Data.SqlClient;
119
294
  using An5Orm.Entities;
120
295
 
@@ -166,7 +341,9 @@ namespace An5Orm
166
341
  `;
167
342
 
168
343
  for (const model of models) {
169
- content += ` public TableClient<${model.name}> ${this.capitalize(model.name)}s => new TableClient<${model.name}>(ConnectionString, "${model.schemaName}.${model.tableName}");\n`;
344
+ const name = this.capitalize(model.name);
345
+ content += ` public TableClient<${model.name}> ${name}s => new TableClient<${model.name}>(ConnectionString, "${model.schemaName}.${model.tableName}");\n`;
346
+ content += ` public TableClient<${model.name}> ${name} => ${name}s;\n`;
170
347
  }
171
348
 
172
349
  content += ` }
@@ -231,6 +408,51 @@ namespace An5Orm
231
408
  return cmd;
232
409
  }
233
410
 
411
+ public List<T> QueryRaw(string query, Dictionary<string, object> parameters = null)
412
+ {
413
+ var list = new List<T>();
414
+ var conn = An5DbContext.GetActiveConnection(ConnectionString, out bool isTx);
415
+ try
416
+ {
417
+ using (var cmd = CreateCommand(conn, query))
418
+ {
419
+ if (parameters != null)
420
+ {
421
+ foreach (var kvp in parameters)
422
+ {
423
+ cmd.Parameters.AddWithValue(kvp.Key.StartsWith("@") ? kvp.Key : "@" + kvp.Key, kvp.Value ?? DBNull.Value);
424
+ }
425
+ }
426
+
427
+ using (var reader = cmd.ExecuteReader())
428
+ {
429
+ var properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
430
+ while (reader.Read())
431
+ {
432
+ var item = new T();
433
+ foreach (var prop in properties)
434
+ {
435
+ if (HasColumn(reader, prop.Name))
436
+ {
437
+ var val = reader[prop.Name];
438
+ if (val != DBNull.Value)
439
+ {
440
+ prop.SetValue(item, val);
441
+ }
442
+ }
443
+ }
444
+ list.Add(item);
445
+ }
446
+ }
447
+ }
448
+ }
449
+ finally
450
+ {
451
+ if (!isTx) conn.Dispose();
452
+ }
453
+ return list;
454
+ }
455
+
234
456
  public List<T> FindMany(string whereClause = null, Dictionary<string, object> parameters = null)
235
457
  {
236
458
  var list = new List<T>();
@@ -439,15 +661,132 @@ namespace An5Orm
439
661
  }
440
662
  }
441
663
 
664
+ public int Count(string whereClause = null, Dictionary<string, object> parameters = null)
665
+ {
666
+ string query = $"SELECT COUNT(*) FROM {TableName}";
667
+ if (!string.IsNullOrEmpty(whereClause)) query += $" WHERE {whereClause}";
668
+ var conn = An5DbContext.GetActiveConnection(ConnectionString, out bool isTx);
669
+ try
670
+ {
671
+ using (var cmd = CreateCommand(conn, query))
672
+ {
673
+ if (parameters != null)
674
+ {
675
+ foreach (var kvp in parameters)
676
+ cmd.Parameters.AddWithValue(kvp.Key.StartsWith("@") ? kvp.Key : "@" + kvp.Key, kvp.Value ?? DBNull.Value);
677
+ }
678
+ var res = cmd.ExecuteScalar();
679
+ return res != null && res != DBNull.Value ? Convert.ToInt32(res) : 0;
680
+ }
681
+ }
682
+ finally { if (!isTx) conn.Dispose(); }
683
+ }
684
+
685
+ public int CreateMany(IEnumerable<T> entities)
686
+ {
687
+ int count = 0;
688
+ foreach (var entity in entities)
689
+ {
690
+ Create(entity);
691
+ count++;
692
+ }
693
+ return count;
694
+ }
695
+
696
+ public int UpdateMany(string whereClause, Dictionary<string, object> updateData, Dictionary<string, object> parameters = null)
697
+ {
698
+ if (updateData == null || updateData.Count == 0) return 0;
699
+ var sets = new List<string>();
700
+ var sqlParams = new List<SqlParameter>();
701
+ int pIndex = 0;
702
+ foreach (var kvp in updateData)
703
+ {
704
+ string paramName = "@u_" + pIndex++;
705
+ sets.Add($"{kvp.Key} = {paramName}");
706
+ sqlParams.Add(new SqlParameter(paramName, kvp.Value ?? DBNull.Value));
707
+ }
708
+ string query = $"UPDATE {TableName} SET {string.Join(", ", sets)}";
709
+ if (!string.IsNullOrEmpty(whereClause)) query += $" WHERE {whereClause}";
710
+ var conn = An5DbContext.GetActiveConnection(ConnectionString, out bool isTx);
711
+ try
712
+ {
713
+ using (var cmd = CreateCommand(conn, query))
714
+ {
715
+ cmd.Parameters.AddRange(sqlParams.ToArray());
716
+ if (parameters != null)
717
+ {
718
+ foreach (var kvp in parameters)
719
+ cmd.Parameters.AddWithValue(kvp.Key.StartsWith("@") ? kvp.Key : "@" + kvp.Key, kvp.Value ?? DBNull.Value);
720
+ }
721
+ return cmd.ExecuteNonQuery();
722
+ }
723
+ }
724
+ finally { if (!isTx) conn.Dispose(); }
725
+ }
726
+
727
+ public int DeleteMany(string whereClause = null, Dictionary<string, object> parameters = null)
728
+ {
729
+ string query = $"DELETE FROM {TableName}";
730
+ if (!string.IsNullOrEmpty(whereClause)) query += $" WHERE {whereClause}";
731
+ var conn = An5DbContext.GetActiveConnection(ConnectionString, out bool isTx);
732
+ try
733
+ {
734
+ using (var cmd = CreateCommand(conn, query))
735
+ {
736
+ if (parameters != null)
737
+ {
738
+ foreach (var kvp in parameters)
739
+ cmd.Parameters.AddWithValue(kvp.Key.StartsWith("@") ? kvp.Key : "@" + kvp.Key, kvp.Value ?? DBNull.Value);
740
+ }
741
+ return cmd.ExecuteNonQuery();
742
+ }
743
+ }
744
+ finally { if (!isTx) conn.Dispose(); }
745
+ }
746
+
747
+ public T Upsert(T entity, string idColumnName = "Id")
748
+ {
749
+ var prop = typeof(T).GetProperty(idColumnName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
750
+ if (prop == null) throw new InvalidOperationException($"Property '{idColumnName}' not found on entity.");
751
+ var idVal = prop.GetValue(entity);
752
+ var existing = idVal != null ? FindUnique(idVal) : default;
753
+ if (existing != null) return Update(entity);
754
+ return Create(entity);
755
+ }
756
+
442
757
  public List<T> VectorSearch(List<double> vector, int take = 10, string whereClause = null, Dictionary<string, object> parameters = null, string vectorField = "Embedding", string distanceMetric = "cosine")
443
758
  {
759
+ // 1. Primary path: Native database SQL vector query execution (VECTOR_DISTANCE)
760
+ try
761
+ {
762
+ var dim = vector.Count;
763
+ var vecJson = JsonSerializer.Serialize(vector);
764
+ 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)";
765
+
766
+ var p = parameters != null ? new Dictionary<string, object>(parameters) : new Dictionary<string, object>();
767
+ p["query_vector"] = vecJson;
768
+
769
+ if (!string.IsNullOrWhiteSpace(whereClause))
770
+ sql += $" WHERE [{vectorField}] IS NOT NULL AND ({whereClause})";
771
+ else
772
+ sql += $" WHERE [{vectorField}] IS NOT NULL";
773
+ sql += " ORDER BY distance ASC";
774
+
775
+ var nativeRows = QueryRaw(sql, p);
776
+ if (nativeRows != null) return nativeRows;
777
+ }
778
+ catch
779
+ {
780
+ // Fallback to in-memory similarity computation if DB instance lacks native VECTOR_DISTANCE
781
+ }
782
+
783
+ // 2. Secondary fallback: In-memory similarity computation
444
784
  var rows = FindMany(whereClause, parameters);
445
785
  var results = new List<Tuple<T, double>>();
446
786
 
447
787
  var propInfo = typeof(T).GetProperty(vectorField, BindingFlags.Public | BindingFlags.Instance);
448
788
  if (propInfo == null)
449
789
  {
450
- // Fallback case-insensitive
451
790
  propInfo = typeof(T).GetProperty(vectorField, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
452
791
  }
453
792
  if (propInfo == null) return rows;
@@ -477,12 +816,11 @@ namespace An5Orm
477
816
  }
478
817
 
479
818
  results.Sort((a, b) => a.Item2.CompareTo(b.Item2));
480
-
819
+
481
820
  var output = new List<T>();
482
821
  int limit = Math.Min(take, results.Count);
483
822
  for (int i = 0; i < limit; i++)
484
823
  {
485
- // Set the distance property if the model has a public property named Distance
486
824
  var distanceProp = typeof(T).GetProperty("Distance", BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
487
825
  if (distanceProp != null && distanceProp.PropertyType == typeof(double))
488
826
  {