@htlkg/data 0.0.19 → 0.0.21

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 (39) hide show
  1. package/dist/client/index.d.ts +257 -1
  2. package/dist/client/index.js +59 -1
  3. package/dist/client/index.js.map +1 -1
  4. package/dist/common-DSxswsZ3.d.ts +40 -0
  5. package/dist/hooks/index.d.ts +162 -10
  6. package/dist/hooks/index.js +191 -33
  7. package/dist/hooks/index.js.map +1 -1
  8. package/dist/index.d.ts +9 -5
  9. package/dist/index.js +789 -13
  10. package/dist/index.js.map +1 -1
  11. package/dist/mutations/index.d.ts +342 -4
  12. package/dist/mutations/index.js +486 -0
  13. package/dist/mutations/index.js.map +1 -1
  14. package/dist/{productInstances-BA3cNsYc.d.ts → productInstances-BpQv1oLS.d.ts} +2 -40
  15. package/dist/queries/index.d.ts +113 -2
  16. package/dist/queries/index.js +192 -1
  17. package/dist/queries/index.js.map +1 -1
  18. package/dist/reservations-C0FNm__0.d.ts +154 -0
  19. package/dist/reservations-CdDfkcZ_.d.ts +172 -0
  20. package/package.json +14 -13
  21. package/src/client/index.ts +18 -0
  22. package/src/client/reservations.ts +336 -0
  23. package/src/hooks/createDataHook.test.ts +534 -0
  24. package/src/hooks/createDataHook.ts +20 -13
  25. package/src/hooks/index.ts +2 -0
  26. package/src/hooks/useContacts.test.ts +159 -0
  27. package/src/hooks/useContacts.ts +176 -0
  28. package/src/hooks/useReservations.ts +145 -0
  29. package/src/mutations/contacts.test.ts +604 -0
  30. package/src/mutations/contacts.ts +554 -0
  31. package/src/mutations/index.ts +32 -0
  32. package/src/mutations/productInstances/productInstances.test.ts +3 -3
  33. package/src/mutations/reservations.test.ts +459 -0
  34. package/src/mutations/reservations.ts +452 -0
  35. package/src/queries/contacts.test.ts +505 -0
  36. package/src/queries/contacts.ts +237 -0
  37. package/src/queries/index.ts +21 -0
  38. package/src/queries/reservations.test.ts +374 -0
  39. package/src/queries/reservations.ts +247 -0
