@visulima/notification 1.0.0-alpha.1 → 1.0.0-alpha.2

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 (32) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/LICENSE.md +440 -0
  3. package/README.md +17 -0
  4. package/dist/digest/index.d.ts +128 -0
  5. package/dist/digest/index.js +1 -0
  6. package/dist/i18n/index.d.ts +42 -0
  7. package/dist/i18n/index.js +1 -0
  8. package/dist/index.d.ts +2 -2
  9. package/dist/index.js +1 -1
  10. package/dist/layouts/index.d.ts +45 -0
  11. package/dist/layouts/index.js +1 -0
  12. package/dist/packem_shared/MemoryDigestStore-Bw-sE5MI.js +1 -0
  13. package/dist/packem_shared/Notification-B22tuJPy.js +1 -0
  14. package/dist/packem_shared/UnstorageDigestStore-QAcO3Vb_.js +1 -0
  15. package/dist/packem_shared/createDigester-vMNPejGF.js +1 -0
  16. package/dist/packem_shared/createNotificationWorkflow-B70OgeED.js +1 -0
  17. package/dist/packem_shared/createTranslator-CY91C2eS.js +1 -0
  18. package/dist/packem_shared/defineLayout-73TNxYPq.js +1 -0
  19. package/dist/packem_shared/{notification.d-BBLyVuqi.d.ts → notification.d-DFCVTods.d.ts} +18 -1
  20. package/dist/packem_shared/{types.d-DyM9JjL7.d.ts → types.d-67TogK4k.d.ts} +1 -1
  21. package/dist/packem_shared/types.d-C1GoEAlI.d-Bv3grfyD.d.ts +83 -0
  22. package/dist/preferences/index.d.ts +1 -1
  23. package/dist/queue/bullmq-queue.d.ts +2 -2
  24. package/dist/queue/index.d.ts +2 -2
  25. package/dist/queue/pg-boss-queue.d.ts +2 -2
  26. package/dist/queue/sqs-queue.d.ts +2 -2
  27. package/dist/queue/unstorage-queue.d.ts +2 -2
  28. package/dist/routing/index.d.ts +1 -1
  29. package/dist/workflow/index.d.ts +83 -0
  30. package/dist/workflow/index.js +1 -0
  31. package/package.json +30 -2
  32. package/dist/packem_shared/Notification-D1YeYxrQ.js +0 -1
