ugly-app 0.1.331 → 0.1.332

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.
Files changed (133) hide show
  1. package/coverage/{server/RateLimit.ts.html → NatsProxyClient.ts.html} +406 -445
  2. package/coverage/clover.xml +140 -3813
  3. package/coverage/coverage-final.json +1 -68
  4. package/coverage/index.html +14 -224
  5. package/dist/cli/version.d.ts +1 -1
  6. package/dist/cli/version.js +1 -1
  7. package/dist/markdown/shared/Comments.d.ts +16 -0
  8. package/dist/markdown/shared/Comments.d.ts.map +1 -0
  9. package/dist/markdown/shared/Comments.js +122 -0
  10. package/dist/markdown/shared/Comments.js.map +1 -0
  11. package/dist/markdown/shared/KatexLazy.d.ts +41 -0
  12. package/dist/markdown/shared/KatexLazy.d.ts.map +1 -0
  13. package/dist/markdown/shared/KatexLazy.js +104 -0
  14. package/dist/markdown/shared/KatexLazy.js.map +1 -0
  15. package/dist/markdown/shared/MdastParser.d.ts +59 -0
  16. package/dist/markdown/shared/MdastParser.d.ts.map +1 -0
  17. package/dist/markdown/shared/MdastParser.js +695 -0
  18. package/dist/markdown/shared/MdastParser.js.map +1 -0
  19. package/dist/markdown/shared/MdastTypes.d.ts +202 -0
  20. package/dist/markdown/shared/MdastTypes.d.ts.map +1 -0
  21. package/dist/markdown/shared/MdastTypes.js +9 -0
  22. package/dist/markdown/shared/MdastTypes.js.map +1 -0
  23. package/dist/markdown/shared/index.d.ts +6 -0
  24. package/dist/markdown/shared/index.d.ts.map +1 -0
  25. package/dist/markdown/shared/index.js +4 -0
  26. package/dist/markdown/shared/index.js.map +1 -0
  27. package/dist/server/DataProxyClient.d.ts.map +1 -1
  28. package/dist/server/DataProxyClient.js +12 -6
  29. package/dist/server/DataProxyClient.js.map +1 -1
  30. package/dist/server/Strings.js.map +1 -1
  31. package/dist/shared/Strings.d.ts +1 -1
  32. package/dist/shared/Strings.d.ts.map +1 -1
  33. package/package.json +13 -1
  34. package/src/cli/version.ts +1 -1
  35. package/src/markdown/shared/Comments.ts +156 -0
  36. package/src/markdown/shared/KatexLazy.ts +133 -0
  37. package/src/markdown/shared/MdastParser.ts +838 -0
  38. package/src/markdown/shared/MdastTypes.ts +288 -0
  39. package/src/markdown/shared/index.ts +76 -0
  40. package/src/server/DataProxyClient.ts +12 -6
  41. package/src/server/Strings.ts +1 -1
  42. package/src/shared/Strings.ts +1 -1
  43. package/templates/client/allPages.ts +1 -0
  44. package/templates/client/main.tsx +11 -0
  45. package/templates/client/pages/StringsTestPage.tsx +74 -0
  46. package/templates/client/pages/TestIndexPage.tsx +1 -0
  47. package/templates/server/index.ts +13 -0
  48. package/templates/shared/lang/en.ts +13 -0
  49. package/templates/shared/lang/es.ts +13 -0
  50. package/templates/shared/pages.ts +1 -0
  51. package/templates/shared/strings.ts +26 -0
  52. package/coverage/client/AppProvider.tsx.html +0 -820
  53. package/coverage/client/FeedbackContext.ts.html +0 -133
  54. package/coverage/client/LoginPopup.tsx.html +0 -337
  55. package/coverage/client/Router.tsx.html +0 -2230
  56. package/coverage/client/Screenshot.ts.html +0 -790
  57. package/coverage/client/ViewFlipper.tsx.html +0 -778
  58. package/coverage/client/animation/Animated.tsx.html +0 -544
  59. package/coverage/client/animation/animatedValue.ts.html +0 -721
  60. package/coverage/client/animation/index.html +0 -131
  61. package/coverage/client/components/Button.tsx.html +0 -379
  62. package/coverage/client/components/FeedbackButton.tsx.html +0 -631
  63. package/coverage/client/components/Input.tsx.html +0 -286
  64. package/coverage/client/components/Modal.tsx.html +0 -277
  65. package/coverage/client/components/Text.tsx.html +0 -349
  66. package/coverage/client/components/Toast.tsx.html +0 -235
  67. package/coverage/client/components/index.html +0 -206
  68. package/coverage/client/components/zIndex.ts.html +0 -118
  69. package/coverage/client/createSocket.ts.html +0 -2248
  70. package/coverage/client/index.html +0 -206
  71. package/coverage/collab/server/CollabServer.ts.html +0 -1681
  72. package/coverage/collab/server/index.html +0 -116
  73. package/coverage/server/Cache.ts.html +0 -568
  74. package/coverage/server/DB.ts.html +0 -1546
  75. package/coverage/server/DataProxyClient.ts.html +0 -1585
  76. package/coverage/server/Email.ts.html +0 -355
  77. package/coverage/server/EmailTemplate.ts.html +0 -223
  78. package/coverage/server/EventLogAdmin.ts.html +0 -1486
  79. package/coverage/server/PushNotification.ts.html +0 -400
  80. package/coverage/server/Router.ts.html +0 -520
  81. package/coverage/server/SchemaCheck.ts.html +0 -484
  82. package/coverage/server/Socket.ts.html +0 -1534
  83. package/coverage/server/ai/ImageGenClient.ts.html +0 -142
  84. package/coverage/server/ai/ProviderBalance.ts.html +0 -184
  85. package/coverage/server/ai/TextGenClient.ts.html +0 -244
  86. package/coverage/server/ai/WebSearchClient.ts.html +0 -190
  87. package/coverage/server/ai/index.html +0 -191
  88. package/coverage/server/ai/index.ts.html +0 -187
  89. package/coverage/server/ai/providers/UglyBotImageGenProvider.ts.html +0 -157
  90. package/coverage/server/ai/providers/UglyBotTextGenProvider.ts.html +0 -343
  91. package/coverage/server/ai/providers/UglyBotWebSearchProvider.ts.html +0 -271
  92. package/coverage/server/ai/providers/index.html +0 -146
  93. package/coverage/server/ai/types.ts.html +0 -520
  94. package/coverage/server/billing/BillingGateway.ts.html +0 -868
  95. package/coverage/server/billing/BillingLedger.ts.html +0 -910
  96. package/coverage/server/billing/CreditStore.ts.html +0 -577
  97. package/coverage/server/billing/LimitEnforcer.ts.html +0 -1237
  98. package/coverage/server/billing/UserLimitCache.ts.html +0 -388
  99. package/coverage/server/billing/index.html +0 -191
  100. package/coverage/server/billing/types.ts.html +0 -889
  101. package/coverage/server/embeddings/EmbeddingClient.ts.html +0 -217
  102. package/coverage/server/embeddings/index.html +0 -131
  103. package/coverage/server/embeddings/providers/OpenAI.ts.html +0 -136
  104. package/coverage/server/embeddings/providers/index.html +0 -116
  105. package/coverage/server/embeddings/registry.ts.html +0 -151
  106. package/coverage/server/index.html +0 -281
  107. package/coverage/server/uglyBotProxy.ts.html +0 -220
  108. package/coverage/shared/Api.ts.html +0 -640
  109. package/coverage/shared/Audio.ts.html +0 -1318
  110. package/coverage/shared/DB.ts.html +0 -1948
  111. package/coverage/shared/Errors.ts.html +0 -145
  112. package/coverage/shared/Experiment.ts.html +0 -298
  113. package/coverage/shared/FrameworkMessages.ts.html +0 -136
  114. package/coverage/shared/FrameworkRequests.ts.html +0 -538
  115. package/coverage/shared/ImageGen.ts.html +0 -1861
  116. package/coverage/shared/Router.ts.html +0 -538
  117. package/coverage/shared/SchemaDiff.ts.html +0 -634
  118. package/coverage/shared/SchemaSerializer.ts.html +0 -517
  119. package/coverage/shared/TextGen.ts.html +0 -3076
  120. package/coverage/shared/UglyBotMessages.ts.html +0 -211
  121. package/coverage/shared/Voice.ts.html +0 -331
  122. package/coverage/shared/index.html +0 -341
  123. package/coverage/shared/index.ts.html +0 -541
  124. package/coverage/shared/uglyBotUrl.ts.html +0 -151
  125. package/coverage/three/server/VideoEncoder.ts.html +0 -2248
  126. package/coverage/three/server/index.html +0 -116
  127. package/coverage/three/shared/CameraController.ts.html +0 -1519
  128. package/coverage/three/shared/PostProcessing.ts.html +0 -1771
  129. package/coverage/three/shared/index.html +0 -131
  130. package/coverage/webrtc/server/VideoRoomServer.ts.html +0 -1726
  131. package/coverage/webrtc/server/index.html +0 -116
  132. package/coverage/worker/TaskHost.ts.html +0 -865
  133. package/coverage/worker/index.html +0 -116
