@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.
- package/README.md +153 -0
- package/dist/call-classes-CSvD7w8U.mjs +206 -0
- package/dist/call-classes-CSvD7w8U.mjs.map +1 -0
- package/dist/column-types.d.mts +33 -0
- package/dist/column-types.d.mts.map +1 -0
- package/dist/column-types.mjs +42 -0
- package/dist/column-types.mjs.map +1 -0
- package/dist/constants-BDxL9Pe3.d.mts +22 -0
- package/dist/constants-BDxL9Pe3.d.mts.map +1 -0
- package/dist/constants-B_2TNvUi.mjs +46 -0
- package/dist/constants-B_2TNvUi.mjs.map +1 -0
- package/dist/control.d.mts +7 -0
- package/dist/control.d.mts.map +1 -0
- package/dist/control.mjs +430 -0
- package/dist/control.mjs.map +1 -0
- package/dist/descriptor-meta-BgQfZTAF.mjs +129 -0
- package/dist/descriptor-meta-BgQfZTAF.mjs.map +1 -0
- package/dist/envelope-P9BxfJNr.mjs +271 -0
- package/dist/envelope-P9BxfJNr.mjs.map +1 -0
- package/dist/middleware.d.mts +13 -0
- package/dist/middleware.d.mts.map +1 -0
- package/dist/middleware.mjs +129 -0
- package/dist/middleware.mjs.map +1 -0
- package/dist/migration.d.mts +141 -0
- package/dist/migration.d.mts.map +1 -0
- package/dist/migration.mjs +2 -0
- package/dist/operation-types.d.mts +49 -0
- package/dist/operation-types.d.mts.map +1 -0
- package/dist/operation-types.mjs +1 -0
- package/dist/pack.d.mts +86 -0
- package/dist/pack.d.mts.map +1 -0
- package/dist/pack.mjs +2 -0
- package/dist/runtime.d.mts +207 -0
- package/dist/runtime.d.mts.map +1 -0
- package/dist/runtime.mjs +429 -0
- package/dist/runtime.mjs.map +1 -0
- package/dist/sdk-D5FTGyzp.d.mts +67 -0
- package/dist/sdk-D5FTGyzp.d.mts.map +1 -0
- package/package.json +69 -0
- package/src/contract/authoring.ts +62 -0
- package/src/contract/contract.d.ts +149 -0
- package/src/contract/contract.json +104 -0
- package/src/contract/contract.prisma +46 -0
- package/src/execution/abort.ts +143 -0
- package/src/execution/codec-runtime.ts +209 -0
- package/src/execution/decrypt-all.ts +217 -0
- package/src/execution/envelope.ts +263 -0
- package/src/execution/operators.ts +211 -0
- package/src/execution/parameterized.ts +71 -0
- package/src/execution/routing.ts +93 -0
- package/src/execution/sdk.ts +68 -0
- package/src/exports/column-types.ts +62 -0
- package/src/exports/contract-space-typing.ts +86 -0
- package/src/exports/control.ts +120 -0
- package/src/exports/middleware.ts +24 -0
- package/src/exports/migration.ts +43 -0
- package/src/exports/operation-types.ts +16 -0
- package/src/exports/pack.ts +13 -0
- package/src/exports/runtime.ts +110 -0
- package/src/extension-metadata/codec-metadata.ts +81 -0
- package/src/extension-metadata/constants.ts +70 -0
- package/src/extension-metadata/descriptor-meta.ts +76 -0
- package/src/middleware/bulk-encrypt.ts +192 -0
- package/src/migration/call-classes.ts +350 -0
- package/src/migration/cipherstash-codec.ts +157 -0
- package/src/migration/eql-bundle.ts +29 -0
- package/src/migration/eql-install.generated.ts +5751 -0
- package/src/types/operation-types.ts +81 -0
package/README.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# @prisma-next/extension-cipherstash
|
|
2
|
+
|
|
3
|
+
[CipherStash](https://cipherstash.com) extension for Prisma Next: searchable application-layer encryption for Postgres via the EQL bundle.
|
|
4
|
+
|
|
5
|
+
## What this package provides
|
|
6
|
+
|
|
7
|
+
- `EncryptedString` envelope + `cipherstash/string@1` codec runtime — the runtime side of the field-level encryption.
|
|
8
|
+
- `cipherstash.EncryptedString()` PSL constructor and `encryptedString()` TS contract factory (byte-identical lowering). Both accept an optional `{ equality?, freeTextSearch? }` object whose flags default to `true`.
|
|
9
|
+
- `SqlControlExtensionDescriptor` carrying the EQL contract space (the `eql_v2_configuration` table, the `eql_v2_encrypted` / `ore_*` composite types, the `eql_v2` domains) plus a baseline migration that installs the vendored EQL bundle SQL.
|
|
10
|
+
- `bulkEncryptMiddleware(sdk)` — coalesces `EncryptedString` parameters into one `bulkEncrypt` SDK call per `(table, column)` before the wire encode.
|
|
11
|
+
- `cipherstashEq(value)` / `cipherstashIlike(pattern)` query operations — lower to `eql_v2.eq(...)` / `eql_v2.ilike(...)` on cipherstash columns.
|
|
12
|
+
- `decryptAll(rows, opts?)` — opt-in read-side amortization that walks a result set, coalesces every `EncryptedString` it finds into one `bulkDecrypt` SDK call per `(table, column)` group, and caches the resolved plaintexts back onto each envelope so subsequent `envelope.decrypt()` calls return synchronously without consulting the SDK.
|
|
13
|
+
|
|
14
|
+
Search operators are deliberately exposed under the cipherstash-namespaced names `cipherstashEq` and `cipherstashIlike` rather than the framework's built-in `eq` / `ilike`. The cipherstash codec declares no `equality` codec trait, so the framework's trait-gated `eq` is not reachable on cipherstash columns — calling `email.eq(...)` on a cipherstash column is `undefined` (typechecks as a no-such-method error). Equality search uses `email.cipherstashEq(value)`, which lowers to `eql_v2.eq(...)`; free-text search uses `email.cipherstashIlike(pattern)` lowering to `eql_v2.ilike(...)`. The user-facing `EncryptedString({ equality: true })` flag is unrelated to the codec trait — it controls whether the codec lifecycle hook emits an `add_search_config` op for the column's `unique` index at migration time.
|
|
15
|
+
|
|
16
|
+
## Subpath exports
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
| Subpath | Purpose |
|
|
20
|
+
| ---------------- | ------------------------------------------------------------- |
|
|
21
|
+
| `./control` | `SqlControlExtensionDescriptor` (contract space + pack meta) |
|
|
22
|
+
| `./runtime` | `EncryptedString` envelope + `CipherstashSdk` + codec runtime + `decryptAll` |
|
|
23
|
+
| `./middleware` | `bulkEncryptMiddleware(sdk)` |
|
|
24
|
+
| `./pack` | `cipherstashPackMeta` for TS contract authoring |
|
|
25
|
+
| `./column-types` | `encryptedString({ equality?, freeTextSearch? })` TS factory |
|
|
26
|
+
|
|
27
|
+
The `./control` and `./runtime` / `./middleware` planes are tree-shakable: a runtime consumer never pulls the EQL bundle SQL or the codec lifecycle hook, and a control-plane consumer never pulls the SDK interface, the codec runtime, or the bulk-encrypt middleware. See [`DEVELOPING.md`](./DEVELOPING.md) — design choices.
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## Configuration
|
|
31
|
+
|
|
32
|
+
Add the extension to your `prisma-next.config.ts`:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { defineConfig } from '@prisma-next/cli/config-types';
|
|
36
|
+
import postgresAdapter from '@prisma-next/adapter-postgres/control';
|
|
37
|
+
import sql from '@prisma-next/family-sql/control';
|
|
38
|
+
import postgres from '@prisma-next/target-postgres/control';
|
|
39
|
+
import cipherstash from '@prisma-next/extension-cipherstash/control';
|
|
40
|
+
|
|
41
|
+
export default defineConfig({
|
|
42
|
+
family: sql,
|
|
43
|
+
target: postgres,
|
|
44
|
+
adapter: postgresAdapter,
|
|
45
|
+
extensionPacks: [cipherstash],
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Authoring
|
|
50
|
+
|
|
51
|
+
### PSL
|
|
52
|
+
|
|
53
|
+
```prisma
|
|
54
|
+
model User {
|
|
55
|
+
id Int @id @default(autoincrement())
|
|
56
|
+
email cipherstash.EncryptedString()
|
|
57
|
+
notes cipherstash.EncryptedString({ freeTextSearch: false })?
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### TypeScript
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
import { encryptedString } from '@prisma-next/extension-cipherstash/column-types';
|
|
65
|
+
import cipherstash from '@prisma-next/extension-cipherstash/pack';
|
|
66
|
+
import sqlFamily from '@prisma-next/family-sql/pack';
|
|
67
|
+
import { defineContract, field, model } from '@prisma-next/sql-contract-ts/contract-builder';
|
|
68
|
+
import postgres from '@prisma-next/target-postgres/pack';
|
|
69
|
+
|
|
70
|
+
export const contract = defineContract({
|
|
71
|
+
family: sqlFamily,
|
|
72
|
+
target: postgres,
|
|
73
|
+
extensionPacks: { cipherstash },
|
|
74
|
+
models: {
|
|
75
|
+
User: model('User', {
|
|
76
|
+
fields: {
|
|
77
|
+
id: field.column({ codecId: 'pg/int4@1', nativeType: 'int4' })
|
|
78
|
+
.defaultSql('autoincrement()').id(),
|
|
79
|
+
email: field.column(encryptedString()),
|
|
80
|
+
notes: field.column(encryptedString({ freeTextSearch: false })).optional(),
|
|
81
|
+
},
|
|
82
|
+
}).sql({ table: 'user' }),
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Both authoring forms emit byte-identical `contract.json`. The codec registers under the `cipherstash/string@1` codec id and maps to the EQL `eql_v2_encrypted` Postgres native type.
|
|
88
|
+
|
|
89
|
+
Per-column search-mode flags `equality` and `freeTextSearch` both default to `true` — searchable encryption is the legitimate default for an extension whose entire reason for existing is to make encrypted columns queryable. Opt out explicitly when you want storage-only encryption (e.g. `cipherstash.EncryptedString({ equality: false, freeTextSearch: false })` / `encryptedString({ equality: false, freeTextSearch: false })`) or to disable just one mode. The flags are validated at the contract boundary by an arktype schema and threaded through the parameterized-codec descriptor model — see [ADR 208 — Higher-order codecs for parameterized types](../../../docs/architecture%20docs/adrs/ADR%20208%20-%20Higher-order%20codecs%20for%20parameterized%20types.md). The codec's `decode` site reads the cell's `(table, column)` from the per-call codec context — see [ADR 207 — Codec call context per-query AbortSignal and column metadata](../../../docs/architecture%20docs/adrs/ADR%20207%20-%20Codec%20call%20context%20per-query%20AbortSignal%20and%20column%20metadata.md).
|
|
90
|
+
|
|
91
|
+
## Database setup
|
|
92
|
+
|
|
93
|
+
The extension contributes its database scaffolding (the `eql_v2_configuration` table, the `eql_v2_encrypted` / `ore_*` composite types, the `eql_v2.bloom_filter` / `hmac_256` / `blake3` domains, and the EQL bundle SQL) as a **contract space** so the Prisma Next framework can plan, apply, and verify it the same way it manages an application's own schema.
|
|
94
|
+
|
|
95
|
+
After `prisma-next migrate plan`, the user's repo gains:
|
|
96
|
+
|
|
97
|
+
- `migrations/cipherstash/contract.json`,
|
|
98
|
+
- `migrations/cipherstash/contract.d.ts`,
|
|
99
|
+
- `migrations/cipherstash/refs/head.json`,
|
|
100
|
+
- `migrations/cipherstash/<name>/` migration directories.
|
|
101
|
+
|
|
102
|
+
`db apply` then runs CipherStash's migrations against the live database in the same transaction as any application-space migration emitted in the same `migrate` invocation.
|
|
103
|
+
|
|
104
|
+
## Runtime usage
|
|
105
|
+
|
|
106
|
+
A worked end-to-end example lives at [`examples/cipherstash-integration/`](../../../examples/cipherstash-integration/) — schema, runtime composition, demo SDK stub, and an `insert` → `cipherstashEq` → `cipherstashIlike` → `decryptAll` flow against a real Postgres database.
|
|
107
|
+
|
|
108
|
+
The minimal runtime composition wires the cipherstash runtime descriptor and the bulk-encrypt middleware into the SQL runtime (sharing one SDK binding):
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
import { bulkEncryptMiddleware } from '@prisma-next/extension-cipherstash/middleware';
|
|
112
|
+
import {
|
|
113
|
+
createCipherstashRuntimeDescriptor,
|
|
114
|
+
decryptAll,
|
|
115
|
+
EncryptedString,
|
|
116
|
+
} from '@prisma-next/extension-cipherstash/runtime';
|
|
117
|
+
import postgres from '@prisma-next/postgres/runtime';
|
|
118
|
+
import type { Contract } from './prisma/contract.d';
|
|
119
|
+
import contractJson from './prisma/contract.json' with { type: 'json' };
|
|
120
|
+
|
|
121
|
+
const sdk = /* your CipherstashSdk implementation */;
|
|
122
|
+
|
|
123
|
+
const db = postgres<Contract>({
|
|
124
|
+
contractJson,
|
|
125
|
+
extensions: [createCipherstashRuntimeDescriptor({ sdk })],
|
|
126
|
+
middleware: [bulkEncryptMiddleware(sdk)],
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
await db.orm.User.create({ id: 'u1', email: EncryptedString.from('alice@example.com') });
|
|
130
|
+
|
|
131
|
+
const rows = await db.orm.User.where((u) => u.email.cipherstashEq('alice@example.com')).all();
|
|
132
|
+
await decryptAll(rows);
|
|
133
|
+
console.log(await rows[0]?.email.decrypt());
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`EncryptedString.from(plaintext)` creates a write-side envelope; the bulk-encrypt middleware fills in the ciphertext at execute time. Read-side envelopes are constructed by the codec's `decode` and carry their `(table, column)` routing key for `decrypt` / `decryptAll`.
|
|
137
|
+
|
|
138
|
+
## Security model
|
|
139
|
+
|
|
140
|
+
- **Plaintext lifetime**. The write-side handle retains its plaintext slot post-encrypt — JS strings are immutable and zeroing is best-effort, so the GC-driven lifecycle is the sufficient bound. Practical implication: the original `EncryptedString.from(plaintext)` envelope's `decrypt()` returns the plaintext synchronously without consulting the SDK. Treat envelope objects as plaintext-equivalent for the lifetime of the variable.
|
|
141
|
+
- **Ciphertext routing**. Every read-side envelope carries the `(table, column)` it was decoded from; `decrypt` / `decryptAll` route their bulk SDK calls by that key so the SDK can pick the right key material per column.
|
|
142
|
+
- **Operator semantics**. Encrypted equality uses `eql_v2.eq` (deterministic-index lookup); encrypted free-text uses `eql_v2.ilike` (bloom-filter lookup). The framework's built-in `eq` / `ilike` are unreachable on cipherstash columns — the codec declares zero traits so no wrong-SQL footgun can exist where a randomized EQL ciphertext is compared with `=` directly.
|
|
143
|
+
- **Cancellation**. Every cipherstash-internal SDK call accepts an `AbortSignal`; mid-flight cancellation surfaces a `RUNTIME.ABORTED` envelope with a phase tag (`bulk-encrypt`, `decrypt`, or `decrypt-all`) mirroring the framework's envelope shape from [ADR 207](../../../docs/architecture%20docs/adrs/ADR%20207%20-%20Codec%20call%20context%20per-query%20AbortSignal%20and%20column%20metadata.md).
|
|
144
|
+
|
|
145
|
+
## Contributing
|
|
146
|
+
|
|
147
|
+
See [`DEVELOPING.md`](./DEVELOPING.md) for source layout, design choices, and the canonical Acceptance Criteria list.
|
|
148
|
+
|
|
149
|
+
## References
|
|
150
|
+
|
|
151
|
+
- [CipherStash](https://cipherstash.com) — managed application-layer encryption for Postgres.
|
|
152
|
+
- [Prisma Next Architecture Overview](../../../docs/Architecture%20Overview.md).
|
|
153
|
+
- [Extension Packs Naming and Layout](../../../docs/reference/Extension-Packs-Naming-and-Layout.md).
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { TsExpression, jsonToTsSource } from "@prisma-next/ts-render";
|
|
2
|
+
import { ifDefined } from "@prisma-next/utils/defined";
|
|
3
|
+
//#region src/migration/call-classes.ts
|
|
4
|
+
const CIPHERSTASH_MIGRATION_MODULE = "@prisma-next/extension-cipherstash/migration";
|
|
5
|
+
/** Mirrors `eql_v2.add_search_config(table, column, index_name, cast_as)`. */
|
|
6
|
+
const DEFAULT_CAST_AS = "text";
|
|
7
|
+
/**
|
|
8
|
+
* Escape a string so it can be embedded inside a Postgres single-quoted
|
|
9
|
+
* literal. Identifiers in our IR are unlikely to contain apostrophes,
|
|
10
|
+
* but doubling them keeps the emitted SQL safe under any future
|
|
11
|
+
* relaxation.
|
|
12
|
+
*/
|
|
13
|
+
function sqlLiteral(value) {
|
|
14
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
15
|
+
}
|
|
16
|
+
function invariantIdFor(tableName, fieldName, action, indexName) {
|
|
17
|
+
return `cipherstash-codec:${tableName}.${fieldName}:${action}:${indexName}@v1`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Base class for cipherstash migration IR nodes.
|
|
21
|
+
*
|
|
22
|
+
* Each instance is *both* an `OpFactoryCall` (renderable to TypeScript,
|
|
23
|
+
* lowerable to a runtime op via `toOp()`) and a structurally-valid
|
|
24
|
+
* {@link CipherstashOp} — `id`, `label`, `operationClass`,
|
|
25
|
+
* `invariantId`, `target`, `precheck`, `execute`, `postcheck` are
|
|
26
|
+
* stored as enumerable own properties, populated in the concrete
|
|
27
|
+
* subclass constructors. So when the planner-rendered `migration.ts`
|
|
28
|
+
* runs and the user's `operations` getter returns Call instances
|
|
29
|
+
* directly, both `MigrationOpSchema` validation (which checks `id` /
|
|
30
|
+
* `label` / `operationClass`) and `JSON.stringify` (which writes
|
|
31
|
+
* `ops.json`) see the runtime op shape unchanged.
|
|
32
|
+
*
|
|
33
|
+
* The cipherstash-specific identity fields (`factoryName`, `table`,
|
|
34
|
+
* `column`, `index`, `castAs`) live on the subclass prototype as
|
|
35
|
+
* accessor getters and on a per-instance backing record kept in a
|
|
36
|
+
* private slot (`#args`). Accessor properties on the class are
|
|
37
|
+
* non-enumerable, and the backing record is a private field, so
|
|
38
|
+
* `Object.keys(call)` and `canonicalizeJson(...)` see only the op
|
|
39
|
+
* fields — `ops.json` and `migrationHash` stay byte-stable.
|
|
40
|
+
*/
|
|
41
|
+
var CipherstashOpFactoryCallNode = class extends TsExpression {
|
|
42
|
+
importRequirements() {
|
|
43
|
+
return [{
|
|
44
|
+
moduleSpecifier: CIPHERSTASH_MIGRATION_MODULE,
|
|
45
|
+
symbol: this.factoryName
|
|
46
|
+
}];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Re-expose the runtime op view for callers that prefer to lower
|
|
50
|
+
* Calls explicitly (notably {@link renderOps} on the postgres lane).
|
|
51
|
+
* The returned object is a plain copy of this Call's op-shaped
|
|
52
|
+
* fields.
|
|
53
|
+
*/
|
|
54
|
+
toOp() {
|
|
55
|
+
return {
|
|
56
|
+
id: this.id,
|
|
57
|
+
label: this.label,
|
|
58
|
+
operationClass: this.operationClass,
|
|
59
|
+
invariantId: this.invariantId,
|
|
60
|
+
target: this.target,
|
|
61
|
+
precheck: this.precheck,
|
|
62
|
+
execute: this.execute,
|
|
63
|
+
postcheck: this.postcheck
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
freeze() {
|
|
67
|
+
Object.freeze(this);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
var CipherstashAddSearchConfigCall = class extends CipherstashOpFactoryCallNode {
|
|
71
|
+
id;
|
|
72
|
+
label;
|
|
73
|
+
operationClass;
|
|
74
|
+
invariantId;
|
|
75
|
+
target;
|
|
76
|
+
precheck;
|
|
77
|
+
execute;
|
|
78
|
+
postcheck;
|
|
79
|
+
#args;
|
|
80
|
+
constructor(table, column, index, castAs = DEFAULT_CAST_AS) {
|
|
81
|
+
super();
|
|
82
|
+
this.#args = {
|
|
83
|
+
table,
|
|
84
|
+
column,
|
|
85
|
+
index,
|
|
86
|
+
castAs
|
|
87
|
+
};
|
|
88
|
+
this.id = `cipherstash-codec.${table}.${column}.add-search-config.${index}`;
|
|
89
|
+
this.label = `Enable cipherstash search on ${table}.${column}`;
|
|
90
|
+
this.operationClass = "additive";
|
|
91
|
+
this.invariantId = invariantIdFor(table, column, "add-search-config", index);
|
|
92
|
+
this.target = { id: "postgres" };
|
|
93
|
+
this.precheck = [];
|
|
94
|
+
this.execute = [{
|
|
95
|
+
description: `Register cipherstash ${index} search config for ${table}.${column}`,
|
|
96
|
+
sql: `SELECT eql_v2.add_search_config(${sqlLiteral(table)}, ${sqlLiteral(column)}, ${sqlLiteral(index)}, ${sqlLiteral(castAs)});`
|
|
97
|
+
}];
|
|
98
|
+
this.postcheck = [];
|
|
99
|
+
this.freeze();
|
|
100
|
+
}
|
|
101
|
+
get factoryName() {
|
|
102
|
+
return "cipherstashAddSearchConfig";
|
|
103
|
+
}
|
|
104
|
+
get table() {
|
|
105
|
+
return this.#args.table;
|
|
106
|
+
}
|
|
107
|
+
get column() {
|
|
108
|
+
return this.#args.column;
|
|
109
|
+
}
|
|
110
|
+
get index() {
|
|
111
|
+
return this.#args.index;
|
|
112
|
+
}
|
|
113
|
+
get castAs() {
|
|
114
|
+
return this.#args.castAs;
|
|
115
|
+
}
|
|
116
|
+
renderTypeScript() {
|
|
117
|
+
return `cipherstashAddSearchConfig(${jsonToTsSource({
|
|
118
|
+
table: this.#args.table,
|
|
119
|
+
column: this.#args.column,
|
|
120
|
+
index: this.#args.index,
|
|
121
|
+
...ifDefined("castAs", this.#args.castAs !== DEFAULT_CAST_AS ? this.#args.castAs : void 0)
|
|
122
|
+
})})`;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
var CipherstashRemoveSearchConfigCall = class extends CipherstashOpFactoryCallNode {
|
|
126
|
+
id;
|
|
127
|
+
label;
|
|
128
|
+
operationClass;
|
|
129
|
+
invariantId;
|
|
130
|
+
target;
|
|
131
|
+
precheck;
|
|
132
|
+
execute;
|
|
133
|
+
postcheck;
|
|
134
|
+
#args;
|
|
135
|
+
constructor(table, column, index) {
|
|
136
|
+
super();
|
|
137
|
+
this.#args = {
|
|
138
|
+
table,
|
|
139
|
+
column,
|
|
140
|
+
index
|
|
141
|
+
};
|
|
142
|
+
this.id = `cipherstash-codec.${table}.${column}.remove-search-config.${index}`;
|
|
143
|
+
this.label = `Disable cipherstash search on ${table}.${column}`;
|
|
144
|
+
this.operationClass = "destructive";
|
|
145
|
+
this.invariantId = invariantIdFor(table, column, "remove-search-config", index);
|
|
146
|
+
this.target = { id: "postgres" };
|
|
147
|
+
this.precheck = [];
|
|
148
|
+
this.execute = [{
|
|
149
|
+
description: `Remove cipherstash ${index} search config for ${table}.${column}`,
|
|
150
|
+
sql: `SELECT eql_v2.remove_search_config(${sqlLiteral(table)}, ${sqlLiteral(column)}, ${sqlLiteral(index)});`
|
|
151
|
+
}];
|
|
152
|
+
this.postcheck = [];
|
|
153
|
+
this.freeze();
|
|
154
|
+
}
|
|
155
|
+
get factoryName() {
|
|
156
|
+
return "cipherstashRemoveSearchConfig";
|
|
157
|
+
}
|
|
158
|
+
get table() {
|
|
159
|
+
return this.#args.table;
|
|
160
|
+
}
|
|
161
|
+
get column() {
|
|
162
|
+
return this.#args.column;
|
|
163
|
+
}
|
|
164
|
+
get index() {
|
|
165
|
+
return this.#args.index;
|
|
166
|
+
}
|
|
167
|
+
renderTypeScript() {
|
|
168
|
+
return `cipherstashRemoveSearchConfig(${jsonToTsSource({
|
|
169
|
+
table: this.#args.table,
|
|
170
|
+
column: this.#args.column,
|
|
171
|
+
index: this.#args.index
|
|
172
|
+
})})`;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Public factory: register a cipherstash search-config row.
|
|
177
|
+
*
|
|
178
|
+
* Use from a hand-written migration when you need to wire EQL
|
|
179
|
+
* search-config alongside a `createTable` / `addColumn`. The
|
|
180
|
+
* `Encrypted<string>` codec hook calls this factory automatically when
|
|
181
|
+
* planning a contract diff that adds a `searchable: true` column.
|
|
182
|
+
*
|
|
183
|
+
* Returns the {@link CipherstashAddSearchConfigCall} IR node, which
|
|
184
|
+
* implements `OpFactoryCall` and is itself a `SqlMigrationPlanOperation`
|
|
185
|
+
* (its readonly op-shaped fields are populated in the constructor) — so
|
|
186
|
+
* the same value flows through both the renderer (planner-time IR) and
|
|
187
|
+
* the runtime ops list (`Migration.operations`) without an extra
|
|
188
|
+
* lowering step at the call site.
|
|
189
|
+
*/
|
|
190
|
+
function cipherstashAddSearchConfig(args) {
|
|
191
|
+
return new CipherstashAddSearchConfigCall(args.table, args.column, args.index, args.castAs ?? DEFAULT_CAST_AS);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Public factory: invert {@link cipherstashAddSearchConfig} for the
|
|
195
|
+
* same (table, column, index) tuple.
|
|
196
|
+
*
|
|
197
|
+
* Returns the {@link CipherstashRemoveSearchConfigCall} IR node — see
|
|
198
|
+
* {@link cipherstashAddSearchConfig} for the rationale.
|
|
199
|
+
*/
|
|
200
|
+
function cipherstashRemoveSearchConfig(args) {
|
|
201
|
+
return new CipherstashRemoveSearchConfigCall(args.table, args.column, args.index);
|
|
202
|
+
}
|
|
203
|
+
//#endregion
|
|
204
|
+
export { cipherstashRemoveSearchConfig as n, cipherstashAddSearchConfig as t };
|
|
205
|
+
|
|
206
|
+
//# sourceMappingURL=call-classes-CSvD7w8U.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"call-classes-CSvD7w8U.mjs","names":["#args"],"sources":["../src/migration/call-classes.ts"],"sourcesContent":["/**\n * Cipherstash migration IR — renderable `*Call` classes for the codec\n * lifecycle hook + the public `@prisma-next/extension-cipherstash/migration`\n * factory functions.\n *\n * Each `*Call` implements the framework `OpFactoryCall` interface (ADR\n * 195) directly, so cipherstash's contributions flow through the postgres\n * planner as first-class IR nodes — no `RawSqlCall` wrap, no detour\n * through the unstructured-op fallback. The codec hook\n * (`./cipherstash-codec.ts`) returns Calls; the postgres planner adds\n * them to its call list and renders them via `renderCallsToTypeScript`.\n *\n * Public factory functions (`cipherstashAddSearchConfig` /\n * `cipherstashRemoveSearchConfig`) are re-exported from\n * `@prisma-next/extension-cipherstash/migration`. Users authoring a\n * hand-written migration can call them directly:\n *\n * ```ts\n * import { cipherstashAddSearchConfig } from '@prisma-next/extension-cipherstash/migration';\n *\n * createTable('public', 'user', [...]);\n * cipherstashAddSearchConfig({ table: 'user', column: 'email', index: 'unique' });\n * ```\n *\n * Round-trip invariant: `toOp()` produces the same op shape the codec\n * hook would emit directly — `ops.json` stays byte-identical;\n * `migration.ts` carries a factory call instead of an opaque\n * `rawSql({...})` block.\n */\n\nimport type { SqlMigrationPlanOperation } from '@prisma-next/family-sql/control';\nimport type {\n MigrationOperationClass,\n OpFactoryCall,\n} from '@prisma-next/framework-components/control';\nimport { type ImportRequirement, jsonToTsSource, TsExpression } from '@prisma-next/ts-render';\nimport { ifDefined } from '@prisma-next/utils/defined';\n\nconst CIPHERSTASH_MIGRATION_MODULE = '@prisma-next/extension-cipherstash/migration';\n\n/** Mirrors `eql_v2.add_search_config(table, column, index_name, cast_as)`. */\nconst DEFAULT_CAST_AS = 'text';\n\n/**\n * Two-valued enumeration matching the EQL search-config indices the\n * cipherstash codec emits — one per enabled flag in\n * `Encrypted<string>`'s `typeParams`:\n *\n * - `equality: true` → `'unique'` index\n * - `freeTextSearch: true` → `'match'` index\n */\nexport type CipherstashSearchIndex = 'unique' | 'match';\n\n/**\n * Args shape accepted by the public `cipherstashAddSearchConfig` /\n * `cipherstashRemoveSearchConfig` factory functions.\n *\n * `castAs` defaults to `'text'` — matches the cipherstash codec hook's\n * canonical output and the EQL bundle's expected cast for\n * `eql_v2_encrypted` columns. Override only if you know the runtime\n * cast for your column differs.\n */\nexport interface CipherstashSearchConfigArgs {\n readonly table: string;\n readonly column: string;\n readonly index: CipherstashSearchIndex;\n readonly castAs?: string;\n}\n\ntype CipherstashOp = SqlMigrationPlanOperation<unknown>;\ntype OpStep = CipherstashOp['execute'][number];\n\n/**\n * Escape a string so it can be embedded inside a Postgres single-quoted\n * literal. Identifiers in our IR are unlikely to contain apostrophes,\n * but doubling them keeps the emitted SQL safe under any future\n * relaxation.\n */\nfunction sqlLiteral(value: string): string {\n return `'${value.replace(/'/g, \"''\")}'`;\n}\n\nfunction invariantIdFor(\n tableName: string,\n fieldName: string,\n action: 'add-search-config' | 'remove-search-config',\n indexName: CipherstashSearchIndex,\n): string {\n return `cipherstash-codec:${tableName}.${fieldName}:${action}:${indexName}@v1`;\n}\n\n/**\n * Base class for cipherstash migration IR nodes.\n *\n * Each instance is *both* an `OpFactoryCall` (renderable to TypeScript,\n * lowerable to a runtime op via `toOp()`) and a structurally-valid\n * {@link CipherstashOp} — `id`, `label`, `operationClass`,\n * `invariantId`, `target`, `precheck`, `execute`, `postcheck` are\n * stored as enumerable own properties, populated in the concrete\n * subclass constructors. So when the planner-rendered `migration.ts`\n * runs and the user's `operations` getter returns Call instances\n * directly, both `MigrationOpSchema` validation (which checks `id` /\n * `label` / `operationClass`) and `JSON.stringify` (which writes\n * `ops.json`) see the runtime op shape unchanged.\n *\n * The cipherstash-specific identity fields (`factoryName`, `table`,\n * `column`, `index`, `castAs`) live on the subclass prototype as\n * accessor getters and on a per-instance backing record kept in a\n * private slot (`#args`). Accessor properties on the class are\n * non-enumerable, and the backing record is a private field, so\n * `Object.keys(call)` and `canonicalizeJson(...)` see only the op\n * fields — `ops.json` and `migrationHash` stay byte-stable.\n */\nabstract class CipherstashOpFactoryCallNode extends TsExpression implements OpFactoryCall {\n abstract get factoryName(): string;\n abstract readonly operationClass: MigrationOperationClass;\n abstract readonly label: string;\n abstract readonly id: string;\n abstract readonly invariantId: string;\n abstract readonly target: { readonly id: string };\n abstract readonly precheck: readonly OpStep[];\n abstract readonly execute: readonly OpStep[];\n abstract readonly postcheck: readonly OpStep[];\n\n importRequirements(): readonly ImportRequirement[] {\n return [{ moduleSpecifier: CIPHERSTASH_MIGRATION_MODULE, symbol: this.factoryName }];\n }\n\n /**\n * Re-expose the runtime op view for callers that prefer to lower\n * Calls explicitly (notably {@link renderOps} on the postgres lane).\n * The returned object is a plain copy of this Call's op-shaped\n * fields.\n */\n toOp(): CipherstashOp {\n return {\n id: this.id,\n label: this.label,\n operationClass: this.operationClass,\n invariantId: this.invariantId,\n target: this.target,\n precheck: this.precheck,\n execute: this.execute,\n postcheck: this.postcheck,\n };\n }\n\n protected freeze(): void {\n Object.freeze(this);\n }\n}\n\n/**\n * `cipherstashAddSearchConfig` — register an EQL search-config row for\n * the given column / index combination. Lowers to a `SELECT\n * eql_v2.add_search_config('<table>', '<column>', '<index>',\n * '<castAs>')` op, classified `'additive'`.\n */\ninterface AddArgs {\n readonly table: string;\n readonly column: string;\n readonly index: CipherstashSearchIndex;\n readonly castAs: string;\n}\n\nexport class CipherstashAddSearchConfigCall extends CipherstashOpFactoryCallNode {\n readonly id: string;\n readonly label: string;\n readonly operationClass: 'additive';\n readonly invariantId: string;\n readonly target: { readonly id: string };\n readonly precheck: readonly OpStep[];\n readonly execute: readonly OpStep[];\n readonly postcheck: readonly OpStep[];\n\n // Private slot keeps the renderer-side args off the enumerable\n // own-property surface; the public accessors below expose them\n // read-only on the prototype, so neither `Object.keys` nor\n // `canonicalizeJson` walks them.\n readonly #args: AddArgs;\n\n constructor(\n table: string,\n column: string,\n index: CipherstashSearchIndex,\n castAs: string = DEFAULT_CAST_AS,\n ) {\n super();\n this.#args = { table, column, index, castAs };\n // Property assignment order is fixed (id → label → operationClass\n // → invariantId → target → precheck → execute → postcheck) so\n // `JSON.stringify(call)` lays out keys in the byte order the\n // baseline `ops.json` carries.\n this.id = `cipherstash-codec.${table}.${column}.add-search-config.${index}`;\n this.label = `Enable cipherstash search on ${table}.${column}`;\n this.operationClass = 'additive';\n this.invariantId = invariantIdFor(table, column, 'add-search-config', index);\n this.target = { id: 'postgres' };\n this.precheck = [];\n this.execute = [\n {\n description: `Register cipherstash ${index} search config for ${table}.${column}`,\n sql: `SELECT eql_v2.add_search_config(${sqlLiteral(table)}, ${sqlLiteral(column)}, ${sqlLiteral(index)}, ${sqlLiteral(castAs)});`,\n },\n ];\n this.postcheck = [];\n this.freeze();\n }\n\n get factoryName(): 'cipherstashAddSearchConfig' {\n return 'cipherstashAddSearchConfig';\n }\n\n get table(): string {\n return this.#args.table;\n }\n\n get column(): string {\n return this.#args.column;\n }\n\n get index(): CipherstashSearchIndex {\n return this.#args.index;\n }\n\n get castAs(): string {\n return this.#args.castAs;\n }\n\n renderTypeScript(): string {\n const args = {\n table: this.#args.table,\n column: this.#args.column,\n index: this.#args.index,\n ...ifDefined('castAs', this.#args.castAs !== DEFAULT_CAST_AS ? this.#args.castAs : undefined),\n };\n return `cipherstashAddSearchConfig(${jsonToTsSource(args)})`;\n }\n}\n\n/**\n * `cipherstashRemoveSearchConfig` — invert\n * {@link CipherstashAddSearchConfigCall} for the same (table, column,\n * index) tuple. Lowers to `SELECT eql_v2.remove_search_config('<table>',\n * '<column>', '<index>')`, classified `'destructive'`.\n *\n * No `castAs` argument — `eql_v2.remove_search_config` takes only the\n * three identifying fields; the cast was applied at the index's add\n * site.\n */\ninterface RemoveArgs {\n readonly table: string;\n readonly column: string;\n readonly index: CipherstashSearchIndex;\n}\n\nexport class CipherstashRemoveSearchConfigCall extends CipherstashOpFactoryCallNode {\n readonly id: string;\n readonly label: string;\n readonly operationClass: 'destructive';\n readonly invariantId: string;\n readonly target: { readonly id: string };\n readonly precheck: readonly OpStep[];\n readonly execute: readonly OpStep[];\n readonly postcheck: readonly OpStep[];\n\n readonly #args: RemoveArgs;\n\n constructor(table: string, column: string, index: CipherstashSearchIndex) {\n super();\n this.#args = { table, column, index };\n this.id = `cipherstash-codec.${table}.${column}.remove-search-config.${index}`;\n this.label = `Disable cipherstash search on ${table}.${column}`;\n this.operationClass = 'destructive';\n this.invariantId = invariantIdFor(table, column, 'remove-search-config', index);\n this.target = { id: 'postgres' };\n this.precheck = [];\n this.execute = [\n {\n description: `Remove cipherstash ${index} search config for ${table}.${column}`,\n sql: `SELECT eql_v2.remove_search_config(${sqlLiteral(table)}, ${sqlLiteral(column)}, ${sqlLiteral(index)});`,\n },\n ];\n this.postcheck = [];\n this.freeze();\n }\n\n get factoryName(): 'cipherstashRemoveSearchConfig' {\n return 'cipherstashRemoveSearchConfig';\n }\n\n get table(): string {\n return this.#args.table;\n }\n\n get column(): string {\n return this.#args.column;\n }\n\n get index(): CipherstashSearchIndex {\n return this.#args.index;\n }\n\n renderTypeScript(): string {\n return `cipherstashRemoveSearchConfig(${jsonToTsSource({\n table: this.#args.table,\n column: this.#args.column,\n index: this.#args.index,\n })})`;\n }\n}\n\n/**\n * Public factory: register a cipherstash search-config row.\n *\n * Use from a hand-written migration when you need to wire EQL\n * search-config alongside a `createTable` / `addColumn`. The\n * `Encrypted<string>` codec hook calls this factory automatically when\n * planning a contract diff that adds a `searchable: true` column.\n *\n * Returns the {@link CipherstashAddSearchConfigCall} IR node, which\n * implements `OpFactoryCall` and is itself a `SqlMigrationPlanOperation`\n * (its readonly op-shaped fields are populated in the constructor) — so\n * the same value flows through both the renderer (planner-time IR) and\n * the runtime ops list (`Migration.operations`) without an extra\n * lowering step at the call site.\n */\nexport function cipherstashAddSearchConfig(\n args: CipherstashSearchConfigArgs,\n): CipherstashAddSearchConfigCall {\n return new CipherstashAddSearchConfigCall(\n args.table,\n args.column,\n args.index,\n args.castAs ?? DEFAULT_CAST_AS,\n );\n}\n\n/**\n * Public factory: invert {@link cipherstashAddSearchConfig} for the\n * same (table, column, index) tuple.\n *\n * Returns the {@link CipherstashRemoveSearchConfigCall} IR node — see\n * {@link cipherstashAddSearchConfig} for the rationale.\n */\nexport function cipherstashRemoveSearchConfig(\n args: CipherstashSearchConfigArgs,\n): CipherstashRemoveSearchConfigCall {\n return new CipherstashRemoveSearchConfigCall(args.table, args.column, args.index);\n}\n"],"mappings":";;;AAsCA,MAAM,+BAA+B;;AAGrC,MAAM,kBAAkB;;;;;;;AAqCxB,SAAS,WAAW,OAAuB;CACzC,OAAO,IAAI,MAAM,QAAQ,MAAM,KAAK,CAAC;;AAGvC,SAAS,eACP,WACA,WACA,QACA,WACQ;CACR,OAAO,qBAAqB,UAAU,GAAG,UAAU,GAAG,OAAO,GAAG,UAAU;;;;;;;;;;;;;;;;;;;;;;;;AAyB5E,IAAe,+BAAf,cAAoD,aAAsC;CAWxF,qBAAmD;EACjD,OAAO,CAAC;GAAE,iBAAiB;GAA8B,QAAQ,KAAK;GAAa,CAAC;;;;;;;;CAStF,OAAsB;EACpB,OAAO;GACL,IAAI,KAAK;GACT,OAAO,KAAK;GACZ,gBAAgB,KAAK;GACrB,aAAa,KAAK;GAClB,QAAQ,KAAK;GACb,UAAU,KAAK;GACf,SAAS,KAAK;GACd,WAAW,KAAK;GACjB;;CAGH,SAAyB;EACvB,OAAO,OAAO,KAAK;;;AAiBvB,IAAa,iCAAb,cAAoD,6BAA6B;CAC/E;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAMA;CAEA,YACE,OACA,QACA,OACA,SAAiB,iBACjB;EACA,OAAO;EACP,KAAKA,QAAQ;GAAE;GAAO;GAAQ;GAAO;GAAQ;EAK7C,KAAK,KAAK,qBAAqB,MAAM,GAAG,OAAO,qBAAqB;EACpE,KAAK,QAAQ,gCAAgC,MAAM,GAAG;EACtD,KAAK,iBAAiB;EACtB,KAAK,cAAc,eAAe,OAAO,QAAQ,qBAAqB,MAAM;EAC5E,KAAK,SAAS,EAAE,IAAI,YAAY;EAChC,KAAK,WAAW,EAAE;EAClB,KAAK,UAAU,CACb;GACE,aAAa,wBAAwB,MAAM,qBAAqB,MAAM,GAAG;GACzE,KAAK,mCAAmC,WAAW,MAAM,CAAC,IAAI,WAAW,OAAO,CAAC,IAAI,WAAW,MAAM,CAAC,IAAI,WAAW,OAAO,CAAC;GAC/H,CACF;EACD,KAAK,YAAY,EAAE;EACnB,KAAK,QAAQ;;CAGf,IAAI,cAA4C;EAC9C,OAAO;;CAGT,IAAI,QAAgB;EAClB,OAAO,KAAKA,MAAM;;CAGpB,IAAI,SAAiB;EACnB,OAAO,KAAKA,MAAM;;CAGpB,IAAI,QAAgC;EAClC,OAAO,KAAKA,MAAM;;CAGpB,IAAI,SAAiB;EACnB,OAAO,KAAKA,MAAM;;CAGpB,mBAA2B;EAOzB,OAAO,8BAA8B,eAAe;GALlD,OAAO,KAAKA,MAAM;GAClB,QAAQ,KAAKA,MAAM;GACnB,OAAO,KAAKA,MAAM;GAClB,GAAG,UAAU,UAAU,KAAKA,MAAM,WAAW,kBAAkB,KAAKA,MAAM,SAAS,KAAA,EAAU;GAEvC,CAAC,CAAC;;;AAoB9D,IAAa,oCAAb,cAAuD,6BAA6B;CAClF;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CAEA,YAAY,OAAe,QAAgB,OAA+B;EACxE,OAAO;EACP,KAAKA,QAAQ;GAAE;GAAO;GAAQ;GAAO;EACrC,KAAK,KAAK,qBAAqB,MAAM,GAAG,OAAO,wBAAwB;EACvE,KAAK,QAAQ,iCAAiC,MAAM,GAAG;EACvD,KAAK,iBAAiB;EACtB,KAAK,cAAc,eAAe,OAAO,QAAQ,wBAAwB,MAAM;EAC/E,KAAK,SAAS,EAAE,IAAI,YAAY;EAChC,KAAK,WAAW,EAAE;EAClB,KAAK,UAAU,CACb;GACE,aAAa,sBAAsB,MAAM,qBAAqB,MAAM,GAAG;GACvE,KAAK,sCAAsC,WAAW,MAAM,CAAC,IAAI,WAAW,OAAO,CAAC,IAAI,WAAW,MAAM,CAAC;GAC3G,CACF;EACD,KAAK,YAAY,EAAE;EACnB,KAAK,QAAQ;;CAGf,IAAI,cAA+C;EACjD,OAAO;;CAGT,IAAI,QAAgB;EAClB,OAAO,KAAKA,MAAM;;CAGpB,IAAI,SAAiB;EACnB,OAAO,KAAKA,MAAM;;CAGpB,IAAI,QAAgC;EAClC,OAAO,KAAKA,MAAM;;CAGpB,mBAA2B;EACzB,OAAO,iCAAiC,eAAe;GACrD,OAAO,KAAKA,MAAM;GAClB,QAAQ,KAAKA,MAAM;GACnB,OAAO,KAAKA,MAAM;GACnB,CAAC,CAAC;;;;;;;;;;;;;;;;;;AAmBP,SAAgB,2BACd,MACgC;CAChC,OAAO,IAAI,+BACT,KAAK,OACL,KAAK,QACL,KAAK,OACL,KAAK,UAAU,gBAChB;;;;;;;;;AAUH,SAAgB,8BACd,MACmC;CACnC,OAAO,IAAI,kCAAkC,KAAK,OAAO,KAAK,QAAQ,KAAK,MAAM"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { n as CIPHERSTASH_STRING_CODEC_ID, r as EQL_V2_ENCRYPTED_TYPE } from "./constants-BDxL9Pe3.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/exports/column-types.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Search-mode parameters for `encryptedString({...})`. Both flags are
|
|
6
|
+
* optional and default to `true` when omitted.
|
|
7
|
+
*/
|
|
8
|
+
interface EncryptedStringOptions {
|
|
9
|
+
readonly equality?: boolean;
|
|
10
|
+
readonly freeTextSearch?: boolean;
|
|
11
|
+
}
|
|
12
|
+
interface EncryptedStringColumnDescriptor {
|
|
13
|
+
readonly codecId: typeof CIPHERSTASH_STRING_CODEC_ID;
|
|
14
|
+
readonly nativeType: typeof EQL_V2_ENCRYPTED_TYPE;
|
|
15
|
+
readonly typeParams: {
|
|
16
|
+
readonly equality: boolean;
|
|
17
|
+
readonly freeTextSearch: boolean;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* `encryptedString({ equality?, freeTextSearch? })` — TS contract
|
|
22
|
+
* factory that lowers to a `ColumnTypeDescriptor` with the
|
|
23
|
+
* `cipherstash/string@1` codec and the `eql_v2_encrypted` Postgres
|
|
24
|
+
* native type. The two boolean flags become `typeParams.equality` and
|
|
25
|
+
* `typeParams.freeTextSearch`. Both default to `true`.
|
|
26
|
+
*
|
|
27
|
+
* The shape matches what the PSL constructor
|
|
28
|
+
* `cipherstash.EncryptedString({...})` lowers to, byte-for-byte.
|
|
29
|
+
*/
|
|
30
|
+
declare function encryptedString(options?: EncryptedStringOptions): EncryptedStringColumnDescriptor;
|
|
31
|
+
//#endregion
|
|
32
|
+
export { EncryptedStringColumnDescriptor, EncryptedStringOptions, encryptedString };
|
|
33
|
+
//# sourceMappingURL=column-types.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"column-types.d.mts","names":[],"sources":["../src/exports/column-types.ts"],"mappings":";;;;;;;UA0BiB,sBAAA;EAAA,SACN,QAAA;EAAA,SACA,cAAA;AAAA;AAAA,UAGM,+BAAA;EAAA,SACN,OAAA,SAAgB,2BAAA;EAAA,SAChB,UAAA,SAAmB,qBAAA;EAAA,SACnB,UAAA;IAAA,SACE,QAAA;IAAA,SACA,cAAA;EAAA;AAAA;;;;;;;;;;;iBAcG,eAAA,CACd,OAAA,GAAS,sBAAA,GACR,+BAAA"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { a as EQL_V2_ENCRYPTED_TYPE, i as CIPHERSTASH_STRING_CODEC_ID } from "./constants-B_2TNvUi.mjs";
|
|
2
|
+
//#region src/exports/column-types.ts
|
|
3
|
+
/**
|
|
4
|
+
* TS contract factory for cipherstash-encrypted string columns.
|
|
5
|
+
*
|
|
6
|
+
* Counterpart to the PSL constructor `cipherstash.EncryptedString({...})`
|
|
7
|
+
* registered in `../contract/authoring.ts`. Both factories produce the same
|
|
8
|
+
* `ColumnTypeDescriptor` shape so PSL- and TS-authored contracts emit
|
|
9
|
+
* byte-identical `contract.json` (verified by the parity fixture under
|
|
10
|
+
* `test/integration/test/authoring/parity/cipherstash-encrypted-string/`).
|
|
11
|
+
*
|
|
12
|
+
* Both flags default to `true` — searchable encryption is the
|
|
13
|
+
* legitimate default for an extension whose entire reason for existing
|
|
14
|
+
* is to make encrypted columns queryable. Users who want storage-only
|
|
15
|
+
* encryption opt out explicitly: `encryptedString({ equality: false,
|
|
16
|
+
* freeTextSearch: false })`. Mirrors the PSL constructor's `true`
|
|
17
|
+
* defaults declared via `AuthoringArgRef.default`.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* `encryptedString({ equality?, freeTextSearch? })` — TS contract
|
|
21
|
+
* factory that lowers to a `ColumnTypeDescriptor` with the
|
|
22
|
+
* `cipherstash/string@1` codec and the `eql_v2_encrypted` Postgres
|
|
23
|
+
* native type. The two boolean flags become `typeParams.equality` and
|
|
24
|
+
* `typeParams.freeTextSearch`. Both default to `true`.
|
|
25
|
+
*
|
|
26
|
+
* The shape matches what the PSL constructor
|
|
27
|
+
* `cipherstash.EncryptedString({...})` lowers to, byte-for-byte.
|
|
28
|
+
*/
|
|
29
|
+
function encryptedString(options = {}) {
|
|
30
|
+
return {
|
|
31
|
+
codecId: CIPHERSTASH_STRING_CODEC_ID,
|
|
32
|
+
nativeType: EQL_V2_ENCRYPTED_TYPE,
|
|
33
|
+
typeParams: {
|
|
34
|
+
equality: options.equality ?? true,
|
|
35
|
+
freeTextSearch: options.freeTextSearch ?? true
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
//#endregion
|
|
40
|
+
export { encryptedString };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=column-types.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"column-types.mjs","names":[],"sources":["../src/exports/column-types.ts"],"sourcesContent":["/**\n * TS contract factory for cipherstash-encrypted string columns.\n *\n * Counterpart to the PSL constructor `cipherstash.EncryptedString({...})`\n * registered in `../contract/authoring.ts`. Both factories produce the same\n * `ColumnTypeDescriptor` shape so PSL- and TS-authored contracts emit\n * byte-identical `contract.json` (verified by the parity fixture under\n * `test/integration/test/authoring/parity/cipherstash-encrypted-string/`).\n *\n * Both flags default to `true` — searchable encryption is the\n * legitimate default for an extension whose entire reason for existing\n * is to make encrypted columns queryable. Users who want storage-only\n * encryption opt out explicitly: `encryptedString({ equality: false,\n * freeTextSearch: false })`. Mirrors the PSL constructor's `true`\n * defaults declared via `AuthoringArgRef.default`.\n */\n\nimport {\n CIPHERSTASH_STRING_CODEC_ID,\n EQL_V2_ENCRYPTED_TYPE,\n} from '../extension-metadata/constants';\n\n/**\n * Search-mode parameters for `encryptedString({...})`. Both flags are\n * optional and default to `true` when omitted.\n */\nexport interface EncryptedStringOptions {\n readonly equality?: boolean;\n readonly freeTextSearch?: boolean;\n}\n\nexport interface EncryptedStringColumnDescriptor {\n readonly codecId: typeof CIPHERSTASH_STRING_CODEC_ID;\n readonly nativeType: typeof EQL_V2_ENCRYPTED_TYPE;\n readonly typeParams: {\n readonly equality: boolean;\n readonly freeTextSearch: boolean;\n };\n}\n\n/**\n * `encryptedString({ equality?, freeTextSearch? })` — TS contract\n * factory that lowers to a `ColumnTypeDescriptor` with the\n * `cipherstash/string@1` codec and the `eql_v2_encrypted` Postgres\n * native type. The two boolean flags become `typeParams.equality` and\n * `typeParams.freeTextSearch`. Both default to `true`.\n *\n * The shape matches what the PSL constructor\n * `cipherstash.EncryptedString({...})` lowers to, byte-for-byte.\n */\nexport function encryptedString(\n options: EncryptedStringOptions = {},\n): EncryptedStringColumnDescriptor {\n return {\n codecId: CIPHERSTASH_STRING_CODEC_ID,\n nativeType: EQL_V2_ENCRYPTED_TYPE,\n typeParams: {\n equality: options.equality ?? true,\n freeTextSearch: options.freeTextSearch ?? true,\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,SAAgB,gBACd,UAAkC,EAAE,EACH;CACjC,OAAO;EACL,SAAS;EACT,YAAY;EACZ,YAAY;GACV,UAAU,QAAQ,YAAY;GAC9B,gBAAgB,QAAQ,kBAAkB;GAC3C;EACF"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
//#region src/extension-metadata/constants.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Version advertised by both `cipherstashPackMeta.version` (control plane)
|
|
4
|
+
* and the SDK-bound `SqlRuntimeExtensionDescriptor` (runtime plane).
|
|
5
|
+
*
|
|
6
|
+
* Single source of truth so the descriptor surfaces and the contract-emit
|
|
7
|
+
* pack metadata cannot drift apart; consumed downstream by capability
|
|
8
|
+
* gating and contract round-trips.
|
|
9
|
+
*/
|
|
10
|
+
declare const CIPHERSTASH_EXTENSION_VERSION: "0.0.1";
|
|
11
|
+
/**
|
|
12
|
+
* Codec id the application-side `Encrypted<string>` lowering targets.
|
|
13
|
+
* Lives here so the codec lifecycle hook (which emits
|
|
14
|
+
* `add_search_config` / `remove_search_config` ops on field events) and
|
|
15
|
+
* the descriptor's `controlPlaneHooks` wiring share the same constant.
|
|
16
|
+
*/
|
|
17
|
+
declare const CIPHERSTASH_STRING_CODEC_ID = "cipherstash/string@1";
|
|
18
|
+
/** JSONB-domain composite type user `Encrypted<string>` columns reference. */
|
|
19
|
+
declare const EQL_V2_ENCRYPTED_TYPE = "eql_v2_encrypted";
|
|
20
|
+
//#endregion
|
|
21
|
+
export { CIPHERSTASH_STRING_CODEC_ID as n, EQL_V2_ENCRYPTED_TYPE as r, CIPHERSTASH_EXTENSION_VERSION as t };
|
|
22
|
+
//# sourceMappingURL=constants-BDxL9Pe3.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants-BDxL9Pe3.d.mts","names":[],"sources":["../src/extension-metadata/constants.ts"],"mappings":";;;;;;;;;cAyBa,6BAAA;;;;;;;cAQA,2BAAA;;cAYA,qBAAA"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//#region src/extension-metadata/constants.ts
|
|
2
|
+
/**
|
|
3
|
+
* Static names and identifiers used across CipherStash's contract space.
|
|
4
|
+
*
|
|
5
|
+
* Centralising the strings here so:
|
|
6
|
+
* - the contract IR (`./contract`), the migration ops (`./migrations`),
|
|
7
|
+
* the head ref (`./head-ref`), and the descriptor (`../exports/control`)
|
|
8
|
+
* all reference the same values without typos;
|
|
9
|
+
* - the `cipherstash:*` invariantId namespace is locked in one place
|
|
10
|
+
* (once published, an invariantId cannot be renamed).
|
|
11
|
+
*
|
|
12
|
+
* The space identifier `'cipherstash'` is what the framework writes to
|
|
13
|
+
* `migrations/cipherstash/` in the user's repo and what the marker table's
|
|
14
|
+
* `space` column carries for CipherStash-owned rows.
|
|
15
|
+
*/
|
|
16
|
+
const CIPHERSTASH_SPACE_ID = "cipherstash";
|
|
17
|
+
/**
|
|
18
|
+
* Version advertised by both `cipherstashPackMeta.version` (control plane)
|
|
19
|
+
* and the SDK-bound `SqlRuntimeExtensionDescriptor` (runtime plane).
|
|
20
|
+
*
|
|
21
|
+
* Single source of truth so the descriptor surfaces and the contract-emit
|
|
22
|
+
* pack metadata cannot drift apart; consumed downstream by capability
|
|
23
|
+
* gating and contract round-trips.
|
|
24
|
+
*/
|
|
25
|
+
const CIPHERSTASH_EXTENSION_VERSION = "0.0.1";
|
|
26
|
+
/**
|
|
27
|
+
* Codec id the application-side `Encrypted<string>` lowering targets.
|
|
28
|
+
* Lives here so the codec lifecycle hook (which emits
|
|
29
|
+
* `add_search_config` / `remove_search_config` ops on field events) and
|
|
30
|
+
* the descriptor's `controlPlaneHooks` wiring share the same constant.
|
|
31
|
+
*/
|
|
32
|
+
const CIPHERSTASH_STRING_CODEC_ID = "cipherstash/string@1";
|
|
33
|
+
/** JSONB-domain composite type user `Encrypted<string>` columns reference. */
|
|
34
|
+
const EQL_V2_ENCRYPTED_TYPE = "eql_v2_encrypted";
|
|
35
|
+
/**
|
|
36
|
+
* Migration directory name for the baseline.
|
|
37
|
+
*
|
|
38
|
+
* Per the framework's per-space layout convention this name is
|
|
39
|
+
* preserved verbatim when the framework writes the package to
|
|
40
|
+
* `migrations/cipherstash/<this-name>/` in the user's repo.
|
|
41
|
+
*/
|
|
42
|
+
const CIPHERSTASH_BASELINE_MIGRATION_NAME = "20260601T0000_install_eql_bundle";
|
|
43
|
+
//#endregion
|
|
44
|
+
export { EQL_V2_ENCRYPTED_TYPE as a, CIPHERSTASH_STRING_CODEC_ID as i, CIPHERSTASH_EXTENSION_VERSION as n, CIPHERSTASH_SPACE_ID as r, CIPHERSTASH_BASELINE_MIGRATION_NAME as t };
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=constants-B_2TNvUi.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants-B_2TNvUi.mjs","names":[],"sources":["../src/extension-metadata/constants.ts"],"sourcesContent":["/**\n * Static names and identifiers used across CipherStash's contract space.\n *\n * Centralising the strings here so:\n * - the contract IR (`./contract`), the migration ops (`./migrations`),\n * the head ref (`./head-ref`), and the descriptor (`../exports/control`)\n * all reference the same values without typos;\n * - the `cipherstash:*` invariantId namespace is locked in one place\n * (once published, an invariantId cannot be renamed).\n *\n * The space identifier `'cipherstash'` is what the framework writes to\n * `migrations/cipherstash/` in the user's repo and what the marker table's\n * `space` column carries for CipherStash-owned rows.\n */\n\nexport const CIPHERSTASH_SPACE_ID = 'cipherstash';\n\n/**\n * Version advertised by both `cipherstashPackMeta.version` (control plane)\n * and the SDK-bound `SqlRuntimeExtensionDescriptor` (runtime plane).\n *\n * Single source of truth so the descriptor surfaces and the contract-emit\n * pack metadata cannot drift apart; consumed downstream by capability\n * gating and contract round-trips.\n */\nexport const CIPHERSTASH_EXTENSION_VERSION = '0.0.1' as const;\n\n/**\n * Codec id the application-side `Encrypted<string>` lowering targets.\n * Lives here so the codec lifecycle hook (which emits\n * `add_search_config` / `remove_search_config` ops on field events) and\n * the descriptor's `controlPlaneHooks` wiring share the same constant.\n */\nexport const CIPHERSTASH_STRING_CODEC_ID = 'cipherstash/string@1';\n\n/** Schema CipherStash installs its functions/operators/casts/types into. */\nexport const EQL_V2_SCHEMA = 'eql_v2';\n\n/** Configuration table used by EQL's per-column index configuration. */\nexport const EQL_V2_CONFIGURATION_TABLE = 'eql_v2_configuration';\n\n/** Enum type backing the `state` column on `eql_v2_configuration`. */\nexport const EQL_V2_CONFIGURATION_STATE_TYPE = 'eql_v2_configuration_state';\n\n/** JSONB-domain composite type user `Encrypted<string>` columns reference. */\nexport const EQL_V2_ENCRYPTED_TYPE = 'eql_v2_encrypted';\n\n/**\n * Migration directory name for the baseline.\n *\n * Per the framework's per-space layout convention this name is\n * preserved verbatim when the framework writes the package to\n * `migrations/cipherstash/<this-name>/` in the user's repo.\n */\nexport const CIPHERSTASH_BASELINE_MIGRATION_NAME = '20260601T0000_install_eql_bundle';\n\n/**\n * `cipherstash:*` invariantIds emitted by the baseline migration. Each\n * `cipherstash:*` id, once published, is immutable: downstream\n * consumers (other extensions, the marker table) reference them by\n * literal string match.\n *\n * Today the baseline emits a single op (`installBundle`); the bundle\n * SQL is the source of truth for every typed object it creates inside\n * the `eql_v2` schema. New bundle versions or additional structural\n * ops will mint new `cipherstash:*` ids alongside this entry.\n */\nexport const CIPHERSTASH_INVARIANTS = {\n installBundle: 'cipherstash:install-eql-bundle-v1',\n} as const;\n"],"mappings":";;;;;;;;;;;;;;;AAeA,MAAa,uBAAuB;;;;;;;;;AAUpC,MAAa,gCAAgC;;;;;;;AAQ7C,MAAa,8BAA8B;;AAY3C,MAAa,wBAAwB;;;;;;;;AASrC,MAAa,sCAAsC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { SqlControlExtensionDescriptor } from "@prisma-next/family-sql/control";
|
|
2
|
+
|
|
3
|
+
//#region src/exports/control.d.ts
|
|
4
|
+
declare const cipherstashExtensionDescriptor: SqlControlExtensionDescriptor<'postgres'>;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { cipherstashExtensionDescriptor, cipherstashExtensionDescriptor as default };
|
|
7
|
+
//# sourceMappingURL=control.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"control.d.mts","names":[],"sources":["../src/exports/control.ts"],"mappings":";;;cAuFM,8BAAA,EAAgC,6BAAA"}
|