@enfyra/sdk-nuxt 0.2.0 → 0.2.2

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
@@ -24,17 +24,10 @@ Add the module to your `nuxt.config.ts`:
24
24
 
25
25
  ```typescript
26
26
  export default defineNuxtConfig({
27
- modules: [
28
- '@enfyra/sdk-nuxt'
29
- ],
30
- runtimeConfig: {
31
- public: {
32
- enfyraSDK: {
33
- apiUrl: process.env.ENFYRA_API_URL || 'https://api.enfyra.com',
34
- apiPrefix: '/api/v1', // Optional: API prefix
35
- appUrl: process.env.ENFYRA_APP_URL || 'https://app.enfyra.com'
36
- },
37
- },
27
+ modules: ["@enfyra/sdk-nuxt"],
28
+ enfyraSDK: {
29
+ apiUrl: "http://localhost:1105",
30
+ appUrl: "http://localhost:3001",
38
31
  },
39
32
  })
40
33
  ```
@@ -46,10 +39,10 @@ export default defineNuxtConfig({
46
39
  ```typescript
47
40
  // pages/users.vue
48
41
  <script setup>
49
- // ✅ Automatic server-side rendering with caching
42
+ // ✅ Automatic execution on server-side with caching (runs immediately, no execute() needed)
50
43
  const { data: users, pending, error, refresh } = useEnfyraApi('/users', {
51
44
  ssr: true,
52
- key: 'users-list'
45
+ key: 'users-list' // Optional cache key
53
46
  });
54
47
  </script>
55
48
 
@@ -121,19 +114,21 @@ await logout();
121
114
  Main composable for API requests with both SSR and client-side support.
122
115
 
123
116
  ```typescript
124
- // SSR Mode - Automatic execution
117
+ // SSR Mode - Runs immediately (like useFetch)
125
118
  const { data, pending, error, refresh } = useEnfyraApi('/endpoint', {
126
119
  ssr: true,
127
- key: 'cache-key',
120
+ key: 'cache-key', // Optional
128
121
  method: 'get',
129
122
  query: { page: 1 }
130
123
  });
124
+ // ⚠️ Returns useFetch result: { data, pending, error, refresh }
131
125
 
132
126
  // Client Mode - Manual execution
133
127
  const { data, pending, error, execute } = useEnfyraApi('/endpoint', {
134
128
  method: 'post',
135
129
  errorContext: 'Create Resource'
136
130
  });
131
+ // ⚠️ Returns custom result: { data, pending, error, execute }
137
132
 
138
133
  await execute({
139
134
  body: { name: 'New Item' },
@@ -142,20 +137,29 @@ await execute({
142
137
  ```
143
138
 
144
139
  **Options:**
145
- - `ssr?: boolean` - Enable server-side rendering mode
140
+ - `ssr?: boolean` - Enable server-side rendering mode (executes immediately like useFetch)
146
141
  - `method?: 'get' | 'post' | 'patch' | 'delete'` - HTTP method
147
142
  - `body?: any` - Request body (POST/PATCH)
148
143
  - `query?: Record<string, any>` - URL query parameters
149
144
  - `headers?: Record<string, string>` - Custom headers
150
145
  - `errorContext?: string` - Error context for logging
151
- - `key?: string` - Cache key (SSR mode only)
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
+ - `key?: string` - Cache key (SSR mode, optional)
152
150
  - `default?: () => T` - Default value (SSR mode only)
153
151
 
152
+ **⚠️ Important: Return Types Differ**
153
+ - **SSR Mode**: Returns `useFetch` result `{ data, pending, error, refresh }`
154
+ - **Client Mode**: Returns custom result `{ data, pending, error, execute }`
155
+
154
156
  **Execute Options (Client mode only):**
155
157
  - `id?: string | number` - Single resource ID
156
158
  - `ids?: (string | number)[]` - Batch operation IDs (PATCH/DELETE)
157
- - `files?: FormData[]` - Batch file upload (POST)
159
+ - `files?: FormData[]` - Array of FormData objects for batch upload (POST)
158
160
  - `body?: any` - Override request body
161
+ - `batchSize?: number` - Override batch size for this execution
162
+ - `concurrent?: number` - Override concurrent limit for this execution
159
163
 
160
164
  ### `useEnfyraAuth()`
161
165
 
@@ -179,7 +183,7 @@ await fetchUser() // Refresh user data
179
183
  ### Batch Operations
180
184
 
181
185
  ```typescript
182
- // Batch delete multiple items
186
+ // Basic batch delete - unlimited parallel requests
183
187
  const { execute: deleteItems } = useEnfyraApi('/items', {
184
188
  method: 'delete',
185
189
  errorContext: 'Delete Items'
@@ -187,14 +191,50 @@ const { execute: deleteItems } = useEnfyraApi('/items', {
187
191
 
188
192
  await deleteItems({ ids: ['1', '2', '3'] });
189
193
 
190
- // Batch file upload
191
- const { execute: uploadFiles } = useEnfyraApi('/files', {
192
- method: 'post',
194
+ // Advanced batch operations with concurrency control
195
+ const { execute: deleteMany } = useEnfyraApi('/users', {
196
+ method: 'delete',
197
+ batchSize: 10, // Process 10 items at a time
198
+ concurrent: 3, // Max 3 requests simultaneously
199
+ onError: (error, context) => toast.error(`${context}: ${error.message}`)
200
+ });
201
+
202
+ // Delete 100 users in controlled batches
203
+ await deleteMany({ ids: Array.from({length: 100}, (_, i) => `user-${i}`) });
204
+
205
+ // Override batch settings per execution
206
+ const { execute: updateUsers } = useEnfyraApi('/users', {
207
+ method: 'patch',
208
+ batchSize: 20, // Default: 20 per batch
209
+ concurrent: 5 // Default: 5 concurrent
210
+ });
211
+
212
+ // This execution uses different settings
213
+ await updateUsers({
214
+ ids: largeUserList,
215
+ body: { status: 'active' },
216
+ batchSize: 50, // Override: 50 per batch for this call
217
+ concurrent: 10 // Override: 10 concurrent for this call
218
+ });
219
+
220
+ // Batch file upload with progress control
221
+ const { execute: uploadFiles } = useEnfyraApi('/file_definition', {
222
+ method: 'post',
223
+ batchSize: 5, // Upload 5 files at a time
224
+ concurrent: 2, // Max 2 uploads simultaneously
193
225
  errorContext: 'Upload Files'
194
226
  });
195
227
 
228
+ // Convert files to FormData array (matches enfyra_app pattern)
229
+ const formDataArray = selectedFiles.map(file => {
230
+ const formData = new FormData();
231
+ formData.append('file', file);
232
+ formData.append('folder', folderId || 'null');
233
+ return formData;
234
+ });
235
+
196
236
  await uploadFiles({
197
- files: [formData1, formData2, formData3]
237
+ files: formDataArray // Array of FormData objects
198
238
  });
199
239
  ```
200
240
 
@@ -228,10 +268,10 @@ const users = computed(() => data.value?.data || []);
228
268
  const searchQuery = ref('');
229
269
  const page = ref(1);
230
270
 
231
- // SSR mode with reactive query
271
+ // SSR mode with reactive query (executes immediately)
232
272
  const { data, refresh } = useEnfyraApi('/users', {
233
273
  ssr: true,
234
- key: () => `users-${page.value}-${searchQuery.value}`,
274
+ key: () => `users-${page.value}-${searchQuery.value}`, // Optional
235
275
  query: computed(() => ({
236
276
  search: searchQuery.value,
237
277
  page: page.value,
@@ -247,7 +287,7 @@ watch([searchQuery, page], () => refresh());
247
287
 
248
288
  For comprehensive guides and examples:
249
289
 
250
- 📚 **[useEnfyraApi Complete Guide](./docs/useEnfyraApi.md)** - Detailed documentation with examples, best practices, and troubleshooting
290
+ 📚 **[useEnfyraApi Complete Guide](https://github.com/dothinh115/enfyra-sdk-nuxt/blob/main/docs/useEnfyraApi.md)** - Detailed documentation with examples, best practices, and troubleshooting
251
291
 
252
292
  Key topics covered:
253
293
  - SSR vs Client Mode comparison
@@ -260,30 +300,21 @@ Key topics covered:
260
300
 
261
301
  ## Configuration
262
302
 
263
- ### Runtime Config Options
303
+ ### Module Options
264
304
 
265
305
  ```typescript
266
306
  // nuxt.config.ts
267
307
  export default defineNuxtConfig({
268
- runtimeConfig: {
269
- public: {
270
- enfyraSDK: {
271
- // Required: Main API URL
272
- apiUrl: process.env.ENFYRA_API_URL,
273
-
274
- // Optional: API path prefix (default: '')
275
- apiPrefix: '/api/v1',
276
-
277
- // Required: App URL for SSR requests
278
- appUrl: process.env.ENFYRA_APP_URL,
279
-
280
- // Optional: Default headers for all requests
281
- defaultHeaders: {
282
- 'Accept': 'application/json',
283
- 'Content-Type': 'application/json'
284
- }
285
- },
286
- },
308
+ modules: ["@enfyra/sdk-nuxt"],
309
+ enfyraSDK: {
310
+ // Required: Main API URL
311
+ apiUrl: process.env.ENFYRA_API_URL || "http://localhost:1105",
312
+
313
+ // Optional: API path prefix (default: '')
314
+ apiPrefix: '/api/v1',
315
+
316
+ // Required: App URL for SSR requests
317
+ appUrl: process.env.ENFYRA_APP_URL || "http://localhost:3001",
287
318
  },
288
319
  })
289
320
  ```
@@ -301,13 +332,13 @@ ENFYRA_APP_URL=https://app.enfyra.com
301
332
  ### 1. Choose the Right Mode
302
333
 
303
334
  ```typescript
304
- // ✅ Use SSR for initial page data
335
+ // ✅ Use SSR for initial page data (runs immediately)
305
336
  const { data } = useEnfyraApi('/dashboard', {
306
337
  ssr: true,
307
- key: 'dashboard'
338
+ key: 'dashboard' // Optional
308
339
  });
309
340
 
310
- // ✅ Use Client mode for user interactions
341
+ // ✅ Use Client mode for user interactions (manual execution)
311
342
  const { execute: saveData } = useEnfyraApi('/settings', {
312
343
  method: 'patch',
313
344
  errorContext: 'Save Settings'
@@ -370,11 +401,11 @@ Pull requests are welcome! Please read our contributing guidelines and ensure te
370
401
 
371
402
  ## Changelog
372
403
 
373
- See [CHANGELOG.md](./CHANGELOG.md) for a detailed history of changes and migration guides.
404
+ See [CHANGELOG.md](https://github.com/dothinh115/enfyra-sdk-nuxt/blob/main/CHANGELOG.md) for a detailed history of changes and migration guides.
374
405
 
375
406
  ## Support
376
407
 
377
408
  For issues and questions:
378
- - 📖 Check the [detailed documentation](./docs/useEnfyraApi.md)
379
- - 🐛 [Report bugs](https://github.com/enfyra/sdk-nuxt/issues)
380
- - 💬 [Join our community](https://discord.gg/enfyra)
409
+ - 📖 Check the [detailed documentation](https://github.com/dothinh115/enfyra-sdk-nuxt/blob/main/docs/useEnfyraApi.md)
410
+ - 🐛 [Report bugs](https://github.com/dothinh115/enfyra-sdk-nuxt/issues)
411
+ - 💬 [GitHub Discussions](https://github.com/dothinh115/enfyra-sdk-nuxt/discussions)
@@ -1,2 +1,12 @@
1
- import type { ApiOptions, UseEnfyraApiReturn } from "../types";
2
- export declare function useEnfyraApi<T = any>(path: (() => string) | string, opts?: ApiOptions<T>): UseEnfyraApiReturn<T>;
1
+ import type { ApiOptions, UseEnfyraApiSSRReturn, UseEnfyraApiClientReturn } from "../types";
2
+
3
+ // Function overloads for proper TypeScript support
4
+ export declare function useEnfyraApi<T = any>(
5
+ path: (() => string) | string,
6
+ opts: ApiOptions<T> & { ssr: true }
7
+ ): UseEnfyraApiSSRReturn<T>;
8
+
9
+ export declare function useEnfyraApi<T = any>(
10
+ path: (() => string) | string,
11
+ opts?: ApiOptions<T> & { ssr?: false | undefined }
12
+ ): UseEnfyraApiClientReturn<T>;
@@ -1,39 +1,22 @@
1
1
  import { ref, unref, toRaw } from "vue";
2
2
  import { $fetch } from "../utils/http";
3
3
  import { useRuntimeConfig, useFetch, useRequestHeaders } from "#imports";
4
- function handleApiError(error, context) {
5
- let message = "Request failed";
6
- let errorCode = "UNKNOWN_ERROR";
7
- let correlationId;
8
- if (error?.response?.data) {
9
- const responseData = error.response.data;
10
- if (responseData.error) {
11
- message = responseData.error.message || responseData.message || "Request failed";
12
- errorCode = responseData.error.code;
13
- correlationId = responseData.error.correlationId;
14
- } else {
15
- message = responseData.message || "Request failed";
16
- }
17
- } else if (error?.data) {
18
- const errorData = error.data;
19
- if (errorData.error) {
20
- message = errorData.error.message || errorData.message || "Request failed";
21
- errorCode = errorData.error.code;
22
- correlationId = errorData.error.correlationId;
23
- } else {
24
- message = errorData.message || "Request failed";
25
- }
26
- } else if (error?.message) {
27
- message = error.message;
4
+ function handleError(error, context, customHandler) {
5
+ const apiError = {
6
+ message: error?.message || error?.data?.message || "Request failed",
7
+ status: error?.status || error?.response?.status,
8
+ data: error?.data || error?.response?.data,
9
+ response: error?.response || error
10
+ };
11
+ if (customHandler) {
12
+ customHandler(apiError, context);
13
+ } else {
14
+ console.error(`[Enfyra API Error]`, { error: apiError, context });
28
15
  }
29
- console.error(`[Enfyra API Error] ${errorCode}: ${message}`, {
30
- context,
31
- correlationId,
32
- error
33
- });
16
+ return apiError;
34
17
  }
35
18
  export function useEnfyraApi(path, opts = {}) {
36
- const { method = "get", body, query, errorContext, ssr, key } = opts;
19
+ const { method = "get", body, query, errorContext, onError, ssr, key, batchSize, concurrent } = opts;
37
20
  if (ssr) {
38
21
  const config = useRuntimeConfig().public.enfyraSDK;
39
22
  const basePath = (typeof path === "function" ? path() : path).replace(/^\/?api\/?/, "").replace(/^\/+/, "");
@@ -82,12 +65,38 @@ export function useEnfyraApi(path, opts = {}) {
82
65
  const basePath = (typeof path === "function" ? path() : path).replace(/^\/?api\/?/, "").replace(/^\/+/, "");
83
66
  const finalBody = executeOpts?.body || unref(body);
84
67
  const finalQuery = unref(query);
68
+ const effectiveBatchSize = executeOpts?.batchSize ?? batchSize;
69
+ const effectiveConcurrent = executeOpts?.concurrent ?? concurrent;
85
70
  const buildPath = (...segments) => {
86
71
  return segments.filter(Boolean).join("/");
87
72
  };
88
73
  const fullBaseURL = apiUrl + (apiPrefix || "");
74
+ async function processBatch(items, processor) {
75
+ const results = [];
76
+ if (!effectiveBatchSize && !effectiveConcurrent) {
77
+ const promises = items.map(processor);
78
+ return await Promise.all(promises);
79
+ }
80
+ const chunks = effectiveBatchSize ? Array.from(
81
+ { length: Math.ceil(items.length / effectiveBatchSize) },
82
+ (_, i) => items.slice(i * effectiveBatchSize, i * effectiveBatchSize + effectiveBatchSize)
83
+ ) : [items];
84
+ for (const chunk of chunks) {
85
+ if (effectiveConcurrent && chunk.length > effectiveConcurrent) {
86
+ for (let i = 0; i < chunk.length; i += effectiveConcurrent) {
87
+ const batch = chunk.slice(i, i + effectiveConcurrent);
88
+ const batchResults = await Promise.all(batch.map(processor));
89
+ results.push(...batchResults);
90
+ }
91
+ } else {
92
+ const chunkResults = await Promise.all(chunk.map(processor));
93
+ results.push(...chunkResults);
94
+ }
95
+ }
96
+ return results;
97
+ }
89
98
  if (!opts.disableBatch && executeOpts?.ids && executeOpts.ids.length > 0 && (method.toLowerCase() === "patch" || method.toLowerCase() === "delete")) {
90
- const promises = executeOpts.ids.map(async (id) => {
99
+ const responses = await processBatch(executeOpts.ids, async (id) => {
91
100
  const finalPath2 = buildPath(basePath, id);
92
101
  return $fetch(finalPath2, {
93
102
  baseURL: fullBaseURL,
@@ -97,12 +106,11 @@ export function useEnfyraApi(path, opts = {}) {
97
106
  query: finalQuery
98
107
  });
99
108
  });
100
- const responses = await Promise.all(promises);
101
109
  data.value = responses;
102
110
  return responses;
103
111
  }
104
112
  if (!opts.disableBatch && method.toLowerCase() === "post" && executeOpts?.files && Array.isArray(executeOpts.files) && executeOpts.files.length > 0) {
105
- const promises = executeOpts.files.map(async (fileObj) => {
113
+ const responses = await processBatch(executeOpts.files, async (fileObj) => {
106
114
  return $fetch(basePath, {
107
115
  baseURL: fullBaseURL,
108
116
  method,
@@ -112,7 +120,6 @@ export function useEnfyraApi(path, opts = {}) {
112
120
  query: finalQuery
113
121
  });
114
122
  });
115
- const responses = await Promise.all(promises);
116
123
  data.value = responses;
117
124
  return responses;
118
125
  }
@@ -127,8 +134,8 @@ export function useEnfyraApi(path, opts = {}) {
127
134
  data.value = response;
128
135
  return response;
129
136
  } catch (err) {
130
- error.value = err;
131
- handleApiError(err, errorContext);
137
+ const apiError = handleError(err, errorContext, onError);
138
+ error.value = apiError;
132
139
  return null;
133
140
  } finally {
134
141
  pending.value = false;
@@ -1,5 +1,6 @@
1
1
  import type { Ref, ComputedRef } from 'vue';
2
- import type { LoginPayload, User, ApiOptions, UseEnfyraApiReturn } from './index';
2
+ import type { LoginPayload, User, ApiOptions, UseEnfyraApiSSRReturn, UseEnfyraApiClientReturn } from './index';
3
+
3
4
  export declare function useEnfyraAuth(): {
4
5
  me: Ref<User | null>;
5
6
  login: (payload: LoginPayload) => Promise<any>;
@@ -7,5 +8,14 @@ export declare function useEnfyraAuth(): {
7
8
  fetchUser: () => Promise<void>;
8
9
  isLoggedIn: ComputedRef<boolean>;
9
10
  };
10
- export declare function useEnfyraApi<T = any>(path: (() => string) | string, opts?: ApiOptions<T>): UseEnfyraApiReturn<T>;
11
- //# sourceMappingURL=composables.d.ts.map
11
+
12
+ // Function overloads for proper TypeScript support
13
+ export declare function useEnfyraApi<T = any>(
14
+ path: (() => string) | string,
15
+ opts: ApiOptions<T> & { ssr: true }
16
+ ): UseEnfyraApiSSRReturn<T>;
17
+
18
+ export declare function useEnfyraApi<T = any>(
19
+ path: (() => string) | string,
20
+ opts?: ApiOptions<T> & { ssr?: false | undefined }
21
+ ): UseEnfyraApiClientReturn<T>;
package/dist/index.d.ts CHANGED
@@ -3,13 +3,24 @@ export interface EnfyraConfig {
3
3
  apiPrefix?: string;
4
4
  defaultHeaders?: Record<string, string>;
5
5
  }
6
+ export interface ApiError {
7
+ message: string;
8
+ status?: number;
9
+ data?: any;
10
+ response?: any;
11
+ }
6
12
  export interface ApiOptions<T> {
7
13
  method?: 'get' | 'post' | 'put' | 'patch' | 'delete' | 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
8
14
  body?: any;
9
15
  query?: Record<string, any>;
10
16
  headers?: Record<string, string>;
11
17
  errorContext?: string;
18
+ onError?: (error: ApiError, context?: string) => void;
12
19
  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;
13
24
  default?: () => T;
14
25
  /** Enable SSR with useFetch instead of $fetch */
15
26
  ssr?: boolean;
@@ -32,17 +43,29 @@ export interface BackendErrorExtended extends BackendError {
32
43
  };
33
44
  }
34
45
  import type { Ref } from 'vue';
35
- export interface UseEnfyraApiReturn<T> {
46
+ import type { AsyncData } from 'nuxt/app';
47
+ export interface UseEnfyraApiSSRReturn<T> extends AsyncData<T | null, ApiError> {
48
+ data: Ref<T | null>;
49
+ pending: Ref<boolean>;
50
+ error: Ref<ApiError | null>;
51
+ refresh: () => Promise<void>;
52
+ }
53
+ export interface ExecuteOptions {
54
+ body?: any;
55
+ id?: string | number;
56
+ ids?: (string | number)[];
57
+ /** Array of FormData objects for batch upload */
58
+ files?: FormData[];
59
+ /** Override batch size for this specific execution */
60
+ batchSize?: number;
61
+ /** Override concurrent limit for this specific execution */
62
+ concurrent?: number;
63
+ }
64
+ export interface UseEnfyraApiClientReturn<T> {
36
65
  data: Ref<T | null>;
37
- error: Ref<any>;
66
+ error: Ref<ApiError | null>;
38
67
  pending: Ref<boolean>;
39
- execute: (executeOpts?: {
40
- body?: any;
41
- id?: string | number;
42
- ids?: (string | number)[];
43
- files?: any[];
44
- }) => Promise<T | T[] | null>;
68
+ execute: (executeOpts?: ExecuteOptions) => Promise<T | T[] | null>;
45
69
  }
46
70
  export * from './auth';
47
- export * from './composables';
48
71
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/types/index.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,CAAC;IACnG,IAAI,CAAC,EAAE,GAAG,CAAC;IACX,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;IAClB,iDAAiD;IACjD,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,sCAAsC;IACtC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,KAAK,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACxD,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,GAAG,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AAED,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAE/B,MAAM,WAAW,kBAAkB,CAAC,CAAC;IACnC,IAAI,EAAE,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IACpB,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;IAChB,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACtB,OAAO,EAAE,CAAC,WAAW,CAAC,EAAE;QACtB,IAAI,CAAC,EAAE,GAAG,CAAC;QACX,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;QACrB,GAAG,CAAC,EAAE,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;QAC1B,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC;KACf,KAAK,OAAO,CAAC,CAAC,GAAG,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC;CAC/B;AAGD,cAAc,QAAQ,CAAC;AAGvB,cAAc,eAAe,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/types/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,4 +1,2 @@
1
1
  // Re-export auth types
2
2
  export * from './auth';
3
- // Re-export composables types
4
- export * from './composables';
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.0",
4
+ "version": "0.2.2",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "0.8.4",
7
7
  "unbuild": "2.0.0"
package/package.json CHANGED
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "@enfyra/sdk-nuxt",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Nuxt SDK for Enfyra CMS",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/dothinh115/enfyra-sdk-nuxt.git"
8
+ },
9
+ "bugs": {
10
+ "url": "https://github.com/dothinh115/enfyra-sdk-nuxt/issues"
11
+ },
12
+ "homepage": "https://github.com/dothinh115/enfyra-sdk-nuxt#readme",
5
13
  "main": "./dist/module.mjs",
6
14
  "types": "./dist/index.d.ts",
7
15
  "exports": {
@@ -1,56 +1,53 @@
1
1
  import { ref, unref, toRaw } from "vue";
2
2
  import type {
3
3
  ApiOptions,
4
+ ApiError,
4
5
  BackendErrorExtended,
5
- UseEnfyraApiReturn,
6
+ ExecuteOptions,
7
+ UseEnfyraApiSSRReturn,
8
+ UseEnfyraApiClientReturn,
6
9
  } from "../types";
7
10
  import { $fetch } from "../utils/http";
8
11
  import { useRuntimeConfig, useFetch, useRequestHeaders } from "#imports";
9
12
 
10
- function handleApiError(error: any, context?: string) {
11
- let message = "Request failed";
12
- let errorCode = "UNKNOWN_ERROR";
13
- let correlationId: string | undefined;
14
-
15
- // Handle backend error response format
16
- if (error?.response?.data) {
17
- const responseData = error.response.data as BackendErrorExtended;
18
- if (responseData.error) {
19
- message =
20
- responseData.error.message || responseData.message || "Request failed";
21
- errorCode = responseData.error.code;
22
- correlationId = responseData.error.correlationId;
23
- } else {
24
- message = responseData.message || "Request failed";
25
- }
26
- } else if (error?.data) {
27
- const errorData = error.data as BackendErrorExtended;
28
- if (errorData.error) {
29
- message =
30
- errorData.error.message || errorData.message || "Request failed";
31
- errorCode = errorData.error.code;
32
- correlationId = errorData.error.correlationId;
33
- } else {
34
- message = errorData.message || "Request failed";
35
- }
36
- } else if (error?.message) {
37
- message = error.message;
13
+ function handleError(
14
+ error: any,
15
+ context?: string,
16
+ customHandler?: (error: ApiError, context?: string) => void
17
+ ) {
18
+ // Transform error to ApiError format
19
+ const apiError: ApiError = {
20
+ message: error?.message || error?.data?.message || "Request failed",
21
+ status: error?.status || error?.response?.status,
22
+ data: error?.data || error?.response?.data,
23
+ response: error?.response || error
24
+ };
25
+
26
+ if (customHandler) {
27
+ customHandler(apiError, context);
28
+ } else {
29
+ console.error(`[Enfyra API Error]`, { error: apiError, context });
38
30
  }
39
31
 
40
- // You can customize error handling here
41
- // For now, just log the error
42
- console.error(`[Enfyra API Error] ${errorCode}: ${message}`, {
43
- context,
44
- correlationId,
45
- error,
46
- });
32
+ return apiError;
47
33
  }
48
34
 
35
+ // Function overloads for proper TypeScript support
36
+ export function useEnfyraApi<T = any>(
37
+ path: (() => string) | string,
38
+ opts: ApiOptions<T> & { ssr: true }
39
+ ): UseEnfyraApiSSRReturn<T>;
40
+
41
+ export function useEnfyraApi<T = any>(
42
+ path: (() => string) | string,
43
+ opts?: ApiOptions<T> & { ssr?: false | undefined }
44
+ ): UseEnfyraApiClientReturn<T>;
45
+
49
46
  export function useEnfyraApi<T = any>(
50
47
  path: (() => string) | string,
51
48
  opts: ApiOptions<T> = {}
52
- ): UseEnfyraApiReturn<T> {
53
- const { method = "get", body, query, errorContext, ssr, key } = opts;
49
+ ): UseEnfyraApiSSRReturn<T> | UseEnfyraApiClientReturn<T> {
50
+ const { method = "get", body, query, errorContext, onError, ssr, key, batchSize, concurrent } = opts;
54
51
 
55
52
  // SSR mode - use useFetch
56
53
  if (ssr) {
@@ -99,18 +96,13 @@ export function useEnfyraApi<T = any>(
99
96
  fetchOptions.default = opts.default;
100
97
  }
101
98
 
102
- return useFetch<T>(finalUrl, fetchOptions) as any;
99
+ return useFetch<T>(finalUrl, fetchOptions) as UseEnfyraApiSSRReturn<T>;
103
100
  }
104
101
  const data = ref<T | null>(null);
105
- const error = ref<any>(null);
102
+ const error = ref<ApiError | null>(null);
106
103
  const pending = ref(false);
107
104
 
108
- const execute = async (executeOpts?: {
109
- body?: any;
110
- id?: string | number;
111
- ids?: (string | number)[];
112
- files?: any[];
113
- }) => {
105
+ const execute = async (executeOpts?: ExecuteOptions) => {
114
106
  pending.value = true;
115
107
  error.value = null;
116
108
 
@@ -124,6 +116,10 @@ export function useEnfyraApi<T = any>(
124
116
  .replace(/^\/+/, ""); // Remove leading slashes
125
117
  const finalBody = executeOpts?.body || unref(body);
126
118
  const finalQuery = unref(query);
119
+
120
+ // Use executeOpts overrides if provided, otherwise fall back to options
121
+ const effectiveBatchSize = executeOpts?.batchSize ?? batchSize;
122
+ const effectiveConcurrent = executeOpts?.concurrent ?? concurrent;
127
123
 
128
124
  // Helper function to build clean path
129
125
  const buildPath = (...segments: (string | number)[]): string => {
@@ -133,6 +129,44 @@ export function useEnfyraApi<T = any>(
133
129
  // Build full base URL with prefix
134
130
  const fullBaseURL = apiUrl + (apiPrefix || "");
135
131
 
132
+ // Helper function for batch processing with chunking and concurrency control
133
+ async function processBatch<T>(
134
+ items: any[],
135
+ processor: (item: any) => Promise<T>
136
+ ): Promise<T[]> {
137
+ const results: T[] = [];
138
+
139
+ // If no limits, process all at once (current behavior)
140
+ if (!effectiveBatchSize && !effectiveConcurrent) {
141
+ const promises = items.map(processor);
142
+ return await Promise.all(promises);
143
+ }
144
+
145
+ // Chunk items by batchSize
146
+ const chunks = effectiveBatchSize ?
147
+ Array.from({ length: Math.ceil(items.length / effectiveBatchSize) }, (_, i) =>
148
+ items.slice(i * effectiveBatchSize, i * effectiveBatchSize + effectiveBatchSize)
149
+ ) : [items];
150
+
151
+ // Process each chunk
152
+ for (const chunk of chunks) {
153
+ if (effectiveConcurrent && chunk.length > effectiveConcurrent) {
154
+ // Process chunk with concurrency limit
155
+ for (let i = 0; i < chunk.length; i += effectiveConcurrent) {
156
+ const batch = chunk.slice(i, i + effectiveConcurrent);
157
+ const batchResults = await Promise.all(batch.map(processor));
158
+ results.push(...batchResults);
159
+ }
160
+ } else {
161
+ // Process entire chunk at once
162
+ const chunkResults = await Promise.all(chunk.map(processor));
163
+ results.push(...chunkResults);
164
+ }
165
+ }
166
+
167
+ return results;
168
+ }
169
+
136
170
  // Batch operation with multiple IDs (only for patch and delete)
137
171
  if (
138
172
  !opts.disableBatch &&
@@ -140,7 +174,7 @@ export function useEnfyraApi<T = any>(
140
174
  executeOpts.ids.length > 0 &&
141
175
  (method.toLowerCase() === "patch" || method.toLowerCase() === "delete")
142
176
  ) {
143
- const promises = executeOpts.ids.map(async (id) => {
177
+ const responses = await processBatch(executeOpts.ids, async (id) => {
144
178
  const finalPath = buildPath(basePath, id);
145
179
  return $fetch<T>(finalPath, {
146
180
  baseURL: fullBaseURL,
@@ -151,7 +185,6 @@ export function useEnfyraApi<T = any>(
151
185
  });
152
186
  });
153
187
 
154
- const responses = await Promise.all(promises);
155
188
  data.value = responses as T;
156
189
  return responses;
157
190
  }
@@ -164,7 +197,7 @@ export function useEnfyraApi<T = any>(
164
197
  Array.isArray(executeOpts.files) &&
165
198
  executeOpts.files.length > 0
166
199
  ) {
167
- const promises = executeOpts.files.map(async (fileObj: any) => {
200
+ const responses = await processBatch(executeOpts.files, async (fileObj: any) => {
168
201
  return $fetch<T>(basePath, {
169
202
  baseURL: fullBaseURL,
170
203
  method: method as any,
@@ -174,7 +207,6 @@ export function useEnfyraApi<T = any>(
174
207
  });
175
208
  });
176
209
 
177
- const responses = await Promise.all(promises);
178
210
  data.value = responses as T;
179
211
  return responses;
180
212
  }
@@ -195,8 +227,8 @@ export function useEnfyraApi<T = any>(
195
227
  data.value = response;
196
228
  return response;
197
229
  } catch (err) {
198
- error.value = err;
199
- handleApiError(err, errorContext);
230
+ const apiError = handleError(err, errorContext, onError);
231
+ error.value = apiError;
200
232
  return null;
201
233
  } finally {
202
234
  pending.value = false;
@@ -204,9 +236,9 @@ export function useEnfyraApi<T = any>(
204
236
  };
205
237
 
206
238
  return {
207
- data: data as any,
239
+ data,
208
240
  error,
209
241
  pending,
210
242
  execute,
211
- };
243
+ } as UseEnfyraApiClientReturn<T>;
212
244
  }
@@ -4,13 +4,25 @@ export interface EnfyraConfig {
4
4
  defaultHeaders?: Record<string, string>;
5
5
  }
6
6
 
7
+ export interface ApiError {
8
+ message: string;
9
+ status?: number;
10
+ data?: any;
11
+ response?: any;
12
+ }
13
+
7
14
  export interface ApiOptions<T> {
8
15
  method?: 'get' | 'post' | 'put' | 'patch' | 'delete' | 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
9
16
  body?: any;
10
17
  query?: Record<string, any>;
11
18
  headers?: Record<string, string>;
12
19
  errorContext?: string;
20
+ onError?: (error: ApiError, context?: string) => void;
13
21
  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;
14
26
  default?: () => T;
15
27
  /** Enable SSR with useFetch instead of $fetch */
16
28
  ssr?: boolean;
@@ -36,21 +48,38 @@ export interface BackendErrorExtended extends BackendError {
36
48
  }
37
49
 
38
50
  import type { Ref } from 'vue';
51
+ import type { AsyncData } from 'nuxt/app';
39
52
 
40
- export interface UseEnfyraApiReturn<T> {
53
+ // SSR Mode return type (same as useFetch)
54
+ export interface UseEnfyraApiSSRReturn<T> extends AsyncData<T | null, ApiError> {
41
55
  data: Ref<T | null>;
42
- error: Ref<any>;
43
56
  pending: Ref<boolean>;
44
- execute: (executeOpts?: {
45
- body?: any;
46
- id?: string | number;
47
- ids?: (string | number)[];
48
- files?: any[];
49
- }) => Promise<T | T[] | null>;
57
+ error: Ref<ApiError | null>;
58
+ refresh: () => Promise<void>;
59
+ }
60
+
61
+ // Execute options interface
62
+ export interface ExecuteOptions {
63
+ body?: any;
64
+ id?: string | number;
65
+ ids?: (string | number)[];
66
+ /** Array of FormData objects for batch upload */
67
+ files?: FormData[];
68
+ /** Override batch size for this specific execution */
69
+ batchSize?: number;
70
+ /** Override concurrent limit for this specific execution */
71
+ concurrent?: number;
50
72
  }
51
73
 
74
+ // Client Mode return type
75
+ export interface UseEnfyraApiClientReturn<T> {
76
+ data: Ref<T | null>;
77
+ error: Ref<ApiError | null>;
78
+ pending: Ref<boolean>;
79
+ execute: (executeOpts?: ExecuteOptions) => Promise<T | T[] | null>;
80
+ }
81
+
82
+
52
83
  // Re-export auth types
53
84
  export * from './auth';
54
85
 
55
- // Re-export composables types
56
- export * from './composables';
@@ -1 +0,0 @@
1
- {"version":3,"file":"composables.d.ts","sourceRoot":"","sources":["../src/types/composables.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,WAAW,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAA;AAEjF,MAAM,CAAC,OAAO,UAAU,aAAa,IAAI;IACvC,EAAE,EAAE,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,CAAA;IACpB,KAAK,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,OAAO,CAAC,GAAG,CAAC,CAAA;IAC9C,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3B,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9B,UAAU,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;CACjC,CAAA;AAED,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,CAAC,GAAG,GAAG,EAC1C,IAAI,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,MAAM,EAC7B,IAAI,CAAC,EAAE,UAAU,CAAC,CAAC,CAAC,GACnB,kBAAkB,CAAC,CAAC,CAAC,CAAA"}
@@ -1 +0,0 @@
1
- export {};
@@ -1,15 +0,0 @@
1
- import type { Ref, ComputedRef } from 'vue'
2
- import type { LoginPayload, User, ApiOptions, UseEnfyraApiReturn } from './index'
3
-
4
- export declare function useEnfyraAuth(): {
5
- me: Ref<User | null>
6
- login: (payload: LoginPayload) => Promise<any>
7
- logout: () => Promise<void>
8
- fetchUser: () => Promise<void>
9
- isLoggedIn: ComputedRef<boolean>
10
- }
11
-
12
- export declare function useEnfyraApi<T = any>(
13
- path: (() => string) | string,
14
- opts?: ApiOptions<T>
15
- ): UseEnfyraApiReturn<T>