Skip to content

Commit 1fdb1f5

Browse files
committed
Update EQL v3 concrete type names
1 parent 4d5bf63 commit 1fdb1f5

22 files changed

Lines changed: 442 additions & 326 deletions
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@cipherstash/stack': patch
3+
---
4+
5+
Upgrade `@cipherstash/protect-ffi` to 0.27.0 and update the EQL v3 concrete type factories/domains to the new names (`Integer`, `Smallint`, `Boolean`, `Real`, `Double`, and `Bigint`).

.changeset/eql-v3-typed-schema.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@
22
'@cipherstash/stack': minor
33
---
44

5-
Add EQL v3 schema builders for all generated SQL domains under `@cipherstash/stack/eql/v3`, exposed as the `types` namespace (one member per EQL v3 domain, e.g. `types.TextEq` / `types.Int4Ord` / `types.Timestamp`), including explicit query capability metadata (`getQueryCapabilities()` / `isQueryable()`) and v3 table support in model encryption helpers (`encryptModel` / `bulkEncryptModels`).
5+
Add EQL v3 schema builders for all generated SQL domains under `@cipherstash/stack/eql/v3`, exposed as the `types` namespace (one member per EQL v3 domain, e.g. `types.TextEq` / `types.IntegerOrd` / `types.Timestamp`), including explicit query capability metadata (`getQueryCapabilities()` / `isQueryable()`) and v3 table support in model encryption helpers (`encryptModel` / `bulkEncryptModels`).
66

77
Also widen the accepted plaintext input type for `encrypt` / `encryptQuery` to include `Date` and `bigint` (via the new `Plaintext` type), so v3 `date` / `timestamp` / `int8` domains can be encrypted and queried with their natural JavaScript values.

