@apito-io/js-admin-sdk 2.1.1 → 2.5.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 CHANGED
@@ -11,6 +11,11 @@ import {
11
11
  ApitoError,
12
12
  ValidationError,
13
13
  InjectedDBOperationInterface,
14
+ TenantLoginResponse,
15
+ TenantUser,
16
+ TenantUsersResponse,
17
+ TenantByDomainResponse,
18
+ TenantCatalogSearchRow,
14
19
  } from './types';
15
20
 
16
21
  /**
@@ -32,7 +37,9 @@ export class ApitoClient implements InjectedDBOperationInterface {
32
37
  timeout: config.timeout || 30000,
33
38
  headers: {
34
39
  'Content-Type': 'application/json',
35
- 'X-Apito-Key': this.apiKey,
40
+ ...(this.apiKey.startsWith('cli-') || this.apiKey.startsWith('sdk-')
41
+ ? { 'X-Apito-Sync-Key': this.apiKey }
42
+ : { 'X-Apito-Key': this.apiKey }),
36
43
  },
37
44
  ...config.httpClient,
38
45
  });
@@ -60,7 +67,9 @@ export class ApitoClient implements InjectedDBOperationInterface {
60
67
 
61
68
  const headers: Record<string, string> = {
62
69
  'Content-Type': 'application/json',
63
- 'X-Apito-Key': this.apiKey,
70
+ ...(this.apiKey.startsWith('cli-') || this.apiKey.startsWith('sdk-')
71
+ ? { 'X-Apito-Sync-Key': this.apiKey }
72
+ : { 'X-Apito-Key': this.apiKey }),
64
73
  };
65
74
 
66
75
  if (options?.tenantId || this.tenantId) {
@@ -96,18 +105,35 @@ export class ApitoClient implements InjectedDBOperationInterface {
96
105
  }
97
106
 
98
107
  /**
99
- * Generate a new tenant token for the specified tenant ID
108
+ * Generate a tenant-scoped API key. Matches engine `generateTenantToken`: `tenant_id`, `duration`, optional `role`.
109
+ * Auth uses `X-Apito-Key` (client `apiKey`).
110
+ *
111
+ * @param tenantId Catalog tenant id (`tenant_id` in the mutation).
112
+ * @param duration Expiry calendar day `YYYY-MM-DD`. If omitted/empty, defaults to one year ahead in UTC.
113
+ * @param role Optional token role; if omitted/empty, the engine defaults to `admin`.
100
114
  */
