@cipherstash/stack 0.4.0 → 0.6.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 (80) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +9 -36
  3. package/dist/bin/stash.js +220 -184
  4. package/dist/bin/stash.js.map +1 -1
  5. package/dist/{chunk-OAPLZLR5.js → chunk-GXGEW6T4.js} +2 -76
  6. package/dist/chunk-GXGEW6T4.js.map +1 -0
  7. package/dist/{chunk-KOU2MHXJ.js → chunk-HCL75LXQ.js} +1 -1
  8. package/dist/chunk-HCL75LXQ.js.map +1 -0
  9. package/dist/chunk-LBMC4D6D.js +19 -0
  10. package/dist/chunk-LBMC4D6D.js.map +1 -0
  11. package/dist/chunk-Q5FTQLYG.js +68 -0
  12. package/dist/chunk-Q5FTQLYG.js.map +1 -0
  13. package/dist/{chunk-WUZGKTMG.js → chunk-TGUELLJS.js} +16 -19
  14. package/dist/chunk-TGUELLJS.js.map +1 -0
  15. package/dist/chunk-YLISU7FD.js +357 -0
  16. package/dist/chunk-YLISU7FD.js.map +1 -0
  17. package/dist/{client-CrGQnSlk.d.ts → client-CJueivkw.d.cts} +36 -3
  18. package/dist/{client-CUGXheRq.d.cts → client-hHEpZIZH.d.ts} +36 -3
  19. package/dist/client.d.cts +4 -4
  20. package/dist/client.d.ts +4 -4
  21. package/dist/drizzle/index.cjs.map +1 -1
  22. package/dist/drizzle/index.d.cts +14 -6
  23. package/dist/drizzle/index.d.ts +14 -6
  24. package/dist/drizzle/index.js +3 -3
  25. package/dist/drizzle/index.js.map +1 -1
  26. package/dist/dynamodb/index.cjs +68 -0
  27. package/dist/dynamodb/index.cjs.map +1 -1
  28. package/dist/dynamodb/index.d.cts +4 -4
  29. package/dist/dynamodb/index.d.ts +4 -4
  30. package/dist/dynamodb/index.js +9 -0
  31. package/dist/dynamodb/index.js.map +1 -1
  32. package/dist/encryption/index.cjs +2572 -0
  33. package/dist/encryption/index.cjs.map +1 -0
  34. package/dist/encryption/index.d.cts +7 -0
  35. package/dist/encryption/index.d.ts +7 -0
  36. package/dist/encryption/index.js +17 -0
  37. package/dist/encryption/index.js.map +1 -0
  38. package/dist/errors/index.cjs +44 -0
  39. package/dist/errors/index.cjs.map +1 -0
  40. package/dist/errors/index.d.cts +94 -0
  41. package/dist/errors/index.d.ts +94 -0
  42. package/dist/errors/index.js +9 -0
  43. package/dist/errors/index.js.map +1 -0
  44. package/dist/identity/index.cjs +21 -13
  45. package/dist/identity/index.cjs.map +1 -1
  46. package/dist/identity/index.d.cts +78 -2
  47. package/dist/identity/index.d.ts +78 -2
  48. package/dist/identity/index.js +7 -3
  49. package/dist/identity/index.js.map +1 -1
  50. package/dist/index.cjs +379 -41
  51. package/dist/index.cjs.map +1 -1
  52. package/dist/index.d.cts +21 -19
  53. package/dist/index.d.ts +21 -19
  54. package/dist/index.js +14 -8
  55. package/dist/schema/index.d.cts +1 -2
  56. package/dist/schema/index.d.ts +1 -2
  57. package/dist/secrets/index.cjs +70 -39
  58. package/dist/secrets/index.cjs.map +1 -1
  59. package/dist/secrets/index.d.cts +4 -6
  60. package/dist/secrets/index.d.ts +4 -6
  61. package/dist/secrets/index.js +9 -318
  62. package/dist/secrets/index.js.map +1 -1
  63. package/dist/supabase/index.cjs +84 -1
  64. package/dist/supabase/index.cjs.map +1 -1
  65. package/dist/supabase/index.d.cts +4 -4
  66. package/dist/supabase/index.d.ts +4 -4
  67. package/dist/supabase/index.js +24 -1
  68. package/dist/supabase/index.js.map +1 -1
  69. package/dist/{types-public-Wj6tB_ux.d.ts → types-public-07ilcGY-.d.cts} +1 -9
  70. package/dist/{types-public-Wj6tB_ux.d.cts → types-public-07ilcGY-.d.ts} +1 -9
  71. package/dist/types-public.cjs.map +1 -1
  72. package/dist/types-public.d.cts +1 -2
  73. package/dist/types-public.d.ts +1 -2
  74. package/dist/types-public.js +1 -1
  75. package/package.json +29 -1
  76. package/dist/chunk-KOU2MHXJ.js.map +0 -1
  77. package/dist/chunk-OAPLZLR5.js.map +0 -1
  78. package/dist/chunk-WUZGKTMG.js.map +0 -1
  79. package/dist/index-9-Ya3fDK.d.cts +0 -169
  80. package/dist/index-9-Ya3fDK.d.ts +0 -169
