@wix/headless-bookings 1.0.8 → 1.0.10

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 (57) hide show
  1. package/cjs/dist/__mocks__/services/services-mocks.d.ts +17 -1
  2. package/cjs/dist/__mocks__/services/services-mocks.js +35 -1
  3. package/cjs/dist/api/index.d.ts +1 -1
  4. package/cjs/dist/api/index.js +2 -1
  5. package/cjs/dist/api/query-services/index.d.ts +60 -15
  6. package/cjs/dist/api/query-services/index.js +86 -10
  7. package/cjs/dist/react/core/service-list/ServiceList.d.ts +2 -3
  8. package/cjs/dist/react/service-list/ServiceList.d.ts +3 -3
  9. package/cjs/dist/react/service-list/ServiceList.js +1 -1
  10. package/cjs/dist/react/service-list/ServiceList.stories.d.ts +29 -0
  11. package/cjs/dist/react/service-list/ServiceList.stories.js +125 -0
  12. package/cjs/dist/services/booking/book-action/bookAction.js +9 -18
  13. package/cjs/dist/services/booking/book-action/buildCartRequest.d.ts +7 -18
  14. package/cjs/dist/services/booking/book-action/buildCartRequest.js +0 -7
  15. package/cjs/dist/services/booking/book-action/buildDayRangeBookingRequest.d.ts +7 -4
  16. package/cjs/dist/services/booking/book-action/buildDayRangeBookingRequest.js +4 -5
  17. package/cjs/dist/services/booking/book-action/createBookingsForCart.d.ts +2 -2
  18. package/cjs/dist/services/booking/book-action/createBookingsForCart.js +5 -21
  19. package/cjs/dist/services/booking/book-action/dayRange.d.ts +2 -1
  20. package/cjs/dist/services/booking/book-action/dayRange.js +2 -1
  21. package/cjs/dist/services/booking/book-action/types.d.ts +2 -3
  22. package/cjs/dist/services/booking/booking.d.ts +4 -4
  23. package/cjs/dist/services/constants.d.ts +2 -0
  24. package/cjs/dist/services/constants.js +3 -1
  25. package/cjs/dist/services/index.d.ts +3 -3
  26. package/cjs/dist/services/index.js +2 -2
  27. package/cjs/dist/services/service-list/service-list.d.ts +45 -36
  28. package/cjs/dist/services/service-list/service-list.js +90 -112
  29. package/dist/__mocks__/services/services-mocks.d.ts +17 -1
  30. package/dist/__mocks__/services/services-mocks.js +34 -0
  31. package/dist/api/index.d.ts +1 -1
  32. package/dist/api/index.js +1 -1
  33. package/dist/api/query-services/index.d.ts +60 -15
  34. package/dist/api/query-services/index.js +89 -10
  35. package/dist/react/core/service-list/ServiceList.d.ts +2 -3
  36. package/dist/react/service-list/ServiceList.d.ts +3 -3
  37. package/dist/react/service-list/ServiceList.js +1 -1
  38. package/dist/react/service-list/ServiceList.stories.d.ts +29 -0
  39. package/dist/react/service-list/ServiceList.stories.js +89 -0
  40. package/dist/services/booking/book-action/bookAction.js +10 -19
  41. package/dist/services/booking/book-action/buildCartRequest.d.ts +7 -18
  42. package/dist/services/booking/book-action/buildCartRequest.js +0 -6
  43. package/dist/services/booking/book-action/buildDayRangeBookingRequest.d.ts +7 -4
  44. package/dist/services/booking/book-action/buildDayRangeBookingRequest.js +4 -5
  45. package/dist/services/booking/book-action/createBookingsForCart.d.ts +2 -2
  46. package/dist/services/booking/book-action/createBookingsForCart.js +3 -19
  47. package/dist/services/booking/book-action/dayRange.d.ts +2 -1
  48. package/dist/services/booking/book-action/dayRange.js +2 -1
  49. package/dist/services/booking/book-action/types.d.ts +2 -3
  50. package/dist/services/booking/booking.d.ts +4 -4
  51. package/dist/services/constants.d.ts +2 -0
  52. package/dist/services/constants.js +2 -0
  53. package/dist/services/index.d.ts +3 -3
  54. package/dist/services/index.js +2 -2
  55. package/dist/services/service-list/service-list.d.ts +45 -36
  56. package/dist/services/service-list/service-list.js +90 -112
  57. package/package.json +17 -2
