@apito-io/js-admin-sdk 2.0.0

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/package.json ADDED
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "@apito-io/js-admin-sdk",
3
+ "version": "2.0.0",
4
+ "description": "Admin JavaScript SDK for Apito GraphQL API (mirrors go-internal-sdk)",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "src"
18
+ ],
19
+ "engines": {
20
+ "node": ">=18"
21
+ },
22
+ "scripts": {
23
+ "start": "tsup --watch",
24
+ "build": "tsup",
25
+ "test": "jest --passWithNoTests",
26
+ "lint": "eslint src --ext .ts",
27
+ "prepare": "tsup",
28
+ "size": "size-limit",
29
+ "analyze": "size-limit --why"
30
+ },
31
+ "keywords": [
32
+ "apito",
33
+ "sdk",
34
+ "graphql",
35
+ "api",
36
+ "javascript",
37
+ "typescript",
38
+ "headless-cms",
39
+ "backend"
40
+ ],
41
+ "author": "Apito Team",
42
+ "license": "MIT",
43
+ "repository": {
44
+ "type": "git",
45
+ "url": "https://github.com/apito-io/js-admin-sdk.git"
46
+ },
47
+ "bugs": {
48
+ "url": "https://github.com/apito-io/js-admin-sdk/issues"
49
+ },
50
+ "homepage": "https://docs.apito.io",
51
+ "dependencies": {
52
+ "axios": "^1.14.0"
53
+ },
54
+ "devDependencies": {
55
+ "@types/jest": "^30.0.0",
56
+ "@types/node": "^22.19.3",
57
+ "@typescript-eslint/eslint-plugin": "^7.18.0",
58
+ "@typescript-eslint/parser": "^7.18.0",
59
+ "eslint": "^8.57.1",
60
+ "eslint-config-prettier": "^9.1.2",
61
+ "eslint-plugin-prettier": "^5.5.5",
62
+ "jest": "^30.3.0",
63
+ "prettier": "^3.8.1",
64
+ "size-limit": "^12.0.1",
65
+ "ts-jest": "^29.4.9",
66
+ "tsup": "^8.5.1",
67
+ "typescript": "~5.9.3"
68
+ },
69
+ "size-limit": [
70
+ {
71
+ "path": "dist/index.js",
72
+ "limit": "50 KB"
73
+ },
74
+ {
75
+ "path": "dist/index.mjs",
76
+ "limit": "50 KB"
77
+ }
78
+ ],
79
+ "publishConfig": {
80
+ "access": "public"
81
+ }
82
+ }
@@ -0,0 +1,493 @@
1
+ import { ApitoClient, createClient } from '../client';
2
+ import { TypedOperations } from '../typed-operations';
3
+ import {
4
+ TypedDocumentStructure,
5
+ TypedSearchResult,
6
+ CreateAndUpdateRequest,
7
+ } from '../types';
8
+
9
+ // Test constants matching Go SDK
10
+ const BaseURL = 'http://localhost:5050/system/graphql';
11
+ const APIKey = 'EhvreBZvFOKYCxWx3xL9xuW4g1WLx3dhdbCWmPhuIaIVI4zeBMk5gUYfuXM4jccwNGjRqitMaNyK1kt6b3S8NKowNXzwFDL6ivZL4rscGu49w8E3vVEYPeyvAgzT0NeTPO9SiJxmI4nBGkMpcBX789VqEfH1tuwacKKivQ4jhLtGt3PsyfmIXX9';
12
+
13
+ // Task interface matching Go SDK
14
+ interface Task {
15
+ name: string;
16
+ took: number | string;
17
+ description: string | object;
18
+ progress: string;
19
+ list?: Array<{
20
+ id: string;
21
+ title: string;
22
+ description: string | object;
23
+ status: string;
24
+ }>;
25
+ properties?: {
26
+ given_by: string;
27
+ handover_date: string;
28
+ commission: string;
29
+ };
30
+ }
31
+
32
+ // Product interface for testing typed operations
33
+ interface Product {
34
+ name: string;
35
+ description: string;
36
+ price: number;
37
+ category_id: string;
38
+ in_stock: boolean;
39
+ created_at: string;
40
+ }
41
+
42
+ // User interface for testing typed operations
43
+ interface User {
44
+ email: string;
45
+ first_name: string;
46
+ last_name: string;
47
+ age: number;
48
+ active: boolean;
49
+ }
50
+
51
+ // Helper function to get test client
52
+ function getTestClient(): ApitoClient {
53
+ return new ApitoClient({
54
+ baseURL: BaseURL,
55
+ apiKey: APIKey,
56
+ timeout: 30000,
57
+ });
58
+ }
59
+
60
+ describe('ApitoClient', () => {
61
+ describe('NewClient', () => {
62
+ it('should create client with required config', () => {
63
+ const client = new ApitoClient({
64
+ baseURL: BaseURL,
65
+ apiKey: APIKey,
66
+ timeout: 10000,
67
+ });
68
+
69
+ expect(client).toBeInstanceOf(ApitoClient);
70
+ });
71
+
72
+ it('should create client with default timeout', () => {
73
+ const client = new ApitoClient({
74
+ baseURL: BaseURL,
75
+ apiKey: APIKey,
76
+ });
77
+
78
+ expect(client).toBeDefined();
79
+ });
80
+ });
81
+
82
+ describe('GetSingleResource', () => {
83
+ it('should fetch single resource', async () => {
84
+ const client = getTestClient();
85
+
86
+ try {
87
+ const resource = await client.getSingleResource(
88
+ 'task',
89
+ '401fa9f2-b174-42b1-84da-1227be8d8755',
90
+ false
91
+ );
92
+
93
+ expect(resource).toBeDefined();
94
+ expect(resource.id).toBeDefined();
95
+ console.log('✅ GetSingleResource succeeded:', resource);
96
+ } catch (error) {
97
+ console.log('GetSingleResource failed (may be expected):', error);
98
+ }
99
+ }, 30000);
100
+ });
101
+
102
+ describe('SearchResources', () => {
103
+ it('should search resources with filters', async () => {
104
+ const client = getTestClient();
105
+
106
+ const filter = {
107
+ page: 1,
108
+ limit: 5,
109
+ };
110
+
111
+ try {
112
+ const results = await client.searchResources('task', filter, false);
113
+
114
+ expect(results).toBeDefined();
115
+ expect(results.count).toBeDefined();
116
+ console.log('✅ SearchResources succeeded:', results);
117
+ } catch (error) {
118
+ console.log('SearchResources failed (may be expected):', error);
119
+ }
120
+ }, 30000); // Increase timeout to 30 seconds for real API calls
121
+ });
122
+
123
+ describe('GetRelationDocuments', () => {
124
+ it('should fetch related documents', async () => {
125
+ const client = getTestClient();
126
+
127
+ const connection = {
128
+ model: 'users',
129
+ _id: 'test-id',
130
+ to_model: 'roles',
131
+ relation_type: 'belongs_to',
132
+ known_as: 'user_role',
133
+ connection_type: 'outbound',
134
+ };
135
+
136
+ try {
137
+ const results = await client.getRelationDocuments('test-id', connection);
138
+
139
+ expect(results).toBeDefined();
140
+ console.log('✅ GetRelationDocuments succeeded:', results);
141
+ } catch (error) {
142
+ console.log('GetRelationDocuments failed (may be expected):', error);
143
+ }
144
+ }, 30000);
145
+ });
146
+
147
+ describe('CreateNewResource', () => {
148
+ it('should create new resource', async () => {
149
+ const client = getTestClient();
150
+
151
+ const data = {
152
+ name: 'Test',
153
+ took: 3,
154
+ description: 'Test Description',
155
+ progress: 'DONE',
156
+ };
157
+
158
+ const connect = {
159
+ category_ids: ['56b2a1dd-25cf-44b4-ad65-8a78b6deab89'],
160
+ executor_id: '354c47b6-8693-4720-9a4d-7404a64386f9',
161
+ };
162
+
163
+ try {
164
+ const result = await client.createNewResource({
165
+ model: 'task',
166
+ payload: data,
167
+ connect: connect,
168
+ });
169
+
170
+ expect(result).toBeDefined();
171
+ expect(result.id).toBeDefined();
172
+ console.log('✅ CreateNewResource succeeded:', result);
173
+ } catch (error) {
174
+ console.log('CreateNewResource failed (may be expected):', error);
175
+ }
176
+ }, 30000);
177
+ });
178
+
179
+ describe('UpdateResource', () => {
180
+ it('should update existing resource', async () => {
181
+ const client = getTestClient();
182
+
183
+ const data = {
184
+ name: 'Test',
185
+ took: 3,
186
+ description: 'Test Description',
187
+ progress: 'DONE',
188
+ };
189
+
190
+ const connect = {
191
+ category_ids: ['56b2a1dd-25cf-44b4-ad65-8a78b6deab89'],
192
+ };
193
+
194
+ const disconnect = {
195
+ executor_id: '354c47b6-8693-4720-9a4d-7404a64386f9',
196
+ };
197
+
198
+ try {
199
+ const result = await client.updateResource({
200
+ model: 'task',
201
+ id: 'a0d50ad7-3001-4be0-92bd-d0daac0af3a9',
202
+ payload: data,
203
+ connect: connect,
204
+ disconnect: disconnect,
205
+ });
206
+
207
+ expect(result).toBeDefined();
208
+ console.log('✅ UpdateResource succeeded:', result);
209
+ } catch (error) {
210
+ console.log('UpdateResource failed (may be expected):', error);
211
+ }
212
+ }, 30000);
213
+ });
214
+
215
+ describe('DeleteResource', () => {
216
+ it('should delete resource', async () => {
217
+ const client = getTestClient();
218
+
219
+ try {
220
+ await client.deleteResource('task', 'a0d50ad7-3001-4be0-92bd-d0daac0af3a9');
221
+ console.log('✅ DeleteResource succeeded');
222
+ } catch (error) {
223
+ console.log('DeleteResource failed (may be expected):', error);
224
+ }
225
+ }, 30000);
226
+ });
227
+
228
+ describe('GenerateTenantToken', () => {
229
+ it('should generate tenant token', async () => {
230
+ const client = getTestClient();
231
+
232
+ try {
233
+ const token = await client.generateTenantToken(
234
+ 'ak_4HESWVQEXE7V4GVGGDRYGXVWXSCAJL44TAUICSLBPQTOB6CJ53KTU3GUOEXJUIXVAKFMM2BDRJRWWPKEN3DRA3HDLZUY4NZMVLFJUIK5H4BWLY26AUKDOHPZE2ENGJNCXPPPEBKCNLTUXXUFUKVDGYJ2H6CZCSMQCY5KSCYNJVYBXVJBYE6O7C73DI3NV7Q',
235
+ 'ba0ee756-6aea-43a6-b052-c7baab3da91c'
236
+ );
237
+
238
+ expect(token).toBeDefined();
239
+ expect(typeof token).toBe('string');
240
+ expect(token.length).toBeGreaterThan(0);
241
+ console.log('✅ GenerateTenantToken succeeded:', token);
242
+ } catch (error) {
243
+ console.log('GenerateTenantToken failed (may be expected):', error);
244
+ }
245
+ }, 30000);
246
+ });
247
+
248
+ describe('Debug', () => {
249
+ it('should send debug data', async () => {
250
+ const client = getTestClient();
251
+
252
+ try {
253
+ const result = await client.debug('test_stage', 'debug', 'data', {
254
+ test: true,
255
+ });
256
+
257
+ expect(result).toBeDefined();
258
+ console.log('✅ Debug succeeded:', result);
259
+ } catch (error) {
260
+ console.log('Debug failed (may be expected):', error);
261
+ }
262
+ }, 30000);
263
+ });
264
+ });
265
+
266
+ describe('TypedOperations', () => {
267
+ let client: ApitoClient;
268
+ let typed: TypedOperations;
269
+
270
+ beforeEach(() => {
271
+ client = new ApitoClient({
272
+ baseURL: BaseURL,
273
+ apiKey: APIKey,
274
+ timeout: 30000,
275
+ });
276
+ typed = new TypedOperations(client);
277
+ });
278
+
279
+ describe('GetSingleResourceTyped', () => {
280
+ it('should demonstrate typed operation usage', () => {
281
+ // This would be called like:
282
+ // const product = await typed.getSingleResourceTyped<Product>('products', 'product-123', false);
283
+ // Now product.data is of type Product, not any
284
+ // console.log('Product name:', product.data.name, 'price:', product.data.price);
285
+
286
+ expect(typed).toBeDefined();
287
+ });
288
+ });
289
+
290
+ describe('SearchResourcesTyped', () => {
291
+ it('should demonstrate typed search usage', () => {
292
+ // This would be called like:
293
+ // const filter = {
294
+ // limit: 10,
295
+ // where: { in_stock: true }
296
+ // };
297
+ // const results = await typed.searchResourcesTyped<Product>('products', filter, false);
298
+ // for (const product of results.results) {
299
+ // console.log('Product:', product.data.name, '-', product.data.price);
300
+ // }
301
+
302
+ expect(typed).toBeDefined();
303
+ });
304
+ });
305
+
306
+ describe('CreateNewResourceTyped', () => {
307
+ it('should demonstrate typed create usage', () => {
308
+ // This would be called like:
309
+ // const data = {
310
+ // name: 'New Product',
311
+ // description: 'A great new product',
312
+ // price: 29.99,
313
+ // in_stock: true,
314
+ // };
315
+ // const product = await typed.createNewResourceTyped<Product>({
316
+ // model: 'products',
317
+ // payload: data,
318
+ // });
319
+ // console.log('Created product:', product.data.name, 'with ID:', product.id);
320
+
321
+ expect(typed).toBeDefined();
322
+ });
323
+ });
324
+
325
+ describe('UpdateResourceTyped', () => {
326
+ it('should demonstrate typed update usage', () => {
327
+ // This would be called like:
328
+ // const data = { price: 24.99, in_stock: false };
329
+ // const product = await typed.updateResourceTyped<Product>({
330
+ // model: 'products',
331
+ // id: 'product-123',
332
+ // payload: data,
333
+ // });
334
+ // console.log('Updated product:', product.data.name, 'new price:', product.data.price);
335
+
336
+ expect(typed).toBeDefined();
337
+ });
338
+ });
339
+
340
+ describe('GetRelationDocumentsTyped', () => {
341
+ it('should demonstrate typed relation usage', () => {
342
+ // This would be called like:
343
+ // const connection = {
344
+ // model: 'users',
345
+ // relation: 'purchased_by',
346
+ // filter: { limit: 5 },
347
+ // };
348
+ // const users = await typed.getRelationDocumentsTyped<User>('product-123', connection);
349
+ // for (const user of users.results) {
350
+ // console.log('User:', user.data.first_name, user.data.last_name, '(', user.data.email, ')');
351
+ // }
352
+
353
+ expect(typed).toBeDefined();
354
+ });
355
+ });
356
+ });
357
+
358
+ describe('TypedOperations Integration Tests', () => {
359
+ let client: ApitoClient;
360
+
361
+ beforeEach(() => {
362
+ client = new ApitoClient({
363
+ baseURL: BaseURL,
364
+ apiKey: APIKey,
365
+ timeout: 30000,
366
+ });
367
+ });
368
+
369
+ describe('GetSingleTask with Type Safety', () => {
370
+ it('should get single task with typed data', async () => {
371
+ try {
372
+ const typed = new TypedOperations(client);
373
+ const task = await typed.getSingleResourceTyped<Task>(
374
+ 'task',
375
+ '401fa9f2-b174-42b1-84da-1227be8d8755',
376
+ false
377
+ );
378
+
379
+ // Now task.data is strongly typed as Task
380
+ console.log('Task name:', task.data.name);
381
+ console.log('Task took:', task.data.took);
382
+ console.log('Task description:', task.data.description);
383
+ console.log('Task progress:', task.data.progress);
384
+
385
+ // Verify type safety - these are compile-time checked
386
+ expect(typeof task.data.name).toBe('string');
387
+ expect(task.data.progress).toBeDefined();
388
+ } catch (error) {
389
+ console.log('Failed to get single task:', error);
390
+ }
391
+ });
392
+ });
393
+
394
+ describe('SearchTasks with Type Safety', () => {
395
+ it('should search tasks with typed results', async () => {
396
+ try {
397
+ const typed = new TypedOperations(client);
398
+ const filter = {
399
+ limit: 10,
400
+ where: {
401
+ progress: {
402
+ eq: 'INPROGRESS',
403
+ },
404
+ },
405
+ };
406
+
407
+ const results = await typed.searchResourcesTyped<Task>('task', filter, false);
408
+
409
+ console.log(`Found ${results.count} tasks`);
410
+
411
+ // All results are strongly typed
412
+ for (let i = 0; i < Math.min(3, results.results.length); i++) {
413
+ const task = results.results[i];
414
+ console.log(`Task ${i + 1}: ${task.data.name} - ${task.data.progress}`);
415
+
416
+ // Type safety verification - no need for type assertions!
417
+ expect(typeof task.data.name).toBe('string');
418
+ expect(task.data.progress).toBeDefined();
419
+ }
420
+ } catch (error) {
421
+ console.log('Failed to search tasks:', error);
422
+ }
423
+ });
424
+ });
425
+ });
426
+
427
+ describe('Factory Function', () => {
428
+ it('should create client using createClient factory', () => {
429
+ const client = createClient({
430
+ baseURL: BaseURL,
431
+ apiKey: APIKey,
432
+ timeout: 30000,
433
+ });
434
+
435
+ expect(client).toBeInstanceOf(ApitoClient);
436
+ });
437
+ });
438
+
439
+ describe('Error Handling', () => {
440
+ it('should validate required parameters in createNewResource', async () => {
441
+ const client = getTestClient();
442
+
443
+ try {
444
+ await client.createNewResource({} as any);
445
+ } catch (error: any) {
446
+ expect(error.message).toContain('model is required');
447
+ }
448
+ });
449
+
450
+ it('should validate required parameters in updateResource', async () => {
451
+ const client = getTestClient();
452
+
453
+ try {
454
+ await client.updateResource({
455
+ model: 'task',
456
+ payload: { name: 'Test' },
457
+ } as any);
458
+ } catch (error: any) {
459
+ expect(error.message).toContain('id is required');
460
+ }
461
+ });
462
+
463
+ it('should validate model in getRelationDocuments', async () => {
464
+ const client = getTestClient();
465
+
466
+ try {
467
+ await client.getRelationDocuments('test-id', {
468
+ // Missing model parameter
469
+ relation: 'test',
470
+ });
471
+ } catch (error: any) {
472
+ expect(error.message).toContain('model is required');
473
+ }
474
+ });
475
+ });
476
+
477
+ // Example usage for documentation
478
+ describe('Example Usage', () => {
479
+ it('should demonstrate basic CRUD operations', () => {
480
+ const client = new ApitoClient({
481
+ baseURL: BaseURL,
482
+ apiKey: APIKey,
483
+ timeout: 30000,
484
+ });
485
+
486
+ // Example: Get a single task with full type safety
487
+ // const task = await typed.getSingleResourceTyped<Task>('task', '401fa9f2-b174-42b1-84da-1227be8d8755', false);
488
+ // const taskName = task.data.name;
489
+ // const taskProgress = task.data.progress;
490
+
491
+ expect(client).toBeDefined();
492
+ });
493
+ });