Skip to content

refactor(stack): EQL v3 types namespace on @cipherstash/stack/eql/v3#541

Open
tobyhede wants to merge 6 commits into
feat/eql-v3-text-search-schemafrom
feat/eql-v3-types-module
Open

refactor(stack): EQL v3 types namespace on @cipherstash/stack/eql/v3#541
tobyhede wants to merge 6 commits into
feat/eql-v3-text-search-schemafrom
feat/eql-v3-types-module

Conversation

@tobyhede

@tobyhede tobyhede commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Refactors the EQL v3 authoring surface: the per-domain encrypted<Domain>Column factories become a single types namespace whose members mirror the underlying eql_v3.<name> domains, on the renamed @cipherstash/stack/eql/v3 subpath.

import { encryptedTable, types } from "@cipherstash/stack/eql/v3";

const events = encryptedTable("events", {
  actor:     types.TextEq("actor"),           // equality
  weight:    types.Int4Ord("weight"),         // order + range
  createdAt: types.Timestamptz("created_at"), // storage only
});

Before → after — the name maps 1:1 to the EQL v3 domain:

// before                                          // after
encryptedTextEqColumn("actor")                     types.TextEq("actor")          // eql_v3.text_eq
encryptedInt4OrdColumn("weight")                    types.Int4Ord("weight")        // eql_v3.int4_ord
encryptedTimestamptzColumn("created_at")            types.Timestamptz("created_at")// eql_v3.timestamptz
encryptedTextSearchColumn("email").freeTextSearch() types.TextSearch("email").freeTextSearch()

Per-domain plaintext inference and compile-time queryability are unchanged:

import { Encryption } from "@cipherstash/stack";
import type { InferPlaintext } from "@cipherstash/stack/eql/v3";

type Events = InferPlaintext<typeof events>;
// { actor: string; weight: number; createdAt: Date }

const client = await Encryption({ schemas: [events] });

await client.encryptQuery(30, { table: events, column: events.weight, queryType: "orderAndRange" });
await client.encryptQuery(new Date(), { table: events, column: events.createdAt });
//                                                            ^ type error: storage-only, not queryable

The @cipherstash/stack/v3 typed client re-exports types (in place of the standalone builders):

import { EncryptionV3, encryptedTable, types } from "@cipherstash/stack/v3";

const users = encryptedTable("users", { email: types.TextSearch("email") });
const client = await EncryptionV3({ schemas: [users] });

await client.encrypt("a@b.com", { table: users, column: users.email }); // ok
await client.encrypt(123,       { table: users, column: users.email }); // ✗ number ≠ string

types members

One member per generated EQL v3 domain (PascalCase of the eql_v3.<name>):

Int4 Int4Eq Int4OrdOre Int4Ord · Int2* · Date* · Timestamptz* · Numeric* · Text TextEq TextMatch TextOrdOre TextOrd TextSearch · Bool · Float4* Float8* (int8/bigint still omitted pending lossless FFI I/O). Each returns its concrete branded class, so per-column inference stays precise.

Scope

  • Splits the 992-line schema/v3/index.ts into src/eql/v3/{columns,types,table,index}.ts.
  • Renames the subpath schema/v3eql/v3 (exports, tsup entry, FTA gate, ./v3 re-export); the old subpath and the standalone factories are removed.
  • Behaviour preserved: same classes, nominal typing, and build() output (text_search stays byte-identical).

Verified: schema/v3 no longer resolves (ERR_PACKAGE_PATH_NOT_EXPORTED) and factories are undefined on both subpaths; 101 v3 runtime + 50 type tests pass; FTA scores all 4 files (max 68.68 < 72). Stacked on #535.

@tobyhede tobyhede requested a review from a team as a code owner July 3, 2026 01:13
@changeset-bot

changeset-bot Bot commented Jul 3, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 2a078b8

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes changesets to release 6 packages
Name Type
@cipherstash/stack Minor
@cipherstash/bench Patch
@cipherstash/prisma-next Patch
@cipherstash/basic-example Patch
@cipherstash/prisma-next-example Patch
@cipherstash/e2e Patch

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 59e155c8-0047-4336-adf3-7bdd9985c320

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/eql-v3-types-module

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

tobyhede added 6 commits July 3, 2026 11:55
Replace the 35 verbose `encrypted<Domain>Column` factories with a single
`types` namespace whose members mirror the underlying `eql_v3.<name>` domains
1:1 (`types.TextEq`, `types.Int4Ord`, `types.Timestamptz`, …), and split the
992-line `src/schema/v3/index.ts` into a cohesive module under `src/eql/v3/`
(`columns.ts`, `types.ts`, `table.ts`, curated `index.ts`).

The authoring subpath is renamed `@cipherstash/stack/schema/v3` ->
`@cipherstash/stack/eql/v3`; the `./v3` typed-client surface now re-exports the
`types` namespace instead of the standalone factories. Behaviour is preserved:
same classes, same nominal-typing mechanism, same `build()` output.

- Rewire tsup entry, package.json exports/typesVersions/analyze:complexity,
  the `@/eql/v3` re-export, `[eql/v3]` error prefix, and fta-v3.yml paths.
- Migrate all v3 tests + CJS smoke test to `types.*` and the new subpath.
- Reconcile the three unreleased changesets and refresh tracked v3 design docs
  (supersede banners on completed-work records; correct the not-yet-built
  Stryker gate spec's single-file premise for the 4-file split).

Verified: build emits dist/eql/v3; schema/v3 subpath and factories are gone
(ERR_PACKAGE_PATH_NOT_EXPORTED, undefined on both subpaths); 101 v3 runtime +
50 type tests pass; FTA scores all 4 files (max 68.68 < 72); e2e authoring
config byte-matches expected.
Two JS properties whose builders resolve to the same DB name (getName())
silently overwrote in the built config — the later column won and the first's
config was dropped. Throw instead, matching the existing duplicate-tableName
guard in buildEncryptConfig and the reserved-key guard in encryptedTable.

Regression tests: `EncryptedTable.build()` and `buildEncryptConfig` both throw
on a duplicate DB name (schema-v3.test.ts, eql_v3 encryptedTable block).
The structural builder contracts (BuildableColumn, BuildableQueryColumn,
BuildableV3QueryableColumn, BuildableTable, BuildableTableColumns) and the
encryptModel/bulkEncryptModels return-type mapper (EncryptedFromBuildableTable)
appear in public return positions but were not re-exported from
`@cipherstash/stack/types`, so consumers could not name them — an inconsistency
with the already-exposed `EncryptedFromSchema`. No build breakage (the mapped
types were emitted inline); this closes the nameability gap.

Regression guard: types-public-surface.test-d.ts imports each contract from the
public `@/types-public` entrypoint (a missing re-export fails typecheck).

Note: these types are inherited from the base branch (feat/eql-v3-text-search-schema,
PR #535); the export is added here in response to review feedback on the stacked PR.
The v3-matrix domain suite (catalog.ts + matrix tests) landed on the base
branch via PR #540 after this branch was cut, and used the pre-refactor
`@/schema/v3` path and `encrypted<Domain>Column` factories. Retarget it to
`@/eql/v3` and the `types.*` namespace so the base's matrix coverage keeps
working on top of the refactor. `EqlTypeForColumn` (which #540's catalog.ts
consumes) is preserved — ported into eql/v3/columns.ts and re-exported from the
barrel during the rebase.

Post-rebase reconciliation only; no behavior change.
@tobyhede tobyhede force-pushed the feat/eql-v3-types-module branch from b98ad89 to 2a078b8 Compare July 3, 2026 02:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant