feat: 建立 ApiState Temporal native 开发链
This commit is contained in:
+2
-3
@@ -1,4 +1,4 @@
|
|||||||
FROM oven/bun:1.2.21-slim
|
FROM oven/bun:1.3.13-slim
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
@@ -8,7 +8,6 @@ RUN apt-get update \
|
|||||||
|
|
||||||
COPY package.json bun.lock ./
|
COPY package.json bun.lock ./
|
||||||
RUN bun install --frozen-lockfile --production
|
RUN bun install --frozen-lockfile --production
|
||||||
|
|
||||||
COPY config ./config
|
COPY config ./config
|
||||||
COPY src ./src
|
COPY src ./src
|
||||||
COPY static ./static
|
COPY static ./static
|
||||||
@@ -16,4 +15,4 @@ COPY static ./static
|
|||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
EXPOSE 8080
|
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",
|
"name": "sub2rank",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@temporalio/client": "^1.20.3",
|
||||||
|
"@temporalio/worker": "^1.20.3",
|
||||||
|
"@temporalio/workflow": "^1.20.3",
|
||||||
"luxon": "^3.7.2",
|
"luxon": "^3.7.2",
|
||||||
"yaml": "^2.8.1",
|
"yaml": "^2.8.1",
|
||||||
},
|
},
|
||||||
@@ -15,21 +18,355 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"trustedDependencies": [
|
||||||
|
"@swc/core",
|
||||||
|
"protobufjs",
|
||||||
|
],
|
||||||
"packages": {
|
"packages": {
|
||||||
|
"@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=="],
|
||||||
|
|
||||||
|
"@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=="],
|
||||||
|
|
||||||
|
"@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=="],
|
||||||
|
|
||||||
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
|
"@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/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=="],
|
"@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=="],
|
"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=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"fs-monkey": ["fs-monkey@1.1.0", "", {}, "sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"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=="],
|
"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=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"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=="],
|
||||||
|
|
||||||
|
"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=="],
|
"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=="],
|
"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=="],
|
||||||
|
|
||||||
|
"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=="],
|
"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=="],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+94
-3
@@ -74,27 +74,118 @@ ranking:
|
|||||||
records:
|
records:
|
||||||
publicLimit: 20
|
publicLimit: 20
|
||||||
|
|
||||||
|
temporal:
|
||||||
|
addressEnv: APISTATE_TEMPORAL_ADDRESS
|
||||||
|
namespace: unidesk
|
||||||
|
taskQueue: apistate
|
||||||
|
scoreScheduleWorkflowId: apistate-score-refresh-schedule
|
||||||
|
workflowExecutionTimeout: 5m
|
||||||
|
activityStartToCloseTimeout: 4m
|
||||||
|
retry:
|
||||||
|
maximumAttempts: 1
|
||||||
|
|
||||||
runtime:
|
runtime:
|
||||||
secretsRoot: /root/.unidesk/.state/secrets
|
secretsRoot: /root/.unidesk/.state/secrets
|
||||||
secretSourcePaths:
|
secretSourcePaths:
|
||||||
platform-infra/sub2api.env: /root/.unidesk/.state/secrets/platform-infra/sub2api.env
|
platform-infra/sub2api.env: /root/.unidesk/.state/secrets/platform-infra/sub2api.env
|
||||||
platform-infra/apistate.env: /root/.unidesk/.state/secrets/platform-infra/apistate.env
|
platform-infra/apistate.env: /root/.unidesk/.state/secrets/platform-infra/apistate.env
|
||||||
defaultCliTarget: local
|
defaultCliTarget: local
|
||||||
|
overApiTarget: native-api
|
||||||
cliTargets:
|
cliTargets:
|
||||||
local:
|
local:
|
||||||
mode: embedded
|
mode: embedded
|
||||||
databasePath: .state/sub2rank.sqlite
|
databasePath: .state/sub2rank.sqlite
|
||||||
|
scoreCachePath: .state/account-scores.json
|
||||||
production:
|
production:
|
||||||
mode: http
|
mode: http
|
||||||
baseUrl: https://apistate.hwpod.com
|
baseUrl: https://apistate.hwpod.com
|
||||||
adminToken:
|
adminToken:
|
||||||
sourceRef: platform-infra/apistate.env
|
sourceRef: platform-infra/apistate.env
|
||||||
sourceKey: APISTATE_API_KEY
|
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: platform-infra/apistate.env
|
||||||
|
sourceKey: APISTATE_WEB_PASSWORD
|
||||||
|
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:
|
||||||
|
- bun
|
||||||
|
- --hot
|
||||||
|
- src/dev-web.ts
|
||||||
|
- --config
|
||||||
|
- config/sub2rank.yaml
|
||||||
|
- --runtime
|
||||||
|
- native
|
||||||
|
pidFile: web.pid
|
||||||
|
logFile: web.log
|
||||||
serverTargets:
|
serverTargets:
|
||||||
k8s:
|
native:
|
||||||
listenHost: 0.0.0.0
|
listenHost: 127.0.0.1
|
||||||
listenPort: 8080
|
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
|
||||||
|
databasePath: .state/sub2rank.sqlite
|
||||||
|
scoreCachePath: .state/account-scores.json
|
||||||
|
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
|
||||||
|
databasePath: /var/lib/sub2rank/sub2rank.sqlite
|
||||||
|
scoreCachePath: /var/lib/sub2rank/account-scores.json
|
||||||
adminTokenEnv: APISTATE_API_KEY
|
adminTokenEnv: APISTATE_API_KEY
|
||||||
sub2apiAdminEmailEnv: SUB2API_ADMIN_EMAIL
|
sub2apiAdminEmailEnv: SUB2API_ADMIN_EMAIL
|
||||||
sub2apiAdminPasswordEnv: SUB2API_ADMIN_PASSWORD
|
sub2apiAdminPasswordEnv: SUB2API_ADMIN_PASSWORD
|
||||||
|
|||||||
+12
-2
@@ -5,10 +5,16 @@
|
|||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"cli": "bun scripts/apistate-cli.ts --config config/sub2rank.yaml",
|
"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": "bun --hot src/dev-web.ts --config config/sub2rank.yaml --runtime native",
|
||||||
|
"server": "bun src/api.ts --config config/sub2rank.yaml --runtime k8s",
|
||||||
"check": "bunx tsc --noEmit"
|
"check": "bunx tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@temporalio/client": "^1.20.3",
|
||||||
|
"@temporalio/worker": "^1.20.3",
|
||||||
|
"@temporalio/workflow": "^1.20.3",
|
||||||
"luxon": "^3.7.2",
|
"luxon": "^3.7.2",
|
||||||
"yaml": "^2.8.1"
|
"yaml": "^2.8.1"
|
||||||
},
|
},
|
||||||
@@ -16,5 +22,9 @@
|
|||||||
"@types/bun": "^1.2.21",
|
"@types/bun": "^1.2.21",
|
||||||
"@types/luxon": "^3.7.1",
|
"@types/luxon": "^3.7.1",
|
||||||
"typescript": "^5.9.2"
|
"typescript": "^5.9.2"
|
||||||
}
|
},
|
||||||
|
"trustedDependencies": [
|
||||||
|
"@swc/core",
|
||||||
|
"protobufjs"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
+104
-96
@@ -1,7 +1,12 @@
|
|||||||
import { AdminHttpClient } from "../../src/admin-http-client";
|
import { AdminHttpClient } from "../../src/admin-http-client";
|
||||||
import { mergeAccountScores } from "../../src/account-score-aggregation";
|
import { mergeAccountScores } from "../../src/account-score-aggregation";
|
||||||
import { createEmbeddedContext } from "../../src/bootstrap";
|
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 {
|
interface Parsed {
|
||||||
configPath: string;
|
configPath: string;
|
||||||
@@ -9,9 +14,13 @@ interface Parsed {
|
|||||||
command: string[];
|
command: string[];
|
||||||
confirm: boolean;
|
confirm: boolean;
|
||||||
includeRecords: boolean;
|
includeRecords: boolean;
|
||||||
|
overApi: boolean;
|
||||||
|
json: boolean;
|
||||||
id: string | null;
|
id: string | null;
|
||||||
|
component: NativeServiceId | null;
|
||||||
limit: number | null;
|
limit: number | null;
|
||||||
draws: number | null;
|
draws: number | null;
|
||||||
|
tail: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function record(value: unknown): Record<string, unknown> | 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 {
|
function parseArgs(args: string[]): Parsed {
|
||||||
const configPath = value(args, "--config");
|
const configPath = value(args, "--config");
|
||||||
if (!configPath) throw new Error("--config is required");
|
if (!configPath) throw new Error("--config is required");
|
||||||
const optionNames = new Set(["--config", "--target", "--id", "--limit", "--draws"]);
|
const optionNames = new Set(["--config", "--target", "--id", "--limit", "--draws", "--component", "--tail"]);
|
||||||
const flags = new Set(["--confirm", "--include-records"]);
|
const flags = new Set(["--confirm", "--include-records", "--over-api", "--json"]);
|
||||||
const command: string[] = [];
|
const command: string[] = [];
|
||||||
for (let index = 0; index < args.length; index += 1) {
|
for (let index = 0; index < args.length; index += 1) {
|
||||||
const item = args[index]!;
|
const item = args[index]!;
|
||||||
@@ -46,56 +55,90 @@ function parseArgs(args: string[]): Parsed {
|
|||||||
if (!Number.isInteger(parsed) || parsed < 0) throw new Error(`${name} must be a non-negative integer`);
|
if (!Number.isInteger(parsed) || parsed < 0) throw new Error(`${name} must be a non-negative integer`);
|
||||||
return parsed;
|
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 {
|
return {
|
||||||
configPath,
|
configPath,
|
||||||
targetId: value(args, "--target"),
|
targetId: value(args, "--target"),
|
||||||
command,
|
command,
|
||||||
confirm: args.includes("--confirm"),
|
confirm: args.includes("--confirm"),
|
||||||
includeRecords: args.includes("--include-records"),
|
includeRecords: args.includes("--include-records"),
|
||||||
|
overApi: args.includes("--over-api"),
|
||||||
|
json: args.includes("--json"),
|
||||||
id: value(args, "--id"),
|
id: value(args, "--id"),
|
||||||
|
component,
|
||||||
limit: integer("--limit"),
|
limit: integer("--limit"),
|
||||||
draws: integer("--draws"),
|
draws: integer("--draws"),
|
||||||
|
tail: integer("--tail"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function help(): Record<string, unknown> {
|
function help(): Record<string, unknown> {
|
||||||
return {
|
return {
|
||||||
ok: true,
|
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: [
|
commands: [
|
||||||
"config validate",
|
"config validate",
|
||||||
"backend check",
|
"backend check",
|
||||||
"scores aggregate-smoke",
|
"scores get|refresh|aggregate-smoke",
|
||||||
"lottery status",
|
"lottery status|draw|reset",
|
||||||
"lottery draw [--confirm]",
|
"records list|delete",
|
||||||
"lottery reset [--draws N] [--include-records] --confirm",
|
"credit test",
|
||||||
"records list [--limit N]",
|
"api smoke --over-api",
|
||||||
"records delete --id <record-id> --confirm",
|
"native start|stop|status|logs --component api|worker|web [--tail N]",
|
||||||
"credit test [--confirm]",
|
|
||||||
"api smoke",
|
|
||||||
],
|
],
|
||||||
|
output: "k8s-style text by default; add --json for machine output",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function embedded(parsed: Parsed, config: ReturnType<typeof loadConfig>, target: EmbeddedCliTarget): Promise<unknown> {
|
function emit(value: Record<string, unknown>, json: boolean): void {
|
||||||
const context = createEmbeddedContext(config, target);
|
if (json) {
|
||||||
try {
|
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;
|
const [group, action] = parsed.command;
|
||||||
if (group === "backend" && action === "check") return await context.service.status(true);
|
if (group === "backend" && action === "check") return { kind: "backend.check" };
|
||||||
if (group === "lottery" && action === "status") return await context.service.status(false);
|
if (group === "scores" && action === "get") return { kind: "scores.get" };
|
||||||
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 === "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") {
|
if (group === "lottery" && action === "reset") {
|
||||||
const draws = parsed.draws ?? config.lottery.initialDrawCount;
|
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" };
|
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 { ok: true, records: context.service.listRecords(parsed.limit ?? config.records.publicLimit) };
|
if (group === "records" && action === "list") return { kind: "records.list", limit: parsed.limit ?? config.records.publicLimit };
|
||||||
if (group === "records" && action === "delete") {
|
if (group === "records" && action === "delete") {
|
||||||
if (!parsed.id) throw new Error("records delete requires --id");
|
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" };
|
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 await context.service.creditTest(parsed.confirm);
|
if (group === "credit" && action === "test") return { kind: "credit.test", execute: parsed.confirm };
|
||||||
throw new Error(`unknown command: ${parsed.command.join(" ")}`);
|
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> {
|
||||||
|
const command = appCommand(parsed, config);
|
||||||
|
if (!isAppCommand(command)) return command;
|
||||||
|
const context = createEmbeddedContext(config, target);
|
||||||
|
let temporal: TemporalGateway | null = null;
|
||||||
|
try {
|
||||||
|
if (usesWorkflow(command)) temporal = await TemporalGateway.connect(config);
|
||||||
|
return await new ApplicationDispatcher({ lottery: context.service, scores: context.monitor }, temporal).dispatch(command);
|
||||||
} finally {
|
} finally {
|
||||||
|
if (temporal) await temporal.close();
|
||||||
context.close();
|
context.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,6 +147,8 @@ async function remote(parsed: Parsed, config: ReturnType<typeof loadConfig>, tar
|
|||||||
const client = new AdminHttpClient(config, target);
|
const client = new AdminHttpClient(config, target);
|
||||||
const [group, action] = parsed.command;
|
const [group, action] = parsed.command;
|
||||||
if (group === "backend" && action === "check") return await client.backendCheck();
|
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.refreshScores();
|
||||||
if (group === "lottery" && action === "status") return await client.status();
|
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 === "draw") return parsed.confirm ? await client.draw() : { ok: true, mutation: false, action: "lottery-draw", hint: "add --confirm to execute" };
|
||||||
if (group === "lottery" && action === "reset") {
|
if (group === "lottery" && action === "reset") {
|
||||||
@@ -117,72 +162,25 @@ async function remote(parsed: Parsed, config: ReturnType<typeof loadConfig>, tar
|
|||||||
}
|
}
|
||||||
if (group === "credit" && action === "test") return await client.creditTest(parsed.confirm);
|
if (group === "credit" && action === "test") return await client.creditTest(parsed.confirm);
|
||||||
if (group === "api" && action === "smoke") {
|
if (group === "api" && action === "smoke") {
|
||||||
const [status, scores, ranking, lottery] = await Promise.all([
|
const [status, scores, ranking, lottery] = await Promise.all([client.serviceStatus(), client.scores(), client.ranking(), client.lottery()]);
|
||||||
client.serviceStatus(),
|
|
||||||
client.scores(),
|
|
||||||
client.ranking(),
|
|
||||||
client.lottery(),
|
|
||||||
]);
|
|
||||||
const refreshed = await client.refreshScores();
|
const refreshed = await client.refreshScores();
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: status.ok === true && scores.ok === true && refreshed.ok === true && ranking.ok === true && lottery.ok === true,
|
||||||
action: "apistate-api-smoke",
|
action: "apistate-api-smoke",
|
||||||
checks: {
|
checks: { status: status.ok === true, scores: scores.ok === true, refresh: refreshed.ok === true && refreshed.snapshotOk === true, ranking: ranking.ok === true, lottery: lottery.ok === true },
|
||||||
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,
|
valuesPrinted: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
throw new Error(`unknown command: ${parsed.command.join(" ")}`);
|
throw new Error(`unknown command: ${parsed.command.join(" ")}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runCli(args: string[]): Promise<void> {
|
function aggregateSmoke(): Record<string, unknown> {
|
||||||
try {
|
|
||||||
if (args.includes("--help") || args.length === 0) {
|
|
||||||
console.log(JSON.stringify(help(), null, 2));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
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(" ") === "scores aggregate-smoke") {
|
|
||||||
const account = (groupId: number, groupName: string, successRequests: number, failureRequests: number, ttftP95Ms: number, apiAmountUsd: number) => ({
|
const account = (groupId: number, groupName: string, successRequests: number, failureRequests: number, ttftP95Ms: number, apiAmountUsd: number) => ({
|
||||||
accountId: 15,
|
accountId: 15, accountName: "lyon9801 0.0", groupId, groupName, status: "active", currentlyAvailable: true, priority: 1,
|
||||||
accountName: "lyon9801 0.0",
|
successRequests, failureRequests, observedAttempts: successRequests + failureRequests, streamSuccessRequests: successRequests,
|
||||||
groupId,
|
firstTokenSamples: successRequests, ttftP95Ms, usage: { requestCount: successRequests, tokenCount: successRequests * 1000, apiAmountUsd },
|
||||||
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([
|
const rows = mergeAccountScores([account(3, "自用", 100, 0, 10_591, 1), account(2, "unidesk-codex-pool", 50, 1, 13_206, 0.5)]);
|
||||||
account(3, "自用", 100, 0, 10_591, 1),
|
|
||||||
account(2, "unidesk-codex-pool", 50, 1, 13_206, 0.5),
|
|
||||||
]);
|
|
||||||
const row = rows[0] ?? {};
|
const row = rows[0] ?? {};
|
||||||
const checks = {
|
const checks = {
|
||||||
uniqueAccount: rows.length === 1,
|
uniqueAccount: rows.length === 1,
|
||||||
@@ -192,30 +190,40 @@ export async function runCli(args: string[]): Promise<void> {
|
|||||||
decimalScore: typeof row.score === "number" && !Number.isInteger(row.score),
|
decimalScore: typeof row.score === "number" && !Number.isInteger(row.score),
|
||||||
conservativeTtft: row.ttftP95Ms === 13_206,
|
conservativeTtft: row.ttftP95Ms === 13_206,
|
||||||
};
|
};
|
||||||
console.log(JSON.stringify({
|
return { ok: Object.values(checks).every(Boolean), action: "account-score-aggregate-smoke", checks, mutation: false };
|
||||||
ok: Object.values(checks).every(Boolean),
|
}
|
||||||
action: "account-score-aggregate-smoke",
|
|
||||||
checks,
|
export async function runCli(args: string[]): Promise<void> {
|
||||||
result: {
|
const wantsJson = args.includes("--json");
|
||||||
accountId: row.accountId,
|
try {
|
||||||
accountName: row.accountName,
|
if (args.includes("--help") || args.length === 0) return emit(help(), wantsJson);
|
||||||
groupNames: row.groupNames,
|
const parsed = parseArgs(args);
|
||||||
observedAttempts: row.observedAttempts,
|
const config = loadConfig(parsed.configPath);
|
||||||
score: row.score,
|
if (parsed.command.join(" ") === "config validate") return emit({
|
||||||
ttftP95Ms: row.ttftP95Ms,
|
ok: true, configPath: config.configPath, kind: config.kind, service: config.metadata.name,
|
||||||
usage: row.usage,
|
temporalNamespace: config.temporal.namespace, temporalTaskQueue: config.temporal.taskQueue,
|
||||||
},
|
refreshIntervalMinutes: config.monitor.refreshIntervalMinutes, scoreWindow: config.monitor.scoreWindow,
|
||||||
mutation: false,
|
automaticCreditEnabled: config.lottery.automaticCredit.enabled, valuesPrinted: false,
|
||||||
}, null, 2));
|
}, parsed.json);
|
||||||
return;
|
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);
|
||||||
}
|
}
|
||||||
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];
|
const target = config.runtime.cliTargets[targetId];
|
||||||
if (!target) throw new Error(`runtime.cliTargets.${targetId} does not exist`);
|
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);
|
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) {
|
} 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;
|
process.exitCode = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+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),
|
||||||
|
});
|
||||||
|
|
||||||
|
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 {
|
export function createEmbeddedContext(config: AppConfig, target: EmbeddedCliTarget): AppContext {
|
||||||
const store = new LotteryStore(config, resolveDataPath(config, target.databasePath));
|
const store = new LotteryStore(config, resolveDataPath(config, target.databasePath));
|
||||||
const client = new Sub2ApiClient(config, readSub2ApiCredentials(config));
|
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));
|
||||||
return {
|
return {
|
||||||
service: new LotteryService(config, store, client),
|
service: new LotteryService(config, store, client),
|
||||||
store,
|
store,
|
||||||
@@ -39,7 +39,7 @@ export function createServerContext(config: AppConfig, target: ServerTarget): Ap
|
|||||||
}
|
}
|
||||||
const store = new LotteryStore(config, resolveDataPath(config, target.databasePath));
|
const store = new LotteryStore(config, resolveDataPath(config, target.databasePath));
|
||||||
const client = new Sub2ApiClient(config, { email, password });
|
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));
|
||||||
return {
|
return {
|
||||||
service: new LotteryService(config, store, client),
|
service: new LotteryService(config, store, client),
|
||||||
store,
|
store,
|
||||||
|
|||||||
+80
-1
@@ -50,11 +50,26 @@ export interface AppConfig {
|
|||||||
};
|
};
|
||||||
ranking: { timezone: string; windowDays: number; sourceLimit: number; displayLimit: number };
|
ranking: { timezone: string; windowDays: number; sourceLimit: number; displayLimit: number };
|
||||||
records: { publicLimit: number };
|
records: { publicLimit: number };
|
||||||
|
temporal: {
|
||||||
|
addressEnv: string;
|
||||||
|
namespace: string;
|
||||||
|
taskQueue: string;
|
||||||
|
scoreScheduleWorkflowId: string;
|
||||||
|
workflowExecutionTimeout: string;
|
||||||
|
activityStartToCloseTimeout: string;
|
||||||
|
retry: { maximumAttempts: number };
|
||||||
|
};
|
||||||
runtime: {
|
runtime: {
|
||||||
secretsRoot: string;
|
secretsRoot: string;
|
||||||
secretSourcePaths: Record<string, string>;
|
secretSourcePaths: Record<string, string>;
|
||||||
defaultCliTarget: string;
|
defaultCliTarget: string;
|
||||||
|
overApiTarget: string;
|
||||||
cliTargets: Record<string, EmbeddedCliTarget | HttpCliTarget>;
|
cliTargets: Record<string, EmbeddedCliTarget | HttpCliTarget>;
|
||||||
|
native: {
|
||||||
|
stateDir: string;
|
||||||
|
env: Record<string, SecretRef>;
|
||||||
|
services: Record<NativeServiceId, NativeServiceConfig>;
|
||||||
|
};
|
||||||
serverTargets: Record<string, ServerTarget>;
|
serverTargets: Record<string, ServerTarget>;
|
||||||
};
|
};
|
||||||
configPath: string;
|
configPath: string;
|
||||||
@@ -64,6 +79,7 @@ export interface AppConfig {
|
|||||||
export interface EmbeddedCliTarget {
|
export interface EmbeddedCliTarget {
|
||||||
mode: "embedded";
|
mode: "embedded";
|
||||||
databasePath: string;
|
databasePath: string;
|
||||||
|
scoreCachePath: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HttpCliTarget {
|
export interface HttpCliTarget {
|
||||||
@@ -75,7 +91,13 @@ export interface HttpCliTarget {
|
|||||||
export interface ServerTarget {
|
export interface ServerTarget {
|
||||||
listenHost: string;
|
listenHost: string;
|
||||||
listenPort: number;
|
listenPort: number;
|
||||||
|
workerHealthHost: string;
|
||||||
|
workerHealthPort: number;
|
||||||
|
webListenHost: string;
|
||||||
|
webListenPort: number;
|
||||||
|
webApiBaseUrl: string;
|
||||||
databasePath: string;
|
databasePath: string;
|
||||||
|
scoreCachePath: string;
|
||||||
adminTokenEnv: string;
|
adminTokenEnv: string;
|
||||||
sub2apiAdminEmailEnv: string;
|
sub2apiAdminEmailEnv: string;
|
||||||
sub2apiAdminPasswordEnv: string;
|
sub2apiAdminPasswordEnv: string;
|
||||||
@@ -84,6 +106,14 @@ export interface ServerTarget {
|
|||||||
sessionSecretEnv: string;
|
sessionSecretEnv: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type NativeServiceId = "api" | "worker" | "web";
|
||||||
|
|
||||||
|
export interface NativeServiceConfig {
|
||||||
|
command: string[];
|
||||||
|
pidFile: string;
|
||||||
|
logFile: string;
|
||||||
|
}
|
||||||
|
|
||||||
type ObjectValue = Record<string, unknown>;
|
type ObjectValue = Record<string, unknown>;
|
||||||
|
|
||||||
function object(value: unknown, path: string): ObjectValue {
|
function object(value: unknown, path: string): ObjectValue {
|
||||||
@@ -139,6 +169,12 @@ function secretRef(value: unknown, path: string): SecretRef {
|
|||||||
return { sourceRef: stringValue(raw, "sourceRef", path), sourceKey: stringValue(raw, "sourceKey", path) };
|
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 {
|
export function loadConfig(path: string): AppConfig {
|
||||||
const configPath = resolve(path);
|
const configPath = resolve(path);
|
||||||
const rootDirectory = resolve(dirname(configPath), "..");
|
const rootDirectory = resolve(dirname(configPath), "..");
|
||||||
@@ -157,7 +193,12 @@ export function loadConfig(path: string): AppConfig {
|
|||||||
const creditTest = object(lottery.creditTest, "lottery.creditTest");
|
const creditTest = object(lottery.creditTest, "lottery.creditTest");
|
||||||
const ranking = object(raw.ranking, "ranking");
|
const ranking = object(raw.ranking, "ranking");
|
||||||
const records = object(raw.records, "records");
|
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 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 secretSourcePathsRaw = object(runtime.secretSourcePaths, "runtime.secretSourcePaths");
|
||||||
const cliTargetsRaw = object(runtime.cliTargets, "runtime.cliTargets");
|
const cliTargetsRaw = object(runtime.cliTargets, "runtime.cliTargets");
|
||||||
const serverTargetsRaw = object(runtime.serverTargets, "runtime.serverTargets");
|
const serverTargetsRaw = object(runtime.serverTargets, "runtime.serverTargets");
|
||||||
@@ -165,7 +206,11 @@ export function loadConfig(path: string): AppConfig {
|
|||||||
for (const [id, value] of Object.entries(cliTargetsRaw)) {
|
for (const [id, value] of Object.entries(cliTargetsRaw)) {
|
||||||
const target = object(value, `runtime.cliTargets.${id}`);
|
const target = object(value, `runtime.cliTargets.${id}`);
|
||||||
const mode = stringValue(target, "mode", `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}`),
|
||||||
|
};
|
||||||
else if (mode === "http") cliTargets[id] = { mode, baseUrl: stringValue(target, "baseUrl", `runtime.cliTargets.${id}`), adminToken: secretRef(target.adminToken, `runtime.cliTargets.${id}.adminToken`) };
|
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`);
|
else throw new Error(`runtime.cliTargets.${id}.mode must be embedded or http`);
|
||||||
}
|
}
|
||||||
@@ -175,7 +220,13 @@ export function loadConfig(path: string): AppConfig {
|
|||||||
serverTargets[id] = {
|
serverTargets[id] = {
|
||||||
listenHost: stringValue(target, "listenHost", `runtime.serverTargets.${id}`),
|
listenHost: stringValue(target, "listenHost", `runtime.serverTargets.${id}`),
|
||||||
listenPort: numberValue(target, "listenPort", `runtime.serverTargets.${id}`, 1),
|
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}`),
|
||||||
databasePath: stringValue(target, "databasePath", `runtime.serverTargets.${id}`),
|
databasePath: stringValue(target, "databasePath", `runtime.serverTargets.${id}`),
|
||||||
|
scoreCachePath: stringValue(target, "scoreCachePath", `runtime.serverTargets.${id}`),
|
||||||
adminTokenEnv: stringValue(target, "adminTokenEnv", `runtime.serverTargets.${id}`),
|
adminTokenEnv: stringValue(target, "adminTokenEnv", `runtime.serverTargets.${id}`),
|
||||||
sub2apiAdminEmailEnv: stringValue(target, "sub2apiAdminEmailEnv", `runtime.serverTargets.${id}`),
|
sub2apiAdminEmailEnv: stringValue(target, "sub2apiAdminEmailEnv", `runtime.serverTargets.${id}`),
|
||||||
sub2apiAdminPasswordEnv: stringValue(target, "sub2apiAdminPasswordEnv", `runtime.serverTargets.${id}`),
|
sub2apiAdminPasswordEnv: stringValue(target, "sub2apiAdminPasswordEnv", `runtime.serverTargets.${id}`),
|
||||||
@@ -188,6 +239,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");
|
if (automaticMode !== "dry-run" && automaticMode !== "live") throw new Error("lottery.automaticCredit.mode must be dry-run or live");
|
||||||
const defaultCliTarget = stringValue(runtime, "defaultCliTarget", "runtime");
|
const defaultCliTarget = stringValue(runtime, "defaultCliTarget", "runtime");
|
||||||
if (!cliTargets[defaultCliTarget]) throw new Error(`runtime.defaultCliTarget references missing target ${defaultCliTarget}`);
|
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 {
|
return {
|
||||||
apiVersion: stringValue(raw, "apiVersion", "config"),
|
apiVersion: stringValue(raw, "apiVersion", "config"),
|
||||||
kind: stringValue(raw, "kind", "config"),
|
kind: stringValue(raw, "kind", "config"),
|
||||||
@@ -255,6 +319,15 @@ export function loadConfig(path: string): AppConfig {
|
|||||||
displayLimit: integerValue(ranking, "displayLimit", "ranking", 1),
|
displayLimit: integerValue(ranking, "displayLimit", "ranking", 1),
|
||||||
},
|
},
|
||||||
records: { publicLimit: integerValue(records, "publicLimit", "records", 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: {
|
runtime: {
|
||||||
secretsRoot: stringValue(runtime, "secretsRoot", "runtime"),
|
secretsRoot: stringValue(runtime, "secretsRoot", "runtime"),
|
||||||
secretSourcePaths: Object.fromEntries(Object.entries(secretSourcePathsRaw).map(([ref, value]) => {
|
secretSourcePaths: Object.fromEntries(Object.entries(secretSourcePathsRaw).map(([ref, value]) => {
|
||||||
@@ -262,7 +335,13 @@ export function loadConfig(path: string): AppConfig {
|
|||||||
return [ref, value];
|
return [ref, value];
|
||||||
})),
|
})),
|
||||||
defaultCliTarget,
|
defaultCliTarget,
|
||||||
|
overApiTarget,
|
||||||
cliTargets,
|
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,
|
serverTargets,
|
||||||
},
|
},
|
||||||
configPath,
|
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,56 @@
|
|||||||
|
import { resolve } from "node:path";
|
||||||
|
import { loadConfig } from "./config";
|
||||||
|
import { requiredOption } from "./runtime-args";
|
||||||
|
|
||||||
|
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 staticRoot = resolve(import.meta.dir, "../static");
|
||||||
|
|
||||||
|
const contentTypes: Record<string, string> = {
|
||||||
|
".css": "text/css; charset=utf-8",
|
||||||
|
".html": "text/html; charset=utf-8",
|
||||||
|
".js": "text/javascript; charset=utf-8",
|
||||||
|
};
|
||||||
|
|
||||||
|
function staticName(pathname: string): string | null {
|
||||||
|
if (pathname === "/") return "login.html";
|
||||||
|
const value = pathname.slice(1);
|
||||||
|
if (!["login.html", "scores.html", "ranking.html", "lottery.html", "app.js", "styles.css"].includes(value)) return null;
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = Bun.serve({
|
||||||
|
hostname: target.webListenHost,
|
||||||
|
port: target.webListenPort,
|
||||||
|
async fetch(request) {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
if (url.pathname.startsWith("/api/") || url.pathname === "/health") {
|
||||||
|
const upstream = new URL(url.pathname + url.search, target.webApiBaseUrl);
|
||||||
|
return await fetch(new Request(upstream, request));
|
||||||
|
}
|
||||||
|
const pageAlias = ({ "/login": "login.html", "/scores": "scores.html", "/ranking": "ranking.html", "/lottery": "lottery.html" } as Record<string, string>)[url.pathname];
|
||||||
|
const name = pageAlias ?? staticName(url.pathname);
|
||||||
|
if (!name) return Response.json({ ok: false, error: "not found" }, { status: 404 });
|
||||||
|
const file = Bun.file(resolve(staticRoot, name));
|
||||||
|
if (!(await file.exists())) return Response.json({ ok: false, error: "not found" }, { status: 404 });
|
||||||
|
const extension = name.slice(name.lastIndexOf("."));
|
||||||
|
return new Response(file, { headers: { "content-type": contentTypes[extension] ?? "application/octet-stream", "cache-control": "no-store" } });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(JSON.stringify({
|
||||||
|
ok: true,
|
||||||
|
component: "apistate-web-native",
|
||||||
|
runtime: runtimeId,
|
||||||
|
listen: server.url.toString(),
|
||||||
|
apiBaseUrl: target.webApiBaseUrl,
|
||||||
|
hotReload: true,
|
||||||
|
valuesPrinted: false,
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (const signal of ["SIGINT", "SIGTERM"] as const) process.on(signal, () => {
|
||||||
|
server.stop(true);
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createActivities(services: DispatcherServices): { executeOperation(request: OperationRequest): Promise<unknown> } {
|
||||||
|
return {
|
||||||
|
executeOperation: async (request) => await dispatchDirect(services, request.command),
|
||||||
|
};
|
||||||
|
}
|
||||||
+17
-19
@@ -1,7 +1,6 @@
|
|||||||
import { resolve } from "node:path";
|
import { resolve } from "node:path";
|
||||||
import type { AccountScoreService } from "./account-score-service";
|
|
||||||
import type { AppConfig } from "./config";
|
import type { AppConfig } from "./config";
|
||||||
import type { LotteryService } from "./lottery-service";
|
import type { ApplicationDispatcher } from "./dispatcher";
|
||||||
import {
|
import {
|
||||||
apiKeyAuthorized,
|
apiKeyAuthorized,
|
||||||
clearSessionCookie,
|
clearSessionCookie,
|
||||||
@@ -44,8 +43,7 @@ async function staticFile(name: string, contentType: string): Promise<Response>
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function createHandler(
|
export function createHandler(
|
||||||
service: LotteryService,
|
dispatcher: ApplicationDispatcher,
|
||||||
monitor: AccountScoreService,
|
|
||||||
config: AppConfig,
|
config: AppConfig,
|
||||||
auth: WebAuthSecrets,
|
auth: WebAuthSecrets,
|
||||||
legacyAdminToken: string,
|
legacyAdminToken: string,
|
||||||
@@ -56,8 +54,8 @@ export function createHandler(
|
|||||||
const apiKey = apiKeyAuthorized(request, auth) || request.headers.get("authorization") === `Bearer ${legacyAdminToken}`;
|
const apiKey = apiKeyAuthorized(request, auth) || request.headers.get("authorization") === `Bearer ${legacyAdminToken}`;
|
||||||
try {
|
try {
|
||||||
if (request.method === "GET" && url.pathname === "/health") {
|
if (request.method === "GET" && url.pathname === "/health") {
|
||||||
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 });
|
return json({ ok: true, service: "apistate-api", scoreStatus: scores.status, refreshedAt: scores.refreshedAt });
|
||||||
}
|
}
|
||||||
if (request.method === "POST" && url.pathname === "/api/login") {
|
if (request.method === "POST" && url.pathname === "/api/login") {
|
||||||
const input = await body(request);
|
const input = await body(request);
|
||||||
@@ -78,41 +76,41 @@ export function createHandler(
|
|||||||
|
|
||||||
if (url.pathname.startsWith("/api/") && !session && !apiKey) return json({ ok: false, error: "unauthorized" }, 401);
|
if (url.pathname.startsWith("/api/") && !session && !apiKey) return json({ ok: false, error: "unauthorized" }, 401);
|
||||||
if (request.method === "GET" && url.pathname === "/api/status") {
|
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 });
|
return json({ ok: true, service: "apistate", scoreStatus: scores.status, refreshedAt: scores.refreshedAt, nextRefreshAt: scores.nextRefreshAt });
|
||||||
}
|
}
|
||||||
if (request.method === "GET" && url.pathname === "/api/scores") {
|
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 });
|
return json({ ...state, snapshotOk: state.ok, ok: true });
|
||||||
}
|
}
|
||||||
if (request.method === "POST" && url.pathname === "/api/scores/refresh") {
|
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 });
|
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/ranking") return json({ ok: true, ranking: await dispatcher.dispatch({ kind: "ranking.get" }) });
|
||||||
if (request.method === "GET" && url.pathname === "/api/lottery") return json(await service.publicState());
|
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 service.publicDraw() });
|
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 (!url.pathname.startsWith("/api/admin/")) return json({ ok: false, error: "not found" }, 404);
|
||||||
if (!apiKey) return json({ ok: false, error: "unauthorized" }, 401);
|
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/status") return json(await dispatcher.dispatch({ kind: "lottery.status" }));
|
||||||
if (request.method === "GET" && url.pathname === "/api/admin/backend-check") return json(await service.status(true));
|
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({ ok: true, record: await service.draw() });
|
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") {
|
if (request.method === "POST" && url.pathname === "/api/admin/reset") {
|
||||||
const input = await body(request);
|
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);
|
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") {
|
if (request.method === "GET" && url.pathname === "/api/admin/records") {
|
||||||
const limit = Number(url.searchParams.get("limit"));
|
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);
|
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(service.deleteRecord(decodeURIComponent(url.pathname.slice("/api/admin/records/".length))));
|
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/credit-test") {
|
if (request.method === "POST" && url.pathname === "/api/admin/credit-test") {
|
||||||
const input = await body(request);
|
const input = await body(request);
|
||||||
if (typeof input.execute !== "boolean") return json({ ok: false, error: "execute must be boolean" }, 400);
|
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);
|
return json({ ok: false, error: "not found" }, 404);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
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);
|
||||||
|
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
-34
@@ -1,35 +1,3 @@
|
|||||||
import { loadConfig } from "./config";
|
await import("./api");
|
||||||
import { createServerContext } from "./bootstrap";
|
|
||||||
import { createHandler } from "./http";
|
|
||||||
|
|
||||||
function option(name: string): string {
|
export {};
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,60 @@
|
|||||||
|
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 operation: OperationRequest = { operationId: randomUUID(), command };
|
||||||
|
return await this.client.workflow.execute("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,
|
||||||
|
}],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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",
|
"module": "ESNext",
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "Bundler",
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"types": ["bun"]
|
"types": ["bun"]
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user