@prisma-next/extension-cipherstash 0.0.1

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 (68) hide show
  1. package/README.md +153 -0
  2. package/dist/call-classes-CSvD7w8U.mjs +206 -0
  3. package/dist/call-classes-CSvD7w8U.mjs.map +1 -0
  4. package/dist/column-types.d.mts +33 -0
  5. package/dist/column-types.d.mts.map +1 -0
  6. package/dist/column-types.mjs +42 -0
  7. package/dist/column-types.mjs.map +1 -0
  8. package/dist/constants-BDxL9Pe3.d.mts +22 -0
  9. package/dist/constants-BDxL9Pe3.d.mts.map +1 -0
  10. package/dist/constants-B_2TNvUi.mjs +46 -0
  11. package/dist/constants-B_2TNvUi.mjs.map +1 -0
  12. package/dist/control.d.mts +7 -0
  13. package/dist/control.d.mts.map +1 -0
  14. package/dist/control.mjs +430 -0
  15. package/dist/control.mjs.map +1 -0
  16. package/dist/descriptor-meta-BgQfZTAF.mjs +129 -0
  17. package/dist/descriptor-meta-BgQfZTAF.mjs.map +1 -0
  18. package/dist/envelope-P9BxfJNr.mjs +271 -0
  19. package/dist/envelope-P9BxfJNr.mjs.map +1 -0
  20. package/dist/middleware.d.mts +13 -0
  21. package/dist/middleware.d.mts.map +1 -0
  22. package/dist/middleware.mjs +129 -0
  23. package/dist/middleware.mjs.map +1 -0
  24. package/dist/migration.d.mts +141 -0
  25. package/dist/migration.d.mts.map +1 -0
  26. package/dist/migration.mjs +2 -0
  27. package/dist/operation-types.d.mts +49 -0
  28. package/dist/operation-types.d.mts.map +1 -0
  29. package/dist/operation-types.mjs +1 -0
  30. package/dist/pack.d.mts +86 -0
  31. package/dist/pack.d.mts.map +1 -0
  32. package/dist/pack.mjs +2 -0
  33. package/dist/runtime.d.mts +207 -0
  34. package/dist/runtime.d.mts.map +1 -0
  35. package/dist/runtime.mjs +429 -0
  36. package/dist/runtime.mjs.map +1 -0
  37. package/dist/sdk-D5FTGyzp.d.mts +67 -0
  38. package/dist/sdk-D5FTGyzp.d.mts.map +1 -0
  39. package/package.json +69 -0
  40. package/src/contract/authoring.ts +62 -0
  41. package/src/contract/contract.d.ts +149 -0
  42. package/src/contract/contract.json +104 -0
  43. package/src/contract/contract.prisma +46 -0
  44. package/src/execution/abort.ts +143 -0
  45. package/src/execution/codec-runtime.ts +209 -0
  46. package/src/execution/decrypt-all.ts +217 -0
  47. package/src/execution/envelope.ts +263 -0
  48. package/src/execution/operators.ts +211 -0
  49. package/src/execution/parameterized.ts +71 -0
  50. package/src/execution/routing.ts +93 -0
  51. package/src/execution/sdk.ts +68 -0
  52. package/src/exports/column-types.ts +62 -0
  53. package/src/exports/contract-space-typing.ts +86 -0
  54. package/src/exports/control.ts +120 -0
  55. package/src/exports/middleware.ts +24 -0
  56. package/src/exports/migration.ts +43 -0
  57. package/src/exports/operation-types.ts +16 -0
  58. package/src/exports/pack.ts +13 -0
  59. package/src/exports/runtime.ts +110 -0
  60. package/src/extension-metadata/codec-metadata.ts +81 -0
  61. package/src/extension-metadata/constants.ts +70 -0
  62. package/src/extension-metadata/descriptor-meta.ts +76 -0
  63. package/src/middleware/bulk-encrypt.ts +192 -0
  64. package/src/migration/call-classes.ts +350 -0
  65. package/src/migration/cipherstash-codec.ts +157 -0
  66. package/src/migration/eql-bundle.ts +29 -0
  67. package/src/migration/eql-install.generated.ts +5751 -0
  68. package/src/types/operation-types.ts +81 -0