@@ -3,15 +3,15 @@
3
3
  <html lang="en">
4
4
 
5
5
  <head>
6
- <title>Code coverage report for server/RateLimit.ts</title>
6
+ <title>Code coverage report for NatsProxyClient.ts</title>
7
7
  <meta charset="utf-8" />
8
- <link rel="stylesheet" href="../prettify.css" />
9
- <link rel="stylesheet" href="../base.css" />
10
- <link rel="shortcut icon" type="image/x-icon" href="../favicon.png" />
8
+ <link rel="stylesheet" href="prettify.css" />
9
+ <link rel="stylesheet" href="base.css" />
10
+ <link rel="shortcut icon" type="image/x-icon" href="favicon.png" />
11
11
  <meta name="viewport" content="width=device-width, initial-scale=1" />
12
12
  <style type='text/css'>
13
13
  .coverage-summary .sorter {
14
- background-image: url(../sort-arrow-sprite.png);
14
+ background-image: url(sort-arrow-sprite.png);
15
15
  }
16
16
  </style>
17
17
  </head>
@@ -19,34 +19,34 @@
19
19
  <body>
20
20
  <div class='wrapper'>
21
21
  <div class='pad1'>
22
- <h1><a href="../index.html">All files</a> / <a href="index.html">server</a> RateLimit.ts</h1>
22
+ <h1><a href="index.html">All files</a> NatsProxyClient.ts</h1>
23
23
  <div class='clearfix'>
24
24
 
25
25
  <div class='fl pad1y space-right2'>
26
- <span class="strong">99.15% </span>
26
+ <span class="strong">100% </span>
27
27
  <span class="quiet">Statements</span>
28
- <span class='fraction'>117/118</span>
28
+ <span class='fraction'>140/140</span>
29
29
  </div>
30
30
 
31
31
 
32
32
  <div class='fl pad1y space-right2'>
33
- <span class="strong">95.34% </span>
33
+ <span class="strong">100% </span>
34
34
  <span class="quiet">Branches</span>
35
- <span class='fraction'>41/43</span>
35
+ <span class='fraction'>32/32</span>
36
36
  </div>
37
37
 
38
38
 
39
39
  <div class='fl pad1y space-right2'>
40
40
  <span class="strong">100% </span>
41
41
  <span class="quiet">Functions</span>
42
- <span class='fraction'>26/26</span>
42
+ <span class='fraction'>35/35</span>
43
43
  </div>
44
44
 
45
45
 
46
46
  <div class='fl pad1y space-right2'>
47
47
  <span class="strong">100% </span>
48
48
  <span class="quiet">Lines</span>
49
- <span class='fraction'>112/112</span>
49
+ <span class='fraction'>134/134</span>
50
50
  </div>
51
51
 
52
52
 
@@ -384,27 +384,7 @@
384
384
  <a name='L319'></a><a href='#L319'>319</a>
385
385
  <a name='L320'></a><a href='#L320'>320</a>
386
386
  <a name='L321'></a><a href='#L321'>321</a>
387
- <a name='L322'></a><a href='#L322'>322</a>
388
- <a name='L323'></a><a href='#L323'>323</a>
389
- <a name='L324'></a><a href='#L324'>324</a>
390
- <a name='L325'></a><a href='#L325'>325</a>
391
- <a name='L326'></a><a href='#L326'>326</a>
392
- <a name='L327'></a><a href='#L327'>327</a>
393
- <a name='L328'></a><a href='#L328'>328</a>
394
- <a name='L329'></a><a href='#L329'>329</a>
395
- <a name='L330'></a><a href='#L330'>330</a>
396
- <a name='L331'></a><a href='#L331'>331</a>
397
- <a name='L332'></a><a href='#L332'>332</a>
398
- <a name='L333'></a><a href='#L333'>333</a>
399
- <a name='L334'></a><a href='#L334'>334</a>
400
- <a name='L335'></a><a href='#L335'>335</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
401
- <span class="cline-any cline-neutral">&nbsp;</span>
402
- <span class="cline-any cline-neutral">&nbsp;</span>
403
- <span class="cline-any cline-neutral">&nbsp;</span>
404
- <span class="cline-any cline-neutral">&nbsp;</span>
405
- <span class="cline-any cline-neutral">&nbsp;</span>
406
- <span class="cline-any cline-neutral">&nbsp;</span>
407
- <span class="cline-any cline-neutral">&nbsp;</span>
387
+ <a name='L322'></a><a href='#L322'>322</a></td><td class="line-coverage quiet"><span class="cline-any cline-neutral">&nbsp;</span>
408
388
  <span class="cline-any cline-neutral">&nbsp;</span>
409
389
  <span class="cline-any cline-neutral">&nbsp;</span>
410
390
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -415,23 +395,50 @@
415
395
  <span class="cline-any cline-neutral">&nbsp;</span>
416
396
  <span class="cline-any cline-neutral">&nbsp;</span>
417
397
  <span class="cline-any cline-neutral">&nbsp;</span>
398
+ <span class="cline-any cline-yes">37x</span>
399
+ <span class="cline-any cline-yes">37x</span>
400
+ <span class="cline-any cline-yes">37x</span>
418
401
  <span class="cline-any cline-neutral">&nbsp;</span>
402
+ <span class="cline-any cline-yes">37x</span>
403
+ <span class="cline-any cline-yes">37x</span>
404
+ <span class="cline-any cline-yes">37x</span>
405
+ <span class="cline-any cline-yes">37x</span>
406
+ <span class="cline-any cline-yes">37x</span>
419
407
  <span class="cline-any cline-neutral">&nbsp;</span>
408
+ <span class="cline-any cline-yes">37x</span>
409
+ <span class="cline-any cline-yes">37x</span>
410
+ <span class="cline-any cline-yes">37x</span>
411
+ <span class="cline-any cline-yes">37x</span>
412
+ <span class="cline-any cline-yes">37x</span>
413
+ <span class="cline-any cline-yes">37x</span>
414
+ <span class="cline-any cline-yes">37x</span>
420
415
  <span class="cline-any cline-neutral">&nbsp;</span>
416
+ <span class="cline-any cline-yes">37x</span>
421
417
  <span class="cline-any cline-neutral">&nbsp;</span>
418
+ <span class="cline-any cline-yes">37x</span>
419
+ <span class="cline-any cline-yes">37x</span>
422
420
  <span class="cline-any cline-neutral">&nbsp;</span>
421
+ <span class="cline-any cline-yes">37x</span>
423
422
  <span class="cline-any cline-neutral">&nbsp;</span>
424
423
  <span class="cline-any cline-neutral">&nbsp;</span>
425
424
  <span class="cline-any cline-neutral">&nbsp;</span>
425
+ <span class="cline-any cline-yes">37x</span>
426
+ <span class="cline-any cline-yes">37x</span>
426
427
  <span class="cline-any cline-neutral">&nbsp;</span>
427
428
  <span class="cline-any cline-neutral">&nbsp;</span>
428
429
  <span class="cline-any cline-neutral">&nbsp;</span>
429
430
  <span class="cline-any cline-neutral">&nbsp;</span>
430
431
  <span class="cline-any cline-neutral">&nbsp;</span>
431
432
  <span class="cline-any cline-neutral">&nbsp;</span>
433
+ <span class="cline-any cline-yes">37x</span>
434
+ <span class="cline-any cline-yes">37x</span>
435
+ <span class="cline-any cline-yes">37x</span>
432
436
  <span class="cline-any cline-neutral">&nbsp;</span>
433
437
  <span class="cline-any cline-neutral">&nbsp;</span>
438
+ <span class="cline-any cline-yes">37x</span>
434
439
  <span class="cline-any cline-neutral">&nbsp;</span>
440
+ <span class="cline-any cline-yes">37x</span>
441
+ <span class="cline-any cline-yes">37x</span>
435
442
  <span class="cline-any cline-neutral">&nbsp;</span>
436
443
  <span class="cline-any cline-neutral">&nbsp;</span>
437
444
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -441,15 +448,23 @@
441
448
  <span class="cline-any cline-neutral">&nbsp;</span>
442
449
  <span class="cline-any cline-neutral">&nbsp;</span>
443
450
  <span class="cline-any cline-neutral">&nbsp;</span>
