@an5/orm 1.1.1 → 1.3.0

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 (39) hide show
  1. package/README.md +5 -1
  2. package/dist/generator/src/api.d.ts +5 -0
  3. package/dist/generator/src/api.js +11 -1
  4. package/dist/generator/src/code-generator.js +12 -1
  5. package/dist/generator/src/code-request.d.ts +26 -0
  6. package/dist/generator/src/code-request.js +122 -0
  7. package/dist/generator/src/config.d.ts +18 -0
  8. package/dist/generator/src/config.js +24 -0
  9. package/dist/generator/src/dialect.js +4 -2
  10. package/dist/generator/src/dotnet-generator.js +97 -1
  11. package/dist/generator/src/field-types.js +3 -0
  12. package/dist/generator/src/golang-generator.d.ts +8 -0
  13. package/dist/generator/src/golang-generator.js +183 -9
  14. package/dist/generator/src/index.js +18 -0
  15. package/dist/generator/src/java-generator.d.ts +56 -0
  16. package/dist/generator/src/java-generator.js +1406 -0
  17. package/dist/generator/src/kotlin-generator.d.ts +34 -0
  18. package/dist/generator/src/kotlin-generator.js +753 -0
  19. package/dist/generator/src/metadata-generator.js +3 -1
  20. package/dist/generator/src/rust-generator.js +36 -1
  21. package/dist/generator/src/swift-generator.d.ts +42 -0
  22. package/dist/generator/src/swift-generator.js +768 -0
  23. package/dist/migration-core.d.ts +1 -1
  24. package/dist/migration-core.js +6 -4
  25. package/generator/src/api.ts +5 -0
  26. package/generator/src/code-generator.ts +13 -1
  27. package/generator/src/code-request.ts +95 -0
  28. package/generator/src/config.ts +48 -0
  29. package/generator/src/dialect.ts +5 -3
  30. package/generator/src/dotnet-generator.ts +97 -1
  31. package/generator/src/field-types.ts +3 -0
  32. package/generator/src/golang-generator.ts +184 -9
  33. package/generator/src/index.ts +21 -0
  34. package/generator/src/java-generator.ts +1457 -0
  35. package/generator/src/kotlin-generator.ts +798 -0
  36. package/generator/src/metadata-generator.ts +2 -1
  37. package/generator/src/rust-generator.ts +37 -1
  38. package/generator/src/swift-generator.ts +830 -0
  39. package/package.json +2 -2
