@cimplify/sdk 0.3.3 → 0.3.5

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.d.mts CHANGED
@@ -1,3 +1,95 @@
1
+ /**
2
+ * Observability hooks for monitoring SDK behavior.
3
+ *
4
+ * These hooks allow you to plug in your own logging, metrics, and tracing
5
+ * without the SDK depending on any specific observability library.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const client = createCimplifyClient({
10
+ * hooks: {
11
+ * onRequestStart: ({ method, path }) => {
12
+ * console.log(`[SDK] ${method} ${path}`);
13
+ * },
14
+ * onRequestSuccess: ({ method, path, durationMs }) => {
15
+ * metrics.histogram('sdk.request.duration', durationMs, { method, path });
16
+ * },
17
+ * onRequestError: ({ method, path, error, retryCount }) => {
18
+ * Sentry.captureException(error, { extra: { method, path, retryCount } });
19
+ * },
20
+ * },
21
+ * });
22
+ * ```
23
+ */
24
+ /** Context passed to request lifecycle hooks */
25
+ interface RequestContext {
26
+ /** HTTP method */
27
+ method: "GET" | "POST" | "DELETE";
28
+ /** Request path (e.g., "/api/q", "/api/m") */
29
+ path: string;
30
+ /** Full URL */
31
+ url: string;
32
+ /** Request body (for POST requests) */
33
+ body?: unknown;
34
+ /** Timestamp when request started */
35
+ startTime: number;
36
+ }
37
+ /** Passed when a request starts */
38
+ interface RequestStartEvent extends RequestContext {
39
+ }
40
+ /** Passed when a request succeeds */
41
+ interface RequestSuccessEvent extends RequestContext {
42
+ /** HTTP status code */
43
+ status: number;
44
+ /** Duration in milliseconds */
45
+ durationMs: number;
46
+ }
47
+ /** Passed when a request fails */
48
+ interface RequestErrorEvent extends RequestContext {
49
+ /** The error that occurred */
50
+ error: Error;
51
+ /** Duration in milliseconds */
52
+ durationMs: number;
53
+ /** Number of retries attempted before giving up */
54
+ retryCount: number;
55
+ /** Whether the error is retryable */
56
+ retryable: boolean;
57
+ }
58
+ /** Passed when a retry is about to happen */
59
+ interface RetryEvent extends RequestContext {
60
+ /** Which retry attempt (1, 2, 3...) */
61
+ attempt: number;
62
+ /** Delay before retry in milliseconds */
63
+ delayMs: number;
64
+ /** The error that triggered the retry */
65
+ error: Error;
66
+ }
67
+ /** Passed when session token changes */
68
+ interface SessionChangeEvent {
69
+ /** Previous token (null if none) */
70
+ previousToken: string | null;
71
+ /** New token (null if cleared) */
72
+ newToken: string | null;
73
+ /** Source of the change */
74
+ source: "response" | "manual" | "clear";
75
+ }
76
+ /**
77
+ * Observability hooks configuration.
78
+ * All hooks are optional - only implement what you need.
79
+ */
80
+ interface ObservabilityHooks {
81
+ /** Called when a request is about to be sent */
82
+ onRequestStart?: (event: RequestStartEvent) => void;
83
+ /** Called when a request completes successfully */
84
+ onRequestSuccess?: (event: RequestSuccessEvent) => void;
85
+ /** Called when a request fails (after all retries exhausted) */
86
+ onRequestError?: (event: RequestErrorEvent) => void;
87
+ /** Called before each retry attempt */
88
+ onRetry?: (event: RetryEvent) => void;
89
+ /** Called when session token changes */
90
+ onSessionChange?: (event: SessionChangeEvent) => void;
91
+ }
92
+
1
93
  /** Decimal value represented as string for precision */
2
94
  type Money = string;
3
95
  /** Supported currencies */
@@ -2839,6 +2931,8 @@ interface CimplifyConfig {
2839
2931
  maxRetries?: number;
2840
2932
  /** Base delay between retries in milliseconds (default: 1000) */
2841
2933
  retryDelay?: number;
2934
+ /** Observability hooks for logging, metrics, and tracing */
2935
+ hooks?: ObservabilityHooks;
2842
2936
  }
