Generate TypeScript error-code declarations from the registry - #357
Open
lmars wants to merge 1 commit into
Open
Conversation
The JS SDKs each name error codes themselves, and the names have drifted from this registry: 15 of ably-chat-js's 20 disagree with the code's `identifier`, and ably-js uses bare numeric literals at ~149 call sites. Generating the names instead makes `identifier` the single spelling every SDK uses, and makes registering a code here the only step needed before referring to it. errors/scripts/generate-ts.js emits one of two shapes. `--format=type` gives a bare `ErrorCode` union of numeric literals, for a consumer that wants compile-time checking at no bundle-size cost — the type erases, and because registry codes are 5-6 digits while HTTP statuses are 3, a `code` and `statusCode` transposed at a call site fails to compile. `--format=const` gives one documented `export const` per code plus the union, for consumers that need the values at runtime; individual consts rather than an aggregate object or a TS `enum` so that unused codes tree-shake out of the browser bundles those SDKs ship. Nothing generated is committed here. Each consuming repository generates from the submodule it already vendors, commits the output into its own src/, and regenerates in CI at the pinned commit to fail on a diff. That mirrors how ably/docs publishes the error pages, and means an SDK cannot merge a reference to a code that is not merged here first. The generator refuses to emit rather than produce an unusable file, on a duplicate `identifier`, two identifiers colliding on one PascalCase name, or a name that is not a valid JavaScript identifier. None of the three occurs in the registry today. Those failures, a bad argument, and an unwritable `--out` all print as a plain message and exit 1; a stack trace there would only be noise, so one now indicates a bug in the generator rather than a problem with the input. Output is sorted by numeric code and byte-identical for a given registry state, as the drift check requires, and depends only on Node's standard library and the local frontmatter parser, so it runs from a superproject with no `npm install` inside the submodule. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lmars
force-pushed
the
error-code-typescript-gen
branch
from
July 31, 2026 15:11
ee284fd to
b0ae20c
Compare
lmars
added a commit
to ably/ably-ai-transport-js
that referenced
this pull request
Jul 31, 2026
The AIT error codes are now registered, so pin ably-common at the merge of ably/ably-common#353. That brings in 104009, 104012 and 104013, which the previous commit's enum referred to before they existed, and renames 104003's identifier to run_lifecycle_event_publish_failed. protocol/errors.json no longer maps a code straight to a description string. Entries now sit under a "codes" envelope, each an object with an identifier, title and summary. validate-error-codes.ts indexed the top level, so against the bumped pin it would have found nothing and reported all 19 codes as missing - a misleading failure rather than a clean pass. It now reads codes and prints each entry's identifier, and reports an absent envelope as a stale or uninitialised submodule instead of letting it read as 19 unregistered codes. The check compares codes, not names. Generating the constants from the registry (ably/ably-common#357) is what makes the registry identifier the one spelling every SDK uses, and replaces this script. [AIT-1259] Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Summary
Adds
errors/scripts/generate-ts.js, which turns the registry inerrors/codes/into TypeScript error-code declarations for the JavaScript SDKs. Nothing generated is committed here — this PR adds the generator, its tests, and thegenerate:errorcodes-tsscript.The JS SDKs currently name error codes themselves, and the names have drifted from the registry: 15 of
ably-chat-js's 20 disagree with the code'sidentifier, andably-jsuses bare numeric literals at ~149 call sites (five of which hadcodeandstatusCodetransposed). Generating the names makesidentifierthe single spelling every SDK uses, and makes registering a code here the only step needed before referring to it — which is also whyidentifieris a frozen contract rather than something to churn.The two output formats
--format=typeemits a bare union of numeric literals:This is for
ably-js, which gates PRs on bundle size. The type erases at compile time, so it costs zero bytes, and existing numeric literals already satisfy the union — so all ~149 call sites keep compiling untouched while gaining the checking. Because registry codes are 5–6 digits and HTTP statuses are 3, a transposedcode/statusCodepair is caught exactly rather than heuristically.--format=constemits one documented constant per code, then the union:This is for the wrapper SDKs, which need the values at runtime. Individual consts rather than an aggregate object or a TS
enum, because object properties and enum members don't tree-shake — a 260-member enum would land whole in the bundles those SDKs ship to browsers. The JSDoc carries the registrytitle,summary, and help link, so hovering a constant in an editor shows what the code means.How consumers use it
Each SDK runs the generator against the
ably-commonsubmodule it already vendors, commits the output into its ownsrc/, and adds a CI step that regenerates at the pinned commit and diffs — the same arrangement asably/docsand its error pages. Two properties that buys: a developer can register a code and use it without publishing anything, and because CI regenerates at the pinned commit, an SDK can't merge a reference to a code that isn't merged here first.Output is sorted by numeric code and byte-identical for a given registry state, as that drift check requires, and depends only on Node's standard library plus the local
frontmatter.js— so it runs from a superproject with nonpm installinside the submodule.Failure behaviour
The generator refuses to emit rather than produce an unusable file, on a duplicate
identifier, two identifiers colliding on one PascalCase name, or a name that isn't a valid JavaScript identifier. None of the three occurs in the registry today; the assertions are to keep it that way.Those failures, a bad argument, an absent
errors/codes/, and an unwritable--outall print as a plain message and exit 1. A stack trace there would only be noise, so one now indicates a bug in the generator rather than a problem with the input. The absent-registry message calls out an uninitialised or stale submodule specifically, sinceably-js's pin predateserrors/codes/existing and that's the first thing anyone adopting this there will hit.Verification
tsc --strict.const d: ErrorCode = 400(a transposed HTTP status) and an unregistered12345are both rejected, which is the propertyably-jswants. Not wired into the test suite, since TypeScript isn't a dependency here.ably-chat-jsandably-ai-transport-jsare produced exactly as tabulated.The
.eslintrc.jschange belongs to the tests: they assert through anexpectCleanFailurehelper, whichjest/expect-expectreads as a test with no assertions, soassertFunctionNamesis configured fortest/**rather than disabling the rule.Follow-ups, not in this PR
ably-jsneeds its pin moved off496da5e, which predateserrors/codes/.ably-chat-jsandably-ai-transport-jsare breaking, since both exportErrorCodeas a TSenumfrom their public API — one major, or@deprecatedaliases for a release.isErrorCode(n): n is ErrorCodeguard for narrowing a relayed wire code. It needs the full set as runtime data, so it can't be part of thetypeoutput.errors/scripts/generate-errors-json.jshas the stack-trace-on-registry-fault behaviour that this generator now avoids; worth aligning separately.🤖 Generated with Claude Code