@@ -1,7 +1,13 @@
1
1
  "use strict";
2
2
  /**
3
3
  * ServiceList Service - Reactive service list data management
4
- * Handles querying and pagination of services list (NO selection management)
4
+ * Handles querying and pagination of services list (NO selection management).
5
+ *
6
+ * Availability is just part of the query: when the query options carry an
7
+ * `availabilityFilter`, the API layer queries services by real bookable
8
+ * availability (and each service gets an `available` flag); otherwise it runs the
9
+ * plain offset query. The service does not know or care which endpoint is used —
10
+ * that decision lives in {@link queryServices}.
5
11
  */
6
12
  Object.defineProperty(exports, "__esModule", { value: true });
7
13
  exports.ServiceListService = exports.ServiceListServiceDefinition = void 0;
@@ -12,10 +18,6 @@ const index_js_1 = require("../../api/query-services/index.js");
12
18
  const index_js_2 = require("../../api/query-categories/index.js");
13
19
  const booking_js_1 = require("../booking/booking.js");
14
20
  const constants_js_1 = require("../constants.js");
15
- /**
16
- * Default query limit
17
- */
18
- const DEFAULT_QUERY_LIMIT = 100;
19
21
  /**
20
22
  * Service definition for the ServiceList service
21
23
  */
@@ -46,7 +48,6 @@ exports.ServiceListServiceDefinition = (0, services_definitions_1.defineService)
46
48
  * const services = serviceListService.servicesSignal.get();
47
49
  * const isLoading = serviceListService.isLoadingSignal.get();
48
50
  * const error = serviceListService.errorSignal.get();
49
- * const pagingMetadata = serviceListService.pagingMetadataSignal.get();
50
51
  * const hasMore = serviceListService.hasMoreServicesSignal.get();
51
52
  *
52
53
  * if (isLoading) return <div>Loading services...</div>;
@@ -65,9 +66,6 @@ exports.ServiceListServiceDefinition = (0, services_definitions_1.defineService)
65
66
  * Load More
66
67
  * </button>
67
68
  * )}
68
- * {pagingMetadata.hasNext && (
69
- * <span>More services available</span>
70
- * )}
71
69
  * </div>
72
70
  * );
73
71
  * }
@@ -85,20 +83,22 @@ exports.ServiceListService = services_definitions_1.implementService.withConfig(
85
83
  // Initialize services signal - use empty array if not provided
86
84
  const initialServices = config.services || [];
87
85
  const servicesSignal = signalsService.signal(initialServices);
88
- // Initialize query options based on mode:
89
- // - If services provided: reset to defaults (appId only) - showcase mode
90
- // - If no services: use full queryOptions from config - query mode
91
- const initialQueryOptions = hasInitialServices
92
- ? { appId } // Reset to defaults when services are provided
93
- : {
94
- appId,
95
- filter: config.options?.filter,
96
- sort: config.options?.sort,
97
- };
98
- // Initialize paging metadata separately (response data - doesn't trigger effect)
86
+ // Build query options from config. The filter/availabilityFilter/sort must
87
+ // be preserved even when services are prefetched (e.g. SSR), so loadMore
88
+ // keeps querying the same way and stays in availability mode. A pure
89
+ // showcase config (services without options) naturally yields just
90
+ // { appId } since these fields are absent.
91
+ const initialQueryOptions = {
92
+ appId,
93
+ filter: config.options?.filter,
94
+ availabilityFilter: config.options?.availabilityFilter,
95
+ sort: config.options?.sort,
96
+ };
97
+ // Initialize paging metadata separately (response data - doesn't trigger effect).
98
+ // Carries either offset (plain mode) or cursor (availability mode).
99
99
  const initialPagingMetadata = config.options
100
100
  ?.pagingMetadata || {
101
- limit: DEFAULT_QUERY_LIMIT,
101
+ limit: constants_js_1.DEFAULT_QUERY_LIMIT,
102
102
  offset: 0,
103
103
  count: initialServices.length,
104
104
  hasNext: false,
@@ -159,6 +159,10 @@ exports.ServiceListService = services_definitions_1.implementService.withConfig(
159
159
  return true;
160
160
  });
161
161
  });
