@thalorlabs/dadjoke 1.0.0 → 1.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.
- package/dist/DadJokeClient.d.ts +4 -6
- package/dist/DadJokeClient.js +10 -16
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/src/DadJokeClient.ts +13 -14
- package/src/index.ts +1 -1
- package/tests/DadJokeClient.test.ts +27 -52
package/dist/DadJokeClient.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { BaseHttpClient } from '@thalorlabs/api';
|
|
1
2
|
import { TLJoke } from '@thalorlabs/types';
|
|
2
3
|
export interface DadJokeClientConfig {
|
|
3
4
|
baseURL?: string;
|
|
@@ -6,15 +7,12 @@ export interface DadJokeClientConfig {
|
|
|
6
7
|
/**
|
|
7
8
|
* HTTP adapter for icanhazdadjoke.com.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
+
* Extends BaseHttpClient for retry logic, exponential backoff, and
|
|
11
|
+
* observability. Normalises the raw response to TLJoke.
|
|
10
12
|
* No DB, no cache, no logging — pure HTTP adapter.
|
|
11
|
-
*
|
|
12
|
-
* When @thalorlabs/api is available, this should extend BaseHttpClient
|
|
13
|
-
* instead of managing its own axios instance.
|
|
14
13
|
*/
|
|
15
|
-
export declare class DadJokeClient {
|
|
14
|
+
export declare class DadJokeClient extends BaseHttpClient {
|
|
16
15
|
readonly serviceName = "dadjoke";
|
|
17
|
-
private readonly axiosInstance;
|
|
18
16
|
constructor(config?: DadJokeClientConfig);
|
|
19
17
|
getJoke(): Promise<TLJoke>;
|
|
20
18
|
private normalise;
|
package/dist/DadJokeClient.js
CHANGED
|
@@ -1,35 +1,29 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.DadJokeClient = void 0;
|
|
7
|
-
const axios_1 = __importDefault(require("axios"));
|
|
8
4
|
const crypto_1 = require("crypto");
|
|
5
|
+
const api_1 = require("@thalorlabs/api");
|
|
9
6
|
const types_1 = require("@thalorlabs/types");
|
|
10
7
|
/**
|
|
11
8
|
* HTTP adapter for icanhazdadjoke.com.
|
|
12
9
|
*
|
|
13
|
-
*
|
|
10
|
+
* Extends BaseHttpClient for retry logic, exponential backoff, and
|
|
11
|
+
* observability. Normalises the raw response to TLJoke.
|
|
14
12
|
* No DB, no cache, no logging — pure HTTP adapter.
|
|
15
|
-
*
|
|
16
|
-
* When @thalorlabs/api is available, this should extend BaseHttpClient
|
|
17
|
-
* instead of managing its own axios instance.
|
|
18
13
|
*/
|
|
19
|
-
class DadJokeClient {
|
|
14
|
+
class DadJokeClient extends api_1.BaseHttpClient {
|
|
20
15
|
constructor(config = {}) {
|
|
21
|
-
|
|
22
|
-
this.axiosInstance = axios_1.default.create({
|
|
16
|
+
super({
|
|
23
17
|
baseURL: config.baseURL ?? 'https://icanhazdadjoke.com',
|
|
18
|
+
serviceName: 'dadjoke',
|
|
19
|
+
retries: 3,
|
|
24
20
|
timeout: config.timeout ?? 10000,
|
|
25
|
-
headers: {
|
|
26
|
-
Accept: 'application/json',
|
|
27
|
-
},
|
|
28
21
|
});
|
|
22
|
+
this.serviceName = 'dadjoke';
|
|
29
23
|
}
|
|
30
24
|
async getJoke() {
|
|
31
|
-
const {
|
|
32
|
-
return this.normalise(data);
|
|
25
|
+
const { response } = await this.handleRequest({ method: 'GET', url: '/' }, { method: 'GET', url: '/', requestId: (0, crypto_1.randomUUID)() });
|
|
26
|
+
return this.normalise(response.data);
|
|
33
27
|
}
|
|
34
28
|
normalise(raw) {
|
|
35
29
|
return types_1.TLJoke.parse({
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -4,6 +4,6 @@ exports.DAD_JOKE_CONFIG = exports.DadJokeClient = void 0;
|
|
|
4
4
|
var DadJokeClient_1 = require("./DadJokeClient");
|
|
5
5
|
Object.defineProperty(exports, "DadJokeClient", { enumerable: true, get: function () { return DadJokeClient_1.DadJokeClient; } });
|
|
6
6
|
exports.DAD_JOKE_CONFIG = {
|
|
7
|
-
cacheTtlMs:
|
|
7
|
+
cacheTtlMs: 0, // random endpoint — no caching
|
|
8
8
|
isBillable: false, // icanhazdadjoke is free
|
|
9
9
|
};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@thalorlabs/dadjoke",
|
|
3
3
|
"author": "ThalorLabs",
|
|
4
4
|
"private": false,
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.1.0",
|
|
6
6
|
"description": "Provider adapter for icanhazdadjoke.com — returns TLJoke",
|
|
7
7
|
"homepage": "https://github.com/ThalorLabs/dadjoke#readme",
|
|
8
8
|
"bugs": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"vitest": "^3.0.0"
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@thalorlabs/
|
|
44
|
-
"
|
|
43
|
+
"@thalorlabs/api": "^1.0.0",
|
|
44
|
+
"@thalorlabs/types": "^1.7.0"
|
|
45
45
|
}
|
|
46
46
|
}
|
package/src/DadJokeClient.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { createHash, randomUUID } from 'crypto';
|
|
2
|
+
import { BaseHttpClient } from '@thalorlabs/api';
|
|
3
3
|
import { TLJoke, EJokeType, EJokeCategory } from '@thalorlabs/types';
|
|
4
4
|
import { DadJokeRawResponse } from './DadJokeTypes';
|
|
5
5
|
|
|
@@ -11,29 +11,28 @@ export interface DadJokeClientConfig {
|
|
|
11
11
|
/**
|
|
12
12
|
* HTTP adapter for icanhazdadjoke.com.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
14
|
+
* Extends BaseHttpClient for retry logic, exponential backoff, and
|
|
15
|
+
* observability. Normalises the raw response to TLJoke.
|
|
15
16
|
* No DB, no cache, no logging — pure HTTP adapter.
|
|
16
|
-
*
|
|
17
|
-
* When @thalorlabs/api is available, this should extend BaseHttpClient
|
|
18
|
-
* instead of managing its own axios instance.
|
|
19
17
|
*/
|
|
20
|
-
export class DadJokeClient {
|
|
18
|
+
export class DadJokeClient extends BaseHttpClient {
|
|
21
19
|
public readonly serviceName = 'dadjoke';
|
|
22
|
-
private readonly axiosInstance: AxiosInstance;
|
|
23
20
|
|
|
24
21
|
constructor(config: DadJokeClientConfig = {}) {
|
|
25
|
-
|
|
22
|
+
super({
|
|
26
23
|
baseURL: config.baseURL ?? 'https://icanhazdadjoke.com',
|
|
24
|
+
serviceName: 'dadjoke',
|
|
25
|
+
retries: 3,
|
|
27
26
|
timeout: config.timeout ?? 10000,
|
|
28
|
-
headers: {
|
|
29
|
-
Accept: 'application/json',
|
|
30
|
-
},
|
|
31
27
|
});
|
|
32
28
|
}
|
|
33
29
|
|
|
34
30
|
async getJoke(): Promise<TLJoke> {
|
|
35
|
-
const {
|
|
36
|
-
|
|
31
|
+
const { response } = await this.handleRequest<DadJokeRawResponse>(
|
|
32
|
+
{ method: 'GET', url: '/' },
|
|
33
|
+
{ method: 'GET', url: '/', requestId: randomUUID() }
|
|
34
|
+
);
|
|
35
|
+
return this.normalise(response.data);
|
|
37
36
|
}
|
|
38
37
|
|
|
39
38
|
private normalise(raw: DadJokeRawResponse): TLJoke {
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { DadJokeClient, DadJokeClientConfig } from './DadJokeClient';
|
|
2
2
|
|
|
3
3
|
export const DAD_JOKE_CONFIG = {
|
|
4
|
-
cacheTtlMs:
|
|
4
|
+
cacheTtlMs: 0, // random endpoint — no caching
|
|
5
5
|
isBillable: false, // icanhazdadjoke is free
|
|
6
6
|
} as const;
|
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
2
|
import { DadJokeClient } from '../src/DadJokeClient';
|
|
3
3
|
import { EJokeType, EJokeCategory } from '@thalorlabs/types';
|
|
4
|
-
import
|
|
4
|
+
import { BaseHttpClient } from '@thalorlabs/api';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
default: {
|
|
12
|
-
create: vi.fn(() => mockAxiosInstance),
|
|
13
|
-
},
|
|
14
|
-
};
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
function getMockAxios() {
|
|
18
|
-
const instance = (axios.create as ReturnType<typeof vi.fn>).mock.results[0]
|
|
19
|
-
.value;
|
|
20
|
-
return instance.get as ReturnType<typeof vi.fn>;
|
|
21
|
-
}
|
|
6
|
+
// Mock handleRequest on the prototype — no axios import needed in tests
|
|
7
|
+
const mockHandleRequest = vi.fn();
|
|
8
|
+
vi.spyOn(BaseHttpClient.prototype as any, 'handleRequest').mockImplementation(
|
|
9
|
+
mockHandleRequest
|
|
10
|
+
);
|
|
22
11
|
|
|
23
12
|
describe('DadJokeClient', () => {
|
|
24
13
|
let client: DadJokeClient;
|
|
@@ -32,35 +21,19 @@ describe('DadJokeClient', () => {
|
|
|
32
21
|
it('sets serviceName to dadjoke', () => {
|
|
33
22
|
expect(client.serviceName).toBe('dadjoke');
|
|
34
23
|
});
|
|
35
|
-
|
|
36
|
-
it('creates axios instance with default config', () => {
|
|
37
|
-
expect(axios.create).toHaveBeenCalledWith({
|
|
38
|
-
baseURL: 'https://icanhazdadjoke.com',
|
|
39
|
-
timeout: 10000,
|
|
40
|
-
headers: { Accept: 'application/json' },
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('accepts custom baseURL and timeout', () => {
|
|
45
|
-
vi.clearAllMocks();
|
|
46
|
-
new DadJokeClient({ baseURL: 'http://localhost:3000', timeout: 5000 });
|
|
47
|
-
expect(axios.create).toHaveBeenCalledWith({
|
|
48
|
-
baseURL: 'http://localhost:3000',
|
|
49
|
-
timeout: 5000,
|
|
50
|
-
headers: { Accept: 'application/json' },
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
24
|
});
|
|
54
25
|
|
|
55
26
|
describe('getJoke', () => {
|
|
56
27
|
it('returns a valid TLJoke with SINGLE type and DAD category', async () => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
28
|
+
mockHandleRequest.mockResolvedValueOnce({
|
|
29
|
+
response: {
|
|
30
|
+
data: {
|
|
31
|
+
id: 'abc123',
|
|
32
|
+
joke: 'Why did the scarecrow win an award? He was outstanding in his field.',
|
|
33
|
+
status: 200,
|
|
34
|
+
},
|
|
63
35
|
},
|
|
36
|
+
durationMs: 50,
|
|
64
37
|
});
|
|
65
38
|
|
|
66
39
|
const joke = await client.getJoke();
|
|
@@ -76,10 +49,10 @@ describe('DadJokeClient', () => {
|
|
|
76
49
|
});
|
|
77
50
|
|
|
78
51
|
it('generates a deterministic id from joke text', async () => {
|
|
79
|
-
const mockGet = getMockAxios();
|
|
80
52
|
const jokeText = 'Test joke';
|
|
81
|
-
|
|
82
|
-
data: { id: 'raw-id', joke: jokeText, status: 200 },
|
|
53
|
+
mockHandleRequest.mockResolvedValue({
|
|
54
|
+
response: { data: { id: 'raw-id', joke: jokeText, status: 200 } },
|
|
55
|
+
durationMs: 10,
|
|
83
56
|
});
|
|
84
57
|
|
|
85
58
|
const joke1 = await client.getJoke();
|
|
@@ -88,20 +61,22 @@ describe('DadJokeClient', () => {
|
|
|
88
61
|
expect(joke1.id).toBe(joke2.id);
|
|
89
62
|
});
|
|
90
63
|
|
|
91
|
-
it('calls GET /
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
64
|
+
it('calls handleRequest with GET /', async () => {
|
|
65
|
+
mockHandleRequest.mockResolvedValueOnce({
|
|
66
|
+
response: { data: { id: 'abc', joke: 'A joke', status: 200 } },
|
|
67
|
+
durationMs: 10,
|
|
95
68
|
});
|
|
96
69
|
|
|
97
70
|
await client.getJoke();
|
|
98
71
|
|
|
99
|
-
expect(
|
|
72
|
+
expect(mockHandleRequest).toHaveBeenCalledWith(
|
|
73
|
+
expect.objectContaining({ method: 'GET', url: '/' }),
|
|
74
|
+
expect.objectContaining({ method: 'GET', url: '/' })
|
|
75
|
+
);
|
|
100
76
|
});
|
|
101
77
|
|
|
102
|
-
it('propagates
|
|
103
|
-
|
|
104
|
-
mockGet.mockRejectedValueOnce(new Error('Network error'));
|
|
78
|
+
it('propagates errors from handleRequest', async () => {
|
|
79
|
+
mockHandleRequest.mockRejectedValueOnce(new Error('Network error'));
|
|
105
80
|
|
|
106
81
|
await expect(client.getJoke()).rejects.toThrow('Network error');
|
|
107
82
|
});
|