@cipherstash/stack 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +670 -0
  4. package/dist/bin/stash.js +5049 -0
  5. package/dist/bin/stash.js.map +1 -0
  6. package/dist/chunk-2GZMIJFO.js +2400 -0
  7. package/dist/chunk-2GZMIJFO.js.map +1 -0
  8. package/dist/chunk-5DCT6YU2.js +138 -0
  9. package/dist/chunk-5DCT6YU2.js.map +1 -0
  10. package/dist/chunk-7XRPN2KX.js +336 -0
  11. package/dist/chunk-7XRPN2KX.js.map +1 -0
  12. package/dist/chunk-SJ7JO4ME.js +28 -0
  13. package/dist/chunk-SJ7JO4ME.js.map +1 -0
  14. package/dist/chunk-SUYMGQBY.js +67 -0
  15. package/dist/chunk-SUYMGQBY.js.map +1 -0
  16. package/dist/client-BxJG56Ey.d.cts +647 -0
  17. package/dist/client-DtGq9dJp.d.ts +647 -0
  18. package/dist/client.cjs +347 -0
  19. package/dist/client.cjs.map +1 -0
  20. package/dist/client.d.cts +7 -0
  21. package/dist/client.d.ts +7 -0
  22. package/dist/client.js +11 -0
  23. package/dist/client.js.map +1 -0
  24. package/dist/drizzle/index.cjs +1528 -0
  25. package/dist/drizzle/index.cjs.map +1 -0
  26. package/dist/drizzle/index.d.cts +350 -0
  27. package/dist/drizzle/index.d.ts +350 -0
  28. package/dist/drizzle/index.js +1212 -0
  29. package/dist/drizzle/index.js.map +1 -0
  30. package/dist/dynamodb/index.cjs +382 -0
  31. package/dist/dynamodb/index.cjs.map +1 -0
  32. package/dist/dynamodb/index.d.cts +125 -0
  33. package/dist/dynamodb/index.d.ts +125 -0
  34. package/dist/dynamodb/index.js +355 -0
  35. package/dist/dynamodb/index.js.map +1 -0
  36. package/dist/identity/index.cjs +271 -0
  37. package/dist/identity/index.cjs.map +1 -0
  38. package/dist/identity/index.d.cts +3 -0
  39. package/dist/identity/index.d.ts +3 -0
  40. package/dist/identity/index.js +117 -0
  41. package/dist/identity/index.js.map +1 -0
  42. package/dist/index-9-Ya3fDK.d.cts +169 -0
  43. package/dist/index-9-Ya3fDK.d.ts +169 -0
  44. package/dist/index.cjs +2915 -0
  45. package/dist/index.cjs.map +1 -0
  46. package/dist/index.d.cts +22 -0
  47. package/dist/index.d.ts +22 -0
  48. package/dist/index.js +23 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/schema/index.cjs +368 -0
  51. package/dist/schema/index.cjs.map +1 -0
  52. package/dist/schema/index.d.cts +4 -0
  53. package/dist/schema/index.d.ts +4 -0
  54. package/dist/schema/index.js +23 -0
  55. package/dist/schema/index.js.map +1 -0
  56. package/dist/secrets/index.cjs +3207 -0
  57. package/dist/secrets/index.cjs.map +1 -0
  58. package/dist/secrets/index.d.cts +227 -0
  59. package/dist/secrets/index.d.ts +227 -0
  60. package/dist/secrets/index.js +323 -0
  61. package/dist/secrets/index.js.map +1 -0
  62. package/dist/supabase/index.cjs +1113 -0
  63. package/dist/supabase/index.cjs.map +1 -0
  64. package/dist/supabase/index.d.cts +144 -0
  65. package/dist/supabase/index.d.ts +144 -0
  66. package/dist/supabase/index.js +864 -0
  67. package/dist/supabase/index.js.map +1 -0
  68. package/dist/types-public-BCj1L4fi.d.cts +1013 -0
  69. package/dist/types-public-BCj1L4fi.d.ts +1013 -0
  70. package/dist/types-public.cjs +40 -0
  71. package/dist/types-public.cjs.map +1 -0
  72. package/dist/types-public.d.cts +4 -0
  73. package/dist/types-public.d.ts +4 -0
  74. package/dist/types-public.js +7 -0
  75. package/dist/types-public.js.map +1 -0
  76. package/package.json +202 -0