@@ -0,0 +1,429 @@
1
+ import { i as CIPHERSTASH_STRING_CODEC_ID, n as CIPHERSTASH_EXTENSION_VERSION, r as CIPHERSTASH_SPACE_ID } from "./constants-B_2TNvUi.mjs";
2
+ import { a as setHandleRoutingKey, i as setHandlePlaintextCache, n as isHandleDecrypted, o as checkCipherstashAborted, s as raceCipherstashAbort, t as EncryptedString } from "./envelope-P9BxfJNr.mjs";
3
+ import { CodecImpl } from "@prisma-next/framework-components/codec";
4
+ import { ifDefined } from "@prisma-next/utils/defined";
5
+ import { ParamRef } from "@prisma-next/sql-relational-core/ast";
6
+ import { buildOperation, toExpr } from "@prisma-next/sql-relational-core/expression";
7
+ import { type } from "arktype";
8
+ //#region src/execution/operators.ts
9
+ /**
10
+ * Codec ID of the framework's Postgres boolean codec. Referenced as a
11
+ * string (rather than imported from `@prisma-next/target-postgres`)
12
+ * so cipherstash does not pick up a peer-dep on the target package
13
+ * just to identify a return-codec id. Mirrors the same pattern in the
14
+ * reference cipherstash integration's `operation-templates.ts:RETURN_BOOL`.
15
+ */
16
+ const PG_BOOL_CODEC_ID = "pg/bool@1";
17
+ /**
18
+ * Convert a user-supplied value (raw string plaintext or an existing
19
+ * `EncryptedString` envelope) into a `ParamRef` carrying an envelope
20
+ * tagged with the cipherstash storage codec id. The envelope's handle
21
+ * is stamped with the column's `(table, column)` routing context so
22
+ * the bulk-encrypt middleware can group it for SELECT-side bulk
23
+ * encryption (the middleware's AST walk only stamps for INSERT /
24
+ * UPDATE).
25
+ *
26
+ * Already-stamped envelopes are preserved write-once-wins per
27
+ * `setHandleRoutingKey`'s contract.
28
+ */
29
+ function asEncryptedParam(selfAst, value) {
30
+ const envelope = coerceToEnvelope(value);
31
+ const columnRef = extractColumnRef(selfAst);
32
+ if (columnRef !== void 0) setHandleRoutingKey(envelope, columnRef.table, columnRef.column);
33
+ return ParamRef.of(envelope, { codecId: CIPHERSTASH_STRING_CODEC_ID });
34
+ }
35
+ function coerceToEnvelope(value) {
36
+ if (value instanceof EncryptedString) return value;
37
+ if (typeof value === "string") return EncryptedString.from(value);
38
+ throw new TypeError(`cipherstash operator: expected a string plaintext or an EncryptedString envelope, got ${value === null ? "null" : typeof value}. Use \`EncryptedString.from(plaintext)\` to construct an envelope explicitly, or pass the plaintext directly and let the operator wrap it.`);
39
+ }
40
+ /**
41
+ * Find the column reference inside a `self` expression so the operator
42
+ * can stamp its `(table, column)` onto the encrypted-param envelope.
43
+ *
44
+ * Most calls flow through the ORM model-accessor, where `self` is a
45
+ * column-field accessor whose `buildAst()` returns a `ColumnRef`
46
+ * directly. For more complex `self` expressions (e.g. wrapped in a
47
+ * function call) we fall back to the `baseColumnRef()` inherited from
48
+ * `Expression` — every standard AST node walks down to the underlying
49
+ * column. If no column is reachable (e.g. a literal `self`), routing
50
+ * stamping is skipped; the envelope will surface the
51
+ * "envelope reached the bulk-encrypt phase without a (table, column)
52
+ * routing context" diagnostic from `collectTargets` at execute time.
53
+ */
54
+ function extractColumnRef(selfAst) {
55
+ if (selfAst.kind === "column-ref") return selfAst;
56
+ try {
57
+ return selfAst.baseColumnRef();
58
+ } catch {
59
+ return;
60
+ }
61
+ }
62
+ /**
63
+ * Build a cipherstash operator descriptor.
64
+ *
65
+ * @param publicMethod - The user-facing method name on the column
66
+ * accessor (e.g. `cipherstashEq`). Must not collide with any
67
+ * framework- or adapter-shipped method name.
68
+ * @param eqlFunction - The EQL function to lower to (`eq`, `ilike`).
69
+ * Embedded into the SQL lowering template as `eql_v2.<eqlFunction>(...)`.
70
+ */
71
+ function eqlOperator(publicMethod, eqlFunction) {
72
+ return {
73
+ self: { codecId: CIPHERSTASH_STRING_CODEC_ID },
74
+ impl: (self, value) => {
75
+ const selfAst = toExpr(self);
76
+ return buildOperation({
77
+ method: publicMethod,
78
+ args: [selfAst, asEncryptedParam(selfAst, value)],
79
+ returns: {
80
+ codecId: PG_BOOL_CODEC_ID,
81
+ nullable: false
82
+ },
83
+ lowering: {
84
+ targetFamily: "sql",
85
+ strategy: "function",
86
+ template: `eql_v2.${eqlFunction}({{self}}, {{arg0}})`
87
+ }
88
+ });
89
+ }
90
+ };
91
+ }
92
+ /**
93
+ * Cipherstash`s query-operations contributions. Wired into the
94
+ * runtime descriptor by `createCipherstashRuntimeDescriptor` and read
95
+ * by the SQL runtime`s `extractCodecLookup` / `queryOperations`
96
+ * aggregation (`packages/2-sql/5-runtime/src/sql-context.ts`). Two
97
+ * descriptors today:
98
+ *
99
+ * - `cipherstashEq` — encrypted equality via EQL`s `unique` index.
100
+ * SQL: `eql_v2.eq("col", $1::eql_v2_encrypted)`.
101
+ * - `cipherstashIlike` — encrypted free-text match via EQL`s
102
+ * `match` index. SQL:
103
+ * `eql_v2.ilike("col", $1::eql_v2_encrypted)`.
104
+ *
105
+ * Both descriptors register `self: { codecId: 'cipherstash/string@1' }`
106
+ * so the model accessor only attaches them to columns whose codec id
107
+ * is exactly `cipherstash/string@1`. The method names are
108
+ * intentionally cipherstash-prefixed so they coexist with the
109
+ * framework`s `eq` / `ilike` registrations rather than overriding
110
+ * them — see the `Why unique method names` section in this file`s
111
+ * top-level docblock.
112
+ */
113
+ function cipherstashQueryOperations() {
114
+ return {
115
+ cipherstashEq: eqlOperator("cipherstashEq", "eq"),
116
+ cipherstashIlike: eqlOperator("cipherstashIlike", "ilike")
117
+ };
118
+ }
119
+ //#endregion
120
+ //#region src/execution/codec-runtime.ts
121
+ const CIPHERSTASH_STRING_TARGET_TYPE = "eql_v2_encrypted";
122
+ const CIPHERSTASH_STRING_TRAITS = [];
123
+ /**
124
+ * Encode the SDK ciphertext payload as a Postgres composite literal
125
+ * `("...escaped JSON...")`. Embedded `"` are doubled per the composite
126
+ * text-format escape rules.
127
+ */
128
+ function encodeEqlV2EncryptedWire(payload) {
129
+ const json = JSON.stringify(payload);
130
+ if (json === void 0) throw new Error("cipherstash codec: ciphertext payload is not JSON-serializable. The CipherStash SDK must return a JSON-encodable bulk-encrypt result.");
131
+ return `("${json.replaceAll("\"", "\"\"")}")`;
132
+ }
133
+ /**
134
+ * Inverse of {@link encodeEqlV2EncryptedWire}. Postgres returns
135
+ * `eql_v2_encrypted` cells in composite text format; some pg clients
136
+ * pre-parse composite cells into `{ data: ... }` row objects. Both
137
+ * shapes — and `null`/`undefined` passthrough — are accepted.
138
+ */
139
+ function decodeEqlV2EncryptedWire(wire) {
140
+ if (wire === null || wire === void 0) return wire;
141
+ if (typeof wire === "object") {
142
+ if ("data" in wire) return wire.data;
143
+ return wire;
144
+ }
145
+ if (typeof wire !== "string") throw new Error(`cipherstash codec: unexpected wire shape for eql_v2_encrypted: ${typeof wire}`);
146
+ const trimmed = wire.trim();
147
+ if (!trimmed.startsWith("(") || !trimmed.endsWith(")")) throw new Error(`cipherstash codec: expected composite literal "(...)" but got: ${trimmed.slice(0, 40)}`);
148
+ const inner = trimmed.slice(1, -1);
149
+ const unquoted = inner.startsWith("\"") && inner.endsWith("\"") ? inner.slice(1, -1).replaceAll("\"\"", "\"") : inner;
150
+ return JSON.parse(unquoted);
151
+ }
152
+ var CipherstashStringCodec = class extends CodecImpl {
153
+ sdk;
154
+ constructor(descriptor, sdk) {
155
+ super(descriptor);
156
+ this.sdk = sdk;
157
+ }
158
+ async encode(value, _ctx) {
159
+ const handle = value.expose();
160
+ if (handle.ciphertext === void 0) throw new Error("cipherstash codec: envelope has no ciphertext at encode time. Register the bulk-encrypt middleware in the runtime so envelopes are encrypted before encoding.");
161
+ return encodeEqlV2EncryptedWire(handle.ciphertext);
162
+ }
163
+ async decode(wire, ctx) {
164
+ if (this.sdk === void 0) throw new Error("cipherstash codec: decode called on the metadata-only codec instance. Construct a runtime descriptor via `createCipherstashRuntimeDescriptor({ sdk })` and use that instead.");
165
+ const column = ctx.column;
166
+ if (!column) throw new Error("cipherstash codec: decode requires ctx.column to construct a routing-aware envelope. The SQL runtime populates ctx.column for projected columns; aggregate/computed cells are not supported by this codec.");
167
+ return EncryptedString.fromInternal({
168
+ ciphertext: decodeEqlV2EncryptedWire(wire),
169
+ table: column.table,
170
+ column: column.name,
171
+ sdk: this.sdk
172
+ });
173
+ }
174
+ encodeJson(_value) {
175
+ return { $encryptedString: "<opaque>" };
176
+ }
177
+ decodeJson(_json) {
178
+ throw new Error("cipherstash codec: decodeJson is not supported; envelopes do not round-trip through JSON.");
179
+ }
180
+ };
181
+ /**
182
+ * Variance-erased descriptor placeholder used by `createCipherstashStringCodec`
183
+ * so legacy callers that need a bare `Codec` instance (e.g. extension control-
184
+ * plane wiring that built up a flat list of codecs) can keep constructing one
185
+ * directly. Production runtime descriptors should resolve the per-instance
186
+ * codec through `CipherstashStringDescriptor.factory(params)(ctx)`.
187
+ */
188
+ const FALLBACK_DESCRIPTOR = {
189
+ codecId: CIPHERSTASH_STRING_CODEC_ID,
190
+ traits: CIPHERSTASH_STRING_TRAITS,
191
+ targetTypes: [CIPHERSTASH_STRING_TARGET_TYPE],
192
+ meta: { db: { sql: { postgres: { nativeType: CIPHERSTASH_STRING_TARGET_TYPE } } } },
193
+ paramsSchema: { "~standard": {
194
+ version: 1,
195
+ vendor: "cipherstash",
196
+ validate: (value) => ({ value })
197
+ } },
198
+ isParameterized: false,
199
+ renderOutputType: () => "EncryptedString",
200
+ factory: () => () => {
201
+ throw new Error("cipherstash codec: fallback descriptor factory is not callable");
202
+ }
203
+ };
204
+ function createCipherstashStringCodec(sdk) {
205
+ return new CipherstashStringCodec(FALLBACK_DESCRIPTOR, sdk);
206
+ }
207
+ //#endregion
208
+ //#region src/execution/parameterized.ts
209
+ const encryptedStringParamsSchema = type({
210
+ equality: "boolean",
211
+ freeTextSearch: "boolean"
212
+ });
213
+ function renderEncryptedStringOutputType(_params) {
214
+ return "EncryptedString";
215
+ }
216
+ function createParameterizedCodecDescriptors(sdk) {
217
+ const sharedCodec = createCipherstashStringCodec(sdk);
218
+ const factory = (_params) => (_ctx) => sharedCodec;
219
+ return [{
220
+ codecId: CIPHERSTASH_STRING_CODEC_ID,
221
+ traits: [],
222
+ targetTypes: ["eql_v2_encrypted"],
223
+ meta: { db: { sql: { postgres: { nativeType: "eql_v2_encrypted" } } } },
224
+ paramsSchema: encryptedStringParamsSchema,
225
+ isParameterized: true,
226
+ renderOutputType: renderEncryptedStringOutputType,
227
+ factory
228
+ }];
229
+ }
230
+ //#endregion
231
+ //#region src/execution/decrypt-all.ts
232
+ /**
233
+ * `decryptAll` — read-side bulk-decrypt walker.
234
+ *
235
+ * Public utility users invoke after `findMany` (or any other read
236
+ * surface) to materialize the plaintext for every `EncryptedString`
237
+ * envelope reachable from the result set in a fixed number of bulk SDK
238
+ * round-trips:
239
+ *
240
+ * const rows = await db.select(...).from(User).execute();
241
+ * await decryptAll(rows);
242
+ * // every envelope's `decrypt()` now returns plaintext synchronously.
243
+ *
244
+ * Why a separate utility (rather than middleware that auto-decrypts on
245
+ * every read): the framework`s streaming-read path cannot bulk-amortize
246
+ * decryption across rows it`s yielding incrementally — by the time row
247
+ * N is yielded, rows 1..N-1 have already been delivered to the caller.
248
+ * The `decryptAll` shape lets the caller buffer the result set
249
+ * explicitly (with `await stream.toArray()`) and then opt into bulk
250
+ * decryption in one round-trip per `(table, column)` group. The runtime
251
+ * descriptor wrapper deliberately does NOT register an implicit-decrypt
252
+ * middleware for this reason.
253
+ *
254
+ * **Walker shape**.
255
+ *
256
+ * - Recursive on plain objects + plain arrays only. Date / Map / Set /
257
+ * typed arrays / Buffer / function / etc. are not recursed into:
258
+ * cipherstash envelopes are user data and would not normally embed
259
+ * inside these host containers; if a future caller needs to bulk-
260
+ * decrypt envelopes inside such a container they extract them into a
261
+ * plain row first. The narrow scope keeps the walker`s behavior
262
+ * trivially predictable and avoids the cycle / iterator / lazy-eval
263
+ * surface those exotic types bring.
264
+ * - Cycle-safe via a `WeakSet` of visited objects/arrays; the same
265
+ * envelope appearing in N positions is collected once.
266
+ * - Skips envelopes whose plaintext slot is already populated
267
+ * (write-side envelopes from `EncryptedString.from(plaintext)`, or
268
+ * read-side envelopes already materialized by a prior
269
+ * `decrypt()` / `decryptAll(...)`). The skip means a re-run is a
270
+ * no-op and a mixed write/read row tree only round-trips for the
271
+ * envelopes that need it.
272
+ *
273
+ * **Grouping**. Envelopes are grouped by `(sdk, table, column)` —
274
+ * routing key plus the envelope handle`s SDK reference. The SDK split
275
+ * preserves the per-tenant SDK isolation `runtime.ts`'s docblock spells
276
+ * out: each tenant constructs its own runtime descriptor with its own
277
+ * SDK so per-tenant key material never crosses runtimes. Envelopes from
278
+ * different tenants happening to share `(table, column)` therefore
279
+ * still receive separate `bulkDecrypt` calls.
280
+ *
281
+ * **Cancellation**. `opts.signal` is forwarded by identity to every
282
+ * `bulkDecrypt` call via `ifDefined` — the same shape the bulk-encrypt
283
+ * middleware and `EncryptedString.decrypt({ signal? })` use. The
284
+ * walker also races each SDK promise against `opts.signal` via
285
+ * `raceCipherstashAbort` so an abort surfaces `RUNTIME.ABORTED
286
+ * { phase: 'decrypt-all' }` promptly even when the SDK body itself
287
+ * ignores the signal. A pre-check before the first SDK round-trip
288
+ * short-circuits when the signal is already aborted at entry; the
289
+ * no-envelopes-reachable fast path returns immediately without
290
+ * observing the signal.
291
+ */
292
+ /**
293
+ * Walk a result set and bulk-decrypt every `EncryptedString` envelope
294
+ * reachable from it. After the returned promise resolves, every touched
295
+ * envelope's `decrypt()` returns the cached plaintext synchronously
296
+ * without consulting the SDK.
297
+ *
298
+ * The walker is a no-op when no envelopes are reachable (returns
299
+ * without making any SDK call), so it is cheap to call defensively
300
+ * after queries that may or may not contain encrypted columns.
301
+ */
302
+ async function decryptAll(rows, opts) {
303
+ const targets = collectTargets(rows);
304
+ if (targets.length === 0) return;
305
+ const groups = groupTargets(targets);
306
+ for (const group of groups.values()) {
307
+ const first = group[0];
308
+ if (!first) continue;
309
+ const ciphertexts = group.map((t) => t.ciphertext);
310
+ checkCipherstashAborted(opts?.signal, "decrypt-all");
311
+ const plaintexts = await raceCipherstashAbort(first.sdk.bulkDecrypt({
312
+ routingKey: first.routingKey,
313
+ ciphertexts,
314
+ ...ifDefined("signal", opts?.signal)
315
+ }), opts?.signal, "decrypt-all");
316
+ if (plaintexts.length !== group.length) throw new Error(`cipherstash decryptAll: SDK returned ${plaintexts.length} plaintexts for routing key (${first.routingKey.table}, ${first.routingKey.column}) but ${group.length} were requested.`);
317
+ for (let i = 0; i < group.length; i++) {
318
+ const target = group[i];
319
+ const plaintext = plaintexts[i];
320
+ if (!target || plaintext === void 0) continue;
321
+ setHandlePlaintextCache(target.envelope, plaintext);
322
+ }
323
+ }
324
+ }
325
+ function collectTargets(root) {
326
+ const targets = [];
327
+ const seenObjects = /* @__PURE__ */ new WeakSet();
328
+ const seenEnvelopes = /* @__PURE__ */ new WeakSet();
329
+ visit(root, seenObjects, (envelope) => {
330
+ if (seenEnvelopes.has(envelope)) return;
331
+ seenEnvelopes.add(envelope);
332
+ if (isHandleDecrypted(envelope)) return;
333
+ const handle = envelope.expose();
334
+ if (handle.table === void 0 || handle.column === void 0) throw new Error("cipherstash decryptAll: envelope is missing (table, column) routing context. Read-side envelopes constructed via codec.decode always carry routing context; this typically means the envelope was constructed manually outside the codec path.");
335
+ if (handle.sdk === void 0) throw new Error("cipherstash decryptAll: envelope is missing the SDK reference needed to decrypt. Read-side envelopes constructed via codec.decode always carry an SDK reference; this typically means the envelope was constructed manually outside the codec path.");
336
+ targets.push({
337
+ envelope,
338
+ ciphertext: handle.ciphertext,
339
+ sdk: handle.sdk,
340
+ routingKey: {
341
+ table: handle.table,
342
+ column: handle.column
343
+ }
344
+ });
345
+ });
346
+ return targets;
347
+ }
348
+ function visit(value, seen, found) {
349
+ if (value === null || value === void 0) return;
350
+ if (value instanceof EncryptedString) {
351
+ found(value);
352
+ return;
353
+ }
354
+ if (typeof value !== "object") return;
355
+ if (seen.has(value)) return;
356
+ if (Array.isArray(value)) {
357
+ seen.add(value);
358
+ for (const item of value) visit(item, seen, found);
359
+ return;
360
+ }
361
+ if (!isPlainObject(value)) return;
362
+ seen.add(value);
363
+ for (const key of Object.keys(value)) visit(value[key], seen, found);
364
+ }
365
+ function isPlainObject(value) {
366
+ const proto = Object.getPrototypeOf(value);
367
+ return proto === null || proto === Object.prototype;
368
+ }
369
+ function groupTargets(targets) {
370
+ const sdkIndex = /* @__PURE__ */ new Map();
371
+ const groups = /* @__PURE__ */ new Map();
372
+ for (const target of targets) {
373
+ let idx = sdkIndex.get(target.sdk);
374
+ if (idx === void 0) {
375
+ idx = sdkIndex.size;
376
+ sdkIndex.set(target.sdk, idx);
377
+ }
378
+ const id = `${idx}\u0000${target.routingKey.table}\u0000${target.routingKey.column}`;
379
+ let group = groups.get(id);
380
+ if (!group) {
381
+ group = [];
382
+ groups.set(id, group);
383
+ }
384
+ group.push(target);
385
+ }
386
+ return groups;
387
+ }
388
+ //#endregion
389
+ //#region src/exports/runtime.ts
390
+ /**
391
+ * Compose the SDK-bound codec runtime + parameterized codec descriptors
392
+ * + runtime-plane codec-instances metadata into a single
393
+ * `SqlRuntimeExtensionDescriptor<'postgres'>`.
394
+ *
395
+ * The descriptor is per-SDK: cipherstash's codec captures the SDK at
396
+ * `decode` time (read-side single-cell `decrypt`) and the bulk-encrypt
397
+ * middleware captures it at `beforeExecute` time (write-side bulk
398
+ * round-trip). Multi-tenant deployments construct one descriptor per
399
+ * tenant SDK so per-tenant key material never crosses runtimes.
400
+ *
401
+ * Mirrors `packages/3-extensions/pgvector/src/exports/runtime.ts` —
402
+ * pgvector's vectorRuntimeDescriptor is a static default-export because
403
+ * its codec is fully stateless; cipherstash needs the factory wrapper
404
+ * because the codec depends on `sdk`.
405
+ */
406
+ function createCipherstashRuntimeDescriptor(opts) {
407
+ const { sdk } = opts;
408
+ const parameterizedDescriptors = createParameterizedCodecDescriptors(sdk);
409
+ return {
410
+ kind: "extension",
411
+ id: CIPHERSTASH_SPACE_ID,
412
+ version: CIPHERSTASH_EXTENSION_VERSION,
413
+ familyId: "sql",
414
+ targetId: "postgres",
415
+ types: { codecTypes: { codecDescriptors: parameterizedDescriptors } },
416
+ codecs: () => parameterizedDescriptors,
417
+ queryOperations: () => cipherstashQueryOperations(),
418
+ create() {
419
+ return {
420
+ familyId: "sql",
421
+ targetId: "postgres"
422
+ };
423
+ }
424
+ };
425
+ }
426
+ //#endregion
427
+ export { CIPHERSTASH_EXTENSION_VERSION, CIPHERSTASH_STRING_CODEC_ID, EncryptedString, createCipherstashRuntimeDescriptor, createCipherstashStringCodec, createParameterizedCodecDescriptors, decryptAll, encryptedStringParamsSchema, renderEncryptedStringOutputType };
428
+
429
+ //# sourceMappingURL=runtime.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.mjs","names":["arktype"],"sources":["../src/execution/operators.ts","../src/execution/codec-runtime.ts","../src/execution/parameterized.ts","../src/execution/decrypt-all.ts","../src/exports/runtime.ts"],"sourcesContent":["/**\n * Cipherstash query-operations registry.\n *\n * `cipherstashEq` and `cipherstashIlike` lower to EQL's encrypted-aware\n * comparison functions (`eql_v2.eq`, `eql_v2.ilike`) on\n * `cipherstash/string@1`-typed columns. The lowering shape mirrors the\n * canonical templates in the reference Prisma integration at\n * `reference/cipherstash/stack/packages/stack/src/prisma/core/\n * operation-templates.ts`:\n *\n * eql_v2.eq(<self>, <encrypted-arg>)\n * eql_v2.ilike(<self>, <encrypted-arg>)\n *\n * Why we diverge from Postgres' native `=` / `ILIKE` operators: EQL\n * ciphers contain randomized nonces, so two encrypts of the same\n * plaintext do not byte-equal under SQL `=`. EQL's `eql_v2.eq` /\n * `eql_v2.ilike` short-circuit through the per-column index\n * (`unique` / `match`) emitted by the codec lifecycle hook and produce\n * correct results.\n *\n * **Why cipherstash-namespaced method names (`cipherstashEq`,\n * `cipherstashIlike`) rather than reusing the framework`s `eq` /\n * `ilike`.** The framework`s `OperationRegistry` is a flat method-keyed\n * map and operator overriding is disallowed by project decision. Equally\n * importantly, cipherstash`s search operators are semantically distinct\n * from the framework built-ins — they take encrypted-aware envelope\n * arguments and lower to `eql_v2.eq` / `eql_v2.ilike`, which short-\n * circuit through EQL`s per-column index — so they belong under a\n * cipherstash-prefixed surface that flags the divergence at the call\n * site. The supported user-facing call shape on a cipherstash column is:\n *\n * model.users.where((u) => u.email.cipherstashEq('alice@example.com'))\n * model.users.where((u) => u.email.cipherstashIlike('%alice%'))\n *\n * The framework`s built-in `email.eq(...)` is **not reachable** on\n * cipherstash columns: the cipherstash codec declares no `equality`\n * trait (see `codec-runtime.ts` / `codec-metadata.ts` / `parameterized.ts`),\n * and the model-accessor synthesis in `sql-orm-client` gates\n * `COMPARISON_METHODS_META.eq` on the `equality` trait being present in\n * the column codec`s trait set. Calling `email.eq(...)` on a cipherstash\n * column is therefore `undefined` — the wrong-SQL footgun (where the\n * built-in `eq` would lower to standard SQL `=` against an\n * `eql_v2_encrypted` value, silently returning zero rows because EQL\n * ciphers contain randomized nonces) is closed at the codec layer, not\n * the operator layer. The trait declaration is regression-pinned by\n * `test/equality-trait-removal.test.ts`.\n *\n * The encrypted-arg path: the operator wraps the user-supplied value\n * in an `EncryptedString` envelope and stamps the column`s\n * `(table, column)` routing context onto the envelope`s handle. The\n * bulk-encrypt middleware then groups the envelope alongside\n * any others targeting the same `(table, column)` and issues one\n * `sdk.bulkEncrypt` per group. The cipherstash codec encodes the\n * resulting ciphertext as the wire payload at\n * `eql_v2_encrypted` cast time. Stamping at lowering time is the\n * load-bearing step — the middleware`s AST walk only handles\n * `InsertAst` / `UpdateAst` (see\n * `src/middleware/bulk-encrypt.ts:stampRoutingKeysFromAst`); SELECT\n * envelopes have to arrive at the middleware already routing-keyed.\n *\n * Build-time return type is the postgres `pg/bool@1` codec — that`s\n * the codec the framework`s predicate machinery looks at via the\n * `'boolean'` trait to decide that the operator`s return value is a\n * predicate suitable for a WHERE clause (see\n * `packages/3-extensions/sql-orm-client/src/model-accessor.ts:172-178`).\n *\n * **`isNull` / `isNotNull` are NOT registered here.** The framework`s\n * always-on `isNull` / `isNotNull` comparison methods construct\n * `NullCheckExpr` directly, bypassing\n * the operator-registry dispatch, and lower to `<col> IS [NOT] NULL`\n * regardless of codec — pinned by `test/operator-lowering.test.ts`.\n */\n\nimport type { SqlOperationDescriptor, SqlOperationDescriptors } from '@prisma-next/sql-operations';\nimport { type AnyExpression, type ColumnRef, ParamRef } from '@prisma-next/sql-relational-core/ast';\nimport {\n buildOperation,\n type Expression,\n type ScopeField,\n toExpr,\n} from '@prisma-next/sql-relational-core/expression';\nimport { CIPHERSTASH_STRING_CODEC_ID } from '../extension-metadata/constants';\nimport { EncryptedString, setHandleRoutingKey } from './envelope';\n\n/**\n * Codec ID of the framework's Postgres boolean codec. Referenced as a\n * string (rather than imported from `@prisma-next/target-postgres`)\n * so cipherstash does not pick up a peer-dep on the target package\n * just to identify a return-codec id. Mirrors the same pattern in the\n * reference cipherstash integration's `operation-templates.ts:RETURN_BOOL`.\n */\nconst PG_BOOL_CODEC_ID = 'pg/bool@1' as const;\n\ntype PgBoolReturn = { readonly codecId: typeof PG_BOOL_CODEC_ID; readonly nullable: false };\n\n/**\n * Convert a user-supplied value (raw string plaintext or an existing\n * `EncryptedString` envelope) into a `ParamRef` carrying an envelope\n * tagged with the cipherstash storage codec id. The envelope's handle\n * is stamped with the column's `(table, column)` routing context so\n * the bulk-encrypt middleware can group it for SELECT-side bulk\n * encryption (the middleware's AST walk only stamps for INSERT /\n * UPDATE).\n *\n * Already-stamped envelopes are preserved write-once-wins per\n * `setHandleRoutingKey`'s contract.\n */\nfunction asEncryptedParam(selfAst: AnyExpression, value: unknown): ParamRef {\n const envelope = coerceToEnvelope(value);\n const columnRef = extractColumnRef(selfAst);\n if (columnRef !== undefined) {\n setHandleRoutingKey(envelope, columnRef.table, columnRef.column);\n }\n return ParamRef.of(envelope, { codecId: CIPHERSTASH_STRING_CODEC_ID });\n}\n\nfunction coerceToEnvelope(value: unknown): EncryptedString {\n if (value instanceof EncryptedString) {\n return value;\n }\n if (typeof value === 'string') {\n return EncryptedString.from(value);\n }\n throw new TypeError(\n 'cipherstash operator: expected a string plaintext or an EncryptedString envelope, ' +\n `got ${value === null ? 'null' : typeof value}. ` +\n 'Use `EncryptedString.from(plaintext)` to construct an envelope explicitly, or ' +\n 'pass the plaintext directly and let the operator wrap it.',\n );\n}\n\n/**\n * Find the column reference inside a `self` expression so the operator\n * can stamp its `(table, column)` onto the encrypted-param envelope.\n *\n * Most calls flow through the ORM model-accessor, where `self` is a\n * column-field accessor whose `buildAst()` returns a `ColumnRef`\n * directly. For more complex `self` expressions (e.g. wrapped in a\n * function call) we fall back to the `baseColumnRef()` inherited from\n * `Expression` — every standard AST node walks down to the underlying\n * column. If no column is reachable (e.g. a literal `self`), routing\n * stamping is skipped; the envelope will surface the\n * \"envelope reached the bulk-encrypt phase without a (table, column)\n * routing context\" diagnostic from `collectTargets` at execute time.\n */\nfunction extractColumnRef(selfAst: AnyExpression): ColumnRef | undefined {\n if (selfAst.kind === 'column-ref') {\n return selfAst;\n }\n try {\n return selfAst.baseColumnRef();\n } catch {\n return undefined;\n }\n}\n\n/**\n * Build a cipherstash operator descriptor.\n *\n * @param publicMethod - The user-facing method name on the column\n * accessor (e.g. `cipherstashEq`). Must not collide with any\n * framework- or adapter-shipped method name.\n * @param eqlFunction - The EQL function to lower to (`eq`, `ilike`).\n * Embedded into the SQL lowering template as `eql_v2.<eqlFunction>(...)`.\n */\nfunction eqlOperator(publicMethod: string, eqlFunction: 'eq' | 'ilike'): SqlOperationDescriptor {\n return {\n self: { codecId: CIPHERSTASH_STRING_CODEC_ID },\n impl: (self: Expression<ScopeField>, value: unknown): Expression<PgBoolReturn> => {\n const selfAst = toExpr(self);\n return buildOperation({\n method: publicMethod,\n args: [selfAst, asEncryptedParam(selfAst, value)],\n returns: { codecId: PG_BOOL_CODEC_ID, nullable: false },\n lowering: {\n targetFamily: 'sql',\n strategy: 'function',\n template: `eql_v2.${eqlFunction}({{self}}, {{arg0}})`,\n },\n });\n },\n };\n}\n\n/**\n * Cipherstash`s query-operations contributions. Wired into the\n * runtime descriptor by `createCipherstashRuntimeDescriptor` and read\n * by the SQL runtime`s `extractCodecLookup` / `queryOperations`\n * aggregation (`packages/2-sql/5-runtime/src/sql-context.ts`). Two\n * descriptors today:\n *\n * - `cipherstashEq` — encrypted equality via EQL`s `unique` index.\n * SQL: `eql_v2.eq(\"col\", $1::eql_v2_encrypted)`.\n * - `cipherstashIlike` — encrypted free-text match via EQL`s\n * `match` index. SQL:\n * `eql_v2.ilike(\"col\", $1::eql_v2_encrypted)`.\n *\n * Both descriptors register `self: { codecId: 'cipherstash/string@1' }`\n * so the model accessor only attaches them to columns whose codec id\n * is exactly `cipherstash/string@1`. The method names are\n * intentionally cipherstash-prefixed so they coexist with the\n * framework`s `eq` / `ilike` registrations rather than overriding\n * them — see the `Why unique method names` section in this file`s\n * top-level docblock.\n */\nexport function cipherstashQueryOperations(): SqlOperationDescriptors {\n return {\n cipherstashEq: eqlOperator('cipherstashEq', 'eq'),\n cipherstashIlike: eqlOperator('cipherstashIlike', 'ilike'),\n };\n}\n","/**\n * Cipherstash storage codec runtime — wraps the `EncryptedString`\n * envelope at the SQL codec boundary.\n *\n * Responsibilities are intentionally thin:\n *\n * - `decode(wire, ctx)` constructs a fresh envelope carrying the wire\n * ciphertext + the cell's `(table, column)` from `ctx.column` + the\n * SDK reference captured at codec construction time. The envelope's\n * `decrypt({signal?})` later routes through the captured SDK; callers\n * can also `await decryptAll(rows)` to coalesce decrypts across many\n * envelopes into one bulk SDK call.\n *\n * - `encode(envelope, ctx)` extracts the ciphertext from the envelope's\n * handle. The bulk-encrypt middleware populates the ciphertext slot\n * before the codec runs; an envelope whose ciphertext\n * slot is empty at encode time is a programmer error (the middleware\n * was not registered, or this codec instance was used in a non-\n * cipherstash context).\n *\n * The wire format wraps the SDK's JSON ciphertext payload in the\n * Postgres composite literal `(\"...escaped JSON...\")` because EQL\n * defines `eql_v2_encrypted` as `CREATE TYPE eql_v2_encrypted AS (data\n * jsonb)`, not as a domain over jsonb. The default `pg` driver encodes\n * JS objects as JSON which Postgres then rejects when coercing into the\n * composite. Mirrors the reference Drizzle integration at\n * `reference/cipherstash/.../drizzle/src/pg/index.ts`.\n *\n * The codec captures the SDK at construction time, so multi-tenant\n * deployments construct one extension instance per tenant — each with\n * its own SDK — rather than sharing a module-singleton codec.\n */\n\nimport type { JsonValue } from '@prisma-next/contract/types';\nimport { type AnyCodecDescriptor, CodecImpl } from '@prisma-next/framework-components/codec';\nimport type { Codec, SqlCodecCallContext } from '@prisma-next/sql-relational-core/ast';\nimport { CIPHERSTASH_STRING_CODEC_ID } from '../extension-metadata/constants';\nimport { EncryptedString } from './envelope';\nimport type { CipherstashSdk } from './sdk';\n\nconst CIPHERSTASH_STRING_TARGET_TYPE = 'eql_v2_encrypted' as const;\n// Cipherstash columns intentionally declare no codec traits.\n//\n// The framework's `equality` trait gates the built-in `eq` / `neq` /\n// `in` / `notIn` comparison methods (see `COMPARISON_METHODS_META` in\n// `packages/3-extensions/sql-orm-client/src/types.ts`). Those built-\n// ins lower to standard SQL `=` / `!=` / `IN`, which is wrong for\n// cipherstash columns because EQL ciphers contain randomized nonces\n// and do not byte-equal under `=`. Declaring `equality` here would\n// silently expose the wrong-SQL footgun; declaring `[]` makes\n// `email.eq(...)` undefined at the column accessor and forces callers\n// onto the cipherstash-namespaced operator surface\n// (`email.cipherstashEq(...)` — see `./operators.ts`). The trait\n// declaration is regression-pinned by `test/equality-trait-removal.test.ts`.\n//\n// The user-visible `EncryptedString({ equality: true })` flag in PSL\n// / TS authoring is unrelated to this codec trait — it controls\n// whether the codec lifecycle hook contributes a per-column search-\n// config migration op for the column's `unique` index. The two\n// `equality` concepts share only their name.\nconst CIPHERSTASH_STRING_TRAITS = [] as const;\n\n/**\n * Encode the SDK ciphertext payload as a Postgres composite literal\n * `(\"...escaped JSON...\")`. Embedded `\"` are doubled per the composite\n * text-format escape rules.\n */\nfunction encodeEqlV2EncryptedWire(payload: unknown): string {\n const json = JSON.stringify(payload);\n if (json === undefined) {\n throw new Error(\n 'cipherstash codec: ciphertext payload is not JSON-serializable. ' +\n 'The CipherStash SDK must return a JSON-encodable bulk-encrypt result.',\n );\n }\n const escaped = json.replaceAll('\"', '\"\"');\n return `(\"${escaped}\")`;\n}\n\n/**\n * Inverse of {@link encodeEqlV2EncryptedWire}. Postgres returns\n * `eql_v2_encrypted` cells in composite text format; some pg clients\n * pre-parse composite cells into `{ data: ... }` row objects. Both\n * shapes — and `null`/`undefined` passthrough — are accepted.\n */\nfunction decodeEqlV2EncryptedWire(wire: unknown): unknown {\n if (wire === null || wire === undefined) return wire;\n if (typeof wire === 'object') {\n if ('data' in wire) {\n return (wire as { data: unknown }).data;\n }\n return wire;\n }\n if (typeof wire !== 'string') {\n throw new Error(\n `cipherstash codec: unexpected wire shape for eql_v2_encrypted: ${typeof wire}`,\n );\n }\n const trimmed = wire.trim();\n if (!trimmed.startsWith('(') || !trimmed.endsWith(')')) {\n throw new Error(\n `cipherstash codec: expected composite literal \"(...)\" but got: ${trimmed.slice(0, 40)}`,\n );\n }\n const inner = trimmed.slice(1, -1);\n const unquoted =\n inner.startsWith('\"') && inner.endsWith('\"') ? inner.slice(1, -1).replaceAll('\"\"', '\"') : inner;\n return JSON.parse(unquoted);\n}\n\nexport class CipherstashStringCodec\n extends CodecImpl<\n typeof CIPHERSTASH_STRING_CODEC_ID,\n typeof CIPHERSTASH_STRING_TRAITS,\n unknown,\n EncryptedString\n >\n implements\n Codec<\n typeof CIPHERSTASH_STRING_CODEC_ID,\n typeof CIPHERSTASH_STRING_TRAITS,\n unknown,\n EncryptedString\n >\n{\n readonly sdk: CipherstashSdk | undefined;\n\n constructor(descriptor: AnyCodecDescriptor, sdk: CipherstashSdk | undefined) {\n super(descriptor);\n this.sdk = sdk;\n }\n\n async encode(value: EncryptedString, _ctx: SqlCodecCallContext): Promise<unknown> {\n const handle = value.expose();\n if (handle.ciphertext === undefined) {\n throw new Error(\n 'cipherstash codec: envelope has no ciphertext at encode time. ' +\n 'Register the bulk-encrypt middleware in the runtime so envelopes are encrypted before encoding.',\n );\n }\n return encodeEqlV2EncryptedWire(handle.ciphertext);\n }\n\n async decode(wire: unknown, ctx: SqlCodecCallContext): Promise<EncryptedString> {\n if (this.sdk === undefined) {\n throw new Error(\n 'cipherstash codec: decode called on the metadata-only codec instance. ' +\n 'Construct a runtime descriptor via `createCipherstashRuntimeDescriptor({ sdk })` and use that instead.',\n );\n }\n const column = ctx.column;\n if (!column) {\n throw new Error(\n 'cipherstash codec: decode requires ctx.column to construct a routing-aware envelope. ' +\n 'The SQL runtime populates ctx.column for projected columns; aggregate/computed cells are not supported by this codec.',\n );\n }\n return EncryptedString.fromInternal({\n ciphertext: decodeEqlV2EncryptedWire(wire),\n table: column.table,\n column: column.name,\n sdk: this.sdk,\n });\n }\n\n encodeJson(_value: EncryptedString): JsonValue {\n return { $encryptedString: '<opaque>' };\n }\n\n decodeJson(_json: JsonValue): EncryptedString {\n throw new Error(\n 'cipherstash codec: decodeJson is not supported; envelopes do not round-trip through JSON.',\n );\n }\n}\n\n/**\n * Variance-erased descriptor placeholder used by `createCipherstashStringCodec`\n * so legacy callers that need a bare `Codec` instance (e.g. extension control-\n * plane wiring that built up a flat list of codecs) can keep constructing one\n * directly. Production runtime descriptors should resolve the per-instance\n * codec through `CipherstashStringDescriptor.factory(params)(ctx)`.\n */\nconst FALLBACK_DESCRIPTOR: AnyCodecDescriptor = {\n codecId: CIPHERSTASH_STRING_CODEC_ID,\n traits: CIPHERSTASH_STRING_TRAITS,\n targetTypes: [CIPHERSTASH_STRING_TARGET_TYPE],\n meta: {\n db: { sql: { postgres: { nativeType: CIPHERSTASH_STRING_TARGET_TYPE } } },\n },\n paramsSchema: {\n '~standard': {\n version: 1,\n vendor: 'cipherstash',\n validate: (value: unknown) => ({ value }),\n },\n },\n isParameterized: false,\n renderOutputType: () => 'EncryptedString',\n factory: () => () => {\n throw new Error('cipherstash codec: fallback descriptor factory is not callable');\n },\n};\n\nexport function createCipherstashStringCodec(sdk: CipherstashSdk): CipherstashStringCodec {\n return new CipherstashStringCodec(FALLBACK_DESCRIPTOR, sdk);\n}\n\nexport { CIPHERSTASH_STRING_CODEC_ID };\n","/**\n * `RuntimeParameterizedCodecDescriptor` for the cipherstash storage\n * codec — the post-#402 unified `CodecDescriptor<P>` shape consumed by\n * the SQL runtime via `SqlStaticContributions.parameterizedCodecs()`.\n *\n * Mirrors pgvector's `vectorParamsSchema` + `vectorFactory` precedent\n * (`packages/3-extensions/pgvector/src/exports/runtime.ts`). Cipherstash\n * differs from pgvector in one respect: the codec depends on the SDK\n * (read-side single-cell `decrypt`, the bulk-encrypt middleware), so\n * each `createParameterizedCodecDescriptors(sdk)` call produces its\n * own descriptor list closed over its SDK so multi-tenant\n * deployments can side-by-side multiple cipherstash extensions without\n * cross-talk.\n *\n * The factory is per-cell stateless across `(equality, freeTextSearch)`\n * params on the write side (encode reads ciphertext from the handle,\n * independent of the search-mode flags) — search-mode flags only affect\n * operator lowering and the codec lifecycle hook on the control plane.\n * The factory therefore returns the same shared codec\n * for every params instance, mirroring pgvector's `vectorFactory`. When\n * future per-instance state (e.g. decode-time index gating) lands, the\n * closure is the place to add it.\n */\n\nimport type { CodecInstanceContext } from '@prisma-next/framework-components/codec';\nimport type { RuntimeParameterizedCodecDescriptor } from '@prisma-next/sql-runtime';\nimport { type as arktype } from 'arktype';\nimport { CIPHERSTASH_STRING_CODEC_ID, createCipherstashStringCodec } from './codec-runtime';\nimport type { CipherstashSdk } from './sdk';\n\nexport interface CipherstashStringParams {\n readonly equality: boolean;\n readonly freeTextSearch: boolean;\n}\n\nexport const encryptedStringParamsSchema = arktype({\n equality: 'boolean',\n freeTextSearch: 'boolean',\n});\n\nexport function renderEncryptedStringOutputType(_params: CipherstashStringParams): string {\n return 'EncryptedString';\n}\n\nexport function createParameterizedCodecDescriptors(\n sdk: CipherstashSdk,\n): ReadonlyArray<RuntimeParameterizedCodecDescriptor<CipherstashStringParams>> {\n const sharedCodec = createCipherstashStringCodec(sdk);\n const factory = (_params: CipherstashStringParams) => (_ctx: CodecInstanceContext) => sharedCodec;\n return [\n {\n codecId: CIPHERSTASH_STRING_CODEC_ID,\n // Empty traits — equality search on cipherstash columns goes\n // through the cipherstash-namespaced operator (`cipherstashEq`\n // in `./operators.ts`), not the framework`s trait-gated built-in\n // `eq`. See `./codec-runtime.ts` for the full rationale.\n traits: [] as const,\n targetTypes: ['eql_v2_encrypted'] as const,\n // Postgres native-type metadata. The SQL renderer reads this off\n // the descriptor via `codecLookup.metaFor(codecId)` to insert the\n // `$N::eql_v2_encrypted` cast on bound params (the EQL composite\n // type isn`t inferrable from a `text` literal, so the cast is\n // load-bearing).\n meta: { db: { sql: { postgres: { nativeType: 'eql_v2_encrypted' } } } },\n paramsSchema: encryptedStringParamsSchema,\n isParameterized: true as const,\n renderOutputType: renderEncryptedStringOutputType,\n factory,\n },\n ] as const satisfies ReadonlyArray<RuntimeParameterizedCodecDescriptor<CipherstashStringParams>>;\n}\n","/**\n * `decryptAll` — read-side bulk-decrypt walker.\n *\n * Public utility users invoke after `findMany` (or any other read\n * surface) to materialize the plaintext for every `EncryptedString`\n * envelope reachable from the result set in a fixed number of bulk SDK\n * round-trips:\n *\n * const rows = await db.select(...).from(User).execute();\n * await decryptAll(rows);\n * // every envelope's `decrypt()` now returns plaintext synchronously.\n *\n * Why a separate utility (rather than middleware that auto-decrypts on\n * every read): the framework`s streaming-read path cannot bulk-amortize\n * decryption across rows it`s yielding incrementally — by the time row\n * N is yielded, rows 1..N-1 have already been delivered to the caller.\n * The `decryptAll` shape lets the caller buffer the result set\n * explicitly (with `await stream.toArray()`) and then opt into bulk\n * decryption in one round-trip per `(table, column)` group. The runtime\n * descriptor wrapper deliberately does NOT register an implicit-decrypt\n * middleware for this reason.\n *\n * **Walker shape**.\n *\n * - Recursive on plain objects + plain arrays only. Date / Map / Set /\n * typed arrays / Buffer / function / etc. are not recursed into:\n * cipherstash envelopes are user data and would not normally embed\n * inside these host containers; if a future caller needs to bulk-\n * decrypt envelopes inside such a container they extract them into a\n * plain row first. The narrow scope keeps the walker`s behavior\n * trivially predictable and avoids the cycle / iterator / lazy-eval\n * surface those exotic types bring.\n * - Cycle-safe via a `WeakSet` of visited objects/arrays; the same\n * envelope appearing in N positions is collected once.\n * - Skips envelopes whose plaintext slot is already populated\n * (write-side envelopes from `EncryptedString.from(plaintext)`, or\n * read-side envelopes already materialized by a prior\n * `decrypt()` / `decryptAll(...)`). The skip means a re-run is a\n * no-op and a mixed write/read row tree only round-trips for the\n * envelopes that need it.\n *\n * **Grouping**. Envelopes are grouped by `(sdk, table, column)` —\n * routing key plus the envelope handle`s SDK reference. The SDK split\n * preserves the per-tenant SDK isolation `runtime.ts`'s docblock spells\n * out: each tenant constructs its own runtime descriptor with its own\n * SDK so per-tenant key material never crosses runtimes. Envelopes from\n * different tenants happening to share `(table, column)` therefore\n * still receive separate `bulkDecrypt` calls.\n *\n * **Cancellation**. `opts.signal` is forwarded by identity to every\n * `bulkDecrypt` call via `ifDefined` — the same shape the bulk-encrypt\n * middleware and `EncryptedString.decrypt({ signal? })` use. The\n * walker also races each SDK promise against `opts.signal` via\n * `raceCipherstashAbort` so an abort surfaces `RUNTIME.ABORTED\n * { phase: 'decrypt-all' }` promptly even when the SDK body itself\n * ignores the signal. A pre-check before the first SDK round-trip\n * short-circuits when the signal is already aborted at entry; the\n * no-envelopes-reachable fast path returns immediately without\n * observing the signal.\n */\n\nimport { ifDefined } from '@prisma-next/utils/defined';\nimport { checkCipherstashAborted, raceCipherstashAbort } from './abort';\nimport { EncryptedString, isHandleDecrypted, setHandlePlaintextCache } from './envelope';\nimport type { CipherstashRoutingKey, CipherstashSdk } from './sdk';\n\nexport interface DecryptAllOptions {\n readonly signal?: AbortSignal;\n}\n\ninterface BulkDecryptTarget {\n readonly envelope: EncryptedString;\n readonly ciphertext: unknown;\n readonly sdk: CipherstashSdk;\n readonly routingKey: CipherstashRoutingKey;\n}\n\n/**\n * Walk a result set and bulk-decrypt every `EncryptedString` envelope\n * reachable from it. After the returned promise resolves, every touched\n * envelope's `decrypt()` returns the cached plaintext synchronously\n * without consulting the SDK.\n *\n * The walker is a no-op when no envelopes are reachable (returns\n * without making any SDK call), so it is cheap to call defensively\n * after queries that may or may not contain encrypted columns.\n */\nexport async function decryptAll(rows: unknown, opts?: DecryptAllOptions): Promise<void> {\n const targets = collectTargets(rows);\n if (targets.length === 0) {\n return;\n }\n const groups = groupTargets(targets);\n for (const group of groups.values()) {\n const first = group[0];\n if (!first) continue;\n const ciphertexts = group.map((t) => t.ciphertext);\n checkCipherstashAborted(opts?.signal, 'decrypt-all');\n const plaintexts = await raceCipherstashAbort(\n first.sdk.bulkDecrypt({\n routingKey: first.routingKey,\n ciphertexts,\n ...ifDefined('signal', opts?.signal),\n }),\n opts?.signal,\n 'decrypt-all',\n );\n if (plaintexts.length !== group.length) {\n throw new Error(\n `cipherstash decryptAll: SDK returned ${plaintexts.length} plaintexts ` +\n `for routing key (${first.routingKey.table}, ${first.routingKey.column}) ` +\n `but ${group.length} were requested.`,\n );\n }\n for (let i = 0; i < group.length; i++) {\n const target = group[i];\n const plaintext = plaintexts[i];\n if (!target || plaintext === undefined) continue;\n setHandlePlaintextCache(target.envelope, plaintext);\n }\n }\n}\n\nfunction collectTargets(root: unknown): BulkDecryptTarget[] {\n const targets: BulkDecryptTarget[] = [];\n const seenObjects = new WeakSet<object>();\n const seenEnvelopes = new WeakSet<EncryptedString>();\n visit(root, seenObjects, (envelope) => {\n if (seenEnvelopes.has(envelope)) return;\n seenEnvelopes.add(envelope);\n if (isHandleDecrypted(envelope)) return;\n const handle = envelope.expose();\n if (handle.table === undefined || handle.column === undefined) {\n throw new Error(\n 'cipherstash decryptAll: envelope is missing (table, column) routing context. ' +\n 'Read-side envelopes constructed via codec.decode always carry routing context; ' +\n 'this typically means the envelope was constructed manually outside the codec path.',\n );\n }\n if (handle.sdk === undefined) {\n throw new Error(\n 'cipherstash decryptAll: envelope is missing the SDK reference needed to decrypt. ' +\n 'Read-side envelopes constructed via codec.decode always carry an SDK reference; ' +\n 'this typically means the envelope was constructed manually outside the codec path.',\n );\n }\n targets.push({\n envelope,\n ciphertext: handle.ciphertext,\n sdk: handle.sdk,\n routingKey: { table: handle.table, column: handle.column },\n });\n });\n return targets;\n}\n\nfunction visit(\n value: unknown,\n seen: WeakSet<object>,\n found: (envelope: EncryptedString) => void,\n): void {\n if (value === null || value === undefined) return;\n if (value instanceof EncryptedString) {\n found(value);\n return;\n }\n if (typeof value !== 'object') return;\n if (seen.has(value)) return;\n // Walker is intentionally scoped to plain arrays + plain objects.\n // Date / Map / Set / typed arrays / Buffer / Error / class instances\n // are passed over so the walker`s shape stays trivially predictable\n // and immune to host-object iterator surprises.\n if (Array.isArray(value)) {\n seen.add(value);\n for (const item of value) {\n visit(item, seen, found);\n }\n return;\n }\n if (!isPlainObject(value)) {\n return;\n }\n seen.add(value);\n for (const key of Object.keys(value)) {\n visit((value as Record<string, unknown>)[key], seen, found);\n }\n}\n\nfunction isPlainObject(value: object): boolean {\n const proto = Object.getPrototypeOf(value);\n return proto === null || proto === Object.prototype;\n}\n\nfunction groupTargets(targets: ReadonlyArray<BulkDecryptTarget>): Map<string, BulkDecryptTarget[]> {\n // Group by `(sdk identity, table, column)`. The SDK identity portion\n // of the key uses a per-SDK index issued on first encounter so\n // grouping never depends on object reference equality colliding\n // accidentally (different SDK instances always partition into\n // different groups even if their `(table, column)` matches).\n const sdkIndex = new Map<CipherstashSdk, number>();\n const groups = new Map<string, BulkDecryptTarget[]>();\n for (const target of targets) {\n let idx = sdkIndex.get(target.sdk);\n if (idx === undefined) {\n idx = sdkIndex.size;\n sdkIndex.set(target.sdk, idx);\n }\n const id = `${idx}\\u0000${target.routingKey.table}\\u0000${target.routingKey.column}`;\n let group = groups.get(id);\n if (!group) {\n group = [];\n groups.set(id, group);\n }\n group.push(target);\n }\n return groups;\n}\n","/**\n * Runtime-plane entry point for the CipherStash extension.\n *\n * Consumed at query time by application runtimes that need to encode /\n * decode `cipherstash/string@1` columns (envelope class) and talk to the\n * CipherStash SDK shape the codec runtime + bulk-encrypt middleware\n * depend on.\n *\n * The runtime entry point is deliberately separate from `./control`\n * (descriptor, codec lifecycle hook, contract-space artefacts) so apps\n * that only emit migrations against cipherstash never load the runtime,\n * and apps that only run queries never load the migration-time\n * descriptor — the control plane and runtime plane are tree-shakable\n * along this seam.\n *\n * `createCipherstashRuntimeDescriptor({ sdk })` is the recommended\n * composition entry — it bundles the SDK-bound codec, the parameterized\n * codec descriptor, and the runtime-plane `codecInstances` slot into a\n * single `SqlRuntimeExtensionDescriptor<'postgres'>` mirroring\n * pgvector's `runtime.ts` precedent. The bulk-encrypt middleware ships\n * separately at `@prisma-next/extension-cipherstash/middleware` because\n * `SqlRuntimeExtensionDescriptor` does not own a middleware slot;\n * consumers register it via `createRuntime({ middleware:\n * [bulkEncryptMiddleware(sdk)] })`.\n */\n\nimport type { SqlRuntimeExtensionDescriptor } from '@prisma-next/sql-runtime';\nimport { cipherstashQueryOperations } from '../execution/operators';\nimport { createParameterizedCodecDescriptors } from '../execution/parameterized';\nimport type { CipherstashSdk } from '../execution/sdk';\nimport {\n CIPHERSTASH_EXTENSION_VERSION,\n CIPHERSTASH_SPACE_ID,\n} from '../extension-metadata/constants';\n\nexport type { CipherstashStringCodec } from '../execution/codec-runtime';\nexport {\n CIPHERSTASH_STRING_CODEC_ID,\n createCipherstashStringCodec,\n} from '../execution/codec-runtime';\nexport type { DecryptAllOptions } from '../execution/decrypt-all';\nexport { decryptAll } from '../execution/decrypt-all';\nexport type {\n EncryptedStringFromInternalArgs,\n EncryptedStringHandle,\n} from '../execution/envelope';\nexport { EncryptedString } from '../execution/envelope';\nexport type { CipherstashStringParams } from '../execution/parameterized';\nexport {\n createParameterizedCodecDescriptors,\n encryptedStringParamsSchema,\n renderEncryptedStringOutputType,\n} from '../execution/parameterized';\nexport type {\n CipherstashBulkDecryptArgs,\n CipherstashBulkEncryptArgs,\n CipherstashRoutingKey,\n CipherstashSdk,\n CipherstashSingleDecryptArgs,\n} from '../execution/sdk';\n\nexport { CIPHERSTASH_EXTENSION_VERSION };\n\nexport interface CreateCipherstashRuntimeDescriptorOptions {\n readonly sdk: CipherstashSdk;\n}\n\n/**\n * Compose the SDK-bound codec runtime + parameterized codec descriptors\n * + runtime-plane codec-instances metadata into a single\n * `SqlRuntimeExtensionDescriptor<'postgres'>`.\n *\n * The descriptor is per-SDK: cipherstash's codec captures the SDK at\n * `decode` time (read-side single-cell `decrypt`) and the bulk-encrypt\n * middleware captures it at `beforeExecute` time (write-side bulk\n * round-trip). Multi-tenant deployments construct one descriptor per\n * tenant SDK so per-tenant key material never crosses runtimes.\n *\n * Mirrors `packages/3-extensions/pgvector/src/exports/runtime.ts` —\n * pgvector's vectorRuntimeDescriptor is a static default-export because\n * its codec is fully stateless; cipherstash needs the factory wrapper\n * because the codec depends on `sdk`.\n */\nexport function createCipherstashRuntimeDescriptor(\n opts: CreateCipherstashRuntimeDescriptorOptions,\n): SqlRuntimeExtensionDescriptor<'postgres'> {\n const { sdk } = opts;\n const parameterizedDescriptors = createParameterizedCodecDescriptors(sdk);\n\n return {\n kind: 'extension' as const,\n id: CIPHERSTASH_SPACE_ID,\n version: CIPHERSTASH_EXTENSION_VERSION,\n familyId: 'sql' as const,\n targetId: 'postgres' as const,\n types: {\n codecTypes: {\n codecDescriptors: parameterizedDescriptors,\n },\n },\n codecs: () => parameterizedDescriptors,\n queryOperations: () => cipherstashQueryOperations(),\n create() {\n return {\n familyId: 'sql' as const,\n targetId: 'postgres' as const,\n };\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,67 @@
1
+ //#region src/execution/sdk.d.ts
2
+ /**
3
+ * Framework-native shape for the CipherStash SDK that the cipherstash
4
+ * extension wraps.
5
+ *
6
+ * The first-attempt SDK (see `reference/cipherstash/stack/...`) is rich
7
+ * and Prisma-adapter shaped. The framework-native shape consumed by the
8
+ * codec runtime, the bulk-encrypt middleware, and `decryptAll` is
9
+ * intentionally smaller — three async methods that each map cleanly to
10
+ * one CipherStash bulk-call shape:
11
+ *
12
+ * - `decrypt` — single-cell read used by `EncryptedString#decrypt()`
13
+ * when the user opts out of bulk decryption.
14
+ * - `bulkEncrypt` — write-side coalesced encrypt; the bulk-encrypt
15
+ * middleware calls this from `beforeExecute`.
16
+ * - `bulkDecrypt` — read-side coalesced decrypt; `decryptAll` calls
17
+ * this from a recursive walker.
18
+ *
19
+ * Each method accepts an optional `AbortSignal`. Cancellation is forwarded
20
+ * directly to the SDK (the per-execute `MiddlewareContext.signal` from
21
+ * the middleware-param-transform seam, or the caller-supplied signal on
22
+ * `decrypt({signal})`).
23
+ */
24
+ /**
25
+ * Routing-key tuple used by `bulkEncrypt`/`bulkDecrypt` to group requests
26
+ * so each ZeroKMS round-trip handles one homogeneous batch. Routing key
27
+ * is `(table, column)`.
28
+ */
29
+ interface CipherstashRoutingKey {
30
+ readonly table: string;
31
+ readonly column: string;
32
+ }
33
+ interface CipherstashSingleDecryptArgs {
34
+ /**
35
+ * The wire ciphertext to decrypt. Opaque to the framework; the SDK
36
+ * inspects the embedded `i.t` / `i.c` schema markers to pick the
37
+ * right `cast_as` for the round-trip.
38
+ */
39
+ readonly ciphertext: unknown;
40
+ readonly table: string;
41
+ readonly column: string;
42
+ readonly signal?: AbortSignal;
43
+ }
44
+ interface CipherstashBulkEncryptArgs {
45
+ readonly routingKey: CipherstashRoutingKey;
46
+ readonly values: ReadonlyArray<string>;
47
+ readonly signal?: AbortSignal;
48
+ }
49
+ interface CipherstashBulkDecryptArgs {
50
+ readonly routingKey: CipherstashRoutingKey;
51
+ readonly ciphertexts: ReadonlyArray<unknown>;
52
+ readonly signal?: AbortSignal;
53
+ }
54
+ /**
55
+ * The framework-native CipherStash SDK contract consumed by the envelope,
56
+ * codec, middleware, and `decryptAll` surfaces. Real implementations wrap
57
+ * a CipherStash `EncryptionClient`; tests construct mock SDKs that
58
+ * implement these three methods directly.
59
+ */
60
+ interface CipherstashSdk {
61
+ decrypt(args: CipherstashSingleDecryptArgs): Promise<string>;
62
+ bulkEncrypt(args: CipherstashBulkEncryptArgs): Promise<ReadonlyArray<unknown>>;
63
+ bulkDecrypt(args: CipherstashBulkDecryptArgs): Promise<ReadonlyArray<string>>;
64
+ }
65
+ //#endregion
66
+ export { CipherstashSingleDecryptArgs as a, CipherstashSdk as i, CipherstashBulkEncryptArgs as n, CipherstashRoutingKey as r, CipherstashBulkDecryptArgs as t };
67
+ //# sourceMappingURL=sdk-D5FTGyzp.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk-D5FTGyzp.d.mts","names":[],"sources":["../src/execution/sdk.ts"],"mappings":";;AA4BA;;;;;AAKA;;;;;;;;;;;AAYA;;;;;;;;;;UAjBiB,qBAAA;EAAA,SACN,KAAA;EAAA,SACA,MAAA;AAAA;AAAA,UAGM,4BAAA;EAec;AAG/B;;;;EAH+B,SATpB,UAAA;EAAA,SACA,KAAA;EAAA,SACA,MAAA;EAAA,SACA,MAAA,GAAS,WAAA;AAAA;AAAA,UAGH,0BAAA;EAAA,SACN,UAAA,EAAY,qBAAA;EAAA,SACZ,MAAA,EAAQ,aAAA;EAAA,SACR,MAAA,GAAS,WAAA;AAAA;AAAA,UAGH,0BAAA;EAAA,SACN,UAAA,EAAY,qBAAA;EAAA,SACZ,WAAA,EAAa,aAAA;EAAA,SACb,MAAA,GAAS,WAAA;AAAA;;;;;;;UASH,cAAA;EACf,OAAA,CAAQ,IAAA,EAAM,4BAAA,GAA+B,OAAA;EAC7C,WAAA,CAAY,IAAA,EAAM,0BAAA,GAA6B,OAAA,CAAQ,aAAA;EACvD,WAAA,CAAY,IAAA,EAAM,0BAAA,GAA6B,OAAA,CAAQ,aAAA;AAAA"}
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@prisma-next/extension-cipherstash",
3
+ "version": "0.0.1",
4
+ "license": "Apache-2.0",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "description": "CipherStash EQL extension for Prisma Next: contract-space authoring of the encrypted-column scaffolding (eql_v2_configuration table, eql_v2_encrypted/ore_* composite types, eql_v2 domains) plus a baseline migration that installs the vendored EQL bundle SQL byte-for-byte.",
8
+ "scripts": {
9
+ "build:contract-space": "prisma-next contract emit",
10
+ "build": "tsdown",
11
+ "test": "vitest run",
12
+ "test:coverage": "vitest run --coverage",
13
+ "typecheck": "tsc --project tsconfig.json --noEmit",
14
+ "lint": "biome check . --error-on-warnings",
15
+ "lint:fix": "biome check --write .",
16
+ "lint:fix:unsafe": "biome check --write --unsafe .",
17
+ "clean": "rm -rf dist dist-tsc dist-tsc-prod coverage .tmp-output"
18
+ },
19
+ "dependencies": {
20
+ "@prisma-next/contract": "workspace:*",
21
+ "@prisma-next/family-sql": "workspace:*",
22
+ "@prisma-next/framework-components": "workspace:*",
23
+ "@prisma-next/migration-tools": "workspace:*",
24
+ "@prisma-next/sql-contract": "workspace:*",
25
+ "@prisma-next/sql-operations": "workspace:*",
26
+ "@prisma-next/sql-relational-core": "workspace:*",
27
+ "@prisma-next/sql-runtime": "workspace:*",
28
+ "@prisma-next/ts-render": "workspace:*",
29
+ "@prisma-next/utils": "workspace:*",
30
+ "arktype": "catalog:"
31
+ },
32
+ "devDependencies": {
33
+ "@prisma-next/adapter-postgres": "workspace:*",
34
+ "@prisma-next/cli": "workspace:*",
35
+ "@prisma-next/driver-postgres": "workspace:*",
36
+ "@prisma-next/psl-parser": "workspace:*",
37
+ "@prisma-next/sql-contract-psl": "workspace:*",
38
+ "@prisma-next/sql-contract-ts": "workspace:*",
39
+ "@prisma-next/sql-schema-ir": "workspace:*",
40
+ "@prisma-next/target-postgres": "workspace:*",
41
+ "@prisma-next/test-utils": "workspace:*",
42
+ "@prisma-next/tsconfig": "workspace:*",
43
+ "@prisma-next/tsdown": "workspace:*",
44
+ "pathe": "^2.0.3",
45
+ "tsdown": "catalog:",
46
+ "typescript": "catalog:",
47
+ "vitest": "catalog:"
48
+ },
49
+ "files": [
50
+ "dist",
51
+ "src"
52
+ ],
53
+ "exports": {
54
+ "./column-types": "./dist/column-types.mjs",
55
+ "./control": "./dist/control.mjs",
56
+ "./middleware": "./dist/middleware.mjs",
57
+ "./migration": "./dist/migration.mjs",
58
+ "./operation-types": "./dist/operation-types.mjs",
59
+ "./pack": "./dist/pack.mjs",
60
+ "./runtime": "./dist/runtime.mjs",
61
+ "./package.json": "./package.json"
62
+ },
63
+ "repository": {
64
+ "type": "git",
65
+ "url": "https://github.com/prisma/prisma-next.git",
66
+ "directory": "packages/3-extensions/cipherstash"
67
+ },
68
+ "types": "./dist/control.d.mts"
69
+ }