162
+ // Computed signal indicating whether an availability filter is currently
163
+ // applied. Reuses the same predicate as the API dispatch so the two can't
164
+ // drift. Exposed for the availability filter UI (follow-up PR).
165
+ const hasAvailabilityFilterSignal = signalsService.computed(() => (0, index_js_1.hasActiveAvailabilityFilter)(queryOptionsSignal.get().availabilityFilter));
162
166
  // Computed signal for effective filter - combines queryOptions.filter with location from BookingService
163
167
  const effectiveFilterSignal = signalsService.computed(() => {
164
168
  const filter = queryOptionsSignal.get().filter;
@@ -175,23 +179,21 @@ exports.ServiceListService = services_definitions_1.implementService.withConfig(
175
179
  : {}),
176
180
  };
177
181
  });
178
- // Initial fetch - runs once on client when no prefetched data
179
- // Fetches both services AND categories in parallel
182
+ // Initial fetch - runs once on client when no prefetched data.
183
+ // Fetches both services AND categories in parallel.
180
184
  if (typeof window !== 'undefined' && !hasInitialServices) {
181
- const initialQueryOptions = queryOptionsSignal.peek();
182
- const currentPaging = pagingMetadataSignal.peek();
183
- // Use effectiveFilterSignal.peek() to get the computed filter value
184
- const effectiveFilter = effectiveFilterSignal.peek();
185
+ const options = queryOptionsSignal.peek();
185
186
  isLoadingSignal.set(true);
186
187
  Promise.allSettled([
187
188
  (0, index_js_1.queryServices)({
188
- appId: initialQueryOptions.appId,
189
- filter: effectiveFilter,
189
+ appId: options.appId,
190
+ filter: effectiveFilterSignal.peek(),
191
+ availabilityFilter: options.availabilityFilter,
190
192
  pagingMetadata: {
191
- limit: currentPaging.limit,
193
+ limit: pagingMetadataSignal.peek().limit,
192
194
  offset: 0,
193
195
  },
194
- sort: initialQueryOptions.sort,
196
+ sort: options.sort,
195
197
  }),
196
198
  !hasInitialCategories ? (0, index_js_2.queryCategories)() : Promise.resolve(null),
197
199
  ])
@@ -215,37 +217,34 @@ exports.ServiceListService = services_definitions_1.implementService.withConfig(
215
217
  isLoadingSignal.set(false);
216
218
  });
217
219
  }
