fix(adapters): make the two shipped adapters agree - #59
Merged
Conversation
Three places the sequelize and valkey adapters were required to produce the same answer and quietly did not. Each is a definition a caller could reasonably write and expect to be portable; each was found by consolidating a near-duplicate and discovering the two copies had drifted apart. Enum types. The sequelize adapter capitalised the generated type name and sanitised every member into a legal GraphQL name; the valkey adapter did neither. So the same model produced `TaskstatusEnum` on one backend and `TaskStatusEnum` on the other, and a member such as `in-progress` or `2xl` was rejected outright by graphql — names must match /^[_a-zA-Z][_a-zA-Z0-9]*$/. That throw landed nowhere near the cause: `new GraphQLEnumType` builds its values lazily, so construction succeeded and the failure surfaced wherever something first materialised them, naming `assertEnumValueName` rather than the definition. Both adapters now call `createEnumType` in `@azerothian/graphql-types` — the one GraphQL-aware package both already depend on, since `utilize` is deliberately GraphQL-free. Only the schema-facing name is rewritten; the value reaching the backend is the member exactly as declared. Two members differing only in punctuation now raise a build-time error naming both, instead of collapsing into one value and leaving the loser unqueryable. Bare constructors. `type: String` was accepted by the valkey adapter and passed straight through by the sequelize one into `sequelize.define`, where `normalizeDataType` does `new Type()` and yields a `String` wrapper object with no `.key` — not a valid Sequelize type, failing later in DDL generation rather than at define time. The token table now lives once, as `authoredDataType` in `@azerothian/utilize/types/data-type`, and both adapters consult it. Additive on sequelize: nothing that worked before stops working. `Number` maps to `Int`, not `Float` — JavaScript has one numeric type and no way to say which was meant, and silently widening an id column to floating point loses precision. Accessor names. `getAssociations` reported valkey's relationship accessor names from the shared `relationshipAccessors` table while `tag()`, which actually defines them, derived them a second way through a local helper and string interpolation. Two sources of truth for one set of names, in one file — exactly what that helper's docstring says must not happen, because a cross-adapter relationship looks accessors up by the reported name and finds nothing if the two drift. They agreed by coincidence; nothing enforced it. `tag()` now uses the table, `relNames` is gone, and `pluralize` drops out of the package entirely. Also renames temporalize's exported `OrderEntry` to `SortEntry`. It collided with utilize's `OrderEntry`, a different and non-assignable shape on the barrel of a package temporalize depends on. The shapes differ for a good reason — the bare-string spelling is a convenience this layer accepts and passes through — so the fix is one name each, not one type. It is restated rather than imported because `workflow-types.ts` is bundled into the Temporal workflow sandbox and its header requires the module stay import-free. The parity tests run against both backends, which is the point: a single-adapter test would have passed throughout. Verified they fail without the fix. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019fGumVzMfMXDZ5vS7PJGDW
This was referenced Aug 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Group 1 of the condense/dedupe/optimise pass. Three places the sequelize and valkey adapters were required to produce the same answer and quietly did not — each found by consolidating a near-duplicate and discovering the two copies had drifted.
These are correctness fixes, not tidying. A single-adapter test would have passed throughout the period they were broken, which is why the new tests run against both backends.
1. Enum types
TaskStatusEnumTaskstatusEnumin-progressinProgressGraphQL names must match
/^[_a-zA-Z][_a-zA-Z0-9]*$/. A member likein-progressor2xlwas used verbatim as the enum value name and graphql refused it — and the throw landed nowhere near the cause:new GraphQLEnumTypebuilds its values lazily, so construction succeeded and the failure surfaced wherever something first materialised them, namingassertEnumValueNamerather than the definition that declared the member.Both adapters now call
createEnumTypein@azerothian/graphql-types— the one GraphQL-aware package both already depend on (utilizeis deliberately GraphQL-free, so it is the wrong home).Only the schema-facing name is rewritten; the value reaching the backend is the member exactly as declared. Two members differing only in punctuation (
in-progress/in progress) now raise a build-time error naming both, rather than collapsing into one value and leaving the loser unqueryable.2. Bare JS constructors
type: Stringwas accepted by valkey and passed straight through by sequelize intosequelize.define, wherenormalizeDataTypedoesnew Type()and yields aStringwrapper object with no.key— not a valid Sequelize type, failing later in DDL generation rather than at define time.The table now lives once as
authoredDataTypein@azerothian/utilize/types/data-type. Additive on sequelize — nothing that worked before stops working.Numbermaps toInt, notFloat: JavaScript has one numeric type and no way to say which was meant, and silently widening an id column to floating point loses precision.3. Relationship accessor names
getAssociationsreported valkey's accessor names from the sharedrelationshipAccessorstable, whiletag()— which actually defines them — derived them a second way via a local helper and string interpolation. Two sources of truth for one set of names, in one file.That helper's own docstring says exactly this must not happen: a cross-adapter relationship looks accessors up by the reported name and finds nothing if the two drift. They agreed by coincidence; nothing enforced it.
tag()now uses the table,relNamesis gone, andpluralizedrops out of the package entirely.Also
Renames temporalize's exported
OrderEntry→SortEntry. It collided with utilize'sOrderEntry, a different and non-assignable shape on the barrel of a package temporalize depends on. The shapes differ for a good reason — the bare-string form is a convenience temporalize accepts and passes through — so the fix is one name each, not one type. Restated rather than imported:workflow-types.tsis bundled into the Temporal workflow sandbox and its header requires the module stay import-free.Breaking
Documented in
docs/migration-6-to-7.mdwith a members table:TaskstatusEnum→TaskStatusEnum). A persisted schema artifact built against valkey must be rebuilt.OrderEntry→SortEntryin@azerothian/temporalize.Verification
Confirmed the new tests are real pins — stashed the fix and re-ran: the bare-constructor case fails on sequelize, the enum-name case fails on valkey.
pnpm typecheckcleanpnpm lintclean at--max-warnings 0🤖 Generated with Claude Code
https://claude.ai/code/session_019fGumVzMfMXDZ5vS7PJGDW