@@ -0,0 +1,534 @@
1
+ /**
2
+ * createDataHook Tests
3
+ *
4
+ * Tests for the proxy-based data hook factory that creates
5
+ * Vue composables for fetching data from GraphQL models.
6
+ */
7
+
8
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
9
+ import { ref, computed, nextTick } from "vue";
10
+ import {
11
+ createDataHook,
12
+ resetClientInstance,
13
+ type BaseHookOptions,
14
+ type CreateDataHookOptions,
15
+ } from "./createDataHook";
16
+
17
+ // Mock Vue's onMounted to execute immediately for testing
18
+ vi.mock("vue", async () => {
19
+ const actual = await vi.importActual("vue");
20
+ return {
21
+ ...actual,
22
+ onMounted: vi.fn((callback: () => void) => callback()),
23
+ };
24
+ });
25
+
26
+ // Mock the proxy module
27
+ vi.mock("../client/proxy", () => ({
28
+ query: vi.fn(),
29
+ hasErrors: vi.fn((response: any) => !!(response.errors && response.errors.length > 0)),
30
+ getErrorMessage: vi.fn((response: any) => response.errors?.[0]?.message || null),
31
+ }));
32
+
33
+ // Import the mocked functions for assertions
34
+ import { query } from "../client/proxy";
35
+
36
+ interface MockItem {
37
+ id: string;
38
+ name: string;
39
+ status: "active" | "inactive";
40
+ accountId?: string;
41
+ }
42
+
43
+ describe("createDataHook", () => {
44
+ const mockItems: MockItem[] = [
45
+ { id: "1", name: "Item 1", status: "active", accountId: "acc-1" },
46
+ { id: "2", name: "Item 2", status: "inactive", accountId: "acc-1" },
47
+ { id: "3", name: "Item 3", status: "active", accountId: "acc-2" },
48
+ ];
49
+
50
+ beforeEach(() => {
51
+ vi.clearAllMocks();
52
+ // Default successful response
53
+ (query as any).mockResolvedValue({
54
+ data: mockItems,
55
+ errors: null,
56
+ });
57
+ });
58
+
59
+ describe("basic functionality", () => {
60
+ it("should create a hook function", () => {
61
+ const useItems = createDataHook<MockItem>({
62
+ model: "Item",
63
+ });
64
+
65
+ expect(typeof useItems).toBe("function");
66
+ });
67
+
68
+ it("should return reactive state refs", () => {
69
+ const useItems = createDataHook<MockItem>({
70
+ model: "Item",
71
+ });
72
+
73
+ const result = useItems();
74
+
75
+ expect(result.data).toBeDefined();
76
+ expect(result.loading).toBeDefined();
77
+ expect(result.error).toBeDefined();
78
+ expect(result.refetch).toBeDefined();
79
+ expect(typeof result.refetch).toBe("function");
80
+ });
81
+
82
+ it("should auto-fetch data on mount by default", async () => {
83
+ const useItems = createDataHook<MockItem>({
84
+ model: "Item",
85
+ });
86
+
87
+ useItems();
88
+
89
+ expect(query).toHaveBeenCalledWith("Item", "list", {});
90
+ });
91
+
92
+ it("should not auto-fetch when autoFetch is false", () => {
93
+ const useItems = createDataHook<MockItem>({
94
+ model: "Item",
95
+ });
96
+
97
+ useItems({ autoFetch: false });
98
+
99
+ expect(query).not.toHaveBeenCalled();
100
+ });
101
+
102
+ it("should populate data on successful fetch", async () => {
103
+ const useItems = createDataHook<MockItem>({
104
+ model: "Item",
105
+ });
106
+
107
+ const result = useItems();
108
+
109
+ // Wait for async operation
110
+ await nextTick();
111
+
112
+ expect(result.data.value).toEqual(mockItems);
113
+ expect(result.loading.value).toBe(false);
114
+ expect(result.error.value).toBeNull();
115
+ });
116
+
117
+ it("should set error on fetch failure", async () => {
118
+ (query as any).mockResolvedValue({
119
+ data: null,
120
+ errors: [{ message: "Query failed" }],
121
+ });
122
+
123
+ const useItems = createDataHook<MockItem>({
124
+ model: "Item",
125
+ });
126
+
127
+ const result = useItems();
128
+ await nextTick();
129
+
130
+ expect(result.error.value).toBeInstanceOf(Error);
131
+ expect(result.error.value?.message).toBe("Query failed");
132
+ expect(result.data.value).toEqual([]);
133
+ });
134
+
135
+ it("should handle network errors", async () => {
136
+ (query as any).mockRejectedValue(new Error("Network error"));
137
+
138
+ const useItems = createDataHook<MockItem>({
139
+ model: "Item",
140
+ });
141
+
142
+ const result = useItems();
143
+ await nextTick();
144
+
145
+ expect(result.error.value).toBeInstanceOf(Error);
146
+ expect(result.error.value?.message).toBe("Network error");
147
+ });
148
+ });
149
+
150
+ describe("query options", () => {
151
+ it("should pass limit to query", async () => {
152
+ const useItems = createDataHook<MockItem>({
153
+ model: "Item",
154
+ defaultLimit: 50,
155
+ });
156
+
157
+ useItems({ limit: 100 });
158
+
159
+ expect(query).toHaveBeenCalledWith("Item", "list", { limit: 100 });
160
+ });
161
+
162
+ it("should use default limit when not specified", async () => {
163
+ const useItems = createDataHook<MockItem>({
164
+ model: "Item",
165
+ defaultLimit: 50,
166
+ });
167
+
168
+ useItems();
169
+
170
+ expect(query).toHaveBeenCalledWith("Item", "list", { limit: 50 });
171
+ });
172
+
173
+ it("should pass filter to query", async () => {
174
+ const useItems = createDataHook<MockItem>({
175
+ model: "Item",
176
+ });
177
+
178
+ const filter = { status: { eq: "active" } };
179
+ useItems({ filter });
180
+
181
+ expect(query).toHaveBeenCalledWith("Item", "list", { filter });
182
+ });
183
+
184
+ it("should pass selectionSet to query", async () => {
185
+ const useItems = createDataHook<MockItem>({
186
+ model: "Item",
187
+ selectionSet: ["id", "name", "status"],
188
+ });
189
+
190
+ useItems();
191
+
192
+ expect(query).toHaveBeenCalledWith("Item", "list", {
193
+ selectionSet: ["id", "name", "status"],
194
+ });
195
+ });
196
+
197
+ it("should combine all query options", async () => {
198
+ const useItems = createDataHook<MockItem>({
199
+ model: "Item",
200
+ defaultLimit: 50,
201
+ selectionSet: ["id", "name"],
202
+ });
203
+
204
+ const filter = { status: { eq: "active" } };
205
+ useItems({ filter, limit: 25 });
206
+
207
+ expect(query).toHaveBeenCalledWith("Item", "list", {
208
+ filter,
209
+ limit: 25,
210
+ selectionSet: ["id", "name"],
211
+ });
212
+ });
213
+ });
214
+
215
+ describe("custom filter builder", () => {
216
+ interface UseItemsOptions extends BaseHookOptions {
217
+ accountId?: string;
218
+ activeOnly?: boolean;
219
+ }
220
+
221
+ it("should use buildFilter to construct filters", async () => {
222
+ const useItems = createDataHook<MockItem, UseItemsOptions>({
223
+ model: "Item",
224
+ buildFilter: (options) => {
225
+ const filter: any = {};
226
+ if (options.accountId) {
227
+ filter.accountId = { eq: options.accountId };
228
+ }
229
+ if (options.activeOnly) {
230
+ filter.status = { eq: "active" };
231
+ }
232
+ return Object.keys(filter).length > 0 ? filter : undefined;
233
+ },
234
+ });
235
+
236
+ useItems({ accountId: "acc-123", activeOnly: true });
237
+
238
+ expect(query).toHaveBeenCalledWith("Item", "list", {
239
+ filter: {
240
+ accountId: { eq: "acc-123" },
241
+ status: { eq: "active" },
242
+ },
243
+ });
244
+ });
245
+
246
+ it("should return undefined filter when buildFilter returns empty", async () => {
247
+ const useItems = createDataHook<MockItem, UseItemsOptions>({
248
+ model: "Item",
249
+ buildFilter: () => undefined,
250
+ });
251
+
252
+ useItems({});
253
+
254
+ expect(query).toHaveBeenCalledWith("Item", "list", {});
255
+ });
256
+ });
257
+
258
+ describe("data transformation", () => {
259
+ it("should apply transform function to fetched items", async () => {
260
+ const useItems = createDataHook<MockItem>({
261
+ model: "Item",
262
+ transform: (item) => ({
263
+ ...item,
264
+ name: item.name.toUpperCase(),
265
+ }),
266
+ });
267
+
268
+ const result = useItems();
269
+ await nextTick();
270
+
271
+ expect(result.data.value[0].name).toBe("ITEM 1");
272
+ expect(result.data.value[1].name).toBe("ITEM 2");
273
+ });
274
+
275
+ it("should handle transform errors gracefully", async () => {
276
+ const useItems = createDataHook<MockItem>({
277
+ model: "Item",
278
+ transform: () => {
279
+ throw new Error("Transform error");
280
+ },
281
+ });
282
+
283
+ const result = useItems();
284
+ await nextTick();
285
+
286
+ expect(result.error.value).toBeInstanceOf(Error);
287
+ expect(result.error.value?.message).toBe("Transform error");
288
+ });
289
+ });
290
+
291
+ describe("computed properties", () => {
292
+ it("should create computed properties from config", async () => {
293
+ const useItems = createDataHook<MockItem, BaseHookOptions, { activeItems: MockItem[] }>({
294
+ model: "Item",
295
+ computedProperties: {
296
+ activeItems: (items) => items.filter((item) => item.status === "active"),
297
+ },
298
+ });
299
+
300
+ const result = useItems();
301
+ await nextTick();
302
+
303
+ expect(result.computed.activeItems.value).toHaveLength(2);
304
+ expect(result.computed.activeItems.value.every((i) => i.status === "active")).toBe(true);
305
+ });
306
+
307
+ it("should expose computed properties at top level for backwards compatibility", async () => {
308
+ const useItems = createDataHook<MockItem, BaseHookOptions, { activeItems: MockItem[] }>({
309
+ model: "Item",
310
+ computedProperties: {
311
+ activeItems: (items) => items.filter((item) => item.status === "active"),
312
+ },
313
+ });
314
+
315
+ const result = useItems();
316
+ await nextTick();
317
+
318
+ // Should be accessible at top level
319
+ expect(result.activeItems).toBeDefined();
320
+ expect(result.activeItems.value).toHaveLength(2);
321
+ });
322
+
323
+ it("should support multiple computed properties", async () => {
324
+ const useItems = createDataHook<
325
+ MockItem,
326
+ BaseHookOptions,
327
+ { activeItems: MockItem[]; inactiveItems: MockItem[]; totalCount: number }
328
+ >({
329
+ model: "Item",
330
+ computedProperties: {
331
+ activeItems: (items) => items.filter((item) => item.status === "active"),
332
+ inactiveItems: (items) => items.filter((item) => item.status === "inactive"),
333
+ totalCount: (items) => items.length,
334
+ },
335
+ });
336
+
337
+ const result = useItems();
338
+ await nextTick();
339
+
340
+ expect(result.computed.activeItems.value).toHaveLength(2);
341
+ expect(result.computed.inactiveItems.value).toHaveLength(1);
342
+ expect(result.computed.totalCount.value).toBe(3);
343
+ });
344
+ });
345
+
346
+ describe("custom data property name", () => {
347
+ it("should expose data with custom property name", async () => {
348
+ const useItems = createDataHook<MockItem>({
349
+ model: "Item",
350
+ dataPropertyName: "items",
351
+ });
352
+
353
+ const result = useItems();
354
+ await nextTick();
355
+
356
+ expect(result.items).toBeDefined();
357
+ expect(result.items.value).toEqual(mockItems);
358
+ // Original data property should still exist
359
+ expect(result.data.value).toEqual(mockItems);
360
+ });
361
+ });
362
+
363
+ describe("refetch functionality", () => {
364
+ it("should refetch data when refetch is called", async () => {
365
+ const useItems = createDataHook<MockItem>({
366
+ model: "Item",
367
+ });
368
+
369
+ const result = useItems();
370
+ await nextTick();
371
+
372
+ expect(query).toHaveBeenCalledTimes(1);
373
+
374
+ await result.refetch();
375
+
376
+ expect(query).toHaveBeenCalledTimes(2);
377
+ });
378
+
379
+ it("should update data after refetch", async () => {
380
+ const useItems = createDataHook<MockItem>({
381
+ model: "Item",
382
+ });
383
+
384
+ const result = useItems();
385
+ await nextTick();
386
+
387
+ const newItems = [{ id: "4", name: "New Item", status: "active" as const }];
388
+ (query as any).mockResolvedValue({
389
+ data: newItems,
390
+ errors: null,
391
+ });
392
+
393
+ await result.refetch();
394
+
395
+ expect(result.data.value).toEqual(newItems);
396
+ });
397
+
398
+ it("should clear previous error on successful refetch", async () => {
399
+ (query as any).mockResolvedValueOnce({
400
+ data: null,
401
+ errors: [{ message: "First error" }],
402
+ });
403
+
404
+ const useItems = createDataHook<MockItem>({
405
+ model: "Item",
406
+ });
407
+
408
+ const result = useItems();
409
+ await nextTick();
410
+
411
+ expect(result.error.value).not.toBeNull();
412
+
413
+ (query as any).mockResolvedValue({
414
+ data: mockItems,
415
+ errors: null,
416
+ });
417
+
418
+ await result.refetch();
419
+
420
+ expect(result.error.value).toBeNull();
421
+ expect(result.data.value).toEqual(mockItems);
422
+ });
423
+
424
+ it("should set loading state during refetch", async () => {
425
+ let resolveQuery: (value: any) => void;
426
+ (query as any).mockReturnValue(
427
+ new Promise((resolve) => {
428
+ resolveQuery = resolve;
429
+ }),
430
+ );
431
+
432
+ const useItems = createDataHook<MockItem>({
433
+ model: "Item",
434
+ });
435
+
436
+ const result = useItems({ autoFetch: false });
437
+
438
+ expect(result.loading.value).toBe(false);
439
+
440
+ const refetchPromise = result.refetch();
441
+
442
+ expect(result.loading.value).toBe(true);
443
+
444
+ resolveQuery!({ data: mockItems, errors: null });
445
+ await refetchPromise;
446
+
447
+ expect(result.loading.value).toBe(false);
448
+ });
449
+ });
450
+
451
+ describe("resetClientInstance", () => {
452
+ it("should be a no-op function (proxy-based approach)", () => {
453
+ // Should not throw
454
+ expect(() => resetClientInstance()).not.toThrow();
455
+ });
456
+ });
457
+
458
+ describe("edge cases", () => {
459
+ it("should handle empty data response", async () => {
460
+ (query as any).mockResolvedValue({
461
+ data: [],
462
+ errors: null,
463
+ });
464
+
465
+ const useItems = createDataHook<MockItem>({
466
+ model: "Item",
467
+ });
468
+
469
+ const result = useItems();
470
+ await nextTick();
471
+
472
+ expect(result.data.value).toEqual([]);
473
+ expect(result.error.value).toBeNull();
474
+ });
475
+
476
+ it("should handle null data response", async () => {
477
+ (query as any).mockResolvedValue({
478
+ data: null,
479
+ errors: null,
480
+ });
481
+
482
+ const useItems = createDataHook<MockItem>({
483
+ model: "Item",
484
+ });
485
+
486
+ const result = useItems();
487
+ await nextTick();
488
+
489
+ expect(result.data.value).toEqual([]);
490
+ });
491
+
492
+ it("should handle undefined data response", async () => {
493
+ (query as any).mockResolvedValue({
494
+ data: undefined,
495
+ errors: null,
496
+ });
497
+
498
+ const useItems = createDataHook<MockItem>({
499
+ model: "Item",
500
+ });
501
+
502
+ const result = useItems();
503
+ await nextTick();
504
+
505
+ expect(result.data.value).toEqual([]);
506
+ });
507
+
508
+ it("should not pass empty filter object", async () => {
509
+ const useItems = createDataHook<MockItem>({
510
+ model: "Item",
511
+ });
512
+
513
+ useItems({ filter: {} });
514
+
515
+ expect(query).toHaveBeenCalledWith("Item", "list", {});
516
+ });
517
+
518
+ it("should generate default error message when getErrorMessage returns null", async () => {
519
+ (query as any).mockResolvedValue({
520
+ data: null,
521
+ errors: [{}], // Error without message
522
+ });
523
+
524
+ const useItems = createDataHook<MockItem>({
525
+ model: "TestModel",
526
+ });
527
+
528
+ const result = useItems();
529
+ await nextTick();
530
+
531
+ expect(result.error.value?.message).toBe("Failed to fetch TestModel");
532
+ });
533
+ });
534
+ });
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { ref, computed, onMounted, type Ref, type ComputedRef } from "vue";
9
- import { getSharedClient, resetSharedClient } from "../client";
9
+ import { query, hasErrors, getErrorMessage } from "../client/proxy";
10
10
 
