initialised trigger.dev for migrations

This commit is contained in:
John Yeo
2026-05-07 13:23:43 +08:00
parent 362b81d29b
commit f6f55c002c
70 changed files with 2207 additions and 581 deletions

2
.gitignore vendored
View File

@@ -145,3 +145,5 @@ TAKEHOME.md
AGENTS.md
server/.turbo
.trigger

241
bun.lock
View File

@@ -8,6 +8,7 @@
"@aws-sdk/client-sqs": "^3.985.0",
"@better-auth/core": "catalog:",
"@better-auth/oauth-provider": "catalog:",
"@trigger.dev/sdk": "4.4.5",
"@wooorm/starry-night": "^3.8.0",
"ag-charts-react": "^12.3.0",
"better-auth": "catalog:",
@@ -19,6 +20,7 @@
"devDependencies": {
"@better-auth/cli": "^1.4.21",
"@biomejs/biome": "^2.2.7",
"@trigger.dev/build": "4.4.5",
"@types/node": "^24.9.1",
"concurrently": "^9.2.1",
"dotenv": "^16.6.1",
@@ -526,6 +528,7 @@
"date-fns": "^3.6.0",
"decimal.js": "^10.5.0",
"input-otp": "^1.4.2",
"json5": "^2.2.3",
"lodash": "^4.17.21",
"lucide-react": "^0.562.0",
"motion": "^12.26.1",
@@ -952,6 +955,8 @@
"@bufbuild/protobuf": ["@bufbuild/protobuf@2.12.0", "", {}, "sha512-B/XlCaFIP8LOwzo+bz5uFzATYokcwCKQcghqnlfwSmM5eX/qTkvDBnDPs+gXtX/RyjxJ4DRikECcPJbyALA8FA=="],
"@bugsnag/cuid": ["@bugsnag/cuid@3.2.2", "", {}, "sha512-7onuYLTMqMmHE9BBPG0YER4nFsU1rB+me1/YIeMusqcLbVbKKuG9u9+BDVDpje5e0llkkrVNOKYwmzM9DRIo7A=="],
"@canvas/image-data": ["@canvas/image-data@1.1.0", "", {}, "sha512-QdObRRjRbcXGmM1tmJ+MrHcaz1MftF2+W7YI+MsphnsCrmtyfS0d5qJbk0MeSbUeyM/jCb0hmnkXPsy026L7dA=="],
"@cfworker/json-schema": ["@cfworker/json-schema@4.1.1", "", {}, "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og=="],
@@ -994,6 +999,8 @@
"@ecies/ciphers": ["@ecies/ciphers@0.2.6", "", { "peerDependencies": { "@noble/ciphers": "^1.0.0" } }, "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g=="],
"@electric-sql/client": ["@electric-sql/client@1.0.14", "", { "dependencies": { "@microsoft/fetch-event-source": "^2.0.1" }, "optionalDependencies": { "@rollup/rollup-darwin-arm64": "^4.18.1" } }, "sha512-LtPAfeMxXRiYS0hyDQ5hue2PjljUiK9stvzsVyVb4nwxWQxfOWTSF42bHTs/o5i3x1T4kAQ7mwHpxa4A+f8X7Q=="],
"@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
"@emnapi/runtime": ["@emnapi/runtime@1.9.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw=="],
@@ -1088,6 +1095,8 @@
"@fortawesome/react-fontawesome": ["@fortawesome/react-fontawesome@0.2.6", "", { "dependencies": { "prop-types": "^15.8.1" }, "peerDependencies": { "@fortawesome/fontawesome-svg-core": "~1 || ~6 || ~7", "react": "^16.3 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-mtBFIi1UsYQo7rYonYFkjgYKGoL8T+fEH6NGUpvuqtY3ytMsAoDaPo5rk25KuMtKDipY4bGYM/CkmCHA1N3FUg=="],
"@google-cloud/precise-date": ["@google-cloud/precise-date@4.0.0", "", {}, "sha512-1TUx3KdaU3cN7nfCdNf+UVqA/PSX29Cjcox3fZZBtINlRrXVTmUkQnCKv2MbBUbCopbK4olAT1IHl76uZyCiVA=="],
"@google/genai": ["@google/genai@1.50.1", "", { "dependencies": { "google-auth-library": "^10.3.0", "p-retry": "^4.6.2", "protobufjs": "^7.5.4", "ws": "^8.18.0" }, "peerDependencies": { "@modelcontextprotocol/sdk": "^1.25.2" }, "optionalPeers": ["@modelcontextprotocol/sdk"] }, "sha512-YbkX7H9+1Pt8wOt7DDREy8XSoiL6fRDzZQRyaVBarFf8MR3zHGqVdvM4cLbDXqPhxqvegZShgfxb8kw9C7YhAQ=="],
"@grpc/grpc-js": ["@grpc/grpc-js@1.14.3", "", { "dependencies": { "@grpc/proto-loader": "^0.8.0", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA=="],
@@ -1238,6 +1247,8 @@
"@jsep-plugin/ternary": ["@jsep-plugin/ternary@1.1.4", "", { "peerDependencies": { "jsep": "^0.4.0||^1.0.0" } }, "sha512-ck5wiqIbqdMX6WRQztBL7ASDty9YLgJ3sSAK5ZpBzXeySvFGCzIvM6UiAI4hTZ22fEcYQVV/zhUbNscggW+Ukg=="],
"@jsonhero/path": ["@jsonhero/path@1.0.21", "", {}, "sha512-gVUDj/92acpVoJwsVJ/RuWOaHyG4oFzn898WNGQItLCTQ+hOaVlEaImhwE1WqOTf+l3dGOUkbSiVKlb3q1hd1Q=="],
"@keyv/serialize": ["@keyv/serialize@1.1.1", "", {}, "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA=="],
"@kubiks/otel-drizzle": ["@kubiks/otel-drizzle@2.1.0", "", { "peerDependencies": { "@opentelemetry/api": ">=1.9.0 <2.0.0", "drizzle-orm": ">=0.28.0" } }, "sha512-9UHb0od3jwa6zTWMyEYPIZcUq5PDaziCmQLMLakSK2zeqy12SFZ3SAGWXJTgEr8valn/Wa+DKVs+Z3aqKQUpvg=="],
@@ -1260,6 +1271,8 @@
"@mermaid-js/parser": ["@mermaid-js/parser@1.1.0", "", { "dependencies": { "langium": "^4.0.0" } }, "sha512-gxK9ZX2+Fex5zu8LhRQoMeMPEHbc73UKZ0FQ54YrQtUxE1VVhMwzeNtKRPAu5aXks4FasbMe4xB4bWrmq6Jlxw=="],
"@microsoft/fetch-event-source": ["@microsoft/fetch-event-source@2.0.1", "", {}, "sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA=="],
"@mintlify/cli": ["@mintlify/cli@4.0.1134", "", { "dependencies": { "@inquirer/prompts": "7.9.0", "@mintlify/common": "1.0.865", "@mintlify/link-rot": "3.0.1043", "@mintlify/prebuild": "1.0.1008", "@mintlify/previewing": "4.0.1069", "@mintlify/validation": "0.1.676", "adm-zip": "0.5.16", "chalk": "5.2.0", "color": "4.2.3", "detect-port": "1.5.1", "front-matter": "4.0.2", "fs-extra": "11.2.0", "ink": "6.3.0", "inquirer": "12.3.0", "js-yaml": "4.1.0", "mdast-util-mdx-jsx": "3.2.0", "open": "^8.4.2", "openid-client": "^6.8.2", "posthog-node": "5.17.2", "react": "19.2.3", "semver": "7.7.2", "unist-util-visit": "5.0.0", "yargs": "17.7.1", "zod": "^4.3.6" }, "optionalDependencies": { "keytar": "^7.9.0" }, "bin": { "mint": "bin/index.js", "mintlify": "bin/index.js" } }, "sha512-BTDUtv15RiqyN9ci/4zPKKpYWdnSvR57WAAlWXxCBCk2nNMYMKBTSVCmmJ91CriYOyUrXrKXrAI1UMiWBrWvzQ=="],
"@mintlify/common": ["@mintlify/common@1.0.865", "", { "dependencies": { "@asyncapi/parser": "3.4.0", "@asyncapi/specs": "6.8.1", "@mintlify/mdx": "^3.0.4", "@mintlify/models": "0.0.296", "@mintlify/openapi-parser": "^0.0.8", "@mintlify/validation": "0.1.676", "@sindresorhus/slugify": "2.2.0", "@types/mdast": "4.0.4", "acorn": "8.11.2", "acorn-jsx": "5.3.2", "color-blend": "4.0.0", "estree-util-to-js": "2.0.0", "estree-walker": "3.0.3", "front-matter": "4.0.2", "hast-util-from-html": "2.0.3", "hast-util-to-html": "9.0.4", "hast-util-to-text": "4.0.2", "hex-rgb": "5.0.0", "ignore": "7.0.5", "js-yaml": "4.1.0", "lodash": "4.18.1", "mdast-util-from-markdown": "2.0.2", "mdast-util-gfm": "3.0.0", "mdast-util-mdx": "3.0.0", "mdast-util-mdx-jsx": "3.1.3", "micromark-extension-gfm": "3.0.0", "micromark-extension-mdx-jsx": "3.0.1", "micromark-extension-mdxjs": "3.0.0", "openapi-types": "12.1.3", "postcss": "8.5.6", "rehype-stringify": "10.0.1", "remark": "15.0.1", "remark-frontmatter": "5.0.0", "remark-gfm": "4.0.0", "remark-math": "6.0.0", "remark-mdx": "3.1.0", "remark-parse": "11.0.0", "remark-rehype": "11.1.1", "remark-stringify": "11.0.0", "sucrase": "^3.34.0", "tailwindcss": "^3.4.17", "unified": "11.0.5", "unist-builder": "4.0.0", "unist-util-map": "4.0.0", "unist-util-remove": "4.0.0", "unist-util-remove-position": "5.0.0", "unist-util-visit": "5.0.0", "unist-util-visit-parents": "6.0.1", "vfile": "6.0.3", "xss": "1.0.15" } }, "sha512-p+mDIOwdtSGhgiRvr3mVNBT/PeXB3x2klkmX10SmRdePoyKheDtCqwao67f+4Av2bOeCUX3nti8Ccz6XTW/4BQ=="],
@@ -1390,6 +1403,8 @@
"@opentelemetry/exporter-zipkin": ["@opentelemetry/exporter-zipkin@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-trace-base": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-a9eeyHIipfdxzCfc2XPrE+/TI3wmrZUDFtG2RRXHSbZZULAny7SyybSvaDvS77a7iib5MPiAvluwVvbGTsHxsw=="],
"@opentelemetry/host-metrics": ["@opentelemetry/host-metrics@0.37.0", "", { "dependencies": { "systeminformation": "5.23.8" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-gf6nRFci0PTni9R1QQKjZ2uZE4Y6olLKhlwdM0qqLbbn3SBVKyP2jyBMiosBTHtRNLjY7s8hzQ44eLdK5wkGNQ=="],
"@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.202.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.202.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Uz3BxZWPgDwgHM2+vCKEQRh0R8WKrd/q6Tus1vThRClhlPO39Dyz7mDrOr6KuqGXAlBQ1e5Tnymzri4RMZNaWA=="],
"@opentelemetry/instrumentation-amqplib": ["@opentelemetry/instrumentation-amqplib@0.58.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/instrumentation": "^0.211.0", "@opentelemetry/semantic-conventions": "^1.33.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-fjpQtH18J6GxzUZ+cwNhWUpb71u+DzT7rFkg5pLssDGaEber91Y2WNGdpVpwGivfEluMlNMZumzjEqfg8DeKXQ=="],
@@ -1606,8 +1621,12 @@
"@prisma/client": ["@prisma/client@5.22.0", "", { "peerDependencies": { "prisma": "*" }, "optionalPeers": ["prisma"] }, "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA=="],
"@prisma/config": ["@prisma/config@6.19.3", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.21.0", "empathic": "2.0.0" } }, "sha512-CBPT44BjlQxEt8kiMEauji2WHTDoVBOKl7UlewXmUgBPnr/oPRZC3psci5chJnYmH0ivEIog2OU9PGWoki3DLQ=="],
"@prisma/instrumentation": ["@prisma/instrumentation@7.2.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.207.0" }, "peerDependencies": { "@opentelemetry/api": "^1.8" } }, "sha512-Rh9Z4x5kEj1OdARd7U18AtVrnL6rmLSI0qYShaB4W7Wx5BKbgzndWF+QnuzMb7GLfVdlT5aYCXoPQVYuYtVu0g=="],
"@protobuf-ts/runtime": ["@protobuf-ts/runtime@2.11.1", "", {}, "sha512-KuDaT1IfHkugM2pyz+FwiY80ejWrkH1pAtOBOZFuR6SXEFTsnb/jiQWQ1rCIrcKx2BtyxnxW6BWwsVSA/Ie+WQ=="],
"@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=="],
@@ -1850,6 +1869,8 @@
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
"@s2-dev/streamstore": ["@s2-dev/streamstore@0.22.5", "", { "dependencies": { "@protobuf-ts/runtime": "^2.11.1", "debug": "^4.4.3" } }, "sha512-GqdOKIbIoIxT+40fnKzHbrsHB6gBqKdECmFe7D3Ojk4FoN1Hu0LhFzZv6ZmVMjoHHU+55debS1xSWjZwQmbIyQ=="],
"@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="],
"@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="],
@@ -2162,6 +2183,12 @@
"@tootallnate/quickjs-emscripten": ["@tootallnate/quickjs-emscripten@0.23.0", "", {}, "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA=="],
"@trigger.dev/build": ["@trigger.dev/build@4.4.5", "", { "dependencies": { "@prisma/config": "^6.10.0", "@trigger.dev/core": "4.4.5", "mlly": "^1.7.1", "pkg-types": "^1.1.3", "resolve": "^1.22.8", "tinyglobby": "^0.2.2", "tsconfck": "3.1.3" } }, "sha512-45l8cv59JERkDrC5tADkKVDJriZjPtCtkAHVWvCZXRJMlm93Ao8deTHdtOf++TqdsqdG9Tk5cxhupY1COq4muA=="],
"@trigger.dev/core": ["@trigger.dev/core@4.4.5", "", { "dependencies": { "@bugsnag/cuid": "^3.1.1", "@electric-sql/client": "1.0.14", "@google-cloud/precise-date": "^4.0.0", "@jsonhero/path": "^1.0.21", "@opentelemetry/api": "1.9.0", "@opentelemetry/api-logs": "0.203.0", "@opentelemetry/core": "2.0.1", "@opentelemetry/exporter-logs-otlp-http": "0.203.0", "@opentelemetry/exporter-metrics-otlp-http": "0.203.0", "@opentelemetry/exporter-trace-otlp-http": "0.203.0", "@opentelemetry/host-metrics": "^0.37.0", "@opentelemetry/instrumentation": "0.203.0", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-logs": "0.203.0", "@opentelemetry/sdk-metrics": "2.0.1", "@opentelemetry/sdk-trace-base": "2.0.1", "@opentelemetry/sdk-trace-node": "2.0.1", "@opentelemetry/semantic-conventions": "1.36.0", "@s2-dev/streamstore": "0.22.5", "dequal": "^2.0.3", "eventsource": "^3.0.5", "eventsource-parser": "^3.0.0", "execa": "^8.0.1", "humanize-duration": "^3.27.3", "jose": "^5.4.0", "nanoid": "3.3.8", "prom-client": "^15.1.0", "socket.io": "4.7.4", "socket.io-client": "4.7.5", "std-env": "^3.8.1", "tinyexec": "^0.3.2", "uncrypto": "^0.1.3", "zod": "3.25.76", "zod-error": "1.5.0", "zod-validation-error": "^1.5.0" } }, "sha512-VI4AjX5ivbm+E8RMdP2kjuo/ph8cTCQTNkZSx6UknnAxyu2mihqPbcYWl2tqZCGbbX/HIlzhEtM2NM3a01Juxg=="],
"@trigger.dev/sdk": ["@trigger.dev/sdk@4.4.5", "", { "dependencies": { "@opentelemetry/api": "1.9.0", "@opentelemetry/semantic-conventions": "1.36.0", "@trigger.dev/core": "4.4.5", "chalk": "^5.2.0", "cronstrue": "^2.21.0", "debug": "^4.3.4", "evt": "^2.4.13", "slug": "^6.0.0", "ulid": "^2.3.0", "uncrypto": "^0.1.3", "ws": "^8.11.0" }, "peerDependencies": { "ai": "^4.2.0 || ^5.0.0 || ^6.0.0", "zod": "^3.0.0 || ^4.0.0" }, "optionalPeers": ["ai"] }, "sha512-iXjEWQQy+Dc7WDdjOtGLTlHIcVdcg00GUYL/hTr5FwMlY/Aswvfe0Snfn+7XECtls2tgZTPJ+rnTP4mo77o5eg=="],
"@ts-morph/common": ["@ts-morph/common@0.27.0", "", { "dependencies": { "fast-glob": "^3.3.3", "minimatch": "^10.0.1", "path-browserify": "^1.0.1" } }, "sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ=="],
"@tsconfig/node10": ["@tsconfig/node10@1.0.12", "", {}, "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ=="],
@@ -2210,6 +2237,8 @@
"@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="],
"@types/cookie": ["@types/cookie@0.4.1", "", {}, "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="],
"@types/cookiejar": ["@types/cookiejar@2.1.5", "", {}, "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q=="],
"@types/cors": ["@types/cors@2.8.19", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="],
@@ -2874,6 +2903,8 @@
"ci-parallel-vars": ["ci-parallel-vars@1.0.1", "", {}, "sha512-uvzpYrpmidaoxvIQHM+rKSrigjOe9feHYbw4uOI2gdfe1C3xIlxO+kVXq83WQWNniTf8bAxVpy+cQeFQsMERKg=="],
"citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="],
"cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="],
"class-variance-authority": ["class-variance-authority@0.7.1", "", { "dependencies": { "clsx": "^2.1.1" } }, "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg=="],
@@ -2992,6 +3023,8 @@
"cron-parser": ["cron-parser@4.9.0", "", { "dependencies": { "luxon": "^3.2.1" } }, "sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q=="],
"cronstrue": ["cronstrue@2.61.0", "", { "bin": { "cronstrue": "bin/cli.js" } }, "sha512-ootN5bvXbIQI9rW94+QsXN5eROtXWwew6NkdGxIRpS/UFWRggL0G5Al7a9GTBFEsuvVhJ2K3CntIIVt7L2ILhA=="],
"cross-env": ["cross-env@7.0.3", "", { "dependencies": { "cross-spawn": "^7.0.1" }, "bin": { "cross-env": "src/bin/cross-env.js", "cross-env-shell": "src/bin/cross-env-shell.js" } }, "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw=="],
"cross-fetch": ["cross-fetch@3.2.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q=="],
@@ -3146,6 +3179,8 @@
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
"deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="],
"default-browser": ["default-browser@5.5.0", "", { "dependencies": { "bundle-name": "^4.1.0", "default-browser-id": "^5.0.0" } }, "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw=="],
"default-browser-id": ["default-browser-id@5.0.1", "", {}, "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q=="],
@@ -3262,6 +3297,8 @@
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
"effect": ["effect@3.21.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ=="],
"ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
"electron-to-chromium": ["electron-to-chromium@1.5.344", "", {}, "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg=="],
@@ -3270,6 +3307,8 @@
"emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
"empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="],
"encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
"encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="],
@@ -3278,7 +3317,7 @@
"engine.io": ["engine.io@6.6.7", "", { "dependencies": { "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "@types/ws": "^8.5.12", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.18.3" } }, "sha512-DgOngfDKM2EviOH3Mr9m7ks1q8roetLy/IMmYthAYzbpInMbYc/GS+fWFA3rl1gvwKVsQrVV61fo5emD1y3OJQ=="],
"engine.io-client": ["engine.io-client@6.6.4", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.18.3", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw=="],
"engine.io-client": ["engine.io-client@6.5.4", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", "ws": "~8.17.1", "xmlhttprequest-ssl": "~2.0.0" } }, "sha512-GeZeeRjpD2qf49cZQ0Wvh/8NJNfeXkXXcoGh+F77oEAgo9gUHwT1fCRxSNU+YEEaysOJTnsFHmM5oAcPy4ntvQ=="],
"engine.io-parser": ["engine.io-parser@5.2.3", "", {}, "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q=="],
@@ -3436,6 +3475,8 @@
"eventsource-parser": ["eventsource-parser@3.0.8", "", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="],
"evt": ["evt@2.5.9", "", { "dependencies": { "minimal-polyfills": "^2.2.3", "run-exclusive": "^2.2.19", "tsafe": "^1.8.5" } }, "sha512-GpjX476FSlttEGWHT8BdVMoI8wGXQGbEOtKcP4E+kggg+yJzXBZN2n4x7TS/zPBJ1DZqWI+rguZZApjjzQ0HpA=="],
"execa": ["execa@9.6.1", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA=="],
"expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
@@ -3454,6 +3495,8 @@
"extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="],
"fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="],
"fast-copy": ["fast-copy@4.0.3", "", {}, "sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw=="],
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
@@ -3778,6 +3821,8 @@
"human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="],
"humanize-duration": ["humanize-duration@3.33.2", "", {}, "sha512-K7Ny/ULO1hDm2nnhvAY+SJV1skxFb61fd073SG1IWJl+D44ULrruCuTyjHKjBVVcSuTlnY99DKtgEG39CM5QOQ=="],
"humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="],
"husky": ["husky@9.1.7", "", { "bin": { "husky": "bin.js" } }, "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA=="],
@@ -4396,6 +4441,8 @@
"min-indent": ["min-indent@1.0.1", "", {}, "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg=="],
"minimal-polyfills": ["minimal-polyfills@2.2.3", "", {}, "sha512-oxdmJ9cL+xV72h0xYxp4tP2d5/fTBpP45H8DIOn9pASuF8a3IYTf+25fMGDYGiWW+MFsuog6KD6nfmhZJQ+uUw=="],
"minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="],
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
@@ -4500,6 +4547,8 @@
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
"node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],
"node-releases": ["node-releases@2.0.38", "", {}, "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw=="],
@@ -4524,6 +4573,8 @@
"nuqs": ["nuqs@2.8.9", "", { "dependencies": { "@standard-schema/spec": "1.0.0" }, "peerDependencies": { "@remix-run/react": ">=2", "@tanstack/react-router": "^1", "next": ">=14.2.0", "react": ">=18.2.0 || ^19.0.0-0", "react-router": "^5 || ^6 || ^7", "react-router-dom": "^5 || ^6 || ^7" }, "optionalPeers": ["@remix-run/react", "@tanstack/react-router", "next", "react-router", "react-router-dom"] }, "sha512-8ou6AEwsxMWSYo2qkfZtYFVzngwbKmg4c00HVxC1fF6CEJv3Fwm6eoZmfVPALB+vw8Udo7KL5uy96PFcYe1BIQ=="],
"nypm": ["nypm@0.6.6", "", { "dependencies": { "citty": "^0.2.2", "pathe": "^2.0.3", "tinyexec": "^1.1.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-vRyr0r4cbBapw07Xw8xrj9Teq3o7MUD35rSaTcanDbW+aK2XHDgJFiU6ZTj2GBw7Q12ysdsyFss+Vdz4hQ0Y6Q=="],
"oauth4webapi": ["oauth4webapi@3.8.5", "", {}, "sha512-A8jmyUckVhRJj5lspguklcl90Ydqk61H3dcU0oLhH3Yv13KpAliKTt5hknpGGPZSSfOwGyraNEFmofDYH+1kSg=="],
"obj-props": ["obj-props@1.4.0", "", {}, "sha512-p7p/7ltzPDiBs6DqxOrIbtRdwxxVRBj5ROukeNb9RgA+fawhrz5n2hpNz8DDmYR//tviJSj7nUnlppGmONkjiQ=="],
@@ -4556,7 +4607,7 @@
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"onetime": ["onetime@6.0.0", "", { "dependencies": { "mimic-fn": "^4.0.0" } }, "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ=="],
"oniguruma-parser": ["oniguruma-parser@0.12.2", "", {}, "sha512-6HVa5oIrgMC6aA6WF6XyyqbhRPJrKR02L20+2+zpDtO5QAzGHAUGw5TKQvwi5vctNnRHkJYmjAhRVQF2EKdTQw=="],
@@ -4712,7 +4763,7 @@
"pkg-dir": ["pkg-dir@5.0.0", "", { "dependencies": { "find-up": "^5.0.0" } }, "sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA=="],
"pkg-types": ["pkg-types@2.3.1", "", { "dependencies": { "confbox": "^0.2.4", "exsolve": "^1.0.8", "pathe": "^2.0.3" } }, "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg=="],
"pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
"playwright": ["playwright@1.59.1", "", { "dependencies": { "playwright-core": "1.59.1" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw=="],
@@ -4820,6 +4871,8 @@
"puppeteer-core": ["puppeteer-core@24.42.0", "", { "dependencies": { "@puppeteer/browsers": "2.13.0", "chromium-bidi": "14.0.0", "debug": "^4.4.3", "devtools-protocol": "0.0.1595872", "typed-query-selector": "^2.12.1", "webdriver-bidi-protocol": "0.4.1", "ws": "^8.19.0" } }, "sha512-T4zXokk/izH01fYPhyyev1A4piWiOKrYq7CUFpdoYQxmOnXoV6YjUabmfIjCYkNspSoAXIxRid3Tw+Vg0fthYg=="],
"pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
"qs": ["qs@6.15.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg=="],
"query-selector-shadow-dom": ["query-selector-shadow-dom@1.0.1", "", {}, "sha512-lT5yCqEBgfoMYpf3F2xQRK7zEr1rhIIZuceDK6+xRkJQ4NMbHTwXqk4NkwDwQMNqXgG9r9fyHnzwNVs6zV5KRw=="],
@@ -4994,7 +5047,7 @@
"resend": ["resend@4.8.0", "", { "dependencies": { "@react-email/render": "1.1.2" } }, "sha512-R8eBOFQDO6dzRTDmaMEdpqrkmgSjPpVXt4nGfWsZdYOet0kqra0xgbvTES6HmCriZEXbmGk3e0DiGIaLFTFSHA=="],
"resolve": ["resolve@2.0.0-next.6", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA=="],
"resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="],
"resolve-alpn": ["resolve-alpn@1.2.1", "", {}, "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g=="],
@@ -5044,6 +5097,8 @@
"run-async": ["run-async@4.0.6", "", {}, "sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ=="],
"run-exclusive": ["run-exclusive@2.2.19", "", { "dependencies": { "minimal-polyfills": "^2.2.3" } }, "sha512-K3mdoAi7tjJ/qT7Flj90L7QyPozwUaAG+CVhkdDje4HLKXUYC3N/Jzkau3flHVDLQVhiHBtcimVodMjN9egYbA=="],
"run-jxa": ["run-jxa@3.0.0", "", { "dependencies": { "execa": "^5.1.1", "macos-version": "^6.0.0", "subsume": "^4.0.0", "type-fest": "^2.0.0" } }, "sha512-4f2CrY7H+sXkKXJn/cE6qRA3z+NMVO7zvlZ/nUV0e62yWftpiLAfw5eV9ZdomzWd2TXWwEIiGjAT57+lWIzzvA=="],
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
@@ -5142,6 +5197,8 @@
"slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="],
"slug": ["slug@6.1.0", "", {}, "sha512-x6vLHCMasg4DR2LPiyFGI0gJJhywY6DTiGhCrOMzb3SOk/0JVLIaL4UhyFSHu04SD3uAavrKY/K3zZ3i6iRcgA=="],
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
"smol-toml": ["smol-toml@1.6.1", "", {}, "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg=="],
@@ -5150,7 +5207,7 @@
"socket.io-adapter": ["socket.io-adapter@2.5.6", "", { "dependencies": { "debug": "~4.4.1", "ws": "~8.18.3" } }, "sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ=="],
"socket.io-client": ["socket.io-client@4.8.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g=="],
"socket.io-client": ["socket.io-client@4.7.5", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.3.2", "engine.io-client": "~6.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ=="],
"socket.io-parser": ["socket.io-parser@4.2.6", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1" } }, "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg=="],
@@ -5202,6 +5259,8 @@
"statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
"std-env": ["std-env@3.10.0", "", {}, "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg=="],
"stdin-discarder": ["stdin-discarder@0.2.2", "", {}, "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ=="],
"stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="],
@@ -5292,6 +5351,8 @@
"system-architecture": ["system-architecture@0.1.0", "", {}, "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA=="],
"systeminformation": ["systeminformation@5.23.8", "", { "os": "!aix", "bin": { "systeminformation": "lib/cli.js" } }, "sha512-Osd24mNKe6jr/YoXLLK3k8TMdzaxDffhpCxgkfgBHcapykIkd50HXThM3TCEuHO2pPuCsSx2ms/SunqhU5MmsQ=="],
"tagged-tag": ["tagged-tag@1.0.0", "", {}, "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng=="],
"tailwind-merge": ["tailwind-merge@3.5.0", "", {}, "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A=="],
@@ -5436,9 +5497,11 @@
"ts-to-zod": ["ts-to-zod@5.1.0", "", { "dependencies": { "@clack/prompts": "1.0.0-alpha.4", "@oclif/core": "^4.5.4", "@typescript/vfs": "^1.5.0", "chokidar": "^4.0.3", "listr2": "^9.0.4", "slash": "^5.1.0", "text-case": "^1.2.4", "tslib": "^2.3.1", "tsutils": "^3.21.0", "typescript": "^5.2.2", "zod": "^4.1.5" }, "bin": { "ts-to-zod": "bin/run" } }, "sha512-giqqlvRHunlJqG9tBL/KAO3wWIVZGF//mZiWLKm/fdQnKnz4EN2mtiK5cugN9slytBkdMEXQIaLvMzIScbhhFw=="],
"tsafe": ["tsafe@1.8.12", "", {}, "sha512-nFRqW0ttu/2o6XTXsHiVZWJBCOaxhVqZLg7dgs3coZNsCMPXPfwz+zPHAQA+70fNnVJLAPg1EgGIqK9Q84tvAw=="],
"tsc-alias": ["tsc-alias@1.8.16", "", { "dependencies": { "chokidar": "^3.5.3", "commander": "^9.0.0", "get-tsconfig": "^4.10.0", "globby": "^11.0.4", "mylas": "^2.1.9", "normalize-path": "^3.0.0", "plimit-lit": "^1.2.6" }, "bin": { "tsc-alias": "dist/bin/index.js" } }, "sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g=="],
"tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
"tsconfck": ["tsconfck@3.1.3", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ulNZP1SVpRDesxeMLON/LtWM8HIgAJEIVpVVhBM6gsmvQ8+Rh+ZG7FWGvHh7Ah3pRABwVJWklWCr/BTZSv0xnQ=="],
"tsconfig-paths": ["tsconfig-paths@4.2.0", "", { "dependencies": { "json5": "^2.2.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg=="],
@@ -5486,6 +5549,8 @@
"uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="],
"ulid": ["ulid@2.4.0", "", { "bin": { "ulid": "bin/cli.js" } }, "sha512-fIRiVTJNcSRmXKPZtGzFQv9WRrZ3M9eoptl/teFJvjOzmpU+/K/JH6HZ8deBfb5vMEpicJcLn7JmvdknlMq7Zg=="],
"unbash": ["unbash@3.0.0", "", {}, "sha512-FeFPZ/WFT0mbRCuydiZzpPFlrYN8ZUpphQKoq4EeElVIYjYyGzPMxQR/simUwCOJIyVhpFk4RbtyO7RuMpMnHA=="],
"unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="],
@@ -5684,7 +5749,7 @@
"xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
"xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="],
"xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.0.0", "", {}, "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A=="],
"xo": ["xo@0.53.1", "", { "dependencies": { "@eslint/eslintrc": "^1.3.3", "@typescript-eslint/eslint-plugin": "^5.43.0", "@typescript-eslint/parser": "^5.43.0", "arrify": "^3.0.0", "cosmiconfig": "^7.1.0", "define-lazy-prop": "^3.0.0", "eslint": "^8.27.0", "eslint-config-prettier": "^8.5.0", "eslint-config-xo": "^0.43.1", "eslint-config-xo-typescript": "^0.55.0", "eslint-formatter-pretty": "^4.1.0", "eslint-import-resolver-webpack": "^0.13.2", "eslint-plugin-ava": "^13.2.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.26.0", "eslint-plugin-n": "^15.5.1", "eslint-plugin-no-use-extend-native": "^0.5.0", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-unicorn": "^44.0.2", "esm-utils": "^4.1.0", "find-cache-dir": "^4.0.0", "find-up": "^6.3.0", "get-stdin": "^9.0.0", "globby": "^13.1.2", "imurmurhash": "^0.1.4", "json-stable-stringify-without-jsonify": "^1.0.1", "json5": "^2.2.1", "lodash-es": "^4.17.21", "meow": "^11.0.0", "micromatch": "^4.0.5", "open-editor": "^4.0.0", "prettier": "^2.7.1", "semver": "^7.3.8", "slash": "^5.0.0", "to-absolute-glob": "^2.0.2", "typescript": "^4.9.3" }, "bin": { "xo": "cli.js" } }, "sha512-/2R8SPehv1UhiIqJ9uSvrAjslcoygICNsUlEb/Zf2V6rMtr7YCoggc6hlt6b/kbncpR989Roqt6AvEO779dFxw=="],
@@ -5724,11 +5789,13 @@
"zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"zod-error": ["zod-error@1.5.0", "", { "dependencies": { "zod": "^3.20.2" } }, "sha512-zzopKZ/skI9iXpqCEPj+iLCKl9b88E43ehcU+sbRoHuwGd9F1IDVGQ70TyO6kmfiRL1g4IXkjsXK+g1gLYl4WQ=="],
"zod-openapi": ["zod-openapi@5.4.6", "", { "peerDependencies": { "zod": "^3.25.74 || ^4.0.0" } }, "sha512-P2jsOOBAq/6hCwUsMCjUATZ8szkMsV5VAwZENfyxp2Hc/XPJQpVwAgevWZc65xZauCwWB9LAn7zYeiCJFAEL+A=="],
"zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="],
"zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
"zod-validation-error": ["zod-validation-error@1.5.0", "", { "peerDependencies": { "zod": "^3.18.0" } }, "sha512-/7eFkAI4qV0tcxMBB/3+d2c1P6jzzZYdYSlBuAklzMuCrJu5bzJfHS0yVAS87dRHVlhftd6RFJDIvv03JgkSbw=="],
"zustand": ["zustand@5.0.12", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g=="],
@@ -5748,6 +5815,8 @@
"@artilleryio/int-core/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="],
"@artilleryio/int-core/socket.io-client": ["socket.io-client@4.8.3", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-client": "~6.6.1", "socket.io-parser": "~4.2.4" } }, "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g=="],
"@artilleryio/int-core/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
"@asyncapi/parser/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=="],
@@ -6428,6 +6497,8 @@
"@posthog/ai/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"@prisma/config/c12": ["c12@3.1.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^16.6.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.4.2", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw=="],
"@prisma/instrumentation/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.207.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.207.0", "import-in-the-middle": "^2.0.0", "require-in-the-middle": "^8.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-y6eeli9+TLKnznrR8AZlQMSJT7wILpXH+6EYq5Vf/4Ao+huI7EedxQHwRgVUOMLFbe7VFDvHJrX9/f4lcwnJsA=="],
"@radix-ui/react-alert-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
@@ -6534,6 +6605,40 @@
"@tailwindcss/vite/tailwindcss": ["tailwindcss@4.2.1", "", {}, "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw=="],
"@trigger.dev/core/@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
"@trigger.dev/core/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.203.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-9B9RU0H7Ya1Dx/Rkyc4stuBZSGVQF27WigitInx2QQoj6KUpEFYPKoWjdFTunJYxmXmh17HeBvbMa1EhGyPmqQ=="],
"@trigger.dev/core/@opentelemetry/core": ["@opentelemetry/core@2.0.1", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-MaZk9SJIDgo1peKevlbhP6+IwIiNPNmswNL4AF0WaQJLbHXjr9SrZMgS12+iqr9ToV4ZVosCcc0f8Rg67LXjxw=="],
"@trigger.dev/core/@opentelemetry/exporter-logs-otlp-http": ["@opentelemetry/exporter-logs-otlp-http@0.203.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.203.0", "@opentelemetry/core": "2.0.1", "@opentelemetry/otlp-exporter-base": "0.203.0", "@opentelemetry/otlp-transformer": "0.203.0", "@opentelemetry/sdk-logs": "0.203.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-s0hys1ljqlMTbXx2XiplmMJg9wG570Z5lH7wMvrZX6lcODI56sG4HL03jklF63tBeyNwK2RV1/ntXGo3HgG4Qw=="],
"@trigger.dev/core/@opentelemetry/exporter-metrics-otlp-http": ["@opentelemetry/exporter-metrics-otlp-http@0.203.0", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/otlp-exporter-base": "0.203.0", "@opentelemetry/otlp-transformer": "0.203.0", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-metrics": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-HFSW10y8lY6BTZecGNpV3GpoSy7eaO0Z6GATwZasnT4bEsILp8UJXNG5OmEsz4SdwCSYvyCbTJdNbZP3/8LGCQ=="],
"@trigger.dev/core/@opentelemetry/exporter-trace-otlp-http": ["@opentelemetry/exporter-trace-otlp-http@0.203.0", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/otlp-exporter-base": "0.203.0", "@opentelemetry/otlp-transformer": "0.203.0", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-trace-base": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-ZDiaswNYo0yq/cy1bBLJFe691izEJ6IgNmkjm4C6kE9ub/OMQqDXORx2D2j8fzTBTxONyzusbaZlqtfmyqURPw=="],
"@trigger.dev/core/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.203.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.203.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-ke1qyM+3AK2zPuBPb6Hk/GCsc5ewbLvPNkEuELx/JmANeEp6ZjnZ+wypPAJSucTw0wvCGrUaibDSdcrGFoWxKQ=="],
"@trigger.dev/core/@opentelemetry/resources": ["@opentelemetry/resources@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw=="],
"@trigger.dev/core/@opentelemetry/sdk-logs": ["@opentelemetry/sdk-logs@0.203.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.203.0", "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.4.0 <1.10.0" } }, "sha512-vM2+rPq0Vi3nYA5akQD2f3QwossDnTDLvKbea6u/A2NZ3XDkPxMfo/PNrDoXhDUD/0pPo2CdH5ce/thn9K0kLw=="],
"@trigger.dev/core/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xYLlvk/xdScGx1aEqvxLwf6sXQLXCjk3/1SQT9X9AoN5rXRhkdvIFShuNNmtTEPRBqcsMbS4p/gJLNI2wXaDuQ=="],
"@trigger.dev/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.36.0", "", {}, "sha512-TtxJSRD8Ohxp6bKkhrm27JRHAxPczQA7idtcTOMYI+wQRRrfgqxHv1cFbCApcSnNjtXkmzFozn6jQtFrOmbjPQ=="],
"@trigger.dev/core/execa": ["execa@8.0.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^8.0.1", "human-signals": "^5.0.0", "is-stream": "^3.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^5.1.0", "onetime": "^6.0.0", "signal-exit": "^4.1.0", "strip-final-newline": "^3.0.0" } }, "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg=="],
"@trigger.dev/core/jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="],
"@trigger.dev/core/nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="],
"@trigger.dev/core/socket.io": ["socket.io@4.7.4", "", { "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", "engine.io": "~6.5.2", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-DcotgfP1Zg9iP/dH9zvAQcWrE0TtbMVwXmlV4T4mqsvY+gw+LqUGPfx2AoVyRk0FLME+GQhufDMyacFmw7ksqw=="],
"@trigger.dev/sdk/@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="],
"@trigger.dev/sdk/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.36.0", "", {}, "sha512-TtxJSRD8Ohxp6bKkhrm27JRHAxPczQA7idtcTOMYI+wQRRrfgqxHv1cFbCApcSnNjtXkmzFozn6jQtFrOmbjPQ=="],
"@ts-morph/common/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
"@types/body-parser/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
@@ -6712,6 +6817,8 @@
"c12/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
"c12/pkg-types": ["pkg-types@2.3.1", "", { "dependencies": { "confbox": "^0.2.4", "exsolve": "^1.0.8", "pathe": "^2.0.3" } }, "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg=="],
"cacheable-request/get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="],
"cacheable-request/mimic-response": ["mimic-response@4.0.0", "", {}, "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg=="],
@@ -6792,7 +6899,9 @@
"engine.io/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"engine.io-client/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"engine.io-client/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"engine.io-client/ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="],
"escodegen/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
@@ -6816,10 +6925,14 @@
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"eslint-import-resolver-node/resolve": ["resolve@2.0.0-next.6", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA=="],
"eslint-import-resolver-webpack/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"eslint-import-resolver-webpack/enhanced-resolve": ["enhanced-resolve@0.9.1", "", { "dependencies": { "graceful-fs": "^4.1.2", "memory-fs": "^0.2.0", "tapable": "^0.1.8" } }, "sha512-kxpoMgrdtkXZ5h0SeraBS1iRntpTpQ3R8ussdb38+UAFnMGX5DDyJXePm+OCHOcoXvHDw7mc2erbJBpDnl7TPw=="],
"eslint-import-resolver-webpack/resolve": ["resolve@2.0.0-next.6", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA=="],
"eslint-import-resolver-webpack/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="],
"eslint-module-utils/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
@@ -6844,10 +6957,10 @@
"eslint-plugin-n/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
"eslint-plugin-n/resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="],
"eslint-plugin-react/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
"eslint-plugin-react/resolve": ["resolve@2.0.0-next.6", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "node-exports-info": "^1.6.0", "object-keys": "^1.1.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA=="],
"eslint-plugin-react/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"eslint-plugin-unicorn/ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
@@ -6886,8 +6999,6 @@
"filing-cabinet/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
"filing-cabinet/resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="],
"finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
"find-cache-dir/pkg-dir": ["pkg-dir@7.0.0", "", { "dependencies": { "find-up": "^6.3.0" } }, "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA=="],
@@ -6988,8 +7099,6 @@
"mixpanel/https-proxy-agent": ["https-proxy-agent@5.0.0", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA=="],
"mlly/pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
"mocha/escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
"mocha/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
@@ -7034,7 +7143,9 @@
"nuqs/@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
"onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"nypm/citty": ["citty@0.2.2", "", {}, "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w=="],
"nypm/tinyexec": ["tinyexec@1.1.1", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="],
"open-editor/execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
@@ -7078,12 +7189,12 @@
"pkg-conf/find-up": ["find-up@6.3.0", "", { "dependencies": { "locate-path": "^7.1.0", "path-exists": "^5.0.0" } }, "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw=="],
"pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
"playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="],
"postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"postcss-import/resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="],
"postcss-load-config/lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
"postcss-nested/postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
@@ -7156,12 +7267,12 @@
"recast/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"require-in-the-middle/resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="],
"requirejs-config-file/stringify-object": ["stringify-object@3.3.0", "", { "dependencies": { "get-own-enumerable-property-symbols": "^3.0.0", "is-obj": "^1.0.1", "is-regexp": "^1.0.0" } }, "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw=="],
"resolve-import/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
"restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"rimraf/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
"router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="],
@@ -7212,6 +7323,8 @@
"socket.io-adapter/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"socket.io-client/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
@@ -7274,6 +7387,8 @@
"unplugin/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"vite-tsconfig-paths/tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
"webpack/eslint-scope": ["eslint-scope@5.1.1", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" } }, "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw=="],
"whatwg-encoding/iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
@@ -7406,6 +7521,8 @@
"@artilleryio/int-core/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
"@artilleryio/int-core/socket.io-client/engine.io-client": ["engine.io-client@6.6.4", "", { "dependencies": { "@socket.io/component-emitter": "~3.1.0", "debug": "~4.4.1", "engine.io-parser": "~5.2.1", "ws": "~8.18.3", "xmlhttprequest-ssl": "~2.1.1" } }, "sha512-+kjUJnZGwzewFDw951CDWcwj35vMNf2fcj7xQWOctq1F2i1jkDdVvdFG9kM/BEChymCH36KgjnW0NsL58JYRxw=="],
"@asyncapi/parser/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
"@asyncapi/parser/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
@@ -7596,6 +7713,8 @@
"@dotenvx/dotenvx/execa/npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
"@dotenvx/dotenvx/execa/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"@dotenvx/dotenvx/execa/strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
"@dotenvx/dotenvx/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="],
@@ -7698,8 +7817,6 @@
"@mintlify/common/tailwindcss/postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
"@mintlify/common/tailwindcss/resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="],
"@mintlify/common/unist-util-visit/unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="],
"@mintlify/link-rot/unist-util-visit/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
@@ -8060,6 +8177,14 @@
"@opentelemetry/sdk-trace-node/@opentelemetry/sdk-trace-base/@opentelemetry/resources": ["@opentelemetry/resources@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-dZOB3R6zvBwDKnHDTB4X1xtMArB/d324VsbiPkX/Yu0Q8T2xceRthoIVFhJdvgVM2QhGVUyX9tzwiNxGtoBJUw=="],
"@prisma/config/c12/giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="],
"@prisma/config/c12/perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
"@prisma/config/c12/pkg-types": ["pkg-types@2.3.1", "", { "dependencies": { "confbox": "^0.2.4", "exsolve": "^1.0.8", "pathe": "^2.0.3" } }, "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg=="],
"@prisma/config/c12/rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="],
"@prisma/instrumentation/@opentelemetry/instrumentation/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.207.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-lAb0jQRVyleQQGiuuvCOTDVspc14nx6XJjP4FspJ1sNARo3Regq4ZZbrc3rN4b1TYSuUCvgH+UXUPug4SLOqEQ=="],
"@prisma/instrumentation/@opentelemetry/instrumentation/import-in-the-middle": ["import-in-the-middle@2.0.6", "", { "dependencies": { "acorn": "^8.15.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw=="],
@@ -8112,6 +8237,40 @@
"@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw=="],
"@trigger.dev/core/@opentelemetry/api-logs/@opentelemetry/api": ["@opentelemetry/api@1.9.1", "", {}, "sha512-gLyJlPHPZYdAk1JENA9LeHejZe1Ti77/pTeFm/nMXmQH/HFZlcS/O2XJB+L8fkbrNSqhdtlvjBVjxwUYanNH5Q=="],
"@trigger.dev/core/@opentelemetry/core/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="],
"@trigger.dev/core/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.203.0", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/otlp-transformer": "0.203.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Wbxf7k+87KyvxFr5D7uOiSq/vHXWommvdnNE7vECO3tAhsA2GfOlpWINCMWUEPdHZ7tCXxw6Epp3vgx3jU7llQ=="],
"@trigger.dev/core/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.203.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.203.0", "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-logs": "0.203.0", "@opentelemetry/sdk-metrics": "2.0.1", "@opentelemetry/sdk-trace-base": "2.0.1", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Y8I6GgoCna0qDQ2W6GCRtaF24SnvqvA8OfeTi7fqigD23u8Jpb4R5KFv/pRvrlGagcCLICMIyh9wiejp4TXu/A=="],
"@trigger.dev/core/@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.203.0", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/otlp-transformer": "0.203.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Wbxf7k+87KyvxFr5D7uOiSq/vHXWommvdnNE7vECO3tAhsA2GfOlpWINCMWUEPdHZ7tCXxw6Epp3vgx3jU7llQ=="],
"@trigger.dev/core/@opentelemetry/exporter-metrics-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.203.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.203.0", "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-logs": "0.203.0", "@opentelemetry/sdk-metrics": "2.0.1", "@opentelemetry/sdk-trace-base": "2.0.1", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Y8I6GgoCna0qDQ2W6GCRtaF24SnvqvA8OfeTi7fqigD23u8Jpb4R5KFv/pRvrlGagcCLICMIyh9wiejp4TXu/A=="],
"@trigger.dev/core/@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-exporter-base": ["@opentelemetry/otlp-exporter-base@0.203.0", "", { "dependencies": { "@opentelemetry/core": "2.0.1", "@opentelemetry/otlp-transformer": "0.203.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Wbxf7k+87KyvxFr5D7uOiSq/vHXWommvdnNE7vECO3tAhsA2GfOlpWINCMWUEPdHZ7tCXxw6Epp3vgx3jU7llQ=="],
"@trigger.dev/core/@opentelemetry/exporter-trace-otlp-http/@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.203.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.203.0", "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-logs": "0.203.0", "@opentelemetry/sdk-metrics": "2.0.1", "@opentelemetry/sdk-trace-base": "2.0.1", "protobufjs": "^7.3.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-Y8I6GgoCna0qDQ2W6GCRtaF24SnvqvA8OfeTi7fqigD23u8Jpb4R5KFv/pRvrlGagcCLICMIyh9wiejp4TXu/A=="],
"@trigger.dev/core/@opentelemetry/resources/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="],
"@trigger.dev/core/@opentelemetry/sdk-trace-base/@opentelemetry/semantic-conventions": ["@opentelemetry/semantic-conventions@1.40.0", "", {}, "sha512-cifvXDhcqMwwTlTK04GBNeIe7yyo28Mfby85QXFe1Yk8nmi36Ab/5UQwptOx84SsoGNRg+EVSjwzfSZMy6pmlw=="],
"@trigger.dev/core/execa/get-stream": ["get-stream@8.0.1", "", {}, "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA=="],
"@trigger.dev/core/execa/human-signals": ["human-signals@5.0.0", "", {}, "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ=="],
"@trigger.dev/core/execa/npm-run-path": ["npm-run-path@5.3.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="],
"@trigger.dev/core/execa/signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
"@trigger.dev/core/execa/strip-final-newline": ["strip-final-newline@3.0.0", "", {}, "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw=="],
"@trigger.dev/core/socket.io/debug": ["debug@4.3.7", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ=="],
"@trigger.dev/core/socket.io/engine.io": ["engine.io@6.5.5", "", { "dependencies": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", "cookie": "~0.4.1", "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", "ws": "~8.17.1" } }, "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA=="],
"@ts-morph/common/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="],
"@types/body-parser/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
@@ -8386,6 +8545,8 @@
"eslint-config-next/eslint-plugin-react-hooks/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"eslint-config-next/eslint-plugin-react-hooks/zod-validation-error": ["zod-validation-error@4.0.2", "", { "peerDependencies": { "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ=="],
"eslint-formatter-pretty/ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
"eslint-formatter-pretty/chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
@@ -8546,8 +8707,6 @@
"mixpanel/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="],
"mlly/pkg-types/confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
"mocha/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"msw/@inquirer/confirm/@inquirer/core": ["@inquirer/core@11.1.9", "", { "dependencies": { "@inquirer/ansi": "^2.0.5", "@inquirer/figures": "^2.0.5", "@inquirer/type": "^4.0.5", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-BDE4fG22uYh1bGSifcj7JSx119TVYNViMhMu85usp4Fswrzh6M0DV3yld64jA98uOAa2GSQ4Bg4bZRm2d2cwSg=="],
@@ -8590,6 +8749,8 @@
"open-editor/execa/npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
"open-editor/execa/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"open-editor/execa/strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
"open-editor/open/define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="],
@@ -8696,8 +8857,6 @@
"read-pkg/normalize-package-data/hosted-git-info": ["hosted-git-info@2.8.9", "", {}, "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="],
"read-pkg/normalize-package-data/resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="],
"read-pkg/normalize-package-data/semver": ["semver@5.7.2", "", { "bin": { "semver": "bin/semver" } }, "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g=="],
"requirejs-config-file/stringify-object/is-obj": ["is-obj@1.0.1", "", {}, "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg=="],
@@ -8708,6 +8867,8 @@
"resolve-import/glob/path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
"restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"rimraf/glob/minimatch": ["minimatch@10.2.5", "", { "dependencies": { "brace-expansion": "^5.0.5" } }, "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg=="],
"rimraf/glob/path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
@@ -8720,6 +8881,8 @@
"run-jxa/execa/npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
"run-jxa/execa/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"run-jxa/execa/strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
"schema-utils/ajv/json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="],
@@ -8880,6 +9043,10 @@
"@artilleryio/int-core/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
"@artilleryio/int-core/socket.io-client/engine.io-client/ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="],
"@artilleryio/int-core/socket.io-client/engine.io-client/xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="],
"@autumn/server/ink/@alcalzone/ansi-tokenize/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="],
"@autumn/server/ink/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="],
@@ -8958,6 +9125,8 @@
"@aws-sdk/signature-v4/@smithy/signature-v4/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@1.1.0", "", { "dependencies": { "@smithy/is-array-buffer": "^1.1.0", "tslib": "^2.5.0" } }, "sha512-9m6NXE0ww+ra5HKHCHig20T+FAwxBAm7DIdwc/767uGWbRcY720ybgPacQNB96JMOI7xVr/CDa3oMzKmW4a+kw=="],
"@dotenvx/dotenvx/execa/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"@infisical/sdk/@aws-sdk/credential-providers/@aws-sdk/client-cognito-identity/@aws-sdk/core": ["@aws-sdk/core@3.598.0", "", { "dependencies": { "@smithy/core": "^2.2.1", "@smithy/protocol-http": "^4.0.1", "@smithy/signature-v4": "^3.1.0", "@smithy/smithy-client": "^3.1.2", "@smithy/types": "^3.1.0", "fast-xml-parser": "4.2.5", "tslib": "^2.6.2" } }, "sha512-HaSjt7puO5Cc7cOlrXFCW0rtA0BM9lvzjl56x0A20Pt+0wxXGeTOZZOkXQIepbrFkV2e/HYukuT9e99vXDm59g=="],
"@infisical/sdk/@aws-sdk/credential-providers/@aws-sdk/client-cognito-identity/@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.598.0", "", { "dependencies": { "@aws-sdk/types": "3.598.0", "@smithy/protocol-http": "^4.0.1", "@smithy/types": "^3.1.0", "tslib": "^2.6.2" } }, "sha512-WiaG059YBQwQraNejLIi0gMNkX7dfPZ8hDIhvMr5aVPRbaHH8AYF3iNSsXYCHvA2Cfa1O9haYXsuMF9flXnCmA=="],
@@ -9184,8 +9353,6 @@
"@mintlify/scraping/@mintlify/common/tailwindcss/postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
"@mintlify/scraping/@mintlify/common/tailwindcss/resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="],
"@mintlify/scraping/yargs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"@mintlify/scraping/yargs/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
@@ -9290,6 +9457,14 @@
"@tailwindcss/postcss/@tailwindcss/oxide/@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"@trigger.dev/core/execa/npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
"@trigger.dev/core/socket.io/engine.io/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
"@trigger.dev/core/socket.io/engine.io/cookie": ["cookie@0.4.2", "", {}, "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA=="],
"@trigger.dev/core/socket.io/engine.io/ws": ["ws@8.17.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ=="],
"@ts-morph/common/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"@typescript-eslint/typescript-estree/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
@@ -9458,6 +9633,10 @@
"nodemon/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
"open-editor/execa/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"ora/cli-cursor/restore-cursor/onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
"ora/log-symbols/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
"ora/log-symbols/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
@@ -9500,6 +9679,8 @@
"rimraf/glob/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="],
"run-jxa/execa/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"sdk-test/next/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"shadcn/cosmiconfig/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
@@ -9684,6 +9865,8 @@
"@mintlify/scraping/yargs/string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"@trigger.dev/core/socket.io/engine.io/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
"artillery-plugin-ensure/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
"artillery-plugin-publish-metrics/@opentelemetry/exporter-metrics-otlp-grpc/@opentelemetry/otlp-transformer/protobufjs/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
@@ -9726,6 +9909,8 @@
"meow/read-pkg-up/read-pkg/normalize-package-data/hosted-git-info": ["hosted-git-info@4.1.0", "", { "dependencies": { "lru-cache": "^6.0.0" } }, "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA=="],
"ora/cli-cursor/restore-cursor/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"ora/log-symbols/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
"ora/log-symbols/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],

