@fjell/client-api 4.4.7 → 4.4.10

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.
@@ -0,0 +1,49 @@
1
+ import { render, screen } from '@testing-library/react'
2
+ import { expect, test, vi } from 'vitest'
3
+ import App from './App'
4
+
5
+ // Mock fetch for testing
6
+ global.fetch = vi.fn()
7
+
8
+ const mockFetch = fetch as any
9
+
10
+ test('renders App component', () => {
11
+ // Setup fetch mocks
12
+ mockFetch.mockImplementation((url: string) => {
13
+ if (url.includes('package.json')) {
14
+ return Promise.resolve({
15
+ ok: true,
16
+ json: () => Promise.resolve({ version: '4.4.9' })
17
+ })
18
+ }
19
+ return Promise.resolve({
20
+ ok: false
21
+ })
22
+ })
23
+
24
+ render(<App />)
25
+
26
+ // Check if the loading text appears initially
27
+ expect(screen.getByText(/Loading Client API Documentation/i)).toBeInTheDocument()
28
+ })
29
+
30
+ test('renders header with correct branding', () => {
31
+ mockFetch.mockImplementation((url: string) => {
32
+ if (url.includes('package.json')) {
33
+ return Promise.resolve({
34
+ ok: true,
35
+ json: () => Promise.resolve({ version: '4.4.9' })
36
+ })
37
+ }
38
+ return Promise.resolve({
39
+ ok: false
40
+ })
41
+ })
42
+
43
+ render(<App />)
44
+
45
+ // Check for Fjell Client API branding
46
+ expect(screen.getByText('Fjell')).toBeInTheDocument()
47
+ expect(screen.getByText('Client API')).toBeInTheDocument()
48
+ expect(screen.getByText('HTTP client library for modern applications')).toBeInTheDocument()
49
+ })
@@ -0,0 +1,513 @@
1
+ import React, { useEffect, useState } from 'react'
2
+ import ReactMarkdown from 'react-markdown'
3
+ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
4
+ import { oneLight } from 'react-syntax-highlighter/dist/esm/styles/prism'
5
+ import remarkGfm from 'remark-gfm'
6
+ import './App.css'
7
+
8
+ interface DocumentSection {
9
+ id: string;
10
+ title: string;
11
+ subtitle: string;
12
+ file: string;
13
+ content?: string;
14
+ }
15
+
16
+ const documentSections: DocumentSection[] = [
17
+ { id: 'overview', title: 'Foundation', subtitle: 'Core concepts & client patterns', file: '/client-api/README.md' },
18
+ { id: 'getting-started', title: 'Getting Started', subtitle: 'Your first HTTP client setup', file: '/client-api/examples-README.md' },
19
+ { id: 'examples', title: 'Examples', subtitle: 'API patterns & usage scenarios', file: '/client-api/examples-README.md' },
20
+ { id: 'api-reference', title: 'API Reference', subtitle: 'Complete API documentation', file: '/client-api/api-reference.md' }
21
+ ];
22
+
23
+ const App: React.FC = () => {
24
+ const [currentSection, setCurrentSection] = useState('overview')
25
+ const [documents, setDocuments] = useState<{ [key: string]: string }>({})
26
+ const [loading, setLoading] = useState(true)
27
+ const [sidebarOpen, setSidebarOpen] = useState(false)
28
+ const [version, setVersion] = useState<string>('4.4.9')
29
+
30
+ useEffect(() => {
31
+ const loadDocuments = async () => {
32
+ const loadedDocs: { [key: string]: string } = {}
33
+
34
+ for (const section of documentSections) {
35
+ try {
36
+ const response = await fetch(section.file)
37
+
38
+ if (response.ok) {
39
+ loadedDocs[section.id] = await response.text()
40
+ } else {
41
+ // Fallback content for missing files
42
+ loadedDocs[section.id] = getFallbackContent(section.id)
43
+ }
44
+ } catch (error) {
45
+ console.error(`Error loading ${section.file}:`, error)
46
+ loadedDocs[section.id] = getFallbackContent(section.id)
47
+ }
48
+ }
49
+
50
+ setDocuments(loadedDocs)
51
+ setLoading(false)
52
+ }
53
+
54
+ const loadVersion = async () => {
55
+ try {
56
+ const response = await fetch('/client-api/package.json')
57
+ if (response.ok) {
58
+ const packageData = await response.json()
59
+ setVersion(packageData.version)
60
+ console.log('Version loaded:', packageData.version)
61
+ } else {
62
+ console.error('Failed to fetch package.json:', response.status)
63
+ setVersion('4.4.9') // Fallback version
64
+ }
65
+ } catch (error) {
66
+ console.error('Error loading version:', error)
67
+ setVersion('4.4.9') // Fallback version
68
+ }
69
+ }
70
+
71
+ loadDocuments()
72
+ loadVersion()
73
+ }, [])
74
+
75
+ const getFallbackContent = (sectionId: string): string => {
76
+ switch (sectionId) {
77
+ case 'overview':
78
+ return `# Fjell Client API
79
+
80
+ A comprehensive HTTP client library for the Fjell ecosystem.
81
+ The Client API provides powerful abstractions for HTTP-based data operations,
82
+ making it easy to build robust client applications that consume REST APIs.
83
+
84
+ ## Installation
85
+
86
+ \`\`\`bash
87
+ npm install @fjell/client-api
88
+ \`\`\`
89
+
90
+ ## Quick Start
91
+
92
+ \`\`\`typescript
93
+ import { createPItemApi, createCItemApi } from '@fjell/client-api'
94
+
95
+ // Configure API endpoints
96
+ const apiConfig = {
97
+ baseUrl: 'https://api.example.com',
98
+ headers: { 'Authorization': 'Bearer token' }
99
+ }
100
+
101
+ // Create Primary Item API (independent entities)
102
+ const userApi = createPItemApi<User, 'user'>('user', ['users'], apiConfig)
103
+
104
+ // Create Contained Item API (hierarchical entities)
105
+ const taskApi = createCItemApi<Task, 'task', 'user'>('task', ['users', 'tasks'], apiConfig)
106
+
107
+ // Basic operations
108
+ const users = await userApi.all(query)
109
+ const user = await userApi.create(userData)
110
+ const tasks = await taskApi.all(query, [userId]) // Location-based query
111
+ \`\`\`
112
+
113
+ ## Key Features
114
+
115
+ - **HTTP-based Operations**: Complete CRUD operations over HTTP
116
+ - **Type-safe APIs**: Full TypeScript support with generic type parameters
117
+ - **Hierarchical Data**: Support for nested resource relationships
118
+ - **Business Logic**: Actions and facets for complex operations
119
+ - **Authentication**: Built-in support for various auth patterns
120
+ - **Error Handling**: Comprehensive error handling and retry logic
121
+
122
+ ## Architecture
123
+
124
+ The Client API is built around two main concepts:
125
+
126
+ ### Primary Items (PItemApi)
127
+ Independent entities that exist at the top level of your API hierarchy.
128
+
129
+ ### Contained Items (CItemApi)
130
+ Entities that belong to parent resources and require location context.
131
+
132
+ This design mirrors RESTful API patterns while providing powerful abstractions for complex business operations.`
133
+
134
+ case 'getting-started':
135
+ return `# Getting Started with Fjell Client API
136
+
137
+ Learn how to set up and use the Fjell Client API for HTTP-based data operations.
138
+
139
+ ## Installation
140
+
141
+ \`\`\`bash
142
+ npm install @fjell/client-api
143
+ \`\`\`
144
+
145
+ ## Basic Configuration
146
+
147
+ \`\`\`typescript
148
+ import { createPItemApi, createCItemApi } from '@fjell/client-api'
149
+
150
+ const config = {
151
+ baseUrl: 'https://api.example.com',
152
+ headers: {
153
+ 'Content-Type': 'application/json',
154
+ 'Authorization': 'Bearer your-token'
155
+ }
156
+ }
157
+ \`\`\`
158
+
159
+ ## Your First API
160
+
161
+ \`\`\`typescript
162
+ // Define your data types
163
+ interface User {
164
+ id: string
165
+ name: string
166
+ email: string
167
+ }
168
+
169
+ // Create API instance
170
+ const userApi = createPItemApi<User, 'user'>('user', ['users'], config)
171
+
172
+ // Use the API
173
+ const users = await userApi.all()
174
+ const user = await userApi.create({ name: 'John', email: 'john@example.com' })
175
+ \`\`\`
176
+
177
+ See the Examples section for more detailed patterns and use cases.`
178
+
179
+ case 'examples':
180
+ return `# Examples
181
+
182
+ Comprehensive examples demonstrating various Client API patterns.
183
+
184
+ ## Available Examples
185
+
186
+ ### Simple Example
187
+ Basic CRUD operations with Primary and Contained APIs.
188
+
189
+ ### Multi-Level Keys
190
+ Complex hierarchical data structures with nested relationships.
191
+
192
+ ### Enterprise Example
193
+ Full business application with multiple interconnected entities.
194
+
195
+ Run examples locally:
196
+
197
+ \`\`\`bash
198
+ npx tsx examples/simple-example.ts
199
+ npx tsx examples/multi-level-keys.ts
200
+ npx tsx examples/enterprise-example.ts
201
+ \`\`\`
202
+
203
+ Each example includes detailed documentation and demonstrates different aspects of the Client API.`
204
+
205
+ case 'api-reference':
206
+ return `# API Reference
207
+
208
+ Complete reference for all Client API interfaces and methods.
209
+
210
+ ## PItemApi<V, S>
211
+
212
+ Primary Item API for independent entities.
213
+
214
+ ### Methods
215
+
216
+ - \`all(query?: ItemQuery): Promise<V[]>\`
217
+ - \`create(item: Partial<Item<S>>): Promise<V>\`
218
+ - \`get(key: PriKey<S>): Promise<V>\`
219
+ - \`update(key: PriKey<S>, updates: Partial<Item<S>>): Promise<V>\`
220
+ - \`remove(key: PriKey<S>): Promise<boolean>\`
221
+ - \`action(key: PriKey<S>, action: string, body?: any): Promise<any>\`
222
+ - \`facet(key: PriKey<S>, facet: string, params?: any): Promise<any>\`
223
+
224
+ ## CItemApi<V, S, L1, L2, L3, L4, L5>
225
+
226
+ Contained Item API for hierarchical entities.
227
+
228
+ ### Methods
229
+
230
+ All PItemApi methods plus location-aware variants:
231
+
232
+ - \`all(query: ItemQuery, locations?: LocKeyArray<L1, L2, L3, L4, L5>): Promise<V[]>\`
233
+ - \`create(item: Partial<Item<S>>, locations?: LocKeyArray<L1, L2, L3, L4, L5>): Promise<V>\`
234
+
235
+ And more...`
236
+
237
+ default:
238
+ return `# ${sectionId}
239
+
240
+ Documentation for this section is being prepared.`
241
+ }
242
+ }
243
+
244
+ const currentContent = documents[currentSection] || ''
245
+
246
+ const toggleSidebar = () => {
247
+ setSidebarOpen(!sidebarOpen)
248
+ }
249
+
250
+ return (
251
+ <div className="app">
252
+ <header className="header">
253
+ <div className="header-container">
254
+ <div className="brand">
255
+ <h1 className="brand-title">
256
+ <span className="brand-fjell">Fjell</span>{' '}
257
+ <span className="brand-client-api">Client API</span>
258
+ </h1>
259
+ <p className="brand-tagline">
260
+ HTTP client library for modern applications
261
+ </p>
262
+ </div>
263
+
264
+ <div className="header-actions">
265
+ <a
266
+ href="https://github.com/getfjell/client-api"
267
+ className="header-link"
268
+ target="_blank"
269
+ rel="noopener noreferrer"
270
+ >
271
+ GitHub
272
+ </a>
273
+ <a
274
+ href={`https://www.npmjs.com/package/@fjell/client-api/v/${version}`}
275
+ className="version-badge"
276
+ target="_blank"
277
+ rel="noopener noreferrer"
278
+ >
279
+ v{version}
280
+ </a>
281
+ <button className="menu-toggle" onClick={toggleSidebar}>
282
+ <div className="menu-line"></div>
283
+ <div className="menu-line"></div>
284
+ <div className="menu-line"></div>
285
+ </button>
286
+ </div>
287
+ </div>
288
+ </header>
289
+
290
+ <div className="layout">
291
+ <aside className={`sidebar ${sidebarOpen ? 'sidebar-open' : ''}`}>
292
+ <nav className="nav-content">
293
+ <div className="nav-sections">
294
+ {documentSections.map((section) => (
295
+ <button
296
+ key={section.id}
297
+ className={`nav-item ${currentSection === section.id ? 'active' : ''}`}
298
+ onClick={() => {
299
+ setCurrentSection(section.id)
300
+ setSidebarOpen(false)
301
+ }}
302
+ >
303
+ <div className="nav-item-content">
304
+ <div className="nav-item-title">{section.title}</div>
305
+ <div className="nav-item-subtitle">{section.subtitle}</div>
306
+ </div>
307
+ </button>
308
+ ))}
309
+ </div>
310
+ <img
311
+ src="/client-api/fjell-icon.svg"
312
+ alt="Fjell"
313
+ className="fjell-logo"
314
+ />
315
+ </nav>
316
+ </aside>
317
+
318
+ <main className="main">
319
+ <div className="content-container">
320
+ {loading ? (
321
+ <div className="loading">
322
+ <div className="loading-animation">
323
+ <div className="loading-dot"></div>
324
+ <div className="loading-dot"></div>
325
+ <div className="loading-dot"></div>
326
+ </div>
327
+ <div className="loading-text">Loading Client API Documentation</div>
328
+ </div>
329
+ ) : (
330
+ <div className="content-wrapper">
331
+ <div className="content-header">
332
+ <div className="breadcrumb">
333
+ <span className="breadcrumb-home">Fjell Client API</span>
334
+ <span className="breadcrumb-separator">•</span>
335
+ <span className="breadcrumb-current">
336
+ {documentSections.find(s => s.id === currentSection)?.title}
337
+ </span>
338
+ </div>
339
+ <div className="section-header">
340
+ <h1 className="content-title">
341
+ {documentSections.find(s => s.id === currentSection)?.title}
342
+ </h1>
343
+ <p className="content-subtitle">
344
+ {documentSections.find(s => s.id === currentSection)?.subtitle}
345
+ </p>
346
+ </div>
347
+ </div>
348
+
349
+ <div className="content">
350
+ {currentSection === 'examples' ? (
351
+ <div className="examples-content">
352
+ <ReactMarkdown
353
+ remarkPlugins={[remarkGfm]}
354
+ components={{
355
+ code({ className, children, ...props }: any) {
356
+ const match = /language-(\w+)/.exec(className || '')
357
+ return !props.inline && match ? (
358
+ <SyntaxHighlighter
359
+ style={oneLight as { [key: string]: React.CSSProperties }}
360
+ language={match[1]}
361
+ PreTag="div"
362
+ {...props}
363
+ >
364
+ {String(children).replace(/\n$/, '')}
365
+ </SyntaxHighlighter>
366
+ ) : (
367
+ <code className={className} {...props}>
368
+ {children}
369
+ </code>
370
+ )
371
+ },
372
+ h1({ children }) {
373
+ return <h1 className="content-h1">{children}</h1>
374
+ },
375
+ h2({ children }) {
376
+ return <h2 className="content-h2">{children}</h2>
377
+ },
378
+ h3({ children }) {
379
+ return <h3 className="content-h3">{children}</h3>
380
+ }
381
+ }}
382
+ >
383
+ {currentContent}
384
+ </ReactMarkdown>
385
+
386
+ <div className="examples-grid">
387
+ <div className="example-card">
388
+ <h3>Simple Example</h3>
389
+ <p>Basic CRUD operations with HTTP client patterns.</p>
390
+ <details>
391
+ <summary>View Code</summary>
392
+ <div className="example-code-block">
393
+ <SyntaxHighlighter
394
+ style={oneLight as { [key: string]: React.CSSProperties }}
395
+ language="bash"
396
+ PreTag="div"
397
+ >
398
+ npx tsx examples/simple-example.ts
399
+ </SyntaxHighlighter>
400
+ </div>
401
+ </details>
402
+ </div>
403
+
404
+ <div className="example-card">
405
+ <h3>Multi-Level Keys</h3>
406
+ <p>Complex hierarchical data models with nested relationships.</p>
407
+ <details>
408
+ <summary>View Code</summary>
409
+ <div className="example-code-block">
410
+ <SyntaxHighlighter
411
+ style={oneLight as { [key: string]: React.CSSProperties }}
412
+ language="bash"
413
+ PreTag="div"
414
+ >
415
+ npx tsx examples/multi-level-keys.ts
416
+ </SyntaxHighlighter>
417
+ </div>
418
+ </details>
419
+ </div>
420
+
421
+ <div className="example-card">
422
+ <h3>Enterprise Example</h3>
423
+ <p>Complete business application with e-commerce workflows.</p>
424
+ <details>
425
+ <summary>View Code</summary>
426
+ <div className="example-code-block">
427
+ <SyntaxHighlighter
428
+ style={oneLight as { [key: string]: React.CSSProperties }}
429
+ language="bash"
430
+ PreTag="div"
431
+ >
432
+ npx tsx examples/enterprise-example.ts
433
+ </SyntaxHighlighter>
434
+ </div>
435
+ </details>
436
+ </div>
437
+ </div>
438
+ </div>
439
+ ) : (
440
+ <ReactMarkdown
441
+ remarkPlugins={[remarkGfm]}
442
+ components={{
443
+ code({ className, children, ...props }: any) {
444
+ const match = /language-(\w+)/.exec(className || '')
445
+ return !props.inline && match ? (
446
+ <SyntaxHighlighter
447
+ style={oneLight as { [key: string]: React.CSSProperties }}
448
+ language={match[1]}
449
+ PreTag="div"
450
+ {...props}
451
+ >
452
+ {String(children).replace(/\n$/, '')}
453
+ </SyntaxHighlighter>
454
+ ) : (
455
+ <code className={className} {...props}>
456
+ {children}
457
+ </code>
458
+ )
459
+ },
460
+ h1({ children }) {
461
+ return <h1 className="content-h1">{children}</h1>
462
+ },
463
+ h2({ children }) {
464
+ return <h2 className="content-h2">{children}</h2>
465
+ },
466
+ h3({ children }) {
467
+ return <h3 className="content-h3">{children}</h3>
468
+ }
469
+ }}
470
+ >
471
+ {currentContent}
472
+ </ReactMarkdown>
473
+ )}
474
+ </div>
475
+
476
+ <div className="content-navigation">
477
+ {documentSections.map((section) => {
478
+ if (section.id === currentSection) return null
479
+ return (
480
+ <button
481
+ key={section.id}
482
+ className="nav-suggestion"
483
+ onClick={() => setCurrentSection(section.id)}
484
+ >
485
+ <span className="nav-suggestion-label">Next</span>
486
+ <span className="nav-suggestion-title">{section.title}</span>
487
+ </button>
488
+ )
489
+ }).filter(Boolean).slice(0, 1)}
490
+ </div>
491
+ </div>
492
+ )}
493
+ </div>
494
+ </main>
495
+ </div>
496
+
497
+ <footer className="footer">
498
+ <div className="footer-container">
499
+ <div className="footer-content">
500
+ <p className="footer-text">
501
+ Crafted with intention for the Fjell ecosystem
502
+ </p>
503
+ <p className="footer-license">
504
+ Licensed under Apache-2.0 &nbsp;•&nbsp; 2024
505
+ </p>
506
+ </div>
507
+ </div>
508
+ </footer>
509
+ </div>
510
+ )
511
+ }
512
+
513
+ export default App
@@ -0,0 +1,34 @@
1
+ :root {
2
+ --fjell-blue: #2563eb;
3
+ --fjell-blue-light: #3b82f6;
4
+ --fjell-gray: #374151;
5
+ --fjell-gray-light: #6b7280;
6
+ --fjell-background: #f9fafb;
7
+ --fjell-border: #e5e7eb;
8
+ }
9
+
10
+ * {
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ margin: 0;
16
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
17
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
18
+ sans-serif;
19
+ -webkit-font-smoothing: antialiased;
20
+ -moz-osx-font-smoothing: grayscale;
21
+ color: var(--fjell-gray);
22
+ background-color: var(--fjell-background);
23
+ line-height: 1.6;
24
+ }
25
+
26
+ code {
27
+ font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
28
+ background-color: #f1f5f9;
29
+ padding: 0.125rem 0.25rem;
30
+ border-radius: 0.25rem;
31
+ font-size: 0.875em;
32
+ }
33
+
34
+ /* Removed conflicting pre code styles - handled in App.css */
@@ -0,0 +1,10 @@
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import App from './App.tsx'
4
+ import './index.css'
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')!).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>,
10
+ )
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom'
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "allowSyntheticDefaultImports": true,
8
+ "strict": true,
9
+ "noEmit": true
10
+ },
11
+ "include": [
12
+ "vite.config.ts"
13
+ ]
14
+ }
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from 'vitest/config'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ test: {
7
+ globals: true,
8
+ environment: 'jsdom',
9
+ setupFiles: './src/test/setup.ts',
10
+ coverage: {
11
+ provider: 'v8'
12
+ }
13
+ },
14
+ })
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.7",
4
+ "version": "4.4.10",
5
5
  "keywords": [
6
6
  "client",
7
7
  "api",
@@ -17,19 +17,20 @@
17
17
  }
18
18
  },
19
19
  "dependencies": {
20
- "@fjell/core": "^4.4.7",
21
- "@fjell/http-api": "^4.4.5",
22
- "@fjell/logging": "^4.4.7",
23
- "@fjell/registry": "^4.4.7",
20
+ "@fjell/core": "^4.4.13",
21
+ "@fjell/http-api": "^4.4.15",
22
+ "@fjell/logging": "^4.4.13",
23
+ "@fjell/registry": "^4.4.11",
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
+ "@fjell/eslint-config": "^1.0.5",
29
30
  "@swc/core": "^1.13.1",
30
31
  "@tsconfig/recommended": "^1.0.10",
31
- "@typescript-eslint/eslint-plugin": "^8.37.0",
32
- "@typescript-eslint/parser": "^8.37.0",
32
+ "@typescript-eslint/eslint-plugin": "^8.38.0",
33
+ "@typescript-eslint/parser": "^8.38.0",
33
34
  "@vitest/coverage-istanbul": "^3.2.4",
34
35
  "@vitest/coverage-v8": "^3.2.4",
35
36
  "@vitest/ui": "^3.2.4",