@explorins/pers-sdk-react-native 2.3.0-alpha.1 → 2.3.0-alpha.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.
package/dist/index.js CHANGED
@@ -3517,15 +3517,38 @@ const validateFieldValue = (value, rules, fieldType) => {
3517
3517
  }
3518
3518
  }
3519
3519
  break;
3520
- case 'objectArray':
3520
+ case 'phone':
3521
+ // Basic phone validation - allows digits, spaces, dashes, parentheses, plus
3522
+ const phoneRegex = /^[\d\s\-\(\)\+]+$/;
3523
+ if (!phoneRegex.test(stringValue)) {
3524
+ return { field: '', message: 'Invalid phone number format', code: 'phone' };
3525
+ }
3526
+ if (rules.minLength && stringValue.replace(/\D/g, '').length < rules.minLength) {
3527
+ return { field: '', message: `Phone number too short`, code: 'minLength' };
3528
+ }
3529
+ if (rules.maxLength && stringValue.length > rules.maxLength) {
3530
+ return { field: '', message: `Maximum ${rules.maxLength} characters allowed`, code: 'maxLength' };
3531
+ }
3532
+ break;
3533
+ case 'select':
3534
+ // Single selection - just validate it's a string (option validation done separately)
3535
+ if (typeof value !== 'string') {
3536
+ return { field: '', message: 'Must be a string', code: 'type' };
3537
+ }
3538
+ break;
3539
+ case 'multiselect':
3521
3540
  if (!Array.isArray(value)) {
3522
3541
  return { field: '', message: 'Must be an array', code: 'type' };
3523
3542
  }
3524
3543
  if (rules.minItems !== undefined && value.length < rules.minItems) {
3525
- return { field: '', message: `Minimum ${rules.minItems} items required`, code: 'minItems' };
3544
+ return { field: '', message: `Select at least ${rules.minItems} options`, code: 'minItems' };
3526
3545
  }
3527
3546
  if (rules.maxItems !== undefined && value.length > rules.maxItems) {
3528
- return { field: '', message: `Maximum ${rules.maxItems} items allowed`, code: 'maxItems' };
3547
+ return { field: '', message: `Select at most ${rules.maxItems} options`, code: 'maxItems' };
3548
+ }
3549
+ // Validate each item is a string
3550
+ if (!value.every(item => typeof item === 'string')) {
3551
+ return { field: '', message: 'All selections must be strings', code: 'type' };
3529
3552
  }
3530
3553
  break;
3531
3554
  }
@@ -3597,75 +3620,6 @@ const validateFieldComparisons = (fieldKey, value, comparisons, allData, fieldTy
3597
3620
  return errors;
3598
3621
  };
3599
3622
  // ============================================================================
3600
- // Object Array Item Validation
3601
- // ============================================================================
3602
- /**
3603
- * Validate each item in an object array against the object schema.
3604
- * Checks required properties, type validation, and validates each property.
3605
- *
3606
- * @param fieldKey - Parent field key (for error reporting)
3607
- * @param items - Array of objects to validate
3608
- * @param schema - Schema defining expected properties
3609
- * @returns Array of validation errors with item index in field path (e.g., "contacts[0].phone")
3610
- */
3611
- const validateObjectArrayItems = (fieldKey, items, schema) => {
3612
- const errors = [];
3613
- if (!Array.isArray(items))
3614
- return errors;
3615
- items.forEach((item, index) => {
3616
- if (typeof item !== 'object' || item === null) {
3617
- errors.push({
3618
- field: `${fieldKey}[${index}]`,
3619
- code: 'type',
3620
- message: 'Item must be an object',
3621
- });
3622
- return;
3623
- }
3624
- const obj = item;
3625
- // Check each schema property
3626
- for (const prop of schema) {
3627
- const value = obj[prop.key];
3628
- const propPath = `${fieldKey}[${index}].${prop.key}`;
3629
- const isEmpty = value === null || value === undefined || value === '';
3630
- // Required check for nested properties (ObjectFieldSchema.required)
3631
- if (prop.required && isEmpty) {
3632
- errors.push({
3633
- field: propPath,
3634
- code: 'required',
3635
- message: `${prop.label} is required`,
3636
- });
3637
- continue;
3638
- }
3639
- if (isEmpty)
3640
- continue;
3641
- // Convert ObjectFieldSchema inline rules to FieldValidationRules
3642
- const rules = {
3643
- min: prop.min,
3644
- max: prop.max,
3645
- minLength: prop.minLength,
3646
- maxLength: prop.maxLength,
3647
- pattern: prop.pattern,
3648
- patternMessage: prop.patternMessage,
3649
- };
3650
- // Validate the property value
3651
- const error = validateFieldValue(value, rules, prop.fieldType);
3652
- if (error) {
3653
- errors.push({ ...error, field: propPath });
3654
- }
3655
- }
3656
- // Cross-property comparisons within the same object item
3657
- // e.g., checkOutDate > checkInDate within each reservation
3658
- for (const prop of schema) {
3659
- const value = obj[prop.key];
3660
- if (value === null || value === undefined || value === '')
3661
- continue;
3662
- // ObjectFieldSchema doesn't currently have comparisons, but we could
3663
- // validate date fields against 'now' using minDate/maxDate if we extend the schema
3664
- }
3665
- });
3666
- return errors;
3667
- };
3668
- // ============================================================================
3669
3623
  // Full Data Validation
