@enfyra/sdk-nuxt 0.2.1 → 0.2.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/README.md +168 -10
- package/dist/composables/useEnfyraApi.mjs +158 -7
- package/dist/index.d.ts +64 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/module.cjs +10 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +10 -0
- package/package.json +1 -1
- package/src/composables/useEnfyraApi.ts +218 -24
- package/src/module.ts +14 -0
- package/src/types/index.ts +85 -7
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ Nuxt SDK for Enfyra CMS - A powerful composable-based API client with full SSR s
|
|
|
7
7
|
✅ **SSR & Client-Side Support** - Automatic server-side rendering with `useFetch` or client-side with `$fetch`
|
|
8
8
|
✅ **Authentication Integration** - Built-in auth composables with automatic header forwarding
|
|
9
9
|
✅ **TypeScript Support** - Full type safety with auto-generated declarations
|
|
10
|
-
✅ **Batch Operations** - Efficient bulk operations
|
|
10
|
+
✅ **Batch Operations** - Efficient bulk operations with real-time progress tracking (client-side)
|
|
11
11
|
✅ **Error Handling** - Automatic error management with console logging
|
|
12
12
|
✅ **Reactive State** - Built-in loading, error, and data states
|
|
13
13
|
✅ **Caching Support** - Optional cache keys for SSR mode optimization
|
|
@@ -143,19 +143,36 @@ await execute({
|
|
|
143
143
|
- `query?: Record<string, any>` - URL query parameters
|
|
144
144
|
- `headers?: Record<string, string>` - Custom headers
|
|
145
145
|
- `errorContext?: string` - Error context for logging
|
|
146
|
+
- `onError?: (error: ApiError, context?: string) => void` - Custom error handler
|
|
146
147
|
- `key?: string` - Cache key (SSR mode, optional)
|
|
147
148
|
- `default?: () => T` - Default value (SSR mode only)
|
|
148
149
|
|
|
150
|
+
**Batch Options (only available for PATCH, DELETE, and POST methods):**
|
|
151
|
+
- `batchSize?: number` - Batch size for chunking large operations (default: no limit)
|
|
152
|
+
- `concurrent?: number` - Maximum concurrent requests (default: no limit)
|
|
153
|
+
- `onProgress?: (progress: BatchProgress) => void` - Real-time progress callback for batch operations
|
|
154
|
+
|
|
155
|
+
> 🎯 **TypeScript Smart:** Batch options (`batchSize`, `concurrent`, `onProgress`) are only available in TypeScript IntelliSense when using methods that support batch operations (PATCH, DELETE, POST). For GET and PUT methods, these options won't appear in autocomplete.
|
|
156
|
+
|
|
149
157
|
**⚠️ Important: Return Types Differ**
|
|
150
158
|
- **SSR Mode**: Returns `useFetch` result `{ data, pending, error, refresh }`
|
|
151
159
|
- **Client Mode**: Returns custom result `{ data, pending, error, execute }`
|
|
152
160
|
|
|
153
161
|
**Execute Options (Client mode only):**
|
|
162
|
+
|
|
163
|
+
**Basic Options:**
|
|
154
164
|
- `id?: string | number` - Single resource ID
|
|
155
|
-
- `ids?: (string | number)[]` - Batch operation IDs (PATCH/DELETE)
|
|
156
|
-
- `files?: FormData[]` - Batch file upload (POST)
|
|
157
165
|
- `body?: any` - Override request body
|
|
158
166
|
|
|
167
|
+
**Batch Options (only when using `ids` or `files`):**
|
|
168
|
+
- `ids?: (string | number)[]` - Batch operation IDs (PATCH/DELETE)
|
|
169
|
+
- `files?: FormData[]` - Array of FormData objects for batch upload (POST)
|
|
170
|
+
- `batchSize?: number` - Override batch size for this execution
|
|
171
|
+
- `concurrent?: number` - Override concurrent limit for this execution
|
|
172
|
+
- `onProgress?: (progress: BatchProgress) => void` - Override progress callback for this execution
|
|
173
|
+
|
|
174
|
+
> 🎯 **TypeScript Smart:** Batch execute options (`batchSize`, `concurrent`, `onProgress`) are only available when you provide `ids` or `files` parameters, ensuring type safety.
|
|
175
|
+
|
|
159
176
|
### `useEnfyraAuth()`
|
|
160
177
|
|
|
161
178
|
Authentication composable with reactive state management.
|
|
@@ -178,7 +195,8 @@ await fetchUser() // Refresh user data
|
|
|
178
195
|
### Batch Operations
|
|
179
196
|
|
|
180
197
|
```typescript
|
|
181
|
-
//
|
|
198
|
+
// Basic batch delete - unlimited parallel requests
|
|
199
|
+
// 🎯 Note: Batch options only appear in IntelliSense for DELETE method
|
|
182
200
|
const { execute: deleteItems } = useEnfyraApi('/items', {
|
|
183
201
|
method: 'delete',
|
|
184
202
|
errorContext: 'Delete Items'
|
|
@@ -186,15 +204,155 @@ const { execute: deleteItems } = useEnfyraApi('/items', {
|
|
|
186
204
|
|
|
187
205
|
await deleteItems({ ids: ['1', '2', '3'] });
|
|
188
206
|
|
|
189
|
-
//
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
207
|
+
// Advanced batch operations with concurrency control
|
|
208
|
+
// 🎯 TypeScript shows batch options (batchSize, concurrent, onProgress) for DELETE method
|
|
209
|
+
const { execute: deleteMany } = useEnfyraApi('/users', {
|
|
210
|
+
method: 'delete',
|
|
211
|
+
batchSize: 10, // ✅ Available: DELETE method supports batching
|
|
212
|
+
concurrent: 3, // ✅ Available: DELETE method supports batching
|
|
213
|
+
onProgress: (progress) => { // ✅ Available: DELETE method supports batching
|
|
214
|
+
console.log(`Deleting: ${progress.completed}/${progress.total}`);
|
|
215
|
+
},
|
|
216
|
+
onError: (error, context) => toast.error(`${context}: ${error.message}`)
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// GET method example - batch options NOT available
|
|
220
|
+
// 🎯 TypeScript won't show batch options for GET method
|
|
221
|
+
const { execute: getUsers } = useEnfyraApi('/users', {
|
|
222
|
+
method: 'get',
|
|
223
|
+
// batchSize: 10, // ❌ Not available: GET doesn't support batching
|
|
224
|
+
// concurrent: 3, // ❌ Not available: GET doesn't support batching
|
|
225
|
+
// onProgress: () => {} // ❌ Not available: GET doesn't support batching
|
|
226
|
+
errorContext: 'Fetch Users'
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// Delete 100 users in controlled batches
|
|
230
|
+
await deleteMany({ ids: Array.from({length: 100}, (_, i) => `user-${i}`) });
|
|
231
|
+
|
|
232
|
+
// Override batch settings per execution
|
|
233
|
+
// 🎯 TypeScript shows batch options for PATCH method
|
|
234
|
+
const { execute: updateUsers } = useEnfyraApi('/users', {
|
|
235
|
+
method: 'patch',
|
|
236
|
+
batchSize: 20, // ✅ Available: PATCH method supports batching
|
|
237
|
+
concurrent: 5 // ✅ Available: PATCH method supports batching
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// This execution uses different settings
|
|
241
|
+
// 🎯 Batch options in execute() only available when using `ids` or `files`
|
|
242
|
+
await updateUsers({
|
|
243
|
+
ids: largeUserList, // ✅ Triggers batch mode
|
|
244
|
+
body: { status: 'active' },
|
|
245
|
+
batchSize: 50, // ✅ Available: Using `ids` parameter
|
|
246
|
+
concurrent: 10, // ✅ Available: Using `ids` parameter
|
|
247
|
+
onProgress: (progress) => { // ✅ Available: Using `ids` parameter
|
|
248
|
+
console.log(`Updating: ${progress.completed}/${progress.total}`);
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Single operation - batch options NOT available in execute
|
|
253
|
+
await updateUsers({
|
|
254
|
+
id: 'single-user-id', // ❌ Single operation, no batch options
|
|
255
|
+
body: { status: 'active' }
|
|
256
|
+
// batchSize: 50, // ❌ Not available: Not using `ids` or `files`
|
|
257
|
+
// concurrent: 10, // ❌ Not available: Not using `ids` or `files`
|
|
258
|
+
// onProgress: () => {} // ❌ Not available: Not using `ids` or `files`
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
// Batch file upload with real-time progress tracking
|
|
262
|
+
const progressState = ref({
|
|
263
|
+
progress: 0,
|
|
264
|
+
completed: 0,
|
|
265
|
+
total: 0,
|
|
266
|
+
failed: 0,
|
|
267
|
+
estimatedTimeRemaining: 0,
|
|
268
|
+
operationsPerSecond: 0
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// 🎯 TypeScript shows batch options for POST method (supports file uploads)
|
|
272
|
+
const { execute: uploadFiles } = useEnfyraApi('/file_definition', {
|
|
273
|
+
method: 'post',
|
|
274
|
+
batchSize: 5, // ✅ Available: POST method supports batching for files
|
|
275
|
+
concurrent: 2, // ✅ Available: POST method supports batching for files
|
|
276
|
+
errorContext: 'Upload Files',
|
|
277
|
+
onProgress: (progress) => { // ✅ Available: POST method supports batching
|
|
278
|
+
progressState.value = progress;
|
|
279
|
+
console.log(`Progress: ${progress.progress}% (${progress.completed}/${progress.total})`);
|
|
280
|
+
console.log(`ETA: ${Math.round((progress.estimatedTimeRemaining || 0) / 1000)}s`);
|
|
281
|
+
console.log(`Speed: ${progress.operationsPerSecond?.toFixed(1)} ops/sec`);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// Convert files to FormData array (matches enfyra_app pattern)
|
|
286
|
+
const formDataArray = selectedFiles.map(file => {
|
|
287
|
+
const formData = new FormData();
|
|
288
|
+
formData.append('file', file);
|
|
289
|
+
formData.append('folder', folderId || 'null');
|
|
290
|
+
return formData;
|
|
193
291
|
});
|
|
194
292
|
|
|
195
293
|
await uploadFiles({
|
|
196
|
-
files:
|
|
294
|
+
files: formDataArray // Array of FormData objects
|
|
197
295
|
});
|
|
296
|
+
|
|
297
|
+
// Real-time progress tracking with detailed results
|
|
298
|
+
const { execute: processData } = useEnfyraApi('/process', {
|
|
299
|
+
method: 'post',
|
|
300
|
+
batchSize: 10,
|
|
301
|
+
concurrent: 3,
|
|
302
|
+
onProgress: (progress) => {
|
|
303
|
+
// Display progress bar
|
|
304
|
+
updateProgressBar(progress.progress);
|
|
305
|
+
|
|
306
|
+
// Show detailed metrics
|
|
307
|
+
console.log('Batch Progress:', {
|
|
308
|
+
percentage: progress.progress,
|
|
309
|
+
completed: progress.completed,
|
|
310
|
+
total: progress.total,
|
|
311
|
+
failed: progress.failed,
|
|
312
|
+
currentBatch: progress.currentBatch,
|
|
313
|
+
totalBatches: progress.totalBatches,
|
|
314
|
+
averageTime: progress.averageTime,
|
|
315
|
+
estimatedTimeRemaining: progress.estimatedTimeRemaining,
|
|
316
|
+
operationsPerSecond: progress.operationsPerSecond
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Handle individual results
|
|
320
|
+
progress.results.forEach(result => {
|
|
321
|
+
if (result.status === 'failed') {
|
|
322
|
+
console.error(`Item ${result.index} failed:`, result.error);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
await processData({
|
|
329
|
+
ids: largeDataSet,
|
|
330
|
+
body: processingOptions
|
|
331
|
+
});
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### Real-time Progress Interface
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
interface BatchProgress {
|
|
338
|
+
progress: number; // 0-100 percentage
|
|
339
|
+
completed: number; // Number of completed operations
|
|
340
|
+
total: number; // Total number of operations
|
|
341
|
+
failed: number; // Number of failed operations
|
|
342
|
+
inProgress: number; // Operations currently running
|
|
343
|
+
estimatedTimeRemaining?: number; // Milliseconds remaining
|
|
344
|
+
averageTime?: number; // Average time per operation (ms)
|
|
345
|
+
currentBatch: number; // Current batch being processed
|
|
346
|
+
totalBatches: number; // Total number of batches
|
|
347
|
+
operationsPerSecond?: number; // Processing speed
|
|
348
|
+
results: Array<{ // Detailed results
|
|
349
|
+
index: number;
|
|
350
|
+
status: 'completed' | 'failed';
|
|
351
|
+
result?: any;
|
|
352
|
+
error?: ApiError;
|
|
353
|
+
duration?: number;
|
|
354
|
+
}>;
|
|
355
|
+
}
|
|
198
356
|
```
|
|
199
357
|
|
|
200
358
|
### TypeScript Integration
|
|
@@ -269,7 +427,7 @@ export default defineNuxtConfig({
|
|
|
269
427
|
// Required: Main API URL
|
|
270
428
|
apiUrl: process.env.ENFYRA_API_URL || "http://localhost:1105",
|
|
271
429
|
|
|
272
|
-
// Optional: API path prefix (default: '')
|
|
430
|
+
// Optional: API path prefix (default: '/api')
|
|
273
431
|
apiPrefix: '/api/v1',
|
|
274
432
|
|
|
275
433
|
// Required: App URL for SSR requests
|
|
@@ -16,7 +16,7 @@ function handleError(error, context, customHandler) {
|
|
|
16
16
|
return apiError;
|
|
17
17
|
}
|
|
18
18
|
export function useEnfyraApi(path, opts = {}) {
|
|
19
|
-
const { method = "get", body, query, errorContext, onError, ssr, key } = opts;
|
|
19
|
+
const { method = "get", body, query, errorContext, onError, ssr, key, batchSize, concurrent, onProgress } = opts;
|
|
20
20
|
if (ssr) {
|
|
21
21
|
const config = useRuntimeConfig().public.enfyraSDK;
|
|
22
22
|
const basePath = (typeof path === "function" ? path() : path).replace(/^\/?api\/?/, "").replace(/^\/+/, "");
|
|
@@ -65,12 +65,165 @@ export function useEnfyraApi(path, opts = {}) {
|
|
|
65
65
|
const basePath = (typeof path === "function" ? path() : path).replace(/^\/?api\/?/, "").replace(/^\/+/, "");
|
|
66
66
|
const finalBody = executeOpts?.body || unref(body);
|
|
67
67
|
const finalQuery = unref(query);
|
|
68
|
+
const isBatchOperation = !opts.disableBatch && (executeOpts?.ids && executeOpts.ids.length > 0 && (method.toLowerCase() === "patch" || method.toLowerCase() === "delete") || method.toLowerCase() === "post" && executeOpts?.files && Array.isArray(executeOpts.files) && executeOpts.files.length > 0);
|
|
69
|
+
const effectiveBatchSize = isBatchOperation ? executeOpts?.batchSize ?? batchSize : void 0;
|
|
70
|
+
const effectiveConcurrent = isBatchOperation ? executeOpts?.concurrent ?? concurrent : void 0;
|
|
71
|
+
const effectiveOnProgress = isBatchOperation ? executeOpts?.onProgress ?? onProgress : void 0;
|
|
68
72
|
const buildPath = (...segments) => {
|
|
69
73
|
return segments.filter(Boolean).join("/");
|
|
70
74
|
};
|
|
71
75
|
const fullBaseURL = apiUrl + (apiPrefix || "");
|
|
72
|
-
|
|
73
|
-
const
|
|
76
|
+
async function processBatch(items, processor) {
|
|
77
|
+
const results = [];
|
|
78
|
+
const progressResults = [];
|
|
79
|
+
const startTime = Date.now();
|
|
80
|
+
let completed = 0;
|
|
81
|
+
let failed = 0;
|
|
82
|
+
const chunks = effectiveBatchSize ? Array.from(
|
|
83
|
+
{ length: Math.ceil(items.length / effectiveBatchSize) },
|
|
84
|
+
(_, i) => items.slice(i * effectiveBatchSize, i * effectiveBatchSize + effectiveBatchSize)
|
|
85
|
+
) : [items];
|
|
86
|
+
const totalBatches = chunks.length;
|
|
87
|
+
let currentBatch = 0;
|
|
88
|
+
const updateProgress = (inProgress = 0) => {
|
|
89
|
+
if (effectiveOnProgress) {
|
|
90
|
+
const elapsed = Date.now() - startTime;
|
|
91
|
+
const progress = items.length > 0 ? Math.round(completed / items.length * 100) : 0;
|
|
92
|
+
const averageTime = completed > 0 ? elapsed / completed : void 0;
|
|
93
|
+
const operationsPerSecond = completed > 0 ? completed / elapsed * 1e3 : void 0;
|
|
94
|
+
const estimatedTimeRemaining = averageTime && items.length > completed ? Math.round(averageTime * (items.length - completed)) : void 0;
|
|
95
|
+
const progressData = {
|
|
96
|
+
progress,
|
|
97
|
+
completed,
|
|
98
|
+
total: items.length,
|
|
99
|
+
failed,
|
|
100
|
+
inProgress,
|
|
101
|
+
estimatedTimeRemaining,
|
|
102
|
+
averageTime,
|
|
103
|
+
currentBatch: currentBatch + 1,
|
|
104
|
+
totalBatches,
|
|
105
|
+
operationsPerSecond,
|
|
106
|
+
results: [...progressResults]
|
|
107
|
+
};
|
|
108
|
+
effectiveOnProgress(progressData);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
updateProgress(0);
|
|
112
|
+
if (!effectiveBatchSize && !effectiveConcurrent) {
|
|
113
|
+
updateProgress(items.length);
|
|
114
|
+
const promises = items.map(async (item, index) => {
|
|
115
|
+
const itemStartTime = Date.now();
|
|
116
|
+
try {
|
|
117
|
+
const result = await processor(item, index);
|
|
118
|
+
const duration = Date.now() - itemStartTime;
|
|
119
|
+
completed++;
|
|
120
|
+
progressResults.push({
|
|
121
|
+
index,
|
|
122
|
+
status: "completed",
|
|
123
|
+
result,
|
|
124
|
+
duration
|
|
125
|
+
});
|
|
126
|
+
updateProgress(items.length - completed);
|
|
127
|
+
return result;
|
|
128
|
+
} catch (error2) {
|
|
129
|
+
const duration = Date.now() - itemStartTime;
|
|
130
|
+
failed++;
|
|
131
|
+
completed++;
|
|
132
|
+
progressResults.push({
|
|
133
|
+
index,
|
|
134
|
+
status: "failed",
|
|
135
|
+
error: error2,
|
|
136
|
+
duration
|
|
137
|
+
});
|
|
138
|
+
updateProgress(items.length - completed);
|
|
139
|
+
throw error2;
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
const results2 = await Promise.all(promises);
|
|
143
|
+
updateProgress(0);
|
|
144
|
+
return results2;
|
|
145
|
+
}
|
|
146
|
+
for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
|
|
147
|
+
currentBatch = chunkIndex;
|
|
148
|
+
const chunk = chunks[chunkIndex];
|
|
149
|
+
if (effectiveConcurrent && chunk.length > effectiveConcurrent) {
|
|
150
|
+
for (let i = 0; i < chunk.length; i += effectiveConcurrent) {
|
|
151
|
+
const batch = chunk.slice(i, i + effectiveConcurrent);
|
|
152
|
+
const baseIndex = chunkIndex * (effectiveBatchSize || items.length) + i;
|
|
153
|
+
updateProgress(batch.length);
|
|
154
|
+
const batchPromises = batch.map(async (item, batchItemIndex) => {
|
|
155
|
+
const globalIndex = baseIndex + batchItemIndex;
|
|
156
|
+
const itemStartTime = Date.now();
|
|
157
|
+
try {
|
|
158
|
+
const result = await processor(item, globalIndex);
|
|
159
|
+
const duration = Date.now() - itemStartTime;
|
|
160
|
+
completed++;
|
|
161
|
+
progressResults.push({
|
|
162
|
+
index: globalIndex,
|
|
163
|
+
status: "completed",
|
|
164
|
+
result,
|
|
165
|
+
duration
|
|
166
|
+
});
|
|
167
|
+
updateProgress(Math.max(0, batch.length - (batchItemIndex + 1)));
|
|
168
|
+
return result;
|
|
169
|
+
} catch (error2) {
|
|
170
|
+
const duration = Date.now() - itemStartTime;
|
|
171
|
+
failed++;
|
|
172
|
+
completed++;
|
|
173
|
+
progressResults.push({
|
|
174
|
+
index: globalIndex,
|
|
175
|
+
status: "failed",
|
|
176
|
+
error: error2,
|
|
177
|
+
duration
|
|
178
|
+
});
|
|
179
|
+
updateProgress(Math.max(0, batch.length - (batchItemIndex + 1)));
|
|
180
|
+
throw error2;
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
const batchResults = await Promise.all(batchPromises);
|
|
184
|
+
results.push(...batchResults);
|
|
185
|
+
}
|
|
186
|
+
} else {
|
|
187
|
+
const baseIndex = chunkIndex * (effectiveBatchSize || items.length);
|
|
188
|
+
updateProgress(chunk.length);
|
|
189
|
+
const chunkPromises = chunk.map(async (item, chunkItemIndex) => {
|
|
190
|
+
const globalIndex = baseIndex + chunkItemIndex;
|
|
191
|
+
const itemStartTime = Date.now();
|
|
192
|
+
try {
|
|
193
|
+
const result = await processor(item, globalIndex);
|
|
194
|
+
const duration = Date.now() - itemStartTime;
|
|
195
|
+
completed++;
|
|
196
|
+
progressResults.push({
|
|
197
|
+
index: globalIndex,
|
|
198
|
+
status: "completed",
|
|
199
|
+
result,
|
|
200
|
+
duration
|
|
201
|
+
});
|
|
202
|
+
updateProgress(Math.max(0, chunk.length - (chunkItemIndex + 1)));
|
|
203
|
+
return result;
|
|
204
|
+
} catch (error2) {
|
|
205
|
+
const duration = Date.now() - itemStartTime;
|
|
206
|
+
failed++;
|
|
207
|
+
completed++;
|
|
208
|
+
progressResults.push({
|
|
209
|
+
index: globalIndex,
|
|
210
|
+
status: "failed",
|
|
211
|
+
error: error2,
|
|
212
|
+
duration
|
|
213
|
+
});
|
|
214
|
+
updateProgress(Math.max(0, chunk.length - (chunkItemIndex + 1)));
|
|
215
|
+
throw error2;
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
const chunkResults = await Promise.all(chunkPromises);
|
|
219
|
+
results.push(...chunkResults);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
updateProgress(0);
|
|
223
|
+
return results;
|
|
224
|
+
}
|
|
225
|
+
if (isBatchOperation && executeOpts?.ids && executeOpts.ids.length > 0) {
|
|
226
|
+
const responses = await processBatch(executeOpts.ids, async (id, index) => {
|
|
74
227
|
const finalPath2 = buildPath(basePath, id);
|
|
75
228
|
return $fetch(finalPath2, {
|
|
76
229
|
baseURL: fullBaseURL,
|
|
@@ -80,12 +233,11 @@ export function useEnfyraApi(path, opts = {}) {
|
|
|
80
233
|
query: finalQuery
|
|
81
234
|
});
|
|
82
235
|
});
|
|
83
|
-
const responses = await Promise.all(promises);
|
|
84
236
|
data.value = responses;
|
|
85
237
|
return responses;
|
|
86
238
|
}
|
|
87
|
-
if (
|
|
88
|
-
const
|
|
239
|
+
if (isBatchOperation && executeOpts?.files && Array.isArray(executeOpts.files) && executeOpts.files.length > 0) {
|
|
240
|
+
const responses = await processBatch(executeOpts.files, async (fileObj, index) => {
|
|
89
241
|
return $fetch(basePath, {
|
|
90
242
|
baseURL: fullBaseURL,
|
|
91
243
|
method,
|
|
@@ -95,7 +247,6 @@ export function useEnfyraApi(path, opts = {}) {
|
|
|
95
247
|
query: finalQuery
|
|
96
248
|
});
|
|
97
249
|
});
|
|
98
|
-
const responses = await Promise.all(promises);
|
|
99
250
|
data.value = responses;
|
|
100
251
|
return responses;
|
|
101
252
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,7 +9,37 @@ export interface ApiError {
|
|
|
9
9
|
data?: any;
|
|
10
10
|
response?: any;
|
|
11
11
|
}
|
|
12
|
-
export interface
|
|
12
|
+
export interface BatchProgress {
|
|
13
|
+
/** Current progress percentage (0-100) */
|
|
14
|
+
progress: number;
|
|
15
|
+
/** Number of completed operations */
|
|
16
|
+
completed: number;
|
|
17
|
+
/** Total number of operations */
|
|
18
|
+
total: number;
|
|
19
|
+
/** Number of failed operations */
|
|
20
|
+
failed: number;
|
|
21
|
+
/** Number of operations currently in progress */
|
|
22
|
+
inProgress: number;
|
|
23
|
+
/** Estimated time remaining in milliseconds */
|
|
24
|
+
estimatedTimeRemaining?: number;
|
|
25
|
+
/** Average time per operation in milliseconds */
|
|
26
|
+
averageTime?: number;
|
|
27
|
+
/** Current batch being processed */
|
|
28
|
+
currentBatch: number;
|
|
29
|
+
/** Total number of batches */
|
|
30
|
+
totalBatches: number;
|
|
31
|
+
/** Processing speed (operations per second) */
|
|
32
|
+
operationsPerSecond?: number;
|
|
33
|
+
/** Detailed results array for completed operations */
|
|
34
|
+
results: Array<{
|
|
35
|
+
index: number;
|
|
36
|
+
status: 'completed' | 'failed';
|
|
37
|
+
result?: any;
|
|
38
|
+
error?: ApiError;
|
|
39
|
+
duration?: number;
|
|
40
|
+
}>;
|
|
41
|
+
}
|
|
42
|
+
interface BaseApiOptions<T> {
|
|
13
43
|
method?: 'get' | 'post' | 'put' | 'patch' | 'delete' | 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
14
44
|
body?: any;
|
|
15
45
|
query?: Record<string, any>;
|
|
@@ -23,6 +53,22 @@ export interface ApiOptions<T> {
|
|
|
23
53
|
/** Unique key for useFetch caching */
|
|
24
54
|
key?: string;
|
|
25
55
|
}
|
|
56
|
+
interface BatchApiOptions {
|
|
57
|
+
/** Batch size for chunking large operations (default: no limit) - Only available for batch operations */
|
|
58
|
+
batchSize?: number;
|
|
59
|
+
/** Maximum concurrent requests (default: no limit) - Only available for batch operations */
|
|
60
|
+
concurrent?: number;
|
|
61
|
+
/** Real-time progress callback for batch operations - Only available for batch operations */
|
|
62
|
+
onProgress?: (progress: BatchProgress) => void;
|
|
63
|
+
}
|
|
64
|
+
type ConditionalBatchOptions<T> = T extends {
|
|
65
|
+
method?: 'patch' | 'delete' | 'PATCH' | 'DELETE';
|
|
66
|
+
} ? BatchApiOptions : T extends {
|
|
67
|
+
method?: 'post' | 'POST';
|
|
68
|
+
} ? BatchApiOptions : T extends {
|
|
69
|
+
method?: undefined;
|
|
70
|
+
} ? Partial<BatchApiOptions> : {};
|
|
71
|
+
export type ApiOptions<T> = BaseApiOptions<T> & ConditionalBatchOptions<BaseApiOptions<T>>;
|
|
26
72
|
export interface BackendError {
|
|
27
73
|
success: false;
|
|
28
74
|
message: string;
|
|
@@ -46,16 +92,27 @@ export interface UseEnfyraApiSSRReturn<T> extends AsyncData<T | null, ApiError>
|
|
|
46
92
|
error: Ref<ApiError | null>;
|
|
47
93
|
refresh: () => Promise<void>;
|
|
48
94
|
}
|
|
95
|
+
interface BaseExecuteOptions {
|
|
96
|
+
body?: any;
|
|
97
|
+
id?: string | number;
|
|
98
|
+
}
|
|
99
|
+
interface BatchExecuteOptions {
|
|
100
|
+
ids?: (string | number)[];
|
|
101
|
+
/** Array of FormData objects for batch upload */
|
|
102
|
+
files?: FormData[];
|
|
103
|
+
/** Override batch size for this specific execution */
|
|
104
|
+
batchSize?: number;
|
|
105
|
+
/** Override concurrent limit for this specific execution */
|
|
106
|
+
concurrent?: number;
|
|
107
|
+
/** Override progress callback for this specific execution */
|
|
108
|
+
onProgress?: (progress: BatchProgress) => void;
|
|
109
|
+
}
|
|
110
|
+
export type ExecuteOptions = BaseExecuteOptions & BatchExecuteOptions;
|
|
49
111
|
export interface UseEnfyraApiClientReturn<T> {
|
|
50
112
|
data: Ref<T | null>;
|
|
51
113
|
error: Ref<ApiError | null>;
|
|
52
114
|
pending: Ref<boolean>;
|
|
53
|
-
execute: (executeOpts?:
|
|
54
|
-
body?: any;
|
|
55
|
-
id?: string | number;
|
|
56
|
-
ids?: (string | number)[];
|
|
57
|
-
files?: any[];
|
|
58
|
-
}) => Promise<T | T[] | null>;
|
|
115
|
+
execute: (executeOpts?: ExecuteOptions) => Promise<T | T[] | null>;
|
|
59
116
|
}
|
|
60
117
|
export * from './auth';
|
|
61
118
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,QAAQ,CAAC,EAAE,GAAG,CAAC;CAChB;AAED,MAAM,WAAW,UAAU,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/types/index.ts"],"names":[],"mappings":"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"}
|
package/dist/module.cjs
CHANGED
|
@@ -15,6 +15,16 @@ const module$1 = kit.defineNuxtModule({
|
|
|
15
15
|
},
|
|
16
16
|
setup(options, nuxt) {
|
|
17
17
|
const { resolve } = kit.createResolver((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('module.cjs', document.baseURI).href)));
|
|
18
|
+
if (!options.apiUrl || !options.appUrl) {
|
|
19
|
+
throw new Error(
|
|
20
|
+
`[Enfyra SDK Nuxt] Missing required configuration:
|
|
21
|
+
${!options.apiUrl ? "- apiUrl is required\n" : ""}${!options.appUrl ? "- appUrl is required\n" : ""}Please configure both in your nuxt.config.ts:
|
|
22
|
+
enfyraSDK: {
|
|
23
|
+
apiUrl: 'https://your-api-url',
|
|
24
|
+
appUrl: 'https://your-app-url'
|
|
25
|
+
}`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
18
28
|
nuxt.options.runtimeConfig.public.enfyraSDK = {
|
|
19
29
|
...nuxt.options.runtimeConfig.public.enfyraSDK,
|
|
20
30
|
...options
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -12,6 +12,16 @@ const module = defineNuxtModule({
|
|
|
12
12
|
},
|
|
13
13
|
setup(options, nuxt) {
|
|
14
14
|
const { resolve } = createResolver(import.meta.url);
|
|
15
|
+
if (!options.apiUrl || !options.appUrl) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`[Enfyra SDK Nuxt] Missing required configuration:
|
|
18
|
+
${!options.apiUrl ? "- apiUrl is required\n" : ""}${!options.appUrl ? "- appUrl is required\n" : ""}Please configure both in your nuxt.config.ts:
|
|
19
|
+
enfyraSDK: {
|
|
20
|
+
apiUrl: 'https://your-api-url',
|
|
21
|
+
appUrl: 'https://your-app-url'
|
|
22
|
+
}`
|
|
23
|
+
);
|
|
24
|
+
}
|
|
15
25
|
nuxt.options.runtimeConfig.public.enfyraSDK = {
|
|
16
26
|
...nuxt.options.runtimeConfig.public.enfyraSDK,
|
|
17
27
|
...options
|
package/package.json
CHANGED
|
@@ -3,8 +3,10 @@ import type {
|
|
|
3
3
|
ApiOptions,
|
|
4
4
|
ApiError,
|
|
5
5
|
BackendErrorExtended,
|
|
6
|
+
ExecuteOptions,
|
|
6
7
|
UseEnfyraApiSSRReturn,
|
|
7
8
|
UseEnfyraApiClientReturn,
|
|
9
|
+
BatchProgress,
|
|
8
10
|
} from "../types";
|
|
9
11
|
import { $fetch } from "../utils/http";
|
|
10
12
|
import { useRuntimeConfig, useFetch, useRequestHeaders } from "#imports";
|
|
@@ -46,7 +48,7 @@ export function useEnfyraApi<T = any>(
|
|
|
46
48
|
path: (() => string) | string,
|
|
47
49
|
opts: ApiOptions<T> = {}
|
|
48
50
|
): UseEnfyraApiSSRReturn<T> | UseEnfyraApiClientReturn<T> {
|
|
49
|
-
const { method = "get", body, query, errorContext, onError, ssr, key } = opts;
|
|
51
|
+
const { method = "get", body, query, errorContext, onError, ssr, key, batchSize, concurrent, onProgress } = opts;
|
|
50
52
|
|
|
51
53
|
// SSR mode - use useFetch
|
|
52
54
|
if (ssr) {
|
|
@@ -101,12 +103,7 @@ export function useEnfyraApi<T = any>(
|
|
|
101
103
|
const error = ref<ApiError | null>(null);
|
|
102
104
|
const pending = ref(false);
|
|
103
105
|
|
|
104
|
-
const execute = async (executeOpts?: {
|
|
105
|
-
body?: any;
|
|
106
|
-
id?: string | number;
|
|
107
|
-
ids?: (string | number)[];
|
|
108
|
-
files?: any[];
|
|
109
|
-
}) => {
|
|
106
|
+
const execute = async (executeOpts?: ExecuteOptions) => {
|
|
110
107
|
pending.value = true;
|
|
111
108
|
error.value = null;
|
|
112
109
|
|
|
@@ -120,6 +117,20 @@ export function useEnfyraApi<T = any>(
|
|
|
120
117
|
.replace(/^\/+/, ""); // Remove leading slashes
|
|
121
118
|
const finalBody = executeOpts?.body || unref(body);
|
|
122
119
|
const finalQuery = unref(query);
|
|
120
|
+
|
|
121
|
+
// Check if this is actually a batch operation
|
|
122
|
+
const isBatchOperation = (
|
|
123
|
+
!opts.disableBatch &&
|
|
124
|
+
(
|
|
125
|
+
(executeOpts?.ids && executeOpts.ids.length > 0 && (method.toLowerCase() === "patch" || method.toLowerCase() === "delete")) ||
|
|
126
|
+
(method.toLowerCase() === "post" && executeOpts?.files && Array.isArray(executeOpts.files) && executeOpts.files.length > 0)
|
|
127
|
+
)
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// Use batch options only if this is actually a batch operation
|
|
131
|
+
const effectiveBatchSize = isBatchOperation ? (executeOpts?.batchSize ?? batchSize) : undefined;
|
|
132
|
+
const effectiveConcurrent = isBatchOperation ? (executeOpts?.concurrent ?? concurrent) : undefined;
|
|
133
|
+
const effectiveOnProgress = isBatchOperation ? (executeOpts?.onProgress ?? onProgress) : undefined;
|
|
123
134
|
|
|
124
135
|
// Helper function to build clean path
|
|
125
136
|
const buildPath = (...segments: (string | number)[]): string => {
|
|
@@ -129,14 +140,205 @@ export function useEnfyraApi<T = any>(
|
|
|
129
140
|
// Build full base URL with prefix
|
|
130
141
|
const fullBaseURL = apiUrl + (apiPrefix || "");
|
|
131
142
|
|
|
143
|
+
// Helper function for batch processing with chunking, concurrency control, and real-time progress tracking
|
|
144
|
+
async function processBatch<T>(
|
|
145
|
+
items: any[],
|
|
146
|
+
processor: (item: any, index: number) => Promise<T>
|
|
147
|
+
): Promise<T[]> {
|
|
148
|
+
const results: T[] = [];
|
|
149
|
+
const progressResults: BatchProgress['results'] = [];
|
|
150
|
+
const startTime = Date.now();
|
|
151
|
+
let completed = 0;
|
|
152
|
+
let failed = 0;
|
|
153
|
+
|
|
154
|
+
// Calculate batch structure
|
|
155
|
+
const chunks = effectiveBatchSize ?
|
|
156
|
+
Array.from({ length: Math.ceil(items.length / effectiveBatchSize) }, (_, i) =>
|
|
157
|
+
items.slice(i * effectiveBatchSize, i * effectiveBatchSize + effectiveBatchSize)
|
|
158
|
+
) : [items];
|
|
159
|
+
|
|
160
|
+
const totalBatches = chunks.length;
|
|
161
|
+
let currentBatch = 0;
|
|
162
|
+
|
|
163
|
+
// Initialize progress tracking
|
|
164
|
+
const updateProgress = (inProgress: number = 0) => {
|
|
165
|
+
if (effectiveOnProgress) {
|
|
166
|
+
const elapsed = Date.now() - startTime;
|
|
167
|
+
const progress = items.length > 0 ? Math.round((completed / items.length) * 100) : 0;
|
|
168
|
+
const averageTime = completed > 0 ? elapsed / completed : undefined;
|
|
169
|
+
const operationsPerSecond = completed > 0 ? (completed / elapsed) * 1000 : undefined;
|
|
170
|
+
const estimatedTimeRemaining = averageTime && items.length > completed
|
|
171
|
+
? Math.round(averageTime * (items.length - completed))
|
|
172
|
+
: undefined;
|
|
173
|
+
|
|
174
|
+
const progressData: BatchProgress = {
|
|
175
|
+
progress,
|
|
176
|
+
completed,
|
|
177
|
+
total: items.length,
|
|
178
|
+
failed,
|
|
179
|
+
inProgress,
|
|
180
|
+
estimatedTimeRemaining,
|
|
181
|
+
averageTime,
|
|
182
|
+
currentBatch: currentBatch + 1,
|
|
183
|
+
totalBatches,
|
|
184
|
+
operationsPerSecond,
|
|
185
|
+
results: [...progressResults]
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
effectiveOnProgress(progressData);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
// Initial progress update
|
|
193
|
+
updateProgress(0);
|
|
194
|
+
|
|
195
|
+
// If no limits, process all at once with progress tracking
|
|
196
|
+
if (!effectiveBatchSize && !effectiveConcurrent) {
|
|
197
|
+
updateProgress(items.length);
|
|
198
|
+
|
|
199
|
+
const promises = items.map(async (item, index) => {
|
|
200
|
+
const itemStartTime = Date.now();
|
|
201
|
+
try {
|
|
202
|
+
const result = await processor(item, index);
|
|
203
|
+
const duration = Date.now() - itemStartTime;
|
|
204
|
+
|
|
205
|
+
completed++;
|
|
206
|
+
progressResults.push({
|
|
207
|
+
index,
|
|
208
|
+
status: 'completed',
|
|
209
|
+
result,
|
|
210
|
+
duration
|
|
211
|
+
});
|
|
212
|
+
updateProgress(items.length - completed);
|
|
213
|
+
|
|
214
|
+
return result;
|
|
215
|
+
} catch (error) {
|
|
216
|
+
const duration = Date.now() - itemStartTime;
|
|
217
|
+
failed++;
|
|
218
|
+
completed++;
|
|
219
|
+
|
|
220
|
+
progressResults.push({
|
|
221
|
+
index,
|
|
222
|
+
status: 'failed',
|
|
223
|
+
error: error as ApiError,
|
|
224
|
+
duration
|
|
225
|
+
});
|
|
226
|
+
updateProgress(items.length - completed);
|
|
227
|
+
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const results = await Promise.all(promises);
|
|
233
|
+
updateProgress(0);
|
|
234
|
+
return results;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Process each chunk with progress tracking
|
|
238
|
+
for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
|
|
239
|
+
currentBatch = chunkIndex;
|
|
240
|
+
const chunk = chunks[chunkIndex];
|
|
241
|
+
|
|
242
|
+
if (effectiveConcurrent && chunk.length > effectiveConcurrent) {
|
|
243
|
+
// Process chunk with concurrency limit
|
|
244
|
+
for (let i = 0; i < chunk.length; i += effectiveConcurrent) {
|
|
245
|
+
const batch = chunk.slice(i, i + effectiveConcurrent);
|
|
246
|
+
const baseIndex = chunkIndex * (effectiveBatchSize || items.length) + i;
|
|
247
|
+
|
|
248
|
+
updateProgress(batch.length);
|
|
249
|
+
|
|
250
|
+
const batchPromises = batch.map(async (item, batchItemIndex) => {
|
|
251
|
+
const globalIndex = baseIndex + batchItemIndex;
|
|
252
|
+
const itemStartTime = Date.now();
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
const result = await processor(item, globalIndex);
|
|
256
|
+
const duration = Date.now() - itemStartTime;
|
|
257
|
+
|
|
258
|
+
completed++;
|
|
259
|
+
progressResults.push({
|
|
260
|
+
index: globalIndex,
|
|
261
|
+
status: 'completed',
|
|
262
|
+
result,
|
|
263
|
+
duration
|
|
264
|
+
});
|
|
265
|
+
updateProgress(Math.max(0, batch.length - (batchItemIndex + 1)));
|
|
266
|
+
|
|
267
|
+
return result;
|
|
268
|
+
} catch (error) {
|
|
269
|
+
const duration = Date.now() - itemStartTime;
|
|
270
|
+
failed++;
|
|
271
|
+
completed++;
|
|
272
|
+
|
|
273
|
+
progressResults.push({
|
|
274
|
+
index: globalIndex,
|
|
275
|
+
status: 'failed',
|
|
276
|
+
error: error as ApiError,
|
|
277
|
+
duration
|
|
278
|
+
});
|
|
279
|
+
updateProgress(Math.max(0, batch.length - (batchItemIndex + 1)));
|
|
280
|
+
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
const batchResults = await Promise.all(batchPromises);
|
|
286
|
+
results.push(...batchResults);
|
|
287
|
+
}
|
|
288
|
+
} else {
|
|
289
|
+
// Process entire chunk at once
|
|
290
|
+
const baseIndex = chunkIndex * (effectiveBatchSize || items.length);
|
|
291
|
+
|
|
292
|
+
updateProgress(chunk.length);
|
|
293
|
+
|
|
294
|
+
const chunkPromises = chunk.map(async (item, chunkItemIndex) => {
|
|
295
|
+
const globalIndex = baseIndex + chunkItemIndex;
|
|
296
|
+
const itemStartTime = Date.now();
|
|
297
|
+
|
|
298
|
+
try {
|
|
299
|
+
const result = await processor(item, globalIndex);
|
|
300
|
+
const duration = Date.now() - itemStartTime;
|
|
301
|
+
|
|
302
|
+
completed++;
|
|
303
|
+
progressResults.push({
|
|
304
|
+
index: globalIndex,
|
|
305
|
+
status: 'completed',
|
|
306
|
+
result,
|
|
307
|
+
duration
|
|
308
|
+
});
|
|
309
|
+
updateProgress(Math.max(0, chunk.length - (chunkItemIndex + 1)));
|
|
310
|
+
|
|
311
|
+
return result;
|
|
312
|
+
} catch (error) {
|
|
313
|
+
const duration = Date.now() - itemStartTime;
|
|
314
|
+
failed++;
|
|
315
|
+
completed++;
|
|
316
|
+
|
|
317
|
+
progressResults.push({
|
|
318
|
+
index: globalIndex,
|
|
319
|
+
status: 'failed',
|
|
320
|
+
error: error as ApiError,
|
|
321
|
+
duration
|
|
322
|
+
});
|
|
323
|
+
updateProgress(Math.max(0, chunk.length - (chunkItemIndex + 1)));
|
|
324
|
+
|
|
325
|
+
throw error;
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
const chunkResults = await Promise.all(chunkPromises);
|
|
330
|
+
results.push(...chunkResults);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Final progress update
|
|
335
|
+
updateProgress(0);
|
|
336
|
+
return results;
|
|
337
|
+
}
|
|
338
|
+
|
|
132
339
|
// Batch operation with multiple IDs (only for patch and delete)
|
|
133
|
-
if (
|
|
134
|
-
|
|
135
|
-
executeOpts?.ids &&
|
|
136
|
-
executeOpts.ids.length > 0 &&
|
|
137
|
-
(method.toLowerCase() === "patch" || method.toLowerCase() === "delete")
|
|
138
|
-
) {
|
|
139
|
-
const promises = executeOpts.ids.map(async (id) => {
|
|
340
|
+
if (isBatchOperation && executeOpts?.ids && executeOpts.ids.length > 0) {
|
|
341
|
+
const responses = await processBatch(executeOpts.ids, async (id, index) => {
|
|
140
342
|
const finalPath = buildPath(basePath, id);
|
|
141
343
|
return $fetch<T>(finalPath, {
|
|
142
344
|
baseURL: fullBaseURL,
|
|
@@ -147,20 +349,13 @@ export function useEnfyraApi<T = any>(
|
|
|
147
349
|
});
|
|
148
350
|
});
|
|
149
351
|
|
|
150
|
-
const responses = await Promise.all(promises);
|
|
151
352
|
data.value = responses as T;
|
|
152
353
|
return responses;
|
|
153
354
|
}
|
|
154
355
|
|
|
155
356
|
// Batch operation with files array for POST method
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
method.toLowerCase() === "post" &&
|
|
159
|
-
executeOpts?.files &&
|
|
160
|
-
Array.isArray(executeOpts.files) &&
|
|
161
|
-
executeOpts.files.length > 0
|
|
162
|
-
) {
|
|
163
|
-
const promises = executeOpts.files.map(async (fileObj: any) => {
|
|
357
|
+
if (isBatchOperation && executeOpts?.files && Array.isArray(executeOpts.files) && executeOpts.files.length > 0) {
|
|
358
|
+
const responses = await processBatch(executeOpts.files, async (fileObj: any, index) => {
|
|
164
359
|
return $fetch<T>(basePath, {
|
|
165
360
|
baseURL: fullBaseURL,
|
|
166
361
|
method: method as any,
|
|
@@ -170,7 +365,6 @@ export function useEnfyraApi<T = any>(
|
|
|
170
365
|
});
|
|
171
366
|
});
|
|
172
367
|
|
|
173
|
-
const responses = await Promise.all(promises);
|
|
174
368
|
data.value = responses as T;
|
|
175
369
|
return responses;
|
|
176
370
|
}
|
package/src/module.ts
CHANGED
|
@@ -18,6 +18,20 @@ export default defineNuxtModule({
|
|
|
18
18
|
setup(options, nuxt) {
|
|
19
19
|
const { resolve } = createResolver(import.meta.url);
|
|
20
20
|
|
|
21
|
+
// Validate required configuration
|
|
22
|
+
if (!options.apiUrl || !options.appUrl) {
|
|
23
|
+
throw new Error(
|
|
24
|
+
`[Enfyra SDK Nuxt] Missing required configuration:\n` +
|
|
25
|
+
`${!options.apiUrl ? '- apiUrl is required\n' : ''}` +
|
|
26
|
+
`${!options.appUrl ? '- appUrl is required\n' : ''}` +
|
|
27
|
+
`Please configure both in your nuxt.config.ts:\n` +
|
|
28
|
+
`enfyraSDK: {\n` +
|
|
29
|
+
` apiUrl: 'https://your-api-url',\n` +
|
|
30
|
+
` appUrl: 'https://your-app-url'\n` +
|
|
31
|
+
`}`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
21
35
|
// Make module options available at runtime
|
|
22
36
|
nuxt.options.runtimeConfig.public.enfyraSDK = {
|
|
23
37
|
...nuxt.options.runtimeConfig.public.enfyraSDK,
|
package/src/types/index.ts
CHANGED
|
@@ -11,7 +11,39 @@ export interface ApiError {
|
|
|
11
11
|
response?: any;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
export interface
|
|
14
|
+
export interface BatchProgress {
|
|
15
|
+
/** Current progress percentage (0-100) */
|
|
16
|
+
progress: number;
|
|
17
|
+
/** Number of completed operations */
|
|
18
|
+
completed: number;
|
|
19
|
+
/** Total number of operations */
|
|
20
|
+
total: number;
|
|
21
|
+
/** Number of failed operations */
|
|
22
|
+
failed: number;
|
|
23
|
+
/** Number of operations currently in progress */
|
|
24
|
+
inProgress: number;
|
|
25
|
+
/** Estimated time remaining in milliseconds */
|
|
26
|
+
estimatedTimeRemaining?: number;
|
|
27
|
+
/** Average time per operation in milliseconds */
|
|
28
|
+
averageTime?: number;
|
|
29
|
+
/** Current batch being processed */
|
|
30
|
+
currentBatch: number;
|
|
31
|
+
/** Total number of batches */
|
|
32
|
+
totalBatches: number;
|
|
33
|
+
/** Processing speed (operations per second) */
|
|
34
|
+
operationsPerSecond?: number;
|
|
35
|
+
/** Detailed results array for completed operations */
|
|
36
|
+
results: Array<{
|
|
37
|
+
index: number;
|
|
38
|
+
status: 'completed' | 'failed';
|
|
39
|
+
result?: any;
|
|
40
|
+
error?: ApiError;
|
|
41
|
+
duration?: number;
|
|
42
|
+
}>;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Base options available for all operations
|
|
46
|
+
interface BaseApiOptions<T> {
|
|
15
47
|
method?: 'get' | 'post' | 'put' | 'patch' | 'delete' | 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
16
48
|
body?: any;
|
|
17
49
|
query?: Record<string, any>;
|
|
@@ -26,6 +58,28 @@ export interface ApiOptions<T> {
|
|
|
26
58
|
key?: string;
|
|
27
59
|
}
|
|
28
60
|
|
|
61
|
+
// Batch-specific options (only available for batch operations)
|
|
62
|
+
interface BatchApiOptions {
|
|
63
|
+
/** Batch size for chunking large operations (default: no limit) - Only available for batch operations */
|
|
64
|
+
batchSize?: number;
|
|
65
|
+
/** Maximum concurrent requests (default: no limit) - Only available for batch operations */
|
|
66
|
+
concurrent?: number;
|
|
67
|
+
/** Real-time progress callback for batch operations - Only available for batch operations */
|
|
68
|
+
onProgress?: (progress: BatchProgress) => void;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Conditional type that adds batch options only for batch-capable methods
|
|
72
|
+
type ConditionalBatchOptions<T> = T extends { method?: 'patch' | 'delete' | 'PATCH' | 'DELETE' }
|
|
73
|
+
? BatchApiOptions
|
|
74
|
+
: T extends { method?: 'post' | 'POST' }
|
|
75
|
+
? BatchApiOptions // POST supports file batch uploads
|
|
76
|
+
: T extends { method?: undefined } // Default method is 'get', but could be overridden at execution
|
|
77
|
+
? Partial<BatchApiOptions> // Allow batch options but make them optional since method could change
|
|
78
|
+
: {};
|
|
79
|
+
|
|
80
|
+
// Main ApiOptions interface with conditional batch support
|
|
81
|
+
export type ApiOptions<T> = BaseApiOptions<T> & ConditionalBatchOptions<BaseApiOptions<T>>;
|
|
82
|
+
|
|
29
83
|
export interface BackendError {
|
|
30
84
|
success: false;
|
|
31
85
|
message: string;
|
|
@@ -54,17 +108,41 @@ export interface UseEnfyraApiSSRReturn<T> extends AsyncData<T | null, ApiError>
|
|
|
54
108
|
refresh: () => Promise<void>;
|
|
55
109
|
}
|
|
56
110
|
|
|
111
|
+
// Execute options interface
|
|
112
|
+
// Base execute options available for all operations
|
|
113
|
+
interface BaseExecuteOptions {
|
|
114
|
+
body?: any;
|
|
115
|
+
id?: string | number;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Batch execute options (only available when doing batch operations)
|
|
119
|
+
interface BatchExecuteOptions {
|
|
120
|
+
ids?: (string | number)[];
|
|
121
|
+
/** Array of FormData objects for batch upload */
|
|
122
|
+
files?: FormData[];
|
|
123
|
+
/** Override batch size for this specific execution */
|
|
124
|
+
batchSize?: number;
|
|
125
|
+
/** Override concurrent limit for this specific execution */
|
|
126
|
+
concurrent?: number;
|
|
127
|
+
/** Override progress callback for this specific execution */
|
|
128
|
+
onProgress?: (progress: BatchProgress) => void;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Conditional execute options based on what's being executed
|
|
132
|
+
type ConditionalExecuteOptions<T> = T extends { ids: any }
|
|
133
|
+
? BatchExecuteOptions // If ids provided, enable all batch options
|
|
134
|
+
: T extends { files: any }
|
|
135
|
+
? BatchExecuteOptions // If files provided, enable all batch options
|
|
136
|
+
: BaseExecuteOptions & Partial<BatchExecuteOptions>; // Otherwise base options + optional batch options
|
|
137
|
+
|
|
138
|
+
export type ExecuteOptions = BaseExecuteOptions & BatchExecuteOptions;
|
|
139
|
+
|
|
57
140
|
// Client Mode return type
|
|
58
141
|
export interface UseEnfyraApiClientReturn<T> {
|
|
59
142
|
data: Ref<T | null>;
|
|
60
143
|
error: Ref<ApiError | null>;
|
|
61
144
|
pending: Ref<boolean>;
|
|
62
|
-
execute: (executeOpts?:
|
|
63
|
-
body?: any;
|
|
64
|
-
id?: string | number;
|
|
65
|
-
ids?: (string | number)[];
|
|
66
|
-
files?: any[];
|
|
67
|
-
}) => Promise<T | T[] | null>;
|
|
145
|
+
execute: (executeOpts?: ExecuteOptions) => Promise<T | T[] | null>;
|
|
68
146
|
}
|
|
69
147
|
|
|
70
148
|
|