@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/README.md +38 -1
- package/dist/index.d.mts +67 -5
- package/dist/index.d.ts +67 -5
- package/dist/index.js +88 -11
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +88 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/client.test.ts +56 -4
- package/src/client.ts +211 -7
- package/src/types.ts +49 -1
- package/src/version.ts +1 -1
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
|
-
'
|
|
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
|
-
'
|
|
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
|
|
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(
|
|
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($
|
|
104
|
-
generateTenantToken(
|
|
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 = {
|
|
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
|
-
|
|
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>;
|