@@ -0,0 +1,2572 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/encryption/index.ts
31
+ var encryption_exports = {};
32
+ __export(encryption_exports, {
33
+ Encryption: () => Encryption,
34
+ EncryptionClient: () => EncryptionClient,
35
+ noClientError: () => noClientError
36
+ });
37
+ module.exports = __toCommonJS(encryption_exports);
38
+
39
+ // src/errors/index.ts
40
+ var EncryptionErrorTypes = {
41
+ ClientInitError: "ClientInitError",
42
+ EncryptionError: "EncryptionError",
43
+ DecryptionError: "DecryptionError",
44
+ LockContextError: "LockContextError",
45
+ CtsTokenError: "CtsTokenError"
46
+ };
47
+
48
+ // src/schema/index.ts
49
+ var import_zod = require("zod");
50
+ var castAsEnum = import_zod.z.enum(["bigint", "boolean", "date", "number", "string", "json", "text"]).default("text");
51
+ var tokenFilterSchema = import_zod.z.object({
52
+ kind: import_zod.z.literal("downcase")
53
+ });
54
+ var tokenizerSchema = import_zod.z.union([
55
+ import_zod.z.object({
56
+ kind: import_zod.z.literal("standard")
57
+ }),
58
+ import_zod.z.object({
59
+ kind: import_zod.z.literal("ngram"),
60
+ token_length: import_zod.z.number()
61
+ })
62
+ ]).default({ kind: "ngram", token_length: 3 }).optional();
63
+ var oreIndexOptsSchema = import_zod.z.object({});
64
+ var uniqueIndexOptsSchema = import_zod.z.object({
65
+ token_filters: import_zod.z.array(tokenFilterSchema).default([]).optional()
66
+ });
67
+ var matchIndexOptsSchema = import_zod.z.object({
68
+ tokenizer: tokenizerSchema,
69
+ token_filters: import_zod.z.array(tokenFilterSchema).default([]).optional(),
70
+ k: import_zod.z.number().default(6).optional(),
71
+ m: import_zod.z.number().default(2048).optional(),
72
+ include_original: import_zod.z.boolean().default(false).optional()
73
+ });
74
+ var steVecIndexOptsSchema = import_zod.z.object({
75
+ prefix: import_zod.z.string()
76
+ });
77
+ var indexesSchema = import_zod.z.object({
78
+ ore: oreIndexOptsSchema.optional(),
79
+ unique: uniqueIndexOptsSchema.optional(),
80
+ match: matchIndexOptsSchema.optional(),
81
+ ste_vec: steVecIndexOptsSchema.optional()
82
+ }).default({});
83
+ var columnSchema = import_zod.z.object({
84
+ cast_as: castAsEnum,
85
+ indexes: indexesSchema
86
+ }).default({});
87
+ var tableSchema = import_zod.z.record(columnSchema).default({});
88
+ var tablesSchema = import_zod.z.record(tableSchema).default({});
89
+ var encryptConfigSchema = import_zod.z.object({
90
+ v: import_zod.z.number(),
91
+ tables: tablesSchema
92
+ });
93
+ function buildEncryptConfig(...protectTables) {
94
+ const config = {
95
+ v: 2,
96
+ tables: {}
97
+ };
98
+ for (const tb of protectTables) {
99
+ const tableDef = tb.build();
100
+ config.tables[tableDef.tableName] = tableDef.columns;
101
+ }
102
+ return config;
103
+ }
104
+
105
+ // src/utils/config/index.ts
106
+ var import_node_fs = __toESM(require("fs"), 1);
107
+ var import_node_path = __toESM(require("path"), 1);
108
+ function getWorkspaceCrn(tomlString) {
109
+ let currentSection = "";
110
+ let workspaceCrn;
111
+ const lines = tomlString.split(/\r?\n/);
112
+ for (const line of lines) {
113
+ const trimmedLine = line.trim();
114
+ if (!trimmedLine || trimmedLine.startsWith("#")) {
115
+ continue;
116
+ }
117
+ const sectionMatch = trimmedLine.match(/^\[([^\]]+)\]$/);
118
+ if (sectionMatch) {
119
+ currentSection = sectionMatch[1];
120
+ continue;
121
+ }
122
+ const kvMatch = trimmedLine.match(/^(\w+)\s*=\s*"([^"]+)"$/);
123
+ if (kvMatch) {
124
+ const [_, key, value] = kvMatch;
125
+ if (currentSection === "auth" && key === "workspace_crn") {
126
+ workspaceCrn = value;
127
+ break;
128
+ }
129
+ }
130
+ }
131
+ return workspaceCrn;
132
+ }
133
+ function extractWorkspaceIdFromCrn(crn) {
134
+ const match = crn.match(/crn:[^:]+:([^:]+)$/);
135
+ if (!match) {
136
+ throw new Error("Invalid CRN format");
137
+ }
138
+ return match[1];
139
+ }
140
+ function loadWorkSpaceId(suppliedCrn) {
141
+ const configPath = import_node_path.default.join(process.cwd(), "cipherstash.toml");
142
+ if (suppliedCrn) {
143
+ return extractWorkspaceIdFromCrn(suppliedCrn);
144
+ }
145
+ if (!import_node_fs.default.existsSync(configPath) && !process.env.CS_WORKSPACE_CRN) {
146
+ throw new Error(
147
+ "You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable."
148
+ );
149
+ }
150
+ if (process.env.CS_WORKSPACE_CRN) {
151
+ return extractWorkspaceIdFromCrn(process.env.CS_WORKSPACE_CRN);
152
+ }
153
+ if (!import_node_fs.default.existsSync(configPath)) {
154
+ throw new Error(
155
+ "You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable."
156
+ );
157
+ }
158
+ const tomlString = import_node_fs.default.readFileSync(configPath, "utf8");
159
+ const workspaceCrn = getWorkspaceCrn(tomlString);
160
+ if (!workspaceCrn) {
161
+ throw new Error(
162
+ "You have not defined a workspace CRN in your config file, or the CS_WORKSPACE_CRN environment variable."
163
+ );
164
+ }
165
+ return extractWorkspaceIdFromCrn(workspaceCrn);
166
+ }
167
+
168
+ // src/utils/logger/index.ts
169
+ var import_evlog = require("evlog");
170
+ var validLevels = ["debug", "info", "error"];
171
+ function levelFromEnv() {
172
+ const env = process.env.STASH_STACK_LOG;
173
+ if (env && validLevels.includes(env)) return env;
174
+ return "error";
175
+ }
176
+ function samplingRatesForLevel(level) {
177
+ switch (level) {
178
+ case "debug":
179
+ return { debug: 100, info: 100, warn: 100, error: 100 };
180
+ case "info":
181
+ return { debug: 0, info: 100, warn: 100, error: 100 };
182
+ case "error":
183
+ default:
184
+ return { debug: 0, info: 0, warn: 0, error: 100 };
185
+ }
186
+ }
187
+ var initialized = false;
188
+ function initStackLogger() {
189
+ if (initialized) return;
190
+ initialized = true;
191
+ const level = levelFromEnv();
192
+ const rates = samplingRatesForLevel(level);
193
+ (0, import_evlog.initLogger)({
194
+ env: { service: "@cipherstash/stack" },
195
+ enabled: true,
196
+ sampling: { rates }
197
+ });
198
+ }
199
+ initStackLogger();
200
+ function safeMessage(args) {
201
+ return typeof args[0] === "string" ? args[0] : "";
202
+ }
203
+ var logger = {
204
+ debug(...args) {
205
+ const log = (0, import_evlog.createRequestLogger)();
206
+ log.set({
207
+ level: "debug",
208
+ source: "@cipherstash/stack",
209
+ message: safeMessage(args)
210
+ });
211
+ log.emit();
212
+ },
213
+ info(...args) {
214
+ const log = (0, import_evlog.createRequestLogger)();
215
+ log.set({ source: "@cipherstash/stack" });
216
+ log.info(safeMessage(args));
217
+ log.emit();
218
+ },
219
+ warn(...args) {
220
+ const log = (0, import_evlog.createRequestLogger)();
221
+ log.warn(safeMessage(args));
222
+ log.emit();
223
+ },
224
+ error(...args) {
225
+ const log = (0, import_evlog.createRequestLogger)();
226
+ log.error(safeMessage(args));
227
+ log.emit();
228
+ }
229
+ };
230
+
231
+ // src/encryption/index.ts
232
+ var import_result11 = require("@byteslice/result");
233
+ var import_protect_ffi9 = require("@cipherstash/protect-ffi");
234
+ var import_uuid = require("uuid");
235
+
236
+ // src/encryption/helpers/index.ts
237
+ function encryptedToCompositeLiteral(obj) {
238
+ return `(${JSON.stringify(JSON.stringify(obj))})`;
239
+ }
240
+ function encryptedToEscapedCompositeLiteral(obj) {
241
+ return JSON.stringify(encryptedToCompositeLiteral(obj));
242
+ }
243
+ function formatEncryptedResult(encrypted, returnType) {
244
+ if (returnType === "composite-literal") {
245
+ return encryptedToCompositeLiteral(encrypted);
246
+ }
247
+ if (returnType === "escaped-composite-literal") {
248
+ return encryptedToEscapedCompositeLiteral(encrypted);
249
+ }
250
+ return encrypted;
251
+ }
252
+ function toFfiKeysetIdentifier(keyset) {
253
+ if (!keyset) return void 0;
254
+ if ("name" in keyset) {
255
+ return { Name: keyset.name };
256
+ }
257
+ return { Uuid: keyset.id };
258
+ }
259
+ function isEncryptedPayload(value) {
260
+ if (value === null) return false;
261
+ if (typeof value !== "object") return false;
262
+ const obj = value;
263
+ if (!("v" in obj) || typeof obj.v !== "number") return false;
264
+ if (!("i" in obj) || typeof obj.i !== "object") return false;
265
+ if (!("c" in obj) && !("sv" in obj)) return false;
266
+ return true;
267
+ }
268
+
269
+ // src/encryption/helpers/type-guards.ts
270
+ function isScalarQueryTermArray(value) {
271
+ return Array.isArray(value) && value.length > 0 && typeof value[0] === "object" && value[0] !== null && "column" in value[0] && "table" in value[0];
272
+ }
273
+
274
+ // src/encryption/helpers/error-code.ts
275
+ var import_protect_ffi = require("@cipherstash/protect-ffi");
276
+ function getErrorCode(error) {
277
+ return error instanceof import_protect_ffi.ProtectError ? error.code : void 0;
278
+ }
279
+
280
+ // src/encryption/operations/batch-encrypt-query.ts
281
+ var import_result = require("@byteslice/result");
282
+ var import_protect_ffi2 = require("@cipherstash/protect-ffi");
283
+
284
+ // src/types.ts
285
+ var queryTypeToFfi = {
286
+ orderAndRange: "ore",
287
+ freeTextSearch: "match",
288
+ equality: "unique",
289
+ steVecSelector: "ste_vec",
290
+ steVecTerm: "ste_vec",
291
+ searchableJson: "ste_vec"
292
+ };
293
+ var queryTypeToQueryOp = {
294
+ steVecSelector: "ste_vec_selector",
295
+ steVecTerm: "ste_vec_term"
296
+ };
297
+
298
+ // src/encryption/helpers/infer-index-type.ts
299
+ function inferIndexType(column) {
300
+ const config = column.build();
301
+ const indexes = config.indexes;
302
+ if (!indexes || Object.keys(indexes).length === 0) {
303
+ throw new Error(`Column "${column.getName()}" has no indexes configured`);
304
+ }
305
+ if (indexes.unique) return "unique";
306
+ if (indexes.match) return "match";
307
+ if (indexes.ore) return "ore";
308
+ if (indexes.ste_vec) return "ste_vec";
309
+ throw new Error(
310
+ `Column "${column.getName()}" has no suitable index for queries`
311
+ );
312
+ }
313
+ function inferQueryOpFromPlaintext(plaintext) {
314
+ if (typeof plaintext === "string") {
315
+ return "ste_vec_selector";
316
+ }
317
+ if (typeof plaintext === "object" || typeof plaintext === "number" || typeof plaintext === "boolean" || typeof plaintext === "bigint") {
318
+ return "ste_vec_term";
319
+ }
320
+ return "ste_vec_term";
321
+ }
322
+ function validateIndexType(column, indexType) {
323
+ const config = column.build();
324
+ const indexes = config.indexes ?? {};
325
+ const indexMap = {
326
+ unique: !!indexes.unique,
327
+ match: !!indexes.match,
328
+ ore: !!indexes.ore,
329
+ ste_vec: !!indexes.ste_vec
330
+ };
331
+ if (!indexMap[indexType]) {
332
+ throw new Error(
333
+ `Index type "${indexType}" is not configured on column "${column.getName()}"`
334
+ );
335
+ }
336
+ }
337
+ function resolveIndexType(column, queryType, plaintext) {
338
+ const indexType = queryType ? queryTypeToFfi[queryType] : inferIndexType(column);
339
+ if (queryType) {
340
+ validateIndexType(column, indexType);
341
+ if (queryType === "searchableJson") {
342
+ if (plaintext === void 0 || plaintext === null) {
343
+ return { indexType };
344
+ }
345
+ return { indexType, queryOp: inferQueryOpFromPlaintext(plaintext) };
346
+ }
347
+ return { indexType, queryOp: queryTypeToQueryOp[queryType] };
348
+ }
349
+ if (indexType === "ste_vec") {
350
+ if (plaintext === void 0 || plaintext === null) {
351
+ return { indexType };
352
+ }
353
+ return { indexType, queryOp: inferQueryOpFromPlaintext(plaintext) };
354
+ }
355
+ return { indexType };
356
+ }
357
+
358
+ // src/encryption/helpers/validation.ts
359
+ function validateNumericValue(value) {
360
+ if (typeof value === "number" && Number.isNaN(value)) {
361
+ return {
362
+ failure: {
363
+ type: EncryptionErrorTypes.EncryptionError,
364
+ message: "[encryption]: Cannot encrypt NaN value"
365
+ }
366
+ };
367
+ }
368
+ if (typeof value === "number" && !Number.isFinite(value)) {
369
+ return {
370
+ failure: {
371
+ type: EncryptionErrorTypes.EncryptionError,
372
+ message: "[encryption]: Cannot encrypt Infinity value"
373
+ }
374
+ };
375
+ }
376
+ return void 0;
377
+ }
378
+ function assertValidNumericValue(value) {
379
+ if (typeof value === "number" && Number.isNaN(value)) {
380
+ throw new Error("[encryption]: Cannot encrypt NaN value");
381
+ }
382
+ if (typeof value === "number" && !Number.isFinite(value)) {
383
+ throw new Error("[encryption]: Cannot encrypt Infinity value");
384
+ }
385
+ }
386
+ function assertValueIndexCompatibility(value, indexType, columnName) {
387
+ if (typeof value === "number" && indexType === "match") {
388
+ throw new Error(
389
+ `[encryption]: Cannot use 'match' index with numeric value on column "${columnName}". The 'freeTextSearch' index only supports string values. Configure the column with 'orderAndRange()' or 'equality()' for numeric queries.`
390
+ );
391
+ }
392
+ }
393
+
394
+ // src/encryption/operations/base-operation.ts
395
+ var EncryptionOperation = class {
396
+ auditMetadata;
397
+ /**
398
+ * Attach audit metadata to this operation. Can be chained.
399
+ * @param config Configuration for ZeroKMS audit logging
400
+ * @param config.metadata Arbitrary JSON object for appending metadata to the audit log
401
+ */
402
+ audit(config) {
403
+ this.auditMetadata = config.metadata;
404
+ return this;
405
+ }
406
+ /**
407
+ * Get the audit data for this operation.
408
+ */
409
+ getAuditData() {
410
+ return {
411
+ metadata: this.auditMetadata
412
+ };
413
+ }
414
+ /**
415
+ * Make the operation thenable
416
+ */
417
+ then(onfulfilled, onrejected) {
418
+ return this.execute().then(onfulfilled, onrejected);
419
+ }
420
+ };
421
+
422
+ // src/encryption/operations/batch-encrypt-query.ts
423
+ function buildQueryPayload(term, lockContext) {
424
+ assertValidNumericValue(term.value);
425
+ const { indexType, queryOp } = resolveIndexType(
426
+ term.column,
427
+ term.queryType,
428
+ term.value
429
+ );
430
+ assertValueIndexCompatibility(term.value, indexType, term.column.getName());
431
+ const payload = {
432
+ plaintext: term.value,
433
+ column: term.column.getName(),
434
+ table: term.table.tableName,
435
+ indexType,
436
+ queryOp
437
+ };
438
+ if (lockContext != null) {
439
+ payload.lockContext = lockContext;
440
+ }
441
+ return payload;
442
+ }
443
+ function assembleResults(terms, encryptedValues) {
444
+ return terms.map(
445
+ (term, i) => formatEncryptedResult(encryptedValues[i], term.returnType)
446
+ );
447
+ }
448
+ var BatchEncryptQueryOperation = class extends EncryptionOperation {
449
+ constructor(client, terms) {
450
+ super();
451
+ this.client = client;
452
+ this.terms = terms;
453
+ }
454
+ withLockContext(lockContext) {
455
+ return new BatchEncryptQueryOperationWithLockContext(
456
+ this.client,
457
+ this.terms,
458
+ lockContext,
459
+ this.auditMetadata
460
+ );
461
+ }
462
+ async execute() {
463
+ const log = (0, import_evlog.createRequestLogger)();
464
+ log.set({
465
+ op: "batchEncryptQuery",
466
+ count: this.terms.length,
467
+ lockContext: false
468
+ });
469
+ if (this.terms.length === 0) {
470
+ log.emit();
471
+ return { data: [] };
472
+ }
473
+ const result = await (0, import_result.withResult)(
474
+ async () => {
475
+ if (!this.client) throw noClientError();
476
+ const { metadata } = this.getAuditData();
477
+ const queries = this.terms.map(
478
+ (term) => buildQueryPayload(term)
479
+ );
480
+ const encrypted = await (0, import_protect_ffi2.encryptQueryBulk)(this.client, {
481
+ queries,
482
+ unverifiedContext: metadata
483
+ });
484
+ return assembleResults(this.terms, encrypted);
485
+ },
486
+ (error) => {
487
+ log.set({ errorCode: getErrorCode(error) ?? "unknown" });
488
+ return {
489
+ type: EncryptionErrorTypes.EncryptionError,
490
+ message: error.message,
491
+ code: getErrorCode(error)
492
+ };
493
+ }
494
+ );
495
+ log.emit();
496
+ return result;
497
+ }
498
+ };
499
+ var BatchEncryptQueryOperationWithLockContext = class extends EncryptionOperation {
500
+ constructor(client, terms, lockContext, auditMetadata) {
501
+ super();
502
+ this.client = client;
503
+ this.terms = terms;
504
+ this.lockContext = lockContext;
505
+ this.auditMetadata = auditMetadata;
506
+ }
507
+ async execute() {
508
+ const log = (0, import_evlog.createRequestLogger)();
509
+ log.set({
510
+ op: "batchEncryptQuery",
511
+ count: this.terms.length,
512
+ lockContext: true
513
+ });
514
+ if (this.terms.length === 0) {
515
+ log.emit();
516
+ return { data: [] };
517
+ }
518
+ const lockContextResult = await this.lockContext.getLockContext();
519
+ if (lockContextResult.failure) {
520
+ log.emit();
521
+ return { failure: lockContextResult.failure };
522
+ }
523
+ const { ctsToken, context } = lockContextResult.data;
524
+ const result = await (0, import_result.withResult)(
525
+ async () => {
526
+ if (!this.client) throw noClientError();
527
+ const { metadata } = this.getAuditData();
528
+ const queries = this.terms.map(
529
+ (term) => buildQueryPayload(term, context)
530
+ );
531
+ const encrypted = await (0, import_protect_ffi2.encryptQueryBulk)(this.client, {
532
+ queries,
533
+ serviceToken: ctsToken,
534
+ unverifiedContext: metadata
535
+ });
536
+ return assembleResults(this.terms, encrypted);
537
+ },
538
+ (error) => {
539
+ log.set({ errorCode: getErrorCode(error) ?? "unknown" });
540
+ return {
541
+ type: EncryptionErrorTypes.EncryptionError,
542
+ message: error.message,
543
+ code: getErrorCode(error)
544
+ };
545
+ }
546
+ );
547
+ log.emit();
548
+ return result;
549
+ }
550
+ };
551
+
552
+ // src/encryption/operations/bulk-decrypt.ts
553
+ var import_result2 = require("@byteslice/result");
554
+ var import_protect_ffi3 = require("@cipherstash/protect-ffi");
555
+ var createDecryptPayloads = (encryptedPayloads, lockContext) => {
556
+ return encryptedPayloads.map(({ id, data }) => ({
557
+ id,
558
+ ciphertext: data,
559
+ ...lockContext && { lockContext }
560
+ }));
561
+ };
562
+ var mapDecryptedDataToResult = (encryptedPayloads, decryptedData) => {
563
+ return decryptedData.map((decryptResult, i) => {
564
+ if ("error" in decryptResult) {
565
+ return {
566
+ id: encryptedPayloads[i].id,
567
+ error: decryptResult.error
568
+ };
569
+ }
570
+ return {
571
+ id: encryptedPayloads[i].id,
572
+ data: decryptResult.data
573
+ };
574
+ });
575
+ };
576
+ var BulkDecryptOperation = class extends EncryptionOperation {
577
+ client;
578
+ encryptedPayloads;
579
+ constructor(client, encryptedPayloads) {
580
+ super();
581
+ this.client = client;
582
+ this.encryptedPayloads = encryptedPayloads;
583
+ }
584
+ withLockContext(lockContext) {
585
+ return new BulkDecryptOperationWithLockContext(this, lockContext);
586
+ }
587
+ async execute() {
588
+ const log = (0, import_evlog.createRequestLogger)();
589
+ log.set({
590
+ op: "bulkDecrypt",
591
+ count: this.encryptedPayloads?.length ?? 0,
592
+ lockContext: false
593
+ });
594
+ const result = await (0, import_result2.withResult)(
595
+ async () => {
596
+ if (!this.client) throw noClientError();
597
+ if (!this.encryptedPayloads || this.encryptedPayloads.length === 0)
598
+ return [];
599
+ const payloads = createDecryptPayloads(this.encryptedPayloads);
600
+ const { metadata } = this.getAuditData();
601
+ const decryptedData = await (0, import_protect_ffi3.decryptBulkFallible)(this.client, {
602
+ ciphertexts: payloads,
603
+ unverifiedContext: metadata
604
+ });
605
+ return mapDecryptedDataToResult(this.encryptedPayloads, decryptedData);
606
+ },
607
+ (error) => {
608
+ log.set({ errorCode: getErrorCode(error) ?? "unknown" });
609
+ return {
610
+ type: EncryptionErrorTypes.DecryptionError,
611
+ message: error.message,
612
+ code: getErrorCode(error)
613
+ };
614
+ }
615
+ );
616
+ log.emit();
617
+ return result;
618
+ }
619
+ getOperation() {
620
+ return {
621
+ client: this.client,
622
+ encryptedPayloads: this.encryptedPayloads
623
+ };
624
+ }
625
+ };
626
+ var BulkDecryptOperationWithLockContext = class extends EncryptionOperation {
627
+ operation;
628
+ lockContext;
629
+ constructor(operation, lockContext) {
630
+ super();
631
+ this.operation = operation;
632
+ this.lockContext = lockContext;
633
+ const auditData = operation.getAuditData();
634
+ if (auditData) {
635
+ this.audit(auditData);
636
+ }
637
+ }
638
+ async execute() {
639
+ const { client, encryptedPayloads } = this.operation.getOperation();
640
+ const log = (0, import_evlog.createRequestLogger)();
641
+ log.set({
642
+ op: "bulkDecrypt",
643
+ count: encryptedPayloads?.length ?? 0,
644
+ lockContext: true
645
+ });
646
+ const result = await (0, import_result2.withResult)(
647
+ async () => {
648
+ if (!client) throw noClientError();
649
+ if (!encryptedPayloads || encryptedPayloads.length === 0) return [];
650
+ const context = await this.lockContext.getLockContext();
651
+ if (context.failure) {
652
+ throw new Error(`[encryption]: ${context.failure.message}`);
653
+ }
654
+ const payloads = createDecryptPayloads(
655
+ encryptedPayloads,
656
+ context.data.context
657
+ );
658
+ const { metadata } = this.getAuditData();
659
+ const decryptedData = await (0, import_protect_ffi3.decryptBulkFallible)(client, {
660
+ ciphertexts: payloads,
661
+ serviceToken: context.data.ctsToken,
662
+ unverifiedContext: metadata
663
+ });
664
+ return mapDecryptedDataToResult(encryptedPayloads, decryptedData);
665
+ },
666
+ (error) => {
667
+ log.set({ errorCode: getErrorCode(error) ?? "unknown" });
668
+ return {
669
+ type: EncryptionErrorTypes.DecryptionError,
670
+ message: error.message,
671
+ code: getErrorCode(error)
672
+ };
673
+ }
674
+ );
675
+ log.emit();
676
+ return result;
677
+ }
678
+ };
679
+
680
+ // src/encryption/operations/bulk-decrypt-models.ts
681
+ var import_result3 = require("@byteslice/result");
682
+
683
+ // src/encryption/helpers/model-helpers.ts
684
+ var import_protect_ffi4 = require("@cipherstash/protect-ffi");
685
+ function setNestedValue(obj, path2, value) {
686
+ const FORBIDDEN_KEYS = ["__proto__", "prototype", "constructor"];
687
+ let current = obj;
688
+ for (let i = 0; i < path2.length - 1; i++) {
689
+ const part = path2[i];
690
+ if (FORBIDDEN_KEYS.includes(part)) {
691
+ throw new Error(`[encryption]: Forbidden key "${part}" in field path`);
692
+ }
693
+ if (!(part in current) || typeof current[part] !== "object" || current[part] === null) {
694
+ current[part] = {};
695
+ }
696
+ current = current[part];
697
+ }
698
+ const lastKey = path2[path2.length - 1];
699
+ if (FORBIDDEN_KEYS.includes(lastKey)) {
700
+ throw new Error(`[encryption]: Forbidden key "${lastKey}" in field path`);
701
+ }
702
+ current[lastKey] = value;
703
+ }
704
+ async function handleSingleModelBulkOperation(items, operation, keyMap) {
705
+ if (items.length === 0) {
706
+ return {};
707
+ }
708
+ const results = await operation(items);
709
+ const mappedResults = {};
710
+ results.forEach((result, index) => {
711
+ const originalKey = keyMap[index.toString()];
712
+ mappedResults[originalKey] = result;
713
+ });
714
+ return mappedResults;
715
+ }
716
+ async function handleMultiModelBulkOperation(items, operation, keyMap) {
717
+ if (items.length === 0) {
718
+ return {};
719
+ }
720
+ const results = await operation(items);
721
+ const mappedResults = {};
722
+ results.forEach((result, index) => {
723
+ const key = index.toString();
724
+ const { modelIndex, fieldKey } = keyMap[key];
725
+ mappedResults[`${modelIndex}-${fieldKey}`] = result;
726
+ });
727
+ return mappedResults;
728
+ }
729
+ function prepareFieldsForDecryption(model) {
730
+ const otherFields = { ...model };
731
+ const operationFields = {};
732
+ const nullFields = {};
733
+ const keyMap = {};
734
+ let index = 0;
735
+ const processNestedFields = (obj, prefix = "") => {
736
+ for (const [key, value] of Object.entries(obj)) {
737
+ const fullKey = prefix ? `${prefix}.${key}` : key;
738
+ if (value === null || value === void 0) {
739
+ nullFields[fullKey] = value;
740
+ continue;
741
+ }
742
+ if (typeof value === "object" && !isEncryptedPayload(value)) {
743
+ processNestedFields(value, fullKey);
744
+ } else if (isEncryptedPayload(value)) {
745
+ const id = index.toString();
746
+ keyMap[id] = fullKey;
747
+ operationFields[fullKey] = value;
748
+ index++;
749
+ const parts = fullKey.split(".");
750
+ let current = otherFields;
751
+ for (let i = 0; i < parts.length - 1; i++) {
752
+ current = current[parts[i]];
753
+ }
754
+ delete current[parts[parts.length - 1]];
755
+ }
756
+ }
757
+ };
758
+ processNestedFields(model);
759
+ return { otherFields, operationFields, keyMap, nullFields };
760
+ }
761
+ function prepareFieldsForEncryption(model, table) {
762
+ const otherFields = { ...model };
763
+ const operationFields = {};
764
+ const nullFields = {};
765
+ const keyMap = {};
766
+ let index = 0;
767
+ const processNestedFields = (obj, prefix = "", columnPaths2 = []) => {
768
+ for (const [key, value] of Object.entries(obj)) {
769
+ const fullKey = prefix ? `${prefix}.${key}` : key;
770
+ if (value === null || value === void 0) {
771
+ nullFields[fullKey] = value;
772
+ continue;
773
+ }
774
+ if (typeof value === "object" && !isEncryptedPayload(value) && !columnPaths2.includes(fullKey)) {
775
+ if (columnPaths2.some((path2) => path2.startsWith(fullKey))) {
776
+ processNestedFields(
777
+ value,
778
+ fullKey,
779
+ columnPaths2
780
+ );
781
+ }
782
+ } else if (columnPaths2.includes(fullKey)) {
783
+ const id = index.toString();
784
+ keyMap[id] = fullKey;
785
+ operationFields[fullKey] = value;
786
+ index++;
787
+ const parts = fullKey.split(".");
788
+ let current = otherFields;
789
+ for (let i = 0; i < parts.length - 1; i++) {
790
+ current = current[parts[i]];
791
+ }
792
+ delete current[parts[parts.length - 1]];
793
+ }
794
+ }
795
+ };
796
+ const columnPaths = Object.keys(table.build().columns);
797
+ processNestedFields(model, "", columnPaths);
798
+ return { otherFields, operationFields, keyMap, nullFields };
799
+ }
800
+ async function decryptModelFields(model, client, auditData) {
801
+ if (!client) {
802
+ throw new Error("Client not initialized");
803
+ }
804
+ const { otherFields, operationFields, keyMap, nullFields } = prepareFieldsForDecryption(model);
805
+ const bulkDecryptPayload = Object.entries(operationFields).map(
806
+ ([key, value]) => ({
807
+ id: key,
808
+ ciphertext: value
809
+ })
810
+ );
811
+ const decryptedFields = await handleSingleModelBulkOperation(
812
+ bulkDecryptPayload,
813
+ (items) => (0, import_protect_ffi4.decryptBulk)(client, {
814
+ ciphertexts: items,
815
+ unverifiedContext: auditData?.metadata
816
+ }),
817
+ keyMap
818
+ );
819
+ const result = { ...otherFields };
820
+ for (const [key, value] of Object.entries(nullFields)) {
821
+ const parts = key.split(".");
822
+ setNestedValue(result, parts, value);
823
+ }
824
+ for (const [key, value] of Object.entries(decryptedFields)) {
825
+ const parts = key.split(".");
826
+ setNestedValue(result, parts, value);
827
+ }
828
+ return result;
829
+ }
830
+ async function encryptModelFields(model, table, client, auditData) {
831
+ if (!client) {
832
+ throw new Error("Client not initialized");
833
+ }
834
+ const { otherFields, operationFields, keyMap, nullFields } = prepareFieldsForEncryption(model, table);
835
+ const bulkEncryptPayload = Object.entries(operationFields).map(
836
+ ([key, value]) => ({
837
+ id: key,
838
+ plaintext: value,
839
+ table: table.tableName,
840
+ column: key
841
+ })
842
+ );
843
+ const encryptedData = await handleSingleModelBulkOperation(
844
+ bulkEncryptPayload,
845
+ (items) => (0, import_protect_ffi4.encryptBulk)(client, {
846
+ plaintexts: items,
847
+ unverifiedContext: auditData?.metadata
848
+ }),
849
+ keyMap
850
+ );
851
+ const result = { ...otherFields };
852
+ for (const [key, value] of Object.entries(nullFields)) {
853
+ const parts = key.split(".");
854
+ setNestedValue(result, parts, value);
855
+ }
856
+ for (const [key, value] of Object.entries(encryptedData)) {
857
+ const parts = key.split(".");
858
+ setNestedValue(result, parts, value);
859
+ }
860
+ return result;
861
+ }
862
+ async function decryptModelFieldsWithLockContext(model, client, lockContext, auditData) {
863
+ if (!client) {
864
+ throw new Error("Client not initialized");
865
+ }
866
+ if (!lockContext) {
867
+ throw new Error("Lock context is not initialized");
868
+ }
869
+ const { otherFields, operationFields, keyMap, nullFields } = prepareFieldsForDecryption(model);
870
+ const bulkDecryptPayload = Object.entries(operationFields).map(
871
+ ([key, value]) => ({
872
+ id: key,
873
+ ciphertext: value,
874
+ lockContext: lockContext.context
875
+ })
876
+ );
877
+ const decryptedFields = await handleSingleModelBulkOperation(
878
+ bulkDecryptPayload,
879
+ (items) => (0, import_protect_ffi4.decryptBulk)(client, {
880
+ ciphertexts: items,
881
+ serviceToken: lockContext.ctsToken,
882
+ unverifiedContext: auditData?.metadata
883
+ }),
884
+ keyMap
885
+ );
886
+ const result = { ...otherFields };
887
+ for (const [key, value] of Object.entries(nullFields)) {
888
+ const parts = key.split(".");
889
+ setNestedValue(result, parts, value);
890
+ }
891
+ for (const [key, value] of Object.entries(decryptedFields)) {
892
+ const parts = key.split(".");
893
+ setNestedValue(result, parts, value);
894
+ }
895
+ return result;
896
+ }
897
+ async function encryptModelFieldsWithLockContext(model, table, client, lockContext, auditData) {
898
+ if (!client) {
899
+ throw new Error("Client not initialized");
900
+ }
901
+ if (!lockContext) {
902
+ throw new Error("Lock context is not initialized");
903
+ }
904
+ const { otherFields, operationFields, keyMap, nullFields } = prepareFieldsForEncryption(model, table);
905
+ const bulkEncryptPayload = Object.entries(operationFields).map(
906
+ ([key, value]) => ({
907
+ id: key,
908
+ plaintext: value,
909
+ table: table.tableName,
910
+ column: key,
911
+ lockContext: lockContext.context
912
+ })
913
+ );
914
+ const encryptedData = await handleSingleModelBulkOperation(
915
+ bulkEncryptPayload,
916
+ (items) => (0, import_protect_ffi4.encryptBulk)(client, {
917
+ plaintexts: items,
918
+ serviceToken: lockContext.ctsToken,
919
+ unverifiedContext: auditData?.metadata
920
+ }),
921
+ keyMap
922
+ );
923
+ const result = { ...otherFields };
924
+ for (const [key, value] of Object.entries(nullFields)) {
925
+ const parts = key.split(".");
926
+ setNestedValue(result, parts, value);
927
+ }
928
+ for (const [key, value] of Object.entries(encryptedData)) {
929
+ const parts = key.split(".");
930
+ setNestedValue(result, parts, value);
931
+ }
932
+ return result;
933
+ }
934
+ function prepareBulkModelsForOperation(models, table) {
935
+ const otherFields = [];
936
+ const operationFields = [];
937
+ const nullFields = [];
938
+ const keyMap = {};
939
+ let index = 0;
940
+ for (let modelIndex = 0; modelIndex < models.length; modelIndex++) {
941
+ const model = models[modelIndex];
942
+ const modelOtherFields = { ...model };
943
+ const modelOperationFields = {};
944
+ const modelNullFields = {};
945
+ const processNestedFields = (obj, prefix = "", columnPaths = []) => {
946
+ for (const [key, value] of Object.entries(obj)) {
947
+ const fullKey = prefix ? `${prefix}.${key}` : key;
948
+ if (value === null || value === void 0) {
949
+ modelNullFields[fullKey] = value;
950
+ continue;
951
+ }
952
+ if (typeof value === "object" && !isEncryptedPayload(value) && !columnPaths.includes(fullKey)) {
953
+ if (columnPaths.some((path2) => path2.startsWith(fullKey))) {
954
+ processNestedFields(
955
+ value,
956
+ fullKey,
957
+ columnPaths
958
+ );
959
+ }
960
+ } else if (columnPaths.includes(fullKey)) {
961
+ const id = index.toString();
962
+ keyMap[id] = { modelIndex, fieldKey: fullKey };
963
+ modelOperationFields[fullKey] = value;
964
+ index++;
965
+ const parts = fullKey.split(".");
966
+ let current = modelOtherFields;
967
+ for (let i = 0; i < parts.length - 1; i++) {
968
+ current = current[parts[i]];
969
+ }
970
+ delete current[parts[parts.length - 1]];
971
+ }
972
+ }
973
+ };
974
+ if (table) {
975
+ const columnPaths = Object.keys(table.build().columns);
976
+ processNestedFields(model, "", columnPaths);
977
+ } else {
978
+ const processEncryptedFields = (obj, prefix = "", columnPaths = []) => {
979
+ for (const [key, value] of Object.entries(obj)) {
980
+ const fullKey = prefix ? `${prefix}.${key}` : key;
981
+ if (value === null || value === void 0) {
982
+ modelNullFields[fullKey] = value;
983
+ continue;
984
+ }
985
+ if (typeof value === "object" && !isEncryptedPayload(value) && !columnPaths.includes(fullKey)) {
986
+ processEncryptedFields(
987
+ value,
988
+ fullKey,
989
+ columnPaths
990
+ );
991
+ } else if (isEncryptedPayload(value)) {
992
+ const id = index.toString();
993
+ keyMap[id] = { modelIndex, fieldKey: fullKey };
994
+ modelOperationFields[fullKey] = value;
995
+ index++;
996
+ const parts = fullKey.split(".");
997
+ let current = modelOtherFields;
998
+ for (let i = 0; i < parts.length - 1; i++) {
999
+ current = current[parts[i]];
1000
+ }
1001
+ delete current[parts[parts.length - 1]];
1002
+ }
1003
+ }
1004
+ };
1005
+ processEncryptedFields(model);
1006
+ }
1007
+ otherFields.push(modelOtherFields);
1008
+ operationFields.push(modelOperationFields);
1009
+ nullFields.push(modelNullFields);
1010
+ }
1011
+ return { otherFields, operationFields, keyMap, nullFields };
1012
+ }
1013
+ async function bulkEncryptModels(models, table, client, auditData) {
1014
+ if (!client) {
1015
+ throw new Error("Client not initialized");
1016
+ }
1017
+ if (!models || models.length === 0) {
1018
+ return [];
1019
+ }
1020
+ const { otherFields, operationFields, keyMap, nullFields } = prepareBulkModelsForOperation(models, table);
1021
+ const bulkEncryptPayload = operationFields.flatMap(
1022
+ (fields, modelIndex) => Object.entries(fields).map(([key, value]) => ({
1023
+ id: `${modelIndex}-${key}`,
1024
+ plaintext: value,
1025
+ table: table.tableName,
1026
+ column: key
1027
+ }))
1028
+ );
1029
+ const encryptedData = await handleMultiModelBulkOperation(
1030
+ bulkEncryptPayload,
1031
+ (items) => (0, import_protect_ffi4.encryptBulk)(client, {
1032
+ plaintexts: items,
1033
+ unverifiedContext: auditData?.metadata
1034
+ }),
1035
+ keyMap
1036
+ );
1037
+ return models.map((_, modelIndex) => {
1038
+ const result = { ...otherFields[modelIndex] };
1039
+ for (const [key, value] of Object.entries(nullFields[modelIndex])) {
1040
+ const parts = key.split(".");
1041
+ setNestedValue(result, parts, value);
1042
+ }
1043
+ const modelData = Object.fromEntries(
1044
+ Object.entries(encryptedData).filter(([key]) => {
1045
+ const [idx] = key.split("-");
1046
+ return Number.parseInt(idx) === modelIndex;
1047
+ }).map(([key, value]) => {
1048
+ const [_2, fieldKey] = key.split("-");
1049
+ return [fieldKey, value];
1050
+ })
1051
+ );
1052
+ for (const [key, value] of Object.entries(modelData)) {
1053
+ const parts = key.split(".");
1054
+ setNestedValue(result, parts, value);
1055
+ }
1056
+ return result;
1057
+ });
1058
+ }
1059
+ async function bulkDecryptModels(models, client, auditData) {
1060
+ if (!client) {
1061
+ throw new Error("Client not initialized");
1062
+ }
1063
+ if (!models || models.length === 0) {
1064
+ return [];
1065
+ }
1066
+ const { otherFields, operationFields, keyMap, nullFields } = prepareBulkModelsForOperation(models);
1067
+ const bulkDecryptPayload = operationFields.flatMap(
1068
+ (fields, modelIndex) => Object.entries(fields).map(([key, value]) => ({
1069
+ id: `${modelIndex}-${key}`,
1070
+ ciphertext: value
1071
+ }))
1072
+ );
1073
+ const decryptedFields = await handleMultiModelBulkOperation(
1074
+ bulkDecryptPayload,
1075
+ (items) => (0, import_protect_ffi4.decryptBulk)(client, {
1076
+ ciphertexts: items,
1077
+ unverifiedContext: auditData?.metadata
1078
+ }),
1079
+ keyMap
1080
+ );
1081
+ return models.map((_, modelIndex) => {
1082
+ const result = { ...otherFields[modelIndex] };
1083
+ for (const [key, value] of Object.entries(nullFields[modelIndex])) {
1084
+ const parts = key.split(".");
1085
+ setNestedValue(result, parts, value);
1086
+ }
1087
+ const modelData = Object.fromEntries(
1088
+ Object.entries(decryptedFields).filter(([key]) => {
1089
+ const [idx] = key.split("-");
1090
+ return Number.parseInt(idx) === modelIndex;
1091
+ }).map(([key, value]) => {
1092
+ const [_2, fieldKey] = key.split("-");
1093
+ return [fieldKey, value];
1094
+ })
1095
+ );
1096
+ for (const [key, value] of Object.entries(modelData)) {
1097
+ const parts = key.split(".");
1098
+ setNestedValue(result, parts, value);
1099
+ }
1100
+ return result;
1101
+ });
1102
+ }
1103
+ async function bulkDecryptModelsWithLockContext(models, client, lockContext, auditData) {
1104
+ if (!client) {
1105
+ throw new Error("Client not initialized");
1106
+ }
1107
+ if (!lockContext) {
1108
+ throw new Error("Lock context is not initialized");
1109
+ }
1110
+ const { otherFields, operationFields, keyMap, nullFields } = prepareBulkModelsForOperation(models);
1111
+ const bulkDecryptPayload = operationFields.flatMap(
1112
+ (fields, modelIndex) => Object.entries(fields).map(([key, value]) => ({
1113
+ id: `${modelIndex}-${key}`,
1114
+ ciphertext: value,
1115
+ lockContext: lockContext.context
1116
+ }))
1117
+ );
1118
+ const decryptedFields = await handleMultiModelBulkOperation(
1119
+ bulkDecryptPayload,
1120
+ (items) => (0, import_protect_ffi4.decryptBulk)(client, {
1121
+ ciphertexts: items,
1122
+ serviceToken: lockContext.ctsToken,
1123
+ unverifiedContext: auditData?.metadata
1124
+ }),
1125
+ keyMap
1126
+ );
1127
+ return models.map((_, modelIndex) => {
1128
+ const result = { ...otherFields[modelIndex] };
1129
+ for (const [key, value] of Object.entries(nullFields[modelIndex])) {
1130
+ const parts = key.split(".");
1131
+ setNestedValue(result, parts, value);
1132
+ }
1133
+ const modelData = Object.fromEntries(
1134
+ Object.entries(decryptedFields).filter(([key]) => {
1135
+ const [idx] = key.split("-");
1136
+ return Number.parseInt(idx) === modelIndex;
1137
+ }).map(([key, value]) => {
1138
+ const [_2, fieldKey] = key.split("-");
1139
+ return [fieldKey, value];
1140
+ })
1141
+ );
1142
+ for (const [key, value] of Object.entries(modelData)) {
1143
+ const parts = key.split(".");
1144
+ setNestedValue(result, parts, value);
1145
+ }
1146
+ return result;
1147
+ });
1148
+ }
1149
+ async function bulkEncryptModelsWithLockContext(models, table, client, lockContext, auditData) {
1150
+ if (!client) {
1151
+ throw new Error("Client not initialized");
1152
+ }
1153
+ if (!lockContext) {
1154
+ throw new Error("Lock context is not initialized");
1155
+ }
1156
+ const { otherFields, operationFields, keyMap, nullFields } = prepareBulkModelsForOperation(models, table);
1157
+ const bulkEncryptPayload = operationFields.flatMap(
1158
+ (fields, modelIndex) => Object.entries(fields).map(([key, value]) => ({
1159
+ id: `${modelIndex}-${key}`,
1160
+ plaintext: value,
1161
+ table: table.tableName,
1162
+ column: key,
1163
+ lockContext: lockContext.context
1164
+ }))
1165
+ );
1166
+ const encryptedData = await handleMultiModelBulkOperation(
1167
+ bulkEncryptPayload,
1168
+ (items) => (0, import_protect_ffi4.encryptBulk)(client, {
1169
+ plaintexts: items,
1170
+ serviceToken: lockContext.ctsToken,
1171
+ unverifiedContext: auditData?.metadata
1172
+ }),
1173
+ keyMap
1174
+ );
1175
+ return models.map((_, modelIndex) => {
1176
+ const result = { ...otherFields[modelIndex] };
1177
+ for (const [key, value] of Object.entries(nullFields[modelIndex])) {
1178
+ const parts = key.split(".");
1179
+ setNestedValue(result, parts, value);
1180
+ }
1181
+ const modelData = Object.fromEntries(
1182
+ Object.entries(encryptedData).filter(([key]) => {
1183
+ const [idx] = key.split("-");
1184
+ return Number.parseInt(idx) === modelIndex;
1185
+ }).map(([key, value]) => {
1186
+ const [_2, fieldKey] = key.split("-");
1187
+ return [fieldKey, value];
1188
+ })
1189
+ );
1190
+ for (const [key, value] of Object.entries(modelData)) {
1191
+ const parts = key.split(".");
1192
+ setNestedValue(result, parts, value);
1193
+ }
1194
+ return result;
1195
+ });
1196
+ }
1197
+
1198
+ // src/encryption/operations/bulk-decrypt-models.ts
1199
+ var BulkDecryptModelsOperation = class extends EncryptionOperation {
1200
+ client;
1201
+ models;
1202
+ constructor(client, models) {
1203
+ super();
1204
+ this.client = client;
1205
+ this.models = models;
1206
+ }
1207
+ withLockContext(lockContext) {
1208
+ return new BulkDecryptModelsOperationWithLockContext(this, lockContext);
1209
+ }
1210
+ async execute() {
1211
+ const log = (0, import_evlog.createRequestLogger)();
1212
+ log.set({
1213
+ op: "bulkDecryptModels",
1214
+ count: this.models.length,
1215
+ lockContext: false
1216
+ });
1217
+ const result = await (0, import_result3.withResult)(
1218
+ async () => {
1219
+ if (!this.client) {
1220
+ throw noClientError();
1221
+ }
1222
+ const auditData = this.getAuditData();
1223
+ return await bulkDecryptModels(this.models, this.client, auditData);
1224
+ },
1225
+ (error) => {
1226
+ log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1227
+ return {
1228
+ type: EncryptionErrorTypes.DecryptionError,
1229
+ message: error.message,
1230
+ code: getErrorCode(error)
1231
+ };
1232
+ }
1233
+ );
1234
+ log.emit();
1235
+ return result;
1236
+ }
1237
+ getOperation() {
1238
+ return {
1239
+ client: this.client,
1240
+ models: this.models
1241
+ };
1242
+ }
1243
+ };
1244
+ var BulkDecryptModelsOperationWithLockContext = class extends EncryptionOperation {
1245
+ operation;
1246
+ lockContext;
1247
+ constructor(operation, lockContext) {
1248
+ super();
1249
+ this.operation = operation;
1250
+ this.lockContext = lockContext;
1251
+ const auditData = operation.getAuditData();
1252
+ if (auditData) {
1253
+ this.audit(auditData);
1254
+ }
1255
+ }
1256
+ async execute() {
1257
+ const { client, models } = this.operation.getOperation();
1258
+ const log = (0, import_evlog.createRequestLogger)();
1259
+ log.set({
1260
+ op: "bulkDecryptModels",
1261
+ count: models.length,
1262
+ lockContext: true
1263
+ });
1264
+ const result = await (0, import_result3.withResult)(
1265
+ async () => {
1266
+ if (!client) {
1267
+ throw noClientError();
1268
+ }
1269
+ const context = await this.lockContext.getLockContext();
1270
+ if (context.failure) {
1271
+ throw new Error(`[encryption]: ${context.failure.message}`);
1272
+ }
1273
+ const auditData = this.getAuditData();
1274
+ return await bulkDecryptModelsWithLockContext(
1275
+ models,
1276
+ client,
1277
+ context.data,
1278
+ auditData
1279
+ );
1280
+ },
1281
+ (error) => {
1282
+ log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1283
+ return {
1284
+ type: EncryptionErrorTypes.DecryptionError,
1285
+ message: error.message,
1286
+ code: getErrorCode(error)
1287
+ };
1288
+ }
1289
+ );
1290
+ log.emit();
1291
+ return result;
1292
+ }
1293
+ };
1294
+
1295
+ // src/encryption/operations/bulk-encrypt.ts
1296
+ var import_result4 = require("@byteslice/result");
1297
+ var import_protect_ffi5 = require("@cipherstash/protect-ffi");
1298
+ var createEncryptPayloads = (plaintexts, column, table, lockContext) => {
1299
+ return plaintexts.map(({ id, plaintext }) => ({
1300
+ id,
1301
+ plaintext,
1302
+ column: column.getName(),
1303
+ table: table.tableName,
1304
+ ...lockContext && { lockContext }
1305
+ }));
1306
+ };
1307
+ var BulkEncryptOperation = class extends EncryptionOperation {
1308
+ client;
1309
+ plaintexts;
1310
+ column;
1311
+ table;
1312
+ constructor(client, plaintexts, opts) {
1313
+ super();
1314
+ this.client = client;
1315
+ this.plaintexts = plaintexts;
1316
+ this.column = opts.column;
1317
+ this.table = opts.table;
1318
+ }
1319
+ withLockContext(lockContext) {
1320
+ return new BulkEncryptOperationWithLockContext(this, lockContext);
1321
+ }
1322
+ async execute() {
1323
+ const log = (0, import_evlog.createRequestLogger)();
1324
+ log.set({
1325
+ op: "bulkEncrypt",
1326
+ table: this.table.tableName,
1327
+ column: this.column.getName(),
1328
+ count: this.plaintexts?.length ?? 0,
1329
+ lockContext: false
1330
+ });
1331
+ const result = await (0, import_result4.withResult)(
1332
+ async () => {
1333
+ if (!this.client) {
1334
+ throw noClientError();
1335
+ }
1336
+ if (!this.plaintexts || this.plaintexts.length === 0) {
1337
+ return [];
1338
+ }
1339
+ const payloads = createEncryptPayloads(
1340
+ this.plaintexts,
1341
+ this.column,
1342
+ this.table
1343
+ );
1344
+ const { metadata } = this.getAuditData();
1345
+ const encryptedData = await (0, import_protect_ffi5.encryptBulk)(this.client, {
1346
+ plaintexts: payloads,
1347
+ unverifiedContext: metadata
1348
+ });
1349
+ return encryptedData.map((data, i) => ({
1350
+ id: this.plaintexts[i].id,
1351
+ data
1352
+ }));
1353
+ },
1354
+ (error) => {
1355
+ log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1356
+ return {
1357
+ type: EncryptionErrorTypes.EncryptionError,
1358
+ message: error.message,
1359
+ code: getErrorCode(error)
1360
+ };
1361
+ }
1362
+ );
1363
+ log.emit();
1364
+ return result;
1365
+ }
1366
+ getOperation() {
1367
+ return {
1368
+ client: this.client,
1369
+ plaintexts: this.plaintexts,
1370
+ column: this.column,
1371
+ table: this.table
1372
+ };
1373
+ }
1374
+ };
1375
+ var BulkEncryptOperationWithLockContext = class extends EncryptionOperation {
1376
+ operation;
1377
+ lockContext;
1378
+ constructor(operation, lockContext) {
1379
+ super();
1380
+ this.operation = operation;
1381
+ this.lockContext = lockContext;
1382
+ const auditData = operation.getAuditData();
1383
+ if (auditData) {
1384
+ this.audit(auditData);
1385
+ }
1386
+ }
1387
+ async execute() {
1388
+ const { client, plaintexts, column, table } = this.operation.getOperation();
1389
+ const log = (0, import_evlog.createRequestLogger)();
1390
+ log.set({
1391
+ op: "bulkEncrypt",
1392
+ table: table.tableName,
1393
+ column: column.getName(),
1394
+ count: plaintexts?.length ?? 0,
1395
+ lockContext: true
1396
+ });
1397
+ const result = await (0, import_result4.withResult)(
1398
+ async () => {
1399
+ if (!client) {
1400
+ throw noClientError();
1401
+ }
1402
+ if (!plaintexts || plaintexts.length === 0) {
1403
+ return [];
1404
+ }
1405
+ const context = await this.lockContext.getLockContext();
1406
+ if (context.failure) {
1407
+ throw new Error(`[encryption]: ${context.failure.message}`);
1408
+ }
1409
+ const payloads = createEncryptPayloads(
1410
+ plaintexts,
1411
+ column,
1412
+ table,
1413
+ context.data.context
1414
+ );
1415
+ const { metadata } = this.getAuditData();
1416
+ const encryptedData = await (0, import_protect_ffi5.encryptBulk)(client, {
1417
+ plaintexts: payloads,
1418
+ serviceToken: context.data.ctsToken,
1419
+ unverifiedContext: metadata
1420
+ });
1421
+ return encryptedData.map((data, i) => ({
1422
+ id: plaintexts[i].id,
1423
+ data
1424
+ }));
1425
+ },
1426
+ (error) => {
1427
+ log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1428
+ return {
1429
+ type: EncryptionErrorTypes.EncryptionError,
1430
+ message: error.message,
1431
+ code: getErrorCode(error)
1432
+ };
1433
+ }
1434
+ );
1435
+ log.emit();
1436
+ return result;
1437
+ }
1438
+ };
1439
+
1440
+ // src/encryption/operations/bulk-encrypt-models.ts
1441
+ var import_result5 = require("@byteslice/result");
1442
+ var BulkEncryptModelsOperation = class extends EncryptionOperation {
1443
+ client;
1444
+ models;
1445
+ table;
1446
+ constructor(client, models, table) {
1447
+ super();
1448
+ this.client = client;
1449
+ this.models = models;
1450
+ this.table = table;
1451
+ }
1452
+ withLockContext(lockContext) {
1453
+ return new BulkEncryptModelsOperationWithLockContext(this, lockContext);
1454
+ }
1455
+ async execute() {
1456
+ const log = (0, import_evlog.createRequestLogger)();
1457
+ log.set({
1458
+ op: "bulkEncryptModels",
1459
+ table: this.table.tableName,
1460
+ count: this.models.length,
1461
+ lockContext: false
1462
+ });
1463
+ const result = await (0, import_result5.withResult)(
1464
+ async () => {
1465
+ if (!this.client) {
1466
+ throw noClientError();
1467
+ }
1468
+ const auditData = this.getAuditData();
1469
+ return await bulkEncryptModels(
1470
+ this.models,
1471
+ this.table,
1472
+ this.client,
1473
+ auditData
1474
+ );
1475
+ },
1476
+ (error) => {
1477
+ log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1478
+ return {
1479
+ type: EncryptionErrorTypes.EncryptionError,
1480
+ message: error.message,
1481
+ code: getErrorCode(error)
1482
+ };
1483
+ }
1484
+ );
1485
+ log.emit();
1486
+ return result;
1487
+ }
1488
+ getOperation() {
1489
+ return {
1490
+ client: this.client,
1491
+ models: this.models,
1492
+ table: this.table
1493
+ };
1494
+ }
1495
+ };
1496
+ var BulkEncryptModelsOperationWithLockContext = class extends EncryptionOperation {
1497
+ operation;
1498
+ lockContext;
1499
+ constructor(operation, lockContext) {
1500
+ super();
1501
+ this.operation = operation;
1502
+ this.lockContext = lockContext;
1503
+ const auditData = operation.getAuditData();
1504
+ if (auditData) {
1505
+ this.audit(auditData);
1506
+ }
1507
+ }
1508
+ async execute() {
1509
+ const { client, models, table } = this.operation.getOperation();
1510
+ const log = (0, import_evlog.createRequestLogger)();
1511
+ log.set({
1512
+ op: "bulkEncryptModels",
1513
+ table: table.tableName,
1514
+ count: models.length,
1515
+ lockContext: true
1516
+ });
1517
+ const result = await (0, import_result5.withResult)(
1518
+ async () => {
1519
+ if (!client) {
1520
+ throw noClientError();
1521
+ }
1522
+ const context = await this.lockContext.getLockContext();
1523
+ if (context.failure) {
1524
+ throw new Error(`[encryption]: ${context.failure.message}`);
1525
+ }
1526
+ const auditData = this.getAuditData();
1527
+ return await bulkEncryptModelsWithLockContext(
1528
+ models,
1529
+ table,
1530
+ client,
1531
+ context.data,
1532
+ auditData
1533
+ );
1534
+ },
1535
+ (error) => {
1536
+ log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1537
+ return {
1538
+ type: EncryptionErrorTypes.EncryptionError,
1539
+ message: error.message,
1540
+ code: getErrorCode(error)
1541
+ };
1542
+ }
1543
+ );
1544
+ log.emit();
1545
+ return result;
1546
+ }
1547
+ };
1548
+
1549
+ // src/encryption/operations/decrypt.ts
1550
+ var import_result6 = require("@byteslice/result");
1551
+ var import_protect_ffi6 = require("@cipherstash/protect-ffi");
1552
+ var DecryptOperation = class extends EncryptionOperation {
1553
+ client;
1554
+ encryptedData;
1555
+ constructor(client, encryptedData) {
1556
+ super();
1557
+ this.client = client;
1558
+ this.encryptedData = encryptedData;
1559
+ }
1560
+ withLockContext(lockContext) {
1561
+ return new DecryptOperationWithLockContext(this, lockContext);
1562
+ }
1563
+ async execute() {
1564
+ const log = (0, import_evlog.createRequestLogger)();
1565
+ log.set({
1566
+ op: "decrypt",
1567
+ lockContext: false
1568
+ });
1569
+ const result = await (0, import_result6.withResult)(
1570
+ async () => {
1571
+ if (!this.client) {
1572
+ throw noClientError();
1573
+ }
1574
+ const { metadata } = this.getAuditData();
1575
+ return await (0, import_protect_ffi6.decrypt)(this.client, {
1576
+ ciphertext: this.encryptedData,
1577
+ unverifiedContext: metadata
1578
+ });
1579
+ },
1580
+ (error) => {
1581
+ log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1582
+ return {
1583
+ type: EncryptionErrorTypes.DecryptionError,
1584
+ message: error.message,
1585
+ code: getErrorCode(error)
1586
+ };
1587
+ }
1588
+ );
1589
+ log.emit();
1590
+ return result;
1591
+ }
1592
+ getOperation() {
1593
+ return {
1594
+ client: this.client,
1595
+ encryptedData: this.encryptedData,
1596
+ auditData: this.getAuditData()
1597
+ };
1598
+ }
1599
+ };
1600
+ var DecryptOperationWithLockContext = class extends EncryptionOperation {
1601
+ operation;
1602
+ lockContext;
1603
+ constructor(operation, lockContext) {
1604
+ super();
1605
+ this.operation = operation;
1606
+ this.lockContext = lockContext;
1607
+ const auditData = operation.getAuditData();
1608
+ if (auditData) {
1609
+ this.audit(auditData);
1610
+ }
1611
+ }
1612
+ async execute() {
1613
+ const log = (0, import_evlog.createRequestLogger)();
1614
+ log.set({
1615
+ op: "decrypt",
1616
+ lockContext: true
1617
+ });
1618
+ const result = await (0, import_result6.withResult)(
1619
+ async () => {
1620
+ const { client, encryptedData } = this.operation.getOperation();
1621
+ if (!client) {
1622
+ throw noClientError();
1623
+ }
1624
+ const { metadata } = this.getAuditData();
1625
+ const context = await this.lockContext.getLockContext();
1626
+ if (context.failure) {
1627
+ throw new Error(`[encryption]: ${context.failure.message}`);
1628
+ }
1629
+ return await (0, import_protect_ffi6.decrypt)(client, {
1630
+ ciphertext: encryptedData,
1631
+ unverifiedContext: metadata,
1632
+ lockContext: context.data.context,
1633
+ serviceToken: context.data.ctsToken
1634
+ });
1635
+ },
1636
+ (error) => {
1637
+ log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1638
+ return {
1639
+ type: EncryptionErrorTypes.DecryptionError,
1640
+ message: error.message,
1641
+ code: getErrorCode(error)
1642
+ };
1643
+ }
1644
+ );
1645
+ log.emit();
1646
+ return result;
1647
+ }
1648
+ };
1649
+
1650
+ // src/encryption/operations/decrypt-model.ts
1651
+ var import_result7 = require("@byteslice/result");
1652
+ var DecryptModelOperation = class extends EncryptionOperation {
1653
+ client;
1654
+ model;
1655
+ constructor(client, model) {
1656
+ super();
1657
+ this.client = client;
1658
+ this.model = model;
1659
+ }
1660
+ withLockContext(lockContext) {
1661
+ return new DecryptModelOperationWithLockContext(this, lockContext);
1662
+ }
1663
+ async execute() {
1664
+ const log = (0, import_evlog.createRequestLogger)();
1665
+ log.set({
1666
+ op: "decryptModel",
1667
+ lockContext: false
1668
+ });
1669
+ const result = await (0, import_result7.withResult)(
1670
+ async () => {
1671
+ if (!this.client) {
1672
+ throw noClientError();
1673
+ }
1674
+ const auditData = this.getAuditData();
1675
+ return await decryptModelFields(this.model, this.client, auditData);
1676
+ },
1677
+ (error) => {
1678
+ log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1679
+ return {
1680
+ type: EncryptionErrorTypes.DecryptionError,
1681
+ message: error.message,
1682
+ code: getErrorCode(error)
1683
+ };
1684
+ }
1685
+ );
1686
+ log.emit();
1687
+ return result;
1688
+ }
1689
+ getOperation() {
1690
+ return {
1691
+ client: this.client,
1692
+ model: this.model
1693
+ };
1694
+ }
1695
+ };
1696
+ var DecryptModelOperationWithLockContext = class extends EncryptionOperation {
1697
+ operation;
1698
+ lockContext;
1699
+ constructor(operation, lockContext) {
1700
+ super();
1701
+ this.operation = operation;
1702
+ this.lockContext = lockContext;
1703
+ const auditData = operation.getAuditData();
1704
+ if (auditData) {
1705
+ this.audit(auditData);
1706
+ }
1707
+ }
1708
+ async execute() {
1709
+ const log = (0, import_evlog.createRequestLogger)();
1710
+ log.set({
1711
+ op: "decryptModel",
1712
+ lockContext: true
1713
+ });
1714
+ const result = await (0, import_result7.withResult)(
1715
+ async () => {
1716
+ const { client, model } = this.operation.getOperation();
1717
+ if (!client) {
1718
+ throw noClientError();
1719
+ }
1720
+ const context = await this.lockContext.getLockContext();
1721
+ if (context.failure) {
1722
+ throw new Error(`[encryption]: ${context.failure.message}`);
1723
+ }
1724
+ const auditData = this.getAuditData();
1725
+ return await decryptModelFieldsWithLockContext(
1726
+ model,
1727
+ client,
1728
+ context.data,
1729
+ auditData
1730
+ );
1731
+ },
1732
+ (error) => {
1733
+ log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1734
+ return {
1735
+ type: EncryptionErrorTypes.DecryptionError,
1736
+ message: error.message,
1737
+ code: getErrorCode(error)
1738
+ };
1739
+ }
1740
+ );
1741
+ log.emit();
1742
+ return result;
1743
+ }
1744
+ };
1745
+
1746
+ // src/encryption/operations/encrypt.ts
1747
+ var import_result8 = require("@byteslice/result");
1748
+ var import_protect_ffi7 = require("@cipherstash/protect-ffi");
1749
+ var EncryptOperation = class extends EncryptionOperation {
1750
+ client;
1751
+ plaintext;
1752
+ column;
1753
+ table;
1754
+ constructor(client, plaintext, opts) {
1755
+ super();
1756
+ this.client = client;
1757
+ this.plaintext = plaintext;
1758
+ this.column = opts.column;
1759
+ this.table = opts.table;
1760
+ }
1761
+ withLockContext(lockContext) {
1762
+ return new EncryptOperationWithLockContext(this, lockContext);
1763
+ }
1764
+ async execute() {
1765
+ const log = (0, import_evlog.createRequestLogger)();
1766
+ log.set({
1767
+ op: "encrypt",
1768
+ table: this.table.tableName,
1769
+ column: this.column.getName(),
1770
+ lockContext: false
1771
+ });
1772
+ const result = await (0, import_result8.withResult)(
1773
+ async () => {
1774
+ if (!this.client) {
1775
+ throw noClientError();
1776
+ }
1777
+ if (typeof this.plaintext === "number" && Number.isNaN(this.plaintext)) {
1778
+ throw new Error("[encryption]: Cannot encrypt NaN value");
1779
+ }
1780
+ if (typeof this.plaintext === "number" && !Number.isFinite(this.plaintext)) {
1781
+ throw new Error("[encryption]: Cannot encrypt Infinity value");
1782
+ }
1783
+ const { metadata } = this.getAuditData();
1784
+ return await (0, import_protect_ffi7.encrypt)(this.client, {
1785
+ plaintext: this.plaintext,
1786
+ column: this.column.getName(),
1787
+ table: this.table.tableName,
1788
+ unverifiedContext: metadata
1789
+ });
1790
+ },
1791
+ (error) => {
1792
+ log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1793
+ return {
1794
+ type: EncryptionErrorTypes.EncryptionError,
1795
+ message: error.message,
1796
+ code: getErrorCode(error)
1797
+ };
1798
+ }
1799
+ );
1800
+ log.emit();
1801
+ return result;
1802
+ }
1803
+ getOperation() {
1804
+ return {
1805
+ client: this.client,
1806
+ plaintext: this.plaintext,
1807
+ column: this.column,
1808
+ table: this.table
1809
+ };
1810
+ }
1811
+ };
1812
+ var EncryptOperationWithLockContext = class extends EncryptionOperation {
1813
+ operation;
1814
+ lockContext;
1815
+ constructor(operation, lockContext) {
1816
+ super();
1817
+ this.operation = operation;
1818
+ this.lockContext = lockContext;
1819
+ const auditData = operation.getAuditData();
1820
+ if (auditData) {
1821
+ this.audit(auditData);
1822
+ }
1823
+ }
1824
+ async execute() {
1825
+ const { client, plaintext, column, table } = this.operation.getOperation();
1826
+ const log = (0, import_evlog.createRequestLogger)();
1827
+ log.set({
1828
+ op: "encrypt",
1829
+ table: table.tableName,
1830
+ column: column.getName(),
1831
+ lockContext: true
1832
+ });
1833
+ const result = await (0, import_result8.withResult)(
1834
+ async () => {
1835
+ if (!client) {
1836
+ throw noClientError();
1837
+ }
1838
+ const { metadata } = this.getAuditData();
1839
+ const context = await this.lockContext.getLockContext();
1840
+ if (context.failure) {
1841
+ throw new Error(`[encryption]: ${context.failure.message}`);
1842
+ }
1843
+ return await (0, import_protect_ffi7.encrypt)(client, {
1844
+ plaintext,
1845
+ column: column.getName(),
1846
+ table: table.tableName,
1847
+ lockContext: context.data.context,
1848
+ serviceToken: context.data.ctsToken,
1849
+ unverifiedContext: metadata
1850
+ });
1851
+ },
1852
+ (error) => {
1853
+ log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1854
+ return {
1855
+ type: EncryptionErrorTypes.EncryptionError,
1856
+ message: error.message,
1857
+ code: getErrorCode(error)
1858
+ };
1859
+ }
1860
+ );
1861
+ log.emit();
1862
+ return result;
1863
+ }
1864
+ };
1865
+
1866
+ // src/encryption/operations/encrypt-model.ts
1867
+ var import_result9 = require("@byteslice/result");
1868
+ var EncryptModelOperation = class extends EncryptionOperation {
1869
+ client;
1870
+ model;
1871
+ table;
1872
+ constructor(client, model, table) {
1873
+ super();
1874
+ this.client = client;
1875
+ this.model = model;
1876
+ this.table = table;
1877
+ }
1878
+ withLockContext(lockContext) {
1879
+ return new EncryptModelOperationWithLockContext(this, lockContext);
1880
+ }
1881
+ async execute() {
1882
+ const log = (0, import_evlog.createRequestLogger)();
1883
+ log.set({
1884
+ op: "encryptModel",
1885
+ table: this.table.tableName,
1886
+ lockContext: false
1887
+ });
1888
+ const result = await (0, import_result9.withResult)(
1889
+ async () => {
1890
+ if (!this.client) {
1891
+ throw noClientError();
1892
+ }
1893
+ const auditData = this.getAuditData();
1894
+ return await encryptModelFields(
1895
+ this.model,
1896
+ this.table,
1897
+ this.client,
1898
+ auditData
1899
+ );
1900
+ },
1901
+ (error) => {
1902
+ log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1903
+ return {
1904
+ type: EncryptionErrorTypes.EncryptionError,
1905
+ message: error.message,
1906
+ code: getErrorCode(error)
1907
+ };
1908
+ }
1909
+ );
1910
+ log.emit();
1911
+ return result;
1912
+ }
1913
+ getOperation() {
1914
+ return {
1915
+ client: this.client,
1916
+ model: this.model,
1917
+ table: this.table
1918
+ };
1919
+ }
1920
+ };
1921
+ var EncryptModelOperationWithLockContext = class extends EncryptionOperation {
1922
+ operation;
1923
+ lockContext;
1924
+ constructor(operation, lockContext) {
1925
+ super();
1926
+ this.operation = operation;
1927
+ this.lockContext = lockContext;
1928
+ const auditData = operation.getAuditData();
1929
+ if (auditData) {
1930
+ this.audit(auditData);
1931
+ }
1932
+ }
1933
+ async execute() {
1934
+ const { client, model, table } = this.operation.getOperation();
1935
+ const log = (0, import_evlog.createRequestLogger)();
1936
+ log.set({
1937
+ op: "encryptModel",
1938
+ table: table.tableName,
1939
+ lockContext: true
1940
+ });
1941
+ const result = await (0, import_result9.withResult)(
1942
+ async () => {
1943
+ if (!client) {
1944
+ throw noClientError();
1945
+ }
1946
+ const context = await this.lockContext.getLockContext();
1947
+ if (context.failure) {
1948
+ throw new Error(`[encryption]: ${context.failure.message}`);
1949
+ }
1950
+ const auditData = this.getAuditData();
1951
+ return await encryptModelFieldsWithLockContext(
1952
+ model,
1953
+ table,
1954
+ client,
1955
+ context.data,
1956
+ auditData
1957
+ );
1958
+ },
1959
+ (error) => {
1960
+ log.set({ errorCode: getErrorCode(error) ?? "unknown" });
1961
+ return {
1962
+ type: EncryptionErrorTypes.EncryptionError,
1963
+ message: error.message,
1964
+ code: getErrorCode(error)
1965
+ };
1966
+ }
1967
+ );
1968
+ log.emit();
1969
+ return result;
1970
+ }
1971
+ };
1972
+
1973
+ // src/encryption/operations/encrypt-query.ts
1974
+ var import_result10 = require("@byteslice/result");
1975
+ var import_protect_ffi8 = require("@cipherstash/protect-ffi");
1976
+ var EncryptQueryOperation = class extends EncryptionOperation {
1977
+ constructor(client, plaintext, opts) {
1978
+ super();
1979
+ this.client = client;
1980
+ this.plaintext = plaintext;
1981
+ this.opts = opts;
1982
+ }
1983
+ withLockContext(lockContext) {
1984
+ return new EncryptQueryOperationWithLockContext(
1985
+ this.client,
1986
+ this.plaintext,
1987
+ this.opts,
1988
+ lockContext,
1989
+ this.auditMetadata
1990
+ );
1991
+ }
1992
+ async execute() {
1993
+ const log = (0, import_evlog.createRequestLogger)();
1994
+ log.set({
1995
+ op: "encryptQuery",
1996
+ table: this.opts.table.tableName,
1997
+ column: this.opts.column.getName(),
1998
+ queryType: this.opts.queryType,
1999
+ lockContext: false
2000
+ });
2001
+ const validationError = validateNumericValue(this.plaintext);
2002
+ if (validationError?.failure) {
2003
+ log.emit();
2004
+ return { failure: validationError.failure };
2005
+ }
2006
+ const result = await (0, import_result10.withResult)(
2007
+ async () => {
2008
+ if (!this.client) throw noClientError();
2009
+ const { metadata } = this.getAuditData();
2010
+ const { indexType, queryOp } = resolveIndexType(
2011
+ this.opts.column,
2012
+ this.opts.queryType,
2013
+ this.plaintext
2014
+ );
2015
+ assertValueIndexCompatibility(
2016
+ this.plaintext,
2017
+ indexType,
2018
+ this.opts.column.getName()
2019
+ );
2020
+ const encrypted = await (0, import_protect_ffi8.encryptQuery)(this.client, {
2021
+ plaintext: this.plaintext,
2022
+ column: this.opts.column.getName(),
2023
+ table: this.opts.table.tableName,
2024
+ indexType,
2025
+ queryOp,
2026
+ unverifiedContext: metadata
2027
+ });
2028
+ return formatEncryptedResult(encrypted, this.opts.returnType);
2029
+ },
2030
+ (error) => {
2031
+ log.set({ errorCode: getErrorCode(error) ?? "unknown" });
2032
+ return {
2033
+ type: EncryptionErrorTypes.EncryptionError,
2034
+ message: error.message,
2035
+ code: getErrorCode(error)
2036
+ };
2037
+ }
2038
+ );
2039
+ log.emit();
2040
+ return result;
2041
+ }
2042
+ getOperation() {
2043
+ return { client: this.client, plaintext: this.plaintext, ...this.opts };
2044
+ }
2045
+ };
2046
+ var EncryptQueryOperationWithLockContext = class extends EncryptionOperation {
2047
+ constructor(client, plaintext, opts, lockContext, auditMetadata) {
2048
+ super();
2049
+ this.client = client;
2050
+ this.plaintext = plaintext;
2051
+ this.opts = opts;
2052
+ this.lockContext = lockContext;
2053
+ this.auditMetadata = auditMetadata;
2054
+ }
2055
+ async execute() {
2056
+ const log = (0, import_evlog.createRequestLogger)();
2057
+ log.set({
2058
+ op: "encryptQuery",
2059
+ table: this.opts.table.tableName,
2060
+ column: this.opts.column.getName(),
2061
+ queryType: this.opts.queryType,
2062
+ lockContext: true
2063
+ });
2064
+ const validationError = validateNumericValue(this.plaintext);
2065
+ if (validationError?.failure) {
2066
+ log.emit();
2067
+ return { failure: validationError.failure };
2068
+ }
2069
+ const lockContextResult = await this.lockContext.getLockContext();
2070
+ if (lockContextResult.failure) {
2071
+ log.emit();
2072
+ return { failure: lockContextResult.failure };
2073
+ }
2074
+ const { ctsToken, context } = lockContextResult.data;
2075
+ const result = await (0, import_result10.withResult)(
2076
+ async () => {
2077
+ if (!this.client) throw noClientError();
2078
+ const { metadata } = this.getAuditData();
2079
+ const { indexType, queryOp } = resolveIndexType(
2080
+ this.opts.column,
2081
+ this.opts.queryType,
2082
+ this.plaintext
2083
+ );
2084
+ assertValueIndexCompatibility(
2085
+ this.plaintext,
2086
+ indexType,
2087
+ this.opts.column.getName()
2088
+ );
2089
+ const encrypted = await (0, import_protect_ffi8.encryptQuery)(this.client, {
2090
+ plaintext: this.plaintext,
2091
+ column: this.opts.column.getName(),
2092
+ table: this.opts.table.tableName,
2093
+ indexType,
2094
+ queryOp,
2095
+ lockContext: context,
2096
+ serviceToken: ctsToken,
2097
+ unverifiedContext: metadata
2098
+ });
2099
+ return formatEncryptedResult(encrypted, this.opts.returnType);
2100
+ },
2101
+ (error) => {
2102
+ log.set({ errorCode: getErrorCode(error) ?? "unknown" });
2103
+ return {
2104
+ type: EncryptionErrorTypes.EncryptionError,
2105
+ message: error.message,
2106
+ code: getErrorCode(error)
2107
+ };
2108
+ }
2109
+ );
2110
+ log.emit();
2111
+ return result;
2112
+ }
2113
+ };
2114
+
2115
+ // src/encryption/index.ts
2116
+ var noClientError = () => new Error(
2117
+ "The Encryption client has not been initialized. Please call init() before using the client."
2118
+ );
2119
+ var EncryptionClient = class {
2120
+ client;
2121
+ encryptConfig;
2122
+ workspaceId;
2123
+ constructor(workspaceCrn) {
2124
+ const workspaceId = loadWorkSpaceId(workspaceCrn);
2125
+ this.workspaceId = workspaceId;
2126
+ }
2127
+ /**
2128
+ * Initializes the EncryptionClient with the provided configuration.
2129
+ * @internal
2130
+ * @param config - The configuration object for initializing the client.
2131
+ * @returns A promise that resolves to a {@link Result} containing the initialized EncryptionClient or an {@link EncryptionError}.
2132
+ **/
2133
+ async init(config) {
2134
+ return await (0, import_result11.withResult)(
2135
+ async () => {
2136
+ const validated = encryptConfigSchema.parse(
2137
+ config.encryptConfig
2138
+ );
2139
+ logger.debug(
2140
+ "Initializing the Encryption client with the following config:",
2141
+ {
2142
+ encryptConfig: validated
2143
+ }
2144
+ );
2145
+ this.client = await (0, import_protect_ffi9.newClient)({
2146
+ encryptConfig: validated,
2147
+ clientOpts: {
2148
+ workspaceCrn: config.workspaceCrn ?? process.env.CS_WORKSPACE_CRN,
2149
+ accessKey: config.accessKey ?? process.env.CS_CLIENT_ACCESS_KEY,
2150
+ clientId: config.clientId ?? process.env.CS_CLIENT_ID,
2151
+ clientKey: config.clientKey ?? process.env.CS_CLIENT_KEY,
2152
+ keyset: toFfiKeysetIdentifier(config.keyset)
2153
+ }
2154
+ });
2155
+ this.encryptConfig = validated;
2156
+ logger.debug("Successfully initialized the Encryption client.");
2157
+ return this;
2158
+ },
2159
+ (error) => ({
2160
+ type: EncryptionErrorTypes.ClientInitError,
2161
+ message: error.message
2162
+ })
2163
+ );
2164
+ }
2165
+ /**
2166
+ * Encrypt a value - returns a promise which resolves to an encrypted value.
2167
+ *
2168
+ * @param plaintext - The plaintext value to be encrypted.
2169
+ * @param opts - Options specifying the column (or nested field) and table for encryption. See {@link EncryptOptions}.
2170
+ * @returns An EncryptOperation that can be awaited or chained with additional methods.
2171
+ *
2172
+ * @example
2173
+ * The following example demonstrates how to encrypt a value using the Encryption client.
2174
+ * It includes defining an encryption schema with {@link encryptedTable} and {@link encryptedColumn},
2175
+ * initializing the client with {@link Encryption}, and performing the encryption.
2176
+ *
2177
+ * `encrypt` returns an {@link EncryptOperation} which can be awaited to get a {@link Result}
2178
+ * which can either be the encrypted value or an {@link EncryptionError}.
2179
+ *
2180
+ * ```typescript
2181
+ * // Define encryption schema
2182
+ * import { Encryption } from "@cipherstash/stack"
2183
+ * import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
2184
+ * const userSchema = encryptedTable("users", {
2185
+ * email: encryptedColumn("email"),
2186
+ * });
2187
+ *
2188
+ * // Initialize Encryption client
2189
+ * const client = await Encryption({ schemas: [userSchema] })
2190
+ *
2191
+ * // Encrypt a value
2192
+ * const encryptedResult = await client.encrypt(
2193
+ * "person@example.com",
2194
+ * { column: userSchema.email, table: userSchema }
2195
+ * )
2196
+ *
2197
+ * // Handle encryption result
2198
+ * if (encryptedResult.failure) {
2199
+ * throw new Error(`Encryption failed: ${encryptedResult.failure.message}`);
2200
+ * }
2201
+ *
2202
+ * console.log("Encrypted data:", encryptedResult.data);
2203
+ * ```
2204
+ *
2205
+ * @example
2206
+ * When encrypting data, a {@link LockContext} can be provided to tie the encryption to a specific user or session.
2207
+ * This ensures that the same lock context is required for decryption.
2208
+ *
2209
+ * The following example demonstrates how to create a lock context using a user's JWT token
2210
+ * and use it during encryption.
2211
+ *
2212
+ * ```typescript
2213
+ * // Define encryption schema and initialize client as above
2214
+ *
2215
+ * // Create a lock for the user's `sub` claim from their JWT
2216
+ * const lc = new LockContext();
2217
+ * const lockContext = await lc.identify(userJwt);
2218
+ *
2219
+ * if (lockContext.failure) {
2220
+ * // Handle the failure
2221
+ * }
2222
+ *
2223
+ * // Encrypt a value with the lock context
2224
+ * // Decryption will then require the same lock context
2225
+ * const encryptedResult = await client.encrypt(
2226
+ * "person@example.com",
2227
+ * { column: userSchema.email, table: userSchema }
2228
+ * )
2229
+ * .withLockContext(lockContext)
2230
+ * ```
2231
+ *
2232
+ * @see {@link EncryptOptions}
2233
+ * @see {@link Result}
2234
+ * @see {@link encryptedTable}
2235
+ * @see {@link encryptedColumn}
2236
+ * @see {@link encryptedField}
2237
+ * @see {@link LockContext}
2238
+ * @see {@link EncryptOperation}
2239
+ */
2240
+ encrypt(plaintext, opts) {
2241
+ return new EncryptOperation(this.client, plaintext, opts);
2242
+ }
2243
+ encryptQuery(plaintextOrTerms, opts) {
2244
+ if (isScalarQueryTermArray(plaintextOrTerms)) {
2245
+ return new BatchEncryptQueryOperation(this.client, plaintextOrTerms);
2246
+ }
2247
+ if (Array.isArray(plaintextOrTerms) && plaintextOrTerms.length === 0 && !opts) {
2248
+ return new BatchEncryptQueryOperation(
2249
+ this.client,
2250
+ []
2251
+ );
2252
+ }
2253
+ if (!opts) {
2254
+ throw new Error("EncryptQueryOptions are required");
2255
+ }
2256
+ return new EncryptQueryOperation(
2257
+ this.client,
2258
+ plaintextOrTerms,
2259
+ opts
2260
+ );
2261
+ }
2262
+ /**
2263
+ * Decryption - returns a promise which resolves to a decrypted value.
2264
+ *
2265
+ * @param encryptedData - The encrypted data to be decrypted.
2266
+ * @returns A DecryptOperation that can be awaited or chained with additional methods.
2267
+ *
2268
+ * @example
2269
+ * The following example demonstrates how to decrypt a value that was previously encrypted using the {@link encrypt} method.
2270
+ * It includes encrypting a value first, then decrypting it, and handling the result.
2271
+ *
2272
+ * ```typescript
2273
+ * const encryptedData = await client.encrypt(
2274
+ * "person@example.com",
2275
+ * { column: "email", table: "users" }
2276
+ * )
2277
+ * const decryptResult = await client.decrypt(encryptedData)
2278
+ * if (decryptResult.failure) {
2279
+ * throw new Error(`Decryption failed: ${decryptResult.failure.message}`);
2280
+ * }
2281
+ * console.log("Decrypted data:", decryptResult.data);
2282
+ * ```
2283
+ *
2284
+ * @example
2285
+ * Provide a lock context when decrypting:
2286
+ * ```typescript
2287
+ * await client.decrypt(encryptedData)
2288
+ * .withLockContext(lockContext)
2289
+ * ```
2290
+ *
2291
+ * @see {@link LockContext}
2292
+ * @see {@link DecryptOperation}
2293
+ */
2294
+ decrypt(encryptedData) {
2295
+ return new DecryptOperation(this.client, encryptedData);
2296
+ }
2297
+ /**
2298
+ * Encrypt a model (object) based on the table schema.
2299
+ *
2300
+ * Only fields whose keys match columns defined in the table schema are encrypted.
2301
+ * All other fields are passed through unchanged. Returns a thenable operation
2302
+ * that supports `.withLockContext()` for identity-aware encryption.
2303
+ *
2304
+ * The return type is **schema-aware**: fields matching the table schema are
2305
+ * typed as `Encrypted`, while other fields retain their original types. For
2306
+ * best results, let TypeScript infer the type parameters from the arguments
2307
+ * rather than providing an explicit type argument.
2308
+ *
2309
+ * @param input - The model object with plaintext values to encrypt.
2310
+ * @param table - The table schema defining which fields to encrypt.
2311
+ * @returns An `EncryptModelOperation` that can be awaited to get a `Result`
2312
+ * containing the model with schema-defined fields typed as `Encrypted`,
2313
+ * or an `EncryptionError`.
2314
+ *
2315
+ * @example
2316
+ * ```typescript
2317
+ * import { Encryption } from "@cipherstash/stack"
2318
+ * import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
2319
+ *
2320
+ * type User = { id: string; email: string; createdAt: Date }
2321
+ *
2322
+ * const usersSchema = encryptedTable("users", {
2323
+ * email: encryptedColumn("email").equality(),
2324
+ * })
2325
+ *
2326
+ * const client = await Encryption({ schemas: [usersSchema] })
2327
+ *
2328
+ * // Let TypeScript infer the return type from the schema.
2329
+ * // result.data.email is typed as `Encrypted`, result.data.id stays `string`.
2330
+ * const result = await client.encryptModel(
2331
+ * { id: "user_123", email: "alice@example.com", createdAt: new Date() },
2332
+ * usersSchema,
2333
+ * )
2334
+ *
2335
+ * if (result.failure) {
2336
+ * console.error(result.failure.message)
2337
+ * } else {
2338
+ * console.log(result.data.id) // string
2339
+ * console.log(result.data.email) // Encrypted
2340
+ * }
2341
+ * ```
2342
+ */
2343
+ encryptModel(input, table) {
2344
+ return new EncryptModelOperation(
2345
+ this.client,
2346
+ input,
2347
+ table
2348
+ );
2349
+ }
2350
+ /**
2351
+ * Decrypt a model (object) whose fields contain encrypted values.
2352
+ *
2353
+ * Identifies encrypted fields automatically and decrypts them, returning the
2354
+ * model with plaintext values. Returns a thenable operation that supports
2355
+ * `.withLockContext()` for identity-aware decryption.
2356
+ *
2357
+ * @param input - The model object with encrypted field values.
2358
+ * @returns A `DecryptModelOperation<T>` that can be awaited to get a `Result`
2359
+ * containing the model with decrypted plaintext fields, or an `EncryptionError`.
2360
+ *
2361
+ * @example
2362
+ * ```typescript
2363
+ * // Decrypt a previously encrypted model
2364
+ * const decrypted = await client.decryptModel<User>(encryptedUser)
2365
+ *
2366
+ * if (decrypted.failure) {
2367
+ * console.error(decrypted.failure.message)
2368
+ * } else {
2369
+ * console.log(decrypted.data.email) // "alice@example.com"
2370
+ * }
2371
+ *
2372
+ * // With a lock context
2373
+ * const decrypted = await client
2374
+ * .decryptModel<User>(encryptedUser)
2375
+ * .withLockContext(lockContext)
2376
+ * ```
2377
+ */
2378
+ decryptModel(input) {
2379
+ return new DecryptModelOperation(this.client, input);
2380
+ }
2381
+ /**
2382
+ * Encrypt multiple models (objects) in a single bulk operation.
2383
+ *
2384
+ * Performs a single call to ZeroKMS regardless of the number of models,
2385
+ * while still using a unique key for each encrypted value. Only fields
2386
+ * matching the table schema are encrypted; other fields pass through unchanged.
2387
+ *
2388
+ * The return type is **schema-aware**: fields matching the table schema are
2389
+ * typed as `Encrypted`, while other fields retain their original types. For
2390
+ * best results, let TypeScript infer the type parameters from the arguments.
2391
+ *
2392
+ * @param input - An array of model objects with plaintext values to encrypt.
2393
+ * @param table - The table schema defining which fields to encrypt.
2394
+ * @returns A `BulkEncryptModelsOperation` that can be awaited to get a `Result`
2395
+ * containing an array of models with schema-defined fields typed as `Encrypted`,
2396
+ * or an `EncryptionError`.
2397
+ *
2398
+ * @example
2399
+ * ```typescript
2400
+ * import { Encryption } from "@cipherstash/stack"
2401
+ * import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
2402
+ *
2403
+ * type User = { id: string; email: string }
2404
+ *
2405
+ * const usersSchema = encryptedTable("users", {
2406
+ * email: encryptedColumn("email"),
2407
+ * })
2408
+ *
2409
+ * const client = await Encryption({ schemas: [usersSchema] })
2410
+ *
2411
+ * // Let TypeScript infer the return type from the schema.
2412
+ * // Each item's email is typed as `Encrypted`, id stays `string`.
2413
+ * const result = await client.bulkEncryptModels(
2414
+ * [
2415
+ * { id: "1", email: "alice@example.com" },
2416
+ * { id: "2", email: "bob@example.com" },
2417
+ * ],
2418
+ * usersSchema,
2419
+ * )
2420
+ *
2421
+ * if (!result.failure) {
2422
+ * console.log(result.data) // array of models with encrypted email fields
2423
+ * }
2424
+ * ```
2425
+ */
2426
+ bulkEncryptModels(input, table) {
2427
+ return new BulkEncryptModelsOperation(
2428
+ this.client,
2429
+ input,
2430
+ table
2431
+ );
2432
+ }
2433
+ /**
2434
+ * Decrypt multiple models (objects) in a single bulk operation.
2435
+ *
2436
+ * Performs a single call to ZeroKMS regardless of the number of models,
2437
+ * restoring all encrypted fields to their original plaintext values.
2438
+ *
2439
+ * @param input - An array of model objects with encrypted field values.
2440
+ * @returns A `BulkDecryptModelsOperation<T>` that can be awaited to get a `Result`
2441
+ * containing an array of models with decrypted plaintext fields, or an `EncryptionError`.
2442
+ *
2443
+ * @example
2444
+ * ```typescript
2445
+ * const encryptedUsers = encryptedResult.data // from bulkEncryptModels
2446
+ *
2447
+ * const result = await client.bulkDecryptModels<User>(encryptedUsers)
2448
+ *
2449
+ * if (!result.failure) {
2450
+ * for (const user of result.data) {
2451
+ * console.log(user.email) // plaintext email
2452
+ * }
2453
+ * }
2454
+ *
2455
+ * // With a lock context
2456
+ * const result = await client
2457
+ * .bulkDecryptModels<User>(encryptedUsers)
2458
+ * .withLockContext(lockContext)
2459
+ * ```
2460
+ */
2461
+ bulkDecryptModels(input) {
2462
+ return new BulkDecryptModelsOperation(this.client, input);
2463
+ }
2464
+ /**
2465
+ * Encrypt multiple plaintext values in a single bulk operation.
2466
+ *
2467
+ * Each value is encrypted with its own unique key via a single call to ZeroKMS.
2468
+ * Values can include optional `id` fields for correlating results back to
2469
+ * your application data.
2470
+ *
2471
+ * @param plaintexts - An array of objects with `plaintext` (and optional `id`) fields.
2472
+ * @param opts - Options specifying the target column (or nested {@link encryptedField}) and table. See {@link EncryptOptions}.
2473
+ * @returns A `BulkEncryptOperation` that can be awaited to get a `Result`
2474
+ * containing an array of `{ id?, data: Encrypted }` objects, or an `EncryptionError`.
2475
+ *
2476
+ * @example
2477
+ * ```typescript
2478
+ * import { Encryption } from "@cipherstash/stack"
2479
+ * import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
2480
+ *
2481
+ * const users = encryptedTable("users", {
2482
+ * email: encryptedColumn("email"),
2483
+ * })
2484
+ * const client = await Encryption({ schemas: [users] })
2485
+ *
2486
+ * const result = await client.bulkEncrypt(
2487
+ * [
2488
+ * { id: "u1", plaintext: "alice@example.com" },
2489
+ * { id: "u2", plaintext: "bob@example.com" },
2490
+ * ],
2491
+ * { column: users.email, table: users },
2492
+ * )
2493
+ *
2494
+ * if (!result.failure) {
2495
+ * // result.data = [{ id: "u1", data: Encrypted }, { id: "u2", data: Encrypted }, ...]
2496
+ * console.log(result.data)
2497
+ * }
2498
+ * ```
2499
+ */
2500
+ bulkEncrypt(plaintexts, opts) {
2501
+ return new BulkEncryptOperation(this.client, plaintexts, opts);
2502
+ }
2503
+ /**
2504
+ * Decrypt multiple encrypted values in a single bulk operation.
2505
+ *
2506
+ * Performs a single call to ZeroKMS to decrypt all values. The result uses
2507
+ * a multi-status pattern: each item in the returned array has either a `data`
2508
+ * field (success) or an `error` field (failure), allowing graceful handling
2509
+ * of partial failures.
2510
+ *
2511
+ * @param encryptedPayloads - An array of objects with `data` (encrypted payload) and optional `id` fields.
2512
+ * @returns A `BulkDecryptOperation` that can be awaited to get a `Result`
2513
+ * containing an array of `{ id?, data: plaintext }` or `{ id?, error: string }` objects,
2514
+ * or an `EncryptionError` if the entire operation fails.
2515
+ *
2516
+ * @example
2517
+ * ```typescript
2518
+ * const encrypted = await client.bulkEncrypt(plaintexts, { column: users.email, table: users })
2519
+ *
2520
+ * const result = await client.bulkDecrypt(encrypted.data)
2521
+ *
2522
+ * if (!result.failure) {
2523
+ * for (const item of result.data) {
2524
+ * if ("data" in item) {
2525
+ * console.log(`${item.id}: ${item.data}`)
2526
+ * } else {
2527
+ * console.error(`${item.id} failed: ${item.error}`)
2528
+ * }
2529
+ * }
2530
+ * }
2531
+ * ```
2532
+ */
2533
+ bulkDecrypt(encryptedPayloads) {
2534
+ return new BulkDecryptOperation(this.client, encryptedPayloads);
2535
+ }
2536
+ /** e.g., debugging or environment info */
2537
+ clientInfo() {
2538
+ return {
2539
+ workspaceId: this.workspaceId
2540
+ };
2541
+ }
2542
+ };
2543
+ var Encryption = async (config) => {
2544
+ const { schemas, config: clientConfig } = config;
2545
+ if (!schemas.length) {
2546
+ throw new Error(
2547
+ "[encryption]: At least one encryptedTable must be provided to initialize the encryption client"
2548
+ );
2549
+ }
2550
+ if (clientConfig?.keyset && "id" in clientConfig.keyset && !(0, import_uuid.validate)(clientConfig.keyset.id)) {
2551
+ throw new Error(
2552
+ "[encryption]: Invalid UUID provided for keyset id. Must be a valid UUID."
2553
+ );
2554
+ }
2555
+ const client = new EncryptionClient(clientConfig?.workspaceCrn);
2556
+ const encryptConfig = buildEncryptConfig(...schemas);
2557
+ const result = await client.init({
2558
+ encryptConfig,
2559
+ ...clientConfig
2560
+ });
2561
+ if (result.failure) {
2562
+ throw new Error(`[encryption]: ${result.failure.message}`);
2563
+ }
2564
+ return result.data;
2565
+ };
2566
+ // Annotate the CommonJS export names for ESM import in node:
2567
+ 0 && (module.exports = {
2568
+ Encryption,
2569
+ EncryptionClient,
2570
+ noClientError
2571
+ });
2572
+ //# sourceMappingURL=index.cjs.map