444
- <span class="cline-any cline-yes">17x</span>
445
- <span class="cline-any cline-yes">17x</span>
446
- <span class="cline-any cline-yes">17x</span>
447
- <span class="cline-any cline-yes">17x</span>
451
+ <span class="cline-any cline-yes">39x</span>
452
+ <span class="cline-any cline-yes">39x</span>
453
+ <span class="cline-any cline-yes">39x</span>
454
+ <span class="cline-any cline-yes">39x</span>
455
+ <span class="cline-any cline-yes">39x</span>
456
+ <span class="cline-any cline-yes">39x</span>
457
+ <span class="cline-any cline-yes">39x</span>
458
+ <span class="cline-any cline-yes">39x</span>
448
459
  <span class="cline-any cline-neutral">&nbsp;</span>
449
- <span class="cline-any cline-yes">17x</span>
450
- <span class="cline-any cline-yes">17x</span>
451
460
  <span class="cline-any cline-neutral">&nbsp;</span>
452
461
  <span class="cline-any cline-neutral">&nbsp;</span>
462
+ <span class="cline-any cline-yes">4x</span>
463
+ <span class="cline-any cline-yes">4x</span>
464
+ <span class="cline-any cline-yes">4x</span>
465
+ <span class="cline-any cline-yes">4x</span>
466
+ <span class="cline-any cline-yes">4x</span>
467
+ <span class="cline-any cline-yes">4x</span>
453
468
  <span class="cline-any cline-neutral">&nbsp;</span>
454
469
  <span class="cline-any cline-neutral">&nbsp;</span>
455
470
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -457,204 +472,176 @@
457
472
  <span class="cline-any cline-neutral">&nbsp;</span>
458
473
  <span class="cline-any cline-neutral">&nbsp;</span>
459
474
  <span class="cline-any cline-neutral">&nbsp;</span>
460
- <span class="cline-any cline-yes">6x</span>
461
- <span class="cline-any cline-yes">6x</span>
462
475
  <span class="cline-any cline-neutral">&nbsp;</span>
463
476
  <span class="cline-any cline-neutral">&nbsp;</span>
464
- <span class="cline-any cline-yes">16x</span>
477
+ <span class="cline-any cline-yes">31x</span>
465
478
  <span class="cline-any cline-neutral">&nbsp;</span>
466
479
  <span class="cline-any cline-neutral">&nbsp;</span>
467
480
  <span class="cline-any cline-neutral">&nbsp;</span>
468
- <span class="cline-any cline-yes">23x</span>
469
481
  <span class="cline-any cline-neutral">&nbsp;</span>
470
482
  <span class="cline-any cline-neutral">&nbsp;</span>
471
483
  <span class="cline-any cline-neutral">&nbsp;</span>
472
484
  <span class="cline-any cline-neutral">&nbsp;</span>
485
+ <span class="cline-any cline-yes">53x</span>
473
486
  <span class="cline-any cline-neutral">&nbsp;</span>
487
+ <span class="cline-any cline-yes">52x</span>
474
488
  <span class="cline-any cline-neutral">&nbsp;</span>
475
489
  <span class="cline-any cline-neutral">&nbsp;</span>
490
+ <span class="cline-any cline-yes">52x</span>
491
+ <span class="cline-any cline-yes">16x</span>
476
492
  <span class="cline-any cline-neutral">&nbsp;</span>
493
+ <span class="cline-any cline-yes">16x</span>
494
+ <span class="cline-any cline-yes">4x</span>
495
+ <span class="cline-any cline-yes">4x</span>
496
+ <span class="cline-any cline-yes">4x</span>
497
+ <span class="cline-any cline-yes">4x</span>
498
+ <span class="cline-any cline-yes">4x</span>
499
+ <span class="cline-any cline-yes">4x</span>
500
+ <span class="cline-any cline-yes">4x</span>
477
501
  <span class="cline-any cline-neutral">&nbsp;</span>
478
- <span class="cline-any cline-yes">54x</span>
479
502
  <span class="cline-any cline-neutral">&nbsp;</span>
503
+ <span class="cline-any cline-yes">12x</span>
504
+ <span class="cline-any cline-yes">11x</span>
505
+ <span class="cline-any cline-yes">11x</span>
506
+ <span class="cline-any cline-yes">11x</span>
507
+ <span class="cline-any cline-yes">11x</span>
508
+ <span class="cline-any cline-yes">11x</span>
509
+ <span class="cline-any cline-yes">11x</span>
510
+ <span class="cline-any cline-yes">11x</span>
480
511
  <span class="cline-any cline-neutral">&nbsp;</span>
481
512
  <span class="cline-any cline-neutral">&nbsp;</span>
513
+ <span class="cline-any cline-yes">1x</span>
482
514
  <span class="cline-any cline-neutral">&nbsp;</span>
483
515
  <span class="cline-any cline-neutral">&nbsp;</span>
484
516
  <span class="cline-any cline-neutral">&nbsp;</span>
517
+ <span class="cline-any cline-yes">36x</span>
518
+ <span class="cline-any cline-yes">36x</span>
519
+ <span class="cline-any cline-yes">36x</span>
520
+ <span class="cline-any cline-yes">36x</span>
521
+ <span class="cline-any cline-yes">35x</span>
522
+ <span class="cline-any cline-yes">35x</span>
523
+ <span class="cline-any cline-yes">29x</span>
485
524
  <span class="cline-any cline-neutral">&nbsp;</span>
486
- <span class="cline-any cline-yes">54x</span>
525
+ <span class="cline-any cline-yes">6x</span>
487
526
  <span class="cline-any cline-neutral">&nbsp;</span>
527
+ <span class="cline-any cline-yes">35x</span>
488
528
  <span class="cline-any cline-neutral">&nbsp;</span>
489
- <span class="cline-any cline-yes">47x</span>
490
- <span class="cline-any cline-yes">47x</span>
491
- <span class="cline-any cline-yes">34x</span>
492
- <span class="cline-any cline-yes">34x</span>
493
529
  <span class="cline-any cline-neutral">&nbsp;</span>
494
- <span class="cline-any cline-yes">47x</span>
530
+ <span class="cline-any cline-yes">1x</span>
495
531
  <span class="cline-any cline-neutral">&nbsp;</span>
496
532
  <span class="cline-any cline-neutral">&nbsp;</span>
497
533
  <span class="cline-any cline-neutral">&nbsp;</span>
534
+ <span class="cline-any cline-yes">39x</span>
535
+ <span class="cline-any cline-yes">38x</span>
498
536
  <span class="cline-any cline-neutral">&nbsp;</span>
499
537
  <span class="cline-any cline-neutral">&nbsp;</span>
500
538
  <span class="cline-any cline-neutral">&nbsp;</span>
501
539
  <span class="cline-any cline-neutral">&nbsp;</span>
540
+ <span class="cline-any cline-yes">26x</span>
541
+ <span class="cline-any cline-yes">26x</span>
502
542
  <span class="cline-any cline-neutral">&nbsp;</span>
543
+ <span class="cline-any cline-yes">26x</span>
544
+ <span class="cline-any cline-yes">26x</span>
545
+ <span class="cline-any cline-yes">26x</span>
503
546
  <span class="cline-any cline-neutral">&nbsp;</span>
504
547
  <span class="cline-any cline-neutral">&nbsp;</span>
505
- <span class="cline-any cline-yes">54x</span>
506
548
  <span class="cline-any cline-neutral">&nbsp;</span>
507
549
  <span class="cline-any cline-neutral">&nbsp;</span>
508
- <span class="cline-any cline-yes">42x</span>
509
550
  <span class="cline-any cline-neutral">&nbsp;</span>
510
551
  <span class="cline-any cline-neutral">&nbsp;</span>
511
552
  <span class="cline-any cline-neutral">&nbsp;</span>
512
- <span class="cline-any cline-yes">42x</span>
513
- <span class="cline-any cline-yes">42x</span>
514
- <span class="cline-any cline-yes">22x</span>
515
- <span class="cline-any cline-yes">22x</span>
516
553
  <span class="cline-any cline-neutral">&nbsp;</span>
517
554
  <span class="cline-any cline-neutral">&nbsp;</span>
518
- <span class="cline-any cline-yes">42x</span>
519
- <span class="cline-any cline-yes">42x</span>
555
+ <span class="cline-any cline-yes">6x</span>
520
556
  <span class="cline-any cline-yes">4x</span>
521
557
  <span class="cline-any cline-yes">4x</span>
522
558
  <span class="cline-any cline-neutral">&nbsp;</span>
