Merge pull request #12 from pikasTech/feat/temporal-native
feat: 建立 Temporal native 开发链
This commit is contained in:
File diff suppressed because one or more lines are too long
+2
-3
@@ -1,4 +1,4 @@
|
||||
FROM oven/bun:1.2.21-slim
|
||||
FROM oven/bun:1.3.13-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -8,7 +8,6 @@ RUN apt-get update \
|
||||
|
||||
COPY package.json bun.lock ./
|
||||
RUN bun install --frozen-lockfile --production
|
||||
|
||||
COPY config ./config
|
||||
COPY src ./src
|
||||
COPY static ./static
|
||||
@@ -16,4 +15,4 @@ COPY static ./static
|
||||
ENV NODE_ENV=production
|
||||
EXPOSE 8080
|
||||
|
||||
CMD ["bun", "src/server.ts", "--config", "config/sub2rank.yaml", "--runtime", "k8s"]
|
||||
CMD ["bun", "src/api.ts", "--config", "config/sub2rank.yaml", "--runtime", "k8s"]
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"": {
|
||||
"name": "sub2rank",
|
||||
"dependencies": {
|
||||
"@temporalio/client": "^1.20.3",
|
||||
"@temporalio/worker": "^1.20.3",
|
||||
"@temporalio/workflow": "^1.20.3",
|
||||
"luxon": "^3.7.2",
|
||||
"yaml": "^2.8.1",
|
||||
},
|
||||
@@ -12,24 +15,445 @@
|
||||
"@types/bun": "^1.2.21",
|
||||
"@types/luxon": "^3.7.1",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^8.1.5",
|
||||
},
|
||||
},
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"@swc/core",
|
||||
"protobufjs",
|
||||
],
|
||||
"packages": {
|
||||
"@emnapi/core": ["@emnapi/core@1.11.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ=="],
|
||||
|
||||
"@emnapi/runtime": ["@emnapi/runtime@1.11.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw=="],
|
||||
|
||||
"@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA=="],
|
||||
|
||||
"@grpc/grpc-js": ["@grpc/grpc-js@1.14.4", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-k9Dj3DV/itK9D06Y8f190Qgop7/Ui+D0njFV3LHMPwPT75DpXLQohE9Wmz0QElrJnzsjB7KPWiKJbOl7IPDArQ=="],
|
||||
|
||||
"@grpc/proto-loader": ["@grpc/proto-loader@0.8.1", "", { "dependencies": { "lodash.camelcase": "^4.3.0", "long": "^5.0.0", "protobufjs": "^7.5.5", "yargs": "^17.7.2" }, "bin": { "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" } }, "sha512-wtF6h+DY6M3YaDBPAmvuuA6jV8Sif9MjtOI5euKFWRgCDl5PeDpPsHR9u2l6St5ceY8AZgoNDww5+HvEsXFsGg=="],
|
||||
|
||||
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
|
||||
|
||||
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
|
||||
|
||||
"@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@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=="],
|
||||
|
||||
"@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="],
|
||||
|
||||
"@jsonjoy.com/base64": ["@jsonjoy.com/base64@1.1.2", "", { "peerDependencies": { "tslib": "2" } }, "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA=="],
|
||||
|
||||
"@jsonjoy.com/buffers": ["@jsonjoy.com/buffers@17.67.0", "", { "peerDependencies": { "tslib": "2" } }, "sha512-tfExRpYxBvi32vPs9ZHaTjSP4fHAfzSmcahOfNxtvGHcyJel+aibkPlGeBB+7AoC6hL7lXIE++8okecBxx7lcw=="],
|
||||
|
||||
"@jsonjoy.com/codegen": ["@jsonjoy.com/codegen@1.0.0", "", { "peerDependencies": { "tslib": "2" } }, "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g=="],
|
||||
|
||||
"@jsonjoy.com/fs-core": ["@jsonjoy.com/fs-core@4.64.0", "", { "dependencies": { "@jsonjoy.com/fs-node-builtins": "4.64.0", "@jsonjoy.com/fs-node-utils": "4.64.0", "thingies": "^2.5.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-zs2TAq7Six5jgMuoMNjpspAvOP3mhtgq/k1UyQodEzCtQi/N83y2/y+zcvnZSGp/Rxq96DBN+bValOBQAyn/ew=="],
|
||||
|
||||
"@jsonjoy.com/fs-fsa": ["@jsonjoy.com/fs-fsa@4.64.0", "", { "dependencies": { "@jsonjoy.com/fs-core": "4.64.0", "@jsonjoy.com/fs-node-builtins": "4.64.0", "@jsonjoy.com/fs-node-utils": "4.64.0", "thingies": "^2.5.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-nMWOVbkLFyEgmXZih3wyvxA9XpgyyqyfrINMHvEFqhi7uqfRl7c9ERJt6yX7vgMPrB9Uo+OJO+Spa0cFzPD01w=="],
|
||||
|
||||
"@jsonjoy.com/fs-node": ["@jsonjoy.com/fs-node@4.64.0", "", { "dependencies": { "@jsonjoy.com/fs-core": "4.64.0", "@jsonjoy.com/fs-node-builtins": "4.64.0", "@jsonjoy.com/fs-node-utils": "4.64.0", "@jsonjoy.com/fs-print": "4.64.0", "@jsonjoy.com/fs-snapshot": "4.64.0", "glob-to-regex.js": "^1.0.0", "thingies": "^2.5.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-dO+NNkODbUli4uV42bcNrrLvq5rE7SNpdZ5TNd0dtbLsAaNK3MDiIC9lUi+brboGoIjW6vd2fB1qao60nrk5xA=="],
|
||||
|
||||
"@jsonjoy.com/fs-node-builtins": ["@jsonjoy.com/fs-node-builtins@4.64.0", "", { "peerDependencies": { "tslib": "2" } }, "sha512-/o7WRFhUWaM/fOrslwLZGnzn4RmRILykn+lAL+mNObqqRNw+CQSiij6hpCeZ+C7buhdoVo7go/OYqzaSUfDYmA=="],
|
||||
|
||||
"@jsonjoy.com/fs-node-to-fsa": ["@jsonjoy.com/fs-node-to-fsa@4.64.0", "", { "dependencies": { "@jsonjoy.com/fs-fsa": "4.64.0", "@jsonjoy.com/fs-node-builtins": "4.64.0", "@jsonjoy.com/fs-node-utils": "4.64.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-WDD9WVs0hb7UAEKTgZW2f66WDrbj7gIIWwpP3spbLyXa0rghtUaFTB8L4gdR3ZCWwiKIsj38/CNijpVmpnuPUw=="],
|
||||
|
||||
"@jsonjoy.com/fs-node-utils": ["@jsonjoy.com/fs-node-utils@4.64.0", "", { "dependencies": { "@jsonjoy.com/fs-node-builtins": "4.64.0", "glob-to-regex.js": "^1.0.1" }, "peerDependencies": { "tslib": "2" } }, "sha512-k5Indsx9hWW9xSF7Y6oSKKwtCUNhzZxadub3owhIlitc+iMRVlPPdX2duTKQWBL3qNWpXya8jykgaaWpheeS4w=="],
|
||||
|
||||
"@jsonjoy.com/fs-print": ["@jsonjoy.com/fs-print@4.64.0", "", { "dependencies": { "@jsonjoy.com/fs-node-utils": "4.64.0", "tree-dump": "^1.1.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-PHZFccchvkhWrwPWHjmVAhbC3vSHCtyZvlZfJJ3ho2bnzl450hXri6/8e6pbkWdH+SkmLXNml0sV8e5HDAfxKw=="],
|
||||
|
||||
"@jsonjoy.com/fs-snapshot": ["@jsonjoy.com/fs-snapshot@4.64.0", "", { "dependencies": { "@jsonjoy.com/buffers": "^17.65.0", "@jsonjoy.com/fs-node-utils": "4.64.0", "@jsonjoy.com/json-pack": "^17.65.0", "@jsonjoy.com/util": "^17.65.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-oM7UDeL83q6NBzzsfKAsYKXKVXlykKFqqOLh4xZZKAzzROTlInkPbc6LTDGThEOnPiFiUzA7tYziHG9xavd76Q=="],
|
||||
|
||||
"@jsonjoy.com/json-pack": ["@jsonjoy.com/json-pack@1.21.0", "", { "dependencies": { "@jsonjoy.com/base64": "^1.1.2", "@jsonjoy.com/buffers": "^1.2.0", "@jsonjoy.com/codegen": "^1.0.0", "@jsonjoy.com/json-pointer": "^1.0.2", "@jsonjoy.com/util": "^1.9.0", "hyperdyperid": "^1.2.0", "thingies": "^2.5.0", "tree-dump": "^1.1.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg=="],
|
||||
|
||||
"@jsonjoy.com/json-pointer": ["@jsonjoy.com/json-pointer@1.0.2", "", { "dependencies": { "@jsonjoy.com/codegen": "^1.0.0", "@jsonjoy.com/util": "^1.9.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg=="],
|
||||
|
||||
"@jsonjoy.com/util": ["@jsonjoy.com/util@1.9.0", "", { "dependencies": { "@jsonjoy.com/buffers": "^1.0.0", "@jsonjoy.com/codegen": "^1.0.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ=="],
|
||||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.6", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" } }, "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg=="],
|
||||
|
||||
"@oxc-project/types": ["@oxc-project/types@0.139.0", "", {}, "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw=="],
|
||||
|
||||
"@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="],
|
||||
|
||||
"@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="],
|
||||
|
||||
"@protobufjs/codegen": ["@protobufjs/codegen@2.0.5", "", {}, "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g=="],
|
||||
|
||||
"@protobufjs/eventemitter": ["@protobufjs/eventemitter@1.1.1", "", {}, "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg=="],
|
||||
|
||||
"@protobufjs/fetch": ["@protobufjs/fetch@1.1.1", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.1" } }, "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw=="],
|
||||
|
||||
"@protobufjs/float": ["@protobufjs/float@1.0.2", "", {}, "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="],
|
||||
|
||||
"@protobufjs/path": ["@protobufjs/path@1.1.2", "", {}, "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="],
|
||||
|
||||
"@protobufjs/pool": ["@protobufjs/pool@1.1.0", "", {}, "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="],
|
||||
|
||||
"@protobufjs/utf8": ["@protobufjs/utf8@1.1.2", "", {}, "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug=="],
|
||||
|
||||
"@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.1.5", "", { "os": "android", "cpu": "arm64" }, "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ=="],
|
||||
|
||||
"@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.1.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw=="],
|
||||
|
||||
"@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.1.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g=="],
|
||||
|
||||
"@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.1.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA=="],
|
||||
|
||||
"@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.1.5", "", { "os": "linux", "cpu": "arm" }, "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.1.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q=="],
|
||||
|
||||
"@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.1.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA=="],
|
||||
|
||||
"@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.1.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg=="],
|
||||
|
||||
"@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.1.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.1.5", "", { "os": "linux", "cpu": "x64" }, "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ=="],
|
||||
|
||||
"@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.1.5", "", { "os": "linux", "cpu": "x64" }, "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg=="],
|
||||
|
||||
"@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.1.5", "", { "os": "none", "cpu": "arm64" }, "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw=="],
|
||||
|
||||
"@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.1.5", "", { "dependencies": { "@emnapi/core": "1.11.1", "@emnapi/runtime": "1.11.1", "@napi-rs/wasm-runtime": "^1.1.6" }, "cpu": "none" }, "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA=="],
|
||||
|
||||
"@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.1.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw=="],
|
||||
|
||||
"@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.1.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA=="],
|
||||
|
||||
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.1", "", {}, "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw=="],
|
||||
|
||||
"@swc/core": ["@swc/core@1.15.43", "", { "dependencies": { "@swc/counter": "^0.1.3", "@swc/types": "^0.1.27" }, "optionalDependencies": { "@swc/core-darwin-arm64": "1.15.43", "@swc/core-darwin-x64": "1.15.43", "@swc/core-linux-arm-gnueabihf": "1.15.43", "@swc/core-linux-arm64-gnu": "1.15.43", "@swc/core-linux-arm64-musl": "1.15.43", "@swc/core-linux-ppc64-gnu": "1.15.43", "@swc/core-linux-s390x-gnu": "1.15.43", "@swc/core-linux-x64-gnu": "1.15.43", "@swc/core-linux-x64-musl": "1.15.43", "@swc/core-win32-arm64-msvc": "1.15.43", "@swc/core-win32-ia32-msvc": "1.15.43", "@swc/core-win32-x64-msvc": "1.15.43" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" }, "optionalPeers": ["@swc/helpers"] }, "sha512-1CuKjFkPxIgGdeHVuNbkxmBxkcbdc08u0aiI43pFq6yY1tTVKmXT9hFEooyyKs/sJ3xf1GPHyEwTtk9Xl8dvQw=="],
|
||||
|
||||
"@swc/core-darwin-arm64": ["@swc/core-darwin-arm64@1.15.43", "", { "os": "darwin", "cpu": "arm64" }, "sha512-v1aVuvXdo/BHxJzco9V2xpHrvwWmhfS8t6gziY5wJxd+Z2h8AeJRnAwPD8itCDaGXVBwJ/CaKfxEzTkG0Va0OA=="],
|
||||
|
||||
"@swc/core-darwin-x64": ["@swc/core-darwin-x64@1.15.43", "", { "os": "darwin", "cpu": "x64" }, "sha512-lp3d4Lamc8dt5huYdGLSR+9hLxmfr1jb0l+4XXG2zPqZwYWRN9R0U2qYoTrggiU2RWW0oV9VbWM3kBnqIc2kdQ=="],
|
||||
|
||||
"@swc/core-linux-arm-gnueabihf": ["@swc/core-linux-arm-gnueabihf@1.15.43", "", { "os": "linux", "cpu": "arm" }, "sha512-JWTQQELtsG5GgphDrr/XqqmM2pDN3cZqbMS0Mrg+iTiXL3F74sn/S2IyYE/5u4h2KLkTf9qQ7dXyxsbx7YzkeA=="],
|
||||
|
||||
"@swc/core-linux-arm64-gnu": ["@swc/core-linux-arm64-gnu@1.15.43", "", { "os": "linux", "cpu": "arm64" }, "sha512-B4otJRdPWIsmiSBf0uG7Z/+vMWmkufjz5MmYxubwKuZazDW14Zd3symga1N62QR4RT+kEFeHEgsXfZGyn/w0hw=="],
|
||||
|
||||
"@swc/core-linux-arm64-musl": ["@swc/core-linux-arm64-musl@1.15.43", "", { "os": "linux", "cpu": "arm64" }, "sha512-6zB6OnpViBxYy4tgY3v2i6AZY9fwkcHZ032UOwtwUuW1d19sdT07qF0kZe6/3UR1tUaK6jjg2rmVcUIBCEYVjQ=="],
|
||||
|
||||
"@swc/core-linux-ppc64-gnu": ["@swc/core-linux-ppc64-gnu@1.15.43", "", { "os": "linux", "cpu": "ppc64" }, "sha512-coxE1ZWdB3uSDVNoEtYNrRi/1epvckZx9cTJ8ICUxTMTxGk+yvQ/Twacp3ruZSaMPGCriUjP86C37VhaT6nyRg=="],
|
||||
|
||||
"@swc/core-linux-s390x-gnu": ["@swc/core-linux-s390x-gnu@1.15.43", "", { "os": "linux", "cpu": "s390x" }, "sha512-lXfLhs+LpBsD5inuYx+YDH5WsPPBQ95KPUiy8P5wq9ob9xKDZFqwNfU2QW6bGO8NqRO/H9JQomTSt5Yyh+FGfA=="],
|
||||
|
||||
"@swc/core-linux-x64-gnu": ["@swc/core-linux-x64-gnu@1.15.43", "", { "os": "linux", "cpu": "x64" }, "sha512-07XnKwTmKy8TGOZG3D9fRnLWGynxPjwQnZLVmBFbo6F+7vHYzBIOuwXEhemrChBWb6yDNZsVCcMWCPX6FDD2xg=="],
|
||||
|
||||
"@swc/core-linux-x64-musl": ["@swc/core-linux-x64-musl@1.15.43", "", { "os": "linux", "cpu": "x64" }, "sha512-TJc+bsSIaBh+hZvZ5GRtW/K1bw66TJ9vsUwvVIsZdiWxU5ObLwZvfcnZ3UpgVfMnFibRes9uriJrQNBHEEogRQ=="],
|
||||
|
||||
"@swc/core-win32-arm64-msvc": ["@swc/core-win32-arm64-msvc@1.15.43", "", { "os": "win32", "cpu": "arm64" }, "sha512-jfd7s2/bUQYkOHLs+LWQNKZdmDa8+sufKLllhpWAhVQ2GDCwsHe3vR/j+OSiItZNtkzFuaawa3+SAKz9y5gYfw=="],
|
||||
|
||||
"@swc/core-win32-ia32-msvc": ["@swc/core-win32-ia32-msvc@1.15.43", "", { "os": "win32", "cpu": "ia32" }, "sha512-rLAE8JvucqEW1ZGohxPQrQWPBQeJG4+ypKbWfdlU/qmKScvCkxf9/Jxnzki1dkUQCQ7P5Enp13RlvqOlvx/32g=="],
|
||||
|
||||
"@swc/core-win32-x64-msvc": ["@swc/core-win32-x64-msvc@1.15.43", "", { "os": "win32", "cpu": "x64" }, "sha512-h8MLDHZcfIukwQWj03rIJZx1I0E81AYj2X7J/nGErG4nz+QAv6G1Z+peotvinL3lqpbo32tLYSMFo32/ySzxKg=="],
|
||||
|
||||
"@swc/counter": ["@swc/counter@0.1.3", "", {}, "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ=="],
|
||||
|
||||
"@swc/types": ["@swc/types@0.1.27", "", { "dependencies": { "@swc/counter": "^0.1.3" } }, "sha512-K6h3iUlqeM946U4sXFYeahefR1YBbXJvko+hv8WS8/0BNJ4OHiHRywMnQUJCqkR7Y9+hqQ1TvEpiKqUhz7NEFg=="],
|
||||
|
||||
"@temporalio/activity": ["@temporalio/activity@1.20.3", "", { "dependencies": { "@temporalio/client": "1.20.3", "@temporalio/common": "1.20.3" } }, "sha512-7jtIkCkUe6wwVrnlCd/7DmnVcu1/r4KFvVHvPVszd/EXAz1ziYfsjhenLfxWIvyxSIU9v3tC96uHODN3ABdv4A=="],
|
||||
|
||||
"@temporalio/client": ["@temporalio/client@1.20.3", "", { "dependencies": { "@grpc/grpc-js": "^1.12.4", "@temporalio/common": "1.20.3", "@temporalio/proto": "1.20.3", "abort-controller": "^3.0.0", "long": "^5.2.3", "nexus-rpc": "^0.0.2", "uuid": "^11.1.0" } }, "sha512-RmEX0Z7h3mWq8PMqeDSbbDZPK8IweajGnRsghiJI0fnaWx61YeW8bhAAKfD/iSop+0PM0oe0KaW5XPAKEY4XEw=="],
|
||||
|
||||
"@temporalio/common": ["@temporalio/common@1.20.3", "", { "dependencies": { "@temporalio/proto": "1.20.3", "long": "^5.2.3", "ms": "3.0.0-canary.1", "nexus-rpc": "^0.0.2", "proto3-json-serializer": "^2.0.0" } }, "sha512-VM9GyUAa7tl413nM24mcBhUfUeqcGazmDkx/GfDA0c+KuEVusclMg9eqa63EDv/rguC/YhWaXu6q5Kl8+99GSw=="],
|
||||
|
||||
"@temporalio/core-bridge": ["@temporalio/core-bridge@1.20.3", "", { "dependencies": { "@grpc/grpc-js": "^1.12.4", "@temporalio/common": "1.20.3" } }, "sha512-i7hu6lg6bm2esyYYDwHh0XmGkOWSL5iicLjC7DNRU4b4As/dKcRxYMxNY6dXtKzbM94xhmX6Fh4j5/fxen/4kw=="],
|
||||
|
||||
"@temporalio/nexus": ["@temporalio/nexus@1.20.3", "", { "dependencies": { "@temporalio/client": "1.20.3", "@temporalio/common": "1.20.3", "@temporalio/proto": "1.20.3", "long": "^5.2.3", "nexus-rpc": "^0.0.2" } }, "sha512-OSl73enJ9M8OMk2QaGHr4cQ1DDLEkjMhU1Z3tBt2zbWkKDs2IHKkl1wU/rMeXg7AJf5uqHcp209SBaDN/rqDiA=="],
|
||||
|
||||
"@temporalio/proto": ["@temporalio/proto@1.20.3", "", { "dependencies": { "long": "^5.2.3", "protobufjs": "^7.6.4" } }, "sha512-RdbHC3zH+N0QqG1Q38h2LilFIQ3lbGLVmLYCyNcV+PPfzrtrZ8pwBACpSGwEuF7WWFziLAhJ2oMVXTnmXY5ocQ=="],
|
||||
|
||||
"@temporalio/worker": ["@temporalio/worker@1.20.3", "", { "dependencies": { "@grpc/grpc-js": "^1.12.4", "@swc/core": "^1.3.102", "@temporalio/activity": "1.20.3", "@temporalio/client": "1.20.3", "@temporalio/common": "1.20.3", "@temporalio/core-bridge": "1.20.3", "@temporalio/nexus": "1.20.3", "@temporalio/proto": "1.20.3", "@temporalio/workflow": "1.20.3", "heap-js": "^2.6.0", "memfs": "^4.6.0", "nexus-rpc": "^0.0.2", "protobufjs": "^7.6.4", "rxjs": "^7.8.1", "source-map": "^0.7.4", "source-map-loader": "^5.0.0", "supports-color": "^8.1.1", "swc-loader": "^0.2.3", "unionfs": "^4.5.1", "webpack": "^5.108.4" } }, "sha512-29W39KxGoz8QfiELT5al1jr+nnZEnpWCjr5gYT2a6ZlXr2WKxbJw4rRBy+OyGwHs5kGT0pPYayYvwcDTvWRpQQ=="],
|
||||
|
||||
"@temporalio/workflow": ["@temporalio/workflow@1.20.3", "", { "dependencies": { "@temporalio/common": "1.20.3", "@temporalio/proto": "1.20.3", "nexus-rpc": "^0.0.2" } }, "sha512-6LRfnWp3HcvqvKFwbOhs5iDQkHNIM5Rxtxhsi2E5ViPDvJMBqlQ0sQ4oRo5SEgr5Bri650i7GfEQQJMVl7xoKg=="],
|
||||
|
||||
"@tybys/wasm-util": ["@tybys/wasm-util@0.10.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg=="],
|
||||
|
||||
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="],
|
||||
|
||||
"@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
|
||||
|
||||
"@types/luxon": ["@types/luxon@3.7.2", "", {}, "sha512-gW+Oib+vUtGJBtNC8V9Reww0oIpusw+4m81uncg9REGZAJfqOQHfo/nkabnc7w0QReXyPqjrbWMJk6NuAkiX3Q=="],
|
||||
|
||||
"@types/node": ["@types/node@26.1.1", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw=="],
|
||||
|
||||
"@webassemblyjs/ast": ["@webassemblyjs/ast@1.14.1", "", { "dependencies": { "@webassemblyjs/helper-numbers": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2" } }, "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ=="],
|
||||
|
||||
"@webassemblyjs/floating-point-hex-parser": ["@webassemblyjs/floating-point-hex-parser@1.13.2", "", {}, "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA=="],
|
||||
|
||||
"@webassemblyjs/helper-api-error": ["@webassemblyjs/helper-api-error@1.13.2", "", {}, "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ=="],
|
||||
|
||||
"@webassemblyjs/helper-buffer": ["@webassemblyjs/helper-buffer@1.14.1", "", {}, "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA=="],
|
||||
|
||||
"@webassemblyjs/helper-numbers": ["@webassemblyjs/helper-numbers@1.13.2", "", { "dependencies": { "@webassemblyjs/floating-point-hex-parser": "1.13.2", "@webassemblyjs/helper-api-error": "1.13.2", "@xtuc/long": "4.2.2" } }, "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA=="],
|
||||
|
||||
"@webassemblyjs/helper-wasm-bytecode": ["@webassemblyjs/helper-wasm-bytecode@1.13.2", "", {}, "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA=="],
|
||||
|
||||
"@webassemblyjs/helper-wasm-section": ["@webassemblyjs/helper-wasm-section@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/wasm-gen": "1.14.1" } }, "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw=="],
|
||||
|
||||
"@webassemblyjs/ieee754": ["@webassemblyjs/ieee754@1.13.2", "", { "dependencies": { "@xtuc/ieee754": "^1.2.0" } }, "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw=="],
|
||||
|
||||
"@webassemblyjs/leb128": ["@webassemblyjs/leb128@1.13.2", "", { "dependencies": { "@xtuc/long": "4.2.2" } }, "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw=="],
|
||||
|
||||
"@webassemblyjs/utf8": ["@webassemblyjs/utf8@1.13.2", "", {}, "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ=="],
|
||||
|
||||
"@webassemblyjs/wasm-edit": ["@webassemblyjs/wasm-edit@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/helper-wasm-section": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-opt": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1", "@webassemblyjs/wast-printer": "1.14.1" } }, "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ=="],
|
||||
|
||||
"@webassemblyjs/wasm-gen": ["@webassemblyjs/wasm-gen@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg=="],
|
||||
|
||||
"@webassemblyjs/wasm-opt": ["@webassemblyjs/wasm-opt@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-buffer": "1.14.1", "@webassemblyjs/wasm-gen": "1.14.1", "@webassemblyjs/wasm-parser": "1.14.1" } }, "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw=="],
|
||||
|
||||
"@webassemblyjs/wasm-parser": ["@webassemblyjs/wasm-parser@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@webassemblyjs/helper-api-error": "1.13.2", "@webassemblyjs/helper-wasm-bytecode": "1.13.2", "@webassemblyjs/ieee754": "1.13.2", "@webassemblyjs/leb128": "1.13.2", "@webassemblyjs/utf8": "1.13.2" } }, "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ=="],
|
||||
|
||||
"@webassemblyjs/wast-printer": ["@webassemblyjs/wast-printer@1.14.1", "", { "dependencies": { "@webassemblyjs/ast": "1.14.1", "@xtuc/long": "4.2.2" } }, "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw=="],
|
||||
|
||||
"@xtuc/ieee754": ["@xtuc/ieee754@1.2.0", "", {}, "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA=="],
|
||||
|
||||
"@xtuc/long": ["@xtuc/long@4.2.2", "", {}, "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ=="],
|
||||
|
||||
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
|
||||
|
||||
"acorn": ["acorn@8.17.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="],
|
||||
|
||||
"acorn-import-phases": ["acorn-import-phases@1.0.4", "", { "peerDependencies": { "acorn": "^8.14.0" } }, "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ=="],
|
||||
|
||||
"ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="],
|
||||
|
||||
"ajv-formats": ["ajv-formats@2.1.1", "", { "dependencies": { "ajv": "^8.0.0" } }, "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA=="],
|
||||
|
||||
"ajv-keywords": ["ajv-keywords@5.1.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3" }, "peerDependencies": { "ajv": "^8.8.2" } }, "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"baseline-browser-mapping": ["baseline-browser-mapping@2.10.43", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ=="],
|
||||
|
||||
"browserslist": ["browserslist@4.28.6", "", { "dependencies": { "baseline-browser-mapping": "^2.10.42", "caniuse-lite": "^1.0.30001803", "electron-to-chromium": "^1.5.389", "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw=="],
|
||||
|
||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||
|
||||
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
||||
|
||||
"caniuse-lite": ["caniuse-lite@1.0.30001806", "", {}, "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw=="],
|
||||
|
||||
"chrome-trace-event": ["chrome-trace-event@1.0.4", "", {}, "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ=="],
|
||||
|
||||
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
|
||||
|
||||
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
|
||||
|
||||
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
|
||||
|
||||
"commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
|
||||
|
||||
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
|
||||
|
||||
"electron-to-chromium": ["electron-to-chromium@1.5.392", "", {}, "sha512-1yQq3VQCZRwsnYc67Oc+1fge6Lwtn0hzi6zmEVkB61Zx21kTbwJAW4dFLadl5Rc1tKhG/kSpYXnfiAhu0f0a1g=="],
|
||||
|
||||
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
|
||||
|
||||
"enhanced-resolve": ["enhanced-resolve@5.24.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-rpsZEGT1jFuve6QlpyRp9ckQ+kN61hvF9BzCPyMdaKTm8UJce96KBn3sorXOFXlzjPrs3Vc4T1NsSroZ3PxlFw=="],
|
||||
|
||||
"es-module-lexer": ["es-module-lexer@2.3.1", "", {}, "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA=="],
|
||||
|
||||
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
|
||||
|
||||
"eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="],
|
||||
|
||||
"esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
|
||||
|
||||
"estraverse": ["estraverse@4.3.0", "", {}, "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="],
|
||||
|
||||
"event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
|
||||
|
||||
"events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
|
||||
|
||||
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
|
||||
|
||||
"fast-uri": ["fast-uri@3.1.3", "", {}, "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
"fs-monkey": ["fs-monkey@1.1.0", "", {}, "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
|
||||
|
||||
"glob-to-regex.js": ["glob-to-regex.js@1.2.0", "", { "peerDependencies": { "tslib": "2" } }, "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ=="],
|
||||
|
||||
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
|
||||
|
||||
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
|
||||
|
||||
"heap-js": ["heap-js@2.7.1", "", {}, "sha512-EQfezRg0NCZGNlhlDR3Evrw1FVL2G3LhU7EgPoxufQKruNBSYA8MiRPHeWbU+36o+Fhel0wMwM+sLEiBAlNLJA=="],
|
||||
|
||||
"hyperdyperid": ["hyperdyperid@1.2.0", "", {}, "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||
|
||||
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
|
||||
|
||||
"jest-worker": ["jest-worker@27.5.1", "", { "dependencies": { "@types/node": "*", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg=="],
|
||||
|
||||
"json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
|
||||
|
||||
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
|
||||
|
||||
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
|
||||
|
||||
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
|
||||
|
||||
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
|
||||
|
||||
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
|
||||
|
||||
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
|
||||
|
||||
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
|
||||
|
||||
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
|
||||
|
||||
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
|
||||
|
||||
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
|
||||
|
||||
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
|
||||
|
||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
||||
|
||||
"loader-runner": ["loader-runner@4.3.2", "", {}, "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w=="],
|
||||
|
||||
"lodash.camelcase": ["lodash.camelcase@4.3.0", "", {}, "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="],
|
||||
|
||||
"long": ["long@5.3.2", "", {}, "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA=="],
|
||||
|
||||
"luxon": ["luxon@3.7.2", "", {}, "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew=="],
|
||||
|
||||
"memfs": ["memfs@4.64.0", "", { "dependencies": { "@jsonjoy.com/fs-core": "4.64.0", "@jsonjoy.com/fs-fsa": "4.64.0", "@jsonjoy.com/fs-node": "4.64.0", "@jsonjoy.com/fs-node-builtins": "4.64.0", "@jsonjoy.com/fs-node-to-fsa": "4.64.0", "@jsonjoy.com/fs-node-utils": "4.64.0", "@jsonjoy.com/fs-print": "4.64.0", "@jsonjoy.com/fs-snapshot": "4.64.0", "@jsonjoy.com/json-pack": "^1.11.0", "@jsonjoy.com/util": "^1.9.0", "glob-to-regex.js": "^1.0.1", "thingies": "^2.5.0", "tree-dump": "^1.0.3", "tslib": "^2.0.0" } }, "sha512-Kw72fgY7Wn+sD8KmtNWSafl1dz0UvAsE/PHs3YVfLiaZuA3HxNm9sRLqAu0ATiBGJvME1PxZXbBZPv5GycDeAw=="],
|
||||
|
||||
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
|
||||
|
||||
"mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
|
||||
|
||||
"minimizer-webpack-plugin": ["minimizer-webpack-plugin@5.6.1", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", "terser": "^5.31.1" }, "peerDependencies": { "webpack": "^5.1.0" } }, "sha512-DoeAZz8Q1C1znwsUzej1fdoi4jCf7/+Em27ouLqfK/+3m8G+D7yDhUwrc3CNhjSzGUN1kn7Iv4sWmjflQHenpw=="],
|
||||
|
||||
"ms": ["ms@3.0.0-canary.1", "", {}, "sha512-kh8ARjh8rMN7Du2igDRO9QJnqCb2xYTJxyQYK7vJJS4TvLLmsbyhiKpSW+t+y26gyOyMd0riphX0GeWKU3ky5g=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="],
|
||||
|
||||
"neo-async": ["neo-async@2.6.2", "", {}, "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="],
|
||||
|
||||
"nexus-rpc": ["nexus-rpc@0.0.2", "", {}, "sha512-IWjIExdVYlmwXuzHdY/Q3lXCv1gbqoAXPazQhy2w4Xgtgha3H0OOujEESVPQcFUFMWm+pAk2gKnb57g8S41JZg=="],
|
||||
|
||||
"node-releases": ["node-releases@2.0.51", "", {}, "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"picomatch": ["picomatch@4.0.5", "", {}, "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A=="],
|
||||
|
||||
"postcss": ["postcss@8.5.19", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ=="],
|
||||
|
||||
"proto3-json-serializer": ["proto3-json-serializer@2.0.2", "", { "dependencies": { "protobufjs": "^7.2.5" } }, "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ=="],
|
||||
|
||||
"protobufjs": ["protobufjs@7.6.5", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.5", "@protobufjs/eventemitter": "^1.1.1", "@protobufjs/fetch": "^1.1.1", "@protobufjs/float": "^1.0.2", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.1", "@types/node": ">=13.7.0", "long": "^5.3.2" } }, "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw=="],
|
||||
|
||||
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
|
||||
|
||||
"require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="],
|
||||
|
||||
"rolldown": ["rolldown@1.1.5", "", { "dependencies": { "@oxc-project/types": "=0.139.0", "@rolldown/pluginutils": "^1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.1.5", "@rolldown/binding-darwin-arm64": "1.1.5", "@rolldown/binding-darwin-x64": "1.1.5", "@rolldown/binding-freebsd-x64": "1.1.5", "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", "@rolldown/binding-linux-arm64-gnu": "1.1.5", "@rolldown/binding-linux-arm64-musl": "1.1.5", "@rolldown/binding-linux-ppc64-gnu": "1.1.5", "@rolldown/binding-linux-s390x-gnu": "1.1.5", "@rolldown/binding-linux-x64-gnu": "1.1.5", "@rolldown/binding-linux-x64-musl": "1.1.5", "@rolldown/binding-openharmony-arm64": "1.1.5", "@rolldown/binding-wasm32-wasi": "1.1.5", "@rolldown/binding-win32-arm64-msvc": "1.1.5", "@rolldown/binding-win32-x64-msvc": "1.1.5" }, "bin": { "rolldown": "./bin/cli.mjs" } }, "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA=="],
|
||||
|
||||
"rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"schema-utils": ["schema-utils@4.3.3", "", { "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", "ajv-formats": "^2.1.1", "ajv-keywords": "^5.1.0" } }, "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA=="],
|
||||
|
||||
"source-map": ["source-map@0.7.6", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"source-map-loader": ["source-map-loader@5.0.0", "", { "dependencies": { "iconv-lite": "^0.6.3", "source-map-js": "^1.0.2" }, "peerDependencies": { "webpack": "^5.72.1" } }, "sha512-k2Dur7CbSLcAH73sBcIkV5xjPV4SzqO1NJ7+XaQl8if3VODDUj3FNchNGpqgJSKbvUfJuhVdv8K2Eu8/TNl2eA=="],
|
||||
|
||||
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
|
||||
|
||||
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
|
||||
|
||||
"swc-loader": ["swc-loader@0.2.7", "", { "dependencies": { "@swc/counter": "^0.1.3" }, "peerDependencies": { "@swc/core": "^1.2.147", "webpack": ">=2" } }, "sha512-nwYWw3Fh9ame3Rtm7StS9SBLpHRRnYcK7bnpF3UKZmesAK0gw2/ADvlURFAINmPvKtDLzp+GBiP9yLoEjg6S9w=="],
|
||||
|
||||
"tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="],
|
||||
|
||||
"terser": ["terser@5.49.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA=="],
|
||||
|
||||
"thingies": ["thingies@2.6.0", "", { "peerDependencies": { "tslib": "^2" } }, "sha512-rMHRjmlFLM1R96UYPvpmnc3LYtdFrT33JIB7L9hetGue1qAPfn1N2LJeEjxUSidu1Iku+haLZXDuEXUHNGO/lg=="],
|
||||
|
||||
"tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="],
|
||||
|
||||
"tree-dump": ["tree-dump@1.1.0", "", { "peerDependencies": { "tslib": "2" } }, "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA=="],
|
||||
|
||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"undici-types": ["undici-types@8.3.0", "", {}, "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ=="],
|
||||
|
||||
"unionfs": ["unionfs@4.6.0", "", { "dependencies": { "fs-monkey": "^1.0.0" } }, "sha512-fJAy3gTHjFi5S3TP5EGdjs/OUMFFvI/ady3T8qVuZfkv8Qi8prV/Q8BuFEgODJslhZTT2z2qdD2lGdee9qjEnA=="],
|
||||
|
||||
"update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
|
||||
|
||||
"uuid": ["uuid@11.1.1", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="],
|
||||
|
||||
"vite": ["vite@8.1.5", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.5", "postcss": "^8.5.17", "rolldown": "~1.1.5", "tinyglobby": "^0.2.17" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.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", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw=="],
|
||||
|
||||
"watchpack": ["watchpack@2.5.2", "", { "dependencies": { "graceful-fs": "^4.1.2" } }, "sha512-6i/00NBjP4yGPs+caKSyRfpTF/8Torsu0MOW3mMzIbhgISFder8i7xbqgHlLMwJrdiN8ndBV3UA1/AfzPSr+jg=="],
|
||||
|
||||
"webpack": ["webpack@5.108.4", "", { "dependencies": { "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.16.0", "acorn-import-phases": "^1.0.3", "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.22.2", "es-module-lexer": "^2.1.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "graceful-fs": "^4.2.11", "loader-runner": "^4.3.2", "mime-db": "^1.54.0", "minimizer-webpack-plugin": "^5.6.1", "neo-async": "^2.6.2", "schema-utils": "^4.3.3", "tapable": "^2.3.0", "watchpack": "^2.5.2", "webpack-sources": "^3.5.0" }, "bin": { "webpack": "bin/webpack.js" } }, "sha512-yur8LyJoeiWh47dErD+Ok7vlbmDsJ3UbbRPAoxbGJ54WpE2y5yVo5G/inUzujnYgw3tPmBRdn+G7PoxXaYC33w=="],
|
||||
|
||||
"webpack-sources": ["webpack-sources@3.5.1", "", {}, "sha512-jyuiGJdtvY434z5bUZrjz67v76/ePNvFZTp9Mdz29IlH4+GPsgyGjiv0fKI+M7BdkU6ADjulUcKAd3tUK3WlEw=="],
|
||||
|
||||
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
|
||||
|
||||
"yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="],
|
||||
|
||||
"yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="],
|
||||
|
||||
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
|
||||
|
||||
"@jsonjoy.com/fs-snapshot/@jsonjoy.com/json-pack": ["@jsonjoy.com/json-pack@17.67.0", "", { "dependencies": { "@jsonjoy.com/base64": "17.67.0", "@jsonjoy.com/buffers": "17.67.0", "@jsonjoy.com/codegen": "17.67.0", "@jsonjoy.com/json-pointer": "17.67.0", "@jsonjoy.com/util": "17.67.0", "hyperdyperid": "^1.2.0", "thingies": "^2.5.0", "tree-dump": "^1.1.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-t0ejURcGaZsn1ClbJ/3kFqSOjlryd92eQY465IYrezsXmPcfHPE/av4twRSxf6WE+TkZgLY+71vCZbiIiFKA/w=="],
|
||||
|
||||
"@jsonjoy.com/fs-snapshot/@jsonjoy.com/util": ["@jsonjoy.com/util@17.67.0", "", { "dependencies": { "@jsonjoy.com/buffers": "17.67.0", "@jsonjoy.com/codegen": "17.67.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-6+8xBaz1rLSohlGh68D1pdw3AwDi9xydm8QNlAFkvnavCJYSze+pxoW2VKP8p308jtlMRLs5NTHfPlZLd4w7ew=="],
|
||||
|
||||
"@jsonjoy.com/json-pack/@jsonjoy.com/buffers": ["@jsonjoy.com/buffers@1.2.1", "", { "peerDependencies": { "tslib": "2" } }, "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA=="],
|
||||
|
||||
"@jsonjoy.com/util/@jsonjoy.com/buffers": ["@jsonjoy.com/buffers@1.2.1", "", { "peerDependencies": { "tslib": "2" } }, "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA=="],
|
||||
|
||||
"esrecurse/estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="],
|
||||
|
||||
"source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
|
||||
|
||||
"@jsonjoy.com/fs-snapshot/@jsonjoy.com/json-pack/@jsonjoy.com/base64": ["@jsonjoy.com/base64@17.67.0", "", { "peerDependencies": { "tslib": "2" } }, "sha512-5SEsJGsm15aP8TQGkDfJvz9axgPwAEm98S5DxOuYe8e1EbfajcDmgeXXzccEjh+mLnjqEKrkBdjHWS5vFNwDdw=="],
|
||||
|
||||
"@jsonjoy.com/fs-snapshot/@jsonjoy.com/json-pack/@jsonjoy.com/codegen": ["@jsonjoy.com/codegen@17.67.0", "", { "peerDependencies": { "tslib": "2" } }, "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q=="],
|
||||
|
||||
"@jsonjoy.com/fs-snapshot/@jsonjoy.com/json-pack/@jsonjoy.com/json-pointer": ["@jsonjoy.com/json-pointer@17.67.0", "", { "dependencies": { "@jsonjoy.com/util": "17.67.0" }, "peerDependencies": { "tslib": "2" } }, "sha512-+iqOFInH+QZGmSuaybBUNdh7yvNrXvqR+h3wjXm0N/3JK1EyyFAeGJvqnmQL61d1ARLlk/wJdFKSL+LHJ1eaUA=="],
|
||||
|
||||
"@jsonjoy.com/fs-snapshot/@jsonjoy.com/util/@jsonjoy.com/codegen": ["@jsonjoy.com/codegen@17.67.0", "", { "peerDependencies": { "tslib": "2" } }, "sha512-idnkUplROpdBOV0HMcwhsCUS5TRUi9poagdGs70A6S4ux9+/aPuKbh8+UYRTLYQHtXvAdNfQWXDqZEx5k4Dj2Q=="],
|
||||
}
|
||||
}
|
||||
|
||||
+97
-3
@@ -74,27 +74,121 @@ ranking:
|
||||
records:
|
||||
publicLimit: 20
|
||||
|
||||
temporal:
|
||||
addressEnv: APISTATE_TEMPORAL_ADDRESS
|
||||
namespace: unidesk
|
||||
taskQueue: apistate
|
||||
scoreScheduleWorkflowId: apistate-score-refresh-schedule
|
||||
workflowExecutionTimeout: 5m
|
||||
activityStartToCloseTimeout: 4m
|
||||
retry:
|
||||
maximumAttempts: 1
|
||||
|
||||
runtime:
|
||||
secretsRoot: /root/.unidesk/.state/secrets
|
||||
secretSourcePaths:
|
||||
/root/.unidesk/.env/unified-admin-password.txt: /root/.unidesk/.env/unified-admin-password.txt
|
||||
platform-infra/sub2api.env: /root/.unidesk/.state/secrets/platform-infra/sub2api.env
|
||||
platform-infra/apistate.env: /root/.unidesk/.state/secrets/platform-infra/apistate.env
|
||||
defaultCliTarget: local
|
||||
overApiTarget: native-api
|
||||
cliTargets:
|
||||
local:
|
||||
mode: embedded
|
||||
databasePath: .state/sub2rank.sqlite
|
||||
scoreCachePath: .state/account-scores.json
|
||||
monitorWorkDir: /root/unidesk
|
||||
production:
|
||||
mode: http
|
||||
baseUrl: https://apistate.hwpod.com
|
||||
adminToken:
|
||||
sourceRef: platform-infra/apistate.env
|
||||
sourceKey: APISTATE_API_KEY
|
||||
native-api:
|
||||
mode: http
|
||||
baseUrl: http://127.0.0.1:8080
|
||||
adminToken:
|
||||
sourceRef: platform-infra/apistate.env
|
||||
sourceKey: APISTATE_API_KEY
|
||||
native:
|
||||
stateDir: .state/native
|
||||
env:
|
||||
APISTATE_WEB_PASSWORD:
|
||||
sourceRef: /root/.unidesk/.env/unified-admin-password.txt
|
||||
sourceKey: contents
|
||||
APISTATE_API_KEY:
|
||||
sourceRef: platform-infra/apistate.env
|
||||
sourceKey: APISTATE_API_KEY
|
||||
APISTATE_SESSION_SECRET:
|
||||
sourceRef: platform-infra/apistate.env
|
||||
sourceKey: APISTATE_SESSION_SECRET
|
||||
SUB2API_ADMIN_EMAIL:
|
||||
sourceRef: platform-infra/sub2api.env
|
||||
sourceKey: ADMIN_EMAIL
|
||||
SUB2API_ADMIN_PASSWORD:
|
||||
sourceRef: platform-infra/sub2api.env
|
||||
sourceKey: ADMIN_PASSWORD
|
||||
services:
|
||||
api:
|
||||
command:
|
||||
- bun
|
||||
- --hot
|
||||
- src/api.ts
|
||||
- --config
|
||||
- config/sub2rank.yaml
|
||||
- --runtime
|
||||
- native
|
||||
pidFile: api.pid
|
||||
logFile: api.log
|
||||
worker:
|
||||
command:
|
||||
- bun
|
||||
- src/worker.ts
|
||||
- --config
|
||||
- config/sub2rank.yaml
|
||||
- --runtime
|
||||
- native
|
||||
pidFile: worker.pid
|
||||
logFile: worker.log
|
||||
web:
|
||||
command:
|
||||
- bunx
|
||||
- vite
|
||||
- --config
|
||||
- vite.config.ts
|
||||
pidFile: web.pid
|
||||
logFile: web.log
|
||||
serverTargets:
|
||||
k8s:
|
||||
listenHost: 0.0.0.0
|
||||
native:
|
||||
listenHost: 127.0.0.1
|
||||
listenPort: 8080
|
||||
databasePath: /var/lib/sub2rank/sub2rank.sqlite
|
||||
workerHealthHost: 127.0.0.1
|
||||
workerHealthPort: 8081
|
||||
webListenHost: 127.0.0.1
|
||||
webListenPort: 5173
|
||||
webApiBaseUrl: http://127.0.0.1:8080
|
||||
secureCookies: false
|
||||
databasePath: .state/sub2rank.sqlite
|
||||
scoreCachePath: .state/account-scores.json
|
||||
monitorWorkDir: /root/unidesk
|
||||
adminTokenEnv: APISTATE_API_KEY
|
||||
sub2apiAdminEmailEnv: SUB2API_ADMIN_EMAIL
|
||||
sub2apiAdminPasswordEnv: SUB2API_ADMIN_PASSWORD
|
||||
webPasswordEnv: APISTATE_WEB_PASSWORD
|
||||
apiKeyEnv: APISTATE_API_KEY
|
||||
sessionSecretEnv: APISTATE_SESSION_SECRET
|
||||
k8s:
|
||||
listenHost: 0.0.0.0
|
||||
listenPort: 8080
|
||||
workerHealthHost: 0.0.0.0
|
||||
workerHealthPort: 8081
|
||||
webListenHost: 127.0.0.1
|
||||
webListenPort: 5173
|
||||
webApiBaseUrl: http://127.0.0.1:8080
|
||||
secureCookies: true
|
||||
databasePath: /var/lib/sub2rank/sub2rank.sqlite
|
||||
scoreCachePath: /var/lib/sub2rank/account-scores.json
|
||||
monitorWorkDir: /opt/unidesk
|
||||
adminTokenEnv: APISTATE_API_KEY
|
||||
sub2apiAdminEmailEnv: SUB2API_ADMIN_EMAIL
|
||||
sub2apiAdminPasswordEnv: SUB2API_ADMIN_PASSWORD
|
||||
|
||||
+14
-3
@@ -5,16 +5,27 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"cli": "bun scripts/apistate-cli.ts --config config/sub2rank.yaml",
|
||||
"server": "bun src/server.ts --config config/sub2rank.yaml --runtime k8s",
|
||||
"api": "bun --hot src/api.ts --config config/sub2rank.yaml --runtime native",
|
||||
"worker": "bun src/worker.ts --config config/sub2rank.yaml --runtime native",
|
||||
"dev:web": "APISTATE_CONFIG_PATH=config/sub2rank.yaml APISTATE_RUNTIME_ID=native bunx vite --config vite.config.ts",
|
||||
"server": "bun src/api.ts --config config/sub2rank.yaml --runtime k8s",
|
||||
"check": "bunx tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@temporalio/client": "^1.20.3",
|
||||
"@temporalio/worker": "^1.20.3",
|
||||
"@temporalio/workflow": "^1.20.3",
|
||||
"luxon": "^3.7.2",
|
||||
"yaml": "^2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.2.21",
|
||||
"@types/luxon": "^3.7.1",
|
||||
"typescript": "^5.9.2"
|
||||
}
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^8.1.5"
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"@swc/core",
|
||||
"protobufjs"
|
||||
]
|
||||
}
|
||||
|
||||
+131
-108
@@ -1,7 +1,12 @@
|
||||
import { AdminHttpClient } from "../../src/admin-http-client";
|
||||
import { mergeAccountScores } from "../../src/account-score-aggregation";
|
||||
import { createEmbeddedContext } from "../../src/bootstrap";
|
||||
import { loadConfig, type EmbeddedCliTarget, type HttpCliTarget } from "../../src/config";
|
||||
import { loadConfig, type EmbeddedCliTarget, type HttpCliTarget, type NativeServiceId } from "../../src/config";
|
||||
import type { AppCommand } from "../../src/contracts";
|
||||
import { usesWorkflow } from "../../src/contracts";
|
||||
import { ApplicationDispatcher } from "../../src/dispatcher";
|
||||
import { nativeLogs, nativeStart, nativeStatus, nativeStop } from "../../src/native-services";
|
||||
import { TemporalGateway } from "../../src/temporal-client";
|
||||
|
||||
interface Parsed {
|
||||
configPath: string;
|
||||
@@ -9,9 +14,13 @@ interface Parsed {
|
||||
command: string[];
|
||||
confirm: boolean;
|
||||
includeRecords: boolean;
|
||||
overApi: boolean;
|
||||
json: boolean;
|
||||
id: string | null;
|
||||
component: NativeServiceId | null;
|
||||
limit: number | null;
|
||||
draws: number | null;
|
||||
tail: number | null;
|
||||
}
|
||||
|
||||
function record(value: unknown): Record<string, unknown> | null {
|
||||
@@ -29,8 +38,8 @@ function value(args: string[], name: string): string | null {
|
||||
function parseArgs(args: string[]): Parsed {
|
||||
const configPath = value(args, "--config");
|
||||
if (!configPath) throw new Error("--config is required");
|
||||
const optionNames = new Set(["--config", "--target", "--id", "--limit", "--draws"]);
|
||||
const flags = new Set(["--confirm", "--include-records"]);
|
||||
const optionNames = new Set(["--config", "--target", "--id", "--limit", "--draws", "--component", "--tail"]);
|
||||
const flags = new Set(["--confirm", "--include-records", "--over-api", "--json"]);
|
||||
const command: string[] = [];
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
const item = args[index]!;
|
||||
@@ -46,56 +55,101 @@ function parseArgs(args: string[]): Parsed {
|
||||
if (!Number.isInteger(parsed) || parsed < 0) throw new Error(`${name} must be a non-negative integer`);
|
||||
return parsed;
|
||||
};
|
||||
const component = value(args, "--component");
|
||||
if (component !== null && component !== "api" && component !== "worker" && component !== "web") throw new Error("--component must be api, worker, or web");
|
||||
return {
|
||||
configPath,
|
||||
targetId: value(args, "--target"),
|
||||
command,
|
||||
confirm: args.includes("--confirm"),
|
||||
includeRecords: args.includes("--include-records"),
|
||||
overApi: args.includes("--over-api"),
|
||||
json: args.includes("--json"),
|
||||
id: value(args, "--id"),
|
||||
component,
|
||||
limit: integer("--limit"),
|
||||
draws: integer("--draws"),
|
||||
tail: integer("--tail"),
|
||||
};
|
||||
}
|
||||
|
||||
function help(): Record<string, unknown> {
|
||||
return {
|
||||
ok: true,
|
||||
usage: "bun scripts/apistate-cli.ts --config config/sub2rank.yaml [--target local|production] <command>",
|
||||
usage: "bun scripts/apistate-cli.ts --config config/sub2rank.yaml [--over-api] [--target <id>] <command>",
|
||||
commands: [
|
||||
"config validate",
|
||||
"backend check",
|
||||
"scores aggregate-smoke",
|
||||
"lottery status",
|
||||
"lottery draw [--confirm]",
|
||||
"lottery reset [--draws N] [--include-records] --confirm",
|
||||
"records list [--limit N]",
|
||||
"records delete --id <record-id> --confirm",
|
||||
"credit test [--confirm]",
|
||||
"api smoke",
|
||||
"scores get|refresh|aggregate-smoke",
|
||||
"lottery status|draw|reset",
|
||||
"records list|delete",
|
||||
"credit test",
|
||||
"api smoke --over-api",
|
||||
"workflow status --id <workflow-id>",
|
||||
"native start|stop|status|logs --component api|worker|web [--tail N]",
|
||||
],
|
||||
output: "k8s-style text by default; add --json for machine output",
|
||||
};
|
||||
}
|
||||
|
||||
function emit(value: Record<string, unknown>, json: boolean): void {
|
||||
if (json) {
|
||||
console.log(JSON.stringify(value, null, 2));
|
||||
return;
|
||||
}
|
||||
console.log("APISTATE RESULT");
|
||||
console.log("FIELD VALUE");
|
||||
console.log("-------------------- ------------------------------------------------------------");
|
||||
for (const [key, item] of Object.entries(value)) {
|
||||
const rendered = item === null || item === undefined ? "-" : typeof item === "object" ? JSON.stringify(item) : String(item);
|
||||
console.log(`${key.padEnd(20)} ${rendered}`);
|
||||
}
|
||||
}
|
||||
|
||||
function appCommand(parsed: Parsed, config: ReturnType<typeof loadConfig>): AppCommand | Record<string, unknown> {
|
||||
const [group, action] = parsed.command;
|
||||
if (group === "backend" && action === "check") return { kind: "backend.check" };
|
||||
if (group === "scores" && action === "get") return { kind: "scores.get" };
|
||||
if (group === "scores" && action === "refresh") return { kind: "scores.refresh" };
|
||||
if (group === "lottery" && action === "status") return { kind: "lottery.status" };
|
||||
if (group === "lottery" && action === "draw") return parsed.confirm ? { kind: "lottery.draw" } : { ok: true, mutation: false, action: "lottery-draw", hint: "add --confirm to execute" };
|
||||
if (group === "lottery" && action === "reset") {
|
||||
const draws = parsed.draws ?? config.lottery.initialDrawCount;
|
||||
return parsed.confirm ? { kind: "lottery.reset", draws, includeRecords: parsed.includeRecords } : { ok: true, mutation: false, action: "lottery-reset", draws, includeRecords: parsed.includeRecords, hint: "add --confirm to execute" };
|
||||
}
|
||||
if (group === "records" && action === "list") return { kind: "records.list", limit: parsed.limit ?? config.records.publicLimit };
|
||||
if (group === "records" && action === "delete") {
|
||||
if (!parsed.id) throw new Error("records delete requires --id");
|
||||
return parsed.confirm ? { kind: "records.delete", id: parsed.id } : { ok: true, mutation: false, action: "record-delete", id: parsed.id, hint: "add --confirm to execute" };
|
||||
}
|
||||
if (group === "credit" && action === "test") return { kind: "credit.test", execute: parsed.confirm };
|
||||
throw new Error(`unknown command: ${parsed.command.join(" ")}`);
|
||||
}
|
||||
|
||||
function isAppCommand(value: AppCommand | Record<string, unknown>): value is AppCommand {
|
||||
return typeof value.kind === "string";
|
||||
}
|
||||
|
||||
async function embedded(parsed: Parsed, config: ReturnType<typeof loadConfig>, target: EmbeddedCliTarget): Promise<unknown> {
|
||||
if (parsed.command.join(" ") === "scores refresh" || parsed.command.join(" ") === "workflow status") {
|
||||
const temporal = await TemporalGateway.connect(config);
|
||||
try {
|
||||
if (parsed.command[0] === "scores") return await temporal.submit({ kind: "scores.refresh" });
|
||||
if (!parsed.id) throw new Error("workflow status requires --id");
|
||||
return await temporal.status(parsed.id);
|
||||
} finally {
|
||||
await temporal.close();
|
||||
}
|
||||
}
|
||||
const command = appCommand(parsed, config);
|
||||
if (!isAppCommand(command)) return command;
|
||||
const context = createEmbeddedContext(config, target);
|
||||
let temporal: TemporalGateway | null = null;
|
||||
try {
|
||||
const [group, action] = parsed.command;
|
||||
if (group === "backend" && action === "check") return await context.service.status(true);
|
||||
if (group === "lottery" && action === "status") return await context.service.status(false);
|
||||
if (group === "lottery" && action === "draw") return parsed.confirm ? { ok: true, record: await context.service.draw() } : { ok: true, mutation: false, action: "lottery-draw", hint: "add --confirm to execute" };
|
||||
if (group === "lottery" && action === "reset") {
|
||||
const draws = parsed.draws ?? config.lottery.initialDrawCount;
|
||||
return parsed.confirm ? context.service.reset(draws, parsed.includeRecords) : { ok: true, mutation: false, action: "lottery-reset", draws, includeRecords: parsed.includeRecords, hint: "add --confirm to execute" };
|
||||
}
|
||||
if (group === "records" && action === "list") return { ok: true, records: context.service.listRecords(parsed.limit ?? config.records.publicLimit) };
|
||||
if (group === "records" && action === "delete") {
|
||||
if (!parsed.id) throw new Error("records delete requires --id");
|
||||
return parsed.confirm ? context.service.deleteRecord(parsed.id) : { ok: true, mutation: false, action: "record-delete", id: parsed.id, hint: "add --confirm to execute" };
|
||||
}
|
||||
if (group === "credit" && action === "test") return await context.service.creditTest(parsed.confirm);
|
||||
throw new Error(`unknown command: ${parsed.command.join(" ")}`);
|
||||
if (usesWorkflow(command)) temporal = await TemporalGateway.connect(config);
|
||||
return await new ApplicationDispatcher({ lottery: context.service, scores: context.monitor }, temporal).dispatch(command);
|
||||
} finally {
|
||||
if (temporal) await temporal.close();
|
||||
context.close();
|
||||
}
|
||||
}
|
||||
@@ -104,6 +158,12 @@ async function remote(parsed: Parsed, config: ReturnType<typeof loadConfig>, tar
|
||||
const client = new AdminHttpClient(config, target);
|
||||
const [group, action] = parsed.command;
|
||||
if (group === "backend" && action === "check") return await client.backendCheck();
|
||||
if (group === "scores" && action === "get") return await client.scores();
|
||||
if (group === "scores" && action === "refresh") return await client.workflowSubmit({ kind: "scores.refresh" });
|
||||
if (group === "workflow" && action === "status") {
|
||||
if (!parsed.id) throw new Error("workflow status requires --id");
|
||||
return await client.workflowStatus(parsed.id);
|
||||
}
|
||||
if (group === "lottery" && action === "status") return await client.status();
|
||||
if (group === "lottery" && action === "draw") return parsed.confirm ? await client.draw() : { ok: true, mutation: false, action: "lottery-draw", hint: "add --confirm to execute" };
|
||||
if (group === "lottery" && action === "reset") {
|
||||
@@ -117,105 +177,68 @@ async function remote(parsed: Parsed, config: ReturnType<typeof loadConfig>, tar
|
||||
}
|
||||
if (group === "credit" && action === "test") return await client.creditTest(parsed.confirm);
|
||||
if (group === "api" && action === "smoke") {
|
||||
const [status, scores, ranking, lottery] = await Promise.all([
|
||||
client.serviceStatus(),
|
||||
client.scores(),
|
||||
client.ranking(),
|
||||
client.lottery(),
|
||||
]);
|
||||
const [status, scores, ranking, lottery] = await Promise.all([client.serviceStatus(), client.scores(), client.ranking(), client.lottery()]);
|
||||
const refreshed = await client.refreshScores();
|
||||
return {
|
||||
ok: true,
|
||||
ok: status.ok === true && scores.ok === true && refreshed.ok === true && ranking.ok === true && lottery.ok === true,
|
||||
action: "apistate-api-smoke",
|
||||
checks: {
|
||||
status: status.ok === true,
|
||||
scores: scores.ok === true,
|
||||
refresh: refreshed.ok === true && refreshed.snapshotOk === true,
|
||||
ranking: ranking.ok === true,
|
||||
lottery: lottery.ok === true,
|
||||
},
|
||||
checks: { status: status.ok === true, scores: scores.ok === true, refresh: refreshed.ok === true && refreshed.snapshotOk === true, ranking: ranking.ok === true, lottery: lottery.ok === true },
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
throw new Error(`unknown command: ${parsed.command.join(" ")}`);
|
||||
}
|
||||
|
||||
function aggregateSmoke(): Record<string, unknown> {
|
||||
const account = (groupId: number, groupName: string, successRequests: number, failureRequests: number, ttftP95Ms: number, apiAmountUsd: number) => ({
|
||||
accountId: 15, accountName: "lyon9801 0.0", groupId, groupName, status: "active", currentlyAvailable: true, priority: 1,
|
||||
successRequests, failureRequests, observedAttempts: successRequests + failureRequests, streamSuccessRequests: successRequests,
|
||||
firstTokenSamples: successRequests, ttftP95Ms, usage: { requestCount: successRequests, tokenCount: successRequests * 1000, apiAmountUsd },
|
||||
});
|
||||
const rows = mergeAccountScores([account(3, "自用", 100, 0, 10_591, 1), account(2, "unidesk-codex-pool", 50, 1, 13_206, 0.5)]);
|
||||
const row = rows[0] ?? {};
|
||||
const checks = {
|
||||
uniqueAccount: rows.length === 1,
|
||||
groupsMerged: Array.isArray(row.groupNames) && row.groupNames.length === 2,
|
||||
attemptsMerged: row.observedAttempts === 151,
|
||||
usageMerged: record(row.usage)?.requestCount === 150 && record(row.usage)?.apiAmountUsd === 1.5,
|
||||
decimalScore: typeof row.score === "number" && !Number.isInteger(row.score),
|
||||
conservativeTtft: row.ttftP95Ms === 13_206,
|
||||
};
|
||||
return { ok: Object.values(checks).every(Boolean), action: "account-score-aggregate-smoke", checks, mutation: false };
|
||||
}
|
||||
|
||||
export async function runCli(args: string[]): Promise<void> {
|
||||
const wantsJson = args.includes("--json");
|
||||
try {
|
||||
if (args.includes("--help") || args.length === 0) {
|
||||
console.log(JSON.stringify(help(), null, 2));
|
||||
return;
|
||||
}
|
||||
if (args.includes("--help") || args.length === 0) return emit(help(), wantsJson);
|
||||
const parsed = parseArgs(args);
|
||||
const config = loadConfig(parsed.configPath);
|
||||
if (parsed.command.join(" ") === "config validate") {
|
||||
console.log(JSON.stringify({
|
||||
ok: true,
|
||||
configPath: config.configPath,
|
||||
kind: config.kind,
|
||||
service: config.metadata.name,
|
||||
refreshIntervalMinutes: config.monitor.refreshIntervalMinutes,
|
||||
scoreWindow: config.monitor.scoreWindow,
|
||||
automaticCreditEnabled: config.lottery.automaticCredit.enabled,
|
||||
excludedIdentities: config.lottery.eligibility.excludedIdentities,
|
||||
valuesPrinted: false,
|
||||
}, null, 2));
|
||||
return;
|
||||
if (parsed.command.join(" ") === "config validate") return emit({
|
||||
ok: true, configPath: config.configPath, kind: config.kind, service: config.metadata.name,
|
||||
temporalNamespace: config.temporal.namespace, temporalTaskQueue: config.temporal.taskQueue,
|
||||
refreshIntervalMinutes: config.monitor.refreshIntervalMinutes, scoreWindow: config.monitor.scoreWindow,
|
||||
automaticCreditEnabled: config.lottery.automaticCredit.enabled, valuesPrinted: false,
|
||||
}, parsed.json);
|
||||
if (parsed.command.join(" ") === "scores aggregate-smoke") return emit(aggregateSmoke(), parsed.json);
|
||||
if (parsed.command[0] === "native") {
|
||||
const action = parsed.command[1];
|
||||
if (!parsed.component) throw new Error("native commands require --component api, worker, or web");
|
||||
const result = action === "start" ? nativeStart(config, parsed.component)
|
||||
: action === "stop" ? nativeStop(config, parsed.component)
|
||||
: action === "status" ? nativeStatus(config, parsed.component)
|
||||
: action === "logs" ? nativeLogs(config, parsed.component, parsed.tail ?? 40)
|
||||
: (() => { throw new Error(`unknown native action ${action ?? ""}`); })();
|
||||
return emit(result, parsed.json);
|
||||
}
|
||||
if (parsed.command.join(" ") === "scores aggregate-smoke") {
|
||||
const account = (groupId: number, groupName: string, successRequests: number, failureRequests: number, ttftP95Ms: number, apiAmountUsd: number) => ({
|
||||
accountId: 15,
|
||||
accountName: "lyon9801 0.0",
|
||||
groupId,
|
||||
groupName,
|
||||
status: "active",
|
||||
currentlyAvailable: true,
|
||||
priority: 1,
|
||||
successRequests,
|
||||
failureRequests,
|
||||
observedAttempts: successRequests + failureRequests,
|
||||
streamSuccessRequests: successRequests,
|
||||
firstTokenSamples: successRequests,
|
||||
ttftP95Ms,
|
||||
usage: { requestCount: successRequests, tokenCount: successRequests * 1000, apiAmountUsd },
|
||||
});
|
||||
const rows = mergeAccountScores([
|
||||
account(3, "自用", 100, 0, 10_591, 1),
|
||||
account(2, "unidesk-codex-pool", 50, 1, 13_206, 0.5),
|
||||
]);
|
||||
const row = rows[0] ?? {};
|
||||
const checks = {
|
||||
uniqueAccount: rows.length === 1,
|
||||
groupsMerged: Array.isArray(row.groupNames) && row.groupNames.length === 2,
|
||||
attemptsMerged: row.observedAttempts === 151,
|
||||
usageMerged: record(row.usage)?.requestCount === 150 && record(row.usage)?.apiAmountUsd === 1.5,
|
||||
decimalScore: typeof row.score === "number" && !Number.isInteger(row.score),
|
||||
conservativeTtft: row.ttftP95Ms === 13_206,
|
||||
};
|
||||
console.log(JSON.stringify({
|
||||
ok: Object.values(checks).every(Boolean),
|
||||
action: "account-score-aggregate-smoke",
|
||||
checks,
|
||||
result: {
|
||||
accountId: row.accountId,
|
||||
accountName: row.accountName,
|
||||
groupNames: row.groupNames,
|
||||
observedAttempts: row.observedAttempts,
|
||||
score: row.score,
|
||||
ttftP95Ms: row.ttftP95Ms,
|
||||
usage: row.usage,
|
||||
},
|
||||
mutation: false,
|
||||
}, null, 2));
|
||||
return;
|
||||
}
|
||||
const targetId = parsed.targetId ?? config.runtime.defaultCliTarget;
|
||||
const targetId = parsed.targetId ?? (parsed.overApi ? config.runtime.overApiTarget : config.runtime.defaultCliTarget);
|
||||
const target = config.runtime.cliTargets[targetId];
|
||||
if (!target) throw new Error(`runtime.cliTargets.${targetId} does not exist`);
|
||||
if (parsed.overApi && target.mode !== "http") throw new Error(`--over-api requires an http target; ${targetId} is ${target.mode}`);
|
||||
const result = target.mode === "embedded" ? await embedded(parsed, config, target) : await remote(parsed, config, target);
|
||||
console.log(JSON.stringify({ target: targetId, ...result as Record<string, unknown> }, null, 2));
|
||||
emit({ target: targetId, transport: target.mode === "embedded" ? "local-dispatcher" : "http", ...result as Record<string, unknown> }, parsed.json);
|
||||
} catch (error) {
|
||||
console.error(JSON.stringify({ ok: false, error: error instanceof Error ? error.message : String(error) }, null, 2));
|
||||
emit({ ok: false, error: error instanceof Error ? error.message : String(error), valuesPrinted: false }, wantsJson);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ export class AccountScoreService {
|
||||
private inFlight: Promise<ScoreSnapshot> | null = null;
|
||||
private snapshot: ScoreSnapshot;
|
||||
|
||||
constructor(private readonly config: AppConfig, private readonly cachePath: string) {
|
||||
constructor(private readonly config: AppConfig, private readonly cachePath: string, private readonly cliWorkDir: string) {
|
||||
this.snapshot = this.readCache();
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ export class AccountScoreService {
|
||||
|
||||
private async invoke(scope: string[]): Promise<Record<string, unknown>> {
|
||||
const args = [
|
||||
resolve(this.config.monitor.cli.workDir, this.config.monitor.cli.entrypoint),
|
||||
resolve(this.cliWorkDir, this.config.monitor.cli.entrypoint),
|
||||
"platform-infra", "sub2api", "codex-pool", "runtime", "errors",
|
||||
"--target", this.config.monitor.target,
|
||||
"--since", this.config.monitor.scoreWindow,
|
||||
@@ -133,7 +133,7 @@ export class AccountScoreService {
|
||||
"--raw",
|
||||
];
|
||||
const process = Bun.spawn([this.config.monitor.cli.executable, ...args], {
|
||||
cwd: this.config.monitor.cli.workDir,
|
||||
cwd: this.cliWorkDir,
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
env: { ...Bun.env, UNIDESK_MAIN_SERVER_IP: this.config.monitor.cli.mainServerHost },
|
||||
|
||||
@@ -38,4 +38,10 @@ export class AdminHttpClient {
|
||||
}
|
||||
ranking(): Promise<Record<string, unknown>> { return this.request("/api/ranking"); }
|
||||
lottery(): Promise<Record<string, unknown>> { return this.request("/api/lottery"); }
|
||||
workflowSubmit(command: Record<string, unknown>): Promise<Record<string, unknown>> {
|
||||
return this.request("/api/admin/workflows", { method: "POST", body: JSON.stringify({ command }) });
|
||||
}
|
||||
workflowStatus(id: string): Promise<Record<string, unknown>> {
|
||||
return this.request(`/api/admin/workflows/${encodeURIComponent(id)}`);
|
||||
}
|
||||
}
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
import { createServerContext } from "./bootstrap";
|
||||
import { loadConfig } from "./config";
|
||||
import { ApplicationDispatcher } from "./dispatcher";
|
||||
import { createHandler } from "./http";
|
||||
import { requiredOption } from "./runtime-args";
|
||||
import { TemporalGateway } from "./temporal-client";
|
||||
|
||||
const config = loadConfig(requiredOption("--config"));
|
||||
const runtimeId = requiredOption("--runtime");
|
||||
const target = config.runtime.serverTargets[runtimeId];
|
||||
if (!target) throw new Error(`runtime.serverTargets.${runtimeId} does not exist`);
|
||||
const adminToken = process.env[target.adminTokenEnv];
|
||||
if (!adminToken) throw new Error(`server target requires env ${target.adminTokenEnv}`);
|
||||
|
||||
const context = createServerContext(config, target);
|
||||
const temporal = await TemporalGateway.connect(config);
|
||||
const dispatcher = new ApplicationDispatcher({ lottery: context.service, scores: context.monitor }, temporal);
|
||||
const server = Bun.serve({
|
||||
hostname: target.listenHost,
|
||||
port: target.listenPort,
|
||||
fetch: createHandler(dispatcher, config, context.auth, adminToken, target.secureCookies),
|
||||
});
|
||||
|
||||
console.log(JSON.stringify({
|
||||
ok: true,
|
||||
component: "apistate-api",
|
||||
runtime: runtimeId,
|
||||
listen: server.url.toString(),
|
||||
temporalNamespace: config.temporal.namespace,
|
||||
temporalTaskQueue: config.temporal.taskQueue,
|
||||
automaticCreditEnabled: config.lottery.automaticCredit.enabled,
|
||||
valuesPrinted: false,
|
||||
}));
|
||||
|
||||
let stopping = false;
|
||||
async function stop(): Promise<void> {
|
||||
if (stopping) return;
|
||||
stopping = true;
|
||||
server.stop(true);
|
||||
context.close();
|
||||
await temporal.close();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
for (const signal of ["SIGINT", "SIGTERM"] as const) process.on(signal, () => void stop());
|
||||
+2
-2
@@ -18,7 +18,7 @@ export interface AppContext {
|
||||
export function createEmbeddedContext(config: AppConfig, target: EmbeddedCliTarget): AppContext {
|
||||
const store = new LotteryStore(config, resolveDataPath(config, target.databasePath));
|
||||
const client = new Sub2ApiClient(config, readSub2ApiCredentials(config));
|
||||
const monitor = new AccountScoreService(config, resolveDataPath(config, ".state/account-scores.json"));
|
||||
const monitor = new AccountScoreService(config, resolveDataPath(config, target.scoreCachePath), target.monitorWorkDir);
|
||||
return {
|
||||
service: new LotteryService(config, store, client),
|
||||
store,
|
||||
@@ -39,7 +39,7 @@ export function createServerContext(config: AppConfig, target: ServerTarget): Ap
|
||||
}
|
||||
const store = new LotteryStore(config, resolveDataPath(config, target.databasePath));
|
||||
const client = new Sub2ApiClient(config, { email, password });
|
||||
const monitor = new AccountScoreService(config, resolveDataPath(config, "account-scores.json"));
|
||||
const monitor = new AccountScoreService(config, resolveDataPath(config, target.scoreCachePath), target.monitorWorkDir);
|
||||
return {
|
||||
service: new LotteryService(config, store, client),
|
||||
store,
|
||||
|
||||
+86
-1
@@ -50,11 +50,26 @@ export interface AppConfig {
|
||||
};
|
||||
ranking: { timezone: string; windowDays: number; sourceLimit: number; displayLimit: number };
|
||||
records: { publicLimit: number };
|
||||
temporal: {
|
||||
addressEnv: string;
|
||||
namespace: string;
|
||||
taskQueue: string;
|
||||
scoreScheduleWorkflowId: string;
|
||||
workflowExecutionTimeout: string;
|
||||
activityStartToCloseTimeout: string;
|
||||
retry: { maximumAttempts: number };
|
||||
};
|
||||
runtime: {
|
||||
secretsRoot: string;
|
||||
secretSourcePaths: Record<string, string>;
|
||||
defaultCliTarget: string;
|
||||
overApiTarget: string;
|
||||
cliTargets: Record<string, EmbeddedCliTarget | HttpCliTarget>;
|
||||
native: {
|
||||
stateDir: string;
|
||||
env: Record<string, SecretRef>;
|
||||
services: Record<NativeServiceId, NativeServiceConfig>;
|
||||
};
|
||||
serverTargets: Record<string, ServerTarget>;
|
||||
};
|
||||
configPath: string;
|
||||
@@ -64,6 +79,8 @@ export interface AppConfig {
|
||||
export interface EmbeddedCliTarget {
|
||||
mode: "embedded";
|
||||
databasePath: string;
|
||||
scoreCachePath: string;
|
||||
monitorWorkDir: string;
|
||||
}
|
||||
|
||||
export interface HttpCliTarget {
|
||||
@@ -75,7 +92,15 @@ export interface HttpCliTarget {
|
||||
export interface ServerTarget {
|
||||
listenHost: string;
|
||||
listenPort: number;
|
||||
workerHealthHost: string;
|
||||
workerHealthPort: number;
|
||||
webListenHost: string;
|
||||
webListenPort: number;
|
||||
webApiBaseUrl: string;
|
||||
secureCookies: boolean;
|
||||
databasePath: string;
|
||||
scoreCachePath: string;
|
||||
monitorWorkDir: string;
|
||||
adminTokenEnv: string;
|
||||
sub2apiAdminEmailEnv: string;
|
||||
sub2apiAdminPasswordEnv: string;
|
||||
@@ -84,6 +109,14 @@ export interface ServerTarget {
|
||||
sessionSecretEnv: string;
|
||||
}
|
||||
|
||||
export type NativeServiceId = "api" | "worker" | "web";
|
||||
|
||||
export interface NativeServiceConfig {
|
||||
command: string[];
|
||||
pidFile: string;
|
||||
logFile: string;
|
||||
}
|
||||
|
||||
type ObjectValue = Record<string, unknown>;
|
||||
|
||||
function object(value: unknown, path: string): ObjectValue {
|
||||
@@ -139,6 +172,12 @@ function secretRef(value: unknown, path: string): SecretRef {
|
||||
return { sourceRef: stringValue(raw, "sourceRef", path), sourceKey: stringValue(raw, "sourceKey", path) };
|
||||
}
|
||||
|
||||
function nativeFile(parent: ObjectValue, key: string, path: string): string {
|
||||
const value = stringValue(parent, key, path);
|
||||
if (value.includes("/") || value.includes("\\") || value === "." || value === "..") throw new Error(`${path}.${key} must be a filename`);
|
||||
return value;
|
||||
}
|
||||
|
||||
export function loadConfig(path: string): AppConfig {
|
||||
const configPath = resolve(path);
|
||||
const rootDirectory = resolve(dirname(configPath), "..");
|
||||
@@ -157,7 +196,12 @@ export function loadConfig(path: string): AppConfig {
|
||||
const creditTest = object(lottery.creditTest, "lottery.creditTest");
|
||||
const ranking = object(raw.ranking, "ranking");
|
||||
const records = object(raw.records, "records");
|
||||
const temporal = object(raw.temporal, "temporal");
|
||||
const temporalRetry = object(temporal.retry, "temporal.retry");
|
||||
const runtime = object(raw.runtime, "runtime");
|
||||
const native = object(runtime.native, "runtime.native");
|
||||
const nativeServicesRaw = object(native.services, "runtime.native.services");
|
||||
const nativeEnvRaw = object(native.env, "runtime.native.env");
|
||||
const secretSourcePathsRaw = object(runtime.secretSourcePaths, "runtime.secretSourcePaths");
|
||||
const cliTargetsRaw = object(runtime.cliTargets, "runtime.cliTargets");
|
||||
const serverTargetsRaw = object(runtime.serverTargets, "runtime.serverTargets");
|
||||
@@ -165,7 +209,12 @@ export function loadConfig(path: string): AppConfig {
|
||||
for (const [id, value] of Object.entries(cliTargetsRaw)) {
|
||||
const target = object(value, `runtime.cliTargets.${id}`);
|
||||
const mode = stringValue(target, "mode", `runtime.cliTargets.${id}`);
|
||||
if (mode === "embedded") cliTargets[id] = { mode, databasePath: stringValue(target, "databasePath", `runtime.cliTargets.${id}`) };
|
||||
if (mode === "embedded") cliTargets[id] = {
|
||||
mode,
|
||||
databasePath: stringValue(target, "databasePath", `runtime.cliTargets.${id}`),
|
||||
scoreCachePath: stringValue(target, "scoreCachePath", `runtime.cliTargets.${id}`),
|
||||
monitorWorkDir: stringValue(target, "monitorWorkDir", `runtime.cliTargets.${id}`),
|
||||
};
|
||||
else if (mode === "http") cliTargets[id] = { mode, baseUrl: stringValue(target, "baseUrl", `runtime.cliTargets.${id}`), adminToken: secretRef(target.adminToken, `runtime.cliTargets.${id}.adminToken`) };
|
||||
else throw new Error(`runtime.cliTargets.${id}.mode must be embedded or http`);
|
||||
}
|
||||
@@ -175,7 +224,15 @@ export function loadConfig(path: string): AppConfig {
|
||||
serverTargets[id] = {
|
||||
listenHost: stringValue(target, "listenHost", `runtime.serverTargets.${id}`),
|
||||
listenPort: numberValue(target, "listenPort", `runtime.serverTargets.${id}`, 1),
|
||||
workerHealthHost: stringValue(target, "workerHealthHost", `runtime.serverTargets.${id}`),
|
||||
workerHealthPort: numberValue(target, "workerHealthPort", `runtime.serverTargets.${id}`, 1),
|
||||
webListenHost: stringValue(target, "webListenHost", `runtime.serverTargets.${id}`),
|
||||
webListenPort: numberValue(target, "webListenPort", `runtime.serverTargets.${id}`, 1),
|
||||
webApiBaseUrl: stringValue(target, "webApiBaseUrl", `runtime.serverTargets.${id}`),
|
||||
secureCookies: booleanValue(target, "secureCookies", `runtime.serverTargets.${id}`),
|
||||
databasePath: stringValue(target, "databasePath", `runtime.serverTargets.${id}`),
|
||||
scoreCachePath: stringValue(target, "scoreCachePath", `runtime.serverTargets.${id}`),
|
||||
monitorWorkDir: stringValue(target, "monitorWorkDir", `runtime.serverTargets.${id}`),
|
||||
adminTokenEnv: stringValue(target, "adminTokenEnv", `runtime.serverTargets.${id}`),
|
||||
sub2apiAdminEmailEnv: stringValue(target, "sub2apiAdminEmailEnv", `runtime.serverTargets.${id}`),
|
||||
sub2apiAdminPasswordEnv: stringValue(target, "sub2apiAdminPasswordEnv", `runtime.serverTargets.${id}`),
|
||||
@@ -188,6 +245,19 @@ export function loadConfig(path: string): AppConfig {
|
||||
if (automaticMode !== "dry-run" && automaticMode !== "live") throw new Error("lottery.automaticCredit.mode must be dry-run or live");
|
||||
const defaultCliTarget = stringValue(runtime, "defaultCliTarget", "runtime");
|
||||
if (!cliTargets[defaultCliTarget]) throw new Error(`runtime.defaultCliTarget references missing target ${defaultCliTarget}`);
|
||||
const overApiTarget = stringValue(runtime, "overApiTarget", "runtime");
|
||||
if (cliTargets[overApiTarget]?.mode !== "http") throw new Error(`runtime.overApiTarget must reference an http target`);
|
||||
const nativeServices = {} as Record<NativeServiceId, NativeServiceConfig>;
|
||||
for (const id of ["api", "worker", "web"] as const) {
|
||||
const service = object(nativeServicesRaw[id], `runtime.native.services.${id}`);
|
||||
const command = strings(service, "command", `runtime.native.services.${id}`);
|
||||
if (command.length === 0) throw new Error(`runtime.native.services.${id}.command must not be empty`);
|
||||
nativeServices[id] = {
|
||||
command,
|
||||
pidFile: nativeFile(service, "pidFile", `runtime.native.services.${id}`),
|
||||
logFile: nativeFile(service, "logFile", `runtime.native.services.${id}`),
|
||||
};
|
||||
}
|
||||
return {
|
||||
apiVersion: stringValue(raw, "apiVersion", "config"),
|
||||
kind: stringValue(raw, "kind", "config"),
|
||||
@@ -255,6 +325,15 @@ export function loadConfig(path: string): AppConfig {
|
||||
displayLimit: integerValue(ranking, "displayLimit", "ranking", 1),
|
||||
},
|
||||
records: { publicLimit: integerValue(records, "publicLimit", "records", 1) },
|
||||
temporal: {
|
||||
addressEnv: stringValue(temporal, "addressEnv", "temporal"),
|
||||
namespace: stringValue(temporal, "namespace", "temporal"),
|
||||
taskQueue: stringValue(temporal, "taskQueue", "temporal"),
|
||||
scoreScheduleWorkflowId: stringValue(temporal, "scoreScheduleWorkflowId", "temporal"),
|
||||
workflowExecutionTimeout: stringValue(temporal, "workflowExecutionTimeout", "temporal"),
|
||||
activityStartToCloseTimeout: stringValue(temporal, "activityStartToCloseTimeout", "temporal"),
|
||||
retry: { maximumAttempts: integerValue(temporalRetry, "maximumAttempts", "temporal.retry", 1) },
|
||||
},
|
||||
runtime: {
|
||||
secretsRoot: stringValue(runtime, "secretsRoot", "runtime"),
|
||||
secretSourcePaths: Object.fromEntries(Object.entries(secretSourcePathsRaw).map(([ref, value]) => {
|
||||
@@ -262,7 +341,13 @@ export function loadConfig(path: string): AppConfig {
|
||||
return [ref, value];
|
||||
})),
|
||||
defaultCliTarget,
|
||||
overApiTarget,
|
||||
cliTargets,
|
||||
native: {
|
||||
stateDir: stringValue(native, "stateDir", "runtime.native"),
|
||||
env: Object.fromEntries(Object.entries(nativeEnvRaw).map(([targetKey, value]) => [targetKey, secretRef(value, `runtime.native.env.${targetKey}`)])),
|
||||
services: nativeServices,
|
||||
},
|
||||
serverTargets,
|
||||
},
|
||||
configPath,
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
export type AppCommand =
|
||||
| { kind: "backend.check" }
|
||||
| { kind: "scores.get" }
|
||||
| { kind: "scores.refresh" }
|
||||
| { kind: "ranking.get" }
|
||||
| { kind: "lottery.publicState" }
|
||||
| { kind: "lottery.publicDraw" }
|
||||
| { kind: "lottery.status" }
|
||||
| { kind: "lottery.draw" }
|
||||
| { kind: "lottery.reset"; draws: number; includeRecords: boolean }
|
||||
| { kind: "records.list"; limit: number }
|
||||
| { kind: "records.delete"; id: string }
|
||||
| { kind: "credit.test"; execute: boolean };
|
||||
|
||||
export interface OperationRequest {
|
||||
operationId: string;
|
||||
command: AppCommand;
|
||||
}
|
||||
|
||||
export interface WorkflowOptions {
|
||||
activityStartToCloseTimeout: string;
|
||||
maximumAttempts: number;
|
||||
}
|
||||
|
||||
export interface ScheduledScoreRefreshInput extends WorkflowOptions {
|
||||
intervalMs: number;
|
||||
}
|
||||
|
||||
export function usesWorkflow(command: AppCommand): boolean {
|
||||
return command.kind === "scores.refresh"
|
||||
|| command.kind === "lottery.publicDraw"
|
||||
|| command.kind === "lottery.draw"
|
||||
|| command.kind === "lottery.reset"
|
||||
|| command.kind === "records.delete"
|
||||
|| (command.kind === "credit.test" && command.execute);
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { AppCommand, OperationRequest } from "./contracts";
|
||||
import type { AccountScoreService } from "./account-score-service";
|
||||
import type { LotteryService } from "./lottery-service";
|
||||
import type { TemporalGateway } from "./temporal-client";
|
||||
import { usesWorkflow } from "./contracts";
|
||||
|
||||
export interface DispatcherServices {
|
||||
lottery: LotteryService;
|
||||
scores: AccountScoreService;
|
||||
}
|
||||
|
||||
export async function dispatchDirect(services: DispatcherServices, command: AppCommand): Promise<unknown> {
|
||||
if (command.kind === "backend.check") return await services.lottery.status(true);
|
||||
if (command.kind === "scores.get") return services.scores.state();
|
||||
if (command.kind === "scores.refresh") return await services.scores.refresh();
|
||||
if (command.kind === "ranking.get") return await services.lottery.ranking();
|
||||
if (command.kind === "lottery.publicState") return await services.lottery.publicState();
|
||||
if (command.kind === "lottery.publicDraw") return await services.lottery.publicDraw();
|
||||
if (command.kind === "lottery.status") return await services.lottery.status(false);
|
||||
if (command.kind === "lottery.draw") return { ok: true, record: await services.lottery.draw() };
|
||||
if (command.kind === "lottery.reset") return services.lottery.reset(command.draws, command.includeRecords);
|
||||
if (command.kind === "records.list") return { ok: true, records: services.lottery.listRecords(command.limit) };
|
||||
if (command.kind === "records.delete") return services.lottery.deleteRecord(command.id);
|
||||
if (command.kind === "credit.test") return await services.lottery.creditTest(command.execute);
|
||||
const exhaustive: never = command;
|
||||
throw new Error(`unsupported command ${JSON.stringify(exhaustive)}`);
|
||||
}
|
||||
|
||||
export class ApplicationDispatcher {
|
||||
constructor(
|
||||
private readonly services: DispatcherServices,
|
||||
private readonly temporal: TemporalGateway | null,
|
||||
) {}
|
||||
|
||||
async dispatch(command: AppCommand): Promise<unknown> {
|
||||
if (!usesWorkflow(command)) return await dispatchDirect(this.services, command);
|
||||
if (!this.temporal) throw new Error(`command ${command.kind} requires Temporal`);
|
||||
return await this.temporal.execute(command);
|
||||
}
|
||||
|
||||
async submit(command: AppCommand): Promise<unknown> {
|
||||
if (!usesWorkflow(command)) throw new Error(`command ${command.kind} does not use Temporal`);
|
||||
if (!this.temporal) throw new Error(`command ${command.kind} requires Temporal`);
|
||||
return await this.temporal.submit(command);
|
||||
}
|
||||
|
||||
async workflowStatus(workflowId: string): Promise<unknown> {
|
||||
if (!this.temporal) throw new Error("workflow status requires Temporal");
|
||||
return await this.temporal.status(workflowId);
|
||||
}
|
||||
}
|
||||
|
||||
export function createActivities(services: DispatcherServices): { executeOperation(request: OperationRequest): Promise<unknown> } {
|
||||
return {
|
||||
executeOperation: async (request) => await dispatchDirect(services, request.command),
|
||||
};
|
||||
}
|
||||
+31
-21
@@ -1,7 +1,6 @@
|
||||
import { resolve } from "node:path";
|
||||
import type { AccountScoreService } from "./account-score-service";
|
||||
import type { AppConfig } from "./config";
|
||||
import type { LotteryService } from "./lottery-service";
|
||||
import type { ApplicationDispatcher } from "./dispatcher";
|
||||
import {
|
||||
apiKeyAuthorized,
|
||||
clearSessionCookie,
|
||||
@@ -44,11 +43,11 @@ async function staticFile(name: string, contentType: string): Promise<Response>
|
||||
}
|
||||
|
||||
export function createHandler(
|
||||
service: LotteryService,
|
||||
monitor: AccountScoreService,
|
||||
dispatcher: ApplicationDispatcher,
|
||||
config: AppConfig,
|
||||
auth: WebAuthSecrets,
|
||||
legacyAdminToken: string,
|
||||
secureCookies: boolean,
|
||||
): (request: Request) => Promise<Response> {
|
||||
return async (request) => {
|
||||
const url = new URL(request.url);
|
||||
@@ -56,16 +55,16 @@ export function createHandler(
|
||||
const apiKey = apiKeyAuthorized(request, auth) || request.headers.get("authorization") === `Bearer ${legacyAdminToken}`;
|
||||
try {
|
||||
if (request.method === "GET" && url.pathname === "/health") {
|
||||
const scores = monitor.state();
|
||||
return json({ ok: true, service: "apistate", scoreStatus: scores.status, refreshedAt: scores.refreshedAt });
|
||||
const scores = await dispatcher.dispatch({ kind: "scores.get" }) as Record<string, unknown>;
|
||||
return json({ ok: true, service: "apistate-api", scoreStatus: scores.status, refreshedAt: scores.refreshedAt });
|
||||
}
|
||||
if (request.method === "POST" && url.pathname === "/api/login") {
|
||||
const input = await body(request);
|
||||
if (!validLogin(input.username, input.password, config, auth)) return json({ ok: false, error: "用户名或密码错误" }, 401);
|
||||
return json({ ok: true, username: config.webAuth.username }, 200, { "set-cookie": createSessionCookie(config, auth) });
|
||||
return json({ ok: true, username: config.webAuth.username }, 200, { "set-cookie": createSessionCookie(config, auth, secureCookies) });
|
||||
}
|
||||
if (request.method === "POST" && url.pathname === "/api/logout") {
|
||||
return json({ ok: true }, 200, { "set-cookie": clearSessionCookie(config) });
|
||||
return json({ ok: true }, 200, { "set-cookie": clearSessionCookie(config, secureCookies) });
|
||||
}
|
||||
if ((request.method === "GET" || request.method === "HEAD") && url.pathname === "/login") {
|
||||
return session ? redirect("/scores") : await staticFile("login.html", "text/html; charset=utf-8");
|
||||
@@ -78,41 +77,52 @@ export function createHandler(
|
||||
|
||||
if (url.pathname.startsWith("/api/") && !session && !apiKey) return json({ ok: false, error: "unauthorized" }, 401);
|
||||
if (request.method === "GET" && url.pathname === "/api/status") {
|
||||
const scores = monitor.state();
|
||||
const scores = await dispatcher.dispatch({ kind: "scores.get" }) as Record<string, unknown>;
|
||||
return json({ ok: true, service: "apistate", scoreStatus: scores.status, refreshedAt: scores.refreshedAt, nextRefreshAt: scores.nextRefreshAt });
|
||||
}
|
||||
if (request.method === "GET" && url.pathname === "/api/scores") {
|
||||
const state = monitor.state();
|
||||
const state = await dispatcher.dispatch({ kind: "scores.get" }) as Record<string, unknown>;
|
||||
return json({ ...state, snapshotOk: state.ok, ok: true });
|
||||
}
|
||||
if (request.method === "POST" && url.pathname === "/api/scores/refresh") {
|
||||
const state = await monitor.refresh();
|
||||
const state = await dispatcher.dispatch({ kind: "scores.refresh" }) as Record<string, unknown>;
|
||||
return json({ ...state, snapshotOk: state.ok, ok: true });
|
||||
}
|
||||
if (request.method === "GET" && url.pathname === "/api/ranking") return json({ ok: true, ranking: await service.ranking() });
|
||||
if (request.method === "GET" && url.pathname === "/api/lottery") return json(await service.publicState());
|
||||
if (request.method === "POST" && url.pathname === "/api/lottery/draw") return json({ ok: true, record: await service.publicDraw() });
|
||||
if (request.method === "GET" && url.pathname === "/api/ranking") return json({ ok: true, ranking: await dispatcher.dispatch({ kind: "ranking.get" }) });
|
||||
if (request.method === "GET" && url.pathname === "/api/lottery") return json(await dispatcher.dispatch({ kind: "lottery.publicState" }));
|
||||
if (request.method === "POST" && url.pathname === "/api/lottery/draw") return json({ ok: true, record: await dispatcher.dispatch({ kind: "lottery.publicDraw" }) });
|
||||
|
||||
if (!url.pathname.startsWith("/api/admin/")) return json({ ok: false, error: "not found" }, 404);
|
||||
if (!apiKey) return json({ ok: false, error: "unauthorized" }, 401);
|
||||
if (request.method === "GET" && url.pathname === "/api/admin/status") return json(await service.status(false));
|
||||
if (request.method === "GET" && url.pathname === "/api/admin/backend-check") return json(await service.status(true));
|
||||
if (request.method === "POST" && url.pathname === "/api/admin/draw") return json({ ok: true, record: await service.draw() });
|
||||
if (request.method === "GET" && url.pathname === "/api/admin/status") return json(await dispatcher.dispatch({ kind: "lottery.status" }));
|
||||
if (request.method === "GET" && url.pathname === "/api/admin/backend-check") return json(await dispatcher.dispatch({ kind: "backend.check" }));
|
||||
if (request.method === "POST" && url.pathname === "/api/admin/draw") return json(await dispatcher.dispatch({ kind: "lottery.draw" }));
|
||||
if (request.method === "POST" && url.pathname === "/api/admin/reset") {
|
||||
const input = await body(request);
|
||||
if (!Number.isInteger(input.draws) || Number(input.draws) < 0 || typeof input.includeRecords !== "boolean") return json({ ok: false, error: "draws must be a non-negative integer and includeRecords must be boolean" }, 400);
|
||||
return json(service.reset(Number(input.draws), input.includeRecords));
|
||||
return json(await dispatcher.dispatch({ kind: "lottery.reset", draws: Number(input.draws), includeRecords: input.includeRecords }));
|
||||
}
|
||||
if (request.method === "GET" && url.pathname === "/api/admin/records") {
|
||||
const limit = Number(url.searchParams.get("limit"));
|
||||
if (!Number.isInteger(limit) || limit < 1) return json({ ok: false, error: "limit must be a positive integer" }, 400);
|
||||
return json({ ok: true, records: service.listRecords(limit) });
|
||||
return json(await dispatcher.dispatch({ kind: "records.list", limit }));
|
||||
}
|
||||
if (request.method === "DELETE" && url.pathname.startsWith("/api/admin/records/")) return json(await dispatcher.dispatch({ kind: "records.delete", id: decodeURIComponent(url.pathname.slice("/api/admin/records/".length)) }));
|
||||
if (request.method === "POST" && url.pathname === "/api/admin/workflows") {
|
||||
const input = await body(request);
|
||||
const command = input.command as Record<string, unknown> | undefined;
|
||||
if (command?.kind !== "scores.refresh") return json({ ok: false, error: "command.kind must be scores.refresh" }, 400);
|
||||
return json(await dispatcher.submit({ kind: "scores.refresh" }));
|
||||
}
|
||||
if (request.method === "GET" && url.pathname.startsWith("/api/admin/workflows/")) {
|
||||
const workflowId = decodeURIComponent(url.pathname.slice("/api/admin/workflows/".length));
|
||||
if (!workflowId) return json({ ok: false, error: "workflow id is required" }, 400);
|
||||
return json(await dispatcher.workflowStatus(workflowId));
|
||||
}
|
||||
if (request.method === "DELETE" && url.pathname.startsWith("/api/admin/records/")) return json(service.deleteRecord(decodeURIComponent(url.pathname.slice("/api/admin/records/".length))));
|
||||
if (request.method === "POST" && url.pathname === "/api/admin/credit-test") {
|
||||
const input = await body(request);
|
||||
if (typeof input.execute !== "boolean") return json({ ok: false, error: "execute must be boolean" }, 400);
|
||||
return json(await service.creditTest(input.execute));
|
||||
return json(await dispatcher.dispatch({ kind: "credit.test", execute: input.execute }));
|
||||
}
|
||||
return json({ ok: false, error: "not found" }, 404);
|
||||
} catch (error) {
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { spawn } from "node:child_process";
|
||||
import { resolve } from "node:path";
|
||||
import type { AppConfig, NativeServiceId } from "./config";
|
||||
import { readSecret } from "./secrets";
|
||||
|
||||
function paths(config: AppConfig, component: NativeServiceId): { stateDir: string; pid: string; log: string } {
|
||||
const service = config.runtime.native.services[component];
|
||||
const stateDir = resolve(config.rootDirectory, config.runtime.native.stateDir);
|
||||
return { stateDir, pid: resolve(stateDir, service.pidFile), log: resolve(stateDir, service.logFile) };
|
||||
}
|
||||
|
||||
function processId(path: string): number | null {
|
||||
if (!existsSync(path)) return null;
|
||||
const pid = Number(readFileSync(path, "utf8").trim());
|
||||
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
||||
}
|
||||
|
||||
function running(pid: number | null): boolean {
|
||||
if (!pid) return false;
|
||||
try {
|
||||
process.kill(pid, 0);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export function nativeStatus(config: AppConfig, component: NativeServiceId): Record<string, unknown> {
|
||||
const target = paths(config, component);
|
||||
const pid = processId(target.pid);
|
||||
return {
|
||||
ok: true,
|
||||
component,
|
||||
state: running(pid) ? "running" : "stopped",
|
||||
pid: running(pid) ? pid : null,
|
||||
pidFile: target.pid,
|
||||
logFile: target.log,
|
||||
valuesPrinted: false,
|
||||
};
|
||||
}
|
||||
|
||||
export function nativeStart(config: AppConfig, component: NativeServiceId): Record<string, unknown> {
|
||||
const current = nativeStatus(config, component);
|
||||
if (current.state === "running") return { ...current, mutation: false, reason: "already-running" };
|
||||
const temporalAddress = process.env[config.temporal.addressEnv];
|
||||
if ((component === "api" || component === "worker") && !temporalAddress) throw new Error(`native ${component} requires env ${config.temporal.addressEnv}`);
|
||||
const service = config.runtime.native.services[component];
|
||||
const target = paths(config, component);
|
||||
mkdirSync(target.stateDir, { recursive: true });
|
||||
rmSync(target.pid, { force: true });
|
||||
const env: Record<string, string> = { ...process.env } as Record<string, string>;
|
||||
for (const [targetKey, ref] of Object.entries(config.runtime.native.env)) env[targetKey] = readSecret(config, ref);
|
||||
env.APISTATE_CONFIG_PATH = config.configPath;
|
||||
env.APISTATE_RUNTIME_ID = "native";
|
||||
const log = openSync(target.log, "a", 0o600);
|
||||
try {
|
||||
const child = spawn(service.command[0]!, service.command.slice(1), {
|
||||
cwd: config.rootDirectory,
|
||||
env,
|
||||
detached: true,
|
||||
stdio: ["ignore", log, log],
|
||||
});
|
||||
if (!child.pid) throw new Error(`native ${component} did not return a pid`);
|
||||
writeFileSync(target.pid, `${child.pid}\n`, { encoding: "utf8", mode: 0o600 });
|
||||
child.unref();
|
||||
return { ok: true, component, state: "starting", pid: child.pid, pidFile: target.pid, logFile: target.log, mutation: true, valuesPrinted: false };
|
||||
} finally {
|
||||
closeSync(log);
|
||||
}
|
||||
}
|
||||
|
||||
export function nativeStop(config: AppConfig, component: NativeServiceId): Record<string, unknown> {
|
||||
const target = paths(config, component);
|
||||
const pid = processId(target.pid);
|
||||
if (!running(pid)) {
|
||||
rmSync(target.pid, { force: true });
|
||||
return { ok: true, component, state: "stopped", mutation: false, reason: "already-stopped", valuesPrinted: false };
|
||||
}
|
||||
process.kill(pid!, "SIGTERM");
|
||||
rmSync(target.pid, { force: true });
|
||||
return { ok: true, component, state: "stopping", pid, mutation: true, valuesPrinted: false };
|
||||
}
|
||||
|
||||
export function nativeLogs(config: AppConfig, component: NativeServiceId, tail: number): Record<string, unknown> {
|
||||
const target = paths(config, component);
|
||||
const lines = existsSync(target.log) ? readFileSync(target.log, "utf8").split(/\r?\n/u).filter(Boolean).slice(-tail) : [];
|
||||
return { ok: true, component, logFile: target.log, lines, lineCount: lines.length, mutation: false, valuesPrinted: false };
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export function requiredOption(name: string): string {
|
||||
const index = process.argv.indexOf(name);
|
||||
const value = index >= 0 ? process.argv[index + 1] : undefined;
|
||||
if (!value || value.startsWith("--")) throw new Error(`${name} is required`);
|
||||
return value;
|
||||
}
|
||||
+2
-1
@@ -19,7 +19,8 @@ function parseEnv(text: string): Map<string, string> {
|
||||
export function readSecret(config: AppConfig, ref: SecretRef): string {
|
||||
const path = config.runtime.secretSourcePaths[ref.sourceRef];
|
||||
if (!path) throw new Error(`runtime.secretSourcePaths is missing sourceRef ${ref.sourceRef}`);
|
||||
const value = parseEnv(readFileSync(path, "utf8")).get(ref.sourceKey);
|
||||
const text = readFileSync(path, "utf8");
|
||||
const value = ref.sourceKey === "contents" ? text.trim() : parseEnv(text).get(ref.sourceKey);
|
||||
if (!value) throw new Error(`secret source ${ref.sourceRef} is missing key ${ref.sourceKey}`);
|
||||
return value;
|
||||
}
|
||||
|
||||
+2
-34
@@ -1,35 +1,3 @@
|
||||
import { loadConfig } from "./config";
|
||||
import { createServerContext } from "./bootstrap";
|
||||
import { createHandler } from "./http";
|
||||
await import("./api");
|
||||
|
||||
function option(name: string): string {
|
||||
const index = process.argv.indexOf(name);
|
||||
const value = index >= 0 ? process.argv[index + 1] : undefined;
|
||||
if (!value) throw new Error(`${name} is required`);
|
||||
return value;
|
||||
}
|
||||
|
||||
const config = loadConfig(option("--config"));
|
||||
const runtimeId = option("--runtime");
|
||||
const target = config.runtime.serverTargets[runtimeId];
|
||||
if (!target) throw new Error(`runtime.serverTargets.${runtimeId} does not exist`);
|
||||
const adminToken = process.env[target.adminTokenEnv];
|
||||
if (!adminToken) throw new Error(`server target requires env ${target.adminTokenEnv}`);
|
||||
const context = createServerContext(config, target);
|
||||
const server = Bun.serve({
|
||||
hostname: target.listenHost,
|
||||
port: target.listenPort,
|
||||
fetch: createHandler(context.service, context.monitor, config, context.auth, adminToken),
|
||||
});
|
||||
|
||||
context.monitor.start();
|
||||
|
||||
console.log(JSON.stringify({ ok: true, service: config.metadata.name, runtime: runtimeId, listen: server.url.toString(), automaticCreditEnabled: config.lottery.automaticCredit.enabled }));
|
||||
|
||||
for (const signal of ["SIGINT", "SIGTERM"] as const) {
|
||||
process.on(signal, () => {
|
||||
context.close();
|
||||
server.stop(true);
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
export {};
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { Client, Connection } from "@temporalio/client";
|
||||
import type { AppConfig } from "./config";
|
||||
import type { AppCommand, OperationRequest } from "./contracts";
|
||||
|
||||
export function temporalAddress(config: AppConfig): string {
|
||||
const address = process.env[config.temporal.addressEnv];
|
||||
if (!address) throw new Error(`Temporal address requires env ${config.temporal.addressEnv}`);
|
||||
return address;
|
||||
}
|
||||
|
||||
export class TemporalGateway {
|
||||
private constructor(
|
||||
private readonly connection: Connection,
|
||||
private readonly client: Client,
|
||||
private readonly config: AppConfig,
|
||||
) {}
|
||||
|
||||
static async connect(config: AppConfig): Promise<TemporalGateway> {
|
||||
const connection = await Connection.connect({ address: temporalAddress(config) });
|
||||
return new TemporalGateway(connection, new Client({ connection, namespace: config.temporal.namespace }), config);
|
||||
}
|
||||
|
||||
async execute(command: AppCommand): Promise<unknown> {
|
||||
const submitted = await this.submit(command);
|
||||
return await this.client.workflow.getHandle(submitted.workflowId).result();
|
||||
}
|
||||
|
||||
async submit(command: AppCommand): Promise<{ ok: true; workflowId: string; runId: string; state: "submitted" }> {
|
||||
const operation: OperationRequest = { operationId: randomUUID(), command };
|
||||
const handle = await this.client.workflow.start("operationWorkflow", {
|
||||
taskQueue: this.config.temporal.taskQueue,
|
||||
workflowId: `apistate-${command.kind.replaceAll(".", "-")}-${operation.operationId}`,
|
||||
workflowExecutionTimeout: this.config.temporal.workflowExecutionTimeout,
|
||||
args: [{
|
||||
operation,
|
||||
activityStartToCloseTimeout: this.config.temporal.activityStartToCloseTimeout,
|
||||
maximumAttempts: this.config.temporal.retry.maximumAttempts,
|
||||
}],
|
||||
});
|
||||
return { ok: true, workflowId: handle.workflowId, runId: handle.firstExecutionRunId, state: "submitted" };
|
||||
}
|
||||
|
||||
async status(workflowId: string): Promise<Record<string, unknown>> {
|
||||
const handle = this.client.workflow.getHandle(workflowId);
|
||||
const description = await handle.describe();
|
||||
const state = description.status.name.toLocaleLowerCase("en-US");
|
||||
const terminal = ["completed", "failed", "cancelled", "terminated", "timed_out"].includes(state);
|
||||
let result: unknown = null;
|
||||
let error: string | null = null;
|
||||
if (state === "completed") result = await handle.result();
|
||||
else if (terminal) {
|
||||
try { await handle.result(); } catch (caught) { error = caught instanceof Error ? caught.message : String(caught); }
|
||||
}
|
||||
return { ok: state === "completed" || !terminal, workflowId, runId: description.runId, state, terminal, result, error };
|
||||
}
|
||||
|
||||
async ensureScoreSchedule(): Promise<{ started: boolean; workflowId: string }> {
|
||||
const workflowId = this.config.temporal.scoreScheduleWorkflowId;
|
||||
try {
|
||||
await this.client.workflow.start("scoreRefreshScheduleWorkflow", {
|
||||
taskQueue: this.config.temporal.taskQueue,
|
||||
workflowId,
|
||||
args: [{
|
||||
intervalMs: this.config.monitor.refreshIntervalMinutes * 60_000,
|
||||
activityStartToCloseTimeout: this.config.temporal.activityStartToCloseTimeout,
|
||||
maximumAttempts: this.config.temporal.retry.maximumAttempts,
|
||||
}],
|
||||
});
|
||||
return { started: true, workflowId };
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === "WorkflowExecutionAlreadyStartedError") return { started: false, workflowId };
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
await this.connection.close();
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -42,17 +42,17 @@ export function sessionAuthorized(request: Request, config: AppConfig, secrets:
|
||||
}
|
||||
}
|
||||
|
||||
export function createSessionCookie(config: AppConfig, secrets: WebAuthSecrets): string {
|
||||
export function createSessionCookie(config: AppConfig, secrets: WebAuthSecrets, secure: boolean): string {
|
||||
const payload = Buffer.from(JSON.stringify({
|
||||
user: config.webAuth.username,
|
||||
exp: Date.now() + config.webAuth.sessionTtlSeconds * 1000,
|
||||
})).toString("base64url");
|
||||
const value = `${payload}.${signature(payload, secrets.sessionSecret)}`;
|
||||
return `${config.webAuth.cookieName}=${value}; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=${config.webAuth.sessionTtlSeconds}`;
|
||||
return `${config.webAuth.cookieName}=${value}; Path=/; HttpOnly;${secure ? " Secure;" : ""} SameSite=Strict; Max-Age=${config.webAuth.sessionTtlSeconds}`;
|
||||
}
|
||||
|
||||
export function clearSessionCookie(config: AppConfig): string {
|
||||
return `${config.webAuth.cookieName}=; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=0`;
|
||||
export function clearSessionCookie(config: AppConfig, secure: boolean): string {
|
||||
return `${config.webAuth.cookieName}=; Path=/; HttpOnly;${secure ? " Secure;" : ""} SameSite=Strict; Max-Age=0`;
|
||||
}
|
||||
|
||||
export function validLogin(username: unknown, password: unknown, config: AppConfig, secrets: WebAuthSecrets): boolean {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { NativeConnection, Worker } from "@temporalio/worker";
|
||||
import { createServerContext } from "./bootstrap";
|
||||
import { loadConfig } from "./config";
|
||||
import { createActivities } from "./dispatcher";
|
||||
import { requiredOption } from "./runtime-args";
|
||||
import { temporalAddress, TemporalGateway } from "./temporal-client";
|
||||
|
||||
const config = loadConfig(requiredOption("--config"));
|
||||
const runtimeId = requiredOption("--runtime");
|
||||
const target = config.runtime.serverTargets[runtimeId];
|
||||
if (!target) throw new Error(`runtime.serverTargets.${runtimeId} does not exist`);
|
||||
|
||||
const context = createServerContext(config, target);
|
||||
const connection = await NativeConnection.connect({ address: temporalAddress(config) });
|
||||
const worker = await Worker.create({
|
||||
connection,
|
||||
namespace: config.temporal.namespace,
|
||||
taskQueue: config.temporal.taskQueue,
|
||||
workflowsPath: fileURLToPath(new URL("./workflows.ts", import.meta.url)),
|
||||
activities: createActivities({ lottery: context.service, scores: context.monitor }),
|
||||
});
|
||||
const temporal = await TemporalGateway.connect(config);
|
||||
const schedule = await temporal.ensureScoreSchedule();
|
||||
let state: "ready" | "stopping" = "ready";
|
||||
const health = Bun.serve({
|
||||
hostname: target.workerHealthHost,
|
||||
port: target.workerHealthPort,
|
||||
fetch: () => Response.json({
|
||||
ok: state === "ready",
|
||||
component: "apistate-worker",
|
||||
state,
|
||||
namespace: config.temporal.namespace,
|
||||
taskQueue: config.temporal.taskQueue,
|
||||
schedule,
|
||||
}, { status: state === "ready" ? 200 : 503 }),
|
||||
});
|
||||
|
||||
console.log(JSON.stringify({
|
||||
ok: true,
|
||||
component: "apistate-worker",
|
||||
runtime: runtimeId,
|
||||
health: health.url.toString(),
|
||||
temporalNamespace: config.temporal.namespace,
|
||||
temporalTaskQueue: config.temporal.taskQueue,
|
||||
schedule,
|
||||
valuesPrinted: false,
|
||||
}));
|
||||
|
||||
let stopping = false;
|
||||
async function stop(): Promise<void> {
|
||||
if (stopping) return;
|
||||
stopping = true;
|
||||
state = "stopping";
|
||||
health.stop(true);
|
||||
worker.shutdown();
|
||||
}
|
||||
for (const signal of ["SIGINT", "SIGTERM"] as const) process.on(signal, () => void stop());
|
||||
|
||||
try {
|
||||
await worker.run();
|
||||
} finally {
|
||||
await temporal.close();
|
||||
await connection.close();
|
||||
context.close();
|
||||
console.log(JSON.stringify({ ok: true, component: "apistate-worker", state: "stopped", valuesPrinted: false }));
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { continueAsNew, proxyActivities, sleep, workflowInfo } from "@temporalio/workflow";
|
||||
import type { OperationRequest, ScheduledScoreRefreshInput, WorkflowOptions } from "./contracts";
|
||||
|
||||
export interface Activities {
|
||||
executeOperation(request: OperationRequest): Promise<unknown>;
|
||||
}
|
||||
|
||||
interface OperationWorkflowInput extends WorkflowOptions {
|
||||
operation: OperationRequest;
|
||||
}
|
||||
|
||||
function activities(options: WorkflowOptions): Activities {
|
||||
return proxyActivities<Activities>({
|
||||
startToCloseTimeout: options.activityStartToCloseTimeout,
|
||||
retry: { maximumAttempts: options.maximumAttempts },
|
||||
});
|
||||
}
|
||||
|
||||
export async function operationWorkflow(input: OperationWorkflowInput): Promise<unknown> {
|
||||
return await activities(input).executeOperation(input.operation);
|
||||
}
|
||||
|
||||
export async function scoreRefreshScheduleWorkflow(input: ScheduledScoreRefreshInput): Promise<void> {
|
||||
const activity = activities(input);
|
||||
for (let iteration = 0; iteration < 500; iteration += 1) {
|
||||
await activity.executeOperation({
|
||||
operationId: `${workflowInfo().runId}:${iteration}`,
|
||||
command: { kind: "scores.refresh" },
|
||||
});
|
||||
await sleep(input.intervalMs);
|
||||
}
|
||||
await continueAsNew<typeof scoreRefreshScheduleWorkflow>(input);
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"types": ["bun"]
|
||||
},
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { defineConfig } from "vite";
|
||||
import { loadConfig } from "./src/config";
|
||||
|
||||
const configPath = process.env.APISTATE_CONFIG_PATH;
|
||||
const runtimeId = process.env.APISTATE_RUNTIME_ID;
|
||||
if (!configPath) throw new Error("Vite requires APISTATE_CONFIG_PATH");
|
||||
if (!runtimeId) throw new Error("Vite requires APISTATE_RUNTIME_ID");
|
||||
|
||||
const config = loadConfig(configPath);
|
||||
const target = config.runtime.serverTargets[runtimeId];
|
||||
if (!target) throw new Error(`runtime.serverTargets.${runtimeId} does not exist`);
|
||||
|
||||
export default defineConfig({
|
||||
root: "static",
|
||||
server: {
|
||||
host: target.webListenHost,
|
||||
port: target.webListenPort,
|
||||
strictPort: true,
|
||||
watch: { usePolling: true, interval: 300 },
|
||||
proxy: {
|
||||
"/api": { target: target.webApiBaseUrl, changeOrigin: true },
|
||||
"/health": { target: target.webApiBaseUrl, changeOrigin: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user