2843
2937
  declare class CimplifyClient {
2844
2938
  private baseUrl;
@@ -2849,6 +2943,9 @@ declare class CimplifyClient {
2849
2943
  private timeout;
2850
2944
  private maxRetries;
2851
2945
  private retryDelay;
2946
+ private hooks;
2947
+ /** In-flight request deduplication map */
2948
+ private inflightRequests;
2852
2949
  private _catalogue?;
2853
2950
  private _cart?;
2854
2951
  private _checkout?;
@@ -2868,11 +2965,29 @@ declare class CimplifyClient {
2868
2965
  private getHeaders;
2869
2966
  private updateSessionFromResponse;
2870
2967
  /**
2871
- * Resilient fetch with timeout and automatic retries for network errors.
2968
+ * Resilient fetch with timeout, automatic retries, and observability hooks.
2872
2969
  * Uses exponential backoff: 1s, 2s, 4s between retries.
2873
2970
  */
2874
2971
  private resilientFetch;
2972
+ /**
2973
+ * Generate a deduplication key for a request.
2974
+ * Same query + variables = same key = deduplicated.
2975
+ */
2976
+ private getDedupeKey;
2977
+ /**
2978
+ * Execute a request with deduplication.
2979
+ * If an identical request is already in-flight, return the same promise.
2980
+ * This prevents redundant network calls when multiple components request the same data.
2981
+ */
2982
+ private deduplicatedRequest;
2983
+ /**
2984
+ * Execute a query with deduplication.
2985
+ * Multiple identical queries made simultaneously will share a single network request.
2986
+ */
2875
2987
  query<T = unknown>(query: string, variables?: Record<string, unknown>): Promise<T>;
2988
+ /**
2989
+ * Execute a mutation. NOT deduplicated - mutations have side effects.
2990
+ */
2876
2991
  call<T = unknown>(method: string, args?: unknown): Promise<T>;
2877
2992
  get<T = unknown>(path: string): Promise<T>;
2878
2993
  post<T = unknown>(path: string, body?: unknown): Promise<T>;
@@ -3415,4 +3530,4 @@ interface ApiResponse<T> {
3415
3530
  metadata?: ResponseMetadata;
3416
3531
  }
3417
3532
 
3418
- export { AUTHORIZATION_TYPE, AUTH_MUTATION, type AddOn, type AddOnDetails, type AddOnGroupDetails, type AddOnOption, type AddOnOptionDetails, type AddOnOptionPrice, type AddOnWithOptions, type AddToCartInput, type AddressData, type AdjustmentType, type AmountToPay, type ApiError, type ApiResponse, type AppliedDiscount, type AuthResponse, AuthService, type AuthStatus, type AuthorizationType, type AvailabilityCheck, type AvailabilityResult, type AvailableSlot, type BenefitType, type Booking, type BookingRequirementOverride, type BookingStatus, type BookingWithDetails, type BufferTimes, type Bundle, type BundleComponentData, type BundleComponentInfo, type BundlePriceType, type BundleProduct, type BundleSelectionData, type BundleSelectionInput, type BundleStoredSelection, type BundleSummary, type BundleWithDetails, type Business, type BusinessHours, type BusinessPreferences, BusinessService, type BusinessSettings, type BusinessType, type BusinessWithLocations, CHECKOUT_MODE, CHECKOUT_MUTATION, CHECKOUT_STEP, CURRENCY_SYMBOLS, type CancelBookingInput, type CancelOrderInput, type CancellationPolicy, type Cart, type CartAddOn, type CartChannel, type CartItem, type CartItemDetails, CartOperations, type CartStatus, type CartSummary, type CartTotals, CatalogueQueries, type Category, type CategoryInfo, type CategorySummary, type ChangePasswordInput, type CheckSlotAvailabilityInput, type CheckoutAddressInfo, type CheckoutCustomerInfo, type CheckoutFormData, type CheckoutInput, type CheckoutMode, CheckoutService as CheckoutOperations, type CheckoutOrderType, type CheckoutPaymentMethod, type CheckoutResult, CheckoutService, type CheckoutStep, type ChosenPrice, CimplifyClient, type CimplifyConfig, CimplifyError, type Collection, type CollectionProduct, type CollectionSummary, type ComponentGroup, type ComponentGroupWithComponents, type ComponentPriceBreakdown, type ComponentSelectionInput, type ComponentSourceType, type Composite, type CompositeComponent, type CompositePriceBreakdown, type CompositePriceResult, type CompositePricingMode, type CompositeSelectionData, type ComponentSelectionInput as CompositeSelectionInput, type CompositeStoredSelection, type CompositeWithDetails, type ContactType, type CreateAddressInput, type CreateMobileMoneyInput, type Currency, type Customer, type CustomerAddress, type CustomerLinkPreferences, type CustomerMobileMoney, type CustomerServicePreferences, DEFAULT_COUNTRY, DEFAULT_CURRENCY, type DayAvailability, type DepositResult, type DepositType, type DeviceType, type DigitalProductType, type DiscountBreakdown, type DiscountDetails, type DisplayAddOn, type DisplayAddOnOption, type DisplayCart, type DisplayCartItem, type EnrollAndLinkOrderInput, type EnrollAndLinkOrderResult, type EnrollmentData, type Err, ErrorCode, type ErrorCodeType, type FeeBearerType, type FormatCompactOptions, type FormatPriceOptions, type FulfillmentLink, type FulfillmentStatus, type FulfillmentType, type GetAvailableSlotsInput, type GetOrdersOptions, type GetProductsOptions, type GroupPricingBehavior, type InitializePaymentResult, InventoryService, type InventorySummary, type InventoryType, type KitchenOrderItem, type KitchenOrderResult, LINK_MUTATION, LINK_QUERY, type LineConfiguration, type LineItem, type LineType, type LinkData, type LinkEnrollResult, LinkService, type LinkSession, type LinkStatusResult, type LiteBootstrap, LiteService, type Location, type LocationAppointment, type LocationProductPrice, type LocationStock, type LocationTaxBehavior, type LocationTaxOverrides, type LocationTimeProfile, type LocationWithDetails, MOBILE_MONEY_PROVIDER, MOBILE_MONEY_PROVIDERS, type MobileMoneyData, type MobileMoneyDetails, type MobileMoneyProvider, type Money, type MutationRequest, ORDER_MUTATION, ORDER_TYPE, type Ok, type Order, type OrderChannel, type OrderFilter, type OrderFulfillmentSummary, type OrderGroup, type OrderGroupDetails, type OrderGroupPayment, type OrderGroupPaymentState, type OrderGroupPaymentSummary, type OrderHistory, type OrderLineState, type OrderLineStatus, type OrderPaymentEvent, OrderQueries, type OrderSplitDetail, type OrderStatus, type OtpResult, PAYMENT_METHOD, PAYMENT_MUTATION, PAYMENT_STATE, PICKUP_TIME_TYPE, type Pagination, type PaginationParams, type ParsedPrice, type Payment, type PaymentErrorDetails, type PaymentMethod, type PaymentMethodType, type PaymentProcessingState, type PaymentProvider, type PaymentResponse, type PaymentState, type PaymentStatus, type PaymentStatusResponse, type PickupTime, type PickupTimeType, type Price, type PriceAdjustment, type PriceDecisionPath, type PriceEntryType, type PriceInfo, type PricePathTaxInfo, type PriceSource, type PricingOverrides, type Product, type ProductAddOn, type ProductAvailability, type ProductStock, type ProductTimeProfile, type ProductType, type ProductVariant, type ProductVariantValue, type ProductWithDetails, type ProductWithPrice, QueryBuilder, type QueryRequest, type RefundOrderInput, type ReminderMethod, type ReminderSettings, type RequestOtpInput, type RescheduleBookingInput, type ResourceAssignment, type ResourceAvailabilityException, type ResourceAvailabilityRule, type ResourceType, type ResponseMetadata, type Result, type RevokeAllSessionsResult, type RevokeSessionResult, type Room, type SalesChannel, type SchedulingMetadata, type SchedulingResult, SchedulingService, type SearchOptions, type SelectedAddOnOption, type Service, type ServiceAvailabilityException, type ServiceAvailabilityParams, type ServiceAvailabilityResult, type ServiceAvailabilityRule, type ServiceCharge, type ServiceNotes, type ServiceScheduleRequest, type ServiceStaffRequirement, type ServiceStatus, type ServiceWithStaff, type Staff, type StaffAssignment, type StaffAvailabilityException, type StaffAvailabilityRule, type StaffBookingProfile, type StaffRole, type StaffScheduleItem, type Stock, type StockLevel, type StockOwnershipType, type StockStatus, type StorefrontBootstrap, type SubmitAuthorizationInput, type Table, type TableInfo, type TaxComponent, type TaxInfo, type TaxPathComponent, type TimeRange, type TimeRanges, type TimeSlot, type UICart, type UICartBusiness, type UICartCustomer, type UICartLocation, type UICartPricing, type UpdateAddressInput, type UpdateCartItemInput, type UpdateOrderStatusInput, type UpdateProfileInput, type VariantAxis, type VariantAxisSelection, type VariantAxisValue, type VariantAxisWithValues, type VariantDetails, type VariantDetailsDTO, type VariantDisplayAttribute, type VariantLocationAvailability, type VariantStock, type VariantStrategy, type VerifyOtpInput, categorizePaymentError, combine, combineObject, createCimplifyClient, detectMobileMoneyProvider, err, extractPriceInfo, flatMap, formatMoney, formatNumberCompact, formatPrice, formatPriceAdjustment, formatPriceCompact, formatProductPrice, fromPromise, generateIdempotencyKey, getBasePrice, getCurrencySymbol, getDiscountPercentage, getDisplayPrice, getMarkupPercentage, getOrElse, getProductCurrency, isCimplifyError, isErr, isOk, isOnSale, isRetryableError, mapError, mapResult, normalizePaymentResponse, normalizeStatusResponse, ok, parsePrice, parsePricePath, parsedPriceToPriceInfo, query, toNullable, tryCatch, unwrap };
3533
+ export { AUTHORIZATION_TYPE, AUTH_MUTATION, type AddOn, type AddOnDetails, type AddOnGroupDetails, type AddOnOption, type AddOnOptionDetails, type AddOnOptionPrice, type AddOnWithOptions, type AddToCartInput, type AddressData, type AdjustmentType, type AmountToPay, type ApiError, type ApiResponse, type AppliedDiscount, type AuthResponse, AuthService, type AuthStatus, type AuthorizationType, type AvailabilityCheck, type AvailabilityResult, type AvailableSlot, type BenefitType, type Booking, type BookingRequirementOverride, type BookingStatus, type BookingWithDetails, type BufferTimes, type Bundle, type BundleComponentData, type BundleComponentInfo, type BundlePriceType, type BundleProduct, type BundleSelectionData, type BundleSelectionInput, type BundleStoredSelection, type BundleSummary, type BundleWithDetails, type Business, type BusinessHours, type BusinessPreferences, BusinessService, type BusinessSettings, type BusinessType, type BusinessWithLocations, CHECKOUT_MODE, CHECKOUT_MUTATION, CHECKOUT_STEP, CURRENCY_SYMBOLS, type CancelBookingInput, type CancelOrderInput, type CancellationPolicy, type Cart, type CartAddOn, type CartChannel, type CartItem, type CartItemDetails, CartOperations, type CartStatus, type CartSummary, type CartTotals, CatalogueQueries, type Category, type CategoryInfo, type CategorySummary, type ChangePasswordInput, type CheckSlotAvailabilityInput, type CheckoutAddressInfo, type CheckoutCustomerInfo, type CheckoutFormData, type CheckoutInput, type CheckoutMode, CheckoutService as CheckoutOperations, type CheckoutOrderType, type CheckoutPaymentMethod, type CheckoutResult, CheckoutService, type CheckoutStep, type ChosenPrice, CimplifyClient, type CimplifyConfig, CimplifyError, type Collection, type CollectionProduct, type CollectionSummary, type ComponentGroup, type ComponentGroupWithComponents, type ComponentPriceBreakdown, type ComponentSelectionInput, type ComponentSourceType, type Composite, type CompositeComponent, type CompositePriceBreakdown, type CompositePriceResult, type CompositePricingMode, type CompositeSelectionData, type ComponentSelectionInput as CompositeSelectionInput, type CompositeStoredSelection, type CompositeWithDetails, type ContactType, type CreateAddressInput, type CreateMobileMoneyInput, type Currency, type Customer, type CustomerAddress, type CustomerLinkPreferences, type CustomerMobileMoney, type CustomerServicePreferences, DEFAULT_COUNTRY, DEFAULT_CURRENCY, type DayAvailability, type DepositResult, type DepositType, type DeviceType, type DigitalProductType, type DiscountBreakdown, type DiscountDetails, type DisplayAddOn, type DisplayAddOnOption, type DisplayCart, type DisplayCartItem, type EnrollAndLinkOrderInput, type EnrollAndLinkOrderResult, type EnrollmentData, type Err, ErrorCode, type ErrorCodeType, type FeeBearerType, type FormatCompactOptions, type FormatPriceOptions, type FulfillmentLink, type FulfillmentStatus, type FulfillmentType, type GetAvailableSlotsInput, type GetOrdersOptions, type GetProductsOptions, type GroupPricingBehavior, type InitializePaymentResult, InventoryService, type InventorySummary, type InventoryType, type KitchenOrderItem, type KitchenOrderResult, LINK_MUTATION, LINK_QUERY, type LineConfiguration, type LineItem, type LineType, type LinkData, type LinkEnrollResult, LinkService, type LinkSession, type LinkStatusResult, type LiteBootstrap, LiteService, type Location, type LocationAppointment, type LocationProductPrice, type LocationStock, type LocationTaxBehavior, type LocationTaxOverrides, type LocationTimeProfile, type LocationWithDetails, MOBILE_MONEY_PROVIDER, MOBILE_MONEY_PROVIDERS, type MobileMoneyData, type MobileMoneyDetails, type MobileMoneyProvider, type Money, type MutationRequest, ORDER_MUTATION, ORDER_TYPE, type ObservabilityHooks, type Ok, type Order, type OrderChannel, type OrderFilter, type OrderFulfillmentSummary, type OrderGroup, type OrderGroupDetails, type OrderGroupPayment, type OrderGroupPaymentState, type OrderGroupPaymentSummary, type OrderHistory, type OrderLineState, type OrderLineStatus, type OrderPaymentEvent, OrderQueries, type OrderSplitDetail, type OrderStatus, type OtpResult, PAYMENT_METHOD, PAYMENT_MUTATION, PAYMENT_STATE, PICKUP_TIME_TYPE, type Pagination, type PaginationParams, type ParsedPrice, type Payment, type PaymentErrorDetails, type PaymentMethod, type PaymentMethodType, type PaymentProcessingState, type PaymentProvider, type PaymentResponse, type PaymentState, type PaymentStatus, type PaymentStatusResponse, type PickupTime, type PickupTimeType, type Price, type PriceAdjustment, type PriceDecisionPath, type PriceEntryType, type PriceInfo, type PricePathTaxInfo, type PriceSource, type PricingOverrides, type Product, type ProductAddOn, type ProductAvailability, type ProductStock, type ProductTimeProfile, type ProductType, type ProductVariant, type ProductVariantValue, type ProductWithDetails, type ProductWithPrice, QueryBuilder, type QueryRequest, type RefundOrderInput, type ReminderMethod, type ReminderSettings, type RequestContext, type RequestErrorEvent, type RequestOtpInput, type RequestStartEvent, type RequestSuccessEvent, type RescheduleBookingInput, type ResourceAssignment, type ResourceAvailabilityException, type ResourceAvailabilityRule, type ResourceType, type ResponseMetadata, type Result, type RetryEvent, type RevokeAllSessionsResult, type RevokeSessionResult, type Room, type SalesChannel, type SchedulingMetadata, type SchedulingResult, SchedulingService, type SearchOptions, type SelectedAddOnOption, type Service, type ServiceAvailabilityException, type ServiceAvailabilityParams, type ServiceAvailabilityResult, type ServiceAvailabilityRule, type ServiceCharge, type ServiceNotes, type ServiceScheduleRequest, type ServiceStaffRequirement, type ServiceStatus, type ServiceWithStaff, type SessionChangeEvent, type Staff, type StaffAssignment, type StaffAvailabilityException, type StaffAvailabilityRule, type StaffBookingProfile, type StaffRole, type StaffScheduleItem, type Stock, type StockLevel, type StockOwnershipType, type StockStatus, type StorefrontBootstrap, type SubmitAuthorizationInput, type Table, type TableInfo, type TaxComponent, type TaxInfo, type TaxPathComponent, type TimeRange, type TimeRanges, type TimeSlot, type UICart, type UICartBusiness, type UICartCustomer, type UICartLocation, type UICartPricing, type UpdateAddressInput, type UpdateCartItemInput, type UpdateOrderStatusInput, type UpdateProfileInput, type VariantAxis, type VariantAxisSelection, type VariantAxisValue, type VariantAxisWithValues, type VariantDetails, type VariantDetailsDTO, type VariantDisplayAttribute, type VariantLocationAvailability, type VariantStock, type VariantStrategy, type VerifyOtpInput, categorizePaymentError, combine, combineObject, createCimplifyClient, detectMobileMoneyProvider, err, extractPriceInfo, flatMap, formatMoney, formatNumberCompact, formatPrice, formatPriceAdjustment, formatPriceCompact, formatProductPrice, fromPromise, generateIdempotencyKey, getBasePrice, getCurrencySymbol, getDiscountPercentage, getDisplayPrice, getMarkupPercentage, getOrElse, getProductCurrency, isCimplifyError, isErr, isOk, isOnSale, isRetryableError, mapError, mapResult, normalizePaymentResponse, normalizeStatusResponse, ok, parsePrice, parsePricePath, parsedPriceToPriceInfo, query, toNullable, tryCatch, unwrap };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,95 @@
1
+ /**
2
+ * Observability hooks for monitoring SDK behavior.
3
+ *
4
+ * These hooks allow you to plug in your own logging, metrics, and tracing
5
+ * without the SDK depending on any specific observability library.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const client = createCimplifyClient({
10
+ * hooks: {
11
+ * onRequestStart: ({ method, path }) => {
12
+ * console.log(`[SDK] ${method} ${path}`);
13
+ * },
14
+ * onRequestSuccess: ({ method, path, durationMs }) => {
15
+ * metrics.histogram('sdk.request.duration', durationMs, { method, path });
16
+ * },
17
+ * onRequestError: ({ method, path, error, retryCount }) => {
18
+ * Sentry.captureException(error, { extra: { method, path, retryCount } });
19
+ * },
20
+ * },
21
+ * });
22
+ * ```
23
+ */
24
+ /** Context passed to request lifecycle hooks */
25
+ interface RequestContext {
26
+ /** HTTP method */
27
+ method: "GET" | "POST" | "DELETE";
28
+ /** Request path (e.g., "/api/q", "/api/m") */
29
+ path: string;
30
+ /** Full URL */
31
+ url: string;
32
+ /** Request body (for POST requests) */
33
+ body?: unknown;
34
+ /** Timestamp when request started */
35
+ startTime: number;
36
+ }
37
+ /** Passed when a request starts */
38
+ interface RequestStartEvent extends RequestContext {
39
+ }
40
+ /** Passed when a request succeeds */
41
+ interface RequestSuccessEvent extends RequestContext {
42
+ /** HTTP status code */
43
+ status: number;
44
+ /** Duration in milliseconds */
45
+ durationMs: number;
46
+ }
47
+ /** Passed when a request fails */
48
+ interface RequestErrorEvent extends RequestContext {
49
+ /** The error that occurred */
50
+ error: Error;
51
+ /** Duration in milliseconds */
52
+ durationMs: number;
53
+ /** Number of retries attempted before giving up */
54
+ retryCount: number;
55
+ /** Whether the error is retryable */
56
+ retryable: boolean;
57
+ }
58
+ /** Passed when a retry is about to happen */
59
+ interface RetryEvent extends RequestContext {
60
+ /** Which retry attempt (1, 2, 3...) */
61
+ attempt: number;
62
+ /** Delay before retry in milliseconds */
63
+ delayMs: number;
64
+ /** The error that triggered the retry */
65
+ error: Error;
66
+ }
67
+ /** Passed when session token changes */
68
+ interface SessionChangeEvent {
69
+ /** Previous token (null if none) */
70
+ previousToken: string | null;
71
+ /** New token (null if cleared) */
72
+ newToken: string | null;
73
+ /** Source of the change */
74
+ source: "response" | "manual" | "clear";
75
+ }
76
+ /**
77
+ * Observability hooks configuration.
78
+ * All hooks are optional - only implement what you need.
79
+ */
80
+ interface ObservabilityHooks {
81
+ /** Called when a request is about to be sent */
82
+ onRequestStart?: (event: RequestStartEvent) => void;
83
+ /** Called when a request completes successfully */
84
+ onRequestSuccess?: (event: RequestSuccessEvent) => void;
85
+ /** Called when a request fails (after all retries exhausted) */
86
+ onRequestError?: (event: RequestErrorEvent) => void;
87
+ /** Called before each retry attempt */
88
+ onRetry?: (event: RetryEvent) => void;
89
+ /** Called when session token changes */
90
+ onSessionChange?: (event: SessionChangeEvent) => void;
91
+ }
92
+
1
93
  /** Decimal value represented as string for precision */
2
94
  type Money = string;
3
95
  /** Supported currencies */
@@ -2839,6 +2931,8 @@ interface CimplifyConfig {
2839
2931
  maxRetries?: number;
2840
2932
  /** Base delay between retries in milliseconds (default: 1000) */
2841
2933
  retryDelay?: number;
2934
+ /** Observability hooks for logging, metrics, and tracing */
2935
+ hooks?: ObservabilityHooks;
2842
2936
  }
2843
2937
  declare class CimplifyClient {
2844
2938
  private baseUrl;
@@ -2849,6 +2943,9 @@ declare class CimplifyClient {
2849
2943
  private timeout;
2850
2944
  private maxRetries;
2851
2945
  private retryDelay;
2946
+ private hooks;
2947
+ /** In-flight request deduplication map */
2948
+ private inflightRequests;
2852
2949
  private _catalogue?;
2853
2950
  private _cart?;
2854
2951
  private _checkout?;
@@ -2868,11 +2965,29 @@ declare class CimplifyClient {
2868
2965
  private getHeaders;
2869
2966
  private updateSessionFromResponse;
2870
2967
  /**
2871
- * Resilient fetch with timeout and automatic retries for network errors.
2968
+ * Resilient fetch with timeout, automatic retries, and observability hooks.
2872
2969
  * Uses exponential backoff: 1s, 2s, 4s between retries.
2873
2970
  */
2874
2971
  private resilientFetch;
2972
+ /**
2973
+ * Generate a deduplication key for a request.
2974
+ * Same query + variables = same key = deduplicated.
2975
+ */
2976
+ private getDedupeKey;
2977
+ /**
2978
+ * Execute a request with deduplication.
2979
+ * If an identical request is already in-flight, return the same promise.
2980
+ * This prevents redundant network calls when multiple components request the same data.
2981
+ */
2982
+ private deduplicatedRequest;
2983
+ /**
2984
+ * Execute a query with deduplication.
2985
+ * Multiple identical queries made simultaneously will share a single network request.
2986
+ */
2875
2987
  query<T = unknown>(query: string, variables?: Record<string, unknown>): Promise<T>;
2988
+ /**
2989
+ * Execute a mutation. NOT deduplicated - mutations have side effects.
2990
+ */
2876
2991
  call<T = unknown>(method: string, args?: unknown): Promise<T>;
2877
2992
  get<T = unknown>(path: string): Promise<T>;
2878
2993
  post<T = unknown>(path: string, body?: unknown): Promise<T>;
@@ -3415,4 +3530,4 @@ interface ApiResponse<T> {
3415
3530
  metadata?: ResponseMetadata;
3416
3531
  }
3417
3532
 
3418
- export { AUTHORIZATION_TYPE, AUTH_MUTATION, type AddOn, type AddOnDetails, type AddOnGroupDetails, type AddOnOption, type AddOnOptionDetails, type AddOnOptionPrice, type AddOnWithOptions, type AddToCartInput, type AddressData, type AdjustmentType, type AmountToPay, type ApiError, type ApiResponse, type AppliedDiscount, type AuthResponse, AuthService, type AuthStatus, type AuthorizationType, type AvailabilityCheck, type AvailabilityResult, type AvailableSlot, type BenefitType, type Booking, type BookingRequirementOverride, type BookingStatus, type BookingWithDetails, type BufferTimes, type Bundle, type BundleComponentData, type BundleComponentInfo, type BundlePriceType, type BundleProduct, type BundleSelectionData, type BundleSelectionInput, type BundleStoredSelection, type BundleSummary, type BundleWithDetails, type Business, type BusinessHours, type BusinessPreferences, BusinessService, type BusinessSettings, type BusinessType, type BusinessWithLocations, CHECKOUT_MODE, CHECKOUT_MUTATION, CHECKOUT_STEP, CURRENCY_SYMBOLS, type CancelBookingInput, type CancelOrderInput, type CancellationPolicy, type Cart, type CartAddOn, type CartChannel, type CartItem, type CartItemDetails, CartOperations, type CartStatus, type CartSummary, type CartTotals, CatalogueQueries, type Category, type CategoryInfo, type CategorySummary, type ChangePasswordInput, type CheckSlotAvailabilityInput, type CheckoutAddressInfo, type CheckoutCustomerInfo, type CheckoutFormData, type CheckoutInput, type CheckoutMode, CheckoutService as CheckoutOperations, type CheckoutOrderType, type CheckoutPaymentMethod, type CheckoutResult, CheckoutService, type CheckoutStep, type ChosenPrice, CimplifyClient, type CimplifyConfig, CimplifyError, type Collection, type CollectionProduct, type CollectionSummary, type ComponentGroup, type ComponentGroupWithComponents, type ComponentPriceBreakdown, type ComponentSelectionInput, type ComponentSourceType, type Composite, type CompositeComponent, type CompositePriceBreakdown, type CompositePriceResult, type CompositePricingMode, type CompositeSelectionData, type ComponentSelectionInput as CompositeSelectionInput, type CompositeStoredSelection, type CompositeWithDetails, type ContactType, type CreateAddressInput, type CreateMobileMoneyInput, type Currency, type Customer, type CustomerAddress, type CustomerLinkPreferences, type CustomerMobileMoney, type CustomerServicePreferences, DEFAULT_COUNTRY, DEFAULT_CURRENCY, type DayAvailability, type DepositResult, type DepositType, type DeviceType, type DigitalProductType, type DiscountBreakdown, type DiscountDetails, type DisplayAddOn, type DisplayAddOnOption, type DisplayCart, type DisplayCartItem, type EnrollAndLinkOrderInput, type EnrollAndLinkOrderResult, type EnrollmentData, type Err, ErrorCode, type ErrorCodeType, type FeeBearerType, type FormatCompactOptions, type FormatPriceOptions, type FulfillmentLink, type FulfillmentStatus, type FulfillmentType, type GetAvailableSlotsInput, type GetOrdersOptions, type GetProductsOptions, type GroupPricingBehavior, type InitializePaymentResult, InventoryService, type InventorySummary, type InventoryType, type KitchenOrderItem, type KitchenOrderResult, LINK_MUTATION, LINK_QUERY, type LineConfiguration, type LineItem, type LineType, type LinkData, type LinkEnrollResult, LinkService, type LinkSession, type LinkStatusResult, type LiteBootstrap, LiteService, type Location, type LocationAppointment, type LocationProductPrice, type LocationStock, type LocationTaxBehavior, type LocationTaxOverrides, type LocationTimeProfile, type LocationWithDetails, MOBILE_MONEY_PROVIDER, MOBILE_MONEY_PROVIDERS, type MobileMoneyData, type MobileMoneyDetails, type MobileMoneyProvider, type Money, type MutationRequest, ORDER_MUTATION, ORDER_TYPE, type Ok, type Order, type OrderChannel, type OrderFilter, type OrderFulfillmentSummary, type OrderGroup, type OrderGroupDetails, type OrderGroupPayment, type OrderGroupPaymentState, type OrderGroupPaymentSummary, type OrderHistory, type OrderLineState, type OrderLineStatus, type OrderPaymentEvent, OrderQueries, type OrderSplitDetail, type OrderStatus, type OtpResult, PAYMENT_METHOD, PAYMENT_MUTATION, PAYMENT_STATE, PICKUP_TIME_TYPE, type Pagination, type PaginationParams, type ParsedPrice, type Payment, type PaymentErrorDetails, type PaymentMethod, type PaymentMethodType, type PaymentProcessingState, type PaymentProvider, type PaymentResponse, type PaymentState, type PaymentStatus, type PaymentStatusResponse, type PickupTime, type PickupTimeType, type Price, type PriceAdjustment, type PriceDecisionPath, type PriceEntryType, type PriceInfo, type PricePathTaxInfo, type PriceSource, type PricingOverrides, type Product, type ProductAddOn, type ProductAvailability, type ProductStock, type ProductTimeProfile, type ProductType, type ProductVariant, type ProductVariantValue, type ProductWithDetails, type ProductWithPrice, QueryBuilder, type QueryRequest, type RefundOrderInput, type ReminderMethod, type ReminderSettings, type RequestOtpInput, type RescheduleBookingInput, type ResourceAssignment, type ResourceAvailabilityException, type ResourceAvailabilityRule, type ResourceType, type ResponseMetadata, type Result, type RevokeAllSessionsResult, type RevokeSessionResult, type Room, type SalesChannel, type SchedulingMetadata, type SchedulingResult, SchedulingService, type SearchOptions, type SelectedAddOnOption, type Service, type ServiceAvailabilityException, type ServiceAvailabilityParams, type ServiceAvailabilityResult, type ServiceAvailabilityRule, type ServiceCharge, type ServiceNotes, type ServiceScheduleRequest, type ServiceStaffRequirement, type ServiceStatus, type ServiceWithStaff, type Staff, type StaffAssignment, type StaffAvailabilityException, type StaffAvailabilityRule, type StaffBookingProfile, type StaffRole, type StaffScheduleItem, type Stock, type StockLevel, type StockOwnershipType, type StockStatus, type StorefrontBootstrap, type SubmitAuthorizationInput, type Table, type TableInfo, type TaxComponent, type TaxInfo, type TaxPathComponent, type TimeRange, type TimeRanges, type TimeSlot, type UICart, type UICartBusiness, type UICartCustomer, type UICartLocation, type UICartPricing, type UpdateAddressInput, type UpdateCartItemInput, type UpdateOrderStatusInput, type UpdateProfileInput, type VariantAxis, type VariantAxisSelection, type VariantAxisValue, type VariantAxisWithValues, type VariantDetails, type VariantDetailsDTO, type VariantDisplayAttribute, type VariantLocationAvailability, type VariantStock, type VariantStrategy, type VerifyOtpInput, categorizePaymentError, combine, combineObject, createCimplifyClient, detectMobileMoneyProvider, err, extractPriceInfo, flatMap, formatMoney, formatNumberCompact, formatPrice, formatPriceAdjustment, formatPriceCompact, formatProductPrice, fromPromise, generateIdempotencyKey, getBasePrice, getCurrencySymbol, getDiscountPercentage, getDisplayPrice, getMarkupPercentage, getOrElse, getProductCurrency, isCimplifyError, isErr, isOk, isOnSale, isRetryableError, mapError, mapResult, normalizePaymentResponse, normalizeStatusResponse, ok, parsePrice, parsePricePath, parsedPriceToPriceInfo, query, toNullable, tryCatch, unwrap };
3533
+ export { AUTHORIZATION_TYPE, AUTH_MUTATION, type AddOn, type AddOnDetails, type AddOnGroupDetails, type AddOnOption, type AddOnOptionDetails, type AddOnOptionPrice, type AddOnWithOptions, type AddToCartInput, type AddressData, type AdjustmentType, type AmountToPay, type ApiError, type ApiResponse, type AppliedDiscount, type AuthResponse, AuthService, type AuthStatus, type AuthorizationType, type AvailabilityCheck, type AvailabilityResult, type AvailableSlot, type BenefitType, type Booking, type BookingRequirementOverride, type BookingStatus, type BookingWithDetails, type BufferTimes, type Bundle, type BundleComponentData, type BundleComponentInfo, type BundlePriceType, type BundleProduct, type BundleSelectionData, type BundleSelectionInput, type BundleStoredSelection, type BundleSummary, type BundleWithDetails, type Business, type BusinessHours, type BusinessPreferences, BusinessService, type BusinessSettings, type BusinessType, type BusinessWithLocations, CHECKOUT_MODE, CHECKOUT_MUTATION, CHECKOUT_STEP, CURRENCY_SYMBOLS, type CancelBookingInput, type CancelOrderInput, type CancellationPolicy, type Cart, type CartAddOn, type CartChannel, type CartItem, type CartItemDetails, CartOperations, type CartStatus, type CartSummary, type CartTotals, CatalogueQueries, type Category, type CategoryInfo, type CategorySummary, type ChangePasswordInput, type CheckSlotAvailabilityInput, type CheckoutAddressInfo, type CheckoutCustomerInfo, type CheckoutFormData, type CheckoutInput, type CheckoutMode, CheckoutService as CheckoutOperations, type CheckoutOrderType, type CheckoutPaymentMethod, type CheckoutResult, CheckoutService, type CheckoutStep, type ChosenPrice, CimplifyClient, type CimplifyConfig, CimplifyError, type Collection, type CollectionProduct, type CollectionSummary, type ComponentGroup, type ComponentGroupWithComponents, type ComponentPriceBreakdown, type ComponentSelectionInput, type ComponentSourceType, type Composite, type CompositeComponent, type CompositePriceBreakdown, type CompositePriceResult, type CompositePricingMode, type CompositeSelectionData, type ComponentSelectionInput as CompositeSelectionInput, type CompositeStoredSelection, type CompositeWithDetails, type ContactType, type CreateAddressInput, type CreateMobileMoneyInput, type Currency, type Customer, type CustomerAddress, type CustomerLinkPreferences, type CustomerMobileMoney, type CustomerServicePreferences, DEFAULT_COUNTRY, DEFAULT_CURRENCY, type DayAvailability, type DepositResult, type DepositType, type DeviceType, type DigitalProductType, type DiscountBreakdown, type DiscountDetails, type DisplayAddOn, type DisplayAddOnOption, type DisplayCart, type DisplayCartItem, type EnrollAndLinkOrderInput, type EnrollAndLinkOrderResult, type EnrollmentData, type Err, ErrorCode, type ErrorCodeType, type FeeBearerType, type FormatCompactOptions, type FormatPriceOptions, type FulfillmentLink, type FulfillmentStatus, type FulfillmentType, type GetAvailableSlotsInput, type GetOrdersOptions, type GetProductsOptions, type GroupPricingBehavior, type InitializePaymentResult, InventoryService, type InventorySummary, type InventoryType, type KitchenOrderItem, type KitchenOrderResult, LINK_MUTATION, LINK_QUERY, type LineConfiguration, type LineItem, type LineType, type LinkData, type LinkEnrollResult, LinkService, type LinkSession, type LinkStatusResult, type LiteBootstrap, LiteService, type Location, type LocationAppointment, type LocationProductPrice, type LocationStock, type LocationTaxBehavior, type LocationTaxOverrides, type LocationTimeProfile, type LocationWithDetails, MOBILE_MONEY_PROVIDER, MOBILE_MONEY_PROVIDERS, type MobileMoneyData, type MobileMoneyDetails, type MobileMoneyProvider, type Money, type MutationRequest, ORDER_MUTATION, ORDER_TYPE, type ObservabilityHooks, type Ok, type Order, type OrderChannel, type OrderFilter, type OrderFulfillmentSummary, type OrderGroup, type OrderGroupDetails, type OrderGroupPayment, type OrderGroupPaymentState, type OrderGroupPaymentSummary, type OrderHistory, type OrderLineState, type OrderLineStatus, type OrderPaymentEvent, OrderQueries, type OrderSplitDetail, type OrderStatus, type OtpResult, PAYMENT_METHOD, PAYMENT_MUTATION, PAYMENT_STATE, PICKUP_TIME_TYPE, type Pagination, type PaginationParams, type ParsedPrice, type Payment, type PaymentErrorDetails, type PaymentMethod, type PaymentMethodType, type PaymentProcessingState, type PaymentProvider, type PaymentResponse, type PaymentState, type PaymentStatus, type PaymentStatusResponse, type PickupTime, type PickupTimeType, type Price, type PriceAdjustment, type PriceDecisionPath, type PriceEntryType, type PriceInfo, type PricePathTaxInfo, type PriceSource, type PricingOverrides, type Product, type ProductAddOn, type ProductAvailability, type ProductStock, type ProductTimeProfile, type ProductType, type ProductVariant, type ProductVariantValue, type ProductWithDetails, type ProductWithPrice, QueryBuilder, type QueryRequest, type RefundOrderInput, type ReminderMethod, type ReminderSettings, type RequestContext, type RequestErrorEvent, type RequestOtpInput, type RequestStartEvent, type RequestSuccessEvent, type RescheduleBookingInput, type ResourceAssignment, type ResourceAvailabilityException, type ResourceAvailabilityRule, type ResourceType, type ResponseMetadata, type Result, type RetryEvent, type RevokeAllSessionsResult, type RevokeSessionResult, type Room, type SalesChannel, type SchedulingMetadata, type SchedulingResult, SchedulingService, type SearchOptions, type SelectedAddOnOption, type Service, type ServiceAvailabilityException, type ServiceAvailabilityParams, type ServiceAvailabilityResult, type ServiceAvailabilityRule, type ServiceCharge, type ServiceNotes, type ServiceScheduleRequest, type ServiceStaffRequirement, type ServiceStatus, type ServiceWithStaff, type SessionChangeEvent, type Staff, type StaffAssignment, type StaffAvailabilityException, type StaffAvailabilityRule, type StaffBookingProfile, type StaffRole, type StaffScheduleItem, type Stock, type StockLevel, type StockOwnershipType, type StockStatus, type StorefrontBootstrap, type SubmitAuthorizationInput, type Table, type TableInfo, type TaxComponent, type TaxInfo, type TaxPathComponent, type TimeRange, type TimeRanges, type TimeSlot, type UICart, type UICartBusiness, type UICartCustomer, type UICartLocation, type UICartPricing, type UpdateAddressInput, type UpdateCartItemInput, type UpdateOrderStatusInput, type UpdateProfileInput, type VariantAxis, type VariantAxisSelection, type VariantAxisValue, type VariantAxisWithValues, type VariantDetails, type VariantDetailsDTO, type VariantDisplayAttribute, type VariantLocationAvailability, type VariantStock, type VariantStrategy, type VerifyOtpInput, categorizePaymentError, combine, combineObject, createCimplifyClient, detectMobileMoneyProvider, err, extractPriceInfo, flatMap, formatMoney, formatNumberCompact, formatPrice, formatPriceAdjustment, formatPriceCompact, formatProductPrice, fromPromise, generateIdempotencyKey, getBasePrice, getCurrencySymbol, getDiscountPercentage, getDisplayPrice, getMarkupPercentage, getOrElse, getProductCurrency, isCimplifyError, isErr, isOk, isOnSale, isRetryableError, mapError, mapResult, normalizePaymentResponse, normalizeStatusResponse, ok, parsePrice, parsePricePath, parsedPriceToPriceInfo, query, toNullable, tryCatch, unwrap };
package/dist/index.js CHANGED
@@ -1469,6 +1469,8 @@ function deriveUrls() {
1469
1469
  var CimplifyClient = class {
1470
1470
  constructor(config = {}) {
1471
1471
  this.sessionToken = null;
1472
+ /** In-flight request deduplication map */
1473
+ this.inflightRequests = /* @__PURE__ */ new Map();
1472
1474
  this.publicKey = config.publicKey || "";
1473
1475
  const urls = deriveUrls();
1474
1476
  this.baseUrl = urls.baseUrl;
@@ -1477,18 +1479,31 @@ var CimplifyClient = class {
1477
1479
  this.timeout = config.timeout ?? DEFAULT_TIMEOUT_MS;
1478
1480
  this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
1479
1481
  this.retryDelay = config.retryDelay ?? DEFAULT_RETRY_DELAY_MS;
1482
+ this.hooks = config.hooks ?? {};
1480
1483
  this.sessionToken = this.loadSessionToken();
1481
1484
  }
1482
1485
  getSessionToken() {
1483
1486
  return this.sessionToken;
1484
1487
  }
1485
1488
  setSessionToken(token) {
1489
+ const previous = this.sessionToken;
1486
1490
  this.sessionToken = token;
1487
1491
  this.saveSessionToken(token);
1492
+ this.hooks.onSessionChange?.({
1493
+ previousToken: previous,
1494
+ newToken: token,
1495
+ source: "manual"
1496
+ });
1488
1497
  }
1489
1498
  clearSession() {
1499
+ const previous = this.sessionToken;
1490
1500
  this.sessionToken = null;
1491
1501
  this.saveSessionToken(null);
1502
+ this.hooks.onSessionChange?.({
1503
+ previousToken: previous,
1504
+ newToken: null,
1505
+ source: "clear"
1506
+ });
1492
1507
  }
1493
1508
  loadSessionToken() {
1494
1509
  if (typeof window !== "undefined" && window.localStorage) {
@@ -1518,15 +1533,33 @@ var CimplifyClient = class {
1518
1533
  updateSessionFromResponse(response) {
1519
1534
  const newToken = response.headers.get(SESSION_TOKEN_HEADER);
1520
1535
  if (newToken && newToken !== this.sessionToken) {
1536
+ const previous = this.sessionToken;
1521
1537
  this.sessionToken = newToken;
1522
1538
  this.saveSessionToken(newToken);
1539
+ this.hooks.onSessionChange?.({
1540
+ previousToken: previous,
1541
+ newToken,
1542
+ source: "response"
1543
+ });
1523
1544
  }
1524
1545
  }
1525
1546
  /**
1526
- * Resilient fetch with timeout and automatic retries for network errors.
1547
+ * Resilient fetch with timeout, automatic retries, and observability hooks.
1527
1548
  * Uses exponential backoff: 1s, 2s, 4s between retries.
1528
1549
  */
1529
1550
  async resilientFetch(url, options) {
1551
+ const method = options.method || "GET";
1552
+ const path = url.replace(this.baseUrl, "").replace(this.linkApiUrl, "");
1553
+ const startTime = Date.now();
1554
+ let retryCount = 0;
1555
+ const context = {
1556
+ method,
1557
+ path,
1558
+ url,
1559
+ body: options.body ? JSON.parse(options.body) : void 0,
1560
+ startTime
1561
+ };
1562
+ this.hooks.onRequestStart?.(context);
1530
1563
  let lastError;
1531
1564
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
1532
1565
  try {
@@ -1538,39 +1571,113 @@ var CimplifyClient = class {
1538
1571
  });
1539
1572
  clearTimeout(timeoutId);
1540
1573
  if (response.ok || response.status >= 400 && response.status < 500) {
1574
+ this.hooks.onRequestSuccess?.({
1575
+ ...context,
1576
+ status: response.status,
1577
+ durationMs: Date.now() - startTime
1578
+ });
1541
1579
  return response;
1542
1580
  }
1543
1581
  if (response.status >= 500 && attempt < this.maxRetries) {
1582
+ retryCount++;
1544
1583
  const delay = this.retryDelay * Math.pow(2, attempt);
1584
+ this.hooks.onRetry?.({
1585
+ ...context,
1586
+ attempt: retryCount,
1587
+ delayMs: delay,
1588
+ error: new Error(`Server error: ${response.status}`)
1589
+ });
1545
1590
  await sleep(delay);
1546
1591
  continue;
1547
1592
  }
1593
+ this.hooks.onRequestSuccess?.({
1594
+ ...context,
1595
+ status: response.status,
1596
+ durationMs: Date.now() - startTime
1597
+ });
1548
1598
  return response;
1549
1599
  } catch (error) {
1550
1600
  lastError = error;
1551
- if (!isRetryable(error) || attempt >= this.maxRetries) {
1552
- throw toNetworkError(error);
1601
+ const networkError = toNetworkError(error);
1602
+ const errorRetryable = isRetryable(error);
1603
+ if (!errorRetryable || attempt >= this.maxRetries) {
1604
+ this.hooks.onRequestError?.({
1605
+ ...context,
1606
+ error: networkError,
1607
+ durationMs: Date.now() - startTime,
1608
+ retryCount,
1609
+ retryable: errorRetryable
1610
+ });
1611
+ throw networkError;
1553
1612
  }
1613
+ retryCount++;
1554
1614
  const delay = this.retryDelay * Math.pow(2, attempt);
1615
+ this.hooks.onRetry?.({
1616
+ ...context,
1617
+ attempt: retryCount,
1618
+ delayMs: delay,
1619
+ error: networkError
1620
+ });
1555
1621
  await sleep(delay);
1556
1622
  }
1557
1623
  }
1558
- throw toNetworkError(lastError);
1624
+ const finalError = toNetworkError(lastError);
1625
+ this.hooks.onRequestError?.({
1626
+ ...context,
1627
+ error: finalError,
1628
+ durationMs: Date.now() - startTime,
1629
+ retryCount,
1630
+ retryable: false
1631
+ });
1632
+ throw finalError;
1559
1633
  }
1634
+ /**
1635
+ * Generate a deduplication key for a request.
1636
+ * Same query + variables = same key = deduplicated.
1637
+ */
1638
+ getDedupeKey(type, payload) {
1639
+ return `${type}:${JSON.stringify(payload)}`;
1640
+ }
1641
+ /**
1642
+ * Execute a request with deduplication.
1643
+ * If an identical request is already in-flight, return the same promise.
1644
+ * This prevents redundant network calls when multiple components request the same data.
1645
+ */
1646
+ async deduplicatedRequest(key, requestFn) {
1647
+ const existing = this.inflightRequests.get(key);
1648
+ if (existing) {
1649
+ return existing;
1650
+ }
1651
+ const request = requestFn().finally(() => {
1652
+ this.inflightRequests.delete(key);
1653
+ });
1654
+ this.inflightRequests.set(key, request);
1655
+ return request;
1656
+ }
1657
+ /**
1658
+ * Execute a query with deduplication.
1659
+ * Multiple identical queries made simultaneously will share a single network request.
1660
+ */
1560
1661
  async query(query2, variables) {
1561
1662
  const body = { query: query2 };
1562
1663
  if (variables) {
1563
1664
  body.variables = variables;
1564
1665
  }
1565
- const response = await this.resilientFetch(`${this.baseUrl}/api/q`, {
1566
- method: "POST",
1567
- credentials: this.credentials,
1568
- headers: this.getHeaders(),
1569
- body: JSON.stringify(body)
1666
+ const key = this.getDedupeKey("query", body);
1667
+ return this.deduplicatedRequest(key, async () => {
1668
+ const response = await this.resilientFetch(`${this.baseUrl}/api/q`, {
1669
+ method: "POST",
1670
+ credentials: this.credentials,
1671
+ headers: this.getHeaders(),
1672
+ body: JSON.stringify(body)
1673
+ });
1674
+ this.updateSessionFromResponse(response);
1675
+ return this.handleResponse(response);
1570
1676
  });
1571
- this.updateSessionFromResponse(response);
1572
- return this.handleResponse(response);
1573
1677
  }
1678
+ /**
1679
+ * Execute a mutation. NOT deduplicated - mutations have side effects.
1680
+ */
1574
1681
  async call(method, args) {
1575
1682
  const body = {
1576
1683
  method,
@@ -1586,13 +1693,16 @@ var CimplifyClient = class {
1586
1693
  return this.handleResponse(response);
1587
1694
  }
1588
1695
  async get(path) {
1589
- const response = await this.resilientFetch(`${this.baseUrl}${path}`, {
1590
- method: "GET",
1591
- credentials: this.credentials,
1592
- headers: this.getHeaders()
1696
+ const key = this.getDedupeKey("get", path);
1697
+ return this.deduplicatedRequest(key, async () => {
1698
+ const response = await this.resilientFetch(`${this.baseUrl}${path}`, {
1699
+ method: "GET",
1700
+ credentials: this.credentials,
1701
+ headers: this.getHeaders()
1702
+ });
1703
+ this.updateSessionFromResponse(response);
1704
+ return this.handleRestResponse(response);
1593
1705
  });
1594
- this.updateSessionFromResponse(response);
1595
- return this.handleRestResponse(response);
1596
1706
  }
1597
1707
  async post(path, body) {
1598
1708
  const response = await this.resilientFetch(`${this.baseUrl}${path}`, {
@@ -1614,13 +1724,16 @@ var CimplifyClient = class {
1614
1724
  return this.handleRestResponse(response);
1615
1725
  }
1616
1726
  async linkGet(path) {
1617
- const response = await this.resilientFetch(`${this.linkApiUrl}${path}`, {
1618
- method: "GET",
1619
- credentials: this.credentials,
1620
- headers: this.getHeaders()
1727
+ const key = this.getDedupeKey("linkGet", path);
1728
+ return this.deduplicatedRequest(key, async () => {
1729
+ const response = await this.resilientFetch(`${this.linkApiUrl}${path}`, {
1730
+ method: "GET",
1731
+ credentials: this.credentials,
1732
+ headers: this.getHeaders()
1733
+ });
1734
+ this.updateSessionFromResponse(response);
1735
+ return this.handleRestResponse(response);
1621
1736
  });
1622
- this.updateSessionFromResponse(response);
1623
- return this.handleRestResponse(response);
1624
1737
  }
1625
1738
  async linkPost(path, body) {
1626
1739
  const response = await this.resilientFetch(`${this.linkApiUrl}${path}`, {
package/dist/index.mjs CHANGED
@@ -1467,6 +1467,8 @@ function deriveUrls() {
1467
1467
  var CimplifyClient = class {
1468
1468
  constructor(config = {}) {
1469
1469
  this.sessionToken = null;
1470
+ /** In-flight request deduplication map */
1471
+ this.inflightRequests = /* @__PURE__ */ new Map();
1470
1472
  this.publicKey = config.publicKey || "";
1471
1473
  const urls = deriveUrls();
1472
1474
  this.baseUrl = urls.baseUrl;
@@ -1475,18 +1477,31 @@ var CimplifyClient = class {
1475
1477
  this.timeout = config.timeout ?? DEFAULT_TIMEOUT_MS;
1476
1478
  this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
1477
1479
  this.retryDelay = config.retryDelay ?? DEFAULT_RETRY_DELAY_MS;
1480
+ this.hooks = config.hooks ?? {};
1478
1481
  this.sessionToken = this.loadSessionToken();
1479
1482
  }
1480
1483
  getSessionToken() {
1481
1484
  return this.sessionToken;
1482
1485
  }
1483
1486
  setSessionToken(token) {
1487
+ const previous = this.sessionToken;
1484
1488
  this.sessionToken = token;
1485
1489
  this.saveSessionToken(token);
1490
+ this.hooks.onSessionChange?.({
1491
+ previousToken: previous,
1492
+ newToken: token,
1493
+ source: "manual"
1494
+ });
1486
1495
  }
1487
1496
  clearSession() {
1497
+ const previous = this.sessionToken;
1488
1498
  this.sessionToken = null;
1489
1499
  this.saveSessionToken(null);
1500
+ this.hooks.onSessionChange?.({
1501
+ previousToken: previous,
1502
+ newToken: null,
1503
+ source: "clear"
1504
+ });
1490
1505
  }
1491
1506
  loadSessionToken() {
1492
1507
  if (typeof window !== "undefined" && window.localStorage) {
@@ -1516,15 +1531,33 @@ var CimplifyClient = class {
1516
1531
  updateSessionFromResponse(response) {
1517
1532
  const newToken = response.headers.get(SESSION_TOKEN_HEADER);
1518
1533
  if (newToken && newToken !== this.sessionToken) {
1534
+ const previous = this.sessionToken;
1519
1535
  this.sessionToken = newToken;
1520
1536
  this.saveSessionToken(newToken);
1537
+ this.hooks.onSessionChange?.({
1538
+ previousToken: previous,
1539
+ newToken,
1540
+ source: "response"
1541
+ });
1521
1542
  }
1522
1543
  }
1523
1544
  /**
1524
- * Resilient fetch with timeout and automatic retries for network errors.
1545
+ * Resilient fetch with timeout, automatic retries, and observability hooks.
1525
1546
  * Uses exponential backoff: 1s, 2s, 4s between retries.
1526
1547
  */
1527
1548
  async resilientFetch(url, options) {
1549
+ const method = options.method || "GET";
1550
+ const path = url.replace(this.baseUrl, "").replace(this.linkApiUrl, "");
1551
+ const startTime = Date.now();
1552
+ let retryCount = 0;
1553
+ const context = {
1554
+ method,
1555
+ path,
1556
+ url,
1557
+ body: options.body ? JSON.parse(options.body) : void 0,
1558
+ startTime
1559
+ };
1560
+ this.hooks.onRequestStart?.(context);
1528
1561
  let lastError;
1529
1562
  for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
1530
1563
  try {
@@ -1536,39 +1569,113 @@ var CimplifyClient = class {
1536
1569
  });
1537
1570
  clearTimeout(timeoutId);
1538
1571
  if (response.ok || response.status >= 400 && response.status < 500) {
1572
+ this.hooks.onRequestSuccess?.({
1573
+ ...context,
1574
+ status: response.status,
1575
+ durationMs: Date.now() - startTime
1576
+ });
1539
1577
  return response;
1540
1578
  }
1541
1579
  if (response.status >= 500 && attempt < this.maxRetries) {
1580
+ retryCount++;
1542
1581
  const delay = this.retryDelay * Math.pow(2, attempt);
1582
+ this.hooks.onRetry?.({
1583
+ ...context,
1584
+ attempt: retryCount,
1585
+ delayMs: delay,
1586
+ error: new Error(`Server error: ${response.status}`)
1587
+ });
1543
1588
  await sleep(delay);
1544
1589
  continue;
1545
1590
  }
1591
+ this.hooks.onRequestSuccess?.({
1592
+ ...context,
1593
+ status: response.status,
1594
+ durationMs: Date.now() - startTime
1595
+ });
1546
1596
  return response;
1547
1597
  } catch (error) {
1548
1598
  lastError = error;
1549
- if (!isRetryable(error) || attempt >= this.maxRetries) {
1550
- throw toNetworkError(error);
1599
+ const networkError = toNetworkError(error);
1600
+ const errorRetryable = isRetryable(error);
1601
+ if (!errorRetryable || attempt >= this.maxRetries) {
1602
+ this.hooks.onRequestError?.({
1603
+ ...context,
1604
+ error: networkError,
1605
+ durationMs: Date.now() - startTime,
1606
+ retryCount,
1607
+ retryable: errorRetryable
1608
+ });
1609
+ throw networkError;
1551
1610
  }
1611
+ retryCount++;
1552
1612
  const delay = this.retryDelay * Math.pow(2, attempt);
1613
+ this.hooks.onRetry?.({
1614
+ ...context,
1615
+ attempt: retryCount,
1616
+ delayMs: delay,
1617
+ error: networkError
1618
+ });
1553
1619
  await sleep(delay);
1554
1620
  }
1555
1621
  }
1556
- throw toNetworkError(lastError);
1622
+ const finalError = toNetworkError(lastError);
1623
+ this.hooks.onRequestError?.({
1624
+ ...context,
1625
+ error: finalError,
1626
+ durationMs: Date.now() - startTime,
1627
+ retryCount,
1628
+ retryable: false
1629
+ });
1630
+ throw finalError;
1557
1631
  }
1632
+ /**
1633
+ * Generate a deduplication key for a request.
1634
+ * Same query + variables = same key = deduplicated.
1635
+ */
1636
+ getDedupeKey(type, payload) {
1637
+ return `${type}:${JSON.stringify(payload)}`;
1638
+ }
1639
+ /**
1640
+ * Execute a request with deduplication.
1641
+ * If an identical request is already in-flight, return the same promise.
1642
+ * This prevents redundant network calls when multiple components request the same data.
1643
+ */
1644
+ async deduplicatedRequest(key, requestFn) {
1645
+ const existing = this.inflightRequests.get(key);
1646
+ if (existing) {
1647
+ return existing;
1648
+ }
1649
+ const request = requestFn().finally(() => {
1650
+ this.inflightRequests.delete(key);
1651
+ });
1652
+ this.inflightRequests.set(key, request);
1653
+ return request;
1654
+ }
1655
+ /**
1656
+ * Execute a query with deduplication.
1657
+ * Multiple identical queries made simultaneously will share a single network request.
1658
+ */
1558
1659
  async query(query2, variables) {
1559
1660
  const body = { query: query2 };
1560
1661
  if (variables) {
1561
1662
  body.variables = variables;
1562
1663
  }
1563
- const response = await this.resilientFetch(`${this.baseUrl}/api/q`, {
1564
- method: "POST",
1565
- credentials: this.credentials,
1566
- headers: this.getHeaders(),
1567
- body: JSON.stringify(body)
1664
+ const key = this.getDedupeKey("query", body);
1665
+ return this.deduplicatedRequest(key, async () => {
1666
+ const response = await this.resilientFetch(`${this.baseUrl}/api/q`, {
1667
+ method: "POST",
1668
+ credentials: this.credentials,
1669
+ headers: this.getHeaders(),
1670
+ body: JSON.stringify(body)
1671
+ });
1672
+ this.updateSessionFromResponse(response);
1673
+ return this.handleResponse(response);
1568
1674
  });
1569
- this.updateSessionFromResponse(response);
1570
- return this.handleResponse(response);
1571
1675
  }
1676
+ /**
1677
+ * Execute a mutation. NOT deduplicated - mutations have side effects.
1678
+ */
1572
1679
  async call(method, args) {
1573
1680
  const body = {
1574
1681
  method,
@@ -1584,13 +1691,16 @@ var CimplifyClient = class {
1584
1691
  return this.handleResponse(response);
1585
1692
  }
1586
1693
  async get(path) {
1587
- const response = await this.resilientFetch(`${this.baseUrl}${path}`, {
1588
- method: "GET",
1589
- credentials: this.credentials,
1590
- headers: this.getHeaders()
1694
+ const key = this.getDedupeKey("get", path);
1695
+ return this.deduplicatedRequest(key, async () => {
1696
+ const response = await this.resilientFetch(`${this.baseUrl}${path}`, {
1697
+ method: "GET",
1698
+ credentials: this.credentials,
1699
+ headers: this.getHeaders()
1700
+ });
1701
+ this.updateSessionFromResponse(response);
1702
+ return this.handleRestResponse(response);
1591
1703
  });
1592
- this.updateSessionFromResponse(response);
1593
- return this.handleRestResponse(response);
1594
1704
  }
1595
1705
  async post(path, body) {
1596
1706
  const response = await this.resilientFetch(`${this.baseUrl}${path}`, {
@@ -1612,13 +1722,16 @@ var CimplifyClient = class {
1612
1722
  return this.handleRestResponse(response);
1613
1723
  }
1614
1724
  async linkGet(path) {
1615
- const response = await this.resilientFetch(`${this.linkApiUrl}${path}`, {
1616
- method: "GET",
1617
- credentials: this.credentials,
1618
- headers: this.getHeaders()
1725
+ const key = this.getDedupeKey("linkGet", path);
1726
+ return this.deduplicatedRequest(key, async () => {
1727
+ const response = await this.resilientFetch(`${this.linkApiUrl}${path}`, {
1728
+ method: "GET",
1729
+ credentials: this.credentials,
1730
+ headers: this.getHeaders()
1731
+ });
1732
+ this.updateSessionFromResponse(response);
1733
+ return this.handleRestResponse(response);
1619
1734
  });
1620
- this.updateSessionFromResponse(response);
1621
- return this.handleRestResponse(response);
1622
1735
  }
1623
1736
  async linkPost(path, body) {
1624
1737
  const response = await this.resilientFetch(`${this.linkApiUrl}${path}`, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cimplify/sdk",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "Cimplify Commerce SDK for storefronts",
5
5
  "keywords": [
6
6
  "cimplify",