@nice-code/error 0.2.9 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +237 -5
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,5 +1,237 @@
1
- # @nice-code/error
2
-
3
- Typed, serializable errors with domain hierarchies, pattern matching, and safe transport across API boundaries.
4
-
5
- See the [main README](../../README.md) for full documentation and examples.
1
+ # @nice-code/error
2
+
3
+ Typed, serializable errors with domain hierarchies, pattern matching, and safe transport across API boundaries.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ bun add @nice-code/error
9
+ ```
10
+
11
+ ## Core concepts
12
+
13
+ - **Domain** — a named group of related errors (`err_user_auth`, `err_payment`)
14
+ - **Schema** — maps error id strings to message, HTTP status, and optional typed context
15
+ - **Error id** — identifies what went wrong within a domain; multiple ids can be active on one error
16
+ - **Context** — structured data attached to an id (e.g. `{ username: string }`)
17
+ - **Hydration** — deserializing context from a JSON round-trip back to the original typed value
18
+
19
+ ---
20
+
21
+ ## Defining an error domain
22
+
23
+ ```ts
24
+ import { defineNiceError, err } from "@nice-code/error";
25
+
26
+ export const err_auth = defineNiceError({
27
+ domain: "err_auth",
28
+ schema: {
29
+ // No context — second arg to fromId is not accepted
30
+ account_locked: err({
31
+ message: "Account is locked",
32
+ httpStatusCode: 403,
33
+ }),
34
+
35
+ // Required context — second arg to fromId is required
36
+ invalid_credentials: err<{ username: string }>({
37
+ message: ({ username }) => `Invalid credentials for: ${username}`,
38
+ httpStatusCode: 401,
39
+ context: { required: true },
40
+ }),
41
+
42
+ // Optional context
43
+ rate_limited: err<{ retryAfter: number }>({
44
+ message: (ctx) => ctx ? `Retry after ${ctx.retryAfter}s` : "Rate limited",
45
+ httpStatusCode: 429,
46
+ context: {},
47
+ }),
48
+ },
49
+ } as const);
50
+ ```
51
+
52
+ ### `err()` — schema entry helper
53
+
54
+ | Schema entry | `fromId("id")` | `fromId("id", ctx)` |
55
+ |---|---|---|
56
+ | `err()` / `err({ message, httpStatusCode })` | ✓ | ✗ |
57
+ | `err<C>({ context: {} })` | ✓ optional | ✓ optional |
58
+ | `err<C>({ context: { required: true } })` | ✗ | ✓ required |
59
+
60
+ ### Custom serialization (non-JSON-safe context)
61
+
62
+ ```ts
63
+ fs_error: err<{ cause: NodeJS.ErrnoException }>({
64
+ message: ({ cause }) => `FS error: ${cause.message}`,
65
+ context: {
66
+ required: true,
67
+ serialization: {
68
+ toJsonSerializable: ({ cause }) => ({ code: cause.code, message: cause.message }),
69
+ fromJsonSerializable: (obj) => ({ cause: Object.assign(new Error(obj.message), obj) }),
70
+ },
71
+ },
72
+ }),
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Creating errors
78
+
79
+ ```ts
80
+ // Single id — context required per schema
81
+ const err1 = err_auth.fromId("invalid_credentials", { username: "alice" });
82
+
83
+ // Multi-id in one error
84
+ const err2 = err_auth.fromContext({
85
+ invalid_credentials: { username: "bob" },
86
+ rate_limited: { retryAfter: 30 },
87
+ });
88
+
89
+ // Chain additional ids after construction
90
+ const err3 = err_auth.fromId("account_locked").addId("rate_limited");
91
+ ```
92
+
93
+ ---
94
+
95
+ ## Reading errors
96
+
97
+ ```ts
98
+ // Check and narrow type
99
+ if (err1.hasId("invalid_credentials")) {
100
+ const { username } = err1.getContext("invalid_credentials"); // typed
101
+ }
102
+
103
+ // Check multiple ids at once
104
+ if (err2.hasOneOfIds(["invalid_credentials", "rate_limited"])) {
105
+ // ACTIVE_IDS narrowed to that subset
106
+ }
107
+
108
+ // Pattern match — calls first matching handler, returns its result
109
+ import { matchFirst } from "@nice-code/error";
110
+
111
+ const message = matchFirst(err1, {
112
+ invalid_credentials: ({ username }) => `Wrong password for ${username}`,
113
+ account_locked: () => "Account locked",
114
+ _: () => "Unknown auth error",
115
+ });
116
+ ```
117
+
118
+ ---
119
+
120
+ ## Domain hierarchies
121
+
122
+ ```ts
123
+ const err_app = defineNiceError({ domain: "err_app", schema: {} } as const);
124
+
125
+ const err_auth = err_app.createChildDomain({
126
+ domain: "err_auth",
127
+ schema: { /* ... */ },
128
+ });
129
+
130
+ const err_auth_registration = err_auth.createChildDomain({
131
+ domain: "err_auth_registration",
132
+ schema: { /* ... */ },
133
+ });
134
+
135
+ // Ancestry checks
136
+ err_app.isParentOf(err_auth_registration); // true
137
+ err_auth.isParentOf(err_auth_registration); // true
138
+ err_auth_registration.isParentOf(err_auth); // false
139
+
140
+ // Type guard — exact domain match only
141
+ err_auth.isExact(someError); // true only for err_auth domain
142
+ err_auth.isThisOrChild(someError); // true for err_auth and all children
143
+ ```
144
+
145
+ ---
146
+
147
+ ## Serialization & transport
148
+
149
+ ```ts
150
+ // Serialize to a plain JSON object (safe to send over HTTP)
151
+ const json = error.toJsonObject();
152
+ const str = error.toJsonString();
153
+ const response = error.toHttpResponse(); // Response with correct HTTP status
154
+
155
+ // On the receiving side — reconstruct from anything
156
+ import { castNiceError } from "@nice-code/error";
157
+
158
+ const caught = castNiceError(unknownValue); // always returns NiceError
159
+
160
+ if (err_auth.isExact(caught)) {
161
+ const hydrated = err_auth.hydrate(caught); // deserializes context
162
+ const { username } = hydrated.getContext("invalid_credentials");
163
+ }
164
+ ```
165
+
166
+ `castNiceError` handles: `NiceError` instances, serialized JSON objects, native `Error`, error-like objects, nullish values, and primitives.
167
+
168
+ ---
169
+
170
+ ## Error handling with handlers
171
+
172
+ ### Functional style (composable)
173
+
174
+ ```ts
175
+ import { forDomain, forId, forIds } from "@nice-code/error";
176
+
177
+ const handled = error.handleWithSync([
178
+ forId(err_auth, "invalid_credentials", (h) => {
179
+ const { username } = h.getContext("invalid_credentials");
180
+ return res.status(401).json({ error: `Bad credentials for ${username}` });
181
+ }),
182
+ forIds(err_auth, ["account_locked", "rate_limited"], (h) => {
183
+ return res.status(h.httpStatusCode).json({ error: h.message });
184
+ }),
185
+ forDomain(err_payment, (h) => {
186
+ return res.status(500).json({ error: "Payment failed" });
187
+ }),
188
+ ]);
189
+
190
+ // Async handlers
191
+ await error.handleWithAsync([
192
+ forDomain(err_payment, async (h) => {
193
+ await db.logFailure(h.message);
194
+ await notify(h.toJsonObject());
195
+ }),
196
+ ]);
197
+ ```
198
+
199
+ ### Class-based style (reusable handler)
200
+
201
+ ```ts
202
+ import { NiceErrorHandler } from "@nice-code/error";
203
+
204
+ const handler = new NiceErrorHandler()
205
+ .forId(err_auth, "invalid_credentials", (h) => "unauthorized")
206
+ .forDomain(err_payment, (h) => "payment_error")
207
+ .setDefaultHandler((e) => "unknown_error");
208
+
209
+ handler.handleErrorWithPromiseInspection(error);
210
+ ```
211
+
212
+ ---
213
+
214
+ ## Utility types
215
+
216
+ ```ts
217
+ import type { InferNiceError, InferNiceErrorHydrated } from "@nice-code/error";
218
+
219
+ type TAuthError = InferNiceError<typeof err_auth>;
220
+ // → NiceError<{ domain: "err_auth"; ... }, keyof schema>
221
+
222
+ type TAuthErrorHydrated = InferNiceErrorHydrated<typeof err_auth>;
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Packing for transport
228
+
229
+ Useful when errors must travel through systems that don't preserve custom properties (e.g. some RPC layers).
230
+
231
+ ```ts
232
+ import { EErrorPackType } from "@nice-code/error";
233
+
234
+ error.pack(EErrorPackType.msg_pack); // embeds JSON in error.message
235
+ error.pack(EErrorPackType.cause_pack); // embeds JSON in error.cause
236
+ error.unpack(); // restore original
237
+ ```
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "module": "build/index.js",
5
5
  "types": "build/types/index.d.ts",
6
6
  "type": "module",
7
- "version": "0.2.9",
7
+ "version": "0.2.10",
8
8
  "exports": {
9
9
  ".": {
10
10
  "types": "./build/types/index.d.ts",