watch-tail 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +158 -0
- package/build/client/_app/immutable/assets/0.Dfts6BC4.css +2 -0
- package/build/client/_app/immutable/assets/0.Dfts6BC4.css.br +0 -0
- package/build/client/_app/immutable/assets/0.Dfts6BC4.css.gz +0 -0
- package/build/client/_app/immutable/assets/BrushContext.2tkUc39H.css +1 -0
- package/build/client/_app/immutable/assets/BrushContext.2tkUc39H.css.br +0 -0
- package/build/client/_app/immutable/assets/BrushContext.2tkUc39H.css.gz +0 -0
- package/build/client/_app/immutable/assets/ChartAnnotations.Bw8CGteN.css +1 -0
- package/build/client/_app/immutable/assets/ChartAnnotations.Bw8CGteN.css.br +0 -0
- package/build/client/_app/immutable/assets/ChartAnnotations.Bw8CGteN.css.gz +0 -0
- package/build/client/_app/immutable/assets/Circle.C5MKzgk2.css +1 -0
- package/build/client/_app/immutable/assets/Circle.C5MKzgk2.css.br +1 -0
- package/build/client/_app/immutable/assets/Circle.C5MKzgk2.css.gz +0 -0
- package/build/client/_app/immutable/assets/DefaultTooltip.CGOVIm3D.css +1 -0
- package/build/client/_app/immutable/assets/DefaultTooltip.CGOVIm3D.css.br +0 -0
- package/build/client/_app/immutable/assets/DefaultTooltip.CGOVIm3D.css.gz +0 -0
- package/build/client/_app/immutable/assets/EventScatter.DA5sZ1c0.css +1 -0
- package/build/client/_app/immutable/assets/EventScatter.DA5sZ1c0.css.br +0 -0
- package/build/client/_app/immutable/assets/EventScatter.DA5sZ1c0.css.gz +0 -0
- package/build/client/_app/immutable/assets/Labels.sEaJtb97.css +1 -0
- package/build/client/_app/immutable/assets/Labels.sEaJtb97.css.br +0 -0
- package/build/client/_app/immutable/assets/Labels.sEaJtb97.css.gz +0 -0
- package/build/client/_app/immutable/assets/Legend.DnhxhJ2I.css +1 -0
- package/build/client/_app/immutable/assets/Legend.DnhxhJ2I.css.br +0 -0
- package/build/client/_app/immutable/assets/Legend.DnhxhJ2I.css.gz +0 -0
- package/build/client/_app/immutable/assets/Path.B0wDzTrn.css +1 -0
- package/build/client/_app/immutable/assets/Path.B0wDzTrn.css.br +0 -0
- package/build/client/_app/immutable/assets/Path.B0wDzTrn.css.gz +0 -0
- package/build/client/_app/immutable/assets/TransformContext.1cjyViIk.css +1 -0
- package/build/client/_app/immutable/assets/TransformContext.1cjyViIk.css.br +0 -0
- package/build/client/_app/immutable/assets/TransformContext.1cjyViIk.css.gz +0 -0
- package/build/client/_app/immutable/assets/Voronoi.ce7atosu.css +1 -0
- package/build/client/_app/immutable/assets/Voronoi.ce7atosu.css.br +0 -0
- package/build/client/_app/immutable/assets/Voronoi.ce7atosu.css.gz +0 -0
- package/build/client/_app/immutable/chunks/Amq8zxdJ.js +1 -0
- package/build/client/_app/immutable/chunks/Amq8zxdJ.js.br +0 -0
- package/build/client/_app/immutable/chunks/Amq8zxdJ.js.gz +0 -0
- package/build/client/_app/immutable/chunks/B3p3umIY.js +1 -0
- package/build/client/_app/immutable/chunks/B3p3umIY.js.br +0 -0
- package/build/client/_app/immutable/chunks/B3p3umIY.js.gz +0 -0
- package/build/client/_app/immutable/chunks/B43Eeyih.js +1 -0
- package/build/client/_app/immutable/chunks/B43Eeyih.js.br +0 -0
- package/build/client/_app/immutable/chunks/B43Eeyih.js.gz +0 -0
- package/build/client/_app/immutable/chunks/B5q6rkQ-.js +1 -0
- package/build/client/_app/immutable/chunks/B5q6rkQ-.js.br +0 -0
- package/build/client/_app/immutable/chunks/B5q6rkQ-.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BAynhd7i.js +1 -0
- package/build/client/_app/immutable/chunks/BAynhd7i.js.br +0 -0
- package/build/client/_app/immutable/chunks/BAynhd7i.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BDlvDAqe.js +1 -0
- package/build/client/_app/immutable/chunks/BDlvDAqe.js.br +0 -0
- package/build/client/_app/immutable/chunks/BDlvDAqe.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BGmgUZMc.js +10 -0
- package/build/client/_app/immutable/chunks/BGmgUZMc.js.br +0 -0
- package/build/client/_app/immutable/chunks/BGmgUZMc.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BJpWBbAS.js +1 -0
- package/build/client/_app/immutable/chunks/BJpWBbAS.js.br +0 -0
- package/build/client/_app/immutable/chunks/BJpWBbAS.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BOT2hFMn.js +1 -0
- package/build/client/_app/immutable/chunks/BOT2hFMn.js.br +0 -0
- package/build/client/_app/immutable/chunks/BOT2hFMn.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BR2LaRms.js +1 -0
- package/build/client/_app/immutable/chunks/BR2LaRms.js.br +1 -0
- package/build/client/_app/immutable/chunks/BR2LaRms.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BZFYtRPj.js +1 -0
- package/build/client/_app/immutable/chunks/BZFYtRPj.js.br +0 -0
- package/build/client/_app/immutable/chunks/BZFYtRPj.js.gz +0 -0
- package/build/client/_app/immutable/chunks/BjxZ_neZ.js +5 -0
- package/build/client/_app/immutable/chunks/BjxZ_neZ.js.br +0 -0
- package/build/client/_app/immutable/chunks/BjxZ_neZ.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Bl052uUt.js +1 -0
- package/build/client/_app/immutable/chunks/Bl052uUt.js.br +0 -0
- package/build/client/_app/immutable/chunks/Bl052uUt.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{Bw_9uNRP.js → BsH4MNzX.js} +1 -1
- package/build/client/_app/immutable/chunks/BsH4MNzX.js.br +0 -0
- package/build/client/_app/immutable/chunks/BsH4MNzX.js.gz +0 -0
- package/build/client/_app/immutable/chunks/ByhwhtiG.js +1 -0
- package/build/client/_app/immutable/chunks/ByhwhtiG.js.br +0 -0
- package/build/client/_app/immutable/chunks/ByhwhtiG.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C1lycbEF.js +1 -0
- package/build/client/_app/immutable/chunks/C1lycbEF.js.br +0 -0
- package/build/client/_app/immutable/chunks/C1lycbEF.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C58PZtCD.js +4 -0
- package/build/client/_app/immutable/chunks/C58PZtCD.js.br +0 -0
- package/build/client/_app/immutable/chunks/C58PZtCD.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CCch3uox.js +1 -0
- package/build/client/_app/immutable/chunks/CCch3uox.js.br +0 -0
- package/build/client/_app/immutable/chunks/CCch3uox.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CEU4ihsz.js +1 -0
- package/build/client/_app/immutable/chunks/CEU4ihsz.js.br +0 -0
- package/build/client/_app/immutable/chunks/CEU4ihsz.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CG6rRxUy.js +2 -0
- package/build/client/_app/immutable/chunks/CG6rRxUy.js.br +0 -0
- package/build/client/_app/immutable/chunks/CG6rRxUy.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CM5sQDq_.js +1 -0
- package/build/client/_app/immutable/chunks/CM5sQDq_.js.br +0 -0
- package/build/client/_app/immutable/chunks/CM5sQDq_.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CT_NYh_-.js +1 -0
- package/build/client/_app/immutable/chunks/CT_NYh_-.js.br +0 -0
- package/build/client/_app/immutable/chunks/CT_NYh_-.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CZIk93qi.js +1 -0
- package/build/client/_app/immutable/chunks/CZIk93qi.js.br +0 -0
- package/build/client/_app/immutable/chunks/CZIk93qi.js.gz +0 -0
- package/build/client/_app/immutable/chunks/C_aXFBl3.js +1 -0
- package/build/client/_app/immutable/chunks/C_aXFBl3.js.br +0 -0
- package/build/client/_app/immutable/chunks/C_aXFBl3.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CqWPu3PG.js +1 -0
- package/build/client/_app/immutable/chunks/CqWPu3PG.js.br +1 -0
- package/build/client/_app/immutable/chunks/CqWPu3PG.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CyncJW0D.js +1 -0
- package/build/client/_app/immutable/chunks/CyncJW0D.js.br +0 -0
- package/build/client/_app/immutable/chunks/CyncJW0D.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D4sC5v3c.js +1 -0
- package/build/client/_app/immutable/chunks/D4sC5v3c.js.br +0 -0
- package/build/client/_app/immutable/chunks/D4sC5v3c.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D76YLq-2.js +1 -0
- package/build/client/_app/immutable/chunks/D76YLq-2.js.br +0 -0
- package/build/client/_app/immutable/chunks/D76YLq-2.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D8H_ivbb.js +1 -0
- package/build/client/_app/immutable/chunks/D8H_ivbb.js.br +0 -0
- package/build/client/_app/immutable/chunks/D8H_ivbb.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DU0OQtdJ.js +1 -0
- package/build/client/_app/immutable/chunks/DU0OQtdJ.js.br +0 -0
- package/build/client/_app/immutable/chunks/DU0OQtdJ.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Db0q5_zr.js +1 -0
- package/build/client/_app/immutable/chunks/Db0q5_zr.js.br +0 -0
- package/build/client/_app/immutable/chunks/Db0q5_zr.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Dfvzj6n2.js +1 -0
- package/build/client/_app/immutable/chunks/Dfvzj6n2.js.br +0 -0
- package/build/client/_app/immutable/chunks/Dfvzj6n2.js.gz +0 -0
- package/build/client/_app/immutable/chunks/HclGiUj8.js +1 -0
- package/build/client/_app/immutable/chunks/HclGiUj8.js.br +0 -0
- package/build/client/_app/immutable/chunks/HclGiUj8.js.gz +0 -0
- package/build/client/_app/immutable/chunks/RobXhXPM.js +1 -0
- package/build/client/_app/immutable/chunks/RobXhXPM.js.br +1 -0
- package/build/client/_app/immutable/chunks/RobXhXPM.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Y5urAfNy.js +1 -0
- package/build/client/_app/immutable/chunks/Y5urAfNy.js.br +0 -0
- package/build/client/_app/immutable/chunks/Y5urAfNy.js.gz +0 -0
- package/build/client/_app/immutable/chunks/Yk6ZJZnu.js +1 -0
- package/build/client/_app/immutable/chunks/Yk6ZJZnu.js.br +0 -0
- package/build/client/_app/immutable/chunks/Yk6ZJZnu.js.gz +0 -0
- package/build/client/_app/immutable/chunks/jO4fmY1N.js +1 -0
- package/build/client/_app/immutable/chunks/jO4fmY1N.js.br +0 -0
- package/build/client/_app/immutable/chunks/jO4fmY1N.js.gz +0 -0
- package/build/client/_app/immutable/chunks/pqiPC7Y8.js +3 -0
- package/build/client/_app/immutable/chunks/pqiPC7Y8.js.br +0 -0
- package/build/client/_app/immutable/chunks/pqiPC7Y8.js.gz +0 -0
- package/build/client/_app/immutable/chunks/snfmbqby.js +1 -0
- package/build/client/_app/immutable/chunks/snfmbqby.js.br +0 -0
- package/build/client/_app/immutable/chunks/snfmbqby.js.gz +0 -0
- package/build/client/_app/immutable/chunks/wx-wtMGx.js +15 -0
- package/build/client/_app/immutable/chunks/wx-wtMGx.js.br +0 -0
- package/build/client/_app/immutable/chunks/wx-wtMGx.js.gz +0 -0
- package/build/client/_app/immutable/chunks/yCfI4PCa.js +1 -0
- package/build/client/_app/immutable/chunks/yCfI4PCa.js.br +0 -0
- package/build/client/_app/immutable/chunks/yCfI4PCa.js.gz +0 -0
- package/build/client/_app/immutable/chunks/zHL7lw4G.js +1 -0
- package/build/client/_app/immutable/chunks/zHL7lw4G.js.br +0 -0
- package/build/client/_app/immutable/chunks/zHL7lw4G.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.DFSifnlU.js +2 -0
- package/build/client/_app/immutable/entry/app.DFSifnlU.js.br +0 -0
- package/build/client/_app/immutable/entry/app.DFSifnlU.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.2H5s3Lol.js +1 -0
- package/build/client/_app/immutable/entry/start.2H5s3Lol.js.br +1 -0
- package/build/client/_app/immutable/entry/start.2H5s3Lol.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.BN3NguWs.js +1 -0
- package/build/client/_app/immutable/nodes/0.BN3NguWs.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.BN3NguWs.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.Djdw1GzA.js +1 -0
- package/build/client/_app/immutable/nodes/1.Djdw1GzA.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.Djdw1GzA.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.CjvaJDQJ.js +4 -0
- package/build/client/_app/immutable/nodes/2.CjvaJDQJ.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.CjvaJDQJ.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/handler.js +9 -7
- package/build/handler.js.map +1 -1
- package/build/index.js +9 -7
- package/build/index.js.map +1 -1
- package/build/server/chunks/chunks/Arc.canvas.js-D6g58nSp.js +701 -0
- package/build/server/chunks/chunks/Arc.canvas.js-D6g58nSp.js.map +1 -0
- package/build/server/chunks/chunks/Arc.js-D78Cf_4M.js +31 -0
- package/build/server/chunks/chunks/Arc.js-D78Cf_4M.js.map +1 -0
- package/build/server/chunks/chunks/Bar.js-Cv73T8_D.js +448 -0
- package/build/server/chunks/chunks/Bar.js-Cv73T8_D.js.map +1 -0
- package/build/server/chunks/chunks/ChartAnnotations.js-Ci22VB_6.js +983 -0
- package/build/server/chunks/chunks/ChartAnnotations.js-Ci22VB_6.js.map +1 -0
- package/build/server/chunks/chunks/Circle.html.js-BjwyNRG3.js +355 -0
- package/build/server/chunks/chunks/Circle.html.js-BjwyNRG3.js.map +1 -0
- package/build/server/chunks/chunks/ClipPath.canvas.js-CFTBDWVU.js +170 -0
- package/build/server/chunks/chunks/ClipPath.canvas.js-CFTBDWVU.js.map +1 -0
- package/build/server/chunks/chunks/DefaultTooltip.js-0r5rbE-1.js +352 -0
- package/build/server/chunks/chunks/DefaultTooltip.js-0r5rbE-1.js.map +1 -0
- package/build/server/chunks/chunks/EventScatter.js-D3Jaw_Cw.js +7308 -0
- package/build/server/chunks/chunks/EventScatter.js-D3Jaw_Cw.js.map +1 -0
- package/build/server/chunks/chunks/Labels.js-DpagpV6d.js +497 -0
- package/build/server/chunks/chunks/Labels.js-DpagpV6d.js.map +1 -0
- package/build/server/chunks/chunks/Legend.js-BOpwpe5s.js +343 -0
- package/build/server/chunks/chunks/Legend.js-BOpwpe5s.js.map +1 -0
- package/build/server/chunks/chunks/Line.html.js-BM5tR11G.js +423 -0
- package/build/server/chunks/chunks/Line.html.js-BM5tR11G.js.map +1 -0
- package/build/server/chunks/chunks/Link.canvas.js-P8ySzEAw.js +756 -0
- package/build/server/chunks/chunks/Link.canvas.js-P8ySzEAw.js.map +1 -0
- package/build/server/chunks/chunks/Path.canvas.js-DNAtnEBt.js +1259 -0
- package/build/server/chunks/chunks/Path.canvas.js-DNAtnEBt.js.map +1 -0
- package/build/server/chunks/chunks/Points.html.js-CLbHD6OP.js +228 -0
- package/build/server/chunks/chunks/Points.html.js-CLbHD6OP.js.map +1 -0
- package/build/server/chunks/chunks/Points.js-e6g0Re6H.js +14 -0
- package/build/server/chunks/chunks/Points.js-e6g0Re6H.js.map +1 -0
- package/build/server/chunks/chunks/Points2.js-BMGdkzTE.js +25 -0
- package/build/server/chunks/chunks/Points2.js-BMGdkzTE.js.map +1 -0
- package/build/server/chunks/chunks/Rect.html.js-Q3UvRwNn.js +547 -0
- package/build/server/chunks/chunks/Rect.html.js-Q3UvRwNn.js.map +1 -0
- package/build/server/chunks/chunks/Spline.js-BuZPz2PJ.js +516 -0
- package/build/server/chunks/chunks/Spline.js-BuZPz2PJ.js.map +1 -0
- package/build/server/chunks/chunks/Text.html.js-C8xO5Nz6.js +847 -0
- package/build/server/chunks/chunks/Text.html.js-C8xO5Nz6.js.map +1 -0
- package/build/server/chunks/chunks/TooltipItem.js-T0cKLjLu.js +314 -0
- package/build/server/chunks/chunks/TooltipItem.js-T0cKLjLu.js.map +1 -0
- package/build/server/chunks/chunks/Voronoi.js-DevTsXQE.js +2052 -0
- package/build/server/chunks/chunks/Voronoi.js-DevTsXQE.js.map +1 -0
- package/build/server/chunks/chunks/archive.js-C2D-5edp.js +823 -0
- package/build/server/chunks/chunks/archive.js-C2D-5edp.js.map +1 -0
- package/build/server/chunks/chunks/array.js-CBp0H4t3.js +8 -0
- package/build/server/chunks/chunks/array.js-CBp0H4t3.js.map +1 -0
- package/build/server/chunks/chunks/attributes.js-BVWo7wbg.js +25 -0
- package/build/server/chunks/chunks/attributes.js-BVWo7wbg.js.map +1 -0
- package/build/server/chunks/chunks/{env2.js-DXveJt1Y.js → aws.js-DkPqAybc.js} +2 -59
- package/build/server/chunks/chunks/aws.js-DkPqAybc.js.map +1 -0
- package/build/server/chunks/chunks/band.js-C7izXphE.js +155 -0
- package/build/server/chunks/chunks/band.js-C7izXphE.js.map +1 -0
- package/build/server/chunks/chunks/centroid.js-BnGN3LH0.js +1356 -0
- package/build/server/chunks/chunks/centroid.js-BnGN3LH0.js.map +1 -0
- package/build/server/chunks/chunks/common.js-BWQB8Die.js +5760 -0
- package/build/server/chunks/chunks/common.js-BWQB8Die.js.map +1 -0
- package/build/server/chunks/chunks/env2.js-CcIskxOZ.js +60 -0
- package/build/server/chunks/chunks/env2.js-CcIskxOZ.js.map +1 -0
- package/build/server/chunks/chunks/{exports.js-CvFPPbaH.js → exports.js-ClPPfcHj.js} +6 -1
- package/build/server/chunks/chunks/exports.js-ClPPfcHj.js.map +1 -0
- package/build/server/chunks/chunks/group.js-DBFzWAox.js +30 -0
- package/build/server/chunks/chunks/group.js-DBFzWAox.js.map +1 -0
- package/build/server/chunks/chunks/index-server.js-DNLOKvM0.js +248 -0
- package/build/server/chunks/chunks/index-server.js-DNLOKvM0.js.map +1 -0
- package/build/server/chunks/chunks/index-server2.js-l0sNRNKZ.js +2 -0
- package/build/server/chunks/chunks/index-server2.js-l0sNRNKZ.js.map +1 -0
- package/build/server/chunks/chunks/{internal2.js-DZ8ZzzjN.js → internal.js-DZ8ZzzjN.js} +1 -1
- package/build/server/chunks/chunks/internal.js-DZ8ZzzjN.js.map +1 -0
- package/build/server/chunks/chunks/internal2.js-BPo5dcDi.js +188 -0
- package/build/server/chunks/chunks/internal2.js-BPo5dcDi.js.map +1 -0
- package/build/server/chunks/chunks/key.svelte.js-CWIvXc6L.js +695 -0
- package/build/server/chunks/chunks/key.svelte.js-CWIvXc6L.js.map +1 -0
- package/build/server/chunks/chunks/{internal.js-Cc3oBhL-.js → legacy-client.js-DJmDw3Zx.js} +13 -439
- package/build/server/chunks/chunks/legacy-client.js-DJmDw3Zx.js.map +1 -0
- package/build/server/chunks/chunks/level-filter.js-B3o2hyWD.js +313 -0
- package/build/server/chunks/chunks/level-filter.js-B3o2hyWD.js.map +1 -0
- package/build/server/chunks/chunks/lineRadial.js-Dm0GMqo3.js +126 -0
- package/build/server/chunks/chunks/lineRadial.js-Dm0GMqo3.js.map +1 -0
- package/build/server/chunks/chunks/linear.js-CTOKrHn9.js +1046 -0
- package/build/server/chunks/chunks/linear.js-CTOKrHn9.js.map +1 -0
- package/build/server/chunks/chunks/log-buffer.js-BI_gQOls.js +587 -0
- package/build/server/chunks/chunks/log-buffer.js-BI_gQOls.js.map +1 -0
- package/build/server/chunks/chunks/math.js-BqujbaBs.js +50 -0
- package/build/server/chunks/chunks/math.js-BqujbaBs.js.map +1 -0
- package/build/server/chunks/chunks/max.js-CuS0BpRI.js +11 -0
- package/build/server/chunks/chunks/max.js-CuS0BpRI.js.map +1 -0
- package/build/server/chunks/chunks/min.js-DZWt5tPl.js +11 -0
- package/build/server/chunks/chunks/min.js-DZWt5tPl.js.map +1 -0
- package/build/server/chunks/chunks/motion.svelte.js-DCHSFMu-.js +677 -0
- package/build/server/chunks/chunks/motion.svelte.js-DCHSFMu-.js.map +1 -0
- package/build/server/chunks/chunks/occlusion.js-DJ-AdK9u.js +33 -0
- package/build/server/chunks/chunks/occlusion.js-DJ-AdK9u.js.map +1 -0
- package/build/server/chunks/chunks/path.js-BizMrlzb.js +488 -0
- package/build/server/chunks/chunks/path.js-BizMrlzb.js.map +1 -0
- package/build/server/chunks/chunks/path2.js-pquT2Kg6.js +27 -0
- package/build/server/chunks/chunks/path2.js-pquT2Kg6.js.map +1 -0
- package/build/server/chunks/chunks/pointRadial.js-Deeu1LXx.js +7 -0
- package/build/server/chunks/chunks/pointRadial.js-Deeu1LXx.js.map +1 -0
- package/build/server/chunks/chunks/rect.svelte.js-zeS7FWjB.js +187 -0
- package/build/server/chunks/chunks/rect.svelte.js-zeS7FWjB.js.map +1 -0
- package/build/server/chunks/chunks/scales.svelte.js-D_1popPi.js +276 -0
- package/build/server/chunks/chunks/scales.svelte.js-D_1popPi.js.map +1 -0
- package/build/server/chunks/chunks/series-buckets.js-BjigySff.js +290 -0
- package/build/server/chunks/chunks/series-buckets.js-BjigySff.js.map +1 -0
- package/build/server/chunks/chunks/{server.js-ByNB8uti.js → server.js-CAEBuMIH.js} +167 -2
- package/build/server/chunks/chunks/server.js-CAEBuMIH.js.map +1 -0
- package/build/server/chunks/chunks/source.js-DZEVvuho.js +21 -0
- package/build/server/chunks/chunks/source.js-DZEVvuho.js.map +1 -0
- package/build/server/chunks/chunks/{state.js-9DxsWxU-.js → state.js-DgkwfYFo.js} +5 -4
- package/build/server/chunks/chunks/state.js-DgkwfYFo.js.map +1 -0
- package/build/server/chunks/chunks/types.js-x3GyGPOz.js +11 -0
- package/build/server/chunks/chunks/types.js-x3GyGPOz.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/archive/_server.ts.js-83Ch7djd.js +39 -0
- package/build/server/chunks/entries/endpoints/api/archive/_server.ts.js-83Ch7djd.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/health/{_server.ts.js-BOjix7GN.js → _server.ts.js-VctnBhIJ.js} +5 -4
- package/build/server/chunks/entries/endpoints/api/health/_server.ts.js-VctnBhIJ.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/identity/{_server.ts.js-g13uvmrX.js → _server.ts.js-Dt540Sio.js} +4 -3
- package/build/server/chunks/entries/endpoints/api/identity/_server.ts.js-Dt540Sio.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/log-groups/{_server.ts.js-CmHRxdZK.js → _server.ts.js-drLgUdvB.js} +57 -8
- package/build/server/chunks/entries/endpoints/api/log-groups/_server.ts.js-drLgUdvB.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/regions/{_server.ts.js-BgUwioxp.js → _server.ts.js-CBqfN6Pg.js} +5 -4
- package/build/server/chunks/entries/endpoints/api/regions/_server.ts.js-CBqfN6Pg.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/series/_server.ts.js-DOvBhEDU.js +225 -0
- package/build/server/chunks/entries/endpoints/api/series/_server.ts.js-DOvBhEDU.js.map +1 -0
- package/build/server/chunks/entries/endpoints/api/stream/{_server.ts.js-CIYo4C-0.js → _server.ts.js-BWRHawY8.js} +324 -223
- package/build/server/chunks/entries/endpoints/api/stream/_server.ts.js-BWRHawY8.js.map +1 -0
- package/build/server/chunks/entries/fallbacks/{error.svelte.js-CFonuZkV.js → error.svelte.js-CsCL5PMW.js} +7 -5
- package/build/server/chunks/entries/fallbacks/{error.svelte.js-CFonuZkV.js.map → error.svelte.js-CsCL5PMW.js.map} +1 -1
- package/build/server/chunks/entries/pages/{_layout.svelte.js-Dzluuq12.js → _layout.svelte.js-CBQpEToy.js} +2 -2
- package/build/server/chunks/entries/pages/{_layout.svelte.js-Dzluuq12.js.map → _layout.svelte.js-CBQpEToy.js.map} +1 -1
- package/build/server/chunks/entries/pages/_page.svelte.js-qqtU0apo.js +1641 -0
- package/build/server/chunks/entries/pages/_page.svelte.js-qqtU0apo.js.map +1 -0
- package/build/server/chunks/{handler-DS32NMsJ.js → handler-DlfTB05m.js} +3 -3
- package/build/server/chunks/{handler-DS32NMsJ.js.map → handler-DlfTB05m.js.map} +1 -1
- package/build/server/chunks/{index.js-HHu-yOEK.js → index.js-BMrd9hfO.js} +6 -5
- package/build/server/chunks/index.js-BMrd9hfO.js.map +1 -0
- package/build/server/chunks/{manifest.js-BUrA7Zyr.js → manifest.js-D1kCuVe5.js} +24 -10
- package/build/server/chunks/manifest.js-D1kCuVe5.js.map +1 -0
- package/build/server/chunks/nodes/0.js-CNFDDPj5.js +9 -0
- package/build/server/chunks/nodes/{0.js-gSRmrAt_.js.map → 0.js-CNFDDPj5.js.map} +1 -1
- package/build/server/chunks/nodes/1.js-B5FPLYnf.js +9 -0
- package/build/server/chunks/nodes/{1.js-D68K7Ad_.js.map → 1.js-B5FPLYnf.js.map} +1 -1
- package/build/server/chunks/nodes/2.js-BEuxzw6b.js +9 -0
- package/build/server/chunks/nodes/2.js-BEuxzw6b.js.map +1 -0
- package/dist/cli/completions.js +6 -0
- package/dist/cli/index.js +131 -88
- package/dist/cli/options.js +28 -2
- package/dist/cli/preflight.js +30 -0
- package/dist/cli/ui.js +18 -3
- package/dist/lib/cli/aws.js +12 -4
- package/dist/lib/log-buffer.js +443 -0
- package/dist/lib/log-format.js +185 -0
- package/dist/lib/types.js +0 -4
- package/package.json +6 -2
- package/build/client/_app/immutable/assets/0.BPmQoXaO.css +0 -2
- package/build/client/_app/immutable/assets/0.BPmQoXaO.css.br +0 -0
- package/build/client/_app/immutable/assets/0.BPmQoXaO.css.gz +0 -0
- package/build/client/_app/immutable/chunks/Bw_9uNRP.js.br +0 -0
- package/build/client/_app/immutable/chunks/Bw_9uNRP.js.gz +0 -0
- package/build/client/_app/immutable/chunks/De_x7GQi.js +0 -1
- package/build/client/_app/immutable/chunks/De_x7GQi.js.br +0 -0
- package/build/client/_app/immutable/chunks/De_x7GQi.js.gz +0 -0
- package/build/client/_app/immutable/chunks/ZAD2y5xJ.js +0 -3
- package/build/client/_app/immutable/chunks/ZAD2y5xJ.js.br +0 -0
- package/build/client/_app/immutable/chunks/ZAD2y5xJ.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.Be166AxG.js +0 -2
- package/build/client/_app/immutable/entry/app.Be166AxG.js.br +0 -0
- package/build/client/_app/immutable/entry/app.Be166AxG.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.clEDpQL9.js +0 -1
- package/build/client/_app/immutable/entry/start.clEDpQL9.js.br +0 -0
- package/build/client/_app/immutable/entry/start.clEDpQL9.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.BJ6Zdc8H.js +0 -1
- package/build/client/_app/immutable/nodes/0.BJ6Zdc8H.js.br +0 -4
- package/build/client/_app/immutable/nodes/0.BJ6Zdc8H.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.xMNWuLLz.js +0 -1
- package/build/client/_app/immutable/nodes/1.xMNWuLLz.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.xMNWuLLz.js.gz +0 -0
- package/build/client/_app/immutable/nodes/2.CaxDXwmh.js +0 -5
- package/build/client/_app/immutable/nodes/2.CaxDXwmh.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.CaxDXwmh.js.gz +0 -0
- package/build/server/chunks/chunks/env2.js-DXveJt1Y.js.map +0 -1
- package/build/server/chunks/chunks/exports.js-CvFPPbaH.js.map +0 -1
- package/build/server/chunks/chunks/internal.js-Cc3oBhL-.js.map +0 -1
- package/build/server/chunks/chunks/internal2.js-DZ8ZzzjN.js.map +0 -1
- package/build/server/chunks/chunks/server.js-ByNB8uti.js.map +0 -1
- package/build/server/chunks/chunks/state.js-9DxsWxU-.js.map +0 -1
- package/build/server/chunks/entries/endpoints/api/health/_server.ts.js-BOjix7GN.js.map +0 -1
- package/build/server/chunks/entries/endpoints/api/identity/_server.ts.js-g13uvmrX.js.map +0 -1
- package/build/server/chunks/entries/endpoints/api/log-groups/_server.ts.js-CmHRxdZK.js.map +0 -1
- package/build/server/chunks/entries/endpoints/api/regions/_server.ts.js-BgUwioxp.js.map +0 -1
- package/build/server/chunks/entries/endpoints/api/stream/_server.ts.js-CIYo4C-0.js.map +0 -1
- package/build/server/chunks/entries/pages/_page.svelte.js-CpwYbxbM.js +0 -1291
- package/build/server/chunks/entries/pages/_page.svelte.js-CpwYbxbM.js.map +0 -1
- package/build/server/chunks/index.js-HHu-yOEK.js.map +0 -1
- package/build/server/chunks/manifest.js-BUrA7Zyr.js.map +0 -1
- package/build/server/chunks/nodes/0.js-gSRmrAt_.js +0 -9
- package/build/server/chunks/nodes/1.js-D68K7Ad_.js +0 -9
- package/build/server/chunks/nodes/2.js-ChYsaaoC.js +0 -9
- package/build/server/chunks/nodes/2.js-ChYsaaoC.js.map +0 -1
|
@@ -0,0 +1,823 @@
|
|
|
1
|
+
import { d as detectRequestId, a as detectLevelWithSource, i as isLogLevel, L as LEVEL_RANK } from './log-buffer.js-BI_gQOls.js';
|
|
2
|
+
import { mkdirSync, statSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join, dirname } from 'node:path';
|
|
5
|
+
import { setTimeout } from 'node:timers/promises';
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
|
|
8
|
+
//#region src/lib/server/archive-sql.ts
|
|
9
|
+
/**
|
|
10
|
+
* SQL and row mapping for the local DuckDB archive.
|
|
11
|
+
*
|
|
12
|
+
* DuckDB is a native dependency and is loaded lazily by `archive.ts`; everything
|
|
13
|
+
* in this file is plain text and pure functions so the archive can be unit
|
|
14
|
+
* tested without the driver. Column order matters: {@link ARCHIVE_INSERT_COLUMNS}
|
|
15
|
+
* is the contract with {@link toArchiveParams}, and the `SELECT` in
|
|
16
|
+
* {@link buildPageQuery} uses the same names as {@link rowsToPage}.
|
|
17
|
+
*
|
|
18
|
+
* `level` and `request_id` are both derived from the message on the way in, and
|
|
19
|
+
* both may be NULL in an archive written by an older build. NULL does not mean
|
|
20
|
+
* the same thing for the two columns: for `level` it is a verdict ("this line
|
|
21
|
+
* carries no level"), while for `request_id` it means "not looked at yet", since
|
|
22
|
+
* the client can still detect an id in the message it renders. {@link rowsToPage}
|
|
23
|
+
* and the request-id backfill below are written from that difference.
|
|
24
|
+
*/
|
|
25
|
+
/**
|
|
26
|
+
* Statements that bring a database file up to the current schema.
|
|
27
|
+
*
|
|
28
|
+
* Every statement is idempotent so an existing archive can be opened by a newer
|
|
29
|
+
* build without a migration step. `seq` orders events by arrival, which keeps
|
|
30
|
+
* paging stable when many events share a millisecond timestamp.
|
|
31
|
+
*/
|
|
32
|
+
var ARCHIVE_SCHEMA = [
|
|
33
|
+
`CREATE SEQUENCE IF NOT EXISTS log_events_seq`,
|
|
34
|
+
`CREATE TABLE IF NOT EXISTS log_events (
|
|
35
|
+
region VARCHAR NOT NULL,
|
|
36
|
+
log_group VARCHAR NOT NULL,
|
|
37
|
+
log_stream VARCHAR,
|
|
38
|
+
event_key VARCHAR NOT NULL,
|
|
39
|
+
event_id VARCHAR,
|
|
40
|
+
timestamp_ms BIGINT NOT NULL,
|
|
41
|
+
ingestion_time_ms BIGINT,
|
|
42
|
+
message VARCHAR NOT NULL,
|
|
43
|
+
level VARCHAR,
|
|
44
|
+
level_source VARCHAR,
|
|
45
|
+
request_id VARCHAR,
|
|
46
|
+
seq BIGINT NOT NULL DEFAULT nextval('log_events_seq'),
|
|
47
|
+
archived_at TIMESTAMP NOT NULL DEFAULT now()
|
|
48
|
+
)`,
|
|
49
|
+
`ALTER TABLE log_events ADD COLUMN IF NOT EXISTS level VARCHAR`,
|
|
50
|
+
`ALTER TABLE log_events ADD COLUMN IF NOT EXISTS level_source VARCHAR`,
|
|
51
|
+
`ALTER TABLE log_events ADD COLUMN IF NOT EXISTS request_id VARCHAR`,
|
|
52
|
+
`CREATE TABLE IF NOT EXISTS archive_meta (key VARCHAR PRIMARY KEY, value VARCHAR)`,
|
|
53
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS log_events_unique ON log_events (region, log_group, event_key)`,
|
|
54
|
+
`CREATE INDEX IF NOT EXISTS log_events_time ON log_events (region, log_group, timestamp_ms)`
|
|
55
|
+
];
|
|
56
|
+
/** Columns written by {@link buildInsertSql}, in binding order. */
|
|
57
|
+
var ARCHIVE_INSERT_COLUMNS = [
|
|
58
|
+
"region",
|
|
59
|
+
"log_group",
|
|
60
|
+
"log_stream",
|
|
61
|
+
"event_key",
|
|
62
|
+
"event_id",
|
|
63
|
+
"timestamp_ms",
|
|
64
|
+
"ingestion_time_ms",
|
|
65
|
+
"message",
|
|
66
|
+
"level",
|
|
67
|
+
"level_source",
|
|
68
|
+
"request_id"
|
|
69
|
+
];
|
|
70
|
+
/** Escape character used with `LIKE`/`ILIKE` so user input cannot inject wildcards. */
|
|
71
|
+
var LIKE_ESCAPE = "\\";
|
|
72
|
+
/**
|
|
73
|
+
* Stable identity of an event inside one log group.
|
|
74
|
+
*
|
|
75
|
+
* CloudWatch event ids are unique per log group and are reused as the key;
|
|
76
|
+
* events without an id (floci and LocalStack omit them) get a deterministic
|
|
77
|
+
* hash of timestamp, stream and message, so re-scanning the same window is
|
|
78
|
+
* idempotent instead of duplicating rows.
|
|
79
|
+
*/
|
|
80
|
+
function archiveEventKey(event) {
|
|
81
|
+
if (event.id !== null && event.id.length > 0) return event.id;
|
|
82
|
+
return `h:${createHash("sha256").update(`${event.timestamp}\u0000${event.streamName ?? ""}\u0000${event.message}`).digest("hex").slice(0, 32)}`;
|
|
83
|
+
}
|
|
84
|
+
/** Renders `count` placeholder rows, one group of columns per row. */
|
|
85
|
+
function placeholderRows(count) {
|
|
86
|
+
const row = `(${ARCHIVE_INSERT_COLUMNS.map(() => "?").join(", ")})`;
|
|
87
|
+
return Array.from({ length: count }, () => row).join(", ");
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Builds a multi-row `INSERT OR IGNORE`, so events already archived are skipped
|
|
91
|
+
* by the unique index instead of raising.
|
|
92
|
+
*/
|
|
93
|
+
function buildInsertSql(rowCount) {
|
|
94
|
+
if (!Number.isInteger(rowCount) || rowCount < 1) throw new RangeError(`rowCount must be a positive integer, received ${String(rowCount)}`);
|
|
95
|
+
return `INSERT OR IGNORE INTO log_events (${ARCHIVE_INSERT_COLUMNS.join(", ")}) VALUES ${placeholderRows(rowCount)}`;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Flattens events into the parameter list {@link buildInsertSql} expects.
|
|
99
|
+
*
|
|
100
|
+
* The request id is detected here, exactly like the level, so the archive holds
|
|
101
|
+
* what a reader of the line would see rather than whatever a writer claimed.
|
|
102
|
+
*/
|
|
103
|
+
function toArchiveParams(region, logGroup, events) {
|
|
104
|
+
const params = [];
|
|
105
|
+
for (const event of events) {
|
|
106
|
+
const detected = detectLevelWithSource(event.message);
|
|
107
|
+
params.push(region, logGroup, event.streamName ?? null, archiveEventKey(event), event.id ?? null, BigInt(Math.round(event.timestamp)), event.ingestionTime === void 0 ? null : BigInt(Math.round(event.ingestionTime)), event.message, detected.level, detected.source, detectRequestId(event.message));
|
|
108
|
+
}
|
|
109
|
+
return params;
|
|
110
|
+
}
|
|
111
|
+
/** Column list shared by {@link buildPageQuery} and {@link rowsToPage}. */
|
|
112
|
+
var PAGE_COLUMNS = `region, log_group, log_stream, event_key, event_id, timestamp_ms, ingestion_time_ms, message, level, level_source, request_id, seq`;
|
|
113
|
+
/** Escapes `LIKE` metacharacters so a search term is matched literally. */
|
|
114
|
+
function escapeLike(value) {
|
|
115
|
+
return value.replace(/[\\%_]/g, (match) => `${LIKE_ESCAPE}${match}`);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Builds one keyset page of archived events.
|
|
119
|
+
*
|
|
120
|
+
* Ordering is `(timestamp_ms, seq)`, and the cursor compares the same tuple, so
|
|
121
|
+
* pages never repeat or skip a row even when thousands of events share a
|
|
122
|
+
* timestamp.
|
|
123
|
+
*/
|
|
124
|
+
function buildPageQuery(request) {
|
|
125
|
+
const limit = Math.max(1, Math.round(request.limit));
|
|
126
|
+
const groups = request.logGroups.length > 0 ? request.logGroups : [""];
|
|
127
|
+
const where = [
|
|
128
|
+
"region = ?",
|
|
129
|
+
`log_group IN (${groups.map(() => "?").join(", ")})`,
|
|
130
|
+
"timestamp_ms >= ?",
|
|
131
|
+
"timestamp_ms <= ?"
|
|
132
|
+
];
|
|
133
|
+
const params = [
|
|
134
|
+
request.region,
|
|
135
|
+
...groups,
|
|
136
|
+
BigInt(Math.round(request.startTime)),
|
|
137
|
+
BigInt(Math.round(request.endTime))
|
|
138
|
+
];
|
|
139
|
+
const search = request.search?.trim() ?? "";
|
|
140
|
+
if (search.length > 0) {
|
|
141
|
+
where.push(`message ILIKE ? ESCAPE '${LIKE_ESCAPE}'`);
|
|
142
|
+
params.push(`%${escapeLike(search)}%`);
|
|
143
|
+
}
|
|
144
|
+
const prefix = request.streamPrefix?.trim() ?? "";
|
|
145
|
+
if (prefix.length > 0) {
|
|
146
|
+
where.push(`log_stream LIKE ? ESCAPE '${LIKE_ESCAPE}'`);
|
|
147
|
+
params.push(`${escapeLike(prefix)}%`);
|
|
148
|
+
}
|
|
149
|
+
const levels = request.levels ?? [];
|
|
150
|
+
if (levels.length > 0) {
|
|
151
|
+
where.push(`level IN (${levels.map(() => "?").join(", ")})`);
|
|
152
|
+
params.push(...levels);
|
|
153
|
+
}
|
|
154
|
+
const after = request.after ?? null;
|
|
155
|
+
if (after !== null) {
|
|
156
|
+
where.push("(timestamp_ms > ? OR (timestamp_ms = ? AND seq > ?))");
|
|
157
|
+
params.push(BigInt(Math.round(after.timestamp)), BigInt(Math.round(after.timestamp)), BigInt(Math.round(after.seq)));
|
|
158
|
+
}
|
|
159
|
+
params.push(BigInt(limit));
|
|
160
|
+
return {
|
|
161
|
+
sql: `SELECT ${PAGE_COLUMNS} FROM log_events WHERE ${where.join(" AND ")} ORDER BY timestamp_ms, seq LIMIT ?`,
|
|
162
|
+
params
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/** Converts a DuckDB value (which may be a bigint) into a JS number. */
|
|
166
|
+
function toNumber(value) {
|
|
167
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
168
|
+
if (typeof value === "bigint") return Number(value);
|
|
169
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
170
|
+
const parsed = Number(value);
|
|
171
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
172
|
+
}
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
/** Converts a nullable DuckDB string column into `string` or `null`. */
|
|
176
|
+
function toNullableString(value) {
|
|
177
|
+
if (typeof value === "string") return value.length > 0 ? value : null;
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
/** Reads one column from a row object, tolerating case differences. */
|
|
181
|
+
function pick(row, column) {
|
|
182
|
+
if (column in row) return row[column];
|
|
183
|
+
const upper = column.toUpperCase();
|
|
184
|
+
if (upper in row) return row[upper];
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Maps query rows onto wire events plus the cursor of the last row.
|
|
188
|
+
*
|
|
189
|
+
* Rows without a usable timestamp are dropped rather than rendered as 1970.
|
|
190
|
+
*/
|
|
191
|
+
function rowsToPage(rows) {
|
|
192
|
+
const events = [];
|
|
193
|
+
let last = null;
|
|
194
|
+
for (const row of rows) {
|
|
195
|
+
const timestamp = toNumber(pick(row, "timestamp_ms"));
|
|
196
|
+
const seq = toNumber(pick(row, "seq"));
|
|
197
|
+
if (timestamp === null || seq === null) continue;
|
|
198
|
+
const rawLevel = pick(row, "level");
|
|
199
|
+
const group = toNullableString(pick(row, "log_group"));
|
|
200
|
+
const event = {
|
|
201
|
+
id: toNullableString(pick(row, "event_key")),
|
|
202
|
+
timestamp,
|
|
203
|
+
message: typeof pick(row, "message") === "string" ? pick(row, "message") : "",
|
|
204
|
+
level: isLogLevel(rawLevel) ? rawLevel : null
|
|
205
|
+
};
|
|
206
|
+
if (group !== null) event.group = group;
|
|
207
|
+
const requestId = toNullableString(pick(row, "request_id"));
|
|
208
|
+
if (requestId !== null) event.requestId = requestId;
|
|
209
|
+
const streamName = toNullableString(pick(row, "log_stream"));
|
|
210
|
+
if (streamName !== null) event.streamName = streamName;
|
|
211
|
+
const ingestionTime = toNumber(pick(row, "ingestion_time_ms"));
|
|
212
|
+
if (ingestionTime !== null) event.ingestionTime = ingestionTime;
|
|
213
|
+
events.push(event);
|
|
214
|
+
last = {
|
|
215
|
+
timestamp,
|
|
216
|
+
seq
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
return {
|
|
220
|
+
events,
|
|
221
|
+
last
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
/** Aggregate query behind {@link ArchiveTotals}. */
|
|
225
|
+
var ARCHIVE_TOTALS_SQL = `SELECT count(*) AS rows, count(DISTINCT log_group) AS groups, count(DISTINCT region) AS regions, min(timestamp_ms) AS oldest, max(timestamp_ms) AS newest FROM log_events`;
|
|
226
|
+
/** Log groups held by the archive, optionally limited to one region. */
|
|
227
|
+
function buildGroupsQuery(region) {
|
|
228
|
+
const where = region === null || region.length === 0 ? "" : "WHERE region = ?";
|
|
229
|
+
return {
|
|
230
|
+
sql: `SELECT region, log_group, count(*) AS events, min(timestamp_ms) AS oldest, max(timestamp_ms) AS newest FROM log_events ${where} GROUP BY region, log_group ORDER BY region, log_group`,
|
|
231
|
+
params: where === "" ? [] : [region]
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
/** Maps the single row of {@link ARCHIVE_TOTALS_SQL}. */
|
|
235
|
+
function rowToTotals(row) {
|
|
236
|
+
if (row === void 0) return {
|
|
237
|
+
rows: 0,
|
|
238
|
+
groups: 0,
|
|
239
|
+
regions: 0,
|
|
240
|
+
oldest: null,
|
|
241
|
+
newest: null
|
|
242
|
+
};
|
|
243
|
+
return {
|
|
244
|
+
rows: toNumber(pick(row, "rows")) ?? 0,
|
|
245
|
+
groups: toNumber(pick(row, "groups")) ?? 0,
|
|
246
|
+
regions: toNumber(pick(row, "regions")) ?? 0,
|
|
247
|
+
oldest: toNumber(pick(row, "oldest")),
|
|
248
|
+
newest: toNumber(pick(row, "newest"))
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
/** Maps the rows of {@link buildGroupsQuery}. */
|
|
252
|
+
function rowsToGroups(rows) {
|
|
253
|
+
const groups = [];
|
|
254
|
+
for (const row of rows) {
|
|
255
|
+
const region = pick(row, "region");
|
|
256
|
+
const logGroup = pick(row, "log_group");
|
|
257
|
+
if (typeof region !== "string" || typeof logGroup !== "string") continue;
|
|
258
|
+
groups.push({
|
|
259
|
+
region,
|
|
260
|
+
logGroup,
|
|
261
|
+
events: toNumber(pick(row, "events")) ?? 0,
|
|
262
|
+
oldest: toNumber(pick(row, "oldest")),
|
|
263
|
+
newest: toNumber(pick(row, "newest"))
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
return groups;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Row budget of one request-id backfill pass.
|
|
270
|
+
*
|
|
271
|
+
* The pass runs on every open, so it has to stay cheap. 20000 messages is a
|
|
272
|
+
* fraction of a second of regex work, and an archive with more than that scans
|
|
273
|
+
* the rest on the next open, because the watermark moves.
|
|
274
|
+
*/
|
|
275
|
+
var REQUEST_ID_BACKFILL_LIMIT = 2e4;
|
|
276
|
+
/** `archive_meta` key holding the request-id backfill watermark. */
|
|
277
|
+
var REQUEST_ID_BACKFILL_KEY = "request_id_backfill";
|
|
278
|
+
/** Highest `seq` in the archive: the ceiling of a backfill pass. */
|
|
279
|
+
var ARCHIVE_MAX_SEQ_SQL = `SELECT max(seq) AS maxSeq FROM log_events`;
|
|
280
|
+
/** Reads the single row of {@link ARCHIVE_MAX_SEQ_SQL}; `null` for an empty archive. */
|
|
281
|
+
function rowToMaxSeq(row) {
|
|
282
|
+
return row === void 0 ? null : toNumber(pick(row, "maxSeq"));
|
|
283
|
+
}
|
|
284
|
+
/** Statement that reads the backfill watermark; no row means it never ran. */
|
|
285
|
+
function buildRequestIdBackfillStateQuery() {
|
|
286
|
+
return `SELECT value FROM archive_meta WHERE key = '${REQUEST_ID_BACKFILL_KEY}'`;
|
|
287
|
+
}
|
|
288
|
+
/** Statement that stores the backfill watermark, which is the highest scanned `seq`. */
|
|
289
|
+
function buildRequestIdBackfillStateWrite() {
|
|
290
|
+
return `INSERT OR REPLACE INTO archive_meta (key, value) VALUES ('${REQUEST_ID_BACKFILL_KEY}', ?)`;
|
|
291
|
+
}
|
|
292
|
+
/** Reads the watermark row; an absent row or junk means "scan from the start". */
|
|
293
|
+
function rowToBackfillWatermark(row) {
|
|
294
|
+
const value = row === void 0 ? null : toNumber(pick(row, "value"));
|
|
295
|
+
return value === null || value < 0 ? null : value;
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Builds the scan that finds rows archived before `request_id` existed.
|
|
299
|
+
*
|
|
300
|
+
* `seq` is selected although the update does not need it: the caller stores
|
|
301
|
+
* where the pass stopped. `afterSeq` skips the range a previous pass already
|
|
302
|
+
* looked at, which is what lets a capped pass resume - a message with no request
|
|
303
|
+
* id stays NULL for ever, so without the lower bound every pass would start on
|
|
304
|
+
* the same id-less rows and never reach the rows above them.
|
|
305
|
+
*/
|
|
306
|
+
function buildRequestIdBackfillQuery(request) {
|
|
307
|
+
const limit = Math.max(1, Math.round(request.limit));
|
|
308
|
+
const scanned = request.afterSeq ?? 0;
|
|
309
|
+
const after = scanned > 0 ? " AND seq > ?" : "";
|
|
310
|
+
const params = [BigInt(Math.round(request.maxSeq))];
|
|
311
|
+
if (scanned > 0) params.push(BigInt(Math.round(scanned)));
|
|
312
|
+
params.push(BigInt(limit));
|
|
313
|
+
return {
|
|
314
|
+
sql: `SELECT event_key, log_group, region, message, seq FROM log_events WHERE seq <= ?${after} AND request_id IS NULL ORDER BY seq LIMIT ?`,
|
|
315
|
+
params
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
/** Maps the rows of {@link buildRequestIdBackfillQuery}, dropping unusable ones. */
|
|
319
|
+
function rowsToBackfillCandidates(rows) {
|
|
320
|
+
const candidates = [];
|
|
321
|
+
for (const row of rows) {
|
|
322
|
+
const seq = toNumber(pick(row, "seq"));
|
|
323
|
+
const region = pick(row, "region");
|
|
324
|
+
const logGroup = pick(row, "log_group");
|
|
325
|
+
const eventKey = pick(row, "event_key");
|
|
326
|
+
const message = pick(row, "message");
|
|
327
|
+
if (seq === null || typeof region !== "string" || typeof logGroup !== "string" || typeof eventKey !== "string" || typeof message !== "string") continue;
|
|
328
|
+
candidates.push({
|
|
329
|
+
seq,
|
|
330
|
+
region,
|
|
331
|
+
logGroup,
|
|
332
|
+
eventKey,
|
|
333
|
+
message
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
return candidates;
|
|
337
|
+
}
|
|
338
|
+
/** Highest `seq` of a scanned set, or `null` when nothing was scanned. */
|
|
339
|
+
function maxSeqOf(candidates) {
|
|
340
|
+
let highest = null;
|
|
341
|
+
for (const candidate of candidates) if (highest === null || candidate.seq > highest) highest = candidate.seq;
|
|
342
|
+
return highest;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Detects the request id of every candidate.
|
|
346
|
+
*
|
|
347
|
+
* A message that yields no id is dropped rather than written as an empty string:
|
|
348
|
+
* it is a finished row, not a failure, and the watermark moves past it so it is
|
|
349
|
+
* never looked at again.
|
|
350
|
+
*/
|
|
351
|
+
function planRequestIdBackfill(candidates) {
|
|
352
|
+
const updates = [];
|
|
353
|
+
for (const candidate of candidates) {
|
|
354
|
+
const requestId = detectRequestId(candidate.message);
|
|
355
|
+
if (requestId === null) continue;
|
|
356
|
+
updates.push({
|
|
357
|
+
region: candidate.region,
|
|
358
|
+
logGroup: candidate.logGroup,
|
|
359
|
+
eventKey: candidate.eventKey,
|
|
360
|
+
requestId
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
return updates;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Builds one `UPDATE ... FROM (VALUES ...)` that writes many ids at once.
|
|
367
|
+
*
|
|
368
|
+
* One statement per chunk instead of one per row keeps a 20000-row pass down to
|
|
369
|
+
* a handful of statements, and the predicate is an exact row match on the unique
|
|
370
|
+
* index (`region`, `log_group`, `event_key`).
|
|
371
|
+
*/
|
|
372
|
+
function buildRequestIdBackfillUpdate(rowCount) {
|
|
373
|
+
if (!Number.isInteger(rowCount) || rowCount < 1) throw new RangeError(`rowCount must be a positive integer, received ${String(rowCount)}`);
|
|
374
|
+
return `UPDATE log_events SET request_id = v.request_id FROM (VALUES ${Array.from({ length: rowCount }, () => "(?, ?, ?, ?)").join(", ")}) AS v(region, log_group, event_key, request_id) WHERE log_events.region = v.region AND log_events.log_group = v.log_group AND log_events.event_key = v.event_key`;
|
|
375
|
+
}
|
|
376
|
+
/** Flattens updates into the parameter list {@link buildRequestIdBackfillUpdate} expects. */
|
|
377
|
+
function toRequestIdBackfillParams(updates) {
|
|
378
|
+
const params = [];
|
|
379
|
+
for (const update of updates) params.push(update.region, update.logGroup, update.eventKey, update.requestId);
|
|
380
|
+
return params;
|
|
381
|
+
}
|
|
382
|
+
/** Levels and their rank, highest first; `unknown` is left to the `ELSE` branch. */
|
|
383
|
+
function rankArms() {
|
|
384
|
+
return Object.entries(LEVEL_RANK).filter(([level]) => level !== "unknown");
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* SQL `CASE` that turns a level into its severity rank.
|
|
388
|
+
*
|
|
389
|
+
* Generated from {@link LEVEL_RANK} so the ranks have one source of truth: the
|
|
390
|
+
* statement and the UI cannot disagree about which level is worse.
|
|
391
|
+
*/
|
|
392
|
+
function levelRankCase(expression) {
|
|
393
|
+
return `CASE ${expression} ${rankArms().map(([level, rank]) => `WHEN '${level}' THEN ${rank}`).join(" ")} ELSE ${LEVEL_RANK.unknown} END`;
|
|
394
|
+
}
|
|
395
|
+
/** SQL `CASE` that turns a severity rank back into a level name. */
|
|
396
|
+
function levelNameCase(expression) {
|
|
397
|
+
return `CASE ${expression} ${rankArms().map(([level, rank]) => `WHEN ${rank} THEN '${level}'`).join(" ")} ELSE 'unknown' END`;
|
|
398
|
+
}
|
|
399
|
+
/** The `WHERE` clause both series forms share: one region, one window, one or more groups. */
|
|
400
|
+
function seriesScope(request, groups) {
|
|
401
|
+
return {
|
|
402
|
+
where: [
|
|
403
|
+
"region = ?",
|
|
404
|
+
`log_group IN (${groups.map(() => "?").join(", ")})`,
|
|
405
|
+
"timestamp_ms >= ?",
|
|
406
|
+
"timestamp_ms <= ?"
|
|
407
|
+
],
|
|
408
|
+
params: [
|
|
409
|
+
request.region,
|
|
410
|
+
...groups,
|
|
411
|
+
BigInt(Math.round(request.startTime)),
|
|
412
|
+
BigInt(Math.round(request.endTime))
|
|
413
|
+
]
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Builds the bucketed counts behind the chart.
|
|
418
|
+
*
|
|
419
|
+
* One statement returns every series. The bucket is plain integer arithmetic on
|
|
420
|
+
* `timestamp_ms`, so it cannot drift with a time zone; NULL levels are reported
|
|
421
|
+
* as `unknown` rather than dropped; and a level filter matches only rows that
|
|
422
|
+
* carry one of the requested levels.
|
|
423
|
+
*
|
|
424
|
+
* `by: 'request'` selects the request form below; `by: 'event'` (the default)
|
|
425
|
+
* keeps the event form unchanged.
|
|
426
|
+
*/
|
|
427
|
+
function buildSeriesQuery(request) {
|
|
428
|
+
const bucketMs = Math.max(1, Math.round(request.bucketMs));
|
|
429
|
+
const scope = seriesScope(request, request.logGroups.length > 0 ? request.logGroups : [""]);
|
|
430
|
+
if (request.by === "request") return buildRequestSeriesQuery(request, bucketMs, scope.where, scope.params);
|
|
431
|
+
const params = [
|
|
432
|
+
BigInt(bucketMs),
|
|
433
|
+
BigInt(bucketMs),
|
|
434
|
+
...scope.params
|
|
435
|
+
];
|
|
436
|
+
const where = scope.where.slice();
|
|
437
|
+
const levels = request.levels ?? [];
|
|
438
|
+
if (levels.length > 0) {
|
|
439
|
+
where.push(`level IN (${levels.map(() => "?").join(", ")})`);
|
|
440
|
+
params.push(...levels);
|
|
441
|
+
}
|
|
442
|
+
return {
|
|
443
|
+
sql: `SELECT CAST(floor(timestamp_ms / ?) * ? AS BIGINT) AS bucket, log_group, coalesce(level, 'unknown') AS level, count(*) AS events FROM log_events WHERE ${where.join(" AND ")} GROUP BY bucket, log_group, level ORDER BY bucket, log_group, level`,
|
|
444
|
+
params
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Builds the request form: one mark per request instead of one per line.
|
|
449
|
+
*
|
|
450
|
+
* `coalesce(request_id, event_key)` makes a line with no request id a request of
|
|
451
|
+
* its own, so no line is dropped. A request is placed at its FIRST line
|
|
452
|
+
* (`min(timestamp_ms)`) and counted once, and it takes the WORST level of its
|
|
453
|
+
* lines (`max` of the ranks). A level filter selects on that same worst level in
|
|
454
|
+
* the `HAVING`: asking for errors keeps every request that logged one, whatever
|
|
455
|
+
* else it logged, and drops the requests that stayed below it.
|
|
456
|
+
*
|
|
457
|
+
* BIND ORDER: DuckDB binds `?` positionally in statement text order, and the CTE
|
|
458
|
+
* comes first, so the parameters are the scope ones, then one rank per requested
|
|
459
|
+
* level (the RANKS, not the level names), and the bucket width LAST - the
|
|
460
|
+
* opposite of the event form above.
|
|
461
|
+
*/
|
|
462
|
+
function buildRequestSeriesQuery(request, bucketMs, where, scopeParams) {
|
|
463
|
+
const levels = request.levels ?? [];
|
|
464
|
+
const having = levels.length > 0 ? ` HAVING level_rank IN (${levels.map(() => "?").join(", ")})` : "";
|
|
465
|
+
const params = [
|
|
466
|
+
...scopeParams,
|
|
467
|
+
...levels.map((level) => LEVEL_RANK[level]),
|
|
468
|
+
BigInt(bucketMs),
|
|
469
|
+
BigInt(bucketMs)
|
|
470
|
+
];
|
|
471
|
+
return {
|
|
472
|
+
sql: [
|
|
473
|
+
"WITH requests AS (",
|
|
474
|
+
`SELECT coalesce(request_id, event_key) AS request_key, log_group, min(timestamp_ms) AS start_ms, max(${levelRankCase(`coalesce(level, 'unknown')`)}) AS level_rank`,
|
|
475
|
+
`FROM log_events WHERE ${where.join(" AND ")}`,
|
|
476
|
+
`GROUP BY request_key, log_group${having})`,
|
|
477
|
+
`SELECT CAST(floor(start_ms / ?) * ? AS BIGINT) AS bucket, log_group, ${levelNameCase("level_rank")} AS level, count(*) AS events`,
|
|
478
|
+
"FROM requests GROUP BY bucket, log_group, level ORDER BY bucket, log_group, level"
|
|
479
|
+
].join(" "),
|
|
480
|
+
params
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
/** Maps the rows of {@link buildSeriesQuery} onto chart points. */
|
|
484
|
+
function rowsToSeries(rows) {
|
|
485
|
+
const points = [];
|
|
486
|
+
for (const row of rows) {
|
|
487
|
+
const t = toNumber(pick(row, "bucket"));
|
|
488
|
+
const group = pick(row, "log_group");
|
|
489
|
+
const level = pick(row, "level");
|
|
490
|
+
if (t === null || typeof group !== "string") continue;
|
|
491
|
+
points.push({
|
|
492
|
+
t,
|
|
493
|
+
group,
|
|
494
|
+
level: typeof level === "string" && level.length > 0 ? level : "unknown",
|
|
495
|
+
events: toNumber(pick(row, "events")) ?? 0
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
return points;
|
|
499
|
+
}
|
|
500
|
+
//#endregion
|
|
501
|
+
//#region src/lib/server/archive.ts
|
|
502
|
+
/**
|
|
503
|
+
* Local DuckDB archive of the events the app has streamed.
|
|
504
|
+
*
|
|
505
|
+
* The driver (`@duckdb/node-api` plus its per-platform native binding) is an
|
|
506
|
+
* optional dependency: it is imported lazily, on the first request that needs
|
|
507
|
+
* it, and every failure is turned into an unavailable archive instead of an
|
|
508
|
+
* error. The rest of the app therefore works unchanged on platforms where the
|
|
509
|
+
* binding is missing.
|
|
510
|
+
*
|
|
511
|
+
* One process owns the file: DuckDB allows a single writer, and the SvelteKit
|
|
512
|
+
* server is that writer. Statements are serialised through an internal queue so
|
|
513
|
+
* a stream writing while another request reads cannot interleave on the
|
|
514
|
+
* connection.
|
|
515
|
+
*/
|
|
516
|
+
/** Package name of the optional DuckDB driver. */
|
|
517
|
+
var DUCKDB_MODULE = "@duckdb/node-api";
|
|
518
|
+
/** Directory name used under the platform data directory. */
|
|
519
|
+
var ARCHIVE_DIR_NAME = "watch-tail";
|
|
520
|
+
/** File name of the archive inside {@link ARCHIVE_DIR_NAME}. */
|
|
521
|
+
var ARCHIVE_FILE_NAME = "archive.duckdb";
|
|
522
|
+
/**
|
|
523
|
+
* Imports the optional DuckDB driver.
|
|
524
|
+
*
|
|
525
|
+
* The specifier is held in a variable on purpose: bundlers must leave the
|
|
526
|
+
* import alone so Node resolves it at run time, where a missing binding can be
|
|
527
|
+
* reported as an unavailable archive rather than a build error.
|
|
528
|
+
*/
|
|
529
|
+
async function loadDuckDbDriver() {
|
|
530
|
+
const loaded = await import(DUCKDB_MODULE);
|
|
531
|
+
if (typeof loaded.DuckDBInstance?.create !== "function") throw new Error(`${DUCKDB_MODULE} did not expose DuckDBInstance.create`);
|
|
532
|
+
return loaded;
|
|
533
|
+
}
|
|
534
|
+
var DISABLED_VALUES = /* @__PURE__ */ new Set([
|
|
535
|
+
"0",
|
|
536
|
+
"false",
|
|
537
|
+
"no",
|
|
538
|
+
"off"
|
|
539
|
+
]);
|
|
540
|
+
/** True when an environment value means "off". */
|
|
541
|
+
function isDisabled(value) {
|
|
542
|
+
return value !== void 0 && DISABLED_VALUES.has(value.trim().toLowerCase());
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* True when this process is a test runner.
|
|
546
|
+
*
|
|
547
|
+
* Vitest sets `VITEST`, and `NODE_ENV=test` covers other harnesses. Tests must
|
|
548
|
+
* not append to the archive of the machine they run on, so the archive is off
|
|
549
|
+
* unless a test asks for it through `WATCH_STREAM_ARCHIVE_DB` or
|
|
550
|
+
* `WATCH_STREAM_ARCHIVE=on`.
|
|
551
|
+
*/
|
|
552
|
+
function isTestProcess(env) {
|
|
553
|
+
return env.VITEST !== void 0 || env.NODE_ENV === "test";
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Resolves the archive file path.
|
|
557
|
+
*
|
|
558
|
+
* `WATCH_STREAM_ARCHIVE_DB` wins; otherwise the file lives in the platform's
|
|
559
|
+
* data directory (`XDG_DATA_HOME`, `%LOCALAPPDATA%` or
|
|
560
|
+
* `~/Library/Application Support`), never in the current directory, so history
|
|
561
|
+
* survives running the CLI from a different folder.
|
|
562
|
+
*/
|
|
563
|
+
function resolveArchiveConfig(env, platform = process.platform, home = homedir()) {
|
|
564
|
+
const path = env.WATCH_STREAM_ARCHIVE_DB?.trim() ?? "";
|
|
565
|
+
const dataDir = env.XDG_DATA_HOME?.trim() ?? (platform === "darwin" ? join(home, "Library", "Application Support") : platform === "win32" ? env.LOCALAPPDATA?.trim() ?? join(home, "AppData", "Local") : join(home, ".local", "share"));
|
|
566
|
+
const requested = env.WATCH_STREAM_ARCHIVE?.trim() ?? "";
|
|
567
|
+
return {
|
|
568
|
+
enabled: isDisabled(requested) ? false : path.length > 0 || requested.length > 0 ? true : !isTestProcess(env),
|
|
569
|
+
path: path.length > 0 ? path : join(dataDir, ARCHIVE_DIR_NAME, ARCHIVE_FILE_NAME)
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* A short, non-throwing description of an unknown failure.
|
|
574
|
+
*
|
|
575
|
+
* A conflicting lock is the one failure a user can act on, and DuckDB's own
|
|
576
|
+
* message buries the reason in a pid and a documentation link, so it is
|
|
577
|
+
* reported as what it means for this app.
|
|
578
|
+
*/
|
|
579
|
+
function describeOpenError(error, path) {
|
|
580
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
581
|
+
if (/Could not set lock|Conflicting lock/i.test(message)) return `the archive file ${path} is locked by another process (another watch-tail is using it); use --db to keep a separate file`;
|
|
582
|
+
return message;
|
|
583
|
+
}
|
|
584
|
+
/** True when the failure is the exclusive file lock. */
|
|
585
|
+
function isLockError(error) {
|
|
586
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
587
|
+
return /Could not set lock|Conflicting lock/i.test(message);
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* One DuckDB file holding every event the app has streamed.
|
|
591
|
+
*
|
|
592
|
+
* `open` never throws: a missing driver, a locked file or a corrupt database all
|
|
593
|
+
* produce an instance with `available === false` and the reason on `error`. The
|
|
594
|
+
* read and write methods then behave as no-ops, so callers never have to guard
|
|
595
|
+
* their happy path.
|
|
596
|
+
*/
|
|
597
|
+
var LogArchive = class LogArchive {
|
|
598
|
+
/** Database file this archive uses. */
|
|
599
|
+
path;
|
|
600
|
+
#connection = null;
|
|
601
|
+
#available = false;
|
|
602
|
+
#error = null;
|
|
603
|
+
#queue = Promise.resolve();
|
|
604
|
+
constructor(path) {
|
|
605
|
+
this.path = path;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Opens (and creates) the archive.
|
|
609
|
+
*
|
|
610
|
+
* Statements are executed in order: the sequence first, then the table, then
|
|
611
|
+
* the indexes, so an existing file from an older build is upgraded in place.
|
|
612
|
+
* The request-id backfill runs last, inside the same serialised path, and
|
|
613
|
+
* cannot fail the open.
|
|
614
|
+
*/
|
|
615
|
+
static async open(options) {
|
|
616
|
+
const archive = new LogArchive(options.path);
|
|
617
|
+
const load = options.load ?? loadDuckDbDriver;
|
|
618
|
+
const ensureDir = options.ensureDir ?? ((dir) => mkdirSync(dir, { recursive: true }));
|
|
619
|
+
try {
|
|
620
|
+
ensureDir(dirname(options.path));
|
|
621
|
+
const driver = await load();
|
|
622
|
+
for (let attempt = 1;; attempt += 1) try {
|
|
623
|
+
const connection = await (await driver.DuckDBInstance.create(options.path)).connect();
|
|
624
|
+
for (const statement of ARCHIVE_SCHEMA) await connection.run(statement);
|
|
625
|
+
archive.#connection = connection;
|
|
626
|
+
archive.#available = true;
|
|
627
|
+
await archive.#backfillRequestIds();
|
|
628
|
+
break;
|
|
629
|
+
} catch (error) {
|
|
630
|
+
if (attempt >= 3 || !isLockError(error)) throw error;
|
|
631
|
+
await setTimeout(150);
|
|
632
|
+
}
|
|
633
|
+
} catch (error) {
|
|
634
|
+
archive.#available = false;
|
|
635
|
+
archive.#error = describeOpenError(error, options.path);
|
|
636
|
+
}
|
|
637
|
+
return archive;
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* An archive that is off by configuration or could not be opened.
|
|
641
|
+
*
|
|
642
|
+
* Every method degrades to a no-op, so callers can use one code path for
|
|
643
|
+
* "no DuckDB on this machine" and "the user asked for no archive".
|
|
644
|
+
*/
|
|
645
|
+
static unavailable(path, error = null) {
|
|
646
|
+
const archive = new LogArchive(path);
|
|
647
|
+
archive.#available = false;
|
|
648
|
+
archive.#error = error;
|
|
649
|
+
return archive;
|
|
650
|
+
}
|
|
651
|
+
/** True when the archive can be read and written. */
|
|
652
|
+
get available() {
|
|
653
|
+
return this.#available;
|
|
654
|
+
}
|
|
655
|
+
/** Why the archive is unavailable, or the last failure, or `null`. */
|
|
656
|
+
get error() {
|
|
657
|
+
return this.#error;
|
|
658
|
+
}
|
|
659
|
+
/** Runs one task after every task queued before it. */
|
|
660
|
+
#enqueue(task) {
|
|
661
|
+
const next = this.#queue.then(task, task);
|
|
662
|
+
this.#queue = next.then(() => void 0, () => void 0);
|
|
663
|
+
return next;
|
|
664
|
+
}
|
|
665
|
+
/** Runs a statement, returning the failure instead of throwing. */
|
|
666
|
+
async #guard(fallback, task) {
|
|
667
|
+
if (this.#connection === null) return fallback;
|
|
668
|
+
return this.#enqueue(async () => {
|
|
669
|
+
try {
|
|
670
|
+
return await task();
|
|
671
|
+
} catch (error) {
|
|
672
|
+
this.#error = error instanceof Error ? error.message : String(error);
|
|
673
|
+
return fallback;
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Migrates the request id of rows written before the column existed.
|
|
679
|
+
*
|
|
680
|
+
* It runs inside {@link LogArchive.#guard}, so it shares the serialised
|
|
681
|
+
* statement queue with every other write and reports a failure on `error`
|
|
682
|
+
* instead of letting it escape `open`. The watermark is the highest `seq` the
|
|
683
|
+
* migration has looked at, so a capped or interrupted pass carries on from
|
|
684
|
+
* there on the next open and no row is scanned twice; a row whose message
|
|
685
|
+
* holds no id stays NULL, which is the archive's answer for that row.
|
|
686
|
+
*/
|
|
687
|
+
async #backfillRequestIds() {
|
|
688
|
+
await this.#guard(void 0, async () => {
|
|
689
|
+
const connection = this.#connection;
|
|
690
|
+
if (connection === null) return;
|
|
691
|
+
const ceiling = rowToMaxSeq((await connection.runAndReadAll(ARCHIVE_MAX_SEQ_SQL)).getRowObjects()[0]);
|
|
692
|
+
if (ceiling === null) return;
|
|
693
|
+
const watermark = rowToBackfillWatermark((await connection.runAndReadAll(buildRequestIdBackfillStateQuery())).getRowObjects()[0]);
|
|
694
|
+
if (watermark !== null && watermark >= ceiling) return;
|
|
695
|
+
const { sql, params } = buildRequestIdBackfillQuery({
|
|
696
|
+
maxSeq: ceiling,
|
|
697
|
+
afterSeq: watermark,
|
|
698
|
+
limit: REQUEST_ID_BACKFILL_LIMIT
|
|
699
|
+
});
|
|
700
|
+
const candidates = rowsToBackfillCandidates((await connection.runAndReadAll(sql, params)).getRowObjects());
|
|
701
|
+
const updates = planRequestIdBackfill(candidates);
|
|
702
|
+
for (let start = 0; start < updates.length; start += 500) {
|
|
703
|
+
const chunk = updates.slice(start, start + 500);
|
|
704
|
+
await connection.run(buildRequestIdBackfillUpdate(chunk.length), toRequestIdBackfillParams(chunk));
|
|
705
|
+
}
|
|
706
|
+
const scanned = candidates.length < 2e4 ? ceiling : maxSeqOf(candidates);
|
|
707
|
+
await connection.run(buildRequestIdBackfillStateWrite(), [String(scanned ?? ceiling)]);
|
|
708
|
+
});
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Writes one batch of streamed events.
|
|
712
|
+
*
|
|
713
|
+
* Batches are split into chunks of {@link ARCHIVE_INSERT_CHUNK} rows, and
|
|
714
|
+
* `INSERT OR IGNORE` plus the unique index make a repeated scan idempotent.
|
|
715
|
+
* Failures never propagate: a full disk must not interrupt a log stream.
|
|
716
|
+
*/
|
|
717
|
+
async record(region, logGroup, events) {
|
|
718
|
+
if (events.length === 0) return 0;
|
|
719
|
+
return this.#guard(0, async () => {
|
|
720
|
+
const connection = this.#connection;
|
|
721
|
+
if (connection === null) return 0;
|
|
722
|
+
let written = 0;
|
|
723
|
+
for (let start = 0; start < events.length; start += 500) {
|
|
724
|
+
const chunk = events.slice(start, start + 500);
|
|
725
|
+
await connection.run(buildInsertSql(chunk.length), toArchiveParams(region, logGroup, chunk));
|
|
726
|
+
written += chunk.length;
|
|
727
|
+
}
|
|
728
|
+
return written;
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
/** Reads one page of archived events. */
|
|
732
|
+
async page(request) {
|
|
733
|
+
const empty = {
|
|
734
|
+
events: [],
|
|
735
|
+
last: null
|
|
736
|
+
};
|
|
737
|
+
return this.#guard(empty, async () => {
|
|
738
|
+
const connection = this.#connection;
|
|
739
|
+
if (connection === null) return empty;
|
|
740
|
+
const { sql, params } = buildPageQuery(request);
|
|
741
|
+
return rowsToPage((await connection.runAndReadAll(sql, params)).getRowObjects());
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Bucketed counts for the chart, per group and level.
|
|
746
|
+
*
|
|
747
|
+
* The request is forwarded as it arrives: it carries the grouping that says
|
|
748
|
+
* whether a mark counts an event or a request.
|
|
749
|
+
*/
|
|
750
|
+
async seriesQuery(request) {
|
|
751
|
+
return this.#guard([], async () => {
|
|
752
|
+
const connection = this.#connection;
|
|
753
|
+
if (connection === null) return [];
|
|
754
|
+
const { sql, params } = buildSeriesQuery(request);
|
|
755
|
+
return rowsToSeries((await connection.runAndReadAll(sql, params)).getRowObjects());
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
/** Aggregate row count and time bounds. */
|
|
759
|
+
async totals() {
|
|
760
|
+
return this.#guard(rowToTotals(void 0), async () => {
|
|
761
|
+
const connection = this.#connection;
|
|
762
|
+
if (connection === null) return rowToTotals(void 0);
|
|
763
|
+
return rowToTotals((await connection.runAndReadAll(ARCHIVE_TOTALS_SQL)).getRowObjects()[0]);
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
/** Log groups held by the archive, optionally limited to one region. */
|
|
767
|
+
async groups(region = null) {
|
|
768
|
+
return this.#guard([], async () => {
|
|
769
|
+
const connection = this.#connection;
|
|
770
|
+
if (connection === null) return [];
|
|
771
|
+
const { sql, params } = buildGroupsQuery(region);
|
|
772
|
+
return rowsToGroups((await connection.runAndReadAll(sql, params)).getRowObjects());
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
/** Status for `GET /api/archive`, including the file size on disk. */
|
|
776
|
+
async status() {
|
|
777
|
+
const totals = await this.totals();
|
|
778
|
+
let bytes = null;
|
|
779
|
+
if (this.#available) try {
|
|
780
|
+
bytes = statSync(this.path).size;
|
|
781
|
+
} catch {
|
|
782
|
+
bytes = null;
|
|
783
|
+
}
|
|
784
|
+
return {
|
|
785
|
+
path: this.path,
|
|
786
|
+
available: this.#available,
|
|
787
|
+
error: this.#error,
|
|
788
|
+
bytes,
|
|
789
|
+
totals
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
/** Closes the connection. Safe to call twice. */
|
|
793
|
+
async close() {
|
|
794
|
+
const connection = this.#connection;
|
|
795
|
+
this.#connection = null;
|
|
796
|
+
this.#available = false;
|
|
797
|
+
if (connection?.closeSync === void 0) return;
|
|
798
|
+
await this.#enqueue(async () => {
|
|
799
|
+
connection.closeSync?.();
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
/** The archive for the running server, opened once per process. */
|
|
804
|
+
var sharedArchive = null;
|
|
805
|
+
/**
|
|
806
|
+
* Returns the process-wide archive for `env`, opening it on first use.
|
|
807
|
+
*
|
|
808
|
+
* The same file is never opened twice per process; a disabled archive
|
|
809
|
+
* (`WATCH_STREAM_ARCHIVE=off`) is remembered so no driver import is attempted.
|
|
810
|
+
*/
|
|
811
|
+
function getArchive(env, load) {
|
|
812
|
+
if (sharedArchive === null) {
|
|
813
|
+
const config = resolveArchiveConfig(env);
|
|
814
|
+
sharedArchive = config.enabled ? LogArchive.open({
|
|
815
|
+
path: config.path,
|
|
816
|
+
...{}
|
|
817
|
+
}) : Promise.resolve(LogArchive.unavailable(config.path));
|
|
818
|
+
}
|
|
819
|
+
return sharedArchive;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
export { getArchive as g };
|
|
823
|
+
//# sourceMappingURL=archive.js-C2D-5edp.js.map
|