218
- // Reactive effect - only handles query/filter changes (not initial fetch)
219
- // IMPORTANT: Subscribes to effectiveFilterSignal which includes queryOptions.filter + location
220
+ // Re-fetch when query options or the effective filter (filter + location) change.
220
221
  if (typeof window !== 'undefined') {
221
222
  let isFirstEffectRun = true;
222
223
  signalsService.effect(async () => {
223
- // CRITICAL: Read queryOptionsSignal and effectiveFilterSignal to establish dependencies
224
- // effectiveFilterSignal already subscribes to bookingService.location internally
225
- const queryOptions = queryOptionsSignal.get();
224
+ // Read both signals to establish dependencies.
225
+ const options = queryOptionsSignal.get();
226
226
  const effectiveFilter = effectiveFilterSignal.get();
227
- // Skip first run - initial fetch is handled separately above
227
+ // Skip first run - initial fetch is handled separately above.
228
228
  if (isFirstEffectRun) {
229
229
  isFirstEffectRun = false;
230
230
  return;
231
231
  }
232
- // Read paging metadata outside of dependency tracking for the fetch
233
- const currentPaging = pagingMetadataSignal.peek();
234
232
  try {
235
233
  isLoadingSignal.set(true);
236
234
  servicesSignal.set([]);
237
235
  errorSignal.set(null);
236
+ // Reset to the first page when query options change.
238
237
  const result = await (0, index_js_1.queryServices)({
239
- appId: queryOptions.appId,
238
+ appId: options.appId,
240
239
  filter: effectiveFilter,
240
+ availabilityFilter: options.availabilityFilter,
241
241
  pagingMetadata: {
242
- limit: currentPaging.limit,
243
- offset: 0, // Reset to first page when query options change
242
+ limit: pagingMetadataSignal.peek().limit,
243
+ offset: 0,
244
244
  },
245
- sort: queryOptions.sort,
245
+ sort: options.sort,
246
246
  });
247
247
  servicesSignal.set(result.services);
248
- // Update ONLY pagingMetadataSignal (not queryOptionsSignal) to avoid re-triggering effect
249
248
  pagingMetadataSignal.set(result.pagingMetadata);
250
249
  }
251
250
  catch (error) {
@@ -256,29 +255,25 @@ exports.ServiceListService = services_definitions_1.implementService.withConfig(
256
255
  }
257
256
  });
258
257
  }
259
- const loadMoreCursor = async (count) => {
260
- const currentQueryOptions = queryOptionsSignal.get();
261
- // Calculate next page offset based on current services count
262
- const currentServicesCount = servicesSignal.get().length;
263
- const nextOffset = currentServicesCount;
264
- const nextPaging = {
265
- limit: count || DEFAULT_QUERY_LIMIT,
266
- offset: nextOffset,
267
- };
268
- // Get effective filter from computed signal (includes location from BookingService)
269
- const effectiveFilter = effectiveFilterSignal.get();
258
+ const loadMore = async (count) => {
259
+ const options = queryOptionsSignal.peek();
260
+ const currentPaging = pagingMetadataSignal.peek();
270
261
  try {
271
262
  isLoadingSignal.set(true);
272
263
  errorSignal.set(null);
273
264
  const result = await (0, index_js_1.queryServices)({
274
- appId: currentQueryOptions.appId,
275
- filter: effectiveFilter,
276
- pagingMetadata: nextPaging,
277
- sort: currentQueryOptions.sort,
265
+ appId: options.appId,
266
+ filter: effectiveFilterSignal.peek(),
267
+ availabilityFilter: options.availabilityFilter,
268
+ pagingMetadata: {
269
+ limit: count ?? currentPaging.limit ?? constants_js_1.DEFAULT_QUERY_LIMIT,
270
+ // Offset (plain mode) and cursor (availability mode) — queryServices uses what it needs.
271
+ offset: servicesSignal.peek().length,
272
+ cursor: currentPaging.cursor,
273
+ },
274
+ sort: options.sort,
278
275
  });
279
- // Append new services to existing ones
280
276
  servicesSignal.set([...servicesSignal.get(), ...result.services]);
281
- // Update ONLY pagingMetadataSignal (not queryOptionsSignal)
282
277
  pagingMetadataSignal.set(result.pagingMetadata);
283
278
  }
284
279
  catch (error) {
@@ -297,101 +292,81 @@ exports.ServiceListService = services_definitions_1.implementService.withConfig(
297
292
  categoriesSignal,
298
293
  filterOptionsSignal,
299
294
  isFilteredSignal,
295
+ hasAvailabilityFilterSignal,
300
296
  effectiveFilterSignal,
301
297
  actions: {
302
- loadMore: async (count) => {
303
- await loadMoreCursor(count);
304
- },
298
+ loadMore,
305
299
  setQueryOptions: (options) => {
306
- // Reset services and paging when query options change
300
+ // Reset services and paging when query options change.
307
301
  servicesSignal.set([]);
308
302
  pagingMetadataSignal.set({
309
- limit: pagingMetadataSignal.get().limit || DEFAULT_QUERY_LIMIT,
303
+ limit: pagingMetadataSignal.get().limit || constants_js_1.DEFAULT_QUERY_LIMIT,
310
304
  offset: 0,
311
305
  count: 0,
312
306
  hasNext: false,
307
+ cursor: undefined,
313
308
  });
314
309
  queryOptionsSignal.set(options);
315
310
  },
316
311
  setFilter: (filter) => {
317
- // Update query options with new filter (triggers re-fetch via effectiveFilterSignal)
318
- const currentOptions = queryOptionsSignal.get();
319
- queryOptionsSignal.set({
320
- ...currentOptions,
321
- filter,
322
- });
312
+ queryOptionsSignal.set({ ...queryOptionsSignal.get(), filter });
323
313
  },
324
314
  resetFilter: () => {
325
- // Clear filter from query options (triggers re-fetch)
326
- const currentOptions = queryOptionsSignal.get();
327
315
  queryOptionsSignal.set({
328
- ...currentOptions,
316
+ ...queryOptionsSignal.get(),
329
317
  filter: undefined,
330
318
  });
331
319
  },
332
320
  resetFilterField: (fieldName) => {
333
- // Reset a specific filter field
334
- const currentFilter = queryOptionsSignal.get().filter || {};
335
- const newFilter = { ...currentFilter };
336
- // Remove the specific field
321
+ const newFilter = { ...(queryOptionsSignal.get().filter || {}) };
337
322
  delete newFilter[fieldName];
338
- // Update query options with new filter (triggers re-fetch)
339
- const currentOptions = queryOptionsSignal.get();
340
323
  queryOptionsSignal.set({
341
- ...currentOptions,
324
+ ...queryOptionsSignal.get(),
342
325
  filter: Object.keys(newFilter).length > 0 ? newFilter : undefined,
343
326
  });
344
327
  },
345
328
  },
346
- hasMoreServicesSignal: signalsService.computed(() => {
347
- const paging = pagingMetadataSignal.get();
348
- // Use hasNext flag from pagingMetadata to determine if more services are available
349
- return paging.hasNext ?? false;
350
- }),
329
+ hasMoreServicesSignal: signalsService.computed(() => pagingMetadataSignal.get().hasNext ?? false),
351
330
  };
352
331
  });
353
332
  /**
354
- * Load service list configuration for Server-Side Rendering (SSR).
355
- * This function is designed to be called during the build or server-side rendering phase
356
- * to pre-fetch services data before the React components are hydrated on the client.
357
- * The returned config includes paging metadata with hasNext flag indicating if more services are available.
333
+ * Loads service list configuration for SSR, pre-fetching services and categories.
334
+ * Pass an `availabilityFilter` to query availability-aware services.
358
335
  *
359
- * @param options - Query options following QueryServicesRequest type, or a URL string (to be implemented)
360
- * @returns A promise that resolves to either a success result with the config or an error result
336
+ * @param options - Query options.
337
+ * @returns A success result with the config, or an error result.
361
338
  *
362
339
  * @example
363
340
  * ```ts
364
- * // In an Astro page or API route
365
- * import { loadServiceListServiceInitialData } from '@wix/bookings/services';
366
- *
367
- * // Load with query options
368
- * const serviceListResult = await loadServiceListServiceInitialData({
341
+ * // Plain offset query
342
+ * const result = await loadServiceListServiceInitialData({
369
343
  * appId: '13d21c63-b5ec-5912-8397-c3a5ddb27a97',
370
344
  * pagingMetadata: { limit: 20, offset: 0 },
371
345
  * });
372
346
  *
373
- * if (serviceListResult.type === 'error') {
374
- * console.error('Failed to load services:', serviceListResult.error);
375
- * return Astro.redirect('/500');
376
- * }
377
- *
378
- * const serviceListConfig = serviceListResult.config;
379
- *
380
- * // Check if more services are available
381
- * if (serviceListConfig.options?.pagingMetadata?.hasNext) {
382
- * // More services can be loaded
383
- * }
347
+ * // Availability-aware query (cursor paging + `available` flag)
348
+ * const availabilityResult = await loadServiceListServiceInitialData({
349
+ * availabilityFilter: {
350
+ * localStartDate: '2026-06-01T09:00:00',
351
+ * localEndDate: '2026-06-07T17:00:00',
352
+ * timeZone: 'America/New_York',
353
+ * },
354
+ * pagingMetadata: { limit: 20 },
355
+ * });
384
356
  * ```
385
357
  */
386
358
  async function loadServiceListServiceInitialData(options) {
387
359
  try {
388
- // Fetch services and categories in parallel; categories failure is non-fatal
360
+ const appId = options.appId ?? constants_js_1.BOOKING_APP_ID;
361
+ // Fetch services and categories in parallel; categories failure is non-fatal.
362
+ // queryServices picks the endpoint by the presence of availabilityFilter.
389
363
  const [servicesResult, categoriesResult] = await Promise.all([
390
364
  (0, index_js_1.queryServices)({
391
- appId: options.appId,
365
+ appId,
392
366
  filter: options.filter,
393
367
  pagingMetadata: options.pagingMetadata,
394
368
  sort: options.sort,
369
+ availabilityFilter: options.availabilityFilter,
395
370
  }),
396
371
  (0, index_js_2.queryCategories)().catch(() => null),
397
372
  ]);
@@ -401,7 +376,10 @@ async function loadServiceListServiceInitialData(options) {
401
376
  services: servicesResult.services,
402
377
  categories: categoriesResult?.categories ?? [],
403
378
  options: {
404
- ...options,
379
+ appId,
380
+ filter: options.filter,
381
+ sort: options.sort,
382
+ availabilityFilter: options.availabilityFilter,
405
383
  pagingMetadata: servicesResult.pagingMetadata,
406
384
  },
407
385
  },
@@ -5,7 +5,7 @@ import type { Service, StaffMember } from '@wix/auto_sdk_bookings_services';
5
5
  import type { ServiceServiceConfig } from '../../services/service/service.js';
6
6
  import type { ServiceListServiceConfig } from '../../services/service-list/service-list.js';
7
7
  import type { StaffMemberListServiceConfig } from '../../services/staff-member-list/staff-member-list.js';
8
- import type { PagingMetadata } from '../../api/query-services/index.js';
8
+ import type { PagingMetadata, AvailabilityFilter, ServiceWithAvailabilityItem } from '../../api/query-services/index.js';
9
9
  import type { Category } from '../../api/query-categories/index.js';
10
10
  /**
11
11
  * Base mock service with all fields populated (APPOINTMENT type, FIXED pricing)
@@ -102,6 +102,22 @@ export declare const mockCategories: Category[];
102
102
  * Service list config with categories for filter testing
103
103
  */
104
104
  export declare const mockServiceListConfigWithCategories: ServiceListServiceConfig;
105
+ /**
106
+ * Mock services enriched with an `available` flag (catalog-search results).
107
+ */
108
+ export declare const mockServicesWithAvailability: ServiceWithAvailabilityItem[];
109
+ /**
110
+ * Cursor-based paging metadata with a next cursor available.
111
+ */
112
+ export declare const mockAvailabilityPagingMetadata: PagingMetadata;
113
+ /**
114
+ * Cursor-based paging metadata for the last page (no next cursor).
115
+ */
116
+ export declare const mockAvailabilityPagingMetadataLast: PagingMetadata;
117
+ /**
118
+ * Mock availability filter (date window + timezone).
119
+ */
120
+ export declare const mockAvailabilityFilter: AvailabilityFilter;
105
121
  /**
106
122
  * Base mock staff member with all fields
107
123
  */
@@ -396,6 +396,40 @@ export const mockServiceListConfigWithCategories = {
396
396
  },
397
397
  };
398
398
  // ============================================================================
399
+ // Availability (catalog-search) Mocks
400
+ // ============================================================================
401
+ /**
402
+ * Mock services enriched with an `available` flag (catalog-search results).
403
+ */
404
+ export const mockServicesWithAvailability = mockServices.map((service, index) => ({
405
+ ...service,
406
+ available: index !== 2, // third service is unavailable
407
+ }));
408
+ /**
409
+ * Cursor-based paging metadata with a next cursor available.
410
+ */
411
+ export const mockAvailabilityPagingMetadata = {
412
+ count: 3,
413
+ hasNext: true,
414
+ cursor: 'next-cursor-token',
415
+ };
416
+ /**
417
+ * Cursor-based paging metadata for the last page (no next cursor).
418
+ */
419
+ export const mockAvailabilityPagingMetadataLast = {
420
+ count: 3,
421
+ hasNext: false,
422
+ cursor: undefined,
423
+ };
424
+ /**
425
+ * Mock availability filter (date window + timezone).
426
+ */
427
+ export const mockAvailabilityFilter = {
428
+ localStartDate: '2026-06-01T09:00:00',
429
+ localEndDate: '2026-06-01T17:00:00',
430
+ timeZone: 'America/New_York',
431
+ };
432
+ // ============================================================================
399
433
  // Staff Member Mocks
400
434
  // ============================================================================
401
435
  /**
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * @module API
6
6
  */
7
- export { getServiceById, getServiceBySlug, queryServices, convertPlatformFilterToApiFilter, type PagingMetadata, type QueryServicesRequest, SYNTHETIC_CUSTOM_ID, SYNTHETIC_CUSTOMER_ID, } from './query-services/index.js';
7
+ export { getServiceById, getServiceBySlug, queryServices, convertPlatformFilterToApiFilter, type PagingMetadata, type QueryServicesRequest, type QueryServicesResult, type ServiceWithAvailabilityItem, AvailabilityLocationType, type AvailabilityFilter, type Attribute, type AvailabilityLocation, SYNTHETIC_CUSTOM_ID, SYNTHETIC_CUSTOMER_ID, } from './query-services/index.js';
8
8
  export { fetchAvailability } from './fetch-availability/index.js';
9
9
  export * from '@wix/auto_sdk_bookings_bookings';
10
10
  export { createBooking, type CreateBookingOptions, } from './create-booking/index.js';
package/dist/api/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * @module API
6
6
  */
7
- export { getServiceById, getServiceBySlug, queryServices, convertPlatformFilterToApiFilter, SYNTHETIC_CUSTOM_ID, SYNTHETIC_CUSTOMER_ID, } from './query-services/index.js';
7
+ export { getServiceById, getServiceBySlug, queryServices, convertPlatformFilterToApiFilter, AvailabilityLocationType, SYNTHETIC_CUSTOM_ID, SYNTHETIC_CUSTOMER_ID, } from './query-services/index.js';
8
8
  export { fetchAvailability } from './fetch-availability/index.js';
9
9
  export * from '@wix/auto_sdk_bookings_bookings';
10
10
  export { createBooking, } from './create-booking/index.js';
@@ -1,8 +1,16 @@
1
1
  /**
2
2
  * Query Services API
3
- * Fetches single or multiple services with pagination and sorting
3
+ * Fetches single or multiple services with pagination and sorting.
4
+ *
5
+ * `queryServices` is the single entry point. It decides which underlying endpoint
6
+ * to call based on the request: when an `availabilityFilter` is present it queries
7
+ * the catalog-search `QueryServicesByAvailability` endpoint (cursor paging, each
8
+ * service enriched with an `available` flag); otherwise it uses the services
9
+ * `queryServices` endpoint (offset paging). Callers always get the same
10
+ * `{ services, pagingMetadata }` shape.
4
11
  */
5
12
  import { type Service, type Paging, type Sorting } from '@wix/auto_sdk_bookings_services';
13
+ import { LocationType as AvailabilityLocationType, type AvailabilityFilter, type Attribute, type Location as AvailabilityLocation } from '@wix/auto_sdk_bookings_catalog-search';
6
14
  import type { FilterValue as Filter } from '@wix/headless-components/react';
7
15
  /**
8
16
  * Synthetic location ID constants for non-business locations
@@ -10,24 +18,48 @@ import type { FilterValue as Filter } from '@wix/headless-components/react';
10
18
  export declare const SYNTHETIC_CUSTOM_ID = "custom";
11
19
  export declare const SYNTHETIC_CUSTOMER_ID = "customer";
12
20
  /**
13
- * Paging metadata from query services response.
14
- * Extends the SDK's Paging type with hasNext flag to indicate if more services are available.
21
+ * Re-exported availability filter types so consumers can build availability UIs
22
+ * without importing the ambassador package directly.
23
+ */
24
+ export { AvailabilityLocationType, type AvailabilityFilter, type Attribute, type AvailabilityLocation, };
25
+ /**
26
+ * A service, optionally enriched with a bookable-availability flag.
27
+ * `available` is only populated when the query runs in availability mode.
28
+ */
29
+ export type ServiceWithAvailabilityItem = Service & {
30
+ /** Whether the service has bookable slots in the requested window. */
31
+ available?: boolean;
32
+ };
33
+ /**
34
+ * Paging metadata from a query response. Extends the SDK's Paging type with a
35
+ * `hasNext` flag and an opaque `cursor` (used in availability/cursor mode).
15
36
  */
16
37
  export type PagingMetadata = Paging & {
17
38
  /** Number of items returned in the current page */
18
39
  count?: number;
19
40
  /** Whether there are more services available to load */
20
41
  hasNext?: boolean;
42
+ /** Opaque cursor for the next page (availability/cursor mode only). Undefined in offset mode. */
43
+ cursor?: string;
21
44
  };
22
45
  /**
23
- * Query services request options
24
- * Used internally by ServiceListService to track query state
46
+ * Query services request options.
47
+ * When `availabilityFilter` is present, services are queried by real bookable
48
+ * availability; `pagingMetadata.cursor` then drives pagination instead of `offset`.
25
49
  */
26
50
  export interface QueryServicesRequest {
27
51
  appId: string;
28
52
  filter?: Filter;
29
53
  pagingMetadata?: PagingMetadata;
30
54
  sort?: Sorting[];
55
+ availabilityFilter?: AvailabilityFilter;
56
+ }
57
+ /**
58
+ * Result shape shared by both query modes.
59
+ */
60
+ export interface QueryServicesResult {
61
+ services: ServiceWithAvailabilityItem[];
62
+ pagingMetadata: PagingMetadata;
31
63
  }
32
64
  /**
33
65
  * Fetches a service by its ID
@@ -72,28 +104,41 @@ export declare const getServiceBySlug: (slug: string, appId?: string) => Promise
72
104
  * ```
73
105
  */
74
106
  export declare function convertPlatformFilterToApiFilter(platformFilter: Filter): Record<string, any>;
107
+ /**
108
+ * Whether an availability filter carries a real bookable constraint that warrants
109
+ * routing to the availability endpoint. Modifiers that don't define a query on their
110
+ * own (`timeZone`, `exactMatch`, `includeUnavailable`) are ignored: only a complete
111
+ * date window (both `localStartDate` and `localEndDate`) or a resource constraint
112
+ * (`locations`/`attributes`/`resourceTypes`) counts.
113
+ *
114
+ * This is the single source of truth shared by the API dispatch and the service's
115
+ * `hasAvailabilityFilterSignal`.
116
+ */
117
+ export declare function hasActiveAvailabilityFilter(availabilityFilter?: AvailabilityFilter): boolean;
75
118
  /**
76
119
  * Query services with pagination and sorting.
77
- * Returns services array and paging metadata including hasNext flag.
120
+ * Picks the underlying endpoint by request: availability filter present → catalog-search
121
+ * (cursor paging, `available` flag); otherwise the offset services query.
78
122
  *
79
- * @param params - Query parameters including appId, filter (platform Filter type), paging metadata, and sort options
123
+ * @param request - Query parameters including appId, filter (platform Filter type), paging
124
+ * metadata, sort options, and an optional availability filter
80
125
  * @returns Promise that resolves to services array and paging metadata with hasNext flag
81
126
  *
82
127
  * @example
83
128
  * ```ts
129
+ * // Offset query
84
130
  * const { services, pagingMetadata } = await queryServices({
85
131
  * appId: '13d21c63-b5ec-5912-8397-c3a5ddb27a97',
86
132
  * filter: { '_id': { $in: ['service1', 'service2'] } },
87
133
  * pagingMetadata: { limit: 20, offset: 0 },
88
- * sort: [{ fieldName: 'name', order: 'ASC' }]
89
134
  * });
90
135
  *
91
- * if (pagingMetadata.hasNext) {
92
- * // Load more services
93
- * }
136
+ * // Availability-aware query (cursor paging + `available` flag)
137
+ * const availability = await queryServices({
138
+ * appId: '13d21c63-b5ec-5912-8397-c3a5ddb27a97',
139
+ * availabilityFilter: { localStartDate: '...', localEndDate: '...', timeZone: '...' },
140
+ * pagingMetadata: { limit: 20 },
141
+ * });
94
142
  * ```
95
143
  */
96
- export declare function queryServices({ appId, filter, pagingMetadata, sort, }: QueryServicesRequest): Promise<{
97
- services: Service[];
98
- pagingMetadata: PagingMetadata;
99
- }>;
144
+ export declare function queryServices(request: QueryServicesRequest): Promise<QueryServicesResult>;