@ragbits/api-client 0.0.3 → 1.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 CHANGED
@@ -25,6 +25,12 @@ import { RagbitsClient } from '@ragbits/api-client'
25
25
  // Initialize the client
26
26
  const client = new RagbitsClient({
27
27
  baseUrl: 'http://127.0.0.1:8000', // Optional, defaults to http://127.0.0.1:8000
28
+ auth: {
29
+ getToken: () => 'token',
30
+ onUnauthorized: () => {
31
+ console.warn('⚠️ Unauthorized')
32
+ },
33
+ }, // Optional, used to provide auth details
28
34
  })
29
35
 
30
36
  // Get API configuration
@@ -80,6 +86,7 @@ new RagbitsClient(config?: ClientConfig)
80
86
  **Parameters:**
81
87
 
82
88
  - `config.baseUrl` (optional): Base URL for the API. Defaults to 'http://127.0.0.1:8000'
89
+ - `config.auth` (optional): An object containing authentication details. Provide `getToken` to automatically attach `Authorization: Bearer <token>` to every request. `onUnauthorized` is called if the library encounters a 401 status code.
83
90
 
84
91
  **Throws:** `Error` if the base URL is invalid
85
92
 
@@ -91,13 +98,14 @@ Get the base URL used by this client.
91
98
 
92
99
  **Returns:** The configured base URL
93
100
 
94
- ##### `makeRequest<T>(endpoint, options?): Promise<T>`
101
+ ##### `async makeRequest<Endpoints, URL>(endpoint, options?): Promise<Result>`
95
102
 
96
- Make a type-safe API request to predefined endpoints.
103
+ Make a type-safe API request to any endpoint. `endpoint` must be one of the key of `Endpoints` type
104
+ that define schema of the available endpoints. All default Ragbits routes are supported out of the box.
97
105
 
98
106
  **Parameters:**
99
107
 
100
- - `endpoint`: Predefined API endpoint path (e.g., '/api/config', '/api/feedback')
108
+ - `endpoint`: API endpoint path (e.g., '/api/config', '/api/feedback')
101
109
  - `options` (optional): Request options
102
110
  - `method`: HTTP method (defaults to endpoint's predefined method)
103
111
  - `body`: Request body (typed based on endpoint)
@@ -121,15 +129,39 @@ const feedback = await client.makeRequest('/api/feedback', {
121
129
  payload: { rating: 5 },
122
130
  },
123
131
  })
132
+
133
+ // Custom endpoints
134
+ type MyEndpoints = {
135
+ '/api/my-endpoint': {
136
+ method: 'GET'
137
+ request: never
138
+ response: string
139
+ }
140
+ }
141
+
142
+ // or
143
+ type ExtendedEndpoints = BaseApiEndpoints & {
144
+ '/api/my-endpoint': {
145
+ method: 'GET'
146
+ request: never
147
+ response: string
148
+ }
149
+ }
150
+
151
+ // In case of usage of custom Endpoints, we have to specify the URL as generic parameter
152
+ const myResponse = await client.makeRequest<MyEndpoints, '/api/my-endpoint'>(
153
+ '/api/my-endpoint'
154
+ )
124
155
  ```
125
156
 
126
- ##### `makeStreamRequest<T>(endpoint, data, callbacks, signal?): () => void`
157
+ ##### `makeStreamRequest<Endpoints, URL>(endpoint, data, callbacks, signal?): () => void`
127
158
 
128
- Make a type-safe streaming request to predefined streaming endpoints.
159
+ Make a type-safe streaming request to any streaming endpoints. `endpoint` must be one of the key of `Endpoints` type
160
+ that define schema of the available endpoints. All default Ragbits routes are supported out of the box.
129
161
 
130
162
  **Parameters:**
131
163
 
132
- - `endpoint`: Predefined streaming endpoint path (e.g., '/api/chat')
164
+ - `endpoint`: Streaming endpoint path (e.g., '/api/chat')
133
165
  - `data`: Request data (typed based on endpoint)
134
166
  - `callbacks`: Stream callbacks
135
167
  - `onMessage`: Called when a message chunk is received
@@ -147,8 +179,8 @@ const cleanup = client.makeStreamRequest(
147
179
  {
148
180
  message: 'Tell me about AI',
149
181
  history: [
150
- { role: 'user', content: 'Hello', id: 'msg-1' },
151
- { role: 'assistant', content: 'Hi there!', id: 'msg-2' },
182
+ { role: 'user', content: 'Hello' },
183
+ { role: 'assistant', content: 'Hi there!' },
152
184
  ],
153
185
  context: { user_id: 'user-123' },
154
186
  },
@@ -177,105 +209,23 @@ const cleanup = client.makeStreamRequest(
177
209
 
178
210
  // Cancel stream
179
211
  cleanup()
180
- ```
181
-
182
- ## Types
183
-
184
- ### Core Types
185
-
186
- #### `ClientConfig`
187
212
 
