@adriangalilea/utils 0.4.1 → 0.5.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 +56 -21
- package/dist/offensive.d.ts +89 -17
- package/dist/offensive.d.ts.map +1 -1
- package/dist/offensive.js +103 -17
- package/dist/offensive.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,12 +107,14 @@ format.percentage(123.456) // "123%"
|
|
|
107
107
|
|
|
108
108
|
### Offensive Programming
|
|
109
109
|
|
|
110
|
-
Fail loud, fail fast.
|
|
110
|
+
Fail loud, fail fast. Zero dependencies, works in Node, Deno, Bun, and browsers.
|
|
111
|
+
|
|
112
|
+
Two kinds of errors, kept separate: **`Panic`** (bugs in us — crash the process) and **`SourcedError`** (boundary failures — handle per-source).
|
|
111
113
|
|
|
112
114
|
```typescript
|
|
113
|
-
import { assert, panic, must, unwrap, Panic } from '@adriangalilea/utils'
|
|
115
|
+
import { assert, panic, assertNever, must, unwrap, Panic, SourcedError, isSourcedError } from '@adriangalilea/utils'
|
|
114
116
|
|
|
115
|
-
// Assert invariants — narrows types
|
|
117
|
+
// Assert invariants — narrows types via `asserts condition`
|
|
116
118
|
assert(port > 0 && port < 65536, 'invalid port:', port)
|
|
117
119
|
|
|
118
120
|
// Impossible state
|
|
@@ -121,33 +123,66 @@ switch (state) {
|
|
|
121
123
|
default: panic('impossible state:', state)
|
|
122
124
|
}
|
|
123
125
|
|
|
126
|
+
// Exhaustiveness check — TS compile error if you miss a case
|
|
127
|
+
type Event = { kind: 'click' } | { kind: 'hover' } | { kind: 'scroll' }
|
|
128
|
+
function handle(e: Event) {
|
|
129
|
+
switch (e.kind) {
|
|
130
|
+
case 'click': return handleClick()
|
|
131
|
+
case 'hover': return handleHover()
|
|
132
|
+
// forgot 'scroll' → TS error: Argument of type '{ kind: "scroll" }' not assignable to 'never'
|
|
133
|
+
default: return assertNever(e)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// Add a new variant to Event → every assertNever site lights up at compile time.
|
|
137
|
+
|
|
124
138
|
// Unwrap operations that shouldn't fail (sync + async)
|
|
125
139
|
const data = must(() => JSON.parse(staticJsonString))
|
|
126
140
|
const file = must(() => readFileSync(path))
|
|
127
141
|
const resp = await must(() => fetch(url))
|
|
128
142
|
|
|
129
|
-
// Unwrap nullable values —
|
|
130
|
-
// (assert needs two statements, unwrap does it inline)
|
|
143
|
+
// Unwrap nullable values — T | null | undefined → T in one expression
|
|
131
144
|
const user = unwrap(db.findUser(id), 'user not found:', id)
|
|
132
145
|
const el = unwrap(document.getElementById('app'))
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### Typed boundary errors — `SourcedError`
|
|
149
|
+
|
|
150
|
+
Every external system call should wear its source. When it fails, carry forensics:
|
|
133
151
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
152
|
+
```typescript
|
|
153
|
+
import { SourcedError, isSourcedError, Panic } from '@adriangalilea/utils'
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
return await stripe.charges.create({ customer, amount })
|
|
157
|
+
} catch (e) {
|
|
158
|
+
throw new SourcedError({
|
|
159
|
+
source: 'stripe',
|
|
160
|
+
operation: 'charge_customer',
|
|
161
|
+
message: e instanceof Error ? e.message : String(e),
|
|
162
|
+
status: (e as any)?.statusCode,
|
|
163
|
+
cause: e,
|
|
164
|
+
context: { customer, amount },
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// At catch boundaries — keep Panics and SourcedErrors separate:
|
|
169
|
+
try { await doWork() }
|
|
170
|
+
catch (e) {
|
|
171
|
+
if (e instanceof Panic) throw e // bug in us — crash
|
|
172
|
+
if (isSourcedError(e, 'stripe') && e.status === 402) {
|
|
173
|
+
// TS knows e.source === 'stripe' here (generic narrows)
|
|
174
|
+
return { error: 'card declined' }
|
|
175
|
+
}
|
|
176
|
+
if (isSourcedError(e)) {
|
|
177
|
+
logger.error(`[${e.source}:${e.operation}]`, e.toJSON()) // structured forensics
|
|
178
|
+
throw e
|
|
179
|
+
}
|
|
180
|
+
throw e // unknown — re-throw
|
|
181
|
+
}
|
|
149
182
|
```
|
|
150
183
|
|
|
184
|
+
Every `SourcedError` carries `source`, `operation`, `status`, `context`, and the original exception via `cause`. Call `.toJSON()` for serialization across process boundaries.
|
|
185
|
+
|
|
151
186
|
## Features
|
|
152
187
|
|
|
153
188
|
- **Logger**: Next.js-style colored console output with symbols
|
|
@@ -158,7 +193,7 @@ expect(() => assert(false, 'boom')).toThrow(Panic)
|
|
|
158
193
|
- Percentage and basis point utilities
|
|
159
194
|
- Fiat and stablecoin detection
|
|
160
195
|
- **Format**: Number and currency formatting with compact notation
|
|
161
|
-
- **Offensive Programming**: assert, panic, must, unwrap
|
|
196
|
+
- **Offensive Programming**: assert, panic, assertNever, must, unwrap (throw `Panic`) + SourcedError for typed boundary failures
|
|
162
197
|
- **File Operations**: Read, write with automatic path resolution
|
|
163
198
|
- **Directory Operations**: Create, list, walk directories
|
|
164
199
|
- **KEV**: Redis-style environment variable management with monorepo support
|
package/dist/offensive.d.ts
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Offensive programming primitives + typed boundary errors.
|
|
3
3
|
*
|
|
4
|
-
* "A confused program SHOULD scream"
|
|
4
|
+
* "A confused program SHOULD scream." — John Carmack
|
|
5
5
|
*
|
|
6
6
|
* Throw, always. An uncaught Panic crashes the process with a full stack trace.
|
|
7
7
|
* Zero dependencies. Works identically in Node, Deno, Bun, and browsers.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* assert(cond, ...msg)
|
|
11
|
-
* panic(...msg)
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* Fail-fast primitives (throw Panic — bugs in us):
|
|
10
|
+
* assert(cond, ...msg) invariant checking, narrows types via `asserts`
|
|
11
|
+
* panic(...msg) impossible state reached
|
|
12
|
+
* assertNever(value, ...msg) exhaustiveness check — compile error on missed cases
|
|
13
|
+
* must(() => expr) unwrap-or-die for operations (sync + async)
|
|
14
|
+
* unwrap(value, ...msg) unwrap nullable T | null | undefined → T
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
+
* Typed boundary errors (throw SourcedError — the external system failed):
|
|
17
|
+
* SourcedError typed error with source/operation/status/context
|
|
18
|
+
* isSourcedError(e, source?) type guard for catch-site narrowing
|
|
16
19
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* const data = readFileSync(path, 'utf-8')
|
|
20
|
-
* return data
|
|
21
|
-
* } catch (err) {
|
|
22
|
-
* check(err)
|
|
23
|
-
* }
|
|
20
|
+
* Panics are bugs. SourcedErrors are boundary failures. Keep them separate at
|
|
21
|
+
* catch boundaries:
|
|
24
22
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
23
|
+
* try { await doWork() }
|
|
24
|
+
* catch (e) {
|
|
25
|
+
* if (e instanceof Panic) throw e // bug in us — crash
|
|
26
|
+
* if (isSourcedError(e, 'stripe')) { ... } // stripe failed — handle
|
|
27
|
+
* throw e // unknown — re-throw
|
|
28
|
+
* }
|
|
27
29
|
*/
|
|
28
30
|
/**
|
|
29
31
|
* Distinct error class for offensive programming failures.
|
|
@@ -61,6 +63,23 @@ export declare function assert(condition: boolean, ...msg: any[]): asserts condi
|
|
|
61
63
|
* }
|
|
62
64
|
*/
|
|
63
65
|
export declare function panic(...msg: any[]): never;
|
|
66
|
+
/**
|
|
67
|
+
* Exhaustiveness check — compile error if a switch/if misses a case.
|
|
68
|
+
* The `never` type means TS won't let you call this if all cases are handled.
|
|
69
|
+
* Add a new variant to a union → every assertNever site lights up at compile time.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* type Event = { kind: 'click' } | { kind: 'hover' } | { kind: 'scroll' }
|
|
73
|
+
* function handle(e: Event) {
|
|
74
|
+
* switch (e.kind) {
|
|
75
|
+
* case 'click': return handleClick()
|
|
76
|
+
* case 'hover': return handleHover()
|
|
77
|
+
* // forgot 'scroll' → TS error: Argument of type '{ kind: "scroll" }' not assignable to 'never'
|
|
78
|
+
* default: assertNever(e)
|
|
79
|
+
* }
|
|
80
|
+
* }
|
|
81
|
+
*/
|
|
82
|
+
export declare function assertNever(value: never, ...msg: any[]): never;
|
|
64
83
|
/**
|
|
65
84
|
* Must unwraps an operation that should never fail. Handles sync and async.
|
|
66
85
|
*
|
|
@@ -85,11 +104,64 @@ export declare function must<T>(fn: () => T): T;
|
|
|
85
104
|
* const el = unwrap(document.getElementById('app'))
|
|
86
105
|
*/
|
|
87
106
|
export declare function unwrap<T>(value: T | null | undefined, ...msg: any[]): T;
|
|
107
|
+
/**
|
|
108
|
+
* Typed error from a named external source with structured context.
|
|
109
|
+
*
|
|
110
|
+
* Raise at boundaries with the messy world (HTTP APIs, databases, external
|
|
111
|
+
* processes). Every SourcedError carries enough context to reconstruct the
|
|
112
|
+
* failure without a debugger.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* try {
|
|
116
|
+
* return await stripe.charges.create({ customer, amount })
|
|
117
|
+
* } catch (e) {
|
|
118
|
+
* throw new SourcedError({
|
|
119
|
+
* source: 'stripe',
|
|
120
|
+
* operation: 'charge_customer',
|
|
121
|
+
* message: e instanceof Error ? e.message : String(e),
|
|
122
|
+
* status: (e as any)?.statusCode,
|
|
123
|
+
* cause: e,
|
|
124
|
+
* context: { customer, amount },
|
|
125
|
+
* })
|
|
126
|
+
* }
|
|
127
|
+
*/
|
|
128
|
+
export declare class SourcedError<S extends string = string> extends Error {
|
|
129
|
+
readonly source: S;
|
|
130
|
+
readonly operation: string;
|
|
131
|
+
readonly status?: number;
|
|
132
|
+
readonly context: Record<string, unknown>;
|
|
133
|
+
constructor(args: {
|
|
134
|
+
source: S;
|
|
135
|
+
operation: string;
|
|
136
|
+
message: string;
|
|
137
|
+
status?: number;
|
|
138
|
+
cause?: unknown;
|
|
139
|
+
context?: Record<string, unknown>;
|
|
140
|
+
});
|
|
141
|
+
toJSON(): Record<string, unknown>;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Type guard for SourcedError. Optionally narrows to a specific source.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* try { await charge(customer, amount) }
|
|
148
|
+
* catch (e) {
|
|
149
|
+
* if (isSourcedError(e, 'stripe') && e.status === 402) {
|
|
150
|
+
* // TS knows e.source === 'stripe' here
|
|
151
|
+
* return 'card declined'
|
|
152
|
+
* }
|
|
153
|
+
* throw e
|
|
154
|
+
* }
|
|
155
|
+
*/
|
|
156
|
+
export declare function isSourcedError<S extends string>(e: unknown, source?: S): e is SourcedError<S>;
|
|
88
157
|
export declare const offensive: {
|
|
89
158
|
Panic: typeof Panic;
|
|
90
159
|
assert: typeof assert;
|
|
91
160
|
panic: typeof panic;
|
|
161
|
+
assertNever: typeof assertNever;
|
|
92
162
|
must: typeof must;
|
|
93
163
|
unwrap: typeof unwrap;
|
|
164
|
+
SourcedError: typeof SourcedError;
|
|
165
|
+
isSourcedError: typeof isSourcedError;
|
|
94
166
|
};
|
|
95
167
|
//# sourceMappingURL=offensive.d.ts.map
|
package/dist/offensive.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"offensive.d.ts","sourceRoot":"","sources":["../src/offensive.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"offensive.d.ts","sourceRoot":"","sources":["../src/offensive.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH;;;;;;;;;;;;;GAaG;AACH,qBAAa,KAAM,SAAQ,KAAK;gBAClB,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;GAMG;AACH,wBAAgB,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,SAAS,CAE3E;AAED;;;;;;;;;GASG;AACH,wBAAgB,KAAK,CAAC,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,CAE1C;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,KAAK,CAE9D;AAED;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;AACzD,wBAAgB,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,CAAA;AAevC;;;;;;;;;;;;GAYG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,EAAE,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAGvE;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,YAAY,CAAC,CAAC,SAAS,MAAM,GAAG,MAAM,CAAE,SAAQ,KAAK;IAChE,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAA;IAClB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;gBAE7B,IAAI,EAAE;QAChB,MAAM,EAAE,CAAC,CAAA;QACT,SAAS,EAAE,MAAM,CAAA;QACjB,OAAO,EAAE,MAAM,CAAA;QACf,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAClC;IAUD,MAAM,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAUlC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,CAAC,CAAC,CAE7F;AAED,eAAO,MAAM,SAAS;;;;;;;;;CASrB,CAAA"}
|
package/dist/offensive.js
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Offensive programming primitives + typed boundary errors.
|
|
3
3
|
*
|
|
4
|
-
* "A confused program SHOULD scream"
|
|
4
|
+
* "A confused program SHOULD scream." — John Carmack
|
|
5
5
|
*
|
|
6
6
|
* Throw, always. An uncaught Panic crashes the process with a full stack trace.
|
|
7
7
|
* Zero dependencies. Works identically in Node, Deno, Bun, and browsers.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* assert(cond, ...msg)
|
|
11
|
-
* panic(...msg)
|
|
12
|
-
*
|
|
13
|
-
*
|
|
9
|
+
* Fail-fast primitives (throw Panic — bugs in us):
|
|
10
|
+
* assert(cond, ...msg) invariant checking, narrows types via `asserts`
|
|
11
|
+
* panic(...msg) impossible state reached
|
|
12
|
+
* assertNever(value, ...msg) exhaustiveness check — compile error on missed cases
|
|
13
|
+
* must(() => expr) unwrap-or-die for operations (sync + async)
|
|
14
|
+
* unwrap(value, ...msg) unwrap nullable T | null | undefined → T
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
+
* Typed boundary errors (throw SourcedError — the external system failed):
|
|
17
|
+
* SourcedError typed error with source/operation/status/context
|
|
18
|
+
* isSourcedError(e, source?) type guard for catch-site narrowing
|
|
16
19
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* const data = readFileSync(path, 'utf-8')
|
|
20
|
-
* return data
|
|
21
|
-
* } catch (err) {
|
|
22
|
-
* check(err)
|
|
23
|
-
* }
|
|
20
|
+
* Panics are bugs. SourcedErrors are boundary failures. Keep them separate at
|
|
21
|
+
* catch boundaries:
|
|
24
22
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
23
|
+
* try { await doWork() }
|
|
24
|
+
* catch (e) {
|
|
25
|
+
* if (e instanceof Panic) throw e // bug in us — crash
|
|
26
|
+
* if (isSourcedError(e, 'stripe')) { ... } // stripe failed — handle
|
|
27
|
+
* throw e // unknown — re-throw
|
|
28
|
+
* }
|
|
27
29
|
*/
|
|
28
30
|
/**
|
|
29
31
|
* Distinct error class for offensive programming failures.
|
|
@@ -69,6 +71,25 @@ export function assert(condition, ...msg) {
|
|
|
69
71
|
export function panic(...msg) {
|
|
70
72
|
throw new Panic(msg.join(' ') || 'panic');
|
|
71
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Exhaustiveness check — compile error if a switch/if misses a case.
|
|
76
|
+
* The `never` type means TS won't let you call this if all cases are handled.
|
|
77
|
+
* Add a new variant to a union → every assertNever site lights up at compile time.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* type Event = { kind: 'click' } | { kind: 'hover' } | { kind: 'scroll' }
|
|
81
|
+
* function handle(e: Event) {
|
|
82
|
+
* switch (e.kind) {
|
|
83
|
+
* case 'click': return handleClick()
|
|
84
|
+
* case 'hover': return handleHover()
|
|
85
|
+
* // forgot 'scroll' → TS error: Argument of type '{ kind: "scroll" }' not assignable to 'never'
|
|
86
|
+
* default: assertNever(e)
|
|
87
|
+
* }
|
|
88
|
+
* }
|
|
89
|
+
*/
|
|
90
|
+
export function assertNever(value, ...msg) {
|
|
91
|
+
throw new Panic(msg.join(' ') || `assertNever: unexpected value: ${JSON.stringify(value)}`);
|
|
92
|
+
}
|
|
72
93
|
export function must(fn) {
|
|
73
94
|
try {
|
|
74
95
|
const result = fn();
|
|
@@ -101,11 +122,76 @@ export function unwrap(value, ...msg) {
|
|
|
101
122
|
throw new Panic(msg.join(' ') || `unwrap: got ${value}`);
|
|
102
123
|
return value;
|
|
103
124
|
}
|
|
125
|
+
/**
|
|
126
|
+
* Typed error from a named external source with structured context.
|
|
127
|
+
*
|
|
128
|
+
* Raise at boundaries with the messy world (HTTP APIs, databases, external
|
|
129
|
+
* processes). Every SourcedError carries enough context to reconstruct the
|
|
130
|
+
* failure without a debugger.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* try {
|
|
134
|
+
* return await stripe.charges.create({ customer, amount })
|
|
135
|
+
* } catch (e) {
|
|
136
|
+
* throw new SourcedError({
|
|
137
|
+
* source: 'stripe',
|
|
138
|
+
* operation: 'charge_customer',
|
|
139
|
+
* message: e instanceof Error ? e.message : String(e),
|
|
140
|
+
* status: (e as any)?.statusCode,
|
|
141
|
+
* cause: e,
|
|
142
|
+
* context: { customer, amount },
|
|
143
|
+
* })
|
|
144
|
+
* }
|
|
145
|
+
*/
|
|
146
|
+
export class SourcedError extends Error {
|
|
147
|
+
source;
|
|
148
|
+
operation;
|
|
149
|
+
status;
|
|
150
|
+
context;
|
|
151
|
+
constructor(args) {
|
|
152
|
+
const status = args.status != null ? ` status=${args.status}` : '';
|
|
153
|
+
super(`[${args.source}:${args.operation}${status}] ${args.message}`, { cause: args.cause });
|
|
154
|
+
this.name = 'SourcedError';
|
|
155
|
+
this.source = args.source;
|
|
156
|
+
this.operation = args.operation;
|
|
157
|
+
this.status = args.status;
|
|
158
|
+
this.context = args.context ?? {};
|
|
159
|
+
}
|
|
160
|
+
toJSON() {
|
|
161
|
+
return {
|
|
162
|
+
source: this.source,
|
|
163
|
+
operation: this.operation,
|
|
164
|
+
status: this.status,
|
|
165
|
+
message: this.message,
|
|
166
|
+
context: this.context,
|
|
167
|
+
cause: this.cause instanceof Error ? this.cause.message : this.cause != null ? String(this.cause) : null,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Type guard for SourcedError. Optionally narrows to a specific source.
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* try { await charge(customer, amount) }
|
|
176
|
+
* catch (e) {
|
|
177
|
+
* if (isSourcedError(e, 'stripe') && e.status === 402) {
|
|
178
|
+
* // TS knows e.source === 'stripe' here
|
|
179
|
+
* return 'card declined'
|
|
180
|
+
* }
|
|
181
|
+
* throw e
|
|
182
|
+
* }
|
|
183
|
+
*/
|
|
184
|
+
export function isSourcedError(e, source) {
|
|
185
|
+
return e instanceof SourcedError && (source === undefined || e.source === source);
|
|
186
|
+
}
|
|
104
187
|
export const offensive = {
|
|
105
188
|
Panic,
|
|
106
189
|
assert,
|
|
107
190
|
panic,
|
|
191
|
+
assertNever,
|
|
108
192
|
must,
|
|
109
193
|
unwrap,
|
|
194
|
+
SourcedError,
|
|
195
|
+
isSourcedError,
|
|
110
196
|
};
|
|
111
197
|
//# sourceMappingURL=offensive.js.map
|
package/dist/offensive.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"offensive.js","sourceRoot":"","sources":["../src/offensive.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"offensive.js","sourceRoot":"","sources":["../src/offensive.ts"],"names":[],"mappings":"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"}
|