Your music, beautifully tracked. All yours. (coming soon) teal.fm
teal-fm atproto

Merge branch 'main' into move-social-app

authored by mmatt.net and committed by

GitHub 52406210 fff7ffe6

+1002 -115
+2 -2
README.md
··· 9 9 To get started with this template, simply paste this command into your terminal: 10 10 11 11 ```bash 12 - bun install && bun install -g turbo && cp apps/aqua/.env.example apps/aqua/.env && 13 - bun run db:migrate 12 + pnpm install && pnpm install -g turbo && cp apps/aqua/.env.example apps/aqua/.env && 13 + pnpm run db:migrate 14 14 ``` 15 15 Running on a Mac may also require adding @libsql/darwin-x64 dependency 16 16
+6 -10
apps/aqua/package.json
··· 7 7 "dev": "tsx watch --clear-screen=false src/index.ts | pino-pretty", 8 8 "build": "tsup && cp -r public dist/", 9 9 "start": "node dist/index.cjs | pino-pretty", 10 - "lexgen": "lex gen-server ./src/lexicon ./lexicons/*", 11 10 "clean": "rimraf dist coverage", 12 - "check-types": "tsc --noEmit", 13 - "db:migrate": "drizzle-kit migrate", 14 - "db:seed": "drizzle-kit seed", 15 - "db:studio": "drizzle-kit studio" 11 + "check-types": "tsc --noEmit" 16 12 }, 17 13 "dependencies": { 18 14 "@atproto/api": "^0.13.15", ··· 22 18 "@atproto/oauth-client-node": "^0.2.1", 23 19 "@atproto/sync": "^0.1.5", 24 20 "@atproto/syntax": "^0.3.0", 25 - "@atproto/xrpc-server": "^0.6.4", 21 + "@atproto/xrpc-server": "^0.7.4", 26 22 "@braintree/sanitize-url": "^7.1.0", 23 + "drizzle-orm": "^0.38.3", 27 24 "@hono/node-server": "^1.13.7", 28 25 "@libsql/client": "^0.14.0", 29 26 "dotenv": "^16.4.5", 30 - "drizzle-orm": "^0.36.1", 31 27 "envalid": "^8.0.0", 32 28 "hono": "^4.6.9", 33 29 "jose": "^5.9.6", ··· 39 35 }, 40 36 "devDependencies": { 41 37 "@teal/tsconfig": "workspace:*", 42 - "@atproto/lex-cli": "^0.4.1", 38 + "@atproto/lex-cli": "^0.5.4", 43 39 "@types/node": "^20.17.6", 44 - "drizzle-kit": "^0.27.2", 45 - "pino-pretty": "^11.3.0", 40 + "drizzle-kit": "^0.30.1", 41 + "pino-pretty": "^13.0.0", 46 42 "rimraf": "^6.0.1", 47 43 "tsup": "^8.3.5", 48 44 "tsx": "^4.19.2",
-11
drizzle.config.ts
··· 1 - import { defineConfig } from "drizzle-kit"; 2 - 3 - export default defineConfig({ 4 - dialect: "sqlite", 5 - schema: "./packages/db/schema.ts", 6 - out: "./packages/db/.drizzle", 7 - casing: "snake_case", 8 - dbCredentials: { 9 - url: process.env.DATABASE_URL ?? "./db.sqlite", 10 - }, 11 - });
+5 -8
package.json
··· 8 8 "build": "pnpm turbo run build --filter='./packages/*' --filter='./apps/*'", 9 9 "typecheck": "pnpm -r exec tsc --noEmit", 10 10 "fix": "biome lint --apply . && biome format --write . && biome check . --apply", 11 - "lex:gen-server": "lex gen-server ./packages/lexicons/generated/server ./packages/lexicons/src/*", 12 - "lex:gen-api": "lex gen-api ./packages/lexicons/generated/api ./packages/lexicons/src/*", 13 - "lex:gen-md": "lex gen-md ./packages/lexicons/generated/md ./packages/lexicons/src/*", 14 - "db:studio": "drizzle-kit studio", 15 - "db:migrate": "drizzle-kit migrate", 16 - "db:seed": "drizzle-kit seed" 11 + "nuke": "rimraf node_modules */*/node_modules" 17 12 }, 18 13 "devDependencies": { 19 - "turbo": "^2.3.3", 20 - "biome": "^0.3.3" 14 + "@types/node": "^20.17.10", 15 + "biome": "^0.3.3", 16 + "rimraf": "^6.0.1", 17 + "turbo": "^2.3.3" 21 18 }, 22 19 "workspaces": [ 23 20 "apps/*",
+29
packages/db/.drizzle/0004_exotic_ironclad.sql
··· 1 + CREATE TABLE `follow` ( 2 + `follower` text PRIMARY KEY NOT NULL, 3 + `followed` text NOT NULL, 4 + `created_at` text NOT NULL 5 + ); 6 + --> statement-breakpoint 7 + CREATE TABLE `play` ( 8 + `uri` text PRIMARY KEY NOT NULL, 9 + `author_did` text NOT NULL, 10 + `created_at` text NOT NULL, 11 + `indexed_at` text NOT NULL, 12 + `track_name` text NOT NULL, 13 + `track_mb_id` text, 14 + `recording_mb_id` text, 15 + `duration` integer, 16 + `artist_name` text NOT NULL, 17 + `artist_mb_ids` text, 18 + `release_name` text, 19 + `release_mb_id` text, 20 + `isrc` text, 21 + `origin_url` text, 22 + `music_service_base_domain` text, 23 + `submission_client_agent` text, 24 + `played_time` text 25 + ); 26 + --> statement-breakpoint 27 + ALTER TABLE `teal_user` ADD `avatar` text NOT NULL;--> statement-breakpoint 28 + ALTER TABLE `teal_user` ADD `bio` text;--> statement-breakpoint 29 + ALTER TABLE `teal_user` DROP COLUMN `email`;
+12
packages/db/.drizzle/0005_conscious_johnny_blaze.sql
··· 1 + PRAGMA foreign_keys=OFF;--> statement-breakpoint 2 + CREATE TABLE `__new_follow` ( 3 + `rel_id` text PRIMARY KEY NOT NULL, 4 + `follower` text NOT NULL, 5 + `followed` text NOT NULL, 6 + `created_at` text NOT NULL 7 + ); 8 + --> statement-breakpoint 9 + INSERT INTO `__new_follow`("rel_id", "follower", "followed", "created_at") SELECT '0', "follower", "followed", "created_at" FROM `follow`;--> statement-breakpoint 10 + DROP TABLE `follow`;--> statement-breakpoint 11 + ALTER TABLE `__new_follow` RENAME TO `follow`;--> statement-breakpoint 12 + PRAGMA foreign_keys=ON;
+347
packages/db/.drizzle/meta/0004_snapshot.json
··· 1 + { 2 + "version": "6", 3 + "dialect": "sqlite", 4 + "id": "639ec806-61a1-448d-a922-1935bf8f6cf3", 5 + "prevId": "7710000b-44fd-4d23-a768-0117f22926c3", 6 + "tables": { 7 + "atp_session": { 8 + "name": "atp_session", 9 + "columns": { 10 + "key": { 11 + "name": "key", 12 + "type": "text", 13 + "primaryKey": true, 14 + "notNull": true, 15 + "autoincrement": false 16 + }, 17 + "session": { 18 + "name": "session", 19 + "type": "text", 20 + "primaryKey": false, 21 + "notNull": true, 22 + "autoincrement": false 23 + } 24 + }, 25 + "indexes": {}, 26 + "foreignKeys": {}, 27 + "compositePrimaryKeys": {}, 28 + "uniqueConstraints": {}, 29 + "checkConstraints": {} 30 + }, 31 + "auth_state": { 32 + "name": "auth_state", 33 + "columns": { 34 + "key": { 35 + "name": "key", 36 + "type": "text", 37 + "primaryKey": true, 38 + "notNull": true, 39 + "autoincrement": false 40 + }, 41 + "state": { 42 + "name": "state", 43 + "type": "text", 44 + "primaryKey": false, 45 + "notNull": true, 46 + "autoincrement": false 47 + } 48 + }, 49 + "indexes": {}, 50 + "foreignKeys": {}, 51 + "compositePrimaryKeys": {}, 52 + "uniqueConstraints": {}, 53 + "checkConstraints": {} 54 + }, 55 + "follow": { 56 + "name": "follow", 57 + "columns": { 58 + "follower": { 59 + "name": "follower", 60 + "type": "text", 61 + "primaryKey": true, 62 + "notNull": true, 63 + "autoincrement": false 64 + }, 65 + "followed": { 66 + "name": "followed", 67 + "type": "text", 68 + "primaryKey": true, 69 + "notNull": true, 70 + "autoincrement": false 71 + }, 72 + "created_at": { 73 + "name": "created_at", 74 + "type": "text", 75 + "primaryKey": false, 76 + "notNull": true, 77 + "autoincrement": false 78 + } 79 + }, 80 + "indexes": {}, 81 + "foreignKeys": {}, 82 + "compositePrimaryKeys": {}, 83 + "uniqueConstraints": {}, 84 + "checkConstraints": {} 85 + }, 86 + "play": { 87 + "name": "play", 88 + "columns": { 89 + "uri": { 90 + "name": "uri", 91 + "type": "text", 92 + "primaryKey": true, 93 + "notNull": true, 94 + "autoincrement": false 95 + }, 96 + "author_did": { 97 + "name": "author_did", 98 + "type": "text", 99 + "primaryKey": false, 100 + "notNull": true, 101 + "autoincrement": false 102 + }, 103 + "created_at": { 104 + "name": "created_at", 105 + "type": "text", 106 + "primaryKey": false, 107 + "notNull": true, 108 + "autoincrement": false 109 + }, 110 + "indexed_at": { 111 + "name": "indexed_at", 112 + "type": "text", 113 + "primaryKey": false, 114 + "notNull": true, 115 + "autoincrement": false 116 + }, 117 + "track_name": { 118 + "name": "track_name", 119 + "type": "text", 120 + "primaryKey": false, 121 + "notNull": true, 122 + "autoincrement": false 123 + }, 124 + "track_mb_id": { 125 + "name": "track_mb_id", 126 + "type": "text", 127 + "primaryKey": false, 128 + "notNull": false, 129 + "autoincrement": false 130 + }, 131 + "recording_mb_id": { 132 + "name": "recording_mb_id", 133 + "type": "text", 134 + "primaryKey": false, 135 + "notNull": false, 136 + "autoincrement": false 137 + }, 138 + "duration": { 139 + "name": "duration", 140 + "type": "integer", 141 + "primaryKey": false, 142 + "notNull": false, 143 + "autoincrement": false 144 + }, 145 + "artist_name": { 146 + "name": "artist_name", 147 + "type": "text", 148 + "primaryKey": false, 149 + "notNull": true, 150 + "autoincrement": false 151 + }, 152 + "artist_mb_ids": { 153 + "name": "artist_mb_ids", 154 + "type": "text", 155 + "primaryKey": false, 156 + "notNull": false, 157 + "autoincrement": false 158 + }, 159 + "release_name": { 160 + "name": "release_name", 161 + "type": "text", 162 + "primaryKey": false, 163 + "notNull": false, 164 + "autoincrement": false 165 + }, 166 + "release_mb_id": { 167 + "name": "release_mb_id", 168 + "type": "text", 169 + "primaryKey": false, 170 + "notNull": false, 171 + "autoincrement": false 172 + }, 173 + "isrc": { 174 + "name": "isrc", 175 + "type": "text", 176 + "primaryKey": false, 177 + "notNull": false, 178 + "autoincrement": false 179 + }, 180 + "origin_url": { 181 + "name": "origin_url", 182 + "type": "text", 183 + "primaryKey": false, 184 + "notNull": false, 185 + "autoincrement": false 186 + }, 187 + "music_service_base_domain": { 188 + "name": "music_service_base_domain", 189 + "type": "text", 190 + "primaryKey": false, 191 + "notNull": false, 192 + "autoincrement": false 193 + }, 194 + "submission_client_agent": { 195 + "name": "submission_client_agent", 196 + "type": "text", 197 + "primaryKey": false, 198 + "notNull": false, 199 + "autoincrement": false 200 + }, 201 + "played_time": { 202 + "name": "played_time", 203 + "type": "text", 204 + "primaryKey": false, 205 + "notNull": false, 206 + "autoincrement": false 207 + } 208 + }, 209 + "indexes": {}, 210 + "foreignKeys": {}, 211 + "compositePrimaryKeys": {}, 212 + "uniqueConstraints": {}, 213 + "checkConstraints": {} 214 + }, 215 + "status": { 216 + "name": "status", 217 + "columns": { 218 + "uri": { 219 + "name": "uri", 220 + "type": "text", 221 + "primaryKey": true, 222 + "notNull": true, 223 + "autoincrement": false 224 + }, 225 + "author_did": { 226 + "name": "author_did", 227 + "type": "text", 228 + "primaryKey": false, 229 + "notNull": true, 230 + "autoincrement": false 231 + }, 232 + "status": { 233 + "name": "status", 234 + "type": "text", 235 + "primaryKey": false, 236 + "notNull": true, 237 + "autoincrement": false 238 + }, 239 + "created_at": { 240 + "name": "created_at", 241 + "type": "text", 242 + "primaryKey": false, 243 + "notNull": true, 244 + "autoincrement": false 245 + }, 246 + "indexed_at": { 247 + "name": "indexed_at", 248 + "type": "text", 249 + "primaryKey": false, 250 + "notNull": true, 251 + "autoincrement": false 252 + } 253 + }, 254 + "indexes": {}, 255 + "foreignKeys": {}, 256 + "compositePrimaryKeys": {}, 257 + "uniqueConstraints": {}, 258 + "checkConstraints": {} 259 + }, 260 + "teal_session": { 261 + "name": "teal_session", 262 + "columns": { 263 + "key": { 264 + "name": "key", 265 + "type": "text", 266 + "primaryKey": true, 267 + "notNull": true, 268 + "autoincrement": false 269 + }, 270 + "session": { 271 + "name": "session", 272 + "type": "text", 273 + "primaryKey": false, 274 + "notNull": true, 275 + "autoincrement": false 276 + }, 277 + "provider": { 278 + "name": "provider", 279 + "type": "text", 280 + "primaryKey": false, 281 + "notNull": true, 282 + "autoincrement": false 283 + } 284 + }, 285 + "indexes": {}, 286 + "foreignKeys": {}, 287 + "compositePrimaryKeys": {}, 288 + "uniqueConstraints": {}, 289 + "checkConstraints": {} 290 + }, 291 + "teal_user": { 292 + "name": "teal_user", 293 + "columns": { 294 + "did": { 295 + "name": "did", 296 + "type": "text", 297 + "primaryKey": true, 298 + "notNull": true, 299 + "autoincrement": false 300 + }, 301 + "handle": { 302 + "name": "handle", 303 + "type": "text", 304 + "primaryKey": false, 305 + "notNull": true, 306 + "autoincrement": false 307 + }, 308 + "avatar": { 309 + "name": "avatar", 310 + "type": "text", 311 + "primaryKey": false, 312 + "notNull": true, 313 + "autoincrement": false 314 + }, 315 + "bio": { 316 + "name": "bio", 317 + "type": "text", 318 + "primaryKey": false, 319 + "notNull": false, 320 + "autoincrement": false 321 + }, 322 + "created_at": { 323 + "name": "created_at", 324 + "type": "text", 325 + "primaryKey": false, 326 + "notNull": true, 327 + "autoincrement": false 328 + } 329 + }, 330 + "indexes": {}, 331 + "foreignKeys": {}, 332 + "compositePrimaryKeys": {}, 333 + "uniqueConstraints": {}, 334 + "checkConstraints": {} 335 + } 336 + }, 337 + "views": {}, 338 + "enums": {}, 339 + "_meta": { 340 + "schemas": {}, 341 + "tables": {}, 342 + "columns": {} 343 + }, 344 + "internal": { 345 + "indexes": {} 346 + } 347 + }
+354
packages/db/.drizzle/meta/0005_snapshot.json
··· 1 + { 2 + "version": "6", 3 + "dialect": "sqlite", 4 + "id": "42a94e7a-c4c2-4bd5-92d9-ba8a829c0704", 5 + "prevId": "639ec806-61a1-448d-a922-1935bf8f6cf3", 6 + "tables": { 7 + "atp_session": { 8 + "name": "atp_session", 9 + "columns": { 10 + "key": { 11 + "name": "key", 12 + "type": "text", 13 + "primaryKey": true, 14 + "notNull": true, 15 + "autoincrement": false 16 + }, 17 + "session": { 18 + "name": "session", 19 + "type": "text", 20 + "primaryKey": false, 21 + "notNull": true, 22 + "autoincrement": false 23 + } 24 + }, 25 + "indexes": {}, 26 + "foreignKeys": {}, 27 + "compositePrimaryKeys": {}, 28 + "uniqueConstraints": {}, 29 + "checkConstraints": {} 30 + }, 31 + "auth_state": { 32 + "name": "auth_state", 33 + "columns": { 34 + "key": { 35 + "name": "key", 36 + "type": "text", 37 + "primaryKey": true, 38 + "notNull": true, 39 + "autoincrement": false 40 + }, 41 + "state": { 42 + "name": "state", 43 + "type": "text", 44 + "primaryKey": false, 45 + "notNull": true, 46 + "autoincrement": false 47 + } 48 + }, 49 + "indexes": {}, 50 + "foreignKeys": {}, 51 + "compositePrimaryKeys": {}, 52 + "uniqueConstraints": {}, 53 + "checkConstraints": {} 54 + }, 55 + "follow": { 56 + "name": "follow", 57 + "columns": { 58 + "rel_id": { 59 + "name": "rel_id", 60 + "type": "text", 61 + "primaryKey": true, 62 + "notNull": true, 63 + "autoincrement": false 64 + }, 65 + "follower": { 66 + "name": "follower", 67 + "type": "text", 68 + "primaryKey": false, 69 + "notNull": true, 70 + "autoincrement": false 71 + }, 72 + "followed": { 73 + "name": "followed", 74 + "type": "text", 75 + "primaryKey": false, 76 + "notNull": true, 77 + "autoincrement": false 78 + }, 79 + "created_at": { 80 + "name": "created_at", 81 + "type": "text", 82 + "primaryKey": false, 83 + "notNull": true, 84 + "autoincrement": false 85 + } 86 + }, 87 + "indexes": {}, 88 + "foreignKeys": {}, 89 + "compositePrimaryKeys": {}, 90 + "uniqueConstraints": {}, 91 + "checkConstraints": {} 92 + }, 93 + "play": { 94 + "name": "play", 95 + "columns": { 96 + "uri": { 97 + "name": "uri", 98 + "type": "text", 99 + "primaryKey": true, 100 + "notNull": true, 101 + "autoincrement": false 102 + }, 103 + "author_did": { 104 + "name": "author_did", 105 + "type": "text", 106 + "primaryKey": false, 107 + "notNull": true, 108 + "autoincrement": false 109 + }, 110 + "created_at": { 111 + "name": "created_at", 112 + "type": "text", 113 + "primaryKey": false, 114 + "notNull": true, 115 + "autoincrement": false 116 + }, 117 + "indexed_at": { 118 + "name": "indexed_at", 119 + "type": "text", 120 + "primaryKey": false, 121 + "notNull": true, 122 + "autoincrement": false 123 + }, 124 + "track_name": { 125 + "name": "track_name", 126 + "type": "text", 127 + "primaryKey": false, 128 + "notNull": true, 129 + "autoincrement": false 130 + }, 131 + "track_mb_id": { 132 + "name": "track_mb_id", 133 + "type": "text", 134 + "primaryKey": false, 135 + "notNull": false, 136 + "autoincrement": false 137 + }, 138 + "recording_mb_id": { 139 + "name": "recording_mb_id", 140 + "type": "text", 141 + "primaryKey": false, 142 + "notNull": false, 143 + "autoincrement": false 144 + }, 145 + "duration": { 146 + "name": "duration", 147 + "type": "integer", 148 + "primaryKey": false, 149 + "notNull": false, 150 + "autoincrement": false 151 + }, 152 + "artist_name": { 153 + "name": "artist_name", 154 + "type": "text", 155 + "primaryKey": false, 156 + "notNull": true, 157 + "autoincrement": false 158 + }, 159 + "artist_mb_ids": { 160 + "name": "artist_mb_ids", 161 + "type": "text", 162 + "primaryKey": false, 163 + "notNull": false, 164 + "autoincrement": false 165 + }, 166 + "release_name": { 167 + "name": "release_name", 168 + "type": "text", 169 + "primaryKey": false, 170 + "notNull": false, 171 + "autoincrement": false 172 + }, 173 + "release_mb_id": { 174 + "name": "release_mb_id", 175 + "type": "text", 176 + "primaryKey": false, 177 + "notNull": false, 178 + "autoincrement": false 179 + }, 180 + "isrc": { 181 + "name": "isrc", 182 + "type": "text", 183 + "primaryKey": false, 184 + "notNull": false, 185 + "autoincrement": false 186 + }, 187 + "origin_url": { 188 + "name": "origin_url", 189 + "type": "text", 190 + "primaryKey": false, 191 + "notNull": false, 192 + "autoincrement": false 193 + }, 194 + "music_service_base_domain": { 195 + "name": "music_service_base_domain", 196 + "type": "text", 197 + "primaryKey": false, 198 + "notNull": false, 199 + "autoincrement": false 200 + }, 201 + "submission_client_agent": { 202 + "name": "submission_client_agent", 203 + "type": "text", 204 + "primaryKey": false, 205 + "notNull": false, 206 + "autoincrement": false 207 + }, 208 + "played_time": { 209 + "name": "played_time", 210 + "type": "text", 211 + "primaryKey": false, 212 + "notNull": false, 213 + "autoincrement": false 214 + } 215 + }, 216 + "indexes": {}, 217 + "foreignKeys": {}, 218 + "compositePrimaryKeys": {}, 219 + "uniqueConstraints": {}, 220 + "checkConstraints": {} 221 + }, 222 + "status": { 223 + "name": "status", 224 + "columns": { 225 + "uri": { 226 + "name": "uri", 227 + "type": "text", 228 + "primaryKey": true, 229 + "notNull": true, 230 + "autoincrement": false 231 + }, 232 + "author_did": { 233 + "name": "author_did", 234 + "type": "text", 235 + "primaryKey": false, 236 + "notNull": true, 237 + "autoincrement": false 238 + }, 239 + "status": { 240 + "name": "status", 241 + "type": "text", 242 + "primaryKey": false, 243 + "notNull": true, 244 + "autoincrement": false 245 + }, 246 + "created_at": { 247 + "name": "created_at", 248 + "type": "text", 249 + "primaryKey": false, 250 + "notNull": true, 251 + "autoincrement": false 252 + }, 253 + "indexed_at": { 254 + "name": "indexed_at", 255 + "type": "text", 256 + "primaryKey": false, 257 + "notNull": true, 258 + "autoincrement": false 259 + } 260 + }, 261 + "indexes": {}, 262 + "foreignKeys": {}, 263 + "compositePrimaryKeys": {}, 264 + "uniqueConstraints": {}, 265 + "checkConstraints": {} 266 + }, 267 + "teal_session": { 268 + "name": "teal_session", 269 + "columns": { 270 + "key": { 271 + "name": "key", 272 + "type": "text", 273 + "primaryKey": true, 274 + "notNull": true, 275 + "autoincrement": false 276 + }, 277 + "session": { 278 + "name": "session", 279 + "type": "text", 280 + "primaryKey": false, 281 + "notNull": true, 282 + "autoincrement": false 283 + }, 284 + "provider": { 285 + "name": "provider", 286 + "type": "text", 287 + "primaryKey": false, 288 + "notNull": true, 289 + "autoincrement": false 290 + } 291 + }, 292 + "indexes": {}, 293 + "foreignKeys": {}, 294 + "compositePrimaryKeys": {}, 295 + "uniqueConstraints": {}, 296 + "checkConstraints": {} 297 + }, 298 + "teal_user": { 299 + "name": "teal_user", 300 + "columns": { 301 + "did": { 302 + "name": "did", 303 + "type": "text", 304 + "primaryKey": true, 305 + "notNull": true, 306 + "autoincrement": false 307 + }, 308 + "handle": { 309 + "name": "handle", 310 + "type": "text", 311 + "primaryKey": false, 312 + "notNull": true, 313 + "autoincrement": false 314 + }, 315 + "avatar": { 316 + "name": "avatar", 317 + "type": "text", 318 + "primaryKey": false, 319 + "notNull": true, 320 + "autoincrement": false 321 + }, 322 + "bio": { 323 + "name": "bio", 324 + "type": "text", 325 + "primaryKey": false, 326 + "notNull": false, 327 + "autoincrement": false 328 + }, 329 + "created_at": { 330 + "name": "created_at", 331 + "type": "text", 332 + "primaryKey": false, 333 + "notNull": true, 334 + "autoincrement": false 335 + } 336 + }, 337 + "indexes": {}, 338 + "foreignKeys": {}, 339 + "compositePrimaryKeys": {}, 340 + "uniqueConstraints": {}, 341 + "checkConstraints": {} 342 + } 343 + }, 344 + "views": {}, 345 + "enums": {}, 346 + "_meta": { 347 + "schemas": {}, 348 + "tables": {}, 349 + "columns": {} 350 + }, 351 + "internal": { 352 + "indexes": {} 353 + } 354 + }
+14
packages/db/.drizzle/meta/_journal.json
··· 29 29 "when": 1731093709171, 30 30 "tag": "0003_sharp_medusa", 31 31 "breakpoints": true 32 + }, 33 + { 34 + "idx": 4, 35 + "version": "6", 36 + "when": 1735101894454, 37 + "tag": "0004_exotic_ironclad", 38 + "breakpoints": true 39 + }, 40 + { 41 + "idx": 5, 42 + "version": "6", 43 + "when": 1735497040757, 44 + "tag": "0005_conscious_johnny_blaze", 45 + "breakpoints": true 32 46 } 33 47 ] 34 48 }
+10 -8
packages/db/connect.ts
··· 1 1 import { drizzle } from "drizzle-orm/libsql"; 2 - import * as schema from "@teal/db/schema"; 2 + import { createClient } from "@libsql/client"; 3 + import * as schema from "./schema"; 3 4 import process from "node:process"; 4 5 import path from "node:path"; 5 6 6 - console.log("Loading SQLite file at", path.join(process.cwd(), "./db.sqlite")); 7 + console.log("Loading SQLite file at", path.join(process.cwd(), "./../../db.sqlite")); 7 8 8 - export const db = drizzle({ 9 - connection: 10 - // default is in project root / db.sqlite 9 + const client = createClient({ 10 + url: 11 11 process.env.DATABASE_URL ?? 12 - "file:" + path.join(process.cwd(), "./db.sqlite"), 13 - // doesn't seem to work? 14 - //casing: "snake_case", 12 + "file:" + path.join(process.cwd(), "./../../db.sqlite"), 13 + }); 14 + 15 + export const db = drizzle(client, { 15 16 schema: schema, 16 17 }); 17 18 19 + // If you need to export the type: 18 20 export type Database = typeof db;
+11
packages/db/drizzle.config.ts
··· 1 + import { defineConfig } from "drizzle-kit"; 2 + 3 + export default defineConfig({ 4 + dialect: "sqlite", 5 + schema: "./schema.ts", 6 + out: "./.drizzle", 7 + casing: "snake_case", 8 + dbCredentials: { 9 + url: process.env.DATABASE_URL ?? "./../../db.sqlite", 10 + }, 11 + });
+3
packages/db/index.ts
··· 1 + // barrel file :) kms 2 + export * from "./connect"; 3 + export * from "./schema";
+16 -2
packages/db/package.json
··· 1 1 { 2 2 "name": "@teal/db", 3 + "version": "0.0.0", 4 + "private": true, 5 + "main": "index.js", 3 6 "type": "module", 7 + "scripts": { 8 + "db": "drizzle-kit", 9 + "db:generate": "drizzle-kit generate", 10 + "db:migrate": "drizzle-kit migrate", 11 + "db:studio": "drizzle-kit studio" 12 + }, 4 13 "dependencies": { 5 - "drizzle-kit": "^0.27.1", 6 - "drizzle-orm": "^0.36.0" 14 + "@libsql/client": "^0.14.0", 15 + "@teal/tsconfig": "workspace:*", 16 + "drizzle-kit": "^0.30.1", 17 + "drizzle-orm": "^0.38.3" 18 + }, 19 + "devDependencies": { 20 + "@types/node": "^20.17.6" 7 21 } 8 22 }
+56 -30
packages/db/schema.ts
··· 1 - import { sqliteTable, text } from "drizzle-orm/sqlite-core"; 2 - 3 - export type DatabaseSchema = { 4 - status: Status; 5 - auth_session: AuthSession; 6 - auth_state: AuthState; 7 - }; 1 + import { 2 + numeric, 3 + sqliteTable, 4 + text, 5 + customType, 6 + integer, 7 + } from "drizzle-orm/sqlite-core"; 8 8 9 - export type Status = { 10 - uri: string; 11 - authorDid: string; 12 - status: string; 13 - createdAt: string; 14 - indexedAt: string; 15 - }; 16 - 17 - export type AuthSession = { 18 - key: string; 19 - session: AuthSessionJson; 20 - }; 21 - 22 - export type AuthState = { 23 - key: string; 24 - state: AuthStateJson; 25 - }; 26 - 27 - type AuthStateJson = string; 28 - 29 - type AuthSessionJson = string; 9 + // string array custom type 10 + const json = <TData>() => 11 + customType<{ data: TData; driverData: string }>({ 12 + dataType() { 13 + return "text"; 14 + }, 15 + toDriver(value: TData): string { 16 + return JSON.stringify(value); 17 + }, 18 + })(); 30 19 31 20 // Tables 32 21 ··· 66 55 67 56 // follow relationship 68 57 export const follow = sqliteTable("follow", { 69 - follower: text().primaryKey(), 70 - followed: text().primaryKey(), 58 + relId: text().primaryKey(), 59 + follower: text().notNull(), 60 + followed: text().notNull(), 71 61 createdAt: text().notNull(), 72 62 }); 63 + 64 + // play 65 + export const play = sqliteTable("play", { 66 + uri: text().primaryKey(), 67 + authorDid: text().notNull(), 68 + createdAt: text().notNull(), 69 + indexedAt: text().notNull(), 70 + 71 + /** The name of the track */ 72 + trackName: text().notNull(), 73 + /** The Musicbrainz ID of the track */ 74 + trackMbId: text(), 75 + /** The Musicbrainz recording ID of the track */ 76 + recordingMbId: text(), 77 + /** The length of the track in seconds */ 78 + duration: integer(), 79 + /** The name of the artist */ 80 + artistName: text().notNull(), 81 + /** Array of Musicbrainz artist IDs */ 82 + // type of string[] 83 + artistMbIds: json<string[]>(), 84 + /** The name of the release/album */ 85 + releaseName: text(), 86 + /** The Musicbrainz release ID */ 87 + releaseMbId: text(), 88 + /** The ISRC code associated with the recording */ 89 + isrc: text(), 90 + /** The URL associated with this track */ 91 + originUrl: text(), 92 + /** The base domain of the music service. e.g. music.apple.com, tidal.com, spotify.com. */ 93 + musicServiceBaseDomain: text(), 94 + /** A user-agent style string specifying the user agent. e.g. tealtracker/0.0.1b */ 95 + submissionClientAgent: text(), 96 + /** The unix timestamp of when the track was played */ 97 + playedTime: text(), 98 + });
+5 -2
packages/jetstring/package.json
··· 1 1 { 2 2 "name": "@teal/jetstring", 3 - "type": "module", 4 3 "scripts": { 5 4 "dev": "tsx watch src/index.ts | pino-pretty" 6 5 }, 7 6 "dependencies": { 7 + "@libsql/client": "^0.14.0", 8 8 "@skyware/jetstream": "^0.2.0", 9 - "@teal/db": "workspace:*" 9 + "@teal/db": "workspace:*", 10 + "@teal/lexicons": "workspace:*", 11 + "@teal/tsconfig": "workspace:*", 12 + "pino-pretty": "^13.0.0" 10 13 }, 11 14 "devDependencies": { 12 15 "tsup": "^8.3.5",
+60 -12
packages/jetstring/src/index.ts
··· 1 1 import type { Database } from "@teal/db/connect"; 2 2 import { db } from "@teal/db/connect"; 3 - import { status } from "@teal/db/schema"; 3 + import { status, play } from "@teal/db/schema"; 4 4 import { CommitCreateEvent, Jetstream } from "@skyware/jetstream"; 5 5 6 6 import { 7 7 Record as XyzStatusphereStatus, 8 8 isRecord as isStatusphereStatus, 9 - } from "@teal/lexicons/generated/server/types/xyz/statusphere/status"; 9 + } from "@teal/lexicons/src/types/xyz/statusphere/status"; 10 + 11 + import { 12 + Record as FmTealAlphaPlay, 13 + isRecord as isTealAlphaPlay, 14 + } from "@teal/lexicons/src/types/fm/teal/alpha/play"; 10 15 11 16 class Handler { 12 17 private static instance: Handler; ··· 25 30 if (isStatusphereStatus(msg) && msg.$type === "xyz.statusphere.status") { 26 31 if (record.commit.operation === "create") { 27 32 // serialize message as xyz.statusphere.status 28 - db.insert(status).values({ 29 - createdAt: new Date().getSeconds().toString(), 30 - indexedAt: new Date(record.time_us).getSeconds().toString(), 31 - status: msg.status, 32 - // the AT path 33 - uri: record.commit.rkey, 34 - authorDid: record.did, 35 - }); 33 + db.insert(status) 34 + .values({ 35 + createdAt: new Date().getTime().toString(), 36 + indexedAt: new Date(record.time_us).getTime().toString(), 37 + status: msg.status, 38 + // the AT path 39 + uri: record.commit.rkey, 40 + authorDid: record.did, 41 + }) 42 + .execute(); 36 43 } else { 37 - console.log("unsupported operation:", record.commit.operation); 44 + // TODO: sentry 45 + console.log( 46 + "unsupported operation for xyz.statusphere.status", 47 + record.commit.operation, 48 + ); 49 + } 50 + } else if (isTealAlphaPlay(msg) && msg.$type === "fm.teal.alpha.play") { 51 + if (record.commit.operation === "create") { 52 + // serialize message as fm.teal.alpha.play 53 + db.insert(play) 54 + .values({ 55 + createdAt: new Date().getTime().toString(), 56 + indexedAt: new Date(record.time_us).getTime().toString(), 57 + // the AT path 58 + uri: record.commit.rkey, 59 + authorDid: record.did, 60 + 61 + artistName: msg.artistName, 62 + trackName: msg.trackName, 63 + artistMbIds: msg.artistMbIds || [], 64 + trackMbId: msg.trackMbId || "", 65 + duration: msg.duration || null, 66 + isrc: msg.isrc || null, 67 + musicServiceBaseDomain: msg.musicServiceBaseDomain || "local", 68 + originUrl: msg.originUrl || null, 69 + playedTime: msg.playedTime ? msg.playedTime.toString() : undefined, 70 + recordingMbId: msg.recordingMbId || null, 71 + releaseMbId: msg.releaseMbId || null, 72 + releaseName: msg.releaseName || null, 73 + submissionClientAgent: 74 + msg.submissionClientAgent || "manual/unknown", 75 + }) 76 + .execute(); 77 + } else { 78 + // TODO: sentry 79 + console.log( 80 + "unsupported operation for fm.teal.alpha.play", 81 + record.commit.operation, 82 + ); 38 83 } 39 84 } else { 40 85 console.log("Unknown message type:", msg_type); ··· 110 155 // Main function to run the application 111 156 async function main() { 112 157 try { 113 - const streamer = Streamer.getInstance(["xyz.statusphere.status"]); 158 + const streamer = Streamer.getInstance([ 159 + "xyz.statusphere.status", 160 + "fm.teal.alpha.play", 161 + ]); 114 162 await streamer.start(); 115 163 116 164 // Keep the process running
packages/lexicons/generated/server/index.ts packages/lexicons/src/index.ts
+2 -2
packages/lexicons/generated/server/lexicons.ts packages/lexicons/src/lexicons.ts
··· 310 310 musicServiceBaseDomain: { 311 311 type: 'string', 312 312 description: 313 - 'The base domain of the music service. e.g. music.apple.com, tidal.com, spotify.com.', 313 + "The base domain of the music service. e.g. music.apple.com, tidal.com, spotify.com. Defaults to 'local' if not provided.", 314 314 }, 315 315 submissionClientAgent: { 316 316 type: 'string', 317 317 maxLength: 256, 318 318 maxGraphemes: 2560, 319 319 description: 320 - 'A user-agent style string specifying the user agent. e.g. tealtracker/0.0.1b', 320 + "A user-agent style string specifying the user agent. e.g. tealtracker/0.0.1b (Linux; Android 13; SM-A715F). Defaults to 'manual/unknown' if not provided.", 321 321 }, 322 322 playedTime: { 323 323 type: 'string',
packages/lexicons/generated/server/types/app/bsky/actor/profile.ts packages/lexicons/src/types/app/bsky/actor/profile.ts
packages/lexicons/generated/server/types/app/bsky/richtext/facet.ts packages/lexicons/src/types/app/bsky/richtext/facet.ts
packages/lexicons/generated/server/types/fm/teal/alpha/actor/profile.ts packages/lexicons/src/types/fm/teal/alpha/actor/profile.ts
packages/lexicons/generated/server/types/fm/teal/alpha/actor/status.ts packages/lexicons/src/types/fm/teal/alpha/actor/status.ts
+2 -2
packages/lexicons/generated/server/types/fm/teal/alpha/play.ts packages/lexicons/src/types/fm/teal/alpha/play.ts
··· 27 27 isrc?: string 28 28 /** The URL associated with this track */ 29 29 originUrl?: string 30 - /** The base domain of the music service. e.g. music.apple.com, tidal.com, spotify.com. */ 30 + /** The base domain of the music service. e.g. music.apple.com, tidal.com, spotify.com. Defaults to 'local' if not provided. */ 31 31 musicServiceBaseDomain?: string 32 - /** A user-agent style string specifying the user agent. e.g. tealtracker/0.0.1b */ 32 + /** A user-agent style string specifying the user agent. e.g. tealtracker/0.0.1b (Linux; Android 13; SM-A715F). Defaults to 'manual/unknown' if not provided. */ 33 33 submissionClientAgent?: string 34 34 /** The unix timestamp of when the track was played */ 35 35 playedTime?: string
packages/lexicons/generated/server/types/xyz/statusphere/status.ts packages/lexicons/src/types/xyz/statusphere/status.ts
packages/lexicons/generated/server/util.ts packages/lexicons/src/util.ts
packages/lexicons/index.ts

This is a binary file and will not be displayed.

+7 -1
packages/lexicons/package.json
··· 1 1 { 2 2 "name": "@teal/lexicons", 3 3 "type": "module", 4 + "main": "./index.ts", 4 5 "dependencies": { 6 + "@atproto/lex-cli": "^0.5.4", 5 7 "@atproto/lexicon": "^0.4.2", 6 - "@atproto/xrpc-server": "^0.6.4" 8 + "@atproto/xrpc-server": "^0.7.4", 9 + "@teal/tsconfig": "workspace:*" 10 + }, 11 + "scripts": { 12 + "lex:gen-server": "lex gen-server ./src ./real/*" 7 13 } 8 14 }
packages/lexicons/src/app.bsky.actor.profile.json packages/lexicons/real/app.bsky.actor.profile.json
packages/lexicons/src/app.bsky.richtext.facet.json packages/lexicons/real/app.bsky.richtext.facet.json
packages/lexicons/src/fm.teal.alpha.actor.profile.json packages/lexicons/real/fm.teal.alpha.actor.profile.json
packages/lexicons/src/fm.teal.alpha.actor.status.json packages/lexicons/real/fm.teal.alpha.actor.status.json
+2 -2
packages/lexicons/src/fm.teal.alpha.play.json packages/lexicons/real/fm.teal.alpha.play.json
··· 64 64 }, 65 65 "musicServiceBaseDomain": { 66 66 "type": "string", 67 - "description": "The base domain of the music service. e.g. music.apple.com, tidal.com, spotify.com." 67 + "description": "The base domain of the music service. e.g. music.apple.com, tidal.com, spotify.com. Defaults to 'local' if not provided." 68 68 }, 69 69 "submissionClientAgent": { 70 70 "type": "string", 71 71 "maxLength": 256, 72 72 "maxGraphemes": 2560, 73 - "description": "A user-agent style string specifying the user agent. e.g. tealtracker/0.0.1b" 73 + "description": "A user-agent style string specifying the user agent. e.g. tealtracker/0.0.1b (Linux; Android 13; SM-A715F). Defaults to 'manual/unknown' if not provided." 74 74 }, 75 75 "playedTime": { 76 76 "type": "string",
packages/lexicons/src/xyz.statusphere.status.json packages/lexicons/real/xyz.statusphere.status.json
+1 -1
packages/lexicons/tsconfig.json
··· 1 1 { 2 2 "extends": "@teal/tsconfig/base.json", 3 - "include": ["./generated/**/*.ts"] 3 + "include": ["./src/**/*.ts"] 4 4 }
+5 -1
packages/tsconfig/package.json
··· 1 1 { 2 2 "name": "@teal/tsconfig", 3 - "type": "module" 3 + "version": "0.0.0", 4 + "private": true, 5 + "publishConfig": { 6 + "access": "public" 7 + } 4 8 }
+50 -21
pnpm-lock.yaml
··· 8 8 9 9 .: 10 10 devDependencies: 11 + '@types/node': 12 + specifier: ^20.17.10 13 + version: 20.17.10 11 14 biome: 12 15 specifier: ^0.3.3 13 16 version: 0.3.3 17 + rimraf: 18 + specifier: ^6.0.1 19 + version: 6.0.1 14 20 turbo: 15 21 specifier: ^2.3.3 16 22 version: 2.3.3 ··· 39 45 specifier: ^0.3.0 40 46 version: 0.3.1 41 47 '@atproto/xrpc-server': 42 - specifier: ^0.6.4 43 - version: 0.6.4 48 + specifier: ^0.7.4 49 + version: 0.7.4 44 50 '@braintree/sanitize-url': 45 51 specifier: ^7.1.0 46 52 version: 7.1.0 ··· 82 88 version: 4.7.0 83 89 devDependencies: 84 90 '@atproto/lex-cli': 85 - specifier: ^0.4.1 86 - version: 0.4.1 91 + specifier: ^0.5.4 92 + version: 0.5.4 87 93 '@teal/tsconfig': 88 94 specifier: workspace:* 89 95 version: link:../../packages/tsconfig ··· 91 97 specifier: ^20.17.6 92 98 version: 20.17.9 93 99 drizzle-kit: 94 - specifier: ^0.27.2 95 - version: 0.27.2 100 + specifier: ^0.30.1 101 + version: 0.30.1 96 102 pino-pretty: 97 - specifier: ^11.3.0 98 - version: 11.3.0 103 + specifier: ^13.0.0 104 + version: 13.0.0 99 105 rimraf: 100 106 specifier: ^6.0.1 101 107 version: 6.0.1 ··· 280 286 281 287 packages/db: 282 288 dependencies: 289 + '@libsql/client': 290 + specifier: ^0.14.0 291 + version: 0.14.0 292 + '@teal/tsconfig': 293 + specifier: workspace:* 294 + version: link:../tsconfig 283 295 drizzle-kit: 284 - specifier: ^0.27.1 285 - version: 0.27.2 296 + specifier: ^0.30.1 297 + version: 0.30.1 286 298 drizzle-orm: 287 299 specifier: ^0.36.0 288 300 version: 0.36.4(@libsql/client@0.14.0)(@types/react@18.3.12)(expo-sqlite@15.0.3(expo@52.0.21(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@expo/metro-runtime@4.0.0(react-native@0.76.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1)))(react-native@0.76.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react-native@0.76.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@types/react@18.3.12)(react@18.3.1))(react@18.3.1))(react@18.3.1) 289 301 290 302 packages/jetstring: 291 303 dependencies: 304 + '@libsql/client': 305 + specifier: ^0.14.0 306 + version: 0.14.0 292 307 '@skyware/jetstream': 293 308 specifier: ^0.2.0 294 309 version: 0.2.1(@atcute/client@2.0.6) 295 310 '@teal/db': 296 311 specifier: workspace:* 297 312 version: link:../db 313 + '@teal/lexicons': 314 + specifier: workspace:* 315 + version: link:../lexicons 316 + '@teal/tsconfig': 317 + specifier: workspace:* 318 + version: link:../tsconfig 319 + pino-pretty: 320 + specifier: ^13.0.0 321 + version: 13.0.0 298 322 devDependencies: 299 323 tsup: 300 324 specifier: ^8.3.5 ··· 308 332 309 333 packages/lexicons: 310 334 dependencies: 335 + '@atproto/lex-cli': 336 + specifier: ^0.5.4 337 + version: 0.5.4 311 338 '@atproto/lexicon': 312 339 specifier: ^0.4.2 313 340 version: 0.4.3 314 341 '@atproto/xrpc-server': 315 - specifier: ^0.6.4 316 - version: 0.6.4 342 + specifier: ^0.7.4 343 + version: 0.7.4 344 + '@teal/tsconfig': 345 + specifier: workspace:* 346 + version: link:../tsconfig 317 347 318 348 packages/matcher: {} 319 349 ··· 399 429 '@atproto/common@0.4.4': 400 430 resolution: {integrity: sha512-58tMbn6A1Zu296s/l3uIj8z9d7IRHpZvLOfsFRikaQaYrzhJpL2aPY4uFQ8GJcxnsxeUnxBCrQz9we5jVVJI5Q==} 401 431 432 + '@atproto/common@0.4.5': 433 + resolution: {integrity: sha512-LFAGqHcxCI5+b31Xgk+VQQtZU258iGPpHJzNeHVcdh6teIKZi4C2l6YV+m+3CEz+yYcfP7jjUmgqesx7l9Arsg==} 434 + 402 435 '@atproto/crypto@0.4.2': 403 436 resolution: {integrity: sha512-aeOfPQYCDbhn2hV06oBF2KXrWjf/BK4yL8lfANJKSmKl3tKWCkiW/moi643rUXXxSE72KtWtQeqvNFYnnFJ0ig==} 404 437 ··· 417 450 '@atproto/jwk@0.1.1': 418 451 resolution: {integrity: sha512-6h/bj1APUk7QcV9t/oA6+9DB5NZx9SZru9x+/pV5oHFI9Xz4ZuM5+dq1PfsJV54pZyqdnZ6W6M717cxoC7q7og==} 419 452 420 - '@atproto/lex-cli@0.4.1': 421 - resolution: {integrity: sha512-QP9mE8MYzXR2ydhCBb/mtGqKZjqpffqcpZCr7JM4mFOZPvXV8k7OqVP1h+T94JB/tGcGPhB750S6tqUH9VRLVg==} 453 + '@atproto/lex-cli@0.5.4': 454 + resolution: {integrity: sha512-mNEPeQLXl3iCXPO/FSo0BTfP00lx+9xEQpf9LEpDuKA6WCWjIB7WHzU2VLk26NSftzH3sf6zf+A2yZ+WWRbYpw==} 422 455 hasBin: true 423 456 424 457 '@atproto/lex-cli@0.5.4': ··· 455 488 '@atproto/syntax@0.3.1': 456 489 resolution: {integrity: sha512-fzW0Mg1QUOVCWUD3RgEsDt6d1OZ6DdFmbKcDdbzUfh0t4rhtRAC05KbZYmxuMPWDAiJ4BbbQ5dkAc/mNypMXkw==} 457 490 458 - '@atproto/xrpc-server@0.6.4': 459 - resolution: {integrity: sha512-AL9okOTpJpxh3wJjT27RiPkp2IWIxDPCyyvuO1SJu0E9URGfWZL26SlT7/IR/tadZTJezr5+ZNuxhV0uhI+s1A==} 460 - 461 - '@atproto/xrpc-server@0.7.3': 462 - resolution: {integrity: sha512-x0qegkN6snrbXJO3v9h2kuh9e90g6ZZkDXv3COiraGS3yRTzIm6i4bMvDSfCI50+0xCNtPKOkpn8taRoRgkyiw==} 491 + '@atproto/xrpc-server@0.7.4': 492 + resolution: {integrity: sha512-MrAwxfJBQm/kCol3D8qc+vpQzBMzLqvtUbauSSfVVJ10PlGtxg4LlXqcjkAuhrjyrqp3dQH9LHuhDpgVQK+G3w==} 463 493 464 494 '@atproto/xrpc@0.6.4': 465 495 resolution: {integrity: sha512-9ZAJ8nsXTqC4XFyS0E1Wlg7bAvonhXQNQ3Ocs1L1LIwFLXvsw/4fNpIHXxvXvqTCVeyHLbImOnE9UiO1c/qIYA==} ··· 14276 14306 dependencies: 14277 14307 split2: 4.2.0 14278 14308 14279 - pino-pretty@11.3.0: 14309 + pino-pretty@13.0.0: 14280 14310 dependencies: 14281 14311 colorette: 2.0.20 14282 14312 dateformat: 4.6.3 ··· 14288 14318 on-exit-leak-free: 2.1.2 14289 14319 pino-abstract-transport: 2.0.0 14290 14320 pump: 3.0.2 14291 - readable-stream: 4.5.2 14292 14321 secure-json-parse: 2.7.0 14293 14322 sonic-boom: 4.2.0 14294 14323 strip-json-comments: 3.1.1
+3
turbo.json
··· 14 14 "dev": { 15 15 "persistent": true, 16 16 "cache": false 17 + }, 18 + "db:migrate": { 19 + "cache": false 17 20 } 18 21 } 19 22 }