188
- ```typescript
189
- interface ClientConfig {
190
- baseUrl?: string
191
- }
192
- ```
193
-
194
- #### `Message`
195
-
196
- ```typescript
197
- interface Message {
198
- role: MessageRole
199
- content: string
200
- id?: string
201
- }
202
-
203
- enum MessageRole {
204
- USER = 'user',
205
- ASSISTANT = 'assistant',
206
- SYSTEM = 'system',
207
- }
208
- ```
209
-
210
- #### `ChatRequest`
211
-
212
- ```typescript
213
- interface ChatRequest {
214
- message: string
215
- history: Message[]
216
- context?: Record<string, unknown>
217
- }
218
- ```
219
-
220
- #### `TypedChatResponse`
221
-
222
- Union type for all possible chat response types:
223
-
224
- ```typescript
225
- type TypedChatResponse =
226
- | { type: 'text'; content: string }
227
- | { type: 'reference'; content: Reference }
228
- | { type: 'message_id'; content: string }
229
- | { type: 'conversation_id'; content: string }
230
- | { type: 'state_update'; content: ServerState }
231
- ```
232
-
233
- #### `FeedbackRequest`
234
-
235
- ```typescript
236
- interface FeedbackRequest {
237
- message_id: string
238
- feedback: FeedbackType
239
- payload: Record<string, unknown> | null
240
- }
241
-
242
- enum FeedbackType {
243
- LIKE = 'like',
244
- DISLIKE = 'dislike',
245
- }
246
- ```
247
-
248
- #### `ConfigResponse`
249
-
250
- ```typescript
251
- interface ConfigResponse {
252
- feedback: {
253
- like: { enabled: boolean; form: RJSFSchema | null }
254
- dislike: { enabled: boolean; form: RJSFSchema | null }
213
+ // Custom endpoints
214
+ type MyEndpoints = {
215
+ '/api/my-endpoint': {
216
+ method: 'GET'
217
+ request: never
218
+ response: string
255
219
  }
256
- customization: UICustomization | null
257
220
  }
258
- ```
259
-
260
- #### `StreamCallbacks<T, E>`
261
221
 
262
- ```typescript
263
- interface StreamCallbacks<T, E = Error> {
264
- onMessage: (data: T) => void | Promise<void>
265
- onError: (error: E) => void | Promise<void>
266
- onClose?: () => void | Promise<void>
267
- }
222
+ // In case of usage of custom Endpoints, we have to specify the URL as generic parameter
223
+ const cleanup = client.makeStreamRequest<MyEndpoints, '/api/my-endpoint'>(
224
+ '/api/my-endpoint',
225
+ ...
226
+ )
268
227
  ```
269
228
 
270
- ### Advanced Types
271
-
272
- The package provides extensive TypeScript support with predefined endpoint types:
273
-
274
- - `ApiEndpointPath` - Union of all available API endpoints
275
- - `StreamingEndpointPath` - Union of all available streaming endpoints
276
- - `ApiEndpointResponse<T>` - Response type for a specific endpoint
277
- - `StreamingEndpointStream<T>` - Stream response type for a specific endpoint
278
-
279
229
  ## Error Handling
280
230
 
281
231
  The client provides comprehensive error handling:
