@tracelog/lib 0.4.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/dist/browser/tracelog.js +576 -610
  2. package/dist/cjs/api.d.ts +1 -53
  3. package/dist/cjs/api.js +0 -59
  4. package/dist/cjs/app.constants.d.ts +1 -1
  5. package/dist/cjs/app.d.ts +1 -5
  6. package/dist/cjs/app.js +4 -12
  7. package/dist/cjs/constants/api.constants.d.ts +5 -2
  8. package/dist/cjs/constants/api.constants.js +5 -14
  9. package/dist/cjs/constants/config.constants.d.ts +3 -3
  10. package/dist/cjs/constants/config.constants.js +3 -3
  11. package/dist/cjs/constants/error.constants.d.ts +7 -2
  12. package/dist/cjs/constants/error.constants.js +13 -2
  13. package/dist/cjs/handlers/click.handler.js +0 -6
  14. package/dist/cjs/handlers/error.handler.js +9 -0
  15. package/dist/cjs/handlers/scroll.handler.js +0 -5
  16. package/dist/cjs/handlers/session.handler.js +5 -2
  17. package/dist/cjs/integrations/google-analytics.integration.d.ts +1 -1
  18. package/dist/cjs/integrations/google-analytics.integration.js +2 -1
  19. package/dist/cjs/managers/api.manager.d.ts +1 -1
  20. package/dist/cjs/managers/api.manager.js +3 -3
  21. package/dist/cjs/managers/config.builder.d.ts +33 -0
  22. package/dist/cjs/managers/config.builder.js +116 -0
  23. package/dist/cjs/managers/config.manager.d.ts +13 -14
  24. package/dist/cjs/managers/config.manager.js +52 -58
  25. package/dist/cjs/managers/event.manager.d.ts +0 -45
  26. package/dist/cjs/managers/event.manager.js +14 -67
  27. package/dist/cjs/managers/sender.manager.d.ts +1 -28
  28. package/dist/cjs/managers/sender.manager.js +43 -73
  29. package/dist/cjs/managers/session.manager.d.ts +2 -49
  30. package/dist/cjs/managers/session.manager.js +37 -79
  31. package/dist/cjs/managers/state.manager.d.ts +1 -28
  32. package/dist/cjs/managers/state.manager.js +5 -33
  33. package/dist/cjs/managers/storage.manager.d.ts +6 -0
  34. package/dist/cjs/managers/storage.manager.js +18 -1
  35. package/dist/cjs/public-api.d.ts +1 -1
  36. package/dist/cjs/test-bridge.d.ts +3 -2
  37. package/dist/cjs/test-bridge.js +34 -7
  38. package/dist/cjs/types/api.types.d.ts +24 -8
  39. package/dist/cjs/types/api.types.js +24 -8
  40. package/dist/cjs/types/event.types.d.ts +2 -3
  41. package/dist/cjs/types/event.types.js +0 -1
  42. package/dist/cjs/types/test-bridge.types.d.ts +2 -1
  43. package/dist/cjs/utils/logging/debug-logger.utils.d.ts +1 -2
  44. package/dist/cjs/utils/logging/debug-logger.utils.js +2 -3
  45. package/dist/cjs/utils/validations/config-validations.utils.d.ts +1 -26
  46. package/dist/cjs/utils/validations/config-validations.utils.js +5 -117
  47. package/dist/cjs/utils/validations/event-validations.utils.d.ts +2 -2
  48. package/dist/cjs/utils/validations/metadata-validations.utils.d.ts +3 -3
  49. package/dist/cjs/utils/validations/metadata-validations.utils.js +41 -3
  50. package/dist/esm/api.d.ts +1 -53
  51. package/dist/esm/api.js +0 -59
  52. package/dist/esm/app.constants.d.ts +1 -1
  53. package/dist/esm/app.d.ts +1 -5
  54. package/dist/esm/app.js +5 -13
  55. package/dist/esm/constants/api.constants.d.ts +5 -2
  56. package/dist/esm/constants/api.constants.js +5 -13
  57. package/dist/esm/constants/config.constants.d.ts +3 -3
  58. package/dist/esm/constants/config.constants.js +3 -3
  59. package/dist/esm/constants/error.constants.d.ts +7 -2
  60. package/dist/esm/constants/error.constants.js +12 -1
  61. package/dist/esm/handlers/click.handler.js +0 -6
  62. package/dist/esm/handlers/error.handler.js +10 -1
  63. package/dist/esm/handlers/scroll.handler.js +0 -5
  64. package/dist/esm/handlers/session.handler.js +5 -2
  65. package/dist/esm/integrations/google-analytics.integration.d.ts +1 -1
  66. package/dist/esm/integrations/google-analytics.integration.js +2 -1
  67. package/dist/esm/managers/api.manager.d.ts +1 -1
  68. package/dist/esm/managers/api.manager.js +3 -3
  69. package/dist/esm/managers/config.builder.d.ts +33 -0
  70. package/dist/esm/managers/config.builder.js +112 -0
  71. package/dist/esm/managers/config.manager.d.ts +13 -14
  72. package/dist/esm/managers/config.manager.js +54 -60
  73. package/dist/esm/managers/event.manager.d.ts +0 -45
  74. package/dist/esm/managers/event.manager.js +14 -67
  75. package/dist/esm/managers/sender.manager.d.ts +1 -28
  76. package/dist/esm/managers/sender.manager.js +44 -74
  77. package/dist/esm/managers/session.manager.d.ts +2 -49
  78. package/dist/esm/managers/session.manager.js +37 -79
  79. package/dist/esm/managers/state.manager.d.ts +1 -28
  80. package/dist/esm/managers/state.manager.js +4 -33
  81. package/dist/esm/managers/storage.manager.d.ts +6 -0
  82. package/dist/esm/managers/storage.manager.js +18 -1
  83. package/dist/esm/public-api.d.ts +1 -1
  84. package/dist/esm/test-bridge.d.ts +3 -2
  85. package/dist/esm/test-bridge.js +34 -7
  86. package/dist/esm/types/api.types.d.ts +24 -8
  87. package/dist/esm/types/api.types.js +24 -8
  88. package/dist/esm/types/event.types.d.ts +2 -3
  89. package/dist/esm/types/event.types.js +0 -1
  90. package/dist/esm/types/test-bridge.types.d.ts +2 -1
  91. package/dist/esm/utils/logging/debug-logger.utils.d.ts +1 -2
  92. package/dist/esm/utils/logging/debug-logger.utils.js +3 -4
  93. package/dist/esm/utils/validations/config-validations.utils.d.ts +1 -26
  94. package/dist/esm/utils/validations/config-validations.utils.js +5 -114
  95. package/dist/esm/utils/validations/event-validations.utils.d.ts +2 -2
  96. package/dist/esm/utils/validations/metadata-validations.utils.d.ts +3 -3
  97. package/dist/esm/utils/validations/metadata-validations.utils.js +41 -3
  98. package/package.json +1 -1
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Special project IDs for testing and development
3
3
  *
4
- * Both automatically force mode: 'debug' but differ in HTTP behavior:
4
+ * All automatically force mode: 'debug' but differ in HTTP behavior:
5
5
  * - Skip: NO network calls (pure offline testing)
6
6
  * - Localhost: Makes network calls to local server (integration testing)
7
+ * - Fail: Makes network calls that intentionally fail (persistence testing)
7
8
  */
8
9
  export declare enum SpecialProjectId {
9
10
  /**
@@ -20,17 +21,32 @@ export declare enum SpecialProjectId {
20
21
  */
21
22
  Skip = "skip",
22
23
  /**
23
- * Value: 'localhost:' (used as prefix)
24
+ * Value: 'localhost:8080'
24
25
  *
25
- * Makes HTTP calls to local development server
26
- * Must specify full address: 'localhost:PORT' (e.g., 'localhost:3000')
27
- * Converts to http://localhost:PORT/config for requests
26
+ * Makes HTTP calls to local development server on port 8080
27
+ * Converts to http://localhost:8080/config for requests
28
28
  * Requires origin to be in ALLOWED_ORIGINS list, forces debug mode
29
29
  * Perfect for local development with running middleware
30
30
  *
31
31
  * @example
32
- * await TraceLog.init({ id: 'localhost:3000' });
33
- * // Makes requests to: http://localhost:3000/config
32
+ * await TraceLog.init({ id: SpecialProjectId.Localhost });
33
+ * // or
34
+ * await TraceLog.init({ id: 'localhost:8080' });
35
+ * // Makes requests to: http://localhost:8080/config
36
+ */
37
+ Localhost = "localhost:8080",
38
+ /**
39
+ * Value: 'localhost:9999'
40
+ *
41
+ * Makes HTTP calls to non-existent server (port 9999)
42
+ * All HTTP requests will fail naturally, triggering persistence
43
+ * Forces debug mode, perfect for testing event persistence & recovery
44
+ *
45
+ * @example
46
+ * await TraceLog.init({ id: SpecialProjectId.Fail });
47
+ * // or
48
+ * await TraceLog.init({ id: 'localhost:9999' });
49
+ * // Makes requests to: http://localhost:9999 (will fail)
34
50
  */
35
- Localhost = "localhost:"
51
+ Fail = "localhost:9999"
36
52
  }
@@ -1,9 +1,10 @@
1
1
  /**
2
2
  * Special project IDs for testing and development
3
3
  *
4
- * Both automatically force mode: 'debug' but differ in HTTP behavior:
4
+ * All automatically force mode: 'debug' but differ in HTTP behavior:
5
5
  * - Skip: NO network calls (pure offline testing)
6
6
  * - Localhost: Makes network calls to local server (integration testing)
7
+ * - Fail: Makes network calls that intentionally fail (persistence testing)
7
8
  */
8
9
  export var SpecialProjectId;
9
10
  (function (SpecialProjectId) {
@@ -21,17 +22,32 @@ export var SpecialProjectId;
21
22
  */
22
23
  SpecialProjectId["Skip"] = "skip";
23
24
  /**
24
- * Value: 'localhost:' (used as prefix)
25
+ * Value: 'localhost:8080'
25
26
  *
26
- * Makes HTTP calls to local development server
27
- * Must specify full address: 'localhost:PORT' (e.g., 'localhost:3000')
28
- * Converts to http://localhost:PORT/config for requests
27
+ * Makes HTTP calls to local development server on port 8080
28
+ * Converts to http://localhost:8080/config for requests
29
29
  * Requires origin to be in ALLOWED_ORIGINS list, forces debug mode
30
30
  * Perfect for local development with running middleware
31
31
  *
32
32
  * @example
33
- * await TraceLog.init({ id: 'localhost:3000' });
34
- * // Makes requests to: http://localhost:3000/config
33
+ * await TraceLog.init({ id: SpecialProjectId.Localhost });
34
+ * // or
35
+ * await TraceLog.init({ id: 'localhost:8080' });
36
+ * // Makes requests to: http://localhost:8080/config
37
+ */
38
+ SpecialProjectId["Localhost"] = "localhost:8080";
39
+ /**
40
+ * Value: 'localhost:9999'
41
+ *
42
+ * Makes HTTP calls to non-existent server (port 9999)
43
+ * All HTTP requests will fail naturally, triggering persistence
44
+ * Forces debug mode, perfect for testing event persistence & recovery
45
+ *
46
+ * @example
47
+ * await TraceLog.init({ id: SpecialProjectId.Fail });
48
+ * // or
49
+ * await TraceLog.init({ id: 'localhost:9999' });
50
+ * // Makes requests to: http://localhost:9999 (will fail)
35
51
  */
36
- SpecialProjectId["Localhost"] = "localhost:";
52
+ SpecialProjectId["Fail"] = "localhost:9999";
37
53
  })(SpecialProjectId || (SpecialProjectId = {}));
@@ -17,8 +17,7 @@ export declare enum ScrollDirection {
17
17
  }
18
18
  export declare enum ErrorType {
19
19
  JS_ERROR = "js_error",
20
- PROMISE_REJECTION = "promise_rejection",
21
- NETWORK_ERROR = "network_error"
20
+ PROMISE_REJECTION = "promise_rejection"
22
21
  }
