added migrations to git

This commit is contained in:
johnyeo
2026-05-18 20:48:51 +01:00
parent 5018cca2ce
commit 36e530dee7
22 changed files with 8541 additions and 76 deletions

3
.gitignore vendored
View File

@@ -83,9 +83,6 @@ next-env.d.ts
*.rdb
shared/drizzle
run.sh
commands.sh
env.sh

2
ai

Submodule ai updated: a6d3ad0403...b1efb8d303

116
bun.lock
View File

@@ -315,10 +315,12 @@
"inquirer": "^12.6.3",
"ioredis": "^5.10.0",
"p-limit": "^7.2.0",
"pg": "8.20.0",
"react": "^18.3.1",
},
"devDependencies": {
"@types/bun": "^1.3.11",
"@types/pg": "8.11.10",
"@types/react": "^18.3.1",
"tsx": "^4.19.2",
"typescript": "^5.7.3",
@@ -2438,7 +2440,7 @@
"@types/parse-json": ["@types/parse-json@4.0.2", "", {}, "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="],
"@types/pg": ["@types/pg@8.20.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow=="],
"@types/pg": ["@types/pg@8.11.10", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^4.0.1" } }, "sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg=="],
"@types/pg-pool": ["@types/pg-pool@2.0.7", "", { "dependencies": { "@types/pg": "*" } }, "sha512-U4CwmGVQcbEuqpyju8/ptOKg6gEC+Tqsvj2xS9o1g71bUh8twxnC6ZL5rZKCsGN0iyH0CwgUyc9VR5owNQF9Ng=="],
@@ -4678,6 +4680,8 @@
"object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
"obuf": ["obuf@1.1.2", "", {}, "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg=="],
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
"on-exit-leak-free": ["on-exit-leak-free@2.1.2", "", {}, "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA=="],
@@ -4816,11 +4820,13 @@
"pg-node-migrations": ["pg-node-migrations@0.0.8", "", { "dependencies": { "pg": "^8.6.0", "sql-template-strings": "^2.2.2" }, "bin": { "pg-validate-migrations": "dist/bin/validate.js" } }, "sha512-44cMl9umOmCv0hzZyEcvjEq8Bm8u7mrzggZ06qXTJVSsMMB4j2OsjG+rSp+uzeKWyP2Vu0K9Ye2wKtjFUJwrdw=="],
"pg-numeric": ["pg-numeric@1.0.2", "", {}, "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw=="],
"pg-pool": ["pg-pool@3.13.0", "", { "peerDependencies": { "pg": ">=8.0" } }, "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA=="],
"pg-protocol": ["pg-protocol@1.13.0", "", {}, "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w=="],
"pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
"pg-types": ["pg-types@4.1.0", "", { "dependencies": { "pg-int8": "1.0.1", "pg-numeric": "1.0.2", "postgres-array": "~3.0.1", "postgres-bytea": "~3.0.0", "postgres-date": "~2.1.0", "postgres-interval": "^3.0.0", "postgres-range": "^1.1.1" } }, "sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg=="],
"pgpass": ["pgpass@1.0.5", "", { "dependencies": { "split2": "^4.1.0" } }, "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug=="],
@@ -4888,13 +4894,15 @@
"postgres": ["postgres@3.4.7", "", {}, "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw=="],
"postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
"postgres-array": ["postgres-array@3.0.4", "", {}, "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ=="],
"postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="],
"postgres-bytea": ["postgres-bytea@3.0.0", "", { "dependencies": { "obuf": "~1.1.2" } }, "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw=="],
"postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="],
"postgres-date": ["postgres-date@2.1.0", "", {}, "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA=="],
"postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
"postgres-interval": ["postgres-interval@3.0.0", "", {}, "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw=="],
"postgres-range": ["postgres-range@1.1.4", "", {}, "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w=="],
"posthog-js": ["posthog-js@1.372.10", "", { "dependencies": { "@opentelemetry/api": "^1.9.0", "@opentelemetry/api-logs": "^0.208.0", "@opentelemetry/exporter-logs-otlp-http": "^0.208.0", "@opentelemetry/resources": "^2.2.0", "@opentelemetry/sdk-logs": "^0.208.0", "@posthog/core": "1.28.4", "@posthog/types": "1.372.10", "core-js": "^3.38.1", "dompurify": "^3.3.2", "fflate": "^0.4.8", "preact": "^10.28.2", "query-selector-shadow-dom": "^1.0.1", "web-vitals": "^5.1.0" } }, "sha512-ZQslIenDM8UpwKhmeeEnJ+t2nXr8mOIjCG+Ej3DCJnTBk9NX9Sr5RMuwHeGG8UJitwvtGOADyiY7DignOXaZwg=="],
@@ -5930,6 +5938,8 @@
"@autumn/server/@types/node": ["@types/node@25.6.2", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw=="],
"@autumn/server/@types/pg": ["@types/pg@8.20.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow=="],
"@autumn/server/@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260511.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260511.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260511.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-cUyY4Sr6065280lB6hCwTMCBMTxlEIGjSLzHym28yikA5sFiEsAzlwiU0i+XkTUIqr5K5M/SzSJiioDN+vpjtA=="],
"@autumn/server/autumn-js": ["autumn-js@0.1.85", "", { "dependencies": { "query-string": "^9.2.2", "rou3": "^0.6.1", "swr": "^2.3.3", "zod": "^4.0.0" }, "peerDependencies": { "better-auth": "^1.3.17", "better-call": "^1.0.12", "convex": "^1.25.4", "react": "*" }, "optionalPeers": ["better-auth", "better-call", "convex", "react"] }, "sha512-PDud/t8z5bDJcD7ptyHzTaoJ0A8zkxvQ4TYcJ48RtgKDdOkVY36D1T6udVLwLDnWw4J5KXwJgEuGxHdd+cuABw=="],
@@ -5938,6 +5948,8 @@
"@autumn/shared/@date-fns/utc": ["@date-fns/utc@2.1.0", "", {}, "sha512-176grgAgU2U303rD2/vcOmNg0kGPbhzckuH1TEP2al7n0AQipZIy9P15usd2TKQCG1g+E1jX/ZVQSzs4sUDwgA=="],
"@autumn/stripe-sync/@types/pg": ["@types/pg@8.20.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow=="],
"@autumn/vite/@types/node": ["@types/node@22.19.19", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew=="],
"@autumn/vite/date-fns": ["date-fns@3.6.0", "", {}, "sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww=="],
@@ -6134,6 +6146,8 @@
"@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
"@better-auth/cli/@types/pg": ["@types/pg@8.20.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow=="],
"@better-auth/cli/commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
"@better-auth/cli/dotenv": ["dotenv@17.4.2", "", {}, "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw=="],
@@ -6834,6 +6848,8 @@
"@types/pg/@types/node": ["@types/node@25.6.2", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw=="],
"@types/pg-pool/@types/pg": ["@types/pg@8.20.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow=="],
"@types/react-syntax-highlighter/@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
"@types/responselike/@types/node": ["@types/node@25.6.2", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw=="],
@@ -7346,6 +7362,8 @@
"path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
"pg/pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
"pino/pino-abstract-transport": ["pino-abstract-transport@2.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw=="],
"pino-pretty/pino-abstract-transport": ["pino-abstract-transport@3.0.0", "", { "dependencies": { "split2": "^4.0.0" } }, "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg=="],
@@ -7462,6 +7480,8 @@
"sdk-test/@types/node": ["@types/node@20.19.41", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ=="],
"sdk-test/@types/pg": ["@types/pg@8.20.0", "", { "dependencies": { "@types/node": "*", "pg-protocol": "*", "pg-types": "^2.2.0" } }, "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow=="],
"sdk-test/@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
"sdk-test/@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
@@ -7710,6 +7730,8 @@
"@autumn/server/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
"@autumn/server/@types/pg/pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
"@autumn/server/@typescript/native-preview/@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260511.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-SYrqVOlapDxDG7FzHBIJbfgaix+mXPkYzYGqwpz/TAhoPA7sgbfAoGLaqi3ut9N88C/OYNhEX4tjz/0PC9i1nw=="],
"@autumn/server/@typescript/native-preview/@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260511.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zIe31OYgBvkgTIQEwJtKim6SYyuVTkr+9fK/87hVwKN15X3Ikjeh0C0g2W/Vl4rXeMvy95wBGDN1jpW11DIvgg=="],
@@ -7748,6 +7770,10 @@
"@autumn/server/ink/widest-line": ["widest-line@6.0.0", "", { "dependencies": { "string-width": "^8.1.0" } }, "sha512-U89AsyEeAsyoF0zVJBkG9zBgekjgjK7yk9sje3F4IQpXBJ10TF6ByLlIfjMhcmHMJgHZI4KHt4rdNfktzxIAMA=="],
"@autumn/stripe-sync/@types/pg/@types/node": ["@types/node@25.6.2", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw=="],
"@autumn/stripe-sync/@types/pg/pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
"@autumn/vite/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"@autumn/website/eslint/@eslint/config-array": ["@eslint/config-array@0.23.5", "", { "dependencies": { "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", "minimatch": "^10.2.4" } }, "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA=="],
@@ -7866,6 +7892,10 @@
"@babel/helper-compilation-targets/lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
"@better-auth/cli/@types/pg/@types/node": ["@types/node@25.6.2", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw=="],
"@better-auth/cli/@types/pg/pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
"@datadog/datadog-api-client/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
"@dotenvx/dotenvx/execa/get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
@@ -8314,6 +8344,8 @@
"@opentelemetry/instrumentation-pg/@types/pg/@types/node": ["@types/node@25.6.2", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw=="],
"@opentelemetry/instrumentation-pg/@types/pg/pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
"@opentelemetry/instrumentation-redis/@opentelemetry/instrumentation/@opentelemetry/api-logs": ["@opentelemetry/api-logs@0.211.0", "", { "dependencies": { "@opentelemetry/api": "^1.3.0" } }, "sha512-swFdZq8MCdmdR22jTVGQDhwqDzcI4M10nhjXkLr1EsIzXgZBqm4ZlmmcWsg3TSNf+3mzgOiqveXmBLZuDi2Lgg=="],
"@opentelemetry/instrumentation-redis/@opentelemetry/instrumentation/import-in-the-middle": ["import-in-the-middle@2.0.6", "", { "dependencies": { "acorn": "^8.15.0", "acorn-import-attributes": "^1.9.5", "cjs-module-lexer": "^2.2.0", "module-details-from-path": "^1.0.4" } }, "sha512-3vZV3jX0XRFW3EJDTwzWoZa+RH1b8eTTx6YOCjglrLyPuepwoBti1k3L2dKwdCUrnVEfc5CuRuGstaC/uQJJaw=="],
@@ -8478,6 +8510,10 @@
"@types/node-fetch/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
"@types/pg-pool/@types/pg/@types/node": ["@types/node@25.6.2", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw=="],
"@types/pg-pool/@types/pg/pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
"@types/pg/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
"@types/responselike/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
@@ -8938,6 +8974,14 @@
"p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
"pg/pg-types/postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
"pg/pg-types/postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="],
"pg/pg-types/postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="],
"pg/pg-types/postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
"pkg-conf/find-up/locate-path": ["locate-path@7.2.0", "", { "dependencies": { "p-locate": "^6.0.0" } }, "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA=="],
"pkg-conf/find-up/path-exists": ["path-exists@5.0.0", "", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="],
@@ -9072,6 +9116,10 @@
"sdk-test/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="],
"sdk-test/@types/pg/@types/node": ["@types/node@25.6.2", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw=="],
"sdk-test/@types/pg/pg-types": ["pg-types@2.2.0", "", { "dependencies": { "pg-int8": "1.0.1", "postgres-array": "~2.0.0", "postgres-bytea": "~1.0.0", "postgres-date": "~1.0.4", "postgres-interval": "^1.1.0" } }, "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA=="],
"sdk-test/next/@next/env": ["@next/env@16.1.6", "", {}, "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ=="],
"sdk-test/next/@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.1.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw=="],
@@ -9230,10 +9278,28 @@
"@artilleryio/int-core/socket.io-client/engine.io-client/xmlhttprequest-ssl": ["xmlhttprequest-ssl@2.1.2", "", {}, "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ=="],
"@autumn/server/@types/pg/pg-types/postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
"@autumn/server/@types/pg/pg-types/postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="],
"@autumn/server/@types/pg/pg-types/postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="],
"@autumn/server/@types/pg/pg-types/postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
"@autumn/server/ink/@alcalzone/ansi-tokenize/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="],
"@autumn/server/ink/slice-ansi/is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="],
"@autumn/stripe-sync/@types/pg/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
"@autumn/stripe-sync/@types/pg/pg-types/postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
"@autumn/stripe-sync/@types/pg/pg-types/postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="],
"@autumn/stripe-sync/@types/pg/pg-types/postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="],
"@autumn/stripe-sync/@types/pg/pg-types/postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
"@autumn/website/eslint/@eslint/config-array/@eslint/object-schema": ["@eslint/object-schema@3.0.5", "", {}, "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw=="],
"@aws-sdk/client-sso-oidc/@aws-sdk/core/@smithy/signature-v4/@smithy/is-array-buffer": ["@smithy/is-array-buffer@3.0.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-+Fsu6Q6C4RSJiy81Y8eApjEB5gVtM+oFKTffg+jSuwtvomJJrhUJBu2zS8wjXSgH/g1MKEWrzyChTBe6clb5FQ=="],
@@ -9298,6 +9364,16 @@
"@aws-sdk/signature-v4/@smithy/signature-v4/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@1.1.0", "", { "dependencies": { "@smithy/is-array-buffer": "^1.1.0", "tslib": "^2.5.0" } }, "sha512-9m6NXE0ww+ra5HKHCHig20T+FAwxBAm7DIdwc/767uGWbRcY720ybgPacQNB96JMOI7xVr/CDa3oMzKmW4a+kw=="],
"@better-auth/cli/@types/pg/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
"@better-auth/cli/@types/pg/pg-types/postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
"@better-auth/cli/@types/pg/pg-types/postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="],
"@better-auth/cli/@types/pg/pg-types/postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="],
"@better-auth/cli/@types/pg/pg-types/postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
"@dotenvx/dotenvx/execa/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"@eslint/config-array/minimatch/brace-expansion/balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
@@ -9580,6 +9656,14 @@
"@opentelemetry/instrumentation-pg/@types/pg/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
"@opentelemetry/instrumentation-pg/@types/pg/pg-types/postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
"@opentelemetry/instrumentation-pg/@types/pg/pg-types/postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="],
"@opentelemetry/instrumentation-pg/@types/pg/pg-types/postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="],
"@opentelemetry/instrumentation-pg/@types/pg/pg-types/postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
"@opentelemetry/instrumentation-redis/@opentelemetry/instrumentation/import-in-the-middle/cjs-module-lexer": ["cjs-module-lexer@2.2.0", "", {}, "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ=="],
"@opentelemetry/instrumentation-tedious/@opentelemetry/instrumentation/import-in-the-middle/cjs-module-lexer": ["cjs-module-lexer@2.2.0", "", {}, "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ=="],
@@ -9642,6 +9726,16 @@
"@types/bun/bun-types/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
"@types/pg-pool/@types/pg/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
"@types/pg-pool/@types/pg/pg-types/postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
"@types/pg-pool/@types/pg/pg-types/postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="],
"@types/pg-pool/@types/pg/pg-types/postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="],
"@types/pg-pool/@types/pg/pg-types/postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
"artillery-plugin-ensure/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
"artillery-plugin-ensure/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
@@ -9878,6 +9972,16 @@
"run-jxa/execa/onetime/mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
"sdk-test/@types/pg/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
"sdk-test/@types/pg/pg-types/postgres-array": ["postgres-array@2.0.0", "", {}, "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA=="],
"sdk-test/@types/pg/pg-types/postgres-bytea": ["postgres-bytea@1.0.1", "", {}, "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ=="],
"sdk-test/@types/pg/pg-types/postgres-date": ["postgres-date@1.0.7", "", {}, "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q=="],
"sdk-test/@types/pg/pg-types/postgres-interval": ["postgres-interval@1.2.0", "", { "dependencies": { "xtend": "^4.0.0" } }, "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ=="],
"sdk-test/next/postcss/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="],
"shadcn/cosmiconfig/js-yaml/argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],