package/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ ## @visulima/notification [1.0.0-alpha.2](https://github.com/visulima/visulima/compare/@visulima/notification@1.0.0-alpha.1...@visulima/notification@1.0.0-alpha.2) (2026-06-23)
2
+
3
+ ### Features
4
+
5
+ * **notification:** add workflow steps and digest subsystems ([#698](https://github.com/visulima/visulima/issues/698)) ([81e2551](https://github.com/visulima/visulima/commit/81e2551b00508e1b5b50e96cb2aec248d938f8fc))
6
+
7
+ ### Bug Fixes
8
+
9
+ * fixed hoisted package ([45d4062](https://github.com/visulima/visulima/commit/45d40626dc6930fcd457ae90d6d4293cfad92595))
10
+
11
+
12
+ ### Dependencies
13
+
14
+ * **@visulima/email:** upgraded to 1.0.0-alpha.42
15
+ * **@visulima/workflow:** upgraded to 1.0.0-alpha.1
16
+
1
17
  ## @visulima/notification 1.0.0-alpha.1 (2026-06-20)
2
18
 
3
19
  ### Features
package/LICENSE.md CHANGED
@@ -405,6 +405,446 @@ Repository: git+https://github.com/visulima/visulima.git
405
405
 
406
406
  ---------------------------------------
407
407
 
408
+ ## @visulima/workflow
409
+ License: MIT
410
+ By: Daniel Bannert
411
+ Repository: git+https://github.com/visulima/visulima.git
412
+
413
+ > MIT License
414
+ >
415
+ > Copyright (c) 2026 visulima
416
+ >
417
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
418
+ > of this software and associated documentation files (the "Software"), to deal
419
+ > in the Software without restriction, including without limitation the rights
420
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
421
+ > copies of the Software, and to permit persons to whom the Software is
422
+ > furnished to do so, subject to the following conditions:
423
+ >
424
+ > The above copyright notice and this permission notice shall be included in all
425
+ > copies or substantial portions of the Software.
426
+ >
427
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
428
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
429
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
430
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
431
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
432
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
433
+ > SOFTWARE.
434
+ >
435
+ > <!-- DEPENDENCIES -->
436
+ >
437
+ > # Licenses of bundled dependencies
438
+ >
439
+ > The published @visulima/workflow artifact additionally contains code with the following licenses:
440
+ > MIT
441
+ >
442
+ > # Bundled dependencies:
443
+ >
444
+ > ## @visulima/error
445
+ >
446
+ > License: MIT
447
+ > By: Daniel Bannert
448
+ > Repository: git+https://github.com/visulima/visulima.git
449
+ >
450
+ > > MIT License
451
+ > >
452
+ > > Copyright (c) 2026 visulima
453
+ > >
454
+ > > Permission is hereby granted, free of charge, to any person obtaining a copy
455
+ > > of this software and associated documentation files (the "Software"), to deal
456
+ > > in the Software without restriction, including without limitation the rights
457
+ > > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
458
+ > > copies of the Software, and to permit persons to whom the Software is
459
+ > > furnished to do so, subject to the following conditions:
460
+ > >
461
+ > > The above copyright notice and this permission notice shall be included in all
462
+ > > copies or substantial portions of the Software.
463
+ > >
464
+ > > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
465
+ > > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
466
+ > > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
467
+ > > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
468
+ > > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
469
+ > > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
470
+ > > SOFTWARE.
471
+ > >
472
+ > > # Licenses of bundled dependencies
473
+ > >
474
+ > > The published @visulima/error artifact additionally contains code with the following licenses:
475
+ > > MIT
476
+ > >
477
+ > > # Bundled dependencies:
478
+ > >
479
+ > > ## is-plain-obj
480
+ > >
481
+ > > License: MIT
482
+ > > By: Sindre Sorhus
483
+ > > Repository: sindresorhus/is-plain-obj
484
+ > >
485
+ > > > MIT License
486
+ > > >
487
+ > > > Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
488
+ > > >
489
+ > > > Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
490
+ > > >
491
+ > > > The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
492
+ > > >
493
+ > > > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
494
+ > >
495
+ > > # Licenses of bundled types
496
+ > >
497
+ > > The published @visulima/error artifact additionally contains code with the following licenses:
498
+ > > Apache-2.0
499
+ > >
500
+ > > # Bundled types:
501
+ > >
502
+ > > ## ai
503
+ > >
504
+ > > License: Apache-2.0
505
+ > > Repository: https://github.com/vercel/ai
506
+ > >
507
+ > > > Copyright 2023 Vercel, Inc.
508
+ > > >
509
+ > > > Licensed under the Apache License, Version 2.0 (the "License");
510
+ > > > you may not use this file except in compliance with the License.
511
+ > > > You may obtain a copy of the License at
512
+ > > >
513
+ > > > http://www.apache.org/licenses/LICENSE-2.0
514
+ > > >
515
+ > > > Unless required by applicable law or agreed to in writing, software
516
+ > > > distributed under the License is distributed on an "AS IS" BASIS,
517
+ > > > WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
518
+ > > > See the License for the specific language governing permissions and
519
+ > > > limitations under the License.
520
+ >
521
+ > <!-- /DEPENDENCIES -->
522
+ >
523
+ >
524
+ >
525
+ > # Licenses of bundled types
526
+ >
527
+ > The published @visulima/workflow artifact additionally contains code with the following licenses:
528
+ > Apache-2.0, MIT
529
+ >
530
+ > # Bundled types:
531
+ >
532
+ > ## @opentelemetry/api
533
+ >
534
+ > License: Apache-2.0
535
+ > By: OpenTelemetry Authors
536
+ > Repository: open-telemetry/opentelemetry-js
537
+ >
538
+ > > Apache License
539
+ > > Version 2.0, January 2004
540
+ > > http://www.apache.org/licenses/
541
+ > >
542
+ > > TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
543
+ > >
544
+ > > 1. Definitions.
545
+ > >
546
+ > > "License" shall mean the terms and conditions for use, reproduction,
547
+ > > and distribution as defined by Sections 1 through 9 of this document.
548
+ > >
549
+ > > "Licensor" shall mean the copyright owner or entity authorized by
550
+ > > the copyright owner that is granting the License.
551
+ > >
552
+ > > "Legal Entity" shall mean the union of the acting entity and all
553
+ > > other entities that control, are controlled by, or are under common
554
+ > > control with that entity. For the purposes of this definition,
555
+ > > "control" means (i) the power, direct or indirect, to cause the
556
+ > > direction or management of such entity, whether by contract or
557
+ > > otherwise, or (ii) ownership of fifty percent (50%) or more of the
558
+ > > outstanding shares, or (iii) beneficial ownership of such entity.
559
+ > >
560
+ > > "You" (or "Your") shall mean an individual or Legal Entity
561
+ > > exercising permissions granted by this License.
562
+ > >
563
+ > > "Source" form shall mean the preferred form for making modifications,
564
+ > > including but not limited to software source code, documentation
565
+ > > source, and configuration files.
566
+ > >
567
+ > > "Object" form shall mean any form resulting from mechanical
568
+ > > transformation or translation of a Source form, including but
569
+ > > not limited to compiled object code, generated documentation,
570
+ > > and conversions to other media types.
571
+ > >
572
+ > > "Work" shall mean the work of authorship, whether in Source or
573
+ > > Object form, made available under the License, as indicated by a
574
+ > > copyright notice that is included in or attached to the work
575
+ > > (an example is provided in the Appendix below).
576
+ > >
577
+ > > "Derivative Works" shall mean any work, whether in Source or Object
578
+ > > form, that is based on (or derived from) the Work and for which the
579
+ > > editorial revisions, annotations, elaborations, or other modifications
580
+ > > represent, as a whole, an original work of authorship. For the purposes
581
+ > > of this License, Derivative Works shall not include works that remain
582
+ > > separable from, or merely link (or bind by name) to the interfaces of,
583
+ > > the Work and Derivative Works thereof.
584
+ > >
585
+ > > "Contribution" shall mean any work of authorship, including
586
+ > > the original version of the Work and any modifications or additions
587
+ > > to that Work or Derivative Works thereof, that is intentionally
588
+ > > submitted to Licensor for inclusion in the Work by the copyright owner
589
+ > > or by an individual or Legal Entity authorized to submit on behalf of
590
+ > > the copyright owner. For the purposes of this definition, "submitted"
591
+ > > means any form of electronic, verbal, or written communication sent
592
+ > > to the Licensor or its representatives, including but not limited to
593
+ > > communication on electronic mailing lists, source code control systems,
594
+ > > and issue tracking systems that are managed by, or on behalf of, the
595
+ > > Licensor for the purpose of discussing and improving the Work, but
596
+ > > excluding communication that is conspicuously marked or otherwise
597
+ > > designated in writing by the copyright owner as "Not a Contribution."
598
+ > >
599
+ > > "Contributor" shall mean Licensor and any individual or Legal Entity
600
+ > > on behalf of whom a Contribution has been received by Licensor and
601
+ > > subsequently incorporated within the Work.
602
+ > >
603
+ > > 2. Grant of Copyright License. Subject to the terms and conditions of
604
+ > > this License, each Contributor hereby grants to You a perpetual,
605
+ > > worldwide, non-exclusive, no-charge, royalty-free, irrevocable
606
+ > > copyright license to reproduce, prepare Derivative Works of,
607
+ > > publicly display, publicly perform, sublicense, and distribute the
608
+ > > Work and such Derivative Works in Source or Object form.
609
+ > > 3. Grant of Patent License. Subject to the terms and conditions of
610
+ > > this License, each Contributor hereby grants to You a perpetual,
611
+ > > worldwide, non-exclusive, no-charge, royalty-free, irrevocable
612
+ > > (except as stated in this section) patent license to make, have made,
613
+ > > use, offer to sell, sell, import, and otherwise transfer the Work,
614
+ > > where such license applies only to those patent claims licensable
615
+ > > by such Contributor that are necessarily infringed by their
616
+ > > Contribution(s) alone or by combination of their Contribution(s)
617
+ > > with the Work to which such Contribution(s) was submitted. If You
618
+ > > institute patent litigation against any entity (including a
619
+ > > cross-claim or counterclaim in a lawsuit) alleging that the Work
620
+ > > or a Contribution incorporated within the Work constitutes direct
621
+ > > or contributory patent infringement, then any patent licenses
622
+ > > granted to You under this License for that Work shall terminate
623
+ > > as of the date such litigation is filed.
624
+ > > 4. Redistribution. You may reproduce and distribute copies of the
625
+ > > Work or Derivative Works thereof in any medium, with or without
626
+ > > modifications, and in Source or Object form, provided that You
627
+ > > meet the following conditions:
628
+ > >
629
+ > > (a) You must give any other recipients of the Work or
630
+ > > Derivative Works a copy of this License; and
631
+ > >
632
+ > > (b) You must cause any modified files to carry prominent notices
633
+ > > stating that You changed the files; and
634
+ > >
635
+ > > (c) You must retain, in the Source form of any Derivative Works
636
+ > > that You distribute, all copyright, patent, trademark, and
637
+ > > attribution notices from the Source form of the Work,
638
+ > > excluding those notices that do not pertain to any part of
639
+ > > the Derivative Works; and
640
+ > >
641
+ > > (d) If the Work includes a "NOTICE" text file as part of its
642
+ > > distribution, then any Derivative Works that You distribute must
643
+ > > include a readable copy of the attribution notices contained
644
+ > > within such NOTICE file, excluding those notices that do not
645
+ > > pertain to any part of the Derivative Works, in at least one
646
+ > > of the following places: within a NOTICE text file distributed
647
+ > > as part of the Derivative Works; within the Source form or
648
+ > > documentation, if provided along with the Derivative Works; or,
649
+ > > within a display generated by the Derivative Works, if and
650
+ > > wherever such third-party notices normally appear. The contents
651
+ > > of the NOTICE file are for informational purposes only and
652
+ > > do not modify the License. You may add Your own attribution
653
+ > > notices within Derivative Works that You distribute, alongside
654
+ > > or as an addendum to the NOTICE text from the Work, provided
655
+ > > that such additional attribution notices cannot be construed
656
+ > > as modifying the License.
657
+ > >
658
+ > > You may add Your own copyright statement to Your modifications and
659
+ > > may provide additional or different license terms and conditions
660
+ > > for use, reproduction, or distribution of Your modifications, or
661
+ > > for any such Derivative Works as a whole, provided Your use,
662
+ > > reproduction, and distribution of the Work otherwise complies with
663
+ > > the conditions stated in this License.
664
+ > >
665
+ > > 5. Submission of Contributions. Unless You explicitly state otherwise,
666
+ > > any Contribution intentionally submitted for inclusion in the Work
667
+ > > by You to the Licensor shall be under the terms and conditions of
668
+ > > this License, without any additional terms or conditions.
669
+ > > Notwithstanding the above, nothing herein shall supersede or modify
670
+ > > the terms of any separate license agreement you may have executed
671
+ > > with Licensor regarding such Contributions.
672
+ > > 6. Trademarks. This License does not grant permission to use the trade
673
+ > > names, trademarks, service marks, or product names of the Licensor,
674
+ > > except as required for reasonable and customary use in describing the
675
+ > > origin of the Work and reproducing the content of the NOTICE file.
676
+ > > 7. Disclaimer of Warranty. Unless required by applicable law or
677
+ > > agreed to in writing, Licensor provides the Work (and each
678
+ > > Contributor provides its Contributions) on an "AS IS" BASIS,
679
+ > > WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
680
+ > > implied, including, without limitation, any warranties or conditions
681
+ > > of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
682
+ > > PARTICULAR PURPOSE. You are solely responsible for determining the
683
+ > > appropriateness of using or redistributing the Work and assume any
684
+ > > risks associated with Your exercise of permissions under this License.
685
+ > > 8. Limitation of Liability. In no event and under no legal theory,
686
+ > > whether in tort (including negligence), contract, or otherwise,
687
+ > > unless required by applicable law (such as deliberate and grossly
688
+ > > negligent acts) or agreed to in writing, shall any Contributor be
689
+ > > liable to You for damages, including any direct, indirect, special,
690
+ > > incidental, or consequential damages of any character arising as a
691
+ > > result of this License or out of the use or inability to use the
692
+ > > Work (including but not limited to damages for loss of goodwill,
693
+ > > work stoppage, computer failure or malfunction, or any and all
694
+ > > other commercial damages or losses), even if such Contributor
695
+ > > has been advised of the possibility of such damages.
696
+ > > 9. Accepting Warranty or Additional Liability. While redistributing
697
+ > > the Work or Derivative Works thereof, You may choose to offer,
698
+ > > and charge a fee for, acceptance of support, warranty, indemnity,
699
+ > > or other liability obligations and/or rights consistent with this
700
+ > > License. However, in accepting such obligations, You may act only
701
+ > > on Your own behalf and on Your sole responsibility, not on behalf
702
+ > > of any other Contributor, and only if You agree to indemnify,
703
+ > > defend, and hold each Contributor harmless for any liability
704
+ > > incurred by, or claims asserted against, such Contributor by reason
705
+ > > of your accepting any such warranty or additional liability.
706
+ > >
707
+ > > END OF TERMS AND CONDITIONS
708
+ > >
709
+ > > APPENDIX: How to apply the Apache License to your work.
710
+ > >
711
+ > > To apply the Apache License to your work, attach the following
712
+ > > boilerplate notice, with the fields enclosed by brackets "[]"
713
+ > > replaced with your own identifying information. (Don't include
714
+ > > the brackets!) The text should be enclosed in the appropriate
715
+ > > comment syntax for the file format. We also recommend that a
716
+ > > file or class name and description of purpose be included on the
717
+ > > same "printed page" as the copyright notice for easier
718
+ > > identification within third-party archives.
719
+ > >
720
+ > > Copyright [yyyy] [name of copyright owner]
721
+ > >
722
+ > > Licensed under the Apache License, Version 2.0 (the "License");
723
+ > > you may not use this file except in compliance with the License.
724
+ > > You may obtain a copy of the License at
725
+ > >
726
+ > > http://www.apache.org/licenses/LICENSE-2.0
727
+ > >
728
+ > > Unless required by applicable law or agreed to in writing, software
729
+ > > distributed under the License is distributed on an "AS IS" BASIS,
730
+ > > WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
731
+ > > See the License for the specific language governing permissions and
732
+ > > limitations under the License.
733
+ >
734
+ > ---
735
+ >
736
+ > ## @visulima/error
737
+ >
738
+ > License: MIT
739
+ > By: Daniel Bannert
740
+ > Repository: git+https://github.com/visulima/visulima.git
741
+ >
742
+ > > MIT License
743
+ > >
744
+ > > Copyright (c) 2026 visulima
745
+ > >
746
+ > > Permission is hereby granted, free of charge, to any person obtaining a copy
747
+ > > of this software and associated documentation files (the "Software"), to deal
748
+ > > in the Software without restriction, including without limitation the rights
749
+ > > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
750
+ > > copies of the Software, and to permit persons to whom the Software is
751
+ > > furnished to do so, subject to the following conditions:
752
+ > >
753
+ > > The above copyright notice and this permission notice shall be included in all
754
+ > > copies or substantial portions of the Software.
755
+ > >
756
+ > > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
757
+ > > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
758
+ > > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
759
+ > > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
760
+ > > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
761
+ > > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
762
+ > > SOFTWARE.
763
+ > >
764
+ > > <!-- DEPENDENCIES -->
765
+ > >
766
+ > > # Licenses of bundled dependencies
767
+ > >
768
+ > > The published @visulima/error artifact additionally contains code with the following licenses:
769
+ > > MIT
770
+ > >
771
+ > > # Bundled dependencies:
772
+ > >
773
+ > > ## is-plain-obj
774
+ > >
775
+ > > License: MIT
776
+ > > By: Sindre Sorhus
777
+ > > Repository: sindresorhus/is-plain-obj
778
+ > >
779
+ > > > MIT License
780
+ > > >
781
+ > > > Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
782
+ > > >
783
+ > > > Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
784
+ > > >
785
+ > > > The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
786
+ > > >
787
+ > > > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
788
+ > >
789
+ > > <!-- /DEPENDENCIES -->
790
+ > >
791
+ > > # Licenses of bundled types
792
+ > >
793
+ > > The published @visulima/error artifact additionally contains code with the following licenses:
794
+ > > Apache-2.0
795
+ > >
796
+ > > # Bundled types:
797
+ > >
798
+ > > ## ai
799
+ > >
800
+ > > License: Apache-2.0
801
+ > > Repository: https://github.com/vercel/ai
802
+ > >
803
+ > > > Copyright 2023 Vercel, Inc.
804
+ > > >
805
+ > > > Licensed under the Apache License, Version 2.0 (the "License");
806
+ > > > you may not use this file except in compliance with the License.
807
+ > > > You may obtain a copy of the License at
808
+ > > >
809
+ > > > http://www.apache.org/licenses/LICENSE-2.0
810
+ > > >
811
+ > > > Unless required by applicable law or agreed to in writing, software
812
+ > > > distributed under the License is distributed on an "AS IS" BASIS,
813
+ > > > WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
814
+ > > > See the License for the specific language governing permissions and
815
+ > > > limitations under the License.
816
+ >
817
+ > ---
818
+ >
819
+ > ## unstorage
820
+ >
821
+ > License: MIT
822
+ > Repository: unjs/unstorage
823
+ >
824
+ > > MIT License
825
+ > >
826
+ > > Copyright (c) Pooya Parsa <pooya@pi0.io>
827
+ > >
828
+ > > Permission is hereby granted, free of charge, to any person obtaining a copy
829
+ > > of this software and associated documentation files (the "Software"), to deal
830
+ > > in the Software without restriction, including without limitation the rights
831
+ > > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
832
+ > > copies of the Software, and to permit persons to whom the Software is
833
+ > > furnished to do so, subject to the following conditions:
834
+ > >
835
+ > > The above copyright notice and this permission notice shall be included in all
836
+ > > copies or substantial portions of the Software.
837
+ > >
838
+ > > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
839
+ > > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
840
+ > > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
841
+ > > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
842
+ > > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
843
+ > > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
844
+ > > SOFTWARE.
845
+
846
+ ---------------------------------------
847
+
408
848
  ## unstorage
409
849
  License: MIT
410
850
  Repository: unjs/unstorage
package/README.md CHANGED
@@ -56,6 +56,23 @@ pnpm add @visulima/notification
56
56
 
57
57
  ## Usage
58
58
 
59
+ > **Every provider is `fetch`-based** — no per-provider SDK to install, and it runs on Node, Cloudflare Workers, Vercel
60
+ > Edge, Deno and Bun. Optional peers are only needed by specific subpaths (e.g. `liquidjs` for `./template/liquid`,
61
+ > `@visulima/workflow` for `./workflow`) — see the [install table](https://visulima.com/docs/packages/notification/installation#optional-peer-dependencies).
62
+ > Beyond sending, the package ships [workflows](https://visulima.com/docs/packages/notification/workflow),
63
+ > [digest](https://visulima.com/docs/packages/notification/digest),
64
+ > [layouts](https://visulima.com/docs/packages/notification/layouts) and
65
+ > [i18n](https://visulima.com/docs/packages/notification/i18n).
66
+
67
+ ### Quick send (one channel, no setup)
68
+
69
+ ```typescript
70
+ import { send } from "@visulima/notification";
71
+ import { twilioProvider } from "@visulima/notification/providers/twilio";
72
+
73
+ await send("sms", twilioProvider({ accountSid: "AC…", authToken: "…", from: "+15555550100" }), { to: "+15555550100", text: "Your code is 123" });
74
+ ```
75
+
59
76
  ### Multi-channel send
60
77
 
61
78
  ```typescript
@@ -0,0 +1,128 @@
1
+ import { M as MaybePromise, D as Duration } from "../packem_shared/types.d-C1GoEAlI.d-Bv3grfyD.js";
2
+ import '@standard-schema/spec';
3
+ /** A single event buffered inside a digest window. */
4
+ interface DigestEvent<PayloadT> {
5
+ /** Stable id of this event within the window. */
6
+ id: string;
7
+ /** The event payload. */
8
+ payload: PayloadT;
9
+ /** Epoch ms the event was added. */
10
+ time: number;
11
+ }
12
+ /** An open digest window: the events collected so far for a key, and when it closes. */
13
+ interface DigestWindow<PayloadT> {
14
+ events: DigestEvent<PayloadT>[];
15
+ key: string;
16
+ /** Epoch ms at which the window is due to flush. */
17
+ wakeAt: number;
18
+ }
19
+ /**
20
+ * Durability contract for `createDigester`. Implementations own the buffered
21
+ * windows and the "what is due" query; the digester stays storage-agnostic.
22
+ *
23
+ * `read` and `remove` are separate (rather than a single destructive `drain`) so
24
+ * the digester can flush a window's events *before* removing it — a failed flush
25
+ * leaves the window in place to be retried (at-least-once).
26
+ */
27
+ interface DigestStore<PayloadT> {
28
+ /**
29
+ * Append an event to the window for `key`, opening it (with `wakeAt`) if absent.
30
+ * The window's `wakeAt` is fixed when it opens — later events do not extend it.
31
+ * @returns `true` if this call opened a new window.
32
+ */
33
+ append: (key: string, event: DigestEvent<PayloadT>, wakeAt: number) => Promise<boolean>;
34
+ /** Keys whose window `wakeAt` is at or before `now`, up to `limit`. */
35
+ due: (now: number, limit: number) => Promise<string[]>;
36
+ /** Return the window for `key` without removing it, or `undefined` if none. */
37
+ read: (key: string) => Promise<DigestWindow<PayloadT> | undefined>;
38
+ /** Permanently remove the window for `key`. */
39
+ remove: (key: string) => Promise<void>;
40
+ }
41
+ /** Options for `createDigester`. */
42
+ interface DigesterOptions<PayloadT> {
43
+ /** Group events into windows by this key (e.g. `subscriberId` or `subscriberId:postId`). */
44
+ key: (event: PayloadT) => string;
45
+ /**
46
+ * Called once per window when it closes, with every event collected. Delivery is
47
+ * **at-least-once**: the window is removed only after `onFlush` resolves, so a
48
+ * throwing `onFlush` is retried on the next sweep (and may run again if removal
49
+ * later fails). Make it idempotent.
50
+ */
51
+ onFlush: (events: DigestEvent<PayloadT>[], key: string) => MaybePromise<void>;
52
+ /** The durable store; defaults to an in-memory store. */
53
+ store?: DigestStore<PayloadT>;
54
+ /** How long a window stays open from its first event (a {@link Duration} or a per-event function). */
55
+ window: ((event: PayloadT) => Duration) | Duration;
56
+ }
57
+ /** Aggregates many events into windowed batches, flushing each as one notification. */
58
+ interface Digester<PayloadT> {
59
+ /** Buffer an event; returns `true` if it opened a new window for its key. */
60
+ add: (event: PayloadT) => Promise<boolean>;
61
+ /** Flush every window whose wake-at has passed; returns the number flushed. */
62
+ sweep: (now?: number, limit?: number) => Promise<number>;
63
+ }
64
+ /**
65
+ * Create a {@link Digester} that batches events into time/cron windows keyed by
66
+ * `options.key`, flushing each closed window once via `options.onFlush` — turning
67
+ * a burst of N events into a single notification.
68
+ *
69
+ * Windows close on a poll: call {@link Digester.sweep} from a cron job, a
70
+ * Cloudflare alarm, or any timer (alongside a workflow runtime's `sweep`).
71
+ * @param options Key function, window duration, flush handler and optional store.
72
+ * @returns A {@link Digester}.
73
+ * @example
74
+ * ```ts
75
+ * const digester = createDigester<{ subscriberId: string; postId: string }>({
76
+ * key: (event) => `${event.subscriberId}:${event.postId}`,
77
+ * window: { amount: 10, unit: "minutes" },
78
+ * onFlush: (events) => runtime.trigger(summaryWorkflow, { count: events.length, events }),
79
+ * });
80
+ *
81
+ * await digester.add({ subscriberId: "u1", postId: "p1" }); // opens a 10-minute window
82
+ * // …later, on a timer:
83
+ * await digester.sweep();
84
+ * ```
85
+ */
86
+ declare const createDigester: <PayloadT>(options: DigesterOptions<PayloadT>) => Digester<PayloadT>;
87
+ /**
88
+ * In-process {@link DigestStore}. Ideal for tests and single-instance apps; swap
89
+ * for the unstorage adapter for durability across restarts and instances.
90
+ */
91
+ declare class MemoryDigestStore<PayloadT> implements DigestStore<PayloadT> {
92
+ #private;
93
+ append(key: string, event: DigestEvent<PayloadT>, wakeAt: number): Promise<boolean>;
94
+ read(key: string): Promise<DigestWindow<PayloadT> | undefined>;
95
+ remove(key: string): Promise<void>;
96
+ due(now: number, limit: number): Promise<string[]>;
97
+ }
98
+ /**
99
+ * Minimal structural view of an [unstorage](https://unstorage.unjs.io) instance,
100
+ * declared locally so the package needs no hard `unstorage` dependency.
101
+ */
102
+ interface UnstorageLike {
103
+ getItem: <T = unknown>(key: string) => Promise<T | null>;
104
+ getKeys: (base?: string) => Promise<string[]>;
105
+ removeItem: (key: string) => Promise<void>;
106
+ setItem: (key: string, value: unknown) => Promise<void>;
107
+ }
108
+ /**
109
+ * {@link DigestStore} backed by [unstorage](https://unstorage.unjs.io), for durable,
110
+ * edge-friendly digest windows over any unstorage driver.
111
+ *
112
+ * Each window is a single self-contained document keyed by `digest:w:` plus the
113
+ * digest key; there is no shared index, so concurrent `add`s for different keys cannot
114
+ * lose-update each other. `due` scans the window documents under the prefix — fine for the
115
+ * transient, bounded set of open windows. Individual reads/writes are still not
116
+ * transactional, so concurrent writers on the *same* key (or a sweep racing an
117
+ * `add` for that key) can drop an event or double-flush; for that level of
118
+ * contention prefer a store with atomic guarantees, or sweep from one instance.
119
+ */
120
+ declare class UnstorageDigestStore<PayloadT> implements DigestStore<PayloadT> {
121
+ #private;
122
+ constructor(storage: UnstorageLike);
123
+ append(key: string, event: DigestEvent<PayloadT>, wakeAt: number): Promise<boolean>;
124
+ read(key: string): Promise<DigestWindow<PayloadT> | undefined>;
125
+ remove(key: string): Promise<void>;
126
+ due(now: number, limit: number): Promise<string[]>;
127
+ }
128
+ export { type DigestEvent, type DigestStore, type DigestWindow, type Digester, type DigesterOptions, MemoryDigestStore, UnstorageDigestStore, createDigester };
@@ -0,0 +1 @@
1
+ import{default as r}from"../packem_shared/createDigester-vMNPejGF.js";import{default as a}from"../packem_shared/MemoryDigestStore-Bw-sE5MI.js";import{default as f}from"../packem_shared/UnstorageDigestStore-QAcO3Vb_.js";export{a as MemoryDigestStore,f as UnstorageDigestStore,r as createDigester};
@@ -0,0 +1,42 @@
1
+ /** Per-locale map of message key → ICU MessageFormat string. */
2
+ type LocaleMessages = Record<string, string>;
3
+ /** Options for {@link createTranslator}. */
4
+ interface TranslatorOptions {
5
+ /** The locale to fall back to when a key is missing for the requested locale. */
6
+ fallbackLocale: string;
7
+ /** `locale → (key → ICU message)`. Locales may be `en`, `de-DE`, `de_DE`, … */
8
+ messages: Record<string, LocaleMessages>;
9
+ /** Called when a key is missing in every candidate locale; defaults to returning the key. */
10
+ onMissing?: (locale: string, key: string) => string;
11
+ }
12
+ /** Resolves and formats localized messages with ICU MessageFormat. */
13
+ interface Translator {
14
+ /** Whether `key` resolves for `locale` (via region → language → fallback). */
15
+ has: (locale: string, key: string) => boolean;
16
+ /** Format `key` for `locale`, interpolating `values` (plurals/select via ICU). */
17
+ translate: (locale: string, key: string, values?: Record<string, unknown>) => string;
18
+ }
19
+ /**
20
+ * Create a {@link Translator} over a set of locale message tables, using
21
+ * [ICU MessageFormat](https://formatjs.io/docs/core-concepts/icu-syntax/) (plurals,
22
+ * select, number/date) via `intl-messageformat`, with region → language → fallback
23
+ * resolution driven by the subscriber's locale. Edge-safe and dependency-light
24
+ * (requires the `intl-messageformat` peer).
25
+ * @param options Message tables, fallback locale, and an optional missing-key handler.
26
+ * @returns A {@link Translator}.
27
+ * @example
28
+ * ```ts
29
+ * const t = createTranslator({
30
+ * fallbackLocale: "en",
31
+ * messages: {
32
+ * en: { likes: "{count, plural, one {# like} other {# likes}}" },
33
+ * de_DE: { likes: "{count, plural, one {# Like} other {# Likes}}" },
34
+ * },
35
+ * });
36
+ *
37
+ * t.translate("de_DE", "likes", { count: 3 }); // "3 Likes"
38
+ * t.translate("fr", "likes", { count: 1 }); // falls back to en → "1 like"
39
+ * ```
40
+ */
41
+ declare const createTranslator: (options: TranslatorOptions) => Translator;
42
+ export { type Translator, type TranslatorOptions, createTranslator };
@@ -0,0 +1 @@
1
+ import{default as e}from"../packem_shared/createTranslator-CY91C2eS.js";export{e as createTranslator};
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { type M as Middleware, type S as SendContext, type a as SendFunction, c as composeMiddleware } from "./packem_shared/types.d-CximoA8M.js";
2
- import { C as ChannelPayloadMap, a as NotificationMessage } from "./packem_shared/notification.d-BBLyVuqi.js";
3
- export { N as Notification, type b as NotificationProviders, type S as SendManyOptions, c as createNotification } from "./packem_shared/notification.d-BBLyVuqi.js";
2
+ import { C as ChannelPayloadMap, a as NotificationMessage } from "./packem_shared/notification.d-DFCVTods.js";
3
+ export { N as Notification, type b as NotificationProviders, type S as SendManyOptions, c as createNotification, s as send } from "./packem_shared/notification.d-DFCVTods.js";
4
4
  export { type P as Provider, type a as ProviderFactory, d as defineProvider } from "./packem_shared/provider.d-BQAGriz6.js";
5
5
  export type { d as BaseConfig, B as BaseNotificationPayload, C as ChannelType, b as ChatPayload, f as EmailAddressLike, E as EmailChannelPayload, g as FailureReceipt, F as FeatureFlags, I as InAppPayload, M as MaybePromise, N as NotificationEvent, e as NotificationEventType, h as NotificationPayload, a as NotificationResult, P as PushPayload, c as Receipt, i as RecipientResult, R as Result, S as SmsPayload, j as SuccessReceipt, W as WebhookPayload } from "./packem_shared/types.d-GUzX1jTk.js";
6
6
  interface ErrorProperties {
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{default as r}from"./packem_shared/NotificationError-DVfMJQ1u.js";import{default as t}from"./packem_shared/RequiredOptionError-CuL42XRq.js";import{composeMiddleware as f}from"./packem_shared/composeMiddleware-D6dR8DUS.js";import{Notification as d,createNotification as p}from"./packem_shared/Notification-D1YeYxrQ.js";import{NotificationMessageBuilder as n,createNotificationMessage as s}from"./packem_shared/NotificationMessageBuilder-Br_aZusY.js";import{defineProvider as N}from"./packem_shared/defineProvider-C9q-7iwt.js";export{d as Notification,r as NotificationError,n as NotificationMessageBuilder,t as RequiredOptionError,f as composeMiddleware,p as createNotification,s as createNotificationMessage,N as defineProvider};
1
+ import{default as r}from"./packem_shared/NotificationError-DVfMJQ1u.js";import{default as t}from"./packem_shared/RequiredOptionError-CuL42XRq.js";import{composeMiddleware as f}from"./packem_shared/composeMiddleware-D6dR8DUS.js";import{Notification as c,createNotification as n,send as p}from"./packem_shared/Notification-B22tuJPy.js";import{NotificationMessageBuilder as m,createNotificationMessage as x}from"./packem_shared/NotificationMessageBuilder-Br_aZusY.js";import{defineProvider as l}from"./packem_shared/defineProvider-C9q-7iwt.js";export{c as Notification,r as NotificationError,m as NotificationMessageBuilder,t as RequiredOptionError,f as composeMiddleware,n as createNotification,x as createNotificationMessage,l as defineProvider,p as send};
@@ -0,0 +1,45 @@
1
+ import { T as TemplateRenderer } from "../packem_shared/types.d-td-F2Oh0.js";
2
+ /** Options for {@link defineLayout}. */
3
+ interface LayoutOptions {
4
+ /**
5
+ * The renderer used for the layout template. Defaults to the zero-dependency
6
+ * `renderString` (`{{var}}`); pass the Handlebars/Liquid renderer for logic.
7
+ */
8
+ render?: TemplateRenderer;
9
+ /** Name of the slot variable the content is injected as. Defaults to `"content"`. */
10
+ slot?: string;
11
+ /**
12
+ * The layout template. Must contain the slot placeholder (default `{{content}}`).
13
+ * With the Handlebars renderer use the unescaped `{{{content}}}` so the already-
14
+ * rendered HTML body is not double-escaped.
15
+ */
16
+ template: string;
17
+ }
18
+ /** A reusable layout: renders a content string into its slot, then renders the wrapper. */
19
+ interface Layout {
20
+ /**
21
+ * Wrap `content` in the layout.
22
+ * @param content The already-rendered body to inject into the slot.
23
+ * @param variables Extra variables available to the layout template (e.g. `unsubscribeUrl`).
24
+ */
25
+ render: (content: string, variables?: Record<string, unknown>) => Promise<string>;
26
+ }
27
+ /**
28
+ * Define a reusable content layout — shared chrome (header/footer/brand) with a
29
+ * `{{content}}` slot the per-notification body drops into. Code-first and engine-
30
+ * agnostic: it reuses the package's template renderers, so it stays edge-safe with
31
+ * the default `renderString`.
32
+ * @param options The layout template, optional renderer and slot name.
33
+ * @returns A {@link Layout} whose `render(content, vars)` returns the wrapped string.
34
+ * @example
35
+ * ```ts
36
+ * const branded = defineLayout({
37
+ * template: `<table><tr><td>{{content}}</td></tr><tr><td><a href="{{unsubscribeUrl}}">unsubscribe</a></td></tr></table>`,
38
+ * });
39
+ *
40
+ * const html = await branded.render("<p>Welcome!</p>", { unsubscribeUrl: "https://app/u/123" });
41
+ * await notify.sendToChannel("email", { from, to, subject: "Hi", html });
42
+ * ```
43
+ */
44
+ declare const defineLayout: (options: LayoutOptions) => Layout;
45
+ export { type Layout, type LayoutOptions, defineLayout };
@@ -0,0 +1 @@
1
+ import{default as f}from"../packem_shared/defineLayout-73TNxYPq.js";export{f as defineLayout};
@@ -0,0 +1 @@
1
+ class a{#e=new Map;append(e,s,r){const t=this.#e.get(e);return t===void 0?(this.#e.set(e,{events:[s],key:e,wakeAt:r}),Promise.resolve(!0)):(t.events.push(s),Promise.resolve(!1))}read(e){return Promise.resolve(this.#e.get(e))}remove(e){return this.#e.delete(e),Promise.resolve()}due(e,s){const r=[...this.#e.values()].filter(t=>t.wakeAt<=e).toSorted((t,o)=>t.wakeAt-o.wakeAt).slice(0,s).map(t=>t.key);return Promise.resolve(r)}}export{a as default};
@@ -0,0 +1 @@
1
+ import h from"./NotificationError-DVfMJQ1u.js";import{composeMiddleware as u}from"./composeMiddleware-D6dR8DUS.js";const c=(n,s,t)=>{if(n.success&&n.data)return{channel:s,messageId:n.data.messageId,provider:n.data.provider??t,response:n.data.response,successful:!0,timestamp:n.data.timestamp};const{error:e}=n;let i;return e instanceof Error?i=e.message:e==null?i="Unknown error":typeof e=="string"?i=e:i=JSON.stringify(e),{channel:s,errorMessages:[i],provider:t,successful:!1}};class d{#s;#e=[];#t=new Set;#n;constructor(s){this.#s=s}use(s){return this.#e.push(s),this}setLogger(s){return this.#n=s,this}getProvider(s){return this.#s[s]}async sendToChannel(s,t){const e=this.#s[s];if(!e)return{channel:s,errorMessages:[`No provider registered for channel "${s}"`],successful:!1};try{await this.#i(e)}catch(r){return c({error:r,success:!1},s,e.id)}const i=async r=>e.send(r.payload),a=await u(this.#e,i)({channel:s,payload:t,provider:e.id});return c(a,s,e.id)}async send(s){const t=Object.keys(s);if(t.length===0)throw new h("notification","send() called with an empty message");return Promise.all(t.map(async e=>this.sendToChannel(e,s[e])))}async*sendMany(s,t={}){const e=Math.max(1,t.concurrency??10),i=[...s];let a=0;for(;a<i.length;){const r=i.slice(a,a+e);a+=e;const l=await Promise.all(r.map(async o=>this.send(o)));for(const o of l)yield o}}async initialize(){await Promise.all(Object.values(this.#s).map(async s=>this.#i(s)))}async shutdown(){await Promise.all(Object.values(this.#s).map(async s=>{const{shutdown:t}=s;t&&await t()}))}async#i(s){this.#t.has(s.id)||(await s.initialize(),this.#n?.debug(`[@visulima/notification] initialized provider "${s.id}"`),this.#t.add(s.id))}}const p=n=>new d(n),y=async(n,s,t)=>new d({[n]:s}).sendToChannel(n,t);export{d as Notification,p as createNotification,y as send};
@@ -0,0 +1 @@
1
+ const a="digest:w:";class o{#t;constructor(t){this.#t=t}async append(t,s,i){const e=await this.#t.getItem(a+t);return e!==null?(e.events.push(s),await this.#t.setItem(a+t,e),!1):(await this.#t.setItem(a+t,{events:[s],key:t,wakeAt:i}),!0)}async read(t){return await this.#t.getItem(a+t)??void 0}async remove(t){await this.#t.removeItem(a+t)}async due(t,s){const i=await this.#t.getKeys(a);return(await Promise.all(i.map(async e=>this.#t.getItem(e)))).filter(e=>e!==null&&e.wakeAt<=t).toSorted((e,n)=>e.wakeAt-n.wakeAt).slice(0,s).map(e=>e.key)}}export{o as default};
@@ -0,0 +1 @@
1
+ import r from"./NotificationError-DVfMJQ1u.js";import u from"./MemoryDigestStore-Bw-sE5MI.js";const d={days:864e5,hours:36e5,milliseconds:1,minutes:6e4,ms:1,seconds:1e3,weeks:6048e5},c=async(e,t)=>{if(typeof e=="number"){if(!Number.isFinite(e))throw new r("digest",`Window must be a finite number of milliseconds. Received: ${String(e)}.`);return t+Math.max(0,e)}if("cron"in e){let i;try{i=await import("croner")}catch(o){throw new r("digest","croner is not installed (required for cron windows). Please install it: pnpm add croner",{cause:o})}const n=new i.Cron(e.cron,e.tz?{timezone:e.tz}:{}).nextRun(new Date(t));if(n===null)throw new r("digest",`Cron expression "${e.cron}" has no future occurrence.`);return n.getTime()}if(!Number.isFinite(e.amount))throw new r("digest",`Window amount must be a finite number. Received: ${String(e.amount)}.`);return t+Math.max(0,e.amount)*d[e.unit]},m=()=>globalThis.crypto.randomUUID(),p=e=>{const t=e.store??new u;return{add:async i=>{const n=e.key(i),o=typeof e.window=="function"?e.window(i):e.window,s={id:m(),payload:i,time:Date.now()};return t.append(n,s,await c(o,Date.now()))},sweep:async(i=Date.now(),n=100)=>{if(!Number.isInteger(n)||n<=0)throw new r("digest",`sweep limit must be a positive integer. Received: ${String(n)}.`);const o=await t.due(i,n);let s=0;for(const w of o){const a=await t.read(w);a!==void 0&&a.events.length>0&&(await e.onFlush(a.events,w),s+=1),a!==void 0&&await t.remove(w)}return s}}};export{p as default};
@@ -0,0 +1 @@
1
+ import{defineWorkflow as y}from"@visulima/workflow";import c from"./NotificationError-DVfMJQ1u.js";const m=(d,p)=>{const l=e=>{const n=async(a,s,o,r)=>e.step(s,async()=>{if(r?.skip&&await r.skip({payload:e.payload}))return;const u=await o({payload:e.payload}),t=await d.sendToChannel(a,u);if(r?.throwOnFailure&&!t.successful)throw new c(a,t.errorMessages.join("; "));return t}),i={chat:(a,s,o)=>n("chat",a,s,o),custom:(a,s)=>e.step(a,s),delay:(a,s)=>e.sleep(a,s),email:(a,s,o)=>n("email",a,s,o),inApp:(a,s,o)=>n("inapp",a,s,o),push:(a,s,o)=>n("push",a,s,o),sms:(a,s,o)=>n("sms",a,s,o),webhook:(a,s,o)=>n("webhook",a,s,o)};return p.run({payload:e.payload,runId:e.runId,step:i})};return y({id:p.id,payload:p.payload,run:l,tags:p.tags})};export{m as default};
@@ -0,0 +1 @@
1
+ import{IntlMessageFormat as f}from"intl-messageformat";import g from"./NotificationError-DVfMJQ1u.js";const u=/[-_]/u,m=e=>e.split(u)[0]??e,$=e=>{const n=new Map,c=(t,o)=>{for(const s of[t,m(t),e.fallbackLocale,m(e.fallbackLocale)]){const a=e.messages[s]?.[o];if(a!==void 0)return{locale:s,message:a}}};return{has:(t,o)=>c(t,o)!==void 0,translate:(t,o,s)=>{const a=c(t,o);if(a===void 0)return e.onMissing?e.onMissing(t,o):o;const r=`${a.locale}\0${o}`;let l=n.get(r);l===void 0&&(l=new f(a.message,a.locale.replaceAll("_","-")),n.set(r,l));try{return String(l.format(s))}catch(i){throw new g("i18n",`Failed to format message "${o}" for locale "${a.locale}": ${i.message}`,{cause:i})}}}};export{$ as default};
@@ -0,0 +1 @@
1
+ import s from"../template-engines/string.js";const c=r=>{const t=r.render??s,e=r.slot??"content";return{render:async(n,o={})=>t(r.template,{...o,[e]:n})}};export{c as default};
@@ -84,4 +84,21 @@ declare class Notification {
84
84
  * @returns A configured notification facade.
85
85
  */
86
86
  declare const createNotification: (providers: NotificationProviders) => Notification;
87
- export { ChannelPayloadMap as C, Notification as N, SendManyOptions as S, NotificationMessage as a, NotificationProviders as b, createNotification as c };
87
+ /**
88
+ * One-shot send through a single provider — the quickest path for a single-channel
89
+ * send without wiring a {@link Notification} facade. For multiple channels,
90
+ * middleware, routing or reuse, use {@link createNotification}.
91
+ * @param channel The channel to deliver on.
92
+ * @param provider The provider to deliver through.
93
+ * @param payload The channel-specific payload.
94
+ * @returns The send {@link Receipt}.
95
+ * @example
96
+ * ```ts
97
+ * import { send } from "@visulima/notification";
98
+ * import { twilioProvider } from "@visulima/notification/providers/twilio";
99
+ *
100
+ * await send("sms", twilioProvider({ accountSid, authToken, from }), { to: "+15555550100", text: "Hi" });
101
+ * ```
102
+ */
103
+ declare const send: <ChannelT extends ChannelType>(channel: ChannelT, provider: Provider<unknown, ChannelPayloadMap[ChannelT]>, payload: ChannelPayloadMap[ChannelT]) => Promise<Receipt>;
104
+ export { ChannelPayloadMap as C, Notification as N, SendManyOptions as S, NotificationMessage as a, NotificationProviders as b, createNotification as c, send as s };
@@ -1,4 +1,4 @@
1
- import { a as NotificationMessage } from "./notification.d-BBLyVuqi.js";
1
+ import { a as NotificationMessage } from "./notification.d-DFCVTods.js";
2
2
  /**
3
3
  * A queued notification job.
4
4
  */
@@ -0,0 +1,83 @@
1
+ import { StandardSchemaV1 } from '@standard-schema/spec';
2
+ /** A value that may be returned directly or as a promise. */
3
+ type MaybePromise<T> = Promise<T> | T;
4
+ /**
5
+ * A human-friendly duration. Either a raw millisecond count, a structured
6
+ * `{ amount, unit }` pair, or a `{ cron }` expression resolved against "now".
7
+ */
8
+ type Duration = number | {
9
+ amount: number;
10
+ unit: DurationUnit;
11
+ } | {
12
+ cron: string;
13
+ tz?: string;
14
+ };
15
+ /** Units accepted by the structured {@link Duration} form. */
16
+ type DurationUnit = "days" | "hours" | "milliseconds" | "minutes" | "ms" | "seconds" | "weeks";
17
+ /** A JSON-serialisable snapshot of an `Error`, persisted in run history. */
18
+
19
+ /**
20
+ * The context handed to a workflow body. Every durable operation goes through
21
+ * `ctx` so the engine can record, skip and resume it deterministically.
22
+ *
23
+ * IMPORTANT: any side effect that must run *exactly once* has to be wrapped in
24
+ * {@link RunContext.step}. Code outside `step`/`sleep`/`waitForEvent` re-executes
25
+ * on every replay.
26
+ */
27
+ interface RunContext<PayloadT = unknown> {
28
+ /** The validated trigger payload. */
29
+ readonly payload: PayloadT;
30
+ /** The id of the current run. */
31
+ readonly runId: string;
32
+ /**
33
+ * Durably pause the run for a duration, then resume.
34
+ * @param id A stable, unique id for this sleep.
35
+ * @param duration How long to pause.
36
+ */
37
+ sleep: (id: string, duration: Duration) => Promise<void>;
38
+ /**
39
+ * Run a side effect exactly once. The result is recorded; on replay the
40
+ * recorded value is returned without re-executing the function.
41
+ * @param id A stable, unique id for this step within the workflow.
42
+ * @param function_ The side effect to run.
43
+ */
44
+ step: <T>(id: string, function_: () => MaybePromise<T>) => Promise<T>;
45
+ /**
46
+ * Durably suspend until an external event is delivered via
47
+ * `runtime.signal(runId, name, payload)`, or the optional timeout elapses.
48
+ * @param id A stable, unique id for this wait.
49
+ * @param name The event name to wait for.
50
+ * @param options Optional settings for the wait.
51
+ * @param options.timeout How long to wait before resolving to `undefined`.
52
+ * @returns The signalled payload, or `undefined` on timeout.
53
+ */
54
+ waitForEvent: <T = unknown>(id: string, name: string, options?: {
55
+ timeout?: Duration;
56
+ }) => Promise<T | undefined>;
57
+ }
58
+ /** The body of a workflow: an async function driven by {@link RunContext}. */
59
+ type WorkflowRun<PayloadT, OutputT> = (context: RunContext<PayloadT>) => MaybePromise<OutputT>;
60
+ /** Options accepted by `defineWorkflow`. */
61
+ interface WorkflowConfig<PayloadT, OutputT> {
62
+ /** Unique id of the workflow, used as the namespace for run ids and storage. */
63
+ id: string;
64
+ /** Optional Standard Schema validating (and typing) the trigger payload. */
65
+ payload?: StandardSchemaV1<unknown, PayloadT>;
66
+ /** The workflow body. */
67
+ run: WorkflowRun<PayloadT, OutputT>;
68
+ /** Optional free-form tags for routing/observability. */
69
+ tags?: string[];
70
+ }
71
+ /** A defined workflow: the config plus the validated payload-schema accessor. */
72
+ interface WorkflowDefinition<PayloadT = unknown, OutputT = unknown> extends WorkflowConfig<PayloadT, OutputT> {
73
+ /** Validate (and parse) an unknown input against the payload schema. */
74
+ parsePayload: (input: unknown) => Promise<PayloadT>;
75
+ /** Brand marking this object as a defined workflow. */
76
+ readonly [WORKFLOW_BRAND]: true;
77
+ }
78
+ /**
79
+ * The persisted form of a run. `snapshot` is the opaque XState persisted
80
+ * snapshot; `status`, `wakeAt` and `eventName` are denormalised so a store can
81
+ * answer {@link WorkflowStore.due} and signal lookups without parsing it.
82
+ */
83
+ export { Duration as D, MaybePromise as M, WorkflowConfig as W, WorkflowDefinition as a };
@@ -1,6 +1,6 @@
1
1
  import { ChannelGate } from "../routing/index.js";
2
2
  import { C as ChannelType } from "../packem_shared/types.d-GUzX1jTk.js";
3
- import "../packem_shared/notification.d-BBLyVuqi.js";
3
+ import "../packem_shared/notification.d-DFCVTods.js";
4
4
  import "../packem_shared/types.d-CximoA8M.js";
5
5
  import "../packem_shared/provider.d-BQAGriz6.js";
6
6
  /**
@@ -1,5 +1,5 @@
1
- import { a as NotificationMessage } from "../packem_shared/notification.d-BBLyVuqi.js";
2
- import { Q as QueueJob, N as NotificationQueue } from "../packem_shared/types.d-DyM9JjL7.js";
1
+ import { a as NotificationMessage } from "../packem_shared/notification.d-DFCVTods.js";
2
+ import { Q as QueueJob, N as NotificationQueue } from "../packem_shared/types.d-67TogK4k.js";
3
3
  import "../packem_shared/types.d-CximoA8M.js";
4
4
  import "../packem_shared/types.d-GUzX1jTk.js";
5
5
  import "../packem_shared/provider.d-BQAGriz6.js";
@@ -1,6 +1,6 @@
1
1
  export { BullMqQueue, createBullMqQueue } from "./bullmq-queue.js";
2
- import { a as NotificationMessage, N as Notification } from "../packem_shared/notification.d-BBLyVuqi.js";
3
- import { Q as QueueJob, N as NotificationQueue } from "../packem_shared/types.d-DyM9JjL7.js";
2
+ import { a as NotificationMessage, N as Notification } from "../packem_shared/notification.d-DFCVTods.js";
3
+ import { Q as QueueJob, N as NotificationQueue } from "../packem_shared/types.d-67TogK4k.js";
4
4
  export { PgBossQueue, type PgBossQueueOptions, createPgBossQueue } from "./pg-boss-queue.js";
5
5
  export { SqsQueue, type SqsQueueOptions, createSqsQueue } from "./sqs-queue.js";
6
6
  import { c as Receipt } from "../packem_shared/types.d-GUzX1jTk.js";
@@ -1,5 +1,5 @@
1
- import { a as NotificationMessage } from "../packem_shared/notification.d-BBLyVuqi.js";
2
- import { Q as QueueJob, N as NotificationQueue } from "../packem_shared/types.d-DyM9JjL7.js";
1
+ import { a as NotificationMessage } from "../packem_shared/notification.d-DFCVTods.js";
2
+ import { Q as QueueJob, N as NotificationQueue } from "../packem_shared/types.d-67TogK4k.js";
3
3
  import "../packem_shared/types.d-CximoA8M.js";
4
4
  import "../packem_shared/types.d-GUzX1jTk.js";
5
5
  import "../packem_shared/provider.d-BQAGriz6.js";
@@ -1,5 +1,5 @@
1
- import { a as NotificationMessage } from "../packem_shared/notification.d-BBLyVuqi.js";
2
- import { Q as QueueJob, N as NotificationQueue } from "../packem_shared/types.d-DyM9JjL7.js";
1
+ import { a as NotificationMessage } from "../packem_shared/notification.d-DFCVTods.js";
2
+ import { Q as QueueJob, N as NotificationQueue } from "../packem_shared/types.d-67TogK4k.js";
3
3
  import "../packem_shared/types.d-CximoA8M.js";
4
4
  import "../packem_shared/types.d-GUzX1jTk.js";
5
5
  import "../packem_shared/provider.d-BQAGriz6.js";
@@ -1,6 +1,6 @@
1
1
  import { S as Storage } from "../packem_shared/unstorage.DqlWKU2I.d-CogP3Etw.js";
2
- import { a as NotificationMessage } from "../packem_shared/notification.d-BBLyVuqi.js";
3
- import { Q as QueueJob, N as NotificationQueue } from "../packem_shared/types.d-DyM9JjL7.js";
2
+ import { a as NotificationMessage } from "../packem_shared/notification.d-DFCVTods.js";
3
+ import { Q as QueueJob, N as NotificationQueue } from "../packem_shared/types.d-67TogK4k.js";
4
4
  import "../packem_shared/types.d-CximoA8M.js";
5
5
  import "../packem_shared/types.d-GUzX1jTk.js";
6
6
  import "../packem_shared/provider.d-BQAGriz6.js";
@@ -1,4 +1,4 @@
1
- import { C as ChannelPayloadMap, N as Notification, a as NotificationMessage } from "../packem_shared/notification.d-BBLyVuqi.js";
1
+ import { C as ChannelPayloadMap, N as Notification, a as NotificationMessage } from "../packem_shared/notification.d-DFCVTods.js";
2
2
  import { C as ChannelType, M as MaybePromise, c as Receipt } from "../packem_shared/types.d-GUzX1jTk.js";
3
3
  import "../packem_shared/types.d-CximoA8M.js";
4
4
  import "../packem_shared/provider.d-BQAGriz6.js";
@@ -0,0 +1,83 @@
1
+ import { M as MaybePromise, D as Duration, W as WorkflowConfig, a as WorkflowDefinition } from "../packem_shared/types.d-C1GoEAlI.d-Bv3grfyD.js";
2
+ import '@standard-schema/spec';
3
+ import { C as ChannelPayloadMap, N as Notification } from "../packem_shared/notification.d-DFCVTods.js";
4
+ import { C as ChannelType, c as Receipt } from "../packem_shared/types.d-GUzX1jTk.js";
5
+ import "../packem_shared/types.d-CximoA8M.js";
6
+ import "../packem_shared/provider.d-BQAGriz6.js";
7
+ /** The context handed to a step resolver / skip predicate. */
8
+ interface StepResolverContext<PayloadT> {
9
+ /** The validated trigger payload of the run. */
10
+ payload: PayloadT;
11
+ }
12
+ /** Resolves the payload sent by a channel step (or decides to skip it). */
13
+ type StepResolver<PayloadT, ResultT> = (context: StepResolverContext<PayloadT>) => MaybePromise<ResultT>;
14
+ /** Per-step options for a channel step. */
15
+ interface ChannelStepOptions<PayloadT> {
16
+ /** Skip the send when this returns `true`; the step records `undefined`. */
17
+ skip?: (context: StepResolverContext<PayloadT>) => MaybePromise<boolean>;
18
+ /**
19
+ * Treat a `FailureReceipt` as a thrown error so the run fails and the step
20
+ * re-runs on the next resume/sweep. Defaults to `false`.
21
+ */
22
+ throwOnFailure?: boolean;
23
+ }
24
+ /**
25
+ * A durable channel step. Resolves a channel payload and delivers it through the
26
+ * bound {@link Notification} facade exactly once; the receipt is recorded and
27
+ * returned on replay without re-sending. Returns `undefined` when `skip` matches.
28
+ *
29
+ * IMPORTANT: a delivery failure surfaces as a `FailureReceipt` (the facade does not
30
+ * throw), so by default a failed send is **recorded as a completed step and not
31
+ * retried** — inspect `receipt.successful`, or pass `throwOnFailure: true` to turn
32
+ * a failure into a thrown error that re-runs the step on the next activation.
33
+ */
34
+ type ChannelStep<ChannelT extends ChannelType, PayloadT> = (id: string, resolve: StepResolver<PayloadT, ChannelPayloadMap[ChannelT]>, options?: ChannelStepOptions<PayloadT>) => Promise<Receipt | undefined>;
35
+ /**
36
+ * The `step` object handed to a notification workflow body. Channel steps deliver
37
+ * exactly once; `delay` pauses durably; `custom` is an escape hatch to the raw
38
+ * engine step.
39
+ */
40
+ interface NotificationStep<PayloadT> {
41
+ chat: ChannelStep<"chat", PayloadT>;
42
+ custom: <T>(id: string, function_: () => MaybePromise<T>) => Promise<T>;
43
+ delay: (id: string, duration: Duration) => Promise<void>;
44
+ email: ChannelStep<"email", PayloadT>;
45
+ inApp: ChannelStep<"inapp", PayloadT>;
46
+ push: ChannelStep<"push", PayloadT>;
47
+ sms: ChannelStep<"sms", PayloadT>;
48
+ webhook: ChannelStep<"webhook", PayloadT>;
49
+ }
50
+ /** The body of a notification workflow, driven by {@link NotificationStep}. */
51
+ type NotificationWorkflowRun<PayloadT, OutputT> = (context: {
52
+ payload: PayloadT;
53
+ runId: string;
54
+ step: NotificationStep<PayloadT>;
55
+ }) => MaybePromise<OutputT>;
56
+ /** Config for {@link createNotificationWorkflow}: a {@link WorkflowConfig} whose `run` is notification-flavoured. */
57
+ interface NotificationWorkflowConfig<PayloadT, OutputT> extends Omit<WorkflowConfig<PayloadT, OutputT>, "run"> {
58
+ run: NotificationWorkflowRun<PayloadT, OutputT>;
59
+ }
60
+ /**
61
+ * Define a durable notification workflow on top of [`@visulima/workflow`](https://visulima.com/packages/workflow).
62
+ *
63
+ * The returned value is a plain `WorkflowDefinition`, so it plugs straight into a
64
+ * workflow runtime (`createRuntime({ workflows: [wf] })` → `runtime.trigger(wf, payload)`).
65
+ * Channel steps deliver through the bound `notification` facade exactly once.
66
+ * @param notification The multi-channel facade messages are delivered through.
67
+ * @param config The workflow id, optional payload schema, and notification-flavoured `run`.
68
+ * @returns A {@link WorkflowDefinition} ready to register/trigger on a runtime.
69
+ * @example
70
+ * ```ts
71
+ * const onComment = createNotificationWorkflow(notify, {
72
+ * id: "comment-posted",
73
+ * payload: z.object({ subscriberId: z.string(), author: z.string() }),
74
+ * run: async ({ step, payload }) => {
75
+ * await step.inApp("inbox", () => ({ to: payload.subscriberId, body: `${payload.author} commented` }));
76
+ * await step.delay("cooldown", { amount: 1, unit: "hours" });
77
+ * await step.email("nudge", () => ({ from: "x@y.com", to: payload.subscriberId, subject: "New comment", html: "<p>…</p>" }));
78
+ * },
79
+ * });
80
+ * ```
81
+ */
82
+ declare const createNotificationWorkflow: <PayloadT = unknown, OutputT = unknown>(notification: Notification, config: NotificationWorkflowConfig<PayloadT, OutputT>) => WorkflowDefinition<PayloadT, OutputT>;
83
+ export { type ChannelStep, type ChannelStepOptions, type NotificationStep, type NotificationWorkflowConfig, type NotificationWorkflowRun, type StepResolver, type StepResolverContext, createNotificationWorkflow };
@@ -0,0 +1 @@
1
+ import{default as a}from"../packem_shared/createNotificationWorkflow-B70OgeED.js";export{a as createNotificationWorkflow};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@visulima/notification",
3
- "version": "1.0.0-alpha.1",
3
+ "version": "1.0.0-alpha.2",
4
4
  "description": "A reusable, ESM-only, edge-ready multi-channel notification library with SMS, push, chat, in-app and webhook providers",
5
5
  "keywords": [
6
6
  "notification",
@@ -204,6 +204,22 @@
204
204
  "types": "./dist/utils/result.d.ts",
205
205
  "default": "./dist/utils/result.js"
206
206
  },
207
+ "./workflow": {
208
+ "types": "./dist/workflow/index.d.ts",
209
+ "default": "./dist/workflow/index.js"
210
+ },
211
+ "./digest": {
212
+ "types": "./dist/digest/index.d.ts",
213
+ "default": "./dist/digest/index.js"
214
+ },
215
+ "./layouts": {
216
+ "types": "./dist/layouts/index.d.ts",
217
+ "default": "./dist/layouts/index.js"
218
+ },
219
+ "./i18n": {
220
+ "types": "./dist/i18n/index.d.ts",
221
+ "default": "./dist/i18n/index.js"
222
+ },
207
223
  "./package.json": "./package.json"
208
224
  },
209
225
  "files": [
@@ -214,9 +230,12 @@
214
230
  "peerDependencies": {
215
231
  "@aws-sdk/client-sqs": "^3.0.0",
216
232
  "@opentelemetry/api": "^1.0.0 || ^2.0.0",
217
- "@visulima/email": "1.0.0-alpha.41",
233
+ "@visulima/email": "1.0.0-alpha.42",
234
+ "@visulima/workflow": "1.0.0-alpha.1",
218
235
  "bullmq": "^5.0.0",
236
+ "croner": "^10.0.1",
219
237
  "handlebars": "4.7.9",
238
+ "intl-messageformat": "^11.0.0",
220
239
  "liquidjs": "^10.0.0",
221
240
  "pg-boss": "^10.0.0",
222
241
  "unstorage": "^1.13.0"
@@ -231,12 +250,21 @@
231
250
  "@visulima/email": {
232
251
  "optional": true
233
252
  },
253
+ "@visulima/workflow": {
254
+ "optional": true
255
+ },
234
256
  "bullmq": {
235
257
  "optional": true
236
258
  },
259
+ "croner": {
260
+ "optional": true
261
+ },
237
262
  "handlebars": {
238
263
  "optional": true
239
264
  },
265
+ "intl-messageformat": {
266
+ "optional": true
267
+ },
240
268
  "liquidjs": {
241
269
  "optional": true
242
270
  },
@@ -1 +0,0 @@
1
- import d from"./NotificationError-DVfMJQ1u.js";import{composeMiddleware as h}from"./composeMiddleware-D6dR8DUS.js";const c=(a,s,t)=>{if(a.success&&a.data)return{channel:s,messageId:a.data.messageId,provider:a.data.provider??t,response:a.data.response,successful:!0,timestamp:a.data.timestamp};const{error:e}=a;let n;return e instanceof Error?n=e.message:e==null?n="Unknown error":typeof e=="string"?n=e:n=JSON.stringify(e),{channel:s,errorMessages:[n],provider:t,successful:!1}};class u{#s;#e=[];#t=new Set;#a;constructor(s){this.#s=s}use(s){return this.#e.push(s),this}setLogger(s){return this.#a=s,this}getProvider(s){return this.#s[s]}async sendToChannel(s,t){const e=this.#s[s];if(!e)return{channel:s,errorMessages:[`No provider registered for channel "${s}"`],successful:!1};try{await this.#n(e)}catch(r){return c({error:r,success:!1},s,e.id)}const n=async r=>e.send(r.payload),i=await h(this.#e,n)({channel:s,payload:t,provider:e.id});return c(i,s,e.id)}async send(s){const t=Object.keys(s);if(t.length===0)throw new d("notification","send() called with an empty message");return Promise.all(t.map(async e=>this.sendToChannel(e,s[e])))}async*sendMany(s,t={}){const e=Math.max(1,t.concurrency??10),n=[...s];let i=0;for(;i<n.length;){const r=n.slice(i,i+e);i+=e;const l=await Promise.all(r.map(async o=>this.send(o)));for(const o of l)yield o}}async initialize(){await Promise.all(Object.values(this.#s).map(async s=>this.#n(s)))}async shutdown(){await Promise.all(Object.values(this.#s).map(async s=>{const{shutdown:t}=s;t&&await t()}))}async#n(s){this.#t.has(s.id)||(await s.initialize(),this.#a?.debug(`[@visulima/notification] initialized provider "${s.id}"`),this.#t.add(s.id))}}const p=a=>new u(a);export{u as Notification,p as createNotification};