@cipherstash/stack 0.16.0 → 0.19.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 (102) hide show
  1. package/CHANGELOG.md +225 -0
  2. package/README.md +2 -2
  3. package/dist/{chunk-CY5B4LIF.js → chunk-36AA7IBJ.js} +303 -204
  4. package/dist/chunk-36AA7IBJ.js.map +1 -0
  5. package/dist/{chunk-MD6742R6.js → chunk-4AVL4VZD.js} +1 -1
  6. package/dist/chunk-4AVL4VZD.js.map +1 -0
  7. package/dist/{chunk-4RNBI3UH.js → chunk-BNO32ZMV.js} +1 -1
  8. package/dist/chunk-BNO32ZMV.js.map +1 -0
  9. package/dist/{chunk-6FD7OBGM.js → chunk-HQANMV7R.js} +3 -3
  10. package/dist/chunk-HQANMV7R.js.map +1 -0
  11. package/dist/chunk-IADZCZEA.js +23 -0
  12. package/dist/chunk-IADZCZEA.js.map +1 -0
  13. package/dist/chunk-IBSK6P33.js +209 -0
  14. package/dist/chunk-IBSK6P33.js.map +1 -0
  15. package/dist/chunk-JSG2AMDI.js +17 -0
  16. package/dist/chunk-JSG2AMDI.js.map +1 -0
  17. package/dist/chunk-MP3SSDNN.js +4439 -0
  18. package/dist/chunk-MP3SSDNN.js.map +1 -0
  19. package/dist/chunk-OFQ555AX.js +191 -0
  20. package/dist/chunk-OFQ555AX.js.map +1 -0
  21. package/dist/chunk-PZ5AY32C.js +10 -0
  22. package/dist/chunk-PZ5AY32C.js.map +1 -0
  23. package/dist/chunk-U66S7VIF.js +435 -0
  24. package/dist/chunk-U66S7VIF.js.map +1 -0
  25. package/dist/{client-C_7TF8EL.d.cts → client-DSGHBN-g.d.cts} +209 -72
  26. package/dist/{client-BLbA6aFD.d.ts → client-DfCrlHXh.d.ts} +209 -72
  27. package/dist/client.cjs +4103 -43
  28. package/dist/client.cjs.map +1 -1
  29. package/dist/client.d.cts +5 -5
  30. package/dist/client.d.ts +5 -5
  31. package/dist/client.js +3 -1
  32. package/dist/drizzle/index.cjs +4139 -77
  33. package/dist/drizzle/index.cjs.map +1 -1
  34. package/dist/drizzle/index.d.cts +33 -33
  35. package/dist/drizzle/index.d.ts +33 -33
  36. package/dist/drizzle/index.js +39 -33
  37. package/dist/drizzle/index.js.map +1 -1
  38. package/dist/dynamodb/index.cjs +31 -14
  39. package/dist/dynamodb/index.cjs.map +1 -1
  40. package/dist/dynamodb/index.d.cts +12 -12
  41. package/dist/dynamodb/index.d.ts +12 -12
  42. package/dist/dynamodb/index.js +16 -12
  43. package/dist/dynamodb/index.js.map +1 -1
  44. package/dist/encryption/index.cjs +4611 -257
  45. package/dist/encryption/index.cjs.map +1 -1
  46. package/dist/encryption/index.d.cts +3 -3
  47. package/dist/encryption/index.d.ts +3 -3
  48. package/dist/encryption/index.js +35 -7
  49. package/dist/encryption/v3.cjs +7791 -0
  50. package/dist/encryption/v3.cjs.map +1 -0
  51. package/dist/encryption/v3.d.cts +100 -0
  52. package/dist/encryption/v3.d.ts +100 -0
  53. package/dist/encryption/v3.js +161 -0
  54. package/dist/encryption/v3.js.map +1 -0
  55. package/dist/eql/v3/index.cjs +612 -0
  56. package/dist/eql/v3/index.cjs.map +1 -0
  57. package/dist/eql/v3/index.d.cts +71 -0
  58. package/dist/eql/v3/index.d.ts +71 -0
  59. package/dist/eql/v3/index.js +89 -0
  60. package/dist/eql/v3/index.js.map +1 -0
  61. package/dist/errors/index.js +1 -0
  62. package/dist/identity/index.cjs +55 -35
  63. package/dist/identity/index.cjs.map +1 -1
  64. package/dist/identity/index.d.cts +54 -30
  65. package/dist/identity/index.d.ts +54 -30
  66. package/dist/identity/index.js +9 -177
  67. package/dist/identity/index.js.map +1 -1
  68. package/dist/index.cjs +4625 -273
  69. package/dist/index.cjs.map +1 -1
  70. package/dist/index.d.cts +18 -7
  71. package/dist/index.d.ts +18 -7
  72. package/dist/index.js +20 -5
  73. package/dist/index.js.map +1 -1
  74. package/dist/schema/index.cjs +4108 -44
  75. package/dist/schema/index.cjs.map +1 -1
  76. package/dist/schema/index.d.cts +1 -1
  77. package/dist/schema/index.d.ts +1 -1
  78. package/dist/schema/index.js +3 -1
  79. package/dist/supabase/index.cjs +4500 -75
  80. package/dist/supabase/index.cjs.map +1 -1
  81. package/dist/supabase/index.d.cts +113 -34
  82. package/dist/supabase/index.d.ts +113 -34
  83. package/dist/supabase/index.js +350 -32
  84. package/dist/supabase/index.js.map +1 -1
  85. package/dist/table-CIH7jZ2h.d.ts +603 -0
  86. package/dist/table-DihEAlxG.d.cts +603 -0
  87. package/dist/{types-public-Dv-r6Ik0.d.ts → types-public-CpS5KjwX.d.cts} +209 -36
  88. package/dist/{types-public-Dv-r6Ik0.d.cts → types-public-CpS5KjwX.d.ts} +209 -36
  89. package/dist/types-public.cjs.map +1 -1
  90. package/dist/types-public.d.cts +2 -2
  91. package/dist/types-public.d.ts +2 -2
  92. package/dist/types-public.js +2 -1
  93. package/dist/wasm-inline.d.ts +1269 -0
  94. package/dist/wasm-inline.js +4594 -0
  95. package/dist/wasm-inline.js.map +1 -0
  96. package/package.json +63 -13
  97. package/dist/chunk-4RNBI3UH.js.map +0 -1
  98. package/dist/chunk-6FD7OBGM.js.map +0 -1
  99. package/dist/chunk-CY5B4LIF.js.map +0 -1
  100. package/dist/chunk-JDZ2QUUF.js +0 -381
  101. package/dist/chunk-JDZ2QUUF.js.map +0 -1
  102. package/dist/chunk-MD6742R6.js.map +0 -1
@@ -1,26 +1,31 @@
1
+ import {
2
+ resolveLockContext
3
+ } from "./chunk-OFQ555AX.js";
1
4
  import {
2
5
  formatEncryptedResult,
3
6
  isEncryptedPayload,
4
7
  toFfiKeysetIdentifier
5
- } from "./chunk-4RNBI3UH.js";
8
+ } from "./chunk-BNO32ZMV.js";
6
9
  import {
7
10
  buildEncryptConfig,
8
11
  encryptConfigSchema
9
- } from "./chunk-JDZ2QUUF.js";
12
+ } from "./chunk-MP3SSDNN.js";
10
13
  import {
11
14
  queryTypeToFfi,
12
15
  queryTypeToQueryOp
13
- } from "./chunk-MD6742R6.js";
16
+ } from "./chunk-4AVL4VZD.js";
17
+ import {
18
+ withResult
19
+ } from "./chunk-JSG2AMDI.js";
14
20
  import {
15
21
  createRequestLogger,
16
22
  logger
17
- } from "./chunk-6FD7OBGM.js";
23
+ } from "./chunk-HQANMV7R.js";
18
24
  import {
19
25
  EncryptionErrorTypes
20
26
  } from "./chunk-LBMC4D6D.js";