23
22
  export interface ScrollData {
24
23
  depth: number;
@@ -48,7 +47,7 @@ export interface ClickTrackingElementData {
48
47
  }
49
48
  export interface CustomEventData {
50
49
  name: string;
51
- metadata?: Record<string, MetadataType>;
50
+ metadata?: Record<string, MetadataType> | Record<string, MetadataType>[];
52
51
  }
53
52
  export interface WebVitalsData {
54
53
  type: WebVitalType;
@@ -18,5 +18,4 @@ export var ErrorType;
18
18
  (function (ErrorType) {
19
19
  ErrorType["JS_ERROR"] = "js_error";
20
20
  ErrorType["PROMISE_REJECTION"] = "promise_rejection";
21
- ErrorType["NETWORK_ERROR"] = "network_error";
22
21
  })(ErrorType || (ErrorType = {}));
@@ -18,13 +18,14 @@ export interface TraceLogTestBridge {
18
18
  init(config: AppConfig): Promise<void>;
19
19
  destroy(): Promise<void>;
20
20
  isInitializing(): boolean;
21
- sendCustomEvent(name: string, data?: Record<string, unknown>): void;
21
+ sendCustomEvent(name: string, data?: Record<string, unknown> | Record<string, unknown>[]): void;
22
22
  on(event: string, callback: (data: any) => void): void;
23
23
  off(event: string, callback: (data: any) => void): void;
24
24
  getSessionData(): Record<string, unknown> | null;
25
25
  setSessionTimeout(timeout: number): void;
26
26
  getQueueLength(): number;
27
27
  forceInitLock(enabled?: boolean): void;
28
+ simulatePersistedEvents(events: any[]): void;
28
29
  get<T extends keyof State>(key: T): State[T];
29
30
  getStorageManager(): TraceLogStorageManager | null;
30
31
  getEventManager(): EventManager | null;
@@ -1,5 +1,4 @@
1
- import { StateManager } from '../../managers/state.manager';
2
- declare class DebugLogger extends StateManager {
1
+ declare class DebugLogger {
3
2
  clientError: (ns: string, msg: string, data?: unknown) => void;
4
3
  clientWarn: (ns: string, msg: string, data?: unknown) => void;
5
4
  info: (ns: string, msg: string, data?: unknown) => void;
@@ -1,7 +1,6 @@
1
- import { StateManager } from '../../managers/state.manager';
2
- class DebugLogger extends StateManager {
1
+ import { getGlobalState } from '../../managers/state.manager';
2
+ class DebugLogger {
3
3
  constructor() {
4
- super(...arguments);
5
4
  this.clientError = (ns, msg, data) => this.log('CLIENT_ERROR', ns, msg, data);
6
5
  this.clientWarn = (ns, msg, data) => this.log('CLIENT_WARN', ns, msg, data);
7
6
  this.info = (ns, msg, data) => this.log('INFO', ns, msg, data);
@@ -11,7 +10,7 @@ class DebugLogger extends StateManager {
11
10
  this.verbose = (ns, msg, data) => this.log('VERBOSE', ns, msg, data);
12
11
  }
13
12
  log(level, ns, msg, data) {
14
- const mode = this.get('config')?.mode;
13
+ const mode = getGlobalState()?.config?.mode;
15
14
  if (!this.shouldShow(level, mode))
16
15
  return;
17
16
  const formattedMsg = `[TraceLog:${ns}] ${msg}`;
@@ -1,4 +1,4 @@
1
- import { AppConfig, Config, ApiConfig } from '../../types';
1
+ import { AppConfig, ApiConfig } from '../../types';
2
2
  /**
3
3
  * Validates the app configuration object (before normalization)
4
4
  * This validates the structure and basic types but allows for normalization afterward
@@ -16,31 +16,6 @@ export declare const validateAppConfig: (config: AppConfig) => void;
16
16
  * @throws {AppConfigValidationError} If other configuration validation fails
17
17
  */
18
18
  export declare const validateAndNormalizeConfig: (config: AppConfig) => AppConfig;
19
- /**
20
- * Validates a complete configuration object
21
- * @param config - The configuration to validate
22
- * @returns Validation result with errors and warnings
23
- */
24
- export declare const validateConfig: (config: Config) => {
25
- errors: string[];
26
- warnings: string[];
27
- samplingRate: number;
28
- };
29
- export declare const normalizeConfig: (config: Config) => {
30
- config: Config;
31
- errors: string[];
32
- warnings: string[];
33
- };
34
- /**
35
- * Validates the final configuration
36
- * @param config - The configuration to validate
37
- * @returns Validation result with errors and warnings
38
- */
39
- export declare const validateFinalConfig: (config: Config) => {
40
- errors: string[];
41
- warnings: string[];
42
- samplingRate: number;
43
- };
44
19
  /**
45
20
  * Type guard to check if a JSON response is a valid API config
46
21
  * @param json - The JSON to validate
@@ -1,4 +1,4 @@
1
- import { DEFAULT_SAMPLING_RATE, MAX_SESSION_TIMEOUT_MS, MIN_SESSION_TIMEOUT_MS, VALIDATION_MESSAGES, } from '../../constants';
1
+ import { MAX_SESSION_TIMEOUT_MS, MIN_SESSION_TIMEOUT_MS, VALIDATION_MESSAGES } from '../../constants';
2
2
  import { Mode } from '../../types';
3
3
  import { ProjectIdValidationError, AppConfigValidationError, SessionTimeoutValidationError, SamplingRateValidationError, IntegrationValidationError, } from '../../types/validation-error.types';
4
4
  import { debugLog } from '../logging';
@@ -204,118 +204,6 @@ export const validateAndNormalizeConfig = (config) => {
204
204
  }
205
205
  return normalizedConfig;
206
206
  };
207
- /**
208
- * Validates sampling rate
209
- * @param samplingRate - The sampling rate to validate
210
- * @param errors - Array to push errors to
211
- */
212
- const validateSamplingRate = (samplingRate, errors) => {
213
- if (samplingRate === undefined) {
214
- return undefined;
215
- }
216
- if (typeof samplingRate !== 'number') {
217
- errors.push('samplingRate must be a number');
218
- return DEFAULT_SAMPLING_RATE;
219
- }
220
- if (Number.isNaN(samplingRate) || samplingRate <= 0 || samplingRate > 1) {
221
- errors.push(VALIDATION_MESSAGES.INVALID_SAMPLING_RATE);
222
- return DEFAULT_SAMPLING_RATE;
223
- }
224
- return samplingRate;
225
- };
226
- /**
227
- * Validates excluded URL paths
228
- * @param excludedUrlPaths - The excluded URL paths to validate
229
- * @param errors - Array to push errors to
230
- * @param prefix - Optional prefix for error messages
231
- */
232
- const validateExcludedUrlPaths = (excludedUrlPaths, errors, prefix = '') => {
233
- if (excludedUrlPaths !== undefined) {
234
- if (Array.isArray(excludedUrlPaths)) {
235
- for (const [index, path] of excludedUrlPaths.entries()) {
236
- if (typeof path === 'string') {
237
- try {
238
- new RegExp(path);
239
- }
240
- catch {
241
- errors.push(`${prefix}excludedUrlPaths[${index}] is not a valid regex pattern`);
242
- }
243
- }
244
- else {
245
- errors.push(`${prefix}excludedUrlPaths[${index}] must be a string`);
246
- }
247
- }
248
- }
249
- else {
250
- errors.push(`${prefix}excludedUrlPaths must be an array`);
251
- }
252
- }
253
- };
254
- /**
255
- * Validates a complete configuration object
256
- * @param config - The configuration to validate
257
- * @returns Validation result with errors and warnings
258
- */
259
- export const validateConfig = (config) => {
260
- const errors = [];
261
- const warnings = [];
262
- if (config.sessionTimeout !== undefined) {
263
- if (typeof config.sessionTimeout !== 'number') {
264
- errors.push('sessionTimeout must be a number');
265
- }
266
- else if (config.sessionTimeout < MIN_SESSION_TIMEOUT_MS) {
267
- errors.push('sessionTimeout must be at least 30 seconds (30000ms)');
268
- }
269
- else if (config.sessionTimeout > MAX_SESSION_TIMEOUT_MS) {
270
- warnings.push('sessionTimeout is very long (>24 hours), consider reducing it');
271
- }
272
- }
273
- if (config.globalMetadata !== undefined) {
274
- if (typeof config.globalMetadata !== 'object' || config.globalMetadata === null) {
275
- errors.push('globalMetadata must be an object');
276
- }
277
- else {
278
- const metadataSize = JSON.stringify(config.globalMetadata).length;
279
- if (metadataSize > 10240) {
280
- errors.push('globalMetadata is too large (max 10KB)');
281
- }
282
- if (Object.keys(config.globalMetadata).length > 12) {
283
- errors.push('globalMetadata has too many keys (max 12)');
284
- }
285
- }
286
- }
287
- // No custom API endpoints supported
288
- const validatedSamplingRate = validateSamplingRate(config.samplingRate, errors) ?? DEFAULT_SAMPLING_RATE;
289
- if (config.tags !== undefined && !Array.isArray(config.tags)) {
290
- errors.push('tags must be an array');
291
- }
292
- validateExcludedUrlPaths(config.excludedUrlPaths, errors);
293
- return { errors, warnings, samplingRate: validatedSamplingRate };
294
- };
295
- export const normalizeConfig = (config) => {
296
- const { errors, warnings, samplingRate } = validateConfig(config);
297
- return {
298
- config: {
299
- ...config,
300
- samplingRate,
301
- },
302
- errors,
303
- warnings,
304
- };
305
- };
306
- /**
307
- * Validates the final configuration
308
- * @param config - The configuration to validate
309
- * @returns Validation result with errors and warnings
310
- */
311
- export const validateFinalConfig = (config) => {
312
- const errors = [];
313
- const warnings = [];
314
- const validatedSamplingRate = validateSamplingRate(config.samplingRate, errors) ?? DEFAULT_SAMPLING_RATE;
315
- validateExcludedUrlPaths(config.excludedUrlPaths, errors);
316
- // No custom API endpoints supported
317
- return { errors, warnings, samplingRate: validatedSamplingRate };
318
- };
319
207
  /**
320
208
  * Type guard to check if a JSON response is a valid API config
321
209
  * @param json - The JSON to validate
@@ -329,8 +217,11 @@ export const isValidConfigApiResponse = (json) => {
329
217
  const response = json;
330
218
  const result = {
331
219
  mode: response['mode'] === undefined || [Mode.QA, Mode.DEBUG].includes(response['mode']),
220
+ // Zero is valid for samplingRate (means "sample nothing")
332
221
  samplingRate: response['samplingRate'] === undefined ||
333
- (typeof response['samplingRate'] === 'number' && response['samplingRate'] > 0 && response['samplingRate'] <= 1),
222
+ (typeof response['samplingRate'] === 'number' &&
223
+ response['samplingRate'] >= 0 &&
224
+ response['samplingRate'] <= 1),
334
225
  tags: response['tags'] === undefined || Array.isArray(response['tags']),
335
226
  excludedUrlPaths: response['excludedUrlPaths'] === undefined || Array.isArray(response['excludedUrlPaths']),
336
227
  ipExcluded: response['ipExcluded'] === undefined || typeof response['ipExcluded'] === 'boolean',
@@ -5,8 +5,8 @@ import { MetadataType } from '../../types';
5
5
  * @param metadata - Optional metadata to validate
6
6
  * @returns Validation result with sanitized metadata if valid
7
7
  */
8
- export declare const isEventValid: (eventName: string, metadata?: Record<string, unknown>) => {
8
+ export declare const isEventValid: (eventName: string, metadata?: Record<string, unknown> | Record<string, unknown>[]) => {
9
9
  valid: boolean;
10
10
  error?: string;
11
- sanitizedMetadata?: Record<string, MetadataType>;
11
+ sanitizedMetadata?: Record<string, MetadataType> | Record<string, MetadataType>[];
12
12
  };
@@ -9,14 +9,14 @@ export declare const isValidEventName: (eventName: string) => {
9
9
  error?: string;
10
10
  };
11
11
  /**
12
- * Validates metadata for events
12
+ * Validates metadata for events (supports both objects and arrays of objects)
13
13
  * @param eventName - The event name (for error messages)
14
14
  * @param metadata - The metadata to validate
15
15
  * @param type - Type of metadata (globalMetadata or customEvent)
16
16
  * @returns Validation result with sanitized metadata if valid
17
17
  */
18
- export declare const isValidMetadata: (eventName: string, metadata: Record<string, unknown>, type?: "globalMetadata" | "customEvent") => {
18
+ export declare const isValidMetadata: (eventName: string, metadata: Record<string, unknown> | Record<string, unknown>[], type?: "globalMetadata" | "customEvent") => {
19
19
  valid: boolean;
20
20
  error?: string;
21
- sanitizedMetadata?: Record<string, MetadataType>;
21
+ sanitizedMetadata?: Record<string, MetadataType> | Record<string, MetadataType>[];
22
22
  };
@@ -41,13 +41,13 @@ export const isValidEventName = (eventName) => {
41
41
  return { valid: true };
42
42
  };
43
43
  /**
44
- * Validates metadata for events
44
+ * Validates a single metadata object
45
45
  * @param eventName - The event name (for error messages)
46
- * @param metadata - The metadata to validate
46
+ * @param metadata - The metadata object to validate
47
47
  * @param type - Type of metadata (globalMetadata or customEvent)
48
48
  * @returns Validation result with sanitized metadata if valid
49
49
  */
50
- export const isValidMetadata = (eventName, metadata, type) => {
50
+ const validateSingleMetadata = (eventName, metadata, type) => {
51
51
  const sanitizedMetadata = sanitizeMetadata(metadata);
52
52
  const intro = type && type === 'customEvent' ? `${type} "${eventName}" metadata error` : `${eventName} metadata error`;
53
53
  if (!isOnlyPrimitiveFields(sanitizedMetadata)) {
@@ -108,3 +108,41 @@ export const isValidMetadata = (eventName, metadata, type) => {
108
108
  sanitizedMetadata,
109
109
  };
110
110
  };
111
+ /**
112
+ * Validates metadata for events (supports both objects and arrays of objects)
113
+ * @param eventName - The event name (for error messages)
114
+ * @param metadata - The metadata to validate
115
+ * @param type - Type of metadata (globalMetadata or customEvent)
116
+ * @returns Validation result with sanitized metadata if valid
117
+ */
118
+ export const isValidMetadata = (eventName, metadata, type) => {
119
+ if (Array.isArray(metadata)) {
120
+ const sanitizedArray = [];
121
+ const intro = type && type === 'customEvent' ? `${type} "${eventName}" metadata error` : `${eventName} metadata error`;
122
+ for (let i = 0; i < metadata.length; i++) {
123
+ const item = metadata[i];
124
+ if (typeof item !== 'object' || item === null || Array.isArray(item)) {
125
+ return {
126
+ valid: false,
127
+ error: `${intro}: array item at index ${i} must be an object.`,
128
+ };
129
+ }
130
+ const itemValidation = validateSingleMetadata(eventName, item, type);
131
+ if (!itemValidation.valid) {
132
+ return {
133
+ valid: false,
134
+ error: `${intro}: array item at index ${i} is invalid: ${itemValidation.error}`,
135
+ };
136
+ }
137
+ if (itemValidation.sanitizedMetadata) {
138
+ sanitizedArray.push(itemValidation.sanitizedMetadata);
139
+ }
140
+ }
141
+ // Allow empty arrays after sanitization
142
+ return {
143
+ valid: true,
144
+ sanitizedMetadata: sanitizedArray,
145
+ };
146
+ }
147
+ return validateSingleMetadata(eventName, metadata, type);
148
+ };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@tracelog/lib",
3
3
  "description": "JavaScript library for web analytics and real-time event tracking",
4
4
  "license": "MIT",
5
- "version": "0.4.1",
5
+ "version": "0.5.0",
6
6
  "main": "./dist/cjs/public-api.js",
7
7
  "module": "./dist/esm/public-api.js",
8
8
  "types": "./dist/esm/public-api.d.ts",