@mirafive/protocol 2.0.0 → 2.1.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/README.md +12 -0
- package/dist/cli.js +141 -113
- package/dist/index.js +144 -113
- package/dist/index.js.map +4 -4
- package/dist/wire.d.ts +21 -1
- package/fixtures/batches/invalid/deal-lost-without-deal.json +13 -0
- package/fixtures/batches/invalid/deal-stage-without-stage.json +13 -0
- package/fixtures/batches/invalid/deal-value-not-integer.json +15 -0
- package/fixtures/batches/valid/server-deals.json +28 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,6 +28,18 @@ integration, to validate batches, or to port MIRA FIVE to another language.
|
|
|
28
28
|
| `@mirafive/protocol/fixtures/event-ids.json` | `eventIdFor` test vectors for SDKs in other languages. |
|
|
29
29
|
| `@mirafive/protocol/fixtures/batches/{valid,invalid}/*.json` | Batches the ingest accepts and rejects, for SDK test suites. |
|
|
30
30
|
|
|
31
|
+
## Deal events
|
|
32
|
+
|
|
33
|
+
A project's pipeline (its sales stages) can be fed by any events, mapped in MIRA FIVE's settings.
|
|
34
|
+
Two built-in events need no mapping at all; send them with a secret key and a person:
|
|
35
|
+
|
|
36
|
+
| Event | Properties | Meaning |
|
|
37
|
+
| ------------- | ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
38
|
+
| `$deal_stage` | `deal_id`, `stage`, optional `value` (whole minor units of the reporting currency) | The deal reached this stage. The latest `value` is the deal's expected value. |
|
|
39
|
+
| `$deal_lost` | `deal_id`, optional `reason` | The deal will not reach the end of the pipeline. |
|
|
40
|
+
|
|
41
|
+
`@mirafive/node` (`dealStage`, `dealLost`), `@mirafive/convex` and `mirafive/sdk-php` send them.
|
|
42
|
+
|
|
31
43
|
## The wire in one request
|
|
32
44
|
|
|
33
45
|
```sh
|
package/dist/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ var LIMITS = {
|
|
|
15
15
|
idLength: 200,
|
|
16
16
|
maxClientAgeDays: 7
|
|
17
17
|
};
|
|
18
|
+
var DEAL_LIMITS = { idLength: 200, stageLength: 120, reasonLength: 200 };
|
|
18
19
|
var consentStates = ["granted", "denied", "unknown"];
|
|
19
20
|
var externalIdTypes = [
|
|
20
21
|
"stripe_customer",
|
|
@@ -163,125 +164,152 @@ var properties = (value, path, issues) => {
|
|
|
163
164
|
issues.push({ path, message: `at most ${LIMITS.propertyKeys} properties per event` });
|
|
164
165
|
}
|
|
165
166
|
};
|
|
166
|
-
var
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
167
|
+
var built;
|
|
168
|
+
function rules() {
|
|
169
|
+
if (built)
|
|
170
|
+
return built;
|
|
171
|
+
const short = string({ max: LIMITS.stringLength });
|
|
172
|
+
const url = string({ max: LIMITS.urlLength });
|
|
173
|
+
const size = integer(0, 20000);
|
|
174
|
+
const context = object({
|
|
175
|
+
page: object({ url, referrer: url, title: short }),
|
|
176
|
+
app: object({ name: short, version: short, build: short, namespace: short }),
|
|
177
|
+
device: object({
|
|
178
|
+
type: oneOf(["desktop", "mobile", "tablet", "tv", "other"]),
|
|
179
|
+
manufacturer: short,
|
|
180
|
+
model: short
|
|
181
|
+
}),
|
|
182
|
+
os: object({ name: short, version: short }),
|
|
183
|
+
screen: object({ width: size, height: size, density: number({ positive: true, max: 10 }) }),
|
|
184
|
+
viewport: object({ width: size, height: size }),
|
|
185
|
+
locale: string({ max: 35 }),
|
|
186
|
+
languages: (value, path, issues) => {
|
|
187
|
+
if (!Array.isArray(value)) {
|
|
188
|
+
issues.push({ path, message: "expected a list" });
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (value.length > 10)
|
|
192
|
+
issues.push({ path, message: "at most 10 languages" });
|
|
193
|
+
value.forEach((entry, index) => string({ max: 35 })(entry, join(path, index), issues));
|
|
194
|
+
},
|
|
195
|
+
timezone: string({ max: 64 }),
|
|
196
|
+
location: object({
|
|
197
|
+
latitude: number({ min: -90, max: 90 }),
|
|
198
|
+
longitude: number({ min: -180, max: 180 })
|
|
199
|
+
}, ["latitude", "longitude"]),
|
|
200
|
+
library: object({ name: string({ max: 50 }), version: string({ max: 30 }) }, [
|
|
201
|
+
"name",
|
|
202
|
+
"version"
|
|
203
|
+
]),
|
|
204
|
+
ip: string({ max: 45 }),
|
|
205
|
+
user_agent: string({ max: 1024 })
|
|
206
|
+
});
|
|
207
|
+
const consentState = oneOf(consentStates);
|
|
208
|
+
const consent = (value, path, issues) => {
|
|
209
|
+
if (value === "not_applicable")
|
|
184
210
|
return;
|
|
211
|
+
if (!isObject(value)) {
|
|
212
|
+
{
|
|
213
|
+
issues.push({ path, message: 'expected { analytics, marketing } or "not_applicable"' });
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
185
216
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
217
|
+
object({ analytics: consentState, marketing: consentState }, ["analytics"])(value, path, issues);
|
|
218
|
+
};
|
|
219
|
+
const minor = integer(-1000000000000000, 1000000000000000);
|
|
220
|
+
const id = string({ max: LIMITS.idLength });
|
|
221
|
+
const revenue = object({
|
|
222
|
+
amount: minor,
|
|
223
|
+
currency: string({ min: 3, max: 3, hint: "an ISO 4217 code" }),
|
|
224
|
+
entry_id: id,
|
|
225
|
+
kind: oneOf(revenueKinds),
|
|
226
|
+
fee: minor,
|
|
227
|
+
tax: minor,
|
|
228
|
+
refund_of: id,
|
|
229
|
+
product: id,
|
|
230
|
+
subscription: object({
|
|
231
|
+
id: string({ min: 1, max: LIMITS.idLength }),
|
|
232
|
+
interval: oneOf(billingIntervals),
|
|
233
|
+
interval_count: integer(1, 36),
|
|
234
|
+
period_start: dateTime,
|
|
235
|
+
period_end: dateTime
|
|
236
|
+
}, ["id"])
|
|
237
|
+
}, ["amount", "currency"]);
|
|
238
|
+
const externalIds = (value, path, issues) => {
|
|
239
|
+
if (!isObject(value)) {
|
|
240
|
+
issues.push({ path, message: "expected an object" });
|
|
206
241
|
return;
|
|
207
242
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
kind: oneOf(revenueKinds),
|
|
218
|
-
fee: minor,
|
|
219
|
-
tax: minor,
|
|
220
|
-
refund_of: id,
|
|
221
|
-
product: id,
|
|
222
|
-
subscription: object({
|
|
223
|
-
id: string({ min: 1, max: LIMITS.idLength }),
|
|
224
|
-
interval: oneOf(billingIntervals),
|
|
225
|
-
interval_count: integer(1, 36),
|
|
226
|
-
period_start: dateTime,
|
|
227
|
-
period_end: dateTime
|
|
228
|
-
}, ["id"])
|
|
229
|
-
}, ["amount", "currency"]);
|
|
230
|
-
var externalIds = (value, path, issues) => {
|
|
231
|
-
if (!isObject(value)) {
|
|
232
|
-
issues.push({ path, message: "expected an object" });
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
for (const [key, item] of Object.entries(value)) {
|
|
236
|
-
if (!externalIdTypes.includes(key)) {
|
|
237
|
-
issues.push({
|
|
238
|
-
path: join(path, key),
|
|
239
|
-
message: `expected one of ${externalIdTypes.join(", ")}`
|
|
240
|
-
});
|
|
241
|
-
} else if (item !== undefined) {
|
|
242
|
-
string({ min: 1, max: LIMITS.idLength })(item, join(path, key), issues);
|
|
243
|
+
for (const [key, item] of Object.entries(value)) {
|
|
244
|
+
if (!externalIdTypes.includes(key)) {
|
|
245
|
+
issues.push({
|
|
246
|
+
path: join(path, key),
|
|
247
|
+
message: `expected one of ${externalIdTypes.join(", ")}`
|
|
248
|
+
});
|
|
249
|
+
} else if (item !== undefined) {
|
|
250
|
+
string({ min: 1, max: LIMITS.idLength })(item, join(path, key), issues);
|
|
251
|
+
}
|
|
243
252
|
}
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
253
|
+
};
|
|
254
|
+
const text = string({ max: 200, trim: true });
|
|
255
|
+
const touchpoint = object({
|
|
256
|
+
kind: oneOf(touchpointKinds),
|
|
257
|
+
channel: oneOf(touchpointChannels),
|
|
258
|
+
source: text,
|
|
259
|
+
medium: text,
|
|
260
|
+
campaign: text,
|
|
261
|
+
content: text,
|
|
262
|
+
campaign_id: text,
|
|
263
|
+
ad_group_id: text,
|
|
264
|
+
ad_id: text,
|
|
265
|
+
code: text,
|
|
266
|
+
answer: string({ max: 500, trim: true }),
|
|
267
|
+
external_ref: text
|
|
268
|
+
}, ["kind"]);
|
|
269
|
+
const event = object({
|
|
270
|
+
event_id: string({ max: 36, pattern: UUID, hint: "expected a UUID" }),
|
|
271
|
+
type: oneOf(["track", "page", "screen", "identify", "group"]),
|
|
272
|
+
event: string({
|
|
273
|
+
min: 1,
|
|
274
|
+
max: LIMITS.nameLength,
|
|
275
|
+
pattern: /^\P{Cc}+$/u,
|
|
276
|
+
hint: "no control characters"
|
|
277
|
+
}),
|
|
278
|
+
timestamp: dateTime,
|
|
279
|
+
anonymous_id: id,
|
|
280
|
+
user_id: id,
|
|
281
|
+
session_id: id,
|
|
282
|
+
group_id: id,
|
|
283
|
+
properties,
|
|
284
|
+
traits: properties,
|
|
285
|
+
external_ids: externalIds,
|
|
286
|
+
revenue,
|
|
287
|
+
context,
|
|
288
|
+
consent
|
|
289
|
+
}, ["event_id", "type", "event"]);
|
|
290
|
+
const dealId = string({ min: 1, max: DEAL_LIMITS.idLength });
|
|
291
|
+
const dealStage = object({
|
|
292
|
+
deal_id: dealId,
|
|
293
|
+
stage: string({ min: 1, max: DEAL_LIMITS.stageLength }),
|
|
294
|
+
value: integer(0, 1000000000000000)
|
|
295
|
+
}, ["deal_id", "stage"]);
|
|
296
|
+
const dealLost = object({ deal_id: dealId, reason: string({ max: DEAL_LIMITS.reasonLength }) }, [
|
|
297
|
+
"deal_id"
|
|
298
|
+
]);
|
|
299
|
+
built = { event, touchpoint, dealStage, dealLost };
|
|
300
|
+
return built;
|
|
301
|
+
}
|
|
282
302
|
function validateEvent(value, path = "") {
|
|
283
303
|
const issues = [];
|
|
304
|
+
const { event, touchpoint, dealStage, dealLost } = rules();
|
|
284
305
|
event(value, path, issues);
|
|
306
|
+
if (isObject(value) && (value.event === "$deal_stage" || value.event === "$deal_lost")) {
|
|
307
|
+
if (value.type !== "track") {
|
|
308
|
+
issues.push({ path: join(path, "type"), message: `${value.event} is a track event` });
|
|
309
|
+
}
|
|
310
|
+
const props = isObject(value.properties) ? value.properties : {};
|
|
311
|
+
(value.event === "$deal_stage" ? dealStage : dealLost)(props, join(path, "properties"), issues);
|
|
312
|
+
}
|
|
285
313
|
if (isObject(value) && value.event === "$touchpoint") {
|
|
286
314
|
if (value.type !== "track") {
|
|
287
315
|
issues.push({ path: join(path, "type"), message: "$touchpoint is a track event" });
|
|
@@ -323,10 +351,10 @@ function validateBatch(value) {
|
|
|
323
351
|
|
|
324
352
|
// src/cli.ts
|
|
325
353
|
var file = process.argv[2];
|
|
326
|
-
var
|
|
354
|
+
var text = readFileSync(file && file !== "-" ? file : 0, "utf8");
|
|
327
355
|
var input;
|
|
328
356
|
try {
|
|
329
|
-
input = JSON.parse(
|
|
357
|
+
input = JSON.parse(text);
|
|
330
358
|
} catch (error) {
|
|
331
359
|
console.error(`✗ not JSON: ${String(error)}`);
|
|
332
360
|
process.exit(1);
|
package/dist/index.js
CHANGED
|
@@ -24,8 +24,11 @@ var reservedEvents = [
|
|
|
24
24
|
"$engagement",
|
|
25
25
|
"$outbound_click",
|
|
26
26
|
"$file_download",
|
|
27
|
-
"$form_submit"
|
|
27
|
+
"$form_submit",
|
|
28
|
+
"$deal_stage",
|
|
29
|
+
"$deal_lost"
|
|
28
30
|
];
|
|
31
|
+
var DEAL_LIMITS = { idLength: 200, stageLength: 120, reasonLength: 200 };
|
|
29
32
|
var consentStates = ["granted", "denied", "unknown"];
|
|
30
33
|
var externalIdTypes = [
|
|
31
34
|
"stripe_customer",
|
|
@@ -307,125 +310,152 @@ var properties = (value, path, issues) => {
|
|
|
307
310
|
issues.push({ path, message: `at most ${LIMITS.propertyKeys} properties per event` });
|
|
308
311
|
}
|
|
309
312
|
};
|
|
310
|
-
var
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
313
|
+
var built;
|
|
314
|
+
function rules() {
|
|
315
|
+
if (built)
|
|
316
|
+
return built;
|
|
317
|
+
const short = string({ max: LIMITS.stringLength });
|
|
318
|
+
const url = string({ max: LIMITS.urlLength });
|
|
319
|
+
const size = integer(0, 20000);
|
|
320
|
+
const context = object({
|
|
321
|
+
page: object({ url, referrer: url, title: short }),
|
|
322
|
+
app: object({ name: short, version: short, build: short, namespace: short }),
|
|
323
|
+
device: object({
|
|
324
|
+
type: oneOf(["desktop", "mobile", "tablet", "tv", "other"]),
|
|
325
|
+
manufacturer: short,
|
|
326
|
+
model: short
|
|
327
|
+
}),
|
|
328
|
+
os: object({ name: short, version: short }),
|
|
329
|
+
screen: object({ width: size, height: size, density: number({ positive: true, max: 10 }) }),
|
|
330
|
+
viewport: object({ width: size, height: size }),
|
|
331
|
+
locale: string({ max: 35 }),
|
|
332
|
+
languages: (value, path, issues) => {
|
|
333
|
+
if (!Array.isArray(value)) {
|
|
334
|
+
issues.push({ path, message: "expected a list" });
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (value.length > 10)
|
|
338
|
+
issues.push({ path, message: "at most 10 languages" });
|
|
339
|
+
value.forEach((entry, index) => string({ max: 35 })(entry, join(path, index), issues));
|
|
340
|
+
},
|
|
341
|
+
timezone: string({ max: 64 }),
|
|
342
|
+
location: object({
|
|
343
|
+
latitude: number({ min: -90, max: 90 }),
|
|
344
|
+
longitude: number({ min: -180, max: 180 })
|
|
345
|
+
}, ["latitude", "longitude"]),
|
|
346
|
+
library: object({ name: string({ max: 50 }), version: string({ max: 30 }) }, [
|
|
347
|
+
"name",
|
|
348
|
+
"version"
|
|
349
|
+
]),
|
|
350
|
+
ip: string({ max: 45 }),
|
|
351
|
+
user_agent: string({ max: 1024 })
|
|
352
|
+
});
|
|
353
|
+
const consentState = oneOf(consentStates);
|
|
354
|
+
const consent = (value, path, issues) => {
|
|
355
|
+
if (value === "not_applicable")
|
|
328
356
|
return;
|
|
357
|
+
if (!isObject(value)) {
|
|
358
|
+
{
|
|
359
|
+
issues.push({ path, message: 'expected { analytics, marketing } or "not_applicable"' });
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
329
362
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
363
|
+
object({ analytics: consentState, marketing: consentState }, ["analytics"])(value, path, issues);
|
|
364
|
+
};
|
|
365
|
+
const minor = integer(-1000000000000000, 1000000000000000);
|
|
366
|
+
const id = string({ max: LIMITS.idLength });
|
|
367
|
+
const revenue = object({
|
|
368
|
+
amount: minor,
|
|
369
|
+
currency: string({ min: 3, max: 3, hint: "an ISO 4217 code" }),
|
|
370
|
+
entry_id: id,
|
|
371
|
+
kind: oneOf(revenueKinds),
|
|
372
|
+
fee: minor,
|
|
373
|
+
tax: minor,
|
|
374
|
+
refund_of: id,
|
|
375
|
+
product: id,
|
|
376
|
+
subscription: object({
|
|
377
|
+
id: string({ min: 1, max: LIMITS.idLength }),
|
|
378
|
+
interval: oneOf(billingIntervals),
|
|
379
|
+
interval_count: integer(1, 36),
|
|
380
|
+
period_start: dateTime,
|
|
381
|
+
period_end: dateTime
|
|
382
|
+
}, ["id"])
|
|
383
|
+
}, ["amount", "currency"]);
|
|
384
|
+
const externalIds = (value, path, issues) => {
|
|
385
|
+
if (!isObject(value)) {
|
|
386
|
+
issues.push({ path, message: "expected an object" });
|
|
350
387
|
return;
|
|
351
388
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
kind: oneOf(revenueKinds),
|
|
362
|
-
fee: minor,
|
|
363
|
-
tax: minor,
|
|
364
|
-
refund_of: id,
|
|
365
|
-
product: id,
|
|
366
|
-
subscription: object({
|
|
367
|
-
id: string({ min: 1, max: LIMITS.idLength }),
|
|
368
|
-
interval: oneOf(billingIntervals),
|
|
369
|
-
interval_count: integer(1, 36),
|
|
370
|
-
period_start: dateTime,
|
|
371
|
-
period_end: dateTime
|
|
372
|
-
}, ["id"])
|
|
373
|
-
}, ["amount", "currency"]);
|
|
374
|
-
var externalIds = (value, path, issues) => {
|
|
375
|
-
if (!isObject(value)) {
|
|
376
|
-
issues.push({ path, message: "expected an object" });
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
for (const [key, item] of Object.entries(value)) {
|
|
380
|
-
if (!externalIdTypes.includes(key)) {
|
|
381
|
-
issues.push({
|
|
382
|
-
path: join(path, key),
|
|
383
|
-
message: `expected one of ${externalIdTypes.join(", ")}`
|
|
384
|
-
});
|
|
385
|
-
} else if (item !== undefined) {
|
|
386
|
-
string({ min: 1, max: LIMITS.idLength })(item, join(path, key), issues);
|
|
389
|
+
for (const [key, item] of Object.entries(value)) {
|
|
390
|
+
if (!externalIdTypes.includes(key)) {
|
|
391
|
+
issues.push({
|
|
392
|
+
path: join(path, key),
|
|
393
|
+
message: `expected one of ${externalIdTypes.join(", ")}`
|
|
394
|
+
});
|
|
395
|
+
} else if (item !== undefined) {
|
|
396
|
+
string({ min: 1, max: LIMITS.idLength })(item, join(path, key), issues);
|
|
397
|
+
}
|
|
387
398
|
}
|
|
388
|
-
}
|
|
389
|
-
};
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
399
|
+
};
|
|
400
|
+
const text = string({ max: 200, trim: true });
|
|
401
|
+
const touchpoint = object({
|
|
402
|
+
kind: oneOf(touchpointKinds),
|
|
403
|
+
channel: oneOf(touchpointChannels),
|
|
404
|
+
source: text,
|
|
405
|
+
medium: text,
|
|
406
|
+
campaign: text,
|
|
407
|
+
content: text,
|
|
408
|
+
campaign_id: text,
|
|
409
|
+
ad_group_id: text,
|
|
410
|
+
ad_id: text,
|
|
411
|
+
code: text,
|
|
412
|
+
answer: string({ max: 500, trim: true }),
|
|
413
|
+
external_ref: text
|
|
414
|
+
}, ["kind"]);
|
|
415
|
+
const event = object({
|
|
416
|
+
event_id: string({ max: 36, pattern: UUID2, hint: "expected a UUID" }),
|
|
417
|
+
type: oneOf(["track", "page", "screen", "identify", "group"]),
|
|
418
|
+
event: string({
|
|
419
|
+
min: 1,
|
|
420
|
+
max: LIMITS.nameLength,
|
|
421
|
+
pattern: /^\P{Cc}+$/u,
|
|
422
|
+
hint: "no control characters"
|
|
423
|
+
}),
|
|
424
|
+
timestamp: dateTime,
|
|
425
|
+
anonymous_id: id,
|
|
426
|
+
user_id: id,
|
|
427
|
+
session_id: id,
|
|
428
|
+
group_id: id,
|
|
429
|
+
properties,
|
|
430
|
+
traits: properties,
|
|
431
|
+
external_ids: externalIds,
|
|
432
|
+
revenue,
|
|
433
|
+
context,
|
|
434
|
+
consent
|
|
435
|
+
}, ["event_id", "type", "event"]);
|
|
436
|
+
const dealId = string({ min: 1, max: DEAL_LIMITS.idLength });
|
|
437
|
+
const dealStage = object({
|
|
438
|
+
deal_id: dealId,
|
|
439
|
+
stage: string({ min: 1, max: DEAL_LIMITS.stageLength }),
|
|
440
|
+
value: integer(0, 1000000000000000)
|
|
441
|
+
}, ["deal_id", "stage"]);
|
|
442
|
+
const dealLost = object({ deal_id: dealId, reason: string({ max: DEAL_LIMITS.reasonLength }) }, [
|
|
443
|
+
"deal_id"
|
|
444
|
+
]);
|
|
445
|
+
built = { event, touchpoint, dealStage, dealLost };
|
|
446
|
+
return built;
|
|
447
|
+
}
|
|
426
448
|
function validateEvent(value, path = "") {
|
|
427
449
|
const issues = [];
|
|
450
|
+
const { event, touchpoint, dealStage, dealLost } = rules();
|
|
428
451
|
event(value, path, issues);
|
|
452
|
+
if (isObject(value) && (value.event === "$deal_stage" || value.event === "$deal_lost")) {
|
|
453
|
+
if (value.type !== "track") {
|
|
454
|
+
issues.push({ path: join(path, "type"), message: `${value.event} is a track event` });
|
|
455
|
+
}
|
|
456
|
+
const props = isObject(value.properties) ? value.properties : {};
|
|
457
|
+
(value.event === "$deal_stage" ? dealStage : dealLost)(props, join(path, "properties"), issues);
|
|
458
|
+
}
|
|
429
459
|
if (isObject(value) && value.event === "$touchpoint") {
|
|
430
460
|
if (value.type !== "track") {
|
|
431
461
|
issues.push({ path: join(path, "type"), message: "$touchpoint is a track event" });
|
|
@@ -465,6 +495,7 @@ function validateBatch(value) {
|
|
|
465
495
|
return issues;
|
|
466
496
|
}
|
|
467
497
|
export {
|
|
498
|
+
DEAL_LIMITS,
|
|
468
499
|
DEFAULT_HOST,
|
|
469
500
|
LIMITS,
|
|
470
501
|
backoffMs,
|
|
@@ -492,5 +523,5 @@ export {
|
|
|
492
523
|
validateEvent
|
|
493
524
|
};
|
|
494
525
|
|
|
495
|
-
//# debugId=
|
|
526
|
+
//# debugId=0FB51ED08C63485464756E2164756E21
|
|
496
527
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/wire.ts", "../src/helpers.ts", "../src/properties.ts", "../src/uuid.ts", "../src/validate.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * The MIRA FIVE wire format, as plain TypeScript: what every SDK sends to `/v1/batch`.\n *\n * The server validates it with Zod (`packages/schema` in the MIRA FIVE monorepo); `wire.test.ts`\n * proves both describe the same shape, so these types cannot drift from what the ingest accepts.\n * Keeping Zod out of here keeps every SDK free of runtime dependencies.\n */\n\n/** The ingest every SDK talks to unless told otherwise. */\nexport const DEFAULT_HOST = \"https://events.mirafive.io\";\n\n/** Limits the ingest enforces. SDKs apply them before sending, so one bad value never costs an event. */\nexport const LIMITS = {\n eventsPerBatch: 100,\n nameLength: 120,\n propertyKeys: 64,\n propertyKeyLength: 100,\n stringLength: 2048,\n arrayLength: 50,\n urlLength: 4096,\n idLength: 200,\n /** Events older than this are rejected unless they come with a secret key (backfills). */\n maxClientAgeDays: 7,\n} as const;\n\n/** Event names MIRA defines. Your own event names must not start with `$`. */\nexport const reservedEvents = [\n \"$pageview\",\n \"$screen\",\n \"$identify\",\n \"$group\",\n \"$consent_changed\",\n \"$revenue\",\n \"$app_install\",\n \"$app_open\",\n \"$touchpoint\",\n \"$engagement\",\n \"$outbound_click\",\n \"$file_download\",\n \"$form_submit\",\n] as const;\nexport type ReservedEvent = (typeof reservedEvents)[number];\n\n/** One purpose's state. `unknown`: the banner is still open, so the event is collected at tier 0. */\nexport const consentStates = [\"granted\", \"denied\", \"unknown\"] as const;\nexport type ConsentState = (typeof consentStates)[number];\n\n/**\n * The consent an event was collected under. Server SDKs send `not_applicable` for facts only the\n * server knows, such as a paid invoice.\n */\nexport type Consent = { analytics: ConsentState; marketing?: ConsentState } | \"not_applicable\";\n\n/** Property values: scalars, or arrays of scalars. SDKs flatten nested objects to dot keys. */\nexport type PropertyValue = string | number | boolean | null | (string | number | boolean)[];\nexport type Properties = Record<string, PropertyValue>;\n\n/** Ids of the same person in other systems, so connectors can link their money to the user. */\nexport const externalIdTypes = [\n \"stripe_customer\",\n \"revenuecat_app_user\",\n \"shopify_customer\",\n \"shopware_customer\",\n] as const;\nexport type ExternalIdType = (typeof externalIdTypes)[number];\nexport type ExternalIds = Partial<Record<ExternalIdType, string>>;\n\n/** What a ledger entry is: one-time money, a subscription payment, or money taken back. */\nexport const revenueKinds = [\n \"charge\",\n \"subscription_payment\",\n \"refund\",\n \"dispute\",\n \"dispute_won\",\n] as const;\nexport type RevenueKind = (typeof revenueKinds)[number];\n\n/** How often a subscription bills: every `interval_count` days, weeks, months or years. */\nexport const billingIntervals = [\"day\", \"week\", \"month\", \"year\"] as const;\nexport type BillingInterval = (typeof billingIntervals)[number];\n\n/** Money on an event, in integer minor units. Only secret keys may send it. */\nexport interface Revenue {\n /** Gross, tax included. Negative for a refund or dispute. */\n amount: number;\n /** ISO 4217, for example `EUR`. */\n currency: string;\n /** The provider object (`stripe:ch_123`); the ledger keeps one entry per id. */\n entry_id?: string;\n kind?: RevenueKind;\n fee?: number;\n tax?: number;\n refund_of?: string;\n product?: string;\n subscription?: {\n id: string;\n interval?: BillingInterval;\n interval_count?: number;\n period_start?: string;\n period_end?: string;\n };\n}\n\n/** Touchpoints that are not a visit: a survey answer, an offline contact, a redeemed code. */\nexport const touchpointKinds = [\"survey\", \"offline\", \"code\", \"custom\"] as const;\nexport type TouchpointKind = (typeof touchpointKinds)[number];\n\n/** The kinds a public key (browser, app) may send. The others need a secret key. */\nexport const publicTouchpointKinds = [\"survey\"] as const satisfies readonly TouchpointKind[];\n\n/** MIRA's channels a touchpoint may name. */\nexport const touchpointChannels = [\n \"paid_search\",\n \"paid_social\",\n \"paid_other\",\n \"organic_search\",\n \"organic_social\",\n \"ai_assistant\",\n \"email\",\n \"affiliate\",\n \"display\",\n \"video\",\n \"qr\",\n \"app_store\",\n \"referral\",\n \"direct\",\n] as const;\nexport type TouchpointChannel = (typeof touchpointChannels)[number];\n\n/** What the SDK knows about where and how the event happened. */\nexport interface EventContext {\n page?: { url?: string; referrer?: string; title?: string };\n app?: { name?: string; version?: string; build?: string; namespace?: string };\n device?: {\n type?: \"desktop\" | \"mobile\" | \"tablet\" | \"tv\" | \"other\";\n manufacturer?: string;\n model?: string;\n };\n os?: { name?: string; version?: string };\n screen?: { width?: number; height?: number; density?: number };\n viewport?: { width?: number; height?: number };\n locale?: string;\n languages?: string[];\n timezone?: string;\n /** A location the site or app already has. MIRA rounds it to 2 decimals (about 1 km). */\n location?: { latitude: number; longitude: number };\n library?: { name: string; version: string };\n /** Server SDKs only: the end user's IP and user agent, for geo and device. Never stored. */\n ip?: string;\n user_agent?: string;\n}\n\n/** One event on the wire. */\nexport interface WireEvent {\n /** UUIDv7 made by the SDK. The same id twice is one event. */\n event_id: string;\n type: \"track\" | \"page\" | \"screen\" | \"identify\" | \"group\";\n event: string;\n timestamp?: string;\n /** Only with analytics consent. */\n anonymous_id?: string;\n user_id?: string;\n session_id?: string;\n group_id?: string;\n properties?: Properties;\n traits?: Properties;\n external_ids?: ExternalIds;\n revenue?: Revenue;\n context?: EventContext;\n consent?: Consent;\n}\n\n/** A batch, to `/v1/batch` (JSON) or `/v1/p` (`text/plain`, from `sendBeacon`). */\nexport interface WireBatch {\n key?: string;\n sent_at?: string;\n /** A random id per SDK instance, in memory only. */\n stream?: string;\n /** The stream's batch number from 1. A retry resends the same number. */\n seq?: number;\n events: WireEvent[];\n}\n\n/** The ingest's answer. Per-event problems are listed, never thrown. */\nexport interface BatchResult {\n accepted: number;\n duplicates: number;\n rejected: { index: number; reason: string }[];\n}\n",
|
|
5
|
+
"/**\n * The MIRA FIVE wire format, as plain TypeScript: what every SDK sends to `/v1/batch`.\n *\n * The server validates it with Zod (`packages/schema` in the MIRA FIVE monorepo); `wire.test.ts`\n * proves both describe the same shape, so these types cannot drift from what the ingest accepts.\n * Keeping Zod out of here keeps every SDK free of runtime dependencies.\n */\n\n/** The ingest every SDK talks to unless told otherwise. */\nexport const DEFAULT_HOST = \"https://events.mirafive.io\";\n\n/** Limits the ingest enforces. SDKs apply them before sending, so one bad value never costs an event. */\nexport const LIMITS = {\n eventsPerBatch: 100,\n nameLength: 120,\n propertyKeys: 64,\n propertyKeyLength: 100,\n stringLength: 2048,\n arrayLength: 50,\n urlLength: 4096,\n idLength: 200,\n /** Events older than this are rejected unless they come with a secret key (backfills). */\n maxClientAgeDays: 7,\n} as const;\n\n/** Event names MIRA defines. Your own event names must not start with `$`. */\nexport const reservedEvents = [\n \"$pageview\",\n \"$screen\",\n \"$identify\",\n \"$group\",\n \"$consent_changed\",\n \"$revenue\",\n \"$app_install\",\n \"$app_open\",\n \"$touchpoint\",\n \"$engagement\",\n \"$outbound_click\",\n \"$file_download\",\n \"$form_submit\",\n // A deal of the project's pipeline reached a stage, or was lost (secret keys only).\n \"$deal_stage\",\n \"$deal_lost\",\n] as const;\nexport type ReservedEvent = (typeof reservedEvents)[number];\n\n/**\n * The properties of `$deal_stage`: one deal (a case, an order, an opportunity) reached a stage\n * of the project's pipeline. `value` is what the deal is expected to be worth now, in minor units\n * of the project's reporting currency; MIRA FIVE keeps the latest. Other properties are kept.\n */\nexport interface DealStageProperties {\n deal_id: string;\n stage: string;\n value?: number;\n}\n\n/** The properties of `$deal_lost`: the deal will not reach the end of the pipeline. */\nexport interface DealLostProperties {\n deal_id: string;\n reason?: string;\n}\n\nexport const DEAL_LIMITS = { idLength: 200, stageLength: 120, reasonLength: 200 } as const;\n\n/** One purpose's state. `unknown`: the banner is still open, so the event is collected at tier 0. */\nexport const consentStates = [\"granted\", \"denied\", \"unknown\"] as const;\nexport type ConsentState = (typeof consentStates)[number];\n\n/**\n * The consent an event was collected under. Server SDKs send `not_applicable` for facts only the\n * server knows, such as a paid invoice.\n */\nexport type Consent = { analytics: ConsentState; marketing?: ConsentState } | \"not_applicable\";\n\n/** Property values: scalars, or arrays of scalars. SDKs flatten nested objects to dot keys. */\nexport type PropertyValue = string | number | boolean | null | (string | number | boolean)[];\nexport type Properties = Record<string, PropertyValue>;\n\n/** Ids of the same person in other systems, so connectors can link their money to the user. */\nexport const externalIdTypes = [\n \"stripe_customer\",\n \"revenuecat_app_user\",\n \"shopify_customer\",\n \"shopware_customer\",\n] as const;\nexport type ExternalIdType = (typeof externalIdTypes)[number];\nexport type ExternalIds = Partial<Record<ExternalIdType, string>>;\n\n/** What a ledger entry is: one-time money, a subscription payment, or money taken back. */\nexport const revenueKinds = [\n \"charge\",\n \"subscription_payment\",\n \"refund\",\n \"dispute\",\n \"dispute_won\",\n] as const;\nexport type RevenueKind = (typeof revenueKinds)[number];\n\n/** How often a subscription bills: every `interval_count` days, weeks, months or years. */\nexport const billingIntervals = [\"day\", \"week\", \"month\", \"year\"] as const;\nexport type BillingInterval = (typeof billingIntervals)[number];\n\n/** Money on an event, in integer minor units. Only secret keys may send it. */\nexport interface Revenue {\n /** Gross, tax included. Negative for a refund or dispute. */\n amount: number;\n /** ISO 4217, for example `EUR`. */\n currency: string;\n /** The provider object (`stripe:ch_123`); the ledger keeps one entry per id. */\n entry_id?: string;\n kind?: RevenueKind;\n fee?: number;\n tax?: number;\n refund_of?: string;\n product?: string;\n subscription?: {\n id: string;\n interval?: BillingInterval;\n interval_count?: number;\n period_start?: string;\n period_end?: string;\n };\n}\n\n/** Touchpoints that are not a visit: a survey answer, an offline contact, a redeemed code. */\nexport const touchpointKinds = [\"survey\", \"offline\", \"code\", \"custom\"] as const;\nexport type TouchpointKind = (typeof touchpointKinds)[number];\n\n/** The kinds a public key (browser, app) may send. The others need a secret key. */\nexport const publicTouchpointKinds = [\"survey\"] as const satisfies readonly TouchpointKind[];\n\n/** MIRA's channels a touchpoint may name. */\nexport const touchpointChannels = [\n \"paid_search\",\n \"paid_social\",\n \"paid_other\",\n \"organic_search\",\n \"organic_social\",\n \"ai_assistant\",\n \"email\",\n \"affiliate\",\n \"display\",\n \"video\",\n \"qr\",\n \"app_store\",\n \"referral\",\n \"direct\",\n] as const;\nexport type TouchpointChannel = (typeof touchpointChannels)[number];\n\n/** What the SDK knows about where and how the event happened. */\nexport interface EventContext {\n page?: { url?: string; referrer?: string; title?: string };\n app?: { name?: string; version?: string; build?: string; namespace?: string };\n device?: {\n type?: \"desktop\" | \"mobile\" | \"tablet\" | \"tv\" | \"other\";\n manufacturer?: string;\n model?: string;\n };\n os?: { name?: string; version?: string };\n screen?: { width?: number; height?: number; density?: number };\n viewport?: { width?: number; height?: number };\n locale?: string;\n languages?: string[];\n timezone?: string;\n /** A location the site or app already has. MIRA rounds it to 2 decimals (about 1 km). */\n location?: { latitude: number; longitude: number };\n library?: { name: string; version: string };\n /** Server SDKs only: the end user's IP and user agent, for geo and device. Never stored. */\n ip?: string;\n user_agent?: string;\n}\n\n/** One event on the wire. */\nexport interface WireEvent {\n /** UUIDv7 made by the SDK. The same id twice is one event. */\n event_id: string;\n type: \"track\" | \"page\" | \"screen\" | \"identify\" | \"group\";\n event: string;\n timestamp?: string;\n /** Only with analytics consent. */\n anonymous_id?: string;\n user_id?: string;\n session_id?: string;\n group_id?: string;\n properties?: Properties;\n traits?: Properties;\n external_ids?: ExternalIds;\n revenue?: Revenue;\n context?: EventContext;\n consent?: Consent;\n}\n\n/** A batch, to `/v1/batch` (JSON) or `/v1/p` (`text/plain`, from `sendBeacon`). */\nexport interface WireBatch {\n key?: string;\n sent_at?: string;\n /** A random id per SDK instance, in memory only. */\n stream?: string;\n /** The stream's batch number from 1. A retry resends the same number. */\n seq?: number;\n events: WireEvent[];\n}\n\n/** The ingest's answer. Per-event problems are listed, never thrown. */\nexport interface BatchResult {\n accepted: number;\n duplicates: number;\n rejected: { index: number; reason: string }[];\n}\n",
|
|
6
6
|
"import type { Consent, ConsentState } from \"./wire\";\n\n/** What callers may pass for one purpose: a plain boolean from a custom banner, or a wire state. */\nexport type ConsentInput = boolean | ConsentState | undefined;\n\n/** `true` → granted, `false` → denied, nothing → unknown. */\nexport const consentState = (value: ConsentInput): ConsentState =>\n value === true ? \"granted\" : value === false ? \"denied\" : (value ?? \"unknown\");\n\n/** Consent for an event from booleans or states; `not_applicable` passes through. */\nexport function toConsent(\n input: \"not_applicable\" | { analytics: ConsentInput; marketing?: ConsentInput } | undefined,\n fallback: Consent = \"not_applicable\",\n): Consent {\n if (input === undefined) return fallback;\n if (input === \"not_applicable\") return input;\n return { analytics: consentState(input.analytics), marketing: consentState(input.marketing) };\n}\n\n/**\n * Which kind of key this is. Public keys (`mira_pk_…`) belong in browsers and apps; secret keys\n * (`mira_sk_…`) only on servers, because they may send revenue and backfills.\n */\nexport function keyKind(key: string | undefined): \"public\" | \"secret\" | \"unknown\" {\n if (!key) return \"unknown\";\n if (key.startsWith(\"mira_pk_\")) return \"public\";\n if (key.startsWith(\"mira_sk_\")) return \"secret\";\n return \"unknown\";\n}\n\n/** Strips trailing slashes, so `host + \"/v1/batch\"` is always one URL. */\nexport const trimHost = (host: string) => host.replace(/\\/+$/, \"\");\n\n/** Answers worth retrying: timeouts, rate limits and server errors. Other 4xx are final. */\nexport const retryable = (status: number) => status === 408 || status === 429 || status >= 500;\n\n/**\n * Exponential backoff with full jitter, capped, but never sooner than the server asked\n * (`Retry-After`, in milliseconds).\n */\nexport function backoffMs(attempt: number, atLeastMs = 0, baseMs = 500, capMs = 30_000) {\n const ceiling = Math.min(capMs, baseMs * 2 ** attempt);\n return Math.max(atLeastMs, Math.random() * ceiling);\n}\n\n/** `Retry-After` in milliseconds: seconds or an HTTP date. 0 when absent or unreadable. */\nexport function retryAfterMs(header: string | null, now = Date.now()): number {\n if (!header) return 0;\n const seconds = Number(header);\n if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);\n const at = Date.parse(header);\n return Number.isNaN(at) ? 0 : Math.max(0, at - now);\n}\n\nconst UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;\n\n/** A UUID of version 1 to 8 in the RFC 9562 variant, as the ingest accepts for `event_id`. */\nexport const isUuid = (value: string) => UUID.test(value);\n\n/**\n * A stable event id from your own key, such as `order:123:paid`. The same key always gives the\n * same UUID, in every MIRA FIVE SDK and language, so a retried job, a backfill run twice, or the\n * browser and the server reporting one conversion all count once.\n * Format: UUIDv8 (RFC 9562) from the first 122 bits of SHA-256.\n */\nexport async function eventIdFor(key: string): Promise<string> {\n const digest = new Uint8Array(\n await crypto.subtle.digest(\"SHA-256\", new TextEncoder().encode(key)),\n );\n digest[6] = (digest[6]! & 0x0f) | 0x80;\n digest[8] = (digest[8]! & 0x3f) | 0x80;\n const hex = Array.from(digest.subarray(0, 16), (byte) => byte.toString(16).padStart(2, \"0\")).join(\n \"\",\n );\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n",
|
|
7
7
|
"import { externalIdTypes, LIMITS, type ExternalIds, type Properties } from \"./wire\";\n\nconst MAX_KEYS = LIMITS.propertyKeys;\nconst MAX_STRING = LIMITS.stringLength;\nconst MAX_KEY = LIMITS.propertyKeyLength;\nconst MAX_ARRAY = LIMITS.arrayLength;\nconst MAX_DEPTH = 3;\n\ntype Scalar = string | number | boolean | null;\n\nfunction scalar(value: unknown): Scalar | undefined {\n if (value === null || typeof value === \"boolean\") return value;\n if (typeof value === \"string\") return value.slice(0, MAX_STRING);\n if (typeof value === \"number\") return Number.isFinite(value) ? value : null;\n if (typeof value === \"bigint\") return value.toString();\n if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value.toISOString();\n return undefined;\n}\n\n/**\n * Turns whatever the caller passes into properties the ingest accepts: nested objects are\n * flattened to dot keys (`plan.name`), dates become ISO strings, functions and `undefined` are\n * dropped, and every limit is applied here. The ingest validates each event on its own and\n * rejects the whole event on a violation, so trimming here keeps the rest of the data.\n */\nexport function cleanProperties(input: unknown): Properties {\n const output: Properties = {};\n let count = 0;\n\n const walk = (value: unknown, prefix: string, depth: number) => {\n if (!value || typeof value !== \"object\" || value instanceof Date) return;\n for (const [rawKey, item] of Object.entries(value)) {\n if (count >= MAX_KEYS) return;\n const key = (prefix ? `${prefix}.${rawKey}` : rawKey).slice(0, MAX_KEY);\n if (!key) continue;\n if (Array.isArray(item)) {\n const list = item\n .map(scalar)\n .filter(\n (entry): entry is string | number | boolean => entry !== undefined && entry !== null,\n );\n output[key] = list.slice(0, MAX_ARRAY);\n count += 1;\n continue;\n }\n const flat = scalar(item);\n if (flat !== undefined) {\n output[key] = flat;\n count += 1;\n } else if (item && typeof item === \"object\" && depth < MAX_DEPTH) {\n walk(item, key, depth + 1);\n }\n }\n };\n\n walk(input, \"\", 0);\n return output;\n}\n\n/**\n * Known types with usable values only; `undefined` when nothing is left. An id is never cut\n * short: a truncated id would link the money to the wrong person, so an overlong one is dropped.\n */\nexport function cleanExternalIds(input: unknown): ExternalIds | undefined {\n if (!input || typeof input !== \"object\") return undefined;\n const ids: ExternalIds = {};\n let found = false;\n for (const type of externalIdTypes) {\n const value: unknown = Reflect.get(input, type);\n if (typeof value === \"string\" && value.trim() && value.length <= LIMITS.idLength) {\n ids[type] = value;\n found = true;\n }\n }\n return found ? ids : undefined;\n}\n",
|
|
8
8
|
"/**\n * UUIDv7 (RFC 9562): 48 bits of Unix milliseconds, then randomness. Ids sort by creation time,\n * which keeps ClickHouse inserts local and makes a retried batch easy to recognise.\n *\n * Monotonic within one page or process: when two ids fall into the same millisecond, the 12-bit `rand_a`\n * field is used as a counter (RFC 9562, section 6.2, method 1), so ids made in a loop still sort\n * in the order they were made. If the counter overflows, the timestamp is moved forward by 1 ms.\n */\nlet lastMs = 0;\nlet counter = 0;\n\nconst hex = (bytes: Uint8Array) =>\n Array.from(bytes, (byte) => byte.toString(16).padStart(2, \"0\")).join(\"\");\n\nexport function uuidv7(now: number = Date.now()): string {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n\n if (now > lastMs) {\n lastMs = now;\n // Start each millisecond at a random point in the lower half, so there is room to count up.\n counter = ((bytes[6]! & 0x07) << 8) | bytes[7]!;\n } else {\n counter += 1;\n if (counter > 0xfff) {\n lastMs += 1;\n counter = 0;\n }\n }\n\n // 48-bit timestamp, big-endian. Division instead of bit shifts: shifts are 32-bit in JavaScript.\n let ms = lastMs;\n for (let index = 5; index >= 0; index -= 1) {\n bytes[index] = ms % 256;\n ms = Math.floor(ms / 256);\n }\n bytes[6] = 0x70 | (counter >> 8);\n bytes[7] = counter & 0xff;\n bytes[8] = (bytes[8]! & 0x3f) | 0x80;\n\n const value = hex(bytes);\n return `${value.slice(0, 8)}-${value.slice(8, 12)}-${value.slice(12, 16)}-${value.slice(16, 20)}-${value.slice(20)}`;\n}\n",
|
|
9
|
-
"/**\n * Validates events and batches exactly as the MIRA FIVE ingest does, without dependencies. For\n * SDK test suites, for integrations in other languages (`mirafive-validate`, see `cli.ts`), and\n * for checking an event before sending it.\n *\n * The rules mirror the ingest's own schema; the MIRA FIVE monorepo runs the same inputs through\n * both on every build and fails when they disagree.\n */\nimport {\n billingIntervals,\n consentStates,\n externalIdTypes,\n LIMITS,\n revenueKinds,\n touchpointChannels,\n touchpointKinds,\n} from \"./wire\";\n\n/** One problem: where (`events.0.revenue.amount`) and what. */\nexport interface Issue {\n path: string;\n message: string;\n}\n\ntype Check = (value: unknown, path: string, issues: Issue[]) => void;\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst join = (path: string, key: string | number) => (path ? `${path}.${key}` : String(key));\n\nconst UUID =\n /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/;\nconst DATETIME =\n /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.\\d+)?(?:Z|([+-])(\\d{2}):(\\d{2}))$/;\n\n/** ISO 8601 date and time with `Z` or an offset, as the ingest accepts it. */\nexport function isDateTime(value: string): boolean {\n const match = DATETIME.exec(value);\n if (!match) return false;\n const [, year, month, day, hour, minute, second, , offsetHour, offsetMinute] = match;\n const days = new Date(Date.UTC(Number(year), Number(month), 0)).getUTCDate();\n return (\n Number(month) >= 1 &&\n Number(month) <= 12 &&\n Number(day) >= 1 &&\n Number(day) <= days &&\n Number(hour) <= 23 &&\n Number(minute) <= 59 &&\n Number(second) <= 59 &&\n (offsetHour === undefined || (Number(offsetHour) <= 23 && Number(offsetMinute) <= 59))\n );\n}\n\nconst string =\n (options: {\n min?: number;\n max: number;\n trim?: boolean;\n pattern?: RegExp;\n hint?: string;\n }): Check =>\n (value, path, issues) => {\n if (typeof value !== \"string\") {\n issues.push({ path, message: \"expected a string\" });\n return;\n }\n const text = options.trim ? value.trim() : value;\n if (options.min !== undefined && text.length < options.min) {\n issues.push({ path, message: `at least ${options.min} character(s)` });\n } else if (text.length > options.max) {\n issues.push({ path, message: `at most ${options.max} characters` });\n }\n if (options.pattern && !options.pattern.test(text)) {\n issues.push({ path, message: options.hint ?? \"invalid format\" });\n }\n };\n\nconst dateTime: Check = (value, path, issues) => {\n if (typeof value !== \"string\" || !isDateTime(value)) {\n issues.push({ path, message: \"expected an ISO 8601 date and time with Z or an offset\" });\n }\n};\n\nconst oneOf =\n (values: readonly string[]): Check =>\n (value, path, issues) => {\n if (typeof value !== \"string\" || !values.includes(value)) {\n issues.push({ path, message: `expected one of ${values.join(\", \")}` });\n }\n };\n\nconst integer =\n (min: number, max: number): Check =>\n (value, path, issues) => {\n if (typeof value !== \"number\" || !Number.isInteger(value)) {\n {\n issues.push({ path, message: \"expected an integer\" });\n return;\n }\n }\n if (value < min || value > max) issues.push({ path, message: `expected ${min} to ${max}` });\n };\n\nconst number =\n (options: { min?: number; max?: number; positive?: boolean; int?: boolean }): Check =>\n (value, path, issues) => {\n if (typeof value !== \"number\" || !Number.isFinite(value)) {\n {\n issues.push({ path, message: \"expected a number\" });\n return;\n }\n }\n if (options.int && !Number.isInteger(value))\n issues.push({ path, message: \"expected an integer\" });\n if (options.positive && value <= 0)\n issues.push({ path, message: \"expected a positive number\" });\n if (options.min !== undefined && value < options.min) {\n issues.push({ path, message: `at least ${options.min}` });\n }\n if (options.max !== undefined && value > options.max) {\n issues.push({ path, message: `at most ${options.max}` });\n }\n };\n\n/** An object with optional fields; unknown fields are allowed (the ingest ignores them). */\nconst object =\n (fields: Record<string, Check>, required: string[] = []): Check =>\n (value, path, issues) => {\n if (!isObject(value)) {\n issues.push({ path, message: \"expected an object\" });\n return;\n }\n for (const [key, check] of Object.entries(fields)) {\n if (value[key] === undefined) {\n if (required.includes(key)) issues.push({ path: join(path, key), message: \"required\" });\n continue;\n }\n check(value[key], join(path, key), issues);\n }\n };\n\nconst scalar = (value: unknown) =>\n typeof value === \"boolean\" ||\n (typeof value === \"number\" && Number.isFinite(value)) ||\n (typeof value === \"string\" && value.length <= LIMITS.stringLength);\n\nconst properties: Check = (value, path, issues) => {\n if (!isObject(value)) {\n issues.push({ path, message: \"expected an object\" });\n return;\n }\n const keys = Object.keys(value);\n for (const key of keys) {\n const at = join(path, key);\n if (key.length < 1 || key.length > LIMITS.propertyKeyLength) {\n issues.push({ path: at, message: `keys have 1 to ${LIMITS.propertyKeyLength} characters` });\n }\n const item = value[key];\n if (item === null || scalar(item)) continue;\n if (Array.isArray(item)) {\n if (item.length > LIMITS.arrayLength) {\n issues.push({ path: at, message: `at most ${LIMITS.arrayLength} items` });\n }\n item.forEach((entry, index) => {\n if (!scalar(entry)) {\n issues.push({ path: join(at, index), message: \"expected a string, number or boolean\" });\n }\n });\n continue;\n }\n issues.push({\n path: at,\n message:\n typeof item === \"string\"\n ? `at most ${LIMITS.stringLength} characters`\n : \"expected a string, number, boolean, null or a list of them (nested objects are flattened by the SDKs)\",\n });\n }\n if (keys.length > LIMITS.propertyKeys) {\n issues.push({ path, message: `at most ${LIMITS.propertyKeys} properties per event` });\n }\n};\n\nconst short = string({ max: LIMITS.stringLength });\nconst url = string({ max: LIMITS.urlLength });\nconst size = integer(0, 20_000);\n\nconst context = object({\n page: object({ url, referrer: url, title: short }),\n app: object({ name: short, version: short, build: short, namespace: short }),\n device: object({\n type: oneOf([\"desktop\", \"mobile\", \"tablet\", \"tv\", \"other\"]),\n manufacturer: short,\n model: short,\n }),\n os: object({ name: short, version: short }),\n screen: object({ width: size, height: size, density: number({ positive: true, max: 10 }) }),\n viewport: object({ width: size, height: size }),\n locale: string({ max: 35 }),\n languages: (value, path, issues) => {\n if (!Array.isArray(value)) {\n issues.push({ path, message: \"expected a list\" });\n return;\n }\n if (value.length > 10) issues.push({ path, message: \"at most 10 languages\" });\n value.forEach((entry, index) => string({ max: 35 })(entry, join(path, index), issues));\n },\n timezone: string({ max: 64 }),\n location: object(\n {\n latitude: number({ min: -90, max: 90 }),\n longitude: number({ min: -180, max: 180 }),\n },\n [\"latitude\", \"longitude\"],\n ),\n library: object({ name: string({ max: 50 }), version: string({ max: 30 }) }, [\"name\", \"version\"]),\n ip: string({ max: 45 }),\n user_agent: string({ max: 1024 }),\n});\n\nconst consentState = oneOf(consentStates);\nconst consent: Check = (value, path, issues) => {\n if (value === \"not_applicable\") return;\n if (!isObject(value)) {\n {\n issues.push({ path, message: 'expected { analytics, marketing } or \"not_applicable\"' });\n return;\n }\n }\n object({ analytics: consentState, marketing: consentState }, [\"analytics\"])(value, path, issues);\n};\n\nconst minor = integer(-1e15, 1e15);\nconst id = string({ max: LIMITS.idLength });\nconst revenue = object(\n {\n amount: minor,\n currency: string({ min: 3, max: 3, hint: \"an ISO 4217 code\" }),\n entry_id: id,\n kind: oneOf(revenueKinds),\n fee: minor,\n tax: minor,\n refund_of: id,\n product: id,\n subscription: object(\n {\n id: string({ min: 1, max: LIMITS.idLength }),\n interval: oneOf(billingIntervals),\n interval_count: integer(1, 36),\n period_start: dateTime,\n period_end: dateTime,\n },\n [\"id\"],\n ),\n },\n [\"amount\", \"currency\"],\n);\n\nconst externalIds: Check = (value, path, issues) => {\n if (!isObject(value)) {\n issues.push({ path, message: \"expected an object\" });\n return;\n }\n for (const [key, item] of Object.entries(value)) {\n if (!(externalIdTypes as readonly string[]).includes(key)) {\n issues.push({\n path: join(path, key),\n message: `expected one of ${externalIdTypes.join(\", \")}`,\n });\n } else if (item !== undefined) {\n string({ min: 1, max: LIMITS.idLength })(item, join(path, key), issues);\n }\n }\n};\n\nconst text = string({ max: 200, trim: true });\nconst touchpoint = object(\n {\n kind: oneOf(touchpointKinds),\n channel: oneOf(touchpointChannels),\n source: text,\n medium: text,\n campaign: text,\n content: text,\n campaign_id: text,\n ad_group_id: text,\n ad_id: text,\n code: text,\n answer: string({ max: 500, trim: true }),\n external_ref: text,\n },\n [\"kind\"],\n);\n\nconst event = object(\n {\n event_id: string({ max: 36, pattern: UUID, hint: \"expected a UUID\" }),\n type: oneOf([\"track\", \"page\", \"screen\", \"identify\", \"group\"]),\n event: string({\n min: 1,\n max: LIMITS.nameLength,\n pattern: /^\\P{Cc}+$/u,\n hint: \"no control characters\",\n }),\n timestamp: dateTime,\n anonymous_id: id,\n user_id: id,\n session_id: id,\n group_id: id,\n properties,\n traits: properties,\n external_ids: externalIds,\n revenue,\n context,\n consent,\n },\n [\"event_id\", \"type\", \"event\"],\n);\n\n/** Every problem the ingest would reject this event for; an empty list means it is accepted. */\nexport function validateEvent(value: unknown, path = \"\"): Issue[] {\n const issues: Issue[] = [];\n event(value, path, issues);\n if (isObject(value) && value.event === \"$touchpoint\") {\n if (value.type !== \"track\") {\n issues.push({ path: join(path, \"type\"), message: \"$touchpoint is a track event\" });\n }\n const props = isObject(value.properties) ? value.properties : {};\n const before = issues.length;\n touchpoint(props, join(path, \"properties\"), issues);\n const source = typeof props.source === \"string\" ? props.source.trim() : \"\";\n if (issues.length === before && !props.channel && !source) {\n issues.push({\n path: join(path, \"properties.source\"),\n message: \"a touchpoint needs a channel or a source\",\n });\n }\n }\n return issues;\n}\n\n/**\n * Problems with the batch envelope (key, sent_at, stream, seq, 1 to 100 events) and with each\n * event. The ingest rejects a bad envelope as a whole and a bad event on its own.\n */\nexport function validateBatch(value: unknown): Issue[] {\n const issues: Issue[] = [];\n object({\n key: string({ max: 100 }),\n sent_at: dateTime,\n stream: string({ min: 1, max: 64 }),\n seq: integer(1, 2_147_483_647),\n })(value, \"\", issues);\n if (!isObject(value)) return issues;\n const events = value.events;\n if (!Array.isArray(events)) {\n issues.push({ path: \"events\", message: \"expected a list of events\" });\n return issues;\n }\n if (events.length < 1 || events.length > LIMITS.eventsPerBatch) {\n issues.push({ path: \"events\", message: `1 to ${LIMITS.eventsPerBatch} events per batch` });\n }\n events.forEach((item, index) => issues.push(...validateEvent(item, join(\"events\", index))));\n return issues;\n}\n"
|
|
9
|
+
"/**\n * Validates events and batches exactly as the MIRA FIVE ingest does, without dependencies. For\n * SDK test suites, for integrations in other languages (`mirafive-validate`, see `cli.ts`), and\n * for checking an event before sending it.\n *\n * The rules mirror the ingest's own schema; the MIRA FIVE monorepo runs the same inputs through\n * both on every build and fails when they disagree.\n */\nimport {\n billingIntervals,\n DEAL_LIMITS,\n consentStates,\n externalIdTypes,\n LIMITS,\n revenueKinds,\n touchpointChannels,\n touchpointKinds,\n} from \"./wire\";\n\n/** One problem: where (`events.0.revenue.amount`) and what. */\nexport interface Issue {\n path: string;\n message: string;\n}\n\ntype Check = (value: unknown, path: string, issues: Issue[]) => void;\n\nconst isObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst join = (path: string, key: string | number) => (path ? `${path}.${key}` : String(key));\n\nconst UUID =\n /^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-8][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}|00000000-0000-0000-0000-000000000000|ffffffff-ffff-ffff-ffff-ffffffffffff)$/;\nconst DATETIME =\n /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.\\d+)?(?:Z|([+-])(\\d{2}):(\\d{2}))$/;\n\n/** ISO 8601 date and time with `Z` or an offset, as the ingest accepts it. */\nexport function isDateTime(value: string): boolean {\n const match = DATETIME.exec(value);\n if (!match) return false;\n const [, year, month, day, hour, minute, second, , offsetHour, offsetMinute] = match;\n const days = new Date(Date.UTC(Number(year), Number(month), 0)).getUTCDate();\n return (\n Number(month) >= 1 &&\n Number(month) <= 12 &&\n Number(day) >= 1 &&\n Number(day) <= days &&\n Number(hour) <= 23 &&\n Number(minute) <= 59 &&\n Number(second) <= 59 &&\n (offsetHour === undefined || (Number(offsetHour) <= 23 && Number(offsetMinute) <= 59))\n );\n}\n\nconst string =\n (options: {\n min?: number;\n max: number;\n trim?: boolean;\n pattern?: RegExp;\n hint?: string;\n }): Check =>\n (value, path, issues) => {\n if (typeof value !== \"string\") {\n issues.push({ path, message: \"expected a string\" });\n return;\n }\n const text = options.trim ? value.trim() : value;\n if (options.min !== undefined && text.length < options.min) {\n issues.push({ path, message: `at least ${options.min} character(s)` });\n } else if (text.length > options.max) {\n issues.push({ path, message: `at most ${options.max} characters` });\n }\n if (options.pattern && !options.pattern.test(text)) {\n issues.push({ path, message: options.hint ?? \"invalid format\" });\n }\n };\n\nconst dateTime: Check = (value, path, issues) => {\n if (typeof value !== \"string\" || !isDateTime(value)) {\n issues.push({ path, message: \"expected an ISO 8601 date and time with Z or an offset\" });\n }\n};\n\nconst oneOf =\n (values: readonly string[]): Check =>\n (value, path, issues) => {\n if (typeof value !== \"string\" || !values.includes(value)) {\n issues.push({ path, message: `expected one of ${values.join(\", \")}` });\n }\n };\n\nconst integer =\n (min: number, max: number): Check =>\n (value, path, issues) => {\n if (typeof value !== \"number\" || !Number.isInteger(value)) {\n {\n issues.push({ path, message: \"expected an integer\" });\n return;\n }\n }\n if (value < min || value > max) issues.push({ path, message: `expected ${min} to ${max}` });\n };\n\nconst number =\n (options: { min?: number; max?: number; positive?: boolean; int?: boolean }): Check =>\n (value, path, issues) => {\n if (typeof value !== \"number\" || !Number.isFinite(value)) {\n {\n issues.push({ path, message: \"expected a number\" });\n return;\n }\n }\n if (options.int && !Number.isInteger(value))\n issues.push({ path, message: \"expected an integer\" });\n if (options.positive && value <= 0)\n issues.push({ path, message: \"expected a positive number\" });\n if (options.min !== undefined && value < options.min) {\n issues.push({ path, message: `at least ${options.min}` });\n }\n if (options.max !== undefined && value > options.max) {\n issues.push({ path, message: `at most ${options.max}` });\n }\n };\n\n/** An object with optional fields; unknown fields are allowed (the ingest ignores them). */\nconst object =\n (fields: Record<string, Check>, required: string[] = []): Check =>\n (value, path, issues) => {\n if (!isObject(value)) {\n issues.push({ path, message: \"expected an object\" });\n return;\n }\n for (const [key, check] of Object.entries(fields)) {\n if (value[key] === undefined) {\n if (required.includes(key)) issues.push({ path: join(path, key), message: \"required\" });\n continue;\n }\n check(value[key], join(path, key), issues);\n }\n };\n\nconst scalar = (value: unknown) =>\n typeof value === \"boolean\" ||\n (typeof value === \"number\" && Number.isFinite(value)) ||\n (typeof value === \"string\" && value.length <= LIMITS.stringLength);\n\nconst properties: Check = (value, path, issues) => {\n if (!isObject(value)) {\n issues.push({ path, message: \"expected an object\" });\n return;\n }\n const keys = Object.keys(value);\n for (const key of keys) {\n const at = join(path, key);\n if (key.length < 1 || key.length > LIMITS.propertyKeyLength) {\n issues.push({ path: at, message: `keys have 1 to ${LIMITS.propertyKeyLength} characters` });\n }\n const item = value[key];\n if (item === null || scalar(item)) continue;\n if (Array.isArray(item)) {\n if (item.length > LIMITS.arrayLength) {\n issues.push({ path: at, message: `at most ${LIMITS.arrayLength} items` });\n }\n item.forEach((entry, index) => {\n if (!scalar(entry)) {\n issues.push({ path: join(at, index), message: \"expected a string, number or boolean\" });\n }\n });\n continue;\n }\n issues.push({\n path: at,\n message:\n typeof item === \"string\"\n ? `at most ${LIMITS.stringLength} characters`\n : \"expected a string, number, boolean, null or a list of them (nested objects are flattened by the SDKs)\",\n });\n }\n if (keys.length > LIMITS.propertyKeys) {\n issues.push({ path, message: `at most ${LIMITS.propertyKeys} properties per event` });\n }\n};\n\n/**\n * The rules, built on first use: nothing runs when the module loads, so bundlers drop the\n * validator from apps that never call it (the browser SDK's script tag, for one).\n */\nlet built: { event: Check; touchpoint: Check; dealStage: Check; dealLost: Check } | undefined;\nfunction rules() {\n if (built) return built;\n const short = string({ max: LIMITS.stringLength });\n const url = string({ max: LIMITS.urlLength });\n const size = integer(0, 20_000);\n\n const context = object({\n page: object({ url, referrer: url, title: short }),\n app: object({ name: short, version: short, build: short, namespace: short }),\n device: object({\n type: oneOf([\"desktop\", \"mobile\", \"tablet\", \"tv\", \"other\"]),\n manufacturer: short,\n model: short,\n }),\n os: object({ name: short, version: short }),\n screen: object({ width: size, height: size, density: number({ positive: true, max: 10 }) }),\n viewport: object({ width: size, height: size }),\n locale: string({ max: 35 }),\n languages: (value, path, issues) => {\n if (!Array.isArray(value)) {\n issues.push({ path, message: \"expected a list\" });\n return;\n }\n if (value.length > 10) issues.push({ path, message: \"at most 10 languages\" });\n value.forEach((entry, index) => string({ max: 35 })(entry, join(path, index), issues));\n },\n timezone: string({ max: 64 }),\n location: object(\n {\n latitude: number({ min: -90, max: 90 }),\n longitude: number({ min: -180, max: 180 }),\n },\n [\"latitude\", \"longitude\"],\n ),\n library: object({ name: string({ max: 50 }), version: string({ max: 30 }) }, [\n \"name\",\n \"version\",\n ]),\n ip: string({ max: 45 }),\n user_agent: string({ max: 1024 }),\n });\n\n const consentState = oneOf(consentStates);\n const consent: Check = (value, path, issues) => {\n if (value === \"not_applicable\") return;\n if (!isObject(value)) {\n {\n issues.push({ path, message: 'expected { analytics, marketing } or \"not_applicable\"' });\n return;\n }\n }\n object({ analytics: consentState, marketing: consentState }, [\"analytics\"])(\n value,\n path,\n issues,\n );\n };\n\n const minor = integer(-1e15, 1e15);\n const id = string({ max: LIMITS.idLength });\n const revenue = object(\n {\n amount: minor,\n currency: string({ min: 3, max: 3, hint: \"an ISO 4217 code\" }),\n entry_id: id,\n kind: oneOf(revenueKinds),\n fee: minor,\n tax: minor,\n refund_of: id,\n product: id,\n subscription: object(\n {\n id: string({ min: 1, max: LIMITS.idLength }),\n interval: oneOf(billingIntervals),\n interval_count: integer(1, 36),\n period_start: dateTime,\n period_end: dateTime,\n },\n [\"id\"],\n ),\n },\n [\"amount\", \"currency\"],\n );\n\n const externalIds: Check = (value, path, issues) => {\n if (!isObject(value)) {\n issues.push({ path, message: \"expected an object\" });\n return;\n }\n for (const [key, item] of Object.entries(value)) {\n if (!(externalIdTypes as readonly string[]).includes(key)) {\n issues.push({\n path: join(path, key),\n message: `expected one of ${externalIdTypes.join(\", \")}`,\n });\n } else if (item !== undefined) {\n string({ min: 1, max: LIMITS.idLength })(item, join(path, key), issues);\n }\n }\n };\n\n const text = string({ max: 200, trim: true });\n const touchpoint = object(\n {\n kind: oneOf(touchpointKinds),\n channel: oneOf(touchpointChannels),\n source: text,\n medium: text,\n campaign: text,\n content: text,\n campaign_id: text,\n ad_group_id: text,\n ad_id: text,\n code: text,\n answer: string({ max: 500, trim: true }),\n external_ref: text,\n },\n [\"kind\"],\n );\n\n const event = object(\n {\n event_id: string({ max: 36, pattern: UUID, hint: \"expected a UUID\" }),\n type: oneOf([\"track\", \"page\", \"screen\", \"identify\", \"group\"]),\n event: string({\n min: 1,\n max: LIMITS.nameLength,\n pattern: /^\\P{Cc}+$/u,\n hint: \"no control characters\",\n }),\n timestamp: dateTime,\n anonymous_id: id,\n user_id: id,\n session_id: id,\n group_id: id,\n properties,\n traits: properties,\n external_ids: externalIds,\n revenue,\n context,\n consent,\n },\n [\"event_id\", \"type\", \"event\"],\n );\n\n const dealId = string({ min: 1, max: DEAL_LIMITS.idLength });\n const dealStage = object(\n {\n deal_id: dealId,\n stage: string({ min: 1, max: DEAL_LIMITS.stageLength }),\n value: integer(0, 1e15),\n },\n [\"deal_id\", \"stage\"],\n );\n const dealLost = object({ deal_id: dealId, reason: string({ max: DEAL_LIMITS.reasonLength }) }, [\n \"deal_id\",\n ]);\n\n built = { event, touchpoint, dealStage, dealLost };\n return built;\n}\n\n/** Every problem the ingest would reject this event for; an empty list means it is accepted. */\nexport function validateEvent(value: unknown, path = \"\"): Issue[] {\n const issues: Issue[] = [];\n const { event, touchpoint, dealStage, dealLost } = rules();\n event(value, path, issues);\n if (isObject(value) && (value.event === \"$deal_stage\" || value.event === \"$deal_lost\")) {\n if (value.type !== \"track\") {\n issues.push({ path: join(path, \"type\"), message: `${value.event} is a track event` });\n }\n const props = isObject(value.properties) ? value.properties : {};\n (value.event === \"$deal_stage\" ? dealStage : dealLost)(props, join(path, \"properties\"), issues);\n }\n if (isObject(value) && value.event === \"$touchpoint\") {\n if (value.type !== \"track\") {\n issues.push({ path: join(path, \"type\"), message: \"$touchpoint is a track event\" });\n }\n const props = isObject(value.properties) ? value.properties : {};\n const before = issues.length;\n touchpoint(props, join(path, \"properties\"), issues);\n const source = typeof props.source === \"string\" ? props.source.trim() : \"\";\n if (issues.length === before && !props.channel && !source) {\n issues.push({\n path: join(path, \"properties.source\"),\n message: \"a touchpoint needs a channel or a source\",\n });\n }\n }\n return issues;\n}\n\n/**\n * Problems with the batch envelope (key, sent_at, stream, seq, 1 to 100 events) and with each\n * event. The ingest rejects a bad envelope as a whole and a bad event on its own.\n */\nexport function validateBatch(value: unknown): Issue[] {\n const issues: Issue[] = [];\n object({\n key: string({ max: 100 }),\n sent_at: dateTime,\n stream: string({ min: 1, max: 64 }),\n seq: integer(1, 2_147_483_647),\n })(value, \"\", issues);\n if (!isObject(value)) return issues;\n const events = value.events;\n if (!Array.isArray(events)) {\n issues.push({ path: \"events\", message: \"expected a list of events\" });\n return issues;\n }\n if (events.length < 1 || events.length > LIMITS.eventsPerBatch) {\n issues.push({ path: \"events\", message: `1 to ${LIMITS.eventsPerBatch} events per batch` });\n }\n events.forEach((item, index) => issues.push(...validateEvent(item, join(\"events\", index))));\n return issues;\n}\n"
|
|
10
10
|
],
|
|
11
|
-
"mappings": "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",
|
|
12
|
-
"debugId": "
|
|
11
|
+
"mappings": "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",
|
|
12
|
+
"debugId": "0FB51ED08C63485464756E2164756E21",
|
|
13
13
|
"names": []
|
|
14
14
|
}
|
package/dist/wire.d.ts
CHANGED
|
@@ -21,8 +21,28 @@ export declare const LIMITS: {
|
|
|
21
21
|
readonly maxClientAgeDays: 7;
|
|
22
22
|
};
|
|
23
23
|
/** Event names MIRA defines. Your own event names must not start with `$`. */
|
|
24
|
-
export declare const reservedEvents: readonly ["$pageview", "$screen", "$identify", "$group", "$consent_changed", "$revenue", "$app_install", "$app_open", "$touchpoint", "$engagement", "$outbound_click", "$file_download", "$form_submit"];
|
|
24
|
+
export declare const reservedEvents: readonly ["$pageview", "$screen", "$identify", "$group", "$consent_changed", "$revenue", "$app_install", "$app_open", "$touchpoint", "$engagement", "$outbound_click", "$file_download", "$form_submit", "$deal_stage", "$deal_lost"];
|
|
25
25
|
export type ReservedEvent = (typeof reservedEvents)[number];
|
|
26
|
+
/**
|
|
27
|
+
* The properties of `$deal_stage`: one deal (a case, an order, an opportunity) reached a stage
|
|
28
|
+
* of the project's pipeline. `value` is what the deal is expected to be worth now, in minor units
|
|
29
|
+
* of the project's reporting currency; MIRA FIVE keeps the latest. Other properties are kept.
|
|
30
|
+
*/
|
|
31
|
+
export interface DealStageProperties {
|
|
32
|
+
deal_id: string;
|
|
33
|
+
stage: string;
|
|
34
|
+
value?: number;
|
|
35
|
+
}
|
|
36
|
+
/** The properties of `$deal_lost`: the deal will not reach the end of the pipeline. */
|
|
37
|
+
export interface DealLostProperties {
|
|
38
|
+
deal_id: string;
|
|
39
|
+
reason?: string;
|
|
40
|
+
}
|
|
41
|
+
export declare const DEAL_LIMITS: {
|
|
42
|
+
readonly idLength: 200;
|
|
43
|
+
readonly stageLength: 120;
|
|
44
|
+
readonly reasonLength: 200;
|
|
45
|
+
};
|
|
26
46
|
/** One purpose's state. `unknown`: the banner is still open, so the event is collected at tier 0. */
|
|
27
47
|
export declare const consentStates: readonly ["granted", "denied", "unknown"];
|
|
28
48
|
export type ConsentState = (typeof consentStates)[number];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"events": [
|
|
3
|
+
{
|
|
4
|
+
"event_id": "0192d4a8-7b1c-7e8a-9c1d-2b3e4f5a6c01",
|
|
5
|
+
"type": "track",
|
|
6
|
+
"event": "$deal_stage",
|
|
7
|
+
"user_id": "user_42",
|
|
8
|
+
"properties": {
|
|
9
|
+
"deal_id": "case_17",
|
|
10
|
+
"stage": "consultation_booked",
|
|
11
|
+
"value": 21900,
|
|
12
|
+
"package": "antragsservice"
|
|
13
|
+
},
|
|
14
|
+
"consent": "not_applicable"
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"event_id": "0192d4a8-7b1c-7e8a-9c1d-2b3e4f5a6c02",
|
|
18
|
+
"type": "track",
|
|
19
|
+
"event": "$deal_lost",
|
|
20
|
+
"user_id": "user_42",
|
|
21
|
+
"properties": {
|
|
22
|
+
"deal_id": "case_18",
|
|
23
|
+
"reason": "too_expensive"
|
|
24
|
+
},
|
|
25
|
+
"consent": "not_applicable"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
package/package.json
CHANGED