View File

@@ -112,9 +112,7 @@
"setupci": "node scripts/setup/setupci.js",
"replicate": "bun scripts/db/replicate.ts",
"db:pull": "bun scripts/db/pull.ts",
"db:push": " bun -F @autumn/shared db:push",
"db:generate": "bun -F @autumn/shared db:generate",
"db:migrate": " bun -F @autumn/shared db:migrate",
"db": "bun scripts/db/index.ts",
"test:integration": "cd server && bun test:integration",
"exp": "sh -c 'infisical run --recursive --env=staging -- bun run server/experiments/$0'",
"q": "lsof -ti:8080 -ti:3000 | xargs kill -9",

168
scripts/db/README.md Normal file
View File

@@ -0,0 +1,168 @@
# `bun db` — Drizzle Migration CLI
Unified entrypoint for everything migration-related. Subcommands route through `scripts/db/index.ts`, which auto-wraps DB-touching subcommands in `infisical run` so `DATABASE_URL` is injected from the right secret environment.
## Surface
```
bun db help # print usage
bun db generate # write a new migration .sql from schema changes
bun db migrate [--env=dev|staging|prod] # apply pending migrations to the target DB
bun db migrate:dry [--env=dev|staging|prod] # preview pending SQL + safety checks, don't apply
bun db mark-applied [--env=dev|staging|prod] # seed drizzle.__drizzle_migrations on an existing DB
bun db rebase # auto-resolve a local migration that collided with origin/dev
```
`migrate` and `migrate:dry` also run a safety check that **refuses to apply any pending migration containing `CREATE INDEX`, `DROP INDEX`, or `REINDEX` without `CONCURRENTLY`**. Those DDL statements take an ACCESS EXCLUSIVE lock and can block reads/writes on busy tables. To get through the check: either rewrite the SQL with `CONCURRENTLY` and apply manually + `mark-applied`, or add `.concurrently()` to the index in your schema and regenerate.
`--env` defaults to `dev`. `generate` and `rebase` never touch a DB and don't take `--env`.
---
## Day-to-day workflow
```bash
# 1. Make a schema change in shared/db/**.ts or shared/models/**.ts
# 2. Generate the migration
bun db generate
# 3. Review the new shared/drizzle/{N}_*.sql and updated meta/.
# Commit both with the code change in your PR.
# 4. Apply locally
bun db migrate
```
If your branch's migration collides with one that landed on `origin/dev` first (CI will tell you via duplicate-idx errors), pull and run:
```bash
git pull origin dev
bun db rebase # auto-renumbers your migration, regenerates against the new baseline
```
`bun db rebase` is idempotent and bails out cleanly if drizzle-kit would need interactive input (e.g. column rename detection) — see the "Edge cases" section below.
---
## Bootstrapping a new DB
`bun db migrate` won't run against a DB that has the tables but no `drizzle.__drizzle_migrations` tracking row — it'd try to `CREATE TABLE` on tables that already exist and fail. Seed the tracking table first:
```bash
bun db mark-applied # local dev DB
bun db mark-applied --env=staging # if/when staging is wired up — see below
bun db mark-applied --env=prod # if/when prod is wired up — see below
```
Idempotent. Re-running is safe.
---
## Status: staging & prod are NOT wired into the CLI yet
**Right now, staging and prod migrations are still applied manually (paste the `.sql` into TablePlus per env).** The `--env=staging|prod` plumbing exists in the code and works, but we haven't:
1. Run `bun db mark-applied --env=staging` / `--env=prod` to seed the tracking table on those DBs.
2. Switched the deploy pipeline to call `bun db migrate --env=…` automatically.
Until both happen, **do not run `bun db migrate --env=staging` or `--env=prod`** — without the mark-applied step, it will try to re-create existing tables and fail. (Failing safely, but failing.)
---
## Upgrade path: moving staging / prod into the CLI
When you're ready to flip from "TablePlus paste" to automated migrations on a shared env, the sequence is:
### One-time per env
1. **Confirm the env's secret name in infisical.** The CLI assumes:
- `dev` → infisical env `dev`, `ENV_FILE=.env`
- `staging` → infisical env `staging`, `ENV_FILE=.env.staging`
- `prod` → infisical env `prod`, `ENV_FILE=.env.prod`
This mapping lives in `scripts/db/helpers/env.ts`. If your infisical env names diverge, update there.
2. **Confirm `DATABASE_URL` in that infisical env points at the right DB.** A wrong URL here means migrations apply to the wrong DB. Run:
```bash
bun db mark-applied --env=<env>
```
The first log line prints the host parsed from `DATABASE_URL` (e.g. `host=staging-pg.foo.com:5432`) — verify it before letting the script run further. Hit Ctrl-C if it's wrong; mark-applied does its writes after the log line.
3. **Run mark-applied.** Same command as step 2. It seeds `drizzle.__drizzle_migrations` with rows for every migration currently in `shared/drizzle/meta/_journal.json`, so `db migrate` will skip them.
4. **Verify migrate is a clean no-op.** This proves the seeding worked:
```bash
bun db migrate --env=<env>
```
Expected: drizzle-kit reports "applied successfully" with no DDL run, and `drizzle.__drizzle_migrations` has the same row count as before.
### Ongoing — moving the apply step from TablePlus to the CLI / CI
Once the bootstrap above is done for an env, you have two ways to apply new migrations:
- **Manual command**, same shape as dev:
```bash
bun db migrate --env=staging
bun db migrate --env=prod
```
- **CI-based** — call the same command from a deploy step. Two things to wire up in the pipeline:
1. The CI runner needs `infisical` installed and authenticated with credentials that can read the relevant env. Existing workflows that use `infisical run --env=…` are the precedent; copy that auth setup.
2. Add a step like:
```yaml
- name: Apply DB migrations
run: bun db migrate --env=prod
```
after the build step and before traffic is shifted to the new revision. If the migration fails, the deploy should halt — `bun db migrate` exits non-zero on any DDL failure.
Recommendation: **start with staging-only CI migration first**, run it for a week or two, and only then enable prod. Prod migration failures during a deploy are high-blast-radius and you want to be confident the pipeline works before you bet a deploy on it.
---
## File layout
```
scripts/db/
├── README.md # this file
├── index.ts # subcommand dispatch
├── commands/
│ ├── help.ts
│ ├── generate.ts # passthrough to `bun -F @autumn/shared db:generate`
│ ├── migrate.ts # passthrough to `bun -F @autumn/shared db:migrate`
│ ├── markApplied.ts # seeds drizzle.__drizzle_migrations
│ └── rebase.ts # auto-resolves duplicate-idx conflicts
├── helpers/
│ ├── env.ts # --env parsing + infisical wrap + DATABASE_URL host extraction
│ ├── paths.ts # canonical paths to shared/drizzle/ and meta/
│ └── spawn.ts # thin child_process.spawn wrapper
└── pull.ts # unrelated — customer data pull (legacy)
```
`shared/package.json` still owns the implementation of `db:generate` and `db:migrate` (which are what the CLI shells out to under the hood). The unified `bun db` interface lives at the repo root.
---
## Edge cases
### `bun db rebase` bailed with "drizzle-kit needs interactive input"
This happens when the regeneration would prompt for a rename decision (column renamed, enum value reordered, etc.). The script restored your original files from a temp backup and exited non-zero. Recovery:
1. `bun db generate` directly in a terminal — answer the prompts.
2. Drizzle will produce a new migration. If it has a duplicate idx with `origin/dev`, manually rename the `.sql` file and update the `idx` + `tag` in `meta/_journal.json`.
3. Re-run `bun db rebase` to confirm the journal is now clean.
This is rare. ~95% of migrations (ADD COLUMN, DROP COLUMN, CREATE INDEX) auto-resolve cleanly.
### CI says "migration idx N already exists"
Someone else's PR with the same migration idx merged first. Run `bun db rebase` on your branch, push, re-run CI.
### "I deleted shared/drizzle/ by accident"
Pull from `origin/dev` to get it back. Your in-flight schema changes in TS are still intact; run `bun db generate` and it'll regenerate the migration.

