@shware/http 0.2.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.
package/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # Client Side Error Handling
2
+
3
+ # Server Side Error Handling
4
+
5
+ ## hono example
6
+
7
+ ### With Adapter
8
+
9
+ ```typescript
10
+ import { Hono } from 'hono';
11
+ import { HTTPException } from 'hono/http-exception';
12
+ import { Status } from '@repo/error';
13
+
14
+ Status.adapter = (status, response) => {
15
+ const headers = { 'Content-Type': 'application/json' };
16
+ const res = new Response(JSON.stringify(response), { status, headers });
17
+ return new HTTPException(status, { res });
18
+ };
19
+
20
+ const app = new Hono();
21
+
22
+ app.get('/', () => {
23
+ throw Status.invalidArgument('Hello World!').error();
24
+ });
25
+
26
+ serve({ fetch: app.fetch, port: 3000 });
27
+ ```
28
+
29
+ ### With Global Error Handler
30
+
31
+ ```typescript
32
+ import { Hono } from 'hono';
33
+ import { HTTPException } from 'hono/http-exception';
34
+ import { Status, StatusError } from '@repo/error';
35
+
36
+ const app = new Hono();
37
+
38
+ app.onError((error, c) => {
39
+ if (error instanceof StatusError) {
40
+ return c.json(error.response, error.status);
41
+ }
42
+ return c.json({ error: 'Internal Server Error' }, 500);
43
+ });
44
+
45
+ app.get('/', () => {
46
+ throw Status.invalidArgument('Hello World!').error();
47
+ });
48
+
49
+ serve({ fetch: app.fetch, port: 3000 });
50
+ ```
51
+
52
+ ## nestjs example
@@ -0,0 +1,241 @@
1
+ import { ZodSchema, z } from 'zod';
2
+
3
+ interface NetworkErrorReason {
4
+ DNS_ERROR: string;
5
+ MISCONFIGURATION: string;
6
+ CONNECTION_ERROR: string;
7
+ }
8
+ interface StatusErrorReason {
9
+ OK: string;
10
+ CANCELLED: string;
11
+ UNKNOWN: string;
12
+ INVALID_ARGUMENT: string;
13
+ DEADLINE_EXCEEDED: string;
14
+ NOT_FOUND: string;
15
+ ALREADY_EXISTS: string;
16
+ PERMISSION_DENIED: string;
17
+ RESOURCE_EXHAUSTED: string;
18
+ FAILED_PRECONDITION: string;
19
+ ABORTED: string;
20
+ OUT_OF_RANGE: string;
21
+ UNIMPLEMENTED: string;
22
+ INTERNAL: string;
23
+ UNAVAILABLE: string;
24
+ DATA_LOSS: string;
25
+ UNAUTHENTICATED: string;
26
+ }
27
+ interface AuthenticationErrorReason {
28
+ ACCOUNT_LOCKED: string;
29
+ ACCOUNT_EXPIRED: string;
30
+ ACCOUNT_INACTIVE: string;
31
+ ACCOUNT_DISABLED: string;
32
+ ACCOUNT_SUSPENDED: string;
33
+ ACCESS_DENIED: string;
34
+ ACCESS_TOKEN_REQUIRED: string;
35
+ PASSWORD_MISMATCH: string;
36
+ USERNAME_ALREADY_EXISTS: string;
37
+ VERIFICATION_CODE_MISMATCH: string;
38
+ VERIFICATION_CODE_SEND_FAILED: string;
39
+ }
40
+ interface ModerationErrorReason {
41
+ POSSIBLY_SENSITIVE: string;
42
+ ADULT_CONTENT: string;
43
+ NUDITY_CONTENT: string;
44
+ SEXUAL_CONTENT: string;
45
+ BLOODY_CONTENT: string;
46
+ WEAPON_CONTENT: string;
47
+ POLITICS_CONTENT: string;
48
+ VIOLENCE_CONTENT: string;
49
+ ABUSE_CONTENT: string;
50
+ ADVERTISEMENT_CONTENT: string;
51
+ CONTRABAND_CONTENT: string;
52
+ SPAM_CONTENT: string;
53
+ MEANINGLESS_CONTENT: string;
54
+ UNSAFE_TEXT_DETECTED: string;
55
+ }
56
+ interface MultipartErrorReason {
57
+ MAX_UPLOAD_SIZE_EXCEEDED: string;
58
+ MEDIA_TYPE_NOT_SUPPORTED: string;
59
+ MEDIA_TYPE_NOT_ACCEPTABLE: string;
60
+ }
61
+ interface AppErrorReason {
62
+ NOT_SUBSCRIBED: string;
63
+ CREDIT_NOT_ENOUGH: string;
64
+ ALREADY_SUBSCRIBED_AT_OTHER_PLATFORM: string;
65
+ }
66
+ type ErrorReason = NetworkErrorReason & StatusErrorReason & AuthenticationErrorReason & ModerationErrorReason & MultipartErrorReason & AppErrorReason;
67
+
68
+ declare enum DetailType {
69
+ ERROR_INFO = "ERROR_INFO",
70
+ RETRY_INFO = "RETRY_INFO",
71
+ DEBUG_INFO = "DEBUG_INFO",
72
+ QUOTA_FAILURE = "QUOTA_FAILURE",
73
+ PRECONDITION_FAILURE = "PRECONDITION_FAILURE",
74
+ BAD_REQUEST = "BAD_REQUEST",
75
+ REQUEST_INFO = "REQUEST_INFO",
76
+ RESOURCE_INFO = "RESOURCE_INFO",
77
+ HELP = "HELP",
78
+ LOCALIZED_MESSAGE = "LOCALIZED_MESSAGE"
79
+ }
80
+ interface ErrorInfo {
81
+ type: DetailType.ERROR_INFO;
82
+ reason: keyof ErrorReason;
83
+ domain?: string;
84
+ metadata?: {
85
+ [key: string]: string;
86
+ };
87
+ }
88
+ interface RetryInfo {
89
+ type: DetailType.RETRY_INFO;
90
+ retryDelay: number;
91
+ }
92
+ interface DebugInfo {
93
+ type: DetailType.DEBUG_INFO;
94
+ stackEntries: string[];
95
+ detail?: string;
96
+ }
97
+ interface QuotaFailure {
98
+ type: DetailType.QUOTA_FAILURE;
99
+ violations: {
100
+ subject: string;
101
+ description: string;
102
+ }[];
103
+ }
104
+ interface PreconditionFailure {
105
+ type: DetailType.PRECONDITION_FAILURE;
106
+ violations: {
107
+ type: string;
108
+ subject: string;
109
+ description: string;
110
+ }[];
111
+ }
112
+ interface BadRequest {
113
+ type: DetailType.BAD_REQUEST;
114
+ fieldViolations: {
115
+ field: string;
116
+ description: string;
117
+ }[];
118
+ }
119
+ interface RequestInfo {
120
+ type: DetailType.REQUEST_INFO;
121
+ requestId: string;
122
+ servingData: string;
123
+ }
124
+ interface ResourceInfo {
125
+ type: DetailType.RESOURCE_INFO;
126
+ resourceType: string;
127
+ resourceName: string;
128
+ owner: string;
129
+ description: string;
130
+ }
131
+ interface Help {
132
+ type: DetailType.HELP;
133
+ links: {
134
+ url: string;
135
+ description: string;
136
+ }[];
137
+ }
138
+ interface LocalizedMessage {
139
+ type: DetailType.LOCALIZED_MESSAGE;
140
+ locale: string;
141
+ message: string;
142
+ }
143
+ type Detail = RetryInfo | DebugInfo | QuotaFailure | ErrorInfo | PreconditionFailure | BadRequest | RequestInfo | ResourceInfo | Help | LocalizedMessage;
144
+ /**
145
+ * Example usage:
146
+ * const details = Details.new()
147
+ * .requestInfo({ requestId: '1234567890', servingData: '/v1/tests' })
148
+ * .errorInfo({ reason: 'ACCOUNT_LOCKED' });
149
+ * */
150
+ declare class Details {
151
+ readonly list: Detail[];
152
+ private constructor();
153
+ static new(): Details;
154
+ errorInfo(detail: Omit<ErrorInfo, 'type'>): this;
155
+ retryInfo(detail: Omit<RetryInfo, 'type'>): this;
156
+ debugInfo(detail: Omit<DebugInfo, 'type'>): this;
157
+ quotaFailure(detail: Omit<QuotaFailure, 'type'>): this;
158
+ preconditionFailure(detail: Omit<PreconditionFailure, 'type'>): this;
159
+ badRequest(detail: Omit<BadRequest, 'type'>): this;
160
+ requestInfo(detail: Omit<RequestInfo, 'type'>): this;
161
+ resourceInfo(detail: Omit<ResourceInfo, 'type'>): this;
162
+ help(detail: Omit<Help, 'type'>): this;
163
+ localizedMessage(detail: Omit<LocalizedMessage, 'type'>): this;
164
+ }
165
+
166
+ declare const Code: {
167
+ readonly OK: 200;
168
+ readonly CANCELLED: 499;
169
+ readonly UNKNOWN: 500;
170
+ readonly INVALID_ARGUMENT: 400;
171
+ readonly DEADLINE_EXCEEDED: 504;
172
+ readonly NOT_FOUND: 404;
173
+ readonly ALREADY_EXISTS: 409;
174
+ readonly PERMISSION_DENIED: 403;
175
+ readonly RESOURCE_EXHAUSTED: 429;
176
+ readonly FAILED_PRECONDITION: 400;
177
+ readonly ABORTED: 409;
178
+ readonly OUT_OF_RANGE: 400;
179
+ readonly UNIMPLEMENTED: 501;
180
+ readonly INTERNAL: 500;
181
+ readonly UNAVAILABLE: 503;
182
+ readonly DATA_LOSS: 500;
183
+ readonly UNAUTHENTICATED: 401;
184
+ readonly METHOD_NOT_ALLOWED: 405;
185
+ };
186
+ declare const DEFAULT_MESSAGES: Record<keyof typeof Code, string>;
187
+ interface ErrorBody {
188
+ error: {
189
+ code: number;
190
+ status: keyof typeof Code;
191
+ message: string;
192
+ details: Detail[];
193
+ };
194
+ }
195
+ declare class StatusError extends Error {
196
+ readonly status: number;
197
+ readonly body?: ErrorBody;
198
+ constructor(status: number, body?: ErrorBody);
199
+ }
200
+ declare class StatusCode {
201
+ code: keyof typeof Code;
202
+ message?: string;
203
+ private constructor();
204
+ static of(code: keyof typeof Code, message?: string): StatusCode;
205
+ body(details?: Details): ErrorBody;
206
+ error(details?: Details): Error;
207
+ response(details?: Details): Response;
208
+ }
209
+ declare class Status {
210
+ static adapter?: (status: number, response: ErrorBody) => Error;
211
+ static ok: (message?: string) => StatusCode;
212
+ static cancelled: (message?: string) => StatusCode;
213
+ static unknown: (message?: string) => StatusCode;
214
+ static invalidArgument: (message?: string) => StatusCode;
215
+ static deadlineExceeded: (message?: string) => StatusCode;
216
+ static notFound: (message?: string) => StatusCode;
217
+ static alreadyExists: (message?: string) => StatusCode;
218
+ static permissionDenied: (message?: string) => StatusCode;
219
+ static unauthorized: (message?: string) => StatusCode;
220
+ static resourceExhausted: (message?: string) => StatusCode;
221
+ static failedPrecondition: (message?: string) => StatusCode;
222
+ static aborted: (message?: string) => StatusCode;
223
+ static outOfRange: (message?: string) => StatusCode;
224
+ static unimplemented: (message?: string) => StatusCode;
225
+ static internal: (message?: string) => StatusCode;
226
+ static unavailable: (message?: string) => StatusCode;
227
+ static dataLoss: (message?: string) => StatusCode;
228
+ static methodNotAllowed: (message?: string) => StatusCode;
229
+ }
230
+
231
+ type Result<S extends ZodSchema> = {
232
+ data: z.infer<S>;
233
+ error: null;
234
+ } | {
235
+ data: null;
236
+ error: Response;
237
+ };
238
+ type Target = 'json' | 'form' | 'query' | 'param' | 'header' | 'cookie';
239
+ declare function valid<S extends ZodSchema>(request: Request, target: Target, schema: S): Promise<Result<S>>;
240
+
241
+ export { type AppErrorReason, type AuthenticationErrorReason, type BadRequest, Code, DEFAULT_MESSAGES, type DebugInfo, type Detail, DetailType, Details, type ErrorBody, type ErrorInfo, type ErrorReason, type Help, type LocalizedMessage, type ModerationErrorReason, type MultipartErrorReason, type NetworkErrorReason, type PreconditionFailure, type QuotaFailure, type RequestInfo, type ResourceInfo, type Result, type RetryInfo, Status, StatusCode, StatusError, type StatusErrorReason, valid };
package/dist/index.mjs ADDED
@@ -0,0 +1,312 @@
1
+ var DetailType = /* @__PURE__ */ ((DetailType2) => {
2
+ DetailType2["ERROR_INFO"] = "ERROR_INFO";
3
+ DetailType2["RETRY_INFO"] = "RETRY_INFO";
4
+ DetailType2["DEBUG_INFO"] = "DEBUG_INFO";
5
+ DetailType2["QUOTA_FAILURE"] = "QUOTA_FAILURE";
6
+ DetailType2["PRECONDITION_FAILURE"] = "PRECONDITION_FAILURE";
7
+ DetailType2["BAD_REQUEST"] = "BAD_REQUEST";
8
+ DetailType2["REQUEST_INFO"] = "REQUEST_INFO";
9
+ DetailType2["RESOURCE_INFO"] = "RESOURCE_INFO";
10
+ DetailType2["HELP"] = "HELP";
11
+ DetailType2["LOCALIZED_MESSAGE"] = "LOCALIZED_MESSAGE";
12
+ return DetailType2;
13
+ })(DetailType || {});
14
+ class Details {
15
+ list = [];
16
+ constructor() {
17
+ }
18
+ static new() {
19
+ return new Details();
20
+ }
21
+ errorInfo(detail) {
22
+ this.list.push({ type: "ERROR_INFO" /* ERROR_INFO */, ...detail });
23
+ return this;
24
+ }
25
+ retryInfo(detail) {
26
+ this.list.push({ type: "RETRY_INFO" /* RETRY_INFO */, ...detail });
27
+ return this;
28
+ }
29
+ debugInfo(detail) {
30
+ this.list.push({ type: "DEBUG_INFO" /* DEBUG_INFO */, ...detail });
31
+ return this;
32
+ }
33
+ quotaFailure(detail) {
34
+ this.list.push({ type: "QUOTA_FAILURE" /* QUOTA_FAILURE */, ...detail });
35
+ return this;
36
+ }
37
+ preconditionFailure(detail) {
38
+ this.list.push({ type: "PRECONDITION_FAILURE" /* PRECONDITION_FAILURE */, ...detail });
39
+ return this;
40
+ }
41
+ badRequest(detail) {
42
+ this.list.push({ type: "BAD_REQUEST" /* BAD_REQUEST */, ...detail });
43
+ return this;
44
+ }
45
+ requestInfo(detail) {
46
+ this.list.push({ type: "REQUEST_INFO" /* REQUEST_INFO */, ...detail });
47
+ return this;
48
+ }
49
+ resourceInfo(detail) {
50
+ this.list.push({ type: "RESOURCE_INFO" /* RESOURCE_INFO */, ...detail });
51
+ return this;
52
+ }
53
+ help(detail) {
54
+ this.list.push({ type: "HELP" /* HELP */, ...detail });
55
+ return this;
56
+ }
57
+ localizedMessage(detail) {
58
+ this.list.push({ type: "LOCALIZED_MESSAGE" /* LOCALIZED_MESSAGE */, ...detail });
59
+ return this;
60
+ }
61
+ }
62
+
63
+ const Code = {
64
+ // Not an error; returned on success
65
+ //
66
+ // HTTP Mapping: 200 OK
67
+ OK: 200,
68
+ // The operation was cancelled, typically by the caller.
69
+ //
70
+ // HTTP Mapping: 499 Client Closed Request
71
+ CANCELLED: 499,
72
+ // Unknown error. For example, this error may be returned when
73
+ // a `Status` value received from another address space belongs to
74
+ // an error space that is not known in this address space. Also,
75
+ // errors raised by APIs that do not return enough error information
76
+ // may be converted to this error.
77
+ //
78
+ // HTTP Mapping: 500 Internal Server Error
79
+ UNKNOWN: 500,
80
+ // The client specified an invalid argument. Note that this differs
81
+ // from `FAILED_PRECONDITION`. `INVALID_ARGUMENT` indicates arguments
82
+ // that are problematic regardless of the state of the system
83
+ // (e.g., a malformed file name).
84
+ //
85
+ // HTTP Mapping: 400 Bad Request
86
+ INVALID_ARGUMENT: 400,
87
+ // The deadline expired before the operation could complete. For operations
88
+ // that change the state of the system, this error may be returned
89
+ // even if the operation has completed successfully. For example, a
90
+ // successful response from a server could have been delayed long
91
+ // enough for the deadline to expire.
92
+ //
93
+ // HTTP Mapping: 504 Gateway Timeout
94
+ DEADLINE_EXCEEDED: 504,
95
+ // Some requested entity (e.g., file or directory) was not found.
96
+ //
97
+ // Note to server developers: if a request is denied for an entire class
98
+ // of users, such as gradual feature rollout or undocumented whitelist,
99
+ // `NOT_FOUND` may be used. If a request is denied for some users within
100
+ // a class of users, such as user-based access control, `PERMISSION_DENIED`
101
+ // must be used.
102
+ //
103
+ // HTTP Mapping: 404 Not Found
104
+ NOT_FOUND: 404,
105
+ // The entity that a client attempted to create (e.g., file or directory)
106
+ // already exists.
107
+ //
108
+ // HTTP Mapping: 409 Conflict
109
+ ALREADY_EXISTS: 409,
110
+ // The caller does not have permission to execute the specified
111
+ // operation. `PERMISSION_DENIED` must not be used for rejections
112
+ // caused by exhausting some resource (use `RESOURCE_EXHAUSTED`
113
+ // instead for those errors). `PERMISSION_DENIED` must not be
114
+ // used if the caller can not be identified (use `UNAUTHENTICATED`
115
+ // instead for those errors). This error code does not imply the
116
+ // request is valid or the requested entity exists or satisfies
117
+ // other pre-conditions.
118
+ //
119
+ // HTTP Mapping: 403 Forbidden
120
+ PERMISSION_DENIED: 403,
121
+ // Some resource has been exhausted, perhaps a per-user quota, or
122
+ // perhaps the entire file system is out of space.
123
+ //
124
+ // HTTP Mapping: 429 Too Many Requests
125
+ RESOURCE_EXHAUSTED: 429,
126
+ // The operation was rejected because the system is not in a state
127
+ // required for the operation's execution. For example, the directory
128
+ // to be deleted is non-empty, a rmdir operation is applied to
129
+ // a non-directory, etc.
130
+ //
131
+ // Service implementors can use the following guidelines to decide
132
+ // between `FAILED_PRECONDITION`, `ABORTED`, and `UNAVAILABLE`:
133
+ // (a) Use `UNAVAILABLE` if the client can retry just the failing call.
134
+ // (b) Use `ABORTED` if the client should retry at a higher level
135
+ // (e.g., when a client-specified test-and-set fails, indicating the
136
+ // client should restart a read-modify-write sequence).
137
+ // (c) Use `FAILED_PRECONDITION` if the client should not retry until
138
+ // the system state has been explicitly fixed. E.g., if a "rmdir"
139
+ // fails because the directory is non-empty, `FAILED_PRECONDITION`
140
+ // should be returned since the client should not retry unless
141
+ // the files are deleted from the directory.
142
+ //
143
+ // HTTP Mapping: 400 Bad Request
144
+ FAILED_PRECONDITION: 400,
145
+ // The operation was aborted, typically due to a concurrency issue such as
146
+ // a sequencer check failure or transaction abort.
147
+ //
148
+ // See the guidelines above for deciding between `FAILED_PRECONDITION`,
149
+ // `ABORTED`, and `UNAVAILABLE`.
150
+ //
151
+ // HTTP Mapping: 409 Conflict
152
+ ABORTED: 409,
153
+ // The operation was attempted past the valid range. E.g., seeking or
154
+ // reading past end-of-file.
155
+ //
156
+ // Unlike `INVALID_ARGUMENT`, this error indicates a problem that may
157
+ // be fixed if the system state changes. For example, a 32-bit file
158
+ // system will generate `INVALID_ARGUMENT` if asked to read at an
159
+ // offset that is not in the range [0,2^32-1], but it will generate
160
+ // `OUT_OF_RANGE` if asked to read from an offset past the current
161
+ // file size.
162
+ //
163
+ // There is a fair bit of overlap between `FAILED_PRECONDITION` and
164
+ // `OUT_OF_RANGE`. We recommend using `OUT_OF_RANGE` (the more specific
165
+ // error) when it applies so that callers who are iterating through
166
+ // a space can easily look for an `OUT_OF_RANGE` error to detect when
167
+ // they are done.
168
+ //
169
+ // HTTP Mapping: 400 Bad Request
170
+ OUT_OF_RANGE: 400,
171
+ // The operation is not implemented or is not supported/enabled in this
172
+ // service.
173
+ //
174
+ // HTTP Mapping: 501 Not Implemented
175
+ UNIMPLEMENTED: 501,
176
+ // Internal errors. This means that some invariants expected by the
177
+ // underlying system have been broken. This error code is reserved
178
+ // for serious errors.
179
+ //
180
+ // HTTP Mapping: 500 Internal Server Error
181
+ INTERNAL: 500,
182
+ // The service is currently unavailable. This is most likely a
183
+ // transient condition, which can be corrected by retrying with
184
+ // a backoff. Note that it is not always safe to retry
185
+ // non-idempotent operations.
186
+ //
187
+ // See the guidelines above for deciding between `FAILED_PRECONDITION`,
188
+ // `ABORTED`, and `UNAVAILABLE`.
189
+ //
190
+ // HTTP Mapping: 503 Service Unavailable
191
+ UNAVAILABLE: 503,
192
+ // Unrecoverable data loss or corruption.
193
+ //
194
+ // HTTP Mapping: 500 Internal Server Error
195
+ DATA_LOSS: 500,
196
+ // The request does not have valid authentication credentials for the
197
+ // operation.
198
+ //
199
+ // HTTP Mapping: 401 Unauthorized
200
+ UNAUTHENTICATED: 401,
201
+ // The request method is not supported by the server and cannot be handled.
202
+ //
203
+ // HTTP Mapping: 405 Method Not Allowed
204
+ METHOD_NOT_ALLOWED: 405
205
+ };
206
+ const DEFAULT_MESSAGES = {
207
+ OK: "OK",
208
+ CANCELLED: "The operation was cancelled",
209
+ UNKNOWN: "Unknown error",
210
+ INVALID_ARGUMENT: "The client specified an invalid argument",
211
+ DEADLINE_EXCEEDED: "The deadline expired before the operation could complete",
212
+ NOT_FOUND: "Some requested entity was not found",
213
+ ALREADY_EXISTS: "The entity that a client attempted to create already exists",
214
+ PERMISSION_DENIED: "The caller does not have permission to execute the specified operation",
215
+ RESOURCE_EXHAUSTED: "Some resource has been exhausted",
216
+ FAILED_PRECONDITION: "The operation was rejected because the system is not in a state required for the operation's execution",
217
+ ABORTED: "The operation was aborted",
218
+ OUT_OF_RANGE: "The operation was attempted past the valid range",
219
+ UNIMPLEMENTED: "The operation is not implemented or is not supported/enabled in this service",
220
+ INTERNAL: "Internal errors",
221
+ UNAVAILABLE: "The service is currently unavailable",
222
+ DATA_LOSS: "Unrecoverable data loss or corruption",
223
+ UNAUTHENTICATED: "The request does not have valid authentication credentials for the operation",
224
+ METHOD_NOT_ALLOWED: "The request method is not supported by the server and cannot be handled"
225
+ };
226
+ class StatusError extends Error {
227
+ status;
228
+ body;
229
+ constructor(status, body) {
230
+ super(body?.error?.message ?? `Status Error: ${status}`);
231
+ this.name = "StatusError";
232
+ this.status = status;
233
+ this.body = body;
234
+ if (Error.captureStackTrace) {
235
+ Error.captureStackTrace(this, StatusError);
236
+ }
237
+ Object.setPrototypeOf(this, StatusError.prototype);
238
+ }
239
+ }
240
+ class StatusCode {
241
+ code;
242
+ message;
243
+ constructor(code, message) {
244
+ this.code = code;
245
+ this.message = message;
246
+ }
247
+ static of(code, message) {
248
+ return new StatusCode(code, message ?? DEFAULT_MESSAGES[code]);
249
+ }
250
+ body(details) {
251
+ return {
252
+ error: {
253
+ code: Code[this.code],
254
+ status: this.code,
255
+ message: this.message ?? "",
256
+ details: details?.list ?? []
257
+ }
258
+ };
259
+ }
260
+ error(details) {
261
+ const body = this.body(details);
262
+ if (Status.adapter) return Status.adapter(Code[this.code], body);
263
+ return new StatusError(Code[this.code], body);
264
+ }
265
+ response(details) {
266
+ const body = this.body(details);
267
+ return Response.json(body, { status: body.error.code });
268
+ }
269
+ }
270
+ class Status {
271
+ static adapter;
272
+ static ok = (message) => StatusCode.of("OK", message);
273
+ static cancelled = (message) => StatusCode.of("CANCELLED", message);
274
+ static unknown = (message) => StatusCode.of("UNKNOWN", message);
275
+ static invalidArgument = (message) => StatusCode.of("INVALID_ARGUMENT", message);
276
+ static deadlineExceeded = (message) => StatusCode.of("DEADLINE_EXCEEDED", message);
277
+ static notFound = (message) => StatusCode.of("NOT_FOUND", message);
278
+ static alreadyExists = (message) => StatusCode.of("ALREADY_EXISTS", message);
279
+ static permissionDenied = (message) => StatusCode.of("PERMISSION_DENIED", message);
280
+ static unauthorized = (message) => StatusCode.of("UNAUTHENTICATED", message);
281
+ static resourceExhausted = (message) => StatusCode.of("RESOURCE_EXHAUSTED", message);
282
+ static failedPrecondition = (message) => StatusCode.of("FAILED_PRECONDITION", message);
283
+ static aborted = (message) => StatusCode.of("ABORTED", message);
284
+ static outOfRange = (message) => StatusCode.of("OUT_OF_RANGE", message);
285
+ static unimplemented = (message) => StatusCode.of("UNIMPLEMENTED", message);
286
+ static internal = (message) => StatusCode.of("INTERNAL", message);
287
+ static unavailable = (message) => StatusCode.of("UNAVAILABLE", message);
288
+ static dataLoss = (message) => StatusCode.of("DATA_LOSS", message);
289
+ static methodNotAllowed = (message) => StatusCode.of("METHOD_NOT_ALLOWED", message);
290
+ }
291
+
292
+ async function getTarget(request, target) {
293
+ switch (target) {
294
+ case "json":
295
+ return request.json();
296
+ default:
297
+ throw new Error(`Unsupported target: ${target}`);
298
+ }
299
+ }
300
+ async function valid(request, target, schema) {
301
+ const value = await getTarget(request, target);
302
+ const validator = await schema.safeParseAsync(value);
303
+ if (validator.success) return { data: validator.data, error: null };
304
+ const fieldViolations = validator.error.issues.map(
305
+ ({ path, message }) => ({ field: path.join("."), description: message })
306
+ );
307
+ const details = Details.new().badRequest({ fieldViolations });
308
+ const error = Status.invalidArgument().response(details);
309
+ return { data: null, error };
310
+ }
311
+
312
+ export { Code, DEFAULT_MESSAGES, DetailType, Details, Status, StatusCode, StatusError, valid };
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@shware/http",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "tsc --watch",
7
+ "build": "unbuild",
8
+ "test": "jest"
9
+ },
10
+ "main": "./dist/index.mjs",
11
+ "module": "./dist/index.mjs",
12
+ "types": "./dist/index.d.mts",
13
+ "exports": {
14
+ ".": "./dist/index.mjs"
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "devDependencies": {
20
+ "@types/jest": "^29.5.14",
21
+ "@types/node": "^22.13.10",
22
+ "jest": "^29.7.0",
23
+ "ts-jest": "^29.2.6",
24
+ "typescript": "5.8.2"
25
+ },
26
+ "dependencies": {
27
+ "zod": "^3.24.2"
28
+ }
29
+ }