@enfyra/sdk-nuxt 0.2.2 → 0.2.4
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 +133 -19
- package/dist/composables/useEnfyraApi.mjs +144 -16
- package/dist/constants/config.d.ts +5 -0
- package/dist/constants/config.mjs +1 -0
- package/dist/index.d.ts +53 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/module.cjs +16 -5
- package/dist/module.d.cts +0 -2
- package/dist/module.d.mts +0 -2
- package/dist/module.d.ts +0 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +16 -5
- package/dist/utils/config.mjs +0 -1
- package/dist/utils/server/proxy.mjs +2 -1
- package/package.json +1 -1
- package/src/composables/useEnfyraApi.ts +193 -34
- package/src/constants/config.ts +5 -0
- package/src/module.ts +21 -6
- package/src/types/index.ts +72 -7
- package/src/utils/config.ts +0 -1
- package/src/utils/server/proxy.ts +2 -1
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
|
|
@@ -144,22 +144,34 @@ await execute({
|
|
|
144
144
|
- `headers?: Record<string, string>` - Custom headers
|
|
145
145
|
- `errorContext?: string` - Error context for logging
|
|
146
146
|
- `onError?: (error: ApiError, context?: string) => void` - Custom error handler
|
|
147
|
-
- `batchSize?: number` - Batch size for chunking large operations (default: no limit)
|
|
148
|
-
- `concurrent?: number` - Maximum concurrent requests (default: no limit)
|
|
149
147
|
- `key?: string` - Cache key (SSR mode, optional)
|
|
150
148
|
- `default?: () => T` - Default value (SSR mode only)
|
|
151
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
|
+
|
|
152
157
|
**⚠️ Important: Return Types Differ**
|
|
153
158
|
- **SSR Mode**: Returns `useFetch` result `{ data, pending, error, refresh }`
|
|
154
159
|
- **Client Mode**: Returns custom result `{ data, pending, error, execute }`
|
|
155
160
|
|
|
156
161
|
**Execute Options (Client mode only):**
|
|
162
|
+
|
|
163
|
+
**Basic Options:**
|
|
157
164
|
- `id?: string | number` - Single resource ID
|
|
165
|
+
- `body?: any` - Override request body
|
|
166
|
+
|
|
167
|
+
**Batch Options (only when using `ids` or `files`):**
|
|
158
168
|
- `ids?: (string | number)[]` - Batch operation IDs (PATCH/DELETE)
|
|
159
169
|
- `files?: FormData[]` - Array of FormData objects for batch upload (POST)
|
|
160
|
-
- `body?: any` - Override request body
|
|
161
170
|
- `batchSize?: number` - Override batch size for this execution
|
|
162
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.
|
|
163
175
|
|
|
164
176
|
### `useEnfyraAuth()`
|
|
165
177
|
|
|
@@ -184,6 +196,7 @@ await fetchUser() // Refresh user data
|
|
|
184
196
|
|
|
185
197
|
```typescript
|
|
186
198
|
// Basic batch delete - unlimited parallel requests
|
|
199
|
+
// 🎯 Note: Batch options only appear in IntelliSense for DELETE method
|
|
187
200
|
const { execute: deleteItems } = useEnfyraApi('/items', {
|
|
188
201
|
method: 'delete',
|
|
189
202
|
errorContext: 'Delete Items'
|
|
@@ -191,38 +204,82 @@ const { execute: deleteItems } = useEnfyraApi('/items', {
|
|
|
191
204
|
|
|
192
205
|
await deleteItems({ ids: ['1', '2', '3'] });
|
|
193
206
|
|
|
194
|
-
// Advanced batch operations with concurrency control
|
|
207
|
+
// Advanced batch operations with concurrency control
|
|
208
|
+
// 🎯 TypeScript shows batch options (batchSize, concurrent, onProgress) for DELETE method
|
|
195
209
|
const { execute: deleteMany } = useEnfyraApi('/users', {
|
|
196
210
|
method: 'delete',
|
|
197
|
-
batchSize: 10, //
|
|
198
|
-
concurrent: 3, //
|
|
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
|
+
},
|
|
199
216
|
onError: (error, context) => toast.error(`${context}: ${error.message}`)
|
|
200
217
|
});
|
|
201
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
|
+
|
|
202
229
|
// Delete 100 users in controlled batches
|
|
203
230
|
await deleteMany({ ids: Array.from({length: 100}, (_, i) => `user-${i}`) });
|
|
204
231
|
|
|
205
232
|
// Override batch settings per execution
|
|
233
|
+
// 🎯 TypeScript shows batch options for PATCH method
|
|
206
234
|
const { execute: updateUsers } = useEnfyraApi('/users', {
|
|
207
235
|
method: 'patch',
|
|
208
|
-
batchSize: 20, //
|
|
209
|
-
concurrent: 5 //
|
|
236
|
+
batchSize: 20, // ✅ Available: PATCH method supports batching
|
|
237
|
+
concurrent: 5 // ✅ Available: PATCH method supports batching
|
|
210
238
|
});
|
|
211
239
|
|
|
212
240
|
// This execution uses different settings
|
|
241
|
+
// 🎯 Batch options in execute() only available when using `ids` or `files`
|
|
213
242
|
await updateUsers({
|
|
214
|
-
ids: largeUserList,
|
|
243
|
+
ids: largeUserList, // ✅ Triggers batch mode
|
|
215
244
|
body: { status: 'active' },
|
|
216
|
-
batchSize: 50, //
|
|
217
|
-
concurrent: 10
|
|
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`
|
|
218
259
|
});
|
|
219
260
|
|
|
220
|
-
// Batch file upload with progress
|
|
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)
|
|
221
272
|
const { execute: uploadFiles } = useEnfyraApi('/file_definition', {
|
|
222
273
|
method: 'post',
|
|
223
|
-
batchSize: 5, //
|
|
224
|
-
concurrent: 2, //
|
|
225
|
-
errorContext: 'Upload Files'
|
|
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
|
+
}
|
|
226
283
|
});
|
|
227
284
|
|
|
228
285
|
// Convert files to FormData array (matches enfyra_app pattern)
|
|
@@ -236,6 +293,66 @@ const formDataArray = selectedFiles.map(file => {
|
|
|
236
293
|
await uploadFiles({
|
|
237
294
|
files: formDataArray // Array of FormData objects
|
|
238
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
|
+
}
|
|
239
356
|
```
|
|
240
357
|
|
|
241
358
|
### TypeScript Integration
|
|
@@ -310,9 +427,6 @@ export default defineNuxtConfig({
|
|
|
310
427
|
// Required: Main API URL
|
|
311
428
|
apiUrl: process.env.ENFYRA_API_URL || "http://localhost:1105",
|
|
312
429
|
|
|
313
|
-
// Optional: API path prefix (default: '')
|
|
314
|
-
apiPrefix: '/api/v1',
|
|
315
|
-
|
|
316
430
|
// Required: App URL for SSR requests
|
|
317
431
|
appUrl: process.env.ENFYRA_APP_URL || "http://localhost:3001",
|
|
318
432
|
},
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ref, unref, toRaw } from "vue";
|
|
2
2
|
import { $fetch } from "../utils/http";
|
|
3
|
+
import { ENFYRA_API_PREFIX } from "../constants/config";
|
|
3
4
|
import { useRuntimeConfig, useFetch, useRequestHeaders } from "#imports";
|
|
4
5
|
function handleError(error, context, customHandler) {
|
|
5
6
|
const apiError = {
|
|
@@ -16,11 +17,11 @@ function handleError(error, context, customHandler) {
|
|
|
16
17
|
return apiError;
|
|
17
18
|
}
|
|
18
19
|
export function useEnfyraApi(path, opts = {}) {
|
|
19
|
-
const { method = "get", body, query, errorContext, onError, ssr, key, batchSize, concurrent } = opts;
|
|
20
|
+
const { method = "get", body, query, errorContext, onError, ssr, key, batchSize, concurrent, onProgress } = opts;
|
|
20
21
|
if (ssr) {
|
|
21
22
|
const config = useRuntimeConfig().public.enfyraSDK;
|
|
22
23
|
const basePath = (typeof path === "function" ? path() : path).replace(/^\/?api\/?/, "").replace(/^\/+/, "");
|
|
23
|
-
const finalUrl = (config?.appUrl || "") + (config?.apiPrefix ||
|
|
24
|
+
const finalUrl = (config?.appUrl || "") + (config?.apiPrefix || ENFYRA_API_PREFIX) + "/" + basePath;
|
|
24
25
|
const clientHeaders = process.client ? {} : useRequestHeaders([
|
|
25
26
|
"authorization",
|
|
26
27
|
"cookie",
|
|
@@ -65,38 +66,165 @@ export function useEnfyraApi(path, opts = {}) {
|
|
|
65
66
|
const basePath = (typeof path === "function" ? path() : path).replace(/^\/?api\/?/, "").replace(/^\/+/, "");
|
|
66
67
|
const finalBody = executeOpts?.body || unref(body);
|
|
67
68
|
const finalQuery = unref(query);
|
|
68
|
-
const
|
|
69
|
-
const
|
|
69
|
+
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);
|
|
70
|
+
const effectiveBatchSize = isBatchOperation ? executeOpts?.batchSize ?? batchSize : void 0;
|
|
71
|
+
const effectiveConcurrent = isBatchOperation ? executeOpts?.concurrent ?? concurrent : void 0;
|
|
72
|
+
const effectiveOnProgress = isBatchOperation ? executeOpts?.onProgress ?? onProgress : void 0;
|
|
70
73
|
const buildPath = (...segments) => {
|
|
71
74
|
return segments.filter(Boolean).join("/");
|
|
72
75
|
};
|
|
73
|
-
const fullBaseURL = apiUrl + (apiPrefix ||
|
|
76
|
+
const fullBaseURL = apiUrl + (apiPrefix || ENFYRA_API_PREFIX);
|
|
74
77
|
async function processBatch(items, processor) {
|
|
75
78
|
const results = [];
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
const progressResults = [];
|
|
80
|
+
const startTime = Date.now();
|
|
81
|
+
let completed = 0;
|
|
82
|
+
let failed = 0;
|
|
80
83
|
const chunks = effectiveBatchSize ? Array.from(
|
|
81
84
|
{ length: Math.ceil(items.length / effectiveBatchSize) },
|
|
82
85
|
(_, i) => items.slice(i * effectiveBatchSize, i * effectiveBatchSize + effectiveBatchSize)
|
|
83
86
|
) : [items];
|
|
84
|
-
|
|
87
|
+
const totalBatches = chunks.length;
|
|
88
|
+
let currentBatch = 0;
|
|
89
|
+
const updateProgress = (inProgress = 0) => {
|
|
90
|
+
if (effectiveOnProgress) {
|
|
91
|
+
const elapsed = Date.now() - startTime;
|
|
92
|
+
const progress = items.length > 0 ? Math.round(completed / items.length * 100) : 0;
|
|
93
|
+
const averageTime = completed > 0 ? elapsed / completed : void 0;
|
|
94
|
+
const operationsPerSecond = completed > 0 ? completed / elapsed * 1e3 : void 0;
|
|
95
|
+
const estimatedTimeRemaining = averageTime && items.length > completed ? Math.round(averageTime * (items.length - completed)) : void 0;
|
|
96
|
+
const progressData = {
|
|
97
|
+
progress,
|
|
98
|
+
completed,
|
|
99
|
+
total: items.length,
|
|
100
|
+
failed,
|
|
101
|
+
inProgress,
|
|
102
|
+
estimatedTimeRemaining,
|
|
103
|
+
averageTime,
|
|
104
|
+
currentBatch: currentBatch + 1,
|
|
105
|
+
totalBatches,
|
|
106
|
+
operationsPerSecond,
|
|
107
|
+
results: [...progressResults]
|
|
108
|
+
};
|
|
109
|
+
effectiveOnProgress(progressData);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
updateProgress(0);
|
|
113
|
+
if (!effectiveBatchSize && !effectiveConcurrent) {
|
|
114
|
+
updateProgress(items.length);
|
|
115
|
+
const promises = items.map(async (item, index) => {
|
|
116
|
+
const itemStartTime = Date.now();
|
|
117
|
+
try {
|
|
118
|
+
const result = await processor(item, index);
|
|
119
|
+
const duration = Date.now() - itemStartTime;
|
|
120
|
+
completed++;
|
|
121
|
+
progressResults.push({
|
|
122
|
+
index,
|
|
123
|
+
status: "completed",
|
|
124
|
+
result,
|
|
125
|
+
duration
|
|
126
|
+
});
|
|
127
|
+
updateProgress(items.length - completed);
|
|
128
|
+
return result;
|
|
129
|
+
} catch (error2) {
|
|
130
|
+
const duration = Date.now() - itemStartTime;
|
|
131
|
+
failed++;
|
|
132
|
+
completed++;
|
|
133
|
+
progressResults.push({
|
|
134
|
+
index,
|
|
135
|
+
status: "failed",
|
|
136
|
+
error: error2,
|
|
137
|
+
duration
|
|
138
|
+
});
|
|
139
|
+
updateProgress(items.length - completed);
|
|
140
|
+
throw error2;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
const results2 = await Promise.all(promises);
|
|
144
|
+
updateProgress(0);
|
|
145
|
+
return results2;
|
|
146
|
+
}
|
|
147
|
+
for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
|
|
148
|
+
currentBatch = chunkIndex;
|
|
149
|
+
const chunk = chunks[chunkIndex];
|
|
85
150
|
if (effectiveConcurrent && chunk.length > effectiveConcurrent) {
|
|
86
151
|
for (let i = 0; i < chunk.length; i += effectiveConcurrent) {
|
|
87
152
|
const batch = chunk.slice(i, i + effectiveConcurrent);
|
|
88
|
-
const
|
|
153
|
+
const baseIndex = chunkIndex * (effectiveBatchSize || items.length) + i;
|
|
154
|
+
updateProgress(batch.length);
|
|
155
|
+
const batchPromises = batch.map(async (item, batchItemIndex) => {
|
|
156
|
+
const globalIndex = baseIndex + batchItemIndex;
|
|
157
|
+
const itemStartTime = Date.now();
|
|
158
|
+
try {
|
|
159
|
+
const result = await processor(item, globalIndex);
|
|
160
|
+
const duration = Date.now() - itemStartTime;
|
|
161
|
+
completed++;
|
|
162
|
+
progressResults.push({
|
|
163
|
+
index: globalIndex,
|
|
164
|
+
status: "completed",
|
|
165
|
+
result,
|
|
166
|
+
duration
|
|
167
|
+
});
|
|
168
|
+
updateProgress(Math.max(0, batch.length - (batchItemIndex + 1)));
|
|
169
|
+
return result;
|
|
170
|
+
} catch (error2) {
|
|
171
|
+
const duration = Date.now() - itemStartTime;
|
|
172
|
+
failed++;
|
|
173
|
+
completed++;
|
|
174
|
+
progressResults.push({
|
|
175
|
+
index: globalIndex,
|
|
176
|
+
status: "failed",
|
|
177
|
+
error: error2,
|
|
178
|
+
duration
|
|
179
|
+
});
|
|
180
|
+
updateProgress(Math.max(0, batch.length - (batchItemIndex + 1)));
|
|
181
|
+
throw error2;
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
const batchResults = await Promise.all(batchPromises);
|
|
89
185
|
results.push(...batchResults);
|
|
90
186
|
}
|
|
91
187
|
} else {
|
|
92
|
-
const
|
|
188
|
+
const baseIndex = chunkIndex * (effectiveBatchSize || items.length);
|
|
189
|
+
updateProgress(chunk.length);
|
|
190
|
+
const chunkPromises = chunk.map(async (item, chunkItemIndex) => {
|
|
191
|
+
const globalIndex = baseIndex + chunkItemIndex;
|
|
192
|
+
const itemStartTime = Date.now();
|
|
193
|
+
try {
|
|
194
|
+
const result = await processor(item, globalIndex);
|
|
195
|
+
const duration = Date.now() - itemStartTime;
|
|
196
|
+
completed++;
|
|
197
|
+
progressResults.push({
|
|
198
|
+
index: globalIndex,
|
|
199
|
+
status: "completed",
|
|
200
|
+
result,
|
|
201
|
+
duration
|
|
202
|
+
});
|
|
203
|
+
updateProgress(Math.max(0, chunk.length - (chunkItemIndex + 1)));
|
|
204
|
+
return result;
|
|
205
|
+
} catch (error2) {
|
|
206
|
+
const duration = Date.now() - itemStartTime;
|
|
207
|
+
failed++;
|
|
208
|
+
completed++;
|
|
209
|
+
progressResults.push({
|
|
210
|
+
index: globalIndex,
|
|
211
|
+
status: "failed",
|
|
212
|
+
error: error2,
|
|
213
|
+
duration
|
|
214
|
+
});
|
|
215
|
+
updateProgress(Math.max(0, chunk.length - (chunkItemIndex + 1)));
|
|
216
|
+
throw error2;
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
const chunkResults = await Promise.all(chunkPromises);
|
|
93
220
|
results.push(...chunkResults);
|
|
94
221
|
}
|
|
95
222
|
}
|
|
223
|
+
updateProgress(0);
|
|
96
224
|
return results;
|
|
97
225
|
}
|
|
98
|
-
if (
|
|
99
|
-
const responses = await processBatch(executeOpts.ids, async (id) => {
|
|
226
|
+
if (isBatchOperation && executeOpts?.ids && executeOpts.ids.length > 0) {
|
|
227
|
+
const responses = await processBatch(executeOpts.ids, async (id, index) => {
|
|
100
228
|
const finalPath2 = buildPath(basePath, id);
|
|
101
229
|
return $fetch(finalPath2, {
|
|
102
230
|
baseURL: fullBaseURL,
|
|
@@ -109,8 +237,8 @@ export function useEnfyraApi(path, opts = {}) {
|
|
|
109
237
|
data.value = responses;
|
|
110
238
|
return responses;
|
|
111
239
|
}
|
|
112
|
-
if (
|
|
113
|
-
const responses = await processBatch(executeOpts.files, async (fileObj) => {
|
|
240
|
+
if (isBatchOperation && executeOpts?.files && Array.isArray(executeOpts.files) && executeOpts.files.length > 0) {
|
|
241
|
+
const responses = await processBatch(executeOpts.files, async (fileObj, index) => {
|
|
114
242
|
return $fetch(basePath, {
|
|
115
243
|
baseURL: fullBaseURL,
|
|
116
244
|
method,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const ENFYRA_API_PREFIX = "/enfyra/api";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export interface EnfyraConfig {
|
|
2
2
|
apiUrl: string;
|
|
3
|
-
apiPrefix?: string;
|
|
4
3
|
defaultHeaders?: Record<string, string>;
|
|
5
4
|
}
|
|
6
5
|
export interface ApiError {
|
|
@@ -9,7 +8,37 @@ export interface ApiError {
|
|
|
9
8
|
data?: any;
|
|
10
9
|
response?: any;
|
|
11
10
|
}
|
|
12
|
-
export interface
|
|
11
|
+
export interface BatchProgress {
|
|
12
|
+
/** Current progress percentage (0-100) */
|
|
13
|
+
progress: number;
|
|
14
|
+
/** Number of completed operations */
|
|
15
|
+
completed: number;
|
|
16
|
+
/** Total number of operations */
|
|
17
|
+
total: number;
|
|
18
|
+
/** Number of failed operations */
|
|
19
|
+
failed: number;
|
|
20
|
+
/** Number of operations currently in progress */
|
|
21
|
+
inProgress: number;
|
|
22
|
+
/** Estimated time remaining in milliseconds */
|
|
23
|
+
estimatedTimeRemaining?: number;
|
|
24
|
+
/** Average time per operation in milliseconds */
|
|
25
|
+
averageTime?: number;
|
|
26
|
+
/** Current batch being processed */
|
|
27
|
+
currentBatch: number;
|
|
28
|
+
/** Total number of batches */
|
|
29
|
+
totalBatches: number;
|
|
30
|
+
/** Processing speed (operations per second) */
|
|
31
|
+
operationsPerSecond?: number;
|
|
32
|
+
/** Detailed results array for completed operations */
|
|
33
|
+
results: Array<{
|
|
34
|
+
index: number;
|
|
35
|
+
status: 'completed' | 'failed';
|
|
36
|
+
result?: any;
|
|
37
|
+
error?: ApiError;
|
|
38
|
+
duration?: number;
|
|
39
|
+
}>;
|
|
40
|
+
}
|
|
41
|
+
interface BaseApiOptions<T> {
|
|
13
42
|
method?: 'get' | 'post' | 'put' | 'patch' | 'delete' | 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
14
43
|
body?: any;
|
|
15
44
|
query?: Record<string, any>;
|
|
@@ -17,16 +46,28 @@ export interface ApiOptions<T> {
|
|
|
17
46
|
errorContext?: string;
|
|
18
47
|
onError?: (error: ApiError, context?: string) => void;
|
|
19
48
|
disableBatch?: boolean;
|
|
20
|
-
/** Batch size for chunking large operations (default: no limit) */
|
|
21
|
-
batchSize?: number;
|
|
22
|
-
/** Maximum concurrent requests (default: no limit) */
|
|
23
|
-
concurrent?: number;
|
|
24
49
|
default?: () => T;
|
|
25
50
|
/** Enable SSR with useFetch instead of $fetch */
|
|
26
51
|
ssr?: boolean;
|
|
27
52
|
/** Unique key for useFetch caching */
|
|
28
53
|
key?: string;
|
|
29
54
|
}
|
|
55
|
+
interface BatchApiOptions {
|
|
56
|
+
/** Batch size for chunking large operations (default: no limit) - Only available for batch operations */
|
|
57
|
+
batchSize?: number;
|
|
58
|
+
/** Maximum concurrent requests (default: no limit) - Only available for batch operations */
|
|
59
|
+
concurrent?: number;
|
|
60
|
+
/** Real-time progress callback for batch operations - Only available for batch operations */
|
|
61
|
+
onProgress?: (progress: BatchProgress) => void;
|
|
62
|
+
}
|
|
63
|
+
type ConditionalBatchOptions<T> = T extends {
|
|
64
|
+
method?: 'patch' | 'delete' | 'PATCH' | 'DELETE';
|
|
65
|
+
} ? BatchApiOptions : T extends {
|
|
66
|
+
method?: 'post' | 'POST';
|
|
67
|
+
} ? BatchApiOptions : T extends {
|
|
68
|
+
method?: undefined;
|
|
69
|
+
} ? Partial<BatchApiOptions> : {};
|
|
70
|
+
export type ApiOptions<T> = BaseApiOptions<T> & ConditionalBatchOptions<BaseApiOptions<T>>;
|
|
30
71
|
export interface BackendError {
|
|
31
72
|
success: false;
|
|
32
73
|
message: string;
|
|
@@ -50,9 +91,11 @@ export interface UseEnfyraApiSSRReturn<T> extends AsyncData<T | null, ApiError>
|
|
|
50
91
|
error: Ref<ApiError | null>;
|
|
51
92
|
refresh: () => Promise<void>;
|
|
52
93
|
}
|
|
53
|
-
|
|
94
|
+
interface BaseExecuteOptions {
|
|
54
95
|
body?: any;
|
|
55
96
|
id?: string | number;
|
|
97
|
+
}
|
|
98
|
+
interface BatchExecuteOptions {
|
|
56
99
|
ids?: (string | number)[];
|
|
57
100
|
/** Array of FormData objects for batch upload */
|
|
58
101
|
files?: FormData[];
|
|
@@ -60,7 +103,10 @@ export interface ExecuteOptions {
|
|
|
60
103
|
batchSize?: number;
|
|
61
104
|
/** Override concurrent limit for this specific execution */
|
|
62
105
|
concurrent?: number;
|
|
106
|
+
/** Override progress callback for this specific execution */
|
|
107
|
+
onProgress?: (progress: BatchProgress) => void;
|
|
63
108
|
}
|
|
109
|
+
export type ExecuteOptions = BaseExecuteOptions & BatchExecuteOptions;
|
|
64
110
|
export interface UseEnfyraApiClientReturn<T> {
|
|
65
111
|
data: Ref<T | null>;
|
|
66
112
|
error: Ref<ApiError | null>;
|
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,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/types/index.ts"],"names":[],"mappings":"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"}
|
package/dist/module.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const kit = require('@nuxt/kit');
|
|
4
|
+
const config_mjs = require('../dist/constants/config.mjs');
|
|
4
5
|
|
|
5
6
|
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
|
|
6
7
|
const module$1 = kit.defineNuxtModule({
|
|
@@ -10,14 +11,24 @@ const module$1 = kit.defineNuxtModule({
|
|
|
10
11
|
},
|
|
11
12
|
defaults: {
|
|
12
13
|
apiUrl: "",
|
|
13
|
-
apiPrefix: "/api",
|
|
14
14
|
appUrl: ""
|
|
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
|
-
...options
|
|
30
|
+
...options,
|
|
31
|
+
apiPrefix: config_mjs.ENFYRA_API_PREFIX
|
|
21
32
|
};
|
|
22
33
|
kit.addImportsDir(resolve("./composables"));
|
|
23
34
|
kit.addServerHandler({
|
|
@@ -25,17 +36,17 @@ const module$1 = kit.defineNuxtModule({
|
|
|
25
36
|
middleware: true
|
|
26
37
|
});
|
|
27
38
|
kit.addServerHandler({
|
|
28
|
-
route:
|
|
39
|
+
route: `${config_mjs.ENFYRA_API_PREFIX}/login`,
|
|
29
40
|
handler: resolve("./runtime/server/api/login.post"),
|
|
30
41
|
method: "post"
|
|
31
42
|
});
|
|
32
43
|
kit.addServerHandler({
|
|
33
|
-
route:
|
|
44
|
+
route: `${config_mjs.ENFYRA_API_PREFIX}/logout`,
|
|
34
45
|
handler: resolve("./runtime/server/api/logout.post"),
|
|
35
46
|
method: "post"
|
|
36
47
|
});
|
|
37
48
|
kit.addServerHandler({
|
|
38
|
-
route:
|
|
49
|
+
route: `${config_mjs.ENFYRA_API_PREFIX}/**`,
|
|
39
50
|
handler: resolve("./runtime/server/api/all")
|
|
40
51
|
});
|
|
41
52
|
}
|
package/dist/module.d.cts
CHANGED
package/dist/module.d.mts
CHANGED
package/dist/module.d.ts
CHANGED
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { defineNuxtModule, createResolver, addImportsDir, addServerHandler } from '@nuxt/kit';
|
|
2
|
+
import { ENFYRA_API_PREFIX } from '../dist/constants/config.mjs';
|
|
2
3
|
|
|
3
4
|
const module = defineNuxtModule({
|
|
4
5
|
meta: {
|
|
@@ -7,14 +8,24 @@ const module = defineNuxtModule({
|
|
|
7
8
|
},
|
|
8
9
|
defaults: {
|
|
9
10
|
apiUrl: "",
|
|
10
|
-
apiPrefix: "/api",
|
|
11
11
|
appUrl: ""
|
|
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
|
-
...options
|
|
27
|
+
...options,
|
|
28
|
+
apiPrefix: ENFYRA_API_PREFIX
|
|
18
29
|
};
|
|
19
30
|
addImportsDir(resolve("./composables"));
|
|
20
31
|
addServerHandler({
|
|
@@ -22,17 +33,17 @@ const module = defineNuxtModule({
|
|
|
22
33
|
middleware: true
|
|
23
34
|
});
|
|
24
35
|
addServerHandler({
|
|
25
|
-
route:
|
|
36
|
+
route: `${ENFYRA_API_PREFIX}/login`,
|
|
26
37
|
handler: resolve("./runtime/server/api/login.post"),
|
|
27
38
|
method: "post"
|
|
28
39
|
});
|
|
29
40
|
addServerHandler({
|
|
30
|
-
route:
|
|
41
|
+
route: `${ENFYRA_API_PREFIX}/logout`,
|
|
31
42
|
handler: resolve("./runtime/server/api/logout.post"),
|
|
32
43
|
method: "post"
|
|
33
44
|
});
|
|
34
45
|
addServerHandler({
|
|
35
|
-
route:
|
|
46
|
+
route: `${ENFYRA_API_PREFIX}/**`,
|
|
36
47
|
handler: resolve("./runtime/server/api/all")
|
|
37
48
|
});
|
|
38
49
|
}
|
package/dist/utils/config.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { proxyRequest } from "h3";
|
|
2
2
|
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import { ENFYRA_API_PREFIX } from "../../constants/config";
|
|
3
4
|
export function proxyToAPI(event, customPath) {
|
|
4
5
|
const config = useRuntimeConfig();
|
|
5
|
-
const apiPrefix = config.public.enfyraSDK.apiPrefix;
|
|
6
|
+
const apiPrefix = config.public.enfyraSDK.apiPrefix || ENFYRA_API_PREFIX;
|
|
6
7
|
const rawPath = customPath || event.path.replace(new RegExp(`^${apiPrefix}`), "");
|
|
7
8
|
const targetUrl = `${config.public.enfyraSDK.apiUrl}${rawPath}`;
|
|
8
9
|
const headers = event.context.proxyHeaders || {};
|
package/package.json
CHANGED
|
@@ -6,8 +6,10 @@ import type {
|
|
|
6
6
|
ExecuteOptions,
|
|
7
7
|
UseEnfyraApiSSRReturn,
|
|
8
8
|
UseEnfyraApiClientReturn,
|
|
9
|
+
BatchProgress,
|
|
9
10
|
} from "../types";
|
|
10
11
|
import { $fetch } from "../utils/http";
|
|
12
|
+
import { ENFYRA_API_PREFIX } from "../constants/config";
|
|
11
13
|
import { useRuntimeConfig, useFetch, useRequestHeaders } from "#imports";
|
|
12
14
|
|
|
13
15
|
function handleError(
|
|
@@ -47,7 +49,7 @@ export function useEnfyraApi<T = any>(
|
|
|
47
49
|
path: (() => string) | string,
|
|
48
50
|
opts: ApiOptions<T> = {}
|
|
49
51
|
): UseEnfyraApiSSRReturn<T> | UseEnfyraApiClientReturn<T> {
|
|
50
|
-
const { method = "get", body, query, errorContext, onError, ssr, key, batchSize, concurrent } = opts;
|
|
52
|
+
const { method = "get", body, query, errorContext, onError, ssr, key, batchSize, concurrent, onProgress } = opts;
|
|
51
53
|
|
|
52
54
|
// SSR mode - use useFetch
|
|
53
55
|
if (ssr) {
|
|
@@ -57,7 +59,7 @@ export function useEnfyraApi<T = any>(
|
|
|
57
59
|
.replace(/^\/+/, ""); // Remove leading slashes
|
|
58
60
|
|
|
59
61
|
const finalUrl =
|
|
60
|
-
(config?.appUrl || "") + (config?.apiPrefix ||
|
|
62
|
+
(config?.appUrl || "") + (config?.apiPrefix || ENFYRA_API_PREFIX) + "/" + basePath;
|
|
61
63
|
|
|
62
64
|
// Get headers from client request and filter out connection-specific headers
|
|
63
65
|
const clientHeaders = process.client
|
|
@@ -117,9 +119,19 @@ export function useEnfyraApi<T = any>(
|
|
|
117
119
|
const finalBody = executeOpts?.body || unref(body);
|
|
118
120
|
const finalQuery = unref(query);
|
|
119
121
|
|
|
120
|
-
//
|
|
121
|
-
const
|
|
122
|
-
|
|
122
|
+
// Check if this is actually a batch operation
|
|
123
|
+
const isBatchOperation = (
|
|
124
|
+
!opts.disableBatch &&
|
|
125
|
+
(
|
|
126
|
+
(executeOpts?.ids && executeOpts.ids.length > 0 && (method.toLowerCase() === "patch" || method.toLowerCase() === "delete")) ||
|
|
127
|
+
(method.toLowerCase() === "post" && executeOpts?.files && Array.isArray(executeOpts.files) && executeOpts.files.length > 0)
|
|
128
|
+
)
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
// Use batch options only if this is actually a batch operation
|
|
132
|
+
const effectiveBatchSize = isBatchOperation ? (executeOpts?.batchSize ?? batchSize) : undefined;
|
|
133
|
+
const effectiveConcurrent = isBatchOperation ? (executeOpts?.concurrent ?? concurrent) : undefined;
|
|
134
|
+
const effectiveOnProgress = isBatchOperation ? (executeOpts?.onProgress ?? onProgress) : undefined;
|
|
123
135
|
|
|
124
136
|
// Helper function to build clean path
|
|
125
137
|
const buildPath = (...segments: (string | number)[]): string => {
|
|
@@ -127,54 +139,207 @@ export function useEnfyraApi<T = any>(
|
|
|
127
139
|
};
|
|
128
140
|
|
|
129
141
|
// Build full base URL with prefix
|
|
130
|
-
const fullBaseURL = apiUrl + (apiPrefix ||
|
|
142
|
+
const fullBaseURL = apiUrl + (apiPrefix || ENFYRA_API_PREFIX);
|
|
131
143
|
|
|
132
|
-
// Helper function for batch processing with chunking and
|
|
144
|
+
// Helper function for batch processing with chunking, concurrency control, and real-time progress tracking
|
|
133
145
|
async function processBatch<T>(
|
|
134
146
|
items: any[],
|
|
135
|
-
processor: (item: any) => Promise<T>
|
|
147
|
+
processor: (item: any, index: number) => Promise<T>
|
|
136
148
|
): Promise<T[]> {
|
|
137
149
|
const results: T[] = [];
|
|
150
|
+
const progressResults: BatchProgress['results'] = [];
|
|
151
|
+
const startTime = Date.now();
|
|
152
|
+
let completed = 0;
|
|
153
|
+
let failed = 0;
|
|
138
154
|
|
|
139
|
-
//
|
|
140
|
-
if (!effectiveBatchSize && !effectiveConcurrent) {
|
|
141
|
-
const promises = items.map(processor);
|
|
142
|
-
return await Promise.all(promises);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Chunk items by batchSize
|
|
155
|
+
// Calculate batch structure
|
|
146
156
|
const chunks = effectiveBatchSize ?
|
|
147
157
|
Array.from({ length: Math.ceil(items.length / effectiveBatchSize) }, (_, i) =>
|
|
148
158
|
items.slice(i * effectiveBatchSize, i * effectiveBatchSize + effectiveBatchSize)
|
|
149
159
|
) : [items];
|
|
160
|
+
|
|
161
|
+
const totalBatches = chunks.length;
|
|
162
|
+
let currentBatch = 0;
|
|
163
|
+
|
|
164
|
+
// Initialize progress tracking
|
|
165
|
+
const updateProgress = (inProgress: number = 0) => {
|
|
166
|
+
if (effectiveOnProgress) {
|
|
167
|
+
const elapsed = Date.now() - startTime;
|
|
168
|
+
const progress = items.length > 0 ? Math.round((completed / items.length) * 100) : 0;
|
|
169
|
+
const averageTime = completed > 0 ? elapsed / completed : undefined;
|
|
170
|
+
const operationsPerSecond = completed > 0 ? (completed / elapsed) * 1000 : undefined;
|
|
171
|
+
const estimatedTimeRemaining = averageTime && items.length > completed
|
|
172
|
+
? Math.round(averageTime * (items.length - completed))
|
|
173
|
+
: undefined;
|
|
174
|
+
|
|
175
|
+
const progressData: BatchProgress = {
|
|
176
|
+
progress,
|
|
177
|
+
completed,
|
|
178
|
+
total: items.length,
|
|
179
|
+
failed,
|
|
180
|
+
inProgress,
|
|
181
|
+
estimatedTimeRemaining,
|
|
182
|
+
averageTime,
|
|
183
|
+
currentBatch: currentBatch + 1,
|
|
184
|
+
totalBatches,
|
|
185
|
+
operationsPerSecond,
|
|
186
|
+
results: [...progressResults]
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
effectiveOnProgress(progressData);
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// Initial progress update
|
|
194
|
+
updateProgress(0);
|
|
195
|
+
|
|
196
|
+
// If no limits, process all at once with progress tracking
|
|
197
|
+
if (!effectiveBatchSize && !effectiveConcurrent) {
|
|
198
|
+
updateProgress(items.length);
|
|
199
|
+
|
|
200
|
+
const promises = items.map(async (item, index) => {
|
|
201
|
+
const itemStartTime = Date.now();
|
|
202
|
+
try {
|
|
203
|
+
const result = await processor(item, index);
|
|
204
|
+
const duration = Date.now() - itemStartTime;
|
|
205
|
+
|
|
206
|
+
completed++;
|
|
207
|
+
progressResults.push({
|
|
208
|
+
index,
|
|
209
|
+
status: 'completed',
|
|
210
|
+
result,
|
|
211
|
+
duration
|
|
212
|
+
});
|
|
213
|
+
updateProgress(items.length - completed);
|
|
214
|
+
|
|
215
|
+
return result;
|
|
216
|
+
} catch (error) {
|
|
217
|
+
const duration = Date.now() - itemStartTime;
|
|
218
|
+
failed++;
|
|
219
|
+
completed++;
|
|
220
|
+
|
|
221
|
+
progressResults.push({
|
|
222
|
+
index,
|
|
223
|
+
status: 'failed',
|
|
224
|
+
error: error as ApiError,
|
|
225
|
+
duration
|
|
226
|
+
});
|
|
227
|
+
updateProgress(items.length - completed);
|
|
228
|
+
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const results = await Promise.all(promises);
|
|
234
|
+
updateProgress(0);
|
|
235
|
+
return results;
|
|
236
|
+
}
|
|
150
237
|
|
|
151
|
-
// Process each chunk
|
|
152
|
-
for (
|
|
238
|
+
// Process each chunk with progress tracking
|
|
239
|
+
for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
|
|
240
|
+
currentBatch = chunkIndex;
|
|
241
|
+
const chunk = chunks[chunkIndex];
|
|
242
|
+
|
|
153
243
|
if (effectiveConcurrent && chunk.length > effectiveConcurrent) {
|
|
154
244
|
// Process chunk with concurrency limit
|
|
155
245
|
for (let i = 0; i < chunk.length; i += effectiveConcurrent) {
|
|
156
246
|
const batch = chunk.slice(i, i + effectiveConcurrent);
|
|
157
|
-
const
|
|
247
|
+
const baseIndex = chunkIndex * (effectiveBatchSize || items.length) + i;
|
|
248
|
+
|
|
249
|
+
updateProgress(batch.length);
|
|
250
|
+
|
|
251
|
+
const batchPromises = batch.map(async (item, batchItemIndex) => {
|
|
252
|
+
const globalIndex = baseIndex + batchItemIndex;
|
|
253
|
+
const itemStartTime = Date.now();
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
const result = await processor(item, globalIndex);
|
|
257
|
+
const duration = Date.now() - itemStartTime;
|
|
258
|
+
|
|
259
|
+
completed++;
|
|
260
|
+
progressResults.push({
|
|
261
|
+
index: globalIndex,
|
|
262
|
+
status: 'completed',
|
|
263
|
+
result,
|
|
264
|
+
duration
|
|
265
|
+
});
|
|
266
|
+
updateProgress(Math.max(0, batch.length - (batchItemIndex + 1)));
|
|
267
|
+
|
|
268
|
+
return result;
|
|
269
|
+
} catch (error) {
|
|
270
|
+
const duration = Date.now() - itemStartTime;
|
|
271
|
+
failed++;
|
|
272
|
+
completed++;
|
|
273
|
+
|
|
274
|
+
progressResults.push({
|
|
275
|
+
index: globalIndex,
|
|
276
|
+
status: 'failed',
|
|
277
|
+
error: error as ApiError,
|
|
278
|
+
duration
|
|
279
|
+
});
|
|
280
|
+
updateProgress(Math.max(0, batch.length - (batchItemIndex + 1)));
|
|
281
|
+
|
|
282
|
+
throw error;
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const batchResults = await Promise.all(batchPromises);
|
|
158
287
|
results.push(...batchResults);
|
|
159
288
|
}
|
|
160
289
|
} else {
|
|
161
290
|
// Process entire chunk at once
|
|
162
|
-
const
|
|
291
|
+
const baseIndex = chunkIndex * (effectiveBatchSize || items.length);
|
|
292
|
+
|
|
293
|
+
updateProgress(chunk.length);
|
|
294
|
+
|
|
295
|
+
const chunkPromises = chunk.map(async (item, chunkItemIndex) => {
|
|
296
|
+
const globalIndex = baseIndex + chunkItemIndex;
|
|
297
|
+
const itemStartTime = Date.now();
|
|
298
|
+
|
|
299
|
+
try {
|
|
300
|
+
const result = await processor(item, globalIndex);
|
|
301
|
+
const duration = Date.now() - itemStartTime;
|
|
302
|
+
|
|
303
|
+
completed++;
|
|
304
|
+
progressResults.push({
|
|
305
|
+
index: globalIndex,
|
|
306
|
+
status: 'completed',
|
|
307
|
+
result,
|
|
308
|
+
duration
|
|
309
|
+
});
|
|
310
|
+
updateProgress(Math.max(0, chunk.length - (chunkItemIndex + 1)));
|
|
311
|
+
|
|
312
|
+
return result;
|
|
313
|
+
} catch (error) {
|
|
314
|
+
const duration = Date.now() - itemStartTime;
|
|
315
|
+
failed++;
|
|
316
|
+
completed++;
|
|
317
|
+
|
|
318
|
+
progressResults.push({
|
|
319
|
+
index: globalIndex,
|
|
320
|
+
status: 'failed',
|
|
321
|
+
error: error as ApiError,
|
|
322
|
+
duration
|
|
323
|
+
});
|
|
324
|
+
updateProgress(Math.max(0, chunk.length - (chunkItemIndex + 1)));
|
|
325
|
+
|
|
326
|
+
throw error;
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
const chunkResults = await Promise.all(chunkPromises);
|
|
163
331
|
results.push(...chunkResults);
|
|
164
332
|
}
|
|
165
333
|
}
|
|
166
334
|
|
|
335
|
+
// Final progress update
|
|
336
|
+
updateProgress(0);
|
|
167
337
|
return results;
|
|
168
338
|
}
|
|
169
339
|
|
|
170
340
|
// Batch operation with multiple IDs (only for patch and delete)
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
executeOpts?.ids &&
|
|
174
|
-
executeOpts.ids.length > 0 &&
|
|
175
|
-
(method.toLowerCase() === "patch" || method.toLowerCase() === "delete")
|
|
176
|
-
) {
|
|
177
|
-
const responses = await processBatch(executeOpts.ids, async (id) => {
|
|
341
|
+
if (isBatchOperation && executeOpts?.ids && executeOpts.ids.length > 0) {
|
|
342
|
+
const responses = await processBatch(executeOpts.ids, async (id, index) => {
|
|
178
343
|
const finalPath = buildPath(basePath, id);
|
|
179
344
|
return $fetch<T>(finalPath, {
|
|
180
345
|
baseURL: fullBaseURL,
|
|
@@ -190,14 +355,8 @@ export function useEnfyraApi<T = any>(
|
|
|
190
355
|
}
|
|
191
356
|
|
|
192
357
|
// Batch operation with files array for POST method
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
method.toLowerCase() === "post" &&
|
|
196
|
-
executeOpts?.files &&
|
|
197
|
-
Array.isArray(executeOpts.files) &&
|
|
198
|
-
executeOpts.files.length > 0
|
|
199
|
-
) {
|
|
200
|
-
const responses = await processBatch(executeOpts.files, async (fileObj: any) => {
|
|
358
|
+
if (isBatchOperation && executeOpts?.files && Array.isArray(executeOpts.files) && executeOpts.files.length > 0) {
|
|
359
|
+
const responses = await processBatch(executeOpts.files, async (fileObj: any, index) => {
|
|
201
360
|
return $fetch<T>(basePath, {
|
|
202
361
|
baseURL: fullBaseURL,
|
|
203
362
|
method: method as any,
|
package/src/module.ts
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
addServerHandler,
|
|
5
5
|
addImportsDir,
|
|
6
6
|
} from "@nuxt/kit";
|
|
7
|
+
import { ENFYRA_API_PREFIX } from "./constants/config";
|
|
7
8
|
|
|
8
9
|
export default defineNuxtModule({
|
|
9
10
|
meta: {
|
|
@@ -12,16 +13,30 @@ export default defineNuxtModule({
|
|
|
12
13
|
},
|
|
13
14
|
defaults: {
|
|
14
15
|
apiUrl: "",
|
|
15
|
-
apiPrefix: "/api",
|
|
16
16
|
appUrl: "",
|
|
17
17
|
},
|
|
18
18
|
setup(options, nuxt) {
|
|
19
19
|
const { resolve } = createResolver(import.meta.url);
|
|
20
20
|
|
|
21
|
-
//
|
|
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
|
+
|
|
35
|
+
// Make module options available at runtime with hardcoded apiPrefix
|
|
22
36
|
nuxt.options.runtimeConfig.public.enfyraSDK = {
|
|
23
37
|
...nuxt.options.runtimeConfig.public.enfyraSDK,
|
|
24
38
|
...options,
|
|
39
|
+
apiPrefix: ENFYRA_API_PREFIX,
|
|
25
40
|
};
|
|
26
41
|
|
|
27
42
|
// Auto-import composables
|
|
@@ -33,21 +48,21 @@ export default defineNuxtModule({
|
|
|
33
48
|
middleware: true,
|
|
34
49
|
});
|
|
35
50
|
|
|
36
|
-
// Register server handlers from SDK
|
|
51
|
+
// Register server handlers from SDK with hardcoded prefix
|
|
37
52
|
addServerHandler({
|
|
38
|
-
route:
|
|
53
|
+
route: `${ENFYRA_API_PREFIX}/login`,
|
|
39
54
|
handler: resolve("./runtime/server/api/login.post"),
|
|
40
55
|
method: "post",
|
|
41
56
|
});
|
|
42
57
|
|
|
43
58
|
addServerHandler({
|
|
44
|
-
route:
|
|
59
|
+
route: `${ENFYRA_API_PREFIX}/logout`,
|
|
45
60
|
handler: resolve("./runtime/server/api/logout.post"),
|
|
46
61
|
method: "post",
|
|
47
62
|
});
|
|
48
63
|
|
|
49
64
|
addServerHandler({
|
|
50
|
-
route:
|
|
65
|
+
route: `${ENFYRA_API_PREFIX}/**`,
|
|
51
66
|
handler: resolve("./runtime/server/api/all"),
|
|
52
67
|
});
|
|
53
68
|
},
|
package/src/types/index.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export interface EnfyraConfig {
|
|
2
2
|
apiUrl: string;
|
|
3
|
-
apiPrefix?: string;
|
|
4
3
|
defaultHeaders?: Record<string, string>;
|
|
5
4
|
}
|
|
6
5
|
|
|
@@ -11,7 +10,39 @@ export interface ApiError {
|
|
|
11
10
|
response?: any;
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
export interface
|
|
13
|
+
export interface BatchProgress {
|
|
14
|
+
/** Current progress percentage (0-100) */
|
|
15
|
+
progress: number;
|
|
16
|
+
/** Number of completed operations */
|
|
17
|
+
completed: number;
|
|
18
|
+
/** Total number of operations */
|
|
19
|
+
total: number;
|
|
20
|
+
/** Number of failed operations */
|
|
21
|
+
failed: number;
|
|
22
|
+
/** Number of operations currently in progress */
|
|
23
|
+
inProgress: number;
|
|
24
|
+
/** Estimated time remaining in milliseconds */
|
|
25
|
+
estimatedTimeRemaining?: number;
|
|
26
|
+
/** Average time per operation in milliseconds */
|
|
27
|
+
averageTime?: number;
|
|
28
|
+
/** Current batch being processed */
|
|
29
|
+
currentBatch: number;
|
|
30
|
+
/** Total number of batches */
|
|
31
|
+
totalBatches: number;
|
|
32
|
+
/** Processing speed (operations per second) */
|
|
33
|
+
operationsPerSecond?: number;
|
|
34
|
+
/** Detailed results array for completed operations */
|
|
35
|
+
results: Array<{
|
|
36
|
+
index: number;
|
|
37
|
+
status: 'completed' | 'failed';
|
|
38
|
+
result?: any;
|
|
39
|
+
error?: ApiError;
|
|
40
|
+
duration?: number;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Base options available for all operations
|
|
45
|
+
interface BaseApiOptions<T> {
|
|
15
46
|
method?: 'get' | 'post' | 'put' | 'patch' | 'delete' | 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
16
47
|
body?: any;
|
|
17
48
|
query?: Record<string, any>;
|
|
@@ -19,10 +50,6 @@ export interface ApiOptions<T> {
|
|
|
19
50
|
errorContext?: string;
|
|
20
51
|
onError?: (error: ApiError, context?: string) => void;
|
|
21
52
|
disableBatch?: boolean;
|
|
22
|
-
/** Batch size for chunking large operations (default: no limit) */
|
|
23
|
-
batchSize?: number;
|
|
24
|
-
/** Maximum concurrent requests (default: no limit) */
|
|
25
|
-
concurrent?: number;
|
|
26
53
|
default?: () => T;
|
|
27
54
|
/** Enable SSR with useFetch instead of $fetch */
|
|
28
55
|
ssr?: boolean;
|
|
@@ -30,6 +57,28 @@ export interface ApiOptions<T> {
|
|
|
30
57
|
key?: string;
|
|
31
58
|
}
|
|
32
59
|
|
|
60
|
+
// Batch-specific options (only available for batch operations)
|
|
61
|
+
interface BatchApiOptions {
|
|
62
|
+
/** Batch size for chunking large operations (default: no limit) - Only available for batch operations */
|
|
63
|
+
batchSize?: number;
|
|
64
|
+
/** Maximum concurrent requests (default: no limit) - Only available for batch operations */
|
|
65
|
+
concurrent?: number;
|
|
66
|
+
/** Real-time progress callback for batch operations - Only available for batch operations */
|
|
67
|
+
onProgress?: (progress: BatchProgress) => void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Conditional type that adds batch options only for batch-capable methods
|
|
71
|
+
type ConditionalBatchOptions<T> = T extends { method?: 'patch' | 'delete' | 'PATCH' | 'DELETE' }
|
|
72
|
+
? BatchApiOptions
|
|
73
|
+
: T extends { method?: 'post' | 'POST' }
|
|
74
|
+
? BatchApiOptions // POST supports file batch uploads
|
|
75
|
+
: T extends { method?: undefined } // Default method is 'get', but could be overridden at execution
|
|
76
|
+
? Partial<BatchApiOptions> // Allow batch options but make them optional since method could change
|
|
77
|
+
: {};
|
|
78
|
+
|
|
79
|
+
// Main ApiOptions interface with conditional batch support
|
|
80
|
+
export type ApiOptions<T> = BaseApiOptions<T> & ConditionalBatchOptions<BaseApiOptions<T>>;
|
|
81
|
+
|
|
33
82
|
export interface BackendError {
|
|
34
83
|
success: false;
|
|
35
84
|
message: string;
|
|
@@ -59,9 +108,14 @@ export interface UseEnfyraApiSSRReturn<T> extends AsyncData<T | null, ApiError>
|
|
|
59
108
|
}
|
|
60
109
|
|
|
61
110
|
// Execute options interface
|
|
62
|
-
|
|
111
|
+
// Base execute options available for all operations
|
|
112
|
+
interface BaseExecuteOptions {
|
|
63
113
|
body?: any;
|
|
64
114
|
id?: string | number;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Batch execute options (only available when doing batch operations)
|
|
118
|
+
interface BatchExecuteOptions {
|
|
65
119
|
ids?: (string | number)[];
|
|
66
120
|
/** Array of FormData objects for batch upload */
|
|
67
121
|
files?: FormData[];
|
|
@@ -69,8 +123,19 @@ export interface ExecuteOptions {
|
|
|
69
123
|
batchSize?: number;
|
|
70
124
|
/** Override concurrent limit for this specific execution */
|
|
71
125
|
concurrent?: number;
|
|
126
|
+
/** Override progress callback for this specific execution */
|
|
127
|
+
onProgress?: (progress: BatchProgress) => void;
|
|
72
128
|
}
|
|
73
129
|
|
|
130
|
+
// Conditional execute options based on what's being executed
|
|
131
|
+
type ConditionalExecuteOptions<T> = T extends { ids: any }
|
|
132
|
+
? BatchExecuteOptions // If ids provided, enable all batch options
|
|
133
|
+
: T extends { files: any }
|
|
134
|
+
? BatchExecuteOptions // If files provided, enable all batch options
|
|
135
|
+
: BaseExecuteOptions & Partial<BatchExecuteOptions>; // Otherwise base options + optional batch options
|
|
136
|
+
|
|
137
|
+
export type ExecuteOptions = BaseExecuteOptions & BatchExecuteOptions;
|
|
138
|
+
|
|
74
139
|
// Client Mode return type
|
|
75
140
|
export interface UseEnfyraApiClientReturn<T> {
|
|
76
141
|
data: Ref<T | null>;
|
package/src/utils/config.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { H3Event, proxyRequest } from "h3";
|
|
2
2
|
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
import { ENFYRA_API_PREFIX } from "../../constants/config";
|
|
3
4
|
|
|
4
5
|
export function proxyToAPI(event: H3Event, customPath?: string) {
|
|
5
6
|
const config = useRuntimeConfig();
|
|
6
|
-
const apiPrefix = config.public.enfyraSDK.apiPrefix;
|
|
7
|
+
const apiPrefix = config.public.enfyraSDK.apiPrefix || ENFYRA_API_PREFIX;
|
|
7
8
|
const rawPath =
|
|
8
9
|
customPath || event.path.replace(new RegExp(`^${apiPrefix}`), "");
|
|
9
10
|
const targetUrl = `${config.public.enfyraSDK.apiUrl}${rawPath}`;
|