@htlkg/data 0.0.1 → 0.0.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 +53 -0
- package/dist/client/index.d.ts +117 -31
- package/dist/client/index.js +74 -22
- package/dist/client/index.js.map +1 -1
- package/dist/content-collections/index.js +20 -24
- package/dist/content-collections/index.js.map +1 -1
- package/dist/hooks/index.d.ts +113 -5
- package/dist/hooks/index.js +165 -182
- package/dist/hooks/index.js.map +1 -1
- package/dist/index.d.ts +8 -4
- package/dist/index.js +305 -182
- package/dist/index.js.map +1 -1
- package/dist/queries/index.d.ts +78 -1
- package/dist/queries/index.js +47 -0
- package/dist/queries/index.js.map +1 -1
- package/dist/stores/index.d.ts +106 -0
- package/dist/stores/index.js +108 -0
- package/dist/stores/index.js.map +1 -0
- package/package.json +60 -37
- package/src/client/__tests__/server.test.ts +100 -0
- package/src/client/client.md +91 -0
- package/src/client/index.test.ts +232 -0
- package/src/client/index.ts +145 -0
- package/src/client/server.ts +118 -0
- package/src/content-collections/content-collections.md +87 -0
- package/src/content-collections/generator.ts +314 -0
- package/src/content-collections/index.ts +32 -0
- package/src/content-collections/schemas.ts +75 -0
- package/src/content-collections/sync.ts +139 -0
- package/src/hooks/README.md +293 -0
- package/src/hooks/createDataHook.ts +208 -0
- package/src/hooks/data-hook-errors.property.test.ts +270 -0
- package/src/hooks/data-hook-filters.property.test.ts +263 -0
- package/src/hooks/data-hooks.property.test.ts +190 -0
- package/src/hooks/hooks.test.ts +76 -0
- package/src/hooks/index.ts +21 -0
- package/src/hooks/useAccounts.ts +66 -0
- package/src/hooks/useBrands.ts +95 -0
- package/src/hooks/useProducts.ts +88 -0
- package/src/hooks/useUsers.ts +89 -0
- package/src/index.ts +32 -0
- package/src/mutations/accounts.ts +127 -0
- package/src/mutations/brands.ts +133 -0
- package/src/mutations/index.ts +32 -0
- package/src/mutations/mutations.md +96 -0
- package/src/mutations/users.ts +136 -0
- package/src/queries/accounts.ts +121 -0
- package/src/queries/brands.ts +176 -0
- package/src/queries/index.ts +45 -0
- package/src/queries/products.ts +282 -0
- package/src/queries/queries.md +88 -0
- package/src/queries/server-helpers.ts +114 -0
- package/src/queries/users.ts +199 -0
- package/src/stores/createStores.ts +148 -0
- package/src/stores/index.ts +15 -0
- package/src/stores/stores.md +104 -0
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
# Data Hooks
|
|
2
|
+
|
|
3
|
+
Vue composables for fetching and managing data with reactive state management.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Data hooks provide a consistent, type-safe way to interact with Amplify Data models using Vue's Composition API. Each hook provides:
|
|
8
|
+
|
|
9
|
+
- **Reactive state**: Automatic reactivity for data, loading, and error states
|
|
10
|
+
- **Auto-fetch**: Optional automatic data fetching on component mount
|
|
11
|
+
- **Error handling**: Built-in error state management and logging
|
|
12
|
+
- **Refetch**: Manual refetch capability for refreshing data
|
|
13
|
+
- **Filtering**: Support for GraphQL filters and options
|
|
14
|
+
|
|
15
|
+
## Available Hooks
|
|
16
|
+
|
|
17
|
+
### useBrands
|
|
18
|
+
|
|
19
|
+
Fetch and manage brand data.
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { useBrands } from '@htlkg/data/hooks';
|
|
23
|
+
|
|
24
|
+
const { brands, activeBrands, loading, error, refetch } = useBrands({
|
|
25
|
+
accountId: 'account-123',
|
|
26
|
+
activeOnly: true,
|
|
27
|
+
limit: 50
|
|
28
|
+
});
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Options:**
|
|
32
|
+
- `filter` - GraphQL filter criteria
|
|
33
|
+
- `limit` - Maximum number of results
|
|
34
|
+
- `autoFetch` - Auto-fetch on mount (default: true)
|
|
35
|
+
- `accountId` - Filter by account ID
|
|
36
|
+
- `activeOnly` - Only return active brands
|
|
37
|
+
|
|
38
|
+
**Returns:**
|
|
39
|
+
- `brands` - Reactive array of all brands
|
|
40
|
+
- `activeBrands` - Computed array of active brands only
|
|
41
|
+
- `loading` - Loading state
|
|
42
|
+
- `error` - Error state
|
|
43
|
+
- `refetch` - Function to refetch data
|
|
44
|
+
|
|
45
|
+
### useAccounts
|
|
46
|
+
|
|
47
|
+
Fetch and manage account data.
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { useAccounts } from '@htlkg/data/hooks';
|
|
51
|
+
|
|
52
|
+
const { accounts, loading, error, refetch } = useAccounts({
|
|
53
|
+
filter: { status: { eq: 'active' } },
|
|
54
|
+
limit: 50
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Options:**
|
|
59
|
+
- `filter` - GraphQL filter criteria
|
|
60
|
+
- `limit` - Maximum number of results
|
|
61
|
+
- `autoFetch` - Auto-fetch on mount (default: true)
|
|
62
|
+
|
|
63
|
+
**Returns:**
|
|
64
|
+
- `accounts` - Reactive array of accounts
|
|
65
|
+
- `loading` - Loading state
|
|
66
|
+
- `error` - Error state
|
|
67
|
+
- `refetch` - Function to refetch data
|
|
68
|
+
|
|
69
|
+
### useUsers
|
|
70
|
+
|
|
71
|
+
Fetch and manage user data.
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { useUsers } from '@htlkg/data/hooks';
|
|
75
|
+
|
|
76
|
+
const { users, loading, error, refetch } = useUsers({
|
|
77
|
+
brandId: 'brand-123',
|
|
78
|
+
accountId: 'account-456',
|
|
79
|
+
limit: 50
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Options:**
|
|
84
|
+
- `filter` - GraphQL filter criteria
|
|
85
|
+
- `limit` - Maximum number of results
|
|
86
|
+
- `autoFetch` - Auto-fetch on mount (default: true)
|
|
87
|
+
- `brandId` - Filter by brand ID
|
|
88
|
+
- `accountId` - Filter by account ID
|
|
89
|
+
|
|
90
|
+
**Returns:**
|
|
91
|
+
- `users` - Reactive array of users
|
|
92
|
+
- `loading` - Loading state
|
|
93
|
+
- `error` - Error state
|
|
94
|
+
- `refetch` - Function to refetch data
|
|
95
|
+
|
|
96
|
+
### useProducts
|
|
97
|
+
|
|
98
|
+
Fetch and manage product data.
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { useProducts } from '@htlkg/data/hooks';
|
|
102
|
+
|
|
103
|
+
const { products, activeProducts, loading, error, refetch } = useProducts({
|
|
104
|
+
activeOnly: true,
|
|
105
|
+
limit: 50
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
**Options:**
|
|
110
|
+
- `filter` - GraphQL filter criteria
|
|
111
|
+
- `limit` - Maximum number of results
|
|
112
|
+
- `autoFetch` - Auto-fetch on mount (default: true)
|
|
113
|
+
- `activeOnly` - Only return active products
|
|
114
|
+
|
|
115
|
+
**Returns:**
|
|
116
|
+
- `products` - Reactive array of all products
|
|
117
|
+
- `activeProducts` - Computed array of active products only
|
|
118
|
+
- `loading` - Loading state
|
|
119
|
+
- `error` - Error state
|
|
120
|
+
- `refetch` - Function to refetch data
|
|
121
|
+
|
|
122
|
+
## Usage Examples
|
|
123
|
+
|
|
124
|
+
### Basic Usage
|
|
125
|
+
|
|
126
|
+
```vue
|
|
127
|
+
<script setup lang="ts">
|
|
128
|
+
import { useBrands } from '@htlkg/data/hooks';
|
|
129
|
+
|
|
130
|
+
// Auto-fetches on mount
|
|
131
|
+
const { brands, loading, error } = useBrands();
|
|
132
|
+
</script>
|
|
133
|
+
|
|
134
|
+
<template>
|
|
135
|
+
<div v-if="loading">Loading...</div>
|
|
136
|
+
<div v-else-if="error">Error: {{ error.message }}</div>
|
|
137
|
+
<div v-else>
|
|
138
|
+
<div v-for="brand in brands" :key="brand.id">
|
|
139
|
+
{{ brand.name }}
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</template>
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### With Filters
|
|
146
|
+
|
|
147
|
+
```vue
|
|
148
|
+
<script setup lang="ts">
|
|
149
|
+
import { useBrands } from '@htlkg/data/hooks';
|
|
150
|
+
|
|
151
|
+
const { brands, loading } = useBrands({
|
|
152
|
+
accountId: 'account-123',
|
|
153
|
+
activeOnly: true,
|
|
154
|
+
limit: 50
|
|
155
|
+
});
|
|
156
|
+
</script>
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Manual Fetch
|
|
160
|
+
|
|
161
|
+
```vue
|
|
162
|
+
<script setup lang="ts">
|
|
163
|
+
import { useBrands } from '@htlkg/data/hooks';
|
|
164
|
+
import { onMounted } from 'vue';
|
|
165
|
+
|
|
166
|
+
const { brands, loading, refetch } = useBrands({
|
|
167
|
+
autoFetch: false
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
onMounted(async () => {
|
|
171
|
+
await refetch();
|
|
172
|
+
});
|
|
173
|
+
</script>
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### Computed Filters
|
|
177
|
+
|
|
178
|
+
```vue
|
|
179
|
+
<script setup lang="ts">
|
|
180
|
+
import { ref, computed } from 'vue';
|
|
181
|
+
import { useBrands } from '@htlkg/data/hooks';
|
|
182
|
+
|
|
183
|
+
const searchQuery = ref('');
|
|
184
|
+
const { brands } = useBrands();
|
|
185
|
+
|
|
186
|
+
const filteredBrands = computed(() =>
|
|
187
|
+
brands.value.filter(b =>
|
|
188
|
+
b.name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
|
189
|
+
)
|
|
190
|
+
);
|
|
191
|
+
</script>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Implementation Details
|
|
195
|
+
|
|
196
|
+
### State Management
|
|
197
|
+
|
|
198
|
+
Each hook uses Vue's `ref` and `computed` for reactive state:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
const brands = ref<Brand[]>([]);
|
|
202
|
+
const loading = ref(false);
|
|
203
|
+
const error = ref<Error | null>(null);
|
|
204
|
+
|
|
205
|
+
const activeBrands = computed(() =>
|
|
206
|
+
brands.value.filter(b => b.status === 'active')
|
|
207
|
+
);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Auto-fetch
|
|
211
|
+
|
|
212
|
+
Hooks automatically fetch data on mount when `autoFetch` is true (default):
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
if (autoFetch) {
|
|
216
|
+
onMounted(() => {
|
|
217
|
+
fetch();
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Error Handling
|
|
223
|
+
|
|
224
|
+
Errors are caught, logged, and stored in the error state:
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
try {
|
|
228
|
+
const { data, errors } = await client.models.Brand.list({ filter, limit });
|
|
229
|
+
|
|
230
|
+
if (errors) {
|
|
231
|
+
throw new Error(errors[0]?.message || "Failed to fetch brands");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
brands.value = data || [];
|
|
235
|
+
} catch (e) {
|
|
236
|
+
error.value = e as Error;
|
|
237
|
+
console.error("[useBrands] Error fetching brands:", e);
|
|
238
|
+
}
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Filter Building
|
|
242
|
+
|
|
243
|
+
Hooks build GraphQL filters from options:
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
let filter: any = baseFilter || {};
|
|
247
|
+
|
|
248
|
+
if (accountId) {
|
|
249
|
+
filter = { ...filter, accountId: { eq: accountId } };
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (activeOnly) {
|
|
253
|
+
filter = { ...filter, status: { eq: "active" } };
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Testing
|
|
258
|
+
|
|
259
|
+
Basic smoke tests verify hook structure:
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
import { useBrands } from '@htlkg/data/hooks';
|
|
263
|
+
|
|
264
|
+
const result = useBrands({ autoFetch: false });
|
|
265
|
+
|
|
266
|
+
expect(result).toHaveProperty("brands");
|
|
267
|
+
expect(result).toHaveProperty("loading");
|
|
268
|
+
expect(result).toHaveProperty("error");
|
|
269
|
+
expect(result).toHaveProperty("refetch");
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Run tests:
|
|
273
|
+
|
|
274
|
+
```bash
|
|
275
|
+
npm run test
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## Requirements
|
|
279
|
+
|
|
280
|
+
Implements requirements:
|
|
281
|
+
- **3.4**: Data hooks provide Vue composables for brands, accounts, users, and products
|
|
282
|
+
- **11.1**: useBrands returns brands, loading, error, and refetch
|
|
283
|
+
- **11.2**: useAccounts returns accounts, loading, error, and refetch
|
|
284
|
+
- **11.3**: useUsers returns users, loading, error, and refetch
|
|
285
|
+
|
|
286
|
+
## Next Steps
|
|
287
|
+
|
|
288
|
+
Future enhancements may include:
|
|
289
|
+
- Caching with localStorage
|
|
290
|
+
- Real-time subscriptions
|
|
291
|
+
- Pagination support
|
|
292
|
+
- Optimistic updates
|
|
293
|
+
- Request deduplication
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Data Hook Factory
|
|
3
|
+
*
|
|
4
|
+
* Creates reusable Vue composables for fetching data from GraphQL models.
|
|
5
|
+
* Provides a DRY approach to data fetching with consistent patterns.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { ref, computed, onMounted, type Ref, type ComputedRef } from "vue";
|
|
9
|
+
import { getSharedClient, resetSharedClient } from "../client";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Configuration options for creating a data hook
|
|
13
|
+
*/
|
|
14
|
+
export interface CreateDataHookOptions<T, TOptions extends BaseHookOptions = BaseHookOptions> {
|
|
15
|
+
/** The GraphQL model name (e.g., 'Account', 'User', 'Brand') */
|
|
16
|
+
model: string;
|
|
17
|
+
/** Default limit for queries */
|
|
18
|
+
defaultLimit?: number;
|
|
19
|
+
/** Selection set for the query (fields to fetch) */
|
|
20
|
+
selectionSet?: string[];
|
|
21
|
+
/** Transform function to apply to fetched data */
|
|
22
|
+
transform?: (item: any) => T;
|
|
23
|
+
/** Build filter from hook options */
|
|
24
|
+
buildFilter?: (options: TOptions) => any;
|
|
25
|
+
/** Define computed properties based on the data */
|
|
26
|
+
computedProperties?: Record<string, (data: T[]) => any>;
|
|
27
|
+
/** Plural name for the data property (e.g., 'accounts', 'users') */
|
|
28
|
+
dataPropertyName?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Base options available to all hooks
|
|
33
|
+
*/
|
|
34
|
+
export interface BaseHookOptions {
|
|
35
|
+
/** Filter criteria */
|
|
36
|
+
filter?: any;
|
|
37
|
+
/** Limit number of results */
|
|
38
|
+
limit?: number;
|
|
39
|
+
/** Auto-fetch on mount (default: true) */
|
|
40
|
+
autoFetch?: boolean;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Return type for data hooks
|
|
45
|
+
*/
|
|
46
|
+
export interface DataHookReturn<T, TComputed extends Record<string, any> = Record<string, never>> {
|
|
47
|
+
/** Reactive array of data */
|
|
48
|
+
data: Ref<T[]>;
|
|
49
|
+
/** Loading state */
|
|
50
|
+
loading: Ref<boolean>;
|
|
51
|
+
/** Error state */
|
|
52
|
+
error: Ref<Error | null>;
|
|
53
|
+
/** Refetch data */
|
|
54
|
+
refetch: () => Promise<void>;
|
|
55
|
+
/** Computed properties */
|
|
56
|
+
computed: { [K in keyof TComputed]: ComputedRef<TComputed[K]> };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Re-export resetSharedClient for testing convenience
|
|
60
|
+
export { resetSharedClient as resetClientInstance };
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Creates a reusable data hook for a specific model
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* // Create a simple hook
|
|
68
|
+
* export const useAccounts = createDataHook<Account>({
|
|
69
|
+
* model: 'Account',
|
|
70
|
+
* dataPropertyName: 'accounts',
|
|
71
|
+
* });
|
|
72
|
+
*
|
|
73
|
+
* // Usage
|
|
74
|
+
* const { data: accounts, loading, error, refetch } = useAccounts();
|
|
75
|
+
* ```
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* // Create a hook with custom filters and computed properties
|
|
80
|
+
* interface UseBrandsOptions extends BaseHookOptions {
|
|
81
|
+
* accountId?: string;
|
|
82
|
+
* activeOnly?: boolean;
|
|
83
|
+
* }
|
|
84
|
+
*
|
|
85
|
+
* export const useBrands = createDataHook<Brand, UseBrandsOptions>({
|
|
86
|
+
* model: 'Brand',
|
|
87
|
+
* dataPropertyName: 'brands',
|
|
88
|
+
* buildFilter: (options) => {
|
|
89
|
+
* const filter: any = options.filter || {};
|
|
90
|
+
* if (options.accountId) filter.accountId = { eq: options.accountId };
|
|
91
|
+
* if (options.activeOnly) filter.status = { eq: 'active' };
|
|
92
|
+
* return Object.keys(filter).length > 0 ? filter : undefined;
|
|
93
|
+
* },
|
|
94
|
+
* computedProperties: {
|
|
95
|
+
* activeBrands: (brands) => brands.filter(b => b.status === 'active'),
|
|
96
|
+
* },
|
|
97
|
+
* });
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export function createDataHook<
|
|
101
|
+
T,
|
|
102
|
+
TOptions extends BaseHookOptions = BaseHookOptions,
|
|
103
|
+
TComputed extends Record<string, any> = Record<string, never>,
|
|
104
|
+
>(
|
|
105
|
+
config: CreateDataHookOptions<T, TOptions> & {
|
|
106
|
+
computedProperties?: { [K in keyof TComputed]: (data: T[]) => TComputed[K] };
|
|
107
|
+
},
|
|
108
|
+
) {
|
|
109
|
+
const {
|
|
110
|
+
model,
|
|
111
|
+
defaultLimit,
|
|
112
|
+
selectionSet,
|
|
113
|
+
transform,
|
|
114
|
+
buildFilter,
|
|
115
|
+
computedProperties,
|
|
116
|
+
dataPropertyName = "data",
|
|
117
|
+
} = config;
|
|
118
|
+
|
|
119
|
+
return function useData(options: TOptions = {} as TOptions): DataHookReturn<T, TComputed> & Record<string, any> {
|
|
120
|
+
const { filter: baseFilter, limit = defaultLimit, autoFetch = true } = options;
|
|
121
|
+
|
|
122
|
+
// State
|
|
123
|
+
const data = ref<T[]>([]) as Ref<T[]>;
|
|
124
|
+
const loading = ref(false);
|
|
125
|
+
const error = ref<Error | null>(null);
|
|
126
|
+
|
|
127
|
+
// Build filter using custom builder or default
|
|
128
|
+
const getFilter = () => {
|
|
129
|
+
if (buildFilter) {
|
|
130
|
+
return buildFilter(options);
|
|
131
|
+
}
|
|
132
|
+
return baseFilter && Object.keys(baseFilter).length > 0 ? baseFilter : undefined;
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// Fetch function
|
|
136
|
+
async function fetch() {
|
|
137
|
+
loading.value = true;
|
|
138
|
+
error.value = null;
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const client = getSharedClient();
|
|
142
|
+
const queryOptions: any = {
|
|
143
|
+
filter: getFilter(),
|
|
144
|
+
limit,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
if (selectionSet) {
|
|
148
|
+
queryOptions.selectionSet = selectionSet;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const { data: responseData, errors } = await (client as any).models[model].list(queryOptions);
|
|
152
|
+
|
|
153
|
+
if (errors) {
|
|
154
|
+
throw new Error(errors[0]?.message || `Failed to fetch ${model}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const items = responseData || [];
|
|
158
|
+
data.value = transform ? items.map(transform) : items;
|
|
159
|
+
} catch (e) {
|
|
160
|
+
error.value = e as Error;
|
|
161
|
+
console.error(`[use${model}] Error fetching ${model}:`, e);
|
|
162
|
+
} finally {
|
|
163
|
+
loading.value = false;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Create computed properties
|
|
168
|
+
const computedRefs: Record<string, ComputedRef<any>> = {};
|
|
169
|
+
if (computedProperties) {
|
|
170
|
+
for (const [key, fn] of Object.entries(computedProperties)) {
|
|
171
|
+
computedRefs[key] = computed(() => fn(data.value));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Auto-fetch on mount if enabled
|
|
176
|
+
if (autoFetch) {
|
|
177
|
+
onMounted(() => {
|
|
178
|
+
fetch();
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Build return object
|
|
183
|
+
const result: DataHookReturn<T, TComputed> & Record<string, any> = {
|
|
184
|
+
data,
|
|
185
|
+
loading,
|
|
186
|
+
error,
|
|
187
|
+
refetch: fetch,
|
|
188
|
+
computed: computedRefs as { [K in keyof TComputed]: ComputedRef<TComputed[K]> },
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// Add data property with custom name for backwards compatibility
|
|
192
|
+
if (dataPropertyName !== "data") {
|
|
193
|
+
result[dataPropertyName] = data;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Add computed properties at top level for backwards compatibility
|
|
197
|
+
for (const [key, computedRef] of Object.entries(computedRefs)) {
|
|
198
|
+
result[key] = computedRef;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return result;
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Type helper to extract the return type of a created hook
|
|
207
|
+
*/
|
|
208
|
+
export type InferHookReturn<THook extends (...args: any[]) => any> = ReturnType<THook>;
|