feat: remove mongo and all of its deps (#1525)

* feat: remove mongo and all of its deps

* feat: remove all mongodb logic+thin out some unused deps
This commit is contained in:
zk
2026-05-20 19:48:39 +01:00
committed by GitHub
parent 23482d3ae8
commit c569243194
61 changed files with 446 additions and 388293 deletions
-144
View File
@@ -1,144 +0,0 @@
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"iidx-sp","userID":1,"sessionID":"Q578fa997a13b92203dbc59082a452636631ff691","type":"Created","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":0},"timestamp":1778174638000,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qc252f411274450ce5698af2907997cbab60105d2","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":0},"timestamp":1778174660089,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q5d5e46543de37de7144aabde86aaeaf00498195a","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":1},"timestamp":1778174660094,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qc37df566339572b6fee3ca5edd4b3bf0314e29c5","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":2},"timestamp":1778174660099,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q8cbcd83ef2ee60cecb791c09e573cacb82614819","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":3},"timestamp":1778174660105,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q189cf02b87c556a9b3f871e303758dd88d16c96a","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":4},"timestamp":1778174660108,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qe40bb6742d397db1bfedc9aa2da386994054cb8e","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":5},"timestamp":1778174660112,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q7d9e5a5aa58b424f33253e67721fb416d43c0a8b","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":6},"timestamp":1778174660115,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qcafb063529a7746b14be61b5f8b8f785b4c0bb9d","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":7},"timestamp":1778174660121,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qdde44db5089244cc55f662c6a41ffb2fb714a8b8","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":8},"timestamp":1778174660131,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q6e8d8cbad5078faf2b592504808d09e213ed0129","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":9},"timestamp":1778174660136,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q8559d0c86bf3b2f8b15801d36f27fb876ea552b3","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":10},"timestamp":1778174660142,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qe37e28c4561537d764d26d3084f544d4f07fc794","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":11},"timestamp":1778174660147,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q6657d8805d106093b0dddb14cb504b937f132760","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":12},"timestamp":1778174660155,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q48f3114dd0c29c80252fd65a670d97fa4a600f82","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":13},"timestamp":1778174660162,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q990c934c1383480e3d37baf91bd6a765cf8b3a2a","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":14},"timestamp":1778174660165,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q7a417c5a2159bf29e7a01e5250a0711528697f8f","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":15},"timestamp":1778174660170,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q7e1c4f468c77e2fefc8f7c6883e2de759bdd25b9","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":16},"timestamp":1778174660174,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qee252e93a7d251e87e9a7fe6c17139b2d2f99e7a","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":17},"timestamp":1778174660178,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qa105eb34d8423b192e43c238f8ff91083e1bffc9","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":18},"timestamp":1778174660181,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q27daaebb4468a50aae51882418658cc1aa106a34","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":19},"timestamp":1778174660184,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q8c121fa8b73857c8ee117e700842b866e3baf6a7","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":20},"timestamp":1778174660199,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q7210852aa9d6b82fe8d99da03f02d86782285985","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":21},"timestamp":1778174660205,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qcfb1824558a1a99b1538dbd7432289a8be57b3e6","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":22},"timestamp":1778174660209,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q49efff3408ecd0c5b7e41548875f8e443d0dd7f9","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":23},"timestamp":1778174660217,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qc2618e82e8d9911531d1020c4d467859c9214b36","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":24},"timestamp":1778174660223,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q0f170b6b312d00be302e5cc0d4285da541524fbe","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":25},"timestamp":1778174660227,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qdb008e8c2209f91ad53ec0cdee933f67f6e5331e","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":26},"timestamp":1778174660233,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q045d94351296c45a1eaabae9a30029dc68318e19","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":27},"timestamp":1778174660236,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q39b379df7a10f4a72574f3680b52a7ef69751af4","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":28},"timestamp":1778174660245,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q811a4764a3d2534fb4b1da6cbaea1d407a60d0b5","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":29},"timestamp":1778174660248,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qb79f15ac66e1965c35be2004006e577e071b87b7","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":30},"timestamp":1778174660252,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q0f4611dfe6752e90e0ea7ad0a339aed51391d94a","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":31},"timestamp":1778174660257,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q9f0a302cbb6160468da3399669832c52ae7c5910","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":32},"timestamp":1778174660260,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q9418f31ea6a52ffaca93f0657abab15f648d3f24","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":33},"timestamp":1778174660263,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qb4db5061c31ee7743fd4c592a3136cee6c53225d","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":34},"timestamp":1778174660267,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qa7a6babb5c27931e45e0396d81c95272beee00f8","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":35},"timestamp":1778174660270,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q927681faa73a41ffbcc2b2e51bb3220656f7a27c","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":36},"timestamp":1778174660278,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q1edafb103d7f842bd3164bf2d77cca323ddc24d5","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":37},"timestamp":1778174660287,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qc93f8402727171169df28aa12c1852e59a00b0b4","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":38},"timestamp":1778174660294,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qae0736c538299e163942c0fd4fd5c34bcd8ab7c5","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":39},"timestamp":1778174660299,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qa8723107d169ea4e765d28d9dec764e119d4879e","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":40},"timestamp":1778174660303,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qe8e555ad077e1897eab4ece65580c27f1fd8b0bb","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":41},"timestamp":1778174660309,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qfef6ca0ce28485e788d6562db2527b7a8ddce498","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":42},"timestamp":1778174660314,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q5c68d2fe24dc80dfd015b2abcb89e4eba774feaf","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":43},"timestamp":1778174660317,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q4d88a6581e28b7024f020673feb45b75c1d101b3","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":44},"timestamp":1778174660321,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q7d8887d5d3fd265b73b1b29363d305ce13360c94","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":45},"timestamp":1778174660327,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q6f0fb12cb6e8aaf252a1d6040e00d391ea8464d4","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":46},"timestamp":1778174660345,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qd4ec361100e6590f313350b9e3e9150d6387e2ba","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":47},"timestamp":1778174660348,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qe0381db5230e8b7dd310bf2d23d8427dbbb84822","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":48},"timestamp":1778174660350,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q31e15ba0f00783be89720b1aec46550b58976d3b","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":49},"timestamp":1778174660354,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qba5cc5abba6d6f03e6868628b3a4de1461abbc4c","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":50},"timestamp":1778174660360,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qc9d38a91243fdd685b83f61292103dc53da0d4cf","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":51},"timestamp":1778174660365,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q8dabfc9ce6038eb05610efabd1bbe169fe060c46","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":52},"timestamp":1778174660369,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q44c21d76c1f0aceca460bbd6dd9f32e3ba5c7732","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":53},"timestamp":1778174660372,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qba93137b42cdac7a6a33e6b0b8936879e526089c","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":54},"timestamp":1778174660378,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qac474a57764cbaeb6b055f0c98d12bd1dfc75497","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":55},"timestamp":1778174660383,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q54aed62223d5f4a774bbf7283e1d71bffa6a8877","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":56},"timestamp":1778174660389,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qed26c346b3a65815a31b5228e4e632f42f62fa39","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":57},"timestamp":1778174660401,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qaf7a4851b6f7ddf6e9c186c8418b6130bad70798","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":58},"timestamp":1778174660405,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q801affd326b159befcc5ec230f6eac3149603622","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":59},"timestamp":1778174660415,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qc69f6c9c7ab8ecdbcad074aab2f796f052f5d0f8","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":60},"timestamp":1778174660430,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q8b639b6300480f0ecfd5db79b36d2e3c132a54ac","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":61},"timestamp":1778174660438,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qf85158125f1aeaea09a2827da2b3d7d45aba72bc","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":62},"timestamp":1778174660443,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qd9ea6889198ee5e8a099baa6c91bcffa7eca95c3","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":63},"timestamp":1778174660449,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q0585eab487fb017863aaac9f8768e9d1b72cc123","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":64},"timestamp":1778174660453,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q567de464814f23d575e9004f505e1a322be5de31","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":65},"timestamp":1778174660456,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q0b7df3b5afa5e0b50e42c67639b43f175072ce22","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":66},"timestamp":1778174660462,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qdc9e3f944ec4759285e25a25c7a372303c7e18b9","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":67},"timestamp":1778174660465,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q38e82752ad57d923af66a12471ade3d9b883f958","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":68},"timestamp":1778174660469,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q452773b20751ea073659360a00cec1b3861b85ef","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":69},"timestamp":1778174660474,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qd55da19de534bd8038ed3e52b82a890567b6ad19","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":70},"timestamp":1778174660482,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q314a9e746a8b962a00703e9d84a93190c0e915f3","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":71},"timestamp":1778174660487,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qfa374369968d49856bca05e82f36b4d7145ed1ec","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":72},"timestamp":1778174660490,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q76b0603b5a47f4a0236c9087dfbbfeba1fb3bc7b","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":73},"timestamp":1778174660506,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q203f0fe1b402e9a0448ac39e6cd1db01d511a50a","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":74},"timestamp":1778174660514,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q8c27ac1b60300274df3d669847921d35d01289ae","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":75},"timestamp":1778174660520,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q25b244ba922b0d4f1092bf419ea9788b66708f7c","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":76},"timestamp":1778174660524,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q70d5b9a4d9b79fff4f66b8ae300cf64fc488f02a","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":77},"timestamp":1778174660531,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q20e1e28b721c3b0733cd924e720704962e54e3e8","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":78},"timestamp":1778174660553,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qadc97ca77718ccfb8bde7ac9593edaac4bb87a6f","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":79},"timestamp":1778174660569,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q4b2feb98be77e6882f84d6a9cbeca42887abdbf3","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":80},"timestamp":1778174660573,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q77b97d7a7b1bfda84da3f472cc32bacec267219f","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":81},"timestamp":1778174660581,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q424e90d547303ea4923e0258cfc2d8d392679cae","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":82},"timestamp":1778174660586,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q97f2bc192eeb70752c9e30cfe7dcde99bc47e690","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":83},"timestamp":1778174660590,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q59001766f9b8a52c8dcc97bf1d042b454708eae4","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":84},"timestamp":1778174660593,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q00e706d002c2e9ea6086371b935a50b282706d39","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":85},"timestamp":1778174660598,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q0086ff797f2b95359b9472c107f20ae075898b21","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":86},"timestamp":1778174660601,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qe989edaca5a2f24b8d604bf98ad03a48baf0f010","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":87},"timestamp":1778174660606,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q12ef1db3064ea85095e2ff9d74a45a21d7ccc9b5","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":88},"timestamp":1778174660622,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q660bd3bb616c87eeaa3df3b65f25f1a98c57af10","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":89},"timestamp":1778174660626,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q1d038349e88718833f4acd084ca6940a66deb9c4","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":90},"timestamp":1778174660636,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q11339defac1468d048e2b57b1b4d2cc67751ac86","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":91},"timestamp":1778174660639,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q44a76aab9debe86da79c4b870f83d88f76dc4118","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":92},"timestamp":1778174660642,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qe1adc2f7ec768f394813223ed21bd2b571969a4a","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":93},"timestamp":1778174660652,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q489661edfc15cfc92c3834eceb309f1e2d4bb893","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":94},"timestamp":1778174660660,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q489661edfc15cfc92c3834eceb309f1e2d4bb893","type":"Appended","repeatSessionInThisLoad":true,"willPush":false,"runId":"post-fix","priorReturnCount":95},"timestamp":1778174660671,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q06014b95faecab25d0f47b051f1e38d96af5ea7d","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":95},"timestamp":1778174660680,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qdcf58e13a815753fe75b38a564c8e581fef42d17","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":96},"timestamp":1778174660684,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q016e78c91f15ef13016dedc24a16c91284de26ba","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":97},"timestamp":1778174660688,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q12b6a4887a80ecc13a74079376f1259c5cc40e86","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":98},"timestamp":1778174660690,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q577397051d7966d96c1fb5cf649654f3b8dd6edb","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":99},"timestamp":1778174660699,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qa0b0c7a4bb6862f33febafdc80de2efe2d2adb2d","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":100},"timestamp":1778174660702,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qa17b785caf6e37dc996afe0f218baa008a63fb1d","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":101},"timestamp":1778174660705,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qa5bac01b00d12082158013a264ae8c2b10c837c6","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":102},"timestamp":1778174660708,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q6c6e8a526936a57adab446b28d870ae68bee3910","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":103},"timestamp":1778174660715,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q190d862f7853a94510ae603cefa8fec2d0010c17","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":104},"timestamp":1778174660719,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q68b4212ad3a72c9029272f3c8dba2909f6c717d5","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":105},"timestamp":1778174660724,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q61e5881c2e33e7b9c47afc832e54fa23c07657a3","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":106},"timestamp":1778174660729,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q151c94c4fb26eab70ff8db30652dac361d09d11b","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":107},"timestamp":1778174660736,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qb19bcd6ae2f4c2b1bee880fd6021214bfbf05604","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":108},"timestamp":1778174660741,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q952a8c756b74a7c556f19edb77628dbb04c0800b","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":109},"timestamp":1778174660744,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q673e224863b4bd6073c46591576644da16c4324f","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":110},"timestamp":1778174660750,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qd814902d29a9e34622ddc9dfa5089dc8c57f80bc","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":111},"timestamp":1778174660754,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q3ebba12414b002bbde0e9f5ca58134ca37071867","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":112},"timestamp":1778174660758,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qd073fc8da813e7fefa8fe1d6352714f48dd0ff7a","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":113},"timestamp":1778174660761,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q401bdb009b493618518bd7c4a066bd54acdabc10","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":114},"timestamp":1778174660768,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q2ce7b164c51b3bf0e4dcfd4b9b2228892815b856","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":115},"timestamp":1778174660772,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q0f405f56021d7115617fde39cadcaf6b89866705","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":116},"timestamp":1778174660778,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q9b7187fe54b150ef7e249c75257cd2b252084e85","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":117},"timestamp":1778174660797,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q70dc4b3da3675781ee314b6f44f95187cba7d3c1","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":118},"timestamp":1778174660809,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q60b1a04f2e28aab22c166378e89871125de6d563","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":119},"timestamp":1778174660813,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q7fc3d41514a6629a92e0c89674848e4224fed28d","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":120},"timestamp":1778174660818,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q4d84680f2603292d7f650921e4fdea73b0a6c6ca","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":121},"timestamp":1778174660822,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q3d517c038e36209ee4256306c34690fd53a8306e","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":122},"timestamp":1778174660826,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qaa50b6fadf96a3e12d60cca87f87cb5f7f462c69","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":123},"timestamp":1778174660843,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qc769036b5a38cb57c5ee1bf6f17360ed47f5ea54","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":124},"timestamp":1778174660858,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q84b63c6eefe648254d00a5ec71dc3808339dcce2","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":125},"timestamp":1778174660862,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q7a1ed0017220e8b96e861fa6289129b7e7f0cb33","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":126},"timestamp":1778174660867,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q86e26421f03118913a2f7a9adf630a3b10c879f9","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":127},"timestamp":1778174660873,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q7554af752b6d25d731396bdb31432e41e77ac694","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":128},"timestamp":1778174660877,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q100d03555b2f9ffbc2a9da5bb2e973b75356f9e7","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":129},"timestamp":1778174660883,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q9a42fb36c8479ca5c84e5d7c9e9d5103ae61e8fb","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":130},"timestamp":1778174660888,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q935ab1b2ff13b6a00f83bff9dc92e1b816eb8c98","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":131},"timestamp":1778174660893,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q97c8f2cf1053a36e4966d82265ecea04d1051f1e","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":132},"timestamp":1778174660904,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q5ded6f7f8d7f5d37223f280f2d900da118f7a193","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":133},"timestamp":1778174660908,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q7d5db734e4edef4477ef4cab6b6427fb9f45e4a8","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":134},"timestamp":1778174660911,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q948fd05d2c9ed5d49aa75daace5658c6a64aa74c","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":135},"timestamp":1778174660919,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q10363461fbf6a6be6dd31c1f1e978b0c0a9c08a9","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":136},"timestamp":1778174660923,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Q55b15f2d894f8e3bcd768ed1510da91c2158992f","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":137},"timestamp":1778174660928,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qb87640a3371880e254627383c3b23cf5230527c9","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":138},"timestamp":1778174660936,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"sessions.ts:LoadScoresIntoSessions:beforePush","message":"sessionInfoReturn push","data":{"game":"jubeat","userID":1,"sessionID":"Qf199ca3f596016185da0299200f1c4322656cce1","type":"Appended","repeatSessionInThisLoad":false,"willPush":true,"runId":"post-fix","priorReturnCount":139},"timestamp":1778174660941,"hypothesisId":"H1"}
{"sessionId":"7b27d8","location":"finalize-import-pg.ts:finalizeImportToPostgres:entry","message":"finalizeImportToPostgres called","data":{"importID":"4584b4feb70692dba1707a42c243a56a4b3be56b","runId":"post-fix"},"timestamp":1778174661244,"hypothesisId":"H2"}
{"sessionId":"7b27d8","location":"finalize-import-pg.ts:import_session:preInsert","message":"import_session insert payload stats","data":{"importID":"4584b4feb70692dba1707a42c243a56a4b3be56b","createdLen":140,"uniqueSessionCount":140,"dupSessionIds":[],"runId":"post-fix"},"timestamp":1778174661246,"hypothesisId":"H1"}
-80
View File
@@ -1,80 +0,0 @@
/**
* Seed / upsert users into MongoDB (standalone; not wired to this repo's app).
*
* Usage:
* mongosh "mongodb://127.0.0.1:27017/YOUR_DB_NAME" --file scripts/mongo-insert-users.mongosh.js
*
* Override IDs (comma- or space-separated):
* USER_IDS="1001,1002" mongosh "mongodb://127.0.0.1:27017/YOUR_DB" --file scripts/mongo-insert-users.mongosh.js
*
* If USER_IDS is unset, defaults to 1, 2, 3 (edit DEFAULT_USER_IDS below).
*/
const DEFAULT_USER_IDS = [1, 2, 3];
const USER_IDS =
typeof process !== "undefined" && process.env.USER_IDS
? process.env.USER_IDS.split(/[\s,]+/)
.map((s) => parseInt(s, 10))
.filter((n) => !Number.isNaN(n))
: DEFAULT_USER_IDS;
const PASSWORD_HASH = "$2b$12$XXXXXXXjo9lrnLGCree4e.tm7PSWwA5N.Lb8yLDzP8ytDt6cZAtuy";
for (const userId of USER_IDS) {
const username = `mysteryman${userId}`;
const email = `${username}@example.com`;
db.getCollection("users").updateOne(
{ id: userId },
{
$set: {
id: userId,
username,
usernameLowercase: username,
about: "I'm a fairly nondescript person.",
socialMedia: {},
status: null,
customBannerLocation: null,
customPfpLocation: null,
joinDate: 1638903236112,
lastSeen: 1772301833519,
authLevel: 1,
badges: [],
},
},
{ upsert: true },
);
db.getCollection("user-private-information").updateOne(
{ userID: userId },
{
$set: {
userID: userId,
password: PASSWORD_HASH,
email,
},
},
{ upsert: true },
);
db.getCollection("user-settings").updateOne(
{ userID: userId },
{
$set: {
userID: userId,
preferences: {
developerMode: false,
advancedMode: false,
invisible: false,
contentiousContent: false,
deletableScores: false,
},
following: [],
},
},
{ upsert: true },
);
}
print(`Upserted ${USER_IDS.length} user(s): ${USER_IDS.join(", ")}`);
-52
View File
@@ -1,52 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# ---------------------------------------------------------------------------
# restore-mongo.sh
# Runs mongorestore (via a throwaway Docker container) against MongoDB
# already running on localhost:27017. Nothing is installed to the system.
#
# Usage:
# ./restore-mongo.sh <dump.gz> [target-db-name]
#
# Env overrides:
# MONGO_VERSION (default: 7)
# MONGO_PORT (default: 27017)
# ---------------------------------------------------------------------------
DUMP_FILE="${1:-dump.gz}"
MONGO_VERSION="${MONGO_VERSION:-7}"
MONGO_PORT="${MONGO_PORT:-27017}"
DB_NAME="${2:-}"
if ! command -v docker &>/dev/null; then
echo "ERROR: docker is not in PATH." >&2
exit 1
fi
if [[ ! -f "$DUMP_FILE" ]]; then
echo "ERROR: dump file not found: $DUMP_FILE" >&2
echo "Usage: $0 <path/to/dump.gz> [db-name-override]" >&2
exit 1
fi
DUMP_FILE="$(realpath "$DUMP_FILE")"
DUMP_DIR="$(dirname "$DUMP_FILE")"
DUMP_BASENAME="$(basename "$DUMP_FILE")"
RESTORE_ARGS=(--archive="/dump/${DUMP_BASENAME}" --gzip --verbose)
if [[ -n "$DB_NAME" ]]; then
RESTORE_ARGS+=(--db "$DB_NAME")
fi
echo "Restoring $DUMP_BASENAME -> localhost:$MONGO_PORT ..."
docker run --rm \
--network host \
-v "${DUMP_DIR}:/dump:ro" \
"mongo:$MONGO_VERSION" \
mongorestore \
--host "localhost:${MONGO_PORT}" \
"${RESTORE_ARGS[@]}"
echo "Done."
+60 -8
View File
@@ -1,22 +1,44 @@
#!/usr/bin/env bun
/**
* ts_autoinherit: Move pinned dependency versions from workspace package.json files
* into the root catalog so every package uses `catalog:` references.
*
* Usage:
* bun .scripts/ts_autoinherit.js # apply changes in place
* bun .scripts/ts_autoinherit.js --check # report violations, exit 1 if any
*/
import path from "node:path";
import fs from "node:fs";
import { Glob } from "bun";
import { execSync } from "node:child_process";
const args = process.argv.slice(2);
const check = args.includes("--check");
// ANSI colours
const RED = "\x1b[31m";
const GREEN = "\x1b[32m";
const YELLOW = "\x1b[33m";
const BOLD = "\x1b[1m";
const DIM = "\x1b[2m";
const RESET = "\x1b[0m";
const cwd = path.resolve(__dirname, "..");
process.chdir(cwd);
const rootPkg = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf8"));
/** @type {Array<{pkgPath: string, key: string, dependency: string, version: string}>} */
const violations = [];
for (const workspacePattern of rootPkg.workspaces) {
// "typescript/*" + "/package.json" => "typescript/*/package.json" (one workspace dir only).
// "typescript/*" + "*/package.json" would merge *+* into ** and match nested paths / node_modules.
let glob = new Glob(workspacePattern + "/package.json");
for (const pkgPath of glob.scanSync(".")) {
console.log(pkgPath);
const pkg = JSON.parse(fs.readFileSync(path.join(cwd, pkgPath), "utf8"));
for (const key of ["devDependencies", "dependencies"]) {
@@ -29,15 +51,45 @@ for (const workspacePattern of rootPkg.workspaces) {
continue;
}
pkg[key][dependency] = "catalog:";
rootPkg.catalog[dependency] = version;
violations.push({ pkgPath, key, dependency, version });
if (!check) {
pkg[key][dependency] = "catalog:";
rootPkg.catalog[dependency] = version;
}
}
}
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, "\t"));
if (!check) {
console.log(pkgPath);
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, "\t"));
}
}
}
fs.writeFileSync("package.json", JSON.stringify(rootPkg, null, "\t"));
execSync("just fmt", { stdio: "inherit" });
if (check) {
if (violations.length === 0) {
console.log(`${GREEN}${BOLD}✓ All dependencies use catalog: references.${RESET}`);
} else {
const byFile = /** @type {Map<string, typeof violations>} */ (new Map());
for (const v of violations) {
const list = byFile.get(v.pkgPath) ?? [];
list.push(v);
byFile.set(v.pkgPath, list);
}
for (const [pkgPath, vs] of byFile) {
console.log(`\n${BOLD}${pkgPath}${RESET}`);
for (const { key, dependency, version } of vs) {
console.log(` ${RED}✗${RESET} ${DIM}${key}:${RESET} ${dependency}@${version}`);
}
}
console.log(
`\n${YELLOW}${BOLD}⚠ ${violations.length} dep(s) not using catalog: references.${RESET}`,
);
console.log(`${DIM} Run \`.scripts/ts_autoinherit.js\` to fix automatically.${RESET}`);
process.exit(1);
}
} else {
fs.writeFileSync("package.json", JSON.stringify(rootPkg, null, "\t"));
execSync("just fmt", { stdio: "inherit" });
}
-1
View File
@@ -4,7 +4,6 @@ import "Justfile-java"
import "Justfile-db"
import "Justfile-backup"
import "Justfile-misc"
import "Justfile-migrate"
import "Justfile-observability"
import "Justfile-test"
import "Justfile-bench"
-32
View File
@@ -1,32 +0,0 @@
# Justfile for the v3 migration. To be deleted once everything has moved over.
# do the full migration suite.
migrate-v3:
just gen
just seeds-v3-migrate
just mongo-to-pg kamai kamai
just mongo-to-pg boku boku
# Migrate all MongoDB data into Postgres (run once, requires both DBs to be reachable).
mongo-to-pg POSTGRES_DB_NAME MONGO_DB_NAME:
just db-cheap-reset "{{ POSTGRES_DB_NAME }}"
cd typescript/server && VERSION=$(jq -r .version package.json) COMMIT_HASH=$(git rev-parse --short HEAD) POSTGRES_URL="{{POSTGRES_URL}}/{{POSTGRES_DB_NAME}}" MONGO_URL="mongodb://tachi-mongo/{{MONGO_DB_NAME}}" \
bun run src/scripts/migrate-to-postgres.ts
# Rollback the seeds to the state stored somewhere (v2 keeps seeds in seeds/collections, v3 in db/seeds).
seeds-rollback ROLLBACK_DIR="/host-pc/Documents/Repos/Tachi/seeds/collections":
rsync -avz --delete "{{ROLLBACK_DIR}}/" db/seeds
seeds-test:
cd "typescript/seeds-scripts" && ./run-tests.sh
seeds-v3-migrate: seeds-rollback
#!/usr/bin/env bash
set -euo pipefail
cd "typescript/seeds-scripts"
for script in $(ls -1 rerunners/v3/*.ts); do
bun run "./$script"
done
just seeds-test
+4 -6
View File
@@ -7,6 +7,8 @@ check:
evaluate "just fmt-check"
evaluate "bun run --filter '*' lint"
evaluate "just typecheck"
evaluate "bun .scripts/ts_machete.js"
evaluate "bun .scripts/ts_autoinherit.js --check"
post_evaluate
@@ -66,12 +68,8 @@ test-typescript FILTER="*":
#!/bin/bash
set -euo pipefail
# `tachi-postgres-test` (tmpfs + fsync=off, see docker-compose-dev.yml) is the
# fast path; falls back to the dev PG if the dedicated service isn't running.
export POSTGRES_TEST_HOST="${POSTGRES_TEST_HOST:-tachi-postgres-test}"
# Keep POSTGRES_TEST_URL in sync so the `*-db-test-template-reset` recipes
# (called from vitest.globalSetup.ts) hit the same host the workers will.
export POSTGRES_TEST_URL="${POSTGRES_TEST_URL:-postgresql://tachi:tachi@${POSTGRES_TEST_HOST}}"
export POSTGRES_TEST_HOST="tachi-postgres-test"
export POSTGRES_TEST_URL="postgresql://tachi:tachi@${POSTGRES_TEST_HOST}"
bun run --elide-lines=0 --sequential --filter '{{FILTER}}' test -- --reporter=default --reporter=junit --outputFile.junit=test-results/junit.xml
# Summarize Vitest v8 coverage across workspaces (see typescript/coverage-tools).
+18 -247
View File
@@ -18,7 +18,6 @@
"eslint": "^9.20.1",
"kanel": "^3.16.1",
"kanel-kysely": "^0.8.0",
"ts-node": "10.9.1",
"typescript": "5.9.3",
},
},
@@ -56,8 +55,6 @@
"prudence": "catalog:",
"tachi-common": "workspace:*",
"tachi-db": "workspace:*",
"ts-node": "catalog:",
"tsconfig-paths": "catalog:",
"typescript": "catalog:",
},
"devDependencies": {
@@ -135,7 +132,6 @@
"name": "tachi-common",
"version": "2.4.0-dev",
"dependencies": {
"@types/mongodb": "catalog:",
"prudence": "catalog:",
"zod": "catalog:",
},
@@ -207,7 +203,6 @@
"@types/pg": "catalog:",
"@typescript/native-preview": "catalog:",
"eslint-config-tachi": "workspace:*",
"ts-node": "catalog:",
"typescript": "catalog:",
},
},
@@ -306,7 +301,6 @@
"devDependencies": {
"@typescript/native-preview": "catalog:",
"eslint-config-tachi": "workspace:*",
"tsconfig-paths": "catalog:",
},
},
"typescript/seeds-webui": {
@@ -320,10 +314,7 @@
"codemirror": "catalog:",
"comlink": "catalog:",
"fast-json-patch": "catalog:",
"nanoid": "catalog:",
"natural-compare": "catalog:",
"react": "catalog:",
"react-bootstrap": "catalog:",
"react-dom": "catalog:",
"react-query": "catalog:",
"react-router-dom": "catalog:",
@@ -331,18 +322,14 @@
"zod": "catalog:",
},
"devDependencies": {
"@types/natural-compare": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@types/react-router-dom": "catalog:",
"@typescript/native-preview": "catalog:",
"@vitejs/plugin-react": "catalog:",
"bootstrap-scss": "catalog:",
"eslint-config-tachi": "workspace:*",
"sass": "catalog:",
"typescript": "catalog:",
"vite": "catalog:",
"vite-plugin-html": "catalog:",
},
},
"typescript/server": {
@@ -352,26 +339,17 @@
"@aws-sdk/client-s3": "catalog:",
"@octokit/app": "catalog:",
"@types/bcryptjs": "catalog:",
"@types/bunyan": "catalog:",
"@types/connect-redis": "catalog:",
"@types/express": "catalog:",
"@types/express-rate-limit": "catalog:",
"@types/express-serve-static-core": "catalog:",
"@types/express-session": "catalog:",
"@types/mongodb": "catalog:",
"@types/multer": "catalog:",
"@types/node": "catalog:",
"@types/node-fetch": "catalog:",
"@types/nodemailer": "catalog:",
"@types/rate-limit-redis": "catalog:",
"@types/redis": "catalog:",
"bcryptjs": "catalog:",
"bliss": "workspace:*",
"bms-table-loader": "catalog:",
"bullmq": "catalog:",
"bunyan": "catalog:",
"commander": "catalog:",
"connect-redis": "catalog:",
"cron-parser": "catalog:",
"csv-parse": "catalog:",
"deepmerge": "catalog:",
@@ -379,7 +357,6 @@
"express": "catalog:",
"express-async-errors": "catalog:",
"express-prom-bundle": "catalog:",
"express-rate-limit": "catalog:",
"express-session": "catalog:",
"fast-json-stable-hash": "catalog:",
"fast-xml-parser": "catalog:",
@@ -387,8 +364,6 @@
"html-entities": "catalog:",
"kysely": "catalog:",
"lodash": "catalog:",
"mongodb": "catalog:",
"monk": "catalog:",
"multer": "catalog:",
"node-cache": "catalog:",
"node-fetch": "catalog:",
@@ -397,17 +372,15 @@
"pg": "catalog:",
"prom-client": "catalog:",
"prudence": "catalog:",
"rate-limit-redis": "catalog:",
"redis": "catalog:",
"rg-stats": "catalog:",
"tachi-common": "workspace:*",
"tachi-db": "workspace:*",
"tachi-db-migration-engine": "workspace:*",
"ts-node": "catalog:",
"typescript": "catalog:",
"zod": "catalog:",
},
"devDependencies": {
"@types/bun": "catalog:",
"@types/pg": "catalog:",
"@types/supertest": "catalog:",
"@typescript/native-preview": "catalog:",
@@ -415,7 +388,6 @@
"eslint-config-tachi": "workspace:*",
"node-mocks-http": "catalog:",
"supertest": "catalog:",
"tsconfig-paths": "catalog:",
"vitest": "catalog:",
},
},
@@ -438,18 +410,10 @@
},
},
},
"patchedDependencies": {
"monk@7.3.4": "patches/monk@7.3.4.patch",
"@types/mongodb@3.6.20": "patches/@types__mongodb@3.6.20.patch",
},
"overrides": {
"@types/bson": "1.0.11",
"@types/express": "4.17.13",
"@types/express-serve-static-core": "4.17.29",
"@types/mongodb": "3.6.20",
"@types/node": "20.19.37",
"mongodb": "3.6.3",
"ts-node": "10.9.1",
"typescript": "5.9.3",
},
"catalog": {
@@ -473,11 +437,9 @@
"@octokit/webhooks-types": "7.5.1",
"@sqlite.org/sqlite-wasm": "^3.51.2-build9",
"@types/bcryptjs": "^2.4.2",
"@types/bunyan": "^1.8.8",
"@types/connect-redis": "0.0.18",
"@types/bun": "1.3.14",
"@types/eslint__js": "8.42.3",
"@types/express": "4.17.13",
"@types/express-rate-limit": "5.1.3",
"@types/express-serve-static-core": "4.17.29",
"@types/express-session": "1.17.4",
"@types/history": "4.7.11",
@@ -486,20 +448,17 @@
"@types/lodash": "4.14.172",
"@types/lodash.get": "^4.4.7",
"@types/luxon": "^2.3.1",
"@types/mongodb": "3.6.20",
"@types/multer": "1.4.7",
"@types/natural-compare": "^1.4.1",
"@types/node": "*",
"@types/node-fetch": "2.5.12",
"@types/nodemailer": "^6.4.4",
"@types/pg": "^8.11.11",
"@types/rate-limit-redis": "1.7.4",
"@types/react": "17.0.13",
"@types/react-dom": "17.0.8",
"@types/react-google-recaptcha": "2.1.2",
"@types/react-router-bootstrap": "0.26.1",
"@types/react-router-dom": "5.1.7",
"@types/redis": "2.8.32",
"@types/supertest": "2.0.11",
"@types/tap": "^15.0.5",
"@typescript-eslint/eslint-plugin": "^4.27.0",
@@ -512,14 +471,11 @@
"binary-parser": "^2.3.0",
"bms-table-loader": "2.0.0",
"bootstrap-scss": "^5.3.0",
"bullmq": "1.68.4",
"bunyan": "1.8.15",
"chalk": "4",
"cheerio": "^1.0.0-rc.12",
"codemirror": "^6.0.2",
"comlink": "^4.4.2",
"commander": "^8.2.0",
"connect-redis": "6.1.1",
"cron-parser": "5.0.2",
"csv-parse": "^5.0.3",
"deepmerge": "^4.2.2",
@@ -539,7 +495,6 @@
"express": "4.20.0",
"express-async-errors": "3.1.1",
"express-prom-bundle": "^7.0.0",
"express-rate-limit": "5.5.1",
"express-session": "1.17.2",
"fast-json-patch": "^3.1.1",
"fast-json-stable-hash": "^1.0.3",
@@ -555,8 +510,6 @@
"lodash": "^4.17.23",
"lodash.get": "^4.4.2",
"luxon": "^2.3.1",
"mongodb": "3.6.3",
"monk": "^7.3.4",
"multer": "2.0.1",
"nanoid": "3.1.23",
"natural-compare": "^1.4.0",
@@ -573,7 +526,6 @@
"pretty-bytes": "5.6.0",
"prom-client": "15.1.3",
"prudence": "0.10.0",
"rate-limit-redis": "2.1.0",
"react": "17.0.2",
"react-async": "10.0.1",
"react-bootstrap": "2.9.0-beta.0",
@@ -585,14 +537,11 @@
"react-router-bootstrap": "^0.25.0",
"react-router-dom": "5.1.2",
"react-select": "^5.6.1",
"redis": "3.1.2",
"rg-stats": "workspace:*",
"sass": "^1.77.0",
"supertest": "6.2.2",
"sync-fetch": "^0.3.1",
"tap": "^15.1.6",
"ts-node": "^8.5.2",
"tsconfig-paths": "3.12.0",
"typescript": "^4.3.4",
"typescript-eslint": "^8.24.1",
"vite": "^8.0.0",
@@ -849,8 +798,6 @@
"@connectrpc/protoc-gen-connect-es": ["@connectrpc/protoc-gen-connect-es@1.7.0", "", { "dependencies": { "@bufbuild/protobuf": "^1.10.0", "@bufbuild/protoplugin": "^1.10.0" }, "peerDependencies": { "@bufbuild/protoc-gen-es": "^1.10.0", "@connectrpc/connect": "1.7.0" }, "optionalPeers": ["@bufbuild/protoc-gen-es", "@connectrpc/connect"], "bin": { "protoc-gen-connect-es": "bin/protoc-gen-connect-es" } }, "sha512-g2rE799dxGgXtwSTBOJoSlzCy3HN0IX/Es8uKsCgXRmco8o277/bb5nz1X8TmvBooCBGNdtEdUDG50olcvS9jQ=="],
"@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="],
"@discordjs/builders": ["@discordjs/builders@0.5.0", "", { "dependencies": { "@sindresorhus/is": "^4.0.1", "discord-api-types": "^0.22.0", "ow": "^0.27.0", "ts-mixer": "^6.0.0", "tslib": "^2.3.0" } }, "sha512-HP5y4Rqw68o61Qv4qM5tVmDbWi4mdTFftqIOGRo33SNPpLJ1Ga3KEIR2ibKofkmsoQhEpLmopD1AZDs3cKpHuw=="],
"@discordjs/collection": ["@discordjs/collection@0.1.6", "", {}, "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ=="],
@@ -981,8 +928,6 @@
"@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="],
"@ioredis/commands": ["@ioredis/commands@1.5.1", "", {}, "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw=="],
"@isaacs/cliui": ["@isaacs/cliui@8.0.2", "", { "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", "strip-ansi": "^7.0.1", "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" } }, "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA=="],
"@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="],
@@ -995,7 +940,7 @@
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@kristiandupont/recase": ["@kristiandupont/recase@1.4.1", "", { "dependencies": { "ramda": "^0.30.0" } }, "sha512-e5t4YqhnRGbS9sU4N52cQgTn37qKwTsxDDcIuIkgPvX0UmnL+7eoOR6oFXeCib5zYuw03vYKpR55NBq+W43j1A=="],
@@ -1007,18 +952,6 @@
"@marijn/find-cluster-break": ["@marijn/find-cluster-break@1.0.2", "", {}, "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g=="],
"@msgpackr-extract/msgpackr-extract-darwin-arm64": ["@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="],
"@msgpackr-extract/msgpackr-extract-darwin-x64": ["@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="],
"@msgpackr-extract/msgpackr-extract-linux-arm": ["@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3", "", { "os": "linux", "cpu": "arm" }, "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw=="],
"@msgpackr-extract/msgpackr-extract-linux-arm64": ["@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg=="],
"@msgpackr-extract/msgpackr-extract-linux-x64": ["@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg=="],
"@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="],
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.12", "", { "dependencies": { "@emnapi/core": "^1.4.3", "@emnapi/runtime": "^1.4.3", "@tybys/wasm-util": "^0.10.0" } }, "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ=="],
"@nivo/annotations": ["@nivo/annotations@0.79.1", "", { "dependencies": { "@nivo/colors": "0.79.1", "@react-spring/web": "9.3.1", "lodash": "^4.17.21" }, "peerDependencies": { "@nivo/core": "0.79.0", "react": ">= 16.14.0 < 18.0.0" } }, "sha512-lYso9Luu0maSDtIufwvyVt2+Wue7R9Fh3CIjuRDmNR72UjAgAVEcCar27Fy865UXGsj2hRJZ7KY/1s6kT3gu/w=="],
@@ -1257,14 +1190,6 @@
"@swc/helpers": ["@swc/helpers@0.5.19", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA=="],
"@tsconfig/node10": ["@tsconfig/node10@1.0.12", "", {}, "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ=="],
"@tsconfig/node12": ["@tsconfig/node12@1.0.11", "", {}, "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag=="],
"@tsconfig/node14": ["@tsconfig/node14@1.0.3", "", {}, "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow=="],
"@tsconfig/node16": ["@tsconfig/node16@1.0.4", "", {}, "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA=="],
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
"@types/aws-lambda": ["@types/aws-lambda@8.10.161", "", {}, "sha512-rUYdp+MQwSFocxIOcSsYSF3YYYC/uUpMbCY/mbO21vGqfrEYvNSoPyKYDj6RhXXpPfS0KstW9RwG3qXh9sL7FQ=="],
@@ -1273,16 +1198,12 @@
"@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="],
"@types/bson": ["@types/bson@1.0.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-j+UcCWI+FsbI5/FQP/Kj2CXyplWAz39ktHFkXk84h7dNblKRSoNJs95PZFRd96NQGqsPEPgeclqnznWZr14ZDA=="],
"@types/bunyan": ["@types/bunyan@1.8.11", "", { "dependencies": { "@types/node": "*" } }, "sha512-758fRH7umIMk5qt5ELmRMff4mLDlN+xyYzC+dkPTdKwbSkJFvz6xwyScrytPU0QIBbRRwbiE8/BIg8bpajerNQ=="],
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
"@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="],
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
"@types/connect-redis": ["@types/connect-redis@0.0.18", "", { "dependencies": { "@types/express": "*", "@types/express-session": "*", "@types/ioredis": "*", "@types/redis": "^2.8.0" } }, "sha512-iGygGbXgPIr94DEAuoluWhzre3c2/ew5NPlbW9IWvwCTXMM1YCmc7M9wpXMkYqt6kB9aO1sjZnmDzyugUu+2vQ=="],
"@types/cookiejar": ["@types/cookiejar@2.1.5", "", {}, "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q=="],
"@types/debug": ["@types/debug@4.1.12", "", { "dependencies": { "@types/ms": "*" } }, "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ=="],
@@ -1297,8 +1218,6 @@
"@types/express": ["@types/express@4.17.13", "", { "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.18", "@types/qs": "*", "@types/serve-static": "*" } }, "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA=="],
"@types/express-rate-limit": ["@types/express-rate-limit@5.1.3", "", { "dependencies": { "@types/express": "*" } }, "sha512-H+TYy3K53uPU2TqPGFYaiWc2xJV6+bIFkDd/Ma2/h67Pa6ARk9kWE0p/K9OH1Okm0et9Sfm66fmXoAxsH2PHXg=="],
"@types/express-serve-static-core": ["@types/express-serve-static-core@4.17.29", "", { "dependencies": { "@types/node": "*", "@types/qs": "*", "@types/range-parser": "*" } }, "sha512-uMd++6dMKS32EOuw1Uli3e3BPgdLIXmezcfHv7N4c1s3gkhikBplORPpMq3fuWkxncZN1reb16d5n8yhQ80x7Q=="],
"@types/express-session": ["@types/express-session@1.17.4", "", { "dependencies": { "@types/express": "*" } }, "sha512-7cNlSI8+oOBUHTfPXMwDxF/Lchx5aJ3ho7+p9jJZYVg9dVDJFh3qdMXmJtRsysnvS+C6x46k9DRYmrmCkE+MVg=="],
@@ -1311,8 +1230,6 @@
"@types/humanize-duration": ["@types/humanize-duration@3.27.4", "", {}, "sha512-yaf7kan2Sq0goxpbcwTQ+8E9RP6HutFBPv74T/IA/ojcHKhuKVlk2YFYyHhWZeLvZPzzLE3aatuQB4h0iqyyUA=="],
"@types/ioredis": ["@types/ioredis@5.0.0", "", { "dependencies": { "ioredis": "*" } }, "sha512-zJbJ3FVE17CNl5KXzdeSPtdltc4tMT3TzC6fxQS0sQngkbFZ6h+0uTafsRqu+eSLIugf6Yb0Ea0SUuRr42Nk9g=="],
"@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="],
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
@@ -1331,8 +1248,6 @@
"@types/methods": ["@types/methods@1.1.4", "", {}, "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ=="],
"@types/mongodb": ["@types/mongodb@3.6.20", "", { "dependencies": { "@types/bson": "*", "@types/node": "*" } }, "sha512-WcdpPJCakFzcWWD9juKoZbRtQxKIMYF/JIAM4JrNHrMcnJL6/a2NWjXxW7fo9hxboxxkg+icff8d7+WIEvKgYQ=="],
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
"@types/multer": ["@types/multer@1.4.7", "", { "dependencies": { "@types/express": "*" } }, "sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA=="],
@@ -1355,8 +1270,6 @@
"@types/range-parser": ["@types/range-parser@1.2.7", "", {}, "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ=="],
"@types/rate-limit-redis": ["@types/rate-limit-redis@1.7.4", "", { "dependencies": { "@types/express-rate-limit": "3", "@types/ioredis": "*", "@types/redis": "^2.8.0" } }, "sha512-TDsh9CJHHhx+sGHlqF6MI+PgEv91o/wQ67e2IR02ebYPzWQ8Rk0pcVY+WBy2pjxa+cAkPwjcOFDd21VToKaOzQ=="],
"@types/react": ["@types/react@17.0.13", "", { "dependencies": { "@types/prop-types": "*", "@types/scheduler": "*", "csstype": "^3.0.2" } }, "sha512-D/G3PiuqTfE3IMNjLn/DCp6umjVCSvtZTPdtAFy5+Ved6CsdRvivfKeCzw79W4AatShtU4nGqgvOv5Gro534vQ=="],
"@types/react-dom": ["@types/react-dom@17.0.8", "", { "dependencies": { "@types/react": "*" } }, "sha512-0ohAiJAx1DAUEcY9UopnfwCE9sSMDGnY/oXjWMax6g3RpzmTt2GMyMVAXcbn0mo8XAff0SbQJl2/SBU+hjSZ1A=="],
@@ -1371,8 +1284,6 @@
"@types/react-transition-group": ["@types/react-transition-group@4.4.12", "", { "peerDependencies": { "@types/react": "*" } }, "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w=="],
"@types/redis": ["@types/redis@2.8.32", "", { "dependencies": { "@types/node": "*" } }, "sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w=="],
"@types/scheduler": ["@types/scheduler@0.26.0", "", {}, "sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA=="],
"@types/serve-static": ["@types/serve-static@2.2.0", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*" } }, "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ=="],
@@ -1491,8 +1402,6 @@
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
"acorn-walk": ["acorn-walk@8.3.5", "", { "dependencies": { "acorn": "^8.11.0" } }, "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw=="],
"ajv": ["ajv@6.14.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw=="],
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
@@ -1501,8 +1410,6 @@
"append-field": ["append-field@1.0.0", "", {}, "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="],
"arg": ["arg@4.1.3", "", {}, "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
@@ -1571,7 +1478,7 @@
"bintrees": ["bintrees@1.0.2", "", {}, "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw=="],
"bl": ["bl@2.2.1", "", { "dependencies": { "readable-stream": "^2.3.5", "safe-buffer": "^5.1.1" } }, "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g=="],
"bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
"bliss": ["bliss@workspace:typescript/bliss"],
@@ -1591,15 +1498,11 @@
"broadcast-channel": ["broadcast-channel@3.7.0", "", { "dependencies": { "@babel/runtime": "^7.7.2", "detect-node": "^2.1.0", "js-sha3": "0.8.0", "microseconds": "0.2.0", "nano-time": "1.0.0", "oblivious-set": "1.0.0", "rimraf": "3.0.2", "unload": "2.2.0" } }, "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg=="],
"bson": ["bson@1.1.6", "", {}, "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg=="],
"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
"bullmq": ["bullmq@1.68.4", "", { "dependencies": { "cron-parser": "^2.18.0", "get-port": "^5.1.1", "glob": "^7.2.0", "ioredis": "^4.28.2", "lodash": "^4.17.21", "msgpackr": "^1.4.6", "semver": "^6.3.0", "tslib": "^1.14.1", "uuid": "^8.3.2" } }, "sha512-I0BYxhRocWHcNpzdXX5jR8DwKG1PkGJ8KUt9SJhWCCWd4sYvsMl6KV6J35ZfigGydXuAja19tbEefTUxdVv/sw=="],
"bunyan": ["bunyan@1.8.15", "", { "optionalDependencies": { "dtrace-provider": "~0.8", "moment": "^2.19.3", "mv": "~2", "safe-json-stringify": "~1" }, "bin": { "bunyan": "bin/bunyan" } }, "sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig=="],
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
"busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="],
@@ -1639,8 +1542,6 @@
"clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="],
"cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
"codemirror": ["codemirror@6.0.2", "", { "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/commands": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/lint": "^6.0.0", "@codemirror/search": "^6.0.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0" } }, "sha512-VhydHotNW5w1UGK0Qj96BwSk/Zqbp9WbnyK2W/eVMv4QyF41INRGpjUhFJY7/uDNuudSc33a/PKr4iDqRduvHw=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
@@ -1669,8 +1570,6 @@
"connect-history-api-fallback": ["connect-history-api-fallback@1.6.0", "", {}, "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg=="],
"connect-redis": ["connect-redis@6.1.1", "", {}, "sha512-4on27NmUz1hVCVsrUFUOe06YkgCx88ZZ29Wi7D5mXg22alN/Xx+Y+iPrJ1Cc7coUKAe5SP3W2LF/zo6WXSJFFg=="],
"consola": ["consola@2.15.3", "", {}, "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw=="],
"content-disposition": ["content-disposition@0.5.4", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ=="],
@@ -1689,8 +1588,6 @@
"cosmiconfig": ["cosmiconfig@7.1.0", "", { "dependencies": { "@types/parse-json": "^4.0.0", "import-fresh": "^3.2.1", "parse-json": "^5.0.0", "path-type": "^4.0.0", "yaml": "^1.10.0" } }, "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA=="],
"create-require": ["create-require@1.1.1", "", {}, "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ=="],
"crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="],
"cron-parser": ["cron-parser@5.0.2", "", { "dependencies": { "luxon": "^3.5.0" } }, "sha512-RXXr5WuvLInay/DstwRD087/DpLOm24Y3mrkRmOdrMjbdGfQyPiMsx1Ad+SaJF2zN0tN78ckBjcEkIHVd+MX7Q=="],
@@ -1749,8 +1646,6 @@
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
"defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="],
"define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
"define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
@@ -1759,8 +1654,6 @@
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"denque": ["denque@1.5.1", "", {}, "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw=="],
"depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
@@ -1773,7 +1666,7 @@
"dezalgo": ["dezalgo@1.0.4", "", { "dependencies": { "asap": "^2.0.0", "wrappy": "1" } }, "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig=="],
"diff": ["diff@4.0.4", "", {}, "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ=="],
"diff": ["diff@5.2.2", "", {}, "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A=="],
"dir-glob": ["dir-glob@3.0.1", "", { "dependencies": { "path-type": "^4.0.0" } }, "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA=="],
@@ -1801,8 +1694,6 @@
"dotenv-expand": ["dotenv-expand@8.0.3", "", {}, "sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg=="],
"dtrace-provider": ["dtrace-provider@0.8.8", "", { "dependencies": { "nan": "^2.14.0" } }, "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"duplexify": ["duplexify@3.7.1", "", { "dependencies": { "end-of-stream": "^1.0.0", "inherits": "^2.0.1", "readable-stream": "^2.0.0", "stream-shift": "^1.0.0" } }, "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g=="],
@@ -1957,8 +1848,6 @@
"express-prom-bundle": ["express-prom-bundle@7.0.2", "", { "dependencies": { "@types/express": "^4.17.21", "express": "^4.18.2", "on-finished": "^2.3.0", "url-value-parser": "^2.0.0" }, "peerDependencies": { "prom-client": ">=15.0.0" } }, "sha512-ffFV4HGHvCKnkNJFqm42sYztRJE5mLgOj8MpGey1HOatuFhtcwXoBD2m5gca7ZbcyjkIf7lOH5ZdrhlrBf0sGw=="],
"express-rate-limit": ["express-rate-limit@5.5.1", "", {}, "sha512-MTjE2eIbHv5DyfuFz4zLYWxpqVhEhkTiwFGuB74Q9CSou2WHO52nlE5y3Zlg6SIsiYUIPj6ifFxnkPz6O3sIUg=="],
"express-session": ["express-session@1.17.2", "", { "dependencies": { "cookie": "0.4.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "~2.0.0", "on-headers": "~1.0.2", "parseurl": "~1.3.3", "safe-buffer": "5.2.1", "uid-safe": "~2.1.5" } }, "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ=="],
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
@@ -2041,8 +1930,6 @@
"get-package-type": ["get-package-type@0.1.0", "", {}, "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="],
"get-port": ["get-port@5.1.1", "", {}, "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
"get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
@@ -2051,7 +1938,7 @@
"getopts": ["getopts@2.3.0", "", {}, "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA=="],
"glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
"glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
@@ -2129,8 +2016,6 @@
"invariant": ["invariant@2.2.4", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA=="],
"ioredis": ["ioredis@4.31.0", "", { "dependencies": { "@ioredis/commands": "^1.0.2", "cluster-key-slot": "^1.1.0", "debug": "^4.3.1", "denque": "^1.1.0", "lodash.defaults": "^4.2.0", "lodash.flatten": "^4.4.0", "lodash.isarguments": "^3.1.0", "p-map": "^2.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-tVrCrc4LWJwX82GD79dZ0teZQGq+5KJEGpXJRgzHOrhHtLgF9ME6rTwDV5+HN5bjnvmtrnS8ioXhflY16sy2HQ=="],
"ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="],
"is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
@@ -2167,8 +2052,6 @@
"is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="],
"is-nan": ["is-nan@1.3.2", "", { "dependencies": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" } }, "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w=="],
"is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="],
"is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
@@ -2299,14 +2182,8 @@
"lodash-es": ["lodash-es@4.17.23", "", {}, "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg=="],
"lodash.defaults": ["lodash.defaults@4.2.0", "", {}, "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="],
"lodash.flatten": ["lodash.flatten@4.4.0", "", {}, "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g=="],
"lodash.get": ["lodash.get@4.4.2", "", {}, "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ=="],
"lodash.isarguments": ["lodash.isarguments@3.1.0", "", {}, "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="],
"lodash.isequal": ["lodash.isequal@4.5.0", "", {}, "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ=="],
"lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
@@ -2327,8 +2204,6 @@
"make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="],
"make-error": ["make-error@1.3.6", "", {}, "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="],
"match-sorter": ["match-sorter@6.4.0", "", { "dependencies": { "@babel/runtime": "^7.23.8", "remove-accents": "0.5.0" } }, "sha512-d4664ahzdL1QTTvmK1iI0JsrxWeJ6gn33qkYtnPg3mcn+naBLtXSgSPOe+X2vUgtgGwaAk3eiaj7gwKjjMAq+Q=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
@@ -2347,8 +2222,6 @@
"memoize-one": ["memoize-one@6.0.0", "", {}, "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="],
"memory-pager": ["memory-pager@1.5.0", "", {}, "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="],
"merge-descriptors": ["merge-descriptors@1.0.3", "", {}, "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ=="],
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
@@ -2417,26 +2290,6 @@
"mkdirp": ["mkdirp@0.5.6", "", { "dependencies": { "minimist": "^1.2.6" }, "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw=="],
"moment": ["moment@2.30.1", "", {}, "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how=="],
"moment-timezone": ["moment-timezone@0.5.48", "", { "dependencies": { "moment": "^2.29.4" } }, "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw=="],
"mongodb": ["mongodb@3.6.3", "", { "dependencies": { "bl": "^2.2.1", "bson": "^1.1.4", "denque": "^1.4.1", "require_optional": "^1.0.1", "safe-buffer": "^5.1.2" }, "optionalDependencies": { "saslprep": "^1.0.0" } }, "sha512-rOZuR0QkodZiM+UbQE5kDsJykBqWi0CL4Ec2i1nrGrUI3KO11r6Fbxskqmq3JK2NH7aW4dcccBuUujAP0ERl5w=="],
"monk": ["monk@7.3.4", "", { "dependencies": { "@types/mongodb": "^3.5.25", "debug": "*", "mongodb": "^3.2.3", "monk-middleware-cast-ids": "^0.2.1", "monk-middleware-fields": "^0.2.0", "monk-middleware-handle-callback": "^0.2.0", "monk-middleware-options": "^0.2.1", "monk-middleware-query": "^0.2.0", "monk-middleware-wait-for-connection": "^0.2.0", "object-assign": "^4.1.1" } }, "sha512-PkPNiElwroVyKQj01usyziOvwiKYBUVSq7YU1FB4KFr0J3v0GeXW0TebYsLR4u33WB8JGqPiAcuzDspfdujqQg=="],
"monk-middleware-cast-ids": ["monk-middleware-cast-ids@0.2.1", "", {}, "sha512-Hp9gwjXo+inSPhXUlsV1u3DyZh2cXT3Osij/1gSNCJQCgacAF1lfmnTj6bKmlf1Et4dvLhwUL0PquxdZRIofkA=="],
"monk-middleware-fields": ["monk-middleware-fields@0.2.0", "", {}, "sha512-pp+k0JeBX+HkIKnCisrtsW/QM2WGkOVG1Bd1qKLSanuBCf1l5khpdHrEullfko0HO6tCjCJMp//MsR16gd5tAw=="],
"monk-middleware-handle-callback": ["monk-middleware-handle-callback@0.2.2", "", {}, "sha512-5hBynb7asZ2uw9XVze7C3XH0zXT51yFDvYydk/5HnWWzh2NLglDSiKDcX0yLKPHzFgiq+5Z4Laq5fFVnFsmm8w=="],
"monk-middleware-options": ["monk-middleware-options@0.2.1", "", {}, "sha512-ypE0wGC8n5ARvOq2tlERr1uyu1edckeDRkheQLaXPRm9LoW7kr9P7xMGEjW2vmBh7IxnkpONVc7555X8x+34hQ=="],
"monk-middleware-query": ["monk-middleware-query@0.2.0", "", {}, "sha512-BApd5HBiczJl1eVy65c9/NBVLtygBePFxuoHgar/LeMWCjWP9odSJk3vsnbxTnsOT1CMJ+hFysbaosLzDsxvtA=="],
"monk-middleware-wait-for-connection": ["monk-middleware-wait-for-connection@0.2.0", "", {}, "sha512-jSTz73B/+pGTTvhu5Ym8xsG6+QqaWab53UXnXdNNlTijTdLvcHABCLJXudQiJxob5N1Mzr5EOSx5ziwn2sihPQ=="],
"mqtt": ["mqtt@4.3.8", "", { "dependencies": { "commist": "^1.0.0", "concat-stream": "^2.0.0", "debug": "^4.1.1", "duplexify": "^4.1.1", "help-me": "^3.0.0", "inherits": "^2.0.3", "lru-cache": "^6.0.0", "minimist": "^1.2.5", "mqtt-packet": "^6.8.0", "number-allocator": "^1.0.9", "pump": "^3.0.0", "readable-stream": "^3.6.0", "reinterval": "^1.1.0", "rfdc": "^1.3.0", "split2": "^3.1.0", "ws": "^7.5.5", "xtend": "^4.0.2" }, "bin": { "mqtt": "bin/mqtt.js", "mqtt_pub": "bin/pub.js", "mqtt_sub": "bin/sub.js" } }, "sha512-2xT75uYa0kiPEF/PE0VPdavmEkoBzMT/UL9moid0rAvlCtV48qBwxD62m7Ld/4j8tSkIO1E/iqRl/S72SEOhOw=="],
"mqtt-packet": ["mqtt-packet@6.10.0", "", { "dependencies": { "bl": "^4.0.2", "debug": "^4.1.1", "process-nextick-args": "^2.0.1" } }, "sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA=="],
@@ -2445,16 +2298,8 @@
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"msgpackr": ["msgpackr@1.11.8", "", { "optionalDependencies": { "msgpackr-extract": "^3.0.2" } }, "sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA=="],
"msgpackr-extract": ["msgpackr-extract@3.0.3", "", { "dependencies": { "node-gyp-build-optional-packages": "5.2.2" }, "optionalDependencies": { "@msgpackr-extract/msgpackr-extract-darwin-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-darwin-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-arm64": "3.0.3", "@msgpackr-extract/msgpackr-extract-linux-x64": "3.0.3", "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.3" }, "bin": { "download-msgpackr-prebuilds": "bin/download-prebuilds.js" } }, "sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA=="],
"multer": ["multer@2.0.1", "", { "dependencies": { "append-field": "^1.0.0", "busboy": "^1.6.0", "concat-stream": "^2.0.0", "mkdirp": "^0.5.6", "object-assign": "^4.1.1", "type-is": "^1.6.18", "xtend": "^4.0.2" } }, "sha512-Ug8bXeTIUlxurg8xLTEskKShvcKDZALo1THEX5E41pYCD2sCVub5/kIRIGqWNoqV6szyLyQKV6mD4QUrWE5GCQ=="],
"mv": ["mv@2.1.1", "", { "dependencies": { "mkdirp": "~0.5.1", "ncp": "~2.0.0", "rimraf": "~2.4.0" } }, "sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg=="],
"nan": ["nan@2.25.0", "", {}, "sha512-0M90Ag7Xn5KMLLZ7zliPWP3rT90P6PN+IzVFS0VqmnPktBk3700xUVv8Ikm9EUaUE5SDWdp/BIxdENzVznpm1g=="],
"nano-time": ["nano-time@1.0.0", "", { "dependencies": { "big-integer": "^1.6.16" } }, "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA=="],
"nanoid": ["nanoid@3.1.23", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw=="],
@@ -2465,8 +2310,6 @@
"natural-compare-lite": ["natural-compare-lite@1.4.0", "", {}, "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g=="],
"ncp": ["ncp@2.0.0", "", { "bin": { "ncp": "./bin/ncp" } }, "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA=="],
"negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
"no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="],
@@ -2479,8 +2322,6 @@
"node-fetch": ["node-fetch@2.6.7", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ=="],
"node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],
"node-html-parser": ["node-html-parser@5.4.2", "", { "dependencies": { "css-select": "^4.2.1", "he": "1.2.0" } }, "sha512-RaBPP3+51hPne/OolXxcz89iYvQvKOydaqoePpOgXcrOKZhjVIzmpKZz+Hd/RBO2/zN2q6CNJhQzucVz+u3Jyw=="],
"node-mocks-http": ["node-mocks-http@1.11.0", "", { "dependencies": { "accepts": "^1.3.7", "content-disposition": "^0.5.3", "depd": "^1.1.0", "fresh": "^0.5.2", "merge-descriptors": "^1.0.1", "methods": "^1.1.2", "mime": "^1.3.4", "parseurl": "^1.3.3", "range-parser": "^1.2.0", "type-is": "^1.6.18" } }, "sha512-jS/WzSOcKbOeGrcgKbenZeNhxUNnP36Yw11+hL4TTxQXErGfqYZ+MaYNNvhaTiGIJlzNSqgQkk9j8dSu1YWSuw=="],
@@ -2527,8 +2368,6 @@
"p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
"p-map": ["p-map@2.1.0", "", {}, "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw=="],
"package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
"param-case": ["param-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A=="],
@@ -2651,8 +2490,6 @@
"range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
"rate-limit-redis": ["rate-limit-redis@2.1.0", "", { "dependencies": { "defaults": "^1.0.3", "redis": "^3.0.2" } }, "sha512-6SAsTCzY0v6UCIKLOLLYqR2XzFmgdtF7jWXlSPq2FrNIZk8tZ7xwBvyGW7GFMCe5I4S9lYNdrSJ9E84rz3/CpA=="],
"raw-body": ["raw-body@2.5.2", "", { "dependencies": { "bytes": "3.1.2", "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } }, "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA=="],
"react": ["react@17.0.2", "", { "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" } }, "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA=="],
@@ -2697,14 +2534,6 @@
"rechoir": ["rechoir@0.8.0", "", { "dependencies": { "resolve": "^1.20.0" } }, "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ=="],
"redis": ["redis@3.1.2", "", { "dependencies": { "denque": "^1.5.0", "redis-commands": "^1.7.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0" } }, "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw=="],
"redis-commands": ["redis-commands@1.7.0", "", {}, "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ=="],
"redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="],
"redis-parser": ["redis-parser@3.0.0", "", { "dependencies": { "redis-errors": "^1.0.0" } }, "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A=="],
"reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="],
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
@@ -2721,11 +2550,9 @@
"remove-accents": ["remove-accents@0.5.0", "", {}, "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A=="],
"require_optional": ["require_optional@1.0.1", "", { "dependencies": { "resolve-from": "^2.0.0", "semver": "^5.1.0" } }, "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g=="],
"resolve": ["resolve@2.0.0-next.6", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA=="],
"resolve-from": ["resolve-from@2.0.0", "", {}, "sha512-qpFcKaXsq8+oRoLilkwyc7zHGF5i9Q2/25NIgLQQ/+VVv9rU4qvr6nXVAw1DsnXJyQkZsR4Ytfbtg5ehfcUssQ=="],
"resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
"resolve-pathname": ["resolve-pathname@3.0.0", "", {}, "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng=="],
@@ -2751,8 +2578,6 @@
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
"safe-json-stringify": ["safe-json-stringify@1.2.0", "", {}, "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg=="],
"safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="],
"safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="],
@@ -2761,8 +2586,6 @@
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"saslprep": ["saslprep@1.0.3", "", { "dependencies": { "sparse-bitfield": "^3.0.3" } }, "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag=="],
"sass": ["sass@1.98.0", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.1.5", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-+4N/u9dZ4PrgzGgPlKnaaRQx64RO0JBKs9sDhQ2pLgN6JQZ25uPQZKQYaBJU48Kd5BxgXoJ4e09Dq7nMcOUW3A=="],
"scheduler": ["scheduler@0.20.2", "", { "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" } }, "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ=="],
@@ -2811,8 +2634,6 @@
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
"sparse-bitfield": ["sparse-bitfield@3.0.3", "", { "dependencies": { "memory-pager": "^1.0.2" } }, "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ=="],
"split2": ["split2@4.2.0", "", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="],
"stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="],
@@ -2821,8 +2642,6 @@
"stackback": ["stackback@0.0.2", "", {}, "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw=="],
"standard-as-callback": ["standard-as-callback@2.1.0", "", {}, "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="],
"statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="],
"std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
@@ -2949,9 +2768,7 @@
"ts-mixer": ["ts-mixer@6.0.4", "", {}, "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA=="],
"ts-node": ["ts-node@10.9.1", "", { "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", "@tsconfig/node12": "^1.0.7", "@tsconfig/node14": "^1.0.0", "@tsconfig/node16": "^1.0.2", "acorn": "^8.4.1", "acorn-walk": "^8.1.1", "arg": "^4.1.0", "create-require": "^1.1.0", "diff": "^4.0.1", "make-error": "^1.1.1", "v8-compile-cache-lib": "^3.0.1", "yn": "3.1.1" }, "peerDependencies": { "@swc/core": ">=1.2.50", "@swc/wasm": ">=1.2.50", "@types/node": "*", "typescript": ">=2.7" }, "optionalPeers": ["@swc/core", "@swc/wasm"], "bin": { "ts-node": "dist/bin.js", "ts-script": "dist/bin-script-deprecated.js", "ts-node-cwd": "dist/bin-cwd.js", "ts-node-esm": "dist/bin-esm.js", "ts-node-script": "dist/bin-script.js", "ts-node-transpile-only": "dist/bin-transpile.js" } }, "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw=="],
"tsconfig-paths": ["tsconfig-paths@3.12.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.1", "minimist": "^1.2.0", "strip-bom": "^3.0.0" } }, "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg=="],
"tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
@@ -3029,8 +2846,6 @@
"uvu": ["uvu@0.5.6", "", { "dependencies": { "dequal": "^2.0.0", "diff": "^5.0.0", "kleur": "^4.0.3", "sade": "^1.7.3" }, "bin": { "uvu": "bin.js" } }, "sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA=="],
"v8-compile-cache-lib": ["v8-compile-cache-lib@3.0.1", "", {}, "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="],
"vali-date": ["vali-date@1.0.0", "", {}, "sha512-sgECfZthyaCKW10N0fm27cg8HYTFK5qMWgypqkXMQ4Wbl/zZKx7xZICgcoxIIE+WFAP/MBL2EFwC/YvLxw3Zeg=="],
"value-equal": ["value-equal@1.0.1", "", {}, "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw=="],
@@ -3089,14 +2904,10 @@
"yaml": ["yaml@1.10.2", "", {}, "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg=="],
"yn": ["yn@3.1.1", "", {}, "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q=="],
"yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@ampproject/remapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@aws-crypto/crc32/@aws-sdk/types": ["@aws-sdk/types@3.973.5", "", { "dependencies": { "@smithy/types": "^4.13.0", "tslib": "^2.6.2" } }, "sha512-hl7BGwDCWsjH8NkZfx+HgS7H2LyM2lTMAI7ba9c8O0KqdBLTdNJivsHpqjg9rNlAlPyREb6DeDRXUl0s8uFdmQ=="],
"@aws-crypto/crc32/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
@@ -3121,8 +2932,6 @@
"@babel/code-frame/js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"@babel/generator/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@connectrpc/protoc-gen-connect-es/@bufbuild/protobuf": ["@bufbuild/protobuf@1.10.1", "", {}, "sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ=="],
"@connectrpc/protoc-gen-connect-es/@bufbuild/protoplugin": ["@bufbuild/protoplugin@1.10.1", "", { "dependencies": { "@bufbuild/protobuf": "1.10.1", "@typescript/vfs": "^1.4.0", "typescript": "4.5.2" } }, "sha512-LaSbfwabAFIvbVnbn8jWwElRoffCIxhVraO8arliVwWupWezHLXgqPHEYLXZY/SsAR+/YsFBQJa8tAGtNPJyaQ=="],
@@ -3145,10 +2954,6 @@
"@isaacs/cliui/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
"@jridgewell/gen-mapping/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@jridgewell/source-map/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@kristiandupont/recase/ramda": ["ramda@0.30.1", "", {}, "sha512-tEF5I22zJnuclswcZMc8bDIrwRHRzf+NqVEmqg50ShAZMP7MWeR/RGDthfM/p+BlqvF2fXAzpn8i+SJcYD3alw=="],
"@octokit/request/content-type": ["content-type@2.0.0", "", {}, "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ=="],
@@ -3163,8 +2968,6 @@
"@rollup/pluginutils/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"@types/rate-limit-redis/@types/express-rate-limit": ["@types/express-rate-limit@3.3.5", "", { "dependencies": { "@types/express": "*" } }, "sha512-v6M28DcbdATYvzTN5lFJhN2EP6BgY8MbTskcKJ/m5bmGuzFKHfbEVXF3po8U8ivOyyeT5o6HV0janPlFBBN5yg=="],
"@types/superagent/form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
"@typescript-eslint/experimental-utils/eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="],
@@ -3185,8 +2988,6 @@
"@vitest/mocker/vite": ["vite@3.2.11", "", { "dependencies": { "esbuild": "^0.15.9", "postcss": "^8.4.18", "resolve": "^1.22.1", "rollup": "^2.79.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@types/node": ">= 14", "less": "*", "sass": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "sass", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ=="],
"ast-v8-to-istanbul/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"aws-crt/@aws-sdk/util-utf8-browser": ["@aws-sdk/util-utf8-browser@3.259.0", "", { "dependencies": { "tslib": "^2.3.1" } }, "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw=="],
"aws-crt/buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
@@ -3195,8 +2996,6 @@
"babel-plugin-macros/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
"bl/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="],
"bms-table-loader/node-fetch": ["node-fetch@2.6.2", "", {}, "sha512-aLoxToI6RfZ+0NOjmWAgn9+LEd30YCkJKFSyWacNZdEKTit/ZMcKjGkTRo8uWEsnIb/hfKecNPEbln02PdWbcA=="],
"bms-table-loader/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
@@ -3209,12 +3008,6 @@
"broadcast-channel/rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
"bullmq/cron-parser": ["cron-parser@2.18.0", "", { "dependencies": { "is-nan": "^1.3.0", "moment-timezone": "^0.5.31" } }, "sha512-s4odpheTyydAbTBQepsqd2rNWGa2iV3cyo8g7zbI2QQYGLVsfbhmwukayS1XHppe02Oy1fg7mg6xoaraVJeEcg=="],
"bullmq/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"bullmq/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
"cheerio-select/css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
"cron-parser/luxon": ["luxon@3.7.2", "", {}, "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew=="],
@@ -3227,8 +3020,6 @@
"d3-time-format/d3-time": ["d3-time@2.1.1", "", { "dependencies": { "d3-array": "2" } }, "sha512-/eIQe/eR4kCQwq7yxi7z4c6qEXf2IYGcjoWB5OOQy4Tq9Uv39/947qlDcN2TLkiTzQWzvnsuYPB9TrWaNfipKQ=="],
"defaults/clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="],
"discord.js/@discordjs/builders": ["@discordjs/builders@0.11.0", "", { "dependencies": { "@sindresorhus/is": "^4.2.0", "discord-api-types": "^0.26.0", "ts-mixer": "^6.0.0", "tslib": "^2.3.1", "zod": "^3.11.6" } }, "sha512-ZTB8yJdJKrKlq44dpWkNUrAtEJEq0gqpb7ASdv4vmq6/mZal5kOv312hQ56I/vxwMre+VIkoHquNUAfnTbiYtg=="],
"discord.js/@discordjs/collection": ["@discordjs/collection@0.4.0", "", {}, "sha512-zmjq+l/rV35kE6zRrwe8BHqV78JvIh2ybJeZavBi5NySjWXqN3hmmAKg7kYMMXSeiWtSsMoZ/+MQi0DiQWy2lw=="],
@@ -3251,8 +3042,6 @@
"eslint-plugin-import/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"eslint-plugin-import/tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
"eslint-plugin-import-x/@typescript-eslint/types": ["@typescript-eslint/types@8.56.1", "", {}, "sha512-dbMkdIUkIkchgGDIv7KLUpa0Mda4IYjo4IAMJUZ+3xNoUXxMsk9YtKpTHSChRS85o+H9ftm51gsK1dZReY9CVw=="],
"eslint-plugin-import-x/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
@@ -3289,14 +3078,12 @@
"formik/tslib": ["tslib@1.14.1", "", {}, "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="],
"glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
"hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
"htmlparser2/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
"import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
"istanbul-lib-source-maps/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"knex/colorette": ["colorette@2.0.19", "", {}, "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ=="],
"knex/commander": ["commander@10.0.1", "", {}, "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug=="],
@@ -3321,10 +3108,6 @@
"mqtt/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
"mqtt-packet/bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="],
"mv/rimraf": ["rimraf@2.4.5", "", { "dependencies": { "glob": "^6.0.1" }, "bin": { "rimraf": "./bin.js" } }, "sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ=="],
"node-exports-info/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"node-mocks-http/depd": ["depd@1.1.2", "", {}, "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ=="],
@@ -3345,10 +3128,6 @@
"rechoir/resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
"require_optional/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="],
"rimraf/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
"rolldown/@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.9", "", {}, "sha512-w6oiRWgEBl04QkFZgmW+jnU1EC9b57Oihi2ot3HNWIQRqgHp5PnYDia5iZ5FF7rpa4EQdiqMDXjlqKGXBhsoXw=="],
"send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
@@ -3385,8 +3164,6 @@
"typescript-eslint/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.56.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.56.1", "@typescript-eslint/tsconfig-utils": "8.56.1", "@typescript-eslint/types": "8.56.1", "@typescript-eslint/visitor-keys": "8.56.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qzUL1qgalIvKWAf9C1HpvBjif+Vm6rcT5wZd4VoMb9+Km3iS3Cv9DY6dMRMDtPnwRAFyAi7YXJpTIEXLvdfPxg=="],
"uvu/diff": ["diff@5.2.2", "", {}, "sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A=="],
"vite-node/vite": ["vite@7.3.1", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
"vite-plugin-html/pathe": ["pathe@0.2.0", "", {}, "sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw=="],
@@ -3431,14 +3208,10 @@
"@vitest/mocker/vite/rollup": ["rollup@2.80.0", "", { "optionalDependencies": { "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-cIFJOD1DESzpjOBl763Kp1AH7UE/0fcdHe6rZXUdQ9c50uvgigvW97u3IcSeBwOkgqL/PXPBktBCh0KEu5L8XQ=="],
"bl/readable-stream/isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
"bl/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
"bl/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
"body-parser/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"broadcast-channel/rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"css-select/domutils/dom-serializer": ["dom-serializer@1.4.1", "", { "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.2.0", "entities": "^2.0.0" } }, "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag=="],
"discord.js/@discordjs/builders/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
@@ -3461,14 +3234,14 @@
"finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"glob/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
"knex/debug/ms": ["ms@2.1.2", "", {}, "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="],
"mv/rimraf/glob": ["glob@6.0.4", "", { "dependencies": { "inflight": "^1.0.4", "inherits": "2", "minimatch": "2 || 3", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A=="],
"mqtt/help-me/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
"react-router/path-to-regexp/isarray": ["isarray@0.0.1", "", {}, "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ=="],
"rimraf/glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
"send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"serve-static/send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
@@ -3515,7 +3288,7 @@
"eslint-plugin-import-x/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"rimraf/glob/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
"glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"serve-static/send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
@@ -3541,8 +3314,6 @@
"@typescript-eslint/utils/@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"rimraf/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"typescript-eslint/@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
}
}
-4
View File
@@ -1,4 +0,0 @@
storage:
wiredTiger:
engineConfig:
cacheSizeGB: 2
-1
View File
@@ -1,6 +1,5 @@
#!/bin/fish
# add a local bin for ts-node
fish_add_path /tachi/node_modules/.bin
source ./dev/fish-plugins/fisher/functions/fisher.fish
+1 -13
View File
@@ -1,15 +1,4 @@
services:
tachi-mongo:
container_name: tachi-mongo
command: "--config /etc/mongod.conf"
image: mongo:5
restart: unless-stopped
ports:
- "27017:27017"
volumes:
- tachi-volume:/data/db
- "./dev/mongod.conf:/etc/mongod.conf"
tachi-redis:
container_name: tachi-redis
command: redis-server --save 60 1 --loglevel warning
@@ -55,7 +44,7 @@ services:
# Dedicated test Postgres. Lives on tmpfs with durability disabled - every
# `CREATE DATABASE ... TEMPLATE`, every `TRUNCATE`, every commit is ~5-20x
# faster than the dev DB. Wiped on container restart, which is exactly what
# we want for tests. Use POSTGRES_TEST_HOST in vitest setup to point at it.
# we want for tests.
tachi-postgres-test:
container_name: tachi-postgres-test
image: postgres:18
@@ -216,7 +205,6 @@ services:
- ./observability/alloy-config.alloy:/etc/alloy/config.alloy
volumes:
tachi-volume:
tachi-redis:
tachi-logs:
tachi-postgres:
+5 -42
View File
@@ -9,7 +9,9 @@
],
"tsMachete": {
"excludeGlobal": [
"@types/bun",
"@typescript/native-preview",
"@vitest/coverage-v8",
"typescript"
],
"exclude": {
@@ -18,33 +20,16 @@
"bootstrap-scss",
"sass"
],
"tachi-bot": [
"ts-node",
"tsconfig-paths",
"@vitest/coverage-v8"
],
"tachi-db": [
"@types/express",
"@types/node",
"@types/node-fetch",
"@vitest/coverage-v8"
"@types/node-fetch"
],
"eslint-config-tachi": [
"@typescript-eslint/eslint-plugin",
"@typescript-eslint/parser",
"eslint-import-resolver-typescript",
"@types/eslint__js"
],
"tachi-seeds-scripts": [
"tsconfig-paths"
],
"tachi-server": [
"ts-node",
"tsconfig-paths",
"@vitest/coverage-v8"
],
"tachi-db-migration-engine": [
"ts-node"
]
}
},
@@ -58,7 +43,6 @@
"eslint": "^9.20.1",
"kanel": "^3.16.1",
"kanel-kysely": "^0.8.0",
"ts-node": "10.9.1",
"typescript": "5.9.3"
},
"catalog": {
@@ -78,11 +62,9 @@
"@octokit/app": "^16.1.2",
"@octokit/webhooks-types": "7.5.1",
"@types/bcryptjs": "^2.4.2",
"@types/bunyan": "^1.8.8",
"@types/connect-redis": "0.0.18",
"@types/bun": "1.3.14",
"@types/eslint__js": "8.42.3",
"@types/express": "4.17.13",
"@types/express-rate-limit": "5.1.3",
"@types/express-serve-static-core": "4.17.29",
"@types/express-session": "1.17.4",
"@types/history": "4.7.11",
@@ -90,20 +72,17 @@
"@types/lodash": "4.14.172",
"@types/lodash.get": "^4.4.7",
"@types/luxon": "^2.3.1",
"@types/mongodb": "3.6.20",
"@types/multer": "1.4.7",
"@types/natural-compare": "^1.4.1",
"@types/node": "*",
"@types/node-fetch": "2.5.12",
"@types/nodemailer": "^6.4.4",
"@types/pg": "^8.11.11",
"@types/rate-limit-redis": "1.7.4",
"@types/react": "17.0.13",
"@types/react-dom": "17.0.8",
"@types/react-google-recaptcha": "2.1.2",
"@types/react-router-bootstrap": "0.26.1",
"@types/react-router-dom": "5.1.7",
"@types/redis": "2.8.32",
"@types/supertest": "2.0.11",
"@typescript-eslint/eslint-plugin": "^4.27.0",
"@typescript-eslint/parser": "^4.27.0",
@@ -115,12 +94,9 @@
"binary-parser": "^2.3.0",
"bms-table-loader": "2.0.0",
"bootstrap-scss": "^5.3.0",
"bullmq": "1.68.4",
"bunyan": "1.8.15",
"chalk": "4",
"cheerio": "^1.0.0-rc.12",
"commander": "^8.2.0",
"connect-redis": "6.1.1",
"csv-parse": "^5.0.3",
"deepmerge": "^4.2.2",
"discord-api-types": "0.22.0",
@@ -139,7 +115,6 @@
"express": "4.20.0",
"express-async-errors": "3.1.1",
"express-prom-bundle": "^7.0.0",
"express-rate-limit": "5.5.1",
"express-session": "1.17.2",
"fast-json-stable-hash": "^1.0.3",
"fast-xml-parser": "^4.2.5",
@@ -153,8 +128,6 @@
"lodash": "^4.17.23",
"lodash.get": "^4.4.2",
"luxon": "^2.3.1",
"mongodb": "3.6.3",
"monk": "^7.3.4",
"multer": "2.0.1",
"nanoid": "3.1.23",
"natural-compare": "^1.4.0",
@@ -171,7 +144,6 @@
"pretty-bytes": "5.6.0",
"prom-client": "15.1.3",
"prudence": "0.10.0",
"rate-limit-redis": "2.1.0",
"react": "17.0.2",
"react-async": "10.0.1",
"react-bootstrap": "2.9.0-beta.0",
@@ -183,13 +155,10 @@
"react-router-bootstrap": "^0.25.0",
"react-router-dom": "5.1.2",
"react-select": "^5.6.1",
"redis": "3.1.2",
"rg-stats": "workspace:*",
"sass": "^1.77.0",
"supertest": "6.2.2",
"sync-fetch": "^0.3.1",
"ts-node": "^8.5.2",
"tsconfig-paths": "3.12.0",
"typescript": "^4.3.4",
"typescript-eslint": "^8.24.1",
"vite": "^8.0.0",
@@ -210,19 +179,13 @@
"cron-parser": "5.0.2"
},
"overrides": {
"@types/mongodb": "3.6.20",
"@types/bson": "1.0.11",
"@types/node": "20.19.37",
"@types/express-serve-static-core": "4.17.29",
"@types/express": "4.17.13",
"ts-node": "10.9.1",
"mongodb": "3.6.3",
"typescript": "5.9.3"
},
"patchedDependencies": {
"@types/mongodb@3.6.20": "patches/@types__mongodb@3.6.20.patch",
"browserslist@4.21.4": "patches/browserslist@4.21.4.patch",
"monk@7.3.4": "patches/monk@7.3.4.patch"
"browserslist@4.21.4": "patches/browserslist@4.21.4.patch"
},
"engines": {
"bun": ">=1.2.0"
-87
View File
@@ -1,87 +0,0 @@
diff --git a/index.d.ts b/index.d.ts
index d37f518dd645945193de473874cd1fecc4f68d11..ad40dcf6b2ca944043572ea63b4b7a9c46ca79cb 100755
--- a/index.d.ts
+++ b/index.d.ts
@@ -1193,11 +1193,11 @@ export class Db extends EventEmitter {
* @returns Promise if no callback is passed
* @see https://mongodb.github.io/node-mongodb-native/3.6/api/Db.html#collection
*/
- collection<TSchema = DefaultSchema>(
+ collection<TSchema extends { [key: string]: any } = DefaultSchema>(
name: string,
callback?: MongoCallback<Collection<TSchema>>,
): Collection<TSchema>;
- collection<TSchema = DefaultSchema>(
+ collection<TSchema extends { [key: string]: any } = DefaultSchema>(
name: string,
options: DbCollectionOptions,
callback?: MongoCallback<Collection<TSchema>>,
@@ -1241,12 +1241,12 @@ export class Db extends EventEmitter {
* @returns Promise if no callback is passed
* @see https://mongodb.github.io/node-mongodb-native/3.6/api/Db.html#createCollection
*/
- createCollection<TSchema = DefaultSchema>(name: string, callback: MongoCallback<Collection<TSchema>>): void;
- createCollection<TSchema = DefaultSchema>(
+ createCollection<TSchema extends { [key: string]: any } = DefaultSchema>(name: string, callback: MongoCallback<Collection<TSchema>>): void;
+ createCollection<TSchema extends { [key: string]: any } = DefaultSchema>(
name: string,
options?: CollectionCreateOptions,
): Promise<Collection<TSchema>>;
- createCollection<TSchema = DefaultSchema>(
+ createCollection<TSchema extends { [key: string]: any } = DefaultSchema>(
name: string,
options: CollectionCreateOptions,
callback: MongoCallback<Collection<TSchema>>,
@@ -1378,17 +1378,17 @@ export class Db extends EventEmitter {
* @returns Promise if no callback is passed
* @see https://mongodb.github.io/node-mongodb-native/3.6/api/Db.html#renameCollection
*/
- renameCollection<TSchema = DefaultSchema>(
+ renameCollection<TSchema extends { [key: string]: any } = DefaultSchema>(
fromCollection: string,
toCollection: string,
callback: MongoCallback<Collection<TSchema>>,
): void;
- renameCollection<TSchema = DefaultSchema>(
+ renameCollection<TSchema extends { [key: string]: any } = DefaultSchema>(
fromCollection: string,
toCollection: string,
options?: { dropTarget?: boolean | undefined },
): Promise<Collection<TSchema>>;
- renameCollection<TSchema = DefaultSchema>(
+ renameCollection<TSchema extends { [key: string]: any } = DefaultSchema>(
fromCollection: string,
toCollection: string,
options: { dropTarget?: boolean | undefined },
@@ -2553,11 +2553,11 @@ export interface Collection<TSchema extends { [key: string]: any } = DefaultSche
* @param options Optional settings
* @see https://mongodb.github.io/node-mongodb-native/3.6/api/Collection.html#watch
*/
- watch<T = TSchema>(
+ watch<T extends { [key: string]: any } = TSchema>(
pipeline?: object[],
options?: ChangeStreamOptions & { session?: ClientSession | undefined },
): ChangeStream<T>;
- watch<T = TSchema>(options?: ChangeStreamOptions & { session?: ClientSession | undefined }): ChangeStream<T>;
+ watch<T extends { [key: string]: any } = TSchema>(options?: ChangeStreamOptions & { session?: ClientSession | undefined }): ChangeStream<T>;
}
/** Update Query */
@@ -2906,7 +2906,7 @@ export type FilterQuery<T> = {
RootQuerySelector<T>;
/** @see https://docs.mongodb.com/v3.6/reference/method/db.collection.bulkWrite/#insertone */
-export type BulkWriteInsertOneOperation<TSchema> = {
+export type BulkWriteInsertOneOperation<TSchema extends { [key: string]: any }> = {
insertOne: {
document: OptionalId<TSchema>;
};
@@ -2982,7 +2982,7 @@ export type BulkWriteDeleteManyOperation<TSchema> = {
*
* @see https://mongodb.github.io/node-mongodb-native/3.6/api/Collection.html#bulkWrite
*/
-export type BulkWriteOperation<TSchema> =
+export type BulkWriteOperation<TSchema extends { [key: string]: any }> =
| BulkWriteInsertOneOperation<TSchema>
| BulkWriteUpdateOneOperation<TSchema>
| BulkWriteUpdateManyOperation<TSchema>
-15
View File
@@ -1,15 +0,0 @@
diff --git a/index.d.ts b/index.d.ts
index e5c4acb9f8defb48045a54119a6500ff1a81d4b7..a3c9deedb1d9929085302e69bd0505ec1fd97788 100644
--- a/index.d.ts
+++ b/index.d.ts
@@ -101,8 +101,8 @@ declare module "monk" {
readonly close: () => Promise<void>;
readonly listCollections: (query?: Object) => Promise<Array<ICollection>>;
- get<T = any>(name: string, options?: CollectionOptions): ICollection<T>;
- create<T = any>(
+ get<T extends { [key: string]: any } = any>(name: string, options?: CollectionOptions): ICollection<T>;
+ create<T extends { [key: string]: any } = any>(
name: string,
creationOption?: CollectionCreateOptions,
options?: CollectionOptions
-2
View File
@@ -42,8 +42,6 @@
"prudence": "catalog:",
"tachi-common": "workspace:*",
"tachi-db": "workspace:*",
"ts-node": "catalog:",
"tsconfig-paths": "catalog:",
"typescript": "catalog:"
},
"engines": {
@@ -19,7 +19,6 @@ import {
type FolderDocument,
FormatDifficultySearch,
GameToGameGroup,
GetGameConfig,
GetGameGroupConfig,
type SongDocument,
type V3Game,
@@ -49,8 +48,6 @@ export default function ChartInfoFormat({
return <Loading />;
}
const versions = Object.keys(GetGameConfig(game).versions);
return (
<Row
className="text-center align-items-center"
@@ -64,12 +61,6 @@ export default function ChartInfoFormat({
{data.length !== 0 ? (
data
.sort((a, b) => a.title.localeCompare(b.title))
.sort((a, b) =>
"versions" in a.data && "versions" in b.data
? versions.indexOf(a.data.versions) -
versions.indexOf(b.data.versions)
: 0,
)
.map((e) => (
<li key={e.slug}>
{user && ugs ? (
@@ -1,7 +1,6 @@
import Divider from "#components/util/Divider";
import Muted from "#components/util/Muted";
import { type CellsRenderFN } from "#types/seeds";
import { FlattenValue, StringifyKeyChain } from "#util/misc";
import { StrSOV } from "#util/sorts";
import { type SearchFunctions } from "#util/ztable/search";
import React from "react";
@@ -44,13 +43,7 @@ export const SeedsFolderSearchFns: SearchFunctions<FolderDocument> = {
query: (x) => {
const w = (x as { where?: string }).where;
if (typeof w === "string" && w.length > 0) {
return w;
}
return FlattenValue(x.data)
.map((e) => `${StringifyKeyChain(e.keychain)} ${e.value}`)
.join("\n");
return typeof w === "string" ? w : "";
},
};
@@ -87,16 +80,7 @@ export const SeedsFolderCells: CellsRenderFN<FolderDocument> = ({
TYPE: <b>{folderSeedKindLabel(data)}</b>
<Divider />
<div className="text-start">
{showWhere ? (
<code className="text-break">{whereSql}</code>
) : (
FlattenValue(data.data).map((e, i) => (
<React.Fragment key={i}>
{StringifyKeyChain(e.keychain)} = {String(e.value)}
<br />
</React.Fragment>
))
)}
{showWhere && <code className="text-break">{whereSql}</code>}
</div>
</td>
</>
+1 -3
View File
@@ -36,7 +36,6 @@
"author": "zk",
"license": "MIT",
"dependencies": {
"@types/mongodb": "catalog:",
"prudence": "catalog:",
"zod": "catalog:"
},
@@ -57,8 +56,7 @@
"src/*/!(test-utils)/**/*.ts"
],
"exclude": [
"src/**/*.test.ts",
"src/external/mongo/schemas.ts"
"src/**/*.test.ts"
]
}
}
-14
View File
@@ -347,23 +347,9 @@ const PRE_SCHEMAS = {
folders: prSchemaFnWrap({
title: "string",
game: p.isIn(games),
playtype: isValidPlaytype,
folderID: "string",
inactive: "boolean",
searchTerms: ["string"],
type: p.isIn("songs", "charts", "static"),
data: (self, parent) => {
if (parent.type === "songs") {
return true; // temp. should be a song.
} else if (parent.type === "charts") {
return true; // temp. should be a chart.
}
return (
(Array.isArray(self) && self.every((r) => typeof r === "string")) ||
"Expected an array of only strings."
);
},
}),
tables: prSchemaFnWrap({
tableID: "string",
+1 -18
View File
@@ -1,5 +1,3 @@
import type { FilterQuery } from "mongodb";
import type {
AnyClasses,
ChartDocumentData,
@@ -397,22 +395,7 @@ export interface BaseFolderDocument {
searchTerms: Array<string>;
}
export interface FolderSongsDocument extends BaseFolderDocument {
type: "songs";
data: FilterQuery<SongDocument>;
}
export interface FolderChartsDocument extends BaseFolderDocument {
type: "charts";
data: FilterQuery<ChartDocument>;
}
export interface FolderStaticDocument extends BaseFolderDocument {
type: "static";
data: Array<string>;
}
export type FolderDocument = FolderChartsDocument | FolderSongsDocument | FolderStaticDocument;
export type FolderDocument = BaseFolderDocument;
export interface FolderChartLookup {
chartID: string;
@@ -28,7 +28,6 @@
"@types/node": "catalog:",
"@types/pg": "catalog:",
"eslint-config-tachi": "workspace:*",
"ts-node": "catalog:",
"typescript": "catalog:"
},
"engines": {
-51
View File
@@ -1,51 +0,0 @@
// THIS SCRIPT IS NOT FOR IMPORTING TO AN INSTANCE OF TACHI.
// THAT PROCESS IS *SIGNIFICANTLY* MORE COMPLEX, AND IS PART
// OF TACHI-SERVER.
// This script is for importing the collections *immediately* into
// any old mongo instance.
import { execSync } from "child_process";
import { Command } from "commander";
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
import { log as logger } from "./log.ts";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const program = new Command();
program.option("-d, --db <Database Name>");
program.parse(process.argv);
const options = program.opts();
if (!options.db) {
throw new Error(`Must provide database name with -d or --db.`);
}
const base = path.join(__dirname, "../../db/seeds");
for (const file of fs.readdirSync(base)) {
const filename = path.parse(file).name;
const fileLoc = path.join(base, file);
const command = `mongoimport -c=${filename} --jsonArray ${fileLoc} --db=${options.db}`;
const data = fs.readFileSync(fileLoc, "utf-8");
if (data === "[]") {
logger.info(`Skipping collection ${file} as the data is empty.`);
continue;
}
logger.info(`Running ${command}.`);
try {
execSync(command, { stdio: "inherit" });
} catch (err) {
logger.error(`Failed to import ${file}.`, { command, err });
}
}
+1 -2
View File
@@ -17,8 +17,7 @@
"license": "MIT",
"devDependencies": {
"@typescript/native-preview": "catalog:",
"eslint-config-tachi": "workspace:*",
"tsconfig-paths": "catalog:"
"eslint-config-tachi": "workspace:*"
},
"dependencies": {
"@types/lodash.get": "catalog:",
@@ -1,68 +0,0 @@
import fs from "fs";
import path from "path";
import { ALL_GAMES, GAME_GROUP_CONFIGS } from "tachi-common";
import { ReadCollection, WRITE_COLLECTION_SKIP_BIOME, WriteCollection } from "../../util";
const COLLECTIONS_DIR = path.join(__dirname, "../../../../db/seeds");
// Games where the Postgres game name equals the game group name (single playtype).
// These files are left as-is.
const SINGLE_PT_GAMES = new Set([
"arcaea",
"chunithm",
"jubeat",
"maimai",
"maimaidx",
"museca",
"ongeki",
"popn",
"sdvx",
"wacca",
]);
for (const game of Object.keys(GAME_GROUP_CONFIGS)) {
if (!fs.existsSync(path.join(COLLECTIONS_DIR, `charts-${game}.json`))) {
console.log(`charts-${game}.json: does not exist, skipping`);
continue;
}
if (SINGLE_PT_GAMES.has(game)) {
console.log(`charts-${game}: single playtype, skipping`);
continue;
}
const data = ReadCollection(`charts-${game}.json`);
const byPlaytype = new Map();
for (const entry of data) {
const pt = entry.playtype.toLowerCase();
if (!byPlaytype.has(pt)) {
byPlaytype.set(pt, []);
}
byPlaytype.get(pt).push(entry);
}
for (const [pt, charts] of byPlaytype) {
const newName = `charts-${game}-${pt}.json`;
WriteCollection(newName, charts, WRITE_COLLECTION_SKIP_BIOME);
console.log(`${newName}: wrote ${charts.length} charts`);
}
fs.unlinkSync(path.join(COLLECTIONS_DIR, `charts-${game}.json`));
console.log(`charts-${game}.json: deleted`);
}
// now, for each collection remove the playtype field.
for (const nugame of ALL_GAMES) {
const data = ReadCollection(`charts-${nugame}.json`);
for (const entry of data) {
delete entry.playtype;
}
WriteCollection(`charts-${nugame}.json`, data, WRITE_COLLECTION_SKIP_BIOME);
console.log(`charts-${nugame}.json: removed playtype field`);
}
@@ -1,194 +0,0 @@
import fs from "fs";
import path from "path";
import { ALL_GAMES, GAME_GROUP_CONFIGS, GameToGameGroup } from "tachi-common";
import {
CreateChartID,
CreateSongID,
ReadCollection,
WRITE_COLLECTION_SKIP_BIOME,
WriteCollection,
} from "../../util";
// ── Stability map ─────────────────────────────────────────────────────────────
// Persists legacy→new ID mappings so re-runs produce the same IDs.
const STABILITY_MAP_PATH = path.join(__dirname, "stability-map.json");
interface StabilityMap {
songs: Record<string, string>;
charts: Record<string, string>;
folders: Record<string, string>;
tables: Record<string, string>;
goals: Record<string, string>;
}
function readStabilityMap(): StabilityMap {
if (fs.existsSync(STABILITY_MAP_PATH)) {
const raw = JSON.parse(
fs.readFileSync(STABILITY_MAP_PATH, "utf-8"),
) as Partial<StabilityMap>;
return {
songs: raw.songs ?? {},
charts: raw.charts ?? {},
folders: raw.folders ?? {},
tables: raw.tables ?? {},
goals: raw.goals ?? {},
};
}
return { songs: {}, charts: {}, folders: {}, tables: {}, goals: {} };
}
function writeStabilityMap(map: StabilityMap) {
fs.writeFileSync(STABILITY_MAP_PATH, JSON.stringify(map, null, "\t"));
}
const stabilityMap = readStabilityMap();
/**
* Charts whose pre-remap mongo integer `songID` falls here reference songs absent from rollback
* (BMS)—TODO remove band once upstream collections include them.
*
* Canonical list supplied to this repo was every id inclusive from 97678 through 97928.
*/
const SKIP_CHART_ORPHAN_SONG_MONGO_MIN = 97678;
const SKIP_CHART_ORPHAN_SONG_MONGO_MAX = 97928;
function chartSkippedForOrphanRollbackSongMongoId(songFk: unknown): boolean {
return (
typeof songFk === "number" &&
songFk >= SKIP_CHART_ORPHAN_SONG_MONGO_MIN &&
songFk <= SKIP_CHART_ORPHAN_SONG_MONGO_MAX
);
}
// ── Pass 1: songs ─────────────────────────────────────────────────────────────
//
// Writes each song row's final `id` (hex song id).
//
const songIdMap = new Map<string, string>(); // "game:<mongoSongInt>" → hex song id (from id or legacySongID)
const songLegacyIdMap = new Map(); // "game:hexSongId" → integer mongo song id
for (const gameGroup of Object.keys(GAME_GROUP_CONFIGS)) {
const collection = `songs-${gameGroup}.json`;
const data = ReadCollection(collection);
let modified = 0;
for (const entry of data) {
const stabilityKey = `${gameGroup}:${entry.id}`;
const savedId = stabilityMap.songs[stabilityKey];
const nextSongHexId =
savedId ?? CreateSongID(Date.now() - 1000000 + (entry.legacySongID ?? entry.id));
if (entry.id !== undefined && typeof entry.id === "number") {
entry.legacySongID = entry.id;
songIdMap.set(stabilityKey, nextSongHexId);
songLegacyIdMap.set(`${gameGroup}:${nextSongHexId}`, entry.id);
stabilityMap.songs[stabilityKey] = nextSongHexId;
}
entry.id = nextSongHexId;
delete entry.sid;
if (typeof entry.legacySongID === "number") {
songIdMap.set(`${gameGroup}:${entry.legacySongID}`, entry.id);
}
modified++;
}
if (modified > 0) {
WriteCollection(collection, data, WRITE_COLLECTION_SKIP_BIOME);
console.log(`${collection}: migrated ${modified} entries`);
} else {
console.log(`${collection}: already migrated, skipped`);
}
}
// ── Pass 2: charts ────────────────────────────────────────────────────────────
//
// Each chart row gains `id` (hex chart id) and retains `songID` as FK → parent song.
// Charts have many‑to‑one songs: `songID` is normalized from mongo ints to hex song ids.
//
for (const game of ALL_GAMES) {
const gameGroup = GameToGameGroup(game);
const collection = `charts-${game}.json`;
const rawCharts = ReadCollection(collection);
let droppedHardcoded = 0;
const data = rawCharts.filter((entry: Record<string, unknown>) => {
const songFk = entry.songID;
if (chartSkippedForOrphanRollbackSongMongoId(songFk)) {
droppedHardcoded++;
return false;
}
return true;
});
if (droppedHardcoded > 0) {
console.warn(
`${collection}: omitted ${String(droppedHardcoded)} chart(s) (mongo songID in [${String(SKIP_CHART_ORPHAN_SONG_MONGO_MIN)}, ${String(SKIP_CHART_ORPHAN_SONG_MONGO_MAX)}] — rollback gap)`,
);
}
let modified = 0;
for (const entry of data) {
// Stability map keyed by mongo chart hash; after migrate it mirrors legacyChartID.
const mongoChartStableKey =
typeof entry.chartID === "string" && entry.chartID !== ""
? entry.chartID
: typeof entry.legacyChartID === "string" && entry.legacyChartID !== ""
? entry.legacyChartID
: undefined;
const savedHexChartId =
mongoChartStableKey !== undefined
? stabilityMap.charts[mongoChartStableKey]
: undefined;
const retainedHexChartId =
mongoChartStableKey === undefined && typeof entry.id === "string" && entry.id !== ""
? entry.id
: undefined;
const nextChartHexId =
retainedHexChartId ??
savedHexChartId ??
CreateChartID(Date.now() - 1000000 + (entry.songID ?? entry.legacySongID));
if (mongoChartStableKey !== undefined) {
stabilityMap.charts[mongoChartStableKey] = nextChartHexId;
}
if (typeof entry.chartID === "string" && entry.chartID !== "") {
entry.legacyChartID = entry.chartID;
delete entry.chartID;
}
const newSongFkHexId = songIdMap.get(`${gameGroup}:${entry.songID}`);
if (newSongFkHexId === undefined) {
throw new Error(
`${collection}: chart (mongo chart hash=${mongoChartStableKey ?? "none"}, chart hex provisional=${nextChartHexId}) ` +
`— parent song FK songID=${JSON.stringify(entry.songID)} → no song with that mongo integer id under songs-${gameGroup}.json ` +
`(add the song to the collections mirror, fix songID on the chart, or remove the orphaned chart row).`,
);
}
entry.songID = newSongFkHexId;
entry.id = nextChartHexId;
delete entry.sid;
modified++;
}
if (modified > 0 || droppedHardcoded > 0) {
WriteCollection(collection, data, WRITE_COLLECTION_SKIP_BIOME);
console.log(`${collection}: migrated ${modified} entries`);
} else {
console.log(`${collection}: already migrated, skipped`);
}
}
writeStabilityMap(stabilityMap);
console.log(
`stability-map.json: saved ${Object.keys(stabilityMap.songs).length} songs, ${Object.keys(stabilityMap.charts).length} charts`,
);
@@ -1,128 +0,0 @@
/**
* Legacy Mongo → v3 hex ids for folders/tables. Also run `3-migrate-goals-quests-questlines-game.ts`
* in the same pass for goal/quest/questline `game` strings. After this, run `4-tablefolders-to-object.ts`,
* `5-folders-to-sql-queries.ts`, then `6-tables-folder-refs-to-slugs.ts` so `tables.json`
* references folders by slug.
*/
import fs from "fs";
import path from "path";
import {
type GameGroup,
LEGACY_GetGPTString,
LEGACY_GPTStringToGame,
type LEGACY_Playtype,
} from "tachi-common";
import {
CreateFolderID,
CreateTableID,
ReadCollection,
WRITE_COLLECTION_SKIP_BIOME,
WriteCollection,
} from "../../util";
// ── Stability map ─────────────────────────────────────────────────────────────
const STABILITY_MAP_PATH = path.join(__dirname, "stability-map.json");
interface StabilityMap {
songs: Record<string, string>;
charts: Record<string, string>;
folders: Record<string, string>;
tables: Record<string, string>;
goals: Record<string, string>;
}
function readStabilityMap(): StabilityMap {
if (fs.existsSync(STABILITY_MAP_PATH)) {
const raw = JSON.parse(
fs.readFileSync(STABILITY_MAP_PATH, "utf-8"),
) as Partial<StabilityMap>;
return {
songs: raw.songs ?? {},
charts: raw.charts ?? {},
folders: raw.folders ?? {},
tables: raw.tables ?? {},
goals: raw.goals ?? {},
};
}
return { songs: {}, charts: {}, folders: {}, tables: {}, goals: {} };
}
function writeStabilityMap(map: StabilityMap): void {
fs.writeFileSync(STABILITY_MAP_PATH, JSON.stringify(map, null, "\t"));
}
const stabilityMap = readStabilityMap();
// ── Pass 1: folders ───────────────────────────────────────────────────────────
// Build a map of old folderID → new hex id so tables can update their refs.
const folderIdMap = new Map<string, string>(); // oldFolderID → new hex id
const folders = ReadCollection("folders.json");
for (const entry of folders) {
if (!entry.id) {
entry.id = stabilityMap.folders[entry.folderID] ?? CreateFolderID();
}
if (entry.folderID) {
entry.legacyFolderID = entry.folderID;
folderIdMap.set(entry.folderID, entry.id);
stabilityMap.folders[entry.folderID] = entry.id;
delete entry.folderID;
}
entry.game = LEGACY_GPTStringToGame(
LEGACY_GetGPTString(entry.game as GameGroup, entry.playtype as LEGACY_Playtype),
);
delete entry.playtype;
}
WriteCollection("folders.json", folders, WRITE_COLLECTION_SKIP_BIOME);
console.log(`folders.json: migrated ${folders.length} entries`);
// ── Pass 2: tables ────────────────────────────────────────────────────────────
const tables = ReadCollection("tables.json");
for (const entry of tables) {
if (!entry.id) {
entry.id = stabilityMap.tables[entry.tableID] ?? CreateTableID();
}
if (entry.tableID) {
entry.legacyTableID = entry.tableID;
stabilityMap.tables[entry.tableID] = entry.id;
delete entry.tableID;
}
entry.game = LEGACY_GPTStringToGame(
LEGACY_GetGPTString(entry.game as GameGroup, entry.playtype as LEGACY_Playtype),
);
delete entry.playtype;
// Rewrite folder references to new hex ids.
entry.folders = entry.folders.map((oldId: string) => {
const newId = folderIdMap.get(oldId);
if (newId === undefined) {
throw new Error(`Table "${entry.title}": unknown folder reference ${oldId}`);
}
return newId;
});
}
WriteCollection("tables.json", tables, WRITE_COLLECTION_SKIP_BIOME);
console.log(`tables.json: migrated ${tables.length} entries`);
writeStabilityMap(stabilityMap);
console.log(
`stability-map.json: saved ${Object.keys(stabilityMap.folders).length} folders, ${
Object.keys(stabilityMap.tables).length
} tables`,
);
@@ -1,110 +0,0 @@
/**
* Normalise `game` on `goals.json`, `quests.json`, and `questlines.json` to canonical {@link V3Game}
* strings (aligned with Folder/Table migration in `3-migrate-folders-tables.ts`).
*
* Accepted shapes:
* - Mongo-era **game group** + **playtype** → {@link LEGACY_GetGPTString} + {@link LEGACY_GPTStringToGame}.
* - Already a **V3Game** string (passthrough).
* - Colon **GPT strings** (`sdvx:Single`, …) → {@link LEGACY_GPTStringToGame}.
*
* Strips stale `playtype` when consumed or when `game` is already a V3 string. Runs before
* `7-remap-goals-folder-and-chart-ids.ts` so goalID hashes use the corrected game.
*/
import {
ALL_GAMES,
type GameGroup,
LEGACY_GetGPTString,
type LEGACY_GPTString,
LEGACY_GPTStringToGame,
type LEGACY_Playtype,
type V3Game,
} from "tachi-common";
import { ReadCollection, WRITE_COLLECTION_SKIP_BIOME, WriteCollection } from "../../util";
function isV3GameString(s: string): s is V3Game {
return (ALL_GAMES as readonly string[]).includes(s);
}
function migrateSeedDocGame(entry: Record<string, unknown>, label: string): boolean {
const rawGame = entry.game;
if (typeof rawGame !== "string") {
throw new Error(`${label}: expected string game, got ${JSON.stringify(rawGame)}`);
}
let changed = false;
const hadPlaytypeKey = Object.hasOwn(entry, "playtype");
const ptRaw = entry.playtype;
const nonemptyPt =
ptRaw !== undefined && ptRaw !== null && !(typeof ptRaw === "string" && ptRaw === "");
let v3: V3Game;
if (nonemptyPt) {
v3 = LEGACY_GPTStringToGame(
LEGACY_GetGPTString(rawGame as GameGroup, String(ptRaw) as LEGACY_Playtype),
);
delete entry.playtype;
changed = rawGame !== v3 || hadPlaytypeKey;
} else if (isV3GameString(rawGame)) {
v3 = rawGame;
if (hadPlaytypeKey) {
delete entry.playtype;
changed = true;
}
} else {
const fromGptColon = LEGACY_GPTStringToGame(rawGame as LEGACY_GPTString);
if (fromGptColon !== undefined && isV3GameString(fromGptColon)) {
v3 = fromGptColon;
changed = rawGame !== v3 || hadPlaytypeKey;
} else {
throw new Error(
`${label}: could not migrate game ${JSON.stringify(rawGame)} to a V3Game ` +
`(expected V3 string, GPT string such as 'sdvx:Single', or game group plus playtype)`,
);
}
if (hadPlaytypeKey) {
delete entry.playtype;
}
}
entry.game = v3;
return changed;
}
function migrateCollection(name: string, rowLabelPrefix: string): number {
let changedRows = 0;
const rows = ReadCollection(name) as Array<Record<string, unknown>>;
for (let i = 0; i < rows.length; i++) {
const row = rows[i]!;
const human =
rowLabelPrefix +
String(row.name ?? row.questID ?? row.questlineID ?? `index ${String(i)}`);
if (migrateSeedDocGame(row, human)) {
changedRows++;
}
}
WriteCollection(name, rows, WRITE_COLLECTION_SKIP_BIOME);
return changedRows;
}
const goalsChanged = migrateCollection("goals.json", "goals.json ");
const questsChanged = migrateCollection("quests.json", "quests.json ");
const questlinesChanged = migrateCollection("questlines.json", "questlines.json ");
console.log(`goals.json: normalised game on ${String(goalsChanged)} goals`);
console.log(`quests.json: normalised game on ${String(questsChanged)} quests`);
console.log(`questlines.json: normalised game on ${String(questlinesChanged)} questlines`);
console.log(
`done: ${String(goalsChanged + questsChanged + questlinesChanged)} total entries updated`,
);
@@ -1,179 +0,0 @@
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
import { ReadCollection, WRITE_COLLECTION_SKIP_BIOME, WriteCollection } from "../../util";
/**
* BMS / PMS / USC chart `data.tableFolders`: migrate from Mongo-style
* `[{ table, level }, …]` to `{ [table]: level }` so folder SQL can use
* `chart.data->'tableFolders'->>'…'` (see `5-folders-to-sql-queries.ts`).
*
* Must run **before** `5-folders-to-sql-queries.ts`.
*/
const TABLEFOLDERS_OBJECT_CHART_COLLECTIONS = [
"charts-bms-7k.json",
"charts-bms-14k.json",
"charts-pms-controller.json",
"charts-pms-keyboard.json",
"charts-usc-controller.json",
"charts-usc-keyboard.json",
] as const;
/** Folder `game` values that use object-shaped `chart.data.tableFolders` after this script. */
const TABLEFOLDERS_OBJECT_GAMES = new Set([
"bms-7k",
"bms-14k",
"pms-controller",
"pms-keyboard",
"usc-controller",
"usc-keyboard",
]);
function sqlStr(value: string): string {
return `'${value.replace(/'/gu, "''")}'`;
}
/**
* `jsonb_array_elements` does not work on JSON objects - rewrite legacy elemMatch SQL
* to key lookup (must match `5-folders-to-sql-queries.ts`).
*/
function rewriteFolderWhereTableFoldersElemExists(where: string): string | null {
const pattern =
/^EXISTS \(SELECT 1 FROM jsonb_array_elements\(chart\.data->'tableFolders'\) AS elem WHERE elem->>'level' = '((?:''|[^'])*)' AND elem->>'table' = '((?:''|[^'])*)'\)$/u;
const m = where.match(pattern);
if (!m) {
return null;
}
const level = m[1].replace(/''/gu, "'");
const table = m[2].replace(/''/gu, "'");
return `(chart.data->'tableFolders'->>${sqlStr(table)}) = ${sqlStr(level)}`;
}
function tableFoldersArrayToObject(arr: unknown, chartId: string): Record<string, string> {
if (!Array.isArray(arr)) {
throw new Error(`chart ${chartId}: expected tableFolders array, got ${typeof arr}`);
}
const out: Record<string, string> = {};
for (const row of arr) {
if (row === null || typeof row !== "object") {
continue;
}
const r = row as Record<string, unknown>;
if (!("table" in r) || !("level" in r)) {
throw new Error(
`chart ${chartId}: tableFolders row must have table and level: ${JSON.stringify(row)}`,
);
}
const table = String(r.table);
const level = r.level;
const levelStr =
typeof level === "string" || typeof level === "number" || typeof level === "boolean"
? String(level)
: JSON.stringify(level);
if (Object.hasOwn(out, table) && out[table] !== levelStr) {
throw new Error(
`chart ${chartId}: duplicate tableFolders table ${JSON.stringify(table)} with conflicting levels (${JSON.stringify(out[table])} vs ${JSON.stringify(levelStr)})`,
);
}
out[table] = levelStr;
}
return out;
}
function transformTableFolders(data: Record<string, unknown>, chartId: string): boolean {
const tf = data.tableFolders;
if (tf === undefined) {
return false;
}
if (tf !== null && typeof tf === "object" && !Array.isArray(tf)) {
return false;
}
if (Array.isArray(tf)) {
data.tableFolders = tableFoldersArrayToObject(tf, chartId);
return true;
}
throw new Error(`chart ${chartId}: expected tableFolders array or object, got ${typeof tf}`);
}
const __dirname = path.dirname(fileURLToPath(import.meta.url));
for (const name of TABLEFOLDERS_OBJECT_CHART_COLLECTIONS) {
const data = ReadCollection(name) as Array<Record<string, unknown>>;
let modified = 0;
for (const entry of data) {
const chartId = String(entry.id ?? "?");
const dataObj = entry.data as Record<string, unknown> | undefined;
if (!dataObj || typeof dataObj !== "object") {
continue;
}
if (transformTableFolders(dataObj, chartId)) {
modified++;
}
}
if (modified > 0) {
WriteCollection(name, data, WRITE_COLLECTION_SKIP_BIOME);
console.log(`${name}: converted tableFolders on ${modified} charts`);
} else {
console.log(`${name}: no tableFolders arrays to convert`);
}
}
// ── folders.json: WHERE clauses that still use jsonb_array_elements on tableFolders ──
const FOLDERS_JSON = path.join(__dirname, "../../../../db/seeds/folders.json");
const folders = JSON.parse(fs.readFileSync(FOLDERS_JSON, "utf-8")) as Array<
Record<string, unknown>
>;
let foldersRewritten = 0;
for (const entry of folders) {
const game = entry.game;
if (typeof game !== "string" || !TABLEFOLDERS_OBJECT_GAMES.has(game)) {
continue;
}
const w = entry.where;
if (typeof w !== "string") {
continue;
}
const next = rewriteFolderWhereTableFoldersElemExists(w);
if (next !== null) {
entry.where = next;
foldersRewritten++;
}
}
if (foldersRewritten > 0) {
fs.writeFileSync(FOLDERS_JSON, `${JSON.stringify(folders, null, "\t")}\n`);
console.log(
`folders.json: rewrote ${foldersRewritten} tableFolders WHERE clauses (array elem → object key lookup)`,
);
} else {
console.log(`folders.json: no tableFolders array-style WHERE clauses to rewrite`);
}
@@ -1,454 +0,0 @@
import fs from "fs";
import path from "path";
import { computeFolderSlug, type SeedFolderRow } from "tachi-common";
import { fileURLToPath } from "url";
/**
* Converts legacy Mongo-style folder `data` into SQL `where` strings, then assigns
* `slug` per {@link computeFolderSlug} (required for Postgres `folder.slug`).
*
* After this, run `6-tables-folder-refs-to-slugs.ts` so `tables.json` lists folder
* slugs instead of ids (see `importSeeds` in the server).
*/
// ── Helpers ───────────────────────────────────────────────────────────────────
function sqlStr(value: string): string {
// Escape single quotes for SQL string literals.
return `'${value.replace(/'/gu, "''")}'`;
}
const RANGE_OPS: Record<string, string> = {
"~gte": ">=",
"~gt": ">",
"~lt": "<",
"~lte": "<=",
};
/**
* Range / inequality on `chart.data->>'field'` or `song.data->>'field'`.
* `->>` returns text; compare to numbers with `::numeric` or Postgres errors (e.g. text >= int).
*/
function rangeConditions(expr: string, ops: Record<string, unknown>): Array<string> {
const conds: Array<string> = [];
for (const [op, val] of Object.entries(ops)) {
if (op === "~in") {
if (!Array.isArray(val)) {
throw new Error(`~in value must be an array, got: ${JSON.stringify(val)}`);
}
const list = val.map((v: unknown) => sqlStr(String(v))).join(", ");
conds.push(`${expr} IN (${list})`);
continue;
}
if (op === "~not") {
if (typeof val !== "object" || val === null) {
throw new Error(
`~not value must be an operator object, got: ${JSON.stringify(val)}`,
);
}
const inner = rangeConditions(expr, val as Record<string, unknown>);
conds.push(`NOT (${inner.join(" AND ")})`);
continue;
}
const sqlOp = RANGE_OPS[op];
if (!sqlOp) {
throw new Error(`Unknown operator: ${op}`);
}
if (typeof val === "number") {
conds.push(`${expr} ${sqlOp} ${val}`);
} else {
conds.push(`${expr} ${sqlOp} ${sqlStr(String(val))}`);
}
}
return conds;
}
function isOperatorObject(v: unknown): v is Record<string, unknown> {
return (
typeof v === "object" &&
v !== null &&
!Array.isArray(v) &&
Object.keys(v as object).some((k) => k.startsWith("~"))
);
}
/**
* JSONB text extraction `expr` / numeric `numExpr` (same path, `::numeric`).
* Used for `data->>'field'`, `elem->>'k'`, and `tableFolders` key lookups.
*/
function rangeConditionsJsonFieldExpr(
textExpr: string,
numExpr: string,
ops: Record<string, unknown>,
): Array<string> {
const conds: Array<string> = [];
for (const [op, val] of Object.entries(ops)) {
if (op === "~in") {
if (!Array.isArray(val)) {
throw new Error(`~in value must be an array, got: ${JSON.stringify(val)}`);
}
const list = val.map((v: unknown) => sqlStr(String(v))).join(", ");
conds.push(`${textExpr} IN (${list})`);
continue;
}
if (op === "~not") {
if (typeof val !== "object" || val === null) {
throw new Error(
`~not value must be an operator object, got: ${JSON.stringify(val)}`,
);
}
const inner = rangeConditionsJsonFieldExpr(
textExpr,
numExpr,
val as Record<string, unknown>,
);
conds.push(`NOT (${inner.join(" AND ")})`);
continue;
}
const sqlOp = RANGE_OPS[op];
if (!sqlOp) {
throw new Error(`Unknown operator: ${op}`);
}
if (typeof val === "number") {
conds.push(`${numExpr} ${sqlOp} ${val}`);
} else {
conds.push(`${numExpr} ${sqlOp} ${sqlStr(String(val))}`);
}
}
return conds;
}
/**
* JSONB `data->>'field'` is text. For numeric comparisons, use `::numeric` on the LHS.
* `~in` stays as text equality to string literals.
*/
function rangeConditionsJsonField(
tableAlias: "chart" | "song",
field: string,
ops: Record<string, unknown>,
): Array<string> {
const alias = tableAlias === "chart" ? "chart" : "s";
const textExpr = `${alias}.data->>'${field}'`;
const numExpr = `(${alias}.data->>'${field}')::numeric`;
return rangeConditionsJsonFieldExpr(textExpr, numExpr, ops);
}
/** `elem->>'field'` conditions; supports `~in` / range ops on a match field (e.g. USC `level`). */
function elemMatchFieldSql(field: string, v: unknown): Array<string> {
const expr = `elem->>'${field}'`;
const numExpr = `(elem->>'${field}')::numeric`;
if (isOperatorObject(v)) {
return rangeConditionsJsonFieldExpr(expr, numExpr, v);
}
return [`${expr} = ${sqlStr(String(v))}`];
}
// ── Chart filter ──────────────────────────────────────────────────────────────
const CHART_FIELD_MAP: Record<string, string> = {
level: "chart.level",
levelNum: "chart.level_num",
difficulty: "chart.difficulty",
isPrimary: "chart.is_primary",
};
/** Chart `data.tableFolders` is object-shaped for these games after `4-tablefolders-to-object.ts`. */
const TABLEFOLDERS_OBJECT_GAMES = new Set([
"bms-7k",
"bms-14k",
"pms-controller",
"pms-keyboard",
"usc-controller",
"usc-keyboard",
]);
interface ChartFilterResult {
where: string;
versionFilter: Array<string> | undefined;
}
function convertChartFilter(data: Record<string, unknown>, folderGame: string): ChartFilterResult {
const conds: Array<string> = [];
let versionFilter: Array<string> | undefined;
for (const [key, val] of Object.entries(data)) {
// ── versions → extracted to versionFilter, not inlined into where ────
if (key === "versions") {
if (typeof val === "string") {
versionFilter = [val];
} else if (Array.isArray(val)) {
versionFilter = val.map(String);
} else {
throw new Error(`Unexpected value for "versions": ${JSON.stringify(val)}`);
}
continue;
}
// ── data¬field (path into JSONB data column) ──────────────────────────
if (key.includes("\u00ac")) {
const [, field] = key.split("\u00ac");
if (!field) {
throw new Error(`Malformed data¬ key: ${key}`);
}
// elemMatch → object key lookup for tableFolders (see 4-tablefolders-to-object.ts),
// else EXISTS over jsonb_array_elements.
if (typeof val === "object" && val !== null && "~elemMatch" in val) {
const match = (val as Record<string, unknown>)["~elemMatch"];
if (typeof match !== "object" || match === null) {
throw new Error(
`~elemMatch value must be an object, got: ${JSON.stringify(match)}`,
);
}
const m = match as Record<string, unknown>;
if (
field === "tableFolders" &&
TABLEFOLDERS_OBJECT_GAMES.has(folderGame) &&
"table" in m &&
"level" in m
) {
const tableKey =
typeof m.table === "string" ||
typeof m.table === "number" ||
typeof m.table === "boolean"
? String(m.table)
: JSON.stringify(m.table);
const textExpr = `(chart.data->'tableFolders'->>${sqlStr(tableKey)})`;
const numExpr = `(${textExpr})::numeric`;
const lvl = m.level;
if (isOperatorObject(lvl)) {
conds.push(
...rangeConditionsJsonFieldExpr(
textExpr,
numExpr,
lvl as Record<string, unknown>,
),
);
} else if (typeof lvl === "object" && lvl !== null) {
throw new Error(
`tableFolders ~elemMatch level must be a scalar or operator object (~in, …), got: ${JSON.stringify(lvl)}`,
);
} else {
conds.push(`${textExpr} = ${sqlStr(String(lvl))}`);
}
continue;
}
const elemConds = Object.entries(m).flatMap(([k, v]) => elemMatchFieldSql(k, v));
conds.push(
`EXISTS (SELECT 1 FROM jsonb_array_elements(chart.data->'${field}') AS elem WHERE ${elemConds.join(
" AND ",
)})`,
);
continue;
}
// Meta-table folder: scalar means "this table key exists" on object-shaped tableFolders.
// (`->>'tableFolders'` would compare the whole JSON text, never a single table name.)
if (
field === "tableFolders" &&
TABLEFOLDERS_OBJECT_GAMES.has(folderGame) &&
(typeof val === "string" || typeof val === "number" || typeof val === "boolean")
) {
conds.push(`(chart.data->'tableFolders') ? ${sqlStr(String(val))}`);
continue;
}
// Range / equality on a JSONB scalar (text extract → cast for numeric compares)
if (typeof val === "object" && val !== null) {
conds.push(
...rangeConditionsJsonField("chart", field, val as Record<string, unknown>),
);
} else if (typeof val === "number") {
conds.push(`(chart.data->>'${field}')::numeric = ${val}`);
} else {
conds.push(`chart.data->>'${field}' = ${sqlStr(String(val))}`);
}
continue;
}
// ── mapped top-level chart columns ────────────────────────────────────
const col = CHART_FIELD_MAP[key];
if (col) {
if (typeof val === "object" && val !== null) {
conds.push(...rangeConditions(col, val as Record<string, unknown>));
} else if (typeof val === "boolean") {
conds.push(`${col} = ${val}`);
} else if (typeof val === "number") {
conds.push(`${col} = ${val}`);
} else {
conds.push(`${col} = ${sqlStr(String(val))}`);
}
continue;
}
throw new Error(`Unknown chart filter key: ${key}`);
}
return { where: conds.join(" AND "), versionFilter };
}
// ── Song filter ───────────────────────────────────────────────────────────────
function convertSongFilter(data: Record<string, unknown>): string {
const conds: Array<string> = [];
for (const [key, val] of Object.entries(data)) {
// The one observed pattern: { "data": { "genre": "LUNATIC" } }
// "data" refers to the song's JSONB data column.
if (key === "data") {
if (typeof val !== "object" || val === null) {
throw new Error(
`Expected object for song "data" filter, got: ${JSON.stringify(val)}`,
);
}
for (const [field, fieldVal] of Object.entries(val as Record<string, unknown>)) {
if (typeof fieldVal === "object" && fieldVal !== null) {
conds.push(
...rangeConditionsJsonField(
"song",
field,
fieldVal as Record<string, unknown>,
),
);
} else if (typeof fieldVal === "number") {
conds.push(`(song.data->>'${field}')::numeric = ${fieldVal}`);
} else {
conds.push(`song.data->>'${field}' = ${sqlStr(String(fieldVal))}`);
}
}
continue;
}
throw new Error(`Unknown song filter key: ${key}`);
}
return conds.join(" AND ");
}
/** When `type` is omitted, song filters are the only shape whose sole top-level key is `data`. */
function inferFolderKindFromFilterData(data: Record<string, unknown>): "charts" | "songs" {
const keys = Object.keys(data);
if (keys.length === 1 && keys[0] === "data") {
return "songs";
}
return "charts";
}
// ── Main ──────────────────────────────────────────────────────────────────────
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const FOLDERS_JSON = path.join(__dirname, "../../../../db/seeds/folders.json");
const folders = JSON.parse(fs.readFileSync(FOLDERS_JSON, "utf-8")) as Array<
Record<string, unknown>
>;
for (const entry of folders) {
const filterData = entry.data as Record<string, unknown> | undefined;
if (filterData === undefined) {
if (typeof entry.where !== "string" || entry.where.trim() === "") {
throw new Error(
`Folder ${String(entry.id ?? "?")} has no "data" and no non-empty "where".`,
);
}
delete entry.type;
continue;
}
if (Array.isArray(filterData)) {
throw new Error("Static folders are no longer used.");
}
const dataObj = filterData as Record<string, unknown>;
const explicitType = entry.type as string | undefined;
let kind: "charts" | "songs";
if (explicitType === "charts" || explicitType === "songs") {
kind = explicitType;
} else if (explicitType === "static") {
throw new Error("Static folders are no longer used.");
} else if (explicitType === undefined) {
kind = inferFolderKindFromFilterData(dataObj);
} else {
throw new Error(`Unknown folder type: ${String(explicitType)}`);
}
const folderGame = String(entry.game ?? "");
if (kind === "charts") {
const { where, versionFilter } = convertChartFilter(dataObj, folderGame);
entry.where = where;
if (versionFilter !== undefined) {
entry.versionFilter = versionFilter;
}
} else {
entry.where = convertSongFilter(dataObj);
}
delete entry.data;
delete entry.type;
}
const slugKeys = new Map<string, string>();
for (const entry of folders) {
const slug = computeFolderSlug(entry as SeedFolderRow);
const game = String(entry.game);
const key = `${game}\0${slug}`;
if (slugKeys.has(key)) {
throw new Error(
`Duplicate slug "${slug}" for game ${game}: "${slugKeys.get(key)}" vs "${String(entry.title)}"`,
);
}
slugKeys.set(key, String(entry.title));
entry.slug = slug;
}
fs.writeFileSync(FOLDERS_JSON, `${JSON.stringify(folders, null, "\t")}\n`);
console.log(`Wrote ${FOLDERS_JSON} (${folders.length} folders, with slugs).`);
@@ -1,21 +0,0 @@
import { ReadCollection, WRITE_COLLECTION_SKIP_BIOME, WriteCollection } from "../../util";
const courses = ReadCollection("bms-course-lookup.json");
let modified = 0;
for (const entry of courses) {
if (entry.game !== undefined && entry.playtype === undefined) {
continue;
}
if (entry.playtype === undefined) {
throw new Error(`Entry missing both playtype and game: ${JSON.stringify(entry)}`);
}
entry.game = entry.playtype === "7K" ? "bms-7k" : "bms-14k";
delete entry.playtype;
modified++;
}
WriteCollection("bms-course-lookup.json", courses, WRITE_COLLECTION_SKIP_BIOME);
console.log(`bms-course-lookup.json: migrated ${modified} entries`);
@@ -1,96 +0,0 @@
/**
* Rewrites `tables.json` so each `folders[]` entry is a folder **slug** (per game),
* not a hex folder id. Run after `5-folders-to-sql-queries.ts` (folders must have
* `where` and `slug`).
*
* Idempotent: if an entry is already a slug for that game, it is left unchanged.
*/
import fs from "fs";
import path from "path";
import { computeFolderSlug, type SeedFolderRow } from "tachi-common";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const SEEDS = path.join(__dirname, "../../../../db/seeds");
function resolvedFolderSlug(f: {
game: string;
id: string;
slug?: string;
title: string;
versionFilter?: Array<string>;
where: string;
}): string {
if (f.slug !== undefined && f.slug !== "") {
return f.slug;
}
return computeFolderSlug({
game: f.game,
id: f.id,
title: f.title,
where: f.where,
versionFilter: f.versionFilter,
} as SeedFolderRow);
}
const foldersPath = path.join(SEEDS, "folders.json");
const tablesPath = path.join(SEEDS, "tables.json");
const folders = JSON.parse(fs.readFileSync(foldersPath, "utf-8")) as Array<{
game: string;
id: string;
slug?: string;
title: string;
versionFilter?: Array<string>;
where: string;
}>;
const idToSlug = new Map<string, string>();
const slugByGame = new Map<string, Set<string>>();
for (const f of folders) {
const slug = resolvedFolderSlug(f);
idToSlug.set(f.id, slug);
const set = slugByGame.get(f.game) ?? new Set<string>();
set.add(slug);
slugByGame.set(f.game, set);
}
const tables = JSON.parse(fs.readFileSync(tablesPath, "utf-8")) as Array<{
folders: Array<string>;
game: string;
id: string;
title: string;
}>;
for (const t of tables) {
const validSlugs = slugByGame.get(t.game);
if (validSlugs === undefined) {
throw new Error(`Table "${t.title}": no folders for game ${JSON.stringify(t.game)}`);
}
t.folders = t.folders.map((ref, i) => {
const asSlug = idToSlug.get(ref);
if (asSlug !== undefined) {
return asSlug;
}
if (validSlugs.has(ref)) {
return ref;
}
throw new Error(
`Table "${t.title}" (${t.id}): folders[${i}] = ${JSON.stringify(ref)} is neither a known folder id nor a slug for game ${JSON.stringify(t.game)}`,
);
});
}
fs.writeFileSync(tablesPath, `${JSON.stringify(tables, null, "\t")}\n`);
console.log(`Wrote ${tablesPath} (${tables.length} tables; folder refs are slugs).`);
@@ -1,331 +0,0 @@
/**
* After charts + folders gained Postgres-style ids (`id` plus `legacyChartID` /
* `legacyFolderID`), `goals.json` may still reference legacy chart hashes and legacy
* folder ids inside `charts.data`. Remap those references, recalculate `goalID` hashes
* (same `{ charts, criteria, game }` convention as ../../util CreateGoalID), rewrite
* `quests.json` goal refs when ids change, and update `stability-map.json` `goals` so
* mongo→postgres can translate subscriptions.
*
* Expects each goal row’s {@link GoalSeed.game} to already be a canonical V3 string (run
* `3-migrate-goals-quests-questlines-game.ts` earlier in `just seeds-v3-migrate`).
*/
import fs from "fs";
import path from "path";
import { fileURLToPath } from "url";
import { spawnSync } from "child_process";
import { CreateGoalID } from "../../util";
type GoalCharts =
| { data: Array<string>; type: "multi" }
| { data: string; folderSlug?: string; type: "folder" }
| { data: string; type: "single" };
interface GoalSeed {
charts: GoalCharts;
criteria:
| {
countNum: number;
key: string;
mode: "absolute" | "proportion";
value: number;
}
| {
key: string;
mode: "single";
value: number;
};
game: string;
goalID: string;
name: string;
/** @deprecated Legacy field; omit on v3 seeds. */
playtype?: string;
}
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const SEEDS_DIR = path.join(__dirname, "../../../../db/seeds");
const STABILITY_MAP_PATH = path.join(__dirname, "stability-map.json");
interface StabilityMap {
songs: Record<string, string>;
charts: Record<string, string>;
folders: Record<string, string>;
tables: Record<string, string>;
goals: Record<string, string>;
}
function readStabilityMap(): StabilityMap {
if (fs.existsSync(STABILITY_MAP_PATH)) {
const raw = JSON.parse(
fs.readFileSync(STABILITY_MAP_PATH, "utf-8"),
) as Partial<StabilityMap>;
return {
songs: raw.songs ?? {},
charts: raw.charts ?? {},
folders: raw.folders ?? {},
tables: raw.tables ?? {},
goals: raw.goals ?? {},
};
}
return { songs: {}, charts: {}, folders: {}, tables: {}, goals: {} };
}
function writeStabilityMap(map: StabilityMap): void {
fs.writeFileSync(STABILITY_MAP_PATH, `${JSON.stringify(map, null, "\t")}\n`);
}
const LEGACY_CHART_HEX_RE = /^[0-9a-f]{40}$/u;
function readJsonArray<T>(filePath: string): Array<T> {
return JSON.parse(fs.readFileSync(filePath, "utf8")) as Array<T>;
}
function buildChartLegacyToIdMaps(): {
canonicalChartIds: Set<string>;
legacyToSeedId: Map<string, string>;
} {
const legacyToSeedId = new Map<string, string>();
const canonicalChartIds = new Set<string>();
for (const name of fs.readdirSync(SEEDS_DIR)) {
if (!/^charts-/u.test(name) || !name.endsWith(".json")) {
continue;
}
const charts = readJsonArray<{ id: string; legacyChartID?: string }>(
path.join(SEEDS_DIR, name),
);
for (const c of charts) {
canonicalChartIds.add(c.id);
const leg = c.legacyChartID;
if (leg) {
legacyToSeedId.set(leg.toLowerCase(), c.id);
}
}
}
return { canonicalChartIds, legacyToSeedId };
}
function buildFolderLegacyToIdMaps(): {
canonicalFolderIds: Set<string>;
legacyToSeedId: Map<string, string>;
} {
const legacyToSeedId = new Map<string, string>();
const canonicalFolderIds = new Set<string>();
const folders = readJsonArray<{
id: string;
legacyFolderID: string;
}>(path.join(SEEDS_DIR, "folders.json"));
for (const f of folders) {
canonicalFolderIds.add(f.id);
const leg = f.legacyFolderID;
if (leg !== undefined && leg !== "") {
legacyToSeedId.set(leg.trim(), f.id);
}
}
return { canonicalFolderIds, legacyToSeedId };
}
function resolveChartGoalRef(
candidate: string,
legacyToSeedId: Map<string, string>,
canonicalChartIds: Set<string>,
context: string,
legacyHexRe: RegExp,
): string {
const t = candidate.trim();
const lowered = t.toLowerCase();
if (canonicalChartIds.has(t)) {
return t;
}
const migrated = legacyToSeedId.get(lowered);
if (migrated !== undefined) {
return migrated;
}
if (!legacyHexRe.test(t)) {
console.warn(
`${context}: chart ref ${JSON.stringify(t)} not in canonical set and not a known legacy SHA — leaving as-is`,
);
return t;
}
throw new Error(`${context}: unknown legacy chart SHA ${JSON.stringify(t)}`);
}
function resolveFolderGoalRef(
candidate: string,
legacyToSeedId: Map<string, string>,
canonicalFolderIds: Set<string>,
context: string,
): string {
const t = candidate.trim();
if (canonicalFolderIds.has(t)) {
return t;
}
const migrated = legacyToSeedId.get(t);
if (migrated !== undefined) {
return migrated;
}
throw new Error(`${context}: unknown folder reference ${JSON.stringify(t)}`);
}
function remapChartsBlock(
charts: GoalCharts,
context: string,
legacyHexRe: RegExp,
legacyToSeedChart: Map<string, string>,
canonicalChartIds: Set<string>,
legacyToSeedFolder: Map<string, string>,
canonicalFolderIds: Set<string>,
): GoalCharts {
switch (charts.type) {
case "single":
return {
...charts,
data: resolveChartGoalRef(
charts.data,
legacyToSeedChart,
canonicalChartIds,
context,
legacyHexRe,
),
};
case "multi":
return {
...charts,
data: charts.data.map((cid, i) =>
resolveChartGoalRef(
cid,
legacyToSeedChart,
canonicalChartIds,
`${context} [multi ${i}]`,
legacyHexRe,
),
),
};
case "folder":
return {
...charts,
data: resolveFolderGoalRef(
charts.data,
legacyToSeedFolder,
canonicalFolderIds,
context,
),
};
}
}
const translateMap = new Map<string, string>();
const goalsPath = path.join(SEEDS_DIR, "goals.json");
const goals = readJsonArray<GoalSeed>(goalsPath);
const { canonicalChartIds, legacyToSeedId: chartLegacy } = buildChartLegacyToIdMaps();
const { canonicalFolderIds, legacyToSeedId: folderLegacy } = buildFolderLegacyToIdMaps();
for (const goal of goals) {
const oldId = goal.goalID;
goal.charts = remapChartsBlock(
goal.charts,
`goal ${JSON.stringify(goal.name)} (${oldId})`,
LEGACY_CHART_HEX_RE,
chartLegacy,
canonicalChartIds,
folderLegacy,
canonicalFolderIds,
);
const newId = CreateGoalID(goal.charts, goal.criteria, goal.game);
goal.goalID = newId;
if (newId !== oldId) {
translateMap.set(oldId, newId);
}
}
console.log(`goals.json: remapped chart/folder refs; ${translateMap.size} goal IDs will change`);
fs.writeFileSync(goalsPath, `${JSON.stringify(goals, null, "\t")}\n`);
type QuestSeed = {
desc: string;
game: string;
name: string;
playtype: string;
questData: Array<{
desc?: string;
goals: Array<{ goalID: string; note?: string }>;
title: string;
}>;
questID: string;
};
const questsPath = path.join(SEEDS_DIR, "quests.json");
const quests = readJsonArray<QuestSeed>(questsPath);
let questsUpdated = 0;
for (const quest of quests) {
let touched = false;
for (const qd of quest.questData) {
for (let i = 0; i < qd.goals.length; i++) {
const gRef = qd.goals[i];
const nextId = translateMap.get(gRef.goalID);
if (nextId !== undefined && nextId !== gRef.goalID) {
qd.goals[i] = { ...gRef, goalID: nextId };
touched = true;
}
}
}
if (touched) {
questsUpdated++;
}
}
fs.writeFileSync(questsPath, `${JSON.stringify(quests, null, "\t")}\n`);
console.log(`quests.json: updated goal refs on ${questsUpdated} quests`);
const stabilityMap = readStabilityMap();
stabilityMap.goals = Object.fromEntries(translateMap);
writeStabilityMap(stabilityMap);
console.log(`stability-map.json: wrote ${translateMap.size} goal id translations`);
const repoRoot = path.resolve(SEEDS_DIR, "..", "..");
const biome = path.join(repoRoot, "node_modules", ".bin", "biome");
const biomeResult = spawnSync(
biome,
["format", "--write", goalsPath, questsPath, STABILITY_MAP_PATH],
{
cwd: repoRoot,
stdio: "inherit",
},
);
if (biomeResult.status !== 0) {
throw new Error(`biome format exited ${biomeResult.status}`);
}
File diff suppressed because it is too large Load Diff
+1 -8
View File
@@ -22,10 +22,7 @@
"codemirror": "catalog:",
"comlink": "catalog:",
"fast-json-patch": "catalog:",
"nanoid": "catalog:",
"natural-compare": "catalog:",
"react": "catalog:",
"react-bootstrap": "catalog:",
"react-dom": "catalog:",
"react-query": "catalog:",
"react-router-dom": "catalog:",
@@ -33,18 +30,14 @@
"zod": "catalog:"
},
"devDependencies": {
"@types/natural-compare": "catalog:",
"@types/react": "catalog:",
"@types/react-dom": "catalog:",
"@types/react-router-dom": "catalog:",
"@typescript/native-preview": "catalog:",
"@vitejs/plugin-react": "catalog:",
"bootstrap-scss": "catalog:",
"eslint-config-tachi": "workspace:*",
"sass": "catalog:",
"typescript": "catalog:",
"vite": "catalog:",
"vite-plugin-html": "catalog:"
"vite": "catalog:"
},
"engines": {
"bun": ">=1.2.0"
-1
View File
@@ -11,7 +11,6 @@ COMMIT_HASH=
LOG_LEVEL=
# Tachi server config (required)
TACHI_MONGO_DATABASE_NAME=
TACHI_CAPTCHA_SECRET_KEY=
TACHI_SESSION_SECRET=
TACHI_OUR_URL=
+2 -19
View File
@@ -20,6 +20,7 @@
"author": "zk",
"license": "AGPL3",
"devDependencies": {
"@types/bun": "catalog:",
"@types/pg": "catalog:",
"@types/supertest": "catalog:",
"@typescript/native-preview": "catalog:",
@@ -27,33 +28,23 @@
"eslint-config-tachi": "workspace:*",
"node-mocks-http": "catalog:",
"supertest": "catalog:",
"tsconfig-paths": "catalog:",
"vitest": "catalog:"
},
"dependencies": {
"@aws-sdk/client-s3": "catalog:",
"@octokit/app": "catalog:",
"@types/bcryptjs": "catalog:",
"@types/bunyan": "catalog:",
"@types/connect-redis": "catalog:",
"@types/express": "catalog:",
"@types/express-rate-limit": "catalog:",
"@types/express-serve-static-core": "catalog:",
"@types/express-session": "catalog:",
"@types/mongodb": "catalog:",
"@types/multer": "catalog:",
"@types/node": "catalog:",
"@types/node-fetch": "catalog:",
"@types/nodemailer": "catalog:",
"@types/rate-limit-redis": "catalog:",
"@types/redis": "catalog:",
"bcryptjs": "catalog:",
"bliss": "workspace:*",
"bms-table-loader": "catalog:",
"bullmq": "catalog:",
"bunyan": "catalog:",
"commander": "catalog:",
"connect-redis": "catalog:",
"cron-parser": "catalog:",
"csv-parse": "catalog:",
"deepmerge": "catalog:",
@@ -61,7 +52,6 @@
"express": "catalog:",
"express-async-errors": "catalog:",
"express-prom-bundle": "catalog:",
"express-rate-limit": "catalog:",
"express-session": "catalog:",
"fast-json-stable-hash": "catalog:",
"fast-xml-parser": "catalog:",
@@ -69,8 +59,6 @@
"html-entities": "catalog:",
"kysely": "catalog:",
"lodash": "catalog:",
"mongodb": "catalog:",
"monk": "catalog:",
"multer": "catalog:",
"node-cache": "catalog:",
"node-fetch": "catalog:",
@@ -79,13 +67,10 @@
"pg": "catalog:",
"prom-client": "catalog:",
"prudence": "catalog:",
"rate-limit-redis": "catalog:",
"redis": "catalog:",
"rg-stats": "catalog:",
"tachi-common": "workspace:*",
"tachi-db": "workspace:*",
"tachi-db-migration-engine": "workspace:*",
"ts-node": "catalog:",
"typescript": "catalog:",
"zod": "catalog:"
},
@@ -98,9 +83,7 @@
"include": [
"src/*/!(test-utils)/**/*.ts"
],
"exclude": [
"src/external/mongo/schemas.ts"
]
"exclude": []
},
"engines": {
"bun": ">=1.2.0"
-7
View File
@@ -1,7 +0,0 @@
declare module "mongodb" {
interface FindOneOptions<T> {
projectID?: boolean;
}
}
export {};
@@ -18,7 +18,6 @@ import { log } from "#lib/log/log";
import { maybeStartWorkerMetricsServer } from "#lib/metrics/worker-metrics";
import ScoreImportFatalError from "#lib/score-import/framework/score-importing/score-import-error";
import { MarkImportAsFailed } from "#lib/score-import/framework/status-tracking/import-status-tracking";
import { CloseScoreImportQueue } from "#lib/score-import/worker/queue";
import { processScoreImportJobFromPayload } from "#lib/score-import/worker/score-import-job-processor";
import { Env } from "#lib/setup/config";
import { ClosePgConnection } from "#services/pg/db";
@@ -183,7 +182,6 @@ async function bootstrap() {
clearInterval(heartbeatInterval);
metrics?.close();
log.info("Job worker loops stopped, closing resources.");
await CloseScoreImportQueue();
await CloseRedisConnection();
await ClosePgConnection();
process.exit(0);
@@ -20,15 +20,10 @@ export function ToFolderDocument(
return {
folderID: row.id,
slug: row.slug,
data: {
$comment:
"This data does not exist anymore, and you never should've been depending on it, anyway. This field is only here for backwards compatibility.",
},
game: row.game,
inactive: row.inactive,
searchTerms: row.search_terms ?? [],
title: row.title,
type: "charts",
};
}
+1 -4
View File
@@ -1,6 +1,5 @@
import { log } from "#lib/log/log";
import { Env, ServerConfig } from "#lib/setup/config";
import bunyan from "bunyan";
import nodemailer, { type SentMessageInfo, type Transporter } from "nodemailer";
let transporter: Transporter | undefined;
@@ -12,9 +11,7 @@ try {
transporter = nodemailer.createTransport({
newline: "unix",
logger: emailConf.TRANSPORT_OPS?.debug
? bunyan.createLogger({ name: "Email Logger" })
: undefined,
logger: emailConf.TRANSPORT_OPS?.debug ? log : undefined,
...emailConf.TRANSPORT_OPS,
});
@@ -1,7 +1,6 @@
import type http from "http";
import { log } from "#lib/log/log";
import { CloseScoreImportQueue } from "#lib/score-import/worker/queue";
import { ClosePgConnection } from "#services/pg/db";
import { CloseRedisConnection } from "#services/redis/redis";
@@ -34,9 +33,6 @@ async function CloseEverythingElse() {
log.info({ shutdownInfo: true }, "Closing Redis Connection.");
await CloseRedisConnection();
log.info({ shutdownInfo: true }, "Closing Score Import Queue.");
await CloseScoreImportQueue();
log.info(
{
shutdownInfo: true,
@@ -444,8 +444,6 @@ function ParseImportInfo(importInfo: Array<ImportProcessingInfo>) {
return { scoreIDs, errors, scoreGameMap: scoreGameMap, chartIDs };
}
function SetJobProgress(job: ScoreImportJob | undefined, description: string) {
if (job) {
return job.updateProgress({ description });
}
function SetJobProgress(_job: ScoreImportJob | undefined, _description: string) {
// TODO(zk): impl
}
@@ -1,11 +0,0 @@
/**
* BullMQ score import queue (legacy). Replaced by Postgres `job_queue`.
* The API process no longer instantiates a Redis queue; keep a no-op close for shutdown paths.
*/
export default null;
export const ScoreImportQueueEvents = null;
export async function CloseScoreImportQueue(): Promise<void> {
// no-op (Postgres `job_queue` is used for score imports)
}
@@ -1,4 +1,3 @@
import type { Job } from "bullmq";
import type { ImportDocument, ImportTypes, integer } from "tachi-common";
import type { Parsers } from "../import-types/parsers";
@@ -37,12 +36,15 @@ export interface ScoreImportJobData<I extends ImportTypes> {
parserArguments: ParserArguments<I>;
}
export type ScoreImportJob<I extends ImportTypes = ImportTypes> = Job<ScoreImportJobData<I>>;
export interface ScoreImportProgress {
description: string;
}
/** Minimal interface for reporting import progress to a job runner. */
export interface ScoreImportJob {
updateProgress(progress: ScoreImportProgress): Promise<void> | void;
}
export type ScoreImportWorkerReturns =
| {
description: string;
-20
View File
@@ -1,4 +1,3 @@
import type { ICollection } from "monk";
import type { GameGroup } from "tachi-common";
import fs from "fs/promises";
@@ -290,22 +289,3 @@ export async function PullDatabaseSeeds() {
throw new Error(`SEEDS_CONFIG was not defined. You cannot pull a seeds repo.`);
}
}
export async function BacksyncCollection(
collectionName: SeedsCollections,
collection: ICollection,
commitMessage: string,
) {
const repo = await PullDatabaseSeeds();
let charts = await collection.find({});
await repo.WriteCollection(collectionName, charts);
// @ts-expect-error Force node to free the memory.
charts = null;
await repo.CommitChangesBack(`${commitMessage} ${new Date().toISOString()}`);
await repo.Destroy();
}
@@ -1,405 +0,0 @@
/**
* MongoDB document shapes for {@link ./migrate-to-postgres.ts}.
*
* These intentionally do **not** match normalized API / `tachi-common` document
* types: legacy rows use `game` as a **game group** plus `playtype`, and many
* fields are optional or nullable compared to current validation.
*
* Primary spec reference: `PRE_SCHEMAS` in `typescript/common/src/lib/schemas.ts`
* (collection keys match Mongo collection names where applicable).
*/
import type { ScoreData, V3Game } from "tachi-common";
import type { GameGroup } from "tachi-db";
// ─── api-clients ─────────────────────────────────────────────────────────────
export interface MongoApiClientsCollectionDocument {
clientID: string;
clientSecret: string;
name: string;
author: number;
requestedPermissions: Array<string>;
apiKeyTemplate: string | null;
apiKeyFilename: string | null;
}
// ─── api-tokens ──────────────────────────────────────────────────────────────
/** Stored field names follow server usage (`fromAPIClient`); see PRE_SCHEMAS for idealized names. */
export interface MongoApiTokensCollectionDocument {
fromAPIClient: string | null;
identifier: string;
permissions: Record<string, boolean>;
token: string | null;
userID: number | null;
}
// ─── cg-card-info ────────────────────────────────────────────────────────────
export interface MongoCgCardInfoCollectionDocument {
userID: number;
service: string;
cardID: string;
pin: string;
}
// ─── class-achievements ──────────────────────────────────────────────────────
export interface MongoClassAchievementsCollectionDocument {
userID: number;
/** Game group (not V3 slug) in legacy Mongo. */
game: string;
playtype: string;
classSet: string;
classOldValue: number | null;
classValue: number;
timeAchieved: number;
}
// ─── fer-settings ────────────────────────────────────────────────────────────
export interface MongoFerSettingsCollectionDocument {
userID: number;
cards?: Array<string> | null;
forceStaticImport: boolean;
}
// ─── game-stats ───────────────────────────────────────────────────────────────
export interface MongoGameStatsCollectionDocument {
userID: number;
game: string;
playtype?: string;
ratings: Record<string, number | null>;
classes: Record<string, number | null | undefined>;
}
// ─── game-stats-snapshots ────────────────────────────────────────────────────
export interface MongoGameStatsSnapshotsCollectionDocument {
userID: number;
game: string;
playtype?: string;
timestamp: number;
playcount: number;
ratings: Record<string, number | null>;
classes: Record<string, number | null | undefined>;
rankings: Record<string, { outOf: number; ranking: number }>;
}
// ─── game-settings (UGPT) ────────────────────────────────────────────────────
export interface MongoGameSettingsCollectionDocument {
userID: number;
game: string;
playtype?: string;
rivals: Array<number>;
preferences: {
defaultTable: string | null;
gameSpecific: unknown;
preferredDefaultEnum: "grade" | "lamp" | null;
preferredProfileAlg: string | null;
preferredRanking: "global" | "rival" | null;
preferredScoreAlg: string | null;
preferredSessionAlg: string | null;
stats: unknown;
};
}
// ─── goal-subs ───────────────────────────────────────────────────────────────
export interface MongoGoalSubsCollectionDocument {
goalID: string;
userID: number;
game: string;
playtype: string;
achieved: boolean;
timeAchieved: number | null;
lastInteraction: number | null;
progress: number | null;
progressHuman: string;
outOf: number;
outOfHuman: string;
wasInstantlyAchieved: boolean;
wasAssignedStandalone: boolean;
}
// ─── import-locks ────────────────────────────────────────────────────────────
export interface MongoImportLocksCollectionDocument {
userID: number;
locked: boolean;
lockedAt: number | null;
}
// ─── import-timings ───────────────────────────────────────────────────────────
export interface MongoImportTimingsCollectionDocument {
importID: string;
timestamp: number;
total: number;
rel: {
import: number;
importParse: number;
pb: number;
session: number;
};
abs: {
goal?: number;
import?: number;
importParse?: number;
parse?: number;
pb?: number;
quest?: number;
session?: number;
ugs?: number;
};
}
// ─── imports ─────────────────────────────────────────────────────────────────
export interface MongoImportsCollectionDocument {
importID: string;
userID: number;
timeStarted: number;
timeFinished: number;
scoreIDs?: Array<string>;
/** Legacy field name — game group, not V3 slug. */
game?: GameGroup;
gameGroup?: GameGroup;
/** Oldest-era composite keys; see LEGACY_GPTStringToGame. */
gptStrings?: Array<string>;
/** Legacy list of playtypes for `game` / `gameGroup`. */
playtypes?: Array<string>;
/** Some eras store one playtype string instead of a `playtypes` array. */
playtype?: string;
/** New-era V3 game slugs (API ImportDocument.games). */
games?: Array<string>;
errors?: Array<{ message: string; type: string }>;
classDeltas?: Array<{
game: string;
new: number | string;
old?: number | string | null;
playtype?: string;
set: string;
}>;
createdSessions?: Array<{ sessionID: string; type: string }>;
importType?: string | null;
userIntent?: boolean;
}
// ─── invite-locks ────────────────────────────────────────────────────────────
export interface MongoInviteLocksCollectionDocument {
userID: number;
locked: boolean;
}
// ─── invites ─────────────────────────────────────────────────────────────────
export interface MongoInvitesCollectionDocument {
code: string;
createdBy: number;
createdAt: number;
consumed: boolean;
consumedBy: number | null;
consumedAt: number | null;
}
// ─── kai-auth-tokens ───────────────────────────────────────────────────────────
export interface MongoKaiAuthTokensCollectionDocument {
userID: number;
service: string;
token: string;
refreshToken: string;
}
// ─── kshook-sv6c-settings ────────────────────────────────────────────────────
export interface MongoKshookSv6cSettingsCollectionDocument {
userID: number;
forceStaticImport: boolean;
}
// ─── myt-card-info ─────────────────────────────────────────────────────────────
export interface MongoMytCardInfoCollectionDocument {
userID: number;
cardAccessCode: string;
}
// ─── notifications ─────────────────────────────────────────────────────────────
export interface MongoNotificationsCollectionDocument {
title: string;
sentTo: number;
sentAt: number;
read: boolean;
body: { [key: string]: unknown; type: string };
}
// ─── orphan-chart-queue ──────────────────────────────────────────────────────
export interface MongoOrphanChartQueueCollectionDocument {
/** `game:playtype` composite; current schema name in PRE_SCHEMAS. */
gptString?: string;
/** Same semantics as {@link gptString} on older Mongo rows (e.g. USC orphans). */
idString?: string;
/** V3 slug or denormalized game field when present on the envelope. */
game?: string;
/** Embedding often matches {@link ChartDocument}; `game` lives here even when absent at root. */
chartDoc: { chartID: string; game?: string } & Record<string, unknown>;
songDoc: unknown;
userIDs: Array<number>;
}
// ─── orphan-scores ─────────────────────────────────────────────────────────────
export interface MongoOrphanScoresCollectionDocument {
orphanID: string;
importType: string;
game: GameGroup;
userID: number;
timeInserted: number;
errMsg?: string | null;
data: unknown;
context: unknown;
}
// ─── quest-subs ──────────────────────────────────────────────────────────────
export interface MongoQuestSubsCollectionDocument {
questID: string;
userID: number;
game: string;
playtype: string;
achieved: boolean;
timeAchieved: number | null;
progress: number;
lastInteraction: number | null;
wasInstantlyAchieved: boolean;
}
// ─── recent-folder-views ─────────────────────────────────────────────────────
export interface MongoRecentFolderViewsCollectionDocument {
userID: number;
folderID: string;
lastViewed: number;
game?: string;
playtype?: string;
}
// ─── score-blacklist ─────────────────────────────────────────────────────────
export interface MongoScoreBlacklistCollectionDocument {
scoreID: string;
userID: number;
}
// ─── scores ──────────────────────────────────────────────────────────────────
export interface MongoScoresCollectionDocument {
scoreID: string;
service: string;
/** Game group or V3 slug depending on era. */
game: string;
playtype?: string;
userID: number;
songID: number;
chartID: string;
isPrimary: boolean;
highlight: boolean;
comment: string | null;
timeAdded: number;
importType: string | null;
timeAchieved: number | null;
scoreData: ScoreData<V3Game>;
scoreMeta: Record<string, unknown>;
calculatedData: Partial<Record<string, number | null>>;
}
// ─── sessions ────────────────────────────────────────────────────────────────
export interface MongoSessionsCollectionDocument {
sessionID: string;
userID: number;
name: string;
desc: string | null;
game: string;
playtype?: string;
timeInserted: number;
timeEnded: number;
timeStarted: number;
highlight: boolean;
calculatedData: Record<string, number | null | undefined>;
scoreIDs: Array<string>;
}
/** Projection used when resolving score → session from Mongo. */
export type MongoSessionsScoreLookupProjection = Pick<
MongoSessionsCollectionDocument,
"scoreIDs" | "sessionID"
>;
// ─── user-name-changes ────────────────────────────────────────────────────────
export interface MongoUserNameChangesCollectionDocument {
userID: number;
username: string;
previousUsername: string;
timestamp: number;
}
// ─── user-private-information ────────────────────────────────────────────────
export interface MongoUserPrivateInformationCollectionDocument {
userID: number;
password: string;
email: string;
}
// ─── user-settings ───────────────────────────────────────────────────────────
export interface MongoUserSettingsCollectionDocument {
userID: number;
following: Array<number>;
preferences: {
advancedMode: boolean;
contentiousContent: boolean;
deletableScores: boolean;
developerMode: boolean;
invisible: boolean;
};
}
// ─── users ───────────────────────────────────────────────────────────────────
export interface MongoUsersCollectionDocument {
id: number;
username: string;
socialMedia: {
discord: string | null;
github: string | null;
steam: string | null;
twitch: string | null;
twitter: string | null;
youtube: string | null;
};
joinDate: number;
about: string;
status: string | null;
customPfpLocation: string | null;
customBannerLocation: string | null;
lastSeen: number;
badges: Array<string>;
authLevel: number;
}
File diff suppressed because it is too large Load Diff
@@ -1,257 +0,0 @@
/**
* Re-import Mongo `api-clients` → Postgres `priv_api_client` and Mongo `api-tokens` → `priv_api_token`.
*
* Use after correcting permission-field mapping bugs — upserts Mongo rows into Postgres (overwrite on PK conflict).
*
* Postgres dependency order:
* If you truncate tokens only: `TRUNCATE priv_api_token;`
* If you redo clients too (recommended when fixing client permission columns):
* `TRUNCATE priv_api_token, priv_api_client;`
* (ordering does not matter in one statement; tokens must be cleared if you truncate clients.)
*
* Heads-up: `priv_discord_user_map` FKs into `priv_api_token` — resetting tokens may unlink Discord API mapping rows until you reconcile.
*
* Env:
* MONGO_URL — MongoDB URL (default: mongodb://mongo/tachi)
* POSTGRES_URL — required
*
* From `typescript/server`:
* MONGO_URL=... POSTGRES_URL=... bun run src/scripts/reimport-priv-api-from-mongo.ts
*/
import type { Database, NewPrivApiClient, NewPrivApiToken } from "tachi-db";
import { type Insertable, Kysely, PostgresDialect, type RawBuilder, sql } from "kysely";
import monk from "monk";
import { Pool } from "pg";
import type {
MongoApiClientsCollectionDocument,
MongoApiTokensCollectionDocument,
} from "./migrate-to-postgres.mongo-docs";
const MONGO_URL = process.env.MONGO_URL ?? "mongodb://mongo/tachi";
const POSTGRES_URL = process.env.POSTGRES_URL;
if (!POSTGRES_URL) {
console.error("[reimport-priv-api] POSTGRES_URL is not set.");
process.exit(1);
}
const mongoDB = monk(MONGO_URL);
const pg = new Kysely<Database>({
dialect: new PostgresDialect({
pool: new Pool({ connectionString: POSTGRES_URL }),
}),
});
// ── Same permission normalization as migrate-to-postgres.ts ─────────────────
function permissionKeyDashVariant(canonicalUnderscoreKey: string): string {
return canonicalUnderscoreKey.replaceAll("_", "-");
}
/** requestedPermissions entries may use underscore (`customise_profile`) or dashed legacy strings. */
function perm(permissions: Array<string>, canonicalUnderscoreKey: string): boolean | null {
const dashed = permissionKeyDashVariant(canonicalUnderscoreKey);
return permissions.includes(canonicalUnderscoreKey) || permissions.includes(dashed)
? true
: null;
}
function permRec(
permissions: Record<string, boolean>,
canonicalUnderscoreKey: string,
): boolean | null {
const dashed = permissionKeyDashVariant(canonicalUnderscoreKey);
if (Object.hasOwn(permissions, canonicalUnderscoreKey)) {
return permissions[canonicalUnderscoreKey]!;
}
if (Object.hasOwn(permissions, dashed)) {
return permissions[dashed]!;
}
return null;
}
const INSERT_CHUNK = 500;
/** ON CONFLICT DO UPDATE — set each column from the proposed row (`excluded`). */
function onConflictExcludedAll(
exampleRow: Record<string, unknown>,
): Record<string, RawBuilder<unknown>> {
const toSet: Record<string, RawBuilder<unknown>> = {};
for (const key of Object.keys(exampleRow)) {
toSet[key] = sql`excluded.${sql.ref(key)}`;
}
return toSet;
}
async function batchUpsertPrivApiClient(
rows: ReadonlyArray<Insertable<Database["priv_api_client"]>>,
): Promise<void> {
if (rows.length === 0) {
return;
}
for (let i = 0; i < rows.length; i = i + INSERT_CHUNK) {
const chunk = rows.slice(i, i + INSERT_CHUNK);
// Sequential chunks — avoid oversized multi-row INSERT payloads.
// eslint-disable-next-line no-await-in-loop -- bounded batch upserts
await pg
.insertInto("priv_api_client")
.values(chunk as never)
.onConflict((oc) =>
oc
.column("client_id")
.doUpdateSet(onConflictExcludedAll(chunk[0] as Record<string, unknown>)),
)
.execute();
}
}
async function batchUpsertPrivApiToken(
rows: ReadonlyArray<Insertable<Database["priv_api_token"]>>,
): Promise<void> {
if (rows.length === 0) {
return;
}
for (let i = 0; i < rows.length; i = i + INSERT_CHUNK) {
const chunk = rows.slice(i, i + INSERT_CHUNK);
// eslint-disable-next-line no-await-in-loop -- bounded batch upserts
await pg
.insertInto("priv_api_token")
.values(chunk as never)
.onConflict((oc) =>
oc
.column("token")
.doUpdateSet(onConflictExcludedAll(chunk[0] as Record<string, unknown>)),
)
.execute();
}
}
function getFromApiClient(doc: MongoApiTokensCollectionDocument): string | null {
const d = doc as {
fromOAuth2Client?: string | null;
} & MongoApiTokensCollectionDocument;
if (typeof d.fromOAuth2Client === "string") {
return d.fromOAuth2Client;
}
return d.fromAPIClient;
}
function mapApiClientRow(c: MongoApiClientsCollectionDocument): NewPrivApiClient {
const extended = c as {
redirectURI?: string | null;
redirectUri?: string | null;
webhookURI?: string | null;
webhookUri?: string | null;
} & MongoApiClientsCollectionDocument;
const p = c.requestedPermissions as unknown as Array<string>;
return {
client_id: c.clientID,
client_secret: c.clientSecret,
name: c.name,
author: c.author,
pm_customise_profile: perm(p, "customise_profile"),
pm_customise_score: perm(p, "customise_score"),
pm_customise_session: perm(p, "customise_session"),
pm_delete_score: perm(p, "delete_score"),
pm_manage_rivals: perm(p, "manage_rivals"),
pm_manage_targets: perm(p, "manage_targets"),
pm_submit_score: perm(p, "submit_score"),
pm_manage_challenges: perm(p, "manage_challenges"),
api_key_template: c.apiKeyTemplate,
api_key_filename: c.apiKeyFilename,
redirect_uri: extended.redirectUri ?? extended.redirectURI ?? null,
webhook_uri: extended.webhookUri ?? extended.webhookURI ?? null,
};
}
async function main(): Promise<void> {
console.log("[reimport-priv-api] fetching api-clients from Mongo...");
const clients = await mongoDB.get<MongoApiClientsCollectionDocument>("api-clients").find({});
const clientRows: Array<NewPrivApiClient> = clients.map((c) => mapApiClientRow(c));
console.log(
`[reimport-priv-api] upserting ${clientRows.length} row(s) into priv_api_client (conflict target: client_id)...`,
);
await batchUpsertPrivApiClient(clientRows);
console.log("[reimport-priv-api] fetching api-tokens from Mongo...");
const apiTokens = await mongoDB.get<MongoApiTokensCollectionDocument>("api-tokens").find({});
const existingClientIds = new Set(
(await pg.selectFrom("priv_api_client").select("client_id").execute()).map(
(r) => r.client_id,
),
);
const validTokens = apiTokens.filter((t) => {
const fromClient = getFromApiClient(t);
return (
t.token !== null &&
t.userID !== null &&
(fromClient === null || fromClient === undefined || existingClientIds.has(fromClient))
);
});
if (validTokens.length !== apiTokens.length) {
console.warn(
`[reimport-priv-api] Skipping ${apiTokens.length - validTokens.length} token row(s): null token/userID or dangling oauth client`,
);
}
const tokenRows: Array<NewPrivApiToken> = validTokens.map((t) => {
const fromClient = getFromApiClient(t);
const p =
t.permissions && typeof t.permissions === "object"
? t.permissions
: ({} as Record<string, boolean>);
return {
token: t.token!,
user_id: t.userID!,
identifier: t.identifier,
pm_customise_profile: permRec(p, "customise_profile"),
pm_customise_score: permRec(p, "customise_score"),
pm_customise_session: permRec(p, "customise_session"),
pm_delete_score: permRec(p, "delete_score"),
pm_manage_rivals: permRec(p, "manage_rivals"),
pm_manage_targets: permRec(p, "manage_targets"),
pm_submit_score: permRec(p, "submit_score"),
pm_manage_challenges: permRec(p, "manage_challenges"),
from_oauth2_client: fromClient ?? null,
};
});
console.log(
`[reimport-priv-api] upserting ${tokenRows.length} row(s) into priv_api_token (conflict target: token)...`,
);
await batchUpsertPrivApiToken(tokenRows);
console.log("[reimport-priv-api] done.");
await mongoDB.close();
await pg.destroy();
}
main().catch((err) => {
console.error("[reimport-priv-api]", err);
process.exit(1);
});
@@ -0,0 +1,123 @@
import express from "express";
import supertest from "supertest";
import { describe, expect, it, vi } from "vitest";
import { createRateLimiter } from "./rate-limiter";
function makeApp(max: number, windowMs: number) {
const limiter = createRateLimiter(max, windowMs);
const app = express();
app.set("trust proxy", true);
app.use(limiter);
app.get("/", (_req, res) => res.status(200).json({ ok: true }));
return { app, limiter };
}
describe("createRateLimiter", () => {
describe("under the limit", () => {
it("allows requests below max", async () => {
const { app } = makeApp(3, 60_000);
for (let i = 0; i < 3; i++) {
const res = await supertest(app).get("/");
expect(res.status).toBe(200);
}
});
});
describe("over the limit", () => {
it("returns 429 once max is exceeded", async () => {
const { app } = makeApp(2, 60_000);
await supertest(app).get("/");
await supertest(app).get("/");
const res = await supertest(app).get("/");
expect(res.status).toBe(429);
expect(res.body.success).toBe(false);
expect(res.body.status).toBe(429);
});
it("includes the window duration in the description", async () => {
const { app } = makeApp(1, 30_000);
await supertest(app).get("/");
const res = await supertest(app).get("/");
expect(res.body.description).toContain("30 seconds");
});
});
describe("window reset", () => {
it("resets the counter after the window expires", async () => {
vi.useFakeTimers();
const { app } = makeApp(1, 1_000);
await supertest(app).get("/");
expect((await supertest(app).get("/")).status).toBe(429);
// Advance past the window
vi.advanceTimersByTime(1_001);
expect((await supertest(app).get("/")).status).toBe(200);
vi.useRealTimers();
});
});
describe("key isolation", () => {
it("tracks different IPs independently", async () => {
const { app } = makeApp(1, 60_000);
// First IP exhausts its quota
await supertest(app).get("/").set("X-Forwarded-For", "1.2.3.4");
expect((await supertest(app).get("/").set("X-Forwarded-For", "1.2.3.4")).status).toBe(
429,
);
// Second IP still has quota
expect((await supertest(app).get("/").set("X-Forwarded-For", "5.6.7.8")).status).toBe(
200,
);
});
});
describe("resetKey", () => {
it("clears the counter for a key so requests are allowed again", async () => {
const { app, limiter } = makeApp(1, 60_000);
await supertest(app).get("/");
expect((await supertest(app).get("/")).status).toBe(429);
limiter.resetKey("::ffff:127.0.0.1");
expect((await supertest(app).get("/")).status).toBe(200);
});
});
describe("Infinity max", () => {
it("always allows requests through", async () => {
const { app } = makeApp(Infinity, 1);
for (let i = 0; i < 100; i++) {
expect((await supertest(app).get("/")).status).toBe(200);
}
});
});
describe("exact boundary", () => {
it("allows the max-th request and blocks the (max+1)-th", async () => {
const max = 5;
const { app } = makeApp(max, 60_000);
for (let i = 0; i < max; i++) {
expect((await supertest(app).get("/")).status).toBe(200);
}
expect((await supertest(app).get("/")).status).toBe(429);
});
});
});
@@ -1,28 +1,72 @@
import type { Request } from "express";
import type { NextFunction, Request, Response } from "express";
import type { integer } from "tachi-common";
import { ONE_MINUTE } from "#lib/constants/time";
import { log } from "#lib/log/log";
import { Env, ServerConfig, TachiConfig } from "#lib/setup/config";
import { RedisClient } from "#services/redis/redis";
import { OmitUndefinedKeys } from "#utils/misc";
import rateLimit, { type Options } from "express-rate-limit";
import RateLimitRedis from "rate-limit-redis";
import { Env, ServerConfig } from "#lib/setup/config";
if (process.env.NODE_ENV === "test") {
// Signal to vitest.setup.ts that this worker has loaded the rate limiter,
// so its beforeEach should clear the in-memory cache. Pure-unit test files
// that never load any router skip the cache-clear (and the import chain)
// entirely.
(globalThis as { __tachi_rate_limiter_loaded?: boolean }).__tachi_rate_limiter_loaded = true;
interface RateLimiterEntry {
count: integer;
resetAt: number;
}
function CreateStore(name: string) {
// undefined forces a default to an in-memory store
// So we use that when in testing or localdev.
return Env.NODE_ENV === "production" || Env.NODE_ENV === "staging"
? new RateLimitRedis({ prefix: `${TachiConfig.NAME}-RL:${name}`, client: RedisClient })
: undefined;
export interface RateLimitMiddleware {
(req: Request, res: Response, next: NextFunction): void;
resetKey(key: string): void;
}
/**
* Fixed-window in-memory rate limiter. Each unique `req.ip` gets its own counter
* that resets after `windowMs`. Pass `Infinity` for `max` to create a no-op limiter.
*/
export function createRateLimiter(
max: integer | typeof Infinity,
windowMs: number,
): RateLimitMiddleware {
const store = new Map<string, RateLimiterEntry>();
function middleware(req: Request, res: Response, next: NextFunction): void {
if (max === Infinity) {
next();
return;
}
const key = req.ip ?? "unknown";
const now = Date.now();
let entry = store.get(key);
if (!entry || now >= entry.resetAt) {
entry = { count: 0, resetAt: now + windowMs };
store.set(key, entry);
}
entry.count++;
if (entry.count <= max) {
next();
return;
}
log.warn({ url: req.url, method: req.method }, `User ${key} hit rate limit.`);
res.status(429).json({
success: false,
description: `You have exceeded ${max} requests per ${windowMs / 1000} seconds. Please wait.`,
status: 429,
message: "You're being rate limited.",
});
}
middleware.resetKey = (key: string): void => {
store.delete(key);
};
return middleware;
}
if (Env.NODE_ENV === "test") {
(globalThis as { __tachi_rate_limiter_loaded?: boolean }).__tachi_rate_limiter_loaded = true;
}
/** Express / supertest may use any of these as `req.ip` for loopback. */
@@ -36,50 +80,17 @@ export function ClearTestingRateLimitCache() {
}
}
const CreateRateLimitOptions = (max: integer, name: string, windowMs?: number): Partial<Options> =>
OmitUndefinedKeys({
max,
onLimitReached: (req: Request) => {
log.warn(
{
url: req.url,
method: req.method,
hideFromConsole: ["req"],
},
`User ${req.ip} hit rate limit.`,
);
},
store: CreateStore(name),
message: {
success: false,
description: `You have exceeded ${max} requests per ${
(windowMs ?? 60_000) / 1000
} seconds. Please wait.`,
status: 429,
message: "You're being rate limited.",
},
windowMs,
});
// 100 requests / minute
export const NormalRateLimitMiddleware = createRateLimiter(ServerConfig.RATE_LIMIT, ONE_MINUTE);
// 100 requests / minute is the current cap
export const NormalRateLimitMiddleware = rateLimit(
CreateRateLimitOptions(ServerConfig.RATE_LIMIT, "Normal"),
);
// 15 requests every 10 minutes
export const AggressiveRateLimitMiddleware = createRateLimiter(15, ONE_MINUTE * 10);
// 15 requests every 10 minutes.
export const AggressiveRateLimitMiddleware = rateLimit(
CreateRateLimitOptions(15, "Aggressive", ONE_MINUTE * 10),
);
// 2 requests every 5 minutes
export const HyperAggressiveRateLimitMiddleware = createRateLimiter(2, ONE_MINUTE * 5);
// 2 requests every 5 minutes.
export const HyperAggressiveRateLimitMiddleware = rateLimit(
CreateRateLimitOptions(2, "HyAgressive", ONE_MINUTE * 5),
);
// 5 requests every minute. This one has a tighter window, so it is less
// vulnerable to bursting down the server.
// Vitest: unlimited. Optional: TACHI_DISABLE_SCORE_IMPORT_RATE_LIMIT for local stress runs.
// 5 requests per minute; unlimited in test or when explicitly disabled for load testing
export const ScoreImportRateLimiter =
Env.NODE_ENV === "test" || ServerConfig.DISABLE_SCORE_IMPORT_RATE_LIMIT
? rateLimit(CreateRateLimitOptions(Infinity, "ScImport", ONE_MINUTE))
: rateLimit(CreateRateLimitOptions(5, "ScImport", ONE_MINUTE));
? createRateLimiter(Infinity, ONE_MINUTE)
: createRateLimiter(5, ONE_MINUTE);
@@ -678,9 +678,7 @@ API_V1_ROUTER.add("GET /games/:game/targets/recently-raised", withGame, async ({
API_V1_ROUTER.add("GET /games/:game/targets/goals/popular", withGame, async ({ ctx }) => {
const { gameGroup, playtype } = LEGACY_GameToGameGroupPT(ctx.game);
// GetMostSubscribedGoals internally converts GameGroup+playtype to V3Game for filtering
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const goals = await GetMostSubscribedGoals({ game: gameGroup as any, playtype });
const goals = await GetMostSubscribedGoals({ game: gameGroup, playtype });
return success(`Returned ${goals.length} goals.`, goals);
});
@@ -97,9 +97,7 @@ describe("GET /api/v1/users/:userID/games/:game/custom-tables/:tableUrlName", ()
it("returns HTML with a bmstable meta header for user-specific tables", async () => {
const { id } = await seedUser({ username: "bms_custom_tbl" });
const res = await mockApi.get(
`/api/v1/users/${id}/games/bms-7k/custom-tables/rival-info`,
);
const res = await mockApi.get(`/api/v1/users/${id}/games/bms-7k/custom-tables/rival-info`);
expect(res.status).toBe(200);
expect(res.text).toContain('meta name="bmstable"');
@@ -111,9 +109,7 @@ describe("GET /api/v1/users/:userID/games/:game/custom-tables/:tableUrlName", ()
it("returns 404 when fetching a public table from the user route", async () => {
const { id } = await seedUser({ username: "bms_custom_tbl_pub" });
const res = await mockApi.get(
`/api/v1/users/${id}/games/bms-7k/custom-tables/sieglindeEC`,
);
const res = await mockApi.get(`/api/v1/users/${id}/games/bms-7k/custom-tables/sieglindeEC`);
expect(res.status).toBe(404);
expect(res.body.description).toContain("user specific");
+8 -10
View File
@@ -1,7 +1,6 @@
import type { integer } from "tachi-common";
import "express-async-errors";
import connectRedis from "connect-redis";
import express, { type Express } from "express";
// THIS IMPORT **MUST** GO HERE. DO NOT MOVE IT. IT MUST OCCUR BEFORE ANYTHING HAPPENS WITH EXPRESS
// BUT AFTER EXPRESS IS IMPORTED.
@@ -9,7 +8,6 @@ import express, { type Express } from "express";
import { SYMBOL_TACHI_API_AUTH } from "#lib/constants/tachi";
import { log } from "#lib/log/log";
import { Env, ServerConfig, TachiConfig } from "#lib/setup/config";
import { RedisClient } from "#services/redis/redis";
import { IsNonEmptyString, IsRecord } from "#utils/misc";
import { ExpectedErr } from "bliss";
import expressSession from "express-session";
@@ -23,14 +21,14 @@ let store;
if (Env.NODE_ENV !== "test") {
log.info({ bootInfo: true }, "Connecting ExpressSession to Redis.");
const RedisStore = connectRedis(expressSession);
store = new RedisStore({
host: "localhost",
port: 6379,
client: RedisClient,
prefix: TachiConfig.NAME,
});
// n.b. load bearing prefix here - do not remove the prefix for any reason.
// Dynamic import keeps redis.ts (which uses Bun.RedisClient) out of the
// module graph when running under Vitest's Node.js worker threads in tests.
const [{ RedisClient }, { BunRedisSessionStore }] = await Promise.all([
import("#services/redis/redis"),
import("#services/redis/session-store"),
]);
store = new BunRedisSessionStore(RedisClient, { prefix: TachiConfig.NAME });
}
const userSessionMiddleware = expressSession({
+6 -40
View File
@@ -1,54 +1,20 @@
import { ONE_MINUTE, ONE_SECOND } from "#lib/constants/time";
import { log } from "#lib/log/log";
import { Env } from "#lib/setup/config";
import { GetMillisecondsSince } from "#utils/misc";
import redis from "redis";
log.debug({ bootInfo: true }, "Instantiated Redis Store");
export const RedisClient = redis.createClient({
url: `redis://${Env.REDIS_URL}`,
});
const startConnect = process.hrtime.bigint();
log.debug({ bootInfo: true }, "Instantiated Redis Client");
function EmitCritical() {
/* istanbul ignore next */
if (!RedisClient.connected) {
log.fatal(`Could not connect to redis in time. No more information is available.`);
export const RedisClient = new Bun.RedisClient(`redis://${Env.REDIS_URL}`);
// can't connect. kill self after 1 second.
setTimeout(() => {
process.exit(1);
}, ONE_SECOND);
}
}
// awful performance on windows and in test runners mean that connecting to redis can be a
// nearly FIVE minute endeavour!
const ref = setTimeout(EmitCritical, ONE_MINUTE * 5);
RedisClient.on("connect", () => {
RedisClient.onconnect = () => {
log.info(
{
bootInfo: true,
},
{ bootInfo: true },
`Connected to Redis. Took ${GetMillisecondsSince(startConnect)}ms`,
);
};
clearTimeout(ref);
});
export function CloseRedisConnection() {
return new Promise((resolve, reject) => {
RedisClient.quit((err, reply) => {
if (err) {
reject(err);
} else {
resolve(reply);
}
});
});
export function CloseRedisConnection(): void {
RedisClient.close();
}
@@ -0,0 +1,70 @@
import { type SessionData, Store } from "express-session";
/**
* express-session store backed by Bun's built-in Redis client.
*/
export class BunRedisSessionStore extends Store {
private readonly client: InstanceType<typeof Bun.RedisClient>;
private readonly defaultTtl: number;
private readonly prefix: string;
constructor(
client: InstanceType<typeof Bun.RedisClient>,
options: { prefix?: string; ttl?: number } = {},
) {
super();
this.client = client;
this.prefix = options.prefix ?? "sess:";
this.defaultTtl = options.ttl ?? 86_400;
}
private key(sid: string): string {
return this.prefix + sid;
}
private ttl(session: SessionData): number {
if (session.cookie?.expires) {
const ms = Number(new Date(session.cookie.expires)) - Date.now();
return Math.max(1, Math.ceil(ms / 1000));
}
return this.defaultTtl;
}
destroy(sid: string, callback?: (err?: unknown) => void): void {
this.client
.del(this.key(sid))
.then(() => callback?.())
.catch((err) => callback?.(err));
}
get(sid: string, callback: (err: unknown, session?: SessionData | null) => void): void {
this.client
.get(this.key(sid))
.then((data) => {
if (!data) {
return callback(null, null);
}
try {
callback(null, JSON.parse(data) as SessionData);
} catch (e) {
callback(e);
}
})
.catch((err) => callback(err));
}
set(sid: string, session: SessionData, callback?: (err?: unknown) => void): void {
this.client
.setex(this.key(sid), this.ttl(session), JSON.stringify(session))
.then(() => callback?.())
.catch((err) => callback?.(err));
}
touch(sid: string, session: SessionData, callback?: () => void): void {
this.client
.expire(this.key(sid), this.ttl(session))
.then(() => callback?.())
.catch(() => callback?.());
}
}
@@ -926,12 +926,8 @@ export const HC511UserGoal: GoalSubscriptionDocument = {
export const TestingIIDXFolderSP10: FolderDocument = {
title: "Level 10",
game: "iidx-sp",
type: "charts",
folderID: "ed9d8c734447ce67d7135c0067441a98cc81aeaf",
slug: "level-10",
data: {
level: "10",
},
searchTerms: [],
inactive: false,
};
+57 -143
View File
@@ -1,4 +1,3 @@
import type { FilterQuery } from "mongodb";
import type { Game } from "tachi-db";
import { GetChartsByIds, SELECT_CHART, ToChartDocument } from "#lib/db-formats/chart";
@@ -21,14 +20,12 @@ import {
FormatChart,
type GameGroup,
type GoalDocument,
type GoalSubscriptionDocument,
type integer,
LEGACY_GameGroupPTToGame,
type LEGACY_Playtype,
type PBScoreDocument,
type QuestDocument,
type QuestlineDocument,
type QuestSubscriptionDocument,
type ScoreDocument,
} from "tachi-common";
@@ -36,6 +33,16 @@ type MsTimeRange =
| number
| { $gt?: number; $gte?: number; $lt?: number; $lte?: number; $ne?: null };
type UserIDFilter = integer | { $in: Array<integer> };
interface TargetSubFilter {
game?: GameGroup;
playtype?: LEGACY_Playtype;
userID?: UserIDFilter;
timeAchieved?: MsTimeRange;
lastInteraction?: MsTimeRange;
}
function applyMsTimeOnColumn(
column:
| "goal_sub.last_interaction"
@@ -92,7 +99,7 @@ function applyMsTimeOnColumn(
};
}
function whereUserIdOnGoalSub(userID: unknown) {
function whereUserIdOnGoalSub(userID: UserIDFilter | undefined) {
if (userID === undefined) {
return undefined;
}
@@ -101,25 +108,16 @@ function whereUserIdOnGoalSub(userID: unknown) {
return (eb: any) => eb("goal_sub.user_id", "=", userID);
}
if (
userID &&
typeof userID === "object" &&
"$in" in userID &&
Array.isArray((userID as { $in: Array<integer> }).$in)
) {
const ids = (userID as { $in: Array<integer> }).$in;
const ids = userID.$in;
if (ids.length === 0) {
return () => sql`false`;
}
return (eb: any) => eb("goal_sub.user_id", "in", ids);
if (ids.length === 0) {
return () => sql`false`;
}
throw new Error("Unsupported userID filter for goal_sub.");
return (eb: any) => eb("goal_sub.user_id", "in", ids);
}
function whereUserIdOnQuestSub(userID: unknown) {
function whereUserIdOnQuestSub(userID: UserIDFilter | undefined) {
if (userID === undefined) {
return undefined;
}
@@ -128,22 +126,13 @@ function whereUserIdOnQuestSub(userID: unknown) {
return (eb: any) => eb("quest_sub.user_id", "=", userID);
}
if (
userID &&
typeof userID === "object" &&
"$in" in userID &&
Array.isArray((userID as { $in: Array<integer> }).$in)
) {
const ids = (userID as { $in: Array<integer> }).$in;
const ids = userID.$in;
if (ids.length === 0) {
return () => sql`false`;
}
return (eb: any) => eb("quest_sub.user_id", "in", ids);
if (ids.length === 0) {
return () => sql`false`;
}
throw new Error("Unsupported userID filter for quest_sub.");
return (eb: any) => eb("quest_sub.user_id", "in", ids);
}
/**
@@ -266,49 +255,36 @@ export async function HumaniseChartID(chartID: string) {
* Get recently achieved goals for this query.
*
* @param baseQuery - A base query, used to limit results on GPTs or UGPTs.
* @param limit - How many recently achieved goals to search for. MongoDB: `0` means no limit.
* @param limit - How many recently achieved goals to search for. `0` means no limit.
* @returns - The goals and their subs.
*/
export async function GetRecentlyAchievedGoals(
baseQuery: Omit<FilterQuery<GoalSubscriptionDocument>, "achieved">,
{ game, playtype, userID, timeAchieved, lastInteraction }: TargetSubFilter,
limit = 100,
) {
const b = baseQuery as Record<string, unknown>;
let q = DB.selectFrom("goal_sub")
.innerJoin("goal", "goal.id", "goal_sub.goal_id")
.select(SELECT_GOAL_SUB_WITH_GOAL_GAME)
.where("goal_sub.was_instantly_achieved", "=", false)
.where("goal_sub.achieved", "=", true);
const uidFn = whereUserIdOnGoalSub(b.userID);
const uidFn = whereUserIdOnGoalSub(userID);
if (uidFn) {
q = q.where(uidFn);
}
if (b.game !== undefined && b.playtype !== undefined) {
const v3 = LEGACY_GameGroupPTToGame(
b.game as GameGroup,
b.playtype as LEGACY_Playtype,
) as Game;
q = q.where("goal.game", "=", v3);
if (game !== undefined && playtype !== undefined) {
q = q.where("goal.game", "=", LEGACY_GameGroupPTToGame(game, playtype) as Game);
}
const taFn = applyMsTimeOnColumn(
"goal_sub.time_achieved",
b.timeAchieved as MsTimeRange | undefined,
);
const taFn = applyMsTimeOnColumn("goal_sub.time_achieved", timeAchieved);
if (taFn) {
q = q.where(taFn);
}
const liFn = applyMsTimeOnColumn(
"goal_sub.last_interaction",
b.lastInteraction as MsTimeRange | undefined,
);
const liFn = applyMsTimeOnColumn("goal_sub.last_interaction", lastInteraction);
if (liFn) {
q = q.where(liFn);
@@ -341,11 +317,9 @@ export async function GetRecentlyAchievedGoals(
}
export async function GetRecentlyInteractedGoals(
baseQuery: Omit<FilterQuery<GoalSubscriptionDocument>, "achieved">,
{ game, playtype, userID, timeAchieved, lastInteraction }: TargetSubFilter,
limit = 100,
) {
const b = baseQuery as Record<string, unknown>;
let q = DB.selectFrom("goal_sub")
.innerJoin("goal", "goal.id", "goal_sub.goal_id")
.select(SELECT_GOAL_SUB_WITH_GOAL_GAME)
@@ -353,34 +327,23 @@ export async function GetRecentlyInteractedGoals(
.where("goal_sub.achieved", "=", false)
.where("goal_sub.last_interaction", "is not", null);
const uidFn = whereUserIdOnGoalSub(b.userID);
const uidFn = whereUserIdOnGoalSub(userID);
if (uidFn) {
q = q.where(uidFn);
}
if (b.game !== undefined && b.playtype !== undefined) {
const v3 = LEGACY_GameGroupPTToGame(
b.game as GameGroup,
b.playtype as LEGACY_Playtype,
) as Game;
q = q.where("goal.game", "=", v3);
if (game !== undefined && playtype !== undefined) {
q = q.where("goal.game", "=", LEGACY_GameGroupPTToGame(game, playtype) as Game);
}
const taFn = applyMsTimeOnColumn(
"goal_sub.time_achieved",
b.timeAchieved as MsTimeRange | undefined,
);
const taFn = applyMsTimeOnColumn("goal_sub.time_achieved", timeAchieved);
if (taFn) {
q = q.where(taFn);
}
const liFn = applyMsTimeOnColumn(
"goal_sub.last_interaction",
b.lastInteraction as MsTimeRange | undefined,
);
const liFn = applyMsTimeOnColumn("goal_sub.last_interaction", lastInteraction);
if (liFn) {
q = q.where(liFn);
@@ -413,45 +376,32 @@ export async function GetRecentlyInteractedGoals(
}
export async function GetRecentlyAchievedQuests(
baseQuery: Omit<FilterQuery<QuestSubscriptionDocument>, "achieved">,
{ game, playtype, userID, timeAchieved, lastInteraction }: TargetSubFilter,
limit = 100,
) {
const b = baseQuery as Record<string, unknown>;
let q = DB.selectFrom("quest_sub")
.innerJoin("quest", "quest.id", "quest_sub.quest_id")
.select(SELECT_QUEST_SUB_WITH_QUEST_GAME)
.where("quest_sub.was_instantly_achieved", "=", false)
.where("quest_sub.achieved", "=", true);
const uidFn = whereUserIdOnQuestSub(b.userID);
const uidFn = whereUserIdOnQuestSub(userID);
if (uidFn) {
q = q.where(uidFn);
}
if (b.game !== undefined && b.playtype !== undefined) {
const v3 = LEGACY_GameGroupPTToGame(
b.game as GameGroup,
b.playtype as LEGACY_Playtype,
) as Game;
q = q.where("quest.game", "=", v3);
if (game !== undefined && playtype !== undefined) {
q = q.where("quest.game", "=", LEGACY_GameGroupPTToGame(game, playtype) as Game);
}
const taFn = applyMsTimeOnColumn(
"quest_sub.time_achieved",
b.timeAchieved as MsTimeRange | undefined,
);
const taFn = applyMsTimeOnColumn("quest_sub.time_achieved", timeAchieved);
if (taFn) {
q = q.where(taFn);
}
const liFn = applyMsTimeOnColumn(
"quest_sub.last_interaction",
b.lastInteraction as MsTimeRange | undefined,
);
const liFn = applyMsTimeOnColumn("quest_sub.last_interaction", lastInteraction);
if (liFn) {
q = q.where(liFn);
@@ -483,11 +433,9 @@ export async function GetRecentlyAchievedQuests(
}
export async function GetRecentlyInteractedQuests(
baseQuery: Omit<FilterQuery<QuestSubscriptionDocument>, "achieved">,
{ game, playtype, userID, timeAchieved, lastInteraction }: TargetSubFilter,
limit = 100,
) {
const b = baseQuery as Record<string, unknown>;
let q = DB.selectFrom("quest_sub")
.innerJoin("quest", "quest.id", "quest_sub.quest_id")
.select(SELECT_QUEST_SUB_WITH_QUEST_GAME)
@@ -495,34 +443,23 @@ export async function GetRecentlyInteractedQuests(
.where("quest_sub.achieved", "=", false)
.where("quest_sub.was_instantly_achieved", "=", false);
const uidFn = whereUserIdOnQuestSub(b.userID);
const uidFn = whereUserIdOnQuestSub(userID);
if (uidFn) {
q = q.where(uidFn);
}
if (b.game !== undefined && b.playtype !== undefined) {
const v3 = LEGACY_GameGroupPTToGame(
b.game as GameGroup,
b.playtype as LEGACY_Playtype,
) as Game;
q = q.where("quest.game", "=", v3);
if (game !== undefined && playtype !== undefined) {
q = q.where("quest.game", "=", LEGACY_GameGroupPTToGame(game, playtype) as Game);
}
const taFn = applyMsTimeOnColumn(
"quest_sub.time_achieved",
b.timeAchieved as MsTimeRange | undefined,
);
const taFn = applyMsTimeOnColumn("quest_sub.time_achieved", timeAchieved);
if (taFn) {
q = q.where(taFn);
}
const liFn = applyMsTimeOnColumn(
"quest_sub.last_interaction",
b.lastInteraction as MsTimeRange | undefined,
);
const liFn = applyMsTimeOnColumn("quest_sub.last_interaction", lastInteraction);
if (liFn) {
q = q.where(liFn);
@@ -553,7 +490,7 @@ export async function GetRecentlyInteractedQuests(
return { quests, questSubs };
}
function whereUserIdOnGoalSubForAggregate(userID: unknown) {
function whereUserIdOnGoalSubForAggregate(userID: UserIDFilter | undefined) {
if (userID === undefined) {
return undefined;
}
@@ -562,49 +499,33 @@ function whereUserIdOnGoalSubForAggregate(userID: unknown) {
return (eb: any) => eb("goal_sub.user_id", "=", userID);
}
if (
userID &&
typeof userID === "object" &&
"$in" in userID &&
Array.isArray((userID as { $in: Array<integer> }).$in)
) {
const ids = (userID as { $in: Array<integer> }).$in;
const ids = userID.$in;
if (ids.length === 0) {
return () => sql`false`;
}
return (eb: any) => eb("goal_sub.user_id", "in", ids);
if (ids.length === 0) {
return () => sql`false`;
}
throw new Error("Unsupported userID filter for goal_sub aggregate.");
return (eb: any) => eb("goal_sub.user_id", "in", ids);
}
export async function GetMostSubscribedGoals(
query: FilterQuery<GoalSubscriptionDocument>,
{ game, playtype, userID }: TargetSubFilter,
limit = 100,
): Promise<Array<{ __subscriptions: integer } & GoalDocument>> {
const b = query as Record<string, unknown>;
let q = DB.selectFrom("goal_sub")
.innerJoin("goal", "goal.id", "goal_sub.goal_id")
.select("goal_sub.goal_id")
.select(sql<number>`count(*)::int`.as("subscriptions"))
.groupBy("goal_sub.goal_id");
const uidFn = whereUserIdOnGoalSubForAggregate(b.userID);
const uidFn = whereUserIdOnGoalSubForAggregate(userID);
if (uidFn) {
q = q.where(uidFn);
}
if (b.game !== undefined && b.playtype !== undefined) {
const v3 = LEGACY_GameGroupPTToGame(
b.game as GameGroup,
b.playtype as LEGACY_Playtype,
) as Game;
q = q.where("goal.game", "=", v3);
if (game !== undefined && playtype !== undefined) {
q = q.where("goal.game", "=", LEGACY_GameGroupPTToGame(game, playtype) as Game);
}
const ranked = await q.orderBy("subscriptions", "desc").limit(limit).execute();
@@ -639,24 +560,17 @@ export async function GetMostSubscribedGoals(
}
export async function GetMostSubscribedQuests(
query: FilterQuery<QuestSubscriptionDocument>,
{ game, playtype }: TargetSubFilter,
limit = 100,
): Promise<Array<{ __subscriptions: integer } & QuestDocument>> {
const b = query as Record<string, unknown>;
let q = DB.selectFrom("quest")
.innerJoin("quest_sub", "quest_sub.quest_id", "quest.id")
.select("quest.id")
.select(sql<number>`count(*)::int`.as("subscriptions"))
.groupBy("quest.id");
if (b.game !== undefined && b.playtype !== undefined) {
const v3 = LEGACY_GameGroupPTToGame(
b.game as GameGroup,
b.playtype as LEGACY_Playtype,
) as Game;
q = q.where("quest.game", "=", v3);
if (game !== undefined && playtype !== undefined) {
q = q.where("quest.game", "=", LEGACY_GameGroupPTToGame(game, playtype) as Game);
}
const ranked = await q.orderBy("subscriptions", "desc").limit(limit).execute();
+1
View File
@@ -4,6 +4,7 @@
"module": "Preserve",
"moduleResolution": "bundler",
"lib": ["ES2022"],
"types": ["bun"],
"paths": {
"#*": ["./src/*"]
}
+1 -2
View File
@@ -4,8 +4,7 @@ import pg from "pg";
import { ensureTestCdnBucket } from "./src/test-utils/ensure-test-cdn-bucket";
// See note in vitest.setup.ts about POSTGRES_TEST_HOST + tachi-postgres-test.
const POSTGRES_HOST = process.env.POSTGRES_TEST_HOST ?? "tachi-postgres";
const POSTGRES_HOST = process.env.POSTGRES_TEST_HOST ?? "tachi-postgres-test";
const POSTGRES_USER = "tachi";
const POSTGRES_PASS = "tachi";
const TEMPLATE_DB = "tachi_server_test_template";
+1 -1
View File
@@ -28,7 +28,7 @@ const WORKER_DB_NAME = `tachi_server_test_${WORKER_ID}`;
// `tachi-postgres-test` (tmpfs + fsync=off, see docker-compose-dev.yml) is the
// preferred backend for tests. Fall back to the dev Postgres if the dedicated
// service isn't running so a freshly-cloned repo still passes tests.
const POSTGRES_HOST = process.env.POSTGRES_TEST_HOST ?? "tachi-postgres";
const POSTGRES_HOST = process.env.POSTGRES_TEST_HOST ?? "tachi-postgres-test";
const POSTGRES_USER = "tachi";
const POSTGRES_PASS = "tachi";
+1 -1
View File
@@ -6,7 +6,7 @@
"main": "index.js",
"scripts": {
"build": "tsgo -b",
"calc-v1": "ts-node src/main.ts $@",
"calc-v1": "bun run src/main.ts $@",
"lint": "eslint .",
"lint-fix": "eslint . --fix",
"typecheck": "tsgo --noEmit",