🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

@constructive-io/errors

Package Overview
Dependencies
Maintainers
3
Versions
11
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@constructive-io/errors - npm Package Compare versions

Comparing version
0.9.0
to
0.10.0
+30
esm/http.d.ts
/** Status used when a code carries no mapping. */
export declare const UNMAPPED_HTTP_STATUS = 500;
export interface HttpStatusResolution {
/** The status a transport should send. */
status: number;
/**
* Whether `status` came from the registry. `false` means the code is not
* registered and `status` is the {@link UNMAPPED_HTTP_STATUS} fallback — the
* one case where a 500 does not mean "the server broke".
*/
mapped: boolean;
}
/** Notified once per unmapped code. */
export type UnmappedStatusReporter = (code: string) => void;
/**
* Replace the unmapped-code reporter (pass `null` to restore the default, or a
* no-op to silence it). Transports with a real logger should route it there.
*/
export declare function setUnmappedStatusReporter(next: UnmappedStatusReporter | null): void;
/** Test seam: forget which codes have already been reported. */
export declare function resetUnmappedStatusReports(): void;
/**
* Resolve the HTTP status for an error code.
*
* An unregistered code is reported (once per code) rather than silently
* degrading to 500: a refusal that is plainly a 403 or 409 turning into a 500
* looks like a crash, and the only way anyone notices is by reading the
* transport's source.
*/
export declare function httpStatusFor(code: string | null | undefined): HttpStatusResolution;
import { getDefinition } from './registry';
/** Status used when a code carries no mapping. */
export const UNMAPPED_HTTP_STATUS = 500;
const reported = new Set();
const defaultReporter = code => {
// eslint-disable-next-line no-console
console.warn(`[constructive-errors] no HTTP status mapping for ${code}; responding ${UNMAPPED_HTTP_STATUS}. ` +
'Register the code so intentional refusals stop surfacing as server errors.');
};
let reporter = defaultReporter;
/**
* Replace the unmapped-code reporter (pass `null` to restore the default, or a
* no-op to silence it). Transports with a real logger should route it there.
*/
export function setUnmappedStatusReporter(next) {
reporter = next ?? defaultReporter;
}
/** Test seam: forget which codes have already been reported. */
export function resetUnmappedStatusReports() {
reported.clear();
}
/**
* Resolve the HTTP status for an error code.
*
* An unregistered code is reported (once per code) rather than silently
* degrading to 500: a refusal that is plainly a 403 or 409 turning into a 500
* looks like a crash, and the only way anyone notices is by reading the
* transport's source.
*/
export function httpStatusFor(code) {
const def = code ? getDefinition(code) : undefined;
if (def)
return { status: def.http, mapped: true };
if (code && !reported.has(code)) {
reported.add(code);
reporter(code);
}
return { status: UNMAPPED_HTTP_STATUS, mapped: false };
}
/** Status used when a code carries no mapping. */
export declare const UNMAPPED_HTTP_STATUS = 500;
export interface HttpStatusResolution {
/** The status a transport should send. */
status: number;
/**
* Whether `status` came from the registry. `false` means the code is not
* registered and `status` is the {@link UNMAPPED_HTTP_STATUS} fallback — the
* one case where a 500 does not mean "the server broke".
*/
mapped: boolean;
}
/** Notified once per unmapped code. */
export type UnmappedStatusReporter = (code: string) => void;
/**
* Replace the unmapped-code reporter (pass `null` to restore the default, or a
* no-op to silence it). Transports with a real logger should route it there.
*/
export declare function setUnmappedStatusReporter(next: UnmappedStatusReporter | null): void;
/** Test seam: forget which codes have already been reported. */
export declare function resetUnmappedStatusReports(): void;
/**
* Resolve the HTTP status for an error code.
*
* An unregistered code is reported (once per code) rather than silently
* degrading to 500: a refusal that is plainly a 403 or 409 turning into a 500
* looks like a crash, and the only way anyone notices is by reading the
* transport's source.
*/
export declare function httpStatusFor(code: string | null | undefined): HttpStatusResolution;
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UNMAPPED_HTTP_STATUS = void 0;
exports.setUnmappedStatusReporter = setUnmappedStatusReporter;
exports.resetUnmappedStatusReports = resetUnmappedStatusReports;
exports.httpStatusFor = httpStatusFor;
const registry_1 = require("./registry");
/** Status used when a code carries no mapping. */
exports.UNMAPPED_HTTP_STATUS = 500;
const reported = new Set();
const defaultReporter = code => {
// eslint-disable-next-line no-console
console.warn(`[constructive-errors] no HTTP status mapping for ${code}; responding ${exports.UNMAPPED_HTTP_STATUS}. ` +
'Register the code so intentional refusals stop surfacing as server errors.');
};
let reporter = defaultReporter;
/**
* Replace the unmapped-code reporter (pass `null` to restore the default, or a
* no-op to silence it). Transports with a real logger should route it there.
*/
function setUnmappedStatusReporter(next) {
reporter = next ?? defaultReporter;
}
/** Test seam: forget which codes have already been reported. */
function resetUnmappedStatusReports() {
reported.clear();
}
/**
* Resolve the HTTP status for an error code.
*
* An unregistered code is reported (once per code) rather than silently
* degrading to 500: a refusal that is plainly a 403 or 409 turning into a 500
* looks like a crash, and the only way anyone notices is by reading the
* transport's source.
*/
function httpStatusFor(code) {
const def = code ? (0, registry_1.getDefinition)(code) : undefined;
if (def)
return { status: def.http, mapped: true };
if (code && !reported.has(code)) {
reported.add(code);
reporter(code);
}
return { status: exports.UNMAPPED_HTTP_STATUS, mapped: false };
}
+3
-3
/**
* GENERATED FILE — DO NOT EDIT BY HAND.
*
* Source of truth: the constructive-db error audit (531 distinct codes
* Source of truth: the constructive-db error audit (546 distinct codes
* raised via EXCEPTION/THROW across deploy sources + generated output).

@@ -12,3 +12,3 @@ * Regenerate with `python3 scripts/generate-registry.py` (see README.md).

*
* Counts: 531 total, 401 public, 130 internal.
* Counts: 546 total, 410 public, 136 internal.
*/