@@ -0,0 +1,98 @@
1
+ import { http, HttpResponse } from 'msw'
2
+ import { defaultConfigResponse } from '../utils'
3
+
4
+ export const handlers = [
5
+ // Config endpoint with conditional error handling
6
+ http.get('http://127.0.0.1:8000/api/config', ({ request }) => {
7
+ const url = new URL(request.url)
8
+ if (url.searchParams.get('error') === 'true') {
9
+ return new HttpResponse(null, { status: 500 })
10
+ }
11
+
12
+ return HttpResponse.json(defaultConfigResponse)
13
+ }),
14
+
15
+ // Feedback endpoint
16
+ http.post('http://127.0.0.1:8000/api/feedback', async ({ request }) => {
17
+ const _body = await request.json()
18
+ return HttpResponse.json({
19
+ status: 'success',
20
+ })
21
+ }),
22
+
23
+ // Chat streaming endpoint
24
+ http.post('http://127.0.0.1:8000/api/chat', ({ request }) => {
25
+ const url = new URL(request.url)
26
+
27
+ // Handle different test scenarios
28
+ if (url.searchParams.get('error') === 'true') {
29
+ return new HttpResponse(null, { status: 500 })
30
+ }
31
+
32
+ if (url.searchParams.get('malformed') === 'true') {
33
+ const encoder = new TextEncoder()
34
+ const stream = new ReadableStream({
35
+ start(controller) {
36
+ controller.enqueue(encoder.encode('data: invalid-json\n\n'))
37
+ controller.close()
38
+ },
39
+ })
40
+ return new HttpResponse(stream, {
41
+ headers: { 'Content-Type': 'text/event-stream' },
42
+ })
43
+ }
44
+
45
+ if (url.searchParams.get('empty') === 'true') {
46
+ return new HttpResponse(null, {
47
+ headers: { 'Content-Type': 'text/event-stream' },
48
+ })
49
+ }
50
+
51
+ if (url.searchParams.get('slow') === 'true') {
52
+ return new Promise((resolve) => {
53
+ setTimeout(() => {
54
+ resolve(
55
+ HttpResponse.json({
56
+ type: 'text',
57
+ content: 'Slow response',
58
+ })
59
+ )
60
+ }, 1000)
61
+ })
62
+ }
63
+
64
+ const encoder = new TextEncoder()
65
+
66
+ const stream = new ReadableStream({
67
+ start(controller) {
68
+ const messages = [
69
+ { type: 'text', content: 'Hello there!' },
70
+ { type: 'text', content: 'How can I help you?' },
71
+ { type: 'message_id', content: 'msg-123' },
72
+ { type: 'conversation_id', content: 'conv-456' },
73
+ ]
74
+
75
+ messages.forEach((message, index) => {
76
+ setTimeout(() => {
77
+ controller.enqueue(
78
+ encoder.encode(
79
+ `data: ${JSON.stringify(message)}\n\n`
80
+ )
81
+ )
82
+ if (index === messages.length - 1) {
83
+ controller.close()
84
+ }
85
+ }, index * 10)
86
+ })
87
+ },
88
+ })
89
+
90
+ return new HttpResponse(stream, {
91
+ headers: {
92
+ 'Content-Type': 'text/event-stream',
93
+ 'Cache-Control': 'no-cache',
94
+ Connection: 'keep-alive',
95
+ },
96
+ })
97
+ }),
98
+ ]
@@ -0,0 +1,23 @@
1
+ import '@testing-library/jest-dom'
2
+ import { beforeAll, afterEach, afterAll } from 'vitest'
3
+ import { setupServer } from 'msw/node'
4
+ import { handlers } from './mocks/handlers'
5
+
6
+ // Setup MSW server
7
+ export const server = setupServer(...handlers)
8
+
9
+ beforeAll(() => {
10
+ // Start the interception on the client side before all tests run.
11
+ server.listen({ onUnhandledRequest: 'error' })
12
+ })
13
+
14
+ afterEach(() => {
15
+ // Reset any request handlers that we may add during the tests,
16
+ // so they don't affect other tests.
17
+ server.resetHandlers()
18
+ })
19
+
20
+ afterAll(() => {
21
+ // Clean up after the tests are finished.
22
+ server.close()
23
+ })
@@ -0,0 +1,45 @@
1
+ import type { ConfigResponse } from '../src'
2
+
3
+ // Shared config response for tests
4
+ export const defaultConfigResponse: ConfigResponse = {
5
+ feedback: {
6
+ like: {
7
+ enabled: true,
8
+ form: {
9
+ title: 'Like Form',
10
+ type: 'object',
11
+ required: ['like_reason'],
12
+ properties: {
13
+ like_reason: {
14
+ title: 'Like Reason',
15
+ description: 'Why do you like this?',
16
+ type: 'string',
17
+ minLength: 1,
18
+ },
19
+ },
20
+ },
21
+ },
22
+ dislike: {
23
+ enabled: true,
24
+ form: null,
25
+ },
26
+ },
27
+ customization: null,
28
+ user_settings: {
29
+ form: {
30
+ title: 'Chat Form',
31
+ type: 'object',
32
+ required: ['language'],
33
+ properties: {
34
+ language: {
35
+ title: 'Language',
36
+ description: 'Please select the language',
37
+ type: 'string',
38
+ enum: ['English', 'Polish'],
39
+ },
40
+ },
41
+ },
42
+ },
43
+ debug_mode: false,
44
+ conversation_history: false,
45
+ }