@semiont/api-client 0.1.0-build.6

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.
Files changed (74) hide show
  1. package/README.md +85 -0
  2. package/dist/__tests__/client.test.d.ts +28 -0
  3. package/dist/__tests__/client.test.d.ts.map +1 -0
  4. package/dist/__tests__/client.test.js +567 -0
  5. package/dist/__tests__/client.test.js.map +1 -0
  6. package/dist/__tests__/sse-client.test.d.ts +7 -0
  7. package/dist/__tests__/sse-client.test.d.ts.map +1 -0
  8. package/dist/__tests__/sse-client.test.js +421 -0
  9. package/dist/__tests__/sse-client.test.js.map +1 -0
  10. package/dist/__tests__/sse-stream.test.d.ts +7 -0
  11. package/dist/__tests__/sse-stream.test.d.ts.map +1 -0
  12. package/dist/__tests__/sse-stream.test.js +394 -0
  13. package/dist/__tests__/sse-stream.test.js.map +1 -0
  14. package/dist/__tests__/svg-selectors.test.d.ts +5 -0
  15. package/dist/__tests__/svg-selectors.test.d.ts.map +1 -0
  16. package/dist/__tests__/svg-selectors.test.js +124 -0
  17. package/dist/__tests__/svg-selectors.test.js.map +1 -0
  18. package/dist/branded-types.d.ts +70 -0
  19. package/dist/branded-types.d.ts.map +1 -0
  20. package/dist/branded-types.js +62 -0
  21. package/dist/branded-types.js.map +1 -0
  22. package/dist/client.d.ts +243 -0
  23. package/dist/client.d.ts.map +1 -0
  24. package/dist/client.js +460 -0
  25. package/dist/client.js.map +1 -0
  26. package/dist/index.d.ts +43 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +62 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/mime-utils.d.ts +27 -0
  31. package/dist/mime-utils.d.ts.map +1 -0
  32. package/dist/mime-utils.js +49 -0
  33. package/dist/mime-utils.js.map +1 -0
  34. package/dist/sse/index.d.ts +343 -0
  35. package/dist/sse/index.d.ts.map +1 -0
  36. package/dist/sse/index.js +404 -0
  37. package/dist/sse/index.js.map +1 -0
  38. package/dist/sse/stream.d.ts +58 -0
  39. package/dist/sse/stream.d.ts.map +1 -0
  40. package/dist/sse/stream.js +187 -0
  41. package/dist/sse/stream.js.map +1 -0
  42. package/dist/sse/types.d.ts +295 -0
  43. package/dist/sse/types.d.ts.map +1 -0
  44. package/dist/sse/types.js +10 -0
  45. package/dist/sse/types.js.map +1 -0
  46. package/dist/types.d.ts +3177 -0
  47. package/dist/types.d.ts.map +1 -0
  48. package/dist/types.js +7 -0
  49. package/dist/types.js.map +1 -0
  50. package/dist/utils/annotations.d.ts +191 -0
  51. package/dist/utils/annotations.d.ts.map +1 -0
  52. package/dist/utils/annotations.js +404 -0
  53. package/dist/utils/annotations.js.map +1 -0
  54. package/dist/utils/events.d.ts +74 -0
  55. package/dist/utils/events.d.ts.map +1 -0
  56. package/dist/utils/events.js +329 -0
  57. package/dist/utils/events.js.map +1 -0
  58. package/dist/utils/index.d.ts +12 -0
  59. package/dist/utils/index.d.ts.map +1 -0
  60. package/dist/utils/index.js +28 -0
  61. package/dist/utils/index.js.map +1 -0
  62. package/dist/utils/locales.d.ts +31 -0
  63. package/dist/utils/locales.d.ts.map +1 -0
  64. package/dist/utils/locales.js +83 -0
  65. package/dist/utils/locales.js.map +1 -0
  66. package/dist/utils/resources.d.ts +34 -0
  67. package/dist/utils/resources.d.ts.map +1 -0
  68. package/dist/utils/resources.js +63 -0
  69. package/dist/utils/resources.js.map +1 -0
  70. package/dist/utils/validation.d.ts +57 -0
  71. package/dist/utils/validation.d.ts.map +1 -0
  72. package/dist/utils/validation.js +89 -0
  73. package/dist/utils/validation.js.map +1 -0
  74. package/package.json +65 -0
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # @semiont/api-client
2
+
3
+ TypeScript SDK for [Semiont](https://github.com/The-AI-Alliance/semiont) - a knowledge management system for semantic annotations, AI-powered entity detection, and collaborative document analysis.
4
+
5
+ ## What is Semiont?
6
+
7
+ Semiont lets you:
8
+
9
+ - **Store and manage documents** (text, markdown, code)
10
+ - **Create semantic annotations** using W3C Web Annotation standard
11
+ - **Link and reference** between documents
12
+ - **Track provenance** with event sourcing
13
+ - **Collaborate in real-time** via SSE streams
14
+ - **Detect entities** using AI (people, organizations, concepts)
15
+ - **Retrieve context** for LLMs via graph traversal
16
+ - **Generate new documents** from annotations with AI
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ npm install @semiont/api-client
22
+ ```
23
+
24
+ **Prerequisites**: Semiont backend running. See [Running the Backend](../../apps/backend/README.md#quick-start) for setup.
25
+
26
+ ## Quick Start
27
+
28
+ ```typescript
29
+ import { SemiontApiClient, baseUrl, email, resourceUri } from '@semiont/api-client';
30
+
31
+ const client = new SemiontApiClient({
32
+ baseUrl: baseUrl('http://localhost:4000'),
33
+ });
34
+
35
+ // Authenticate (local development mode)
36
+ await client.authenticateLocal(email('user@example.com'));
37
+
38
+ // Create a text document
39
+ const { resource } = await client.createResource({
40
+ name: 'My Document',
41
+ file: Buffer.from('The quick brown fox jumps over the lazy dog.'),
42
+ format: 'text/plain',
43
+ entityTypes: ['example']
44
+ });
45
+
46
+ console.log('Created resource:', resource['@id']);
47
+
48
+ // Detect entities with AI
49
+ const stream = client.sse.detectAnnotations(resourceUri(resource['@id']), {
50
+ entityTypes: ['Animal', 'Color']
51
+ });
52
+
53
+ stream.onProgress((p) => console.log(`Scanning: ${p.currentEntityType}`));
54
+ stream.onComplete((result) => console.log(`Found ${result.foundCount} entities`));
55
+
56
+ // Get annotations
57
+ const annotations = await client.getResourceAnnotations(resourceUri(resource['@id']));
58
+ console.log('Annotations:', annotations.annotations.length);
59
+ ```
60
+
61
+ ## Documentation
62
+
63
+ 📚 **[Usage Guide](./docs/Usage.md)** - Authentication, resources, annotations, SSE streaming
64
+
65
+ 📖 **[API Reference](./docs/API-Reference.md)** - Complete method documentation
66
+
67
+ ## Key Features
68
+
69
+ - **Type-safe** - Generated from OpenAPI spec with branded types
70
+ - **W3C compliant** - Web Annotation standard
71
+ - **Real-time** - SSE streaming for long operations
72
+ - **Framework-agnostic** - Works everywhere JavaScript runs
73
+
74
+ ## Use Cases
75
+
76
+ - ✅ MCP servers and AI integrations
77
+ - ✅ Frontend applications (wrap with React hooks)
78
+ - ✅ CLI tools and automation scripts
79
+ - ✅ Third-party integrations
80
+
81
+ ❌ **Not for backend internal code** - Use [`@semiont/core`](../core/) instead
82
+
83
+ ## License
84
+
85
+ Apache-2.0
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Unit tests for SemiontApiClient with mocked HTTP
3
+ */
4
+ export {};
5
+ /**
6
+ * Example usage documentation
7
+ *
8
+ * Archive a resource:
9
+ * ```typescript
10
+ * await client.updateResource(resourceUri, { archived: true });
11
+ * ```
12
+ *
13
+ * Unarchive a resource:
14
+ * ```typescript
15
+ * await client.updateResource(resourceUri, { archived: false });
16
+ * ```
17
+ *
18
+ * List active resources only:
19
+ * ```typescript
20
+ * const active = await client.listResources(20, false);
21
+ * ```
22
+ *
23
+ * List archived resources only:
24
+ * ```typescript
25
+ * const archived = await client.listResources(20, true);
26
+ * ```
27
+ */
28
+ //# sourceMappingURL=client.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/client.test.ts"],"names":[],"mappings":"AAAA;;GAEG;;AA+pBH;;;;;;;;;;;;;;;;;;;;;;GAsBG"}
@@ -0,0 +1,567 @@
1
+ "use strict";
2
+ /**
3
+ * Unit tests for SemiontApiClient with mocked HTTP
4
+ */
5
+ var __importDefault = (this && this.__importDefault) || function (mod) {
6
+ return (mod && mod.__esModule) ? mod : { "default": mod };
7
+ };
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ const vitest_1 = require("vitest");
10
+ // Mock ky module
11
+ vitest_1.vi.mock('ky', () => ({
12
+ default: {
13
+ create: vitest_1.vi.fn(),
14
+ },
15
+ }));
16
+ const ky_1 = __importDefault(require("ky"));
17
+ const client_1 = require("../client");
18
+ const branded_types_1 = require("../branded-types");
19
+ (0, vitest_1.describe)('SemiontApiClient - Archive Operations', () => {
20
+ let client;
21
+ let mockKy;
22
+ const testBaseUrl = (0, branded_types_1.baseUrl)('http://localhost:4000');
23
+ const testResourceUri = `${testBaseUrl}/resources/test-resource-id`;
24
+ (0, vitest_1.beforeEach)(() => {
25
+ // Create mock ky instance with chainable methods
26
+ mockKy = {
27
+ get: vitest_1.vi.fn(),
28
+ post: vitest_1.vi.fn(),
29
+ patch: vitest_1.vi.fn(),
30
+ put: vitest_1.vi.fn(),
31
+ delete: vitest_1.vi.fn(),
32
+ };
33
+ // Mock ky.create to return our mock instance
34
+ vitest_1.vi.mocked(ky_1.default.create).mockReturnValue(mockKy);
35
+ client = new client_1.SemiontApiClient({
36
+ baseUrl: testBaseUrl,
37
+ timeout: 10000,
38
+ });
39
+ });
40
+ (0, vitest_1.describe)('updateResource - archive operations', () => {
41
+ (0, vitest_1.test)('should archive a resource', async () => {
42
+ const mockResponse = {
43
+ resource: {
44
+ id: 'test-resource-id',
45
+ name: 'Test Resource',
46
+ archived: true,
47
+ entityTypes: [],
48
+ },
49
+ annotations: [],
50
+ entityReferences: [],
51
+ };
52
+ vitest_1.vi.mocked(mockKy.patch).mockReturnValue({
53
+ json: vitest_1.vi.fn().mockResolvedValue(mockResponse),
54
+ });
55
+ const result = await client.updateResource(testResourceUri, {
56
+ archived: true,
57
+ });
58
+ (0, vitest_1.expect)(result.resource.archived).toBe(true);
59
+ (0, vitest_1.expect)(mockKy.patch).toHaveBeenCalledWith(testResourceUri, vitest_1.expect.objectContaining({
60
+ json: { archived: true },
61
+ }));
62
+ });
63
+ (0, vitest_1.test)('should unarchive a resource', async () => {
64
+ const mockResponse = {
65
+ resource: {
66
+ id: 'test-resource-id',
67
+ name: 'Test Resource',
68
+ archived: false,
69
+ entityTypes: [],
70
+ },
71
+ annotations: [],
72
+ entityReferences: [],
73
+ };
74
+ vitest_1.vi.mocked(mockKy.patch).mockReturnValue({
75
+ json: vitest_1.vi.fn().mockResolvedValue(mockResponse),
76
+ });
77
+ const result = await client.updateResource(testResourceUri, {
78
+ archived: false,
79
+ });
80
+ (0, vitest_1.expect)(result.resource.archived).toBe(false);
81
+ (0, vitest_1.expect)(mockKy.patch).toHaveBeenCalledWith(testResourceUri, vitest_1.expect.objectContaining({
82
+ json: { archived: false },
83
+ }));
84
+ });
85
+ (0, vitest_1.test)('should update entity types and archive in single operation', async () => {
86
+ const mockResponse = {
87
+ resource: {
88
+ id: 'test-resource-id',
89
+ name: 'Test Resource',
90
+ archived: true,
91
+ entityTypes: ['article', 'draft'],
92
+ },
93
+ annotations: [],
94
+ entityReferences: [],
95
+ };
96
+ vitest_1.vi.mocked(mockKy.patch).mockReturnValue({
97
+ json: vitest_1.vi.fn().mockResolvedValue(mockResponse),
98
+ });
99
+ const result = await client.updateResource(testResourceUri, {
100
+ archived: true,
101
+ entityTypes: ['article', 'draft'],
102
+ });
103
+ (0, vitest_1.expect)(result.resource.archived).toBe(true);
104
+ (0, vitest_1.expect)(result.resource.entityTypes).toEqual(['article', 'draft']);
105
+ (0, vitest_1.expect)(mockKy.patch).toHaveBeenCalledWith(testResourceUri, vitest_1.expect.objectContaining({
106
+ json: {
107
+ archived: true,
108
+ entityTypes: ['article', 'draft'],
109
+ },
110
+ }));
111
+ });
112
+ });
113
+ (0, vitest_1.describe)('listResources - filter by archived', () => {
114
+ (0, vitest_1.test)('should list only active resources', async () => {
115
+ const mockResponse = {
116
+ resources: [
117
+ { id: 'res1', name: 'Active 1', archived: false },
118
+ { id: 'res2', name: 'Active 2', archived: false },
119
+ ],
120
+ total: 2,
121
+ };
122
+ vitest_1.vi.mocked(mockKy.get).mockReturnValue({
123
+ json: vitest_1.vi.fn().mockResolvedValue(mockResponse),
124
+ });
125
+ const result = await client.listResources(10, false);
126
+ (0, vitest_1.expect)(result.resources).toHaveLength(2);
127
+ (0, vitest_1.expect)(result.resources.every(r => !r.archived)).toBe(true);
128
+ (0, vitest_1.expect)(mockKy.get).toHaveBeenCalledWith(`${testBaseUrl}/resources`, vitest_1.expect.objectContaining({
129
+ searchParams: vitest_1.expect.any(URLSearchParams),
130
+ }));
131
+ });
132
+ (0, vitest_1.test)('should list only archived resources', async () => {
133
+ const mockResponse = {
134
+ resources: [
135
+ { id: 'res3', name: 'Archived 1', archived: true },
136
+ ],
137
+ total: 1,
138
+ };
139
+ vitest_1.vi.mocked(mockKy.get).mockReturnValue({
140
+ json: vitest_1.vi.fn().mockResolvedValue(mockResponse),
141
+ });
142
+ const result = await client.listResources(10, true);
143
+ (0, vitest_1.expect)(result.resources).toHaveLength(1);
144
+ (0, vitest_1.expect)(result.resources.every(r => r.archived)).toBe(true);
145
+ (0, vitest_1.expect)(mockKy.get).toHaveBeenCalledWith(`${testBaseUrl}/resources`, vitest_1.expect.objectContaining({
146
+ searchParams: vitest_1.expect.any(URLSearchParams),
147
+ }));
148
+ });
149
+ });
150
+ (0, vitest_1.describe)('Entity Detection and Jobs', () => {
151
+ (0, vitest_1.test)('should get job status', async () => {
152
+ const mockResponse = {
153
+ jobId: 'job-123',
154
+ type: 'detection',
155
+ status: 'running',
156
+ userId: 'user-1',
157
+ created: '2024-01-01T00:00:00Z',
158
+ progress: {
159
+ current: 50,
160
+ total: 100,
161
+ message: 'Processing entities...',
162
+ },
163
+ };
164
+ vitest_1.vi.mocked(mockKy.get).mockReturnValue({
165
+ json: vitest_1.vi.fn().mockResolvedValue(mockResponse),
166
+ });
167
+ const result = await client.getJobStatus((0, branded_types_1.jobId)('job-123'));
168
+ (0, vitest_1.expect)(result.status).toBe('running');
169
+ (0, vitest_1.expect)(result.jobId).toBe('job-123');
170
+ (0, vitest_1.expect)(mockKy.get).toHaveBeenCalledWith(`${testBaseUrl}/api/jobs/job-123`);
171
+ });
172
+ (0, vitest_1.test)('should poll job until complete', async () => {
173
+ const responses = [
174
+ { jobId: 'job-123', status: 'pending', type: 'detection', created: '2024-01-01T00:00:00Z', userId: 'user-1' },
175
+ { jobId: 'job-123', status: 'running', type: 'detection', created: '2024-01-01T00:00:00Z', userId: 'user-1' },
176
+ { jobId: 'job-123', status: 'complete', type: 'detection', created: '2024-01-01T00:00:00Z', userId: 'user-1', result: { detected: 5 } },
177
+ ];
178
+ let callCount = 0;
179
+ vitest_1.vi.mocked(mockKy.get).mockImplementation(() => ({
180
+ json: vitest_1.vi.fn().mockResolvedValue(responses[callCount++]),
181
+ }));
182
+ const progressCalls = [];
183
+ const result = await client.pollJobUntilComplete((0, branded_types_1.jobId)('job-123'), {
184
+ interval: 10, // Fast polling for tests
185
+ onProgress: (status) => progressCalls.push(status),
186
+ });
187
+ (0, vitest_1.expect)(result.status).toBe('complete');
188
+ (0, vitest_1.expect)(progressCalls).toHaveLength(3);
189
+ (0, vitest_1.expect)(progressCalls[0].status).toBe('pending');
190
+ (0, vitest_1.expect)(progressCalls[1].status).toBe('running');
191
+ (0, vitest_1.expect)(progressCalls[2].status).toBe('complete');
192
+ });
193
+ (0, vitest_1.test)('should timeout when polling takes too long', async () => {
194
+ vitest_1.vi.mocked(mockKy.get).mockReturnValue({
195
+ json: vitest_1.vi.fn().mockResolvedValue({
196
+ jobId: 'job-123',
197
+ status: 'running',
198
+ type: 'detection',
199
+ created: '2024-01-01T00:00:00Z',
200
+ userId: 'user-1',
201
+ }),
202
+ });
203
+ await (0, vitest_1.expect)(client.pollJobUntilComplete((0, branded_types_1.jobId)('job-123'), {
204
+ interval: 10,
205
+ timeout: 50, // Very short timeout for testing
206
+ })).rejects.toThrow('Job polling timeout after 50ms');
207
+ });
208
+ });
209
+ (0, vitest_1.describe)('LLM Context Operations', () => {
210
+ (0, vitest_1.test)('should get resource LLM context with default options', async () => {
211
+ const mockResponse = {
212
+ mainResource: {
213
+ '@context': 'http://www.w3.org/ns/anno.jsonld',
214
+ '@id': 'test-resource-id',
215
+ name: 'Test Resource',
216
+ content: 'Full content here',
217
+ },
218
+ relatedResources: [],
219
+ annotations: [],
220
+ graph: { nodes: [], edges: [] },
221
+ };
222
+ vitest_1.vi.mocked(mockKy.get).mockReturnValue({
223
+ json: vitest_1.vi.fn().mockResolvedValue(mockResponse),
224
+ });
225
+ const result = await client.getResourceLLMContext(testResourceUri);
226
+ (0, vitest_1.expect)(result.mainResource.name).toBe('Test Resource');
227
+ (0, vitest_1.expect)(mockKy.get).toHaveBeenCalledWith(`${testResourceUri}/llm-context`, vitest_1.expect.objectContaining({
228
+ searchParams: vitest_1.expect.any(URLSearchParams),
229
+ }));
230
+ });
231
+ (0, vitest_1.test)('should get resource LLM context with custom options', async () => {
232
+ const mockResponse = {
233
+ mainResource: {
234
+ '@context': 'http://www.w3.org/ns/anno.jsonld',
235
+ '@id': 'test-resource-id',
236
+ name: 'Test Resource',
237
+ },
238
+ relatedResources: [],
239
+ annotations: [],
240
+ graph: { nodes: [], edges: [] },
241
+ };
242
+ vitest_1.vi.mocked(mockKy.get).mockReturnValue({
243
+ json: vitest_1.vi.fn().mockResolvedValue(mockResponse),
244
+ });
245
+ const result = await client.getResourceLLMContext(testResourceUri, {
246
+ depth: 3,
247
+ maxResources: 15,
248
+ includeContent: true,
249
+ includeSummary: true,
250
+ });
251
+ (0, vitest_1.expect)(result.mainResource).toBeDefined();
252
+ const call = vitest_1.vi.mocked(mockKy.get).mock.calls[0];
253
+ const searchParams = call[1]?.searchParams;
254
+ (0, vitest_1.expect)(searchParams.get('depth')).toBe('3');
255
+ (0, vitest_1.expect)(searchParams.get('maxResources')).toBe('15');
256
+ (0, vitest_1.expect)(searchParams.get('includeContent')).toBe('true');
257
+ (0, vitest_1.expect)(searchParams.get('includeSummary')).toBe('true');
258
+ });
259
+ (0, vitest_1.test)('should get annotation LLM context with default options', async () => {
260
+ const annotationId = 'ann-123';
261
+ const mockResponse = {
262
+ annotation: {
263
+ '@context': 'http://www.w3.org/ns/anno.jsonld',
264
+ type: 'Annotation',
265
+ id: 'ann-123',
266
+ motivation: 'highlighting',
267
+ target: { source: testResourceUri },
268
+ body: [],
269
+ },
270
+ sourceResource: {
271
+ '@context': 'http://www.w3.org/ns/anno.jsonld',
272
+ '@id': 'source-id',
273
+ name: 'Source Resource',
274
+ },
275
+ targetResource: null,
276
+ sourceContext: {
277
+ before: 'Text before',
278
+ selected: 'Selected text',
279
+ after: 'Text after',
280
+ },
281
+ };
282
+ vitest_1.vi.mocked(mockKy.get).mockReturnValue({
283
+ json: vitest_1.vi.fn().mockResolvedValue(mockResponse),
284
+ });
285
+ const result = await client.getAnnotationLLMContext(testResourceUri, annotationId);
286
+ (0, vitest_1.expect)(result.annotation.id).toBe('ann-123');
287
+ (0, vitest_1.expect)(mockKy.get).toHaveBeenCalledWith(`${testResourceUri}/annotations/${annotationId}/llm-context`, vitest_1.expect.objectContaining({
288
+ searchParams: vitest_1.expect.any(URLSearchParams),
289
+ }));
290
+ });
291
+ (0, vitest_1.test)('should get annotation LLM context with custom options', async () => {
292
+ const annotationId = 'ann-123';
293
+ const mockResponse = {
294
+ annotation: {
295
+ '@context': 'http://www.w3.org/ns/anno.jsonld',
296
+ type: 'Annotation',
297
+ id: 'ann-123',
298
+ motivation: 'linking',
299
+ target: { source: testResourceUri },
300
+ body: [],
301
+ },
302
+ sourceResource: {
303
+ '@context': 'http://www.w3.org/ns/anno.jsonld',
304
+ '@id': 'source-id',
305
+ name: 'Source',
306
+ },
307
+ targetResource: null,
308
+ sourceContext: {
309
+ before: 'Limited',
310
+ selected: 'text',
311
+ after: 'here',
312
+ },
313
+ };
314
+ vitest_1.vi.mocked(mockKy.get).mockReturnValue({
315
+ json: vitest_1.vi.fn().mockResolvedValue(mockResponse),
316
+ });
317
+ const result = await client.getAnnotationLLMContext(testResourceUri, annotationId, {
318
+ contextWindow: 500,
319
+ });
320
+ (0, vitest_1.expect)(result.sourceContext).toBeDefined();
321
+ const call = vitest_1.vi.mocked(mockKy.get).mock.calls[0];
322
+ const searchParams = call[1]?.searchParams;
323
+ (0, vitest_1.expect)(searchParams.get('contextWindow')).toBe('500');
324
+ });
325
+ });
326
+ (0, vitest_1.describe)('User Operations', () => {
327
+ (0, vitest_1.test)('should logout user', async () => {
328
+ const mockResponse = {
329
+ message: 'Logged out successfully',
330
+ };
331
+ vitest_1.vi.mocked(mockKy.post).mockReturnValue({
332
+ json: vitest_1.vi.fn().mockResolvedValue(mockResponse),
333
+ });
334
+ const result = await client.logout();
335
+ (0, vitest_1.expect)(result.message).toBe('Logged out successfully');
336
+ (0, vitest_1.expect)(mockKy.post).toHaveBeenCalledWith(`${testBaseUrl}/api/users/logout`);
337
+ });
338
+ });
339
+ (0, vitest_1.describe)('System Status', () => {
340
+ (0, vitest_1.test)('should get system status', async () => {
341
+ const mockResponse = {
342
+ status: 'healthy',
343
+ version: '1.0.0',
344
+ features: {
345
+ semanticContent: 'enabled',
346
+ collaboration: 'enabled',
347
+ rbac: 'disabled',
348
+ },
349
+ };
350
+ vitest_1.vi.mocked(mockKy.get).mockReturnValue({
351
+ json: vitest_1.vi.fn().mockResolvedValue(mockResponse),
352
+ });
353
+ const result = await client.getStatus();
354
+ (0, vitest_1.expect)(result.version).toBe('1.0.0');
355
+ (0, vitest_1.expect)(result.features.semanticContent).toBe('enabled');
356
+ (0, vitest_1.expect)(mockKy.get).toHaveBeenCalledWith(`${testBaseUrl}/api/status`);
357
+ });
358
+ });
359
+ (0, vitest_1.describe)('Entity Types Bulk Operations', () => {
360
+ (0, vitest_1.test)('should add multiple entity types at once', async () => {
361
+ const mockResponse = {
362
+ success: true,
363
+ entityTypes: ['concept', 'person', 'organization'],
364
+ };
365
+ vitest_1.vi.mocked(mockKy.post).mockReturnValue({
366
+ json: vitest_1.vi.fn().mockResolvedValue(mockResponse),
367
+ });
368
+ const result = await client.addEntityTypesBulk([(0, branded_types_1.entityType)('concept'), (0, branded_types_1.entityType)('person'), (0, branded_types_1.entityType)('organization')]);
369
+ (0, vitest_1.expect)(result.success).toBe(true);
370
+ (0, vitest_1.expect)(result.entityTypes).toHaveLength(3);
371
+ (0, vitest_1.expect)(mockKy.post).toHaveBeenCalledWith(`${testBaseUrl}/api/entity-types/bulk`, vitest_1.expect.objectContaining({
372
+ json: { tags: [(0, branded_types_1.entityType)('concept'), (0, branded_types_1.entityType)('person'), (0, branded_types_1.entityType)('organization')] },
373
+ }));
374
+ });
375
+ });
376
+ (0, vitest_1.describe)('Annotation History', () => {
377
+ (0, vitest_1.test)('should get annotation event history', async () => {
378
+ const annotationUri = `${testResourceUri}/annotations/ann-123`;
379
+ const mockResponse = {
380
+ events: [
381
+ {
382
+ id: 'evt-1',
383
+ type: 'highlight.created',
384
+ timestamp: '2024-01-01T00:00:00Z',
385
+ userId: 'user-1',
386
+ resourceId: 'test-resource-id',
387
+ payload: { highlightId: 'ann-123' },
388
+ metadata: {
389
+ sequenceNumber: 1,
390
+ prevEventHash: 'hash-0',
391
+ checksum: 'checksum-1',
392
+ },
393
+ },
394
+ {
395
+ id: 'evt-2',
396
+ type: 'highlight.updated',
397
+ timestamp: '2024-01-01T00:01:00Z',
398
+ userId: 'user-1',
399
+ resourceId: 'test-resource-id',
400
+ payload: { highlightId: 'ann-123' },
401
+ metadata: {
402
+ sequenceNumber: 2,
403
+ prevEventHash: 'hash-1',
404
+ checksum: 'checksum-2',
405
+ },
406
+ },
407
+ ],
408
+ total: 2,
409
+ annotationId: 'ann-123',
410
+ resourceId: 'test-resource-id',
411
+ };
412
+ vitest_1.vi.mocked(mockKy.get).mockReturnValue({
413
+ json: vitest_1.vi.fn().mockResolvedValue(mockResponse),
414
+ });
415
+ const result = await client.getAnnotationHistory(annotationUri);
416
+ (0, vitest_1.expect)(result.total).toBe(2);
417
+ (0, vitest_1.expect)(result.events).toHaveLength(2);
418
+ (0, vitest_1.expect)(result.annotationId).toBe('ann-123');
419
+ (0, vitest_1.expect)(result.events[0].metadata.sequenceNumber).toBe(1);
420
+ (0, vitest_1.expect)(mockKy.get).toHaveBeenCalledWith(`${annotationUri}/history`);
421
+ });
422
+ });
423
+ (0, vitest_1.describe)('W3C Content Negotiation', () => {
424
+ (0, vitest_1.test)('should get resource representation with default accept header', async () => {
425
+ const mockText = '# Hello World\n\nThis is markdown content.';
426
+ const mockBuffer = new TextEncoder().encode(mockText).buffer;
427
+ vitest_1.vi.mocked(mockKy.get).mockReturnValue({
428
+ headers: {
429
+ get: vitest_1.vi.fn((header) => header === 'content-type' ? 'text/plain' : null)
430
+ },
431
+ arrayBuffer: vitest_1.vi.fn().mockResolvedValue(mockBuffer),
432
+ });
433
+ const result = await client.getResourceRepresentation(testResourceUri);
434
+ (0, vitest_1.expect)(result.data).toBeInstanceOf(ArrayBuffer);
435
+ (0, vitest_1.expect)(result.contentType).toBe('text/plain');
436
+ (0, vitest_1.expect)(new TextDecoder().decode(result.data)).toBe(mockText);
437
+ (0, vitest_1.expect)(mockKy.get).toHaveBeenCalledWith(testResourceUri, vitest_1.expect.objectContaining({
438
+ headers: {
439
+ Accept: 'text/plain',
440
+ },
441
+ }));
442
+ });
443
+ (0, vitest_1.test)('should get resource representation with custom accept header', async () => {
444
+ const mockMarkdown = '# Title\n\n## Section\n\nContent here.';
445
+ const mockBuffer = new TextEncoder().encode(mockMarkdown).buffer;
446
+ vitest_1.vi.mocked(mockKy.get).mockReturnValue({
447
+ headers: {
448
+ get: vitest_1.vi.fn((header) => header === 'content-type' ? 'text/markdown' : null)
449
+ },
450
+ arrayBuffer: vitest_1.vi.fn().mockResolvedValue(mockBuffer),
451
+ });
452
+ const result = await client.getResourceRepresentation(testResourceUri, {
453
+ accept: 'text/markdown',
454
+ });
455
+ (0, vitest_1.expect)(result.data).toBeInstanceOf(ArrayBuffer);
456
+ (0, vitest_1.expect)(result.contentType).toBe('text/markdown');
457
+ (0, vitest_1.expect)(new TextDecoder().decode(result.data)).toBe(mockMarkdown);
458
+ (0, vitest_1.expect)(mockKy.get).toHaveBeenCalledWith(testResourceUri, vitest_1.expect.objectContaining({
459
+ headers: {
460
+ Accept: 'text/markdown',
461
+ },
462
+ }));
463
+ });
464
+ (0, vitest_1.test)('should get resource representation with text/plain', async () => {
465
+ const mockText = 'Hello World';
466
+ const mockBuffer = new TextEncoder().encode(mockText).buffer;
467
+ vitest_1.vi.mocked(mockKy.get).mockReturnValue({
468
+ headers: {
469
+ get: vitest_1.vi.fn((header) => header === 'content-type' ? 'text/plain; charset=utf-8' : null)
470
+ },
471
+ arrayBuffer: vitest_1.vi.fn().mockResolvedValue(mockBuffer),
472
+ });
473
+ const result = await client.getResourceRepresentation(testResourceUri, {
474
+ accept: 'text/plain',
475
+ });
476
+ (0, vitest_1.expect)(result.data).toBeInstanceOf(ArrayBuffer);
477
+ (0, vitest_1.expect)(result.contentType).toBe('text/plain; charset=utf-8');
478
+ (0, vitest_1.expect)(new TextDecoder().decode(result.data)).toBe(mockText);
479
+ (0, vitest_1.expect)(mockKy.get).toHaveBeenCalledWith(testResourceUri, vitest_1.expect.objectContaining({
480
+ headers: {
481
+ Accept: 'text/plain',
482
+ },
483
+ }));
484
+ });
485
+ });
486
+ (0, vitest_1.describe)('Streaming Content Negotiation', () => {
487
+ (0, vitest_1.test)('should get resource representation as stream', async () => {
488
+ const mockData = new Uint8Array([1, 2, 3, 4, 5]);
489
+ const mockStream = new ReadableStream({
490
+ start(controller) {
491
+ controller.enqueue(mockData);
492
+ controller.close();
493
+ }
494
+ });
495
+ vitest_1.vi.mocked(mockKy.get).mockReturnValue({
496
+ headers: {
497
+ get: vitest_1.vi.fn((header) => header === 'content-type' ? 'video/mp4' : null)
498
+ },
499
+ body: mockStream,
500
+ });
501
+ const result = await client.getResourceRepresentationStream(testResourceUri, {
502
+ accept: 'video/mp4',
503
+ });
504
+ (0, vitest_1.expect)(result.stream).toBeInstanceOf(ReadableStream);
505
+ (0, vitest_1.expect)(result.contentType).toBe('video/mp4');
506
+ (0, vitest_1.expect)(mockKy.get).toHaveBeenCalledWith(testResourceUri, vitest_1.expect.objectContaining({
507
+ headers: {
508
+ Accept: 'video/mp4',
509
+ },
510
+ }));
511
+ // Verify stream can be consumed
512
+ const reader = result.stream.getReader();
513
+ const { value, done } = await reader.read();
514
+ (0, vitest_1.expect)(done).toBe(false);
515
+ (0, vitest_1.expect)(value).toEqual(mockData);
516
+ });
517
+ (0, vitest_1.test)('should throw error if response body is null', async () => {
518
+ vitest_1.vi.mocked(mockKy.get).mockReturnValue({
519
+ headers: {
520
+ get: vitest_1.vi.fn(() => 'text/plain')
521
+ },
522
+ body: null,
523
+ });
524
+ await (0, vitest_1.expect)(client.getResourceRepresentationStream(testResourceUri)).rejects.toThrow('Response body is null - cannot create stream');
525
+ });
526
+ (0, vitest_1.test)('should use default content type if header missing', async () => {
527
+ const mockStream = new ReadableStream({
528
+ start(controller) {
529
+ controller.close();
530
+ }
531
+ });
532
+ vitest_1.vi.mocked(mockKy.get).mockReturnValue({
533
+ headers: {
534
+ get: vitest_1.vi.fn(() => null)
535
+ },
536
+ body: mockStream,
537
+ });
538
+ const result = await client.getResourceRepresentationStream(testResourceUri);
539
+ (0, vitest_1.expect)(result.contentType).toBe('application/octet-stream');
540
+ (0, vitest_1.expect)(result.stream).toBeInstanceOf(ReadableStream);
541
+ });
542
+ });
543
+ });
544
+ /**
545
+ * Example usage documentation
546
+ *
547
+ * Archive a resource:
548
+ * ```typescript
549
+ * await client.updateResource(resourceUri, { archived: true });
550
+ * ```
551
+ *
552
+ * Unarchive a resource:
553
+ * ```typescript
554
+ * await client.updateResource(resourceUri, { archived: false });
555
+ * ```
556
+ *
557
+ * List active resources only:
558
+ * ```typescript
559
+ * const active = await client.listResources(20, false);
560
+ * ```
561
+ *
562
+ * List archived resources only:
563
+ * ```typescript
564
+ * const archived = await client.listResources(20, true);
565
+ * ```
566
+ */
567
+ //# sourceMappingURL=client.test.js.map