@vollcrypt/db-guard 0.1.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/README.md ADDED
@@ -0,0 +1,271 @@
1
+ # db-guard
2
+
3
+ Application-level, field-level encryption integrations for ORMs (Prisma, Mongoose, Drizzle, TypeORM, Diesel, SeaORM) powered by FIPS-compliant cryptography.
4
+
5
+ `db-guard` secures sensitive database columns (SSN, credit card numbers, addresses, personal data) by encrypting them before they hit the database. It prevents data leakage from compromised database dumps, unauthorized database connections, or compromised database administrators (DBAs).
6
+
7
+ ---
8
+
9
+ ## Key Features
10
+
11
+ - **Multi-ORM Support**: Integrations for Node.js (Prisma, Mongoose, Drizzle, TypeORM) and Rust (Diesel, SeaORM).
12
+ - **Dynamic Multi-Tenant Routing**: Dynamically resolves distinct KMS keys or database configurations per request context using AsyncLocalStorage.
13
+ - **Secure Key Cache**: Protects memory from dumps using an ephemeral master key generated randomly at boot. Plaint-text DEKs are wrapped with AES-256-KW and cached with automated TTL eviction and zeroization.
14
+ - **Schema Evolution & Crypto-Agility**: Features backward-compatible prefixes for smooth algorithm transitions without database downtime.
15
+ - **M-of-N Break-Glass Protocol**: Emergency KMS bypass via threshold Ed25519 signature verification.
16
+ - **Compliance Scorecard CLI**: Built-in CLI scans configuration and outputs compliance scorecards for GDPR Article 32, KVKK Article 12, and PCI-DSS v4.0.
17
+ - **Supply Chain Security (SLSA Level 4)**: Build pipeline automatically compiles CycloneDX SBOM and SLSA Level 4 Provenance files, cryptographically signed with Ed25519.
18
+ - **FIPS 140-3 & Post-Quantum Hybrid Transition**: Conforms to FIPS 140-3 logical and physical boundaries with NIST FIPS 203 (ML-KEM) lattice-based algorithms registered for hybrid key exchange.
19
+ - **Hardened Blind Indexing**: Allows exact-match querying on encrypted columns via HKDF-SHA256 shadow columns, avoiding frequency analysis vulnerabilities.
20
+ - **RAM Security**: Aggressive memory zeroization (null-byte writing) for keys and plaintext buffers in memory.
21
+ - **Batch Migration CLI**: Built-in CLI tool to perform chunked shadow database migrations in the background.
22
+
23
+ ---
24
+
25
+ ## Installation
26
+
27
+ For Node.js (Prisma, Mongoose, Drizzle, TypeORM):
28
+ ```bash
29
+ npm install @vollcrypt/db-guard
30
+ ```
31
+
32
+ For Rust (Diesel, SeaORM):
33
+ ```toml
34
+ # Cargo.toml
35
+ [dependencies]
36
+ vollcrypt-db-guard = { path = "db-guard/rust", features = ["sqlite", "sea-orm"] }
37
+ ```
38
+
39
+ ---
40
+
41
+ ## Configuration & Usage
42
+
43
+ ### 1. Prisma ORM (TypeScript)
44
+
45
+ Register `prismaDbGuard` extension on your client:
46
+
47
+ ```typescript
48
+ import { PrismaClient } from '@prisma/client';
49
+ import { prismaDbGuard } from '@vollcrypt/db-guard';
50
+
51
+ const key = Buffer.from('your-secure-32-byte-encryption-key-here');
52
+
53
+ const basePrisma = new PrismaClient();
54
+ export const prisma = basePrisma.$extends(
55
+ prismaDbGuard({
56
+ key,
57
+ models: {
58
+ User: ['credit_card', 'ssn'],
59
+ },
60
+ })
61
+ );
62
+ ```
63
+
64
+ ### 2. Mongoose (TypeScript)
65
+
66
+ Register `mongooseDbGuard` as a schema plugin:
67
+
68
+ ```typescript
69
+ import { Schema, model } from 'mongoose';
70
+ import { mongooseDbGuard } from '@vollcrypt/db-guard';
71
+
72
+ const key = Buffer.from('your-secure-32-byte-encryption-key-here');
73
+
74
+ const UserSchema = new Schema({
75
+ name: String,
76
+ credit_card: String,
77
+ });
78
+
79
+ UserSchema.plugin(mongooseDbGuard, {
80
+ key,
81
+ fields: ['credit_card'],
82
+ });
83
+
84
+ export const User = model('User', UserSchema);
85
+ ```
86
+
87
+ ### 3. Drizzle ORM (TypeScript)
88
+
89
+ Use the `createDrizzleGuard` factory to declare encrypted text columns:
90
+
91
+ ```typescript
92
+ import { pgTable, serial } from 'drizzle-orm/pg-core';
93
+ import { createDrizzleGuard } from '@vollcrypt/db-guard';
94
+
95
+ const guard = createDrizzleGuard({
96
+ key: Buffer.from('your-secure-32-byte-encryption-key-here'),
97
+ });
98
+
99
+ export const users = pgTable('users', {
100
+ id: serial('id').primaryKey(),
101
+ creditCard: guard.pgText('credit_card'), // Automatically encrypted/decrypted
102
+ });
103
+ ```
104
+
105
+ ### 4. TypeORM (TypeScript)
106
+
107
+ Define your entity subscribers using `createTypeOrmSubscriber`:
108
+
109
+ ```typescript
110
+ import { DataSource } from 'typeorm';
111
+ import { createTypeOrmSubscriber } from '@vollcrypt/db-guard';
112
+
113
+ const key = Buffer.from('your-secure-32-byte-encryption-key-here');
114
+
115
+ const VollcryptSubscriber = createTypeOrmSubscriber({
116
+ key,
117
+ entities: {
118
+ User: ['credit_card', 'ssn'],
119
+ },
120
+ });
121
+
122
+ export const AppDataSource = new DataSource({
123
+ subscribers: [VollcryptSubscriber],
124
+ });
125
+ ```
126
+
127
+ ### 5. Diesel (Rust)
128
+
129
+ Use `EncryptedString` in your schema and models:
130
+
131
+ ```rust
132
+ use diesel::prelude::*;
133
+ use vollcrypt_db_guard::diesel_impl::EncryptedString;
134
+
135
+ #[derive(Queryable, Selectable, Insertable)]
136
+ #[diesel(table_name = users)]
137
+ pub struct User {
138
+ pub id: i32,
139
+ pub name: String,
140
+ pub credit_card: EncryptedString,
141
+ }
142
+ ```
143
+
144
+ ### 6. SeaORM (Rust)
145
+
146
+ Use the SeaORM-compatible `EncryptedString` type wrapper:
147
+
148
+ ```rust
149
+ use sea_orm::entity::prelude::*;
150
+ use vollcrypt_db_guard::seaorm_impl::EncryptedString;
151
+
152
+ #[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
153
+ #[sea_orm(table_name = "users")]
154
+ pub struct Model {
155
+ #[sea_orm(primary_key)]
156
+ pub id: i32,
157
+ pub name: String,
158
+ pub credit_card: EncryptedString,
159
+ }
160
+ ```
161
+
162
+ Initialize your keys at application boot for Rust:
163
+ ```rust
164
+ use vollcrypt_db_guard::{set_key, set_active_version};
165
+
166
+ fn main() {
167
+ let key = [0u8; 32]; // Secure 32-byte key
168
+ set_key("1", &key);
169
+ set_active_version("1").unwrap();
170
+ }
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Cloud & On-Premises KMS Providers
176
+
177
+ `db-guard` supports multiple key management systems (KMS) and hardware security modules (HSM) to resolve keys dynamically for envelope encryption.
178
+
179
+ ### 1. Node.js KMS Providers
180
+
181
+ We provide several KmsProvider implementations:
182
+
183
+ - **AwsKmsProvider**: Resolves keys using AWS KMS.
184
+ - **GcpKmsProvider**: Resolves keys using Google Cloud KMS.
185
+ - **VaultKmsProvider**: Resolves keys using HashiCorp Vault.
186
+ - **Pkcs11KmsProvider**: Interacts with physical or virtual HSMs (YubiHSM2, Thales, Nitrokey, SoftHSM2, etc.) using the standard PKCS#11 protocol.
187
+
188
+ #### Node.js PKCS#11 Configuration Example:
189
+ ```typescript
190
+ import { Pkcs11KmsProvider } from '@vollcrypt/db-guard';
191
+
192
+ const kmsProvider = new Pkcs11KmsProvider({
193
+ libraryPath: '/usr/local/lib/softhsm/libsofthsm2.so', // Path to vendor PKCS#11 library
194
+ pin: '123456', // Slot/Token PIN
195
+ slotId: 0, // Target Slot Index (optional, default: 0)
196
+ keyId: '000102', // Hex-encoded CKA_ID of the AES-256 key in HSM
197
+ });
198
+
199
+ // Decrypt wrapped key (DEK) inside HSM
200
+ const decryptedKey = await kmsProvider.decrypt(wrappedKeyBuffer);
201
+ ```
202
+
203
+ ### 2. Rust PKCS#11 Support
204
+
205
+ To use PKCS#11 in Rust, enable the `pkcs11` feature:
206
+ ```toml
207
+ # Cargo.toml
208
+ [dependencies]
209
+ vollcrypt-db-guard = { path = "db-guard/rust", features = ["sqlite", "pkcs11"] }
210
+ ```
211
+
212
+ You can then decrypt wrapped keys directly inside your HSM:
213
+ ```rust
214
+ use vollcrypt_db_guard::pkcs11_impl::decrypt_with_hsm;
215
+
216
+ let decrypted = decrypt_with_hsm(
217
+ "/usr/local/lib/softhsm/libsofthsm2.so", // Path to PKCS#11 module
218
+ "123456", // PIN
219
+ Some(0), // Slot ID
220
+ "010203", // Hex CKA_ID
221
+ &wrapped_data, // Ciphertext containing wrapped DEK
222
+ ).unwrap();
223
+ ```
224
+
225
+ ---
226
+
227
+ ## CLI Commands
228
+
229
+ The package includes a dual-purpose CLI tool for database migrations and compliance auditing.
230
+
231
+ ### 1. Database Migrations (`migrate`)
232
+ Encrypts existing plaintext records in a live database using batch processing:
233
+
234
+ ```bash
235
+ # Run PostgreSQL migration
236
+ npx vollcrypt-db-guard migrate \
237
+ --db-type postgres \
238
+ --db-url "postgres://user:pass@localhost:5432/db" \
239
+ --table users \
240
+ --column credit_card \
241
+ --key "your_32_byte_hex_key_here" \
242
+ --chunk-size 100 \
243
+ --id-col id
244
+
245
+ # Run MongoDB migration
246
+ npx vollcrypt-db-guard migrate \
247
+ --db-type mongodb \
248
+ --db-url "mongodb://localhost:27017/db" \
249
+ --table users \
250
+ --column credit_card \
251
+ --key "your_32_byte_hex_key_here" \
252
+ --chunk-size 100 \
253
+ --id-col _id
254
+ ```
255
+
256
+ ### 2. Compliance Scorecard Generator (`compliance`)
257
+ Scans cryptographic configurations and generates an auditor-ready HTML compliance report:
258
+
259
+ ```bash
260
+ npx vollcrypt-db-guard compliance \
261
+ --config compliance-config.json \
262
+ --output compliance-report.html
263
+ ```
264
+
265
+ ---
266
+
267
+ ## Supply Chain & Compliance Verification
268
+
269
+ Refer to the following standalone validation documentation for formal verification processes:
270
+ - **Supply Chain Artifacts**: Signed CycloneDX SBOM and SLSA Level 4 Provenance are located in `dist/sbom.json` and `dist/provenance.json` post-build.
271
+ - **FIPS 140-3 Boundaries**: Detailed logical boundaries, Approved algorithm registries, and post-quantum hybrid structures are defined in [FIPS_VALIDATION.md](file:///c:/Users/iTopya/Desktop/Project/vollcrypt/db-guard/FIPS_VALIDATION.md).
@@ -0,0 +1,39 @@
1
+ {
2
+ "kms": {
3
+ "provider": "aws",
4
+ "wrappedKey": "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f",
5
+ "wrappedKek": "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f",
6
+ "activeKeyVersion": "1"
7
+ },
8
+ "models": {
9
+ "User": ["email", "credit_card"]
10
+ },
11
+ "blindIndexes": {
12
+ "rootSalt": "000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f",
13
+ "models": {
14
+ "User": ["email"]
15
+ }
16
+ },
17
+ "cryptoRbac": {
18
+ "roles": {
19
+ "ADMIN": {
20
+ "decrypt": ["User.email", "User.credit_card"]
21
+ },
22
+ "SUPPORT": {
23
+ "decrypt": [],
24
+ "mask": {
25
+ "User.credit_card": "credit_card"
26
+ }
27
+ }
28
+ }
29
+ },
30
+ "rateLimiter": {
31
+ "maxDecryptionsPerSecond": 100,
32
+ "mode": "fail_closed",
33
+ "maxPageSize": 100,
34
+ "onPageSizeExceeded": "error"
35
+ },
36
+ "breakGlassThreshold": 2,
37
+ "breakGlassPublicKeys": ["2be3f91a7b204d104b60d3ce756b7555f7c7c437f1470b826d03c877b9349dd8", "1234f91a7b204d104b60d3ce756b7555f7c7c437f1470b826d03c877b9349dd8"],
38
+ "postQuantumEnabled": true
39
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Computes a hardened, frequency-resistant blind index for a database field.
3
+ *
4
+ * Uses HKDF-SHA256 to derive a unique column key from the root salt,
5
+ * preventing cross-column frequency analysis. Zeroizes intermediate keys immediately.
6
+ */
7
+ export declare function computeBlindIndex(value: any, rootSalt: Buffer, columnName: string): string;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.computeBlindIndex = computeBlindIndex;
4
+ const security_1 = require("./security");
5
+ /**
6
+ * Computes a hardened, frequency-resistant blind index for a database field.
7
+ *
8
+ * Uses HKDF-SHA256 to derive a unique column key from the root salt,
9
+ * preventing cross-column frequency analysis. Zeroizes intermediate keys immediately.
10
+ */
11
+ function computeBlindIndex(value, rootSalt, columnName) {
12
+ if (value === null || value === undefined)
13
+ return value;
14
+ const plaintext = typeof value === 'string' ? value : JSON.stringify(value);
15
+ const columnNameBuf = Buffer.from(columnName, 'utf8');
16
+ // 1. Derive column-specific key using HKDF-SHA256
17
+ const derivedColumnKey = (0, security_1.deriveHkdf)(rootSalt, null, columnNameBuf, 32);
18
+ // 2. Compute the final blind index using the derived column key
19
+ const plaintextBuf = Buffer.from(plaintext, 'utf8');
20
+ const blindIndex = (0, security_1.deriveHkdf)(derivedColumnKey, null, plaintextBuf, 32);
21
+ // 3. RAM Security: Zeroize the derived key immediately (Anti-Core Dump)
22
+ derivedColumnKey.fill(0);
23
+ return blindIndex.toString('hex');
24
+ }
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const prisma_1 = require("./prisma");
5
+ function printProgressBar(current, total) {
6
+ const percentage = Math.min(100, Math.floor((current / total) * 100));
7
+ const barLength = 40;
8
+ const completedLength = Math.floor((percentage / 100) * barLength);
9
+ const remainingLength = barLength - completedLength;
10
+ const progressBar = '='.repeat(completedLength) + '>'.repeat(completedLength > 0 && remainingLength > 0 ? 1 : 0) + ' '.repeat(Math.max(0, remainingLength - (completedLength > 0 && remainingLength > 0 ? 1 : 0)));
11
+ process.stdout.write(`\rProgress: [${progressBar}] ${percentage}% (${current}/${total} records)`);
12
+ }
13
+ async function run() {
14
+ const args = process.argv.slice(2);
15
+ if (args.length === 0 || (args[0] !== 'migrate' && args[0] !== 'compliance')) {
16
+ console.error("Usage: vollcrypt-db-guard <command> [options]");
17
+ console.error("\nCommands:");
18
+ console.error(" migrate Run shadow database migrations");
19
+ console.error(" compliance Scan database configurations and generate a compliance HTML scorecard");
20
+ console.error("\nMigrate Options:");
21
+ console.error(" --db-type <postgres|mongodb> Database type");
22
+ console.error(" --db-url <url> Database connection URL");
23
+ console.error(" --table <table-name> Table/collection name to migrate");
24
+ console.error(" --column <column-name> Column/field name to encrypt");
25
+ console.error(" --key <32-byte-hex-key> Encryption key (hex-encoded)");
26
+ console.error(" --active-version <version> Encryption key version (default: 1)");
27
+ console.error(" --chunk-size <size> Batch processing chunk size (default: 100)");
28
+ console.error(" --id-col <id-col-name> Primary key column (default: 'id' / '_id')");
29
+ console.error("\nCompliance Options:");
30
+ console.error(" --config <path-to-json-file> Path to configuration file");
31
+ console.error(" --output <output-html-path> Path to write HTML compliance report (default: compliance-report.html)");
32
+ process.exit(1);
33
+ }
34
+ if (args[0] === 'compliance') {
35
+ const options = {};
36
+ for (let i = 1; i < args.length; i += 2) {
37
+ if (args[i] && args[i + 1]) {
38
+ const key = args[i].replace(/^--/, '');
39
+ const val = args[i + 1];
40
+ options[key] = val;
41
+ }
42
+ }
43
+ const configPath = options['config'];
44
+ const outputPath = options['output'] || 'compliance-report.html';
45
+ if (!configPath) {
46
+ console.error("Error: --config <path-to-json-file> is required for compliance scan.");
47
+ process.exit(1);
48
+ }
49
+ const fs = require('fs');
50
+ const path = require('path');
51
+ try {
52
+ const fullPath = path.resolve(configPath);
53
+ if (!fs.existsSync(fullPath)) {
54
+ console.error(`Error: Configuration file not found at ${fullPath}`);
55
+ process.exit(1);
56
+ }
57
+ const raw = fs.readFileSync(fullPath, 'utf8');
58
+ const config = JSON.parse(raw);
59
+ const { generateComplianceHtmlReport } = require('./compliance');
60
+ const html = generateComplianceHtmlReport(config);
61
+ const outFullPath = path.resolve(outputPath);
62
+ fs.writeFileSync(outFullPath, html, 'utf8');
63
+ console.log(`Compliance report generated successfully at ${outFullPath}`);
64
+ }
65
+ catch (err) {
66
+ console.error(`Error generating compliance report: ${err.message}`);
67
+ process.exit(1);
68
+ }
69
+ return;
70
+ }
71
+ // Parse arguments
72
+ const options = {};
73
+ for (let i = 1; i < args.length; i += 2) {
74
+ if (args[i] && args[i + 1]) {
75
+ const key = args[i].replace(/^--/, '');
76
+ const val = args[i + 1];
77
+ options[key] = val;
78
+ }
79
+ }
80
+ const dbType = options['db-type'];
81
+ const dbUrl = options['db-url'];
82
+ const table = options['table'] || options['collection'];
83
+ const column = options['column'];
84
+ const keyHex = options['key'];
85
+ const activeVersion = options['active-version'] || '1';
86
+ const chunkSize = parseInt(options['chunk-size'] || '100', 10);
87
+ const idCol = options['id-col'] || (dbType === 'mongodb' ? '_id' : 'id');
88
+ if (!dbType || !dbUrl || !table || !column || !keyHex) {
89
+ console.error("Error: Missing required arguments. --db-type, --db-url, --table, --column, and --key are required.");
90
+ process.exit(1);
91
+ }
92
+ if (keyHex.length !== 64) {
93
+ console.error("Error: Key must be a 32-byte hex-encoded string (64 characters).");
94
+ process.exit(1);
95
+ }
96
+ const encryptionKey = Buffer.from(keyHex, 'hex');
97
+ console.log(`Starting shadow migration on: table/collection: "${table}", column: "${column}"`);
98
+ console.log(`Active key version: "${activeVersion}", Batch size: ${chunkSize}`);
99
+ if (dbType === 'postgres') {
100
+ await migratePostgres(dbUrl, table, column, idCol, encryptionKey, activeVersion, chunkSize);
101
+ }
102
+ else if (dbType === 'mongodb') {
103
+ await migrateMongo(dbUrl, table, column, idCol, encryptionKey, activeVersion, chunkSize);
104
+ }
105
+ else {
106
+ console.error(`Error: Unsupported db-type "${dbType}". Supported: postgres, mongodb.`);
107
+ process.exit(1);
108
+ }
109
+ }
110
+ async function migratePostgres(url, table, column, idCol, key, version, chunkSize) {
111
+ let Client;
112
+ try {
113
+ Client = require('pg').Client;
114
+ }
115
+ catch (err) {
116
+ console.error("Error: The 'pg' package is not installed. Please install 'pg' to run migrations on PostgreSQL.");
117
+ process.exit(1);
118
+ }
119
+ const client = new Client({ connectionString: url });
120
+ await client.connect();
121
+ try {
122
+ // 1. Count unencrypted rows (value doesn't start with VOLLVALT:)
123
+ const countRes = await client.query(`SELECT COUNT(*) FROM "${table}" WHERE "${column}" IS NOT NULL AND "${column}" NOT LIKE 'VOLLVALT:%'`);
124
+ const total = parseInt(countRes.rows[0].count, 10);
125
+ if (total === 0) {
126
+ console.log("No unencrypted records found. Migration complete!");
127
+ return;
128
+ }
129
+ console.log(`Found ${total} unencrypted records. Processing...`);
130
+ let processed = 0;
131
+ printProgressBar(processed, total);
132
+ while (true) {
133
+ // 2. Fetch a batch of unencrypted rows
134
+ const batchRes = await client.query(`SELECT "${idCol}", "${column}" FROM "${table}" WHERE "${column}" IS NOT NULL AND "${column}" NOT LIKE 'VOLLVALT:%' LIMIT $1`, [chunkSize]);
135
+ if (batchRes.rows.length === 0) {
136
+ break;
137
+ }
138
+ // 3. Encrypt and update each row
139
+ for (const row of batchRes.rows) {
140
+ const rawVal = row[column];
141
+ const encryptedVal = (0, prisma_1.encryptValue)(rawVal, key, version);
142
+ await client.query(`UPDATE "${table}" SET "${column}" = $1 WHERE "${idCol}" = $2`, [encryptedVal, row[idCol]]);
143
+ processed++;
144
+ printProgressBar(processed, total);
145
+ }
146
+ }
147
+ console.log(`\nSuccessfully migrated ${processed} records!`);
148
+ }
149
+ catch (err) {
150
+ console.error(`\nMigration failed: ${err.message}`);
151
+ }
152
+ finally {
153
+ await client.end();
154
+ }
155
+ }
156
+ async function migrateMongo(url, collectionName, field, idCol, key, version, chunkSize) {
157
+ let MongoClient;
158
+ try {
159
+ MongoClient = require('mongodb').MongoClient;
160
+ }
161
+ catch (err) {
162
+ console.error("Error: The 'mongodb' package is not installed. Please install 'mongodb' to run migrations on MongoDB.");
163
+ process.exit(1);
164
+ }
165
+ const client = new MongoClient(url);
166
+ await client.connect();
167
+ try {
168
+ const db = client.db();
169
+ const collection = db.collection(collectionName);
170
+ // Filter to find docs where field exists, is not null, and doesn't start with VOLLVALT:
171
+ const filter = {
172
+ [field]: {
173
+ $exists: true,
174
+ $ne: null,
175
+ $not: /^VOLLVALT:/
176
+ }
177
+ };
178
+ const total = await collection.countDocuments(filter);
179
+ if (total === 0) {
180
+ console.log("No unencrypted records found. Migration complete!");
181
+ return;
182
+ }
183
+ console.log(`Found ${total} unencrypted records. Processing...`);
184
+ let processed = 0;
185
+ printProgressBar(processed, total);
186
+ while (true) {
187
+ const batch = await collection.find(filter).limit(chunkSize).toArray();
188
+ if (batch.length === 0) {
189
+ break;
190
+ }
191
+ for (const doc of batch) {
192
+ const rawVal = doc[field];
193
+ const encryptedVal = (0, prisma_1.encryptValue)(rawVal, key, version);
194
+ await collection.updateOne({ [idCol]: doc[idCol] }, { $set: { [field]: encryptedVal } });
195
+ processed++;
196
+ printProgressBar(processed, total);
197
+ }
198
+ }
199
+ console.log(`\nSuccessfully migrated ${processed} records!`);
200
+ }
201
+ catch (err) {
202
+ console.error(`\nMigration failed: ${err.message}`);
203
+ }
204
+ finally {
205
+ await client.close();
206
+ }
207
+ }
208
+ run().catch((err) => {
209
+ console.error(err);
210
+ process.exit(1);
211
+ });