523
- <span class="cline-any cline-yes">42x</span>
524
- <span class="cline-any cline-neutral">&nbsp;</span>
525
- <span class="cline-any cline-neutral">&nbsp;</span>
526
- <span class="cline-any cline-neutral">&nbsp;</span>
527
- <span class="cline-any cline-yes">17x</span>
528
- <span class="cline-any cline-yes">5x</span>
529
- <span class="cline-any cline-yes">5x</span>
530
- <span class="cline-any cline-yes">4x</span>
531
559
  <span class="cline-any cline-yes">4x</span>
532
560
  <span class="cline-any cline-neutral">&nbsp;</span>
533
- <span class="cline-any cline-yes">1x</span>
534
- <span class="cline-any cline-neutral">&nbsp;</span>
535
561
  <span class="cline-any cline-neutral">&nbsp;</span>
536
562
  <span class="cline-any cline-neutral">&nbsp;</span>
537
563
  <span class="cline-any cline-neutral">&nbsp;</span>
538
564
  <span class="cline-any cline-neutral">&nbsp;</span>
539
565
  <span class="cline-any cline-neutral">&nbsp;</span>
540
566
  <span class="cline-any cline-neutral">&nbsp;</span>
541
- <span class="cline-any cline-yes">32x</span>
542
567
  <span class="cline-any cline-neutral">&nbsp;</span>
543
568
  <span class="cline-any cline-neutral">&nbsp;</span>
544
569
  <span class="cline-any cline-neutral">&nbsp;</span>
545
570
  <span class="cline-any cline-neutral">&nbsp;</span>
546
571
  <span class="cline-any cline-neutral">&nbsp;</span>
547
- <span class="cline-any cline-yes">37x</span>
548
- <span class="cline-any cline-yes">37x</span>
549
- <span class="cline-any cline-yes">14x</span>
550
- <span class="cline-any cline-neutral">&nbsp;</span>
551
- <span class="cline-any cline-yes">23x</span>
572
+ <span class="cline-any cline-yes">3x</span>
573
+ <span class="cline-any cline-yes">3x</span>
574
+ <span class="cline-any cline-yes">3x</span>
575
+ <span class="cline-any cline-yes">3x</span>
552
576
  <span class="cline-any cline-neutral">&nbsp;</span>
553
- <span class="cline-any cline-yes">23x</span>
577
+ <span class="cline-any cline-yes">3x</span>
578
+ <span class="cline-any cline-yes">2x</span>
579
+ <span class="cline-any cline-yes">2x</span>
580
+ <span class="cline-any cline-yes">2x</span>
581
+ <span class="cline-any cline-yes">2x</span>
582
+ <span class="cline-any cline-yes">2x</span>
583
+ <span class="cline-any cline-yes">2x</span>
554
584
  <span class="cline-any cline-yes">2x</span>
555
- <span class="cline-any cline-neutral">&nbsp;</span>
556
- <span class="cline-any cline-yes">21x</span>
557
- <span class="cline-any cline-neutral">&nbsp;</span>
558
- <span class="cline-any cline-neutral">&nbsp;</span>
559
585
  <span class="cline-any cline-neutral">&nbsp;</span>
560
586
  <span class="cline-any cline-neutral">&nbsp;</span>
561
587
  <span class="cline-any cline-neutral">&nbsp;</span>
562
588
  <span class="cline-any cline-neutral">&nbsp;</span>
563
589
  <span class="cline-any cline-neutral">&nbsp;</span>
564
- <span class="cline-any cline-yes">16x</span>
565
- <span class="cline-any cline-yes">16x</span>
566
- <span class="cline-any cline-yes">9x</span>
567
590
  <span class="cline-any cline-neutral">&nbsp;</span>
568
- <span class="cline-any cline-yes">7x</span>
569
591
  <span class="cline-any cline-neutral">&nbsp;</span>
570
592
  <span class="cline-any cline-neutral">&nbsp;</span>
571
593
  <span class="cline-any cline-neutral">&nbsp;</span>
594
+ <span class="cline-any cline-yes">5x</span>
572
595
  <span class="cline-any cline-neutral">&nbsp;</span>
573
596
  <span class="cline-any cline-neutral">&nbsp;</span>
574
597
  <span class="cline-any cline-neutral">&nbsp;</span>
575
598
  <span class="cline-any cline-neutral">&nbsp;</span>
576
599
  <span class="cline-any cline-neutral">&nbsp;</span>
577
600
  <span class="cline-any cline-neutral">&nbsp;</span>
578
- <span class="cline-any cline-yes">12x</span>
579
- <span class="cline-any cline-yes">12x</span>
580
- <span class="cline-any cline-yes">2x</span>
581
601
  <span class="cline-any cline-yes">2x</span>
582
602
  <span class="cline-any cline-yes">2x</span>
583
- <span class="cline-any cline-yes">1x</span>
584
- <span class="cline-any cline-yes">1x</span>
585
- <span class="cline-any cline-neutral">&nbsp;</span>
586
- <span class="cline-any cline-yes">1x</span>
587
603
  <span class="cline-any cline-neutral">&nbsp;</span>
588
604
  <span class="cline-any cline-neutral">&nbsp;</span>
589
605
  <span class="cline-any cline-neutral">&nbsp;</span>
590
606
  <span class="cline-any cline-neutral">&nbsp;</span>
591
607
  <span class="cline-any cline-neutral">&nbsp;</span>
592
608
  <span class="cline-any cline-neutral">&nbsp;</span>
593
- <span class="cline-any cline-yes">54x</span>
594
- <span class="cline-any cline-neutral">&nbsp;</span>
595
- <span class="cline-any cline-yes">16x</span>
596
609
  <span class="cline-any cline-neutral">&nbsp;</span>
597
- <span class="cline-any cline-yes">15x</span>
598
- <span class="cline-any cline-yes">15x</span>
610
+ <span class="cline-any cline-yes">1x</span>
599
611
  <span class="cline-any cline-neutral">&nbsp;</span>
600
- <span class="cline-any cline-yes">15x</span>
601
- <span class="cline-any cline-yes">4x</span>
602
612
  <span class="cline-any cline-neutral">&nbsp;</span>
603
613
  <span class="cline-any cline-neutral">&nbsp;</span>
604
614
  <span class="cline-any cline-neutral">&nbsp;</span>
605
- <span class="cline-any cline-yes">11x</span>
606
- <span class="cline-any cline-yes">2x</span>
607
615
  <span class="cline-any cline-neutral">&nbsp;</span>
608
616
  <span class="cline-any cline-neutral">&nbsp;</span>
617
+ <span class="cline-any cline-yes">1x</span>
609
618
  <span class="cline-any cline-neutral">&nbsp;</span>
610
- <span class="cline-any cline-yes">9x</span>
611
- <span class="cline-any cline-yes">9x</span>
612
- <span class="cline-any cline-yes">3x</span>
613
619
  <span class="cline-any cline-neutral">&nbsp;</span>
614
620
  <span class="cline-any cline-neutral">&nbsp;</span>
615
- <span class="cline-any cline-yes">6x</span>
616
- <span class="cline-any cline-yes">6x</span>
617
- <span class="cline-any cline-yes">3x</span>
618
- <span class="cline-any cline-yes">3x</span>
619
- <span class="cline-any cline-yes">3x</span>
620
- <span class="cline-any cline-neutral">&nbsp;</span>
621
- <span class="cline-any cline-neutral">&nbsp;</span>
622
- <span class="cline-any cline-yes">6x</span>
623
- <span class="cline-any cline-yes">1x</span>
621
+ <span class="cline-any cline-yes">2x</span>
622
+ <span class="cline-any cline-yes">2x</span>
624
623
  <span class="cline-any cline-yes">1x</span>
625
624
  <span class="cline-any cline-neutral">&nbsp;</span>
626
625
  <span class="cline-any cline-neutral">&nbsp;</span>
627
- <span class="cline-any cline-yes">6x</span>
628
626
  <span class="cline-any cline-neutral">&nbsp;</span>
629
627
  <span class="cline-any cline-neutral">&nbsp;</span>
630
628
  <span class="cline-any cline-neutral">&nbsp;</span>
629
+ <span class="cline-any cline-yes">1x</span>
631
630
  <span class="cline-any cline-neutral">&nbsp;</span>
632
- <span class="cline-any cline-yes">13x</span>
633
- <span class="cline-any cline-yes">13x</span>
634
631
  <span class="cline-any cline-neutral">&nbsp;</span>
635
- <span class="cline-any cline-yes">13x</span>
636
- <span class="cline-any cline-yes">16x</span>
637
- <span class="cline-any cline-yes">16x</span>
638
632
  <span class="cline-any cline-neutral">&nbsp;</span>
633
+ <span class="cline-any cline-yes">2x</span>
639
634
  <span class="cline-any cline-neutral">&nbsp;</span>
640
635
  <span class="cline-any cline-neutral">&nbsp;</span>
641
- <span class="cline-any cline-yes">16x</span>
642
- <span class="cline-any cline-yes">16x</span>
643
636
  <span class="cline-any cline-neutral">&nbsp;</span>
