11/**
22 * TS contract factories for cipherstash-encrypted columns.
33 *
4- * Counterparts to the PSL constructors `cipherstash.Encrypted<Type>({...})`
5- * registered in `../contract-authoring`. The six factories
6- * (`encryptedString`, `encryptedDouble`, `encryptedBigInt`,
7- * `encryptedDate`, `encryptedBoolean`, `encryptedJson`) produce the
8- * same `ColumnTypeDescriptor` shape as their PSL counterparts, so
9- * PSL- and TS-authored contracts emit byte-identical `contract.json`.
10- * Pinned by the parity fixtures at
11- * `test/integration/test/authoring/parity/cipherstash-encrypted-{string,double,bigint,date,boolean,json}/`.
4+ * ## v3: one factory per exposed domain
125 *
13- * Every search-mode flag defaults to `true` — searchable encryption
14- * is the legitimate default for an extension whose entire reason for
15- * existing is to make encrypted columns queryable. Users who want
16- * storage-only encryption opt out explicitly:
17- * `encryptedString({ equality: false, freeTextSearch: false, orderAndRange: false })`.
18- * Mirrors the PSL constructors' `true` defaults declared via
6+ * Counterparts to the argument-less PSL constructors
7+ * `cipherstash.Encrypted<Stem><Suffix>()` registered in
8+ * `../contract-authoring`. Each factory returns the domain's concrete
9+ * descriptor — static codec id, `public.eql_v3_*` native type, and a static
10+ * `{ castAs, capabilities }` typeParams block — identical to what its PSL
11+ * counterpart lowers to, so PSL- and TS-authored contracts emit
12+ * byte-identical `contract.json`. There are NO options: the factory IS the
13+ * capability set (`encryptedTextSearch` vs `encryptedText`, and so on).
14+ *
15+ * ## v2: legacy `*V2` aliases
16+ *
17+ * The six pre-rename factories (`encryptedStringV2`, `encryptedDoubleV2`,
18+ * `encryptedBigIntV2`, `encryptedDateV2`, `encryptedBooleanV2`,
19+ * `encryptedJsonV2`) keep their previous bodies verbatim: single optional
20+ * options object, every search-mode flag defaulting to `true` — searchable
21+ * encryption is the legitimate default for an extension whose entire reason
22+ * for existing is to make encrypted columns queryable — and the
23+ * `cipherstash/*@1` codec + `eql_v2_encrypted` native type outputs. They
24+ * mirror the `*V2` PSL constructors' `true` defaults declared via
1925 * `AuthoringArgRef.default`.
2026 */
2127
28+ import {
29+ type AnyEncryptedV3Column ,
30+ stripDomainSchema ,
31+ types ,
32+ } from '@cipherstash/stack/eql/v3'
2233import {
2334 CIPHERSTASH_BIGINT_CODEC_ID ,
2435 CIPHERSTASH_BOOLEAN_CODEC_ID ,
@@ -28,9 +39,131 @@ import {
2839 CIPHERSTASH_STRING_CODEC_ID ,
2940 EQL_V2_ENCRYPTED_TYPE ,
3041} from '../extension-metadata/constants'
42+ import { toV3CodecId , type V3CastAs } from '../v3/catalog'
43+
44+ // ---------------------------------------------------------------------------
45+ // v3 factories (derived from the concrete stack columns)
46+ // ---------------------------------------------------------------------------
47+
48+ /**
49+ * Per-column literal inference via the getters, NOT `EncryptedV3Column<infer D>`:
50+ * the stack's bundled `.d.ts` drops the private `definition` field's type, so
51+ * inferring through the class widens to the constraint. The getters' declared
52+ * return types survive d.ts bundling (see
53+ * `../extension-metadata/constants-v3.ts` for the full rationale).
54+ */
55+ type EqlTypeFromGetter < C > = C extends { getEqlType ( ) : infer T extends string }
56+ ? T
57+ : never
58+ type CapabilitiesFromGetter < C > = C extends { getQueryCapabilities ( ) : infer T }
59+ ? T
60+ : never
61+
62+ type StripPublic < T extends string > = T extends `public.${infer D } ` ? D : never
63+
64+ /**
65+ * Literal-preserving authoring descriptor, generic over the concrete stack
66+ * column `C`. `codecId`, `nativeType`, and `capabilities` are the domain's
67+ * LITERAL types (not `string` / `unknown`), so two domains' factories are
68+ * mutually non-assignable — this is the type spine (design "Type Discipline").
69+ * `castAs` is the one field the stack widens (`build(): ColumnSchema`), which
70+ * is harmless: `nativeType` already discriminates every domain. Pinned by the
71+ * type-level tests in `test/v3/column-types.test-d.ts`.
72+ */
73+ export interface V3ColumnDescriptor < C extends AnyEncryptedV3Column > {
74+ readonly codecId : `cipherstash/eql-v3/${StripPublic < EqlTypeFromGetter < C > > } @1`
75+ readonly nativeType : EqlTypeFromGetter < C >
76+ readonly typeParams : {
77+ readonly castAs : V3CastAs
78+ readonly capabilities : CapabilitiesFromGetter < C >
79+ }
80+ }
81+
82+ /**
83+ * Build one authoring factory from a concrete stack `types.*` factory. VALUES
84+ * are read from the column (`getEqlType` / `getQueryCapabilities` / `build`) —
85+ * nothing is hardcoded — and the codec id goes through the catalog's
86+ * `toV3CodecId` (registry key VERBATIM, never a name transform), while the
87+ * literal TYPES flow from `C`. The factory set is a typed 1:1 mirror of the
88+ * exposed catalog domains, which is how "derive from the catalog" and
89+ * "distinct static type per domain" coexist: TypeScript cannot hand distinct
90+ * static types to values pulled from a runtime record, so each factory names
91+ * its stack factory. Drift is caught by the 1:1 runtime test in
92+ * `test/column-types.test.ts` (exposed factory set === catalog minus
93+ * `*_ord_ore`) and the catalog drift tests.
94+ */
95+ function v3Authored < C extends AnyEncryptedV3Column > (
96+ make : ( name : string ) => C ,
97+ ) : ( ) => V3ColumnDescriptor < C > {
98+ const probe = make ( '__probe__' )
99+ const nativeType = probe . getEqlType ( )
100+ const descriptor = {
101+ codecId : toV3CodecId ( stripDomainSchema ( nativeType ) ) ,
102+ nativeType,
103+ typeParams : {
104+ // Narrowing assertion, mirrors ../v3/catalog.ts: `build()` widens
105+ // `cast_as` in the bundled d.ts; the value is the domain's literal.
106+ castAs : probe . build ( ) . cast_as as V3CastAs ,
107+ capabilities : probe . getQueryCapabilities ( ) ,
108+ } ,
109+ // Narrowing assertion: runtime strings are the literals the getters
110+ // declare; `toV3CodecId` returns `string` by signature.
111+ } as V3ColumnDescriptor < C >
112+ return ( ) => descriptor
113+ }
114+
115+ // Each factory has a DISTINCT return type (V3ColumnDescriptor<EncryptedXColumn>);
116+ // `encryptedBigIntOrd()` is not assignable to `encryptedText()` and vice-versa.
117+ // Note the stack factory names: bigint is `types.Bigint*` (not `BigInt*`).
118+ // text family
119+ export const encryptedText = v3Authored ( types . Text )
120+ export const encryptedTextEq = v3Authored ( types . TextEq )
121+ export const encryptedTextOrd = v3Authored ( types . TextOrd )
122+ export const encryptedTextMatch = v3Authored ( types . TextMatch )
123+ export const encryptedTextSearch = v3Authored ( types . TextSearch )
124+ // integer
125+ export const encryptedInteger = v3Authored ( types . Integer )
126+ export const encryptedIntegerEq = v3Authored ( types . IntegerEq )
127+ export const encryptedIntegerOrd = v3Authored ( types . IntegerOrd )
128+ // smallint
129+ export const encryptedSmallint = v3Authored ( types . Smallint )
130+ export const encryptedSmallintEq = v3Authored ( types . SmallintEq )
131+ export const encryptedSmallintOrd = v3Authored ( types . SmallintOrd )
132+ // bigint
133+ export const encryptedBigInt = v3Authored ( types . Bigint )
134+ export const encryptedBigIntEq = v3Authored ( types . BigintEq )
135+ export const encryptedBigIntOrd = v3Authored ( types . BigintOrd )
136+ // numeric
137+ export const encryptedNumeric = v3Authored ( types . Numeric )
138+ export const encryptedNumericEq = v3Authored ( types . NumericEq )
139+ export const encryptedNumericOrd = v3Authored ( types . NumericOrd )
140+ // real
141+ export const encryptedReal = v3Authored ( types . Real )
142+ export const encryptedRealEq = v3Authored ( types . RealEq )
143+ export const encryptedRealOrd = v3Authored ( types . RealOrd )
144+ // double
145+ export const encryptedDouble = v3Authored ( types . Double )
146+ export const encryptedDoubleEq = v3Authored ( types . DoubleEq )
147+ export const encryptedDoubleOrd = v3Authored ( types . DoubleOrd )
148+ // date
149+ export const encryptedDate = v3Authored ( types . Date )
150+ export const encryptedDateEq = v3Authored ( types . DateEq )
151+ export const encryptedDateOrd = v3Authored ( types . DateOrd )
152+ // timestamp
153+ export const encryptedTimestamp = v3Authored ( types . Timestamp )
154+ export const encryptedTimestampEq = v3Authored ( types . TimestampEq )
155+ export const encryptedTimestampOrd = v3Authored ( types . TimestampOrd )
156+ // boolean (storage-only)
157+ export const encryptedBoolean = v3Authored ( types . Boolean )
158+ // json (encrypted JSONB, ste_vec containment)
159+ export const encryptedJson = v3Authored ( types . Json )
160+
161+ // ---------------------------------------------------------------------------
162+ // v2 legacy aliases (verbatim pre-rename bodies, now named *V2)
163+ // ---------------------------------------------------------------------------
31164
32165/**
33- * Search-mode parameters for `encryptedString ({...})`. Every flag is
166+ * Search-mode parameters for `encryptedStringV2 ({...})`. Every flag is
34167 * optional and defaults to `true` when omitted — searchable
35168 * encryption is the legitimate default. `orderAndRange` gives string
36169 * columns the same sortable / range-queryable surface the numeric +
@@ -53,16 +186,16 @@ export interface EncryptedStringColumnDescriptor {
53186}
54187
55188/**
56- * `encryptedString ({ equality?, freeTextSearch?, orderAndRange? })` —
189+ * `encryptedStringV2 ({ equality?, freeTextSearch?, orderAndRange? })` —
57190 * TS contract factory that lowers to a `ColumnTypeDescriptor` with
58191 * the `cipherstash/string@1` codec and the `eql_v2_encrypted`
59192 * Postgres native type. Each boolean flag becomes a `typeParams.*`
60193 * slot; all default to `true`.
61194 *
62195 * The shape matches what the PSL constructor
63- * `cipherstash.EncryptedString ({...})` lowers to, byte-for-byte.
196+ * `cipherstash.EncryptedStringV2 ({...})` lowers to, byte-for-byte.
64197 */
65- export function encryptedString (
198+ export function encryptedStringV2 (
66199 options : EncryptedStringOptions = { } ,
67200) : EncryptedStringColumnDescriptor {
68201 return {
@@ -77,8 +210,8 @@ export function encryptedString(
77210}
78211
79212/**
80- * Search-mode parameters for `encryptedDouble ({...})` and
81- * `encryptedBigInt ({...})`. Both flags are optional and default to
213+ * Search-mode parameters for `encryptedDoubleV2 ({...})` and
214+ * `encryptedBigIntV2 ({...})`. Both flags are optional and default to
82215 * `true` when omitted — searchable encryption is the legitimate
83216 * default.
84217 */
@@ -106,13 +239,13 @@ export interface EncryptedBigIntColumnDescriptor {
106239}
107240
108241/**
109- * `encryptedDouble ({ equality?, orderAndRange? })` — TS contract
242+ * `encryptedDoubleV2 ({ equality?, orderAndRange? })` — TS contract
110243 * factory that lowers to a `ColumnTypeDescriptor` with the
111244 * `cipherstash/double@1` codec and the `eql_v2_encrypted` Postgres
112245 * native type. Mirrors what
113- * `cipherstash.EncryptedDouble ({...})` lowers to byte-for-byte.
246+ * `cipherstash.EncryptedDoubleV2 ({...})` lowers to byte-for-byte.
114247 */
115- export function encryptedDouble (
248+ export function encryptedDoubleV2 (
116249 options : EncryptedNumericOptions = { } ,
117250) : EncryptedDoubleColumnDescriptor {
118251 return {
@@ -126,10 +259,10 @@ export function encryptedDouble(
126259}
127260
128261/**
129- * `encryptedBigInt ({ equality?, orderAndRange? })` — TS contract
130- * factory matching `cipherstash.EncryptedBigInt ({...})`.
262+ * `encryptedBigIntV2 ({ equality?, orderAndRange? })` — TS contract
263+ * factory matching `cipherstash.EncryptedBigIntV2 ({...})`.
131264 */
132- export function encryptedBigInt (
265+ export function encryptedBigIntV2 (
133266 options : EncryptedNumericOptions = { } ,
134267) : EncryptedBigIntColumnDescriptor {
135268 return {
@@ -143,7 +276,7 @@ export function encryptedBigInt(
143276}
144277
145278/**
146- * Search-mode parameters for `encryptedDate ({...})`. Both flags are
279+ * Search-mode parameters for `encryptedDateV2 ({...})`. Both flags are
147280 * optional and default to `true`.
148281 */
149282export interface EncryptedDateOptions {
@@ -161,10 +294,10 @@ export interface EncryptedDateColumnDescriptor {
161294}
162295
163296/**
164- * `encryptedDate ({ equality?, orderAndRange? })` — TS contract factory
165- * matching `cipherstash.EncryptedDate ({...})`.
297+ * `encryptedDateV2 ({ equality?, orderAndRange? })` — TS contract factory
298+ * matching `cipherstash.EncryptedDateV2 ({...})`.
166299 */
167- export function encryptedDate (
300+ export function encryptedDateV2 (
168301 options : EncryptedDateOptions = { } ,
169302) : EncryptedDateColumnDescriptor {
170303 return {
@@ -178,7 +311,7 @@ export function encryptedDate(
178311}
179312
180313/**
181- * Search-mode parameters for `encryptedBoolean ({...})`. The flag is
314+ * Search-mode parameters for `encryptedBooleanV2 ({...})`. The flag is
182315 * optional and defaults to `true`. Booleans only support equality
183316 * search (no meaningful range predicate over a 2-value domain).
184317 */
@@ -195,10 +328,10 @@ export interface EncryptedBooleanColumnDescriptor {
195328}
196329
197330/**
198- * `encryptedBoolean ({ equality? })` — TS contract factory matching
199- * `cipherstash.EncryptedBoolean ({...})`.
331+ * `encryptedBooleanV2 ({ equality? })` — TS contract factory matching
332+ * `cipherstash.EncryptedBooleanV2 ({...})`.
200333 */
201- export function encryptedBoolean (
334+ export function encryptedBooleanV2 (
202335 options : EncryptedBooleanOptions = { } ,
203336) : EncryptedBooleanColumnDescriptor {
204337 return {
@@ -211,7 +344,7 @@ export function encryptedBoolean(
211344}
212345
213346/**
214- * Search-mode parameters for `encryptedJson ({...})`. Single flag —
347+ * Search-mode parameters for `encryptedJsonV2 ({...})`. Single flag —
215348 * `searchableJson` gates the entire `ste_vec` index family (containment
216349 * + path-extraction predicates). Defaults to `true`.
217350 */
@@ -228,10 +361,10 @@ export interface EncryptedJsonColumnDescriptor {
228361}
229362
230363/**
231- * `encryptedJson ({ searchableJson? })` — TS contract factory matching
232- * `cipherstash.EncryptedJson ({...})`.
364+ * `encryptedJsonV2 ({ searchableJson? })` — TS contract factory matching
365+ * `cipherstash.EncryptedJsonV2 ({...})`.
233366 */
234- export function encryptedJson (
367+ export function encryptedJsonV2 (
235368 options : EncryptedJsonOptions = { } ,
236369) : EncryptedJsonColumnDescriptor {
237370 return {
0 commit comments