@ewyn/client 0.1.0 → 0.3.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 +64 -16
- package/dist/__tests__/cli-fetch-config.test.d.ts +2 -0
- package/dist/__tests__/cli-fetch-config.test.d.ts.map +1 -0
- package/dist/__tests__/cli-fetch-config.test.js +70 -0
- package/dist/__tests__/config-types.test-d.js +7 -7
- package/dist/__tests__/dashboard-config.test.js +7 -12
- package/dist/__tests__/errors.test.js +37 -37
- package/dist/__tests__/ewyn.test.d.ts +2 -0
- package/dist/__tests__/ewyn.test.d.ts.map +1 -0
- package/dist/__tests__/ewyn.test.js +428 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +140 -0
- package/dist/errors.d.ts +2 -2
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -4
- package/dist/index.d.ts +6 -54
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -14
- package/dist/types.d.ts +17 -7
- package/dist/types.d.ts.map +1 -1
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -22,12 +22,11 @@ yarn add @ewyn/client
|
|
|
22
22
|
### Basic Usage
|
|
23
23
|
|
|
24
24
|
```typescript
|
|
25
|
-
import {
|
|
25
|
+
import { Ewyn } from '@ewyn/client';
|
|
26
26
|
|
|
27
|
-
const client = new
|
|
27
|
+
const client = new Ewyn({
|
|
28
28
|
workspaceId: 'your-workspace-id',
|
|
29
29
|
apiKey: 'your-api-key',
|
|
30
|
-
baseUrl: 'https://www.ewyn.ai/api/v1', // optional; omit to use production default
|
|
31
30
|
});
|
|
32
31
|
|
|
33
32
|
// Send an email using template version ID
|
|
@@ -46,7 +45,7 @@ await client.send({
|
|
|
46
45
|
Get full TypeScript autocomplete and validation by copying your template config from the dashboard:
|
|
47
46
|
|
|
48
47
|
```typescript
|
|
49
|
-
import {
|
|
48
|
+
import { Ewyn } from '@ewyn/client';
|
|
50
49
|
|
|
51
50
|
// 1. Copy template config from dashboard (API Keys page)
|
|
52
51
|
const config = {
|
|
@@ -71,7 +70,7 @@ const config = {
|
|
|
71
70
|
} as const; // ← Important: use 'as const'
|
|
72
71
|
|
|
73
72
|
// 2. Initialize with config
|
|
74
|
-
const client = new
|
|
73
|
+
const client = new Ewyn({
|
|
75
74
|
workspaceId: 'your-workspace-id',
|
|
76
75
|
apiKey: 'your-api-key',
|
|
77
76
|
templates: config,
|
|
@@ -91,7 +90,58 @@ await client.send({
|
|
|
91
90
|
|
|
92
91
|
## Getting Your Template Configuration
|
|
93
92
|
|
|
94
|
-
|
|
93
|
+
You can get your template config in three ways:
|
|
94
|
+
|
|
95
|
+
1. **CLI (recommended)** – Run `npx @ewyn/client fetch-config` to download config and generate `ewynTemplates.ts` (see [Fetching template config (CLI)](#fetching-template-config-cli)).
|
|
96
|
+
2. **Dashboard** – Copy JSON from the API Keys page.
|
|
97
|
+
3. **API** – Call the templates config endpoint with curl or your own code.
|
|
98
|
+
|
|
99
|
+
### Fetching template config (CLI)
|
|
100
|
+
|
|
101
|
+
The easiest way to keep your template config in sync is to use the built-in CLI. From your project root (where you have an `ewyn.config.ts` or `ewyn.config.js`), run:
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npx @ewyn/client fetch-config
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
This fetches your workspace’s template config from the API and writes a TypeScript file (e.g. `ewynTemplates.ts`) that you can import for type-safe sending.
|
|
108
|
+
|
|
109
|
+
**Config file** (`ewyn.config.ts` in project root):
|
|
110
|
+
|
|
111
|
+
- `workspaceId` (required): Your workspace UUID (e.g. `process.env.EWYN_WORKSPACE_ID`)
|
|
112
|
+
- `apiKey` (required): Your API key (e.g. `process.env.EWYN_API_KEY`)
|
|
113
|
+
- `configurationPath` (optional): Where to write the generated file (e.g. `./src/ewynTemplates.ts`). If omitted, the file is written as `./ewynTemplates.ts` in the current directory.
|
|
114
|
+
|
|
115
|
+
Example `ewyn.config.ts`:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import type { EwynFetchConfig } from '@ewyn/client';
|
|
119
|
+
|
|
120
|
+
const config: EwynFetchConfig = {
|
|
121
|
+
workspaceId: process.env.EWYN_WORKSPACE_ID!,
|
|
122
|
+
apiKey: process.env.EWYN_API_KEY!,
|
|
123
|
+
configurationPath: './src/ewynTemplates.ts', // optional
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export default config;
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
After running `fetch-config`, import the generated config and pass it to the client:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { Ewyn } from '@ewyn/client';
|
|
133
|
+
import { ewynTemplates } from './ewynTemplates'; // or from your configurationPath
|
|
134
|
+
|
|
135
|
+
const client = new Ewyn({
|
|
136
|
+
workspaceId: process.env.EWYN_WORKSPACE_ID!,
|
|
137
|
+
apiKey: process.env.EWYN_API_KEY!,
|
|
138
|
+
templates: ewynTemplates,
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Note:** Regenerate the config after creating or updating templates (re-run `npx @ewyn/client fetch-config`).
|
|
143
|
+
|
|
144
|
+
### From Dashboard
|
|
95
145
|
|
|
96
146
|
1. Go to your dashboard → **API Keys** page
|
|
97
147
|
2. Scroll to **Template Configuration** section
|
|
@@ -103,7 +153,7 @@ const config = {
|
|
|
103
153
|
// ... paste here
|
|
104
154
|
} as const;
|
|
105
155
|
|
|
106
|
-
const client = new
|
|
156
|
+
const client = new Ewyn({
|
|
107
157
|
workspaceId: 'your-workspace-id',
|
|
108
158
|
apiKey: 'your-api-key',
|
|
109
159
|
templates: config, // Now fully type-safe!
|
|
@@ -119,23 +169,20 @@ curl -X GET https://www.ewyn.ai/api/v1/workspaces/YOUR_WORKSPACE_ID/templates/co
|
|
|
119
169
|
-H "Authorization: Bearer YOUR_API_KEY"
|
|
120
170
|
```
|
|
121
171
|
|
|
122
|
-
**Note:** Regenerate the config after creating or updating templates to keep it in sync.
|
|
123
|
-
|
|
124
172
|
## API Reference
|
|
125
173
|
|
|
126
|
-
### `
|
|
174
|
+
### `Ewyn` Class
|
|
127
175
|
|
|
128
176
|
#### Constructor
|
|
129
177
|
|
|
130
178
|
```typescript
|
|
131
|
-
new
|
|
179
|
+
new Ewyn(options: EwynOptions)
|
|
132
180
|
```
|
|
133
181
|
|
|
134
182
|
**Options:**
|
|
135
183
|
|
|
136
184
|
- `workspaceId` (string, required): Your workspace UUID
|
|
137
185
|
- `apiKey` (string, required): Your API key secret
|
|
138
|
-
- `baseUrl` (string, optional): Base URL for the API (defaults to `https://www.ewyn.ai/api/v1`)
|
|
139
186
|
- `templates` (TemplateConfig, optional): Template configuration for name-based sending
|
|
140
187
|
- `maxRetries` (number, optional): Maximum retries for retryable errors (default: 3)
|
|
141
188
|
- `timeout` (number, optional): Request timeout in milliseconds (default: 30000)
|
|
@@ -168,20 +215,20 @@ Send an email using a template.
|
|
|
168
215
|
|
|
169
216
|
**Throws:**
|
|
170
217
|
|
|
171
|
-
- `
|
|
218
|
+
- `EwynApiError`: If the API request fails
|
|
172
219
|
- `Error`: If validation fails (missing template, missing variables, etc.)
|
|
173
220
|
|
|
174
221
|
### Error Handling
|
|
175
222
|
|
|
176
|
-
The SDK throws `
|
|
223
|
+
The SDK throws `EwynApiError` for API errors:
|
|
177
224
|
|
|
178
225
|
```typescript
|
|
179
|
-
import {
|
|
226
|
+
import { Ewyn, EwynApiError } from '@ewyn/client';
|
|
180
227
|
|
|
181
228
|
try {
|
|
182
229
|
await client.send({ /* ... */ });
|
|
183
230
|
} catch (error) {
|
|
184
|
-
if (error instanceof
|
|
231
|
+
if (error instanceof EwynApiError) {
|
|
185
232
|
console.error('API Error:', error.status, error.message);
|
|
186
233
|
console.error('Details:', error.details);
|
|
187
234
|
|
|
@@ -235,6 +282,7 @@ Check out the [examples directory](./examples/) for comprehensive examples:
|
|
|
235
282
|
|
|
236
283
|
- [Basic send](./examples/basic-send.ts) - Simple sending with template ID
|
|
237
284
|
- [Type-safe usage](./examples/with-template-config.ts) - Full type safety with template config
|
|
285
|
+
- [ewyn.config example](./examples/ewyn.config.example.ts) - Example config for `fetch-config` CLI
|
|
238
286
|
- [Error handling](./examples/error-handling.ts) - Comprehensive error handling patterns
|
|
239
287
|
- [Idempotency](./examples/idempotency.ts) - Preventing duplicate sends
|
|
240
288
|
- [Advanced patterns](./examples/advanced-usage.ts) - Batch sending, retries, and more
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-fetch-config.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/cli-fetch-config.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
+
import { runFetchConfig } from '../cli.js';
|
|
6
|
+
const MOCK_TEMPLATES = {
|
|
7
|
+
welcome: {
|
|
8
|
+
id: 'version-uuid-1',
|
|
9
|
+
name: 'Welcome Email',
|
|
10
|
+
version: 1,
|
|
11
|
+
vars: {
|
|
12
|
+
firstName: { required: true },
|
|
13
|
+
lastName: { required: false },
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
describe('fetch-config CLI', () => {
|
|
18
|
+
let tempDir;
|
|
19
|
+
let originalFetch;
|
|
20
|
+
beforeEach(() => {
|
|
21
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ewyn-cli-test-'));
|
|
22
|
+
originalFetch = globalThis.fetch;
|
|
23
|
+
globalThis.fetch = vi.fn().mockResolvedValue({
|
|
24
|
+
ok: true,
|
|
25
|
+
json: async () => ({ templates: MOCK_TEMPLATES }),
|
|
26
|
+
text: async () => '',
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
globalThis.fetch = originalFetch;
|
|
31
|
+
vi.restoreAllMocks();
|
|
32
|
+
try {
|
|
33
|
+
fs.rmSync(tempDir, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// ignore
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
it('writes ewynTemplates.ts with fetched config when using default path', async () => {
|
|
40
|
+
const configPath = path.join(tempDir, 'ewyn.config.js');
|
|
41
|
+
fs.writeFileSync(configPath, `export default {
|
|
42
|
+
workspaceId: 'test-workspace-id',
|
|
43
|
+
apiKey: 'test-api-key',
|
|
44
|
+
};
|
|
45
|
+
`, 'utf-8');
|
|
46
|
+
await runFetchConfig(tempDir);
|
|
47
|
+
const outputPath = path.join(tempDir, 'ewynTemplates.ts');
|
|
48
|
+
expect(fs.existsSync(outputPath)).toBe(true);
|
|
49
|
+
const content = fs.readFileSync(outputPath, 'utf-8');
|
|
50
|
+
expect(content).toContain('// Generated by @ewyn/client fetch-config');
|
|
51
|
+
expect(content).toContain('export const ewynTemplates = ');
|
|
52
|
+
expect(content).toContain('"welcome"');
|
|
53
|
+
expect(content).toContain('"version-uuid-1"');
|
|
54
|
+
expect(content).toContain(' as const;');
|
|
55
|
+
});
|
|
56
|
+
it('writes to configurationPath when set in config', async () => {
|
|
57
|
+
const configPath = path.join(tempDir, 'ewyn.config.js');
|
|
58
|
+
fs.writeFileSync(configPath, `export default {
|
|
59
|
+
workspaceId: 'ws-1',
|
|
60
|
+
apiKey: 'key-1',
|
|
61
|
+
configurationPath: './src/ewynTemplates.ts',
|
|
62
|
+
};
|
|
63
|
+
`, 'utf-8');
|
|
64
|
+
await runFetchConfig(tempDir);
|
|
65
|
+
const outputPath = path.join(tempDir, 'src', 'ewynTemplates.ts');
|
|
66
|
+
expect(fs.existsSync(outputPath)).toBe(true);
|
|
67
|
+
const content = fs.readFileSync(outputPath, 'utf-8');
|
|
68
|
+
expect(content).toContain('"welcome"');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* variables at compile time.
|
|
8
8
|
*/
|
|
9
9
|
import { expectTypeOf, test } from 'vitest';
|
|
10
|
-
import {
|
|
10
|
+
import { Ewyn } from '../index.js';
|
|
11
11
|
// Dashboard-shaped config: same structure as getConfig response (id, name, version, vars)
|
|
12
12
|
const dashboardConfig = {
|
|
13
13
|
welcome: {
|
|
@@ -29,7 +29,7 @@ const dashboardConfig = {
|
|
|
29
29
|
},
|
|
30
30
|
};
|
|
31
31
|
test('client with dashboard config has typed send', () => {
|
|
32
|
-
const client = new
|
|
32
|
+
const client = new Ewyn({
|
|
33
33
|
workspaceId: 'ws',
|
|
34
34
|
apiKey: 'key',
|
|
35
35
|
templates: dashboardConfig,
|
|
@@ -38,7 +38,7 @@ test('client with dashboard config has typed send', () => {
|
|
|
38
38
|
expectTypeOf(client.send).toBeFunction();
|
|
39
39
|
});
|
|
40
40
|
test('valid send: required var present, optional omitted', () => {
|
|
41
|
-
const client = new
|
|
41
|
+
const client = new Ewyn({
|
|
42
42
|
workspaceId: 'ws',
|
|
43
43
|
apiKey: 'key',
|
|
44
44
|
templates: dashboardConfig,
|
|
@@ -50,7 +50,7 @@ test('valid send: required var present, optional omitted', () => {
|
|
|
50
50
|
}).toMatchTypeOf();
|
|
51
51
|
});
|
|
52
52
|
test('valid send: required and optional vars', () => {
|
|
53
|
-
const client = new
|
|
53
|
+
const client = new Ewyn({
|
|
54
54
|
workspaceId: 'ws',
|
|
55
55
|
apiKey: 'key',
|
|
56
56
|
templates: dashboardConfig,
|
|
@@ -62,7 +62,7 @@ test('valid send: required and optional vars', () => {
|
|
|
62
62
|
}).toMatchTypeOf();
|
|
63
63
|
});
|
|
64
64
|
test('valid send: hyphenated template name', () => {
|
|
65
|
-
const client = new
|
|
65
|
+
const client = new Ewyn({
|
|
66
66
|
workspaceId: 'ws',
|
|
67
67
|
apiKey: 'key',
|
|
68
68
|
templates: dashboardConfig,
|
|
@@ -74,7 +74,7 @@ test('valid send: hyphenated template name', () => {
|
|
|
74
74
|
}).toMatchTypeOf();
|
|
75
75
|
});
|
|
76
76
|
test('send return type is Promise<SendEmailResponse>', () => {
|
|
77
|
-
const client = new
|
|
77
|
+
const client = new Ewyn({
|
|
78
78
|
workspaceId: 'ws',
|
|
79
79
|
apiKey: 'key',
|
|
80
80
|
templates: dashboardConfig,
|
|
@@ -86,7 +86,7 @@ test('template name is constrained to config keys at type level', () => {
|
|
|
86
86
|
expectTypeOf().toMatchTypeOf();
|
|
87
87
|
});
|
|
88
88
|
test('missing required variable is rejected by types', () => {
|
|
89
|
-
const client = new
|
|
89
|
+
const client = new Ewyn({
|
|
90
90
|
workspaceId: 'ws',
|
|
91
91
|
apiKey: 'key',
|
|
92
92
|
templates: dashboardConfig,
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* the dashboard" work with the SDK and that runtime validation behaves correctly.
|
|
8
8
|
*/
|
|
9
9
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
10
|
-
import {
|
|
10
|
+
import { Ewyn } from '../index.js';
|
|
11
11
|
global.fetch = vi.fn();
|
|
12
12
|
describe('Dashboard config (runtime)', () => {
|
|
13
13
|
beforeEach(() => {
|
|
@@ -45,10 +45,9 @@ describe('Dashboard config (runtime)', () => {
|
|
|
45
45
|
},
|
|
46
46
|
};
|
|
47
47
|
it('sends with dashboard-shaped config (only required vars)', async () => {
|
|
48
|
-
const client = new
|
|
48
|
+
const client = new Ewyn({
|
|
49
49
|
workspaceId: 'ws-1',
|
|
50
50
|
apiKey: 'key-1',
|
|
51
|
-
baseUrl: 'https://api.test.com/api/v1',
|
|
52
51
|
templates: configOnlyRequired,
|
|
53
52
|
maxRetries: 1,
|
|
54
53
|
timeout: 5000,
|
|
@@ -72,7 +71,7 @@ describe('Dashboard config (runtime)', () => {
|
|
|
72
71
|
},
|
|
73
72
|
});
|
|
74
73
|
expect(result).toEqual(mockResponse);
|
|
75
|
-
expect(global.fetch).toHaveBeenCalledWith('https://
|
|
74
|
+
expect(global.fetch).toHaveBeenCalledWith('https://www.ewyn.ai/api/v1/workspaces/ws-1/send', expect.objectContaining({
|
|
76
75
|
method: 'POST',
|
|
77
76
|
body: expect.stringContaining('"templateId":"version-uuid-1"'),
|
|
78
77
|
}));
|
|
@@ -81,10 +80,9 @@ describe('Dashboard config (runtime)', () => {
|
|
|
81
80
|
expect(body.variables).toEqual({ firstName: 'Jane', lastName: 'Doe' });
|
|
82
81
|
});
|
|
83
82
|
it('sends with dashboard-shaped config (mixed required/optional, hyphenated key)', async () => {
|
|
84
|
-
const client = new
|
|
83
|
+
const client = new Ewyn({
|
|
85
84
|
workspaceId: 'ws-2',
|
|
86
85
|
apiKey: 'key-2',
|
|
87
|
-
baseUrl: 'https://api.test.com/api/v1',
|
|
88
86
|
templates: configMixedVars,
|
|
89
87
|
maxRetries: 1,
|
|
90
88
|
timeout: 5000,
|
|
@@ -117,10 +115,9 @@ describe('Dashboard config (runtime)', () => {
|
|
|
117
115
|
});
|
|
118
116
|
});
|
|
119
117
|
it('sends with template that has empty vars (dashboard shape)', async () => {
|
|
120
|
-
const client = new
|
|
118
|
+
const client = new Ewyn({
|
|
121
119
|
workspaceId: 'ws-3',
|
|
122
120
|
apiKey: 'key-3',
|
|
123
|
-
baseUrl: 'https://api.test.com/api/v1',
|
|
124
121
|
templates: configEmptyVars,
|
|
125
122
|
maxRetries: 1,
|
|
126
123
|
timeout: 5000,
|
|
@@ -147,10 +144,9 @@ describe('Dashboard config (runtime)', () => {
|
|
|
147
144
|
expect(body.variables).toEqual({});
|
|
148
145
|
});
|
|
149
146
|
it('throws when required variable is missing (dashboard config)', async () => {
|
|
150
|
-
const client = new
|
|
147
|
+
const client = new Ewyn({
|
|
151
148
|
workspaceId: 'ws-1',
|
|
152
149
|
apiKey: 'key-1',
|
|
153
|
-
baseUrl: 'https://api.test.com/api/v1',
|
|
154
150
|
templates: configOnlyRequired,
|
|
155
151
|
maxRetries: 1,
|
|
156
152
|
timeout: 5000,
|
|
@@ -165,10 +161,9 @@ describe('Dashboard config (runtime)', () => {
|
|
|
165
161
|
})).rejects.toThrow('Missing required variables for template "welcome": lastName');
|
|
166
162
|
});
|
|
167
163
|
it('throws when template name is not in config (dashboard config)', async () => {
|
|
168
|
-
const client = new
|
|
164
|
+
const client = new Ewyn({
|
|
169
165
|
workspaceId: 'ws-1',
|
|
170
166
|
apiKey: 'key-1',
|
|
171
|
-
baseUrl: 'https://api.test.com/api/v1',
|
|
172
167
|
templates: configOnlyRequired,
|
|
173
168
|
maxRetries: 1,
|
|
174
169
|
timeout: 5000,
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
import { describe,
|
|
2
|
-
import {
|
|
3
|
-
describe('
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { EwynApiError } from '../errors.js';
|
|
3
|
+
describe('EwynApiError', () => {
|
|
4
4
|
describe('constructor', () => {
|
|
5
5
|
it('should create error with status code', () => {
|
|
6
|
-
const error = new
|
|
6
|
+
const error = new EwynApiError(400);
|
|
7
7
|
expect(error).toBeInstanceOf(Error);
|
|
8
|
-
expect(error).toBeInstanceOf(
|
|
8
|
+
expect(error).toBeInstanceOf(EwynApiError);
|
|
9
9
|
expect(error.status).toBe(400);
|
|
10
10
|
expect(error.message).toBe('API request failed with status 400');
|
|
11
|
-
expect(error.name).toBe('
|
|
11
|
+
expect(error.name).toBe('EwynApiError');
|
|
12
12
|
});
|
|
13
13
|
it('should create error with custom message', () => {
|
|
14
|
-
const error = new
|
|
14
|
+
const error = new EwynApiError(400, 'INVALID_REQUEST', undefined, 'Invalid email address');
|
|
15
15
|
expect(error.status).toBe(400);
|
|
16
16
|
expect(error.code).toBe('INVALID_REQUEST');
|
|
17
17
|
expect(error.message).toBe('Invalid email address');
|
|
18
18
|
});
|
|
19
19
|
it('should create error with details', () => {
|
|
20
20
|
const details = { field: 'to', reason: 'Invalid email format' };
|
|
21
|
-
const error = new
|
|
21
|
+
const error = new EwynApiError(400, 'VALIDATION_ERROR', details);
|
|
22
22
|
expect(error.status).toBe(400);
|
|
23
23
|
expect(error.code).toBe('VALIDATION_ERROR');
|
|
24
24
|
expect(error.details).toEqual(details);
|
|
@@ -26,62 +26,62 @@ describe('MailerApiError', () => {
|
|
|
26
26
|
});
|
|
27
27
|
describe('isClientError', () => {
|
|
28
28
|
it('should return true for 4xx errors', () => {
|
|
29
|
-
expect(new
|
|
30
|
-
expect(new
|
|
31
|
-
expect(new
|
|
32
|
-
expect(new
|
|
33
|
-
expect(new
|
|
29
|
+
expect(new EwynApiError(400).isClientError()).toBe(true);
|
|
30
|
+
expect(new EwynApiError(401).isClientError()).toBe(true);
|
|
31
|
+
expect(new EwynApiError(404).isClientError()).toBe(true);
|
|
32
|
+
expect(new EwynApiError(429).isClientError()).toBe(true);
|
|
33
|
+
expect(new EwynApiError(499).isClientError()).toBe(true);
|
|
34
34
|
});
|
|
35
35
|
it('should return false for non-4xx errors', () => {
|
|
36
|
-
expect(new
|
|
37
|
-
expect(new
|
|
38
|
-
expect(new
|
|
36
|
+
expect(new EwynApiError(200).isClientError()).toBe(false);
|
|
37
|
+
expect(new EwynApiError(399).isClientError()).toBe(false);
|
|
38
|
+
expect(new EwynApiError(500).isClientError()).toBe(false);
|
|
39
39
|
});
|
|
40
40
|
});
|
|
41
41
|
describe('isServerError', () => {
|
|
42
42
|
it('should return true for 5xx errors', () => {
|
|
43
|
-
expect(new
|
|
44
|
-
expect(new
|
|
45
|
-
expect(new
|
|
46
|
-
expect(new
|
|
43
|
+
expect(new EwynApiError(500).isServerError()).toBe(true);
|
|
44
|
+
expect(new EwynApiError(502).isServerError()).toBe(true);
|
|
45
|
+
expect(new EwynApiError(503).isServerError()).toBe(true);
|
|
46
|
+
expect(new EwynApiError(599).isServerError()).toBe(true);
|
|
47
47
|
});
|
|
48
48
|
it('should return false for non-5xx errors', () => {
|
|
49
|
-
expect(new
|
|
50
|
-
expect(new
|
|
51
|
-
expect(new
|
|
49
|
+
expect(new EwynApiError(200).isServerError()).toBe(false);
|
|
50
|
+
expect(new EwynApiError(400).isServerError()).toBe(false);
|
|
51
|
+
expect(new EwynApiError(499).isServerError()).toBe(false);
|
|
52
52
|
});
|
|
53
53
|
});
|
|
54
54
|
describe('isRetryable', () => {
|
|
55
55
|
it('should return true for 429 rate limit', () => {
|
|
56
|
-
expect(new
|
|
56
|
+
expect(new EwynApiError(429).isRetryable()).toBe(true);
|
|
57
57
|
});
|
|
58
58
|
it('should return true for 5xx errors', () => {
|
|
59
|
-
expect(new
|
|
60
|
-
expect(new
|
|
61
|
-
expect(new
|
|
59
|
+
expect(new EwynApiError(500).isRetryable()).toBe(true);
|
|
60
|
+
expect(new EwynApiError(502).isRetryable()).toBe(true);
|
|
61
|
+
expect(new EwynApiError(503).isRetryable()).toBe(true);
|
|
62
62
|
});
|
|
63
63
|
it('should return false for other errors', () => {
|
|
64
|
-
expect(new
|
|
65
|
-
expect(new
|
|
66
|
-
expect(new
|
|
67
|
-
expect(new
|
|
64
|
+
expect(new EwynApiError(200).isRetryable()).toBe(false);
|
|
65
|
+
expect(new EwynApiError(400).isRetryable()).toBe(false);
|
|
66
|
+
expect(new EwynApiError(401).isRetryable()).toBe(false);
|
|
67
|
+
expect(new EwynApiError(404).isRetryable()).toBe(false);
|
|
68
68
|
});
|
|
69
69
|
});
|
|
70
70
|
describe('error prototype chain', () => {
|
|
71
71
|
it('should maintain correct prototype chain', () => {
|
|
72
|
-
const error = new
|
|
72
|
+
const error = new EwynApiError(400);
|
|
73
73
|
expect(error instanceof Error).toBe(true);
|
|
74
|
-
expect(error instanceof
|
|
75
|
-
expect(Object.getPrototypeOf(error)).toBe(
|
|
74
|
+
expect(error instanceof EwynApiError).toBe(true);
|
|
75
|
+
expect(Object.getPrototypeOf(error)).toBe(EwynApiError.prototype);
|
|
76
76
|
});
|
|
77
77
|
it('should work with try-catch instanceof checks', () => {
|
|
78
78
|
try {
|
|
79
|
-
throw new
|
|
79
|
+
throw new EwynApiError(400, 'TEST_ERROR');
|
|
80
80
|
}
|
|
81
81
|
catch (error) {
|
|
82
|
-
expect(error instanceof
|
|
82
|
+
expect(error instanceof EwynApiError).toBe(true);
|
|
83
83
|
expect(error instanceof Error).toBe(true);
|
|
84
|
-
if (error instanceof
|
|
84
|
+
if (error instanceof EwynApiError) {
|
|
85
85
|
expect(error.code).toBe('TEST_ERROR');
|
|
86
86
|
}
|
|
87
87
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ewyn.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/ewyn.test.ts"],"names":[],"mappings":""}
|