@eventvisor/sdk 0.21.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/CHANGELOG.md +19 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.gz +0 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/instance.spec.ts +289 -0
- package/src/instance.ts +43 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eventvisor/sdk",
|
|
3
|
-
"version": "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.
|
|
42
|
+
"@eventvisor/types": "0.23.0"
|
|
43
43
|
},
|
|
44
|
-
"gitHead": "
|
|
44
|
+
"gitHead": "8e89c74cde2a35ab0cf76b00f964cf8e606d9b22"
|
|
45
45
|
}
|
package/src/instance.spec.ts
CHANGED
|
@@ -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
|
|
335
|
-
|
|
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 (
|
|
338
|
-
this.
|
|
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
|
*/
|