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