trilium-api 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,477 +0,0 @@
1
- import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
- import { createTriliumClient } from './client.js';
3
-
4
- // Mock fetch globally - openapi-fetch uses Request objects
5
- const mockFetch = vi.fn();
6
- globalThis.fetch = mockFetch;
7
-
8
- // Helper to create a proper Response mock
9
- function createMockResponse(body: any, status = 200, contentType = 'application/json') {
10
- return {
11
- ok: status >= 200 && status < 300,
12
- status,
13
- headers: new Headers({ 'content-type': contentType }),
14
- json: async () => body,
15
- text: async () => (typeof body === 'string' ? body : JSON.stringify(body)),
16
- blob: async () => new Blob([JSON.stringify(body)]),
17
- clone: function() { return this; },
18
- };
19
- }
20
-
21
- describe('createTriliumClient', () => {
22
- const config = {
23
- baseUrl: 'http://localhost:37840',
24
- apiKey: 'test-api-key',
25
- };
26
-
27
- beforeEach(() => {
28
- mockFetch.mockReset();
29
- });
30
-
31
- afterEach(() => {
32
- vi.restoreAllMocks();
33
- });
34
-
35
- describe('client creation', () => {
36
- it('should create a client with correct baseUrl', () => {
37
- const client = createTriliumClient(config);
38
- expect(client).toBeDefined();
39
- expect(typeof client.GET).toBe('function');
40
- expect(typeof client.POST).toBe('function');
41
- expect(typeof client.PATCH).toBe('function');
42
- expect(typeof client.DELETE).toBe('function');
43
- });
44
-
45
- it('should handle trailing slash in baseUrl', () => {
46
- const clientWithSlash = createTriliumClient({
47
- baseUrl: 'http://localhost:37840/',
48
- apiKey: 'test-api-key',
49
- });
50
- expect(clientWithSlash).toBeDefined();
51
- });
52
- });
53
-
54
- describe('GET /app-info', () => {
55
- it('should fetch app info', async () => {
56
- const mockAppInfo = {
57
- appVersion: '0.60.0',
58
- dbVersion: 220,
59
- syncVersion: 30,
60
- buildDate: '2024-01-01T00:00:00Z',
61
- buildRevision: 'abc123',
62
- dataDirectory: '/home/user/data',
63
- clipperProtocolVersion: 1,
64
- utcDateTime: '2024-01-01T12:00:00Z',
65
- };
66
-
67
- mockFetch.mockResolvedValueOnce(createMockResponse(mockAppInfo));
68
-
69
- const client = createTriliumClient(config);
70
- const { data, error } = await client.GET('/app-info');
71
-
72
- expect(error).toBeUndefined();
73
- expect(data).toEqual(mockAppInfo);
74
- expect(mockFetch).toHaveBeenCalledTimes(1);
75
-
76
- // Check the Request object
77
- const request = mockFetch.mock.calls[0]![0] as Request;
78
- expect(request.url).toBe('http://localhost:37840/etapi/app-info');
79
- expect(request.method).toBe('GET');
80
- });
81
- });
82
-
83
- describe('GET /notes/{noteId}', () => {
84
- it('should fetch a note by ID', async () => {
85
- const mockNote = {
86
- noteId: 'test123',
87
- title: 'Test Note',
88
- type: 'text',
89
- mime: 'text/html',
90
- isProtected: false,
91
- blobId: 'blob123',
92
- attributes: [],
93
- parentNoteIds: ['root'],
94
- childNoteIds: [],
95
- parentBranchIds: ['branch123'],
96
- childBranchIds: [],
97
- dateCreated: '2024-01-01 12:00:00.000+0000',
98
- dateModified: '2024-01-01 12:00:00.000+0000',
99
- utcDateCreated: '2024-01-01 12:00:00.000Z',
100
- utcDateModified: '2024-01-01 12:00:00.000Z',
101
- };
102
-
103
- mockFetch.mockResolvedValueOnce(createMockResponse(mockNote));
104
-
105
- const client = createTriliumClient(config);
106
- const { data, error } = await client.GET('/notes/{noteId}', {
107
- params: { path: { noteId: 'test123' } },
108
- });
109
-
110
- expect(error).toBeUndefined();
111
- expect(data).toEqual(mockNote);
112
-
113
- const request = mockFetch.mock.calls[0]![0] as Request;
114
- expect(request.url).toBe('http://localhost:37840/etapi/notes/test123');
115
- expect(request.method).toBe('GET');
116
- });
117
-
118
- it('should handle 404 error when note not found', async () => {
119
- const errorResponse = { status: 404, code: 'NOTE_NOT_FOUND', message: 'Note not found' };
120
- mockFetch.mockResolvedValueOnce(createMockResponse(errorResponse, 404));
121
-
122
- const client = createTriliumClient(config);
123
- const { data, error } = await client.GET('/notes/{noteId}', {
124
- params: { path: { noteId: 'nonexistent' } },
125
- });
126
-
127
- expect(data).toBeUndefined();
128
- expect(error).toBeDefined();
129
- });
130
- });
131
-
132
- describe('GET /notes (search)', () => {
133
- it('should search notes with query', async () => {
134
- const mockResults = {
135
- results: [
136
- { noteId: 'note1', title: 'Blog Post 1' },
137
- { noteId: 'note2', title: 'Blog Post 2' },
138
- ],
139
- };
140
-
141
- mockFetch.mockResolvedValueOnce(createMockResponse(mockResults));
142
-
143
- const client = createTriliumClient(config);
144
- const { data, error } = await client.GET('/notes', {
145
- params: { query: { search: '#blog' } },
146
- });
147
-
148
- expect(error).toBeUndefined();
149
- expect(data).toEqual(mockResults);
150
-
151
- const request = mockFetch.mock.calls[0]![0] as Request;
152
- expect(request.url).toContain('search=%23blog');
153
- });
154
-
155
- it('should search with limit parameter', async () => {
156
- mockFetch.mockResolvedValueOnce(createMockResponse({ results: [] }));
157
-
158
- const client = createTriliumClient(config);
159
- await client.GET('/notes', {
160
- params: { query: { search: 'test', limit: 10 } },
161
- });
162
-
163
- const request = mockFetch.mock.calls[0]![0] as Request;
164
- expect(request.url).toContain('limit=10');
165
- });
166
- });
167
-
168
- describe('POST /create-note', () => {
169
- it('should create a new note', async () => {
170
- const mockCreatedNote = {
171
- note: {
172
- noteId: 'newNote123',
173
- title: 'New Note',
174
- type: 'text',
175
- mime: 'text/html',
176
- isProtected: false,
177
- },
178
- branch: {
179
- branchId: 'branch456',
180
- noteId: 'newNote123',
181
- parentNoteId: 'root',
182
- notePosition: 10,
183
- isExpanded: false,
184
- },
185
- };
186
-
187
- mockFetch.mockResolvedValueOnce(createMockResponse(mockCreatedNote, 201));
188
-
189
- const client = createTriliumClient(config);
190
- const { data, error } = await client.POST('/create-note', {
191
- body: {
192
- parentNoteId: 'root',
193
- title: 'New Note',
194
- type: 'text',
195
- content: '<p>Hello World</p>',
196
- },
197
- });
198
-
199
- expect(error).toBeUndefined();
200
- expect(data).toEqual(mockCreatedNote);
201
-
202
- const request = mockFetch.mock.calls[0]![0] as Request;
203
- expect(request.url).toBe('http://localhost:37840/etapi/create-note');
204
- expect(request.method).toBe('POST');
205
- });
206
- });
207
-
208
- describe('PATCH /notes/{noteId}', () => {
209
- it('should patch a note', async () => {
210
- const mockUpdatedNote = {
211
- noteId: 'test123',
212
- title: 'Updated Title',
213
- type: 'text',
214
- mime: 'text/html',
215
- };
216
-
217
- mockFetch.mockResolvedValueOnce(createMockResponse(mockUpdatedNote));
218
-
219
- const client = createTriliumClient(config);
220
- const { data, error } = await client.PATCH('/notes/{noteId}', {
221
- params: { path: { noteId: 'test123' } },
222
- body: { title: 'Updated Title' },
223
- });
224
-
225
- expect(error).toBeUndefined();
226
- expect(data?.title).toBe('Updated Title');
227
-
228
- const request = mockFetch.mock.calls[0]![0] as Request;
229
- expect(request.url).toBe('http://localhost:37840/etapi/notes/test123');
230
- expect(request.method).toBe('PATCH');
231
- });
232
- });
233
-
234
- describe('DELETE /notes/{noteId}', () => {
235
- it('should delete a note', async () => {
236
- mockFetch.mockResolvedValueOnce(createMockResponse('', 204, 'text/plain'));
237
-
238
- const client = createTriliumClient(config);
239
- const { error } = await client.DELETE('/notes/{noteId}', {
240
- params: { path: { noteId: 'test123' } },
241
- });
242
-
243
- expect(error).toBeUndefined();
244
-
245
- const request = mockFetch.mock.calls[0]![0] as Request;
246
- expect(request.url).toBe('http://localhost:37840/etapi/notes/test123');
247
- expect(request.method).toBe('DELETE');
248
- });
249
- });
250
-
251
- describe('GET /notes/{noteId}/content', () => {
252
- it('should fetch note content', async () => {
253
- const mockContent = '<p>This is the note content</p>';
254
-
255
- mockFetch.mockResolvedValueOnce(createMockResponse(mockContent, 200, 'text/html'));
256
-
257
- const client = createTriliumClient(config);
258
- const { data, error } = await client.GET('/notes/{noteId}/content', {
259
- params: { path: { noteId: 'test123' } },
260
- });
261
-
262
- expect(error).toBeUndefined();
263
- // Content comes back as parsed
264
- expect(data).toBeDefined();
265
- });
266
- });
267
-
268
- describe('PUT /notes/{noteId}/content', () => {
269
- it('should update note content', async () => {
270
- mockFetch.mockResolvedValueOnce(createMockResponse('', 204, 'text/plain'));
271
-
272
- const client = createTriliumClient(config);
273
- const { error } = await client.PUT('/notes/{noteId}/content', {
274
- params: { path: { noteId: 'test123' } },
275
- body: '<p>Updated content</p>' as any,
276
- });
277
-
278
- expect(error).toBeUndefined();
279
-
280
- const request = mockFetch.mock.calls[0]![0] as Request;
281
- expect(request.url).toBe('http://localhost:37840/etapi/notes/test123/content');
282
- expect(request.method).toBe('PUT');
283
- });
284
- });
285
-
286
- describe('Branches API', () => {
287
- it('should create a branch', async () => {
288
- const mockBranch = {
289
- branchId: 'branch123',
290
- noteId: 'note123',
291
- parentNoteId: 'parent123',
292
- notePosition: 10,
293
- prefix: '',
294
- isExpanded: false,
295
- };
296
-
297
- mockFetch.mockResolvedValueOnce(createMockResponse(mockBranch, 201));
298
-
299
- const client = createTriliumClient(config);
300
- const { data, error } = await client.POST('/branches', {
301
- body: {
302
- noteId: 'note123',
303
- parentNoteId: 'parent123',
304
- },
305
- });
306
-
307
- expect(error).toBeUndefined();
308
- expect(data).toEqual(mockBranch);
309
- });
310
-
311
- it('should get a branch by ID', async () => {
312
- const mockBranch = {
313
- branchId: 'branch123',
314
- noteId: 'note123',
315
- parentNoteId: 'root',
316
- };
317
-
318
- mockFetch.mockResolvedValueOnce(createMockResponse(mockBranch));
319
-
320
- const client = createTriliumClient(config);
321
- const { data, error } = await client.GET('/branches/{branchId}', {
322
- params: { path: { branchId: 'branch123' } },
323
- });
324
-
325
- expect(error).toBeUndefined();
326
- expect(data).toEqual(mockBranch);
327
- });
328
-
329
- it('should delete a branch', async () => {
330
- mockFetch.mockResolvedValueOnce(createMockResponse('', 204, 'text/plain'));
331
-
332
- const client = createTriliumClient(config);
333
- const { error } = await client.DELETE('/branches/{branchId}', {
334
- params: { path: { branchId: 'branch123' } },
335
- });
336
-
337
- expect(error).toBeUndefined();
338
- });
339
- });
340
-
341
- describe('Attributes API', () => {
342
- it('should create an attribute', async () => {
343
- const mockAttribute = {
344
- attributeId: 'attr123',
345
- noteId: 'note123',
346
- type: 'label',
347
- name: 'testLabel',
348
- value: 'testValue',
349
- position: 10,
350
- isInheritable: false,
351
- };
352
-
353
- mockFetch.mockResolvedValueOnce(createMockResponse(mockAttribute, 201));
354
-
355
- const client = createTriliumClient(config);
356
- const { data, error } = await client.POST('/attributes', {
357
- body: {
358
- noteId: 'note123',
359
- type: 'label',
360
- name: 'testLabel',
361
- value: 'testValue',
362
- },
363
- });
364
-
365
- expect(error).toBeUndefined();
366
- expect(data).toEqual(mockAttribute);
367
- });
368
-
369
- it('should get an attribute by ID', async () => {
370
- const mockAttribute = {
371
- attributeId: 'attr123',
372
- noteId: 'note123',
373
- type: 'label',
374
- name: 'testLabel',
375
- value: 'testValue',
376
- };
377
-
378
- mockFetch.mockResolvedValueOnce(createMockResponse(mockAttribute));
379
-
380
- const client = createTriliumClient(config);
381
- const { data, error } = await client.GET('/attributes/{attributeId}', {
382
- params: { path: { attributeId: 'attr123' } },
383
- });
384
-
385
- expect(error).toBeUndefined();
386
- expect(data).toEqual(mockAttribute);
387
- });
388
-
389
- it('should delete an attribute', async () => {
390
- mockFetch.mockResolvedValueOnce(createMockResponse('', 204, 'text/plain'));
391
-
392
- const client = createTriliumClient(config);
393
- const { error } = await client.DELETE('/attributes/{attributeId}', {
394
- params: { path: { attributeId: 'attr123' } },
395
- });
396
-
397
- expect(error).toBeUndefined();
398
- });
399
- });
400
-
401
- describe('Attachments API', () => {
402
- it('should get an attachment by ID', async () => {
403
- const mockAttachment = {
404
- attachmentId: 'attach123',
405
- ownerId: 'note123',
406
- role: 'file',
407
- mime: 'image/png',
408
- title: 'test.png',
409
- position: 10,
410
- blobId: 'blob123',
411
- };
412
-
413
- mockFetch.mockResolvedValueOnce(createMockResponse(mockAttachment));
414
-
415
- const client = createTriliumClient(config);
416
- const { data, error } = await client.GET('/attachments/{attachmentId}', {
417
- params: { path: { attachmentId: 'attach123' } },
418
- });
419
-
420
- expect(error).toBeUndefined();
421
- expect(data).toEqual(mockAttachment);
422
- });
423
-
424
- it('should delete an attachment', async () => {
425
- mockFetch.mockResolvedValueOnce(createMockResponse('', 204, 'text/plain'));
426
-
427
- const client = createTriliumClient(config);
428
- const { error } = await client.DELETE('/attachments/{attachmentId}', {
429
- params: { path: { attachmentId: 'attach123' } },
430
- });
431
-
432
- expect(error).toBeUndefined();
433
- });
434
- });
435
-
436
- describe('Authorization header', () => {
437
- it('should include Authorization header in requests', async () => {
438
- mockFetch.mockResolvedValueOnce(createMockResponse({}));
439
-
440
- const client = createTriliumClient(config);
441
- await client.GET('/app-info');
442
-
443
- const request = mockFetch.mock.calls[0]![0] as Request;
444
- expect(request.headers.get('Authorization')).toBe('test-api-key');
445
- });
446
- });
447
-
448
- describe('Error handling', () => {
449
- it('should handle network errors', async () => {
450
- mockFetch.mockRejectedValueOnce(new Error('Network error'));
451
-
452
- const client = createTriliumClient(config);
453
-
454
- await expect(client.GET('/app-info')).rejects.toThrow('Network error');
455
- });
456
-
457
- it('should handle 500 server errors', async () => {
458
- mockFetch.mockResolvedValueOnce(createMockResponse({ status: 500, message: 'Internal Server Error' }, 500));
459
-
460
- const client = createTriliumClient(config);
461
- const { data, error } = await client.GET('/app-info');
462
-
463
- expect(data).toBeUndefined();
464
- expect(error).toBeDefined();
465
- });
466
-
467
- it('should handle 401 unauthorized errors', async () => {
468
- mockFetch.mockResolvedValueOnce(createMockResponse({ status: 401, message: 'Unauthorized' }, 401));
469
-
470
- const client = createTriliumClient(config);
471
- const { data, error } = await client.GET('/app-info');
472
-
473
- expect(data).toBeUndefined();
474
- expect(error).toBeDefined();
475
- });
476
- });
477
- });
package/src/client.ts DELETED
@@ -1,91 +0,0 @@
1
- /**
2
- * Trilium API Client using openapi-fetch
3
- *
4
- * This provides a type-safe client for the Trilium ETAPI.
5
- * Types are auto-generated from the OpenAPI specification.
6
- */
7
-
8
- import createClient from 'openapi-fetch';
9
- import type { paths, components } from './generated/trilium.js';
10
-
11
- // Re-export common types for convenience
12
- export type TriliumNote = components['schemas']['Note'];
13
- export type TriliumBranch = components['schemas']['Branch'];
14
- export type TriliumAttribute = components['schemas']['Attribute'];
15
- export type TriliumAttachment = components['schemas']['Attachment'];
16
- export type TriliumAppInfo = components['schemas']['AppInfo'];
17
-
18
- // Export the paths type for advanced usage
19
- export type { paths, components };
20
-
21
- // Re-export mapper utilities
22
- export {
23
- TriliumMapper,
24
- buildSearchQuery,
25
- transforms,
26
- type MappingConfig,
27
- type FieldMapping,
28
- type TransformFunction,
29
- type ComputedFunction,
30
- type TriliumSearchHelpers,
31
- type TriliumSearchConditions,
32
- type TriliumSearchLogical,
33
- type ComparisonOperator,
34
- type ConditionValue,
35
- type SearchValue,
36
- } from './mapper.js';
37
-
38
- export interface TriliumClientConfig {
39
- baseUrl: string;
40
- apiKey: string;
41
- }
42
-
43
- /**
44
- * Create a type-safe Trilium API client
45
- *
46
- * @example
47
- * ```ts
48
- * const client = createTriliumClient({
49
- * baseUrl: 'http://localhost:37840',
50
- * apiKey: 'your-etapi-token'
51
- * });
52
- *
53
- * // Get app info
54
- * const { data, error } = await client.GET('/app-info');
55
- *
56
- * // Get a note by ID
57
- * const { data: note } = await client.GET('/notes/{noteId}', {
58
- * params: { path: { noteId: 'root' } }
59
- * });
60
- *
61
- * // Create a note
62
- * const { data: newNote } = await client.POST('/notes', {
63
- * body: {
64
- * parentNoteId: 'root',
65
- * title: 'My Note',
66
- * type: 'text',
67
- * content: '<p>Hello World</p>'
68
- * }
69
- * });
70
- *
71
- * // Search notes
72
- * const { data: searchResults } = await client.GET('/notes', {
73
- * params: { query: { search: '#blog' } }
74
- * });
75
- * ```
76
- */
77
- export function createTriliumClient(config: TriliumClientConfig) {
78
- const baseUrl = config.baseUrl.endsWith('/')
79
- ? config.baseUrl.slice(0, -1)
80
- : config.baseUrl;
81
-
82
- return createClient<paths>({
83
- baseUrl: `${baseUrl}/etapi`,
84
- headers: {
85
- Authorization: config.apiKey,
86
- },
87
- });
88
- }
89
-
90
- // Default export for convenience
91
- export default createTriliumClient;