From bbb70bdc3527862e1528ce9c833c9487c69cb978 Mon Sep 17 00:00:00 2001 From: will Farrell Date: Thu, 16 Apr 2026 08:29:06 -0600 Subject: [PATCH 1/8] docs: remove unused Signed-off-by: will Farrell --- docs/CNAME | 1 - docs/PassThrough.md | 12 - docs/Readable.md | 24 -- docs/Transform.md | 37 -- docs/Writable.md | 16 - docs/img/datastream-logo.svg | 1 - .../Screenshot 2026-02-21 at 19.54.38.png | Bin 43017 -> 0 bytes .../Screenshot 2026-02-21 at 20.02.16.png | Bin 23827 -> 0 bytes docs/packages/_example.md | 72 ---- docs/packages/aws.md | 386 ------------------ docs/packages/base64.md | 4 - docs/packages/charset.md | 7 - docs/packages/compress.md | 14 - docs/packages/core.md | 66 --- docs/packages/csv.md | 107 ----- docs/packages/digest.md | 3 - docs/packages/fetch.md | 5 - docs/packages/file.md | 4 - docs/packages/indexeddb.md | 4 - docs/packages/ipfs.md | 4 - docs/packages/object.md | 16 - docs/packages/string.md | 8 - docs/packages/validate.md | 3 - docs/patterns/file.md | 30 -- docs/patterns/image.md | 3 - docs/patterns/sql.md | 9 - 26 files changed, 836 deletions(-) delete mode 100644 docs/CNAME delete mode 100644 docs/PassThrough.md delete mode 100644 docs/Readable.md delete mode 100644 docs/Transform.md delete mode 100644 docs/Writable.md delete mode 100644 docs/img/datastream-logo.svg delete mode 100644 docs/img/logo/Screenshot 2026-02-21 at 19.54.38.png delete mode 100644 docs/img/logo/Screenshot 2026-02-21 at 20.02.16.png delete mode 100644 docs/packages/_example.md delete mode 100644 docs/packages/aws.md delete mode 100644 docs/packages/base64.md delete mode 100644 docs/packages/charset.md delete mode 100644 docs/packages/compress.md delete mode 100644 docs/packages/core.md delete mode 100644 docs/packages/csv.md delete mode 100644 docs/packages/digest.md delete mode 100644 docs/packages/fetch.md delete mode 100644 docs/packages/file.md delete mode 100644 docs/packages/indexeddb.md delete mode 100644 docs/packages/ipfs.md delete mode 100644 docs/packages/object.md delete mode 100644 docs/packages/string.md delete mode 100644 docs/packages/validate.md delete mode 100644 docs/patterns/file.md delete mode 100644 docs/patterns/image.md delete mode 100644 docs/patterns/sql.md diff --git a/docs/CNAME b/docs/CNAME deleted file mode 100644 index f90c44a..0000000 --- a/docs/CNAME +++ /dev/null @@ -1 +0,0 @@ -datastream.js.org \ No newline at end of file diff --git a/docs/PassThrough.md b/docs/PassThrough.md deleted file mode 100644 index fbd139b..0000000 --- a/docs/PassThrough.md +++ /dev/null @@ -1,12 +0,0 @@ -# Pass Through Stream - -## Streams - -- charset - - detect [passthrough] -- crypto - - digest [passthrough] -- count [passthrough] - - bytes - - chunks -- abort controller \ No newline at end of file diff --git a/docs/Readable.md b/docs/Readable.md deleted file mode 100644 index 96a1cd7..0000000 --- a/docs/Readable.md +++ /dev/null @@ -1,24 +0,0 @@ -# Readable Streams - -- node:fs -- fetch -- ipfs -- aws-s3 - - get [readable] -- pg - - copyfrom [readable] -- postgres - - copyfrom [readable] - - -## Streams - -- charset - - detect [passthrough] - - decode [transform] - - encode [transform] -- crypto - - digest [passthrough] -- csv [transform] - - format - - parse diff --git a/docs/Transform.md b/docs/Transform.md deleted file mode 100644 index fdc2e79..0000000 --- a/docs/Transform.md +++ /dev/null @@ -1,37 +0,0 @@ -# Transform Streams -## Streams - -- charset - - decode [transform] - - encode [transform] -- csv [transform] - - format - - parse -- batch [transform] -- compression [transform] - - brotli - - gzip - - deflate - - protobuf - - zopfli - - zstd - -- json [transform] - - - https://github.com/uhop/stream-json - - https://github.com/creationix/jsonparse - - https://github.com/dominictarr/JSONStream - - https://www.npmjs.com/package/json-stream - - parse - - format - - https://www.npmjs.com/package/json-stream-stringify - - xml [transform] - - parse - - format -- validate [transform] - -- crypto - - encrypt [transform] - - decrypt [transform] -- json-stream - - diff --git a/docs/Writable.md b/docs/Writable.md deleted file mode 100644 index 491e30a..0000000 --- a/docs/Writable.md +++ /dev/null @@ -1,16 +0,0 @@ -# Writable Stream - -## Streams - - -- aws-s3 - - put [writable] -- postgres - - copyto [writable] -- pg - - copyto [writable] -- file -- fetch -- ipfs? - - diff --git a/docs/img/datastream-logo.svg b/docs/img/datastream-logo.svg deleted file mode 100644 index 5fda422..0000000 --- a/docs/img/datastream-logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/img/logo/Screenshot 2026-02-21 at 19.54.38.png b/docs/img/logo/Screenshot 2026-02-21 at 19.54.38.png deleted file mode 100644 index 2447590d78825b7b091cfaff25492001e860526a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 43017 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