3670
3624
  // ============================================================================
3671
3625
  /**
@@ -3693,11 +3647,6 @@ const validateAllFields = (data, definitions) => {
3693
3647
  const comparisonErrors = validateFieldComparisons(key, value, def.validation.comparisons, data, def.fieldType);
3694
3648
  errors.push(...comparisonErrors);
3695
3649
  }
3696
- // Object array item validation (validate each item's properties against objectSchema)
3697
- if (def.fieldType === 'objectArray' && def.objectSchema && Array.isArray(value)) {
3698
- const itemErrors = validateObjectArrayItems(key, value, def.objectSchema);
3699
- errors.push(...itemErrors);
3700
- }
3701
3650
  }
3702
3651
  return errors;
3703
3652
  };
@@ -3780,7 +3729,6 @@ const getBuiltInFieldDefinition = (field) => {
3780
3729
  label: metadata.label,
3781
3730
  description: metadata.description,
3782
3731
  fieldType: metadata.fieldType,
3783
- objectSchema: null,
3784
3732
  selectOptions: null,
3785
3733
  selectOptionsSource: null,
3786
3734
  validation: null,
@@ -3808,6 +3756,14 @@ const getAllBuiltInFieldDefinitions = () => {
3808
3756
  const customFieldEntityTypesArray = ['user', 'business', 'campaign', 'redemption'];
3809
3757
  /** All supported custom field entity types for iteration/validation */
3810
3758
  const customFieldEntityTypes = [...customFieldEntityTypesArray];
3759
+ /**
3760
+ * Entity types available for select options source (includes static sources)
3761
+ * - Database entities: user, business, campaign, redemption
3762
+ * - Static sources: country (ISO 3166-1 country list)
3763
+ */
3764
+ const selectOptionsSourceEntityArray = [...customFieldEntityTypesArray, 'country'];
3765
+ /** All supported select options source entities for iteration/validation */
3766
+ const selectOptionsSourceEntities = [...selectOptionsSourceEntityArray];
3811
3767
 
