@forgecart/sdk 1.2.2 → 1.2.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.
@@ -4,7 +4,7 @@
4
4
  * This file was automatically generated and should not be manually edited.
5
5
  * To regenerate, run: npm run codegen:ts
6
6
  *
7
- * Generated at: 2025-12-15T07:13:55.745Z
7
+ * Generated at: 2025-12-17T10:24:29.306Z
8
8
  * Generator version: 1.0.0
9
9
  *
10
10
  * 🤖 Generated with ForgeCart SDK Generator
@@ -25,6 +25,30 @@ export interface SDKConfig {
25
25
  webSocketImpl?: unknown;
26
26
  /** Use HTTP only, skip WebSocket initialization (default: false) */
27
27
  httpOnly?: boolean;
28
+ /** Enable debug logging with request timing (default: false) */
29
+ debug?: boolean;
30
+ }
31
+ /**
32
+ * SDK Logger - Cross-platform logging with colors
33
+ * Respects debug flag for log/warn, errors are always visible
34
+ */
35
+ export declare class SDKLogger {
36
+ private debug;
37
+ private prefix;
38
+ constructor(debug?: boolean);
39
+ private isNode;
40
+ /**
41
+ * Debug log - only outputs when debug: true
42
+ */
43
+ log(message: string, ...args: unknown[]): void;
44
+ /**
45
+ * Warning log - only outputs when debug: true
46
+ */
47
+ warn(message: string, ...args: unknown[]): void;
48
+ /**
49
+ * Error log - always outputs (errors should always be visible)
50
+ */
51
+ error(message: string, ...args: unknown[]): void;
28
52
  }
29
53
  export type ActiveAdministratorQueryVariables = Types.ActiveAdministratorQueryVariables;
30
54
  export type ActiveAdministratorQuery = Types.ActiveAdministratorQuery;
