feat(errors): @constructive-io/errors package + server code normalization & class-based masking#1415
Open
pyramation wants to merge 2 commits into
Open
feat(errors): @constructive-io/errors package + server code normalization & class-based masking#1415pyramation wants to merge 2 commits into
pyramation wants to merge 2 commits into
Conversation
Add a standalone, zero-dependency @constructive-io/errors package (canonical registry, parse/format/classify, i18n, type-safe factory), make pgpm consume it, and normalize DB codes into GraphQL extensions with class-based masking.
Contributor
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
Collect every constructive-db EXCEPTION/THROW code (287) into a generated registry layer with public/internal class, HTTP hint, and message (public copy seeded from dashboard catalogs). Curated typed entries override generated ones. Reproducible via scripts/generate-registry.py from a committed audit snapshot.
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
Phase 1 of the Constructive error system (plan issue #1235): a standalone, zero-dependency
@constructive-io/errorspackage that is the single source of truth for error codes, messages, i18n, and cross-source parsing — plus wiring it into the GraphQL server so database error codes actually reach clients as structuredextensions.code, and pgpm's old error factory now consumes it.Forward-only: the package owns the canonical shape; no backwards-compat shims.
packages/errors(new)A registry-driven system. Each code is defined once with its class/HTTP/message; factories, formatting, and classification derive from it.
parse()recovers a code by precedence: structuredDETAILJSON ({"code","context"}) → GraphQLextensions.code→ leadingALL_CAPSmessage token incl. legacy dynamicCODE (arg, arg)(mapped onto named context via a per-codepositionallist) → native SQLSTATE constraint mapping (23505→UNIQUE_VIOLATION, …). Unknown ⇒known:false, class:'internal'.Full registry — every constructive-db error collected (287 codes)
The registry is now two layers merged at lookup time (curated wins):
src/generated/registry.generated.ts— all 287 codes raised viaEXCEPTION/THROWacross constructive-db (deploy sources + generated output, incl. the 16 that only appear in generated SQL). Each carries apublic/internalclass, HTTP hint, and default message; public copy is seeded from the dashboard error catalogs where a matching code exists, developer/invariant codes keep their raw message (%-args rendered as{{argN}}). Split: 83 public / 204 internal (unknown/uncertain fail safe to internal, so they stay masked).src/registry.ts— refined, typed-context entries (public auth/limit copy, native pg constraint aliases, pgpm CLI codes) that override the generated ones.Reproducible, not hand-maintained:
reads a committed audit snapshot (
scripts/db-error-inventory.json) + optional dashboard checkout → regenerates the generated layer.GENERATED_CODE_METArecords each code'sclass/dynamic/generatedOnlyfor auditing.graphql/server—maskErrornow normalizes + masks by classThe bug (confirmed empirically in Phase 0): DB errors reach Grafast with an empty
extensions— the semantic code lives only in the message (and any SQLSTATE/DETAIL onoriginalError). So the oldSAFE_ERROR_CODESallowlist, which matched onextensions.code, never matched DB errors.Net effect: clients now always get a machine-readable
extensions.codefor recognized errors (the real win), public errors pass through in prod, internal/unknown stay masked.SAFE_ERROR_CODESis retained as a transitional allowlist (GraphQL protocol + not-yet-registered auth codes) until the registry is generated.pgpm/types— factory becomes a thin consumererror-factory.ts(previously ~148 lines of inline English + a loosemakeError) is now just:Call sites (
throw errors.MODULE_NOT_FOUND({ name }),errors.DEPLOYMENT_FAILED({ type, module }), …) are unchanged; messages/context types match the old definitions.PgpmErroris left in place for anything importing it directly.Validation
@constructive-io/errorsbuild + tests: 21/21 pass.graphql/serverand@pgpmjs/typesbuilds: green.pnpm install --frozen-lockfile: consistent (lockfile edited by hand to add only the new package + twoworkspace:^links, avoiding a full lockfile rewrite by the local pnpm).Later phases (separate PRs): add structured
RAISE ... USING ERRCODE, DETAIL=<json>upstream in constructive-db (so context travels without string-parsing), convert user-facing phrase-only throws into codes, and migrate the dashboard/blocks consumers onto the package (retiring the transitionalSAFE_ERROR_CODESallowlist and the duplicated client error systems).Link to Devin session: https://app.devin.ai/sessions/f4d3ce9225894883ba9cfc9ed2f0ba7e
Requested by: @pyramation