3812
3768
  /**
3813
3769
  * Core Entity Types
@@ -4123,7 +4079,8 @@ function isValidCampaignClaimRelation(value) {
4123
4079
  const VALID_REDEMPTION_RELATIONS = [
4124
4080
  'redeemCount',
4125
4081
  'ownerBusiness',
4126
- 'requiredUserFieldDefinitions'
4082
+ 'requiredUserInfo',
4083
+ 'booking'
4127
4084
  ];
4128
4085
  /**
4129
4086
  * Type guard to validate redemption include relation at runtime
@@ -4221,6 +4178,88 @@ function normalizeFilters(filters) {
4221
4178
  };
4222
4179
  }
4223
4180
 
4181
+ /**
4182
+ * Location types for bookings.
4183
+ * Broad categories for different booking contexts.
4184
+ */
4185
+ const LOCATION_TYPES = [
4186
+ 'property', // Physical property (hotel, resort, vacation rental)
4187
+ 'service', // Service-based booking (spa, salon, restaurant)
4188
+ 'event', // Event/activity booking (concert, tour, class)
4189
+ 'experience', // Experience package (multi-activity, bundle)
4190
+ 'destination', // Destination/location-based (city pass, area access)
4191
+ ];
4192
+
4193
+ /**
4194
+ * Unified data source tracking for analytics across all entities.
4195
+ *
4196
+ * Use this type to track the origin/channel of data for:
4197
+ * - Users (registration source)
4198
+ * - Claims (how the claim was initiated)
4199
+ * - Redeems (redemption channel)
4200
+ * - Bookings (booking source)
4201
+ * - Purchases (transaction origin)
4202
+ *
4203
+ * ## HTTP Headers for SDK/API
4204
+ *
4205
+ * | Header | Purpose | Examples |
4206
+ * |---------------------|----------------------------|-------------------------------------------------------|
4207
+ * | `x-source-channel` | How the request arrived | `web`, `mobile`, `api`, `admin`, `webhook`, `import` |
4208
+ * | `x-source-medium` | Marketing channel type | `email`, `referral`, `direct`, `organic`, `paid` |
4209
+ * | `x-source-campaign` | Campaign ID for attribution| `summer_promo_2026`, `welcome_series`, `partner_launch`|
4210
+ * | `x-source` | Traffic origin reference | `google`, `facebook`, `partner_xyz`, `qr_code_lobby` |
4211
+ *
4212
+ * ## Example Scenarios
4213
+ *
4214
+ * | Scenario | channel | medium | campaign | source |
4215
+ * |-----------------------------|-----------|------------|--------------|--------------|
4216
+ * | User clicks email link | `web` | `email` | `summer_promo`| `mailchimp` |
4217
+ * | Mobile app registration | `mobile` | `app` | - | - |
4218
+ * | Partner API integration | `api` | `affiliate`| - | `partner_xyz`|
4219
+ * | QR code scan in hotel | `mobile` | `referral` | `lobby_qr` | `qr_code` |
4220
+ * | Admin creates user | `admin` | - | - | - |
4221
+ * | Webhook from Stripe | `webhook` | - | - | `stripe` |
4222
+ *
4223
+ * @example
4224
+ * // Simple usage - just channel
4225
+ * const source: DataSource = { channel: 'web' };
4226
+ *
4227
+ * // Full attribution tracking
4228
+ * const source: DataSource = {
4229
+ * channel: 'web',
4230
+ * medium: 'email',
4231
+ * campaign: 'summer_promo_2026',
4232
+ * source: 'mailchimp',
4233
+ * referrer: 'https://email.example.com/campaign/123'
4234
+ * };
4235
+ */
4236
+ /**
4237
+ * Primary ingestion channels for data source tracking.
4238
+ */
4239
+ const DATA_SOURCE_CHANNELS = [
4240
+ 'web', // Web application/browser
4241
+ 'mobile', // Mobile app (iOS/Android)
4242
+ 'api', // Direct API integration
4243
+ 'admin', // Admin panel/back-office
4244
+ 'system', // System-generated (automated)
4245
+ 'import', // Bulk import/CSV upload
4246
+ 'webhook', // External webhook callback
4247
+ 'sync', // Synchronization from external system
4248
+ 'migration', // Data migration
4249
+ ];
4250
+ /**
4251
+ * Type guard to check if a value is a valid DataSourceChannel.
4252
+ */
4253
+ function isDataSourceChannel(value) {
4254
+ return typeof value === 'string' && DATA_SOURCE_CHANNELS.includes(value);
4255
+ }
4256
+ /**
4257
+ * Default data source for manual/admin operations.
4258
+ */
4259
+ const DEFAULT_DATA_SOURCE = {
4260
+ channel: 'admin',
4261
+ };
4262
+
4224
4263
  /**
4225
4264
  * Analytics Constants
4226
4265
  *
@@ -11657,7 +11696,7 @@ class DefaultAuthProvider {
11657
11696
  /** SDK package name */
11658
11697
  const SDK_NAME = "@explorins/pers-sdk";
11659
11698
  /** SDK version - injected from package.json at build time */
11660
- const SDK_VERSION = "2.2.0-alpha.1";
11699
+ const SDK_VERSION = "2.2.0-alpha.2";
11661
11700
  /** Full SDK identifier for headers */
11662
11701
  const SDK_USER_AGENT = `${SDK_NAME}/${SDK_VERSION}`;
11663
11702
 
@@ -11993,6 +12032,24 @@ class PersApiClient {
11993
12032
  else if (this.mergedConfig.apiProjectKey) {
11994
12033
  headers['x-project-key'] = this.mergedConfig.apiProjectKey;
11995
12034
  }
12035
+ // Add data source tracking headers
12036
+ const dataSource = this.mergedConfig.dataSource;
12037
+ if (dataSource) {
12038
+ headers['x-source-channel'] = dataSource.channel;
12039
+ if (dataSource.medium) {
12040
+ headers['x-source-medium'] = dataSource.medium;
12041
+ }
12042
+ if (dataSource.campaign) {
12043
+ headers['x-source-campaign'] = dataSource.campaign;
12044
+ }
12045
+ if (dataSource.source) {
12046
+ headers['x-source'] = dataSource.source;
12047
+ }
12048
+ // Note: referrer is typically set by browser, but can be overridden
12049
+ if (dataSource.referrer) {
12050
+ headers['Referer'] = dataSource.referrer;
12051
+ }
12052
+ }
11996
12053
  return headers;
11997
12054
  }