644
- <span class="cline-any cline-yes">12x</span>
645
- <span class="cline-any cline-yes">12x</span>
646
637
  <span class="cline-any cline-neutral">&nbsp;</span>
647
638
  <span class="cline-any cline-neutral">&nbsp;</span>
648
- <span class="cline-any cline-yes">4x</span>
649
639
  <span class="cline-any cline-neutral">&nbsp;</span>
640
+ <span class="cline-any cline-yes">1x</span>
650
641
  <span class="cline-any cline-neutral">&nbsp;</span>
651
642
  <span class="cline-any cline-neutral">&nbsp;</span>
652
643
  <span class="cline-any cline-neutral">&nbsp;</span>
653
644
  <span class="cline-any cline-neutral">&nbsp;</span>
654
- <span class="cline-any cline-yes">4x</span>
655
- <span class="cline-any cline-yes">4x</span>
656
- <span class="cline-any cline-yes">4x</span>
657
- <span class="cline-any cline-yes">4x</span>
658
645
  <span class="cline-any cline-neutral">&nbsp;</span>
659
646
  <span class="cline-any cline-neutral">&nbsp;</span>
660
647
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -666,30 +653,38 @@
666
653
  <span class="cline-any cline-neutral">&nbsp;</span>
667
654
  <span class="cline-any cline-neutral">&nbsp;</span>
668
655
  <span class="cline-any cline-neutral">&nbsp;</span>
656
+ <span class="cline-any cline-yes">7x</span>
669
657
  <span class="cline-any cline-neutral">&nbsp;</span>
670
- <span class="cline-any cline-yes">38x</span>
671
658
  <span class="cline-any cline-neutral">&nbsp;</span>
672
- <span class="cline-any cline-yes">22x</span>
673
- <span class="cline-any cline-yes">22x</span>
674
659
  <span class="cline-any cline-neutral">&nbsp;</span>
675
- <span class="cline-any cline-yes">22x</span>
676
660
  <span class="cline-any cline-neutral">&nbsp;</span>
677
661
  <span class="cline-any cline-yes">7x</span>
678
662
  <span class="cline-any cline-neutral">&nbsp;</span>
663
+ <span class="cline-any cline-yes">7x</span>
664
+ <span class="cline-any cline-yes">9x</span>
679
665
  <span class="cline-any cline-neutral">&nbsp;</span>
680
666
  <span class="cline-any cline-neutral">&nbsp;</span>
681
667
  <span class="cline-any cline-neutral">&nbsp;</span>
682
- <span class="cline-any cline-yes">15x</span>
683
- <span class="cline-any cline-yes">4x</span>
684
- <span class="cline-any cline-neutral">&nbsp;</span>
668
+ <span class="cline-any cline-yes">2x</span>
669
+ <span class="cline-any cline-yes">2x</span>
670
+ <span class="cline-any cline-yes">2x</span>
671
+ <span class="cline-any cline-yes">2x</span>
672
+ <span class="cline-any cline-yes">2x</span>
673
+ <span class="cline-any cline-yes">2x</span>
685
674
  <span class="cline-any cline-neutral">&nbsp;</span>
686
675
  <span class="cline-any cline-neutral">&nbsp;</span>
687
- <span class="cline-any cline-yes">11x</span>
676
+ <span class="cline-any cline-yes">3x</span>
677
+ <span class="cline-any cline-yes">3x</span>
678
+ <span class="cline-any cline-yes">3x</span>
679
+ <span class="cline-any cline-yes">3x</span>
680
+ <span class="cline-any cline-yes">3x</span>
681
+ <span class="cline-any cline-yes">3x</span>
688
682
  <span class="cline-any cline-yes">3x</span>
689
683
  <span class="cline-any cline-neutral">&nbsp;</span>
690
684
  <span class="cline-any cline-neutral">&nbsp;</span>
691
- <span class="cline-any cline-yes">8x</span>
692
- <span class="cline-any cline-yes">8x</span>
685
+ <span class="cline-any cline-yes">2x</span>
686
+ <span class="cline-any cline-yes">2x</span>
687
+ <span class="cline-any cline-yes">2x</span>
693
688
  <span class="cline-any cline-yes">2x</span>
694
689
  <span class="cline-any cline-yes">2x</span>
695
690
  <span class="cline-any cline-yes">2x</span>
@@ -697,32 +692,9 @@
697
692
  <span class="cline-any cline-neutral">&nbsp;</span>
698
693
  <span class="cline-any cline-neutral">&nbsp;</span>
699
694
  <span class="cline-any cline-neutral">&nbsp;</span>
700
- <span class="cline-any cline-yes">8x</span>
701
- <span class="cline-any cline-neutral">&nbsp;</span>
702
- <span class="cline-any cline-neutral">&nbsp;</span>
703
- <span class="cline-any cline-yes">4x</span>
704
- <span class="cline-any cline-yes">4x</span>
705
- <span class="cline-any cline-neutral">&nbsp;</span>
706
- <span class="cline-any cline-neutral">&nbsp;</span>
707
- <span class="cline-any cline-neutral">&nbsp;</span>
708
- <span class="cline-any cline-neutral">&nbsp;</span>
709
- <span class="cline-any cline-neutral">&nbsp;</span>
710
- <span class="cline-any cline-neutral">&nbsp;</span>
711
- <span class="cline-any cline-neutral">&nbsp;</span>
712
- <span class="cline-any cline-neutral">&nbsp;</span>
713
- <span class="cline-any cline-yes">17x</span>
714
- <span class="cline-any cline-yes">17x</span>
715
- <span class="cline-any cline-yes">17x</span>
716
- <span class="cline-any cline-yes">17x</span>
717
- <span class="cline-any cline-neutral">&nbsp;</span>
718
- <span class="cline-any cline-neutral">&nbsp;</span>
719
- <span class="cline-any cline-neutral">&nbsp;</span>
720
- <span class="cline-any cline-neutral">&nbsp;</span>
721
- <span class="cline-any cline-yes">3x</span>
722
- <span class="cline-any cline-yes">3x</span>
723
- <span class="cline-any cline-yes">3x</span>
724
- <span class="cline-any cline-neutral">&nbsp;</span>
695
+ <span class="cline-any cline-yes">7x</span>
725
696
  <span class="cline-any cline-neutral">&nbsp;</span>
697
+ <span class="cline-any cline-yes">1x</span>
726
698
  <span class="cline-any cline-neutral">&nbsp;</span>
727
699
  <span class="cline-any cline-neutral">&nbsp;</span>
728
700
  <span class="cline-any cline-neutral">&nbsp;</span>
@@ -730,341 +702,330 @@
730
702
  <span class="cline-any cline-neutral">&nbsp;</span>
731
703
  <span class="cline-any cline-neutral">&nbsp;</span>
732
704
  <span class="cline-any cline-neutral">&nbsp;</span>
705
+ <span class="cline-any cline-yes">1x</span>
706
+ <span class="cline-any cline-yes">1x</span>
733
707
  <span class="cline-any cline-neutral">&nbsp;</span>
734
- <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">import type { KV } from 'nats';
708
+ <span class="cline-any cline-neutral">&nbsp;</span></td><td class="text"><pre class="prettyprint lang-js">/**
709
+ * NatsProxyClient — Binary WebSocket client for NATS operations via data-proxy.
710
+ *
711
+ * Speaks the binary frame protocol defined in data-proxy/protocol.ts.
712
+ * Shares the WebSocket connection with DataProxyClient (JSON-RPC).
713
+ */
735
714
  &nbsp;
736
- export interface RateLimitConfig {
737
- // Window size in seconds (default: 3600 = 1 hour)
738
- windowSeconds?: number;
739
- // Max units in the window (default: 1.0)
740
- maxPerWindow?: number;
741
- // Max concurrent requests waiting in queue (default: 100)
742
- maxQueueDepth?: number;
743
- // How long to wait in queue before giving up, ms (default: 300000 = 5 min)
744
- maxWaitMs?: number;
745
- // Optional NATS KV bucket for cross-server rate limiting
746
- bucket?: KV;
747
- }
715
+ import WebSocket from 'ws';
748
716
  &nbsp;
749
- export interface UsageRecord {
750
- userId: string;
751
- operation: string;
752
- used: number; // units charged so far in current window
753
- limit: number; // max units per window
754
- windowStart: Date; // when the current window started
755
- queued: number; // currently waiting in queue
756
- }
717
+ // ── Protocol constants (mirrored from data-proxy/protocol.ts) ─────────────
757
718
  &nbsp;
