@ultdai/pulse 0.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 ADDED
@@ -0,0 +1,46 @@
1
+ # @ultdai/pulse
2
+
3
+ Serverové TypeScript SDK pro odesílání pulsů do Unlimited. Funguje i v Cloudflare Workers.
4
+
5
+ ```sh
6
+ npm install @ultdai/pulse
7
+ ```
8
+
9
+ ```ts
10
+ import { CloudflareSdkConfigurationCache, UnlimitedClient } from '@ultdai/pulse'
11
+
12
+ export interface Env { ULTD_API_TOKEN: string }
13
+
14
+ export default {
15
+ async fetch(request: Request, env: Env) {
16
+ const session = getPlaybackSession(request) // stabilní ID a čas vzniku v Unixových milisekundách
17
+ const client = new UnlimitedClient({
18
+ apiToken: env.ULTD_API_TOKEN,
19
+ baseUrl: 'https://api.ultd.tivio.studio',
20
+ cache: new CloudflareSdkConfigurationCache(caches.default),
21
+ source: 'stream-worker',
22
+ })
23
+ await client.emit({
24
+ occurredAtMs: Date.now(),
25
+ organizationId: 'tivio',
26
+ pulse: {
27
+ type: 'stream.segment.requested',
28
+ version: 3,
29
+ id: session.id,
30
+ payload: {
31
+ sessionStartedAtMs: session.startedAtMs,
32
+ videoId: session.videoId,
33
+ ...(session.channelId ? { channelId: session.channelId } : {}),
34
+ },
35
+ },
36
+ })
37
+ return new Response('ok')
38
+ },
39
+ }
40
+ ```
41
+
42
+ Klíč patří do serverového secretu, nikdy do klientského JavaScriptu. Konstruktor bez neprázdného `apiToken` skončí chybou ještě před síťovým voláním. Unlimited ověřuje klíč znovu na `GET /v1/sdk-config` i `POST /v1/pulses`; neplatný klíč nemůže zapsat puls.
43
+
44
+ SDK dynamicky načítá seznam PulseTypes, jejich schémata, povolení sběru a sampling politiku při prvním `emit()`. Neznámý typ nebo nevalidní `pulse.payload` SDK odmítne lokálně. Vypnutý nebo nevzorkovaný puls neodešle. `occurredAtMs` a playback v3 `sessionStartedAtMs` jsou Unixové časy v milisekundách. Playback v2 se starším ISO polem `sessionStartedAt` zůstává podporován. Konfigurace má krátkou platnost a po jejím vypršení se obnovuje; pokud obnovení selže, puls se neodešle. Cloudflare cache je volitelná; bez ní SDK používá paměť instance.
45
+
46
+ SDK obsahuje interní kontrakty přibalené při buildu z monorepa. Aplikace instaluje jen `@ultdai/pulse` a jeho běžnou závislost `zod`; `@ultdai/contracts` se samostatně nevydává. Samotné zveřejnění SDK neposkytuje oprávnění k API.
@@ -0,0 +1,22 @@
1
+ import type { SamplingPolicy } from './contracts/index.js';
2
+ export interface SamplingPolicyCache {
3
+ get(key: string): Promise<SamplingPolicy | undefined>;
4
+ set(key: string, policy: SamplingPolicy): Promise<void>;
5
+ }
6
+ export declare class MemorySamplingPolicyCache implements SamplingPolicyCache {
7
+ private readonly policies;
8
+ get(key: string): Promise<SamplingPolicy | undefined>;
9
+ set(key: string, policy: SamplingPolicy): Promise<void>;
10
+ }
11
+ export interface CacheApiLike {
12
+ match(request: Request): Promise<Response | undefined>;
13
+ put(request: Request, response: Response): Promise<void>;
14
+ }
15
+ export declare class CloudflareSamplingPolicyCache implements SamplingPolicyCache {
16
+ private readonly cache;
17
+ private readonly namespace;
18
+ constructor(cache: CacheApiLike, namespace?: string);
19
+ private request;
20
+ get(key: string): Promise<SamplingPolicy | undefined>;
21
+ set(key: string, policy: SamplingPolicy): Promise<void>;
22
+ }
@@ -0,0 +1,23 @@
1
+ import type { SdkConfiguration } from './contracts/index.js';
2
+ import type { CacheApiLike } from './cache.js';
3
+ export interface CachedSdkConfiguration {
4
+ configuration: SdkConfiguration;
5
+ etag: string | null;
6
+ }
7
+ export interface SdkConfigurationCache {
8
+ get(key: string): Promise<CachedSdkConfiguration | undefined>;
9
+ set(key: string, value: CachedSdkConfiguration): Promise<void>;
10
+ }
11
+ export declare class MemorySdkConfigurationCache implements SdkConfigurationCache {
12
+ private readonly values;
13
+ get(key: string): Promise<CachedSdkConfiguration | undefined>;
14
+ set(key: string, value: CachedSdkConfiguration): Promise<void>;
15
+ }
16
+ export declare class CloudflareSdkConfigurationCache implements SdkConfigurationCache {
17
+ private readonly cache;
18
+ private readonly namespace;
19
+ constructor(cache: CacheApiLike, namespace?: string);
20
+ private request;
21
+ get(key: string): Promise<CachedSdkConfiguration | undefined>;
22
+ set(key: string, value: CachedSdkConfiguration): Promise<void>;
23
+ }
@@ -0,0 +1,544 @@
1
+ import { z } from 'zod';
2
+ export { compilePayloadSchema } from './payloadSchema.js';
3
+ export declare const PulseSubjectSchema: z.ZodObject<{
4
+ id: z.ZodString;
5
+ type: z.ZodString;
6
+ }, z.core.$strip>;
7
+ export declare const SamplingMethodSchema: z.ZodEnum<{
8
+ none: "none";
9
+ "pulse-hash-v1": "pulse-hash-v1";
10
+ }>;
11
+ export declare const SamplingUnitSchema: z.ZodEnum<{
12
+ member: "member";
13
+ pulse: "pulse";
14
+ }>;
15
+ export declare const SamplingContextSchema: z.ZodObject<{
16
+ applied: z.ZodBoolean;
17
+ appliedKeepProbability: z.ZodNumber;
18
+ effectiveKeepProbability: z.ZodNumber;
19
+ ingressApplied: z.ZodBoolean;
20
+ ingressKeepProbability: z.ZodNumber;
21
+ ingressMethod: z.ZodEnum<{
22
+ none: "none";
23
+ "pulse-hash-v1": "pulse-hash-v1";
24
+ }>;
25
+ knownKeepProbability: z.ZodNullable<z.ZodNumber>;
26
+ method: z.ZodEnum<{
27
+ none: "none";
28
+ "pulse-hash-v1": "pulse-hash-v1";
29
+ }>;
30
+ policyId: z.ZodNullable<z.ZodString>;
31
+ policyRevision: z.ZodNullable<z.ZodNumber>;
32
+ unit: z.ZodEnum<{
33
+ member: "member";
34
+ pulse: "pulse";
35
+ }>;
36
+ }, z.core.$strict>;
37
+ export declare const NoSamplingContext: z.infer<typeof SamplingContextSchema>;
38
+ export declare const PulseInputSchema: z.ZodObject<{
39
+ data: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
40
+ eventId: z.ZodOptional<z.ZodString>;
41
+ id: z.ZodString;
42
+ organizationId: z.ZodString;
43
+ occurredAt: z.ZodOptional<z.ZodString>;
44
+ sampling: z.ZodDefault<z.ZodObject<{
45
+ applied: z.ZodBoolean;
46
+ appliedKeepProbability: z.ZodNumber;
47
+ effectiveKeepProbability: z.ZodNumber;
48
+ ingressApplied: z.ZodBoolean;
49
+ ingressKeepProbability: z.ZodNumber;
50
+ ingressMethod: z.ZodEnum<{
51
+ none: "none";
52
+ "pulse-hash-v1": "pulse-hash-v1";
53
+ }>;
54
+ knownKeepProbability: z.ZodNullable<z.ZodNumber>;
55
+ method: z.ZodEnum<{
56
+ none: "none";
57
+ "pulse-hash-v1": "pulse-hash-v1";
58
+ }>;
59
+ policyId: z.ZodNullable<z.ZodString>;
60
+ policyRevision: z.ZodNullable<z.ZodNumber>;
61
+ unit: z.ZodEnum<{
62
+ member: "member";
63
+ pulse: "pulse";
64
+ }>;
65
+ }, z.core.$strict>>;
66
+ source: z.ZodString;
67
+ subject: z.ZodOptional<z.ZodObject<{
68
+ id: z.ZodString;
69
+ type: z.ZodString;
70
+ }, z.core.$strip>>;
71
+ type: z.ZodString;
72
+ typeVersion: z.ZodNumber;
73
+ }, z.core.$strict>;
74
+ export declare const PulseSubmissionSchema: z.ZodObject<{
75
+ occurredAtMs: z.ZodNumber;
76
+ organizationId: z.ZodString;
77
+ pulse: z.ZodObject<{
78
+ eventId: z.ZodOptional<z.ZodString>;
79
+ id: z.ZodString;
80
+ payload: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
81
+ subject: z.ZodOptional<z.ZodObject<{
82
+ id: z.ZodString;
83
+ type: z.ZodString;
84
+ }, z.core.$strip>>;
85
+ type: z.ZodString;
86
+ version: z.ZodNumber;
87
+ }, z.core.$strict>;
88
+ sampling: z.ZodDefault<z.ZodObject<{
89
+ applied: z.ZodBoolean;
90
+ appliedKeepProbability: z.ZodNumber;
91
+ effectiveKeepProbability: z.ZodNumber;
92
+ ingressApplied: z.ZodBoolean;
93
+ ingressKeepProbability: z.ZodNumber;
94
+ ingressMethod: z.ZodEnum<{
95
+ none: "none";
96
+ "pulse-hash-v1": "pulse-hash-v1";
97
+ }>;
98
+ knownKeepProbability: z.ZodNullable<z.ZodNumber>;
99
+ method: z.ZodEnum<{
100
+ none: "none";
101
+ "pulse-hash-v1": "pulse-hash-v1";
102
+ }>;
103
+ policyId: z.ZodNullable<z.ZodString>;
104
+ policyRevision: z.ZodNullable<z.ZodNumber>;
105
+ unit: z.ZodEnum<{
106
+ member: "member";
107
+ pulse: "pulse";
108
+ }>;
109
+ }, z.core.$strict>>;
110
+ source: z.ZodString;
111
+ }, z.core.$strict>;
112
+ export declare const RawPulseSchema: z.ZodObject<{
113
+ data: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
114
+ eventId: z.ZodOptional<z.ZodString>;
115
+ id: z.ZodString;
116
+ organizationId: z.ZodString;
117
+ sampling: z.ZodDefault<z.ZodObject<{
118
+ applied: z.ZodBoolean;
119
+ appliedKeepProbability: z.ZodNumber;
120
+ effectiveKeepProbability: z.ZodNumber;
121
+ ingressApplied: z.ZodBoolean;
122
+ ingressKeepProbability: z.ZodNumber;
123
+ ingressMethod: z.ZodEnum<{
124
+ none: "none";
125
+ "pulse-hash-v1": "pulse-hash-v1";
126
+ }>;
127
+ knownKeepProbability: z.ZodNullable<z.ZodNumber>;
128
+ method: z.ZodEnum<{
129
+ none: "none";
130
+ "pulse-hash-v1": "pulse-hash-v1";
131
+ }>;
132
+ policyId: z.ZodNullable<z.ZodString>;
133
+ policyRevision: z.ZodNullable<z.ZodNumber>;
134
+ unit: z.ZodEnum<{
135
+ member: "member";
136
+ pulse: "pulse";
137
+ }>;
138
+ }, z.core.$strict>>;
139
+ source: z.ZodString;
140
+ subject: z.ZodOptional<z.ZodObject<{
141
+ id: z.ZodString;
142
+ type: z.ZodString;
143
+ }, z.core.$strip>>;
144
+ type: z.ZodString;
145
+ typeVersion: z.ZodNumber;
146
+ occurredAt: z.ZodString;
147
+ receivedAt: z.ZodString;
148
+ }, z.core.$strict>;
149
+ export declare const PulseDeliverySchema: z.ZodEnum<{
150
+ live: "live";
151
+ reliable: "reliable";
152
+ }>;
153
+ export declare const PulseTypeDefinitionSchema: z.ZodObject<{
154
+ delivery: z.ZodEnum<{
155
+ live: "live";
156
+ reliable: "reliable";
157
+ }>;
158
+ id: z.ZodString;
159
+ payloadSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
160
+ requiresExplicitId: z.ZodOptional<z.ZodBoolean>;
161
+ sampling: z.ZodOptional<z.ZodObject<{
162
+ keyField: z.ZodNullable<z.ZodString>;
163
+ unit: z.ZodEnum<{
164
+ member: "member";
165
+ pulse: "pulse";
166
+ }>;
167
+ }, z.core.$strict>>;
168
+ samplingAllowed: z.ZodBoolean;
169
+ version: z.ZodNumber;
170
+ }, z.core.$strict>;
171
+ export declare const SignalReducerSchema: z.ZodEnum<{
172
+ "distinct-per-window": "distinct-per-window";
173
+ presence: "presence";
174
+ }>;
175
+ export declare const SignalTypeDefinitionSchema: z.ZodObject<{
176
+ id: z.ZodString;
177
+ inputPulseTypes: z.ZodArray<z.ZodObject<{
178
+ id: z.ZodString;
179
+ version: z.ZodNumber;
180
+ }, z.core.$strict>>;
181
+ dimensions: z.ZodOptional<z.ZodArray<z.ZodString>>;
182
+ partitionKey: z.ZodOptional<z.ZodString>;
183
+ presence: z.ZodOptional<z.ZodObject<{
184
+ immutableDimensions: z.ZodOptional<z.ZodArray<z.ZodString>>;
185
+ memberIdField: z.ZodString;
186
+ startedAtField: z.ZodOptional<z.ZodString>;
187
+ timeoutMs: z.ZodNumber;
188
+ }, z.core.$strict>>;
189
+ reducer: z.ZodEnum<{
190
+ "distinct-per-window": "distinct-per-window";
191
+ presence: "presence";
192
+ }>;
193
+ shardCount: z.ZodNumber;
194
+ shardSchedule: z.ZodOptional<z.ZodArray<z.ZodObject<{
195
+ effectiveFrom: z.ZodString;
196
+ generation: z.ZodNumber;
197
+ shardCount: z.ZodNumber;
198
+ }, z.core.$strict>>>;
199
+ version: z.ZodNumber;
200
+ windowMs: z.ZodOptional<z.ZodNumber>;
201
+ }, z.core.$strict>;
202
+ export declare const MetricPulseTargetSchema: z.ZodObject<{
203
+ generation: z.ZodNumber;
204
+ immutableDimensions: z.ZodOptional<z.ZodArray<z.ZodString>>;
205
+ kind: z.ZodEnum<{
206
+ "distinct-per-window": "distinct-per-window";
207
+ presence: "presence";
208
+ }>;
209
+ memberId: z.ZodString;
210
+ organizationId: z.ZodString;
211
+ ownerId: z.ZodString;
212
+ refs: z.ZodArray<z.ZodObject<{
213
+ signalId: z.ZodString;
214
+ dimension: z.ZodString;
215
+ }, z.core.$strict>>;
216
+ sessionStartedAt: z.ZodString;
217
+ shardCount: z.ZodNumber;
218
+ shardIndex: z.ZodNumber;
219
+ signalTypeId: z.ZodString;
220
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
221
+ windowEndAt: z.ZodOptional<z.ZodString>;
222
+ windowStartAt: z.ZodOptional<z.ZodString>;
223
+ }, z.core.$strict>;
224
+ export declare const RoutedMetricPulseSchema: z.ZodObject<{
225
+ pulse: z.ZodObject<{
226
+ data: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
227
+ eventId: z.ZodOptional<z.ZodString>;
228
+ id: z.ZodString;
229
+ organizationId: z.ZodString;
230
+ sampling: z.ZodDefault<z.ZodObject<{
231
+ applied: z.ZodBoolean;
232
+ appliedKeepProbability: z.ZodNumber;
233
+ effectiveKeepProbability: z.ZodNumber;
234
+ ingressApplied: z.ZodBoolean;
235
+ ingressKeepProbability: z.ZodNumber;
236
+ ingressMethod: z.ZodEnum<{
237
+ none: "none";
238
+ "pulse-hash-v1": "pulse-hash-v1";
239
+ }>;
240
+ knownKeepProbability: z.ZodNullable<z.ZodNumber>;
241
+ method: z.ZodEnum<{
242
+ none: "none";
243
+ "pulse-hash-v1": "pulse-hash-v1";
244
+ }>;
245
+ policyId: z.ZodNullable<z.ZodString>;
246
+ policyRevision: z.ZodNullable<z.ZodNumber>;
247
+ unit: z.ZodEnum<{
248
+ member: "member";
249
+ pulse: "pulse";
250
+ }>;
251
+ }, z.core.$strict>>;
252
+ source: z.ZodString;
253
+ subject: z.ZodOptional<z.ZodObject<{
254
+ id: z.ZodString;
255
+ type: z.ZodString;
256
+ }, z.core.$strip>>;
257
+ type: z.ZodString;
258
+ typeVersion: z.ZodNumber;
259
+ occurredAt: z.ZodString;
260
+ receivedAt: z.ZodString;
261
+ }, z.core.$strict>;
262
+ target: z.ZodObject<{
263
+ generation: z.ZodNumber;
264
+ immutableDimensions: z.ZodOptional<z.ZodArray<z.ZodString>>;
265
+ kind: z.ZodEnum<{
266
+ "distinct-per-window": "distinct-per-window";
267
+ presence: "presence";
268
+ }>;
269
+ memberId: z.ZodString;
270
+ organizationId: z.ZodString;
271
+ ownerId: z.ZodString;
272
+ refs: z.ZodArray<z.ZodObject<{
273
+ signalId: z.ZodString;
274
+ dimension: z.ZodString;
275
+ }, z.core.$strict>>;
276
+ sessionStartedAt: z.ZodString;
277
+ shardCount: z.ZodNumber;
278
+ shardIndex: z.ZodNumber;
279
+ signalTypeId: z.ZodString;
280
+ timeoutMs: z.ZodOptional<z.ZodNumber>;
281
+ windowEndAt: z.ZodOptional<z.ZodString>;
282
+ windowStartAt: z.ZodOptional<z.ZodString>;
283
+ }, z.core.$strict>;
284
+ }, z.core.$strict>;
285
+ export declare const MetricSnapshotSchema: z.ZodObject<{
286
+ estimatedCount: z.ZodNumber;
287
+ generation: z.ZodNumber;
288
+ kind: z.ZodEnum<{
289
+ "distinct-per-window": "distinct-per-window";
290
+ presence: "presence";
291
+ }>;
292
+ observedAt: z.ZodString;
293
+ organizationId: z.ZodString;
294
+ originId: z.ZodString;
295
+ recovering: z.ZodBoolean;
296
+ revision: z.ZodNumber;
297
+ sampledCount: z.ZodNumber;
298
+ shardCount: z.ZodNumber;
299
+ signalId: z.ZodString;
300
+ signalTypeId: z.ZodString;
301
+ windowEndAt: z.ZodOptional<z.ZodString>;
302
+ windowStartAt: z.ZodOptional<z.ZodString>;
303
+ }, z.core.$strict>;
304
+ export declare const SamplingPolicySchema: z.ZodObject<{
305
+ expiresAt: z.ZodString;
306
+ keyField: z.ZodNullable<z.ZodString>;
307
+ keepProbability: z.ZodNumber;
308
+ method: z.ZodLiteral<"pulse-hash-v1">;
309
+ policyId: z.ZodString;
310
+ pulseTypeId: z.ZodString;
311
+ pulseTypeVersion: z.ZodNumber;
312
+ revision: z.ZodNumber;
313
+ unit: z.ZodEnum<{
314
+ member: "member";
315
+ pulse: "pulse";
316
+ }>;
317
+ }, z.core.$strict>;
318
+ export declare const SdkPulseTypeSchema: z.ZodObject<{
319
+ definition: z.ZodObject<{
320
+ delivery: z.ZodEnum<{
321
+ live: "live";
322
+ reliable: "reliable";
323
+ }>;
324
+ id: z.ZodString;
325
+ payloadSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
326
+ requiresExplicitId: z.ZodOptional<z.ZodBoolean>;
327
+ sampling: z.ZodOptional<z.ZodObject<{
328
+ keyField: z.ZodNullable<z.ZodString>;
329
+ unit: z.ZodEnum<{
330
+ member: "member";
331
+ pulse: "pulse";
332
+ }>;
333
+ }, z.core.$strict>>;
334
+ samplingAllowed: z.ZodBoolean;
335
+ version: z.ZodNumber;
336
+ }, z.core.$strict>;
337
+ enabled: z.ZodBoolean;
338
+ samplingPolicy: z.ZodNullable<z.ZodObject<{
339
+ expiresAt: z.ZodString;
340
+ keyField: z.ZodNullable<z.ZodString>;
341
+ keepProbability: z.ZodNumber;
342
+ method: z.ZodLiteral<"pulse-hash-v1">;
343
+ policyId: z.ZodString;
344
+ pulseTypeId: z.ZodString;
345
+ pulseTypeVersion: z.ZodNumber;
346
+ revision: z.ZodNumber;
347
+ unit: z.ZodEnum<{
348
+ member: "member";
349
+ pulse: "pulse";
350
+ }>;
351
+ }, z.core.$strict>>;
352
+ }, z.core.$strict>;
353
+ export declare const SdkConfigurationSchema: z.ZodObject<{
354
+ expiresAt: z.ZodString;
355
+ pulseTypes: z.ZodArray<z.ZodObject<{
356
+ definition: z.ZodObject<{
357
+ delivery: z.ZodEnum<{
358
+ live: "live";
359
+ reliable: "reliable";
360
+ }>;
361
+ id: z.ZodString;
362
+ payloadSchema: z.ZodRecord<z.ZodString, z.ZodUnknown>;
363
+ requiresExplicitId: z.ZodOptional<z.ZodBoolean>;
364
+ sampling: z.ZodOptional<z.ZodObject<{
365
+ keyField: z.ZodNullable<z.ZodString>;
366
+ unit: z.ZodEnum<{
367
+ member: "member";
368
+ pulse: "pulse";
369
+ }>;
370
+ }, z.core.$strict>>;
371
+ samplingAllowed: z.ZodBoolean;
372
+ version: z.ZodNumber;
373
+ }, z.core.$strict>;
374
+ enabled: z.ZodBoolean;
375
+ samplingPolicy: z.ZodNullable<z.ZodObject<{
376
+ expiresAt: z.ZodString;
377
+ keyField: z.ZodNullable<z.ZodString>;
378
+ keepProbability: z.ZodNumber;
379
+ method: z.ZodLiteral<"pulse-hash-v1">;
380
+ policyId: z.ZodString;
381
+ pulseTypeId: z.ZodString;
382
+ pulseTypeVersion: z.ZodNumber;
383
+ revision: z.ZodNumber;
384
+ unit: z.ZodEnum<{
385
+ member: "member";
386
+ pulse: "pulse";
387
+ }>;
388
+ }, z.core.$strict>>;
389
+ }, z.core.$strict>>;
390
+ revision: z.ZodNumber;
391
+ }, z.core.$strict>;
392
+ export declare const LivePulseTargetSchema: z.ZodObject<{
393
+ generation: z.ZodNumber;
394
+ memberId: z.ZodString;
395
+ organizationId: z.ZodString;
396
+ reducer: z.ZodEnum<{
397
+ "distinct-per-window": "distinct-per-window";
398
+ presence: "presence";
399
+ }>;
400
+ shardId: z.ZodString;
401
+ signalId: z.ZodString;
402
+ signalTypeId: z.ZodString;
403
+ signalTypeVersion: z.ZodNumber;
404
+ timeoutMs: z.ZodNumber;
405
+ }, z.core.$strict>;
406
+ export declare const RoutedLivePulseSchema: z.ZodObject<{
407
+ pulse: z.ZodObject<{
408
+ data: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
409
+ eventId: z.ZodOptional<z.ZodString>;
410
+ id: z.ZodString;
411
+ organizationId: z.ZodString;
412
+ sampling: z.ZodDefault<z.ZodObject<{
413
+ applied: z.ZodBoolean;
414
+ appliedKeepProbability: z.ZodNumber;
415
+ effectiveKeepProbability: z.ZodNumber;
416
+ ingressApplied: z.ZodBoolean;
417
+ ingressKeepProbability: z.ZodNumber;
418
+ ingressMethod: z.ZodEnum<{
419
+ none: "none";
420
+ "pulse-hash-v1": "pulse-hash-v1";
421
+ }>;
422
+ knownKeepProbability: z.ZodNullable<z.ZodNumber>;
423
+ method: z.ZodEnum<{
424
+ none: "none";
425
+ "pulse-hash-v1": "pulse-hash-v1";
426
+ }>;
427
+ policyId: z.ZodNullable<z.ZodString>;
428
+ policyRevision: z.ZodNullable<z.ZodNumber>;
429
+ unit: z.ZodEnum<{
430
+ member: "member";
431
+ pulse: "pulse";
432
+ }>;
433
+ }, z.core.$strict>>;
434
+ source: z.ZodString;
435
+ subject: z.ZodOptional<z.ZodObject<{
436
+ id: z.ZodString;
437
+ type: z.ZodString;
438
+ }, z.core.$strip>>;
439
+ type: z.ZodString;
440
+ typeVersion: z.ZodNumber;
441
+ occurredAt: z.ZodString;
442
+ receivedAt: z.ZodString;
443
+ }, z.core.$strict>;
444
+ target: z.ZodObject<{
445
+ generation: z.ZodNumber;
446
+ memberId: z.ZodString;
447
+ organizationId: z.ZodString;
448
+ reducer: z.ZodEnum<{
449
+ "distinct-per-window": "distinct-per-window";
450
+ presence: "presence";
451
+ }>;
452
+ shardId: z.ZodString;
453
+ signalId: z.ZodString;
454
+ signalTypeId: z.ZodString;
455
+ signalTypeVersion: z.ZodNumber;
456
+ timeoutMs: z.ZodNumber;
457
+ }, z.core.$strict>;
458
+ }, z.core.$strict>;
459
+ export declare const LiveShardSnapshotSchema: z.ZodObject<{
460
+ activeCount: z.ZodNumber;
461
+ estimatedActiveCount: z.ZodNumber;
462
+ observedAt: z.ZodString;
463
+ organizationId: z.ZodString;
464
+ recovering: z.ZodBoolean;
465
+ revision: z.ZodNumber;
466
+ shardId: z.ZodString;
467
+ signalId: z.ZodString;
468
+ signalTypeId: z.ZodString;
469
+ }, z.core.$strict>;
470
+ export declare const PulseAggregateSchema: z.ZodObject<{
471
+ aggregationKey: z.ZodString;
472
+ count: z.ZodNumber;
473
+ createdAt: z.ZodString;
474
+ id: z.ZodString;
475
+ organizationId: z.ZodString;
476
+ pulses: z.ZodArray<z.ZodObject<{
477
+ data: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
478
+ eventId: z.ZodOptional<z.ZodString>;
479
+ id: z.ZodString;
480
+ organizationId: z.ZodString;
481
+ sampling: z.ZodDefault<z.ZodObject<{
482
+ applied: z.ZodBoolean;
483
+ appliedKeepProbability: z.ZodNumber;
484
+ effectiveKeepProbability: z.ZodNumber;
485
+ ingressApplied: z.ZodBoolean;
486
+ ingressKeepProbability: z.ZodNumber;
487
+ ingressMethod: z.ZodEnum<{
488
+ none: "none";
489
+ "pulse-hash-v1": "pulse-hash-v1";
490
+ }>;
491
+ knownKeepProbability: z.ZodNullable<z.ZodNumber>;
492
+ method: z.ZodEnum<{
493
+ none: "none";
494
+ "pulse-hash-v1": "pulse-hash-v1";
495
+ }>;
496
+ policyId: z.ZodNullable<z.ZodString>;
497
+ policyRevision: z.ZodNullable<z.ZodNumber>;
498
+ unit: z.ZodEnum<{
499
+ member: "member";
500
+ pulse: "pulse";
501
+ }>;
502
+ }, z.core.$strict>>;
503
+ source: z.ZodString;
504
+ subject: z.ZodOptional<z.ZodObject<{
505
+ id: z.ZodString;
506
+ type: z.ZodString;
507
+ }, z.core.$strip>>;
508
+ type: z.ZodString;
509
+ typeVersion: z.ZodNumber;
510
+ occurredAt: z.ZodString;
511
+ receivedAt: z.ZodString;
512
+ }, z.core.$strict>>;
513
+ source: z.ZodString;
514
+ subject: z.ZodOptional<z.ZodObject<{
515
+ id: z.ZodString;
516
+ type: z.ZodString;
517
+ }, z.core.$strip>>;
518
+ type: z.ZodString;
519
+ window: z.ZodObject<{
520
+ endAt: z.ZodString;
521
+ startAt: z.ZodString;
522
+ }, z.core.$strip>;
523
+ }, z.core.$strict>;
524
+ export declare const PulseProcessorResultSchema: z.ZodObject<{
525
+ alreadyProcessed: z.ZodBoolean;
526
+ ok: z.ZodLiteral<true>;
527
+ signalId: z.ZodString;
528
+ }, z.core.$strict>;
529
+ export type PulseAggregate = z.infer<typeof PulseAggregateSchema>;
530
+ export type PulseInput = z.infer<typeof PulseInputSchema>;
531
+ export type PulseSubmission = z.infer<typeof PulseSubmissionSchema>;
532
+ export type PulseTypeDefinition = z.infer<typeof PulseTypeDefinitionSchema>;
533
+ export type PulseProcessorResult = z.infer<typeof PulseProcessorResultSchema>;
534
+ export type RawPulse = z.infer<typeof RawPulseSchema>;
535
+ export type RoutedLivePulse = z.infer<typeof RoutedLivePulseSchema>;
536
+ export type SamplingContext = z.infer<typeof SamplingContextSchema>;
537
+ export type SamplingPolicy = z.infer<typeof SamplingPolicySchema>;
538
+ export type SdkConfiguration = z.infer<typeof SdkConfigurationSchema>;
539
+ export type SdkPulseType = z.infer<typeof SdkPulseTypeSchema>;
540
+ export type SignalTypeDefinition = z.infer<typeof SignalTypeDefinitionSchema>;
541
+ export type LiveShardSnapshot = z.infer<typeof LiveShardSnapshotSchema>;
542
+ export type MetricPulseTarget = z.infer<typeof MetricPulseTargetSchema>;
543
+ export type MetricSnapshot = z.infer<typeof MetricSnapshotSchema>;
544
+ export type RoutedMetricPulse = z.infer<typeof RoutedMetricPulseSchema>;
@@ -0,0 +1,2 @@
1
+ import { z } from 'zod';
2
+ export declare const compilePayloadSchema: (value: Record<string, unknown>) => z.ZodType<Record<string, unknown>>;
@@ -0,0 +1,43 @@
1
+ import type { PulseSubmission } from './contracts/index.js';
2
+ import type { SdkConfigurationCache } from './configurationCache.js';
3
+ export { CloudflareSdkConfigurationCache, MemorySdkConfigurationCache } from './configurationCache.js';
4
+ export type { CachedSdkConfiguration, SdkConfigurationCache } from './configurationCache.js';
5
+ export { CloudflareSamplingPolicyCache, MemorySamplingPolicyCache } from './cache.js';
6
+ export type { CacheApiLike, SamplingPolicyCache } from './cache.js';
7
+ export type SdkPulseInput = Omit<PulseSubmission, 'source' | 'sampling' | 'pulse'> & {
8
+ pulse: Omit<PulseSubmission['pulse'], 'id'> & {
9
+ id?: string;
10
+ };
11
+ };
12
+ export interface UnlimitedClientOptions {
13
+ apiToken: string;
14
+ baseUrl: string;
15
+ source: string;
16
+ cache?: SdkConfigurationCache;
17
+ fetch?: typeof fetch;
18
+ now?: () => Date;
19
+ randomUUID?: () => string;
20
+ }
21
+ export interface EmitResult {
22
+ accepted: boolean;
23
+ disabled?: boolean;
24
+ pulseId: string;
25
+ sampledOut: boolean;
26
+ }
27
+ export declare class UnlimitedClient {
28
+ private readonly apiToken;
29
+ private readonly baseUrl;
30
+ private readonly source;
31
+ private readonly cache;
32
+ private readonly fetcher;
33
+ private readonly now;
34
+ private readonly randomUUID;
35
+ private readonly cacheKey;
36
+ private loaded?;
37
+ private refreshing?;
38
+ constructor(options: UnlimitedClientOptions);
39
+ private install;
40
+ private refresh;
41
+ private configuration;
42
+ emit(input: SdkPulseInput): Promise<EmitResult>;
43
+ }
package/dist/index.js ADDED
@@ -0,0 +1,656 @@
1
+ // ../../libs/contracts/dist/index.js
2
+ import { z as z2 } from "zod";
3
+
4
+ // ../../libs/contracts/dist/payloadSchema.js
5
+ import { z } from "zod";
6
+ var compilePayloadSchema = (value) => {
7
+ const schema = value;
8
+ if (schema.type !== "object" || !schema.properties || !Array.isArray(schema.required)) {
9
+ throw new Error("PulseType needs an object payload schema with properties and required fields.");
10
+ }
11
+ const required = new Set(schema.required);
12
+ const fields = {};
13
+ for (const [name, field] of Object.entries(schema.properties)) {
14
+ let validator;
15
+ if (field.enum) {
16
+ if (field.enum.length === 0 || field.enum.some((item) => typeof item !== "string")) {
17
+ throw new Error(`Invalid enum for ${name}.`);
18
+ }
19
+ validator = z.enum(field.enum);
20
+ } else if (field.type === "string") {
21
+ let string = z.string();
22
+ if (field.minLength !== void 0)
23
+ string = string.min(field.minLength);
24
+ if (field.maxLength !== void 0)
25
+ string = string.max(field.maxLength);
26
+ if (field.format === "date-time") {
27
+ validator = string.refine((item) => Number.isFinite(Date.parse(item)), "Invalid date-time.");
28
+ } else if (field.format === void 0) {
29
+ validator = string;
30
+ } else {
31
+ throw new Error(`Unsupported string format for ${name}.`);
32
+ }
33
+ } else if (field.type === "number" || field.type === "integer") {
34
+ let number = field.type === "integer" ? z.number().int() : z.number();
35
+ if (field.minimum !== void 0)
36
+ number = number.min(field.minimum);
37
+ if (field.maximum !== void 0)
38
+ number = number.max(field.maximum);
39
+ validator = number;
40
+ } else if (field.type === "boolean") {
41
+ validator = z.boolean();
42
+ } else {
43
+ throw new Error(`Unsupported payload field ${name}.`);
44
+ }
45
+ fields[name] = required.has(name) ? validator : validator.optional();
46
+ }
47
+ for (const name of required) {
48
+ if (!fields[name])
49
+ throw new Error(`Required payload field ${name} is undefined.`);
50
+ }
51
+ const object = z.object(fields);
52
+ return schema.additionalProperties === false ? object.strict() : object.passthrough();
53
+ };
54
+
55
+ // ../../libs/contracts/dist/index.js
56
+ var isoDateTime = z2.string().refine((value) => Number.isFinite(Date.parse(value)), "Expected an ISO date-time string.");
57
+ var PulseSubjectSchema = z2.object({
58
+ id: z2.string().trim().min(1).max(200),
59
+ type: z2.string().trim().min(1).max(100)
60
+ });
61
+ var SamplingMethodSchema = z2.enum(["none", "pulse-hash-v1"]);
62
+ var SamplingUnitSchema = z2.enum(["pulse", "member"]);
63
+ var SamplingContextSchema = z2.object({
64
+ applied: z2.boolean(),
65
+ appliedKeepProbability: z2.number().min(0).max(1),
66
+ effectiveKeepProbability: z2.number().min(0).max(1),
67
+ ingressApplied: z2.boolean(),
68
+ ingressKeepProbability: z2.number().min(0).max(1),
69
+ ingressMethod: SamplingMethodSchema,
70
+ knownKeepProbability: z2.number().min(0).max(1).nullable(),
71
+ method: SamplingMethodSchema,
72
+ policyId: z2.string().trim().min(1).max(200).nullable(),
73
+ policyRevision: z2.number().int().nonnegative().nullable(),
74
+ unit: SamplingUnitSchema
75
+ }).strict().superRefine((sampling, context) => {
76
+ if (!sampling.applied && sampling.appliedKeepProbability !== 1) {
77
+ context.addIssue({
78
+ code: "custom",
79
+ message: "appliedKeepProbability must be 1 when sampling was not applied.",
80
+ path: ["appliedKeepProbability"]
81
+ });
82
+ }
83
+ if (sampling.applied && sampling.method === "none") {
84
+ context.addIssue({
85
+ code: "custom",
86
+ message: "Sampling method cannot be none when sampling was applied.",
87
+ path: ["method"]
88
+ });
89
+ }
90
+ if (!sampling.applied && sampling.method !== "none") {
91
+ context.addIssue({
92
+ code: "custom",
93
+ message: "Sampling method must be none when SDK sampling was not applied.",
94
+ path: ["method"]
95
+ });
96
+ }
97
+ if (!sampling.ingressApplied && sampling.ingressKeepProbability !== 1) {
98
+ context.addIssue({
99
+ code: "custom",
100
+ message: "ingressKeepProbability must be 1 when ingress sampling was not applied.",
101
+ path: ["ingressKeepProbability"]
102
+ });
103
+ }
104
+ if (sampling.ingressApplied && sampling.ingressMethod === "none") {
105
+ context.addIssue({
106
+ code: "custom",
107
+ message: "Ingress sampling method cannot be none when ingress sampling was applied.",
108
+ path: ["ingressMethod"]
109
+ });
110
+ }
111
+ if (!sampling.ingressApplied && sampling.ingressMethod !== "none") {
112
+ context.addIssue({
113
+ code: "custom",
114
+ message: "Ingress sampling method must be none when ingress sampling was not applied.",
115
+ path: ["ingressMethod"]
116
+ });
117
+ }
118
+ if (sampling.policyId === null !== (sampling.policyRevision === null)) {
119
+ context.addIssue({
120
+ code: "custom",
121
+ message: "policyId and policyRevision must either both be set or both be null.",
122
+ path: ["policyId"]
123
+ });
124
+ }
125
+ const expected = sampling.appliedKeepProbability * sampling.ingressKeepProbability;
126
+ if (Math.abs(sampling.effectiveKeepProbability - expected) > 1e-12) {
127
+ context.addIssue({
128
+ code: "custom",
129
+ message: "effectiveKeepProbability must equal the product of applied sampling stages.",
130
+ path: ["effectiveKeepProbability"]
131
+ });
132
+ }
133
+ });
134
+ var NoSamplingContext = {
135
+ applied: false,
136
+ appliedKeepProbability: 1,
137
+ effectiveKeepProbability: 1,
138
+ ingressApplied: false,
139
+ ingressKeepProbability: 1,
140
+ ingressMethod: "none",
141
+ knownKeepProbability: null,
142
+ method: "none",
143
+ policyId: null,
144
+ policyRevision: null,
145
+ unit: "pulse"
146
+ };
147
+ var PulseInputSchema = z2.object({
148
+ data: z2.record(z2.string(), z2.unknown()).default({}),
149
+ eventId: z2.string().trim().min(1).max(200).optional(),
150
+ id: z2.string().trim().min(1).max(200),
151
+ organizationId: z2.string().trim().min(1).max(100),
152
+ occurredAt: isoDateTime.optional(),
153
+ sampling: SamplingContextSchema.default(NoSamplingContext),
154
+ source: z2.string().trim().min(1).max(100),
155
+ subject: PulseSubjectSchema.optional(),
156
+ type: z2.string().trim().min(1).max(100),
157
+ typeVersion: z2.number().int().positive()
158
+ }).strict();
159
+ var PulseSubmissionSchema = z2.object({
160
+ occurredAtMs: z2.number().int().min(0).max(864e13),
161
+ organizationId: z2.string().trim().min(1).max(100),
162
+ pulse: z2.object({
163
+ eventId: z2.string().trim().min(1).max(200).optional(),
164
+ id: z2.string().trim().min(1).max(200),
165
+ payload: z2.record(z2.string(), z2.unknown()).default({}),
166
+ subject: PulseSubjectSchema.optional(),
167
+ type: z2.string().trim().min(1).max(100),
168
+ version: z2.number().int().positive()
169
+ }).strict(),
170
+ sampling: SamplingContextSchema.default(NoSamplingContext),
171
+ source: z2.string().trim().min(1).max(100)
172
+ }).strict();
173
+ var RawPulseSchema = PulseInputSchema.extend({
174
+ occurredAt: isoDateTime,
175
+ receivedAt: isoDateTime
176
+ });
177
+ var PulseDeliverySchema = z2.enum(["live", "reliable"]);
178
+ var PulseTypeDefinitionSchema = z2.object({
179
+ delivery: PulseDeliverySchema,
180
+ id: z2.string().trim().min(1).max(100),
181
+ payloadSchema: z2.record(z2.string(), z2.unknown()),
182
+ requiresExplicitId: z2.boolean().optional(),
183
+ sampling: z2.object({
184
+ keyField: z2.string().trim().min(1).max(200).nullable(),
185
+ unit: SamplingUnitSchema
186
+ }).strict().optional(),
187
+ samplingAllowed: z2.boolean(),
188
+ version: z2.number().int().positive()
189
+ }).strict().superRefine((definition, context) => {
190
+ if (definition.samplingAllowed !== Boolean(definition.sampling)) {
191
+ context.addIssue({ code: "custom", path: ["sampling"], message: "Sampling selector must be present exactly when sampling is allowed." });
192
+ }
193
+ if (definition.sampling?.unit === "member" && !definition.sampling.keyField) {
194
+ context.addIssue({ code: "custom", path: ["sampling", "keyField"], message: "Member sampling requires keyField." });
195
+ }
196
+ if (definition.sampling?.unit === "pulse" && definition.sampling.keyField !== null) {
197
+ context.addIssue({ code: "custom", path: ["sampling", "keyField"], message: "Pulse sampling must not define keyField." });
198
+ }
199
+ });
200
+ var SignalReducerSchema = z2.enum(["presence", "distinct-per-window"]);
201
+ var SignalTypeDefinitionSchema = z2.object({
202
+ id: z2.string().trim().min(1).max(100),
203
+ inputPulseTypes: z2.array(z2.object({
204
+ id: z2.string().trim().min(1).max(100),
205
+ version: z2.number().int().positive()
206
+ }).strict()).min(1),
207
+ dimensions: z2.array(z2.string().trim().min(1).max(100)).optional(),
208
+ partitionKey: z2.string().trim().min(1).max(200).optional(),
209
+ presence: z2.object({
210
+ immutableDimensions: z2.array(z2.string().trim().min(1).max(100)).optional(),
211
+ memberIdField: z2.string().trim().min(1).max(200),
212
+ startedAtField: z2.string().trim().min(1).max(200).optional(),
213
+ timeoutMs: z2.number().int().min(1e3).max(864e5)
214
+ }).strict().optional(),
215
+ reducer: SignalReducerSchema,
216
+ shardCount: z2.number().int().min(1).max(1024),
217
+ shardSchedule: z2.array(z2.object({
218
+ effectiveFrom: isoDateTime,
219
+ generation: z2.number().int().positive(),
220
+ shardCount: z2.number().int().min(1).max(65536)
221
+ }).strict()).optional(),
222
+ version: z2.number().int().positive(),
223
+ windowMs: z2.number().int().min(1e3).max(864e5).optional()
224
+ }).strict().superRefine((definition, context) => {
225
+ if (definition.reducer === "presence" && !definition.presence) {
226
+ context.addIssue({ code: "custom", path: ["presence"], message: "Presence needs a timeout." });
227
+ }
228
+ if (definition.reducer === "distinct-per-window" && !definition.windowMs) {
229
+ context.addIssue({ code: "custom", path: ["windowMs"], message: "Windowed distinct needs a window." });
230
+ }
231
+ const schedule = definition.shardSchedule ?? [];
232
+ if (schedule.length > 0 && (!definition.partitionKey || !definition.dimensions?.length)) {
233
+ context.addIssue({ code: "custom", path: ["partitionKey"], message: "Scheduled routing needs partitionKey and dimensions." });
234
+ }
235
+ if (schedule.length > 0 && definition.reducer === "presence" && !definition.presence?.startedAtField) {
236
+ context.addIssue({ code: "custom", path: ["presence", "startedAtField"], message: "Scheduled presence needs a stable start time." });
237
+ }
238
+ if (definition.windowMs) {
239
+ for (let index = 0; index < schedule.length; index += 1) {
240
+ if (Date.parse(schedule[index].effectiveFrom) % definition.windowMs !== 0) {
241
+ context.addIssue({ code: "custom", path: ["shardSchedule", index], message: "Window generation must start on a window boundary." });
242
+ }
243
+ }
244
+ }
245
+ for (let index = 1; index < schedule.length; index += 1) {
246
+ if (Date.parse(schedule[index].effectiveFrom) <= Date.parse(schedule[index - 1].effectiveFrom) || schedule[index].generation <= schedule[index - 1].generation) {
247
+ context.addIssue({ code: "custom", path: ["shardSchedule", index], message: "Routing generations must increase in time and number." });
248
+ }
249
+ }
250
+ });
251
+ var MetricSignalRefSchema = z2.object({
252
+ signalId: z2.string().trim().min(1).max(500),
253
+ dimension: z2.string().trim().min(1).max(100)
254
+ }).strict();
255
+ var MetricPulseTargetSchema = z2.object({
256
+ generation: z2.number().int().positive(),
257
+ immutableDimensions: z2.array(z2.string().trim().min(1).max(100)).optional(),
258
+ kind: z2.enum(["presence", "distinct-per-window"]),
259
+ memberId: z2.string().trim().min(1).max(200),
260
+ organizationId: z2.string().trim().min(1).max(100),
261
+ ownerId: z2.string().trim().min(1).max(500),
262
+ refs: z2.array(MetricSignalRefSchema).min(1),
263
+ sessionStartedAt: isoDateTime,
264
+ shardCount: z2.number().int().min(1).max(65536),
265
+ shardIndex: z2.number().int().nonnegative(),
266
+ signalTypeId: z2.string().trim().min(1).max(100),
267
+ timeoutMs: z2.number().int().positive().optional(),
268
+ windowEndAt: isoDateTime.optional(),
269
+ windowStartAt: isoDateTime.optional()
270
+ }).strict().superRefine((target, context) => {
271
+ if (target.shardIndex >= target.shardCount) {
272
+ context.addIssue({ code: "custom", path: ["shardIndex"], message: "Shard index exceeds shard count." });
273
+ }
274
+ if (target.kind === "presence" && !target.timeoutMs) {
275
+ context.addIssue({ code: "custom", path: ["timeoutMs"], message: "Presence needs a timeout." });
276
+ }
277
+ if (target.kind === "distinct-per-window" && (!target.windowStartAt || !target.windowEndAt)) {
278
+ context.addIssue({ code: "custom", path: ["windowStartAt"], message: "Distinct needs a window." });
279
+ }
280
+ });
281
+ var RoutedMetricPulseSchema = z2.object({
282
+ pulse: RawPulseSchema,
283
+ target: MetricPulseTargetSchema
284
+ }).strict();
285
+ var MetricSnapshotSchema = z2.object({
286
+ estimatedCount: z2.number().finite().nonnegative(),
287
+ generation: z2.number().int().positive(),
288
+ kind: z2.enum(["presence", "distinct-per-window"]),
289
+ observedAt: isoDateTime,
290
+ organizationId: z2.string().trim().min(1).max(100),
291
+ originId: z2.string().trim().min(1).max(200),
292
+ recovering: z2.boolean(),
293
+ revision: z2.number().int().nonnegative(),
294
+ sampledCount: z2.number().int().nonnegative(),
295
+ shardCount: z2.number().int().min(1).max(65536),
296
+ signalId: z2.string().trim().min(1).max(500),
297
+ signalTypeId: z2.string().trim().min(1).max(100),
298
+ windowEndAt: isoDateTime.optional(),
299
+ windowStartAt: isoDateTime.optional()
300
+ }).strict().superRefine((snapshot, context) => {
301
+ if (snapshot.kind === "distinct-per-window" && (!snapshot.windowStartAt || !snapshot.windowEndAt)) {
302
+ context.addIssue({ code: "custom", path: ["windowStartAt"], message: "Distinct snapshot needs a window." });
303
+ }
304
+ if (snapshot.kind === "presence" && (snapshot.windowStartAt || snapshot.windowEndAt)) {
305
+ context.addIssue({ code: "custom", path: ["windowStartAt"], message: "Presence snapshot must not have a window." });
306
+ }
307
+ });
308
+ var SamplingPolicySchema = z2.object({
309
+ expiresAt: isoDateTime,
310
+ keyField: z2.string().trim().min(1).max(200).nullable(),
311
+ keepProbability: z2.number().min(0).max(1),
312
+ method: z2.literal("pulse-hash-v1"),
313
+ policyId: z2.string().trim().min(1).max(200),
314
+ pulseTypeId: z2.string().trim().min(1).max(100),
315
+ pulseTypeVersion: z2.number().int().positive(),
316
+ revision: z2.number().int().nonnegative(),
317
+ unit: SamplingUnitSchema
318
+ }).strict().superRefine((policy, context) => {
319
+ if (policy.unit === "member" && !policy.keyField) {
320
+ context.addIssue({ code: "custom", message: "Member sampling requires keyField.", path: ["keyField"] });
321
+ }
322
+ if (policy.unit === "pulse" && policy.keyField !== null) {
323
+ context.addIssue({ code: "custom", message: "Pulse sampling cannot define keyField.", path: ["keyField"] });
324
+ }
325
+ });
326
+ var SdkPulseTypeSchema = z2.object({
327
+ definition: PulseTypeDefinitionSchema,
328
+ enabled: z2.boolean(),
329
+ samplingPolicy: SamplingPolicySchema.nullable()
330
+ }).strict().superRefine((entry, context) => {
331
+ if (!entry.definition.samplingAllowed && entry.samplingPolicy !== null) {
332
+ context.addIssue({ code: "custom", path: ["samplingPolicy"], message: "Sampling is forbidden by PulseType." });
333
+ }
334
+ if (entry.samplingPolicy && (entry.samplingPolicy.pulseTypeId !== entry.definition.id || entry.samplingPolicy.pulseTypeVersion !== entry.definition.version)) {
335
+ context.addIssue({ code: "custom", path: ["samplingPolicy"], message: "Sampling policy targets a different PulseType." });
336
+ }
337
+ });
338
+ var SdkConfigurationSchema = z2.object({
339
+ expiresAt: isoDateTime,
340
+ pulseTypes: z2.array(SdkPulseTypeSchema),
341
+ revision: z2.number().int().nonnegative()
342
+ }).strict();
343
+ var LivePulseTargetSchema = z2.object({
344
+ generation: z2.number().int().positive(),
345
+ memberId: z2.string().trim().min(1).max(300),
346
+ organizationId: z2.string().trim().min(1).max(100),
347
+ reducer: SignalReducerSchema,
348
+ shardId: z2.string().trim().min(1).max(100),
349
+ signalId: z2.string().trim().min(1).max(500),
350
+ signalTypeId: z2.string().trim().min(1).max(100),
351
+ signalTypeVersion: z2.number().int().positive(),
352
+ timeoutMs: z2.number().int().min(1e3).max(864e5)
353
+ }).strict();
354
+ var RoutedLivePulseSchema = z2.object({
355
+ pulse: RawPulseSchema,
356
+ target: LivePulseTargetSchema
357
+ }).strict();
358
+ var LiveShardSnapshotSchema = z2.object({
359
+ activeCount: z2.number().int().nonnegative(),
360
+ estimatedActiveCount: z2.number().finite().nonnegative(),
361
+ observedAt: isoDateTime,
362
+ organizationId: z2.string().trim().min(1).max(100),
363
+ recovering: z2.boolean(),
364
+ revision: z2.number().int().nonnegative(),
365
+ shardId: z2.string().trim().min(1).max(100),
366
+ signalId: z2.string().trim().min(1).max(500),
367
+ signalTypeId: z2.string().trim().min(1).max(100)
368
+ }).strict();
369
+ var PulseAggregateSchema = z2.object({
370
+ aggregationKey: z2.string().min(1).max(600),
371
+ count: z2.number().int().positive().max(100),
372
+ createdAt: isoDateTime,
373
+ id: z2.string().trim().min(1).max(100),
374
+ organizationId: z2.string().trim().min(1).max(100),
375
+ pulses: z2.array(RawPulseSchema).min(1).max(100),
376
+ source: z2.string().trim().min(1).max(100),
377
+ subject: PulseSubjectSchema.optional(),
378
+ type: z2.string().trim().min(1).max(100),
379
+ window: z2.object({
380
+ endAt: isoDateTime,
381
+ startAt: isoDateTime
382
+ })
383
+ }).strict().superRefine((aggregate, context) => {
384
+ if (aggregate.count !== aggregate.pulses.length) {
385
+ context.addIssue({
386
+ code: "custom",
387
+ message: "count must match pulses.length.",
388
+ path: ["count"]
389
+ });
390
+ }
391
+ });
392
+ var PulseProcessorResultSchema = z2.object({
393
+ alreadyProcessed: z2.boolean(),
394
+ ok: z2.literal(true),
395
+ signalId: z2.string().min(1)
396
+ }).strict();
397
+
398
+ // src/configurationCache.ts
399
+ var MemorySdkConfigurationCache = class {
400
+ values = /* @__PURE__ */ new Map();
401
+ async get(key) {
402
+ return this.values.get(key);
403
+ }
404
+ async set(key, value) {
405
+ this.values.set(key, { configuration: SdkConfigurationSchema.parse(value.configuration), etag: value.etag });
406
+ }
407
+ };
408
+ var CloudflareSdkConfigurationCache = class {
409
+ constructor(cache, namespace = "https://sdk-config-cache.ultd.invalid") {
410
+ this.cache = cache;
411
+ this.namespace = namespace;
412
+ }
413
+ cache;
414
+ namespace;
415
+ request(key) {
416
+ return new Request(`${this.namespace}/${encodeURIComponent(key)}`);
417
+ }
418
+ async get(key) {
419
+ const response = await this.cache.match(this.request(key));
420
+ if (!response) return void 0;
421
+ const parsed = SdkConfigurationSchema.safeParse(await response.json());
422
+ return parsed.success ? { configuration: parsed.data, etag: response.headers.get("etag") } : void 0;
423
+ }
424
+ async set(key, value) {
425
+ const configuration = SdkConfigurationSchema.parse(value.configuration);
426
+ const remainingSeconds = Math.max(1, Math.floor((Date.parse(configuration.expiresAt) - Date.now()) / 1e3));
427
+ await this.cache.put(this.request(key), new Response(JSON.stringify(configuration), {
428
+ headers: {
429
+ // This is an internal Cache API entry under a token-derived key, not an HTTP response to the caller.
430
+ "cache-control": `public, max-age=${remainingSeconds}`,
431
+ "content-type": "application/json",
432
+ ...value.etag ? { etag: value.etag } : {}
433
+ }
434
+ }));
435
+ }
436
+ };
437
+
438
+ // src/cache.ts
439
+ var MemorySamplingPolicyCache = class {
440
+ policies = /* @__PURE__ */ new Map();
441
+ async get(key) {
442
+ return this.policies.get(key);
443
+ }
444
+ async set(key, policy) {
445
+ this.policies.set(key, SamplingPolicySchema.parse(policy));
446
+ }
447
+ };
448
+ var CloudflareSamplingPolicyCache = class {
449
+ constructor(cache, namespace = "https://sampling-cache.ultd.invalid") {
450
+ this.cache = cache;
451
+ this.namespace = namespace;
452
+ }
453
+ cache;
454
+ namespace;
455
+ request(key) {
456
+ return new Request(`${this.namespace}/${encodeURIComponent(key)}`);
457
+ }
458
+ async get(key) {
459
+ const response = await this.cache.match(this.request(key));
460
+ if (!response) return void 0;
461
+ const parsed = SamplingPolicySchema.safeParse(await response.json());
462
+ return parsed.success ? parsed.data : void 0;
463
+ }
464
+ async set(key, policy) {
465
+ const parsed = SamplingPolicySchema.parse(policy);
466
+ const remainingSeconds = Math.max(1, Math.floor((Date.parse(parsed.expiresAt) - Date.now()) / 1e3));
467
+ await this.cache.put(this.request(key), new Response(JSON.stringify(parsed), {
468
+ headers: {
469
+ "cache-control": `max-age=${remainingSeconds}`,
470
+ "content-type": "application/json",
471
+ etag: `"${parsed.policyId}:${parsed.revision}"`
472
+ }
473
+ }));
474
+ }
475
+ };
476
+
477
+ // src/index.ts
478
+ var typeKey = (type, version) => `${type}@${version}`;
479
+ var selectedByHash = async (value, probability) => {
480
+ if (probability <= 0) return false;
481
+ if (probability >= 1) return true;
482
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
483
+ return new DataView(digest).getUint32(0) / 4294967296 < probability;
484
+ };
485
+ var selectionValue = (input, policy, pulseId) => {
486
+ if (policy.unit === "pulse") return pulseId;
487
+ const value = policy.keyField === "pulse.id" ? pulseId : policy.keyField ? input.pulse.payload[policy.keyField] : void 0;
488
+ if (typeof value !== "string" || value.length === 0) {
489
+ throw new Error(`Sampling policy ${policy.policyId} requires string field ${policy.keyField}.`);
490
+ }
491
+ return value;
492
+ };
493
+ var configKey = async (baseUrl, apiToken) => {
494
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`${baseUrl}\0${apiToken}`));
495
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
496
+ };
497
+ var UnlimitedClient = class {
498
+ apiToken;
499
+ baseUrl;
500
+ source;
501
+ cache;
502
+ fetcher;
503
+ now;
504
+ randomUUID;
505
+ cacheKey;
506
+ loaded;
507
+ refreshing;
508
+ constructor(options) {
509
+ if (!options.apiToken?.trim()) {
510
+ throw new Error("apiToken is required.");
511
+ }
512
+ if (!options.source?.trim()) {
513
+ throw new Error("source is required.");
514
+ }
515
+ this.apiToken = options.apiToken;
516
+ this.baseUrl = options.baseUrl.replace(/\/$/, "");
517
+ this.source = options.source;
518
+ this.cache = options.cache ?? new MemorySdkConfigurationCache();
519
+ this.fetcher = options.fetch ?? fetch;
520
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
521
+ this.randomUUID = options.randomUUID ?? (() => crypto.randomUUID());
522
+ this.cacheKey = configKey(this.baseUrl, this.apiToken);
523
+ }
524
+ install(cached) {
525
+ const configuration = SdkConfigurationSchema.parse(cached.configuration);
526
+ if (Date.parse(configuration.expiresAt) <= this.now().getTime()) {
527
+ throw new Error("SDK configuration has expired.");
528
+ }
529
+ const types = /* @__PURE__ */ new Map();
530
+ for (const entry of configuration.pulseTypes) {
531
+ const key = typeKey(entry.definition.id, entry.definition.version);
532
+ if (types.has(key)) throw new Error(`Duplicate PulseType ${key} in SDK configuration.`);
533
+ types.set(key, { entry, validate: compilePayloadSchema(entry.definition.payloadSchema) });
534
+ }
535
+ return { cached: { configuration, etag: cached.etag }, types };
536
+ }
537
+ async refresh() {
538
+ const key = await this.cacheKey;
539
+ const cached = this.loaded?.cached ?? await this.cache.get(key).catch(() => void 0);
540
+ if (cached && Date.parse(cached.configuration.expiresAt) > this.now().getTime()) {
541
+ return this.install(cached);
542
+ }
543
+ const response = await this.fetcher(`${this.baseUrl}/v1/sdk-config`, {
544
+ headers: {
545
+ authorization: `Bearer ${this.apiToken}`,
546
+ ...cached?.etag ? { "if-none-match": cached.etag } : {}
547
+ }
548
+ });
549
+ let next;
550
+ if (response.status === 304) {
551
+ if (!cached) throw new Error("SDK configuration returned 304 without a cached body.");
552
+ const expiresAt = response.headers.get("x-ultd-config-expires-at");
553
+ if (!expiresAt || !Number.isFinite(Date.parse(expiresAt))) {
554
+ throw new Error("SDK configuration returned 304 without a valid expiry.");
555
+ }
556
+ next = {
557
+ configuration: {
558
+ ...cached.configuration,
559
+ expiresAt,
560
+ pulseTypes: cached.configuration.pulseTypes.map((entry) => ({
561
+ ...entry,
562
+ samplingPolicy: entry.samplingPolicy && { ...entry.samplingPolicy, expiresAt }
563
+ }))
564
+ },
565
+ etag: cached.etag
566
+ };
567
+ } else {
568
+ if (!response.ok) throw new Error(`SDK configuration request failed with ${response.status}.`);
569
+ next = { configuration: SdkConfigurationSchema.parse(await response.json()), etag: response.headers.get("etag") };
570
+ }
571
+ const loaded = this.install(next);
572
+ await this.cache.set(key, loaded.cached).catch(() => void 0);
573
+ return loaded;
574
+ }
575
+ async configuration() {
576
+ if (this.loaded && Date.parse(this.loaded.cached.configuration.expiresAt) > this.now().getTime()) {
577
+ return this.loaded;
578
+ }
579
+ this.refreshing ??= this.refresh().then((loaded) => {
580
+ this.loaded = loaded;
581
+ return loaded;
582
+ }).finally(() => {
583
+ this.refreshing = void 0;
584
+ });
585
+ return this.refreshing;
586
+ }
587
+ async emit(input) {
588
+ const loaded = await this.configuration();
589
+ const type = loaded.types.get(typeKey(input.pulse.type, input.pulse.version));
590
+ if (!type) throw new Error(`Unknown PulseType ${input.pulse.type}@${input.pulse.version}.`);
591
+ if (type.entry.definition.requiresExplicitId && !input.pulse.id) {
592
+ throw new Error(`PulseType ${input.pulse.type}@${input.pulse.version} requires an explicit stable pulse.id.`);
593
+ }
594
+ const id = input.pulse.id ?? this.randomUUID();
595
+ const validated = PulseSubmissionSchema.parse({
596
+ ...input,
597
+ pulse: { ...input.pulse, id },
598
+ sampling: NoSamplingContext,
599
+ source: this.source
600
+ });
601
+ const payload = type.validate.parse(validated.pulse.payload);
602
+ if (!type.entry.enabled) {
603
+ return { accepted: false, disabled: true, pulseId: id, sampledOut: false };
604
+ }
605
+ const policy = type.entry.samplingPolicy;
606
+ let sampling = NoSamplingContext;
607
+ if (policy) {
608
+ sampling = {
609
+ applied: policy.keepProbability < 1,
610
+ appliedKeepProbability: policy.keepProbability,
611
+ effectiveKeepProbability: policy.keepProbability,
612
+ ingressApplied: false,
613
+ ingressKeepProbability: 1,
614
+ ingressMethod: "none",
615
+ knownKeepProbability: policy.keepProbability,
616
+ method: policy.keepProbability < 1 ? policy.method : "none",
617
+ policyId: policy.policyId,
618
+ policyRevision: policy.revision,
619
+ unit: policy.unit
620
+ };
621
+ const selected = await selectedByHash(
622
+ `sdk:${policy.policyId}:${selectionValue(input, policy, id)}`,
623
+ policy.keepProbability
624
+ );
625
+ if (!selected) return { accepted: false, pulseId: id, sampledOut: true };
626
+ }
627
+ const submission = PulseSubmissionSchema.parse({
628
+ ...validated,
629
+ pulse: { ...validated.pulse, payload },
630
+ sampling
631
+ });
632
+ const response = await this.fetcher(`${this.baseUrl}/v1/pulses`, {
633
+ body: JSON.stringify(submission),
634
+ headers: {
635
+ authorization: `Bearer ${this.apiToken}`,
636
+ "content-type": "application/json"
637
+ },
638
+ method: "POST"
639
+ });
640
+ if (!response.ok) throw new Error(`Pulse request failed with ${response.status}.`);
641
+ const body = await response.json();
642
+ return {
643
+ accepted: body.accepted !== false,
644
+ ...body.disabled === true ? { disabled: true } : {},
645
+ pulseId: id,
646
+ sampledOut: body.sampledOut === true
647
+ };
648
+ }
649
+ };
650
+ export {
651
+ CloudflareSamplingPolicyCache,
652
+ CloudflareSdkConfigurationCache,
653
+ MemorySamplingPolicyCache,
654
+ MemorySdkConfigurationCache,
655
+ UnlimitedClient
656
+ };
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@ultdai/pulse",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "files": ["dist", "README.md"],
6
+ "publishConfig": { "access": "public" },
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "scripts": {
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",
18
+ "test": "vitest run test --exclude 'dist/**'",
19
+ "typecheck": "tsc -p tsconfig.json --noEmit"
20
+ },
21
+ "dependencies": {
22
+ "zod": "4.5.4"
23
+ },
24
+ "devDependencies": {
25
+ "@ultdai/contracts": "workspace:*",
26
+ "esbuild": "0.28.1",
27
+ "typescript": "7.0.2",
28
+ "vitest": "4.1.11"
29
+ }
30
+ }