@eventvisor/sdk 0.22.0 → 0.23.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventvisor/sdk",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Eventvisor SDK for Node.js and the browser",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.mjs",
@@ -39,7 +39,7 @@
39
39
  },
40
40
  "license": "MIT",
41
41
  "dependencies": {
42
- "@eventvisor/types": "0.22.0"
42
+ "@eventvisor/types": "0.23.0"
43
43
  },
44
- "gitHead": "901456bc9aaeb046c280edc33ad3b1ad81fa3eab"
44
+ "gitHead": "8e89c74cde2a35ab0cf76b00f964cf8e606d9b22"
45
45
  }
@@ -181,4 +181,293 @@ describe("sdk: instance", function () {
181
181
  // should not increase
182
182
  expect(capturedHandles.length).toEqual(1);
183
183
  });
184
+
185
+ describe("skipValidation", function () {
186
+ const baseDatafile: DatafileContent = {
187
+ ...emptyDatafile,
188
+ events: {
189
+ strictEvent: {
190
+ type: "object",
191
+ properties: {
192
+ url: { type: "string" },
193
+ count: { type: "number" },
194
+ },
195
+ required: ["url", "count"],
196
+ },
197
+ },
198
+ destinations: {
199
+ test: {
200
+ transport: "test",
201
+ transforms: [
202
+ { type: "set", value: {} },
203
+ { type: "set", source: "payload", target: "payload" },
204
+ { type: "set", source: "eventName", target: "eventName" },
205
+ ],
206
+ },
207
+ },
208
+ };
209
+
210
+ async function createEventvisorWithDatafile(datafile: DatafileContent) {
211
+ const captured: Record<string, any>[] = [];
212
+ const eventvisor = createInstance({
213
+ datafile,
214
+ modules: [
215
+ {
216
+ name: "test",
217
+ transport: async ({ destinationName, eventName, payload }) => {
218
+ captured.push({ destinationName, eventName, payload });
219
+ },
220
+ },
221
+ ],
222
+ logLevel: "warn",
223
+ });
224
+ await eventvisor.onReady();
225
+ return { eventvisor, captured };
226
+ }
227
+
228
+ it("runs validation when skipValidation is undefined", async function () {
229
+ const { eventvisor, captured } = await createEventvisorWithDatafile(baseDatafile);
230
+
231
+ const validResult = await eventvisor.trackAsync("strictEvent", {
232
+ url: "https://example.com",
233
+ count: 1,
234
+ });
235
+ expect(validResult).not.toBeNull();
236
+ expect(captured.length).toBe(1);
237
+ expect(captured[0].payload.payload).toEqual({
238
+ url: "https://example.com",
239
+ count: 1,
240
+ });
241
+
242
+ const invalidResult = await eventvisor.trackAsync("strictEvent", {
243
+ url: "https://example.com",
244
+ // missing required "count"
245
+ } as any);
246
+ expect(invalidResult).toBeNull();
247
+ expect(captured.length).toBe(1); // still 1, not 2
248
+ });
249
+
250
+ it("runs validation when skipValidation is true", async function () {
251
+ const datafile: DatafileContent = {
252
+ ...baseDatafile,
253
+ events: {
254
+ strictEvent: {
255
+ ...baseDatafile.events!.strictEvent!,
256
+ skipValidation: true,
257
+ } as any,
258
+ },
259
+ };
260
+ const { eventvisor, captured } = await createEventvisorWithDatafile(datafile);
261
+
262
+ const invalidResult = await eventvisor.trackAsync("strictEvent", {
263
+ url: "https://example.com",
264
+ } as any);
265
+ expect(invalidResult).toBeNull();
266
+ expect(captured.length).toBe(0);
267
+ });
268
+
269
+ it("skips validation when skipValidation is false", async function () {
270
+ const datafile: DatafileContent = {
271
+ ...baseDatafile,
272
+ events: {
273
+ strictEvent: {
274
+ ...baseDatafile.events!.strictEvent!,
275
+ skipValidation: false,
276
+ } as any,
277
+ },
278
+ };
279
+ const { eventvisor, captured } = await createEventvisorWithDatafile(datafile);
280
+
281
+ const result = await eventvisor.trackAsync("strictEvent", {
282
+ url: "https://example.com",
283
+ // missing required "count" - would fail validation
284
+ } as any);
285
+ expect(result).not.toBeNull();
286
+ expect(captured.length).toBe(1);
287
+ expect(captured[0].payload.payload).toEqual({ url: "https://example.com" });
288
+ });
289
+
290
+ it("skips validation when skipValidation.conditions do NOT match", async function () {
291
+ const datafile: DatafileContent = {
292
+ ...baseDatafile,
293
+ events: {
294
+ strictEvent: {
295
+ ...baseDatafile.events!.strictEvent!,
296
+ skipValidation: {
297
+ conditions: [
298
+ {
299
+ source: "eventName",
300
+ operator: "equals",
301
+ value: "otherEvent",
302
+ },
303
+ ],
304
+ },
305
+ } as any,
306
+ },
307
+ };
308
+ const { eventvisor, captured } = await createEventvisorWithDatafile(datafile);
309
+
310
+ // eventName is "strictEvent", condition expects "otherEvent" -> not matched -> skip validation
311
+ const result = await eventvisor.trackAsync("strictEvent", {
312
+ url: "https://example.com",
313
+ } as any);
314
+ expect(result).not.toBeNull();
315
+ expect(captured.length).toBe(1);
316
+ expect(captured[0].payload.payload).toEqual({ url: "https://example.com" });
317
+ });
318
+
319
+ it("runs validation when skipValidation.conditions match", async function () {
320
+ const datafile: DatafileContent = {
321
+ ...baseDatafile,
322
+ events: {
323
+ strictEvent: {
324
+ ...baseDatafile.events!.strictEvent!,
325
+ skipValidation: {
326
+ conditions: [
327
+ {
328
+ source: "eventName",
329
+ operator: "equals",
330
+ value: "strictEvent",
331
+ },
332
+ ],
333
+ },
334
+ } as any,
335
+ },
336
+ };
337
+ const { eventvisor, captured } = await createEventvisorWithDatafile(datafile);
338
+
339
+ // condition matches -> validate -> invalid payload fails
340
+ const invalidResult = await eventvisor.trackAsync("strictEvent", {
341
+ url: "https://example.com",
342
+ } as any);
343
+ expect(invalidResult).toBeNull();
344
+ expect(captured.length).toBe(0);
345
+
346
+ // valid payload still passes
347
+ const validResult = await eventvisor.trackAsync("strictEvent", {
348
+ url: "https://example.com",
349
+ count: 2,
350
+ });
351
+ expect(validResult).not.toBeNull();
352
+ expect(captured.length).toBe(1);
353
+ expect(captured[0].payload.payload).toEqual({
354
+ url: "https://example.com",
355
+ count: 2,
356
+ });
357
+ });
358
+
359
+ it("skipValidation.conditions with payload source: skips when condition does not match", async function () {
360
+ const datafile: DatafileContent = {
361
+ ...baseDatafile,
362
+ events: {
363
+ strictEvent: {
364
+ ...baseDatafile.events!.strictEvent!,
365
+ skipValidation: {
366
+ conditions: [
367
+ {
368
+ payload: "skip",
369
+ operator: "equals",
370
+ value: true,
371
+ },
372
+ ],
373
+ },
374
+ } as any,
375
+ },
376
+ };
377
+ const { eventvisor, captured } = await createEventvisorWithDatafile(datafile);
378
+
379
+ // payload.skip !== true -> conditions not matched -> skip validation
380
+ const result = await eventvisor.trackAsync("strictEvent", {
381
+ url: "https://example.com",
382
+ } as any);
383
+ expect(result).not.toBeNull();
384
+ expect(captured.length).toBe(1);
385
+
386
+ // payload.skip === true -> conditions matched -> run validation -> invalid fails
387
+ const result2 = await eventvisor.trackAsync("strictEvent", {
388
+ url: "https://example.com",
389
+ skip: true,
390
+ } as any);
391
+ expect(result2).toBeNull();
392
+ expect(captured.length).toBe(1);
393
+ });
394
+
395
+ it("skipValidation.conditions with multiple conditions (and semantics): all must match to run validation", async function () {
396
+ const datafile: DatafileContent = {
397
+ ...baseDatafile,
398
+ events: {
399
+ strictEvent: {
400
+ ...baseDatafile.events!.strictEvent!,
401
+ skipValidation: {
402
+ conditions: [
403
+ { source: "eventName", operator: "equals", value: "strictEvent" },
404
+ { payload: "env", operator: "equals", value: "prod" },
405
+ ],
406
+ },
407
+ } as any,
408
+ },
409
+ };
410
+ const { eventvisor, captured } = await createEventvisorWithDatafile(datafile);
411
+
412
+ // only eventName matches, env !== "prod" -> conditions not all matched -> skip validation
413
+ const result = await eventvisor.trackAsync("strictEvent", {
414
+ url: "https://example.com",
415
+ env: "dev",
416
+ } as any);
417
+ expect(result).not.toBeNull();
418
+ expect(captured.length).toBe(1);
419
+
420
+ // both match -> validate -> invalid (missing count) fails
421
+ const result2 = await eventvisor.trackAsync("strictEvent", {
422
+ url: "https://example.com",
423
+ env: "prod",
424
+ } as any);
425
+ expect(result2).toBeNull();
426
+ expect(captured.length).toBe(1);
427
+ });
428
+
429
+ it("skipValidation as empty object does not skip validation", async function () {
430
+ const datafile: DatafileContent = {
431
+ ...baseDatafile,
432
+ events: {
433
+ strictEvent: {
434
+ ...baseDatafile.events!.strictEvent!,
435
+ skipValidation: {},
436
+ } as any,
437
+ },
438
+ };
439
+ const { eventvisor, captured } = await createEventvisorWithDatafile(datafile);
440
+
441
+ const invalidResult = await eventvisor.trackAsync("strictEvent", {
442
+ url: "https://example.com",
443
+ } as any);
444
+ expect(invalidResult).toBeNull();
445
+ expect(captured.length).toBe(0);
446
+ });
447
+
448
+ it("skipValidation.conditions with single condition as object (not array)", async function () {
449
+ const datafile: DatafileContent = {
450
+ ...baseDatafile,
451
+ events: {
452
+ strictEvent: {
453
+ ...baseDatafile.events!.strictEvent!,
454
+ skipValidation: {
455
+ conditions: {
456
+ source: "eventName",
457
+ operator: "equals",
458
+ value: "otherEvent",
459
+ },
460
+ },
461
+ } as any,
462
+ },
463
+ };
464
+ const { eventvisor, captured } = await createEventvisorWithDatafile(datafile);
465
+
466
+ const result = await eventvisor.trackAsync("strictEvent", {
467
+ url: "https://example.com",
468
+ } as any);
469
+ expect(result).not.toBeNull();
470
+ expect(captured.length).toBe(1);
471
+ });
472
+ });
184
473
  });