758
- export interface RateLimiter {
759
- // Check if an operation would exceed the limit.
760
- // If within limit: returns immediately.
761
- // If over limit: queues the request and waits until budget is available.
762
- // Throws RateLimitError if queue is full or wait timeout exceeded.
763
- check(
764
- userId: string,
765
- operation: string,
766
- estimatedCost: number,
767
- ): Promise&lt;void&gt;;
719
+ const OP_PUBLISH = 0x01;
720
+ const OP_SUBSCRIBE = 0x02;
721
+ const OP_UNSUBSCRIBE = 0x03;
768
722
  &nbsp;
769
- // Record actual cost after operation completes.
770
- charge(userId: string, operation: string, actualCost: number): Promise&lt;void&gt;;
723
+ const OP_KV_ENSURE = 0x10;
724
+ const OP_KV_GET = 0x11;
725
+ const OP_KV_PUT = 0x12;
726
+ const OP_KV_DELETE = 0x13;
727
+ const OP_KV_KEYS = 0x14;
771
728
  &nbsp;
772
- // Get current usage for a user/operation.
773
- getUsage(userId: string, operation: string): Promise&lt;UsageRecord&gt;;
774
- }
729
+ const OP_STREAM_ENSURE = 0x20;
730
+ const OP_JS_PUBLISH = 0x21;
731
+ const OP_JS_CONSUMER_CREATE = 0x22;
732
+ const OP_JS_CONSUMER_CONSUME = 0x23;
733
+ const OP_JS_ACK = 0x24;
734
+ const OP_JS_NAK = 0x25;
735
+ const OP_JS_WORKING = 0x26;
775
736
  &nbsp;
776
- export class RateLimitError extends Error {
777
- constructor(
778
- public readonly userId: string,
779
- public readonly operation: string,
780
- public readonly used: number,
781
- public readonly limit: number,
782
- ) {
783
- super(`[RateLimit] Rate limit exceeded for operation '${operation}'`);
784
- this.name = 'RateLimitError';
785
- }
786
- }
737
+ const OP_STATUS = 0x30;
738
+ &nbsp;
739
+ const PUSH_SUB_MESSAGE = 0x01;
740
+ const PUSH_JS_MESSAGE = 0x02;
741
+ &nbsp;
742
+ const STATUS_OK = 0x00;
743
+ &nbsp;
744
+ // ── State ─────────────────────────────────────────────────────────────────
745
+ &nbsp;
746
+ let _ws: WebSocket | null = null;
747
+ let _requestId = 0;
787
748
  &nbsp;
