@zerotal/orm 1.1.0 → 1.4.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.
@@ -1,6 +1,8 @@
1
1
  import path from "node:path";
2
2
  import type { ColumnOptions } from "../model/decorators/column.ts";
3
3
  import { columnRegistry, columnsFor } from "../model/decorators/_metadata.ts";
4
+ import { ctorChain } from "../support/identifiers.ts";
5
+ import { isEncryptedCast } from "../casts/encrypted.ts";
4
6
 
5
7
  // ── Model schema descriptor ───────────────────────────────────────────────────
6
8
 
@@ -54,12 +56,22 @@ function collectColumns(ctor: Function): Map<string, ColumnOptions> | null {
54
56
  return columnsFor(ctor);
55
57
  }
56
58
 
57
- function toModelColumns(fields: Map<string, ColumnOptions>): ModelColumn[] {
59
+ function toModelColumns(
60
+ fields: Map<string, ColumnOptions>,
61
+ encrypted: ReadonlySet<string>,
62
+ ): ModelColumn[] {
58
63
  const columns: ModelColumn[] = [];
59
64
  for (const [name, opts] of fields.entries()) {
60
65
  columns.push({
61
66
  name,
62
- type: opts.type,
67
+ // An encrypted column is generated as TEXT whatever it was declared as. The
68
+ // stored payload is the plaintext plus 28 bytes of IV and auth tag, base64'd —
69
+ // about 1.4× longer — so a VARCHAR(255) that comfortably held the value no
70
+ // longer holds its ciphertext. MySQL outside strict mode truncates rather than
71
+ // failing, and a truncated payload will not decrypt: the row is lost, quietly,
72
+ // at write time. The generated migration says `table.text(...)`, so the
73
+ // widening is visible in review rather than only here.
74
+ type: encrypted.has(name) ? "text" : opts.type,
63
75
  nullable: opts.nullable ?? false,
64
76
  primary: opts.primary ?? false,
65
77
  default: opts.default,
@@ -70,6 +82,21 @@ function toModelColumns(fields: Map<string, ColumnOptions>): ModelColumn[] {
70
82
  return columns;
71
83
  }
72
84
 
85
+ /** Columns encrypted by either route — `cast: "encrypted…"` or `static encryptable`. */
86
+ function encryptedColumns(
87
+ chain: readonly object[],
88
+ fields: Map<string, ColumnOptions>,
89
+ ): Set<string> {
90
+ const names = new Set<string>();
91
+ for (const [name, opts] of fields.entries()) {
92
+ if (isEncryptedCast(opts.cast)) names.add(name);
93
+ }
94
+ for (const entry of chain) {
95
+ for (const key of (entry as { encryptable?: string[] }).encryptable ?? []) names.add(key);
96
+ }
97
+ return names;
98
+ }
99
+
73
100
  // ── ModelInspector ────────────────────────────────────────────────────────────
74
101
 
75
102
  /**
@@ -133,7 +160,7 @@ export const ModelInspector = {
133
160
  primaryKey: (M["primaryKey"] as string | undefined) ?? "id",
134
161
  timestamps: (M["timestamps"] as boolean | undefined) ?? true,
135
162
  softDeletes: (M["softDeletes"] as boolean | undefined) ?? false,
136
- columns: toModelColumns(fields),
163
+ columns: toModelColumns(fields, encryptedColumns(ctorChain(ctor), fields)),
137
164
  };
138
165
  },
139
166
  };