@t402/streaming-payments 1.0.0-beta.1

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.
@@ -0,0 +1,1321 @@
1
+ // src/streaming/types.ts
2
+ import { z } from "zod";
3
+ var StreamState = z.enum([
4
+ "idle",
5
+ // Stream not started
6
+ "active",
7
+ // Streaming in progress
8
+ "paused",
9
+ // Temporarily paused
10
+ "completed",
11
+ // Stream completed (exhausted)
12
+ "cancelled"
13
+ // Stream cancelled
14
+ ]);
15
+ var RateType = z.enum([
16
+ "fixed",
17
+ // Fixed rate per second
18
+ "variable",
19
+ // Rate can change
20
+ "tiered",
21
+ // Different rates based on usage
22
+ "dynamic"
23
+ // Demand-based pricing
24
+ ]);
25
+ var StreamRate = z.object({
26
+ type: RateType,
27
+ baseRate: z.string(),
28
+ // Base rate per second
29
+ minRate: z.string().optional(),
30
+ // Minimum rate
31
+ maxRate: z.string().optional(),
32
+ // Maximum rate
33
+ // For tiered rates
34
+ tiers: z.array(z.object({
35
+ threshold: z.string(),
36
+ // Usage threshold
37
+ rate: z.string()
38
+ // Rate after threshold
39
+ })).optional(),
40
+ // For dynamic rates
41
+ adjustmentInterval: z.number().optional(),
42
+ // How often to adjust (seconds)
43
+ adjustmentFactor: z.number().optional()
44
+ // Max adjustment per interval
45
+ });
46
+ var UsageMetrics = z.object({
47
+ totalSeconds: z.number(),
48
+ totalAmount: z.string(),
49
+ averageRate: z.string(),
50
+ peakRate: z.string(),
51
+ startTime: z.number(),
52
+ endTime: z.number().optional(),
53
+ // Breakdown by period
54
+ hourly: z.array(z.object({
55
+ hour: z.number(),
56
+ amount: z.string(),
57
+ seconds: z.number()
58
+ })).optional()
59
+ });
60
+ var MeteringRecord = z.object({
61
+ timestamp: z.number(),
62
+ duration: z.number(),
63
+ // Seconds since last record
64
+ amount: z.string(),
65
+ // Amount for this period
66
+ rate: z.string(),
67
+ // Rate applied
68
+ cumulative: z.string(),
69
+ // Cumulative total
70
+ metadata: z.record(z.unknown()).optional()
71
+ });
72
+ var BillingPeriod = z.enum([
73
+ "realtime",
74
+ // Continuous real-time billing
75
+ "second",
76
+ // Per-second
77
+ "minute",
78
+ // Per-minute
79
+ "hour",
80
+ // Per-hour
81
+ "day"
82
+ // Per-day
83
+ ]);
84
+ var BillingConfig = z.object({
85
+ period: BillingPeriod,
86
+ minimumCharge: z.string().default("0"),
87
+ roundingMode: z.enum(["floor", "ceil", "round"]).default("floor"),
88
+ gracePeriod: z.number().default(0),
89
+ // Seconds of free usage
90
+ invoiceInterval: z.number().optional()
91
+ // Generate invoice every N seconds
92
+ });
93
+ var InvoiceItem = z.object({
94
+ description: z.string(),
95
+ quantity: z.number(),
96
+ // Duration in billing periods
97
+ rate: z.string(),
98
+ amount: z.string(),
99
+ startTime: z.number(),
100
+ endTime: z.number()
101
+ });
102
+ var Invoice = z.object({
103
+ id: z.string(),
104
+ channelId: z.string(),
105
+ payer: z.string(),
106
+ payee: z.string(),
107
+ items: z.array(InvoiceItem),
108
+ subtotal: z.string(),
109
+ fees: z.string(),
110
+ total: z.string(),
111
+ currency: z.string(),
112
+ status: z.enum(["pending", "paid", "settled", "disputed"]),
113
+ createdAt: z.number(),
114
+ dueAt: z.number().optional(),
115
+ paidAt: z.number().optional()
116
+ });
117
+ var StreamSession = z.object({
118
+ id: z.string(),
119
+ channelId: z.string(),
120
+ state: StreamState,
121
+ rate: StreamRate,
122
+ startedAt: z.number().optional(),
123
+ pausedAt: z.number().optional(),
124
+ endedAt: z.number().optional(),
125
+ totalDuration: z.number(),
126
+ // Total streaming seconds
127
+ totalAmount: z.string(),
128
+ // Total amount streamed
129
+ meteringRecords: z.array(MeteringRecord),
130
+ billingConfig: BillingConfig,
131
+ invoices: z.array(z.string())
132
+ // Invoice IDs
133
+ });
134
+ var RateAdjustmentRequest = z.object({
135
+ sessionId: z.string(),
136
+ newRate: z.string(),
137
+ reason: z.string(),
138
+ effectiveFrom: z.number().optional(),
139
+ // Timestamp, default now
140
+ signature: z.string().optional()
141
+ // For mutual rate changes
142
+ });
143
+ var StreamEvent = z.discriminatedUnion("type", [
144
+ z.object({
145
+ type: z.literal("started"),
146
+ sessionId: z.string(),
147
+ timestamp: z.number(),
148
+ rate: z.string()
149
+ }),
150
+ z.object({
151
+ type: z.literal("paused"),
152
+ sessionId: z.string(),
153
+ timestamp: z.number(),
154
+ totalStreamed: z.string()
155
+ }),
156
+ z.object({
157
+ type: z.literal("resumed"),
158
+ sessionId: z.string(),
159
+ timestamp: z.number()
160
+ }),
161
+ z.object({
162
+ type: z.literal("rate_changed"),
163
+ sessionId: z.string(),
164
+ timestamp: z.number(),
165
+ oldRate: z.string(),
166
+ newRate: z.string()
167
+ }),
168
+ z.object({
169
+ type: z.literal("checkpoint"),
170
+ sessionId: z.string(),
171
+ timestamp: z.number(),
172
+ amount: z.string(),
173
+ checkpointId: z.string()
174
+ }),
175
+ z.object({
176
+ type: z.literal("completed"),
177
+ sessionId: z.string(),
178
+ timestamp: z.number(),
179
+ totalAmount: z.string(),
180
+ totalDuration: z.number()
181
+ }),
182
+ z.object({
183
+ type: z.literal("cancelled"),
184
+ sessionId: z.string(),
185
+ timestamp: z.number(),
186
+ reason: z.string()
187
+ })
188
+ ]);
189
+
190
+ // src/streaming/flow.ts
191
+ var FlowController = class {
192
+ session;
193
+ config;
194
+ updateTimer;
195
+ checkpointTimer;
196
+ eventListeners = /* @__PURE__ */ new Set();
197
+ lastUpdateTime = 0;
198
+ constructor(channelId, rate, billingConfig, config = {}) {
199
+ this.config = {
200
+ updateInterval: config.updateInterval ?? 1e3,
201
+ bufferTime: config.bufferTime ?? 60,
202
+ autoCheckpoint: config.autoCheckpoint ?? true,
203
+ checkpointInterval: config.checkpointInterval ?? 3600
204
+ };
205
+ this.session = {
206
+ id: this.generateSessionId(),
207
+ channelId,
208
+ state: "idle",
209
+ rate,
210
+ totalDuration: 0,
211
+ totalAmount: "0",
212
+ meteringRecords: [],
213
+ billingConfig,
214
+ invoices: []
215
+ };
216
+ }
217
+ /**
218
+ * Get current session state
219
+ */
220
+ getSession() {
221
+ return { ...this.session };
222
+ }
223
+ /**
224
+ * Get current state
225
+ */
226
+ getState() {
227
+ return this.session.state;
228
+ }
229
+ /**
230
+ * Start streaming
231
+ */
232
+ start() {
233
+ if (this.session.state !== "idle" && this.session.state !== "paused") {
234
+ return { success: false, error: "Stream must be idle or paused to start" };
235
+ }
236
+ const now = Date.now();
237
+ if (this.session.state === "idle") {
238
+ this.session.startedAt = now;
239
+ } else {
240
+ const pauseDuration = now - (this.session.pausedAt ?? now);
241
+ this.session.pausedAt = void 0;
242
+ this.addMeteringRecord({
243
+ timestamp: now,
244
+ duration: 0,
245
+ amount: "0",
246
+ rate: "0",
247
+ cumulative: this.session.totalAmount,
248
+ metadata: { event: "resume", pauseDuration }
249
+ });
250
+ }
251
+ this.session.state = "active";
252
+ this.lastUpdateTime = now;
253
+ this.startUpdateTimer();
254
+ if (this.config.autoCheckpoint) {
255
+ this.startCheckpointTimer();
256
+ }
257
+ this.emitEvent({
258
+ type: "started",
259
+ sessionId: this.session.id,
260
+ timestamp: now,
261
+ rate: this.session.rate.baseRate
262
+ });
263
+ return { success: true };
264
+ }
265
+ /**
266
+ * Pause streaming
267
+ */
268
+ pause() {
269
+ if (this.session.state !== "active") {
270
+ return { success: false, error: "Stream must be active to pause" };
271
+ }
272
+ const now = Date.now();
273
+ this.updateTotals(now);
274
+ this.session.state = "paused";
275
+ this.session.pausedAt = now;
276
+ this.stopTimers();
277
+ this.emitEvent({
278
+ type: "paused",
279
+ sessionId: this.session.id,
280
+ timestamp: now,
281
+ totalStreamed: this.session.totalAmount
282
+ });
283
+ return { success: true };
284
+ }
285
+ /**
286
+ * Resume streaming (alias for start when paused)
287
+ */
288
+ resume() {
289
+ if (this.session.state !== "paused") {
290
+ return { success: false, error: "Stream must be paused to resume" };
291
+ }
292
+ const result = this.start();
293
+ if (result.success) {
294
+ this.emitEvent({
295
+ type: "resumed",
296
+ sessionId: this.session.id,
297
+ timestamp: Date.now()
298
+ });
299
+ }
300
+ return result;
301
+ }
302
+ /**
303
+ * Stop streaming (complete)
304
+ */
305
+ stop() {
306
+ const now = Date.now();
307
+ if (this.session.state === "active") {
308
+ this.updateTotals(now);
309
+ }
310
+ this.stopTimers();
311
+ this.session.state = "completed";
312
+ this.session.endedAt = now;
313
+ this.emitEvent({
314
+ type: "completed",
315
+ sessionId: this.session.id,
316
+ timestamp: now,
317
+ totalAmount: this.session.totalAmount,
318
+ totalDuration: this.session.totalDuration
319
+ });
320
+ return {
321
+ success: true,
322
+ finalAmount: this.session.totalAmount
323
+ };
324
+ }
325
+ /**
326
+ * Cancel streaming
327
+ */
328
+ cancel(reason) {
329
+ const now = Date.now();
330
+ if (this.session.state === "active") {
331
+ this.updateTotals(now);
332
+ }
333
+ this.stopTimers();
334
+ this.session.state = "cancelled";
335
+ this.session.endedAt = now;
336
+ this.emitEvent({
337
+ type: "cancelled",
338
+ sessionId: this.session.id,
339
+ timestamp: now,
340
+ reason
341
+ });
342
+ return { success: true };
343
+ }
344
+ /**
345
+ * Get current streamed amount
346
+ */
347
+ getCurrentAmount() {
348
+ if (this.session.state !== "active") {
349
+ return this.session.totalAmount;
350
+ }
351
+ const now = Date.now();
352
+ const elapsed = Math.floor((now - this.lastUpdateTime) / 1e3);
353
+ const additionalAmount = this.calculateAmount(elapsed, this.session.rate);
354
+ return (BigInt(this.session.totalAmount) + BigInt(additionalAmount)).toString();
355
+ }
356
+ /**
357
+ * Get current rate
358
+ */
359
+ getCurrentRate() {
360
+ return this.getEffectiveRate(this.session.rate, this.session.totalAmount);
361
+ }
362
+ /**
363
+ * Get time until exhaustion (returns -1 if infinite)
364
+ */
365
+ getTimeUntilExhaustion(channelCapacity) {
366
+ if (this.session.state !== "active") {
367
+ return -1;
368
+ }
369
+ const remaining = BigInt(channelCapacity) - BigInt(this.getCurrentAmount());
370
+ if (remaining <= 0n) {
371
+ return 0;
372
+ }
373
+ const rate = BigInt(this.getCurrentRate());
374
+ if (rate <= 0n) {
375
+ return -1;
376
+ }
377
+ return Number(remaining / rate);
378
+ }
379
+ /**
380
+ * Check if stream is near exhaustion
381
+ */
382
+ isNearExhaustion(channelCapacity) {
383
+ const remaining = this.getTimeUntilExhaustion(channelCapacity);
384
+ return remaining >= 0 && remaining <= this.config.bufferTime;
385
+ }
386
+ /**
387
+ * Create manual checkpoint
388
+ */
389
+ createCheckpoint() {
390
+ const now = Date.now();
391
+ const amount = this.getCurrentAmount();
392
+ const checkpointId = `cp_${this.session.id}_${now}`;
393
+ this.emitEvent({
394
+ type: "checkpoint",
395
+ sessionId: this.session.id,
396
+ timestamp: now,
397
+ amount,
398
+ checkpointId
399
+ });
400
+ return { id: checkpointId, amount, timestamp: now };
401
+ }
402
+ /**
403
+ * Subscribe to stream events
404
+ */
405
+ onEvent(callback) {
406
+ this.eventListeners.add(callback);
407
+ return () => this.eventListeners.delete(callback);
408
+ }
409
+ /**
410
+ * Clean up resources
411
+ */
412
+ destroy() {
413
+ this.stopTimers();
414
+ this.eventListeners.clear();
415
+ }
416
+ startUpdateTimer() {
417
+ this.updateTimer = setInterval(() => {
418
+ if (this.session.state === "active") {
419
+ this.updateTotals(Date.now());
420
+ }
421
+ }, this.config.updateInterval);
422
+ }
423
+ startCheckpointTimer() {
424
+ this.checkpointTimer = setInterval(() => {
425
+ if (this.session.state === "active") {
426
+ this.createCheckpoint();
427
+ }
428
+ }, this.config.checkpointInterval * 1e3);
429
+ }
430
+ stopTimers() {
431
+ if (this.updateTimer) {
432
+ clearInterval(this.updateTimer);
433
+ this.updateTimer = void 0;
434
+ }
435
+ if (this.checkpointTimer) {
436
+ clearInterval(this.checkpointTimer);
437
+ this.checkpointTimer = void 0;
438
+ }
439
+ }
440
+ updateTotals(now) {
441
+ const elapsed = Math.floor((now - this.lastUpdateTime) / 1e3);
442
+ if (elapsed <= 0) return;
443
+ const amount = this.calculateAmount(elapsed, this.session.rate);
444
+ const newTotal = BigInt(this.session.totalAmount) + BigInt(amount);
445
+ this.session.totalDuration += elapsed;
446
+ this.session.totalAmount = newTotal.toString();
447
+ this.lastUpdateTime = now;
448
+ this.addMeteringRecord({
449
+ timestamp: now,
450
+ duration: elapsed,
451
+ amount,
452
+ rate: this.getCurrentRate(),
453
+ cumulative: this.session.totalAmount
454
+ });
455
+ }
456
+ calculateAmount(seconds, rate) {
457
+ const effectiveRate = this.getEffectiveRate(rate, this.session.totalAmount);
458
+ return (BigInt(effectiveRate) * BigInt(seconds)).toString();
459
+ }
460
+ getEffectiveRate(rate, totalAmount) {
461
+ if (rate.type === "fixed") {
462
+ return rate.baseRate;
463
+ }
464
+ if (rate.type === "tiered" && rate.tiers) {
465
+ const amount = BigInt(totalAmount);
466
+ let applicableRate = rate.baseRate;
467
+ for (const tier of rate.tiers) {
468
+ if (amount >= BigInt(tier.threshold)) {
469
+ applicableRate = tier.rate;
470
+ }
471
+ }
472
+ return applicableRate;
473
+ }
474
+ return rate.baseRate;
475
+ }
476
+ addMeteringRecord(record) {
477
+ this.session.meteringRecords.push(record);
478
+ if (this.session.meteringRecords.length > 1e3) {
479
+ this.session.meteringRecords = this.session.meteringRecords.slice(-1e3);
480
+ }
481
+ }
482
+ emitEvent(event) {
483
+ this.eventListeners.forEach((callback) => {
484
+ try {
485
+ callback(event);
486
+ } catch {
487
+ }
488
+ });
489
+ }
490
+ generateSessionId() {
491
+ return `ss_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
492
+ }
493
+ };
494
+ function createFixedRateFlow(channelId, ratePerSecond, config) {
495
+ const rate = {
496
+ type: "fixed",
497
+ baseRate: ratePerSecond
498
+ };
499
+ const billingConfig = {
500
+ period: "realtime",
501
+ minimumCharge: "0",
502
+ roundingMode: "floor",
503
+ gracePeriod: 0
504
+ };
505
+ return new FlowController(channelId, rate, billingConfig, config);
506
+ }
507
+ function createTieredRateFlow(channelId, baseRate, tiers, config) {
508
+ const rate = {
509
+ type: "tiered",
510
+ baseRate,
511
+ tiers
512
+ };
513
+ const billingConfig = {
514
+ period: "realtime",
515
+ minimumCharge: "0",
516
+ roundingMode: "floor",
517
+ gracePeriod: 0
518
+ };
519
+ return new FlowController(channelId, rate, billingConfig, config);
520
+ }
521
+
522
+ // src/streaming/rate.ts
523
+ var RateController = class {
524
+ currentRate;
525
+ config;
526
+ history = [];
527
+ lastChangeTime = 0;
528
+ constructor(initialRate, config = {}) {
529
+ this.currentRate = { ...initialRate };
530
+ this.config = {
531
+ maxChangePercent: config.maxChangePercent ?? 50,
532
+ minChangeInterval: config.minChangeInterval ?? 60,
533
+ smoothingFactor: config.smoothingFactor ?? 0.3
534
+ };
535
+ }
536
+ /**
537
+ * Get current rate configuration
538
+ */
539
+ getRate() {
540
+ return { ...this.currentRate };
541
+ }
542
+ /**
543
+ * Get current effective rate
544
+ */
545
+ getEffectiveRate(totalUsage) {
546
+ if (this.currentRate.type === "fixed") {
547
+ return this.currentRate.baseRate;
548
+ }
549
+ if (this.currentRate.type === "tiered" && this.currentRate.tiers && totalUsage) {
550
+ return this.calculateTieredRate(totalUsage);
551
+ }
552
+ return this.currentRate.baseRate;
553
+ }
554
+ /**
555
+ * Request rate adjustment
556
+ */
557
+ adjustRate(request) {
558
+ const now = Date.now();
559
+ const timeSinceLastChange = (now - this.lastChangeTime) / 1e3;
560
+ if (timeSinceLastChange < this.config.minChangeInterval) {
561
+ return {
562
+ success: false,
563
+ error: `Rate can only be changed every ${this.config.minChangeInterval} seconds`
564
+ };
565
+ }
566
+ const newRateBigInt = BigInt(request.newRate);
567
+ if (newRateBigInt <= 0n) {
568
+ return { success: false, error: "Rate must be positive" };
569
+ }
570
+ if (this.currentRate.minRate && newRateBigInt < BigInt(this.currentRate.minRate)) {
571
+ return {
572
+ success: false,
573
+ error: `Rate cannot be below minimum: ${this.currentRate.minRate}`
574
+ };
575
+ }
576
+ if (this.currentRate.maxRate && newRateBigInt > BigInt(this.currentRate.maxRate)) {
577
+ return {
578
+ success: false,
579
+ error: `Rate cannot exceed maximum: ${this.currentRate.maxRate}`
580
+ };
581
+ }
582
+ const currentRateBigInt = BigInt(this.currentRate.baseRate);
583
+ const changePercent = this.calculateChangePercent(currentRateBigInt, newRateBigInt);
584
+ let adjustedRate = request.newRate;
585
+ if (changePercent > this.config.maxChangePercent) {
586
+ adjustedRate = this.applyMaxChange(
587
+ currentRateBigInt,
588
+ newRateBigInt,
589
+ this.config.maxChangePercent
590
+ );
591
+ }
592
+ this.history.push({
593
+ timestamp: now,
594
+ rate: adjustedRate,
595
+ reason: request.reason,
596
+ previousRate: this.currentRate.baseRate
597
+ });
598
+ this.currentRate.baseRate = adjustedRate;
599
+ this.lastChangeTime = now;
600
+ return {
601
+ success: true,
602
+ newRate: adjustedRate,
603
+ adjustedAmount: adjustedRate !== request.newRate ? adjustedRate : void 0
604
+ };
605
+ }
606
+ /**
607
+ * Set rate bounds
608
+ */
609
+ setBounds(minRate, maxRate) {
610
+ if (minRate !== void 0) {
611
+ this.currentRate.minRate = minRate;
612
+ }
613
+ if (maxRate !== void 0) {
614
+ this.currentRate.maxRate = maxRate;
615
+ }
616
+ }
617
+ /**
618
+ * Add or update a tier
619
+ */
620
+ addTier(threshold, rate) {
621
+ if (this.currentRate.type !== "tiered") {
622
+ this.currentRate.type = "tiered";
623
+ this.currentRate.tiers = [];
624
+ }
625
+ const tiers = this.currentRate.tiers ?? [];
626
+ const existingIndex = tiers.findIndex((t) => t.threshold === threshold);
627
+ if (existingIndex >= 0) {
628
+ tiers[existingIndex].rate = rate;
629
+ } else {
630
+ tiers.push({ threshold, rate });
631
+ tiers.sort((a, b) => {
632
+ return BigInt(a.threshold) < BigInt(b.threshold) ? -1 : 1;
633
+ });
634
+ }
635
+ this.currentRate.tiers = tiers;
636
+ }
637
+ /**
638
+ * Remove a tier
639
+ */
640
+ removeTier(threshold) {
641
+ if (!this.currentRate.tiers) return false;
642
+ const initialLength = this.currentRate.tiers.length;
643
+ this.currentRate.tiers = this.currentRate.tiers.filter(
644
+ (t) => t.threshold !== threshold
645
+ );
646
+ return this.currentRate.tiers.length < initialLength;
647
+ }
648
+ /**
649
+ * Get rate history
650
+ */
651
+ getHistory() {
652
+ return [...this.history];
653
+ }
654
+ /**
655
+ * Calculate average rate over time period
656
+ */
657
+ getAverageRate(startTime, endTime) {
658
+ const relevantHistory = this.history.filter(
659
+ (h) => h.timestamp >= startTime && h.timestamp <= endTime
660
+ );
661
+ if (relevantHistory.length === 0) {
662
+ return this.currentRate.baseRate;
663
+ }
664
+ let totalWeight = 0n;
665
+ let weightedSum = 0n;
666
+ let prevTime = startTime;
667
+ for (const entry of relevantHistory) {
668
+ const duration = BigInt(entry.timestamp - prevTime);
669
+ weightedSum += BigInt(entry.previousRate) * duration;
670
+ totalWeight += duration;
671
+ prevTime = entry.timestamp;
672
+ }
673
+ const finalDuration = BigInt(endTime - prevTime);
674
+ weightedSum += BigInt(this.currentRate.baseRate) * finalDuration;
675
+ totalWeight += finalDuration;
676
+ if (totalWeight === 0n) {
677
+ return this.currentRate.baseRate;
678
+ }
679
+ return (weightedSum / totalWeight).toString();
680
+ }
681
+ /**
682
+ * Calculate rate for dynamic pricing based on demand
683
+ */
684
+ calculateDynamicRate(demand, _baseRate) {
685
+ const base = BigInt(this.currentRate.baseRate);
686
+ const min = this.currentRate.minRate ? BigInt(this.currentRate.minRate) : base / 2n;
687
+ const max = this.currentRate.maxRate ? BigInt(this.currentRate.maxRate) : base * 2n;
688
+ const range = max - min;
689
+ const adjustment = BigInt(Math.floor(Number(range) * demand));
690
+ return (min + adjustment).toString();
691
+ }
692
+ /**
693
+ * Apply exponential smoothing for rate changes
694
+ */
695
+ smoothRate(targetRate) {
696
+ const current = BigInt(this.currentRate.baseRate);
697
+ const target = BigInt(targetRate);
698
+ const alpha = this.config.smoothingFactor;
699
+ const smoothed = BigInt(Math.floor(
700
+ alpha * Number(target) + (1 - alpha) * Number(current)
701
+ ));
702
+ return smoothed.toString();
703
+ }
704
+ calculateTieredRate(totalUsage) {
705
+ const usage = BigInt(totalUsage);
706
+ const tiers = this.currentRate.tiers ?? [];
707
+ let applicableRate = this.currentRate.baseRate;
708
+ for (const tier of tiers) {
709
+ if (usage >= BigInt(tier.threshold)) {
710
+ applicableRate = tier.rate;
711
+ }
712
+ }
713
+ return applicableRate;
714
+ }
715
+ calculateChangePercent(from, to) {
716
+ if (from === 0n) return 100;
717
+ const diff = to > from ? to - from : from - to;
718
+ return Number(diff * 100n / from);
719
+ }
720
+ applyMaxChange(from, to, maxPercent) {
721
+ const maxChange = from * BigInt(maxPercent) / 100n;
722
+ if (to > from) {
723
+ return (from + maxChange).toString();
724
+ } else {
725
+ return (from - maxChange).toString();
726
+ }
727
+ }
728
+ };
729
+ var RateLimiter = class {
730
+ requests = /* @__PURE__ */ new Map();
731
+ maxRequests;
732
+ windowMs;
733
+ constructor(maxRequests = 10, windowMs = 6e4) {
734
+ this.maxRequests = maxRequests;
735
+ this.windowMs = windowMs;
736
+ }
737
+ /**
738
+ * Check if request is allowed
739
+ */
740
+ isAllowed(key) {
741
+ const now = Date.now();
742
+ const windowStart = now - this.windowMs;
743
+ let requests = this.requests.get(key) ?? [];
744
+ requests = requests.filter((t) => t > windowStart);
745
+ if (requests.length >= this.maxRequests) {
746
+ return false;
747
+ }
748
+ requests.push(now);
749
+ this.requests.set(key, requests);
750
+ return true;
751
+ }
752
+ /**
753
+ * Get remaining requests in window
754
+ */
755
+ getRemainingRequests(key) {
756
+ const now = Date.now();
757
+ const windowStart = now - this.windowMs;
758
+ const requests = (this.requests.get(key) ?? []).filter((t) => t > windowStart);
759
+ return Math.max(0, this.maxRequests - requests.length);
760
+ }
761
+ /**
762
+ * Reset limits for a key
763
+ */
764
+ reset(key) {
765
+ this.requests.delete(key);
766
+ }
767
+ /**
768
+ * Clear all limits
769
+ */
770
+ clear() {
771
+ this.requests.clear();
772
+ }
773
+ };
774
+ function calculateOptimalRate(channelCapacity, desiredDurationSeconds, bufferPercent = 10) {
775
+ const capacity = BigInt(channelCapacity);
776
+ const duration = BigInt(desiredDurationSeconds);
777
+ if (duration === 0n) {
778
+ return "0";
779
+ }
780
+ const effectiveCapacity = capacity * BigInt(100 - bufferPercent) / 100n;
781
+ return (effectiveCapacity / duration).toString();
782
+ }
783
+ function convertRate(rate, fromUnit, toUnit) {
784
+ const unitToSeconds = {
785
+ second: 1,
786
+ minute: 60,
787
+ hour: 3600,
788
+ day: 86400
789
+ };
790
+ const fromSeconds = unitToSeconds[fromUnit];
791
+ const toSeconds = unitToSeconds[toUnit];
792
+ const rateBigInt = BigInt(rate);
793
+ const perSecond = rateBigInt / BigInt(fromSeconds);
794
+ return (perSecond * BigInt(toSeconds)).toString();
795
+ }
796
+
797
+ // src/streaming/metering.ts
798
+ var MeteringManager = class {
799
+ records = [];
800
+ config;
801
+ sessionId;
802
+ startTime;
803
+ constructor(sessionId, config = {}) {
804
+ this.sessionId = sessionId;
805
+ this.startTime = Date.now();
806
+ this.config = {
807
+ recordInterval: config.recordInterval ?? 1,
808
+ aggregationInterval: config.aggregationInterval ?? 3600,
809
+ maxRecords: config.maxRecords ?? 1e4,
810
+ precision: config.precision ?? 18
811
+ };
812
+ }
813
+ /**
814
+ * Record usage
815
+ */
816
+ record(duration, amount, rate, metadata) {
817
+ const cumulative = this.calculateCumulative(amount);
818
+ const record = {
819
+ timestamp: Date.now(),
820
+ duration,
821
+ amount,
822
+ rate,
823
+ cumulative,
824
+ metadata
825
+ };
826
+ this.records.push(record);
827
+ if (this.records.length > this.config.maxRecords) {
828
+ this.pruneRecords();
829
+ }
830
+ return record;
831
+ }
832
+ /**
833
+ * Get all records
834
+ */
835
+ getRecords() {
836
+ return [...this.records];
837
+ }
838
+ /**
839
+ * Get records in time range
840
+ */
841
+ getRecordsInRange(startTime, endTime) {
842
+ return this.records.filter(
843
+ (r) => r.timestamp >= startTime && r.timestamp <= endTime
844
+ );
845
+ }
846
+ /**
847
+ * Calculate usage metrics
848
+ */
849
+ getMetrics() {
850
+ if (this.records.length === 0) {
851
+ return {
852
+ totalSeconds: 0,
853
+ totalAmount: "0",
854
+ averageRate: "0",
855
+ peakRate: "0",
856
+ startTime: this.startTime
857
+ };
858
+ }
859
+ const totalSeconds = this.records.reduce((sum, r) => sum + r.duration, 0);
860
+ const totalAmount = this.records[this.records.length - 1].cumulative;
861
+ const rates = this.records.map((r) => BigInt(r.rate));
862
+ const peakRate = rates.length > 0 ? rates.reduce((max, r) => r > max ? r : max, 0n) : 0n;
863
+ const averageRate = totalSeconds > 0 ? (BigInt(totalAmount) / BigInt(totalSeconds)).toString() : "0";
864
+ return {
865
+ totalSeconds,
866
+ totalAmount,
867
+ averageRate,
868
+ peakRate: peakRate.toString(),
869
+ startTime: this.startTime,
870
+ endTime: this.records[this.records.length - 1].timestamp,
871
+ hourly: this.getHourlyBreakdown()
872
+ };
873
+ }
874
+ /**
875
+ * Get aggregated usage by period
876
+ */
877
+ aggregate(intervalSeconds = 3600) {
878
+ if (this.records.length === 0) return [];
879
+ const aggregated = [];
880
+ const intervalMs = intervalSeconds * 1e3;
881
+ const groups = /* @__PURE__ */ new Map();
882
+ for (const record of this.records) {
883
+ const periodStart = Math.floor(record.timestamp / intervalMs) * intervalMs;
884
+ const existing = groups.get(periodStart) ?? [];
885
+ existing.push(record);
886
+ groups.set(periodStart, existing);
887
+ }
888
+ for (const [periodStart, records] of groups) {
889
+ const totalAmount = records.reduce(
890
+ (sum, r) => sum + BigInt(r.amount),
891
+ 0n
892
+ );
893
+ const totalDuration = records.reduce((sum, r) => sum + r.duration, 0);
894
+ const averageRate = totalDuration > 0 ? (totalAmount / BigInt(totalDuration)).toString() : "0";
895
+ aggregated.push({
896
+ period: new Date(periodStart).toISOString(),
897
+ totalAmount: totalAmount.toString(),
898
+ totalDuration,
899
+ averageRate,
900
+ recordCount: records.length
901
+ });
902
+ }
903
+ return aggregated.sort((a, b) => a.period.localeCompare(b.period));
904
+ }
905
+ /**
906
+ * Get cumulative amount at a point in time
907
+ */
908
+ getCumulativeAt(timestamp) {
909
+ for (let i = this.records.length - 1; i >= 0; i--) {
910
+ if (this.records[i].timestamp <= timestamp) {
911
+ return this.records[i].cumulative;
912
+ }
913
+ }
914
+ return "0";
915
+ }
916
+ /**
917
+ * Calculate usage for billing period
918
+ */
919
+ getUsageForBillingPeriod(startTime, endTime) {
920
+ const periodRecords = this.getRecordsInRange(startTime, endTime);
921
+ const amount = periodRecords.reduce(
922
+ (sum, r) => sum + BigInt(r.amount),
923
+ 0n
924
+ );
925
+ const duration = periodRecords.reduce((sum, r) => sum + r.duration, 0);
926
+ return {
927
+ amount: amount.toString(),
928
+ duration,
929
+ records: periodRecords.length
930
+ };
931
+ }
932
+ /**
933
+ * Export records for backup/audit
934
+ */
935
+ export() {
936
+ return JSON.stringify({
937
+ sessionId: this.sessionId,
938
+ startTime: this.startTime,
939
+ records: this.records,
940
+ exportedAt: Date.now()
941
+ });
942
+ }
943
+ /**
944
+ * Import records from backup
945
+ */
946
+ import(data) {
947
+ try {
948
+ const parsed = JSON.parse(data);
949
+ if (parsed.sessionId !== this.sessionId) {
950
+ return { success: false, recordsImported: 0 };
951
+ }
952
+ const importedRecords = parsed.records;
953
+ let importedCount = 0;
954
+ for (const record of importedRecords) {
955
+ const exists = this.records.some((r) => r.timestamp === record.timestamp);
956
+ if (!exists) {
957
+ this.records.push(record);
958
+ importedCount++;
959
+ }
960
+ }
961
+ this.records.sort((a, b) => a.timestamp - b.timestamp);
962
+ return { success: true, recordsImported: importedCount };
963
+ } catch {
964
+ return { success: false, recordsImported: 0 };
965
+ }
966
+ }
967
+ /**
968
+ * Clear all records
969
+ */
970
+ clear() {
971
+ this.records = [];
972
+ }
973
+ calculateCumulative(newAmount) {
974
+ if (this.records.length === 0) {
975
+ return newAmount;
976
+ }
977
+ const lastCumulative = BigInt(this.records[this.records.length - 1].cumulative);
978
+ return (lastCumulative + BigInt(newAmount)).toString();
979
+ }
980
+ pruneRecords() {
981
+ const keepCount = this.config.maxRecords;
982
+ if (this.records.length <= keepCount) return;
983
+ const first = this.records[0];
984
+ const recent = this.records.slice(-(keepCount - 1));
985
+ this.records = [first, ...recent];
986
+ }
987
+ getHourlyBreakdown() {
988
+ const hourlyMap = /* @__PURE__ */ new Map();
989
+ for (const record of this.records) {
990
+ const hour = new Date(record.timestamp).getUTCHours();
991
+ const existing = hourlyMap.get(hour) ?? { amount: 0n, seconds: 0 };
992
+ existing.amount += BigInt(record.amount);
993
+ existing.seconds += record.duration;
994
+ hourlyMap.set(hour, existing);
995
+ }
996
+ return Array.from(hourlyMap.entries()).map(([hour, data]) => ({
997
+ hour,
998
+ amount: data.amount.toString(),
999
+ seconds: data.seconds
1000
+ }));
1001
+ }
1002
+ };
1003
+ function calculateProRatedUsage(fullPeriodAmount, fullPeriodSeconds, actualSeconds) {
1004
+ if (fullPeriodSeconds === 0) return "0";
1005
+ const amount = BigInt(fullPeriodAmount);
1006
+ return (amount * BigInt(actualSeconds) / BigInt(fullPeriodSeconds)).toString();
1007
+ }
1008
+ function estimateUsage(metrics, futureSeconds) {
1009
+ if (metrics.totalSeconds === 0) return "0";
1010
+ const avgRate = BigInt(metrics.averageRate);
1011
+ return (avgRate * BigInt(futureSeconds)).toString();
1012
+ }
1013
+ function compareUsage(current, previous) {
1014
+ const currentAmount = BigInt(current.totalAmount);
1015
+ const previousAmount = BigInt(previous.totalAmount);
1016
+ const amountChange = currentAmount - previousAmount;
1017
+ const amountChangePercent = previousAmount > 0n ? Number(amountChange * 100n / previousAmount) : 0;
1018
+ const currentRate = BigInt(current.averageRate);
1019
+ const previousRate = BigInt(previous.averageRate);
1020
+ const rateChange = currentRate - previousRate;
1021
+ const rateChangePercent = previousRate > 0n ? Number(rateChange * 100n / previousRate) : 0;
1022
+ return {
1023
+ amountChange: amountChange.toString(),
1024
+ amountChangePercent,
1025
+ rateChange: rateChange.toString(),
1026
+ rateChangePercent
1027
+ };
1028
+ }
1029
+
1030
+ // src/streaming/billing.ts
1031
+ var BillingManager = class {
1032
+ config;
1033
+ billingConfig;
1034
+ invoices = [];
1035
+ channelId;
1036
+ payer;
1037
+ payee;
1038
+ lastInvoiceTime;
1039
+ constructor(channelId, payer, payee, billingConfig, config = {}) {
1040
+ this.channelId = channelId;
1041
+ this.payer = payer;
1042
+ this.payee = payee;
1043
+ this.billingConfig = billingConfig;
1044
+ this.lastInvoiceTime = Date.now();
1045
+ this.config = {
1046
+ autoInvoice: config.autoInvoice ?? false,
1047
+ invoiceInterval: config.invoiceInterval ?? 86400,
1048
+ currency: config.currency ?? "USDT",
1049
+ taxRate: config.taxRate ?? 0
1050
+ };
1051
+ }
1052
+ /**
1053
+ * Generate invoice from metering records
1054
+ */
1055
+ generateInvoice(records, startTime, endTime) {
1056
+ const items = this.createInvoiceItems(records, startTime, endTime);
1057
+ const subtotal = this.calculateSubtotal(items);
1058
+ const fees = this.calculateFees(subtotal);
1059
+ const total = (BigInt(subtotal) + BigInt(fees)).toString();
1060
+ const invoice = {
1061
+ id: this.generateInvoiceId(),
1062
+ channelId: this.channelId,
1063
+ payer: this.payer,
1064
+ payee: this.payee,
1065
+ items,
1066
+ subtotal,
1067
+ fees,
1068
+ total,
1069
+ currency: this.config.currency,
1070
+ status: "pending",
1071
+ createdAt: Date.now(),
1072
+ dueAt: Date.now() + 864e5
1073
+ // Due in 24 hours
1074
+ };
1075
+ this.invoices.push(invoice);
1076
+ this.lastInvoiceTime = endTime;
1077
+ return invoice;
1078
+ }
1079
+ /**
1080
+ * Get all invoices
1081
+ */
1082
+ getInvoices() {
1083
+ return [...this.invoices];
1084
+ }
1085
+ /**
1086
+ * Get invoice by ID
1087
+ */
1088
+ getInvoice(id) {
1089
+ return this.invoices.find((inv) => inv.id === id);
1090
+ }
1091
+ /**
1092
+ * Mark invoice as paid
1093
+ */
1094
+ markPaid(invoiceId) {
1095
+ const invoice = this.invoices.find((inv) => inv.id === invoiceId);
1096
+ if (!invoice || invoice.status !== "pending") {
1097
+ return false;
1098
+ }
1099
+ invoice.status = "paid";
1100
+ invoice.paidAt = Date.now();
1101
+ return true;
1102
+ }
1103
+ /**
1104
+ * Mark invoice as settled
1105
+ */
1106
+ markSettled(invoiceId) {
1107
+ const invoice = this.invoices.find((inv) => inv.id === invoiceId);
1108
+ if (!invoice) return false;
1109
+ invoice.status = "settled";
1110
+ return true;
1111
+ }
1112
+ /**
1113
+ * Get pending amount
1114
+ */
1115
+ getPendingAmount() {
1116
+ return this.invoices.filter((inv) => inv.status === "pending").reduce((sum, inv) => sum + BigInt(inv.total), 0n).toString();
1117
+ }
1118
+ /**
1119
+ * Get total billed amount
1120
+ */
1121
+ getTotalBilled() {
1122
+ return this.invoices.reduce((sum, inv) => sum + BigInt(inv.total), 0n).toString();
1123
+ }
1124
+ /**
1125
+ * Check if new invoice is due
1126
+ */
1127
+ isInvoiceDue(currentTime) {
1128
+ const elapsed = currentTime - this.lastInvoiceTime;
1129
+ return elapsed >= this.config.invoiceInterval * 1e3;
1130
+ }
1131
+ /**
1132
+ * Calculate amount for billing period
1133
+ */
1134
+ calculatePeriodAmount(rate, durationSeconds) {
1135
+ const periodSeconds = this.getPeriodSeconds(this.billingConfig.period);
1136
+ const periods = durationSeconds / periodSeconds;
1137
+ const amount = BigInt(rate) * BigInt(Math.floor(periods * periodSeconds));
1138
+ return this.applyRounding(amount.toString());
1139
+ }
1140
+ /**
1141
+ * Apply minimum charge
1142
+ */
1143
+ applyMinimumCharge(amount) {
1144
+ const minCharge = BigInt(this.billingConfig.minimumCharge);
1145
+ const actualAmount = BigInt(amount);
1146
+ return (actualAmount < minCharge ? minCharge : actualAmount).toString();
1147
+ }
1148
+ /**
1149
+ * Calculate grace period savings
1150
+ */
1151
+ calculateGracePeriodSavings(rate, totalDuration) {
1152
+ const gracePeriod = this.billingConfig.gracePeriod;
1153
+ if (gracePeriod <= 0 || totalDuration <= gracePeriod) {
1154
+ return "0";
1155
+ }
1156
+ return (BigInt(rate) * BigInt(Math.min(gracePeriod, totalDuration))).toString();
1157
+ }
1158
+ /**
1159
+ * Get billing summary
1160
+ */
1161
+ getSummary() {
1162
+ const totalBilled = this.getTotalBilled();
1163
+ const totalPaid = this.invoices.filter((inv) => inv.status === "paid" || inv.status === "settled").reduce((sum, inv) => sum + BigInt(inv.total), 0n).toString();
1164
+ const totalPending = this.getPendingAmount();
1165
+ const averageInvoice = this.invoices.length > 0 ? (BigInt(totalBilled) / BigInt(this.invoices.length)).toString() : "0";
1166
+ return {
1167
+ totalInvoices: this.invoices.length,
1168
+ totalBilled,
1169
+ totalPaid,
1170
+ totalPending,
1171
+ averageInvoice
1172
+ };
1173
+ }
1174
+ /**
1175
+ * Export billing data
1176
+ */
1177
+ export() {
1178
+ return JSON.stringify({
1179
+ channelId: this.channelId,
1180
+ invoices: this.invoices,
1181
+ exportedAt: Date.now()
1182
+ });
1183
+ }
1184
+ createInvoiceItems(records, startTime, endTime) {
1185
+ if (records.length === 0) {
1186
+ return [];
1187
+ }
1188
+ const rateGroups = /* @__PURE__ */ new Map();
1189
+ for (const record of records) {
1190
+ const existing = rateGroups.get(record.rate) ?? [];
1191
+ existing.push(record);
1192
+ rateGroups.set(record.rate, existing);
1193
+ }
1194
+ const items = [];
1195
+ for (const [rate, groupRecords] of rateGroups) {
1196
+ const totalDuration = groupRecords.reduce((sum, r) => sum + r.duration, 0);
1197
+ const totalAmount = groupRecords.reduce(
1198
+ (sum, r) => sum + BigInt(r.amount),
1199
+ 0n
1200
+ );
1201
+ const periodName = this.getPeriodName(this.billingConfig.period);
1202
+ const quantity = this.calculateQuantity(totalDuration);
1203
+ items.push({
1204
+ description: `Streaming usage at ${rate} per ${periodName}`,
1205
+ quantity,
1206
+ rate,
1207
+ amount: totalAmount.toString(),
1208
+ startTime,
1209
+ endTime
1210
+ });
1211
+ }
1212
+ return items;
1213
+ }
1214
+ calculateSubtotal(items) {
1215
+ return items.reduce((sum, item) => sum + BigInt(item.amount), 0n).toString();
1216
+ }
1217
+ calculateFees(subtotal) {
1218
+ const amount = BigInt(subtotal);
1219
+ const taxRate = this.config.taxRate;
1220
+ if (taxRate <= 0) return "0";
1221
+ return BigInt(Math.floor(Number(amount) * taxRate)).toString();
1222
+ }
1223
+ applyRounding(amount) {
1224
+ const value = BigInt(amount);
1225
+ const mode = this.billingConfig.roundingMode;
1226
+ switch (mode) {
1227
+ case "floor":
1228
+ return value.toString();
1229
+ case "ceil":
1230
+ return value.toString();
1231
+ case "round":
1232
+ return value.toString();
1233
+ default:
1234
+ return value.toString();
1235
+ }
1236
+ }
1237
+ getPeriodSeconds(period) {
1238
+ switch (period) {
1239
+ case "realtime":
1240
+ case "second":
1241
+ return 1;
1242
+ case "minute":
1243
+ return 60;
1244
+ case "hour":
1245
+ return 3600;
1246
+ case "day":
1247
+ return 86400;
1248
+ }
1249
+ }
1250
+ getPeriodName(period) {
1251
+ switch (period) {
1252
+ case "realtime":
1253
+ case "second":
1254
+ return "second";
1255
+ case "minute":
1256
+ return "minute";
1257
+ case "hour":
1258
+ return "hour";
1259
+ case "day":
1260
+ return "day";
1261
+ }
1262
+ }
1263
+ calculateQuantity(totalSeconds) {
1264
+ const periodSeconds = this.getPeriodSeconds(this.billingConfig.period);
1265
+ return totalSeconds / periodSeconds;
1266
+ }
1267
+ generateInvoiceId() {
1268
+ return `inv_${this.channelId.slice(-8)}_${Date.now().toString(36)}`;
1269
+ }
1270
+ };
1271
+ function formatCurrencyAmount(amount, decimals = 6, symbol = "USDT") {
1272
+ const value = BigInt(amount);
1273
+ const divisor = BigInt(10 ** decimals);
1274
+ const wholePart = value / divisor;
1275
+ const fractionalPart = value % divisor;
1276
+ const fractionalStr = fractionalPart.toString().padStart(decimals, "0");
1277
+ const trimmedFractional = fractionalStr.replace(/0+$/, "") || "0";
1278
+ return `${wholePart}.${trimmedFractional} ${symbol}`;
1279
+ }
1280
+ function parseCurrencyAmount(display, decimals = 6) {
1281
+ const cleaned = display.replace(/[^\d.]/g, "");
1282
+ const [whole, fractional = ""] = cleaned.split(".");
1283
+ const paddedFractional = fractional.slice(0, decimals).padEnd(decimals, "0");
1284
+ const combined = whole + paddedFractional;
1285
+ return BigInt(combined).toString();
1286
+ }
1287
+ function estimateFutureBill(currentRate, durationSeconds, minimumCharge = "0") {
1288
+ const estimated = BigInt(currentRate) * BigInt(durationSeconds);
1289
+ const minimum = BigInt(minimumCharge);
1290
+ return (estimated < minimum ? minimum : estimated).toString();
1291
+ }
1292
+ export {
1293
+ BillingConfig,
1294
+ BillingManager,
1295
+ BillingPeriod,
1296
+ FlowController,
1297
+ Invoice,
1298
+ InvoiceItem,
1299
+ MeteringManager,
1300
+ MeteringRecord,
1301
+ RateAdjustmentRequest,
1302
+ RateController,
1303
+ RateLimiter,
1304
+ RateType,
1305
+ StreamEvent,
1306
+ StreamRate,
1307
+ StreamSession,
1308
+ StreamState,
1309
+ UsageMetrics,
1310
+ calculateOptimalRate,
1311
+ calculateProRatedUsage,
1312
+ compareUsage,
1313
+ convertRate,
1314
+ createFixedRateFlow,
1315
+ createTieredRateFlow,
1316
+ estimateFutureBill,
1317
+ estimateUsage,
1318
+ formatCurrencyAmount,
1319
+ parseCurrencyAmount
1320
+ };
1321
+ //# sourceMappingURL=index.js.map