@apito-io/js-admin-sdk 2.1.2 → 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,28 +105,35 @@ export class ApitoClient implements InjectedDBOperationInterface {
96
105
  }
97
106
 
98
107
  /**
99
- * Generate a tenant-scoped API key for {@link tenantId}.
108
+ * Generate a tenant-scoped API key. Matches engine `generateTenantToken`: `tenant_id`, `duration`, optional `role`.
109
+ * Auth uses `X-Apito-Key` (client `apiKey`).
100
110
  *
101
- * `token` is legacy and ignored; the engine authenticates via `X-Apito-Key` (client `apiKey`).
102
- * Expiry is sent as a calendar day `YYYY-MM-DD`; defaults to one year ahead in UTC (same default as Go admin/internal SDK).
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`.
103
114
  */
104
- async generateTenantToken(token: string, tenantId: string): Promise<string> {
105
- void token;
106
-
107
- const d = new Date();
108
- const duration = `${d.getUTCFullYear() + 1}-${String(d.getUTCMonth() + 1).padStart(2, '0')}-${String(
109
- d.getUTCDate()
110
- ).padStart(2, '0')}`;
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
+ }
111
123
 
112
124
  const query = `
113
- mutation GenerateTenantToken($tenantId: String!, $duration: String!) {
114
- generateTenantToken(tenant_id: $tenantId, duration: $duration) {
125
+ mutation GenerateTenantToken($tenantId: String!, $duration: String!, $role: String) {
126
+ generateTenantToken(tenant_id: $tenantId, duration: $duration, role: $role) {
115
127
  token
116
128
  }
117
129
  }
118
130
  `;
119
131
 
120
- const variables = { tenantId, duration };
132
+ const variables: Record<string, any> = {
133
+ tenantId,
134
+ duration: dur,
135
+ role: role !== undefined && role.trim() !== '' ? role.trim() : null,
136
+ };
121
137
  const response = await this.executeGraphQL(query, variables, { tenantId });
122
138
 
123
139
  const data = response.data?.generateTenantToken;
@@ -128,6 +144,184 @@ export class ApitoClient implements InjectedDBOperationInterface {
128
144
  return data.token;
129
145
  }
130
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
+
131
325
  /**
132
326
  * Get a single resource by model and ID
133
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;
@@ -121,7 +157,18 @@ export interface InjectedDBOperationInterface {
121
157
  options?: { tenantId?: string }
122
158
  ): Promise<GraphQLResponse>;
123
159
  /** @param token Legacy; ignored. Auth uses client API key. */
124
- generateTenantToken(token: string, tenantId: string): Promise<string>;
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>;
125
172
  getSingleResource(model: string, id: string, singlePageData?: boolean): Promise<DefaultDocumentStructure>;
126
173
  searchResources(model: string, filter?: Record<string, any>, aggregate?: boolean): Promise<SearchResult>;
127
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