@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/src/client.ts ADDED
@@ -0,0 +1,467 @@
1
+ import axios, { AxiosInstance } from 'axios';
2
+ import {
3
+ ClientConfig,
4
+ DefaultDocumentStructure,
5
+ SearchResult,
6
+ TypedDocumentStructure,
7
+ TypedSearchResult,
8
+ CreateAndUpdateRequest,
9
+ GraphQLResponse,
10
+ GraphQLError as SDKGraphQLError,
11
+ ApitoError,
12
+ ValidationError,
13
+ InjectedDBOperationInterface,
14
+ } from './types';
15
+
16
+ /**
17
+ * Apito SDK Client - JavaScript implementation matching the Go SDK
18
+ */
19
+ export class ApitoClient implements InjectedDBOperationInterface {
20
+ private httpClient: AxiosInstance;
21
+ private baseURL: string;
22
+ private apiKey: string;
23
+ private tenantId?: string;
24
+
25
+ constructor(config: ClientConfig) {
26
+ this.baseURL = config.baseURL;
27
+ this.apiKey = config.apiKey;
28
+ this.tenantId = config.tenantId;
29
+
30
+ // Create axios instance with default configuration
31
+ this.httpClient = axios.create({
32
+ timeout: config.timeout || 30000,
33
+ headers: {
34
+ 'Content-Type': 'application/json',
35
+ 'X-Apito-Key': this.apiKey,
36
+ },
37
+ ...config.httpClient,
38
+ });
39
+
40
+ // Add tenant ID to headers if provided
41
+ if (this.tenantId) {
42
+ this.httpClient.defaults.headers['X-Apito-Tenant-ID'] = this.tenantId;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Execute a GraphQL query or mutation
48
+ */
49
+ private async executeGraphQL<T = any>(
50
+ query: string,
51
+ variables?: Record<string, any>,
52
+ options?: { tenantId?: string }
53
+ ): Promise<GraphQLResponse> {
54
+ try {
55
+ const payload = {
56
+ query,
57
+ variables: variables || {},
58
+ };
59
+
60
+ const headers: Record<string, string> = {
61
+ 'Content-Type': 'application/json',
62
+ 'X-Apito-Key': this.apiKey,
63
+ };
64
+
65
+ if (options?.tenantId || this.tenantId) {
66
+ headers['X-Apito-Tenant-ID'] = options?.tenantId || this.tenantId!;
67
+ }
68
+
69
+ const response = await this.httpClient.post<GraphQLResponse>(
70
+ this.baseURL,
71
+ payload,
72
+ { headers }
73
+ );
74
+
75
+ if (response.data.errors && response.data.errors.length > 0) {
76
+ throw new SDKGraphQLError(
77
+ 'GraphQL query failed',
78
+ response.data.errors,
79
+ response.data
80
+ );
81
+ }
82
+
83
+ return response.data;
84
+ } catch (error) {
85
+ if (axios.isAxiosError(error)) {
86
+ throw new ApitoError(
87
+ error.response?.data?.message || error.message,
88
+ 'HTTP_ERROR',
89
+ error.response?.status,
90
+ error.response?.data
91
+ );
92
+ }
93
+ throw error;
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Generate a new tenant token for the specified tenant ID
99
+ */
100
+ async generateTenantToken(token: string, tenantId: string): Promise<string> {
101
+ const query = `
102
+ mutation GenerateTenantToken($token: String!, $tenantId: String!) {
103
+ generateTenantToken(token: $token, tenant_id: $tenantId) {
104
+ token
105
+ }
106
+ }
107
+ `;
108
+
109
+ const variables = { token, tenantId };
110
+ const response = await this.executeGraphQL(query, variables, { tenantId });
111
+
112
+ const data = response.data?.generateTenantToken;
113
+ if (!data?.token) {
114
+ throw new ValidationError('Invalid response format for tenant token');
115
+ }
116
+
117
+ return data.token;
118
+ }
119
+
120
+ /**
121
+ * Get a single resource by model and ID
122
+ */
123
+ async getSingleResource(
124
+ model: string,
125
+ id: string,
126
+ singlePageData: boolean = false
127
+ ): Promise<DefaultDocumentStructure> {
128
+ const query = `
129
+ query GetSingleData($model: String, $_id: String!, $single_page_data: Boolean) {
130
+ getSingleData(model: $model, _id: $_id, single_page_data: $single_page_data) {
131
+ _key
132
+ data
133
+ meta {
134
+ created_at
135
+ updated_at
136
+ status
137
+ revision
138
+ revision_at
139
+ }
140
+ id
141
+ expire_at
142
+ relation_doc_id
143
+ type
144
+ }
145
+ }
146
+ `;
147
+
148
+ const variables = {
149
+ model,
150
+ _id: id,
151
+ single_page_data: singlePageData,
152
+ };
153
+
154
+ const response = await this.executeGraphQL(query, variables);
155
+
156
+ if (!response.data?.getSingleData) {
157
+ throw new ValidationError('Resource not found');
158
+ }
159
+
160
+ return response.data.getSingleData;
161
+ }
162
+
163
+ /**
164
+ * Search resources in a model
165
+ */
166
+ async searchResources(
167
+ model: string,
168
+ filter: Record<string, any> = {},
169
+ aggregate: boolean = false
170
+ ): Promise<SearchResult> {
171
+ const query = `
172
+ query GetModelData(
173
+ $model: String!
174
+ $page: Int
175
+ $limit: Int
176
+ $_key: JSON
177
+ $where: JSON
178
+ $search: String
179
+ ) {
180
+ getModelData(
181
+ model: $model
182
+ page: $page
183
+ limit: $limit
184
+ _key: $_key
185
+ where: $where
186
+ search: $search
187
+ ) {
188
+ results {
189
+ id
190
+ relation_doc_id
191
+ data
192
+ type
193
+ expire_at
194
+ meta {
195
+ created_at
196
+ updated_at
197
+ status
198
+ root_revision_id
199
+ }
200
+ }
201
+ count
202
+ }
203
+ }
204
+ `;
205
+
206
+ // Only forward keys declared on the GraphQL operation (matches go-internal-sdk)
207
+ const variables: Record<string, any> = { model };
208
+ if (filter && typeof filter === 'object') {
209
+ if (filter._key !== undefined) {
210
+ variables._key = filter._key;
211
+ }
212
+ if (filter.page !== undefined) {
213
+ variables.page = filter.page;
214
+ }
215
+ if (filter.limit !== undefined) {
216
+ variables.limit = filter.limit;
217
+ }
218
+ if (filter.where !== undefined) {
219
+ variables.where = filter.where;
220
+ }
221
+ if (filter.search !== undefined) {
222
+ variables.search = filter.search;
223
+ }
224
+ }
225
+
226
+ const response = await this.executeGraphQL(query, variables);
227
+
228
+ if (!response.data?.getModelData) {
229
+ throw new ValidationError('Invalid search response format');
230
+ }
231
+
232
+ return response.data.getModelData;
233
+ }
234
+
235
+ /**
236
+ * Get related documents
237
+ */
238
+ async getRelationDocuments(
239
+ id: string,
240
+ connection: Record<string, any>
241
+ ): Promise<SearchResult> {
242
+ const query = `
243
+ query GetModelData($model: String!, $page: Int, $limit: Int, $where: JSON, $search: String, $connection : ListAllDataDetailedOfAModelConnectionPayload) {
244
+ getModelData(model: $model, page: $page, limit: $limit, where: $where, search: $search, connection: $connection) {
245
+ results {
246
+ id
247
+ relation_doc_id
248
+ data
249
+ type
250
+ expire_at
251
+ meta {
252
+ created_at
253
+ updated_at
254
+ status
255
+ root_revision_id
256
+ }
257
+ }
258
+ count
259
+ }
260
+ }
261
+ `;
262
+
263
+ const variables: Record<string, any> = {
264
+ connection,
265
+ };
266
+
267
+ // Extract model from connection if available
268
+ if (connection.model) {
269
+ variables.model = connection.model;
270
+ } else {
271
+ throw new ValidationError('model is required in connection parameters');
272
+ }
273
+
274
+ // Add filter parameters if provided in connection
275
+ if (connection.filter) {
276
+ const filter = connection.filter;
277
+ if (filter.page !== undefined) {
278
+ variables.page = filter.page;
279
+ }
280
+ if (filter.limit !== undefined) {
281
+ variables.limit = filter.limit;
282
+ }
283
+ if (filter.where !== undefined) {
284
+ variables.where = filter.where;
285
+ }
286
+ if (filter.search !== undefined) {
287
+ variables.search = filter.search;
288
+ }
289
+ }
290
+
291
+ const response = await this.executeGraphQL(query, variables);
292
+
293
+ if (!response.data?.getModelData) {
294
+ throw new ValidationError('Invalid relation documents response format');
295
+ }
296
+
297
+ return response.data.getModelData;
298
+ }
299
+
300
+ /**
301
+ * Create a new resource
302
+ */
303
+ async createNewResource(request: CreateAndUpdateRequest): Promise<DefaultDocumentStructure> {
304
+ if (!request.model) {
305
+ throw new ValidationError('model is required');
306
+ }
307
+
308
+ if (!request.payload) {
309
+ throw new ValidationError('payload is required');
310
+ }
311
+
312
+ const query = `
313
+ mutation CreateNewData($model: String!, $single_page_data: Boolean, $payload: JSON!, $connect: JSON) {
314
+ upsertModelData(
315
+ connect: $connect
316
+ model_name: $model
317
+ single_page_data: $single_page_data
318
+ payload: $payload
319
+ ) {
320
+ id
321
+ type
322
+ data
323
+ meta {
324
+ created_at
325
+ updated_at
326
+ status
327
+ revision
328
+ revision_at
329
+ }
330
+ }
331
+ }
332
+ `;
333
+
334
+ const variables: Record<string, any> = {
335
+ model: request.model,
336
+ payload: request.payload,
337
+ single_page_data: request.singlePageData || false,
338
+ };
339
+
340
+ if (request.connect) {
341
+ variables.connect = request.connect;
342
+ }
343
+
344
+ const response = await this.executeGraphQL(query, variables);
345
+
346
+ if (!response.data?.upsertModelData) {
347
+ throw new ValidationError('Invalid create response format');
348
+ }
349
+
350
+ return response.data.upsertModelData;
351
+ }
352
+
353
+ /**
354
+ * Update an existing resource
355
+ */
356
+ async updateResource(request: CreateAndUpdateRequest): Promise<DefaultDocumentStructure> {
357
+ if (!request.id) {
358
+ throw new ValidationError('id is required');
359
+ }
360
+
361
+ if (!request.model) {
362
+ throw new ValidationError('model is required');
363
+ }
364
+
365
+ if (!request.payload) {
366
+ throw new ValidationError('payload is required');
367
+ }
368
+
369
+ const query = `
370
+ mutation UpdateModelData($_id: String!, $model: String!, $single_page_data: Boolean, $force_update: Boolean, $payload: JSON!, $connect: JSON, $disconnect: JSON) {
371
+ upsertModelData(
372
+ connect: $connect
373
+ model_name: $model
374
+ single_page_data: $single_page_data
375
+ force_update: $force_update
376
+ disconnect: $disconnect
377
+ _id: $_id
378
+ payload: $payload
379
+ ) {
380
+ id
381
+ type
382
+ data
383
+ meta {
384
+ created_at
385
+ updated_at
386
+ status
387
+ revision
388
+ revision_at
389
+ }
390
+ }
391
+ }
392
+ `;
393
+
394
+ const variables: Record<string, any> = {
395
+ _id: request.id,
396
+ model: request.model,
397
+ payload: request.payload,
398
+ single_page_data: request.singlePageData || false,
399
+ force_update: request.forceUpdate || false,
400
+ };
401
+
402
+ if (request.connect) {
403
+ variables.connect = request.connect;
404
+ }
405
+ if (request.disconnect) {
406
+ variables.disconnect = request.disconnect;
407
+ }
408
+
409
+ const response = await this.executeGraphQL(query, variables);
410
+
411
+ if (!response.data?.upsertModelData) {
412
+ throw new ValidationError('Invalid update response format');
413
+ }
414
+
415
+ return response.data.upsertModelData;
416
+ }
417
+
418
+ /**
419
+ * Delete a resource by model and ID
420
+ */
421
+ async deleteResource(model: string, id: string): Promise<void> {
422
+ const query = `
423
+ mutation DeleteData($model: String!, $_id: String!) {
424
+ deleteModelData(model_name: $model, _id: $_id) {
425
+ id
426
+ }
427
+ }
428
+ `;
429
+
430
+ const variables = {
431
+ model,
432
+ _id: id,
433
+ };
434
+
435
+ await this.executeGraphQL(query, variables);
436
+ }
437
+
438
+ /**
439
+ * Debug is used to debug the plugin, you can pass data here to debug the plugin
440
+ */
441
+ async debug(stage: string, ...data: any[]): Promise<any> {
442
+ const query = `
443
+ mutation Debug($stage: String!, $data: JSON) {
444
+ debug(stage: $stage, data: $data) {
445
+ message
446
+ data
447
+ }
448
+ }
449
+ `;
450
+
451
+ const variables = {
452
+ stage,
453
+ data,
454
+ };
455
+
456
+ const response = await this.executeGraphQL(query, variables);
457
+
458
+ return response.data?.debug;
459
+ }
460
+ }
461
+
462
+ /**
463
+ * Factory function to create a new Apito client
464
+ */
465
+ export function createClient(config: ClientConfig): ApitoClient {
466
+ return new ApitoClient(config);
467
+ }
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Apito JavaScript SDK
3
+ * A comprehensive JavaScript SDK for communicating with Apito GraphQL API endpoints
4
+ *
5
+ * @module @apito-io/js-admin-sdk
6
+ */
7
+
8
+ // Export main client and types
9
+ export { ApitoClient, createClient } from './client';
10
+ export { TypedOperations } from './typed-operations';
11
+ export { Version, getVersion } from './version';
12
+ export * from './types';
13
+
14
+ // Re-export commonly used types for convenience
15
+ export type {
16
+ ClientConfig,
17
+ DefaultDocumentStructure,
18
+ SearchResult,
19
+ TypedDocumentStructure,
20
+ TypedSearchResult,
21
+ CreateAndUpdateRequest,
22
+ GraphQLResponse,
23
+ GraphQLError,
24
+ ApitoError,
25
+ ValidationError,
26
+ InjectedDBOperationInterface,
27
+ } from './types';
28
+
29
+ // Default export for convenience
30
+ export { ApitoClient as default } from './client';
@@ -0,0 +1,93 @@
1
+ import {
2
+ DefaultDocumentStructure,
3
+ TypedDocumentStructure,
4
+ TypedSearchResult,
5
+ CreateAndUpdateRequest,
6
+ } from './types';
7
+ import { ApitoClient } from './client';
8
+
9
+ /**
10
+ * Typed operations wrapper for the Apito SDK
11
+ * Provides type-safe methods for working with typed data
12
+ */
13
+ export class TypedOperations {
14
+ constructor(private client: ApitoClient) {}
15
+
16
+ private static toTypedDocument<T>(raw: DefaultDocumentStructure): TypedDocumentStructure<T> {
17
+ const data = JSON.parse(JSON.stringify(raw.data)) as T;
18
+ return {
19
+ _key: raw._key,
20
+ _id: raw._id,
21
+ data,
22
+ meta: raw.meta,
23
+ id: raw.id,
24
+ expire_at: raw.expire_at,
25
+ relation_doc_id: raw.relation_doc_id,
26
+ last_revision_doc_id: raw.last_revision_doc_id,
27
+ type: raw.type,
28
+ tenant_id: raw.tenant_id,
29
+ tenant_model: raw.tenant_model,
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Get a single resource with type safety
35
+ */
36
+ async getSingleResourceTyped<T>(
37
+ model: string,
38
+ id: string,
39
+ singlePageData: boolean = false
40
+ ): Promise<TypedDocumentStructure<T>> {
41
+ const result = await this.client.getSingleResource(model, id, singlePageData);
42
+ return TypedOperations.toTypedDocument<T>(result);
43
+ }
44
+
45
+ /**
46
+ * Search resources with type safety
47
+ */
48
+ async searchResourcesTyped<T>(
49
+ model: string,
50
+ filter: Record<string, any> = {},
51
+ aggregate: boolean = false
52
+ ): Promise<TypedSearchResult<T>> {
53
+ const result = await this.client.searchResources(model, filter, aggregate);
54
+ return {
55
+ results: result.results.map((doc) => TypedOperations.toTypedDocument<T>(doc)),
56
+ count: result.count,
57
+ };
58
+ }
59
+
60
+ /**
61
+ * Get related documents with type safety
62
+ */
63
+ async getRelationDocumentsTyped<T>(
64
+ id: string,
65
+ connection: Record<string, any>
66
+ ): Promise<TypedSearchResult<T>> {
67
+ const result = await this.client.getRelationDocuments(id, connection);
68
+ return {
69
+ results: result.results.map((doc) => TypedOperations.toTypedDocument<T>(doc)),
70
+ count: result.count,
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Create a new resource with type safety
76
+ */
77
+ async createNewResourceTyped<T>(
78
+ request: CreateAndUpdateRequest
79
+ ): Promise<TypedDocumentStructure<T>> {
80
+ const result = await this.client.createNewResource(request);
81
+ return TypedOperations.toTypedDocument<T>(result);
82
+ }
83
+
84
+ /**
85
+ * Update a resource with type safety
86
+ */
87
+ async updateResourceTyped<T>(
88
+ request: CreateAndUpdateRequest
89
+ ): Promise<TypedDocumentStructure<T>> {
90
+ const result = await this.client.updateResource(request);
91
+ return TypedOperations.toTypedDocument<T>(result);
92
+ }
93
+ }