@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 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 for CRUD actions (client-side)
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
- // Batch delete multiple items
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
- // Batch file upload
190
- const { execute: uploadFiles } = useEnfyraApi('/files', {
191
- method: 'post',
192
- errorContext: 'Upload Files'
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: [formData1, formData2, formData3]
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
- if (!opts.disableBatch && executeOpts?.ids && executeOpts.ids.length > 0 && (method.toLowerCase() === "patch" || method.toLowerCase() === "delete")) {
73
- const promises = executeOpts.ids.map(async (id) => {
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 (!opts.disableBatch && method.toLowerCase() === "post" && executeOpts?.files && Array.isArray(executeOpts.files) && executeOpts.files.length > 0) {
88
- const promises = executeOpts.files.map(async (fileObj) => {
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 ApiOptions<T> {
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/types/index.ts"],"names":[],"mappings":"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"}
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
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@enfyra/sdk-nuxt",
3
3
  "configKey": "enfyraSDK",
4
- "version": "0.2.1",
4
+ "version": "0.2.3",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "0.8.4",
7
7
  "unbuild": "2.0.0"
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enfyra/sdk-nuxt",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Nuxt SDK for Enfyra CMS",
5
5
  "repository": {
6
6
  "type": "git",
@@ -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
- !opts.disableBatch &&
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
- !opts.disableBatch &&
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,
@@ -11,7 +11,39 @@ export interface ApiError {
11
11
  response?: any;
12
12
  }
13
13
 
14
- export interface ApiOptions<T> {
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