@ultdai/pulse 0.2.0 → 0.3.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/contracts/index.d.ts +120 -0
- package/dist/index.js +141 -18
- package/package.json +2 -2
|
@@ -177,11 +177,37 @@ export declare const PulseTypeDefinitionSchema: z.ZodObject<{
|
|
|
177
177
|
version: z.ZodNumber;
|
|
178
178
|
}, z.core.$strict>;
|
|
179
179
|
export declare const SignalReducerSchema: z.ZodEnum<{
|
|
180
|
+
"additive-window": "additive-window";
|
|
180
181
|
"distinct-per-window": "distinct-per-window";
|
|
181
182
|
lifecycle: "lifecycle";
|
|
182
183
|
presence: "presence";
|
|
183
184
|
}>;
|
|
184
185
|
export declare const SignalTypeDefinitionSchema: z.ZodObject<{
|
|
186
|
+
additive: z.ZodOptional<z.ZodObject<{
|
|
187
|
+
baseScopeFields: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
188
|
+
cohortAtField: z.ZodString;
|
|
189
|
+
dedupeFields: z.ZodArray<z.ZodString>;
|
|
190
|
+
dimensionSets: z.ZodArray<z.ZodObject<{
|
|
191
|
+
kind: z.ZodString;
|
|
192
|
+
fields: z.ZodArray<z.ZodString>;
|
|
193
|
+
}, z.core.$strict>>;
|
|
194
|
+
latenessMs: z.ZodNumber;
|
|
195
|
+
metricRules: z.ZodArray<z.ZodObject<{
|
|
196
|
+
name: z.ZodString;
|
|
197
|
+
op: z.ZodEnum<{
|
|
198
|
+
count: "count";
|
|
199
|
+
sum: "sum";
|
|
200
|
+
}>;
|
|
201
|
+
unitField: z.ZodOptional<z.ZodString>;
|
|
202
|
+
valueField: z.ZodOptional<z.ZodString>;
|
|
203
|
+
when: z.ZodOptional<z.ZodObject<{
|
|
204
|
+
field: z.ZodString;
|
|
205
|
+
equals: z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodBoolean]>;
|
|
206
|
+
}, z.core.$strict>>;
|
|
207
|
+
}, z.core.$strict>>;
|
|
208
|
+
primaryMetric: z.ZodString;
|
|
209
|
+
stableScopeField: z.ZodOptional<z.ZodString>;
|
|
210
|
+
}, z.core.$strict>>;
|
|
185
211
|
id: z.ZodString;
|
|
186
212
|
inputPulseTypes: z.ZodArray<z.ZodObject<{
|
|
187
213
|
id: z.ZodString;
|
|
@@ -203,6 +229,7 @@ export declare const SignalTypeDefinitionSchema: z.ZodObject<{
|
|
|
203
229
|
timeoutMs: z.ZodNumber;
|
|
204
230
|
}, z.core.$strict>>;
|
|
205
231
|
reducer: z.ZodEnum<{
|
|
232
|
+
"additive-window": "additive-window";
|
|
206
233
|
"distinct-per-window": "distinct-per-window";
|
|
207
234
|
lifecycle: "lifecycle";
|
|
208
235
|
presence: "presence";
|
|
@@ -229,6 +256,7 @@ export declare const MetricPulseTargetSchema: z.ZodObject<{
|
|
|
229
256
|
refs: z.ZodArray<z.ZodObject<{
|
|
230
257
|
signalId: z.ZodString;
|
|
231
258
|
dimension: z.ZodString;
|
|
259
|
+
scope: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
232
260
|
}, z.core.$strict>>;
|
|
233
261
|
sessionStartedAt: z.ZodString;
|
|
234
262
|
shardCount: z.ZodNumber;
|
|
@@ -289,6 +317,7 @@ export declare const RoutedMetricPulseSchema: z.ZodObject<{
|
|
|
289
317
|
refs: z.ZodArray<z.ZodObject<{
|
|
290
318
|
signalId: z.ZodString;
|
|
291
319
|
dimension: z.ZodString;
|
|
320
|
+
scope: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
292
321
|
}, z.core.$strict>>;
|
|
293
322
|
sessionStartedAt: z.ZodString;
|
|
294
323
|
shardCount: z.ZodNumber;
|
|
@@ -299,6 +328,86 @@ export declare const RoutedMetricPulseSchema: z.ZodObject<{
|
|
|
299
328
|
windowStartAt: z.ZodOptional<z.ZodString>;
|
|
300
329
|
}, z.core.$strict>;
|
|
301
330
|
}, z.core.$strict>;
|
|
331
|
+
/** A pulse carries a bounded vector of exact, non-sampled metric additions. */
|
|
332
|
+
export declare const AdditivePulseTargetSchema: z.ZodObject<{
|
|
333
|
+
dedupeKey: z.ZodString;
|
|
334
|
+
generation: z.ZodNumber;
|
|
335
|
+
latenessMs: z.ZodNumber;
|
|
336
|
+
metrics: z.ZodRecord<z.ZodString, z.ZodNumber>;
|
|
337
|
+
organizationId: z.ZodString;
|
|
338
|
+
ownerId: z.ZodString;
|
|
339
|
+
primaryMetric: z.ZodString;
|
|
340
|
+
refs: z.ZodArray<z.ZodObject<{
|
|
341
|
+
signalId: z.ZodString;
|
|
342
|
+
dimension: z.ZodString;
|
|
343
|
+
scope: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
344
|
+
}, z.core.$strict>>;
|
|
345
|
+
shardCount: z.ZodNumber;
|
|
346
|
+
shardIndex: z.ZodNumber;
|
|
347
|
+
signalTypeId: z.ZodString;
|
|
348
|
+
stableScopeKey: z.ZodOptional<z.ZodString>;
|
|
349
|
+
windowEndAt: z.ZodString;
|
|
350
|
+
windowStartAt: z.ZodString;
|
|
351
|
+
}, z.core.$strict>;
|
|
352
|
+
export declare const RoutedAdditivePulseSchema: z.ZodObject<{
|
|
353
|
+
pulse: z.ZodObject<{
|
|
354
|
+
data: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
355
|
+
eventId: z.ZodOptional<z.ZodString>;
|
|
356
|
+
id: z.ZodString;
|
|
357
|
+
organizationId: z.ZodString;
|
|
358
|
+
sampling: z.ZodDefault<z.ZodObject<{
|
|
359
|
+
applied: z.ZodBoolean;
|
|
360
|
+
appliedKeepProbability: z.ZodNumber;
|
|
361
|
+
effectiveKeepProbability: z.ZodNumber;
|
|
362
|
+
ingressApplied: z.ZodBoolean;
|
|
363
|
+
ingressKeepProbability: z.ZodNumber;
|
|
364
|
+
ingressMethod: z.ZodEnum<{
|
|
365
|
+
none: "none";
|
|
366
|
+
"pulse-hash-v1": "pulse-hash-v1";
|
|
367
|
+
}>;
|
|
368
|
+
knownKeepProbability: z.ZodNullable<z.ZodNumber>;
|
|
369
|
+
method: z.ZodEnum<{
|
|
370
|
+
none: "none";
|
|
371
|
+
"pulse-hash-v1": "pulse-hash-v1";
|
|
372
|
+
}>;
|
|
373
|
+
policyId: z.ZodNullable<z.ZodString>;
|
|
374
|
+
policyRevision: z.ZodNullable<z.ZodNumber>;
|
|
375
|
+
unit: z.ZodEnum<{
|
|
376
|
+
member: "member";
|
|
377
|
+
pulse: "pulse";
|
|
378
|
+
}>;
|
|
379
|
+
}, z.core.$strict>>;
|
|
380
|
+
source: z.ZodString;
|
|
381
|
+
subject: z.ZodOptional<z.ZodObject<{
|
|
382
|
+
id: z.ZodString;
|
|
383
|
+
type: z.ZodString;
|
|
384
|
+
}, z.core.$strip>>;
|
|
385
|
+
type: z.ZodString;
|
|
386
|
+
typeVersion: z.ZodNumber;
|
|
387
|
+
occurredAt: z.ZodString;
|
|
388
|
+
receivedAt: z.ZodString;
|
|
389
|
+
}, z.core.$strict>;
|
|
390
|
+
target: z.ZodObject<{
|
|
391
|
+
dedupeKey: z.ZodString;
|
|
392
|
+
generation: z.ZodNumber;
|
|
393
|
+
latenessMs: z.ZodNumber;
|
|
394
|
+
metrics: z.ZodRecord<z.ZodString, z.ZodNumber>;
|
|
395
|
+
organizationId: z.ZodString;
|
|
396
|
+
ownerId: z.ZodString;
|
|
397
|
+
primaryMetric: z.ZodString;
|
|
398
|
+
refs: z.ZodArray<z.ZodObject<{
|
|
399
|
+
signalId: z.ZodString;
|
|
400
|
+
dimension: z.ZodString;
|
|
401
|
+
scope: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
402
|
+
}, z.core.$strict>>;
|
|
403
|
+
shardCount: z.ZodNumber;
|
|
404
|
+
shardIndex: z.ZodNumber;
|
|
405
|
+
signalTypeId: z.ZodString;
|
|
406
|
+
stableScopeKey: z.ZodOptional<z.ZodString>;
|
|
407
|
+
windowEndAt: z.ZodString;
|
|
408
|
+
windowStartAt: z.ZodString;
|
|
409
|
+
}, z.core.$strict>;
|
|
410
|
+
}, z.core.$strict>;
|
|
302
411
|
export declare const LifecyclePulseTargetSchema: z.ZodObject<{
|
|
303
412
|
generation: z.ZodNumber;
|
|
304
413
|
groupId: z.ZodString;
|
|
@@ -312,6 +421,7 @@ export declare const LifecyclePulseTargetSchema: z.ZodObject<{
|
|
|
312
421
|
refs: z.ZodArray<z.ZodObject<{
|
|
313
422
|
signalId: z.ZodString;
|
|
314
423
|
dimension: z.ZodString;
|
|
424
|
+
scope: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
315
425
|
}, z.core.$strict>>;
|
|
316
426
|
scopeId: z.ZodString;
|
|
317
427
|
sessionStartedAt: z.ZodString;
|
|
@@ -372,6 +482,7 @@ export declare const RoutedLifecyclePulseSchema: z.ZodObject<{
|
|
|
372
482
|
refs: z.ZodArray<z.ZodObject<{
|
|
373
483
|
signalId: z.ZodString;
|
|
374
484
|
dimension: z.ZodString;
|
|
485
|
+
scope: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
375
486
|
}, z.core.$strict>>;
|
|
376
487
|
scopeId: z.ZodString;
|
|
377
488
|
sessionStartedAt: z.ZodString;
|
|
@@ -393,15 +504,20 @@ export declare const MetricSnapshotSchema: z.ZodObject<{
|
|
|
393
504
|
varianceEstimate: z.ZodOptional<z.ZodNumber>;
|
|
394
505
|
generation: z.ZodNumber;
|
|
395
506
|
kind: z.ZodEnum<{
|
|
507
|
+
"additive-window": "additive-window";
|
|
396
508
|
"distinct-per-window": "distinct-per-window";
|
|
397
509
|
presence: "presence";
|
|
398
510
|
}>;
|
|
511
|
+
latenessMs: z.ZodOptional<z.ZodNumber>;
|
|
512
|
+
metrics: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
|
|
399
513
|
observedAt: z.ZodString;
|
|
400
514
|
organizationId: z.ZodString;
|
|
401
515
|
originId: z.ZodString;
|
|
402
516
|
recovering: z.ZodBoolean;
|
|
403
517
|
revision: z.ZodNumber;
|
|
404
518
|
sampledCount: z.ZodNumber;
|
|
519
|
+
scope: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
520
|
+
scopeKind: z.ZodOptional<z.ZodString>;
|
|
405
521
|
shardCount: z.ZodNumber;
|
|
406
522
|
signalId: z.ZodString;
|
|
407
523
|
signalTypeId: z.ZodString;
|
|
@@ -517,6 +633,7 @@ export declare const LivePulseTargetSchema: z.ZodObject<{
|
|
|
517
633
|
memberId: z.ZodString;
|
|
518
634
|
organizationId: z.ZodString;
|
|
519
635
|
reducer: z.ZodEnum<{
|
|
636
|
+
"additive-window": "additive-window";
|
|
520
637
|
"distinct-per-window": "distinct-per-window";
|
|
521
638
|
lifecycle: "lifecycle";
|
|
522
639
|
presence: "presence";
|
|
@@ -570,6 +687,7 @@ export declare const RoutedLivePulseSchema: z.ZodObject<{
|
|
|
570
687
|
memberId: z.ZodString;
|
|
571
688
|
organizationId: z.ZodString;
|
|
572
689
|
reducer: z.ZodEnum<{
|
|
690
|
+
"additive-window": "additive-window";
|
|
573
691
|
"distinct-per-window": "distinct-per-window";
|
|
574
692
|
lifecycle: "lifecycle";
|
|
575
693
|
presence: "presence";
|
|
@@ -667,6 +785,8 @@ export type LiveShardSnapshot = z.infer<typeof LiveShardSnapshotSchema>;
|
|
|
667
785
|
export type MetricPulseTarget = z.infer<typeof MetricPulseTargetSchema>;
|
|
668
786
|
export type MetricSnapshot = z.infer<typeof MetricSnapshotSchema>;
|
|
669
787
|
export type RoutedMetricPulse = z.infer<typeof RoutedMetricPulseSchema>;
|
|
788
|
+
export type RoutedAdditivePulse = z.infer<typeof RoutedAdditivePulseSchema>;
|
|
789
|
+
export type AdditivePulseTarget = z.infer<typeof AdditivePulseTargetSchema>;
|
|
670
790
|
export type RoutedLifecyclePulse = z.infer<typeof RoutedLifecyclePulseSchema>;
|
|
671
791
|
export type LifecyclePulseTarget = z.infer<typeof LifecyclePulseTargetSchema>;
|
|
672
792
|
export type LifecycleDecision = z.infer<typeof LifecycleDecisionSchema>;
|
package/dist/index.js
CHANGED
|
@@ -11,22 +11,29 @@ var compilePayloadSchema = (value) => {
|
|
|
11
11
|
const required = new Set(schema.required);
|
|
12
12
|
const fields = {};
|
|
13
13
|
for (const [name, field] of Object.entries(schema.properties)) {
|
|
14
|
-
let
|
|
15
|
-
if (field.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
validator = z.enum(field.enum);
|
|
20
|
-
} else if (field.type === "string") {
|
|
14
|
+
let validator2;
|
|
15
|
+
if (field.pattern !== void 0 && field.type !== "string") {
|
|
16
|
+
throw new Error(`Pattern is only supported for string field ${name}.`);
|
|
17
|
+
}
|
|
18
|
+
if (field.type === "string" || field.type === void 0 && field.enum?.every((item) => typeof item === "string")) {
|
|
21
19
|
let string = z.string();
|
|
22
20
|
if (field.minLength !== void 0)
|
|
23
21
|
string = string.min(field.minLength);
|
|
24
22
|
if (field.maxLength !== void 0)
|
|
25
23
|
string = string.max(field.maxLength);
|
|
24
|
+
if (field.pattern !== void 0) {
|
|
25
|
+
let pattern;
|
|
26
|
+
try {
|
|
27
|
+
pattern = new RegExp(field.pattern);
|
|
28
|
+
} catch {
|
|
29
|
+
throw new Error(`Invalid pattern for ${name}.`);
|
|
30
|
+
}
|
|
31
|
+
string = string.regex(pattern);
|
|
32
|
+
}
|
|
26
33
|
if (field.format === "date-time") {
|
|
27
|
-
|
|
34
|
+
validator2 = string.refine((item) => Number.isFinite(Date.parse(item)), "Invalid date-time.");
|
|
28
35
|
} else if (field.format === void 0) {
|
|
29
|
-
|
|
36
|
+
validator2 = string;
|
|
30
37
|
} else {
|
|
31
38
|
throw new Error(`Unsupported string format for ${name}.`);
|
|
32
39
|
}
|
|
@@ -36,20 +43,46 @@ var compilePayloadSchema = (value) => {
|
|
|
36
43
|
number = number.min(field.minimum);
|
|
37
44
|
if (field.maximum !== void 0)
|
|
38
45
|
number = number.max(field.maximum);
|
|
39
|
-
|
|
46
|
+
validator2 = number;
|
|
40
47
|
} else if (field.type === "boolean") {
|
|
41
|
-
|
|
48
|
+
validator2 = z.boolean();
|
|
42
49
|
} else {
|
|
43
50
|
throw new Error(`Unsupported payload field ${name}.`);
|
|
44
51
|
}
|
|
45
|
-
|
|
52
|
+
if (field.enum !== void 0) {
|
|
53
|
+
const enumType = field.type === "number" || field.type === "integer" ? "number" : "string";
|
|
54
|
+
if (field.enum.length === 0 || field.enum.some((item) => typeof item !== enumType)) {
|
|
55
|
+
throw new Error(`Invalid enum for ${name}.`);
|
|
56
|
+
}
|
|
57
|
+
const allowed = new Set(field.enum);
|
|
58
|
+
validator2 = validator2.refine((item) => allowed.has(item), "Invalid enum value.");
|
|
59
|
+
}
|
|
60
|
+
fields[name] = required.has(name) ? validator2 : validator2.optional();
|
|
46
61
|
}
|
|
47
62
|
for (const name of required) {
|
|
48
63
|
if (!fields[name])
|
|
49
64
|
throw new Error(`Required payload field ${name} is undefined.`);
|
|
50
65
|
}
|
|
51
66
|
const object = z.object(fields);
|
|
52
|
-
|
|
67
|
+
const validator = schema.additionalProperties === false ? object.strict() : object.passthrough();
|
|
68
|
+
if (!schema.dependentRequired)
|
|
69
|
+
return validator;
|
|
70
|
+
for (const [field, dependents] of Object.entries(schema.dependentRequired)) {
|
|
71
|
+
if (!fields[field] || !Array.isArray(dependents) || dependents.some((dependent) => !fields[dependent])) {
|
|
72
|
+
throw new Error(`Invalid dependentRequired for ${field}.`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return validator.superRefine((payload, context) => {
|
|
76
|
+
for (const [field, dependents] of Object.entries(schema.dependentRequired ?? {})) {
|
|
77
|
+
if (payload[field] === void 0)
|
|
78
|
+
continue;
|
|
79
|
+
for (const dependent of dependents) {
|
|
80
|
+
if (payload[dependent] === void 0) {
|
|
81
|
+
context.addIssue({ code: "custom", path: [dependent], message: `${dependent} is required with ${field}.` });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
});
|
|
53
86
|
};
|
|
54
87
|
|
|
55
88
|
// ../../libs/contracts/dist/index.js
|
|
@@ -205,8 +238,36 @@ var PulseTypeDefinitionSchema = z2.object({
|
|
|
205
238
|
context.addIssue({ code: "custom", path: ["lifecycle"], message: "Lifecycle pulses need explicit IDs, live delivery, and an interval only for observations." });
|
|
206
239
|
}
|
|
207
240
|
});
|
|
208
|
-
var SignalReducerSchema = z2.enum(["presence", "distinct-per-window", "lifecycle"]);
|
|
241
|
+
var SignalReducerSchema = z2.enum(["presence", "distinct-per-window", "lifecycle", "additive-window"]);
|
|
242
|
+
var MetricNameSchema = z2.string().regex(/^[A-Za-z][A-Za-z0-9_]*$/).max(64);
|
|
243
|
+
var AdditiveMetricRuleSchema = z2.object({
|
|
244
|
+
name: MetricNameSchema,
|
|
245
|
+
op: z2.enum(["count", "sum"]),
|
|
246
|
+
unitField: z2.string().trim().min(1).max(200).optional(),
|
|
247
|
+
valueField: z2.string().trim().min(1).max(200).optional(),
|
|
248
|
+
when: z2.object({
|
|
249
|
+
field: z2.string().trim().min(1).max(200),
|
|
250
|
+
equals: z2.union([z2.string(), z2.number().finite(), z2.boolean()])
|
|
251
|
+
}).strict().optional()
|
|
252
|
+
}).strict().superRefine((rule, context) => {
|
|
253
|
+
if (rule.op === "sum" !== Boolean(rule.valueField)) {
|
|
254
|
+
context.addIssue({ code: "custom", path: ["valueField"], message: "Sum needs valueField; count must not have one." });
|
|
255
|
+
}
|
|
256
|
+
});
|
|
209
257
|
var SignalTypeDefinitionSchema = z2.object({
|
|
258
|
+
additive: z2.object({
|
|
259
|
+
baseScopeFields: z2.array(z2.string().trim().min(1).max(100)).max(4).optional(),
|
|
260
|
+
cohortAtField: z2.string().trim().min(1).max(200),
|
|
261
|
+
dedupeFields: z2.array(z2.string().trim().min(1).max(200)).min(1).max(4),
|
|
262
|
+
dimensionSets: z2.array(z2.object({
|
|
263
|
+
kind: z2.string().trim().min(1).max(100),
|
|
264
|
+
fields: z2.array(z2.string().trim().min(1).max(100)).max(4)
|
|
265
|
+
}).strict()).min(1).max(16),
|
|
266
|
+
latenessMs: z2.number().int().min(0).max(36e5),
|
|
267
|
+
metricRules: z2.array(AdditiveMetricRuleSchema).min(1).max(32),
|
|
268
|
+
primaryMetric: MetricNameSchema,
|
|
269
|
+
stableScopeField: z2.string().trim().min(1).max(200).optional()
|
|
270
|
+
}).strict().optional(),
|
|
210
271
|
id: z2.string().trim().min(1).max(100),
|
|
211
272
|
inputPulseTypes: z2.array(z2.object({
|
|
212
273
|
id: z2.string().trim().min(1).max(100),
|
|
@@ -246,8 +307,26 @@ var SignalTypeDefinitionSchema = z2.object({
|
|
|
246
307
|
if (definition.reducer === "lifecycle" && (!definition.lifecycle || !definition.partitionKey || !definition.dimensions?.length)) {
|
|
247
308
|
context.addIssue({ code: "custom", path: ["lifecycle"], message: "Lifecycle needs a configuration, partition key and dimensions." });
|
|
248
309
|
}
|
|
310
|
+
if (definition.reducer === "additive-window") {
|
|
311
|
+
const additive = definition.additive;
|
|
312
|
+
if (!additive || !definition.windowMs || !definition.partitionKey) {
|
|
313
|
+
context.addIssue({ code: "custom", path: ["additive"], message: "Additive window needs metrics, windowMs and partitionKey." });
|
|
314
|
+
} else {
|
|
315
|
+
const names = additive.metricRules.map((rule) => rule.name);
|
|
316
|
+
if (new Set(names).size !== names.length || !names.includes(additive.primaryMetric)) {
|
|
317
|
+
context.addIssue({ code: "custom", path: ["additive", "metricRules"], message: "Metric names must be unique and include the primary metric." });
|
|
318
|
+
}
|
|
319
|
+
const kinds = additive.dimensionSets.map((set) => set.kind);
|
|
320
|
+
const sets = additive.dimensionSets.map((set) => JSON.stringify(set.fields));
|
|
321
|
+
if (new Set(sets).size !== sets.length || new Set(kinds).size !== kinds.length) {
|
|
322
|
+
context.addIssue({ code: "custom", path: ["additive", "dimensionSets"], message: "Dimension sets and kinds must be unique." });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
} else if (definition.additive) {
|
|
326
|
+
context.addIssue({ code: "custom", path: ["additive"], message: "Only additive-window signals may define additive metrics." });
|
|
327
|
+
}
|
|
249
328
|
const schedule = definition.shardSchedule ?? [];
|
|
250
|
-
if (schedule.length > 0 && (!definition.partitionKey || !definition.dimensions?.length)) {
|
|
329
|
+
if (schedule.length > 0 && (!definition.partitionKey || !definition.dimensions?.length && !definition.additive?.dimensionSets.length)) {
|
|
251
330
|
context.addIssue({ code: "custom", path: ["partitionKey"], message: "Scheduled routing needs partitionKey and dimensions." });
|
|
252
331
|
}
|
|
253
332
|
if (schedule.length > 0 && definition.reducer === "presence" && !definition.presence?.startedAtField) {
|
|
@@ -269,7 +348,8 @@ var SignalTypeDefinitionSchema = z2.object({
|
|
|
269
348
|
});
|
|
270
349
|
var MetricSignalRefSchema = z2.object({
|
|
271
350
|
signalId: z2.string().trim().min(1).max(500),
|
|
272
|
-
dimension: z2.string().trim().min(1).max(100)
|
|
351
|
+
dimension: z2.string().trim().min(1).max(100),
|
|
352
|
+
scope: z2.record(z2.string().trim().min(1).max(100), z2.string().trim().min(1).max(200)).optional()
|
|
273
353
|
}).strict();
|
|
274
354
|
var MetricPulseTargetSchema = z2.object({
|
|
275
355
|
generation: z2.number().int().positive(),
|
|
@@ -301,6 +381,33 @@ var RoutedMetricPulseSchema = z2.object({
|
|
|
301
381
|
pulse: RawPulseSchema,
|
|
302
382
|
target: MetricPulseTargetSchema
|
|
303
383
|
}).strict();
|
|
384
|
+
var AdditivePulseTargetSchema = z2.object({
|
|
385
|
+
dedupeKey: z2.string().trim().min(1).max(500),
|
|
386
|
+
generation: z2.number().int().positive(),
|
|
387
|
+
latenessMs: z2.number().int().min(0).max(36e5),
|
|
388
|
+
metrics: z2.record(MetricNameSchema, z2.number().int().safe().nonnegative()),
|
|
389
|
+
organizationId: z2.string().trim().min(1).max(100),
|
|
390
|
+
ownerId: z2.string().trim().min(1).max(500),
|
|
391
|
+
primaryMetric: MetricNameSchema,
|
|
392
|
+
refs: z2.array(MetricSignalRefSchema).min(1).max(16),
|
|
393
|
+
shardCount: z2.number().int().min(1).max(65536),
|
|
394
|
+
shardIndex: z2.number().int().nonnegative(),
|
|
395
|
+
signalTypeId: z2.string().trim().min(1).max(100),
|
|
396
|
+
stableScopeKey: z2.string().trim().min(1).max(500).optional(),
|
|
397
|
+
windowEndAt: isoDateTime,
|
|
398
|
+
windowStartAt: isoDateTime
|
|
399
|
+
}).strict().superRefine((target, context) => {
|
|
400
|
+
if (target.shardIndex >= target.shardCount || Date.parse(target.windowStartAt) >= Date.parse(target.windowEndAt)) {
|
|
401
|
+
context.addIssue({ code: "custom", path: ["shardIndex"], message: "Invalid additive shard or window." });
|
|
402
|
+
}
|
|
403
|
+
if (!Object.hasOwn(target.metrics, target.primaryMetric)) {
|
|
404
|
+
context.addIssue({ code: "custom", path: ["metrics"], message: "Additive metrics must include the primary metric." });
|
|
405
|
+
}
|
|
406
|
+
if (new Set(target.refs.map((ref) => ref.signalId)).size !== target.refs.length) {
|
|
407
|
+
context.addIssue({ code: "custom", path: ["refs"], message: "Additive signal references must be unique." });
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
var RoutedAdditivePulseSchema = z2.object({ pulse: RawPulseSchema, target: AdditivePulseTargetSchema }).strict();
|
|
304
411
|
var LifecyclePulseTargetSchema = z2.object({
|
|
305
412
|
generation: z2.number().int().positive(),
|
|
306
413
|
groupId: z2.string().trim().min(1).max(100),
|
|
@@ -335,22 +442,38 @@ var MetricSnapshotSchema = z2.object({
|
|
|
335
442
|
estimatedCount: z2.number().finite().nonnegative(),
|
|
336
443
|
varianceEstimate: z2.number().finite().nonnegative().optional(),
|
|
337
444
|
generation: z2.number().int().positive(),
|
|
338
|
-
kind: z2.enum(["presence", "distinct-per-window"]),
|
|
445
|
+
kind: z2.enum(["presence", "distinct-per-window", "additive-window"]),
|
|
446
|
+
latenessMs: z2.number().int().min(0).max(36e5).optional(),
|
|
447
|
+
metrics: z2.record(MetricNameSchema, z2.number().int().safe().nonnegative()).optional(),
|
|
339
448
|
observedAt: isoDateTime,
|
|
340
449
|
organizationId: z2.string().trim().min(1).max(100),
|
|
341
450
|
originId: z2.string().trim().min(1).max(200),
|
|
342
451
|
recovering: z2.boolean(),
|
|
343
452
|
revision: z2.number().int().nonnegative(),
|
|
344
453
|
sampledCount: z2.number().int().nonnegative(),
|
|
454
|
+
scope: z2.record(z2.string().trim().min(1).max(100), z2.string().trim().min(1).max(200)).optional(),
|
|
455
|
+
scopeKind: z2.string().trim().min(1).max(100).optional(),
|
|
345
456
|
shardCount: z2.number().int().min(1).max(65536),
|
|
346
457
|
signalId: z2.string().trim().min(1).max(500),
|
|
347
458
|
signalTypeId: z2.string().trim().min(1).max(100),
|
|
348
459
|
windowEndAt: isoDateTime.optional(),
|
|
349
460
|
windowStartAt: isoDateTime.optional()
|
|
350
461
|
}).strict().superRefine((snapshot, context) => {
|
|
351
|
-
if (snapshot.kind === "distinct-per-window" && (!snapshot.windowStartAt || !snapshot.windowEndAt)) {
|
|
462
|
+
if ((snapshot.kind === "distinct-per-window" || snapshot.kind === "additive-window") && (!snapshot.windowStartAt || !snapshot.windowEndAt)) {
|
|
352
463
|
context.addIssue({ code: "custom", path: ["windowStartAt"], message: "Distinct snapshot needs a window." });
|
|
353
464
|
}
|
|
465
|
+
if (snapshot.kind === "additive-window" && !snapshot.metrics) {
|
|
466
|
+
context.addIssue({ code: "custom", path: ["metrics"], message: "Additive snapshot needs metrics." });
|
|
467
|
+
}
|
|
468
|
+
if (snapshot.kind === "additive-window" && snapshot.latenessMs === void 0) {
|
|
469
|
+
context.addIssue({ code: "custom", path: ["latenessMs"], message: "Additive snapshot needs its lateness bound." });
|
|
470
|
+
}
|
|
471
|
+
if (snapshot.kind === "additive-window" && (!snapshot.scope || !snapshot.scopeKind)) {
|
|
472
|
+
context.addIssue({ code: "custom", path: ["scope"], message: "Additive snapshot needs a structured scope." });
|
|
473
|
+
}
|
|
474
|
+
if (snapshot.kind !== "additive-window" && snapshot.metrics) {
|
|
475
|
+
context.addIssue({ code: "custom", path: ["metrics"], message: "Only additive snapshots carry metrics." });
|
|
476
|
+
}
|
|
354
477
|
if (snapshot.kind === "presence" && (snapshot.windowStartAt || snapshot.windowEndAt)) {
|
|
355
478
|
context.addIssue({ code: "custom", path: ["windowStartAt"], message: "Presence snapshot must not have a window." });
|
|
356
479
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ultdai/pulse",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": ["dist", "README.md"],
|
|
6
6
|
"publishConfig": { "access": "public" },
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json --emitDeclarationOnly && node scripts/bundle.mjs",
|
|
17
|
-
"prepack": "pnpm --dir ../../libs/contracts run build && pnpm run build",
|
|
17
|
+
"prepack": "corepack pnpm --dir ../../libs/contracts run build && corepack pnpm run build",
|
|
18
18
|
"test": "vitest run test --exclude 'dist/**'",
|
|
19
19
|
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
20
20
|
},
|