@cipherstash/stack 0.3.0 → 0.5.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 (93) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +10 -37
  3. package/dist/bin/stash.js +261 -320
  4. package/dist/bin/stash.js.map +1 -1
  5. package/dist/{chunk-SUYMGQBY.js → chunk-4RNBI3UH.js} +1 -7
  6. package/dist/chunk-4RNBI3UH.js.map +1 -0
  7. package/dist/{chunk-OAPLZLR5.js → chunk-GXGEW6T4.js} +2 -76
  8. package/dist/chunk-GXGEW6T4.js.map +1 -0
  9. package/dist/{chunk-JLI27P46.js → chunk-HCL75LXQ.js} +1 -1
  10. package/dist/chunk-HCL75LXQ.js.map +1 -0
  11. package/dist/chunk-LBMC4D6D.js +19 -0
  12. package/dist/chunk-LBMC4D6D.js.map +1 -0
  13. package/dist/chunk-Q5FTQLYG.js +68 -0
  14. package/dist/chunk-Q5FTQLYG.js.map +1 -0
  15. package/dist/{chunk-MW6D52V2.js → chunk-TGUELLJS.js} +58 -150
  16. package/dist/chunk-TGUELLJS.js.map +1 -0
  17. package/dist/{chunk-TBAIVO6T.js → chunk-Y35W4WFN.js} +6 -6
  18. package/dist/chunk-Y35W4WFN.js.map +1 -0
  19. package/dist/chunk-YLISU7FD.js +357 -0
  20. package/dist/chunk-YLISU7FD.js.map +1 -0
  21. package/dist/{client-Bf0Xw2xo.d.cts → client-BFrHTgC6.d.ts} +50 -18
  22. package/dist/{client-Kfp8OsPB.d.ts → client-BVQvgvdY.d.cts} +50 -18
  23. package/dist/client.cjs +5 -5
  24. package/dist/client.cjs.map +1 -1
  25. package/dist/client.d.cts +4 -4
  26. package/dist/client.d.ts +4 -4
  27. package/dist/client.js +1 -1
  28. package/dist/drizzle/index.cjs +5 -5
  29. package/dist/drizzle/index.cjs.map +1 -1
  30. package/dist/drizzle/index.d.cts +14 -6
  31. package/dist/drizzle/index.d.ts +14 -6
  32. package/dist/drizzle/index.js +4 -4
  33. package/dist/drizzle/index.js.map +1 -1
  34. package/dist/dynamodb/index.cjs +68 -0
  35. package/dist/dynamodb/index.cjs.map +1 -1
  36. package/dist/dynamodb/index.d.cts +4 -4
  37. package/dist/dynamodb/index.d.ts +4 -4
  38. package/dist/dynamodb/index.js +9 -0
  39. package/dist/dynamodb/index.js.map +1 -1
  40. package/dist/encryption/index.cjs +2572 -0
  41. package/dist/encryption/index.cjs.map +1 -0
  42. package/dist/encryption/index.d.cts +7 -0
  43. package/dist/encryption/index.d.ts +7 -0
  44. package/dist/encryption/index.js +17 -0
  45. package/dist/encryption/index.js.map +1 -0
  46. package/dist/errors/index.cjs +44 -0
  47. package/dist/errors/index.cjs.map +1 -0
  48. package/dist/errors/index.d.cts +94 -0
  49. package/dist/errors/index.d.ts +94 -0
  50. package/dist/errors/index.js +9 -0
  51. package/dist/errors/index.js.map +1 -0
  52. package/dist/identity/index.cjs +21 -13
  53. package/dist/identity/index.cjs.map +1 -1
  54. package/dist/identity/index.d.cts +78 -2
  55. package/dist/identity/index.d.ts +78 -2
  56. package/dist/identity/index.js +7 -3
  57. package/dist/identity/index.js.map +1 -1
  58. package/dist/index.cjs +425 -182
  59. package/dist/index.cjs.map +1 -1
  60. package/dist/index.d.cts +21 -19
  61. package/dist/index.d.ts +21 -19
  62. package/dist/index.js +15 -9
  63. package/dist/schema/index.cjs +5 -5
  64. package/dist/schema/index.cjs.map +1 -1
  65. package/dist/schema/index.d.cts +1 -2
  66. package/dist/schema/index.d.ts +1 -2
  67. package/dist/schema/index.js +1 -1
  68. package/dist/secrets/index.cjs +116 -180
  69. package/dist/secrets/index.cjs.map +1 -1
  70. package/dist/secrets/index.d.cts +4 -6
  71. package/dist/secrets/index.d.ts +4 -6
  72. package/dist/secrets/index.js +9 -318
  73. package/dist/secrets/index.js.map +1 -1
  74. package/dist/supabase/index.cjs +86 -3
  75. package/dist/supabase/index.cjs.map +1 -1
  76. package/dist/supabase/index.d.cts +4 -4
  77. package/dist/supabase/index.d.ts +4 -4
  78. package/dist/supabase/index.js +26 -3
  79. package/dist/supabase/index.js.map +1 -1
  80. package/dist/{types-public-0CzBV45X.d.cts → types-public-dKxDzTIL.d.cts} +47 -34
  81. package/dist/{types-public-0CzBV45X.d.ts → types-public-dKxDzTIL.d.ts} +47 -34
  82. package/dist/types-public.cjs.map +1 -1
  83. package/dist/types-public.d.cts +1 -2
  84. package/dist/types-public.d.ts +1 -2
  85. package/dist/types-public.js +1 -1
  86. package/package.json +30 -2
  87. package/dist/chunk-JLI27P46.js.map +0 -1
  88. package/dist/chunk-MW6D52V2.js.map +0 -1
  89. package/dist/chunk-OAPLZLR5.js.map +0 -1
  90. package/dist/chunk-SUYMGQBY.js.map +0 -1
  91. package/dist/chunk-TBAIVO6T.js.map +0 -1
  92. package/dist/index-9-Ya3fDK.d.cts +0 -169
  93. package/dist/index-9-Ya3fDK.d.ts +0 -169