@@ -989,7 +1013,12 @@ declare class BaseGraphQLClient {
989
1013
  private endpoint;
990
1014
  private wsEndpoint;
991
1015
  private config;
1016
+ private logger;
992
1017
  constructor(config: SDKConfig);
1018
+ /**
1019
+ * Extract operation name from GraphQL document
1020
+ */
1021
+ private getOperationName;
993
1022
  /**
994
1023
  * Execute a GraphQL query or mutation
995
1024
  * Uses WebSocket if available, falls back to HTTP
@@ -2570,6 +2599,7 @@ declare class ZoneOperations {
2570
2599
  */
2571
2600
  export declare class AdminNamespace {
2572
2601
  private client;
2602
+ private logger;
2573
2603
  readonly administrator: AdministratorOperations;
2574
2604
  readonly asset: AssetOperations;
2575
2605
  readonly blog: BlogOperations;
@@ -5,15 +5,66 @@
5
5
  * This file was automatically generated and should not be manually edited.
6
6
  * To regenerate, run: npm run codegen:ts
7
7
  *
8
- * Generated at: 2025-12-15T07:13:55.745Z
8
+ * Generated at: 2025-12-17T10:24:29.306Z
9
9
  * Generator version: 1.0.0
10
10
  *
11
11
  * 🤖 Generated with ForgeCart SDK Generator
12
12
  */
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.AdminNamespace = exports.AllEvents = exports.CatalogEvents = exports.PaymentEvents = exports.FulfillmentEvents = exports.CustomerEvents = exports.OrderEvents = exports.ProductEvents = void 0;
14
+ exports.AdminNamespace = exports.AllEvents = exports.CatalogEvents = exports.PaymentEvents = exports.FulfillmentEvents = exports.CustomerEvents = exports.OrderEvents = exports.ProductEvents = exports.SDKLogger = void 0;
15
15
  const graphql_request_1 = require("graphql-request");
16
16
  const graphql_ws_1 = require("graphql-ws");
17
+ /**
18
+ * SDK Logger - Cross-platform logging with colors
19
+ * Respects debug flag for log/warn, errors are always visible
20
+ */
21
+ class SDKLogger {
22
+ constructor(debug = false) {
23
+ this.prefix = '[ForgeCart SDK]';
24
+ this.debug = debug;
25
+ }
26
+ isNode() {
27
+ return typeof process !== 'undefined' && process.versions?.node != null;
28
+ }
29
+ /**
30
+ * Debug log - only outputs when debug: true
31
+ */
32
+ log(message, ...args) {
33
+ if (!this.debug)
34
+ return;
35
+ if (this.isNode()) {
36
+ console.log(`\x1b[36m${this.prefix}\x1b[0m ${message}`, ...args);
37
+ }
38
+ else {
39
+ console.log(`%c${this.prefix}%c ${message}`, 'color: #06b6d4; font-weight: bold', 'color: inherit', ...args);
40
+ }
41
+ }
42
+ /**
43
+ * Warning log - only outputs when debug: true
44
+ */
45
+ warn(message, ...args) {
46
+ if (!this.debug)
47
+ return;
48
+ if (this.isNode()) {
49
+ console.warn(`\x1b[33m${this.prefix}\x1b[0m ${message}`, ...args);
50
+ }
51
+ else {
52
+ console.warn(`%c${this.prefix}%c ${message}`, 'color: #eab308; font-weight: bold', 'color: inherit', ...args);
53
+ }
54
+ }
55
+ /**
56
+ * Error log - always outputs (errors should always be visible)
57
+ */
58
+ error(message, ...args) {
59
+ if (this.isNode()) {
60
+ console.error(`\x1b[31m${this.prefix}\x1b[0m ${message}`, ...args);
61
+ }
62
+ else {
63
+ console.error(`%c${this.prefix}%c ${message}`, 'color: #ef4444; font-weight: bold', 'color: inherit', ...args);
64
+ }
65
+ }
66
+ }
67
+ exports.SDKLogger = SDKLogger;
17
68
  const activeAdministratorDocument = `query activeAdministrator {
18
69
  activeAdministrator {
19
70
  id
@@ -6887,6 +6938,7 @@ class BaseGraphQLClient {
6887
6938
  this.endpoint = config.endpoint || 'https://api.forgecart.com/admin-api';
6888
6939
  this.wsEndpoint = config.wsEndpoint || 'wss://api.forgecart.com/admin-api';
6889
6940
  this.config = config;
6941
+ this.logger = new SDKLogger(config?.debug ?? false);
6890
6942
  // Custom fetch that captures session token from response headers
6891
6943
  const customFetch = async (url, options) => {
6892
6944
  const response = await fetch(url, options);
@@ -6914,26 +6966,50 @@ class BaseGraphQLClient {
6914
6966
  // Initialize WebSocket connection immediately
6915
6967
  this.initializeWebSocket();
6916
6968
  }
6969
+ /**
6970
+ * Extract operation name from GraphQL document
6971
+ */
6972
+ getOperationName(document) {
6973
+ const docString = typeof document === 'string' ? document : String(document);
6974
+ const match = docString.match(/(?:query|mutation|subscription)\s+(\w+)/);
6975
+ return match ? match[1] : 'UnknownOperation';
6976
+ }
6917
6977
  /**
6918
6978
  * Execute a GraphQL query or mutation
6919
6979
  * Uses WebSocket if available, falls back to HTTP
6920
6980
  */
6921
6981
  async request(document, variables) {
6982
+ const startTime = this.config.debug ? performance.now() : 0;
6983
+ const operationName = this.config.debug ? this.getOperationName(document) : '';
6984
+ let transport = 'HTTP';
6922
6985
  // Try WebSocket first if available
6923
6986
  if (this.wsClient && this.wsConnected) {
6924
6987
  try {
6925
- return await this.requestViaWebSocket(document, variables);
6988
+ transport = 'WebSocket';
6989
+ const result = await this.requestViaWebSocket(document, variables);
6990
+ if (this.config.debug) {
6991
+ this.logger.log(`${operationName} (${transport}) - ${(performance.now() - startTime).toFixed(0)}ms`);
6992
+ }
6993
+ return result;
6926
6994
  }
6927
6995
  catch (wsError) {
6928
- console.warn('WebSocket request failed, falling back to HTTP:', wsError);
6996
+ transport = 'WebSocket -> HTTP';
6997
+ this.logger.warn('WebSocket request failed, falling back to HTTP:', wsError);
6929
6998
  // Fall through to HTTP
6930
6999
  }
6931
7000
  }
6932
7001
  // Use HTTP as fallback or when WebSocket is not available
6933
7002
  try {
6934
- return await this.httpClient.request(document, variables);
7003
+ const result = await this.httpClient.request(document, variables);
7004
+ if (this.config.debug) {
7005
+ this.logger.log(`${operationName} (${transport}) - ${(performance.now() - startTime).toFixed(0)}ms`);
7006
+ }
7007
+ return result;
6935
7008
  }
6936
7009
  catch (error) {
7010
+ if (this.config.debug) {
7011
+ this.logger.log(`${operationName} (${transport}) - FAILED after ${(performance.now() - startTime).toFixed(0)}ms`);
7012
+ }
6937
7013
  this.handleError(error);
6938
7014
  throw error;
6939
7015
  }
@@ -6974,7 +7050,7 @@ class BaseGraphQLClient {
6974
7050
  * Handle GraphQL errors
6975
7051
  */
6976
7052
  handleError(error) {
6977
- console.error('GraphQL Error:', error);
7053
+ this.logger.error('GraphQL Error:', error);
6978
7054
  }
6979
7055
  /**
6980
7056
  * Get WebSocket implementation for current environment
@@ -6994,7 +7070,7 @@ class BaseGraphQLClient {
6994
7070
  return require('ws');
6995
7071
  }
6996
7072
  catch (e) {
6997
- console.warn('WebSocket not available. Install "ws" package for Node.js support or provide webSocketImpl in config.');
7073
+ this.logger.warn('WebSocket not available. Install "ws" package for Node.js support or provide webSocketImpl in config.');
6998
7074
  return undefined;
6999
7075
  }
7000
7076
  }
@@ -7013,7 +7089,7 @@ class BaseGraphQLClient {
7013
7089
  }
7014
7090
  const WebSocketImpl = this.getWebSocketImpl();
7015
7091
  if (!WebSocketImpl) {
7016
- console.warn('WebSocket not available. Will use HTTP fallback only.');
7092
+ this.logger.warn('WebSocket not available. Will use HTTP fallback only.');
7017
7093
  return;
7018
7094
  }
7019
7095
  this.wsInitializing = new Promise((resolve, reject) => {
@@ -7042,7 +7118,7 @@ class BaseGraphQLClient {
7042
7118
  return params;
7043
7119
  }
7044
7120
  catch (error) {
7045
- console.error('Error in connectionParams:', error);
7121
+ this.logger.error('Error in connectionParams:', error);
7046
7122
  return {};
7047
7123
  }
7048
7124
  },
@@ -7055,7 +7131,7 @@ class BaseGraphQLClient {
7055
7131
  on: {
7056
7132
  connected: () => {
7057
7133
  this.wsConnected = true;
7058
- console.log('WebSocket connected');
7134
+ this.logger.log('WebSocket connected');
7059
7135
  if (!isResolved) {
7060
7136
  isResolved = true;
7061
7137
  resolve();
@@ -7064,13 +7140,13 @@ class BaseGraphQLClient {
7064
7140
  closed: () => {
7065
7141
  this.wsConnected = false;
7066
7142
  if (!this.isDisposing) {
7067
- console.log('WebSocket disconnected');
7143
+ this.logger.log('WebSocket disconnected');
7068
7144
  }
7069
7145
  },
7070
7146
  error: (error) => {
7071
7147
  // Suppress errors during disposal - they're expected
7072
7148
  if (!this.isDisposing && this.wsConnected) {
7073
- console.error('WebSocket error:', error);
7149
+ this.logger.error('WebSocket error:', error);
7074
7150
  }
7075
7151
  // Don't fail the connection on errors - let retry logic handle it
7076
7152
  // Only log errors if we're connected (unexpected errors)
@@ -7082,7 +7158,7 @@ class BaseGraphQLClient {
7082
7158
  this.wsInitializing = null;
7083
7159
  }
7084
7160
  catch (error) {
7085
- console.error('Failed to create WebSocket client:', error);
7161
+ this.logger.error('Failed to create WebSocket client:', error);
7086
7162
  this.wsInitializing = null;
7087
7163
  // Resolve anyway to not block - we'll fallback to HTTP
7088
7164
  if (!isResolved) {
@@ -9417,6 +9493,7 @@ class AdminNamespace {
9417
9493
  this.retryCount = 0;
9418
9494
  this.abortController = null;
9419
9495
  this.client = new BaseGraphQLClient(config);
9496
+ this.logger = new SDKLogger(config?.debug ?? false);
9420
9497
  this.administrator = new AdministratorOperations(this.client);
9421
9498
  this.asset = new AssetOperations(this.client);
9422
9499
  this.blog = new BlogOperations(this.client);
@@ -9530,7 +9607,7 @@ class AdminNamespace {
9530
9607
  }
9531
9608
  catch (error) {
9532
9609
  if (this.isListening) {
9533
- console.error('Event subscription error:', error);
9610
+ this.logger.error('Event subscription error:', error);
9534
9611
  await this.handleReconnect();
9535
9612
  }
9536
9613
  }
@@ -9544,7 +9621,7 @@ class AdminNamespace {
9544
9621
  // Calculate delay with exponential backoff
9545
9622
  const delay = Math.min(baseDelayMs * Math.pow(backoffMultiplier, this.retryCount), maxDelayMs);
9546
9623
  this.retryCount++;
9547
- console.log(`Reconnecting in ${delay}ms (attempt ${this.retryCount}/${maxRetries})`);
9624
+ this.logger.log(`Reconnecting in ${delay}ms (attempt ${this.retryCount}/${maxRetries})`);
9548
9625
  await new Promise(resolve => setTimeout(resolve, delay));
9549
9626
  await this.startSubscription();
9550
9627
  }
@@ -9561,7 +9638,7 @@ class AdminNamespace {
9561
9638
  handler(event);
9562
9639
  }
9563
9640
  catch (error) {
9564
- console.error(`Error in event handler for ${eventName}:`, error);
9641
+ this.logger.error(`Error in event handler for ${eventName}:`, error);
9565
9642
  }
9566
9643
  }
9567
9644
  }
@@ -9573,7 +9650,7 @@ class AdminNamespace {
9573
9650
  await this.executeWithTimeout(handler(event), AdminNamespace.BLOCKING_TIMEOUT_MS, `Blocking handler for ${eventName} exceeded ${AdminNamespace.BLOCKING_TIMEOUT_MS}ms`);
9574
9651
  }
9575
9652
  catch (error) {
9576
- console.error(`Error in blocking event handler for ${eventName}:`, error);
9653
+ this.logger.error(`Error in blocking event handler for ${eventName}:`, error);
9577
9654
  throw error; // Re-throw for blocking handlers
9578
9655
  }
9579
9656
  }
@@ -973,6 +973,7 @@ export type CreateCustomerInput = {
973
973
  };
974
974
  export type CreateCustomerResult = Customer | EmailAddressConflictError;
975
975
  export type CreateDeploymentInput = {
976
+ envVars?: InputMaybe<Array<EnvVarInput>>;
976
977
  type: DeploymentType;
977
978
  };
978
979
  export type CreateFacetInput = {
@@ -1900,6 +1901,10 @@ export type EntityEventPayload = {
1900
1901
  type: Scalars['String']['output'];
1901
1902
  };
1902
1903
  export type EntityEventType = 'assigned' | 'created' | 'deleted' | 'removed' | 'updated';
1904
+ export type EnvVarInput = {
1905
+ key: Scalars['String']['input'];
1906
+ value: Scalars['String']['input'];
1907
+ };
1903
1908
  export type ErrorCode = 'ALREADY_REFUNDED_ERROR' | 'CANCEL_ACTIVE_ORDER_ERROR' | 'CANCEL_PAYMENT_ERROR' | 'CHANNEL_DEFAULT_LANGUAGE_ERROR' | 'COUPON_CODE_EXPIRED_ERROR' | 'COUPON_CODE_INVALID_ERROR' | 'COUPON_CODE_LIMIT_ERROR' | 'CREATE_FULFILLMENT_ERROR' | 'DUPLICATE_ENTITY_ERROR' | 'EMAIL_ADDRESS_CONFLICT_ERROR' | 'EMPTY_ORDER_LINE_SELECTION_ERROR' | 'FACET_IN_USE_ERROR' | 'FULFILLMENT_STATE_TRANSITION_ERROR' | 'GUEST_CHECKOUT_ERROR' | 'INELIGIBLE_SHIPPING_METHOD_ERROR' | 'INSUFFICIENT_STOCK_ERROR' | 'INSUFFICIENT_STOCK_ON_HAND_ERROR' | 'INVALID_CREDENTIALS_ERROR' | 'INVALID_FULFILLMENT_HANDLER_ERROR' | 'ITEMS_ALREADY_FULFILLED_ERROR' | 'LANGUAGE_NOT_AVAILABLE_ERROR' | 'MANUAL_PAYMENT_STATE_ERROR' | 'MIME_TYPE_ERROR' | 'MISSING_CONDITIONS_ERROR' | 'MULTIPLE_ORDER_ERROR' | 'NATIVE_AUTH_STRATEGY_ERROR' | 'NEGATIVE_QUANTITY_ERROR' | 'NOTHING_TO_REFUND_ERROR' | 'NO_ACTIVE_ORDER_ERROR' | 'NO_CHANGES_SPECIFIED_ERROR' | 'ORDER_INTERCEPTOR_ERROR' | 'ORDER_LIMIT_ERROR' | 'ORDER_MODIFICATION_ERROR' | 'ORDER_MODIFICATION_STATE_ERROR' | 'ORDER_STATE_TRANSITION_ERROR' | 'PAYMENT_METHOD_MISSING_ERROR' | 'PAYMENT_ORDER_MISMATCH_ERROR' | 'PAYMENT_STATE_TRANSITION_ERROR' | 'PRODUCT_OPTION_IN_USE_ERROR' | 'QUANTITY_TOO_GREAT_ERROR' | 'REFUND_AMOUNT_ERROR' | 'REFUND_ORDER_STATE_ERROR' | 'REFUND_PAYMENT_ID_MISSING_ERROR' | 'REFUND_STATE_TRANSITION_ERROR' | 'SETTLE_PAYMENT_ERROR' | 'UNKNOWN_ERROR';
1904
1909
  export type ErrorResult = {
1905
1910
  errorCode: ErrorCode;
@@ -6185,9 +6190,8 @@ export type SessionInput = {
6185
6190
  };
6186
6191
  export type SetCustomerForDraftOrderResult = EmailAddressConflictError | Order;
6187
6192
  export type SetEnvInput = {
6188
- key: Scalars['String']['input'];
6193
+ envVars: Array<EnvVarInput>;
6189
6194
  type: DeploymentType;
6190
- value: Scalars['String']['input'];
6191
6195
  };
6192
6196
  export type SetOrderCustomerInput = {
6193
6197
  customerId: Scalars['ID']['input'];
@@ -6783,7 +6787,7 @@ export type TransitionFulfillmentToStateResult = Fulfillment | FulfillmentStateT
6783
6787
  export type TransitionOrderToStateResult = Order | OrderStateTransitionError;
6784
6788
  export type TransitionPaymentToStateResult = Payment | PaymentStateTransitionError;
6785
6789
  export type UnsetEnvInput = {
6786
- key: Scalars['String']['input'];
6790
+ keys: Array<Scalars['String']['input']>;
6787
6791
  type: DeploymentType;
6788
6792
  };
6789
6793
  export type UpdateActiveAdministratorInput = {
package/dist/index.d.ts CHANGED
@@ -18,6 +18,8 @@ export interface ForgeCartSDKConfig {
18
18
  token: string;
19
19
  /** Use HTTP only, skip WebSocket initialization (default: false) */
20
20
  httpOnly?: boolean;
21
+ /** Enable debug logging with request timing (default: false) */
22
+ debug?: boolean;
21
23
  /** Custom HTTP headers */
22
24
  headers?: Record<string, string>;
23
25
  /** Custom WebSocket implementation (optional, auto-detected if not provided) */
package/dist/index.js CHANGED
@@ -52,6 +52,7 @@ class ForgeCartSDK {
52
52
  headers,
53
53
  webSocketImpl: config.webSocketImpl,
54
54
  httpOnly: config.httpOnly,
55
+ debug: config.debug,
55
56
  };
56
57
  this.admin = new admin_js_1.AdminNamespace(adminConfig);
57
58
  const shopConfig = {
@@ -60,6 +61,7 @@ class ForgeCartSDK {
60
61
  headers,
61
62
  webSocketImpl: config.webSocketImpl,
62
63
  httpOnly: config.httpOnly,
64
+ debug: config.debug,
63
65
  };
64
66
  this.shop = new shop_js_1.ShopNamespace(shopConfig);
65
67
  }
@@ -4,7 +4,7 @@
4
4
  * This file was automatically generated and should not be manually edited.
5
5
  * To regenerate, run: npm run codegen:ts
6
6
  *
7
- * Generated at: 2025-12-15T07:13:55.632Z
7
+ * Generated at: 2025-12-17T10:24:29.175Z
8
8
  * Generator version: 1.0.0
9
9
  *
10
10
  * 🤖 Generated with ForgeCart SDK Generator
@@ -25,6 +25,30 @@ export interface SDKConfig {
25
25
  webSocketImpl?: unknown;
26
26
  /** Use HTTP only, skip WebSocket initialization (default: false) */
27
27
  httpOnly?: boolean;
28
+ /** Enable debug logging with request timing (default: false) */
29
+ debug?: boolean;
30
+ }
31
+ /**
32
+ * SDK Logger - Cross-platform logging with colors
33
+ * Respects debug flag for log/warn, errors are always visible
34
+ */
35
+ export declare class SDKLogger {
36
+ private debug;
37
+ private prefix;
38
+ constructor(debug?: boolean);
39
+ private isNode;
40
+ /**
41
+ * Debug log - only outputs when debug: true
42
+ */
43
+ log(message: string, ...args: unknown[]): void;
44
+ /**
45
+ * Warning log - only outputs when debug: true
46
+ */
47
+ warn(message: string, ...args: unknown[]): void;
48
+ /**
49
+ * Error log - always outputs (errors should always be visible)
50
+ */
51
+ error(message: string, ...args: unknown[]): void;
28
52
  }
29
53
  export type AssetQueryVariables = Types.AssetQueryVariables;
30
54
  export type AssetQuery = Types.AssetQuery;
@@ -245,7 +269,12 @@ declare class BaseGraphQLClient {
245
269
  private endpoint;
246
270
  private wsEndpoint;
247
271
  private config;
272
+ private logger;
248
273
  constructor(config: SDKConfig);
274
+ /**
275
+ * Extract operation name from GraphQL document
276
+ */
277
+ private getOperationName;
249
278
  /**
250
279
  * Execute a GraphQL query or mutation
251
280
  * Uses WebSocket if available, falls back to HTTP
@@ -657,6 +686,7 @@ declare class SearchOperations {
657
686
  */
658
687
  export declare class ShopNamespace {
659
688
  private client;
689
+ private logger;
660
690
  readonly asset: AssetOperations;
661
691
  readonly auth: AuthOperations;
662
692
  readonly blog: BlogOperations;
@@ -5,15 +5,66 @@
5
5
  * This file was automatically generated and should not be manually edited.
6
6
  * To regenerate, run: npm run codegen:ts
7
7
  *
8
- * Generated at: 2025-12-15T07:13:55.632Z
8
+ * Generated at: 2025-12-17T10:24:29.175Z
9
9
  * Generator version: 1.0.0
10
10
  *
11
11
  * 🤖 Generated with ForgeCart SDK Generator
12
12
  */
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
- exports.ShopNamespace = void 0;
14
+ exports.ShopNamespace = exports.SDKLogger = void 0;
15
15
  const graphql_request_1 = require("graphql-request");
16
16
  const graphql_ws_1 = require("graphql-ws");
17
+ /**
18
+ * SDK Logger - Cross-platform logging with colors
19
+ * Respects debug flag for log/warn, errors are always visible
20
+ */
21
+ class SDKLogger {
22
+ constructor(debug = false) {
23
+ this.prefix = '[ForgeCart SDK]';
24
+ this.debug = debug;
25
+ }
26
+ isNode() {
27
+ return typeof process !== 'undefined' && process.versions?.node != null;
28
+ }
29
+ /**
30
+ * Debug log - only outputs when debug: true
31
+ */
32
+ log(message, ...args) {
33
+ if (!this.debug)
34
+ return;
35
+ if (this.isNode()) {
36
+ console.log(`\x1b[36m${this.prefix}\x1b[0m ${message}`, ...args);
37
+ }
38
+ else {
39
+ console.log(`%c${this.prefix}%c ${message}`, 'color: #06b6d4; font-weight: bold', 'color: inherit', ...args);
40
+ }
41
+ }
42
+ /**
43
+ * Warning log - only outputs when debug: true
44
+ */
45
+ warn(message, ...args) {
46
+ if (!this.debug)
47
+ return;
48
+ if (this.isNode()) {
49
+ console.warn(`\x1b[33m${this.prefix}\x1b[0m ${message}`, ...args);
50
+ }
51
+ else {
52
+ console.warn(`%c${this.prefix}%c ${message}`, 'color: #eab308; font-weight: bold', 'color: inherit', ...args);
53
+ }
54
+ }
55
+ /**
56
+ * Error log - always outputs (errors should always be visible)
57
+ */
58
+ error(message, ...args) {
59
+ if (this.isNode()) {
60
+ console.error(`\x1b[31m${this.prefix}\x1b[0m ${message}`, ...args);
61
+ }
62
+ else {
63
+ console.error(`%c${this.prefix}%c ${message}`, 'color: #ef4444; font-weight: bold', 'color: inherit', ...args);
64
+ }
65
+ }
66
+ }
67
+ exports.SDKLogger = SDKLogger;
17
68
  const assetDocument = `query asset($id: ID!) {
18
69
  asset(id: $id) {
19
70
  id
@@ -2203,6 +2254,7 @@ class BaseGraphQLClient {
2203
2254
  this.endpoint = config.endpoint || 'https://api.forgecart.com/shop-api';
2204
2255
  this.wsEndpoint = config.wsEndpoint || 'wss://api.forgecart.com/shop-api';
2205
2256
  this.config = config;
2257
+ this.logger = new SDKLogger(config?.debug ?? false);
2206
2258
  // Custom fetch that captures session token from response headers
2207
2259
  const customFetch = async (url, options) => {
2208
2260
  const response = await fetch(url, options);
@@ -2230,26 +2282,50 @@ class BaseGraphQLClient {
2230
2282
  // Initialize WebSocket connection immediately
2231
2283
  this.initializeWebSocket();
2232
2284
  }
2285
+ /**
2286
+ * Extract operation name from GraphQL document
2287
+ */
2288
+ getOperationName(document) {
2289
+ const docString = typeof document === 'string' ? document : String(document);
2290
+ const match = docString.match(/(?:query|mutation|subscription)\s+(\w+)/);
2291
+ return match ? match[1] : 'UnknownOperation';
2292
+ }
2233
2293
  /**
2234
2294
  * Execute a GraphQL query or mutation
2235
2295
  * Uses WebSocket if available, falls back to HTTP
2236
2296
  */
2237
2297
  async request(document, variables) {
2298
+ const startTime = this.config.debug ? performance.now() : 0;
2299
+ const operationName = this.config.debug ? this.getOperationName(document) : '';
2300
+ let transport = 'HTTP';
2238
2301
  // Try WebSocket first if available
2239
2302
  if (this.wsClient && this.wsConnected) {
2240
2303
  try {
2241
- return await this.requestViaWebSocket(document, variables);
2304
+ transport = 'WebSocket';
2305
+ const result = await this.requestViaWebSocket(document, variables);
2306
+ if (this.config.debug) {
2307
+ this.logger.log(`${operationName} (${transport}) - ${(performance.now() - startTime).toFixed(0)}ms`);
2308
+ }
2309
+ return result;
2242
2310
  }
2243
2311
  catch (wsError) {
2244
- console.warn('WebSocket request failed, falling back to HTTP:', wsError);
2312
+ transport = 'WebSocket -> HTTP';
2313
+ this.logger.warn('WebSocket request failed, falling back to HTTP:', wsError);
2245
2314
  // Fall through to HTTP
2246
2315
  }
2247
2316
  }
2248
2317
  // Use HTTP as fallback or when WebSocket is not available
2249
2318
  try {
2250
- return await this.httpClient.request(document, variables);
2319
+ const result = await this.httpClient.request(document, variables);
2320
+ if (this.config.debug) {
2321
+ this.logger.log(`${operationName} (${transport}) - ${(performance.now() - startTime).toFixed(0)}ms`);
2322
+ }
2323
+ return result;
2251
2324
  }
2252
2325
  catch (error) {
2326
+ if (this.config.debug) {
2327
+ this.logger.log(`${operationName} (${transport}) - FAILED after ${(performance.now() - startTime).toFixed(0)}ms`);
2328
+ }
2253
2329
  this.handleError(error);
2254
2330
  throw error;
2255
2331
  }
@@ -2290,7 +2366,7 @@ class BaseGraphQLClient {
2290
2366
  * Handle GraphQL errors
2291
2367
  */
2292
2368
  handleError(error) {
2293
- console.error('GraphQL Error:', error);
2369
+ this.logger.error('GraphQL Error:', error);
2294
2370
  }
2295
2371
  /**
2296
2372
  * Get WebSocket implementation for current environment
@@ -2310,7 +2386,7 @@ class BaseGraphQLClient {
2310
2386
  return require('ws');
2311
2387
  }
2312
2388
  catch (e) {
2313
- console.warn('WebSocket not available. Install "ws" package for Node.js support or provide webSocketImpl in config.');
2389
+ this.logger.warn('WebSocket not available. Install "ws" package for Node.js support or provide webSocketImpl in config.');
2314
2390
  return undefined;
2315
2391
  }
2316
2392
  }
@@ -2329,7 +2405,7 @@ class BaseGraphQLClient {
2329
2405
  }
2330
2406
  const WebSocketImpl = this.getWebSocketImpl();
2331
2407
  if (!WebSocketImpl) {
2332
- console.warn('WebSocket not available. Will use HTTP fallback only.');
2408
+ this.logger.warn('WebSocket not available. Will use HTTP fallback only.');
2333
2409
  return;
2334
2410
  }
2335
2411
  this.wsInitializing = new Promise((resolve, reject) => {
@@ -2358,7 +2434,7 @@ class BaseGraphQLClient {
2358
2434
  return params;
2359
2435
  }
2360
2436
  catch (error) {
2361
- console.error('Error in connectionParams:', error);
2437
+ this.logger.error('Error in connectionParams:', error);
2362
2438
  return {};
2363
2439
  }
2364
2440
  },
@@ -2371,7 +2447,7 @@ class BaseGraphQLClient {
2371
2447
  on: {
2372
2448
  connected: () => {
2373
2449
  this.wsConnected = true;
2374
- console.log('WebSocket connected');
2450
+ this.logger.log('WebSocket connected');
2375
2451
  if (!isResolved) {
2376
2452
  isResolved = true;
2377
2453
  resolve();
@@ -2380,13 +2456,13 @@ class BaseGraphQLClient {
2380
2456
  closed: () => {
2381
2457
  this.wsConnected = false;
2382
2458
  if (!this.isDisposing) {
2383
- console.log('WebSocket disconnected');
2459
+ this.logger.log('WebSocket disconnected');
2384
2460
  }
2385
2461
  },
2386
2462
  error: (error) => {
2387
2463
  // Suppress errors during disposal - they're expected
2388
2464
  if (!this.isDisposing && this.wsConnected) {
2389
- console.error('WebSocket error:', error);
2465
+ this.logger.error('WebSocket error:', error);
2390
2466
  }
2391
2467
  // Don't fail the connection on errors - let retry logic handle it
2392
2468
  // Only log errors if we're connected (unexpected errors)
@@ -2398,7 +2474,7 @@ class BaseGraphQLClient {
2398
2474
  this.wsInitializing = null;
2399
2475
  }
2400
2476
  catch (error) {
2401
- console.error('Failed to create WebSocket client:', error);
2477
+ this.logger.error('Failed to create WebSocket client:', error);
2402
2478
  this.wsInitializing = null;
2403
2479
  // Resolve anyway to not block - we'll fallback to HTTP
2404
2480
  if (!isResolved) {
@@ -3010,6 +3086,7 @@ class SearchOperations {
3010
3086
  class ShopNamespace {
3011
3087
  constructor(config) {
3012
3088
  this.client = new BaseGraphQLClient(config);
3089
+ this.logger = new SDKLogger(config?.debug ?? false);
3013
3090
  this.asset = new AssetOperations(this.client);
3014
3091
  this.auth = new AuthOperations(this.client);
3015
3092
  this.blog = new BlogOperations(this.client);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgecart/sdk",
3
- "version": "1.2.2",
3
+ "version": "1.2.5",
4
4
  "description": "TypeScript SDK for ForgeCart API - Auto-generated GraphQL client",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",
@@ -4,7 +4,7 @@
4
4
  * This file was automatically generated and should not be manually edited.
5
5
  * To regenerate, run: npm run codegen:ts
6
6
  *
7
- * Generated at: 2025-12-15T07:13:55.745Z
7
+ * Generated at: 2025-12-17T10:24:29.306Z
8
8
  * Generator version: 1.0.0
9
9
  *
10
10
  * 🤖 Generated with ForgeCart SDK Generator
@@ -30,6 +30,60 @@ export interface SDKConfig {
30
30
  webSocketImpl?: unknown;
31
31
  /** Use HTTP only, skip WebSocket initialization (default: false) */
32
32
  httpOnly?: boolean;
33
+ /** Enable debug logging with request timing (default: false) */
34
+ debug?: boolean;
35
+ }
36
+
37
+ /**
38
+ * SDK Logger - Cross-platform logging with colors
39
+ * Respects debug flag for log/warn, errors are always visible
40
+ */
41
+ export class SDKLogger {
42
+ private debug: boolean;
43
+ private prefix = '[ForgeCart SDK]';
44
+
45
+ constructor(debug: boolean = false) {
46
+ this.debug = debug;
47
+ }
48
+
49
+ private isNode(): boolean {
50
+ return typeof process !== 'undefined' && process.versions?.node != null;
51
+ }
52
+
53
+ /**
54
+ * Debug log - only outputs when debug: true
55
+ */
56
+ log(message: string, ...args: unknown[]): void {
57
+ if (!this.debug) return;
58
+ if (this.isNode()) {
59
+ console.log(`\x1b[36m${this.prefix}\x1b[0m ${message}`, ...args);
60
+ } else {
61
+ console.log(`%c${this.prefix}%c ${message}`, 'color: #06b6d4; font-weight: bold', 'color: inherit', ...args);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Warning log - only outputs when debug: true
67
+ */
68
+ warn(message: string, ...args: unknown[]): void {
69
+ if (!this.debug) return;
70
+ if (this.isNode()) {
71
+ console.warn(`\x1b[33m${this.prefix}\x1b[0m ${message}`, ...args);
72
+ } else {
73
+ console.warn(`%c${this.prefix}%c ${message}`, 'color: #eab308; font-weight: bold', 'color: inherit', ...args);
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Error log - always outputs (errors should always be visible)
79
+ */
80
+ error(message: string, ...args: unknown[]): void {
81
+ if (this.isNode()) {
82
+ console.error(`\x1b[31m${this.prefix}\x1b[0m ${message}`, ...args);
83
+ } else {
84
+ console.error(`%c${this.prefix}%c ${message}`, 'color: #ef4444; font-weight: bold', 'color: inherit', ...args);
85
+ }
86
+ }
33
87
  }
34
88
 
35
89
  export type ActiveAdministratorQueryVariables = Types.ActiveAdministratorQueryVariables;
@@ -8170,11 +8224,13 @@ class BaseGraphQLClient {
8170
8224
  private endpoint: string;
8171
8225
  private wsEndpoint: string;
8172
8226
  private config: SDKConfig;
8227
+ private logger: SDKLogger;
8173
8228
 
8174
8229
  constructor(config: SDKConfig) {
8175
8230
  this.endpoint = config.endpoint || 'https://api.forgecart.com/admin-api';
8176
8231
  this.wsEndpoint = config.wsEndpoint || 'wss://api.forgecart.com/admin-api';
8177
8232
  this.config = config;
8233
+ this.logger = new SDKLogger(config?.debug ?? false);
8178
8234
 
8179
8235
  // Custom fetch that captures session token from response headers
8180
8236
  const customFetch = async (url: RequestInfo | URL, options?: RequestInit): Promise<Response> => {
@@ -8208,6 +8264,15 @@ class BaseGraphQLClient {
8208
8264
  this.initializeWebSocket();
8209
8265
  }
8210
8266
 
8267
+ /**
8268
+ * Extract operation name from GraphQL document
8269
+ */
8270
+ private getOperationName(document: RequestDocument): string {
8271
+ const docString = typeof document === 'string' ? document : String(document);
8272
+ const match = docString.match(/(?:query|mutation|subscription)\s+(\w+)/);
8273
+ return match ? match[1] : 'UnknownOperation';
8274
+ }
8275
+
8211
8276
  /**
8212
8277
  * Execute a GraphQL query or mutation
8213
8278
  * Uses WebSocket if available, falls back to HTTP
@@ -8216,13 +8281,23 @@ class BaseGraphQLClient {
8216
8281
  document: RequestDocument,
8217
8282
  variables?: V
8218
8283
  ): Promise<T> {
8284
+ const startTime = this.config.debug ? performance.now() : 0;
8285
+ const operationName = this.config.debug ? this.getOperationName(document) : '';
8286
+ let transport = 'HTTP';
8287
+
8219
8288
 
8220
8289
  // Try WebSocket first if available
8221
8290
  if (this.wsClient && this.wsConnected) {
8222
8291
  try {
8223
- return await this.requestViaWebSocket<T>(document, variables);
8292
+ transport = 'WebSocket';
8293
+ const result = await this.requestViaWebSocket<T>(document, variables);
8294
+ if (this.config.debug) {
8295
+ this.logger.log(`${operationName} (${transport}) - ${(performance.now() - startTime).toFixed(0)}ms`);
8296
+ }
8297
+ return result;
8224
8298
  } catch (wsError) {
8225
- console.warn('WebSocket request failed, falling back to HTTP:', wsError);
8299
+ transport = 'WebSocket -> HTTP';
8300
+ this.logger.warn('WebSocket request failed, falling back to HTTP:', wsError);
8226
8301
  // Fall through to HTTP
8227
8302
  }
8228
8303
  }
@@ -8230,8 +8305,15 @@ class BaseGraphQLClient {
8230
8305
 
8231
8306
  // Use HTTP as fallback or when WebSocket is not available
8232
8307
  try {
8233
- return await this.httpClient.request<T>(document, variables);
8308
+ const result = await this.httpClient.request<T>(document, variables);
8309
+ if (this.config.debug) {
8310
+ this.logger.log(`${operationName} (${transport}) - ${(performance.now() - startTime).toFixed(0)}ms`);
8311
+ }
8312
+ return result;
8234
8313
  } catch (error) {
8314
+ if (this.config.debug) {
8315
+ this.logger.log(`${operationName} (${transport}) - FAILED after ${(performance.now() - startTime).toFixed(0)}ms`);
8316
+ }
8235
8317
  this.handleError(error);
8236
8318
  throw error;
8237
8319
  }
@@ -8283,7 +8365,7 @@ class BaseGraphQLClient {
8283
8365
  * Handle GraphQL errors
8284
8366
  */
8285
8367
  private handleError(error: any): void {
8286
- console.error('GraphQL Error:', error);
8368
+ this.logger.error('GraphQL Error:', error);
8287
8369
  }
8288
8370
 
8289
8371
 
@@ -8306,7 +8388,7 @@ class BaseGraphQLClient {
8306
8388
  // Dynamic import for Node.js
8307
8389
  return require('ws');
8308
8390
  } catch (e) {
8309
- console.warn('WebSocket not available. Install "ws" package for Node.js support or provide webSocketImpl in config.');
8391
+ this.logger.warn('WebSocket not available. Install "ws" package for Node.js support or provide webSocketImpl in config.');
8310
8392
  return undefined;
8311
8393
  }
8312
8394
  }
@@ -8328,7 +8410,7 @@ class BaseGraphQLClient {
8328
8410
 
8329
8411
  const WebSocketImpl = this.getWebSocketImpl();
8330
8412
  if (!WebSocketImpl) {
8331
- console.warn('WebSocket not available. Will use HTTP fallback only.');
8413
+ this.logger.warn('WebSocket not available. Will use HTTP fallback only.');
8332
8414
  return;
8333
8415
  }
8334
8416
 
@@ -8361,7 +8443,7 @@ class BaseGraphQLClient {
8361
8443
 
8362
8444
  return params;
8363
8445
  } catch (error) {
8364
- console.error('Error in connectionParams:', error);
8446
+ this.logger.error('Error in connectionParams:', error);
8365
8447
  return {};
8366
8448
  }
8367
8449
  },
@@ -8374,7 +8456,7 @@ class BaseGraphQLClient {
8374
8456
  on: {
8375
8457
  connected: () => {
8376
8458
  this.wsConnected = true;
8377
- console.log('WebSocket connected');
8459
+ this.logger.log('WebSocket connected');
8378
8460
  if (!isResolved) {
8379
8461
  isResolved = true;
8380
8462
  resolve();
@@ -8383,13 +8465,13 @@ class BaseGraphQLClient {
8383
8465
  closed: () => {
8384
8466
  this.wsConnected = false;
8385
8467
  if (!this.isDisposing) {
8386
- console.log('WebSocket disconnected');
8468
+ this.logger.log('WebSocket disconnected');
8387
8469
  }
8388
8470
  },
8389
8471
  error: (error) => {
8390
8472
  // Suppress errors during disposal - they're expected
8391
8473
  if (!this.isDisposing && this.wsConnected) {
8392
- console.error('WebSocket error:', error);
8474
+ this.logger.error('WebSocket error:', error);
8393
8475
  }
8394
8476
  // Don't fail the connection on errors - let retry logic handle it
8395
8477
  // Only log errors if we're connected (unexpected errors)
@@ -8401,7 +8483,7 @@ class BaseGraphQLClient {
8401
8483
  this.wsClient = createClient(wsOptions);
8402
8484
  this.wsInitializing = null;
8403
8485
  } catch (error) {
8404
- console.error('Failed to create WebSocket client:', error);
8486
+ this.logger.error('Failed to create WebSocket client:', error);
8405
8487
  this.wsInitializing = null;
8406
8488
  // Resolve anyway to not block - we'll fallback to HTTP
8407
8489
  if (!isResolved) {
@@ -11055,6 +11137,7 @@ class ZoneOperations {
11055
11137
  */
11056
11138
  export class AdminNamespace {
11057
11139
  private client: BaseGraphQLClient;
11140
+ private logger: SDKLogger;
11058
11141
 
11059
11142
  readonly administrator: AdministratorOperations;
11060
11143
  readonly asset: AssetOperations;
@@ -11113,6 +11196,7 @@ export class AdminNamespace {
11113
11196
 
11114
11197
  constructor(config: SDKConfig) {
11115
11198
  this.client = new BaseGraphQLClient(config);
11199
+ this.logger = new SDKLogger(config?.debug ?? false);
11116
11200
 
11117
11201
  this.administrator = new AdministratorOperations(this.client);
11118
11202
  this.asset = new AssetOperations(this.client);
@@ -11247,7 +11331,7 @@ export class AdminNamespace {
11247
11331
  }
11248
11332
  } catch (error) {
11249
11333
  if (this.isListening) {
11250
- console.error('Event subscription error:', error);
11334
+ this.logger.error('Event subscription error:', error);
11251
11335
  await this.handleReconnect();
11252
11336
  }
11253
11337
  }
@@ -11268,7 +11352,7 @@ export class AdminNamespace {
11268
11352
  );
11269
11353
 
11270
11354
  this.retryCount++;
11271
- console.log(`Reconnecting in ${delay}ms (attempt ${this.retryCount}/${maxRetries})`);
11355
+ this.logger.log(`Reconnecting in ${delay}ms (attempt ${this.retryCount}/${maxRetries})`);
11272
11356
 
11273
11357
  await new Promise(resolve => setTimeout(resolve, delay));
11274
11358
  await this.startSubscription();
@@ -11287,7 +11371,7 @@ export class AdminNamespace {
11287
11371
  try {
11288
11372
  handler(event);
11289
11373
  } catch (error) {
11290
- console.error(`Error in event handler for ${eventName}:`, error);
11374
+ this.logger.error(`Error in event handler for ${eventName}:`, error);
11291
11375
  }
11292
11376
  }
11293
11377
  }
@@ -11303,7 +11387,7 @@ export class AdminNamespace {
11303
11387
  `Blocking handler for ${eventName} exceeded ${AdminNamespace.BLOCKING_TIMEOUT_MS}ms`
11304
11388
  );
11305
11389
  } catch (error) {
11306
- console.error(`Error in blocking event handler for ${eventName}:`, error);
11390
+ this.logger.error(`Error in blocking event handler for ${eventName}:`, error);
11307
11391
  throw error; // Re-throw for blocking handlers
11308
11392
  }
11309
11393
  }
@@ -1063,6 +1063,7 @@ export type CreateCustomerInput = {
1063
1063
  export type CreateCustomerResult = Customer | EmailAddressConflictError;
1064
1064
 
1065
1065
  export type CreateDeploymentInput = {
1066
+ envVars?: InputMaybe<Array<EnvVarInput>>;
1066
1067
  type: DeploymentType;
1067
1068
  };
1068
1069
 
@@ -2095,6 +2096,11 @@ export type EntityEventType =
2095
2096
  | 'removed'
2096
2097
  | 'updated';
2097
2098
 
2099
+ export type EnvVarInput = {
2100
+ key: Scalars['String']['input'];
2101
+ value: Scalars['String']['input'];
2102
+ };
2103
+
2098
2104
  export type ErrorCode =
2099
2105
  | 'ALREADY_REFUNDED_ERROR'
2100
2106
  | 'CANCEL_ACTIVE_ORDER_ERROR'
@@ -7389,9 +7395,8 @@ export type SessionInput = {
7389
7395
  export type SetCustomerForDraftOrderResult = EmailAddressConflictError | Order;
7390
7396
 
7391
7397
  export type SetEnvInput = {
7392
- key: Scalars['String']['input'];
7398
+ envVars: Array<EnvVarInput>;
7393
7399
  type: DeploymentType;
7394
- value: Scalars['String']['input'];
7395
7400
  };
7396
7401
 
7397
7402
  export type SetOrderCustomerInput = {
@@ -8078,7 +8083,7 @@ export type TransitionOrderToStateResult = Order | OrderStateTransitionError;
8078
8083
  export type TransitionPaymentToStateResult = Payment | PaymentStateTransitionError;
8079
8084
 
8080
8085
  export type UnsetEnvInput = {
8081
- key: Scalars['String']['input'];
8086
+ keys: Array<Scalars['String']['input']>;
8082
8087
  type: DeploymentType;
8083
8088
  };
8084
8089
 
package/src/index.ts CHANGED
@@ -20,6 +20,8 @@ export interface ForgeCartSDKConfig {
20
20
  token: string
21
21
  /** Use HTTP only, skip WebSocket initialization (default: false) */
22
22
  httpOnly?: boolean
23
+ /** Enable debug logging with request timing (default: false) */
24
+ debug?: boolean
23
25
  /** Custom HTTP headers */
24
26
  headers?: Record<string, string>
25
27
  /** Custom WebSocket implementation (optional, auto-detected if not provided) */
@@ -75,6 +77,7 @@ export class ForgeCartSDK {
75
77
  headers,
76
78
  webSocketImpl: config.webSocketImpl,
77
79
  httpOnly: config.httpOnly,
80
+ debug: config.debug,
78
81
  }
79
82
  this.admin = new AdminNamespace(adminConfig)
80
83
 
@@ -84,6 +87,7 @@ export class ForgeCartSDK {
84
87
  headers,
85
88
  webSocketImpl: config.webSocketImpl,
86
89
  httpOnly: config.httpOnly,
90
+ debug: config.debug,
87
91
  }
88
92
  this.shop = new ShopNamespace(shopConfig)
89
93
  }
@@ -4,7 +4,7 @@
4
4
  * This file was automatically generated and should not be manually edited.
5
5
  * To regenerate, run: npm run codegen:ts
6
6
  *
7
- * Generated at: 2025-12-15T07:13:55.632Z
7
+ * Generated at: 2025-12-17T10:24:29.175Z
8
8
  * Generator version: 1.0.0
9
9
  *
10
10
  * 🤖 Generated with ForgeCart SDK Generator
@@ -30,6 +30,60 @@ export interface SDKConfig {
30
30
  webSocketImpl?: unknown;
31
31
  /** Use HTTP only, skip WebSocket initialization (default: false) */
32
32
  httpOnly?: boolean;
33
+ /** Enable debug logging with request timing (default: false) */
34
+ debug?: boolean;
35
+ }
36
+
37
+ /**
38
+ * SDK Logger - Cross-platform logging with colors
39
+ * Respects debug flag for log/warn, errors are always visible
40
+ */
41
+ export class SDKLogger {
42
+ private debug: boolean;
43
+ private prefix = '[ForgeCart SDK]';
44
+
45
+ constructor(debug: boolean = false) {
46
+ this.debug = debug;
47
+ }
48
+
49
+ private isNode(): boolean {
50
+ return typeof process !== 'undefined' && process.versions?.node != null;
51
+ }
52
+
53
+ /**
54
+ * Debug log - only outputs when debug: true
55
+ */
56
+ log(message: string, ...args: unknown[]): void {
57
+ if (!this.debug) return;
58
+ if (this.isNode()) {
59
+ console.log(`\x1b[36m${this.prefix}\x1b[0m ${message}`, ...args);
60
+ } else {
61
+ console.log(`%c${this.prefix}%c ${message}`, 'color: #06b6d4; font-weight: bold', 'color: inherit', ...args);
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Warning log - only outputs when debug: true
67
+ */
68
+ warn(message: string, ...args: unknown[]): void {
69
+ if (!this.debug) return;
70
+ if (this.isNode()) {
71
+ console.warn(`\x1b[33m${this.prefix}\x1b[0m ${message}`, ...args);
72
+ } else {
73
+ console.warn(`%c${this.prefix}%c ${message}`, 'color: #eab308; font-weight: bold', 'color: inherit', ...args);
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Error log - always outputs (errors should always be visible)
79
+ */
80
+ error(message: string, ...args: unknown[]): void {
81
+ if (this.isNode()) {
82
+ console.error(`\x1b[31m${this.prefix}\x1b[0m ${message}`, ...args);
83
+ } else {
84
+ console.error(`%c${this.prefix}%c ${message}`, 'color: #ef4444; font-weight: bold', 'color: inherit', ...args);
85
+ }
86
+ }
33
87
  }
34
88
 
35
89
  export type AssetQueryVariables = Types.AssetQueryVariables;
@@ -2494,11 +2548,13 @@ class BaseGraphQLClient {
2494
2548
  private endpoint: string;
2495
2549
  private wsEndpoint: string;
2496
2550
  private config: SDKConfig;
2551
+ private logger: SDKLogger;
2497
2552
 
2498
2553
  constructor(config: SDKConfig) {
2499
2554
  this.endpoint = config.endpoint || 'https://api.forgecart.com/shop-api';
2500
2555
  this.wsEndpoint = config.wsEndpoint || 'wss://api.forgecart.com/shop-api';
2501
2556
  this.config = config;
2557
+ this.logger = new SDKLogger(config?.debug ?? false);
2502
2558
 
2503
2559
  // Custom fetch that captures session token from response headers
2504
2560
  const customFetch = async (url: RequestInfo | URL, options?: RequestInit): Promise<Response> => {
@@ -2532,6 +2588,15 @@ class BaseGraphQLClient {
2532
2588
  this.initializeWebSocket();
2533
2589
  }
2534
2590
 
2591
+ /**
2592
+ * Extract operation name from GraphQL document
2593
+ */
2594
+ private getOperationName(document: RequestDocument): string {
2595
+ const docString = typeof document === 'string' ? document : String(document);
2596
+ const match = docString.match(/(?:query|mutation|subscription)\s+(\w+)/);
2597
+ return match ? match[1] : 'UnknownOperation';
2598
+ }
2599
+
2535
2600
  /**
2536
2601
  * Execute a GraphQL query or mutation
2537
2602
  * Uses WebSocket if available, falls back to HTTP
@@ -2540,13 +2605,23 @@ class BaseGraphQLClient {
2540
2605
  document: RequestDocument,
2541
2606
  variables?: V
2542
2607
  ): Promise<T> {
2608
+ const startTime = this.config.debug ? performance.now() : 0;
2609
+ const operationName = this.config.debug ? this.getOperationName(document) : '';
2610
+ let transport = 'HTTP';
2611
+
2543
2612
 
2544
2613
  // Try WebSocket first if available
2545
2614
  if (this.wsClient && this.wsConnected) {
2546
2615
  try {
2547
- return await this.requestViaWebSocket<T>(document, variables);
2616
+ transport = 'WebSocket';
2617
+ const result = await this.requestViaWebSocket<T>(document, variables);
2618
+ if (this.config.debug) {
2619
+ this.logger.log(`${operationName} (${transport}) - ${(performance.now() - startTime).toFixed(0)}ms`);
2620
+ }
2621
+ return result;
2548
2622
  } catch (wsError) {
2549
- console.warn('WebSocket request failed, falling back to HTTP:', wsError);
2623
+ transport = 'WebSocket -> HTTP';
2624
+ this.logger.warn('WebSocket request failed, falling back to HTTP:', wsError);
2550
2625
  // Fall through to HTTP
2551
2626
  }
2552
2627
  }
@@ -2554,8 +2629,15 @@ class BaseGraphQLClient {
2554
2629
 
2555
2630
  // Use HTTP as fallback or when WebSocket is not available
2556
2631
  try {
2557
- return await this.httpClient.request<T>(document, variables);
2632
+ const result = await this.httpClient.request<T>(document, variables);
2633
+ if (this.config.debug) {
2634
+ this.logger.log(`${operationName} (${transport}) - ${(performance.now() - startTime).toFixed(0)}ms`);
2635
+ }
2636
+ return result;
2558
2637
  } catch (error) {
2638
+ if (this.config.debug) {
2639
+ this.logger.log(`${operationName} (${transport}) - FAILED after ${(performance.now() - startTime).toFixed(0)}ms`);
2640
+ }
2559
2641
  this.handleError(error);
2560
2642
  throw error;
2561
2643
  }
@@ -2607,7 +2689,7 @@ class BaseGraphQLClient {
2607
2689
  * Handle GraphQL errors
2608
2690
  */
2609
2691
  private handleError(error: any): void {
2610
- console.error('GraphQL Error:', error);
2692
+ this.logger.error('GraphQL Error:', error);
2611
2693
  }
2612
2694
 
2613
2695
 
@@ -2630,7 +2712,7 @@ class BaseGraphQLClient {
2630
2712
  // Dynamic import for Node.js
2631
2713
  return require('ws');
2632
2714
  } catch (e) {
2633
- console.warn('WebSocket not available. Install "ws" package for Node.js support or provide webSocketImpl in config.');
2715
+ this.logger.warn('WebSocket not available. Install "ws" package for Node.js support or provide webSocketImpl in config.');
2634
2716
  return undefined;
2635
2717
  }
2636
2718
  }
@@ -2652,7 +2734,7 @@ class BaseGraphQLClient {
2652
2734
 
2653
2735
  const WebSocketImpl = this.getWebSocketImpl();
2654
2736
  if (!WebSocketImpl) {
2655
- console.warn('WebSocket not available. Will use HTTP fallback only.');
2737
+ this.logger.warn('WebSocket not available. Will use HTTP fallback only.');
2656
2738
  return;
2657
2739
  }
2658
2740
 
@@ -2685,7 +2767,7 @@ class BaseGraphQLClient {
2685
2767
 
2686
2768
  return params;
2687
2769
  } catch (error) {
2688
- console.error('Error in connectionParams:', error);
2770
+ this.logger.error('Error in connectionParams:', error);
2689
2771
  return {};
2690
2772
  }
2691
2773
  },
@@ -2698,7 +2780,7 @@ class BaseGraphQLClient {
2698
2780
  on: {
2699
2781
  connected: () => {
2700
2782
  this.wsConnected = true;
2701
- console.log('WebSocket connected');
2783
+ this.logger.log('WebSocket connected');
2702
2784
  if (!isResolved) {
2703
2785
  isResolved = true;
2704
2786
  resolve();
@@ -2707,13 +2789,13 @@ class BaseGraphQLClient {
2707
2789
  closed: () => {
2708
2790
  this.wsConnected = false;
2709
2791
  if (!this.isDisposing) {
2710
- console.log('WebSocket disconnected');
2792
+ this.logger.log('WebSocket disconnected');
2711
2793
  }
2712
2794
  },
2713
2795
  error: (error) => {
2714
2796
  // Suppress errors during disposal - they're expected
2715
2797
  if (!this.isDisposing && this.wsConnected) {
2716
- console.error('WebSocket error:', error);
2798
+ this.logger.error('WebSocket error:', error);
2717
2799
  }
2718
2800
  // Don't fail the connection on errors - let retry logic handle it
2719
2801
  // Only log errors if we're connected (unexpected errors)
@@ -2725,7 +2807,7 @@ class BaseGraphQLClient {
2725
2807
  this.wsClient = createClient(wsOptions);
2726
2808
  this.wsInitializing = null;
2727
2809
  } catch (error) {
2728
- console.error('Failed to create WebSocket client:', error);
2810
+ this.logger.error('Failed to create WebSocket client:', error);
2729
2811
  this.wsInitializing = null;
2730
2812
  // Resolve anyway to not block - we'll fallback to HTTP
2731
2813
  if (!isResolved) {
@@ -3405,6 +3487,7 @@ class SearchOperations {
3405
3487
  */
3406
3488
  export class ShopNamespace {
3407
3489
  private client: BaseGraphQLClient;
3490
+ private logger: SDKLogger;
3408
3491
 
3409
3492
  readonly asset: AssetOperations;
3410
3493
  readonly auth: AuthOperations;
@@ -3422,6 +3505,7 @@ export class ShopNamespace {
3422
3505
 
3423
3506
  constructor(config: SDKConfig) {
3424
3507
  this.client = new BaseGraphQLClient(config);
3508
+ this.logger = new SDKLogger(config?.debug ?? false);
3425
3509
 
3426
3510
  this.asset = new AssetOperations(this.client);
3427
3511
  this.auth = new AuthOperations(this.client);