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

first commit!

+2 -1
.gitignore
··· 32 32 33 33 # vercel 34 34 .vercel 35 + .turbo 35 36 36 37 **/*.trace 37 38 **/*.zip ··· 39 40 **/*.tgz 40 41 **/*.log 41 42 package-lock.json 42 - **/*.bun 43 + **/*.bun
+17
apps/aqua/.drizzle/0000_same_maelstrom.sql
··· 1 + CREATE TABLE `auth_session` ( 2 + `key` text PRIMARY KEY NOT NULL, 3 + `session` text NOT NULL 4 + ); 5 + --> statement-breakpoint 6 + CREATE TABLE `auth_state` ( 7 + `key` text PRIMARY KEY NOT NULL, 8 + `state` text NOT NULL 9 + ); 10 + --> statement-breakpoint 11 + CREATE TABLE `status` ( 12 + `uri` text PRIMARY KEY NOT NULL, 13 + `authorDid` text NOT NULL, 14 + `status` text NOT NULL, 15 + `createdAt` text NOT NULL, 16 + `indexedAt` text NOT NULL 17 + );
+111
apps/aqua/.drizzle/meta/0000_snapshot.json
··· 1 + { 2 + "version": "6", 3 + "dialect": "sqlite", 4 + "id": "417cce08-b23b-4a9f-ad7e-e96215e0fb38", 5 + "prevId": "00000000-0000-0000-0000-000000000000", 6 + "tables": { 7 + "auth_session": { 8 + "name": "auth_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 + "status": { 56 + "name": "status", 57 + "columns": { 58 + "uri": { 59 + "name": "uri", 60 + "type": "text", 61 + "primaryKey": true, 62 + "notNull": true, 63 + "autoincrement": false 64 + }, 65 + "authorDid": { 66 + "name": "authorDid", 67 + "type": "text", 68 + "primaryKey": false, 69 + "notNull": true, 70 + "autoincrement": false 71 + }, 72 + "status": { 73 + "name": "status", 74 + "type": "text", 75 + "primaryKey": false, 76 + "notNull": true, 77 + "autoincrement": false 78 + }, 79 + "createdAt": { 80 + "name": "createdAt", 81 + "type": "text", 82 + "primaryKey": false, 83 + "notNull": true, 84 + "autoincrement": false 85 + }, 86 + "indexedAt": { 87 + "name": "indexedAt", 88 + "type": "text", 89 + "primaryKey": false, 90 + "notNull": true, 91 + "autoincrement": false 92 + } 93 + }, 94 + "indexes": {}, 95 + "foreignKeys": {}, 96 + "compositePrimaryKeys": {}, 97 + "uniqueConstraints": {}, 98 + "checkConstraints": {} 99 + } 100 + }, 101 + "views": {}, 102 + "enums": {}, 103 + "_meta": { 104 + "schemas": {}, 105 + "tables": {}, 106 + "columns": {} 107 + }, 108 + "internal": { 109 + "indexes": {} 110 + } 111 + }
+13
apps/aqua/.drizzle/meta/_journal.json
··· 1 + { 2 + "version": "7", 3 + "dialect": "sqlite", 4 + "entries": [ 5 + { 6 + "idx": 0, 7 + "version": "6", 8 + "when": 1729467718506, 9 + "tag": "0000_same_maelstrom", 10 + "breakpoints": true 11 + } 12 + ] 13 + }
+11
apps/aqua/.env
··· 1 + PRIVATE_KEY_1={"kty":"EC","d":"8rx-D2vaik7FgRUaMeK_M8yZQ57J5NFs4MP6300_gek","use":"sig","crv":"P-256","kid":"44J2ZYr_4O3wp1B8GSRPvHMb7Cf506Nss3ISOplRx9I","x":"f9RLs9sqKyL38dPKsQaX-P_qTHVnNRCXuzkjbPvh7Ls","y":"ZnH5GuTAl5TTb-hZzsVgf1kUl4OB6qCS0PmM4_SPXvw","alg":"ES256"} 2 + PRIVATE_KEY_2={"kty":"EC","d":"5Rk8UuCz-chUX_OZ4WgB7lb3OELn-xlGEedk4P-qY_M","use":"sig","crv":"P-256","kid":"kzDrzoJdNtK3bfTiuJYQZSk_Z7nsZqEpzqYSqHVBN_Q","x":"WuMNQ3slMhmvUJze-q4pxmC_Xqu5MkpkD3eSh1dPDBs","y":"0CS96lObk2UWnRbbrhQQDbduyZ_A4zKZtwSQTfqVkcU","alg":"ES256"} 3 + PRIVATE_KEY_3={"kty":"EC","d":"GvpzAoGaHCG3OFe8qqi8FRs3WShGvS8OAOhjcN2vyuQ","use":"sig","crv":"P-256","kid":"y0HFLgCqOSwfbRJdO48dM8prLrLrT-qxNs_UrdvrbNQ","x":"VJ13t663tWZa67wUNQw26iU9iatIg4ZIklNKOrqMiYw","y":"Fqyc7qiOfwaYDXO259G8T66Wg2Kf_WLEjyi0ZenX2pI","alg":"ES256"} 4 + NODE_ENV=development # Options: development, production 5 + PORT=3000 # The port your server will listen on 6 + HOST=localhost # Hostname for the server 7 + PUBLIC_URL= # Set when deployed publicly, e.g. "https://mysite.com". Informs OAuth client id. 8 + DB_PATH=:memory: # The SQLite database path. Leave as ":memory:" to use a temporary in-memory database. 9 + # Secrets 10 + # Must set this in production. May be generated with `openssl rand -base64 33`=undefined 11 + # COOKIE_SECRET=""
apps/aqua/bun.lockb

This is a binary file and will not be displayed.

apps/aqua/db.sqlite

This is a binary file and will not be displayed.

+55
apps/aqua/db/schema.ts
··· 1 + import { defineConfig } from "drizzle-kit"; 2 + import { sqliteTable, text } from "drizzle-orm/sqlite-core"; 3 + 4 + export default defineConfig({ 5 + dialect: "sqlite", // 'mysql' | 'postgresql' | 'sqlite' | 'turso' 6 + schema: "./src/db/schema.ts", 7 + }); 8 + 9 + export type DatabaseSchema = { 10 + status: Status; 11 + auth_session: AuthSession; 12 + auth_state: AuthState; 13 + }; 14 + 15 + export type Status = { 16 + uri: string; 17 + authorDid: string; 18 + status: string; 19 + createdAt: string; 20 + indexedAt: string; 21 + }; 22 + 23 + export type AuthSession = { 24 + key: string; 25 + session: AuthSessionJson; 26 + }; 27 + 28 + export type AuthState = { 29 + key: string; 30 + state: AuthStateJson; 31 + }; 32 + 33 + type AuthStateJson = string; 34 + 35 + type AuthSessionJson = string; 36 + 37 + // Tables 38 + 39 + export const status = sqliteTable("status", { 40 + uri: text().primaryKey(), 41 + authorDid: text().notNull(), 42 + status: text().notNull(), 43 + createdAt: text().notNull(), 44 + indexedAt: text().notNull(), 45 + }); 46 + 47 + export const authSession = sqliteTable("auth_session", { 48 + key: text().primaryKey(), 49 + session: text().notNull(), 50 + }); 51 + 52 + export const authState = sqliteTable("auth_state", { 53 + key: text().primaryKey(), 54 + state: text().notNull(), 55 + });
+12
apps/aqua/drizzle.config.ts
··· 1 + import { defineConfig } from "drizzle-kit"; 2 + import process from "node:process"; 3 + 4 + export default defineConfig({ 5 + dialect: "sqlite", 6 + schema: "./db/schema.ts", 7 + out: "./.drizzle", 8 + casing: "snake_case", 9 + dbCredentials: { 10 + url: process.env.DATABASE_URL ?? "./db.sqlite", 11 + }, 12 + });
+60
apps/aqua/package.json
··· 1 + { 2 + "name": "@teal/aqua", 3 + "type": "module", 4 + "version": "1.0.50", 5 + "main": "index.ts", 6 + "packageManager": "bun@1.0.0+", 7 + "scripts": { 8 + "dev": "tsx watch --clear-screen=false src/index.ts | pino-pretty", 9 + "bun:dev": "bun run src/index.ts | pino-pretty", 10 + "build": "tsup", 11 + "start": "node dist/index.js", 12 + "lexgen": "lex gen-server ./src/lexicon ./lexicons/*", 13 + "clean": "rimraf dist coverage", 14 + "check-types": "tsc --noEmit", 15 + "db:migrate": "drizzle-kit migrate", 16 + "db:seed": "drizzle-kit seed", 17 + "db:studio": "drizzle-kit studio" 18 + }, 19 + "dependencies": { 20 + "@atproto/api": "^0.13.14", 21 + "@atproto/common": "^0.4.4", 22 + "@atproto/identity": "^0.4.2", 23 + "@atproto/lexicon": "^0.4.2", 24 + "@atproto/oauth-client-node": "^0.1.4", 25 + "@atproto/sync": "^0.1.4", 26 + "@atproto/syntax": "^0.3.0", 27 + "@atproto/xrpc-server": "^0.6.4", 28 + "@hono/node-server": "^1.13.4", 29 + "@libsql/client": "^0.14.0", 30 + "dotenv": "^16.4.5", 31 + "drizzle-orm": "^0.35.3", 32 + "envalid": "^8.0.0", 33 + "hono": "^4.6.8", 34 + "jose": "^5.9.6", 35 + "pino": "^9.5.0", 36 + "turbo": "^2.2.3", 37 + "uhtml": "^4.5.11" 38 + }, 39 + "devDependencies": { 40 + "@teal/tsconfig": "*", 41 + "@atproto/lex-cli": "^0.4.1", 42 + "@types/node": "^20.17.5", 43 + "drizzle-kit": "^0.26.2", 44 + "pino-pretty": "^11.3.0", 45 + "rimraf": "^6.0.1", 46 + "tsup": "^8.3.5", 47 + "tsx": "^4.19.2", 48 + "typescript": "^5.6.3" 49 + }, 50 + "tsup": { 51 + "entry": [ 52 + "src", 53 + "!src/**/__tests__/**", 54 + "!src/**/*.test.*" 55 + ], 56 + "splitting": false, 57 + "sourcemap": true, 58 + "clean": true 59 + } 60 + }
+31
apps/aqua/scripts/generateJWKS.ts
··· 1 + import { generateKeyPair, exportJWK } from "jose"; 2 + import { randomBytes } from "crypto"; 3 + 4 + // Function to create JWKS and append to .env file 5 + async function createJWKS() { 6 + const jwks: any = { keys: [] }; 7 + 8 + for (let i = 0; i < 3; i++) { 9 + // Generate a new key pair 10 + // const alg = "ES256"; 11 + // const { publicKey, privateKey } = await generateKeyPair(alg, { 12 + // extractable: true, 13 + // }); 14 + // // Export the key pair as a JWK 15 + // const jwk = await exportJWK(privateKey); 16 + // jwk.kid = randomBytes(16).toString("hex"); 17 + // jwk.alg = alg; 18 + // jwks.keys.push(jwk); 19 + 20 + // TODO: replace this ASAP if in prod 21 + let res = await fetch( 22 + "https://mkjwk.org/jwk/ec?alg=ES256&use=sig&gen=sha256&crv=P-256", 23 + ); 24 + let jwk_res = await res.json(); 25 + let jwk = jwk_res.jwk; 26 + console.log("PRIVATE_KEY_" + (i + 1) + "=" + JSON.stringify(jwk)); 27 + } 28 + } 29 + 30 + // Execute the function 31 + createJWKS().catch(console.error);
+28
apps/aqua/src/auth/client.ts
··· 1 + import { NodeOAuthClient } from "@atproto/oauth-client-node"; 2 + import type { Database } from "@/db"; 3 + import { env } from "@/lib/env"; 4 + import { SessionStore, StateStore } from "./storage"; 5 + 6 + export const createClient = async (db: Database) => { 7 + const publicUrl = env.PUBLIC_URL; 8 + const url = publicUrl || `http://127.0.0.1:${env.PORT}`; 9 + const enc = encodeURIComponent; 10 + return new NodeOAuthClient({ 11 + clientMetadata: { 12 + client_name: "Teal", 13 + client_id: publicUrl 14 + ? `${url}/client-metadata.json` 15 + : `http://localhost?redirect_uri=${enc(`${url}/oauth/callback`)}&scope=${enc("atproto transition:generic")}`, 16 + client_uri: url, 17 + redirect_uris: [`${url}/oauth/callback`], 18 + scope: "atproto transition:generic", 19 + grant_types: ["authorization_code", "refresh_token"], 20 + response_types: ["code"], 21 + application_type: "web", 22 + token_endpoint_auth_method: "none", 23 + dpop_bound_access_tokens: true, 24 + }, 25 + stateStore: new StateStore(db), 26 + sessionStore: new SessionStore(db), 27 + }); 28 + };
+82
apps/aqua/src/auth/router.ts
··· 1 + import { createClient } from "./client"; 2 + import { db } from "@/db"; 3 + import { EnvWithCtx, TealContext } from "@/ctx"; 4 + import { authSession } from "../../db/schema"; 5 + import { Hono } from "hono"; 6 + 7 + interface LoginBody { 8 + handle?: string; 9 + } 10 + 11 + export const login = async (c: TealContext) => { 12 + let body: LoginBody = await c.req.json(); 13 + // Initiate the OAuth flow 14 + const auth = await createClient(db); 15 + if (!body) return Response.json({ error: "Could not parse body" }); 16 + // handle is the handle of the user 17 + if (!body.handle && body.handle === undefined) 18 + return Response.json({ error: "Handle is required" }); 19 + try { 20 + const url = await auth.authorize(body.handle, { 21 + scope: "atproto transition:generic", 22 + state: crypto.randomUUID(), 23 + }); 24 + return Response.json({ redirect_to: url }); 25 + } catch (e) { 26 + console.error(e); 27 + return Response.json({ error: "Could not authorize user" }); 28 + } 29 + }; 30 + 31 + export async function loginGet(handle: string) { 32 + // Initiate the OAuth flow 33 + const auth = await createClient(db); 34 + try { 35 + const url = await auth.authorize("natalie.sh", { 36 + scope: "atproto transition:generic", 37 + }); 38 + return Response.redirect(url); 39 + } catch (e) { 40 + console.error(e); 41 + return Response.json({ error: "Could not authorize user" }); 42 + } 43 + } 44 + 45 + export async function callback(c: TealContext) { 46 + // Initiate the OAuth flow 47 + const auth = await createClient(db); 48 + try { 49 + const honoParams = c.req.query(); 50 + console.log("params", honoParams); 51 + const params = new URLSearchParams(honoParams); 52 + const cb = await auth.callback(params); 53 + 54 + // generate a session key (random) 55 + 56 + const sessionKey = crypto.randomUUID(); 57 + 58 + // insert in session table, return data from cb 59 + 60 + c.var.db.insert(authSession).values({ 61 + key: sessionKey, 62 + session: JSON.stringify(cb.session), 63 + }); 64 + 65 + return Response.json(cb); 66 + } catch (e) { 67 + console.error(e); 68 + return Response.json({ error: "Could not authorize user" }); 69 + } 70 + } 71 + 72 + const app = new Hono<EnvWithCtx>(); 73 + 74 + app.get("/login", async (c) => loginGet("natalie.sh")); 75 + 76 + app.post("/login", async (c) => login(c)); 77 + 78 + app.get("/callback", async (c) => callback(c)); 79 + 80 + export const getAuthRouter = () => { 81 + return app; 82 + };
+85
apps/aqua/src/auth/storage.ts
··· 1 + import type { 2 + NodeSavedSession, 3 + NodeSavedSessionStore, 4 + NodeSavedState, 5 + NodeSavedStateStore, 6 + } from "@atproto/oauth-client-node"; 7 + import type { Database } from "@/db"; 8 + import { authSession, authState } from "../../db/schema"; 9 + import { eq } from "drizzle-orm"; 10 + 11 + export class StateStore implements NodeSavedStateStore { 12 + constructor(private db: Database) {} 13 + async get(key: string): Promise<NodeSavedState | undefined> { 14 + const result = await this.db 15 + .select() 16 + .from(authState) 17 + .where(eq(authState.key, key)) 18 + .limit(1) 19 + .execute(); 20 + // .selectFrom("auth_state") 21 + // .selectAll() 22 + // .where("key", "=", key) 23 + // .executeTakeFirst(); 24 + console.log("getting state", key, result); 25 + if (!result[0]) return; 26 + return JSON.parse(result[0].state) as NodeSavedState; 27 + } 28 + async set(key: string, val: NodeSavedState) { 29 + const state = JSON.stringify(val); 30 + console.log("inserting state", key, state); 31 + await this.db 32 + .insert(authState) 33 + .values({ key, state }) 34 + .onConflictDoUpdate({ 35 + set: { state: state }, 36 + target: authState.key, 37 + }) 38 + .execute(); 39 + // .insertInto("auth_state") 40 + // .values({ key, state }) 41 + // .onConflict((oc) => oc.doUpdateSet({ state })) 42 + // .execute(); 43 + } 44 + async del(key: string) { 45 + await this.db.delete(authState).where(eq(authState.key, key)).execute(); 46 + //.deleteFrom("auth_state").where("key", "=", key).execute(); 47 + } 48 + } 49 + 50 + export class SessionStore implements NodeSavedSessionStore { 51 + constructor(private db: Database) {} 52 + async get(key: string): Promise<NodeSavedSession | undefined> { 53 + const result = await this.db 54 + .select() 55 + .from(authSession) 56 + .where(eq(authSession.key, key)) 57 + .limit(1) 58 + .all(); 59 + // .selectFrom("auth_session") 60 + // .selectAll() 61 + // .where("key", "=", key) 62 + // .executeTakeFirst(); 63 + if (!result[0]) return; 64 + return JSON.parse(result[0].session) as NodeSavedSession; 65 + } 66 + async set(key: string, val: NodeSavedSession) { 67 + const session = JSON.stringify(val); 68 + await this.db 69 + .insert(authSession) 70 + .values({ key, session }) 71 + .onConflictDoUpdate({ 72 + set: { session: session }, 73 + target: authSession.key, 74 + }) 75 + .execute(); 76 + // .insertInto("auth_session") 77 + // .values({ key, session }) 78 + // .onConflict((oc) => oc.doUpdateSet({ session })) 79 + // .execute(); 80 + } 81 + async del(key: string) { 82 + await this.db.delete(authSession).where(eq(authSession.key, key)).execute(); 83 + //.deleteFrom("auth_session").where("key", "=", key).execute(); 84 + } 85 + }
+36
apps/aqua/src/ctx.ts
··· 1 + import { NodeOAuthClient } from "@atproto/oauth-client-node"; 2 + import { Client } from "@libsql/client/."; 3 + import { LibSQLDatabase } from "drizzle-orm/libsql"; 4 + import { Context, Next } from "hono"; 5 + import { createClient } from "./auth/client"; 6 + import { Logger } from "pino"; 7 + 8 + export type TealContext = Context<EnvWithCtx, any, any>; 9 + 10 + export type EnvWithCtx = { 11 + Variables: Ctx; 12 + }; 13 + 14 + export type Ctx = { 15 + auth: NodeOAuthClient; 16 + db: LibSQLDatabase<typeof import("/Users/natalie/Code/teal/db/schema")> & { 17 + $client: Client; 18 + }; 19 + logger: Logger<never, boolean>; 20 + }; 21 + 22 + export const setupContext = async ( 23 + c: TealContext, 24 + db: LibSQLDatabase<typeof import("/Users/natalie/Code/teal/db/schema")> & { 25 + $client: Client; 26 + }, 27 + logger: Logger<never, boolean>, 28 + next: Next, 29 + ) => { 30 + const auth = await createClient(db); 31 + 32 + c.set("db", db); 33 + c.set("auth", auth); 34 + c.set("logger", logger); 35 + await next(); 36 + };
+12
apps/aqua/src/db.ts
··· 1 + import { drizzle } from "drizzle-orm/libsql"; 2 + import * as schema from "../db/schema"; 3 + import process from "node:process"; 4 + 5 + export const db = drizzle({ 6 + connection: process.env.DATABASE_URL ?? "file:./db.sqlite", 7 + // doesn't seem to work? 8 + //casing: "snake_case", 9 + schema: schema, 10 + }); 11 + 12 + export type Database = typeof db;
+42
apps/aqua/src/index.ts
··· 1 + import { serve as serveNode } from "@hono/node-server"; 2 + import { Hono } from "hono"; 3 + import { db } from "@/db"; 4 + import { getAuthRouter, loginGet } from "./auth/router"; 5 + import pino from "pino"; 6 + import { EnvWithCtx, setupContext } from "./ctx"; 7 + import { env } from "./lib/env"; 8 + 9 + const logger = pino({ name: "server start" }); 10 + 11 + const app = new Hono<EnvWithCtx>(); 12 + 13 + app.use((c, next) => setupContext(c, db, logger, next)); 14 + 15 + app.get("/", (c) => c.text("Hono meets Node.js")); 16 + 17 + app.get("/info", async (c) => { 18 + const result = await db.query.status.findFirst().execute(); 19 + console.log("result", result); 20 + return c.json(result); 21 + }); 22 + 23 + app.route("/oauth", getAuthRouter()); 24 + 25 + // const run = async () => { 26 + // serve( 27 + // { 28 + // fetch: app.fetch, 29 + // port: env.PORT, 30 + // hostname: env.HOST, 31 + // }, 32 + // (info) => { 33 + // console.log( 34 + // `Listening on ${info.address == "::1" ? "http://localhost" : info.address}:${info.port} (${info.family})`, 35 + // ); 36 + // }, 37 + // ); 38 + // }; 39 + 40 + // run(); 41 + 42 + export default app;
+99
apps/aqua/src/lexicon/index.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { 5 + createServer as createXrpcServer, 6 + Server as XrpcServer, 7 + Options as XrpcOptions, 8 + AuthVerifier, 9 + StreamAuthVerifier, 10 + } from '@atproto/xrpc-server' 11 + import { schemas } from './lexicons' 12 + 13 + export function createServer(options?: XrpcOptions): Server { 14 + return new Server(options) 15 + } 16 + 17 + export class Server { 18 + xrpc: XrpcServer 19 + app: AppNS 20 + xyz: XyzNS 21 + 22 + constructor(options?: XrpcOptions) { 23 + this.xrpc = createXrpcServer(schemas, options) 24 + this.app = new AppNS(this) 25 + this.xyz = new XyzNS(this) 26 + } 27 + } 28 + 29 + export class AppNS { 30 + _server: Server 31 + bsky: AppBskyNS 32 + 33 + constructor(server: Server) { 34 + this._server = server 35 + this.bsky = new AppBskyNS(server) 36 + } 37 + } 38 + 39 + export class AppBskyNS { 40 + _server: Server 41 + actor: AppBskyActorNS 42 + 43 + constructor(server: Server) { 44 + this._server = server 45 + this.actor = new AppBskyActorNS(server) 46 + } 47 + } 48 + 49 + export class AppBskyActorNS { 50 + _server: Server 51 + 52 + constructor(server: Server) { 53 + this._server = server 54 + } 55 + } 56 + 57 + export class XyzNS { 58 + _server: Server 59 + statusphere: XyzStatusphereNS 60 + 61 + constructor(server: Server) { 62 + this._server = server 63 + this.statusphere = new XyzStatusphereNS(server) 64 + } 65 + } 66 + 67 + export class XyzStatusphereNS { 68 + _server: Server 69 + 70 + constructor(server: Server) { 71 + this._server = server 72 + } 73 + } 74 + 75 + type SharedRateLimitOpts<T> = { 76 + name: string 77 + calcKey?: (ctx: T) => string 78 + calcPoints?: (ctx: T) => number 79 + } 80 + type RouteRateLimitOpts<T> = { 81 + durationMs: number 82 + points: number 83 + calcKey?: (ctx: T) => string 84 + calcPoints?: (ctx: T) => number 85 + } 86 + type HandlerOpts = { blobLimit?: number } 87 + type HandlerRateLimitOpts<T> = SharedRateLimitOpts<T> | RouteRateLimitOpts<T> 88 + type ConfigOf<Auth, Handler, ReqCtx> = 89 + | Handler 90 + | { 91 + auth?: Auth 92 + opts?: HandlerOpts 93 + rateLimit?: HandlerRateLimitOpts<ReqCtx> | HandlerRateLimitOpts<ReqCtx>[] 94 + handler: Handler 95 + } 96 + type ExtractAuth<AV extends AuthVerifier | StreamAuthVerifier> = Extract< 97 + Awaited<ReturnType<AV>>, 98 + { credentials: unknown } 99 + >
+94
apps/aqua/src/lexicon/lexicons.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { LexiconDoc, Lexicons } from '@atproto/lexicon' 5 + 6 + export const schemaDict = { 7 + AppBskyActorProfile: { 8 + lexicon: 1, 9 + id: 'app.bsky.actor.profile', 10 + defs: { 11 + main: { 12 + type: 'record', 13 + description: 'A declaration of a Bluesky account profile.', 14 + key: 'literal:self', 15 + record: { 16 + type: 'object', 17 + properties: { 18 + displayName: { 19 + type: 'string', 20 + maxGraphemes: 64, 21 + maxLength: 640, 22 + }, 23 + description: { 24 + type: 'string', 25 + description: 'Free-form profile description text.', 26 + maxGraphemes: 256, 27 + maxLength: 2560, 28 + }, 29 + avatar: { 30 + type: 'blob', 31 + description: 32 + "Small image to be displayed next to posts from account. AKA, 'profile picture'", 33 + accept: ['image/png', 'image/jpeg'], 34 + maxSize: 1000000, 35 + }, 36 + banner: { 37 + type: 'blob', 38 + description: 39 + 'Larger horizontal image to display behind profile view.', 40 + accept: ['image/png', 'image/jpeg'], 41 + maxSize: 1000000, 42 + }, 43 + labels: { 44 + type: 'union', 45 + description: 46 + 'Self-label values, specific to the Bluesky application, on the overall account.', 47 + refs: ['lex:com.atproto.label.defs#selfLabels'], 48 + }, 49 + joinedViaStarterPack: { 50 + type: 'ref', 51 + ref: 'lex:com.atproto.repo.strongRef', 52 + }, 53 + createdAt: { 54 + type: 'string', 55 + format: 'datetime', 56 + }, 57 + }, 58 + }, 59 + }, 60 + }, 61 + }, 62 + XyzStatusphereStatus: { 63 + lexicon: 1, 64 + id: 'xyz.statusphere.status', 65 + defs: { 66 + main: { 67 + type: 'record', 68 + key: 'tid', 69 + record: { 70 + type: 'object', 71 + required: ['status', 'createdAt'], 72 + properties: { 73 + status: { 74 + type: 'string', 75 + minLength: 1, 76 + maxGraphemes: 1, 77 + maxLength: 32, 78 + }, 79 + createdAt: { 80 + type: 'string', 81 + format: 'datetime', 82 + }, 83 + }, 84 + }, 85 + }, 86 + }, 87 + }, 88 + } 89 + export const schemas: LexiconDoc[] = Object.values(schemaDict) as LexiconDoc[] 90 + export const lexicons: Lexicons = new Lexicons(schemas) 91 + export const ids = { 92 + AppBskyActorProfile: 'app.bsky.actor.profile', 93 + XyzStatusphereStatus: 'xyz.statusphere.status', 94 + }
+38
apps/aqua/src/lexicon/types/app/bsky/actor/profile.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { ValidationResult, BlobRef } from '@atproto/lexicon' 5 + import { lexicons } from '../../../../lexicons' 6 + import { isObj, hasProp } from '../../../../util' 7 + import { CID } from 'multiformats/cid' 8 + import * as ComAtprotoLabelDefs from '../../../com/atproto/label/defs' 9 + import * as ComAtprotoRepoStrongRef from '../../../com/atproto/repo/strongRef' 10 + 11 + export interface Record { 12 + displayName?: string 13 + /** Free-form profile description text. */ 14 + description?: string 15 + /** Small image to be displayed next to posts from account. AKA, 'profile picture' */ 16 + avatar?: BlobRef 17 + /** Larger horizontal image to display behind profile view. */ 18 + banner?: BlobRef 19 + labels?: 20 + | ComAtprotoLabelDefs.SelfLabels 21 + | { $type: string; [k: string]: unknown } 22 + joinedViaStarterPack?: ComAtprotoRepoStrongRef.Main 23 + createdAt?: string 24 + [k: string]: unknown 25 + } 26 + 27 + export function isRecord(v: unknown): v is Record { 28 + return ( 29 + isObj(v) && 30 + hasProp(v, '$type') && 31 + (v.$type === 'app.bsky.actor.profile#main' || 32 + v.$type === 'app.bsky.actor.profile') 33 + ) 34 + } 35 + 36 + export function validateRecord(v: unknown): ValidationResult { 37 + return lexicons.validate('app.bsky.actor.profile#main', v) 38 + }
+26
apps/aqua/src/lexicon/types/xyz/statusphere/status.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { ValidationResult, BlobRef } from '@atproto/lexicon' 5 + import { lexicons } from '../../../lexicons' 6 + import { isObj, hasProp } from '../../../util' 7 + import { CID } from 'multiformats/cid' 8 + 9 + export interface Record { 10 + status: string 11 + createdAt: string 12 + [k: string]: unknown 13 + } 14 + 15 + export function isRecord(v: unknown): v is Record { 16 + return ( 17 + isObj(v) && 18 + hasProp(v, '$type') && 19 + (v.$type === 'xyz.statusphere.status#main' || 20 + v.$type === 'xyz.statusphere.status') 21 + ) 22 + } 23 + 24 + export function validateRecord(v: unknown): ValidationResult { 25 + return lexicons.validate('xyz.statusphere.status#main', v) 26 + }
+13
apps/aqua/src/lexicon/util.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + export function isObj(v: unknown): v is Record<string, unknown> { 5 + return typeof v === 'object' && v !== null 6 + } 7 + 8 + export function hasProp<K extends PropertyKey>( 9 + data: object, 10 + prop: K, 11 + ): data is Record<K, unknown> { 12 + return prop in data 13 + }
+17
apps/aqua/src/lib/env.ts
··· 1 + import dotenv from "dotenv"; 2 + import { cleanEnv, host, port, str, testOnly } from "envalid"; 3 + import process from "node:process"; 4 + 5 + dotenv.config(); 6 + 7 + export const env = cleanEnv(process.env, { 8 + NODE_ENV: str({ 9 + devDefault: testOnly("test"), 10 + choices: ["development", "production", "test"], 11 + }), 12 + HOST: host({ devDefault: testOnly("0.0.0.0") }), 13 + PORT: port({ devDefault: testOnly(3000) }), 14 + PUBLIC_URL: str({}), 15 + DB_PATH: str({ devDefault: "file:./db.sqlite" }), 16 + COOKIE_SECRET: str({ devDefault: "secret_cookie! very secret!" }), 17 + });
+12
apps/aqua/src/lib/view.ts
··· 1 + // @ts-ignore 2 + import ssr from "uhtml/ssr"; 3 + import type initSSR from "uhtml/types/init-ssr"; 4 + import type { Hole } from "uhtml/types/keyed"; 5 + 6 + export type { Hole }; 7 + 8 + export const { html }: ReturnType<typeof initSSR> = ssr(); 9 + 10 + export function page(hole: Hole) { 11 + return `<!DOCTYPE html>\n${hole.toDOM().toString()}`; 12 + }
+8
apps/aqua/tsconfig.json
··· 1 + { 2 + "extends": "@teal/tsconfig/base.json", 3 + "compilerOptions": { 4 + "paths": { 5 + "@/*": ["./src/*"] 6 + } 7 + } 8 + }
+5
apps/viridian/go.mod
··· 1 + module viridian 2 + 3 + go 1.21.5 4 + 5 + require github.com/pebbe/zmq4 v1.2.11
+2
apps/viridian/go.sum
··· 1 + github.com/pebbe/zmq4 v1.2.11 h1:Ua5mgIaZeabUGnH7tqswkUcjkL7JYGai5e8v4hpEU9Q= 2 + github.com/pebbe/zmq4 v1.2.11/go.mod h1:nqnPueOapVhE2wItZ0uOErngczsJdLOGkebMxaO8r48=
+29
apps/viridian/main.go
··· 1 + package main 2 + 3 + import ( 4 + "fmt" 5 + "sync" 6 + 7 + server "viridian/server" 8 + worker "viridian/worker" 9 + ) 10 + 11 + func main() { 12 + var wg sync.WaitGroup 13 + 14 + // Run the server in a goroutine 15 + go server.RunServer() 16 + 17 + // Simulate workers, each running in a separate goroutine 18 + workerCount := worker.GetCoreCount() 19 + fmt.Printf("Starting %d workers\n", workerCount) 20 + for i := 1; i <= workerCount; i++ { 21 + fmt.Printf("Starting worker %d\n", i) 22 + workerID := fmt.Sprintf("worker-%d", i) 23 + wg.Add(1) 24 + go worker.RunWorker(workerID, &wg) 25 + } 26 + 27 + // Wait for all workers to complete (this won't happen in this example since workers run indefinitely) 28 + wg.Wait() 29 + }
+11
apps/viridian/package.json
··· 1 + { 2 + "name": "@teal/viridian", 3 + "version": "0.0.0", 4 + "private": true, 5 + "scripts": { 6 + "install": "go mod download", 7 + "build": "go build main.go", 8 + "dev": "go run main.go", 9 + "start": "go run main.go" 10 + } 11 + }
+62
apps/viridian/server/server.go
··· 1 + package server 2 + 3 + import ( 4 + "fmt" 5 + "time" 6 + 7 + "viridian/types" 8 + 9 + zmq "github.com/pebbe/zmq4" 10 + ) 11 + 12 + // RunServer launches the server that distributes jobs to workers 13 + func RunServer() { 14 + server := &types.Server{ 15 + Jobs: []types.Job{{"job1", "pending"}, {"job2", "pending"}, {"job3", "pending"}}, 16 + WorkerJobs: make(map[string]string), 17 + JobStatus: make(map[string]string), 18 + } 19 + 20 + // Initialize job status 21 + for _, job := range server.Jobs { 22 + server.JobStatus[job.ID] = "pending" 23 + } 24 + 25 + // Create a ZeroMQ ROUTER socket for distributing jobs 26 + socket, _ := zmq.NewSocket(zmq.ROUTER) 27 + defer socket.Close() 28 + socket.Bind("tcp://*:5555") 29 + 30 + for len(server.Jobs) > 0 { 31 + // Wait for a worker to send a ready message 32 + workerAddr, _ := socket.RecvMessage(0) 33 + 34 + fmt.Printf("Received ready message from worker %s\n", workerAddr) 35 + 36 + fmt.Print(workerAddr[2]) 37 + 38 + // Lock jobs to pick the next one safely 39 + server.Mu.Lock() 40 + if len(server.Jobs) == 0 { 41 + server.Mu.Unlock() 42 + continue 43 + } 44 + job := server.Jobs[0] 45 + server.Jobs = server.Jobs[1:] 46 + server.Mu.Unlock() 47 + 48 + // Track the job for this worker 49 + server.TrackJob(workerAddr[0], job.ID) 50 + 51 + // Send job to the worker 52 + fmt.Printf("Sending job %s to worker %s\n", job.ID, workerAddr[0]) 53 + socket.SendMessage(workerAddr[0], "", job.ID) 54 + 55 + time.Sleep(1 * time.Second) // Simulate a delay 56 + 57 + // In a real-world scenario, this would be based on a worker response 58 + server.MarkJobCompleted(workerAddr[0]) 59 + } 60 + 61 + fmt.Println("All jobs processed") 62 + }
+46
apps/viridian/types/types.go
··· 1 + package types 2 + 3 + import ( 4 + "strconv" 5 + "sync" 6 + ) 7 + 8 + // Job represents a job with an ID and status 9 + type Job struct { 10 + ID string 11 + Status string 12 + } 13 + 14 + // Server represents the state of the server, including jobs, worker-job associations, and job status 15 + type Server struct { 16 + Jobs []Job // Job queue 17 + WorkerJobs map[string]string // Mapping worker ID -> job ID 18 + JobStatus map[string]string // Mapping job ID -> status 19 + Mu sync.Mutex // Mutex to handle concurrency 20 + } 21 + 22 + func (s *Server) AddJob() Job { 23 + s.Mu.Lock() 24 + defer s.Mu.Unlock() 25 + job := Job{"job" + strconv.Itoa(len(s.Jobs)), "pending"} 26 + s.Jobs = append(s.Jobs, job) 27 + 28 + return job 29 + } 30 + 31 + // TrackJob assigns a job to a worker and marks it as in-progress 32 + func (s *Server) TrackJob(workerID, jobID string) { 33 + s.Mu.Lock() 34 + defer s.Mu.Unlock() 35 + s.WorkerJobs[workerID] = jobID 36 + s.JobStatus[jobID] = "in-progress" 37 + } 38 + 39 + // MarkJobCompleted marks a job as completed and removes the worker-job association 40 + func (s *Server) MarkJobCompleted(workerID string) { 41 + s.Mu.Lock() 42 + defer s.Mu.Unlock() 43 + jobID := s.WorkerJobs[workerID] 44 + s.JobStatus[jobID] = "completed" 45 + delete(s.WorkerJobs, workerID) // Remove worker-job association 46 + }
+1
apps/viridian/worker/ffmpeg.go
··· 1 + package worker
+47
apps/viridian/worker/worker.go
··· 1 + package worker 2 + 3 + import ( 4 + "fmt" 5 + "runtime" 6 + "sync" 7 + "time" 8 + 9 + zmq "github.com/pebbe/zmq4" 10 + ) 11 + 12 + // RunWorker simulates a worker that connects to the server and processes jobs 13 + func RunWorker(id string, wg *sync.WaitGroup) { 14 + defer wg.Done() 15 + 16 + // Create a ZeroMQ REQ socket for receiving jobs 17 + socket, _ := zmq.NewSocket(zmq.REQ) 18 + defer socket.Close() 19 + socket.Connect("tcp://localhost:5555") 20 + 21 + for { 22 + // Send a ready signal to the server 23 + socket.SendMessage(fmt.Sprintf("READY %s", id), 0) 24 + 25 + // Receive the job 26 + msg, err := socket.RecvMessage(0) 27 + if err != nil { 28 + fmt.Println("Error receiving job:", err) 29 + continue 30 + } 31 + job := msg[0] 32 + 33 + // Process the job 34 + fmt.Printf("Worker %s received job: %s\n", id, job) 35 + socket.SendMessage(fmt.Sprintf("RECEIVED %s", job), 0) 36 + 37 + // Simulate processing the job 38 + fmt.Printf("Worker %s processing job: %s\n", id, job) 39 + socket.SendMessage(fmt.Sprintf("PROCESSING %s", job), 0) 40 + time.Sleep(2 * time.Second) // Simulate job processing delay 41 + } 42 + } 43 + 44 + // GetCoreCount returns the number of CPU cores on the current machine 45 + func GetCoreCount() int { 46 + return runtime.NumCPU() 47 + }
bun.lockb

This is a binary file and will not be displayed.

+51
compose.dev.yml
··· 1 + services: 2 + traefik: 3 + image: traefik:v2.10 4 + container_name: traefik 5 + command: 6 + - "--api.insecure=true" 7 + - "--providers.file.directory=/etc/traefik/dynamic" 8 + - "--providers.file.watch=true" 9 + - "--entrypoints.web.address=:80" 10 + ports: 11 + - "80:80" # HTTP 12 + - "8080:8080" # Dashboard 13 + volumes: 14 + - ./traefik/dynamic:/etc/traefik/dynamic:ro 15 + networks: 16 + - app_network 17 + extra_hosts: 18 + - "host.docker.internal:host-gateway" # This allows reaching host machine 19 + 20 + postgres: 21 + image: postgres:latest 22 + container_name: postgres_db 23 + environment: 24 + POSTGRES_USER: postgres 25 + POSTGRES_PASSWORD: yourpassword 26 + POSTGRES_DB: yourdatabase 27 + ports: 28 + - "5432:5432" 29 + volumes: 30 + - postgres_data:/var/lib/postgresql/data 31 + networks: 32 + - app_network 33 + 34 + redis: 35 + image: redis:latest 36 + container_name: redis_cache 37 + ports: 38 + - "6379:6379" 39 + volumes: 40 + - redis_data:/data 41 + command: redis-server --appendonly yes 42 + networks: 43 + - app_network 44 + 45 + networks: 46 + app_network: 47 + driver: bridge 48 + 49 + volumes: 50 + postgres_data: 51 + redis_data:
+49
lexicons/app.bsky.actor.profile.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.bsky.actor.profile", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A declaration of a Bluesky account profile.", 8 + "key": "literal:self", 9 + "record": { 10 + "type": "object", 11 + "properties": { 12 + "displayName": { 13 + "type": "string", 14 + "maxGraphemes": 64, 15 + "maxLength": 640 16 + }, 17 + "description": { 18 + "type": "string", 19 + "description": "Free-form profile description text.", 20 + "maxGraphemes": 256, 21 + "maxLength": 2560 22 + }, 23 + "avatar": { 24 + "type": "blob", 25 + "description": "Small image to be displayed next to posts from account. AKA, 'profile picture'", 26 + "accept": ["image/png", "image/jpeg"], 27 + "maxSize": 1000000 28 + }, 29 + "banner": { 30 + "type": "blob", 31 + "description": "Larger horizontal image to display behind profile view.", 32 + "accept": ["image/png", "image/jpeg"], 33 + "maxSize": 1000000 34 + }, 35 + "labels": { 36 + "type": "union", 37 + "description": "Self-label values, specific to the Bluesky application, on the overall account.", 38 + "refs": ["com.atproto.label.defs#selfLabels"] 39 + }, 40 + "joinedViaStarterPack": { 41 + "type": "ref", 42 + "ref": "com.atproto.repo.strongRef" 43 + }, 44 + "createdAt": { "type": "string", "format": "datetime" } 45 + } 46 + } 47 + } 48 + } 49 + }
+23
lexicons/xyz.statusphere.status.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "xyz.statusphere.status", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "tid", 8 + "record": { 9 + "type": "object", 10 + "required": ["status", "createdAt"], 11 + "properties": { 12 + "status": { 13 + "type": "string", 14 + "minLength": 1, 15 + "maxGraphemes": 1, 16 + "maxLength": 32 17 + }, 18 + "createdAt": { "type": "string", "format": "datetime" } 19 + } 20 + } 21 + } 22 + } 23 + }
+16 -9
package.json
··· 1 1 { 2 2 "name": "teal", 3 - "version": "1.0.50", 3 + "private": true, 4 + "version": "0.0.0", 5 + "packageManager": "bun@1.0.0+", 4 6 "scripts": { 5 - "test": "echo \"Error: no test specified\" && exit 1", 6 - "dev": "bun run --watch src/index.ts" 7 + "install": "turbo run install", 8 + "build": "turbo run build", 9 + "dev": "turbo run dev", 10 + "lint": "turbo run lint" 7 11 }, 8 - "dependencies": { 9 - "elysia": "latest" 12 + "devDependencies": { 13 + "turbo": "latest" 10 14 }, 11 - "devDependencies": { 12 - "bun-types": "latest" 15 + "engines": { 16 + "node": ">=20.0.0" 13 17 }, 14 - "module": "src/index.js" 15 - } 18 + "workspaces": [ 19 + "apps/*", 20 + "packages/*" 21 + ] 22 + }
+3
packages/tsconfig/package.json
··· 1 + { 2 + "name": "@teal/tsconfig" 3 + }
-7
src/index.ts
··· 1 - import { Elysia } from "elysia"; 2 - 3 - const app = new Elysia().get("/", () => "Hello Elysia").listen(3000); 4 - 5 - console.log( 6 - `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` 7 - );
+22
traefik/dev_routes.yml
··· 1 + http: 2 + routers: 3 + frontend: 4 + rule: "PathPrefix(`/`)" 5 + service: frontend 6 + priority: 1 7 + 8 + backend: 9 + rule: "PathPrefix(`/api`)" 10 + service: backend 11 + priority: 2 12 + 13 + services: 14 + frontend: 15 + loadBalancer: 16 + servers: 17 + - url: "http://host.docker.internal:3000" 18 + 19 + backend: 20 + loadBalancer: 21 + servers: 22 + - url: "http://host.docker.internal:3031"
+24
traefik/dynamic_conf.yml
··· 1 + http: 2 + middlewares: 3 + security-headers: 4 + headers: 5 + frameDeny: true 6 + sslRedirect: true 7 + browserXssFilter: true 8 + contentTypeNosniff: true 9 + forceSTSHeader: true 10 + stsIncludeSubdomains: true 11 + stsPreload: true 12 + stsSeconds: 31536000 13 + 14 + cors-headers: 15 + headers: 16 + accessControlAllowMethods: 17 + - GET 18 + - POST 19 + - PUT 20 + - DELETE 21 + - OPTIONS 22 + accessControlAllowHeaders: 23 + - "*" 24 + accessControlAllowOrigin: "*"
+56
traefik/traefik.yml
··· 1 + api: 2 + insecure: true 3 + dashboard: true 4 + 5 + entryPoints: 6 + web: 7 + address: ":80" 8 + http: 9 + redirections: 10 + entryPoint: 11 + to: websecure 12 + scheme: https 13 + 14 + websecure: 15 + address: ":443" 16 + 17 + providers: 18 + docker: 19 + endpoint: "unix:///var/run/docker.sock" 20 + exposedByDefault: false 21 + network: app_network 22 + 23 + file: 24 + filename: /etc/traefik/dynamic_conf.yml 25 + 26 + certificatesResolvers: 27 + letsencrypt: 28 + acme: 29 + email: your-email@domain.com 30 + storage: /letsencrypt/acme.json 31 + httpChallenge: 32 + entryPoint: web 33 + http: 34 + middlewares: 35 + security-headers: 36 + headers: 37 + frameDeny: true 38 + sslRedirect: true 39 + browserXssFilter: true 40 + contentTypeNosniff: true 41 + forceSTSHeader: true 42 + stsIncludeSubdomains: true 43 + stsPreload: true 44 + stsSeconds: 31536000 45 + 46 + cors-headers: 47 + headers: 48 + accessControlAllowMethods: 49 + - GET 50 + - POST 51 + - PUT 52 + - DELETE 53 + - OPTIONS 54 + accessControlAllowHeaders: 55 + - "*" 56 + accessControlAllowOrigin: "*"
+9 -9
tsconfig.json packages/tsconfig/base.json
··· 11 11 // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 12 13 13 /* Language and Environment */ 14 - "target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 14 + "target": "ES2021" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 15 // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 16 // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 17 // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ ··· 25 25 // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 26 27 27 /* Modules */ 28 - "module": "ES2022", /* Specify what module code is generated. */ 28 + "module": "ES2022" /* Specify what module code is generated. */, 29 29 // "rootDir": "./", /* Specify the root folder within your source files. */ 30 - "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 30 + "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, 31 31 // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 32 + //"paths": {} /* Specify a set of entries that re-map imports to additional lookup locations. */, 33 33 // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 34 // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 - "types": ["bun-types"], /* Specify type package names to be included without being referenced in a source file. */ 35 + // "types": [] /* Specify type package names to be included without being referenced in a source file. */, 36 36 // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 37 37 // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 38 38 // "resolveJsonModule": true, /* Enable importing .json files. */ ··· 71 71 /* Interop Constraints */ 72 72 // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 73 73 // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 74 - "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ 74 + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 75 75 // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 76 - "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 76 + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 77 77 78 78 /* Type Checking */ 79 - "strict": true, /* Enable all strict type-checking options. */ 79 + "strict": true /* Enable all strict type-checking options. */, 80 80 // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 81 81 // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 82 82 // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ ··· 98 98 99 99 /* Completeness */ 100 100 // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 101 - "skipLibCheck": true /* Skip type checking all .d.ts files. */ 101 + "skipLibCheck": true /* Skip type checking all .d.ts files. */ 102 102 } 103 103 }
+19
turbo.json
··· 1 + { 2 + "$schema": "https://turbo.build/schema.json", 3 + "tasks": { 4 + "install": { 5 + "dependsOn": ["^install"] 6 + }, 7 + "build": { 8 + "dependsOn": ["^build"], 9 + "outputs": [".next/**", "!.next/cache/**"] 10 + }, 11 + "check-types": { 12 + "dependsOn": ["^check-types"] 13 + }, 14 + "dev": { 15 + "persistent": true, 16 + "cache": false 17 + } 18 + } 19 + }