@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.
Files changed (56) hide show
  1. package/README.md +53 -0
  2. package/dist/client/index.d.ts +117 -31
  3. package/dist/client/index.js +74 -22
  4. package/dist/client/index.js.map +1 -1
  5. package/dist/content-collections/index.js +20 -24
  6. package/dist/content-collections/index.js.map +1 -1
  7. package/dist/hooks/index.d.ts +113 -5
  8. package/dist/hooks/index.js +165 -182
  9. package/dist/hooks/index.js.map +1 -1
  10. package/dist/index.d.ts +8 -4
  11. package/dist/index.js +305 -182
  12. package/dist/index.js.map +1 -1
  13. package/dist/queries/index.d.ts +78 -1
  14. package/dist/queries/index.js +47 -0
  15. package/dist/queries/index.js.map +1 -1
  16. package/dist/stores/index.d.ts +106 -0
  17. package/dist/stores/index.js +108 -0
  18. package/dist/stores/index.js.map +1 -0
  19. package/package.json +60 -37
  20. package/src/client/__tests__/server.test.ts +100 -0
  21. package/src/client/client.md +91 -0
  22. package/src/client/index.test.ts +232 -0
  23. package/src/client/index.ts +145 -0
  24. package/src/client/server.ts +118 -0
  25. package/src/content-collections/content-collections.md +87 -0
  26. package/src/content-collections/generator.ts +314 -0
  27. package/src/content-collections/index.ts +32 -0
  28. package/src/content-collections/schemas.ts +75 -0
  29. package/src/content-collections/sync.ts +139 -0
  30. package/src/hooks/README.md +293 -0
  31. package/src/hooks/createDataHook.ts +208 -0
  32. package/src/hooks/data-hook-errors.property.test.ts +270 -0
  33. package/src/hooks/data-hook-filters.property.test.ts +263 -0
  34. package/src/hooks/data-hooks.property.test.ts +190 -0
  35. package/src/hooks/hooks.test.ts +76 -0
  36. package/src/hooks/index.ts +21 -0
  37. package/src/hooks/useAccounts.ts +66 -0
  38. package/src/hooks/useBrands.ts +95 -0
  39. package/src/hooks/useProducts.ts +88 -0
  40. package/src/hooks/useUsers.ts +89 -0
  41. package/src/index.ts +32 -0
  42. package/src/mutations/accounts.ts +127 -0
  43. package/src/mutations/brands.ts +133 -0
  44. package/src/mutations/index.ts +32 -0
  45. package/src/mutations/mutations.md +96 -0
  46. package/src/mutations/users.ts +136 -0
  47. package/src/queries/accounts.ts +121 -0
  48. package/src/queries/brands.ts +176 -0
  49. package/src/queries/index.ts +45 -0
  50. package/src/queries/products.ts +282 -0
  51. package/src/queries/queries.md +88 -0
  52. package/src/queries/server-helpers.ts +114 -0
  53. package/src/queries/users.ts +199 -0
  54. package/src/stores/createStores.ts +148 -0
  55. package/src/stores/index.ts +15 -0
  56. 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>;