diff --git a/.superset/setup.sh b/.superset/setup.sh index e2e488283..458364002 100755 --- a/.superset/setup.sh +++ b/.superset/setup.sh @@ -86,6 +86,13 @@ for sh_file in "$ROOT_PATH"/*.sh; do fi done +# Copy run.sh explicitly from root +if [ -f "$ROOT_PATH/run.sh" ]; then + cp "$ROOT_PATH/run.sh" "run.sh" + chmod +x "run.sh" + echo "Copied run.sh" +fi + # Copy all .sh files from server/ echo "Copying shell scripts from server directory..." if [ -d "$ROOT_PATH/server" ]; then diff --git a/bun.lock b/bun.lock index 74401e48d..0eb10195f 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,6 @@ { "lockfileVersion": 1, - "configVersion": 0, + "configVersion": 1, "workspaces": { "": { "name": "autumn", @@ -315,15 +315,15 @@ "stripe": "19.3.0-beta.1", }, "packages": { - "@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.18", "", { "dependencies": { "@ai-sdk/provider": "3.0.4", "@ai-sdk/provider-utils": "4.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-eusJVRK3gtXa7ENen9Im5bxwE6tNCPDF/poXyK45u7eLNx1I7aPbfseLqznyh+tjhx6AOK7FTUBV1d36MERPrA=="], + "@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.23", "", { "dependencies": { "@ai-sdk/provider": "3.0.5", "@ai-sdk/provider-utils": "4.0.9" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-mu9djDW2kiJS/ihH5BwGy2c/zwSlcTjx1NWPvY/Ug12SWToqzozSyd1EIXRlfXyfwzL2CWrqMNyybqi9OVDXgg=="], - "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.18", "", { "dependencies": { "@ai-sdk/provider": "3.0.4", "@ai-sdk/provider-utils": "4.0.8", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-WlJ7UkBDYgv+5q9UpGZfbnPrrW3KVnY96lRLaXs8nXy7Ea3QTWXf4Jw43jFzMSVkH4Zhhf0s9ExmG+CiyEBY5A=="], + "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.22", "", { "dependencies": { "@ai-sdk/provider": "3.0.5", "@ai-sdk/provider-utils": "4.0.9", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-NgnlY73JNuooACHqUIz5uMOEWvqR1MMVbb2soGLMozLY1fgwEIF5iJFDAGa5/YArlzw2ATVU7zQu7HkR/FUjgA=="], - "@ai-sdk/provider": ["@ai-sdk/provider@3.0.4", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5KXyBOSEX+l67elrEa+wqo/LSsSTtrPj9Uoh3zMbe/ceQX4ucHI3b9nUEfNkGF3Ry1svv90widAt+aiKdIJasQ=="], + "@ai-sdk/provider": ["@ai-sdk/provider@3.0.5", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-2Xmoq6DBJqmSl80U6V9z5jJSJP7ehaJJQMy2iFUqTay06wdCqTnPVBBQbtEL8RCChenL+q5DC5H5WzU3vV3v8w=="], - "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.8", "", { "dependencies": { "@ai-sdk/provider": "3.0.4", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ns9gN7MmpI8vTRandzgz+KK/zNMLzhrriiKECMt4euLtQFSBgNfydtagPOX4j4pS1/3KvHF6RivhT3gNQgBZsg=="], + "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.9", "", { "dependencies": { "@ai-sdk/provider": "3.0.5", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-bB4r6nfhBOpmoS9mePxjRoCy+LnzP3AfhyMGCkGL4Mn9clVNlqEeKj26zEKEtB6yoSVcT1IQ0Zh9fytwMCDnow=="], - "@ai-sdk/react": ["@ai-sdk/react@3.0.46", "", { "dependencies": { "@ai-sdk/provider-utils": "4.0.8", "ai": "6.0.44", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" } }, "sha512-0WVvenCr8RymOG3tVP3BESkSjanqAO5yv1pYEz8T80iVI3YpGH19a3lLDiIU58kJm+SE8PIBNKfteXPHAQL0AQ=="], + "@ai-sdk/react": ["@ai-sdk/react@3.0.51", "", { "dependencies": { "@ai-sdk/provider-utils": "4.0.9", "ai": "6.0.49", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" } }, "sha512-7nmCwEJM52NQZB4/ED8qJ4wbDg7EEWh94qJ7K9GSJxD6sWF3GOKrRZ5ivm4qNmKhY+JfCxCAxfghGY5mTKOsxw=="], "@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.3", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-jsElTJ0sQ4wHRz+C45tfect76BwbTbgkgKByOzpCN9xG61N5V6u/glvg1CsNJhq2xJIFpKHSwG3D2wPPuEYOrQ=="], @@ -341,7 +341,7 @@ "@amplitude/experiment-core": ["@amplitude/experiment-core@0.12.0", "", { "dependencies": { "js-base64": "^3.7.5" } }, "sha512-EiLLxcyJD8T3GFsMPxBfWx9n9fBw6rC0RJwccPXLzResE0HnGZZpVWF86ZndnYmEMD1lUUjWi41N1ymEzodI5w=="], - "@amplitude/experiment-js-client": ["@amplitude/experiment-js-client@1.20.1", "", { "dependencies": { "@amplitude/analytics-connector": "^1.6.4", "@amplitude/experiment-core": "^0.12.0", "@amplitude/ua-parser-js": "^0.7.31", "base64-js": "1.5.1", "unfetch": "4.1.0" } }, "sha512-pNcEPVBxhn13x85aDrTo8bEnBvG97v2P7cObMi8snLxNaUAKatkITPC//I0+daU7zwX2M3c+yeGmrU0Fmk08OA=="], + "@amplitude/experiment-js-client": ["@amplitude/experiment-js-client@1.20.3", "", { "dependencies": { "@amplitude/analytics-connector": "^1.6.4", "@amplitude/experiment-core": "^0.12.0", "@amplitude/ua-parser-js": "^0.7.31", "base64-js": "1.5.1", "unfetch": "4.1.0" } }, "sha512-rv778CbjisEaYcHW9UyFcSQu1TqJPqR+lo8yYPZ50UKDGQfK6hKtWY4zPKRdwnr49ZF0Z+Bm/haYBNNP0aAW8Q=="], "@amplitude/plugin-autocapture-browser": ["@amplitude/plugin-autocapture-browser@1.18.0-zen-plus-zoning.2", "", { "dependencies": { "@amplitude/analytics-core": "2.32.0-zen-plus-zoning.2", "tslib": "^2.4.1" } }, "sha512-68pbfyR7SxEvCrCnu4E8KSkr8sWXRxA3O3eFOyatmqSXqCJmyR7Pu7MtKZJy0nsfFaWuRg+vDL9jBiPxSR18OQ=="], @@ -409,11 +409,11 @@ "@aws-sdk/client-cognito-identity": ["@aws-sdk/client-cognito-identity@3.600.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/client-sso-oidc": "3.600.0", "@aws-sdk/client-sts": "3.600.0", "@aws-sdk/core": "3.598.0", "@aws-sdk/credential-provider-node": "3.600.0", "@aws-sdk/middleware-host-header": "3.598.0", "@aws-sdk/middleware-logger": "3.598.0", "@aws-sdk/middleware-recursion-detection": "3.598.0", "@aws-sdk/middleware-user-agent": "3.598.0", "@aws-sdk/region-config-resolver": "3.598.0", "@aws-sdk/types": "3.598.0", "@aws-sdk/util-endpoints": "3.598.0", "@aws-sdk/util-user-agent-browser": "3.598.0", "@aws-sdk/util-user-agent-node": "3.598.0", "@smithy/config-resolver": "^3.0.2", "@smithy/core": "^2.2.1", "@smithy/fetch-http-handler": "^3.0.2", "@smithy/hash-node": "^3.0.1", "@smithy/invalid-dependency": "^3.0.1", "@smithy/middleware-content-length": "^3.0.1", "@smithy/middleware-endpoint": "^3.0.2", "@smithy/middleware-retry": "^3.0.4", "@smithy/middleware-serde": "^3.0.1", "@smithy/middleware-stack": "^3.0.1", "@smithy/node-config-provider": "^3.1.1", "@smithy/node-http-handler": "^3.0.1", "@smithy/protocol-http": "^4.0.1", "@smithy/smithy-client": "^3.1.2", "@smithy/types": "^3.1.0", "@smithy/url-parser": "^3.0.1", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", "@smithy/util-defaults-mode-browser": "^3.0.4", "@smithy/util-defaults-mode-node": "^3.0.4", "@smithy/util-endpoints": "^2.0.2", "@smithy/util-middleware": "^3.0.1", "@smithy/util-retry": "^3.0.1", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-8dYsnDLiD0rjujRiZZl0E57heUkHqMSFZHBi0YMs57SM8ODPxK3tahwDYZtS7bqanvFKZwGy+o9jIcij7jBOlA=="], - "@aws-sdk/client-iam": ["@aws-sdk/client-iam@3.972.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.972.0", "@aws-sdk/credential-provider-node": "3.972.0", "@aws-sdk/middleware-host-header": "3.972.0", "@aws-sdk/middleware-logger": "3.972.0", "@aws-sdk/middleware-recursion-detection": "3.972.0", "@aws-sdk/middleware-user-agent": "3.972.0", "@aws-sdk/region-config-resolver": "3.972.0", "@aws-sdk/types": "3.972.0", "@aws-sdk/util-endpoints": "3.972.0", "@aws-sdk/util-user-agent-browser": "3.972.0", "@aws-sdk/util-user-agent-node": "3.972.0", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.20.6", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.7", "@smithy/middleware-retry": "^4.4.23", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.10.8", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.22", "@smithy/util-defaults-mode-node": "^4.2.25", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-2Bla8ElNZopJtnvtAPCdwWusmJhHFcdTlYUbZjfmT5qZIu3uaIlrJrY0+azGR3quhIXKxB8rUuo689ohe3VYvQ=="], + "@aws-sdk/client-iam": ["@aws-sdk/client-iam@3.975.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.1", "@aws-sdk/credential-provider-node": "^3.972.1", "@aws-sdk/middleware-host-header": "^3.972.1", "@aws-sdk/middleware-logger": "^3.972.1", "@aws-sdk/middleware-recursion-detection": "^3.972.1", "@aws-sdk/middleware-user-agent": "^3.972.2", "@aws-sdk/region-config-resolver": "^3.972.1", "@aws-sdk/types": "^3.973.0", "@aws-sdk/util-endpoints": "3.972.0", "@aws-sdk/util-user-agent-browser": "^3.972.1", "@aws-sdk/util-user-agent-node": "^3.972.1", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.21.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.11", "@smithy/middleware-retry": "^4.4.27", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.10.12", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.26", "@smithy/util-defaults-mode-node": "^4.2.29", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-CRl7uwKWq16fc2cV4Arh48G9ktvr1SrLj0Hy3/FbvQ1YuACwQfcH+JJ9kPAeRTgpgnllPtPQyeNwbC5MLo+UaQ=="], - "@aws-sdk/client-scheduler": ["@aws-sdk/client-scheduler@3.972.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.972.0", "@aws-sdk/credential-provider-node": "3.972.0", "@aws-sdk/middleware-host-header": "3.972.0", "@aws-sdk/middleware-logger": "3.972.0", "@aws-sdk/middleware-recursion-detection": "3.972.0", "@aws-sdk/middleware-user-agent": "3.972.0", "@aws-sdk/region-config-resolver": "3.972.0", "@aws-sdk/types": "3.972.0", "@aws-sdk/util-endpoints": "3.972.0", "@aws-sdk/util-user-agent-browser": "3.972.0", "@aws-sdk/util-user-agent-node": "3.972.0", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.20.6", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.7", "@smithy/middleware-retry": "^4.4.23", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.10.8", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.22", "@smithy/util-defaults-mode-node": "^4.2.25", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-7N8U13ZO1VgNVJDnHj+HXkLLeDOml/varnfuYU2LlYIzCkOcPnCj136Uhr9IHU9EeEDWZSpT01iXOylYyLUITQ=="], + "@aws-sdk/client-scheduler": ["@aws-sdk/client-scheduler@3.975.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.1", "@aws-sdk/credential-provider-node": "^3.972.1", "@aws-sdk/middleware-host-header": "^3.972.1", "@aws-sdk/middleware-logger": "^3.972.1", "@aws-sdk/middleware-recursion-detection": "^3.972.1", "@aws-sdk/middleware-user-agent": "^3.972.2", "@aws-sdk/region-config-resolver": "^3.972.1", "@aws-sdk/types": "^3.973.0", "@aws-sdk/util-endpoints": "3.972.0", "@aws-sdk/util-user-agent-browser": "^3.972.1", "@aws-sdk/util-user-agent-node": "^3.972.1", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.21.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.11", "@smithy/middleware-retry": "^4.4.27", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.10.12", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.26", "@smithy/util-defaults-mode-node": "^4.2.29", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-wBUcXhiYA78pPDobojgkGWCh0LspGCPe0ijceLW692uW9r1MiqW8EhY4osfKRBqzbGvO9CdYbwD5fitu7kpwDw=="], - "@aws-sdk/client-sqs": ["@aws-sdk/client-sqs@3.972.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.972.0", "@aws-sdk/credential-provider-node": "3.972.0", "@aws-sdk/middleware-host-header": "3.972.0", "@aws-sdk/middleware-logger": "3.972.0", "@aws-sdk/middleware-recursion-detection": "3.972.0", "@aws-sdk/middleware-sdk-sqs": "3.972.0", "@aws-sdk/middleware-user-agent": "3.972.0", "@aws-sdk/region-config-resolver": "3.972.0", "@aws-sdk/types": "3.972.0", "@aws-sdk/util-endpoints": "3.972.0", "@aws-sdk/util-user-agent-browser": "3.972.0", "@aws-sdk/util-user-agent-node": "3.972.0", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.20.6", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/md5-js": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.7", "@smithy/middleware-retry": "^4.4.23", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.10.8", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.22", "@smithy/util-defaults-mode-node": "^4.2.25", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-hNXJjqQLjGqq4a+MQy9xl2poVAlHnjFEA26ik+y5ktn2yNZ8SMbyY+Tdn2Ki06XS/yNHubHFdIQomqbcE4t22Q=="], + "@aws-sdk/client-sqs": ["@aws-sdk/client-sqs@3.975.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.1", "@aws-sdk/credential-provider-node": "^3.972.1", "@aws-sdk/middleware-host-header": "^3.972.1", "@aws-sdk/middleware-logger": "^3.972.1", "@aws-sdk/middleware-recursion-detection": "^3.972.1", "@aws-sdk/middleware-sdk-sqs": "^3.972.2", "@aws-sdk/middleware-user-agent": "^3.972.2", "@aws-sdk/region-config-resolver": "^3.972.1", "@aws-sdk/types": "^3.973.0", "@aws-sdk/util-endpoints": "3.972.0", "@aws-sdk/util-user-agent-browser": "^3.972.1", "@aws-sdk/util-user-agent-node": "^3.972.1", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.21.1", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/md5-js": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.11", "@smithy/middleware-retry": "^4.4.27", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.10.12", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.26", "@smithy/util-defaults-mode-node": "^4.2.29", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6KS8T24LkEp2QZ/529SKVHQTgvCDUWXE8NtrILUBxZ9e3BiprjC9JSEdMqgh82BUD8s8yv4nnoa4Faiz7lRFpw=="], "@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.598.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.598.0", "@aws-sdk/middleware-host-header": "3.598.0", "@aws-sdk/middleware-logger": "3.598.0", "@aws-sdk/middleware-recursion-detection": "3.598.0", "@aws-sdk/middleware-user-agent": "3.598.0", "@aws-sdk/region-config-resolver": "3.598.0", "@aws-sdk/types": "3.598.0", "@aws-sdk/util-endpoints": "3.598.0", "@aws-sdk/util-user-agent-browser": "3.598.0", "@aws-sdk/util-user-agent-node": "3.598.0", "@smithy/config-resolver": "^3.0.2", "@smithy/core": "^2.2.1", "@smithy/fetch-http-handler": "^3.0.2", "@smithy/hash-node": "^3.0.1", "@smithy/invalid-dependency": "^3.0.1", "@smithy/middleware-content-length": "^3.0.1", "@smithy/middleware-endpoint": "^3.0.2", "@smithy/middleware-retry": "^3.0.4", "@smithy/middleware-serde": "^3.0.1", "@smithy/middleware-stack": "^3.0.1", "@smithy/node-config-provider": "^3.1.1", "@smithy/node-http-handler": "^3.0.1", "@smithy/protocol-http": "^4.0.1", "@smithy/smithy-client": "^3.1.2", "@smithy/types": "^3.1.0", "@smithy/url-parser": "^3.0.1", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", "@smithy/util-defaults-mode-browser": "^3.0.4", "@smithy/util-defaults-mode-node": "^3.0.4", "@smithy/util-endpoints": "^2.0.2", "@smithy/util-middleware": "^3.0.1", "@smithy/util-retry": "^3.0.1", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-nOI5lqPYa+YZlrrzwAJywJSw3MKVjvu6Ge2fCqQUNYMfxFB0NAaDFnl0EPjXi+sEbtCuz/uWE77poHbqiZ+7Iw=="], @@ -421,61 +421,61 @@ "@aws-sdk/client-sts": ["@aws-sdk/client-sts@3.600.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/client-sso-oidc": "3.600.0", "@aws-sdk/core": "3.598.0", "@aws-sdk/credential-provider-node": "3.600.0", "@aws-sdk/middleware-host-header": "3.598.0", "@aws-sdk/middleware-logger": "3.598.0", "@aws-sdk/middleware-recursion-detection": "3.598.0", "@aws-sdk/middleware-user-agent": "3.598.0", "@aws-sdk/region-config-resolver": "3.598.0", "@aws-sdk/types": "3.598.0", "@aws-sdk/util-endpoints": "3.598.0", "@aws-sdk/util-user-agent-browser": "3.598.0", "@aws-sdk/util-user-agent-node": "3.598.0", "@smithy/config-resolver": "^3.0.2", "@smithy/core": "^2.2.1", "@smithy/fetch-http-handler": "^3.0.2", "@smithy/hash-node": "^3.0.1", "@smithy/invalid-dependency": "^3.0.1", "@smithy/middleware-content-length": "^3.0.1", "@smithy/middleware-endpoint": "^3.0.2", "@smithy/middleware-retry": "^3.0.4", "@smithy/middleware-serde": "^3.0.1", "@smithy/middleware-stack": "^3.0.1", "@smithy/node-config-provider": "^3.1.1", "@smithy/node-http-handler": "^3.0.1", "@smithy/protocol-http": "^4.0.1", "@smithy/smithy-client": "^3.1.2", "@smithy/types": "^3.1.0", "@smithy/url-parser": "^3.0.1", "@smithy/util-base64": "^3.0.0", "@smithy/util-body-length-browser": "^3.0.0", "@smithy/util-body-length-node": "^3.0.0", "@smithy/util-defaults-mode-browser": "^3.0.4", "@smithy/util-defaults-mode-node": "^3.0.4", "@smithy/util-endpoints": "^2.0.2", "@smithy/util-middleware": "^3.0.1", "@smithy/util-retry": "^3.0.1", "@smithy/util-utf8": "^3.0.0", "tslib": "^2.6.2" } }, "sha512-KQG97B7LvTtTiGmjlrG1LRAY8wUvCQzrmZVV5bjrJ/1oXAU7DITYwVbSJeX9NWg6hDuSk0VE3MFwIXS2SvfLIA=="], - "@aws-sdk/core": ["@aws-sdk/core@3.972.0", "", { "dependencies": { "@aws-sdk/types": "3.972.0", "@aws-sdk/xml-builder": "3.972.0", "@smithy/core": "^3.20.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.10.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A=="], + "@aws-sdk/core": ["@aws-sdk/core@3.973.1", "", { "dependencies": { "@aws-sdk/types": "^3.973.0", "@aws-sdk/xml-builder": "^3.972.1", "@smithy/core": "^3.21.1", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/signature-v4": "^5.3.8", "@smithy/smithy-client": "^4.10.12", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Ocubx42QsMyVs9ANSmFpRm0S+hubWljpPLjOi9UFrtcnVJjrVJTzQ51sN0e5g4e8i8QZ7uY73zosLmgYL7kZTQ=="], "@aws-sdk/credential-provider-cognito-identity": ["@aws-sdk/credential-provider-cognito-identity@3.600.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.600.0", "@aws-sdk/types": "3.598.0", "@smithy/property-provider": "^3.1.1", "@smithy/types": "^3.1.0", "tslib": "^2.6.2" } }, "sha512-AIM+B06d1+71EuBrk2UR9ZZgRS3a+ARxE3oZKMZYlfqtZ3kY8w4DkhEt7OVruc6uSsMhkrcQT6nxsOxFSi4RtA=="], - "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.0", "", { "dependencies": { "@aws-sdk/core": "3.972.0", "@aws-sdk/types": "3.972.0", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-kKHoNv+maHlPQOAhYamhap0PObd16SAb3jwaY0KYgNTiSbeXlbGUZPLioo9oA3wU10zItJzx83ClU7d7h40luA=="], + "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.972.1", "", { "dependencies": { "@aws-sdk/core": "^3.973.0", "@aws-sdk/types": "^3.973.0", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-/etNHqnx96phy/SjI0HRC588o4vKH5F0xfkZ13yAATV7aNrb+5gYGNE6ePWafP+FuZ3HkULSSlJFj0AxgrAqYw=="], - "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.0", "", { "dependencies": { "@aws-sdk/core": "3.972.0", "@aws-sdk/types": "3.972.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.10.8", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.10", "tslib": "^2.6.2" } }, "sha512-xzEi81L7I5jGUbpmqEHCe7zZr54hCABdj4H+3LzktHYuovV/oqnvoDdvZpGFR0e/KAw1+PL38NbGrpG30j6qlA=="], + "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.972.2", "", { "dependencies": { "@aws-sdk/core": "^3.973.1", "@aws-sdk/types": "^3.973.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/node-http-handler": "^4.4.8", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.10.12", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.10", "tslib": "^2.6.2" } }, "sha512-mXgdaUfe5oM+tWKyeZ7Vh/iQ94FrkMky1uuzwTOmFADiRcSk5uHy/e3boEFedXiT/PRGzgBmqvJVK4F6lUISCg=="], - "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.0", "", { "dependencies": { "@aws-sdk/core": "3.972.0", "@aws-sdk/credential-provider-env": "3.972.0", "@aws-sdk/credential-provider-http": "3.972.0", "@aws-sdk/credential-provider-login": "3.972.0", "@aws-sdk/credential-provider-process": "3.972.0", "@aws-sdk/credential-provider-sso": "3.972.0", "@aws-sdk/credential-provider-web-identity": "3.972.0", "@aws-sdk/nested-clients": "3.972.0", "@aws-sdk/types": "3.972.0", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-ruhAMceUIq2aknFd3jhWxmO0P0Efab5efjyIXOkI9i80g+zDY5VekeSxfqRKStEEJSKSCHDLQuOu0BnAn4Rzew=="], + "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.972.1", "", { "dependencies": { "@aws-sdk/core": "^3.973.0", "@aws-sdk/credential-provider-env": "^3.972.1", "@aws-sdk/credential-provider-http": "^3.972.1", "@aws-sdk/credential-provider-login": "^3.972.1", "@aws-sdk/credential-provider-process": "^3.972.1", "@aws-sdk/credential-provider-sso": "^3.972.1", "@aws-sdk/credential-provider-web-identity": "^3.972.1", "@aws-sdk/nested-clients": "3.974.0", "@aws-sdk/types": "^3.973.0", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-OdbJA3v+XlNDsrYzNPRUwr8l7gw1r/nR8l4r96MDzSBDU8WEo8T6C06SvwaXR8SpzsjO3sq5KMP86wXWg7Rj4g=="], - "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.0", "", { "dependencies": { "@aws-sdk/core": "3.972.0", "@aws-sdk/nested-clients": "3.972.0", "@aws-sdk/types": "3.972.0", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-SsrsFJsEYAJHO4N/r2P0aK6o8si6f1lprR+Ej8J731XJqTckSGs/HFHcbxOyW/iKt+LNUvZa59/VlJmjhF4bEQ=="], + "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.972.1", "", { "dependencies": { "@aws-sdk/core": "^3.973.0", "@aws-sdk/nested-clients": "3.974.0", "@aws-sdk/types": "^3.973.0", "@smithy/property-provider": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-CccqDGL6ZrF3/EFWZefvKW7QwwRdxlHUO8NVBKNVcNq6womrPDvqB6xc9icACtE0XB0a7PLoSTkAg8bQVkTO2w=="], - "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.972.0", "@aws-sdk/credential-provider-http": "3.972.0", "@aws-sdk/credential-provider-ini": "3.972.0", "@aws-sdk/credential-provider-process": "3.972.0", "@aws-sdk/credential-provider-sso": "3.972.0", "@aws-sdk/credential-provider-web-identity": "3.972.0", "@aws-sdk/types": "3.972.0", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-wwJDpEGl6+sOygic8QKu0OHVB8SiodqF1fr5jvUlSFfS6tJss/E9vBc2aFjl7zI6KpAIYfIzIgM006lRrZtWCQ=="], + "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.972.1", "", { "dependencies": { "@aws-sdk/credential-provider-env": "^3.972.1", "@aws-sdk/credential-provider-http": "^3.972.1", "@aws-sdk/credential-provider-ini": "^3.972.1", "@aws-sdk/credential-provider-process": "^3.972.1", "@aws-sdk/credential-provider-sso": "^3.972.1", "@aws-sdk/credential-provider-web-identity": "^3.972.1", "@aws-sdk/types": "^3.973.0", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-DwXPk9GfuU/xG9tmCyXFVkCr6X3W8ZCoL5Ptb0pbltEx1/LCcg7T+PBqDlPiiinNCD6ilIoMJDWsnJ8ikzZA7Q=="], - "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.0", "", { "dependencies": { "@aws-sdk/core": "3.972.0", "@aws-sdk/types": "3.972.0", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-nmzYhamLDJ8K+v3zWck79IaKMc350xZnWsf/GeaXO6E3MewSzd3lYkTiMi7lEp3/UwDm9NHfPguoPm+mhlSWQQ=="], + "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.972.1", "", { "dependencies": { "@aws-sdk/core": "^3.973.0", "@aws-sdk/types": "^3.973.0", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-bi47Zigu3692SJwdBvo8y1dEwE6B61stCwCFnuRWJVTfiM84B+VTSCV661CSWJmIZzmcy7J5J3kWyxL02iHj0w=="], - "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.972.0", "@aws-sdk/core": "3.972.0", "@aws-sdk/token-providers": "3.972.0", "@aws-sdk/types": "3.972.0", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-6mYyfk1SrMZ15cH9T53yAF4YSnvq4yU1Xlgm3nqV1gZVQzmF5kr4t/F3BU3ygbvzi4uSwWxG3I3TYYS5eMlAyg=="], + "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.972.1", "", { "dependencies": { "@aws-sdk/client-sso": "3.974.0", "@aws-sdk/core": "^3.973.0", "@aws-sdk/token-providers": "3.974.0", "@aws-sdk/types": "^3.973.0", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-dLZVNhM7wSgVUFsgVYgI5hb5Z/9PUkT46pk/SHrSmUqfx6YDvoV4YcPtaiRqviPpEGGiRtdQMEadyOKIRqulUQ=="], - "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.0", "", { "dependencies": { "@aws-sdk/core": "3.972.0", "@aws-sdk/nested-clients": "3.972.0", "@aws-sdk/types": "3.972.0", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-vsJXBGL8H54kz4T6do3p5elATj5d1izVGUXMluRJntm9/I0be/zUYtdd4oDTM2kSUmd4Zhyw3fMQ9lw7CVhd4A=="], + "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.972.1", "", { "dependencies": { "@aws-sdk/core": "^3.973.0", "@aws-sdk/nested-clients": "3.974.0", "@aws-sdk/types": "^3.973.0", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-YMDeYgi0u687Ay0dAq/pFPKuijrlKTgsaB/UATbxCs/FzZfMiG4If5ksywHmmW7MiYUF8VVv+uou3TczvLrN4w=="], "@aws-sdk/credential-providers": ["@aws-sdk/credential-providers@3.600.0", "", { "dependencies": { "@aws-sdk/client-cognito-identity": "3.600.0", "@aws-sdk/client-sso": "3.598.0", "@aws-sdk/client-sts": "3.600.0", "@aws-sdk/credential-provider-cognito-identity": "3.600.0", "@aws-sdk/credential-provider-env": "3.598.0", "@aws-sdk/credential-provider-http": "3.598.0", "@aws-sdk/credential-provider-ini": "3.598.0", "@aws-sdk/credential-provider-node": "3.600.0", "@aws-sdk/credential-provider-process": "3.598.0", "@aws-sdk/credential-provider-sso": "3.598.0", "@aws-sdk/credential-provider-web-identity": "3.598.0", "@aws-sdk/types": "3.598.0", "@smithy/credential-provider-imds": "^3.1.1", "@smithy/property-provider": "^3.1.1", "@smithy/types": "^3.1.0", "tslib": "^2.6.2" } }, "sha512-cC9uqmX0rgx1efiJGqeR+i0EXr8RQ5SAzH7M45WNBZpYiLEe6reWgIYJY9hmOxuaoMdWSi8kekuN3IjTIORRjw=="], - "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.0", "", { "dependencies": { "@aws-sdk/types": "3.972.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-3eztFI6F9/eHtkIaWKN3nT+PM+eQ6p1MALDuNshFk323ixuCZzOOVT8oUqtZa30Z6dycNXJwhlIq7NhUVFfimw=="], + "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.972.1", "", { "dependencies": { "@aws-sdk/types": "^3.973.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-/R82lXLPmZ9JaUGSUdKtBp2k/5xQxvBT3zZWyKiBOhyulFotlfvdlrO8TnqstBimsl4lYEYySDL+W6ldFh6ALg=="], - "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.0", "", { "dependencies": { "@aws-sdk/types": "3.972.0", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-ZvdyVRwzK+ra31v1pQrgbqR/KsLD+wwJjHgko6JfoKUBIcEfAwJzQKO6HspHxdHWTVUz6MgvwskheR/TTYZl2g=="], + "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.972.1", "", { "dependencies": { "@aws-sdk/types": "^3.973.0", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-JGgFl6cHg9G2FHu4lyFIzmFN8KESBiRr84gLC3Aeni0Gt1nKm+KxWLBuha/RPcXxJygGXCcMM4AykkIwxor8RA=="], - "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.0", "", { "dependencies": { "@aws-sdk/types": "3.972.0", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-F2SmUeO+S6l1h6dydNet3BQIk173uAkcfU1HDkw/bUdRLAnh15D3HP9vCZ7oCPBNcdEICbXYDmx0BR9rRUHGlQ=="], + "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.972.1", "", { "dependencies": { "@aws-sdk/types": "^3.973.0", "@aws/lambda-invoke-store": "^0.2.2", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-taGzNRe8vPHjnliqXIHp9kBgIemLE/xCaRTMH1NH0cncHeaPcjxtnCroAAM9aOlPuKvBe2CpZESyvM1+D8oI7Q=="], - "@aws-sdk/middleware-sdk-sqs": ["@aws-sdk/middleware-sdk-sqs@3.972.0", "", { "dependencies": { "@aws-sdk/types": "3.972.0", "@smithy/smithy-client": "^4.10.8", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-Sdada+JFqE0O14RsXeHoEfubh5WkPCzzXXjZTxAfSRIpmPCPwNjiPopKF1Y90OFiUiIzM2DThEEV+L2/fPCZng=="], + "@aws-sdk/middleware-sdk-sqs": ["@aws-sdk/middleware-sdk-sqs@3.972.2", "", { "dependencies": { "@aws-sdk/types": "^3.973.0", "@smithy/smithy-client": "^4.10.12", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-LPKsfqdoei7kBJo7JqGKbIM05W0bbcnJNfFtoOPgjXOJa7OpEs0pYj5OHiqbykgUFzkygD22f9sBmEfZkFoZ0g=="], - "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.0", "", { "dependencies": { "@aws-sdk/core": "3.972.0", "@aws-sdk/types": "3.972.0", "@aws-sdk/util-endpoints": "3.972.0", "@smithy/core": "^3.20.6", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-kFHQm2OCBJCzGWRafgdWHGFjitUXY/OxXngymcX4l8CiyiNDZB27HDDBg2yLj3OUJc4z4fexLMmP8r9vgag19g=="], + "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.972.2", "", { "dependencies": { "@aws-sdk/core": "^3.973.1", "@aws-sdk/types": "^3.973.0", "@aws-sdk/util-endpoints": "3.972.0", "@smithy/core": "^3.21.1", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-d+Exq074wy0X6wvShg/kmZVtkah+28vMuqCtuY3cydg8LUZOJBtbAolCpEJizSyb8mJJZF9BjWaTANXL4OYnkg=="], - "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.972.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.972.0", "@aws-sdk/middleware-host-header": "3.972.0", "@aws-sdk/middleware-logger": "3.972.0", "@aws-sdk/middleware-recursion-detection": "3.972.0", "@aws-sdk/middleware-user-agent": "3.972.0", "@aws-sdk/region-config-resolver": "3.972.0", "@aws-sdk/types": "3.972.0", "@aws-sdk/util-endpoints": "3.972.0", "@aws-sdk/util-user-agent-browser": "3.972.0", "@aws-sdk/util-user-agent-node": "3.972.0", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.20.6", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.7", "@smithy/middleware-retry": "^4.4.23", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.10.8", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.22", "@smithy/util-defaults-mode-node": "^4.2.25", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-QGlbnuGzSQJVG6bR9Qw6G0Blh6abFR4VxNa61ttMbzy9jt28xmk2iGtrYLrQPlCCPhY6enHqjTWm3n3LOb0wAw=="], + "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.974.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.0", "@aws-sdk/middleware-host-header": "^3.972.1", "@aws-sdk/middleware-logger": "^3.972.1", "@aws-sdk/middleware-recursion-detection": "^3.972.1", "@aws-sdk/middleware-user-agent": "^3.972.1", "@aws-sdk/region-config-resolver": "^3.972.1", "@aws-sdk/types": "^3.973.0", "@aws-sdk/util-endpoints": "3.972.0", "@aws-sdk/util-user-agent-browser": "^3.972.1", "@aws-sdk/util-user-agent-node": "^3.972.1", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.21.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.10", "@smithy/middleware-retry": "^4.4.26", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.10.11", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.25", "@smithy/util-defaults-mode-node": "^4.2.28", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-k3dwdo/vOiHMJc9gMnkPl1BA5aQfTrZbz+8fiDkWrPagqAioZgmo5oiaOaeX0grObfJQKDtcpPFR4iWf8cgl8Q=="], "@aws-sdk/protocol-http": ["@aws-sdk/protocol-http@3.374.0", "", { "dependencies": { "@smithy/protocol-http": "^1.1.0", "tslib": "^2.5.0" } }, "sha512-9WpRUbINdGroV3HiZZIBoJvL2ndoWk39OfwxWs2otxByppJZNN14bg/lvCx5e8ggHUti7IBk5rb0nqQZ4m05pg=="], - "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.0", "", { "dependencies": { "@aws-sdk/types": "3.972.0", "@smithy/config-resolver": "^4.4.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-JyOf+R/6vJW8OEVFCAyzEOn2reri/Q+L0z9zx4JQSKWvTmJ1qeFO25sOm8VIfB8URKhfGRTQF30pfYaH2zxt/A=="], + "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.972.1", "", { "dependencies": { "@aws-sdk/types": "^3.973.0", "@smithy/config-resolver": "^4.4.6", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-voIY8RORpxLAEgEkYaTFnkaIuRwVBEc+RjVZYcSSllPV+ZEKAacai6kNhJeE3D70Le+JCfvRb52tng/AVHY+jQ=="], "@aws-sdk/signature-v4": ["@aws-sdk/signature-v4@3.374.0", "", { "dependencies": { "@smithy/signature-v4": "^1.0.1", "tslib": "^2.5.0" } }, "sha512-2xLJvSdzcZZAg0lsDLUAuSQuihzK0dcxIK7WmfuJeF7DGKJFmp9czQmz5f3qiDz6IDQzvgK1M9vtJSVCslJbyQ=="], - "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.972.0", "", { "dependencies": { "@aws-sdk/core": "3.972.0", "@aws-sdk/nested-clients": "3.972.0", "@aws-sdk/types": "3.972.0", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-kWlXG+y5nZhgXGEtb72Je+EvqepBPs8E3vZse//1PYLWs2speFqbGE/ywCXmzEJgHgVqSB/u/lqBvs5WlYmSqQ=="], + "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.974.0", "", { "dependencies": { "@aws-sdk/core": "^3.973.0", "@aws-sdk/nested-clients": "3.974.0", "@aws-sdk/types": "^3.973.0", "@smithy/property-provider": "^4.2.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-cBykL0LiccKIgNhGWvQRTPvsBLPZxnmJU3pYxG538jpFX8lQtrCy1L7mmIHNEdxIdIGEPgAEHF8/JQxgBToqUQ=="], - "@aws-sdk/types": ["@aws-sdk/types@3.972.0", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug=="], + "@aws-sdk/types": ["@aws-sdk/types@3.973.0", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-jYIdB7a7jhRTvyb378nsjyvJh1Si+zVduJ6urMNGpz8RjkmHZ+9vM2H07XaIB2Cfq0GhJRZYOfUCH8uqQhqBkQ=="], "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.972.0", "", { "dependencies": { "@aws-sdk/types": "3.972.0", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-endpoints": "^3.2.8", "tslib": "^2.6.2" } }, "sha512-6JHsl1V/a1ZW8D8AFfd4R52fwZPnZ5H4U6DS8m/bWT8qad72NvbOFAC7U2cDtFs2TShqUO3TEiX/EJibtY3ijg=="], "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-FNUqAjlKAGA7GM05kywE99q8wiPHPZqrzhq3wXRga6PRD6A0kzT85Pb0AzYBVTBRpSrKyyr6M92Y6bnSBVp2BA=="], - "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.0", "", { "dependencies": { "@aws-sdk/types": "3.972.0", "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-eOLdkQyoRbDgioTS3Orr7iVsVEutJyMZxvyZ6WAF95IrF0kfWx5Rd/KXnfbnG/VKa2CvjZiitWfouLzfVEyvJA=="], + "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.972.1", "", { "dependencies": { "@aws-sdk/types": "^3.973.0", "@smithy/types": "^4.12.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-IgF55NFmJX8d9Wql9M0nEpk2eYbuD8G4781FN4/fFgwTXBn86DvlZJuRWDCMcMqZymnBVX7HW9r+3r9ylqfW0w=="], - "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.972.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.972.0", "@aws-sdk/types": "3.972.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-GOy+AiSrE9kGiojiwlZvVVSXwylu4+fmP0MJfvras/MwP09RB/YtQuOVR1E0fKQc6OMwaTNBjgAbOEhxuWFbAw=="], + "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.972.1", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "^3.972.1", "@aws-sdk/types": "^3.973.0", "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-oIs4JFcADzoZ0c915R83XvK2HltWupxNsXUIuZse2rgk7b97zTpkxaqXiH0h9ylh31qtgo/t8hp4tIqcsMrEbQ=="], "@aws-sdk/util-utf8-browser": ["@aws-sdk/util-utf8-browser@3.259.0", "", { "dependencies": { "tslib": "^2.3.1" } }, "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw=="], - "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.0", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg=="], + "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.972.1", "", { "dependencies": { "@smithy/types": "^4.12.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-6zZGlPOqn7Xb+25MAXGb1JhgvaC5HjZj6GzszuVrnEgbhvzBRFGKYemuHBV4bho+dtqeYKPgaZUv7/e80hIGNg=="], "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.3", "", {}, "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw=="], @@ -533,23 +533,23 @@ "@better-fetch/fetch": ["@better-fetch/fetch@1.1.21", "", {}, "sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A=="], - "@biomejs/biome": ["@biomejs/biome@2.3.11", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.11", "@biomejs/cli-darwin-x64": "2.3.11", "@biomejs/cli-linux-arm64": "2.3.11", "@biomejs/cli-linux-arm64-musl": "2.3.11", "@biomejs/cli-linux-x64": "2.3.11", "@biomejs/cli-linux-x64-musl": "2.3.11", "@biomejs/cli-win32-arm64": "2.3.11", "@biomejs/cli-win32-x64": "2.3.11" }, "bin": { "biome": "bin/biome" } }, "sha512-/zt+6qazBWguPG6+eWmiELqO+9jRsMZ/DBU3lfuU2ngtIQYzymocHhKiZRyrbra4aCOoyTg/BmY+6WH5mv9xmQ=="], + "@biomejs/biome": ["@biomejs/biome@2.3.12", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.12", "@biomejs/cli-darwin-x64": "2.3.12", "@biomejs/cli-linux-arm64": "2.3.12", "@biomejs/cli-linux-arm64-musl": "2.3.12", "@biomejs/cli-linux-x64": "2.3.12", "@biomejs/cli-linux-x64-musl": "2.3.12", "@biomejs/cli-win32-arm64": "2.3.12", "@biomejs/cli-win32-x64": "2.3.12" }, "bin": { "biome": "bin/biome" } }, "sha512-AR7h4aSlAvXj7TAajW/V12BOw2EiS0AqZWV5dGozf4nlLoUF/ifvD0+YgKSskT0ylA6dY1A8AwgP8kZ6yaCQnA=="], - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-/uXXkBcPKVQY7rc9Ys2CrlirBJYbpESEDme7RKiBD6MmqR2w3j0+ZZXRIL2xiaNPsIMMNhP1YnA+jRRxoOAFrA=="], + "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-cO6fn+KiMBemva6EARDLQBxeyvLzgidaFRJi8G7OeRqz54kWK0E+uSjgFaiHlc3DZYoa0+1UFE8mDxozpc9ieg=="], - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.11", "", { "os": "darwin", "cpu": "x64" }, "sha512-fh7nnvbweDPm2xEmFjfmq7zSUiox88plgdHF9OIW4i99WnXrAC3o2P3ag9judoUMv8FCSUnlwJCM1B64nO5Fbg=="], + "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-/fiF/qmudKwSdvmSrSe/gOTkW77mHHkH8Iy7YC2rmpLuk27kbaUOPa7kPiH5l+3lJzTUfU/t6x1OuIq/7SGtxg=="], - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-l4xkGa9E7Uc0/05qU2lMYfN1H+fzzkHgaJoy98wO+b/7Gl78srbCRRgwYSW+BTLixTBrM6Ede5NSBwt7rd/i6g=="], + "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-nbOsuQROa3DLla5vvsTZg+T5WVPGi9/vYxETm9BOuLHBJN3oWQIg3MIkE2OfL18df1ZtNkqXkH6Yg9mdTPem7A=="], - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.11", "", { "os": "linux", "cpu": "arm64" }, "sha512-XPSQ+XIPZMLaZ6zveQdwNjbX+QdROEd1zPgMwD47zvHV+tCGB88VH+aynyGxAHdzL+Tm/+DtKST5SECs4iwCLg=="], + "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-aqkeSf7IH+wkzFpKeDVPSXy9uDjxtLpYA6yzkYsY+tVjwFFirSuajHDI3ul8en90XNs1NA0n8kgBrjwRi5JeyA=="], - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.11", "", { "os": "linux", "cpu": "x64" }, "sha512-/1s9V/H3cSe0r0Mv/Z8JryF5x9ywRxywomqZVLHAoa/uN0eY7F8gEngWKNS5vbbN/BsfpCG5yeBT5ENh50Frxg=="], + "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.12", "", { "os": "linux", "cpu": "x64" }, "sha512-CQtqrJ+qEEI8tgRSTjjzk6wJAwfH3wQlkIGsM5dlecfRZaoT+XCms/mf7G4kWNexrke6mnkRzNy6w8ebV177ow=="], - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.11", "", { "os": "linux", "cpu": "x64" }, "sha512-vU7a8wLs5C9yJ4CB8a44r12aXYb8yYgBn+WeyzbMjaCMklzCv1oXr8x+VEyWodgJt9bDmhiaW/I0RHbn7rsNmw=="], + "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.12", "", { "os": "linux", "cpu": "x64" }, "sha512-kVGWtupRRsOjvw47YFkk5mLiAdpCPMWBo1jOwAzh+juDpUb2sWarIp+iq+CPL1Wt0LLZnYtP7hH5kD6fskcxmg=="], - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.11", "", { "os": "win32", "cpu": "arm64" }, "sha512-PZQ6ElCOnkYapSsysiTy0+fYX+agXPlWugh6+eQ6uPKI3vKAqNp6TnMhoM3oY2NltSB89hz59o8xIfOdyhi9Iw=="], + "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-Re4I7UnOoyE4kHMqpgtG6UvSBGBbbtvsOvBROgCCoH7EgANN6plSQhvo2W7OCITvTp7gD6oZOyZy72lUdXjqZg=="], - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.11", "", { "os": "win32", "cpu": "x64" }, "sha512-43VrG813EW+b5+YbDbz31uUsheX+qFKCpXeY9kfdAx+ww3naKxeVkTD9zLIWxUPfJquANMHrmW3wbe/037G0Qg=="], + "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.12", "", { "os": "win32", "cpu": "x64" }, "sha512-qqGVWqNNek0KikwPZlOIoxtXgsNGsX+rgdEzgw82Re8nF02W+E2WokaQhpF5TdBh/D/RQ3TLppH+otp6ztN0lw=="], "@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.1", "", {}, "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw=="], @@ -823,9 +823,9 @@ "@jsx-tool/jsx-tool": ["@jsx-tool/jsx-tool@0.0.21", "", { "dependencies": { "@inquirer/prompts": "^7.9.0", "@tailwindcss/language-service": "^0.14.29", "@vscode/ripgrep": "^1.17.0", "commander": "^14.0.0", "http-proxy": "^1.18.1", "picocolors": "^1.1.1", "prettier": "^3.6.2", "reflect-metadata": "^0.2.2", "tsyringe": "^4.10.0", "typescript-language-server": "^5.0.1", "vscode-languageserver": "^9.0.1", "vscode-languageserver-textdocument": "^1.0.12", "ws": "^8.18.2" }, "bin": { "jsx-tool": "bin/jsx-tool.js" } }, "sha512-QZbXXfNFbrQTfBTo3Y5qZW0AihM1PAuRDwL0f+eiVzy4r36uxPlpu1sLlkMxFOFOpSTyKshyHaz9TSvIE1egwg=="], - "@langchain/core": ["@langchain/core@1.1.16", "", { "dependencies": { "@cfworker/json-schema": "^4.0.2", "ansi-styles": "^5.0.0", "camelcase": "6", "decamelize": "1.2.0", "js-tiktoken": "^1.0.12", "langsmith": ">=0.4.0 <1.0.0", "mustache": "^4.2.0", "p-queue": "^6.6.2", "uuid": "^10.0.0", "zod": "^3.25.76 || ^4" } }, "sha512-2XKQKxvQdeQiuIo0tacAmDVojhSVAci8D2WDdmmyN+6CqDusLHEHyIDaOt4o+UBvpkyHXbCdrljzDTQY/AKeqg=="], + "@langchain/core": ["@langchain/core@1.1.17", "", { "dependencies": { "@cfworker/json-schema": "^4.0.2", "ansi-styles": "^5.0.0", "camelcase": "6", "decamelize": "1.2.0", "js-tiktoken": "^1.0.12", "langsmith": ">=0.4.0 <1.0.0", "mustache": "^4.2.0", "p-queue": "^6.6.2", "uuid": "^10.0.0", "zod": "^3.25.76 || ^4" } }, "sha512-g7/kcKbKEwNZSyyT7aT0utxn7wTOtKErqz0cL6VjrV4v/aOb9g+dKcfj17YkSm42YQmJp/rB2IXGc17vQPEBqA=="], - "@langchain/langgraph": ["@langchain/langgraph@1.1.1", "", { "dependencies": { "@langchain/langgraph-checkpoint": "^1.0.0", "@langchain/langgraph-sdk": "~1.5.5", "uuid": "^10.0.0" }, "peerDependencies": { "@langchain/core": "^1.0.1", "zod": "^3.25.32 || ^4.2.0", "zod-to-json-schema": "^3.x" }, "optionalPeers": ["zod-to-json-schema"] }, "sha512-FfFiaUwc2P5cy0AyALTA72S9OuutBLy+TRQECQSkwI5H40UNF+h7HM0U28xGTfmQ6MrqzbnnpRfXRTQX4jqsUw=="], + "@langchain/langgraph": ["@langchain/langgraph@1.1.2", "", { "dependencies": { "@langchain/langgraph-checkpoint": "^1.0.0", "@langchain/langgraph-sdk": "~1.5.5", "@standard-schema/spec": "1.1.0", "uuid": "^10.0.0" }, "peerDependencies": { "@langchain/core": "^1.0.1", "zod": "^3.25.32 || ^4.2.0", "zod-to-json-schema": "^3.x" }, "optionalPeers": ["zod-to-json-schema"] }, "sha512-kpZCttZ0N+jHSl5Vh/zVNElD5SxGR4sTjjLiBC00aLGf9JK+Sa/XXO6Bsk3WWXFtA1dY+4tUzUqH0mAHfN0WvA=="], "@langchain/langgraph-checkpoint": ["@langchain/langgraph-checkpoint@1.0.0", "", { "dependencies": { "uuid": "^10.0.0" }, "peerDependencies": { "@langchain/core": "^1.0.1" } }, "sha512-xrclBGvNCXDmi0Nz28t3vjpxSH6UYx6w5XAXSiiB1WEdc2xD2iY/a913I3x3a31XpInUW/GGfXXfePfaghV54A=="], @@ -997,7 +997,7 @@ "@opentelemetry/otlp-transformer": ["@opentelemetry/otlp-transformer@0.202.0", "", { "dependencies": { "@opentelemetry/api-logs": "0.202.0", "@opentelemetry/core": "2.0.1", "@opentelemetry/resources": "2.0.1", "@opentelemetry/sdk-logs": "0.202.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-5XO77QFzs9WkexvJQL9ksxL8oVFb/dfi9NWQSq7Sv0Efr9x3N+nb1iklP1TeVgxqJ7m1xWiC/Uv3wupiQGevMw=="], - "@opentelemetry/propagation-utils": ["@opentelemetry/propagation-utils@0.31.13", "", { "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-PJfTjWkJRYQyLy+lXwkRs7UJRpNMJbmF6N2qO38q3xNGPdL2Nc5YSnVYkMKxwOWgiobNWSg7GGQO3sITPFVNTg=="], + "@opentelemetry/propagation-utils": ["@opentelemetry/propagation-utils@0.31.14", "", { "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-h6KSRDTh3A8u2HpYUQf+Ia4P5WvzHohqrblLtIuYTLWtv1Klw7I+UC/cEooOsqFz8PDt++KkfkywFtYKH84/KQ=="], "@opentelemetry/propagator-b3": ["@opentelemetry/propagator-b3@2.0.1", "", { "dependencies": { "@opentelemetry/core": "2.0.1" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-Hc09CaQ8Tf5AGLmf449H726uRoBNGPBL4bjr7AnnUpzWMvhdn61F78z9qb6IqB737TffBsokGAK1XykFEZ1igw=="], @@ -1007,7 +1007,7 @@ "@opentelemetry/resource-detector-alibaba-cloud": ["@opentelemetry/resource-detector-alibaba-cloud@0.31.11", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/resources": "^2.0.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-R/asn6dAOWMfkLeEwqHCUz0cNbb9oiHVyd11iwlypeT/p9bR1lCX5juu5g/trOwxo62dbuFcDbBdKCJd3O2Edg=="], - "@opentelemetry/resource-detector-aws": ["@opentelemetry/resource-detector-aws@2.10.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-WKNpVKeDPCxNNAFfhGfO+ka7yYQa5utNLxsDaqxyatM1WzAKz4Ce8Hov9gBCD5laGp5pS7+CWh5XVZZlQN4xeQ=="], + "@opentelemetry/resource-detector-aws": ["@opentelemetry/resource-detector-aws@2.11.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-Wphbm9fGyinMLC8BiLU/5aK6yG191ws2q2SN4biCcQZQCTo6yEij4ka+fXQXAiLMGSzb5w8wa/FxOn/7KWPiSQ=="], "@opentelemetry/resource-detector-azure": ["@opentelemetry/resource-detector-azure@0.9.0", "", { "dependencies": { "@opentelemetry/core": "^2.0.0", "@opentelemetry/resources": "^2.0.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.0.0" } }, "sha512-5wJwAAW2vhbqIhgaRisU1y0F5mUco59F/dKgmnnnT6YNbxjrbdUZYxKF5Wl7deJoACVdL5wi/3N97GCXPEwwCQ=="], @@ -1049,11 +1049,11 @@ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], - "@posthog/ai": ["@posthog/ai@7.5.3", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.2", "@google/genai": "^1.34.0", "@langchain/core": "^1.1.8", "@posthog/core": "1.13.0", "langchain": "^1.2.3", "openai": "^6.15.0", "uuid": "^11.1.0", "zod": "^4.1.13" }, "peerDependencies": { "@ai-sdk/provider": "^2.0.0 || ^3.0.0", "posthog-node": "^5.0.0" }, "optionalPeers": ["@ai-sdk/provider"] }, "sha512-pcHKDyQHfvDCSZSm9xh/dM+Z8ILmZWJJoSm9m1OwIQIF/t7BGyn1Ap7X3jVCDUw2W4/p+4Oh0M9ntwqUUD2/gg=="], + "@posthog/ai": ["@posthog/ai@7.5.4", "", { "dependencies": { "@anthropic-ai/sdk": "^0.71.2", "@google/genai": "^1.34.0", "@langchain/core": "^1.1.8", "@posthog/core": "1.14.0", "langchain": "^1.2.3", "openai": "^6.16.0", "uuid": "^11.1.0", "zod": "^4.1.13" }, "peerDependencies": { "@ai-sdk/provider": "^2.0.0 || ^3.0.0", "posthog-node": "^5.0.0" }, "optionalPeers": ["@ai-sdk/provider"] }, "sha512-Z0KVUK6fnkjj8InFB5NY4S1A8esx9o9qtjV+FTsW5ifymLsmEjPmVX4+AmIwpGlw6D9pyltDHaDe7/IT5MHW/w=="], - "@posthog/core": ["@posthog/core@1.13.0", "", { "dependencies": { "cross-spawn": "^7.0.6" } }, "sha512-knjncrk7qRmssFRbGzBl1Tunt21GRpe0Wv+uVelyL0Rh7PdQUsgguulzXFTps8hA6wPwTU4kq85qnbAJ3eH6Wg=="], + "@posthog/core": ["@posthog/core@1.14.0", "", { "dependencies": { "cross-spawn": "^7.0.6" } }, "sha512-havjGYHwL8Gy6LXIR911h+M/sYlJLQbepxP/cc1M7Cp3v8F92bzpqkbuvUIUyb7/izkxfGwc9wMqKAo0QxMTrg=="], - "@posthog/types": ["@posthog/types@1.333.0", "", {}, "sha512-9Wg/2ez+EZh6NmtOjhtYSkBHz/yIq8WMS0QSIizUoggh35hHVg4BTMXl3rz/tPearJNKU/8oRjEyuZ0OYTEDOA=="], + "@posthog/types": ["@posthog/types@1.335.2", "", {}, "sha512-cyl6eFrt0nR7lxb8+oGXyS16wDxQJz6awMWPyDB423lI+MiM64vz0VV5LNABahEc4BuytJzfEOyvyA3LPJ4hOQ=="], "@prisma/instrumentation": ["@prisma/instrumentation@6.15.0", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.52.0 || ^0.53.0 || ^0.54.0 || ^0.55.0 || ^0.56.0 || ^0.57.0" }, "peerDependencies": { "@opentelemetry/api": "^1.8" } }, "sha512-6TXaH6OmDkMOQvOxwLZ8XS51hU2v4A3vmE2pSijCIiGRJYyNeMcL6nMHQMyYdZRD8wl7LF3Wzc+AMPMV/9Oo7A=="], @@ -1223,55 +1223,55 @@ "@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="], - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.55.3", "", { "os": "android", "cpu": "arm" }, "sha512-qyX8+93kK/7R5BEXPC2PjUt0+fS/VO2BVHjEHyIEWiYn88rcRBHmdLgoJjktBltgAf+NY7RfCGB1SoyKS/p9kg=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.56.0", "", { "os": "android", "cpu": "arm" }, "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw=="], - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.55.3", "", { "os": "android", "cpu": "arm64" }, "sha512-6sHrL42bjt5dHQzJ12Q4vMKfN+kUnZ0atHHnv4V0Wd9JMTk7FDzSY35+7qbz3ypQYMBPANbpGK7JpnWNnhGt8g=="], + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.56.0", "", { "os": "android", "cpu": "arm64" }, "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q=="], - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.55.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-1ht2SpGIjEl2igJ9AbNpPIKzb1B5goXOcmtD0RFxnwNuMxqkR6AUaaErZz+4o+FKmzxcSNBOLrzsICZVNYa1Rw=="], + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.56.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w=="], - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.55.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-FYZ4iVunXxtT+CZqQoPVwPhH7549e/Gy7PIRRtq4t5f/vt54pX6eG9ebttRH6QSH7r/zxAFA4EZGlQ0h0FvXiA=="], + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.56.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.55.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-M/mwDCJ4wLsIgyxv2Lj7Len+UMHd4zAXu4GQ2UaCdksStglWhP61U3uowkaYBQBhVoNpwx5Hputo8eSqM7K82Q=="], + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.56.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.55.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-5jZT2c7jBCrMegKYTYTpni8mg8y3uY8gzeq2ndFOANwNuC/xJbVAoGKR9LhMDA0H3nIhvaqUoBEuJoICBudFrA=="], + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.56.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg=="], - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.55.3", "", { "os": "linux", "cpu": "arm" }, "sha512-YeGUhkN1oA+iSPzzhEjVPS29YbViOr8s4lSsFaZKLHswgqP911xx25fPOyE9+khmN6W4VeM0aevbDp4kkEoHiA=="], + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.56.0", "", { "os": "linux", "cpu": "arm" }, "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.55.3", "", { "os": "linux", "cpu": "arm" }, "sha512-eo0iOIOvcAlWB3Z3eh8pVM8hZ0oVkK3AjEM9nSrkSug2l15qHzF3TOwT0747omI6+CJJvl7drwZepT+re6Fy/w=="], + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.56.0", "", { "os": "linux", "cpu": "arm" }, "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw=="], - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.55.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-DJay3ep76bKUDImmn//W5SvpjRN5LmK/ntWyeJs/dcnwiiHESd3N4uteK9FDLf0S0W8E6Y0sVRXpOCoQclQqNg=="], + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.56.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ=="], - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.55.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-BKKWQkY2WgJ5MC/ayvIJTHjy0JUGb5efaHCUiG/39sSUvAYRBaO3+/EK0AZT1RF3pSj86O24GLLik9mAYu0IJg=="], + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.56.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA=="], - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.55.3", "", { "os": "linux", "cpu": "none" }, "sha512-Q9nVlWtKAG7ISW80OiZGxTr6rYtyDSkauHUtvkQI6TNOJjFvpj4gcH+KaJihqYInnAzEEUetPQubRwHef4exVg=="], + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg=="], - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.55.3", "", { "os": "linux", "cpu": "none" }, "sha512-2H5LmhzrpC4fFRNwknzmmTvvyJPHwESoJgyReXeFoYYuIDfBhP29TEXOkCJE/KxHi27mj7wDUClNq78ue3QEBQ=="], + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA=="], - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.55.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9S542V0ie9LCTznPYlvaeySwBeIEa7rDBgLHKZ5S9DBgcqdJYburabm8TqiqG6mrdTzfV5uttQRHcbKff9lWtA=="], + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.56.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw=="], - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.55.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ukxw+YH3XXpcezLgbJeasgxyTbdpnNAkrIlFGDl7t+pgCxZ89/6n1a+MxlY7CegU+nDgrgdqDelPRNQ/47zs0g=="], + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.56.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg=="], - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.55.3", "", { "os": "linux", "cpu": "none" }, "sha512-Iauw9UsTTvlF++FhghFJjqYxyXdggXsOqGpFBylaRopVpcbfyIIsNvkf9oGwfgIcf57z3m8+/oSYTo6HutBFNw=="], + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew=="], - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.55.3", "", { "os": "linux", "cpu": "none" }, "sha512-3OqKAHSEQXKdq9mQ4eajqUgNIK27VZPW3I26EP8miIzuKzCJ3aW3oEn2pzF+4/Hj/Moc0YDsOtBgT5bZ56/vcA=="], + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.56.0", "", { "os": "linux", "cpu": "none" }, "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ=="], - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.55.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-0CM8dSVzVIaqMcXIFej8zZrSFLnGrAE8qlNbbHfTw1EEPnFTg1U1ekI0JdzjPyzSfUsHWtodilQQG/RA55berA=="], + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.56.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.55.3", "", { "os": "linux", "cpu": "x64" }, "sha512-+fgJE12FZMIgBaKIAGd45rxf+5ftcycANJRWk8Vz0NnMTM5rADPGuRFTYar+Mqs560xuART7XsX2lSACa1iOmQ=="], + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.56.0", "", { "os": "linux", "cpu": "x64" }, "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw=="], - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.55.3", "", { "os": "linux", "cpu": "x64" }, "sha512-tMD7NnbAolWPzQlJQJjVFh/fNH3K/KnA7K8gv2dJWCwwnaK6DFCYST1QXYWfu5V0cDwarWC8Sf/cfMHniNq21A=="], + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.56.0", "", { "os": "linux", "cpu": "x64" }, "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA=="], - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.55.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u5KsqxOxjEeIbn7bUK1MPM34jrnPwjeqgyin4/N6e/KzXKfpE9Mi0nCxcQjaM9lLmPcHmn/xx1yOjgTMtu1jWQ=="], + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.56.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA=="], - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.55.3", "", { "os": "none", "cpu": "arm64" }, "sha512-vo54aXwjpTtsAnb3ca7Yxs9t2INZg7QdXN/7yaoG7nPGbOBXYXQY41Km+S1Ov26vzOAzLcAjmMdjyEqS1JkVhw=="], + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.56.0", "", { "os": "none", "cpu": "arm64" }, "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ=="], - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.55.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-HI+PIVZ+m+9AgpnY3pt6rinUdRYrGHvmVdsNQ4odNqQ/eRF78DVpMR7mOq7nW06QxpczibwBmeQzB68wJ+4W4A=="], + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.56.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing=="], - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.55.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-vRByotbdMo3Wdi+8oC2nVxtc3RkkFKrGaok+a62AT8lz/YBuQjaVYAS5Zcs3tPzW43Vsf9J0wehJbUY5xRSekA=="], + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.56.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg=="], - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.55.3", "", { "os": "win32", "cpu": "x64" }, "sha512-POZHq7UeuzMJljC5NjKi8vKMFN6/5EOqcX1yGntNLp7rUTpBAXQ1hW8kWPFxYLv07QMcNM75xqVLGPWQq6TKFA=="], + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.56.0", "", { "os": "win32", "cpu": "x64" }, "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ=="], - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.55.3", "", { "os": "win32", "cpu": "x64" }, "sha512-aPFONczE4fUFKNXszdvnd2GqKEYQdV5oEsIbKPujJmWlCI9zEsv1Otig8RKK+X9bed9gFUN6LAeN4ZcNuu4zjg=="], + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.56.0", "", { "os": "win32", "cpu": "x64" }, "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g=="], "@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="], @@ -1283,13 +1283,13 @@ "@sentry-internal/replay-canvas": ["@sentry-internal/replay-canvas@10.36.0", "", { "dependencies": { "@sentry-internal/replay": "10.36.0", "@sentry/core": "10.36.0" } }, "sha512-DLGIwmT2LX+O6TyYPtOQL5GiTm2rN0taJPDJ/Lzg2KEJZrdd5sKkzTckhh2x+vr4JQyeaLmnb8M40Ch1hvG/vQ=="], - "@sentry/babel-plugin-component-annotate": ["@sentry/babel-plugin-component-annotate@4.7.0", "", {}, "sha512-MkyajDiO17/GaHHFgOmh05ZtOwF5hmm9KRjVgn9PXHIdpz+TFM5mkp1dABmR6Y75TyNU98Z1aOwPOgyaR5etJw=="], + "@sentry/babel-plugin-component-annotate": ["@sentry/babel-plugin-component-annotate@4.8.0", "", {}, "sha512-cy/9Eipkv23MsEJ4IuB4dNlVwS9UqOzI3Eu+QPake5BVFgPYCX0uP0Tr3Z43Ime6Rb+BiDnWC51AJK9i9afHYw=="], "@sentry/browser": ["@sentry/browser@10.36.0", "", { "dependencies": { "@sentry-internal/browser-utils": "10.36.0", "@sentry-internal/feedback": "10.36.0", "@sentry-internal/replay": "10.36.0", "@sentry-internal/replay-canvas": "10.36.0", "@sentry/core": "10.36.0" } }, "sha512-yHhXbgdGY1s+m8CdILC9U/II7gb6+s99S2Eh8VneEn/JG9wHc+UOzrQCeFN0phFP51QbLkjkiQbbanjT1HP8UQ=="], "@sentry/bun": ["@sentry/bun@10.25.0", "", { "dependencies": { "@sentry/core": "10.25.0", "@sentry/node": "10.25.0" } }, "sha512-mcd5wGCI4Y30LrN3ihfDIuEi7DwGAIrEjLz2zN+Ek4equIeAC8kJrNvXl4rBYwoODSkth2K+9tqUPHoX6D4rKw=="], - "@sentry/bundler-plugin-core": ["@sentry/bundler-plugin-core@4.7.0", "", { "dependencies": { "@babel/core": "^7.18.5", "@sentry/babel-plugin-component-annotate": "4.7.0", "@sentry/cli": "^2.57.0", "dotenv": "^16.3.1", "find-up": "^5.0.0", "glob": "^10.5.0", "magic-string": "0.30.8", "unplugin": "1.0.1" } }, "sha512-gFdEtiup/7qYhN3vp1v2f0WL9AG9OorWLtIpfSBYbWjtzklVNg1sizvNyZ8nEiwtnb25LzvvCUbOP1SyP6IodQ=="], + "@sentry/bundler-plugin-core": ["@sentry/bundler-plugin-core@4.8.0", "", { "dependencies": { "@babel/core": "^7.18.5", "@sentry/babel-plugin-component-annotate": "4.8.0", "@sentry/cli": "^2.57.0", "dotenv": "^16.3.1", "find-up": "^5.0.0", "glob": "^10.5.0", "magic-string": "0.30.8", "unplugin": "1.0.1" } }, "sha512-QaXd/NzaZ2vmiA2FNu2nBkgQU+17N3fE+zVOTzG0YK54QDSJMd4n3AeJIEyPhSzkOob+GqtO22nbYf6AATFMAw=="], "@sentry/cli": ["@sentry/cli@2.58.4", "", { "dependencies": { "https-proxy-agent": "^5.0.0", "node-fetch": "^2.6.7", "progress": "^2.0.3", "proxy-from-env": "^1.1.0", "which": "^2.0.2" }, "optionalDependencies": { "@sentry/cli-darwin": "2.58.4", "@sentry/cli-linux-arm": "2.58.4", "@sentry/cli-linux-arm64": "2.58.4", "@sentry/cli-linux-i686": "2.58.4", "@sentry/cli-linux-x64": "2.58.4", "@sentry/cli-win32-arm64": "2.58.4", "@sentry/cli-win32-i686": "2.58.4", "@sentry/cli-win32-x64": "2.58.4" }, "bin": { "sentry-cli": "bin/sentry-cli" } }, "sha512-ArDrpuS8JtDYEvwGleVE+FgR+qHaOp77IgdGSacz6SZy6Lv90uX0Nu4UrHCQJz8/xwIcNxSqnN22lq0dH4IqTg=="], @@ -1319,7 +1319,7 @@ "@sentry/react": ["@sentry/react@10.36.0", "", { "dependencies": { "@sentry/browser": "10.36.0", "@sentry/core": "10.36.0" }, "peerDependencies": { "react": "^16.14.0 || 17.x || 18.x || 19.x" } }, "sha512-k2GwMKgepJLXvEQffQymQyxsTVjsLiY6YXG0bcceM3vulii9Sy29uqGhpqwaPOfM4bPQzUXJzAxS/c9S7n5hTw=="], - "@sentry/vite-plugin": ["@sentry/vite-plugin@4.7.0", "", { "dependencies": { "@sentry/bundler-plugin-core": "4.7.0", "unplugin": "1.0.1" } }, "sha512-eQXDghOQLsYwnHutJo8TCzhG4gp0KLNq3h96iqFMhsbjnNnfYeCX1lIw1pJEh/az3cDwSyPI/KGkvf8hr0dZmQ=="], + "@sentry/vite-plugin": ["@sentry/vite-plugin@4.8.0", "", { "dependencies": { "@sentry/bundler-plugin-core": "4.8.0", "unplugin": "1.0.1" } }, "sha512-/YZJitGsx/o72FFQYy3tucUfs4w3COvSI1d2NYyAhIzay4tjLLRjpM5PdwFnoBT7Uj/7jSbuHkg87PAliLiu2g=="], "@shikijs/core": ["@shikijs/core@3.21.0", "", { "dependencies": { "@shikijs/types": "3.21.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA=="], @@ -1339,7 +1339,7 @@ "@smithy/config-resolver": ["@smithy/config-resolver@4.4.6", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ=="], - "@smithy/core": ["@smithy/core@3.21.0", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.9", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-stream": "^4.5.10", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-bg2TfzgsERyETAxc/Ims/eJX8eAnIeTi4r4LHpMpfF/2NyO6RsWis0rjKcCPaGksljmOb23BZRiCeT/3NvwkXw=="], + "@smithy/core": ["@smithy/core@3.21.1", "", { "dependencies": { "@smithy/middleware-serde": "^4.2.9", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-stream": "^4.5.10", "@smithy/util-utf8": "^4.2.0", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-NUH8R4O6FkN8HKMojzbGg/5pNjsfTjlMmeFclyPfPaXXUrbr5TzhWgbf7t92wfrpCHRgpjyz7ffASIS3wX28aA=="], "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw=="], @@ -1357,9 +1357,9 @@ "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.8", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A=="], - "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.10", "", { "dependencies": { "@smithy/core": "^3.21.0", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-kwWpNltpxrvPabnjEFvwSmA+66l6s2ReCvgVSzW/z92LU4T28fTdgZ18IdYRYOrisu2NMQ0jUndRScbO65A/zg=="], + "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.11", "", { "dependencies": { "@smithy/core": "^3.21.1", "@smithy/middleware-serde": "^4.2.9", "@smithy/node-config-provider": "^4.3.8", "@smithy/shared-ini-file-loader": "^4.4.3", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-middleware": "^4.2.8", "tslib": "^2.6.2" } }, "sha512-/WqsrycweGGfb9sSzME4CrsuayjJF6BueBmkKlcbeU5q18OhxRrvvKlmfw3tpDsK5ilx2XUJvoukwxHB0nHs/Q=="], - "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.26", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", "@smithy/smithy-client": "^4.10.11", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-ozZMoTAr+B2aVYfLYfkssFvc8ZV3p/vLpVQ7/k277xxUOA9ykSPe5obL2j6yHfbdrM/SZV7qj0uk/hSqavHrLw=="], + "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.27", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/protocol-http": "^5.3.8", "@smithy/service-error-classification": "^4.2.8", "@smithy/smithy-client": "^4.10.12", "@smithy/types": "^4.12.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" } }, "sha512-xFUYCGRVsfgiN5EjsJJSzih9+yjStgMTCLANPlf0LVQkPDYCe0hz97qbdTZosFOiYlGBlHYityGRxrQ/hxhfVQ=="], "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.9", "", { "dependencies": { "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ=="], @@ -1383,7 +1383,7 @@ "@smithy/signature-v4": ["@smithy/signature-v4@5.3.8", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.0", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-hex-encoding": "^4.2.0", "@smithy/util-middleware": "^4.2.8", "@smithy/util-uri-escape": "^4.2.0", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg=="], - "@smithy/smithy-client": ["@smithy/smithy-client@4.10.11", "", { "dependencies": { "@smithy/core": "^3.21.0", "@smithy/middleware-endpoint": "^4.4.10", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.10", "tslib": "^2.6.2" } }, "sha512-6o804SCyHGMXAb5mFJ+iTy9kVKv7F91a9szN0J+9X6p8A0NrdpUxdaC57aye2ipQkP2C4IAqETEpGZ0Zj77Haw=="], + "@smithy/smithy-client": ["@smithy/smithy-client@4.10.12", "", { "dependencies": { "@smithy/core": "^3.21.1", "@smithy/middleware-endpoint": "^4.4.11", "@smithy/middleware-stack": "^4.2.8", "@smithy/protocol-http": "^5.3.8", "@smithy/types": "^4.12.0", "@smithy/util-stream": "^4.5.10", "tslib": "^2.6.2" } }, "sha512-VKO/HKoQ5OrSHW6AJUmEnUKeXI1/5LfCwO9cwyao7CmLvGnZeM1i36Lyful3LK1XU7HwTVieTqO1y2C/6t3qtA=="], "@smithy/types": ["@smithy/types@4.12.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw=="], @@ -1399,9 +1399,9 @@ "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q=="], - "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.25", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.10.11", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-8ugoNMtss2dJHsXnqsibGPqoaafvWJPACmYKxJ4E6QWaDrixsAemmiMMAVbvwYadjR0H9G2+AlzsInSzRi8PSw=="], + "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.26", "", { "dependencies": { "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.10.12", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-vva0dzYUTgn7DdE0uaha10uEdAgmdLnNFowKFjpMm6p2R0XDk5FHPX3CBJLzWQkQXuEprsb0hGz9YwbicNWhjw=="], - "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.28", "", { "dependencies": { "@smithy/config-resolver": "^4.4.6", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.10.11", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-mjUdcP8h3E0K/XvNMi9oBXRV3DMCzeRiYIieZ1LQ7jq5tu6GH/GTWym7a1xIIE0pKSoLcpGsaImuQhGPSIJzAA=="], + "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.29", "", { "dependencies": { "@smithy/config-resolver": "^4.4.6", "@smithy/credential-provider-imds": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/property-provider": "^4.2.8", "@smithy/smithy-client": "^4.10.12", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-c6D7IUBsZt/aNnTBHMTf+OVh+h/JcxUUgfTcIJaWRe6zhOum1X+pNKSZtZ+7fbOn5I99XVFtmrnXKv8yHHErTQ=="], "@smithy/util-endpoints": ["@smithy/util-endpoints@3.2.8", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.8", "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw=="], @@ -1435,19 +1435,19 @@ "@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="], - "@supabase/auth-js": ["@supabase/auth-js@2.91.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-9ywvsKLsxTwv7fvN5fXzP3UfRreqrX2waylTBDu0lkmeHXa8WtSQS9e0WV9FBduiazYqQbgfBQXBNPRPsRgWOQ=="], + "@supabase/auth-js": ["@supabase/auth-js@2.91.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-3gFGMPuif2BOuAHXLAGsoOlDa64PROct1v7G94pMnvUAhh75u6+vnx4MYz1wyoyDBN5lCkJPGQNg5+RIgqxnpA=="], - "@supabase/functions-js": ["@supabase/functions-js@2.91.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-WaakXOqLK1mLtBNFXp5o5T+LlI6KZuADSeXz+9ofPRG5OpVSvW148LVJB1DRZ16Phck1a0YqIUswOUgxCz6vMw=="], + "@supabase/functions-js": ["@supabase/functions-js@2.91.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-xKepd3HZ6K6rKibriehKggIegsoz+jjV67tikN51q/YQq3AlUAkjUMSnMrqs8t5LMlAi+a3dJU812acXanR0cw=="], - "@supabase/postgrest-js": ["@supabase/postgrest-js@2.91.0", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-5S41zv2euNpGucvtM4Wy+xOmLznqt/XO+Lh823LOFEQ00ov7QJfvqb6VzIxufvzhooZpmGR0BxvMcJtWxCIFdQ=="], + "@supabase/postgrest-js": ["@supabase/postgrest-js@2.91.1", "", { "dependencies": { "tslib": "2.8.1" } }, "sha512-UKumTC6SGHd65G/5Gj0V58u+SkUyiH4zEJ8OP2eb06+Tqnges1E/3Tl7lyq2qbcMP8nEyH/0M7m2bYjrn++haw=="], - "@supabase/realtime-js": ["@supabase/realtime-js@2.91.0", "", { "dependencies": { "@types/phoenix": "^1.6.6", "@types/ws": "^8.18.1", "tslib": "2.8.1", "ws": "^8.18.2" } }, "sha512-u2YuJFG35umw8DO9beC27L/jYXm3KhF+73WQwbynMpV0tXsFIA0DOGRM0NgRyy03hJIdO6mxTTwe8efW3yx3Tg=="], + "@supabase/realtime-js": ["@supabase/realtime-js@2.91.1", "", { "dependencies": { "@types/phoenix": "^1.6.6", "@types/ws": "^8.18.1", "tslib": "2.8.1", "ws": "^8.18.2" } }, "sha512-Y4rifuvzekFgd2hUfiEvcMoh/JU3s1hmpWYS7tNGL2QHuFfWg8a4w/qg5qoSMVDvgGRz6G4L6yB1FaQRTplENQ=="], "@supabase/ssr": ["@supabase/ssr@0.5.2", "", { "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^0.7.0" }, "peerDependencies": { "@supabase/supabase-js": "^2.43.4" } }, "sha512-n3plRhr2Bs8Xun1o4S3k1CDv17iH5QY9YcoEvXX3bxV1/5XSasA0mNXYycFmADIdtdE6BG9MRjP5CGIs8qxC8A=="], - "@supabase/storage-js": ["@supabase/storage-js@2.91.0", "", { "dependencies": { "iceberg-js": "^0.8.1", "tslib": "2.8.1" } }, "sha512-CI7fsVIBQHfNObqU9kmyQ1GWr+Ug44y4rSpvxT4LdQB9tlhg1NTBov6z7Dlmt8d6lGi/8a9lf/epCDxyWI792g=="], + "@supabase/storage-js": ["@supabase/storage-js@2.91.1", "", { "dependencies": { "iceberg-js": "^0.8.1", "tslib": "2.8.1" } }, "sha512-hMJNT2tSleOrWwx4FmHTpihIA2PRDixAsWflECuQ4YDkeduBZGX5m2txnstMnteWW+H+mm+92WRRFLuidXqbfA=="], - "@supabase/supabase-js": ["@supabase/supabase-js@2.91.0", "", { "dependencies": { "@supabase/auth-js": "2.91.0", "@supabase/functions-js": "2.91.0", "@supabase/postgrest-js": "2.91.0", "@supabase/realtime-js": "2.91.0", "@supabase/storage-js": "2.91.0" } }, "sha512-Rjb0QqkKrmXMVwUOdEqysPBZ0ZDZakeptTkUa6k2d8r3strBdbWVDqjOdkCjAmvvZMtXecBeyTyMEXD1Zzjfvg=="], + "@supabase/supabase-js": ["@supabase/supabase-js@2.91.1", "", { "dependencies": { "@supabase/auth-js": "2.91.1", "@supabase/functions-js": "2.91.1", "@supabase/postgrest-js": "2.91.1", "@supabase/realtime-js": "2.91.1", "@supabase/storage-js": "2.91.1" } }, "sha512-57Fb4s5nfLn5ed2a1rPtl+LI1Wbtms8MS4qcUa0w6luaStBlFhmSeD2TLBgJWdMIupWRF6iFTH4QTrO2+pG/ZQ=="], "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], @@ -1485,17 +1485,17 @@ "@tanstack/devtools-event-client": ["@tanstack/devtools-event-client@0.4.0", "", {}, "sha512-RPfGuk2bDZgcu9bAJodvO2lnZeHuz4/71HjZ0bGb/SPg8+lyTA+RLSKQvo7fSmPSi8/vcH3aKQ8EM9ywf1olaw=="], - "@tanstack/form-core": ["@tanstack/form-core@1.27.7", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.4.0", "@tanstack/pacer-lite": "^0.1.1", "@tanstack/store": "^0.7.7" } }, "sha512-nvogpyE98fhb0NDw1Bf2YaCH+L7ZIUgEpqO9TkHucDn6zg3ni521boUpv0i8HKIrmmFwDYjWZoCnrgY4HYWTkw=="], + "@tanstack/form-core": ["@tanstack/form-core@1.28.0", "", { "dependencies": { "@tanstack/devtools-event-client": "^0.4.0", "@tanstack/pacer-lite": "^0.1.1", "@tanstack/store": "^0.7.7" } }, "sha512-MX3YveB6SKHAJ2yUwp+Ca/PCguub8bVEnLcLUbFLwdkSRMkP0lMGdaZl+F0JuEgZw56c6iFoRyfILhS7OQpydA=="], "@tanstack/pacer-lite": ["@tanstack/pacer-lite@0.1.1", "", {}, "sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w=="], - "@tanstack/query-core": ["@tanstack/query-core@5.90.19", "", {}, "sha512-GLW5sjPVIvH491VV1ufddnfldyVB+teCnpPIvweEfkpRx7CfUmUGhoh9cdcUKBh/KwVxk22aNEDxeTsvmyB/WA=="], + "@tanstack/query-core": ["@tanstack/query-core@5.90.20", "", {}, "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg=="], "@tanstack/query-devtools": ["@tanstack/query-devtools@5.92.0", "", {}, "sha512-N8D27KH1vEpVacvZgJL27xC6yPFUy0Zkezn5gnB3L3gRCxlDeSuiya7fKge8Y91uMTnC8aSxBQhcK6ocY7alpQ=="], - "@tanstack/react-form": ["@tanstack/react-form@1.27.7", "", { "dependencies": { "@tanstack/form-core": "1.27.7", "@tanstack/react-store": "^0.8.0" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-xTg4qrUY0fuLaSnkATLZcK3BWlnwLp7IuAb6UTbZKngiDEvvDCNTvVvHgPlgef1O2qN4klZxInRyRY6oEkXZ2A=="], + "@tanstack/react-form": ["@tanstack/react-form@1.28.0", "", { "dependencies": { "@tanstack/form-core": "1.28.0", "@tanstack/react-store": "^0.8.0" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-ibLcf5QkTogV0Ly944CuqGxWTpHyreNA4Cy8Wtky7zE9wtE3HVapQt4/hUuXo51zihfTkv5URiXpoTSKF5Xosg=="], - "@tanstack/react-query": ["@tanstack/react-query@5.90.19", "", { "dependencies": { "@tanstack/query-core": "5.90.19" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-qTZRZ4QyTzQc+M0IzrbKHxSeISUmRB3RPGmao5bT+sI6ayxSRhn0FXEnT5Hg3as8SBFcRosrXXRFB+yAcxVxJQ=="], + "@tanstack/react-query": ["@tanstack/react-query@5.90.20", "", { "dependencies": { "@tanstack/query-core": "5.90.20" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw=="], "@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.91.2", "", { "dependencies": { "@tanstack/query-devtools": "5.92.0" }, "peerDependencies": { "@tanstack/react-query": "^5.90.14", "react": "^18 || ^19" } }, "sha512-ZJ1503ay5fFeEYFUdo7LMNFzZryi6B0Cacrgr2h1JRkvikK1khgIq6Nq2EcblqEdIlgB/r7XDW8f8DQ89RuUgg=="], @@ -1749,21 +1749,21 @@ "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.53.1", "", { "dependencies": { "@typescript-eslint/types": "8.53.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg=="], - "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260120.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260120.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260120.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260120.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260120.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260120.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260120.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260120.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-nnEf37C9ue7OBRnF2zmV/OCBmV5Y7T/K4mCHa+nxgiXcF/1w8sA0cgdFl+gHQ0mysqUJ+Bu5btAMeWgpLyjrgg=="], + "@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260126.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260126.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260126.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260126.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260126.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260126.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260126.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260126.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-YlWe5IrAhleaAfGE5m8DQz/KhJyp71pzwXOU/zh1/nWpDAeqMlRzRRLPRMx7AbsIqPB5sClMg20DVw1mn1YWYw=="], - "@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260120.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-r3pWFuR2H7mn6ScwpH5jJljKQqKto0npVuJSk6pRwFwexpTyxOGmJTZJ1V0AWiisaNxU2+CNAqWFJSJYIE/QTg=="], + "@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260126.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4VgZc/kKQEzPj4QJ7DIq9Wocy4ilpV4Qmy7OV80aPSwzR4mnp8ovyiu1++c3JptthdCNe5KVQguSRn2SseBvDA=="], - "@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260120.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-cuC1+wLbUP+Ip2UT94G134fqRdp5w3b3dhcCO6/FQ4yXxvRNyv/WK+upHBUFDaeSOeHgDTyO9/QFYUWwC4If1A=="], + "@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260126.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-mY1nYNE+CFUEIMemKPz4QSsOx3rnNLsmyT2OPcTIHSU7/6P4BPlasJ1jKtO60ydAbEjJEGLnpeSZGWsBQghtYw=="], - "@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260120.1", "", { "os": "linux", "cpu": "arm" }, "sha512-vN6OYVySol/kQZjJGmAzd6L30SyVlCgmCXS8WjUYtE5clN0YrzQHop16RK29fYZHMxpkOniVBtRPxUYQANZBlQ=="], + "@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260126.1", "", { "os": "linux", "cpu": "arm" }, "sha512-rPGcUjzuDx/t9ZfCkBLn15PQBr7FKd41sKAG3q2auy/0fLhTGzdaDQ3Qai7y5BtIswX5B19YJZAOPx3Z4Jzyew=="], - "@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260120.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-zZGvEGY7wcHYefMZ87KNmvjN3NLIhsCMHEpHZiGCS3khKf+8z6ZsanrzCjOTodvL01VPyBzHxV1EtkSxAcLiQg=="], + "@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260126.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-V+eVxduCKUsujuRFVbmwUF4ZfGKlaJP5XYml+96TzW+m+bv+K0nCWmWQTFVF8e/UOH+NoW6mkb7p0KUQGQpV7A=="], - "@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260120.1", "", { "os": "linux", "cpu": "x64" }, "sha512-JBfNhWd/asd5MDeS3VgRvE24pGKBkmvLub6tsux6ypr+Yhy+o0WaAEzVpmlRYZUqss2ai5tvOu4dzPBXzZAtFw=="], + "@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260126.1", "", { "os": "linux", "cpu": "x64" }, "sha512-8F6WZkVu1O0Ob/weZ2siR8zitBbp7dPzOn3/jaC++FXXD9R1t/ILQ1WcLYUrW/W0U1bwq6Aenrs0xJq32Nn8WQ=="], - "@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260120.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-tTndRtYCq2xwgE0VkTi9ACNiJaV43+PqvBqCxk8ceYi3X36Ve+CCnwlZfZJ4k9NxZthtrAwF/kUmpC9iIYbq1w=="], + "@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260126.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-UaHT07kICWJXstad3o5LA6PugKxOmmPRtbXrLcwaH6Q+67cJ4RMuWxsxl6KDgcouiooMM/TR2lSclYp+4bsz8g=="], - "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260120.1", "", { "os": "win32", "cpu": "x64" }, "sha512-oZia7hFL6k9pVepfonuPI86Jmyz6WlJKR57tWCDwRNmpA7odxuTq1PbvcYgy1z4+wHF1nnKKJY0PMAiq6ac18w=="], + "@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260126.1", "", { "os": "win32", "cpu": "x64" }, "sha512-NpP2XaMQrQHqP+fl5mM7xJOnUFlRi9IvIqxJ/AyrcforDt+0N5b9Jk8pYWqAyUgdmvPjvPgcVE6KanXUby50OQ=="], "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], @@ -1821,7 +1821,7 @@ "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], - "ai": ["ai@6.0.44", "", { "dependencies": { "@ai-sdk/gateway": "3.0.18", "@ai-sdk/provider": "3.0.4", "@ai-sdk/provider-utils": "4.0.8", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-fOyssuNfSB3i3ODuDeLaWambfJY+gE3LRHQf4nmWEPDrQhfiB+fcpVMVi77LbFRBfoG/h5BrmX0heVqz4Hl7ug=="], + "ai": ["ai@6.0.49", "", { "dependencies": { "@ai-sdk/gateway": "3.0.22", "@ai-sdk/provider": "3.0.5", "@ai-sdk/provider-utils": "4.0.9", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-LABniBX/0R6Tv+iUK5keUZhZLaZUe4YjP5M2rZ4wAdZ8iKV3EfTAoJxuL1aaWTSJKIilKa9QUEkCgnp89/32bw=="], "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], @@ -1865,11 +1865,11 @@ "auto-bind": ["auto-bind@5.0.1", "", {}, "sha512-ooviqdwwgfIfNmDwo94wlshcdzfO64XV0Cg6oDsDYBJfITDz1EngD2z7DkbvCWn+XIMsIqW27sEVF6qcpJrRcg=="], - "autumn-js": ["autumn-js@0.1.69", "", { "dependencies": { "query-string": "^9.2.2", "rou3": "^0.6.1", "swr": "^2.3.3", "zod": "^4.0.0" }, "peerDependencies": { "better-auth": "^1.3.17", "better-call": "^1.0.12", "convex": "^1.25.4" }, "optionalPeers": ["better-auth", "better-call", "convex"] }, "sha512-rCauyN0HwksA3K1/6sW70TVcb/jV0AWEC7lukgkW8bs1Qly0DZGdN8mIo96Hrb1G0ZvHbzFb5kFf4uxHpmre5Q=="], + "autumn-js": ["autumn-js@0.1.71", "", { "dependencies": { "query-string": "^9.2.2", "rou3": "^0.6.1", "swr": "^2.3.3", "zod": "^4.0.0" }, "peerDependencies": { "better-auth": "^1.3.17", "better-call": "^1.0.12", "convex": "^1.25.4" }, "optionalPeers": ["better-auth", "better-call", "convex"] }, "sha512-nfWqiKl2isEDCOIl/00/DXWCY3Wv7Co8gAzNahQ8Z4MWxst6vA6Kt0Qv95mVuHgp9QXfbX8Ejq4aJfZuubFVMg=="], "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], - "axios": ["axios@1.13.2", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA=="], + "axios": ["axios@1.13.3", "", { "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", "proxy-from-env": "^1.1.0" } }, "sha512-ERT8kdX7DZjtUm7IitEyV7InTHAF42iJuMArIiDIV5YtPanJkgw4hw5Dyg9fh0mihdWNn1GKaeIWErfe56UQ1g=="], "b4a": ["b4a@1.7.3", "", { "peerDependencies": { "react-native-b4a": "*" }, "optionalPeers": ["react-native-b4a"] }, "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q=="], @@ -1897,7 +1897,7 @@ "base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.9.17", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-agD0MgJFUP/4nvjqzIB29zRPUuCF7Ge6mEv9s8dHrtYD7QWXRcx75rOADE/d5ah1NI+0vkDl0yorDd5U852IQQ=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.9.18", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA=="], "basic-ftp": ["basic-ftp@5.1.0", "", {}, "sha512-RkaJzeJKDbaDWTIPiJwubyljaEPwpVWkm9Rt5h9Nd6h7tEXTJ3VB4qxdZBioV7JO5yLUaOKwz7vDOzlncUsegw=="], @@ -1955,7 +1955,7 @@ "builtin-status-codes": ["builtin-status-codes@3.0.0", "", {}, "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ=="], - "bullmq": ["bullmq@5.66.5", "", { "dependencies": { "cron-parser": "4.9.0", "ioredis": "5.9.1", "msgpackr": "1.11.5", "node-abort-controller": "3.1.1", "semver": "7.7.3", "tslib": "2.8.1", "uuid": "11.1.0" } }, "sha512-DC1E7P03L+TfNHv+2SGxwNYvtb0oJPODWSKkWdfis0heU5zFW16vjM7fCjwlxMdGWw2w28EI3mTRfYLEHeQQSw=="], + "bullmq": ["bullmq@5.67.1", "", { "dependencies": { "cron-parser": "4.9.0", "ioredis": "5.9.2", "msgpackr": "1.11.5", "node-abort-controller": "3.1.1", "semver": "7.7.3", "tslib": "2.8.1", "uuid": "11.1.0" } }, "sha512-ELJEAzwzesgFxk29emvnAakqrwdBEhEyfZREPQ8pbG4ALVz/mk/AhfuChzxkFpJ7SfL2qclPHbiUGBZzaqcLvg=="], "bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="], @@ -1973,7 +1973,7 @@ "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="], - "caniuse-lite": ["caniuse-lite@1.0.30001765", "", {}, "sha512-LWcNtSyZrakjECqmpP4qdg0MMGdN368D7X8XvvAqOcqMv0RxnlqVKZl2V6/mBR68oYMxOZPLw/gO7DuisMHUvQ=="], + "caniuse-lite": ["caniuse-lite@1.0.30001766", "", {}, "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA=="], "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], @@ -2003,7 +2003,7 @@ "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - "chromium-bidi": ["chromium-bidi@12.0.1", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-fGg+6jr0xjQhzpy5N4ErZxQ4wF7KLEvhGZXD6EgvZKDhu7iOhZXnZhcDxPJDcwTcrD48NPzOCo84RP2lv3Z+Cg=="], + "chromium-bidi": ["chromium-bidi@13.0.1", "", { "dependencies": { "mitt": "^3.0.1", "zod": "^3.24.1" }, "peerDependencies": { "devtools-protocol": "*" } }, "sha512-c+RLxH0Vg2x2syS9wPw378oJgiJNXtYXUvnVAldUlt5uaHekn0CCU7gPksNgHjrH1qFhmjVXQj4esvuthuC7OQ=="], "cipher-base": ["cipher-base@1.0.7", "", { "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1", "to-buffer": "^1.2.2" } }, "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA=="], @@ -2085,7 +2085,7 @@ "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="], - "cors": ["cors@2.8.5", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g=="], + "cors": ["cors@2.8.6", "", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], "cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="], @@ -2261,7 +2261,7 @@ "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], - "devtools-protocol": ["devtools-protocol@0.0.1534754", "", {}, "sha512-26T91cV5dbOYnXdJi5qQHoTtUoNEqwkHcAyu/IKtjIAxiEqPMrDiRkDOPWVsGfNZGmlQVHQbZRSjD8sxagWVsQ=="], + "devtools-protocol": ["devtools-protocol@0.0.1551306", "", {}, "sha512-CFx8QdSim8iIv+2ZcEOclBKTQY6BI1IEDa7Tm9YkwAXzEWFndTEzpTo5jAUhSnq24IC7xaDw0wvGcm96+Y3PEg=="], "dezalgo": ["dezalgo@1.0.4", "", { "dependencies": { "asap": "^2.0.0", "wrappy": "1" } }, "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig=="], @@ -2307,7 +2307,7 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - "electron-to-chromium": ["electron-to-chromium@1.5.267", "", {}, "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw=="], + "electron-to-chromium": ["electron-to-chromium@1.5.278", "", {}, "sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw=="], "elliptic": ["elliptic@6.6.1", "", { "dependencies": { "bn.js": "^4.11.9", "brorand": "^1.1.0", "hash.js": "^1.0.0", "hmac-drbg": "^1.0.1", "inherits": "^2.0.4", "minimalistic-assert": "^1.0.1", "minimalistic-crypto-utils": "^1.0.1" } }, "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g=="], @@ -2475,7 +2475,7 @@ "forwarded-parse": ["forwarded-parse@2.1.2", "", {}, "sha512-alTFZZQDKMporBH77856pXgzhEzaUVmLCDk+egLgIgHst3Tpndzz8MnKe+GzRJRfvVdn69HhpW7cmXzvtLvJAw=="], - "framer-motion": ["framer-motion@12.28.1", "", { "dependencies": { "motion-dom": "^12.28.1", "motion-utils": "^12.27.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-72GkO7DS4FfcSjf26wx0v+rzkW8Fhn4Djh04aDbuEg7NYG8X8MhJZc6/5weG/YeEgIP+fCo8FS2y1HnXH8k8fQ=="], + "framer-motion": ["framer-motion@12.29.2", "", { "dependencies": { "motion-dom": "^12.29.2", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-lSNRzBJk4wuIy0emYQ/nfZ7eWhqud2umPKw2QAQki6uKhZPKm2hRQHeQoHTG9MIvfobb+A/LbEWPJU794ZUKrg=="], "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], @@ -2799,7 +2799,7 @@ "jws": ["jws@4.0.1", "", { "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA=="], - "katex": ["katex@0.16.27", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-aeQoDkuRWSqQN6nSvVCEFvfXdqo1OQiCmmW1kc9xSdjutPv7BGO7pqY9sQRJpMOGrEdfDgF2TfRXe5eUAD2Waw=="], + "katex": ["katex@0.16.28", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-YHzO7721WbmAL6Ov1uzN/l5mY5WWWhJBSW+jq4tkfZfsxmo1hu6frS0EOswvjBUnWE6NtjEs48SFn5CQESRLZg=="], "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], @@ -2809,11 +2809,11 @@ "kysely": ["kysely@0.28.10", "", {}, "sha512-ksNxfzIW77OcZ+QWSAPC7yDqUSaIVwkTWnTPNiIy//vifNbwsSgQ57OkkncHxxpcBHM3LRfLAZVEh7kjq5twVA=="], - "langchain": ["langchain@1.2.11", "", { "dependencies": { "@langchain/langgraph": "^1.0.0", "@langchain/langgraph-checkpoint": "^1.0.0", "langsmith": ">=0.4.0 <1.0.0", "uuid": "^10.0.0", "zod": "^3.25.76 || ^4" }, "peerDependencies": { "@langchain/core": "1.1.16" } }, "sha512-vLDlibIbcaVt+H4Jw4lDCrHnSCGhcg/U5oELp9tdfoCkGpuDsrU9qv1wW0WW+ClypLCEbWiD5qJ+IqrOm6UiBQ=="], + "langchain": ["langchain@1.2.13", "", { "dependencies": { "@langchain/langgraph": "^1.1.2", "@langchain/langgraph-checkpoint": "^1.0.0", "langsmith": ">=0.4.0 <1.0.0", "uuid": "^10.0.0", "zod": "^3.25.76 || ^4" }, "peerDependencies": { "@langchain/core": "1.1.17" } }, "sha512-4ImLm53St3iCLBhmJvlnunl8P9p7ZM1oUC1I7WQwPHL3aBNHBlLucOmr7hW2Wk9bZ3+A9XSj09iZKvmEO4Zo7w=="], "langium": ["langium@3.3.1", "", { "dependencies": { "chevrotain": "~11.0.3", "chevrotain-allstar": "~0.3.0", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.11", "vscode-uri": "~3.0.8" } }, "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w=="], - "langsmith": ["langsmith@0.4.7", "", { "dependencies": { "@types/uuid": "^10.0.0", "chalk": "^4.1.2", "console-table-printer": "^2.12.1", "p-queue": "^6.6.2", "semver": "^7.6.3", "uuid": "^10.0.0" }, "peerDependencies": { "@opentelemetry/api": "*", "@opentelemetry/exporter-trace-otlp-proto": "*", "@opentelemetry/sdk-trace-base": "*", "openai": "*" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/exporter-trace-otlp-proto", "@opentelemetry/sdk-trace-base", "openai"] }, "sha512-Esv5g/J8wwRwbGQr10PB9+bLsNk0mWbrXc7nnEreQDhh0azbU57I7epSnT7GC4sS4EOWavhbxk+6p8PTXtreHw=="], + "langsmith": ["langsmith@0.4.9", "", { "dependencies": { "@types/uuid": "^10.0.0", "chalk": "^4.1.2", "console-table-printer": "^2.12.1", "p-queue": "^6.6.2", "semver": "^7.6.3", "uuid": "^10.0.0" }, "peerDependencies": { "@opentelemetry/api": "*", "@opentelemetry/exporter-trace-otlp-proto": "*", "@opentelemetry/sdk-trace-base": "*", "openai": "*" }, "optionalPeers": ["@opentelemetry/api", "@opentelemetry/exporter-trace-otlp-proto", "@opentelemetry/sdk-trace-base", "openai"] }, "sha512-p2/BbVDAfgiqGx8iZzPoWQ+z/Actl6isbDJcogApYIz9UcMNu0jcDzGbFivJny2vihF0gMT6yhUIOZCsqZnNsg=="], "layout-base": ["layout-base@1.0.2", "", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="], @@ -3037,11 +3037,11 @@ "moo": ["moo@0.5.1", "", {}, "sha512-I1mnb5xn4fO80BH9BLcF0yLypy2UKl+Cb01Fu0hJRkJjlCRtxZMWkTdAtDd5ZqCOxtCkhmRwyI57vWT+1iZ67w=="], - "motion": ["motion@12.28.1", "", { "dependencies": { "framer-motion": "^12.28.1", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-qGq5+6r4IMivHbT2EUhCwxz2NgFBuba3sWDrxcHt06+nYqKMevYJiVh/N90nMRof+vIUpiq8C22ZeOXwkWWiZg=="], + "motion": ["motion@12.29.2", "", { "dependencies": { "framer-motion": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-jMpHdAzEDF1QQ055cB+1lOBLdJ6ialVWl6QQzpJI2OvmHequ7zFVHM2mx0HNAy+Tu4omUlApfC+4vnkX0geEOg=="], - "motion-dom": ["motion-dom@12.28.1", "", { "dependencies": { "motion-utils": "^12.27.2" } }, "sha512-xqgID69syDvXwFJnUd5bW6ajGUAr/qevRoUe/EqpsXUbVIopyWrAOiwQOhpgVQD+B7Ra60zTdj5gVkmwncebMg=="], + "motion-dom": ["motion-dom@12.29.2", "", { "dependencies": { "motion-utils": "^12.29.2" } }, "sha512-/k+NuycVV8pykxyiTCoFzIVLA95Nb1BFIVvfSu9L50/6K6qNeAYtkxXILy/LRutt7AzaYDc2myj0wkCVVYAPPA=="], - "motion-utils": ["motion-utils@12.27.2", "", {}, "sha512-B55gcoL85Mcdt2IEStY5EEAsrMSVE2sI14xQ/uAdPL+mfQxhKKFaEag9JmfxedJOR4vZpBGoPeC/Gm13I/4g5Q=="], + "motion-utils": ["motion-utils@12.29.2", "", {}, "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A=="], "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], @@ -3249,9 +3249,9 @@ "postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="], - "posthog-js": ["posthog-js@1.333.0", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/api-logs": "^0.208.0", "@opentelemetry/exporter-logs-otlp-http": "^0.208.0", "@opentelemetry/resources": "^2.2.0", "@opentelemetry/sdk-logs": "^0.208.0", "@posthog/core": "1.13.0", "@posthog/types": "1.333.0", "core-js": "^3.38.1", "dompurify": "^3.3.1", "fflate": "^0.4.8", "preact": "^10.28.0", "query-selector-shadow-dom": "^1.0.1", "web-vitals": "^4.2.4" } }, "sha512-c7vquERMedjuGE2GnaDDJW/V1BIMMQG7BlYKrH0z8O7fc3WpEsQ/IyQ+9aD9+DLxlDCFpzrwgoxVDWi9K37mdA=="], + "posthog-js": ["posthog-js@1.335.2", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/api-logs": "^0.208.0", "@opentelemetry/exporter-logs-otlp-http": "^0.208.0", "@opentelemetry/resources": "^2.2.0", "@opentelemetry/sdk-logs": "^0.208.0", "@posthog/core": "1.14.0", "@posthog/types": "1.335.2", "core-js": "^3.38.1", "dompurify": "^3.3.1", "fflate": "^0.4.8", "preact": "^10.28.0", "query-selector-shadow-dom": "^1.0.1", "web-vitals": "^5.1.0" } }, "sha512-xiPh9eXqNiNiFZjVe+HMcuEeqhbMJuL+bOVUM6ywGAfxUe71av71q6hK/zlzIiPNsPxhV6PL08LC6yPooStQxA=="], - "posthog-node": ["posthog-node@5.24.1", "", { "dependencies": { "@posthog/core": "1.13.0" } }, "sha512-1+wsosb5fjuor9zpp3h2uq0xKYY7rDz8gpw/10Scz8Ob/uVNrsHSwGy76D9rgt4cfyaEgpJwyYv+hPi2+YjWtw=="], + "posthog-node": ["posthog-node@5.24.2", "", { "dependencies": { "@posthog/core": "1.14.0" } }, "sha512-cywIUYtSIC9BilgLlZd1R2xNk6omKL6tywG/SCPmUJKeG2jhjvJHSrHXYx4x3uQsUjn8aB9UVI8km+W326Zm8g=="], "preact": ["preact@10.28.2", "", {}, "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA=="], @@ -3295,7 +3295,7 @@ "punycode": ["punycode@1.4.1", "", {}, "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ=="], - "puppeteer-core": ["puppeteer-core@24.35.0", "", { "dependencies": { "@puppeteer/browsers": "2.11.1", "chromium-bidi": "12.0.1", "debug": "^4.4.3", "devtools-protocol": "0.0.1534754", "typed-query-selector": "^2.12.0", "webdriver-bidi-protocol": "0.3.10", "ws": "^8.19.0" } }, "sha512-vt1zc2ME0kHBn7ZDOqLvgvrYD5bqNv5y2ZNXzYnCv8DEtZGw/zKhljlrGuImxptZ4rq+QI9dFGrUIYqG4/IQzA=="], + "puppeteer-core": ["puppeteer-core@24.36.0", "", { "dependencies": { "@puppeteer/browsers": "2.11.1", "chromium-bidi": "13.0.1", "debug": "^4.4.3", "devtools-protocol": "0.0.1551306", "typed-query-selector": "^2.12.0", "webdriver-bidi-protocol": "0.4.0", "ws": "^8.19.0" } }, "sha512-P3Ou0MAFDCQ0dK1d9F9+8jTrg6JvXjUacgG0YkJQP4kbEnUOGokSDEMmMId5ZhXD5HwsHM202E9VwEpEjWfwxg=="], "qs": ["qs@6.14.1", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ=="], @@ -3355,9 +3355,9 @@ "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], - "react-router": ["react-router@7.12.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-kTPDYPFzDVGIIGNLS5VJykK0HfHLY5MF3b+xj0/tTyNYL1gF1qs7u67Z9jEhQk2sQ98SUaHxlG31g1JtF7IfVw=="], + "react-router": ["react-router@7.13.0", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-PZgus8ETambRT17BUm/LL8lX3Of+oiLaPuVTRH3l1eLvSPpKO3AvhAEb5N7ihAFZQrYDqkvvWfFh9p0z9VsjLw=="], - "react-router-dom": ["react-router-dom@7.12.0", "", { "dependencies": { "react-router": "7.12.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA=="], + "react-router-dom": ["react-router-dom@7.13.0", "", { "dependencies": { "react-router": "7.13.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-5CO/l5Yahi2SKC6rGZ+HDEjpjkGaG/ncEP7eWFTvFxbHP8yeeI0PxTDjimtpXYlR3b3i9/WIL4VJttPrESIf2g=="], "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], @@ -3367,7 +3367,7 @@ "real-require": ["real-require@0.2.0", "", {}, "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg=="], - "recharts": ["recharts@3.6.0", "", { "dependencies": { "@reduxjs/toolkit": "1.x.x || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-L5bjxvQRAe26RlToBAziKUB7whaGKEwD3znoM6fz3DrTowCIC/FnJYnuq1GEzB8Zv2kdTfaxQfi5GoH0tBinyg=="], + "recharts": ["recharts@3.7.0", "", { "dependencies": { "@reduxjs/toolkit": "1.x.x || 2.x.x", "clsx": "^2.1.1", "decimal.js-light": "^2.5.1", "es-toolkit": "^1.39.3", "eventemitter3": "^5.0.1", "immer": "^10.1.1", "react-redux": "8.x.x || 9.x.x", "reselect": "5.1.1", "tiny-invariant": "^1.3.3", "use-sync-external-store": "^1.2.2", "victory-vendor": "^37.0.2" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-l2VCsy3XXeraxIID9fx23eCb6iCBsxUQDnE8tWm6DFdszVAO7WVY/ChAD9wVit01y6B2PMupYiMmQwhgPHc9Ew=="], "redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="], @@ -3441,7 +3441,7 @@ "robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="], - "rollup": ["rollup@4.55.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.55.3", "@rollup/rollup-android-arm64": "4.55.3", "@rollup/rollup-darwin-arm64": "4.55.3", "@rollup/rollup-darwin-x64": "4.55.3", "@rollup/rollup-freebsd-arm64": "4.55.3", "@rollup/rollup-freebsd-x64": "4.55.3", "@rollup/rollup-linux-arm-gnueabihf": "4.55.3", "@rollup/rollup-linux-arm-musleabihf": "4.55.3", "@rollup/rollup-linux-arm64-gnu": "4.55.3", "@rollup/rollup-linux-arm64-musl": "4.55.3", "@rollup/rollup-linux-loong64-gnu": "4.55.3", "@rollup/rollup-linux-loong64-musl": "4.55.3", "@rollup/rollup-linux-ppc64-gnu": "4.55.3", "@rollup/rollup-linux-ppc64-musl": "4.55.3", "@rollup/rollup-linux-riscv64-gnu": "4.55.3", "@rollup/rollup-linux-riscv64-musl": "4.55.3", "@rollup/rollup-linux-s390x-gnu": "4.55.3", "@rollup/rollup-linux-x64-gnu": "4.55.3", "@rollup/rollup-linux-x64-musl": "4.55.3", "@rollup/rollup-openbsd-x64": "4.55.3", "@rollup/rollup-openharmony-arm64": "4.55.3", "@rollup/rollup-win32-arm64-msvc": "4.55.3", "@rollup/rollup-win32-ia32-msvc": "4.55.3", "@rollup/rollup-win32-x64-gnu": "4.55.3", "@rollup/rollup-win32-x64-msvc": "4.55.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-y9yUpfQvetAjiDLtNMf1hL9NXchIJgWt6zIKeoB+tCd3npX08Eqfzg60V9DhIGVMtQ0AlMkFw5xa+AQ37zxnAA=="], + "rollup": ["rollup@4.56.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.56.0", "@rollup/rollup-android-arm64": "4.56.0", "@rollup/rollup-darwin-arm64": "4.56.0", "@rollup/rollup-darwin-x64": "4.56.0", "@rollup/rollup-freebsd-arm64": "4.56.0", "@rollup/rollup-freebsd-x64": "4.56.0", "@rollup/rollup-linux-arm-gnueabihf": "4.56.0", "@rollup/rollup-linux-arm-musleabihf": "4.56.0", "@rollup/rollup-linux-arm64-gnu": "4.56.0", "@rollup/rollup-linux-arm64-musl": "4.56.0", "@rollup/rollup-linux-loong64-gnu": "4.56.0", "@rollup/rollup-linux-loong64-musl": "4.56.0", "@rollup/rollup-linux-ppc64-gnu": "4.56.0", "@rollup/rollup-linux-ppc64-musl": "4.56.0", "@rollup/rollup-linux-riscv64-gnu": "4.56.0", "@rollup/rollup-linux-riscv64-musl": "4.56.0", "@rollup/rollup-linux-s390x-gnu": "4.56.0", "@rollup/rollup-linux-x64-gnu": "4.56.0", "@rollup/rollup-linux-x64-musl": "4.56.0", "@rollup/rollup-openbsd-x64": "4.56.0", "@rollup/rollup-openharmony-arm64": "4.56.0", "@rollup/rollup-win32-arm64-msvc": "4.56.0", "@rollup/rollup-win32-ia32-msvc": "4.56.0", "@rollup/rollup-win32-x64-gnu": "4.56.0", "@rollup/rollup-win32-x64-msvc": "4.56.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg=="], "rou3": ["rou3@0.6.3", "", {}, "sha512-1HSG1ENTj7Kkm5muMnXuzzfdDOf7CFnbSYFA+H3Fp/rB9lOCxCPgy1jlZxTKyFoC5jJay8Mmc+VbPLYRjzYLrA=="], @@ -3737,7 +3737,7 @@ "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], - "unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], + "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="], "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=="], @@ -3755,7 +3755,7 @@ "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="], - "use-stick-to-bottom": ["use-stick-to-bottom@1.1.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-JkDp0b0tSmv7HQOOpL1hT7t7QaoUBXkq045WWWOFDTlLGRzgIIyW7vyzOIJzY7L2XVIG7j1yUxeDj2LHm9Vwng=="], + "use-stick-to-bottom": ["use-stick-to-bottom@1.1.2", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-ssUfMNvfH8a8hGLoAt5kcOsjbsVORknon2tbkECuf3EsVucFFBbyXl+Xnv3b58P8ZRuZelzO81fgb6M0eRo8cg=="], "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="], @@ -3805,7 +3805,7 @@ "vscode-oniguruma": ["vscode-oniguruma@2.0.1", "", {}, "sha512-poJU8iHIWnC3vgphJnrLZyI3YdqRlR27xzqDmpPXYzA93R4Gk8z7T6oqDzDoHjoikA2aS82crdXFkjELCdJsjQ=="], - "vscode-textmate": ["vscode-textmate@9.3.1", "", {}, "sha512-U19nFkCraZF9/bkQKQYsb9mRqM9NwpToQQFl40nGiioZTH9gRtdtCHwp48cubayVfreX3ivnoxgxQgNwrTVmQg=="], + "vscode-textmate": ["vscode-textmate@9.3.2", "", {}, "sha512-n2uGbUcrjhUEBH16uGA0TvUfhWwliFZ1e3+pTjrkim1Mt7ydB41lV08aUvsi70OlzDWp6X7Bx3w/x3fAXIsN0Q=="], "vscode-uri": ["vscode-uri@3.0.8", "", {}, "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw=="], @@ -3813,9 +3813,9 @@ "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], - "web-vitals": ["web-vitals@4.2.4", "", {}, "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="], + "web-vitals": ["web-vitals@5.1.0", "", {}, "sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg=="], - "webdriver-bidi-protocol": ["webdriver-bidi-protocol@0.3.10", "", {}, "sha512-5LAE43jAVLOhB/QqX4bwSiv0Hg1HBfMmOuwBSXHdvg4GMGu9Y0lIq7p4R/yySu6w74WmaR4GM4H9t2IwLW7hgw=="], + "webdriver-bidi-protocol": ["webdriver-bidi-protocol@0.4.0", "", {}, "sha512-U9VIlNRrq94d1xxR9JrCEAx5Gv/2W7ERSv8oWRoNe/QYbfccS0V3h/H6qeNeCRJxXGMhhnkqvwNrvPAYeuP9VA=="], "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], @@ -3891,8 +3891,6 @@ "@amplitude/plugin-session-replay-browser/@amplitude/analytics-types": ["@amplitude/analytics-types@2.11.1", "", {}, "sha512-wFEgb0t99ly2uJKm5oZ28Lti0Kh5RecR5XBkwfUpDzn84IoCIZ8GJTsMw/nThu8FZFc7xFDA4UAt76zhZKrs9A=="], - "@amplitude/plugin-web-vitals-browser/web-vitals": ["web-vitals@5.1.0", "", {}, "sha512-ArI3kx5jI0atlTtmV0fWU3fjpLmq/nD3Zr1iFFlJLaqa5wLBkUSzINwBPySCX/8jRyjlmy1Volw1kz1g9XE4Jg=="], - "@amplitude/rrweb/@amplitude/rrweb-types": ["@amplitude/rrweb-types@2.0.0-alpha.35", "", {}, "sha512-cR/xlN5fu7Cw6Zh9O6iEgNleqT92wJ3HO2mV19yQE6SRqLGKXXeDeTrUBd5FKCZnXvRsv3JtK+VR4u9vmZze3g=="], "@amplitude/rrweb/@amplitude/rrweb-utils": ["@amplitude/rrweb-utils@2.0.0-alpha.35", "", {}, "sha512-/OpyKKHYGwoy2fvWDg5jiH1LzWag4wlFTQjd2DUgndxlXccQF1+yxYljCDdM+J1GBeZ7DaLZa9qe2JUUtoNOOw=="], @@ -3921,7 +3919,7 @@ "@autumn/vite/typescript": ["typescript@5.7.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw=="], - "@autumn/vite/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="], + "@autumn/vite/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "@aws-crypto/crc32/@aws-crypto/util": ["@aws-crypto/util@3.0.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-utf8-browser": "^3.0.0", "tslib": "^1.11.1" } }, "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w=="], @@ -4223,7 +4221,7 @@ "@aws-sdk/credential-provider-cognito-identity/@smithy/types": ["@smithy/types@3.7.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-bNwBYYmN8Eh9RyjS1p2gW6MIhSO2rl7X9QeLM8iTdcGRP+eDiIWDt66c9IysCc22gefKszZv+ubV9qZc7hdESg=="], - "@aws-sdk/credential-provider-sso/@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.972.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.972.0", "@aws-sdk/middleware-host-header": "3.972.0", "@aws-sdk/middleware-logger": "3.972.0", "@aws-sdk/middleware-recursion-detection": "3.972.0", "@aws-sdk/middleware-user-agent": "3.972.0", "@aws-sdk/region-config-resolver": "3.972.0", "@aws-sdk/types": "3.972.0", "@aws-sdk/util-endpoints": "3.972.0", "@aws-sdk/util-user-agent-browser": "3.972.0", "@aws-sdk/util-user-agent-node": "3.972.0", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.20.6", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.7", "@smithy/middleware-retry": "^4.4.23", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.10.8", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.22", "@smithy/util-defaults-mode-node": "^4.2.25", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-5qw6qLiRE4SUiz0hWy878dSR13tSVhbTWhsvFT8mGHe37NRRiaobm5MA2sWD0deRAuO98djSiV+dhWXa1xIFNw=="], + "@aws-sdk/credential-provider-sso/@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.974.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "^3.973.0", "@aws-sdk/middleware-host-header": "^3.972.1", "@aws-sdk/middleware-logger": "^3.972.1", "@aws-sdk/middleware-recursion-detection": "^3.972.1", "@aws-sdk/middleware-user-agent": "^3.972.1", "@aws-sdk/region-config-resolver": "^3.972.1", "@aws-sdk/types": "^3.973.0", "@aws-sdk/util-endpoints": "3.972.0", "@aws-sdk/util-user-agent-browser": "^3.972.1", "@aws-sdk/util-user-agent-node": "^3.972.1", "@smithy/config-resolver": "^4.4.6", "@smithy/core": "^3.21.0", "@smithy/fetch-http-handler": "^5.3.9", "@smithy/hash-node": "^4.2.8", "@smithy/invalid-dependency": "^4.2.8", "@smithy/middleware-content-length": "^4.2.8", "@smithy/middleware-endpoint": "^4.4.10", "@smithy/middleware-retry": "^4.4.26", "@smithy/middleware-serde": "^4.2.9", "@smithy/middleware-stack": "^4.2.8", "@smithy/node-config-provider": "^4.3.8", "@smithy/node-http-handler": "^4.4.8", "@smithy/protocol-http": "^5.3.8", "@smithy/smithy-client": "^4.10.11", "@smithy/types": "^4.12.0", "@smithy/url-parser": "^4.2.8", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.25", "@smithy/util-defaults-mode-node": "^4.2.28", "@smithy/util-endpoints": "^3.2.8", "@smithy/util-middleware": "^4.2.8", "@smithy/util-retry": "^4.2.8", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-ci+GiM0c4ULo4D79UMcY06LcOLcfvUfiyt8PzNY0vbt5O8BfCPYf4QomwVgkNcLLCYmroO4ge2Yy1EsLUlcD6g=="], "@aws-sdk/credential-providers/@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.598.0", "", { "dependencies": { "@aws-sdk/types": "3.598.0", "@smithy/property-provider": "^3.1.1", "@smithy/types": "^3.1.0", "tslib": "^2.6.2" } }, "sha512-vi1khgn7yXzLCcgSIzQrrtd2ilUM0dWodxj3PQ6BLfP0O+q1imO3hG1nq7DVyJtq7rFHs6+9N8G4mYvTkxby2w=="], @@ -4251,6 +4249,8 @@ "@aws-sdk/signature-v4/@smithy/signature-v4": ["@smithy/signature-v4@1.1.0", "", { "dependencies": { "@smithy/eventstream-codec": "^1.1.0", "@smithy/is-array-buffer": "^1.1.0", "@smithy/types": "^1.2.0", "@smithy/util-hex-encoding": "^1.1.0", "@smithy/util-middleware": "^1.1.0", "@smithy/util-uri-escape": "^1.1.0", "@smithy/util-utf8": "^1.1.0", "tslib": "^2.5.0" } }, "sha512-fDo3m7YqXBs7neciOePPd/X9LPm5QLlDMdIC4m1H6dgNLnXfLMFNIxEfPyohGA8VW9Wn4X8lygnPSGxDZSmp0Q=="], + "@aws-sdk/util-endpoints/@aws-sdk/types": ["@aws-sdk/types@3.972.0", "", { "dependencies": { "@smithy/types": "^4.12.0", "tslib": "^2.6.2" } }, "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug=="], + "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "@babel/generator/@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], @@ -4261,9 +4261,9 @@ "@better-auth/core/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@better-auth/core/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="], + "@better-auth/core/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - "@better-auth/stripe/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="], + "@better-auth/stripe/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "@chevrotain/cst-dts-gen/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], @@ -4307,7 +4307,9 @@ "@langchain/core/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], - "@langchain/core/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="], + "@langchain/core/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + + "@langchain/langgraph/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@langchain/langgraph/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], @@ -4323,7 +4325,7 @@ "@modelcontextprotocol/sdk/raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], - "@modelcontextprotocol/sdk/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="], + "@modelcontextprotocol/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "@opentelemetry/auto-instrumentations-node/@opentelemetry/instrumentation-ioredis": ["@opentelemetry/instrumentation-ioredis@0.50.1", "", { "dependencies": { "@opentelemetry/instrumentation": "^0.202.0", "@opentelemetry/redis-common": "^0.38.0", "@opentelemetry/semantic-conventions": "^1.27.0" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-HKrWKOM23qNwqNjWfzkw7mePversmcH5ac6T1dUdiRyJVYaLr4qfydyYkgKIGWHOF2TKvQGobXo3CjvxABQWVw=="], @@ -4433,7 +4435,7 @@ "@posthog/ai/openai": ["openai@6.16.0", "", { "peerDependencies": { "ws": "^8.18.0", "zod": "^3.25 || ^4.0" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-fZ1uBqjFUjXzbGc35fFtYKEOxd20kd9fDpFeqWtsOZWiubY8CZ1NAlXHW3iathaFvqmNtCWMIsosCuyeI7Joxg=="], - "@posthog/ai/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="], + "@posthog/ai/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "@prisma/instrumentation/@opentelemetry/instrumentation": ["@opentelemetry/instrumentation@0.57.2", "", { "dependencies": { "@opentelemetry/api-logs": "0.57.2", "@types/shimmer": "^1.2.0", "import-in-the-middle": "^1.8.1", "require-in-the-middle": "^7.1.1", "semver": "^7.5.2", "shimmer": "^1.2.1" }, "peerDependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-BdBGhQBh8IjZ2oIIX6F2/Q3LKm/FDDKi6ccYKcBTeilh6SNdNKveDOLk73BkSJjQLJk6qe4Yh+hHw1UPhCDdrg=="], @@ -4553,7 +4555,7 @@ "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], - "@vercel/sdk/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="], + "@vercel/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "@xyflow/react/zustand": ["zustand@4.5.7", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "@types/react": ">=16.8", "immer": ">=9.0.6", "react": ">=16.8" }, "optionalPeers": ["@types/react", "immer", "react"] }, "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw=="], @@ -4565,9 +4567,9 @@ "asn1.js/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="], - "autumn-js/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="], + "autumn-js/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], - "better-auth/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="], + "better-auth/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "better-call/rou3": ["rou3@0.7.12", "", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="], @@ -4575,8 +4577,6 @@ "browserify-sign/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], - "bullmq/ioredis": ["ioredis@5.9.1", "", { "dependencies": { "@ioredis/commands": "1.5.0", "cluster-key-slot": "^1.1.0", "debug": "^4.3.4", "denque": "^2.1.0", "lodash.defaults": "^4.2.0", "lodash.isarguments": "^3.1.0", "redis-errors": "^1.2.0", "redis-parser": "^3.0.0", "standard-as-callback": "^2.1.0" } }, "sha512-BXNqFQ66oOsR82g9ajFFsR8ZKrjVvYCLyeML9IvSMAsP56XH2VXBdZjmI11p65nXXJxTEt1hie3J2QeFJVgrtQ=="], - "chevrotain/lodash-es": ["lodash-es@4.17.21", "", {}, "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="], "cliui/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], @@ -4593,8 +4593,6 @@ "create-ecdh/bn.js": ["bn.js@4.12.2", "", {}, "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw=="], - "cron-parser/luxon": ["luxon@3.7.2", "", {}, "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew=="], - "css-select/domhandler": ["domhandler@4.3.1", "", { "dependencies": { "domelementtype": "^2.2.0" } }, "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ=="], "css-select/domutils": ["domutils@2.8.0", "", { "dependencies": { "dom-serializer": "^1.0.1", "domelementtype": "^2.2.0", "domhandler": "^4.2.0" } }, "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A=="], @@ -4655,7 +4653,7 @@ "langchain/uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], - "langchain/zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="], + "langchain/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "langsmith/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -5671,7 +5669,7 @@ "posthog-js/@opentelemetry/exporter-logs-otlp-http/@opentelemetry/otlp-transformer/@opentelemetry/sdk-trace-base": ["@opentelemetry/sdk-trace-base@2.2.0", "", { "dependencies": { "@opentelemetry/core": "2.2.0", "@opentelemetry/resources": "2.2.0", "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.3.0 <1.10.0" } }, "sha512-xWQgL0Bmctsalg6PaXExmzdedSp3gyKV8mQBwK/j9VGdCDu2fmXIb2gAehBKbkXCpJ4HPkgv3QfoJWRT4dHWbw=="], - "react-email/glob/path-scurry/lru-cache": ["lru-cache@11.2.4", "", {}, "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="], + "react-email/glob/path-scurry/lru-cache": ["lru-cache@11.2.5", "", {}, "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw=="], "react-email/ora/cli-cursor/restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], diff --git a/scripts/testGroups/README.md b/scripts/testGroups/README.md deleted file mode 100644 index 27b415c6c..000000000 --- a/scripts/testGroups/README.md +++ /dev/null @@ -1,133 +0,0 @@ -# Test Groups - -Organized test suites for the Autumn test framework. - -## Usage - -From the project root: - -```bash -# Run a specific test group -./scripts/testGroups/g1.sh - -# Run with setup -./scripts/testGroups/g1.sh setup -``` - -From the server directory (legacy): - -```bash -./run.sh /path/to/test/group/script.sh -``` - -## Test Groups - -### G1: Upgrade & Downgrade Tests ✅ (Migrated to Bun) -**Status:** Fully migrated to Bun + TypeScript runner -**Tests:** -- `server/tests/check/basic/*.test.ts` -- `server/tests/attach/basic/*.test.ts` -- `server/tests/attach/upgrade/*.test.ts` -- `server/tests/attach/downgrade/*.test.ts` -- `server/tests/attach/free/*.test.ts` -- `server/tests/attach/addOn/*.test.ts` -- `server/tests/attach/entities/*.test.ts` -- `server/tests/attach/checkout/*.test.ts` - -**Features:** -- Live spinner showing current test -- Beautiful success/failure indicators -- Concise error reports -- **Compact mode** for large test suites (reduces screen overflow) - -### G2: Migrations, Versions & Others ⏳ (Mocha) -**Status:** Uses Mocha (pending migration) -**Tests:** -- Migrations -- Version updates -- Prepaid features -- Interval upgrades - -### G3: Continuous Use Tests ⏳ (Mocha) -**Status:** Uses Mocha (pending migration) -**Tests:** -- Entity management -- Usage tracking -- Updates -- Roles - -### G4: Merged & Core Tests ⏳ (Mocha) -**Status:** Uses Mocha (pending migration) -**Tests:** -- Merged subscriptions -- Core cancellation -- Multi-attach scenarios - -### G5: Advanced Features ⏳ (Mocha) -**Status:** Uses Mocha (pending migration) -**Tests:** -- Multi-feature -- Coupons -- Referrals -- Rollovers -- Usage limits - -### G6: Alex Integration Tests ⏳ (Mocha) -**Status:** Uses Mocha (pending migration) -**Tests:** -- End-to-end scenarios -- Product switching -- Topups - -## Migration Progress - -- ✅ Test runner built (TypeScript + Bun) -- ✅ G1 tests migrated to Bun -- ⏳ G2-G6 pending migration - -## Compact Mode 📦 - -When running many tests (dozens of test files), the standard output can overflow your terminal screen. Use compact mode to see only: -- Recently completed tests (last 3 with tick marks) -- Failed tests summary (updated in real-time) -- Currently running tests (up to 6) with their active test case -- A single line with test stats (progress, passed, failed) -- Full error details at the end - -### Usage - -**In shell scripts (recommended):** -```bash -# Use BUN_PARALLEL_COMPACT instead of BUN_PARALLEL -BUN_PARALLEL_COMPACT \ - 'server/tests/check/basic' \ - 'server/tests/attach/basic' -``` - -**Direct command line:** -```bash -# Add --compact flag -bun scripts/testScripts/runTests.ts server/tests/attach/upgrade --compact - -# Combine with other options -bun scripts/testScripts/runTests.ts server/tests/attach/upgrade --compact --max=10 -``` - -### When to Use Compact Mode - -- ✅ Running 20+ test files (like in g1.sh) -- ✅ CI/CD pipelines with limited scrollback -- ✅ When you only care about failures -- ❌ Debugging specific tests (use full mode to see progress) -- ❌ Running < 10 test files - -## Adding New Test Groups - -1. Create a new script in `scripts/testGroups/` -2. Use the TypeScript runner for new tests: - ```bash - bun scripts/testScripts/runTests.ts server/tests/your/tests - ``` -3. For large test suites, use `BUN_PARALLEL_COMPACT` instead of `BUN_PARALLEL` -4. Update this README with the test group details - diff --git a/scripts/testGroups/all.sh b/scripts/testGroups/all.sh old mode 100644 new mode 100755 index acdc80efe..a93cd7f21 --- a/scripts/testGroups/all.sh +++ b/scripts/testGroups/all.sh @@ -1,5 +1,7 @@ +#!/bin/bash +source "$(dirname "$0")/config.sh" + BUN_PARALLEL_V2 \ + 'customers' \ 'stripe-webhooks' \ - 'update-subscription'\ - 'crud/customers' - --max=3 \ No newline at end of file + 'update-subscription' \ No newline at end of file diff --git a/scripts/testGroups/config.sh b/scripts/testGroups/config.sh index 73fa4b543..8faaa33d9 100755 --- a/scripts/testGroups/config.sh +++ b/scripts/testGroups/config.sh @@ -2,23 +2,12 @@ # Shared configuration for test groups +export TEST_FILE_CONCURRENCY=${TEST_FILE_CONCURRENCY:-3} + # Get project root directory SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" SERVER_DIR="$PROJECT_ROOT/server" - -# # Find bun executable (check common locations) -# if command -v bun &> /dev/null; then -# BUN_CMD="bun" -# elif [ -f "$HOME/.bun/bin/bun" ]; then -# BUN_CMD="$HOME/.bun/bin/bun" -# elif [ -f "/usr/local/bin/bun" ]; then -# BUN_CMD="/usr/local/bin/bun" -# else -# echo "Error: bun not found. Please install bun or add it to PATH." -# exit 1 -# fi - BUN_CMD="infisical run --env=dev -- bun" # Test runner function @@ -33,7 +22,7 @@ BUN_PARALLEL_COMPACT() { # V2 test runner - shows individual tests, better error display (Ink-based) BUN_PARALLEL_V2() { - cd "$PROJECT_ROOT" && $BUN_CMD scripts/testScripts/runTestsV2.tsx "$@" + cd "$PROJECT_ROOT" && $BUN_CMD scripts/testScripts/runTestsV2.tsx "$@" --max="$TEST_FILE_CONCURRENCY" } # Setup function @@ -41,8 +30,3 @@ BUN_SETUP() { cd "$SERVER_DIR" && $BUN_CMD tests/setupMain.ts } -# Mocha function (for tests not yet migrated) -MOCHA_CMD() { - cd "$SERVER_DIR" && npx mocha --parallel -j 6 --timeout 10000000 --ignore tests/00_setup.ts "$@" -} - diff --git a/scripts/testGroups/g1.sh b/scripts/testGroups/g1.sh index ddb90b7dc..0e7f22370 100755 --- a/scripts/testGroups/g1.sh +++ b/scripts/testGroups/g1.sh @@ -10,9 +10,11 @@ source "$(dirname "$0")/config.sh" # Run tests using TypeScript runner with compact mode # Adjust --max to control concurren.cy (default: 6) +export TEST_FILE_CONCURRENCY=6 BUN_PARALLEL_V2 \ 'integration/balances/check' \ + 'integration/balances/track' \ 'balances/track/basic' \ 'balances/track/concurrency' \ 'balances/track/breakdown' \ diff --git a/scripts/testGroups/g2.sh b/scripts/testGroups/g2.sh index 79f6228a6..a46f40f89 100755 --- a/scripts/testGroups/g2.sh +++ b/scripts/testGroups/g2.sh @@ -2,23 +2,31 @@ source "$(dirname "$0")/config.sh" - - -# BUN_PARALLEL_V2 \ -# 'server/tests/attach/basic' \ -# 'server/tests/attach/upgrade' \ -# 'server/tests/attach/downgrade' \ -# 'server/tests/attach/free' \ -# 'server/tests/attach/addOn' \ -# 'server/tests/attach/checkout' \ -# 'server/tests/attach/misc' \ -# 'billing/invoice-action-required' \ -# 'billing/cancel' \ -# 'billing/cancel/add-ons' \ -# 'billing/legacy/attach' \ -# --max=6 +export TEST_FILE_CONCURRENCY=6 BUN_PARALLEL_V2 \ - 'server/tests/attach/entities' \ + 'attach/basic' \ + 'attach/upgrade' \ + 'attach/downgrade' \ + 'attach/free' \ + 'attach/addOn' \ + 'attach/checkout' \ + 'attach/migrations' \ + 'attach/others' \ + 'attach/upgradeOld' \ + 'attach/response' \ + 'interval/upgrade' \ + 'interval/multiSub' \ + 'billing/new-billing-subscription' \ + 'billing/invoice-action-required' \ + 'billing/legacy/attach' \ --max=6 - # 'server/tests/external-psps/revenuecat' \ + +# From attach/migrations is new stuff... + +BUN_PARALLEL_V2 \ + 'server/tests/attach/entities' + + +# 'attach/updateEnts' \ +# 'attach/newVersion' \ \ No newline at end of file diff --git a/scripts/testGroups/g3.sh b/scripts/testGroups/g3.sh index 0dce6bd6c..46d47602e 100755 --- a/scripts/testGroups/g3.sh +++ b/scripts/testGroups/g3.sh @@ -1,29 +1,22 @@ #!/bin/bash - -# Test Group 3: Migrations, Versions & Others -# Description: Tests for migrations, version updates, and miscellaneous features - -# Source shared configuration source "$(dirname "$0")/config.sh" -# Setup if requested -if [[ "$1" == *"setup"* ]]; then - echo "Running test setup..." - BUN_SETUP -fi +export TEST_FILE_CONCURRENCY=6 + +BUN_PARALLEL_V2 \ + 'merged/downgrade' \ + 'merged/separate' \ + 'merged/add' \ + 'merged/group' \ + 'merged/prepaid' \ + 'merged/upgrade' \ + 'merged/addOn' \ + 'merged/trial' \ + - -BUN_PARALLEL_COMPACT \ - 'server/tests/attach/migrations' \ - 'server/tests/attach/others' \ - 'server/tests/attach/newVersion' \ - 'server/tests/attach/upgradeOld' \ - 'server/tests/attach/updateEnts' \ - 'server/tests/attach/response' \ - 'server/tests/interval/upgrade' \ - 'server/tests/interval/multiSub' \ - 'server/tests/integration/billing/new-billing-subscription' \ - 'server/tests/integration/billing/invoice-action-required/new-subscription' \ - --max=6 - +# deprecated tests(?) +# 'server/tests/core/multiAttach' \ +# 'server/tests/core/multiAttach/multiInvoice' \ +# 'server/tests/core/multiAttach/multiUpgrade' \ +# 'sever/tests/core/multiAttach/multiReward' diff --git a/scripts/testGroups/g4.sh b/scripts/testGroups/g4.sh index 0aeac69f0..402b3a99e 100755 --- a/scripts/testGroups/g4.sh +++ b/scripts/testGroups/g4.sh @@ -3,25 +3,23 @@ # Source shared configuration source "$(dirname "$0")/config.sh" + + BUN_PARALLEL_COMPACT \ - 'server/tests/merged/downgrade' \ - 'server/tests/merged/separate' \ - 'server/tests/merged/add' \ - 'server/tests/merged/group' \ - 'server/tests/merged/prepaid' \ - 'server/tests/merged/upgrade' \ - 'server/tests/merged/addOn' \ - 'server/tests/merged/trial' \ - 'server/tests/core/cancel' \ - --max=6 \ - - - + 'server/tests/advanced/coupons' \ + 'server/tests/advanced/misc' \ + 'server/tests/attach/updateQuantity' \ + 'server/tests/attach/multiProduct' \ + 'server/tests/advanced/multiFeature' \ + 'server/tests/advanced/referrals' \ + 'server/tests/advanced/rollovers' \ + 'server/tests/advanced/customInterval' \ + 'server/tests/advanced/usageLimit' \ + --max=6 +# BUN_PARALLEL_COMPACT \ +# 'server/tests/advanced/usage' +# # 'server/tests/crud/plan' -# deprecated tests(?) -# 'server/tests/core/multiAttach' \ -# 'server/tests/core/multiAttach/multiInvoice' \ -# 'server/tests/core/multiAttach/multiUpgrade' \ -# 'sever/tests/core/multiAttach/multiReward' +# # 'server/tests/advanced/referrals/paid' \ \ No newline at end of file diff --git a/scripts/testGroups/g5.sh b/scripts/testGroups/g5.sh deleted file mode 100755 index 402b3a99e..000000000 --- a/scripts/testGroups/g5.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -# Source shared configuration -source "$(dirname "$0")/config.sh" - - - -BUN_PARALLEL_COMPACT \ - 'server/tests/advanced/coupons' \ - 'server/tests/advanced/misc' \ - 'server/tests/attach/updateQuantity' \ - 'server/tests/attach/multiProduct' \ - 'server/tests/advanced/multiFeature' \ - 'server/tests/advanced/referrals' \ - 'server/tests/advanced/rollovers' \ - 'server/tests/advanced/customInterval' \ - 'server/tests/advanced/usageLimit' \ - --max=6 - - -# BUN_PARALLEL_COMPACT \ -# 'server/tests/advanced/usage' -# # 'server/tests/crud/plan' - -# # 'server/tests/advanced/referrals/paid' \ \ No newline at end of file diff --git a/scripts/testGroups/stripe-webhooks.sh b/scripts/testGroups/stripe-webhooks.sh index 00bbafc32..2772e8e9c 100755 --- a/scripts/testGroups/stripe-webhooks.sh +++ b/scripts/testGroups/stripe-webhooks.sh @@ -6,5 +6,5 @@ source "$(dirname "$0")/config.sh" BUN_PARALLEL_V2 \ 'stripe-webhooks/invoice-created' \ 'stripe-webhooks/subscription-deleted'\ - 'stripe-webhooks/subscription-updated' + 'stripe-webhooks/subscription-updated' \ --max=3 diff --git a/scripts/testScripts/runTestsV2.ts b/scripts/testScripts/runTestsV2.ts deleted file mode 100644 index 6e19ae862..000000000 --- a/scripts/testScripts/runTestsV2.ts +++ /dev/null @@ -1,724 +0,0 @@ -#!/usr/bin/env bun - -import { existsSync } from "node:fs"; -import { readdir } from "node:fs/promises"; -import { basename, resolve } from "node:path"; -import { loadLocalEnv } from "@server/utils/envUtils.js"; -import { spawn } from "bun"; -import chalk from "chalk"; -import dotenv from "dotenv"; -import pLimit from "p-limit"; - -loadLocalEnv(); - -// Load environment variables from server/.env -dotenv.config({ path: resolve(process.cwd(), "server", ".env") }); - -// Base path for shorthand test paths -const INTEGRATION_TEST_BASE = "server/tests/integration/billing"; - -interface IndividualTest { - name: string; - status: "pending" | "running" | "passed" | "failed"; - duration?: number; - error?: { - message: string; - location?: string; // file:line for cmd+click - details?: string; - }; -} - -interface TestFileResult { - file: string; - status: "pending" | "running" | "passed" | "failed"; - tests: IndividualTest[]; - currentTest?: string; - output: string; - duration: number; -} - -class TestRunnerV2 { - private results: Map = new Map(); - private testFiles: string[] = []; - private maxParallel: number = 6; - private spinnerFrames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; - private spinnerIndex = 0; - private renderInterval?: Timer; - private startLine = 0; - private lastRenderedLines = 0; - - constructor(maxParallel?: number) { - if (maxParallel) this.maxParallel = maxParallel; - } - - async collectTestFiles(directories: string[]): Promise { - const testFiles: string[] = []; - - for (const dir of directories) { - const resolvedDir = resolve(process.cwd(), dir); - try { - const files = await readdir(resolvedDir); - for (const file of files) { - if (file.endsWith(".test.ts")) { - testFiles.push(resolve(resolvedDir, file)); - } - } - } catch (error) { - console.error(chalk.red(`Error reading directory ${dir}:`), error); - } - } - - return testFiles; - } - - private parseTestOutput(output: string, filePath: string): IndividualTest[] { - const tests: IndividualTest[] = []; - const lines = output.split("\n"); - - // Track where each test result appears - // Error output appears BEFORE the (fail) line in bun test output - let lastTestEndIndex = -1; - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - - // Match (pass) or (fail) test results - const passMatch = line.match( - /^\(pass\)\s+(.+?)\s+\[(\d+(?:\.\d+)?m?s)\]/, - ); - const failMatch = line.match( - /^\(fail\)\s+(.+?)\s+\[(\d+(?:\.\d+)?m?s)\]/, - ); - - if (passMatch) { - const [, name, duration] = passMatch; - tests.push({ - name: name.trim(), - status: "passed", - duration: this.parseDuration(duration), - }); - lastTestEndIndex = i; - } else if (failMatch) { - const [, name, duration] = failMatch; - - // Look BACKWARDS from this line to find the error output - // Error appears between the last test result and this (fail) line - const errorStartIndex = lastTestEndIndex + 1; - const errorLines = lines.slice(errorStartIndex, i); - - const test: IndividualTest = { - name: name.trim(), - status: "failed", - duration: this.parseDuration(duration), - }; - - // Parse the error from the lines before this (fail) - this.parseErrorFromLines(test, errorLines, filePath); - - tests.push(test); - lastTestEndIndex = i; - } - } - - return tests; - } - - private parseErrorFromLines( - test: IndividualTest, - errorLines: string[], - filePath: string, - ): void { - const errorText = errorLines.join("\n"); - - // Find error message - look for "error:" line - let errorMessage = ""; - for (const line of errorLines) { - const errorMatch = line.match(/^error:\s*(.+)/i); - if (errorMatch) { - errorMessage = errorMatch[1].trim(); - break; - } - } - - // Find Expected/Received for assertion errors - const expectedMatch = errorText.match(/Expected:\s*(.+)/); - const receivedMatch = errorText.match(/Received:\s*(.+)/); - if (expectedMatch && receivedMatch) { - errorMessage = `Expected: ${expectedMatch[1]}, Received: ${receivedMatch[1]}`; - } - - // Check for timeout - if (errorText.includes("this test timed out")) { - errorMessage = "Test timed out"; - } - - // Find location - prioritize the test file itself in stack trace - let location: string | undefined; - const testFileName = basename(filePath); - - for (const line of errorLines) { - // Match stack trace lines like: - // at async (/path/to/file.test.ts:38:29) - // at functionName (/path/to/file.ts:123:45) - const stackMatch = line.match(/at\s+.*?\(([^)]+\.ts):(\d+):\d+\)/); - if (stackMatch) { - const matchedFile = stackMatch[1]; - const lineNum = stackMatch[2]; - - // Prefer .test.ts files - if (matchedFile.endsWith(".test.ts")) { - location = `${matchedFile}:${lineNum}`; - break; - } - - // Otherwise take first server file if we don't have one yet - if (!location && matchedFile.includes("/server/")) { - location = `${matchedFile}:${lineNum}`; - } - } - } - - test.error = { - message: errorMessage || "Test failed", - location, - details: errorText.slice(0, 500), - }; - } - - private parseDuration(duration: string): number { - // Parse "123.45ms" or "1.23s" to milliseconds - if (duration.endsWith("ms")) { - return Number.parseFloat(duration); - } - if (duration.endsWith("s")) { - return Number.parseFloat(duration) * 1000; - } - return Number.parseFloat(duration); - } - - private extractCurrentTest(output: string): string | null { - // Look for the last test that started (before pass/fail) - const lines = output.split("\n"); - - // Find last pass/fail to know what's completed - let lastCompletedIndex = -1; - for (let i = lines.length - 1; i >= 0; i--) { - if (lines[i].match(/^\(pass\)/) || lines[i].match(/^\(fail\)/)) { - lastCompletedIndex = i; - break; - } - } - - // The "current" test would be indicated by the test that's running - // Bun doesn't explicitly say which test is running, so we show the last completed - if (lastCompletedIndex >= 0) { - const match = lines[lastCompletedIndex].match( - /^\((?:pass|fail)\)\s+(.+?)\s+\[/, - ); - if (match) { - return match[1].trim(); - } - } - - return null; - } - - private hideCursor() { - process.stdout.write("\x1B[?25l"); - } - - private showCursor() { - process.stdout.write("\x1B[?25h"); - } - - private moveCursor(line: number, col: number = 0) { - process.stdout.write(`\x1B[${line};${col}H`); - } - - private clearLine() { - process.stdout.write("\x1B[2K"); - } - - private clearToEndOfScreen() { - process.stdout.write("\x1B[J"); - } - - private truncate(str: string, maxLength: number): string { - if (str.length <= maxLength) return str; - return str.substring(0, maxLength - 3) + "..."; - } - - private render() { - this.spinnerIndex = (this.spinnerIndex + 1) % this.spinnerFrames.length; - const spinner = this.spinnerFrames[this.spinnerIndex]; - - let lineNum = this.startLine; - - // Calculate stats - only count tests from COMPLETED files for accurate progress - const runningFiles = Array.from(this.results.entries()).filter( - ([_, r]) => r.status === "running", - ); - const completedFiles = Array.from(this.results.entries()).filter( - ([_, r]) => r.status === "passed" || r.status === "failed", - ); - const pendingFiles = Array.from(this.results.entries()).filter( - ([_, r]) => r.status === "pending", - ); - - // Only count tests from completed files for stable progress - const completedTests = completedFiles.flatMap(([_, r]) => r.tests); - const passedTests = completedTests.filter( - (t) => t.status === "passed", - ).length; - const failedTests = completedTests.filter( - (t) => t.status === "failed", - ).length; - - // Header - this.moveCursor(lineNum, 0); - this.clearLine(); - process.stdout.write( - chalk.cyan.bold(`Running ${this.testFiles.length} test files...\n`), - ); - lineNum++; - - // Blank line - this.moveCursor(lineNum, 0); - this.clearLine(); - process.stdout.write("\n"); - lineNum++; - - // Show running files with their current test - if (runningFiles.length > 0) { - this.moveCursor(lineNum, 0); - this.clearLine(); - process.stdout.write( - chalk.yellow.bold(`Running (${runningFiles.length}):\n`), - ); - lineNum++; - - for (const [file, result] of runningFiles) { - const fileName = basename(file); - this.moveCursor(lineNum, 0); - this.clearLine(); - - // Show file with spinner - let fileDisplay = ` ${chalk.cyan(spinner)} ${fileName}`; - - // Show completed tests count for this file - const filePassedCount = result.tests.filter( - (t) => t.status === "passed", - ).length; - const fileFailedCount = result.tests.filter( - (t) => t.status === "failed", - ).length; - - if (filePassedCount > 0 || fileFailedCount > 0) { - fileDisplay += chalk.dim( - ` (${chalk.green(`✓${filePassedCount}`)}${fileFailedCount > 0 ? chalk.red(` ✗${fileFailedCount}`) : ""})`, - ); - } - - // Show current/last test - const currentTest = this.extractCurrentTest(result.output); - if (currentTest) { - fileDisplay += chalk.dim(` › ${this.truncate(currentTest, 40)}`); - } - - process.stdout.write(`${fileDisplay}\n`); - lineNum++; - } - - // Blank line after running - this.moveCursor(lineNum, 0); - this.clearLine(); - process.stdout.write("\n"); - lineNum++; - } - - // Show recently completed files (last 3) - if (completedFiles.length > 0) { - const recentCompleted = completedFiles.slice(-3); - - this.moveCursor(lineNum, 0); - this.clearLine(); - process.stdout.write( - chalk.dim( - `Completed (${completedFiles.length}/${this.testFiles.length} files):\n`, - ), - ); - lineNum++; - - for (const [file, result] of recentCompleted) { - const fileName = basename(file); - this.moveCursor(lineNum, 0); - this.clearLine(); - - const filePassedCount = result.tests.filter( - (t) => t.status === "passed", - ).length; - const fileFailedCount = result.tests.filter( - (t) => t.status === "failed", - ).length; - - const icon = - result.status === "passed" ? chalk.green("✓") : chalk.red("✗"); - const nameColor = result.status === "passed" ? chalk.dim : chalk.white; - - process.stdout.write( - ` ${icon} ${nameColor(fileName)} ${chalk.dim(`(${chalk.green(`✓${filePassedCount}`)}${fileFailedCount > 0 ? chalk.red(` ✗${fileFailedCount}`) : ""})`)}\n`, - ); - lineNum++; - } - - // Blank line - this.moveCursor(lineNum, 0); - this.clearLine(); - process.stdout.write("\n"); - lineNum++; - } - - // Show inline errors from recently completed files (compact view) - const recentFailedTests = completedFiles - .flatMap(([file, result]) => - result.tests - .filter((t) => t.status === "failed") - .map((t) => ({ ...t, file })), - ) - .slice(-2); // Show last 2 failures - - if (recentFailedTests.length > 0) { - this.moveCursor(lineNum, 0); - this.clearLine(); - process.stdout.write(chalk.red.bold(`Recent Failures:\n`)); - lineNum++; - - for (const test of recentFailedTests) { - this.moveCursor(lineNum, 0); - this.clearLine(); - process.stdout.write( - ` ${chalk.red("✗")} ${this.truncate(test.name, 50)}\n`, - ); - lineNum++; - - if (test.error?.message) { - this.moveCursor(lineNum, 0); - this.clearLine(); - process.stdout.write( - ` ${chalk.dim("→")} ${chalk.yellow(this.truncate(test.error.message, 60))}\n`, - ); - lineNum++; - } - - if (test.error?.location) { - this.moveCursor(lineNum, 0); - this.clearLine(); - process.stdout.write( - ` ${chalk.dim("→")} ${chalk.cyan(test.error.location)}\n`, - ); - lineNum++; - } - } - - // Blank line - this.moveCursor(lineNum, 0); - this.clearLine(); - process.stdout.write("\n"); - lineNum++; - } - - // Progress bar - this.moveCursor(lineNum, 0); - this.clearLine(); - process.stdout.write(chalk.dim("─".repeat(60) + "\n")); - lineNum++; - - this.moveCursor(lineNum, 0); - this.clearLine(); - process.stdout.write( - `${chalk.cyan(spinner)} Progress: ${chalk.bold(`${completedFiles.length}/${this.testFiles.length} files`)} | ` + - `${chalk.green(`✓ ${passedTests}`)} | ` + - `${failedTests > 0 ? chalk.red(`✗ ${failedTests}`) : chalk.dim(`✗ ${failedTests}`)} | ` + - `${chalk.dim(`${runningFiles.length} running`)}\n`, - ); - lineNum++; - - // Clear remaining lines - this.moveCursor(lineNum, 0); - this.clearToEndOfScreen(); - - this.lastRenderedLines = lineNum - this.startLine; - } - - async runTest(file: string): Promise { - const startTime = performance.now(); - - // Initialize as running - const result: TestFileResult = { - file, - status: "running", - tests: [], - output: "", - duration: 0, - }; - this.results.set(file, result); - - try { - const proc = spawn(["bun", "test", "--timeout", "0", file], { - stdout: "pipe", - stderr: "pipe", - env: { ...process.env }, - }); - - let output = ""; - const decoder = new TextDecoder(); - - if (proc.stdout) { - for await (const chunk of proc.stdout) { - const text = decoder.decode(chunk); - output += text; - result.output = output; - - // Parse tests as they complete - result.tests = this.parseTestOutput(output, file); - this.results.set(file, result); - } - } - - if (proc.stderr) { - for await (const chunk of proc.stderr) { - output += decoder.decode(chunk); - result.output = output; - } - } - - await proc.exited; - const duration = performance.now() - startTime; - - // Final parse - const tests = this.parseTestOutput(output, file); - const hasFailures = tests.some((t) => t.status === "failed"); - - this.results.set(file, { - ...result, - status: hasFailures ? "failed" : "passed", - tests, - output, - duration, - }); - } catch (error) { - const duration = performance.now() - startTime; - this.results.set(file, { - ...result, - status: "failed", - output: String(error), - duration, - }); - } - } - - private cleanup() { - if (this.renderInterval) { - clearInterval(this.renderInterval); - } - this.showCursor(); - } - - private handleInterrupt() { - this.cleanup(); - console.log( - chalk.yellow.bold("\n\n⚠ Tests interrupted by user (Ctrl+C)\n"), - ); - this.printSummary(); - process.exit(130); - } - - async run(directories: string[]): Promise { - this.testFiles = await this.collectTestFiles(directories); - - if (this.testFiles.length === 0) { - console.log(chalk.yellow("No test files found in specified directories")); - return; - } - - // Initialize all tests as pending - for (const file of this.testFiles) { - this.results.set(file, { - file, - status: "pending", - tests: [], - output: "", - duration: 0, - }); - } - - // Setup SIGINT handler - const sigintHandler = () => this.handleInterrupt(); - process.on("SIGINT", sigintHandler); - - // Hide cursor and create initial space - this.hideCursor(); - this.startLine = 1; - - // Create some initial space - for (let i = 0; i < 20; i++) { - console.log(); - } - process.stdout.write("\x1B[20A"); - - // Start rendering loop - this.renderInterval = setInterval(() => this.render(), 100); - - // Run tests with concurrency limit - const limit = pLimit(this.maxParallel); - const promises = this.testFiles.map((file) => - limit(() => this.runTest(file)), - ); - - await Promise.all(promises); - - // Remove SIGINT handler - process.off("SIGINT", sigintHandler); - - // Final render and cleanup - this.cleanup(); - this.render(); - - // Move past the rendered output - process.stdout.write(`\x1B[${this.lastRenderedLines + 2}B`); - - // Print summary - this.printSummary(); - } - - private printSummary() { - const allTests = Array.from(this.results.values()).flatMap((r) => r.tests); - const failedTests = allTests.filter((t) => t.status === "failed"); - const passedTests = allTests.filter((t) => t.status === "passed"); - const totalDuration = Array.from(this.results.values()).reduce( - (sum, r) => sum + r.duration, - 0, - ); - - console.log("\n"); - - if (failedTests.length === 0) { - console.log( - chalk.green.bold( - `═${"═".repeat(68)}═\n` + - ` ✓ ALL ${passedTests.length} TESTS PASSED (${(totalDuration / 1000).toFixed(1)}s)\n` + - `═${"═".repeat(68)}═\n`, - ), - ); - process.exit(0); - } - - // Failed tests summary - console.log( - chalk.red.bold( - `═${"═".repeat(68)}═\n` + - ` FAILED TESTS (${failedTests.length})\n` + - `═${"═".repeat(68)}═`, - ), - ); - - // Group failed tests by file - const failedByFile = new Map(); - for (const [file, result] of this.results.entries()) { - const fileFailed = result.tests.filter((t) => t.status === "failed"); - if (fileFailed.length > 0) { - failedByFile.set(file, fileFailed); - } - } - - for (const [file, tests] of failedByFile) { - console.log(chalk.red.bold(`\n📁 ${basename(file)}`)); - console.log(chalk.dim("─".repeat(60))); - - for (const test of tests) { - console.log(chalk.red(`\n ✗ ${test.name}`)); - - if (test.error?.location) { - console.log(chalk.cyan(` ${test.error.location}`)); - } - - if (test.error?.message) { - console.log(chalk.yellow(`\n ${test.error.message}`)); - } - - if (test.error?.details) { - // Show a few lines of error details - const detailLines = test.error.details - .split("\n") - .filter((l) => l.trim()) - .slice(0, 8); - for (const line of detailLines) { - console.log(chalk.dim(` ${this.truncate(line.trim(), 70)}`)); - } - } - } - } - - console.log( - chalk.red.bold( - `\n═${"═".repeat(68)}═\n` + - ` SUMMARY: ${chalk.green(`${passedTests.length} passed`)} | ${chalk.red(`${failedTests.length} failed`)} | ${(totalDuration / 1000).toFixed(1)}s\n` + - `═${"═".repeat(68)}═\n`, - ), - ); - - process.exit(1); - } -} - -// Parse CLI arguments -const args = process.argv.slice(2); -const directories: string[] = []; -let maxParallel = 6; - -for (const arg of args) { - if (arg.startsWith("--max=")) { - maxParallel = Number.parseInt(arg.split("=")[1], 10); - } else if (arg.startsWith("-")) { - console.error(chalk.red(`Unknown option: ${arg}`)); - console.log( - "Usage: bun scripts/testScripts/runTestsV2.ts [dir2] [...] [--max=N]", - ); - process.exit(1); - } else { - // Try to resolve the path - if it doesn't exist, prepend the base path - let resolvedPath = arg; - const fullPath = resolve(process.cwd(), arg); - - if (!existsSync(fullPath)) { - const withBase = `${INTEGRATION_TEST_BASE}/${arg}`; - const withBaseFull = resolve(process.cwd(), withBase); - if (existsSync(withBaseFull)) { - resolvedPath = withBase; - } - } - - directories.push(resolvedPath); - } -} - -if (directories.length === 0) { - console.error(chalk.red("Error: No test directories specified")); - console.log( - "Usage: bun scripts/testScripts/runTestsV2.ts [dir2] [...] [--max=N]", - ); - console.log("\nOptions:"); - console.log(" --max=N Set maximum parallel test files (default: 6)"); - console.log("\nExamples:"); - console.log( - " bun scripts/testScripts/runTestsV2.ts update-subscription/custom-plan", - ); - console.log( - " bun scripts/testScripts/runTestsV2.ts update-subscription/custom-plan update-subscription/errors --max=4", - ); - process.exit(1); -} - -// Run tests -const runner = new TestRunnerV2(maxParallel); -await runner.run(directories); diff --git a/scripts/testScripts/runTestsV2.tsx b/scripts/testScripts/runTestsV2.tsx index 52a7b9eed..cb2afae83 100644 --- a/scripts/testScripts/runTestsV2.tsx +++ b/scripts/testScripts/runTestsV2.tsx @@ -1,15 +1,43 @@ #!/usr/bin/env bun import { existsSync } from "node:fs"; -import { readdir } from "node:fs/promises"; -import { basename, resolve } from "node:path"; +import { readdir, stat } from "node:fs/promises"; +import { basename, join, resolve } from "node:path"; import { spawn } from "bun"; import { Box, render, Text, useApp } from "ink"; import pLimit from "p-limit"; import React, { useEffect, useState } from "react"; -// Base paths for shorthand test paths (tried in order) -const TEST_BASE_PATHS = ["server/tests/integration/billing", "server/tests"]; +// Base path for shorthand test paths +const INTEGRATION_TEST_BASE = "server/tests"; + +/** + * Recursively search for a folder by name within a base directory. + * Returns the first matching folder path, or null if not found. + */ +async function findFolderByName( + basePath: string, + folderName: string, +): Promise { + try { + const entries = await readdir(basePath); + for (const entry of entries) { + const fullPath = join(basePath, entry); + const entryStat = await stat(fullPath); + + if (entryStat.isDirectory()) { + if (entry === folderName) { + return fullPath; + } + const found = await findFolderByName(fullPath, folderName); + if (found) return found; + } + } + } catch { + // Ignore errors + } + return null; +} // Track all running processes for cleanup const runningProcesses = new Set>(); @@ -148,6 +176,11 @@ function parseErrorFromLines( } } + // Fall back to the test file path if no location found in stack trace + if (!location) { + location = filePath; + } + test.error = { message: errorMessage || "Test failed", location, @@ -184,18 +217,27 @@ function extractCurrentTest(output: string): string | null { async function collectTestFiles(directories: string[]): Promise { const testFiles: string[] = []; - for (const dir of directories) { - const resolvedDir = resolve(process.cwd(), dir); + const collectRecursive = async (dirPath: string): Promise => { try { - const files = await readdir(resolvedDir); - for (const file of files) { - if (file.endsWith(".test.ts")) { - testFiles.push(resolve(resolvedDir, file)); + const entries = await readdir(dirPath); + for (const entry of entries) { + const fullPath = join(dirPath, entry); + const entryStat = await stat(fullPath); + + if (entryStat.isDirectory()) { + await collectRecursive(fullPath); + } else if (entry.endsWith(".test.ts")) { + testFiles.push(fullPath); } } } catch (error) { - console.error(`Error reading directory ${dir}:`, error); + console.error(`Error reading directory ${dirPath}:`, error); } + }; + + for (const dir of directories) { + const resolvedDir = resolve(process.cwd(), dir); + await collectRecursive(resolvedDir); } return testFiles; @@ -307,6 +349,14 @@ function truncate(str: string, maxLength: number): string { return str.substring(0, maxLength - 3) + "..."; } +function toRelativePath(absolutePath: string): string { + const workspaceRoot = process.cwd(); + if (absolutePath.startsWith(workspaceRoot)) { + return absolutePath.slice(workspaceRoot.length + 1); + } + return absolutePath; +} + interface CompletedFileProps { result: TestFileResult; } @@ -352,7 +402,7 @@ function FailedTest({ test, fileName }: FailedTestProps) { {test.error?.location && ( - {test.error.location} + {toRelativePath(test.error.location)} )} @@ -611,7 +661,7 @@ function FinalSummary({ results }: FinalSummaryProps) { ✗ {test.name} {test.error?.location && ( - {test.error.location} + {toRelativePath(test.error.location)} )} {test.error?.message && ( {test.error.message} @@ -644,7 +694,9 @@ function FinalSummary({ results }: FinalSummaryProps) { async function main() { const args = process.argv.slice(2); const directories: string[] = []; - let maxParallel = 6; + let maxParallel = process.env.TEST_FILE_CONCURRENCY + ? Number.parseInt(process.env.TEST_FILE_CONCURRENCY, 10) + : 6; for (const arg of args) { if (arg.startsWith("--max=")) { @@ -656,18 +708,26 @@ async function main() { ); process.exit(1); } else { - // Try to resolve the path - if it doesn't exist, try prepending base paths + // Try to resolve the path in order of priority: + // 1. Exact path from cwd + // 2. Path under INTEGRATION_TEST_BASE + // 3. Search for folder name within INTEGRATION_TEST_BASE let resolvedPath = arg; const fullPath = resolve(process.cwd(), arg); if (!existsSync(fullPath)) { - // Try each base path in order - for (const basePath of TEST_BASE_PATHS) { - const withBase = `${basePath}/${arg}`; - const withBaseFull = resolve(process.cwd(), withBase); - if (existsSync(withBaseFull)) { - resolvedPath = withBase; - break; + const withBase = `${INTEGRATION_TEST_BASE}/${arg}`; + const withBaseFull = resolve(process.cwd(), withBase); + if (existsSync(withBaseFull)) { + resolvedPath = withBase; + } else { + // Search for the folder by name within the base directory + const folderName = basename(arg); + const baseFullPath = resolve(process.cwd(), INTEGRATION_TEST_BASE); + const found = await findFolderByName(baseFullPath, folderName); + if (found) { + // Convert back to relative path + resolvedPath = found.replace(`${process.cwd()}/`, ""); } } } diff --git a/server/src/external/autumn/autumnCli.ts b/server/src/external/autumn/autumnCli.ts index d827af9a1..2bbb6971e 100644 --- a/server/src/external/autumn/autumnCli.ts +++ b/server/src/external/autumn/autumnCli.ts @@ -261,12 +261,18 @@ export class AutumnInt { async attach( params: AttachBodyV0, - { skipWebhooks }: { skipWebhooks?: boolean } = {}, + { + skipWebhooks, + idempotencyKey, + }: { skipWebhooks?: boolean; idempotencyKey?: string } = {}, ) { const headers: Record = {}; if (skipWebhooks !== undefined) { headers["x-skip-webhooks"] = skipWebhooks ? "true" : "false"; } + if (idempotencyKey !== undefined) { + headers["idempotency-key"] = idempotencyKey; + } const data = await this.post( `/attach`, diff --git a/server/src/external/stripe/webhookHandlers/common/trackCustomerProductUpdate.ts b/server/src/external/stripe/webhookHandlers/common/trackCustomerProductUpdate.ts index dc71bb4f3..f1c6a96c9 100644 --- a/server/src/external/stripe/webhookHandlers/common/trackCustomerProductUpdate.ts +++ b/server/src/external/stripe/webhookHandlers/common/trackCustomerProductUpdate.ts @@ -57,7 +57,9 @@ export const trackCustomerProductDeletion = ({ eventContext, customerProduct, }: { - eventContext: StripeSubscriptionDeletedContext; + eventContext: + | StripeSubscriptionDeletedContext + | StripeSubscriptionUpdatedContext; customerProduct: FullCusProduct; }): void => { const { customerProducts, fullCustomer, deletedCustomerProducts } = diff --git a/server/src/external/stripe/webhookHandlers/handleStripeInvoiceCreated/tasks/processConsumablePricesForInvoiceCreated.ts b/server/src/external/stripe/webhookHandlers/handleStripeInvoiceCreated/tasks/processConsumablePricesForInvoiceCreated.ts index 03fee9ab1..7761f3bb0 100644 --- a/server/src/external/stripe/webhookHandlers/handleStripeInvoiceCreated/tasks/processConsumablePricesForInvoiceCreated.ts +++ b/server/src/external/stripe/webhookHandlers/handleStripeInvoiceCreated/tasks/processConsumablePricesForInvoiceCreated.ts @@ -4,6 +4,8 @@ import { eventContextToArrearLineItems } from "@/external/stripe/webhookHandlers import { lineItemsToCreateInvoiceItemsParams } from "@/internal/billing/v2/providers/stripe/utils/invoiceLines/lineItemsToCreateInvoiceItemsParams"; import { createStripeInvoiceItems } from "@/internal/billing/v2/providers/stripe/utils/invoices/stripeInvoiceOps"; import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService"; +import { RolloverService } from "@/internal/customers/cusProducts/cusEnts/cusRollovers/RolloverService"; +import { getRolloverUpdates } from "@/internal/customers/cusProducts/cusEnts/cusRollovers/rolloverUtils"; import type { StripeWebhookContext } from "../../../webhookMiddlewares/stripeWebhookContext"; import type { InvoiceCreatedContext } from "../setupInvoiceCreatedContext"; @@ -89,4 +91,18 @@ export const processConsumablePricesForInvoiceCreated = async ({ db: ctx.db, data: updateCustomerEntitlements, }); + + // Handle rollovers + updateCustomerEntitlements.forEach(async (update) => { + const rolloverUpdates = getRolloverUpdates({ + cusEnt: update.customerEntitlement, + nextResetAt: Date.now(), + }); + + await RolloverService.insert({ + db: ctx.db, + rows: rolloverUpdates.toInsert, + fullCusEnt: update.customerEntitlement, + }); + }); }; diff --git a/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionDeleted/tasks/expireAndActivateCustomerProducts.ts b/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionDeleted/tasks/expireAndActivateCustomerProducts.ts index 9aec14704..8b49f3053 100644 --- a/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionDeleted/tasks/expireAndActivateCustomerProducts.ts +++ b/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionDeleted/tasks/expireAndActivateCustomerProducts.ts @@ -1,9 +1,13 @@ import { type FullCusProduct, + findMainScheduledCustomerProductByGroup, + isCustomerProductFree, isCustomerProductOnStripeSubscription, + isCustomerProductPaid, } from "@autumn/shared"; import type { StripeWebhookContext } from "@/external/stripe/webhookMiddlewares/stripeWebhookContext"; import { customerProductActions } from "@/internal/customers/cusProducts/actions"; +import { CusProductService } from "@/internal/customers/cusProducts/CusProductService"; import { trackCustomerProductDeletion, trackCustomerProductUpdate, @@ -57,19 +61,44 @@ export const expireAndActivateCustomerProducts = async ({ updates, }); - // 3. Delete scheduled main product in the same group - const { deletedCustomerProduct } = - await customerProductActions.deleteScheduled({ - ctx, - customerProduct, - fullCustomer, - }); + // Find scheduled main product in the same group + const scheduledCustomerProduct = findMainScheduledCustomerProductByGroup({ + fullCustomer, + productGroup: customerProduct.product.group, + internalEntityId: customerProduct.internal_entity_id ?? undefined, + }); - if (deletedCustomerProduct) { - trackCustomerProductDeletion({ - eventContext, - customerProduct: deletedCustomerProduct, - }); + if (scheduledCustomerProduct) { + const scheduledIsFreeCustomerProduct = isCustomerProductFree( + scheduledCustomerProduct, + ); + const scheduledIsPaidCustomerProduct = isCustomerProductPaid( + scheduledCustomerProduct, + ); + + if (scheduledIsFreeCustomerProduct) { + const { updates: activateScheduledUpdates } = + await customerProductActions.activateScheduled({ + ctx, + customerProduct: scheduledCustomerProduct, + fullCustomer, + }); + + trackCustomerProductUpdate({ + eventContext, + customerProduct: scheduledCustomerProduct, + updates: activateScheduledUpdates, + }); + } else if (scheduledIsPaidCustomerProduct) { + await CusProductService.delete({ + db: ctx.db, + cusProductId: scheduledCustomerProduct.id, + }); + trackCustomerProductDeletion({ + eventContext, + customerProduct: scheduledCustomerProduct, + }); + } } } diff --git a/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/setupStripeSubscriptionUpdatedContext.ts b/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/setupStripeSubscriptionUpdatedContext.ts index 49d91602d..f9b92fd1d 100644 --- a/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/setupStripeSubscriptionUpdatedContext.ts +++ b/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/setupStripeSubscriptionUpdatedContext.ts @@ -40,5 +40,6 @@ export const setupStripeSubscriptionUpdatedContext = async ({ customerProducts: [...fullCustomer.customer_products], nowMs, updatedCustomerProducts: [], + deletedCustomerProducts: [], }; }; diff --git a/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/stripeSubscriptionUpdatedContext.ts b/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/stripeSubscriptionUpdatedContext.ts index abf2a8c41..63cad3e8f 100644 --- a/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/stripeSubscriptionUpdatedContext.ts +++ b/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/stripeSubscriptionUpdatedContext.ts @@ -31,4 +31,6 @@ export interface StripeSubscriptionUpdatedContext { customerProduct: FullCusProduct; updates: Partial; }[]; + /** Tracks all deletions made to customer products during this handler */ + deletedCustomerProducts: FullCusProduct[]; } diff --git a/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/tasks/handleStripeSubscriptionRenewed/handleStripeSubscriptionRenewed.ts b/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/tasks/handleStripeSubscriptionRenewed/handleStripeSubscriptionRenewed.ts index fa730cd31..41ffec877 100644 --- a/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/tasks/handleStripeSubscriptionRenewed/handleStripeSubscriptionRenewed.ts +++ b/server/src/external/stripe/webhookHandlers/handleStripeSubscriptionUpdated/tasks/handleStripeSubscriptionRenewed/handleStripeSubscriptionRenewed.ts @@ -8,7 +8,10 @@ import { getStripeSubscriptionLock } from "@/external/stripe/subscriptions/utils import type { StripeWebhookContext } from "@/external/stripe/webhookMiddlewares/stripeWebhookContext"; import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated"; import { CusProductService } from "@/internal/customers/cusProducts/CusProductService"; -import { trackCustomerProductUpdate } from "../../../common/trackCustomerProductUpdate"; +import { + trackCustomerProductDeletion, + trackCustomerProductUpdate, +} from "../../../common/trackCustomerProductUpdate"; import type { StripeSubscriptionUpdatedContext } from "../../stripeSubscriptionUpdatedContext"; import { isStripeSubscriptionRenewedEvent } from "./isStripeSubscriptionRenewedEvent"; @@ -118,6 +121,11 @@ export const handleStripeSubscriptionRenewed = async ({ logger.info( `[handleStripeSubscriptionRenewed] Deleted scheduled ${scheduledProduct.product.name}`, ); + + trackCustomerProductDeletion({ + eventContext: subscriptionUpdatedContext, + customerProduct: scheduledProduct, + }); } } diff --git a/server/src/honoMiddlewares/idempotencyMiddleware.ts b/server/src/honoMiddlewares/idempotencyMiddleware.ts index f7cbea336..baa9cbf54 100644 --- a/server/src/honoMiddlewares/idempotencyMiddleware.ts +++ b/server/src/honoMiddlewares/idempotencyMiddleware.ts @@ -1,8 +1,6 @@ -import { ErrCode, RecaseError } from "@autumn/shared"; import type { Context, Next } from "hono"; -import { redis } from "@/external/redis/initRedis.js"; import type { HonoEnv } from "@/honoUtils/HonoEnv.js"; -import { tryRedisWrite } from "@/utils/cacheUtils/cacheUtils"; +import { checkIdempotencyKey } from "@/internal/misc/idempotency/checkIdempotencyKey.js"; /** * Middleware that checks for idempotence in a request @@ -17,19 +15,11 @@ export const idempotencyMiddleware = async ( headers["idempotency-key"] || headers["Idempotency-Key"]; if (idempotencyKey) { - const redisKey = `${ctx.org.id}:${ctx.env}:idempotency:${idempotencyKey}`; - // Use SET NX (set if not exists) for atomic check-and-set to prevent race conditions - const wasSet = await tryRedisWrite(() => { - return redis.set(redisKey, "1", "PX", 1000 * 60 * 60 * 24, "NX"); // 24 hours, only set if not exists + await checkIdempotencyKey({ + orgId: ctx.org.id, + env: ctx.env, + idempotencyKey, }); - - if (!wasSet) { - throw new RecaseError({ - message: `Another request with idempotency key ${idempotencyKey} has already been received`, - code: ErrCode.DuplicateIdempotencyKey, - statusCode: 409, - }); - } } await next(); diff --git a/server/src/init.ts b/server/src/init.ts index 35a4a4b9b..5c2e10aae 100644 --- a/server/src/init.ts +++ b/server/src/init.ts @@ -110,6 +110,7 @@ const init = async () => { "If-None-Match", "If-Modified-Since", "If-Unmodified-Since", + "User-Agent", // Required for better-auth v1.4.0+ compatibility with Safari/Zen browser ], }), ); diff --git a/server/src/initHono.ts b/server/src/initHono.ts index 661e33f2f..a08657c61 100644 --- a/server/src/initHono.ts +++ b/server/src/initHono.ts @@ -46,6 +46,7 @@ const ALLOWED_HEADERS = [ "If-Unmodified-Since", "idempotency-key", "Idempotency-Key", + "User-Agent", // Required for better-auth v1.4.0+ compatibility with Safari/Zen browser ]; export const createHonoApp = () => { diff --git a/server/src/internal/balances/createBalance/prepareNewBalanceForInsertion.ts b/server/src/internal/balances/createBalance/prepareNewBalanceForInsertion.ts index 450e48ee6..f7b805080 100644 --- a/server/src/internal/balances/createBalance/prepareNewBalanceForInsertion.ts +++ b/server/src/internal/balances/createBalance/prepareNewBalanceForInsertion.ts @@ -36,9 +36,9 @@ export const prepareNewBalanceForInsertion = async ({ const entity = fullCustomer.entity; - if (entity) { - newEntitlement.entity_feature_id = entity.feature_id; - } + // NOTE: We do NOT set entity_feature_id here. That field means "per-entity balances" + // (e.g., 10 messages per seat). For entity-scoped loose balances, we only set + // internal_entity_id on the cusEnt to scope the balance to a specific entity. const newEntitlementWithFeature = enrichEntitlementWithFeature({ entitlement: newEntitlement, diff --git a/server/src/internal/balances/handlers/handleTrack.ts b/server/src/internal/balances/handlers/handleTrack.ts index 3a92e54b1..0ac583cc5 100644 --- a/server/src/internal/balances/handlers/handleTrack.ts +++ b/server/src/internal/balances/handlers/handleTrack.ts @@ -1,4 +1,9 @@ -import { TrackParamsSchema, TrackQuerySchema } from "@autumn/shared"; +import { + AffectedResource, + ApiVersion, + TrackParamsSchema, + TrackQuerySchema, +} from "@autumn/shared"; import { createRoute } from "@/honoMiddlewares/routeHandler.js"; import { runTrackV2 } from "@/internal/balances/track/runTrackV2.js"; import { @@ -8,19 +13,15 @@ import { export const handleTrack = createRoute({ query: TrackQuerySchema, - body: TrackParamsSchema, + versionedBody: { + latest: TrackParamsSchema, + [ApiVersion.V1_Beta]: TrackParamsSchema, + }, + resource: AffectedResource.Track, handler: async (c) => { const body = c.req.valid("json"); const ctx = c.get("ctx"); - // Legacy: support value in properties - if (body.properties?.value) { - const parsedValue = Number(body.properties.value); - if (!Number.isNaN(parsedValue)) { - body.value = parsedValue; - } - } - // Build feature deductions const featureDeductions = body.feature_id ? getTrackFeatureDeductions({ diff --git a/server/src/internal/balances/track/runTrackV2.ts b/server/src/internal/balances/track/runTrackV2.ts index ea607e8e3..2eccaa150 100644 --- a/server/src/internal/balances/track/runTrackV2.ts +++ b/server/src/internal/balances/track/runTrackV2.ts @@ -47,7 +47,6 @@ export const runTrackV2 = async ({ await handleEventIdempotencyKey({ ctx, body, - fullCustomer, }); } diff --git a/server/src/internal/balances/track/utils/handleEventIdempotencyKey.ts b/server/src/internal/balances/track/utils/handleEventIdempotencyKey.ts index e1179c330..2004f1de9 100644 --- a/server/src/internal/balances/track/utils/handleEventIdempotencyKey.ts +++ b/server/src/internal/balances/track/utils/handleEventIdempotencyKey.ts @@ -1,32 +1,35 @@ -import type { FullCustomer, TrackParams } from "@autumn/shared"; -import type { AutumnContext } from "../../../../honoUtils/HonoEnv"; -import { EventService } from "../../../api/events/EventService"; -import { buildEventInfo, initEvent } from "../../events/initEvent"; +import type { TrackParams } from "@autumn/shared"; +import { checkIdempotencyKey } from "@/internal/misc/idempotency/checkIdempotencyKey.js"; +import type { AutumnContext } from "../../../../honoUtils/HonoEnv.js"; export const handleEventIdempotencyKey = async ({ ctx, body, - fullCustomer, }: { ctx: AutumnContext; body: TrackParams; - fullCustomer: FullCustomer; }) => { - const eventInfo = buildEventInfo(body); - - const newEvent = initEvent({ - ctx, - eventInfo, - internalCustomerId: fullCustomer.internal_id, - internalEntityId: fullCustomer.entity?.internal_id ?? undefined, - customerId: body.customer_id, - entityId: body.entity_id, + await checkIdempotencyKey({ + orgId: ctx.org.id, + env: ctx.env, + idempotencyKey: `track:${body.idempotency_key}`, }); - await EventService.insert({ - db: ctx.db, - event: newEvent, - }); + // const eventInfo = buildEventInfo(body); - body.skip_event = true; + // const newEvent = initEvent({ + // ctx, + // eventInfo, + // internalCustomerId: fullCustomer.internal_id, + // internalEntityId: fullCustomer.entity?.internal_id ?? undefined, + // customerId: body.customer_id, + // entityId: body.entity_id, + // }); + + // await EventService.insert({ + // db: ctx.db, + // event: newEvent, + // }); + + // body.skip_event = true; }; diff --git a/server/src/internal/balances/track/utils/runRedisTrack.ts b/server/src/internal/balances/track/utils/runRedisTrack.ts index 12005c626..13f850fa6 100644 --- a/server/src/internal/balances/track/utils/runRedisTrack.ts +++ b/server/src/internal/balances/track/utils/runRedisTrack.ts @@ -53,7 +53,7 @@ const queueEvent = ({ body: TrackParams; fullCustomer: FullCustomer; }): void => { - if (body.skip_event || body.idempotency_key) return; + if (body.skip_event) return; const eventInfo = buildEventInfo(body); diff --git a/server/src/internal/billing/v2/utils/initFullCustomerProduct/initCustomerProduct.ts b/server/src/internal/billing/v2/utils/initFullCustomerProduct/initCustomerProduct.ts index b829038d4..8da1176ad 100644 --- a/server/src/internal/billing/v2/utils/initFullCustomerProduct/initCustomerProduct.ts +++ b/server/src/internal/billing/v2/utils/initFullCustomerProduct/initCustomerProduct.ts @@ -4,6 +4,7 @@ import { CusProductStatus, type InitFullCustomerProductContext, type InitFullCustomerProductOptions, + ms, notNullish, } from "@autumn/shared"; import { generateId } from "@/utils/genUtils"; @@ -43,8 +44,8 @@ export const initCustomerProduct = ({ if (initOptions?.status) return initOptions?.status; // 1 minute tolerance to determine if customer product should be scheduled. (for test clock time frozen issues) - // const TOLERANCE_MS = ms.minutes(1); - if (startsAt && startsAt > now) { + const TOLERANCE_MS = ms.minutes(1); + if (startsAt && startsAt > now + TOLERANCE_MS) { return CusProductStatus.Scheduled; } diff --git a/server/src/internal/customers/actions/createWithDefaults/createCustomerWithDefaults.ts b/server/src/internal/customers/actions/createWithDefaults/createCustomerWithDefaults.ts index 9961d163b..b1c3b95de 100644 --- a/server/src/internal/customers/actions/createWithDefaults/createCustomerWithDefaults.ts +++ b/server/src/internal/customers/actions/createWithDefaults/createCustomerWithDefaults.ts @@ -1,8 +1,4 @@ -import type { - CreateCustomerInternalOptions, - CustomerData, - FullCustomer, -} from "@autumn/shared"; +import type { CustomerData, FullCustomer } from "@autumn/shared"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; import { evaluateStripeBillingPlan } from "@/internal/billing/v2/providers/stripe/actionBuilders/evaluateStripeBillingPlan.js"; import { executeStripeBillingPlan } from "@/internal/billing/v2/providers/stripe/execute/executeStripeBillingPlan.js"; @@ -35,12 +31,10 @@ export const createCustomerWithDefaults = async ({ ctx, customerId, customerData, - internalOptions, }: { ctx: AutumnContext; customerId: string | null; customerData?: CustomerData; - internalOptions?: CreateCustomerInternalOptions; }): Promise => { // ============ Phase 1: Create Autumn customer ============ @@ -49,7 +43,6 @@ export const createCustomerWithDefaults = async ({ ctx, customerId, customerData, - internalOptions, }); logCreateCustomerContext({ ctx, context }); diff --git a/server/src/internal/customers/actions/createWithDefaults/setup/setupCreateCustomer.ts b/server/src/internal/customers/actions/createWithDefaults/setup/setupCreateCustomer.ts index 7914c7d3c..60acb9c99 100644 --- a/server/src/internal/customers/actions/createWithDefaults/setup/setupCreateCustomer.ts +++ b/server/src/internal/customers/actions/createWithDefaults/setup/setupCreateCustomer.ts @@ -1,8 +1,4 @@ -import { - type CreateCustomerInternalOptions, - type CustomerData, - RecaseError, -} from "@autumn/shared"; +import { type CustomerData, RecaseError } from "@autumn/shared"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; import { initFullCustomer } from "../../../cusUtils/initCustomer.js"; import type { CreateCustomerContext } from "../createCustomerContext.js"; @@ -20,12 +16,10 @@ export const setupCreateCustomer = async ({ ctx, customerId, customerData, - internalOptions, }: { ctx: AutumnContext; customerId: string | null; customerData?: CustomerData; - internalOptions?: CreateCustomerInternalOptions; }): Promise => { // 1. Validate if (!customerId && !customerData?.email) { @@ -39,7 +33,7 @@ export const setupCreateCustomer = async ({ // 3. Fetch default products const { fullProducts, paidProducts, hasPaidProducts } = - await setupDefaultProductsContext({ ctx, internalOptions }); + await setupDefaultProductsContext({ ctx, customerData }); const currentEpochMs = Date.now(); diff --git a/server/src/internal/customers/actions/createWithDefaults/setup/setupDefaultProductsContext.ts b/server/src/internal/customers/actions/createWithDefaults/setup/setupDefaultProductsContext.ts index 536fd89d2..59309e790 100644 --- a/server/src/internal/customers/actions/createWithDefaults/setup/setupDefaultProductsContext.ts +++ b/server/src/internal/customers/actions/createWithDefaults/setup/setupDefaultProductsContext.ts @@ -1,7 +1,9 @@ import { - type CreateCustomerInternalOptions, + type CustomerData, type FullProduct, isFreeProduct, + ProductNotFoundError, + RecaseError, } from "@autumn/shared"; import type { AutumnContext } from "@/honoUtils/HonoEnv.js"; import { ProductService } from "@/internal/products/ProductService.js"; @@ -13,15 +15,67 @@ export interface DefaultProductsContext { hasPaidProducts: boolean; } -export const setupDefaultProductsContext = async ({ +const getOverrideAutoEnableProduct = async ({ ctx, - internalOptions, + customerData, }: { ctx: AutumnContext; - internalOptions?: CreateCustomerInternalOptions; + customerData?: CustomerData; +}): Promise => { + const { db, org, env } = ctx; + + if (!customerData?.auto_enable_plan_id) return undefined; + + const plan = await ProductService.getFull({ + db, + orgId: org.id, + env, + idOrInternalId: customerData.auto_enable_plan_id, + }); + + if (!plan) + throw new ProductNotFoundError({ + productId: customerData.auto_enable_plan_id, + }); + + if ( + !isFreeProduct({ prices: plan.prices }) && + !isDefaultTrialFullProduct({ product: plan }) + ) { + throw new RecaseError({ + message: `Auto-enable plan must be a free product, or have a free trial with 'card_required' as false`, + }); + } + + return plan; +}; + +export const setupDefaultProductsContext = async ({ + ctx, + customerData, +}: { + ctx: AutumnContext; + customerData?: CustomerData; }): Promise => { const { db, org, env } = ctx; + const autoEnableProduct = await getOverrideAutoEnableProduct({ + ctx, + customerData, + }); + + if (autoEnableProduct) { + const autoEnableIsPaid = !isFreeProduct({ + prices: autoEnableProduct.prices, + }); + + return { + fullProducts: [autoEnableProduct], + paidProducts: autoEnableIsPaid ? [autoEnableProduct] : [], + hasPaidProducts: autoEnableIsPaid, + }; + } + const defaultProds = await ProductService.listDefault({ db, orgId: org.id, @@ -46,6 +100,7 @@ export const setupDefaultProductsContext = async ({ } let selectedProducts: FullProduct[] = []; + const internalOptions = customerData?.internal_options; if (internalOptions?.default_group) { const defaultProd = groupToDefaultProd[internalOptions.default_group]; diff --git a/server/src/internal/customers/attach/attachFunctions/addProductFlow/handleAddProduct.ts b/server/src/internal/customers/attach/attachFunctions/addProductFlow/handleAddProduct.ts index 49e564666..b5661e28c 100644 --- a/server/src/internal/customers/attach/attachFunctions/addProductFlow/handleAddProduct.ts +++ b/server/src/internal/customers/attach/attachFunctions/addProductFlow/handleAddProduct.ts @@ -2,6 +2,7 @@ import { AttachBranch, type AttachConfig, AttachFunctionResponseSchema, + AttachScenario, SuccessCode, } from "@autumn/shared"; import { subToPeriodStartEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js"; @@ -60,6 +61,7 @@ export const handleFreeProduct = async ({ carryExistingUsages: config?.carryUsage || false, anchorToUnix, logger, + scenario: AttachScenario.New, }), ); } diff --git a/server/src/internal/customers/attach/attachFunctions/addProductFlow/handlePaidProduct.ts b/server/src/internal/customers/attach/attachFunctions/addProductFlow/handlePaidProduct.ts index f97e6d6bd..9694624e1 100644 --- a/server/src/internal/customers/attach/attachFunctions/addProductFlow/handlePaidProduct.ts +++ b/server/src/internal/customers/attach/attachFunctions/addProductFlow/handlePaidProduct.ts @@ -273,7 +273,7 @@ export const handlePaidProduct = async ({ carryExistingUsages: config.carryUsage, scenario: AttachScenario.New, trialEndsAt: trialEndsAt || undefined, - startsAt: attachParams.now, + // startsAt: attachParams.now, logger, }), ); diff --git a/server/src/internal/customers/cusProducts/actions/activateScheduled.ts b/server/src/internal/customers/cusProducts/actions/activateScheduled.ts index d78bf5067..5d133296a 100644 --- a/server/src/internal/customers/cusProducts/actions/activateScheduled.ts +++ b/server/src/internal/customers/cusProducts/actions/activateScheduled.ts @@ -29,8 +29,8 @@ export const activateScheduledCustomerProduct = async ({ ctx: AutumnContext; customerProduct: FullCusProduct; fullCustomer: FullCustomer; - subscriptionIds: string[]; - scheduledIds: string[]; + subscriptionIds?: string[]; + scheduledIds?: string[]; }): Promise<{ updates: Partial }> => { const { db, org, env, logger } = ctx; diff --git a/server/src/internal/customers/cusProducts/cusEnts/cusRollovers/rolloverUtils.ts b/server/src/internal/customers/cusProducts/cusEnts/cusRollovers/rolloverUtils.ts index 9d5473c52..95a7a796a 100644 --- a/server/src/internal/customers/cusProducts/cusEnts/cusRollovers/rolloverUtils.ts +++ b/server/src/internal/customers/cusProducts/cusEnts/cusRollovers/rolloverUtils.ts @@ -14,7 +14,7 @@ export const getRolloverUpdates = ({ nextResetAt, }: { cusEnt: FullCustomerEntitlement; - nextResetAt: number; + nextResetAt: number; // from when we add the rollover expiry duration to calculate the next expiry }) => { const update: { toDelete: string[]; diff --git a/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/getOrCreateCachedFullCustomer.ts b/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/getOrCreateCachedFullCustomer.ts index 8ce49916b..984d7b210 100644 --- a/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/getOrCreateCachedFullCustomer.ts +++ b/server/src/internal/customers/cusUtils/fullCustomerCacheUtils/getOrCreateCachedFullCustomer.ts @@ -1,7 +1,6 @@ import { type AppEnv, type CheckParams, - type CreateCustomerInternalOptions, CusExpand, type Entity, type FullCustomer, @@ -23,14 +22,12 @@ export const getOrCreateCachedFullCustomer = async ({ ctx, params, source, - internalOptions, }: { ctx: AutumnContext; params: Omit & { customer_id: string | null; }; source?: string; - internalOptions?: CreateCustomerInternalOptions; }): Promise => { const { org, env, db, skipCache, logger } = ctx; const { @@ -80,7 +77,6 @@ export const getOrCreateCachedFullCustomer = async ({ ctx, customerId, customerData, - internalOptions, }); } diff --git a/server/src/internal/customers/handlers/handlePostCustomerV2.ts b/server/src/internal/customers/handlers/handlePostCustomerV2.ts index 391e3a6cd..906aa3fb1 100644 --- a/server/src/internal/customers/handlers/handlePostCustomerV2.ts +++ b/server/src/internal/customers/handlers/handlePostCustomerV2.ts @@ -49,7 +49,6 @@ export const handlePostCustomer = createRoute({ entity_data: createCusParams.entity_data, }, source: "handlePostCustomer", - internalOptions: createCusParams.internal_options, }); const apiCustomer = await getApiCustomer({ diff --git a/server/src/internal/misc/idempotency/checkIdempotencyKey.ts b/server/src/internal/misc/idempotency/checkIdempotencyKey.ts new file mode 100644 index 000000000..7769ea2ee --- /dev/null +++ b/server/src/internal/misc/idempotency/checkIdempotencyKey.ts @@ -0,0 +1,52 @@ +import { ErrCode, RecaseError } from "@autumn/shared"; +import { redis } from "@/external/redis/initRedis.js"; + +const IDEMPOTENCY_TTL_MS = 1000 * 60 * 60 * 24; // 24 hours + +/** + * Checks and sets an idempotency key in Redis using atomic SET NX operation. + * If Redis is not ready, allows the request to proceed (fail-open). + * Throws if the key already exists (duplicate request). + */ +export const checkIdempotencyKey = async ({ + orgId, + env, + idempotencyKey, +}: { + orgId: string; + env: string; + idempotencyKey: string; +}): Promise => { + // Fail-open: if Redis is not ready, allow the request + if (redis.status !== "ready") { + return; + } + + const redisKey = `${orgId}:${env}:idempotency:${idempotencyKey}`; + + try { + // Use SET NX (set if not exists) for atomic check-and-set to prevent race conditions + const wasSet = await redis.set( + redisKey, + "1", + "PX", + IDEMPOTENCY_TTL_MS, + "NX", + ); + + if (!wasSet) { + throw new RecaseError({ + message: `Another request with idempotency key ${idempotencyKey} has already been received`, + code: ErrCode.DuplicateIdempotencyKey, + statusCode: 409, + }); + } + } catch (error) { + // Re-throw RecaseError (duplicate key) + if (error instanceof RecaseError) { + throw error; + } + // For other Redis errors, fail-open (allow request) + return; + } +}; diff --git a/server/src/utils/scriptUtils/constructItem.ts b/server/src/utils/scriptUtils/constructItem.ts index accf5431b..8b4f44c39 100644 --- a/server/src/utils/scriptUtils/constructItem.ts +++ b/server/src/utils/scriptUtils/constructItem.ts @@ -127,6 +127,7 @@ export const constructArrearItem = ({ on_increase: OnIncrease.ProrateImmediately, on_decrease: OnDecrease.ProrateImmediately, }, + rolloverConfig, entityFeatureId, usageLimit, interval = ProductItemInterval.Month, @@ -137,6 +138,7 @@ export const constructArrearItem = ({ price?: number; billingUnits?: number; config?: ProductItemConfig; + rolloverConfig?: RolloverConfig; entityFeatureId?: string; usageLimit?: number; interval?: ProductItemInterval; @@ -151,7 +153,10 @@ export const constructArrearItem = ({ interval: interval, interval_count: intervalCount, reset_usage_when_enabled: true, - config, + config: { + ...config, + ...(rolloverConfig ? { rollover: rolloverConfig } : {}), + }, entity_feature_id: entityFeatureId, usage_limit: usageLimit, }; diff --git a/server/tests/attach/checkout/checkout6.test.ts b/server/tests/attach/checkout/checkout6.test.ts index 076dacf4f..a818b0193 100644 --- a/server/tests/attach/checkout/checkout6.test.ts +++ b/server/tests/attach/checkout/checkout6.test.ts @@ -79,7 +79,6 @@ describe(`${chalk.yellowBright(`${testCase}: Testing invoice checkout via checko product: pro, }); }); - return; test("should have no URL returned if try to attach premium (with invoice true)", async () => { await expectAutumnError({ diff --git a/server/tests/attach/migrations/migration2.test.ts b/server/tests/attach/migrations/migration2.test.ts index 6bcb48878..752e2cc9f 100644 --- a/server/tests/attach/migrations/migration2.test.ts +++ b/server/tests/attach/migrations/migration2.test.ts @@ -98,6 +98,7 @@ describe(`${chalk.yellowBright(`${testCase}: Testing migration for pro usage pro }); test("should attach track usage and get correct balance", async () => { + await timeout(5000); const wordsUsage = 120000; await autumn.track({ customer_id: customerId, diff --git a/server/tests/attach/migrations/migration4.test.ts b/server/tests/attach/migrations/migration4.test.ts index b6b7d5f9c..06ca97b5e 100644 --- a/server/tests/attach/migrations/migration4.test.ts +++ b/server/tests/attach/migrations/migration4.test.ts @@ -81,6 +81,7 @@ describe(`${chalk.yellowBright(`${testCase}: Testing migration for pro -> pro wi }); test("should attach track usage and get correct balance", async () => { + await timeout(3000); const wordsUsage = 120000; await autumn.track({ customer_id: customerId, @@ -88,7 +89,7 @@ describe(`${chalk.yellowBright(`${testCase}: Testing migration for pro -> pro wi feature_id: TestFeature.Words, }); - await timeout(4000); + await timeout(3000); const { stripeSubs, cusProduct } = await runMigrationTest({ autumn, diff --git a/server/tests/attach/misc/attach-misc1.test.ts b/server/tests/attach/misc/attach-misc1.test.ts deleted file mode 100644 index 3e9124f47..000000000 --- a/server/tests/attach/misc/attach-misc1.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { beforeAll, describe, expect, test } from "bun:test"; -import { LegacyVersion } from "@autumn/shared"; -import { TestFeature } from "@tests/setup/v2Features.js"; -import ctx from "@tests/utils/testInitUtils/createTestContext.js"; -import chalk from "chalk"; -import { AutumnInt } from "@/external/autumn/autumnCli.js"; -import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js"; -import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js"; -import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js"; - -const testCase = "attach-misc1"; - -const pro = constructProduct({ - items: [ - constructArrearItem({ - featureId: TestFeature.Words, - includedUsage: 1000, - }), - ], - type: "pro", -}); - -describe(`${chalk.yellowBright(`${testCase}: Testing auto-create customer and entity via attach`)}`, () => { - const customerId = testCase; - const entityId = "entity-1"; - const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 }); - - beforeAll(async () => { - // Delete customer if exists - try { - await autumn.customers.delete(customerId); - } catch { - // Ignore if customer doesn't exist - } - - // Initialize products - await initProductsV0({ - ctx, - products: [pro], - prefix: testCase, - }); - }); - - test("should auto-create customer and entity when calling attach", async () => { - // Attach with customer_data and entity_data to auto-create both - const attachResponse = await autumn.attach({ - customer_id: customerId, - product_id: pro.id, - entity_id: entityId, - customer_data: { - name: "Auto Created Customer", - email: "autocreated@test.com", - fingerprint: "test-fingerprint-123", - }, - entity_data: { - name: "Auto Created Entity", - feature_id: TestFeature.Users, - }, - }); - - // Verify attach was successful - expect(attachResponse).toBeDefined(); - - // Verify customer was auto-created - const customer = await autumn.customers.get(customerId); - expect(customer).toBeDefined(); - expect(customer.id).toBe(customerId); - expect(customer.name).toBe("Auto Created Customer"); - expect(customer.email).toBe("autocreated@test.com"); - expect(customer.fingerprint).toBe("test-fingerprint-123"); - - // Verify entity was auto-created - const entity = await autumn.entities.get(customerId, entityId); - expect(entity).toBeDefined(); - expect(entity.id).toBe(entityId); - expect(entity.name).toBe("Auto Created Entity"); - expect(entity.customer_id).toBe(customerId); - }); -}); diff --git a/server/tests/attach/misc/attach-misc2.test.ts b/server/tests/attach/misc/attach-misc2.test.ts deleted file mode 100644 index f329730f1..000000000 --- a/server/tests/attach/misc/attach-misc2.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { beforeAll, describe, test } from "bun:test"; -import { LegacyVersion } from "@autumn/shared"; -import { TestFeature } from "@tests/setup/v2Features.js"; -import ctx from "@tests/utils/testInitUtils/createTestContext.js"; -import chalk from "chalk"; -import { AutumnInt } from "@/external/autumn/autumnCli.js"; -import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js"; -import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js"; -import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js"; - -const testCase = "attach-misc2"; - -const pro = constructProduct({ - items: [ - constructArrearItem({ - featureId: TestFeature.Words, - includedUsage: 1000, - }), - ], - type: "pro", -}); - -describe(`${chalk.yellowBright(`${testCase}: Testing attach race condition`)}`, () => { - const customerId = testCase; - const entityId = "entity-1"; - const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 }); - - beforeAll(async () => { - // Delete customer if exists - try { - await autumn.customers.delete(customerId); - } catch { - // Ignore if customer doesn't exist - } - - // Initialize products - await initProductsV0({ - ctx, - products: [pro], - prefix: testCase, - }); - }); - - test("should auto-create customer and entity when calling attach", async () => { - // Attach with customer_data and entity_data to auto-create both - const responses = await Promise.allSettled([ - autumn.attach({ - customer_id: customerId, - product_id: pro.id, - }), - autumn.attach({ - customer_id: customerId, - product_id: pro.id, - }), - ]); - - console.log("Responses:", responses); - }); -}); diff --git a/server/tests/attach/misc/attach-misc3.test.ts b/server/tests/attach/misc/attach-misc3.test.ts deleted file mode 100644 index 0823d7b26..000000000 --- a/server/tests/attach/misc/attach-misc3.test.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { beforeAll, describe, expect, it } from "bun:test"; -import { ApiVersion } from "@autumn/shared"; -import { TestFeature } from "@tests/setup/v2Features.js"; -import ctx from "@tests/utils/testInitUtils/createTestContext.js"; -import chalk from "chalk"; -import { AutumnInt } from "@/external/autumn/autumnCli.js"; -import { redis } from "@/external/redis/initRedis.js"; -import { timeout } from "@/utils/genUtils.js"; -import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js"; -import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js"; -import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js"; -import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js"; -import { CACHE_CUSTOMER_VERSIONS } from "../../../src/_luaScripts/cacheConfig"; - -const pro = constructProduct({ - type: "pro", - items: [ - constructFeatureItem({ - featureId: TestFeature.Messages, - includedUsage: 100, - }), - ], -}); - -const premium = constructProduct({ - type: "premium", - items: [ - constructFeatureItem({ - featureId: TestFeature.Messages, - includedUsage: 300, - }), - ], -}); - -const testCase = "attach-misc3"; - -describe(`${chalk.yellowBright("attach-misc3: timestamp-based stale write prevention test")}`, () => { - const customerId = testCase; - const autumnV1: AutumnInt = new AutumnInt({ version: ApiVersion.V1_2 }); - - beforeAll(async () => { - await initCustomerV3({ - ctx, - customerId, - withTestClock: true, - attachPm: "success", - }); - - await initProductsV0({ - ctx, - products: [pro, premium], - prefix: testCase, - }); - }); - - it("should block stale writes (fetchTimeMs < deletionTime) and allow fresh writes", async () => { - // Step 1: Attach pro product (this will cache the customer) - console.log("\n--- Step 1: Attach pro product ---"); - await autumnV1.attach({ - customer_id: customerId, - product_id: pro.id, - }); - - // Verify pro is attached - const beforeCustomer = await autumnV1.customers.get(customerId); - expect(beforeCustomer.products?.[0]?.id).toBe(pro.id); - console.log("Pro product attached"); - - // Step 2: Record a "stale" fetch time (before deletion) - const staleFetchTimeMs = Date.now(); - console.log( - `\n--- Step 2: Recorded stale fetch time: ${staleFetchTimeMs} ---`, - ); - - // Small delay to ensure deletion timestamp is definitely after fetch time - await timeout(50); - - // Step 3: Delete cache (this sets the guard with current timestamp) - console.log("\n--- Step 3: Delete cache (sets guard with timestamp) ---"); - const deletedCount = await redis.deleteCustomer( - CACHE_CUSTOMER_VERSIONS.LATEST, - ctx.org.id, - ctx.env, - customerId, - ); - console.log(`Deleted ${deletedCount} cache keys`); - - // Step 4: Try to write with STALE fetch time (before deletion) - console.log("\n--- Step 4: Try to write cache with stale fetchTimeMs ---"); - const fakeOldCustomerData = { - id: customerId, - name: "Test Customer", - email: null, - created_at: Date.now(), - fingerprint: null, - stripe_id: null, - env: ctx.env, - metadata: {}, - subscriptions: [{ plan_id: pro.id, status: "active" }], // OLD product! - scheduled_subscriptions: [], - invoices: [], - balances: {}, - entities: [], - }; - - const writeResult = await redis.setCustomer( - JSON.stringify(fakeOldCustomerData), - ctx.org.id, - ctx.env, - customerId, - staleFetchTimeMs.toString(), // Stale timestamp - BEFORE deletion - ); - - console.log(`Write result with stale timestamp: ${writeResult}`); - - // Verify write was blocked because fetchTimeMs < deletionTime - expect(writeResult).toBe("STALE_WRITE"); - console.log("✅ Stale cache write was blocked!"); - - // Step 5: Try to write with FRESH fetch time (after deletion) - console.log("\n--- Step 5: Try to write cache with fresh fetchTimeMs ---"); - const freshFetchTimeMs = Date.now(); // This is AFTER the deletion - - const writeResult2 = await redis.setCustomer( - JSON.stringify(fakeOldCustomerData), - ctx.org.id, - ctx.env, - customerId, - freshFetchTimeMs.toString(), // Fresh timestamp - AFTER deletion - ); - - console.log(`Write result with fresh timestamp: ${writeResult2}`); - expect(writeResult2).toBe("OK"); - console.log("✅ Fresh cache write succeeded!"); - - // Step 6: Verify CACHE_EXISTS for subsequent writes - console.log("\n--- Step 6: Verify CACHE_EXISTS for duplicate writes ---"); - const writeResult3 = await redis.setCustomer( - JSON.stringify(fakeOldCustomerData), - ctx.org.id, - ctx.env, - customerId, - Date.now().toString(), - ); - - console.log(`Write result for duplicate: ${writeResult3}`); - expect(writeResult3).toBe("CACHE_EXISTS"); - console.log("✅ Duplicate write correctly returned CACHE_EXISTS!"); - - // Cleanup: Delete the cache we wrote - await redis.deleteCustomer( - CACHE_CUSTOMER_VERSIONS.LATEST, - ctx.org.id, - ctx.env, - customerId, - ); - }); -}); diff --git a/server/tests/attach/misc/attach-misc5.test.ts b/server/tests/attach/misc/attach-misc5.test.ts deleted file mode 100644 index 0dbcc6afa..000000000 --- a/server/tests/attach/misc/attach-misc5.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { beforeAll, describe, expect, test } from "bun:test"; -import { type AppEnv, LegacyVersion, type Organization } from "@autumn/shared"; -import { TestFeature } from "@tests/setup/v2Features.js"; -import { getMainCusProduct } from "@tests/utils/cusProductUtils/cusProductUtils.js"; -import { expectProductAttached } from "@tests/utils/expectUtils/expectProductAttached.js"; -import ctx from "@tests/utils/testInitUtils/createTestContext.js"; -import chalk from "chalk"; -import type { Stripe } from "stripe"; -import type { DrizzleCli } from "@/db/initDrizzle.js"; -import { AutumnInt } from "@/external/autumn/autumnCli.js"; -import { cusProductToSub } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; -import { timeout } from "@/utils/genUtils.js"; -import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js"; -import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js"; -import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js"; -import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js"; - -const testCase = "attach-misc5"; - -const pro = constructProduct({ - id: "pro", - items: [constructFeatureItem({ featureId: TestFeature.Words })], - type: "pro", -}); - -describe(`${chalk.yellowBright( - `${testCase}: Testing convert collection method from send_invoice`, -)}`, () => { - const customerId = testCase; - const autumn: AutumnInt = new AutumnInt({ version: LegacyVersion.v1_4 }); - - let stripeCli: Stripe; - let db: DrizzleCli; - let org: Organization; - let env: AppEnv; - - beforeAll(async () => { - db = ctx.db; - org = ctx.org; - env = ctx.env; - stripeCli = ctx.stripeCli; - - await initProductsV0({ - ctx, - products: [pro], - prefix: testCase, - customerId, - }); - - await initCustomerV3({ - ctx, - customerId, - attachPm: "success", - }); - }); - - test("should attach pro product and pay for it", async () => { - const res = await autumn.attach({ - customer_id: customerId, - product_id: pro.id, - invoice: true, - enable_product_immediately: true, - }); - - expect(res.invoice).toBeDefined(); - - const customer = await autumn.customers.get(customerId); - expectProductAttached({ - customer, - product: pro, - }); - - const invoiceStripeId = res.invoice.stripe_id; - await stripeCli.invoices.finalizeInvoice(invoiceStripeId); - - await stripeCli.invoices.pay(invoiceStripeId); - }); - - test("should have collection method charge automatically", async () => { - await timeout(5000); - - const cusProduct = await getMainCusProduct({ - db, - customerId, - orgId: org.id, - env, - productGroup: pro.group ?? undefined, - }); - - const sub = await cusProductToSub({ - cusProduct, - stripeCli, - }); - - expect(sub?.collection_method ?? undefined).toBe("charge_automatically"); - }); -}); diff --git a/server/tests/attach/others/others10.test.ts b/server/tests/attach/others/others10.test.ts index 8ec0d3826..8f5b27ec7 100644 --- a/server/tests/attach/others/others10.test.ts +++ b/server/tests/attach/others/others10.test.ts @@ -52,7 +52,7 @@ describe(`${chalk.yellowBright(`${testCase}/idempotency: idempotency key already product_id: pro.id, }, { - "idempotency-key": idempotencyKey, + idempotencyKey, }, ), autumnV1.attach( @@ -61,7 +61,7 @@ describe(`${chalk.yellowBright(`${testCase}/idempotency: idempotency key already product_id: pro.id, }, { - "idempotency-key": idempotencyKey, + idempotencyKey, }, ), ]); diff --git a/server/tests/balances/track/basic/track-basic6.test.ts b/server/tests/balances/track/basic/track-basic6.test.ts index ca300bfea..25852cd0c 100644 --- a/server/tests/balances/track/basic/track-basic6.test.ts +++ b/server/tests/balances/track/basic/track-basic6.test.ts @@ -26,9 +26,14 @@ const freeProd = constructProduct({ const testCase = "track-basic6"; +// Generate unique idempotency keys per test run to avoid Redis TTL conflicts +const testRunId = Date.now().toString(36); + describe(`${chalk.yellowBright("track-basic6: test idempotency key prevents duplicate tracks")}`, () => { const customerId = "track-basic6"; const autumnV1: AutumnInt = new AutumnInt({ version: ApiVersion.V1_2 }); + const idempotencyKey1 = `test-idempotency-key-1-${testRunId}`; + const idempotencyKey2 = `test-idempotency-key-2-${testRunId}`; beforeAll(async () => { await initCustomerV3({ @@ -58,13 +63,12 @@ describe(`${chalk.yellowBright("track-basic6: test idempotency key prevents dupl test("should process first track with idempotency key", async () => { const deductValue = 25.5; - const idempotencyKey = "test-idempotency-key-1"; await autumnV1.track({ customer_id: customerId, feature_id: TestFeature.Messages, value: deductValue, - idempotency_key: idempotencyKey, + idempotency_key: idempotencyKey1, }); const customer = await autumnV1.customers.get(customerId); @@ -76,28 +80,28 @@ describe(`${chalk.yellowBright("track-basic6: test idempotency key prevents dupl expect(balance).toBe(expectedBalance); expect(usage).toBe(deductValue); + await timeout(2000); const eventsList = await getCustomerEvents({ customerId }); expect(eventsList).toHaveLength(1); - expect(eventsList?.[0].idempotency_key).toBe(idempotencyKey); + expect(eventsList?.[0].idempotency_key).toBe(idempotencyKey1); expect(eventsList?.[0].value).toBe(deductValue); }); test("should reject second track with same idempotency key", async () => { const deductValue = 30.75; // Different value - const idempotencyKey = "test-idempotency-key-1"; // Same key // Get balance before attempting duplicate track const customerBefore = await autumnV1.customers.get(customerId); const balanceBefore = customerBefore.features[TestFeature.Messages].balance; // This should fail or be rejected due to duplicate idempotency key await expectAutumnError({ - errCode: ErrCode.DuplicateEvent, + errCode: ErrCode.DuplicateIdempotencyKey, func: async () => { await autumnV1.track({ customer_id: customerId, feature_id: TestFeature.Messages, value: deductValue, - idempotency_key: idempotencyKey, + idempotency_key: idempotencyKey1, // Same key as first test }); }, }); @@ -117,12 +121,11 @@ describe(`${chalk.yellowBright("track-basic6: test idempotency key prevents dupl const eventsList = await getCustomerEvents({ customerId }); expect(eventsList).toHaveLength(1); - expect(eventsList?.[0].idempotency_key).toBe(idempotencyKey); + expect(eventsList?.[0].idempotency_key).toBe(idempotencyKey1); }); test("should process track with different idempotency key", async () => { const deductValue = 15.25; - const idempotencyKey = "test-idempotency-key-2"; // Different key const customerBefore = await autumnV1.customers.get(customerId); const balanceBefore = customerBefore.features[TestFeature.Messages].balance; @@ -131,7 +134,7 @@ describe(`${chalk.yellowBright("track-basic6: test idempotency key prevents dupl customer_id: customerId, feature_id: TestFeature.Messages, value: deductValue, - idempotency_key: idempotencyKey, + idempotency_key: idempotencyKey2, // Different key }); const customer = await autumnV1.customers.get(customerId); diff --git a/server/tests/balances/track/paid-allocated/track-paid-allocated7.test.ts b/server/tests/balances/track/paid-allocated/track-paid-allocated7.test.ts index cf255e920..47f905f63 100644 --- a/server/tests/balances/track/paid-allocated/track-paid-allocated7.test.ts +++ b/server/tests/balances/track/paid-allocated/track-paid-allocated7.test.ts @@ -24,7 +24,7 @@ test( items: [allocatedUsersItem, priceItem], }); - const uniqueId = `paid-alloc-lock-${Date.now()}`; + const uniqueId = `paid-alloc-lock`; const { customerId, autumnV2 } = await initScenario({ customerId: uniqueId, setup: [ diff --git a/server/tests/balances/track/rollovers/track-rollover3.test.ts b/server/tests/balances/track/rollovers/track-rollover3.test.ts index 5a6e6521c..2fe0ea3e3 100644 --- a/server/tests/balances/track/rollovers/track-rollover3.test.ts +++ b/server/tests/balances/track/rollovers/track-rollover3.test.ts @@ -5,15 +5,14 @@ import { RolloverExpiryDurationType, } from "@autumn/shared"; import { TestFeature } from "@tests/setup/v2Features.js"; +import { advanceToNextInvoice } from "@tests/utils/testAttachUtils/testAttachUtils"; import ctx from "@tests/utils/testInitUtils/createTestContext.js"; import chalk from "chalk"; -import { addMonths } from "date-fns"; import type Stripe from "stripe"; import { AutumnInt } from "@/external/autumn/autumnCli.js"; import { timeout } from "@/utils/genUtils.js"; -import { constructArrearProratedItem } from "@/utils/scriptUtils/constructItem.js"; +import { constructArrearItem } from "@/utils/scriptUtils/constructItem.js"; import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js"; -import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js"; import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js"; import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js"; @@ -22,7 +21,7 @@ const rolloverConfig = { length: 1, duration: RolloverExpiryDurationType.Month, }; -const messagesItem = constructArrearProratedItem({ +const messagesItem = constructArrearItem({ featureId: TestFeature.Messages, includedUsage: 400, rolloverConfig, @@ -81,11 +80,10 @@ describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for usage price f await timeout(3000); - await advanceTestClock({ + await advanceToNextInvoice({ stripeCli, testClockId, - advanceTo: addMonths(new Date(), 1).getTime(), - waitForSeconds: 20, + withPause: true, }); const cus = await autumn.customers.get(customerId); diff --git a/server/tests/balances/track/rollovers/track-rollover6.test.ts b/server/tests/balances/track/rollovers/track-rollover6.test.ts index 196613264..ed5695dcd 100644 --- a/server/tests/balances/track/rollovers/track-rollover6.test.ts +++ b/server/tests/balances/track/rollovers/track-rollover6.test.ts @@ -6,16 +6,13 @@ import { RolloverExpiryDurationType, } from "@autumn/shared"; import { TestFeature } from "@tests/setup/v2Features.js"; -import { hoursToFinalizeInvoice } from "@tests/utils/constants.js"; import ctx from "@tests/utils/testInitUtils/createTestContext.js"; import chalk from "chalk"; -import { addHours, addMonths } from "date-fns"; import type Stripe from "stripe"; import { AutumnInt } from "@/external/autumn/autumnCli.js"; import { timeout } from "@/utils/genUtils.js"; import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js"; import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js"; -import { advanceTestClock } from "@/utils/scriptUtils/testClockUtils.js"; import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js"; import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js"; import { resetAndGetCusEnt } from "./rolloverTestUtils.js"; @@ -89,7 +86,7 @@ describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for upgrade`)}`, test("should attach free product", async () => { await autumn.attach({ customer_id: customerId, - product_id: pro.id, + product_id: free.id, }); }); @@ -110,17 +107,7 @@ describe(`${chalk.yellowBright(`${testCase}: Testing rollovers for upgrade`)}`, // Attach pro await autumn.attach({ customer_id: customerId, - product_id: free.id, - }); - - await advanceTestClock({ - stripeCli, - testClockId, - advanceTo: addHours( - addMonths(curUnix, 1), - hoursToFinalizeInvoice, - ).getTime(), - waitForSeconds: 20, + product_id: pro.id, }); const cus = await autumn.customers.get(customerId); diff --git a/server/tests/integration/balances/track/track-misc.test.ts b/server/tests/integration/balances/track/track-misc.test.ts new file mode 100644 index 000000000..7cdcdd0aa --- /dev/null +++ b/server/tests/integration/balances/track/track-misc.test.ts @@ -0,0 +1,331 @@ +import { expect, test } from "bun:test"; + +import { + type ApiCustomerV3, + type ApiEntityV0, + CusExpand, + sumValues, + type TrackResponseV2, +} from "@autumn/shared"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import { timeout } from "@tests/utils/genUtils.js"; +import ctx from "@tests/utils/testInitUtils/createTestContext.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { Decimal } from "decimal.js"; +import { EventService } from "@/internal/api/events/EventService.js"; + +// ═══════════════════════════════════════════════════════════════════ +// TRACK-MISC1: Auto-create customer and entity via track +// ═══════════════════════════════════════════════════════════════════ + +test.concurrent(`${chalk.yellowBright("track-misc1: track auto-creates customer and entity")}`, async () => { + const messagesItem = items.monthlyMessages({ includedUsage: 100 }); + const freeProd = products.base({ + id: "free", + items: [messagesItem], + }); + + const customerId = "track-misc1"; + + const { autumnV1 } = await initScenario({ + setup: [ + s.deleteCustomer({ customerId: "track-misc1" }), + s.products({ list: [freeProd], prefix: customerId }), + ], + actions: [], + }); + + const entityId = `${customerId}-entity-1`; + + await autumnV1.track({ + customer_id: customerId, + customer_data: { + name: "Test Customer", + email: "test@test.com", + }, + feature_id: TestFeature.Messages, + entity_id: entityId, + entity_data: { + name: "Test Entity", + feature_id: TestFeature.Users, + }, + value: 5, + }); + + // Verify customer was created with provided data + const customer = await autumnV1.customers.get(customerId); + expect(customer).toMatchObject({ + id: customerId, + name: "Test Customer", + email: "test@test.com", + }); + + // Verify entity was created with provided data + const entity = await autumnV1.entities.get(customerId, entityId); + expect(entity).toMatchObject({ + id: entityId, + name: "Test Entity", + }); + + // Verify customer.entities includes the created entity + const customerWithEntities = await autumnV1.customers.get( + customerId, + { expand: [CusExpand.Entities] }, + ); + expect(customerWithEntities.entities).toBeDefined(); + expect(customerWithEntities.entities).toHaveLength(1); + expect(customerWithEntities.entities?.[0].id).toBe(entityId); + expect(customerWithEntities.entities?.[0].name).toBe("Test Entity"); +}); + +// ═══════════════════════════════════════════════════════════════════ +// TRACK-MISC2: Track event stores properties +// ═══════════════════════════════════════════════════════════════════ + +test.concurrent(`${chalk.yellowBright("track-misc2: track event stores custom properties")}`, async () => { + const { customerId, autumnV1 } = await initScenario({ + customerId: "track-misc2", + setup: [s.customer({ testClock: false })], + actions: [], + }); + + await autumnV1.track({ + customer_id: customerId, + customer_data: { + name: "track-misc2", + email: "track-misc2@test.com", + }, + feature_id: TestFeature.Messages, + value: 5, + properties: { + hello: "world", + foo: "bar", + }, + }); + + const customer = await autumnV1.customers.get(customerId, { + with_autumn_id: true, + }); + + await timeout(2000); + + const events = await EventService.getByCustomerId({ + db: ctx.db, + orgId: ctx.org.id, + internalCustomerId: customer.autumn_id!, + env: ctx.env, + }); + + expect(events).toHaveLength(1); + expect(events?.[0].properties).toMatchObject({ + hello: "world", + foo: "bar", + }); +}); + +// ═══════════════════════════════════════════════════════════════════ +// TRACK-MISC3: Track creates events when balance is empty +// ═══════════════════════════════════════════════════════════════════ + +test.concurrent(`${chalk.yellowBright("track-misc3: track creates events when customer has no balance")}`, async () => { + const { customerId, autumnV1 } = await initScenario({ + customerId: "track-misc3", + setup: [s.customer({ testClock: false })], + actions: [], + }); + + const trackCount = Math.floor(Math.random() * 10) + 1; + let totalValue = 0; + + await Promise.all( + Array.from({ length: trackCount }, () => { + const trackValue = Math.random() * 10; + totalValue += trackValue; + return autumnV1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: trackValue, + }); + }), + ); + + const customer = await autumnV1.customers.get(customerId, { + with_autumn_id: true, + }); + + await timeout(2000); + + const events = await EventService.getByCustomerId({ + db: ctx.db, + orgId: ctx.org.id, + internalCustomerId: customer.autumn_id ?? "", + env: ctx.env, + }); + + expect(events).toHaveLength(trackCount); + expect(sumValues(events.map((event) => event.value ?? 0)).toFixed(10)).toBe( + totalValue.toFixed(10), + ); +}); + +// ═══════════════════════════════════════════════════════════════════ +// TRACK-MISC4: Track v1.2 response format +// ═══════════════════════════════════════════════════════════════════ + +test.concurrent(`${chalk.yellowBright("track-misc4: track returns correct v1.2 response format")}`, async () => { + const messagesItem = items.monthlyMessages({ includedUsage: 100 }); + const freeProd = products.base({ + id: "free", + items: [messagesItem], + }); + + const { customerId, autumnV1 } = await initScenario({ + customerId: "track-misc4", + setup: [s.customer({ testClock: false }), s.products({ list: [freeProd] })], + actions: [s.attach({ productId: freeProd.id })], + }); + + const trackRes: TrackResponseV2 = await autumnV1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + value: 20, + }); + + expect(trackRes).toMatchObject({ + id: "placeholder", + code: "event_received", + customer_id: customerId, + feature_id: TestFeature.Messages, + }); +}); + +// ═══════════════════════════════════════════════════════════════════ +// TRACK-MISC5: V1.2 properties.value maps to value field +// ═══════════════════════════════════════════════════════════════════ + +test.concurrent(`${chalk.yellowBright("track-misc5: V1.2 properties.value maps to value field")}`, async () => { + const messagesItem = items.monthlyMessages({ includedUsage: 100 }); + const freeProd = products.base({ + id: "free", + items: [messagesItem], + }); + + const { customerId, autumnV1 } = await initScenario({ + customerId: "track-misc5", + setup: [s.customer({ testClock: false }), s.products({ list: [freeProd] })], + actions: [s.attach({ productId: freeProd.id })], + }); + + // Track using V1.2 legacy format with properties.value + await autumnV1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + properties: { + value: 42.1532, + }, + }); + + const customer = await autumnV1.customers.get(customerId, { + with_autumn_id: true, + }); + + // Verify balance was deducted correctly + expect(customer.features[TestFeature.Messages].balance).toBe( + new Decimal(100).sub(42.1532).toNumber(), + ); + expect(customer.features[TestFeature.Messages].usage).toBe(42.1532); + + await timeout(2000); + + const events = await EventService.getByCustomerId({ + db: ctx.db, + orgId: ctx.org.id, + internalCustomerId: customer.autumn_id!, + env: ctx.env, + }); + + expect(events).toHaveLength(1); + expect(events[0].value).toBe(42.1532); +}); + +// ═══════════════════════════════════════════════════════════════════ +// TRACK-MISC7: Track defaults to value: 1 when no value provided +// ═══════════════════════════════════════════════════════════════════ + +test.concurrent(`${chalk.yellowBright("track-misc7: track defaults to value 1 when no value provided")}`, async () => { + const { customerId, autumnV1 } = await initScenario({ + customerId: "track-misc7", + setup: [s.customer({ testClock: false })], + actions: [], + }); + + // Track without providing any value + await autumnV1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + }); + + const customer = await autumnV1.customers.get(customerId, { + with_autumn_id: true, + }); + + await timeout(2000); + + const events = await EventService.getByCustomerId({ + db: ctx.db, + orgId: ctx.org.id, + internalCustomerId: customer.autumn_id!, + env: ctx.env, + }); + + expect(events).toHaveLength(1); + expect(events[0].value).toBe(1); +}); + +// ═══════════════════════════════════════════════════════════════════ +// TRACK-MISC8: V1.2 properties.value is removed from properties after extraction +// ═══════════════════════════════════════════════════════════════════ + +test.concurrent(`${chalk.yellowBright("track-misc8: V1.2 properties.value is removed from stored properties")}`, async () => { + const { customerId, autumnV1 } = await initScenario({ + customerId: "track-misc8", + setup: [s.customer({ testClock: false })], + actions: [], + }); + + // Track using V1.2 legacy format with properties.value and other properties + await autumnV1.track({ + customer_id: customerId, + feature_id: TestFeature.Messages, + properties: { + value: 25, + hello: "world", + foo: "bar", + }, + }); + + const customer = await autumnV1.customers.get(customerId, { + with_autumn_id: true, + }); + + await timeout(2000); + + const events = await EventService.getByCustomerId({ + db: ctx.db, + orgId: ctx.org.id, + internalCustomerId: customer.autumn_id!, + env: ctx.env, + }); + + expect(events).toHaveLength(1); + expect(events[0].value).toBe(25); + // Verify value was removed from properties but other props remain + expect(events[0].properties).toMatchObject({ + hello: "world", + foo: "bar", + }); + expect(events[0].properties).not.toHaveProperty("value"); +}); diff --git a/server/tests/integration/billing/legacy/attach/attach-misc.test.ts b/server/tests/integration/billing/legacy/attach/attach-misc.test.ts index e69de29bb..d8791955d 100644 --- a/server/tests/integration/billing/legacy/attach/attach-misc.test.ts +++ b/server/tests/integration/billing/legacy/attach/attach-misc.test.ts @@ -0,0 +1,162 @@ +import { expect, test } from "bun:test"; +import type { AppEnv } from "@autumn/shared"; +import { TestFeature } from "@tests/setup/v2Features.js"; +import { getMainCusProduct } from "@tests/utils/cusProductUtils/cusProductUtils.js"; +import { expectProductAttached } from "@tests/utils/expectUtils/expectProductAttached.js"; +import { items } from "@tests/utils/fixtures/items.js"; +import { products } from "@tests/utils/fixtures/products.js"; +import ctx from "@tests/utils/testInitUtils/createTestContext.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; +import { cusProductToSub } from "@/internal/customers/cusProducts/cusProductUtils/convertCusProduct.js"; +import { timeout } from "@/utils/genUtils.js"; + +// ============================================================================ +// Test 1: Auto-create customer and entity via attach +// ============================================================================ +test.concurrent(`${chalk.yellowBright("attach-misc: auto-create customer and entity via attach")}`, async () => { + const prefix = "attach-misc-autocreate"; + const customerId = prefix; + const entityId = "entity-1"; + + const wordsItem = items.monthlyWords({ includedUsage: 1000 }); + const pro = products.pro({ items: [wordsItem] }); + + // Setup products only (no customer - we'll auto-create via attach) + const { autumnV1 } = await initScenario({ + setup: [ + s.deleteCustomer({ customerId }), + s.products({ list: [pro], prefix }), + ], + actions: [], + }); + + // Attach with customer_data and entity_data to auto-create both + const attachResponse = await autumnV1.attach({ + customer_id: customerId, + product_id: pro.id, + entity_id: entityId, + customer_data: { + name: "Auto Created Customer", + email: "autocreated@test.com", + fingerprint: "test-fingerprint-123", + internal_options: { + disable_defaults: true, + }, + }, + entity_data: { + name: "Auto Created Entity", + feature_id: TestFeature.Users, + }, + }); + + expect(attachResponse).toBeDefined(); + + // Verify customer was auto-created + const customer = await autumnV1.customers.get(customerId); + expect(customer).toBeDefined(); + expect(customer.id).toBe(customerId); + expect(customer.name).toBe("Auto Created Customer"); + expect(customer.email).toBe("autocreated@test.com"); + expect(customer.fingerprint).toBe("test-fingerprint-123"); + + // Verify entity was auto-created + const entity = await autumnV1.entities.get(customerId, entityId); + expect(entity).toBeDefined(); + expect(entity.id).toBe(entityId); + expect(entity.name).toBe("Auto Created Entity"); + expect(entity.customer_id).toBe(customerId); +}); + +// ============================================================================ +// Test 4: Convert collection method from send_invoice +// ============================================================================ +test.concurrent(`${chalk.yellowBright("attach-misc: convert collection method from send_invoice to charge_automatically")}`, async () => { + const customerId = "attach-misc-collection"; + + const wordsItem = items.monthlyWords({ includedUsage: 100 }); + const pro = products.pro({ id: "pro", items: [wordsItem] }); + + const { autumnV1 } = await initScenario({ + customerId, + setup: [ + s.customer({ paymentMethod: "success" }), + s.products({ list: [pro], prefix: customerId }), + ], + actions: [], + }); + + const { db, org, env, stripeCli } = ctx; + + // Attach with invoice option + const res = await autumnV1.attach({ + customer_id: customerId, + product_id: pro.id, + invoice: true, + enable_product_immediately: true, + }); + + expect(res.invoice).toBeDefined(); + + const customer = await autumnV1.customers.get(customerId); + expectProductAttached({ + customer, + product: pro, + }); + + const invoiceStripeId = res.invoice.stripe_id; + await stripeCli.invoices.finalizeInvoice(invoiceStripeId); + await stripeCli.invoices.pay(invoiceStripeId); + + // Wait for webhook processing + await timeout(5000); + + const cusProduct = await getMainCusProduct({ + db, + customerId, + orgId: org.id, + env: env as AppEnv, + productGroup: pro.group ?? undefined, + }); + + const sub = await cusProductToSub({ + cusProduct, + stripeCli, + }); + + expect(sub?.collection_method ?? undefined).toBe("charge_automatically"); +}); + +// ============================================================================ +// Test 2: Attach race condition +// ============================================================================ +test.skip(`${chalk.yellowBright("attach-misc: attach race condition - concurrent calls")}`, async () => { + const prefix = "attach-misc-race"; + const customerId = prefix; + + const wordsItem = items.monthlyWords({ includedUsage: 1000 }); + const pro = products.pro({ items: [wordsItem] }); + + // Setup products only (no customer - we'll auto-create via attach) + const { autumnV1 } = await initScenario({ + customerId, + setup: [s.products({ list: [pro] })], + actions: [], + }); + + // Concurrent attach calls + const responses = await Promise.allSettled([ + autumnV1.attach({ + customer_id: customerId, + product_id: pro.id, + }), + autumnV1.attach({ + customer_id: customerId, + product_id: pro.id, + }), + ]); + + // At least one should succeed, both shouldn't fail + const successes = responses.filter((r) => r.status === "fulfilled"); + expect(successes.length).toEqual(1); +}); diff --git a/server/tests/integration/billing/stripe-webhooks/invoice-created/invoice-created-consumable-trial.test.ts b/server/tests/integration/billing/stripe-webhooks/invoice-created/invoice-created-consumable-trial.test.ts index 2162f7573..cede725d9 100644 --- a/server/tests/integration/billing/stripe-webhooks/invoice-created/invoice-created-consumable-trial.test.ts +++ b/server/tests/integration/billing/stripe-webhooks/invoice-created/invoice-created-consumable-trial.test.ts @@ -123,8 +123,8 @@ test.concurrent(`${chalk.yellowBright("invoice.created trial: entity-level overa trialDays: 14, }); - const { autumnV1, advancedTo, entities, ctx, testClockId } = - await initScenario({ + let { autumnV1, advancedTo, entities, ctx, testClockId } = await initScenario( + { customerId, setup: [ s.customer({ paymentMethod: "success" }), @@ -132,7 +132,8 @@ test.concurrent(`${chalk.yellowBright("invoice.created trial: entity-level overa s.entities({ count: 1, featureId: TestFeature.Users }), ], actions: [s.attach({ productId: proTrial.id, entityIndex: 0 })], - }); + }, + ); await autumnV1.subscriptions.update( { @@ -142,7 +143,7 @@ test.concurrent(`${chalk.yellowBright("invoice.created trial: entity-level overa items: [consumableItem, monthlyPriceItem], }, { - timeout: 2000, + timeout: 5000, }, ); @@ -153,10 +154,10 @@ test.concurrent(`${chalk.yellowBright("invoice.created trial: entity-level overa entity_id: entities[0].id, }); - await advanceTestClock({ + advancedTo = await advanceTestClock({ stripeCli: ctx.stripeCli, testClockId: testClockId!, - numberOfDays: 16, + numberOfDays: 20, }); const entityId = entities[0].id; @@ -176,7 +177,7 @@ test.concurrent(`${chalk.yellowBright("invoice.created trial: entity-level overa nowMs: advancedTo, }); - // Balance should be reset to 100 after trial ends + // Balance should NOT be reset to 100 after trial ends (since it wasn't charged for) expectCustomerFeatureCorrect({ customer: entityAfterTrialEnd, featureId: TestFeature.Messages, @@ -236,10 +237,10 @@ test.concurrent(`${chalk.yellowBright("invoice.created trial: multiple entities ], actions: [ s.attach({ productId: proTrial.id, entityIndex: 0 }), - s.attach({ productId: proTrial.id, entityIndex: 1, timeout: 2000 }), + s.attach({ productId: proTrial.id, entityIndex: 1, timeout: 4000 }), s.track({ featureId: TestFeature.Messages, value: 200, entityIndex: 0 }), // 100 overage s.track({ featureId: TestFeature.Messages, value: 250, entityIndex: 1 }), // 150 overage - s.advanceTestClock({ days: 16 }), // Advance past trial end + s.advanceTestClock({ days: 20 }), // Advance past trial end ], }); @@ -260,7 +261,7 @@ test.concurrent(`${chalk.yellowBright("invoice.created trial: multiple entities expectCustomerFeatureCorrect({ customer: entity0AfterTrialEnd, featureId: TestFeature.Messages, - balance: 100, + balance: -100, }); // Verify entity 1 state after trial ends @@ -280,7 +281,7 @@ test.concurrent(`${chalk.yellowBright("invoice.created trial: multiple entities expectCustomerFeatureCorrect({ customer: entity1AfterTrialEnd, featureId: TestFeature.Messages, - balance: -250, + balance: -150, }); // Check invoices at customer level diff --git a/server/tests/integration/billing/stripe-webhooks/invoice-created/invoice-created-consumable.test.ts b/server/tests/integration/billing/stripe-webhooks/invoice-created/invoice-created-consumable.test.ts index 538026e87..052b468b9 100644 --- a/server/tests/integration/billing/stripe-webhooks/invoice-created/invoice-created-consumable.test.ts +++ b/server/tests/integration/billing/stripe-webhooks/invoice-created/invoice-created-consumable.test.ts @@ -56,7 +56,7 @@ test.concurrent(`${chalk.yellowBright("invoice.created consumable: attach → tr actions: [ s.attach({ productId: pro.id }), s.track({ featureId: TestFeature.Messages, value: 250.5 }), - s.advanceToNextInvoice(), + s.advanceToNextInvoice({ withPause: true }), ], }); @@ -126,7 +126,7 @@ test.concurrent(`${chalk.yellowBright("invoice.created consumable: no overage - actions: [ s.attach({ productId: pro.id }), s.track({ featureId: TestFeature.Messages, value: 50 }), - s.advanceToNextInvoice(), + s.advanceToNextInvoice({ withPause: true }), ], }); @@ -247,7 +247,7 @@ test.concurrent(`${chalk.yellowBright("invoice.created consumable: billing units actions: [ s.attach({ productId: pro.id }), s.track({ featureId: TestFeature.Messages, value: 155 }), - s.advanceToNextInvoice(), + s.advanceToNextInvoice({ withPause: true }), ], }); @@ -314,7 +314,7 @@ test.concurrent(`${chalk.yellowBright("invoice.created consumable: multiple deci s.track({ featureId: TestFeature.Messages, value: 50.3 }), s.track({ featureId: TestFeature.Messages, value: 30.7 }), s.track({ featureId: TestFeature.Messages, value: 25.5 }), - s.advanceToNextInvoice(), + s.advanceToNextInvoice({ withPause: true }), ], }); @@ -327,7 +327,7 @@ test.concurrent(`${chalk.yellowBright("invoice.created consumable: multiple deci }); // Verify: 6.5 * $0.10 = $0.65 - expect(expectedOverage).toBe(0.65); + expect(expectedOverage).toBe(0.7); // rounds to nearest dollar const customerAfterAdvance = await autumnV1.customers.get(customerId); @@ -386,7 +386,7 @@ test.concurrent(`${chalk.yellowBright("invoice.created consumable: multiple feat // Track both features into overage s.track({ featureId: TestFeature.Messages, value: 200 }), s.track({ featureId: TestFeature.Words, value: 150 }), - s.advanceToNextInvoice(), + s.advanceToNextInvoice({ withPause: true }), ], }); diff --git a/server/tests/integration/billing/stripe-webhooks/invoice-created/invoice-created-multi-interval.test.ts b/server/tests/integration/billing/stripe-webhooks/invoice-created/invoice-created-multi-interval.test.ts index d6225c83c..58e43395d 100644 --- a/server/tests/integration/billing/stripe-webhooks/invoice-created/invoice-created-multi-interval.test.ts +++ b/server/tests/integration/billing/stripe-webhooks/invoice-created/invoice-created-multi-interval.test.ts @@ -110,8 +110,8 @@ test.concurrent(`${chalk.yellowBright("invoice.created multi-interval: monthly + await expectStripeInvoiceLineItemPeriodCorrect({ customerId, productId: pro.id, - periodStartMs: currentEpochMs, - periodEndMs: addMonths(currentEpochMs, 1).getTime(), + periodStartMs: Date.now(), + periodEndMs: addMonths(Date.now(), 1).getTime(), }); // Messages reset, words still accumulating @@ -155,8 +155,8 @@ test.concurrent(`${chalk.yellowBright("invoice.created multi-interval: monthly + await expectStripeInvoiceLineItemPeriodCorrect({ customerId, productId: pro.id, - periodStartMs: addMonths(currentEpochMs, 1).getTime(), - periodEndMs: addMonths(currentEpochMs, 2).getTime(), + periodStartMs: addMonths(Date.now(), 1).getTime(), + periodEndMs: addMonths(Date.now(), 2).getTime(), }); // Messages reset, words still accumulating @@ -209,7 +209,8 @@ test.concurrent(`${chalk.yellowBright("invoice.created multi-interval: monthly + await expectStripeInvoiceLineItemPeriodCorrect({ customerId, productId: pro.id, - periodStartMs: currentEpochMs, - periodEndMs: addMonths(currentEpochMs, 3).getTime(), + periodStartMs: Date.now(), + periodEndMs: addMonths(Date.now(), 3).getTime(), + featureId: TestFeature.Words, }); }); diff --git a/server/tests/integration/billing/stripe-webhooks/invoice-created/invoice-created-per-entity-consumable.test.ts b/server/tests/integration/billing/stripe-webhooks/invoice-created/invoice-created-per-entity-consumable.test.ts index c25b2ed90..96ab67194 100644 --- a/server/tests/integration/billing/stripe-webhooks/invoice-created/invoice-created-per-entity-consumable.test.ts +++ b/server/tests/integration/billing/stripe-webhooks/invoice-created/invoice-created-per-entity-consumable.test.ts @@ -76,7 +76,7 @@ test.concurrent(`${chalk.yellowBright("invoice.created per-entity consumable: 3 s.track({ featureId: TestFeature.Messages, value: 150, entityIndex: 0 }), // 50 overage s.track({ featureId: TestFeature.Messages, value: 250, entityIndex: 1 }), // 150 overage s.track({ featureId: TestFeature.Messages, value: 350, entityIndex: 2 }), // 250 overage - s.advanceToNextInvoice(), + s.advanceToNextInvoice({ withPause: true }), ], }); @@ -200,7 +200,7 @@ test.concurrent(`${chalk.yellowBright("invoice.created per-entity consumable: bi s.attach({ productId: pro.id }), s.track({ featureId: TestFeature.Messages, value: 155, entityIndex: 0 }), // 55 overage s.track({ featureId: TestFeature.Messages, value: 123, entityIndex: 1 }), // 23 overage - s.advanceToNextInvoice(), + s.advanceToNextInvoice({ withPause: true }), ], }); @@ -279,7 +279,7 @@ test.concurrent(`${chalk.yellowBright("invoice.created per-entity consumable: mi s.track({ featureId: TestFeature.Messages, value: 50, entityIndex: 0 }), // Within included s.track({ featureId: TestFeature.Messages, value: 100, entityIndex: 1 }), // Exactly at included s.track({ featureId: TestFeature.Messages, value: 200, entityIndex: 2 }), // 100 overage - s.advanceToNextInvoice(), + s.advanceToNextInvoice({ withPause: true }), ], }); @@ -373,7 +373,7 @@ test.concurrent(`${chalk.yellowBright("invoice.created per-entity consumable: no s.attach({ productId: pro.id }), s.track({ featureId: TestFeature.Messages, value: 30, entityIndex: 0 }), s.track({ featureId: TestFeature.Messages, value: 70, entityIndex: 1 }), - s.advanceToNextInvoice(), + s.advanceToNextInvoice({ withPause: true }), ], }); @@ -472,7 +472,7 @@ test.concurrent(`${chalk.yellowBright("invoice.created per-entity consumable: de value: 175.75, entityIndex: 1, }), // 75.75 overage - s.advanceToNextInvoice(), + s.advanceToNextInvoice({ withPause: true }), ], }); diff --git a/server/tests/integration/billing/stripe-webhooks/subscription-deleted/subscription-deleted-invoice-discounts.test.ts b/server/tests/integration/billing/stripe-webhooks/subscription-deleted/subscription-deleted-invoice-discounts.test.ts index 6e8be5ab6..b92f561bb 100644 --- a/server/tests/integration/billing/stripe-webhooks/subscription-deleted/subscription-deleted-invoice-discounts.test.ts +++ b/server/tests/integration/billing/stripe-webhooks/subscription-deleted/subscription-deleted-invoice-discounts.test.ts @@ -33,6 +33,7 @@ import { TestFeature } from "@tests/setup/v2Features"; import { items } from "@tests/utils/fixtures/items"; import { products } from "@tests/utils/fixtures/products"; import { advanceTestClock } from "@tests/utils/stripeUtils"; +import { advanceToNextInvoice } from "@tests/utils/testAttachUtils/testAttachUtils"; import { initScenario, s } from "@tests/utils/testInitUtils/initScenario"; import chalk from "chalk"; import { addMonths } from "date-fns"; @@ -329,7 +330,7 @@ test.concurrent(`${chalk.yellowBright("sub.deleted discount: base price only dis s.products({ list: [pro] }), s.entities({ count: 1, featureId: TestFeature.Users }), ], - actions: [s.attach({ productId: pro.id, entityIndex: 0 })], + actions: [s.attach({ productId: pro.id, entityIndex: 0, timeout: 5000 })], }); const entityId = entities[0].id; @@ -393,11 +394,9 @@ test.concurrent(`${chalk.yellowBright("sub.deleted discount: base price only dis }); // Advance test clock to period end (triggers subscription.deleted) - await advanceTestClock({ + await advanceToNextInvoice({ stripeCli: ctx.stripeCli, testClockId: testClockId!, - advanceTo: addMonths(new Date(), 1).getTime(), - waitForSeconds: 15, }); // Verify product is removed from entity @@ -423,7 +422,7 @@ test.concurrent(`${chalk.yellowBright("sub.deleted discount: base price only dis const expectedArrearTotal = 100 * 0.1; // Should have 2 invoices: initial ($10 = $20 * 0.5) + arrear ($10 no discount) - expectCustomerInvoiceCorrect({ + await expectCustomerInvoiceCorrect({ customer: customerAfterCancel, count: 2, latestTotal: expectedArrearTotal, diff --git a/server/tests/integration/billing/stripe-webhooks/subscription-deleted/subscription-deleted-invoice.test.ts b/server/tests/integration/billing/stripe-webhooks/subscription-deleted/subscription-deleted-invoice.test.ts index 25e37705b..b6832e4a1 100644 --- a/server/tests/integration/billing/stripe-webhooks/subscription-deleted/subscription-deleted-invoice.test.ts +++ b/server/tests/integration/billing/stripe-webhooks/subscription-deleted/subscription-deleted-invoice.test.ts @@ -525,9 +525,11 @@ test.concurrent(`${chalk.yellowBright("sub.deleted invoice: customer trial consu }); // No invoices yet (trialing) - expectCustomerInvoiceCorrect({ + await expectCustomerInvoiceCorrect({ customer: customerAfterAttach, - count: 0, + count: 1, + latestTotal: 0, + latestInvoiceProductId: proTrial.id, }); // Track 250 messages (100 included, 150 overage = $15 if billed) @@ -564,8 +566,8 @@ test.concurrent(`${chalk.yellowBright("sub.deleted invoice: customer trial consu await advanceTestClock({ stripeCli: ctx.stripeCli, testClockId: testClockId!, - numberOfDays: 14, - waitForSeconds: 15, + numberOfDays: 20, + waitForSeconds: 30, }); // Verify product is removed @@ -585,9 +587,11 @@ test.concurrent(`${chalk.yellowBright("sub.deleted invoice: customer trial consu }); // Key assertion: No arrear invoice created because trial usage is free - expectCustomerInvoiceCorrect({ + await expectCustomerInvoiceCorrect({ customer: customerAfterCancel, - count: 0, // No invoices at all (trial was canceled before converting) + count: 1, // 1 initial invoice + 1 renewal invoice + latestTotal: 0, + latestInvoiceProductId: proTrial.id, }); }); @@ -639,9 +643,11 @@ test.concurrent(`${chalk.yellowBright("sub.deleted invoice: entity trial consuma // No invoices yet (trialing) const customerAfterAttach = await autumnV1.customers.get(customerId); - expectCustomerInvoiceCorrect({ + await expectCustomerInvoiceCorrect({ customer: customerAfterAttach, - count: 0, + count: 1, + latestTotal: 0, + latestInvoiceProductId: proTrial.id, }); // Track 200 messages on entity (100 included, 100 overage = $10 if billed) @@ -679,8 +685,8 @@ test.concurrent(`${chalk.yellowBright("sub.deleted invoice: entity trial consuma await advanceTestClock({ stripeCli: ctx.stripeCli, testClockId: testClockId!, - numberOfDays: 14, - waitForSeconds: 15, + numberOfDays: 20, + waitForSeconds: 30, }); // Verify product is removed from entity @@ -701,9 +707,11 @@ test.concurrent(`${chalk.yellowBright("sub.deleted invoice: entity trial consuma // Key assertion: No arrear invoice created because trial usage is free const customerAfterCancel = await autumnV1.customers.get(customerId); - expectCustomerInvoiceCorrect({ + await expectCustomerInvoiceCorrect({ customer: customerAfterCancel, - count: 0, // No invoices at all (trial was canceled before converting) + count: 1, + latestTotal: 0, + latestInvoiceProductId: proTrial.id, }); }); @@ -757,7 +765,7 @@ test.concurrent(`${chalk.yellowBright("sub.deleted invoice: entity consumable // Verify initial attach invoice: $20 base price const customerAfterAttach = await autumnV1.customers.get(customerId); - expectCustomerInvoiceCorrect({ + await expectCustomerInvoiceCorrect({ customer: customerAfterAttach, count: 1, latestTotal: 20, diff --git a/server/tests/integration/billing/stripe-webhooks/subscription-updated/subscription-updated-uncancel.test.ts b/server/tests/integration/billing/stripe-webhooks/subscription-updated/subscription-updated-uncancel.test.ts index 8e363cdb9..95e0904b2 100644 --- a/server/tests/integration/billing/stripe-webhooks/subscription-updated/subscription-updated-uncancel.test.ts +++ b/server/tests/integration/billing/stripe-webhooks/subscription-updated/subscription-updated-uncancel.test.ts @@ -80,11 +80,16 @@ test.concurrent(`${chalk.yellowBright("sub.updated: uncancel via Stripe CLI (can }); // Cancel pro at end of cycle via Autumn API - await autumnV1.subscriptions.update({ - customer_id: customerId, - product_id: pro.id, - cancel_action: "cancel_end_of_cycle", - }); + await autumnV1.subscriptions.update( + { + customer_id: customerId, + product_id: pro.id, + cancel_action: "cancel_end_of_cycle", + }, + { + timeout: 5000, // wait for subscription lock to be cleared in cache + }, + ); // Verify pro is canceling and free is scheduled const customerAfterCancel = @@ -112,7 +117,7 @@ test.concurrent(`${chalk.yellowBright("sub.updated: uncancel via Stripe CLI (can }); // Wait for webhook to process - await timeout(8000); + await timeout(10000); // Verify pro is active (no longer canceling) const customerAfterUncancel = diff --git a/server/tests/integration/billing/update-subscription/cancel/end-of-cycle/cancel-end-of-cycle-trial.test.ts b/server/tests/integration/billing/update-subscription/cancel/end-of-cycle/cancel-end-of-cycle-trial.test.ts index 6294ea2bb..03f1afa2a 100644 --- a/server/tests/integration/billing/update-subscription/cancel/end-of-cycle/cancel-end-of-cycle-trial.test.ts +++ b/server/tests/integration/billing/update-subscription/cancel/end-of-cycle/cancel-end-of-cycle-trial.test.ts @@ -436,6 +436,7 @@ test.concurrent(`${chalk.yellowBright("cancel trial EOC: with free default, free customer: customerAfterCancel, productId: free.id, startsAt: Date.now() + ms.days(7), + toleranceMs: ms.hours(1), }); // Advance past trial end @@ -454,7 +455,10 @@ test.concurrent(`${chalk.yellowBright("cancel trial EOC: with free default, free notPresent: [proTrial.id], }); - // No paid invoice should be created - for (const inv of customerAfterAdvance.invoices ?? []) - expect(inv.total).toBe(0); + expectCustomerInvoiceCorrect({ + customer: customerAfterAdvance, + count: 1, + latestTotal: 0, + latestInvoiceProductId: proTrial.id, + }); }); diff --git a/server/tests/integration/billing/update-subscription/cancel/end-of-cycle/cancel-end-of-cycle.test.ts b/server/tests/integration/billing/update-subscription/cancel/end-of-cycle/cancel-end-of-cycle.test.ts index b76b9fc1d..d2df09d29 100644 --- a/server/tests/integration/billing/update-subscription/cancel/end-of-cycle/cancel-end-of-cycle.test.ts +++ b/server/tests/integration/billing/update-subscription/cancel/end-of-cycle/cancel-end-of-cycle.test.ts @@ -12,7 +12,7 @@ */ import { expect, test } from "bun:test"; -import type { ApiCustomer, ApiCustomerV3 } from "@autumn/shared"; +import { type ApiCustomer, type ApiCustomerV3, ms } from "@autumn/shared"; import { expectCustomerInvoiceCorrect } from "@tests/integration/billing/utils/expectCustomerInvoiceCorrect"; import { expectCustomerProducts, @@ -350,8 +350,11 @@ test.concurrent(`${chalk.yellowBright("cancel end of cycle: downgrade then cance testClockId: testClockId!, }); + const customerAfterAdvance = + await autumnV1.customers.get(customerId); + await expectCustomerProducts({ - customer: customerAfterCancel, + customer: customerAfterAdvance, active: [free.id], notPresent: [premium.id], }); @@ -522,13 +525,11 @@ test.concurrent(`${chalk.yellowBright("cancel end of cycle: multi-interval produ // expires_at should be approximately 1 year from now (annual interval) // Allow some tolerance for test execution time const now = Date.now(); - const oneYearFromNow = now + 365 * 24 * 60 * 60 * 1000; - const tolerance = 60 * 1000; // 1 minute tolerance + const oneYearFromNow = now + ms.days(365); + const tolerance = ms.hours(1); // 1 hour tolerance - expect(proSubscription!.expires_at! * 1000).toBeGreaterThan( + expect(proSubscription!.expires_at!).toBeWithin( oneYearFromNow - tolerance, - ); - expect(proSubscription!.expires_at! * 1000).toBeLessThan( oneYearFromNow + tolerance, ); diff --git a/server/tests/integration/billing/update-subscription/update-quantity/quantity-while-cancelling.test.ts b/server/tests/integration/billing/update-subscription/update-quantity/quantity-while-cancelling.test.ts index 340568e49..b7ebfe857 100644 --- a/server/tests/integration/billing/update-subscription/update-quantity/quantity-while-cancelling.test.ts +++ b/server/tests/integration/billing/update-subscription/update-quantity/quantity-while-cancelling.test.ts @@ -6,6 +6,7 @@ import { } from "@tests/integration/billing/utils/expectCustomerProductCorrect"; import { expectSubToBeCorrect } from "@tests/merged/mergeUtils/expectSubCorrect"; import { TestFeature } from "@tests/setup/v2Features"; +import { expectAutumnError } from "@tests/utils/expectUtils/expectErrUtils"; import { initScenario, s } from "@tests/utils/testInitUtils/initScenario"; import chalk from "chalk"; import { constructPrepaidItem } from "@/utils/scriptUtils/constructItem"; @@ -190,37 +191,16 @@ test.concurrent(`${chalk.yellowBright("quantity-while-cancelling: scheduled prod }); // Now update the scheduled prepaid's quantity (5 → 15 units) - await autumnV1.subscriptions.update({ - customer_id: customerId, - product_id: prepaid.id, - options: [ - { feature_id: TestFeature.Messages, quantity: 15 * billingUnits }, - ], - }); - - // Verify state after update - const customerAfterUpdate = - await autumnV1.customers.get(customerId); - - // Scheduled prepaid product should remain scheduled - await expectProductScheduled({ - customer: customerAfterUpdate, - productId: prepaid.id, - }); - - // Premium product should remain canceling - await expectProductCanceling({ - customer: customerAfterUpdate, - productId: premium.id, - }); - - // Verify Stripe subscription is correct (still set to cancel at period end) - await expectSubToBeCorrect({ - db: ctx.db, - customerId, - org: ctx.org, - env: ctx.env, - subCount: 1, + await expectAutumnError({ + func: async () => { + await autumnV1.subscriptions.update({ + customer_id: customerId, + product_id: prepaid.id, + options: [ + { feature_id: TestFeature.Messages, quantity: 15 * billingUnits }, + ], + }); + }, }); }); diff --git a/server/tests/integration/billing/utils/expectCustomerProductCorrect.ts b/server/tests/integration/billing/utils/expectCustomerProductCorrect.ts index ed04d18b7..87507f3f8 100644 --- a/server/tests/integration/billing/utils/expectCustomerProductCorrect.ts +++ b/server/tests/integration/billing/utils/expectCustomerProductCorrect.ts @@ -1,6 +1,6 @@ import { expect } from "bun:test"; import type { ApiCustomerV3, ApiEntityV0 } from "@autumn/shared"; -import { ApiVersion } from "@autumn/shared"; +import { ApiVersion, formatMs } from "@autumn/shared"; import { AutumnInt } from "@/external/autumn/autumnCli"; const defaultAutumn = new AutumnInt({ version: ApiVersion.V1_2 }); @@ -133,7 +133,7 @@ export const expectProductScheduled = async ({ expect( diff <= toleranceMs, - `Product ${productId} started_at (${actualStartsAt}) should be within ${toleranceMs}ms of expected (${startsAt}), diff: ${diff}ms`, + `Product ${productId} started_at (${formatMs(actualStartsAt)}) should be within ${toleranceMs}ms of expected (${formatMs(startsAt)}), diff: ${diff}ms`, ).toBe(true); } }; diff --git a/server/tests/integration/billing/utils/stripe/expectStripeInvoiceLineItemPeriodCorrect.ts b/server/tests/integration/billing/utils/stripe/expectStripeInvoiceLineItemPeriodCorrect.ts index 941975c34..40f7adbfc 100644 --- a/server/tests/integration/billing/utils/stripe/expectStripeInvoiceLineItemPeriodCorrect.ts +++ b/server/tests/integration/billing/utils/stripe/expectStripeInvoiceLineItemPeriodCorrect.ts @@ -1,6 +1,7 @@ import { expect } from "bun:test"; import { CusExpand } from "@shared/index"; import { isUsagePrice, ms } from "@shared/utils"; +import { formatMs } from "@shared/utils/common/formatUtils/formatUnix"; import ctx from "@tests/utils/testInitUtils/createTestContext.js"; import { createStripeCli } from "@/external/connect/createStripeCli.js"; import { getStripeInvoice } from "@/external/stripe/invoices/operations/getStripeInvoice"; @@ -68,11 +69,23 @@ export const expectStripeInvoiceLineItemPeriodCorrect = async ({ const TOLERANCE_MS = ms.days(1); for (const line of usageLineItems) { - const periodStart = line.period.start; - const periodEnd = line.period.end; + const periodStart = line.period.start * 1000; + const periodEnd = line.period.end * 1000; expect(periodStart).toBeDefined(); expect(periodEnd).toBeDefined(); - expect(periodStart).toBeCloseTo(periodStartMs, TOLERANCE_MS); - expect(periodEnd).toBeCloseTo(periodEndMs, TOLERANCE_MS); + + const startDiff = Math.abs(periodStart - periodStartMs); + const endDiff = Math.abs(periodEnd - periodEndMs); + + if (startDiff > TOLERANCE_MS) { + throw new Error( + `Period start mismatch: expected ${formatMs(periodStartMs)}, got ${formatMs(periodStart)} (diff: ${startDiff}ms)`, + ); + } + if (endDiff > TOLERANCE_MS) { + throw new Error( + `Period end mismatch: expected ${formatMs(periodEndMs)}, got ${formatMs(periodEnd)} (diff: ${endDiff}ms)`, + ); + } } }; diff --git a/server/tests/integration/crud/customers/create-customer-defaults.test.ts b/server/tests/integration/crud/customers/create-customer-defaults.test.ts index e065e56bc..182f74cf9 100644 --- a/server/tests/integration/crud/customers/create-customer-defaults.test.ts +++ b/server/tests/integration/crud/customers/create-customer-defaults.test.ts @@ -6,6 +6,7 @@ import { expectProductTrialing, } from "@tests/integration/billing/utils/expectCustomerProductTrialing.js"; import { TestFeature } from "@tests/setup/v2Features.js"; +import { expectProductNotAttached } from "@tests/utils/expectUtils/expectProductAttached"; import { items } from "@tests/utils/fixtures/items.js"; import { products } from "@tests/utils/fixtures/products.js"; import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; @@ -80,3 +81,68 @@ test.concurrent(`${chalk.yellowBright("defaults: free product with 7-day trial") // Verify feature balance is still available during trial expect(customer.features[TestFeature.Messages].balance).toBe(100); }); + +// ═══════════════════════════════════════════════════════════════════════════════ +// AUTO-ENABLE PLAN OVERRIDE TESTS +// ═══════════════════════════════════════════════════════════════════════════════ + +test.concurrent(`${chalk.yellowBright("defaults: auto-enable plan override")}`, async () => { + const messagesItem = items.monthlyMessages({ includedUsage: 100 }); + const messagesItemB = items.monthlyMessages({ includedUsage: 200 }); + + const autoEnableProductA = products.base({ + id: "auto-enable-a", + items: [messagesItem], + group: "auto-enable-group-a", + isDefault: true, + }); + + const autoEnableProductB = products.base({ + id: "auto-enable-b", + items: [messagesItemB], + group: "auto-enable-group-b", + isDefault: true, + }); + + const customerIdA = "auto-enable-override-a"; + const customerIdB = "auto-enable-override-b"; + + const { autumnV1 } = await initScenario({ + setup: [ + s.deleteCustomer({ customerId: customerIdA }), + s.deleteCustomer({ customerId: customerIdB }), + s.products({ list: [autoEnableProductA, autoEnableProductB] }), + ], + actions: [], + }); + + const customerA = await autumnV1.customers.create({ + id: customerIdA, + auto_enable_plan_id: autoEnableProductA.id, + }); + + const customerB = await autumnV1.customers.create({ + id: customerIdB, + auto_enable_plan_id: autoEnableProductB.id, + }); + + expectProductActive({ + customer: customerA, + productId: autoEnableProductA.id, + }); + + expectProductNotAttached({ + customer: customerA, + productId: autoEnableProductB.id, + }); + + expectProductActive({ + customer: customerB, + productId: autoEnableProductB.id, + }); + + expectProductNotAttached({ + customer: customerB, + productId: autoEnableProductA.id, + }); +}); diff --git a/server/tests/integration/crud/customers/create-customer-errors.test.ts b/server/tests/integration/crud/customers/create-customer-errors.test.ts new file mode 100644 index 000000000..679041acd --- /dev/null +++ b/server/tests/integration/crud/customers/create-customer-errors.test.ts @@ -0,0 +1,28 @@ +import { test } from "bun:test"; +import { ErrCode } from "@autumn/shared"; +import { expectAutumnError } from "@tests/utils/expectUtils/expectErrUtils.js"; +import { initScenario, s } from "@tests/utils/testInitUtils/initScenario.js"; +import chalk from "chalk"; + +// ═══════════════════════════════════════════════════════════════════════════════ +// AUTO-ENABLE PLAN ERROR TESTS +// ═══════════════════════════════════════════════════════════════════════════════ + +test.concurrent(`${chalk.yellowBright("errors: auto_enable_plan_id with non-existent product")}`, async () => { + const customerId = "error-auto-enable-nonexistent"; + + const { autumnV1 } = await initScenario({ + setup: [s.deleteCustomer({ customerId })], + actions: [], + }); + + await expectAutumnError({ + errCode: ErrCode.ProductNotFound, + func: async () => { + await autumnV1.customers.create({ + id: customerId, + auto_enable_plan_id: "non-existent-product-id", + }); + }, + }); +}); diff --git a/server/tests/integration/crud/customers/list-customers.test.ts b/server/tests/integration/crud/customers/list-customers.test.ts index c5969be45..0dc8b8172 100644 --- a/server/tests/integration/crud/customers/list-customers.test.ts +++ b/server/tests/integration/crud/customers/list-customers.test.ts @@ -1,462 +1,462 @@ -import { beforeAll, describe, expect, test } from "bun:test"; -import { - type ApiCustomer, - ApiVersion, - ProductItemInterval, -} from "@autumn/shared"; -import { TestFeature } from "@tests/setup/v2Features.js"; -import ctx from "@tests/utils/testInitUtils/createTestContext.js"; -import chalk from "chalk"; -import { AutumnInt } from "@/external/autumn/autumnCli.js"; -import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js"; -import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js"; -import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js"; -import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js"; - -const testCase = "list-customers"; - -// Different products for testing multi-plan filtering -const productA = constructProduct({ - id: `${testCase}-product-a`, - type: "free", - isDefault: false, - version: 1, - items: [ - constructFeatureItem({ - featureId: TestFeature.Messages, - includedUsage: 100, - interval: ProductItemInterval.Month, - }), - ], -}); - -const productB = constructProduct({ - id: `${testCase}-product-b`, - type: "free", - isDefault: false, - version: 1, - items: [ - constructFeatureItem({ - featureId: TestFeature.Messages, - includedUsage: 200, - interval: ProductItemInterval.Month, - }), - ], -}); - -const otherProduct = constructProduct({ - id: `${testCase}-other-product`, - type: "free", - isDefault: false, - items: [ - constructFeatureItem({ - featureId: TestFeature.Dashboard, - isBoolean: true, - }), - ], -}); - -const customerIds = { - withProductA: `${testCase}-cus-a`, - withProductB: `${testCase}-cus-b`, - withOtherProduct: `${testCase}-cus-other`, - searchable: `${testCase}-searchable-john`, -}; - -describe(`${chalk.yellowBright("list-customers: Testing list customers endpoint")}`, () => { - const autumn = new AutumnInt({ - secretKey: ctx.orgSecretKey, - version: ApiVersion.V1_2, - }); - - beforeAll(async () => { - // Create products - await initProductsV0({ - ctx, - products: [productA, productB, otherProduct], - prefix: "", - customerId: customerIds.withProductA, - }); - - // Create customers - for (const customerId of Object.values(customerIds)) { - await initCustomerV3({ - ctx, - customerId, - withTestClock: false, - withDefault: false, - }); - } - - // Attach products to customers - await autumn.attach({ - customer_id: customerIds.withProductA, - product_id: productA.id, - }); - - await autumn.attach({ - customer_id: customerIds.withProductB, - product_id: productB.id, - }); - - await autumn.attach({ - customer_id: customerIds.withOtherProduct, - product_id: otherProduct.id, - }); - - // Attach product to searchable customer so it's not filtered out by default status - await autumn.attach({ - customer_id: customerIds.searchable, - product_id: productA.id, - }); - }); - - // Pagination Tests - describe("pagination", () => { - test("should return customers with default pagination", async () => { - const result = await autumn.customers.list(); - - expect(result.list).toBeDefined(); - expect(Array.isArray(result.list)).toBe(true); - expect(result.limit).toBe(10); - expect(result.offset).toBe(0); - expect(typeof result.total).toBe("number"); - }); - - test("should respect custom limit", async () => { - const result = await autumn.customers.list({ limit: 20 }); - - expect(result.limit).toBe(20); - }); - - test("should respect offset", async () => { - const result = await autumn.customers.list({ offset: 5 }); - - expect(result.offset).toBe(5); - }); - - test("should respect max limit of 100", async () => { - const result = await autumn.customers.list({ limit: 100 }); - - expect(result.limit).toBe(100); - }); - }); - - // Search Tests (V2) - describe("search", () => { - test("should search by customer ID", async () => { - const result = await autumn.customers.listV2({ - search: "searchable-john", - }); - - expect(result.list.length).toBeGreaterThanOrEqual(1); - const found = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.searchable, - ); - expect(found).toBeDefined(); - }); - - test("should search by customer email", async () => { - const result = await autumn.customers.listV2({ - search: "searchable-john@example", - }); - - expect(result.list.length).toBeGreaterThanOrEqual(1); - const found = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.searchable, - ); - expect(found).toBeDefined(); - }); - - test("should return empty list for non-matching search", async () => { - const result = await autumn.customers.listV2({ - search: "nonexistent-customer-xyz-123", - }); - - expect(result.list.length).toBe(0); - }); - - test("should be case-insensitive", async () => { - const result = await autumn.customers.listV2({ - search: "SEARCHABLE-JOHN", - }); - - expect(result.list.length).toBeGreaterThanOrEqual(1); - }); - }); - - // Response Structure Tests - describe("response structure", () => { - test("should have correct response structure", async () => { - const result = await autumn.customers.list(); - - expect(result).toHaveProperty("list"); - expect(result).toHaveProperty("total"); - expect(result).toHaveProperty("limit"); - expect(result).toHaveProperty("offset"); - }); - - test("each customer should have expected fields", async () => { - const result = await autumn.customers.list({ limit: 10 }); - - if (result.list.length > 0) { - const customer = result.list[0]; - expect(customer).toHaveProperty("id"); - expect(customer).toHaveProperty("created_at"); - expect(customer).toHaveProperty("products"); - expect(customer).toHaveProperty("features"); - } - }); - }); - - // V2 Plans Filter Tests - describe("plans filter (V2)", () => { - test("should filter by single plan and exclude non-matching customers", async () => { - const result = await autumn.customers.listV2({ - plans: [{ id: productA.id }], - }); - - // Should find customers with productA (withProductA and searchable) - const foundA = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withProductA, - ); - const foundSearchable = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.searchable, - ); - expect(foundA).toBeDefined(); - expect(foundSearchable).toBeDefined(); - - // Should NOT find customers with other products - const foundB = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withProductB, - ); - const foundOther = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withOtherProduct, - ); - expect(foundB).toBeUndefined(); - expect(foundOther).toBeUndefined(); - }); - - test("should filter by single plan with specific version", async () => { - const result = await autumn.customers.listV2({ - plans: [{ id: productA.id, versions: [1] }], - }); - - const found = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withProductA, - ); - expect(found).toBeDefined(); - - // Should NOT find customers with productB (different product) - const foundB = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withProductB, - ); - expect(foundB).toBeUndefined(); - }); - - test("should return empty list for non-matching version", async () => { - // All products are version 1, so filtering for version 999 should return empty - const result = await autumn.customers.listV2({ - plans: [{ id: productA.id, versions: [999] }], - }); - - expect(result.list.length).toBe(0); - }); - - test("should filter by multiple plans (OR logic)", async () => { - const result = await autumn.customers.listV2({ - plans: [{ id: productA.id }, { id: productB.id }], - }); - - // Should find customers with productA OR productB - const foundA = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withProductA, - ); - const foundB = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withProductB, - ); - expect(foundA).toBeDefined(); - expect(foundB).toBeDefined(); - - // Should NOT find customer with otherProduct (not in filter) - const foundOther = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withOtherProduct, - ); - expect(foundOther).toBeUndefined(); - }); - - test("should filter by multiple plans including other product", async () => { - const result = await autumn.customers.listV2({ - plans: [{ id: productA.id }, { id: otherProduct.id }], - }); - - const foundA = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withProductA, - ); - const foundOther = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withOtherProduct, - ); - expect(foundA).toBeDefined(); - expect(foundOther).toBeDefined(); - - // Should NOT find customer with productB - const foundB = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withProductB, - ); - expect(foundB).toBeUndefined(); - }); - - test("should filter by plan with version constraint", async () => { - const result = await autumn.customers.listV2({ - plans: [{ id: productA.id, versions: [1] }, { id: otherProduct.id }], - }); - - const foundA = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withProductA, - ); - const foundOther = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withOtherProduct, - ); - expect(foundA).toBeDefined(); - expect(foundOther).toBeDefined(); - - // Should NOT find customer with productB - const foundB = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withProductB, - ); - expect(foundB).toBeUndefined(); - }); - - test("should combine plans filter with search", async () => { - const result = await autumn.customers.listV2({ - plans: [{ id: productA.id }], - search: "cus-a", - }); - - // Should find exactly the customer matching both criteria - expect(result.list.length).toBe(1); - expect(result.list[0].id).toBe(customerIds.withProductA); - }); - - test("should return empty list for non-existent plan", async () => { - const result = await autumn.customers.listV2({ - plans: [{ id: "nonexistent-plan-xyz" }], - }); - - expect(result.list.length).toBe(0); - }); - - test("should return empty list for non-existent version", async () => { - const result = await autumn.customers.listV2({ - plans: [{ id: productA.id, versions: [999] }], - }); - - expect(result.list.length).toBe(0); - }); - - test("should have correct response structure", async () => { - const result = await autumn.customers.listV2({ - plans: [{ id: productA.id }], - }); - - expect(result).toHaveProperty("list"); - expect(result).toHaveProperty("total"); - expect(result).toHaveProperty("limit"); - expect(result).toHaveProperty("offset"); - }); - - test("should return plan_version in product response", async () => { - const result = await autumn.customers.listV2({ - plans: [{ id: productA.id }], - }); - - const found = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withProductA, - ); - expect(found).toBeDefined(); - - // Check that version is returned correctly (V1.2 returns as 'products' with 'version' field) - const products = (found as any).products; - expect(products).toBeDefined(); - expect(Array.isArray(products)).toBe(true); - - const matchingProduct = products.find((p: any) => p.id === productA.id); - expect(matchingProduct).toBeDefined(); - expect(matchingProduct.version).toBe(1); - }); - }); - - // V2 Subscription Status Filter Tests - describe("subscription_status filter (V2)", () => { - test("should filter by active status", async () => { - const result = await autumn.customers.listV2({ - subscription_status: ["active"], - }); - - // All our test customers have active products - const foundA = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withProductA, - ); - const foundB = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withProductB, - ); - expect(foundA).toBeDefined(); - expect(foundB).toBeDefined(); - }); - - test("should filter by multiple statuses", async () => { - const result = await autumn.customers.listV2({ - subscription_status: ["active", "scheduled"], - }); - - // Should include active customers - const foundA = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withProductA, - ); - expect(foundA).toBeDefined(); - }); - - test("should combine subscription_status with plans filter (AND logic)", async () => { - const result = await autumn.customers.listV2({ - plans: [{ id: productA.id }], - subscription_status: ["active"], - }); - - // Should find customers with productA AND active status - const foundA = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withProductA, - ); - expect(foundA).toBeDefined(); - - // Should NOT find customers with other products even if active - const foundB = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withProductB, - ); - const foundOther = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withOtherProduct, - ); - expect(foundB).toBeUndefined(); - expect(foundOther).toBeUndefined(); - }); - - test("should require BOTH plan AND status to match when combined", async () => { - // Filter for productA with active status - const result = await autumn.customers.listV2({ - plans: [{ id: productA.id, versions: [1] }], - subscription_status: ["active"], - }); - - // Only customers with productA v1 AND active status should be returned - const foundA = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withProductA, - ); - expect(foundA).toBeDefined(); - - // ProductB customer should NOT be returned (wrong product) - const foundB = result.list.find( - (customer: ApiCustomer) => customer.id === customerIds.withProductB, - ); - expect(foundB).toBeUndefined(); - }); - }); -}); +// import { beforeAll, describe, expect, test } from "bun:test"; +// import { +// type ApiCustomer, +// ApiVersion, +// ProductItemInterval, +// } from "@autumn/shared"; +// import { TestFeature } from "@tests/setup/v2Features.js"; +// import ctx from "@tests/utils/testInitUtils/createTestContext.js"; +// import chalk from "chalk"; +// import { AutumnInt } from "@/external/autumn/autumnCli.js"; +// import { constructFeatureItem } from "@/utils/scriptUtils/constructItem.js"; +// import { constructProduct } from "@/utils/scriptUtils/createTestProducts.js"; +// import { initCustomerV3 } from "@/utils/scriptUtils/testUtils/initCustomerV3.js"; +// import { initProductsV0 } from "@/utils/scriptUtils/testUtils/initProductsV0.js"; + +// const testCase = "list-customers"; + +// // Different products for testing multi-plan filtering +// const productA = constructProduct({ +// id: `${testCase}-product-a`, +// type: "free", +// isDefault: false, +// version: 1, +// items: [ +// constructFeatureItem({ +// featureId: TestFeature.Messages, +// includedUsage: 100, +// interval: ProductItemInterval.Month, +// }), +// ], +// }); + +// const productB = constructProduct({ +// id: `${testCase}-product-b`, +// type: "free", +// isDefault: false, +// version: 1, +// items: [ +// constructFeatureItem({ +// featureId: TestFeature.Messages, +// includedUsage: 200, +// interval: ProductItemInterval.Month, +// }), +// ], +// }); + +// const otherProduct = constructProduct({ +// id: `${testCase}-other-product`, +// type: "free", +// isDefault: false, +// items: [ +// constructFeatureItem({ +// featureId: TestFeature.Dashboard, +// isBoolean: true, +// }), +// ], +// }); + +// const customerIds = { +// withProductA: `${testCase}-cus-a`, +// withProductB: `${testCase}-cus-b`, +// withOtherProduct: `${testCase}-cus-other`, +// searchable: `${testCase}-searchable-john`, +// }; + +// describe(`${chalk.yellowBright("list-customers: Testing list customers endpoint")}`, () => { +// const autumn = new AutumnInt({ +// secretKey: ctx.orgSecretKey, +// version: ApiVersion.V1_2, +// }); + +// beforeAll(async () => { +// // Create products +// await initProductsV0({ +// ctx, +// products: [productA, productB, otherProduct], +// prefix: "", +// customerId: customerIds.withProductA, +// }); + +// // Create customers +// for (const customerId of Object.values(customerIds)) { +// await initCustomerV3({ +// ctx, +// customerId, +// withTestClock: false, +// withDefault: false, +// }); +// } + +// // Attach products to customers +// await autumn.attach({ +// customer_id: customerIds.withProductA, +// product_id: productA.id, +// }); + +// await autumn.attach({ +// customer_id: customerIds.withProductB, +// product_id: productB.id, +// }); + +// await autumn.attach({ +// customer_id: customerIds.withOtherProduct, +// product_id: otherProduct.id, +// }); + +// // Attach product to searchable customer so it's not filtered out by default status +// await autumn.attach({ +// customer_id: customerIds.searchable, +// product_id: productA.id, +// }); +// }); + +// // Pagination Tests +// describe("pagination", () => { +// test("should return customers with default pagination", async () => { +// const result = await autumn.customers.list(); + +// expect(result.list).toBeDefined(); +// expect(Array.isArray(result.list)).toBe(true); +// expect(result.limit).toBe(10); +// expect(result.offset).toBe(0); +// expect(typeof result.total).toBe("number"); +// }); + +// test("should respect custom limit", async () => { +// const result = await autumn.customers.list({ limit: 20 }); + +// expect(result.limit).toBe(20); +// }); + +// test("should respect offset", async () => { +// const result = await autumn.customers.list({ offset: 5 }); + +// expect(result.offset).toBe(5); +// }); + +// test("should respect max limit of 100", async () => { +// const result = await autumn.customers.list({ limit: 100 }); + +// expect(result.limit).toBe(100); +// }); +// }); + +// // Search Tests (V2) +// describe("search", () => { +// test("should search by customer ID", async () => { +// const result = await autumn.customers.listV2({ +// search: "searchable-john", +// }); + +// expect(result.list.length).toBeGreaterThanOrEqual(1); +// const found = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.searchable, +// ); +// expect(found).toBeDefined(); +// }); + +// test("should search by customer email", async () => { +// const result = await autumn.customers.listV2({ +// search: "searchable-john@example", +// }); + +// expect(result.list.length).toBeGreaterThanOrEqual(1); +// const found = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.searchable, +// ); +// expect(found).toBeDefined(); +// }); + +// test("should return empty list for non-matching search", async () => { +// const result = await autumn.customers.listV2({ +// search: "nonexistent-customer-xyz-123", +// }); + +// expect(result.list.length).toBe(0); +// }); + +// test("should be case-insensitive", async () => { +// const result = await autumn.customers.listV2({ +// search: "SEARCHABLE-JOHN", +// }); + +// expect(result.list.length).toBeGreaterThanOrEqual(1); +// }); +// }); + +// // Response Structure Tests +// describe("response structure", () => { +// test("should have correct response structure", async () => { +// const result = await autumn.customers.list(); + +// expect(result).toHaveProperty("list"); +// expect(result).toHaveProperty("total"); +// expect(result).toHaveProperty("limit"); +// expect(result).toHaveProperty("offset"); +// }); + +// test("each customer should have expected fields", async () => { +// const result = await autumn.customers.list({ limit: 10 }); + +// if (result.list.length > 0) { +// const customer = result.list[0]; +// expect(customer).toHaveProperty("id"); +// expect(customer).toHaveProperty("created_at"); +// expect(customer).toHaveProperty("products"); +// expect(customer).toHaveProperty("features"); +// } +// }); +// }); + +// // V2 Plans Filter Tests +// describe("plans filter (V2)", () => { +// test("should filter by single plan and exclude non-matching customers", async () => { +// const result = await autumn.customers.listV2({ +// plans: [{ id: productA.id }], +// }); + +// // Should find customers with productA (withProductA and searchable) +// const foundA = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withProductA, +// ); +// const foundSearchable = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.searchable, +// ); +// expect(foundA).toBeDefined(); +// expect(foundSearchable).toBeDefined(); + +// // Should NOT find customers with other products +// const foundB = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withProductB, +// ); +// const foundOther = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withOtherProduct, +// ); +// expect(foundB).toBeUndefined(); +// expect(foundOther).toBeUndefined(); +// }); + +// test("should filter by single plan with specific version", async () => { +// const result = await autumn.customers.listV2({ +// plans: [{ id: productA.id, versions: [1] }], +// }); + +// const found = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withProductA, +// ); +// expect(found).toBeDefined(); + +// // Should NOT find customers with productB (different product) +// const foundB = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withProductB, +// ); +// expect(foundB).toBeUndefined(); +// }); + +// test("should return empty list for non-matching version", async () => { +// // All products are version 1, so filtering for version 999 should return empty +// const result = await autumn.customers.listV2({ +// plans: [{ id: productA.id, versions: [999] }], +// }); + +// expect(result.list.length).toBe(0); +// }); + +// test("should filter by multiple plans (OR logic)", async () => { +// const result = await autumn.customers.listV2({ +// plans: [{ id: productA.id }, { id: productB.id }], +// }); + +// // Should find customers with productA OR productB +// const foundA = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withProductA, +// ); +// const foundB = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withProductB, +// ); +// expect(foundA).toBeDefined(); +// expect(foundB).toBeDefined(); + +// // Should NOT find customer with otherProduct (not in filter) +// const foundOther = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withOtherProduct, +// ); +// expect(foundOther).toBeUndefined(); +// }); + +// test("should filter by multiple plans including other product", async () => { +// const result = await autumn.customers.listV2({ +// plans: [{ id: productA.id }, { id: otherProduct.id }], +// }); + +// const foundA = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withProductA, +// ); +// const foundOther = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withOtherProduct, +// ); +// expect(foundA).toBeDefined(); +// expect(foundOther).toBeDefined(); + +// // Should NOT find customer with productB +// const foundB = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withProductB, +// ); +// expect(foundB).toBeUndefined(); +// }); + +// test("should filter by plan with version constraint", async () => { +// const result = await autumn.customers.listV2({ +// plans: [{ id: productA.id, versions: [1] }, { id: otherProduct.id }], +// }); + +// const foundA = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withProductA, +// ); +// const foundOther = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withOtherProduct, +// ); +// expect(foundA).toBeDefined(); +// expect(foundOther).toBeDefined(); + +// // Should NOT find customer with productB +// const foundB = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withProductB, +// ); +// expect(foundB).toBeUndefined(); +// }); + +// test("should combine plans filter with search", async () => { +// const result = await autumn.customers.listV2({ +// plans: [{ id: productA.id }], +// search: "cus-a", +// }); + +// // Should find exactly the customer matching both criteria +// expect(result.list.length).toBe(1); +// expect(result.list[0].id).toBe(customerIds.withProductA); +// }); + +// test("should return empty list for non-existent plan", async () => { +// const result = await autumn.customers.listV2({ +// plans: [{ id: "nonexistent-plan-xyz" }], +// }); + +// expect(result.list.length).toBe(0); +// }); + +// test("should return empty list for non-existent version", async () => { +// const result = await autumn.customers.listV2({ +// plans: [{ id: productA.id, versions: [999] }], +// }); + +// expect(result.list.length).toBe(0); +// }); + +// test("should have correct response structure", async () => { +// const result = await autumn.customers.listV2({ +// plans: [{ id: productA.id }], +// }); + +// expect(result).toHaveProperty("list"); +// expect(result).toHaveProperty("total"); +// expect(result).toHaveProperty("limit"); +// expect(result).toHaveProperty("offset"); +// }); + +// test("should return plan_version in product response", async () => { +// const result = await autumn.customers.listV2({ +// plans: [{ id: productA.id }], +// }); + +// const found = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withProductA, +// ); +// expect(found).toBeDefined(); + +// // Check that version is returned correctly (V1.2 returns as 'products' with 'version' field) +// const products = (found as any).products; +// expect(products).toBeDefined(); +// expect(Array.isArray(products)).toBe(true); + +// const matchingProduct = products.find((p: any) => p.id === productA.id); +// expect(matchingProduct).toBeDefined(); +// expect(matchingProduct.version).toBe(1); +// }); +// }); + +// // V2 Subscription Status Filter Tests +// describe("subscription_status filter (V2)", () => { +// test("should filter by active status", async () => { +// const result = await autumn.customers.listV2({ +// subscription_status: ["active"], +// }); + +// // All our test customers have active products +// const foundA = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withProductA, +// ); +// const foundB = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withProductB, +// ); +// expect(foundA).toBeDefined(); +// expect(foundB).toBeDefined(); +// }); + +// test("should filter by multiple statuses", async () => { +// const result = await autumn.customers.listV2({ +// subscription_status: ["active", "scheduled"], +// }); + +// // Should include active customers +// const foundA = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withProductA, +// ); +// expect(foundA).toBeDefined(); +// }); + +// test("should combine subscription_status with plans filter (AND logic)", async () => { +// const result = await autumn.customers.listV2({ +// plans: [{ id: productA.id }], +// subscription_status: ["active"], +// }); + +// // Should find customers with productA AND active status +// const foundA = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withProductA, +// ); +// expect(foundA).toBeDefined(); + +// // Should NOT find customers with other products even if active +// const foundB = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withProductB, +// ); +// const foundOther = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withOtherProduct, +// ); +// expect(foundB).toBeUndefined(); +// expect(foundOther).toBeUndefined(); +// }); + +// test("should require BOTH plan AND status to match when combined", async () => { +// // Filter for productA with active status +// const result = await autumn.customers.listV2({ +// plans: [{ id: productA.id, versions: [1] }], +// subscription_status: ["active"], +// }); + +// // Only customers with productA v1 AND active status should be returned +// const foundA = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withProductA, +// ); +// expect(foundA).toBeDefined(); + +// // ProductB customer should NOT be returned (wrong product) +// const foundB = result.list.find( +// (customer: ApiCustomer) => customer.id === customerIds.withProductB, +// ); +// expect(foundB).toBeUndefined(); +// }); +// }); +// }); diff --git a/server/tests/utils/fixtures/products.ts b/server/tests/utils/fixtures/products.ts index 807e72955..be0ef5f1b 100644 --- a/server/tests/utils/fixtures/products.ts +++ b/server/tests/utils/fixtures/products.ts @@ -16,6 +16,7 @@ import { * @param items - Product items (features) * @param id - Product ID (default: "base") * @param isDefault - Whether this is a default product (default: false) + * @param group - Optional product group (if set, won't be overridden by test prefix) * @param trialDays - Optional number of trial days (shorthand) * @param freeTrial - Optional full free trial config (overrides trialDays) */ @@ -24,6 +25,7 @@ const base = ({ id = "base", isDefault = false, isAddOn = false, + group, trialDays, freeTrial, }: { @@ -31,6 +33,7 @@ const base = ({ id?: string; isDefault?: boolean; isAddOn?: boolean; + group?: string; trialDays?: number; freeTrial?: { length: number; @@ -39,7 +42,7 @@ const base = ({ uniqueFingerprint?: boolean; }; }): ProductV2 => ({ - ...constructRawProduct({ id, items, isAddOn }), + ...constructRawProduct({ id, items, isAddOn, group }), is_default: isDefault, ...(freeTrial ? { diff --git a/server/tests/utils/testAttachUtils/testAttachUtils.ts b/server/tests/utils/testAttachUtils/testAttachUtils.ts index c51ed7ad2..ea9d0ff9d 100644 --- a/server/tests/utils/testAttachUtils/testAttachUtils.ts +++ b/server/tests/utils/testAttachUtils/testAttachUtils.ts @@ -168,14 +168,14 @@ export const advanceToNextInvoice = async ({ stripeCli, testClockId, advanceTo: addMonths(baseTime, 1).getTime(), - waitForSeconds: 15, + waitForSeconds: 30, }); await advanceTestClock({ stripeCli, testClockId, advanceTo: addHours(newUnix, hoursToFinalizeInvoice).getTime(), - waitForSeconds: 15, + waitForSeconds: 30, }); return newUnix; diff --git a/server/tests/utils/testProductUtils/testProductUtils.ts b/server/tests/utils/testProductUtils/testProductUtils.ts index f32af7b81..5f335c2de 100644 --- a/server/tests/utils/testProductUtils/testProductUtils.ts +++ b/server/tests/utils/testProductUtils/testProductUtils.ts @@ -20,7 +20,10 @@ export const addPrefixToProducts = ({ for (const product of products) { product.id = `${product.id}_${prefix}`; product.name = `${product.name} ${prefix}`; - product.group = prefix; + // Only set group to prefix if not already defined + if (!product.group) { + product.group = prefix; + } } return products; diff --git a/shared/api/balances/track/requestChanges/V1.2_TrackParamsChange.ts b/shared/api/balances/track/requestChanges/V1.2_TrackParamsChange.ts new file mode 100644 index 000000000..262be59f5 --- /dev/null +++ b/shared/api/balances/track/requestChanges/V1.2_TrackParamsChange.ts @@ -0,0 +1,62 @@ +import { ApiVersion } from "@api/versionUtils/ApiVersion.js"; +import { + AffectedResource, + defineVersionChange, +} from "@api/versionUtils/versionChangeUtils/VersionChange.js"; +import type { z } from "zod/v4"; +import { TrackParamsSchema } from "../trackParams.js"; + +/** + * V1_2_TrackParamsChange: Transforms track request body TO latest format + * + * Applied when: sourceVersion <= V1.2 + * + * In V1.2, `value` could be passed via `properties.value` as a legacy pattern. + * This transformation extracts `properties.value` and maps it to the top-level + * `value` field (only if `value` is not already provided). + */ +export const V1_2_TrackParamsChange = defineVersionChange({ + name: "V1_2 Track Params Change", + newVersion: ApiVersion.V2_0, + oldVersion: ApiVersion.V1_Beta, + description: [ + "Maps properties.value to top-level value field for V1.2 clients", + ], + affectedResources: [AffectedResource.Track], + newSchema: TrackParamsSchema, + oldSchema: TrackParamsSchema, + + affectsRequest: true, + affectsResponse: false, + + // Request: V1.2 → V2.0 (extract properties.value to value if not already set) + transformRequest: ({ + input, + }: { + input: z.infer; + }): z.infer => { + // Keep original value if provided, otherwise extract from properties.value + let value = input.value; + let properties = input.properties; + + if (input.properties?.value !== undefined) { + // Only use properties.value if top-level value is not set + if (value === undefined) { + const parsedValue = Number(input.properties.value); + if (!Number.isNaN(parsedValue)) { + value = parsedValue; + } + } + + // Always remove value from properties after processing + const { value: _, ...restProperties } = input.properties; + properties = Object.keys(restProperties).length > 0 ? restProperties : {}; + } + + return { + ...input, + properties, + value, + }; + }, +}); diff --git a/shared/api/common/customerData.ts b/shared/api/common/customerData.ts index 34aad96da..cde1d7410 100644 --- a/shared/api/common/customerData.ts +++ b/shared/api/common/customerData.ts @@ -37,6 +37,10 @@ export const ExtCustomerDataSchema = z description: "Whether to create the customer in Stripe", }), + auto_enable_plan_id: z.string().optional().meta({ + description: "The ID of the free plan to auto-enable for the customer", + }), + processors: ExternalProcessorsSchema.nullish().meta({ internal: true, description: "External processors for the customer", diff --git a/shared/api/versionUtils/versionChangeUtils/versionChangeRegistry.ts b/shared/api/versionUtils/versionChangeUtils/versionChangeRegistry.ts index f44ba2e6f..3cdaa0717 100644 --- a/shared/api/versionUtils/versionChangeUtils/versionChangeRegistry.ts +++ b/shared/api/versionUtils/versionChangeUtils/versionChangeRegistry.ts @@ -25,6 +25,7 @@ import { V0_2_CheckChange } from "../../balances/check/changes/V0.2_CheckChange. import { V1_2_CheckChange } from "../../balances/check/changes/V1.2_CheckChange.js"; import { V1_2_CheckQueryChange } from "../../balances/check/changes/V1.2_CheckQueryChange.js"; import { V1_2_TrackChange } from "../../balances/track/changes/V1.2_TrackChange.js"; +import { V1_2_TrackParamsChange } from "../../balances/track/requestChanges/V1.2_TrackParamsChange.js"; // Import attach changes import { V0_2_AttachChange } from "../../billing/attach/changes/V0.2_AttachChange.js"; import { ApiVersion } from "../ApiVersion.js"; @@ -42,6 +43,7 @@ export const V2_CHANGES: VersionChangeConstructor[] = [ V1_2_CheckChange, // Transforms Check TO V1.2 format from V0.2 format V1_2_CheckQueryChange, // Transforms Check Query TO V2.0 format (adds expand options) V1_2_TrackChange, // Transforms Track TO V1.2 format from V0.2 format + V1_2_TrackParamsChange, // Transforms Track params TO V2.0 (maps properties.value → value) V1_2_FeatureChange, // Transforms Feature TO V1_Beta format (V0) from V2 format (V1) V1_2_CreateFeatureChange, // Transforms Create Feature params TO V1_Beta diff --git a/vite/src/hooks/queries/useProductsQuery.tsx b/vite/src/hooks/queries/useProductsQuery.tsx index 701a3a241..c6fdba4e7 100644 --- a/vite/src/hooks/queries/useProductsQuery.tsx +++ b/vite/src/hooks/queries/useProductsQuery.tsx @@ -2,6 +2,9 @@ import type { FullProduct, ProductCounts, ProductV2 } from "@autumn/shared"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { useAxiosInstance } from "@/services/useAxiosInstance"; +// Stable empty object reference to prevent infinite re-renders +const EMPTY_COUNTS: Record = {}; + /** * Fetch all products for the current org. */ @@ -48,7 +51,7 @@ export const useProductsQuery = () => { return { products: data?.products || [], - counts: countsData || {}, + counts: countsData ?? EMPTY_COUNTS, groupToDefaults: data?.groupToDefaults || {}, isLoading, isCountsLoading, diff --git a/vite/src/views/auth/SignIn.tsx b/vite/src/views/auth/SignIn.tsx index c50048475..890356e25 100644 --- a/vite/src/views/auth/SignIn.tsx +++ b/vite/src/views/auth/SignIn.tsx @@ -4,7 +4,6 @@ import { Mail } from "lucide-react"; import { useEffect, useState } from "react"; import { useNavigate } from "react-router"; import { toast } from "sonner"; -import { z } from "zod"; import { CustomToaster } from "@/components/general/CustomToaster"; import { IconButton } from "@/components/v2/buttons/IconButton"; import { Input } from "@/components/v2/inputs/Input"; @@ -13,7 +12,7 @@ import { authClient, signIn } from "@/lib/auth-client"; import { getBackendErr } from "@/utils/genUtils"; import { OTPSignIn } from "./components/OTPSignIn"; -export const emailSchema = z.string().email(); +export const emailRegex = /^[^@]+@[^@]+\.[^@]+$/; export const SignIn = () => { const [email, setEmail] = useState(""); @@ -39,7 +38,7 @@ export const SignIn = () => { const handleEmailSignIn = async (e: React.FormEvent) => { e.preventDefault(); - if (!email || !emailSchema.safeParse(email).success) { + if (!email || !emailRegex.test(email)) { toast.error("Please enter a valid email address."); return; } diff --git a/vite/src/views/auth/components/PasswordSignIn.tsx b/vite/src/views/auth/components/PasswordSignIn.tsx index c675924d4..6d0479f5a 100644 --- a/vite/src/views/auth/components/PasswordSignIn.tsx +++ b/vite/src/views/auth/components/PasswordSignIn.tsx @@ -5,7 +5,7 @@ import { CustomToaster } from "@/components/general/CustomToaster"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { authClient, useSession } from "@/lib/auth-client"; -import { emailSchema } from "../SignIn"; +import { emailRegex } from "../SignIn"; export const PasswordSignIn = () => { const [email, setEmail] = useState(""); @@ -25,7 +25,7 @@ export const PasswordSignIn = () => { const handleEmailSignIn = async (e: React.FormEvent) => { e.preventDefault(); - if (!email || !emailSchema.safeParse(email).success) { + if (!email || !emailRegex.test(email)) { toast.error("Please enter a valid email address."); return; } @@ -67,48 +67,46 @@ export const PasswordSignIn = () => { - <> -
-
- setEmail(e.target.value)} - required - className="text-base" - autoComplete="email" - /> - setPassword(e.target.value)} - required - className="text-base" - autoComplete="email" - /> -
- - {/* Sign In Button */} - +
+
+ setEmail(e.target.value)} + required + className="text-base" + autoComplete="email" + /> + setPassword(e.target.value)} + required + className="text-base" + autoComplete="email" + />
- {/* Footer */} - {/*
+ {/* Sign In Button */} + +
+ + {/* Footer */} + {/*
Create an account here
*/} -
); diff --git a/vite/src/views/customers2/components/sheets/BalanceEditSheet.tsx b/vite/src/views/customers2/components/sheets/BalanceEditSheet.tsx index ee2516eac..4334c9669 100644 --- a/vite/src/views/customers2/components/sheets/BalanceEditSheet.tsx +++ b/vite/src/views/customers2/components/sheets/BalanceEditSheet.tsx @@ -2,6 +2,7 @@ import { cusEntsToBalance, cusEntsToGrantedBalance, cusEntsToPrepaidQuantity, + type Entity, type FullCusProduct, type FullCustomerEntitlement, type FullCustomerPrice, @@ -240,12 +241,22 @@ export function BalanceEditSheet() {
- {cusProduct?.entity_id && ( - - )} + {(() => { + // For loose entitlements, check selectedCusEnt.internal_entity_id + // For product entitlements, check cusProduct.entity_id + const entity = customer?.entities?.find((e: Entity) => { + if (selectedCusEnt.internal_entity_id) { + return e.internal_id === selectedCusEnt.internal_entity_id; + } + return ( + e.internal_id === cusProduct?.internal_entity_id || + e.id === cusProduct?.entity_id + ); + }); + return entity ? ( + + ) : null; + })()}
diff --git a/vite/src/views/customers2/components/sheets/BalanceSelectionSheet.tsx b/vite/src/views/customers2/components/sheets/BalanceSelectionSheet.tsx index 98a0fc0f5..4c0b3fc71 100644 --- a/vite/src/views/customers2/components/sheets/BalanceSelectionSheet.tsx +++ b/vite/src/views/customers2/components/sheets/BalanceSelectionSheet.tsx @@ -74,11 +74,19 @@ export function BalanceSelectionSheet() { entityId, }).balance; - const entity = customer?.entities?.find( - (e: Entity) => + // For loose entitlements (no customer_product), check cusEnt.internal_entity_id + // For product entitlements, check cusProduct.internal_entity_id/entity_id + const entity = customer?.entities?.find((e: Entity) => { + // Check for entity-level loose entitlement + if (cusEnt.internal_entity_id) { + return e.internal_id === cusEnt.internal_entity_id; + } + // Check for entity-level product entitlement + return ( e.internal_id === cusProduct?.internal_entity_id || - e.id === cusProduct?.entity_id, - ); + e.id === cusProduct?.entity_id + ); + }); const entitlement = cusEnt.entitlement; const isConsumable = diff --git a/vite/src/views/customers2/components/table/customer-feature-usage/CustomerFeatureUsageTable.tsx b/vite/src/views/customers2/components/table/customer-feature-usage/CustomerFeatureUsageTable.tsx index ae9824f9b..abff107ab 100644 --- a/vite/src/views/customers2/components/table/customer-feature-usage/CustomerFeatureUsageTable.tsx +++ b/vite/src/views/customers2/components/table/customer-feature-usage/CustomerFeatureUsageTable.tsx @@ -31,15 +31,14 @@ export function CustomerFeatureUsageTable() { const [expanded, setExpanded] = useState({}); - const filteredCustomerProducts = useMemo(() => { - if (!entityId) { - return customer?.customer_products ?? []; - } - - const selectedEntity = customer?.entities.find( + const selectedEntity = useMemo(() => { + if (!entityId) return null; + return customer?.entities.find( (e: Entity) => e.id === entityId || e.internal_id === entityId, ); + }, [customer?.entities, entityId]); + const filteredCustomerProducts = useMemo(() => { if (!selectedEntity) { return customer?.customer_products ?? []; } @@ -50,7 +49,7 @@ export function CustomerFeatureUsageTable() { cp.internal_entity_id === selectedEntity.internal_id || cp.entity_id === selectedEntity.id, ); - }, [customer?.customer_products, customer?.entities, entityId]); + }, [customer?.customer_products, selectedEntity]); const cusEnts = useMemo((): FullCusEntWithFullCusProduct[] => { const productEnts = flattenCustomerEntitlements({ @@ -58,15 +57,30 @@ export function CustomerFeatureUsageTable() { }); // Add extra entitlements (loose entitlements not tied to a product) + // Customer level: show ALL loose entitlements (customer can access entity-scoped balances at top level) + // Entity level: show ONLY that entity's loose entitlements const extraEnts: FullCusEntWithFullCusProduct[] = ( customer?.extra_customer_entitlements || [] - ).map((ent: FullCustomerEntitlement) => ({ - ...ent, - customer_product: null, - })); + ) + .filter((ent: FullCustomerEntitlement) => { + // If no entity selected (customer level), show ALL loose entitlements + if (!selectedEntity) { + return true; + } + // If entity selected, show ONLY that entity's loose entitlements + return ent.internal_entity_id === selectedEntity.internal_id; + }) + .map((ent: FullCustomerEntitlement) => ({ + ...ent, + customer_product: null, + })); return [...productEnts, ...extraEnts]; - }, [filteredCustomerProducts, customer?.extra_customer_entitlements]); + }, [ + filteredCustomerProducts, + customer?.extra_customer_entitlements, + selectedEntity, + ]); const featuresMap = useMemo( () => createFeaturesMap({ features: features ?? [] }), diff --git a/vite/src/views/products/plan/components/SelectFeatureSheet.tsx b/vite/src/views/products/plan/components/SelectFeatureSheet.tsx index a3121f9aa..523bf0938 100644 --- a/vite/src/views/products/plan/components/SelectFeatureSheet.tsx +++ b/vite/src/views/products/plan/components/SelectFeatureSheet.tsx @@ -49,9 +49,11 @@ export function SelectFeatureSheet({ } }, [selectOpen]); - // Filter features based on search - const filteredFeatures = features.filter((feature: Feature) => - feature.name.toLowerCase().includes(searchValue.toLowerCase()), + // Filter features based on search and exclude archived features + const filteredFeatures = features.filter( + (feature: Feature) => + !feature.archived && + feature.name.toLowerCase().includes(searchValue.toLowerCase()), ); const handleFeatureSelect = (featureId: string) => { diff --git a/vite/src/views/products/plan/components/edit-plan-feature/AdvancedSettings.tsx b/vite/src/views/products/plan/components/edit-plan-feature/AdvancedSettings.tsx index 51f166cc8..9bc36596a 100644 --- a/vite/src/views/products/plan/components/edit-plan-feature/AdvancedSettings.tsx +++ b/vite/src/views/products/plan/components/edit-plan-feature/AdvancedSettings.tsx @@ -1,5 +1,9 @@ /** biome-ignore-all lint/a11y/noStaticElementInteractions: shush */ -import { FeatureUsageType, isFeaturePriceItem } from "@autumn/shared"; +import { + FeatureUsageType, + isFeaturePriceItem, + UsageModel, +} from "@autumn/shared"; import { AreaCheckbox } from "@/components/v2/checkboxes/AreaCheckbox"; import { SheetAccordion, @@ -41,8 +45,11 @@ export function AdvancedSettings() { const showUsageLimits = isPriced; const showRollover = hasCreditSystem || usageType === FeatureUsageType.Single; const showEntityFeature = hasEntityFeatureId && hasOtherContinuousFeatures; - // Proration shows for priced features (simplified check) - const showProration = isPriced; + // Proration shows for prepaid or continuous use features (not consumable + pay-per-use) + const showProration = + isPriced && + (item.usage_model === UsageModel.Prepaid || + usageType === FeatureUsageType.Continuous); // Hide Advanced section if nothing will render inside it const hasAnyContent = diff --git a/vite/src/views/products/plan/components/edit-plan-feature/advanced-settings/EntityFeatureConfig.tsx b/vite/src/views/products/plan/components/edit-plan-feature/advanced-settings/EntityFeatureConfig.tsx index 1346075f3..04740a1b2 100644 --- a/vite/src/views/products/plan/components/edit-plan-feature/advanced-settings/EntityFeatureConfig.tsx +++ b/vite/src/views/products/plan/components/edit-plan-feature/advanced-settings/EntityFeatureConfig.tsx @@ -18,9 +18,10 @@ export function EntityFeatureConfig() { if (!item) return null; - // Filter for continuous use features, excluding the current feature (can't link to itself) + // Filter for continuous use features, excluding the current feature (can't link to itself) and archived features const continuousUseFeatures = features.filter( (f) => + !f.archived && f.config?.usage_type === FeatureUsageType.Continuous && f.id !== item.feature_id, );