@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/README.md +38 -1
- package/dist/index.d.mts +65 -7
- package/dist/index.d.ts +65 -7
- 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 +209 -15
- package/src/types.ts +48 -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,28 +105,35 @@ export class ApitoClient implements InjectedDBOperationInterface {
|
|
|
96
105
|
}
|
|
97
106
|
|
|
98
107
|
/**
|
|
99
|
-
* Generate a tenant-scoped API key
|
|
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
|
-
*
|
|
102
|
-
*
|
|
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(
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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 = {
|
|
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(
|
|
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>;
|