@htlkg/data 0.0.20 → 0.0.22

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 (69) hide show
  1. package/dist/hooks/index.d.ts +659 -66
  2. package/dist/hooks/index.js +711 -42
  3. package/dist/hooks/index.js.map +1 -1
  4. package/dist/index.d.ts +4 -3
  5. package/dist/index.js +1098 -51
  6. package/dist/index.js.map +1 -1
  7. package/dist/mutations/index.d.ts +338 -2
  8. package/dist/mutations/index.js +290 -4
  9. package/dist/mutations/index.js.map +1 -1
  10. package/dist/queries/index.d.ts +110 -2
  11. package/dist/queries/index.js +115 -6
  12. package/dist/queries/index.js.map +1 -1
  13. package/package.json +2 -2
  14. package/src/hooks/accounts/index.ts +2 -0
  15. package/src/hooks/{useAccounts.ts → accounts/useAccounts.ts} +48 -5
  16. package/src/hooks/accounts/usePaginatedAccounts.ts +166 -0
  17. package/src/hooks/brands/index.ts +2 -0
  18. package/src/hooks/{useBrands.ts → brands/useBrands.ts} +1 -1
  19. package/src/hooks/brands/usePaginatedBrands.ts +206 -0
  20. package/src/hooks/createPaginatedDataHook.ts +359 -0
  21. package/src/hooks/data-hook-errors.property.test.ts +4 -4
  22. package/src/hooks/data-hook-filters.property.test.ts +4 -4
  23. package/src/hooks/data-hooks.property.test.ts +4 -4
  24. package/src/hooks/index.ts +96 -7
  25. package/src/hooks/productInstances/index.ts +1 -0
  26. package/src/hooks/{useProductInstances.ts → productInstances/useProductInstances.ts} +9 -6
  27. package/src/hooks/products/index.ts +1 -0
  28. package/src/hooks/{useProducts.ts → products/useProducts.ts} +4 -5
  29. package/src/hooks/reservations/index.ts +2 -0
  30. package/src/hooks/reservations/usePaginatedReservations.ts +258 -0
  31. package/src/hooks/{useReservations.ts → reservations/useReservations.ts} +65 -10
  32. package/src/hooks/useContacts.test.ts +159 -0
  33. package/src/hooks/useContacts.ts +176 -0
  34. package/src/hooks/users/index.ts +2 -0
  35. package/src/hooks/users/usePaginatedUsers.ts +213 -0
  36. package/src/hooks/{useUsers.ts → users/useUsers.ts} +1 -1
  37. package/src/mutations/accounts/accounts.test.ts +287 -0
  38. package/src/mutations/{accounts.ts → accounts/accounts.ts} +2 -2
  39. package/src/mutations/accounts/index.ts +1 -0
  40. package/src/mutations/brands/brands.test.ts +292 -0
  41. package/src/mutations/{brands.ts → brands/brands.ts} +2 -2
  42. package/src/mutations/brands/index.ts +1 -0
  43. package/src/mutations/contacts.test.ts +604 -0
  44. package/src/mutations/contacts.ts +554 -0
  45. package/src/mutations/index.ts +18 -0
  46. package/src/mutations/reservations/index.ts +1 -0
  47. package/src/mutations/{reservations.test.ts → reservations/reservations.test.ts} +1 -1
  48. package/src/mutations/{reservations.ts → reservations/reservations.ts} +2 -2
  49. package/src/mutations/users/index.ts +1 -0
  50. package/src/mutations/users/users.test.ts +289 -0
  51. package/src/mutations/{users.ts → users/users.ts} +2 -2
  52. package/src/queries/accounts/accounts.test.ts +228 -0
  53. package/src/queries/accounts/index.ts +1 -0
  54. package/src/queries/brands/brands.test.ts +288 -0
  55. package/src/queries/brands/index.ts +1 -0
  56. package/src/queries/contacts.test.ts +505 -0
  57. package/src/queries/contacts.ts +237 -0
  58. package/src/queries/index.ts +10 -0
  59. package/src/queries/products/index.ts +1 -0
  60. package/src/queries/products/products.test.ts +347 -0
  61. package/src/queries/reservations/index.ts +1 -0
  62. package/src/queries/users/index.ts +1 -0
  63. package/src/queries/users/users.test.ts +301 -0
  64. /package/src/queries/{accounts.ts → accounts/accounts.ts} +0 -0
  65. /package/src/queries/{brands.ts → brands/brands.ts} +0 -0
  66. /package/src/queries/{products.ts → products/products.ts} +0 -0
  67. /package/src/queries/{reservations.test.ts → reservations/reservations.test.ts} +0 -0
  68. /package/src/queries/{reservations.ts → reservations/reservations.ts} +0 -0
  69. /package/src/queries/{users.ts → users/users.ts} +0 -0
