wellcrafted 0.35.0 → 0.37.0

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 (41) hide show
  1. package/dist/error/index.d.ts +4 -74
  2. package/dist/error/index.js +3 -106
  3. package/dist/error-DLVm7dsh.js +150 -0
  4. package/dist/error-DLVm7dsh.js.map +1 -0
  5. package/dist/index-BNWUFJ69.d.ts +129 -0
  6. package/dist/index-BNWUFJ69.d.ts.map +1 -0
  7. package/dist/{index-mb9iYu0a.d.ts → index-DnoV2ZDO.d.ts} +2 -2
  8. package/dist/{index-mb9iYu0a.d.ts.map → index-DnoV2ZDO.d.ts.map} +1 -1
  9. package/dist/json.d.ts +57 -1
  10. package/dist/json.d.ts.map +1 -1
  11. package/dist/json.js +51 -0
  12. package/dist/json.js.map +1 -0
  13. package/dist/logger/index.d.ts +40 -6
  14. package/dist/logger/index.d.ts.map +1 -1
  15. package/dist/logger/index.js +39 -5
  16. package/dist/logger/index.js.map +1 -1
  17. package/dist/query/index.d.ts +3 -3
  18. package/dist/query/index.js +3 -3
  19. package/dist/result/index.d.ts +3 -3
  20. package/dist/result/index.js +3 -3
  21. package/dist/{result-DzL3K2yA.js → result-C5cJ1_WU.js} +9 -4
  22. package/dist/result-C5cJ1_WU.js.map +1 -0
  23. package/dist/{result-corfYKOe.js → result-C9V2Knvt.js} +2 -2
  24. package/dist/{result-corfYKOe.js.map → result-C9V2Knvt.js.map} +1 -1
  25. package/dist/{result-BongGO2O.d.ts → result-DKwq9BCr.d.ts} +9 -4
  26. package/dist/result-DKwq9BCr.d.ts.map +1 -0
  27. package/dist/{tap-err-Bqs9aQpZ.d.ts → tap-err-CFhHBPfH.d.ts} +2 -2
  28. package/dist/{tap-err-Bqs9aQpZ.d.ts.map → tap-err-CFhHBPfH.d.ts.map} +1 -1
  29. package/dist/{tap-err-Bf6rQ4Cw.js → tap-err-CP-re1HT.js} +2 -2
  30. package/dist/{tap-err-Bf6rQ4Cw.js.map → tap-err-CP-re1HT.js.map} +1 -1
  31. package/dist/testing.d.ts +53 -0
  32. package/dist/testing.d.ts.map +1 -0
  33. package/dist/testing.js +59 -0
  34. package/dist/testing.js.map +1 -0
  35. package/dist/{types-cY80Bw6u.d.ts → types-tXXk7K9Q.d.ts} +2 -2
  36. package/dist/{types-cY80Bw6u.d.ts.map → types-tXXk7K9Q.d.ts.map} +1 -1
  37. package/package.json +5 -1
  38. package/dist/error/index.d.ts.map +0 -1
  39. package/dist/error/index.js.map +0 -1
  40. package/dist/result-BongGO2O.d.ts.map +0 -1
  41. package/dist/result-DzL3K2yA.js.map +0 -1
@@ -1,74 +1,4 @@
1
- import "../result-BongGO2O.js";
2
- import { AnyTaggedError, DefineErrorsReturn, ErrorBody, ErrorsConfig, InferError, InferErrors, ValidatedConfig } from "../types-cY80Bw6u.js";
3
-
4
- //#region src/error/defineErrors.d.ts
5
-
6
- /**
7
- * Defines a set of typed error factories using Rust-style namespaced variants.
8
- *
9
- * Each key is a short variant name (the namespace provides context). Every
10
- * factory returns `Err<...>` directly — ready for `trySync`/`tryAsync` catch
11
- * handlers. The variant name is stamped as `name` on the error object.
12
- *
13
- * @example
14
- * ```ts
15
- * const HttpError = defineErrors({
16
- * Connection: ({ cause }: { cause: unknown }) => ({
17
- * message: `Failed to connect: ${extractErrorMessage(cause)}`,
18
- * cause,
19
- * }),
20
- * Response: ({ status }: { status: number; bodyMessage?: string }) => ({
21
- * message: `HTTP ${status}`,
22
- * status,
23
- * }),
24
- * Parse: ({ cause }: { cause: unknown }) => ({
25
- * message: `Failed to parse response body: ${extractErrorMessage(cause)}`,
26
- * cause,
27
- * }),
28
- * });
29
- *
30
- * type HttpError = InferErrors<typeof HttpError>;
31
- *
32
- * const result = HttpError.Connection({ cause: 'timeout' }); // Err<...>
33
- * ```
34
- *
35
- * Inspired by Rust's {@link https://docs.rs/thiserror | thiserror} crate. The
36
- * mapping is nearly 1:1:
37
- *
38
- * - `enum HttpError` → `const HttpError = defineErrors(...)`
39
- * - Variant `Connection { cause: String }` → key `Connection: ({ cause }: { cause: unknown }) => (...)`
40
- * - `#[error("Failed: {cause}")]` → `` message: `Failed: ${extractErrorMessage(cause)}` ``
41
- * - `HttpError::Connection { ... }` → `HttpError.Connection({ ... })`
42
- * - `match error { Connection { .. } => }` → `switch (error.name) { case 'Connection': }`
43
- *
44
- * The equivalent Rust `thiserror` enum:
45
- * ```rust
46
- * #[derive(Error, Debug)]
47
- * enum HttpError {
48
- * #[error("Failed to connect: {cause}")]
49
- * Connection { cause: String },
50
- *
51
- * #[error("HTTP {status}")]
52
- * Response { status: u16, body_message: Option<String> },
53
- *
54
- * #[error("Failed to parse response body: {cause}")]
55
- * Parse { cause: String },
56
- * }
57
- * ```
58
- */
59
- declare function defineErrors<const TConfig extends ErrorsConfig>(config: TConfig & ValidatedConfig<TConfig>): DefineErrorsReturn<TConfig>;
60
- //# sourceMappingURL=defineErrors.d.ts.map
61
- //#endregion
62
- //#region src/error/extractErrorMessage.d.ts
63
- /**
64
- * Extracts a readable error message from an unknown error value
65
- *
66
- * @param error - The unknown error to extract a message from
67
- * @returns A string representation of the error
68
- */
69
- declare function extractErrorMessage(error: unknown): string;
70
- //# sourceMappingURL=extractErrorMessage.d.ts.map
71
-
72
- //#endregion
73
- export { AnyTaggedError, DefineErrorsReturn, ErrorBody, ErrorsConfig, InferError, InferErrors, ValidatedConfig, defineErrors, extractErrorMessage };
74
- //# sourceMappingURL=index.d.ts.map
1
+ import "../result-DKwq9BCr.js";
2
+ import { AnyTaggedError, DefineErrorsReturn, ErrorBody, ErrorsConfig, InferError, InferErrors, ValidatedConfig } from "../types-tXXk7K9Q.js";
3
+ import { DefineHttpErrorsReturn, HttpErrorFactory, HttpErrorsConfig, InferHttpError, InferHttpErrors, ValidatedHttpConfig, defineErrors, defineHttpErrors, extractErrorMessage } from "../index-BNWUFJ69.js";
4
+ export { AnyTaggedError, DefineErrorsReturn, DefineHttpErrorsReturn, ErrorBody, ErrorsConfig, HttpErrorFactory, HttpErrorsConfig, InferError, InferErrors, InferHttpError, InferHttpErrors, ValidatedConfig, ValidatedHttpConfig, defineErrors, defineHttpErrors, extractErrorMessage };
@@ -1,107 +1,4 @@
1
- import { Err } from "../result-DzL3K2yA.js";
1
+ import "../result-C5cJ1_WU.js";
2
+ import { defineErrors, defineHttpErrors, extractErrorMessage } from "../error-DLVm7dsh.js";
2
3
 