21
27
 
22
28
  // src/encryption/index.ts
23
- import { withResult as withResult11 } from "@byteslice/result";
24
29
  import { newClient } from "@cipherstash/protect-ffi";
25
30
 
26
31
  // ../../node_modules/.pnpm/uuid@14.0.0/node_modules/uuid/dist-node/regex.js
@@ -37,6 +42,11 @@ function isScalarQueryTermArray(value) {
37
42
  return Array.isArray(value) && value.length > 0 && typeof value[0] === "object" && value[0] !== null && "column" in value[0] && "table" in value[0];
38
43
  }
39
44
 
45
+ // src/encryption/operations/batch-encrypt-query.ts
46
+ import {
47
+ encryptQueryBulk as ffiEncryptQueryBulk
48
+ } from "@cipherstash/protect-ffi";
49
+
40
50
  // src/encryption/helpers/error-code.ts
41
51
  import {
42
52
  ProtectError as FfiProtectError
@@ -45,12 +55,6 @@ function getErrorCode(error) {
45
55
  return error instanceof FfiProtectError ? error.code : void 0;
46
56
  }
47
57
 
48
- // src/encryption/operations/batch-encrypt-query.ts
49
- import { withResult } from "@byteslice/result";
50
- import {
51
- encryptQueryBulk as ffiEncryptQueryBulk
52
- } from "@cipherstash/protect-ffi";
53
-
54
58
  // src/encryption/helpers/infer-index-type.ts
55
59
  function inferIndexType(column) {
56
60
  const config = column.build();
@@ -70,7 +74,7 @@ function inferQueryOpFromPlaintext(plaintext) {
70
74
  if (typeof plaintext === "string") {
71
75
  return "ste_vec_selector";
72
76
  }
73
- if (typeof plaintext === "object" || typeof plaintext === "number" || typeof plaintext === "boolean" || typeof plaintext === "bigint") {
77
+ if (typeof plaintext === "object" || typeof plaintext === "number" || typeof plaintext === "boolean") {
74
78
  return "ste_vec_term";
75
79
  }
76
80
  return "ste_vec_term";
@@ -90,9 +94,18 @@ function validateIndexType(column, indexType) {
90
94
  );
91
95
  }
92
96
  }
97
+ function resolvesEqualityViaOre(column) {
98
+ if (!("getQueryCapabilities" in column)) return false;
99
+ if (!column.getQueryCapabilities().equality) return false;
100
+ const indexes = column.build().indexes ?? {};
101
+ return !indexes.unique && !!indexes.ore;
102
+ }
93
103
  function resolveIndexType(column, queryType, plaintext) {
94
104
  const indexType = queryType ? queryTypeToFfi[queryType] : inferIndexType(column);
95
105
  if (queryType) {
106
+ if (queryType === "equality" && resolvesEqualityViaOre(column)) {
107
+ return { indexType: "ore" };
108
+ }
96
109
  validateIndexType(column, indexType);
97
110
  if (queryType === "searchableJson") {
98
111
  if (plaintext === void 0 || plaintext === null) {
@@ -176,6 +189,15 @@ var EncryptionOperation = class {
176
189
  };
177
190
 
178
191
  // src/encryption/operations/batch-encrypt-query.ts
192
+ function filterNullTerms(terms) {
193
+ const nonNullTerms = [];
194
+ terms.forEach((term, index) => {
195
+ if (term.value !== null && term.value !== void 0) {
196
+ nonNullTerms.push({ term, originalIndex: index });
197
+ }
198
+ });
199
+ return { nonNullTerms };
200
+ }
179
201
  function buildQueryPayload(term, lockContext) {
180
202
  assertValidNumericValue(term.value);
181
203
  const { indexType, queryOp } = resolveIndexType(
@@ -196,10 +218,15 @@ function buildQueryPayload(term, lockContext) {
196
218
  }
197
219
  return payload;
198
220
  }
199
- function assembleResults(terms, encryptedValues) {
200
- return terms.map(
201
- (term, i) => formatEncryptedResult(encryptedValues[i], term.returnType)
202
- );
221
+ function assembleResults(totalLength, encryptedValues, nonNullTerms) {
222
+ const results = new Array(totalLength).fill(null);
223
+ nonNullTerms.forEach(({ term, originalIndex }, i) => {
224
+ results[originalIndex] = formatEncryptedResult(
225
+ encryptedValues[i],
226
+ term.returnType
227
+ );
228
+ });
229
+ return results;
203
230
  }
204
231
  var BatchEncryptQueryOperation = class extends EncryptionOperation {
205
232
  constructor(client, terms) {
@@ -207,6 +234,8 @@ var BatchEncryptQueryOperation = class extends EncryptionOperation {
207
234
  this.client = client;
208
235
  this.terms = terms;
209
236
  }
237
+ client;
238
+ terms;
210
239
  withLockContext(lockContext) {
211
240
  return new BatchEncryptQueryOperationWithLockContext(
212
241
  this.client,
@@ -226,18 +255,23 @@ var BatchEncryptQueryOperation = class extends EncryptionOperation {
226
255
  log.emit();
227
256
  return { data: [] };
228
257
  }
258
+ const { nonNullTerms } = filterNullTerms(this.terms);
259
+ if (nonNullTerms.length === 0) {
260
+ log.emit();
261
+ return { data: this.terms.map(() => null) };
262
+ }
229
263
  const result = await withResult(
230
264
  async () => {
231
265
  if (!this.client) throw noClientError();
232
266
  const { metadata } = this.getAuditData();
233
- const queries = this.terms.map(
234
- (term) => buildQueryPayload(term)
267
+ const queries = nonNullTerms.map(
268
+ ({ term }) => buildQueryPayload(term)
235
269
  );
236
270
  const encrypted = await ffiEncryptQueryBulk(this.client, {
237
271
  queries,
238
272
  unverifiedContext: metadata
239
273
  });
240
- return assembleResults(this.terms, encrypted);
274
+ return assembleResults(this.terms.length, encrypted, nonNullTerms);
241
275
  },
242
276
  (error) => {
243
277
  log.set({ errorCode: getErrorCode(error) ?? "unknown" });
@@ -260,6 +294,9 @@ var BatchEncryptQueryOperationWithLockContext = class extends EncryptionOperatio
260
294
  this.lockContext = lockContext;
261
295
  this.auditMetadata = auditMetadata;
262
296
  }
297
+ client;
298
+ terms;
299
+ lockContext;
263
300
  async execute() {
264
301
  const log = createRequestLogger();
265
302
  log.set({
@@ -271,25 +308,24 @@ var BatchEncryptQueryOperationWithLockContext = class extends EncryptionOperatio
271
308
  log.emit();
272
309
  return { data: [] };
273
310
  }
274
- const lockContextResult = await this.lockContext.getLockContext();
275
- if (lockContextResult.failure) {
311
+ const { nonNullTerms } = filterNullTerms(this.terms);
312
+ if (nonNullTerms.length === 0) {
276
313
  log.emit();
277
- return { failure: lockContextResult.failure };
314
+ return { data: this.terms.map(() => null) };
278
315
  }
279
- const { ctsToken, context } = lockContextResult.data;
280
316
  const result = await withResult(
281
317
  async () => {
282
318
  if (!this.client) throw noClientError();
319
+ const context = resolveLockContext(this.lockContext);
283
320
  const { metadata } = this.getAuditData();
284
- const queries = this.terms.map(
285
- (term) => buildQueryPayload(term, context)
321
+ const queries = nonNullTerms.map(
322
+ ({ term }) => buildQueryPayload(term, context)
286
323
  );
287
324
  const encrypted = await ffiEncryptQueryBulk(this.client, {
288
325
  queries,
289
- serviceToken: ctsToken,
290
326
  unverifiedContext: metadata
291
327
  });
292
- return assembleResults(this.terms, encrypted);
328
+ return assembleResults(this.terms.length, encrypted, nonNullTerms);
293
329
  },
294
330
  (error) => {
295
331
  log.set({ errorCode: getErrorCode(error) ?? "unknown" });
@@ -306,30 +342,34 @@ var BatchEncryptQueryOperationWithLockContext = class extends EncryptionOperatio
306
342
  };
307
343
 
308
344
  // src/encryption/operations/bulk-decrypt.ts
309
- import { withResult as withResult2 } from "@byteslice/result";
310
345
  import {
311
346
  decryptBulkFallible
312
347
  } from "@cipherstash/protect-ffi";
313
348
  var createDecryptPayloads = (encryptedPayloads, lockContext) => {
314
- return encryptedPayloads.map(({ id, data }) => ({
349
+ return encryptedPayloads.filter(({ data }) => data !== null).map(({ id, data }) => ({
315
350
  id,
316
351
  ciphertext: data,
317
352
  ...lockContext && { lockContext }
318
353
  }));
319
354
  };
355
+ var createNullResult = (encryptedPayloads) => encryptedPayloads.map(({ id }) => ({ id, data: null }));
320
356
  var mapDecryptedDataToResult = (encryptedPayloads, decryptedData) => {
321
- return decryptedData.map((decryptResult, i) => {
322
- if ("error" in decryptResult) {
323
- return {
324
- id: encryptedPayloads[i].id,
325
- error: decryptResult.error
326
- };
357
+ const result = new Array(encryptedPayloads.length);
358
+ let decryptedIndex = 0;
359
+ for (let i = 0; i < encryptedPayloads.length; i++) {
360
+ if (encryptedPayloads[i].data === null) {
361
+ result[i] = { id: encryptedPayloads[i].id, data: null };
362
+ } else {
363
+ const decryptResult = decryptedData[decryptedIndex];
364
+ if ("error" in decryptResult) {
365
+ result[i] = { id: encryptedPayloads[i].id, error: decryptResult.error };
366
+ } else {
367
+ result[i] = { id: encryptedPayloads[i].id, data: decryptResult.data };
368
+ }
369
+ decryptedIndex++;
327
370
  }
328
- return {
329
- id: encryptedPayloads[i].id,
330
- data: decryptResult.data
331
- };
332
- });
371
+ }
372
+ return result;
333
373
  };
334
374
  var BulkDecryptOperation = class extends EncryptionOperation {
335
375
  client;
@@ -349,15 +389,18 @@ var BulkDecryptOperation = class extends EncryptionOperation {
349
389
  count: this.encryptedPayloads?.length ?? 0,
350
390
  lockContext: false
351
391
  });
352
- const result = await withResult2(
392
+ const result = await withResult(
353
393
  async () => {
354
394
  if (!this.client) throw noClientError();
355
395
  if (!this.encryptedPayloads || this.encryptedPayloads.length === 0)
356
396
  return [];
357
- const payloads = createDecryptPayloads(this.encryptedPayloads);
397
+ const nonNullPayloads = createDecryptPayloads(this.encryptedPayloads);
398
+ if (nonNullPayloads.length === 0) {
399
+ return createNullResult(this.encryptedPayloads);
400
+ }
358
401
  const { metadata } = this.getAuditData();
359
402
  const decryptedData = await decryptBulkFallible(this.client, {
360
- ciphertexts: payloads,
403
+ ciphertexts: nonNullPayloads,
361
404
  unverifiedContext: metadata
362
405
  });
363
406
  return mapDecryptedDataToResult(this.encryptedPayloads, decryptedData);
@@ -401,22 +444,21 @@ var BulkDecryptOperationWithLockContext = class extends EncryptionOperation {
401
444
  count: encryptedPayloads?.length ?? 0,
402
445
  lockContext: true
403
446
  });
404
- const result = await withResult2(
447
+ const result = await withResult(
405
448
  async () => {
406
449
  if (!client) throw noClientError();
407
450
  if (!encryptedPayloads || encryptedPayloads.length === 0) return [];
408
- const context = await this.lockContext.getLockContext();
409
- if (context.failure) {
410
- throw new Error(`[encryption]: ${context.failure.message}`);
411
- }
412
- const payloads = createDecryptPayloads(
451
+ const lockContext = resolveLockContext(this.lockContext);
452
+ const nonNullPayloads = createDecryptPayloads(
413
453
  encryptedPayloads,
414
- context.data.context
454
+ lockContext
415
455
  );
456
+ if (nonNullPayloads.length === 0) {
457
+ return createNullResult(encryptedPayloads);
458
+ }
416
459
  const { metadata } = this.getAuditData();
417
460
  const decryptedData = await decryptBulkFallible(client, {
418
- ciphertexts: payloads,
419
- serviceToken: context.data.ctsToken,
461
+ ciphertexts: nonNullPayloads,
420
462
  unverifiedContext: metadata
421
463
  });
422
464
  return mapDecryptedDataToResult(encryptedPayloads, decryptedData);
@@ -435,9 +477,6 @@ var BulkDecryptOperationWithLockContext = class extends EncryptionOperation {
435
477
  }
436
478
  };
437
479
 
438
- // src/encryption/operations/bulk-decrypt-models.ts
439
- import { withResult as withResult3 } from "@byteslice/result";
440
-
441
480
  // src/encryption/helpers/model-helpers.ts
442
481
  import {
443
482
  decryptBulk,
@@ -519,6 +558,17 @@ function prepareFieldsForDecryption(model) {
519
558
  processNestedFields(model);
520
559
  return { otherFields, operationFields, keyMap, nullFields };
521
560
  }
561
+ function resolveEncryptColumnMap(table) {
562
+ const keyMap = table.buildColumnKeyMap?.();
563
+ if (keyMap) {
564
+ return {
565
+ columnPaths: Object.keys(keyMap),
566
+ toColumnName: (path) => keyMap[path] ?? path
567
+ };
568
+ }
569
+ const columnPaths = Object.keys(table.build().columns);
570
+ return { columnPaths, toColumnName: (path) => path };
571
+ }
522
572
  function prepareFieldsForEncryption(model, table) {
523
573
  const otherFields = { ...model };
524
574
  const operationFields = {};
@@ -554,7 +604,7 @@ function prepareFieldsForEncryption(model, table) {
554
604
  }
555
605
  }
556
606
  };
557
- const columnPaths = Object.keys(table.build().columns);
607
+ const { columnPaths } = resolveEncryptColumnMap(table);
558
608
  processNestedFields(model, "", columnPaths);
559
609
  return { otherFields, operationFields, keyMap, nullFields };
560
610
  }
@@ -593,12 +643,13 @@ async function encryptModelFields(model, table, client, auditData) {
593
643
  throw new Error("Client not initialized");
594
644
  }
595
645
  const { otherFields, operationFields, keyMap, nullFields } = prepareFieldsForEncryption(model, table);
646
+ const { toColumnName } = resolveEncryptColumnMap(table);
596
647
  const bulkEncryptPayload = Object.entries(operationFields).map(
597
648
  ([key, value]) => ({
598
649
  id: key,
599
650
  plaintext: value,
600
651
  table: table.tableName,
601
- column: key
652
+ column: toColumnName(key)
602
653
  })
603
654
  );
604
655
  const encryptedData = await handleSingleModelBulkOperation(
@@ -632,14 +683,13 @@ async function decryptModelFieldsWithLockContext(model, client, lockContext, aud
632
683
  ([key, value]) => ({
633
684
  id: key,
634
685
  ciphertext: value,
635
- lockContext: lockContext.context
686
+ lockContext
636
687
  })
637
688
  );
638
689
  const decryptedFields = await handleSingleModelBulkOperation(
639
690
  bulkDecryptPayload,
640
691
  (items) => decryptBulk(client, {
641
692
  ciphertexts: items,
642
- serviceToken: lockContext.ctsToken,
643
693
  unverifiedContext: auditData?.metadata
644
694
  }),
645
695
  keyMap
@@ -663,20 +713,20 @@ async function encryptModelFieldsWithLockContext(model, table, client, lockConte
663
713
  throw new Error("Lock context is not initialized");
664
714
  }
665
715
  const { otherFields, operationFields, keyMap, nullFields } = prepareFieldsForEncryption(model, table);
716
+ const { toColumnName } = resolveEncryptColumnMap(table);
666
717
  const bulkEncryptPayload = Object.entries(operationFields).map(
667
718
  ([key, value]) => ({
668
719
  id: key,
669
720
  plaintext: value,
670
721
  table: table.tableName,
671
- column: key,
672
- lockContext: lockContext.context
722
+ column: toColumnName(key),
723
+ lockContext
673
724
  })
674
725
  );
675
726
  const encryptedData = await handleSingleModelBulkOperation(
676
727
  bulkEncryptPayload,
677
728
  (items) => encryptBulk(client, {
678
729
  plaintexts: items,
679
- serviceToken: lockContext.ctsToken,
680
730
  unverifiedContext: auditData?.metadata
681
731
  }),
682
732
  keyMap
@@ -733,7 +783,7 @@ function prepareBulkModelsForOperation(models, table) {
733
783
  }
734
784
  };
735
785
  if (table) {
736
- const columnPaths = Object.keys(table.build().columns);
786
+ const { columnPaths } = resolveEncryptColumnMap(table);
737
787
  processNestedFields(model, "", columnPaths);
738
788
  } else {
739
789
  const processEncryptedFields = (obj, prefix = "", columnPaths = []) => {
@@ -771,6 +821,15 @@ function prepareBulkModelsForOperation(models, table) {
771
821
  }
772
822
  return { otherFields, operationFields, keyMap, nullFields };
773
823
  }
824
+ function fieldsForModelIndex(fields, modelIndex) {
825
+ const result = {};
826
+ for (const [id, value] of Object.entries(fields)) {
827
+ const sep = id.indexOf("-");
828
+ if (Number.parseInt(id.slice(0, sep), 10) !== modelIndex) continue;
829
+ result[id.slice(sep + 1)] = value;
830
+ }
831
+ return result;
832
+ }
774
833
  async function bulkEncryptModels(models, table, client, auditData) {
775
834
  if (!client) {
776
835
  throw new Error("Client not initialized");
@@ -779,12 +838,13 @@ async function bulkEncryptModels(models, table, client, auditData) {
779
838
  return [];
780
839
  }
781
840
  const { otherFields, operationFields, keyMap, nullFields } = prepareBulkModelsForOperation(models, table);
841
+ const { toColumnName } = resolveEncryptColumnMap(table);
782
842
  const bulkEncryptPayload = operationFields.flatMap(
783
843
  (fields, modelIndex) => Object.entries(fields).map(([key, value]) => ({
784
844
  id: `${modelIndex}-${key}`,
785
845
  plaintext: value,
786
846
  table: table.tableName,
787
- column: key
847
+ column: toColumnName(key)
788
848
  }))
789
849
  );
790
850
  const encryptedData = await handleMultiModelBulkOperation(
@@ -801,15 +861,7 @@ async function bulkEncryptModels(models, table, client, auditData) {
801
861
  const parts = key.split(".");
802
862
  setNestedValue(result, parts, value);
803
863
  }
804
- const modelData = Object.fromEntries(
805
- Object.entries(encryptedData).filter(([key]) => {
806
- const [idx] = key.split("-");
807
- return Number.parseInt(idx) === modelIndex;
808
- }).map(([key, value]) => {
809
- const [_2, fieldKey] = key.split("-");
810
- return [fieldKey, value];
811
- })
812
- );
864
+ const modelData = fieldsForModelIndex(encryptedData, modelIndex);
813
865
  for (const [key, value] of Object.entries(modelData)) {
814
866
  const parts = key.split(".");
815
867
  setNestedValue(result, parts, value);
@@ -845,15 +897,7 @@ async function bulkDecryptModels(models, client, auditData) {
845
897
  const parts = key.split(".");
846
898
  setNestedValue(result, parts, value);
847
899
  }
848
- const modelData = Object.fromEntries(
849
- Object.entries(decryptedFields).filter(([key]) => {
850
- const [idx] = key.split("-");
851
- return Number.parseInt(idx) === modelIndex;
852
- }).map(([key, value]) => {
853
- const [_2, fieldKey] = key.split("-");
854
- return [fieldKey, value];
855
- })
856
- );
900
+ const modelData = fieldsForModelIndex(decryptedFields, modelIndex);
857
901
  for (const [key, value] of Object.entries(modelData)) {
858
902
  const parts = key.split(".");
859
903
  setNestedValue(result, parts, value);
@@ -873,14 +917,13 @@ async function bulkDecryptModelsWithLockContext(models, client, lockContext, aud
873
917
  (fields, modelIndex) => Object.entries(fields).map(([key, value]) => ({
874
918
  id: `${modelIndex}-${key}`,
875
919
  ciphertext: value,
876
- lockContext: lockContext.context
920
+ lockContext
877
921
  }))
878
922
  );
879
923
  const decryptedFields = await handleMultiModelBulkOperation(
880
924
  bulkDecryptPayload,
881
925
  (items) => decryptBulk(client, {
882
926
  ciphertexts: items,
883
- serviceToken: lockContext.ctsToken,
884
927
  unverifiedContext: auditData?.metadata
885
928
  }),
886
929
  keyMap
@@ -891,15 +934,7 @@ async function bulkDecryptModelsWithLockContext(models, client, lockContext, aud
891
934
  const parts = key.split(".");
892
935
  setNestedValue(result, parts, value);
893
936
  }
894
- const modelData = Object.fromEntries(
895
- Object.entries(decryptedFields).filter(([key]) => {
896
- const [idx] = key.split("-");
897
- return Number.parseInt(idx) === modelIndex;
898
- }).map(([key, value]) => {
899
- const [_2, fieldKey] = key.split("-");
900
- return [fieldKey, value];
901
- })
902
- );
937
+ const modelData = fieldsForModelIndex(decryptedFields, modelIndex);
903
938
  for (const [key, value] of Object.entries(modelData)) {
904
939
  const parts = key.split(".");
905
940
  setNestedValue(result, parts, value);
@@ -915,20 +950,20 @@ async function bulkEncryptModelsWithLockContext(models, table, client, lockConte
915
950
  throw new Error("Lock context is not initialized");
916
951
  }
917
952
  const { otherFields, operationFields, keyMap, nullFields } = prepareBulkModelsForOperation(models, table);
953
+ const { toColumnName } = resolveEncryptColumnMap(table);
918
954
  const bulkEncryptPayload = operationFields.flatMap(
919
955
  (fields, modelIndex) => Object.entries(fields).map(([key, value]) => ({
920
956
  id: `${modelIndex}-${key}`,
921
957
  plaintext: value,
922
958
  table: table.tableName,
923
- column: key,
924
- lockContext: lockContext.context
959
+ column: toColumnName(key),
960
+ lockContext
925
961
  }))
926
962
  );
927
963
  const encryptedData = await handleMultiModelBulkOperation(
928
964
  bulkEncryptPayload,
929
965
  (items) => encryptBulk(client, {
930
966
  plaintexts: items,
931
- serviceToken: lockContext.ctsToken,
932
967
  unverifiedContext: auditData?.metadata
933
968
  }),
934
969
  keyMap
@@ -939,15 +974,7 @@ async function bulkEncryptModelsWithLockContext(models, table, client, lockConte
939
974
  const parts = key.split(".");
940
975
  setNestedValue(result, parts, value);
941
976
  }
942
- const modelData = Object.fromEntries(
943
- Object.entries(encryptedData).filter(([key]) => {
944
- const [idx] = key.split("-");
945
- return Number.parseInt(idx) === modelIndex;
946
- }).map(([key, value]) => {
947
- const [_2, fieldKey] = key.split("-");
948
- return [fieldKey, value];
949
- })
950
- );
977
+ const modelData = fieldsForModelIndex(encryptedData, modelIndex);
951
978
  for (const [key, value] of Object.entries(modelData)) {
952
979
  const parts = key.split(".");
953
980
  setNestedValue(result, parts, value);
@@ -975,7 +1002,7 @@ var BulkDecryptModelsOperation = class extends EncryptionOperation {
975
1002
  count: this.models.length,
976
1003
  lockContext: false
977
1004
  });
978
- const result = await withResult3(
1005
+ const result = await withResult(
979
1006
  async () => {
980
1007
  if (!this.client) {
981
1008
  throw noClientError();
@@ -1022,20 +1049,17 @@ var BulkDecryptModelsOperationWithLockContext = class extends EncryptionOperatio
1022
1049
  count: models.length,
1023
1050
  lockContext: true
1024
1051
  });
1025
- const result = await withResult3(
1052
+ const result = await withResult(
1026
1053
  async () => {
1027
1054
  if (!client) {
1028
1055
  throw noClientError();
1029
1056
  }
1030
- const context = await this.lockContext.getLockContext();
1031
- if (context.failure) {
1032
- throw new Error(`[encryption]: ${context.failure.message}`);
1033
- }
1057
+ const context = resolveLockContext(this.lockContext);
1034
1058
  const auditData = this.getAuditData();
1035
1059
  return await bulkDecryptModelsWithLockContext(
1036
1060
  models,
1037
1061
  client,
1038
- context.data,
1062
+ context,
1039
1063
  auditData
1040
1064
  );
1041
1065
  },
@@ -1054,10 +1078,9 @@ var BulkDecryptModelsOperationWithLockContext = class extends EncryptionOperatio
1054
1078
  };
1055
1079
 
1056
1080
  // src/encryption/operations/bulk-encrypt.ts
1057
- import { withResult as withResult4 } from "@byteslice/result";
1058
1081
  import { encryptBulk as encryptBulk2 } from "@cipherstash/protect-ffi";
1059
1082
  var createEncryptPayloads = (plaintexts, column, table, lockContext) => {
1060
- return plaintexts.map(({ id, plaintext }) => ({
1083
+ return plaintexts.filter(({ plaintext }) => plaintext !== null).map(({ id, plaintext }) => ({
1061
1084
  id,
1062
1085
  plaintext,
1063
1086
  column: column.getName(),
@@ -1065,6 +1088,20 @@ var createEncryptPayloads = (plaintexts, column, table, lockContext) => {
1065
1088
  ...lockContext && { lockContext }
1066
1089
  }));
1067
1090
  };
1091
+ var createNullResult2 = (plaintexts) => plaintexts.map(({ id }) => ({ id, data: null }));
1092
+ var mapEncryptedDataToResult = (plaintexts, encryptedData) => {
1093
+ const result = new Array(plaintexts.length);
1094
+ let encryptedIndex = 0;
1095
+ for (let i = 0; i < plaintexts.length; i++) {
1096
+ if (plaintexts[i].plaintext === null) {
1097
+ result[i] = { id: plaintexts[i].id, data: null };
1098
+ } else {
1099
+ result[i] = { id: plaintexts[i].id, data: encryptedData[encryptedIndex] };
1100
+ encryptedIndex++;
1101
+ }
1102
+ }
1103
+ return result;
1104
+ };
1068
1105
  var BulkEncryptOperation = class extends EncryptionOperation {
1069
1106
  client;
1070
1107
  plaintexts;
@@ -1089,7 +1126,7 @@ var BulkEncryptOperation = class extends EncryptionOperation {
1089
1126
  count: this.plaintexts?.length ?? 0,
1090
1127
  lockContext: false
1091
1128
  });
1092
- const result = await withResult4(
1129
+ const result = await withResult(
1093
1130
  async () => {
1094
1131
  if (!this.client) {
1095
1132
  throw noClientError();
@@ -1097,20 +1134,20 @@ var BulkEncryptOperation = class extends EncryptionOperation {
1097
1134
  if (!this.plaintexts || this.plaintexts.length === 0) {
1098
1135
  return [];
1099
1136
  }
1100
- const payloads = createEncryptPayloads(
1137
+ const nonNullPayloads = createEncryptPayloads(
1101
1138
  this.plaintexts,
1102
1139
  this.column,
1103
1140
  this.table
1104
1141
  );
1142
+ if (nonNullPayloads.length === 0) {
1143
+ return createNullResult2(this.plaintexts);
1144
+ }
1105
1145
  const { metadata } = this.getAuditData();
1106
1146
  const encryptedData = await encryptBulk2(this.client, {
1107
- plaintexts: payloads,
1147
+ plaintexts: nonNullPayloads,
1108
1148
  unverifiedContext: metadata
1109
1149
  });
1110
- return encryptedData.map((data, i) => ({
1111
- id: this.plaintexts[i].id,
1112
- data
1113
- }));
1150
+ return mapEncryptedDataToResult(this.plaintexts, encryptedData);
1114
1151
  },
1115
1152
  (error) => {
1116
1153
  log.set({ errorCode: getErrorCode(error) ?? "unknown" });
@@ -1155,7 +1192,7 @@ var BulkEncryptOperationWithLockContext = class extends EncryptionOperation {
1155
1192
  count: plaintexts?.length ?? 0,
1156
1193
  lockContext: true
1157
1194
  });
1158
- const result = await withResult4(
1195
+ const result = await withResult(
1159
1196
  async () => {
1160
1197
  if (!client) {
1161
1198
  throw noClientError();
@@ -1163,26 +1200,22 @@ var BulkEncryptOperationWithLockContext = class extends EncryptionOperation {
1163
1200
  if (!plaintexts || plaintexts.length === 0) {
1164
1201
  return [];
1165
1202
  }
1166
- const context = await this.lockContext.getLockContext();
1167
- if (context.failure) {
1168
- throw new Error(`[encryption]: ${context.failure.message}`);
1169
- }
1170
- const payloads = createEncryptPayloads(
1203
+ const lockContext = resolveLockContext(this.lockContext);
1204
+ const nonNullPayloads = createEncryptPayloads(
1171
1205
  plaintexts,
1172
1206
  column,
1173
1207
  table,
1174
- context.data.context
1208
+ lockContext
1175
1209
  );
1210
+ if (nonNullPayloads.length === 0) {
1211
+ return createNullResult2(plaintexts);
1212
+ }
1176
1213
  const { metadata } = this.getAuditData();
1177
1214
  const encryptedData = await encryptBulk2(client, {
1178
- plaintexts: payloads,
1179
- serviceToken: context.data.ctsToken,
1215
+ plaintexts: nonNullPayloads,
1180
1216
  unverifiedContext: metadata
1181
1217
  });
1182
- return encryptedData.map((data, i) => ({
1183
- id: plaintexts[i].id,
1184
- data
1185
- }));
1218
+ return mapEncryptedDataToResult(plaintexts, encryptedData);
1186
1219
  },
1187
1220
  (error) => {
1188
1221
  log.set({ errorCode: getErrorCode(error) ?? "unknown" });
@@ -1199,7 +1232,6 @@ var BulkEncryptOperationWithLockContext = class extends EncryptionOperation {
1199
1232
  };
1200
1233
 
1201
1234
  // src/encryption/operations/bulk-encrypt-models.ts
1202
- import { withResult as withResult5 } from "@byteslice/result";
1203
1235
  var BulkEncryptModelsOperation = class extends EncryptionOperation {
1204
1236
  client;
1205
1237
  models;
@@ -1221,7 +1253,7 @@ var BulkEncryptModelsOperation = class extends EncryptionOperation {
1221
1253
  count: this.models.length,
1222
1254
  lockContext: false
1223
1255
  });
1224
- const result = await withResult5(
1256
+ const result = await withResult(
1225
1257
  async () => {
1226
1258
  if (!this.client) {
1227
1259
  throw noClientError();
@@ -1275,21 +1307,18 @@ var BulkEncryptModelsOperationWithLockContext = class extends EncryptionOperatio
1275
1307
  count: models.length,
1276
1308
  lockContext: true
1277
1309
  });
1278
- const result = await withResult5(
1310
+ const result = await withResult(
1279
1311
  async () => {
1280
1312
  if (!client) {
1281
1313
  throw noClientError();
1282
1314
  }
1283
- const context = await this.lockContext.getLockContext();
1284
- if (context.failure) {
1285
- throw new Error(`[encryption]: ${context.failure.message}`);
1286
- }
1315
+ const context = resolveLockContext(this.lockContext);
1287
1316
  const auditData = this.getAuditData();
1288
1317
  return await bulkEncryptModelsWithLockContext(
1289
1318
  models,
1290
1319
  table,
1291
1320
  client,
1292
- context.data,
1321
+ context,
1293
1322
  auditData
1294
1323
  );
1295
1324
  },
@@ -1308,12 +1337,15 @@ var BulkEncryptModelsOperationWithLockContext = class extends EncryptionOperatio
1308
1337
  };
1309
1338
 
1310
1339
  // src/encryption/operations/decrypt.ts
1311
- import { withResult as withResult6 } from "@byteslice/result";
1312
1340
  import {
1313
1341
  decrypt as ffiDecrypt
1314
1342
  } from "@cipherstash/protect-ffi";
1315
1343
  var DecryptOperation = class extends EncryptionOperation {
1316
1344
  client;
1345
+ // Internally widened to allow null so the runtime guard below can
1346
+ // short-circuit on legacy / manually-NULLed rows. The public
1347
+ // `Encryption.decrypt()` signature still rejects null at the type
1348
+ // layer; this is defense in depth for direct construction.
1317
1349
  encryptedData;
1318
1350
  constructor(client, encryptedData) {
1319
1351
  super();
@@ -1329,11 +1361,14 @@ var DecryptOperation = class extends EncryptionOperation {
1329
1361
  op: "decrypt",
1330
1362
  lockContext: false
1331
1363
  });
1332
- const result = await withResult6(
1364
+ const result = await withResult(
1333
1365
  async () => {
1334
1366
  if (!this.client) {
1335
1367
  throw noClientError();
1336
1368
  }
1369
+ if (this.encryptedData === null) {
1370
+ return null;
1371
+ }
1337
1372
  const { metadata } = this.getAuditData();
1338
1373
  return await ffiDecrypt(this.client, {
1339
1374
  ciphertext: this.encryptedData,
@@ -1378,22 +1413,21 @@ var DecryptOperationWithLockContext = class extends EncryptionOperation {
1378
1413
  op: "decrypt",
1379
1414
  lockContext: true
1380
1415
  });
1381
- const result = await withResult6(
1416
+ const result = await withResult(
1382
1417
  async () => {
1383
1418
  const { client, encryptedData } = this.operation.getOperation();
1384
1419
  if (!client) {
1385
1420
  throw noClientError();
1386
1421
  }
1387
- const { metadata } = this.getAuditData();
1388
- const context = await this.lockContext.getLockContext();
1389
- if (context.failure) {
1390
- throw new Error(`[encryption]: ${context.failure.message}`);
1422
+ if (encryptedData === null) {
1423
+ return null;
1391
1424
  }
1425
+ const { metadata } = this.getAuditData();
1426
+ const lockContext = resolveLockContext(this.lockContext);
1392
1427
  return await ffiDecrypt(client, {
1393
1428
  ciphertext: encryptedData,
1394
1429
  unverifiedContext: metadata,
1395
- lockContext: context.data.context,
1396
- serviceToken: context.data.ctsToken
1430
+ lockContext
1397
1431
  });
1398
1432
  },
1399
1433
  (error) => {
@@ -1411,7 +1445,6 @@ var DecryptOperationWithLockContext = class extends EncryptionOperation {
1411
1445
  };
1412
1446
 
1413
1447
  // src/encryption/operations/decrypt-model.ts
1414
- import { withResult as withResult7 } from "@byteslice/result";
1415
1448
  var DecryptModelOperation = class extends EncryptionOperation {
1416
1449
  client;
1417
1450
  model;
@@ -1429,7 +1462,7 @@ var DecryptModelOperation = class extends EncryptionOperation {
1429
1462
  op: "decryptModel",
1430
1463
  lockContext: false
1431
1464
  });
1432
- const result = await withResult7(
1465
+ const result = await withResult(
1433
1466
  async () => {
1434
1467
  if (!this.client) {
1435
1468
  throw noClientError();
@@ -1474,21 +1507,18 @@ var DecryptModelOperationWithLockContext = class extends EncryptionOperation {
1474
1507
  op: "decryptModel",
1475
1508
  lockContext: true
1476
1509
  });
1477
- const result = await withResult7(
1510
+ const result = await withResult(
1478
1511
  async () => {
1479
1512
  const { client, model } = this.operation.getOperation();
1480
1513
  if (!client) {
1481
1514
  throw noClientError();
1482
1515
  }
1483
- const context = await this.lockContext.getLockContext();
1484
- if (context.failure) {
1485
- throw new Error(`[encryption]: ${context.failure.message}`);
1486
- }
1516
+ const context = resolveLockContext(this.lockContext);
1487
1517
  const auditData = this.getAuditData();
1488
1518
  return await decryptModelFieldsWithLockContext(
1489
1519
  model,
1490
1520
  client,
1491
- context.data,
1521
+ context,
1492
1522
  auditData
1493
1523
  );
1494
1524
  },
@@ -1507,12 +1537,15 @@ var DecryptModelOperationWithLockContext = class extends EncryptionOperation {
1507
1537
  };
1508
1538
 
1509
1539
  // src/encryption/operations/encrypt.ts
1510
- import { withResult as withResult8 } from "@byteslice/result";
1511
1540
  import {
1512
1541
  encrypt as ffiEncrypt
1513
1542
  } from "@cipherstash/protect-ffi";
1514
1543
  var EncryptOperation = class extends EncryptionOperation {
1515
1544
  client;
1545
+ // Internally widened to allow null so the runtime guard below can
1546
+ // short-circuit. The public `Encryption.encrypt()` signature still
1547
+ // rejects null at the type layer; this is defense in depth for callers
1548
+ // that reach this class through casts or dynamic field walking.
1516
1549
  plaintext;
1517
1550
  column;
1518
1551
  table;
@@ -1534,19 +1567,20 @@ var EncryptOperation = class extends EncryptionOperation {
1534
1567
  column: this.column.getName(),
1535
1568
  lockContext: false
1536
1569
  });
1537
- const result = await withResult8(
1570
+ const result = await withResult(
1538
1571
  async () => {
1539
1572
  if (!this.client) {
1540
1573
  throw noClientError();
1541
1574
  }
1542
- if (typeof this.plaintext === "number" && Number.isNaN(this.plaintext)) {
1543
- throw new Error("[encryption]: Cannot encrypt NaN value");
1544
- }
1545
- if (typeof this.plaintext === "number" && !Number.isFinite(this.plaintext)) {
1546
- throw new Error("[encryption]: Cannot encrypt Infinity value");
1575
+ if (this.plaintext === null) {
1576
+ return null;
1547
1577
  }
1578
+ assertValidNumericValue(this.plaintext);
1548
1579
  const { metadata } = this.getAuditData();
1549
1580
  return await ffiEncrypt(this.client, {
1581
+ // `Plaintext` widens the FFI `JsPlaintext` with `Date` (serialized via
1582
+ // `toJSON` at the boundary); cast until the upstream `JsPlaintext` input
1583
+ // union is corrected to include it.
1550
1584
  plaintext: this.plaintext,
1551
1585
  column: this.column.getName(),
1552
1586
  table: this.table.tableName,
@@ -1595,22 +1629,22 @@ var EncryptOperationWithLockContext = class extends EncryptionOperation {
1595
1629
  column: column.getName(),
1596
1630
  lockContext: true
1597
1631
  });
1598
- const result = await withResult8(
1632
+ const result = await withResult(
1599
1633
  async () => {
1600
1634
  if (!client) {
1601
1635
  throw noClientError();
1602
1636
  }
1603
- const { metadata } = this.getAuditData();
1604
- const context = await this.lockContext.getLockContext();
1605
- if (context.failure) {
1606
- throw new Error(`[encryption]: ${context.failure.message}`);
1637
+ if (plaintext === null) {
1638
+ return null;
1607
1639
  }
1640
+ assertValidNumericValue(plaintext);
1641
+ const { metadata } = this.getAuditData();
1642
+ const lockContext = resolveLockContext(this.lockContext);
1608
1643
  return await ffiEncrypt(client, {
1609
1644
  plaintext,
1610
1645
  column: column.getName(),
1611
1646
  table: table.tableName,
1612
- lockContext: context.data.context,
1613
- serviceToken: context.data.ctsToken,
1647
+ lockContext,
1614
1648
  unverifiedContext: metadata
1615
1649
  });
1616
1650
  },
@@ -1629,7 +1663,6 @@ var EncryptOperationWithLockContext = class extends EncryptionOperation {
1629
1663
  };
1630
1664
 
1631
1665
  // src/encryption/operations/encrypt-model.ts
1632
- import { withResult as withResult9 } from "@byteslice/result";
1633
1666
  var EncryptModelOperation = class extends EncryptionOperation {
1634
1667
  client;
1635
1668
  model;
@@ -1650,7 +1683,7 @@ var EncryptModelOperation = class extends EncryptionOperation {
1650
1683
  table: this.table.tableName,
1651
1684
  lockContext: false
1652
1685
  });
1653
- const result = await withResult9(
1686
+ const result = await withResult(
1654
1687
  async () => {
1655
1688
  if (!this.client) {
1656
1689
  throw noClientError();
@@ -1703,21 +1736,18 @@ var EncryptModelOperationWithLockContext = class extends EncryptionOperation {
1703
1736
  table: table.tableName,
1704
1737
  lockContext: true
1705
1738
  });
1706
- const result = await withResult9(
1739
+ const result = await withResult(
1707
1740
  async () => {
1708
1741
  if (!client) {
1709
1742
  throw noClientError();
1710
1743
  }
1711
- const context = await this.lockContext.getLockContext();
1712
- if (context.failure) {
1713
- throw new Error(`[encryption]: ${context.failure.message}`);
1714
- }
1744
+ const context = resolveLockContext(this.lockContext);
1715
1745
  const auditData = this.getAuditData();
1716
1746
  return await encryptModelFieldsWithLockContext(
1717
1747
  model,
1718
1748
  table,
1719
1749
  client,
1720
- context.data,
1750
+ context,
1721
1751
  auditData
1722
1752
  );
1723
1753
  },
@@ -1736,7 +1766,6 @@ var EncryptModelOperationWithLockContext = class extends EncryptionOperation {
1736
1766
  };
1737
1767
 
1738
1768
  // src/encryption/operations/encrypt-query.ts
1739
- import { withResult as withResult10 } from "@byteslice/result";
1740
1769
  import {
1741
1770
  encryptQuery as ffiEncryptQuery
1742
1771
  } from "@cipherstash/protect-ffi";
@@ -1747,6 +1776,9 @@ var EncryptQueryOperation = class extends EncryptionOperation {
1747
1776
  this.plaintext = plaintext;
1748
1777
  this.opts = opts;
1749
1778
  }
1779
+ client;
1780
+ plaintext;
1781
+ opts;
1750
1782
  withLockContext(lockContext) {
1751
1783
  return new EncryptQueryOperationWithLockContext(
1752
1784
  this.client,
@@ -1765,27 +1797,35 @@ var EncryptQueryOperation = class extends EncryptionOperation {
1765
1797
  queryType: this.opts.queryType,
1766
1798
  lockContext: false
1767
1799
  });
1768
- const validationError = validateNumericValue(this.plaintext);
1800
+ if (this.plaintext === null || this.plaintext === void 0) {
1801
+ log.emit();
1802
+ return { data: null };
1803
+ }
1804
+ const plaintext = this.plaintext;
1805
+ const validationError = validateNumericValue(plaintext);
1769
1806
  if (validationError?.failure) {
1770
1807
  log.emit();
1771
1808
  return { failure: validationError.failure };
1772
1809
  }
1773
- const result = await withResult10(
1810
+ const result = await withResult(
1774
1811
  async () => {
1775
1812
  if (!this.client) throw noClientError();
1776
1813
  const { metadata } = this.getAuditData();
1777
1814
  const { indexType, queryOp } = resolveIndexType(
1778
1815
  this.opts.column,
1779
1816
  this.opts.queryType,
1780
- this.plaintext
1817
+ plaintext
1781
1818
  );
1782
1819
  assertValueIndexCompatibility(
1783
- this.plaintext,
1820
+ plaintext,
1784
1821
  indexType,
1785
1822
  this.opts.column.getName()
1786
1823
  );
1787
1824
  const encrypted = await ffiEncryptQuery(this.client, {
1788
- plaintext: this.plaintext,
1825
+ // `Plaintext` widens the FFI `JsPlaintext` with `Date` (serialized via
1826
+ // `toJSON` at the boundary); cast until the upstream input union is
1827
+ // corrected to include it.
1828
+ plaintext,
1789
1829
  column: this.opts.column.getName(),
1790
1830
  table: this.opts.table.tableName,
1791
1831
  indexType,
@@ -1819,6 +1859,10 @@ var EncryptQueryOperationWithLockContext = class extends EncryptionOperation {
1819
1859
  this.lockContext = lockContext;
1820
1860
  this.auditMetadata = auditMetadata;
1821
1861
  }
1862
+ client;
1863
+ plaintext;
1864
+ opts;
1865
+ lockContext;
1822
1866
  async execute() {
1823
1867
  const log = createRequestLogger();
1824
1868
  log.set({
@@ -1828,39 +1872,41 @@ var EncryptQueryOperationWithLockContext = class extends EncryptionOperation {
1828
1872
  queryType: this.opts.queryType,
1829
1873
  lockContext: true
1830
1874
  });
1831
- const validationError = validateNumericValue(this.plaintext);
1832
- if (validationError?.failure) {
1875
+ if (this.plaintext === null || this.plaintext === void 0) {
1833
1876
  log.emit();
1834
- return { failure: validationError.failure };
1877
+ return { data: null };
1835
1878
  }
1836
- const lockContextResult = await this.lockContext.getLockContext();
1837
- if (lockContextResult.failure) {
1879
+ const plaintext = this.plaintext;
1880
+ const validationError = validateNumericValue(plaintext);
1881
+ if (validationError?.failure) {
1838
1882
  log.emit();
1839
- return { failure: lockContextResult.failure };
1883
+ return { failure: validationError.failure };
1840
1884
  }
1841
- const { ctsToken, context } = lockContextResult.data;
1842
- const result = await withResult10(
1885
+ const result = await withResult(
1843
1886
  async () => {
1844
1887
  if (!this.client) throw noClientError();
1888
+ const context = resolveLockContext(this.lockContext);
1845
1889
  const { metadata } = this.getAuditData();
1846
1890
  const { indexType, queryOp } = resolveIndexType(
1847
1891
  this.opts.column,
1848
1892
  this.opts.queryType,
1849
- this.plaintext
1893
+ plaintext
1850
1894
  );
1851
1895
  assertValueIndexCompatibility(
1852
- this.plaintext,
1896
+ plaintext,
1853
1897
  indexType,
1854
1898
  this.opts.column.getName()
1855
1899
  );
1856
1900
  const encrypted = await ffiEncryptQuery(this.client, {
1857
- plaintext: this.plaintext,
1901
+ // `Plaintext` widens the FFI `JsPlaintext` with `Date` (serialized via
1902
+ // `toJSON` at the boundary); cast until the upstream input union is
1903
+ // corrected to include it.
1904
+ plaintext,
1858
1905
  column: this.opts.column.getName(),
1859
1906
  table: this.opts.table.tableName,
1860
1907
  indexType,
1861
1908
  queryOp,
1862
1909
  lockContext: context,
1863
- serviceToken: ctsToken,
1864
1910
  unverifiedContext: metadata
1865
1911
  });
1866
1912
  return formatEncryptedResult(encrypted, this.opts.returnType);
@@ -1883,6 +1929,20 @@ var EncryptQueryOperationWithLockContext = class extends EncryptionOperation {
1883
1929
  var noClientError = () => new Error(
1884
1930
  "The Encryption client has not been initialized. Please call init() before using the client."
1885
1931
  );
1932
+ function resolveEqlVersion(schemas, explicit) {
1933
+ const v3Count = schemas.filter(
1934
+ (schema) => typeof schema.buildColumnKeyMap === "function"
1935
+ ).length;
1936
+ if (v3Count > 0 && v3Count < schemas.length) {
1937
+ throw new Error(
1938
+ "[encryption]: cannot mix EQL v2 and EQL v3 tables in one client \u2014 one client emits exactly one wire format. Create separate clients for the v2 and v3 schemas."
1939
+ );
1940
+ }
1941
+ if (explicit !== void 0) {
1942
+ return explicit;
1943
+ }
1944
+ return v3Count === 0 ? void 0 : 3;
1945
+ }
1886
1946
  var EncryptionClient = class {
1887
1947
  client;
1888
1948
  encryptConfig;
@@ -1893,7 +1953,7 @@ var EncryptionClient = class {
1893
1953
  * @returns A promise that resolves to a {@link Result} containing the initialized EncryptionClient or an {@link EncryptionError}.
1894
1954
  **/
1895
1955
  async init(config) {
1896
- return await withResult11(
1956
+ return await withResult(
1897
1957
  async () => {
1898
1958
  const validated = encryptConfigSchema.parse(
1899
1959
  config.encryptConfig
@@ -1912,7 +1972,9 @@ var EncryptionClient = class {
1912
1972
  clientId: config.clientId,
1913
1973
  clientKey: config.clientKey,
1914
1974
  keyset: toFfiKeysetIdentifier(config.keyset)
1915
- }
1975
+ },
1976
+ strategy: config.authStrategy,
1977
+ eqlVersion: config.eqlVersion
1916
1978
  });
1917
1979
  this.encryptConfig = validated;
1918
1980
  logger.debug("Successfully initialized the Encryption client.");
@@ -2003,7 +2065,7 @@ var EncryptionClient = class {
2003
2065
  return new EncryptOperation(this.client, plaintext, opts);
2004
2066
  }
2005
2067
  encryptQuery(plaintextOrTerms, opts) {
2006
- if (isScalarQueryTermArray(plaintextOrTerms)) {
2068
+ if (!opts && isScalarQueryTermArray(plaintextOrTerms)) {
2007
2069
  return new BatchEncryptQueryOperation(this.client, plaintextOrTerms);
2008
2070
  }
2009
2071
  if (Array.isArray(plaintextOrTerms) && plaintextOrTerms.length === 0 && !opts) {
@@ -2050,6 +2112,13 @@ var EncryptionClient = class {
2050
2112
  * .withLockContext(lockContext)
2051
2113
  * ```
2052
2114
  *
2115
+ * @remarks
2116
+ * The public input type rejects null, but at runtime `decrypt` will
2117
+ * short-circuit and return null when given a null ciphertext
2118
+ * (defense in depth for legacy / manually-NULLed DB rows reached via
2119
+ * casts or dynamic field walking). The narrow return type holds for
2120
+ * any caller that respects the input contract.
2121
+ *
2053
2122
  * @see {@link LockContext}
2054
2123
  * @see {@link DecryptOperation}
2055
2124
  */
@@ -2304,6 +2373,17 @@ var EncryptionClient = class {
2304
2373
  return this.encryptConfig;
2305
2374
  }
2306
2375
  };
2376
+ var warnedStrategyDeprecated = false;
2377
+ function warnStrategyDeprecated() {
2378
+ if (warnedStrategyDeprecated) return;
2379
+ warnedStrategyDeprecated = true;
2380
+ console.warn(
2381
+ "[encryption]: `config.strategy` is deprecated and will be removed in a future release \u2014 use `config.authStrategy` instead."
2382
+ );
2383
+ }
2384
+ function __resetStrategyDeprecationWarningForTests() {
2385
+ warnedStrategyDeprecated = false;
2386
+ }
2307
2387
  var Encryption = async (config) => {
2308
2388
  const { schemas, config: clientConfig } = config;
2309
2389
  if (!schemas.length) {
@@ -2316,11 +2396,18 @@ var Encryption = async (config) => {
2316
2396
  "[encryption]: Invalid UUID provided for keyset id. Must be a valid UUID."
2317
2397
  );
2318
2398
  }
2399
+ if (clientConfig?.strategy) {
2400
+ warnStrategyDeprecated();
2401
+ }
2402
+ const authStrategy = clientConfig?.authStrategy ?? clientConfig?.strategy;
2319
2403
  const client = new EncryptionClient();
2320
2404
  const encryptConfig = buildEncryptConfig(...schemas);
2405
+ const eqlVersion = resolveEqlVersion(schemas, clientConfig?.eqlVersion);
2321
2406
  const result = await client.init({
2322
2407
  encryptConfig,
2323
- ...clientConfig
2408
+ ...clientConfig,
2409
+ authStrategy,
2410
+ eqlVersion
2324
2411
  });
2325
2412
  if (result.failure) {
2326
2413
  throw new Error(`[encryption]: ${result.failure.message}`);
@@ -2329,8 +2416,20 @@ var Encryption = async (config) => {
2329
2416
  };
2330
2417
 
2331
2418
  export {
2419
+ BatchEncryptQueryOperation,
2420
+ BulkDecryptOperation,
2421
+ BulkDecryptModelsOperation,
2422
+ BulkEncryptOperation,
2423
+ BulkEncryptModelsOperation,
2424
+ DecryptOperation,
2425
+ DecryptModelOperation,
2426
+ EncryptOperation,
2427
+ EncryptModelOperation,
2428
+ EncryptQueryOperation,
2332
2429
  noClientError,
2430
+ resolveEqlVersion,
2333
2431
  EncryptionClient,
2432
+ __resetStrategyDeprecationWarningForTests,
2334
2433
  Encryption
2335
2434
  };
2336
- //# sourceMappingURL=chunk-CY5B4LIF.js.map
2435
+ //# sourceMappingURL=chunk-36AA7IBJ.js.map