11
11
  /**
12
12
  * Configuration options for creating a data hook
@@ -56,8 +56,10 @@ export interface DataHookReturn<T, TComputed extends Record<string, any> = Recor
56
56
  computed: { [K in keyof TComputed]: ComputedRef<TComputed[K]> };
57
57
  }
58
58
 
59
- // Re-export resetSharedClient for testing convenience
60
- export { resetSharedClient as resetClientInstance };
59
+ // Reset function for testing convenience (no-op with proxy-based approach)
60
+ export function resetClientInstance(): void {
61
+ // No-op: proxy-based approach doesn't need client reset
62
+ }
61
63
 
62
64
  /**
63
65
  * Creates a reusable data hook for a specific model
@@ -132,29 +134,34 @@ export function createDataHook<
132
134
  return baseFilter && Object.keys(baseFilter).length > 0 ? baseFilter : undefined;
133
135
  };
134
136
 
135
- // Fetch function
137
+ // Fetch function using server proxy
136
138
  async function fetch() {
137
139
  loading.value = true;
138
140
  error.value = null;
139
141
 
140
142
  try {
141
- const client = getSharedClient();
142
- const queryOptions: any = {
143
- filter: getFilter(),
144
- limit,
145
- };
143
+ const queryOptions: Record<string, any> = {};
144
+
145
+ const filter = getFilter();
146
+ if (filter) {
147
+ queryOptions.filter = filter;
148
+ }
149
+
150
+ if (limit) {
151
+ queryOptions.limit = limit;
152
+ }
146
153
 
147
154
  if (selectionSet) {
148
155
  queryOptions.selectionSet = selectionSet;
149
156
  }
150
157
 
151
- const { data: responseData, errors } = await (client as any).models[model].list(queryOptions);
158
+ const response = await query(model, "list", queryOptions);
152
159
 
153
- if (errors) {
154
- throw new Error(errors[0]?.message || `Failed to fetch ${model}`);
160
+ if (hasErrors(response)) {
161
+ throw new Error(getErrorMessage(response) || `Failed to fetch ${model}`);
155
162
  }
156
163
 
157
- const items = responseData || [];
164
+ const items = response.data || [];
158
165
  data.value = transform ? items.map(transform) : items;
159
166
  } catch (e) {
160
167
  error.value = e as Error;
@@ -20,3 +20,5 @@ export { useAccounts, type UseAccountsOptions, type UseAccountsReturn } from "./
20
20
  export { useUsers, type UseUsersOptions, type UseUsersReturn } from "./useUsers";
21
21
  export { useProducts, type UseProductsOptions, type UseProductsReturn } from "./useProducts";
22
22
  export { useProductInstances, type UseProductInstancesOptions, type UseProductInstancesReturn } from "./useProductInstances";
23
+ export { useReservations, type UseReservationsOptions, type UseReservationsReturn } from "./useReservations";
24
+ export { useContacts, type UseContactsOptions, type UseContactsReturn } from "./useContacts";