3
- //#region src/error/defineErrors.ts
4
- /**
5
- * Defines a set of typed error factories using Rust-style namespaced variants.
6
- *
7
- * Each key is a short variant name (the namespace provides context). Every
8
- * factory returns `Err<...>` directly — ready for `trySync`/`tryAsync` catch
9
- * handlers. The variant name is stamped as `name` on the error object.
10
- *
11
- * @example
12
- * ```ts
13
- * const HttpError = defineErrors({
14
- * Connection: ({ cause }: { cause: unknown }) => ({
15
- * message: `Failed to connect: ${extractErrorMessage(cause)}`,
16
- * cause,
17
- * }),
18
- * Response: ({ status }: { status: number; bodyMessage?: string }) => ({
19
- * message: `HTTP ${status}`,
20
- * status,
21
- * }),
22
- * Parse: ({ cause }: { cause: unknown }) => ({
23
- * message: `Failed to parse response body: ${extractErrorMessage(cause)}`,
24
- * cause,
25
- * }),
26
- * });
27
- *
28
- * type HttpError = InferErrors<typeof HttpError>;
29
- *
30
- * const result = HttpError.Connection({ cause: 'timeout' }); // Err<...>
31
- * ```
32
- *
33
- * Inspired by Rust's {@link https://docs.rs/thiserror | thiserror} crate. The
34
- * mapping is nearly 1:1:
35
- *
36
- * - `enum HttpError` → `const HttpError = defineErrors(...)`
37
- * - Variant `Connection { cause: String }` → key `Connection: ({ cause }: { cause: unknown }) => (...)`
38
- * - `#[error("Failed: {cause}")]` → `` message: `Failed: ${extractErrorMessage(cause)}` ``
39
- * - `HttpError::Connection { ... }` → `HttpError.Connection({ ... })`
40
- * - `match error { Connection { .. } => }` → `switch (error.name) { case 'Connection': }`
41
- *
42
- * The equivalent Rust `thiserror` enum:
43
- * ```rust
44
- * #[derive(Error, Debug)]
45
- * enum HttpError {
46
- * #[error("Failed to connect: {cause}")]
47
- * Connection { cause: String },
48
- *
49
- * #[error("HTTP {status}")]
50
- * Response { status: u16, body_message: Option<String> },
51
- *
52
- * #[error("Failed to parse response body: {cause}")]
53
- * Parse { cause: String },
54
- * }
55
- * ```
56
- */
57
- function defineErrors(config) {
58
- const result = {};
59
- for (const [name, ctor] of Object.entries(config)) result[name] = (...args) => {
60
- const body = ctor(...args);
61
- return Err(Object.freeze({
62
- ...body,
63
- name
64
- }));
65
- };
66
- return result;
67
- }
68
-
69
- //#endregion
70
- //#region src/error/extractErrorMessage.ts
71
- /**
72
- * Extracts a readable error message from an unknown error value
73
- *
74
- * @param error - The unknown error to extract a message from
75
- * @returns A string representation of the error
76
- */
77
- function extractErrorMessage(error) {
78
- if (error instanceof Error) return error.message;
79
- if (typeof error === "string") return error;
80
- if (typeof error === "number" || typeof error === "boolean" || typeof error === "bigint") return String(error);
81
- if (typeof error === "symbol") return error.toString();
82
- if (error === null) return "null";
83
- if (error === void 0) return "undefined";
84
- if (Array.isArray(error)) return JSON.stringify(error);
85
- if (typeof error === "object") {
86
- const errorObj = error;
87
- const messageProps = [
88
- "message",
89
- "error",
90
- "description",
91
- "title",
92
- "reason",
93
- "details"
94
- ];
95
- for (const prop of messageProps) if (prop in errorObj && typeof errorObj[prop] === "string") return errorObj[prop];
96
- try {
97
- return JSON.stringify(error);
98
- } catch {
99
- return String(error);
100
- }
101
- }
102
- return String(error);
103
- }
104
-
105
- //#endregion
106
- export { defineErrors, extractErrorMessage };
107
- //# sourceMappingURL=index.js.map
4
+ export { defineErrors, defineHttpErrors, extractErrorMessage };
@@ -0,0 +1,150 @@
1
+ import { Err } from "./result-C5cJ1_WU.js";
2
+
3
+ //#region src/error/defineErrors.ts
4
+ /**
5
+ * Defines a set of typed error factories using Rust-style namespaced variants.
6
+ *
7
+ * Each key is a short variant name (the namespace provides context). Every
8
+ * factory returns `Err<...>` directly — ready for `trySync`/`tryAsync` catch
9
+ * handlers. The variant name is stamped as `name` on the error object.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const HttpError = defineErrors({
14
+ * Connection: ({ cause }: { cause: unknown }) => ({
15
+ * message: `Failed to connect: ${extractErrorMessage(cause)}`,
16
+ * cause,
17
+ * }),
18
+ * Response: ({ status }: { status: number; bodyMessage?: string }) => ({
19
+ * message: `HTTP ${status}`,
20
+ * status,
21
+ * }),
22
+ * Parse: ({ cause }: { cause: unknown }) => ({
23
+ * message: `Failed to parse response body: ${extractErrorMessage(cause)}`,
24
+ * cause,
25
+ * }),
26
+ * });
27
+ *
28
+ * type HttpError = InferErrors<typeof HttpError>;
29
+ *
30
+ * const result = HttpError.Connection({ cause: 'timeout' }); // Err<...>
31
+ * ```
32
+ *
33
+ * Inspired by Rust's {@link https://docs.rs/thiserror | thiserror} crate. The
34
+ * mapping is nearly 1:1:
35
+ *
36
+ * - `enum HttpError` → `const HttpError = defineErrors(...)`
37
+ * - Variant `Connection { cause: String }` → key `Connection: ({ cause }: { cause: unknown }) => (...)`
38
+ * - `#[error("Failed: {cause}")]` → `` message: `Failed: ${extractErrorMessage(cause)}` ``
39
+ * - `HttpError::Connection { ... }` → `HttpError.Connection({ ... })`
40
+ * - `match error { Connection { .. } => }` → `switch (error.name) { case 'Connection': }`
41
+ *
42
+ * The equivalent Rust `thiserror` enum:
43
+ * ```rust
44
+ * #[derive(Error, Debug)]
45
+ * enum HttpError {
46
+ * #[error("Failed to connect: {cause}")]
47
+ * Connection { cause: String },
48
+ *
49
+ * #[error("HTTP {status}")]
50
+ * Response { status: u16, body_message: Option<String> },
51
+ *
52
+ * #[error("Failed to parse response body: {cause}")]
53
+ * Parse { cause: String },
54
+ * }
55
+ * ```
56
+ */
57
+ function defineErrors(config) {
58
+ const result = {};
59
+ for (const [name, ctor] of Object.entries(config)) result[name] = (...args) => {
60
+ const body = ctor(...args);
61
+ return Err(Object.freeze({
62
+ ...body,
63
+ name
64
+ }));
65
+ };
66
+ return result;
67
+ }
68
+
69
+ //#endregion
70
+ //#region src/error/defineHttpErrors.ts
71
+ /**
72
+ * Defines a set of typed HTTP error factories, each paired with its HTTP
73
+ * status code.
74
+ *
75
+ * Like `defineErrors`, each factory stamps `name` onto the error and wraps it
76
+ * in `Err`. Unlike `defineErrors`, each factory function carries a static
77
+ * `.status` property with the literal HTTP status code — the status is never
78
+ * included in the serialized error body.
79
+ *
80
+ * @example
81
+ * ```ts
82
+ * const AssetError = defineHttpErrors({
83
+ * MissingFile: [400, () => ({ message: 'Missing file' })],
84
+ * FileTooLarge: [413, ({ size }: { size: number }) => ({ message: `File too large: ${size}`, size })],
85
+ * StorageLimitExceeded: [402, () => ({ message: 'Storage limit exceeded' })],
86
+ * });
87
+ *
88
+ * type AssetError = InferHttpErrors<typeof AssetError>;
89
+ *
90
+ * // In a Hono route handler:
91
+ * return c.json(AssetError.MissingFile(), AssetError.MissingFile.status);
92
+ * // wire body: { error: { name: 'MissingFile', message: 'Missing file' }, data: null }
93
+ * // http status: 400
94
+ * ```
95
+ */
96
+ function defineHttpErrors(config) {
97
+ const result = {};
98
+ for (const [name, [status, factory]] of Object.entries(config)) {
99
+ const fn = (...args) => {
100
+ const body = factory(...args);
101
+ return Err(Object.freeze({
102
+ ...body,
103
+ name
104
+ }));
105
+ };
106
+ fn.status = status;
107
+ result[name] = fn;
108
+ }
109
+ return result;
110
+ }
111
+
112
+ //#endregion
113
+ //#region src/error/extractErrorMessage.ts
114
+ /**
115
+ * Extracts a readable error message from an unknown error value
116
+ *
117
+ * @param error - The unknown error to extract a message from
118
+ * @returns A string representation of the error
119
+ */
120
+ function extractErrorMessage(error) {
121
+ if (error instanceof Error) return error.message;
122
+ if (typeof error === "string") return error;
123
+ if (typeof error === "number" || typeof error === "boolean" || typeof error === "bigint") return String(error);
124
+ if (typeof error === "symbol") return error.toString();
125
+ if (error === null) return "null";
126
+ if (error === void 0) return "undefined";
127
+ if (Array.isArray(error)) return JSON.stringify(error);
128
+ if (typeof error === "object") {
129
+ const errorObj = error;
130
+ const messageProps = [
131
+ "message",
132
+ "error",
133
+ "description",
134
+ "title",
135
+ "reason",
136
+ "details"
137
+ ];
138
+ for (const prop of messageProps) if (prop in errorObj && typeof errorObj[prop] === "string") return errorObj[prop];
139
+ try {
140
+ return JSON.stringify(error);
141
+ } catch {
142
+ return String(error);
143
+ }
144
+ }
145
+ return String(error);
146
+ }
147
+
148
+ //#endregion
149
+ export { defineErrors, defineHttpErrors, extractErrorMessage };
150
+ //# sourceMappingURL=error-DLVm7dsh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error-DLVm7dsh.js","names":["config: TConfig & ValidatedConfig<TConfig>","result: Record<string, unknown>","config: TConfig & ValidatedHttpConfig<TConfig>","result: Record<string, unknown>","error: unknown"],"sources":["../src/error/defineErrors.ts","../src/error/defineHttpErrors.ts","../src/error/extractErrorMessage.ts"],"sourcesContent":["import { Err } from \"../result/result.js\";\nimport type {\n\tDefineErrorsReturn,\n\tErrorsConfig,\n\tValidatedConfig,\n} from \"./types.js\";\n\n/**\n * Defines a set of typed error factories using Rust-style namespaced variants.\n *\n * Each key is a short variant name (the namespace provides context). Every\n * factory returns `Err<...>` directly — ready for `trySync`/`tryAsync` catch\n * handlers. The variant name is stamped as `name` on the error object.\n *\n * @example\n * ```ts\n * const HttpError = defineErrors({\n * Connection: ({ cause }: { cause: unknown }) => ({\n * message: `Failed to connect: ${extractErrorMessage(cause)}`,\n * cause,\n * }),\n * Response: ({ status }: { status: number; bodyMessage?: string }) => ({\n * message: `HTTP ${status}`,\n * status,\n * }),\n * Parse: ({ cause }: { cause: unknown }) => ({\n * message: `Failed to parse response body: ${extractErrorMessage(cause)}`,\n * cause,\n * }),\n * });\n *\n * type HttpError = InferErrors<typeof HttpError>;\n *\n * const result = HttpError.Connection({ cause: 'timeout' }); // Err<...>\n * ```\n *\n * Inspired by Rust's {@link https://docs.rs/thiserror | thiserror} crate. The\n * mapping is nearly 1:1:\n *\n * - `enum HttpError` → `const HttpError = defineErrors(...)`\n * - Variant `Connection { cause: String }` → key `Connection: ({ cause }: { cause: unknown }) => (...)`\n * - `#[error(\"Failed: {cause}\")]` → `` message: `Failed: ${extractErrorMessage(cause)}` ``\n * - `HttpError::Connection { ... }` → `HttpError.Connection({ ... })`\n * - `match error { Connection { .. } => }` → `switch (error.name) { case 'Connection': }`\n *\n * The equivalent Rust `thiserror` enum:\n * ```rust\n * #[derive(Error, Debug)]\n * enum HttpError {\n * #[error(\"Failed to connect: {cause}\")]\n * Connection { cause: String },\n *\n * #[error(\"HTTP {status}\")]\n * Response { status: u16, body_message: Option<String> },\n *\n * #[error(\"Failed to parse response body: {cause}\")]\n * Parse { cause: String },\n * }\n * ```\n */\nexport function defineErrors<const TConfig extends ErrorsConfig>(\n\tconfig: TConfig & ValidatedConfig<TConfig>,\n): DefineErrorsReturn<TConfig> {\n\tconst result: Record<string, unknown> = {};\n\n\tfor (const [name, ctor] of Object.entries(config)) {\n\t\tresult[name] = (...args: unknown[]) => {\n\t\t\tconst body = (ctor as (...a: unknown[]) => Record<string, unknown>)(\n\t\t\t\t...args,\n\t\t\t);\n\t\t\treturn Err(Object.freeze({ ...body, name }));\n\t\t};\n\t}\n\n\treturn result as DefineErrorsReturn<TConfig>;\n}\n","import { Err } from \"../result/result.js\";\nimport type { ErrorBody } from \"./types.js\";\n\n// =============================================================================\n// Config types\n// =============================================================================\n\n/** Config: each key maps to `[httpStatus, factory]`. */\n// biome-ignore lint/suspicious/noExplicitAny: required for TypeScript's function type inference\nexport type HttpErrorsConfig = Record<string, [number, (...args: any[]) => ErrorBody]>;\n\n/**\n * Per-key validation applied to the factory portion of each tuple.\n * Mirrors `ValidatedConfig` from `defineErrors`: prevents `name` in the\n * factory return body since the factory key is stamped as `name`.\n */\ntype ValidateHttpErrorBody<K extends string> = {\n\tmessage: string;\n\tname?: `The 'name' key is reserved as '${K}'. Remove it.`;\n};\n\nexport type ValidatedHttpConfig<T extends HttpErrorsConfig> = {\n\t// biome-ignore lint/suspicious/noExplicitAny: required for TypeScript's function type inference\n\t[K in keyof T & string]: T[K] extends [infer TStatus, (...args: infer A) => infer R]\n\t\t? [TStatus, (...args: A) => R & ValidateHttpErrorBody<K>]\n\t\t: T[K];\n};\n\n// =============================================================================\n// Return types\n// =============================================================================\n\n/**\n * A factory function with a static `.status` property holding the literal\n * HTTP status code. The status is never included in the serialized error body.\n */\n// biome-ignore lint/suspicious/noExplicitAny: required for TypeScript's function type inference\nexport type HttpErrorFactory<TName extends string, TStatus extends number, TFn extends (...args: any[]) => ErrorBody> =\n\t((...args: Parameters<TFn>) => Err<Readonly<{ name: TName } & ReturnType<TFn>>>) & {\n\t\treadonly status: TStatus;\n\t};\n\n/** Return type of `defineHttpErrors`. Maps each config key to its `HttpErrorFactory`. */\nexport type DefineHttpErrorsReturn<TConfig extends HttpErrorsConfig> = {\n\t// biome-ignore lint/suspicious/noExplicitAny: required for conditional type inference\n\t[K in keyof TConfig & string]: TConfig[K] extends [infer TStatus extends number, infer TFn extends (...args: any[]) => ErrorBody]\n\t\t? HttpErrorFactory<K, TStatus, TFn>\n\t\t: never;\n};\n\n// =============================================================================\n// Type utilities\n// =============================================================================\n\n/** Extract the error instance type from a single `HttpErrorFactory`. */\n// biome-ignore lint/suspicious/noExplicitAny: required for conditional type inference\nexport type InferHttpError<T> = T extends (...args: any[]) => Err<infer R> ? R : never;\n\n/** Extract the union of all error instance types from a `defineHttpErrors` return. */\nexport type InferHttpErrors<T> = {\n\t// biome-ignore lint/suspicious/noExplicitAny: required for conditional type inference\n\t[K in keyof T]: T[K] extends (...args: any[]) => Err<infer R> ? R : never;\n}[keyof T];\n\n// =============================================================================\n// Implementation\n// =============================================================================\n\n/**\n * Defines a set of typed HTTP error factories, each paired with its HTTP\n * status code.\n *\n * Like `defineErrors`, each factory stamps `name` onto the error and wraps it\n * in `Err`. Unlike `defineErrors`, each factory function carries a static\n * `.status` property with the literal HTTP status code — the status is never\n * included in the serialized error body.\n *\n * @example\n * ```ts\n * const AssetError = defineHttpErrors({\n * MissingFile: [400, () => ({ message: 'Missing file' })],\n * FileTooLarge: [413, ({ size }: { size: number }) => ({ message: `File too large: ${size}`, size })],\n * StorageLimitExceeded: [402, () => ({ message: 'Storage limit exceeded' })],\n * });\n *\n * type AssetError = InferHttpErrors<typeof AssetError>;\n *\n * // In a Hono route handler:\n * return c.json(AssetError.MissingFile(), AssetError.MissingFile.status);\n * // wire body: { error: { name: 'MissingFile', message: 'Missing file' }, data: null }\n * // http status: 400\n * ```\n */\nexport function defineHttpErrors<const TConfig extends HttpErrorsConfig>(\n\tconfig: TConfig & ValidatedHttpConfig<TConfig>,\n): DefineHttpErrorsReturn<TConfig> {\n\tconst result: Record<string, unknown> = {};\n\n\tfor (const [name, [status, factory]] of Object.entries(config)) {\n\t\tconst fn = (...args: unknown[]) => {\n\t\t\tconst body = (factory as (...a: unknown[]) => Record<string, unknown>)(\n\t\t\t\t...args,\n\t\t\t);\n\t\t\treturn Err(Object.freeze({ ...body, name }));\n\t\t};\n\t\t(fn as unknown as { status: number }).status = status;\n\t\tresult[name] = fn;\n\t}\n\n\treturn result as unknown as DefineHttpErrorsReturn<TConfig>;\n}\n","/**\n * Extracts a readable error message from an unknown error value\n *\n * @param error - The unknown error to extract a message from\n * @returns A string representation of the error\n */\nexport function extractErrorMessage(error: unknown): string {\n\t// Handle Error instances\n\tif (error instanceof Error) {\n\t\treturn error.message;\n\t}\n\n\t// Handle primitives\n\tif (typeof error === \"string\") return error;\n\tif (\n\t\ttypeof error === \"number\" ||\n\t\ttypeof error === \"boolean\" ||\n\t\ttypeof error === \"bigint\"\n\t)\n\t\treturn String(error);\n\tif (typeof error === \"symbol\") return error.toString();\n\tif (error === null) return \"null\";\n\tif (error === undefined) return \"undefined\";\n\n\t// Handle arrays\n\tif (Array.isArray(error)) return JSON.stringify(error);\n\n\t// Handle plain objects\n\tif (typeof error === \"object\") {\n\t\tconst errorObj = error as Record<string, unknown>;\n\n\t\t// Check common error properties\n\t\tconst messageProps = [\n\t\t\t\"message\",\n\t\t\t\"error\",\n\t\t\t\"description\",\n\t\t\t\"title\",\n\t\t\t\"reason\",\n\t\t\t\"details\",\n\t\t] as const;\n\t\tfor (const prop of messageProps) {\n\t\t\tif (prop in errorObj && typeof errorObj[prop] === \"string\") {\n\t\t\t\treturn errorObj[prop];\n\t\t\t}\n\t\t}\n\n\t\t// Fallback to JSON stringification\n\t\ttry {\n\t\t\treturn JSON.stringify(error);\n\t\t} catch {\n\t\t\treturn String(error);\n\t\t}\n\t}\n\n\t// Final fallback\n\treturn String(error);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4DA,SAAgB,aACfA,QAC8B;CAC9B,MAAMC,SAAkC,CAAE;AAE1C,MAAK,MAAM,CAAC,MAAM,KAAK,IAAI,OAAO,QAAQ,OAAO,CAChD,QAAO,QAAQ,CAAC,GAAG,SAAoB;EACtC,MAAM,OAAO,AAAC,KACb,GAAG,KACH;AACD,SAAO,IAAI,OAAO,OAAO;GAAE,GAAG;GAAM;EAAM,EAAC,CAAC;CAC5C;AAGF,QAAO;AACP;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACkBD,SAAgB,iBACfC,QACkC;CAClC,MAAMC,SAAkC,CAAE;AAE1C,MAAK,MAAM,CAAC,MAAM,CAAC,QAAQ,QAAQ,CAAC,IAAI,OAAO,QAAQ,OAAO,EAAE;EAC/D,MAAM,KAAK,CAAC,GAAG,SAAoB;GAClC,MAAM,OAAO,AAAC,QACb,GAAG,KACH;AACD,UAAO,IAAI,OAAO,OAAO;IAAE,GAAG;IAAM;GAAM,EAAC,CAAC;EAC5C;AACD,EAAC,GAAqC,SAAS;AAC/C,SAAO,QAAQ;CACf;AAED,QAAO;AACP;;;;;;;;;;ACxGD,SAAgB,oBAAoBC,OAAwB;AAE3D,KAAI,iBAAiB,MACpB,QAAO,MAAM;AAId,YAAW,UAAU,SAAU,QAAO;AACtC,YACQ,UAAU,mBACV,UAAU,oBACV,UAAU,SAEjB,QAAO,OAAO,MAAM;AACrB,YAAW,UAAU,SAAU,QAAO,MAAM,UAAU;AACtD,KAAI,UAAU,KAAM,QAAO;AAC3B,KAAI,iBAAqB,QAAO;AAGhC,KAAI,MAAM,QAAQ,MAAM,CAAE,QAAO,KAAK,UAAU,MAAM;AAGtD,YAAW,UAAU,UAAU;EAC9B,MAAM,WAAW;EAGjB,MAAM,eAAe;GACpB;GACA;GACA;GACA;GACA;GACA;EACA;AACD,OAAK,MAAM,QAAQ,aAClB,KAAI,QAAQ,mBAAmB,SAAS,UAAU,SACjD,QAAO,SAAS;AAKlB,MAAI;AACH,UAAO,KAAK,UAAU,MAAM;EAC5B,QAAO;AACP,UAAO,OAAO,MAAM;EACpB;CACD;AAGD,QAAO,OAAO,MAAM;AACpB"}
@@ -0,0 +1,129 @@
1
+ import { Err } from "./result-DKwq9BCr.js";
2
+ import { DefineErrorsReturn, ErrorBody, ErrorsConfig, ValidatedConfig } from "./types-tXXk7K9Q.js";
3
+
4
+ //#region src/error/defineErrors.d.ts
5
+
6
+ /**
7
+ * Defines a set of typed error factories using Rust-style namespaced variants.
8
+ *
9
+ * Each key is a short variant name (the namespace provides context). Every
10
+ * factory returns `Err<...>` directly — ready for `trySync`/`tryAsync` catch
11
+ * handlers. The variant name is stamped as `name` on the error object.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const HttpError = defineErrors({
16
+ * Connection: ({ cause }: { cause: unknown }) => ({
17
+ * message: `Failed to connect: ${extractErrorMessage(cause)}`,
18
+ * cause,
19
+ * }),
20
+ * Response: ({ status }: { status: number; bodyMessage?: string }) => ({
21
+ * message: `HTTP ${status}`,
22
+ * status,
23
+ * }),
24
+ * Parse: ({ cause }: { cause: unknown }) => ({
25
+ * message: `Failed to parse response body: ${extractErrorMessage(cause)}`,
26
+ * cause,
27
+ * }),
28
+ * });
29
+ *
30
+ * type HttpError = InferErrors<typeof HttpError>;
31
+ *
32
+ * const result = HttpError.Connection({ cause: 'timeout' }); // Err<...>
33
+ * ```
34
+ *
35
+ * Inspired by Rust's {@link https://docs.rs/thiserror | thiserror} crate. The
36
+ * mapping is nearly 1:1:
37
+ *
38
+ * - `enum HttpError` → `const HttpError = defineErrors(...)`
39
+ * - Variant `Connection { cause: String }` → key `Connection: ({ cause }: { cause: unknown }) => (...)`
40
+ * - `#[error("Failed: {cause}")]` → `` message: `Failed: ${extractErrorMessage(cause)}` ``
41
+ * - `HttpError::Connection { ... }` → `HttpError.Connection({ ... })`
42
+ * - `match error { Connection { .. } => }` → `switch (error.name) { case 'Connection': }`
43
+ *
44
+ * The equivalent Rust `thiserror` enum:
45
+ * ```rust
46
+ * #[derive(Error, Debug)]
47
+ * enum HttpError {
48
+ * #[error("Failed to connect: {cause}")]
49
+ * Connection { cause: String },
50
+ *
51
+ * #[error("HTTP {status}")]
52
+ * Response { status: u16, body_message: Option<String> },
53
+ *
54
+ * #[error("Failed to parse response body: {cause}")]
55
+ * Parse { cause: String },
56
+ * }
57
+ * ```
58
+ */
59
+ declare function defineErrors<const TConfig extends ErrorsConfig>(config: TConfig & ValidatedConfig<TConfig>): DefineErrorsReturn<TConfig>;
60
+ //# sourceMappingURL=defineErrors.d.ts.map
61
+ //#endregion
62
+ //#region src/error/defineHttpErrors.d.ts
63
+ /** Config: each key maps to `[httpStatus, factory]`. */
64
+ type HttpErrorsConfig = Record<string, [number, (...args: any[]) => ErrorBody]>;
65
+ /**
66
+ * Per-key validation applied to the factory portion of each tuple.
67
+ * Mirrors `ValidatedConfig` from `defineErrors`: prevents `name` in the
68
+ * factory return body since the factory key is stamped as `name`.
69
+ */
70
+ type ValidateHttpErrorBody<K extends string> = {
71
+ message: string;
72
+ name?: `The 'name' key is reserved as '${K}'. Remove it.`;
73
+ };
74
+ type ValidatedHttpConfig<T extends HttpErrorsConfig> = { [K in keyof T & string]: T[K] extends [infer TStatus, (...args: infer A) => infer R] ? [TStatus, (...args: A) => R & ValidateHttpErrorBody<K>] : T[K] };
75
+ /**
76
+ * A factory function with a static `.status` property holding the literal
77
+ * HTTP status code. The status is never included in the serialized error body.
78
+ */
79
+ type HttpErrorFactory<TName extends string, TStatus extends number, TFn extends (...args: any[]) => ErrorBody> = ((...args: Parameters<TFn>) => Err<Readonly<{
80
+ name: TName;
81
+ } & ReturnType<TFn>>>) & {
82
+ readonly status: TStatus;
83
+ };
84
+ /** Return type of `defineHttpErrors`. Maps each config key to its `HttpErrorFactory`. */
85
+ type DefineHttpErrorsReturn<TConfig extends HttpErrorsConfig> = { [K in keyof TConfig & string]: TConfig[K] extends [infer TStatus extends number, infer TFn extends (...args: any[]) => ErrorBody] ? HttpErrorFactory<K, TStatus, TFn> : never };
86
+ /** Extract the error instance type from a single `HttpErrorFactory`. */
87
+ type InferHttpError<T> = T extends ((...args: any[]) => Err<infer R>) ? R : never;
88
+ /** Extract the union of all error instance types from a `defineHttpErrors` return. */
89
+ type InferHttpErrors<T> = { [K in keyof T]: T[K] extends ((...args: any[]) => Err<infer R>) ? R : never }[keyof T];
90
+ /**
91
+ * Defines a set of typed HTTP error factories, each paired with its HTTP
92
+ * status code.
93
+ *
94
+ * Like `defineErrors`, each factory stamps `name` onto the error and wraps it
95
+ * in `Err`. Unlike `defineErrors`, each factory function carries a static
96
+ * `.status` property with the literal HTTP status code — the status is never
97
+ * included in the serialized error body.
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * const AssetError = defineHttpErrors({
102
+ * MissingFile: [400, () => ({ message: 'Missing file' })],
103
+ * FileTooLarge: [413, ({ size }: { size: number }) => ({ message: `File too large: ${size}`, size })],
104
+ * StorageLimitExceeded: [402, () => ({ message: 'Storage limit exceeded' })],
105
+ * });
106
+ *
107
+ * type AssetError = InferHttpErrors<typeof AssetError>;
108
+ *
109
+ * // In a Hono route handler:
110
+ * return c.json(AssetError.MissingFile(), AssetError.MissingFile.status);
111
+ * // wire body: { error: { name: 'MissingFile', message: 'Missing file' }, data: null }
112
+ * // http status: 400
113
+ * ```
114
+ */
115
+ declare function defineHttpErrors<const TConfig extends HttpErrorsConfig>(config: TConfig & ValidatedHttpConfig<TConfig>): DefineHttpErrorsReturn<TConfig>;
116
+ //#endregion
117
+ //#region src/error/extractErrorMessage.d.ts
118
+ /**
119
+ * Extracts a readable error message from an unknown error value
120
+ *
121
+ * @param error - The unknown error to extract a message from
122
+ * @returns A string representation of the error
123
+ */
124
+ declare function extractErrorMessage(error: unknown): string;
125
+ //# sourceMappingURL=extractErrorMessage.d.ts.map
126
+
127
+ //#endregion
128
+ export { DefineHttpErrorsReturn, HttpErrorFactory, HttpErrorsConfig, InferHttpError, InferHttpErrors, ValidatedHttpConfig, defineErrors, defineHttpErrors, extractErrorMessage };
129
+ //# sourceMappingURL=index-BNWUFJ69.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index-BNWUFJ69.d.ts","names":[],"sources":["../src/error/defineErrors.ts","../src/error/defineHttpErrors.ts","../src/error/extractErrorMessage.ts"],"sourcesContent":[],"mappings":";;;;;;;;AA4DA;;;;;;;;AAEqB;;;;ACrDrB;;;;AAAqC;AAAkD;AAYvF;;;;;;;;;;;;;AAIO;AAYP;;;;;;;;;;;AAE0B;AAI1B;;;;;;AAEwH,iBDexG,YCfwG,CAAA,sBDerE,YCfqE,CAAA,CAAA,MAAA,EDgB/G,OChB+G,GDgBrG,eChBqG,CDgBrF,OChBqF,CAAA,CAAA,EDiBrH,kBCjBqH,CDiBlG,OCjBkG,CAAA;;;;;KApC5G,gBAAA,GAAmB,4CAA4C;ADmD3E;;;;;KC5CK,qBD6Cc,CAAA,UAAA,MAAA,CAAA,GAAA;EAAe,OACZ,EAAA,MAAA;EAAO,IAA1B,CAAA,EAAA,kCC5CuC,CD4CvC,eAAA;AAAkB,CAAA;KCzCT,8BAA8B,kCAE7B,aAAa,EAAE,6DACvB,mBAAmB,MAAM,IAAI,sBAAsB,MACpD,EAAE;AAhBN;;;;AAAqC,KA4BzB,gBA5ByB,CAAA,cAAA,MAAA,EAAA,gBAAA,MAAA,EAAA,YAAA,CAAA,GAAA,IAAA,EAAA,GAAA,EAAA,EAAA,GA4BsE,SA5BtE,CAAA,GAAA,CAAA,CAAA,GAAA,IAAA,EA6BzB,UA7ByB,CA6Bd,GA7Bc,CAAA,EAAA,GA6BL,GA7BK,CA6BD,QA7BC,CAAA;EAOhC,IAAA,EAsBgD,KAtBhD;AAKL,CAAA,GAiB+D,UAjBnD,CAiB8D,GAjB9D,CAAA,CAAA,CAAA,CAAA,GAAA;EAAmB,SAAA,MAAA,EAkBZ,OAlBY;CAAA;;AAEL,KAoBd,sBApBc,CAAA,gBAoByB,gBApBzB,CAAA,GAAA,QAAE,MAsBf,OAtBe,GAAA,MAAA,GAsBI,OAtBJ,CAsBY,CAtBZ,CAAA,SAAA,CAAA,KAAA,iBAAA,MAAA,EAAA,KAAA,aAAA,CAAA,GAAA,IAAA,EAAA,GAAA,EAAA,EAAA,GAsB4F,SAtB5F,CAAA,GAuBxB,gBAvBwB,CAuBP,CAvBO,EAuBJ,OAvBI,EAuBK,GAvBL,CAAA,GAAA,KAAA,EAAC;;AACC,KAgClB,cAhCkB,CAAA,CAAA,CAAA,GAgCE,CAhCF,UAAA,CAAA,GAAA,IAAA,EAAA,GAAA,EAAA,EAAA,GAgCgC,GAhChC,CAAA,KAAA,EAAA,CAAA,IAgC+C,CAhC/C,GAAA,KAAA;;AAAI,KAmCtB,eAnCsB,CAAA,CAAA,CAAA,GAAA,QAC9B,MAoCS,CApCT,GAoCa,CApCb,CAoCe,CApCf,CAAA,UAAA,CAAA,GAAA,IAAA,EAAA,GAAA,EAAA,EAAA,GAoC8C,GApC9C,CAAA,KAAA,EAAA,CAAA,IAoC6D,CApC7D,GAAA,KAAA,EAAC,CAAA,MAqCG,CArCF,CAAA;AAAC;AAYP;;;;;;;;;;;AAE0B;AAI1B;;;;;;;;;;;AAGoB;AAUR,iBAqCI,gBArCU,CAAA,sBAqC6B,gBArC7B,CAAA,CAAA,MAAA,EAsCjB,OAtCiB,GAsCP,mBAtCO,CAsCa,OAtCb,CAAA,CAAA,EAuCvB,sBAvCuB,CAuCA,OAvCA,CAAA;;;;;;;ADI1B;;AAAmD,iBEtDnC,mBAAA,CFsDmC,KAAA,EAAA,OAAA,CAAA,EAAA,MAAA"}
@@ -1,4 +1,4 @@
1
- import { Err, Ok, Result } from "./result-BongGO2O.js";
1
+ import { Err, Ok, Result } from "./result-DKwq9BCr.js";
2
2
 