View File

@@ -128,6 +128,7 @@
"@aws-sdk/client-sqs": "^3.985.0",
"@better-auth/core": "catalog:",
"@better-auth/oauth-provider": "catalog:",
"@trigger.dev/sdk": "4.4.5",
"@wooorm/starry-night": "^3.8.0",
"ag-charts-react": "^12.3.0",
"better-auth": "catalog:",
@@ -139,6 +140,7 @@
"devDependencies": {
"@better-auth/cli": "^1.4.21",
"@biomejs/biome": "^2.2.7",
"@trigger.dev/build": "4.4.5",
"@types/node": "^24.9.1",
"concurrently": "^9.2.1",
"dotenv": "^16.6.1",

View File

@@ -174,6 +174,14 @@ async function startDev() {
? `"cd server && bun ${workersScript}"`
: `"cd server && bun ${workersScript}"`,
);
names.push("trigger");
colors.push("cyan");
cmds.push(
isWindows
? `"bunx trigger.dev@latest dev"`
: `"bunx trigger.dev@latest dev"`,
);
}
names.push("vite", "checkout");

View File

@@ -2,7 +2,7 @@ import { AppEnv } from "@autumn/shared";
import {
buildCustomerCount,
buildCustomerSelect,
} from "@autumn/shared/api/migrations/compiler/buildCustomerQuery.js";
} from "@/internal/migrations/v2/filters/customers/buildCustomerSelect.js";
import type { CustomerFilter } from "@autumn/shared/api/migrations/filters/customerFilter.js";
import { sql, type SQL } from "drizzle-orm";
import { PgDialect } from "drizzle-orm/pg-core";