@@ -28,2 +28,2 @@ import { type DefinedError } from '../define';

/** Total number of codes collected from constructive-db. */
export declare const GENERATED_CODE_COUNT = 531;
export declare const GENERATED_CODE_COUNT = 546;

@@ -5,2 +5,3 @@ export * from './classify';

export * from './format';
export * from './http';
export * from './interpolate';

@@ -7,0 +8,0 @@ export * from './parse';

@@ -5,2 +5,3 @@ export * from './classify';

export * from './format';
export * from './http';
export * from './interpolate';

@@ -7,0 +8,0 @@ export * from './parse';

@@ -28,4 +28,6 @@ import { ConstructiveError } from './error';

* error's raw message when the code is unknown) and the registry's HTTP hint.
* Codes that could not be resolved become `UNKNOWN_ERROR` (internal).
* Codes that could not be resolved become `UNKNOWN_ERROR` (internal); a code
* with no registered status is reported by {@link httpStatusFor} rather than
* quietly becoming a 500.
*/
export declare function toError(error: unknown, locale?: string): ConstructiveError;
import { classify } from './classify';
import { ConstructiveError } from './error';
import { format } from './format';
import { httpStatusFor } from './http';
import { extractPgErrorFields, RAISE_EXCEPTION_SQLSTATE, SQLSTATE_TO_CODE } from './pg';

@@ -181,3 +182,5 @@ import { getDefinition } from './registry';

* error's raw message when the code is unknown) and the registry's HTTP hint.
* Codes that could not be resolved become `UNKNOWN_ERROR` (internal).
* Codes that could not be resolved become `UNKNOWN_ERROR` (internal); a code
* with no registered status is reported by {@link httpStatusFor} rather than
* quietly becoming a 500.
*/

@@ -197,5 +200,5 @@ export function toError(error, locale) {

errorClass: parsed.class,
http: def?.http ?? 500,
http: def ? def.http : httpStatusFor(code).status,
context: parsed.context
});
}
/**
* GENERATED FILE — DO NOT EDIT BY HAND.
*
* Source of truth: the constructive-db error audit (531 distinct codes
* Source of truth: the constructive-db error audit (546 distinct codes
* raised via EXCEPTION/THROW across deploy sources + generated output).

@@ -12,3 +12,3 @@ * Regenerate with `python3 scripts/generate-registry.py` (see README.md).

*
* Counts: 531 total, 401 public, 130 internal.
* Counts: 546 total, 410 public, 136 internal.
*/