3
3
  //#region src/result/utils.d.ts
4
4
 
@@ -25,4 +25,4 @@ declare function partitionResults<T, E>(results: Result<T, E>[]): {
25
25
  //# sourceMappingURL=utils.d.ts.map
26
26
  //#endregion
27
27
  export { partitionResults };
28
- //# sourceMappingURL=index-mb9iYu0a.d.ts.map
28
+ //# sourceMappingURL=index-DnoV2ZDO.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index-mb9iYu0a.d.ts","names":[],"sources":["../src/result/utils.ts"],"sourcesContent":[],"mappings":";;;;;;AAmBA;;;;;;;;;AAK+B;;;;;iBALf,gCAAgC,OAAO,GAAG;OAK7C,GAAG;QAAY,IAAI"}
1
+ {"version":3,"file":"index-DnoV2ZDO.d.ts","names":[],"sources":["../src/result/utils.ts"],"sourcesContent":[],"mappings":";;;;;;AAmBA;;;;;;;;;AAK+B;;;;;iBALf,gCAAgC,OAAO,GAAG;OAK7C,GAAG;QAAY,IAAI"}
package/dist/json.d.ts CHANGED
@@ -1,4 +1,11 @@
1
+ import { Err, Result } from "./result-DKwq9BCr.js";
2
+ import { InferError } from "./types-tXXk7K9Q.js";
3
+ import "./index-BNWUFJ69.js";
4
+ import "./tap-err-CFhHBPfH.js";
5
+ import "./index-DnoV2ZDO.js";
6
+
1
7
  //#region src/json.d.ts
8
+
2
9
  /**
3
10
  * JSON-serializable value types.
4
11
  * Ensures data can be safely serialized via JSON.stringify.
@@ -19,7 +26,56 @@ type JsonValue = string | number | boolean | null | JsonValue[] | {
19
26
  * A record where every value is a {@link JsonValue}.
20
27
  */
21
28
  type JsonObject = Record<string, JsonValue>;
29
+ /**
30
+ * Constructs a {@link JsonParseError}. Returns `Err<JsonParseError>` directly,
31
+ * ready to return from a `trySync`/`tryAsync` catch handler.
32
+ */
33
+ declare const JsonParseError: (args_0: {
34
+ cause: unknown;
35
+ }) => Err<Readonly<{
36
+ name: "JsonParseError";
37
+ } & {
38
+ message: string;
39
+ cause: unknown;
40
+ }>>;
41
+ /**
42
+ * The error {@link parseJson} produces when its input is not valid JSON.
43
+ *
44
+ * JSON parsing has exactly one failure mode (the engine throws a `SyntaxError`),
45
+ * so this is a single variant. A valid-JSON-but-wrong-shape value is not a
46
+ * parse failure: validate the returned {@link JsonValue} against a schema for
47
+ * that case.
48
+ */
49
+ type JsonParseError = InferError<typeof JsonParseError>;
50
+ /**
51
+ * Parses a JSON string into a {@link JsonValue}, returning a `Result` instead
52
+ * of throwing.
53
+ *
54
+ * Unlike `JSON.parse`, which returns `any` and throws on malformed input, this:
55
+ * - types the success value as {@link JsonValue}, forcing you to narrow or
56
+ * validate before treating it as a known shape
57
+ * - reports failure as a tagged {@link JsonParseError} rather than an exception
58
+ *
59
+ * No reviver argument is accepted. A reviver can return arbitrary values, which
60
+ * would make the {@link JsonValue} success type a lie.
61
+ *
62
+ * @param text - The JSON string to parse.
63
+ * @returns `Ok<JsonValue>` on success, `Err<JsonParseError>` on malformed input.
64
+ *
65
+ * @example
66
+ * ```ts
67
+ * import { parseJson } from "wellcrafted/json";
68
+ *
69
+ * const { data, error } = parseJson('{"count":1}');
70
+ * if (error) {
71
+ * console.error(error.message); // "Failed to parse JSON: ..."
72
+ * } else {
73
+ * data; // JsonValue — narrow or validate before using
74
+ * }
75
+ * ```
76
+ */
77
+ declare function parseJson(text: string): Result<JsonValue, JsonParseError>;
22
78
  //# sourceMappingURL=json.d.ts.map
23
79
  //#endregion
24
- export { JsonObject, JsonValue };
80
+ export { JsonObject, JsonParseError, JsonValue, parseJson };
25
81
  //# sourceMappingURL=json.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"json.d.ts","names":[],"sources":["../src/json.ts"],"sourcesContent":[],"mappings":";;AAYA;;;;AAM6B;AAM7B;;;;AAA+B;;KAZnB,SAAA,sCAKT;iBACiB;;;;;;KAMR,UAAA,GAAa,eAAe"}
1
+ {"version":3,"file":"json.d.ts","names":[],"sources":["../src/json.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;AAmBA;;;;AAM6B;AAM7B;;AAAwC,KAZ5B,SAAA,GAY4B,MAAA,GAAA,MAAA,GAAA,OAAA,GAAA,IAAA,GAPrC,SAOqC,EAAA,GAAA;EAAS,CAAA,GAAxB,EAAA,MAAA,CAAA,EANL,SAMK;AAAM,CAAA;AAU/B;;;;KAVY,UAAA,GAAa,eAAe;AAyBxC;;;;AAAuC,cAfxB,cAewB,EAAA,CAAA,MAAA,EAAA;EA6BvB,KAAA,EAAA,OAAS;CAAA,EAAA,MAAA,CAvCvB,QAuCuB,CAAA;EAAA,IAAuB,EAAA,gBAAA;CAAS,GAAA;EAAgB,OAAhC,EAAA,MAAA;EAAM,KAAA,EAAA,OAAA;;;;;;;;;;KA7BnC,cAAA,GAAiB,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA6B/B,SAAA,gBAAyB,OAAO,WAAW"}
package/dist/json.js CHANGED
@@ -0,0 +1,51 @@
1
+ import { trySync } from "./result-C5cJ1_WU.js";
2
+ import { defineErrors, extractErrorMessage } from "./error-DLVm7dsh.js";
3
+ import "./tap-err-CP-re1HT.js";
4
+ import "./result-C9V2Knvt.js";
5
+
6
+ //#region src/json.ts
7
+ /**
8
+ * Constructs a {@link JsonParseError}. Returns `Err<JsonParseError>` directly,
9
+ * ready to return from a `trySync`/`tryAsync` catch handler.
10
+ */
11
+ const { JsonParseError } = defineErrors({ JsonParseError: ({ cause }) => ({
12
+ message: `Failed to parse JSON: ${extractErrorMessage(cause)}`,
13
+ cause
14
+ }) });
15
+ /**
16
+ * Parses a JSON string into a {@link JsonValue}, returning a `Result` instead
17
+ * of throwing.
18
+ *
19
+ * Unlike `JSON.parse`, which returns `any` and throws on malformed input, this:
20
+ * - types the success value as {@link JsonValue}, forcing you to narrow or
21
+ * validate before treating it as a known shape
22
+ * - reports failure as a tagged {@link JsonParseError} rather than an exception
23
+ *
24
+ * No reviver argument is accepted. A reviver can return arbitrary values, which
25
+ * would make the {@link JsonValue} success type a lie.
26
+ *
27
+ * @param text - The JSON string to parse.
28
+ * @returns `Ok<JsonValue>` on success, `Err<JsonParseError>` on malformed input.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import { parseJson } from "wellcrafted/json";
33
+ *
34
+ * const { data, error } = parseJson('{"count":1}');
35
+ * if (error) {
36
+ * console.error(error.message); // "Failed to parse JSON: ..."
37
+ * } else {
38
+ * data; // JsonValue — narrow or validate before using
39
+ * }
40
+ * ```
41
+ */
42
+ function parseJson(text) {
43
+ return trySync({
44
+ try: () => JSON.parse(text),
45
+ catch: (cause) => JsonParseError({ cause })
46
+ });
47
+ }
48
+
49
+ //#endregion
50
+ export { JsonParseError, parseJson };
51
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.js","names":["text: string"],"sources":["../src/json.ts"],"sourcesContent":["import {\n\tdefineErrors,\n\textractErrorMessage,\n\ttype InferError,\n} from \"./error/index.js\";\nimport { type Result, trySync } from \"./result/index.js\";\n\n/**\n * JSON-serializable value types.\n * Ensures data can be safely serialized via JSON.stringify.\n *\n * @example\n * ```typescript\n * import type { JsonValue, JsonObject } from \"wellcrafted/json\";\n *\n * const value: JsonValue = { key: [1, \"two\", true, null] };\n * const obj: JsonObject = { name: \"Alice\", age: 30 };\n * ```\n */\nexport type JsonValue =\n\t| string\n\t| number\n\t| boolean\n\t| null\n\t| JsonValue[]\n\t| { [key: string]: JsonValue };\n\n/**\n * JSON-serializable object type.\n * A record where every value is a {@link JsonValue}.\n */\nexport type JsonObject = Record<string, JsonValue>;\n\n// =============================================================================\n// Parsing\n// =============================================================================\n\n/**\n * Constructs a {@link JsonParseError}. Returns `Err<JsonParseError>` directly,\n * ready to return from a `trySync`/`tryAsync` catch handler.\n */\nexport const { JsonParseError } = defineErrors({\n\tJsonParseError: ({ cause }: { cause: unknown }) => ({\n\t\tmessage: `Failed to parse JSON: ${extractErrorMessage(cause)}`,\n\t\tcause,\n\t}),\n});\n\n/**\n * The error {@link parseJson} produces when its input is not valid JSON.\n *\n * JSON parsing has exactly one failure mode (the engine throws a `SyntaxError`),\n * so this is a single variant. A valid-JSON-but-wrong-shape value is not a\n * parse failure: validate the returned {@link JsonValue} against a schema for\n * that case.\n */\nexport type JsonParseError = InferError<typeof JsonParseError>;\n\n/**\n * Parses a JSON string into a {@link JsonValue}, returning a `Result` instead\n * of throwing.\n *\n * Unlike `JSON.parse`, which returns `any` and throws on malformed input, this:\n * - types the success value as {@link JsonValue}, forcing you to narrow or\n * validate before treating it as a known shape\n * - reports failure as a tagged {@link JsonParseError} rather than an exception\n *\n * No reviver argument is accepted. A reviver can return arbitrary values, which\n * would make the {@link JsonValue} success type a lie.\n *\n * @param text - The JSON string to parse.\n * @returns `Ok<JsonValue>` on success, `Err<JsonParseError>` on malformed input.\n *\n * @example\n * ```ts\n * import { parseJson } from \"wellcrafted/json\";\n *\n * const { data, error } = parseJson('{\"count\":1}');\n * if (error) {\n * console.error(error.message); // \"Failed to parse JSON: ...\"\n * } else {\n * data; // JsonValue — narrow or validate before using\n * }\n * ```\n */\nexport function parseJson(text: string): Result<JsonValue, JsonParseError> {\n\treturn trySync({\n\t\ttry: () => JSON.parse(text) as JsonValue,\n\t\tcatch: (cause) => JsonParseError({ cause }),\n\t});\n}\n"],"mappings":";;;;;;;;;;AAyCA,MAAa,EAAE,gBAAgB,GAAG,aAAa,EAC9C,gBAAgB,CAAC,EAAE,OAA2B,MAAM;CACnD,SAAS,CAAC,sBAAsB,EAAE,oBAAoB,MAAM,EAAE;CAC9D;AACA,GACD,EAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCF,SAAgB,UAAUA,MAAiD;AAC1E,QAAO,QAAQ;EACd,KAAK,MAAM,KAAK,MAAM,KAAK;EAC3B,OAAO,CAAC,UAAU,eAAe,EAAE,MAAO,EAAC;CAC3C,EAAC;AACF"}