package/dist/index.cjs CHANGED
@@ -31,31 +31,18 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  Encryption: () => Encryption,
34
- EncryptionErrorTypes: () => EncryptionErrorTypes,
34
+ Secrets: () => Secrets,
35
35
  encryptedColumn: () => encryptedColumn,
36
36
  encryptedField: () => encryptedField,
37
37
  encryptedTable: () => encryptedTable,
38
- getErrorMessage: () => getErrorMessage
38
+ encryptedToPgComposite: () => encryptedToPgComposite,
39
+ isEncryptedPayload: () => isEncryptedPayload
39
40
  });
40
41
  module.exports = __toCommonJS(index_exports);
41
42
 
42
- // src/errors/index.ts
43
- var EncryptionErrorTypes = {
44
- ClientInitError: "ClientInitError",
45
- EncryptionError: "EncryptionError",
46
- DecryptionError: "DecryptionError",
47
- LockContextError: "LockContextError",
48
- CtsTokenError: "CtsTokenError"
49
- };
50
- function getErrorMessage(error) {
51
- if (error instanceof Error) return error.message;
52
- if (typeof error === "string") return error;
53
- return String(error);
54
- }
55
-
56
43
  // src/schema/index.ts
57
44
  var import_zod = require("zod");
58
- var castAsEnum = import_zod.z.enum(["bigint", "boolean", "date", "number", "string", "json"]).default("string");
45
+ var castAsEnum = import_zod.z.enum(["bigint", "boolean", "date", "number", "string", "json", "text"]).default("text");
59
46
  var tokenFilterSchema = import_zod.z.object({
60
47
  kind: import_zod.z.literal("downcase")
61
48
  });