101
- async generateTenantToken(token: string, tenantId: string): Promise<string> {
115
+ async generateTenantToken(tenantId: string, duration?: string, role?: string): Promise<string> {
116
+ let dur = (duration ?? '').trim();
117
+ if (!dur) {
118
+ const d = new Date();
119
+ dur = `${d.getUTCFullYear() + 1}-${String(d.getUTCMonth() + 1).padStart(2, '0')}-${String(
120
+ d.getUTCDate()
121
+ ).padStart(2, '0')}`;
122
+ }
123
+
102
124
  const query = `
103
- mutation GenerateTenantToken($token: String!, $tenantId: String!) {
104
- generateTenantToken(token: $token, tenant_id: $tenantId) {
125
+ mutation GenerateTenantToken($tenantId: String!, $duration: String!, $role: String) {
126
+ generateTenantToken(tenant_id: $tenantId, duration: $duration, role: $role) {
105
127
  token
106
128
  }
107
129
  }
108
130
  `;
109
131
 
110
- const variables = { token, tenantId };
132
+ const variables: Record<string, any> = {
133
+ tenantId,
134
+ duration: dur,
135
+ role: role !== undefined && role.trim() !== '' ? role.trim() : null,
136
+ };
111
137
  const response = await this.executeGraphQL(query, variables, { tenantId });
112
138
 
113
139
  const data = response.data?.generateTenantToken;
@@ -118,6 +144,184 @@ export class ApitoClient implements InjectedDBOperationInterface {
118
144
  return data.token;
119
145
  }
120
146
 
147
+ /**
148
+ * Tenant catalog login (system GraphQL). Returns a tenant-scoped `ak_` API token on success.
149
+ */
150
+ async loginTenantUser(
151
+ username: string,
152
+ password: string,
153
+ projectId: string
154
+ ): Promise<TenantLoginResponse> {
155
+ const query = `
156
+ query LoginTenantUser($project_id: String!, $username: String!, $password: String!) {
157
+ loginTenantUser(project_id: $project_id, username: $username, password: $password) {
158
+ token
159
+ user {
160
+ id
161
+ username
162
+ email
163
+ role
164
+ provider
165
+ tenant_id
166
+ status
167
+ created_at
168
+ updated_at
169
+ }
170
+ }
171
+ }
172
+ `;
173
+ const variables: Record<string, any> = { project_id: projectId, username, password };
174
+ const response = await this.executeGraphQL(query, variables);
175
+ const raw = response.data?.loginTenantUser;
176
+ if (!raw?.token) {
177
+ throw new ValidationError('Invalid response format for loginTenantUser');
178
+ }
179
+ return {
180
+ token: raw.token as string,
181
+ user: raw.user as TenantUser | undefined,
182
+ };
183
+ }
184
+
185
+ /**
186
+ * Google ID token login for tenant users (project must have google_client_id set).
187
+ */
188
+ async loginTenantUserGoogle(projectId: string, idToken: string): Promise<TenantLoginResponse> {
189
+ const query = `
190
+ mutation LoginTenantUserGoogle($project_id: String!, $id_token: String!) {
191
+ loginTenantUserGoogle(project_id: $project_id, id_token: $id_token) {
192
+ token
193
+ user {
194
+ id
195
+ username
196
+ email
197
+ role
198
+ provider
199
+ tenant_id
200
+ status
201
+ created_at
202
+ updated_at
203
+ }
204
+ }
205
+ }
206
+ `;
207
+ const variables = { project_id: projectId, id_token: idToken };
208
+ const response = await this.executeGraphQL(query, variables);
209
+ const raw = response.data?.loginTenantUserGoogle;
210
+ if (!raw?.token) {
211
+ throw new ValidationError('Invalid response format for loginTenantUserGoogle');
212
+ }
213
+ return {
214
+ token: raw.token as string,
215
+ user: raw.user as TenantUser | undefined,
216
+ };
217
+ }
218
+
219
+ /**
220
+ * Search tenant users for a project.
221
+ */
222
+ async searchTenantUsers(
223
+ projectId: string,
224
+ limit?: number,
225
+ offset?: number
226
+ ): Promise<TenantUsersResponse> {
227
+ const query = `
228
+ query SearchTenantUsers($project_id: String!, $limit: Int, $offset: Int) {
229
+ searchTenantUsers(project_id: $project_id, limit: $limit, offset: $offset) {
230
+ count
231
+ users {
232
+ id
233
+ username
234
+ email
235
+ role
236
+ provider
237
+ tenant_id
238
+ status
239
+ created_at
240
+ updated_at
241
+ }
242
+ }
243
+ }
244
+ `;
245
+ const variables: Record<string, any> = { project_id: projectId };
246
+ if (limit !== undefined) variables.limit = limit;
247
+ if (offset !== undefined) variables.offset = offset;
248
+ const response = await this.executeGraphQL(query, variables);
249
+ const raw = response.data?.searchTenantUsers;
250
+ if (!raw) {
251
+ throw new ValidationError('Invalid response format for searchTenantUsers');
252
+ }
253
+ let count = 0;
254
+ if (typeof raw.count === 'number') {
255
+ count = raw.count;
256
+ }
257
+ const users = (raw.users ?? []) as TenantUser[];
258
+ return { users, count };
259
+ }
260
+
261
+ /**
262
+ * Resolve the single SaaS catalog tenant for an exact domain match in the project (`tenant` null if none).
263
+ */
264
+ async searchTenantsByDomain(projectId: string, domain: string): Promise<TenantByDomainResponse> {
265
+ const query = `
266
+ query SearchTenantsByDomain($project_id: String!, $domain: String!) {
267
+ searchTenantsByDomain(project_id: $project_id, domain: $domain) {
268
+ tenant {
269
+ id
270
+ name
271
+ status
272
+ domain
273
+ data
274
+ }
275
+ }
276
+ }
277
+ `;
278
+ const variables: Record<string, any> = {
279
+ project_id: projectId,
280
+ domain,
281
+ };
282
+ const response = await this.executeGraphQL(query, variables);
283
+ const raw = response.data?.searchTenantsByDomain;
284
+ if (!raw) {
285
+ throw new ValidationError('Invalid response format for searchTenantsByDomain');
286
+ }
287
+ const tenant = (raw.tenant ?? null) as TenantCatalogSearchRow | null;
288
+ return { tenant };
289
+ }
290
+
291
+ /**
292
+ * Create a tenant catalog user (local password).
293
+ */
294
+ async createTenantUser(
295
+ projectId: string,
296
+ username: string,
297
+ email: string,
298
+ password: string,
299
+ role: string
300
+ ): Promise<TenantUser> {
301
+ const query = `
302
+ mutation CreateTenantUser($project_id: String!, $username: String!, $password: String!, $role: String, $email: String) {
303
+ createTenantUser(project_id: $project_id, username: $username, password: $password, role: $role, email: $email) {
304
+ id
305
+ username
306
+ email
307
+ role
308
+ provider
309
+ tenant_id
310
+ status
311
+ created_at
312
+ updated_at
313
+ }
314
+ }
315
+ `;
316
+ const variables = { project_id: projectId, username, password, email, role };
317
+ const response = await this.executeGraphQL(query, variables);
318
+ const u = response.data?.createTenantUser;
319
+ if (!u?.id) {
320
+ throw new ValidationError('Invalid response format for createTenantUser');
321
+ }
322
+ return u as TenantUser;
323
+ }
324
+
121
325
  /**
122
326
  * Get a single resource by model and ID
123
327
  */
package/src/types.ts CHANGED
@@ -86,6 +86,42 @@ export interface CreateAndUpdateRequest {
86
86
  forceUpdate?: boolean;
87
87
  }
88
88
 
89
+ /** Tenant catalog user from engine system DB (pro_tenant_users). */
90
+ export interface TenantUser {
91
+ id: string;
92
+ username: string;
93
+ email?: string;
94
+ role: string;
95
+ tenant_id: string;
96
+ provider?: string;
97
+ status?: string;
98
+ created_at?: string;
99
+ updated_at?: string;
100
+ }
101
+
102
+ export interface TenantLoginResponse {
103
+ token: string;
104
+ user?: TenantUser;
105
+ }
106
+
107
+ export interface TenantUsersResponse {
108
+ users: TenantUser[];
109
+ count: number;
110
+ }
111
+
112
+ /** One SaaS catalog tenant row from searchTenantsByDomain. */
113
+ export interface TenantCatalogSearchRow {
114
+ id: string;
115
+ name: string;
116
+ status?: string;
117
+ domain?: string;
118
+ data?: string;
119
+ }
120
+
121
+ export interface TenantByDomainResponse {
122
+ tenant: TenantCatalogSearchRow | null;
123
+ }
124
+
89
125
  export interface ClientConfig {
90
126
  baseURL: string;
91
127
  apiKey: string;
@@ -120,7 +156,19 @@ export interface InjectedDBOperationInterface {
120
156
  variables?: Record<string, any>,
121
157
  options?: { tenantId?: string }
122
158
  ): Promise<GraphQLResponse>;
123
- generateTenantToken(token: string, tenantId: string): Promise<string>;
159
+ /** @param token Legacy; ignored. Auth uses client API key. */
160
+ generateTenantToken(tenantId: string, duration?: string, role?: string): Promise<string>;
161
+ loginTenantUser(username: string, password: string, projectId: string): Promise<TenantLoginResponse>;
162
+ loginTenantUserGoogle(projectId: string, idToken: string): Promise<TenantLoginResponse>;
163
+ searchTenantUsers(projectId: string, limit?: number, offset?: number): Promise<TenantUsersResponse>;
164
+ searchTenantsByDomain(projectId: string, domain: string): Promise<TenantByDomainResponse>;
165
+ createTenantUser(
166
+ projectId: string,
167
+ username: string,
168
+ email: string,
169
+ password: string,
170
+ role: string
171
+ ): Promise<TenantUser>;
124
172
  getSingleResource(model: string, id: string, singlePageData?: boolean): Promise<DefaultDocumentStructure>;
125
173
  searchResources(model: string, filter?: Record<string, any>, aggregate?: boolean): Promise<SearchResult>;
126
174
  getRelationDocuments(id: string, connection: Record<string, any>): Promise<SearchResult>;
package/src/version.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Apito JavaScript internal SDK version (kept in sync with package.json for releases)
3
3
  */
4
- export const Version = '2.1.1';
4
+ export const Version = '2.5.0';
5
5
 
6
6
  /**
7
7
  * GetVersion returns the current version of the SDK