@@ -186,6 +186,7 @@ func GetDefaultConnectionString() string {
186
186
  content += this.generateWhereBuilder();
187
187
  content += this.generateCrudMethods();
188
188
  content += this.generateVectorSearch();
189
+ content += this.generateVectorHelpers();
189
190
  content += this.generateHelpers();
190
191
  // NOTE: per-model ORM types live in each `<Model>.go` file.
191
192
  fs_1.default.writeFileSync(path_1.default.join(this.outputDir, 'client.go'), content);
@@ -401,9 +402,18 @@ func (c *TableClient[T]) queryAndScan(ctx context.Context, query string, args ..
401
402
  \t\tvar item T
402
403
  \t\tval := reflect.ValueOf(&item).Elem()
403
404
  \t\tscanArgs := make([]interface{}, len(cols))
405
+ \t\tvectorCols := make([]bool, len(cols))
404
406
  \t\tfor i, colName := range cols {
405
407
  \t\t\tfieldVal := findFieldByDbTag(val, colName)
406
408
  \t\t\tif fieldVal.IsValid() && fieldVal.CanSet() {
409
+ \t\t\t\t// A \`VECTOR(n)\` column is stored as bytes, which database/sql cannot
410
+ \t\t\t\t// scan into a slice, so it is read as bytes and decoded afterwards.
411
+ \t\t\t\tif isVectorGoType(fieldVal.Type()) {
412
+ \t\t\t\t\tvar raw []byte
413
+ \t\t\t\t\tscanArgs[i] = &raw
414
+ \t\t\t\t\tvectorCols[i] = true
415
+ \t\t\t\t\tcontinue
416
+ \t\t\t\t}
407
417
  \t\t\t\tscanArgs[i] = fieldVal.Addr().Interface()
408
418
  \t\t\t} else {
409
419
  \t\t\t\tvar unused interface{}
@@ -413,6 +423,20 @@ func (c *TableClient[T]) queryAndScan(ctx context.Context, query string, args ..
413
423
  \t\tif err := rows.Scan(scanArgs...); err != nil {
414
424
  \t\t\treturn nil, err
415
425
  \t\t}
426
+ \t\tfor i, isVector := range vectorCols {
427
+ \t\t\tif !isVector {
428
+ \t\t\t\tcontinue
429
+ \t\t\t}
430
+ \t\t\tdecoded, ok := decodeVectorBytes(*(scanArgs[i].(*[]byte)))
431
+ \t\t\tif !ok {
432
+ \t\t\t\tcontinue
433
+ \t\t\t}
434
+ \t\t\tfieldVal := findFieldByDbTag(val, cols[i])
435
+ \t\t\tif !fieldVal.IsValid() || !fieldVal.CanSet() {
436
+ \t\t\t\tcontinue
437
+ \t\t\t}
438
+ \t\t\tsetVectorField(fieldVal, decoded)
439
+ \t\t}
416
440
  \t\tresults = append(results, item)
417
441
  \t}
418
442
  \tif err := rows.Err(); err != nil {
@@ -421,6 +445,160 @@ func (c *TableClient[T]) queryAndScan(ctx context.Context, query string, args ..
421
445
  \treturn results, nil
422
446
  }
423
447
 
448
+ `;
449
+ }
450
+ /**
451
+ * The vector codec the generated client needs on both sides of a query.
452
+ *
453
+ * A \`VECTOR(n)\` column stores float32 bytes, which is a third of the JSON text it
454
+ * replaces and is what the in-database distance functions read. A column written
455
+ * before that encoding holds JSON text, so both are still accepted on the way in.
456
+ */
457
+ generateVectorHelpers() {
458
+ return `// bytesPerVectorFloat is the width of one stored vector component.
459
+ const bytesPerVectorFloat = 4
460
+
461
+ // isVectorGoType reports whether a model field is a vector column, which the
462
+ // generator declares as a float32 slice, optionally behind a pointer because the
463
+ // column is nullable.
464
+ func isVectorGoType(t reflect.Type) bool {
465
+ \treturn vectorSliceType(t) != nil
466
+ }
467
+
468
+ // vectorSliceType returns the slice type a vector field holds, or nil for any
469
+ // other field. A pointer to a slice is unwrapped so both the nullable and the
470
+ // required spelling are recognised.
471
+ func vectorSliceType(t reflect.Type) reflect.Type {
472
+ \tif t.Kind() == reflect.Ptr {
473
+ \t\tt = t.Elem()
474
+ \t}
475
+ \tif t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Float32 {
476
+ \t\treturn t
477
+ \t}
478
+ \treturn nil
479
+ }
480
+
481
+ // vectorComponents reads a vector field as float64 components.
482
+ //
483
+ // A field can arrive as JSON text, as a slice, or behind a pointer to either, and a
484
+ // pointer is how the generator spells a nullable column. Anything else is not a vector.
485
+ func vectorComponents(field reflect.Value) []float64 {
486
+ \tfor field.Kind() == reflect.Ptr {
487
+ \t\tif field.IsNil() {
488
+ \t\t\treturn nil
489
+ \t\t}
490
+ \t\tfield = field.Elem()
491
+ \t}
492
+ \tswitch {
493
+ \tcase field.Kind() == reflect.String:
494
+ \t\tvar parsed []float64
495
+ \t\tif err := json.Unmarshal([]byte(field.String()), &parsed); err != nil {
496
+ \t\t\treturn nil
497
+ \t\t}
498
+ \t\treturn parsed
499
+ \tcase field.Kind() == reflect.Slice:
500
+ \t\tout := make([]float64, field.Len())
501
+ \t\tfor i := range out {
502
+ \t\t\tout[i] = field.Index(i).Float()
503
+ \t\t}
504
+ \t\treturn out
505
+ \tdefault:
506
+ \t\treturn nil
507
+ \t}
508
+ }
509
+
510
+ // setVectorField stores decoded components into a vector field, matching whether
511
+ // the field holds the slice directly or behind a pointer.
512
+ func setVectorField(field reflect.Value, values []float64) {
513
+ \tsliceType := vectorSliceType(field.Type())
514
+ \tif sliceType == nil {
515
+ \t\treturn
516
+ \t}
517
+ \tslice := reflect.MakeSlice(sliceType, len(values), len(values))
518
+ \tfor i, value := range values {
519
+ \t\tslice.Index(i).SetFloat(value)
520
+ \t}
521
+ \tif field.Kind() == reflect.Ptr {
522
+ \t\tptr := reflect.New(sliceType)
523
+ \t\tptr.Elem().Set(slice)
524
+ \t\tfield.Set(ptr)
525
+ \t\treturn
526
+ \t}
527
+ \tfield.Set(slice)
528
+ }
529
+
530
+ // encodeVectorArg converts a vector field into the bytes the column stores.
531
+ //
532
+ // database/sql accepts only a fixed set of types, so a slice otherwise reaches the
533
+ // driver as an error. Values that already arrived as stored form pass through.
534
+ func encodeVectorArg(value interface{}) interface{} {
535
+ \tswitch v := value.(type) {
536
+ \tcase *[]float32:
537
+ \t\tif v == nil {
538
+ \t\t\treturn nil
539
+ \t\t}
540
+ \t\treturn encodeVectorFloats(floatsFrom32(*v))
541
+ \tcase []float32:
542
+ \t\treturn encodeVectorFloats(floatsFrom32(v))
543
+ \tcase *[]float64:
544
+ \t\tif v == nil {
545
+ \t\t\treturn nil
546
+ \t\t}
547
+ \t\treturn encodeVectorFloats(*v)
548
+ \tcase []float64:
549
+ \t\treturn encodeVectorFloats(v)
550
+ \tdefault:
551
+ \t\treturn value
552
+ \t}
553
+ }
554
+
555
+ func floatsFrom32(values []float32) []float64 {
556
+ \tout := make([]float64, len(values))
557
+ \tfor i, value := range values {
558
+ \t\tout[i] = float64(value)
559
+ \t}
560
+ \treturn out
561
+ }
562
+
563
+ // encodeVectorFloats writes float32 values little-endian, which is the order the
564
+ // stored bytes are read back in.
565
+ func encodeVectorFloats(values []float64) []byte {
566
+ \tout := make([]byte, len(values)*bytesPerVectorFloat)
567
+ \tfor i, value := range values {
568
+ \t\toffset := i * bytesPerVectorFloat
569
+ \t\tbits := math.Float32bits(float32(value))
570
+ \t\tout[offset] = byte(bits)
571
+ \t\tout[offset+1] = byte(bits >> 8)
572
+ \t\tout[offset+2] = byte(bits >> 16)
573
+ \t\tout[offset+3] = byte(bits >> 24)
574
+ \t}
575
+ \treturn out
576
+ }
577
+
578
+ // decodeVectorBytes reads a stored vector: float32 bytes, legacy JSON text, or
579
+ // nothing at all.
580
+ func decodeVectorBytes(raw []byte) ([]float64, bool) {
581
+ \tif len(raw) == 0 {
582
+ \t\treturn nil, false
583
+ \t}
584
+ \t// JSON text is not a multiple of four bytes once encoded and starts with '[',
585
+ \t// so the two are told apart by length and first byte.
586
+ \tif raw[0] == '[' || len(raw)%bytesPerVectorFloat != 0 {
587
+ \t\tvar parsed []float64
588
+ \t\tif err := json.Unmarshal(raw, &parsed); err != nil || len(parsed) == 0 {
589
+ \t\t\treturn nil, false
590
+ \t\t}
591
+ \t\treturn parsed, true
592
+ \t}
593
+ \tout := make([]float64, len(raw)/bytesPerVectorFloat)
594
+ \tfor i := range out {
595
+ \t\toffset := i * bytesPerVectorFloat
596
+ \t\tbits := uint32(raw[offset]) | uint32(raw[offset+1])<<8 | uint32(raw[offset+2])<<16 | uint32(raw[offset+3])<<24
597
+ \t\tout[i] = float64(math.Float32frombits(bits))
598
+ \t}
599
+ \treturn out, true
600
+ }
601
+
424
602
  `;
425
603
  }
426
604
  // ─── WHERE builder ─────────────────────────────────────────────────────────
@@ -637,7 +815,7 @@ func (c *TableClient[T]) Create(ctx context.Context, entity *T) (*T, error) {
637
815
  \t\tfieldVal := val.Field(i)
638
816
  \t\tif !fieldVal.IsZero() {
639
817
  \t\t\tcols = append(cols, c.quoteName(dbTag))
640
- \t\t\targs = append(args, fieldVal.Interface())
818
+ \t\t\targs = append(args, encodeVectorArg(fieldVal.Interface()))
641
819
  \t\t\tphs = append(phs, c.ph(len(args)))
642
820
  \t\t}
643
821
  \t}
@@ -684,7 +862,7 @@ func (c *TableClient[T]) Update(ctx context.Context, entity *T) (*T, error) {
684
862
  \t\t\tidVal = fieldVal.Interface()
685
863
  \t\t\tidCol = dbTag
686
864
  \t\t} else if !fieldVal.IsZero() {
687
- \t\t\targs = append(args, fieldVal.Interface())
865
+ \t\t\targs = append(args, encodeVectorArg(fieldVal.Interface()))
688
866
  \t\t\tsets = append(sets, c.quoteName(dbTag)+" = "+c.ph(len(args)))
689
867
  \t\t}
690
868
  \t}
@@ -1031,13 +1209,9 @@ func (c *TableClient[T]) VectorSearch(ctx context.Context, vector []float64, opt
1031
1209
  \t\t\tcontinue
1032
1210
  \t\t}
1033
1211
 
1034
- \t\tvar rowVec []float64
1035
- \t\tif fieldVal.Kind() == reflect.String {
1036
- \t\t\t_ = json.Unmarshal([]byte(fieldVal.String()), &rowVec)
1037
- \t\t} else if fieldVal.Kind() == reflect.Slice {
1038
- \t\t\tfor i := 0; i < fieldVal.Len(); i++ {
1039
- \t\t\t\trowVec = append(rowVec, fieldVal.Index(i).Float())
1040
- \t\t\t}
1212
+ \t\trowVec := vectorComponents(fieldVal)
1213
+ \t\tif len(rowVec) == 0 {
1214
+ \t\t\tcontinue
1041
1215
  \t\t}
1042
1216
  \t\tif len(rowVec) != len(vector) {
1043
1217
  \t\t\tcontinue
@@ -13,6 +13,9 @@ const python_generator_1 = require("./python-generator");
13
13
  const dotnet_generator_1 = require("./dotnet-generator");
14
14
  const golang_generator_1 = require("./golang-generator");
15
15
  const rust_generator_1 = require("./rust-generator");
16
+ const java_generator_1 = require("./java-generator");
17
+ const kotlin_generator_1 = require("./kotlin-generator");
18
+ const swift_generator_1 = require("./swift-generator");
16
19
  const fs_1 = __importDefault(require("fs"));
17
20
  function clearGeneratedFiles(outputDir, extension) {
18
21
  if (!fs_1.default.existsSync(outputDir)) {
@@ -70,6 +73,9 @@ async function main() {
70
73
  const outputDotnetDir = config.outputs.dotnetDir;
71
74
  const outputGolangDir = config.outputs.golangDir;
72
75
  const outputRustDir = config.outputs.rustDir;
76
+ const outputJavaDir = config.outputs.javaDir;
77
+ const outputKotlinDir = config.outputs.kotlinDir;
78
+ const outputSwiftDir = config.outputs.swiftDir;
73
79
  const generateMetadata = config.config.generation.generateMetadata;
74
80
  console.log('🚀 Starting ORM generation...');
75
81
  // The target provider comes from the connection string: both the allowed
@@ -81,6 +87,9 @@ async function main() {
81
87
  clearGeneratedFiles(outputDotnetDir, '.cs');
82
88
  clearGeneratedFiles(outputGolangDir, '.go');
83
89
  clearGeneratedFiles(path_1.default.join(outputRustDir, 'src'), '.rs');
90
+ clearGeneratedFiles(outputJavaDir, '.java');
91
+ clearGeneratedFiles(outputKotlinDir, '.kt');
92
+ clearGeneratedFiles(path_1.default.join(outputSwiftDir, 'Sources'), '.swift');
84
93
  const pythonDirEarly = path_1.default.dirname(outputPythonMetadataPath);
85
94
  clearGeneratedPythonFiles(pythonDirEarly);
86
95
  if (config.config.generation.generateMetadata && fs_1.default.existsSync(outputMetadataPath)) {
@@ -120,6 +129,15 @@ async function main() {
120
129
  const rustGen = new rust_generator_1.RustGenerator(outputRustDir);
121
130
  rustGen.generate(models);
122
131
  console.log(`✨ Generated Rust client in ${outputRustDir}`);
132
+ const javaGen = new java_generator_1.JavaGenerator(outputJavaDir);
133
+ javaGen.generate(models);
134
+ console.log(`✨ Generated Java client in ${outputJavaDir}`);
135
+ const kotlinGen = new kotlin_generator_1.KotlinGenerator(outputKotlinDir);
136
+ kotlinGen.generate(models);
137
+ console.log(`✨ Generated Kotlin client in ${outputKotlinDir}`);
138
+ const swiftGen = new swift_generator_1.SwiftGenerator(outputSwiftDir);
139
+ swiftGen.generate(models);
140
+ console.log(`✨ Generated Swift client in ${outputSwiftDir}`);
123
141
  console.log('✅ ORM generation completed successfully.');
124
142
  }
125
143
  catch (error) {
@@ -0,0 +1,56 @@
1
+ import { Model } from './types';
2
+ export declare class JavaGenerator {
3
+ private outputDir;
4
+ /** The database being generated for; decides types like `TIMESTAMP`. */
5
+ private provider;
6
+ constructor(outputDir: string);
7
+ generate(models: Model[]): void;
8
+ private capitalize;
9
+ /**
10
+ * The Java field name for a schema column.
11
+ *
12
+ * A field named `class`, `new` or `from` would not compile as an identifier, so those are
13
+ * suffixed rather than left to fail at the call site.
14
+ */
15
+ private fieldName;
16
+ private getterName;
17
+ private static toCamelCase;
18
+ private static toLowerCamelCase;
19
+ /**
20
+ * Every spelling of a model name the client registers it under.
21
+ *
22
+ * The metadata is keyed in camelCase while the model class is PascalCase, and the adapter
23
+ * resolves the difference; these are the names the context exposes so either spelling
24
+ * works at the call site.
25
+ */
26
+ private propertyNames;
27
+ /**
28
+ * The Java type for a column.
29
+ *
30
+ * Driven by the declared type, so a `DECIMAL` column is a `BigDecimal` and not an `int`:
31
+ * both arrive from the parser as `number`, which is how the earlier clients turned
32
+ * decimal money into integers.
33
+ */
34
+ private mapType;
35
+ /**
36
+ * The boxed form used in a model.
37
+ *
38
+ * Every field is boxed, primitives included: `toValues` skips the columns left null, and a
39
+ * column the schema does not mark optional can still read NULL until a row is written. A
40
+ * primitive cannot represent "unset", so the skip would have to give the column a default
41
+ * and quietly write it.
42
+ */
43
+ private mapNullableType;
44
+ private getFilterType;
45
+ private generateModels;
46
+ private modelSource;
47
+ /** The expression that reads one column into its field. */
48
+ private readExpression;
49
+ private generateValues;
50
+ private generateOrmTypes;
51
+ private generateMetadata;
52
+ private generateConfig;
53
+ private modelClientSource;
54
+ private generateDbContext;
55
+ private tableName;
56
+ }