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