@prisma-next/extension-cipherstash 0.6.0-dev.5 → 0.6.0-dev.7
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/dist/runtime.mjs +1 -1
- package/dist/runtime.mjs.map +1 -1
- package/package.json +21 -21
- package/src/execution/operators.ts +1 -1
package/dist/runtime.mjs
CHANGED
|
@@ -30,7 +30,7 @@ function asEncryptedParam(selfAst, value) {
|
|
|
30
30
|
const envelope = coerceToEnvelope(value);
|
|
31
31
|
const columnRef = extractColumnRef(selfAst);
|
|
32
32
|
if (columnRef !== void 0) setHandleRoutingKey(envelope, columnRef.table, columnRef.column);
|
|
33
|
-
return ParamRef.of(envelope, { codecId: CIPHERSTASH_STRING_CODEC_ID });
|
|
33
|
+
return ParamRef.of(envelope, { codec: { codecId: CIPHERSTASH_STRING_CODEC_ID } });
|
|
34
34
|
}
|
|
35
35
|
function coerceToEnvelope(value) {
|
|
36
36
|
if (value instanceof EncryptedString) return value;
|
package/dist/runtime.mjs.map
CHANGED
|
@@ -1 +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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"}
|
|
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, { codec: { 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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"}
|
package/package.json
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prisma-next/extension-cipherstash",
|
|
3
|
-
"version": "0.6.0-dev.
|
|
3
|
+
"version": "0.6.0-dev.7",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
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
8
|
"dependencies": {
|
|
9
9
|
"arktype": "^2.1.29",
|
|
10
|
-
"@prisma-next/
|
|
11
|
-
"@prisma-next/
|
|
12
|
-
"@prisma-next/
|
|
13
|
-
"@prisma-next/
|
|
14
|
-
"@prisma-next/
|
|
15
|
-
"@prisma-next/sql-
|
|
16
|
-
"@prisma-next/sql-runtime": "0.6.0-dev.
|
|
17
|
-
"@prisma-next/
|
|
18
|
-
"@prisma-next/
|
|
19
|
-
"@prisma-next/
|
|
10
|
+
"@prisma-next/family-sql": "0.6.0-dev.7",
|
|
11
|
+
"@prisma-next/contract": "0.6.0-dev.7",
|
|
12
|
+
"@prisma-next/framework-components": "0.6.0-dev.7",
|
|
13
|
+
"@prisma-next/migration-tools": "0.6.0-dev.7",
|
|
14
|
+
"@prisma-next/sql-contract": "0.6.0-dev.7",
|
|
15
|
+
"@prisma-next/sql-operations": "0.6.0-dev.7",
|
|
16
|
+
"@prisma-next/sql-runtime": "0.6.0-dev.7",
|
|
17
|
+
"@prisma-next/ts-render": "0.6.0-dev.7",
|
|
18
|
+
"@prisma-next/utils": "0.6.0-dev.7",
|
|
19
|
+
"@prisma-next/sql-relational-core": "0.6.0-dev.7"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
22
|
"pathe": "^2.0.3",
|
|
23
23
|
"tsdown": "0.22.0",
|
|
24
24
|
"typescript": "5.9.3",
|
|
25
25
|
"vitest": "4.1.5",
|
|
26
|
-
"@prisma-next/
|
|
27
|
-
"@prisma-next/
|
|
28
|
-
"@prisma-next/
|
|
29
|
-
"@prisma-next/
|
|
30
|
-
"@prisma-next/sql-contract-psl": "0.6.0-dev.
|
|
31
|
-
"@prisma-next/sql-
|
|
32
|
-
"@prisma-next/sql-
|
|
33
|
-
"@prisma-next/tsconfig": "0.0.0",
|
|
26
|
+
"@prisma-next/adapter-postgres": "0.6.0-dev.7",
|
|
27
|
+
"@prisma-next/cli": "0.6.0-dev.7",
|
|
28
|
+
"@prisma-next/psl-parser": "0.6.0-dev.7",
|
|
29
|
+
"@prisma-next/driver-postgres": "0.6.0-dev.7",
|
|
30
|
+
"@prisma-next/sql-contract-psl": "0.6.0-dev.7",
|
|
31
|
+
"@prisma-next/sql-schema-ir": "0.6.0-dev.7",
|
|
32
|
+
"@prisma-next/sql-contract-ts": "0.6.0-dev.7",
|
|
34
33
|
"@prisma-next/test-utils": "0.0.1",
|
|
35
|
-
"@prisma-next/
|
|
36
|
-
"@prisma-next/
|
|
34
|
+
"@prisma-next/target-postgres": "0.6.0-dev.7",
|
|
35
|
+
"@prisma-next/tsconfig": "0.0.0",
|
|
36
|
+
"@prisma-next/tsdown": "0.0.0"
|
|
37
37
|
},
|
|
38
38
|
"files": [
|
|
39
39
|
"dist",
|
|
@@ -111,7 +111,7 @@ function asEncryptedParam(selfAst: AnyExpression, value: unknown): ParamRef {
|
|
|
111
111
|
if (columnRef !== undefined) {
|
|
112
112
|
setHandleRoutingKey(envelope, columnRef.table, columnRef.column);
|
|
113
113
|
}
|
|
114
|
-
return ParamRef.of(envelope, { codecId: CIPHERSTASH_STRING_CODEC_ID });
|
|
114
|
+
return ParamRef.of(envelope, { codec: { codecId: CIPHERSTASH_STRING_CODEC_ID } });
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
function coerceToEnvelope(value: unknown): EncryptedString {
|