&P 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 diff --git a/docs/img/logo/Screenshot 2026-02-21 at 20.02.16.png b/docs/img/logo/Screenshot 2026-02-21 at 20.02.16.png deleted file mode 100644 index 8624bfdc7a7c51b8636b9113e0deae4bb9b9d0ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23827 zcmZU)2UrtZ*9HoR3P=?M1f(8B>7YmrAc#m;Kzb1n5ke0&p(@o-lrB}2-g^(Ip-2rq zw9p~+4xw`g^_=g&_vV?&Br~&T@4d>q-nC{X^trks83_Xk9v&W<(z7R;cz9Rp@bK{Q zh^_&9QaVUh|w_Z)R#d-?eYte0*6Y_CKG;avy*-2_&TF9iRdeWmWpm4AO3D{~`I9poUL*6>LxUA(J0Xv8tpXo#J@Mt+NS9~SSf3|?@4_j;Nx#+2>N?179 z@teJNFt_CQuyee;4xW^U1n|?&(#4F`!_L+oBH;mI|9yr8@cVMN06XjNQ(V9xc0IM{ ztnv=dmaLEX1^ETprAb&>S*4s`zmd><^7L0+&Ywg!lyo{<}8NRO)iC#B*y8OI!UX)^>o*fIg&!MIK50 zKL7uBpO$+6Ybh@B@c*{_KS%z*Enh+`o#h?ufG%C6|EI3MjsNe#zYV1XE@%FK zB=JYgzxM)imL`!B_)lljBu@_fFn}7FteB(bl5B#;1&L~dt&sYr;j!la1Qdic39(~Un2HDopN&VV;6*NlI8T9pfjt^CyE^ijy zUV>|EUqWesXl~dq70cI)OW4~aG0ag$=Sn-B;=Z314#jGROY&U*C}bEEg!@pMt>n`Hy)I`5N%nGH;R zFbuarW(bz}z(v5$dbwmj#Y;Os9?D08v1qNCo)pPo#YpBmTh%H-q7IX!Cb&oIvV4k(e^gH&>}B@qaJ= z!UPX=$t;^gj?$KF@90iUYE!Z9t&_%t8+>S1_1l!|H*2&8_fK-EKK6nt8Kz`C zYipZ4wr3qZJ*}&P7$s(lv1)gIbUuHxHV^4}k$6Ls{*!b6imFQCJE9Q3-;3;R{DAph z$Vw{6-2ao-A-Xc?ct^GQt?>EfJW{+Zz-I~ksn-vY_EYfjUHyBlswYog2@w9{g45Je ze9a~5xam>QVj#C<&RE6nJS9;IpI&fS^D3}RV5G=Cm~W3Rn)NAClQ1ECB73=1sp7Gm zuPk&T8L>@TX1P`$w9kEH%;{hlWLxkxH^SnR8!ddGnvk8!fZFPphEyY0@QE>?!TwL9 zztob3Up5zsIKDT9QV}Op$k8bVY#GaMX})19`*$i#SMX2ogQ7uK8W~HIl8QUo<4N%+ zm8bPXW!`O;?vEFa+>?CrY84oIwTZl<%@kPlpBkMdVHeMES~VsLbGlOd6kUtL;mXCE z`s**H{^}rv>U;bS?{}5~XJ(0X&-yv#ADpN=kt{O9H0gSl)@ej*F@6CX>OA(FZ#ZZ^ zXM@SWsN8Kg4mW-A(l#)G7xPTqq!HBh)}P5gc@;@WS#ziwzy&Ilo<>`DDeUdky0T8i zZ`5*#27LCSst4^j7&B)Ab}XnSE9aj0DPl<$4;$3mvaLs;{Qk>h{kX{d&K(R13459S_K&G^J^kLK zY(_5S6t3Dj)OJ5o(a4_ zL;1b0@0%fqOLc=FHQz94rwu6B9Xu6{J*7ys4;jLl5;+t zZghj2HM$K%XyU*4&{Vm%(%PXcfs~r>DOOO8RXkCc)+TV2ci?#sb-xe6i~&*Q-lD%<}G{uV)q$}0FORqY`x;H!d1D@ho=x0BN+$QIC5ZDLE^6y?B0=+(DJnR3AM;=N05}fl{-l99&?3X|? zhkOq9=c^=0ghlD<9Bb5P4i~<-Gm0Oy*&1F=tif~xKiH1%{l(gPR=+7J8JWnUZvT%N zFXFAc6>dO8dt9vP<7uVN&nWLX5ryU; z?U^Q`ximf`Yw)K;O)Z6HF1Y7qK2p6=3Ws!w&pJMP&~t|9yzT@?a!sBEAuHs@_w@fl zN!;DO)lqn|sk*lm^XKl;9nt56=BO#B$VYXHq& zYkg{9Z`jS1*rCw`{{zq8b%L>r|ArHv3Gas1 zAW=v9{;+rjtv{_E(|un~zP3zH<)yf&ey2b4Yg5N#fscd-4&LLNA_9?Gx`oecIn$Mf z(IZfTW;jpvS4I5cm|WJhRQ7KGT7m#T7|)N6E+uM%ktMsc!$ zDNt0pG(VE|J#WZUj5q5fHLVKzVO^K5Tye84xkv;f$V?uWu0A)THzRb+`!BbV#WU@E z%bz6>e&uJ32`2ZsaPvsEKfQu1hq_AOeMC=#^F7gk+Wo=%4#k(cTqkJkXCUjzsp&zP|mH22zE-{^*d>@YT1+Wy*4YJY|d;Wr!^?sil2In`*#b zLU~OfSfJ8@I4LIhFRLj~!ea^R4G-1ojL9&9&Nv#Ou|(}s#Rc)vrQGN|Lz48@2?M<=t3l$K1|L;+xj_-4hZr~HXJr$9)5NR@ z>c5k*VDLoCz{8`;aF)kgGBI4Ruie;fifngh?Xe)01h{z)uQp5-GE^VnxT)O0&{+Yzn z@2BczXd!aFfsHp=T3HA_UYmGKREH`g0A+>@I8X5TT`B#J!vU zt44~6b#q9I$Kl3TW%=KB_k|8WfYFzz<@rR!Stgx}32A?b3dOyz)sH@kBI@R(lte`} zaQ!V7h2f7gxSkZ@+C%!qL5(r%>AQ2PAcpH}ds3}U%k@C`egq(aeYuHx2X}ydS6{3g zz2uW*mLBR)^-sQJGFs(by&w0bQ0fmaC?KS)^vQ@8A)6)J%yj)KdY4FLpk*^HEln}_ z&cLg#w}sE2LmJWqk~*)XbwTi@Z<{H9z76eqgu$Iu)3itLd4kCR+OhkU(be z`Y+qozCgYX2wd?7@2-n?j<@9}4J#WJxRB=*ggw5HX_iLljQ_VImt4d5;tg*KF=BXc z7(lD#s`rg!_+Gfoe(|FGw7E=G+v47F87ZPhmBpFebuIe-gJL#@j?Mlgl8hD!#)syY zRAVh3=+kF)<1Md-cwIlitSxdV{PXU3$ER%D@5FO>M>h;-;z|o zQbieiB{wehv;oZQm*3m*p5U$|(dJ|xUT zP=2hh?NPg}x?%FV-!Y|p>g070Lp)*s$-#>26W*VX&fo!M_SzQQP%v2;QFC8;OyG|QAK?d>wR`ETWn704=^DzA z1rH?$^?}L4j@o zsUF;hxhOIf+)pwvWOWIhlmK)-1tnM95_=9cE5vV`rz>P7gAb@K06cs8_-eLhoArMh zRKxS{xR*QO!a7CW!9bl}qc*CZ_*j32r7IkF=;@mZMxa@JXCynUHO$krM;H-Zbmdd`nd5KM){>B;Y-}4!PgIe zB>`3_5_oWaT*)ZDf0skuI^Ycvq@8kZB8CI%;2HP@>ICw(Q;;Q81~BB3EntW$DC@dJ z%oAwvuZKCNa=3U4@I#!GQ~-?ooz5P}v5H0!UmWxj_vo;rAN)W!I)i@IrW!w02;8j2 zIkk;Xe2pQp?0^1y7_|T>Vs8#?cN4Qdv+izrVR(gn0(Jf8QUABjt-Pr3@_5_U>seP5 zW&Tb7M{f8}oXl;9BN!a7`>ry?L2hUVaujJw-41Pm$aXp8D~c+podAQ)0%E*6RsSZ2 znAIg)Cp2039j$55Gd9iOBl!wpt03*8J^G%OD}E+C)1hyFYl^*ui=cLLqF3~QoBC*= zJn3frp(XS+1Z*;0ZT;n`Smc#$s?|bRM%&TSC7y0yRu&p1FqWZ|bx8Pl`0!kpQ$WQj zdvZ2y)fZHpSXYbXWeEMf!2a=Tf`^C5v6j(m7f()n9Ezc6yXNJ>Kfkyfw?~5QA9euF z%T|(=!#!N}hGh11qwWjE#hF805WU^|ln~NlvbxnEo?ZR_udMCo<45j!zU7=am&8dY-6I*)TN`EcJux=F<$WCQfY) zgcT`YO}$Frm`s971!;YAC5boyi1-96YdY*os9V8fze)6ja=X@}*n%ao*r5Ne1)};z zjLF0Cw?Ze+piqwDYH1Q5!93I~8)5*gCMzn0Q&>oPP%S|kXN)lK3uXXm(3`ne-^7Wgw=C*XLJ z8K!Zk)R~iww%^pwkEr4HkZ?fyky$5Mlt;r>d>QPw%8j$?a2VNR5=z@}KD> z!g9*l4!zz{DB-Bd?kEcg?*M4#^g{qEQ=fwJWB%F^E5RM&-g4-2)V5uwAX-$;sqm(Wo32fn?m<*g8Hy?9Zuy$+! zj5D&@{-y9=bD{L_AxwaGmdvaMQa?B%1G7l>dEQADOO9$`>aKrHRYx?b{hhqo-bPxG zU;y5&%^-5}ISJkyzEF(Apg+De0gSrdee(+QY7X)z|cRtjCUd1)E6 zB^=4piB{8oGVG^bfoW?YIx@Bo`@TIE3qBa~3A)0bv@Z6M z3(?+1eOXhW^ydWPBfpZNXXqc#@grio>YrS*dvy;{ic79pK$PT)m+if4bV8<5AxPif z737M)nz6{st{@?hveg(y)B{dR-g@G~UZc`n9vMGo4wMk1B}4XqJ+5v?g@O^g(gce2 zfDP3F#=8DNH}r2M1n7kkAF8_i<8_Taw_w*{BXza7KVvJWTJwW<@s3m7Rtof}Ti-Ia zXJ^@K1#ekL^CJ_mA#Z2HuT(A4PRY%u)=n;sd9@y`071=8ysbB$RXY|7Eq&d{rs}xE9dtW<{p-Ol5&PS^d*5p ztodX`TC*>K=>`xfjfpDC0LJyl^+#mzgO-E^L?2PmI*EoTnp~fUs?}fRdbO;URV_9& zC)tH|g}=_s+_=6Pa`8R%#Uep=|IUK!w6ONk(9o=*kFTwY!@-p{GwyCZqxDO5`qKi& zV)$H};XmtD)Ocm{OBmur<&Jo=A=ofVY-eH_Fx^x5HSAudF5P1(PPbe!Xd2bBJLVBh zcJn?mul+&Qw6@+hsqS6AAYMag1+wB#`NalsjNx9bOZ)k~_(!vST6j9(57H-5tJ%jwU&ZiZi&f7HV1rI4;^(lFW$n;lQe z+@kArpORKzX41K?fGUv$FS|xv590Sxaku=-Bx6B)yaP$=uy>v2tM@Kvx&)Nus#y#( z|1GevJN`qs^b!P=G~=P!l%odM80`b&`%!1n%74>twB z8Duf0cgpFD=>GCRwp!2{Ib6{=bYkN9LMhp~B&cqWH=dN`uJ_KI>*Ipi(@#0%fdcZ; z#RU1vyUHBRe4a7$b8|Mo*`Nv#Y>g9ZcK*YQkvsT{SOePk+Jz{$(?)9bWIylZ{`NQF4UxU& zAJeO#YJgoL2i4m9=U3~>2ew^1jt&3p4)@siH+j`;&&}MnuSYzFEiSxlwB~=A#M>x6 z`b!7=hzgYbZpG^pi__ZZ82hX*E;N4Nb#8fX*}VRlx^{0i0$I&ZzO4z@p$dMPAnz}w zEHbqsP zM{7ubzN2_q=mi$_r0;^JLuAX|*mEMUCLgW>M)IzoFOX>`>pHI|S1IprM*>OI-q%(F zWPtv%N4TSpPih66**HjtC*(UV@zQN4c%`${Bc&x~4P*PNe>9AEa1&djC|{q$wQ|(r z(}C?ja^!mEXEI!E|5k_~o~<>c@A2MKJyw_7Hd3eJrJV;=Cp^TplUh(Sw1+RBrQ%X+ zhl+mbvNmnTai{6Iu=bqk#W8wyW$)xPV$*vQc0RX%P(O9H!B%x~jOV>1hxh43=KOrq zD%(3XK~7MTO+gF_%vaI0WtHx^oco!?$E%k^a10})^dvn>xmlfC7W_3n6_v||BwC`g z=eM&t-06qQu-WUg@>DYHfkdZpGoKnY=sxZ3>zdvPDm0tdTS?vuUrX~kUVi+oQ!{34 zExy+d`64$1u#~LPrTC|ScQg8H3;&rY3sEm*^$8a{Mc?h!?qv9fRLlb|x6RrnqgUJ6 z6UuFSU)Vk3i9)gP0yVvt0A+)Osjzc9NjcyfZ^D6YUN@s zX=QZi!NW`HM4uD&8=hh*o1n>0_6x8V=ec7I zbOt8;_r4(umF#O_jYpgt(h@bZH$Y{38l&a7oz#vlER1_J3 zE*ZYq-6M&3#-m}MBlo&Pk?{0=X-{8d3DXcA)S1ft{-@Qe{K!D6I4xwL()Ax=d*t1CBBf>Hz}-VKw`kLi4W)QsGI4#F!-2ifle0id9L)8Uz3%Anf>1-|#$PAc{Y|-aIfxZ+PZNsqYNO zbd4CplyY|N=Hw(4(x697U!=Op3CiRnKz{UOu~T1Tcf9>GEQcxB0po=+^VmNO?sM;# z4HIq_+ix@pddZPmdbQpepPUGr$|Hr{sXnd3)z&~d3nnoSh5p=>4FbAmj8Nv32lD(Y zR)J~SEIhKhaV>mF?>pQ7Adf_u;P;z3iidT@} zz!gamY`g_=4Dy1_r@hJL6E6$PkFaU3l)~#zx|6RB7sgvfc;BpdGPqgi#6LD30rV#s zr+ojh>`!j|H<_5c$k^{Z z57np6)%TQRrEG)4F0fX~*rS?EQV|E7g=Pq*_VI2i_NF9ej?+b>dV`LqHekp+2QCae zY+%;^nCFt56Q2f^Wh&C7)8E)L7uK$J_gD@REN6gl3)&{8Vzgzq$mKsPA-#)kH8j%U z{5$E=>w`^Lq9{W#Vsz`b{D%S4G^BnB6$NI3ku6&xA8N}xj6EU*?1flX@f>15 zBCO!MGp|%VIYG&XXs5$sM~|w2@<=C%`c!?Vo7ZzX=KDMztxA2Mc?NUhcBJGBP3yuWs9QH*9E&qOxRa02~R1e?05J~>W7r&4l_f)^( zuoeZ83~yjXor%sTHFG<~-sFbNF{PgajU$8t_K{dVQM{n4u1rLi(4!a&{zLJlfoEAtCQMp^mJlN`e<)phV2blv`M^0tR5;~(|KIX=AZK0vGcr%d%$5< z_9zADZbTs2_Gx9DIa0`w%#gO$0>75au-afM`?cC?$Ru0YVx%k zsQD4EvPnc2l5uwnr09~-?GMoH*AAC}4w$`9Bw^=By+<8lqyy|`*rDWr4 z(;|MEi__iJQ|11hwml=yfd_9r`EfJ|7*!qLI&a}Ok-#DuUxO*S+G{^y95+NjzULFB z3@mn3(GC4p%fRDo;su1=r=w1?UK}nLhix(@rYEUGha*EN7bU;)o&ClwWD!#?+RH?oExhK_J&Dw5my>DG-ejzZxd~27V{u zUX=;!)=}W_89tf#m~vrT8{|EaYMAe^X{}XUQQP#K0wS;RbQqjpL0Y<6`>-e?33z(L zUAi38qPOyxlaP8c;DEINIyEBq$cx;;MP+IU_ngJTBy$QahDO#^q$(D|TngP{oS9kZ zgy}CH;p!J=AY}M@;OxwZIR5OG*4llLG_%u=%*AqA6vP~@_mRZEQ@PhHawF}0FB?gx zK7GF2bPhHOu%AM%b{U9vZ;IH@Q&-vYsfHS@RUS`rc8t}f#+8gl1y_>)v{F@REEQ9c zkqJOUx|r9&K$xl~Yg5ZA+f$GXniiPlyOGZ)I6LFoPTS3gZaZWGg2VB&C|BfQ=^z`n zUM5YmE%Tt-E2osGB*qwWaI)&Ho;*@YdlFCUH)2;wtv)={q!J711`}7woQ$H#r!MN7 zBu}TW%S^8-{@B?Vi@Yi5b^C@f6sXCA1NboDdrd8Aygv%}Qf+D@;!B#I5Lb0@ha8~xP}%~|zMyAL9V5b`yiPO4XM)(zdbjl*_TsKb zbFLtFOKhumO3Vv8`-98wKQa`{F2rUEBxUl?s5h!ygQrv;@V)2}>4=@(82}`=)ihyB zpOD?z(W%ndADrGvnw_PH45>Y-F2zndu0#WG$g|Cr3?&1 zsD0v&jpXnmR)@{8V&Sz@u(;@85Ljb(!nsRa`mP&QZ3xCOb57j42Nb7!zm5yL0o18}iPGK)!4<9Bt)mAw(r^$IojtXoW z%efPG+*mBl48|my<7ny(U2Pzqml8^n5Ph}s&#go6iTe^;OqlD%RF zQqg;~so*o#FElO@Y1!m-L3@Z5&U}P}5q*XM_C4f2 z7yIvp${<#n_Wj}Ea1 zp8GBMK2fiW%fC29AtQZH=T^ON=jjI~bDd4?u%2e_&dGxjxBQ=oHW3z{3xkt_#)g&$ zZ91FYI#abeckDr?#F#?#^K@@R#H$bl>}mb9_Q8y4hduAvPRw!p-3~7 zDh3x#F}se>ovxW-2k#HnF%1WI-Is)&Nz>M=keY2cHLbN||AR?{g=_fcf9+kN_O7im zKvyp8ER4?%Ln&;2yoT=^&q-G6H5ryD$+_RC?EN0ZX97sV#=F$v1?k`xThad0sW~I- zGrUv_NSLes?$+oxX~=1|Pd>s8M*M7bKZ@HyKvL_dv}xb9>gjFg1)d{2sj$yzG7RuA zb*C4`^n8{s*z{~gm-Ef~VgfJ@f!7-8o#N`Uc-_Q&I`=p9i^MH&vy2~4im3Z`Zvr=v z_@osFk9$N`ojN^Lmh5ZYNbJVvkR9561}0m>jLR|M;0t?KA4I^Xp;U3E>ZRnxku-_n z@xbhT^#qIKWARq`|Y7m#na8)P$gHA89Ti3288lO+RR8 zAFUa5Fg+h{GH$YpcVA6SNfCi9^a@}+FY-|`rg`_^ixvp92M0DpgC+ZQ(hdvfQS8B@ zv&Js2Hb#x}D0B8;B5iiy0gAJ~>UiIPxf1m_Sy5<8$&(0t*u%L3j`V!Cv{5=tkOUSO zX3d?k@YpWna3L}J9tZbGR&2-VP;`Tsmo&qq7@JZc-+0V~rCXuj9K3rx>ZjkSA>>x; z_OJ&d`eseV@>Qn3`$PLU6U%aGFN4+oG-T@IA|uWTL*wF+J{8$2X&Kn)^6F1C)s)Ig zdmGQF0V+v*EWMCvZk?={`t^03+vI6q3GXgcb4=&}yu zfHK1x(TBp7((cdPm2(!GxolNV$*mowEP0B%(k*KKZ&V3B*bF!rE-ns6o0r?&##<*|2$JB%{5dIBzoPqoeC_f{_#0 z)UX24V;$Ajg^lG9L+1%|WB&Uth5Ipi?{(05GHFnfXFOI8hBfP!+54xYcAehpLl*q% zYohrng*$CoCA#sOA(#iErnn*)iXYJc`C0G2Ngf{`kGz*V_2_1;;j4#loAZ!PA)k(;6+qUR>xOC?0tqIc^S-FReHM>hY~wG;k*_w-sSLt zUYs>R69>W`!2+6`(DWm-a^R!s+0HV~dK>$kMkx@z{<@9RAB<1J4q*nSKDZ-!eFk5 z?!A>s#kIP1J1(CTf&&@M2)pByg#SrUlc)pv#c>N=u)u_=+S?cLQmiMz^9+WqPK7vz z{$(~imG#ijXw6TIW5(R{hNe?<&=M~PCyf5i`fV?=j&8X#u!`Z9-wH zC>_mGD-S*n*XW|NH8%Nu8{UOX8lN9-Aba1h2CQc?=5;w*{-kRjs;}lf%s8H|t^Z;}6t$une?u;Jm=iE$MIj zps!YEO!8wl>m&6|wbqNZ#d2;&&_-|E_(n;Fblt*U`kk-k;KX6vm3=2CA>VG=NNtFb)?T$kE^-)QJzB^e(umBz0#{@e*6iUbKpbj0@X$osB z>)*aOUn{>-ylZ{C09#tkHk7~WOHp0zFeTkvm1Z+lyxzV3GQQH4PPpW98pDl9b9mE+ zvVwEhV*iOBsx!XHMddj8hOKy$KGt8NfGpDuy;%O*YjxjruB`waz2J4Hd~I2>&2#zW z@;P%!De3~cTmhSIvY*+X#tl;B6W**}kq;+Bq=fFbGVjckS29~f_$F8F!zkQD4u(?| zwU}&*71A03y`a7^yUus^nHxgZe8x332@Nj9+s<>65$_T5+(YV1| zHdctGjjPY_Sgp(Z_xnb-aS8oA$A{Z(U?jLi9l3C2 z<TkVpWQp#L-9EwFZK=t{2>vm=m$pU30SQ;M5Ym6@$(0vzY$)UW}O? z)_$^fmeJfE0TNTPim$xvnuHr*m!X2#> zo7H|BuwA1{>ZTUy*=D8X&T>XoCPhu|p^tB2%CH3P2sJu95s92MO5@r!VW~^SIQHaU z6utpl9)DvwTiUv;GCwb$eiq(qT*eRHoYFBg40-5luSzWEYt&uUJbBYp1Uq(L2?dRS z(QEsfh=g+K+s~AWEq#%-d+`yJIC3CfG*vWuJ8eCNRQFMR5Jv+_*Ydl)>?pk(_iadOG zz&yL!VR?4|su31yWO^-Nf(F`VW{Al5WGspH&XUw$UO`h7hlwg zm;wW@_lWb=P)sXk%e7aU7!-{immi~RJzb$6eJDuB}>RAqL9<4VqV%% zlOOdpqk5D@a!YD*)0={*{@3KT9c*oG~Xk-fxMb* zmtt!t^4J4tRQym=_pMKJyLVk1u|>Fk=Rj+0|BXmSU;avWCWri~LX_{N28F5o?1L#LV6<^PrZpLr>Z|o6|^&CuwPYsf#b)F70A{Lko-bcP%8V+eqJg!KE z&Ffo0)EXfjo!$Y%E0gTq&Y%LP$np{5&CKG=%$`VbtY)NzN)P^pLHhQDIR=~sZk`ow z&5fJDHazFcML8XNRgdViYiprD4fSY4ON9eZ3XTG;ZI{bq-|ruB@z(D%GiSZn@mz-a zgsNq`bX@>1s$N`#4{b{0;jw+ce6Mglr891#^-~ZWw|cllCbh}-35*(F3Tr5}73dDP z%8bP-@p>vnWa8)}-3>mlk2a_&7`Cgw`}Jf#E%eFP6{NV@7rfW6o9R4Hh+Xz8-8ZM2 zWJ=b9z9X+wd;ne+#5Kuyo$aGr77S=S4%S9zWPEm$%(e1RHr_HIUz0V9e1g`tQ+~|( z@p|>;Yl5+~l53e{AkUrot$7V!?g)p-+Jb$3JNXvJ$g2eoV^zZmUz^R(z8}k@)V2ps z^SfQG!&VaX3{XV4eA`$f`@Ti<)(>g;l_6I z;z=s#VH;`^B;QWap|vU)f{2Md+677#GtgQ~Q|s zVmqMvW8Tax*>eTiT~QRu%m~%E73P`am|QfM>)C_Krk2B+{ShHc$CWgyI?0Z$;VbVx zsov7Ei>_QZhwrS2 z6;;#@uQ$fT&z29R3+CObOO?=)8{)b9l3B<-GR+B;dSY<`Y3r!Le4P{rUVqANcJQgX zS@#BgvavxnECc?$XW_I^(bVH7c>b^b23ssDc>oi z<+m~XVDUV{@F3lTZFxV_@@!H9H=8|n7s2$jPQ&adk$!!YA4_KVqPpNGEgd)#F9?`C>q<2^|yg5IEM@zAa=(dus9`Wwte{cd|WNp zA@?Nqz^ZCkuO_E4!+bYYx8-R`@w_)&x`9rn+U7c28VF}2qv`CaZV}Gy0uIQl*2sQR zHn!0q4O<&GS`Gj9`p%asNF)lec|TP0pwKNf)UKs1Po|V?VPssLW*lpB4S-16FCj6N z*9Zk9TC7~D()m}alEnu7Uv`60ULNDWCcHalA*=1?6$jbJ58@-EAU5L_5(iN@#9?lP z0Ot|&f{82BlnTE^kIj_M?bsehlOG8;^HO)fmFtPyJoEP=I;r8YyA{YnYz?JpJ|YoG zE8fxa<)ApZI}@B8H=gMMGge$WV8`U7_P4j@y;QK^h_62vPkM2^o2heBdNY&E^X}Pw zbRidstFty;+Q6Y!+RG-*11aoK!oOokWr}n`Vbg$H&l?WD(MDq`{BzTT(uD_|{hmlI z4c=>WBMmXfqc^bzQ#8!rxEF0)0w{y2A)l<_`hBSJK5X8Z2=l4@ z!Q41A*L}$xz8|* z{dI#^M@?F5ev2jZX!7`6pz&~aYu$ZH$v$k-0sC%c9ijxokqiwQ&EKZLHqSG0?HhGV z3Dd>9OBsFW1S^KuyNkPe<+VrN{5CcfQwMF!I;_bSCc=O$SC_!;uc?CC zj#UnZE`~I(ck^*k3|YAfu6>Tol)@I}Q^Pwrv2a}C3S{%#+OEgP_C@7l*1X*9xvHfm z*!I9q{@#(ucnd?-kT}xo#Ytg*qhU+YaY2L#=Q>79QMq^M!@Q2ykPzps_@jkzzP2&& zw_CxL>w6{_gRK^4JyjxD^~&|3R=2zb07E)$?eV$-HOq;9jqV$1(dvtwbMNaj8S)uG zDfURx61_aeeocV5kZMQKABEg7} zgW(4QJHGiUZFe#+?EBmSAX?Df9_rw85_xgF(*j4ftfJjc*51hBW{|-wgv)DcZP9lV zOc7955AO;~H?5@5@uX_|I~xP;sDxm$nI9*6=9sH1wN}xcC=F*k5f3v<$fPEguxI{<_dZy`5jqJ$O|6`Vqsn-g~9(>Q-IX=?eO4 z%4c~^dBup`7Xxlm@O<$TL$#YQx;h~haQuN54^(A5H)@w}2SF#pm%T!7VHv+;MX=w3 zl3|%_@CH|->@U-!3E*XTowdXqZy4{%&O&GBS%LqY66^zP{a)fGlO0ClJbYHr zV8b@9((N|{xb7s;U^pD!$yK*qc{tKB)!@1N>fPu87_c1AJQXjSz&vbsH@;P-lyma0Y z8wKU%4C;}Q$?_8TW-&--H+S)c*+7y~m$tP`x|f|KiCY8A)qETokkzV__#RP#LLA;6 zE1Gqp+t>y*^%5?UeM&)Vxby8}pSw@j(kG zDY_a>2it7)*9shKPE|@OyheEA=|tJa*X?_OWKg=M3~bywDSR zFEUsQ_Wx<;%KxEU-#=1_B1#=(jW|No*k^R4#X1#b$reXqFxmI5l&vUhN{BQGGY!Vh zB#bRamKls)8ezt6Fow@lol58X7kpo@^V|KrUeA3!@9TYC*ZaQj=eeKz5@A(;iv(+4 zrB}`=*+@A+eLU^@rqK%NMI-eXg>+{|RryUV%)PCIN~)U8z%W>uOubBxzF^YP>qs6) z#Exa;Dj%RelZ6qh#|wQR+#!B6YwQ(w|LKJyPDNO?Hkop3_p;A5U#r5lsJ5^kQmuTckaV^PpaBL z3c(O`d-PD>wICO$J6tG~jhL}H0EIpl+`P0X(0V^O4qA@z!8V$mB8F&=!;tBXb22bm zZiNJcrR7f-96*%o7>2(yeugM8!vY^dc!_BHHubwGibc-D zG(6s(Dw{*AX5J^p^@2at|iPDJLo>wcwR+rVuE@B`}5M)2$LR#mQ zYG*rBd*kT>O4$o6&OcKF>6F>JR)y&V?TH!joMoB#^5hmu0F?q7iadn+6&^2SfC9o~~;QY1X=kdB7%~ z;wqN?4L`1qgs!)ZH7?M|`eh2(&7zVhFYLew!7gRm6cnuH;K}g>5qhG3bi>_EZ3_k4 z;3oWa>lVv2(__v*qmGbB{HxbY`&`D+_~G#L<|PEKKkJ~R{9Ohv_5niteyIiG2UMDx zPhHCMUtY}IfM-rLwhpdbBRdDBZ@j&aPigDK)eleat0*ZIR*rRWGA2DkJU`KU6d5>R z?6keu$F%~b=?R)i5RI|3#gl`H#EPPrc+g7wdRBUVi^H4RuS7_ZWIt>#(XclB(T$Y! zho}%bslGj@oKY{bUEX3mmRf(5D=(EN3+l-~ma}GVvdKWIHnWh9Se;CR2L^R!ORe)+3 zBu{Q0Vve95inS8%^~xRytGT%CgsbQ4SG=Q4p6 zOBuTjkwHO)UP>`-1mQrByba==%lPyFbHeh3^~BSG_=DS;uFY+wtB}U+0d@V>$a^n2 z!sL`O3T5-X-Efm_iw7WrPr=9u*RLAu-8yii1u^Ii-!^DWE~ZqxiaV!9Vh45&g<2WY zyNVnb`|(Gzu4|zZ*oXL9eCIgCY?lnUV&H`u>nXeXyFTUaO_Hn-X9RwuDfPYb_ zKKl@ZJg;=eI`+MqrT)%m=A5g& ze()`U&`on!E%ZZ79Bf=EGvJedUg)bn>DoN++@RNBEe}#!tZUwOdvYEa@u{NJNEUl= zhTq;$*HY$1<-ycm^tr0#m_ZXuNf!@V0He7-3MJ-cFCZe4A3S9*OVKJsZ@JD*hMW-h zq9x{^gO*K~=<}tDzamUeNyuCD=mtmA?e3}#XAvmqqf~Qy_mTR!(a>s}m~)i7Z$t-U z&VD%NJ($f+i-C@z9!Tm_jyHO=FV!3&;ySazG7y(W$GpO2VVGWDZbO!7LvCy8+NA)L zBiGcVX>0|!A-;3pG01Q}gKRH56SP>;==51o8Ax;L($IY@;v{E_SvG4)Zi20(G|Lk4Y(c?|1Er3Wj3?nid=C4-F3p84F5g zdRCaI1@MQdiS!0|D#MVyxKo9Jl(nSQypk$QQW~zh(NXdhhG7{fFTH?O$+Qf-OxKyI zRhvxZeUlU2(g){GG_gcKPieBY*7p|oT8iiiNh*6|b=0Y#Tl`4zL2cg;)h23E zgAtn9ofy?X?%R2dB~SB{z@SLj;w{R|X&fjxli-kP%qh_upHx#(X-w{^G+ymlf*BHE zxKu&x6`_M4b9xfrgcdHrJmgpW3rkI=F9cx|Xyrj)+?`!6EhQD1P7?X+6gR=%^)yn-&9^?|KJ$o6{W`H+_z9hO7-WD}8;*#IPJ2;N&ezEE?TEoSt~M{Z5D z{7|ujpwh^LM2sXUzI^4=sYSC>?S{@sk*fv)3<2{nsOL#W&>QVCuj&ksv=C}P0^uDG z88&1LksKqa!%9+Xu%m1J-H23dq~t;0!@7pec`XW#ew5uyB~*`rq3d6oX?xbI$tUXfgR+3vu?{EZ4Suqp*RqB?09 zfRo$6tAl;g#G9wfq69BbA?xhrS1&&%E{pv1M&?$%PzJQPR4=m)e9>%-MoUb^i zjbz58b9t`xVML$E$H&9jB0N*sIzmgEo#v~0#ZQ-bbEHC!9-NQ@+f$oa21yDA zQ(MH$?b#YeeegCtJ~T+xdg+Y0NJ?BUXe%;g$OHVuf|IbO+kcul!tH6hxn=a>$l-f8 z%#zae9rr!ZYv`&_960NES*k!ap5K10I?~S*a$ng>$v?6WpF2WcbEnu~@o=FdhqYYy zkyQz1AEbOHT<7er9Jt}W*@})#v-53GQsz#6AR=~OZmV0fr?BJs>N4ST=gIgzfwRQp zVVzc)3-Yihd^KL3`6lnlF^$9V&68L6kj?t8ZfqTri0fkgJizTrjv?kPMQ4V^ZJZ{~ zmn2%MIiJAQj6AKUU z1D%w$i#y!zZzsX425w`ireO+-Lv7wGPRK@IBWMjR0sokxGZmM9H0UnVk6y>m+y1SX z2XQhn_Hq*^W`r2oc+!dl7o3npUn=Q7leVuQwP){l`(aG19SQGVW48ychh;u%#^9`% zL^#-oiiPCg+&MpWZ7k>H52HL`VX0@}icTT^yB(m+ZYaOhpzf;Fx!1H_&6a(N3j-da zb;J5hFJ>eggLwd}|2Uu&&Toe(@jzwG%Dk(AB;(Yl86c?pSp&`XeaHGzdCp9wbdFnp z^YnWk;Dn_*V+%Cd$qmXJVkAy19|#rgtM!?1a+9lDIuYLq##i%!ei-MOI-f=6>*h(i z@M)l?CP>5Kv0y_1MStsjVDnWseD|QxQRF1MD3IahdwzN=ddV=0Jcr%m2!=8TMq8q$ z_Seq2Vm||4TZCyi&QLl4X?4s%?xzc*1suz~$$R=`PXw~xMkxfk3Bfq5GCor)uDZ-= zX#3KIMwx%YQRQT*X+V-?Y`JY_pCwb0Cyhr4Je5&{#&FO~QzVcuilDDC{fC`RZClsy zFTr;zKX(rrZv_ul+#j@<689iWdm@vVfPb8i(9Zt4*@VyR?0mpEs(?3}sI|y@2I4JT z#tTo;;xH21&cTIs9tE9R+x^o?qx3KiBT|N@f7h`tIU953@S9L^{Mgk=ry>guD;;Xm ziA)&43vB8#|4%e$vYwuaJzg+;Jg?Bk9O4)*$UCAvwOHwq)lv)0Al+mW`oez(mFKni zB?&)Z&5YYReUg=W=&{9%*yb9$mdhG|r2Cvev;G7k+8GwMs8>wJO!7lL50qz0m7fP( z)S7HbQwCJVRn>0yF95c?SuzlBVOs6`#tH9>?Mvf1q_q!+(-qG`FhI4r;t$6DU=ASh zAx8wF8rZn2(Fd*ek8u0aJt%t1@X@`Aa-%pDdlLK5eCD2$HRDYm)xV0_?Z!WU#whGv z46EH3oe0Oa6H1m%RHNicS^=9!Zam9f|I#S=La?DQnX5nx^ZpNDyD9M0Ha^&nr`|nK z9<+CJ`3oD$-~&!@T=v!F5FyT?Cz8{}GK}{V2erN$3XN zU+#Yg$90D3g~l1)+syyAG;rVya3E2%yODv7s(<3IU)Au9zIRLj z8@rA%AO5$ctp5VBBN6@!#BX`?Uq$>9XQux@(sb+WD1%O8qx&;9R>n3~W+I=dz=e@k zRRo;sHJ`IgFRGrIj7k5dO^+nzS1F|WnONY({pC+j`!E#XG7{LCChp@oYBVpPO=C^e zySP(qR8?43SyeV0(4_9cDqpIQn@DJ1M6YAiWFy*9AeW{N5r>n~H# z@|zb8IOe-;doUm2>2>j7OLj(}%r54a*?Hxrm`jW#^$i`-W=viW|Q-8+ju-cZn zSYURWk2N6BFya+Mq#Fq9tFslwW^GJOFpo!>HjmxYt>v?$^(> zOWir}9?^f0rxKB7n*ICGD$XM1MxpsB$jk2sjts7;_Lw0QwwXR#Pk6)L6D=g}1wHmS zf_Vp9%-%2OHmK>)I2q6G{sZa^B)5Sbx*!*Hn2+)z~$ z?1Qd0y(+A8$fjfd$ZsR2wCFtWDWyJtLPZIoOa~8<%HTGTe#4>q^e_ diff --git a/docs/packages/_example.md b/docs/packages/_example.md deleted file mode 100644 index 02fddf2..0000000 --- a/docs/packages/_example.md +++ /dev/null @@ -1,72 +0,0 @@ -# @datastream/{package} - -// ToC - -## Setup - -```bash -npm install @datastream/{package} -``` - -## Support - -| Stream | node:stream | node:stream/web | Chrome | Edge | Firefox | Safari | Comments | -| --------------------- | ----------- | --------------- | ------ | ---- | ------- | ------ | ------------ | -| {package}{name}Stream | 18.x | 18.x | 67 | 79 | 102 | 14.1 | Uses ... API | - -### NodeJS - -- pipeline: [15.0.0](https://nodejs.org/dist/latest-v18.x/docs/api/stream.html#streams-promises-api) -- fetch: [18.0.0](https://nodejs.org/en/blog/announcements/v18-release-announce/#fetch-experimental) - -### NodeJS (Web Stream) - -- ReadableStream: [18.0.0](https://nodejs.org/dist/latest-v18.x/docs/api/webstreams.html#new-readablestreamunderlyingsource--strategy) -- TransformStream: [18.0.0](https://nodejs.org/dist/latest-v18.x/docs/api/webstreams.html#new-transformstreamtransformer-writablestrategy-readablestrategy) -- WritableStream: [18.0.0](https://nodejs.org/dist/latest-v18.x/docs/api/webstreams.html#new-writablestreamunderlyingsink-strategy) -- CompressionStream (gzip, deflate): [18.0.0](https://nodejs.org/dist/latest-v18.x/docs/api/webstreams.html#new-compressionstreamformat) -- DecompressionStream (gzip, deflate): [18.0.0](https://nodejs.org/dist/latest-v18.x/docs/api/webstreams.html#new-decompressionstreamformat) -- TextEncoderStream: [18.0.0](https://nodejs.org/dist/latest-v18.x/docs/api/webstreams.html#new-textencoderstream) -- TextDecoderStream: [18.0.0](https://nodejs.org/dist/latest-v18.x/docs/api/webstreams.html#new-textdecoderstreamencoding-options) - -### Browser (Web Stream) - -- ReadableStream: [caniuse](https://caniuse.com/mdn-api_readablestream) - - -- TransformStream: [caniuse](https://caniuse.com/mdn-api_transformstream) -- WritableStream: [caniuse](https://caniuse.com/mdn-api_writablestream) -- CompressionStream (gzip, deflate): [caniuse](https://caniuse.com/mdn-api_compressionstream) -- DecompressionStream (gzip, deflate): [caniuse](https://caniuse.com/mdn-api_decompressionstream) -- TextEncoderStream: [caniuse](https://caniuse.com/mdn-api_textencoderstream) -- TextDecoderStream: [caniuse](https://caniuse.com/mdn-api_textdecoderstream) - -## Help make streams better - -Feature request(s) to +1 - -- W3C: - - webcrypto Streams: https://github.com/w3c/webcrypto/issues/73 - - webcrypto SHA3: https://github.com/w3c/webcrypto/issues/319 -- WHATWG: - - ReadableStream is async iterable: https://github.com/whatwg/streams/issues/778#issuecomment-461341033 -- Web Incubator Community Group (WICG): - - CompressionStream (brotli): https://github.com/WICG/compression/issues/34 - - CompressionStream (zstd): not found -- NodeJS TC39: -- Chrome: -- Firefox: - - CompressionStream: https://bugzilla.mozilla.org/show_bug.cgi?id=1586639 - - TextDecoderStream: https://bugzilla.mozilla.org/show_bug.cgi?id=1486949 - - TextEncoderStream: https://bugzilla.mozilla.org/show_bug.cgi?id=1486949 -- Safari: - - CompressionStream: not found - -## Streams - - - -### {package}{name}Stream ({Readable,PassThrough,Transform,Writable}) - -#### Options - -#### Example(s) diff --git a/docs/packages/aws.md b/docs/packages/aws.md deleted file mode 100644 index 062a233..0000000 --- a/docs/packages/aws.md +++ /dev/null @@ -1,386 +0,0 @@ -# aws - -

-Table of Contents - -
- - -## DynamoDB - - -### awsDynamoDBQueryStream (Readable) - - -Readable stream containing the results of a DynamoDB Query. - -#### Options -See AWS documentation [DynamoDB/Query](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html) - -IAM: `dynamodb:Query` - -#### Egress chunk - -A map of attributes and their values. - -```json -{ - "string" : { - "B": blob, - "BOOL": boolean, - "BS": [ blob ], - "L": [ "AttributeValue" ], - "M": { "string" : "AttributeValue" }, - "N": "string", - "NS": [ "string" ], - "NULL": boolean, - "S": "string", - "SS": [ "string" ] - } -} -``` - -See AWS documentation [DynamoDB/Query](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html) for `Responses` structure. - -#### Example - -```javascript -import { pipeline } from '@datastream/core' -import { awsDynamoDBQueryStream } from '@datastream/aws/dynamodb' - -const streams = [ - await awsDynamoDBQueryStream(options), - ... -] - -await pipeline(streams) -``` - - -### awsDynamoDBScanStream (Readable) - - -Readable stream containing the results of a DynamoDB Scan. - -#### Options -See AWS documentation [DynamoDB/Scan](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html) - -IAM: `dynamodb:Scan` - -#### Egress chunk - -A map of attributes and their values. - -```json -{ - "string" : { - "B": blob, - "BOOL": boolean, - "BS": [ blob ], - "L": [ "AttributeValue" ], - "M": { "string" : "AttributeValue" }, - "N": "string", - "NS": [ "string" ], - "NULL": boolean, - "S": "string", - "SS": [ "string" ] - } -} -``` - -See AWS documentation [DynamoDB/Scan](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html) for `Responses` structure. - -#### Example - -```javascript -import { pipeline } from '@datastream/core' -import { awsDynamoDBScanStream } from '@datastream/aws/dynamodb' - -const streams = [ - await awsDynamoDBScanStream(options), - ... -] - -await pipeline(streams) -``` - -### awsDynamoDBGetItemStream (Readable) - - -Readable stream containing the results of a DynamoDB BatchGetItems. - -#### Options - -- `TableName` (string): Name of the table to get items from. -- `Keys` (object[]): See AWS documentation [DynamoDB/BatchGetItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html) for `Keys` structure. -- `retryCount` (int) [0]: Starting retry count used for back-off timer, `3 ^ retryCount` -- `retryMaxCount` (int) [10]: Max number of retries before stopping - -IAM: `dynamodb:BatchGetItem` - -#### Egress chunk - -A map of attributes and their values. - -```json -{ - "string" : { - "B": blob, - "BOOL": boolean, - "BS": [ blob ], - "L": [ "AttributeValue" ], - "M": { "string" : "AttributeValue" }, - "N": "string", - "NS": [ "string" ], - "NULL": boolean, - "S": "string", - "SS": [ "string" ] - } -} -``` - -See AWS documentation [DynamoDB/BatchGetItem](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html) for `Responses` structure. - -#### Example - -```javascript -import { pipeline } from '@datastream/core' -import { awsDynamoDBGetItemStream } from '@datastream/aws/dynamodb' - -const streams = [ - await awsDynamoDBGetItemStream(options), - ... -] - -await pipeline(streams) -``` - -### awsDynamoDBPutItemStream (Writable) - - -Writable stream that sends items to DynamoDB BatchWriteItems. - -#### Options - -- `TableName` (string): Name of the table to get items from. -- `retryCount` (int) [0]: Starting retry count used for back-off timer, `3 ^ retryCount` -- `retryMaxCount` (int) [10]: Max number of retries before stopping - -IAM: `dynamodb:BatchWriteItem` - -#### Ingress chunk -A map of attributes and their values. Each entry in this map consists of an attribute name and an attribute value. Attribute values must not be null; string and binary type attributes must have lengths greater than zero; and set type attributes must not be empty. Requests that contain empty values are rejected with a ValidationException exception. - -If you specify any attributes that are part of an index key, then the data types for those attributes must match those of the schema in the table's attribute definition. - -```json -{ - "string" : { - "B": blob, - "BOOL": boolean, - "BS": [ blob ], - "L": [ "AttributeValue" ], - "M": { "string" : "AttributeValue" }, - "N": "string", - "NS": [ "string" ], - "NULL": boolean, - "S": "string", - "SS": [ "string" ] - } -} -``` - -See AWS documentation [DynamoDB/BatchWriteItem.PutRequest.Item](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html) - -#### Example - -```javascript -import { pipeline } from '@datastream/core' -import { awsDynamoDBPutItemStream } from '@datastream/aws/dynamodb' - -const streams = [ - ... - await awsDynamoDBPutItemStream(options) -] - -await pipeline(streams) -``` - -### awsDynamoDBDeleteItemStream (Writable) - - -Writable stream that sends keys to DynamoDB BatchWriteItems. - -#### Options - -- `TableName` (string) Required: Name of the table to get items from. -- `retryCount` (int) Optional (0): Starting retry count used for back-off timer, `3 ^ retryCount` -- `retryMaxCount` (int) Optional (10): Max number of retries before stopping - -IAM: `dynamodb:BatchWriteItem` - -#### Ingress chunk -A map of primary key attribute values that uniquely identify the item. Each entry in this map consists of an attribute name and an attribute value. For each primary key, you must provide all of the key attributes. For example, with a simple primary key, you only need to provide a value for the partition key. For a composite primary key, you must provide values for both the partition key and the sort key. - - -```json -{ - "string" : { - "B": blob, - "BOOL": boolean, - "BS": [ blob ], - "L": [ "AttributeValue" ], - "M": { "string" : "AttributeValue" }, - "N": "string", - "NS": [ "string" ], - "NULL": boolean, - "S": "string", - "SS": [ "string" ] - } -} -``` - -See AWS documentation [DynamoDB/BatchWriteItem.DeleteRequest.Key](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchWriteItem.html) - -#### Example - -```javascript -import { pipeline } from '@datastream/core' -import { awsDynamoDBDeleteItemStream } from '@datastream/aws/dynamodb' - -const streams = [ - ... - await awsDynamoDBDeleteItemStream(options) -] - -await pipeline(streams) -``` - -## Lambda - -### awsLambdaResponseStream (Readable) - - -Readable stream from the response of a Lambda invoke. - -### Options - -See AWS documentation [Lambda/InvokeWithResponseStream](https://docs.aws.amazon.com/lambda/latest/dg/API_InvokeWithResponseStream.html) - -#### Example - -```javascript -import { pipeline } from '@datastream/core' -import { awsLambdaResponseStream } from '@datastream/aws/lambda' - -const streams = [ - await awsLambdaResponseStream(options), - ... -] - -await pipeline(streams) -``` - - -## S3 - - - -### awsS3GetObjectStream (Readable) - -#### Support - -| node:stream | node:stream/web | Chrome | Edge | Firefox | Safari | Comments | -| ----------- | --------------- | ------ | ---- | ------- | ------ | -------- | -| 16.x | 18.0.0 | N/A | N/A | N/A | N/A | | - -#### Options - -#### Example - -```javascript -import { pipeline } from '@datastream/core' -import { awsS3GetObjectStream } from '@datastream/aws/s3' - -const streams = [ - await awsS3GetObjectStream(options), - ... -] - -await pipeline(streams) -``` - - -### awsS3PutObjectStream (Writable) - - -#### Support - -| node:stream | node:stream/web | Chrome | Edge | Firefox | Safari | Comments | -| ----------- | --------------- | ------ | ---- | ------- | ------ | -------- | -| 16.x | NO | N/A | N/A | N/A | N/A | | - -#### Options - -#### Example - -```javascript -import { pipeline } from '@datastream/core' -import { awsS3PutObjectStream } from '@datastream/aws/s3' - -const streams = [ - ... - await awsS3PutObjectStream(options) -] - -await pipeline(streams) -``` - -## SNS - - -### awsSNSPublishMessageStream (Writable) - - -## SQS - - -### awsSQSReceiveMessageStream (Readable) - - - - -### awsSQSDeleteMessageStream (Writable) - - - -### awsSQSSendMessageStream (Writable) - diff --git a/docs/packages/base64.md b/docs/packages/base64.md deleted file mode 100644 index 7cfef07..0000000 --- a/docs/packages/base64.md +++ /dev/null @@ -1,4 +0,0 @@ -# base64 - -## base64EncodeStream (Transform) -## base64DecodeStream (Transform) diff --git a/docs/packages/charset.md b/docs/packages/charset.md deleted file mode 100644 index 94cbd33..0000000 --- a/docs/packages/charset.md +++ /dev/null @@ -1,7 +0,0 @@ -# charset - -## charsetDetectStream (PassThrough) - -## charsetDecodeStream (Transform) - -## charsetEncodeStream (Transform) \ No newline at end of file diff --git a/docs/packages/compress.md b/docs/packages/compress.md deleted file mode 100644 index d9db13d..0000000 --- a/docs/packages/compress.md +++ /dev/null @@ -1,14 +0,0 @@ -# compress - - -## brotliCompressStream (Transform) -## brotliDecompressStream (Transform) - -## gzipCompressStream (Transform) -## gzipDecompressStream (Transform) - -## deflateCompressStream (Transform) -## deflateDecompressStream (Transform) - -## zstdCompressStream (Transform) -## zstdDecompressStream (Transform) diff --git a/docs/packages/core.md b/docs/packages/core.md deleted file mode 100644 index fbd2c18..0000000 --- a/docs/packages/core.md +++ /dev/null @@ -1,66 +0,0 @@ -# core - -## Functions -- `pipeline(stream[], streamOptions)`: Connects streams and awaits until completion. Returns results from stream taps. Will add in a terminating Writable if missing. -- `pipejoin(stream[])`: Connects streams and returns resulting stream for use with async iterators -- `result(stream)`: Run and combine streams result responses - -- `streamToArray(stream)`: Returns array from stream chunks. stream must not end with Writable. -- `streamToString(stream)`: Returns string from stream chunks. stream must not end with Writable. -- `streamToObject(stream)`: Returns object from stream chunks. stream must not end with Writable. -- `isReadable(stream)`: Return bool is stream is Readable -- `isWritable(stream)`: Return bool is stream is Writable -- `makeOptions(options)`: Make options interoperable between Readable/Writable and Transform -- `createReadableStream(input = '', streamOptions)`: Create a Readable stream from input (string, array, iterable) with options. -- `createPassThroughStream((chunk)=>{}, streamOptions)`: Create a Pass Through stream that allows observation of chunk while being passed through. -- `createTransformStream((chunk, enqueue)=>{}, streamOptions)`: Create a Transform stream that allows mutation of chunk before being passed. -- `createWritableStream((chunk)=>{}, streamOptions)`: Create a Writable stream that allows mutation of chunk before being passed. -- `tee(stream)`: -- `timeout(ms, {signal})`: setTimeout promise that can be aborted. - -- `streamOptions`: - - `highWaterMark` - - `chunkSize` - - `signal` - -## Null handling - -Node.js streams use `push(null)` to signal end-of-stream (EOF). To allow `null` values to flow through object-mode pipelines without terminating the stream, datastream wraps them with a sentinel Symbol (`Symbol.for("@datastream/null")`). - -This is handled automatically when using datastream's built-in functions (`createReadableStream`, `createTransformStream`, `createPassThroughStream`, `createWritableStream`, `streamToArray`). You only need to be aware of it when reading chunks directly from a stream, such as listening to `data` events or using `for await...of` on a mid-pipeline stream. - -```javascript -// Handled automatically - null values round-trip correctly -const output = await streamToArray(createReadableStream([1, null, 3])) -// [1, null, 3] - -// Direct access - you may see the sentinel Symbol -stream.on('data', (chunk) => { - // chunk may be Symbol.for("@datastream/null") instead of null -}) -``` - -## Examples - -```javascript -import { - pipejoin, - streamToArray, - createReadableStream, - createTransformStream -} from '@datastream/core' -import { csvParseStream } from '@datastream/csv' - -let count -const streams = [ - createReadableStream('a,b,c\r\n1,2,3'), - createTransformStream((chunk, enqueue) => { - chunk.b += 1 - enqueue(chunk) - }), - createTransformStream(console.log) -] - -const river = pipejoin(streams) -const output = await streamToArray(river) -``` diff --git a/docs/packages/csv.md b/docs/packages/csv.md deleted file mode 100644 index 095c47e..0000000 --- a/docs/packages/csv.md +++ /dev/null @@ -1,107 +0,0 @@ -# csv - -## csvParseStream (Transform) - -Takes CSV formatted string chunks and parses into flat object or array chunks. - -### Options - -| Option | Default | Description | -|---|---|---| -| `delimiterChar` | `,` | Field delimiter | -| `newlineChar` | `\r\n` | Row delimiter | -| `quoteChar` | `"` | Quote character | -| `escapeChar` | `quoteChar` | Escape character | -| `parser` | `csvQuotedParser` | Custom parser function | -| `chunkSize` | `2097152` | Buffer size before parsing begins | - -### Example - -```javascript -import { pipeline } from '@datastream/core' -import { csvParseStream } from '@datastream/csv' - -const streams = [ - ... - csvParseStream(options), - ... -] - -await pipeline(streams) -``` - -## csvFormatStream (Transform) - -Takes array chunks and outputs CSV formatted strings. Each array is formatted as one CSV row. - -### Options - -| Option | Default | Description | -|---|---|---| -| `delimiterChar` | `,` | Field delimiter | -| `newlineChar` | `\r\n` | Row delimiter | -| `quoteChar` | `"` | Quote character | -| `escapeChar` | `quoteChar` | Escape character | - -Values are automatically quoted when they contain: delimiters, newlines, quote characters, BOM, leading/trailing spaces, or formula triggers (`=`, `+`, `-`, `@`). - -### Example - -```javascript -import { pipeline } from '@datastream/core' -import { csvFormatStream, csvInjectHeaderStream, csvObjectToArray } from '@datastream/csv' - -const headers = ['a', 'b', 'c'] -const streams = [ - ... - csvObjectToArray({ headers }), - csvInjectHeaderStream({ header: headers }), - csvFormatStream(), - ... -] - -await pipeline(streams) -``` - -## csvInjectHeaderStream (Transform) - -Pushes a header array into the stream before the first data chunk. All subsequent chunks pass through unchanged. - -### Options - -| Option | Description | -|---|---| -| `header` | Array of header values to inject | - -### Example - -```javascript -import { csvInjectHeaderStream, csvFormatStream } from '@datastream/csv' - -const streams = [ - ... - csvInjectHeaderStream({ header: ['a', 'b', 'c'] }), - csvFormatStream(), - ... -] -``` - -## csvArrayToObject (Transform) - -Converts array chunks to objects using provided header keys. Wrapper around `objectFromEntriesStream`. - -### Options - -| Option | Description | -|---|---| -| `headers` | Array of key names | - -## csvObjectToArray (Transform) - -Converts object chunks to arrays using provided header keys. Wrapper around `objectToEntriesStream`. - -### Options - -| Option | Description | -|---|---| -| `headers` | Array of key names | diff --git a/docs/packages/digest.md b/docs/packages/digest.md deleted file mode 100644 index ba7ae79..0000000 --- a/docs/packages/digest.md +++ /dev/null @@ -1,3 +0,0 @@ -# digest - -## digestStream (PassThrough) \ No newline at end of file diff --git a/docs/packages/fetch.md b/docs/packages/fetch.md deleted file mode 100644 index 6db91aa..0000000 --- a/docs/packages/fetch.md +++ /dev/null @@ -1,5 +0,0 @@ -# fetch - -## fetchResponseStream (Readable) - -## fetchRequestStream (Writable) \ No newline at end of file diff --git a/docs/packages/file.md b/docs/packages/file.md deleted file mode 100644 index c97aa56..0000000 --- a/docs/packages/file.md +++ /dev/null @@ -1,4 +0,0 @@ -# file - -## fileReadStream (Readable) -## fileWriteStream (Writable) diff --git a/docs/packages/indexeddb.md b/docs/packages/indexeddb.md deleted file mode 100644 index e32bb1b..0000000 --- a/docs/packages/indexeddb.md +++ /dev/null @@ -1,4 +0,0 @@ -# indexeddb - -## indexedDBReadStream (Readable) -## indexedDBWriteStream (Writable) diff --git a/docs/packages/ipfs.md b/docs/packages/ipfs.md deleted file mode 100644 index 69487ff..0000000 --- a/docs/packages/ipfs.md +++ /dev/null @@ -1,4 +0,0 @@ -# ipfs - -## ipfsGetStream (Readable) -## ipfsAddStream (PassThrough) diff --git a/docs/packages/object.md b/docs/packages/object.md deleted file mode 100644 index 719d12c..0000000 --- a/docs/packages/object.md +++ /dev/null @@ -1,16 +0,0 @@ -# object - -## objectReadableStream (Readable) -## objectCountStream (PassThrough) -## objectBatchStream (Transform) -## objectPivotLongToWideStream (Transform) -## objectPivotWideToLongStream (Transform) -## objectKeyValueStream (Transform) -## objectKeyValuesStream (Transform) -## objectKeyJoinStream (Transform) -## objectKeyMapStream (Transform) -## objectValueMapStream (Transform) -## objectPickStream (Transform) -## objectOmitStream (Transform) -## objectFromEntriesStream (Transform) -## objectSkipConsecutiveDuplicatesStream (Transform) \ No newline at end of file diff --git a/docs/packages/string.md b/docs/packages/string.md deleted file mode 100644 index 29f2fb2..0000000 --- a/docs/packages/string.md +++ /dev/null @@ -1,8 +0,0 @@ -# string - -## stringReadableStream (Readable) -## stringLengthStream (PassThrough) -## stringCountStream (PassThrough) -## stringSkipConsecutiveDuplicates (Transform) -## stringReplaceStream (Transform) -## stringSplitStream (Transform) \ No newline at end of file diff --git a/docs/packages/validate.md b/docs/packages/validate.md deleted file mode 100644 index e344697..0000000 --- a/docs/packages/validate.md +++ /dev/null @@ -1,3 +0,0 @@ -# validate - -## validateStream (Transform) \ No newline at end of file diff --git a/docs/patterns/file.md b/docs/patterns/file.md deleted file mode 100644 index a2c530e..0000000 --- a/docs/patterns/file.md +++ /dev/null @@ -1,30 +0,0 @@ -# file - -## fileReadStream (Readable) - -```javascript -import { createReadStream } from 'node:fs' - -const fileReadableStream = (fileName, streamOptions) => { - return createReadStream(fileName) -} - -export default { - readableStream: fileReadableStream -} -``` - -## fileWriteStream (Writable) - -```javascript -import { createWriteStream } from 'node:fs' - - -export const fileWritableStream = (fileName, streamOptions) => { - return createWriteStream(fileName) -} - -export default { - writableStream: fileWritableStream -} -``` diff --git a/docs/patterns/image.md b/docs/patterns/image.md deleted file mode 100644 index 7e76faa..0000000 --- a/docs/patterns/image.md +++ /dev/null @@ -1,3 +0,0 @@ -# image - -## sharp (Transform) \ No newline at end of file diff --git a/docs/patterns/sql.md b/docs/patterns/sql.md deleted file mode 100644 index 437ea55..0000000 --- a/docs/patterns/sql.md +++ /dev/null @@ -1,9 +0,0 @@ -# sql - -## pgCopyToStream (Readable) -## pgCopuFromStream (Writable) - -## postgresCopyToStream (Readable) -## postgresCopyFromStream (Writable) - - From 4d28bd02cf5399059ba1eda417f5dabe514515c5 Mon Sep 17 00:00:00 2001 From: will Farrell Date: Thu, 16 Apr 2026 08:39:14 -0600 Subject: [PATCH 2/8] fix: add in extra guards Signed-off-by: will Farrell --- .github/CONTRIBUTING.md | 69 +++++++++++++++++++++++++++++++ .github/package.json | 2 +- packages/compress/brotli.node.js | 4 ++ packages/compress/deflate.node.js | 4 ++ packages/compress/deflate.web.js | 28 ++++++++++++- packages/compress/gzip.node.js | 4 ++ packages/compress/gzip.web.js | 28 ++++++++++++- packages/compress/zstd.node.js | 4 ++ packages/core/index.web.js | 5 ++- packages/encrypt/index.node.js | 4 ++ packages/encrypt/index.web.js | 15 ++++++- packages/fetch/index.js | 9 +++- packages/json/README.md | 52 +++++++++++++++++++++++ packages/json/package.json | 3 +- 14 files changed, 219 insertions(+), 12 deletions(-) create mode 100644 packages/json/README.md diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index b6c9d79..1b41e98 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -5,6 +5,75 @@ In the spirit of Open Source Software, everyone is very welcome to contribute to Before contributing to the project, make sure to have a look at our [Code of Conduct](/.github/CODE_OF_CONDUCT.md). +## Development Setup + +### Prerequisites + +- Node.js >= 24 +- npm (comes with Node.js) + +### Getting Started + +```bash +git clone https://github.com/willfarrell/datastream.git +cd datastream +npm install +``` + +This installs all dependencies across all workspaces (`packages/*`, `websites/*`, `.github`). + + +## Project Structure + +``` +packages/ # npm packages (core, csv, compress, encrypt, etc.) +websites/ # documentation website (datastream.js.org) +.github/ # CI workflows, workflow-specific dependencies +bin/ # build scripts +docs/ # additional documentation +``` + +Each package in `packages/` has both a Node.js stream implementation (`index.node.js`) and a Web Streams API implementation (`index.web.js`), selected via conditional exports. + + +## Testing + +```bash +# Run all checks (lint, unit, types, sast, perf, dast) +npm test + +# Run specific test suites +npm run test:lint # Biome linting +npm run test:unit # Unit tests (both Node.js and Web stream variants) +npm run test:unit:node # Unit tests (Node.js streams only) +npm run test:unit:web # Unit tests (Web Streams API only) +npm run test:types # TypeScript type checking (tstyche) +npm run test:perf # Performance benchmarks (tinybench) +npm run test:dast # Fuzz tests (fast-check) + +# Run tests for a single package +node --test ./packages/core +``` + +Unit tests use Node.js built-in `node:test`. Both `--conditions=node` and `--conditions=webstream` are tested to cover both stream implementations. + + +## Building + +```bash +npm run build +``` + +Produces dual ESM builds per package via esbuild: `*.node.mjs` (Node.js) and `*.web.mjs` (Web Streams API), both with external source maps. + + +## Code Style + +- Formatting and linting are handled by [Biome](https://biomejs.dev/) (`biome.json`) +- Commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) enforced by commitlint +- Husky pre-commit hooks run linting and tests automatically + + ## Licence Licensed under [MIT Licence](LICENSE). Copyright (c) 2026 [will Farrell](https://github.com/willfarrell), and the [datastream team](https://github.com/willfarrell/datastream/graphs/contributors). diff --git a/.github/package.json b/.github/package.json index d329f2f..490a4ba 100644 --- a/.github/package.json +++ b/.github/package.json @@ -1,6 +1,6 @@ { "name": "@datastream/github-workflows", - "version": "0.2.0", + "version": "0.3.0", "private": true, "engines": { "node": ">=24.0" diff --git a/packages/compress/brotli.node.js b/packages/compress/brotli.node.js index 094db80..1f75985 100644 --- a/packages/compress/brotli.node.js +++ b/packages/compress/brotli.node.js @@ -29,6 +29,7 @@ export const brotliDecompressStream = (options = {}, streamOptions = {}) => { if (chunk !== null) { outputSize += chunk.length; if (outputSize > maxOutputSize) { + stream.push = originalPush; stream.destroy( new Error( `Decompression output exceeds maxOutputSize (${maxOutputSize} bytes)`, @@ -39,6 +40,9 @@ export const brotliDecompressStream = (options = {}, streamOptions = {}) => { } return originalPush(chunk); }; + stream.on("close", () => { + stream.push = originalPush; + }); } return stream; }; diff --git a/packages/compress/deflate.node.js b/packages/compress/deflate.node.js index 810cb12..0b36661 100644 --- a/packages/compress/deflate.node.js +++ b/packages/compress/deflate.node.js @@ -17,6 +17,7 @@ export const deflateDecompressStream = (options = {}, streamOptions = {}) => { if (chunk !== null) { outputSize += chunk.length; if (outputSize > maxOutputSize) { + stream.push = originalPush; stream.destroy( new Error( `Decompression output exceeds maxOutputSize (${maxOutputSize} bytes)`, @@ -27,6 +28,9 @@ export const deflateDecompressStream = (options = {}, streamOptions = {}) => { } return originalPush(chunk); }; + stream.on("close", () => { + stream.push = originalPush; + }); } return stream; }; diff --git a/packages/compress/deflate.web.js b/packages/compress/deflate.web.js index 1eda13f..fdc9a80 100644 --- a/packages/compress/deflate.web.js +++ b/packages/compress/deflate.web.js @@ -6,8 +6,32 @@ // - not supported on firefox - https://bugzilla.mozilla.org/show_bug.cgi?id=1586639 // - not supported in safari -export const deflateCompressStream = (_options = {}, _streamOptions = {}) => { - return new CompressionStream("deflate"); +export const deflateCompressStream = (options = {}, _streamOptions = {}) => { + const { maxOutputSize } = options; + const compressor = new CompressionStream("deflate"); + if (maxOutputSize != null) { + let outputSize = 0; + const transformer = { + transform(chunk, controller) { + outputSize += chunk.byteLength; + if (outputSize > maxOutputSize) { + controller.error( + new Error( + `Compression output exceeds maxOutputSize (${maxOutputSize} bytes)`, + ), + ); + return; + } + controller.enqueue(chunk); + }, + }; + const limiter = new TransformStream(transformer); + return { + readable: compressor.readable.pipeThrough(limiter), + writable: compressor.writable, + }; + } + return compressor; }; export const deflateDecompressStream = (options = {}, _streamOptions = {}) => { const { maxOutputSize } = options; diff --git a/packages/compress/gzip.node.js b/packages/compress/gzip.node.js index 566e57a..727945b 100644 --- a/packages/compress/gzip.node.js +++ b/packages/compress/gzip.node.js @@ -16,6 +16,7 @@ export const gzipDecompressStream = (options = {}, streamOptions = {}) => { if (chunk !== null) { outputSize += chunk.length; if (outputSize > maxOutputSize) { + stream.push = originalPush; stream.destroy( new Error( `Decompression output exceeds maxOutputSize (${maxOutputSize} bytes)`, @@ -26,6 +27,9 @@ export const gzipDecompressStream = (options = {}, streamOptions = {}) => { } return originalPush(chunk); }; + stream.on("close", () => { + stream.push = originalPush; + }); } return stream; }; diff --git a/packages/compress/gzip.web.js b/packages/compress/gzip.web.js index e6a465b..7776ee1 100644 --- a/packages/compress/gzip.web.js +++ b/packages/compress/gzip.web.js @@ -6,8 +6,32 @@ // - not supported on firefox - https://bugzilla.mozilla.org/show_bug.cgi?id=1586639 // - not supported in safari -export const gzipCompressStream = (_options = {}, _streamOptions = {}) => { - return new CompressionStream("gzip"); +export const gzipCompressStream = (options = {}, _streamOptions = {}) => { + const { maxOutputSize } = options; + const compressor = new CompressionStream("gzip"); + if (maxOutputSize != null) { + let outputSize = 0; + const transformer = { + transform(chunk, controller) { + outputSize += chunk.byteLength; + if (outputSize > maxOutputSize) { + controller.error( + new Error( + `Compression output exceeds maxOutputSize (${maxOutputSize} bytes)`, + ), + ); + return; + } + controller.enqueue(chunk); + }, + }; + const limiter = new TransformStream(transformer); + return { + readable: compressor.readable.pipeThrough(limiter), + writable: compressor.writable, + }; + } + return compressor; }; export const gzipDecompressStream = (options = {}, _streamOptions = {}) => { const { maxOutputSize } = options; diff --git a/packages/compress/zstd.node.js b/packages/compress/zstd.node.js index 1899c16..782617a 100644 --- a/packages/compress/zstd.node.js +++ b/packages/compress/zstd.node.js @@ -22,6 +22,7 @@ export const zstdDecompressStream = (options = {}, _streamOptions = {}) => { if (chunk !== null) { outputSize += chunk.length; if (outputSize > maxOutputSize) { + stream.push = originalPush; stream.destroy( new Error( `Decompression output exceeds maxOutputSize (${maxOutputSize} bytes)`, @@ -32,6 +33,9 @@ export const zstdDecompressStream = (options = {}, _streamOptions = {}) => { } return originalPush(chunk); }; + stream.on("close", () => { + stream.push = originalPush; + }); } return stream; }; diff --git a/packages/core/index.web.js b/packages/core/index.web.js index b7650ef..18049ea 100644 --- a/packages/core/index.web.js +++ b/packages/core/index.web.js @@ -104,14 +104,15 @@ export const makeOptions = ({ signal, ...streamOptions } = {}) => { + const size = chunkSize != null ? () => chunkSize : undefined; return { writableStrategy: { highWaterMark, - size: { chunk: chunkSize }, + size, }, readableStrategy: { highWaterMark, - size: { chunk: chunkSize }, + size, }, signal, ...streamOptions, diff --git a/packages/encrypt/index.node.js b/packages/encrypt/index.node.js index 141509f..f6abcbb 100644 --- a/packages/encrypt/index.node.js +++ b/packages/encrypt/index.node.js @@ -107,6 +107,7 @@ export const decryptStream = ( if (chunk !== null) { outputSize += chunk.length; if (outputSize > maxOutputSize) { + stream.push = originalPush; stream.destroy( new Error( `Decryption output exceeds maxOutputSize (${maxOutputSize} bytes)`, @@ -117,6 +118,9 @@ export const decryptStream = ( } return originalPush(chunk); }; + stream.on("close", () => { + stream.push = originalPush; + }); } return stream; }; diff --git a/packages/encrypt/index.web.js b/packages/encrypt/index.web.js index 9a05f15..7f1acb4 100644 --- a/packages/encrypt/index.web.js +++ b/packages/encrypt/index.web.js @@ -244,7 +244,10 @@ const aesCtrDecrypt = async ({ key, iv, maxOutputSize }, streamOptions) => { }; // ChaCha20-Poly1305: requires optional peer dep -const chacha20Encrypt = async ({ key, iv, aad }, streamOptions) => { +const chacha20Encrypt = async ( + { key, iv, aad, maxInputSize }, + streamOptions, +) => { validateKey(key); validateAad(aad); let sodium; @@ -258,11 +261,19 @@ const chacha20Encrypt = async ({ key, iv, aad }, streamOptions) => { } iv ??= sodium.randombytes_buf(12); validateIv(iv, "CHACHA20-POLY1305"); + maxInputSize ??= DEFAULT_MAX_INPUT_SIZE; const chunks = []; + let inputSize = 0; let authTag; const transform = (chunk) => { const buf = chunk instanceof Uint8Array ? chunk : new TextEncoder().encode(chunk); + inputSize += buf.byteLength; + if (inputSize > maxInputSize) { + throw new Error( + `Encryption input exceeds maxInputSize (${maxInputSize} bytes). Use AES-256-CTR for large data.`, + ); + } chunks.push(buf); }; const flush = (enqueue) => { @@ -343,7 +354,7 @@ export const encryptStream = async ( return aesCtrEncrypt({ key, iv }, streamOptions); } if (algorithm === "CHACHA20-POLY1305") { - return chacha20Encrypt({ key, iv, aad }, streamOptions); + return chacha20Encrypt({ key, iv, aad, maxInputSize }, streamOptions); } throw new Error(`Unsupported algorithm: ${algorithm}`); }; diff --git a/packages/fetch/index.js b/packages/fetch/index.js index 9d9491c..c33d89a 100644 --- a/packages/fetch/index.js +++ b/packages/fetch/index.js @@ -100,8 +100,13 @@ async function* fetchGenerator(fetchOptions, streamOptions) { } options.__origin = new URL(options.url).origin; const response = await fetchUnknown(options, streamOptions); - for await (const chunk of response) { - yield chunk; + try { + for await (const chunk of response) { + yield chunk; + } + } catch (error) { + await response?.cancel?.(); + throw error; } // ensure there is rate limiting between req with different options rateLimitTimestamp = options.rateLimitTimestamp; diff --git a/packages/json/README.md b/packages/json/README.md new file mode 100644 index 0000000..01f83cc --- /dev/null +++ b/packages/json/README.md @@ -0,0 +1,52 @@ +
+

<datastream> `json`

+ datastream logo +

JSON and NDJSON (JSON Lines) parsing and formatting transform streams.

+

+ GitHub Actions unit test status + GitHub Actions dast test status + GitHub Actions perf test status + GitHub Actions SAST test status + GitHub Actions lint test status +
+ npm version + npm install size + + npm weekly downloads + + npm provenance +
+ Open Source Security Foundation (OpenSSF) Scorecard + SLSA 3 + + Checked with Biome + Conventional Commits + + code coverage +

+

You can read the documentation at: https://datastream.js.org

+
+ + +## Install + +To install datastream you can use NPM: + +```bash +npm install --save @datastream/json +``` + + +## Documentation and examples + +For documentation and examples, refer to the main [datastream monorepo on GitHub](https://github.com/willfarrell/datastream) or [datastream official website](https://datastream.js.org). + + +## Contributing + +Everyone is very welcome to contribute to this repository. Feel free to [raise issues](https://github.com/willfarrell/datastream/issues) or to [submit Pull Requests](https://github.com/willfarrell/datastream/pulls). + + +## License + +Licensed under [MIT License](LICENSE). Copyright (c) 2026 [will Farrell](https://github.com/willfarrell), and [datastream contributors](https://github.com/willfarrell/datastream/graphs/contributors). diff --git a/packages/json/package.json b/packages/json/package.json index ee0b73a..fdd458b 100644 --- a/packages/json/package.json +++ b/packages/json/package.json @@ -38,7 +38,8 @@ ], "scripts": { "test": "npm run test:unit", - "test:unit": "node --test" + "test:unit": "node --test", + "test:benchmark": "node __benchmarks__/index.js" }, "license": "MIT", "keywords": [ From e6239659151df590b3a7e66d97141220bf099902 Mon Sep 17 00:00:00 2001 From: will Farrell Date: Thu, 16 Apr 2026 09:05:49 -0600 Subject: [PATCH 3/8] chore: update types Signed-off-by: will Farrell --- packages/aws/cloudwatch-logs.d.ts | 6 +- packages/aws/dynamodb.d.ts | 18 ++-- packages/aws/kinesis.d.ts | 10 ++- packages/aws/lambda.d.ts | 4 +- packages/aws/s3.d.ts | 14 ++- packages/aws/sns.d.ts | 4 +- packages/aws/sqs.d.ts | 12 ++- packages/base64/index.d.ts | 10 +-- packages/charset/decode.d.ts | 4 +- packages/charset/detect.d.ts | 8 +- packages/charset/encode.d.ts | 4 +- packages/compress/brotli.d.ts | 19 ++-- packages/compress/deflate.d.ts | 20 +++-- packages/compress/gzip.d.ts | 19 ++-- packages/compress/index.test.js | 90 +++++++++++++++++++ packages/compress/protobuf.d.ts | 6 +- packages/compress/zstd.d.ts | 19 ++-- packages/core/index.node.js | 25 +++--- packages/core/index.web.js | 9 +- packages/csv/index.d.ts | 27 +++--- packages/digest/index.d.ts | 8 +- packages/digest/index.test.js | 46 ++++++++++ packages/encrypt/index.d.ts | 10 ++- packages/encrypt/index.test.js | 40 +++++++++ packages/encrypt/index.tst.ts | 71 +++++++++++++++ packages/fetch/index.d.ts | 11 ++- packages/fetch/index.js | 30 ++++--- packages/file/index.d.ts | 10 ++- packages/indexeddb/index.d.ts | 10 ++- packages/indexeddb/index.tst.ts | 37 ++++++++ packages/ipfs/index.d.ts | 11 ++- packages/json/index.d.ts | 14 +-- packages/object/index.d.ts | 40 +++++---- packages/string/index.d.ts | 26 +++--- packages/string/index.js | 15 +++- packages/validate/index.d.ts | 8 +- .../routes/docs/packages/compress/+page.md | 17 +++- .../src/routes/docs/packages/core/+page.md | 20 +++++ 38 files changed, 607 insertions(+), 145 deletions(-) create mode 100644 packages/encrypt/index.tst.ts create mode 100644 packages/indexeddb/index.tst.ts diff --git a/packages/aws/cloudwatch-logs.d.ts b/packages/aws/cloudwatch-logs.d.ts index 3933bd6..d081df1 100644 --- a/packages/aws/cloudwatch-logs.d.ts +++ b/packages/aws/cloudwatch-logs.d.ts @@ -1,6 +1,6 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions } from "@datastream/core"; +import type { DatastreamReadable, StreamOptions } from "@datastream/core"; export function awsCloudWatchLogsSetClient(cwlClient: unknown): void; @@ -18,7 +18,7 @@ export function awsCloudWatchLogsGetLogEventsStream( [key: string]: unknown; }, streamOptions?: StreamOptions, -): Promise; +): Promise; export function awsCloudWatchLogsFilterLogEventsStream( options: { @@ -33,4 +33,4 @@ export function awsCloudWatchLogsFilterLogEventsStream( [key: string]: unknown; }, streamOptions?: StreamOptions, -): Promise; +): Promise; diff --git a/packages/aws/dynamodb.d.ts b/packages/aws/dynamodb.d.ts index 43ed3c8..9e03af1 100644 --- a/packages/aws/dynamodb.d.ts +++ b/packages/aws/dynamodb.d.ts @@ -1,6 +1,10 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions } from "@datastream/core"; +import type { + DatastreamReadable, + DatastreamWritable, + StreamOptions, +} from "@datastream/core"; export function awsDynamoDBSetClient( ddbClient: unknown, @@ -14,7 +18,7 @@ export function awsDynamoDBQueryStream( [key: string]: unknown; }, streamOptions?: StreamOptions, -): Promise; +): Promise; export function awsDynamoDBScanStream( options: { @@ -23,7 +27,7 @@ export function awsDynamoDBScanStream( [key: string]: unknown; }, streamOptions?: StreamOptions, -): Promise; +): Promise; export function awsDynamoDBExecuteStatementStream( options: { @@ -33,7 +37,7 @@ export function awsDynamoDBExecuteStatementStream( [key: string]: unknown; }, streamOptions?: StreamOptions, -): Promise; +): Promise; export function awsDynamoDBGetItemStream( options: { @@ -45,7 +49,7 @@ export function awsDynamoDBGetItemStream( [key: string]: unknown; }, streamOptions?: StreamOptions, -): Promise; +): Promise; export function awsDynamoDBPutItemStream( options: { @@ -54,7 +58,7 @@ export function awsDynamoDBPutItemStream( [key: string]: unknown; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamWritable; export function awsDynamoDBDeleteItemStream( options: { @@ -63,4 +67,4 @@ export function awsDynamoDBDeleteItemStream( [key: string]: unknown; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamWritable; diff --git a/packages/aws/kinesis.d.ts b/packages/aws/kinesis.d.ts index be8e906..ff6eee2 100644 --- a/packages/aws/kinesis.d.ts +++ b/packages/aws/kinesis.d.ts @@ -1,6 +1,10 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions } from "@datastream/core"; +import type { + DatastreamReadable, + DatastreamWritable, + StreamOptions, +} from "@datastream/core"; export function awsKinesisSetClient(kinesisClient: unknown): void; @@ -13,7 +17,7 @@ export function awsKinesisGetRecordsStream( [key: string]: unknown; }, streamOptions?: StreamOptions, -): Promise; +): Promise; export function awsKinesisPutRecordsStream( options: { @@ -23,4 +27,4 @@ export function awsKinesisPutRecordsStream( [key: string]: unknown; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamWritable; diff --git a/packages/aws/lambda.d.ts b/packages/aws/lambda.d.ts index ef15bb3..0e45de2 100644 --- a/packages/aws/lambda.d.ts +++ b/packages/aws/lambda.d.ts @@ -1,11 +1,11 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions } from "@datastream/core"; +import type { DatastreamReadable, StreamOptions } from "@datastream/core"; export function awsLambdaSetClient(lambdaClient: unknown): void; export function awsLambdaReadableStream( lambdaOptions: Record | Record[], streamOptions?: StreamOptions, -): unknown; +): DatastreamReadable; export { awsLambdaReadableStream as awsLambdaResponseStream }; diff --git a/packages/aws/s3.d.ts b/packages/aws/s3.d.ts index 0db1863..ae63e83 100644 --- a/packages/aws/s3.d.ts +++ b/packages/aws/s3.d.ts @@ -1,6 +1,12 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions, StreamResult } from "@datastream/core"; +import type { + DatastreamPassThrough, + DatastreamReadable, + DatastreamWritable, + StreamOptions, + StreamResult, +} from "@datastream/core"; export function awsS3SetClient(s3Client: unknown): void; @@ -12,7 +18,7 @@ export function awsS3GetObjectStream( [key: string]: unknown; }, streamOptions?: StreamOptions, -): Promise; +): Promise; export function awsS3PutObjectStream( options: { @@ -24,7 +30,7 @@ export function awsS3PutObjectStream( [key: string]: unknown; }, streamOptions?: StreamOptions, -): unknown & { +): DatastreamWritable & { result: () => Promise>; }; @@ -35,7 +41,7 @@ export function awsS3ChecksumStream( resultKey?: string; }, streamOptions?: StreamOptions, -): unknown & { +): DatastreamPassThrough & { result: () => StreamResult<{ checksum: string; checksums: string[]; diff --git a/packages/aws/sns.d.ts b/packages/aws/sns.d.ts index 65fe082..b4f3eb5 100644 --- a/packages/aws/sns.d.ts +++ b/packages/aws/sns.d.ts @@ -1,6 +1,6 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions } from "@datastream/core"; +import type { DatastreamWritable, StreamOptions } from "@datastream/core"; export function awsSNSSetClient(snsClient: unknown): void; @@ -11,4 +11,4 @@ export function awsSNSPublishMessageStream( [key: string]: unknown; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamWritable; diff --git a/packages/aws/sqs.d.ts b/packages/aws/sqs.d.ts index 1f47400..f1f3013 100644 --- a/packages/aws/sqs.d.ts +++ b/packages/aws/sqs.d.ts @@ -1,6 +1,10 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions } from "@datastream/core"; +import type { + DatastreamReadable, + DatastreamWritable, + StreamOptions, +} from "@datastream/core"; export function awsSQSSetClient(sqsClient: unknown): void; @@ -13,7 +17,7 @@ export function awsSQSReceiveMessageStream( [key: string]: unknown; }, streamOptions?: StreamOptions, -): Promise; +): Promise; export function awsSQSDeleteMessageStream( options: { @@ -22,7 +26,7 @@ export function awsSQSDeleteMessageStream( [key: string]: unknown; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamWritable; export function awsSQSSendMessageStream( options: { @@ -31,4 +35,4 @@ export function awsSQSSendMessageStream( [key: string]: unknown; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamWritable; diff --git a/packages/base64/index.d.ts b/packages/base64/index.d.ts index ecf1045..1c809b3 100644 --- a/packages/base64/index.d.ts +++ b/packages/base64/index.d.ts @@ -1,15 +1,15 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions } from "@datastream/core"; +import type { DatastreamTransform, StreamOptions } from "@datastream/core"; export function base64EncodeStream( - options?: Record, + options?: {}, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function base64DecodeStream( - options?: Record, + options?: {}, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; declare const _default: { encodeStream: typeof base64EncodeStream; diff --git a/packages/charset/decode.d.ts b/packages/charset/decode.d.ts index be4f526..fc27289 100644 --- a/packages/charset/decode.d.ts +++ b/packages/charset/decode.d.ts @@ -1,8 +1,8 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions } from "@datastream/core"; +import type { DatastreamTransform, StreamOptions } from "@datastream/core"; export function charsetDecodeStream( options?: { charset?: string }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; diff --git a/packages/charset/detect.d.ts b/packages/charset/detect.d.ts index 6dd1c61..816d212 100644 --- a/packages/charset/detect.d.ts +++ b/packages/charset/detect.d.ts @@ -1,12 +1,16 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions, StreamResult } from "@datastream/core"; +import type { + DatastreamPassThrough, + StreamOptions, + StreamResult, +} from "@datastream/core"; export function charsetDetectStream( options?: { resultKey?: string; }, streamOptions?: StreamOptions, -): unknown & { +): DatastreamPassThrough & { result: () => StreamResult<{ charset: string; confidence: number }>; }; diff --git a/packages/charset/encode.d.ts b/packages/charset/encode.d.ts index 60e7fde..2a63803 100644 --- a/packages/charset/encode.d.ts +++ b/packages/charset/encode.d.ts @@ -1,8 +1,8 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions } from "@datastream/core"; +import type { DatastreamTransform, StreamOptions } from "@datastream/core"; export function charsetEncodeStream( options?: { charset?: string }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; diff --git a/packages/compress/brotli.d.ts b/packages/compress/brotli.d.ts index d323e2b..554d847 100644 --- a/packages/compress/brotli.d.ts +++ b/packages/compress/brotli.d.ts @@ -1,12 +1,21 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions } from "@datastream/core"; +import type { DatastreamTransform, StreamOptions } from "@datastream/core"; + +export interface BrotliCompressOptions { + quality?: number; + maxOutputSize?: number; +} + +export interface BrotliDecompressOptions { + maxOutputSize?: number; +} export function brotliCompressStream( - options?: { quality?: number }, + options?: BrotliCompressOptions, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function brotliDecompressStream( - options?: Record, + options?: BrotliDecompressOptions, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; diff --git a/packages/compress/deflate.d.ts b/packages/compress/deflate.d.ts index e6c69f9..632a10f 100644 --- a/packages/compress/deflate.d.ts +++ b/packages/compress/deflate.d.ts @@ -1,12 +1,22 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions } from "@datastream/core"; +import type { DatastreamTransform, StreamOptions } from "@datastream/core"; + +export interface DeflateCompressOptions { + quality?: number; + level?: number; + maxOutputSize?: number; +} + +export interface DeflateDecompressOptions { + maxOutputSize?: number; +} export function deflateCompressStream( - options?: Record, + options?: DeflateCompressOptions, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function deflateDecompressStream( - options?: Record, + options?: DeflateDecompressOptions, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; diff --git a/packages/compress/gzip.d.ts b/packages/compress/gzip.d.ts index 74ebad9..7e9c0ff 100644 --- a/packages/compress/gzip.d.ts +++ b/packages/compress/gzip.d.ts @@ -1,12 +1,21 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions } from "@datastream/core"; +import type { DatastreamTransform, StreamOptions } from "@datastream/core"; + +export interface GzipCompressOptions { + quality?: number; + maxOutputSize?: number; +} + +export interface GzipDecompressOptions { + maxOutputSize?: number; +} export function gzipCompressStream( - options?: Record, + options?: GzipCompressOptions, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function gzipDecompressStream( - options?: Record, + options?: GzipDecompressOptions, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; diff --git a/packages/compress/index.test.js b/packages/compress/index.test.js index 466c5c5..3d77e7e 100644 --- a/packages/compress/index.test.js +++ b/packages/compress/index.test.js @@ -155,3 +155,93 @@ if (variant === "node") { strictEqual(output, compressibleBody); }); } + +// *** web variant decompression bomb protection *** // +// *** protobuf *** // +let hasProtobuf = false; +try { + const protobuf = await import("protobufjs"); + hasProtobuf = !!protobuf; +} catch { + // protobufjs not installed +} + +if (hasProtobuf) { + const protobuf = await import("protobufjs"); + const { protobufSerializeStream, protobufDeserializeStream } = await import( + "@datastream/compress/protobuf" + ); + + const TestType = new protobuf.Type("TestMessage") + .add(new protobuf.Field("name", 1, "string")) + .add(new protobuf.Field("value", 2, "int32")); + new protobuf.Root().define("test").add(TestType); + + test(`${variant}: protobuf roundtrip serialize/deserialize`, async (_t) => { + const input = [ + { name: "a", value: 1 }, + { name: "b", value: 2 }, + ]; + const serialize = protobufSerializeStream({ Type: TestType }); + const deserialize = protobufDeserializeStream({ Type: TestType }); + const streams = [createReadableStream(input), serialize, deserialize]; + const output = await streamToString(pipejoin(streams)); + ok(output.includes("a")); + }); +} + +if (variant === "webstream") { + test(`${variant}: gzipDecompressStream should enforce maxOutputSize`, async (_t) => { + const input = gzipSync(compressibleBody); + const streams = [ + createReadableStream(input), + gzipDecompressStream({ maxOutputSize: 100 }), + ]; + try { + await pipeline(streams); + throw new Error("Should have thrown"); + } catch (e) { + ok(e.message.includes("maxOutputSize")); + } + }); + + test(`${variant}: deflateDecompressStream should enforce maxOutputSize`, async (_t) => { + const input = deflateSync(compressibleBody); + const streams = [ + createReadableStream(input), + deflateDecompressStream({ maxOutputSize: 100 }), + ]; + try { + await pipeline(streams); + throw new Error("Should have thrown"); + } catch (e) { + ok(e.message.includes("maxOutputSize")); + } + }); + + test(`${variant}: gzipCompressStream should enforce maxOutputSize`, async (_t) => { + const streams = [ + createReadableStream(compressibleBody), + gzipCompressStream({ maxOutputSize: 10 }), + ]; + try { + await pipeline(streams); + throw new Error("Should have thrown"); + } catch (e) { + ok(e.message.includes("maxOutputSize")); + } + }); + + test(`${variant}: deflateCompressStream should enforce maxOutputSize`, async (_t) => { + const streams = [ + createReadableStream(compressibleBody), + deflateCompressStream({ maxOutputSize: 10 }), + ]; + try { + await pipeline(streams); + throw new Error("Should have thrown"); + } catch (e) { + ok(e.message.includes("maxOutputSize")); + } + }); +} diff --git a/packages/compress/protobuf.d.ts b/packages/compress/protobuf.d.ts index 42ff134..3f55cbe 100644 --- a/packages/compress/protobuf.d.ts +++ b/packages/compress/protobuf.d.ts @@ -1,6 +1,6 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions } from "@datastream/core"; +import type { DatastreamTransform, StreamOptions } from "@datastream/core"; export interface ProtobufType { encode(message: unknown): { finish(): Uint8Array }; @@ -11,8 +11,8 @@ export interface ProtobufType { export function protobufSerializeStream( options?: { Type?: ProtobufType }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function protobufDeserializeStream( options?: { Type?: ProtobufType }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; diff --git a/packages/compress/zstd.d.ts b/packages/compress/zstd.d.ts index 1bc770d..834bca1 100644 --- a/packages/compress/zstd.d.ts +++ b/packages/compress/zstd.d.ts @@ -1,12 +1,21 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions } from "@datastream/core"; +import type { DatastreamTransform, StreamOptions } from "@datastream/core"; + +export interface ZstdCompressOptions { + quality?: number; + maxOutputSize?: number; +} + +export interface ZstdDecompressOptions { + maxOutputSize?: number; +} export function zstdCompressStream( - options?: Record, + options?: ZstdCompressOptions, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function zstdDecompressStream( - options?: Record, + options?: ZstdDecompressOptions, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; diff --git a/packages/core/index.node.js b/packages/core/index.node.js index 7d57e25..8c5b193 100644 --- a/packages/core/index.node.js +++ b/packages/core/index.node.js @@ -28,7 +28,9 @@ export const pipeline = async (streams, streamOptions = {}) => { export const pipejoin = ( streams, onError = (e) => { - throw e; + process.nextTick(() => { + throw e; + }); }, ) => { const pipeline = streams.reduce((pipeline, stream, idx) => { @@ -62,7 +64,7 @@ export const backpressureGauge = (streams) => { const value = values[i]; metrics[keys[i]] = { timeline: [], total: {} }; let timestamp; - let startTimestamp; + const startTimestamp = Date.now(); value.on("pause", () => { timestamp = Date.now(); // process.hrtime.bigint() }); @@ -71,8 +73,6 @@ export const backpressureGauge = (streams) => { // Number.parseInt( (process.hrtime.bigint() - pauseTimestamp).toString() , 10 ) / 1_000_000 // ms const duration = Date.now() - timestamp; metrics[keys[i]].timeline.push({ timestamp, duration }); - } else { - startTimestamp = Date.now(); } }); value.on("end", () => { @@ -289,7 +289,6 @@ export const makeOptions = ({ export const createReadableStream = (input, streamOptions = {}) => { if (input === undefined) { const maxQueueSize = streamOptions.highWaterMark ?? 1024; - let queueSize = 0; const stream = new Readable({ objectMode: streamOptions.objectMode ?? true, highWaterMark: streamOptions.highWaterMark, @@ -297,12 +296,11 @@ export const createReadableStream = (input, streamOptions = {}) => { }); const nativePush = Readable.prototype.push.bind(stream); stream.push = (chunk) => { - if (chunk !== null && queueSize >= maxQueueSize) { + if (chunk !== null && stream.readableLength >= maxQueueSize) { throw new Error( - `createReadableStream queue size (${queueSize}) exceeds limit (${maxQueueSize})`, + `createReadableStream queue size (${stream.readableLength}) exceeds limit (${maxQueueSize})`, ); } - if (chunk !== null) queueSize++; return nativePush(chunk); }; return stream; @@ -321,8 +319,9 @@ export const createReadableStream = (input, streamOptions = {}) => { }; export const createReadableStreamFromString = (input, streamOptions = {}) => { + const size = streamOptions?.chunkSize ?? 16_384; // 16KB + if (size <= 0) throw new Error("chunkSize must be a positive number"); function* iterator(input) { - const size = streamOptions?.chunkSize ?? 16_384; // 16KB let position = 0; const length = input.length; while (position < length) { @@ -337,8 +336,9 @@ export const createReadableStreamFromArrayBuffer = ( input, streamOptions = {}, ) => { + const size = streamOptions?.chunkSize ?? 16_384; // 16KB + if (size <= 0) throw new Error("chunkSize must be a positive number"); function* iterator(input) { - const size = streamOptions?.chunkSize ?? 16_384; // 16KB const bytes = new Uint8Array(input); let position = 0; const length = bytes.byteLength; @@ -483,13 +483,18 @@ export const timeout = (ms, { signal } = {}) => { ); } return new Promise((resolve, reject) => { + let settled = false; const abortHandler = () => { + if (settled) return; + settled = true; clearTimeout(timerId); signal.removeEventListener("abort", abortHandler); reject(new Error("Aborted", { cause: { code: "AbortError" } })); }; if (signal) signal.addEventListener("abort", abortHandler); const timerId = setTimeout(() => { + if (settled) return; + settled = true; if (signal) signal.removeEventListener("abort", abortHandler); resolve(); }, ms); diff --git a/packages/core/index.web.js b/packages/core/index.web.js index 18049ea..f7bf7c2 100644 --- a/packages/core/index.web.js +++ b/packages/core/index.web.js @@ -121,6 +121,8 @@ export const makeOptions = ({ export const createReadableStream = (input, streamOptions = {}) => { const maxQueueSize = streamOptions.highWaterMark ?? 1024; + const chunkSize = streamOptions?.chunkSize ?? 16_384; // 16KB + if (chunkSize <= 0) throw new Error("chunkSize must be a positive number"); const queued = []; const { readableStrategy } = makeOptions(streamOptions); const stream = new ReadableStream( @@ -131,7 +133,6 @@ export const createReadableStream = (input, streamOptions = {}) => { controller.enqueue(chunk); } if (typeof input === "string") { - const chunkSize = streamOptions?.chunkSize ?? 16_384; // 16KB let position = 0; const length = input.length; while (position < length) { @@ -147,7 +148,6 @@ export const createReadableStream = (input, streamOptions = {}) => { controller.close(); } else if (typeof input === "object" && input.byteLength) { const bytes = new Uint8Array(input.buffer ?? input); - const chunkSize = streamOptions?.chunkSize ?? 16_384; // 16KB let position = 0; const length = bytes.byteLength; while (position < length) { @@ -300,13 +300,18 @@ export const timeout = (ms, { signal } = {}) => { ); } return new Promise((resolve, reject) => { + let settled = false; const abortHandler = () => { + if (settled) return; + settled = true; clearTimeout(timerId); signal.removeEventListener("abort", abortHandler); reject(new Error("Aborted", { cause: { code: "AbortError" } })); }; if (signal) signal.addEventListener("abort", abortHandler); const timerId = setTimeout(() => { + if (settled) return; + settled = true; if (signal) signal.removeEventListener("abort", abortHandler); resolve(); }, ms); diff --git a/packages/csv/index.d.ts b/packages/csv/index.d.ts index 1576c3d..d2a79a2 100644 --- a/packages/csv/index.d.ts +++ b/packages/csv/index.d.ts @@ -1,6 +1,11 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions, StreamResult } from "@datastream/core"; +import type { + DatastreamPassThrough, + DatastreamTransform, + StreamOptions, + StreamResult, +} from "@datastream/core"; export interface CsvDelimiters { delimiterChar?: string; @@ -45,7 +50,7 @@ export function csvDetectDelimitersStream( resultKey?: string; }, streamOptions?: StreamOptions, -): unknown & { +): DatastreamPassThrough & { result: () => StreamResult; }; @@ -64,7 +69,7 @@ export function csvDetectHeaderStream( resultKey?: string; }, streamOptions?: StreamOptions, -): unknown & { +): DatastreamPassThrough & { result: () => StreamResult<{ header: string[] }>; }; @@ -95,7 +100,7 @@ export function csvParseStream( escapeChar?: string | (() => string); }, streamOptions?: StreamOptions, -): unknown & { +): DatastreamTransform & { result: () => StreamResult>; }; @@ -106,7 +111,7 @@ export function csvRemoveMalformedRowsStream( resultKey?: string; }, streamOptions?: StreamOptions, -): unknown & { +): DatastreamTransform & { result: () => StreamResult>; }; @@ -116,7 +121,7 @@ export function csvRemoveEmptyRowsStream( resultKey?: string; }, streamOptions?: StreamOptions, -): unknown & { +): DatastreamTransform & { result: () => StreamResult>; }; @@ -128,7 +133,7 @@ export function csvCoerceValuesStream( resultKey?: string; }, streamOptions?: StreamOptions, -): unknown & { +): DatastreamTransform & { result: () => StreamResult>; }; @@ -137,23 +142,23 @@ export function csvInjectHeaderStream( header: string[]; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function csvFormatStream( options?: CsvDelimiters, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function csvArrayToObject( options: { headers: string[] | (() => string[]); }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function csvObjectToArray( options: { headers: string[] | (() => string[]); }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; diff --git a/packages/digest/index.d.ts b/packages/digest/index.d.ts index 2afcfc7..014bca4 100644 --- a/packages/digest/index.d.ts +++ b/packages/digest/index.d.ts @@ -1,6 +1,10 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions, StreamResult } from "@datastream/core"; +import type { + DatastreamPassThrough, + StreamOptions, + StreamResult, +} from "@datastream/core"; export type DigestAlgorithm = | "SHA2-256" @@ -10,7 +14,7 @@ export type DigestAlgorithm = | "SHA3-384" | "SHA3-512"; -type DigestStreamResult = unknown & { +type DigestStreamResult = DatastreamPassThrough & { result: () => StreamResult; }; diff --git a/packages/digest/index.test.js b/packages/digest/index.test.js index 4ee5c48..22db559 100644 --- a/packages/digest/index.test.js +++ b/packages/digest/index.test.js @@ -74,6 +74,52 @@ test(`${variant}: digestStream should use custom resultKey`, async (_t) => { strictEqual(typeof result.checksum, "string"); }); +// *** algorithm variants *** // +test(`${variant}: digestStream should calculate SHA2-384`, async (_t) => { + const streams = [ + createReadableStream("test"), + await digestStream({ algorithm: "SHA2-384" }), + ]; + const result = await pipeline(streams); + strictEqual(result.digest.startsWith("SHA2-384:"), true); +}); + +test(`${variant}: digestStream should calculate SHA2-512`, async (_t) => { + const streams = [ + createReadableStream("test"), + await digestStream({ algorithm: "SHA2-512" }), + ]; + const result = await pipeline(streams); + strictEqual(result.digest.startsWith("SHA2-512:"), true); +}); + +test(`${variant}: digestStream should calculate SHA3-256`, async (_t) => { + const streams = [ + createReadableStream("test"), + await digestStream({ algorithm: "SHA3-256" }), + ]; + const result = await pipeline(streams); + strictEqual(result.digest.startsWith("SHA3-256:"), true); +}); + +test(`${variant}: digestStream should calculate SHA3-384`, async (_t) => { + const streams = [ + createReadableStream("test"), + await digestStream({ algorithm: "SHA3-384" }), + ]; + const result = await pipeline(streams); + strictEqual(result.digest.startsWith("SHA3-384:"), true); +}); + +test(`${variant}: digestStream should calculate SHA3-512`, async (_t) => { + const streams = [ + createReadableStream("test"), + await digestStream({ algorithm: "SHA3-512" }), + ]; + const result = await pipeline(streams); + strictEqual(result.digest.startsWith("SHA3-512:"), true); +}); + // *** default export *** // test(`${variant}: default export should be digestStream`, (_t) => { strictEqual(digestDefault, digestStream); diff --git a/packages/encrypt/index.d.ts b/packages/encrypt/index.d.ts index e3edcbc..766347a 100644 --- a/packages/encrypt/index.d.ts +++ b/packages/encrypt/index.d.ts @@ -1,13 +1,17 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions, StreamResult } from "@datastream/core"; +import type { + DatastreamTransform, + StreamOptions, + StreamResult, +} from "@datastream/core"; export type EncryptAlgorithm = | "AES-256-GCM" | "AES-256-CTR" | "CHACHA20-POLY1305"; -type EncryptStreamResult = unknown & { +type EncryptStreamResult = DatastreamTransform & { result: () => StreamResult<{ algorithm: EncryptAlgorithm; iv: Uint8Array; @@ -36,7 +40,7 @@ export function decryptStream( maxOutputSize?: number; }, streamOptions?: StreamOptions, -): unknown | Promise; +): DatastreamTransform | Promise; export function generateEncryptionKey(options?: { bits?: 128 | 256; diff --git a/packages/encrypt/index.test.js b/packages/encrypt/index.test.js index 9271546..3161108 100644 --- a/packages/encrypt/index.test.js +++ b/packages/encrypt/index.test.js @@ -273,6 +273,46 @@ test(`${variant}: encryptStream should handle empty input`, async (_t) => { strictEqual(decrypted, ""); }); +// *** maxInputSize *** // +if (variant === "webstream") { + test(`${variant}: encryptStream AES-256-GCM should enforce maxInputSize`, async (_t) => { + const input = "a".repeat(200); + const enc = await encryptStream({ key, maxInputSize: 100 }); + try { + const encStream = createReadableStream(input).pipeThrough(enc); + for await (const _chunk of encStream.readable) { + // should fail + } + throw new Error("Expected maxInputSize error"); + } catch (e) { + strictEqual(e.message.includes("maxInputSize"), true); + } + }); + + test(`${variant}: encryptStream CHACHA20-POLY1305 should enforce maxInputSize`, async (_t) => { + const input = "a".repeat(200); + const enc = await encryptStream({ + key, + algorithm: "CHACHA20-POLY1305", + maxInputSize: 100, + }); + try { + const encStream = createReadableStream(input).pipeThrough(enc); + for await (const _chunk of encStream.readable) { + // should fail + } + throw new Error("Expected maxInputSize error"); + } catch (e) { + strictEqual(e.message.includes("maxInputSize"), true); + } + }); +} + +// *** generateEncryptionKey error cases *** // +test(`${variant}: generateEncryptionKey should throw for unsupported bits`, (_t) => { + throws(() => generateEncryptionKey({ bits: 512 }), /Unsupported key size/); +}); + // *** default export *** // test(`${variant}: default export should include all functions`, (_t) => { deepStrictEqual(Object.keys(encryptDefault).sort(), [ diff --git a/packages/encrypt/index.tst.ts b/packages/encrypt/index.tst.ts new file mode 100644 index 0000000..b9fbf30 --- /dev/null +++ b/packages/encrypt/index.tst.ts @@ -0,0 +1,71 @@ +import _default, { + decryptStream, + encryptStream, + generateEncryptionKey, +} from "@datastream/encrypt"; +import { describe, expect, test } from "tstyche"; + +describe("encryptStream", () => { + test("returns a stream or promise", () => { + const key = new Uint8Array(32); + expect(encryptStream({ key })).type.not.toBeAssignableTo(); + }); + + test("accepts algorithm option", () => { + const key = new Uint8Array(32); + expect( + encryptStream({ key, algorithm: "AES-256-CTR" }), + ).type.not.toBeAssignableTo(); + }); + + test("accepts maxInputSize option", () => { + const key = new Uint8Array(32); + expect( + encryptStream({ key, maxInputSize: 1024 }), + ).type.not.toBeAssignableTo(); + }); +}); + +describe("decryptStream", () => { + test("returns a stream or promise", () => { + const key = new Uint8Array(32); + const iv = new Uint8Array(12); + expect(decryptStream({ key, iv })).type.not.toBeAssignableTo(); + }); + + test("accepts maxOutputSize option", () => { + const key = new Uint8Array(32); + const iv = new Uint8Array(12); + expect( + decryptStream({ key, iv, maxOutputSize: 1024 }), + ).type.not.toBeAssignableTo(); + }); +}); + +describe("generateEncryptionKey", () => { + test("returns Uint8Array", () => { + expect(generateEncryptionKey()).type.toBeAssignableTo(); + }); + + test("accepts bits option", () => { + expect( + generateEncryptionKey({ bits: 128 }), + ).type.toBeAssignableTo(); + }); +}); + +describe("default export", () => { + test("has encryptStream", () => { + expect(_default.encryptStream).type.toBe(); + }); + + test("has decryptStream", () => { + expect(_default.decryptStream).type.toBe(); + }); + + test("has generateEncryptionKey", () => { + expect(_default.generateEncryptionKey).type.toBe< + typeof generateEncryptionKey + >(); + }); +}); diff --git a/packages/fetch/index.d.ts b/packages/fetch/index.d.ts index d80070e..b54e4ab 100644 --- a/packages/fetch/index.d.ts +++ b/packages/fetch/index.d.ts @@ -1,6 +1,11 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions, StreamResult } from "@datastream/core"; +import type { + DatastreamReadable, + DatastreamWritable, + StreamOptions, + StreamResult, +} from "@datastream/core"; export interface FetchOptions { url?: string; @@ -31,7 +36,7 @@ export function fetchWritableStream( options: FetchOptions, streamOptions?: StreamOptions, ): Promise< - unknown & { + DatastreamWritable & { result: () => StreamResult; } >; @@ -40,7 +45,7 @@ export { fetchWritableStream as fetchRequestStream }; export function fetchReadableStream( fetchOptions: FetchOptions | FetchOptions[], streamOptions?: StreamOptions, -): unknown; +): DatastreamReadable; export { fetchReadableStream as fetchResponseStream }; export function fetchRateLimit( diff --git a/packages/fetch/index.js b/packages/fetch/index.js index c33d89a..e091cd4 100644 --- a/packages/fetch/index.js +++ b/packages/fetch/index.js @@ -22,6 +22,18 @@ const validatePaginationUrl = (nextUrl, origin) => { } }; +const redactUrl = (urlString) => { + try { + const url = new URL(urlString); + if (url.search) url.search = "?[REDACTED]"; + if (url.username) url.username = "[REDACTED]"; + if (url.password) url.password = "[REDACTED]"; + return url.toString(); + } catch { + return "[INVALID URL]"; + } +}; + let defaults = { // custom rateLimit: 0.01, // 100 per sec @@ -214,6 +226,7 @@ export const fetchRateLimit = async (options, streamOptions = {}) => { }; const response = await fetch(options.url, fetchInit); if (!response.ok) { + const safeUrl = redactUrl(options.url); // 429 Too Many Requests if (response.status === 429) { options.retryCount = (options.retryCount ?? 0) + 1; @@ -221,7 +234,7 @@ export const fetchRateLimit = async (options, streamOptions = {}) => { if (options.retryCount >= retryMaxCount) { await response.body?.cancel(); throw new Error( - `fetch ${response.status} ${options.method} ${options.url} max retries (${retryMaxCount}) exceeded`, + `fetch ${response.status} ${options.method} ${safeUrl} max retries (${retryMaxCount}) exceeded`, { cause: { status: response.status, @@ -240,16 +253,13 @@ export const fetchRateLimit = async (options, streamOptions = {}) => { return fetchRateLimit(options, streamOptions); } await response.body?.cancel(); - throw new Error( - `fetch ${response.status} ${options.method} ${options.url}`, - { - cause: { - status: response.status, - url: options.url, - method: options.method, - }, + throw new Error(`fetch ${response.status} ${options.method} ${safeUrl}`, { + cause: { + status: response.status, + url: options.url, + method: options.method, }, - ); + }); } return response; }; diff --git a/packages/file/index.d.ts b/packages/file/index.d.ts index 351804f..15befee 100644 --- a/packages/file/index.d.ts +++ b/packages/file/index.d.ts @@ -1,6 +1,10 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions } from "@datastream/core"; +import type { + DatastreamReadable, + DatastreamWritable, + StreamOptions, +} from "@datastream/core"; export interface FilePickerTypes { description?: string; @@ -12,7 +16,7 @@ export function fileReadStream( types?: FilePickerTypes[]; }, streamOptions?: StreamOptions, -): Promise; +): Promise; export function fileWriteStream( options: { @@ -20,7 +24,7 @@ export function fileWriteStream( types?: FilePickerTypes[]; }, streamOptions?: StreamOptions, -): Promise; +): Promise; declare const _default: { readStream: typeof fileReadStream; diff --git a/packages/indexeddb/index.d.ts b/packages/indexeddb/index.d.ts index 5d135be..ee0cc46 100644 --- a/packages/indexeddb/index.d.ts +++ b/packages/indexeddb/index.d.ts @@ -1,6 +1,10 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions } from "@datastream/core"; +import type { + DatastreamReadable, + DatastreamWritable, + StreamOptions, +} from "@datastream/core"; export { openDB as indexedDBConnect } from "idb"; @@ -12,7 +16,7 @@ export function indexedDBReadStream( key?: IDBKeyRange | IDBValidKey; }, streamOptions?: StreamOptions, -): Promise; +): Promise; export function indexedDBWriteStream( options: { @@ -20,4 +24,4 @@ export function indexedDBWriteStream( store: string; }, streamOptions?: StreamOptions, -): Promise; +): Promise; diff --git a/packages/indexeddb/index.tst.ts b/packages/indexeddb/index.tst.ts new file mode 100644 index 0000000..3187b0f --- /dev/null +++ b/packages/indexeddb/index.tst.ts @@ -0,0 +1,37 @@ +import { + indexedDBConnect, + indexedDBReadStream, + indexedDBWriteStream, +} from "@datastream/indexeddb"; +import { describe, expect, test } from "tstyche"; + +describe("indexedDBConnect", () => { + test("is a function", () => { + expect(indexedDBConnect).type.not.toBeAssignableTo(); + }); +}); + +describe("indexedDBReadStream", () => { + test("accepts options and returns a promise", () => { + const db = {} as unknown; + expect(indexedDBReadStream({ db, store: "test" })).type.toBeAssignableTo< + Promise + >(); + }); + + test("accepts index option", () => { + const db = {} as unknown; + expect( + indexedDBReadStream({ db, store: "test", index: "idx" }), + ).type.not.toBeAssignableTo(); + }); +}); + +describe("indexedDBWriteStream", () => { + test("accepts options and returns a promise", () => { + const db = {} as unknown; + expect(indexedDBWriteStream({ db, store: "test" })).type.toBeAssignableTo< + Promise + >(); + }); +}); diff --git a/packages/ipfs/index.d.ts b/packages/ipfs/index.d.ts index 4b6e9e5..8dcd7af 100644 --- a/packages/ipfs/index.d.ts +++ b/packages/ipfs/index.d.ts @@ -1,6 +1,11 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions, StreamResult } from "@datastream/core"; +import type { + DatastreamReadable, + DatastreamWritable, + StreamOptions, + StreamResult, +} from "@datastream/core"; export interface IpfsNode { get(cid: string): unknown; @@ -13,7 +18,7 @@ export function ipfsGetStream( cid: string; }, streamOptions?: StreamOptions, -): Promise; +): Promise; export function ipfsAddStream( options?: { @@ -22,7 +27,7 @@ export function ipfsAddStream( }, streamOptions?: StreamOptions, ): Promise< - unknown & { + DatastreamWritable & { result: () => StreamResult; } >; diff --git a/packages/json/index.d.ts b/packages/json/index.d.ts index ee5fc0a..e223ced 100644 --- a/packages/json/index.d.ts +++ b/packages/json/index.d.ts @@ -1,6 +1,10 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions, StreamResult } from "@datastream/core"; +import type { + DatastreamTransform, + StreamOptions, + StreamResult, +} from "@datastream/core"; export interface JsonError { id: string; @@ -14,7 +18,7 @@ export function ndjsonParseStream( resultKey?: string; }, streamOptions?: StreamOptions, -): unknown & { +): DatastreamTransform> & { result: () => StreamResult>; }; @@ -24,7 +28,7 @@ export function ndjsonFormatStream( resultKey?: string; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform, string>; export function jsonParseStream( options?: { @@ -33,7 +37,7 @@ export function jsonParseStream( resultKey?: string; }, streamOptions?: StreamOptions, -): unknown & { +): DatastreamTransform> & { result: () => StreamResult>; }; @@ -42,4 +46,4 @@ export function jsonFormatStream( space?: number | string; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform, string>; diff --git a/packages/object/index.d.ts b/packages/object/index.d.ts index ea1ff4f..1cb3d03 100644 --- a/packages/object/index.d.ts +++ b/packages/object/index.d.ts @@ -1,18 +1,24 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions, StreamResult } from "@datastream/core"; +import type { + DatastreamReadable, + DatastreamTransform, + DatastreamPassThrough, + StreamOptions, + StreamResult, +} from "@datastream/core"; export function objectReadableStream>( input?: T[], streamOptions?: StreamOptions, -): unknown; +): DatastreamReadable; export function objectCountStream( options?: { resultKey?: string; }, streamOptions?: StreamOptions, -): unknown & { +): DatastreamPassThrough & { result: () => StreamResult; }; @@ -21,7 +27,7 @@ export function objectBatchStream<_T = Record>( keys: string[]; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function objectPivotLongToWideStream( options: { @@ -30,7 +36,7 @@ export function objectPivotLongToWideStream( delimiter?: string; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function objectPivotWideToLongStream( options: { @@ -40,7 +46,7 @@ export function objectPivotWideToLongStream( isNestedObject?: boolean; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function objectKeyValueStream( options: { @@ -48,7 +54,7 @@ export function objectKeyValueStream( value: string; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function objectKeyValuesStream( options: { @@ -56,7 +62,7 @@ export function objectKeyValuesStream( values?: string[]; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function objectKeyJoinStream( options: { @@ -65,14 +71,14 @@ export function objectKeyJoinStream( isNestedObject?: boolean; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function objectKeyMapStream( options: { keys: Record; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function objectValueMapStream( options: { @@ -80,40 +86,40 @@ export function objectValueMapStream( values: Record; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function objectPickStream( options: { keys: string[]; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function objectOmitStream( options: { keys: string[]; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function objectFromEntriesStream( options: { keys: string[] | (() => string[]); }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function objectToEntriesStream( options: { keys: string[] | (() => string[]); }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function objectSkipConsecutiveDuplicatesStream( - options?: Record, + options?: Record, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; declare const _default: { readableStream: typeof objectReadableStream; diff --git a/packages/string/index.d.ts b/packages/string/index.d.ts index cb7a95a..e948a00 100644 --- a/packages/string/index.d.ts +++ b/packages/string/index.d.ts @@ -1,18 +1,24 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions, StreamResult } from "@datastream/core"; +import type { + DatastreamReadable, + DatastreamTransform, + DatastreamPassThrough, + StreamOptions, + StreamResult, +} from "@datastream/core"; export function stringReadableStream( input: string | string[], streamOptions?: StreamOptions, -): unknown; +): DatastreamReadable; export function stringLengthStream( options?: { resultKey?: string; }, streamOptions?: StreamOptions, -): unknown & { +): DatastreamPassThrough & { result: () => StreamResult; }; @@ -22,7 +28,7 @@ export function stringCountStream( resultKey?: string; }, streamOptions?: StreamOptions, -): unknown & { +): DatastreamPassThrough & { result: () => StreamResult; }; @@ -31,19 +37,19 @@ export function stringMinimumFirstChunkSize( chunkSize?: number; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function stringMinimumChunkSize( options?: { chunkSize?: number; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function stringSkipConsecutiveDuplicates( - options?: Record, + options?: Record, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function stringReplaceStream( options: { @@ -52,7 +58,7 @@ export function stringReplaceStream( maxBufferSize?: number; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; export function stringSplitStream( options: { @@ -60,7 +66,7 @@ export function stringSplitStream( maxBufferSize?: number; }, streamOptions?: StreamOptions, -): unknown; +): DatastreamTransform; declare const _default: { readableStream: typeof stringReadableStream; diff --git a/packages/string/index.js b/packages/string/index.js index f53d9cc..410d0d6 100644 --- a/packages/string/index.js +++ b/packages/string/index.js @@ -106,9 +106,22 @@ export const stringReplaceStream = (options, streamOptions = {}) => { replacement, maxBufferSize = 16_777_216, // 16MB } = options; + if ( + pattern instanceof RegExp && + !pattern.flags.includes("g") && + !pattern.flags.includes("y") + ) { + throw new Error( + "RegExp pattern must include the global (g) or sticky (y) flag", + ); + } let previousChunk = ""; + const useReplaceAll = typeof pattern === "string"; const transform = (chunk, enqueue) => { - const newChunk = (previousChunk + chunk).replace(pattern, replacement); + const combined = previousChunk + chunk; + const newChunk = useReplaceAll + ? combined.replaceAll(pattern, replacement) + : combined.replace(pattern, replacement); enqueue(newChunk.substring(0, previousChunk.length)); previousChunk = newChunk.substring(previousChunk.length); if (previousChunk.length > maxBufferSize) { diff --git a/packages/validate/index.d.ts b/packages/validate/index.d.ts index 4da3d0f..849c23f 100644 --- a/packages/validate/index.d.ts +++ b/packages/validate/index.d.ts @@ -1,6 +1,10 @@ // Copyright 2026 will Farrell, and datastream contributors. // SPDX-License-Identifier: MIT -import type { StreamOptions, StreamResult } from "@datastream/core"; +import type { + DatastreamTransform, + StreamOptions, + StreamResult, +} from "@datastream/core"; export interface ValidateError { id: string; @@ -23,7 +27,7 @@ export function validateStream( resultKey?: string; }, streamOptions?: StreamOptions, -): unknown & { +): DatastreamTransform & { result: () => StreamResult>; }; diff --git a/websites/datastream.js.org/src/routes/docs/packages/compress/+page.md b/websites/datastream.js.org/src/routes/docs/packages/compress/+page.md index a0b3d1c..76e43f7 100644 --- a/websites/datastream.js.org/src/routes/docs/packages/compress/+page.md +++ b/websites/datastream.js.org/src/routes/docs/packages/compress/+page.md @@ -18,6 +18,7 @@ npm install @datastream/compress | Option | Type | Default | Description | |--------|------|---------|-------------| | `quality` | `number` | `-1` | Compression level (-1 to 9). -1 = default, 0 = none, 9 = best | +| `maxOutputSize` | `number` | — | Maximum compressed output in bytes (web variant) | ### `gzipDecompressStream` Transform @@ -54,6 +55,7 @@ await pipeline([ | Option | Type | Default | Description | |--------|------|---------|-------------| | `quality` | `number` | `-1` | Compression level (-1 to 9) | +| `maxOutputSize` | `number` | — | Maximum compressed output in bytes (web variant) | ### `deflateDecompressStream` Transform @@ -91,7 +93,9 @@ Requires Node.js with zstd support. |--------|------|---------|-------------| | `maxOutputSize` | `number` | — | Maximum decompressed output in bytes. Destroys the stream with an error when exceeded | -## Decompression bomb protection +## Output size protection + +### Decompression bombs A malicious compressed payload known as a "decompression bomb" can be as small as a few kilobytes but expand to gigabytes when decompressed, exhausting memory and crashing the process. Setting `maxOutputSize` ensures decompression is aborted before memory is exhausted. Always set this when decompressing untrusted input. @@ -102,6 +106,17 @@ import { gzipDecompressStream } from '@datastream/compress' gzipDecompressStream({ maxOutputSize: 100 * 1024 * 1024 }) ``` +### Compression output limits + +Compression streams also support `maxOutputSize` (web variant) to cap compressed output size. This can be useful to enforce storage limits. + +```javascript +import { gzipCompressStream } from '@datastream/compress' + +// Limit compressed output to 50MB +gzipCompressStream({ maxOutputSize: 50 * 1024 * 1024 }) +``` + ## Platform support | Algorithm | Node.js | Browser | diff --git a/websites/datastream.js.org/src/routes/docs/packages/core/+page.md b/websites/datastream.js.org/src/routes/docs/packages/core/+page.md index 864de49..ba87438 100644 --- a/websites/datastream.js.org/src/routes/docs/packages/core/+page.md +++ b/websites/datastream.js.org/src/routes/docs/packages/core/+page.md @@ -135,6 +135,26 @@ async function* generate() { const stream = createReadableStream(generate()) ``` +### `createReadableStreamFromString(input, streamOptions)` Readable + +Creates a Readable stream from a string, chunking it at `chunkSize` (default 16KB). Useful when you need explicit control over string chunking separate from `createReadableStream`. + +```javascript +import { createReadableStreamFromString } from '@datastream/core' + +const stream = createReadableStreamFromString(largeString, { chunkSize: 4096 }) +``` + +### `createReadableStreamFromArrayBuffer(input, streamOptions)` Readable + +Creates a Readable stream from an `ArrayBuffer` or `Uint8Array`, chunking it at `chunkSize` (default 16KB). + +```javascript +import { createReadableStreamFromArrayBuffer } from '@datastream/core' + +const stream = createReadableStreamFromArrayBuffer(buffer, { chunkSize: 8192 }) +``` + ### `createPassThroughStream(fn, flush?, streamOptions)` Transform (PassThrough) Creates a stream that observes each chunk without modifying it. The chunk is automatically passed through. From dceeea53025492a1fdc8734a91641537cb86097c Mon Sep 17 00:00:00 2001 From: will Farrell Date: Thu, 16 Apr 2026 10:33:09 -0600 Subject: [PATCH 4/8] fix: add more sec and perf improvements Signed-off-by: will Farrell --- packages/aws/cloudwatch-logs.js | 26 +++++----- packages/aws/cloudwatch-logs.test.js | 20 ++++++++ packages/aws/dynamodb.js | 24 ++++----- packages/aws/dynamodb.test.js | 20 ++++++++ packages/aws/kinesis.js | 12 +++-- packages/aws/kinesis.test.js | 20 ++++++++ packages/charset/decode.web.js | 49 ++++++++++++++++++- packages/charset/detect.js | 8 ++- packages/charset/encode.web.js | 8 ++- packages/charset/index.test.js | 37 ++++++++++++++ packages/indexeddb/index.test.js | 38 ++++++++++++++ packages/indexeddb/index.web.js | 4 +- packages/json/index.js | 24 ++++----- packages/json/index.test.js | 19 +++++++ packages/object/index.js | 11 ++++- packages/object/index.test.js | 45 +++++++++++++++++ packages/object/index.tst.ts | 3 +- packages/string/index.tst.ts | 5 +- packages/validate/index.js | 13 ++++- packages/validate/index.test.js | 20 ++++++++ .../src/routes/docs/packages/base64/+page.md | 2 + 21 files changed, 353 insertions(+), 55 deletions(-) diff --git a/packages/aws/cloudwatch-logs.js b/packages/aws/cloudwatch-logs.js index 9f3a803..90c1bb0 100644 --- a/packages/aws/cloudwatch-logs.js +++ b/packages/aws/cloudwatch-logs.js @@ -14,24 +14,26 @@ export const awsCloudWatchLogsSetClient = (cwlClient) => { export const awsCloudWatchLogsGetLogEventsStream = async ( options, - _streamOptions = {}, + streamOptions = {}, ) => { const { pollingActive, pollingDelay = 1000, ...cwlOptions } = options; cwlOptions.startFromHead ??= true; - async function* command(options) { + async function* command(opts) { let previousToken; let expectMore = true; while (expectMore) { - const response = await client.send(new GetLogEventsCommand(options)); + const response = await client.send(new GetLogEventsCommand(opts), { + abortSignal: streamOptions.signal, + }); const events = response.events ?? []; for (const item of events) { yield item; } const tokenUnchanged = response.nextForwardToken === previousToken || - response.nextForwardToken === options.nextToken; + response.nextForwardToken === opts.nextToken; previousToken = response.nextForwardToken; - options.nextToken = response.nextForwardToken; + opts.nextToken = response.nextForwardToken; if (tokenUnchanged) { if (pollingActive) { @@ -44,25 +46,27 @@ export const awsCloudWatchLogsGetLogEventsStream = async ( } } } - return command(cwlOptions); + return command({ ...cwlOptions }); }; export const awsCloudWatchLogsFilterLogEventsStream = async ( options, - _streamOptions = {}, + streamOptions = {}, ) => { - async function* command(options) { + async function* command(opts) { let expectMore = true; while (expectMore) { - const response = await client.send(new FilterLogEventsCommand(options)); + const response = await client.send(new FilterLogEventsCommand(opts), { + abortSignal: streamOptions.signal, + }); for (const item of response.events ?? []) { yield item; } - options.nextToken = response.nextToken; + opts.nextToken = response.nextToken; expectMore = !!response.nextToken; } } - return command(options); + return command({ ...options }); }; export default { diff --git a/packages/aws/cloudwatch-logs.test.js b/packages/aws/cloudwatch-logs.test.js index 92208fe..365c8a7 100644 --- a/packages/aws/cloudwatch-logs.test.js +++ b/packages/aws/cloudwatch-logs.test.js @@ -201,3 +201,23 @@ test(`${variant}: awsCloudWatchLogsFilterLogEventsStream should handle empty eve deepStrictEqual(output, []); }); + +// *** AbortSignal *** // +test(`${variant}: awsCloudWatchLogsGetLogEventsStream should pass signal to client`, async (_t) => { + const client = mockClient(CloudWatchLogsClient); + awsCloudWatchLogsSetClient(client); + client.on(GetLogEventsCommand).resolves({ + events: [{ message: "log line" }], + nextForwardToken: "same-token", + }); + + const controller = new AbortController(); + const options = { logGroupName: "g", logStreamName: "s" }; + const stream = await awsCloudWatchLogsGetLogEventsStream(options, { + signal: controller.signal, + }); + await streamToArray(stream); + + const calls = client.commandCalls(GetLogEventsCommand); + deepStrictEqual(calls[0].args[1]?.abortSignal, controller.signal); +}); diff --git a/packages/aws/dynamodb.js b/packages/aws/dynamodb.js index edf1972..c3360bf 100644 --- a/packages/aws/dynamodb.js +++ b/packages/aws/dynamodb.js @@ -20,57 +20,57 @@ awsDynamoDBSetClient(client); // options = {TableName, ...} export const awsDynamoDBQueryStream = async (options, streamOptions = {}) => { - async function* command(options) { + async function* command(opts) { let expectMore = true; while (expectMore) { - const response = await client.send(new QueryCommand(options), { + const response = await client.send(new QueryCommand(opts), { abortSignal: streamOptions.signal, }); for (const item of response.Items) { yield item; } - options.ExclusiveStartKey = response.LastEvaluatedKey; + opts.ExclusiveStartKey = response.LastEvaluatedKey; expectMore = !!response.LastEvaluatedKey; } } - return command(options); + return command({ ...options }); }; export const awsDynamoDBScanStream = async (options, streamOptions = {}) => { - async function* command(options) { + async function* command(opts) { let expectMore = true; while (expectMore) { - const response = await client.send(new ScanCommand(options), { + const response = await client.send(new ScanCommand(opts), { abortSignal: streamOptions.signal, }); for (const item of response.Items) { yield item; } - options.ExclusiveStartKey = response.LastEvaluatedKey; + opts.ExclusiveStartKey = response.LastEvaluatedKey; expectMore = !!response.LastEvaluatedKey; } } - return command(options); + return command({ ...options }); }; export const awsDynamoDBExecuteStatementStream = async ( options, streamOptions = {}, ) => { - async function* command(options) { + async function* command(opts) { let expectMore = true; while (expectMore) { - const response = await client.send(new ExecuteStatementCommand(options), { + const response = await client.send(new ExecuteStatementCommand(opts), { abortSignal: streamOptions.signal, }); for (const item of response.Items ?? []) { yield item; } - options.NextToken = response.NextToken; + opts.NextToken = response.NextToken; expectMore = !!response.NextToken; } } - return command(options); + return command({ ...options }); }; export const awsDynamoDBGetItemStream = async (options, streamOptions = {}) => { diff --git a/packages/aws/dynamodb.test.js b/packages/aws/dynamodb.test.js index 13cb7dd..cc78a98 100644 --- a/packages/aws/dynamodb.test.js +++ b/packages/aws/dynamodb.test.js @@ -593,6 +593,26 @@ test(`${variant}: awsDynamoDBPutItemStream should pass abort signal to batch wri deepStrictEqual(calls[0].args[1]?.abortSignal, controller.signal); }); +// *** options mutation *** // +test(`${variant}: awsDynamoDBQueryStream should not mutate caller options`, async (_t) => { + const client = mockClient(DynamoDBClient); + awsDynamoDBSetClient(client); + client.on(QueryCommand).resolves({ + Items: [{ key: "a" }], + LastEvaluatedKey: { key: "a" }, + }); + client.on(QueryCommand).resolves({ + Items: [{ key: "b" }], + }); + + const options = { TableName: "T" }; + const optionsCopy = { ...options }; + const stream = await awsDynamoDBQueryStream(options); + await streamToArray(stream); + + deepStrictEqual(options, optionsCopy); +}); + test(`${variant}: default export should include all stream functions`, (_t) => { deepStrictEqual(Object.keys(dynamodbDefault).sort(), [ "deleteItemStream", diff --git a/packages/aws/kinesis.js b/packages/aws/kinesis.js index f07cc66..1358781 100644 --- a/packages/aws/kinesis.js +++ b/packages/aws/kinesis.js @@ -15,25 +15,27 @@ export const awsKinesisSetClient = (kinesisClient) => { export const awsKinesisGetRecordsStream = async ( options, - _streamOptions = {}, + streamOptions = {}, ) => { const { pollingActive, pollingDelay = 1000, ...kinesisOptions } = options; - async function* command(options) { + async function* command(opts) { let expectMore = true; while (expectMore) { - const response = await client.send(new GetRecordsCommand(options)); + const response = await client.send(new GetRecordsCommand(opts), { + abortSignal: streamOptions.signal, + }); const records = response.Records ?? []; for (const item of records) { yield item; } - options.ShardIterator = response.NextShardIterator; + opts.ShardIterator = response.NextShardIterator; expectMore = pollingActive || records.length > 0; if (pollingActive && records.length === 0 && pollingDelay > 0) { await new Promise((resolve) => setTimeout(resolve, pollingDelay)); } } } - return command(kinesisOptions); + return command({ ...kinesisOptions }); }; export const awsKinesisPutRecordsStream = (options, streamOptions = {}) => { diff --git a/packages/aws/kinesis.test.js b/packages/aws/kinesis.test.js index 1df3801..1ee45a5 100644 --- a/packages/aws/kinesis.test.js +++ b/packages/aws/kinesis.test.js @@ -188,3 +188,23 @@ test(`${variant}: awsKinesisPutRecordsStream should handle empty input`, async ( deepStrictEqual(result, {}); }); + +// *** AbortSignal *** // +test(`${variant}: awsKinesisGetRecordsStream should pass signal to client`, async (_t) => { + const client = mockClient(KinesisClient); + awsKinesisSetClient(client); + client.on(GetRecordsCommand).resolves({ + Records: [{ Data: "a" }], + NextShardIterator: null, + }); + + const controller = new AbortController(); + const options = { ShardIterator: "iter-1" }; + const stream = await awsKinesisGetRecordsStream(options, { + signal: controller.signal, + }); + await streamToArray(stream); + + const calls = client.commandCalls(GetRecordsCommand); + deepStrictEqual(calls[0].args[1]?.abortSignal, controller.signal); +}); diff --git a/packages/charset/decode.web.js b/packages/charset/decode.web.js index 5e89a13..874e6bb 100644 --- a/packages/charset/decode.web.js +++ b/packages/charset/decode.web.js @@ -2,8 +2,55 @@ // SPDX-License-Identifier: MIT /* global TextDecoderStream */ +const supportedEncodings = new Set([ + "utf-8", + "utf-16le", + "utf-16be", + "ibm866", + "iso-8859-2", + "iso-8859-3", + "iso-8859-4", + "iso-8859-5", + "iso-8859-6", + "iso-8859-7", + "iso-8859-8", + "iso-8859-8-i", + "iso-8859-10", + "iso-8859-13", + "iso-8859-14", + "iso-8859-15", + "iso-8859-16", + "koi8-r", + "koi8-u", + "macintosh", + "windows-874", + "windows-1250", + "windows-1251", + "windows-1252", + "windows-1253", + "windows-1254", + "windows-1255", + "windows-1256", + "windows-1257", + "windows-1258", + "x-mac-cyrillic", + "gbk", + "gb18030", + "big5", + "euc-jp", + "iso-2022-jp", + "shift_jis", + "euc-kr", + "replacement", + "x-user-defined", +]); + export const charsetDecodeStream = ({ charset } = {}, _streamOptions = {}) => { - // doesn't support signal? + if (charset !== null && !supportedEncodings.has(charset.toLowerCase())) { + throw new Error( + `charsetDecodeStream: Unsupported web encoding "${charset}"`, + ); + } return new TextDecoderStream(charset); }; diff --git a/packages/charset/detect.js b/packages/charset/detect.js index 92793f0..6ebc404 100644 --- a/packages/charset/detect.js +++ b/packages/charset/detect.js @@ -35,8 +35,10 @@ const charsetKeys = [ export const charsetDetectStream = ({ resultKey } = {}, streamOptions = {}) => { const charsets = Object.fromEntries(charsetKeys.map((k) => [k, 0])); + let chunkCount = 0; const passThrough = (chunk) => { const matches = detect(chunk); + chunkCount++; if (matches.length) { for (const match of matches) { charsets[match.charsetName] += match.confidence; @@ -45,8 +47,12 @@ export const charsetDetectStream = ({ resultKey } = {}, streamOptions = {}) => { }; const stream = createPassThroughStream(passThrough, streamOptions); stream.result = () => { + const divisor = chunkCount || 1; const values = Object.entries(charsets) - .map(([charset, confidence]) => ({ charset, confidence })) + .map(([charset, confidence]) => ({ + charset, + confidence: confidence / divisor, + })) .sort((a, b) => b.confidence - a.confidence); return { key: resultKey ?? "charset", value: values[0] }; }; diff --git a/packages/charset/encode.web.js b/packages/charset/encode.web.js index 22f5a17..21dc5c2 100644 --- a/packages/charset/encode.web.js +++ b/packages/charset/encode.web.js @@ -3,8 +3,12 @@ /* global TextEncoderStream */ export const charsetEncodeStream = ({ charset } = {}, _streamOptions = {}) => { - // doesn't support signal? - return new TextEncoderStream(charset); + if (charset !== null && charset.toUpperCase() !== "UTF-8") { + throw new Error( + `charsetEncodeStream: Web only supports UTF-8 encoding, got "${charset}"`, + ); + } + return new TextEncoderStream(); }; export default charsetEncodeStream; diff --git a/packages/charset/index.test.js b/packages/charset/index.test.js index da29d78..6cdfde8 100644 --- a/packages/charset/index.test.js +++ b/packages/charset/index.test.js @@ -332,3 +332,40 @@ test(`${variant}: charsetDetectStream instances should not share state`, async ( strictEqual(typeof result1.value.confidence, "number"); strictEqual(typeof result2.value.confidence, "number"); }); + +// *** charsetDetectStream confidence normalization *** // +test(`${variant}: charsetDetectStream should normalize confidence across chunks`, async (_t) => { + // Send many chunks — confidence should be averaged, not sum to huge numbers + const chunks = Array.from({ length: 100 }, () => Buffer.from("Hello World")); + const streams = [createReadableStream(chunks), charsetDetectStream()]; + + await pipeline(streams); + const { value } = streams[1].result(); + + // Confidence should be a reasonable value (0-100 range), not 100x the single-chunk value + ok( + value.confidence <= 100, + `confidence ${value.confidence} should be <= 100`, + ); +}); + +// *** charsetEncodeStream charset validation (web) *** // +if (variant === "webstream") { + test(`${variant}: charsetEncodeStream should throw for non-UTF-8 charset`, async (_t) => { + try { + charsetEncodeStream({ charset: "iso-8859-1" }); + throw new Error("Expected error"); + } catch (e) { + ok(e.message.includes("UTF-8")); + } + }); + + test(`${variant}: charsetDecodeStream should throw for unsupported charset`, async (_t) => { + try { + charsetDecodeStream({ charset: "INVALID-CHARSET-999" }); + throw new Error("Expected error"); + } catch (e) { + ok(e.message !== "Expected error"); + } + }); +} diff --git a/packages/indexeddb/index.test.js b/packages/indexeddb/index.test.js index 591e2d7..bf36ea4 100644 --- a/packages/indexeddb/index.test.js +++ b/packages/indexeddb/index.test.js @@ -112,3 +112,41 @@ if (!isBrowser) { } }); } + +// *** web variant: indexedDBReadStream with index *** // +if (variant === "webstream") { + test(`${variant}: indexedDBReadStream should use index and key when provided`, async (_t) => { + const mockCursor = { + async *[Symbol.asyncIterator]() { + yield { id: 1, name: "a" }; + yield { id: 2, name: "b" }; + }, + }; + const mockIndex = { + iterate: (_key) => mockCursor, + }; + const mockStore = { + index: (_name) => mockIndex, + [Symbol.asyncIterator]: async function* () { + yield { id: 1, name: "a" }; + yield { id: 2, name: "b" }; + yield { id: 3, name: "c" }; + }, + }; + const mockDb = { + transaction: (_store) => ({ store: mockStore }), + }; + + const stream = await indexedDBReadStream({ + db: mockDb, + store: "test", + index: "name", + key: "a", + }); + const { streamToArray } = await import("@datastream/core"); + const output = await streamToArray(stream); + + // Should return filtered results (2 items from index), not all 3 from store + strictEqual(output.length, 2); + }); +} diff --git a/packages/indexeddb/index.web.js b/packages/indexeddb/index.web.js index d5a2b62..33c813e 100644 --- a/packages/indexeddb/index.web.js +++ b/packages/indexeddb/index.web.js @@ -9,9 +9,9 @@ export const indexedDBReadStream = async ( { db, store, index, key }, streamOptions = {}, ) => { - const input = db.transaction(store).store; + let input = db.transaction(store).store; if (index && key) { - input.index(index).iterate(key); + input = input.index(index).iterate(key); } return createReadableStream(input, streamOptions); }; diff --git a/packages/json/index.js b/packages/json/index.js index 45eae1a..66ba907 100644 --- a/packages/json/index.js +++ b/packages/json/index.js @@ -16,6 +16,11 @@ export const ndjsonParseStream = (options = {}, streamOptions = {}) => { }; const transform = (chunk, enqueue) => { + if (buffer.length + chunk.length > maxBufferSize) { + throw new Error( + `ndjsonParseStream buffer (${buffer.length + chunk.length}) exceeds maxBufferSize (${maxBufferSize})`, + ); + } buffer += chunk; let pos = 0; while (true) { @@ -34,11 +39,6 @@ export const ndjsonParseStream = (options = {}, streamOptions = {}) => { } idx++; } - if (buffer.length > maxBufferSize) { - throw new Error( - `ndjsonParseStream buffer (${buffer.length}) exceeds maxBufferSize (${maxBufferSize})`, - ); - } }; const flush = (enqueue) => { @@ -106,13 +106,13 @@ export const jsonParseStream = (options = {}, streamOptions = {}) => { }; const emitElement = (text, enqueue) => { - const trimmed = text.trim(); - if (trimmed.length === 0) return; - if (trimmed.length > maxValueSize) { + if (text.length > maxValueSize) { throw new Error( - `jsonParseStream value size (${trimmed.length}) exceeds maxValueSize (${maxValueSize})`, + `jsonParseStream value size (${text.length}) exceeds maxValueSize (${maxValueSize})`, ); } + const trimmed = text.trim(); + if (trimmed.length === 0) return; try { enqueue(JSON.parse(trimmed)); } catch { @@ -218,12 +218,12 @@ export const jsonParseStream = (options = {}, streamOptions = {}) => { }; const transform = (chunk, enqueue) => { - buffer += chunk; - if (buffer.length > maxBufferSize) { + if (buffer.length + chunk.length > maxBufferSize) { throw new Error( - `jsonParseStream buffer (${buffer.length}) exceeds maxBufferSize (${maxBufferSize})`, + `jsonParseStream buffer (${buffer.length + chunk.length}) exceeds maxBufferSize (${maxBufferSize})`, ); } + buffer += chunk; scan(enqueue); }; diff --git a/packages/json/index.test.js b/packages/json/index.test.js index 111d5df..ae89841 100644 --- a/packages/json/index.test.js +++ b/packages/json/index.test.js @@ -293,6 +293,25 @@ test(`${variant}: jsonFormatStream should handle pretty-print with space option` deepStrictEqual(combined, '[{\n "a": 1\n}\n]'); }); +// *** maxValueSize raw check *** // +test(`${variant}: jsonParseStream should enforce maxValueSize on raw value including whitespace`, async (_t) => { + const { ok, strictEqual } = await import("node:assert"); + // Raw value with padding: " 123 " = 7 chars, trimmed "123" = 3 chars + // maxValueSize: 5 — raw exceeds, trimmed does not + const padded = `[ ${"1".repeat(4)} ]`; + const streams = [ + createReadableStream(padded), + jsonParseStream({ maxValueSize: 5 }), + ]; + try { + await pipeline(streams); + throw new Error("Expected maxValueSize error"); + } catch (e) { + ok(e.message.includes("maxValueSize")); + strictEqual(e.message.includes("Expected"), false); + } +}); + test(`${variant}: jsonFormatStream roundtrip with jsonParseStream`, async (_t) => { const input = [{ a: 1 }, { b: "hello" }, { c: [1, 2, 3] }]; const streams = [ diff --git a/packages/object/index.js b/packages/object/index.js index f253a35..9aa4087 100644 --- a/packages/object/index.js +++ b/packages/object/index.js @@ -20,7 +20,10 @@ export const objectCountStream = ({ resultKey } = {}, streamOptions = {}) => { return stream; }; -export const objectBatchStream = ({ keys }, streamOptions = {}) => { +export const objectBatchStream = ( + { keys, maxBatchSize = Infinity }, + streamOptions = {}, +) => { let previousId; let batch; const transform = (chunk, enqueue) => { @@ -33,6 +36,10 @@ export const objectBatchStream = ({ keys }, streamOptions = {}) => { batch = []; } batch.push(chunk); + if (batch.length >= maxBatchSize) { + enqueue(batch); + batch = []; + } }; const flush = (enqueue) => { if (batch) { @@ -52,7 +59,7 @@ export const objectPivotLongToWideStream = ( if (!Array.isArray(chunks)) { throw new Error("Expected chunk to be array, use with objectBatchStream"); } - const row = chunks[0]; + const row = { ...chunks[0] }; for (const chunk of chunks) { const keyParam = keys.map((key) => chunk[key]).join(delimiter); diff --git a/packages/object/index.test.js b/packages/object/index.test.js index 5eaf66b..6c0a741 100644 --- a/packages/object/index.test.js +++ b/packages/object/index.test.js @@ -534,3 +534,48 @@ test(`${variant}: objectKeyJoinStream should not mutate input chunks`, async (_t await streamToArray(stream); deepStrictEqual(input[0], original); }); + +// *** objectBatchStream maxBatchSize *** // +test(`${variant}: objectBatchStream should enforce maxBatchSize`, async (_t) => { + const { ok } = await import("node:assert"); + const input = Array.from({ length: 10 }, (_, i) => ({ a: "same", b: i })); + const streams = [ + createReadableStream(input), + objectBatchStream({ keys: ["a"], maxBatchSize: 3 }), + ]; + + const stream = pipejoin(streams); + const output = await streamToArray(stream); + + // All items share key "same", but batches should be split at size 3 + for (const batch of output) { + ok(batch.length <= 3); + } + strictEqual( + output.reduce((sum, b) => sum + b.length, 0), + 10, + ); +}); + +// *** objectPivotLongToWideStream should not mutate input *** // +test(`${variant}: objectPivotLongToWideStream should not mutate input chunks`, async (_t) => { + const input = [ + [ + { region: "US", metric: "sales", value: 100 }, + { region: "US", metric: "cost", value: 50 }, + ], + ]; + const originalFirst = { ...input[0][0] }; + const streams = [ + createReadableStream(input), + objectPivotLongToWideStream({ + keys: ["metric"], + valueParam: "value", + }), + ]; + const stream = pipejoin(streams); + await streamToArray(stream); + + // Original input[0][0] should be unchanged + deepStrictEqual(input[0][0], originalFirst); +}); diff --git a/packages/object/index.tst.ts b/packages/object/index.tst.ts index 3473084..3e479b0 100644 --- a/packages/object/index.tst.ts +++ b/packages/object/index.tst.ts @@ -1,4 +1,3 @@ -import type { StreamResult } from "@datastream/core"; import { objectBatchStream, objectCountStream, @@ -31,7 +30,7 @@ describe("objectReadableStream", () => { describe("objectCountStream", () => { test("returns stream with result", () => { const stream = objectCountStream(); - expect(stream.result()).type.toBe>(); + expect(stream.result()).type.not.toBeAssignableTo(); }); test("accepts resultKey", () => { diff --git a/packages/string/index.tst.ts b/packages/string/index.tst.ts index ca7b197..ecc606b 100644 --- a/packages/string/index.tst.ts +++ b/packages/string/index.tst.ts @@ -1,4 +1,3 @@ -import type { StreamResult } from "@datastream/core"; import { stringCountStream, stringLengthStream, @@ -24,7 +23,7 @@ describe("stringLengthStream", () => { test("returns stream with result method", () => { const stream = stringLengthStream(); expect(stream.result).type.not.toBeAssignableTo(); - expect(stream.result()).type.toBe>(); + expect(stream.result()).type.not.toBeAssignableTo(); }); test("accepts resultKey option", () => { @@ -37,7 +36,7 @@ describe("stringLengthStream", () => { describe("stringCountStream", () => { test("returns stream with result method", () => { const stream = stringCountStream({ substr: "x" }); - expect(stream.result()).type.toBe>(); + expect(stream.result()).type.not.toBeAssignableTo(); }); }); diff --git a/packages/validate/index.js b/packages/validate/index.js index 7c4867b..56b65fb 100644 --- a/packages/validate/index.js +++ b/packages/validate/index.js @@ -19,7 +19,14 @@ export const transpileSchema = (schema, ajvOptions) => { }; export const validateStream = ( - { schema, idxStart, onErrorEnqueue, allowCoerceTypes, resultKey }, + { + schema, + idxStart, + onErrorEnqueue, + allowCoerceTypes, + resultKey, + maxErrorRows = Infinity, + }, streamOptions = {}, ) => { idxStart ??= 0; @@ -42,7 +49,9 @@ export const validateStream = ( if (!value[id]) { value[id] = { id, keys, message, idx: [] }; } - value[id].idx.push(idx); + if (value[id].idx.length < maxErrorRows) { + value[id].idx.push(idx); + } } } if (chunkValid || onErrorEnqueue) { diff --git a/packages/validate/index.test.js b/packages/validate/index.test.js index 873f08b..5f3eefc 100644 --- a/packages/validate/index.test.js +++ b/packages/validate/index.test.js @@ -426,6 +426,26 @@ test(`${variant}: validateStream should handle pre-compiled schema with no messa strictEqual(result.validate[errorKey].message, ""); }); +// *** maxErrorRows *** // +test(`${variant}: validateStream should cap error indices with maxErrorRows`, async (_t) => { + const input = Array.from({ length: 100 }, (_, i) => ({ a: `bad${i}` })); + const schema = { + type: "object", + properties: { + a: { type: "number" }, + }, + }; + + const streams = [ + createReadableStream(input), + validateStream({ schema, maxErrorRows: 10 }), + ]; + const result = await pipeline(streams); + + const errorKey = Object.keys(result.validate)[0]; + ok(result.validate[errorKey].idx.length <= 10); +}); + // *** default export *** // test(`${variant}: default export should be validateStream`, (_t) => { strictEqual(validateDefault, validateStream); diff --git a/websites/datastream.js.org/src/routes/docs/packages/base64/+page.md b/websites/datastream.js.org/src/routes/docs/packages/base64/+page.md index cde23c4..ac9f128 100644 --- a/websites/datastream.js.org/src/routes/docs/packages/base64/+page.md +++ b/websites/datastream.js.org/src/routes/docs/packages/base64/+page.md @@ -15,6 +15,8 @@ npm install @datastream/base64 Encodes data to base64. Handles chunk boundaries correctly by buffering partial 3-byte groups. +> **Note:** Input must be Latin1-encoded strings (code points 0-255). Use `charsetEncodeStream` to convert other encodings before base64 encoding. + ### Example ```javascript From 8a2d80f7a36e90c0bcad4a374a4ca9c7329195cf Mon Sep 17 00:00:00 2001 From: will Farrell Date: Thu, 16 Apr 2026 10:48:20 -0600 Subject: [PATCH 5/8] chore: clean up Signed-off-by: will Farrell --- packages/csv/index.js | 10 +++++++--- packages/digest/index.web.js | 3 +++ packages/object/index.d.ts | 1 + packages/validate/index.d.ts | 1 + 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/csv/index.js b/packages/csv/index.js index 310808d..29cbdd4 100644 --- a/packages/csv/index.js +++ b/packages/csv/index.js @@ -917,11 +917,15 @@ const coerceToType = (val, type) => { return Number.isNaN(n) ? val : n; } case "boolean": - return val.toLowerCase() === "true"; + return typeof val === "string" + ? val.toLowerCase() === "true" + : Boolean(val); case "null": return null; - case "date": - return new Date(val); + case "date": { + const d = new Date(val); + return Number.isNaN(d.getTime()) ? val : d; + } case "json": try { return JSON.parse(val); diff --git a/packages/digest/index.web.js b/packages/digest/index.web.js index a0a6c52..1d2dc51 100644 --- a/packages/digest/index.web.js +++ b/packages/digest/index.web.js @@ -21,6 +21,9 @@ export const digestStream = async ( { algorithm, resultKey }, streamOptions = {}, ) => { + if (!algorithms[algorithm]) { + throw new Error(`Unsupported algorithm: ${algorithm}`); + } const hash = await algorithms[algorithm](); const passThrough = (chunk) => { hash.update(chunk); diff --git a/packages/object/index.d.ts b/packages/object/index.d.ts index 1cb3d03..1a96dc2 100644 --- a/packages/object/index.d.ts +++ b/packages/object/index.d.ts @@ -25,6 +25,7 @@ export function objectCountStream( export function objectBatchStream<_T = Record>( options: { keys: string[]; + maxBatchSize?: number; }, streamOptions?: StreamOptions, ): DatastreamTransform; diff --git a/packages/validate/index.d.ts b/packages/validate/index.d.ts index 849c23f..84133b7 100644 --- a/packages/validate/index.d.ts +++ b/packages/validate/index.d.ts @@ -25,6 +25,7 @@ export function validateStream( onErrorEnqueue?: boolean; allowCoerceTypes?: boolean; resultKey?: string; + maxErrors?: number; }, streamOptions?: StreamOptions, ): DatastreamTransform & { From 04c153698a67fac92064e28f7dfc0a4f716f48c7 Mon Sep 17 00:00:00 2001 From: will Farrell Date: Thu, 16 Apr 2026 11:13:35 -0600 Subject: [PATCH 6/8] fix: name Signed-off-by: will Farrell --- packages/validate/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/validate/index.d.ts b/packages/validate/index.d.ts index 84133b7..dc22d2e 100644 --- a/packages/validate/index.d.ts +++ b/packages/validate/index.d.ts @@ -25,7 +25,7 @@ export function validateStream( onErrorEnqueue?: boolean; allowCoerceTypes?: boolean; resultKey?: string; - maxErrors?: number; + maxErrorRows?: number; }, streamOptions?: StreamOptions, ): DatastreamTransform & { From 54010ec5991526b5f6d34a5b0ef5f9ceb0116c4c Mon Sep 17 00:00:00 2001 From: will Farrell Date: Thu, 16 Apr 2026 11:15:11 -0600 Subject: [PATCH 7/8] chore: version bump Signed-off-by: will Farrell --- .github/package.json | 2 +- package-lock.json | 484 ++++++++++++------------ package.json | 2 +- packages/aws/package.json | 4 +- packages/base64/package.json | 4 +- packages/charset/package.json | 4 +- packages/compress/package.json | 4 +- packages/core/package.json | 4 +- packages/csv/package.json | 6 +- packages/digest/package.json | 4 +- packages/encrypt/package.json | 4 +- packages/fetch/package.json | 4 +- packages/file/package.json | 4 +- packages/indexeddb/package.json | 4 +- packages/ipfs/package.json | 4 +- packages/json/package.json | 4 +- packages/object/package.json | 4 +- packages/string/package.json | 4 +- packages/validate/package.json | 4 +- websites/datastream.js.org/package.json | 2 +- 20 files changed, 278 insertions(+), 278 deletions(-) diff --git a/.github/package.json b/.github/package.json index 490a4ba..567438f 100644 --- a/.github/package.json +++ b/.github/package.json @@ -1,6 +1,6 @@ { "name": "@datastream/github-workflows", - "version": "0.3.0", + "version": "0.3.1", "private": true, "engines": { "node": ">=24.0" diff --git a/package-lock.json b/package-lock.json index 352c49b..264fae7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@datastream/monorepo", - "version": "0.3.0", + "version": "0.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@datastream/monorepo", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "workspaces": [ "packages/*", @@ -30,7 +30,7 @@ }, ".github": { "name": "@datastream/github-workflows", - "version": "0.2.0", + "version": "0.3.1", "devDependencies": { "license-check-and-add": "4.0.5", "lockfile-lint": "5.0.0" @@ -3832,17 +3832,17 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "4.4.15", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.15.tgz", - "integrity": "sha512-BJdMBY5YO9iHh+lPLYdHv6LbX+J8IcPCYMl1IJdBt2KDWNHwONHrPVHk3ttYBqJd9wxv84wlbN0f7GlQzcQtNQ==", + "version": "4.4.16", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.16.tgz", + "integrity": "sha512-GFlGPNLZKrGfqWpqVb31z7hvYCA9ZscfX1buYnvvMGcRYsQQnhH+4uN6mWWflcD5jB4OXP/LBrdpukEdjl41tg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.13", - "@smithy/types": "^4.14.0", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/types": "^4.14.1", "@smithy/util-config-provider": "^4.2.2", - "@smithy/util-endpoints": "^3.4.0", - "@smithy/util-middleware": "^4.2.13", + "@smithy/util-endpoints": "^3.4.1", + "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" }, "engines": { @@ -3850,19 +3850,19 @@ } }, "node_modules/@smithy/core": { - "version": "3.23.14", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.14.tgz", - "integrity": "sha512-vJ0IhpZxZAkFYOegMKSrxw7ujhhT2pass/1UEcZ4kfl5srTAqtPU5I7MdYQoreVas3204ykCiNhY1o7Xlz6Yyg==", + "version": "3.23.15", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.23.15.tgz", + "integrity": "sha512-E7GVCgsQttzfujEZb6Qep005wWf4xiL4x06apFEtzQMWYBPggZh/0cnOxPficw5cuK/YjjkehKoIN4YUaSh0UQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.13", - "@smithy/types": "^4.14.0", - "@smithy/url-parser": "^4.2.13", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", - "@smithy/util-middleware": "^4.2.13", - "@smithy/util-stream": "^4.5.22", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-stream": "^4.5.23", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" @@ -3872,16 +3872,16 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.13.tgz", - "integrity": "sha512-wboCPijzf6RJKLOvnjDAiBxGSmSnGXj35o5ZAWKDaHa/cvQ5U3ZJ13D4tMCE8JG4dxVAZFy/P0x/V9CwwdfULQ==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.14.tgz", + "integrity": "sha512-Au28zBN48ZAoXdooGUHemuVBrkE+Ie6RPmGNIAJsFqj33Vhb6xAgRifUydZ2aY+M+KaMAETAlKk5NC5h1G7wpg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.13", - "@smithy/property-provider": "^4.2.13", - "@smithy/types": "^4.14.0", - "@smithy/url-parser": "^4.2.13", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", "tslib": "^2.6.2" }, "engines": { @@ -3889,14 +3889,14 @@ } }, "node_modules/@smithy/eventstream-codec": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.13.tgz", - "integrity": "sha512-vYahwBAtRaAcFbOmE9aLr12z7RiHYDSLcnogSdxfm7kKfsNa3wH+NU5r7vTeB5rKvLsWyPjVX8iH94brP7umiQ==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-4.2.14.tgz", + "integrity": "sha512-erZq0nOIpzfeZdCyzZjdJb4nVSKLUmSkaQUVkRGQTXs30gyUGeKnrYEg+Xe1W5gE3aReS7IgsvANwVPxSzY6Pw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@aws-crypto/crc32": "5.2.0", - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" }, @@ -3905,14 +3905,14 @@ } }, "node_modules/@smithy/eventstream-serde-browser": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.13.tgz", - "integrity": "sha512-wwybfcOX0tLqCcBP378TIU9IqrDuZq/tDV48LlZNydMpCnqnYr+hWBAYbRE+rFFf/p7IkDJySM3bgiMKP2ihPg==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.2.14.tgz", + "integrity": "sha512-8IelTCtTctWRbb+0Dcy+C0aICh1qa0qWXqgjcXDmMuCvPJRnv26hiDZoAau2ILOniki65mCPKqOQs/BaWvO4CQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.13", - "@smithy/types": "^4.14.0", + "@smithy/eventstream-serde-universal": "^4.2.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3920,13 +3920,13 @@ } }, "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "4.3.13", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.13.tgz", - "integrity": "sha512-ied1lO559PtAsMJzg2TKRlctLnEi1PfkNeMMpdwXDImk1zV9uvS/Oxoy/vcy9uv1GKZAjDAB5xT6ziE9fzm5wA==", + "version": "4.3.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.3.14.tgz", + "integrity": "sha512-sqHiHpYRYo3FJlaIxD1J8PhbcmJAm7IuM16mVnwSkCToD7g00IBZzKuiLNMGmftULmEUX6/UAz8/NN5uMP8bVA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3934,14 +3934,14 @@ } }, "node_modules/@smithy/eventstream-serde-node": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.13.tgz", - "integrity": "sha512-hFyK+ORJrxAN3RYoaD6+gsGDQjeix8HOEkosoajvXYZ4VeqonM3G4jd9IIRm/sWGXUKmudkY9KdYjzosUqdM8A==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.2.14.tgz", + "integrity": "sha512-Ht/8BuGlKfFTy0H3+8eEu0vdpwGztCnaLLXtpXNdQqiR7Hj4vFScU3T436vRAjATglOIPjJXronY+1WxxNLSiw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-serde-universal": "^4.2.13", - "@smithy/types": "^4.14.0", + "@smithy/eventstream-serde-universal": "^4.2.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3949,14 +3949,14 @@ } }, "node_modules/@smithy/eventstream-serde-universal": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.13.tgz", - "integrity": "sha512-kRrq4EKLGeOxhC2CBEhRNcu1KSzNJzYY7RK3S7CxMPgB5dRrv55WqQOtRwQxQLC04xqORFLUgnDlc6xrNUULaA==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-4.2.14.tgz", + "integrity": "sha512-lWyt4T2XQZUZgK3tQ3Wn0w3XBvZsK/vjTuJl6bXbnGZBHH0ZUSONTYiK9TgjTTzU54xQr3DRFwpjmhp0oLm3gg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/eventstream-codec": "^4.2.13", - "@smithy/types": "^4.14.0", + "@smithy/eventstream-codec": "^4.2.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3964,15 +3964,15 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.16", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.16.tgz", - "integrity": "sha512-nYDRUIvNd4mFmuXraRWt6w5UsZTNqtj4hXJA/iiOD4tuseIdLP9Lq38teH/SZTcIFCa2f+27o7hYpIsWktJKEQ==", + "version": "5.3.17", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.17.tgz", + "integrity": "sha512-bXOvQzaSm6MnmLaWA1elgfQcAtN4UP3vXqV97bHuoOrHQOJiLT3ds6o9eo5bqd0TJfRFpzdGnDQdW3FACiAVdw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.13", - "@smithy/querystring-builder": "^4.2.13", - "@smithy/types": "^4.14.0", + "@smithy/protocol-http": "^5.3.14", + "@smithy/querystring-builder": "^4.2.14", + "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" }, @@ -3981,15 +3981,15 @@ } }, "node_modules/@smithy/hash-blob-browser": { - "version": "4.2.14", - "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.14.tgz", - "integrity": "sha512-rtQ5es8r/5v4rav7q5QTsfx9CtCyzrz/g7ZZZBH2xtMmd6G/KQrLOWfSHTvFOUPlVy59RQvxeBYJaLRoybMEyA==", + "version": "4.2.15", + "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-4.2.15.tgz", + "integrity": "sha512-0PJ4Al3fg2nM4qKrAIxyNcApgqHAXcBkN8FeizOz69z0rb26uZ6lMESYtxegaTlXB5Hj84JfwMPavMrwDMjucA==", "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/chunked-blob-reader": "^5.2.2", "@smithy/chunked-blob-reader-native": "^4.2.3", - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -3997,13 +3997,13 @@ } }, "node_modules/@smithy/hash-node": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.13.tgz", - "integrity": "sha512-4/oy9h0jjmY80a2gOIo75iLl8TOPhmtx4E2Hz+PfMjvx/vLtGY4TMU/35WRyH2JHPfT5CVB38u4JRow7gnmzJA==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.14.tgz", + "integrity": "sha512-8ZBDY2DD4wr+GGjTpPtiglEsqr0lUP+KHqgZcWczFf6qeZ/YRjMIOoQWVQlmwu7EtxKTd8YXD8lblmYcpBIA1g==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" @@ -4013,13 +4013,13 @@ } }, "node_modules/@smithy/hash-stream-node": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.13.tgz", - "integrity": "sha512-WdQ7HwUjINXETeh6dqUeob1UHIYx8kAn9PSp1HhM2WWegiZBYVy2WXIs1lB07SZLan/udys9SBnQGt9MQbDpdg==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-4.2.14.tgz", + "integrity": "sha512-tw4GANWkZPb6+BdD4Fgucqzey2+r73Z/GRo9zklsCdwrnxxumUV83ZIaBDdudV4Ylazw3EPTiJZhpX42105ruQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -4028,13 +4028,13 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.13.tgz", - "integrity": "sha512-jvC0RB/8BLj2SMIkY0Npl425IdnxZJxInpZJbu563zIRnVjpDMXevU3VMCRSabaLB0kf/eFIOusdGstrLJ8IDg==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.14.tgz", + "integrity": "sha512-c21qJiTSb25xvvOp+H2TNZzPCngrvl5vIPqPB8zQ/DmJF4QWXO19x1dWfMJZ6wZuuWUPPm0gV8C0cU3+ifcWuw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -4055,13 +4055,13 @@ } }, "node_modules/@smithy/md5-js": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.13.tgz", - "integrity": "sha512-cNm7I9NXolFxtS20ojROddOEpSAeI1Obq6pd1Kj5HtHws3s9Fkk8DdHDfQSs5KuxCewZuVK6UqrJnfJmiMzDuQ==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-4.2.14.tgz", + "integrity": "sha512-V2v0vx+h0iUSNG1Alt+GNBMSLGCrl9iVsdd+Ap67HPM9PN479x12V8LkuMoKImNZxn3MXeuyUjls+/7ZACZghA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" }, @@ -4070,14 +4070,14 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.13.tgz", - "integrity": "sha512-IPMLm/LE4AZwu6qiE8Rr8vJsWhs9AtOdySRXrOM7xnvclp77Tyh7hMs/FRrMf26kgIe67vFJXXOSmVxS7oKeig==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.14.tgz", + "integrity": "sha512-xhHq7fX4/3lv5NHxLUk3OeEvl0xZ+Ek3qIbWaCL4f9JwgDZEclPBElljaZCAItdGPQl/kSM4LPMOpy1MYgprpw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.13", - "@smithy/types": "^4.14.0", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -4085,19 +4085,19 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.29", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.29.tgz", - "integrity": "sha512-R9Q/58U+qBiSARGWbAbFLczECg/RmysRksX6Q8BaQEpt75I7LI6WGDZnjuC9GXSGKljEbA7N118LhGaMbfrTXw==", + "version": "4.4.30", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.30.tgz", + "integrity": "sha512-qS2XqhKeXmdZ4nEQ4cOxIczSP/Y91wPAHYuRwmWDCh975B7/57uxsm5d6sisnUThn2u2FwzMdJNM7AbO1YPsPg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.14", - "@smithy/middleware-serde": "^4.2.17", - "@smithy/node-config-provider": "^4.3.13", - "@smithy/shared-ini-file-loader": "^4.4.8", - "@smithy/types": "^4.14.0", - "@smithy/url-parser": "^4.2.13", - "@smithy/util-middleware": "^4.2.13", + "@smithy/core": "^3.23.15", + "@smithy/middleware-serde": "^4.2.18", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", + "@smithy/url-parser": "^4.2.14", + "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" }, "engines": { @@ -4105,20 +4105,20 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.1.tgz", - "integrity": "sha512-/zY+Gp7Qj2D2hVm3irkCyONER7E9MiX3cUUm/k2ZmhkzZkrPgwVS4aJ5NriZUEN/M0D1hhjrgjUmX04HhRwdWA==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.5.3.tgz", + "integrity": "sha512-TE8dJNi6JuxzGSxMCVd3i9IEWDndCl3bmluLsBNDWok8olgj65OfkndMhl9SZ7m14c+C5SQn/PcUmrDl57rSFw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.14", - "@smithy/node-config-provider": "^4.3.13", - "@smithy/protocol-http": "^5.3.13", - "@smithy/service-error-classification": "^4.2.13", - "@smithy/smithy-client": "^4.12.9", - "@smithy/types": "^4.14.0", - "@smithy/util-middleware": "^4.2.13", - "@smithy/util-retry": "^4.3.1", + "@smithy/core": "^3.23.15", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/service-error-classification": "^4.2.14", + "@smithy/smithy-client": "^4.12.11", + "@smithy/types": "^4.14.1", + "@smithy/util-middleware": "^4.2.14", + "@smithy/util-retry": "^4.3.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" }, @@ -4127,15 +4127,15 @@ } }, "node_modules/@smithy/middleware-serde": { - "version": "4.2.17", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.17.tgz", - "integrity": "sha512-0T2mcaM6v9W1xku86Dk0bEW7aEseG6KenFkPK98XNw0ZhOqOiD1MrMsdnQw9QsL3/Oa85T53iSMlm0SZdSuIEQ==", + "version": "4.2.18", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.18.tgz", + "integrity": "sha512-M6CSgnp3v4tYz9ynj2JHbA60woBZcGqEwNjTKjBsNHPV26R1ZX52+0wW8WsZU18q45jD0tw2wL22S17Ze9LpEw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.14", - "@smithy/protocol-http": "^5.3.13", - "@smithy/types": "^4.14.0", + "@smithy/core": "^3.23.15", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -4143,13 +4143,13 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.13.tgz", - "integrity": "sha512-g72jN/sGDLyTanrCLH9fhg3oysO3f7tQa6eWWsMyn2BiYNCgjF24n4/I9wff/5XidFvjj9ilipAoQrurTUrLvw==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.14.tgz", + "integrity": "sha512-2dvkUKLuFdKsCRmOE4Mn63co0Djtsm+JMh0bYZQupN1pJwMeE8FmQmRLLzzEMN0dnNi7CDCYYH8F0EVwWiPBeA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -4157,15 +4157,15 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "4.3.13", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.13.tgz", - "integrity": "sha512-iGxQ04DsKXLckbgnX4ipElrOTk+IHgTyu0q0WssZfYhDm9CQWHmu6cOeI5wmWRxpXbBDhIIfXMWz5tPEtcVqbw==", + "version": "4.3.14", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.14.tgz", + "integrity": "sha512-S+gFjyo/weSVL0P1b9Ts8C/CwIfNCgUPikk3sl6QVsfE/uUuO+QsF+NsE/JkpvWqqyz1wg7HFdiaZuj5CoBMRg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.13", - "@smithy/shared-ini-file-loader": "^4.4.8", - "@smithy/types": "^4.14.0", + "@smithy/property-provider": "^4.2.14", + "@smithy/shared-ini-file-loader": "^4.4.9", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -4173,15 +4173,15 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.2.tgz", - "integrity": "sha512-/oD7u8M0oj2ZTFw7GkuuHWpIxtWdLlnyNkbrWcyVYhd5RJNDuczdkb0wfnQICyNFrVPlr8YHOhamjNy3zidhmA==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.5.3.tgz", + "integrity": "sha512-lc5jFL++x17sPhIwMWJ3YOnqmSjw/2Po6VLDlUIXvxVWRuJwRXnJ4jOBBLB0cfI5BB5ehIl02Fxr1PDvk/kxDw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.3.13", - "@smithy/querystring-builder": "^4.2.13", - "@smithy/types": "^4.14.0", + "@smithy/protocol-http": "^5.3.14", + "@smithy/querystring-builder": "^4.2.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -4189,13 +4189,13 @@ } }, "node_modules/@smithy/property-provider": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.13.tgz", - "integrity": "sha512-bGzUCthxRmezuxkbu9wD33wWg9KX3hJpCXpQ93vVkPrHn9ZW6KNNdY5xAUWNuRCwQ+VyboFuWirG1lZhhkcyRQ==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.14.tgz", + "integrity": "sha512-WuM31CgfsnQ/10i7NYr0PyxqknD72Y5uMfUMVSniPjbEPceiTErb4eIqJQ+pdxNEAUEWrewrGjIRjVbVHsxZiQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -4203,13 +4203,13 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.3.13", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.13.tgz", - "integrity": "sha512-+HsmuJUF4u8POo6s8/a2Yb/AQ5t/YgLovCuHF9oxbocqv+SZ6gd8lC2duBFiCA/vFHoHQhoq7QjqJqZC6xOxxg==", + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.14.tgz", + "integrity": "sha512-dN5F8kHx8RNU0r+pCwNmFZyz6ChjMkzShy/zup6MtkRmmix4vZzJdW+di7x//b1LiynIev88FM18ie+wwPcQtQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -4217,13 +4217,13 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.13.tgz", - "integrity": "sha512-tG4aOYFCZdPMjbgfhnIQ322H//ojujldp1SrHPHpBSb3NqgUp3dwiUGRJzie87hS1DYwWGqDuPaowoDF+rYCbQ==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.14.tgz", + "integrity": "sha512-XYA5Z0IqTeF+5XDdh4BBmSA0HvbgVZIyv4cmOoUheDNR57K1HgBp9ukUMx3Cr3XpDHHpLBnexPE3LAtDsZkj2A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" }, @@ -4232,13 +4232,13 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.13.tgz", - "integrity": "sha512-hqW3Q4P+CDzUyQ87GrboGMeD7XYNMOF+CuTwu936UQRB/zeYn3jys8C3w+wMkDfY7CyyyVwZQ5cNFoG0x1pYmA==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.14.tgz", + "integrity": "sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -4246,26 +4246,26 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.13.tgz", - "integrity": "sha512-a0s8XZMfOC/qpqq7RCPvJlk93rWFrElH6O++8WJKz0FqnA4Y7fkNi/0mnGgSH1C4x6MFsuBA8VKu4zxFrMe5Vw==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.14.tgz", + "integrity": "sha512-vVimoUnGxlx4eLLQbZImdOZFOe+Zh+5ACntv8VxZuGP72LdWu5GV3oEmCahSEReBgRJoWjypFkrehSj7BWx1HQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0" + "@smithy/types": "^4.14.1" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.8.tgz", - "integrity": "sha512-VZCZx2bZasxdqxVgEAhREvDSlkatTPnkdWy1+Kiy8w7kYPBosW0V5IeDwzDUMvWBt56zpK658rx1cOBFOYaPaw==", + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.9.tgz", + "integrity": "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -4273,17 +4273,17 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.3.13", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.13.tgz", - "integrity": "sha512-YpYSyM0vMDwKbHD/JA7bVOF6kToVRpa+FM5ateEVRpsTNu564g1muBlkTubXhSKKYXInhpADF46FPyrZcTLpXg==", + "version": "5.3.14", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.14.tgz", + "integrity": "sha512-1D9Y/nmlVjCeSivCbhZ7hgEpmHyY1h0GvpSZt3l0xcD9JjmjVC1CHOozS6+Gh+/ldMH8JuJ6cujObQqfayAVFA==", "dev": true, "license": "Apache-2.0", "dependencies": { "@smithy/is-array-buffer": "^4.2.2", - "@smithy/protocol-http": "^5.3.13", - "@smithy/types": "^4.14.0", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", "@smithy/util-hex-encoding": "^4.2.2", - "@smithy/util-middleware": "^4.2.13", + "@smithy/util-middleware": "^4.2.14", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" @@ -4293,18 +4293,18 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.12.9", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.9.tgz", - "integrity": "sha512-ovaLEcTU5olSeHcRXcxV6viaKtpkHZumn6Ps0yn7dRf2rRSfy794vpjOtrWDO0d1auDSvAqxO+lyhERSXQ03EQ==", + "version": "4.12.11", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.12.11.tgz", + "integrity": "sha512-wzz/Wa1CH/Tlhxh0s4DQPEcXSxSVfJ59AZcUh9Gu0c6JTlKuwGf4o/3P2TExv0VbtPFt8odIBG+eQGK2+vTECg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.23.14", - "@smithy/middleware-endpoint": "^4.4.29", - "@smithy/middleware-stack": "^4.2.13", - "@smithy/protocol-http": "^5.3.13", - "@smithy/types": "^4.14.0", - "@smithy/util-stream": "^4.5.22", + "@smithy/core": "^3.23.15", + "@smithy/middleware-endpoint": "^4.4.30", + "@smithy/middleware-stack": "^4.2.14", + "@smithy/protocol-http": "^5.3.14", + "@smithy/types": "^4.14.1", + "@smithy/util-stream": "^4.5.23", "tslib": "^2.6.2" }, "engines": { @@ -4312,9 +4312,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.14.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.0.tgz", - "integrity": "sha512-OWgntFLW88kx2qvf/c/67Vno1yuXm/f9M7QFAtVkkO29IJXGBIg0ycEaBTH0kvCtwmvZxRujrgP5a86RvsXJAQ==", + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.14.1.tgz", + "integrity": "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -4325,14 +4325,14 @@ } }, "node_modules/@smithy/url-parser": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.13.tgz", - "integrity": "sha512-2G03yoboIRZlZze2+PT4GZEjgwQsJjUgn6iTsvxA02bVceHR6vp4Cuk7TUnPFWKF+ffNUk3kj4COwkENS2K3vw==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.14.tgz", + "integrity": "sha512-p06BiBigJ8bTA3MgnOfCtDUWnAMY0YfedO/GRpmc7p+wg3KW8vbXy1xwSu5ASy0wV7rRYtlfZOIKH4XqfhjSQQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.2.13", - "@smithy/types": "^4.14.0", + "@smithy/querystring-parser": "^4.2.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -4408,15 +4408,15 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.45", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.45.tgz", - "integrity": "sha512-ag9sWc6/nWZAuK3Wm9KlFJUnRkXLrXn33RFjIAmCTFThqLHY+7wCst10BGq56FxslsDrjhSie46c8OULS+BiIw==", + "version": "4.3.47", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.47.tgz", + "integrity": "sha512-zlIuXai3/SHjQUQ8y3g/woLvrH573SK2wNjcDaHu5e9VOcC0JwM1MI0Sq0GZJyN3BwSUneIhpjZ18nsiz5AtQw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.2.13", - "@smithy/smithy-client": "^4.12.9", - "@smithy/types": "^4.14.0", + "@smithy/property-provider": "^4.2.14", + "@smithy/smithy-client": "^4.12.11", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -4424,18 +4424,18 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.50", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.50.tgz", - "integrity": "sha512-xpjncL5XozFA3No7WypTsPU1du0fFS8flIyO+Wh2nhCy7bpEapvU7BR55Bg+wrfw+1cRA+8G8UsTjaxgzrMzXg==", + "version": "4.2.52", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.52.tgz", + "integrity": "sha512-cQBz8g68Vnw1W2meXlkb3D/hXJU+Taiyj9P8qLJtjREEV9/Td65xi4A/H1sRQ8EIgX5qbZbvdYPKygKLholZ3w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.4.15", - "@smithy/credential-provider-imds": "^4.2.13", - "@smithy/node-config-provider": "^4.3.13", - "@smithy/property-provider": "^4.2.13", - "@smithy/smithy-client": "^4.12.9", - "@smithy/types": "^4.14.0", + "@smithy/config-resolver": "^4.4.16", + "@smithy/credential-provider-imds": "^4.2.14", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/property-provider": "^4.2.14", + "@smithy/smithy-client": "^4.12.11", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -4443,14 +4443,14 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.4.0.tgz", - "integrity": "sha512-QQHGPKkw6NPcU6TJ1rNEEa201srPtZiX4k61xL163vvs9sTqW/XKz+UEuJ00uvPqoN+5Rs4Ka1UJ7+Mp03IXJw==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.4.1.tgz", + "integrity": "sha512-wMxNDZJrgS5mQV9oxCs4TWl5767VMgOfqfZ3JHyCkMtGC2ykW9iPqMvFur695Otcc5yxLG8OKO/80tsQBxrhXg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.3.13", - "@smithy/types": "^4.14.0", + "@smithy/node-config-provider": "^4.3.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -4471,13 +4471,13 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.2.13", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.13.tgz", - "integrity": "sha512-GTooyrlmRTqvUen4eK7/K1p6kryF7bnDfq6XsAbIsf2mo51B/utaH+XThY6dKgNCWzMAaH/+OLmqaBuLhLWRow==", + "version": "4.2.14", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.14.tgz", + "integrity": "sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -4485,14 +4485,14 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.1.tgz", - "integrity": "sha512-FwmicpgWOkP5kZUjN3y+3JIom8NLGqSAJBeoIgK0rIToI817TEBHCrd0A2qGeKQlgDeP+Jzn4i0H/NLAXGy9uQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.3.2.tgz", + "integrity": "sha512-2+KTsJEwTi63NUv4uR9IQ+IFT1yu6Rf6JuoBK2WKaaJ/TRvOiOVGcXAsEqX/TQN2thR9yII21kPUJq1UV/WI2A==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.2.13", - "@smithy/types": "^4.14.0", + "@smithy/service-error-classification": "^4.2.14", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -4500,15 +4500,15 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.5.22", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.22.tgz", - "integrity": "sha512-3H8iq/0BfQjUs2/4fbHZ9aG9yNzcuZs24LPkcX1Q7Z+qpqaGM8+qbGmE8zo9m2nCRgamyvS98cHdcWvR6YUsew==", + "version": "4.5.23", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.23.tgz", + "integrity": "sha512-N6on1+ngJ3RznZOnDWNveIwnTSlqxNnXuNAh7ez889ZZaRdXoNRTXKgmYOLe6dB0gCmAVtuRScE1hymQFl4hpg==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.3.16", - "@smithy/node-http-handler": "^4.5.2", - "@smithy/types": "^4.14.0", + "@smithy/fetch-http-handler": "^5.3.17", + "@smithy/node-http-handler": "^4.5.3", + "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", @@ -4547,13 +4547,13 @@ } }, "node_modules/@smithy/util-waiter": { - "version": "4.2.15", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.15.tgz", - "integrity": "sha512-oUt9o7n8hBv3BL56sLSneL0XeigZSuem0Hr78JaoK33D9oKieyCvVP8eTSe3j7g2mm/S1DvzxKieG7JEWNJUNg==", + "version": "4.2.16", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.16.tgz", + "integrity": "sha512-GtclrKoZ3Lt7jPQ7aTIYKfjY92OgceScftVnkTsG8e1KV8rkvZgN+ny6YSRhd9hxB8rZtwVbmln7NTvE5O3GmQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.14.0", + "@smithy/types": "^4.14.1", "tslib": "^2.6.2" }, "engines": { @@ -10005,10 +10005,10 @@ }, "packages/aws": { "name": "@datastream/aws", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { - "@datastream/core": "0.3.0" + "@datastream/core": "0.3.1" }, "devDependencies": { "@aws-sdk/client-cloudwatch-logs": "^3.0.0", @@ -10068,10 +10068,10 @@ }, "packages/base64": { "name": "@datastream/base64", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { - "@datastream/core": "0.3.0" + "@datastream/core": "0.3.1" }, "engines": { "node": ">=24" @@ -10079,10 +10079,10 @@ }, "packages/charset": { "name": "@datastream/charset", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { - "@datastream/core": "0.3.0", + "@datastream/core": "0.3.1", "charset-detector": "0.0.2", "iconv-lite": "0.7.2" }, @@ -10092,10 +10092,10 @@ }, "packages/compress": { "name": "@datastream/compress", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { - "@datastream/core": "0.3.0" + "@datastream/core": "0.3.1" }, "engines": { "node": ">=24" @@ -10119,10 +10119,10 @@ }, "packages/core": { "name": "@datastream/core", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "devDependencies": { - "@datastream/object": "0.3.0" + "@datastream/object": "0.3.1" }, "engines": { "node": ">=24" @@ -10130,11 +10130,11 @@ }, "packages/csv": { "name": "@datastream/csv", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { - "@datastream/core": "0.3.0", - "@datastream/object": "0.3.0" + "@datastream/core": "0.3.1", + "@datastream/object": "0.3.1" }, "engines": { "node": ">=24" @@ -10142,10 +10142,10 @@ }, "packages/digest": { "name": "@datastream/digest", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { - "@datastream/core": "0.3.0", + "@datastream/core": "0.3.1", "hash-wasm": "4.12.0" }, "engines": { @@ -10154,10 +10154,10 @@ }, "packages/encrypt": { "name": "@datastream/encrypt", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { - "@datastream/core": "0.3.0" + "@datastream/core": "0.3.1" }, "engines": { "node": ">=24" @@ -10173,10 +10173,10 @@ }, "packages/fetch": { "name": "@datastream/fetch", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { - "@datastream/core": "0.3.0" + "@datastream/core": "0.3.1" }, "engines": { "node": ">=24" @@ -10184,10 +10184,10 @@ }, "packages/file": { "name": "@datastream/file", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { - "@datastream/core": "0.3.0" + "@datastream/core": "0.3.1" }, "engines": { "node": ">=24" @@ -10195,10 +10195,10 @@ }, "packages/indexeddb": { "name": "@datastream/indexeddb", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { - "@datastream/core": "0.3.0", + "@datastream/core": "0.3.1", "idb": "8.0.3" }, "engines": { @@ -10207,10 +10207,10 @@ }, "packages/ipfs": { "name": "@datastream/ipfs", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { - "@datastream/core": "0.3.0" + "@datastream/core": "0.3.1" }, "engines": { "node": ">=24" @@ -10218,10 +10218,10 @@ }, "packages/json": { "name": "@datastream/json", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { - "@datastream/core": "0.3.0" + "@datastream/core": "0.3.1" }, "engines": { "node": ">=24" @@ -10229,10 +10229,10 @@ }, "packages/object": { "name": "@datastream/object", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { - "@datastream/core": "0.3.0" + "@datastream/core": "0.3.1" }, "engines": { "node": ">=24" @@ -10240,10 +10240,10 @@ }, "packages/string": { "name": "@datastream/string", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { - "@datastream/core": "0.3.0" + "@datastream/core": "0.3.1" }, "engines": { "node": ">=24" @@ -10251,10 +10251,10 @@ }, "packages/validate": { "name": "@datastream/validate", - "version": "0.3.0", + "version": "0.3.1", "license": "MIT", "dependencies": { - "@datastream/core": "0.3.0", + "@datastream/core": "0.3.1", "ajv-cmd": "0.11.0" }, "engines": { @@ -10262,7 +10262,7 @@ } }, "websites/datastream.js.org": { - "version": "0.3.0", + "version": "0.3.1", "dependencies": { "@plausible-analytics/tracker": "0.4.4", "@willfarrell-ds/svelte": "0.0.0-alpha.6", diff --git a/package.json b/package.json index 3e5bd3e..d172920 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@datastream/monorepo", - "version": "0.3.0", + "version": "0.3.1", "description": "Streams made easy.", "private": true, "type": "module", diff --git a/packages/aws/package.json b/packages/aws/package.json index e7b912e..f791f3f 100644 --- a/packages/aws/package.json +++ b/packages/aws/package.json @@ -1,6 +1,6 @@ { "name": "@datastream/aws", - "version": "0.3.0", + "version": "0.3.1", "description": "AWS service streaming integrations for CloudWatch Logs, DynamoDB, Kinesis, Lambda, S3, SNS, and SQS", "type": "module", "engines": { @@ -148,7 +148,7 @@ }, "homepage": "https://datastream.js.org", "dependencies": { - "@datastream/core": "0.3.0" + "@datastream/core": "0.3.1" }, "peerDependencies": { "@aws-sdk/client-cloudwatch-logs": "^3.0.0", diff --git a/packages/base64/package.json b/packages/base64/package.json index 5809708..01c6532 100644 --- a/packages/base64/package.json +++ b/packages/base64/package.json @@ -1,6 +1,6 @@ { "name": "@datastream/base64", - "version": "0.3.0", + "version": "0.3.1", "description": "Base64 encoding and decoding transform streams", "type": "module", "engines": { @@ -60,6 +60,6 @@ }, "homepage": "https://datastream.js.org", "dependencies": { - "@datastream/core": "0.3.0" + "@datastream/core": "0.3.1" } } diff --git a/packages/charset/package.json b/packages/charset/package.json index dd13be7..d460327 100644 --- a/packages/charset/package.json +++ b/packages/charset/package.json @@ -1,6 +1,6 @@ { "name": "@datastream/charset", - "version": "0.3.0", + "version": "0.3.1", "description": "Character encoding detection, decoding, and conversion streams", "type": "module", "engines": { @@ -108,7 +108,7 @@ }, "homepage": "https://datastream.js.org", "dependencies": { - "@datastream/core": "0.3.0", + "@datastream/core": "0.3.1", "charset-detector": "0.0.2", "iconv-lite": "0.7.2" } diff --git a/packages/compress/package.json b/packages/compress/package.json index ddd7ace..c496530 100644 --- a/packages/compress/package.json +++ b/packages/compress/package.json @@ -1,6 +1,6 @@ { "name": "@datastream/compress", - "version": "0.3.0", + "version": "0.3.1", "description": "Compression and decompression streams for gzip, deflate, brotli, and zstd", "type": "module", "engines": { @@ -140,7 +140,7 @@ }, "homepage": "https://datastream.js.org", "dependencies": { - "@datastream/core": "0.3.0" + "@datastream/core": "0.3.1" }, "peerDependencies": { "brotli-wasm": "^3.0.0", diff --git a/packages/core/package.json b/packages/core/package.json index 1c727fc..c589345 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@datastream/core", - "version": "0.3.0", + "version": "0.3.1", "description": "Stream creation utilities and pipeline functions for Web Streams API and Node.js streams", "type": "module", "engines": { @@ -61,6 +61,6 @@ "homepage": "https://datastream.js.org", "dependencies": {}, "devDependencies": { - "@datastream/object": "0.3.0" + "@datastream/object": "0.3.1" } } diff --git a/packages/csv/package.json b/packages/csv/package.json index 1cb73e7..9eb4589 100644 --- a/packages/csv/package.json +++ b/packages/csv/package.json @@ -1,6 +1,6 @@ { "name": "@datastream/csv", - "version": "0.3.0", + "version": "0.3.1", "description": "CSV parsing and formatting transform streams", "type": "module", "engines": { @@ -63,7 +63,7 @@ }, "homepage": "https://datastream.js.org", "dependencies": { - "@datastream/core": "0.3.0", - "@datastream/object": "0.3.0" + "@datastream/core": "0.3.1", + "@datastream/object": "0.3.1" } } diff --git a/packages/digest/package.json b/packages/digest/package.json index 619a1f6..d67fe18 100644 --- a/packages/digest/package.json +++ b/packages/digest/package.json @@ -1,6 +1,6 @@ { "name": "@datastream/digest", - "version": "0.3.0", + "version": "0.3.1", "description": "Cryptographic hash digest pass-through streams", "type": "module", "engines": { @@ -60,7 +60,7 @@ }, "homepage": "https://datastream.js.org", "dependencies": { - "@datastream/core": "0.3.0", + "@datastream/core": "0.3.1", "hash-wasm": "4.12.0" } } diff --git a/packages/encrypt/package.json b/packages/encrypt/package.json index d602a36..4b076ef 100644 --- a/packages/encrypt/package.json +++ b/packages/encrypt/package.json @@ -1,6 +1,6 @@ { "name": "@datastream/encrypt", - "version": "0.3.0", + "version": "0.3.1", "description": "Symmetric encryption/decryption streams", "type": "module", "engines": { @@ -60,7 +60,7 @@ }, "homepage": "https://datastream.js.org", "dependencies": { - "@datastream/core": "0.3.0" + "@datastream/core": "0.3.1" }, "peerDependencies": { "libsodium-wrappers": ">=0.7.0" diff --git a/packages/fetch/package.json b/packages/fetch/package.json index dbe517c..990837e 100644 --- a/packages/fetch/package.json +++ b/packages/fetch/package.json @@ -1,6 +1,6 @@ { "name": "@datastream/fetch", - "version": "0.3.0", + "version": "0.3.1", "description": "HTTP fetch-based readable and writable streams with pagination and rate limiting", "type": "module", "engines": { @@ -60,6 +60,6 @@ }, "homepage": "https://datastream.js.org", "dependencies": { - "@datastream/core": "0.3.0" + "@datastream/core": "0.3.1" } } diff --git a/packages/file/package.json b/packages/file/package.json index 06a04fc..af5f8c8 100644 --- a/packages/file/package.json +++ b/packages/file/package.json @@ -1,6 +1,6 @@ { "name": "@datastream/file", - "version": "0.3.0", + "version": "0.3.1", "description": "File system readable and writable streams with extension type enforcement", "type": "module", "engines": { @@ -60,6 +60,6 @@ }, "homepage": "https://datastream.js.org", "dependencies": { - "@datastream/core": "0.3.0" + "@datastream/core": "0.3.1" } } diff --git a/packages/indexeddb/package.json b/packages/indexeddb/package.json index d32480c..4f3e53d 100644 --- a/packages/indexeddb/package.json +++ b/packages/indexeddb/package.json @@ -1,6 +1,6 @@ { "name": "@datastream/indexeddb", - "version": "0.3.0", + "version": "0.3.1", "description": "IndexedDB readable and writable streams for browser storage", "type": "module", "engines": { @@ -60,7 +60,7 @@ }, "homepage": "https://datastream.js.org", "dependencies": { - "@datastream/core": "0.3.0", + "@datastream/core": "0.3.1", "idb": "8.0.3" } } diff --git a/packages/ipfs/package.json b/packages/ipfs/package.json index a74b05c..1dc7ab7 100644 --- a/packages/ipfs/package.json +++ b/packages/ipfs/package.json @@ -1,6 +1,6 @@ { "name": "@datastream/ipfs", - "version": "0.3.0", + "version": "0.3.1", "description": "IPFS get and add streaming operations", "type": "module", "engines": { @@ -61,6 +61,6 @@ }, "homepage": "https://datastream.js.org", "dependencies": { - "@datastream/core": "0.3.0" + "@datastream/core": "0.3.1" } } diff --git a/packages/json/package.json b/packages/json/package.json index fdd458b..1337691 100644 --- a/packages/json/package.json +++ b/packages/json/package.json @@ -1,6 +1,6 @@ { "name": "@datastream/json", - "version": "0.3.0", + "version": "0.3.1", "description": "JSON and NDJSON (JSON Lines) parsing and formatting transform streams", "type": "module", "engines": { @@ -63,6 +63,6 @@ }, "homepage": "https://datastream.js.org", "dependencies": { - "@datastream/core": "0.3.0" + "@datastream/core": "0.3.1" } } diff --git a/packages/object/package.json b/packages/object/package.json index 81aea1b..ebe35e2 100644 --- a/packages/object/package.json +++ b/packages/object/package.json @@ -1,6 +1,6 @@ { "name": "@datastream/object", - "version": "0.3.0", + "version": "0.3.1", "description": "Object transform streams for picking, omitting, pivoting, batching, and key mapping", "type": "module", "engines": { @@ -60,6 +60,6 @@ }, "homepage": "https://datastream.js.org", "dependencies": { - "@datastream/core": "0.3.0" + "@datastream/core": "0.3.1" } } diff --git a/packages/string/package.json b/packages/string/package.json index 0507619..c50e388 100644 --- a/packages/string/package.json +++ b/packages/string/package.json @@ -1,6 +1,6 @@ { "name": "@datastream/string", - "version": "0.3.0", + "version": "0.3.1", "description": "String transform streams for splitting, replacing, counting, and deduplication", "type": "module", "engines": { @@ -60,6 +60,6 @@ }, "homepage": "https://datastream.js.org", "dependencies": { - "@datastream/core": "0.3.0" + "@datastream/core": "0.3.1" } } diff --git a/packages/validate/package.json b/packages/validate/package.json index ab1a5a3..087ef37 100644 --- a/packages/validate/package.json +++ b/packages/validate/package.json @@ -1,6 +1,6 @@ { "name": "@datastream/validate", - "version": "0.3.0", + "version": "0.3.1", "description": "JSON Schema validation transform streams using Ajv", "type": "module", "engines": { @@ -60,7 +60,7 @@ }, "homepage": "https://datastream.js.org", "dependencies": { - "@datastream/core": "0.3.0", + "@datastream/core": "0.3.1", "ajv-cmd": "0.11.0" } } diff --git a/websites/datastream.js.org/package.json b/websites/datastream.js.org/package.json index 7d864dd..01cf2f8 100644 --- a/websites/datastream.js.org/package.json +++ b/websites/datastream.js.org/package.json @@ -2,7 +2,7 @@ "name": "datastream.js.org", "description": "SvelteKit SSR", "private": true, - "version": "0.3.0", + "version": "0.3.1", "type": "module", "scripts": { "start": "vite dev", From 7ab24b6c5d97993a3f47983ceb8dcb1f10f86672 Mon Sep 17 00:00:00 2001 From: will Farrell Date: Thu, 16 Apr 2026 12:41:26 -0600 Subject: [PATCH 8/8] ci: fix issues Signed-off-by: will Farrell --- packages/aws/index.tst.ts | 2 ++ packages/aws/kinesis.js | 3 +- packages/base64/index.tst.ts | 2 ++ packages/charset/index.test.js | 13 ++++--- packages/charset/index.tst.ts | 2 ++ packages/compress/deflate.node.js | 27 +++++++++++++-- packages/compress/gzip.node.js | 28 ++++++++++++++-- packages/compress/index.tst.ts | 2 ++ packages/csv/index.tst.ts | 2 ++ packages/digest/index.tst.ts | 2 ++ packages/encrypt/index.node.js | 18 +++++++++- packages/encrypt/index.test.js | 56 ++++++++++++++----------------- packages/encrypt/index.tst.ts | 2 ++ packages/fetch/index.tst.ts | 2 ++ packages/indexeddb/index.test.js | 4 ++- packages/json/index.tst.ts | 2 ++ packages/object/index.tst.ts | 2 ++ packages/string/index.tst.ts | 2 ++ packages/validate/index.tst.ts | 2 ++ 19 files changed, 131 insertions(+), 42 deletions(-) diff --git a/packages/aws/index.tst.ts b/packages/aws/index.tst.ts index c70c7f2..4c251dd 100644 --- a/packages/aws/index.tst.ts +++ b/packages/aws/index.tst.ts @@ -1,3 +1,5 @@ +/// +/// import { awsCloudWatchLogsFilterLogEventsStream, awsCloudWatchLogsGetLogEventsStream, diff --git a/packages/aws/kinesis.js b/packages/aws/kinesis.js index 1358781..aa82d64 100644 --- a/packages/aws/kinesis.js +++ b/packages/aws/kinesis.js @@ -29,7 +29,8 @@ export const awsKinesisGetRecordsStream = async ( yield item; } opts.ShardIterator = response.NextShardIterator; - expectMore = pollingActive || records.length > 0; + expectMore = + opts.ShardIterator !== null && (pollingActive || records.length > 0); if (pollingActive && records.length === 0 && pollingDelay > 0) { await new Promise((resolve) => setTimeout(resolve, pollingDelay)); } diff --git a/packages/base64/index.tst.ts b/packages/base64/index.tst.ts index 279bd4c..a69f8e2 100644 --- a/packages/base64/index.tst.ts +++ b/packages/base64/index.tst.ts @@ -1,3 +1,5 @@ +/// +/// import _default, { base64DecodeStream, base64EncodeStream, diff --git a/packages/charset/index.test.js b/packages/charset/index.test.js index 6cdfde8..bc3b17a 100644 --- a/packages/charset/index.test.js +++ b/packages/charset/index.test.js @@ -1,4 +1,4 @@ -import { deepStrictEqual, strictEqual } from "node:assert"; +import { deepStrictEqual, ok, strictEqual } from "node:assert"; import test from "node:test"; import { charsetDecodeStream, @@ -349,9 +349,12 @@ test(`${variant}: charsetDetectStream should normalize confidence across chunks` ); }); -// *** charsetEncodeStream charset validation (web) *** // +// *** charsetEncodeStream charset validation (web-only) *** // +// Web implementation only supports UTF-8; node supports all charsets via iconv if (variant === "webstream") { - test(`${variant}: charsetEncodeStream should throw for non-UTF-8 charset`, async (_t) => { + test(`${variant}: charsetEncodeStream should throw for non-UTF-8 charset`, { + skip: "requires web implementation", + }, async (_t) => { try { charsetEncodeStream({ charset: "iso-8859-1" }); throw new Error("Expected error"); @@ -360,7 +363,9 @@ if (variant === "webstream") { } }); - test(`${variant}: charsetDecodeStream should throw for unsupported charset`, async (_t) => { + test(`${variant}: charsetDecodeStream should throw for unsupported charset`, { + skip: "requires web implementation", + }, async (_t) => { try { charsetDecodeStream({ charset: "INVALID-CHARSET-999" }); throw new Error("Expected error"); diff --git a/packages/charset/index.tst.ts b/packages/charset/index.tst.ts index f6bcf00..e47d6b8 100644 --- a/packages/charset/index.tst.ts +++ b/packages/charset/index.tst.ts @@ -1,3 +1,5 @@ +/// +/// import { charsetDecodeStream, charsetDetectStream, diff --git a/packages/compress/deflate.node.js b/packages/compress/deflate.node.js index 0b36661..ff80036 100644 --- a/packages/compress/deflate.node.js +++ b/packages/compress/deflate.node.js @@ -4,8 +4,31 @@ import { createDeflate, createInflate } from "node:zlib"; // quality -1 - 9 export const deflateCompressStream = (options = {}, _streamOptions = {}) => { - const { quality, ...rest } = options; - return createDeflate({ ...rest, level: rest.level ?? quality }); + const { quality, maxOutputSize, ...rest } = options; + const stream = createDeflate({ ...rest, level: rest.level ?? quality }); + if (maxOutputSize !== null && maxOutputSize !== undefined) { + let outputSize = 0; + const originalPush = stream.push.bind(stream); + stream.push = (chunk) => { + if (chunk !== null) { + outputSize += chunk.length; + if (outputSize > maxOutputSize) { + stream.push = originalPush; + stream.destroy( + new Error( + `Compression output exceeds maxOutputSize (${maxOutputSize} bytes)`, + ), + ); + return false; + } + } + return originalPush(chunk); + }; + stream.on("close", () => { + stream.push = originalPush; + }); + } + return stream; }; export const deflateDecompressStream = (options = {}, streamOptions = {}) => { const { maxOutputSize } = options; diff --git a/packages/compress/gzip.node.js b/packages/compress/gzip.node.js index 727945b..c0a9255 100644 --- a/packages/compress/gzip.node.js +++ b/packages/compress/gzip.node.js @@ -3,8 +3,32 @@ import { createGunzip, createGzip } from "node:zlib"; // quality -1 - 9 -export const gzipCompressStream = ({ quality } = {}, streamOptions = {}) => { - return createGzip({ ...streamOptions, level: quality }); +export const gzipCompressStream = (options = {}, streamOptions = {}) => { + const { quality, maxOutputSize } = options; + const stream = createGzip({ ...streamOptions, level: quality }); + if (maxOutputSize !== null && maxOutputSize !== undefined) { + let outputSize = 0; + const originalPush = stream.push.bind(stream); + stream.push = (chunk) => { + if (chunk !== null) { + outputSize += chunk.length; + if (outputSize > maxOutputSize) { + stream.push = originalPush; + stream.destroy( + new Error( + `Compression output exceeds maxOutputSize (${maxOutputSize} bytes)`, + ), + ); + return false; + } + } + return originalPush(chunk); + }; + stream.on("close", () => { + stream.push = originalPush; + }); + } + return stream; }; export const gzipDecompressStream = (options = {}, streamOptions = {}) => { const { maxOutputSize } = options; diff --git a/packages/compress/index.tst.ts b/packages/compress/index.tst.ts index eebe39d..0a8cd51 100644 --- a/packages/compress/index.tst.ts +++ b/packages/compress/index.tst.ts @@ -1,3 +1,5 @@ +/// +/// import { brotliCompressStream, brotliDecompressStream, diff --git a/packages/csv/index.tst.ts b/packages/csv/index.tst.ts index abb4fb0..0f82f8c 100644 --- a/packages/csv/index.tst.ts +++ b/packages/csv/index.tst.ts @@ -1,3 +1,5 @@ +/// +/// import type { CsvCoerceType, CsvDelimiters, diff --git a/packages/digest/index.tst.ts b/packages/digest/index.tst.ts index 102119e..c730ac7 100644 --- a/packages/digest/index.tst.ts +++ b/packages/digest/index.tst.ts @@ -1,3 +1,5 @@ +/// +/// import type { DigestAlgorithm } from "@datastream/digest"; import { digestStream } from "@datastream/digest"; import { describe, expect, test } from "tstyche"; diff --git a/packages/encrypt/index.node.js b/packages/encrypt/index.node.js index f6abcbb..d2a0a0b 100644 --- a/packages/encrypt/index.node.js +++ b/packages/encrypt/index.node.js @@ -41,7 +41,7 @@ const validateAad = (aad) => { }; export const encryptStream = ( - { algorithm = "AES-256-GCM", key, iv, aad } = {}, + { algorithm = "AES-256-GCM", key, iv, aad, maxInputSize } = {}, streamOptions = {}, ) => { const config = algorithmMap[algorithm]; @@ -61,6 +61,22 @@ export const encryptStream = ( if (aad && authAlgorithms.includes(algorithm)) { stream.setAAD(aad); } + if (maxInputSize !== null && maxInputSize !== undefined) { + let inputSize = 0; + const originalWrite = stream._transform.bind(stream); + stream._transform = (chunk, encoding, callback) => { + inputSize += chunk.length; + if (inputSize > maxInputSize) { + callback( + new Error( + `Encryption input exceeds maxInputSize (${maxInputSize} bytes). Use AES-256-CTR for large data.`, + ), + ); + return; + } + originalWrite(chunk, encoding, callback); + }; + } stream.result = () => ({ key: "encrypt", value: { diff --git a/packages/encrypt/index.test.js b/packages/encrypt/index.test.js index 3161108..36fe1a5 100644 --- a/packages/encrypt/index.test.js +++ b/packages/encrypt/index.test.js @@ -274,39 +274,33 @@ test(`${variant}: encryptStream should handle empty input`, async (_t) => { }); // *** maxInputSize *** // -if (variant === "webstream") { - test(`${variant}: encryptStream AES-256-GCM should enforce maxInputSize`, async (_t) => { - const input = "a".repeat(200); - const enc = await encryptStream({ key, maxInputSize: 100 }); - try { - const encStream = createReadableStream(input).pipeThrough(enc); - for await (const _chunk of encStream.readable) { - // should fail - } - throw new Error("Expected maxInputSize error"); - } catch (e) { - strictEqual(e.message.includes("maxInputSize"), true); - } - }); +test(`${variant}: encryptStream AES-256-GCM should enforce maxInputSize`, async (_t) => { + const input = "a".repeat(200); + const enc = await encryptStream({ key, maxInputSize: 100 }); + try { + const streams = [createReadableStream(input), enc]; + await pipeline(streams); + throw new Error("Expected maxInputSize error"); + } catch (e) { + strictEqual(e.message.includes("maxInputSize"), true); + } +}); - test(`${variant}: encryptStream CHACHA20-POLY1305 should enforce maxInputSize`, async (_t) => { - const input = "a".repeat(200); - const enc = await encryptStream({ - key, - algorithm: "CHACHA20-POLY1305", - maxInputSize: 100, - }); - try { - const encStream = createReadableStream(input).pipeThrough(enc); - for await (const _chunk of encStream.readable) { - // should fail - } - throw new Error("Expected maxInputSize error"); - } catch (e) { - strictEqual(e.message.includes("maxInputSize"), true); - } +test(`${variant}: encryptStream CHACHA20-POLY1305 should enforce maxInputSize`, async (_t) => { + const input = "a".repeat(200); + const enc = await encryptStream({ + key, + algorithm: "CHACHA20-POLY1305", + maxInputSize: 100, }); -} + try { + const streams = [createReadableStream(input), enc]; + await pipeline(streams); + throw new Error("Expected maxInputSize error"); + } catch (e) { + strictEqual(e.message.includes("maxInputSize"), true); + } +}); // *** generateEncryptionKey error cases *** // test(`${variant}: generateEncryptionKey should throw for unsupported bits`, (_t) => { diff --git a/packages/encrypt/index.tst.ts b/packages/encrypt/index.tst.ts index b9fbf30..dbe6d36 100644 --- a/packages/encrypt/index.tst.ts +++ b/packages/encrypt/index.tst.ts @@ -1,3 +1,5 @@ +/// +/// import _default, { decryptStream, encryptStream, diff --git a/packages/fetch/index.tst.ts b/packages/fetch/index.tst.ts index 49e8deb..e3904e3 100644 --- a/packages/fetch/index.tst.ts +++ b/packages/fetch/index.tst.ts @@ -1,3 +1,5 @@ +/// +/// import type { FetchOptions } from "@datastream/fetch"; import { fetchRateLimit, diff --git a/packages/indexeddb/index.test.js b/packages/indexeddb/index.test.js index bf36ea4..f281b81 100644 --- a/packages/indexeddb/index.test.js +++ b/packages/indexeddb/index.test.js @@ -115,7 +115,9 @@ if (!isBrowser) { // *** web variant: indexedDBReadStream with index *** // if (variant === "webstream") { - test(`${variant}: indexedDBReadStream should use index and key when provided`, async (_t) => { + test(`${variant}: indexedDBReadStream should use index and key when provided`, { + skip: "requires web implementation", + }, async (_t) => { const mockCursor = { async *[Symbol.asyncIterator]() { yield { id: 1, name: "a" }; diff --git a/packages/json/index.tst.ts b/packages/json/index.tst.ts index 6c3acc2..bfb5b85 100644 --- a/packages/json/index.tst.ts +++ b/packages/json/index.tst.ts @@ -1,3 +1,5 @@ +/// +/// import type { JsonError } from "@datastream/json"; import { jsonFormatStream, diff --git a/packages/object/index.tst.ts b/packages/object/index.tst.ts index 3e479b0..7d11a28 100644 --- a/packages/object/index.tst.ts +++ b/packages/object/index.tst.ts @@ -1,3 +1,5 @@ +/// +/// import { objectBatchStream, objectCountStream, diff --git a/packages/string/index.tst.ts b/packages/string/index.tst.ts index ecc606b..6a34dd0 100644 --- a/packages/string/index.tst.ts +++ b/packages/string/index.tst.ts @@ -1,3 +1,5 @@ +/// +/// import { stringCountStream, stringLengthStream, diff --git a/packages/validate/index.tst.ts b/packages/validate/index.tst.ts index ff00992..88f5dc8 100644 --- a/packages/validate/index.tst.ts +++ b/packages/validate/index.tst.ts @@ -1,3 +1,5 @@ +/// +/// import type { ValidateError } from "@datastream/validate"; import { transpileSchema, validateStream } from "@datastream/validate"; import { describe, expect, test } from "tstyche";