@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.
- package/CHANGELOG.md +7 -0
- package/LICENSE.md +21 -0
- package/README.md +670 -0
- package/dist/bin/stash.js +5049 -0
- package/dist/bin/stash.js.map +1 -0
- package/dist/chunk-2GZMIJFO.js +2400 -0
- package/dist/chunk-2GZMIJFO.js.map +1 -0
- package/dist/chunk-5DCT6YU2.js +138 -0
- package/dist/chunk-5DCT6YU2.js.map +1 -0
- package/dist/chunk-7XRPN2KX.js +336 -0
- package/dist/chunk-7XRPN2KX.js.map +1 -0
- package/dist/chunk-SJ7JO4ME.js +28 -0
- package/dist/chunk-SJ7JO4ME.js.map +1 -0
- package/dist/chunk-SUYMGQBY.js +67 -0
- package/dist/chunk-SUYMGQBY.js.map +1 -0
- package/dist/client-BxJG56Ey.d.cts +647 -0
- package/dist/client-DtGq9dJp.d.ts +647 -0
- package/dist/client.cjs +347 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +7 -0
- package/dist/client.d.ts +7 -0
- package/dist/client.js +11 -0
- package/dist/client.js.map +1 -0
- package/dist/drizzle/index.cjs +1528 -0
- package/dist/drizzle/index.cjs.map +1 -0
- package/dist/drizzle/index.d.cts +350 -0
- package/dist/drizzle/index.d.ts +350 -0
- package/dist/drizzle/index.js +1212 -0
- package/dist/drizzle/index.js.map +1 -0
- package/dist/dynamodb/index.cjs +382 -0
- package/dist/dynamodb/index.cjs.map +1 -0
- package/dist/dynamodb/index.d.cts +125 -0
- package/dist/dynamodb/index.d.ts +125 -0
- package/dist/dynamodb/index.js +355 -0
- package/dist/dynamodb/index.js.map +1 -0
- package/dist/identity/index.cjs +271 -0
- package/dist/identity/index.cjs.map +1 -0
- package/dist/identity/index.d.cts +3 -0
- package/dist/identity/index.d.ts +3 -0
- package/dist/identity/index.js +117 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/index-9-Ya3fDK.d.cts +169 -0
- package/dist/index-9-Ya3fDK.d.ts +169 -0
- package/dist/index.cjs +2915 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +22 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/schema/index.cjs +368 -0
- package/dist/schema/index.cjs.map +1 -0
- package/dist/schema/index.d.cts +4 -0
- package/dist/schema/index.d.ts +4 -0
- package/dist/schema/index.js +23 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/secrets/index.cjs +3207 -0
- package/dist/secrets/index.cjs.map +1 -0
- package/dist/secrets/index.d.cts +227 -0
- package/dist/secrets/index.d.ts +227 -0
- package/dist/secrets/index.js +323 -0
- package/dist/secrets/index.js.map +1 -0
- package/dist/supabase/index.cjs +1113 -0
- package/dist/supabase/index.cjs.map +1 -0
- package/dist/supabase/index.d.cts +144 -0
- package/dist/supabase/index.d.ts +144 -0
- package/dist/supabase/index.js +864 -0
- package/dist/supabase/index.js.map +1 -0
- package/dist/types-public-BCj1L4fi.d.cts +1013 -0
- package/dist/types-public-BCj1L4fi.d.ts +1013 -0
- package/dist/types-public.cjs +40 -0
- package/dist/types-public.cjs.map +1 -0
- package/dist/types-public.d.cts +4 -0
- package/dist/types-public.d.ts +4 -0
- package/dist/types-public.js +7 -0
- package/dist/types-public.js.map +1 -0
- package/package.json +202 -0
|
@@ -0,0 +1,864 @@
|
|
|
1
|
+
import {
|
|
2
|
+
bulkModelsToEncryptedPgComposites,
|
|
3
|
+
modelToEncryptedPgComposites
|
|
4
|
+
} from "../chunk-SUYMGQBY.js";
|
|
5
|
+
import {
|
|
6
|
+
ProtectColumn
|
|
7
|
+
} from "../chunk-7XRPN2KX.js";
|
|
8
|
+
|
|
9
|
+
// src/supabase/helpers.ts
|
|
10
|
+
function getEncryptedColumnNames(schema) {
|
|
11
|
+
const built = schema.build();
|
|
12
|
+
return Object.keys(built.columns);
|
|
13
|
+
}
|
|
14
|
+
function isEncryptedColumn(columnName, encryptedColumnNames) {
|
|
15
|
+
return encryptedColumnNames.includes(columnName);
|
|
16
|
+
}
|
|
17
|
+
function addJsonbCasts(columns, encryptedColumnNames) {
|
|
18
|
+
return columns.split(",").map((col) => {
|
|
19
|
+
const trimmed = col.trim();
|
|
20
|
+
if (!trimmed) return col;
|
|
21
|
+
if (trimmed.includes("::")) return col;
|
|
22
|
+
if (trimmed.includes("(") || trimmed.includes(".")) return col;
|
|
23
|
+
const parts = trimmed.split(/\s+/);
|
|
24
|
+
const colName = parts[0];
|
|
25
|
+
if (isEncryptedColumn(colName, encryptedColumnNames)) {
|
|
26
|
+
const leadingWhitespace = col.match(/^(\s*)/)?.[1] ?? "";
|
|
27
|
+
if (parts.length > 1) {
|
|
28
|
+
return `${leadingWhitespace}${colName}::jsonb ${parts.slice(1).join(" ")}`;
|
|
29
|
+
}
|
|
30
|
+
return `${leadingWhitespace}${colName}::jsonb`;
|
|
31
|
+
}
|
|
32
|
+
return col;
|
|
33
|
+
}).join(",");
|
|
34
|
+
}
|
|
35
|
+
function mapFilterOpToQueryType(op) {
|
|
36
|
+
switch (op) {
|
|
37
|
+
case "eq":
|
|
38
|
+
case "neq":
|
|
39
|
+
case "in":
|
|
40
|
+
case "is":
|
|
41
|
+
return "equality";
|
|
42
|
+
case "like":
|
|
43
|
+
case "ilike":
|
|
44
|
+
return "freeTextSearch";
|
|
45
|
+
case "gt":
|
|
46
|
+
case "gte":
|
|
47
|
+
case "lt":
|
|
48
|
+
case "lte":
|
|
49
|
+
return "orderAndRange";
|
|
50
|
+
default:
|
|
51
|
+
return "equality";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function parseOrString(orString) {
|
|
55
|
+
const conditions = [];
|
|
56
|
+
const parts = splitOrString(orString);
|
|
57
|
+
for (const part of parts) {
|
|
58
|
+
const trimmed = part.trim();
|
|
59
|
+
if (!trimmed) continue;
|
|
60
|
+
const firstDot = trimmed.indexOf(".");
|
|
61
|
+
if (firstDot === -1) continue;
|
|
62
|
+
const column = trimmed.slice(0, firstDot);
|
|
63
|
+
const rest = trimmed.slice(firstDot + 1);
|
|
64
|
+
const secondDot = rest.indexOf(".");
|
|
65
|
+
if (secondDot === -1) continue;
|
|
66
|
+
const op = rest.slice(0, secondDot);
|
|
67
|
+
const value = rest.slice(secondDot + 1);
|
|
68
|
+
const parsedValue = parseOrValue(value);
|
|
69
|
+
conditions.push({ column, op, value: parsedValue });
|
|
70
|
+
}
|
|
71
|
+
return conditions;
|
|
72
|
+
}
|
|
73
|
+
function rebuildOrString(conditions) {
|
|
74
|
+
return conditions.map((c) => {
|
|
75
|
+
const value = formatOrValue(c.value);
|
|
76
|
+
return `${c.column}.${c.op}.${value}`;
|
|
77
|
+
}).join(",");
|
|
78
|
+
}
|
|
79
|
+
function splitOrString(input) {
|
|
80
|
+
const parts = [];
|
|
81
|
+
let current = "";
|
|
82
|
+
let depth = 0;
|
|
83
|
+
for (const char of input) {
|
|
84
|
+
if (char === "(") {
|
|
85
|
+
depth++;
|
|
86
|
+
current += char;
|
|
87
|
+
} else if (char === ")") {
|
|
88
|
+
depth--;
|
|
89
|
+
current += char;
|
|
90
|
+
} else if (char === "," && depth === 0) {
|
|
91
|
+
parts.push(current);
|
|
92
|
+
current = "";
|
|
93
|
+
} else {
|
|
94
|
+
current += char;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (current) {
|
|
98
|
+
parts.push(current);
|
|
99
|
+
}
|
|
100
|
+
return parts;
|
|
101
|
+
}
|
|
102
|
+
function parseOrValue(value) {
|
|
103
|
+
if (value.startsWith("(") && value.endsWith(")")) {
|
|
104
|
+
return value.slice(1, -1).split(",").map((v) => v.trim());
|
|
105
|
+
}
|
|
106
|
+
if (value === "true") return true;
|
|
107
|
+
if (value === "false") return false;
|
|
108
|
+
if (value === "null") return null;
|
|
109
|
+
return value;
|
|
110
|
+
}
|
|
111
|
+
function formatOrValue(value) {
|
|
112
|
+
if (Array.isArray(value)) {
|
|
113
|
+
return `(${value.join(",")})`;
|
|
114
|
+
}
|
|
115
|
+
if (value === null) return "null";
|
|
116
|
+
if (value === true) return "true";
|
|
117
|
+
if (value === false) return "false";
|
|
118
|
+
return String(value);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// src/supabase/query-builder.ts
|
|
122
|
+
var EncryptedQueryBuilderImpl = class {
|
|
123
|
+
tableName;
|
|
124
|
+
schema;
|
|
125
|
+
encryptionClient;
|
|
126
|
+
supabaseClient;
|
|
127
|
+
encryptedColumnNames;
|
|
128
|
+
// Recorded operations
|
|
129
|
+
mutation = null;
|
|
130
|
+
selectColumns = null;
|
|
131
|
+
selectOptions = void 0;
|
|
132
|
+
filters = [];
|
|
133
|
+
orFilters = [];
|
|
134
|
+
matchFilters = [];
|
|
135
|
+
notFilters = [];
|
|
136
|
+
rawFilters = [];
|
|
137
|
+
transforms = [];
|
|
138
|
+
resultMode = "array";
|
|
139
|
+
shouldThrowOnError = false;
|
|
140
|
+
// Encryption-specific state
|
|
141
|
+
lockContext = null;
|
|
142
|
+
auditConfig = null;
|
|
143
|
+
constructor(tableName, schema, encryptionClient, supabaseClient) {
|
|
144
|
+
this.tableName = tableName;
|
|
145
|
+
this.schema = schema;
|
|
146
|
+
this.encryptionClient = encryptionClient;
|
|
147
|
+
this.supabaseClient = supabaseClient;
|
|
148
|
+
this.encryptedColumnNames = getEncryptedColumnNames(schema);
|
|
149
|
+
}
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
// Mutation methods
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
select(columns, options) {
|
|
154
|
+
if (columns === "*") {
|
|
155
|
+
throw new Error(
|
|
156
|
+
"encryptedSupabase does not support select('*'). Please list columns explicitly so that encrypted columns can be cast with ::jsonb."
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
this.selectColumns = columns;
|
|
160
|
+
this.selectOptions = options;
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
insert(data, options) {
|
|
164
|
+
this.mutation = {
|
|
165
|
+
kind: "insert",
|
|
166
|
+
data,
|
|
167
|
+
options
|
|
168
|
+
};
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
|
+
update(data, options) {
|
|
172
|
+
this.mutation = {
|
|
173
|
+
kind: "update",
|
|
174
|
+
data,
|
|
175
|
+
options
|
|
176
|
+
};
|
|
177
|
+
return this;
|
|
178
|
+
}
|
|
179
|
+
upsert(data, options) {
|
|
180
|
+
this.mutation = {
|
|
181
|
+
kind: "upsert",
|
|
182
|
+
data,
|
|
183
|
+
options
|
|
184
|
+
};
|
|
185
|
+
return this;
|
|
186
|
+
}
|
|
187
|
+
delete(options) {
|
|
188
|
+
this.mutation = { kind: "delete", options };
|
|
189
|
+
return this;
|
|
190
|
+
}
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// Filter methods
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
eq(column, value) {
|
|
195
|
+
this.filters.push({ op: "eq", column, value });
|
|
196
|
+
return this;
|
|
197
|
+
}
|
|
198
|
+
neq(column, value) {
|
|
199
|
+
this.filters.push({ op: "neq", column, value });
|
|
200
|
+
return this;
|
|
201
|
+
}
|
|
202
|
+
gt(column, value) {
|
|
203
|
+
this.filters.push({ op: "gt", column, value });
|
|
204
|
+
return this;
|
|
205
|
+
}
|
|
206
|
+
gte(column, value) {
|
|
207
|
+
this.filters.push({ op: "gte", column, value });
|
|
208
|
+
return this;
|
|
209
|
+
}
|
|
210
|
+
lt(column, value) {
|
|
211
|
+
this.filters.push({ op: "lt", column, value });
|
|
212
|
+
return this;
|
|
213
|
+
}
|
|
214
|
+
lte(column, value) {
|
|
215
|
+
this.filters.push({ op: "lte", column, value });
|
|
216
|
+
return this;
|
|
217
|
+
}
|
|
218
|
+
like(column, pattern) {
|
|
219
|
+
this.filters.push({ op: "like", column, value: pattern });
|
|
220
|
+
return this;
|
|
221
|
+
}
|
|
222
|
+
ilike(column, pattern) {
|
|
223
|
+
this.filters.push({ op: "ilike", column, value: pattern });
|
|
224
|
+
return this;
|
|
225
|
+
}
|
|
226
|
+
is(column, value) {
|
|
227
|
+
this.filters.push({ op: "is", column, value });
|
|
228
|
+
return this;
|
|
229
|
+
}
|
|
230
|
+
in(column, values) {
|
|
231
|
+
this.filters.push({ op: "in", column, value: values });
|
|
232
|
+
return this;
|
|
233
|
+
}
|
|
234
|
+
filter(column, operator, value) {
|
|
235
|
+
this.rawFilters.push({ column, operator, value });
|
|
236
|
+
return this;
|
|
237
|
+
}
|
|
238
|
+
not(column, operator, value) {
|
|
239
|
+
this.notFilters.push({ column, op: operator, value });
|
|
240
|
+
return this;
|
|
241
|
+
}
|
|
242
|
+
or(filtersOrConditions, options) {
|
|
243
|
+
if (typeof filtersOrConditions === "string") {
|
|
244
|
+
this.orFilters.push({
|
|
245
|
+
kind: "string",
|
|
246
|
+
value: filtersOrConditions,
|
|
247
|
+
referencedTable: options?.referencedTable ?? options?.foreignTable
|
|
248
|
+
});
|
|
249
|
+
} else {
|
|
250
|
+
this.orFilters.push({
|
|
251
|
+
kind: "structured",
|
|
252
|
+
conditions: filtersOrConditions
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
return this;
|
|
256
|
+
}
|
|
257
|
+
match(query) {
|
|
258
|
+
this.matchFilters.push({ query });
|
|
259
|
+
return this;
|
|
260
|
+
}
|
|
261
|
+
// ---------------------------------------------------------------------------
|
|
262
|
+
// Transform methods (passthrough)
|
|
263
|
+
// ---------------------------------------------------------------------------
|
|
264
|
+
order(column, options) {
|
|
265
|
+
this.transforms.push({ kind: "order", column, options });
|
|
266
|
+
return this;
|
|
267
|
+
}
|
|
268
|
+
limit(count, options) {
|
|
269
|
+
this.transforms.push({ kind: "limit", count, options });
|
|
270
|
+
return this;
|
|
271
|
+
}
|
|
272
|
+
range(from, to, options) {
|
|
273
|
+
this.transforms.push({ kind: "range", from, to, options });
|
|
274
|
+
return this;
|
|
275
|
+
}
|
|
276
|
+
single() {
|
|
277
|
+
this.resultMode = "single";
|
|
278
|
+
this.transforms.push({ kind: "single" });
|
|
279
|
+
return this;
|
|
280
|
+
}
|
|
281
|
+
maybeSingle() {
|
|
282
|
+
this.resultMode = "maybeSingle";
|
|
283
|
+
this.transforms.push({ kind: "maybeSingle" });
|
|
284
|
+
return this;
|
|
285
|
+
}
|
|
286
|
+
csv() {
|
|
287
|
+
this.transforms.push({ kind: "csv" });
|
|
288
|
+
return this;
|
|
289
|
+
}
|
|
290
|
+
abortSignal(signal) {
|
|
291
|
+
this.transforms.push({ kind: "abortSignal", signal });
|
|
292
|
+
return this;
|
|
293
|
+
}
|
|
294
|
+
throwOnError() {
|
|
295
|
+
this.shouldThrowOnError = true;
|
|
296
|
+
this.transforms.push({ kind: "throwOnError" });
|
|
297
|
+
return this;
|
|
298
|
+
}
|
|
299
|
+
returns() {
|
|
300
|
+
return this;
|
|
301
|
+
}
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
// Encryption-specific methods
|
|
304
|
+
// ---------------------------------------------------------------------------
|
|
305
|
+
withLockContext(lockContext) {
|
|
306
|
+
this.lockContext = lockContext;
|
|
307
|
+
return this;
|
|
308
|
+
}
|
|
309
|
+
audit(config) {
|
|
310
|
+
this.auditConfig = config;
|
|
311
|
+
return this;
|
|
312
|
+
}
|
|
313
|
+
// ---------------------------------------------------------------------------
|
|
314
|
+
// PromiseLike implementation (deferred execution)
|
|
315
|
+
// ---------------------------------------------------------------------------
|
|
316
|
+
then(onfulfilled, onrejected) {
|
|
317
|
+
return this.execute().then(onfulfilled, onrejected);
|
|
318
|
+
}
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
// Core execution
|
|
321
|
+
// ---------------------------------------------------------------------------
|
|
322
|
+
async execute() {
|
|
323
|
+
try {
|
|
324
|
+
const encryptedMutation = await this.encryptMutationData();
|
|
325
|
+
const selectString = this.buildSelectString();
|
|
326
|
+
const encryptedFilters = await this.encryptFilterValues();
|
|
327
|
+
const result = await this.buildAndExecuteQuery(
|
|
328
|
+
encryptedMutation,
|
|
329
|
+
selectString,
|
|
330
|
+
encryptedFilters
|
|
331
|
+
);
|
|
332
|
+
return await this.decryptResults(result);
|
|
333
|
+
} catch (err) {
|
|
334
|
+
const error = {
|
|
335
|
+
message: err instanceof Error ? err.message : String(err),
|
|
336
|
+
encryptionError: void 0
|
|
337
|
+
};
|
|
338
|
+
if (this.shouldThrowOnError) {
|
|
339
|
+
throw err;
|
|
340
|
+
}
|
|
341
|
+
return {
|
|
342
|
+
data: null,
|
|
343
|
+
error,
|
|
344
|
+
count: null,
|
|
345
|
+
status: 500,
|
|
346
|
+
statusText: "Encryption Error"
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// ---------------------------------------------------------------------------
|
|
351
|
+
// Step 1: Encrypt mutation data
|
|
352
|
+
// ---------------------------------------------------------------------------
|
|
353
|
+
async encryptMutationData() {
|
|
354
|
+
if (!this.mutation) return null;
|
|
355
|
+
if (this.mutation.kind === "delete") return null;
|
|
356
|
+
const data = this.mutation.data;
|
|
357
|
+
if (Array.isArray(data)) {
|
|
358
|
+
const baseOp2 = this.encryptionClient.bulkEncryptModels(data, this.schema);
|
|
359
|
+
const op2 = this.lockContext ? baseOp2.withLockContext(this.lockContext) : baseOp2;
|
|
360
|
+
if (this.auditConfig) op2.audit(this.auditConfig);
|
|
361
|
+
const result2 = await op2;
|
|
362
|
+
if (result2.failure) {
|
|
363
|
+
throw new EncryptionFailedError(
|
|
364
|
+
`Failed to encrypt models: ${result2.failure.message}`,
|
|
365
|
+
result2.failure
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
return bulkModelsToEncryptedPgComposites(result2.data);
|
|
369
|
+
}
|
|
370
|
+
const baseOp = this.encryptionClient.encryptModel(data, this.schema);
|
|
371
|
+
const op = this.lockContext ? baseOp.withLockContext(this.lockContext) : baseOp;
|
|
372
|
+
if (this.auditConfig) op.audit(this.auditConfig);
|
|
373
|
+
const result = await op;
|
|
374
|
+
if (result.failure) {
|
|
375
|
+
throw new EncryptionFailedError(
|
|
376
|
+
`Failed to encrypt model: ${result.failure.message}`,
|
|
377
|
+
result.failure
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
return modelToEncryptedPgComposites(result.data);
|
|
381
|
+
}
|
|
382
|
+
// ---------------------------------------------------------------------------
|
|
383
|
+
// Step 2: Build select string with casts
|
|
384
|
+
// ---------------------------------------------------------------------------
|
|
385
|
+
buildSelectString() {
|
|
386
|
+
if (this.selectColumns === null) return null;
|
|
387
|
+
return addJsonbCasts(this.selectColumns, this.encryptedColumnNames);
|
|
388
|
+
}
|
|
389
|
+
// ---------------------------------------------------------------------------
|
|
390
|
+
// Step 3: Encrypt filter values
|
|
391
|
+
// ---------------------------------------------------------------------------
|
|
392
|
+
async encryptFilterValues() {
|
|
393
|
+
const terms = [];
|
|
394
|
+
const termMap = [];
|
|
395
|
+
const tableColumns = this.getColumnMap();
|
|
396
|
+
for (let i = 0; i < this.filters.length; i++) {
|
|
397
|
+
const f = this.filters[i];
|
|
398
|
+
if (!isEncryptedColumn(f.column, this.encryptedColumnNames)) continue;
|
|
399
|
+
const column = tableColumns[f.column];
|
|
400
|
+
if (!column) continue;
|
|
401
|
+
if (f.op === "in" && Array.isArray(f.value)) {
|
|
402
|
+
for (let j = 0; j < f.value.length; j++) {
|
|
403
|
+
terms.push({
|
|
404
|
+
value: f.value[j],
|
|
405
|
+
column,
|
|
406
|
+
table: this.schema,
|
|
407
|
+
queryType: mapFilterOpToQueryType(f.op),
|
|
408
|
+
returnType: "composite-literal"
|
|
409
|
+
});
|
|
410
|
+
termMap.push({ source: "filter", filterIndex: i, inIndex: j });
|
|
411
|
+
}
|
|
412
|
+
} else if (f.op === "is") {
|
|
413
|
+
continue;
|
|
414
|
+
} else {
|
|
415
|
+
terms.push({
|
|
416
|
+
value: f.value,
|
|
417
|
+
column,
|
|
418
|
+
table: this.schema,
|
|
419
|
+
queryType: mapFilterOpToQueryType(f.op),
|
|
420
|
+
returnType: "composite-literal"
|
|
421
|
+
});
|
|
422
|
+
termMap.push({ source: "filter", filterIndex: i });
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
for (let i = 0; i < this.matchFilters.length; i++) {
|
|
426
|
+
const mf = this.matchFilters[i];
|
|
427
|
+
for (const [colName, value] of Object.entries(mf.query)) {
|
|
428
|
+
if (!isEncryptedColumn(colName, this.encryptedColumnNames)) continue;
|
|
429
|
+
const column = tableColumns[colName];
|
|
430
|
+
if (!column) continue;
|
|
431
|
+
terms.push({
|
|
432
|
+
value,
|
|
433
|
+
column,
|
|
434
|
+
table: this.schema,
|
|
435
|
+
queryType: "equality",
|
|
436
|
+
returnType: "composite-literal"
|
|
437
|
+
});
|
|
438
|
+
termMap.push({ source: "match", matchIndex: i, column: colName });
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
for (let i = 0; i < this.notFilters.length; i++) {
|
|
442
|
+
const nf = this.notFilters[i];
|
|
443
|
+
if (!isEncryptedColumn(nf.column, this.encryptedColumnNames)) continue;
|
|
444
|
+
const column = tableColumns[nf.column];
|
|
445
|
+
if (!column) continue;
|
|
446
|
+
terms.push({
|
|
447
|
+
value: nf.value,
|
|
448
|
+
column,
|
|
449
|
+
table: this.schema,
|
|
450
|
+
queryType: mapFilterOpToQueryType(nf.op),
|
|
451
|
+
returnType: "composite-literal"
|
|
452
|
+
});
|
|
453
|
+
termMap.push({ source: "not", notIndex: i });
|
|
454
|
+
}
|
|
455
|
+
for (let i = 0; i < this.orFilters.length; i++) {
|
|
456
|
+
const of_ = this.orFilters[i];
|
|
457
|
+
if (of_.kind === "string") {
|
|
458
|
+
const parsed = parseOrString(of_.value);
|
|
459
|
+
for (let j = 0; j < parsed.length; j++) {
|
|
460
|
+
const cond = parsed[j];
|
|
461
|
+
if (!isEncryptedColumn(cond.column, this.encryptedColumnNames))
|
|
462
|
+
continue;
|
|
463
|
+
const column = tableColumns[cond.column];
|
|
464
|
+
if (!column) continue;
|
|
465
|
+
terms.push({
|
|
466
|
+
value: cond.value,
|
|
467
|
+
column,
|
|
468
|
+
table: this.schema,
|
|
469
|
+
queryType: mapFilterOpToQueryType(cond.op),
|
|
470
|
+
returnType: "composite-literal"
|
|
471
|
+
});
|
|
472
|
+
termMap.push({ source: "or-string", orIndex: i, conditionIndex: j });
|
|
473
|
+
}
|
|
474
|
+
} else {
|
|
475
|
+
for (let j = 0; j < of_.conditions.length; j++) {
|
|
476
|
+
const cond = of_.conditions[j];
|
|
477
|
+
if (!isEncryptedColumn(cond.column, this.encryptedColumnNames))
|
|
478
|
+
continue;
|
|
479
|
+
const column = tableColumns[cond.column];
|
|
480
|
+
if (!column) continue;
|
|
481
|
+
terms.push({
|
|
482
|
+
value: cond.value,
|
|
483
|
+
column,
|
|
484
|
+
table: this.schema,
|
|
485
|
+
queryType: mapFilterOpToQueryType(cond.op),
|
|
486
|
+
returnType: "composite-literal"
|
|
487
|
+
});
|
|
488
|
+
termMap.push({
|
|
489
|
+
source: "or-structured",
|
|
490
|
+
orIndex: i,
|
|
491
|
+
conditionIndex: j
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
for (let i = 0; i < this.rawFilters.length; i++) {
|
|
497
|
+
const rf = this.rawFilters[i];
|
|
498
|
+
if (!isEncryptedColumn(rf.column, this.encryptedColumnNames)) continue;
|
|
499
|
+
const column = tableColumns[rf.column];
|
|
500
|
+
if (!column) continue;
|
|
501
|
+
terms.push({
|
|
502
|
+
value: rf.value,
|
|
503
|
+
column,
|
|
504
|
+
table: this.schema,
|
|
505
|
+
queryType: "equality",
|
|
506
|
+
returnType: "composite-literal"
|
|
507
|
+
});
|
|
508
|
+
termMap.push({ source: "raw", rawIndex: i });
|
|
509
|
+
}
|
|
510
|
+
if (terms.length === 0) {
|
|
511
|
+
return { encryptedValues: [], termMap: [] };
|
|
512
|
+
}
|
|
513
|
+
const baseOp = this.encryptionClient.encryptQuery(terms);
|
|
514
|
+
const op = this.lockContext ? baseOp.withLockContext(this.lockContext) : baseOp;
|
|
515
|
+
if (this.auditConfig) op.audit(this.auditConfig);
|
|
516
|
+
const result = await op;
|
|
517
|
+
if (result.failure) {
|
|
518
|
+
throw new EncryptionFailedError(
|
|
519
|
+
`Failed to encrypt query terms: ${result.failure.message}`,
|
|
520
|
+
result.failure
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
return { encryptedValues: result.data, termMap };
|
|
524
|
+
}
|
|
525
|
+
// ---------------------------------------------------------------------------
|
|
526
|
+
// Step 4: Build and execute real Supabase query
|
|
527
|
+
// ---------------------------------------------------------------------------
|
|
528
|
+
async buildAndExecuteQuery(encryptedMutation, selectString, encryptedFilters) {
|
|
529
|
+
let query = this.supabaseClient.from(this.tableName);
|
|
530
|
+
if (this.mutation) {
|
|
531
|
+
switch (this.mutation.kind) {
|
|
532
|
+
case "insert":
|
|
533
|
+
query = query.insert(encryptedMutation, this.mutation.options);
|
|
534
|
+
break;
|
|
535
|
+
case "update":
|
|
536
|
+
query = query.update(encryptedMutation, this.mutation.options);
|
|
537
|
+
break;
|
|
538
|
+
case "upsert":
|
|
539
|
+
query = query.upsert(encryptedMutation, this.mutation.options);
|
|
540
|
+
break;
|
|
541
|
+
case "delete":
|
|
542
|
+
query = query.delete(this.mutation.options);
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (selectString !== null) {
|
|
547
|
+
query = query.select(selectString, this.selectOptions);
|
|
548
|
+
} else if (!this.mutation) {
|
|
549
|
+
query = query.select("*", this.selectOptions);
|
|
550
|
+
}
|
|
551
|
+
query = this.applyFilters(query, encryptedFilters);
|
|
552
|
+
for (const t of this.transforms) {
|
|
553
|
+
switch (t.kind) {
|
|
554
|
+
case "order":
|
|
555
|
+
query = query.order(t.column, t.options);
|
|
556
|
+
break;
|
|
557
|
+
case "limit":
|
|
558
|
+
query = query.limit(t.count, t.options);
|
|
559
|
+
break;
|
|
560
|
+
case "range":
|
|
561
|
+
query = query.range(t.from, t.to, t.options);
|
|
562
|
+
break;
|
|
563
|
+
case "single":
|
|
564
|
+
query = query.single();
|
|
565
|
+
break;
|
|
566
|
+
case "maybeSingle":
|
|
567
|
+
query = query.maybeSingle();
|
|
568
|
+
break;
|
|
569
|
+
case "csv":
|
|
570
|
+
query = query.csv();
|
|
571
|
+
break;
|
|
572
|
+
case "abortSignal":
|
|
573
|
+
query = query.abortSignal(t.signal);
|
|
574
|
+
break;
|
|
575
|
+
case "throwOnError":
|
|
576
|
+
query = query.throwOnError();
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
const result = await query;
|
|
581
|
+
return result;
|
|
582
|
+
}
|
|
583
|
+
// ---------------------------------------------------------------------------
|
|
584
|
+
// Apply filters with encrypted values substituted
|
|
585
|
+
// ---------------------------------------------------------------------------
|
|
586
|
+
applyFilters(query, encryptedFilters) {
|
|
587
|
+
let q = query;
|
|
588
|
+
const filterValueMap = /* @__PURE__ */ new Map();
|
|
589
|
+
const filterInMap = /* @__PURE__ */ new Map();
|
|
590
|
+
const matchValueMap = /* @__PURE__ */ new Map();
|
|
591
|
+
const notValueMap = /* @__PURE__ */ new Map();
|
|
592
|
+
const rawValueMap = /* @__PURE__ */ new Map();
|
|
593
|
+
const orStringConditionMap = /* @__PURE__ */ new Map();
|
|
594
|
+
const orStructuredConditionMap = /* @__PURE__ */ new Map();
|
|
595
|
+
for (let i = 0; i < encryptedFilters.termMap.length; i++) {
|
|
596
|
+
const mapping = encryptedFilters.termMap[i];
|
|
597
|
+
const encValue = encryptedFilters.encryptedValues[i];
|
|
598
|
+
switch (mapping.source) {
|
|
599
|
+
case "filter":
|
|
600
|
+
if (mapping.inIndex !== void 0) {
|
|
601
|
+
filterInMap.set(
|
|
602
|
+
`${mapping.filterIndex}:${mapping.inIndex}`,
|
|
603
|
+
encValue
|
|
604
|
+
);
|
|
605
|
+
} else {
|
|
606
|
+
filterValueMap.set(mapping.filterIndex, encValue);
|
|
607
|
+
}
|
|
608
|
+
break;
|
|
609
|
+
case "match":
|
|
610
|
+
matchValueMap.set(`${mapping.matchIndex}:${mapping.column}`, encValue);
|
|
611
|
+
break;
|
|
612
|
+
case "not":
|
|
613
|
+
notValueMap.set(mapping.notIndex, encValue);
|
|
614
|
+
break;
|
|
615
|
+
case "raw":
|
|
616
|
+
rawValueMap.set(mapping.rawIndex, encValue);
|
|
617
|
+
break;
|
|
618
|
+
case "or-string":
|
|
619
|
+
orStringConditionMap.set(
|
|
620
|
+
`${mapping.orIndex}:${mapping.conditionIndex}`,
|
|
621
|
+
encValue
|
|
622
|
+
);
|
|
623
|
+
break;
|
|
624
|
+
case "or-structured":
|
|
625
|
+
orStructuredConditionMap.set(
|
|
626
|
+
`${mapping.orIndex}:${mapping.conditionIndex}`,
|
|
627
|
+
encValue
|
|
628
|
+
);
|
|
629
|
+
break;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
for (let i = 0; i < this.filters.length; i++) {
|
|
633
|
+
const f = this.filters[i];
|
|
634
|
+
let value = f.value;
|
|
635
|
+
if (filterValueMap.has(i)) {
|
|
636
|
+
value = filterValueMap.get(i);
|
|
637
|
+
} else if (f.op === "in" && Array.isArray(f.value)) {
|
|
638
|
+
value = f.value.map((v, j) => {
|
|
639
|
+
const key = `${i}:${j}`;
|
|
640
|
+
return filterInMap.has(key) ? filterInMap.get(key) : v;
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
switch (f.op) {
|
|
644
|
+
case "eq":
|
|
645
|
+
q = q.eq(f.column, value);
|
|
646
|
+
break;
|
|
647
|
+
case "neq":
|
|
648
|
+
q = q.neq(f.column, value);
|
|
649
|
+
break;
|
|
650
|
+
case "gt":
|
|
651
|
+
q = q.gt(f.column, value);
|
|
652
|
+
break;
|
|
653
|
+
case "gte":
|
|
654
|
+
q = q.gte(f.column, value);
|
|
655
|
+
break;
|
|
656
|
+
case "lt":
|
|
657
|
+
q = q.lt(f.column, value);
|
|
658
|
+
break;
|
|
659
|
+
case "lte":
|
|
660
|
+
q = q.lte(f.column, value);
|
|
661
|
+
break;
|
|
662
|
+
case "like":
|
|
663
|
+
q = q.like(f.column, value);
|
|
664
|
+
break;
|
|
665
|
+
case "ilike":
|
|
666
|
+
q = q.ilike(f.column, value);
|
|
667
|
+
break;
|
|
668
|
+
case "is":
|
|
669
|
+
q = q.is(f.column, value);
|
|
670
|
+
break;
|
|
671
|
+
case "in":
|
|
672
|
+
q = q.in(f.column, value);
|
|
673
|
+
break;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
for (let i = 0; i < this.matchFilters.length; i++) {
|
|
677
|
+
const mf = this.matchFilters[i];
|
|
678
|
+
const resolvedQuery = {};
|
|
679
|
+
for (const [colName, originalValue] of Object.entries(mf.query)) {
|
|
680
|
+
const key = `${i}:${colName}`;
|
|
681
|
+
resolvedQuery[colName] = matchValueMap.has(key) ? matchValueMap.get(key) : originalValue;
|
|
682
|
+
}
|
|
683
|
+
q = q.match(resolvedQuery);
|
|
684
|
+
}
|
|
685
|
+
for (let i = 0; i < this.notFilters.length; i++) {
|
|
686
|
+
const nf = this.notFilters[i];
|
|
687
|
+
const value = notValueMap.has(i) ? notValueMap.get(i) : nf.value;
|
|
688
|
+
q = q.not(nf.column, nf.op, value);
|
|
689
|
+
}
|
|
690
|
+
for (let i = 0; i < this.orFilters.length; i++) {
|
|
691
|
+
const of_ = this.orFilters[i];
|
|
692
|
+
if (of_.kind === "string") {
|
|
693
|
+
const parsed = parseOrString(of_.value);
|
|
694
|
+
let hasEncrypted = false;
|
|
695
|
+
for (let j = 0; j < parsed.length; j++) {
|
|
696
|
+
const key = `${i}:${j}`;
|
|
697
|
+
if (orStringConditionMap.has(key)) {
|
|
698
|
+
parsed[j] = { ...parsed[j], value: orStringConditionMap.get(key) };
|
|
699
|
+
hasEncrypted = true;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
if (hasEncrypted) {
|
|
703
|
+
q = q.or(rebuildOrString(parsed), {
|
|
704
|
+
referencedTable: of_.referencedTable
|
|
705
|
+
});
|
|
706
|
+
} else {
|
|
707
|
+
q = q.or(of_.value, { referencedTable: of_.referencedTable });
|
|
708
|
+
}
|
|
709
|
+
} else {
|
|
710
|
+
const conditions = of_.conditions.map((cond, j) => {
|
|
711
|
+
const key = `${i}:${j}`;
|
|
712
|
+
if (orStructuredConditionMap.has(key)) {
|
|
713
|
+
return { ...cond, value: orStructuredConditionMap.get(key) };
|
|
714
|
+
}
|
|
715
|
+
return cond;
|
|
716
|
+
});
|
|
717
|
+
q = q.or(rebuildOrString(conditions));
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
for (let i = 0; i < this.rawFilters.length; i++) {
|
|
721
|
+
const rf = this.rawFilters[i];
|
|
722
|
+
const value = rawValueMap.has(i) ? rawValueMap.get(i) : rf.value;
|
|
723
|
+
q = q.filter(rf.column, rf.operator, value);
|
|
724
|
+
}
|
|
725
|
+
return q;
|
|
726
|
+
}
|
|
727
|
+
// ---------------------------------------------------------------------------
|
|
728
|
+
// Step 5: Decrypt results
|
|
729
|
+
// ---------------------------------------------------------------------------
|
|
730
|
+
async decryptResults(result) {
|
|
731
|
+
if (result.error) {
|
|
732
|
+
return {
|
|
733
|
+
data: null,
|
|
734
|
+
error: {
|
|
735
|
+
message: result.error.message,
|
|
736
|
+
details: result.error.details,
|
|
737
|
+
hint: result.error.hint,
|
|
738
|
+
code: result.error.code
|
|
739
|
+
},
|
|
740
|
+
count: result.count ?? null,
|
|
741
|
+
status: result.status,
|
|
742
|
+
statusText: result.statusText
|
|
743
|
+
};
|
|
744
|
+
}
|
|
745
|
+
if (result.data === null || result.data === void 0) {
|
|
746
|
+
return {
|
|
747
|
+
data: null,
|
|
748
|
+
error: null,
|
|
749
|
+
count: result.count ?? null,
|
|
750
|
+
status: result.status,
|
|
751
|
+
statusText: result.statusText
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
const hasSelect = this.selectColumns !== null;
|
|
755
|
+
const hasMutationWithReturning = this.mutation !== null && hasSelect;
|
|
756
|
+
if (!hasSelect && !hasMutationWithReturning) {
|
|
757
|
+
return {
|
|
758
|
+
data: result.data,
|
|
759
|
+
error: null,
|
|
760
|
+
count: result.count ?? null,
|
|
761
|
+
status: result.status,
|
|
762
|
+
statusText: result.statusText
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
if (this.resultMode === "single" || this.resultMode === "maybeSingle") {
|
|
766
|
+
if (result.data === null) {
|
|
767
|
+
return {
|
|
768
|
+
data: null,
|
|
769
|
+
error: null,
|
|
770
|
+
count: result.count ?? null,
|
|
771
|
+
status: result.status,
|
|
772
|
+
statusText: result.statusText
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
const baseDecryptOp = this.encryptionClient.decryptModel(
|
|
776
|
+
result.data
|
|
777
|
+
);
|
|
778
|
+
const decryptOp = this.lockContext ? baseDecryptOp.withLockContext(this.lockContext) : baseDecryptOp;
|
|
779
|
+
if (this.auditConfig) decryptOp.audit(this.auditConfig);
|
|
780
|
+
const decrypted2 = await decryptOp;
|
|
781
|
+
if (decrypted2.failure) {
|
|
782
|
+
throw new EncryptionFailedError(
|
|
783
|
+
`Failed to decrypt model: ${decrypted2.failure.message}`,
|
|
784
|
+
decrypted2.failure
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
return {
|
|
788
|
+
data: decrypted2.data,
|
|
789
|
+
error: null,
|
|
790
|
+
count: result.count ?? null,
|
|
791
|
+
status: result.status,
|
|
792
|
+
statusText: result.statusText
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
const dataArray = result.data;
|
|
796
|
+
if (dataArray.length === 0) {
|
|
797
|
+
return {
|
|
798
|
+
data: [],
|
|
799
|
+
error: null,
|
|
800
|
+
count: result.count ?? null,
|
|
801
|
+
status: result.status,
|
|
802
|
+
statusText: result.statusText
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
const baseBulkDecryptOp = this.encryptionClient.bulkDecryptModels(dataArray);
|
|
806
|
+
const bulkDecryptOp = this.lockContext ? baseBulkDecryptOp.withLockContext(this.lockContext) : baseBulkDecryptOp;
|
|
807
|
+
if (this.auditConfig) bulkDecryptOp.audit(this.auditConfig);
|
|
808
|
+
const decrypted = await bulkDecryptOp;
|
|
809
|
+
if (decrypted.failure) {
|
|
810
|
+
throw new EncryptionFailedError(
|
|
811
|
+
`Failed to decrypt models: ${decrypted.failure.message}`,
|
|
812
|
+
decrypted.failure
|
|
813
|
+
);
|
|
814
|
+
}
|
|
815
|
+
return {
|
|
816
|
+
data: decrypted.data,
|
|
817
|
+
error: null,
|
|
818
|
+
count: result.count ?? null,
|
|
819
|
+
status: result.status,
|
|
820
|
+
statusText: result.statusText
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
// ---------------------------------------------------------------------------
|
|
824
|
+
// Helpers
|
|
825
|
+
// ---------------------------------------------------------------------------
|
|
826
|
+
getColumnMap() {
|
|
827
|
+
const map = {};
|
|
828
|
+
const schema = this.schema;
|
|
829
|
+
for (const colName of this.encryptedColumnNames) {
|
|
830
|
+
const col = schema[colName];
|
|
831
|
+
if (col instanceof ProtectColumn) {
|
|
832
|
+
map[colName] = col;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
return map;
|
|
836
|
+
}
|
|
837
|
+
};
|
|
838
|
+
var EncryptionFailedError = class extends Error {
|
|
839
|
+
encryptionError;
|
|
840
|
+
constructor(message, encryptionError) {
|
|
841
|
+
super(message);
|
|
842
|
+
this.name = "EncryptionFailedError";
|
|
843
|
+
this.encryptionError = encryptionError;
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
// src/supabase/index.ts
|
|
848
|
+
function encryptedSupabase(config) {
|
|
849
|
+
const { encryptionClient, supabaseClient } = config;
|
|
850
|
+
return {
|
|
851
|
+
from(tableName, schema) {
|
|
852
|
+
return new EncryptedQueryBuilderImpl(
|
|
853
|
+
tableName,
|
|
854
|
+
schema,
|
|
855
|
+
encryptionClient,
|
|
856
|
+
supabaseClient
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
}
|
|
861
|
+
export {
|
|
862
|
+
encryptedSupabase
|
|
863
|
+
};
|
|
864
|
+
//# sourceMappingURL=index.js.map
|