@@ -103,7 +90,7 @@ var EncryptedField = class {
103
90
  castAsValue;
104
91
  constructor(valueName) {
105
92
  this.valueName = valueName;
106
- this.castAsValue = "string";
93
+ this.castAsValue = "text";
107
94
  }
108
95
  /**
109
96
  * Set or override the plaintext data type for this field.
@@ -112,7 +99,7 @@ var EncryptedField = class {
112
99
  * a different type so the encryption layer knows how to encode the plaintext
113
100
  * before encrypting.
114
101
  *
115
- * @param castAs - The plaintext data type: `'string'`, `'number'`, `'boolean'`, `'date'`, `'bigint'`, or `'json'`.
102
+ * @param castAs - The plaintext data type: `'string'`, `'number'`, `'boolean'`, `'date'`, `'text'`, `'bigint'`, or `'json'`.
116
103
  * @returns This `EncryptedField` instance for method chaining.
117
104
  *
118
105
  * @example
@@ -128,7 +115,7 @@ var EncryptedField = class {
128
115
  }
129
116
  build() {
130
117
  return {
131
- cast_as: this.castAsValue,
118
+ cast_as: this.castAsValue === "string" ? "text" : this.castAsValue,
132
119
  indexes: {}
133
120
  };
134
121
  }
@@ -282,7 +269,7 @@ var EncryptedColumn = class {
282
269
  }
283
270
  build() {
284
271
  return {
285
- cast_as: this.castAsValue,
272
+ cast_as: this.castAsValue === "string" ? "text" : this.castAsValue,
286
273
  indexes: this.indexesValue
287
274
  };
288
275
  }
@@ -380,6 +367,15 @@ function buildEncryptConfig(...protectTables) {
380
367
  return config;
381
368
  }
382
369
 
370
+ // src/errors/index.ts
371
+ var EncryptionErrorTypes = {
372
+ ClientInitError: "ClientInitError",
373
+ EncryptionError: "EncryptionError",
374
+ DecryptionError: "DecryptionError",
375
+ LockContextError: "LockContextError",
376
+ CtsTokenError: "CtsTokenError"
377
+ };
378
+
383
379
  // src/utils/config/index.ts
384
380
  var import_node_fs = __toESM(require("fs"), 1);
385
381
  var import_node_path = __toESM(require("path"), 1);
@@ -445,25 +441,33 @@ function loadWorkSpaceId(suppliedCrn) {
445
441
 
446
442
  // src/utils/logger/index.ts
447
443
  var import_evlog = require("evlog");
448
- function samplingFromEnv() {
449
- const env = process.env.STASH_LOG_LEVEL;
450
- if (!env) return void 0;
451
- const levels = ["debug", "info", "warn", "error"];
452
- const idx = levels.indexOf(env);
453
- if (idx === -1) return void 0;
454
- return Object.fromEntries(levels.map((l, i) => [l, i >= idx ? 100 : 0]));
444
+ var validLevels = ["debug", "info", "error"];
445
+ function levelFromEnv() {
446
+ const env = process.env.STASH_STACK_LOG;
447
+ if (env && validLevels.includes(env)) return env;
448
+ return "error";
449
+ }
450
+ function samplingRatesForLevel(level) {
451
+ switch (level) {
452
+ case "debug":
453
+ return { debug: 100, info: 100, warn: 100, error: 100 };
454
+ case "info":
455
+ return { debug: 0, info: 100, warn: 100, error: 100 };
456
+ case "error":
457
+ default:
458
+ return { debug: 0, info: 0, warn: 0, error: 100 };
459
+ }
455
460
  }
456
461
  var initialized = false;
457
- function initStackLogger(config) {
462
+ function initStackLogger() {
458
463
  if (initialized) return;
459
464
  initialized = true;
460
- const rates = samplingFromEnv();
465
+ const level = levelFromEnv();
466
+ const rates = samplingRatesForLevel(level);
461
467
  (0, import_evlog.initLogger)({
462
468
  env: { service: "@cipherstash/stack" },
463
- enabled: config?.enabled ?? !!rates,
464
- pretty: config?.pretty,
465
- ...rates && { sampling: { rates } },
466
- ...config?.drain && { drain: config.drain }
469
+ enabled: true,
470
+ sampling: { rates }
467
471
  });
468
472
  }
469
473
  initStackLogger();
@@ -501,18 +505,18 @@ var logger = {
501
505
  // src/encryption/index.ts
502
506
  var import_result11 = require("@byteslice/result");
503
507
  var import_protect_ffi9 = require("@cipherstash/protect-ffi");
508
+ var import_uuid = require("uuid");
504
509
 
505
510
  // src/encryption/helpers/index.ts
511
+ function encryptedToPgComposite(obj) {
512
+ return {
513
+ data: obj
514
+ };
515
+ }
506
516
  function encryptedToCompositeLiteral(obj) {
507
- if (obj === null) {
508
- throw new Error("encryptedToCompositeLiteral: obj cannot be null");
509
- }
510
517
  return `(${JSON.stringify(JSON.stringify(obj))})`;
511
518
  }
512
519
  function encryptedToEscapedCompositeLiteral(obj) {
513
- if (obj === null) {
514
- throw new Error("encryptedToEscapedCompositeLiteral: obj cannot be null");
515
- }
516
520
  return JSON.stringify(encryptedToCompositeLiteral(obj));
517
521
  }
518
522
  function formatEncryptedResult(encrypted, returnType) {
@@ -695,18 +699,6 @@ var EncryptionOperation = class {
695
699
  };
696
700
 
697
701
  // src/encryption/operations/batch-encrypt-query.ts
698
- function filterNullTerms(terms) {
699
- const nullIndices = /* @__PURE__ */ new Set();
700
- const nonNullTerms = [];
701
- terms.forEach((term, index) => {
702
- if (term.value === null || term.value === void 0) {
703
- nullIndices.add(index);
704
- } else {
705
- nonNullTerms.push({ term, originalIndex: index });
706
- }
707
- });
708
- return { nullIndices, nonNullTerms };
709
- }
710
702
  function buildQueryPayload(term, lockContext) {
711
703
  assertValidNumericValue(term.value);
712
704
  const { indexType, queryOp } = resolveIndexType(
@@ -727,13 +719,10 @@ function buildQueryPayload(term, lockContext) {
727
719
  }
728
720
  return payload;
729
721
  }
730
- function assembleResults(totalLength, encryptedValues, nonNullTerms) {
731
- const results = new Array(totalLength).fill(null);
732
- nonNullTerms.forEach(({ term, originalIndex }, i) => {
733
- const encrypted = encryptedValues[i];
734
- results[originalIndex] = formatEncryptedResult(encrypted, term.returnType);
735
- });
736
- return results;
722
+ function assembleResults(terms, encryptedValues) {
723
+ return terms.map(
724
+ (term, i) => formatEncryptedResult(encryptedValues[i], term.returnType)
725
+ );
737
726
  }
738
727
  var BatchEncryptQueryOperation = class extends EncryptionOperation {
739
728
  constructor(client, terms) {
@@ -760,23 +749,18 @@ var BatchEncryptQueryOperation = class extends EncryptionOperation {
760
749
  log.emit();
761
750
  return { data: [] };
762
751
  }
763
- const { nullIndices, nonNullTerms } = filterNullTerms(this.terms);
764
- if (nonNullTerms.length === 0) {
765
- log.emit();
766
- return { data: this.terms.map(() => null) };
767
- }
768
752
  const result = await (0, import_result.withResult)(
769
753
  async () => {
770
754
  if (!this.client) throw noClientError();
771
755
  const { metadata } = this.getAuditData();
772
- const queries = nonNullTerms.map(
773
- ({ term }) => buildQueryPayload(term)
756
+ const queries = this.terms.map(
757
+ (term) => buildQueryPayload(term)
774
758
  );
775
759
  const encrypted = await (0, import_protect_ffi2.encryptQueryBulk)(this.client, {
776
760
  queries,
777
761
  unverifiedContext: metadata
778
762
  });
779
- return assembleResults(this.terms.length, encrypted, nonNullTerms);
763
+ return assembleResults(this.terms, encrypted);
780
764
  },
781
765
  (error) => {
782
766
  log.set({ errorCode: getErrorCode(error) ?? "unknown" });
@@ -810,11 +794,6 @@ var BatchEncryptQueryOperationWithLockContext = class extends EncryptionOperatio
810
794
  log.emit();
811
795
  return { data: [] };
812
796
  }
813
- const { nullIndices, nonNullTerms } = filterNullTerms(this.terms);
814
- if (nonNullTerms.length === 0) {
815
- log.emit();
816
- return { data: this.terms.map(() => null) };
817
- }
818
797
  const lockContextResult = await this.lockContext.getLockContext();
819
798
  if (lockContextResult.failure) {
820
799
  log.emit();
@@ -825,15 +804,15 @@ var BatchEncryptQueryOperationWithLockContext = class extends EncryptionOperatio
825
804
  async () => {
826
805
  if (!this.client) throw noClientError();
827
806
  const { metadata } = this.getAuditData();
828
- const queries = nonNullTerms.map(
829
- ({ term }) => buildQueryPayload(term, context)
807
+ const queries = this.terms.map(
808
+ (term) => buildQueryPayload(term, context)
830
809
  );
831
810
  const encrypted = await (0, import_protect_ffi2.encryptQueryBulk)(this.client, {
832
811
  queries,
833
812
  serviceToken: ctsToken,
834
813
  unverifiedContext: metadata
835
814
  });
836
- return assembleResults(this.terms.length, encrypted, nonNullTerms);
815
+ return assembleResults(this.terms, encrypted);
837
816
  },
838
817
  (error) => {
839
818
  log.set({ errorCode: getErrorCode(error) ?? "unknown" });
@@ -853,42 +832,25 @@ var BatchEncryptQueryOperationWithLockContext = class extends EncryptionOperatio
853
832
  var import_result2 = require("@byteslice/result");
854
833
  var import_protect_ffi3 = require("@cipherstash/protect-ffi");
855
834
  var createDecryptPayloads = (encryptedPayloads, lockContext) => {
856
- return encryptedPayloads.map((item, index) => ({ ...item, originalIndex: index })).filter(({ data }) => data !== null).map(({ id, data, originalIndex }) => ({
835
+ return encryptedPayloads.map(({ id, data }) => ({
857
836
  id,
858
837
  ciphertext: data,
859
- originalIndex,
860
838
  ...lockContext && { lockContext }
861
839
  }));
862
840
  };
863
- var createNullResult = (encryptedPayloads) => {
864
- return encryptedPayloads.map(({ id }) => ({
865
- id,
866
- data: null
867
- }));
868
- };
869
841
  var mapDecryptedDataToResult = (encryptedPayloads, decryptedData) => {
870
- const result = new Array(encryptedPayloads.length);
871
- let decryptedIndex = 0;
872
- for (let i = 0; i < encryptedPayloads.length; i++) {
873
- if (encryptedPayloads[i].data === null) {
874
- result[i] = { id: encryptedPayloads[i].id, data: null };
875
- } else {
876
- const decryptResult = decryptedData[decryptedIndex];
877
- if ("error" in decryptResult) {
878
- result[i] = {
879
- id: encryptedPayloads[i].id,
880
- error: decryptResult.error
881
- };
882
- } else {
883
- result[i] = {
884
- id: encryptedPayloads[i].id,
885
- data: decryptResult.data
886
- };
887
- }
888
- decryptedIndex++;
842
+ return decryptedData.map((decryptResult, i) => {
843
+ if ("error" in decryptResult) {
844
+ return {
845
+ id: encryptedPayloads[i].id,
846
+ error: decryptResult.error
847
+ };
889
848
  }
890
- }
891
- return result;
849
+ return {
850
+ id: encryptedPayloads[i].id,
851
+ data: decryptResult.data
852
+ };
853
+ });
892
854
  };
893
855
  var BulkDecryptOperation = class extends EncryptionOperation {
894
856
  client;
@@ -913,13 +875,10 @@ var BulkDecryptOperation = class extends EncryptionOperation {
913
875
  if (!this.client) throw noClientError();
914
876
  if (!this.encryptedPayloads || this.encryptedPayloads.length === 0)
915
877
  return [];
916
- const nonNullPayloads = createDecryptPayloads(this.encryptedPayloads);
917
- if (nonNullPayloads.length === 0) {
918
- return createNullResult(this.encryptedPayloads);
919
- }
878
+ const payloads = createDecryptPayloads(this.encryptedPayloads);
920
879
  const { metadata } = this.getAuditData();
921
880
  const decryptedData = await (0, import_protect_ffi3.decryptBulkFallible)(this.client, {
922
- ciphertexts: nonNullPayloads,
881
+ ciphertexts: payloads,
923
882
  unverifiedContext: metadata
924
883
  });
925
884
  return mapDecryptedDataToResult(this.encryptedPayloads, decryptedData);
@@ -971,16 +930,13 @@ var BulkDecryptOperationWithLockContext = class extends EncryptionOperation {
971
930
  if (context.failure) {
972
931
  throw new Error(`[encryption]: ${context.failure.message}`);
973
932
  }
974
- const nonNullPayloads = createDecryptPayloads(
933
+ const payloads = createDecryptPayloads(
975
934
  encryptedPayloads,
976
935
  context.data.context
977
936
  );
978
- if (nonNullPayloads.length === 0) {
979
- return createNullResult(encryptedPayloads);
980
- }
981
937
  const { metadata } = this.getAuditData();
982
938
  const decryptedData = await (0, import_protect_ffi3.decryptBulkFallible)(client, {
983
- ciphertexts: nonNullPayloads,
939
+ ciphertexts: payloads,
984
940
  serviceToken: context.data.ctsToken,
985
941
  unverifiedContext: metadata
986
942
  });
@@ -1619,34 +1575,14 @@ var BulkDecryptModelsOperationWithLockContext = class extends EncryptionOperatio
1619
1575
  var import_result4 = require("@byteslice/result");
1620
1576
  var import_protect_ffi5 = require("@cipherstash/protect-ffi");
1621
1577
  var createEncryptPayloads = (plaintexts, column, table, lockContext) => {
1622
- return plaintexts.map((item, index) => ({ ...item, originalIndex: index })).filter(({ plaintext }) => plaintext !== null).map(({ id, plaintext, originalIndex }) => ({
1578
+ return plaintexts.map(({ id, plaintext }) => ({
1623
1579
  id,
1624
1580
  plaintext,
1625
1581
  column: column.getName(),
1626
1582
  table: table.tableName,
1627
- originalIndex,
1628
1583
  ...lockContext && { lockContext }
1629
1584
  }));
1630
1585
  };
1631
- var createNullResult2 = (plaintexts) => {
1632
- return plaintexts.map(({ id }) => ({ id, data: null }));
1633
- };
1634
- var mapEncryptedDataToResult = (plaintexts, encryptedData) => {
1635
- const result = new Array(plaintexts.length);
1636
- let encryptedIndex = 0;
1637
- for (let i = 0; i < plaintexts.length; i++) {
1638
- if (plaintexts[i].plaintext === null) {
1639
- result[i] = { id: plaintexts[i].id, data: null };
1640
- } else {
1641
- result[i] = {
1642
- id: plaintexts[i].id,
1643
- data: encryptedData[encryptedIndex]
1644
- };
1645
- encryptedIndex++;
1646
- }
1647
- }
1648
- return result;
1649
- };
1650
1586
  var BulkEncryptOperation = class extends EncryptionOperation {
1651
1587
  client;
1652
1588
  plaintexts;
@@ -1679,20 +1615,20 @@ var BulkEncryptOperation = class extends EncryptionOperation {
1679
1615
  if (!this.plaintexts || this.plaintexts.length === 0) {
1680
1616
  return [];
1681
1617
  }
1682
- const nonNullPayloads = createEncryptPayloads(
1618
+ const payloads = createEncryptPayloads(
1683
1619
  this.plaintexts,
1684
1620
  this.column,
1685
1621
  this.table
1686
1622
  );
1687
- if (nonNullPayloads.length === 0) {
1688
- return createNullResult2(this.plaintexts);
1689
- }
1690
1623
  const { metadata } = this.getAuditData();
1691
1624
  const encryptedData = await (0, import_protect_ffi5.encryptBulk)(this.client, {
1692
- plaintexts: nonNullPayloads,
1625
+ plaintexts: payloads,
1693
1626
  unverifiedContext: metadata
1694
1627
  });
1695
- return mapEncryptedDataToResult(this.plaintexts, encryptedData);
1628
+ return encryptedData.map((data, i) => ({
1629
+ id: this.plaintexts[i].id,
1630
+ data
1631
+ }));
1696
1632
  },
1697
1633
  (error) => {
1698
1634
  log.set({ errorCode: getErrorCode(error) ?? "unknown" });
@@ -1749,22 +1685,22 @@ var BulkEncryptOperationWithLockContext = class extends EncryptionOperation {
1749
1685
  if (context.failure) {
1750
1686
  throw new Error(`[encryption]: ${context.failure.message}`);
1751
1687
  }
1752
- const nonNullPayloads = createEncryptPayloads(
1688
+ const payloads = createEncryptPayloads(
1753
1689
  plaintexts,
1754
1690
  column,
1755
1691
  table,
1756
1692
  context.data.context
1757
1693
  );
1758
- if (nonNullPayloads.length === 0) {
1759
- return createNullResult2(plaintexts);
1760
- }
1761
1694
  const { metadata } = this.getAuditData();
1762
1695
  const encryptedData = await (0, import_protect_ffi5.encryptBulk)(client, {
1763
- plaintexts: nonNullPayloads,
1696
+ plaintexts: payloads,
1764
1697
  serviceToken: context.data.ctsToken,
1765
1698
  unverifiedContext: metadata
1766
1699
  });
1767
- return mapEncryptedDataToResult(plaintexts, encryptedData);
1700
+ return encryptedData.map((data, i) => ({
1701
+ id: plaintexts[i].id,
1702
+ data
1703
+ }));
1768
1704
  },
1769
1705
  (error) => {
1770
1706
  log.set({ errorCode: getErrorCode(error) ?? "unknown" });
@@ -1914,9 +1850,6 @@ var DecryptOperation = class extends EncryptionOperation {
1914
1850
  if (!this.client) {
1915
1851
  throw noClientError();
1916
1852
  }
1917
- if (this.encryptedData === null) {
1918
- return null;
1919
- }
1920
1853
  const { metadata } = this.getAuditData();
1921
1854
  return await (0, import_protect_ffi6.decrypt)(this.client, {
1922
1855
  ciphertext: this.encryptedData,
@@ -1967,9 +1900,6 @@ var DecryptOperationWithLockContext = class extends EncryptionOperation {
1967
1900
  if (!client) {
1968
1901
  throw noClientError();
1969
1902
  }
1970
- if (encryptedData === null) {
1971
- return null;
1972
- }
1973
1903
  const { metadata } = this.getAuditData();
1974
1904
  const context = await this.lockContext.getLockContext();
1975
1905
  if (context.failure) {
@@ -2123,9 +2053,6 @@ var EncryptOperation = class extends EncryptionOperation {
2123
2053
  if (!this.client) {
2124
2054
  throw noClientError();
2125
2055
  }
2126
- if (this.plaintext === null) {
2127
- return null;
2128
- }
2129
2056
  if (typeof this.plaintext === "number" && Number.isNaN(this.plaintext)) {
2130
2057
  throw new Error("[encryption]: Cannot encrypt NaN value");
2131
2058
  }
@@ -2187,9 +2114,6 @@ var EncryptOperationWithLockContext = class extends EncryptionOperation {
2187
2114
  if (!client) {
2188
2115
  throw noClientError();
2189
2116
  }
2190
- if (plaintext === null) {
2191
- return null;
2192
- }
2193
2117
  const { metadata } = this.getAuditData();
2194
2118
  const context = await this.lockContext.getLockContext();
2195
2119
  if (context.failure) {
@@ -2353,10 +2277,6 @@ var EncryptQueryOperation = class extends EncryptionOperation {
2353
2277
  queryType: this.opts.queryType,
2354
2278
  lockContext: false
2355
2279
  });
2356
- if (this.plaintext === null || this.plaintext === void 0) {
2357
- log.emit();
2358
- return { data: null };
2359
- }
2360
2280
  const validationError = validateNumericValue(this.plaintext);
2361
2281
  if (validationError?.failure) {
2362
2282
  log.emit();
@@ -2420,10 +2340,6 @@ var EncryptQueryOperationWithLockContext = class extends EncryptionOperation {
2420
2340
  queryType: this.opts.queryType,
2421
2341
  lockContext: true
2422
2342
  });
2423
- if (this.plaintext === null || this.plaintext === void 0) {
2424
- log.emit();
2425
- return { data: null };
2426
- }
2427
2343
  const validationError = validateNumericValue(this.plaintext);
2428
2344
  if (validationError?.failure) {
2429
2345
  log.emit();
@@ -2528,7 +2444,7 @@ var EncryptionClient = class {
2528
2444
  /**
2529
2445
  * Encrypt a value - returns a promise which resolves to an encrypted value.
2530
2446
  *
2531
- * @param plaintext - The plaintext value to be encrypted. Can be null.
2447
+ * @param plaintext - The plaintext value to be encrypted.
2532
2448
  * @param opts - Options specifying the column (or nested field) and table for encryption. See {@link EncryptOptions}.
2533
2449
  * @returns An EncryptOperation that can be awaited or chained with additional methods.
2534
2450
  *
@@ -2829,7 +2745,7 @@ var EncryptionClient = class {
2829
2745
  *
2830
2746
  * Each value is encrypted with its own unique key via a single call to ZeroKMS.
2831
2747
  * Values can include optional `id` fields for correlating results back to
2832
- * your application data. Null plaintext values are preserved as null.
2748
+ * your application data.
2833
2749
  *
2834
2750
  * @param plaintexts - An array of objects with `plaintext` (and optional `id`) fields.
2835
2751
  * @param opts - Options specifying the target column (or nested {@link encryptedField}) and table. See {@link EncryptOptions}.
@@ -2850,7 +2766,6 @@ var EncryptionClient = class {
2850
2766
  * [
2851
2767
  * { id: "u1", plaintext: "alice@example.com" },
2852
2768
  * { id: "u2", plaintext: "bob@example.com" },
2853
- * { id: "u3", plaintext: null },
2854
2769
  * ],
2855
2770
  * { column: users.email, table: users },
2856
2771
  * )
@@ -2904,23 +2819,14 @@ var EncryptionClient = class {
2904
2819
  };
2905
2820
  }
2906
2821
  };
2907
-
2908
- // src/index.ts
2909
- function isValidUuid(uuid) {
2910
- 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;
2911
- return uuidRegex.test(uuid);
2912
- }
2913
2822
  var Encryption = async (config) => {
2914
- if (config.logging) {
2915
- initStackLogger(config.logging);
2916
- }
2917
2823
  const { schemas, config: clientConfig } = config;
2918
2824
  if (!schemas.length) {
2919
2825
  throw new Error(
2920
2826
  "[encryption]: At least one encryptedTable must be provided to initialize the encryption client"
2921
2827
  );
2922
2828
  }
2923
- if (clientConfig?.keyset && "id" in clientConfig.keyset && !isValidUuid(clientConfig.keyset.id)) {
2829
+ if (clientConfig?.keyset && "id" in clientConfig.keyset && !(0, import_uuid.validate)(clientConfig.keyset.id)) {
2924
2830
  throw new Error(
2925
2831
  "[encryption]: Invalid UUID provided for keyset id. Must be a valid UUID."
2926
2832
  );
@@ -2936,13 +2842,350 @@ var Encryption = async (config) => {
2936
2842
  }
2937
2843
  return result.data;
2938
2844
  };
2845
+
2846
+ // src/secrets/index.ts
2847
+ var Secrets = class {
2848
+ encryptionClient = null;
2849
+ config;
2850
+ apiBaseUrl = process.env.STASH_API_URL || "https://dashboard.cipherstash.com/api/secrets";
2851
+ secretsSchema = encryptedTable("secrets", {
2852
+ value: encryptedColumn("value")
2853
+ });
2854
+ constructor(config) {
2855
+ const workspaceCRN = config.workspaceCRN ?? process.env.CS_WORKSPACE_CRN;
2856
+ const clientId = config.clientId ?? process.env.CS_CLIENT_ID;
2857
+ const clientKey = config.clientKey ?? process.env.CS_CLIENT_KEY;
2858
+ const accessKey = config.accessKey ?? process.env.CS_CLIENT_ACCESS_KEY;
2859
+ if (!workspaceCRN || !clientId || !clientKey || !accessKey) {
2860
+ throw new Error(
2861
+ "Missing required configuration or environment variables."
2862
+ );
2863
+ }
2864
+ this.config = {
2865
+ environment: config.environment,
2866
+ workspaceCRN,
2867
+ clientId,
2868
+ clientKey,
2869
+ accessKey
2870
+ };
2871
+ }
2872
+ initPromise = null;
2873
+ /**
2874
+ * Initialize the Secrets client and underlying Encryption client
2875
+ */
2876
+ async ensureInitialized() {
2877
+ if (!this.initPromise) {
2878
+ this.initPromise = this._doInit();
2879
+ }
2880
+ return this.initPromise;
2881
+ }
2882
+ async _doInit() {
2883
+ logger.debug("Initializing the Secrets client.");
2884
+ this.encryptionClient = await Encryption({
2885
+ schemas: [this.secretsSchema],
2886
+ config: {
2887
+ workspaceCrn: this.config.workspaceCRN,
2888
+ clientId: this.config.clientId,
2889
+ clientKey: this.config.clientKey,
2890
+ accessKey: this.config.accessKey,
2891
+ keyset: { name: this.config.environment }
2892
+ }
2893
+ });
2894
+ logger.debug("Successfully initialized the Secrets client.");
2895
+ }
2896
+ /**
2897
+ * Get the authorization header for API requests
2898
+ */
2899
+ getAuthHeader() {
2900
+ return `Bearer ${this.config.accessKey}`;
2901
+ }
2902
+ /**
2903
+ * Make an API request with error handling.
2904
+ *
2905
+ * For GET requests, `params` are appended as URL query parameters.
2906
+ * For POST requests, `body` is sent as JSON in the request body.
2907
+ */
2908
+ async apiRequest(method, path2, options) {
2909
+ try {
2910
+ let url = `${this.apiBaseUrl}${path2}`;
2911
+ if (options?.params) {
2912
+ const searchParams = new URLSearchParams(options.params);
2913
+ url = `${url}?${searchParams.toString()}`;
2914
+ }
2915
+ logger.debug(`Secrets API request: ${method} ${path2}`);
2916
+ const headers = {
2917
+ "Content-Type": "application/json",
2918
+ Authorization: this.getAuthHeader()
2919
+ };
2920
+ const response = await fetch(url, {
2921
+ method,
2922
+ headers,
2923
+ body: options?.body ? JSON.stringify(options.body) : void 0
2924
+ });
2925
+ if (!response.ok) {
2926
+ const errorText = await response.text();
2927
+ let errorMessage = `API request failed with status ${response.status}`;
2928
+ try {
2929
+ const errorJson = JSON.parse(errorText);
2930
+ errorMessage = errorJson.message || errorJson.error || errorMessage;
2931
+ } catch {
2932
+ errorMessage = errorText || errorMessage;
2933
+ }
2934
+ logger.error(`Secrets API error on ${method} ${path2}: ${errorMessage}`);
2935
+ return {
2936
+ failure: {
2937
+ type: "ApiError",
2938
+ message: errorMessage
2939
+ }
2940
+ };
2941
+ }
2942
+ logger.debug(`Secrets API request successful: ${method} ${path2}`);
2943
+ const data = await response.json();
2944
+ return { data };
2945
+ } catch (error) {
2946
+ const message = error instanceof Error ? error.message : "Unknown network error occurred";
2947
+ logger.error(`Secrets network error on ${method} ${path2}: ${message}`);
2948
+ return {
2949
+ failure: {
2950
+ type: "NetworkError",
2951
+ message
2952
+ }
2953
+ };
2954
+ }
2955
+ }
2956
+ /**
2957
+ * Store an encrypted secret in the vault.
2958
+ * The value is encrypted locally before being sent to the API.
2959
+ *
2960
+ * API: POST /api/secrets/set
2961
+ *
2962
+ * @param name - The name of the secret
2963
+ * @param value - The plaintext value to encrypt and store
2964
+ * @returns A Result containing the API response or an error
2965
+ */
2966
+ async set(name, value) {
2967
+ logger.debug("Setting secret");
2968
+ await this.ensureInitialized();
2969
+ if (!this.encryptionClient) {
2970
+ return {
2971
+ failure: {
2972
+ type: "ClientError",
2973
+ message: "Failed to initialize Encryption client"
2974
+ }
2975
+ };
2976
+ }
2977
+ const encryptResult = await this.encryptionClient.encrypt(value, {
2978
+ column: this.secretsSchema.value,
2979
+ table: this.secretsSchema
2980
+ });
2981
+ if (encryptResult.failure) {
2982
+ logger.error("Failed to encrypt secret");
2983
+ return {
2984
+ failure: {
2985
+ type: "EncryptionError",
2986
+ message: encryptResult.failure.message
2987
+ }
2988
+ };
2989
+ }
2990
+ const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
2991
+ return await this.apiRequest("POST", "/set", {
2992
+ body: {
2993
+ workspaceId,
2994
+ environment: this.config.environment,
2995
+ name,
2996
+ encryptedValue: encryptedToPgComposite(encryptResult.data)
2997
+ }
2998
+ });
2999
+ }
3000
+ /**
3001
+ * Retrieve and decrypt a secret from the vault.
3002
+ * The secret is decrypted locally after retrieval.
3003
+ *
3004
+ * API: GET /api/secrets/get?workspaceId=...&environment=...&name=...
3005
+ *
3006
+ * @param name - The name of the secret to retrieve
3007
+ * @returns A Result containing the decrypted value or an error
3008
+ */
3009
+ async get(name) {
3010
+ logger.debug("Getting secret");
3011
+ await this.ensureInitialized();
3012
+ if (!this.encryptionClient) {
3013
+ return {
3014
+ failure: {
3015
+ type: "ClientError",
3016
+ message: "Failed to initialize Encryption client"
3017
+ }
3018
+ };
3019
+ }
3020
+ const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
3021
+ const apiResult = await this.apiRequest("GET", "/get", {
3022
+ params: {
3023
+ workspaceId,
3024
+ environment: this.config.environment,
3025
+ name
3026
+ }
3027
+ });
3028
+ if (apiResult.failure) {
3029
+ return apiResult;
3030
+ }
3031
+ const decryptResult = await this.encryptionClient.decrypt(
3032
+ apiResult.data.encryptedValue.data
3033
+ );
3034
+ if (decryptResult.failure) {
3035
+ logger.error("Failed to decrypt secret");
3036
+ return {
3037
+ failure: {
3038
+ type: "DecryptionError",
3039
+ message: decryptResult.failure.message
3040
+ }
3041
+ };
3042
+ }
3043
+ if (typeof decryptResult.data !== "string") {
3044
+ logger.error("Decrypted secret value is not a string");
3045
+ return {
3046
+ failure: {
3047
+ type: "DecryptionError",
3048
+ message: "Decrypted value is not a string"
3049
+ }
3050
+ };
3051
+ }
3052
+ return { data: decryptResult.data };
3053
+ }
3054
+ /**
3055
+ * Retrieve and decrypt many secrets from the vault.
3056
+ * The secrets are decrypted locally after retrieval.
3057
+ * This method only triggers a single network request to the ZeroKMS.
3058
+ *
3059
+ * API: GET /api/secrets/get-many?workspaceId=...&environment=...&names=name1,name2,...
3060
+ *
3061
+ * Constraints:
3062
+ * - Minimum 2 secret names required
3063
+ * - Maximum 100 secret names per request
3064
+ *
3065
+ * @param names - The names of the secrets to retrieve (min 2, max 100)
3066
+ * @returns A Result containing an object mapping secret names to their decrypted values
3067
+ */
3068
+ async getMany(names) {
3069
+ logger.debug(`Getting ${names.length} secrets.`);
3070
+ await this.ensureInitialized();
3071
+ if (!this.encryptionClient) {
3072
+ return {
3073
+ failure: {
3074
+ type: "ClientError",
3075
+ message: "Failed to initialize Encryption client"
3076
+ }
3077
+ };
3078
+ }
3079
+ if (names.length < 2) {
3080
+ return {
3081
+ failure: {
3082
+ type: "ClientError",
3083
+ message: "At least 2 secret names are required for getMany"
3084
+ }
3085
+ };
3086
+ }
3087
+ if (names.length > 100) {
3088
+ return {
3089
+ failure: {
3090
+ type: "ClientError",
3091
+ message: "Maximum 100 secret names per request"
3092
+ }
3093
+ };
3094
+ }
3095
+ const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
3096
+ const apiResult = await this.apiRequest(
3097
+ "GET",
3098
+ "/get-many",
3099
+ {
3100
+ params: {
3101
+ workspaceId,
3102
+ environment: this.config.environment,
3103
+ names: names.join(",")
3104
+ }
3105
+ }
3106
+ );
3107
+ if (apiResult.failure) {
3108
+ return apiResult;
3109
+ }
3110
+ const dataToDecrypt = apiResult.data.map((item) => ({
3111
+ name: item.name,
3112
+ value: item.encryptedValue.data
3113
+ }));
3114
+ const decryptResult = await this.encryptionClient.bulkDecryptModels(dataToDecrypt);
3115
+ if (decryptResult.failure) {
3116
+ logger.error(
3117
+ `Failed to decrypt secrets: ${decryptResult.failure.message}`
3118
+ );
3119
+ return {
3120
+ failure: {
3121
+ type: "DecryptionError",
3122
+ message: decryptResult.failure.message
3123
+ }
3124
+ };
3125
+ }
3126
+ const decryptedSecrets = decryptResult.data;
3127
+ const secretsMap = {};
3128
+ for (const secret of decryptedSecrets) {
3129
+ if (secret.name && secret.value) {
3130
+ secretsMap[secret.name] = secret.value;
3131
+ }
3132
+ }
3133
+ return { data: secretsMap };
3134
+ }
3135
+ /**
3136
+ * List all secrets in the environment.
3137
+ * Only names and metadata are returned; values remain encrypted.
3138
+ *
3139
+ * API: GET /api/secrets/list?workspaceId=...&environment=...
3140
+ *
3141
+ * @returns A Result containing the list of secrets or an error
3142
+ */
3143
+ async list() {
3144
+ logger.debug("Listing secrets.");
3145
+ const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
3146
+ const apiResult = await this.apiRequest(
3147
+ "GET",
3148
+ "/list",
3149
+ {
3150
+ params: {
3151
+ workspaceId,
3152
+ environment: this.config.environment
3153
+ }
3154
+ }
3155
+ );
3156
+ if (apiResult.failure) {
3157
+ return apiResult;
3158
+ }
3159
+ return { data: apiResult.data.secrets };
3160
+ }
3161
+ /**
3162
+ * Delete a secret from the vault.
3163
+ *
3164
+ * API: POST /api/secrets/delete
3165
+ *
3166
+ * @param name - The name of the secret to delete
3167
+ * @returns A Result containing the API response or an error
3168
+ */
3169
+ async delete(name) {
3170
+ logger.debug("Deleting secret");
3171
+ const workspaceId = extractWorkspaceIdFromCrn(this.config.workspaceCRN);
3172
+ return await this.apiRequest("POST", "/delete", {
3173
+ body: {
3174
+ workspaceId,
3175
+ environment: this.config.environment,
3176
+ name
3177
+ }
3178
+ });
3179
+ }
3180
+ };
2939
3181
  // Annotate the CommonJS export names for ESM import in node:
2940
3182
  0 && (module.exports = {
2941
3183
  Encryption,
2942
- EncryptionErrorTypes,
3184
+ Secrets,
2943
3185
  encryptedColumn,
2944
3186
  encryptedField,
2945
3187
  encryptedTable,
2946
- getErrorMessage
3188
+ encryptedToPgComposite,
3189
+ isEncryptedPayload
2947
3190
  });
2948
3191
  //# sourceMappingURL=index.cjs.map