@fjell/client-api 4.4.12 ā 4.4.13
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/docs/docs.config.ts +77 -0
- package/docs/package.json +5 -6
- package/docs/public/examples-README.md +237 -0
- package/docs/src/main.tsx +6 -4
- package/docs/src/types.d.ts +44 -0
- package/package.json +10 -6
- package/docs/src/App.css +0 -1237
- package/docs/src/App.test.tsx +0 -49
- package/docs/src/App.tsx +0 -513
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
interface DocsConfig {
|
|
2
|
+
projectName: string;
|
|
3
|
+
basePath: string;
|
|
4
|
+
port: number;
|
|
5
|
+
branding: {
|
|
6
|
+
theme: string;
|
|
7
|
+
tagline: string;
|
|
8
|
+
logo?: string;
|
|
9
|
+
backgroundImage?: string;
|
|
10
|
+
primaryColor?: string;
|
|
11
|
+
accentColor?: string;
|
|
12
|
+
github?: string;
|
|
13
|
+
npm?: string;
|
|
14
|
+
};
|
|
15
|
+
sections: Array<{
|
|
16
|
+
id: string;
|
|
17
|
+
title: string;
|
|
18
|
+
subtitle: string;
|
|
19
|
+
file: string;
|
|
20
|
+
}>;
|
|
21
|
+
filesToCopy: Array<{
|
|
22
|
+
source: string;
|
|
23
|
+
destination: string;
|
|
24
|
+
}>;
|
|
25
|
+
plugins?: any[];
|
|
26
|
+
version: {
|
|
27
|
+
source: string;
|
|
28
|
+
};
|
|
29
|
+
customContent?: {
|
|
30
|
+
[key: string]: (content: string) => string;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const config: DocsConfig = {
|
|
35
|
+
projectName: 'Fjell Client API',
|
|
36
|
+
basePath: '/client-api/',
|
|
37
|
+
port: 3002,
|
|
38
|
+
branding: {
|
|
39
|
+
theme: 'client-api',
|
|
40
|
+
tagline: 'HTTP Client Library for Fjell',
|
|
41
|
+
backgroundImage: '/pano.png',
|
|
42
|
+
github: 'https://github.com/getfjell/client-api',
|
|
43
|
+
npm: 'https://www.npmjs.com/package/@fjell/client-api'
|
|
44
|
+
},
|
|
45
|
+
sections: [
|
|
46
|
+
{
|
|
47
|
+
id: 'overview',
|
|
48
|
+
title: 'Foundation',
|
|
49
|
+
subtitle: 'Core concepts & HTTP client capabilities',
|
|
50
|
+
file: '/client-api/README.md'
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: 'api-reference',
|
|
54
|
+
title: 'API Reference',
|
|
55
|
+
subtitle: 'Complete method documentation',
|
|
56
|
+
file: '/client-api/api-reference.md'
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 'examples',
|
|
60
|
+
title: 'Examples',
|
|
61
|
+
subtitle: 'Code examples & usage patterns',
|
|
62
|
+
file: '/client-api/examples-README.md'
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
filesToCopy: [
|
|
66
|
+
{
|
|
67
|
+
source: '../examples/README.md',
|
|
68
|
+
destination: 'public/examples-README.md'
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
plugins: [],
|
|
72
|
+
version: {
|
|
73
|
+
source: 'package.json'
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export default config
|
package/docs/package.json
CHANGED
|
@@ -4,18 +4,17 @@
|
|
|
4
4
|
"version": "0.0.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"
|
|
8
|
-
"
|
|
7
|
+
"copy-docs": "node node_modules/@fjell/docs-template/scripts/copy-docs.js",
|
|
8
|
+
"dev": "pnpm run copy-docs && vite",
|
|
9
|
+
"build": "pnpm run copy-docs && tsc && vite build",
|
|
9
10
|
"preview": "vite preview",
|
|
10
11
|
"test": "vitest run --coverage",
|
|
11
12
|
"test:watch": "vitest --watch"
|
|
12
13
|
},
|
|
13
14
|
"dependencies": {
|
|
15
|
+
"@fjell/docs-template": "1.0.5",
|
|
14
16
|
"react": "^19.1.0",
|
|
15
|
-
"react-dom": "^19.1.0"
|
|
16
|
-
"react-markdown": "^10.1.0",
|
|
17
|
-
"react-syntax-highlighter": "^15.6.1",
|
|
18
|
-
"remark-gfm": "^4.0.1"
|
|
17
|
+
"react-dom": "^19.1.0"
|
|
19
18
|
},
|
|
20
19
|
"devDependencies": {
|
|
21
20
|
"@testing-library/jest-dom": "^6.6.3",
|
|
@@ -114,6 +114,202 @@ const customerMetrics = await customerApi.facet(customerKey, 'loyalty-metrics');
|
|
|
114
114
|
const supportAnalytics = await supportApi.allFacet('resolution-metrics', {}, [customer.id]);
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
+
## API Types and Patterns
|
|
118
|
+
|
|
119
|
+
### Primary Item API (PItemApi)
|
|
120
|
+
Used for independent entities that exist at the top level:
|
|
121
|
+
- **Endpoints**: `/entities/{id}`
|
|
122
|
+
- **Use cases**: Customers, Products, Orders, Organizations
|
|
123
|
+
- **Operations**: Standard CRUD + actions + facets
|
|
124
|
+
- **No location context required**
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
interface PItemApi<V, S> {
|
|
128
|
+
all(query: ItemQuery): Promise<V[]>;
|
|
129
|
+
create(item: Partial<Item<S>>): Promise<V>;
|
|
130
|
+
get(key: PriKey<S>): Promise<V>;
|
|
131
|
+
update(key: PriKey<S>, updates: Partial<Item<S>>): Promise<V>;
|
|
132
|
+
remove(key: PriKey<S>): Promise<boolean>;
|
|
133
|
+
action(key: PriKey<S>, action: string, body?: any): Promise<any>;
|
|
134
|
+
find(finder: string, params?: any): Promise<V[]>;
|
|
135
|
+
facet(key: PriKey<S>, facet: string, params?: any): Promise<any>;
|
|
136
|
+
allAction(action: string, body?: any): Promise<V[]>;
|
|
137
|
+
allFacet(facet: string, params?: any): Promise<any>;
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Contained Item API (CItemApi)
|
|
142
|
+
Used for hierarchical entities that belong to parent locations:
|
|
143
|
+
- **Endpoints**: `/parents/{parentId}/entities/{id}`
|
|
144
|
+
- **Use cases**: Tasks (in Users), OrderItems (in Orders), Employees (in Departments)
|
|
145
|
+
- **Operations**: CRUD + actions + facets with location context
|
|
146
|
+
- **Requires location arrays for context**
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
interface CItemApi<V, S, L1, L2, L3, L4, L5> extends ClientApi<V, S, L1, L2, L3, L4, L5> {
|
|
150
|
+
all(query: ItemQuery, locations?: LocKeyArray<L1, L2, L3, L4, L5>): Promise<V[]>;
|
|
151
|
+
create(item: Partial<Item<S>>, locations?: LocKeyArray<L1, L2, L3, L4, L5>): Promise<V>;
|
|
152
|
+
find(finder: string, params?: any, locations?: LocKeyArray<L1, L2, L3, L4, L5>): Promise<V[]>;
|
|
153
|
+
allAction(action: string, body?: any, locations?: LocKeyArray<L1, L2, L3, L4, L5>): Promise<V[]>;
|
|
154
|
+
allFacet(facet: string, params?: any, locations?: LocKeyArray<L1, L2, L3, L4, L5>): Promise<any>;
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Business Logic Patterns
|
|
159
|
+
|
|
160
|
+
### Actions - Business Logic Execution
|
|
161
|
+
Actions execute business logic on entities or collections:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// Single entity actions
|
|
165
|
+
await userApi.action(userKey, 'activate', { reason: 'manual activation' });
|
|
166
|
+
await orderApi.action(orderKey, 'fulfill-order', { warehouse: 'main' });
|
|
167
|
+
await ticketApi.action(ticketKey, 'escalate', { priority: 'urgent' });
|
|
168
|
+
|
|
169
|
+
// Batch actions on collections
|
|
170
|
+
await userApi.allAction('updatePreferences', { newsletter: true });
|
|
171
|
+
await productApi.allAction('applyDiscount', { percent: 10 });
|
|
172
|
+
await orderApi.allAction('expediteShipping', { method: 'express' });
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Facets - Analytics and Data Retrieval
|
|
176
|
+
Facets retrieve analytics, computed data, and business insights:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// Entity-specific analytics
|
|
180
|
+
const userStats = await userApi.facet(userKey, 'purchase-history');
|
|
181
|
+
const productMetrics = await productApi.facet(productKey, 'performance-metrics');
|
|
182
|
+
const orderStatus = await orderApi.facet(orderKey, 'fulfillment-status');
|
|
183
|
+
|
|
184
|
+
// Aggregated analytics
|
|
185
|
+
const customerAnalytics = await customerApi.allFacet('revenue-analytics', { period: 'quarterly' });
|
|
186
|
+
const inventoryReport = await productApi.allFacet('inventory-analytics', { lowStock: true });
|
|
187
|
+
const salesMetrics = await orderApi.allFacet('sales-metrics', { region: 'north-america' });
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Configuration Patterns
|
|
191
|
+
|
|
192
|
+
### Basic Configuration
|
|
193
|
+
```typescript
|
|
194
|
+
const apiConfig = {
|
|
195
|
+
baseUrl: 'https://api.example.com',
|
|
196
|
+
headers: {
|
|
197
|
+
'Content-Type': 'application/json',
|
|
198
|
+
'Authorization': 'Bearer your-api-token'
|
|
199
|
+
},
|
|
200
|
+
timeout: 5000
|
|
201
|
+
};
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Enterprise Configuration
|
|
205
|
+
```typescript
|
|
206
|
+
const enterpriseConfig = {
|
|
207
|
+
baseUrl: 'https://api.enterprise.com/v1',
|
|
208
|
+
headers: {
|
|
209
|
+
'Content-Type': 'application/json',
|
|
210
|
+
'Authorization': 'Bearer enterprise-token',
|
|
211
|
+
'X-Tenant-ID': 'tenant-001',
|
|
212
|
+
'X-Environment': 'production'
|
|
213
|
+
},
|
|
214
|
+
timeout: 10000,
|
|
215
|
+
retries: 3,
|
|
216
|
+
readAuthenticated: true,
|
|
217
|
+
writeAuthenticated: true
|
|
218
|
+
};
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Error Handling Patterns
|
|
222
|
+
|
|
223
|
+
### Basic Error Handling
|
|
224
|
+
```typescript
|
|
225
|
+
try {
|
|
226
|
+
const user = await userApi.get(userKey);
|
|
227
|
+
if (!user) {
|
|
228
|
+
console.log('User not found');
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
// Process user...
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error('API error:', error);
|
|
234
|
+
// Handle specific error types
|
|
235
|
+
if (error.status === 404) {
|
|
236
|
+
// Handle not found
|
|
237
|
+
} else if (error.status === 401) {
|
|
238
|
+
// Handle authentication error
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Enterprise Error Handling
|
|
244
|
+
```typescript
|
|
245
|
+
try {
|
|
246
|
+
const result = await customerApi.action(customerKey, 'process-order', orderData);
|
|
247
|
+
return result;
|
|
248
|
+
} catch (error) {
|
|
249
|
+
// Log error with context
|
|
250
|
+
console.error('Order processing failed:', {
|
|
251
|
+
customerId: customerKey.id,
|
|
252
|
+
error: error.message,
|
|
253
|
+
timestamp: new Date()
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// Implement retry logic for transient errors
|
|
257
|
+
if (error.isRetryable) {
|
|
258
|
+
return await retryOperation(() =>
|
|
259
|
+
customerApi.action(customerKey, 'process-order', orderData)
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
throw error;
|
|
264
|
+
}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## Testing Patterns
|
|
268
|
+
|
|
269
|
+
The examples include comprehensive integration tests in `tests/examples/` that demonstrate:
|
|
270
|
+
|
|
271
|
+
- **API operation testing** - Verifying CRUD operations work correctly
|
|
272
|
+
- **Business workflow testing** - Testing complete business processes
|
|
273
|
+
- **Error scenario testing** - Handling various error conditions
|
|
274
|
+
- **Performance testing** - Measuring API response times
|
|
275
|
+
- **Mock API testing** - Testing with mock backend services
|
|
276
|
+
|
|
277
|
+
### Running Tests
|
|
278
|
+
```bash
|
|
279
|
+
# Run all example tests
|
|
280
|
+
npm test -- tests/examples
|
|
281
|
+
|
|
282
|
+
# Run specific example tests
|
|
283
|
+
npm test -- tests/examples/simple-example.integration.test.ts
|
|
284
|
+
npm test -- tests/examples/multi-level-keys.integration.test.ts
|
|
285
|
+
npm test -- tests/examples/enterprise-example.integration.test.ts
|
|
286
|
+
|
|
287
|
+
# Run with coverage
|
|
288
|
+
npm run test:coverage -- tests/examples
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Real-World Usage
|
|
292
|
+
|
|
293
|
+
### When to Use PItemApi
|
|
294
|
+
- **Independent entities**: Users, Products, Orders, Organizations
|
|
295
|
+
- **Top-level resources**: Resources that don't belong to other entities
|
|
296
|
+
- **Simple CRUD needs**: Basic create, read, update, delete operations
|
|
297
|
+
- **Global operations**: Operations that span across the entire system
|
|
298
|
+
|
|
299
|
+
### When to Use CItemApi
|
|
300
|
+
- **Hierarchical data**: Tasks in Projects, OrderItems in Orders, Comments in Posts
|
|
301
|
+
- **Location-based operations**: Operations within specific parent contexts
|
|
302
|
+
- **Multi-tenant scenarios**: Data that belongs to specific tenants or organizations
|
|
303
|
+
- **Nested resources**: Resources that logically belong to parent resources
|
|
304
|
+
|
|
305
|
+
### Enterprise Considerations
|
|
306
|
+
- **Authentication**: Use proper bearer tokens or API keys
|
|
307
|
+
- **Rate limiting**: Implement client-side rate limiting for high-volume operations
|
|
308
|
+
- **Caching**: Cache frequently accessed data to reduce API calls
|
|
309
|
+
- **Monitoring**: Log API calls and performance metrics
|
|
310
|
+
- **Error recovery**: Implement retry logic and circuit breakers
|
|
311
|
+
- **Data validation**: Validate data before sending to API
|
|
312
|
+
|
|
117
313
|
## Running the Examples
|
|
118
314
|
|
|
119
315
|
### Prerequisites
|
|
@@ -137,6 +333,28 @@ npx tsx examples/multi-level-keys.ts
|
|
|
137
333
|
npx tsx examples/enterprise-example.ts
|
|
138
334
|
```
|
|
139
335
|
|
|
336
|
+
### Run All Examples
|
|
337
|
+
```bash
|
|
338
|
+
# Run all examples in sequence
|
|
339
|
+
npx tsx -e "
|
|
340
|
+
import { runSimpleExample } from './examples/simple-example.js';
|
|
341
|
+
import { runMultiLevelKeysExample } from './examples/multi-level-keys.js';
|
|
342
|
+
import { runEnterpriseExample } from './examples/enterprise-example.js';
|
|
343
|
+
|
|
344
|
+
console.log('š Running All Fjell-Client-API Examples\\n');
|
|
345
|
+
|
|
346
|
+
await runSimpleExample();
|
|
347
|
+
console.log('\\n' + '='.repeat(50) + '\\n');
|
|
348
|
+
|
|
349
|
+
await runMultiLevelKeysExample();
|
|
350
|
+
console.log('\\n' + '='.repeat(50) + '\\n');
|
|
351
|
+
|
|
352
|
+
await runEnterpriseExample();
|
|
353
|
+
|
|
354
|
+
console.log('\\nā
All examples completed successfully!');
|
|
355
|
+
"
|
|
356
|
+
```
|
|
357
|
+
|
|
140
358
|
## Next Steps
|
|
141
359
|
|
|
142
360
|
After exploring these examples:
|
|
@@ -148,3 +366,22 @@ After exploring these examples:
|
|
|
148
366
|
5. **Add monitoring** - Implement logging and monitoring for API operations
|
|
149
367
|
6. **Write tests** - Create tests for your specific API usage patterns
|
|
150
368
|
7. **Optimize performance** - Add caching, pagination, and other performance optimizations
|
|
369
|
+
|
|
370
|
+
## Contributing
|
|
371
|
+
|
|
372
|
+
To add new examples or improve existing ones:
|
|
373
|
+
|
|
374
|
+
1. Create your example file in `examples/`
|
|
375
|
+
2. Add corresponding integration tests in `tests/examples/`
|
|
376
|
+
3. Update this README with documentation
|
|
377
|
+
4. Ensure examples follow the established patterns
|
|
378
|
+
5. Test thoroughly with both success and error scenarios
|
|
379
|
+
|
|
380
|
+
## Support
|
|
381
|
+
|
|
382
|
+
For questions about these examples or fjell-client-api usage:
|
|
383
|
+
|
|
384
|
+
- Check the main fjell-client-api documentation
|
|
385
|
+
- Review the integration tests for detailed usage patterns
|
|
386
|
+
- Look at the enterprise example for complex workflow patterns
|
|
387
|
+
- Examine the source code for specific API method signatures
|
package/docs/src/main.tsx
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import ReactDOM from 'react-dom/client'
|
|
3
|
-
import
|
|
3
|
+
import { DocsApp } from '@fjell/docs-template'
|
|
4
|
+
import '@fjell/docs-template/dist/index.css'
|
|
5
|
+
import config from '../docs.config'
|
|
4
6
|
import './index.css'
|
|
5
7
|
|
|
6
8
|
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
<React.StrictMode>
|
|
10
|
+
<DocsApp config={config} />
|
|
11
|
+
</React.StrictMode>,
|
|
10
12
|
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Types are provided by the @fjell/docs-template package
|
|
2
|
+
|
|
3
|
+
declare module '@fjell/docs-template' {
|
|
4
|
+
export interface DocsConfig {
|
|
5
|
+
projectName: string;
|
|
6
|
+
basePath: string;
|
|
7
|
+
port: number;
|
|
8
|
+
branding: {
|
|
9
|
+
theme: string;
|
|
10
|
+
tagline: string;
|
|
11
|
+
logo?: string;
|
|
12
|
+
backgroundImage?: string;
|
|
13
|
+
primaryColor?: string;
|
|
14
|
+
accentColor?: string;
|
|
15
|
+
github?: string;
|
|
16
|
+
npm?: string;
|
|
17
|
+
};
|
|
18
|
+
sections: Array<{
|
|
19
|
+
id: string;
|
|
20
|
+
title: string;
|
|
21
|
+
subtitle: string;
|
|
22
|
+
file: string;
|
|
23
|
+
}>;
|
|
24
|
+
plugins?: any[];
|
|
25
|
+
version: {
|
|
26
|
+
source: string;
|
|
27
|
+
};
|
|
28
|
+
customContent?: {
|
|
29
|
+
[key: string]: (content: string) => string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface DocsAppProps {
|
|
34
|
+
config: DocsConfig;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const DocsApp: React.ComponentType<DocsAppProps>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
declare module '../docs.config.ts' {
|
|
41
|
+
import type { DocsConfig } from '@fjell/docs-template'
|
|
42
|
+
const config: DocsConfig
|
|
43
|
+
export default config
|
|
44
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fjell/client-api",
|
|
3
3
|
"description": "Client API for Fjell",
|
|
4
|
-
"version": "4.4.
|
|
4
|
+
"version": "4.4.13",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"client",
|
|
7
7
|
"api",
|
|
@@ -18,16 +18,16 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@fjell/core": "^4.4.13",
|
|
21
|
-
"@fjell/http-api": "^4.4.
|
|
22
|
-
"@fjell/logging": "^4.4.
|
|
23
|
-
"@fjell/registry": "^4.4.
|
|
21
|
+
"@fjell/http-api": "^4.4.16",
|
|
22
|
+
"@fjell/logging": "^4.4.17",
|
|
23
|
+
"@fjell/registry": "^4.4.16",
|
|
24
24
|
"deepmerge": "^4.3.1"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@eslint/eslintrc": "^3.3.1",
|
|
28
28
|
"@eslint/js": "^9.31.0",
|
|
29
29
|
"@fjell/eslint-config": "^1.0.5",
|
|
30
|
-
"@swc/core": "^1.13.
|
|
30
|
+
"@swc/core": "^1.13.2",
|
|
31
31
|
"@tsconfig/recommended": "^1.0.10",
|
|
32
32
|
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
|
33
33
|
"@typescript-eslint/parser": "^8.38.0",
|
|
@@ -53,6 +53,10 @@
|
|
|
53
53
|
"lint": "eslint . --ext .ts --fix",
|
|
54
54
|
"clean": "rimraf dist",
|
|
55
55
|
"test": "vitest run --coverage",
|
|
56
|
-
"test:ui": "vitest --ui"
|
|
56
|
+
"test:ui": "vitest --ui",
|
|
57
|
+
"docs:dev": "cd docs && pnpm run dev",
|
|
58
|
+
"docs:build": "cd docs && pnpm run build",
|
|
59
|
+
"docs:preview": "cd docs && pnpm run preview",
|
|
60
|
+
"docs:test": "cd docs && pnpm run test"
|
|
57
61
|
}
|
|
58
62
|
}
|