@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.
- package/CHANGELOG.md +96 -1
- package/README.md +3 -3
- package/package.json +3 -3
- package/src/casts/encrypted.ts +168 -0
- package/src/index.ts +18 -11
- package/src/model/BaseModel.ts +102 -9
- package/src/model/ModelQueryBuilder.ts +18 -0
- package/src/model/SoftDeletes.ts +3 -3
- package/src/model/State.ts +3 -3
- package/src/model/decorators/column.ts +17 -1
- package/src/model/decorators/table.ts +2 -2
- package/src/model/mixins.ts +126 -501
- package/src/schema/ModelInspector.ts +30 -3
|
@@ -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(
|
|
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
|
-
|
|
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
|
};
|