fix(cli): scaffold EQL v3 encryption client from stash init#734
Conversation
stash init installs an EQL v3 database but the client scaffolding still emitted v2 authoring patterns (Encryption, encryptedColumn chains, encryptedType flag objects), handing the customer's agent v2 guidance against a v3 schema. Follow-up to #732. - Rewrite DRIZZLE_PLACEHOLDER / GENERIC_PLACEHOLDER to EncryptionV3, the types.* concrete-domain factories, and the stack-drizzle/v3 entry. - Port the live introspection generators (generateClientFromSchemas + generateDrizzleFromSchemas / generateGenericFromSchemas) to v3 via a new v3DomainFactory() mapping SearchOp sets to fixed-capability domains. - Delete the dead singular generators (generateClientFromSchema, generateDrizzleFromSchema, generateSchemaFromDef, drizzleTsType) that had no callers rather than porting v2 dead code. encryptionClient export shape and the empty-schema error path unchanged.
🦋 Changeset detectedLatest commit: 04f5a13 The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
📝 WalkthroughWalkthrough
ChangesEQL v3 scaffolding
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related issues
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
freshtonic
left a comment
There was a problem hiding this comment.
Overview
Fixes a real drift: stash init installs an EQL v3 database (since #705), but the client scaffold it wrote into the customer's repo still taught v2 authoring (Encryption, encryptedColumn().equality().freeTextSearch(), encryptedType<T>('x', { equality: true })). Since that scaffold is what the customer's coding agent reads to do the real schema work, the scaffold and the database disagreed. This ports the two live surfaces in init/utils.ts — the DRIZZLE_PLACEHOLDER/GENERIC_PLACEHOLDER templates and generateClientFromSchemas — to the v3 surface (EncryptionV3, types.*, @cipherstash/stack-drizzle/v3).
Tight, well-scoped, and correct. LGTM / approvable — details below; I found no blocking issues.
What I verified
- Every v3 API the scaffold now teaches is real. Confirmed against the branch: the
typesnamespace (packages/stack/src/eql/v3/types.ts) exposes all 13 factory names the mapper emits (Text/TextEq/TextOrd/TextMatch/TextSearch,Integer/IntegerEq/IntegerOrd,Date/DateEq/DateOrd,Boolean,Json);@cipherstash/stack/v3re-exportsEncryptionV3+encryptedTable+types(its own docstring shows the identical import the scaffold writes); and@cipherstash/stack-drizzle/v3exportsextractEncryptionSchemaV3and atypesnamespace that is a typed 1:1 mirror (Object.fromEntriesover the stacktypes, keyed[K in keyof V3Types]), sotypes.TextSearch(...)etc. resolve on the Drizzle side too. No broken import or unknown factory can be scaffolded. v3DomainFactoryis exhaustive and the capability mapping is right.DataTypeis exactly'string' | 'number' | 'boolean' | 'date' | 'json', and theswitchreturns on every arm — no path yieldsundefined(which would emittypes.undefined(...)). The capability→domain picks are correct:ord → *Ord(which also answers equality),eq → *Eq, else storage-only; for stringstext && (eq||ord) → TextSearch,text → TextMatch. Matches the fixed-capability domains ineql/v3/columns.ts.- The deletions are safe.
generateClientFromSchema(singular, exported),generateDrizzleFromSchema,generateSchemaFromDef, anddrizzleTsTypehave zero callers anywhere inpackages/,examples/, ore2e/(grepped the branch) —stash schema builduses the pluralgenerateClientFromSchemas. Removing unreachable v2 codegen rather than porting it to dead v3 codegen is the right call. - Changeset (
stashpatch) is correct for a CLI-only scaffold change; theEncryptionV3({ schemas: [] })empty-schema throw path is preserved (delegates toEncryption), sostash encrypt's "no schemas yet" error is unchanged.
Notes — non-blocking, all acknowledged in the PR
- Numeric collapses to
Integer*. Anumbercolumn that's actually float/decimal scaffolds asIntegerOrd/IntegerEq/Integer— the wrong domain for float data. The introspectedSearchOpmodel carries no width/precision signal (v2 collapsed to onedataType: 'number'too), and the template is explicitly "pattern reference, do NOT use as-is" with the user's real schema authoritative. Correctly deferred to #733. Fine for a scaffold. - Boolean equality is silently dropped. v2 allowed
equalityon a boolean; v3 has no boolean eq/ord domain, so a boolean introspected withequalitymaps to storage-onlyBoolean. The generated code is valid v3 and the PR surfaces the difference — just worth being aware the scaffold quietly narrows that capability. - Skills: this changes user-facing scaffold output, and AGENTS.md asks to check skills on user-facing workflow changes. The
stash-cli/stash-encryptionskills document commands rather than reproducing these placeholder strings, so nothing here looks stale — but worth a glance to confirm no skill snippet still shows the oldEncryption/encryptedTypeinit scaffold (the broader v2→v3 skill refresh is happening in sibling PRs).
Verdict
Correct and complete for its scope: the scaffold now teaches v3 APIs that all exist, the capability mapping matches the real domains, and the removed code is genuinely unreachable. The notes are non-blocking and already tracked. I'd approve this — say the word and I'll flip it to a formal approval.
| }) | ||
|
|
||
| const ${schemaVarName} = extractEncryptionSchema(${varName})` | ||
| const ${schemaVarName} = extractEncryptionSchemaV3(${varName})` |
There was a problem hiding this comment.
LOL I've been slowly removing the V3 suffixes and Claude keeps trying to add them back.
With V2 gone, this can just be named extractEncryptionSchema.
There was a problem hiding this comment.
@coderdan I have definitely had the same conversation with Claude. 🙄
There was a problem hiding this comment.
Tracked and decided — see the "Naming — decided" section in #707: v3 takes the unsuffixed name and *V3 becomes a @deprecated alias. Drizzle is the explicit exception — a hard break, because collapsing the ./v3 subpath makes extractEncryptionSchemaV3 → extractEncryptionSchema collide with today's v2 root name. That's why this scaffold can't drop the suffix yet: it imports from @cipherstash/stack/v3 and @cipherstash/stack-drizzle/v3, which only export the suffixed names until the collapse lands. Blocked on the v2-removal prerequisites (#693, #657). Leaving open as a tracked follow-up rather than resolving here.
| ${tableDefs.join('\n\n')} | ||
|
|
||
| export const encryptionClient = await Encryption({ | ||
| export const encryptionClient = await EncryptionV3({ |
There was a problem hiding this comment.
Same here. Its clunky and forces the user to understand a version number that they shouldn't have to know about.
There was a problem hiding this comment.
Tracked and decided — see the "Naming — decided" section in #707: v3 takes the unsuffixed name and *V3 becomes a @deprecated alias. Drizzle is the explicit exception — a hard break, because collapsing the ./v3 subpath makes extractEncryptionSchemaV3 → extractEncryptionSchema collide with today's v2 root name. That's why this scaffold can't drop the suffix yet: it imports from @cipherstash/stack/v3 and @cipherstash/stack-drizzle/v3, which only export the suffixed names until the collapse lands. Blocked on the v2-removal prerequisites (#693, #657). Leaving open as a tracked follow-up rather than resolving here.
coderdan
left a comment
There was a problem hiding this comment.
Approved but I have feelings about the naming :p
Closes #733
stash initinstalls an EQL v3 database (since #705) but the client scaffold it writes into the customer's repo still taught v2 —Encryption,encryptedColumn('x').equality().freeTextSearch(),encryptedType<T>('x', { equality: true }). The scaffold and the database disagreed, and the scaffold is what the customer's coding agent reads to do the real schema work.What changed
Two live surfaces in
init/utils.tsported to v3:DRIZZLE_PLACEHOLDER/GENERIC_PLACEHOLDER) — whatstash initand theeql installsafety net write. The inline pattern-reference comments now showEncryptionV3,types.*, and the@cipherstash/stack-drizzle/v3entry.generateClientFromSchemas(Drizzle + generic) — reached viastash schema build.A shared helper maps a
SearchOpset to the fixed-capability v3 domain, verified againstpackages/stack/src/eql/v3/columns.ts:Text/TextEq/TextOrd/TextMatch/TextSearchInteger/IntegerEq/IntegerOrdDate/DateEq/DateOrdBoolean(storage-only)JsonDeleted, not ported
generateClientFromSchema(singular) +generateDrizzleFromSchema+generateSchemaFromDef+drizzleTsType— zero callers anywhere inpackages/. (stash schema buildimports the plural.) Porting unreachable v2 code to teach v3 would just be dead code in a new dialect.Notes
equalityon a boolean; v3 has no boolean eq/ord domain, so booleans map to storage-only.Integer*— there's no width/float signal in the introspectedSearchOpmodel (v2 collapsed everything todataType: 'number'too). Distinct float/decimal scaffolding needs a richer introspection signal; out of scope, noted on stash init scaffolds the EQL v2 authoring surface into customer repos, against a v3 database #733.EncryptionV3({ schemas: [] })throw is preserved —EncryptionV3delegates toEncryption, sostash encrypt's "no schemas yet" error path is unchanged.Verification
pnpm --filter stash test: 53 files, 638 tests passedpnpm --filter "stash..." build: successpnpm run code:check: 0 errors (pre-existing warnings only)build-schema.test.tsmocks the generator;utils.test.tsasserts nothing about the strings)'stash': patch)🤖 Generated with Claude Code
Summary by CodeRabbit
stash initnow scaffolds EQL v3 encryption clients compatible with the EQL v3 database schema.