@@ -0,0 +1,1113 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/supabase/index.ts
21
+ var supabase_exports = {};
22
+ __export(supabase_exports, {
23
+ encryptedSupabase: () => encryptedSupabase
24
+ });
25
+ module.exports = __toCommonJS(supabase_exports);
26
+
27
+ // src/encryption/helpers/index.ts
28
+ function encryptedToPgComposite(obj) {
29
+ return {
30
+ data: obj
31
+ };
32
+ }
33
+ function modelToEncryptedPgComposites(model) {
34
+ const result = {};
35
+ for (const [key, value] of Object.entries(model)) {
36
+ if (isEncryptedPayload(value)) {
37
+ result[key] = encryptedToPgComposite(value);
38
+ } else {
39
+ result[key] = value;
40
+ }
41
+ }
42
+ return result;
43
+ }
44
+ function bulkModelsToEncryptedPgComposites(models) {
45
+ return models.map((model) => modelToEncryptedPgComposites(model));
46
+ }
47
+ function isEncryptedPayload(value) {
48
+ if (value === null) return false;
49
+ if (typeof value !== "object") return false;
50
+ const obj = value;
51
+ if (!("v" in obj) || typeof obj.v !== "number") return false;
52
+ if (!("i" in obj) || typeof obj.i !== "object") return false;
53
+ if (!("c" in obj) && !("sv" in obj)) return false;
54
+ return true;
55
+ }
56
+
57
+ // src/schema/index.ts
58
+ var import_zod = require("zod");
59
+ var castAsEnum = import_zod.z.enum(["bigint", "boolean", "date", "number", "string", "json"]).default("string");
60
+ var tokenFilterSchema = import_zod.z.object({
61
+ kind: import_zod.z.literal("downcase")
62
+ });
63
+ var tokenizerSchema = import_zod.z.union([
64
+ import_zod.z.object({
65
+ kind: import_zod.z.literal("standard")
66
+ }),
67
+ import_zod.z.object({
68
+ kind: import_zod.z.literal("ngram"),
69
+ token_length: import_zod.z.number()
70
+ })
71
+ ]).default({ kind: "ngram", token_length: 3 }).optional();
72
+ var oreIndexOptsSchema = import_zod.z.object({});
73
+ var uniqueIndexOptsSchema = import_zod.z.object({
74
+ token_filters: import_zod.z.array(tokenFilterSchema).default([]).optional()
75
+ });
76
+ var matchIndexOptsSchema = import_zod.z.object({
77
+ tokenizer: tokenizerSchema,
78
+ token_filters: import_zod.z.array(tokenFilterSchema).default([]).optional(),
79
+ k: import_zod.z.number().default(6).optional(),
80
+ m: import_zod.z.number().default(2048).optional(),
81
+ include_original: import_zod.z.boolean().default(false).optional()
82
+ });
83
+ var steVecIndexOptsSchema = import_zod.z.object({
84
+ prefix: import_zod.z.string()
85
+ });
86
+ var indexesSchema = import_zod.z.object({
87
+ ore: oreIndexOptsSchema.optional(),
88
+ unique: uniqueIndexOptsSchema.optional(),
89
+ match: matchIndexOptsSchema.optional(),
90
+ ste_vec: steVecIndexOptsSchema.optional()
91
+ }).default({});
92
+ var columnSchema = import_zod.z.object({
93
+ cast_as: castAsEnum,
94
+ indexes: indexesSchema
95
+ }).default({});
96
+ var tableSchema = import_zod.z.record(columnSchema).default({});
97
+ var tablesSchema = import_zod.z.record(tableSchema).default({});
98
+ var encryptConfigSchema = import_zod.z.object({
99
+ v: import_zod.z.number(),
100
+ tables: tablesSchema
101
+ });
102
+ var ProtectColumn = class {
103
+ columnName;
104
+ castAsValue;
105
+ indexesValue = {};
106
+ constructor(columnName) {
107
+ this.columnName = columnName;
108
+ this.castAsValue = "string";
109
+ }
110
+ /**
111
+ * Set or override the plaintext data type for this column.
112
+ *
113
+ * By default all columns are treated as `'string'`. Use this method to specify
114
+ * a different type so the encryption layer knows how to encode the plaintext
115
+ * before encrypting.
116
+ *
117
+ * @param castAs - The plaintext data type: `'string'`, `'number'`, `'boolean'`, `'date'`, `'bigint'`, or `'json'`.
118
+ * @returns This `ProtectColumn` instance for method chaining.
119
+ *
120
+ * @example
121
+ * ```typescript
122
+ * import { encryptedColumn } from "@cipherstash/stack/schema"
123
+ *
124
+ * const dateOfBirth = encryptedColumn("date_of_birth").dataType("date")
125
+ * ```
126
+ */
127
+ dataType(castAs) {
128
+ this.castAsValue = castAs;
129
+ return this;
130
+ }
131
+ /**
132
+ * Enable Order-Revealing Encryption (ORE) indexing on this column.
133
+ *
134
+ * ORE allows sorting, comparison, and range queries on encrypted data.
135
+ * Use with `encryptQuery` and `queryType: 'orderAndRange'`.
136
+ *
137
+ * @returns This `ProtectColumn` instance for method chaining.
138
+ *
139
+ * @example
140
+ * ```typescript
141
+ * import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
142
+ *
143
+ * const users = encryptedTable("users", {
144
+ * email: encryptedColumn("email").orderAndRange(),
145
+ * })
146
+ * ```
147
+ */
148
+ orderAndRange() {
149
+ this.indexesValue.ore = {};
150
+ return this;
151
+ }
152
+ /**
153
+ * Enable an exact-match (unique) index on this column.
154
+ *
155
+ * Allows equality queries on encrypted data. Use with `encryptQuery`
156
+ * and `queryType: 'equality'`.
157
+ *
158
+ * @param tokenFilters - Optional array of token filters (e.g. `[{ kind: 'downcase' }]`).
159
+ * When omitted, no token filters are applied.
160
+ * @returns This `ProtectColumn` instance for method chaining.
161
+ *
162
+ * @example
163
+ * ```typescript
164
+ * import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
165
+ *
166
+ * const users = encryptedTable("users", {
167
+ * email: encryptedColumn("email").equality(),
168
+ * })
169
+ * ```
170
+ */
171
+ equality(tokenFilters) {
172
+ this.indexesValue.unique = {
173
+ token_filters: tokenFilters ?? []
174
+ };
175
+ return this;
176
+ }
177
+ /**
178
+ * Enable a full-text / fuzzy search (match) index on this column.
179
+ *
180
+ * Uses n-gram tokenization by default for substring and fuzzy matching.
181
+ * Use with `encryptQuery` and `queryType: 'freeTextSearch'`.
182
+ *
183
+ * @param opts - Optional match index configuration. Defaults to 3-character ngram
184
+ * tokenization with a downcase filter, `k=6`, `m=2048`, and `include_original=true`.
185
+ * @returns This `ProtectColumn` instance for method chaining.
186
+ *
187
+ * @example
188
+ * ```typescript
189
+ * import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
190
+ *
191
+ * const users = encryptedTable("users", {
192
+ * email: encryptedColumn("email").freeTextSearch(),
193
+ * })
194
+ *
195
+ * // With custom options
196
+ * const posts = encryptedTable("posts", {
197
+ * body: encryptedColumn("body").freeTextSearch({
198
+ * tokenizer: { kind: "ngram", token_length: 4 },
199
+ * k: 8,
200
+ * m: 4096,
201
+ * }),
202
+ * })
203
+ * ```
204
+ */
205
+ freeTextSearch(opts) {
206
+ this.indexesValue.match = {
207
+ tokenizer: opts?.tokenizer ?? { kind: "ngram", token_length: 3 },
208
+ token_filters: opts?.token_filters ?? [
209
+ {
210
+ kind: "downcase"
211
+ }
212
+ ],
213
+ k: opts?.k ?? 6,
214
+ m: opts?.m ?? 2048,
215
+ include_original: opts?.include_original ?? true
216
+ };
217
+ return this;
218
+ }
219
+ /**
220
+ * Configure this column for searchable encrypted JSON (STE-Vec).
221
+ *
222
+ * Enables encrypted JSONPath selector queries (e.g. `'$.user.email'`) and
223
+ * containment queries (e.g. `{ role: 'admin' }`). Automatically sets the
224
+ * data type to `'json'`.
225
+ *
226
+ * When used with `encryptQuery`, the query operation is auto-inferred from
227
+ * the plaintext type: strings become selector queries, objects/arrays become
228
+ * containment queries.
229
+ *
230
+ * @returns This `ProtectColumn` instance for method chaining.
231
+ *
232
+ * @example
233
+ * ```typescript
234
+ * import { encryptedTable, encryptedColumn } from "@cipherstash/stack/schema"
235
+ *
236
+ * const documents = encryptedTable("documents", {
237
+ * metadata: encryptedColumn("metadata").searchableJson(),
238
+ * })
239
+ * ```
240
+ */
241
+ searchableJson() {
242
+ this.castAsValue = "json";
243
+ this.indexesValue.ste_vec = { prefix: "enabled" };
244
+ return this;
245
+ }
246
+ build() {
247
+ return {
248
+ cast_as: this.castAsValue,
249
+ indexes: this.indexesValue
250
+ };
251
+ }
252
+ getName() {
253
+ return this.columnName;
254
+ }
255
+ };
256
+
257
+ // src/supabase/helpers.ts
258
+ function getEncryptedColumnNames(schema) {
259
+ const built = schema.build();
260
+ return Object.keys(built.columns);
261
+ }
262
+ function isEncryptedColumn(columnName, encryptedColumnNames) {
263
+ return encryptedColumnNames.includes(columnName);
264
+ }
265
+ function addJsonbCasts(columns, encryptedColumnNames) {
266
+ return columns.split(",").map((col) => {
267
+ const trimmed = col.trim();
268
+ if (!trimmed) return col;
269
+ if (trimmed.includes("::")) return col;
270
+ if (trimmed.includes("(") || trimmed.includes(".")) return col;
271
+ const parts = trimmed.split(/\s+/);
272
+ const colName = parts[0];
273
+ if (isEncryptedColumn(colName, encryptedColumnNames)) {
274
+ const leadingWhitespace = col.match(/^(\s*)/)?.[1] ?? "";
275
+ if (parts.length > 1) {
276
+ return `${leadingWhitespace}${colName}::jsonb ${parts.slice(1).join(" ")}`;
277
+ }
278
+ return `${leadingWhitespace}${colName}::jsonb`;
279
+ }
280
+ return col;
281
+ }).join(",");
282
+ }
283
+ function mapFilterOpToQueryType(op) {
284
+ switch (op) {
285
+ case "eq":
286
+ case "neq":
287
+ case "in":
288
+ case "is":
289
+ return "equality";
290
+ case "like":
291
+ case "ilike":
292
+ return "freeTextSearch";
293
+ case "gt":
294
+ case "gte":
295
+ case "lt":
296
+ case "lte":
297
+ return "orderAndRange";
298
+ default:
299
+ return "equality";
300
+ }
301
+ }
302
+ function parseOrString(orString) {
303
+ const conditions = [];
304
+ const parts = splitOrString(orString);
305
+ for (const part of parts) {
306
+ const trimmed = part.trim();
307
+ if (!trimmed) continue;
308
+ const firstDot = trimmed.indexOf(".");
309
+ if (firstDot === -1) continue;
310
+ const column = trimmed.slice(0, firstDot);
311
+ const rest = trimmed.slice(firstDot + 1);
312
+ const secondDot = rest.indexOf(".");
313
+ if (secondDot === -1) continue;
314
+ const op = rest.slice(0, secondDot);
315
+ const value = rest.slice(secondDot + 1);
316
+ const parsedValue = parseOrValue(value);
317
+ conditions.push({ column, op, value: parsedValue });
318
+ }
319
+ return conditions;
320
+ }
321
+ function rebuildOrString(conditions) {
322
+ return conditions.map((c) => {
323
+ const value = formatOrValue(c.value);
324
+ return `${c.column}.${c.op}.${value}`;
325
+ }).join(",");
326
+ }
327
+ function splitOrString(input) {
328
+ const parts = [];
329
+ let current = "";
330
+ let depth = 0;
331
+ for (const char of input) {
332
+ if (char === "(") {
333
+ depth++;
334
+ current += char;
335
+ } else if (char === ")") {
336
+ depth--;
337
+ current += char;
338
+ } else if (char === "," && depth === 0) {
339
+ parts.push(current);
340
+ current = "";
341
+ } else {
342
+ current += char;
343
+ }
344
+ }
345
+ if (current) {
346
+ parts.push(current);
347
+ }
348
+ return parts;
349
+ }
350
+ function parseOrValue(value) {
351
+ if (value.startsWith("(") && value.endsWith(")")) {
352
+ return value.slice(1, -1).split(",").map((v) => v.trim());
353
+ }
354
+ if (value === "true") return true;
355
+ if (value === "false") return false;
356
+ if (value === "null") return null;
357
+ return value;
358
+ }
359
+ function formatOrValue(value) {
360
+ if (Array.isArray(value)) {
361
+ return `(${value.join(",")})`;
362
+ }
363
+ if (value === null) return "null";
364
+ if (value === true) return "true";
365
+ if (value === false) return "false";
366
+ return String(value);
367
+ }
368
+
369
+ // src/supabase/query-builder.ts
370
+ var EncryptedQueryBuilderImpl = class {
371
+ tableName;
372
+ schema;
373
+ encryptionClient;
374
+ supabaseClient;
375
+ encryptedColumnNames;
376
+ // Recorded operations
377
+ mutation = null;
378
+ selectColumns = null;
379
+ selectOptions = void 0;
380
+ filters = [];
381
+ orFilters = [];
382
+ matchFilters = [];
383
+ notFilters = [];
384
+ rawFilters = [];
385
+ transforms = [];
386
+ resultMode = "array";
387
+ shouldThrowOnError = false;
388
+ // Encryption-specific state
389
+ lockContext = null;
390
+ auditConfig = null;
391
+ constructor(tableName, schema, encryptionClient, supabaseClient) {
392
+ this.tableName = tableName;
393
+ this.schema = schema;
394
+ this.encryptionClient = encryptionClient;
395
+ this.supabaseClient = supabaseClient;
396
+ this.encryptedColumnNames = getEncryptedColumnNames(schema);
397
+ }
398
+ // ---------------------------------------------------------------------------
399
+ // Mutation methods
400
+ // ---------------------------------------------------------------------------
401
+ select(columns, options) {
402
+ if (columns === "*") {
403
+ throw new Error(
404
+ "encryptedSupabase does not support select('*'). Please list columns explicitly so that encrypted columns can be cast with ::jsonb."
405
+ );
406
+ }
407
+ this.selectColumns = columns;
408
+ this.selectOptions = options;
409
+ return this;
410
+ }
411
+ insert(data, options) {
412
+ this.mutation = {
413
+ kind: "insert",
414
+ data,
415
+ options
416
+ };
417
+ return this;
418
+ }
419
+ update(data, options) {
420
+ this.mutation = {
421
+ kind: "update",
422
+ data,
423
+ options
424
+ };
425
+ return this;
426
+ }
427
+ upsert(data, options) {
428
+ this.mutation = {
429
+ kind: "upsert",
430
+ data,
431
+ options
432
+ };
433
+ return this;
434
+ }
435
+ delete(options) {
436
+ this.mutation = { kind: "delete", options };
437
+ return this;
438
+ }
439
+ // ---------------------------------------------------------------------------
440
+ // Filter methods
441
+ // ---------------------------------------------------------------------------
442
+ eq(column, value) {
443
+ this.filters.push({ op: "eq", column, value });
444
+ return this;
445
+ }
446
+ neq(column, value) {
447
+ this.filters.push({ op: "neq", column, value });
448
+ return this;
449
+ }
450
+ gt(column, value) {
451
+ this.filters.push({ op: "gt", column, value });
452
+ return this;
453
+ }
454
+ gte(column, value) {
455
+ this.filters.push({ op: "gte", column, value });
456
+ return this;
457
+ }
458
+ lt(column, value) {
459
+ this.filters.push({ op: "lt", column, value });
460
+ return this;
461
+ }
462
+ lte(column, value) {
463
+ this.filters.push({ op: "lte", column, value });
464
+ return this;
465
+ }
466
+ like(column, pattern) {
467
+ this.filters.push({ op: "like", column, value: pattern });
468
+ return this;
469
+ }
470
+ ilike(column, pattern) {
471
+ this.filters.push({ op: "ilike", column, value: pattern });
472
+ return this;
473
+ }
474
+ is(column, value) {
475
+ this.filters.push({ op: "is", column, value });
476
+ return this;
477
+ }
478
+ in(column, values) {
479
+ this.filters.push({ op: "in", column, value: values });
480
+ return this;
481
+ }
482
+ filter(column, operator, value) {
483
+ this.rawFilters.push({ column, operator, value });
484
+ return this;
485
+ }
486
+ not(column, operator, value) {
487
+ this.notFilters.push({ column, op: operator, value });
488
+ return this;
489
+ }
490
+ or(filtersOrConditions, options) {
491
+ if (typeof filtersOrConditions === "string") {
492
+ this.orFilters.push({
493
+ kind: "string",
494
+ value: filtersOrConditions,
495
+ referencedTable: options?.referencedTable ?? options?.foreignTable
496
+ });
497
+ } else {
498
+ this.orFilters.push({
499
+ kind: "structured",
500
+ conditions: filtersOrConditions
501
+ });
502
+ }
503
+ return this;
504
+ }
505
+ match(query) {
506
+ this.matchFilters.push({ query });
507
+ return this;
508
+ }
509
+ // ---------------------------------------------------------------------------
510
+ // Transform methods (passthrough)
511
+ // ---------------------------------------------------------------------------
512
+ order(column, options) {
513
+ this.transforms.push({ kind: "order", column, options });
514
+ return this;
515
+ }
516
+ limit(count, options) {
517
+ this.transforms.push({ kind: "limit", count, options });
518
+ return this;
519
+ }
520
+ range(from, to, options) {
521
+ this.transforms.push({ kind: "range", from, to, options });
522
+ return this;
523
+ }
524
+ single() {
525
+ this.resultMode = "single";
526
+ this.transforms.push({ kind: "single" });
527
+ return this;
528
+ }
529
+ maybeSingle() {
530
+ this.resultMode = "maybeSingle";
531
+ this.transforms.push({ kind: "maybeSingle" });
532
+ return this;
533
+ }
534
+ csv() {
535
+ this.transforms.push({ kind: "csv" });
536
+ return this;
537
+ }
538
+ abortSignal(signal) {
539
+ this.transforms.push({ kind: "abortSignal", signal });
540
+ return this;
541
+ }
542
+ throwOnError() {
543
+ this.shouldThrowOnError = true;
544
+ this.transforms.push({ kind: "throwOnError" });
545
+ return this;
546
+ }
547
+ returns() {
548
+ return this;
549
+ }
550
+ // ---------------------------------------------------------------------------
551
+ // Encryption-specific methods
552
+ // ---------------------------------------------------------------------------
553
+ withLockContext(lockContext) {
554
+ this.lockContext = lockContext;
555
+ return this;
556
+ }
557
+ audit(config) {
558
+ this.auditConfig = config;
559
+ return this;
560
+ }
561
+ // ---------------------------------------------------------------------------
562
+ // PromiseLike implementation (deferred execution)
563
+ // ---------------------------------------------------------------------------
564
+ then(onfulfilled, onrejected) {
565
+ return this.execute().then(onfulfilled, onrejected);
566
+ }
567
+ // ---------------------------------------------------------------------------
568
+ // Core execution
569
+ // ---------------------------------------------------------------------------
570
+ async execute() {
571
+ try {
572
+ const encryptedMutation = await this.encryptMutationData();
573
+ const selectString = this.buildSelectString();
574
+ const encryptedFilters = await this.encryptFilterValues();
575
+ const result = await this.buildAndExecuteQuery(
576
+ encryptedMutation,
577
+ selectString,
578
+ encryptedFilters
579
+ );
580
+ return await this.decryptResults(result);
581
+ } catch (err) {
582
+ const error = {
583
+ message: err instanceof Error ? err.message : String(err),
584
+ encryptionError: void 0
585
+ };
586
+ if (this.shouldThrowOnError) {
587
+ throw err;
588
+ }
589
+ return {
590
+ data: null,
591
+ error,
592
+ count: null,
593
+ status: 500,
594
+ statusText: "Encryption Error"
595
+ };
596
+ }
597
+ }
598
+ // ---------------------------------------------------------------------------
599
+ // Step 1: Encrypt mutation data
600
+ // ---------------------------------------------------------------------------
601
+ async encryptMutationData() {
602
+ if (!this.mutation) return null;
603
+ if (this.mutation.kind === "delete") return null;
604
+ const data = this.mutation.data;
605
+ if (Array.isArray(data)) {
606
+ const baseOp2 = this.encryptionClient.bulkEncryptModels(data, this.schema);
607
+ const op2 = this.lockContext ? baseOp2.withLockContext(this.lockContext) : baseOp2;
608
+ if (this.auditConfig) op2.audit(this.auditConfig);
609
+ const result2 = await op2;
610
+ if (result2.failure) {
611
+ throw new EncryptionFailedError(
612
+ `Failed to encrypt models: ${result2.failure.message}`,
613
+ result2.failure
614
+ );
615
+ }
616
+ return bulkModelsToEncryptedPgComposites(result2.data);
617
+ }
618
+ const baseOp = this.encryptionClient.encryptModel(data, this.schema);
619
+ const op = this.lockContext ? baseOp.withLockContext(this.lockContext) : baseOp;
620
+ if (this.auditConfig) op.audit(this.auditConfig);
621
+ const result = await op;
622
+ if (result.failure) {
623
+ throw new EncryptionFailedError(
624
+ `Failed to encrypt model: ${result.failure.message}`,
625
+ result.failure
626
+ );
627
+ }
628
+ return modelToEncryptedPgComposites(result.data);
629
+ }
630
+ // ---------------------------------------------------------------------------
631
+ // Step 2: Build select string with casts
632
+ // ---------------------------------------------------------------------------
633
+ buildSelectString() {
634
+ if (this.selectColumns === null) return null;
635
+ return addJsonbCasts(this.selectColumns, this.encryptedColumnNames);
636
+ }
637
+ // ---------------------------------------------------------------------------
638
+ // Step 3: Encrypt filter values
639
+ // ---------------------------------------------------------------------------
640
+ async encryptFilterValues() {
641
+ const terms = [];
642
+ const termMap = [];
643
+ const tableColumns = this.getColumnMap();
644
+ for (let i = 0; i < this.filters.length; i++) {
645
+ const f = this.filters[i];
646
+ if (!isEncryptedColumn(f.column, this.encryptedColumnNames)) continue;
647
+ const column = tableColumns[f.column];
648
+ if (!column) continue;
649
+ if (f.op === "in" && Array.isArray(f.value)) {
650
+ for (let j = 0; j < f.value.length; j++) {
651
+ terms.push({
652
+ value: f.value[j],
653
+ column,
654
+ table: this.schema,
655
+ queryType: mapFilterOpToQueryType(f.op),
656
+ returnType: "composite-literal"
657
+ });
658
+ termMap.push({ source: "filter", filterIndex: i, inIndex: j });
659
+ }
660
+ } else if (f.op === "is") {
661
+ continue;
662
+ } else {
663
+ terms.push({
664
+ value: f.value,
665
+ column,
666
+ table: this.schema,
667
+ queryType: mapFilterOpToQueryType(f.op),
668
+ returnType: "composite-literal"
669
+ });
670
+ termMap.push({ source: "filter", filterIndex: i });
671
+ }
672
+ }
673
+ for (let i = 0; i < this.matchFilters.length; i++) {
674
+ const mf = this.matchFilters[i];
675
+ for (const [colName, value] of Object.entries(mf.query)) {
676
+ if (!isEncryptedColumn(colName, this.encryptedColumnNames)) continue;
677
+ const column = tableColumns[colName];
678
+ if (!column) continue;
679
+ terms.push({
680
+ value,
681
+ column,
682
+ table: this.schema,
683
+ queryType: "equality",
684
+ returnType: "composite-literal"
685
+ });
686
+ termMap.push({ source: "match", matchIndex: i, column: colName });
687
+ }
688
+ }
689
+ for (let i = 0; i < this.notFilters.length; i++) {
690
+ const nf = this.notFilters[i];
691
+ if (!isEncryptedColumn(nf.column, this.encryptedColumnNames)) continue;
692
+ const column = tableColumns[nf.column];
693
+ if (!column) continue;
694
+ terms.push({
695
+ value: nf.value,
696
+ column,
697
+ table: this.schema,
698
+ queryType: mapFilterOpToQueryType(nf.op),
699
+ returnType: "composite-literal"
700
+ });
701
+ termMap.push({ source: "not", notIndex: i });
702
+ }
703
+ for (let i = 0; i < this.orFilters.length; i++) {
704
+ const of_ = this.orFilters[i];
705
+ if (of_.kind === "string") {
706
+ const parsed = parseOrString(of_.value);
707
+ for (let j = 0; j < parsed.length; j++) {
708
+ const cond = parsed[j];
709
+ if (!isEncryptedColumn(cond.column, this.encryptedColumnNames))
710
+ continue;
711
+ const column = tableColumns[cond.column];
712
+ if (!column) continue;
713
+ terms.push({
714
+ value: cond.value,
715
+ column,
716
+ table: this.schema,
717
+ queryType: mapFilterOpToQueryType(cond.op),
718
+ returnType: "composite-literal"
719
+ });
720
+ termMap.push({ source: "or-string", orIndex: i, conditionIndex: j });
721
+ }
722
+ } else {
723
+ for (let j = 0; j < of_.conditions.length; j++) {
724
+ const cond = of_.conditions[j];
725
+ if (!isEncryptedColumn(cond.column, this.encryptedColumnNames))
726
+ continue;
727
+ const column = tableColumns[cond.column];
728
+ if (!column) continue;
729
+ terms.push({
730
+ value: cond.value,
731
+ column,
732
+ table: this.schema,
733
+ queryType: mapFilterOpToQueryType(cond.op),
734
+ returnType: "composite-literal"
735
+ });
736
+ termMap.push({
737
+ source: "or-structured",
738
+ orIndex: i,
739
+ conditionIndex: j
740
+ });
741
+ }
742
+ }
743
+ }
744
+ for (let i = 0; i < this.rawFilters.length; i++) {
745
+ const rf = this.rawFilters[i];
746
+ if (!isEncryptedColumn(rf.column, this.encryptedColumnNames)) continue;
747
+ const column = tableColumns[rf.column];
748
+ if (!column) continue;
749
+ terms.push({
750
+ value: rf.value,
751
+ column,
752
+ table: this.schema,
753
+ queryType: "equality",
754
+ returnType: "composite-literal"
755
+ });
756
+ termMap.push({ source: "raw", rawIndex: i });
757
+ }
758
+ if (terms.length === 0) {
759
+ return { encryptedValues: [], termMap: [] };
760
+ }
761
+ const baseOp = this.encryptionClient.encryptQuery(terms);
762
+ const op = this.lockContext ? baseOp.withLockContext(this.lockContext) : baseOp;
763
+ if (this.auditConfig) op.audit(this.auditConfig);
764
+ const result = await op;
765
+ if (result.failure) {
766
+ throw new EncryptionFailedError(
767
+ `Failed to encrypt query terms: ${result.failure.message}`,
768
+ result.failure
769
+ );
770
+ }
771
+ return { encryptedValues: result.data, termMap };
772
+ }
773
+ // ---------------------------------------------------------------------------
774
+ // Step 4: Build and execute real Supabase query
775
+ // ---------------------------------------------------------------------------
776
+ async buildAndExecuteQuery(encryptedMutation, selectString, encryptedFilters) {
777
+ let query = this.supabaseClient.from(this.tableName);
778
+ if (this.mutation) {
779
+ switch (this.mutation.kind) {
780
+ case "insert":
781
+ query = query.insert(encryptedMutation, this.mutation.options);
782
+ break;
783
+ case "update":
784
+ query = query.update(encryptedMutation, this.mutation.options);
785
+ break;
786
+ case "upsert":
787
+ query = query.upsert(encryptedMutation, this.mutation.options);
788
+ break;
789
+ case "delete":
790
+ query = query.delete(this.mutation.options);
791
+ break;
792
+ }
793
+ }
794
+ if (selectString !== null) {
795
+ query = query.select(selectString, this.selectOptions);
796
+ } else if (!this.mutation) {
797
+ query = query.select("*", this.selectOptions);
798
+ }
799
+ query = this.applyFilters(query, encryptedFilters);
800
+ for (const t of this.transforms) {
801
+ switch (t.kind) {
802
+ case "order":
803
+ query = query.order(t.column, t.options);
804
+ break;
805
+ case "limit":
806
+ query = query.limit(t.count, t.options);
807
+ break;
808
+ case "range":
809
+ query = query.range(t.from, t.to, t.options);
810
+ break;
811
+ case "single":
812
+ query = query.single();
813
+ break;
814
+ case "maybeSingle":
815
+ query = query.maybeSingle();
816
+ break;
817
+ case "csv":
818
+ query = query.csv();
819
+ break;
820
+ case "abortSignal":
821
+ query = query.abortSignal(t.signal);
822
+ break;
823
+ case "throwOnError":
824
+ query = query.throwOnError();
825
+ break;
826
+ }
827
+ }
828
+ const result = await query;
829
+ return result;
830
+ }
831
+ // ---------------------------------------------------------------------------
832
+ // Apply filters with encrypted values substituted
833
+ // ---------------------------------------------------------------------------
834
+ applyFilters(query, encryptedFilters) {
835
+ let q = query;
836
+ const filterValueMap = /* @__PURE__ */ new Map();
837
+ const filterInMap = /* @__PURE__ */ new Map();
838
+ const matchValueMap = /* @__PURE__ */ new Map();
839
+ const notValueMap = /* @__PURE__ */ new Map();
840
+ const rawValueMap = /* @__PURE__ */ new Map();
841
+ const orStringConditionMap = /* @__PURE__ */ new Map();
842
+ const orStructuredConditionMap = /* @__PURE__ */ new Map();
843
+ for (let i = 0; i < encryptedFilters.termMap.length; i++) {
844
+ const mapping = encryptedFilters.termMap[i];
845
+ const encValue = encryptedFilters.encryptedValues[i];
846
+ switch (mapping.source) {
847
+ case "filter":
848
+ if (mapping.inIndex !== void 0) {
849
+ filterInMap.set(
850
+ `${mapping.filterIndex}:${mapping.inIndex}`,
851
+ encValue
852
+ );
853
+ } else {
854
+ filterValueMap.set(mapping.filterIndex, encValue);
855
+ }
856
+ break;
857
+ case "match":
858
+ matchValueMap.set(`${mapping.matchIndex}:${mapping.column}`, encValue);
859
+ break;
860
+ case "not":
861
+ notValueMap.set(mapping.notIndex, encValue);
862
+ break;
863
+ case "raw":
864
+ rawValueMap.set(mapping.rawIndex, encValue);
865
+ break;
866
+ case "or-string":
867
+ orStringConditionMap.set(
868
+ `${mapping.orIndex}:${mapping.conditionIndex}`,
869
+ encValue
870
+ );
871
+ break;
872
+ case "or-structured":
873
+ orStructuredConditionMap.set(
874
+ `${mapping.orIndex}:${mapping.conditionIndex}`,
875
+ encValue
876
+ );
877
+ break;
878
+ }
879
+ }
880
+ for (let i = 0; i < this.filters.length; i++) {
881
+ const f = this.filters[i];
882
+ let value = f.value;
883
+ if (filterValueMap.has(i)) {
884
+ value = filterValueMap.get(i);
885
+ } else if (f.op === "in" && Array.isArray(f.value)) {
886
+ value = f.value.map((v, j) => {
887
+ const key = `${i}:${j}`;
888
+ return filterInMap.has(key) ? filterInMap.get(key) : v;
889
+ });
890
+ }
891
+ switch (f.op) {
892
+ case "eq":
893
+ q = q.eq(f.column, value);
894
+ break;
895
+ case "neq":
896
+ q = q.neq(f.column, value);
897
+ break;
898
+ case "gt":
899
+ q = q.gt(f.column, value);
900
+ break;
901
+ case "gte":
902
+ q = q.gte(f.column, value);
903
+ break;
904
+ case "lt":
905
+ q = q.lt(f.column, value);
906
+ break;
907
+ case "lte":
908
+ q = q.lte(f.column, value);
909
+ break;
910
+ case "like":
911
+ q = q.like(f.column, value);
912
+ break;
913
+ case "ilike":
914
+ q = q.ilike(f.column, value);
915
+ break;
916
+ case "is":
917
+ q = q.is(f.column, value);
918
+ break;
919
+ case "in":
920
+ q = q.in(f.column, value);
921
+ break;
922
+ }
923
+ }
924
+ for (let i = 0; i < this.matchFilters.length; i++) {
925
+ const mf = this.matchFilters[i];
926
+ const resolvedQuery = {};
927
+ for (const [colName, originalValue] of Object.entries(mf.query)) {
928
+ const key = `${i}:${colName}`;
929
+ resolvedQuery[colName] = matchValueMap.has(key) ? matchValueMap.get(key) : originalValue;
930
+ }
931
+ q = q.match(resolvedQuery);
932
+ }
933
+ for (let i = 0; i < this.notFilters.length; i++) {
934
+ const nf = this.notFilters[i];
935
+ const value = notValueMap.has(i) ? notValueMap.get(i) : nf.value;
936
+ q = q.not(nf.column, nf.op, value);
937
+ }
938
+ for (let i = 0; i < this.orFilters.length; i++) {
939
+ const of_ = this.orFilters[i];
940
+ if (of_.kind === "string") {
941
+ const parsed = parseOrString(of_.value);
942
+ let hasEncrypted = false;
943
+ for (let j = 0; j < parsed.length; j++) {
944
+ const key = `${i}:${j}`;
945
+ if (orStringConditionMap.has(key)) {
946
+ parsed[j] = { ...parsed[j], value: orStringConditionMap.get(key) };
947
+ hasEncrypted = true;
948
+ }
949
+ }
950
+ if (hasEncrypted) {
951
+ q = q.or(rebuildOrString(parsed), {
952
+ referencedTable: of_.referencedTable
953
+ });
954
+ } else {
955
+ q = q.or(of_.value, { referencedTable: of_.referencedTable });
956
+ }
957
+ } else {
958
+ const conditions = of_.conditions.map((cond, j) => {
959
+ const key = `${i}:${j}`;
960
+ if (orStructuredConditionMap.has(key)) {
961
+ return { ...cond, value: orStructuredConditionMap.get(key) };
962
+ }
963
+ return cond;
964
+ });
965
+ q = q.or(rebuildOrString(conditions));
966
+ }
967
+ }
968
+ for (let i = 0; i < this.rawFilters.length; i++) {
969
+ const rf = this.rawFilters[i];
970
+ const value = rawValueMap.has(i) ? rawValueMap.get(i) : rf.value;
971
+ q = q.filter(rf.column, rf.operator, value);
972
+ }
973
+ return q;
974
+ }
975
+ // ---------------------------------------------------------------------------
976
+ // Step 5: Decrypt results
977
+ // ---------------------------------------------------------------------------
978
+ async decryptResults(result) {
979
+ if (result.error) {
980
+ return {
981
+ data: null,
982
+ error: {
983
+ message: result.error.message,
984
+ details: result.error.details,
985
+ hint: result.error.hint,
986
+ code: result.error.code
987
+ },
988
+ count: result.count ?? null,
989
+ status: result.status,
990
+ statusText: result.statusText
991
+ };
992
+ }
993
+ if (result.data === null || result.data === void 0) {
994
+ return {
995
+ data: null,
996
+ error: null,
997
+ count: result.count ?? null,
998
+ status: result.status,
999
+ statusText: result.statusText
1000
+ };
1001
+ }
1002
+ const hasSelect = this.selectColumns !== null;
1003
+ const hasMutationWithReturning = this.mutation !== null && hasSelect;
1004
+ if (!hasSelect && !hasMutationWithReturning) {
1005
+ return {
1006
+ data: result.data,
1007
+ error: null,
1008
+ count: result.count ?? null,
1009
+ status: result.status,
1010
+ statusText: result.statusText
1011
+ };
1012
+ }
1013
+ if (this.resultMode === "single" || this.resultMode === "maybeSingle") {
1014
+ if (result.data === null) {
1015
+ return {
1016
+ data: null,
1017
+ error: null,
1018
+ count: result.count ?? null,
1019
+ status: result.status,
1020
+ statusText: result.statusText
1021
+ };
1022
+ }
1023
+ const baseDecryptOp = this.encryptionClient.decryptModel(
1024
+ result.data
1025
+ );
1026
+ const decryptOp = this.lockContext ? baseDecryptOp.withLockContext(this.lockContext) : baseDecryptOp;
1027
+ if (this.auditConfig) decryptOp.audit(this.auditConfig);
1028
+ const decrypted2 = await decryptOp;
1029
+ if (decrypted2.failure) {
1030
+ throw new EncryptionFailedError(
1031
+ `Failed to decrypt model: ${decrypted2.failure.message}`,
1032
+ decrypted2.failure
1033
+ );
1034
+ }
1035
+ return {
1036
+ data: decrypted2.data,
1037
+ error: null,
1038
+ count: result.count ?? null,
1039
+ status: result.status,
1040
+ statusText: result.statusText
1041
+ };
1042
+ }
1043
+ const dataArray = result.data;
1044
+ if (dataArray.length === 0) {
1045
+ return {
1046
+ data: [],
1047
+ error: null,
1048
+ count: result.count ?? null,
1049
+ status: result.status,
1050
+ statusText: result.statusText
1051
+ };
1052
+ }
1053
+ const baseBulkDecryptOp = this.encryptionClient.bulkDecryptModels(dataArray);
1054
+ const bulkDecryptOp = this.lockContext ? baseBulkDecryptOp.withLockContext(this.lockContext) : baseBulkDecryptOp;
1055
+ if (this.auditConfig) bulkDecryptOp.audit(this.auditConfig);
1056
+ const decrypted = await bulkDecryptOp;
1057
+ if (decrypted.failure) {
1058
+ throw new EncryptionFailedError(
1059
+ `Failed to decrypt models: ${decrypted.failure.message}`,
1060
+ decrypted.failure
1061
+ );
1062
+ }
1063
+ return {
1064
+ data: decrypted.data,
1065
+ error: null,
1066
+ count: result.count ?? null,
1067
+ status: result.status,
1068
+ statusText: result.statusText
1069
+ };
1070
+ }
1071
+ // ---------------------------------------------------------------------------
1072
+ // Helpers
1073
+ // ---------------------------------------------------------------------------
1074
+ getColumnMap() {
1075
+ const map = {};
1076
+ const schema = this.schema;
1077
+ for (const colName of this.encryptedColumnNames) {
1078
+ const col = schema[colName];
1079
+ if (col instanceof ProtectColumn) {
1080
+ map[colName] = col;
1081
+ }
1082
+ }
1083
+ return map;
1084
+ }
1085
+ };
1086
+ var EncryptionFailedError = class extends Error {
1087
+ encryptionError;
1088
+ constructor(message, encryptionError) {
1089
+ super(message);
1090
+ this.name = "EncryptionFailedError";
1091
+ this.encryptionError = encryptionError;
1092
+ }
1093
+ };
1094
+
1095
+ // src/supabase/index.ts
1096
+ function encryptedSupabase(config) {
1097
+ const { encryptionClient, supabaseClient } = config;
1098
+ return {
1099
+ from(tableName, schema) {
1100
+ return new EncryptedQueryBuilderImpl(
1101
+ tableName,
1102
+ schema,
1103
+ encryptionClient,
1104
+ supabaseClient
1105
+ );
1106
+ }
1107
+ };
1108
+ }
1109
+ // Annotate the CommonJS export names for ESM import in node:
1110
+ 0 && (module.exports = {
1111
+ encryptedSupabase
1112
+ });
1113
+ //# sourceMappingURL=index.cjs.map