tale-js-sdk 0.1.2 → 0.1.3
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/dist/acl/index.d.ts +170 -0
- package/dist/acl/index.js +747 -0
- package/dist/acl/types.d.ts +208 -0
- package/dist/acl/types.js +1 -0
- package/dist/auth/index.d.ts +2 -134
- package/dist/auth/index.js +120 -96
- package/dist/auth/types.d.ts +122 -0
- package/dist/auth/types.js +1 -0
- package/dist/common/types.d.ts +76 -0
- package/dist/common/types.js +3 -0
- package/dist/errors.js +18 -18
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/rbac/acl.d.ts +152 -0
- package/dist/rbac/acl.js +723 -0
- package/dist/rbac/index.d.ts +2 -0
- package/dist/rbac/index.js +2 -0
- package/dist/rbac/rbac.d.ts +198 -0
- package/dist/rbac/rbac.js +984 -0
- package/dist/rbac/types.d.ts +356 -0
- package/dist/rbac/types.js +1 -0
- package/dist/rbac/user-group.d.ts +122 -0
- package/dist/rbac/user-group.js +570 -0
- package/dist/status.js +3 -3
- package/dist/token.d.ts +1 -1
- package/dist/token.js +5 -4
- package/dist/user/index.d.ts +2 -142
- package/dist/user/index.js +60 -59
- package/dist/user/types.d.ts +96 -0
- package/dist/user/types.js +1 -0
- package/dist/user-group/index.d.ts +230 -0
- package/dist/user-group/index.js +560 -0
- package/dist/user-group/types.d.ts +61 -0
- package/dist/user-group/types.js +1 -0
- package/package.json +13 -14
- package/dist/auth.d.ts +0 -271
- package/dist/auth.js +0 -461
- package/dist/client.d.ts +0 -20
- package/dist/client.js +0 -62
- package/dist/info.d.ts +0 -9
- package/dist/info.js +0 -18
- package/dist/package.json +0 -36
- package/dist/src/index.d.ts +0 -1
- package/dist/src/index.js +0 -1
- package/dist/src/info.d.ts +0 -6
- package/dist/src/info.js +0 -4
- package/dist/user.d.ts +0 -242
- package/dist/user.js +0 -331
|
@@ -0,0 +1,747 @@
|
|
|
1
|
+
import { getAppToken } from '../token.js';
|
|
2
|
+
import { ApiError, ConfigurationError, NetworkError } from '../errors.js';
|
|
3
|
+
// ===== ACL Record 管理 =====
|
|
4
|
+
/**
|
|
5
|
+
* Creates a new ACL record in the Tale application.
|
|
6
|
+
*
|
|
7
|
+
* @param recordData - ACL record data to create
|
|
8
|
+
* @param options - Optional configuration for the request
|
|
9
|
+
* @returns Promise resolving to the created ACL record information
|
|
10
|
+
* @throws {ConfigurationError} When required environment variables are missing
|
|
11
|
+
* @throws {ApiError} When API request fails or returns invalid response
|
|
12
|
+
* @throws {NetworkError} When network request fails
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { createRecord } from '@tale/client';
|
|
17
|
+
*
|
|
18
|
+
* try {
|
|
19
|
+
* const result = await createRecord({
|
|
20
|
+
* template_id: 'template_user_read',
|
|
21
|
+
* subject_type: 'user',
|
|
22
|
+
* subject_id: 'user_123',
|
|
23
|
+
* resource_type: 'document',
|
|
24
|
+
* resource_id: 'doc_456',
|
|
25
|
+
* effect_type: 'allow',
|
|
26
|
+
* priority: 50,
|
|
27
|
+
* description: 'Allow user to read document'
|
|
28
|
+
* });
|
|
29
|
+
* console.log('ACL record created:', result.record.record_id);
|
|
30
|
+
* } catch (error) {
|
|
31
|
+
* console.error('Failed to create ACL record:', error.message);
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export async function createRecord(recordData, options) {
|
|
36
|
+
// Validate required fields
|
|
37
|
+
if (!recordData.template_id || recordData.template_id.trim() === '') {
|
|
38
|
+
throw new ApiError('template_id is required for ACL record creation', 400, '9400');
|
|
39
|
+
}
|
|
40
|
+
if (!recordData.subject_type || !['user', 'role', 'group'].includes(recordData.subject_type)) {
|
|
41
|
+
throw new ApiError('subject_type is required and must be one of: user, role, group', 400, '9400');
|
|
42
|
+
}
|
|
43
|
+
if (!recordData.resource_type || recordData.resource_type.trim() === '') {
|
|
44
|
+
throw new ApiError('resource_type is required for ACL record creation', 400, '9400');
|
|
45
|
+
}
|
|
46
|
+
if (!recordData.subject_id && !recordData.subject_identifier) {
|
|
47
|
+
throw new ApiError('Either subject_id or subject_identifier is required', 400, '9400');
|
|
48
|
+
}
|
|
49
|
+
if (!recordData.resource_id && !recordData.resource_identifier) {
|
|
50
|
+
throw new ApiError('Either resource_id or resource_identifier is required', 400, '9400');
|
|
51
|
+
}
|
|
52
|
+
if (!recordData.effect_type || !['allow', 'deny', 'inherit'].includes(recordData.effect_type)) {
|
|
53
|
+
throw new ApiError('effect_type is required and must be one of: allow, deny, inherit', 400, '9400');
|
|
54
|
+
}
|
|
55
|
+
// Use provided app token or get one from token service
|
|
56
|
+
const token = options?.appToken ?? await getAppToken(options);
|
|
57
|
+
// Determine base URL
|
|
58
|
+
const env = globalThis?.process?.env ?? import.meta?.env ?? undefined;
|
|
59
|
+
const base = options?.baseUrl ?? env?.TALE_BASE_URL ?? undefined;
|
|
60
|
+
if (!base) {
|
|
61
|
+
throw new ConfigurationError('Missing required environment variable: TALE_BASE_URL');
|
|
62
|
+
}
|
|
63
|
+
const url = String(base).replace(/\/+$/, '') + '/acl/v1/records';
|
|
64
|
+
// Validate priority range
|
|
65
|
+
if (recordData.priority !== undefined && (recordData.priority < 0 || recordData.priority > 100)) {
|
|
66
|
+
throw new ApiError('priority must be between 0 and 100', 400, '9400');
|
|
67
|
+
}
|
|
68
|
+
// Set default priority if not provided
|
|
69
|
+
const finalRecordData = {
|
|
70
|
+
...recordData,
|
|
71
|
+
priority: recordData.priority ?? 50
|
|
72
|
+
};
|
|
73
|
+
let response;
|
|
74
|
+
try {
|
|
75
|
+
response = await globalThis.fetch(url, {
|
|
76
|
+
method: 'POST',
|
|
77
|
+
headers: {
|
|
78
|
+
'Content-Type': 'application/json',
|
|
79
|
+
'x-t-token': token,
|
|
80
|
+
},
|
|
81
|
+
body: JSON.stringify(finalRecordData),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
throw new NetworkError(`Failed to create ACL record: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
86
|
+
}
|
|
87
|
+
let json;
|
|
88
|
+
try {
|
|
89
|
+
const responseJson = await response.json();
|
|
90
|
+
json = responseJson;
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
throw new ApiError(`Failed to parse ACL record creation response: ${error instanceof Error ? error.message : 'Invalid JSON'}`, response.status);
|
|
94
|
+
}
|
|
95
|
+
// Handle API errors
|
|
96
|
+
if (json.code !== 200) {
|
|
97
|
+
const errorMsg = typeof json.msg === 'string' ? json.msg : 'ACL record creation failed';
|
|
98
|
+
throw new ApiError(errorMsg, response.status, json.code);
|
|
99
|
+
}
|
|
100
|
+
// Validate response structure
|
|
101
|
+
if (!json.data || !json.data.record) {
|
|
102
|
+
throw new ApiError('Invalid ACL record creation response: missing record data', response.status);
|
|
103
|
+
}
|
|
104
|
+
return json.data;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Updates an existing ACL record by ID.
|
|
108
|
+
*
|
|
109
|
+
* @param recordId - ACL record ID to update
|
|
110
|
+
* @param updateData - ACL record information to update
|
|
111
|
+
* @param options - Optional configuration for the request
|
|
112
|
+
* @returns Promise resolving to the updated ACL record information
|
|
113
|
+
* @throws {ConfigurationError} When required environment variables are missing
|
|
114
|
+
* @throws {ApiError} When API request fails or returns invalid response
|
|
115
|
+
* @throws {NetworkError} When network request fails
|
|
116
|
+
*/
|
|
117
|
+
export async function updateRecord(recordId, updateData, options) {
|
|
118
|
+
// Validate required fields
|
|
119
|
+
if (!recordId || recordId.trim() === '') {
|
|
120
|
+
throw new ApiError('record_id is required for ACL record update', 400, '9400');
|
|
121
|
+
}
|
|
122
|
+
if (updateData.effect_type && !['allow', 'deny', 'inherit'].includes(updateData.effect_type)) {
|
|
123
|
+
throw new ApiError('effect_type must be one of: allow, deny, inherit', 400, '9400');
|
|
124
|
+
}
|
|
125
|
+
// Validate priority range
|
|
126
|
+
if (updateData.priority !== undefined && (updateData.priority < 0 || updateData.priority > 100)) {
|
|
127
|
+
throw new ApiError('priority must be between 0 and 100', 400, '9400');
|
|
128
|
+
}
|
|
129
|
+
// Use provided app token or get one from token service
|
|
130
|
+
const token = options?.appToken ?? await getAppToken(options);
|
|
131
|
+
// Determine base URL
|
|
132
|
+
const env = globalThis?.process?.env ?? import.meta?.env ?? undefined;
|
|
133
|
+
const base = options?.baseUrl ?? env?.TALE_BASE_URL ?? undefined;
|
|
134
|
+
if (!base) {
|
|
135
|
+
throw new ConfigurationError('Missing required environment variable: TALE_BASE_URL');
|
|
136
|
+
}
|
|
137
|
+
const url = String(base).replace(/\/+$/, '') + `/acl/v1/records/${encodeURIComponent(recordId)}`;
|
|
138
|
+
let response;
|
|
139
|
+
try {
|
|
140
|
+
response = await globalThis.fetch(url, {
|
|
141
|
+
method: 'PUT',
|
|
142
|
+
headers: {
|
|
143
|
+
'Content-Type': 'application/json',
|
|
144
|
+
'x-t-token': token,
|
|
145
|
+
},
|
|
146
|
+
body: JSON.stringify(updateData),
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
throw new NetworkError(`Failed to update ACL record: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
151
|
+
}
|
|
152
|
+
let json;
|
|
153
|
+
try {
|
|
154
|
+
const responseJson = await response.json();
|
|
155
|
+
json = responseJson;
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
throw new ApiError(`Failed to parse ACL record update response: ${error instanceof Error ? error.message : 'Invalid JSON'}`, response.status);
|
|
159
|
+
}
|
|
160
|
+
// Handle API errors
|
|
161
|
+
if (json.code !== 200) {
|
|
162
|
+
const errorMsg = typeof json.msg === 'string' ? json.msg : 'ACL record update failed';
|
|
163
|
+
throw new ApiError(errorMsg, response.status, json.code);
|
|
164
|
+
}
|
|
165
|
+
// Validate response structure
|
|
166
|
+
if (!json.data || !json.data.record) {
|
|
167
|
+
throw new ApiError('Invalid ACL record update response: missing record data', response.status);
|
|
168
|
+
}
|
|
169
|
+
return json.data;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Deletes an ACL record by ID.
|
|
173
|
+
*
|
|
174
|
+
* @param recordId - ACL record ID to delete
|
|
175
|
+
* @param options - Optional configuration for the request
|
|
176
|
+
* @returns Promise resolving to the deletion result
|
|
177
|
+
* @throws {ConfigurationError} When required environment variables are missing
|
|
178
|
+
* @throws {ApiError} When API request fails or returns invalid response
|
|
179
|
+
* @throws {NetworkError} When network request fails
|
|
180
|
+
*/
|
|
181
|
+
export async function deleteRecord(recordId, options) {
|
|
182
|
+
// Validate required fields
|
|
183
|
+
if (!recordId || recordId.trim() === '') {
|
|
184
|
+
throw new ApiError('record_id is required for ACL record deletion', 400, '9400');
|
|
185
|
+
}
|
|
186
|
+
// Use provided app token or get one from token service
|
|
187
|
+
const token = options?.appToken ?? await getAppToken(options);
|
|
188
|
+
// Determine base URL
|
|
189
|
+
const env = globalThis?.process?.env ?? import.meta?.env ?? undefined;
|
|
190
|
+
const base = options?.baseUrl ?? env?.TALE_BASE_URL ?? undefined;
|
|
191
|
+
if (!base) {
|
|
192
|
+
throw new ConfigurationError('Missing required environment variable: TALE_BASE_URL');
|
|
193
|
+
}
|
|
194
|
+
const url = String(base).replace(/\/+$/, '') + `/acl/v1/records/${encodeURIComponent(recordId)}`;
|
|
195
|
+
let response;
|
|
196
|
+
try {
|
|
197
|
+
response = await globalThis.fetch(url, {
|
|
198
|
+
method: 'DELETE',
|
|
199
|
+
headers: {
|
|
200
|
+
'Content-Type': 'application/json',
|
|
201
|
+
'x-t-token': token,
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
throw new NetworkError(`Failed to delete ACL record: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
207
|
+
}
|
|
208
|
+
let json;
|
|
209
|
+
try {
|
|
210
|
+
const responseJson = await response.json();
|
|
211
|
+
json = responseJson;
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
throw new ApiError(`Failed to parse ACL record deletion response: ${error instanceof Error ? error.message : 'Invalid JSON'}`, response.status);
|
|
215
|
+
}
|
|
216
|
+
// Handle API errors
|
|
217
|
+
if (json.code !== 200) {
|
|
218
|
+
const errorMsg = typeof json.msg === 'string' ? json.msg : 'ACL record deletion failed';
|
|
219
|
+
throw new ApiError(errorMsg, response.status, json.code);
|
|
220
|
+
}
|
|
221
|
+
// Validate response structure
|
|
222
|
+
if (!json.data || json.data.deleted !== true) {
|
|
223
|
+
throw new ApiError('Invalid ACL record deletion response: deletion not confirmed', response.status);
|
|
224
|
+
}
|
|
225
|
+
return json.data;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Retrieves ACL record information by ID.
|
|
229
|
+
*
|
|
230
|
+
* @param recordId - ACL record ID to query
|
|
231
|
+
* @param options - Optional configuration for the request
|
|
232
|
+
* @returns Promise resolving to the ACL record information
|
|
233
|
+
* @throws {ConfigurationError} When required environment variables are missing
|
|
234
|
+
* @throws {ApiError} When API request fails or returns invalid response
|
|
235
|
+
* @throws {NetworkError} When network request fails
|
|
236
|
+
*/
|
|
237
|
+
export async function getRecordById(recordId, options) {
|
|
238
|
+
// Validate required fields
|
|
239
|
+
if (!recordId || recordId.trim() === '') {
|
|
240
|
+
throw new ApiError('record_id is required for ACL record query', 400, '9400');
|
|
241
|
+
}
|
|
242
|
+
// Use provided app token or get one from token service
|
|
243
|
+
const token = options?.appToken ?? await getAppToken(options);
|
|
244
|
+
// Determine base URL
|
|
245
|
+
const env = globalThis?.process?.env ?? import.meta?.env ?? undefined;
|
|
246
|
+
const base = options?.baseUrl ?? env?.TALE_BASE_URL ?? undefined;
|
|
247
|
+
if (!base) {
|
|
248
|
+
throw new ConfigurationError('Missing required environment variable: TALE_BASE_URL');
|
|
249
|
+
}
|
|
250
|
+
const url = String(base).replace(/\/+$/, '') + `/acl/v1/records/${encodeURIComponent(recordId)}`;
|
|
251
|
+
let response;
|
|
252
|
+
try {
|
|
253
|
+
response = await globalThis.fetch(url, {
|
|
254
|
+
method: 'GET',
|
|
255
|
+
headers: {
|
|
256
|
+
'Content-Type': 'application/json',
|
|
257
|
+
'x-t-token': token,
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
throw new NetworkError(`Failed to get ACL record: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
263
|
+
}
|
|
264
|
+
let json;
|
|
265
|
+
try {
|
|
266
|
+
const responseJson = await response.json();
|
|
267
|
+
json = responseJson;
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
throw new ApiError(`Failed to parse ACL record response: ${error instanceof Error ? error.message : 'Invalid JSON'}`, response.status);
|
|
271
|
+
}
|
|
272
|
+
// Handle API errors
|
|
273
|
+
if (json.code !== 200) {
|
|
274
|
+
const errorMsg = typeof json.msg === 'string' ? json.msg : 'ACL record retrieval failed';
|
|
275
|
+
throw new ApiError(errorMsg, response.status, json.code);
|
|
276
|
+
}
|
|
277
|
+
// Validate response structure
|
|
278
|
+
if (!json.data || !json.data.record) {
|
|
279
|
+
throw new ApiError('Invalid ACL record response: missing record data', response.status);
|
|
280
|
+
}
|
|
281
|
+
return json.data;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Lists ACL records with pagination and filtering.
|
|
285
|
+
*
|
|
286
|
+
* @param options - Optional parameters for pagination, filtering, and configuration
|
|
287
|
+
* @returns Promise resolving to paginated ACL record list with metadata
|
|
288
|
+
* @throws {ConfigurationError} When required environment variables are missing
|
|
289
|
+
* @throws {ApiError} When API request fails or returns invalid response
|
|
290
|
+
* @throws {NetworkError} When network request fails
|
|
291
|
+
*/
|
|
292
|
+
export async function listRecords(options) {
|
|
293
|
+
// Use provided app token or get one from token service
|
|
294
|
+
const token = options?.appToken ?? await getAppToken(options);
|
|
295
|
+
// Determine base URL
|
|
296
|
+
const env = globalThis?.process?.env ?? import.meta?.env ?? undefined;
|
|
297
|
+
const base = options?.baseUrl ?? env?.TALE_BASE_URL ?? undefined;
|
|
298
|
+
if (!base) {
|
|
299
|
+
throw new ConfigurationError('Missing required environment variable: TALE_BASE_URL');
|
|
300
|
+
}
|
|
301
|
+
// Build URL with query parameters
|
|
302
|
+
const url = new URL(String(base).replace(/\/+$/, '') + '/acl/v1/records');
|
|
303
|
+
// Add query parameters with defaults
|
|
304
|
+
const queryParams = {
|
|
305
|
+
page: 0,
|
|
306
|
+
size: 20,
|
|
307
|
+
sort_by: 'priority',
|
|
308
|
+
sort_direction: 'desc',
|
|
309
|
+
...options
|
|
310
|
+
};
|
|
311
|
+
// Add parameters to URL
|
|
312
|
+
if (queryParams.template_id) {
|
|
313
|
+
url.searchParams.append('template_id', queryParams.template_id);
|
|
314
|
+
}
|
|
315
|
+
if (queryParams.subject_type) {
|
|
316
|
+
url.searchParams.append('subject_type', queryParams.subject_type);
|
|
317
|
+
}
|
|
318
|
+
if (queryParams.subject_id) {
|
|
319
|
+
url.searchParams.append('subject_id', queryParams.subject_id);
|
|
320
|
+
}
|
|
321
|
+
if (queryParams.subject_identifier) {
|
|
322
|
+
url.searchParams.append('subject_identifier', queryParams.subject_identifier);
|
|
323
|
+
}
|
|
324
|
+
if (queryParams.resource_type) {
|
|
325
|
+
url.searchParams.append('resource_type', queryParams.resource_type);
|
|
326
|
+
}
|
|
327
|
+
if (queryParams.resource_id) {
|
|
328
|
+
url.searchParams.append('resource_id', queryParams.resource_id);
|
|
329
|
+
}
|
|
330
|
+
if (queryParams.resource_identifier) {
|
|
331
|
+
url.searchParams.append('resource_identifier', queryParams.resource_identifier);
|
|
332
|
+
}
|
|
333
|
+
if (queryParams.page !== undefined) {
|
|
334
|
+
url.searchParams.append('page', String(queryParams.page));
|
|
335
|
+
}
|
|
336
|
+
if (queryParams.size !== undefined) {
|
|
337
|
+
url.searchParams.append('size', String(queryParams.size));
|
|
338
|
+
}
|
|
339
|
+
if (queryParams.sort_by) {
|
|
340
|
+
url.searchParams.append('sort', `${queryParams.sort_by},${queryParams.sort_direction || 'desc'}`);
|
|
341
|
+
}
|
|
342
|
+
let response;
|
|
343
|
+
try {
|
|
344
|
+
response = await globalThis.fetch(url.toString(), {
|
|
345
|
+
method: 'GET',
|
|
346
|
+
headers: {
|
|
347
|
+
'Content-Type': 'application/json',
|
|
348
|
+
'x-t-token': token,
|
|
349
|
+
},
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
throw new NetworkError(`Failed to list ACL records: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
354
|
+
}
|
|
355
|
+
let json;
|
|
356
|
+
try {
|
|
357
|
+
const responseJson = await response.json();
|
|
358
|
+
json = responseJson;
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
throw new ApiError(`Failed to parse ACL records list response: ${error instanceof Error ? error.message : 'Invalid JSON'}`, response.status);
|
|
362
|
+
}
|
|
363
|
+
// Handle API errors
|
|
364
|
+
if (json.code !== 200) {
|
|
365
|
+
const errorMsg = typeof json.msg === 'string' ? json.msg : 'ACL records list retrieval failed';
|
|
366
|
+
throw new ApiError(errorMsg, response.status, json.code);
|
|
367
|
+
}
|
|
368
|
+
// Validate response structure
|
|
369
|
+
if (!json.data || !Array.isArray(json.data.content)) {
|
|
370
|
+
throw new ApiError('Invalid ACL records list response: missing required data', response.status);
|
|
371
|
+
}
|
|
372
|
+
return json.data;
|
|
373
|
+
}
|
|
374
|
+
// ===== ACL Template 管理 =====
|
|
375
|
+
/**
|
|
376
|
+
* Creates a new ACL template in the Tale application.
|
|
377
|
+
*
|
|
378
|
+
* @param templateData - ACL template data to create
|
|
379
|
+
* @param options - Optional configuration for the request
|
|
380
|
+
* @returns Promise resolving to the created ACL template information
|
|
381
|
+
* @throws {ConfigurationError} When required environment variables are missing
|
|
382
|
+
* @throws {ApiError} When API request fails or returns invalid response
|
|
383
|
+
* @throws {NetworkError} When network request fails
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```typescript
|
|
387
|
+
* import { createTemplate } from '@tale/client';
|
|
388
|
+
*
|
|
389
|
+
* try {
|
|
390
|
+
* const result = await createTemplate({
|
|
391
|
+
* template_name: 'User Document Read Template',
|
|
392
|
+
* template_code: 'user_doc_read',
|
|
393
|
+
* subject_type: 'user',
|
|
394
|
+
* resource_type: 'document',
|
|
395
|
+
* effect_type: 'allow',
|
|
396
|
+
* default_priority: 30,
|
|
397
|
+
* description: 'Template for allowing users to read documents'
|
|
398
|
+
* });
|
|
399
|
+
* console.log('ACL template created:', result.template.template_id);
|
|
400
|
+
* } catch (error) {
|
|
401
|
+
* console.error('Failed to create ACL template:', error.message);
|
|
402
|
+
* }
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
export async function createTemplate(templateData, options) {
|
|
406
|
+
// Validate required fields
|
|
407
|
+
if (!templateData.template_name || templateData.template_name.trim() === '') {
|
|
408
|
+
throw new ApiError('template_name is required for ACL template creation', 400, '9400');
|
|
409
|
+
}
|
|
410
|
+
if (!templateData.template_code || templateData.template_code.trim() === '') {
|
|
411
|
+
throw new ApiError('template_code is required for ACL template creation', 400, '9400');
|
|
412
|
+
}
|
|
413
|
+
if (templateData.effect_type && !['allow', 'deny', 'inherit'].includes(templateData.effect_type)) {
|
|
414
|
+
throw new ApiError('effect_type must be one of: allow, deny, inherit', 400, '9400');
|
|
415
|
+
}
|
|
416
|
+
// Validate default priority range
|
|
417
|
+
if (templateData.default_priority !== undefined && (templateData.default_priority < 0 || templateData.default_priority > 100)) {
|
|
418
|
+
throw new ApiError('default_priority must be between 0 and 100', 400, '9400');
|
|
419
|
+
}
|
|
420
|
+
// Use provided app token or get one from token service
|
|
421
|
+
const token = options?.appToken ?? await getAppToken(options);
|
|
422
|
+
// Determine base URL
|
|
423
|
+
const env = globalThis?.process?.env ?? import.meta?.env ?? undefined;
|
|
424
|
+
const base = options?.baseUrl ?? env?.TALE_BASE_URL ?? undefined;
|
|
425
|
+
if (!base) {
|
|
426
|
+
throw new ConfigurationError('Missing required environment variable: TALE_BASE_URL');
|
|
427
|
+
}
|
|
428
|
+
const url = String(base).replace(/\/+$/, '') + '/acl/v1/templates';
|
|
429
|
+
// Set default values
|
|
430
|
+
const finalTemplateData = {
|
|
431
|
+
...templateData,
|
|
432
|
+
default_priority: templateData.default_priority ?? 50,
|
|
433
|
+
effect_type: templateData.effect_type ?? 'allow'
|
|
434
|
+
};
|
|
435
|
+
let response;
|
|
436
|
+
try {
|
|
437
|
+
response = await globalThis.fetch(url, {
|
|
438
|
+
method: 'POST',
|
|
439
|
+
headers: {
|
|
440
|
+
'Content-Type': 'application/json',
|
|
441
|
+
'x-t-token': token,
|
|
442
|
+
},
|
|
443
|
+
body: JSON.stringify(finalTemplateData),
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
catch (error) {
|
|
447
|
+
throw new NetworkError(`Failed to create ACL template: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
448
|
+
}
|
|
449
|
+
let json;
|
|
450
|
+
try {
|
|
451
|
+
const responseJson = await response.json();
|
|
452
|
+
json = responseJson;
|
|
453
|
+
}
|
|
454
|
+
catch (error) {
|
|
455
|
+
throw new ApiError(`Failed to parse ACL template creation response: ${error instanceof Error ? error.message : 'Invalid JSON'}`, response.status);
|
|
456
|
+
}
|
|
457
|
+
// Handle API errors
|
|
458
|
+
if (json.code !== 200) {
|
|
459
|
+
const errorMsg = typeof json.msg === 'string' ? json.msg : 'ACL template creation failed';
|
|
460
|
+
throw new ApiError(errorMsg, response.status, json.code);
|
|
461
|
+
}
|
|
462
|
+
// Validate response structure
|
|
463
|
+
if (!json.data || !json.data.template) {
|
|
464
|
+
throw new ApiError('Invalid ACL template creation response: missing template data', response.status);
|
|
465
|
+
}
|
|
466
|
+
return json.data;
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Updates an existing ACL template by ID.
|
|
470
|
+
*
|
|
471
|
+
* @param templateId - ACL template ID or code to update
|
|
472
|
+
* @param updateData - ACL template information to update
|
|
473
|
+
* @param options - Optional configuration for the request
|
|
474
|
+
* @returns Promise resolving to the updated ACL template information
|
|
475
|
+
* @throws {ConfigurationError} When required environment variables are missing
|
|
476
|
+
* @throws {ApiError} When API request fails or returns invalid response
|
|
477
|
+
* @throws {NetworkError} When network request fails
|
|
478
|
+
*/
|
|
479
|
+
export async function updateTemplate(templateId, updateData, options) {
|
|
480
|
+
// Validate required fields
|
|
481
|
+
if (!templateId || templateId.trim() === '') {
|
|
482
|
+
throw new ApiError('template_id is required for ACL template update', 400, '9400');
|
|
483
|
+
}
|
|
484
|
+
if (updateData.effect_type && !['allow', 'deny', 'inherit'].includes(updateData.effect_type)) {
|
|
485
|
+
throw new ApiError('effect_type must be one of: allow, deny, inherit', 400, '9400');
|
|
486
|
+
}
|
|
487
|
+
// Validate default priority range
|
|
488
|
+
if (updateData.default_priority !== undefined && (updateData.default_priority < 0 || updateData.default_priority > 100)) {
|
|
489
|
+
throw new ApiError('default_priority must be between 0 and 100', 400, '9400');
|
|
490
|
+
}
|
|
491
|
+
// Use provided app token or get one from token service
|
|
492
|
+
const token = options?.appToken ?? await getAppToken(options);
|
|
493
|
+
// Determine base URL
|
|
494
|
+
const env = globalThis?.process?.env ?? import.meta?.env ?? undefined;
|
|
495
|
+
const base = options?.baseUrl ?? env?.TALE_BASE_URL ?? undefined;
|
|
496
|
+
if (!base) {
|
|
497
|
+
throw new ConfigurationError('Missing required environment variable: TALE_BASE_URL');
|
|
498
|
+
}
|
|
499
|
+
const url = String(base).replace(/\/+$/, '') + `/acl/v1/templates/${encodeURIComponent(templateId)}`;
|
|
500
|
+
let response;
|
|
501
|
+
try {
|
|
502
|
+
response = await globalThis.fetch(url, {
|
|
503
|
+
method: 'PUT',
|
|
504
|
+
headers: {
|
|
505
|
+
'Content-Type': 'application/json',
|
|
506
|
+
'x-t-token': token,
|
|
507
|
+
},
|
|
508
|
+
body: JSON.stringify(updateData),
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
catch (error) {
|
|
512
|
+
throw new NetworkError(`Failed to update ACL template: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
513
|
+
}
|
|
514
|
+
let json;
|
|
515
|
+
try {
|
|
516
|
+
const responseJson = await response.json();
|
|
517
|
+
json = responseJson;
|
|
518
|
+
}
|
|
519
|
+
catch (error) {
|
|
520
|
+
throw new ApiError(`Failed to parse ACL template update response: ${error instanceof Error ? error.message : 'Invalid JSON'}`, response.status);
|
|
521
|
+
}
|
|
522
|
+
// Handle API errors
|
|
523
|
+
if (json.code !== 200) {
|
|
524
|
+
const errorMsg = typeof json.msg === 'string' ? json.msg : 'ACL template update failed';
|
|
525
|
+
throw new ApiError(errorMsg, response.status, json.code);
|
|
526
|
+
}
|
|
527
|
+
// Validate response structure
|
|
528
|
+
if (!json.data || !json.data.template) {
|
|
529
|
+
throw new ApiError('Invalid ACL template update response: missing template data', response.status);
|
|
530
|
+
}
|
|
531
|
+
return json.data;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Deletes an ACL template by ID.
|
|
535
|
+
*
|
|
536
|
+
* @param templateId - ACL template ID or code to delete
|
|
537
|
+
* @param options - Optional configuration for the request
|
|
538
|
+
* @returns Promise resolving to the deletion result
|
|
539
|
+
* @throws {ConfigurationError} When required environment variables are missing
|
|
540
|
+
* @throws {ApiError} When API request fails or returns invalid response
|
|
541
|
+
* @throws {NetworkError} When network request fails
|
|
542
|
+
*/
|
|
543
|
+
export async function deleteTemplate(templateId, options) {
|
|
544
|
+
// Validate required fields
|
|
545
|
+
if (!templateId || templateId.trim() === '') {
|
|
546
|
+
throw new ApiError('template_id is required for ACL template deletion', 400, '9400');
|
|
547
|
+
}
|
|
548
|
+
// Use provided app token or get one from token service
|
|
549
|
+
const token = options?.appToken ?? await getAppToken(options);
|
|
550
|
+
// Determine base URL
|
|
551
|
+
const env = globalThis?.process?.env ?? import.meta?.env ?? undefined;
|
|
552
|
+
const base = options?.baseUrl ?? env?.TALE_BASE_URL ?? undefined;
|
|
553
|
+
if (!base) {
|
|
554
|
+
throw new ConfigurationError('Missing required environment variable: TALE_BASE_URL');
|
|
555
|
+
}
|
|
556
|
+
const url = String(base).replace(/\/+$/, '') + `/acl/v1/templates/${encodeURIComponent(templateId)}`;
|
|
557
|
+
let response;
|
|
558
|
+
try {
|
|
559
|
+
response = await globalThis.fetch(url, {
|
|
560
|
+
method: 'DELETE',
|
|
561
|
+
headers: {
|
|
562
|
+
'Content-Type': 'application/json',
|
|
563
|
+
'x-t-token': token,
|
|
564
|
+
},
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
catch (error) {
|
|
568
|
+
throw new NetworkError(`Failed to delete ACL template: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
569
|
+
}
|
|
570
|
+
let json;
|
|
571
|
+
try {
|
|
572
|
+
const responseJson = await response.json();
|
|
573
|
+
json = responseJson;
|
|
574
|
+
}
|
|
575
|
+
catch (error) {
|
|
576
|
+
throw new ApiError(`Failed to parse ACL template deletion response: ${error instanceof Error ? error.message : 'Invalid JSON'}`, response.status);
|
|
577
|
+
}
|
|
578
|
+
// Handle API errors
|
|
579
|
+
if (json.code !== 200) {
|
|
580
|
+
const errorMsg = typeof json.msg === 'string' ? json.msg : 'ACL template deletion failed';
|
|
581
|
+
throw new ApiError(errorMsg, response.status, json.code);
|
|
582
|
+
}
|
|
583
|
+
// Validate response structure
|
|
584
|
+
if (!json.data || json.data.deleted !== true) {
|
|
585
|
+
throw new ApiError('Invalid ACL template deletion response: deletion not confirmed', response.status);
|
|
586
|
+
}
|
|
587
|
+
return json.data;
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Retrieves ACL template information by ID or code.
|
|
591
|
+
*
|
|
592
|
+
* @param templateId - ACL template ID or code to query
|
|
593
|
+
* @param options - Optional configuration for the request
|
|
594
|
+
* @returns Promise resolving to the ACL template information
|
|
595
|
+
* @throws {ConfigurationError} When required environment variables are missing
|
|
596
|
+
* @throws {ApiError} When API request fails or returns invalid response
|
|
597
|
+
* @throws {NetworkError} When network request fails
|
|
598
|
+
*/
|
|
599
|
+
export async function getTemplateById(templateId, options) {
|
|
600
|
+
// Validate required fields
|
|
601
|
+
if (!templateId || templateId.trim() === '') {
|
|
602
|
+
throw new ApiError('template_id is required for ACL template query', 400, '9400');
|
|
603
|
+
}
|
|
604
|
+
// Use provided app token or get one from token service
|
|
605
|
+
const token = options?.appToken ?? await getAppToken(options);
|
|
606
|
+
// Determine base URL
|
|
607
|
+
const env = globalThis?.process?.env ?? import.meta?.env ?? undefined;
|
|
608
|
+
const base = options?.baseUrl ?? env?.TALE_BASE_URL ?? undefined;
|
|
609
|
+
if (!base) {
|
|
610
|
+
throw new ConfigurationError('Missing required environment variable: TALE_BASE_URL');
|
|
611
|
+
}
|
|
612
|
+
const url = String(base).replace(/\/+$/, '') + `/acl/v1/templates/${encodeURIComponent(templateId)}`;
|
|
613
|
+
let response;
|
|
614
|
+
try {
|
|
615
|
+
response = await globalThis.fetch(url, {
|
|
616
|
+
method: 'GET',
|
|
617
|
+
headers: {
|
|
618
|
+
'Content-Type': 'application/json',
|
|
619
|
+
'x-t-token': token,
|
|
620
|
+
},
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
catch (error) {
|
|
624
|
+
throw new NetworkError(`Failed to get ACL template: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
625
|
+
}
|
|
626
|
+
let json;
|
|
627
|
+
try {
|
|
628
|
+
const responseJson = await response.json();
|
|
629
|
+
json = responseJson;
|
|
630
|
+
}
|
|
631
|
+
catch (error) {
|
|
632
|
+
throw new ApiError(`Failed to parse ACL template response: ${error instanceof Error ? error.message : 'Invalid JSON'}`, response.status);
|
|
633
|
+
}
|
|
634
|
+
// Handle API errors
|
|
635
|
+
if (json.code !== 200) {
|
|
636
|
+
const errorMsg = typeof json.msg === 'string' ? json.msg : 'ACL template retrieval failed';
|
|
637
|
+
throw new ApiError(errorMsg, response.status, json.code);
|
|
638
|
+
}
|
|
639
|
+
// Validate response structure
|
|
640
|
+
if (!json.data || !json.data.template) {
|
|
641
|
+
throw new ApiError('Invalid ACL template response: missing template data', response.status);
|
|
642
|
+
}
|
|
643
|
+
return json.data;
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Lists ACL templates with pagination and filtering.
|
|
647
|
+
*
|
|
648
|
+
* @param options - Optional parameters for pagination, filtering, and configuration
|
|
649
|
+
* @returns Promise resolving to paginated ACL template list with metadata
|
|
650
|
+
* @throws {ConfigurationError} When required environment variables are missing
|
|
651
|
+
* @throws {ApiError} When API request fails or returns invalid response
|
|
652
|
+
* @throws {NetworkError} When network request fails
|
|
653
|
+
*/
|
|
654
|
+
export async function listTemplates(options) {
|
|
655
|
+
// Use provided app token or get one from token service
|
|
656
|
+
const token = options?.appToken ?? await getAppToken(options);
|
|
657
|
+
// Determine base URL
|
|
658
|
+
const env = globalThis?.process?.env ?? import.meta?.env ?? undefined;
|
|
659
|
+
const base = options?.baseUrl ?? env?.TALE_BASE_URL ?? undefined;
|
|
660
|
+
if (!base) {
|
|
661
|
+
throw new ConfigurationError('Missing required environment variable: TALE_BASE_URL');
|
|
662
|
+
}
|
|
663
|
+
// Build URL with query parameters
|
|
664
|
+
const url = new URL(String(base).replace(/\/+$/, '') + '/acl/v1/templates');
|
|
665
|
+
// Add query parameters with defaults
|
|
666
|
+
const queryParams = {
|
|
667
|
+
page: 0,
|
|
668
|
+
size: 20,
|
|
669
|
+
sort_by: 'created_at',
|
|
670
|
+
sort_direction: 'desc',
|
|
671
|
+
...options
|
|
672
|
+
};
|
|
673
|
+
// Add parameters to URL
|
|
674
|
+
if (queryParams.resource_type) {
|
|
675
|
+
url.searchParams.append('resource_type', queryParams.resource_type);
|
|
676
|
+
}
|
|
677
|
+
if (queryParams.subject_type) {
|
|
678
|
+
url.searchParams.append('subject_type', queryParams.subject_type);
|
|
679
|
+
}
|
|
680
|
+
if (queryParams.page !== undefined) {
|
|
681
|
+
url.searchParams.append('page', String(queryParams.page));
|
|
682
|
+
}
|
|
683
|
+
if (queryParams.size !== undefined) {
|
|
684
|
+
url.searchParams.append('size', String(queryParams.size));
|
|
685
|
+
}
|
|
686
|
+
if (queryParams.sort_by) {
|
|
687
|
+
url.searchParams.append('sort_by', queryParams.sort_by);
|
|
688
|
+
}
|
|
689
|
+
if (queryParams.sort_direction) {
|
|
690
|
+
url.searchParams.append('sort_direction', queryParams.sort_direction);
|
|
691
|
+
}
|
|
692
|
+
let response;
|
|
693
|
+
try {
|
|
694
|
+
response = await globalThis.fetch(url.toString(), {
|
|
695
|
+
method: 'GET',
|
|
696
|
+
headers: {
|
|
697
|
+
'Content-Type': 'application/json',
|
|
698
|
+
'x-t-token': token,
|
|
699
|
+
},
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
catch (error) {
|
|
703
|
+
throw new NetworkError(`Failed to list ACL templates: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
704
|
+
}
|
|
705
|
+
let json;
|
|
706
|
+
try {
|
|
707
|
+
const responseJson = await response.json();
|
|
708
|
+
json = responseJson;
|
|
709
|
+
}
|
|
710
|
+
catch (error) {
|
|
711
|
+
throw new ApiError(`Failed to parse ACL templates list response: ${error instanceof Error ? error.message : 'Invalid JSON'}`, response.status);
|
|
712
|
+
}
|
|
713
|
+
// Handle API errors
|
|
714
|
+
if (json.code !== 200) {
|
|
715
|
+
const errorMsg = typeof json.msg === 'string' ? json.msg : 'ACL templates list retrieval failed';
|
|
716
|
+
throw new ApiError(errorMsg, response.status, json.code);
|
|
717
|
+
}
|
|
718
|
+
// Validate response structure
|
|
719
|
+
if (!json.data || !Array.isArray(json.data.content)) {
|
|
720
|
+
throw new ApiError('Invalid ACL templates list response: missing required data', response.status);
|
|
721
|
+
}
|
|
722
|
+
return json.data;
|
|
723
|
+
}
|
|
724
|
+
// ===== ACL 核心工具函数 (AclCoreUtils) =====
|
|
725
|
+
/**
|
|
726
|
+
* ACL 核心工具函数,包含跨应用通用的业务逻辑
|
|
727
|
+
*/
|
|
728
|
+
export const AclCoreUtils = {
|
|
729
|
+
/**
|
|
730
|
+
* 验证优先级范围
|
|
731
|
+
*/
|
|
732
|
+
validatePriority(priority) {
|
|
733
|
+
return priority >= 0 && priority <= 100;
|
|
734
|
+
},
|
|
735
|
+
/**
|
|
736
|
+
* 获取可用的效果类型
|
|
737
|
+
*/
|
|
738
|
+
getEffectTypes() {
|
|
739
|
+
return ['allow', 'deny', 'inherit'];
|
|
740
|
+
},
|
|
741
|
+
/**
|
|
742
|
+
* 获取可用的主体类型
|
|
743
|
+
*/
|
|
744
|
+
getSubjectTypes() {
|
|
745
|
+
return ['user', 'role', 'group'];
|
|
746
|
+
}
|
|
747
|
+
};
|