@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,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
+ }
@@ -52,3 +52,24 @@ export {
52
52
  SYSTEM_SETTINGS_KEY,
53
53
  DEFAULT_SOFT_DELETE_RETENTION_DAYS,
54
54
  } from "./systemSettings";
55
+
56
+ // Reservation queries
57
+ export {
58
+ getReservation,
59
+ listReservations,
60
+ listReservationsByBrand,
61
+ listReservationsByContact,
62
+ listReservationsByDateRange,
63
+ getReservationByConfirmation,
64
+ } from "./reservations";
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,374 @@
1
+ /**
2
+ * Reservation Query Tests
3
+ *
4
+ * Tests for reservation query functions including filtering,
5
+ * pagination, and specialized queries.
6
+ */
7
+
8
+ import { describe, it, expect, vi, beforeEach } from "vitest";
9
+ import {
10
+ getReservation,
11
+ listReservations,
12
+ listReservationsByBrand,
13
+ listReservationsByContact,
14
+ listReservationsByDateRange,
15
+ getReservationByConfirmation,
16
+ type Reservation,
17
+ } from "./reservations";
18
+
19
+ describe("Reservation Queries", () => {
20
+ const mockReservation: Reservation = {
21
+ id: "reservation-001",
22
+ brandId: "brand-123",
23
+ visitId: "visit-456",
24
+ confirmationCode: "ABC123",
25
+ checkIn: "2024-06-01",
26
+ checkOut: "2024-06-05",
27
+ status: "confirmed",
28
+ source: "direct",
29
+ room: "101",
30
+ nights: 4,
31
+ };
32
+
33
+ describe("getReservation", () => {
34
+ it("should return a reservation by ID", async () => {
35
+ const mockGet = vi.fn().mockResolvedValue({
36
+ data: mockReservation,
37
+ errors: null,
38
+ });
39
+
40
+ const mockClient = {
41
+ models: {
42
+ Reservation: { get: mockGet },
43
+ },
44
+ };
45
+
46
+ const result = await getReservation(mockClient, "reservation-001");
47
+
48
+ expect(result).toEqual(mockReservation);
49
+ expect(mockGet).toHaveBeenCalledWith({ id: "reservation-001" });
50
+ });
51
+
52
+ it("should return null when reservation not found", async () => {
53
+ const mockGet = vi.fn().mockResolvedValue({
54
+ data: null,
55
+ errors: [{ message: "Not found" }],
56
+ });
57
+
58
+ const mockClient = {
59
+ models: {
60
+ Reservation: { get: mockGet },
61
+ },
62
+ };
63
+
64
+ const result = await getReservation(mockClient, "nonexistent");
65
+
66
+ expect(result).toBeNull();
67
+ });
68
+
69
+ it("should throw on unexpected error", async () => {
70
+ const mockGet = vi.fn().mockRejectedValue(new Error("Network error"));
71
+
72
+ const mockClient = {
73
+ models: {
74
+ Reservation: { get: mockGet },
75
+ },
76
+ };
77
+
78
+ await expect(getReservation(mockClient, "reservation-001")).rejects.toThrow(
79
+ "Network error"
80
+ );
81
+ });
82
+ });
83
+
84
+ describe("listReservations", () => {
85
+ const mockReservations = [
86
+ mockReservation,
87
+ { ...mockReservation, id: "reservation-002", confirmationCode: "DEF456" },
88
+ ];
89
+
90
+ it("should return a list of reservations", async () => {
91
+ const mockList = vi.fn().mockResolvedValue({
92
+ data: mockReservations,
93
+ errors: null,
94
+ nextToken: null,
95
+ });
96
+
97
+ const mockClient = {
98
+ models: {
99
+ Reservation: { list: mockList },
100
+ },
101
+ };
102
+
103
+ const result = await listReservations(mockClient);
104
+
105
+ expect(result.items).toHaveLength(2);
106
+ expect(result.items[0].id).toBe("reservation-001");
107
+ });
108
+
109
+ it("should pass filter options to the query", async () => {
110
+ const mockList = vi.fn().mockResolvedValue({
111
+ data: [mockReservation],
112
+ errors: null,
113
+ nextToken: null,
114
+ });
115
+
116
+ const mockClient = {
117
+ models: {
118
+ Reservation: { list: mockList },
119
+ },
120
+ };
121
+
122
+ const filter = { status: { eq: "confirmed" } };
123
+ await listReservations(mockClient, { filter, limit: 50 });
124
+
125
+ expect(mockList).toHaveBeenCalledWith({
126
+ filter,
127
+ limit: 50,
128
+ });
129
+ });
130
+
131
+ it("should return nextToken for pagination", async () => {
132
+ const mockList = vi.fn().mockResolvedValue({
133
+ data: [mockReservation],
134
+ errors: null,
135
+ nextToken: "token-123",
136
+ });
137
+
138
+ const mockClient = {
139
+ models: {
140
+ Reservation: { list: mockList },
141
+ },
142
+ };
143
+
144
+ const result = await listReservations(mockClient, { limit: 1 });
145
+
146
+ expect(result.nextToken).toBe("token-123");
147
+ });
148
+
149
+ it("should return empty array on GraphQL error", async () => {
150
+ const mockList = vi.fn().mockResolvedValue({
151
+ data: null,
152
+ errors: [{ message: "Query failed" }],
153
+ nextToken: undefined,
154
+ });
155
+
156
+ const mockClient = {
157
+ models: {
158
+ Reservation: { list: mockList },
159
+ },
160
+ };
161
+
162
+ const result = await listReservations(mockClient);
163
+
164
+ expect(result.items).toHaveLength(0);
165
+ expect(result.nextToken).toBeUndefined();
166
+ });
167
+ });
168
+
169
+ describe("listReservationsByBrand", () => {
170
+ it("should filter reservations by brand ID", async () => {
171
+ const mockList = vi.fn().mockResolvedValue({
172
+ data: [mockReservation],
173
+ errors: null,
174
+ nextToken: null,
175
+ });
176
+
177
+ const mockClient = {
178
+ models: {
179
+ Reservation: { list: mockList },
180
+ },
181
+ };
182
+
183
+ await listReservationsByBrand(mockClient, "brand-123");
184
+
185
+ expect(mockList).toHaveBeenCalledWith({
186
+ filter: { brandId: { eq: "brand-123" } },
187
+ limit: undefined,
188
+ nextToken: undefined,
189
+ });
190
+ });
191
+
192
+ it("should combine brand filter with additional filters", async () => {
193
+ const mockList = vi.fn().mockResolvedValue({
194
+ data: [mockReservation],
195
+ errors: null,
196
+ nextToken: null,
197
+ });
198
+
199
+ const mockClient = {
200
+ models: {
201
+ Reservation: { list: mockList },
202
+ },
203
+ };
204
+
205
+ await listReservationsByBrand(mockClient, "brand-123", {
206
+ filter: { status: { eq: "confirmed" } },
207
+ limit: 50,
208
+ });
209
+
210
+ expect(mockList).toHaveBeenCalledWith({
211
+ filter: {
212
+ and: [
213
+ { brandId: { eq: "brand-123" } },
214
+ { status: { eq: "confirmed" } },
215
+ ],
216
+ },
217
+ limit: 50,
218
+ nextToken: undefined,
219
+ });
220
+ });
221
+ });
222
+
223
+ describe("listReservationsByContact", () => {
224
+ it("should filter reservations by contact/visit ID", async () => {
225
+ const mockList = vi.fn().mockResolvedValue({
226
+ data: [mockReservation],
227
+ errors: null,
228
+ nextToken: null,
229
+ });
230
+
231
+ const mockClient = {
232
+ models: {
233
+ Reservation: { list: mockList },
234
+ },
235
+ };
236
+
237
+ await listReservationsByContact(mockClient, "visit-456");
238
+
239
+ expect(mockList).toHaveBeenCalledWith({
240
+ filter: { visitId: { eq: "visit-456" } },
241
+ limit: undefined,
242
+ nextToken: undefined,
243
+ });
244
+ });
245
+ });
246
+
247
+ describe("listReservationsByDateRange", () => {
248
+ it("should filter reservations by date range and brand", async () => {
249
+ const mockList = vi.fn().mockResolvedValue({
250
+ data: [mockReservation],
251
+ errors: null,
252
+ nextToken: null,
253
+ });
254
+
255
+ const mockClient = {
256
+ models: {
257
+ Reservation: { list: mockList },
258
+ },
259
+ };
260
+
261
+ await listReservationsByDateRange(
262
+ mockClient,
263
+ "brand-123",
264
+ "2024-06-01",
265
+ "2024-06-30"
266
+ );
267
+
268
+ expect(mockList).toHaveBeenCalledWith({
269
+ filter: {
270
+ and: [
271
+ { brandId: { eq: "brand-123" } },
272
+ { checkIn: { ge: "2024-06-01" } },
273
+ { checkIn: { le: "2024-06-30" } },
274
+ ],
275
+ },
276
+ limit: undefined,
277
+ nextToken: undefined,
278
+ });
279
+ });
280
+
281
+ it("should support pagination for date range queries", async () => {
282
+ const mockList = vi.fn().mockResolvedValue({
283
+ data: [mockReservation],
284
+ errors: null,
285
+ nextToken: "next-page",
286
+ });
287
+
288
+ const mockClient = {
289
+ models: {
290
+ Reservation: { list: mockList },
291
+ },
292
+ };
293
+
294
+ const result = await listReservationsByDateRange(
295
+ mockClient,
296
+ "brand-123",
297
+ "2024-06-01",
298
+ "2024-06-30",
299
+ { limit: 10 }
300
+ );
301
+
302
+ expect(result.nextToken).toBe("next-page");
303
+ });
304
+ });
305
+
306
+ describe("getReservationByConfirmation", () => {
307
+ it("should return reservation matching confirmation code and brand", async () => {
308
+ const mockList = vi.fn().mockResolvedValue({
309
+ data: [mockReservation],
310
+ errors: null,
311
+ nextToken: null,
312
+ });
313
+
314
+ const mockClient = {
315
+ models: {
316
+ Reservation: { list: mockList },
317
+ },
318
+ };
319
+
320
+ const result = await getReservationByConfirmation(
321
+ mockClient,
322
+ "ABC123",
323
+ "brand-123"
324
+ );
325
+
326
+ expect(result).toEqual(mockReservation);
327
+ expect(mockList).toHaveBeenCalledWith({
328
+ filter: {
329
+ and: [
330
+ { confirmationCode: { eq: "ABC123" } },
331
+ { brandId: { eq: "brand-123" } },
332
+ ],
333
+ },
334
+ limit: 1,
335
+ });
336
+ });
337
+
338
+ it("should return null when no matching reservation found", async () => {
339
+ const mockList = vi.fn().mockResolvedValue({
340
+ data: [],
341
+ errors: null,
342
+ nextToken: null,
343
+ });
344
+
345
+ const mockClient = {
346
+ models: {
347
+ Reservation: { list: mockList },
348
+ },
349
+ };
350
+
351
+ const result = await getReservationByConfirmation(
352
+ mockClient,
353
+ "NONEXISTENT",
354
+ "brand-123"
355
+ );
356
+
357
+ expect(result).toBeNull();
358
+ });
359
+
360
+ it("should throw on unexpected error", async () => {
361
+ const mockList = vi.fn().mockRejectedValue(new Error("Database error"));
362
+
363
+ const mockClient = {
364
+ models: {
365
+ Reservation: { list: mockList },
366
+ },
367
+ };
368
+
369
+ await expect(
370
+ getReservationByConfirmation(mockClient, "ABC123", "brand-123")
371
+ ).rejects.toThrow("Database error");
372
+ });
373
+ });
374
+ });