View File

@@ -47,9 +47,9 @@ import {
type UpdateBalanceParamsV0,
type UpdateSubscriptionV0Params,
} from "@autumn/shared";
import type { PrepareResponse } from "@/internal/migrations/v2/prepare/types";
import { defaultApiVersion } from "@tests/constants.js";
import { timeout } from "@tests/utils/genUtils";
import type { PrepareResponse } from "@/internal/migrations/v2/prepare/types";
export default class AutumnError extends Error {
message: string;
@@ -956,6 +956,27 @@ export class AutumnInt {
const data = await this.post(`/migrations.update`, params);
return data as Migration;
},
delete: async (params: { id: string }): Promise<Migration> => {
const data = await this.post(`/migrations.delete`, params);
return data as Migration;
},
/**
* Idempotent setup helper for tests / scripts: best-effort deletes
* the migration if it exists, then creates a fresh one with the
* given filter + operations. Avoids the (org_id, env, id) unique
* constraint when reseeding fixtures.
*/
deleteAndCreate: async (params: {
id: string;
filter?: MigrationFilter | null;
operations?: Operations | null;
}): Promise<Migration> => {
try {
await this.post(`/migrations.delete`, { id: params.id });
} catch {}
const data = await this.post(`/migrations.create`, params);
return data as Migration;
},
prepare: async (params: {
id: string;
dry_run: boolean;
@@ -963,6 +984,21 @@ export class AutumnInt {
const data = await this.post(`/migrations.prepare`, params);
return data as PrepareResponse;
},
run: async (params: {
id: string;
dry_run?: boolean;
}): Promise<{
migration_id: string;
dry_run: boolean;
run_id: string;
}> => {
const data = await this.post(`/migrations.run`, params);
return data as {
migration_id: string;
dry_run: boolean;
run_id: string;
};
},
};
balances = {

View File

@@ -0,0 +1,115 @@
/**
* Pure REST fetcher for Infisical secrets. Used at trigger.dev DEPLOY
* time via `syncEnvVars` to push secrets to the cloud env. Kept SDK-free
* so trigger.config.ts can import it without bloating the build.
*
* Runtime code uses `initInfisical` (SDK-based, populates process.env).
*/
export type InfisicalSyncEnvVar = { name: string; value: string };
export type FetchInfisicalSecretsArgs = {
clientId?: string | null;
clientSecret?: string | null;
projectId?: string | null;
/** Defaults to "prod" if not set. */
environment?: string | null;
secretPath?: string;
recursive?: boolean;
includeImports?: boolean;
};
/**
* Authenticate via Universal Auth, fetch secrets at the given path, and
* return them as `{ name, value }[]`. Imported groups are flattened in
* after primary secrets, with first-write-wins de-duplication.
*/
export const fetchInfisicalSecrets = async ({
clientId,
clientSecret,
projectId,
environment,
secretPath = "/",
recursive = true,
includeImports = true,
}: FetchInfisicalSecretsArgs): Promise<InfisicalSyncEnvVar[]> => {
const env = environment ?? "prod";
if (!clientId || !clientSecret || !projectId) {
throw new Error(
"Missing Infisical credentials. Set INFISICAL_CLIENT_ID, INFISICAL_CLIENT_SECRET, INFISICAL_PROJECT_ID.",
);
}
const authRes = await fetch(
"https://app.infisical.com/api/v1/auth/universal-auth/login",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ clientId, clientSecret }),
},
);
if (!authRes.ok)
throw new Error(
`Infisical auth failed: ${authRes.status} ${await authRes.text()}`,
);
const { accessToken } = (await authRes.json()) as { accessToken: string };
const params = new URLSearchParams({
environment: env,
workspaceId: projectId,
secretPath,
recursive: String(recursive),
includeImports: String(includeImports),
});
const secretsRes = await fetch(
`https://app.infisical.com/api/v3/secrets/raw?${params}`,
{ headers: { Authorization: `Bearer ${accessToken}` } },
);
if (!secretsRes.ok)
throw new Error(
`Infisical secrets failed: ${secretsRes.status} ${await secretsRes.text()}`,
);
const data = (await secretsRes.json()) as {
secrets: Array<{ secretKey: string; secretValue: string }>;
imports?: Array<{
secrets: Array<{ secretKey: string; secretValue: string }>;
}>;
};
const envVars: InfisicalSyncEnvVar[] = [];
const seen = new Set<string>();
const push = (key: string, value: string) => {
if (!key || !value || seen.has(key)) return;
envVars.push({ name: key, value });
seen.add(key);
};
for (const secret of data.secrets) push(secret.secretKey, secret.secretValue);
for (const importGroup of data.imports ?? [])
for (const secret of importGroup.secrets)
push(secret.secretKey, secret.secretValue);
console.log(
`[fetchInfisicalSecrets] Synced ${envVars.length} secrets from Infisical (env=${env}, path=${secretPath})`,
);
return envVars;
};
/**
* Read the four credential vars (`INFISICAL_CLIENT_ID`, `_SECRET`,
* `_PROJECT_ID`, `_ENVIRONMENT`) from the local process env first then
* trigger.dev's deploy-time `ctx.env`. Convenience for `syncEnvVars`.
*/
export const fetchInfisicalSecretsFromEnv = (
ctxEnv: Record<string, string | undefined> = {},
): Promise<InfisicalSyncEnvVar[]> =>
fetchInfisicalSecrets({
clientId: process.env.INFISICAL_CLIENT_ID ?? ctxEnv.INFISICAL_CLIENT_ID,
clientSecret:
process.env.INFISICAL_CLIENT_SECRET ?? ctxEnv.INFISICAL_CLIENT_SECRET,
projectId: process.env.INFISICAL_PROJECT_ID ?? ctxEnv.INFISICAL_PROJECT_ID,
environment:
process.env.INFISICAL_ENVIRONMENT ?? ctxEnv.INFISICAL_ENVIRONMENT,
});

