@enfyra/sdk-nuxt 0.1.13 → 0.2.1

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
@@ -1,6 +1,16 @@
1
1
  # @enfyra/sdk-nuxt
2
2
 
3
- Nuxt SDK for Enfyra CMS
3
+ Nuxt SDK for Enfyra CMS - A powerful composable-based API client with full SSR support and TypeScript integration.
4
+
5
+ ## Features
6
+
7
+ ✅ **SSR & Client-Side Support** - Automatic server-side rendering with `useFetch` or client-side with `$fetch`
8
+ ✅ **Authentication Integration** - Built-in auth composables with automatic header forwarding
9
+ ✅ **TypeScript Support** - Full type safety with auto-generated declarations
10
+ ✅ **Batch Operations** - Efficient bulk operations for CRUD actions (client-side)
11
+ ✅ **Error Handling** - Automatic error management with console logging
12
+ ✅ **Reactive State** - Built-in loading, error, and data states
13
+ ✅ **Caching Support** - Optional cache keys for SSR mode optimization
4
14
 
5
15
  ## Installation
6
16
 
@@ -8,37 +18,353 @@ Nuxt SDK for Enfyra CMS
8
18
  npm install @enfyra/sdk-nuxt
9
19
  ```
10
20
 
11
- ## Usage
21
+ ## Setup
22
+
23
+ Add the module to your `nuxt.config.ts`:
24
+
25
+ ```typescript
26
+ export default defineNuxtConfig({
27
+ modules: ["@enfyra/sdk-nuxt"],
28
+ enfyraSDK: {
29
+ apiUrl: "http://localhost:1105",
30
+ appUrl: "http://localhost:3001",
31
+ },
32
+ })
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ### SSR Mode - Perfect for Page Data
38
+
39
+ ```typescript
40
+ // pages/users.vue
41
+ <script setup>
42
+ // ✅ Automatic execution on server-side with caching (runs immediately, no execute() needed)
43
+ const { data: users, pending, error, refresh } = useEnfyraApi('/users', {
44
+ ssr: true,
45
+ key: 'users-list' // Optional cache key
46
+ });
47
+ </script>
48
+
49
+ <template>
50
+ <div>
51
+ <div v-if="pending">Loading users...</div>
52
+ <div v-else-if="error">Error: {{ error }}</div>
53
+ <div v-else>
54
+ <h1>Users ({{ users?.meta?.totalCount }})</h1>
55
+ <UserCard v-for="user in users?.data" :key="user.id" :user="user" />
56
+ <button @click="refresh">Refresh</button>
57
+ </div>
58
+ </div>
59
+ </template>
60
+ ```
61
+
62
+ ### Client Mode - Perfect for User Interactions
63
+
64
+ ```typescript
65
+ // components/CreateUserForm.vue
66
+ <script setup>
67
+ // ✅ Manual execution control for form submissions
68
+ const { execute: createUser, pending, error } = useEnfyraApi('/users', {
69
+ method: 'post',
70
+ errorContext: 'Create User'
71
+ });
72
+
73
+ const formData = reactive({
74
+ name: '',
75
+ email: ''
76
+ });
77
+
78
+ async function handleSubmit() {
79
+ await createUser({ body: formData });
80
+
81
+ if (!error.value) {
82
+ toast.success('User created successfully!');
83
+ await navigateTo('/users');
84
+ }
85
+ }
86
+ </script>
87
+ ```
88
+
89
+ ### Authentication
90
+
91
+ ```typescript
92
+ <script setup>
93
+ const { me, login, logout, isLoggedIn } = useEnfyraAuth();
94
+
95
+ // Login
96
+ await login({
97
+ email: 'user@example.com',
98
+ password: 'password123'
99
+ });
100
+
101
+ // Check auth status
102
+ console.log('Logged in:', isLoggedIn.value);
103
+ console.log('Current user:', me.value);
104
+
105
+ // Logout
106
+ await logout();
107
+ </script>
108
+ ```
109
+
110
+ ## Core Composables
111
+
112
+ ### `useEnfyraApi<T>(path, options)`
113
+
114
+ Main composable for API requests with both SSR and client-side support.
115
+
116
+ ```typescript
117
+ // SSR Mode - Runs immediately (like useFetch)
118
+ const { data, pending, error, refresh } = useEnfyraApi('/endpoint', {
119
+ ssr: true,
120
+ key: 'cache-key', // Optional
121
+ method: 'get',
122
+ query: { page: 1 }
123
+ });
124
+ // ⚠️ Returns useFetch result: { data, pending, error, refresh }
125
+
126
+ // Client Mode - Manual execution
127
+ const { data, pending, error, execute } = useEnfyraApi('/endpoint', {
128
+ method: 'post',
129
+ errorContext: 'Create Resource'
130
+ });
131
+ // ⚠️ Returns custom result: { data, pending, error, execute }
132
+
133
+ await execute({
134
+ body: { name: 'New Item' },
135
+ id: '123' // For /endpoint/123
136
+ });
137
+ ```
138
+
139
+ **Options:**
140
+ - `ssr?: boolean` - Enable server-side rendering mode (executes immediately like useFetch)
141
+ - `method?: 'get' | 'post' | 'patch' | 'delete'` - HTTP method
142
+ - `body?: any` - Request body (POST/PATCH)
143
+ - `query?: Record<string, any>` - URL query parameters
144
+ - `headers?: Record<string, string>` - Custom headers
145
+ - `errorContext?: string` - Error context for logging
146
+ - `key?: string` - Cache key (SSR mode, optional)
147
+ - `default?: () => T` - Default value (SSR mode only)
148
+
149
+ **⚠️ Important: Return Types Differ**
150
+ - **SSR Mode**: Returns `useFetch` result `{ data, pending, error, refresh }`
151
+ - **Client Mode**: Returns custom result `{ data, pending, error, execute }`
152
+
153
+ **Execute Options (Client mode only):**
154
+ - `id?: string | number` - Single resource ID
155
+ - `ids?: (string | number)[]` - Batch operation IDs (PATCH/DELETE)
156
+ - `files?: FormData[]` - Batch file upload (POST)
157
+ - `body?: any` - Override request body
158
+
159
+ ### `useEnfyraAuth()`
160
+
161
+ Authentication composable with reactive state management.
162
+
163
+ ```typescript
164
+ const { me, login, logout, fetchUser, isLoggedIn } = useEnfyraAuth();
165
+
166
+ // Properties
167
+ me.value // Current user data (reactive)
168
+ isLoggedIn.value // Auth status (computed)
169
+
170
+ // Methods
171
+ await login({ email, password }) // Login user
172
+ await logout() // Logout user
173
+ await fetchUser() // Refresh user data
174
+ ```
175
+
176
+ ## Advanced Usage
177
+
178
+ ### Batch Operations
179
+
180
+ ```typescript
181
+ // Batch delete multiple items
182
+ const { execute: deleteItems } = useEnfyraApi('/items', {
183
+ method: 'delete',
184
+ errorContext: 'Delete Items'
185
+ });
186
+
187
+ await deleteItems({ ids: ['1', '2', '3'] });
188
+
189
+ // Batch file upload
190
+ const { execute: uploadFiles } = useEnfyraApi('/files', {
191
+ method: 'post',
192
+ errorContext: 'Upload Files'
193
+ });
194
+
195
+ await uploadFiles({
196
+ files: [formData1, formData2, formData3]
197
+ });
198
+ ```
199
+
200
+ ### TypeScript Integration
201
+
202
+ ```typescript
203
+ // Define your API response types
204
+ interface User {
205
+ id: string;
206
+ name: string;
207
+ email: string;
208
+ }
209
+
210
+ interface ApiResponse<T> {
211
+ data: T[];
212
+ meta: { totalCount: number };
213
+ }
214
+
215
+ // Use with full type safety
216
+ const { data } = useEnfyraApi<ApiResponse<User>>('/users', {
217
+ ssr: true
218
+ });
219
+
220
+ // TypeScript knows data.value is ApiResponse<User> | null
221
+ const users = computed(() => data.value?.data || []);
222
+ ```
223
+
224
+ ### Reactive Parameters
225
+
226
+ ```typescript
227
+ const searchQuery = ref('');
228
+ const page = ref(1);
229
+
230
+ // SSR mode with reactive query (executes immediately)
231
+ const { data, refresh } = useEnfyraApi('/users', {
232
+ ssr: true,
233
+ key: () => `users-${page.value}-${searchQuery.value}`, // Optional
234
+ query: computed(() => ({
235
+ search: searchQuery.value,
236
+ page: page.value,
237
+ limit: 10
238
+ }))
239
+ });
240
+
241
+ // Watch for changes and refresh
242
+ watch([searchQuery, page], () => refresh());
243
+ ```
244
+
245
+ ## Documentation
246
+
247
+ For comprehensive guides and examples:
248
+
249
+ 📚 **[useEnfyraApi Complete Guide](https://github.com/dothinh115/enfyra-sdk-nuxt/blob/main/docs/useEnfyraApi.md)** - Detailed documentation with examples, best practices, and troubleshooting
250
+
251
+ Key topics covered:
252
+ - SSR vs Client Mode comparison
253
+ - Authentication and headers forwarding
254
+ - Batch operations and CRUD patterns
255
+ - Error handling best practices
256
+ - TypeScript integration
257
+ - Performance optimization
258
+ - Migration guides
259
+
260
+ ## Configuration
12
261
 
13
- Add to your `nuxt.config.ts`:
262
+ ### Module Options
14
263
 
15
264
  ```typescript
265
+ // nuxt.config.ts
16
266
  export default defineNuxtConfig({
17
- modules: [
18
- '@enfyra/sdk-nuxt'
19
- ],
20
- runtimeConfig: {
21
- public: {
22
- enfyraSDK: {
23
- appUrl: 'http://localhost:3000',
24
- apiUrl: process.env.API_URL,
25
- apiPrefix: '/api',
26
- },
27
- },
267
+ modules: ["@enfyra/sdk-nuxt"],
268
+ enfyraSDK: {
269
+ // Required: Main API URL
270
+ apiUrl: process.env.ENFYRA_API_URL || "http://localhost:1105",
271
+
272
+ // Optional: API path prefix (default: '')
273
+ apiPrefix: '/api/v1',
274
+
275
+ // Required: App URL for SSR requests
276
+ appUrl: process.env.ENFYRA_APP_URL || "http://localhost:3001",
28
277
  },
29
278
  })
30
279
  ```
31
280
 
32
- ## Composables
281
+ ### Environment Variables
33
282
 
34
- ### useEnfyraApi
283
+ ```bash
284
+ # .env
285
+ ENFYRA_API_URL=https://api.enfyra.com
286
+ ENFYRA_APP_URL=https://app.enfyra.com
287
+ ```
288
+
289
+ ## Best Practices
290
+
291
+ ### 1. Choose the Right Mode
292
+
293
+ ```typescript
294
+ // ✅ Use SSR for initial page data (runs immediately)
295
+ const { data } = useEnfyraApi('/dashboard', {
296
+ ssr: true,
297
+ key: 'dashboard' // Optional
298
+ });
299
+
300
+ // ✅ Use Client mode for user interactions (manual execution)
301
+ const { execute: saveData } = useEnfyraApi('/settings', {
302
+ method: 'patch',
303
+ errorContext: 'Save Settings'
304
+ });
305
+ ```
306
+
307
+ ### 2. Proper Error Handling
35
308
 
36
309
  ```typescript
37
- const { data, error, pending, execute } = useEnfyraApi('/users')
310
+ // Check error state (don't use try-catch)
311
+ async function handleSubmit() {
312
+ await execute({ body: formData });
313
+
314
+ if (error.value) {
315
+ return; // Error already logged
316
+ }
317
+
318
+ // Success handling
319
+ toast.success('Saved successfully');
320
+ }
38
321
  ```
39
322
 
40
- ### useEnfyraAuth
323
+ ### 3. Type Safety
41
324
 
42
325
  ```typescript
43
- const { me, login, logout, fetchUser } = useEnfyraAuth()
44
- ```
326
+ // Define interfaces for API responses
327
+ interface CreateUserResponse {
328
+ data: User;
329
+ message: string;
330
+ }
331
+
332
+ const { execute } = useEnfyraApi<CreateUserResponse>('/users', {
333
+ method: 'post'
334
+ });
335
+ ```
336
+
337
+ ## Troubleshooting
338
+
339
+ ### Common Issues
340
+
341
+ 1. **Headers not forwarded in SSR** - Ensure `ssr: true` is set
342
+ 2. **Batch operations not working** - Only available in Client mode
343
+ 3. **Data not reactive** - Use computed refs for reactive parameters
344
+ 4. **TypeScript errors** - Check return type differences between modes
345
+
346
+ ### Performance Tips
347
+
348
+ - Use SSR for initial data loading (better SEO, faster page loads)
349
+ - Use Client mode for user interactions (better UX)
350
+ - Implement proper cache keys to avoid over-caching
351
+ - Group related operations with batch APIs
352
+
353
+ ## License
354
+
355
+ MIT
356
+
357
+ ## Contributing
358
+
359
+ Pull requests are welcome! Please read our contributing guidelines and ensure tests pass.
360
+
361
+ ## Changelog
362
+
363
+ See [CHANGELOG.md](https://github.com/dothinh115/enfyra-sdk-nuxt/blob/main/CHANGELOG.md) for a detailed history of changes and migration guides.
364
+
365
+ ## Support
366
+
367
+ For issues and questions:
368
+ - 📖 Check the [detailed documentation](https://github.com/dothinh115/enfyra-sdk-nuxt/blob/main/docs/useEnfyraApi.md)
369
+ - 🐛 [Report bugs](https://github.com/dothinh115/enfyra-sdk-nuxt/issues)
370
+ - 💬 [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,57 @@
1
1
  import { ref, unref, toRaw } from "vue";
2
2
  import { $fetch } from "../utils/http";
3
- import { useRuntimeConfig } 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;
3
+ import { useRuntimeConfig, useFetch, useRequestHeaders } from "#imports";
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 } = opts;
19
+ const { method = "get", body, query, errorContext, onError, ssr, key } = opts;
20
+ if (ssr) {
21
+ const config = useRuntimeConfig().public.enfyraSDK;
22
+ const basePath = (typeof path === "function" ? path() : path).replace(/^\/?api\/?/, "").replace(/^\/+/, "");
23
+ const finalUrl = (config?.appUrl || "") + (config?.apiPrefix || "") + "/" + basePath;
24
+ const clientHeaders = process.client ? {} : useRequestHeaders([
25
+ "authorization",
26
+ "cookie",
27
+ "user-agent",
28
+ "accept",
29
+ "accept-language",
30
+ "referer"
31
+ ]);
32
+ const serverHeaders = { ...clientHeaders };
33
+ delete serverHeaders.connection;
34
+ delete serverHeaders["keep-alive"];
35
+ delete serverHeaders.host;
36
+ delete serverHeaders["content-length"];
37
+ const fetchOptions = {
38
+ method,
39
+ body,
40
+ query,
41
+ headers: {
42
+ ...serverHeaders,
43
+ ...opts.headers
44
+ // Custom headers override client headers
45
+ }
46
+ };
47
+ if (key) {
48
+ fetchOptions.key = key;
49
+ }
50
+ if (opts.default) {
51
+ fetchOptions.default = opts.default;
52
+ }
53
+ return useFetch(finalUrl, fetchOptions);
54
+ }
37
55
  const data = ref(null);
38
56
  const error = ref(null);
39
57
  const pending = ref(false);
@@ -92,8 +110,8 @@ export function useEnfyraApi(path, opts = {}) {
92
110
  data.value = response;
93
111
  return response;
94
112
  } catch (err) {
95
- error.value = err;
96
- handleApiError(err, errorContext);
113
+ const apiError = handleError(err, errorContext, onError);
114
+ error.value = apiError;
97
115
  return null;
98
116
  } finally {
99
117
  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,14 +3,25 @@ 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;
13
20
  default?: () => T;
21
+ /** Enable SSR with useFetch instead of $fetch */
22
+ ssr?: boolean;
23
+ /** Unique key for useFetch caching */
24
+ key?: string;
14
25
  }
15
26
  export interface BackendError {
16
27
  success: false;
@@ -28,9 +39,16 @@ export interface BackendErrorExtended extends BackendError {
28
39
  };
29
40
  }
30
41
  import type { Ref } from 'vue';
31
- export interface UseEnfyraApiReturn<T> {
42
+ import type { AsyncData } from 'nuxt/app';
43
+ export interface UseEnfyraApiSSRReturn<T> extends AsyncData<T | null, ApiError> {
44
+ data: Ref<T | null>;
45
+ pending: Ref<boolean>;
46
+ error: Ref<ApiError | null>;
47
+ refresh: () => Promise<void>;
48
+ }
49
+ export interface UseEnfyraApiClientReturn<T> {
32
50
  data: Ref<T | null>;
33
- error: Ref<any>;
51
+ error: Ref<ApiError | null>;
34
52
  pending: Ref<boolean>;
35
53
  execute: (executeOpts?: {
36
54
  body?: any;
@@ -40,5 +58,4 @@ export interface UseEnfyraApiReturn<T> {
40
58
  }) => Promise<T | T[] | null>;
41
59
  }
42
60
  export * from './auth';
43
- export * from './composables';
44
61
  //# 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;CACnB;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.cjs CHANGED
@@ -15,6 +15,10 @@ 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
+ nuxt.options.runtimeConfig.public.enfyraSDK = {
19
+ ...nuxt.options.runtimeConfig.public.enfyraSDK,
20
+ ...options
21
+ };
18
22
  kit.addImportsDir(resolve("./composables"));
19
23
  kit.addServerHandler({
20
24
  handler: resolve("./runtime/server/middleware/auth"),
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@enfyra/sdk-nuxt",
3
3
  "configKey": "enfyraSDK",
4
- "version": "0.1.13",
4
+ "version": "0.2.1",
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,10 @@ const module = defineNuxtModule({
12
12
  },
13
13
  setup(options, nuxt) {
14
14
  const { resolve } = createResolver(import.meta.url);
15
+ nuxt.options.runtimeConfig.public.enfyraSDK = {
16
+ ...nuxt.options.runtimeConfig.public.enfyraSDK,
17
+ ...options
18
+ };
15
19
  addImportsDir(resolve("./composables"));
16
20
  addServerHandler({
17
21
  handler: resolve("./runtime/server/middleware/auth"),
package/package.json CHANGED
@@ -1,7 +1,15 @@
1
1
  {
2
2
  "name": "@enfyra/sdk-nuxt",
3
- "version": "0.1.13",
3
+ "version": "0.2.1",
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,58 +1,104 @@
1
1
  import { ref, unref, toRaw } from "vue";
2
2
  import type {
3
3
  ApiOptions,
4
+ ApiError,
4
5
  BackendErrorExtended,
5
- UseEnfyraApiReturn,
6
+ UseEnfyraApiSSRReturn,
7
+ UseEnfyraApiClientReturn,
6
8
  } from "../types";
7
9
  import { $fetch } from "../utils/http";
8
- import { useRuntimeConfig } from "#imports";
9
-
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;
10
+ import { useRuntimeConfig, useFetch, useRequestHeaders } from "#imports";
11
+
12
+ function handleError(
13
+ error: any,
14
+ context?: string,
15
+ customHandler?: (error: ApiError, context?: string) => void
16
+ ) {
17
+ // Transform error to ApiError format
18
+ const apiError: ApiError = {
19
+ message: error?.message || error?.data?.message || "Request failed",
20
+ status: error?.status || error?.response?.status,
21
+ data: error?.data || error?.response?.data,
22
+ response: error?.response || error
23
+ };
24
+
25
+ if (customHandler) {
26
+ customHandler(apiError, context);
27
+ } else {
28
+ console.error(`[Enfyra API Error]`, { error: apiError, context });
38
29
  }
39
30
 
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
- });
31
+ return apiError;
47
32
  }
48
33
 
34
+ // Function overloads for proper TypeScript support
35
+ export function useEnfyraApi<T = any>(
36
+ path: (() => string) | string,
37
+ opts: ApiOptions<T> & { ssr: true }
38
+ ): UseEnfyraApiSSRReturn<T>;
39
+
40
+ export function useEnfyraApi<T = any>(
41
+ path: (() => string) | string,
42
+ opts?: ApiOptions<T> & { ssr?: false | undefined }
43
+ ): UseEnfyraApiClientReturn<T>;
44
+
49
45
  export function useEnfyraApi<T = any>(
50
46
  path: (() => string) | string,
51
47
  opts: ApiOptions<T> = {}
52
- ): UseEnfyraApiReturn<T> {
53
- const { method = "get", body, query, errorContext } = opts;
48
+ ): UseEnfyraApiSSRReturn<T> | UseEnfyraApiClientReturn<T> {
49
+ const { method = "get", body, query, errorContext, onError, ssr, key } = opts;
50
+
51
+ // SSR mode - use useFetch
52
+ if (ssr) {
53
+ const config = useRuntimeConfig().public.enfyraSDK;
54
+ const basePath = (typeof path === "function" ? path() : path)
55
+ .replace(/^\/?api\/?/, "")
56
+ .replace(/^\/+/, ""); // Remove leading slashes
57
+
58
+ const finalUrl =
59
+ (config?.appUrl || "") + (config?.apiPrefix || "") + "/" + basePath;
60
+
61
+ // Get headers from client request and filter out connection-specific headers
62
+ const clientHeaders = process.client
63
+ ? {}
64
+ : useRequestHeaders([
65
+ "authorization",
66
+ "cookie",
67
+ "user-agent",
68
+ "accept",
69
+ "accept-language",
70
+ "referer",
71
+ ]);
72
+
73
+ // Remove connection-specific headers that shouldn't be forwarded
74
+ const serverHeaders = { ...clientHeaders };
75
+ delete serverHeaders.connection;
76
+ delete serverHeaders["keep-alive"];
77
+ delete serverHeaders.host;
78
+ delete serverHeaders["content-length"];
79
+
80
+ const fetchOptions: any = {
81
+ method: method as any,
82
+ body: body,
83
+ query: query,
84
+ headers: {
85
+ ...serverHeaders,
86
+ ...opts.headers, // Custom headers override client headers
87
+ },
88
+ };
89
+
90
+ // Only add useFetch-specific options if provided
91
+ if (key) {
92
+ fetchOptions.key = key;
93
+ }
94
+ if (opts.default) {
95
+ fetchOptions.default = opts.default;
96
+ }
97
+
98
+ return useFetch<T>(finalUrl, fetchOptions) as UseEnfyraApiSSRReturn<T>;
99
+ }
54
100
  const data = ref<T | null>(null);
55
- const error = ref<any>(null);
101
+ const error = ref<ApiError | null>(null);
56
102
  const pending = ref(false);
57
103
 
58
104
  const execute = async (executeOpts?: {
@@ -69,7 +115,6 @@ export function useEnfyraApi<T = any>(
69
115
  const config: any = useRuntimeConfig().public.enfyraSDK;
70
116
  const apiUrl = config?.appUrl;
71
117
  const apiPrefix = config?.apiPrefix;
72
-
73
118
  const basePath = (typeof path === "function" ? path() : path)
74
119
  .replace(/^\/?api\/?/, "")
75
120
  .replace(/^\/+/, ""); // Remove leading slashes
@@ -146,8 +191,8 @@ export function useEnfyraApi<T = any>(
146
191
  data.value = response;
147
192
  return response;
148
193
  } catch (err) {
149
- error.value = err;
150
- handleApiError(err, errorContext);
194
+ const apiError = handleError(err, errorContext, onError);
195
+ error.value = apiError;
151
196
  return null;
152
197
  } finally {
153
198
  pending.value = false;
@@ -155,9 +200,9 @@ export function useEnfyraApi<T = any>(
155
200
  };
156
201
 
157
202
  return {
158
- data: data as any,
203
+ data,
159
204
  error,
160
205
  pending,
161
206
  execute,
162
- };
207
+ } as UseEnfyraApiClientReturn<T>;
163
208
  }
package/src/module.ts CHANGED
@@ -18,6 +18,12 @@ export default defineNuxtModule({
18
18
  setup(options, nuxt) {
19
19
  const { resolve } = createResolver(import.meta.url);
20
20
 
21
+ // Make module options available at runtime
22
+ nuxt.options.runtimeConfig.public.enfyraSDK = {
23
+ ...nuxt.options.runtimeConfig.public.enfyraSDK,
24
+ ...options,
25
+ };
26
+
21
27
  // Auto-import composables
22
28
  addImportsDir(resolve("./composables"));
23
29
 
@@ -4,14 +4,26 @@ 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;
14
22
  default?: () => T;
23
+ /** Enable SSR with useFetch instead of $fetch */
24
+ ssr?: boolean;
25
+ /** Unique key for useFetch caching */
26
+ key?: string;
15
27
  }
16
28
 
17
29
  export interface BackendError {
@@ -32,10 +44,20 @@ export interface BackendErrorExtended extends BackendError {
32
44
  }
33
45
 
34
46
  import type { Ref } from 'vue';
47
+ import type { AsyncData } from 'nuxt/app';
48
+
49
+ // SSR Mode return type (same as useFetch)
50
+ export interface UseEnfyraApiSSRReturn<T> extends AsyncData<T | null, ApiError> {
51
+ data: Ref<T | null>;
52
+ pending: Ref<boolean>;
53
+ error: Ref<ApiError | null>;
54
+ refresh: () => Promise<void>;
55
+ }
35
56
 
36
- export interface UseEnfyraApiReturn<T> {
57
+ // Client Mode return type
58
+ export interface UseEnfyraApiClientReturn<T> {
37
59
  data: Ref<T | null>;
38
- error: Ref<any>;
60
+ error: Ref<ApiError | null>;
39
61
  pending: Ref<boolean>;
40
62
  execute: (executeOpts?: {
41
63
  body?: any;
@@ -45,8 +67,7 @@ export interface UseEnfyraApiReturn<T> {
45
67
  }) => Promise<T | T[] | null>;
46
68
  }
47
69
 
70
+
48
71
  // Re-export auth types
49
72
  export * from './auth';
50
73
 
51
- // Re-export composables types
52
- 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>