@@ -28,2 +28,2 @@ import { type DefinedError } from '../define';

/** Total number of codes collected from constructive-db. */
export declare const GENERATED_CODE_COUNT = 531;
export declare const GENERATED_CODE_COUNT = 546;

@@ -5,2 +5,3 @@ export * from './classify';

export * from './format';
export * from './http';
export * from './interpolate';

@@ -7,0 +8,0 @@ export * from './parse';

@@ -21,2 +21,3 @@ "use strict";

__exportStar(require("./format"), exports);
__exportStar(require("./http"), exports);
__exportStar(require("./interpolate"), exports);

@@ -23,0 +24,0 @@ __exportStar(require("./parse"), exports);

{
"name": "@constructive-io/errors",
"version": "0.9.0",
"version": "0.10.0",
"author": "Constructive <developers@constructive.io>",

@@ -44,3 +44,3 @@ "description": "Canonical Constructive error system: registry, codes, i18n messages, and cross-source (PostgreSQL/GraphQL/client) parsing. Zero runtime dependencies.",

},
"gitHead": "d508453a32a2f1d70ff6916871046615226e6f33"
"gitHead": "43121004a8bedd2c5fcf7bf33208079d55bad01f"
}

@@ -28,4 +28,6 @@ import { ConstructiveError } from './error';

* error's raw message when the code is unknown) and the registry's HTTP hint.
* Codes that could not be resolved become `UNKNOWN_ERROR` (internal).
* Codes that could not be resolved become `UNKNOWN_ERROR` (internal); a code
* with no registered status is reported by {@link httpStatusFor} rather than
* quietly becoming a 500.
*/
export declare function toError(error: unknown, locale?: string): ConstructiveError;

@@ -8,2 +8,3 @@ "use strict";

const format_1 = require("./format");
const http_1 = require("./http");
const pg_1 = require("./pg");

@@ -186,3 +187,5 @@ const registry_1 = require("./registry");

* error's raw message when the code is unknown) and the registry's HTTP hint.
* Codes that could not be resolved become `UNKNOWN_ERROR` (internal).
* Codes that could not be resolved become `UNKNOWN_ERROR` (internal); a code
* with no registered status is reported by {@link httpStatusFor} rather than
* quietly becoming a 500.
*/

@@ -202,5 +205,5 @@ function toError(error, locale) {

errorClass: parsed.class,
http: def?.http ?? 500,
http: def ? def.http : (0, http_1.httpStatusFor)(code).status,
context: parsed.context
});
}

@@ -18,2 +18,5 @@ # @constructive-io/errors

(fail safe) so transports never leak unregistered errors.
- **`httpStatusFor(code)`** — the canonical code → HTTP status mapping, so no
transport keeps its own table. Unregistered codes answer `500` *and are
reported*, never silently.

@@ -50,2 +53,28 @@ ```ts

## HTTP status
Every registry entry carries `http`, so an HTTP surface never needs its own
code → status table: `toError(err).http`, or `httpStatusFor(code)` when all you
have is a code.
```ts
import { httpStatusFor, setUnmappedStatusReporter, toError } from '@constructive-io/errors';
setUnmappedStatusReporter(code => log.warn({ code }, 'error code has no HTTP status'));
const err = toError(caught);
res.status(err.http).json(err.toExtensions());
httpStatusFor('ACCOUNT_DISABLED'); // { status: 403, mapped: true }
httpStatusFor('BRAND_NEW_CODE'); // { status: 500, mapped: false } + one report
```
`mapped: false` is the one case where a 500 does not mean "the server broke" —
it means the code never reached the registry. That is the failure mode this
exists to make loud: a refusal that is plainly a 403 or a 409 answering 500
looks like a crash, and the codes most likely to be missing are the newest ones.
When a constructive-db release adds codes, refresh the registry (below);
`__tests__/registry-sync.test.ts` fails if the snapshot and the generated
registry disagree.
## Regenerating the full registry

@@ -52,0 +81,0 @@

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display