View File

@@ -0,0 +1,11 @@
import { run } from "../helpers/spawn.ts";
import { REPO_ROOT } from "../helpers/paths.ts";
export async function cmdGenerate(): Promise<void> {
const { code } = await run(
"bun",
["-F", "@autumn/shared", "db:generate"],
{ cwd: REPO_ROOT },
);
process.exit(code);
}

View File

@@ -0,0 +1,19 @@
export function cmdHelp(): void {
const lines = [
"",
" bun db <subcommand> [--env=dev|staging|prod]",
"",
" Subcommands:",
" help print this message",
" generate write a new migration .sql file from schema changes",
" migrate apply pending migrations to the target DB",
" migrate:dry preview pending migrations (SQL + safety checks), don't apply",
" mark-applied bootstrap drizzle.__drizzle_migrations on an existing DB",
" rebase resolve a local migration that collided with one on origin/dev",
"",
" --env defaults to dev. migrate and mark-applied auto-wrap themselves",
" in `infisical run` so DATABASE_URL is injected automatically.",
"",
];
process.stdout.write(`${lines.join("\n")}\n`);
}

View File

@@ -0,0 +1,64 @@
import { readMigrationFiles } from "drizzle-orm/migrator";
import pg from "pg";
import { MIGRATIONS_DIR } from "../helpers/paths.ts";
import { type Env, targetHost, wrapInInfisical } from "../helpers/env.ts";
export async function cmdMarkApplied(env: Env): Promise<void> {
await wrapInInfisical(env);
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) {
console.error("DATABASE_URL not set after infisical wrap");
process.exit(1);
}
console.log(
`[db:mark-applied] env=${env} host=${targetHost(databaseUrl)} migrations=${MIGRATIONS_DIR}`,
);
const migrations = readMigrationFiles({ migrationsFolder: MIGRATIONS_DIR });
if (migrations.length === 0) {
console.log("no migrations on disk — nothing to mark");
return;
}
const client = new pg.Client({ connectionString: databaseUrl });
await client.connect();
try {
await client.query(`CREATE SCHEMA IF NOT EXISTS "drizzle"`);
await client.query(`
CREATE TABLE IF NOT EXISTS "drizzle"."__drizzle_migrations" (
id SERIAL PRIMARY KEY,
hash text NOT NULL,
created_at bigint
)
`);
let marked = 0;
let already = 0;
for (const migration of migrations) {
const existing = await client.query<{ id: number }>(
`SELECT id FROM "drizzle"."__drizzle_migrations" WHERE hash = $1 LIMIT 1`,
[migration.hash],
);
if (existing.rowCount && existing.rowCount > 0) {
console.log(` already applied hash=${migration.hash.slice(0, 12)}`);
already++;
continue;
}
await client.query(
`INSERT INTO "drizzle"."__drizzle_migrations" ("hash", "created_at") VALUES ($1, $2)`,
[migration.hash, migration.folderMillis],
);
console.log(` marked hash=${migration.hash.slice(0, 12)}… when=${migration.folderMillis}`);
marked++;
}
console.log(`done — marked=${marked} already=${already} total=${migrations.length}`);
} finally {
await client.end();
}
}

View File

@@ -0,0 +1,132 @@
import pg from "pg";
import { run } from "../helpers/spawn.ts";
import { REPO_ROOT } from "../helpers/paths.ts";
import { type Env, targetHost, wrapInInfisical } from "../helpers/env.ts";
import {
getPendingMigrations,
type PendingMigration,
} from "../helpers/pendingMigrations.ts";
import {
type BlockingStatement,
findBlockingIndexStatements,
} from "../helpers/safetyCheck.ts";
export async function cmdMigrate(
env: Env,
opts: { dryRun: boolean; bootstrap: boolean },
): Promise<void> {
await wrapInInfisical(env);
const databaseUrl = process.env.DATABASE_URL;
if (!databaseUrl) {
console.error("DATABASE_URL not set after infisical wrap");
process.exit(1);
}
const tags = [
opts.dryRun ? "DRY" : null,
opts.bootstrap ? "BOOTSTRAP" : null,
].filter(Boolean);
const tagStr = tags.length > 0 ? ` (${tags.join(" ")})` : "";
console.log(`[db:migrate${tagStr}] env=${env} host=${targetHost(databaseUrl)}`);
if (opts.bootstrap) {
console.log(
"bootstrap mode: skipping index-DDL safety check (use only for fresh DBs)",
);
}
const client = new pg.Client({ connectionString: databaseUrl });
await client.connect();
let pending: PendingMigration[];
try {
pending = await getPendingMigrations(client);
} finally {
await client.end();
}
if (pending.length === 0) {
console.log("no pending migrations");
return;
}
console.log(`${pending.length} pending migration(s):`);
for (const migration of pending) {
console.log(` ${migration.tag}`);
}
const blockers = opts.bootstrap ? [] : collectBlockers(pending);
if (opts.dryRun) {
console.log("");
console.log("--- SQL preview ---");
for (const migration of pending) {
console.log("");
console.log(`-- ${migration.tag}.sql --`);
console.log(migration.sql);
}
console.log("");
if (blockers.length > 0) {
printBlockerError(blockers);
console.log("dry-run: would refuse to apply.");
process.exit(1);
}
console.log(
`dry-run: ok — \`bun db migrate --env=${env}\` would apply these.`,
);
return;
}
if (blockers.length > 0) {
printBlockerError(blockers);
process.exit(1);
}
const { code } = await run("bun", ["-F", "@autumn/shared", "db:migrate"], {
cwd: REPO_ROOT,
});
process.exit(code);
}
type FlaggedBlocker = {
migration: PendingMigration;
blocker: BlockingStatement;
};
function collectBlockers(pending: PendingMigration[]): FlaggedBlocker[] {
const all: FlaggedBlocker[] = [];
for (const migration of pending) {
for (const blocker of findBlockingIndexStatements(migration.sql)) {
all.push({ migration, blocker });
}
}
return all;
}
function printBlockerError(blockers: FlaggedBlocker[]): void {
console.error("");
console.error(
`refusing to apply ${blockers.length} index DDL statement(s) without CONCURRENTLY:`,
);
for (const { migration, blocker } of blockers) {
console.error("");
console.error(` in ${migration.tag}.sql [${blocker.kind}]`);
const indented = blocker.statement
.split("\n")
.map((line) => ` ${line}`)
.join("\n");
console.error(indented);
}
console.error("");
console.error(
"Index DDL without CONCURRENTLY takes an ACCESS EXCLUSIVE lock that blocks reads/writes on the table.",
);
console.error("");
console.error("Options:");
console.error(
" 1. Apply this migration manually (psql / TablePlus) with CONCURRENTLY, then `bun db mark-applied --env=<env>` to record it.",
);
console.error(
" 2. If the table is fresh or empty, add `.concurrently()` to the index in shared/db schema, `bun db generate`, and re-try.",
);
}

View File

