@ord-api/ord-api-types 1.0.2
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 +459 -0
- package/package.json +24 -0
- package/types.ts +3950 -0
package/README.md
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
# @ord-api/ord-api-types
|
|
2
|
+
|
|
3
|
+
TypeScript types for the ORD API, automatically generated from the OpenAPI specification.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# pnpm (recommended for SvelteKit)
|
|
9
|
+
pnpm add @ord-api/ord-api-types
|
|
10
|
+
|
|
11
|
+
# npm
|
|
12
|
+
npm install @ord-api/ord-api-types
|
|
13
|
+
|
|
14
|
+
# yarn
|
|
15
|
+
yarn add @ord-api/ord-api-types
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Setup for SvelteKit
|
|
19
|
+
|
|
20
|
+
### 1. Create Axios Instance
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
// src/lib/api/client.ts
|
|
24
|
+
import axios from 'axios';
|
|
25
|
+
import { browser } from '$app/environment';
|
|
26
|
+
|
|
27
|
+
export const api = axios.create({
|
|
28
|
+
baseURL: browser ? '' : 'http://localhost:8080', // Use proxy in browser, direct in SSR
|
|
29
|
+
withCredentials: true, // Important for JWT cookies
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Optional: Add request/response interceptors
|
|
33
|
+
api.interceptors.response.use(
|
|
34
|
+
(response) => response,
|
|
35
|
+
(error) => {
|
|
36
|
+
if (error.response?.status === 401) {
|
|
37
|
+
// Handle unauthorized
|
|
38
|
+
goto('/login');
|
|
39
|
+
}
|
|
40
|
+
return Promise.reject(error);
|
|
41
|
+
}
|
|
42
|
+
);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 2. Configure SvelteKit Proxy (Optional)
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// vite.config.ts
|
|
49
|
+
import { sveltekit } from '@sveltejs/kit/vite';
|
|
50
|
+
import { defineConfig } from 'vite';
|
|
51
|
+
|
|
52
|
+
export default defineConfig({
|
|
53
|
+
plugins: [sveltekit()],
|
|
54
|
+
server: {
|
|
55
|
+
proxy: {
|
|
56
|
+
'/api': {
|
|
57
|
+
target: 'http://localhost:8080',
|
|
58
|
+
changeOrigin: true,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 3. Export Common Types
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// src/lib/types/api-types.ts
|
|
69
|
+
import type { components } from '@ord-api/ord-api-types';
|
|
70
|
+
|
|
71
|
+
// Export enums
|
|
72
|
+
export type LanguageName = components['schemas']['LanguageName'];
|
|
73
|
+
export type WordType = components['schemas']['WordType'];
|
|
74
|
+
export type WordExtraMark = components['schemas']['WordExtraMark'];
|
|
75
|
+
export type WordToggleableProperty = components['schemas']['WordToggleableProperty'];
|
|
76
|
+
|
|
77
|
+
// Export DTOs
|
|
78
|
+
export type UserDTO = components['schemas']['UserDTO'];
|
|
79
|
+
export type WordDTO = components['schemas']['WordDTO'];
|
|
80
|
+
export type QuicklyAddedWordDTO = components['schemas']['QuicklyAddedWordDTO'];
|
|
81
|
+
export type CreateQAWRequest = components['schemas']['CreateQAWRequest'];
|
|
82
|
+
export type PaginatedDataResponse<T> = components['schemas']['PaginatedDataResponse'] & {
|
|
83
|
+
data: T[];
|
|
84
|
+
};
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Usage with TanStack Query (Svelte)
|
|
88
|
+
|
|
89
|
+
### Setup Query Client
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
// src/routes/+layout.ts
|
|
93
|
+
import { browser } from '$app/environment';
|
|
94
|
+
import { QueryClient } from '@tanstack/svelte-query';
|
|
95
|
+
|
|
96
|
+
export const load = async () => {
|
|
97
|
+
const queryClient = new QueryClient({
|
|
98
|
+
defaultOptions: {
|
|
99
|
+
queries: {
|
|
100
|
+
enabled: browser,
|
|
101
|
+
staleTime: 60 * 1000, // 1 minute
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return { queryClient };
|
|
107
|
+
};
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
```svelte
|
|
111
|
+
<!-- src/routes/+layout.svelte -->
|
|
112
|
+
<script lang="ts">
|
|
113
|
+
import { QueryClientProvider } from '@tanstack/svelte-query';
|
|
114
|
+
|
|
115
|
+
let { data, children } = $props();
|
|
116
|
+
</script>
|
|
117
|
+
|
|
118
|
+
<QueryClientProvider client={data.queryClient}>
|
|
119
|
+
{@render children()}
|
|
120
|
+
</QueryClientProvider>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Create Query Functions
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
// src/lib/api/queries/words.ts
|
|
127
|
+
import { createQuery, createMutation } from '@tanstack/svelte-query';
|
|
128
|
+
import { api } from '$lib/api/client';
|
|
129
|
+
import type { QuicklyAddedWordDTO, CreateQAWRequest, PaginatedDataResponse } from '$lib/types/api-types';
|
|
130
|
+
|
|
131
|
+
export const createQuicklyAddedWordsQuery = (page = 0, perPage = 20) => {
|
|
132
|
+
return createQuery({
|
|
133
|
+
queryKey: ['quickly-added-words', page, perPage],
|
|
134
|
+
queryFn: async () => {
|
|
135
|
+
const response = await api.get<PaginatedDataResponse<QuicklyAddedWordDTO>>(
|
|
136
|
+
'/api/v1/quickly-added-words/',
|
|
137
|
+
{ params: { page, perPage } }
|
|
138
|
+
);
|
|
139
|
+
return response.data;
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const createCreateQAWMutation = () => {
|
|
145
|
+
return createMutation({
|
|
146
|
+
mutationFn: async (word: CreateQAWRequest) => {
|
|
147
|
+
const response = await api.post<QuicklyAddedWordDTO>(
|
|
148
|
+
'/api/v1/quickly-added-words/',
|
|
149
|
+
word
|
|
150
|
+
);
|
|
151
|
+
return response.data;
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
export const createDeleteQAWMutation = () => {
|
|
157
|
+
return createMutation({
|
|
158
|
+
mutationFn: async (id: string) => {
|
|
159
|
+
await api.delete(`/api/v1/quickly-added-words/${id}`);
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Use in Svelte 5 Components
|
|
166
|
+
|
|
167
|
+
```svelte
|
|
168
|
+
<!-- src/routes/words/+page.svelte -->
|
|
169
|
+
<script lang="ts">
|
|
170
|
+
import { createQuicklyAddedWordsQuery, createCreateQAWMutation } from '$lib/api/queries/words';
|
|
171
|
+
import type { LanguageName } from '$lib/types/api-types';
|
|
172
|
+
|
|
173
|
+
let page = $state(0);
|
|
174
|
+
let perPage = $state(20);
|
|
175
|
+
|
|
176
|
+
// Query
|
|
177
|
+
const wordsQuery = createQuicklyAddedWordsQuery(page, perPage);
|
|
178
|
+
const words = $derived($wordsQuery.data?.data ?? []);
|
|
179
|
+
const pagination = $derived($wordsQuery.data?.pagination);
|
|
180
|
+
|
|
181
|
+
// Mutation
|
|
182
|
+
const createWordMutation = createCreateQAWMutation();
|
|
183
|
+
|
|
184
|
+
async function handleCreateWord(word: string, language: LanguageName) {
|
|
185
|
+
await $createWordMutation.mutateAsync({
|
|
186
|
+
word,
|
|
187
|
+
language,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Invalidate and refetch
|
|
191
|
+
$wordsQuery.refetch();
|
|
192
|
+
}
|
|
193
|
+
</script>
|
|
194
|
+
|
|
195
|
+
{#if $wordsQuery.isPending}
|
|
196
|
+
<p>Loading...</p>
|
|
197
|
+
{:else if $wordsQuery.isError}
|
|
198
|
+
<p>Error: {$wordsQuery.error.message}</p>
|
|
199
|
+
{:else}
|
|
200
|
+
<div>
|
|
201
|
+
<h1>Words ({pagination?.totalResults ?? 0})</h1>
|
|
202
|
+
|
|
203
|
+
{#each words as word (word.id)}
|
|
204
|
+
<div>
|
|
205
|
+
<h3>{word.word}</h3>
|
|
206
|
+
<p>{word.language}</p>
|
|
207
|
+
</div>
|
|
208
|
+
{/each}
|
|
209
|
+
|
|
210
|
+
<!-- Pagination -->
|
|
211
|
+
{#if pagination}
|
|
212
|
+
<button
|
|
213
|
+
disabled={page === 0}
|
|
214
|
+
onclick={() => page--}
|
|
215
|
+
>
|
|
216
|
+
Previous
|
|
217
|
+
</button>
|
|
218
|
+
<span>Page {page + 1} of {pagination.totalPages}</span>
|
|
219
|
+
<button
|
|
220
|
+
disabled={page >= pagination.totalPages - 1}
|
|
221
|
+
onclick={() => page++}
|
|
222
|
+
>
|
|
223
|
+
Next
|
|
224
|
+
</button>
|
|
225
|
+
{/if}
|
|
226
|
+
</div>
|
|
227
|
+
{/if}
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Authentication Flow
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
// src/lib/api/queries/auth.ts
|
|
234
|
+
import { createMutation } from '@tanstack/svelte-query';
|
|
235
|
+
import { api } from '$lib/api/client';
|
|
236
|
+
import type { components, paths } from '@ord-api/ord-api-types';
|
|
237
|
+
|
|
238
|
+
type OtpRequestBody = paths['/api/v1/auth/otp-request']['post']['requestBody']['content']['application/json'];
|
|
239
|
+
type OtpVerifyBody = paths['/api/v1/auth/otp-verify']['post']['requestBody']['content']['application/json'];
|
|
240
|
+
type UserDTO = components['schemas']['UserDTO'];
|
|
241
|
+
|
|
242
|
+
export const createRequestOtpMutation = () => {
|
|
243
|
+
return createMutation({
|
|
244
|
+
mutationFn: async (body: OtpRequestBody) => {
|
|
245
|
+
await api.post('/api/v1/auth/otp-request', body);
|
|
246
|
+
},
|
|
247
|
+
});
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
export const createVerifyOtpMutation = () => {
|
|
251
|
+
return createMutation({
|
|
252
|
+
mutationFn: async (body: OtpVerifyBody) => {
|
|
253
|
+
const response = await api.post('/api/v1/auth/otp-verify', body);
|
|
254
|
+
return response.data;
|
|
255
|
+
},
|
|
256
|
+
});
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
export const createLogoutMutation = () => {
|
|
260
|
+
return createMutation({
|
|
261
|
+
mutationFn: async () => {
|
|
262
|
+
await api.delete('/api/v1/auth/logout');
|
|
263
|
+
},
|
|
264
|
+
});
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
export const createCurrentUserQuery = () => {
|
|
268
|
+
return createQuery({
|
|
269
|
+
queryKey: ['user', 'me'],
|
|
270
|
+
queryFn: async () => {
|
|
271
|
+
const response = await api.get<UserDTO>('/api/v1/users/me');
|
|
272
|
+
return response.data;
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
};
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
```svelte
|
|
279
|
+
<!-- src/routes/login/+page.svelte -->
|
|
280
|
+
<script lang="ts">
|
|
281
|
+
import { goto } from '$app/navigation';
|
|
282
|
+
import { createRequestOtpMutation, createVerifyOtpMutation } from '$lib/api/queries/auth';
|
|
283
|
+
|
|
284
|
+
let email = $state('');
|
|
285
|
+
let code = $state('');
|
|
286
|
+
let step = $state<'email' | 'code'>('email');
|
|
287
|
+
|
|
288
|
+
const requestOtpMutation = createRequestOtpMutation();
|
|
289
|
+
const verifyOtpMutation = createVerifyOtpMutation();
|
|
290
|
+
|
|
291
|
+
async function handleRequestOtp() {
|
|
292
|
+
await $requestOtpMutation.mutateAsync({ email });
|
|
293
|
+
step = 'code';
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async function handleVerifyOtp() {
|
|
297
|
+
await $verifyOtpMutation.mutateAsync({ email, code });
|
|
298
|
+
goto('/dashboard');
|
|
299
|
+
}
|
|
300
|
+
</script>
|
|
301
|
+
|
|
302
|
+
{#if step === 'email'}
|
|
303
|
+
<form onsubmit={handleRequestOtp}>
|
|
304
|
+
<input
|
|
305
|
+
type="email"
|
|
306
|
+
bind:value={email}
|
|
307
|
+
placeholder="Enter your email"
|
|
308
|
+
required
|
|
309
|
+
/>
|
|
310
|
+
<button
|
|
311
|
+
type="submit"
|
|
312
|
+
disabled={$requestOtpMutation.isPending}
|
|
313
|
+
>
|
|
314
|
+
{$requestOtpMutation.isPending ? 'Sending...' : 'Send OTP'}
|
|
315
|
+
</button>
|
|
316
|
+
</form>
|
|
317
|
+
{:else}
|
|
318
|
+
<form onsubmit={handleVerifyOtp}>
|
|
319
|
+
<input
|
|
320
|
+
type="text"
|
|
321
|
+
bind:value={code}
|
|
322
|
+
placeholder="Enter 6-digit code"
|
|
323
|
+
required
|
|
324
|
+
/>
|
|
325
|
+
<button
|
|
326
|
+
type="submit"
|
|
327
|
+
disabled={$verifyOtpMutation.isPending}
|
|
328
|
+
>
|
|
329
|
+
{$verifyOtpMutation.isPending ? 'Verifying...' : 'Verify'}
|
|
330
|
+
</button>
|
|
331
|
+
</form>
|
|
332
|
+
{/if}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Server Load Functions with Types
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
// src/routes/words/[id]/+page.ts
|
|
339
|
+
import type { PageLoad } from './$types';
|
|
340
|
+
import type { components } from '@ord-api/ord-api-types';
|
|
341
|
+
import { api } from '$lib/api/client';
|
|
342
|
+
|
|
343
|
+
type WordDTO = components['schemas']['WordDTO'];
|
|
344
|
+
|
|
345
|
+
export const load: PageLoad = async ({ params }) => {
|
|
346
|
+
const response = await api.get<WordDTO>(`/api/v1/words/${params.id}`);
|
|
347
|
+
|
|
348
|
+
return {
|
|
349
|
+
word: response.data,
|
|
350
|
+
};
|
|
351
|
+
};
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
```svelte
|
|
355
|
+
<!-- src/routes/words/[id]/+page.svelte -->
|
|
356
|
+
<script lang="ts">
|
|
357
|
+
let { data } = $props();
|
|
358
|
+
</script>
|
|
359
|
+
|
|
360
|
+
<h1>{data.word.word}</h1>
|
|
361
|
+
<p>Language: {data.word.language}</p>
|
|
362
|
+
<p>Type: {data.word.type ?? 'Not specified'}</p>
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## Advanced Patterns
|
|
366
|
+
|
|
367
|
+
### Type-Safe API Wrapper
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
// src/lib/api/wrapper.ts
|
|
371
|
+
import type { paths } from '@ord-api/ord-api-types';
|
|
372
|
+
import { api } from './client';
|
|
373
|
+
|
|
374
|
+
type ApiPath = keyof paths;
|
|
375
|
+
type ApiMethod = 'get' | 'post' | 'patch' | 'delete';
|
|
376
|
+
|
|
377
|
+
type RequestBody<Path extends ApiPath, Method extends ApiMethod> =
|
|
378
|
+
paths[Path][Method] extends { requestBody: { content: { 'application/json': infer Body } } }
|
|
379
|
+
? Body
|
|
380
|
+
: never;
|
|
381
|
+
|
|
382
|
+
type ResponseData<Path extends ApiPath, Method extends ApiMethod> =
|
|
383
|
+
paths[Path][Method] extends { responses: { 200: { content: { 'application/json': infer Data } } } }
|
|
384
|
+
? Data
|
|
385
|
+
: never;
|
|
386
|
+
|
|
387
|
+
export async function apiCall<Path extends ApiPath, Method extends ApiMethod>(
|
|
388
|
+
method: Method,
|
|
389
|
+
path: Path,
|
|
390
|
+
body?: RequestBody<Path, Method>
|
|
391
|
+
): Promise<ResponseData<Path, Method>> {
|
|
392
|
+
const response = await api.request({
|
|
393
|
+
method,
|
|
394
|
+
url: path as string,
|
|
395
|
+
data: body,
|
|
396
|
+
});
|
|
397
|
+
return response.data;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Usage
|
|
401
|
+
const user = await apiCall('get', '/api/v1/users/me');
|
|
402
|
+
const word = await apiCall('post', '/api/v1/words/', { word: 'test', language: 'ENGLISH' });
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
### Form Actions with Types
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
// src/routes/words/create/+page.server.ts
|
|
409
|
+
import type { Actions } from './$types';
|
|
410
|
+
import type { CreateQAWRequest } from '$lib/types/api-types';
|
|
411
|
+
import { api } from '$lib/api/client';
|
|
412
|
+
import { fail } from '@sveltejs/kit';
|
|
413
|
+
|
|
414
|
+
export const actions = {
|
|
415
|
+
default: async ({ request }) => {
|
|
416
|
+
const data = await request.formData();
|
|
417
|
+
|
|
418
|
+
const wordData: CreateQAWRequest = {
|
|
419
|
+
word: data.get('word') as string,
|
|
420
|
+
language: data.get('language') as any,
|
|
421
|
+
definition: data.get('definition') as string || undefined,
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
await api.post('/api/v1/quickly-added-words/', wordData);
|
|
426
|
+
return { success: true };
|
|
427
|
+
} catch (error) {
|
|
428
|
+
return fail(400, { error: 'Failed to create word' });
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
} satisfies Actions;
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
## Best Practices
|
|
435
|
+
|
|
436
|
+
1. **Use `$derived`** for computed values from queries
|
|
437
|
+
2. **Use `$state`** for local component state
|
|
438
|
+
3. **Export query factories** instead of hooks (Svelte pattern)
|
|
439
|
+
4. **Keep types in a central location** (`src/lib/types/api-types.ts`)
|
|
440
|
+
5. **Use SvelteKit's proxy** for API calls in development
|
|
441
|
+
6. **Handle SSR vs CSR** with `browser` check from `$app/environment`
|
|
442
|
+
|
|
443
|
+
## Updating
|
|
444
|
+
|
|
445
|
+
This package is automatically published when the backend API changes. To update:
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
pnpm update @ord-api/ord-api-types
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
## Resources
|
|
452
|
+
|
|
453
|
+
- [TanStack Query Svelte Docs](https://tanstack.com/query/latest/docs/svelte/overview)
|
|
454
|
+
- [SvelteKit Docs](https://kit.svelte.dev/)
|
|
455
|
+
- [Svelte 5 Runes](https://svelte-5-preview.vercel.app/docs/runes)
|
|
456
|
+
|
|
457
|
+
## License
|
|
458
|
+
|
|
459
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ord-api/ord-api-types",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "TypeScript types for ORD API - Auto-generated from OpenAPI specification",
|
|
5
|
+
"main": "types.ts",
|
|
6
|
+
"types": "types.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"types.ts",
|
|
9
|
+
"README.md"
|
|
10
|
+
],
|
|
11
|
+
"author": "ORD API Team",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "https://github.com/Kacper-Ksiazek/ord-api.git"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/Kacper-Ksiazek/ord-api#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/Kacper-Ksiazek/ord-api/issues"
|
|
20
|
+
},
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public"
|
|
23
|
+
}
|
|
24
|
+
}
|