@@ -0,0 +1,237 @@
1
+ /**
2
+ * Contact Query Functions
3
+ *
4
+ * Provides query functions for fetching contact data from the GraphQL API.
5
+ */
6
+
7
+ import type { Contact } from "@htlkg/core/types";
8
+
9
+ /**
10
+ * Get a single contact by ID
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * import { getContact } from '@htlkg/data/queries';
15
+ * import { generateClient } from '@htlkg/data/client';
16
+ *
17
+ * const client = generateClient<Schema>();
18
+ * const contact = await getContact(client, 'contact-123');
19
+ * ```
20
+ */
21
+ export async function getContact<TClient = any>(
22
+ client: TClient,
23
+ id: string,
24
+ ): Promise<Contact | null> {
25
+ try {
26
+ const { data, errors } = await (client as any).models.Contact.get({ id });
27
+
28
+ if (errors) {
29
+ console.error("[getContact] GraphQL errors:", errors);
30
+ return null;
31
+ }
32
+
33
+ return data as Contact;
34
+ } catch (error) {
35
+ console.error("[getContact] Error fetching contact:", error);
36
+ throw error;
37
+ }
38
+ }
39
+
40
+ /**
41
+ * List all contacts with optional filtering
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * import { listContacts } from '@htlkg/data/queries';
46
+ * import { generateClient } from '@htlkg/data/client';
47
+ *
48
+ * const client = generateClient<Schema>();
49
+ * const contacts = await listContacts(client, {
50
+ * filter: { gdprConsent: { eq: true } },
51
+ * limit: 50
52
+ * });
53
+ * ```
54
+ */
55
+ export async function listContacts<TClient = any>(
56
+ client: TClient,
57
+ options?: {
58
+ filter?: any;
59
+ limit?: number;
60
+ nextToken?: string;
61
+ },
62
+ ): Promise<{ items: Contact[]; nextToken?: string }> {
63
+ try {
64
+ const { data, errors, nextToken } = await (
65
+ client as any
66
+ ).models.Contact.list(options);
67
+
68
+ if (errors) {
69
+ console.error("[listContacts] GraphQL errors:", errors);
70
+ return { items: [], nextToken: undefined };
71
+ }
72
+
73
+ return {
74
+ items: (data || []) as Contact[],
75
+ nextToken,
76
+ };
77
+ } catch (error) {
78
+ console.error("[listContacts] Error fetching contacts:", error);
79
+ throw error;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * List contacts by brand ID
85
+ *
86
+ * @example
87
+ * ```typescript
88
+ * import { listContactsByBrand } from '@htlkg/data/queries';
89
+ * import { generateClient } from '@htlkg/data/client';
90
+ *
91
+ * const client = generateClient<Schema>();
92
+ * const contacts = await listContactsByBrand(client, 'brand-123');
93
+ * ```
94
+ */
95
+ export async function listContactsByBrand<TClient = any>(
96
+ client: TClient,
97
+ brandId: string,
98
+ options?: {
99
+ limit?: number;
100
+ nextToken?: string;
101
+ },
102
+ ): Promise<{ items: Contact[]; nextToken?: string }> {
103
+ return listContacts(client, {
104
+ filter: { brandId: { eq: brandId } },
105
+ ...options,
106
+ });
107
+ }
108
+
109
+ /**
110
+ * Get a contact by email within a brand
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * import { getContactByEmail } from '@htlkg/data/queries';
115
+ * import { generateClient } from '@htlkg/data/client';
116
+ *
117
+ * const client = generateClient<Schema>();
118
+ * const contact = await getContactByEmail(client, 'guest@example.com', 'brand-123');
119
+ * ```
120
+ */
121
+ export async function getContactByEmail<TClient = any>(
122
+ client: TClient,
123
+ email: string,
124
+ brandId: string,
125
+ ): Promise<Contact | null> {
126
+ try {
127
+ const { data, errors } = await (client as any).models.Contact.list({
128
+ filter: {
129
+ and: [{ email: { eq: email } }, { brandId: { eq: brandId } }],
130
+ },
131
+ limit: 1,
132
+ });
133
+
134
+ if (errors) {
135
+ console.error("[getContactByEmail] GraphQL errors:", errors);
136
+ return null;
137
+ }
138
+
139
+ return data?.[0] as Contact | null;
140
+ } catch (error) {
141
+ console.error("[getContactByEmail] Error fetching contact:", error);
142
+ throw error;
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Get a contact by phone within a brand
148
+ *
149
+ * @example
150
+ * ```typescript
151
+ * import { getContactByPhone } from '@htlkg/data/queries';
152
+ * import { generateClient } from '@htlkg/data/client';
153
+ *
154
+ * const client = generateClient<Schema>();
155
+ * const contact = await getContactByPhone(client, '+1234567890', 'brand-123');
156
+ * ```
157
+ */
158
+ export async function getContactByPhone<TClient = any>(
159
+ client: TClient,
160
+ phone: string,
161
+ brandId: string,
162
+ ): Promise<Contact | null> {
163
+ try {
164
+ const { data, errors } = await (client as any).models.Contact.list({
165
+ filter: {
166
+ and: [{ phone: { eq: phone } }, { brandId: { eq: brandId } }],
167
+ },
168
+ limit: 1,
169
+ });
170
+
171
+ if (errors) {
172
+ console.error("[getContactByPhone] GraphQL errors:", errors);
173
+ return null;
174
+ }
175
+
176
+ return data?.[0] as Contact | null;
177
+ } catch (error) {
178
+ console.error("[getContactByPhone] Error fetching contact:", error);
179
+ throw error;
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Search contacts by query string within a brand
185
+ * Searches across email, firstName, and lastName fields
186
+ *
187
+ * @example
188
+ * ```typescript
189
+ * import { searchContacts } from '@htlkg/data/queries';
190
+ * import { generateClient } from '@htlkg/data/client';
191
+ *
192
+ * const client = generateClient<Schema>();
193
+ * const contacts = await searchContacts(client, 'john', 'brand-123');
194
+ * ```
195
+ */
196
+ export async function searchContacts<TClient = any>(
197
+ client: TClient,
198
+ query: string,
199
+ brandId: string,
200
+ options?: {
201
+ limit?: number;
202
+ nextToken?: string;
203
+ },
204
+ ): Promise<{ items: Contact[]; nextToken?: string }> {
205
+ try {
206
+ const { data, errors, nextToken } = await (
207
+ client as any
208
+ ).models.Contact.list({
209
+ filter: {
210
+ and: [
211
+ { brandId: { eq: brandId } },
212
+ {
213
+ or: [
214
+ { email: { contains: query } },
215
+ { firstName: { contains: query } },
216
+ { lastName: { contains: query } },
217
+ ],
218
+ },
219
+ ],
220
+ },
221
+ ...options,
222
+ });
223
+
224
+ if (errors) {
225
+ console.error("[searchContacts] GraphQL errors:", errors);
226
+ return { items: [], nextToken: undefined };
227
+ }
228
+
229
+ return {
230
+ items: (data || []) as Contact[],
231
+ nextToken,
232
+ };
233
+ } catch (error) {
234
+ console.error("[searchContacts] Error searching contacts:", error);
235
+ throw error;
236
+ }
237
+ }
@@ -63,3 +63,13 @@ export {
63
63
  getReservationByConfirmation,
64
64
  } from "./reservations";
65
65
  export type { Reservation } from "./reservations";
66
+
67
+ // Contact queries
68
+ export {
69
+ getContact,
70
+ listContacts,
71
+ listContactsByBrand,
72
+ getContactByEmail,
73
+ getContactByPhone,
74
+ searchContacts,
75
+ } from "./contacts";
@@ -0,0 +1 @@
1
+ export * from "./products";
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Product Query Tests
3
+ *
4
+ * Tests for product and product instance query functions.
5
+ */
6
+
7
+ import { describe, it, expect, vi } from "vitest";
8
+ import {
9
+ getProduct,
10
+ listProducts,
11
+ listActiveProducts,
12
+ getProductInstance,
13
+ listProductInstancesByBrand,
14
+ listProductInstancesByAccount,
15
+ listEnabledProductInstancesByBrand,
16
+ } from "./products";
17
+
18
+ describe("Product Queries", () => {
19
+ const mockProduct = {
20
+ id: "product-001",
21
+ name: "WiFi Portal",
22
+ version: "1.0.0",
23
+ isActive: true,
24
+ schema: {},
25
+ uiSchema: {},
26
+ defaultConfig: {},
27
+ };
28
+
29
+ const mockProductInstance = {
30
+ id: "instance-001",
31
+ productId: "product-001",
32
+ brandId: "brand-001",
33
+ accountId: "account-001",
34
+ enabled: true,
35
+ config: {},
36
+ };
37
+
38
+ describe("getProduct", () => {
39
+ it("should return a product by ID", async () => {
40
+ const mockGet = vi.fn().mockResolvedValue({
41
+ data: mockProduct,
42
+ errors: null,
43
+ });
44
+
45
+ const mockClient = {
46
+ models: {
47
+ Product: { get: mockGet },
48
+ },
49
+ };
50
+
51
+ const result = await getProduct(mockClient, "product-001");
52
+
53
+ expect(result).toEqual(mockProduct);
54
+ expect(mockGet).toHaveBeenCalledWith({ id: "product-001" });
55
+ });
56
+
57
+ it("should return null when product not found", async () => {
58
+ const mockGet = vi.fn().mockResolvedValue({
59
+ data: null,
60
+ errors: [{ message: "Not found" }],
61
+ });
62
+
63
+ const mockClient = {
64
+ models: {
65
+ Product: { get: mockGet },
66
+ },
67
+ };
68
+
69
+ const result = await getProduct(mockClient, "nonexistent");
70
+
71
+ expect(result).toBeNull();
72
+ });
73
+
74
+ it("should throw on unexpected error", async () => {
75
+ const mockGet = vi.fn().mockRejectedValue(new Error("Network error"));
76
+
77
+ const mockClient = {
78
+ models: {
79
+ Product: { get: mockGet },
80
+ },
81
+ };
82
+
83
+ await expect(getProduct(mockClient, "product-001")).rejects.toThrow("Network error");
84
+ });
85
+ });
86
+
87
+ describe("listProducts", () => {
88
+ const mockProducts = [
89
+ mockProduct,
90
+ { ...mockProduct, id: "product-002", name: "WhatsApp CRM" },
91
+ ];
92
+
93
+ it("should return a list of products", async () => {
94
+ const mockList = vi.fn().mockResolvedValue({
95
+ data: mockProducts,
96
+ errors: null,
97
+ nextToken: null,
98
+ });
99
+
100
+ const mockClient = {
101
+ models: {
102
+ Product: { list: mockList },
103
+ },
104
+ };
105
+
106
+ const result = await listProducts(mockClient);
107
+
108
+ expect(result.items).toHaveLength(2);
109
+ expect(result.items[0].id).toBe("product-001");
110
+ });
111
+
112
+ it("should pass filter options to the query", async () => {
113
+ const mockList = vi.fn().mockResolvedValue({
114
+ data: [mockProduct],
115
+ errors: null,
116
+ nextToken: null,
117
+ });
118
+
119
+ const mockClient = {
120
+ models: {
121
+ Product: { list: mockList },
122
+ },
123
+ };
124
+
125
+ const filter = { isActive: { eq: true } };
126
+ await listProducts(mockClient, { filter, limit: 50 });
127
+
128
+ expect(mockList).toHaveBeenCalledWith({
129
+ filter,
130
+ limit: 50,
131
+ });
132
+ });
133
+
134
+ it("should return empty array on GraphQL error", async () => {
135
+ const mockList = vi.fn().mockResolvedValue({
136
+ data: null,
137
+ errors: [{ message: "Query failed" }],
138
+ nextToken: undefined,
139
+ });
140
+
141
+ const mockClient = {
142
+ models: {
143
+ Product: { list: mockList },
144
+ },
145
+ };
146
+
147
+ const result = await listProducts(mockClient);
148
+
149
+ expect(result.items).toHaveLength(0);
150
+ });
151
+ });
152
+
153
+ describe("listActiveProducts", () => {
154
+ it("should filter products by active status", async () => {
155
+ const mockList = vi.fn().mockResolvedValue({
156
+ data: [mockProduct],
157
+ errors: null,
158
+ nextToken: null,
159
+ });
160
+
161
+ const mockClient = {
162
+ models: {
163
+ Product: { list: mockList },
164
+ },
165
+ };
166
+
167
+ await listActiveProducts(mockClient);
168
+
169
+ expect(mockList).toHaveBeenCalledWith({
170
+ filter: { isActive: { eq: true } },
171
+ });
172
+ });
173
+ });
174
+
175
+ describe("getProductInstance", () => {
176
+ it("should return a product instance by ID", async () => {
177
+ const mockGet = vi.fn().mockResolvedValue({
178
+ data: mockProductInstance,
179
+ errors: null,
180
+ });
181
+
182
+ const mockClient = {
183
+ models: {
184
+ ProductInstance: { get: mockGet },
185
+ },
186
+ };
187
+
188
+ const result = await getProductInstance(mockClient, "instance-001");
189
+
190
+ expect(result).toEqual(mockProductInstance);
191
+ expect(mockGet).toHaveBeenCalledWith({ id: "instance-001" });
192
+ });
193
+
194
+ it("should return null when not found", async () => {
195
+ const mockGet = vi.fn().mockResolvedValue({
196
+ data: null,
197
+ errors: [{ message: "Not found" }],
198
+ });
199
+
200
+ const mockClient = {
201
+ models: {
202
+ ProductInstance: { get: mockGet },
203
+ },
204
+ };
205
+
206
+ const result = await getProductInstance(mockClient, "nonexistent");
207
+
208
+ expect(result).toBeNull();
209
+ });
210
+ });
211
+
212
+ describe("listProductInstancesByBrand", () => {
213
+ it("should filter instances by brand ID", async () => {
214
+ const mockList = vi.fn().mockResolvedValue({
215
+ data: [mockProductInstance],
216
+ errors: null,
217
+ nextToken: null,
218
+ });
219
+
220
+ const mockClient = {
221
+ models: {
222
+ ProductInstance: { list: mockList },
223
+ },
224
+ };
225
+
226
+ await listProductInstancesByBrand(mockClient, "brand-001");
227
+
228
+ expect(mockList).toHaveBeenCalledWith({
229
+ filter: { brandId: { eq: "brand-001" } },
230
+ });
231
+ });
232
+
233
+ it("should support pagination", async () => {
234
+ const mockList = vi.fn().mockResolvedValue({
235
+ data: [mockProductInstance],
236
+ errors: null,
237
+ nextToken: "next-page",
238
+ });
239
+
240
+ const mockClient = {
241
+ models: {
242
+ ProductInstance: { list: mockList },
243
+ },
244
+ };
245
+
246
+ const result = await listProductInstancesByBrand(mockClient, "brand-001", {
247
+ limit: 10,
248
+ });
249
+
250
+ expect(result.nextToken).toBe("next-page");
251
+ });
252
+
253
+ it("should return empty array on error", async () => {
254
+ const mockList = vi.fn().mockResolvedValue({
255
+ data: null,
256
+ errors: [{ message: "Error" }],
257
+ nextToken: undefined,
258
+ });
259
+
260
+ const mockClient = {
261
+ models: {
262
+ ProductInstance: { list: mockList },
263
+ },
264
+ };
265
+
266
+ const result = await listProductInstancesByBrand(mockClient, "brand-001");
267
+
268
+ expect(result.items).toHaveLength(0);
269
+ });
270
+ });
271
+
272
+ describe("listProductInstancesByAccount", () => {
273
+ it("should filter instances by account ID", async () => {
274
+ const mockList = vi.fn().mockResolvedValue({
275
+ data: [mockProductInstance],
276
+ errors: null,
277
+ nextToken: null,
278
+ });
279
+
280
+ const mockClient = {
281
+ models: {
282
+ ProductInstance: { list: mockList },
283
+ },
284
+ };
285
+
286
+ await listProductInstancesByAccount(mockClient, "account-001");
287
+
288
+ expect(mockList).toHaveBeenCalledWith({
289
+ filter: { accountId: { eq: "account-001" } },
290
+ });
291
+ });
292
+ });
293
+
294
+ describe("listEnabledProductInstancesByBrand", () => {
295
+ it("should filter by brand ID and enabled status", async () => {
296
+ const mockList = vi.fn().mockResolvedValue({
297
+ data: [mockProductInstance],
298
+ errors: null,
299
+ nextToken: null,
300
+ });
301
+
302
+ const mockClient = {
303
+ models: {
304
+ ProductInstance: { list: mockList },
305
+ },
306
+ };
307
+
308
+ await listEnabledProductInstancesByBrand(mockClient, "brand-001");
309
+
310
+ expect(mockList).toHaveBeenCalledWith({
311
+ filter: {
312
+ brandId: { eq: "brand-001" },
313
+ enabled: { eq: true },
314
+ },
315
+ });
316
+ });
317
+
318
+ it("should support pagination options", async () => {
319
+ const mockList = vi.fn().mockResolvedValue({
320
+ data: [mockProductInstance],
321
+ errors: null,
322
+ nextToken: "token-123",
323
+ });
324
+
325
+ const mockClient = {
326
+ models: {
327
+ ProductInstance: { list: mockList },
328
+ },
329
+ };
330
+
331
+ const result = await listEnabledProductInstancesByBrand(mockClient, "brand-001", {
332
+ limit: 5,
333
+ nextToken: "prev-token",
334
+ });
335
+
336
+ expect(mockList).toHaveBeenCalledWith({
337
+ filter: {
338
+ brandId: { eq: "brand-001" },
339
+ enabled: { eq: true },
340
+ },
341
+ limit: 5,
342
+ nextToken: "prev-token",
343
+ });
344
+ expect(result.nextToken).toBe("token-123");
345
+ });
346
+ });
347
+ });
@@ -0,0 +1 @@
1
+ export * from "./reservations";
@@ -0,0 +1 @@
1
+ export * from "./users";