packages/stack/__tests__/cjs-require.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ describe('CJS consumers can require the built bundles', () => {
9393
`if (typeof v3.encryptedTable !== 'function') { throw new Error('missing v3 CJS export: encryptedTable') }`,
9494
`if (typeof v3.buildEncryptConfig !== 'function') { throw new Error('missing v3 CJS export: buildEncryptConfig') }`,
9595
`if (typeof v3.types !== 'object' || v3.types === null) { throw new Error('missing v3 CJS export: types namespace') }`,
96-
`const requiredTypes = ['TextSearch', 'TextEq', 'Int4Ord', 'Bool', 'Timestamp']`,
96+
`const requiredTypes = ['TextSearch', 'TextEq', 'IntegerOrd', 'Boolean', 'Timestamp', 'Bigint']`,
9797
`const missing = requiredTypes.filter((k) => typeof v3.types[k] !== 'function')`,
9898
`if (missing.length > 0) { throw new Error('missing v3 types.* CJS members: ' + missing.join(', ')) }`,
9999
].join('\n')

packages/stack/__tests__/encrypt-lock-context-guards.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const users = encryptedTable('users', {
4040
})
4141

4242
const usersV3 = encryptedTableV3('users_v3', {
43-
score: types.Int4Ord('score'),
43+
score: types.IntegerOrd('score'),
4444
})
4545

4646
// biome-ignore lint/suspicious/noExplicitAny: test helper reads the Result union

packages/stack/__tests__/schema-v3-client.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import { describeLive, LIVE_CIPHERSTASH_ENABLED } from './helpers/live-gate'
99

1010
const users = encryptedTable('schema_v3_client_users', {
1111
email: types.TextSearch('email'),
12-
age: types.Int4Ord('age'),
12+
age: types.IntegerOrd('age'),
1313
nickname: types.TextEq('nickname'),
1414
body: types.TextMatch('body'),
1515
notes: types.Text('notes'),
16-
active: types.Bool('active'),
16+
active: types.Boolean('active'),
1717
// camelCase JS property → snake_case DB name on purpose: the model path must
1818
// match models by JS property (`createdOn`) yet address the FFI/config by DB
1919
// name (`created_on`). The round-trip tests below exercise that mapping.

packages/stack/__tests__/schema-v3-pg.test.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ const table = encryptedTable('protect_ci_v3_text_search', {
1919
})
2020

2121
const typedTable = encryptedTable('protect_ci_v3_typed_domains', {
22-
age: types.Int4Ord('age'),
22+
age: types.IntegerOrd('age'),
2323
nickname: types.TextEq('nickname'),
24-
active: types.Bool('active'),
24+
active: types.Boolean('active'),
2525
})
2626

2727
const TEST_RUN_ID = `test-run-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`
@@ -107,9 +107,9 @@ beforeAll(async () => {
107107
await sql`
108108
CREATE TABLE IF NOT EXISTS protect_ci_v3_typed_domains (
109109
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
110-
age eql_v3.int4_ord NOT NULL,
110+
age eql_v3.integer_ord NOT NULL,
111111
nickname eql_v3.text_eq NOT NULL,
112-
active eql_v3.bool NOT NULL,
112+
active eql_v3.boolean NOT NULL,
113113
test_run_id TEXT NOT NULL
114114
)
115115
`
@@ -280,9 +280,9 @@ describeLivePg('eql_v3 text_search postgres integration', () => {
280280
const [inserted] = await sql<{ id: number }[]>`
281281
INSERT INTO protect_ci_v3_typed_domains (age, nickname, active, test_run_id)
282282
VALUES (
283-
${sql.json(age as postgres.JSONValue)}::eql_v3.int4_ord,
283+
${sql.json(age as postgres.JSONValue)}::eql_v3.integer_ord,
284284
${sql.json(nickname as postgres.JSONValue)}::eql_v3.text_eq,
285-
${sql.json(active as postgres.JSONValue)}::eql_v3.bool,
285+
${sql.json(active as postgres.JSONValue)}::eql_v3.boolean,
286286
${TEST_RUN_ID}
287287
)
288288
RETURNING id
@@ -310,7 +310,7 @@ describeLivePg('eql_v3 text_search postgres integration', () => {
310310
// regression proves `resolveIndexType` resolves equality to `ore` instead of
311311
// throwing; this proves the resulting term actually SELECTS the right rows
312312
// against real Postgres, using the SQL `=` operator on the ORE term.
313-
it('selects the exact row for an equality term via ORE on an int4_ord column', async () => {
313+
it('selects the exact row for an equality term via ORE on an integer_ord column', async () => {
314314
async function insertAge(age: number): Promise<number> {
315315
const ageCt = unwrapResult(
316316
await protectClient.encrypt(age, {
@@ -333,9 +333,9 @@ describeLivePg('eql_v3 text_search postgres integration', () => {
333333
const [row] = await sql<{ id: number }[]>`
334334
INSERT INTO protect_ci_v3_typed_domains (age, nickname, active, test_run_id)
335335
VALUES (
336-
${sql.json(ageCt)}::eql_v3.int4_ord,
336+
${sql.json(ageCt)}::eql_v3.integer_ord,
337337
${sql.json(nick)}::eql_v3.text_eq,
338-
${sql.json(act)}::eql_v3.bool,
338+
${sql.json(act)}::eql_v3.boolean,
339339
${TEST_RUN_ID}
340340
)
341341
RETURNING id

packages/stack/__tests__/schema-v3.test-d.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ describe('eql_v3 schema type inference', () => {
5656
it('InferPlaintext maps v3 concrete domains to plaintext TypeScript types', () => {
5757
const metrics = encryptedTable('metrics', {
5858
name: types.Text('name'),
59-
age: types.Int4('age'),
60-
active: types.Bool('active'),
59+
age: types.Integer('age'),
60+
active: types.Boolean('active'),
6161
createdAt: types.Timestamp('created_at'),
62-
score: types.Float8('score'),
62+
score: types.Double('score'),
6363
})
6464

6565
type Plaintext = InferPlaintext<typeof metrics>
@@ -75,12 +75,12 @@ describe('eql_v3 schema type inference', () => {
7575

7676
it('v3 domain classes remain nominal by literal domain definition', () => {
7777
const date = types.Date('created_on')
78-
const bool = types.Bool('active')
78+
const boolean = types.Boolean('active')
7979

80-
expectTypeOf(date).not.toEqualTypeOf<typeof bool>()
80+
expectTypeOf(date).not.toEqualTypeOf<typeof boolean>()
8181

82-
// @ts-expect-error - storage-only bool is not assignable to storage-only date
83-
const invalid: typeof date = bool
82+
// @ts-expect-error - storage-only boolean is not assignable to storage-only date
83+
const invalid: typeof date = boolean
8484
void invalid
8585
})
8686
})
@@ -194,7 +194,7 @@ describe('eql_v3 client integration (type-level acceptance)', () => {
194194
it('encryptQuery rejects storage-only v3 columns at compile time', () => {
195195
const users = encryptedTable('users', {
196196
email: types.Text('email'),
197-
active: types.Bool('active'),
197+
active: types.Boolean('active'),
198198
})
199199
const client = {} as EncryptionClient
200200

@@ -206,7 +206,7 @@ describe('eql_v3 client integration (type-level acceptance)', () => {
206206

207207
client.encryptQuery(true, {
208208
table: users,
209-
// @ts-expect-error - storage-only v3 bool column is not queryable
209+
// @ts-expect-error - storage-only v3 boolean column is not queryable
210210
column: users.active,
211211
})
212212
})
@@ -216,7 +216,7 @@ describe('eql_v3 model encryption inference', () => {
216216
it('encryptModel and bulkEncryptModels infer encrypted fields from v3 tables', () => {
217217
const users = encryptedTable('users', {
218218
email: types.TextSearch('email'),
219-
active: types.Bool('active'),
219+
active: types.Boolean('active'),
220220
})
221221
const client = {} as EncryptionClient
222222

packages/stack/__tests__/schema-v3.test.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,37 @@ describe('eql_v3 text_match column', () => {
156156
})
157157
})
158158

159+
describe('eql_v3 concrete type names', () => {
160+
it('exposes the renamed concrete type factories and emits the new EQL v3 domain names', () => {
161+
expect(types.Integer('n').getEqlType()).toBe('eql_v3.integer')
162+
expect(types.IntegerEq('n').getEqlType()).toBe('eql_v3.integer_eq')
163+
expect(types.IntegerOrdOre('n').getEqlType()).toBe('eql_v3.integer_ord_ore')
164+
expect(types.IntegerOrd('n').getEqlType()).toBe('eql_v3.integer_ord')
165+
166+
expect(types.Smallint('n').getEqlType()).toBe('eql_v3.smallint')
167+
expect(types.SmallintEq('n').getEqlType()).toBe('eql_v3.smallint_eq')
168+
expect(types.SmallintOrdOre('n').getEqlType()).toBe(
169+
'eql_v3.smallint_ord_ore',
170+
)
171+
expect(types.SmallintOrd('n').getEqlType()).toBe('eql_v3.smallint_ord')
172+
173+
expect(types.Bigint('n').getEqlType()).toBe('eql_v3.bigint')
174+
expect(types.BigintEq('n').getEqlType()).toBe('eql_v3.bigint_eq')
175+
expect(types.BigintOrdOre('n').getEqlType()).toBe('eql_v3.bigint_ord_ore')
176+
expect(types.BigintOrd('n').getEqlType()).toBe('eql_v3.bigint_ord')
177+
178+
expect(types.Boolean('b').getEqlType()).toBe('eql_v3.boolean')
179+
expect(types.Real('n').getEqlType()).toBe('eql_v3.real')
180+
expect(types.RealEq('n').getEqlType()).toBe('eql_v3.real_eq')
181+
expect(types.RealOrdOre('n').getEqlType()).toBe('eql_v3.real_ord_ore')
182+
expect(types.RealOrd('n').getEqlType()).toBe('eql_v3.real_ord')
183+
expect(types.Double('n').getEqlType()).toBe('eql_v3.double')
184+
expect(types.DoubleEq('n').getEqlType()).toBe('eql_v3.double_eq')
185+
expect(types.DoubleOrdOre('n').getEqlType()).toBe('eql_v3.double_ord_ore')
186+
expect(types.DoubleOrd('n').getEqlType()).toBe('eql_v3.double_ord')
187+
})
188+
})
189+
159190
describe('eql_v3 encryptedTable', () => {
160191
it('creates a table exposing column builders as properties', () => {
161192
const users = encryptedTable('users', {
@@ -402,7 +433,7 @@ describe('eql_v3 equality via ORE on order-capable columns (regression)', () =>
402433
// domains DO carry `hm` and resolve equality to `unique` instead — see the
403434
// text-order regression below.)
404435
it.each([
405-
['int4_ord', types.Int4Ord],
436+
['int4_ord', types.IntegerOrd],
406437
['date_ord', types.DateOrd],
407438
['numeric_ord', types.NumericOrd],
408439
] as const)('%s resolves equality to the ore index', (_name, builder) => {
@@ -439,8 +470,8 @@ describe('eql_v3 text order domains carry the hm (unique) index (regression)', (
439470
})
440471

441472
it.each([
442-
['int4_ord_ore', types.Int4OrdOre],
443-
['int4_ord', types.Int4Ord],
473+
['int4_ord_ore', types.IntegerOrdOre],
474+
['int4_ord', types.IntegerOrd],
444475
['date_ord_ore', types.DateOrdOre],
445476
['numeric_ord', types.NumericOrd],
446477
] as const)('%s (numeric/date order) emits ore only — no unique', (_name, builder) => {

packages/stack/__tests__/typed-client-v3.test-d.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ const users = encryptedTable('users', {
1919
createdAt: types.TimestampOrd('created_at'), // equality + order
2020
})
2121

22-
// A second registered table whose `weight` domain (int4_ord) is NOT present in
22+
// A second registered table whose `weight` domain (integer_ord) is NOT present in
2323
// `users`, so borrowing it is a genuine cross-table type error.
2424
const other = encryptedTable('other', {
25-
weight: types.Int4Ord('weight'),
25+
weight: types.IntegerOrd('weight'),
2626
})
2727

2828
const client = typedClient({} as EncryptionClient, users, other)
@@ -166,7 +166,7 @@ describe('typed v3 client — soundness', () => {
166166
// error is the column itself failing the `ColumnsOf<typeof users>` constraint.
167167
client.encrypt('x', {
168168
table: users,
169-
// @ts-expect-error - int4_ord column from `other` is not in ColumnsOf<typeof users>
169+
// @ts-expect-error - integer_ord column from `other` is not in ColumnsOf<typeof users>
170170
column: other.weight,
171171
})
172172
})

0 commit comments

Comments
 (0)