package/src/instance.ts CHANGED
@@ -331,20 +331,54 @@ export class Eventvisor {
331
331
  /**
332
332
  * Validate
333
333
  */
334
- let error: Error | undefined = value instanceof Error ? value : undefined;
335
- const validationResult = await this.validator.validate(eventSchema, value);
334
+ let shouldValidate = true;
335
+
336
+ if (typeof eventSchema.skipValidation !== "undefined") {
337
+ if (eventSchema.skipValidation === false) {
338
+ // boolean
339
+ shouldValidate = false;
340
+ } else if (
341
+ typeof eventSchema.skipValidation === "object" &&
342
+ eventSchema.skipValidation.conditions
343
+ ) {
344
+ const isMatched = await this.conditionsChecker.allAreMatched(
345
+ eventSchema.skipValidation.conditions,
346
+ {
347
+ eventName,
348
+ eventLevel,
349
+ payload: value,
350
+ },
351
+ );
352
+
353
+ if (!isMatched) {
354
+ shouldValidate = false;
355
+ }
356
+ }
357
+ }
358
+
359
+ let validatedValue: Value | undefined = undefined;
360
+ let error = value instanceof Error ? value : undefined;
336
361
 
337
- if (!validationResult.valid) {
338
- this.logger.warn(`Event validation failed`, {
362
+ if (shouldValidate) {
363
+ const validationResult = await this.validator.validate(eventSchema, value);
364
+
365
+ if (!validationResult.valid) {
366
+ this.logger.warn(`Event validation failed`, {
367
+ eventName,
368
+ errors: validationResult.errors,
369
+ });
370
+
371
+ return null; // @TODO: allow to continue based on schema later
372
+ }
373
+
374
+ validatedValue = validationResult.value;
375
+ } else {
376
+ this.logger.debug(`Event validation skipped`, {
339
377
  eventName,
340
- errors: validationResult.errors,
341
378
  });
342
-
343
- return null; // @TODO: allow to continue based on schema later
379
+ validatedValue = value;
344
380
  }
345
381
 
346
- const validatedValue = validationResult.value;
347
-
348
382
  /**
349
383
  * Conditions
350
384
  */