11998
12055
  // ==========================================
@@ -20601,6 +20658,412 @@ class CustomFieldDefinitionManager {
20601
20658
  }
20602
20659
  }
20603
20660
 
20661
+ /**
20662
+ * Low-level API for booking operations
20663
+ *
20664
+ * Provides direct HTTP calls to the booking endpoints.
20665
+ * For most use cases, prefer using BookingManager or BookingService.
20666
+ *
20667
+ * @internal
20668
+ */
20669
+ class BookingApi {
20670
+ constructor(apiClient) {
20671
+ this.apiClient = apiClient;
20672
+ this.basePath = '/bookings';
20673
+ }
20674
+ /**
20675
+ * List all bookings with optional filters
20676
+ *
20677
+ * @param options - Filter options (userId, businessId, status)
20678
+ * @returns Promise resolving to array of bookings
20679
+ */
20680
+ async getAll(options) {
20681
+ const params = new URLSearchParams();
20682
+ if (options?.userId) {
20683
+ params.append('userId', options.userId);
20684
+ }
20685
+ if (options?.businessId) {
20686
+ params.append('businessId', options.businessId);
20687
+ }
20688
+ if (options?.status) {
20689
+ params.append('status', options.status);
20690
+ }
20691
+ const queryString = params.toString();
20692
+ const url = queryString ? `${this.basePath}?${queryString}` : this.basePath;
20693
+ return this.apiClient.get(url);
20694
+ }
20695
+ /**
20696
+ * Get a single booking by ID
20697
+ *
20698
+ * @param id - Booking UUID
20699
+ * @returns Promise resolving to booking data
20700
+ */
20701
+ async getById(id) {
20702
+ return this.apiClient.get(`${this.basePath}/${id}`);
20703
+ }
20704
+ /**
20705
+ * Create a new booking
20706
+ *
20707
+ * @param data - Booking creation data
20708
+ * @returns Promise resolving to created booking
20709
+ */
20710
+ async create(data) {
20711
+ return this.apiClient.post(this.basePath, data);
20712
+ }
20713
+ /**
20714
+ * Update an existing booking
20715
+ *
20716
+ * @param id - Booking UUID
20717
+ * @param data - Booking update data
20718
+ * @returns Promise resolving to updated booking
20719
+ */
20720
+ async update(id, data) {
20721
+ return this.apiClient.put(`${this.basePath}/${id}`, data);
20722
+ }
20723
+ /**
20724
+ * Delete a booking
20725
+ *
20726
+ * @param id - Booking UUID
20727
+ * @returns Promise resolving to success status
20728
+ */
20729
+ async delete(id) {
20730
+ return this.apiClient.delete(`${this.basePath}/${id}`);
20731
+ }
20732
+ }
20733
+
20734
+ /**
20735
+ * Booking Service - Business logic layer for booking operations
20736
+ *
20737
+ * Provides booking management functionality with validation and
20738
+ * business rule enforcement.
20739
+ *
20740
+ * @internal
20741
+ */
20742
+ class BookingService {
20743
+ constructor(bookingApi) {
20744
+ this.bookingApi = bookingApi;
20745
+ }
20746
+ /**
20747
+ * Get all bookings with optional filters
20748
+ *
20749
+ * @param options - Filter options
20750
+ * @returns Promise resolving to array of bookings
20751
+ */
20752
+ async getAll(options) {
20753
+ return this.bookingApi.getAll(options);
20754
+ }
20755
+ /**
20756
+ * Get bookings for a specific user
20757
+ *
20758
+ * @param userId - User ID to filter by
20759
+ * @param status - Optional status filter
20760
+ * @returns Promise resolving to user's bookings
20761
+ */
20762
+ async getByUser(userId, status) {
20763
+ return this.bookingApi.getAll({ userId, status });
20764
+ }
20765
+ /**
20766
+ * Get bookings for a specific business
20767
+ *
20768
+ * @param businessId - Business ID to filter by
20769
+ * @param status - Optional status filter
20770
+ * @returns Promise resolving to business's bookings
20771
+ */
20772
+ async getByBusiness(businessId, status) {
20773
+ return this.bookingApi.getAll({ businessId, status });
20774
+ }
20775
+ /**
20776
+ * Get a single booking by ID
20777
+ *
20778
+ * @param id - Booking UUID
20779
+ * @returns Promise resolving to booking data
20780
+ */
20781
+ async getById(id) {
20782
+ return this.bookingApi.getById(id);
20783
+ }
20784
+ /**
20785
+ * Create a new booking
20786
+ *
20787
+ * @param data - Booking creation data
20788
+ * @returns Promise resolving to created booking
20789
+ */
20790
+ async create(data) {
20791
+ // Validate check-out is after check-in
20792
+ if (data.checkInDate && data.checkOutDate) {
20793
+ const checkIn = new Date(data.checkInDate);
20794
+ const checkOut = new Date(data.checkOutDate);
20795
+ if (checkOut <= checkIn) {
20796
+ throw new Error('Check-out date must be after check-in date');
20797
+ }
20798
+ }
20799
+ return this.bookingApi.create(data);
20800
+ }
20801
+ /**
20802
+ * Update an existing booking
20803
+ *
20804
+ * @param id - Booking UUID
20805
+ * @param data - Booking update data
20806
+ * @returns Promise resolving to updated booking
20807
+ */
20808
+ async update(id, data) {
20809
+ // Validate dates if both provided
20810
+ if (data.checkInDate && data.checkOutDate) {
20811
+ const checkIn = new Date(data.checkInDate);
20812
+ const checkOut = new Date(data.checkOutDate);
20813
+ if (checkOut <= checkIn) {
20814
+ throw new Error('Check-out date must be after check-in date');
20815
+ }
20816
+ }
20817
+ return this.bookingApi.update(id, data);
20818
+ }
20819
+ /**
20820
+ * Delete a booking
20821
+ *
20822
+ * @param id - Booking UUID
20823
+ * @returns Promise resolving to success status
20824
+ */
20825
+ async delete(id) {
20826
+ return this.bookingApi.delete(id);
20827
+ }
20828
+ /**
20829
+ * Check if a user has an active or future booking
20830
+ *
20831
+ * Useful for eligibility checks on redemptions.
20832
+ *
20833
+ * @param userId - User ID to check
20834
+ * @param options - Validation options (status filter, business filter)
20835
+ * @returns Promise resolving to true if user has valid booking
20836
+ */
20837
+ async hasValidBooking(userId, options) {
20838
+ const queryOptions = {
20839
+ userId,
20840
+ businessId: options?.businessId
20841
+ };
20842
+ // If a specific status is requested, use it
20843
+ if (options?.status && options.status !== 'active_future') {
20844
+ queryOptions.status = options.status === 'any' ? undefined : options.status;
20845
+ const bookings = await this.bookingApi.getAll(queryOptions);
20846
+ return bookings.length > 0;
20847
+ }
20848
+ // Default: check active+future (active_future or undefined)
20849
+ const activeBookings = await this.bookingApi.getAll({ ...queryOptions, status: 'active' });
20850
+ if (activeBookings.length > 0)
20851
+ return true;
20852
+ const futureBookings = await this.bookingApi.getAll({ ...queryOptions, status: 'future' });
20853
+ return futureBookings.length > 0;
20854
+ }
20855
+ }
20856
+
20857
+ /**
20858
+ * Booking Manager - High-level interface for booking operations
20859
+ *
20860
+ * Provides a simplified API for managing bookings (hotel stays, reservations, etc.)
20861
+ * Used for tracking user stays and eligibility checks on redemptions.
20862
+ *
20863
+ * @group Managers
20864
+ * @category Booking Management
20865
+ *
20866
+ * @example List Bookings
20867
+ * ```typescript
20868
+ * // Get all bookings
20869
+ * const allBookings = await sdk.bookings.getAll();
20870
+ *
20871
+ * // Filter by user
20872
+ * const userBookings = await sdk.bookings.getAll({ userId: 'user-123' });
20873
+ *
20874
+ * // Filter by status
20875
+ * const activeBookings = await sdk.bookings.getAll({ status: 'active' });
20876
+ * ```
20877
+ *
20878
+ * @example Create Booking
20879
+ * ```typescript
20880
+ * const booking = await sdk.bookings.create({
20881
+ * userId: 'user-123',
20882
+ * locationName: 'Grand Hotel',
20883
+ * checkInDate: '2026-06-01',
20884
+ * checkOutDate: '2026-06-05',
20885
+ * guests: 2
20886
+ * });
20887
+ * ```
20888
+ *
20889
+ * @example Check Eligibility
20890
+ * ```typescript
20891
+ * // Check if user has valid (active or future) booking
20892
+ * const hasBooking = await sdk.bookings.hasValidBooking('user-123');
20893
+ * if (hasBooking) {
20894
+ * // User is eligible for booking-required redemption
20895
+ * }
20896
+ * ```
20897
+ */
20898
+ class BookingManager {
20899
+ constructor(apiClient, events) {
20900
+ this.apiClient = apiClient;
20901
+ this.events = events;
20902
+ const bookingApi = new BookingApi(apiClient);
20903
+ this.bookingService = new BookingService(bookingApi);
20904
+ }
20905
+ /**
20906
+ * Get all bookings with optional filters
20907
+ *
20908
+ * @param options - Filter options (userId, businessId, status)
20909
+ * @returns Promise resolving to array of bookings
20910
+ *
20911
+ * @example Basic Usage
20912
+ * ```typescript
20913
+ * // All bookings
20914
+ * const bookings = await sdk.bookings.getAll();
20915
+ *
20916
+ * // Filter by user
20917
+ * const userBookings = await sdk.bookings.getAll({ userId: 'user-123' });
20918
+ *
20919
+ * // Filter by status
20920
+ * const activeBookings = await sdk.bookings.getAll({ status: 'active' });
20921
+ * const futureBookings = await sdk.bookings.getAll({ status: 'future' });
20922
+ * const pastBookings = await sdk.bookings.getAll({ status: 'past' });
20923
+ * ```
20924
+ */
20925
+ async getAll(options) {
20926
+ return this.bookingService.getAll(options);
20927
+ }
20928
+ /**
20929
+ * Get bookings for a specific user
20930
+ *
20931
+ * Convenience method for filtering bookings by user ID.
20932
+ *
20933
+ * @param userId - User ID to filter by
20934
+ * @param status - Optional status filter
20935
+ * @returns Promise resolving to user's bookings
20936
+ */
20937
+ async getByUser(userId, status) {
20938
+ return this.bookingService.getByUser(userId, status);
20939
+ }
20940
+ /**
20941
+ * Get bookings for a specific business
20942
+ *
20943
+ * Convenience method for filtering bookings by business ID.
20944
+ *
20945
+ * @param businessId - Business ID to filter by
20946
+ * @param status - Optional status filter
20947
+ * @returns Promise resolving to business's bookings
20948
+ */
20949
+ async getByBusiness(businessId, status) {
20950
+ return this.bookingService.getByBusiness(businessId, status);
20951
+ }
20952
+ /**
20953
+ * Get a single booking by ID
20954
+ *
20955
+ * @param id - Booking UUID
20956
+ * @returns Promise resolving to booking data
20957
+ * @throws {PersApiError} When booking not found
20958
+ */
20959
+ async getById(id) {
20960
+ return this.bookingService.getById(id);
20961
+ }
20962
+ /**
20963
+ * Create a new booking
20964
+ *
20965
+ * Creates a booking for a user at a specific location. Used for tracking
20966
+ * hotel stays, reservations, etc. for eligibility checks.
20967
+ *
20968
+ * @param data - Booking creation data
20969
+ * @returns Promise resolving to created booking
20970
+ * @throws {Error} When check-out date is not after check-in date
20971
+ *
20972
+ * @example Create Hotel Booking
20973
+ * ```typescript
20974
+ * const booking = await sdk.bookings.create({
20975
+ * userId: 'user-123',
20976
+ * locationName: 'Grand Hotel',
20977
+ * locationType: 'hotel',
20978
+ * checkInDate: '2026-06-01',
20979
+ * checkOutDate: '2026-06-05',
20980
+ * confirmationNumber: 'CONF123',
20981
+ * guests: 2,
20982
+ * notes: 'Ocean view room'
20983
+ * });
20984
+ * ```
20985
+ */
20986
+ async create(data) {
20987
+ const result = await this.bookingService.create(data);
20988
+ // TODO: Add event emission when 'booking' domain is added to pers-shared
20989
+ // this.events?.emitSuccess({ domain: 'booking', type: 'booking_created', ... });
20990
+ return result;
20991
+ }
20992
+ /**
20993
+ * Update an existing booking
20994
+ *
20995
+ * @param id - Booking UUID
20996
+ * @param data - Booking update data (partial update supported)
20997
+ * @returns Promise resolving to updated booking
20998
+ * @throws {PersApiError} When booking not found
20999
+ * @throws {Error} When check-out date is not after check-in date
21000
+ *
21001
+ * @example Update Booking Dates
21002
+ * ```typescript
21003
+ * const updated = await sdk.bookings.update('booking-123', {
21004
+ * checkInDate: '2026-06-02',
21005
+ * checkOutDate: '2026-06-07'
21006
+ * });
21007
+ * ```
21008
+ */
21009
+ async update(id, data) {
21010
+ const result = await this.bookingService.update(id, data);
21011
+ // TODO: Add event emission when 'booking' domain is added to pers-shared
21012
+ // this.events?.emitSuccess({ domain: 'booking', type: 'booking_updated', ... });
21013
+ return result;
21014
+ }
21015
+ /**
21016
+ * Delete a booking
21017
+ *
21018
+ * @param id - Booking UUID
21019
+ * @returns Promise resolving to success status
21020
+ * @throws {PersApiError} When booking not found
21021
+ */
21022
+ async delete(id) {
21023
+ const result = await this.bookingService.delete(id);
21024
+ // TODO: Add event emission when 'booking' domain is added to pers-shared
21025
+ // this.events?.emitSuccess({ domain: 'booking', type: 'booking_deleted', ... });
21026
+ return result;
21027
+ }
21028
+ /**
21029
+ * Check if a user has a valid (active or future) booking
21030
+ *
21031
+ * Useful for eligibility checks on redemptions that require a valid booking.
21032
+ *
21033
+ * @param userId - User ID to check
21034
+ * @param options - Validation options (status filter, business filter)
21035
+ * @returns Promise resolving to true if user has valid booking
21036
+ *
21037
+ * @example Eligibility Check
21038
+ * ```typescript
21039
+ * const hasBooking = await sdk.bookings.hasValidBooking('user-123');
21040
+ *
21041
+ * if (!hasBooking) {
21042
+ * console.log('User must have an active or upcoming booking');
21043
+ * }
21044
+ *
21045
+ * // Check for active booking only
21046
+ * const hasActive = await sdk.bookings.hasValidBooking('user-123', { status: 'active' });
21047
+ *
21048
+ * // Check for booking at specific business
21049
+ * const hasHotelBooking = await sdk.bookings.hasValidBooking('user-123', {
21050
+ * businessId: 'hotel-123'
21051
+ * });
21052
+ * ```
21053
+ */
21054
+ async hasValidBooking(userId, options) {
21055
+ return this.bookingService.hasValidBooking(userId, options);
21056
+ }
21057
+ /**
21058
+ * Get the booking service for advanced operations
21059
+ *
21060
+ * @returns BookingService instance with full API access
21061
+ */
21062
+ getBookingService() {
21063
+ return this.bookingService;
21064
+ }
21065
+ }
21066
+
20604
21067
  /**
20605
21068
  * @fileoverview PERS SDK - Platform-agnostic TypeScript SDK with High-Level Managers
20606
21069
  *
@@ -21320,6 +21783,47 @@ class PersSDK {
21320
21783
  }
21321
21784
  return this._customFields;
21322
21785
  }
21786
+ /**
21787
+ * Booking manager - High-level booking operations
21788
+ *
21789
+ * Provides methods for managing user bookings (hotel stays, reservations, etc.)
21790
+ * Used for eligibility checks on redemptions that require valid bookings.
21791
+ *
21792
+ * @returns BookingManager instance
21793
+ *
21794
+ * @example List User Bookings
21795
+ * ```typescript
21796
+ * // Get all bookings for a user
21797
+ * const bookings = await sdk.bookings.getByUser('user-123');
21798
+ *
21799
+ * // Filter by status
21800
+ * const activeBookings = await sdk.bookings.getAll({ status: 'active' });
21801
+ * ```
21802
+ *
21803
+ * @example Create Booking
21804
+ * ```typescript
21805
+ * const booking = await sdk.bookings.create({
21806
+ * userId: 'user-123',
21807
+ * locationName: 'Grand Hotel',
21808
+ * checkInDate: '2026-06-01',
21809
+ * checkOutDate: '2026-06-05'
21810
+ * });
21811
+ * ```
21812
+ *
21813
+ * @example Eligibility Check
21814
+ * ```typescript
21815
+ * const hasBooking = await sdk.bookings.hasValidBooking('user-123');
21816
+ * if (hasBooking) {
21817
+ * // User is eligible for booking-required redemption
21818
+ * }
21819
+ * ```
21820
+ */
21821
+ get bookings() {
21822
+ if (!this._bookings) {
21823
+ this._bookings = new BookingManager(this.apiClient, this._events);
21824
+ }
21825
+ return this._bookings;
21826
+ }
21323
21827
  /**
21324
21828
  * Wallet Events Manager - Real-time blockchain events for user's wallets
21325
21829
  *
@@ -48896,6 +49400,9 @@ exports.AuthService = AuthService;
48896
49400
  exports.AuthTokenManager = AuthTokenManager;
48897
49401
  exports.AuthenticationError = AuthenticationError;
48898
49402
  exports.BaseTokenService = BaseTokenService;
49403
+ exports.BookingApi = BookingApi;
49404
+ exports.BookingManager = BookingManager;
49405
+ exports.BookingService = BookingService;
48899
49406
  exports.BrowserFetchClientAdapter = BrowserFetchClientAdapter;
48900
49407
  exports.BusinessApi = BusinessApi;
48901
49408
  exports.BusinessManager = BusinessManager;
@@ -48911,6 +49418,8 @@ exports.ChainTypes = ChainTypes;
48911
49418
  exports.ClientTransactionType = ClientTransactionType;
48912
49419
  exports.CommonErrorCodes = CommonErrorCodes;
48913
49420
  exports.CustomFieldDefinitionManager = CustomFieldDefinitionManager;
49421
+ exports.DATA_SOURCE_CHANNELS = DATA_SOURCE_CHANNELS;
49422
+ exports.DEFAULT_DATA_SOURCE = DEFAULT_DATA_SOURCE;
48914
49423
  exports.DEFAULT_PERS_CONFIG = DEFAULT_PERS_CONFIG;
48915
49424
  exports.DPOP_STORAGE_KEYS = DPOP_STORAGE_KEYS;
48916
49425
  exports.DPoPManager = DPoPManager;
@@ -48938,6 +49447,7 @@ exports.IMAGE_CDN_MAX_INPUT_SIZE = IMAGE_CDN_MAX_INPUT_SIZE;
48938
49447
  exports.IMAGE_CDN_PRESETS = IMAGE_CDN_PRESETS;
48939
49448
  exports.IMAGE_CDN_SUPPORTED_FORMATS = IMAGE_CDN_SUPPORTED_FORMATS;
48940
49449
  exports.IndexedDBTokenStorage = IndexedDBTokenStorage;
49450
+ exports.LOCATION_TYPES = LOCATION_TYPES;
48941
49451
  exports.LocalStorageTokenStorage = LocalStorageTokenStorage;
48942
49452
  exports.LogoutRequired = LogoutRequired;
48943
49453
  exports.MEMBERSHIP_ROLE_HIERARCHY = MEMBERSHIP_ROLE_HIERARCHY;
@@ -49054,6 +49564,7 @@ exports.hasMinimumRole = hasMinimumRole;
49054
49564
  exports.initializeReactNativePolyfills = initializeReactNativePolyfills;
49055
49565
  exports.isBuiltInUserField = isBuiltInUserField;
49056
49566
  exports.isConnectedMessage = isConnectedMessage;
49567
+ exports.isDataSourceChannel = isDataSourceChannel;
49057
49568
  exports.isErrorMessage = isErrorMessage;
49058
49569
  exports.isEventMessage = isEventMessage;
49059
49570
  exports.isFatalAuthErrorInMessage = isFatalAuthErrorInMessage;
@@ -49088,6 +49599,7 @@ exports.normalizeFilters = normalizeFilters;
49088
49599
  exports.normalizeToPaginated = normalizeToPaginated;
49089
49600
  exports.operatorText = operatorText;
49090
49601
  exports.registerIdentifierType = registerIdentifierType;
49602
+ exports.selectOptionsSourceEntities = selectOptionsSourceEntities;
49091
49603
  exports.testnetPrefix = testnetPrefix;
49092
49604
  exports.testnetShortPrefix = testnetShortPrefix;
49093
49605
  exports.uniqueUserIdentifierKeys = uniqueUserIdentifierKeys;
@@ -49118,6 +49630,5 @@ exports.userModelFields = userModelFields;
49118
49630
  exports.validateAllFields = validateAllFields;
49119
49631
  exports.validateFieldComparisons = validateFieldComparisons;
49120
49632
  exports.validateFieldValue = validateFieldValue;
49121
- exports.validateObjectArrayItems = validateObjectArrayItems;
49122
49633
  exports.warnIfProblematicEnvironment = warnIfProblematicEnvironment;
49123
49634
  //# sourceMappingURL=index.js.map