@trokky/client 2.0.3
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 +833 -0
- package/dist/cache/manager.d.ts +47 -0
- package/dist/cache/manager.d.ts.map +1 -0
- package/dist/cache/manager.js +100 -0
- package/dist/cache/manager.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +159 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/client.d.ts +221 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +310 -0
- package/dist/client.js.map +1 -0
- package/dist/document/client.d.ts +57 -0
- package/dist/document/client.d.ts.map +1 -0
- package/dist/document/client.js +176 -0
- package/dist/document/client.js.map +1 -0
- package/dist/generator/document-generator.d.ts +50 -0
- package/dist/generator/document-generator.d.ts.map +1 -0
- package/dist/generator/document-generator.js +329 -0
- package/dist/generator/document-generator.js.map +1 -0
- package/dist/generator/index.d.ts +121 -0
- package/dist/generator/index.d.ts.map +1 -0
- package/dist/generator/index.js +559 -0
- package/dist/generator/index.js.map +1 -0
- package/dist/http/client.d.ts +117 -0
- package/dist/http/client.d.ts.map +1 -0
- package/dist/http/client.js +411 -0
- package/dist/http/client.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/media/helper.d.ts +89 -0
- package/dist/media/helper.d.ts.map +1 -0
- package/dist/media/helper.js +197 -0
- package/dist/media/helper.js.map +1 -0
- package/dist/media/url-builder.d.ts +142 -0
- package/dist/media/url-builder.d.ts.map +1 -0
- package/dist/media/url-builder.js +280 -0
- package/dist/media/url-builder.js.map +1 -0
- package/dist/query/builder.d.ts +270 -0
- package/dist/query/builder.d.ts.map +1 -0
- package/dist/query/builder.js +731 -0
- package/dist/query/builder.js.map +1 -0
- package/dist/query/index.d.ts +6 -0
- package/dist/query/index.d.ts.map +1 -0
- package/dist/query/index.js +5 -0
- package/dist/query/index.js.map +1 -0
- package/dist/react/hooks.d.ts +42 -0
- package/dist/react/hooks.d.ts.map +1 -0
- package/dist/react/hooks.js +59 -0
- package/dist/react/hooks.js.map +1 -0
- package/dist/react/index.d.ts +6 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +6 -0
- package/dist/react/index.js.map +1 -0
- package/dist/server/index.d.ts +9 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +8 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/media-proxy.d.ts +102 -0
- package/dist/server/media-proxy.d.ts.map +1 -0
- package/dist/server/media-proxy.js +160 -0
- package/dist/server/media-proxy.js.map +1 -0
- package/dist/shortcodes/index.d.ts +11 -0
- package/dist/shortcodes/index.d.ts.map +1 -0
- package/dist/shortcodes/index.js +10 -0
- package/dist/shortcodes/index.js.map +1 -0
- package/dist/shortcodes/parser.d.ts +32 -0
- package/dist/shortcodes/parser.d.ts.map +1 -0
- package/dist/shortcodes/parser.js +88 -0
- package/dist/shortcodes/parser.js.map +1 -0
- package/dist/shortcodes/resolver.d.ts +57 -0
- package/dist/shortcodes/resolver.d.ts.map +1 -0
- package/dist/shortcodes/resolver.js +97 -0
- package/dist/shortcodes/resolver.js.map +1 -0
- package/dist/shortcodes/types.d.ts +17 -0
- package/dist/shortcodes/types.d.ts.map +1 -0
- package/dist/shortcodes/types.js +6 -0
- package/dist/shortcodes/types.js.map +1 -0
- package/dist/types/index.d.ts +134 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +101 -0
package/README.md
ADDED
|
@@ -0,0 +1,833 @@
|
|
|
1
|
+
# @trokky/client
|
|
2
|
+
|
|
3
|
+
**TypeScript-native client SDK for Trokky CMS with automatic type generation**
|
|
4
|
+
|
|
5
|
+
The official client library for interacting with Trokky CMS APIs. Features comprehensive TypeScript support, automatic type generation from schemas, smart caching, and framework-agnostic design.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🔥 **TypeScript Native** - Full type safety with automatic type generation from schemas
|
|
10
|
+
- 🚀 **Framework Agnostic** - Works with React, Vue, Svelte, Node.js, and serverless
|
|
11
|
+
- 🔐 **Authentication Built-in** - JWT token management with automatic refresh
|
|
12
|
+
- ⚡ **Smart Caching** - TTL-based caching with intelligent invalidation
|
|
13
|
+
- 🛡️ **Error Handling** - Comprehensive error handling with retry logic
|
|
14
|
+
- 📁 **Media Management** - File upload support with progress tracking
|
|
15
|
+
- 🔍 **Advanced Querying** - Filtering, sorting, pagination, and search
|
|
16
|
+
- 🧪 **Well Tested** - 88 comprehensive tests covering all functionality
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @trokky/client
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
### Basic Setup
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { TrokkyClient } from '@trokky/client'
|
|
30
|
+
|
|
31
|
+
const client = new TrokkyClient({
|
|
32
|
+
baseUrl: 'https://your-trokky-api.com',
|
|
33
|
+
apiVersion: 'v1'
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
// Authenticate
|
|
37
|
+
await client.authenticate({
|
|
38
|
+
username: 'user@example.com',
|
|
39
|
+
password: 'your-password'
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// Use the client
|
|
43
|
+
const posts = await client.queryDocuments('post', {
|
|
44
|
+
filter: { published: true },
|
|
45
|
+
sort: { createdAt: -1 },
|
|
46
|
+
limit: 10
|
|
47
|
+
})
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### TypeScript Type Generation
|
|
51
|
+
|
|
52
|
+
Generate TypeScript types from your Trokky schemas using the CLI or programmatically.
|
|
53
|
+
|
|
54
|
+
#### CLI Usage
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# From local compiled schemas (recommended for development)
|
|
58
|
+
npx trokky-client generate-types --schema-path ../cms/dist/schemas -o ./src/types/cms
|
|
59
|
+
|
|
60
|
+
# From remote API (requires running CMS server)
|
|
61
|
+
npx trokky-client generate-types \
|
|
62
|
+
--schema-url http://localhost:3000/api/collections \
|
|
63
|
+
--auth-token YOUR_API_TOKEN \
|
|
64
|
+
-o ./src/types/cms
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**CLI Options:**
|
|
68
|
+
| Option | Description |
|
|
69
|
+
|--------|-------------|
|
|
70
|
+
| `-p, --schema-path <path>` | Local path to compiled schemas directory |
|
|
71
|
+
| `-u, --schema-url <url>` | API URL to fetch schemas from |
|
|
72
|
+
| `-o, --output-dir <dir>` | Output directory (default: `./src/types/trokky`) |
|
|
73
|
+
| `-t, --auth-token <token>` | API token for authentication |
|
|
74
|
+
| `-n, --namespace <name>` | TypeScript namespace (default: `Trokky`) |
|
|
75
|
+
| `-e, --extension <ext>` | File extension: `ts` or `d.ts` (default: `ts`) |
|
|
76
|
+
| `--no-validation` | Skip validation schema generation |
|
|
77
|
+
|
|
78
|
+
> **Note:** The `--schema-path` option requires compiled JS files. Run `npm run build` in your CMS project first.
|
|
79
|
+
|
|
80
|
+
#### Programmatic Usage
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { generateTypes, generateTypesFromPath } from '@trokky/client/generator'
|
|
84
|
+
|
|
85
|
+
// From local schemas
|
|
86
|
+
await generateTypesFromPath({
|
|
87
|
+
schemaPath: '../cms/dist/schemas',
|
|
88
|
+
outputDir: './src/types/cms',
|
|
89
|
+
namespace: 'Trokky',
|
|
90
|
+
includeValidation: true
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// From remote API
|
|
94
|
+
await generateTypes({
|
|
95
|
+
schemaUrl: 'https://your-api.com/api/collections',
|
|
96
|
+
outputDir: './src/types/trokky',
|
|
97
|
+
authToken: 'your-token',
|
|
98
|
+
includeValidation: true
|
|
99
|
+
})
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
This generates TypeScript interfaces like:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// Generated types
|
|
106
|
+
export interface PostDocument extends BaseDocument {
|
|
107
|
+
_type: 'post'
|
|
108
|
+
title: string
|
|
109
|
+
content: string
|
|
110
|
+
slug: string
|
|
111
|
+
published?: boolean
|
|
112
|
+
author?: string | UserDocument
|
|
113
|
+
tags?: string[]
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Framework Examples
|
|
118
|
+
|
|
119
|
+
#### React Hook
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { useState, useEffect } from 'react'
|
|
123
|
+
import { TrokkyClient } from '@trokky/client'
|
|
124
|
+
import type { PostDocument } from './types/trokky'
|
|
125
|
+
|
|
126
|
+
const usePosts = (client: TrokkyClient) => {
|
|
127
|
+
const [posts, setPosts] = useState<PostDocument[]>([])
|
|
128
|
+
const [loading, setLoading] = useState(true)
|
|
129
|
+
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
const fetchPosts = async () => {
|
|
132
|
+
try {
|
|
133
|
+
const result = await client.queryDocuments<PostDocument>('post', {
|
|
134
|
+
filter: { published: true },
|
|
135
|
+
sort: { createdAt: -1 }
|
|
136
|
+
})
|
|
137
|
+
setPosts(result.data.map(doc => doc.data))
|
|
138
|
+
} catch (error) {
|
|
139
|
+
console.error('Failed to fetch posts:', error)
|
|
140
|
+
} finally {
|
|
141
|
+
setLoading(false)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
fetchPosts()
|
|
146
|
+
}, [client])
|
|
147
|
+
|
|
148
|
+
return { posts, loading }
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
#### Vue Composable
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { ref, onMounted } from 'vue'
|
|
156
|
+
import { TrokkyClient } from '@trokky/client'
|
|
157
|
+
import type { PostDocument } from './types/trokky'
|
|
158
|
+
|
|
159
|
+
export const usePosts = (client: TrokkyClient) => {
|
|
160
|
+
const posts = ref<PostDocument[]>([])
|
|
161
|
+
const loading = ref(true)
|
|
162
|
+
|
|
163
|
+
const fetchPosts = async () => {
|
|
164
|
+
try {
|
|
165
|
+
const result = await client.queryDocuments<PostDocument>('post', {
|
|
166
|
+
filter: { published: true },
|
|
167
|
+
sort: { createdAt: -1 }
|
|
168
|
+
})
|
|
169
|
+
posts.value = result.data.map(doc => doc.data)
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error('Failed to fetch posts:', error)
|
|
172
|
+
} finally {
|
|
173
|
+
loading.value = false
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
onMounted(fetchPosts)
|
|
178
|
+
|
|
179
|
+
return { posts, loading, refetch: fetchPosts }
|
|
180
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### Svelte Store
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
import { writable } from 'svelte/store'
|
|
187
|
+
import { TrokkyClient } from '@trokky/client'
|
|
188
|
+
import type { PostDocument } from './types/trokky'
|
|
189
|
+
|
|
190
|
+
const createPostStore = (client: TrokkyClient) => {
|
|
191
|
+
const { subscribe, set, update } = writable<PostDocument[]>([])
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
subscribe,
|
|
195
|
+
async load() {
|
|
196
|
+
const result = await client.queryDocuments<PostDocument>('post', {
|
|
197
|
+
filter: { published: true },
|
|
198
|
+
sort: { createdAt: -1 }
|
|
199
|
+
})
|
|
200
|
+
set(result.data.map(doc => doc.data))
|
|
201
|
+
},
|
|
202
|
+
async create(post: Omit<PostDocument, '_id' | '_type' | '_createdAt' | '_updatedAt' | '_version'>) {
|
|
203
|
+
const newPost = await client.createDocument('post', post)
|
|
204
|
+
update(posts => [newPost.data, ...posts])
|
|
205
|
+
return newPost
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export const posts = createPostStore(client)
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## API Reference
|
|
214
|
+
|
|
215
|
+
### TrokkyClient
|
|
216
|
+
|
|
217
|
+
The main client class for interacting with Trokky APIs.
|
|
218
|
+
|
|
219
|
+
#### Constructor
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
new TrokkyClient(config: ClientConfig)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
interface ClientConfig {
|
|
227
|
+
baseUrl: string
|
|
228
|
+
apiVersion?: string
|
|
229
|
+
token?: string
|
|
230
|
+
refreshToken?: string
|
|
231
|
+
timeout?: number
|
|
232
|
+
retries?: number
|
|
233
|
+
enableCache?: boolean
|
|
234
|
+
cacheMaxAge?: number
|
|
235
|
+
debug?: boolean
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
#### Authentication Methods
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
// Authenticate with credentials
|
|
243
|
+
await client.authenticate({ username: 'user@example.com', password: 'password' })
|
|
244
|
+
|
|
245
|
+
// Check authentication status
|
|
246
|
+
client.isAuthenticated() // boolean
|
|
247
|
+
|
|
248
|
+
// Get current tokens
|
|
249
|
+
client.getTokens() // AuthTokens | null
|
|
250
|
+
|
|
251
|
+
// Set tokens manually
|
|
252
|
+
client.setTokens({ accessToken: 'token', refreshToken: 'refresh' })
|
|
253
|
+
|
|
254
|
+
// Logout
|
|
255
|
+
await client.logout()
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
#### Document Methods
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
// Get document by ID
|
|
262
|
+
const post = await client.getDocument<PostDocument>('post', 'post-id')
|
|
263
|
+
|
|
264
|
+
// Get document with reference expansion
|
|
265
|
+
const post = await client.getDocument<PostDocument>('post', 'post-id', {
|
|
266
|
+
expand: 'author,categories[]'
|
|
267
|
+
})
|
|
268
|
+
|
|
269
|
+
// Query documents
|
|
270
|
+
const posts = await client.queryDocuments<PostDocument>('post', {
|
|
271
|
+
filter: { published: true },
|
|
272
|
+
sort: { createdAt: -1 },
|
|
273
|
+
limit: 10,
|
|
274
|
+
offset: 0
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
// Create document
|
|
278
|
+
const newPost = await client.createDocument('post', {
|
|
279
|
+
title: 'New Post',
|
|
280
|
+
content: 'Post content...',
|
|
281
|
+
published: false
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
// Update document
|
|
285
|
+
const updated = await client.updateDocument('post', 'post-id', {
|
|
286
|
+
title: 'Updated Title'
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
// Replace document
|
|
290
|
+
const replaced = await client.replaceDocument('post', 'post-id', {
|
|
291
|
+
title: 'New Title',
|
|
292
|
+
content: 'New content...',
|
|
293
|
+
published: true
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
// Delete document
|
|
297
|
+
await client.deleteDocument('post', 'post-id')
|
|
298
|
+
|
|
299
|
+
// Search documents
|
|
300
|
+
const results = await client.searchDocuments('post', 'search term', ['title', 'content'])
|
|
301
|
+
|
|
302
|
+
// Count documents
|
|
303
|
+
const count = await client.countDocuments('post', { published: true })
|
|
304
|
+
|
|
305
|
+
// Check if document exists
|
|
306
|
+
const exists = await client.documentExists('post', 'post-id')
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
#### Media Methods
|
|
310
|
+
|
|
311
|
+
```typescript
|
|
312
|
+
// Upload file
|
|
313
|
+
const media = await client.uploadFile(file, 'filename.jpg')
|
|
314
|
+
|
|
315
|
+
// Get media
|
|
316
|
+
const media = await client.getMedia('media-id')
|
|
317
|
+
|
|
318
|
+
// Delete media
|
|
319
|
+
await client.deleteMedia('media-id')
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
#### Utility Methods
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
// Test API connection
|
|
326
|
+
await client.ping()
|
|
327
|
+
|
|
328
|
+
// Get API health status
|
|
329
|
+
await client.health()
|
|
330
|
+
|
|
331
|
+
// Clear cache
|
|
332
|
+
client.clearCache()
|
|
333
|
+
|
|
334
|
+
// Get cache stats
|
|
335
|
+
client.getCacheStats()
|
|
336
|
+
|
|
337
|
+
// Cleanup resources
|
|
338
|
+
client.destroy()
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Query Options
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
interface QueryOptions {
|
|
345
|
+
filter?: Record<string, any>
|
|
346
|
+
sort?: Record<string, 1 | -1>
|
|
347
|
+
limit?: number
|
|
348
|
+
offset?: number
|
|
349
|
+
select?: string[]
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Examples
|
|
353
|
+
const options: QueryOptions = {
|
|
354
|
+
filter: {
|
|
355
|
+
published: true,
|
|
356
|
+
author: 'user-id',
|
|
357
|
+
tags: { $in: ['tech', 'programming'] }
|
|
358
|
+
},
|
|
359
|
+
sort: { createdAt: -1, title: 1 },
|
|
360
|
+
limit: 20,
|
|
361
|
+
offset: 0,
|
|
362
|
+
select: ['title', 'slug', 'author']
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
### Type Generation
|
|
367
|
+
|
|
368
|
+
Generate TypeScript types from your Trokky schemas:
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
import { generateTypes, generateTypesFromSchema } from '@trokky/client/generator'
|
|
372
|
+
|
|
373
|
+
// Generate from URL
|
|
374
|
+
await generateTypes({
|
|
375
|
+
schemaUrl: 'https://api.example.com/schema',
|
|
376
|
+
outputDir: './src/types',
|
|
377
|
+
namespace: 'Trokky',
|
|
378
|
+
fileExtension: 'ts', // or 'd.ts'
|
|
379
|
+
includeValidation: true
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
// Generate from schema object
|
|
383
|
+
await generateTypesFromSchema(schema, {
|
|
384
|
+
outputDir: './src/types',
|
|
385
|
+
namespace: 'MyApp'
|
|
386
|
+
})
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## Advanced Usage
|
|
390
|
+
|
|
391
|
+
### Reference Expansion
|
|
392
|
+
|
|
393
|
+
Expand reference fields to include full document data instead of just reference IDs. References are resolved and the full document replaces the reference object.
|
|
394
|
+
|
|
395
|
+
#### Before Expansion (Reference)
|
|
396
|
+
|
|
397
|
+
```json
|
|
398
|
+
{
|
|
399
|
+
"_id": "homepage",
|
|
400
|
+
"documentation": {
|
|
401
|
+
"featuredDocument": {
|
|
402
|
+
"_ref": "doc-legal-legalText-abc123",
|
|
403
|
+
"_type": "document"
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
#### After Expansion (Resolved Document)
|
|
410
|
+
|
|
411
|
+
```json
|
|
412
|
+
{
|
|
413
|
+
"_id": "homepage",
|
|
414
|
+
"documentation": {
|
|
415
|
+
"featuredDocument": {
|
|
416
|
+
"_id": "doc-legal-legalText-abc123",
|
|
417
|
+
"_collection": "document",
|
|
418
|
+
"title": "Terms of Service",
|
|
419
|
+
"slug": "terms-of-service",
|
|
420
|
+
"content": "...",
|
|
421
|
+
"_status": "published"
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
#### Basic Expansion
|
|
428
|
+
|
|
429
|
+
```typescript
|
|
430
|
+
// Direct API - expand specific fields
|
|
431
|
+
const post = await client.getDocument<PostDocument>('post', 'post-id', {
|
|
432
|
+
expand: 'author,categories[]' // Use [] suffix for array references
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
// Expand all reference fields
|
|
436
|
+
const post = await client.getDocument<PostDocument>('post', 'post-id', {
|
|
437
|
+
expand: '*'
|
|
438
|
+
})
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
#### Fluent Query Builder
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
// Query collection with reference expansion
|
|
445
|
+
const posts = await client
|
|
446
|
+
.from('post')
|
|
447
|
+
.published()
|
|
448
|
+
.expand('author') // Single reference field
|
|
449
|
+
.expand('categories[]') // Array reference field (use [] suffix)
|
|
450
|
+
.sort({ _createdAt: 'desc' })
|
|
451
|
+
.limit(10)
|
|
452
|
+
.fetch()
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
#### Singleton with Expansion
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
// Fetch singleton with expanded references
|
|
459
|
+
const homepage = await client
|
|
460
|
+
.singleton('homepage')
|
|
461
|
+
.expand('hero.backgroundImage') // Nested single reference
|
|
462
|
+
.expand('documentation.featuredDocument') // Nested single reference
|
|
463
|
+
.expand('sections.relatedArticles[]') // Nested array reference
|
|
464
|
+
.fetch()
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
#### Nested Field Paths
|
|
468
|
+
|
|
469
|
+
The `expand()` method supports dot notation for nested fields:
|
|
470
|
+
|
|
471
|
+
```typescript
|
|
472
|
+
// Top-level field
|
|
473
|
+
.expand('author')
|
|
474
|
+
|
|
475
|
+
// Nested field (one level deep)
|
|
476
|
+
.expand('content.featuredImage')
|
|
477
|
+
|
|
478
|
+
// Deeply nested field
|
|
479
|
+
.expand('sections.hero.backgroundImage')
|
|
480
|
+
|
|
481
|
+
// Multiple nested expansions
|
|
482
|
+
const homepage = await client
|
|
483
|
+
.singleton('homepage')
|
|
484
|
+
.expand('hero.backgroundImage')
|
|
485
|
+
.expand('documentation.featuredDocument')
|
|
486
|
+
.expand('footer.socialLinks[]')
|
|
487
|
+
.fetch()
|
|
488
|
+
|
|
489
|
+
// Access the expanded data directly
|
|
490
|
+
console.log(homepage.documentation.featuredDocument.title)
|
|
491
|
+
// Output: "Terms of Service"
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
#### Expand Parameter Formats
|
|
495
|
+
|
|
496
|
+
| Format | Description |
|
|
497
|
+
|--------|-------------|
|
|
498
|
+
| `author` | Expand single reference field |
|
|
499
|
+
| `categories[]` | Expand array reference field |
|
|
500
|
+
| `content.author` | Expand nested single reference |
|
|
501
|
+
| `sections.items[]` | Expand nested array reference |
|
|
502
|
+
| `a.b.c` | Expand deeply nested reference |
|
|
503
|
+
| `author,tags[]` | Expand multiple fields |
|
|
504
|
+
| `*` | Expand all reference fields |
|
|
505
|
+
|
|
506
|
+
### Frontend Integration Examples
|
|
507
|
+
|
|
508
|
+
#### Next.js App Router
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
// app/lib/trokky.ts
|
|
512
|
+
import { TrokkyClient } from '@trokky/client'
|
|
513
|
+
|
|
514
|
+
export const trokky = new TrokkyClient({
|
|
515
|
+
baseUrl: process.env.TROKKY_API_URL || 'http://localhost:3000/api'
|
|
516
|
+
})
|
|
517
|
+
|
|
518
|
+
// app/lib/queries.ts
|
|
519
|
+
export async function getHomepage() {
|
|
520
|
+
return trokky
|
|
521
|
+
.singleton('homepage')
|
|
522
|
+
.expand('documentation.featuredDocument')
|
|
523
|
+
.expand('hero.backgroundImage')
|
|
524
|
+
.fetch()
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
export async function getPosts() {
|
|
528
|
+
return trokky
|
|
529
|
+
.from('post')
|
|
530
|
+
.published()
|
|
531
|
+
.expand('author')
|
|
532
|
+
.expand('category')
|
|
533
|
+
.newest()
|
|
534
|
+
.limit(10)
|
|
535
|
+
.fetch()
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// app/page.tsx
|
|
539
|
+
import { getHomepage } from './lib/queries'
|
|
540
|
+
|
|
541
|
+
export default async function HomePage() {
|
|
542
|
+
const homepage = await getHomepage()
|
|
543
|
+
|
|
544
|
+
return (
|
|
545
|
+
<div>
|
|
546
|
+
<h1>{homepage.hero.title}</h1>
|
|
547
|
+
|
|
548
|
+
{/* Access expanded nested reference directly */}
|
|
549
|
+
{homepage.documentation.featuredDocument && (
|
|
550
|
+
<div className="featured">
|
|
551
|
+
<h2>{homepage.documentation.featuredDocument.title}</h2>
|
|
552
|
+
<p>{homepage.documentation.featuredDocument.description}</p>
|
|
553
|
+
</div>
|
|
554
|
+
)}
|
|
555
|
+
</div>
|
|
556
|
+
)
|
|
557
|
+
}
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
#### React with React Query
|
|
561
|
+
|
|
562
|
+
```typescript
|
|
563
|
+
// hooks/useTrokky.ts
|
|
564
|
+
import { useQuery } from '@tanstack/react-query'
|
|
565
|
+
import { TrokkyClient } from '@trokky/client'
|
|
566
|
+
|
|
567
|
+
const client = new TrokkyClient({
|
|
568
|
+
baseUrl: 'http://localhost:3000/api'
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
export function useHomepage() {
|
|
572
|
+
return useQuery({
|
|
573
|
+
queryKey: ['homepage'],
|
|
574
|
+
queryFn: () =>
|
|
575
|
+
client
|
|
576
|
+
.singleton('homepage')
|
|
577
|
+
.expand('documentation.featuredDocument')
|
|
578
|
+
.expand('hero.backgroundImage')
|
|
579
|
+
.fetch()
|
|
580
|
+
})
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
export function usePosts() {
|
|
584
|
+
return useQuery({
|
|
585
|
+
queryKey: ['posts'],
|
|
586
|
+
queryFn: () =>
|
|
587
|
+
client
|
|
588
|
+
.from('post')
|
|
589
|
+
.published()
|
|
590
|
+
.expand('author')
|
|
591
|
+
.newest()
|
|
592
|
+
.limit(10)
|
|
593
|
+
.fetch()
|
|
594
|
+
})
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// components/HomePage.tsx
|
|
598
|
+
import { useHomepage } from '../hooks/useTrokky'
|
|
599
|
+
|
|
600
|
+
export function HomePage() {
|
|
601
|
+
const { data: homepage, isLoading, error } = useHomepage()
|
|
602
|
+
|
|
603
|
+
if (isLoading) return <div>Loading...</div>
|
|
604
|
+
if (error) return <div>Error loading page</div>
|
|
605
|
+
if (!homepage) return null
|
|
606
|
+
|
|
607
|
+
const featuredDoc = homepage.documentation?.featuredDocument
|
|
608
|
+
|
|
609
|
+
return (
|
|
610
|
+
<main>
|
|
611
|
+
<h1>{homepage.hero?.title}</h1>
|
|
612
|
+
|
|
613
|
+
{featuredDoc && (
|
|
614
|
+
<article>
|
|
615
|
+
<h2>{featuredDoc.title}</h2>
|
|
616
|
+
<p>{featuredDoc.excerpt}</p>
|
|
617
|
+
<a href={`/docs/${featuredDoc.slug}`}>Read more</a>
|
|
618
|
+
</article>
|
|
619
|
+
)}
|
|
620
|
+
</main>
|
|
621
|
+
)
|
|
622
|
+
}
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
#### Vue 3 Composition API
|
|
626
|
+
|
|
627
|
+
```typescript
|
|
628
|
+
// composables/useTrokky.ts
|
|
629
|
+
import { ref, onMounted } from 'vue'
|
|
630
|
+
import { TrokkyClient } from '@trokky/client'
|
|
631
|
+
|
|
632
|
+
const client = new TrokkyClient({
|
|
633
|
+
baseUrl: 'http://localhost:3000/api'
|
|
634
|
+
})
|
|
635
|
+
|
|
636
|
+
export function useHomepage() {
|
|
637
|
+
const homepage = ref(null)
|
|
638
|
+
const loading = ref(true)
|
|
639
|
+
const error = ref(null)
|
|
640
|
+
|
|
641
|
+
onMounted(async () => {
|
|
642
|
+
try {
|
|
643
|
+
homepage.value = await client
|
|
644
|
+
.singleton('homepage')
|
|
645
|
+
.expand('documentation.featuredDocument')
|
|
646
|
+
.expand('hero.backgroundImage')
|
|
647
|
+
.fetch()
|
|
648
|
+
} catch (e) {
|
|
649
|
+
error.value = e
|
|
650
|
+
} finally {
|
|
651
|
+
loading.value = false
|
|
652
|
+
}
|
|
653
|
+
})
|
|
654
|
+
|
|
655
|
+
return { homepage, loading, error }
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// components/HomePage.vue
|
|
659
|
+
<script setup lang="ts">
|
|
660
|
+
import { useHomepage } from '../composables/useTrokky'
|
|
661
|
+
|
|
662
|
+
const { homepage, loading, error } = useHomepage()
|
|
663
|
+
</script>
|
|
664
|
+
|
|
665
|
+
<template>
|
|
666
|
+
<div v-if="loading">Loading...</div>
|
|
667
|
+
<div v-else-if="error">Error: {{ error.message }}</div>
|
|
668
|
+
<main v-else-if="homepage">
|
|
669
|
+
<h1>{{ homepage.hero?.title }}</h1>
|
|
670
|
+
|
|
671
|
+
<article v-if="homepage.documentation?.featuredDocument">
|
|
672
|
+
<h2>{{ homepage.documentation.featuredDocument.title }}</h2>
|
|
673
|
+
<p>{{ homepage.documentation.featuredDocument.excerpt }}</p>
|
|
674
|
+
</article>
|
|
675
|
+
</main>
|
|
676
|
+
</template>
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
#### TypeScript Types with Expansion
|
|
680
|
+
|
|
681
|
+
When using TypeScript, you can define types that reflect expanded references:
|
|
682
|
+
|
|
683
|
+
```typescript
|
|
684
|
+
// types/cms.ts
|
|
685
|
+
import type { BaseDocument } from '@trokky/client'
|
|
686
|
+
|
|
687
|
+
interface DocumentReference {
|
|
688
|
+
_ref: string
|
|
689
|
+
_type: string
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
interface LegalDocument extends BaseDocument {
|
|
693
|
+
title: string
|
|
694
|
+
slug: string
|
|
695
|
+
content: string
|
|
696
|
+
category: string
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Before expansion: reference object
|
|
700
|
+
interface HomepageRaw extends BaseDocument {
|
|
701
|
+
documentation: {
|
|
702
|
+
featuredDocument: DocumentReference
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// After expansion: full document
|
|
707
|
+
interface HomepageExpanded extends BaseDocument {
|
|
708
|
+
documentation: {
|
|
709
|
+
featuredDocument: LegalDocument
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Usage with explicit expanded type
|
|
714
|
+
const homepage = await client
|
|
715
|
+
.singleton<HomepageExpanded>('homepage')
|
|
716
|
+
.expand('documentation.featuredDocument')
|
|
717
|
+
.fetch()
|
|
718
|
+
|
|
719
|
+
// TypeScript knows featuredDocument has title, slug, etc.
|
|
720
|
+
console.log(homepage.documentation.featuredDocument.title)
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
### Custom HTTP Client
|
|
724
|
+
|
|
725
|
+
Access the underlying HTTP client for advanced usage:
|
|
726
|
+
|
|
727
|
+
```typescript
|
|
728
|
+
const client = new TrokkyClient({ baseUrl: 'https://api.example.com' })
|
|
729
|
+
|
|
730
|
+
// Direct HTTP requests
|
|
731
|
+
const response = await client.http.get('/custom-endpoint')
|
|
732
|
+
const result = await client.http.post('/custom-endpoint', { data: 'value' })
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
### Cache Management
|
|
736
|
+
|
|
737
|
+
```typescript
|
|
738
|
+
// Configure caching
|
|
739
|
+
const client = new TrokkyClient({
|
|
740
|
+
baseUrl: 'https://api.example.com',
|
|
741
|
+
enableCache: true,
|
|
742
|
+
cacheMaxAge: 300000 // 5 minutes
|
|
743
|
+
})
|
|
744
|
+
|
|
745
|
+
// Cache-specific operations
|
|
746
|
+
client.clearCache()
|
|
747
|
+
const stats = client.getCacheStats()
|
|
748
|
+
|
|
749
|
+
// Disable cache for specific requests
|
|
750
|
+
const freshData = await client.queryDocuments('post', {}, false) // useCache = false
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
### Error Handling
|
|
754
|
+
|
|
755
|
+
```typescript
|
|
756
|
+
import type { ApiError } from '@trokky/client'
|
|
757
|
+
|
|
758
|
+
try {
|
|
759
|
+
await client.getDocument('post', 'invalid-id')
|
|
760
|
+
} catch (error) {
|
|
761
|
+
if (error.status === 404) {
|
|
762
|
+
console.log('Document not found')
|
|
763
|
+
} else if (error.status === 401) {
|
|
764
|
+
console.log('Authentication required')
|
|
765
|
+
await client.authenticate(credentials)
|
|
766
|
+
} else {
|
|
767
|
+
console.error('API Error:', error.message)
|
|
768
|
+
if (error.validation) {
|
|
769
|
+
console.log('Validation errors:', error.validation)
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
```
|
|
774
|
+
|
|
775
|
+
## Configuration
|
|
776
|
+
|
|
777
|
+
### Environment Variables
|
|
778
|
+
|
|
779
|
+
```bash
|
|
780
|
+
# API Configuration
|
|
781
|
+
TROKKY_API_URL=https://your-api.com
|
|
782
|
+
TROKKY_API_VERSION=v1
|
|
783
|
+
|
|
784
|
+
# Authentication
|
|
785
|
+
TROKKY_TOKEN=your-jwt-token
|
|
786
|
+
TROKKY_REFRESH_TOKEN=your-refresh-token
|
|
787
|
+
|
|
788
|
+
# Performance
|
|
789
|
+
TROKKY_CACHE_ENABLED=true
|
|
790
|
+
TROKKY_CACHE_MAX_AGE=300000
|
|
791
|
+
TROKKY_REQUEST_TIMEOUT=30000
|
|
792
|
+
TROKKY_REQUEST_RETRIES=3
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
### Client Configuration
|
|
796
|
+
|
|
797
|
+
```typescript
|
|
798
|
+
const client = new TrokkyClient({
|
|
799
|
+
baseUrl: process.env.TROKKY_API_URL || 'http://localhost:3000',
|
|
800
|
+
apiVersion: process.env.TROKKY_API_VERSION || 'v1',
|
|
801
|
+
token: process.env.TROKKY_TOKEN,
|
|
802
|
+
refreshToken: process.env.TROKKY_REFRESH_TOKEN,
|
|
803
|
+
timeout: parseInt(process.env.TROKKY_REQUEST_TIMEOUT || '30000'),
|
|
804
|
+
retries: parseInt(process.env.TROKKY_REQUEST_RETRIES || '3'),
|
|
805
|
+
enableCache: process.env.TROKKY_CACHE_ENABLED === 'true',
|
|
806
|
+
cacheMaxAge: parseInt(process.env.TROKKY_CACHE_MAX_AGE || '300000'),
|
|
807
|
+
debug: process.env.NODE_ENV === 'development'
|
|
808
|
+
})
|
|
809
|
+
```
|
|
810
|
+
|
|
811
|
+
## Examples
|
|
812
|
+
|
|
813
|
+
Check out complete examples in the [examples directory](./examples):
|
|
814
|
+
|
|
815
|
+
- [React Blog](./examples/react-blog) - Complete blog application with React
|
|
816
|
+
- [Vue Portfolio](./examples/vue-portfolio) - Portfolio site with Vue 3
|
|
817
|
+
- [Svelte News](./examples/svelte-news) - News site with SvelteKit
|
|
818
|
+
- [Node.js API](./examples/node-api) - Server-side usage with Express
|
|
819
|
+
- [Next.js App](./examples/nextjs-app) - Full-stack Next.js application
|
|
820
|
+
|
|
821
|
+
## TypeScript Support
|
|
822
|
+
|
|
823
|
+
This package includes full TypeScript definitions and supports:
|
|
824
|
+
|
|
825
|
+
- Strict type checking
|
|
826
|
+
- Generic document types
|
|
827
|
+
- Auto-completion in IDEs
|
|
828
|
+
- Type-safe query building
|
|
829
|
+
- Generated types from schemas
|
|
830
|
+
|
|
831
|
+
## License
|
|
832
|
+
|
|
833
|
+
MIT
|