View File

@@ -1,5 +1,6 @@
import "dotenv/config";
import type pino from "pino";
import { initLogger } from "@/utils/logging/initLogger";
const pinoLogger = initLogger();
@@ -72,34 +73,45 @@ const createLogMethod = (pinoMethod: any, logtailMethod?: any) => {
};
};
export const createLogger = () => {
// Helper function to create logger structure recursively
const createLoggerStructure = (basePinoLogger: any) => {
return {
debug: createLogMethod(basePinoLogger.debug.bind(basePinoLogger)),
info: createLogMethod(basePinoLogger.info.bind(basePinoLogger)),
warn: createLogMethod(basePinoLogger.warn.bind(basePinoLogger)),
error: createLogMethod(basePinoLogger.error.bind(basePinoLogger)),
child: ({
context,
onlyProd = false,
}: {
context: any;
onlyProd?: boolean;
}) => {
if (onlyProd && process.env.NODE_ENV !== "production") {
return createLoggerStructure(basePinoLogger);
}
const createLoggerStructure = (basePinoLogger: pino.Logger): Logger => ({
debug: createLogMethod(basePinoLogger.debug.bind(basePinoLogger)),
info: createLogMethod(basePinoLogger.info.bind(basePinoLogger)),
warn: createLogMethod(basePinoLogger.warn.bind(basePinoLogger)),
error: createLogMethod(basePinoLogger.error.bind(basePinoLogger)),
child: ({
context,
onlyProd = false,
}: {
context: any;
onlyProd?: boolean;
}) => {
if (onlyProd && process.env.NODE_ENV !== "production") {
return createLoggerStructure(basePinoLogger);
}
const childPinoLogger = basePinoLogger.child(context);
return createLoggerStructure(childPinoLogger);
},
};
};
const childPinoLogger = basePinoLogger.child(context);
return createLoggerStructure(childPinoLogger);
},
});
// Create the root logger using the helper function
return createLoggerStructure(pinoLogger);
export const createLogger = () => createLoggerStructure(pinoLogger);
/**
* Lazy dual-output logger (stdout JSON + axiom). Used only by long-running
* trigger.dev tasks so their lines surface in both the trigger run UI and
* our axiom store. Default `logger` / `createLogger` are unaffected.
*/
let dualPinoLogger: pino.Logger | null = null;
export const createDualLogger = () => {
if (!dualPinoLogger) dualPinoLogger = initLogger({ mode: "dual" });
return createLoggerStructure(dualPinoLogger);
};
export const logger = createLogger();
export type Logger = ReturnType<typeof createLogger>;
export type Logger = {
debug: (...args: any[]) => void;
info: (...args: any[]) => void;
warn: (...args: any[]) => void;
error: (...args: any[]) => void;
child: (args: { context: any; onlyProd?: boolean }) => Logger;
};

View File

@@ -26,6 +26,8 @@ import "./internal/misc/edgeConfig/orgLimitsStore.js";
import "./internal/misc/stripeSync/stripeSyncStore.js";
import "./internal/misc/redisV2Cache/redisV2CacheStore.js";
import "./internal/misc/jobQueues/jobQueueStore.js";
// Side-effect: configures trigger.dev SDK to use TRIGGER_SERVER_SECRET_KEY.
import "./trigger/configureTrigger.js";
import { closeStripeSyncEngine } from "@autumn/stripe-sync";
import {
startRedisMonitor,

View File

@@ -1,7 +0,0 @@
/**
* Higher-level migration business logic (preview, run, idempotency,
* billing-path bridging). Empty in phase 1 — phase 1 is pure CRUD on the
* migration entity, handled directly via `migrationRepo`. Phase 2+ adds
* verbs here as ops grow runtime semantics.
*/
export const migrationActions = {} as const;

View File

@@ -0,0 +1,62 @@
import type { CustomerFilter } from "@autumn/shared";
import { compileFilter } from "@autumn/shared/api/migrations/compiler/compileFilter.js";
import type { ResolutionContext } from "@autumn/shared/api/migrations/compiler/filterToIr/resolutionContext.js";
import { type SQL, sql } from "drizzle-orm";
import { rawWithParamsToDrizzle } from "../rawWithParamsToDrizzle.js";
export type CustomerQueryArgs = {
orgId: string;
env: string;
filter: CustomerFilter;
ctx: ResolutionContext;
};
const compileWhere = ({ orgId, env, filter, ctx }: CustomerQueryArgs): SQL =>
rawWithParamsToDrizzle(
compileFilter({ filter, ctx, ambient: { orgId, env } }),
);
/**
* Full SELECT. Returns `{ internal_id, id }` rows newest-first via keyset
* pagination on `c.internal_id DESC`, so successive iterations over an
* unchanged customer set yield rows in the same order.
*/
export const buildCustomerSelect = ({
orgId,
env,
filter,
ctx,
limit,
afterInternalId,
}: CustomerQueryArgs & {
limit?: number;
afterInternalId?: string;
}): SQL => {
const where = compileWhere({ orgId, env, filter, ctx });
const cursor = afterInternalId
? sql`AND c.internal_id < ${afterInternalId}`
: sql``;
const limitClause = limit !== undefined ? sql`LIMIT ${limit}` : sql``;
return sql`
SELECT c.internal_id, c.id
FROM customers c
WHERE (${where}) ${cursor}
ORDER BY c.internal_id DESC
${limitClause}
`;
};
/** COUNT(*) applying the same filter. */
export const buildCustomerCount = ({
orgId,
env,
filter,
ctx,
}: CustomerQueryArgs): SQL => {
const where = compileWhere({ orgId, env, filter, ctx });
return sql`
SELECT COUNT(*)::bigint AS count
FROM customers c
WHERE (${where})
`;
};

View File

@@ -0,0 +1,55 @@
import type { CustomerFilter } from "@autumn/shared";
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
import { iterateOverFilterResults } from "../iterateOverFilterResults.js";
import {
buildCustomerCount,
buildCustomerSelect,
} from "./buildCustomerSelect.js";
export type CustomerRow = { internal_id: string; id: string | null };
/**
* Pure inner: takes a CustomerFilter directly. Used by `runFilter` shim
* (Migration-fed) and reusable from scripts that don't have a Migration.
*/
export const filterCustomers = ({
ctx,
filter,
batchSize,
}: {
ctx: AutumnContext;
filter: CustomerFilter;
batchSize?: number;
}): AsyncGenerator<CustomerRow[]> => {
const args = {
orgId: ctx.org.id,
env: ctx.env,
filter,
ctx: { features: ctx.features },
};
return iterateOverFilterResults<CustomerRow>({
db: ctx.db,
buildSelect: ({ limit, afterInternalId }) =>
buildCustomerSelect({ ...args, limit, afterInternalId }),
batchSize,
});
};
/** Count of customers matching `filter`. */
export const countCustomers = async ({
ctx,
filter,
}: {
ctx: AutumnContext;
filter: CustomerFilter;
}): Promise<number> => {
const [{ count }] = (await ctx.db.execute(
buildCustomerCount({
orgId: ctx.org.id,
env: ctx.env,
filter,
ctx: { features: ctx.features },
}),
)) as Array<{ count: bigint | number }>;
return Number(count);
};

View File

@@ -0,0 +1,2 @@
export * from "./buildCustomerSelect.js";
export * from "./filterCustomers.js";

View File

@@ -0,0 +1,24 @@
import type { Migration } from "@autumn/shared";
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
import type { RunScopeKind } from "../run/types/runScope.js";
import { countCustomers } from "./customers/filterCustomers.js";
/** Migration-fed shim. Delegates to per-kind pure counters. */
export const getFilterCount = async ({
ctx,
migration,
kind,
}: {
ctx: AutumnContext;
migration: Migration;
kind: RunScopeKind;
}): Promise<number> => {
if (kind !== "customer")
throw new Error(
`getFilterCount: scope kind "${kind}" not supported yet (phase 2+)`,
);
return countCustomers({
ctx,
filter: migration.filter?.customer ?? {},
});
};

View File

@@ -0,0 +1,5 @@
export * from "./customers/index.js";
export * from "./getFilterCount.js";
export * from "./iterateOverFilterResults.js";
export * from "./rawWithParamsToDrizzle.js";
export * from "./runFilter.js";

View File

@@ -0,0 +1,30 @@
import type { SQL } from "drizzle-orm";
export const DEFAULT_BATCH_SIZE = 10_000;
/**
* Iterate keyset-paginated rows in batches. Caller supplies a `buildSelect`
* closure that returns the SQL for the next page given a cursor; rows
* MUST include `internal_id` for the cursor to advance.
*/
export async function* iterateOverFilterResults<
TRow extends { internal_id: string },
>({
db,
buildSelect,
batchSize = DEFAULT_BATCH_SIZE,
}: {
db: { execute: (query: SQL) => Promise<unknown> };
buildSelect: (args: { limit: number; afterInternalId?: string }) => SQL;
batchSize?: number;
}): AsyncGenerator<TRow[]> {
let cursor: string | undefined;
while (true) {
const query = buildSelect({ limit: batchSize, afterInternalId: cursor });
const rows = (await db.execute(query)) as unknown as TRow[];
if (rows.length === 0) return;
yield rows;
if (rows.length < batchSize) return;
cursor = rows[rows.length - 1].internal_id;
}
}

View File

@@ -0,0 +1,22 @@
import { type SQL, sql } from "drizzle-orm";
/** Convert the compiler's `{ sql, params }` output to a Drizzle SQL chunk. */
export const rawWithParamsToDrizzle = ({
sql: raw,
params,
}: {
sql: string;
params: readonly unknown[];
}): SQL => {
const parts = raw.split("?");
if (parts.length - 1 !== params.length)
throw new Error(
`Placeholder/param count mismatch: ${parts.length - 1} placeholders vs ${params.length} params`,
);
const chunks: SQL[] = [];
for (let i = 0; i < parts.length; i++) {
chunks.push(sql.raw(parts[i]));
if (i < params.length) chunks.push(sql`${params[i]}`);
}
return sql.join(chunks, sql.raw(""));
};

View File

@@ -0,0 +1,48 @@
import type { Migration } from "@autumn/shared";
import type { AutumnContext } from "../../../../honoUtils/HonoEnv.js";
import type { RunScopeItem, RunScopeKind } from "../run/types/runScope.js";
import {
countCustomers,
filterCustomers,
} from "./customers/filterCustomers.js";
/**
* Migration-fed shim. Dispatches by scope kind, unwraps the relevant
* filter from `migration.filter`, and delegates to the pure inner fns
* (`countCustomers` / `filterCustomers`). Empty filter ⇒ whole org+env.
*/
export const runFilter = async ({
ctx,
migration,
kind,
}: {
ctx: AutumnContext;
migration: Migration;
kind: RunScopeKind;
}): Promise<{
kind: RunScopeKind;
count: number;
iterate: () => AsyncGenerator<RunScopeItem[]>;
}> => {
if (kind !== "customer")
throw new Error(
`runFilter: scope kind "${kind}" not supported yet (phase 2+)`,
);
const filter = migration.filter?.customer ?? {};
const count = await countCustomers({ ctx, filter });
const iterate = async function* () {
for await (const batch of filterCustomers({ ctx, filter })) {
yield batch.map(
(row): RunScopeItem => ({
kind: "customer",
internal_id: row.internal_id,
id: row.id,
}),
);
}
};
return { kind, count, iterate };
};

View File

@@ -21,6 +21,6 @@ export const handleCreateMigration = createRoute({
const migration = await migrationRepo.insert({ ctx, insert });
return c.json(migration, 201);
return c.json(migration);
},
});

View File

@@ -0,0 +1,28 @@
import { ErrCode, RecaseError, Scopes } from "@autumn/shared";
import { z } from "zod/v4";
import { createRoute } from "@/honoMiddlewares/routeHandler";
import { migrationRepo } from "@/internal/migrations/v2/repos/index.js";
const DeleteMigrationBody = z.object({
id: z.string(),
});
/** POST /migrations.delete — delete by user `id`. */
export const handleDeleteMigration = createRoute({
scopes: [Scopes.Migrations.Write],
body: DeleteMigrationBody,
handler: async (c) => {
const ctx = c.get("ctx");
const { id } = c.req.valid("json");
const deleted = await migrationRepo.delete({ ctx, id });
if (!deleted)
throw new RecaseError({
message: `Migration ${id} not found`,
code: ErrCode.MigrationNotFound,
statusCode: 404,
});
return c.json(deleted);
},
});

View File

@@ -17,13 +17,7 @@ export const handlePrepareMigration = createRoute({
const ctx = c.get("ctx");
const { id, dry_run } = c.req.valid("json");
const [migration] = await migrationRepo.get({ ctx, id });
if (!migration)
throw new RecaseError({
message: `Migration ${id} not found`,
code: ErrCode.MigrationNotFound,
statusCode: 404,
});
const migration = await migrationRepo.find({ ctx, id });
if (!migration.operations)
throw new RecaseError({
@@ -32,7 +26,7 @@ export const handlePrepareMigration = createRoute({
statusCode: 400,
});
const response = await runPrepare({ ctx, migration, dry_run });
const { response } = await runPrepare({ ctx, migration, dry_run });
return c.json(response);
},
});

View File

@@ -0,0 +1,51 @@
import { ErrCode, RecaseError, Scopes } from "@autumn/shared";
import { z } from "zod/v4";
import { createRoute } from "@/honoMiddlewares/routeHandler";
import { migrationRepo } from "@/internal/migrations/v2/repos/index.js";
import { runMigrationTask } from "@/trigger/migrations/runMigrationTask.js";
const RunMigrationBody = z.object({
id: z.string(),
dry_run: z.boolean().default(false),
});
/**
* POST /migrations.run — kick off a migration on trigger.dev. Returns the
* trigger run handle so the dashboard can poll status. In dev we route
* to EU so dev runs don't touch the production US region.
*/
export const handleRunMigration = createRoute({
scopes: [Scopes.Migrations.Write],
body: RunMigrationBody,
handler: async (c) => {
const ctx = c.get("ctx");
const { id, dry_run } = c.req.valid("json");
const migration = await migrationRepo.find({ ctx, id });
if (!migration.operations)
throw new RecaseError({
message: `Migration ${id} has no operations to run`,
code: ErrCode.InvalidRequest,
statusCode: 400,
});
const isDev = process.env.NODE_ENV === "development";
const handle = await runMigrationTask.trigger(
{
orgId: ctx.org.id,
env: ctx.env,
migrationId: id,
dryRun: dry_run,
},
isDev ? { region: "eu-west-1" } : undefined,
);
return c.json({
migration_id: id,
dry_run,
run_id: handle.id,
});
},
});

View File

@@ -1,9 +1,11 @@
import { Hono } from "hono";
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
import { handleCreateMigration } from "./handlers/handleCreateMigration/handleCreateMigration.js";
import { handleListMigrations } from "./handlers/handleListMigrations/handleListMigrations.js";
import { handlePatchMigration } from "./handlers/handlePatchMigration/handlePatchMigration.js";
import { handlePrepareMigration } from "./handlers/handlePrepareMigration/handlePrepareMigration.js";
import { handleCreateMigration } from "./handlers/handleCreateMigration.js";
import { handleDeleteMigration } from "./handlers/handleDeleteMigration.js";
import { handleListMigrations } from "./handlers/handleListMigrations.js";
import { handlePatchMigration } from "./handlers/handlePatchMigration.js";
import { handlePrepareMigration } from "./handlers/handlePrepareMigration.js";
import { handleRunMigration } from "./handlers/handleRunMigration.js";
/**
* V2 user-facing migrations RPC router. Distinct from the legacy
@@ -15,4 +17,6 @@ export const migrationRpcRouter = new Hono<HonoEnv>();
migrationRpcRouter.post("/migrations.create", ...handleCreateMigration);
migrationRpcRouter.post("/migrations.list", ...handleListMigrations);
migrationRpcRouter.post("/migrations.update", ...handlePatchMigration);
migrationRpcRouter.post("/migrations.delete", ...handleDeleteMigration);
migrationRpcRouter.post("/migrations.prepare", ...handlePrepareMigration);
migrationRpcRouter.post("/migrations.run", ...handleRunMigration);

View File

@@ -1,4 +1,5 @@
export * from "./inferImplicitPrep.js";
export * from "./modules/ensurePricesAndEntitlements/index.js";
export * from "./runPrepare.js";
export * from "./runPrepareModules.js";
export * from "./types/index.js";

View File

@@ -1,13 +1,10 @@
import type { Migration } from "@autumn/shared";
import type { Migration, Operations } from "@autumn/shared";
import {
ensurePricesAndEntitlements,
type EnsurePricesAndEntitlementsInput,
ensurePricesAndEntitlements,
} from "./modules/ensurePricesAndEntitlements/index.js";
/**
* One instance of a prep module to run. The orchestrator calls each
* instance's `plan` / `apply` in order.
*/
/** One instance of a prep module to run. */
export type ImplicitPrepInstance = {
key: string;
module: typeof ensurePricesAndEntitlements;
@@ -15,18 +12,17 @@ export type ImplicitPrepInstance = {
};
/**
* Walk the migration's operations and derive which prep modules need to
* run. Phase 1 only emits `ensure_prices_and_entitlements` from
* `update_plans[].add_items[]`.
*
* Module key format: `<kind>:<feature_id>:<plan_id>` so distinct
* (target, feature) pairs get distinct prep entries.
* Pure walker. Takes an `operations` object directly so scripts (and
* any other caller) can derive prep instances without a Migration row.
* Module key format: `<kind>:<feature_id>:<plan_id>`.
*/
export const inferImplicitPrep = (
migration: Migration,
): ImplicitPrepInstance[] => {
export const inferPrepareModules = ({
operations,
}: {
operations: Operations | null | undefined;
}): ImplicitPrepInstance[] => {
const instances: ImplicitPrepInstance[] = [];
const updatePlans = migration.operations?.customer?.update_plans ?? [];
const updatePlans = operations?.customer?.update_plans ?? [];
for (const op of updatePlans) {
const planId =
@@ -35,20 +31,23 @@ export const inferImplicitPrep = (
for (const item of op.add_items ?? []) {
if (!item.feature_id) continue;
// Phase 1 constraint: only entitlement-only items. Priced items
// will be handled by the same module in phase 2+.
// Phase 1 constraint: entitlement-only items. Priced items will be
// handled by the same module in phase 2+.
if (item.price) continue;
instances.push({
key: `ensure_prices_and_entitlements:${item.feature_id}:${planId}`,
module: ensurePricesAndEntitlements,
input: {
target_plan_id: planId,
feature_id: item.feature_id,
},
input: { target_plan_id: planId, feature_id: item.feature_id },
});
}
}
return instances;
};
/** Migration-fed shim. */
export const inferImplicitPrep = (
migration: Migration,
): ImplicitPrepInstance[] =>
inferPrepareModules({ operations: migration.operations });

View File

@@ -13,17 +13,20 @@ export type EnsurePricesAndEntitlementsInput = {
feature_id: string;
};
/** Deterministic ID per (migration, product version, feature). Idempotent across runs. */
/**
* Deterministic ID per (scope, product version, feature). Migration
* scopes pass `scopeId = mig_<internal_id>` to preserve the original
* `ent_mig_<id>_<...>` format; scripts pass their own prefix.
*/
export const entitlementIdFor = ({
migrationInternalId,
scopeId,
productInternalId,
internalFeatureId,
}: {
migrationInternalId: string;
scopeId: string;
productInternalId: string;
internalFeatureId: string;
}): string =>
`ent_mig_${migrationInternalId}_${productInternalId}_${internalFeatureId}`;
}): string => `ent_${scopeId}_${productInternalId}_${internalFeatureId}`;
export const ensurePricesAndEntitlements: PrepareModule<
EnsurePricesAndEntitlementsInput,
@@ -31,7 +34,7 @@ export const ensurePricesAndEntitlements: PrepareModule<
> = {
kind: "ensure_prices_and_entitlements",
async plan({ ctx, migration, input }) {
async plan({ ctx, scope_id, input }) {
const feature = ctx.features.find((f) => f.id === input.feature_id);
if (!feature)
throw new Error(
@@ -50,7 +53,7 @@ export const ensurePricesAndEntitlements: PrepareModule<
const desired: EntitlementItemRef[] = matchingProducts.map((product) => ({
entitlement_id: entitlementIdFor({
migrationInternalId: migration.internal_id,
scopeId: scope_id,
productInternalId: product.internal_id,
internalFeatureId: feature.internal_id,
}),
@@ -67,7 +70,7 @@ export const ensurePricesAndEntitlements: PrepareModule<
const desired = planned.entitlements;
const ids = desired.map((d) => d.entitlement_id);
// Skip rows that already exist in DB. Deterministic IDs make this safe.
// Deterministic IDs let us skip rows already present in DB.
const existing = ids.length
? await ctx.db
.select({ id: entitlements.id })

View File

@@ -2,18 +2,18 @@ import type { Migration } from "@autumn/shared";
import type { AutumnContext } from "../../../../honoUtils/HonoEnv.js";
import { migrationRepo } from "../repos/index.js";
import { inferImplicitPrep } from "./inferImplicitPrep.js";
import type {
PrepareModuleResult,
PrepareResponse,
PreparedState,
} from "./types/index.js";
import { runPrepareModules } from "./runPrepareModules.js";
import type { PreparedState, PrepareResponse } from "./types/index.js";
/** Stable scope_id for a Migration. Preserves the historical entitlement ID format. */
const scopeIdFor = (migration: Migration): string =>
`mig_${migration.internal_id}`;
/**
* Orchestrate a migration's prepare phase. Walks implicit prep modules,
* runs plan → apply per module, persists prepared_state.
*
* On `dry_run: true`, plan runs but apply is skipped and prepared_state
* is not written.
* Migration-fed shim around `runPrepareModules`. Walks implicit prep
* modules from `migration.operations`, runs the pure orchestrator, then
* persists the new `prepared_state` back to the migrations row (skipped
* on dry-run).
*/
export const runPrepare = async ({
ctx,
@@ -23,40 +23,32 @@ export const runPrepare = async ({
ctx: AutumnContext;
migration: Migration;
dry_run: boolean;
}): Promise<PrepareResponse> => {
const instances = inferImplicitPrep(migration);
const warnings: string[] = [];
const modules: PrepareModuleResult[] = [];
const nextState: PreparedState = { ...(migration.prepared_state ?? {}) };
}): Promise<{ response: PrepareResponse; prepared_state: PreparedState }> => {
const modules = inferImplicitPrep(migration);
for (const { key, module, input } of instances) {
const planned = await module.plan({ ctx, migration, input });
if (planned.entitlements.length === 0) {
warnings.push(`No products matched target for ${key}`);
}
const result = dry_run
? planned
: await module.apply({ ctx, migration, input, planned });
if (!dry_run) nextState[key] = result;
modules.push({ key, kind: module.kind, result });
}
const { results, prepared_state } = await runPrepareModules({
ctx,
scope_id: scopeIdFor(migration),
modules,
dry_run,
prior_state: migration.prepared_state ?? {},
});
if (!dry_run) {
await migrationRepo.update({
ctx,
id: migration.id,
updates: { prepared_state: nextState },
updates: { prepared_state },
});
}
return {
migration_id: migration.id,
dry_run,
modules,
warnings,
response: {
migration_id: migration.id,
dry_run,
modules: results,
warnings: [],
},
prepared_state,
};
};

View File

@@ -0,0 +1,40 @@
import type { AutumnContext } from "../../../../honoUtils/HonoEnv.js";
import type { ImplicitPrepInstance } from "./inferImplicitPrep.js";
import type { PreparedState, PrepareModuleResult } from "./types/index.js";
/**
* Pure orchestrator. Walks a list of prep module instances under a
* given `scope_id`, runs plan → apply per module (apply skipped on
* dry-run), threads `prepared_state` through. No DB reads/writes
* outside what the modules themselves do — script-callable.
*/
export const runPrepareModules = async ({
ctx,
scope_id,
modules,
dry_run,
prior_state = {},
}: {
ctx: AutumnContext;
scope_id: string;
modules: ImplicitPrepInstance[];
dry_run: boolean;
prior_state?: PreparedState;
}): Promise<{
results: PrepareModuleResult[];
prepared_state: PreparedState;
}> => {
const results: PrepareModuleResult[] = [];
const next_state: PreparedState = { ...prior_state };
for (const { key, module, input } of modules) {
const planned = await module.plan({ ctx, scope_id, input });
const result = dry_run
? planned
: await module.apply({ ctx, scope_id, input, planned });
if (!dry_run) next_state[key] = result;
results.push({ key, kind: module.kind, result });
}
return { results, prepared_state: next_state };
};

View File

@@ -1,4 +1,4 @@
export * from "./preparedState.js";
export * from "./prepareModule.js";
export * from "./prepareModuleResult.js";
export * from "./prepareResponse.js";
export * from "./preparedState.js";

View File

@@ -1,11 +1,13 @@
import type { Migration } from "@autumn/shared";
import type { AutumnContext } from "../../../../../honoUtils/HonoEnv";
/**
* Common shape every prepare module implements. `Input` and `Result`
* are module-specific (see `modules/<kind>/types.ts`). The orchestrator
* wraps the returned `Result` in the loose `{ key, kind, result }`
* envelope before persistence / response.
* are module-specific. The orchestrator wraps the returned `Result` in
* the loose `{ key, kind, result }` envelope.
*
* `scope_id` is the namespace under which deterministic catalog rows
* are created — `mig_<internal_id>` for migrations, or any other prefix
* for ad-hoc script invocations.
*/
export type PrepareModule<Input, Result> = {
kind: string;
@@ -13,14 +15,14 @@ export type PrepareModule<Input, Result> = {
/** Pure planning. No writes. */
plan: (args: {
ctx: AutumnContext;
migration: Migration;
scope_id: string;
input: Input;
}) => Promise<Result>;
/** Persist the desired set. Idempotent (deterministic IDs). */
apply: (args: {
ctx: AutumnContext;
migration: Migration;
scope_id: string;
input: Input;
planned: Result;
}) => Promise<Result>;

View File

@@ -0,0 +1,24 @@
import { type Migration, migrations } from "@autumn/shared";
import { and, eq } from "drizzle-orm";
import type { RepoContext } from "@/db/repoContext.js";
/** Delete by user `id`, scoped to the current org + env. */
export const deleteMigration = async ({
ctx,
id,
}: {
ctx: RepoContext;
id: string;
}): Promise<Migration | null> => {
const [row] = await ctx.db
.delete(migrations)
.where(
and(
eq(migrations.id, id),
eq(migrations.org_id, ctx.org.id),
eq(migrations.env, ctx.env),
),
)
.returning();
return row ?? null;
};

View File

@@ -0,0 +1,39 @@
import { ErrCode, type Migration, RecaseError } from "@autumn/shared";
import { and, eq } from "drizzle-orm";
import type { RepoContext } from "@/db/repoContext.js";
/**
* Single-row lookup by user `id` (or `internal_id`), scoped to the
* current org + env. Throws `MigrationNotFound` if missing — callers
* that need a nullable result use `migrationRepo.get(...)` instead.
*/
export const findMigration = async ({
ctx,
id,
internalId,
}: {
ctx: RepoContext;
id?: string;
internalId?: string;
}): Promise<Migration> => {
if (!id && !internalId)
throw new Error("findMigration: pass either `id` or `internalId`");
const row = await ctx.db.query.migrations.findFirst({
where: (m) =>
and(
eq(m.org_id, ctx.org.id),
eq(m.env, ctx.env),
id !== undefined ? eq(m.id, id) : eq(m.internal_id, internalId!),
),
});
if (!row)
throw new RecaseError({
message: `Migration ${id ?? internalId} not found`,
code: ErrCode.MigrationNotFound,
statusCode: 404,
});
return row;
};

View File

@@ -1,3 +1,5 @@
import { deleteMigration } from "./deleteMigration.js";
import { findMigration } from "./findMigration.js";
import { getMigration } from "./getMigration.js";
import { insertMigration } from "./insertMigration.js";
import { updateMigration } from "./updateMigration.js";
@@ -5,5 +7,7 @@ import { updateMigration } from "./updateMigration.js";
export const migrationRepo = {
insert: insertMigration,
get: getMigration,
find: findMigration,
update: updateMigration,
delete: deleteMigration,
};

View File

@@ -0,0 +1,4 @@
export * from "./orchestrators/index.js";
export * from "./perItem/index.js";
export * from "./runMigration.js";
export * from "./types/index.js";

View File

@@ -0,0 +1,2 @@
export * from "./iterateScope.js";
export * from "./runPreparation.js";

View File

@@ -0,0 +1,52 @@
import type { RunScopeItem } from "../types/runScope.js";
export type IterateScopeItemResult<T> =
| { status: "ok"; item: RunScopeItem; value: T }
| { status: "failed"; item: RunScopeItem; error: Error };
export type IterateScopeSummary<T> = {
processed: number;
succeeded: number;
failed: number;
results: IterateScopeItemResult<T>[];
};
/**
* Generic iteration over any kind-tagged scope iterator. Calls `perItem`
* for every item, collecting per-item results into a summary. Sequential
* by default; concurrency layer is intentionally out of scope here.
*
* On error: keeps going with `onError: "continue"` (default), or rethrows
* the first error with `onError: "throw"`. Either way every visited
* item shows up in `results` so callers see the full audit trail.
*/
export const iterateScope = async <T>({
iterate,
perItem,
onError = "continue",
}: {
iterate: () => AsyncGenerator<RunScopeItem[]>;
perItem: (item: RunScopeItem) => Promise<T>;
onError?: "throw" | "continue";
}): Promise<IterateScopeSummary<T>> => {
const results: IterateScopeItemResult<T>[] = [];
let succeeded = 0;
let failed = 0;
for await (const batch of iterate()) {
for (const item of batch) {
try {
const value = await perItem(item);
results.push({ status: "ok", item, value });
succeeded++;
} catch (raw) {
const error = raw instanceof Error ? raw : new Error(String(raw));
results.push({ status: "failed", item, error });
failed++;
if (onError === "throw") throw error;
}
}
}
return { processed: succeeded + failed, succeeded, failed, results };
};

View File

@@ -0,0 +1,27 @@
import type { Migration } from "@autumn/shared";
import type { AutumnContext } from "../../../../../honoUtils/HonoEnv.js";
import { runPrepare } from "../../prepare/runPrepare.js";
import type {
PreparedState,
PrepareResponse,
} from "../../prepare/types/index.js";
/**
* Run-phase wrapper around the prepare orchestrator. Returns the
* freshly written `prepared_state` so per-item handlers can read it
* without re-fetching the migration row.
*
* On `dry_run: true` this still computes and returns the planned
* `prepared_state` shape (so dry-run end-to-end can show what each
* customer would see) — but the migrations row is not updated.
*/
export const runPreparation = async ({
ctx,
migration,
dry_run,
}: {
ctx: AutumnContext;
migration: Migration;
dry_run: boolean;
}): Promise<{ response: PrepareResponse; prepared_state: PreparedState }> =>
runPrepare({ ctx, migration, dry_run });

View File

@@ -0,0 +1,105 @@
import type { CreatePlanItemParamsV1, FullCusProduct } from "@autumn/shared";
import { customerEntitlements } from "@autumn/shared";
import { inArray } from "drizzle-orm";
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService.js";
import type { EnsurePricesAndEntitlementsResult } from "@/internal/migrations/v2/prepare/modules/ensurePricesAndEntitlements/types.js";
import type { PreparedState } from "@/internal/migrations/v2/prepare/types/index.js";
export type ApplyAddItemResult =
| { status: "added"; customer_entitlement_id: string }
| { status: "already_present"; customer_entitlement_id: string }
| {
status: "skipped";
reason: "no_prepared_entitlement" | "priced_item_unsupported";
};
/** Customer-entitlement deterministic ID per (scope, cusproduct, entitlement). */
const customerEntitlementIdFor = ({
scopeId,
cusProductInternalId,
entitlementId,
}: {
scopeId: string;
cusProductInternalId: string;
entitlementId: string;
}): string => `cusent_${scopeId}_${cusProductInternalId}_${entitlementId}`;
/**
* Apply ONE add_item to ONE matched cusproduct. Phase 1 handles
* entitlement-only items; priced items will route through the same
* flow once `ensurePricesAndEntitlements` provisions Stripe prices.
*/
export const applyAddItem = async ({
ctx,
scope_id,
cusProduct,
addItem,
prepared_state,
dry_run,
}: {
ctx: AutumnContext;
scope_id: string;
cusProduct: FullCusProduct;
addItem: CreatePlanItemParamsV1;
prepared_state: PreparedState;
dry_run: boolean;
}): Promise<ApplyAddItemResult> => {
if (addItem.price)
return { status: "skipped", reason: "priced_item_unsupported" };
if (!addItem.feature_id)
return { status: "skipped", reason: "no_prepared_entitlement" };
// Look up the prepared entitlement for (feature, target plan) keyed
// `ensure_prices_and_entitlements:<feature_id>:<plan_id>`. The
// matching `entitlement_id` is whichever ref points at this
// cusproduct's product version.
const stateKey = `ensure_prices_and_entitlements:${addItem.feature_id}:${cusProduct.product_id}`;
const slot = prepared_state[stateKey] as
| EnsurePricesAndEntitlementsResult
| undefined;
const ref = slot?.entitlements.find(
(e) => e.product_internal_id === cusProduct.internal_product_id,
);
if (!ref) return { status: "skipped", reason: "no_prepared_entitlement" };
const customerEntitlementId = customerEntitlementIdFor({
scopeId: scope_id,
cusProductInternalId: cusProduct.id,
entitlementId: ref.entitlement_id,
});
const existing = await ctx.db
.select({ id: customerEntitlements.id })
.from(customerEntitlements)
.where(inArray(customerEntitlements.id, [customerEntitlementId]));
if (existing.length > 0)
return {
status: "already_present",
customer_entitlement_id: customerEntitlementId,
};
if (dry_run)
return { status: "added", customer_entitlement_id: customerEntitlementId };
await CusEntService.insert({
ctx,
data: [
{
id: customerEntitlementId,
customer_product_id: cusProduct.id,
entitlement_id: ref.entitlement_id,
internal_customer_id: cusProduct.internal_customer_id,
internal_feature_id: ref.internal_feature_id,
feature_id: ref.feature_id,
customer_id: cusProduct.customer_id ?? null,
balance: 0,
created_at: Date.now(),
usage_allowed: false,
unlimited: false,
},
],
});
return { status: "added", customer_entitlement_id: customerEntitlementId };
};

View File

@@ -0,0 +1,4 @@
export * from "./applyAddItem.js";
export * from "./matchPlanFilter.js";
export * from "./matchStringMatcher.js";
export * from "./runOpsForCustomer.js";

View File

@@ -0,0 +1,35 @@
import type { FullCusProduct, PlanFilter } from "@autumn/shared";
import { matchStringMatcher } from "./matchStringMatcher.js";
/**
* JS-side PlanFilter matcher against a single FullCusProduct. Phase 1
* supports `plan_id` only — `price`, `paid`, `recurring`, `item`, `$or`
* throw `not_supported_in_matcher` so callers see the gap explicitly.
*/
export const matchPlanFilter = (
filter: PlanFilter,
cusProduct: FullCusProduct,
): boolean => {
if (filter.plan_id !== undefined) {
if (!matchStringMatcher(filter.plan_id, cusProduct.product_id))
return false;
}
const unsupported = ["price", "paid", "recurring", "item", "$or"] as const;
for (const key of unsupported) {
if ((filter as Record<string, unknown>)[key] !== undefined)
throw new Error(
`matchPlanFilter: target.${key} not supported in JS matcher yet`,
);
}
return true;
};
/** Returns the cusproducts on a customer that match `target`. */
export const matchCustomerProductsByTarget = ({
cusProducts,
target,
}: {
cusProducts: FullCusProduct[];
target: PlanFilter;
}): FullCusProduct[] => cusProducts.filter((cp) => matchPlanFilter(target, cp));

View File

@@ -0,0 +1,32 @@
import type { StringMatcher } from "@autumn/shared";
/** Phase 1 ops only: bare value, $eq, $ne, $in, $nin. */
export const matchStringMatcher = (
matcher: StringMatcher | undefined,
value: string | null | undefined,
): boolean => {
if (matcher === undefined) return true;
if (matcher === null) return value === null || value === undefined;
if (typeof matcher === "string") return value === matcher;
if ("$eq" in matcher && matcher.$eq !== undefined) {
if (matcher.$eq === null) return value === null || value === undefined;
if (value !== matcher.$eq) return false;
}
if ("$ne" in matcher && matcher.$ne !== undefined) {
if (matcher.$ne === null) {
if (value === null || value === undefined) return false;
} else if (value === matcher.$ne) return false;
}
if (matcher.$in && !matcher.$in.includes(value ?? "")) return false;
if (matcher.$nin && matcher.$nin.includes(value ?? "")) return false;
const unsupported = ["$regex", "$startsWith"] as const;
for (const op of unsupported) {
if (op in matcher && (matcher as Record<string, unknown>)[op] !== undefined)
throw new Error(
`matchStringMatcher: operator ${op} not supported in JS matcher yet`,
);
}
return true;
};

View File

@@ -0,0 +1,70 @@
import type { Operations } from "@autumn/shared";
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
import { CusService } from "@/internal/customers/CusService.js";
import type { PreparedState } from "@/internal/migrations/v2/prepare/types/index.js";
import { type ApplyAddItemResult, applyAddItem } from "./applyAddItem.js";
import { matchCustomerProductsByTarget } from "./matchPlanFilter.js";
export type RunOpsForCustomerResult = {
internal_customer_id: string;
matched_cusproducts: number;
add_items: ApplyAddItemResult[];
};
/**
* Walk a customer's matching cusproducts × the migration's update_plans
* × add_items. Phase 1 only — delete_items + priced add_items will be
* routed in here as their handlers ship.
*/
export const runOpsForCustomer = async ({
ctx,
scope_id,
internal_customer_id,
operations,
prepared_state,
dry_run,
}: {
ctx: AutumnContext;
scope_id: string;
internal_customer_id: string;
operations: Operations;
prepared_state: PreparedState;
dry_run: boolean;
}): Promise<RunOpsForCustomerResult> => {
const fullCustomer = await CusService.getFull({
ctx,
idOrInternalId: internal_customer_id,
});
const updatePlans = operations.customer?.update_plans ?? [];
let matched = 0;
const addItemResults: ApplyAddItemResult[] = [];
for (const op of updatePlans) {
const cps = matchCustomerProductsByTarget({
cusProducts: fullCustomer.customer_products,
target: op.target,
});
matched += cps.length;
for (const cusProduct of cps) {
for (const addItem of op.add_items ?? []) {
const result = await applyAddItem({
ctx,
scope_id,
cusProduct,
addItem,
prepared_state,
dry_run,
});
addItemResults.push(result);
}
}
}
return {
internal_customer_id,
matched_cusproducts: matched,
add_items: addItemResults,
};
};

View File

@@ -0,0 +1,74 @@
import type { Migration, Operations } from "@autumn/shared";
import type { AutumnContext } from "../../../../honoUtils/HonoEnv.js";
import { runFilter } from "../filters/runFilter.js";
import { iterateScope, runPreparation } from "./orchestrators/index.js";
import { runOpsForCustomer } from "./perItem/runOpsForCustomer.js";
import type {
RunMigrationResponse,
RunMigrationScopeResult,
} from "./types/runMigrationResponse.js";
import type { RunScopeKind } from "./types/runScope.js";
/** Top-level migration run: prepare → per-scope filter+iterate → per-item ops. */
export const runMigration = async ({
ctx,
migration,
dry_run,
}: {
ctx: AutumnContext;
migration: Migration;
dry_run: boolean;
}): Promise<RunMigrationResponse> => {
const { response: prepareResponse, prepared_state } = await runPreparation({
ctx,
migration,
dry_run,
});
const scope_id = `mig_${migration.internal_id}`;
const operations: Operations = migration.operations ?? {};
const scopeResults: RunMigrationScopeResult[] = [];
for (const kind of scopesForRun(migration)) {
const { count, iterate } = await runFilter({ ctx, migration, kind });
ctx.logger.info(`run-migration: iterating scope`, {
data: { kind, count, dry_run },
});
const summary = await iterateScope({
iterate,
perItem: async (item) => {
if (item.kind !== "customer")
throw new Error(
`runMigration: per-item handler missing for kind "${item.kind}"`,
);
return runOpsForCustomer({
ctx,
scope_id,
internal_customer_id: item.internal_id,
operations,
prepared_state,
dry_run,
});
},
});
scopeResults.push({ kind, count, summary });
}
return {
migration_id: migration.id,
dry_run,
prepare_warnings: prepareResponse.warnings,
scopes: scopeResults,
};
};
/** Active scopes = top-level keys present in `migration.operations`. */
const scopesForRun = (migration: Migration): RunScopeKind[] => {
const scopes: RunScopeKind[] = [];
if (migration.operations?.customer) scopes.push("customer");
// future: if (migration.operations?.plan) scopes.push("plan");
return scopes;
};

View File

@@ -0,0 +1,2 @@
export * from "./runMigrationResponse.js";
export * from "./runScope.js";

View File

@@ -0,0 +1,17 @@
import type { IterateScopeSummary } from "../orchestrators/iterateScope.js";
import type { RunOpsForCustomerResult } from "../perItem/runOpsForCustomer.js";
import type { RunScopeKind } from "./runScope.js";
/** One scope's portion of a run — kind-tagged for phase-2 multi-scope. */
export type RunMigrationScopeResult = {
kind: RunScopeKind;
count: number;
summary: IterateScopeSummary<RunOpsForCustomerResult>;
};
export type RunMigrationResponse = {
migration_id: string;
dry_run: boolean;
prepare_warnings: string[];
scopes: RunMigrationScopeResult[];
};

View File

@@ -0,0 +1,9 @@
/** Resource kind being iterated. Phase 2+ adds catalog-rooted kinds. */
export type RunScopeKind = "customer" | "plan";
/** One iterated item, kind-tagged for generic dispatch. */
export type RunScopeItem = {
kind: RunScopeKind;
internal_id: string;
id: string | null;
};

View File

@@ -0,0 +1,18 @@
import { configure } from "@trigger.dev/sdk/v3";
/**
* Point the trigger.dev SDK at autumn's project key.
*
* autumn and autumn-cloud each have their own trigger.dev project. We
* keep autumn's secret under `TRIGGER_SERVER_SECRET_KEY` so the two
* never collide in a shared shell — the SDK's default `TRIGGER_SECRET_KEY`
* is left to autumn-cloud.
*
* Module side-effect: this `configure` call runs once on first import.
* Anything that triggers tasks server-side imports from
* `@/trigger/migrations/...`, which re-exports from this file's siblings,
* so the configure happens before any `.trigger()` call.
*/
if (process.env.TRIGGER_SERVER_SECRET_KEY) {
configure({ secretKey: process.env.TRIGGER_SERVER_SECRET_KEY });
}

View File

@@ -0,0 +1,52 @@
import { AppEnv } from "@autumn/shared";
import { task } from "@trigger.dev/sdk/v3";
import { z } from "zod/v4";
import { migrationRepo } from "@/internal/migrations/v2/repos/index.js";
import { runMigration } from "@/internal/migrations/v2/run/runMigration.js";
import { createTriggerContext } from "@/trigger/utils/createTriggerContext.js";
const PayloadSchema = z.object({
orgId: z.string(),
env: z.enum(AppEnv),
migrationId: z.string(),
dryRun: z.boolean().default(false),
});
export type RunMigrationPayload = z.infer<typeof PayloadSchema>;
/** trigger.dev task for long-running migrations (deploys kill api workers). */
export const runMigrationTask = task({
id: "run-migration",
maxDuration: 3600,
run: async (rawPayload: unknown, { ctx: triggerCtx }) => {
const { orgId, env, migrationId, dryRun } = PayloadSchema.parse(rawPayload);
const { ctx, logger } = await createTriggerContext({
orgId,
env,
triggerCtx,
});
logger.info("run-migration: starting", {
data: { migrationId, dryRun },
});
const migration = await migrationRepo.find({ ctx, id: migrationId });
const result = await runMigration({ ctx, migration, dry_run: dryRun });
logger.info("run-migration: done", {
data: {
migration_id: result.migration_id,
dry_run: result.dry_run,
scopes: result.scopes.map((s) => ({
kind: s.kind,
count: s.count,
succeeded: s.summary.succeeded,
failed: s.summary.failed,
})),
},
});
return result;
},
});

View File

@@ -0,0 +1,46 @@
import type { AppEnv } from "@autumn/shared";
import type { Context as TriggerRunContext } from "@trigger.dev/sdk/v3";
import { db } from "@/db/initDrizzle.js";
import {
createDualLogger,
type Logger,
} from "@/external/logtail/logtailUtils.js";
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
import { createWorkerContext } from "@/queue/createWorkerContext.js";
import { addTriggerToLogs } from "@/utils/logging/addContextToLogs.js";
/**
* Build an `AutumnContext` for a trigger.dev task run. Uses the dual
* logger (stdout + axiom) and tags every line with run/task/attempt ids.
*/
export const createTriggerContext = async ({
orgId,
env,
triggerCtx,
}: {
orgId: string;
env: AppEnv;
triggerCtx: TriggerRunContext;
}): Promise<{ ctx: AutumnContext; logger: Logger }> => {
const logger = addTriggerToLogs({
logger: createDualLogger(),
triggerContext: {
run_id: triggerCtx.run.id,
task_id: triggerCtx.task.id,
attempt_number: triggerCtx.attempt.number,
},
});
const ctx = await createWorkerContext({
db,
payload: { orgId, env, requestId: triggerCtx.run.id },
logger,
});
if (!ctx)
throw new Error(
`createTriggerContext: failed to build context for org=${orgId} env=${env}`,
);
return { ctx, logger };
};

View File

@@ -0,0 +1 @@
export * from "./createTriggerContext.js";

View File

@@ -1,17 +1,36 @@
import { existsSync } from "node:fs";
import { join } from "node:path";
import { config } from "dotenv";
let hasLoadedLocalEnv = false;
const shouldLogLocalEnvLoading = false;
/**
* Resolve the directory holding `.env`, robust to cwd:
* - cwd already a `server/` dir
* - cwd is `autumn/` (typical workspace root)
* - cwd is the monorepo root (one level above `autumn/`) — happens with
* `bun test autumn/...` invocations from VSCode tasks
*/
const resolveServerDir = (): string => {
const cwd = process.cwd();
const candidates = [
cwd,
join(cwd, "server"),
join(cwd, "autumn", "server"),
];
for (const dir of candidates) {
if (existsSync(join(dir, "package.json"))) return dir;
}
// Fall back to first guess so dotenv silently no-ops if missing.
return cwd.includes("server") ? cwd : join(cwd, "server");
};
export const loadLocalEnv = ({ force = false }: { force?: boolean } = {}) => {
if (hasLoadedLocalEnv && !force) return;
hasLoadedLocalEnv = true;
const processDir = process.cwd();
const serverDir = processDir.includes("server")
? processDir
: join(processDir, "server");
const serverDir = resolveServerDir();
// Determine which env file to load based on ENV_FILE environment variable
// Defaults to .env if not specified

View File

@@ -4,6 +4,7 @@ import type {
LogRedisData,
LogRequestContext,
LogStripeEventContext,
LogTriggerContext,
LogWorkflowContext,
} from "./loggerTypes.js";
@@ -47,6 +48,16 @@ export const addWorkflowToLogs = ({
return logger.child({ context: { workflow: workflowContext } });
};
export const addTriggerToLogs = ({
logger,
triggerContext,
}: {
logger: Logger;
triggerContext: LogTriggerContext;
}): Logger => {
return logger.child({ context: { trigger: triggerContext } });
};
export const addRedisToLogs = ({
logger,
redisData,

View File

@@ -2,58 +2,84 @@ import { Writable } from "node:stream";
import pino from "pino";
import { getAwsTaskIdentity } from "@/external/aws/ecs/awsTaskIdentity.js";
// Custom log formatter for Bun compatibility
const createDevLogStream = () => {
const colors = {
reset: "\x1b[0m",
bright: "\x1b[1m",
dim: "\x1b[2m",
red: "\x1b[31m",
green: "\x1b[32m",
yellow: "\x1b[33m",
blue: "\x1b[34m",
magenta: "\x1b[35m",
cyan: "\x1b[36m",
white: "\x1b[37m",
gray: "\x1b[90m",
bgRed: "\x1b[41m",
};
/**
* Fields that don't render in the formatted dev/local console output
* (they still go to JSON sinks). Trim noise like trigger metadata,
* request envelopes, and high-cardinality structured data.
*/
const FORMATTED_LOG_EXCLUDE_FIELDS = new Set([
// pino housekeeping
"time",
"level",
"msg",
"pid",
"hostname",
// request / response envelopes
"req",
"res",
"statusCode",
"body",
"query",
"durationMs",
// app-context blocks (added via `addContextToLogs`)
"context",
"workflow",
"trigger",
"stripe_event",
"worker",
"extras",
"type",
// structured payloads
"data",
// AWS task identity (mixin)
"aws",
]);
const levelColors: Record<number | string, string> = {
// Numeric levels
10: colors.gray, // trace
20: colors.blue, // debug
30: colors.green, // info
40: colors.yellow, // warn
50: colors.red, // error
60: colors.bgRed, // fatal
// String levels
TRACE: colors.gray,
DEBUG: colors.blue,
INFO: colors.green,
WARN: colors.yellow,
ERROR: colors.red,
FATAL: colors.bgRed,
};
const colors = {
reset: "\x1b[0m",
bright: "\x1b[1m",
red: "\x1b[31m",
green: "\x1b[32m",
yellow: "\x1b[33m",
blue: "\x1b[34m",
white: "\x1b[37m",
gray: "\x1b[90m",
bgRed: "\x1b[41m",
};
const levelNames: Record<number | string, string> = {
// Numeric levels
10: "TRACE",
20: "DEBUG",
30: "INFO",
40: "WARN",
50: "ERROR",
60: "FATAL",
// String levels (pass through)
TRACE: "TRACE",
DEBUG: "DEBUG",
INFO: "INFO",
WARN: "WARN",
ERROR: "ERROR",
FATAL: "FATAL",
};
const levelColors: Record<number | string, string> = {
10: colors.gray,
20: colors.blue,
30: colors.green,
40: colors.yellow,
50: colors.red,
60: colors.bgRed,
TRACE: colors.gray,
DEBUG: colors.blue,
INFO: colors.green,
WARN: colors.yellow,
ERROR: colors.red,
FATAL: colors.bgRed,
};
return new Writable({
const levelNames: Record<number | string, string> = {
10: "TRACE",
20: "DEBUG",
30: "INFO",
40: "WARN",
50: "ERROR",
60: "FATAL",
TRACE: "TRACE",
DEBUG: "DEBUG",
INFO: "INFO",
WARN: "WARN",
ERROR: "ERROR",
FATAL: "FATAL",
};
/** Bun-friendly pino sink that prints `<ts> <LEVEL> <msg> <extras>`. */
const createDevLogStream = () =>
new Writable({
write(chunk, _encoding, callback) {
try {
const log = JSON.parse(chunk.toString());
@@ -66,44 +92,22 @@ const createDevLogStream = () => {
const levelName =
levelNames[level] || (typeof level === "string" ? level : "UNKNOWN");
// Format the message
let message = log.msg || "";
// Add any additional fields (excluding standard pino fields)
const excludeFields = [
"time",
"level",
"msg",
"pid",
"hostname",
"res",
"statusCode",
"worker",
"context",
"req",
"data",
"body",
"query",
"workflow",
"stripe_event",
"extras",
"type",
"durationMs",
];
const additionalFields = Object.keys(log)
.filter((key) => !excludeFields.includes(key))
.reduce((acc, key) => {
acc[key] = log[key];
return acc;
}, {} as any);
.filter((key) => !FORMATTED_LOG_EXCLUDE_FIELDS.has(key))
.reduce(
(acc, key) => {
acc[key] = log[key];
return acc;
},
{} as Record<string, unknown>,
);
if (Object.keys(additionalFields).length > 0) {
message += ` ${JSON.stringify(additionalFields, null, 2)}`;
}
// Format the final log line
const formattedLog = `${colors.gray}${timestamp}${colors.reset} ${levelColor}${colors.bright}${levelName}${colors.reset} ${message}\n`;
process.stdout.write(formattedLog);
@@ -115,47 +119,79 @@ const createDevLogStream = () => {
}
},
});
/**
* `mode: "dual"` is opt-in only — used by trigger.dev tasks so logs hit
* both stdout (trigger run UI) and axiom (long-term store).
*
* In dev/test the stdout stream uses the formatted dev sink so trigger
* pane lines look like server lines. In prod it stays raw JSON so
* trigger.dev's cloud UI gets structured logs.
*
* `mode: "default"` preserves existing prod behavior verbatim.
*/
export type InitLoggerOptions = {
mode?: "default" | "dual";
};
export const initLogger = () => {
// Create separate streams for console and HyperDX
const streams: pino.StreamEntry[] = [];
export const initLogger = (options: InitLoggerOptions = {}) => {
const { mode = "default" } = options;
const streams: pino.StreamEntry[] = [];
const isDev = process.env.NODE_ENV === "development";
const isTest = process.env.NODE_ENV === "test";
const isDevOrTest = isDev || isTest;
// Enable dev logging for development OR test environments
if (isDev || isTest) {
if (mode === "dual") {
streams.push({
level: "debug",
stream: createDevLogStream(),
level: isDevOrTest ? "debug" : "info",
stream: isDevOrTest ? createDevLogStream() : process.stdout,
});
}
if (process.env.AXIOM_TOKEN) {
streams.push({
level: "info",
stream: pino.transport({
target: "@axiomhq/pino",
options: {
dataset: "express",
token: process.env.AXIOM_TOKEN,
},
}),
});
}
} else {
// DEFAULT FLOW — exact prior behavior. DO NOT MODIFY.
if (isDev || isTest) {
streams.push({
level: "debug",
stream: createDevLogStream(),
});
}
if (process.env.AXIOM_TOKEN) {
streams.push({
level: "info",
stream: pino.transport({
target: "@axiomhq/pino",
options: {
dataset: "express",
token: process.env.AXIOM_TOKEN,
},
}),
});
}
if (process.env.AXIOM_TOKEN) {
streams.push({
level: "info",
stream: pino.transport({
target: "@axiomhq/pino",
options: {
dataset: "express",
token: process.env.AXIOM_TOKEN,
},
}),
});
}
// Fallback: if no streams configured, add dev stream anyway
if (streams.length === 0) {
streams.push({
level: "info",
stream: createDevLogStream(),
});
if (streams.length === 0) {
streams.push({
level: "info",
stream: createDevLogStream(),
});
}
}
const logger = pino(
{
level: isDev || isTest ? "debug" : "info",
level: isDev || isTest || mode === "dual" ? "debug" : "info",
// Tag every log line with this process's AWS task identity so Axiom
// can distinguish blue/green task sets. Returns {} until
// `resolveAwsTaskIdentity` finishes (~100ms after boot) and on
@@ -179,7 +215,6 @@ export const initLogger = () => {
},
},
},
// Use multistream to send logs to multiple destinations
pino.multistream(streams),
);

View File

@@ -52,6 +52,13 @@ export type LogWorkflowContext = {
name: string; // workflow / job name
};
/** trigger.dev run context — goes under context.trigger */
export type LogTriggerContext = {
run_id: string;
task_id: string;
attempt_number?: number;
};
/** Redis slow-command context - goes under context.redis.data (map field) */
export type LogRedisData = {
operation: string;

View File

@@ -1,12 +1,10 @@
/**
* Migrations V2 — add_items
* Migrations V2 — add_items end-to-end (integration).
*
* Phase 1 acceptance: a user can create a migration definition whose
* filter selects customers on a given plan and whose operation adds a
* plan item to their matching cusproducts.
*
* This test only verifies the CREATE path (storing the migration
* definition end-to-end). Execution / preview is phase 2+.
* Seeds 5 customers, parallel-attaches them all to a `free` product,
* creates a migration adding the Dashboard feature, and triggers
* `migrations.run`. Verifies the API call dispatches; downstream
* trigger.dev side effects are out of scope here.
*/
import { expect, test } from "bun:test";
@@ -15,57 +13,56 @@ import { products } from "@tests/utils/fixtures/products";
import { initScenario, s } from "@tests/utils/testInitUtils/initScenario";
import chalk from "chalk";
test.concurrent(
`${chalk.yellowBright("migrations-v2 add-items: create migration on free customers adding a feature item")}`,
async () => {
const customerId = "migrations-v2-add-items";
test.concurrent(`${chalk.yellowBright("migrations-v2 add-items: triggers run for 5 customers on free")}`, async () => {
const customerId = "migration-run-add-items";
const otherIds = [1, 2, 3, 4].map((i) => `${customerId}-c${i}`);
const free = products.base({ id: "free", items: [], isDefault: true });
// Default free product so the seeded customer auto-attaches to it.
const free = products.base({ id: "free", items: [], isDefault: true });
const { autumnV2_2 } = await initScenario({
const { autumnV1, autumnV2_2, customer, otherCustomers } = await initScenario(
{
customerId,
setup: [
s.customer({ paymentMethod: "success" }),
s.products({ list: [free] }),
s.otherCustomers(
otherIds.map((id) => ({ id, paymentMethod: "success" as const })),
),
],
});
actions: [],
},
);
// migrationId reuses the suffixed customerId so it's unique per concurrent
// test run without needing a Date.now() hack.
const created = await autumnV2_2.migrationsV2.create({
id: customerId,
filter: {
customer: {
plan: { plan_id: free.id },
},
expect(customer).toBeTruthy();
expect(otherCustomers.size).toBe(4);
// Explicit parallel attach of all 5 customers to free. `free.id` was
// mutated by initProductsV0 to include the prefix.
const allCustomerIds = [customerId, ...otherIds];
await Promise.all(
allCustomerIds.map((id) =>
autumnV1.attach({ customer_id: id, product_id: free.id }),
),
);
await autumnV2_2.migrationsV2.deleteAndCreate({
id: customerId,
filter: { customer: { plan: { plan_id: free.id } } },
operations: {
customer: {
update_plans: [
{
target: { plan_id: free.id },
add_items: [{ feature_id: TestFeature.Dashboard }],
},
],
},
operations: {
customer: {
update_plans: [
{
target: { plan_id: free.id },
add_items: [
{
feature_id: TestFeature.Dashboard,
},
],
},
],
},
},
});
},
});
expect(created.id).toBe(customerId);
expect(created.internal_id).toBeTruthy();
expect(created.filter?.customer?.plan).toMatchObject({ plan_id: free.id });
expect(created.operations?.customer?.update_plans?.[0]).toMatchObject({
target: { plan_id: free.id },
add_items: [{ feature_id: TestFeature.Dashboard }],
});
// Round-trip: list and confirm presence.
const { list } = await autumnV2_2.migrationsV2.list();
expect(list.some((m) => m.id === customerId)).toBe(true);
},
);
const runHandle = await autumnV2_2.migrationsV2.run({
id: customerId,
dry_run: false,
});
expect(runHandle.run_id).toBeTruthy();
expect(runHandle.dry_run).toBe(false);
});

View File

@@ -1094,25 +1094,27 @@ export async function initScenario({
customer = result.customer;
}
// 2.5. Other customers — share the primary's test clock.
// 2.5. Other customers — share the primary's test clock. Created in
// parallel since they're independent (each gets its own Stripe customer);
// the shared `testClockId` is read-only here so concurrency is safe.
const otherCustomersMap = new Map<string, OtherCustomerResult>();
for (const otherCusConfig of config.otherCustomers) {
const otherResult = await initCustomerV3({
ctx,
customerId: otherCusConfig.id,
customerData: otherCusConfig.data,
attachPm: otherCusConfig.paymentMethod,
withTestClock: false,
...(testClockId ? { existingTestClockId: testClockId } : {}),
withDefault: false,
defaultGroup: productPrefix,
skipWebhooks: config.skipWebhooks,
});
otherCustomersMap.set(otherCusConfig.id, {
id: otherCusConfig.id,
customer: otherResult.customer,
});
}
const otherCustomersResults = await Promise.all(
config.otherCustomers.map(async (otherCusConfig) => {
const otherResult = await initCustomerV3({
ctx,
customerId: otherCusConfig.id,
customerData: otherCusConfig.data,
attachPm: otherCusConfig.paymentMethod,
withTestClock: false,
...(testClockId ? { existingTestClockId: testClockId } : {}),
withDefault: false,
defaultGroup: productPrefix,
skipWebhooks: config.skipWebhooks,
});
return { id: otherCusConfig.id, customer: otherResult.customer };
}),
);
for (const r of otherCustomersResults) otherCustomersMap.set(r.id, r);
// 3. Create autumn clients
const autumnV0 = new AutumnInt({

View File

@@ -1,123 +0,0 @@
import { type SQL, sql } from "drizzle-orm";
import type { CustomerFilter } from "../filters/customerFilter.js";
import { compileFilter } from "./compileFilter.js";
import type { ResolutionContext } from "./filterToIr/resolutionContext.js";
/**
* Top-level query builders for customer-rooted migration filters.
*
* `compileFilter` returns the full WHERE expression (including org/env
* pushdown via the registry's ambient predicates). These helpers wrap it
* into a complete, parameterized Drizzle `SQL` with cursor-based
* pagination for iteration over large customer sets.
*/
export const DEFAULT_BATCH_SIZE = 10_000;
/** Convert the compiler's `{ sql, params }` output to a Drizzle SQL chunk. */
function rawWithParamsToDrizzle({
sql: raw,
params,
}: {
sql: string;
params: readonly unknown[];
}): SQL {
const parts = raw.split("?");
if (parts.length - 1 !== params.length)
throw new Error(
`Placeholder/param count mismatch: ${parts.length - 1} placeholders vs ${params.length} params`,
);
const chunks: SQL[] = [];
for (let i = 0; i < parts.length; i++) {
chunks.push(sql.raw(parts[i]));
if (i < params.length) chunks.push(sql`${params[i]}`);
}
return sql.join(chunks, sql.raw(""));
}
type BuildArgs = {
orgId: string;
env: string;
filter: CustomerFilter;
ctx: ResolutionContext;
};
const compileWhere = ({ orgId, env, filter, ctx }: BuildArgs): SQL =>
rawWithParamsToDrizzle(
compileFilter({ filter, ctx, ambient: { orgId, env } }),
);
/** Full SELECT. Returns `{ internal_id, id }` rows. */
export function buildCustomerSelect({
orgId,
env,
filter,
ctx,
limit,
afterInternalId,
}: BuildArgs & { limit?: number; afterInternalId?: string }): SQL {
const where = compileWhere({ orgId, env, filter, ctx });
const cursor = afterInternalId
? sql`AND c.internal_id > ${afterInternalId}`
: sql``;
const limitClause = limit !== undefined ? sql`LIMIT ${limit}` : sql``;
return sql`
SELECT c.internal_id, c.id
FROM customers c
WHERE (${where}) ${cursor}
ORDER BY c.internal_id
${limitClause}
`;
}
/** COUNT(*) applying the same filter. */
export function buildCustomerCount({
orgId,
env,
filter,
ctx,
}: BuildArgs): SQL {
const where = compileWhere({ orgId, env, filter, ctx });
return sql`
SELECT COUNT(*)::bigint AS count
FROM customers c
WHERE (${where})
`;
}
/**
* Iterate matching customers in batches (default 10k per step) using
* keyset pagination on `c.internal_id`. Yields one batch at a time so
* callers can stream-process without loading the full result set.
*/
export async function* iterateCustomers({
db,
orgId,
env,
filter,
ctx,
batchSize = DEFAULT_BATCH_SIZE,
}: BuildArgs & {
db: { execute: (query: SQL) => Promise<unknown> };
batchSize?: number;
}): AsyncGenerator<Array<{ internal_id: string; id: string | null }>> {
let cursor: string | undefined;
while (true) {
const query = buildCustomerSelect({
orgId,
env,
filter,
ctx,
limit: batchSize,
afterInternalId: cursor,
});
const rows = (await db.execute(query)) as unknown as Array<{
internal_id: string;
id: string | null;
}>;
if (rows.length === 0) return;
yield rows;
if (rows.length < batchSize) return;
cursor = rows[rows.length - 1].internal_id;
}
}

View File

@@ -1,4 +1,3 @@
export * from "./buildCustomerQuery.js";
export * from "./compileFilter.js";
export * from "./compilePlanFilter.js";
export * from "./filterToIr/index.js";

View File

@@ -38,6 +38,8 @@ import { migrationErrorRelations } from "../models/migrationModels/migrationErro
import { migrationErrors } from "../models/migrationModels/migrationErrorTable.js";
// Migration Tables
import { migrationJobs } from "../models/migrationModels/migrationJobTable.js";
// Migrations V2
import { migrations } from "../models/migrationV2Models/migrationTable.js";
/* RELATIONS */
import { organizationsRelations } from "../models/orgModels/orgRelations.js";
import { organizations } from "../models/orgModels/orgTable.js";
@@ -88,76 +90,77 @@ import {
} from "./auth-schema.js";
export {
// Tables
organizations,
account,
actions,
apiKeyRelations,
apiKeys,
autoTopupLimitStates as autoTopupLimits,
chatResults,
checkouts,
freeTrials,
entitlements,
prices,
features,
products,
customerProducts,
customerPrices,
checkoutsRelations,
customerEntitlements,
invoices,
invoiceLineItems,
customerEntitlementsRelations,
customerPrices,
customerPricesRelations,
customerProducts,
customerProductsRelations,
customers,
autoTopupLimitStates as autoTopupLimits,
customersRelations,
entities,
apiKeys,
metadata,
subscriptions,
rewards,
rewardPrograms,
referralCodes,
rewardRedemptions,
migrationJobs,
migrationErrors,
actions,
entitiesRelations,
entitlements,
entitlementsRelations,
events,
replaceables,
rollovers,
schedules,
schedulePhases,
vercelResources,
revenuecatMappings as revcatMappings,
// Auth
user,
session,
account,
verification,
member,
featureRelations,
features,
freeTrialRelations,
freeTrials,
invitation,
inviteRelations,
invoiceLineItems,
invoiceRelations,
invoices,
// OAuth Provider
jwks,
oauthClient,
member,
memberRelations,
metadata,
migrationErrorRelations,
migrationErrors,
migrationJobs,
migrations,
oauthAccessToken,
oauthRefreshToken,
oauthClient,
oauthConsent,
oauthRefreshToken,
// Tables
organizations,
// Relations
organizationsRelations,
entitlementsRelations,
featureRelations,
priceRelations,
prices,
productRelations,
freeTrialRelations,
customerProductsRelations,
customerPricesRelations,
customerEntitlementsRelations,
customersRelations,
entitiesRelations,
apiKeyRelations,
rewardProgramRelations,
products,
referralCodeRelations,
rewardRedemptionRelations,
migrationErrorRelations,
referralCodes,
replaceableRelations,
invoiceRelations,
replaceables,
revenuecatMappings as revcatMappings,
rewardProgramRelations,
rewardPrograms,
rewardRedemptionRelations,
rewardRedemptions,
rewards,
rolloverRelations,
checkoutsRelations,
rollovers,
schedulePhases,
schedules,
session,
subscriptions,
// Auth
user,
// Auth Relations
userRelations,
memberRelations,
inviteRelations,
vercelResources,
verification,
};

78
trigger.config.ts Normal file
View File

@@ -0,0 +1,78 @@
import {
additionalPackages,
aptGet,
syncEnvVars,
} from "@trigger.dev/build/extensions/core";
import { defineConfig } from "@trigger.dev/sdk/v3";
import { fetchInfisicalSecretsFromEnv } from "./server/src/external/infisical/fetchInfisicalSecrets.js";
export default defineConfig({
project: "proj_cwiutfmpdzfcshxevkok",
runtime: "node",
logLevel: "log",
maxDuration: 3600,
retries: {
enabledInDev: true,
default: {
maxAttempts: 3,
minTimeoutInMs: 1000,
maxTimeoutInMs: 10000,
factor: 2,
randomize: true,
},
},
dirs: ["server/src/trigger"],
build: {
// Native / heavy deps stay external — bundling them inflates the deploy
// and breaks platform-specific binaries (pg, ioredis, etc.).
external: [
"@aws-sdk/client-s3",
"@google-cloud/bigquery",
"ioredis",
"pino",
"@axiomhq/pino",
"drizzle-orm",
"postgres",
"pg",
"zod",
],
extensions: [
// Server runtime imports `*.lua` as raw text via Bun's loader. esbuild
// (used by the trigger build) doesn't see bunfig — register a plugin
// that reads .lua files as text so the same imports work post-bundle.
{
name: "lua-text-loader",
onBuildStart: async (context) => {
context.registerPlugin({
name: "lua-text-loader-plugin",
setup(build) {
build.onLoad({ filter: /\.lua$/ }, async (args) => {
const { readFileSync } = await import("node:fs");
return {
contents: readFileSync(args.path, "utf-8"),
loader: "text",
};
});
},
});
},
},
aptGet({ packages: ["pkg-config", "liblzma-dev"] }),
additionalPackages({
packages: [
"@axiomhq/pino",
"postgres",
"pg",
"drizzle-orm",
"ioredis",
"zod",
],
}),
// In DEV: `bun d` runs `bunx trigger.dev dev` under
// `infisical run --env=dev --recursive --`, so the trigger CLI
// inherits secrets from process.env directly. `syncEnvVars` only
// runs at DEPLOY time (pushes Infisical secrets to trigger cloud).
syncEnvVars(async (ctx) => fetchInfisicalSecretsFromEnv(ctx.env)),
],
},
});

View File

@@ -67,6 +67,7 @@
"date-fns": "^3.6.0",
"decimal.js": "^10.5.0",
"input-otp": "^1.4.2",
"json5": "^2.2.3",
"lodash": "^4.17.21",
"lucide-react": "^0.562.0",
"motion": "^12.26.1",

View File

@@ -1,18 +1,66 @@
import type { Migration } from "@autumn/shared";
import { useQuery } from "@tanstack/react-query";
import type { MigrationFilter } from "@autumn/shared/api/migrations/filters/migrationFilter.js";
import type { Operations } from "@autumn/shared/api/migrations/operations/operations.js";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { useQueryKeyFactory } from "@/hooks/common/useQueryKeyFactory";
import { MigrationService } from "@/services/MigrationService";
import { useAxiosInstance } from "@/services/useAxiosInstance";
export const useMigrationsQuery = () => {
const axiosInstance = useAxiosInstance();
const buildKey = useQueryKeyFactory();
const queryClient = useQueryClient();
const queryKey = buildKey(["migrations"]);
const { data, isLoading, error, refetch } = useQuery<{
list: Migration[];
}>({
queryKey: buildKey(["migrations"]),
queryFn: () => MigrationService.list(axiosInstance),
const { data, isLoading, error, refetch } = useQuery<{ list: Migration[] }>({
queryKey,
queryFn: async () => {
const { data } = await axiosInstance.post<{ list: Migration[] }>(
"/migrations.list",
);
return data;
},
});
const invalidate = () => queryClient.invalidateQueries({ queryKey });
const createMutation = useMutation({
mutationFn: async (body: { id: string }) => {
const { data } = await axiosInstance.post<Migration>(
"/migrations.create",
body,
);
return data;
},
onSuccess: invalidate,
});
const updateMutation = useMutation({
mutationFn: async (body: {
id: string;
updates: {
id?: string;
filter?: MigrationFilter | null;
operations?: Operations | null;
};
}) => {
const { data } = await axiosInstance.post<Migration>(
"/migrations.update",
body,
);
return data;
},
onSuccess: invalidate,
});
const runMutation = useMutation({
mutationFn: async (body: { id: string; dry_run?: boolean }) => {
const { data } = await axiosInstance.post<{
migration_id: string;
dry_run: boolean;
run_id: string;
}>("/migrations.run", body);
return data;
},
});
return {
@@ -20,5 +68,11 @@ export const useMigrationsQuery = () => {
isLoading,
error,
refetch,
createMigration: createMutation.mutateAsync,
isCreating: createMutation.isPending,
updateMigration: updateMutation.mutateAsync,
isUpdating: updateMutation.isPending,
runMigration: runMutation.mutateAsync,
isRunning: runMutation.isPending,
};
};

View File

@@ -1,19 +0,0 @@
import type { Migration } from "@autumn/shared";
import type { AxiosInstance } from "axios";
export const MigrationService = {
list: async (axiosInstance: AxiosInstance) => {
const { data } = await axiosInstance.post<{ list: Migration[] }>(
"/migrations.list",
);
return data;
},
create: async (axiosInstance: AxiosInstance, body: { id: string }) => {
const { data } = await axiosInstance.post<Migration>(
"/migrations.create",
body,
);
return data;
},
};

View File

@@ -2,8 +2,10 @@ import { MigrationListTable } from "./migration-list/MigrationListTable";
export const MigrationsView = () => {
return (
<div className="h-fit max-h-full px-4 sm:px-10">
<MigrationListTable />
<div className="flex flex-col gap-4 h-fit relative w-full pb-8 max-w-5xl mx-auto pt-4 sm:pt-8">
<div className="h-fit max-h-full px-4 sm:px-10">
<MigrationListTable />
</div>
</div>
);
};

View File

@@ -10,8 +10,6 @@ import {
} from "@/components/v2/sheets/SharedSheetComponents";
import { Sheet, SheetContent } from "@/components/v2/sheets/Sheet";
import { useMigrationsQuery } from "@/hooks/queries/useMigrationsQuery";
import { MigrationService } from "@/services/MigrationService";
import { useAxiosInstance } from "@/services/useAxiosInstance";
import { getBackendErr } from "@/utils/genUtils";
function CreateMigrationSheet({
@@ -23,15 +21,13 @@ function CreateMigrationSheet({
onOpenChange?: (open: boolean) => void;
onSuccess?: (migrationId: string) => void;
} = {}) {
const [loading, setLoading] = useState(false);
const [internalOpen, setInternalOpen] = useState(false);
const [id, setId] = useState("");
const open = controlledOpen !== undefined ? controlledOpen : internalOpen;
const setOpen = controlledOnOpenChange || setInternalOpen;
const axiosInstance = useAxiosInstance();
const { refetch } = useMigrationsQuery();
const { createMigration, isCreating } = useMigrationsQuery();
const handleCreateMigration = async () => {
if (!id.trim()) {
@@ -39,12 +35,8 @@ function CreateMigrationSheet({
return;
}
setLoading(true);
try {
const created = await MigrationService.create(axiosInstance, {
id: id.trim(),
});
await refetch();
const created = await createMigration({ id: id.trim() });
toast.success("Migration created");
setOpen(false);
onSuccess?.(created.id);
@@ -52,8 +44,6 @@ function CreateMigrationSheet({
toast.error(
getBackendErr(error as AxiosError, "Failed to create migration"),
);
} finally {
setLoading(false);
}
};
@@ -94,7 +84,7 @@ function CreateMigrationSheet({
className="w-full"
onClick={handleCreateMigration}
metaShortcut="enter"
isLoading={loading}
isLoading={isCreating}
>
Create migration
</ShortcutButton>

View File

@@ -0,0 +1,216 @@
import type { Migration } from "@autumn/shared";
import Editor, { type Monaco } from "@monaco-editor/react";
import { PlayIcon } from "@phosphor-icons/react";
import type { AxiosError } from "axios";
import JSON5 from "json5";
import { useEffect, useMemo, useState } from "react";
import { toast } from "sonner";
import { Button } from "@/components/v2/buttons/Button";
import { ShortcutButton } from "@/components/v2/buttons/ShortcutButton";
import {
SheetFooter,
SheetHeader,
SheetSection,
} from "@/components/v2/sheets/SharedSheetComponents";
import { Sheet, SheetContent } from "@/components/v2/sheets/Sheet";
import { useMigrationsQuery } from "@/hooks/queries/useMigrationsQuery";
import { getBackendErr } from "@/utils/genUtils";
const EMPTY_FILTER = `{
customer: {},
}
`;
const EMPTY_OPERATIONS = `{
customer: {},
}
`;
function stringify(value: unknown, fallback: string) {
if (value === null || value === undefined) return fallback;
// JSON5.stringify with no quotes around safe identifier keys
return JSON5.stringify(value, { space: 2, quote: '"' });
}
function tryParse(text: string) {
const trimmed = text.trim();
if (!trimmed) return { value: null as unknown, error: null as string | null };
try {
return { value: JSON5.parse(trimmed), error: null };
} catch (err) {
return {
value: null,
error: err instanceof Error ? err.message : "Invalid syntax",
};
}
}
// Disable TS diagnostics so bare object literals like `{ a: 1 }` don't squiggle.
function configureMonaco(monaco: Monaco) {
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
noSemanticValidation: true,
noSyntaxValidation: true,
noSuggestionDiagnostics: true,
});
}
const EDITOR_OPTIONS = {
minimap: { enabled: false },
scrollBeyondLastLine: false,
fontSize: 12,
tabSize: 2,
wordWrap: "on" as const,
formatOnPaste: true,
formatOnType: true,
glyphMargin: false,
folding: false,
lineNumbersMinChars: 2,
lineDecorationsWidth: 4,
padding: { top: 8, bottom: 8 },
};
function EditMigrationSheet({
migration,
open,
onOpenChange,
}: {
migration: Migration | null;
open: boolean;
onOpenChange: (open: boolean) => void;
}) {
const { updateMigration, isUpdating, runMigration, isRunning } =
useMigrationsQuery();
const [filterText, setFilterText] = useState("");
const [operationsText, setOperationsText] = useState("");
useEffect(() => {
if (!migration) return;
setFilterText(stringify(migration.filter, EMPTY_FILTER));
setOperationsText(stringify(migration.operations, EMPTY_OPERATIONS));
}, [migration]);
const filterParsed = useMemo(() => tryParse(filterText), [filterText]);
const operationsParsed = useMemo(
() => tryParse(operationsText),
[operationsText],
);
const canSave = !filterParsed.error && !operationsParsed.error;
const handleSave = async () => {
if (!migration || !canSave) return;
try {
await updateMigration({
id: migration.id,
updates: {
filter: filterParsed.value as null,
operations: operationsParsed.value as null,
},
});
toast.success("Migration saved");
onOpenChange(false);
} catch (error) {
toast.error(
getBackendErr(error as AxiosError, "Failed to save migration"),
);
}
};
const handleRun = async () => {
if (!migration) return;
try {
const result = await runMigration({ id: migration.id, dry_run: true });
toast.success(`Migration triggered (run ${result.run_id})`);
} catch (error) {
toast.error(
getBackendErr(error as AxiosError, "Failed to run migration"),
);
}
};
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent className="flex flex-col overflow-hidden sm:max-w-[640px]">
<SheetHeader
title={migration?.id ?? "Migration"}
description="Edit the filter that selects rows and the operations applied to each match. Object literal syntax (TypeScript-style) is supported."
action={
<Button
variant="secondary"
size="sm"
onClick={handleRun}
isLoading={isRunning}
disabled={!migration?.operations && !operationsText.trim()}
>
<PlayIcon size={14} weight="fill" />
Run
</Button>
}
/>
<div className="flex-1 overflow-y-auto">
<SheetSection title="Filter">
<div className="rounded-md border border-border overflow-hidden">
<Editor
height="240px"
language="typescript"
value={filterText}
onChange={(value) => setFilterText(value ?? "")}
beforeMount={configureMonaco}
options={EDITOR_OPTIONS}
theme="vs-dark"
/>
</div>
{filterParsed.error && (
<div className="mt-2 text-xs text-red-500">
{filterParsed.error}
</div>
)}
</SheetSection>
<SheetSection title="Operations" withSeparator={false}>
<div className="rounded-md border border-border overflow-hidden">
<Editor
height="280px"
language="typescript"
value={operationsText}
onChange={(value) => setOperationsText(value ?? "")}
beforeMount={configureMonaco}
options={EDITOR_OPTIONS}
theme="vs-dark"
/>
</div>
{operationsParsed.error && (
<div className="mt-2 text-xs text-red-500">
{operationsParsed.error}
</div>
)}
</SheetSection>
</div>
<SheetFooter>
<ShortcutButton
variant="secondary"
className="w-full"
onClick={() => onOpenChange(false)}
singleShortcut="escape"
>
Cancel
</ShortcutButton>
<ShortcutButton
className="w-full"
onClick={handleSave}
metaShortcut="enter"
isLoading={isUpdating}
disabled={!canSave}
>
Save
</ShortcutButton>
</SheetFooter>
</SheetContent>
</Sheet>
);
}
export default EditMigrationSheet;

View File

@@ -1,14 +1,18 @@
import type { Migration } from "@autumn/shared";
import { ArrowsClockwiseIcon } from "@phosphor-icons/react";
import { useMemo } from "react";
import { useMemo, useState } from "react";
import { Table } from "@/components/general/table";
import { EmptyState } from "@/components/v2/empty-states/EmptyState";
import { useMigrationsQuery } from "@/hooks/queries/useMigrationsQuery";
import { useProductTable } from "@/views/products/hooks/useProductTable";
import EditMigrationSheet from "../components/EditMigrationSheet";
import { createMigrationListColumns } from "./MigrationListColumns";
import { MigrationListCreateButton } from "./MigrationListCreateButton";
export function MigrationListTable() {
const { migrations, isLoading } = useMigrationsQuery();
const [selected, setSelected] = useState<Migration | null>(null);
const [sheetOpen, setSheetOpen] = useState(false);
const columns = useMemo(() => createMigrationListColumns(), []);
@@ -23,6 +27,11 @@ export function MigrationListTable() {
const hasRows = table.getRowModel().rows.length > 0;
const handleRowClick = (row: Migration) => {
setSelected(row);
setSheetOpen(true);
};
if (!isLoading && !hasRows) {
return (
<EmptyState
@@ -33,40 +42,51 @@ export function MigrationListTable() {
}
return (
<Table.Provider
config={{
table,
numberOfColumns: columns.length,
enableSorting: false,
isLoading,
rowClassName: "h-10",
}}
>
<Table.Toolbar>
<div className="flex w-full justify-between items-center">
<Table.Heading>
<ArrowsClockwiseIcon
size={16}
weight="fill"
className="text-subtle"
/>
Migrations
</Table.Heading>
<Table.Actions>
<div className="flex items-center gap-2">
<MigrationListCreateButton />
</div>
</Table.Actions>
<>
<Table.Provider
config={{
table,
numberOfColumns: columns.length,
enableSorting: false,
isLoading,
rowClassName: "h-10",
onRowClick: handleRowClick,
}}
>
<Table.Toolbar>
<div className="flex w-full justify-between items-center">
<Table.Heading>
<ArrowsClockwiseIcon
size={16}
weight="fill"
className="text-subtle"
/>
Migrations
</Table.Heading>
<Table.Actions>
<div className="flex items-center gap-2">
<MigrationListCreateButton />
</div>
</Table.Actions>
</div>
</Table.Toolbar>
<div>
<Table.Container>
<Table.Content>
<Table.Header />
<Table.Body />
</Table.Content>
</Table.Container>
</div>
</Table.Toolbar>
<div>
<Table.Container>
<Table.Content>
<Table.Header />
<Table.Body />
</Table.Content>
</Table.Container>
</div>
</Table.Provider>
</Table.Provider>
<EditMigrationSheet
migration={selected}
open={sheetOpen}
onOpenChange={(open) => {
setSheetOpen(open);
if (!open) setSelected(null);
}}
/>
</>
);
}