This commit is contained in:
John Yeo
2025-10-25 10:52:03 +01:00
parent f7ebe22c00
commit dcc26b44a9
107 changed files with 2134 additions and 2738 deletions

348
bun.lock
View File

@@ -275,8 +275,6 @@
"@ai-sdk/ui-utils": ["@ai-sdk/ui-utils@1.2.11", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "@ai-sdk/provider-utils": "2.2.8", "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-3zcwCc8ezzFlwp3ZD15wAPjf2Au4s3vAbKsXQVyhxODHcmu0iyPO2Eua6D/vicq/AUm/BAo60r97O6HU+EI0+w=="],
<<<<<<< HEAD
=======
"@amplitude/analytics-browser": ["@amplitude/analytics-browser@2.27.0", "", { "dependencies": { "@amplitude/analytics-core": "^2.28.0", "@amplitude/plugin-autocapture-browser": "^1.15.3", "@amplitude/plugin-network-capture-browser": "^1.6.9", "@amplitude/plugin-page-view-tracking-browser": "^2.5.3", "@amplitude/plugin-web-vitals-browser": "^0.1.0-beta.31", "tslib": "^2.4.1" } }, "sha512-1LBCLmnr7aUpLtOp64lpr8GzN3vPKM0fwiM/7tWJ9XU9/GKA+k3CUSjI8OdERKrw2yVywujoAVQo4anGZXYIDA=="],
"@amplitude/analytics-client-common": ["@amplitude/analytics-client-common@2.4.8", "", { "dependencies": { "@amplitude/analytics-connector": "^1.4.8", "@amplitude/analytics-core": "^2.28.0", "@amplitude/analytics-types": "^2.10.0", "tslib": "^2.4.1" } }, "sha512-cSm9Q+qcLy65kV2MnD7WlKrFMDOkj14dHM2YQn5njUrSXQljyjtYCss+HVzgIlWPSDgA5v6dDeHwxzZl3VSggw=="],
@@ -333,7 +331,6 @@
"@amplitude/unified": ["@amplitude/unified@1.0.0-beta.9", "", { "dependencies": { "@amplitude/analytics-browser": "^2.25.2", "@amplitude/engagement-browser": "^1.0.3", "@amplitude/plugin-experiment-browser": "^1.0.0-beta.0", "@amplitude/plugin-session-replay-browser": "^1.16.0" } }, "sha512-JS7Dq6GJ6amR0olsVu0ekypAUHpcect8pJNTKjWYtEwdUbfQP6giE41Bum8rLR85SOMBqbuMdDr3yO9oj2CKyw=="],
>>>>>>> staging
"@anthropic-ai/sdk": ["@anthropic-ai/sdk@0.32.1", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-U9JwTrDvdQ9iWuABVsMLj8nJVwAyQz6QXvgLsVhryhCEPkLsbcP/MXxm+jYcAwLoV8ESbaTTjnD4kuAFa+Hyjg=="],
"@autumn/scripts": ["@autumn/scripts@workspace:scripts"],
@@ -388,17 +385,11 @@
"@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="],
<<<<<<< HEAD
"@better-auth/core": ["@better-auth/core@1.3.27", "", { "dependencies": { "better-call": "1.0.19", "zod": "^4.1.5" } }, "sha512-3Sfdax6MQyronY+znx7bOsfQHI6m1SThvJWb0RDscFEAhfqLy95k1sl+/PgGyg0cwc2cUXoEiAOSqYdFYrg3vA=="],
"@better-auth/stripe": ["@better-auth/stripe@1.3.27", "", { "dependencies": { "defu": "^6.1.4", "zod": "^4.1.5" }, "peerDependencies": { "@better-auth/core": "1.3.27", "better-auth": "1.3.27", "stripe": "^18" } }, "sha512-YXmWMvX07lCDHheRB65jl80sSHTClFeJqN2vU6v5tI+0R4tzYBeWxYjvkS6cks1JJ8IjPUKTew0gy2Px3AlOCA=="],
=======
"@better-auth/core": ["@better-auth/core@1.3.28", "", { "dependencies": { "zod": "^4.1.5" }, "peerDependencies": { "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "better-call": "1.0.19", "better-sqlite3": "^12.4.1", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1" } }, "sha512-iZOGKlXaNEIEj0Q3z7+REE94I89YUJ0sel/1pvm1qqdHkm59G+ToTysHtyTcLYby3+UtAeJRKyFAY0nwJH0H7A=="],
"@better-auth/stripe": ["@better-auth/stripe@1.3.28", "", { "dependencies": { "defu": "^6.1.4", "zod": "^4.1.5" }, "peerDependencies": { "@better-auth/core": "1.3.28", "better-auth": "1.3.28", "stripe": "^18" } }, "sha512-FGvQnIcLoMzNrfvzIVmT80/bBYyUwIQwHrG5KXbmTi26r9YTE0pylDiYD7RHX6q7H3wOntUXUyGDJ4b19h1+ew=="],
"@better-auth/telemetry": ["@better-auth/telemetry@1.3.28", "", { "dependencies": { "@better-auth/core": "1.3.28", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18" } }, "sha512-qZtV82IFuyQZc2c37VkiDgO/qfqPnJuWIyeC/iFK1AA5N8RSuC2+CVIH1sNDytPXUAthbYeOzcOCW2YEkgz1Ow=="],
>>>>>>> staging
"@better-auth/utils": ["@better-auth/utils@0.3.0", "", {}, "sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw=="],
@@ -424,15 +415,6 @@
"@browserbasehq/sdk": ["@browserbasehq/sdk@2.6.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" } }, "sha512-83iXP5D7xMm8Wyn66TUaUrgoByCmAJuoMoZQI3sGg3JAiMlTfnCIMqyVBoNSaItaPIkaCnrsj6LiusmXV2X9YA=="],
<<<<<<< HEAD
"@clerk/backend": ["@clerk/backend@2.18.0", "", { "dependencies": { "@clerk/shared": "^3.27.4", "@clerk/types": "^4.93.0", "cookie": "1.0.2", "standardwebhooks": "^1.0.0", "tslib": "2.8.1" } }, "sha512-Vt7yP0FbNDptWGAsNYrCK572k+0m/jFDwXaHor4164rekwUJXunQVo8DX8vqCOZn9LsKUZbs4odmG9uoRVePIQ=="],
"@clerk/express": ["@clerk/express@1.7.38", "", { "dependencies": { "@clerk/backend": "^2.18.0", "@clerk/shared": "^3.27.4", "@clerk/types": "^4.93.0", "tslib": "2.8.1" }, "peerDependencies": { "express": "^4.17.0 || ^5.0.0" } }, "sha512-+5SAeggm0847Ylvh6W/TGCqy/+ddhpzkYBdNNoPgjK13PuKkDodg7t5Wl8VR+Egs62y2Y9YEoTEQ6NE3MBd/sw=="],
"@clerk/shared": ["@clerk/shared@3.27.4", "", { "dependencies": { "@clerk/types": "^4.93.0", "dequal": "2.0.3", "glob-to-regexp": "0.4.1", "js-cookie": "3.0.5", "std-env": "^3.9.0", "swr": "2.3.4" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-K+glWUasxr9CeMHfOcpU+wiRhGQfQm1i7TQcM6tZuKmUD+DltryuF7sPjR+pj2ABUGSXt3Chlv2Y1i+8zaLLRw=="],
"@clerk/types": ["@clerk/types@4.93.0", "", { "dependencies": { "csstype": "3.1.3" } }, "sha512-xrYAAF0OHCvZm5kPjCexff44ImhIC85LG7Z8OU3WEWvsGyeSnEjZuMTp5jZnns6i+hLQbxn5/cgINzC+V7VSpw=="],
=======
"@clerk/backend": ["@clerk/backend@2.18.3", "", { "dependencies": { "@clerk/shared": "^3.28.2", "@clerk/types": "^4.95.0", "cookie": "1.0.2", "standardwebhooks": "^1.0.0", "tslib": "2.8.1" } }, "sha512-fWMq/Tb2hgfUXLKJN8jr6pbpA5XLUwC4BjWz7lB5Y+YhXhBrO7GtfpZIS91L/aDhNb17X6IaE6XvS6tDJBCUUw=="],
"@clerk/express": ["@clerk/express@1.7.41", "", { "dependencies": { "@clerk/backend": "^2.18.3", "@clerk/shared": "^3.28.2", "@clerk/types": "^4.95.0", "tslib": "2.8.1" }, "peerDependencies": { "express": "^4.17.0 || ^5.0.0" } }, "sha512-SYKXi/Prjkxx15QGOjHlvjfwO05vUg6fBaxVdg53/vcLJNyyfER+JM9qRxxcmEqN5vkkpdqURANF3eZ8dkf85w=="],
@@ -440,7 +422,6 @@
"@clerk/shared": ["@clerk/shared@3.28.2", "", { "dependencies": { "@clerk/types": "^4.95.0", "dequal": "2.0.3", "glob-to-regexp": "0.4.1", "js-cookie": "3.0.5", "std-env": "^3.9.0", "swr": "2.3.4" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" }, "optionalPeers": ["react", "react-dom"] }, "sha512-BfBCPaoPoLCiU0b0MhQUfCjs+bWRRLkdHw0vBffSjtsFLxp1b5IL5D8nKgDPIKIIv7DmCCmO15tr+GqG3CGpYQ=="],
"@clerk/types": ["@clerk/types@4.95.0", "", { "dependencies": { "csstype": "3.1.3" } }, "sha512-K1kI3BjvufG1mZBZJ5Q8Yu9wV6AFpjjITml5vhvP95xibJWOi3eYvlRCTKXDNKBFGvQfrTJbwn67jSG2VdyLKw=="],
>>>>>>> staging
"@clickhouse/client": ["@clickhouse/client@1.12.1", "", { "dependencies": { "@clickhouse/client-common": "1.12.1" } }, "sha512-7ORY85rphRazqHzImNXMrh4vsaPrpetFoTWpZYueCO2bbO6PXYDXp/GQ4DgxnGIqbWB/Di1Ai+Xuwq2o7DJ36A=="],
@@ -466,59 +447,6 @@
"@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="],
<<<<<<< HEAD
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.10", "", { "os": "aix", "cpu": "ppc64" }, "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.10", "", { "os": "android", "cpu": "arm" }, "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.10", "", { "os": "android", "cpu": "arm64" }, "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.25.10", "", { "os": "android", "cpu": "x64" }, "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.10", "", { "os": "darwin", "cpu": "arm64" }, "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.10", "", { "os": "darwin", "cpu": "x64" }, "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.10", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.10", "", { "os": "freebsd", "cpu": "x64" }, "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.10", "", { "os": "linux", "cpu": "arm" }, "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.10", "", { "os": "linux", "cpu": "arm64" }, "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.10", "", { "os": "linux", "cpu": "ia32" }, "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.10", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.10", "", { "os": "linux", "cpu": "none" }, "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.10", "", { "os": "linux", "cpu": "s390x" }, "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.10", "", { "os": "linux", "cpu": "x64" }, "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.10", "", { "os": "none", "cpu": "x64" }, "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.10", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.10", "", { "os": "openbsd", "cpu": "x64" }, "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.10", "", { "os": "none", "cpu": "arm64" }, "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.10", "", { "os": "sunos", "cpu": "x64" }, "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.10", "", { "os": "win32", "cpu": "arm64" }, "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.10", "", { "os": "win32", "cpu": "ia32" }, "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.10", "", { "os": "win32", "cpu": "x64" }, "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw=="],
=======
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.11", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.25.11", "", { "os": "android", "cpu": "arm" }, "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg=="],
@@ -570,7 +498,6 @@
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.11", "", { "os": "win32", "cpu": "ia32" }, "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.11", "", { "os": "win32", "cpu": "x64" }, "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA=="],
>>>>>>> staging
"@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="],
@@ -578,21 +505,13 @@
"@eslint/config-array": ["@eslint/config-array@0.21.1", "", { "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA=="],
<<<<<<< HEAD
"@eslint/config-helpers": ["@eslint/config-helpers@0.4.0", "", { "dependencies": { "@eslint/core": "^0.16.0" } }, "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog=="],
=======
"@eslint/config-helpers": ["@eslint/config-helpers@0.4.1", "", { "dependencies": { "@eslint/core": "^0.16.0" } }, "sha512-csZAzkNhsgwb0I/UAV6/RGFTbiakPCf0ZrGmrIxQpYvGZ00PhTkSnyKNolphgIvmnJeGw6rcGVEXfTzUnFuEvw=="],
>>>>>>> staging
"@eslint/core": ["@eslint/core@0.16.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q=="],
"@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="],
<<<<<<< HEAD
"@eslint/js": ["@eslint/js@9.37.0", "", {}, "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg=="],
=======
"@eslint/js": ["@eslint/js@9.38.0", "", {}, "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A=="],
>>>>>>> staging
"@eslint/object-schema": ["@eslint/object-schema@2.1.7", "", {}, "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA=="],
@@ -770,25 +689,6 @@
"@msgpackr-extract/msgpackr-extract-win32-x64": ["@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3", "", { "os": "win32", "cpu": "x64" }, "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ=="],
<<<<<<< HEAD
"@next/env": ["@next/env@15.5.5", "", {}, "sha512-2Zhvss36s/yL+YSxD5ZL5dz5pI6ki1OLxYlh6O77VJ68sBnlUrl5YqhBgCy7FkdMsp9RBeGFwpuDCdpJOqdKeQ=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-lYExGHuFIHeOxf40mRLWoA84iY2sLELB23BV5FIDHhdJkN1LpRTPc1MDOawgTo5ifbM5dvAwnGuHyNm60G1+jw=="],
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-cacs/WQqa96IhqUm+7CY+z/0j9sW6X80KE07v3IAJuv+z0UNvJtKSlT/T1w1SpaQRa9l0wCYYZlRZUhUOvEVmg=="],
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-tLd90SvkRFik6LSfuYjcJEmwqcNEnVYVOyKTacSazya/SLlSwy/VYKsDE4GIzOBd+h3gW+FXqShc2XBavccHCg=="],
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ekV76G2R/l3nkvylkfy9jBSYHeB4QcJ7LdDseT6INnn1p51bmDS1eGoSoq+RxfQ7B1wt+Qa0pIl5aqcx0GLpbw=="],
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.5", "", { "os": "linux", "cpu": "x64" }, "sha512-tI+sBu+3FmWtqlqD4xKJcj3KJtqbniLombKTE7/UWyyoHmOyAo3aZ7QcEHIOgInXOG1nt0rwh0KGmNbvSB0Djg=="],
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.5", "", { "os": "linux", "cpu": "x64" }, "sha512-kDRh+epN/ulroNJLr+toDjN+/JClY5L+OAWjOrrKCI0qcKvTw9GBx7CU/rdA2bgi4WpZN3l0rf/3+b8rduEwrQ=="],
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-GDgdNPFFqiKjTrmfw01sMMRWhVN5wOCmFzPloxa7ksDfX6TZt62tAK986f0ZYqWpvDFqeBCLAzmgTURvtQBdgw=="],
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.5", "", { "os": "win32", "cpu": "x64" }, "sha512-5kE3oRJxc7M8RmcTANP8RGoJkaYlwIiDD92gSwCjJY0+j8w8Sl1lvxgQ3bxfHY2KkHFai9tpy/Qx1saWV8eaJQ=="],
=======
"@next/env": ["@next/env@15.5.6", "", {}, "sha512-3qBGRW+sCGzgbpc5TS1a0p7eNxnOarGVQhZxfvTdnV0gFI61lX7QNtQ4V1TSREctXzYn5NetbUsLvyqwLFJM6Q=="],
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ES3nRz7N+L5Umz4KoGfZ4XX6gwHplwPhioVRc25+QNsDa7RtUF/z8wJcbuQ2Tffm5RZwuN2A063eapoJ1u4nPg=="],
@@ -806,7 +706,6 @@
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-ONOMrqWxdzXDJNh2n60H6gGyKed42Ieu6UTVPZteXpuKbLZTH4G4eBMsr5qWgOBA+s7F+uB4OJbZnrkEDnZ5Fg=="],
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.6", "", { "os": "win32", "cpu": "x64" }, "sha512-pxK4VIjFRx1MY92UycLOOw7dTdvccWsNETQ0kDHkBlcFH1GrTLUjSiHU1ohrznnux6TqRHgv5oflhfIWZwVROQ=="],
>>>>>>> staging
"@noble/ciphers": ["@noble/ciphers@2.0.1", "", {}, "sha512-xHK3XHPUW8DTAobU+G0XT+/w+JLM7/8k1UFdB5xg/zTFPnFCobhftzw8wl4Lw2aq/Rvir5pxfZV5fEazmeCJ2g=="],
@@ -1001,11 +900,8 @@
"@peculiar/x509": ["@peculiar/x509@1.14.0", "", { "dependencies": { "@peculiar/asn1-cms": "^2.5.0", "@peculiar/asn1-csr": "^2.5.0", "@peculiar/asn1-ecc": "^2.5.0", "@peculiar/asn1-pkcs9": "^2.5.0", "@peculiar/asn1-rsa": "^2.5.0", "@peculiar/asn1-schema": "^2.5.0", "@peculiar/asn1-x509": "^2.5.0", "pvtsutils": "^1.3.6", "reflect-metadata": "^0.2.2", "tslib": "^2.8.1", "tsyringe": "^4.10.0" } }, "sha512-Yc4PDxN3OrxUPiXgU63c+ZRXKGE8YKF2McTciYhUHFtHVB0KMnjeFSU0qpztGhsp4P0uKix4+J2xEpIEDu8oXg=="],
"@phosphor-icons/react": ["@phosphor-icons/react@2.1.10", "", { "peerDependencies": { "react": ">= 16.8", "react-dom": ">= 16.8" } }, "sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA=="],
<<<<<<< HEAD
=======
"@pinojs/redact": ["@pinojs/redact@0.4.0", "", {}, "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg=="],
>>>>>>> staging
"@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="],
@@ -1161,55 +1057,6 @@
"@react-email/text": ["@react-email/text@0.1.5", "", { "peerDependencies": { "react": "^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-o5PNHFSE085VMXayxH+SJ1LSOtGsTv+RpNKnTiJDrJUwoBu77G3PlKOsZZQHCNyD28WsQpl9v2WcJLbQudqwPg=="],
<<<<<<< HEAD
"@reduxjs/toolkit": ["@reduxjs/toolkit@2.9.0", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^10.0.3", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-fSfQlSRu9Z5yBkvsNhYF2rPS8cGXn/TZVrlwN1948QyZ8xMZ0JvP50S2acZNaf+o63u6aEeMjipFyksjIcWrog=="],
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.4", "", { "os": "android", "cpu": "arm" }, "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.4", "", { "os": "android", "cpu": "arm64" }, "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.52.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.52.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.52.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.52.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.52.4", "", { "os": "linux", "cpu": "arm" }, "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.52.4", "", { "os": "linux", "cpu": "arm" }, "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.52.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.52.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.52.4", "", { "os": "linux", "cpu": "none" }, "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.52.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.52.4", "", { "os": "linux", "cpu": "none" }, "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.52.4", "", { "os": "linux", "cpu": "none" }, "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.52.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.52.4", "", { "os": "linux", "cpu": "x64" }, "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.52.4", "", { "os": "linux", "cpu": "x64" }, "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.52.4", "", { "os": "none", "cpu": "arm64" }, "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.52.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.52.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.52.4", "", { "os": "win32", "cpu": "x64" }, "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.4", "", { "os": "win32", "cpu": "x64" }, "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w=="],
=======
"@reduxjs/toolkit": ["@reduxjs/toolkit@2.9.1", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^10.0.3", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-sETJ3qO72y7L7WiR5K54UFLT3jRzAtqeBPVO15xC3bGA6kDqCH8m/v7BKCPH4czydXzz/1lPEGLvew7GjOO3Qw=="],
"@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.27", "", {}, "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA=="],
@@ -1261,7 +1108,6 @@
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.52.5", "", { "os": "win32", "cpu": "x64" }, "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.52.5", "", { "os": "win32", "cpu": "x64" }, "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg=="],
>>>>>>> staging
"@selderee/plugin-htmlparser2": ["@selderee/plugin-htmlparser2@0.11.0", "", { "dependencies": { "domhandler": "^5.0.3", "selderee": "^0.11.0" } }, "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ=="],
@@ -1295,23 +1141,6 @@
"@standard-schema/utils": ["@standard-schema/utils@0.3.0", "", {}, "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g=="],
<<<<<<< HEAD
"@supabase/auth-js": ["@supabase/auth-js@2.75.0", "", { "dependencies": { "@supabase/node-fetch": "2.6.15" } }, "sha512-J8TkeqCOMCV4KwGKVoxmEBuDdHRwoInML2vJilthOo7awVCro2SM+tOcpljORwuBQ1vHUtV62Leit+5wlxrNtw=="],
"@supabase/functions-js": ["@supabase/functions-js@2.75.0", "", { "dependencies": { "@supabase/node-fetch": "2.6.15" } }, "sha512-18yk07Moj/xtQ28zkqswxDavXC3vbOwt1hDuYM3/7xPnwwpKnsmPyZ7bQ5th4uqiJzQ135t74La9tuaxBR6e7w=="],
"@supabase/node-fetch": ["@supabase/node-fetch@2.6.15", "", { "dependencies": { "whatwg-url": "^5.0.0" } }, "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ=="],
"@supabase/postgrest-js": ["@supabase/postgrest-js@2.75.0", "", { "dependencies": { "@supabase/node-fetch": "2.6.15" } }, "sha512-YfBz4W/z7eYCFyuvHhfjOTTzRrQIvsMG2bVwJAKEVVUqGdzqfvyidXssLBG0Fqlql1zJFgtsPpK1n4meHrI7tg=="],
"@supabase/realtime-js": ["@supabase/realtime-js@2.75.0", "", { "dependencies": { "@supabase/node-fetch": "2.6.15", "@types/phoenix": "^1.6.6", "@types/ws": "^8.18.1", "ws": "^8.18.2" } }, "sha512-B4Xxsf2NHd5cEnM6MGswOSPSsZKljkYXpvzKKmNxoUmNQOfB7D8HOa6NwHcUBSlxcjV+vIrYKcYXtavGJqeGrw=="],
"@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.75.0", "", { "dependencies": { "@supabase/node-fetch": "2.6.15" } }, "sha512-wpJMYdfFDckDiHQaTpK+Ib14N/O2o0AAWWhguKvmmMurB6Unx17GGmYp5rrrqCTf8S1qq4IfIxTXxS4hzrUySg=="],
"@supabase/supabase-js": ["@supabase/supabase-js@2.75.0", "", { "dependencies": { "@supabase/auth-js": "2.75.0", "@supabase/functions-js": "2.75.0", "@supabase/node-fetch": "2.6.15", "@supabase/postgrest-js": "2.75.0", "@supabase/realtime-js": "2.75.0", "@supabase/storage-js": "2.75.0" } }, "sha512-8UN/vATSgS2JFuJlMVr51L3eUDz+j1m7Ww63wlvHLKULzCDaVWYzvacCjBTLW/lX/vedI2LBI4Vg+01G9ufsJQ=="],
=======
"@supabase/auth-js": ["@supabase/auth-js@2.75.1", "", { "dependencies": { "@supabase/node-fetch": "2.6.15" } }, "sha512-zktlxtXstQuVys/egDpVsargD9hQtG20CMdtn+mMn7d2Ulkzy2tgUT5FUtpppvCJtd9CkhPHO/73rvi5W6Am5A=="],
"@supabase/functions-js": ["@supabase/functions-js@2.75.1", "", { "dependencies": { "@supabase/node-fetch": "2.6.15" } }, "sha512-xO+01SUcwVmmo67J7Htxq8FmhkYLFdWkxfR/taxBOI36wACEUNQZmroXGPl4PkpYxBO7TaDsRHYGxUpv9zTKkg=="],
@@ -1327,7 +1156,6 @@
"@supabase/storage-js": ["@supabase/storage-js@2.75.1", "", { "dependencies": { "@supabase/node-fetch": "2.6.15" } }, "sha512-WdGEhroflt5O398Yg3dpf1uKZZ6N3CGloY9iGsdT873uWbkQKoP0wG8mtx98dh0fhj6dAlzBqOAvnlV12cJfzA=="],
"@supabase/supabase-js": ["@supabase/supabase-js@2.75.1", "", { "dependencies": { "@supabase/auth-js": "2.75.1", "@supabase/functions-js": "2.75.1", "@supabase/node-fetch": "2.6.15", "@supabase/postgrest-js": "2.75.1", "@supabase/realtime-js": "2.75.1", "@supabase/storage-js": "2.75.1" } }, "sha512-GEPVBvjQimcMd9z5K1eTKTixTRb6oVbudoLQ9JKqTUJnR6GQdBU4OifFZean1AnHfsQwtri1fop2OWwsMv019w=="],
>>>>>>> staging
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
@@ -1361,19 +1189,11 @@
"@tailwindcss/vite": ["@tailwindcss/vite@4.1.14", "", { "dependencies": { "@tailwindcss/node": "4.1.14", "@tailwindcss/oxide": "4.1.14", "tailwindcss": "4.1.14" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" } }, "sha512-BoFUoU0XqgCUS1UXWhmDJroKKhNXeDzD7/XwabjkDIAbMnc4ULn5e2FuEuBbhZ6ENZoSYzKlzvZ44Yr6EUDUSA=="],
<<<<<<< HEAD
"@tanstack/query-core": ["@tanstack/query-core@5.90.3", "", {}, "sha512-HtPOnCwmx4dd35PfXU8jjkhwYrsHfuqgC8RCJIwWglmhIUIlzPP0ZcEkDAc+UtAWCiLm7T8rxeEfHZlz3hYMCA=="],
"@tanstack/query-devtools": ["@tanstack/query-devtools@5.90.1", "", {}, "sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ=="],
"@tanstack/react-query": ["@tanstack/react-query@5.90.3", "", { "dependencies": { "@tanstack/query-core": "5.90.3" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-i/LRL6DtuhG6bjGzavIMIVuKKPWx2AnEBIsBfuMm3YoHne0a20nWmsatOCBcVSaT0/8/5YFjNkebHAPLVUSi0Q=="],
=======
"@tanstack/query-core": ["@tanstack/query-core@5.90.5", "", {}, "sha512-wLamYp7FaDq6ZnNehypKI5fNvxHPfTYylE0m/ZpuuzJfJqhR5Pxg9gvGBHZx4n7J+V5Rg5mZxHHTlv25Zt5u+w=="],
"@tanstack/query-devtools": ["@tanstack/query-devtools@5.90.1", "", {}, "sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ=="],
"@tanstack/react-query": ["@tanstack/react-query@5.90.5", "", { "dependencies": { "@tanstack/query-core": "5.90.5" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-pN+8UWpxZkEJ/Rnnj2v2Sxpx1WFlaa9L6a4UO89p6tTQbeo+m0MS8oYDjbggrR8QcTyjKoYWKS3xJQGr3ExT8Q=="],
>>>>>>> staging
"@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.90.2", "", { "dependencies": { "@tanstack/query-devtools": "5.90.1" }, "peerDependencies": { "@tanstack/react-query": "^5.90.2", "react": "^18 || ^19" } }, "sha512-vAXJzZuBXtCQtrY3F/yUNJCV4obT/A/n81kb3+YqLbro5Z2+phdAbceO+deU3ywPw8B42oyJlp4FhO0SoivDFQ=="],
@@ -1423,11 +1243,8 @@
"@types/cors": ["@types/cors@2.8.19", "", { "dependencies": { "@types/node": "*" } }, "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg=="],
<<<<<<< HEAD
=======
"@types/css-font-loading-module": ["@types/css-font-loading-module@0.0.7", "", {}, "sha512-nl09VhutdjINdWyXxHWN/w9zlNCfr60JUqJbd24YXUuCwgeL0TpFSdElCwb6cxfB6ybE19Gjj4g0jsgkXxKv1Q=="],
>>>>>>> staging
"@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="],
"@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="],
@@ -1486,11 +1303,7 @@
"@types/mysql": ["@types/mysql@2.15.27", "", { "dependencies": { "@types/node": "*" } }, "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA=="],
<<<<<<< HEAD
"@types/node": ["@types/node@24.7.2", "", { "dependencies": { "undici-types": "~7.14.0" } }, "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA=="],
=======
"@types/node": ["@types/node@24.8.1", "", { "dependencies": { "undici-types": "~7.14.0" } }, "sha512-alv65KGRadQVfVcG69MuB4IzdYVpRwMG/mq8KWOaoOdyY617P5ivaDiMCGOFDWD2sAn5Q0mR3mRtUOgm99hL9Q=="],
>>>>>>> staging
"@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
@@ -1522,7 +1335,7 @@
"@types/semver": ["@types/semver@7.7.1", "", {}, "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA=="],
"@types/send": ["@types/send@1.2.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ=="],
"@types/send": ["@types/send@0.17.5", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w=="],
"@types/serve-static": ["@types/serve-static@1.15.9", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "<1" } }, "sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA=="],
@@ -1566,23 +1379,6 @@
"@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.46.1", "", { "dependencies": { "@typescript-eslint/types": "8.46.1", "eslint-visitor-keys": "^4.2.1" } }, "sha512-ptkmIf2iDkNUjdeu2bQqhFPV1m6qTnFFjg7PPDjxKWaMaP0Z6I9l30Jr3g5QqbZGdw8YdYvLp+XnqnWWZOg/NA=="],
<<<<<<< HEAD
"@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20251014.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20251014.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20251014.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20251014.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20251014.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20251014.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20251014.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20251014.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-IqmX5CYCBqXbfL+HKlcQAMaDlfJ0Z8OhUxvADFV2TENnzSYI4CuhvKxwOB2wFSLXufVsgtAlf3Fjwn24KmMyPQ=="],
"@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20251014.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-7rQoLlerWnwnvrM56hP4rdEbo4xDE4zr7cch+EzgENq/tbXYereGq1fmnR83UNglb1Eyy53OvJZ3O2csYBa2vg=="],
"@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20251014.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-SF29o9NFRGDM23Jz0nVO4/yS78GQ81rtOemmCVNXuJotoY4bP3npGDyEmfkZQHZgDOXogs2OWy3t7NUJ235ANQ=="],
"@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20251014.1", "", { "os": "linux", "cpu": "arm" }, "sha512-o5cu7h+BBAp6V4qxYY5RWuaYouN3j+MGFLrrUtvvNj4XKM+kbq5qwsgVRsmJZ1LfUvHmzyQs86vt9djAWedzjQ=="],
"@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20251014.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-+YWbW/JF4uggEUBr+vflqI5i7bL4Z3XInCOyUO1qQEY7VmfDCsPEzIwGi37O1mixfxw9Qj8LQsptCkU+fqKwGw=="],
"@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20251014.1", "", { "os": "linux", "cpu": "x64" }, "sha512-3LC4tgcgi6zWJWBUpBNXOGSY3yISJrQezSP/T+v+mQRApkdoIpTSHIyQAhgaagcs3MOQRaqiIPaLOVrdHXdU6A=="],
"@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20251014.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-P0D4UEXwzFZh3pHexe2Ky1tW/HjY/HxTBTIajz2ViDCNPw7uDSEsXSB4H9TTiFJw8gVdTUFbsoAQp1MteTeORA=="],
"@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20251014.1", "", { "os": "win32", "cpu": "x64" }, "sha512-fi53g2ihH7tkQLlz8hZGAb2V+3aNZpcxrZ530CQ4xcWwAqssEj0EaZJX0VLEtIQBar1ttGVK9Pz/wJU9sYyVzg=="],
=======
"@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20251019.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20251019.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20251019.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20251019.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20251019.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20251019.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20251019.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20251019.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-ytCPJouuNmJyGjZwSFg/v0Ugkn/52drU5HymW1p0l6dU+iHuTIaZSKfHFWETJxQVwyyYqNxxvC0QMxTDfwPlGQ=="],
"@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20251019.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-GecLPUXgaptUiBrpuLhKwsxsckJ/rBA1e9pY2HdFx+mIWze1FTUiXu0It6EcFbQ2IZCMke1WuZZz18Bo4lftwA=="],
@@ -1598,7 +1394,6 @@
"@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20251019.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-vJvkjZEN6GRH+Y3atQO5t8WGGjqgnHzwU0ZP+4oqYJl7G6sNiqRw23lHedZxZF6Iav+lE6SPMAhVbz97LlvkbQ=="],
"@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20251019.1", "", { "os": "win32", "cpu": "x64" }, "sha512-GMYGYxRHIX/+hFn7SGj9LMh4CLm90ZByGH4BvgKvwJGEctkYtOB6wXJUvQMo5koel7pDY1Yy3uChiexuG11biQ=="],
>>>>>>> staging
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
@@ -1686,11 +1481,7 @@
"bare-events": ["bare-events@2.8.0", "", { "peerDependencies": { "bare-abort-controller": "*" }, "optionalPeers": ["bare-abort-controller"] }, "sha512-AOhh6Bg5QmFIXdViHbMc2tLDsBIRxdkIaIddPslJF9Z5De3APBScuqGP2uThXnIpqFrgoxMNC6km7uXNIMLHXA=="],
<<<<<<< HEAD
"bare-fs": ["bare-fs@4.4.10", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-arqVF+xX/rJHwrONZaSPhlzleT2gXwVs9rsAe1p1mIVwWZI2A76/raio+KwwxfWMO8oV9Wo90EaUkS2QwVmy4w=="],
=======
"bare-fs": ["bare-fs@4.4.11", "", { "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", "bare-stream": "^2.6.4", "bare-url": "^2.2.2", "fast-fifo": "^1.3.2" }, "peerDependencies": { "bare-buffer": "*" }, "optionalPeers": ["bare-buffer"] }, "sha512-Bejmm9zRMvMTRoHS+2adgmXw1ANZnCNx+B5dgZpGwlP1E3x6Yuxea8RToddHUbWtVV0iUMWqsgZr8+jcgUI2SA=="],
>>>>>>> staging
"bare-os": ["bare-os@3.6.2", "", {}, "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A=="],
@@ -1708,15 +1499,6 @@
"base64id": ["base64id@2.0.0", "", {}, "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog=="],
<<<<<<< HEAD
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.16", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw=="],
"basic-ftp": ["basic-ftp@5.0.5", "", {}, "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg=="],
"better-auth": ["better-auth@1.3.27", "", { "dependencies": { "@better-auth/core": "1.3.27", "@better-auth/utils": "0.3.0", "@better-fetch/fetch": "1.1.18", "@noble/ciphers": "^2.0.0", "@noble/hashes": "^2.0.0", "@simplewebauthn/browser": "^13.1.2", "@simplewebauthn/server": "^13.1.2", "better-call": "1.0.19", "defu": "^6.1.4", "jose": "^6.1.0", "kysely": "^0.28.5", "nanostores": "^1.0.1", "zod": "^4.1.5" } }, "sha512-SwiGAJ7yU6dBhNg0NdV1h5M8T5sa7/AszZVc4vBfMDrLLmvUfbt9JoJ0uRUJUEdKRAAxTyl9yA+F3+GhtAD80w=="],
"better-call": ["better-call@1.0.19", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.5.1", "set-cookie-parser": "^2.7.1", "uncrypto": "^0.1.3" } }, "sha512-sI3GcA1SCVa3H+CDHl8W8qzhlrckwXOTKhqq3OOPXjgn5aTOMIqGY34zLY/pHA6tRRMjTUC3lz5Mi7EbDA24Kw=="],
=======
"baseline-browser-mapping": ["baseline-browser-mapping@2.8.18", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-UYmTpOBwgPScZpS4A+YbapwWuBwasxvO/2IOHArSsAhL/+ZdmATBXTex3t+l2hXwLVYK382ibr/nKoY9GKe86w=="],
"basic-ftp": ["basic-ftp@5.0.5", "", {}, "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg=="],
@@ -1726,7 +1508,6 @@
"better-call": ["better-call@1.0.19", "", { "dependencies": { "@better-auth/utils": "^0.3.0", "@better-fetch/fetch": "^1.1.4", "rou3": "^0.5.1", "set-cookie-parser": "^2.7.1", "uncrypto": "^0.1.3" } }, "sha512-sI3GcA1SCVa3H+CDHl8W8qzhlrckwXOTKhqq3OOPXjgn5aTOMIqGY34zLY/pHA6tRRMjTUC3lz5Mi7EbDA24Kw=="],
"better-sqlite3": ["better-sqlite3@12.4.1", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-3yVdyZhklTiNrtg+4WqHpJpFDd+WHTg2oM7UcR80GqL05AOV0xEJzc6qNvFYoEtE+hRp1n9MpN6/+4yhlGkDXQ=="],
>>>>>>> staging
"bignumber.js": ["bignumber.js@9.3.1", "", {}, "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ=="],
@@ -1770,11 +1551,7 @@
"camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
<<<<<<< HEAD
"caniuse-lite": ["caniuse-lite@1.0.30001750", "", {}, "sha512-cuom0g5sdX6rw00qOoLNSFCJ9/mYIsuSOA+yzpDw8eopiFqcVwQvZHqov0vmEighRxX++cfC0Vg1G+1Iy/mSpQ=="],
=======
"caniuse-lite": ["caniuse-lite@1.0.30001751", "", {}, "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw=="],
>>>>>>> staging
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
@@ -1854,11 +1631,7 @@
"convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
<<<<<<< HEAD
"convex": ["convex@1.27.5", "", { "dependencies": { "esbuild": "0.25.4", "prettier": "^3.0.0" }, "peerDependencies": { "@auth0/auth0-react": "^2.0.1", "@clerk/clerk-react": "^4.12.8 || ^5.0.0", "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" }, "optionalPeers": ["@auth0/auth0-react", "@clerk/clerk-react", "react"], "bin": { "convex": "bin/main.js" } }, "sha512-6YU/AVPnoNdAaJABKBI9c5IqRSKsow/c4yo/ntaOWtd8Dff2P2zaImA/ougICfPgTuTvjKRbgkxk6lJhODzb4g=="],
=======
"convex": ["convex@1.28.0", "", { "dependencies": { "esbuild": "0.25.4", "prettier": "^3.0.0" }, "peerDependencies": { "@auth0/auth0-react": "^2.0.1", "@clerk/clerk-react": "^4.12.8 || ^5.0.0", "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" }, "optionalPeers": ["@auth0/auth0-react", "@clerk/clerk-react", "react"], "bin": { "convex": "bin/main.js" } }, "sha512-40FgeJ/LxP9TxnkDDztU/A5gcGTdq1klcTT5mM0Ak+kSlQiDktMpjNX1TfkWLxXaE3lI4qvawKH95v2RiYgFxA=="],
>>>>>>> staging
"cookie": ["cookie@0.7.1", "", {}, "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w=="],
@@ -1936,11 +1709,8 @@
"decode-uri-component": ["decode-uri-component@0.4.1", "", {}, "sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ=="],
<<<<<<< HEAD
=======
"decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="],
>>>>>>> staging
"deep-eql": ["deep-eql@5.0.2", "", {}, "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q=="],
"deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="],
@@ -2011,11 +1781,7 @@
"ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
<<<<<<< HEAD
"electron-to-chromium": ["electron-to-chromium@1.5.235", "", {}, "sha512-i/7ntLFwOdoHY7sgjlTIDo4Sl8EdoTjWIaKinYOVfC6bOp71bmwenyZthWHcasxgHDNWbWxvG9M3Ia116zIaYQ=="],
=======
"electron-to-chromium": ["electron-to-chromium@1.5.237", "", {}, "sha512-icUt1NvfhGLar5lSWH3tHNzablaA5js3HVHacQimfP8ViEBOQv+L7DKEuHdbTZ0SKCO1ogTJTIL1Gwk9S6Qvcg=="],
>>>>>>> staging
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
@@ -2043,11 +1809,7 @@
"es-toolkit": ["es-toolkit@1.40.0", "", {}, "sha512-8o6w0KFmU0CiIl0/Q/BCEOabF2IJaELM1T2PWj6e8KqzHv1gdx+7JtFnDwOx1kJH/isJ5NwlDG1nCr1HrRF94Q=="],
<<<<<<< HEAD
"esbuild": ["esbuild@0.25.10", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.10", "@esbuild/android-arm": "0.25.10", "@esbuild/android-arm64": "0.25.10", "@esbuild/android-x64": "0.25.10", "@esbuild/darwin-arm64": "0.25.10", "@esbuild/darwin-x64": "0.25.10", "@esbuild/freebsd-arm64": "0.25.10", "@esbuild/freebsd-x64": "0.25.10", "@esbuild/linux-arm": "0.25.10", "@esbuild/linux-arm64": "0.25.10", "@esbuild/linux-ia32": "0.25.10", "@esbuild/linux-loong64": "0.25.10", "@esbuild/linux-mips64el": "0.25.10", "@esbuild/linux-ppc64": "0.25.10", "@esbuild/linux-riscv64": "0.25.10", "@esbuild/linux-s390x": "0.25.10", "@esbuild/linux-x64": "0.25.10", "@esbuild/netbsd-arm64": "0.25.10", "@esbuild/netbsd-x64": "0.25.10", "@esbuild/openbsd-arm64": "0.25.10", "@esbuild/openbsd-x64": "0.25.10", "@esbuild/openharmony-arm64": "0.25.10", "@esbuild/sunos-x64": "0.25.10", "@esbuild/win32-arm64": "0.25.10", "@esbuild/win32-ia32": "0.25.10", "@esbuild/win32-x64": "0.25.10" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ=="],
=======
"esbuild": ["esbuild@0.25.11", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.11", "@esbuild/android-arm": "0.25.11", "@esbuild/android-arm64": "0.25.11", "@esbuild/android-x64": "0.25.11", "@esbuild/darwin-arm64": "0.25.11", "@esbuild/darwin-x64": "0.25.11", "@esbuild/freebsd-arm64": "0.25.11", "@esbuild/freebsd-x64": "0.25.11", "@esbuild/linux-arm": "0.25.11", "@esbuild/linux-arm64": "0.25.11", "@esbuild/linux-ia32": "0.25.11", "@esbuild/linux-loong64": "0.25.11", "@esbuild/linux-mips64el": "0.25.11", "@esbuild/linux-ppc64": "0.25.11", "@esbuild/linux-riscv64": "0.25.11", "@esbuild/linux-s390x": "0.25.11", "@esbuild/linux-x64": "0.25.11", "@esbuild/netbsd-arm64": "0.25.11", "@esbuild/netbsd-x64": "0.25.11", "@esbuild/openbsd-arm64": "0.25.11", "@esbuild/openbsd-x64": "0.25.11", "@esbuild/openharmony-arm64": "0.25.11", "@esbuild/sunos-x64": "0.25.11", "@esbuild/win32-arm64": "0.25.11", "@esbuild/win32-ia32": "0.25.11", "@esbuild/win32-x64": "0.25.11" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q=="],
>>>>>>> staging
"esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="],
@@ -2059,19 +1821,11 @@
"escodegen": ["escodegen@2.1.0", "", { "dependencies": { "esprima": "^4.0.1", "estraverse": "^5.2.0", "esutils": "^2.0.2" }, "optionalDependencies": { "source-map": "~0.6.1" }, "bin": { "esgenerate": "bin/esgenerate.js", "escodegen": "bin/escodegen.js" } }, "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w=="],
<<<<<<< HEAD
"eslint": ["eslint@9.37.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.4.0", "@eslint/core": "^0.16.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.37.0", "@eslint/plugin-kit": "^0.4.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig=="],
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="],
"eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.23", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-G4j+rv0NmbIR45kni5xJOrYvCtyD3/7LjpVH8MPPcudXDcNu8gv+4ATTDXTtbRR8rTCM5HxECvCSsRmxKnWDsA=="],
=======
"eslint": ["eslint@9.38.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.1", "@eslint/config-helpers": "^0.4.1", "@eslint/core": "^0.16.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.38.0", "@eslint/plugin-kit": "^0.4.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw=="],
"eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="],
"eslint-plugin-react-refresh": ["eslint-plugin-react-refresh@0.4.24", "", { "peerDependencies": { "eslint": ">=8.40" } }, "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w=="],
>>>>>>> staging
"eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="],
@@ -2101,11 +1855,8 @@
"events-universal": ["events-universal@1.0.1", "", { "dependencies": { "bare-events": "^2.7.0" } }, "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw=="],
<<<<<<< HEAD
=======
"expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="],
>>>>>>> staging
"express": ["express@4.21.2", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA=="],
"express-rate-limit": ["express-rate-limit@7.5.1", "", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="],
@@ -2254,11 +2005,7 @@
"hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="],
<<<<<<< HEAD
"hono": ["hono@4.9.12", "", {}, "sha512-SrTC0YxqPwnN7yKa8gg/giLyQ2pILCKoideIHbYbFQlWZjYt68D2A4Ae1hehO/aDQ6RmTcpqOV/O2yBtMzx/VQ=="],
=======
"hono": ["hono@4.10.1", "", {}, "sha512-rpGNOfacO4WEPClfkEt1yfl8cbu10uB1lNpiI33AKoiAHwOS8lV748JiLx4b5ozO/u4qLjIvfpFsPXdY5Qjkmg=="],
>>>>>>> staging
"html-minifier-terser": ["html-minifier-terser@6.1.0", "", { "dependencies": { "camel-case": "^4.1.2", "clean-css": "^5.2.2", "commander": "^8.3.0", "he": "^1.2.0", "param-case": "^3.0.4", "relateurl": "^0.2.7", "terser": "^5.10.0" }, "bin": { "html-minifier-terser": "cli.js" } }, "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw=="],
@@ -2546,11 +2293,8 @@
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
<<<<<<< HEAD
=======
"mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="],
>>>>>>> staging
"mocha": ["mocha@11.7.4", "", { "dependencies": { "browser-stdout": "^1.3.1", "chokidar": "^4.0.1", "debug": "^4.3.5", "diff": "^7.0.0", "escape-string-regexp": "^4.0.0", "find-up": "^5.0.0", "glob": "^10.4.5", "he": "^1.2.0", "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", "minimatch": "^9.0.5", "ms": "^2.1.3", "picocolors": "^1.1.1", "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", "workerpool": "^9.2.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1", "yargs-unparser": "^2.0.0" }, "bin": { "mocha": "bin/mocha.js", "_mocha": "bin/_mocha" } }, "sha512-1jYAaY8x0kAZ0XszLWu14pzsf4KV740Gld4HXkhNTXwcHx4AUEDkPzgEHg9CM5dVcW+zv036tjpsEbLraPJj4w=="],
"module-details-from-path": ["module-details-from-path@1.0.4", "", {}, "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w=="],
@@ -2574,11 +2318,8 @@
"nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="],
"nanostores": ["nanostores@1.0.1", "", {}, "sha512-kNZ9xnoJYKg/AfxjrVL4SS0fKX++4awQReGqWnwTRHxeHGZ1FJFVgTqr/eMrNQdp0Tz7M7tG/TDaX8QfHDwVCw=="],
<<<<<<< HEAD
=======
"napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="],
>>>>>>> staging
"natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="],
@@ -2586,21 +2327,14 @@
"netmask": ["netmask@2.0.2", "", {}, "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg=="],
<<<<<<< HEAD
"next": ["next@15.5.5", "", { "dependencies": { "@next/env": "15.5.5", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.5", "@next/swc-darwin-x64": "15.5.5", "@next/swc-linux-arm64-gnu": "15.5.5", "@next/swc-linux-arm64-musl": "15.5.5", "@next/swc-linux-x64-gnu": "15.5.5", "@next/swc-linux-x64-musl": "15.5.5", "@next/swc-win32-arm64-msvc": "15.5.5", "@next/swc-win32-x64-msvc": "15.5.5", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-OQVdBPtpBfq7HxFN0kOVb7rXXOSIkt5lTzDJDGRBcOyVvNRIWFauMqi1gIHd1pszq1542vMOGY0HP4CaiALfkA=="],
=======
"next": ["next@15.5.6", "", { "dependencies": { "@next/env": "15.5.6", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.6", "@next/swc-darwin-x64": "15.5.6", "@next/swc-linux-arm64-gnu": "15.5.6", "@next/swc-linux-arm64-musl": "15.5.6", "@next/swc-linux-x64-gnu": "15.5.6", "@next/swc-linux-x64-musl": "15.5.6", "@next/swc-win32-arm64-msvc": "15.5.6", "@next/swc-win32-x64-msvc": "15.5.6", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-zTxsnI3LQo3c9HSdSf91O1jMNsEzIXDShXd4wVdg9y5shwLqBXi4ZtUUJyB86KGVSJLZx0PFONvO54aheGX8QQ=="],
>>>>>>> staging
"next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="],
"no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="],
<<<<<<< HEAD
=======
"node-abi": ["node-abi@3.78.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-E2wEyrgX/CqvicaQYU3Ze1PFGjc4QYPGsjUrlYkqAE0WjHEZwgOsGMPMzkMse4LjJbDmaEuDX3CM036j5K2DSQ=="],
>>>>>>> staging
"node-abort-controller": ["node-abort-controller@3.1.1", "", {}, "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ=="],
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
@@ -2609,11 +2343,7 @@
"node-gyp-build-optional-packages": ["node-gyp-build-optional-packages@5.2.2", "", { "dependencies": { "detect-libc": "^2.0.1" }, "bin": { "node-gyp-build-optional-packages": "bin.js", "node-gyp-build-optional-packages-optional": "optional.js", "node-gyp-build-optional-packages-test": "build-test.js" } }, "sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw=="],
<<<<<<< HEAD
"node-releases": ["node-releases@2.0.23", "", {}, "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg=="],
=======
"node-releases": ["node-releases@2.0.25", "", {}, "sha512-4auku8B/vw5psvTiiN9j1dAOsXvMoGqJuKJcR+dTdqiXEK20mMTk1UEo3HS16LeGQsVG6+qKTPM9u/qQ2LqATA=="],
>>>>>>> staging
"nodemon": ["nodemon@3.1.10", "", { "dependencies": { "chokidar": "^3.5.2", "debug": "^4", "ignore-by-default": "^1.0.1", "minimatch": "^3.1.2", "pstree.remy": "^1.1.8", "semver": "^7.5.3", "simple-update-notifier": "^2.0.0", "supports-color": "^5.5.0", "touch": "^3.1.0", "undefsafe": "^2.0.5" }, "bin": { "nodemon": "bin/nodemon.js" } }, "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw=="],
@@ -2621,11 +2351,7 @@
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
<<<<<<< HEAD
"nuqs": ["nuqs@2.7.1", "", { "dependencies": { "@standard-schema/spec": "1.0.0" }, "peerDependencies": { "@remix-run/react": ">=2", "@tanstack/react-router": "^1", "next": ">=14.2.0", "react": ">=18.2.0 || ^19.0.0-0", "react-router": "^6 || ^7", "react-router-dom": "^6 || ^7" }, "optionalPeers": ["@remix-run/react", "@tanstack/react-router", "next", "react-router", "react-router-dom"] }, "sha512-3WDgrOZWat0QyOheyljTlXK4TGFh1JKSLvXMgusMDcTyMJXe1xL8+q3zuQ6ke1vyeGnpJwztlZl2aDkMW2eIUg=="],
=======
"nuqs": ["nuqs@2.7.2", "", { "dependencies": { "@standard-schema/spec": "1.0.0" }, "peerDependencies": { "@remix-run/react": ">=2", "@tanstack/react-router": "^1", "next": ">=14.2.0", "react": ">=18.2.0 || ^19.0.0-0", "react-router": "^6 || ^7", "react-router-dom": "^6 || ^7" }, "optionalPeers": ["@remix-run/react", "@tanstack/react-router", "next", "react-router", "react-router-dom"] }, "sha512-wOPJoz5om7jMJQick9zU1S/Q+joL+B2DZTZxfCleHEcUzjUnPoujGod4+nAmUWb+G9TwZnyv+mfNqlyfEi8Zag=="],
>>>>>>> staging
"object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
@@ -2705,11 +2431,7 @@
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
<<<<<<< HEAD
"pino": ["pino@9.13.1", "", { "dependencies": { "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "slow-redact": "^0.3.0", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-Szuj+ViDTjKPQYiKumGmEn3frdl+ZPSdosHyt9SnUevFosOkMY2b7ipxlEctNKPmMD/VibeBI+ZcZCJK+4DPuw=="],
=======
"pino": ["pino@9.14.0", "", { "dependencies": { "@pinojs/redact": "^0.4.0", "atomic-sleep": "^1.0.0", "on-exit-leak-free": "^2.1.0", "pino-abstract-transport": "^2.0.0", "pino-std-serializers": "^7.0.0", "process-warning": "^5.0.0", "quick-format-unescaped": "^4.0.3", "real-require": "^0.2.0", "safe-stable-stringify": "^2.3.1", "sonic-boom": "^4.0.1", "thread-stream": "^3.0.0" }, "bin": { "pino": "bin.js" } }, "sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w=="],
>>>>>>> staging
"pino-abstract-transport": ["pino-abstract-transport@1.2.0", "", { "dependencies": { "readable-stream": "^4.0.0", "split2": "^4.0.0" } }, "sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q=="],
@@ -2731,20 +2453,13 @@
"postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
<<<<<<< HEAD
"posthog-js": ["posthog-js@1.275.3", "", { "dependencies": { "@posthog/core": "1.3.0", "core-js": "^3.38.1", "fflate": "^0.4.8", "preact": "^10.19.3", "web-vitals": "^4.2.4" }, "peerDependencies": { "@rrweb/types": "2.0.0-alpha.17", "rrweb-snapshot": "2.0.0-alpha.17" }, "optionalPeers": ["@rrweb/types", "rrweb-snapshot"] }, "sha512-LitwVprl0Q8p0fN4O4ThvlOuO6r+TBzLfkGbSyI5tR/YhlWzX3yFf4KKHPXJgxge99sxKa0fuVKNzcspYsDzcg=="],
=======
"posthog-js": ["posthog-js@1.276.0", "", { "dependencies": { "@posthog/core": "1.3.0", "core-js": "^3.38.1", "fflate": "^0.4.8", "preact": "^10.19.3", "web-vitals": "^4.2.4" }, "peerDependencies": { "@rrweb/types": "2.0.0-alpha.17", "rrweb-snapshot": "2.0.0-alpha.17" }, "optionalPeers": ["@rrweb/types", "rrweb-snapshot"] }, "sha512-FYZE1037LrAoKKeUU0pUL7u8WwNK2BVeg5TFApwquVPUdj9h7u5Z077A313hPN19Ar+7Y+VHxqYqdHc4VNsVgw=="],
>>>>>>> staging
"posthog-node": ["posthog-node@4.18.0", "", { "dependencies": { "axios": "^1.8.2" } }, "sha512-XROs1h+DNatgKh/AlIlCtDxWzwrKdYDb2mOs58n4yN8BkGN9ewqeQwG5ApS4/IzwCb7HPttUkOVulkYatd2PIw=="],
"preact": ["preact@10.27.2", "", {}, "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg=="],
<<<<<<< HEAD
=======
"prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="],
>>>>>>> staging
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
@@ -2780,11 +2495,7 @@
"punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
<<<<<<< HEAD
"puppeteer-core": ["puppeteer-core@24.24.1", "", { "dependencies": { "@puppeteer/browsers": "2.10.12", "chromium-bidi": "9.1.0", "debug": "^4.4.3", "devtools-protocol": "0.0.1508733", "typed-query-selector": "^2.12.0", "webdriver-bidi-protocol": "0.3.7", "ws": "^8.18.3" } }, "sha512-4R9/hCjmyUBbQqjrCa+y4Pzgl3LneLfqB+Whh2JujA5Wzg+prnO60GxDPjAJmM+uirYxDx/8jIm0hGu8yDTyiA=="],
=======
"puppeteer-core": ["puppeteer-core@24.25.0", "", { "dependencies": { "@puppeteer/browsers": "2.10.12", "chromium-bidi": "9.1.0", "debug": "^4.4.3", "devtools-protocol": "0.0.1508733", "typed-query-selector": "^2.12.0", "webdriver-bidi-protocol": "0.3.7", "ws": "^8.18.3" } }, "sha512-8Xs6q3Ut+C8y7sAaqjIhzv1QykGWG4gc2mEZ2mYE7siZFuRp4xQVehOf8uQKSQAkeL7jXUs3mknEeiqnRqUKvQ=="],
>>>>>>> staging
"pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="],
@@ -2856,11 +2567,7 @@
"recaseai": ["recaseai@0.0.37", "", { "dependencies": { "@anthropic-ai/sdk": "^0.32.1", "@supabase/supabase-js": "^2.47.2", "@types/cors": "^2.8.17", "@types/express": "^5.0.0", "@types/node": "^22.10.1", "async-listen": "^3.0.1", "axios": "^1.7.9", "commander": "^12.1.0", "cors": "^2.8.5", "dotenv": "^16.4.7", "express": "^4.21.2", "figures": "^6.1.0", "inquirer": "^12.1.0", "ksuid": "^3.0.0", "nanoid": "^5.0.9", "openai": "^4.76.0", "ora": "^8.1.1", "picocolors": "^1.1.1", "pino": "^9.5.0", "tsx": "^4.19.2", "typescript": "^5.7.2" }, "bin": { "recase": "dist/cli.js" } }, "sha512-cKVMWTGBnGtm8K+uD2vfMXOzxdHj1U3++vvTePpOoZABI/SY/jjDpW997vM5xeDnzz7Vk/qqVkLQUQ/ZMDXpsQ=="],
<<<<<<< HEAD
"recharts": ["recharts@3.2.1", "", { "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-0JKwHRiFZdmLq/6nmilxEZl3pqb4T+aKkOkOi/ZISRZwfBhVMgInxzlYU9D4KnCH3KINScLy68m/OvMXoYGZUw=="],
=======
"recharts": ["recharts@3.3.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-Vi0qmTB0iz1+/Cz9o5B7irVyUjX2ynvEgImbgMt/3sKRREcUM07QiYjS1QpAVrkmVlXqy5gykq4nGWMz9AS4Rg=="],
>>>>>>> staging
"redis-errors": ["redis-errors@1.2.0", "", {}, "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w=="],
@@ -2894,11 +2601,7 @@
"reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
<<<<<<< HEAD
"rollup": ["rollup@4.52.4", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.4", "@rollup/rollup-android-arm64": "4.52.4", "@rollup/rollup-darwin-arm64": "4.52.4", "@rollup/rollup-darwin-x64": "4.52.4", "@rollup/rollup-freebsd-arm64": "4.52.4", "@rollup/rollup-freebsd-x64": "4.52.4", "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", "@rollup/rollup-linux-arm-musleabihf": "4.52.4", "@rollup/rollup-linux-arm64-gnu": "4.52.4", "@rollup/rollup-linux-arm64-musl": "4.52.4", "@rollup/rollup-linux-loong64-gnu": "4.52.4", "@rollup/rollup-linux-ppc64-gnu": "4.52.4", "@rollup/rollup-linux-riscv64-gnu": "4.52.4", "@rollup/rollup-linux-riscv64-musl": "4.52.4", "@rollup/rollup-linux-s390x-gnu": "4.52.4", "@rollup/rollup-linux-x64-gnu": "4.52.4", "@rollup/rollup-linux-x64-musl": "4.52.4", "@rollup/rollup-openharmony-arm64": "4.52.4", "@rollup/rollup-win32-arm64-msvc": "4.52.4", "@rollup/rollup-win32-ia32-msvc": "4.52.4", "@rollup/rollup-win32-x64-gnu": "4.52.4", "@rollup/rollup-win32-x64-msvc": "4.52.4", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ=="],
=======
"rollup": ["rollup@4.52.5", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.52.5", "@rollup/rollup-android-arm64": "4.52.5", "@rollup/rollup-darwin-arm64": "4.52.5", "@rollup/rollup-darwin-x64": "4.52.5", "@rollup/rollup-freebsd-arm64": "4.52.5", "@rollup/rollup-freebsd-x64": "4.52.5", "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", "@rollup/rollup-linux-arm-musleabihf": "4.52.5", "@rollup/rollup-linux-arm64-gnu": "4.52.5", "@rollup/rollup-linux-arm64-musl": "4.52.5", "@rollup/rollup-linux-loong64-gnu": "4.52.5", "@rollup/rollup-linux-ppc64-gnu": "4.52.5", "@rollup/rollup-linux-riscv64-gnu": "4.52.5", "@rollup/rollup-linux-riscv64-musl": "4.52.5", "@rollup/rollup-linux-s390x-gnu": "4.52.5", "@rollup/rollup-linux-x64-gnu": "4.52.5", "@rollup/rollup-linux-x64-musl": "4.52.5", "@rollup/rollup-openharmony-arm64": "4.52.5", "@rollup/rollup-win32-arm64-msvc": "4.52.5", "@rollup/rollup-win32-ia32-msvc": "4.52.5", "@rollup/rollup-win32-x64-gnu": "4.52.5", "@rollup/rollup-win32-x64-msvc": "4.52.5", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw=="],
>>>>>>> staging
"rou3": ["rou3@0.6.3", "", {}, "sha512-1HSG1ENTj7Kkm5muMnXuzzfdDOf7CFnbSYFA+H3Fp/rB9lOCxCPgy1jlZxTKyFoC5jJay8Mmc+VbPLYRjzYLrA=="],
@@ -2956,19 +2659,14 @@
"signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="],
<<<<<<< HEAD
=======
"simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="],
"simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="],
>>>>>>> staging
"simple-update-notifier": ["simple-update-notifier@2.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w=="],
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
"slow-redact": ["slow-redact@0.3.2", "", {}, "sha512-MseHyi2+E/hBRqdOi5COy6wZ7j7DxXRz9NkseavNYSvvWC06D8a5cidVZX3tcG5eCW3NIyVU4zT63hw0Q486jw=="],
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
"socket.io": ["socket.io@4.8.1", "", { "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } }, "sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg=="],
@@ -3027,11 +2725,7 @@
"strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
<<<<<<< HEAD
"stripe": ["stripe@18.5.0", "", { "dependencies": { "qs": "^6.11.0" }, "peerDependencies": { "@types/node": ">=12.x.x" }, "optionalPeers": ["@types/node"] }, "sha512-Hp+wFiEQtCB0LlNgcFh5uVyKznpDjzyUZ+CNVEf+I3fhlYvh7rZruIg+jOwzJRCpy0ZTPMjlzm7J2/M2N6d+DA=="],
=======
"stripe": ["stripe@18.4.0-beta.2", "", { "dependencies": { "qs": "^6.11.0" }, "peerDependencies": { "@types/node": ">=12.x.x" }, "optionalPeers": ["@types/node"] }, "sha512-4MCaxGkwZcCpMpgiE+Wb4hWwKTlnZgUf4pTlvIolxdrYAA5gb6MnIEJJAowrcrSnl41FvjQP0xV4QvdN2Fq8Zw=="],
>>>>>>> staging
"styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],
@@ -3112,11 +2806,8 @@
"tsx": ["tsx@4.20.6", "", { "dependencies": { "esbuild": "~0.25.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-ytQKuwgmrrkDTFP4LjR0ToE2nqgy886GpvRSpU0JAnrdBYppuY5rLkRUYPU1yCryb24SsKBTL/hlDQAEFVwtZg=="],
"tsyringe": ["tsyringe@4.10.0", "", { "dependencies": { "tslib": "^1.9.3" } }, "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw=="],
<<<<<<< HEAD
=======
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
>>>>>>> staging
"type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
@@ -3180,11 +2871,7 @@
"victory-vendor": ["victory-vendor@37.3.6", "", { "dependencies": { "@types/d3-array": "^3.0.3", "@types/d3-ease": "^3.0.0", "@types/d3-interpolate": "^3.0.1", "@types/d3-scale": "^4.0.2", "@types/d3-shape": "^3.1.0", "@types/d3-time": "^3.0.0", "@types/d3-timer": "^3.0.0", "d3-array": "^3.1.6", "d3-ease": "^3.0.1", "d3-interpolate": "^3.0.1", "d3-scale": "^4.0.2", "d3-shape": "^3.1.0", "d3-time": "^3.0.0", "d3-timer": "^3.0.1" } }, "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ=="],
<<<<<<< HEAD
"vite": ["vite@6.3.7", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-mQYaKepA0NGMBsz8Xktt3tJUG5ELE2iT7IJ+ssXI6nxVdE2sFc/d/6w/JByqMLvWg8hNKHpPgzjgOkrhpKFnrA=="],
=======
"vite": ["vite@6.4.0", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-oLnWs9Hak/LOlKjeSpOwD6JMks8BeICEdYMJBf6P4Lac/pO9tKiv/XhXnAM7nNfSkZahjlCZu9sS50zL8fSnsw=="],
>>>>>>> staging
"vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="],
"vscode-oniguruma": ["vscode-oniguruma@2.0.1", "", {}, "sha512-poJU8iHIWnC3vgphJnrLZyI3YdqRlR27xzqDmpPXYzA93R4Gk8z7T6oqDzDoHjoikA2aS82crdXFkjELCdJsjQ=="],
@@ -3256,13 +2943,6 @@
"@ai-sdk/provider-utils/secure-json-parse": ["secure-json-parse@2.7.0", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="],
<<<<<<< HEAD
"@anthropic-ai/sdk/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
"@autumn/server/@types/node": ["@types/node@22.18.10", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg=="],
"@autumn/vite/@types/node": ["@types/node@22.18.10", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg=="],
=======
"@amplitude/analytics-client-common/@amplitude/analytics-types": ["@amplitude/analytics-types@2.10.0", "", {}, "sha512-WP8eEbJh10MmFVnxkHjg92i5DBxBFsRvSZxjDQPXEGL8ZP+i7rSsleiH2K3VrwoKksYfZ/1eAqrZvevAmjSlig=="],
"@amplitude/analytics-remote-config/@amplitude/analytics-core": ["@amplitude/analytics-core@1.2.8", "", { "dependencies": { "@amplitude/analytics-types": "^1.4.0", "tslib": "^2.4.1" } }, "sha512-Krxpr5uvS3HmmjvpYqPfbMbs2kcZZu09L+6KwQnPiofWRzoXWIM217fRfy6aSD/QrAoPGbZjvtVitw9cB7Cx+A=="],
@@ -3288,7 +2968,6 @@
"@autumn/shared/stripe": ["stripe@18.5.0", "", { "dependencies": { "qs": "^6.11.0" }, "peerDependencies": { "@types/node": ">=12.x.x" }, "optionalPeers": ["@types/node"] }, "sha512-Hp+wFiEQtCB0LlNgcFh5uVyKznpDjzyUZ+CNVEf+I3fhlYvh7rZruIg+jOwzJRCpy0ZTPMjlzm7J2/M2N6d+DA=="],
"@autumn/vite/@types/node": ["@types/node@22.18.11", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-Gd33J2XIrXurb+eT2ktze3rJAfAp9ZNjlBdh4SVgyrKEOADwCbdUDaK7QgJno8Ue4kcajscsKqu6n8OBG3hhCQ=="],
>>>>>>> staging
"@autumn/vite/date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="],
@@ -3308,11 +2987,8 @@
"@better-auth/core/zod": ["zod@4.1.12", "", {}, "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ=="],
<<<<<<< HEAD
=======
"@better-auth/stripe/stripe": ["stripe@18.5.0", "", { "dependencies": { "qs": "^6.11.0" }, "peerDependencies": { "@types/node": ">=12.x.x" }, "optionalPeers": ["@types/node"] }, "sha512-Hp+wFiEQtCB0LlNgcFh5uVyKznpDjzyUZ+CNVEf+I3fhlYvh7rZruIg+jOwzJRCpy0ZTPMjlzm7J2/M2N6d+DA=="],
>>>>>>> staging
"@better-auth/stripe/zod": ["zod@4.1.12", "", {}, "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ=="],
"@browserbasehq/sdk/@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
@@ -3624,11 +3300,6 @@
"@opentelemetry/sdk-trace-node/@opentelemetry/core": ["@opentelemetry/core@2.1.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ=="],
"@opentelemetry/sql-common/@opentelemetry/core": ["@opentelemetry/core@2.1.0", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.29.0" }, "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-RMEtHsxJs/GiHHxYT58IY57UXAQTuUnZVco6ymDEqTNlJKTimM4qPUPVe8InNFyBjhHBEAx4k3Q8LtNayBsbUQ=="],
<<<<<<< HEAD
"@paralleldrive/cuid2/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
=======
>>>>>>> staging
"@sentry/node/@opentelemetry/context-async-hooks": ["@opentelemetry/context-async-hooks@1.30.1", "", { "peerDependencies": { "@opentelemetry/api": ">=1.0.0 <1.10.0" } }, "sha512-s5vvxXPVdjqS3kTLKMeBMvop9hbWkwzBpu+mUO2M7sZtlkyDJGwFe33wRKnbaYDo8ExRVBIIdwIGrqpxHuKttA=="],
@@ -3694,11 +3365,6 @@
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
<<<<<<< HEAD
"@types/serve-static/@types/send": ["@types/send@0.17.5", "", { "dependencies": { "@types/mime": "^1", "@types/node": "*" } }, "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w=="],
=======
>>>>>>> staging
"@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
"accepts/mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
@@ -3803,11 +3469,7 @@
"react-router/cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
<<<<<<< HEAD
"recaseai/@types/node": ["@types/node@22.18.10", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-anNG/V/Efn/YZY4pRzbACnKxNKoBng2VTFydVu8RRs5hQjikP8CQfaeAV59VFSCzKNp90mXiVXW2QzV56rwMrg=="],
=======
"recaseai/@types/node": ["@types/node@22.18.11", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-Gd33J2XIrXurb+eT2ktze3rJAfAp9ZNjlBdh4SVgyrKEOADwCbdUDaK7QgJno8Ue4kcajscsKqu6n8OBG3hhCQ=="],
>>>>>>> staging
"recaseai/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
@@ -4246,13 +3908,10 @@
"ora/log-symbols/is-unicode-supported": ["is-unicode-supported@1.3.0", "", {}, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="],
"ora/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
<<<<<<< HEAD
=======
"prebuild-install/tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="],
"prebuild-install/tar-fs/tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="],
>>>>>>> staging
"react-email/glob/jackspeak": ["jackspeak@4.1.1", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ=="],
@@ -4360,11 +4019,8 @@
"nodemon/chokidar/readdirp/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
<<<<<<< HEAD
=======
"prebuild-install/tar-fs/tar-stream/readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="],
>>>>>>> staging
"react-email/glob/path-scurry/lru-cache": ["lru-cache@11.2.2", "", {}, "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg=="],
"renderkid/htmlparser2/domutils/dom-serializer": ["dom-serializer@1.4.1", "", { "dependencies": { "domelementtype": "^2.0.1", "domhandler": "^4.2.0", "entities": "^2.0.0" } }, "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag=="],

277
server/customer.json Normal file
View File

@@ -0,0 +1,277 @@
{
"id": "123",
"created_at": 1761032316983,
"name": "123",
"email": "123@example.com",
"fingerprint": null,
"stripe_id": "cus_TH8QGlLFtlDpAj",
"env": "sandbox",
"metadata": {},
"products": [
{
"id": "pro",
"name": "Pro",
"group": null,
"status": "trialing",
"canceled_at": null,
"started_at": 1761122358550,
"is_default": false,
"is_add_on": false,
"version": 5,
"current_period_start": 1761032316000,
"current_period_end": 1762073321000,
"items": [
{
"type": "price",
"feature_id": null,
"feature": null,
"interval": "month",
"interval_count": 1,
"price": 20
},
{
"type": "priced_feature",
"feature_id": "messages",
"feature_type": "single_use",
"feature": {
"id": "messages",
"name": "messages",
"type": "single_use",
"display": {
"singular": "message",
"plural": "messages"
},
"archived": false
},
"included_usage": 200,
"interval": "month",
"interval_count": 1,
"price": 0.5,
"usage_model": "pay_per_use",
"billing_units": 1,
"reset_usage_when_enabled": true,
"entity_feature_id": null
},
{
"type": "priced_feature",
"feature_id": "websites",
"feature_type": "continuous_use",
"feature": {
"id": "websites",
"name": "Websites",
"type": "continuous_use",
"display": {
"singular": "website",
"plural": "websites"
},
"archived": false
},
"included_usage": 3,
"interval": "month",
"interval_count": 1,
"price": 20,
"usage_model": "pay_per_use",
"billing_units": 1,
"reset_usage_when_enabled": false,
"entity_feature_id": null
},
{
"type": "feature",
"feature_id": "discord_notifications",
"feature_type": "static",
"feature": {
"id": "discord_notifications",
"name": "Discord notifications",
"type": "boolean",
"display": {
"singular": "Discord notification",
"plural": "Discord notifications"
},
"archived": false
},
"entity_feature_id": null
},
{
"type": "feature",
"feature_id": "messages",
"feature_type": "single_use",
"feature": {
"id": "messages",
"name": "messages",
"type": "single_use",
"display": {
"singular": "message",
"plural": "messages"
},
"archived": false
},
"included_usage": 500,
"interval": null,
"interval_count": 1,
"reset_usage_when_enabled": true,
"entity_feature_id": null
},
{
"type": "feature",
"feature_id": "projects",
"feature_type": "continuous_use",
"feature": {
"id": "projects",
"name": "Projects",
"type": "continuous_use",
"display": {
"singular": "project",
"plural": "projects"
},
"archived": false
},
"included_usage": 12,
"interval": null,
"interval_count": 1,
"reset_usage_when_enabled": false,
"entity_feature_id": null
},
{
"type": "feature",
"feature_id": "swag",
"feature_type": "single_use",
"feature": {
"id": "swag",
"name": "Swag",
"type": "single_use",
"display": {
"singular": "swag",
"plural": "swags"
},
"archived": false
},
"included_usage": "inf",
"interval": null,
"interval_count": 1,
"reset_usage_when_enabled": true,
"entity_feature_id": null
}
],
"quantity": 1
}
],
"features": {
"discord_notifications": {
"id": "discord_notifications",
"type": "static",
"name": "Discord notifications",
"unlimited": false,
"balance": 0,
"usage": 0,
"included_usage": 0,
"next_reset_at": null,
"overage_allowed": false
},
"swag": {
"id": "swag",
"type": "single_use",
"name": "Swag",
"unlimited": true,
"balance": 0,
"usage": 0,
"included_usage": 0,
"next_reset_at": null,
"overage_allowed": false
},
"messages": {
"id": "messages",
"type": "single_use",
"name": "messages",
"interval": "multiple",
"interval_count": null,
"unlimited": false,
"balance": 550,
"usage": 150,
"included_usage": 700,
"next_reset_at": 1764665321000,
"overage_allowed": false,
"breakdown": [
{
"interval": "lifetime",
"interval_count": 1,
"balance": 350,
"usage": 150,
"included_usage": 500,
"next_reset_at": null
},
{
"interval": "month",
"interval_count": 1,
"balance": 200,
"usage": 0,
"included_usage": 200,
"next_reset_at": 1764665321000,
"usage_limit": 500
}
],
"usage_limit": 1000
},
"projects": {
"id": "projects",
"type": "continuous_use",
"name": "Projects",
"interval": "lifetime",
"interval_count": 1,
"unlimited": false,
"balance": 12,
"usage": 0,
"included_usage": 12,
"next_reset_at": null,
"overage_allowed": false
},
"websites": {
"id": "websites",
"type": "continuous_use",
"name": "Websites",
"interval": "lifetime",
"interval_count": 1,
"unlimited": false,
"balance": 3,
"usage": 0,
"included_usage": 3,
"next_reset_at": null,
"overage_allowed": false,
"usage_limit": 10
}
},
"invoices": [
{
"product_ids": [
"pro"
],
"stripe_id": "in_1SKi259mx3u0jkgOr86nPA3R",
"status": "paid",
"total": 0,
"currency": "usd",
"created_at": 1761467921000,
"hosted_invoice_url": "http://localhost:8080/invoices/hosted_invoice_url/inv_34Nmt7zkME1IpjKXCBj0Mr6EBOx"
},
{
"product_ids": [
"pro"
],
"stripe_id": "in_1SKi1F9mx3u0jkgO38q4Vf2B",
"status": "paid",
"total": 0,
"currency": "usd",
"created_at": 1761467921000,
"hosted_invoice_url": "http://localhost:8080/invoices/hosted_invoice_url/inv_34Nmmg52fFTqhf1rJhzKgCjEvzj"
},
{
"product_ids": [
"pro"
],
"stripe_id": "in_1SKa9U9mx3u0jkgOpZa0MZFx",
"status": "paid",
"total": 0,
"currency": "usd",
"created_at": 1761032316000,
"hosted_invoice_url": "http://localhost:8080/invoices/hosted_invoice_url/inv_34MnUDUGNVi0hdc21Rg3gb8h24d"
}
]
}

View File

@@ -1,14 +1,8 @@
// @ts-nocheck
{
id: "team_plan",
// other fields,
licenses: [
{ plan_id: "dev_seat" },
{
plan_id: "pro_seat", price: 50 // to override price
},
]
}
// Old customer object
// Global namesapce:
//

View File

@@ -0,0 +1,59 @@
import type { ZodError, ZodIssue } from "zod/v4";
/**
* Formats Zod validation errors into user-friendly messages
*/
export function formatZodError(error: ZodError): string {
const formatMessage = (issue: ZodIssue): string => {
const path = issue.path.length ? issue.path.join(".") : "input";
// Clean up common Zod error messages
let message = issue.message;
// Handle common patterns and make them more user-friendly
if (
message.includes("Too small") &&
message.includes("expected string to have >=1 characters")
) {
message = "cannot be empty";
} else if (message.includes("Invalid option: expected one of")) {
// Clean up enum error messages
// Example: 'Invalid option: expected one of "a"|"b"|"c"' -> 'must be one of: a, b, c'
const match = message.match(/expected one of (.+)/);
if (match) {
const options = match[1]
.split("|")
.map((opt) => opt.replace(/['"]/g, "").trim())
.join(", ");
message = `must be one of: ${options}`;
}
} else if (message.includes("Invalid string: must match pattern")) {
// Extract the pattern and make it more readable
if (message.includes("/^[a-zA-Z0-9_-]+$/")) {
message =
"must contain only letters, numbers, underscores, and hyphens";
} else {
message = "has invalid format";
}
} else if (message.includes("Invalid input: expected string, received")) {
const receivedType = message.split("received ")[1];
message = `must be a string (received ${receivedType})`;
} else if (message.includes("Invalid input: expected number, received")) {
const receivedType = message.split("received ")[1];
message = `must be a number (received ${receivedType})`;
} else if (message.includes("Invalid input: expected boolean, received")) {
const receivedType = message.split("received ")[1];
message = `must be a boolean (received ${receivedType})`;
}
return `${path}: ${message}`;
};
const formattedIssues = error.issues.map(formatMessage);
// If there are multiple issues, format them nicely
if (formattedIssues.length === 1) {
return formattedIssues[0];
}
return `[Validation Errors] ${formattedIssues.join("; ")}`;
}

View File

@@ -140,7 +140,7 @@ export const priceToStripeItem = ({
};
}
if (apiVersion === ApiVersion.Beta && !isCheckout) {
if (apiVersion === ApiVersion.V1_Beta && !isCheckout) {
return {
lineItem: {
// lineItem: getEmptyPriceItem({ price, org }),

View File

@@ -75,7 +75,7 @@ export const handleCheckoutSub = async ({
if (
arrearPrice &&
(attachParams.internalEntityId ||
attachParams.apiVersion === ApiVersion.Beta)
attachParams.apiVersion === ApiVersion.V1_Beta)
) {
itemsUpdate.push({
id: item.id,

View File

@@ -38,7 +38,7 @@ export const handleRemainingSets = async ({
if (
attachParams.internalEntityId ||
attachParams.apiVersion === ApiVersion.Beta
attachParams.apiVersion === ApiVersion.V1_Beta
) {
const replaceIndex = remainingItems.findIndex(
(item) => item.price === config.stripe_price_id,

View File

@@ -17,11 +17,11 @@ import { RolloverService } from "@/internal/customers/cusProducts/cusEnts/cusRol
import { getRolloverUpdates } from "@/internal/customers/cusProducts/cusEnts/cusRollovers/rolloverUtils.js";
import { getResetBalancesUpdate } from "@/internal/customers/cusProducts/cusEnts/groupByUtils.js";
import { getCusPriceUsage } from "@/internal/customers/cusProducts/cusPrices/cusPriceUtils.js";
import { getAllFullCustomers } from "@/utils/scriptUtils/getAll/getAllAutumnCustomers.js";
import { submitUsageToStripe } from "../../stripeMeterUtils.js";
import { getInvoiceItemForUsage } from "../../stripePriceUtils.js";
import { subToPeriodStartEnd } from "../../stripeSubUtils/convertSubUtils.js";
import { findStripeItemForPrice } from "../../stripeSubUtils/stripeSubItemUtils.js";
import { getAllFullCustomers } from "@/utils/scriptUtils/getAll/getAllAutumnCustomers.js";
export const handleUsagePrices = async ({
db,
@@ -86,7 +86,7 @@ export const handleUsagePrices = async ({
const isNewUsageMethod =
activeProduct.internal_entity_id ||
activeProduct.api_semver === ApiVersion.Beta;
activeProduct.api_semver === ApiVersion.V1_Beta;
if (isNewUsageMethod) {
const invoiceItem = getInvoiceItemForUsage({

View File

@@ -53,7 +53,7 @@ export const handleCusProductDeleted = async ({
stripeId: fullCus.processor?.id,
});
const isV4Usage = cusProduct.api_semver === ApiVersion.Beta;
const isV4Usage = cusProduct.api_semver === ApiVersion.V1_Beta;
// refer to handleUpgradeFlow.ts, when cancel immediately through API / dashboard, this happens...?
const isAutumnCancel =

View File

@@ -47,6 +47,7 @@ export const apiVersionMiddleware = async (c: Context<HonoEnv>, next: Next) => {
finalVersion = new ApiVersionClass(parsedVersion);
}
// 2. Calculate from org creation date
else if (org?.created_at) {
finalVersion = createdAtToVersion({

View File

@@ -68,6 +68,9 @@ export const baseMiddleware = async (c: Context<HonoEnv>, next: Next) => {
userId: undefined,
authType: AuthType.Unknown,
env: AppEnv.Sandbox, // maybe use app_env headers
// Query params
expand: [],
});
childLogger.info(`${method} ${path}`);

View File

@@ -3,8 +3,9 @@ import type { Context } from "hono";
import type { ContentfulStatusCode } from "hono/utils/http-status";
import Stripe from "stripe";
import { ZodError } from "zod/v4";
import { formatZodError } from "@/errors/formatZodError.js";
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
import RecaseError, { formatZodError } from "@/utils/errorUtils.js";
import RecaseError from "@/utils/errorUtils.js";
import { handleErrorSkip } from "./errorSkipMiddleware.js";
/**

View File

@@ -8,8 +8,9 @@ import type { Context } from "hono";
import type { ContentfulStatusCode } from "hono/utils/http-status";
import Stripe from "stripe";
import { ZodError } from "zod/v4";
import { formatZodError } from "@/errors/formatZodError.js";
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
import RecaseError, { formatZodError } from "@/utils/errorUtils.js";
import RecaseError from "@/utils/errorUtils.js";
import { matchRoute } from "./middlewareUtils.js";
// ============================================================================

View File

@@ -0,0 +1,21 @@
import type { MiddlewareHandler } from "hono";
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
/**
* Extracts expand from validated query and sets it in context.
* Must run AFTER versionedValidator so expand has been transformed.
*/
export const expandMiddleware = (): MiddlewareHandler<HonoEnv> => {
return async (c, next) => {
const validatedQuery = c.req.valid("query");
const expand = validatedQuery?.expand || [];
const ctx = c.get("ctx");
c.set("ctx", {
...ctx,
expand: Array.isArray(expand) ? expand : [expand],
});
await next();
};
};

View File

@@ -5,6 +5,7 @@ import type { DrizzleCli } from "@/db/initDrizzle.js";
import type { HonoEnv } from "@/honoUtils/HonoEnv.js";
import { validator } from "./validatorMiddleware.js";
import { versionedValidator } from "./versionedValidator.js";
import { expandMiddleware } from "./expandMiddleware.js";
/**
* Extended context type that includes validated input
@@ -128,6 +129,11 @@ export function createRoute<
middlewares.push(validator("param", opts.params));
}
// Add expand middleware after query validation
if (opts.query || opts.versionedQuery) {
middlewares.push(expandMiddleware());
}
const wrappedHandler = async (
c: ValidatedContext<HonoEnv, Body, Query, Params>,
) => {

View File

@@ -55,28 +55,39 @@ export const versionedValidator = ({
const versionKey = userVersion.value as ApiVersion;
const schema = schemas[versionKey] ?? schemas.latest;
// Validate with version-specific schema using zValidator
const validatorMiddleware = zValidator(target, schema, (result, _c) => {
let validatedData: Record<string, unknown>;
// For query validation, use the parsed query from queryMiddleware
if (target === "query") {
const parsedQuery = c.req.query();
const result = schema.safeParse(parsedQuery);
if (!result.success) {
// Validation errors reference fields from user's version ✅
throw result.error;
}
});
validatedData = result.data as Record<string, unknown>;
c.req.addValidatedData(target, result.data);
} else {
// For other targets, use zValidator
const validatorMiddleware = zValidator(target, schema, (result, _c) => {
if (!result.success) {
// Validation errors reference fields from user's version ✅
throw result.error;
}
});
// Run validation
await validatorMiddleware(c, async () => {});
// Run validation
await validatorMiddleware(c, async () => {});
// Get validated data - type assertion needed due to Hono's dynamic validation target typing
type ValidData = Record<string, unknown>;
// biome-ignore lint/suspicious/noExplicitAny: Hono's typing requires dynamic target access
const validatedData = (c.req as any).valid(target) as ValidData;
// Get validated data - type assertion needed due to Hono's dynamic validation target typing
validatedData = (c.req as any).valid(target) as Record<string, unknown>;
}
// If user is on older version, transform to latest
if (!userVersion.eq(new ApiVersionClass(LATEST_VERSION))) {
const transformed = applyRequestVersionChanges({
input: validatedData,
targetVersion: userVersion,
currentVersion: new ApiVersionClass(LATEST_VERSION),
fromVersion: userVersion,
toVersion: new ApiVersionClass(LATEST_VERSION),
resource,
});

View File

@@ -19,7 +19,7 @@ export type RequestContext = {
// Objects
db: DrizzleCli;
logger: Logger;
clickhouseClient: ClickHouseClient;
clickhouseClient?: ClickHouseClient;
// Info
id: string;
@@ -27,6 +27,9 @@ export type RequestContext = {
authType: AuthType;
apiVersion: ApiVersionClass;
timestamp: number;
// Query params
expand: string[];
};
export type AutumnContext = RequestContext;

View File

@@ -1,11 +1,12 @@
import { DrizzleCli } from "@/db/initDrizzle.js";
import type { ExtendedRequest } from "@/utils/models/Request.js";
import { ActionType, AppEnv, Organization } from "@autumn/shared";
import { addTaskToQueue } from "@/queue/queueUtils.js";
import { JobName } from "@/queue/JobName.js";
import { constructAction, parseReqForAction } from "../actionUtils.js";
import { ActionType, type AppEnv, type Organization } from "@autumn/shared";
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
import { CusService } from "@/internal/customers/CusService.js";
import { JobName } from "@/queue/JobName.js";
import { addTaskToQueue } from "@/queue/queueUtils.js";
import type { ExtendedRequest } from "@/utils/models/Request.js";
import type { HandleCustomerCreatedData } from "@/utils/workerUtils/jobTypes/HandleCustomerCreatedData.js";
import { ActionService } from "../ActionService.js";
import { constructAction, parseReqForAction } from "../actionUtils.js";
export const addCustomerCreatedTask = async ({
req,
@@ -23,31 +24,30 @@ export const addCustomerCreatedTask = async ({
payload: {
req: req ? parseReqForAction(req) : undefined,
internalCustomerId,
org,
orgId: org.id,
env,
},
});
};
export const handleCustomerCreated = async ({
db,
logger,
ctx,
data,
}: {
db: DrizzleCli;
logger: any;
data: any;
ctx: AutumnContext;
data: HandleCustomerCreatedData;
}) => {
const { req, internalCustomerId, org, env } = data;
const { req, internalCustomerId } = data;
const { db, org, env } = ctx;
let customer = await CusService.getFull({
const customer = await CusService.getFull({
db,
idOrInternalId: internalCustomerId,
orgId: org.id,
env,
});
let action = constructAction({
const action = constructAction({
org,
env,
customer,

View File

@@ -2,15 +2,14 @@ import {
ActionType,
type AppEnv,
type AuthType,
createdAtToVersion,
cusProductToProduct,
type FullCusProduct,
type FullProduct,
notNullish,
type Organization,
} from "@autumn/shared";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { sendSvixEvent } from "@/external/svix/svixHelpers.js";
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
import { ActionService } from "@/internal/analytics/ActionService.js";
import {
constructAction,
@@ -19,7 +18,7 @@ import {
import { getSingleEntityResponse } from "@/internal/api/entities/getEntityUtils.js";
import { CusService } from "@/internal/customers/CusService.js";
import { RELEVANT_STATUSES } from "@/internal/customers/cusProducts/CusProductService.js";
import { getCustomerDetails } from "@/internal/customers/cusUtils/getCustomerDetails.js";
import { getApiCustomer } from "@/internal/customers/cusUtils/apiCusUtils/getApiCustomer.js";
import { FeatureService } from "@/internal/features/FeatureService.js";
import { getProductResponse } from "@/internal/products/productUtils/productResponseUtils/getProductResponse.js";
import { JobName } from "@/queue/JobName.js";
@@ -66,8 +65,10 @@ export const addProductsUpdatedWebhookTask = async ({
payload: {
req: req ? parseReqForAction(req) : undefined,
internalCustomerId,
org,
orgId: org.id,
env,
// org,
// env,
customerId,
cusProduct,
scheduledCusProduct,
@@ -90,18 +91,16 @@ export const addProductsUpdatedWebhookTask = async ({
};
export const handleProductsUpdated = async ({
db,
logger,
ctx,
data,
}: {
db: DrizzleCli;
logger: any;
ctx: AutumnContext;
data: {
req: Partial<ExtendedRequest>;
actionDetails: ActionDetails;
internalCustomerId: string;
org: Organization;
env: AppEnv;
// org: Organization;
// env: AppEnv;
customerId: string;
product: FullProduct;
scenario: string;
@@ -110,15 +109,10 @@ export const handleProductsUpdated = async ({
deletedCusProduct?: FullCusProduct;
};
}) => {
const {
req,
org,
env,
scenario,
cusProduct,
scheduledCusProduct,
deletedCusProduct,
} = data;
const { req, scenario, cusProduct, scheduledCusProduct, deletedCusProduct } =
data;
const { db, org, env, logger } = ctx;
// Product:
const product = cusProduct.product;
@@ -126,8 +120,8 @@ export const handleProductsUpdated = async ({
const customer = await CusService.getFull({
db,
idOrInternalId: data.customerId || data.internalCustomerId,
orgId: data.org.id,
env: data.env,
orgId: org.id,
env: env,
inStatuses: RELEVANT_STATUSES,
entityId: cusProduct.internal_entity_id || undefined,
});
@@ -138,20 +132,10 @@ export const handleProductsUpdated = async ({
env,
});
const apiVersion = createdAtToVersion({
createdAt: org.created_at || Date.now(),
});
const cusDetails = await getCustomerDetails({
db,
customer: customer,
org,
env,
features,
logger,
cusProducts: customer.customer_products,
const apiCustomer = await getApiCustomer({
ctx,
fullCus: customer,
expand: [],
apiVersion,
});
const productRes = await getProductResponse({
@@ -199,12 +183,10 @@ export const handleProductsUpdated = async ({
let entityRes = null;
if (notNullish(customer?.entity)) {
entityRes = await getSingleEntityResponse({
ctx,
entityId: customer.entity!.id,
org,
env,
fullCus: customer,
entity: customer.entity!,
features,
});
}
@@ -215,7 +197,7 @@ export const handleProductsUpdated = async ({
eventType: "customer.products.updated",
data: {
scenario,
customer: cusDetails,
customer: apiCustomer,
entity: entityRes,
updated_product: productRes,
},

View File

@@ -1,54 +1,47 @@
import { DrizzleCli } from "@/db/initDrizzle.js";
import type { Job, Queue } from "bullmq";
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
import { JobName } from "@/queue/JobName.js";
import { getLock, releaseLock } from "@/queue/lockUtils.js";
import { Queue } from "bullmq";
import { Job } from "bullmq";
import { handleProductsUpdated } from "./handlers/handleProductsUpdated.js";
import { handleCustomerCreated } from "./handlers/handleCustomerCreated.js";
import { handleProductsUpdated } from "./handlers/handleProductsUpdated.js";
export const runActionHandlerTask = async ({
queue,
job,
logger,
db,
useBackup,
ctx,
}: {
queue: Queue;
job: Job;
logger: any;
db: DrizzleCli;
useBackup: boolean;
ctx: AutumnContext;
}) => {
let payload = job.data;
let internalCustomerId = payload.internalCustomerId;
let lockKey = `action:${internalCustomerId}`;
const payload = job.data;
const internalCustomerId = payload.internalCustomerId;
const lockKey = `action:${internalCustomerId}`;
const { logger } = ctx;
try {
let lock = await getLock({ queue, job, lockKey, useBackup });
const lock = await getLock({ queue, job, lockKey, useBackup });
if (!lock) return;
switch (job.name) {
case JobName.HandleProductsUpdated:
await handleProductsUpdated({
db,
logger,
ctx,
data: payload,
});
break;
case JobName.HandleCustomerCreated:
await handleCustomerCreated({
db,
logger,
ctx,
data: payload,
});
break;
}
} catch (error: any) {
logger.error("Error processing action handler job:", {
// jobName: job.name,
// payload,
message: error.message,
});
logger.error(`Error processing action handler job: ${error.message}`);
} finally {
await releaseLock({ lockKey, useBackup });
}

View File

@@ -17,7 +17,7 @@ import { handleGetOrg } from "../orgs/handlers/handleGetOrg.js";
import { platformRouter } from "../platform/platformLegacy/platformRouter.js";
import { productBetaRouter, productRouter } from "../products/productRouter.js";
import { componentRouter } from "./components/componentRouter.js";
import { entityRouter } from "./entities/entityRouter.js";
import { expressEntityRouter } from "./entities/entityRouter.js";
import { checkRouter } from "./entitled/checkRouter.js";
import { eventsRouter } from "./events/eventRouter.js";
import { usageRouter } from "./events/usageRouter.js";
@@ -44,7 +44,7 @@ apiRouter.use("/features", featureRouter);
apiRouter.use("/internal_features", internalFeatureRouter);
apiRouter.use("/usage", usageRouter);
apiRouter.use("/entities", entityRouter);
apiRouter.use("/entities", expressEntityRouter);
apiRouter.use("/migrations", migrationRouter);
// REWARDS

View File

@@ -1,6 +1,7 @@
import {
type ApiVersionClass,
type AppEnv,
AuthType,
CusExpand,
type CusProductStatus,
type Feature,
@@ -10,7 +11,8 @@ import {
import type { NodeClickHouseClient } from "@clickhouse/client/dist/client.js";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { RELEVANT_STATUSES } from "@/internal/customers/cusProducts/CusProductService.js";
import { getCustomerDetails } from "../../customers/cusUtils/getCustomerDetails.js";
import { getApiCustomer } from "@/internal/customers/cusUtils/apiCusUtils/getApiCustomer.js";
import { generateId } from "@/utils/genUtils.js";
import { getPaginatedFullCusQuery } from "../../customers/getFullCusQuery.js";
export class CusBatchService {
@@ -95,20 +97,24 @@ export class CusBatchService {
const customer = normalizedCustomer as FullCustomer;
const cusProducts = customer.customer_products || [];
const customerDetails = await getCustomerDetails({
db,
customer,
features,
org,
env,
params: {},
logger: console,
cusProducts,
const apiCustomer = await getApiCustomer({
ctx: {
db,
org,
env,
features,
logger,
apiVersion,
id: generateId("local_req"),
isPublic: false,
authType: AuthType.Unknown,
timestamp: Date.now(),
},
fullCus: customer,
expand: expand,
apiVersion: apiVersion,
});
finals.push(customerDetails);
finals.push(apiCustomer);
} catch (error) {
console.error(`Failed to process customer ${result.id}:`, error);
}
@@ -124,7 +130,7 @@ export class CusBatchService {
const normalizeTimestamp = (value: any): number => {
if (typeof value === "string") {
const parsed = parseInt(value, 10);
return isNaN(parsed) ? Date.now() : parsed;
return Number.isNaN(parsed) ? Date.now() : parsed;
}
return typeof value === "number" ? value : Date.now();
};

View File

@@ -1,24 +1,23 @@
import { Router } from "express";
import { routeHandler } from "@/utils/routerUtils.js";
import { CusService } from "@/internal/customers/CusService.js";
import { handleGetEntity } from "./handlers/handleGetEntity.js";
import { routeHandler } from "@/utils/routerUtils.js";
import { handlePostEntityRequest } from "../../entities/handlers/handleCreateEntity/handleCreateEntity.js";
import { handleDeleteEntity } from "./handlers/handleDeleteEntity.js";
import { handleGetEntity } from "./handlers/handleGetEntity.js";
export const entityRouter: Router = Router({ mergeParams: true });
export const expressEntityRouter: Router = Router({ mergeParams: true });
// List entityes
entityRouter.get("", (req: any, res: any) =>
expressEntityRouter.get("", (req: any, res: any) =>
routeHandler({
req,
res,
action: "listEntities",
handler: async (req, res) => {
const customerId = String(req.params.customer_id);
let { orgId, env } = req;
const { orgId, env } = req;
let customer = await CusService.getFull({
const customer = await CusService.getFull({
db: req.db,
idOrInternalId: customerId,
orgId,
@@ -34,10 +33,14 @@ entityRouter.get("", (req: any, res: any) =>
);
// 1. Create entity
entityRouter.post("", handlePostEntityRequest);
expressEntityRouter.post("", handlePostEntityRequest);
// 2. Delete entity
entityRouter.delete("/:entity_id", handleDeleteEntity);
expressEntityRouter.delete("/:entity_id", handleDeleteEntity);
// 3. Get entity
entityRouter.get("/:entity_id", handleGetEntity);
expressEntityRouter.get("/:entity_id", handleGetEntity);
// export const entityRouter = new Hono<HonoEnv>();
// entityRouter.get("/:entity_id", ...handleGetEntity);

View File

@@ -1,44 +1,34 @@
import {
type ApiCusProduct,
ApiVersion,
ApiVersionClass,
type AppEnv,
type ApiEntity,
type Entity,
type EntityExpand,
type EntityResponse,
ErrCode,
type Feature,
type FullCusProduct,
type FullCustomer,
notNullish,
type Organization,
type Subscription,
} from "@autumn/shared";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { getCusWithCache } from "@/internal/customers/cusCache/getCusWithCache.js";
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
import { CusService } from "@/internal/customers/CusService.js";
import { getApiCusPlans } from "@/internal/customers/cusUtils/apiCusUtils/getApiCusPlan/getApiCusPlans.js";
import { getCusFeaturesResponse } from "@/internal/customers/cusUtils/cusFeatureResponseUtils/getCusFeaturesResponse.js";
import { processFullCusProducts } from "@/internal/customers/cusUtils/cusProductResponseUtils/processFullCusProducts.js";
import RecaseError from "@/utils/errorUtils.js";
import { nullish } from "@/utils/genUtils.js";
export const getSingleEntityResponse = async ({
ctx,
entityId,
org,
env,
fullCus,
entity,
features,
withAutumnId = false,
}: {
ctx: AutumnContext;
entityId: string;
org: Organization;
env: AppEnv;
fullCus: FullCustomer;
entity: Entity;
features: Feature[];
withAutumnId?: boolean;
}) => {
const apiVersion = new ApiVersionClass(ApiVersion.V1_2);
const { org, env, apiVersion } = ctx;
if (!entity) {
throw new RecaseError({
@@ -70,16 +60,24 @@ export const getSingleEntityResponse = async ({
),
);
const { main, addOns } = await processFullCusProducts({
fullCusProducts: entityCusProducts,
entity,
subs: entitySubs,
org,
apiVersion,
features,
});
const fullEntity = structuredClone(fullCus);
fullEntity.customer_products = entityCusProducts;
fullEntity.subscriptions = entitySubs;
const products: ApiCusProduct[] = [...main, ...addOns];
// const { main, addOns } = await processFullCusProducts({
// fullCusProducts: entityCusProducts,
// entity,
// subs: entitySubs,
// org,
// apiVersion,
// features,
// });
// const products: ApiCusProduct[] = [...main, ...addOns];
const { apiCusPlans } = await getApiCusPlans({
ctx,
fullCus: fullEntity,
});
const cusFeatures = await getCusFeaturesResponse({
cusProducts: entityCusProducts,
@@ -96,47 +94,52 @@ export const getSingleEntityResponse = async ({
// feature_id: entity.feature_id,
customer_id: fullCus.id || fullCus.internal_id,
env,
products,
plans: apiCusPlans,
features: cusFeatures,
};
} satisfies ApiEntity;
};
export const getEntityResponse = async ({
db,
ctx,
entityIds,
org,
env,
customerId,
expand,
entityId,
withAutumnId = false,
apiVersion,
features,
logger,
skipCache = false,
}: {
db: DrizzleCli;
ctx: AutumnContext;
entityIds: string[];
org: Organization;
env: AppEnv;
customerId: string;
expand?: EntityExpand[];
entityId?: string;
withAutumnId?: boolean;
apiVersion: number;
features: Feature[];
logger: any;
skipCache?: boolean;
}) => {
const fullCus = await getCusWithCache({
// const fullCus = await getCusWithCache({
// db,
// idOrInternalId: customerId,
// org,
// env,
// expand,
// entityId,
// logger,
// skipCache,
// });
// don't use cache anymore?
const { org, env, features, db } = ctx;
const fullCus = await CusService.getFull({
db,
idOrInternalId: customerId,
org,
orgId: org.id,
env,
expand,
entityId,
logger,
skipCache,
expand,
withSubs: true,
withEntities: true,
});
if (!fullCus) {
@@ -147,7 +150,7 @@ export const getEntityResponse = async ({
});
}
const entityResponses: EntityResponse[] = [];
const entityResponses: ApiEntity[] = [];
for (const entityId of entityIds) {
const entity = fullCus.entities.find(
@@ -163,12 +166,10 @@ export const getEntityResponse = async ({
}
const entityResponse = await getSingleEntityResponse({
ctx,
entityId,
org,
env,
fullCus,
entity,
features,
withAutumnId,
});

View File

@@ -1,8 +1,6 @@
import { ApiEntitySchema, EntityExpand, LegacyVersion } from "@autumn/shared";
import { ApiEntitySchema, EntityExpand } from "@autumn/shared";
import { invoicesToResponse } from "@/internal/invoices/invoiceUtils.js";
import { OrgService } from "@/internal/orgs/OrgService.js";
import { routeHandler } from "@/utils/routerUtils.js";
import { orgToVersion } from "@/utils/versionUtils/legacyVersionUtils.js";
import { parseEntityExpand } from "../entityUtils.js";
import { getEntityResponse } from "../getEntityUtils.js";
@@ -16,32 +14,16 @@ export const handleGetEntity = async (req: any, res: any) =>
const customerId = req.params.customer_id as string;
const expand = parseEntityExpand(req.query.expand);
const { env, db, logger, features } = req;
const { logger } = req;
const org = await OrgService.getFromReq(req);
const apiVersion = orgToVersion({
org,
reqApiVersion:
req.apiVersion >= LegacyVersion.v1_1
? req.apiVersion
: LegacyVersion.v1_2,
const { entities, invoices } = await getEntityResponse({
ctx: req,
entityIds: [entityId],
customerId,
expand,
entityId,
});
// const start = performance.now();
const { entities, customer, fullEntities, invoices } =
await getEntityResponse({
db,
entityIds: [entityId],
org,
env,
customerId,
expand,
entityId,
apiVersion,
features,
logger,
});
const entity = entities[0];
const withInvoices = expand.includes(EntityExpand.Invoices);

View File

@@ -196,20 +196,3 @@ checkRouter.post("", async (req: any, res: any) => {
handleRequestError({ req, error, res, action: "Failed to GET entitled" });
}
});
// let features = [feature, ...creditSystems];
// let balanceObj: any, featureToUse: any;
// try {
// balanceObj = balances.length > 0 ? balances[0] : null;
// featureToUse =
// notNullish(balanceObj) && balanceObj.feature_id !== feature.id
// ? features.find((f) => f.id === balanceObj.feature_id)
// : creditSystems.length > 0
// ? creditSystems[0]
// : feature;
// } catch (error) {
// logger.error(`/check: failed to get balance & feature to use`, error);
// }
// 3. If with preview, get preview

View File

@@ -1,4 +1,5 @@
import {
cusProductToProduct,
type Feature,
FeaturePreviewScenario,
type FullCusProduct,
@@ -6,7 +7,6 @@ import {
type FullProduct,
} from "@autumn/shared";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { fullCusProductToProduct } from "@/internal/customers/cusProducts/cusProductUtils.js";
import { ProductService } from "@/internal/products/ProductService.js";
import { getProductResponse } from "@/internal/products/productUtils/productResponseUtils/getProductResponse.js";
import {
@@ -43,7 +43,7 @@ export const getCheckPreview = async ({
);
const cusOwnedProducts = mainCusProds.map((cp: FullCusProduct) =>
fullCusProductToProduct(cp),
cusProductToProduct({ cusProduct: cp }),
);
sortProductsByPrice({ products: cusOwnedProducts });

View File

@@ -1,37 +1,34 @@
import { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
import {
AttachReplaceable,
type AttachReplaceable,
BillingType,
FullCusProduct,
FullCustomerEntitlement,
FullEntitlement,
type FullCusProduct,
type FullCustomerEntitlement,
type FullEntitlement,
getFeatureInvoiceDescription,
intervalsSame,
PreviewLineItem,
Price,
type PreviewLineItem,
type Price,
} from "@autumn/shared";
import Stripe from "stripe";
import { attachParamsToProduct } from "../convertAttachParams.js";
import {
getBillingType,
getPriceEntitlement,
getPriceForOverage,
} from "@/internal/products/prices/priceUtils.js";
import { Decimal } from "decimal.js";
import type Stripe from "stripe";
import type { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
import { findCusEnt } from "@/internal/customers/cusProducts/cusEnts/cusEntUtils/findCusEntUtils.js";
import {
getExistingUsageFromCusProducts,
getRelatedCusPrice,
} from "@/internal/customers/cusProducts/cusEnts/cusEntUtils.js";
import { subToAutumnInterval } from "@/external/stripe/utils.js";
import { Decimal } from "decimal.js";
import { getCurContUseItems } from "@/internal/invoices/previewItemUtils/getCurContUseItems.js";
import { intervalsAreSame } from "../getAttachConfig.js";
import { newPriceToInvoiceDescription } from "@/internal/invoices/invoiceFormatUtils.js";
import { getCurContUseItems } from "@/internal/invoices/previewItemUtils/getCurContUseItems.js";
import { getDefaultPriceStr } from "@/internal/invoices/previewItemUtils/getItemsForNewProduct.js";
import { priceToUsageModel } from "@/internal/products/prices/priceUtils/convertPrice.js";
import { priceToContUseItem } from "./priceToContUseItem.js";
import { shouldProrate } from "@/internal/products/prices/priceUtils/prorationConfigUtils.js";
import {
getBillingType,
getPriceEntitlement,
getPriceForOverage,
} from "@/internal/products/prices/priceUtils.js";
import { attachParamsToProduct } from "../convertAttachParams.js";
import { intervalsAreSame } from "../getAttachConfig.js";
import { priceToContUseItem } from "./priceToContUseItem.js";
export const getContUseNewItems = async ({
price,
@@ -56,14 +53,14 @@ export const getContUseNewItems = async ({
internalEntityId: attachParams.internalEntityId,
});
let description = newPriceToInvoiceDescription({
const description = newPriceToInvoiceDescription({
org,
price,
product: newProduct,
quantity: usage,
});
if (usage == 0) {
if (usage === 0) {
return {
price_id: price.id,
price: getDefaultPriceStr({ org, price, ent, features }),
@@ -80,8 +77,8 @@ export const getContUseNewItems = async ({
prevCusEnt &&
!shouldProrate(price.proration_config?.on_decrease)
) {
let isDowngrade = ent.allowance! > prevCusEnt.entitlement.allowance!;
let prevBalance = prevCusEnt.balance!;
const isDowngrade = ent.allowance! > prevCusEnt.entitlement.allowance!;
const prevBalance = prevCusEnt.balance!;
if (isDowngrade && prevBalance < 0) {
overage = new Decimal(prevBalance).abs().toNumber();
@@ -89,8 +86,8 @@ export const getContUseNewItems = async ({
}
}
let amount = getPriceForOverage(price, overage);
let description = getFeatureInvoiceDescription({
const amount = getPriceForOverage(price, overage);
const description = getFeatureInvoiceDescription({
feature: ent.feature,
usage: usage,
prodName: newProduct.name,
@@ -130,24 +127,24 @@ export const getContUseInvoiceItems = async ({
})
: [];
let newEnts = product.entitlements;
let oldItems: PreviewLineItem[] = [];
let newItems: PreviewLineItem[] = [];
let replaceables: AttachReplaceable[] = [];
const newEnts = product.entitlements;
const oldItems: PreviewLineItem[] = [];
const newItems: PreviewLineItem[] = [];
const replaceables: AttachReplaceable[] = [];
for (const price of product.prices) {
let billingType = getBillingType(price.config);
const billingType = getBillingType(price.config);
if (billingType !== BillingType.InArrearProrated) {
continue;
}
let ent = getPriceEntitlement(price, newEnts);
let prevCusEnt = findCusEnt({
const ent = getPriceEntitlement(price, newEnts);
const prevCusEnt = findCusEnt({
cusEnts,
feature: ent.feature,
});
let prevCusPrice = prevCusEnt
const prevCusPrice = prevCusEnt
? getRelatedCusPrice(prevCusEnt, cusPrices)!
: undefined;
@@ -176,7 +173,7 @@ export const getContUseInvoiceItems = async ({
(item) => item.price_id === prevCusPrice?.price.id,
);
let {
const {
oldItem,
newItems: newItems_,
replaceables: replaceables_,

View File

@@ -1,25 +1,25 @@
import { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
import {
FullCustomerEntitlement,
FullEntitlement,
PreviewLineItem,
Price,
cusProductsToCusPrices,
type FullCustomerEntitlement,
type FullEntitlement,
type PreviewLineItem,
type Price,
} from "@autumn/shared";
import Stripe from "stripe";
import { attachParamsToProduct } from "../convertAttachParams.js";
import { Decimal } from "decimal.js";
import type Stripe from "stripe";
import { subToPeriodStartEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js";
import { findStripeItemForPrice } from "@/external/stripe/stripeSubUtils/stripeSubItemUtils.js";
import type { AttachParams } from "@/internal/customers/cusProducts/AttachParams.js";
import {
getExistingUsageFromCusProducts,
getRelatedCusPrice,
} from "@/internal/customers/cusProducts/cusEnts/cusEntUtils.js";
import { getContUseDowngradeItems } from "./getContUseDowngradeItems.js";
import { shouldProrate } from "@/internal/products/prices/priceUtils/prorationConfigUtils.js";
import { getContUseUpgradeItems } from "./getContUseUpgradeItems.js";
import { priceToInvoiceItem } from "@/internal/products/prices/priceUtils/priceToInvoiceItem.js";
import { Decimal } from "decimal.js";
import { shouldProrate } from "@/internal/products/prices/priceUtils/prorationConfigUtils.js";
import { notNullish } from "@/utils/genUtils.js";
import { cusProductsToCusPrices } from "@autumn/shared";
import { findStripeItemForPrice } from "@/external/stripe/stripeSubUtils/stripeSubItemUtils.js";
import { subToPeriodStartEnd } from "@/external/stripe/stripeSubUtils/convertSubUtils.js";
import { attachParamsToProduct } from "../convertAttachParams.js";
import { getContUseDowngradeItems } from "./getContUseDowngradeItems.js";
import { getContUseUpgradeItems } from "./getContUseUpgradeItems.js";
export const priceToContUseItem = async ({
price,
@@ -74,7 +74,7 @@ export const priceToContUseItem = async ({
: shouldProrate(price.proration_config?.on_increase);
// 1. Get current usage
let curUsage = getExistingUsageFromCusProducts({
const curUsage = getExistingUsageFromCusProducts({
entitlement: ent,
cusProducts,
entities,
@@ -132,8 +132,8 @@ export const priceToContUseItem = async ({
// Clean up items
// 1. If old item and new item same, remove both
let oldAmount = res.oldItem?.amount!;
let newAmount = res.newItem?.amount!;
const oldAmount = res.oldItem?.amount ?? 0;
const newAmount = res.newItem?.amount ?? 0;
if (new Decimal(oldAmount).add(newAmount).eq(0)) {
return {

View File

@@ -1,37 +1,4 @@
import {
getStripeSchedules,
getStripeSubs,
} from "@/external/stripe/stripeSubUtils.js";
import {
AppEnv,
AttachScenario,
FullCusProduct,
intervalsSame,
Organization,
Product,
} from "@autumn/shared";
import Stripe from "stripe";
import { fullCusProductToProduct } from "../cusProducts/cusProductUtils.js";
import {
ACTIVE_STATUSES,
CusProductService,
} from "../cusProducts/CusProductService.js";
import { getExistingCusProducts } from "../cusProducts/cusProductUtils/getExistingCusProducts.js";
import { isFreeProduct } from "@/internal/products/productUtils.js";
import { getFilteredScheduleItems } from "./scheduleUtils/getFilteredScheduleItems.js";
import { updateScheduledSubWithNewItems } from "./scheduleUtils/updateScheduleWithNewItems.js";
import {
addCurMainProductToSchedule,
getOtherCusProductsOnSub,
} from "./scheduleUtils/cancelScheduledFreeProduct.js";
import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js";
import { DrizzleCli } from "@/db/initDrizzle.js";
import { ExtendedRequest } from "@/utils/models/Request.js";
import { getStripeSubItems } from "@/external/stripe/stripeSubUtils/getStripeSubItems.js";
import { notNullish } from "@/utils/genUtils.js";
import { subToAutumnInterval } from "@/external/stripe/utils.js";
import type { FullCusProduct } from "@autumn/shared";
export const getPricesForCusProduct = ({
cusProduct,

View File

@@ -1,8 +1,6 @@
import { cusProductToProduct, type FullCusProduct } from "@autumn/shared";
import { getPricesForCusProduct } from "../scheduleUtils.js";
import { FullCusProduct } from "@autumn/shared";
import { ScheduleObj } from "./ScheduleObj.js";
import { fullCusProductToProduct } from "../../cusProducts/cusProductUtils.js";
import type { ScheduleObj } from "./ScheduleObj.js";
export const getFilteredScheduleItems = ({
scheduleObj,
cusProducts,
@@ -11,7 +9,7 @@ export const getFilteredScheduleItems = ({
cusProducts: (FullCusProduct | undefined)[];
}) => {
const { schedule, prices } = scheduleObj;
let scheduleItems = schedule.phases[0].items;
const scheduleItems = schedule.phases[0].items;
let curPrices: any[] = [];
for (const cusProduct of cusProducts) {
@@ -20,14 +18,14 @@ export const getFilteredScheduleItems = ({
}
}
let products = cusProducts
const products = cusProducts
.filter((cp): cp is FullCusProduct => !!cp)
.map((cp: FullCusProduct) => fullCusProductToProduct(cp));
.map((cp: FullCusProduct) => cusProductToProduct({ cusProduct: cp }));
return scheduleItems.filter((scheduleItem: any) => {
let stripePrice = prices.find((price) => price.id === scheduleItem.price);
const stripePrice = prices.find((price) => price.id === scheduleItem.price);
let inCurProduct =
const inCurProduct =
curPrices.some(
(price) =>
price.config?.stripe_price_id === scheduleItem.price ||

View File

@@ -223,10 +223,10 @@ export const getUnlimitedAndUsageAllowed = ({
);
const usageAllowed = cusEnts.some(
(ent) =>
ent.internal_feature_id === internalFeatureId &&
ent.usage_allowed &&
(includeUsageLimit ? nullish(ent.entitlement.usage_limit) : true),
(ce) =>
ce.internal_feature_id === internalFeatureId &&
ce.usage_allowed &&
(includeUsageLimit ? nullish(ce.entitlement.usage_limit) : true),
);
return { unlimited, usageAllowed };

View File

@@ -1,12 +1,10 @@
import {
ACTIVE_STATUSES,
ApiCusProductSchema,
ApiCusPlanSchema,
ApiVersion,
type ApiVersionClass,
type AppEnv,
AttachScenario,
CusProductStatus,
cusProductToPrices,
type Entity,
type FixedPriceConfig,
type FullCusProduct,
@@ -20,7 +18,6 @@ import {
} from "@autumn/shared";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { createStripeCli } from "@/external/connect/createStripeCli.js";
import { getStripeSubs } from "@/external/stripe/stripeSubUtils.js";
import { addProductsUpdatedWebhookTask } from "@/internal/analytics/handlers/handleProductsUpdated.js";
import { ProductService } from "@/internal/products/ProductService.js";
import {
@@ -28,7 +25,7 @@ import {
getUsageTier,
} from "@/internal/products/prices/priceUtils.js";
import { isDefaultTrialFullProduct } from "@/internal/products/productUtils/classifyProduct.js";
import { isFreeProduct, isOneOff } from "@/internal/products/productUtils.js";
import { isFreeProduct } from "@/internal/products/productUtils.js";
import { notNullish, nullish } from "@/utils/genUtils.js";
import type { ExtendedRequest } from "@/utils/models/Request.js";
import { handleAddProduct } from "../attach/attachFunctions/addProductFlow/handleAddProduct.js";
@@ -38,80 +35,6 @@ import { CusProductService, RELEVANT_STATUSES } from "./CusProductService.js";
import { getRelatedCusEnt } from "./cusPrices/cusPriceUtils.js";
import { getExistingCusProducts } from "./cusProductUtils/getExistingCusProducts.js";
// 1. Cancel cusProductSubscriptions
// CAN DELETE
export const cancelCusProductSubscriptions = async ({
cusProduct,
org,
env,
excludeIds,
expireImmediately = true,
logger,
prorate = true,
}: {
cusProduct: FullCusProduct;
org: Organization;
env: AppEnv;
excludeIds?: string[];
expireImmediately?: boolean;
logger: any;
prorate?: boolean;
}) => {
// 1. Cancel all subscriptions
const stripeCli = createStripeCli({
org: org,
env: env,
});
let latestSubEnd: number | undefined;
if (cusProduct.subscription_ids && cusProduct.subscription_ids.length > 0) {
const stripeSubs = await getStripeSubs({
stripeCli,
subIds: cusProduct.subscription_ids,
});
latestSubEnd = stripeSubs?.[0]?.items.data[0].current_period_end;
}
const cancelStripeSub = async (subId: string) => {
if (excludeIds && excludeIds.includes(subId)) {
return;
}
try {
if (expireImmediately) {
await stripeCli.subscriptions.cancel(subId, {
prorate: prorate,
});
} else {
await stripeCli.subscriptions.update(subId, {
cancel_at: latestSubEnd || undefined,
cancel_at_period_end: latestSubEnd ? undefined : true,
});
}
logger.info(`Cancelled stripe subscription ${subId}, org: ${org.slug}`);
} catch (error: any) {
if (error.code !== "resource_missing") {
console.log(
`Error canceling stripe subscription ${error.code}: ${error.message}`,
);
} // else subscription probably already cancelled
}
};
if (cusProduct.subscription_ids && cusProduct.subscription_ids.length > 0) {
const batchCancel = [];
for (const subId of cusProduct.subscription_ids) {
batchCancel.push(cancelStripeSub(subId));
}
await Promise.all(batchCancel);
return true;
}
return false;
};
export const getDefaultProduct = async ({
req,
productGroup,
@@ -160,22 +83,6 @@ export const activateDefaultProduct = async ({
p.group === productGroup && !isDefaultTrialFullProduct({ product: p }),
);
// const defaultableProducts = {
// free: defaultProducts.filter(
// (p) => p.group === productGroup && isFreeProduct(p.prices),
// ),
// paid: defaultProducts.filter(
// (p) =>
// p.group === productGroup && isDefaultTrialFullProduct({ product: p }),
// ),
// };
// if (defaultableProducts.paid.length > 0) {
// defaultProd = defaultableProducts.paid[0];
// } else if (defaultableProducts.free.length > 0) {
// defaultProd = defaultableProducts.free[0];
// }
if (!defaultProd) return false;
if (curCusProduct?.internal_product_id === defaultProd.internal_id) {
@@ -221,96 +128,6 @@ export const activateDefaultProduct = async ({
});
return true;
// if (!isDefaultTrial) {
// const existingDefaultProduct = fullCus.customer_products.find(
// (cp) =>
// cp.product.internal_id === defaultProd?.internal_id &&
// ACTIVE_STATUSES.includes(cp.status),
// );
// if (existingDefaultProduct) {
// logger.info(
// `Default product ${defaultProd?.name} already exists for customer`,
// );
// return false;
// }
// await handleAddProduct({
// req,
// attachParams: newCusToAttachParams({
// req,
// newCus: fullCus,
// products: [defaultProd],
// stripeCli,
// }),
// });
// return true;
// } else if (isDefaultTrial && defaultableProducts.free.length > 0) {
// defaultProd = defaultableProducts.free[0];
// // Check if the free default product already exists to prevent duplicates
// const existingFreeProduct = fullCus.customer_products.find(
// (cp) =>
// cp.product.internal_id === defaultProd!.internal_id &&
// (cp.status === CusProductStatus.Active ||
// cp.status === CusProductStatus.PastDue),
// );
// if (existingFreeProduct) {
// logger.info(
// `Free default product ${defaultProd?.name} already exists for customer`,
// );
// return false;
// }
// await handleAddProduct({
// req,
// attachParams: newCusToAttachParams({
// req,
// newCus: fullCus,
// products: [defaultProd],
// stripeCli,
// }),
// config: getDefaultAttachConfig(),
// });
// return true;
// }
};
export const expireAndActivate = async ({
req,
cusProduct,
fullCus,
}: {
req: ExtendedRequest;
cusProduct: FullCusProduct;
fullCus: FullCustomer;
}) => {
const { db, org, env, logger } = req;
// 1. Expire current product
await CusProductService.update({
db,
cusProductId: cusProduct.id,
updates: { status: CusProductStatus.Expired, ended_at: Date.now() },
});
// Check if it's one time product
const prices = cusProductToPrices({ cusProduct });
const product = cusProduct.product;
const isOneOffOrAddOn = product.is_add_on || isOneOff(prices);
if (isOneOffOrAddOn || notNullish(cusProduct.internal_entity_id)) {
return;
}
await activateDefaultProduct({
req,
productGroup: cusProduct.product.group,
fullCus,
});
};
export const activateFutureProduct = async ({
@@ -356,45 +173,8 @@ export const activateFutureProduct = async ({
});
return futureProduct;
// if (subIsPrematurelyCanceled(subscription)) {
// console.log(
// " 🔔 Subscription prematurely canceled, deleting scheduled products"
// );
// await deleteScheduledIds({
// stripeCli,
// scheduledIds: futureProduct.scheduled_ids || [],
// });
// await CusProductService.delete({
// db,
// cusProductId: futureProduct.id,
// });
// return false;
// } else {
// await CusProductService.update({
// db,
// cusProductId: futureProduct.id,
// updates: { status: CusProductStatus.Active },
// });
// await addProductsUpdatedWebhookTask({
// req,
// internalCustomerId: cusProduct.internal_customer_id,
// org,
// env,
// customerId: null,
// scenario: AttachScenario.New,
// cusProduct: futureProduct,
// logger,
// });
// return true;
// }
};
// GET CUS ENTS FROM CUS PRODUCTS
export const processFullCusProduct = ({
cusProduct,
subs,
@@ -512,7 +292,7 @@ export const processFullCusProduct = ({
};
}
return ApiCusProductSchema.parse({
return ApiCusPlanSchema.parse({
id: cusProduct.product.id,
name: cusProduct.product.name,
group: cusProduct.product.group || null,
@@ -552,15 +332,6 @@ export const processFullCusProduct = ({
}
};
// GET CUSTOMER PRODUCT & ORG IN PARALLEL
export const fullCusProductToProduct = (cusProduct: FullCusProduct) => {
return {
...cusProduct.product,
prices: cusProduct.customer_prices.map((cp) => cp.price),
entitlements: cusProduct.customer_entitlements.map((ce) => ce.entitlement),
};
};
export const searchCusProducts = ({
productId,
internalProductId,

View File

@@ -9,7 +9,7 @@ import { CusSearchService } from "@/internal/customers/CusSearchService.js";
import { OrgService } from "@/internal/orgs/OrgService.js";
import RecaseError, { handleRequestError } from "@/utils/errorUtils.js";
import { handleBatchCustomers } from "../api/batch/handlers/handleBatchCustomers.js";
import { entityRouter } from "../api/entities/entityRouter.js";
import { expressEntityRouter } from "../api/entities/entityRouter.js";
import { toSuccessUrl } from "../orgs/orgUtils/convertOrgUtils.js";
import { CusService } from "./CusService.js";
import { handleAddCouponToCus } from "./handlers/handleAddCouponToCus.js";
@@ -63,11 +63,6 @@ expressCusRouter.post(
expressCusRouter.post("/:customer_id/balances", handleUpdateBalances);
// cusRouter.post(
// "/customer_products/:customer_product_id",
// handleCusProductExpired
// );
expressCusRouter.get(
"/:customer_id/billing_portal",
async (req: any, res: any) => {
@@ -143,7 +138,7 @@ expressCusRouter.post(
expressCusRouter.post("/:customer_id/coupons/:coupon_id", handleAddCouponToCus);
expressCusRouter.use("/:customer_id/entities", entityRouter);
expressCusRouter.use("/:customer_id/entities", expressEntityRouter);
expressCusRouter.post("/:customer_id/transfer", handleTransferProduct);

View File

@@ -1,33 +1,38 @@
import {
type ApiCusFeature,
type ApiCusRollover,
ApiFeatureType,
type ApiFeature,
cusEntToKey,
type EntInterval,
entIntvToResetIntv,
type FullCusEntWithFullCusProduct,
getRolloverFields,
notNullish,
ResetInterval,
toIntervalCountResponse,
} from "@autumn/shared";
import { getCusFeatureType } from "@/internal/features/featureUtils.js";
export const cusEntsToInterval = ({
cusEnts,
}: {
cusEnts: FullCusEntWithFullCusProduct[];
}): {
interval: EntInterval | "multiple" | null;
interval_count: number | null;
interval: ResetInterval | null;
interval_count: number | undefined;
} => {
const cusEntKeys = cusEnts.map((cusEnt) => cusEntToKey({ cusEnt }));
const uniqueCusEntKeys = [...new Set(cusEntKeys)];
if (uniqueCusEntKeys.length === 1) {
return {
interval: cusEnts[0].entitlement.interval || null,
interval_count: cusEnts[0].entitlement.interval_count,
interval: entIntvToResetIntv({
entInterval: cusEnts[0].entitlement.interval,
}),
interval_count: toIntervalCountResponse({
intervalCount: cusEnts[0].entitlement.interval_count,
}),
};
}
return { interval: "multiple", interval_count: null };
return { interval: null, interval_count: undefined };
};
export const cusEntsToNextResetAt = ({
@@ -76,39 +81,66 @@ export const cusEntsToRollovers = ({
export const getBooleanApiCusFeature = ({
cusEnts,
apiFeature,
}: {
cusEnts: FullCusEntWithFullCusProduct[];
apiFeature?: ApiFeature;
}): ApiCusFeature => {
const feature = cusEnts[0].entitlement.feature;
return {
id: feature.id,
type: ApiFeatureType.Static,
name: feature.name,
feature: apiFeature,
feature_id: feature.id,
unlimited: false,
starting_balance: 0,
balance: 0,
usage: 0,
included_usage: 0,
next_reset_at: null,
unlimited: false,
overage_allowed: false,
};
resets_at: null,
reset_interval: ResetInterval.OneOff,
reset_interval_count: undefined,
breakdown: undefined,
rollovers: undefined,
// Old
// id: feature.id,
// type: ApiFeatureType.Static,
// name: feature.name,
// balance: 0,
// usage: 0,
// included_usage: 0,
// next_reset_at: null,
// unlimited: false,
// overage_allowed: false,
} as ApiCusFeature;
};
export const getUnlimitedApiCusFeature = ({
apiFeature,
cusEnts,
}: {
apiFeature?: ApiFeature;
cusEnts: FullCusEntWithFullCusProduct[];
}): ApiCusFeature => {
const feature = cusEnts[0].entitlement.feature;
return {
id: feature.id,
type: getCusFeatureType({ feature }),
name: feature.name,
feature: apiFeature,
feature_id: feature.id,
unlimited: true,
starting_balance: 0,
balance: 0,
usage: 0,
included_usage: 0,
next_reset_at: null,
unlimited: true,
overage_allowed: false,
resets_at: null,
reset_interval: ResetInterval.OneOff,
reset_interval_count: undefined,
breakdown: undefined,
rollovers: undefined,
};
};

View File

@@ -6,10 +6,11 @@ import {
type ApiCusFeatureBreakdown,
ApiCusFeatureBreakdownSchema,
ApiCusFeatureSchema,
CusExpand,
cusEntToBalance,
cusEntToIncludedUsage,
cusEntToKey,
cusEntToUsageLimit,
cusEntToMaxPurchase,
type Feature,
FeatureType,
getCusEntBalance,
@@ -20,7 +21,6 @@ import {
import { Decimal } from "decimal.js";
import type { RequestContext } from "@/honoUtils/HonoEnv.js";
import { getUnlimitedAndUsageAllowed } from "@/internal/customers/cusProducts/cusEnts/cusEntUtils.js";
import { getCusFeatureType } from "@/internal/features/featureUtils.js";
import {
cusEntsToInterval,
cusEntsToNextResetAt,
@@ -60,11 +60,12 @@ const cusEntsToBreakdown = ({
cusEnts,
feature,
});
breakdown.push(
ApiCusFeatureBreakdownSchema.parse({
...breakdownItem,
interval,
interval_count,
reset_interval: interval,
reset_interval_count: interval_count,
}),
);
}
@@ -85,21 +86,27 @@ export const getApiCusFeature = ({
}) => {
const entityId = fullCus.entity?.id;
const apiFeature = ctx.expand.includes(CusExpand.Feature)
? toApiFeature({ feature })
: undefined;
// 1. If feature is boolean
if (feature.type === FeatureType.Boolean) {
return getBooleanApiCusFeature({
cusEnts,
apiFeature,
});
}
const { unlimited, usageAllowed } = getUnlimitedAndUsageAllowed({
cusEnts: cusEnts,
internalFeatureId: feature.internal_id,
includeUsageLimit: false,
});
// 2. If feature is unlimited
if (unlimited) {
return getUnlimitedApiCusFeature({ cusEnts: cusEnts });
return getUnlimitedApiCusFeature({ apiFeature, cusEnts });
}
const totalBalanceWithRollovers = sumValues(
@@ -130,8 +137,8 @@ export const getApiCusFeature = ({
const nextResetAt = cusEntsToNextResetAt({ cusEnts });
const totalUsageLimit = sumValues(
cusEnts.map((cusEnt) => cusEntToUsageLimit({ cusEnt })),
const totalMaxPurchase = sumValues(
cusEnts.map((cusEnt) => cusEntToMaxPurchase({ cusEnt })),
);
const totalIncludedUsage = sumValues(
@@ -154,30 +161,53 @@ export const getApiCusFeature = ({
const rollovers = cusEntsToRollovers({ cusEnts, entityId });
const apiFeature = toApiFeature({ feature });
// const apiFeature = toApiFeature({ feature });
const { data: apiCusFeature, error } = ApiCusFeatureSchema.safeParse({
id: feature.id,
name: feature.name,
type: getCusFeatureType({ feature }),
balance: totalBalanceWithRollovers,
usage: totalUsage,
included_usage: totalIncludedUsage,
usage_limit:
totalUsageLimit === totalIncludedUsage ? undefined : totalUsageLimit,
next_reset_at: nextResetAt,
feature: ctx.expand.includes(CusExpand.Feature) ? apiFeature : undefined,
feature_id: feature.id,
unlimited: false,
overage_allowed: usageAllowed,
interval,
interval_count,
rollovers,
credit_schema:
apiFeature.credit_schema?.map((credit) => ({
feature_id: credit.metered_feature_id,
credit_amount: credit.credit_cost,
})) || undefined,
starting_balance: totalIncludedUsageWithRollovers,
balance: totalBalanceWithRollovers,
usage: totalUsage,
resets_at: nextResetAt,
reset_interval: interval,
reset_interval_count: interval_count !== 1 ? interval_count : undefined,
// Max purchase...
max_purchase: totalMaxPurchase > 0 ? totalMaxPurchase : undefined,
pay_per_use: usageAllowed || undefined,
breakdown: cusEntsToBreakdown({ ctx, fullCus, cusEnts }),
rollovers,
// Old stuff...
// id: feature.id,
// name: feature.name,
// type: getCusFeatureType({ feature }),
// balance: totalBalanceWithRollovers,
// usage: totalUsage,
// included_usage: totalIncludedUsage,
// usage_limit:
// totalUsageLimit === totalIncludedUsage ? undefined : totalUsageLimit,
// next_reset_at: nextResetAt,
// unlimited: false,
// overage_allowed: usageAllowed,
// interval,
// interval_count,
// rollovers,
// credit_schema:
// apiFeature.credit_schema?.map((credit) => ({
// feature_id: credit.metered_feature_id,
// credit_amount: credit.credit_cost,
// })) || undefined,
// breakdown: cusEntsToBreakdown({ ctx, fullCus, cusEnts }),
});
if (error) throw error;

View File

@@ -0,0 +1,120 @@
import {
ApiCusPlanSchema,
CusExpand,
type CusProductLegacyData,
CusProductStatus,
cusProductToPlanStatus,
cusProductToProduct,
type Entity,
type FullCusProduct,
type FullCustomer,
isTrialing,
type Subscription,
} from "@autumn/shared";
import type { RequestContext } from "@/honoUtils/HonoEnv.js";
import { getPlanResponse } from "@/internal/products/productUtils/productResponseUtils/getPlanResponse.js";
export const getApiCusPlan = async ({
ctx,
fullCus,
cusProduct,
entities,
}: {
ctx: RequestContext;
fullCus: FullCustomer;
cusProduct: FullCusProduct;
entities?: Entity[];
}) => {
const trialing =
cusProduct.trial_ends_at && cusProduct.trial_ends_at > Date.now();
const fullProduct = cusProductToProduct({ cusProduct });
const subId = cusProduct.subscription_ids?.[0];
const autumnSub = fullCus.subscriptions?.find(
(s) => s.id === subId || (s as Subscription).stripe_id === subId,
);
let stripeSubData: {
current_period_start: number | null;
current_period_end: number | null;
} = {
current_period_start: null,
current_period_end: null,
};
if (autumnSub) {
stripeSubData = {
current_period_end: autumnSub?.current_period_end
? autumnSub.current_period_end * 1000
: null,
current_period_start: autumnSub?.current_period_start
? autumnSub.current_period_start * 1000
: null,
};
}
if (!subId && trialing) {
stripeSubData = {
current_period_start: cusProduct.starts_at,
current_period_end: cusProduct.trial_ends_at || null,
};
}
const status = cusProductToPlanStatus({ status: cusProduct.status });
// Check if we should expand the plan object
const shouldExpandPlan = ctx.expand.includes(CusExpand.Plan);
const apiPlan = shouldExpandPlan
? await getPlanResponse({
product: fullProduct,
features: ctx.features,
})
: undefined;
const apiCusPlan = ApiCusPlanSchema.parse({
plan: apiPlan,
plan_id: fullProduct.id,
add_on: fullProduct.is_add_on,
status,
past_due: cusProduct.status === CusProductStatus.PastDue,
canceled_at: cusProduct.canceled_at || null,
expires_at: cusProduct.ended_at || null,
trial_ends_at: isTrialing({ cusProduct }) ? cusProduct.trial_ends_at : null,
started_at: cusProduct.starts_at,
quantity: cusProduct.quantity,
current_period_start: stripeSubData?.current_period_start || null,
current_period_end: stripeSubData?.current_period_end || null,
});
return {
data: apiCusPlan,
legacyData: {
subscription_id: subId || undefined,
options: cusProduct.options,
features: ctx.features,
} satisfies CusProductLegacyData,
};
};
// const apiCusProduct = ApiCusProductSchema.parse({
// id: fullProduct.id,
// name: fullProduct.name,
// group: fullProduct.group || null,
// status: trialing ? CusProductStatus.Trialing : cusProduct.status,
// canceled_at: cusProduct.canceled_at || null,
// is_default: fullProduct.is_default || false,
// is_add_on: fullProduct.is_add_on || false,
// version: fullProduct.version,
// quantity: cusProduct.quantity,
// started_at: cusProduct.starts_at,
// entity_id: entity?.id || cusProduct.entity_id || undefined,
// ...stripeSubData,
// items: v2Product.items,
// });

View File

@@ -0,0 +1,79 @@
import {
ACTIVE_STATUSES,
type ApiCusPlan,
type CusProductLegacyData,
type CusProductStatus,
type FullCustomer,
} from "@autumn/shared";
import type { RequestContext } from "@/honoUtils/HonoEnv.js";
import { getApiCusPlan } from "./getApiCusPlan.js";
const mergeCusPlanResponses = ({
cusPlanResponses,
}: {
cusPlanResponses: ApiCusPlan[];
}) => {
const getPlanKey = (cp: ApiCusPlan) => {
const status = ACTIVE_STATUSES.includes(cp.status as CusProductStatus)
? "active"
: cp.status;
return `${cp.plan_id}:${status}`;
};
const record: Record<string, any> = {};
for (const curr of cusPlanResponses) {
const key = getPlanKey(curr);
const latest = record[key];
const currStartedAt = curr.started_at;
record[key] = {
...(latest || curr),
canceled_at: curr.canceled_at
? curr.canceled_at
: latest?.canceled_at || null,
started_at: latest?.started_at
? Math.min(latest?.started_at, currStartedAt)
: currStartedAt,
quantity: (latest?.quantity || 0) + (curr?.quantity || 0),
};
}
return Object.values(record);
};
export const getApiCusPlans = async ({
ctx,
fullCus,
}: {
ctx: RequestContext;
fullCus: FullCustomer;
}) => {
// Process full cus products
const apiCusPlans: ApiCusPlan[] = [];
const cusProducts = fullCus.customer_products;
const legacyData: Record<string, CusProductLegacyData> = {};
for (const cusProduct of cusProducts) {
const processed = await getApiCusPlan({
cusProduct,
ctx,
fullCus,
entities: fullCus.entities,
});
apiCusPlans.push(processed.data);
legacyData[processed.data.plan_id] = processed.legacyData;
}
const merged = mergeCusPlanResponses({
cusPlanResponses: apiCusPlans,
});
return {
apiCusPlans: merged,
legacyData,
};
};

View File

@@ -1,236 +0,0 @@
import {
ApiCusProductSchema,
type CusProductLegacyData,
CusProductStatus,
cusProductToProduct,
type Entity,
type FullCusProduct,
type FullCustomer,
type Subscription,
} from "@autumn/shared";
import type { RequestContext } from "@/honoUtils/HonoEnv.js";
import { getProductResponse } from "@/internal/products/productUtils/productResponseUtils/getProductResponse.js";
// const prices = cusProduct.customer_prices.map((cp) => {
// const price = cp.price;
// if (price.config?.type === PriceType.Fixed) {
// const config = price.config as FixedPriceConfig;
// return {
// amount: config.amount,
// interval: config.interval,
// };
// } else {
// const config = price.config as UsagePriceConfig;
// const priceOptions = getPriceOptions(price, cusProduct.options);
// const usageTier = getUsageTier(price, priceOptions?.quantity!);
// const cusEnt = getRelatedCusEnt({
// cusPrice: cp,
// cusEnts: cusProduct.customer_entitlements,
// });
// const ent = cusEnt?.entitlement;
// const singleTier =
// ent?.allowance === 0 && config.usage_tiers.length === 1;
// if (singleTier) {
// return {
// amount: usageTier.amount,
// interval: config.interval,
// quantity: priceOptions?.quantity,
// };
// } else {
// // Add allowance to tiers
// const allowance = ent?.allowance;
// let tiers;
// if (notNullish(allowance) && allowance! > 0) {
// tiers = [
// {
// to: allowance,
// amount: 0,
// },
// ...config.usage_tiers.map((tier) => {
// const isLastTier = tier.to === -1 || tier.to === TierInfinite;
// return {
// to: isLastTier ? tier.to : Number(tier.to) + allowance!,
// amount: tier.amount,
// };
// }),
// ];
// } else {
// tiers = config.usage_tiers.map((tier) => {
// const isLastTier = tier.to === -1 || tier.to === TierInfinite;
// return {
// to: isLastTier ? tier.to : Number(tier.to) + allowance!,
// amount: tier.amount,
// };
// });
// }
// return {
// tiers: tiers,
// name: "",
// quantity: priceOptions?.quantity,
// };
// }
// }
// });
export const getApiCusProduct = async ({
ctx,
fullCus,
cusProduct,
entities,
}: {
ctx: RequestContext;
fullCus: FullCustomer;
cusProduct: FullCusProduct;
entities?: Entity[];
}) => {
const { org } = ctx;
const trialing =
cusProduct.trial_ends_at && cusProduct.trial_ends_at > Date.now();
const fullProduct = cusProductToProduct({ cusProduct });
const v2Product = await getProductResponse({
product: fullProduct,
features: ctx.features,
fullCus,
currency: org.default_currency || "usd",
options: cusProduct.options,
withDisplay: false,
});
const entity = entities?.find(
(e) => e.internal_id === cusProduct.internal_entity_id,
);
const subId = cusProduct.subscription_ids?.[0];
const autumnSub = fullCus.subscriptions?.find(
(s) => s.id === subId || (s as Subscription).stripe_id === subId,
);
let stripeSubData = {};
if (autumnSub) {
stripeSubData = {
current_period_end: autumnSub?.current_period_end
? autumnSub.current_period_end * 1000
: null,
current_period_start: autumnSub?.current_period_start
? autumnSub.current_period_start * 1000
: null,
};
}
if (!subId && trialing) {
stripeSubData = {
current_period_start: cusProduct.starts_at,
current_period_end: cusProduct.trial_ends_at,
};
}
const apiCusProduct = ApiCusProductSchema.parse({
id: fullProduct.id,
name: fullProduct.name,
group: fullProduct.group || null,
status: trialing ? CusProductStatus.Trialing : cusProduct.status,
canceled_at: cusProduct.canceled_at || null,
is_default: fullProduct.is_default || false,
is_add_on: fullProduct.is_add_on || false,
version: fullProduct.version,
quantity: cusProduct.quantity,
started_at: cusProduct.starts_at,
entity_id: entity?.id || cusProduct.entity_id || undefined,
...stripeSubData,
items: v2Product.items,
});
return {
data: apiCusProduct,
legacyData: {
subscription_id: subId || undefined,
} satisfies CusProductLegacyData,
};
// if (subIds && subIds.length > 0 && apiVersion.gte(ApiVersion.V0_2)) {
// const baseSub = subs?.find(
// (s) => s.id === subIds[0] || (s as Subscription).stripe_id === subIds[0],
// );
// stripeSubData = {
// current_period_end: baseSub?.current_period_end
// ? baseSub.current_period_end * 1000
// : null,
// current_period_start: baseSub?.current_period_start
// ? baseSub.current_period_start * 1000
// : null,
// };
// }
// if (!subIds && trialing) {
// stripeSubData = {
// current_period_start: cusProduct.starts_at,
// current_period_end: cusProduct.trial_ends_at,
// };
// }
// if (apiVersion.gte(ApiVersion.V1_1)) {
// if ((!subIds || subIds.length === 0) && trialing) {
// stripeSubData = {
// current_period_start: cusProduct.starts_at,
// current_period_end: cusProduct.trial_ends_at,
// };
// }
// const fullProduct = fullCusProductToProduct(cusProduct);
// const v2Product = await getProductResponse({
// product: fullProduct,
// features,
// withDisplay: false,
// options: cusProduct.options,
// });
// return ApiCusProductSchema.parse({
// id: fullProduct.id,
// name: fullProduct.name,
// group: fullProduct.group || null,
// status: trialing ? CusProductStatus.Trialing : cusProduct.status,
// canceled_at: cusProduct.canceled_at || null,
// is_default: fullProduct.is_default || false,
// is_add_on: fullProduct.is_add_on || false,
// version: fullProduct.version,
// quantity: cusProduct.quantity,
// started_at: cusProduct.starts_at,
// entity_id: entity?.id || cusProduct.entity_id || undefined,
// ...stripeSubData,
// items: v2Product.items,
// });
// } else {
// const cusProductResponse = {
// id: cusProduct.product.id,
// name: cusProduct.product.name,
// group: cusProduct.product.group,
// status: trialing ? CusProductStatus.Trialing : cusProduct.status,
// created_at: cusProduct.created_at,
// canceled_at: cusProduct.canceled_at,
// processor: {
// type: cusProduct.processor?.type,
// subscription_id: cusProduct.processor?.subscription_id || null,
// },
// subscription_ids: cusProduct.subscription_ids || [],
// prices: prices,
// starts_at: cusProduct.starts_at,
// ...stripeSubData,
// };
// return cusProductResponse;
// }
};

View File

@@ -1,87 +0,0 @@
import {
ACTIVE_STATUSES,
type ApiCusProduct,
type CusProductLegacyData,
type CusProductStatus,
type FullCustomer,
} from "@autumn/shared";
import type { RequestContext } from "@/honoUtils/HonoEnv.js";
import { getApiCusProduct } from "./getApiCusProduct.js";
const mergeCusProductResponses = ({
cusProductResponses,
}: {
cusProductResponses: ApiCusProduct[];
}) => {
const getProductKey = (product: ApiCusProduct) => {
const status = ACTIVE_STATUSES.includes(product.status as CusProductStatus)
? "active"
: product.status;
return `${product.id}:${status}`;
};
const record: Record<string, any> = {};
for (const curr of cusProductResponses) {
const key = getProductKey(curr);
const latest = record[key];
const currStartedAt = curr.started_at;
record[key] = {
...(latest || curr),
version: Math.max(latest?.version || 1, curr?.version || 1),
canceled_at: curr.canceled_at
? curr.canceled_at
: latest?.canceled_at || null,
started_at: latest?.started_at
? Math.min(latest?.started_at, currStartedAt)
: currStartedAt,
quantity: (latest?.quantity || 0) + (curr?.quantity || 0),
};
}
return Object.values(record);
};
export const getApiCusProducts = async ({
ctx,
fullCus,
}: {
ctx: RequestContext;
fullCus: FullCustomer;
}) => {
// Process full cus products
const apiCusProducts: ApiCusProduct[] = [];
// const inStatuses = ctx.org.config.include_past_due
// ? [CusProductStatus.Active, CusProductStatus.PastDue]
// : [CusProductStatus.Active];
// const cusProducts = fullCus.customer_products.filter((cp) =>
// inStatuses.includes(cp.status),
// );
const cusProducts = fullCus.customer_products;
const legacyData: Record<string, CusProductLegacyData> = {};
for (const cusProduct of cusProducts) {
const processed = await getApiCusProduct({
cusProduct,
ctx,
fullCus,
entities: fullCus.entities,
});
apiCusProducts.push(processed.data);
legacyData[processed.data.id] = processed.legacyData;
}
const merged = mergeCusProductResponses({
cusProductResponses: apiCusProducts,
});
return {
apiCusProducts: merged,
legacyData,
};
};

View File

@@ -3,25 +3,22 @@ import {
type ApiCustomer,
ApiCustomerSchema,
applyResponseVersionChanges,
type CusExpand,
type CustomerLegacyData,
type FullCustomer,
} from "@autumn/shared";
import { z } from "zod/v4";
import type { RequestContext } from "@/honoUtils/HonoEnv.js";
import { getApiCusFeatures } from "./getApiCusFeature/getApiCusFeatures.js";
import { getApiCusProducts } from "./getApiCusProduct/getApiCusProducts.js";
import { getApiCusPlans } from "./getApiCusPlan/getApiCusPlans.js";
import { getApiCustomerExpand } from "./getApiCustomerExpand.js";
export const getApiCustomer = async ({
ctx,
fullCus,
expand,
withAutumnId = false,
}: {
ctx: RequestContext;
fullCus: FullCustomer;
expand: CusExpand[];
withAutumnId?: boolean;
}) => {
const apiCusFeatures = await getApiCusFeatures({
@@ -29,8 +26,8 @@ export const getApiCustomer = async ({
fullCus,
});
const { apiCusProducts, legacyData: cusProductLegacyData } =
await getApiCusProducts({
const { apiCusPlans, legacyData: cusProductLegacyData } =
await getApiCusPlans({
ctx,
fullCus,
});
@@ -38,7 +35,6 @@ export const getApiCustomer = async ({
const apiCusExpand = await getApiCustomerExpand({
ctx,
fullCus,
expand,
});
const apiCustomer = ApiCustomerSchema.extend({
@@ -56,16 +52,16 @@ export const getApiCustomer = async ({
env: fullCus.env,
metadata: fullCus.metadata,
products: apiCusProducts,
plans: apiCusPlans,
features: apiCusFeatures,
...apiCusExpand,
});
return applyResponseVersionChanges<ApiCustomer, CustomerLegacyData>({
input: apiCustomer,
legacyData: {
cusProductLegacyData,
},
legacyData: { cusProductLegacyData },
targetVersion: ctx.apiVersion,
resource: AffectedResource.Customer,
});

View File

@@ -1,6 +1,6 @@
import {
ApiBaseEntitySchema,
type ApiCustomerExpand,
type ApiCusExpand,
CusExpand,
type FullCusProduct,
type FullCustomer,
@@ -15,13 +15,12 @@ import { getCusUpcomingInvoice } from "../cusResponseUtils/getCusUpcomingInvoice
export const getApiCustomerExpand = async ({
ctx,
fullCus,
expand,
}: {
ctx: AutumnContext;
fullCus: FullCustomer;
expand: CusExpand[];
}): Promise<ApiCustomerExpand> => {
const { org, env, db, logger } = ctx;
}): Promise<ApiCusExpand> => {
const { org, env, db, logger, expand } = ctx;
console.log("Expand: ", expand);
const getCusTrialsUsed = () => {
if (expand.includes(CusExpand.TrialsUsed)) {
return fullCus.trials_used;
@@ -43,6 +42,8 @@ export const getApiCustomerExpand = async ({
})
: undefined;
const cusExpand = expand as CusExpand[];
const [rewards, upcomingInvoice, referrals, paymentMethod] =
await Promise.all([
getCusRewards({
@@ -52,25 +53,25 @@ export const getApiCustomerExpand = async ({
subIds: fullCus.customer_products.flatMap(
(cp: FullCusProduct) => cp.subscription_ids || [],
),
expand,
expand: cusExpand,
}),
getCusUpcomingInvoice({
db,
org,
env,
fullCus,
expand,
expand: cusExpand,
}),
getCusReferrals({
db,
fullCus,
expand,
expand: cusExpand,
}),
getCusPaymentMethodRes({
org,
env,
fullCus,
expand,
expand: cusExpand,
}),
]);

View File

@@ -1,191 +0,0 @@
import {
ApiCusProductSchema,
ApiVersion,
type ApiVersionClass,
CusProductStatus,
type Entity,
type Feature,
type FixedPriceConfig,
type FullCusProduct,
type Organization,
PriceType,
type Subscription,
TierInfinite,
type UsagePriceConfig,
} from "@autumn/shared";
import {
getPriceOptions,
getUsageTier,
} from "@/internal/products/prices/priceUtils.js";
import { getProductResponse } from "@/internal/products/productUtils/productResponseUtils/getProductResponse.js";
import { notNullish } from "@/utils/genUtils.js";
import { getRelatedCusEnt } from "../../cusProducts/cusPrices/cusPriceUtils.js";
import { fullCusProductToProduct } from "../../cusProducts/cusProductUtils.js";
export const getCusProductResponse = async ({
cusProduct,
subs,
org,
apiVersion,
features,
entity,
}: {
cusProduct: FullCusProduct;
org: Organization;
subs?: Subscription[];
apiVersion: ApiVersionClass;
features: Feature[];
entity?: Entity;
}) => {
// Process prices
const prices = cusProduct.customer_prices.map((cp) => {
const price = cp.price;
if (price.config?.type === PriceType.Fixed) {
const config = price.config as FixedPriceConfig;
return {
amount: config.amount,
interval: config.interval,
};
} else {
const config = price.config as UsagePriceConfig;
const priceOptions = getPriceOptions(price, cusProduct.options);
const usageTier = getUsageTier(price, priceOptions?.quantity!);
const cusEnt = getRelatedCusEnt({
cusPrice: cp,
cusEnts: cusProduct.customer_entitlements,
});
const ent = cusEnt?.entitlement;
const singleTier =
ent?.allowance === 0 && config.usage_tiers.length === 1;
if (singleTier) {
return {
amount: usageTier.amount,
interval: config.interval,
quantity: priceOptions?.quantity,
};
} else {
// Add allowance to tiers
const allowance = ent?.allowance;
let tiers;
if (notNullish(allowance) && allowance! > 0) {
tiers = [
{
to: allowance,
amount: 0,
},
...config.usage_tiers.map((tier) => {
const isLastTier = tier.to === -1 || tier.to === TierInfinite;
return {
to: isLastTier ? tier.to : Number(tier.to) + allowance!,
amount: tier.amount,
};
}),
];
} else {
tiers = config.usage_tiers.map((tier) => {
const isLastTier = tier.to === -1 || tier.to === TierInfinite;
return {
to: isLastTier ? tier.to : Number(tier.to) + allowance!,
amount: tier.amount,
};
});
}
return {
tiers: tiers,
name: "",
quantity: priceOptions?.quantity,
};
}
}
});
const trialing =
cusProduct.trial_ends_at && cusProduct.trial_ends_at > Date.now();
const subIds = cusProduct.subscription_ids;
let stripeSubData = {};
if (subIds && subIds.length > 0 && apiVersion.gte(ApiVersion.V0_2)) {
const baseSub = subs?.find(
(s) => s.id === subIds[0] || (s as Subscription).stripe_id === subIds[0],
);
stripeSubData = {
current_period_end: baseSub?.current_period_end
? baseSub.current_period_end * 1000
: null,
current_period_start: baseSub?.current_period_start
? baseSub.current_period_start * 1000
: null,
};
}
if (!subIds && trialing) {
stripeSubData = {
current_period_start: cusProduct.starts_at,
current_period_end: cusProduct.trial_ends_at,
};
}
if (apiVersion.gte(ApiVersion.V1_1)) {
if ((!subIds || subIds.length === 0) && trialing) {
stripeSubData = {
current_period_start: cusProduct.starts_at,
current_period_end: cusProduct.trial_ends_at,
};
}
const fullProduct = fullCusProductToProduct(cusProduct);
const v2Product = await getProductResponse({
product: fullProduct,
features,
withDisplay: false,
options: cusProduct.options,
});
return ApiCusProductSchema.parse({
id: fullProduct.id,
name: fullProduct.name,
group: fullProduct.group || null,
status: trialing ? CusProductStatus.Trialing : cusProduct.status,
canceled_at: cusProduct.canceled_at || null,
is_default: fullProduct.is_default || false,
is_add_on: fullProduct.is_add_on || false,
version: fullProduct.version,
quantity: cusProduct.quantity,
started_at: cusProduct.starts_at,
entity_id: entity?.id || cusProduct.entity_id || undefined,
...stripeSubData,
items: v2Product.items,
});
} else {
const cusProductResponse = {
id: cusProduct.product.id,
name: cusProduct.product.name,
group: cusProduct.product.group,
status: trialing ? CusProductStatus.Trialing : cusProduct.status,
created_at: cusProduct.created_at,
canceled_at: cusProduct.canceled_at,
processor: {
type: cusProduct.processor?.type,
subscription_id: cusProduct.processor?.subscription_id || null,
},
subscription_ids: cusProduct.subscription_ids || [],
prices: prices,
starts_at: cusProduct.starts_at,
...stripeSubData,
};
return cusProductResponse;
}
};

View File

@@ -1,98 +0,0 @@
import {
ACTIVE_STATUSES,
type ApiCusProduct,
ApiVersion,
type ApiVersionClass,
type CusProductStatus,
type Entity,
type Feature,
type Organization,
} from "@autumn/shared";
import { getCusProductResponse } from "./getCusProductResponse.js";
const mergeCusProductResponses = ({
cusProductResponses,
}: {
cusProductResponses: ApiCusProduct[];
}) => {
const getProductKey = (product: ApiCusProduct) => {
const status = ACTIVE_STATUSES.includes(product.status as CusProductStatus)
? "active"
: product.status;
return `${product.id}:${status}`;
};
const record: Record<string, any> = {};
for (const curr of cusProductResponses) {
const key = getProductKey(curr);
const latest = record[key];
const currStartedAt = curr.started_at;
record[key] = {
...(latest || curr),
version: Math.max(latest?.version || 1, curr?.version || 1),
canceled_at: curr.canceled_at
? curr.canceled_at
: latest?.canceled_at || null,
started_at: latest?.started_at
? Math.min(latest?.started_at, currStartedAt)
: currStartedAt,
quantity: (latest?.quantity || 0) + (curr?.quantity || 0),
};
}
return Object.values(record);
};
export const processFullCusProducts = async ({
fullCusProducts,
subs,
org,
entity,
apiVersion,
features,
}: {
fullCusProducts: any;
subs: any;
org: Organization;
entity?: Entity;
apiVersion: ApiVersionClass;
features: Feature[];
}) => {
// Process full cus products
let main = [];
let addOns = [];
for (const cusProduct of fullCusProducts) {
const processed = await getCusProductResponse({
cusProduct,
subs,
org,
entity,
apiVersion,
features,
});
const isAddOn = cusProduct.product.is_add_on;
if (isAddOn) {
addOns.push(processed);
} else {
main.push(processed);
}
}
if (apiVersion.gte(ApiVersion.V1_1)) {
main = mergeCusProductResponses({
cusProductResponses: main as ApiCusProduct[],
});
addOns = mergeCusProductResponses({
cusProductResponses: addOns as ApiCusProduct[],
});
}
return {
main: main,
addOns: addOns,
};
};

View File

@@ -1,213 +0,0 @@
import {
ApiBaseEntitySchema,
ApiCusFeatureV2Schema,
ApiCustomerSchema,
ApiCustomerV2Schema,
ApiVersion,
type ApiVersionClass,
type AppEnv,
CusExpand,
CusProductStatus,
CustomerResponseSchema,
cusProductsToCusEnts,
cusProductsToCusPrices,
type Feature,
FeatureType,
type FullCusProduct,
type FullCustomer,
type Organization,
type RewardResponse,
} from "@autumn/shared";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { invoicesToResponse } from "@/internal/invoices/invoiceUtils.js";
import { featuresToObject } from "./cusFeatureResponseUtils/balancesToFeatureResponse.js";
import { getCusBalances } from "./cusFeatureResponseUtils/getCusBalances.js";
import { processFullCusProducts } from "./cusProductResponseUtils/processFullCusProducts.js";
import { getCusPaymentMethodRes } from "./cusResponseUtils/getCusPaymentMethodRes.js";
import { getCusReferrals } from "./cusResponseUtils/getCusReferrals.js";
import { getCusRewards } from "./cusResponseUtils/getCusRewards.js";
import { getCusUpcomingInvoice } from "./cusResponseUtils/getCusUpcomingInvoice.js";
import { getCusInvoices } from "./cusUtils.js";
export const getCustomerDetails = async ({
db,
customer,
features,
org,
env,
params = {},
logger,
cusProducts,
expand,
apiVersion,
}: {
db: DrizzleCli;
customer: FullCustomer;
features: Feature[];
org: Organization;
env: AppEnv;
params?: any;
logger: any;
cusProducts: FullCusProduct[];
expand: CusExpand[];
apiVersion: ApiVersionClass;
}) => {
const withRewards = expand.includes(CusExpand.Rewards);
const inStatuses = org.config.include_past_due
? [CusProductStatus.Active, CusProductStatus.PastDue]
: [CusProductStatus.Active];
const cusEnts = cusProductsToCusEnts({ cusProducts, inStatuses }) as any;
const balances = await getCusBalances({
cusEntsWithCusProduct: cusEnts,
cusPrices: cusProductsToCusPrices({ cusProducts, inStatuses }),
org,
apiVersion,
});
const subIds = cusProducts.flatMap(
(cp: FullCusProduct) => cp.subscription_ids || [],
);
const subs = customer.subscriptions || [];
const { main, addOns } = await processFullCusProducts({
fullCusProducts: cusProducts,
subs,
org,
apiVersion,
features,
});
if (apiVersion.gte(ApiVersion.V1_1)) {
let entList: any = balances.map((b) => {
const isBoolean =
features.find((f: Feature) => f.id === b.feature_id)?.type ===
FeatureType.Boolean;
if (b.unlimited || isBoolean) {
return b;
}
return ApiCusFeatureV2Schema.parse({
...b,
usage: b.used,
included_usage: b.allowance,
});
});
const products: any = [...main, ...addOns];
if (apiVersion.gte(ApiVersion.V1_2)) {
entList = featuresToObject({
features,
entList,
});
}
const withInvoices = expand.includes(CusExpand.Invoices);
const rewards: RewardResponse | undefined = await getCusRewards({
org,
env,
fullCus: customer,
subIds,
expand,
});
const upcomingInvoice = await getCusUpcomingInvoice({
db,
org,
env,
fullCus: customer,
expand,
});
const referrals = await getCusReferrals({
db,
fullCus: customer,
expand,
});
const paymentMethod = await getCusPaymentMethodRes({
org,
env,
fullCus: customer,
expand,
});
const apiCustomerSchema = apiVersion.gte(ApiVersion.V1_2)
? ApiCustomerSchema
: ApiCustomerV2Schema;
const cusResponse = {
...apiCustomerSchema.parse({
...customer,
stripe_id: customer.processor?.id,
features: entList,
products,
// invoices: withInvoices ? invoices : undefined,
invoices: withInvoices
? invoicesToResponse({
invoices: customer.invoices || [],
logger,
})
: undefined,
trials_used: expand.includes(CusExpand.TrialsUsed)
? customer.trials_used
: undefined,
rewards: withRewards ? rewards : undefined,
metadata: customer.metadata,
entities: expand.includes(CusExpand.Entities)
? customer.entities.map((e) =>
ApiBaseEntitySchema.parse({
id: e.id,
name: e.name,
customer_id: customer.id,
feature_id: e.feature_id,
created_at: e.created_at,
env: customer.env,
}),
)
: undefined,
referrals,
payment_method: paymentMethod,
upcoming_invoice: upcomingInvoice,
}),
};
if (params?.with_autumn_id === "true") {
return {
...cusResponse,
autumn_id: customer.internal_id,
};
} else {
return cusResponse;
}
} else {
// Probably don't need items...?
// const withItems = org.config.api_version >= BREAK_API_VERSION;
// const withItems = apiVersion.gte(ApiVersion.V0_2);
const processedInvoices = await getCusInvoices({
db,
internalCustomerId: customer.internal_id,
invoices: customer.invoices,
limit: 20,
withItems: false,
features,
});
return {
customer: CustomerResponseSchema.parse(customer),
products: main,
add_ons: addOns,
entitlements: balances,
invoices: processedInvoices,
trials_used: expand.includes(CusExpand.TrialsUsed)
? customer.trials_used
: undefined,
};
}
};

View File

@@ -1,211 +0,0 @@
import {
CusProductStatus,
cusProductToPrices,
ErrCode,
type FullCusProduct,
type FullCustomer,
} from "@autumn/shared";
import { StatusCodes } from "http-status-codes";
import {
ACTIVE_STATUSES,
CusProductService,
} from "@/internal/customers/cusProducts/CusProductService.js";
import { getExistingCusProducts } from "@/internal/customers/cusProducts/cusProductUtils/getExistingCusProducts.js";
import {
cancelCusProductSubscriptions,
expireAndActivate,
} from "@/internal/customers/cusProducts/cusProductUtils.js";
import { isFreeProduct, isOneOff } from "@/internal/products/productUtils.js";
import RecaseError, { handleRequestError } from "@/utils/errorUtils.js";
import type { ExtendedRequest } from "@/utils/models/Request.js";
import { CusService } from "../CusService.js";
export const expireCusProduct = async ({
req,
cusProduct, // cus product to expire
fullCus,
expireImmediately = true,
prorate,
}: {
req: ExtendedRequest;
cusProduct: FullCusProduct;
fullCus: FullCustomer;
expireImmediately: boolean;
prorate: boolean;
}) => {
const { db, org, env, logger } = req;
logger.info("--------------------------------");
logger.info(
`🔔 Expiring cutomer product (${
expireImmediately ? "immediately" : "end of cycle"
})`,
);
logger.info(
`Customer: ${fullCus.id || fullCus.internal_id} (${env}), Org: ${org.id}`,
);
logger.info(
`Product: ${cusProduct.product.name}, Status: ${cusProduct.status}`,
);
// if (cusProduct.status == CusProductStatus.Scheduled) {
// await CusProductService.delete({
// db,
// cusProductId: cusProduct.id,
// });
// return;
// }
// 1. If main product, can't expire if there's scheduled product
const isMain = !cusProduct.product.is_add_on;
const { curScheduledProduct: futureProduct } = getExistingCusProducts({
product: cusProduct.product,
cusProducts: fullCus.customer_products,
internalEntityId: cusProduct.internal_entity_id,
});
if (isMain) {
if (
cusProduct.canceled_at &&
ACTIVE_STATUSES.includes(cusProduct.status) &&
!expireImmediately
) {
throw new RecaseError({
message: `Product ${cusProduct.product.name} is already about to cancel at the end of cycle.`,
code: ErrCode.InvalidRequest,
statusCode: StatusCodes.BAD_REQUEST,
});
}
if (
futureProduct &&
!isFreeProduct(cusProductToPrices({ cusProduct: futureProduct }))
) {
throw new RecaseError({
message: `Please delete scheduled product ${futureProduct.product.name} first`,
code: ErrCode.InvalidRequest,
statusCode: StatusCodes.BAD_REQUEST,
});
}
}
// 2. If expire at cycle end, just cancel subscriptions
if (!expireImmediately) {
// 1. Check if already canceled
await cancelCusProductSubscriptions({
cusProduct,
org,
env,
expireImmediately,
logger,
});
if (isOneOff(cusProduct.customer_prices.map((p) => p.price))) {
await CusProductService.update({
db,
cusProductId: cusProduct.id,
updates: { status: CusProductStatus.Expired },
});
} else {
await CusProductService.update({
db,
cusProductId: cusProduct.id,
updates: { canceled_at: Date.now() },
});
}
return;
}
if (cusProduct.product.is_add_on) {
await cancelCusProductSubscriptions({
cusProduct,
org,
env,
logger,
});
await CusProductService.update({
db,
cusProductId: cusProduct.id,
updates: {
status: CusProductStatus.Expired,
ended_at: Date.now(),
},
});
return;
}
// Remove scheduled products first...
if (futureProduct) {
await CusProductService.delete({
db,
cusProductId: futureProduct.id,
});
}
logger.info(`Expiring current product: ${cusProduct.product.name}`);
await expireAndActivate({
req,
cusProduct,
fullCus,
});
logger.info(`Cancelling stripe subscriptions`);
await cancelCusProductSubscriptions({
cusProduct,
org,
env,
logger,
prorate,
});
return;
};
export const handleCusProductExpired = async (req: any, res: any) => {
try {
const { db } = req;
const customerProductId = req.params.customer_product_id;
const cusProduct = await CusProductService.get({
db,
id: customerProductId,
orgId: req.orgId,
env: req.env,
withCustomer: true,
});
if (!cusProduct) {
throw new RecaseError({
message: `Cus product not found: ${customerProductId}`,
code: ErrCode.CusProductNotFound,
statusCode: 404,
});
}
const fullCus = await CusService.getFull({
db,
idOrInternalId:
cusProduct.customer!.id || cusProduct.customer!.internal_id,
orgId: req.orgId,
env: req.env,
});
await expireCusProduct({
req,
cusProduct,
fullCus,
expireImmediately: true,
prorate: true,
});
res.status(200).json({ message: "Product expired" });
} catch (error) {
handleRequestError({
req,
error,
res,
action: "update customer product",
});
}
};

View File

@@ -1,68 +0,0 @@
import { CusExpand, ErrCode, LegacyVersion } from "@autumn/shared";
import { StatusCodes } from "http-status-codes";
import { routeHandler } from "@/utils/routerUtils.js";
import { orgToVersion } from "@/utils/versionUtils/legacyVersionUtils.js";
import { getCusWithCache } from "../cusCache/getCusWithCache.js";
import { parseCusExpand } from "../cusUtils/cusUtils.js";
import { getCustomerDetails } from "../cusUtils/getCustomerDetails.js";
export const handleGetCustomer = async (req: any, res: any) =>
routeHandler({
req,
res,
action: "get customer",
handler: async () => {
const customerId = req.params.customer_id;
const { env, db, logger, org, features } = req;
const { expand } = req.query;
const expandArray = parseCusExpand(expand);
const apiVersion = orgToVersion({
org,
reqApiVersion: req.apiVersion,
});
const getInvoices = apiVersion < LegacyVersion.v1_1;
if (getInvoices) expandArray.push(CusExpand.Invoices);
logger.info(`getting customer ${customerId} for org ${org.slug}`);
const startTime = Date.now();
const customer = await getCusWithCache({
db,
idOrInternalId: customerId,
org,
env,
expand: expandArray,
allowNotFound: true,
logger,
});
logger.info(`get customer took ${Date.now() - startTime}ms`);
if (!customer) {
logger.warn(
`GET /customers/${customerId}: not found | Org: ${org.slug}`,
);
res.status(StatusCodes.NOT_FOUND).json({
message: `Customer ${customerId} not found`,
code: ErrCode.CustomerNotFound,
});
return;
}
const cusData = await getCustomerDetails({
db,
customer,
org,
env: req.env,
logger,
cusProducts: customer.customer_products,
expand: expandArray,
features,
apiVersion: req.apiVersion,
});
res.status(200).json(cusData);
},
});

View File

@@ -1,6 +1,9 @@
import {
AffectedResource,
ApiVersion,
backwardsChangeActive,
CusExpand,
type EntityExpand,
GetCustomerQuerySchema,
V0_2_InvoicesAlwaysExpanded,
} from "@autumn/shared";
@@ -9,12 +12,15 @@ import { getCusWithCache } from "../cusCache/getCusWithCache.js";
import { getApiCustomer } from "../cusUtils/apiCusUtils/getApiCustomer.js";
export const handleGetCustomerV2 = createRoute({
query: GetCustomerQuerySchema,
versionedQuery: {
latest: GetCustomerQuerySchema,
[ApiVersion.V1_2]: GetCustomerQuerySchema,
},
resource: AffectedResource.Customer,
handler: async (c) => {
const ctx = c.get("ctx");
const customerId = c.req.param("customer_id");
const { env, db, logger, org } = ctx;
const { expand = [] } = c.req.valid("query");
const { env, db, logger, org, expand } = ctx;
// SIDE EFFECT
if (
@@ -31,7 +37,7 @@ export const handleGetCustomerV2 = createRoute({
idOrInternalId: customerId,
org,
env,
expand,
expand: expand as (CusExpand | EntityExpand)[],
logger,
allowNotFound: false,
});
@@ -39,7 +45,6 @@ export const handleGetCustomerV2 = createRoute({
const customer = await getApiCustomer({
ctx,
fullCus: fullCus,
expand,
});
return c.json(customer);

View File

@@ -1,85 +0,0 @@
import { CusProductStatus, ErrCode } from "@autumn/shared";
import RecaseError, { handleRequestError } from "@/utils/errorUtils.js";
import { parseCusExpand } from "../cusUtils/cusUtils.js";
import { getCustomerDetails } from "../cusUtils/getCustomerDetails.js";
import { getOrCreateCustomer } from "../cusUtils/getOrCreateCustomer.js";
export const handlePostCustomerRequest = async (req: any, res: any) => {
try {
const logger = req.logger;
const data = req.body;
const expand = parseCusExpand(req.query.expand);
const { db, org, features } = req;
if (data.customer_id) {
throw new RecaseError({
message:
"use the `id` field instead of `customer_id` to specify the new customer's ID",
code: ErrCode.InvalidInputs,
});
}
if (data.id === undefined) {
throw new RecaseError({
message: "`id` field must be either a string or null",
code: ErrCode.InvalidInputs,
});
}
if (!data.id && !data.email) {
throw new RecaseError({
message: "ID or email is required",
code: ErrCode.InvalidRequest,
});
}
const customer = await getOrCreateCustomer({
req,
customerId: data.id,
customerData: data,
inStatuses: [
CusProductStatus.Active,
CusProductStatus.PastDue,
CusProductStatus.Scheduled,
],
expand,
entityId: data.entity_id,
entityData: data.entity_data,
withCache: true,
});
const cusDetails = await getCustomerDetails({
db,
customer,
org,
env: req.env,
params: req.query,
logger,
cusProducts: customer.customer_products,
expand,
features,
apiVersion: req.apiVersion,
});
res.status(200).json(cusDetails);
} catch (error: any) {
if (
error instanceof RecaseError &&
error.code === ErrCode.DuplicateCustomerId
) {
req.logger.warn(
`POST /customers: ${error.message} (org: ${req.org?.slug})`,
);
res.status(error.statusCode).json({
message: error.message,
code: error.code,
});
return;
}
console.log(`Error: ${error}`);
handleRequestError({ req, error, res, action: "create customer" });
}
};

View File

@@ -1,6 +1,7 @@
import { CreateCustomerSchema, ErrCode, ProcessorType } from "@autumn/shared";
import { StatusCodes } from "http-status-codes";
import { createStripeCli } from "@/external/connect/createStripeCli.js";
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
import { CusService } from "@/internal/customers/CusService.js";
import { FeatureService } from "@/internal/features/FeatureService.js";
import RecaseError from "@/utils/errorUtils.js";
@@ -10,8 +11,8 @@ import type {
ExtendedResponse,
} from "@/utils/models/Request.js";
import { routeHandler } from "@/utils/routerUtils.js";
import { getApiCustomer } from "../cusUtils/apiCusUtils/getApiCustomer.js";
import { parseCusExpand } from "../cusUtils/cusUtils.js";
import { getCustomerDetails } from "../cusUtils/getCustomerDetails.js";
export const handleUpdateCustomer = async (req: any, res: any) =>
routeHandler({
@@ -134,16 +135,10 @@ export const handleUpdateCustomer = async (req: any, res: any) =>
});
// res.status(200).json({ customer: updatedCustomer });
const customerDetails = await getCustomerDetails({
db,
customer: finalCustomer,
org,
env: req.env,
logger: req.logger,
cusProducts: finalCustomer.customer_products,
const customerDetails = await getApiCustomer({
ctx: req as AutumnContext,
fullCus: finalCustomer,
expand: parseCusExpand(req.query.expand as string),
features,
apiVersion: req.apiVersion,
});
res.status(200).json(customerDetails);

View File

@@ -4,6 +4,7 @@ import {
type Entity,
LegacyVersion,
} from "@autumn/shared";
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
import type { ExtendedRequest } from "@/utils/models/Request.js";
import { routeHandler } from "@/utils/routerUtils.js";
import { orgToVersion } from "@/utils/versionUtils/legacyVersionUtils.js";
@@ -91,16 +92,10 @@ export const createEntities = async ({
}
const { entities } = await getEntityResponse({
db,
ctx: req as AutumnContext,
entityIds: newEntities.map((e: any) => e.id || e.internal_id),
org,
env,
customerId: customer.id || customer.internal_id,
withAutumnId,
apiVersion: apiVersion!,
features,
logger,
skipCache: true,
});
return entities;

View File

@@ -1,8 +1,9 @@
import { CreateFeatureSchema, ErrCode, FeatureType } from "@autumn/shared";
import express, { type Router } from "express";
import { formatZodError } from "@/errors/formatZodError.js";
import { handleDeleteFeature } from "@/internal/features/handlers/handleDeleteFeature.js";
import { handleUpdateFeature } from "@/internal/features/handlers/handleUpdateFeature.js";
import RecaseError, { formatZodError } from "@/utils/errorUtils.js";
import RecaseError from "@/utils/errorUtils.js";
import { generateId } from "@/utils/genUtils.js";
import { FeatureService } from "./FeatureService.js";
import {

View File

@@ -75,7 +75,7 @@ export const isV4Usage = ({
return (
billingType === BillingType.UsageInArrear &&
(cusProduct.api_semver === ApiVersion.Beta ||
(cusProduct.api_semver === ApiVersion.V1_Beta ||
notNullish(cusProduct.internal_entity_id))
);
};

View File

@@ -1,10 +1,10 @@
import { BillingInterval, FullProduct } from "@autumn/shared";
import { generateObject } from "ai";
import { anthropic } from "@ai-sdk/anthropic";
import { BillingInterval, type FullProduct } from "@autumn/shared";
import { generateObject } from "ai";
import type { Logger } from "pino";
import { z } from "zod";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { nullish } from "@/utils/genUtils.js";
import { Logger } from "pino";
import { DrizzleCli } from "@/db/initDrizzle.js";
import { ProductService } from "../ProductService.js";
const prompt = `Detect whether a given product (called "product_to_detect") is an interval variant of a base product from the list of existing products (called "existing_products").
@@ -35,7 +35,7 @@ export const detectBaseVariant = async ({
logger.info(`Detecting base variant for ${curProduct.id}`);
if (!process.env.ANTHROPIC_API_KEY) return;
let existingProducts = (await ProductService.listFull({
const existingProducts = (await ProductService.listFull({
db,
orgId: curProduct.org_id,
env: curProduct.env,
@@ -43,8 +43,8 @@ export const detectBaseVariant = async ({
})) as FullProduct[];
// if (product.base_variant_id == baseVariantId) {
let curPrices = curProduct.prices;
let intervals = curPrices.map((price) => price.config.interval);
const curPrices = curProduct.prices;
const intervals = curPrices.map((price) => price.config.interval);
// 1. Return null if add on
if (curProduct.is_add_on) return null;
@@ -58,17 +58,17 @@ export const detectBaseVariant = async ({
const filteredExistingProducts = existingProducts.filter(
(p) =>
p.id != curProduct.id &&
p.id !== curProduct.id &&
nullish(p.base_variant_id) &&
!p.is_add_on &&
p.prices.length > 0 &&
p.prices.every(
(price) => price.config.interval == BillingInterval.Month,
(price) => price.config.interval === BillingInterval.Month,
) &&
p.group == curProduct.group,
p.group === curProduct.group,
);
if (filteredExistingProducts.length == 0) {
if (filteredExistingProducts.length === 0) {
logger.info(`No base product to search for`);
return null;
}
@@ -95,13 +95,13 @@ export const detectBaseVariant = async ({
</existing_products>
`;
let { object } = await generateObject({
const { object } = await generateObject({
model: anthropic("claude-3-5-haiku-latest"),
schema: z.object({ base_variant_id: z.string().nullable() }),
prompt: `${prompt}\n\n${variables}`,
});
let baseVariantId = object.base_variant_id;
const baseVariantId = object.base_variant_id;
logger.info(
`llm response for base variant of ${curProduct.id}: ${baseVariantId}`,

View File

@@ -4,12 +4,12 @@ import {
type ApiPlan,
ApiPlanSchema,
AttachScenario,
type BillingInterval,
type Feature,
type FeatureOptions,
type FullCustomer,
type FullProduct,
itemsToPlanFeatures,
itemToBillingInterval,
productV2ToBasePrice,
productV2ToFeatureItems,
} from "@autumn/shared";
@@ -114,62 +114,13 @@ export const getPlanResponse = async ({
attachScenario,
});
console.log(
`New feature:\n ${JSON.stringify(
{
// Basic fields
id: product.id,
name: product.name || "",
description: product.description, // Products don't have descriptions
group: product.group,
version: product.version,
// Boolean flags
add_on: product.is_add_on,
default: product.is_default,
// Price field (required in Plan schema)
price: basePrice
? {
amount: basePrice.amount,
interval: basePrice.interval as unknown as BillingInterval,
}
: {
amount: 0,
interval: "month" as BillingInterval,
},
// Features array
features: planFeatures,
// Free trial
free_trial: freeTrial,
// Metadata fields
created_at: product.created_at,
env: product.env,
archived: product.archived,
base_variant_id: product.base_variant_id,
// Customer context (optional)
// Uncomment when ready to add customer context
// customer_context: {
// trial_available: notNullish(freeTrial),
// scenario: attachScenario,
// },
},
null,
4,
)}`,
);
// 9. Build Plan response
return ApiPlanSchema.parse({
// Basic fields
id: product.id,
name: product.name || "",
description: product.description, // Products don't have descriptions
group: product.group,
name: product.name || null,
description: product.description || null, // Products don't have descriptions
group: product.group || null,
version: product.version,
// Boolean flags
@@ -180,12 +131,11 @@ export const getPlanResponse = async ({
price: basePrice
? {
amount: basePrice.amount,
interval: basePrice.interval as BillingInterval,
interval: itemToBillingInterval({ item: basePrice }),
interval_count:
basePrice.intervalCount !== 1 ? basePrice.intervalCount : undefined,
}
: {
amount: 0,
interval: "month" as BillingInterval,
},
: null,
// Features array
features: planFeatures,

View File

@@ -43,7 +43,7 @@ export const mapToProductItems = ({
}
for (const item of items) {
const feature = features.find((f) => f.id == item.feature_id);
const feature = features.find((f) => f.id === item.feature_id);
if (feature) {
item.feature_type = getItemFeatureType({ item, features });
}
@@ -84,7 +84,7 @@ export const mapToProductV2 = ({
const productV2: ProductV2 = {
internal_id: product.internal_id,
env: product.env,
id: product.id,
name: product.name,
is_add_on: product.is_add_on,

View File

@@ -12,6 +12,7 @@ import { runTriggerCheckoutReward } from "@/internal/rewards/triggerCheckoutRewa
import { runUpdateBalanceTask } from "@/trigger/updateBalanceTask.js";
import { runUpdateUsageTask } from "@/trigger/updateUsageTask.js";
import { generateId } from "@/utils/genUtils.js";
import { createWorkerAutumnContext } from "@/utils/workerUtils/createAutumnContext.js";
import { JobName } from "./JobName.js";
import { acquireLock, getRedisConnection, releaseLock } from "./lockUtils.js";
import { QueueManager } from "./QueueManager.js";
@@ -79,11 +80,18 @@ const initWorker = ({
}
if (actionHandlers.includes(job.name as JobName)) {
const ctx = await createWorkerAutumnContext({
db,
orgId: job.data.orgId,
env: job.data.env,
logger: workerLogger,
workerId: id.toString(),
});
await runActionHandlerTask({
queue,
job,
logger: workerLogger,
db,
ctx,
useBackup,
});
return;

View File

@@ -1,5 +1,6 @@
import {
type AppEnv,
AuthType,
createdAtToVersion,
type Feature,
type FullCusEntWithFullCusProduct,
@@ -10,10 +11,12 @@ import {
} from "@autumn/shared";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import { sendSvixEvent } from "@/external/svix/svixHelpers.js";
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
import { getSingleEntityResponse } from "@/internal/api/entities/getEntityUtils.js";
import { getV2CheckResponse } from "@/internal/api/entitled/checkUtils/getV2CheckResponse.js";
import { getCustomerDetails } from "@/internal/customers/cusUtils/getCustomerDetails.js";
import { getApiCustomer } from "@/internal/customers/cusUtils/apiCusUtils/getApiCustomer.js";
import { toApiFeature } from "@/internal/features/utils/mapFeatureUtils.js";
import { generateId } from "@/utils/genUtils.js";
export const mergeNewCusEntsIntoCusProducts = ({
cusProducts,
@@ -39,45 +42,27 @@ export const mergeNewCusEntsIntoCusProducts = ({
};
export const sendSvixThresholdReachedEvent = async ({
db,
org,
env,
features,
logger,
ctx,
feature,
fullCus,
thresholdType,
}: {
db: DrizzleCli;
org: Organization;
env: AppEnv;
features: Feature[];
logger: any;
ctx: AutumnContext;
feature: Feature;
fullCus: FullCustomer;
thresholdType: "limit_reached" | "allowance_used";
}) => {
const apiVersion = createdAtToVersion({
createdAt: org.created_at || undefined,
});
const { org, env, logger } = ctx;
const cusDetails = await getCustomerDetails({
db,
customer: fullCus,
org,
env,
features,
logger,
cusProducts: fullCus.customer_products,
const cusDetails = await getApiCustomer({
ctx,
fullCus,
expand: [],
apiVersion,
});
if (fullCus.entity) {
await getSingleEntityResponse({
org,
env,
features,
ctx,
fullCus,
entity: fullCus.entity,
entityId: fullCus.entity.id,
@@ -100,31 +85,20 @@ export const sendSvixThresholdReachedEvent = async ({
};
export const handleAllowanceUsed = async ({
db,
org,
env,
features,
logger,
ctx,
cusEnts,
newCusEnts,
feature,
fullCus,
}: {
db: DrizzleCli;
org: Organization;
env: AppEnv;
ctx: AutumnContext;
cusEnts: FullCusEntWithFullCusProduct[];
newCusEnts: FullCusEntWithFullCusProduct[];
feature: Feature;
fullCus: FullCustomer;
features: Feature[];
logger: any;
}) => {
const apiVersion = createdAtToVersion({
createdAt: org.created_at || undefined,
});
const { org, env, apiVersion } = ctx;
// Allowance used...
// Make sure overage allowed is false
const oldCusEnts = structuredClone(cusEnts);
for (const cusEnt of oldCusEnts) {
@@ -166,13 +140,9 @@ export const handleAllowanceUsed = async ({
if (prevCheckResponse.allowed === true && v2CheckResponse.allowed === false) {
await sendSvixThresholdReachedEvent({
db,
org,
env,
features,
logger,
feature,
ctx,
fullCus,
feature,
thresholdType: "allowance_used",
});
}
@@ -200,6 +170,21 @@ export const handleThresholdReached = async ({
features: Feature[];
logger: any;
}) => {
const ctx: AutumnContext = {
db,
org,
env,
features,
logger,
apiVersion: createdAtToVersion({
createdAt: org.created_at || undefined,
}),
id: generateId("local_req"),
isPublic: false,
authType: AuthType.Unknown,
timestamp: Date.now(),
};
try {
const apiVersion = createdAtToVersion({
createdAt: org.created_at || undefined,
@@ -236,26 +221,18 @@ export const handleThresholdReached = async ({
prevCheckResponse.allowed === true &&
v2CheckResponse.allowed === false
) {
const cusDetails = await getCustomerDetails({
db,
customer: fullCus,
org,
env,
features,
logger,
cusProducts: newCusProducts,
const cusDetails = await getApiCustomer({
ctx,
fullCus,
expand: [],
apiVersion,
});
if (fullCus.entity) {
await getSingleEntityResponse({
org,
env,
features,
entity: fullCus.entity,
fullCus,
ctx,
entityId: fullCus.entity.id,
fullCus,
entity: fullCus.entity,
});
}
@@ -276,11 +253,7 @@ export const handleThresholdReached = async ({
return;
}
await handleAllowanceUsed({
db,
org,
env,
features,
logger,
ctx,
cusEnts,
newCusEnts,
feature,

View File

@@ -2,7 +2,8 @@ import { ErrCode } from "@autumn/shared";
import chalk from "chalk";
import { StatusCodes } from "http-status-codes";
import Stripe from "stripe";
import { ZodError, type ZodIssue } from "zod/v4";
import { ZodError } from "zod/v4";
import { formatZodError } from "../errors/formatZodError.js";
export const isPaymentDeclined = (error: any) => {
return (
@@ -46,51 +47,6 @@ export default class RecaseError extends Error {
}
}
export function formatZodError(error: ZodError): string {
const formatMessage = (issue: ZodIssue): string => {
const path = issue.path.length ? issue.path.join(".") : "input";
// Clean up common Zod error messages
let message = issue.message;
// Handle common patterns and make them more user-friendly
if (
message.includes("Too small") &&
message.includes("expected string to have >=1 characters")
) {
message = "cannot be empty";
} else if (message.includes("Invalid string: must match pattern")) {
// Extract the pattern and make it more readable
if (message.includes("/^[a-zA-Z0-9_-]+$/")) {
message =
"must contain only letters, numbers, underscores, and hyphens";
} else {
message = "has invalid format";
}
} else if (message.includes("Invalid input: expected string, received")) {
const receivedType = message.split("received ")[1];
message = `must be a string (received ${receivedType})`;
} else if (message.includes("Invalid input: expected number, received")) {
const receivedType = message.split("received ")[1];
message = `must be a number (received ${receivedType})`;
} else if (message.includes("Invalid input: expected boolean, received")) {
const receivedType = message.split("received ")[1];
message = `must be a boolean (received ${receivedType})`;
}
return `${path}: ${message}`;
};
const formattedIssues = error.issues.map(formatMessage);
// If there are multiple issues, format them nicely
if (formattedIssues.length === 1) {
return formattedIssues[0];
} else {
return `[Validation Errors] ${formattedIssues.join("; ")}`;
}
}
export const handleRequestError = ({
error,
req,

View File

@@ -3,11 +3,9 @@ import { StatusCodes } from "http-status-codes";
import qs from "qs";
import Stripe from "stripe";
import { ZodAny, ZodError, ZodObject } from "zod";
import { formatZodError } from "@/errors/formatZodError.js";
import { withSpan as withSpanTracer } from "@/internal/analytics/tracer/spanUtils.js";
import RecaseError, {
formatZodError,
handleRequestError,
} from "./errorUtils.js";
import RecaseError, { handleRequestError } from "./errorUtils.js";
import type { ExtendedRequest } from "./models/Request.js";
/**

View File

@@ -0,0 +1,64 @@
import {
type AppEnv,
AuthType,
createdAtToVersion,
ErrCode,
RecaseError,
} from "@autumn/shared";
import type { DrizzleCli } from "@/db/initDrizzle.js";
import type { Logger } from "@/external/logtail/logtailUtils.js";
import type { AutumnContext } from "@/honoUtils/HonoEnv.js";
import { OrgService } from "@/internal/orgs/OrgService.js";
export const createWorkerAutumnContext = async ({
db,
orgId,
env,
logger,
workerId,
}: {
db: DrizzleCli;
orgId: string;
env: AppEnv;
logger: Logger;
workerId: string;
}): Promise<AutumnContext> => {
// 1. Get org with features
const data = await OrgService.getWithFeatures({
db,
orgId,
env,
});
if (!data) {
logger.warn(`Org ${orgId} not found in DB`);
throw new RecaseError({
message: "Org not found",
code: ErrCode.OrgNotFound,
statusCode: 500,
});
}
const { org, features } = data;
const apiVersion = createdAtToVersion({
createdAt: org.created_at || Date.now(),
});
return {
org,
env,
features,
db,
logger,
expand: [],
id: workerId,
isPublic: false,
authType: AuthType.Unknown,
apiVersion,
timestamp: Date.now(),
} satisfies AutumnContext;
};

View File

@@ -0,0 +1,9 @@
import type { AppEnv } from "@autumn/shared";
import type { ExtendedRequest } from "@/utils/models/Request.js";
export interface HandleCustomerCreatedData {
req: Partial<ExtendedRequest>;
orgId: string;
env: AppEnv;
internalCustomerId: string;
}

View File

@@ -81,7 +81,7 @@ export const expectSubToBeCorrect = async ({
existingUsage: 0,
withEntity: true,
isCheckout: false,
apiVersion: ApiVersion.Beta,
apiVersion: ApiVersion.V1_Beta,
});
const lineItem: any = res?.lineItem;

View File

@@ -2,7 +2,7 @@ import { EntityDataSchema } from "@api/common/entityData.js";
import { ApiProductSchema } from "@api/products/previousVersions/apiProduct.js";
import { z } from "zod/v4";
import { CustomerDataSchema } from "../common/customerData.js";
import { CoreCusFeatureSchema } from "../customers/cusFeatures/apiCusFeature.js";
import { CoreCusFeatureSchema } from "../customers/cusFeatures/previousVersions/apiCusFeatureV3.js";
// Check Feature Enums
export const CheckFeatureScenarioSchema = z.enum([

View File

@@ -1,12 +1,12 @@
import { ApiCusReferralSchema } from "@api/customers/components/apiCusReferral.js";
import { ApiCusFeatureSchema } from "@api/customers/cusFeatures/apiCusFeature.js";
import { ApiBaseEntitySchema } from "@api/entities/apiEntity.js";
import { ApiCusRewardsSchema } from "@api/others/apiDiscount.js";
import { ApiInvoiceSchema } from "@api/others/apiInvoice.js";
import { AppEnv } from "@models/genModels/genEnums.js";
import { z } from "zod/v4";
import { ApiCusReferralSchema } from "./components/apiCusReferral.js";
import { ApiCusUpcomingInvoiceSchema } from "./components/apiCusUpcomingInvoice.js";
import { ApiCusProductSchema } from "./cusProducts/apiCusProduct.js";
import { ApiCusFeatureSchema } from "./cusFeatures/apiCusFeature.js";
import { ApiCusPlanSchema } from "./cusPlans/apiCusPlan.js";
export const ApiTrialsUsedSchema = z.object({
product_id: z.string(),
@@ -25,47 +25,20 @@ export const ApiCusExpandSchema = z.object({
});
export const ApiCustomerSchema = z.object({
// Internal fields
id: z.string().nullable().meta({
description: "Your internal ID for the customer",
example: "cus_123",
}),
created_at: z.number().meta({
description: "Timestamp of customer creation in milliseconds since epoch",
example: 1717000000,
}),
name: z.string().nullable().meta({
description: "Customers name",
example: "John Doe",
}),
email: z.string().nullable().meta({
description: "Customers email address",
example: "john@doe.com",
}),
fingerprint: z.string().nullable().meta({
description:
"Unique identifier (eg. serial number) to detect duplicate customers and prevent key leaks",
example: "fp_9184And92839123hda",
}),
stripe_id: z.string().nullable().default(null).meta({
description: "Stripe customer ID",
example: "cus_J8A5c31A8tlpwN",
}),
env: z.enum(AppEnv).meta({
description: "Environment the customer is in",
example: "production",
}),
metadata: z.record(z.any(), z.any()).default({}),
products: z.array(ApiCusProductSchema).meta({
description: "List of products the customer has access to",
example: [],
}),
features: z.record(z.string(), ApiCusFeatureSchema).meta({
description: "List of features the customer has access to",
example: {},
}),
id: z.string(),
name: z.string().nullable(),
email: z.string().nullable(),
created_at: z.number(),
fingerprint: z.string().nullable(),
stripe_id: z.string().nullable(),
env: z.enum(AppEnv),
metadata: z.record(z.any(), z.any()),
plans: z.array(ApiCusPlanSchema),
features: z.record(z.string(), ApiCusFeatureSchema),
...ApiCusExpandSchema.shape,
});
export type ApiCustomer = z.infer<typeof ApiCustomerSchema>;
export type ApiCustomerExpand = z.infer<typeof ApiCusExpandSchema>;
export type ApiCusExpand = z.infer<typeof ApiCusExpandSchema>;

View File

@@ -9,7 +9,7 @@ import {
ApiCusFeatureV1Schema,
} from "../cusFeatures/previousVersions/apiCusFeatureV1.js";
import type { ApiCusFeatureV2 } from "../cusFeatures/previousVersions/apiCusFeatureV2.js";
import { transformCusProductV2ToV1 } from "../cusProducts/changes/V0_2_CusProductChange.js";
import { transformCusProductV2ToV1 } from "../cusPlans/changes/V0.2_CusProductChange.js";
import {
type CustomerLegacyData,
CustomerLegacyDataSchema,

View File

@@ -1,4 +1,3 @@
import { ApiCustomerSchema } from "@api/customers/apiCustomer.js";
import { ApiFeatureType } from "@api/features/apiFeature.js";
import { ApiVersion } from "@api/versionUtils/ApiVersion.js";
import {
@@ -10,6 +9,7 @@ import {
type ApiCusFeatureV2,
ApiCusFeatureV2Schema,
} from "../cusFeatures/previousVersions/apiCusFeatureV2.js";
import { ApiCustomerV3Schema } from "../previousVersions/apiCustomerV3.js";
/**
* V1_2: Features changed from array to object AND merged multi-interval features
@@ -24,10 +24,10 @@ import {
*/
// V1_2+ customer schema (features as object with breakdown)
const V1_2_CustomerSchema = ApiCustomerSchema;
const V1_2_CustomerSchema = ApiCustomerV3Schema;
// V1_1 customer schema (features as array without breakdown)
const V1_1_CustomerSchema = ApiCustomerSchema.extend({
const V1_1_CustomerSchema = ApiCustomerV3Schema.extend({
features: z.array(ApiCusFeatureV2Schema),
});

View File

@@ -0,0 +1,79 @@
import { ApiVersion } from "@api/versionUtils/ApiVersion.js";
import {
AffectedResource,
defineVersionChange,
} from "@api/versionUtils/versionChangeUtils/VersionChange.js";
import type { z } from "zod/v4";
import { ApiCustomerSchema } from "../apiCustomer.js";
import { transformCusFeatureToV3 } from "../cusFeatures/changes/V1.2_CusFeatureChange.js";
import type { ApiCusFeatureV3 } from "../cusFeatures/previousVersions/apiCusFeatureV3.js";
import { transformCusPlanToProductV3 } from "../cusPlans/changes/V1.2_CusPlanChange.js";
import type { ApiCusProductV3 } from "../cusPlans/previousVersions/apiCusProductV3.js";
import { CustomerLegacyDataSchema } from "../customerLegacyData.js";
import { ApiCustomerV3Schema } from "../previousVersions/apiCustomerV3.js";
/**
* V1_2_CustomerChange: Transforms customer response TO V1_2 format
*
* Applied when: targetVersion <= V1_2
*
* Breaking changes introduced in V2.0:
*
* 1. Products renamed to Plans:
* - V2.0+: "products" field contains ApiCusPlan objects
* - V1.2: "products" field contains ApiCusProductV3 objects
*
* 2. Simplified feature schema:
* - V2.0+: Minimal fields with optional feature object
* - V1.2: Verbose fields with all metadata
*
* 3. Features remain as record (no change from V1.1):
* - Both V2.0 and V1.2: Record<string, ApiCusFeature>
*
* Input: ApiCustomer (V2.0+ format)
* Output: ApiCustomerV3 (V1.2 format)
*/
export const V1_2_CustomerChange = defineVersionChange({
newVersion: ApiVersion.V2,
oldVersion: ApiVersion.V1_Beta,
description: [
"Products renamed to plans in SDK",
"Simplified feature and plan schemas",
"Added optional nested objects for expanded data",
],
affectedResources: [AffectedResource.Customer],
newSchema: ApiCustomerSchema,
oldSchema: ApiCustomerV3Schema,
legacyDataSchema: CustomerLegacyDataSchema,
// Response: V2.0 → V1.2
transformResponse: ({ input, legacyData }) => {
// Step 1: Transform plans V2.0 → V1.2 (products)
const v3CusProducts: ApiCusProductV3[] = input.plans.map((plan) =>
transformCusPlanToProductV3({
input: plan,
legacyData: legacyData?.cusProductLegacyData[plan.plan_id],
}),
);
// Step 2: Transform features V2.0 → V1.2
const v3_features: Record<string, ApiCusFeatureV3> = {};
for (const [featureId, feature] of Object.entries(input.features)) {
v3_features[featureId] = transformCusFeatureToV3({ input: feature });
}
// Step 3: Return V1.2 customer format
return {
id: input.id,
name: input.name,
email: input.email,
created_at: input.created_at,
fingerprint: input.fingerprint,
stripe_id: input.stripe_id,
env: input.env,
metadata: input.metadata,
products: v3CusProducts,
features: v3_features,
} satisfies z.infer<typeof ApiCustomerV3Schema>;
},
});

View File

@@ -1,138 +1,56 @@
import { ApiFeatureType } from "@api/features/apiFeature.js";
import { EntInterval } from "@models/productModels/entModels/entEnums.js";
import { ApiFeatureSchema } from "@api/features/apiFeature.js";
import { ResetInterval } from "@api/products/planEnums.js";
import { z } from "zod/v4";
export const ApiCusRolloverSchema = z.object({
balance: z.number().meta({
description: "The remaining balance amount that has rolled over",
example: 100,
}),
expires_at: z.number().meta({
description: "Timestamp when the rollover balance expires",
example: 1759247877000,
}),
balance: z.number(),
expires_at: z.number(),
});
// Version 3 of cus feature response
export const ApiCusFeatureBreakdownSchema = z.object({
interval: z.enum(EntInterval).meta({
description: "The billing interval for this feature breakdown",
example: "month",
}),
interval_count: z.number().nullish().meta({
description: "The number of intervals between resets",
example: 1,
}),
balance: z.number().nullish().meta({
description: "The remaining balance for this interval",
example: 500,
}),
usage: z.number().nullish().meta({
description: "The current usage amount",
example: 250,
}),
included_usage: z.number().nullish().meta({
description: "The amount of usage included in this interval",
example: 1000,
}),
next_reset_at: z.number().nullish().meta({
description: "Timestamp when the usage resets",
example: 1759247877000,
}),
usage_limit: z.number().nullish().meta({
description: "The maximum usage allowed",
example: 1000,
}),
rollovers: z.array(ApiCusRolloverSchema).nullish().meta({
description: "Array of rollover balances from previous periods",
example: [{ balance: 100, expires_at: 1759247877000 }],
}),
reset: z
.object({
interval: z.enum(ResetInterval),
interval_count: z.number().optional(),
when_enabled: z.boolean().optional(),
})
.optional(),
starting_balance: z.number(),
balance: z.number(),
usage: z.number(),
resets_at: z.number().nullable(),
max_purchase: z.number().optional(),
});
export const CoreCusFeatureSchema = z.object({
interval: z.enum(EntInterval).or(z.literal("multiple")).nullish().meta({
description: "The billing interval or 'multiple' if the feature has multiple intervals",
example: "month",
}),
interval_count: z.number().nullish().meta({
description: "The number of intervals between resets",
example: 1,
}),
unlimited: z.boolean().nullish().meta({
description: "Whether the feature has unlimited usage",
example: false,
}),
balance: z.number().nullish().meta({
description: "The remaining balance for this feature",
example: 500,
}),
usage: z.number().nullish().meta({
description: "The current usage amount",
example: 250,
}),
included_usage: z.number().nullish().meta({
description: "The amount of usage included",
example: 1000,
}),
next_reset_at: z.number().nullish().meta({
description: "Timestamp when the usage resets",
example: 1759247877000,
}),
overage_allowed: z.boolean().nullish().meta({
description: "Whether overage usage beyond the limit is allowed",
example: true,
}),
export const ApiCusFeatureSchema = z.object({
feature_id: z.string(),
feature: ApiFeatureSchema.optional(),
breakdown: z.array(ApiCusFeatureBreakdownSchema).nullish().meta({
description: "Detailed breakdown by interval for features with multiple intervals",
example: [{ interval: "month", interval_count: 1, balance: 500, usage: 250 }],
}),
credit_schema: z
.array(
z.object({
feature_id: z.string().meta({
description: "The ID of the feature that credits are applied to",
example: "feature_123",
}),
credit_amount: z.number().meta({
description: "The amount of credits applied per usage",
example: 10,
}),
}),
)
.nullish()
.meta({
description: "Credit conversion schema for credit system features",
example: [{ feature_id: "feature_123", credit_amount: 10 }],
}),
unlimited: z.boolean(),
usage_limit: z.number().nullish().meta({
description: "The maximum usage allowed",
example: 1000,
}),
rollovers: z.array(ApiCusRolloverSchema).nullish().meta({
description: "Array of rollover balances from previous periods",
example: [{ balance: 100, expires_at: 1759247877000 }],
}),
granted_balance: z.number(), // 0
purchased_balance: z.number(), // 3
current_balance: z.number(), // 1
usage: z.number(), // 2
resets_at: z.number().nullable(),
max_purchase: z.number().optional(),
pay_per_use: z.boolean().optional(),
reset: z
.object({
interval: z.enum(ResetInterval).nullable(),
interval_count: z.number().optional(),
})
.optional(),
breakdown: z.array(ApiCusFeatureBreakdownSchema).nullish(),
rollovers: z.array(ApiCusRolloverSchema).nullish(),
});
export const ApiCusFeatureSchema = z
.object({
id: z.string().meta({
description: "The unique identifier of the feature",
example: "feature_123",
}),
type: z.enum(ApiFeatureType).meta({
description: "The type of the feature",
example: "single_use",
}),
name: z.string().nullish().meta({
description: "The name of the feature",
example: "API Calls",
}),
})
.extend(CoreCusFeatureSchema.shape);
export type ApiCusFeature = z.infer<typeof ApiCusFeatureSchema>;
export type ApiCusRollover = z.infer<typeof ApiCusRolloverSchema>;
export type ApiCusFeatureBreakdown = z.infer<

View File

@@ -0,0 +1,153 @@
import { ApiFeatureType } from "@api/features/apiFeature.js";
import { ApiVersion } from "@api/versionUtils/ApiVersion.js";
import {
AffectedResource,
defineVersionChange,
} from "@api/versionUtils/versionChangeUtils/VersionChange.js";
import { resetIntvToEntIntv } from "@utils/planFeatureUtils/planFeatureIntervals.js";
import { nullish } from "@utils/utils.js";
import { Decimal } from "decimal.js";
import type { z } from "zod/v4";
import { ApiCusFeatureSchema } from "../apiCusFeature.js";
import {
type ApiCusFeatureV3Breakdown,
ApiCusFeatureV3Schema,
} from "../previousVersions/apiCusFeatureV3.js";
// overage_allowed: z.boolean().nullish().meta({
// description: "Whether overage usage beyond the limit is allowed",
// example: true,
// }),
/**
* Transform feature from V2.0 format to V1.2 format
* Exported so it can be reused in other transformations (e.g., V1_2_CustomerChange)
*/
export function transformCusFeatureToV3({
input,
}: {
input: z.infer<typeof ApiCusFeatureSchema>;
}): z.infer<typeof ApiCusFeatureV3Schema> {
const toUsageLimit = ({
maxPurchase,
startingBalance,
}: {
maxPurchase?: number;
startingBalance: number;
}) => {
return maxPurchase && startingBalance
? new Decimal(maxPurchase).add(startingBalance).toNumber()
: undefined;
};
const toV3Type = ({ type }: { type: ApiFeatureType }) => {
if (type === ApiFeatureType.Boolean) {
return ApiFeatureType.Static;
} else return type;
};
const itemInterval =
input.reset_interval === null
? "multiple"
: resetIntvToEntIntv({ resetIntv: input.reset_interval });
const usageLimit = toUsageLimit({
maxPurchase: input.max_purchase,
startingBalance: input.starting_balance,
});
const overageAllowed =
Boolean(input.pay_per_use) && nullish(input.max_purchase);
let newBreakdown: ApiCusFeatureV3Breakdown[] | undefined;
if (input.breakdown && input.breakdown.length > 0) {
newBreakdown = input.breakdown.map((breakdown) => {
const interval = resetIntvToEntIntv({
resetIntv: breakdown.reset_interval,
});
const usageLimit = toUsageLimit({
maxPurchase: breakdown.max_purchase,
startingBalance: breakdown.starting_balance,
});
return {
interval,
interval_count: breakdown.reset_interval_count || 1,
balance: breakdown.balance,
usage: breakdown.usage,
included_usage: breakdown.starting_balance,
next_reset_at: breakdown.resets_at,
usage_limit: usageLimit,
} satisfies ApiCusFeatureV3Breakdown;
});
}
const v3Type = toV3Type({
type: input.feature?.type ?? ApiFeatureType.SingleUsage,
});
const omitInterval = v3Type === ApiFeatureType.Static || input.unlimited;
return {
id: input.feature_id,
type: v3Type,
name: input.feature?.name ?? null,
unlimited: input.unlimited,
included_usage: input.starting_balance,
balance: input.balance,
usage: input.usage,
next_reset_at: input.resets_at,
interval: omitInterval ? undefined : itemInterval,
interval_count: omitInterval
? undefined
: itemInterval === "multiple"
? null
: input.reset_interval_count || 1,
overage_allowed: overageAllowed,
credit_schema: input.feature?.credit_schema
? input.feature.credit_schema.map((credit) => ({
feature_id: credit.metered_feature_id,
credit_amount: credit.credit_cost,
}))
: undefined,
breakdown: newBreakdown,
usage_limit: usageLimit,
rollovers: input.rollovers,
};
}
/**
* V1_2_CusFeatureChange: Transforms customer feature response TO V1_2 format
*
* Applied when: targetVersion <= V1_2
*
* Breaking changes introduced in V2.0:
*
* 1. Simplified schema with minimal required fields
* 2. Optional feature object for expanded data
* 3. Removed verbose metadata and display fields
*
* Input: ApiCusFeature (V2.0+ format)
* Output: ApiCusFeatureV3 (V1.2 format)
*/
export const V1_2_CusFeatureChange = defineVersionChange({
newVersion: ApiVersion.V2,
oldVersion: ApiVersion.V1_2,
description: [
"Simplified customer feature schema",
"Added optional feature object",
"Removed verbose fields",
],
affectedResources: [AffectedResource.Customer],
newSchema: ApiCusFeatureSchema,
oldSchema: ApiCusFeatureV3Schema,
transformResponse: transformCusFeatureToV3,
});

View File

@@ -0,0 +1,155 @@
import { ApiFeatureType } from "@api/features/apiFeature.js";
import { EntInterval } from "@models/productModels/entModels/entEnums.js";
import { z } from "zod/v4";
export const ApiCusFeatureV3RolloverSchema = z.object({
balance: z.number().meta({
description: "The remaining balance amount that has rolled over",
example: 100,
}),
expires_at: z.number().meta({
description: "Timestamp when the rollover balance expires",
example: 1759247877000,
}),
});
// Version 3 of cus feature response
export const ApiCusFeatureV3BreakdownSchema = z.object({
interval: z.enum(EntInterval).meta({
description: "The billing interval for this feature breakdown",
example: "month",
}),
interval_count: z.number().nullish().meta({
description: "The number of intervals between resets",
example: 1,
}),
balance: z.number().nullish().meta({
description: "The remaining balance for this interval",
example: 500,
}),
usage: z.number().nullish().meta({
description: "The current usage amount",
example: 250,
}),
included_usage: z.number().nullish().meta({
description: "The amount of usage included in this interval",
example: 1000,
}),
next_reset_at: z.number().nullish().meta({
description: "Timestamp when the usage resets",
example: 1759247877000,
}),
usage_limit: z.number().nullish().meta({
description: "The maximum usage allowed",
example: 1000,
}),
rollovers: z
.array(ApiCusFeatureV3RolloverSchema)
.nullish()
.meta({
description: "Array of rollover balances from previous periods",
example: [{ balance: 100, expires_at: 1759247877000 }],
}),
});
export const CoreCusFeatureSchema = z.object({
interval: z.enum(EntInterval).or(z.literal("multiple")).nullish().meta({
description:
"The billing interval or 'multiple' if the feature has multiple intervals",
example: "month",
}),
interval_count: z.number().nullish().meta({
description: "The number of intervals between resets",
example: 1,
}),
unlimited: z.boolean().nullish().meta({
description: "Whether the feature has unlimited usage",
example: false,
}),
balance: z.number().nullish().meta({
description: "The remaining balance for this feature",
example: 500,
}),
usage: z.number().nullish().meta({
description: "The current usage amount",
example: 250,
}),
included_usage: z.number().nullish().meta({
description: "The amount of usage included",
example: 1000,
}),
next_reset_at: z.number().nullish().meta({
description: "Timestamp when the usage resets",
example: 1759247877000,
}),
overage_allowed: z.boolean().nullish().meta({
description: "Whether overage usage beyond the limit is allowed",
example: true,
}),
breakdown: z
.array(ApiCusFeatureV3BreakdownSchema)
.nullish()
.meta({
description:
"Detailed breakdown by interval for features with multiple intervals",
example: [
{ interval: "month", interval_count: 1, balance: 500, usage: 250 },
],
}),
credit_schema: z
.array(
z.object({
feature_id: z.string().meta({
description: "The ID of the feature that credits are applied to",
example: "feature_123",
}),
credit_amount: z.number().meta({
description: "The amount of credits applied per usage",
example: 10,
}),
}),
)
.nullish()
.meta({
description: "Credit conversion schema for credit system features",
example: [{ feature_id: "feature_123", credit_amount: 10 }],
}),
usage_limit: z.number().nullish().meta({
description: "The maximum usage allowed",
example: 1000,
}),
rollovers: z
.array(ApiCusFeatureV3RolloverSchema)
.nullish()
.meta({
description: "Array of rollover balances from previous periods",
example: [{ balance: 100, expires_at: 1759247877000 }],
}),
});
export const ApiCusFeatureV3Schema = z
.object({
id: z.string().meta({
description: "The unique identifier of the feature",
example: "feature_123",
}),
type: z.enum(ApiFeatureType).meta({
description: "The type of the feature",
example: "single_use",
}),
name: z.string().nullish().meta({
description: "The name of the feature",
example: "API Calls",
}),
})
.extend(CoreCusFeatureSchema.shape);
export type ApiCusFeatureV3 = z.infer<typeof ApiCusFeatureV3Schema>;
export type ApiCusFeatureV3Rollover = z.infer<
typeof ApiCusFeatureV3RolloverSchema
>;
export type ApiCusFeatureV3Breakdown = z.infer<
typeof ApiCusFeatureV3BreakdownSchema
>;

View File

@@ -0,0 +1,23 @@
import { ApiPlanSchema } from "@api/products/apiPlan.js";
import { z } from "zod/v4";
export const ApiCusPlanSchema = z.object({
plan: ApiPlanSchema.optional(),
plan_id: z.string(),
add_on: z.boolean(),
// Flags / timestamps
status: z.enum(["active", "scheduled", "expired"]),
past_due: z.boolean(),
canceled_at: z.number().nullable(),
expires_at: z.number().nullable(),
trial_ends_at: z.number().nullable(),
started_at: z.number(),
current_period_start: z.number().nullable(),
current_period_end: z.number().nullable(),
quantity: z.number(),
});
export type ApiCusPlan = z.infer<typeof ApiCusPlanSchema>;

View File

@@ -10,8 +10,11 @@ import {
isPriceItem,
} from "@utils/productV2Utils/productItemUtils/getItemType.js";
import { notNullish } from "@utils/utils.js";
import { z } from "zod/v4";
import type { CusProductLegacyData } from "../cusProductLegacyData.js";
import type { z } from "zod/v4";
import {
type CusProductLegacyData,
CusProductLegacyDataSchema,
} from "../cusProductLegacyData.js";
import { ApiCusProductV1Schema } from "../previousVersions/apiCusProductV1.js";
import { ApiCusProductV2Schema } from "../previousVersions/apiCusProductV2.js";
@@ -132,10 +135,6 @@ export function transformCusProductV2ToV1({
return v1CusProduct;
}
const CusProductLegacyDataSchema = z.object({
subscription_id: z.string().optional(),
});
export const V0_2_CusProductChange = defineVersionChange({
newVersion: ApiVersion.V1_1, // Breaking change introduced in V1_1
oldVersion: ApiVersion.V0_2, // Applied when targetVersion <= V0_2

View File

@@ -0,0 +1,112 @@
import type { ApiProductItem } from "@api/models.js";
import { ApiVersion } from "@api/versionUtils/ApiVersion.js";
import {
AffectedResource,
defineVersionChange,
} from "@api/versionUtils/versionChangeUtils/VersionChange.js";
import { CusProductStatus } from "@models/cusProductModels/cusProductEnums.js";
import { convertPlanToItems } from "@utils/planFeatureUtils/planToItems.js";
import { getProductItemResponse } from "@utils/productV2Utils/productItemUtils/getProductItemRes.js";
import type { z } from "zod/v4";
import { type ApiCusPlan, ApiCusPlanSchema } from "../apiCusPlan.js";
import {
type CusProductLegacyData,
CusProductLegacyDataSchema,
} from "../cusProductLegacyData.js";
import { ApiCusProductV3Schema } from "../previousVersions/apiCusProductV3.js";
/**
* Transform plan from V2.0 format to V1.2 product format
* Exported so it can be reused in other transformations (e.g., V1_2_CustomerChange)
*/
export function transformCusPlanToProductV3({
input,
legacyData,
}: {
input: z.infer<typeof ApiCusPlanSchema>;
legacyData?: CusProductLegacyData;
}): z.infer<typeof ApiCusProductV3Schema> {
const cusPlanToCusProductV3Status = (plan: ApiCusPlan) => {
if (plan.status === CusProductStatus.Active) {
if (plan.past_due) {
return "past_due";
}
// trialing
else if (plan.trial_ends_at && plan.trial_ends_at < Date.now()) {
return "trialing";
}
return "active";
}
return plan.status;
};
let items: ApiProductItem[] | null = null;
if (input.plan && legacyData) {
const productItems = convertPlanToItems({ plan: input.plan });
const itemResponses = productItems.map((item) =>
getProductItemResponse({
item,
features: legacyData.features,
options: legacyData.options,
}),
);
items = itemResponses;
}
return {
id: input.plan_id,
// Plan properties...
name: input.plan?.name ?? null,
group: input.plan?.group ?? null,
is_default: input.plan?.default ?? false,
is_add_on: input.add_on,
version: input.plan?.version ?? null,
items: items, // Map from plan to product v2 items...
status: cusPlanToCusProductV3Status(input),
canceled_at: input.canceled_at ?? null,
started_at: input.started_at,
current_period_start: input.current_period_start,
current_period_end: input.current_period_end,
entity_id: null,
quantity: input.quantity,
} satisfies z.infer<typeof ApiCusProductV3Schema>;
}
/**
* V1_2_CusPlanChange: Transforms customer plan response TO V1_2 format
*
* Applied when: targetVersion <= V1_2
*
* Breaking changes introduced in V2.0:
*
* 1. Renamed "products" to "plans" in SDK
* 2. Simplified status enum to only "active"
* 3. Added trial_ends_at field
* 4. Optional product object for expanded plan data
*
* Input: ApiCusPlan (V2.0+ format with "product" field)
* Output: ApiCusProductV3 (V1.2 format with verbose fields)
*/
export const V1_2_CusPlanChange = defineVersionChange({
newVersion: ApiVersion.V2,
oldVersion: ApiVersion.V1_2,
description: [
"Renamed products to plans",
"Simplified status enum",
"Added trial_ends_at field",
],
affectedResources: [AffectedResource.Customer],
newSchema: ApiCusPlanSchema,
oldSchema: ApiCusProductV3Schema,
legacyDataSchema: CusProductLegacyDataSchema,
transformResponse: transformCusPlanToProductV3,
});

View File

@@ -0,0 +1,11 @@
import { FeatureOptionsSchema } from "@models/cusProductModels/cusProductModels.js";
import { FeatureSchema } from "@models/featureModels/featureModels.js";
import { z } from "zod/v4";
export const CusProductLegacyDataSchema = z.object({
subscription_id: z.string().optional(),
options: z.array(FeatureOptionsSchema),
features: z.array(FeatureSchema),
});
export type CusProductLegacyData = z.infer<typeof CusProductLegacyDataSchema>;

View File

@@ -0,0 +1,96 @@
import { ApiProductItemSchema } from "@api/products/planFeature/previousVersions/apiProductItem.js";
import { z } from "zod/v4";
export const ApiCusProductV3Schema = z.object({
id: z.string().meta({
description: "The unique identifier for the product",
example: "pro_plan",
}),
name: z.string().nullable().meta({
description: "The name of the product",
example: "Pro Plan",
}),
group: z.string().nullable().meta({
description: "The group the product belongs to",
example: "product_set_1",
}),
status: z
.enum(["active", "expired", "scheduled", "trialing", "past_due"])
.meta({
description: "Current status of the product for this customer",
example: "active",
}),
canceled_at: z.number().nullish().meta({
description: "Timestamp when the product was canceled for the customer",
example: 1717000000,
}),
started_at: z.number().meta({
description: "Timestamp when the customer started this product",
example: 1700000000000,
}),
is_default: z.boolean().meta({
description: "Whether this product is the default for the customer",
example: true,
}),
is_add_on: z.boolean().meta({
description: "Whether the product is an add-on",
example: false,
}),
version: z.number().nullish().meta({
description: "Version of the product",
example: 1,
}),
stripe_subscription_ids: z
.array(z.string())
.nullish()
.meta({
description:
"List of Stripe subscription IDs associated with this product, if any",
example: ["sub_1Nc0JzBAbcxyz", "sub_1Nc0xyBAnopq"],
}),
current_period_start: z.number().nullish().meta({
description: "Start of the current billing period",
example: 1717000000,
}),
current_period_end: z.number().nullish().meta({
description: "End of the current billing period",
example: 1719600000,
}),
entity_id: z.string().nullish().meta({
description:
"ID of the entity this customer product is attached to, if applicable",
example: "entity_1234abcd",
}),
items: z
.array(ApiProductItemSchema)
.nullish()
.meta({
description: "Array of product items defining the features and pricing",
example: [
{
feature_id: "<string>",
feature_type: "single_use",
included_usage: 123,
interval: "month",
usage_model: "prepaid",
price: 123,
billing_units: 1000,
entity_feature_id: "<string>",
reset_usage_when_enabled: true,
tiers: [
{
to: 100,
amount: 10,
},
],
},
],
}),
quantity: z.number().optional().meta({
description:
"The number of units of this product held by the customer, if applicable",
example: 1,
}),
});
export type ApiCusProductV3 = z.infer<typeof ApiCusProductV3Schema>;

View File

@@ -1,97 +0,0 @@
import { ApiProductItemSchema } from "@api/products/planFeature/previousVersions/apiProductItem.js";
import { z } from "zod/v4";
export const ApiCusProductSchema = z
.object({
id: z.string().meta({
description: "The unique identifier for the product",
example: "pro_plan",
}),
name: z.string().nullable().meta({
description: "The name of the product",
example: "Pro Plan",
}),
group: z.string().nullable().meta({
description: "The group the product belongs to",
example: "product_set_1",
}),
status: z
.enum(["active", "expired", "scheduled", "trialing", "past_due"])
.meta({
description: "Current status of the product for this customer",
example: "active",
}),
canceled_at: z.number().nullish().meta({
description: "Timestamp when the product was canceled for the customer",
example: 1717000000,
}),
started_at: z.number().meta({
description: "Timestamp when the customer started this product",
example: 1700000000000,
}),
is_default: z.boolean().meta({
description: "Whether this product is the default for the customer",
example: true,
}),
is_add_on: z.boolean().meta({
description: "Whether the product is an add-on",
example: false,
}),
version: z.number().nullish().meta({
description: "Version of the product",
example: 1,
}),
stripe_subscription_ids: z
.array(z.string())
.nullish()
.meta({
description:
"List of Stripe subscription IDs associated with this product, if any",
example: ["sub_1Nc0JzBAbcxyz", "sub_1Nc0xyBAnopq"],
}),
current_period_start: z.number().nullish().meta({
description: "Start of the current billing period",
example: 1717000000,
}),
current_period_end: z.number().nullish().meta({
description: "End of the current billing period",
example: 1719600000,
}),
entity_id: z.string().nullish().meta({
description:
"ID of the entity this customer product is attached to, if applicable",
example: "entity_1234abcd",
}),
items: z
.array(ApiProductItemSchema)
.nullish()
.meta({
description: "Array of product items defining the features and pricing",
example: [
{
feature_id: "<string>",
feature_type: "single_use",
included_usage: 123,
interval: "month",
usage_model: "prepaid",
price: 123,
billing_units: 1000,
entity_feature_id: "<string>",
reset_usage_when_enabled: true,
tiers: [
{
to: 100,
amount: 10,
},
],
},
],
}),
quantity: z.number().optional().meta({
description:
"The number of units of this product held by the customer, if applicable",
example: 1,
}),
});
export type ApiCusProduct = z.infer<typeof ApiCusProductSchema>;

View File

@@ -1,7 +0,0 @@
import { z } from "zod/v4";
export const CusProductLegacyDataSchema = z.object({
subscription_id: z.string().optional(),
});
export type CusProductLegacyData = z.infer<typeof CusProductLegacyDataSchema>;

View File

@@ -1,5 +1,5 @@
import { z } from "zod/v4";
import { CusProductLegacyDataSchema } from "./cusProducts/cusProductLegacyData.js";
import { CusProductLegacyDataSchema } from "./cusPlans/cusProductLegacyData.js";
export const CustomerLegacyDataSchema = z.object({
cusProductLegacyData: z.record(z.string(), CusProductLegacyDataSchema),

View File

@@ -1,9 +1,9 @@
import { ApiCusFeatureV0Schema } from "@api/customers/cusFeatures/previousVersions/apiCusFeatureV0.js";
import { ApiCusProductV0Schema } from "@api/customers/cusProducts/previousVersions/apiCusProductV0.js";
import { ApiCusProductV0Schema } from "@api/customers/cusPlans/previousVersions/apiCusProductV0.js";
import { ApiInvoiceSchema } from "@api/others/apiInvoice.js";
import { AppEnv } from "@models/genModels/genEnums.js";
import { z } from "zod/v4";
import { ApiTrialsUsedSchema } from "../apiCustomer.js";
import { ApiTrialsUsedSchema } from "./apiCustomerV3.js";
/**
* ApiCustomerV0Schema - Customer response format for API V0_1

View File

@@ -2,8 +2,8 @@ import { ApiCusFeatureV1Schema } from "@api/customers/cusFeatures/previousVersio
import { ApiInvoiceSchema } from "@api/others/apiInvoice.js";
import { AppEnv } from "@models/genModels/genEnums.js";
import { z } from "zod/v4";
import { ApiTrialsUsedSchema } from "../apiCustomer.js";
import { ApiCusProductV1Schema } from "../cusProducts/previousVersions/apiCusProductV1.js";
import { ApiCusProductV1Schema } from "../cusPlans/previousVersions/apiCusProductV1.js";
import { ApiTrialsUsedSchema } from "./apiCustomerV3.js";
/**
* ApiCustomerV1Schema - Customer response format for API V0_2/V1_0/V1_1 (pre V1_2)

View File

@@ -1,7 +1,7 @@
import { ApiCusReferralSchema } from "@api/customers/components/apiCusReferral.js";
import { ApiCusUpcomingInvoiceSchema } from "@api/customers/components/apiCusUpcomingInvoice.js";
import { ApiCusFeatureV2Schema } from "@api/customers/cusFeatures/previousVersions/apiCusFeatureV2.js";
import { ApiCusProductV2Schema } from "@api/customers/cusProducts/previousVersions/apiCusProductV2.js";
import { ApiCusProductV2Schema } from "@api/customers/cusPlans/previousVersions/apiCusProductV2.js";
import { ApiEntitySchema } from "@api/entities/apiEntity.js";
import { ApiCusRewardsSchema } from "@api/models.js";
import { ApiInvoiceSchema } from "@api/others/apiInvoice.js";

View File

@@ -0,0 +1,71 @@
import { ApiCusReferralSchema } from "@api/customers/components/apiCusReferral.js";
import { ApiBaseEntitySchema } from "@api/entities/apiEntity.js";
import { ApiCusRewardsSchema } from "@api/others/apiDiscount.js";
import { ApiInvoiceSchema } from "@api/others/apiInvoice.js";
import { AppEnv } from "@models/genModels/genEnums.js";
import { z } from "zod/v4";
import { ApiCusUpcomingInvoiceSchema } from "../components/apiCusUpcomingInvoice.js";
import { ApiCusFeatureV3Schema } from "../cusFeatures/previousVersions/apiCusFeatureV3.js";
import { ApiCusProductV3Schema } from "../cusPlans/previousVersions/apiCusProductV3.js";
export const ApiTrialsUsedSchema = z.object({
product_id: z.string(),
customer_id: z.string(),
fingerprint: z.string().nullish(),
});
export const ApiCusExpandV3Schema = z.object({
invoices: z.array(ApiInvoiceSchema).optional(),
entities: z.array(ApiBaseEntitySchema).optional(),
trials_used: z.array(ApiTrialsUsedSchema).optional(),
rewards: ApiCusRewardsSchema.nullish(),
referrals: z.array(ApiCusReferralSchema).optional(),
upcoming_invoice: ApiCusUpcomingInvoiceSchema.nullish(),
payment_method: z.any().nullish(),
});
export const ApiCustomerV3Schema = z.object({
// Internal fields
id: z.string().nullable().meta({
description: "Your internal ID for the customer",
example: "cus_123",
}),
created_at: z.number().meta({
description: "Timestamp of customer creation in milliseconds since epoch",
example: 1717000000,
}),
name: z.string().nullable().meta({
description: "Customer's name",
example: "John Doe",
}),
email: z.string().nullable().meta({
description: "Customer's email address",
example: "john@doe.com",
}),
fingerprint: z.string().nullable().meta({
description:
"Unique identifier (eg. serial number) to detect duplicate customers and prevent key leaks",
example: "fp_9184And92839123hda",
}),
stripe_id: z.string().nullable().default(null).meta({
description: "Stripe customer ID",
example: "cus_J8A5c31A8tlpwN",
}),
env: z.enum(AppEnv).meta({
description: "Environment the customer is in",
example: "production",
}),
metadata: z.record(z.any(), z.any()).default({}),
products: z.array(ApiCusProductV3Schema).meta({
description: "List of products the customer has access to",
example: [],
}),
features: z.record(z.string(), ApiCusFeatureV3Schema).meta({
description: "List of features the customer has access to",
example: {},
}),
...ApiCusExpandV3Schema.shape,
});
export type ApiCustomerV3 = z.infer<typeof ApiCustomerV3Schema>;
export type ApiCustomerV3Expand = z.infer<typeof ApiCusExpandV3Schema>;

View File

@@ -0,0 +1,60 @@
import { ApiVersion } from "@api/versionUtils/ApiVersion.js";
import {
AffectedResource,
defineVersionChange,
} from "@api/versionUtils/versionChangeUtils/VersionChange.js";
import { CusExpand } from "@models/cusModels/cusExpand.js";
import type { z } from "zod/v4";
import { GetCustomerQuerySchema } from "../customerOpModels.js";
/**
* V1_2_CustomerQueryChange: Transforms customer query TO latest format
*
* Applied when: sourceVersion <= V1_2
*
* Changes introduced in V2.0:
*
* 1. Added new expand options:
* - "plans.plan" - expands the full plan object in each customer plan
* - "features.feature" - expands the full feature object in each customer feature
*
* This transformation automatically adds these expand options when transforming
* from V1.2 to V2.0, so that V1.2 clients get the full plan/feature objects
* (matching their expected verbose format).
*
* Input: Query without plans.plan/features.feature expand
* Output: Query with plans.plan and features.feature automatically expanded
*/
export const V1_2_CustomerQueryChange = defineVersionChange({
newVersion: ApiVersion.V2,
oldVersion: ApiVersion.V1_Beta,
description: [
"Automatically expands plans.plan and features.feature for V1.2 clients",
"Ensures V1.2 clients get full plan/feature objects",
],
affectedResources: [AffectedResource.Customer],
newSchema: GetCustomerQuerySchema,
oldSchema: GetCustomerQuerySchema,
// Only transform requests (incoming queries)
affectsRequest: true,
affectsResponse: false,
// Request: V1.2 → V2.0 (add expand options)
transformRequest: ({
input,
}: {
input: z.infer<typeof GetCustomerQuerySchema>;
}) => {
const existingExpand = input.expand || [];
// Add `plan` and `feature` to expand array
const newExpand = [...existingExpand, CusExpand.Plan, CusExpand.Feature];
return {
...input,
expand: newExpand,
} satisfies z.infer<typeof GetCustomerQuerySchema>;
},
});

View File

@@ -1,5 +1,5 @@
import { ApiCusFeatureSchema } from "@api/customers/cusFeatures/apiCusFeature.js";
import { ApiCusProductSchema } from "@api/customers/cusProducts/apiCusProduct.js";
import { ApiCusFeatureV3Schema } from "@api/customers/cusFeatures/previousVersions/apiCusFeatureV3.js";
import { ApiCusPlanSchema } from "@api/customers/cusPlans/apiCusPlan.js";
import { ApiInvoiceSchema } from "@api/others/apiInvoice.js";
import { AppEnv } from "@models/genModels/genEnums.js";
import { z } from "zod/v4";
@@ -29,11 +29,11 @@ export const ApiBaseEntitySchema = z.object({
});
export const ApiEntitySchema = ApiBaseEntitySchema.extend({
products: z.array(ApiCusProductSchema).optional().meta({
description: "Products associated with this entity",
plans: z.array(ApiCusPlanSchema).optional().meta({
description: "Plans associated with this entity",
example: [],
}),
features: z.record(z.string(), ApiCusFeatureSchema).optional().meta({
features: z.record(z.string(), ApiCusFeatureV3Schema).optional().meta({
description: "Features associated with this entity",
example: {},
}),
@@ -44,4 +44,4 @@ export const ApiEntitySchema = ApiBaseEntitySchema.extend({
}),
});
export type EntityResponse = z.infer<typeof ApiEntitySchema>;
export type ApiEntity = z.infer<typeof ApiEntitySchema>;

View File

@@ -18,8 +18,8 @@ export * from "./customers/cusFeatures/apiCusFeature.js";
export * from "./customers/cusFeatures/previousVersions/apiCusFeatureV0.js";
export * from "./customers/cusFeatures/previousVersions/apiCusFeatureV1.js";
export * from "./customers/cusFeatures/previousVersions/apiCusFeatureV2.js";
export * from "./customers/cusProducts/apiCusProduct.js";
export * from "./customers/cusProducts/cusProductLegacyData.js";
export * from "./customers/cusPlans/apiCusPlan.js";
export * from "./customers/cusPlans/cusProductLegacyData.js";
export * from "./customers/customerLegacyData.js";
export * from "./customers/customerOpModels.js";
export * from "./customers/previousVersions/apiCustomerV2.js";

View File

@@ -9,7 +9,7 @@ import { CustomerDataSchema } from "./common/customerData.js";
import { EntityDataSchema } from "./common/entityData.js";
import { coreOps } from "./core/coreOpenApi.js";
import { ApiCusFeatureSchema } from "./customers/cusFeatures/apiCusFeature.js";
import { ApiCusProductSchema } from "./customers/cusProducts/apiCusProduct.js";
import { ApiCusPlanSchema } from "./customers/cusPlans/apiCusPlan.js";
import {
ApiCustomerWithMeta,
customerOps,
@@ -65,9 +65,9 @@ const document = createDocument({
id: "Customer",
description: "Customer object returned by the API",
}),
CustomerProduct: ApiCusProductSchema.meta({
id: "CustomerProduct",
description: "Customer product object returned by the API",
CustomerPlan: ApiCusPlanSchema.meta({
id: "CustomerPlan",
description: "Customer plan object returned by the API",
}),
CustomerFeature: ApiCusFeatureSchema.meta({
id: "CustomerFeature",
@@ -79,7 +79,8 @@ const document = createDocument({
}),
ProductItem: ApiProductItemWithMeta.meta({
id: "ProductItem",
description: "Product item defining features and pricing within a product",
description:
"Product item defining features and pricing within a product",
}),
Feature: ApiFeatureWithMeta.meta({
id: "Feature",

View File

@@ -27,10 +27,15 @@ export const ApiPlanSchema = z.object({
add_on: z.boolean(),
default: z.boolean(),
price: z.object({
amount: z.number(),
interval: z.enum(BillingInterval),
}),
// Change
price: z
.object({
amount: z.number(),
interval: z.enum(BillingInterval),
interval_count: z.number().optional(),
// tiers: z.array(UsageTierSchema).optional(),
})
.nullable(),
features: z.array(ApiPlanFeatureSchema),
free_trial: ApiFreeTrialV2Schema.nullable().optional(),

View File

@@ -40,7 +40,7 @@ import {
export const V1_2_ProductChanges = defineVersionChange({
newVersion: ApiVersion.V2, // Breaking change introduced in V2
oldVersion: ApiVersion.V1_2, // Applied when targetVersion <= V1.2
oldVersion: ApiVersion.V1_Beta, // Applied when targetVersion <= V1.2
description: [
"Product format changed from Plan to ProductV2 schema",
"Features array renamed to items with different structure",

View File

@@ -11,12 +11,11 @@ import { ResetInterval } from "../planEnums.js";
export const ApiPlanFeatureSchema = z
.object({
feature_id: z.string(),
granted: z.number(),
granted_balance: z.number(),
unlimited: z.boolean(),
reset_interval: z.enum(ResetInterval).optional(),
reset_interval_count: z.number().optional(),
reset_usage_on_enabled: z.boolean().optional(),
reset_usage_when_enabled: z.boolean().optional(),
price: z
.object({
@@ -48,6 +47,13 @@ export const ApiPlanFeatureSchema = z
expiry_duration_length: z.number().optional(),
})
.optional(),
display: z
.object({
primary_text: z.string(),
secondary_text: z.string().optional(),
})
.optional(),
})
.check((ctx) => {
const resetGroup =

View File

@@ -5,7 +5,7 @@
*/
export enum ApiVersion {
V2 = "2.0.0",
Beta = "beta",
V1_Beta = "beta",
V1_2 = "1.2.0",
V1_1 = "1.1.0",
V0_2 = "0.2.0",

View File

@@ -52,7 +52,7 @@ export function legacyToSemVer({
return ApiVersion.V1_2;
case LegacyVersion.v1_4:
case 1.4:
return ApiVersion.Beta;
return ApiVersion.V1_Beta;
default:
return null;
}
@@ -76,7 +76,7 @@ export function semVerToLegacy({
return LegacyVersion.v1_1;
case ApiVersion.V1_2:
return LegacyVersion.v1_2;
case ApiVersion.Beta:
case ApiVersion.V1_Beta:
return LegacyVersion.v1_4;
default:
return null;
@@ -86,7 +86,10 @@ export function semVerToLegacy({
/**
* Parse version string (CalVer, SemVer, or legacy)
* Supports .clover suffix: "2025-04-17.clover"
* Supports partial SemVer: "2" → "2.0.0", "2.0" → "2.0.0"
* @example parseVersion({ versionStr: "2025-04-17" }) // ApiVersion.V1_1
* @example parseVersion({ versionStr: "2" }) // ApiVersion.V2
* @example parseVersion({ versionStr: "2.0" }) // ApiVersion.V2
*/
export function parseVersion({
versionStr,
@@ -98,7 +101,18 @@ export function parseVersion({
return calVerToSemVer({ calver: versionStr });
}
// SemVer (X.Y.Z)
// SemVer (X.Y.Z) - normalize partial versions
// Supports "2" → "2.0.0", "2.0" → "2.0.0", "2.0.0" → "2.0.0"
if (/^\d+(\.\d+)*$/.test(versionStr)) {
const parts = versionStr.split(".");
const normalizedVersion = `${parts[0] || "0"}.${parts[1] || "0"}.${parts[2] || "0"}`;
if (Object.values(ApiVersion).includes(normalizedVersion as ApiVersion)) {
return normalizedVersion as ApiVersion;
}
}
// Check for exact match (for special versions like "beta")
if (Object.values(ApiVersion).includes(versionStr as ApiVersion)) {
return versionStr as ApiVersion;
}
@@ -143,8 +157,8 @@ export function createdAtToVersion({
version = ApiVersion.V0_1;
}
// return new ApiVersionClass(version);
return new ApiVersionClass(ApiVersion.V2);
return new ApiVersionClass(version);
// return new ApiVersionClass(ApiVersion.V2);
}
// Convert org creation date

View File

@@ -9,7 +9,6 @@ import type {
* Maps versions → change classes
*/
const changes = new Map<ApiVersion, VersionChangeConstructor[]>();
const instances = new Map<string, VersionChange>();
export const VersionChangeRegistryClass = {
register({
@@ -24,17 +23,8 @@ export const VersionChangeRegistryClass = {
getChangesForVersion({ version }: { version: ApiVersion }): VersionChange[] {
const changeClasses = changes.get(version) || [];
return changeClasses.map((ChangeClass) => {
const key = `${version}-${ChangeClass.name}`;
if (!instances.has(key)) {
instances.set(key, new ChangeClass());
}
const instance = instances.get(key);
if (!instance) {
throw new Error(`Failed to create instance for ${ChangeClass.name}`);
}
return instance;
});
// Simply instantiate each change class - no caching needed since they're stateless
return changeClasses.map((ChangeClass) => new ChangeClass());
},
getRegisteredVersions(): ApiVersion[] {
@@ -54,7 +44,6 @@ export const VersionChangeRegistryClass = {
clear() {
changes.clear();
instances.clear();
},
};

View File

@@ -1,7 +1,13 @@
import { type ApiVersion, LATEST_VERSION } from "../ApiVersion.js";
import { ApiVersionClass } from "../ApiVersionClass.js";
import { getVersionsBetween } from "../versionRegistryUtils.js";
import type { AffectedResource } from "./VersionChange.js";
import {
getVersionsBetween,
getVersionsForRequestTransform,
} from "../versionRegistryUtils.js";
import type {
AffectedResource,
VersionChangeConstructor,
} from "./VersionChange.js";
import { VersionChangeRegistryClass } from "./VersionChangeRegistryClass.js";
/**
@@ -33,6 +39,7 @@ export function applyResponseVersionChanges<T = any, TLegacyData = any>({
}): T {
// Default currentVersion to latest if not provided
const _currentVersion = currentVersion || new ApiVersionClass(LATEST_VERSION);
// If versions are equal, no transformation needed
if (_currentVersion.eq(targetVersion)) {
return input;
@@ -58,6 +65,7 @@ export function applyResponseVersionChanges<T = any, TLegacyData = any>({
// Apply each version's changes
let transformedData = input;
for (const version of versionsToApply) {
const changes = VersionChangeRegistryClass.getChangesForVersion({
version,
@@ -88,7 +96,7 @@ export function applyResponseVersionChanges<T = any, TLegacyData = any>({
? change.description.join("; ")
: change.description;
// console.log(`Applying change [${change.oldVersion}]: ${description}`);
console.log(`Applying change [${change.oldVersion}]: ${description}`);
// Apply the response transformation (backward)
transformedData = change.transformResponse({
input: transformedData,
@@ -127,34 +135,34 @@ export function applyResponseVersionChanges<T = any, TLegacyData = any>({
export function applyRequestVersionChanges<T = any, TLegacyData = any>({
input,
legacyData,
targetVersion,
currentVersion,
fromVersion,
toVersion,
resource,
}: {
input: T;
legacyData?: TLegacyData;
targetVersion: ApiVersionClass; // User's version (old)
currentVersion: ApiVersionClass; // Latest version (new)
fromVersion: ApiVersionClass; // User's version (old)
toVersion: ApiVersionClass; // Latest version (new)
resource: AffectedResource;
}): T {
// If versions are equal, no transformation needed
if (targetVersion.eq(currentVersion)) {
if (fromVersion.eq(toVersion)) {
return input;
}
// If target is newer than current, throw error
if (targetVersion.gt(currentVersion)) {
if (fromVersion.gt(toVersion)) {
throw new Error(
`Cannot transform forward from ${currentVersion} to ${targetVersion}. ` +
`Cannot transform forward from ${fromVersion} to ${toVersion}. ` +
"Current version should be >= target version.",
);
}
// Get all versions between target and current (exclusive of current, inclusive of target)
const versionsToApply = getVersionsBetween({
from: targetVersion.value,
to: currentVersion.value,
}).filter((v) => v !== currentVersion.value); // Exclude current itself
const versionsToApply = getVersionsForRequestTransform({
from: fromVersion.value,
to: toVersion.value,
});
// Don't reverse - we want to go forward (old → new)
// versionsToApply is already in ascending order from getVersionsBetween
@@ -213,7 +221,7 @@ export function backwardsChangeActive({
versionChange,
}: {
apiVersion: ApiVersionClass;
versionChange: new () => any;
versionChange: VersionChangeConstructor;
}): boolean {
const change = new versionChange();
return apiVersion.lte(change.oldVersion);
@@ -254,22 +262,22 @@ export function applyResponseVersionChangesToArray<T = any, TLegacyData = any>({
export function applyRequestVersionChangesToArray<T = any, TLegacyData = any>({
inputArray,
legacyData,
targetVersion,
currentVersion,
fromVersion,
toVersion,
resource,
}: {
inputArray: T[];
legacyData?: TLegacyData;
targetVersion: ApiVersionClass;
currentVersion: ApiVersionClass;
fromVersion: ApiVersionClass;
toVersion: ApiVersionClass;
resource: AffectedResource;
}): T[] {
return inputArray.map((item) =>
applyRequestVersionChanges({
input: item,
legacyData,
targetVersion,
currentVersion,
fromVersion,
toVersion,
resource,
}),
);

View File

@@ -4,16 +4,20 @@ import { V0_1_CustomerChange } from "@api/customers/changes/V0.1_CustomerChange.
import { V0_2_CustomerChange } from "@api/customers/changes/V0.2_CustomerChange.js";
import { V0_2_InvoicesAlwaysExpanded } from "@api/customers/changes/V0.2_InvoicesAlwaysExpanded.js";
import { V1_1_FeaturesArrayToObject } from "@api/customers/changes/V1.1_FeaturesArrayToObject.js";
// Import customer product changes
import { V1_2_CustomerChange } from "@api/customers/changes/V1.2_CustomerChange.js";
import { V1_2_CustomerQueryChange } from "@api/customers/requestChanges/V1.2_CustomerQueryChange.js";
// Import product changes
import { V1_2_ProductChanges } from "@api/products/changes/V1.2_ProductChanges.js";
import { ApiVersion } from "../ApiVersion.js";
import type { VersionChangeConstructor } from "./VersionChange.js";
import { VersionChangeRegistryClass } from "./VersionChangeRegistryClass.js";
export const V2_CHANGES: VersionChangeConstructor[] = [
V1_2_CustomerChange, // Transforms Customer TO V1.2 format from V2 format
V1_2_CustomerQueryChange, // Transforms Customer Query TO V2.0 format (adds expand options)
V1_2_ProductChanges, // Transforms Product TO V1.2 format from V2 Plan format
];
@@ -42,7 +46,7 @@ export function registerAllVersionChanges() {
changes: V2_CHANGES,
});
VersionChangeRegistryClass.register({
version: ApiVersion.Beta,
version: ApiVersion.V1_Beta,
changes: V1_4_CHANGES,
});
VersionChangeRegistryClass.register({

View File

@@ -20,8 +20,9 @@ export const VERSION_REGISTRY: Record<ApiVersion, VersionMetadata> = {
releasedAt: new Date("2025-10-16").getTime(),
description: "Plan features with max_purchase",
},
[ApiVersion.Beta]: {
semver: ApiVersion.Beta,
[ApiVersion.V1_Beta]: {
semver: ApiVersion.V1_Beta,
calver: "beta",
releasedAt: new Date("2025-06-01").getTime(),
description: "Beta version with experimental features",

Some files were not shown because too many files have changed in this diff Show More