@@ -0,0 +1,180 @@
import { mkdtempSync, copyFileSync, existsSync, rmSync, writeFileSync, readFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { run } from "../helpers/spawn.ts";
import { REPO_ROOT, MIGRATIONS_DIR, META_DIR, JOURNAL_PATH } from "../helpers/paths.ts";
type JournalEntry = {
idx: number;
version: string;
when: number;
tag: string;
breakpoints: boolean;
};
type Journal = {
version: string;
dialect: string;
entries: JournalEntry[];
};
const REMOTE_REF = "origin/dev";
export async function cmdRebase(): Promise<void> {
// 1. Bail on unresolved schema conflicts.
const unmerged = await run("git", ["diff", "--name-only", "--diff-filter=U"], {
cwd: REPO_ROOT,
stdio: "pipe",
});
if (unmerged.code !== 0) {
console.error(`git diff failed:\n${unmerged.stderr}`);
process.exit(1);
}
const schemaConflicts = unmerged.stdout
.split("\n")
.filter((line) => line.startsWith("shared/db/"));
if (schemaConflicts.length > 0) {
console.error(
`unresolved schema conflicts — resolve these first:\n${schemaConflicts.map((f) => ` ${f}`).join("\n")}`,
);
process.exit(1);
}
// 2. Read local + remote journals.
const localJournal = readLocalJournal();
const remoteJournal = await readRemoteJournal();
if (!remoteJournal) {
console.log(`nothing to rebase — could not read ${REMOTE_REF}:shared/drizzle/meta/_journal.json (no remote yet?)`);
return;
}
// 3. Find orphaned local entries — same idx as remote, but different tag/when.
const remoteByIdx = new Map<number, JournalEntry>();
for (const entry of remoteJournal.entries) remoteByIdx.set(entry.idx, entry);
const orphans: JournalEntry[] = [];
for (const entry of localJournal.entries) {
const remoteEntry = remoteByIdx.get(entry.idx);
if (!remoteEntry) continue;
if (remoteEntry.tag !== entry.tag || remoteEntry.when !== entry.when) {
orphans.push(entry);
}
}
if (orphans.length === 0) {
console.log("no migration conflicts — nothing to rebase");
return;
}
console.log(`found ${orphans.length} orphaned local migration(s):`);
for (const orphan of orphans) {
console.log(` idx=${orphan.idx} tag=${orphan.tag}`);
}
// 4. Back up the orphaned files.
const backupDir = mkdtempSync(join(tmpdir(), "autumn-db-rebase-"));
console.log(`backup dir: ${backupDir}`);
const backups: Array<{ src: string; backup: string }> = [];
for (const orphan of orphans) {
const sql = join(MIGRATIONS_DIR, `${orphan.tag}.sql`);
const snapshot = join(META_DIR, `${formatIdx(orphan.idx)}_snapshot.json`);
for (const src of [sql, snapshot]) {
if (!existsSync(src)) continue;
const backup = join(backupDir, src.replace(/[/\\]/g, "_"));
copyFileSync(src, backup);
backups.push({ src, backup });
}
}
// 5. Delete orphans, then restore canonical journal + snapshots from origin/dev.
for (const orphan of orphans) {
const sql = join(MIGRATIONS_DIR, `${orphan.tag}.sql`);
const snapshot = join(META_DIR, `${formatIdx(orphan.idx)}_snapshot.json`);
if (existsSync(sql)) rmSync(sql);
if (existsSync(snapshot)) rmSync(snapshot);
}
const remoteJournalRaw = await gitShow(`${REMOTE_REF}:shared/drizzle/meta/_journal.json`);
if (remoteJournalRaw === null) {
restoreBackups(backups);
console.error("failed to fetch remote journal — aborted, backups restored");
process.exit(1);
}
writeFileSync(JOURNAL_PATH, remoteJournalRaw);
for (const orphan of orphans) {
const snapshotPath = `${REMOTE_REF}:shared/drizzle/meta/${formatIdx(orphan.idx)}_snapshot.json`;
const remoteEntry = remoteByIdx.get(orphan.idx);
if (!remoteEntry) continue;
const snapshotRaw = await gitShow(snapshotPath);
if (snapshotRaw === null) {
restoreBackups(backups);
console.error(`failed to fetch ${snapshotPath} — aborted, backups restored`);
process.exit(1);
}
writeFileSync(join(META_DIR, `${formatIdx(orphan.idx)}_snapshot.json`), snapshotRaw);
}
// 6. Regenerate non-interactively. drizzle-kit reads stdin for rename prompts;
// closing stdin makes it fail fast rather than hang.
const generate = await run("bun", ["-F", "@autumn/shared", "db:generate"], {
cwd: REPO_ROOT,
stdio: "pipe",
});
if (generate.code !== 0) {
restoreBackups(backups);
console.error("`db:generate` failed during rebase — backups restored.");
console.error("");
console.error("Likely cause: drizzle-kit needs interactive input (e.g. column rename detection).");
console.error("Resolve manually:");
console.error(" 1. Run `bun db:generate` directly in a terminal and answer prompts.");
console.error(" 2. If a duplicate-idx file is produced, delete it and re-run.");
console.error("");
console.error("drizzle-kit stderr:");
console.error(generate.stderr);
process.exit(1);
}
console.log(generate.stdout);
// 7. Stage everything for the engineer.
await run("git", ["add", "shared/drizzle/"], { cwd: REPO_ROOT });
console.log("");
console.log("rebase complete — review the regenerated migration and commit.");
console.log(`backup retained at: ${backupDir}`);
}
function readLocalJournal(): Journal {
const raw = readFileSync(JOURNAL_PATH, "utf8");
return JSON.parse(raw) as Journal;
}
async function readRemoteJournal(): Promise<Journal | null> {
const raw = await gitShow(`${REMOTE_REF}:shared/drizzle/meta/_journal.json`);
if (raw === null) return null;
return JSON.parse(raw) as Journal;
}
async function gitShow(ref: string): Promise<string | null> {
const result = await run("git", ["show", ref], { cwd: REPO_ROOT, stdio: "pipe" });
if (result.code !== 0) return null;
return result.stdout;
}
function restoreBackups(backups: Array<{ src: string; backup: string }>): void {
for (const { src, backup } of backups) {
try {
copyFileSync(backup, src);
} catch {
// best-effort restore
}
}
}
function formatIdx(idx: number): string {
return idx.toString().padStart(4, "0");
}

84
scripts/db/helpers/env.ts Normal file
View File

@@ -0,0 +1,84 @@
import { spawn } from "node:child_process";
export type Env = "dev" | "staging" | "prod";
const VALID_ENVS: readonly Env[] = ["dev", "staging", "prod"] as const;
export function parseEnv(argv: readonly string[]): Env {
for (const arg of argv) {
if (arg.startsWith("--env=")) {
const value = arg.slice("--env=".length);
if (!VALID_ENVS.includes(value as Env)) {
console.error(
`invalid --env=${value}, must be one of: ${VALID_ENVS.join(", ")}`,
);
process.exit(2);
}
return value as Env;
}
}
return "dev";
}
const ENV_FILE: Record<Env, string> = {
dev: ".env",
staging: ".env.staging",
prod: ".env.prod",
};
/**
* If not already inside an infisical-wrapped context, re-exec this process
* wrapped in `infisical run --env=<env> --recursive --` so DATABASE_URL is
* injected from the right secret environment. Returns true if we re-exec'd
* (caller should exit); false if already wrapped (caller should proceed).
*/
export async function wrapInInfisical(env: Env): Promise<boolean> {
if (process.env.AUTUMN_DB_WRAPPED === "1") return false;
if (process.env.AUTUMN_DB_DIRECT === "1") {
if (!process.env.DATABASE_URL) {
console.error(
"AUTUMN_DB_DIRECT=1 but DATABASE_URL not set — caller must inject the URL when bypassing infisical",
);
process.exit(1);
}
return false;
}
const args = [
"run",
`--env=${env}`,
"--recursive",
"--",
"bun",
...process.argv.slice(1),
];
const child = spawn("infisical", args, {
stdio: "inherit",
env: {
...process.env,
AUTUMN_DB_WRAPPED: "1",
ENV_FILE: ENV_FILE[env],
},
});
const code: number = await new Promise((resolve) => {
child.on("close", (c) => resolve(c ?? 1));
child.on("error", (err) => {
console.error(`failed to spawn infisical: ${err.message}`);
resolve(1);
});
});
process.exit(code);
}
export function targetHost(databaseUrl: string | undefined): string {
if (!databaseUrl) return "<DATABASE_URL not set>";
try {
const url = new URL(databaseUrl);
return url.host;
} catch {
return "<unparseable DATABASE_URL>";
}
}

View File

@@ -0,0 +1,9 @@
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
const here = dirname(fileURLToPath(import.meta.url));
export const REPO_ROOT = resolve(here, "../../..");
export const MIGRATIONS_DIR = resolve(REPO_ROOT, "shared/drizzle");
export const META_DIR = resolve(MIGRATIONS_DIR, "meta");
export const JOURNAL_PATH = resolve(META_DIR, "_journal.json");

View File

@@ -0,0 +1,55 @@
import { readFileSync } from "node:fs";
import { join } from "node:path";
import type pg from "pg";
import { JOURNAL_PATH, MIGRATIONS_DIR } from "./paths.ts";
export type JournalEntry = {
idx: number;
version: string;
when: number;
tag: string;
breakpoints: boolean;
};
export type PendingMigration = JournalEntry & {
sql: string;
sqlPath: string;
};
/**
* Returns migrations on disk that haven't been recorded in drizzle.__drizzle_migrations yet.
* Mirrors drizzle-kit's filter: anything in _journal.json whose `when` is greater than the
* largest `created_at` already in the tracking table.
*/
export async function getPendingMigrations(
client: pg.Client,
): Promise<PendingMigration[]> {
await client.query(`CREATE SCHEMA IF NOT EXISTS "drizzle"`);
await client.query(`
CREATE TABLE IF NOT EXISTS "drizzle"."__drizzle_migrations" (
id SERIAL PRIMARY KEY,
hash text NOT NULL,
created_at bigint
)
`);
const result = await client.query<{ created_at: string | null }>(
`SELECT created_at FROM "drizzle"."__drizzle_migrations" ORDER BY created_at DESC LIMIT 1`,
);
const lastApplied =
result.rowCount && result.rowCount > 0 && result.rows[0].created_at !== null
? Number(result.rows[0].created_at)
: null;
const journal = JSON.parse(readFileSync(JOURNAL_PATH, "utf8")) as {
entries: JournalEntry[];
};
return journal.entries
.filter((entry) => lastApplied === null || entry.when > lastApplied)
.map((entry) => {
const sqlPath = join(MIGRATIONS_DIR, `${entry.tag}.sql`);
const sql = readFileSync(sqlPath, "utf8");
return { ...entry, sql, sqlPath };
});
}

View File

@@ -0,0 +1,26 @@
const INDEX_DDL = /^\s*(CREATE\s+(UNIQUE\s+)?INDEX|DROP\s+INDEX|REINDEX)\b/i;
const HAS_CONCURRENTLY = /\bCONCURRENTLY\b/i;
export type BlockingStatement = {
kind: "CREATE INDEX" | "DROP INDEX" | "REINDEX";
statement: string;
};
export function findBlockingIndexStatements(sql: string): BlockingStatement[] {
const blockers: BlockingStatement[] = [];
const statements = sql.split("--> statement-breakpoint");
for (const raw of statements) {
const trimmed = raw.trim();
if (!trimmed) continue;
if (!INDEX_DDL.test(trimmed)) continue;
if (HAS_CONCURRENTLY.test(trimmed)) continue;
const upper = trimmed.toUpperCase();
const kind: BlockingStatement["kind"] = upper.startsWith("CREATE")
? "CREATE INDEX"
: upper.startsWith("DROP")
? "DROP INDEX"
: "REINDEX";
blockers.push({ kind, statement: trimmed });
}
return blockers;
}

View File

@@ -0,0 +1,46 @@
import { spawn } from "node:child_process";
export type RunOptions = {
cwd?: string;
env?: NodeJS.ProcessEnv;
stdio?: "inherit" | "pipe";
};
export type RunResult = {
code: number;
stdout: string;
stderr: string;
};
export function run(
cmd: string,
args: readonly string[],
options: RunOptions = {},
): Promise<RunResult> {
const { cwd, env, stdio = "inherit" } = options;
return new Promise((resolve) => {
const child = spawn(cmd, [...args], {
cwd,
env: env ?? process.env,
stdio: stdio === "inherit" ? "inherit" : ["ignore", "pipe", "pipe"],
});
let stdout = "";
let stderr = "";
if (stdio === "pipe") {
child.stdout?.on("data", (chunk: Buffer) => {
stdout += chunk.toString();
});
child.stderr?.on("data", (chunk: Buffer) => {
stderr += chunk.toString();
});
}
child.on("close", (code) => {
resolve({ code: code ?? 1, stdout, stderr });
});
child.on("error", (err) => {
resolve({ code: 1, stdout, stderr: stderr + err.message });
});
});
}

46
scripts/db/index.ts Normal file
View File

@@ -0,0 +1,46 @@
import { cmdHelp } from "./commands/help.ts";
import { cmdGenerate } from "./commands/generate.ts";
import { cmdMigrate } from "./commands/migrate.ts";
import { cmdMarkApplied } from "./commands/markApplied.ts";
import { cmdRebase } from "./commands/rebase.ts";
import { parseEnv } from "./helpers/env.ts";
async function main(): Promise<void> {
const sub = process.argv[2];
const rest = process.argv.slice(3);
if (!sub || sub === "help" || sub === "--help" || sub === "-h") {
cmdHelp();
return;
}
switch (sub) {
case "generate":
await cmdGenerate();
return;
case "migrate":
await cmdMigrate(parseEnv(rest), {
dryRun: false,
bootstrap: rest.includes("--bootstrap"),
});
return;
case "migrate:dry":
await cmdMigrate(parseEnv(rest), {
dryRun: true,
bootstrap: rest.includes("--bootstrap"),
});
return;
case "mark-applied":
await cmdMarkApplied(parseEnv(rest));
return;
case "rebase":
await cmdRebase();
return;
default:
console.error(`unknown subcommand: ${sub}`);
cmdHelp();
process.exit(2);
}
}
await main();

View File

@@ -1,71 +1,33 @@
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs";
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { sh, fatal, log } from "./shell.ts";
import { SHARED_DIR, PROJECT_ROOT } from "../constants.ts";
import { sh, shInherit, fatal, log } from "./shell.ts";
import { PROJECT_ROOT } from "../constants.ts";
export function listSqlFiles(dir: string): string[] {
if (!existsSync(dir)) return [];
return readdirSync(dir).filter((f) => f.endsWith(".sql"));
}
export function writeTempDrizzleConfig(outDir: string): string {
const tmp = join(SHARED_DIR, `.dw-${process.pid}.config.ts`);
const content = `import { defineConfig } from "drizzle-kit";\nexport default defineConfig({\n\tdialect: "postgresql",\n\tout: ${JSON.stringify(outDir)},\n\tschema: "./db/schema.ts",\n\tdbCredentials: { url: process.env.DATABASE_URL! },\n});\n`;
writeFileSync(tmp, content);
return tmp;
}
export function generateAndApplyMigration(
/**
* Brings a freshly-provisioned Neon branch up to the canonical schema.
*
* Strategy: invoke the shared `bun db migrate --bootstrap` CLI with the new
* branch's DATABASE_URL injected directly (bypassing infisical). `--bootstrap`
* skips the index-DDL safety check, which is safe here because the DB is empty
* and has no concurrent traffic.
*/
export function applyCommittedMigrations(
branchName: string,
databaseUrl: string,
): void {
// Use the worktree's own shared/drizzle/ as the migration dir (matches
// drizzle.config.ts out: "./drizzle"). Empty it first so the baseline
// generated for this fresh Neon branch isn't a diff against the canonical
// repo's migration history — subsequent `bun db:generate` runs then emit
// clean incrementals against this worktree's actual DB state.
const outDir = join(SHARED_DIR, "drizzle");
if (existsSync(outDir)) rmSync(outDir, { recursive: true, force: true });
mkdirSync(outDir, { recursive: true });
log(`applying committed migrations to ${branchName}`);
const drizzleConfigPath = writeTempDrizzleConfig(outDir);
try {
log(`generating initial migration for ${branchName}`);
const gen = sh(
"bunx",
["drizzle-kit", "generate", "--config", drizzleConfigPath],
{
cwd: SHARED_DIR,
env: {
...(process.env as Record<string, string>),
NODE_OPTIONS: "--import tsx",
},
},
);
if (gen.code !== 0) {
fatal(`drizzle-kit generate failed:\n${gen.stdout}\n${gen.stderr}`);
}
const code = shInherit("bun", ["db", "migrate", "--bootstrap"], {
cwd: PROJECT_ROOT,
env: {
...(process.env as Record<string, string>),
DATABASE_URL: databaseUrl,
AUTUMN_DB_DIRECT: "1",
},
});
const sqlFiles = listSqlFiles(outDir);
if (sqlFiles.length === 0) {
fatal(`no .sql files generated in ${outDir}`);
}
log(`applying ${sqlFiles.length} migration file(s) to ${branchName}`);
for (const f of sqlFiles) {
const p = join(outDir, f);
const sqlBody = readFileSync(p, "utf-8");
const mig = sh("psql", [databaseUrl, "-v", "ON_ERROR_STOP=1"], {
stdin: sqlBody,
});
if (mig.code !== 0) {
fatal(
`applying migration ${f} failed:\n${mig.stdout}\n${mig.stderr}`,
);
}
}
} finally {
if (existsSync(drizzleConfigPath)) rmSync(drizzleConfigPath);
if (code !== 0) {
fatal(`bun db migrate --bootstrap failed for ${branchName} (exit ${code})`);
}
}

View File

@@ -5,7 +5,7 @@ import {
connectionString,
findBranchByName,
} from "./neon.ts";
import { generateAndApplyMigration, loadDbFunctions } from "./migration.ts";
import { applyCommittedMigrations, loadDbFunctions } from "./migration.ts";
import { loadRegistry, saveRegistry } from "./registry.ts";
import { PROJECT_ROOT, NEON_TEMPLATE_BRANCH } from "../constants.ts";
import type { RegistryEntry } from "../types.ts";
@@ -36,7 +36,7 @@ export async function setupAgentWorktree(
const branch = createBranch(branchName, NEON_TEMPLATE_BRANCH);
// Use direct (non-pooled) URL for DDL; pooler can interfere with some DDL paths.
const directUrl = connectionString(branchName, { pooled: false });
generateAndApplyMigration(branchName, directUrl);
applyCommittedMigrations(branchName, directUrl);
loadDbFunctions(branchName, directUrl);
// Pooled URL for runtime.
const pooledUrl = connectionString(branchName, { pooled: true });

View File

@@ -19,10 +19,12 @@
"ioredis": "^5.10.0",
"inquirer": "^12.6.3",
"p-limit": "^7.2.0",
"pg": "8.20.0",
"react": "^18.3.1"
},
"devDependencies": {
"@types/bun": "^1.3.11",
"@types/pg": "8.11.10",
"@types/react": "^18.3.1",
"tsx": "^4.19.2",
"typescript": "^5.7.3"

View File

@@ -0,0 +1,882 @@
CREATE TABLE "account" (
"id" text PRIMARY KEY NOT NULL,
"account_id" text NOT NULL,
"provider_id" text NOT NULL,
"user_id" text NOT NULL,
"access_token" text,
"refresh_token" text,
"id_token" text,
"access_token_expires_at" timestamp,
"refresh_token_expires_at" timestamp,
"scope" text,
"password" text,
"created_at" timestamp with time zone NOT NULL,
"updated_at" timestamp with time zone NOT NULL
);
--> statement-breakpoint
ALTER TABLE "account" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "actions" (
"id" text COLLATE "C" PRIMARY KEY NOT NULL,
"request_id" text NOT NULL,
"org_id" text NOT NULL,
"org_slug" text NOT NULL,
"env" text NOT NULL,
"customer_id" text,
"internal_customer_id" text,
"entity_id" text,
"internal_entity_id" text,
"type" text NOT NULL,
"auth_type" text NOT NULL,
"method" text NOT NULL,
"path" text NOT NULL,
"timestamp" timestamp with time zone DEFAULT now() NOT NULL,
"properties" jsonb
);
--> statement-breakpoint
ALTER TABLE "actions" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "api_keys" (
"id" text COLLATE "C" PRIMARY KEY NOT NULL,
"created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL,
"name" text,
"prefix" text,
"org_id" text,
"user_id" text,
"env" text,
"hashed_key" text,
"meta" jsonb,
"scopes" jsonb,
CONSTRAINT "api_keys_hashed_key_key" UNIQUE("hashed_key")
);
--> statement-breakpoint
CREATE TABLE "auto_topup_limit_states" (
"id" text PRIMARY KEY NOT NULL,
"org_id" text NOT NULL,
"env" text NOT NULL,
"internal_customer_id" text NOT NULL,
"customer_id" text NOT NULL,
"feature_id" text NOT NULL,
"purchase_window_ends_at" numeric NOT NULL,
"purchase_count" numeric DEFAULT 0 NOT NULL,
"attempt_window_ends_at" numeric NOT NULL,
"attempt_count" numeric DEFAULT 0 NOT NULL,
"failed_attempt_window_ends_at" numeric NOT NULL,
"failed_attempt_count" numeric DEFAULT 0 NOT NULL,
"last_attempt_at" numeric,
"last_failed_attempt_at" numeric,
"created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL,
"updated_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL
);
--> statement-breakpoint
CREATE TABLE "chat_results" (
"id" text COLLATE "C" PRIMARY KEY NOT NULL,
"created_at" numeric,
"data" jsonb NOT NULL
);
--> statement-breakpoint
ALTER TABLE "chat_results" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "checkouts" (
"id" text PRIMARY KEY NOT NULL,
"org_id" text NOT NULL,
"env" text NOT NULL,
"internal_customer_id" text NOT NULL,
"customer_id" text NOT NULL,
"action" text NOT NULL,
"params" jsonb NOT NULL,
"params_version" integer DEFAULT 0 NOT NULL,
"status" text DEFAULT 'pending' NOT NULL,
"response" jsonb,
"stripe_invoice_id" text,
"created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL,
"expires_at" numeric NOT NULL,
"completed_at" numeric
);
--> statement-breakpoint
CREATE TABLE "customer_entitlements" (
"id" text COLLATE "C" PRIMARY KEY NOT NULL,
"customer_product_id" text,
"entitlement_id" text NOT NULL,
"internal_customer_id" text COLLATE "C" NOT NULL,
"internal_entity_id" text,
"internal_feature_id" text NOT NULL,
"unlimited" boolean DEFAULT false,
"balance" numeric DEFAULT 0 NOT NULL,
"created_at" numeric NOT NULL,
"next_reset_at" numeric,
"usage_allowed" boolean DEFAULT false,
"adjustment" numeric,
"additional_balance" numeric DEFAULT 0 NOT NULL,
"entities" jsonb,
"expires_at" numeric,
"cache_version" integer DEFAULT 0,
"customer_id" text,
"feature_id" text,
"external_id" text
);
--> statement-breakpoint
CREATE TABLE "customer_prices" (
"id" text PRIMARY KEY NOT NULL,
"created_at" numeric NOT NULL,
"price_id" text,
"options" jsonb,
"internal_customer_id" text,
"customer_product_id" text
);
--> statement-breakpoint
CREATE TABLE "customer_products" (
"id" text COLLATE "C" PRIMARY KEY NOT NULL,
"internal_customer_id" text NOT NULL,
"internal_product_id" text NOT NULL,
"internal_entity_id" text,
"created_at" numeric,
"updated_at" numeric,
"status" text,
"processor" jsonb,
"canceled" boolean DEFAULT false,
"canceled_at" numeric,
"ended_at" numeric,
"starts_at" numeric,
"access_starts_at" numeric,
"options" jsonb[],
"product_id" text,
"free_trial_id" text,
"trial_ends_at" numeric,
"billing_cycle_anchor_resets_at" numeric,
"collection_method" text DEFAULT 'charge_automatically',
"subscription_ids" text[],
"scheduled_ids" text[],
"quantity" numeric DEFAULT 1,
"is_custom" boolean DEFAULT false NOT NULL,
"customer_id" text,
"entity_id" text,
"billing_version" text,
"api_version" numeric,
"api_semver" text,
"external_id" text,
"stripe_checkout_session_id" text,
"previous_customer_product_id" text,
"on_trial_end" text
);
--> statement-breakpoint
CREATE TABLE "customers" (
"internal_id" text COLLATE "C" PRIMARY KEY NOT NULL,
"org_id" text NOT NULL,
"created_at" numeric NOT NULL,
"name" text,
"id" text,
"email" text,
"fingerprint" text DEFAULT null,
"metadata" jsonb,
"env" text NOT NULL,
"processor" jsonb,
"processors" jsonb DEFAULT '{}'::jsonb,
"send_email_receipts" boolean DEFAULT false,
"auto_topups" jsonb,
"spend_limits" jsonb,
"usage_alerts" jsonb,
"overage_allowed" jsonb,
"config" jsonb DEFAULT '{}'::jsonb,
CONSTRAINT "cus_id_constraint" UNIQUE("org_id","id","env")
);
--> statement-breakpoint
ALTER TABLE "customers" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "entities" (
"id" text,
"org_id" text,
"created_at" numeric NOT NULL,
"internal_id" text PRIMARY KEY NOT NULL,
"internal_customer_id" text NOT NULL,
"env" text,
"name" text,
"deleted" boolean DEFAULT false NOT NULL,
"internal_feature_id" text,
"spend_limits" jsonb,
"usage_alerts" jsonb,
"overage_allowed" jsonb,
"feature_id" text,
CONSTRAINT "entity_id_constraint" UNIQUE("org_id","env","internal_customer_id","id")
);
--> statement-breakpoint
CREATE TABLE "entitlements" (
"id" text PRIMARY KEY NOT NULL,
"created_at" numeric NOT NULL,
"internal_feature_id" text NOT NULL,
"internal_product_id" text,
"internal_reward_id" text,
"is_custom" boolean DEFAULT false,
"allowance_type" text,
"allowance" numeric,
"interval" text,
"interval_count" numeric DEFAULT 1,
"carry_from_previous" boolean DEFAULT false,
"entity_feature_id" text DEFAULT null,
"org_id" text,
"feature_id" text,
"usage_limit" numeric,
"expiry_duration" text,
"expiry_length" numeric,
"rollover" jsonb,
CONSTRAINT "entitlements_id_key" UNIQUE("id")
);
--> statement-breakpoint
CREATE TABLE "events" (
"id" text PRIMARY KEY NOT NULL,
"org_id" text NOT NULL,
"org_slug" text NOT NULL,
"internal_customer_id" text,
"env" text NOT NULL,
"created_at" bigint,
"timestamp" timestamp with time zone,
"event_name" text NOT NULL,
"idempotency_key" text DEFAULT null,
"value" numeric,
"set_usage" boolean DEFAULT false,
"entity_id" text,
"internal_entity_id" text,
"internal_product_id" text,
"customer_id" text NOT NULL,
"properties" jsonb,
"deductions" jsonb,
CONSTRAINT "unique_event_constraint" UNIQUE("org_id","env","customer_id","event_name","idempotency_key")
);
--> statement-breakpoint
CREATE TABLE "features" (
"internal_id" text COLLATE "C" PRIMARY KEY NOT NULL,
"org_id" text NOT NULL,
"created_at" numeric,
"env" text,
"id" text NOT NULL,
"name" text,
"type" text NOT NULL,
"config" jsonb,
"display" jsonb DEFAULT null,
"archived" boolean DEFAULT false NOT NULL,
"event_names" text[] DEFAULT '{}',
CONSTRAINT "feature_id_constraint" UNIQUE("org_id","id","env")
);
--> statement-breakpoint
CREATE TABLE "free_trials" (
"id" text PRIMARY KEY NOT NULL,
"created_at" numeric NOT NULL,
"internal_product_id" text,
"duration" text DEFAULT 'day',
"length" numeric,
"unique_fingerprint" boolean,
"is_custom" boolean DEFAULT false,
"card_required" boolean DEFAULT false,
"on_end" text
);
--> statement-breakpoint
CREATE TABLE "invitation" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"email" text NOT NULL,
"role" text,
"status" text DEFAULT 'pending' NOT NULL,
"created_at" timestamp with time zone NOT NULL,
"expires_at" timestamp with time zone NOT NULL,
"inviter_id" text NOT NULL
);
--> statement-breakpoint
ALTER TABLE "invitation" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "invoice_line_items" (
"id" text COLLATE "C" PRIMARY KEY NOT NULL,
"created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL,
"invoice_id" text,
"stripe_id" text,
"stripe_invoice_id" text,
"stripe_invoice_item_id" text,
"stripe_subscription_item_id" text,
"stripe_product_id" text,
"stripe_price_id" text,
"stripe_discountable" boolean DEFAULT true NOT NULL,
"amount" numeric NOT NULL,
"amount_after_discounts" numeric NOT NULL,
"currency" text DEFAULT 'usd' NOT NULL,
"stripe_quantity" numeric,
"total_quantity" numeric,
"paid_quantity" numeric,
"description" text NOT NULL,
"description_source" text,
"direction" text NOT NULL,
"billing_timing" text,
"prorated" boolean DEFAULT false NOT NULL,
"price_id" text,
"customer_product_ids" jsonb DEFAULT '[]'::jsonb NOT NULL,
"customer_price_ids" jsonb DEFAULT '[]'::jsonb NOT NULL,
"customer_entitlement_ids" jsonb DEFAULT '[]'::jsonb NOT NULL,
"internal_product_id" text,
"product_id" text,
"internal_feature_id" text,
"feature_id" text,
"effective_period_start" numeric,
"effective_period_end" numeric,
"discounts" jsonb[] DEFAULT '{}',
CONSTRAINT "invoice_line_items_stripe_id_unique" UNIQUE("stripe_id")
);
--> statement-breakpoint
CREATE TABLE "invoices" (
"id" text COLLATE "C" PRIMARY KEY NOT NULL,
"created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL,
"product_ids" text[] DEFAULT '{}',
"internal_product_ids" text[] DEFAULT '{}',
"internal_customer_id" text NOT NULL,
"internal_entity_id" text,
"stripe_id" text NOT NULL,
"processor_type" text,
"status" text DEFAULT 'draft' NOT NULL,
"hosted_invoice_url" text,
"total" numeric DEFAULT 0 NOT NULL,
"amount_paid" numeric,
"refunded_amount" numeric DEFAULT 0 NOT NULL,
"currency" text DEFAULT 'usd' NOT NULL,
"discounts" jsonb[] DEFAULT '{}',
"items" jsonb[] DEFAULT '{}',
CONSTRAINT "invoices_stripe_id_key" UNIQUE("stripe_id")
);
--> statement-breakpoint
CREATE TABLE "jwks" (
"id" text PRIMARY KEY NOT NULL,
"public_key" text NOT NULL,
"private_key" text NOT NULL,
"created_at" timestamp with time zone NOT NULL,
"expires_at" timestamp with time zone
);
--> statement-breakpoint
ALTER TABLE "jwks" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "member" (
"id" text PRIMARY KEY NOT NULL,
"organization_id" text NOT NULL,
"user_id" text NOT NULL,
"role" text DEFAULT 'member' NOT NULL,
"created_at" timestamp with time zone NOT NULL
);
--> statement-breakpoint
ALTER TABLE "member" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "metadata" (
"id" text PRIMARY KEY NOT NULL,
"created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL,
"expires_at" numeric,
"data" jsonb,
"type" text,
"stripe_invoice_id" text,
"stripe_checkout_session_id" text
);
--> statement-breakpoint
CREATE TABLE "migration_errors" (
"internal_customer_id" text NOT NULL,
"migration_job_id" text NOT NULL,
"created_at" numeric,
"updated_at" numeric,
"data" jsonb,
"message" text,
"code" text,
CONSTRAINT "migration_errors_pkey" PRIMARY KEY("internal_customer_id","migration_job_id")
);
--> statement-breakpoint
CREATE TABLE "migration_item_runs" (
"migration_item_run_id" text PRIMARY KEY NOT NULL,
"migration_internal_id" text NOT NULL,
"migration_run_id" text,
"dry_run" boolean DEFAULT false NOT NULL,
"item_kind" text NOT NULL,
"item_id" text NOT NULL,
"status" text NOT NULL,
"timestamp" timestamp with time zone DEFAULT now() NOT NULL,
"created_at" numeric NOT NULL,
"updated_at" numeric
);
--> statement-breakpoint
CREATE TABLE "migration_jobs" (
"id" text PRIMARY KEY NOT NULL,
"org_id" text NOT NULL,
"env" text NOT NULL,
"created_at" numeric NOT NULL,
"updated_at" numeric,
"current_step" text,
"from_internal_product_id" text,
"to_internal_product_id" text,
"step_details" jsonb
);
--> statement-breakpoint
CREATE TABLE "migration_runs" (
"internal_id" text PRIMARY KEY NOT NULL,
"migration_internal_id" text NOT NULL,
"org_id" text NOT NULL,
"env" text NOT NULL,
"status" text NOT NULL,
"dry_run" boolean NOT NULL,
"lazy_run" boolean DEFAULT false NOT NULL,
"trigger_run_id" text,
"error_message" text,
"only_ids" text[],
"created_at" numeric NOT NULL,
"updated_at" numeric,
"started_at" numeric,
"finished_at" numeric
);
--> statement-breakpoint
CREATE TABLE "migrations" (
"internal_id" text PRIMARY KEY NOT NULL,
"id" text NOT NULL,
"org_id" text NOT NULL,
"env" text NOT NULL,
"filter" jsonb,
"operations" jsonb,
"prepared_state" jsonb,
"no_billing_changes" boolean,
"retry_failed" boolean DEFAULT false NOT NULL,
"created_at" numeric NOT NULL,
"updated_at" numeric
);
--> statement-breakpoint
CREATE TABLE "oauth_access_token" (
"id" text PRIMARY KEY NOT NULL,
"token" text,
"client_id" text NOT NULL,
"session_id" text,
"user_id" text,
"reference_id" text,
"refresh_id" text,
"expires_at" timestamp with time zone,
"created_at" timestamp with time zone,
"scopes" text[] NOT NULL,
CONSTRAINT "oauth_access_token_token_unique" UNIQUE("token")
);
--> statement-breakpoint
ALTER TABLE "oauth_access_token" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "oauth_client" (
"id" text PRIMARY KEY NOT NULL,
"client_id" text NOT NULL,
"client_secret" text,
"disabled" boolean DEFAULT false,
"skip_consent" boolean,
"enable_end_session" boolean,
"scopes" text[],
"user_id" text,
"created_at" timestamp with time zone,
"updated_at" timestamp with time zone,
"name" text,
"uri" text,
"icon" text,
"contacts" text[],
"tos" text,
"policy" text,
"software_id" text,
"software_version" text,
"software_statement" text,
"redirect_uris" text[] NOT NULL,
"post_logout_redirect_uris" text[],
"token_endpoint_auth_method" text,
"grant_types" text[],
"response_types" text[],
"public" boolean,
"type" text,
"reference_id" text,
"metadata" jsonb,
CONSTRAINT "oauth_client_client_id_unique" UNIQUE("client_id")
);
--> statement-breakpoint
ALTER TABLE "oauth_client" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "oauth_consent" (
"id" text PRIMARY KEY NOT NULL,
"client_id" text NOT NULL,
"user_id" text,
"reference_id" text,
"scopes" text[] NOT NULL,
"created_at" timestamp with time zone,
"updated_at" timestamp with time zone
);
--> statement-breakpoint
ALTER TABLE "oauth_consent" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "oauth_refresh_token" (
"id" text PRIMARY KEY NOT NULL,
"token" text NOT NULL,
"client_id" text NOT NULL,
"session_id" text,
"user_id" text NOT NULL,
"reference_id" text,
"expires_at" timestamp with time zone,
"created_at" timestamp with time zone,
"revoked" timestamp with time zone,
"scopes" text[] NOT NULL
);
--> statement-breakpoint
ALTER TABLE "oauth_refresh_token" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "organizations" (
"id" text PRIMARY KEY NOT NULL,
"slug" text NOT NULL,
"name" text NOT NULL,
"logo" text,
"createdAt" timestamp with time zone NOT NULL,
"metadata" text,
"default_currency" text DEFAULT 'usd',
"stripe_connected" boolean DEFAULT false,
"stripe_config" jsonb,
"test_stripe_connect" jsonb DEFAULT '{}'::jsonb,
"live_stripe_connect" jsonb DEFAULT '{}'::jsonb,
"processor_configs" jsonb,
"test_pkey" text,
"live_pkey" text,
"svix_config" jsonb DEFAULT '{}'::jsonb,
"created_at" numeric,
"config" jsonb DEFAULT '{}'::jsonb NOT NULL,
"created_by" text,
"onboarded" boolean DEFAULT false,
"deployed" boolean DEFAULT false,
"redis_config" jsonb,
CONSTRAINT "organizations_slug_unique" UNIQUE("slug"),
CONSTRAINT "organizations_test_pkey_key" UNIQUE("test_pkey"),
CONSTRAINT "organizations_live_pkey_key" UNIQUE("live_pkey")
);
--> statement-breakpoint
CREATE TABLE "prices" (
"id" text COLLATE "C" PRIMARY KEY NOT NULL,
"org_id" text NOT NULL,
"internal_product_id" text NOT NULL,
"config" jsonb,
"created_at" numeric NOT NULL,
"billing_type" text,
"tier_behavior" text DEFAULT null,
"is_custom" boolean DEFAULT false,
"entitlement_id" text DEFAULT null,
"proration_config" jsonb DEFAULT null,
CONSTRAINT "prices_id_key" UNIQUE("id")
);
--> statement-breakpoint
CREATE TABLE "products" (
"internal_id" text PRIMARY KEY NOT NULL,
"id" text NOT NULL,
"name" text,
"description" text,
"org_id" text NOT NULL,
"created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL,
"env" text NOT NULL,
"is_add_on" boolean DEFAULT false NOT NULL,
"is_default" boolean DEFAULT false NOT NULL,
"group" text DEFAULT '',
"version" numeric DEFAULT 1 NOT NULL,
"processor" jsonb DEFAULT null,
"base_variant_id" text,
"archived" boolean DEFAULT false NOT NULL,
"config" jsonb DEFAULT '{}'::jsonb NOT NULL,
CONSTRAINT "unique_product" UNIQUE("org_id","id","env","version")
);
--> statement-breakpoint
CREATE TABLE "referral_codes" (
"code" text NOT NULL,
"org_id" text NOT NULL,
"env" text NOT NULL,
"internal_customer_id" text COLLATE "C",
"internal_reward_program_id" text,
"id" text NOT NULL,
"created_at" numeric,
CONSTRAINT "referral_codes_pkey" PRIMARY KEY("code","org_id","env"),
CONSTRAINT "referral_codes_id_key" UNIQUE("id")
);
--> statement-breakpoint
CREATE TABLE "replaceables" (
"id" text PRIMARY KEY NOT NULL,
"cus_ent_id" text NOT NULL,
"created_at" bigint NOT NULL,
"from_entity_id" text,
"delete_next_cycle" boolean DEFAULT false NOT NULL
);
--> statement-breakpoint
ALTER TABLE "replaceables" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "revenuecat_mappings" (
"org_id" text NOT NULL,
"env" text NOT NULL,
"autumn_product_id" text NOT NULL,
"revenuecat_product_ids" text[] DEFAULT '{}' NOT NULL,
CONSTRAINT "revenuecat_mappings_pkey" PRIMARY KEY("org_id","env","autumn_product_id")
);
--> statement-breakpoint
CREATE TABLE "reward_programs" (
"internal_id" text PRIMARY KEY NOT NULL,
"id" text,
"created_at" numeric,
"internal_reward_id" text,
"max_redemptions" numeric,
"unlimited_redemptions" boolean DEFAULT false,
"org_id" text,
"env" text,
"when" text DEFAULT 'immediately',
"product_ids" text[] DEFAULT '{""}',
"exclude_trial" boolean DEFAULT false,
"received_by" text
);
--> statement-breakpoint
CREATE TABLE "reward_redemptions" (
"id" text PRIMARY KEY NOT NULL,
"created_at" numeric,
"updated_at" numeric,
"internal_customer_id" text COLLATE "C",
"triggered" boolean,
"internal_reward_program_id" text,
"applied" boolean DEFAULT false,
"redeemer_applied" boolean DEFAULT false,
"referral_code_id" text,
"reward_internal_id" text,
"promo_code" text
);
--> statement-breakpoint
CREATE TABLE "rewards" (
"internal_id" text PRIMARY KEY NOT NULL,
"id" text,
"org_id" text,
"env" text,
"created_at" numeric,
"name" text,
"discount_config" jsonb,
"free_product_config" jsonb,
"free_product_id" text,
"promo_codes" jsonb[],
"type" text
);
--> statement-breakpoint
CREATE TABLE "rollovers" (
"id" text PRIMARY KEY NOT NULL,
"cus_ent_id" text NOT NULL,
"balance" numeric NOT NULL,
"expires_at" numeric,
"usage" numeric DEFAULT 0 NOT NULL,
"entities" jsonb DEFAULT '{}'::jsonb NOT NULL
);
--> statement-breakpoint
ALTER TABLE "rollovers" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "phases" (
"id" text PRIMARY KEY NOT NULL,
"schedule_id" text NOT NULL,
"starts_at" numeric NOT NULL,
"customer_product_ids" text[] DEFAULT '{}' NOT NULL,
"created_at" numeric NOT NULL,
CONSTRAINT "phases_schedule_id_starts_at_key" UNIQUE("schedule_id","starts_at")
);
--> statement-breakpoint
CREATE TABLE "schedules" (
"id" text PRIMARY KEY NOT NULL,
"org_id" text NOT NULL,
"env" text NOT NULL,
"internal_customer_id" text NOT NULL,
"customer_id" text NOT NULL,
"internal_entity_id" text,
"entity_id" text,
"created_at" numeric NOT NULL
);
--> statement-breakpoint
CREATE TABLE "session" (
"id" text PRIMARY KEY NOT NULL,
"expires_at" timestamp with time zone NOT NULL,
"token" text NOT NULL,
"created_at" timestamp with time zone NOT NULL,
"updated_at" timestamp with time zone NOT NULL,
"ip_address" text,
"user_agent" text,
"user_id" text NOT NULL,
"impersonated_by" text,
"active_organization_id" text,
"city" text,
"country" text,
CONSTRAINT "session_token_unique" UNIQUE("token")
);
--> statement-breakpoint
ALTER TABLE "session" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
CREATE TABLE "subscriptions" (
"id" text PRIMARY KEY NOT NULL,
"org_id" text NOT NULL,
"stripe_id" text,
"stripe_schedule_id" text,
"created_at" numeric DEFAULT ROUND(date_part('epoch', NOW()) * 1000)::BIGINT NOT NULL,
"metadata" jsonb DEFAULT '{}'::jsonb,
"usage_features" text[] DEFAULT '{}',
"env" text,
"current_period_start" numeric,
"current_period_end" numeric,
CONSTRAINT "subscriptions_stripe_id_key" UNIQUE("stripe_id")
);
--> statement-breakpoint
CREATE TABLE "user" (
"id" text PRIMARY KEY NOT NULL,
"name" text NOT NULL,
"email" text NOT NULL,
"email_verified" boolean NOT NULL,
"image" text,
"created_at" timestamp with time zone NOT NULL,
"updated_at" timestamp with time zone NOT NULL,
"role" text,
"banned" boolean,
"ban_reason" text,
"ban_expires" timestamp with time zone,
"created_by" text,
"last_active_at" timestamp with time zone,
CONSTRAINT "user_email_unique" UNIQUE("email")
);
--> statement-breakpoint
CREATE TABLE "vercel_resources" (
"id" text PRIMARY KEY NOT NULL,
"org_id" text NOT NULL,
"env" text NOT NULL,
"installation_id" text NOT NULL,
"name" text NOT NULL,
"status" text NOT NULL,
"metadata" jsonb DEFAULT '{}'::jsonb NOT NULL
);
--> statement-breakpoint
CREATE TABLE "verification" (
"id" text PRIMARY KEY NOT NULL,
"identifier" text NOT NULL,
"value" text NOT NULL,
"expires_at" timestamp with time zone NOT NULL,
"created_at" timestamp with time zone,
"updated_at" timestamp with time zone
);
--> statement-breakpoint
ALTER TABLE "verification" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
ALTER TABLE "account" ADD CONSTRAINT "account_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "actions" ADD CONSTRAINT "actions_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "actions" ADD CONSTRAINT "actions_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "actions" ADD CONSTRAINT "actions_entity_id_fkey" FOREIGN KEY ("internal_entity_id") REFERENCES "public"."entities"("internal_id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "api_keys" ADD CONSTRAINT "api_keys_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "auto_topup_limit_states" ADD CONSTRAINT "auto_topup_limits_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "auto_topup_limit_states" ADD CONSTRAINT "auto_topup_limits_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "customer_entitlements" ADD CONSTRAINT "entitlements_internal_feature_id_fkey" FOREIGN KEY ("internal_feature_id") REFERENCES "public"."features"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "customer_entitlements" ADD CONSTRAINT "customer_entitlements_internal_entity_id_fkey" FOREIGN KEY ("internal_entity_id") REFERENCES "public"."entities"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "customer_entitlements" ADD CONSTRAINT "customer_entitlements_customer_product_id_fkey" FOREIGN KEY ("customer_product_id") REFERENCES "public"."customer_products"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint
ALTER TABLE "customer_entitlements" ADD CONSTRAINT "customer_entitlements_entitlement_id_fkey" FOREIGN KEY ("entitlement_id") REFERENCES "public"."entitlements"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint
ALTER TABLE "customer_prices" ADD CONSTRAINT "customer_prices_customer_product_id_fkey" FOREIGN KEY ("customer_product_id") REFERENCES "public"."customer_products"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "customer_prices" ADD CONSTRAINT "customer_prices_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "customer_prices" ADD CONSTRAINT "customer_prices_price_id_fkey" FOREIGN KEY ("price_id") REFERENCES "public"."prices"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "customer_products" ADD CONSTRAINT "customer_products_free_trial_id_fkey" FOREIGN KEY ("free_trial_id") REFERENCES "public"."free_trials"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "customer_products" ADD CONSTRAINT "customer_products_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint
ALTER TABLE "customer_products" ADD CONSTRAINT "customer_products_internal_product_id_fkey" FOREIGN KEY ("internal_product_id") REFERENCES "public"."products"("internal_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "customer_products" ADD CONSTRAINT "customer_products_internal_entity_id_fkey" FOREIGN KEY ("internal_entity_id") REFERENCES "public"."entities"("internal_id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "customers" ADD CONSTRAINT "customers_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "entities" ADD CONSTRAINT "entities_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "entities" ADD CONSTRAINT "entities_internal_feature_id_fkey" FOREIGN KEY ("internal_feature_id") REFERENCES "public"."features"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "entities" ADD CONSTRAINT "entities_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "entitlements" ADD CONSTRAINT "entitlements_internal_feature_id_fkey" FOREIGN KEY ("internal_feature_id") REFERENCES "public"."features"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "entitlements" ADD CONSTRAINT "entitlements_internal_product_id_fkey" FOREIGN KEY ("internal_product_id") REFERENCES "public"."products"("internal_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint
ALTER TABLE "entitlements" ADD CONSTRAINT "entitlements_internal_reward_id_fkey" FOREIGN KEY ("internal_reward_id") REFERENCES "public"."rewards"("internal_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint
ALTER TABLE "events" ADD CONSTRAINT "events_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "features" ADD CONSTRAINT "features_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "free_trials" ADD CONSTRAINT "free_trials_internal_product_id_fkey" FOREIGN KEY ("internal_product_id") REFERENCES "public"."products"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "invitation" ADD CONSTRAINT "invitation_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "invitation" ADD CONSTRAINT "invitation_inviter_id_user_id_fk" FOREIGN KEY ("inviter_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "invoice_line_items" ADD CONSTRAINT "invoice_line_items_invoice_id_fkey" FOREIGN KEY ("invoice_id") REFERENCES "public"."invoices"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "invoices" ADD CONSTRAINT "invoices_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "invoices" ADD CONSTRAINT "invoices_internal_entity_id_fkey" FOREIGN KEY ("internal_entity_id") REFERENCES "public"."entities"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "member" ADD CONSTRAINT "member_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "member" ADD CONSTRAINT "member_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "migration_errors" ADD CONSTRAINT "migration_customers_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "migration_errors" ADD CONSTRAINT "migration_customers_migration_job_id_fkey" FOREIGN KEY ("migration_job_id") REFERENCES "public"."migration_jobs"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "migration_jobs" ADD CONSTRAINT "migration_jobs_from_internal_product_id_fkey" FOREIGN KEY ("from_internal_product_id") REFERENCES "public"."products"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "migration_jobs" ADD CONSTRAINT "migration_jobs_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "migration_jobs" ADD CONSTRAINT "migration_jobs_to_internal_product_id_fkey" FOREIGN KEY ("to_internal_product_id") REFERENCES "public"."products"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "migration_runs" ADD CONSTRAINT "migration_runs_migration_internal_id_fkey" FOREIGN KEY ("migration_internal_id") REFERENCES "public"."migrations"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "migration_runs" ADD CONSTRAINT "migration_runs_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "migrations" ADD CONSTRAINT "migrations_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_client_id_oauth_client_client_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."oauth_client"("client_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_session_id_session_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."session"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "oauth_access_token" ADD CONSTRAINT "oauth_access_token_refresh_id_oauth_refresh_token_id_fk" FOREIGN KEY ("refresh_id") REFERENCES "public"."oauth_refresh_token"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "oauth_client" ADD CONSTRAINT "oauth_client_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "oauth_consent" ADD CONSTRAINT "oauth_consent_client_id_oauth_client_client_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."oauth_client"("client_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "oauth_consent" ADD CONSTRAINT "oauth_consent_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "oauth_refresh_token" ADD CONSTRAINT "oauth_refresh_token_client_id_oauth_client_client_id_fk" FOREIGN KEY ("client_id") REFERENCES "public"."oauth_client"("client_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "oauth_refresh_token" ADD CONSTRAINT "oauth_refresh_token_session_id_session_id_fk" FOREIGN KEY ("session_id") REFERENCES "public"."session"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "oauth_refresh_token" ADD CONSTRAINT "oauth_refresh_token_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "prices" ADD CONSTRAINT "prices_entitlement_id_fkey" FOREIGN KEY ("entitlement_id") REFERENCES "public"."entitlements"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "prices" ADD CONSTRAINT "prices_internal_product_id_fkey" FOREIGN KEY ("internal_product_id") REFERENCES "public"."products"("internal_id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint
ALTER TABLE "products" ADD CONSTRAINT "products_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "referral_codes" ADD CONSTRAINT "referral_codes_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "referral_codes" ADD CONSTRAINT "referral_codes_internal_reward_program_id_fkey" FOREIGN KEY ("internal_reward_program_id") REFERENCES "public"."reward_programs"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "referral_codes" ADD CONSTRAINT "referral_codes_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "replaceables" ADD CONSTRAINT "replaceables_cus_ent_id_fkey" FOREIGN KEY ("cus_ent_id") REFERENCES "public"."customer_entitlements"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "revenuecat_mappings" ADD CONSTRAINT "revenuecat_mappings_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "reward_programs" ADD CONSTRAINT "reward_triggers_internal_reward_id_fkey" FOREIGN KEY ("internal_reward_id") REFERENCES "public"."rewards"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "reward_programs" ADD CONSTRAINT "reward_triggers_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "reward_redemptions" ADD CONSTRAINT "reward_redemptions_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "reward_redemptions" ADD CONSTRAINT "reward_redemptions_internal_reward_program_id_fkey" FOREIGN KEY ("internal_reward_program_id") REFERENCES "public"."reward_programs"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "reward_redemptions" ADD CONSTRAINT "reward_redemptions_referral_code_id_fkey" FOREIGN KEY ("referral_code_id") REFERENCES "public"."referral_codes"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "rewards" ADD CONSTRAINT "coupons_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "rollovers" ADD CONSTRAINT "rollover_cus_ent_id_fkey" FOREIGN KEY ("cus_ent_id") REFERENCES "public"."customer_entitlements"("id") ON DELETE cascade ON UPDATE cascade;--> statement-breakpoint
ALTER TABLE "phases" ADD CONSTRAINT "phases_schedule_id_fkey" FOREIGN KEY ("schedule_id") REFERENCES "public"."schedules"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "schedules" ADD CONSTRAINT "schedules_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "schedules" ADD CONSTRAINT "schedules_internal_customer_id_fkey" FOREIGN KEY ("internal_customer_id") REFERENCES "public"."customers"("internal_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "schedules" ADD CONSTRAINT "schedules_internal_entity_id_fkey" FOREIGN KEY ("internal_entity_id") REFERENCES "public"."entities"("internal_id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "session" ADD CONSTRAINT "session_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "subscriptions" ADD CONSTRAINT "subscriptions_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "user" ADD CONSTRAINT "user_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "public"."organizations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "vercel_resources" ADD CONSTRAINT "vercel_resources_org_id_fkey" FOREIGN KEY ("org_id") REFERENCES "public"."organizations"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "account_userId_idx" ON "account" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "idx_actions_on_internal_entity_id" ON "actions" USING btree ("internal_entity_id");--> statement-breakpoint
CREATE UNIQUE INDEX "auto_topup_limits_org_env_internal_customer_feature_unique" ON "auto_topup_limit_states" USING btree ("org_id","env","internal_customer_id","feature_id");--> statement-breakpoint
CREATE INDEX "idx_checkouts_stripe_invoice_id" ON "checkouts" USING btree ("stripe_invoice_id");--> statement-breakpoint
CREATE INDEX "idx_customer_entitlements_product_id" ON "customer_entitlements" USING btree ("customer_product_id");--> statement-breakpoint
CREATE INDEX "idx_customer_entitlements_internal_customer_id" ON "customer_entitlements" USING hash ("internal_customer_id");--> statement-breakpoint
CREATE INDEX "idx_customer_entitlements_internal_customer_id_btree" ON "customer_entitlements" USING btree ("internal_customer_id");--> statement-breakpoint
CREATE INDEX "idx_customer_entitlements_entitlement_id" ON "customer_entitlements" USING btree ("entitlement_id");--> statement-breakpoint
CREATE INDEX "idx_customer_entitlements_internal_entity_id" ON "customer_entitlements" USING hash ("internal_entity_id");--> statement-breakpoint
CREATE INDEX "idx_customer_entitlements_on_next_reset_at" ON "customer_entitlements" USING btree ("next_reset_at");--> statement-breakpoint
CREATE INDEX "idx_customer_entitlements_loose_customer_expires" ON "customer_entitlements" USING btree ("internal_customer_id","expires_at") WHERE "customer_entitlements"."customer_product_id" IS NULL;--> statement-breakpoint
CREATE INDEX "idx_customer_prices_product_id" ON "customer_prices" USING btree ("customer_product_id");--> statement-breakpoint
CREATE INDEX "idx_customer_prices_price_id" ON "customer_prices" USING btree ("price_id");--> statement-breakpoint
CREATE INDEX "idx_customer_products_customer_status" ON "customer_products" USING btree ("internal_customer_id","status");--> statement-breakpoint
CREATE INDEX "idx_customer_products_on_internal_entity_id" ON "customer_products" USING btree ("internal_entity_id");--> statement-breakpoint
CREATE INDEX "idx_customer_products_on_internal_product_id" ON "customer_products" USING btree ("internal_product_id");--> statement-breakpoint
CREATE INDEX "idx_customer_products_subscription_ids" ON "customer_products" USING gin ("subscription_ids");--> statement-breakpoint
CREATE INDEX "idx_customer_products_scheduled_ids" ON "customer_products" USING gin ("scheduled_ids");--> statement-breakpoint
CREATE INDEX "idx_customer_products_stripe_checkout_session_id" ON "customer_products" USING btree ("stripe_checkout_session_id");--> statement-breakpoint
CREATE UNIQUE INDEX "customers_email_null_id_unique" ON "customers" USING btree ("org_id","env",lower("email")) WHERE "customers"."id" IS NULL AND "customers"."email" IS NOT NULL AND "customers"."email" != '';--> statement-breakpoint
CREATE INDEX "idx_customers_org_env_fingerprint" ON "customers" USING btree ("org_id","env","fingerprint") WHERE "customers"."fingerprint" IS NOT NULL;--> statement-breakpoint
CREATE INDEX "idx_customers_processor_id" ON "customers" USING btree (("processor" ->> 'id'));--> statement-breakpoint
CREATE INDEX "idx_customers_composite" ON "customers" USING btree ("org_id","env","id");--> statement-breakpoint
CREATE INDEX "idx_customers_org_env_internal_id" ON "customers" USING btree ("org_id","env","internal_id" DESC);--> statement-breakpoint
CREATE INDEX "idx_customers_email_trgm" ON "customers" USING gin ("email" gin_trgm_ops) WHERE "customers"."email" IS NOT NULL;--> statement-breakpoint
CREATE INDEX "idx_customers_name_trgm" ON "customers" USING gin ("name" gin_trgm_ops) WHERE "customers"."name" IS NOT NULL;--> statement-breakpoint
CREATE INDEX "idx_customers_id_trgm" ON "customers" USING gin ("id" gin_trgm_ops) WHERE "customers"."id" IS NOT NULL;--> statement-breakpoint
CREATE INDEX "idx_customers_org_id_env_created_at" ON "customers" USING btree ("org_id","env","created_at" DESC);--> statement-breakpoint
CREATE INDEX "idx_customers_processors_revenuecat" ON "customers" USING btree (("processors" ->> 'revenuecat'));--> statement-breakpoint
CREATE INDEX "idx_customers_processors_vercel" ON "customers" USING btree (("processors" ->> 'vercel'));--> statement-breakpoint
CREATE INDEX "idx_entities_internal_customer_id" ON "entities" USING btree ("internal_customer_id");--> statement-breakpoint
CREATE INDEX "idx_entities_customer_internal_desc" ON "entities" USING btree ("internal_customer_id","internal_id" DESC);--> statement-breakpoint
CREATE INDEX "idx_entities_org_env_id" ON "entities" USING btree ("org_id","env","id");--> statement-breakpoint
CREATE INDEX "idx_entitlements_internal_product_id" ON "entitlements" USING btree ("internal_product_id");--> statement-breakpoint
CREATE INDEX "idx_entitlements_internal_reward_id" ON "entitlements" USING btree ("internal_reward_id");--> statement-breakpoint
CREATE INDEX "idx_entitlements_reward_feature" ON "entitlements" USING btree ("internal_reward_id","internal_feature_id");--> statement-breakpoint
CREATE INDEX "idx_events_internal_customer_id" ON "events" USING btree ("internal_customer_id");--> statement-breakpoint
CREATE INDEX "idx_events_internal_entity_id" ON "events" USING btree ("internal_entity_id");--> statement-breakpoint
CREATE INDEX "idx_events_customer_non_usage_ts" ON "events" USING btree ("internal_customer_id","timestamp" DESC,"id" DESC) WHERE "events"."set_usage" = false;--> statement-breakpoint
CREATE INDEX "idx_free_trials_internal_product_id" ON "free_trials" USING btree ("internal_product_id");--> statement-breakpoint
CREATE INDEX "invitation_organizationId_idx" ON "invitation" USING btree ("organization_id");--> statement-breakpoint
CREATE INDEX "invitation_email_idx" ON "invitation" USING btree ("email");--> statement-breakpoint
CREATE INDEX "idx_invoices_customer_created" ON "invoices" USING btree ("internal_customer_id","created_at" DESC,"id" DESC);--> statement-breakpoint
CREATE INDEX "member_organizationId_idx" ON "member" USING btree ("organization_id");--> statement-breakpoint
CREATE INDEX "member_userId_idx" ON "member" USING btree ("user_id");--> statement-breakpoint
CREATE UNIQUE INDEX "migration_item_runs_live_unique" ON "migration_item_runs" USING btree ("migration_internal_id","item_kind","item_id") WHERE "migration_item_runs"."dry_run" = false;--> statement-breakpoint
CREATE UNIQUE INDEX "migration_item_runs_dry_run_unique" ON "migration_item_runs" USING btree ("migration_internal_id","migration_run_id","item_kind","item_id") WHERE "migration_item_runs"."dry_run" = true;--> statement-breakpoint
CREATE INDEX "migration_item_runs_customer_recent_idx" ON "migration_item_runs" USING btree ("item_id","updated_at" DESC) WHERE "migration_item_runs"."item_kind" = 'customer';--> statement-breakpoint
CREATE UNIQUE INDEX "migration_runs_active_per_migration_unique" ON "migration_runs" USING btree ("migration_internal_id") WHERE "migration_runs"."status" IN ('queued', 'running');--> statement-breakpoint
CREATE UNIQUE INDEX "migrations_org_env_id_unique" ON "migrations" USING btree ("org_id","env","id");--> statement-breakpoint
CREATE INDEX "idx_organizations_name_trgm" ON "organizations" USING gin ("name" gin_trgm_ops) WHERE "organizations"."name" IS NOT NULL;--> statement-breakpoint
CREATE INDEX "idx_organizations_slug_trgm" ON "organizations" USING gin ("slug" gin_trgm_ops) WHERE "organizations"."slug" IS NOT NULL;--> statement-breakpoint
CREATE INDEX "idx_organizations_created_at_id" ON "organizations" USING btree ("createdAt" DESC,"id" DESC);--> statement-breakpoint
CREATE INDEX "idx_prices_internal_product_id" ON "prices" USING btree ("internal_product_id");--> statement-breakpoint
CREATE INDEX "idx_prices_entitlement_id" ON "prices" USING btree ("entitlement_id");--> statement-breakpoint
CREATE INDEX "idx_products_org_env_id_version" ON "products" USING btree ("org_id","env","id","version");--> statement-breakpoint
CREATE INDEX "idx_referral_codes_internal_customer_id" ON "referral_codes" USING btree ("internal_customer_id");--> statement-breakpoint
CREATE INDEX "idx_replaceables_cus_ent_id" ON "replaceables" USING btree ("cus_ent_id");--> statement-breakpoint
CREATE INDEX "idx_reward_redemptions_referral_code_id" ON "reward_redemptions" USING btree ("referral_code_id");--> statement-breakpoint
CREATE INDEX "idx_reward_redemptions_reward_internal_id" ON "reward_redemptions" USING btree ("reward_internal_id");--> statement-breakpoint
CREATE INDEX "idx_reward_redemptions_customer_reward" ON "reward_redemptions" USING btree ("internal_customer_id","reward_internal_id");--> statement-breakpoint
CREATE INDEX "idx_rollovers_cus_ent_id" ON "rollovers" USING btree ("cus_ent_id");--> statement-breakpoint
CREATE INDEX "idx_rollovers_cus_ent_expires" ON "rollovers" USING btree ("cus_ent_id","expires_at");--> statement-breakpoint
CREATE UNIQUE INDEX "schedules_customer_scope_unique" ON "schedules" USING btree ("org_id","env","internal_customer_id") WHERE "schedules"."internal_entity_id" IS NULL;--> statement-breakpoint
CREATE UNIQUE INDEX "schedules_entity_scope_unique" ON "schedules" USING btree ("org_id","env","internal_customer_id","internal_entity_id") WHERE "schedules"."internal_entity_id" IS NOT NULL;--> statement-breakpoint
CREATE INDEX "idx_schedules_internal_customer_id" ON "schedules" USING btree ("internal_customer_id");--> statement-breakpoint
CREATE INDEX "idx_schedules_internal_entity_id" ON "schedules" USING btree ("internal_entity_id");--> statement-breakpoint
CREATE INDEX "session_userId_idx" ON "session" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "idx_user_name_trgm" ON "user" USING gin ("name" gin_trgm_ops) WHERE "user"."name" IS NOT NULL;--> statement-breakpoint
CREATE INDEX "idx_user_email_trgm" ON "user" USING gin ("email" gin_trgm_ops) WHERE "user"."email" IS NOT NULL;--> statement-breakpoint
CREATE INDEX "idx_user_created_at_id" ON "user" USING btree ("created_at" DESC,"id" DESC);--> statement-breakpoint
CREATE INDEX "verification_identifier_idx" ON "verification" USING btree ("identifier");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1779096275848,
"tag": "0000_bumpy_tinkerer",
"breakpoints": true
}
]
}