788
- // KV value shape stored in NATS KV
789
- interface KvUsage {
790
- used: number;
791
- windowStart: number; // epoch ms
749
+ interface PendingBinaryRequest {
750
+ resolve: (payload: Buffer) =&gt; void;
751
+ reject: (error: Error) =&gt; void;
792
752
  }
793
753
  &nbsp;
754
+ const _pending = new Map&lt;number, PendingBinaryRequest&gt;();
755
+ const _subHandlers = new Map&lt;number, (subject: string, data: Buffer) =&gt; void&gt;();
756
+ const _jsConsumerHandlers = new Map&lt;number, (msgId: number, deliveryCount: number, data: Buffer) =&gt; void&gt;();
757
+ &nbsp;
758
+ // Track active subscriptions for resubscription on reconnect
759
+ const _activeSubs = new Map&lt;number, { subject: string; handler: (subject: string, data: Buffer) =&gt; void }&gt;();
760
+ &nbsp;
794
761
  const textEncoder = new TextEncoder();
795
762
  const textDecoder = new TextDecoder();
796
763
  &nbsp;
797
- function encodeKvUsage(value: KvUsage): Uint8Array {
798
- return textEncoder.encode(JSON.stringify(value));
764
+ // ── Frame encoding/decoding ───────────────────────────────────────────────
765
+ &nbsp;
766
+ function encodeRequest(
767
+ requestId: number,
768
+ opcode: number,
769
+ subject: string,
770
+ payload: Uint8Array,
771
+ ): Buffer {
772
+ const subjectBytes = textEncoder.encode(subject);
773
+ const buf = Buffer.allocUnsafe(4 + 1 + 2 + subjectBytes.length + payload.length);
774
+ buf.writeUInt32BE(requestId, 0);
775
+ buf[4] = opcode;
776
+ buf.writeUInt16BE(subjectBytes.length, 5);
777
+ buf.set(subjectBytes, 7);
778
+ buf.set(payload, 7 + subjectBytes.length);
779
+ return buf;
799
780
  }
800
781
  &nbsp;
801
- function decodeKvUsage(bytes: Uint8Array): KvUsage {
802
- return JSON.parse(textDecoder.decode(bytes)) as KvUsage;
782
+ function encodeKeyPayload(key: string, value?: Uint8Array): Buffer {
783
+ const keyBytes = textEncoder.encode(key);
784
+ const buf = Buffer.allocUnsafe(2 + keyBytes.length + (value?.length ?? 0));
785
+ buf.writeUInt16BE(keyBytes.length, 0);
786
+ buf.set(keyBytes, 2);
787
+ if (value) buf.set(value, 2 + keyBytes.length);
788
+ return buf;
803
789
  }
804
790
  &nbsp;
805
- export function createRateLimiter(config: RateLimitConfig = {}): RateLimiter {
806
- const {
807
- windowSeconds = 3600,
808
- maxPerWindow = 1.0,
809
- maxQueueDepth = 100,
810
- maxWaitMs = 300_000,
811
- bucket,
812
- } = config;
791
+ // ── Connection ────────────────────────────────────────────────────────────
813
792
  &nbsp;
814
- // Per-key local queues (used in both modes for queuing waiting requests)
815
- interface QueueEntry {
816
- resolve: () =&gt; void;
817
- reject: (err: Error) =&gt; void;
818
- cost: number;
819
- }
820
- const queues = new Map&lt;string, QueueEntry[]&gt;();
793
+ /**
794
+ * Initialize the NATS proxy client. Must be called after the WebSocket is open.
795
+ * The WebSocket must be the same one used by DataProxyClient.
796
+ */
797
+ export function initNatsProxy(ws: WebSocket): void {
798
+ _ws = ws;
799
+ }
821
800
  &nbsp;
822
- function getQueue(key: string): QueueEntry[] {
823
- let q = queues.get(key);
824
- if (!q) {
825
- q = [];
826
- queues.set(key, q);
827
- }
828
- return q;
829
- }
801
+ /**
802
+ * Handle a binary message from the data-proxy WebSocket.
803
+ * Returns true if this was a binary NATS message (handled), false otherwise.
804
+ */
805
+ export function handleNatsBinaryMessage(data: Buffer): boolean {
806
+ if (data.length &lt; 5) return false;
830
807
  &nbsp;
831
- // ─── IN-MEMORY MODE ──────────────────────────────────────────────────────
808
+ const requestId = data.readUInt32BE(0);
832
809
  &nbsp;
833
- // Per user+operation tracking
834
- interface Usage {
835
- used: number;
836
- windowStart: Date;
837
- queue: QueueEntry[];
838
- }
839
- const usages = new Map&lt;string, Usage&gt;();
810
+ // Push frame (requestId === 0)
811
+ if (requestId === 0) {
812
+ const pushType = data[4];
840
813
  &nbsp;
841
- function getKey(userId: string, operation: string): string {
842
- return `${userId}:${operation}`;
843
- }
844
- &nbsp;
845
- function getUsageEntry(key: string): Usage {
846
- let entry = usages.get(key);
847
- if (!entry) {
848
- entry = { used: 0, windowStart: new Date(), queue: getQueue(key) };
849
- usages.set(key, entry);
814
+ if (pushType === PUSH_SUB_MESSAGE) {
815
+ const subId = data.readUInt16BE(5);
816
+ const subjectLen = data.readUInt16BE(7);
817
+ const subject = textDecoder.decode(data.subarray(9, 9 + subjectLen));
818
+ const msgData = data.subarray(9 + subjectLen);
819
+ const handler = _subHandlers.get(subId);
820
+ if (handler) handler(subject, msgData);
821
+ return true;
850
822
  }
851
- // Reset window if expired
852
- const age = (Date.now() - entry.windowStart.getTime()) / 1000;
853
- if (age &gt;= windowSeconds) {
854
- entry.used = 0;
855
- entry.windowStart = new Date();
856
- }
857
- return entry;
858
- }
859
823
  &nbsp;
860
- function drainQueue(entry: Usage): void {
861
- while (entry.queue.length &gt; 0) {
862
- const next = entry.queue[0];
863
- if (entry.used + next.cost &lt;= maxPerWindow) {
864
- entry.queue.shift();
865
- next.resolve();
866
- } else {
867
- break;
868
- }
824
+ if (pushType === PUSH_JS_MESSAGE) {
825
+ const consumerId = data.readUInt16BE(5);
826
+ const msgId = data.readUInt32BE(7);
827
+ const deliveryCount = data.readUInt16BE(11);
828
+ const msgData = data.subarray(13);
829
+ const handler = _jsConsumerHandlers.get(consumerId);
830
+ if (handler) handler(msgId, deliveryCount, msgData);
831
+ return true;
869
832
  }
870
- }
871
- &nbsp;
872
- // ─── NATS KV MODE ────────────────────────────────────────────────────────
873
833
  &nbsp;
874
- function kvKey(userId: string, operation: string): string {
875
- return `${userId}.${operation}`;
834
+ return false;
876
835
  }
877
836
  &nbsp;
878
- async function kvReadEntry(
879
- key: string,
880
- ): Promise&lt;{ data: KvUsage; revision: number }&gt; {
881
- const entry = await bucket!.get(key);
882
- if (!entry?.value || entry.value.length === 0) {
883
- return { data: { used: 0, windowStart: Date.now() }, revision: 0 };
884
- }
885
- const data = decodeKvUsage(entry.value);
886
- // Reset window if expired
887
- if ((Date.now() - data.windowStart) / 1000 &gt;= windowSeconds) {
888
- return { data: { used: 0, windowStart: Date.now() }, revision: entry.revision };
889
- }
890
- return { data, revision: entry.revision };
891
- }
892
- &nbsp;
893
- async function kvWriteEntry(
894
- key: string,
895
- value: KvUsage,
896
- revision: number,
897
- ): Promise&lt;void&gt; {
898
- const encoded = encodeKvUsage(value);
899
- if (revision === 0) {
900
- await bucket!.create(key, encoded);
837
+ // Response frame
838
+ const status = data[4];
839
+ const payload = data.subarray(5);
840
+ const pending = _pending.get(requestId);
841
+ if (pending) {
842
+ _pending.delete(requestId);
843
+ if (status === STATUS_OK) {
844
+ pending.resolve(payload);
901
845
  } else {
902
- await bucket!.update(key, encoded, revision);
846
+ pending.reject(new Error(textDecoder.decode(payload)));
903
847
  }
848
+ return true;
904
849
  }
905
850
  &nbsp;
906
- // Drain local queue for a KV key by re-checking current KV usage
907
- async function kvDrainQueue(
908
- key: string,
909
- _userId: string,
910
- _operation: string,
911
- ): Promise&lt;void&gt; {
912
- const q = getQueue(key);
913
- while (q.length &gt; 0) {
914
- const next = q[0];
915
- const { data } = await kvReadEntry(key);
916
- if (data.used + next.cost &lt;= maxPerWindow) {
917
- q.shift();
918
- next.resolve();
919
- } else {
920
- break;
921
- }
922
- }
851
+ return false;
852
+ }
853
+ &nbsp;
854
+ function sendBinary(buf: Buffer): void {
855
+ if (_ws?.readyState === WebSocket.OPEN) {
856
+ _ws.send(buf);
923
857
  }
858
+ }
859
+ &nbsp;
860
+ function request(opcode: number, subject: string, payload: Uint8Array = new Uint8Array(0)): Promise&lt;Buffer&gt; {
861
+ const id = ++_requestId;
862
+ const frame = encodeRequest(id, opcode, subject, payload);
924
863
  &nbsp;
925
- // ─── RETURN ───────────────────────────────────────────────────────────────
864
+ return new Promise&lt;Buffer&gt;((resolve, reject) =&gt; {
865
+ _pending.set(id, { resolve, reject });
866
+ sendBinary(frame);
867
+ });
868
+ }
926
869
  &nbsp;
927
- if (bucket) {
928
- // NATS KV mode
929
- return {
930
- async check(userId, operation, estimatedCost) {
931
- const key = kvKey(userId, operation);
932
- const { data } = await kvReadEntry(key);
870
+ // ── Public API: Pub/Sub ───────────────────────────────────────────────────
933
871
  &nbsp;
934
- if (data.used + estimatedCost &lt;= maxPerWindow) {
935
- return;
936
- }
872
+ /**
873
+ * Publish a message to a NATS subject (fire-and-forget).
874
+ */
875
+ export function natsProxyPublish(subject: string, payload: Uint8Array): void {
876
+ if (_ws?.readyState !== WebSocket.OPEN) return;
877
+ const id = ++_requestId;
878
+ sendBinary(encodeRequest(id, OP_PUBLISH, subject, payload));
879
+ // Fire and forget — don't wait for response
880
+ _pending.set(id, {
881
+ resolve: () =&gt; {},
882
+ reject: () =&gt; {},
883
+ });
884
+ }
937
885
  &nbsp;
938
- // A single request that exceeds the entire window limit can never be served.
939
- if (estimatedCost &gt; maxPerWindow) {
940
- throw new RateLimitError(userId, operation, data.used, maxPerWindow);
941
- }
886
+ /**
887
+ * Subscribe to a NATS subject. Returns an unsubscribe function.
888
+ */
889
+ export async function natsProxySubscribe(
890
+ subject: string,
891
+ handler: (subject: string, data: Buffer) =&gt; void,
892
+ ): Promise&lt;() =&gt; void&gt; {
893
+ const resp = await request(OP_SUBSCRIBE, subject);
894
+ const subId = resp.readUInt16BE(0);
895
+ _subHandlers.set(subId, handler);
896
+ _activeSubs.set(subId, { subject, handler });
942
897
  &nbsp;
943
- // Over limit — queue if there's room
944
- const q = getQueue(key);
945
- if (q.length &gt;= maxQueueDepth) {
946
- throw new RateLimitError(userId, operation, data.used, maxPerWindow);
947
- }
898
+ return () =&gt; {
899
+ _subHandlers.delete(subId);
900
+ _activeSubs.delete(subId);
901
+ const unsubPayload = Buffer.allocUnsafe(2);
902
+ unsubPayload.writeUInt16BE(subId, 0);
903
+ const id = ++_requestId;
904
+ sendBinary(encodeRequest(id, OP_UNSUBSCRIBE, '', unsubPayload));
905
+ _pending.set(id, { resolve: () =&gt; {}, reject: () =&gt; {} });
906
+ };
907
+ }
948
908
  &nbsp;
949
- await new Promise&lt;void&gt;((resolve, reject) =&gt; {
950
- const timer = setTimeout(() =&gt; {
951
- const idx = q.findIndex((item) =&gt; item.resolve === resolveWrapper);
952
- <span class="missing-if-branch" title="else path not taken" >E</span>if (idx &gt;= 0) q.splice(idx, 1);
953
- reject(new RateLimitError(userId, operation, data.used, maxPerWindow));
954
- }, maxWaitMs);
909
+ // ── Public API: KV ────────────────────────────────────────────────────────
955
910
  &nbsp;
956
- const resolveWrapper = () =&gt; {
957
- clearTimeout(timer);
958
- resolve();
959
- };
911
+ export async function natsProxyKvEnsure(
912
+ name: string,
913
+ opts?: Record&lt;string, unknown&gt;,
914
+ ): Promise&lt;void&gt; {
915
+ await request(OP_KV_ENSURE, name, textEncoder.encode(JSON.stringify(opts ?? {})));
916
+ }
960
917
  &nbsp;
961
- q.push({ cost: estimatedCost, resolve: resolveWrapper, reject });
962
- });
963
- },
918
+ export async function natsProxyKvGet(
919
+ name: string,
920
+ key: string,
921
+ ): Promise&lt;Buffer | null&gt; {
922
+ const resp = await request(OP_KV_GET, name, encodeKeyPayload(key));
923
+ return resp.length === 0 ? null : resp;
924
+ }
964
925
  &nbsp;
965
- async charge(userId, operation, actualCost) {
966
- const key = kvKey(userId, operation);
967
- const MAX_RETRIES = 3;
926
+ export async function natsProxyKvPut(
927
+ name: string,
928
+ key: string,
929
+ value: Uint8Array,
930
+ ): Promise&lt;void&gt; {
931
+ await request(OP_KV_PUT, name, encodeKeyPayload(key, value));
932
+ }
968
933
  &nbsp;
969
- for (let attempt = 0; attempt &lt; MAX_RETRIES; attempt++) {
970
- const { data, revision } = await kvReadEntry(key);
971
- const updated: KvUsage = {
972
- used: data.used + actualCost,
973
- windowStart: data.windowStart,
974
- };
975
- try {
976
- await kvWriteEntry(key, updated, revision);
977
- // After successful write, try to drain the local queue
978
- await kvDrainQueue(key, userId, operation);
979
- return;
980
- } catch {
981
- // Revision conflict — retry
982
- if (attempt === MAX_RETRIES - 1) throw new Error('[RateLimit] KV write conflict after max retries');
983
- }
984
- }
985
- },
934
+ export async function natsProxyKvDelete(
935
+ name: string,
936
+ key: string,
937
+ ): Promise&lt;void&gt; {
938
+ await request(OP_KV_DELETE, name, encodeKeyPayload(key));
939
+ }
986
940
  &nbsp;
987
- async getUsage(userId, operation) {
988
- const key = kvKey(userId, operation);
989
- const { data } = await kvReadEntry(key);
990
- const q = getQueue(key);
991
- return {
992
- userId,
993
- operation,
994
- used: data.used,
995
- limit: maxPerWindow,
996
- windowStart: new Date(data.windowStart),
997
- queued: q.length,
998
- };
999
- },
1000
- };
1001
- }
941
+ export async function natsProxyKvKeys(name: string): Promise&lt;string[]&gt; {
942
+ const resp = await request(OP_KV_KEYS, name);
943
+ if (resp.length === 0) return [];
944
+ return JSON.parse(textDecoder.decode(resp)) as string[];
945
+ }
1002
946
  &nbsp;
1003
- // In-memory mode
1004
- return {
1005
- async check(userId, operation, estimatedCost) {
1006
- const key = getKey(userId, operation);
1007
- const entry = getUsageEntry(key);
947
+ // ── Public API: JetStream ─────────────────────────────────────────────────
1008
948
  &nbsp;
1009
- if (entry.used + estimatedCost &lt;= maxPerWindow) {
1010
- // Within limit — proceed immediately
1011
- return;
1012
- }
949
+ export async function natsProxyEnsureStream(config: Record&lt;string, unknown&gt;): Promise&lt;void&gt; {
950
+ await request(OP_STREAM_ENSURE, '', textEncoder.encode(JSON.stringify(config)));
951
+ }
1013
952
  &nbsp;
1014
- // A single request that exceeds the entire window limit can never be served.
1015
- // Queuing it would block for maxWaitMs with no possibility of resolution.
1016
- if (estimatedCost &gt; maxPerWindow) {
1017
- throw new RateLimitError(userId, operation, entry.used, maxPerWindow);
1018
- }
953
+ export async function natsProxyJsPublish(subject: string, payload: Uint8Array): Promise&lt;void&gt; {
954
+ await request(OP_JS_PUBLISH, subject, payload);
955
+ }
1019
956
  &nbsp;
1020
- // Over limit — queue if there's room
1021
- if (entry.queue.length &gt;= maxQueueDepth) {
1022
- throw new RateLimitError(userId, operation, entry.used, maxPerWindow);
1023
- }
957
+ export async function natsProxyJsConsumerCreate(
958
+ streamName: string,
959
+ config: Record&lt;string, unknown&gt;,
960
+ ): Promise&lt;void&gt; {
961
+ await request(OP_JS_CONSUMER_CREATE, streamName, textEncoder.encode(JSON.stringify(config)));
962
+ }
1024
963
  &nbsp;
1025
- await new Promise&lt;void&gt;((resolve, reject) =&gt; {
1026
- const timer = setTimeout(() =&gt; {
1027
- const idx = entry.queue.findIndex((q) =&gt; q.resolve === resolve);
1028
- <span class="missing-if-branch" title="if path not taken" >I</span>if (idx &gt;= 0) <span class="cstat-no" title="statement not covered" >entry.queue.splice(idx, 1);</span>
1029
- reject(
1030
- new RateLimitError(userId, operation, entry.used, maxPerWindow),
1031
- );
1032
- }, maxWaitMs);
964
+ export interface JsProxyMessage {
965
+ data: Buffer;
966
+ deliveryCount: number;
967
+ ack: () =&gt; void;
968
+ nak: (delayMs?: number) =&gt; void;
969
+ working: () =&gt; void;
970
+ }
1033
971
  &nbsp;
1034
- entry.queue.push({
1035
- cost: estimatedCost,
1036
- resolve: () =&gt; {
1037
- clearTimeout(timer);
1038
- resolve();
1039
- },
1040
- reject,
1041
- });
1042
- });
1043
- },
972
+ export async function natsProxyJsConsumerConsume(
973
+ streamName: string,
974
+ consumerName: string,
975
+ handler: (msg: JsProxyMessage) =&gt; void,
976
+ ): Promise&lt;{ stop: () =&gt; void }&gt; {
977
+ const resp = await request(
978
+ OP_JS_CONSUMER_CONSUME,
979
+ streamName,
980
+ textEncoder.encode(consumerName),
981
+ );
982
+ const consumerId = resp.readUInt16BE(0);
1044
983
  &nbsp;
1045
- // eslint-disable-next-line @typescript-eslint/require-await
1046
- async charge(userId, operation, actualCost) {
1047
- const key = getKey(userId, operation);
1048
- const entry = getUsageEntry(key);
1049
- entry.used += actualCost;
1050
- drainQueue(entry);
1051
- },
984
+ _jsConsumerHandlers.set(consumerId, (msgId, deliveryCount, data) =&gt; {
985
+ handler({
986
+ data,
987
+ deliveryCount,
988
+ ack: () =&gt; {
989
+ const payload = Buffer.allocUnsafe(6);
990
+ payload.writeUInt16BE(consumerId, 0);
991
+ payload.writeUInt32BE(msgId, 2);
992
+ const id = ++_requestId;
993
+ sendBinary(encodeRequest(id, OP_JS_ACK, '', payload));
994
+ _pending.set(id, { resolve: () =&gt; {}, reject: () =&gt; {} });
995
+ },
996
+ nak: (delayMs = 0) =&gt; {
997
+ const payload = Buffer.allocUnsafe(10);
998
+ payload.writeUInt16BE(consumerId, 0);
999
+ payload.writeUInt32BE(msgId, 2);
1000
+ payload.writeUInt32BE(delayMs, 6);
1001
+ const id = ++_requestId;
1002
+ sendBinary(encodeRequest(id, OP_JS_NAK, '', payload));
1003
+ _pending.set(id, { resolve: () =&gt; {}, reject: () =&gt; {} });
1004
+ },
1005
+ working: () =&gt; {
1006
+ const payload = Buffer.allocUnsafe(6);
1007
+ payload.writeUInt16BE(consumerId, 0);
1008
+ payload.writeUInt32BE(msgId, 2);
1009
+ const id = ++_requestId;
1010
+ sendBinary(encodeRequest(id, OP_JS_WORKING, '', payload));
1011
+ _pending.set(id, { resolve: () =&gt; {}, reject: () =&gt; {} });
1012
+ },
1013
+ });
1014
+ });
1052
1015
  &nbsp;
1053
- // eslint-disable-next-line @typescript-eslint/require-await
1054
- async getUsage(userId, operation) {
1055
- const key = getKey(userId, operation);
1056
- const entry = getUsageEntry(key);
1057
- return {
1058
- userId,
1059
- operation,
1060
- used: entry.used,
1061
- limit: maxPerWindow,
1062
- windowStart: entry.windowStart,
1063
- queued: entry.queue.length,
1064
- };
1016
+ return {
1017
+ stop: () =&gt; {
1018
+ _jsConsumerHandlers.delete(consumerId);
1065
1019
  },
1066
1020
  };
1067
1021
  }
1022
+ &nbsp;
1023
+ // ── Public API: Status ────────────────────────────────────────────────────
1024
+ &nbsp;
1025
+ export async function natsProxyStatus(): Promise&lt;{ connected: boolean; rtt?: number }&gt; {
1026
+ const resp = await request(OP_STATUS, '');
1027
+ return JSON.parse(textDecoder.decode(resp)) as { connected: boolean; rtt?: number };
1028
+ }
1068
1029
  &nbsp;</pre></td></tr></table></pre>
1069
1030
 
1070
1031
  <div class='push'></div><!-- for sticky footer -->
@@ -1072,16 +1033,16 @@ export function createRateLimiter(config: RateLimitConfig = {}): RateLimiter {
1072
1033
  <div class='footer quiet pad2 space-top1 center small'>
1073
1034
  Code coverage generated by
1074
1035
  <a href="https://istanbul.js.org/" target="_blank" rel="noopener noreferrer">istanbul</a>
1075
- at 2026-04-10T04:51:37.177Z
1036
+ at 2026-04-11T01:13:16.301Z
1076
1037
  </div>
1077
- <script src="../prettify.js"></script>
1038
+ <script src="prettify.js"></script>
1078
1039
  <script>
1079
1040
  window.onload = function () {
1080
1041
  prettyPrint();
1081
1042
  };
1082
1043
  </script>
1083
- <script src="../sorter.js"></script>
1084
- <script src="../block-navigation.js"></script>
1044
+ <script src="sorter.js"></script>
1045
+ <script src="block-navigation.js"></script>
1085
1046
  </body>
1086
1047
  </html>
1087
1048