@mirafive/protocol 0.0.0-stage → 2.0.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/dist/index.js ADDED
@@ -0,0 +1,496 @@
1
+ // src/wire.ts
2
+ var DEFAULT_HOST = "https://events.mirafive.io";
3
+ var LIMITS = {
4
+ eventsPerBatch: 100,
5
+ nameLength: 120,
6
+ propertyKeys: 64,
7
+ propertyKeyLength: 100,
8
+ stringLength: 2048,
9
+ arrayLength: 50,
10
+ urlLength: 4096,
11
+ idLength: 200,
12
+ maxClientAgeDays: 7
13
+ };
14
+ var reservedEvents = [
15
+ "$pageview",
16
+ "$screen",
17
+ "$identify",
18
+ "$group",
19
+ "$consent_changed",
20
+ "$revenue",
21
+ "$app_install",
22
+ "$app_open",
23
+ "$touchpoint",
24
+ "$engagement",
25
+ "$outbound_click",
26
+ "$file_download",
27
+ "$form_submit"
28
+ ];
29
+ var consentStates = ["granted", "denied", "unknown"];
30
+ var externalIdTypes = [
31
+ "stripe_customer",
32
+ "revenuecat_app_user",
33
+ "shopify_customer",
34
+ "shopware_customer"
35
+ ];
36
+ var revenueKinds = [
37
+ "charge",
38
+ "subscription_payment",
39
+ "refund",
40
+ "dispute",
41
+ "dispute_won"
42
+ ];
43
+ var billingIntervals = ["day", "week", "month", "year"];
44
+ var touchpointKinds = ["survey", "offline", "code", "custom"];
45
+ var publicTouchpointKinds = ["survey"];
46
+ var touchpointChannels = [
47
+ "paid_search",
48
+ "paid_social",
49
+ "paid_other",
50
+ "organic_search",
51
+ "organic_social",
52
+ "ai_assistant",
53
+ "email",
54
+ "affiliate",
55
+ "display",
56
+ "video",
57
+ "qr",
58
+ "app_store",
59
+ "referral",
60
+ "direct"
61
+ ];
62
+ // src/helpers.ts
63
+ var consentState = (value) => value === true ? "granted" : value === false ? "denied" : value ?? "unknown";
64
+ function toConsent(input, fallback = "not_applicable") {
65
+ if (input === undefined)
66
+ return fallback;
67
+ if (input === "not_applicable")
68
+ return input;
69
+ return { analytics: consentState(input.analytics), marketing: consentState(input.marketing) };
70
+ }
71
+ function keyKind(key) {
72
+ if (!key)
73
+ return "unknown";
74
+ if (key.startsWith("mira_pk_"))
75
+ return "public";
76
+ if (key.startsWith("mira_sk_"))
77
+ return "secret";
78
+ return "unknown";
79
+ }
80
+ var trimHost = (host) => host.replace(/\/+$/, "");
81
+ var retryable = (status) => status === 408 || status === 429 || status >= 500;
82
+ function backoffMs(attempt, atLeastMs = 0, baseMs = 500, capMs = 30000) {
83
+ const ceiling = Math.min(capMs, baseMs * 2 ** attempt);
84
+ return Math.max(atLeastMs, Math.random() * ceiling);
85
+ }
86
+ function retryAfterMs(header, now = Date.now()) {
87
+ if (!header)
88
+ return 0;
89
+ const seconds = Number(header);
90
+ if (Number.isFinite(seconds))
91
+ return Math.max(0, seconds * 1000);
92
+ const at = Date.parse(header);
93
+ return Number.isNaN(at) ? 0 : Math.max(0, at - now);
94
+ }
95
+ var 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;
96
+ var isUuid = (value) => UUID.test(value);
97
+ async function eventIdFor(key) {
98
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(key)));
99
+ digest[6] = digest[6] & 15 | 128;
100
+ digest[8] = digest[8] & 63 | 128;
101
+ const hex = Array.from(digest.subarray(0, 16), (byte) => byte.toString(16).padStart(2, "0")).join("");
102
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
103
+ }
104
+ // src/properties.ts
105
+ var MAX_KEYS = LIMITS.propertyKeys;
106
+ var MAX_STRING = LIMITS.stringLength;
107
+ var MAX_KEY = LIMITS.propertyKeyLength;
108
+ var MAX_ARRAY = LIMITS.arrayLength;
109
+ var MAX_DEPTH = 3;
110
+ function scalar(value) {
111
+ if (value === null || typeof value === "boolean")
112
+ return value;
113
+ if (typeof value === "string")
114
+ return value.slice(0, MAX_STRING);
115
+ if (typeof value === "number")
116
+ return Number.isFinite(value) ? value : null;
117
+ if (typeof value === "bigint")
118
+ return value.toString();
119
+ if (value instanceof Date)
120
+ return Number.isNaN(value.getTime()) ? null : value.toISOString();
121
+ return;
122
+ }
123
+ function cleanProperties(input) {
124
+ const output = {};
125
+ let count = 0;
126
+ const walk = (value, prefix, depth) => {
127
+ if (!value || typeof value !== "object" || value instanceof Date)
128
+ return;
129
+ for (const [rawKey, item] of Object.entries(value)) {
130
+ if (count >= MAX_KEYS)
131
+ return;
132
+ const key = (prefix ? `${prefix}.${rawKey}` : rawKey).slice(0, MAX_KEY);
133
+ if (!key)
134
+ continue;
135
+ if (Array.isArray(item)) {
136
+ const list = item.map(scalar).filter((entry) => entry !== undefined && entry !== null);
137
+ output[key] = list.slice(0, MAX_ARRAY);
138
+ count += 1;
139
+ continue;
140
+ }
141
+ const flat = scalar(item);
142
+ if (flat !== undefined) {
143
+ output[key] = flat;
144
+ count += 1;
145
+ } else if (item && typeof item === "object" && depth < MAX_DEPTH) {
146
+ walk(item, key, depth + 1);
147
+ }
148
+ }
149
+ };
150
+ walk(input, "", 0);
151
+ return output;
152
+ }
153
+ function cleanExternalIds(input) {
154
+ if (!input || typeof input !== "object")
155
+ return;
156
+ const ids = {};
157
+ let found = false;
158
+ for (const type of externalIdTypes) {
159
+ const value = Reflect.get(input, type);
160
+ if (typeof value === "string" && value.trim() && value.length <= LIMITS.idLength) {
161
+ ids[type] = value;
162
+ found = true;
163
+ }
164
+ }
165
+ return found ? ids : undefined;
166
+ }
167
+ // src/uuid.ts
168
+ var lastMs = 0;
169
+ var counter = 0;
170
+ var hex = (bytes) => Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
171
+ function uuidv7(now = Date.now()) {
172
+ const bytes = new Uint8Array(16);
173
+ crypto.getRandomValues(bytes);
174
+ if (now > lastMs) {
175
+ lastMs = now;
176
+ counter = (bytes[6] & 7) << 8 | bytes[7];
177
+ } else {
178
+ counter += 1;
179
+ if (counter > 4095) {
180
+ lastMs += 1;
181
+ counter = 0;
182
+ }
183
+ }
184
+ let ms = lastMs;
185
+ for (let index = 5;index >= 0; index -= 1) {
186
+ bytes[index] = ms % 256;
187
+ ms = Math.floor(ms / 256);
188
+ }
189
+ bytes[6] = 112 | counter >> 8;
190
+ bytes[7] = counter & 255;
191
+ bytes[8] = bytes[8] & 63 | 128;
192
+ const value = hex(bytes);
193
+ return `${value.slice(0, 8)}-${value.slice(8, 12)}-${value.slice(12, 16)}-${value.slice(16, 20)}-${value.slice(20)}`;
194
+ }
195
+ // src/validate.ts
196
+ var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
197
+ var join = (path, key) => path ? `${path}.${key}` : String(key);
198
+ var UUID2 = /^([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)$/;
199
+ var DATETIME = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d+)?(?:Z|([+-])(\d{2}):(\d{2}))$/;
200
+ function isDateTime(value) {
201
+ const match = DATETIME.exec(value);
202
+ if (!match)
203
+ return false;
204
+ const [, year, month, day, hour, minute, second, , offsetHour, offsetMinute] = match;
205
+ const days = new Date(Date.UTC(Number(year), Number(month), 0)).getUTCDate();
206
+ return Number(month) >= 1 && Number(month) <= 12 && Number(day) >= 1 && Number(day) <= days && Number(hour) <= 23 && Number(minute) <= 59 && Number(second) <= 59 && (offsetHour === undefined || Number(offsetHour) <= 23 && Number(offsetMinute) <= 59);
207
+ }
208
+ var string = (options) => (value, path, issues) => {
209
+ if (typeof value !== "string") {
210
+ issues.push({ path, message: "expected a string" });
211
+ return;
212
+ }
213
+ const text = options.trim ? value.trim() : value;
214
+ if (options.min !== undefined && text.length < options.min) {
215
+ issues.push({ path, message: `at least ${options.min} character(s)` });
216
+ } else if (text.length > options.max) {
217
+ issues.push({ path, message: `at most ${options.max} characters` });
218
+ }
219
+ if (options.pattern && !options.pattern.test(text)) {
220
+ issues.push({ path, message: options.hint ?? "invalid format" });
221
+ }
222
+ };
223
+ var dateTime = (value, path, issues) => {
224
+ if (typeof value !== "string" || !isDateTime(value)) {
225
+ issues.push({ path, message: "expected an ISO 8601 date and time with Z or an offset" });
226
+ }
227
+ };
228
+ var oneOf = (values) => (value, path, issues) => {
229
+ if (typeof value !== "string" || !values.includes(value)) {
230
+ issues.push({ path, message: `expected one of ${values.join(", ")}` });
231
+ }
232
+ };
233
+ var integer = (min, max) => (value, path, issues) => {
234
+ if (typeof value !== "number" || !Number.isInteger(value)) {
235
+ {
236
+ issues.push({ path, message: "expected an integer" });
237
+ return;
238
+ }
239
+ }
240
+ if (value < min || value > max)
241
+ issues.push({ path, message: `expected ${min} to ${max}` });
242
+ };
243
+ var number = (options) => (value, path, issues) => {
244
+ if (typeof value !== "number" || !Number.isFinite(value)) {
245
+ {
246
+ issues.push({ path, message: "expected a number" });
247
+ return;
248
+ }
249
+ }
250
+ if (options.int && !Number.isInteger(value))
251
+ issues.push({ path, message: "expected an integer" });
252
+ if (options.positive && value <= 0)
253
+ issues.push({ path, message: "expected a positive number" });
254
+ if (options.min !== undefined && value < options.min) {
255
+ issues.push({ path, message: `at least ${options.min}` });
256
+ }
257
+ if (options.max !== undefined && value > options.max) {
258
+ issues.push({ path, message: `at most ${options.max}` });
259
+ }
260
+ };
261
+ var object = (fields, required = []) => (value, path, issues) => {
262
+ if (!isObject(value)) {
263
+ issues.push({ path, message: "expected an object" });
264
+ return;
265
+ }
266
+ for (const [key, check] of Object.entries(fields)) {
267
+ if (value[key] === undefined) {
268
+ if (required.includes(key))
269
+ issues.push({ path: join(path, key), message: "required" });
270
+ continue;
271
+ }
272
+ check(value[key], join(path, key), issues);
273
+ }
274
+ };
275
+ var scalar2 = (value) => typeof value === "boolean" || typeof value === "number" && Number.isFinite(value) || typeof value === "string" && value.length <= LIMITS.stringLength;
276
+ var properties = (value, path, issues) => {
277
+ if (!isObject(value)) {
278
+ issues.push({ path, message: "expected an object" });
279
+ return;
280
+ }
281
+ const keys = Object.keys(value);
282
+ for (const key of keys) {
283
+ const at = join(path, key);
284
+ if (key.length < 1 || key.length > LIMITS.propertyKeyLength) {
285
+ issues.push({ path: at, message: `keys have 1 to ${LIMITS.propertyKeyLength} characters` });
286
+ }
287
+ const item = value[key];
288
+ if (item === null || scalar2(item))
289
+ continue;
290
+ if (Array.isArray(item)) {
291
+ if (item.length > LIMITS.arrayLength) {
292
+ issues.push({ path: at, message: `at most ${LIMITS.arrayLength} items` });
293
+ }
294
+ item.forEach((entry, index) => {
295
+ if (!scalar2(entry)) {
296
+ issues.push({ path: join(at, index), message: "expected a string, number or boolean" });
297
+ }
298
+ });
299
+ continue;
300
+ }
301
+ issues.push({
302
+ path: at,
303
+ message: typeof item === "string" ? `at most ${LIMITS.stringLength} characters` : "expected a string, number, boolean, null or a list of them (nested objects are flattened by the SDKs)"
304
+ });
305
+ }
306
+ if (keys.length > LIMITS.propertyKeys) {
307
+ issues.push({ path, message: `at most ${LIMITS.propertyKeys} properties per event` });
308
+ }
309
+ };
310
+ var short = string({ max: LIMITS.stringLength });
311
+ var url = string({ max: LIMITS.urlLength });
312
+ var size = integer(0, 20000);
313
+ var context = object({
314
+ page: object({ url, referrer: url, title: short }),
315
+ app: object({ name: short, version: short, build: short, namespace: short }),
316
+ device: object({
317
+ type: oneOf(["desktop", "mobile", "tablet", "tv", "other"]),
318
+ manufacturer: short,
319
+ model: short
320
+ }),
321
+ os: object({ name: short, version: short }),
322
+ screen: object({ width: size, height: size, density: number({ positive: true, max: 10 }) }),
323
+ viewport: object({ width: size, height: size }),
324
+ locale: string({ max: 35 }),
325
+ languages: (value, path, issues) => {
326
+ if (!Array.isArray(value)) {
327
+ issues.push({ path, message: "expected a list" });
328
+ return;
329
+ }
330
+ if (value.length > 10)
331
+ issues.push({ path, message: "at most 10 languages" });
332
+ value.forEach((entry, index) => string({ max: 35 })(entry, join(path, index), issues));
333
+ },
334
+ timezone: string({ max: 64 }),
335
+ location: object({
336
+ latitude: number({ min: -90, max: 90 }),
337
+ longitude: number({ min: -180, max: 180 })
338
+ }, ["latitude", "longitude"]),
339
+ library: object({ name: string({ max: 50 }), version: string({ max: 30 }) }, ["name", "version"]),
340
+ ip: string({ max: 45 }),
341
+ user_agent: string({ max: 1024 })
342
+ });
343
+ var consentState2 = oneOf(consentStates);
344
+ var consent = (value, path, issues) => {
345
+ if (value === "not_applicable")
346
+ return;
347
+ if (!isObject(value)) {
348
+ {
349
+ issues.push({ path, message: 'expected { analytics, marketing } or "not_applicable"' });
350
+ return;
351
+ }
352
+ }
353
+ object({ analytics: consentState2, marketing: consentState2 }, ["analytics"])(value, path, issues);
354
+ };
355
+ var minor = integer(-1000000000000000, 1000000000000000);
356
+ var id = string({ max: LIMITS.idLength });
357
+ var revenue = object({
358
+ amount: minor,
359
+ currency: string({ min: 3, max: 3, hint: "an ISO 4217 code" }),
360
+ entry_id: id,
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);
387
+ }
388
+ }
389
+ };
390
+ var text = string({ max: 200, trim: true });
391
+ var touchpoint = object({
392
+ kind: oneOf(touchpointKinds),
393
+ channel: oneOf(touchpointChannels),
394
+ source: text,
395
+ medium: text,
396
+ campaign: text,
397
+ content: text,
398
+ campaign_id: text,
399
+ ad_group_id: text,
400
+ ad_id: text,
401
+ code: text,
402
+ answer: string({ max: 500, trim: true }),
403
+ external_ref: text
404
+ }, ["kind"]);
405
+ var event = object({
406
+ event_id: string({ max: 36, pattern: UUID2, hint: "expected a UUID" }),
407
+ type: oneOf(["track", "page", "screen", "identify", "group"]),
408
+ event: string({
409
+ min: 1,
410
+ max: LIMITS.nameLength,
411
+ pattern: /^\P{Cc}+$/u,
412
+ hint: "no control characters"
413
+ }),
414
+ timestamp: dateTime,
415
+ anonymous_id: id,
416
+ user_id: id,
417
+ session_id: id,
418
+ group_id: id,
419
+ properties,
420
+ traits: properties,
421
+ external_ids: externalIds,
422
+ revenue,
423
+ context,
424
+ consent
425
+ }, ["event_id", "type", "event"]);
426
+ function validateEvent(value, path = "") {
427
+ const issues = [];
428
+ event(value, path, issues);
429
+ if (isObject(value) && value.event === "$touchpoint") {
430
+ if (value.type !== "track") {
431
+ issues.push({ path: join(path, "type"), message: "$touchpoint is a track event" });
432
+ }
433
+ const props = isObject(value.properties) ? value.properties : {};
434
+ const before = issues.length;
435
+ touchpoint(props, join(path, "properties"), issues);
436
+ const source = typeof props.source === "string" ? props.source.trim() : "";
437
+ if (issues.length === before && !props.channel && !source) {
438
+ issues.push({
439
+ path: join(path, "properties.source"),
440
+ message: "a touchpoint needs a channel or a source"
441
+ });
442
+ }
443
+ }
444
+ return issues;
445
+ }
446
+ function validateBatch(value) {
447
+ const issues = [];
448
+ object({
449
+ key: string({ max: 100 }),
450
+ sent_at: dateTime,
451
+ stream: string({ min: 1, max: 64 }),
452
+ seq: integer(1, 2147483647)
453
+ })(value, "", issues);
454
+ if (!isObject(value))
455
+ return issues;
456
+ const events = value.events;
457
+ if (!Array.isArray(events)) {
458
+ issues.push({ path: "events", message: "expected a list of events" });
459
+ return issues;
460
+ }
461
+ if (events.length < 1 || events.length > LIMITS.eventsPerBatch) {
462
+ issues.push({ path: "events", message: `1 to ${LIMITS.eventsPerBatch} events per batch` });
463
+ }
464
+ events.forEach((item, index) => issues.push(...validateEvent(item, join("events", index))));
465
+ return issues;
466
+ }
467
+ export {
468
+ DEFAULT_HOST,
469
+ LIMITS,
470
+ backoffMs,
471
+ billingIntervals,
472
+ cleanExternalIds,
473
+ cleanProperties,
474
+ consentState,
475
+ consentStates,
476
+ eventIdFor,
477
+ externalIdTypes,
478
+ isDateTime,
479
+ isUuid,
480
+ keyKind,
481
+ publicTouchpointKinds,
482
+ reservedEvents,
483
+ retryAfterMs,
484
+ retryable,
485
+ revenueKinds,
486
+ toConsent,
487
+ touchpointChannels,
488
+ touchpointKinds,
489
+ trimHost,
490
+ uuidv7,
491
+ validateBatch,
492
+ validateEvent
493
+ };
494
+
495
+ //# debugId=AB2CD420AD4EE9A564756E2164756E21
496
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,14 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/wire.ts", "../src/helpers.ts", "../src/properties.ts", "../src/uuid.ts", "../src/validate.ts"],
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",
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
+ "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
+ "/**\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"
10
+ ],
11
+ "mappings": "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",
12
+ "debugId": "AB2CD420AD4EE9A564756E2164756E21",
13
+ "names": []
14
+ }
@@ -0,0 +1,13 @@
1
+ import { type ExternalIds, type Properties } from "./wire.js";
2
+ /**
3
+ * Turns whatever the caller passes into properties the ingest accepts: nested objects are
4
+ * flattened to dot keys (`plan.name`), dates become ISO strings, functions and `undefined` are
5
+ * dropped, and every limit is applied here. The ingest validates each event on its own and
6
+ * rejects the whole event on a violation, so trimming here keeps the rest of the data.
7
+ */
8
+ export declare function cleanProperties(input: unknown): Properties;
9
+ /**
10
+ * Known types with usable values only; `undefined` when nothing is left. An id is never cut
11
+ * short: a truncated id would link the money to the wrong person, so an overlong one is dropped.
12
+ */
13
+ export declare function cleanExternalIds(input: unknown): ExternalIds | undefined;
package/dist/uuid.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function uuidv7(now?: number): string;
@@ -0,0 +1,14 @@
1
+ /** One problem: where (`events.0.revenue.amount`) and what. */
2
+ export interface Issue {
3
+ path: string;
4
+ message: string;
5
+ }
6
+ /** ISO 8601 date and time with `Z` or an offset, as the ingest accepts it. */
7
+ export declare function isDateTime(value: string): boolean;
8
+ /** Every problem the ingest would reject this event for; an empty list means it is accepted. */
9
+ export declare function validateEvent(value: unknown, path?: string): Issue[];
10
+ /**
11
+ * Problems with the batch envelope (key, sent_at, stream, seq, 1 to 100 events) and with each
12
+ * event. The ingest rejects a bad envelope as a whole and a bad event on its own.
13
+ */
14
+ export declare function validateBatch(value: unknown): Issue[];