@veristone/nuxt-v-app 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.
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* useVCrud - Veristone CRUD Composable
|
|
3
3
|
* Optimized for V-App data flows.
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* Uses $fetch for mutations (POST, PUT, PATCH, DELETE) to avoid Nuxt's useFetch caching issues.
|
|
6
6
|
* Uses useVFetch only for initial data loading (SSR compatible).
|
|
7
7
|
*/
|
|
@@ -156,8 +156,7 @@ export const useVCrud = <T = any>(endpoint: string, options: VCrudOptions<T> = {
|
|
|
156
156
|
onUpdated,
|
|
157
157
|
onDeleted,
|
|
158
158
|
} = options
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
|
|
161
160
|
// Renamed internal state for clarity
|
|
162
161
|
const isBusy = useState<boolean>(`v-crud-busy-${endpoint}`, () => false)
|
|
163
162
|
const lastError = useState<string | null>(`v-crud-error-${endpoint}`, () => null)
|
|
@@ -222,12 +221,12 @@ export const useVCrud = <T = any>(endpoint: string, options: VCrudOptions<T> = {
|
|
|
222
221
|
'Content-Type': 'application/json',
|
|
223
222
|
'X-Client-Version': 'v-app-1.0'
|
|
224
223
|
}
|
|
225
|
-
|
|
224
|
+
|
|
226
225
|
const token = getAuthToken()
|
|
227
226
|
if (token) {
|
|
228
227
|
headers['Authorization'] = `Bearer ${token}`
|
|
229
228
|
}
|
|
230
|
-
|
|
229
|
+
|
|
231
230
|
return headers
|
|
232
231
|
}
|
|
233
232
|
|
|
@@ -344,7 +343,7 @@ export const useVCrud = <T = any>(endpoint: string, options: VCrudOptions<T> = {
|
|
|
344
343
|
isBusy.value = true
|
|
345
344
|
lastError.value = null
|
|
346
345
|
isCreating.value = true
|
|
347
|
-
|
|
346
|
+
|
|
348
347
|
try {
|
|
349
348
|
const hasFile = payload instanceof FormData || Object.values(payload || {}).some((v: any) => v instanceof File || v instanceof FileList)
|
|
350
349
|
let body: any = payload
|
|
@@ -599,6 +598,44 @@ export const useVCrud = <T = any>(endpoint: string, options: VCrudOptions<T> = {
|
|
|
599
598
|
isBusy.value = false
|
|
600
599
|
}
|
|
601
600
|
}
|
|
601
|
+
// CUSTOM - For nested resource operations like /tapes/:id/loans
|
|
602
|
+
const custom = async <T = any>(
|
|
603
|
+
subPath: string,
|
|
604
|
+
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' = 'GET',
|
|
605
|
+
payload?: any,
|
|
606
|
+
id?: string | number
|
|
607
|
+
) => {
|
|
608
|
+
isBusy.value = true
|
|
609
|
+
lastError.value = null
|
|
610
|
+
try {
|
|
611
|
+
const url = id !== undefined
|
|
612
|
+
? `${constructUrl(endpoint, id)}/${subPath}`
|
|
613
|
+
: `${constructUrl(endpoint)}/${subPath}`
|
|
614
|
+
|
|
615
|
+
const hasFile = payload instanceof FormData || Object.values(payload || {}).some((v: any) => v instanceof File || v instanceof FileList)
|
|
616
|
+
let body: any = payload
|
|
617
|
+
if (hasFile && !(payload instanceof FormData)) {
|
|
618
|
+
body = new FormData()
|
|
619
|
+
Object.entries(payload || {}).forEach(([k, v]: any) => {
|
|
620
|
+
if (v instanceof FileList) Array.from(v).forEach(f => body.append(k, f))
|
|
621
|
+
else if (v instanceof File) body.append(k, v)
|
|
622
|
+
else if (v !== undefined && v !== null) body.append(k, String(v))
|
|
623
|
+
})
|
|
624
|
+
}
|
|
625
|
+
const result = await $fetch<T>(url, {
|
|
626
|
+
method,
|
|
627
|
+
baseURL: getBaseUrl(),
|
|
628
|
+
headers: getMutationHeaders(body),
|
|
629
|
+
body: method !== 'GET' ? body : undefined,
|
|
630
|
+
params: method === 'GET' && payload ? payload : undefined
|
|
631
|
+
})
|
|
632
|
+
return result
|
|
633
|
+
} catch (err) {
|
|
634
|
+
handleError(`Custom ${method}`, err)
|
|
635
|
+
} finally {
|
|
636
|
+
isBusy.value = false
|
|
637
|
+
}
|
|
638
|
+
}
|
|
602
639
|
|
|
603
640
|
// Auto-refresh list view when list inputs change.
|
|
604
641
|
watch([filterValues, searchTerm, sortConfig], async () => {
|
|
@@ -632,6 +669,7 @@ export const useVCrud = <T = any>(endpoint: string, options: VCrudOptions<T> = {
|
|
|
632
669
|
patch,
|
|
633
670
|
remove,
|
|
634
671
|
bulkRemove,
|
|
672
|
+
custom,
|
|
635
673
|
loading: isBusy, // Alias internal 'isBusy' to public 'loading'
|
|
636
674
|
errorState: lastError, // Alias internal 'lastError' to public 'errorState'
|
|
637
675
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
const endpoint = "http://localhost:3002/admin/portfolio/loans";
|
|
3
|
+
const tapesEndpoint = "http://localhost:3002/admin/portfolio/tapes";
|
|
3
4
|
|
|
4
5
|
const crud = useVCrud(endpoint, { immediate: false });
|
|
6
|
+
const tapesCrud = useVCrud(tapesEndpoint, { immediate: false });
|
|
5
7
|
|
|
6
8
|
// Store results for each method
|
|
7
9
|
const results = ref<Record<string, any>>({});
|
|
@@ -100,6 +102,22 @@ const testDelete = async () => {
|
|
|
100
102
|
}
|
|
101
103
|
};
|
|
102
104
|
|
|
105
|
+
// Test CUSTOM - nested resource (tapes/:id/loans)
|
|
106
|
+
const testCustom = async () => {
|
|
107
|
+
const tapeId = "5e8523a8-43bf-44ac-a682-7b48e1c823e8";
|
|
108
|
+
loading.value.custom = true;
|
|
109
|
+
errors.value.custom = "";
|
|
110
|
+
try {
|
|
111
|
+
// This will call: POST /admin/portfolio/tapes/5e8523a8-43bf-44ac-a682-7b48e1c823e8/loans
|
|
112
|
+
const data = await tapesCrud.custom("loans", "POST", {}, tapeId);
|
|
113
|
+
results.value.custom = data;
|
|
114
|
+
} catch (err: any) {
|
|
115
|
+
errors.value.custom = err.message || "Failed";
|
|
116
|
+
} finally {
|
|
117
|
+
loading.value.custom = false;
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
103
121
|
// Run all tests
|
|
104
122
|
const testAll = async () => {
|
|
105
123
|
await testGet();
|
|
@@ -266,5 +284,33 @@ const testAll = async () => {
|
|
|
266
284
|
>{{ JSON.stringify(results.delete, null, 2) }}</pre
|
|
267
285
|
>
|
|
268
286
|
</div>
|
|
287
|
+
|
|
288
|
+
<!-- CUSTOM (nested resource) -->
|
|
289
|
+
<div class="border-2 border-indigo-500 p-4 rounded">
|
|
290
|
+
<div class="flex items-center justify-between mb-4">
|
|
291
|
+
<h2 class="text-xl font-semibold">CUSTOM (nested resource)</h2>
|
|
292
|
+
<button
|
|
293
|
+
@click="testCustom"
|
|
294
|
+
:disabled="loading.custom"
|
|
295
|
+
class="px-3 py-1 bg-indigo-500 text-white rounded hover:bg-indigo-600 disabled:opacity-50"
|
|
296
|
+
>
|
|
297
|
+
{{ loading.custom ? "Loading..." : "Test CUSTOM" }}
|
|
298
|
+
</button>
|
|
299
|
+
</div>
|
|
300
|
+
<div class="text-sm text-gray-500 mb-2">
|
|
301
|
+
POST /admin/portfolio/tapes/5e8523a8-43bf-44ac-a682-7b48e1c823e8/loans
|
|
302
|
+
</div>
|
|
303
|
+
<div class="text-xs text-gray-400 mb-2">
|
|
304
|
+
Using: tapesCrud.custom("loans", "POST", {}, tapeId)
|
|
305
|
+
</div>
|
|
306
|
+
<div v-if="errors.custom" class="text-red-500 mb-2">
|
|
307
|
+
Error: {{ errors.custom }}
|
|
308
|
+
</div>
|
|
309
|
+
<pre
|
|
310
|
+
v-if="results.custom"
|
|
311
|
+
class="text-sm bg-gray-100 p-4 rounded overflow-auto max-h-48"
|
|
312
|
+
>{{ JSON.stringify(results.custom, null, 2) }}</pre
|
|
313
|
+
>
|
|
314
|
+
</div>
|
|
269
315
|
</div>
|
|
270
316
|
</template>
|