@zerospin/error 2.0.1 → 2.1.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.
@@ -1 +1 @@
1
- {"root":["../src/zerospinerror.ts","../src/zerospinerrorjsonschema.ts","../src/index.ts","../src/types.ts"],"version":"5.9.0-dev.20250623"}
1
+ {"root":["../src/zerospinerror.ts","../src/zerospinerrorjsonschema.ts","../src/index.ts","../src/types.ts"],"version":"5.9.3"}
@@ -0,0 +1,13 @@
1
+
2
+ > @zerospin/error@2.0.2 build /Users/morgs32/GitHub/zerospin/packages/error
3
+ > tsup
4
+
5
+ CLI Building entry: src/index.ts
6
+ CLI Using tsconfig: tsconfig.json
7
+ CLI tsup v8.5.1
8
+ CLI Using tsup config: /Users/morgs32/GitHub/zerospin/packages/error/tsup.config.ts
9
+ CLI Target: es2022
10
+ CLI Cleaning output folder
11
+ ESM Build start
12
+ ESM dist/index.js 1.30 KB
13
+ ESM ⚡️ Build success in 7ms
@@ -0,0 +1,14 @@
1
+
2
+ > @zerospin/error@2.0.2 test /Users/morgs32/GitHub/zerospin/packages/error
3
+ > vitest packages/profiler/src/toMatchProcedure
4
+
5
+
6
+  RUN  v3.2.4 /Users/morgs32/GitHub/zerospin/packages/error
7
+
8
+ No test files found, exiting with code 1
9
+ 
10
+ filter: packages/profiler/src/toMatchProcedure
11
+ include: **/*.{test,spec}.?(c|m)[jt]s?(x)
12
+ exclude: **/node_modules/**, **/dist/**, **/cypress/**, **/.{idea,git,cache,output,temp}/**, **/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build,eslint,prettier}.config.*
13
+
14
+  ELIFECYCLE  Test failed. See above for more details.
package/README.md ADDED
@@ -0,0 +1,485 @@
1
+ # @zerospin/error
2
+
3
+ ZeroSpin Error provides a type-safe, structured error system for Effect-based applications. It extends Effect's `Data.TaggedError` to provide consistent error handling with serialization support, error codes, and optional metadata.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @zerospin/error effect
9
+ ```
10
+
11
+ ## Basic Usage
12
+
13
+ ### Creating Errors
14
+
15
+ `ZerospinError` can be created in two ways:
16
+
17
+ **Simple form (code only):**
18
+ ```typescript
19
+ import { ZerospinError } from '@zerospin/error'
20
+
21
+ const error = new ZerospinError('my-error-code')
22
+ // Creates an error with code: 'my-error-code', message: 'my-error-code'
23
+ ```
24
+
25
+ **Full form (with metadata):**
26
+ ```typescript
27
+ const error = new ZerospinError({
28
+ code: 'failed-to-fetch',
29
+ message: 'Failed to fetch data from server',
30
+ cause: originalError,
31
+ extra: { url: 'https://api.example.com' },
32
+ status: 500
33
+ })
34
+ ```
35
+
36
+ ## Using ZerospinError in Effect Contexts
37
+
38
+ ### 1. Throwing Errors in Effect.gen
39
+
40
+ In `Effect.gen` functions, use `yield*` to throw errors:
41
+
42
+ ```typescript
43
+ import { Effect } from 'effect'
44
+ import { ZerospinError } from '@zerospin/error'
45
+
46
+ const fetchData = Effect.gen(function* () {
47
+ const res = yield* Effect.promise(() => fetch('/api/data'))
48
+
49
+ if (res.status === 404) {
50
+ return yield* new ZerospinError({
51
+ code: 'resource-not-found',
52
+ message: 'Resource not found',
53
+ })
54
+ }
55
+
56
+ return yield* res.json()
57
+ })
58
+ ```
59
+
60
+ **Example from ZeroSpin:**
61
+ ```typescript
62
+ // From packages/client/src/ZerospinClientAdapter.ts
63
+ export const ZerospinClientAdapter = Layer.effect(
64
+ ZerospinStorageAdapter,
65
+ Effect.fn('getClientAdapter')(function* () {
66
+ const isOPFSSupported = yield* OPFSAdapter.check().pipe(
67
+ Effect.either,
68
+ Effect.map((either) => Either.isRight(either))
69
+ )
70
+ if (isOPFSSupported) {
71
+ return OPFSAdapter
72
+ }
73
+ const isLocalStorageSupported = yield* LocalStorageAdapter.check().pipe(
74
+ Effect.either,
75
+ Effect.map((either) => Either.isRight(either))
76
+ )
77
+ if (isLocalStorageSupported) {
78
+ return LocalStorageAdapter
79
+ }
80
+ return yield* new ZerospinError({
81
+ code: 'no-adapter-available',
82
+ message: 'No adapter available',
83
+ })
84
+ })()
85
+ )
86
+ ```
87
+
88
+ ### 2. Mapping Errors with Effect.mapError
89
+
90
+ Transform errors from other Effect operations:
91
+
92
+ ```typescript
93
+ import { Effect } from 'effect'
94
+ import { ZerospinError } from '@zerospin/error'
95
+
96
+ const parseJson = Effect.promise(() => res.json()).pipe(
97
+ Effect.mapError((error) => {
98
+ return new ZerospinError({
99
+ code: 'failed-to-parse-json',
100
+ message: 'Failed to parse JSON',
101
+ cause: error,
102
+ })
103
+ })
104
+ )
105
+ ```
106
+
107
+ **Example from ZeroSpin:**
108
+ ```typescript
109
+ // From packages/client/src/makeZerospinFetchFrontendApi.ts
110
+ const res = yield* Effect.promise(async () => {
111
+ return fetch(url, {
112
+ body: JSON.stringify(body),
113
+ headers: { 'Content-Type': 'application/json' },
114
+ method: 'POST',
115
+ }).catch((error: unknown) => {
116
+ throw new ZerospinError({
117
+ code: 'failed-to-fetch',
118
+ message: 'Failed to fetch',
119
+ cause: error,
120
+ })
121
+ })
122
+ }).pipe(
123
+ Effect.mapError((error) => {
124
+ return new ZerospinError({
125
+ code: 'failed-to-fetch',
126
+ message: 'Failed to fetch',
127
+ cause: error,
128
+ })
129
+ })
130
+ )
131
+ ```
132
+
133
+ ### 3. Catching All Errors with Effect.catchAll
134
+
135
+ Handle any error and convert it to a ZerospinError:
136
+
137
+ ```typescript
138
+ import { Effect } from 'effect'
139
+ import { ZerospinError } from '@zerospin/error'
140
+
141
+ const safeOperation = someEffect.pipe(
142
+ Effect.catchAll((error) => {
143
+ return new ZerospinError({
144
+ code: 'operation-failed',
145
+ message: error.message,
146
+ cause: error,
147
+ })
148
+ })
149
+ )
150
+ ```
151
+
152
+ **Example from ZeroSpin:**
153
+ ```typescript
154
+ // From packages/zerospin/src/batch/makeSafe.ts
155
+ const safe = yield* validateUnknown({
156
+ onExcessProperty: 'preserve',
157
+ schema,
158
+ value: command,
159
+ }).pipe(
160
+ Effect.catchAll((error) => {
161
+ return new ZerospinError({
162
+ code: 'failed-to-validate-command',
163
+ message: error.message,
164
+ cause: error,
165
+ })
166
+ })
167
+ )
168
+ ```
169
+
170
+ ### 4. Handling Promise Errors
171
+
172
+ When working with promises, catch errors and throw ZerospinError:
173
+
174
+ ```typescript
175
+ import { Effect } from 'effect'
176
+ import { ZerospinError } from '@zerospin/error'
177
+
178
+ const fetchData = Effect.promise(async () => {
179
+ try {
180
+ const response = await fetch('/api/data')
181
+ return await response.json()
182
+ } catch (error) {
183
+ throw new ZerospinError({
184
+ code: 'fetch-failed',
185
+ message: 'Failed to fetch data',
186
+ cause: error,
187
+ })
188
+ }
189
+ })
190
+ ```
191
+
192
+ **Example from ZeroSpin:**
193
+ ```typescript
194
+ // From packages/client/src/makeZerospinFetchFrontendApi.ts
195
+ const res = yield* Effect.promise(async () => {
196
+ return fetch(url, {
197
+ body: JSON.stringify(body),
198
+ headers: { 'Content-Type': 'application/json' },
199
+ method: 'POST',
200
+ }).catch((error: unknown) => {
201
+ throw new ZerospinError({
202
+ code: 'failed-to-fetch',
203
+ message: 'Failed to fetch',
204
+ cause: error,
205
+ })
206
+ })
207
+ })
208
+ ```
209
+
210
+ ### 5. Schema Validation Errors
211
+
212
+ Convert schema validation errors to ZerospinError:
213
+
214
+ ```typescript
215
+ import { Effect, Schema } from 'effect'
216
+ import { ZerospinError } from '@zerospin/error'
217
+
218
+ const validateData = Schema.decode(Schema.String)(value).pipe(
219
+ Effect.mapError((error) => {
220
+ return new ZerospinError({
221
+ code: 'validation-failed',
222
+ message: error.message,
223
+ cause: error,
224
+ })
225
+ })
226
+ )
227
+ ```
228
+
229
+ **Example from ZeroSpin:**
230
+ ```typescript
231
+ // From packages/zerospin/src/utils/encodeUnknown.ts
232
+ export const encodeUnknown = Effect.fn('encodeUnknown')(<
233
+ DECODED,
234
+ ENCODED,
235
+ >(props: {
236
+ schema: Schema.Schema<DECODED, ENCODED>
237
+ value: unknown
238
+ errorMessage?: string
239
+ }): Effect.Effect<ENCODED, ZerospinError<'failed-to-encode-unknown'>> => {
240
+ const { errorMessage, schema, value } = props
241
+
242
+ return Schema.encodeUnknown(schema)(value).pipe(
243
+ Effect.mapError((error) => {
244
+ return new ZerospinError({
245
+ code: 'failed-to-encode-unknown',
246
+ message: errorMessage ?? error.message,
247
+ cause: error,
248
+ })
249
+ })
250
+ )
251
+ })
252
+ ```
253
+
254
+ **Another example:**
255
+ ```typescript
256
+ // From packages/zerospin/src/contract/makeContract.ts
257
+ return validateUnknown({
258
+ onExcessProperty: 'error',
259
+ schema: resultsSchema,
260
+ value: results,
261
+ }).pipe(
262
+ Effect.mapError((error) => {
263
+ return new ZerospinError({
264
+ code: 'failed-to-validate-results',
265
+ message: error.message,
266
+ cause: error,
267
+ })
268
+ })
269
+ )
270
+ ```
271
+
272
+ ### 6. Deserializing Errors from JSON
273
+
274
+ When receiving errors from APIs or serialized sources:
275
+
276
+ ```typescript
277
+ import { ZerospinError } from '@zerospin/error'
278
+
279
+ // From packages/client/src/makeZerospinFetchFrontendApi.ts
280
+ const payload = yield* Effect.promise(() => {
281
+ return res.json() as Promise<IAnyErrorJson | IJson>
282
+ })
283
+
284
+ if (res.status === 200) {
285
+ return payload as IJson
286
+ }
287
+
288
+ // Deserialize error from JSON
289
+ return yield* new ZerospinError(payload as IAnyErrorJson)
290
+ ```
291
+
292
+ ### 7. Error Handling in Route Handlers
293
+
294
+ Handle errors at the boundary and serialize for HTTP responses:
295
+
296
+ ```typescript
297
+ import { Effect, Exit, Cause } from 'effect'
298
+ import { ZerospinError } from '@zerospin/error'
299
+ import { NextResponse } from 'next/server'
300
+
301
+ const exit = await Effect.runPromiseExit(myEffect)
302
+
303
+ return Exit.match(exit, {
304
+ onFailure: (cause) => {
305
+ console.error(Cause.pretty(cause))
306
+ if (Cause.isFailType(cause)) {
307
+ const error = cause.error as ZerospinError
308
+ return NextResponse.json(error.serialize(), {
309
+ status: error.status ?? 400,
310
+ })
311
+ }
312
+ return NextResponse.json(
313
+ new ZerospinError({
314
+ code: 'unexpected-error',
315
+ message: Cause.pretty(cause),
316
+ }).serialize(),
317
+ { status: 500 }
318
+ )
319
+ },
320
+ onSuccess: (value) => {
321
+ return NextResponse.json(value)
322
+ },
323
+ })
324
+ ```
325
+
326
+ **Example from ZeroSpin:**
327
+ ```typescript
328
+ // From packages/zerospin/src/rpc/makeNextRoute.ts
329
+ return Exit.match(exit, {
330
+ onFailure: (cause) => {
331
+ console.error(Cause.pretty(cause))
332
+ if (Cause.isFailType(cause)) {
333
+ const error = cause.error as ZerospinError
334
+ return NextResponse.json(error.serialize(), {
335
+ status: error.status ?? 400,
336
+ })
337
+ }
338
+ return NextResponse.json(
339
+ new ZerospinError({
340
+ code: 'unexpected-error',
341
+ message: Cause.pretty(cause),
342
+ }).serialize(),
343
+ { status: 500 }
344
+ )
345
+ },
346
+ onSuccess: (value) => {
347
+ return NextResponse.json(value)
348
+ },
349
+ })
350
+ ```
351
+
352
+ ## Error Properties
353
+
354
+ ### Error Structure
355
+
356
+ ```typescript
357
+ interface IError<T extends string = string, E = unknown> {
358
+ code: T // Error code identifier
359
+ status: null | number // HTTP status code (optional)
360
+ cause?: unknown // Original error that caused this
361
+ extra?: E // Additional typed metadata
362
+ message?: string // Human-readable message
363
+ }
364
+ ```
365
+
366
+ ### Type Safety with Error Codes
367
+
368
+ You can create type-safe error codes:
369
+
370
+ ```typescript
371
+ type MyErrorCodes =
372
+ | 'failed-to-fetch'
373
+ | 'validation-failed'
374
+ | 'resource-not-found'
375
+
376
+ const error: ZerospinError<MyErrorCodes> = new ZerospinError({
377
+ code: 'failed-to-fetch', // TypeScript will validate this
378
+ message: 'Failed to fetch',
379
+ })
380
+ ```
381
+
382
+ ## Utility Methods
383
+
384
+ ### Checking if Something is a ZerospinError
385
+
386
+ ```typescript
387
+ import { ZerospinError } from '@zerospin/error'
388
+
389
+ if (ZerospinError.isZerospinError(error)) {
390
+ console.log(error.code)
391
+ console.log(error.message)
392
+ }
393
+ ```
394
+
395
+ **Example from ZeroSpin:**
396
+ ```typescript
397
+ // From packages/zerospin/src/ZerospinErrorLayer.ts
398
+ Exit.mapError(exit, (error) => {
399
+ if (ZerospinError.isZerospinError(error)) {
400
+ if (error.cause) {
401
+ error.message += ` ${JSON.stringify(error.cause, null, 2)}`
402
+ }
403
+ if (error.extra) {
404
+ error.message += ` ${JSON.stringify(error.extra, null, 2)}`
405
+ }
406
+ }
407
+ return error
408
+ })
409
+ ```
410
+
411
+ ### Serializing Errors
412
+
413
+ Serialize errors for transmission over networks or storage:
414
+
415
+ ```typescript
416
+ const error = new ZerospinError({
417
+ code: 'my-error',
418
+ message: 'Something went wrong',
419
+ extra: { actorId: '123' },
420
+ status: 400,
421
+ })
422
+
423
+ // Serialize full error
424
+ const json = error.serialize()
425
+
426
+ // Serialize without certain fields
427
+ const jsonWithoutExtra = error.serialize(['extra'])
428
+ ```
429
+
430
+ ## Best Practices
431
+
432
+ 1. **Always provide meaningful error codes**: Use descriptive, kebab-case codes like `'failed-to-fetch'` instead of generic ones.
433
+
434
+ 2. **Preserve original errors**: Use the `cause` property to maintain error chains:
435
+ ```typescript
436
+ new ZerospinError({
437
+ code: 'operation-failed',
438
+ message: 'Operation failed',
439
+ cause: originalError, // Preserve the original error
440
+ })
441
+ ```
442
+
443
+ 3. **Use typed extra data**: Leverage TypeScript generics for type-safe metadata:
444
+ ```typescript
445
+ new ZerospinError<ErrorCode, { actorId: string; action: string }>({
446
+ code: 'permission-denied',
447
+ extra: { actorId: '123', action: 'delete' },
448
+ })
449
+ ```
450
+
451
+ 4. **Handle at boundaries**: Convert to ZerospinError at Effect boundaries (promises, HTTP handlers, etc.) and let them propagate through your Effect pipeline.
452
+
453
+ 5. **Serialize for APIs**: Use `serialize()` when sending errors over HTTP or storing them.
454
+
455
+ ## API Reference
456
+
457
+ ### ZerospinError Class
458
+
459
+ ```typescript
460
+ class ZerospinError<T extends string = never, E = unknown>
461
+ extends Data.TaggedError('ZerospinError')<IError<T, E>>
462
+ ```
463
+
464
+ **Constructor:**
465
+ - `new ZerospinError(code: string)` - Simple form
466
+ - `new ZerospinError(props: IProps<T, E>)` - Full form
467
+
468
+ **Static Methods:**
469
+ - `ZerospinError.isZerospinError(data: unknown): data is ZerospinError` - Type guard
470
+ - `ZerospinError.makeZerospinErrorJson(props): IAnyErrorJson` - Create JSON representation
471
+
472
+ **Instance Methods:**
473
+ - `serialize(omit?: string[]): IAnyErrorJson` - Serialize to JSON
474
+
475
+ ### Types
476
+
477
+ ```typescript
478
+ type IAnyError = ZerospinError<string>
479
+ type IAnyErrorJson = Brand.Brand<'ZerospinErrorJson'> & {
480
+ code: string
481
+ extra: unknown
482
+ message: string
483
+ status: null | number
484
+ }
485
+ ```
@@ -0,0 +1,46 @@
1
+ import * as effect_Cause from 'effect/Cause';
2
+ import * as effect_Types from 'effect/Types';
3
+ import { RequiredKeysOf } from 'type-fest';
4
+ import { Brand, Schema } from 'effect';
5
+
6
+ type IAnyErrorJson<ERROR extends IAnyError = IAnyError> = Brand.Brand<'ZerospinErrorJson'> & {
7
+ code: ERROR['code'];
8
+ extra: ERROR['extra'];
9
+ message: string;
10
+ status: null | number;
11
+ };
12
+ type IAnyError = ZerospinError<string>;
13
+ interface IProps<T extends string = string, E = unknown> {
14
+ code: T;
15
+ cause?: unknown;
16
+ extra?: E;
17
+ message?: string;
18
+ status?: null | number;
19
+ }
20
+ interface IError<T extends string = string, E = unknown> {
21
+ code: T;
22
+ status: null | number;
23
+ cause?: unknown;
24
+ extra?: E;
25
+ message?: string;
26
+ }
27
+ declare const ZerospinError_base: new <A extends Record<string, any> = {}>(args: effect_Types.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => effect_Cause.YieldableError & {
28
+ readonly _tag: "ZerospinError";
29
+ } & Readonly<A>;
30
+ declare class ZerospinError<T extends string = never, E = unknown> extends ZerospinError_base<IError<T, E>> {
31
+ message: string;
32
+ constructor(props: IProps<T, E> | T);
33
+ static isZerospinError(data: unknown): data is ZerospinError;
34
+ static makeZerospinErrorJson(props: {
35
+ cause: unknown;
36
+ code: string;
37
+ extra: unknown;
38
+ message: string;
39
+ status: null | number;
40
+ }): IAnyErrorJson;
41
+ serialize(omit?: Exclude<keyof IAnyErrorJson, RequiredKeysOf<IAnyErrorJson>>[]): IAnyErrorJson;
42
+ }
43
+
44
+ declare const ZerospinErrorJsonSchema: Schema.Schema<IAnyErrorJson, any>;
45
+
46
+ export { type IAnyError, type IAnyErrorJson, type IError, ZerospinError, ZerospinErrorJsonSchema };
package/dist/index.js CHANGED
@@ -1,2 +1,55 @@
1
- export * from './ZerospinError';
2
- export * from './ZerospinErrorJsonSchema';
1
+ // src/ZerospinError.ts
2
+ import { Brand, Data } from "effect";
3
+ import { isObject } from "effect/Predicate";
4
+ var ZerospinError = class _ZerospinError extends Data.TaggedError("ZerospinError") {
5
+ message;
6
+ constructor(props) {
7
+ if (typeof props === "string") {
8
+ super({
9
+ code: props,
10
+ extra: null,
11
+ message: props,
12
+ status: null
13
+ });
14
+ this.message = props;
15
+ } else {
16
+ super({
17
+ status: null,
18
+ ...props
19
+ });
20
+ this.message = props.message ?? props.code;
21
+ }
22
+ }
23
+ static isZerospinError(data) {
24
+ return isObject(data) && "_tag" in data && data._tag === "ZerospinError";
25
+ }
26
+ static makeZerospinErrorJson(props) {
27
+ return Brand.nominal()(props);
28
+ }
29
+ serialize(omit = []) {
30
+ const json = _ZerospinError.makeZerospinErrorJson({
31
+ cause: this.cause,
32
+ code: this.code,
33
+ extra: this.extra,
34
+ message: this.message,
35
+ status: this.status
36
+ });
37
+ for (const key of omit) {
38
+ delete json[key];
39
+ }
40
+ return json;
41
+ }
42
+ };
43
+
44
+ // src/ZerospinErrorJsonSchema.ts
45
+ import { Schema } from "effect";
46
+ var ZerospinErrorJsonSchema = Schema.Struct({
47
+ code: Schema.String,
48
+ extra: Schema.Unknown,
49
+ message: Schema.String,
50
+ status: Schema.Union(Schema.Null, Schema.Number)
51
+ });
52
+ export {
53
+ ZerospinError,
54
+ ZerospinErrorJsonSchema
55
+ };
package/package.json CHANGED
@@ -1,40 +1,44 @@
1
1
  {
2
2
  "name": "@zerospin/error",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "private": false,
5
5
  "type": "module",
6
+ "types": "./dist/index.d.ts",
6
7
  "exports": {
7
8
  ".": {
8
- "types": "./src/index.ts",
9
+ "types": "./dist/index.d.ts",
9
10
  "import": "./dist/index.js",
10
11
  "require": "./dist/index.js"
11
12
  }
12
13
  },
13
14
  "dependencies": {
14
- "type-fest": "^4.41.0"
15
+ "drizzle-orm": "^0.45.1",
16
+ "type-fest": "^5.4.1"
15
17
  },
16
18
  "devDependencies": {
17
- "@effect/language-service": "^0.43.2",
18
- "@effect/vitest": "^0.25.1",
19
- "@vitest/coverage-v8": "^3.2.4",
20
- "@vitest/pretty-format": "^3.1.4",
21
- "effect": "^3.17.11",
22
- "eslint": "^9.27.0",
23
- "glob": "^11.0.3",
24
- "jsdom": "^26.1.0",
25
- "knip": "^4.2.4",
26
- "tsafe": "^1.8.5",
27
- "tsx": "^4.20.3",
28
- "vite-tsconfig-paths": "^5.1.4",
29
- "vitest": "^3.2.4",
19
+ "@effect/language-service": "^0.68.0",
20
+ "@effect/vitest": "^0.27.0",
21
+ "@types/node": "^22.13.2",
22
+ "@vitest/coverage-v8": "^4.0.17",
23
+ "@vitest/pretty-format": "^4.0.17",
24
+ "effect": "^3.19.14",
25
+ "eslint": "^9.39.2",
26
+ "glob": "^13.0.0",
27
+ "jsdom": "^27.4.0",
28
+ "knip": "^5.81.0",
29
+ "tsafe": "^1.8.12",
30
+ "tsup": "^8.5.1",
31
+ "tsx": "^4.21.0",
32
+ "vite-tsconfig-paths": "^6.0.4",
33
+ "vitest": "^4.0.17",
30
34
  "@zerospin/utils": "2.0.1"
31
35
  },
32
36
  "peerDependencies": {
33
- "effect": "^3.15.2"
37
+ "effect": "^3.17.11"
34
38
  },
35
39
  "scripts": {
36
- "build": "tsc -b tsconfig.build.json",
37
- "watch": "tsc -b tsconfig.build.json --watch",
40
+ "build": "tsup",
41
+ "watch": "tsup --watch",
38
42
  "typecheck": "tsc --noEmit",
39
43
  "test": "vitest",
40
44
  "lint": "eslint .",
@@ -1,31 +1,39 @@
1
- import type { RequiredKeysOf } from 'type-fest'
1
+ import type { RequiredKeysOf } from 'type-fest';
2
2
 
3
- import { Brand, Data } from 'effect'
4
- import { isObject } from 'effect/Predicate'
3
+ import { Brand, Data } from 'effect';
4
+ import { isObject } from 'effect/Predicate';
5
5
 
6
- import type { IAnyErrorJson } from './types'
6
+ export type IAnyErrorJson<ERROR extends IAnyError = IAnyError> =
7
+ Brand.Brand<'ZerospinErrorJson'> & {
8
+ code: ERROR['code'];
9
+ extra: ERROR['extra'];
10
+ message: string;
11
+ status: null | number;
12
+ };
13
+
14
+ export type IAnyError = ZerospinError<string>;
7
15
 
8
16
  interface IProps<T extends string = string, E = unknown> {
9
- code: T
10
- cause?: unknown
11
- extra?: E
12
- message?: string
13
- status?: null | number
17
+ code: T;
18
+ cause?: unknown;
19
+ extra?: E;
20
+ message?: string;
21
+ status?: null | number;
14
22
  }
15
23
 
16
24
  export interface IError<T extends string = string, E = unknown> {
17
- code: T
18
- status: null | number
19
- cause?: unknown
20
- extra?: E
21
- message?: string
25
+ code: T;
26
+ status: null | number;
27
+ cause?: unknown;
28
+ extra?: E;
29
+ message?: string;
22
30
  }
23
31
 
24
32
  export class ZerospinError<
25
33
  T extends string = never,
26
34
  E = unknown,
27
35
  > extends Data.TaggedError('ZerospinError')<IError<T, E>> {
28
- public override message!: string
36
+ public override message!: string;
29
37
 
30
38
  constructor(props: IProps<T, E> | T) {
31
39
  if (typeof props === 'string') {
@@ -34,37 +42,34 @@ export class ZerospinError<
34
42
  extra: null as E,
35
43
  message: props,
36
44
  status: null,
37
- })
38
- this.message = props
45
+ });
46
+ this.message = props;
39
47
  } else {
40
48
  super({
41
49
  status: null,
42
50
  ...props,
43
- })
44
- this.message = props.message ?? props.code
51
+ });
52
+ this.message = props.message ?? props.code;
45
53
  }
46
54
  }
47
55
 
48
56
  static isZerospinError(data: unknown): data is ZerospinError {
49
- return isObject(data) && '_tag' in data && data._tag === 'ZerospinError'
57
+ return isObject(data) && '_tag' in data && data._tag === 'ZerospinError';
50
58
  }
51
59
 
52
60
  static makeZerospinErrorJson(props: {
53
- cause: unknown
54
- code: string
55
- extra: unknown
56
- message: string
57
- status: null | number
61
+ cause: unknown;
62
+ code: string;
63
+ extra: unknown;
64
+ message: string;
65
+ status: null | number;
58
66
  }): IAnyErrorJson {
59
- return Brand.nominal<IAnyErrorJson>()(props)
67
+ return Brand.nominal<IAnyErrorJson>()(props);
60
68
  }
61
69
 
62
70
  serialize(
63
71
  // You can omit anything BUT id, code
64
- omit: Exclude<
65
- keyof IAnyErrorJson,
66
- RequiredKeysOf<IAnyErrorJson>
67
- >[] = []
72
+ omit: Exclude<keyof IAnyErrorJson, RequiredKeysOf<IAnyErrorJson>>[] = []
68
73
  ): IAnyErrorJson {
69
74
  const json = ZerospinError.makeZerospinErrorJson({
70
75
  cause: this.cause,
@@ -72,12 +77,12 @@ export class ZerospinError<
72
77
  extra: this.extra,
73
78
  message: this.message,
74
79
  status: this.status,
75
- })
80
+ });
76
81
 
77
82
  for (const key of omit) {
78
- delete json[key]
83
+ delete json[key];
79
84
  }
80
85
 
81
- return json
86
+ return json;
82
87
  }
83
88
  }
@@ -1,6 +1,6 @@
1
1
  import { Schema } from 'effect'
2
2
 
3
- import type { IAnyErrorJson } from './types'
3
+ import type { IAnyErrorJson } from './ZerospinError'
4
4
 
5
5
  export const ZerospinErrorJsonSchema = Schema.Struct({
6
6
  code: Schema.String,
package/src/index.ts CHANGED
@@ -1,3 +1,2 @@
1
- export type * from './types';
2
1
  export * from './ZerospinError';
3
2
  export * from './ZerospinErrorJsonSchema';
package/tsconfig.json CHANGED
@@ -7,6 +7,7 @@
7
7
  "compilerOptions": {
8
8
  "incremental": false,
9
9
  "types": [
10
+ "node",
10
11
  "vitest/globals"
11
12
  ],
12
13
  "outDir": "./dist",
package/tsup.config.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from 'tsup';
2
+
3
+ export default defineConfig({
4
+ clean: true,
5
+ dts: true, // Declarations via tsup; typecheck script still separate
6
+ entry: ['src/index.ts'],
7
+ format: ['esm'],
8
+ minify: process.env.NODE_ENV === 'production',
9
+ outDir: 'dist',
10
+ sourcemap: false,
11
+ splitting: false,
12
+ });
13
+
@@ -1,44 +0,0 @@
1
- import { Brand, Data } from 'effect';
2
- import { isObject } from 'effect/Predicate';
3
- export class ZerospinError extends Data.TaggedError('ZerospinError') {
4
- message;
5
- constructor(props) {
6
- if (typeof props === 'string') {
7
- super({
8
- code: props,
9
- extra: null,
10
- message: props,
11
- status: null,
12
- });
13
- this.message = props;
14
- }
15
- else {
16
- super({
17
- status: null,
18
- ...props,
19
- });
20
- this.message = props.message ?? props.code;
21
- }
22
- }
23
- static isZerospinError(data) {
24
- return isObject(data) && '_tag' in data && data._tag === 'ZerospinError';
25
- }
26
- static makeZerospinErrorJson(props) {
27
- return Brand.nominal()(props);
28
- }
29
- serialize(
30
- // You can omit anything BUT id, code
31
- omit = []) {
32
- const json = ZerospinError.makeZerospinErrorJson({
33
- cause: this.cause,
34
- code: this.code,
35
- extra: this.extra,
36
- message: this.message,
37
- status: this.status,
38
- });
39
- for (const key of omit) {
40
- delete json[key];
41
- }
42
- return json;
43
- }
44
- }
@@ -1,7 +0,0 @@
1
- import { Schema } from 'effect';
2
- export const ZerospinErrorJsonSchema = Schema.Struct({
3
- code: Schema.String,
4
- extra: Schema.Unknown,
5
- message: Schema.String,
6
- status: Schema.Union(Schema.Null, Schema.Number),
7
- });
package/dist/types.js DELETED
@@ -1 +0,0 @@
1
- export {};
package/src/types.ts DELETED
@@ -1,13 +0,0 @@
1
- import type { Brand } from 'effect';
2
-
3
- import type { ZerospinError } from './ZerospinError';
4
-
5
- export type IAnyErrorJson<ERROR extends IAnyError = IAnyError> =
6
- Brand.Brand<'ZerospinErrorJson'> & {
7
- code: ERROR['code'];
8
- extra: ERROR['extra'];
9
- message: string;
10
- status: null | number;
11
- };
12
-
13
- export type IAnyError = ZerospinError<string>;