A personal media tracker built on the AT Protocol opnshelf.xyz
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: enhance API client with TanStack Query support, update documentation, and refactor session handling in backend services

+3186 -883
+44 -1
AGENTS.md
··· 6 6 - **apps/web**: React + TanStack Router + Vite frontend 7 7 - **apps/mobile**: Expo/React Native mobile app 8 8 - **backend**: NestJS API server with Prisma 9 - - **packages/api**: OpenAPI-generated API client 9 + - **packages/api**: OpenAPI-generated API client with TanStack Query support 10 10 - **packages/types**: Shared TypeScript types 11 11 12 12 ## Build Commands ··· 170 170 - `/backend/eslint.config.mjs` - Backend linting rules 171 171 - `/turbo.json` - Build pipeline configuration 172 172 - `/pnpm-workspace.yaml` - Workspace definitions 173 + 174 + ## API Client (@opnshelf/api) 175 + 176 + The API client is generated using `@hey-api/openapi-ts` with TanStack Query support. 177 + 178 + ### Generated Code Structure 179 + 180 + - `src/generated/` - Auto-generated SDK and types 181 + - `src/generated/@tanstack/react-query.gen.ts` - TanStack Query hooks (queryOptions, mutationOptions) 182 + - `src/client.ts` - Custom client wrapper with auth interceptors 183 + 184 + ### Using TanStack Query Hooks 185 + 186 + ```typescript 187 + import { useQuery, useMutation } from '@tanstack/react-query'; 188 + import { moviesControllerSearchMoviesOptions, moviesControllerMarkWatchedMutation } from '@opnshelf/api'; 189 + 190 + // Query example 191 + const { data, isLoading } = useQuery({ 192 + ...moviesControllerSearchMoviesOptions({ 193 + query: { query: 'Inception' } 194 + }) 195 + }); 196 + 197 + // Mutation example 198 + const markWatched = useMutation({ 199 + ...moviesControllerMarkWatchedMutation(), 200 + onSuccess: () => { 201 + // Handle success 202 + } 203 + }); 204 + 205 + markWatched.mutate({ 206 + body: { movieId: '123' } 207 + }); 208 + ``` 209 + 210 + ### Regenerating API Client 211 + 212 + ```bash 213 + # Ensure backend is running on port 3001, then: 214 + pnpm generate:api 215 + ``` 173 216 174 217 ## Cursor Rules 175 218
+9 -1
apps/web/src/components/Header.tsx
··· 31 31 <header className="px-4 py-3 flex items-center justify-between bg-gray-900 text-white border-b border-gray-800"> 32 32 <div className="flex items-center gap-3"> 33 33 <button 34 + type="button" 34 35 onClick={() => setIsOpen(true)} 35 36 className="p-2 hover:bg-gray-800 rounded-lg transition-colors md:hidden" 36 37 aria-label="Open menu" ··· 100 101 {user.displayName || `@${user.handle}`} 101 102 </span> 102 103 <button 104 + type="button" 103 105 onClick={handleLogout} 104 106 className="flex items-center gap-2 px-3 py-1.5 rounded-lg hover:bg-gray-800 transition-colors text-gray-300 hover:text-white text-sm" 105 107 title="Sign out" ··· 109 111 </div> 110 112 ) : ( 111 113 <button 114 + type="button" 112 115 onClick={handleLogin} 113 116 className="flex items-center gap-2 px-4 py-2 rounded-lg bg-purple-600 hover:bg-purple-700 transition-colors text-white text-sm font-medium" 114 117 > ··· 122 125 123 126 {/* Mobile drawer overlay */} 124 127 {isOpen && ( 125 - <div 128 + <button 129 + type="button" 126 130 className="fixed inset-0 bg-black/50 z-40 md:hidden" 127 131 onClick={() => setIsOpen(false)} 132 + aria-label="Close menu overlay" 128 133 /> 129 134 )} 130 135 ··· 140 145 <span className="text-lg font-bold">OpnShelf</span> 141 146 </div> 142 147 <button 148 + type="button" 143 149 onClick={() => setIsOpen(false)} 144 150 className="p-2 hover:bg-gray-800 rounded-lg transition-colors" 145 151 aria-label="Close menu" ··· 213 219 </div> 214 220 </div> 215 221 <button 222 + type="button" 216 223 onClick={() => { 217 224 handleLogout() 218 225 setIsOpen(false) ··· 225 232 </div> 226 233 ) : ( 227 234 <button 235 + type="button" 228 236 onClick={() => { 229 237 handleLogin() 230 238 setIsOpen(false)
+4 -3
apps/web/src/routes/login.tsx
··· 1 1 import { createFileRoute, useNavigate } from '@tanstack/react-router'; 2 - import { useState, useEffect } from 'react'; 2 + import { useState, useEffect, useId } from 'react'; 3 3 import { useQuery } from '@tanstack/react-query'; 4 4 import { Film, LogIn, AlertCircle } from 'lucide-react'; 5 5 import { getAuthUser, getLoginUrl } from '@opnshelf/api'; ··· 22 22 const [isSubmitting, setIsSubmitting] = useState(false); 23 23 const navigate = useNavigate(); 24 24 const { error, redirect, reason } = Route.useSearch(); 25 + const handleId = useId(); 25 26 26 27 // Check if user is already logged in 27 28 const { data: user, isLoading: isAuthLoading } = useQuery({ ··· 104 105 <form onSubmit={handleSubmit} className="space-y-6"> 105 106 <div> 106 107 <label 107 - htmlFor="handle" 108 + htmlFor={handleId} 108 109 className="block text-sm font-medium text-gray-300 mb-2" 109 110 > 110 111 Bluesky Handle 111 112 </label> 112 113 <input 113 - id="handle" 114 + id={handleId} 114 115 type="text" 115 116 value={handle} 116 117 onChange={(e) => setHandle(e.target.value)}
+2 -1
apps/web/src/routes/search.tsx
··· 31 31 // Fetch user's tracked movies when logged in 32 32 const { data: trackedMovies } = useQuery({ 33 33 queryKey: ['shelf', user?.did], 34 - queryFn: () => getUserMovies(user!.did), 34 + queryFn: () => getUserMovies(user?.did), 35 35 enabled: !!user?.did, 36 36 }); 37 37 ··· 149 149 )} 150 150 {user && ( 151 151 <button 152 + type="button" 152 153 onClick={() => { 153 154 if (isWatched) { 154 155 unmarkMutation.mutate(movieId);
+2 -1
apps/web/src/routes/shelf.tsx
··· 21 21 // Fetch user's tracked movies 22 22 const { data: trackedMovies, isLoading: isMoviesLoading } = useQuery({ 23 23 queryKey: ['shelf', user?.did], 24 - queryFn: () => getUserMovies(user!.did), 24 + queryFn: () => getUserMovies(user?.did), 25 25 enabled: !!user?.did, 26 26 }); 27 27 ··· 106 106 </div> 107 107 )} 108 108 <button 109 + type="button" 109 110 onClick={() => unmarkMutation.mutate(tracked.movieId)} 110 111 disabled={unmarkMutation.isPending} 111 112 className="absolute top-2 right-2 p-2 bg-red-600 hover:bg-red-700 rounded-full opacity-0 group-hover:opacity-100 transition-opacity disabled:opacity-50"
+1 -1
apps/web/vite.config.ts
··· 3 3 import { tanstackStart } from '@tanstack/react-start/plugin/vite' 4 4 import viteReact from '@vitejs/plugin-react' 5 5 import viteTsConfigPaths from 'vite-tsconfig-paths' 6 - import { fileURLToPath, URL } from 'url' 6 + import { fileURLToPath, URL } from 'node:url' 7 7 8 8 import tailwindcss from '@tailwindcss/vite' 9 9 import { nitro } from 'nitro/vite'
+11
backend/eslint.config.mjs
··· 32 32 "prettier/prettier": ["error", { endOfLine: "auto" }], 33 33 }, 34 34 }, 35 + { 36 + files: ['**/*.spec.ts', '**/*.test.ts'], 37 + rules: { 38 + '@typescript-eslint/unbound-method': 'off', 39 + '@typescript-eslint/no-unsafe-member-access': 'off', 40 + '@typescript-eslint/no-unsafe-assignment': 'off', 41 + '@typescript-eslint/no-unsafe-call': 'off', 42 + '@typescript-eslint/no-unused-vars': 'off', 43 + '@typescript-eslint/no-require-imports': 'off', 44 + }, 45 + }, 35 46 );
+10 -3
backend/src/auth/auth.controller.spec.ts
··· 17 17 18 18 describe('AuthController', () => { 19 19 let controller: AuthController; 20 - let authService: jest.Mocked<AuthService>; 21 20 22 - const mockAuthService = { 21 + const mockAuthService: { 22 + getClientMetadata: jest.Mock; 23 + authorize: jest.Mock; 24 + callback: jest.Mock; 25 + fetchProfile: jest.Mock; 26 + upsertUser: jest.Mock; 27 + getSessionByUserDid: jest.Mock; 28 + getUser: jest.Mock; 29 + revokeBySessionId: jest.Mock; 30 + } = { 23 31 getClientMetadata: jest.fn(), 24 32 authorize: jest.fn(), 25 33 callback: jest.fn(), ··· 71 79 }).compile(); 72 80 73 81 controller = module.get<AuthController>(AuthController); 74 - authService = module.get(AuthService); 75 82 }); 76 83 77 84 describe('getClientMetadata', () => {
+9 -6
backend/src/auth/auth.controller.ts
··· 12 12 } from '@nestjs/common'; 13 13 import { ApiTags, ApiOperation, ApiQuery, ApiResponse } from '@nestjs/swagger'; 14 14 import { ConfigService } from '@nestjs/config'; 15 - import type { Request, Response } from 'express'; 15 + import type { Response } from 'express'; 16 16 import { AuthService } from './auth.service'; 17 17 import { AuthGuard } from './auth.guard'; 18 18 import { UserDto } from './dto/user.dto'; 19 + import { AuthenticatedRequest } from './types'; 19 20 20 21 const SESSION_COOKIE_NAME = 'session'; 21 22 const PLATFORM_COOKIE_NAME = 'auth_platform'; ··· 158 159 }); 159 160 160 161 // Check if request originated from mobile app 161 - const platform = req.cookies?.[PLATFORM_COOKIE_NAME]; 162 + const cookies = req.cookies as Record<string, string | undefined>; 163 + const platform = cookies?.[PLATFORM_COOKIE_NAME]; 162 164 163 165 // Clear platform cookie after use 164 166 if (platform) { ··· 187 189 @ApiOperation({ summary: 'Get current authenticated user' }) 188 190 @ApiResponse({ status: 200, type: UserDto }) 189 191 @ApiResponse({ status: 401, description: 'Not authenticated' }) 190 - async me(@Req() req: Request): Promise<UserDto> { 191 - const did = (req as any).user?.did; 192 + async me(@Req() req: AuthenticatedRequest): Promise<UserDto> { 193 + const did = req.user?.did; 192 194 if (!did) { 193 195 throw new BadRequestException('User not found in request'); 194 196 } ··· 212 214 @Post('auth/logout') 213 215 @ApiOperation({ summary: 'Logout and clear session' }) 214 216 @ApiResponse({ status: 200, description: 'Logged out successfully' }) 215 - async logout(@Req() req: Request, @Res() res: Response) { 216 - const sessionId = req.cookies?.[SESSION_COOKIE_NAME]; 217 + async logout(@Req() req: AuthenticatedRequest, @Res() res: Response) { 218 + const cookies = req.cookies as Record<string, string | undefined>; 219 + const sessionId = cookies?.[SESSION_COOKIE_NAME]; 217 220 218 221 if (sessionId) { 219 222 await this.authService.revokeBySessionId(sessionId);
+6 -4
backend/src/auth/auth.guard.ts
··· 5 5 UnauthorizedException, 6 6 Logger, 7 7 } from '@nestjs/common'; 8 - import { Request } from 'express'; 9 8 import { AuthService } from './auth.service'; 9 + import { AuthenticatedRequest, AuthUser } from './types'; 10 10 11 11 const SESSION_COOKIE_NAME = 'session'; 12 12 ··· 17 17 constructor(private readonly authService: AuthService) {} 18 18 19 19 async canActivate(context: ExecutionContext): Promise<boolean> { 20 - const request = context.switchToHttp().getRequest<Request>(); 20 + const request = context.switchToHttp().getRequest<AuthenticatedRequest>(); 21 21 22 22 // Try Bearer token first (for mobile apps), then fall back to cookie (for web) 23 23 const authHeader = request.headers.authorization; ··· 28 28 this.logger.debug('Using Bearer token for auth'); 29 29 } else { 30 30 // Cookie stores opaque session id (not DID) 31 - sessionId = request.cookies?.[SESSION_COOKIE_NAME]; 31 + const cookies = request.cookies as Record<string, string | undefined>; 32 + sessionId = cookies?.[SESSION_COOKIE_NAME]; 32 33 } 33 34 34 35 if (!sessionId) { ··· 51 52 } 52 53 53 54 // Attach user info to request 54 - (request as any).user = { 55 + const authUser: AuthUser = { 55 56 did: session.did, 56 57 session, 57 58 }; 59 + request.user = authUser; 58 60 59 61 return true; 60 62 } catch (error) {
+1 -1
backend/src/auth/auth.service.spec.ts
··· 91 91 configService = module.get(ConfigService); 92 92 93 93 // Initialize the OAuth client 94 - await service.onModuleInit(); 94 + service.onModuleInit(); 95 95 }); 96 96 97 97 describe('getSessionById', () => {
+7 -5
backend/src/auth/auth.service.ts
··· 18 18 private readonly configService: ConfigService, 19 19 ) {} 20 20 21 - async onModuleInit() { 22 - await this.initializeOAuthClient(); 21 + onModuleInit() { 22 + this.initializeOAuthClient(); 23 23 } 24 24 25 - private async initializeOAuthClient() { 25 + private initializeOAuthClient() { 26 26 const backendUrl = 27 27 this.configService.get<string>('BACKEND_PUBLIC_URL') || 28 28 'http://127.0.0.1:3001'; ··· 228 228 * @param session - The OAuth session 229 229 * @returns User profile data 230 230 */ 231 - async fetchProfile(session: any) { 232 - const agent = new Agent(session); 231 + async fetchProfile(session: { did: string }) { 232 + const agent = new Agent( 233 + session as unknown as ConstructorParameters<typeof Agent>[0], 234 + ); 233 235 const profile = await agent.getProfile({ actor: session.did }); 234 236 return { 235 237 did: session.did,
+10
backend/src/auth/types.ts
··· 1 + import { Request } from 'express'; 2 + 3 + export interface AuthUser { 4 + did: string; 5 + session: unknown; 6 + } 7 + 8 + export interface AuthenticatedRequest extends Request { 9 + user: AuthUser; 10 + }
+24 -14
backend/src/ingester/ingester.service.ts
··· 32 32 ); 33 33 } 34 34 35 + interface FirehoseEvent { 36 + event: string; 37 + collection?: string; 38 + record?: unknown; 39 + uri?: { toString(): string }; 40 + rkey?: string; 41 + cid?: { toString(): string }; 42 + author?: string; 43 + } 44 + 35 45 @Injectable() 36 46 export class IngesterService implements OnModuleInit, OnModuleDestroy { 37 47 private readonly logger = new Logger(IngesterService.name); ··· 47 57 this.config.get<string>('ATPROTO_RELAY_URL') || 'wss://bsky.network'; 48 58 } 49 59 50 - async onModuleInit() { 51 - await this.startIngester(); 60 + onModuleInit() { 61 + void this.startIngester(); 52 62 } 53 63 54 - async onModuleDestroy() { 55 - await this.stopIngester(); 64 + onModuleDestroy() { 65 + this.stopIngester(); 56 66 } 57 67 58 - private async startIngester() { 68 + private startIngester() { 59 69 this.logger.log( 60 70 `Starting firehose ingester, connecting to ${this.relayUrl}`, 61 71 ); ··· 66 76 this.firehose = new Firehose({ 67 77 idResolver, 68 78 filterCollections: [COLLECTION], 69 - handleEvent: async (evt) => { 79 + handleEvent: async (evt: FirehoseEvent) => { 70 80 try { 71 81 await this.handleEvent(evt); 72 82 } catch (err) { 73 83 this.logger.error('Error handling firehose event', err); 74 84 } 75 85 }, 76 - onError: (err) => { 86 + onError: (err: { message: string }) => { 77 87 // Log non-fatal errors (e.g., parse errors for events we don't handle) 78 88 this.logger.warn('Firehose error (non-fatal)', err.message); 79 89 }, 80 90 }); 81 91 82 92 // Start the firehose connection 83 - this.firehose.start(); 93 + void this.firehose.start(); 84 94 this.logger.log('Firehose ingester started'); 85 95 } 86 96 87 - private async stopIngester() { 97 + private stopIngester() { 88 98 if (this.firehose) { 89 - this.firehose.destroy(); 99 + void this.firehose.destroy(); 90 100 this.firehose = null; 91 101 this.logger.log('Firehose ingester stopped'); 92 102 } ··· 100 110 return match ? match[1] : null; 101 111 } 102 112 103 - private async handleEvent(evt: any) { 113 + private async handleEvent(evt: FirehoseEvent) { 104 114 // Handle create and update events 105 115 if (evt.event === 'create' || evt.event === 'update') { 106 116 if (evt.collection !== COLLECTION) return; ··· 111 121 return; 112 122 } 113 123 114 - const uri = evt.uri.toString(); 115 - const rkey = evt.rkey; 116 - const cid = evt.cid?.toString() || ''; 124 + const uri = evt.uri?.toString() ?? ''; 125 + const rkey = evt.rkey ?? ''; 126 + const cid = evt.cid?.toString() ?? ''; 117 127 118 128 // Extract author DID from event or URI 119 129 const authorDid = evt.author || this.extractDidFromUri(uri);
+1 -1
backend/src/main.ts
··· 43 43 console.log(`🚀 API running on http://127.0.0.1:${port}`); 44 44 console.log(`📚 API docs at http://127.0.0.1:${port}/api`); 45 45 } 46 - bootstrap(); 46 + void bootstrap();
+15 -9
backend/src/movies/movies.controller.ts
··· 13 13 Logger, 14 14 } from '@nestjs/common'; 15 15 import { ApiTags, ApiOperation, ApiResponse, ApiQuery } from '@nestjs/swagger'; 16 - import type { Request } from 'express'; 17 16 import { MoviesService } from './movies.service'; 18 17 import { 19 18 SearchMoviesDto, ··· 23 22 MarkWatchedDto, 24 23 } from './dto/movie.dto'; 25 24 import { AuthGuard } from '../auth/auth.guard'; 25 + import { AuthenticatedRequest } from '../auth/types'; 26 26 27 27 @ApiTags('movies') 28 28 @Controller('movies') ··· 58 58 @ApiOperation({ summary: 'Mark a movie as watched' }) 59 59 @ApiResponse({ status: 201, type: TrackedMovieDto }) 60 60 @ApiResponse({ status: 401, description: 'Not authenticated' }) 61 - async markWatched(@Body() body: MarkWatchedDto, @Req() req: Request) { 62 - const user = (req as any).user; 61 + async markWatched( 62 + @Body() body: MarkWatchedDto, 63 + @Req() req: AuthenticatedRequest, 64 + ) { 65 + const user = req.user; 63 66 64 67 // Write to user's PDS 65 68 const { uri, cid, rkey, record } = await this.moviesService.markWatched( ··· 80 83 record.watchedAt, 81 84 ); 82 85 return trackedMovie; 83 - } catch (err) { 86 + } catch (err: unknown) { 84 87 this.logger.warn( 85 - { err }, 88 + { err: err instanceof Error ? err.message : String(err) }, 86 89 'Failed to optimistically update DB; firehose will catch it', 87 90 ); 88 91 // Return a minimal response since PDS write succeeded ··· 96 99 @ApiOperation({ summary: 'Unmark a movie as watched' }) 97 100 @ApiResponse({ status: 204, description: 'Movie unmarked as watched' }) 98 101 @ApiResponse({ status: 401, description: 'Not authenticated' }) 99 - async unmarkWatched(@Param('movieId') movieId: string, @Req() req: Request) { 100 - const user = (req as any).user; 102 + async unmarkWatched( 103 + @Param('movieId') movieId: string, 104 + @Req() req: AuthenticatedRequest, 105 + ) { 106 + const user = req.user; 101 107 102 108 // Delete from user's PDS 103 109 await this.moviesService.unmarkWatched(user.did, user.session, movieId); ··· 106 112 // If this fails, the firehose ingester will catch it later 107 113 try { 108 114 await this.moviesService.removeTrackedMovie(user.did, movieId); 109 - } catch (err) { 115 + } catch (err: unknown) { 110 116 this.logger.warn( 111 - { err }, 117 + { err: err instanceof Error ? err.message : String(err) }, 112 118 'Failed to optimistically remove from DB; firehose will catch it', 113 119 ); 114 120 }
+42 -15
backend/src/movies/movies.service.ts
··· 5 5 6 6 const COLLECTION = 'app.opnshelf.movie'; 7 7 8 + interface TMDBMovie { 9 + id: number; 10 + title: string; 11 + poster_path?: string; 12 + backdrop_path?: string; 13 + release_date?: string; 14 + overview?: string; 15 + } 16 + 17 + interface TMDBSearchResponse { 18 + page: number; 19 + results: TMDBMovie[]; 20 + total_results: number; 21 + total_pages: number; 22 + } 23 + 24 + interface ATSession { 25 + did: string; 26 + } 27 + 8 28 @Injectable() 9 29 export class MoviesService { 10 30 private readonly logger = new Logger(MoviesService.name); ··· 18 38 this.tmdbApiKey = this.config.get('TMDB_API_KEY') ?? ''; 19 39 } 20 40 21 - async searchMovies(query: string, page: number = 1) { 41 + async searchMovies( 42 + query: string, 43 + page: number = 1, 44 + ): Promise<TMDBSearchResponse> { 22 45 const response = await fetch( 23 46 `${this.tmdbBaseUrl}/search/movie?api_key=${this.tmdbApiKey}&query=${encodeURIComponent(query)}&page=${page}`, 24 47 ); ··· 27 50 throw new Error('Failed to search movies'); 28 51 } 29 52 30 - return response.json(); 53 + return response.json() as Promise<TMDBSearchResponse>; 31 54 } 32 55 33 - async getMovieDetails(movieId: string) { 56 + async getMovieDetails(movieId: string): Promise<TMDBMovie> { 34 57 const response = await fetch( 35 58 `${this.tmdbBaseUrl}/movie/${movieId}?api_key=${this.tmdbApiKey}`, 36 59 ); ··· 39 62 throw new Error('Movie not found'); 40 63 } 41 64 42 - return response.json(); 65 + return response.json() as Promise<TMDBMovie>; 43 66 } 44 67 45 68 async getUserMovies(userDid: string) { ··· 56 79 }); 57 80 } 58 81 59 - async upsertMovie(movieData: any) { 82 + async upsertMovie(movieData: TMDBMovie) { 60 83 return this.prisma.movie.upsert({ 61 84 where: { movieId: movieData.id.toString() }, 62 85 create: { 63 86 movieId: movieData.id.toString(), 64 87 title: movieData.title, 65 - posterPath: movieData.poster_path, 66 - backdropPath: movieData.backdrop_path, 88 + posterPath: movieData.poster_path ?? null, 89 + backdropPath: movieData.backdrop_path ?? null, 67 90 releaseYear: movieData.release_date 68 91 ? new Date(movieData.release_date).getFullYear() 69 92 : null, 70 93 releaseDate: movieData.release_date 71 94 ? new Date(movieData.release_date) 72 95 : null, 73 - overview: movieData.overview, 96 + overview: movieData.overview ?? null, 74 97 }, 75 98 update: { 76 99 title: movieData.title, 77 - posterPath: movieData.poster_path, 78 - backdropPath: movieData.backdrop_path, 100 + posterPath: movieData.poster_path ?? null, 101 + backdropPath: movieData.backdrop_path ?? null, 79 102 releaseYear: movieData.release_date 80 103 ? new Date(movieData.release_date).getFullYear() 81 104 : null, 82 105 releaseDate: movieData.release_date 83 106 ? new Date(movieData.release_date) 84 107 : null, 85 - overview: movieData.overview, 108 + overview: movieData.overview ?? null, 86 109 }, 87 110 }); 88 111 } ··· 91 114 * Mark a movie as watched by creating an AT Protocol record in the user's PDS. 92 115 * Database indexing happens via the firehose ingester or optimistic update in controller. 93 116 */ 94 - async markWatched(userDid: string, session: any, movieId: string) { 117 + async markWatched(userDid: string, session: ATSession, movieId: string) { 95 118 const rkey = `movie-${movieId}`; 96 119 const now = new Date().toISOString(); 97 120 ··· 105 128 }; 106 129 107 130 // Create agent from session and write record to user's PDS 108 - const agent = new Agent(session); 131 + const agent = new Agent( 132 + session as unknown as ConstructorParameters<typeof Agent>[0], 133 + ); 109 134 const response = await agent.com.atproto.repo.putRecord({ 110 135 repo: session.did, 111 136 collection: COLLECTION, ··· 131 156 * Unmark a movie as watched by deleting the AT Protocol record from the user's PDS. 132 157 * Database cleanup happens via the firehose ingester or optimistic update in controller. 133 158 */ 134 - async unmarkWatched(userDid: string, session: any, movieId: string) { 159 + async unmarkWatched(userDid: string, session: ATSession, movieId: string) { 135 160 const rkey = `movie-${movieId}`; 136 161 137 162 // Create agent from session and delete record from user's PDS 138 - const agent = new Agent(session); 163 + const agent = new Agent( 164 + session as unknown as ConstructorParameters<typeof Agent>[0], 165 + ); 139 166 await agent.com.atproto.repo.deleteRecord({ 140 167 repo: session.did, 141 168 collection: COLLECTION,
+1 -1
package.json
··· 7 7 "dev:mobile": "pnpm --filter mobile start", 8 8 "dev:backend": "pnpm --filter backend start:dev", 9 9 "build": "turbo run build", 10 - "generate:api": "pnpm --filter @opnshelf/api exec openapi-typescript http://127.0.0.1:3001/api-json -o src/generated/schema.ts", 10 + "generate:api": "pnpm --filter @opnshelf/api exec openapi-ts", 11 11 "prisma:generate": "pnpm --filter backend exec prisma generate", 12 12 "prisma:migrate": "pnpm --filter backend exec prisma migrate dev" 13 13 },
+16
packages/api/openapi-ts.config.ts
··· 1 + import { defineConfig } from '@hey-api/openapi-ts'; 2 + 3 + export default defineConfig({ 4 + input: 'http://127.0.0.1:3001/api-json', 5 + output: 'src/generated', 6 + plugins: [ 7 + '@hey-api/client-fetch', 8 + '@hey-api/typescript', 9 + { 10 + name: '@tanstack/react-query', 11 + queryOptions: true, 12 + queryKeys: true, 13 + mutationOptions: true, 14 + }, 15 + ], 16 + });
+3 -2
packages/api/package.json
··· 4 4 "main": "src/index.ts", 5 5 "types": "src/index.ts", 6 6 "dependencies": { 7 - "openapi-fetch": "^0.15.0" 7 + "@hey-api/client-fetch": "^0.13.1", 8 + "@tanstack/react-query": "^5.90.20" 8 9 }, 9 10 "devDependencies": { 11 + "@hey-api/openapi-ts": "^0.91.1", 10 12 "@types/node": "^22.10.1", 11 - "openapi-typescript": "^7.10.1", 12 13 "typescript": "^5.9.3" 13 14 }, 14 15 "packageManager": "pnpm@10.28.2"
+66 -118
packages/api/src/client.ts
··· 1 - import createClient, { type Middleware } from 'openapi-fetch'; 2 - import type { paths } from './generated/schema'; 1 + import { client } from './generated/client.gen'; 3 2 4 3 // Allow configuring base URL 5 4 let baseUrl = 'http://127.0.0.1:3001'; ··· 11 10 let onUnauthorized: (() => void) | null = null; 12 11 13 12 export function setOnUnauthorized(callback: (() => void) | null): void { 14 - onUnauthorized = callback; 13 + onUnauthorized = callback; 15 14 } 16 15 17 16 /** ··· 19 18 * When set, requests will use Authorization header instead of cookies. 20 19 */ 21 20 export function setSessionToken(token: string | null): void { 22 - sessionToken = token; 23 - // Recreate client to pick up new middleware config 24 - apiClient = createApiClient(); 21 + sessionToken = token; 22 + // Update client config with new auth header 23 + updateClientConfig(); 25 24 } 26 25 27 26 /** 28 27 * Get the current session token (useful for checking if logged in) 29 28 */ 30 29 export function getSessionToken(): string | null { 31 - return sessionToken; 30 + return sessionToken; 32 31 } 33 32 34 - const unauthorizedMiddleware: Middleware = { 35 - async onResponse({ response }) { 36 - if (response.status === 401) { 37 - onUnauthorized?.(); 38 - } 39 - return response; 40 - }, 41 - }; 42 - 43 - const authMiddleware: Middleware = { 44 - async onRequest({ request }) { 45 - // Add Authorization header if we have a session token (mobile auth) 46 - if (sessionToken) { 47 - request.headers.set('Authorization', `Bearer ${sessionToken}`); 48 - } 49 - return request; 50 - }, 51 - }; 33 + function updateClientConfig() { 34 + const headers: Record<string, string> = {}; 35 + if (sessionToken) { 36 + headers['Authorization'] = `Bearer ${sessionToken}`; 37 + } 52 38 53 - function createApiClient() { 54 - const client = createClient<paths>({ 55 - baseUrl, 56 - credentials: 'include', 57 - }); 58 - client.use(authMiddleware); 59 - client.use(unauthorizedMiddleware); 60 - return client; 39 + client.setConfig({ 40 + baseUrl, 41 + headers, 42 + credentials: 'include', 43 + }); 61 44 } 62 45 63 - let apiClient = createApiClient(); 46 + // Set up response interceptor for 401 handling 47 + client.interceptors.response.use(async (response) => { 48 + if (response.status === 401) { 49 + onUnauthorized?.(); 50 + } 51 + return response; 52 + }); 53 + 54 + // Initialize client with default config 55 + updateClientConfig(); 64 56 65 57 export function configureApiClient(url: string) { 66 - baseUrl = url; 67 - apiClient = createApiClient(); 58 + baseUrl = url; 59 + updateClientConfig(); 68 60 } 69 61 70 - export { apiClient }; 62 + export { client }; 71 63 72 64 // Auth types 73 65 export interface AuthUser { 74 - did: string; 75 - handle: string; 76 - displayName: string | null; 77 - avatar: string | null; 66 + did: string; 67 + handle: string; 68 + displayName: string | null; 69 + avatar: string | null; 78 70 } 79 71 80 72 // Auth functions 81 73 export async function getAuthUser(): Promise<AuthUser | null> { 82 - try { 83 - const headers: HeadersInit = {}; 84 - if (sessionToken) { 85 - headers['Authorization'] = `Bearer ${sessionToken}`; 86 - } 74 + try { 75 + const headers: HeadersInit = {}; 76 + if (sessionToken) { 77 + headers['Authorization'] = `Bearer ${sessionToken}`; 78 + } 87 79 88 - const response = await fetch(`${baseUrl}/auth/me`, { 89 - credentials: 'include', 90 - headers, 91 - }); 80 + const response = await fetch(`${baseUrl}/auth/me`, { 81 + credentials: 'include', 82 + headers, 83 + }); 92 84 93 - if (response.status === 401) { 94 - onUnauthorized?.(); 95 - } 85 + if (response.status === 401) { 86 + onUnauthorized?.(); 87 + } 96 88 97 - if (!response.ok) { 98 - return null; 99 - } 89 + if (!response.ok) { 90 + return null; 91 + } 100 92 101 - return response.json(); 102 - } catch { 103 - return null; 104 - } 93 + return response.json(); 94 + } catch { 95 + return null; 96 + } 105 97 } 106 98 107 99 export function getLoginUrl(handle?: string): string { 108 - const params = handle ? `?handle=${encodeURIComponent(handle)}` : ''; 109 - return `${baseUrl}/auth/login${params}`; 100 + const params = handle ? `?handle=${encodeURIComponent(handle)}` : ''; 101 + return `${baseUrl}/auth/login${params}`; 110 102 } 111 103 112 104 export async function logout(): Promise<void> { 113 - const headers: HeadersInit = {}; 114 - if (sessionToken) { 115 - headers['Authorization'] = `Bearer ${sessionToken}`; 116 - } 117 - 118 - await fetch(`${baseUrl}/auth/logout`, { 119 - method: 'POST', 120 - credentials: 'include', 121 - headers, 122 - }); 123 - 124 - // Clear session token on logout 125 - sessionToken = null; 126 - } 127 - 128 - // Movie functions 129 - export async function searchMovies(query: string) { 130 - const { data, error } = await apiClient.GET('/movies/search', { 131 - params: { query: { query } }, 132 - }); 133 - 134 - if (error) throw new Error('Failed to search movies'); 135 - return data; 136 - } 105 + const headers: HeadersInit = {}; 106 + if (sessionToken) { 107 + headers['Authorization'] = `Bearer ${sessionToken}`; 108 + } 137 109 138 - export async function getMovieDetails(movieId: string) { 139 - const { data, error } = await apiClient.GET('/movies/tmdb/{movieId}', { 140 - params: { path: { movieId } }, 141 - }); 142 - 143 - if (error) throw new Error('Failed to get movie details'); 144 - return data; 145 - } 146 - 147 - export async function getUserMovies(userDid: string) { 148 - const { data, error } = await apiClient.GET('/movies/user/{userDid}', { 149 - params: { path: { userDid } }, 150 - }); 151 - 152 - if (error) throw new Error('Failed to get user movies'); 153 - return data; 154 - } 110 + await fetch(`${baseUrl}/auth/logout`, { 111 + method: 'POST', 112 + credentials: 'include', 113 + headers, 114 + }); 155 115 156 - export async function markMovieWatched(movieId: string) { 157 - const { data, error } = await apiClient.POST('/movies/watched', { 158 - body: { movieId }, 159 - }); 160 - 161 - if (error) throw new Error('Failed to mark movie as watched'); 162 - return data; 116 + // Clear session token on logout 117 + sessionToken = null; 118 + updateClientConfig(); 163 119 } 164 - 165 - export async function unmarkMovieWatched(movieId: string) { 166 - const { error } = await apiClient.DELETE('/movies/watched/{movieId}', { 167 - params: { path: { movieId } }, 168 - }); 169 - 170 - if (error) throw new Error('Failed to unmark movie as watched'); 171 - }
+250
packages/api/src/generated/@tanstack/react-query.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import { type DefaultError, queryOptions, type UseMutationOptions } from '@tanstack/react-query'; 4 + 5 + import { client } from '../client.gen'; 6 + import { appControllerGetHello, authControllerCallback, authControllerGetClientMetadata, authControllerLogin, authControllerLogout, authControllerMe, moviesControllerGetMovie, moviesControllerGetMovieDetails, moviesControllerGetUserMovies, moviesControllerMarkWatched, moviesControllerSearchMovies, moviesControllerUnmarkWatched, type Options } from '../sdk.gen'; 7 + import type { AppControllerGetHelloData, AuthControllerCallbackData, AuthControllerGetClientMetadataData, AuthControllerLoginData, AuthControllerLogoutData, AuthControllerMeData, AuthControllerMeResponse, MoviesControllerGetMovieData, MoviesControllerGetMovieDetailsData, MoviesControllerGetMovieResponse, MoviesControllerGetUserMoviesData, MoviesControllerGetUserMoviesResponse, MoviesControllerMarkWatchedData, MoviesControllerMarkWatchedResponse, MoviesControllerSearchMoviesData, MoviesControllerSearchMoviesResponse, MoviesControllerUnmarkWatchedData, MoviesControllerUnmarkWatchedResponse } from '../types.gen'; 8 + 9 + export type QueryKey<TOptions extends Options> = [ 10 + Pick<TOptions, 'baseUrl' | 'body' | 'headers' | 'path' | 'query'> & { 11 + _id: string; 12 + _infinite?: boolean; 13 + tags?: ReadonlyArray<string>; 14 + } 15 + ]; 16 + 17 + const createQueryKey = <TOptions extends Options>(id: string, options?: TOptions, infinite?: boolean, tags?: ReadonlyArray<string>): [ 18 + QueryKey<TOptions>[0] 19 + ] => { 20 + const params: QueryKey<TOptions>[0] = { _id: id, baseUrl: options?.baseUrl || (options?.client ?? client).getConfig().baseUrl } as QueryKey<TOptions>[0]; 21 + if (infinite) { 22 + params._infinite = infinite; 23 + } 24 + if (tags) { 25 + params.tags = tags; 26 + } 27 + if (options?.body) { 28 + params.body = options.body; 29 + } 30 + if (options?.headers) { 31 + params.headers = options.headers; 32 + } 33 + if (options?.path) { 34 + params.path = options.path; 35 + } 36 + if (options?.query) { 37 + params.query = options.query; 38 + } 39 + return [params]; 40 + }; 41 + 42 + export const appControllerGetHelloQueryKey = (options?: Options<AppControllerGetHelloData>) => createQueryKey('appControllerGetHello', options); 43 + 44 + export const appControllerGetHelloOptions = (options?: Options<AppControllerGetHelloData>) => queryOptions<unknown, DefaultError, unknown, ReturnType<typeof appControllerGetHelloQueryKey>>({ 45 + queryFn: async ({ queryKey, signal }) => { 46 + const { data } = await appControllerGetHello({ 47 + ...options, 48 + ...queryKey[0], 49 + signal, 50 + throwOnError: true 51 + }); 52 + return data; 53 + }, 54 + queryKey: appControllerGetHelloQueryKey(options) 55 + }); 56 + 57 + export const moviesControllerSearchMoviesQueryKey = (options: Options<MoviesControllerSearchMoviesData>) => createQueryKey('moviesControllerSearchMovies', options); 58 + 59 + /** 60 + * Search movies from TMDB 61 + */ 62 + export const moviesControllerSearchMoviesOptions = (options: Options<MoviesControllerSearchMoviesData>) => queryOptions<MoviesControllerSearchMoviesResponse, DefaultError, MoviesControllerSearchMoviesResponse, ReturnType<typeof moviesControllerSearchMoviesQueryKey>>({ 63 + queryFn: async ({ queryKey, signal }) => { 64 + const { data } = await moviesControllerSearchMovies({ 65 + ...options, 66 + ...queryKey[0], 67 + signal, 68 + throwOnError: true 69 + }); 70 + return data; 71 + }, 72 + queryKey: moviesControllerSearchMoviesQueryKey(options) 73 + }); 74 + 75 + export const moviesControllerGetMovieDetailsQueryKey = (options: Options<MoviesControllerGetMovieDetailsData>) => createQueryKey('moviesControllerGetMovieDetails', options); 76 + 77 + /** 78 + * Get movie details from TMDB 79 + */ 80 + export const moviesControllerGetMovieDetailsOptions = (options: Options<MoviesControllerGetMovieDetailsData>) => queryOptions<unknown, DefaultError, unknown, ReturnType<typeof moviesControllerGetMovieDetailsQueryKey>>({ 81 + queryFn: async ({ queryKey, signal }) => { 82 + const { data } = await moviesControllerGetMovieDetails({ 83 + ...options, 84 + ...queryKey[0], 85 + signal, 86 + throwOnError: true 87 + }); 88 + return data; 89 + }, 90 + queryKey: moviesControllerGetMovieDetailsQueryKey(options) 91 + }); 92 + 93 + export const moviesControllerGetUserMoviesQueryKey = (options: Options<MoviesControllerGetUserMoviesData>) => createQueryKey('moviesControllerGetUserMovies', options); 94 + 95 + /** 96 + * Get tracked movies for a user 97 + */ 98 + export const moviesControllerGetUserMoviesOptions = (options: Options<MoviesControllerGetUserMoviesData>) => queryOptions<MoviesControllerGetUserMoviesResponse, DefaultError, MoviesControllerGetUserMoviesResponse, ReturnType<typeof moviesControllerGetUserMoviesQueryKey>>({ 99 + queryFn: async ({ queryKey, signal }) => { 100 + const { data } = await moviesControllerGetUserMovies({ 101 + ...options, 102 + ...queryKey[0], 103 + signal, 104 + throwOnError: true 105 + }); 106 + return data; 107 + }, 108 + queryKey: moviesControllerGetUserMoviesQueryKey(options) 109 + }); 110 + 111 + /** 112 + * Mark a movie as watched 113 + */ 114 + export const moviesControllerMarkWatchedMutation = (options?: Partial<Options<MoviesControllerMarkWatchedData>>): UseMutationOptions<MoviesControllerMarkWatchedResponse, DefaultError, Options<MoviesControllerMarkWatchedData>> => { 115 + const mutationOptions: UseMutationOptions<MoviesControllerMarkWatchedResponse, DefaultError, Options<MoviesControllerMarkWatchedData>> = { 116 + mutationFn: async (fnOptions) => { 117 + const { data } = await moviesControllerMarkWatched({ 118 + ...options, 119 + ...fnOptions, 120 + throwOnError: true 121 + }); 122 + return data; 123 + } 124 + }; 125 + return mutationOptions; 126 + }; 127 + 128 + /** 129 + * Unmark a movie as watched 130 + */ 131 + export const moviesControllerUnmarkWatchedMutation = (options?: Partial<Options<MoviesControllerUnmarkWatchedData>>): UseMutationOptions<MoviesControllerUnmarkWatchedResponse, DefaultError, Options<MoviesControllerUnmarkWatchedData>> => { 132 + const mutationOptions: UseMutationOptions<MoviesControllerUnmarkWatchedResponse, DefaultError, Options<MoviesControllerUnmarkWatchedData>> = { 133 + mutationFn: async (fnOptions) => { 134 + const { data } = await moviesControllerUnmarkWatched({ 135 + ...options, 136 + ...fnOptions, 137 + throwOnError: true 138 + }); 139 + return data; 140 + } 141 + }; 142 + return mutationOptions; 143 + }; 144 + 145 + export const moviesControllerGetMovieQueryKey = (options: Options<MoviesControllerGetMovieData>) => createQueryKey('moviesControllerGetMovie', options); 146 + 147 + /** 148 + * Get movie from database 149 + */ 150 + export const moviesControllerGetMovieOptions = (options: Options<MoviesControllerGetMovieData>) => queryOptions<MoviesControllerGetMovieResponse, DefaultError, MoviesControllerGetMovieResponse, ReturnType<typeof moviesControllerGetMovieQueryKey>>({ 151 + queryFn: async ({ queryKey, signal }) => { 152 + const { data } = await moviesControllerGetMovie({ 153 + ...options, 154 + ...queryKey[0], 155 + signal, 156 + throwOnError: true 157 + }); 158 + return data; 159 + }, 160 + queryKey: moviesControllerGetMovieQueryKey(options) 161 + }); 162 + 163 + export const authControllerGetClientMetadataQueryKey = (options?: Options<AuthControllerGetClientMetadataData>) => createQueryKey('authControllerGetClientMetadata', options); 164 + 165 + /** 166 + * OAuth client metadata 167 + */ 168 + export const authControllerGetClientMetadataOptions = (options?: Options<AuthControllerGetClientMetadataData>) => queryOptions<unknown, DefaultError, unknown, ReturnType<typeof authControllerGetClientMetadataQueryKey>>({ 169 + queryFn: async ({ queryKey, signal }) => { 170 + const { data } = await authControllerGetClientMetadata({ 171 + ...options, 172 + ...queryKey[0], 173 + signal, 174 + throwOnError: true 175 + }); 176 + return data; 177 + }, 178 + queryKey: authControllerGetClientMetadataQueryKey(options) 179 + }); 180 + 181 + export const authControllerLoginQueryKey = (options?: Options<AuthControllerLoginData>) => createQueryKey('authControllerLogin', options); 182 + 183 + /** 184 + * Start AT Protocol OAuth login 185 + */ 186 + export const authControllerLoginOptions = (options?: Options<AuthControllerLoginData>) => queryOptions<unknown, DefaultError, unknown, ReturnType<typeof authControllerLoginQueryKey>>({ 187 + queryFn: async ({ queryKey, signal }) => { 188 + const { data } = await authControllerLogin({ 189 + ...options, 190 + ...queryKey[0], 191 + signal, 192 + throwOnError: true 193 + }); 194 + return data; 195 + }, 196 + queryKey: authControllerLoginQueryKey(options) 197 + }); 198 + 199 + export const authControllerCallbackQueryKey = (options?: Options<AuthControllerCallbackData>) => createQueryKey('authControllerCallback', options); 200 + 201 + /** 202 + * AT Protocol OAuth callback 203 + */ 204 + export const authControllerCallbackOptions = (options?: Options<AuthControllerCallbackData>) => queryOptions<unknown, DefaultError, unknown, ReturnType<typeof authControllerCallbackQueryKey>>({ 205 + queryFn: async ({ queryKey, signal }) => { 206 + const { data } = await authControllerCallback({ 207 + ...options, 208 + ...queryKey[0], 209 + signal, 210 + throwOnError: true 211 + }); 212 + return data; 213 + }, 214 + queryKey: authControllerCallbackQueryKey(options) 215 + }); 216 + 217 + export const authControllerMeQueryKey = (options?: Options<AuthControllerMeData>) => createQueryKey('authControllerMe', options); 218 + 219 + /** 220 + * Get current authenticated user 221 + */ 222 + export const authControllerMeOptions = (options?: Options<AuthControllerMeData>) => queryOptions<AuthControllerMeResponse, DefaultError, AuthControllerMeResponse, ReturnType<typeof authControllerMeQueryKey>>({ 223 + queryFn: async ({ queryKey, signal }) => { 224 + const { data } = await authControllerMe({ 225 + ...options, 226 + ...queryKey[0], 227 + signal, 228 + throwOnError: true 229 + }); 230 + return data; 231 + }, 232 + queryKey: authControllerMeQueryKey(options) 233 + }); 234 + 235 + /** 236 + * Logout and clear session 237 + */ 238 + export const authControllerLogoutMutation = (options?: Partial<Options<AuthControllerLogoutData>>): UseMutationOptions<unknown, DefaultError, Options<AuthControllerLogoutData>> => { 239 + const mutationOptions: UseMutationOptions<unknown, DefaultError, Options<AuthControllerLogoutData>> = { 240 + mutationFn: async (fnOptions) => { 241 + const { data } = await authControllerLogout({ 242 + ...options, 243 + ...fnOptions, 244 + throwOnError: true 245 + }); 246 + return data; 247 + } 248 + }; 249 + return mutationOptions; 250 + };
+16
packages/api/src/generated/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import { type ClientOptions, type Config, createClient, createConfig } from './client'; 4 + import type { ClientOptions as ClientOptions2 } from './types.gen'; 5 + 6 + /** 7 + * The `createClientConfig()` function will be called on client initialization 8 + * and the returned object will become the client's initial configuration. 9 + * 10 + * You may want to initialize your client this way instead of calling 11 + * `setConfig()`. This is useful for example if you're using Next.js 12 + * to ensure your client always has the correct values. 13 + */ 14 + export type CreateClientConfig<T extends ClientOptions = ClientOptions2> = (override?: Config<ClientOptions & T>) => Config<Required<ClientOptions> & T>; 15 + 16 + export const client = createClient(createConfig<ClientOptions2>());
+288
packages/api/src/generated/client/client.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import { createSseClient } from '../core/serverSentEvents.gen'; 4 + import type { HttpMethod } from '../core/types.gen'; 5 + import { getValidRequestBody } from '../core/utils.gen'; 6 + import type { Client, Config, RequestOptions, ResolvedRequestOptions } from './types.gen'; 7 + import { 8 + buildUrl, 9 + createConfig, 10 + createInterceptors, 11 + getParseAs, 12 + mergeConfigs, 13 + mergeHeaders, 14 + setAuthParams, 15 + } from './utils.gen'; 16 + 17 + type ReqInit = Omit<RequestInit, 'body' | 'headers'> & { 18 + body?: any; 19 + headers: ReturnType<typeof mergeHeaders>; 20 + }; 21 + 22 + export const createClient = (config: Config = {}): Client => { 23 + let _config = mergeConfigs(createConfig(), config); 24 + 25 + const getConfig = (): Config => ({ ..._config }); 26 + 27 + const setConfig = (config: Config): Config => { 28 + _config = mergeConfigs(_config, config); 29 + return getConfig(); 30 + }; 31 + 32 + const interceptors = createInterceptors<Request, Response, unknown, ResolvedRequestOptions>(); 33 + 34 + const beforeRequest = async (options: RequestOptions) => { 35 + const opts = { 36 + ..._config, 37 + ...options, 38 + fetch: options.fetch ?? _config.fetch ?? globalThis.fetch, 39 + headers: mergeHeaders(_config.headers, options.headers), 40 + serializedBody: undefined, 41 + }; 42 + 43 + if (opts.security) { 44 + await setAuthParams({ 45 + ...opts, 46 + security: opts.security, 47 + }); 48 + } 49 + 50 + if (opts.requestValidator) { 51 + await opts.requestValidator(opts); 52 + } 53 + 54 + if (opts.body !== undefined && opts.bodySerializer) { 55 + opts.serializedBody = opts.bodySerializer(opts.body); 56 + } 57 + 58 + // remove Content-Type header if body is empty to avoid sending invalid requests 59 + if (opts.body === undefined || opts.serializedBody === '') { 60 + opts.headers.delete('Content-Type'); 61 + } 62 + 63 + const url = buildUrl(opts); 64 + 65 + return { opts, url }; 66 + }; 67 + 68 + const request: Client['request'] = async (options) => { 69 + // @ts-expect-error 70 + const { opts, url } = await beforeRequest(options); 71 + const requestInit: ReqInit = { 72 + redirect: 'follow', 73 + ...opts, 74 + body: getValidRequestBody(opts), 75 + }; 76 + 77 + let request = new Request(url, requestInit); 78 + 79 + for (const fn of interceptors.request.fns) { 80 + if (fn) { 81 + request = await fn(request, opts); 82 + } 83 + } 84 + 85 + // fetch must be assigned here, otherwise it would throw the error: 86 + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation 87 + const _fetch = opts.fetch!; 88 + let response: Response; 89 + 90 + try { 91 + response = await _fetch(request); 92 + } catch (error) { 93 + // Handle fetch exceptions (AbortError, network errors, etc.) 94 + let finalError = error; 95 + 96 + for (const fn of interceptors.error.fns) { 97 + if (fn) { 98 + finalError = (await fn(error, undefined as any, request, opts)) as unknown; 99 + } 100 + } 101 + 102 + finalError = finalError || ({} as unknown); 103 + 104 + if (opts.throwOnError) { 105 + throw finalError; 106 + } 107 + 108 + // Return error response 109 + return opts.responseStyle === 'data' 110 + ? undefined 111 + : { 112 + error: finalError, 113 + request, 114 + response: undefined as any, 115 + }; 116 + } 117 + 118 + for (const fn of interceptors.response.fns) { 119 + if (fn) { 120 + response = await fn(response, request, opts); 121 + } 122 + } 123 + 124 + const result = { 125 + request, 126 + response, 127 + }; 128 + 129 + if (response.ok) { 130 + const parseAs = 131 + (opts.parseAs === 'auto' 132 + ? getParseAs(response.headers.get('Content-Type')) 133 + : opts.parseAs) ?? 'json'; 134 + 135 + if (response.status === 204 || response.headers.get('Content-Length') === '0') { 136 + let emptyData: any; 137 + switch (parseAs) { 138 + case 'arrayBuffer': 139 + case 'blob': 140 + case 'text': 141 + emptyData = await response[parseAs](); 142 + break; 143 + case 'formData': 144 + emptyData = new FormData(); 145 + break; 146 + case 'stream': 147 + emptyData = response.body; 148 + break; 149 + case 'json': 150 + default: 151 + emptyData = {}; 152 + break; 153 + } 154 + return opts.responseStyle === 'data' 155 + ? emptyData 156 + : { 157 + data: emptyData, 158 + ...result, 159 + }; 160 + } 161 + 162 + let data: any; 163 + switch (parseAs) { 164 + case 'arrayBuffer': 165 + case 'blob': 166 + case 'formData': 167 + case 'text': 168 + data = await response[parseAs](); 169 + break; 170 + case 'json': { 171 + // Some servers return 200 with no Content-Length and empty body. 172 + // response.json() would throw; read as text and parse if non-empty. 173 + const text = await response.text(); 174 + data = text ? JSON.parse(text) : {}; 175 + break; 176 + } 177 + case 'stream': 178 + return opts.responseStyle === 'data' 179 + ? response.body 180 + : { 181 + data: response.body, 182 + ...result, 183 + }; 184 + } 185 + 186 + if (parseAs === 'json') { 187 + if (opts.responseValidator) { 188 + await opts.responseValidator(data); 189 + } 190 + 191 + if (opts.responseTransformer) { 192 + data = await opts.responseTransformer(data); 193 + } 194 + } 195 + 196 + return opts.responseStyle === 'data' 197 + ? data 198 + : { 199 + data, 200 + ...result, 201 + }; 202 + } 203 + 204 + const textError = await response.text(); 205 + let jsonError: unknown; 206 + 207 + try { 208 + jsonError = JSON.parse(textError); 209 + } catch { 210 + // noop 211 + } 212 + 213 + const error = jsonError ?? textError; 214 + let finalError = error; 215 + 216 + for (const fn of interceptors.error.fns) { 217 + if (fn) { 218 + finalError = (await fn(error, response, request, opts)) as string; 219 + } 220 + } 221 + 222 + finalError = finalError || ({} as string); 223 + 224 + if (opts.throwOnError) { 225 + throw finalError; 226 + } 227 + 228 + // TODO: we probably want to return error and improve types 229 + return opts.responseStyle === 'data' 230 + ? undefined 231 + : { 232 + error: finalError, 233 + ...result, 234 + }; 235 + }; 236 + 237 + const makeMethodFn = (method: Uppercase<HttpMethod>) => (options: RequestOptions) => 238 + request({ ...options, method }); 239 + 240 + const makeSseFn = (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => { 241 + const { opts, url } = await beforeRequest(options); 242 + return createSseClient({ 243 + ...opts, 244 + body: opts.body as BodyInit | null | undefined, 245 + headers: opts.headers as unknown as Record<string, string>, 246 + method, 247 + onRequest: async (url, init) => { 248 + let request = new Request(url, init); 249 + for (const fn of interceptors.request.fns) { 250 + if (fn) { 251 + request = await fn(request, opts); 252 + } 253 + } 254 + return request; 255 + }, 256 + serializedBody: getValidRequestBody(opts) as BodyInit | null | undefined, 257 + url, 258 + }); 259 + }; 260 + 261 + return { 262 + buildUrl, 263 + connect: makeMethodFn('CONNECT'), 264 + delete: makeMethodFn('DELETE'), 265 + get: makeMethodFn('GET'), 266 + getConfig, 267 + head: makeMethodFn('HEAD'), 268 + interceptors, 269 + options: makeMethodFn('OPTIONS'), 270 + patch: makeMethodFn('PATCH'), 271 + post: makeMethodFn('POST'), 272 + put: makeMethodFn('PUT'), 273 + request, 274 + setConfig, 275 + sse: { 276 + connect: makeSseFn('CONNECT'), 277 + delete: makeSseFn('DELETE'), 278 + get: makeSseFn('GET'), 279 + head: makeSseFn('HEAD'), 280 + options: makeSseFn('OPTIONS'), 281 + patch: makeSseFn('PATCH'), 282 + post: makeSseFn('POST'), 283 + put: makeSseFn('PUT'), 284 + trace: makeSseFn('TRACE'), 285 + }, 286 + trace: makeMethodFn('TRACE'), 287 + } as Client; 288 + };
+25
packages/api/src/generated/client/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type { Auth } from '../core/auth.gen'; 4 + export type { QuerySerializerOptions } from '../core/bodySerializer.gen'; 5 + export { 6 + formDataBodySerializer, 7 + jsonBodySerializer, 8 + urlSearchParamsBodySerializer, 9 + } from '../core/bodySerializer.gen'; 10 + export { buildClientParams } from '../core/params.gen'; 11 + export { serializeQueryKeyValue } from '../core/queryKeySerializer.gen'; 12 + export { createClient } from './client.gen'; 13 + export type { 14 + Client, 15 + ClientOptions, 16 + Config, 17 + CreateClientConfig, 18 + Options, 19 + RequestOptions, 20 + RequestResult, 21 + ResolvedRequestOptions, 22 + ResponseStyle, 23 + TDataShape, 24 + } from './types.gen'; 25 + export { createConfig, mergeHeaders } from './utils.gen';
+213
packages/api/src/generated/client/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Auth } from '../core/auth.gen'; 4 + import type { 5 + ServerSentEventsOptions, 6 + ServerSentEventsResult, 7 + } from '../core/serverSentEvents.gen'; 8 + import type { Client as CoreClient, Config as CoreConfig } from '../core/types.gen'; 9 + import type { Middleware } from './utils.gen'; 10 + 11 + export type ResponseStyle = 'data' | 'fields'; 12 + 13 + export interface Config<T extends ClientOptions = ClientOptions> 14 + extends Omit<RequestInit, 'body' | 'headers' | 'method'>, CoreConfig { 15 + /** 16 + * Base URL for all requests made by this client. 17 + */ 18 + baseUrl?: T['baseUrl']; 19 + /** 20 + * Fetch API implementation. You can use this option to provide a custom 21 + * fetch instance. 22 + * 23 + * @default globalThis.fetch 24 + */ 25 + fetch?: typeof fetch; 26 + /** 27 + * Please don't use the Fetch client for Next.js applications. The `next` 28 + * options won't have any effect. 29 + * 30 + * Install {@link https://www.npmjs.com/package/@hey-api/client-next `@hey-api/client-next`} instead. 31 + */ 32 + next?: never; 33 + /** 34 + * Return the response data parsed in a specified format. By default, `auto` 35 + * will infer the appropriate method from the `Content-Type` response header. 36 + * You can override this behavior with any of the {@link Body} methods. 37 + * Select `stream` if you don't want to parse response data at all. 38 + * 39 + * @default 'auto' 40 + */ 41 + parseAs?: 'arrayBuffer' | 'auto' | 'blob' | 'formData' | 'json' | 'stream' | 'text'; 42 + /** 43 + * Should we return only data or multiple fields (data, error, response, etc.)? 44 + * 45 + * @default 'fields' 46 + */ 47 + responseStyle?: ResponseStyle; 48 + /** 49 + * Throw an error instead of returning it in the response? 50 + * 51 + * @default false 52 + */ 53 + throwOnError?: T['throwOnError']; 54 + } 55 + 56 + export interface RequestOptions< 57 + TData = unknown, 58 + TResponseStyle extends ResponseStyle = 'fields', 59 + ThrowOnError extends boolean = boolean, 60 + Url extends string = string, 61 + > 62 + extends 63 + Config<{ 64 + responseStyle: TResponseStyle; 65 + throwOnError: ThrowOnError; 66 + }>, 67 + Pick< 68 + ServerSentEventsOptions<TData>, 69 + | 'onSseError' 70 + | 'onSseEvent' 71 + | 'sseDefaultRetryDelay' 72 + | 'sseMaxRetryAttempts' 73 + | 'sseMaxRetryDelay' 74 + > { 75 + /** 76 + * Any body that you want to add to your request. 77 + * 78 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#body} 79 + */ 80 + body?: unknown; 81 + path?: Record<string, unknown>; 82 + query?: Record<string, unknown>; 83 + /** 84 + * Security mechanism(s) to use for the request. 85 + */ 86 + security?: ReadonlyArray<Auth>; 87 + url: Url; 88 + } 89 + 90 + export interface ResolvedRequestOptions< 91 + TResponseStyle extends ResponseStyle = 'fields', 92 + ThrowOnError extends boolean = boolean, 93 + Url extends string = string, 94 + > extends RequestOptions<unknown, TResponseStyle, ThrowOnError, Url> { 95 + serializedBody?: string; 96 + } 97 + 98 + export type RequestResult< 99 + TData = unknown, 100 + TError = unknown, 101 + ThrowOnError extends boolean = boolean, 102 + TResponseStyle extends ResponseStyle = 'fields', 103 + > = ThrowOnError extends true 104 + ? Promise< 105 + TResponseStyle extends 'data' 106 + ? TData extends Record<string, unknown> 107 + ? TData[keyof TData] 108 + : TData 109 + : { 110 + data: TData extends Record<string, unknown> ? TData[keyof TData] : TData; 111 + request: Request; 112 + response: Response; 113 + } 114 + > 115 + : Promise< 116 + TResponseStyle extends 'data' 117 + ? (TData extends Record<string, unknown> ? TData[keyof TData] : TData) | undefined 118 + : ( 119 + | { 120 + data: TData extends Record<string, unknown> ? TData[keyof TData] : TData; 121 + error: undefined; 122 + } 123 + | { 124 + data: undefined; 125 + error: TError extends Record<string, unknown> ? TError[keyof TError] : TError; 126 + } 127 + ) & { 128 + request: Request; 129 + response: Response; 130 + } 131 + >; 132 + 133 + export interface ClientOptions { 134 + baseUrl?: string; 135 + responseStyle?: ResponseStyle; 136 + throwOnError?: boolean; 137 + } 138 + 139 + type MethodFn = < 140 + TData = unknown, 141 + TError = unknown, 142 + ThrowOnError extends boolean = false, 143 + TResponseStyle extends ResponseStyle = 'fields', 144 + >( 145 + options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'>, 146 + ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 147 + 148 + type SseFn = < 149 + TData = unknown, 150 + TError = unknown, 151 + ThrowOnError extends boolean = false, 152 + TResponseStyle extends ResponseStyle = 'fields', 153 + >( 154 + options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'>, 155 + ) => Promise<ServerSentEventsResult<TData, TError>>; 156 + 157 + type RequestFn = < 158 + TData = unknown, 159 + TError = unknown, 160 + ThrowOnError extends boolean = false, 161 + TResponseStyle extends ResponseStyle = 'fields', 162 + >( 163 + options: Omit<RequestOptions<TData, TResponseStyle, ThrowOnError>, 'method'> & 164 + Pick<Required<RequestOptions<TData, TResponseStyle, ThrowOnError>>, 'method'>, 165 + ) => RequestResult<TData, TError, ThrowOnError, TResponseStyle>; 166 + 167 + type BuildUrlFn = < 168 + TData extends { 169 + body?: unknown; 170 + path?: Record<string, unknown>; 171 + query?: Record<string, unknown>; 172 + url: string; 173 + }, 174 + >( 175 + options: TData & Options<TData>, 176 + ) => string; 177 + 178 + export type Client = CoreClient<RequestFn, Config, MethodFn, BuildUrlFn, SseFn> & { 179 + interceptors: Middleware<Request, Response, unknown, ResolvedRequestOptions>; 180 + }; 181 + 182 + /** 183 + * The `createClientConfig()` function will be called on client initialization 184 + * and the returned object will become the client's initial configuration. 185 + * 186 + * You may want to initialize your client this way instead of calling 187 + * `setConfig()`. This is useful for example if you're using Next.js 188 + * to ensure your client always has the correct values. 189 + */ 190 + export type CreateClientConfig<T extends ClientOptions = ClientOptions> = ( 191 + override?: Config<ClientOptions & T>, 192 + ) => Config<Required<ClientOptions> & T>; 193 + 194 + export interface TDataShape { 195 + body?: unknown; 196 + headers?: unknown; 197 + path?: unknown; 198 + query?: unknown; 199 + url: string; 200 + } 201 + 202 + type OmitKeys<T, K> = Pick<T, Exclude<keyof T, K>>; 203 + 204 + export type Options< 205 + TData extends TDataShape = TDataShape, 206 + ThrowOnError extends boolean = boolean, 207 + TResponse = unknown, 208 + TResponseStyle extends ResponseStyle = 'fields', 209 + > = OmitKeys< 210 + RequestOptions<TResponse, TResponseStyle, ThrowOnError>, 211 + 'body' | 'path' | 'query' | 'url' 212 + > & 213 + ([TData] extends [never] ? unknown : Omit<TData, 'url'>);
+316
packages/api/src/generated/client/utils.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import { getAuthToken } from '../core/auth.gen'; 4 + import type { QuerySerializerOptions } from '../core/bodySerializer.gen'; 5 + import { jsonBodySerializer } from '../core/bodySerializer.gen'; 6 + import { 7 + serializeArrayParam, 8 + serializeObjectParam, 9 + serializePrimitiveParam, 10 + } from '../core/pathSerializer.gen'; 11 + import { getUrl } from '../core/utils.gen'; 12 + import type { Client, ClientOptions, Config, RequestOptions } from './types.gen'; 13 + 14 + export const createQuerySerializer = <T = unknown>({ 15 + parameters = {}, 16 + ...args 17 + }: QuerySerializerOptions = {}) => { 18 + const querySerializer = (queryParams: T) => { 19 + const search: string[] = []; 20 + if (queryParams && typeof queryParams === 'object') { 21 + for (const name in queryParams) { 22 + const value = queryParams[name]; 23 + 24 + if (value === undefined || value === null) { 25 + continue; 26 + } 27 + 28 + const options = parameters[name] || args; 29 + 30 + if (Array.isArray(value)) { 31 + const serializedArray = serializeArrayParam({ 32 + allowReserved: options.allowReserved, 33 + explode: true, 34 + name, 35 + style: 'form', 36 + value, 37 + ...options.array, 38 + }); 39 + if (serializedArray) search.push(serializedArray); 40 + } else if (typeof value === 'object') { 41 + const serializedObject = serializeObjectParam({ 42 + allowReserved: options.allowReserved, 43 + explode: true, 44 + name, 45 + style: 'deepObject', 46 + value: value as Record<string, unknown>, 47 + ...options.object, 48 + }); 49 + if (serializedObject) search.push(serializedObject); 50 + } else { 51 + const serializedPrimitive = serializePrimitiveParam({ 52 + allowReserved: options.allowReserved, 53 + name, 54 + value: value as string, 55 + }); 56 + if (serializedPrimitive) search.push(serializedPrimitive); 57 + } 58 + } 59 + } 60 + return search.join('&'); 61 + }; 62 + return querySerializer; 63 + }; 64 + 65 + /** 66 + * Infers parseAs value from provided Content-Type header. 67 + */ 68 + export const getParseAs = (contentType: string | null): Exclude<Config['parseAs'], 'auto'> => { 69 + if (!contentType) { 70 + // If no Content-Type header is provided, the best we can do is return the raw response body, 71 + // which is effectively the same as the 'stream' option. 72 + return 'stream'; 73 + } 74 + 75 + const cleanContent = contentType.split(';')[0]?.trim(); 76 + 77 + if (!cleanContent) { 78 + return; 79 + } 80 + 81 + if (cleanContent.startsWith('application/json') || cleanContent.endsWith('+json')) { 82 + return 'json'; 83 + } 84 + 85 + if (cleanContent === 'multipart/form-data') { 86 + return 'formData'; 87 + } 88 + 89 + if ( 90 + ['application/', 'audio/', 'image/', 'video/'].some((type) => cleanContent.startsWith(type)) 91 + ) { 92 + return 'blob'; 93 + } 94 + 95 + if (cleanContent.startsWith('text/')) { 96 + return 'text'; 97 + } 98 + 99 + return; 100 + }; 101 + 102 + const checkForExistence = ( 103 + options: Pick<RequestOptions, 'auth' | 'query'> & { 104 + headers: Headers; 105 + }, 106 + name?: string, 107 + ): boolean => { 108 + if (!name) { 109 + return false; 110 + } 111 + if ( 112 + options.headers.has(name) || 113 + options.query?.[name] || 114 + options.headers.get('Cookie')?.includes(`${name}=`) 115 + ) { 116 + return true; 117 + } 118 + return false; 119 + }; 120 + 121 + export const setAuthParams = async ({ 122 + security, 123 + ...options 124 + }: Pick<Required<RequestOptions>, 'security'> & 125 + Pick<RequestOptions, 'auth' | 'query'> & { 126 + headers: Headers; 127 + }) => { 128 + for (const auth of security) { 129 + if (checkForExistence(options, auth.name)) { 130 + continue; 131 + } 132 + 133 + const token = await getAuthToken(auth, options.auth); 134 + 135 + if (!token) { 136 + continue; 137 + } 138 + 139 + const name = auth.name ?? 'Authorization'; 140 + 141 + switch (auth.in) { 142 + case 'query': 143 + if (!options.query) { 144 + options.query = {}; 145 + } 146 + options.query[name] = token; 147 + break; 148 + case 'cookie': 149 + options.headers.append('Cookie', `${name}=${token}`); 150 + break; 151 + case 'header': 152 + default: 153 + options.headers.set(name, token); 154 + break; 155 + } 156 + } 157 + }; 158 + 159 + export const buildUrl: Client['buildUrl'] = (options) => 160 + getUrl({ 161 + baseUrl: options.baseUrl as string, 162 + path: options.path, 163 + query: options.query, 164 + querySerializer: 165 + typeof options.querySerializer === 'function' 166 + ? options.querySerializer 167 + : createQuerySerializer(options.querySerializer), 168 + url: options.url, 169 + }); 170 + 171 + export const mergeConfigs = (a: Config, b: Config): Config => { 172 + const config = { ...a, ...b }; 173 + if (config.baseUrl?.endsWith('/')) { 174 + config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1); 175 + } 176 + config.headers = mergeHeaders(a.headers, b.headers); 177 + return config; 178 + }; 179 + 180 + const headersEntries = (headers: Headers): Array<[string, string]> => { 181 + const entries: Array<[string, string]> = []; 182 + headers.forEach((value, key) => { 183 + entries.push([key, value]); 184 + }); 185 + return entries; 186 + }; 187 + 188 + export const mergeHeaders = ( 189 + ...headers: Array<Required<Config>['headers'] | undefined> 190 + ): Headers => { 191 + const mergedHeaders = new Headers(); 192 + for (const header of headers) { 193 + if (!header) { 194 + continue; 195 + } 196 + 197 + const iterator = header instanceof Headers ? headersEntries(header) : Object.entries(header); 198 + 199 + for (const [key, value] of iterator) { 200 + if (value === null) { 201 + mergedHeaders.delete(key); 202 + } else if (Array.isArray(value)) { 203 + for (const v of value) { 204 + mergedHeaders.append(key, v as string); 205 + } 206 + } else if (value !== undefined) { 207 + // assume object headers are meant to be JSON stringified, i.e. their 208 + // content value in OpenAPI specification is 'application/json' 209 + mergedHeaders.set( 210 + key, 211 + typeof value === 'object' ? JSON.stringify(value) : (value as string), 212 + ); 213 + } 214 + } 215 + } 216 + return mergedHeaders; 217 + }; 218 + 219 + type ErrInterceptor<Err, Res, Req, Options> = ( 220 + error: Err, 221 + response: Res, 222 + request: Req, 223 + options: Options, 224 + ) => Err | Promise<Err>; 225 + 226 + type ReqInterceptor<Req, Options> = (request: Req, options: Options) => Req | Promise<Req>; 227 + 228 + type ResInterceptor<Res, Req, Options> = ( 229 + response: Res, 230 + request: Req, 231 + options: Options, 232 + ) => Res | Promise<Res>; 233 + 234 + class Interceptors<Interceptor> { 235 + fns: Array<Interceptor | null> = []; 236 + 237 + clear(): void { 238 + this.fns = []; 239 + } 240 + 241 + eject(id: number | Interceptor): void { 242 + const index = this.getInterceptorIndex(id); 243 + if (this.fns[index]) { 244 + this.fns[index] = null; 245 + } 246 + } 247 + 248 + exists(id: number | Interceptor): boolean { 249 + const index = this.getInterceptorIndex(id); 250 + return Boolean(this.fns[index]); 251 + } 252 + 253 + getInterceptorIndex(id: number | Interceptor): number { 254 + if (typeof id === 'number') { 255 + return this.fns[id] ? id : -1; 256 + } 257 + return this.fns.indexOf(id); 258 + } 259 + 260 + update(id: number | Interceptor, fn: Interceptor): number | Interceptor | false { 261 + const index = this.getInterceptorIndex(id); 262 + if (this.fns[index]) { 263 + this.fns[index] = fn; 264 + return id; 265 + } 266 + return false; 267 + } 268 + 269 + use(fn: Interceptor): number { 270 + this.fns.push(fn); 271 + return this.fns.length - 1; 272 + } 273 + } 274 + 275 + export interface Middleware<Req, Res, Err, Options> { 276 + error: Interceptors<ErrInterceptor<Err, Res, Req, Options>>; 277 + request: Interceptors<ReqInterceptor<Req, Options>>; 278 + response: Interceptors<ResInterceptor<Res, Req, Options>>; 279 + } 280 + 281 + export const createInterceptors = <Req, Res, Err, Options>(): Middleware< 282 + Req, 283 + Res, 284 + Err, 285 + Options 286 + > => ({ 287 + error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(), 288 + request: new Interceptors<ReqInterceptor<Req, Options>>(), 289 + response: new Interceptors<ResInterceptor<Res, Req, Options>>(), 290 + }); 291 + 292 + const defaultQuerySerializer = createQuerySerializer({ 293 + allowReserved: false, 294 + array: { 295 + explode: true, 296 + style: 'form', 297 + }, 298 + object: { 299 + explode: true, 300 + style: 'deepObject', 301 + }, 302 + }); 303 + 304 + const defaultHeaders = { 305 + 'Content-Type': 'application/json', 306 + }; 307 + 308 + export const createConfig = <T extends ClientOptions = ClientOptions>( 309 + override: Config<Omit<ClientOptions, keyof T> & T> = {}, 310 + ): Config<Omit<ClientOptions, keyof T> & T> => ({ 311 + ...jsonBodySerializer, 312 + headers: defaultHeaders, 313 + parseAs: 'auto', 314 + querySerializer: defaultQuerySerializer, 315 + ...override, 316 + });
+41
packages/api/src/generated/core/auth.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type AuthToken = string | undefined; 4 + 5 + export interface Auth { 6 + /** 7 + * Which part of the request do we use to send the auth? 8 + * 9 + * @default 'header' 10 + */ 11 + in?: 'header' | 'query' | 'cookie'; 12 + /** 13 + * Header or query parameter name. 14 + * 15 + * @default 'Authorization' 16 + */ 17 + name?: string; 18 + scheme?: 'basic' | 'bearer'; 19 + type: 'apiKey' | 'http'; 20 + } 21 + 22 + export const getAuthToken = async ( 23 + auth: Auth, 24 + callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken, 25 + ): Promise<string | undefined> => { 26 + const token = typeof callback === 'function' ? await callback(auth) : callback; 27 + 28 + if (!token) { 29 + return; 30 + } 31 + 32 + if (auth.scheme === 'bearer') { 33 + return `Bearer ${token}`; 34 + } 35 + 36 + if (auth.scheme === 'basic') { 37 + return `Basic ${btoa(token)}`; 38 + } 39 + 40 + return token; 41 + };
+84
packages/api/src/generated/core/bodySerializer.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { ArrayStyle, ObjectStyle, SerializerOptions } from './pathSerializer.gen'; 4 + 5 + export type QuerySerializer = (query: Record<string, unknown>) => string; 6 + 7 + export type BodySerializer = (body: any) => any; 8 + 9 + type QuerySerializerOptionsObject = { 10 + allowReserved?: boolean; 11 + array?: Partial<SerializerOptions<ArrayStyle>>; 12 + object?: Partial<SerializerOptions<ObjectStyle>>; 13 + }; 14 + 15 + export type QuerySerializerOptions = QuerySerializerOptionsObject & { 16 + /** 17 + * Per-parameter serialization overrides. When provided, these settings 18 + * override the global array/object settings for specific parameter names. 19 + */ 20 + parameters?: Record<string, QuerySerializerOptionsObject>; 21 + }; 22 + 23 + const serializeFormDataPair = (data: FormData, key: string, value: unknown): void => { 24 + if (typeof value === 'string' || value instanceof Blob) { 25 + data.append(key, value); 26 + } else if (value instanceof Date) { 27 + data.append(key, value.toISOString()); 28 + } else { 29 + data.append(key, JSON.stringify(value)); 30 + } 31 + }; 32 + 33 + const serializeUrlSearchParamsPair = (data: URLSearchParams, key: string, value: unknown): void => { 34 + if (typeof value === 'string') { 35 + data.append(key, value); 36 + } else { 37 + data.append(key, JSON.stringify(value)); 38 + } 39 + }; 40 + 41 + export const formDataBodySerializer = { 42 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>( 43 + body: T, 44 + ): FormData => { 45 + const data = new FormData(); 46 + 47 + Object.entries(body).forEach(([key, value]) => { 48 + if (value === undefined || value === null) { 49 + return; 50 + } 51 + if (Array.isArray(value)) { 52 + value.forEach((v) => serializeFormDataPair(data, key, v)); 53 + } else { 54 + serializeFormDataPair(data, key, value); 55 + } 56 + }); 57 + 58 + return data; 59 + }, 60 + }; 61 + 62 + export const jsonBodySerializer = { 63 + bodySerializer: <T>(body: T): string => 64 + JSON.stringify(body, (_key, value) => (typeof value === 'bigint' ? value.toString() : value)), 65 + }; 66 + 67 + export const urlSearchParamsBodySerializer = { 68 + bodySerializer: <T extends Record<string, any> | Array<Record<string, any>>>(body: T): string => { 69 + const data = new URLSearchParams(); 70 + 71 + Object.entries(body).forEach(([key, value]) => { 72 + if (value === undefined || value === null) { 73 + return; 74 + } 75 + if (Array.isArray(value)) { 76 + value.forEach((v) => serializeUrlSearchParamsPair(data, key, v)); 77 + } else { 78 + serializeUrlSearchParamsPair(data, key, value); 79 + } 80 + }); 81 + 82 + return data.toString(); 83 + }, 84 + };
+169
packages/api/src/generated/core/params.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + type Slot = 'body' | 'headers' | 'path' | 'query'; 4 + 5 + export type Field = 6 + | { 7 + in: Exclude<Slot, 'body'>; 8 + /** 9 + * Field name. This is the name we want the user to see and use. 10 + */ 11 + key: string; 12 + /** 13 + * Field mapped name. This is the name we want to use in the request. 14 + * If omitted, we use the same value as `key`. 15 + */ 16 + map?: string; 17 + } 18 + | { 19 + in: Extract<Slot, 'body'>; 20 + /** 21 + * Key isn't required for bodies. 22 + */ 23 + key?: string; 24 + map?: string; 25 + } 26 + | { 27 + /** 28 + * Field name. This is the name we want the user to see and use. 29 + */ 30 + key: string; 31 + /** 32 + * Field mapped name. This is the name we want to use in the request. 33 + * If `in` is omitted, `map` aliases `key` to the transport layer. 34 + */ 35 + map: Slot; 36 + }; 37 + 38 + export interface Fields { 39 + allowExtra?: Partial<Record<Slot, boolean>>; 40 + args?: ReadonlyArray<Field>; 41 + } 42 + 43 + export type FieldsConfig = ReadonlyArray<Field | Fields>; 44 + 45 + const extraPrefixesMap: Record<string, Slot> = { 46 + $body_: 'body', 47 + $headers_: 'headers', 48 + $path_: 'path', 49 + $query_: 'query', 50 + }; 51 + const extraPrefixes = Object.entries(extraPrefixesMap); 52 + 53 + type KeyMap = Map< 54 + string, 55 + | { 56 + in: Slot; 57 + map?: string; 58 + } 59 + | { 60 + in?: never; 61 + map: Slot; 62 + } 63 + >; 64 + 65 + const buildKeyMap = (fields: FieldsConfig, map?: KeyMap): KeyMap => { 66 + if (!map) { 67 + map = new Map(); 68 + } 69 + 70 + for (const config of fields) { 71 + if ('in' in config) { 72 + if (config.key) { 73 + map.set(config.key, { 74 + in: config.in, 75 + map: config.map, 76 + }); 77 + } 78 + } else if ('key' in config) { 79 + map.set(config.key, { 80 + map: config.map, 81 + }); 82 + } else if (config.args) { 83 + buildKeyMap(config.args, map); 84 + } 85 + } 86 + 87 + return map; 88 + }; 89 + 90 + interface Params { 91 + body: unknown; 92 + headers: Record<string, unknown>; 93 + path: Record<string, unknown>; 94 + query: Record<string, unknown>; 95 + } 96 + 97 + const stripEmptySlots = (params: Params) => { 98 + for (const [slot, value] of Object.entries(params)) { 99 + if (value && typeof value === 'object' && !Object.keys(value).length) { 100 + delete params[slot as Slot]; 101 + } 102 + } 103 + }; 104 + 105 + export const buildClientParams = (args: ReadonlyArray<unknown>, fields: FieldsConfig) => { 106 + const params: Params = { 107 + body: {}, 108 + headers: {}, 109 + path: {}, 110 + query: {}, 111 + }; 112 + 113 + const map = buildKeyMap(fields); 114 + 115 + let config: FieldsConfig[number] | undefined; 116 + 117 + for (const [index, arg] of args.entries()) { 118 + if (fields[index]) { 119 + config = fields[index]; 120 + } 121 + 122 + if (!config) { 123 + continue; 124 + } 125 + 126 + if ('in' in config) { 127 + if (config.key) { 128 + const field = map.get(config.key)!; 129 + const name = field.map || config.key; 130 + if (field.in) { 131 + (params[field.in] as Record<string, unknown>)[name] = arg; 132 + } 133 + } else { 134 + params.body = arg; 135 + } 136 + } else { 137 + for (const [key, value] of Object.entries(arg ?? {})) { 138 + const field = map.get(key); 139 + 140 + if (field) { 141 + if (field.in) { 142 + const name = field.map || key; 143 + (params[field.in] as Record<string, unknown>)[name] = value; 144 + } else { 145 + params[field.map] = value; 146 + } 147 + } else { 148 + const extra = extraPrefixes.find(([prefix]) => key.startsWith(prefix)); 149 + 150 + if (extra) { 151 + const [prefix, slot] = extra; 152 + (params[slot] as Record<string, unknown>)[key.slice(prefix.length)] = value; 153 + } else if ('allowExtra' in config && config.allowExtra) { 154 + for (const [slot, allowed] of Object.entries(config.allowExtra)) { 155 + if (allowed) { 156 + (params[slot as Slot] as Record<string, unknown>)[key] = value; 157 + break; 158 + } 159 + } 160 + } 161 + } 162 + } 163 + } 164 + } 165 + 166 + stripEmptySlots(params); 167 + 168 + return params; 169 + };
+171
packages/api/src/generated/core/pathSerializer.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + interface SerializeOptions<T> extends SerializePrimitiveOptions, SerializerOptions<T> {} 4 + 5 + interface SerializePrimitiveOptions { 6 + allowReserved?: boolean; 7 + name: string; 8 + } 9 + 10 + export interface SerializerOptions<T> { 11 + /** 12 + * @default true 13 + */ 14 + explode: boolean; 15 + style: T; 16 + } 17 + 18 + export type ArrayStyle = 'form' | 'spaceDelimited' | 'pipeDelimited'; 19 + export type ArraySeparatorStyle = ArrayStyle | MatrixStyle; 20 + type MatrixStyle = 'label' | 'matrix' | 'simple'; 21 + export type ObjectStyle = 'form' | 'deepObject'; 22 + type ObjectSeparatorStyle = ObjectStyle | MatrixStyle; 23 + 24 + interface SerializePrimitiveParam extends SerializePrimitiveOptions { 25 + value: string; 26 + } 27 + 28 + export const separatorArrayExplode = (style: ArraySeparatorStyle) => { 29 + switch (style) { 30 + case 'label': 31 + return '.'; 32 + case 'matrix': 33 + return ';'; 34 + case 'simple': 35 + return ','; 36 + default: 37 + return '&'; 38 + } 39 + }; 40 + 41 + export const separatorArrayNoExplode = (style: ArraySeparatorStyle) => { 42 + switch (style) { 43 + case 'form': 44 + return ','; 45 + case 'pipeDelimited': 46 + return '|'; 47 + case 'spaceDelimited': 48 + return '%20'; 49 + default: 50 + return ','; 51 + } 52 + }; 53 + 54 + export const separatorObjectExplode = (style: ObjectSeparatorStyle) => { 55 + switch (style) { 56 + case 'label': 57 + return '.'; 58 + case 'matrix': 59 + return ';'; 60 + case 'simple': 61 + return ','; 62 + default: 63 + return '&'; 64 + } 65 + }; 66 + 67 + export const serializeArrayParam = ({ 68 + allowReserved, 69 + explode, 70 + name, 71 + style, 72 + value, 73 + }: SerializeOptions<ArraySeparatorStyle> & { 74 + value: unknown[]; 75 + }) => { 76 + if (!explode) { 77 + const joinedValues = ( 78 + allowReserved ? value : value.map((v) => encodeURIComponent(v as string)) 79 + ).join(separatorArrayNoExplode(style)); 80 + switch (style) { 81 + case 'label': 82 + return `.${joinedValues}`; 83 + case 'matrix': 84 + return `;${name}=${joinedValues}`; 85 + case 'simple': 86 + return joinedValues; 87 + default: 88 + return `${name}=${joinedValues}`; 89 + } 90 + } 91 + 92 + const separator = separatorArrayExplode(style); 93 + const joinedValues = value 94 + .map((v) => { 95 + if (style === 'label' || style === 'simple') { 96 + return allowReserved ? v : encodeURIComponent(v as string); 97 + } 98 + 99 + return serializePrimitiveParam({ 100 + allowReserved, 101 + name, 102 + value: v as string, 103 + }); 104 + }) 105 + .join(separator); 106 + return style === 'label' || style === 'matrix' ? separator + joinedValues : joinedValues; 107 + }; 108 + 109 + export const serializePrimitiveParam = ({ 110 + allowReserved, 111 + name, 112 + value, 113 + }: SerializePrimitiveParam) => { 114 + if (value === undefined || value === null) { 115 + return ''; 116 + } 117 + 118 + if (typeof value === 'object') { 119 + throw new Error( 120 + 'Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.', 121 + ); 122 + } 123 + 124 + return `${name}=${allowReserved ? value : encodeURIComponent(value)}`; 125 + }; 126 + 127 + export const serializeObjectParam = ({ 128 + allowReserved, 129 + explode, 130 + name, 131 + style, 132 + value, 133 + valueOnly, 134 + }: SerializeOptions<ObjectSeparatorStyle> & { 135 + value: Record<string, unknown> | Date; 136 + valueOnly?: boolean; 137 + }) => { 138 + if (value instanceof Date) { 139 + return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`; 140 + } 141 + 142 + if (style !== 'deepObject' && !explode) { 143 + let values: string[] = []; 144 + Object.entries(value).forEach(([key, v]) => { 145 + values = [...values, key, allowReserved ? (v as string) : encodeURIComponent(v as string)]; 146 + }); 147 + const joinedValues = values.join(','); 148 + switch (style) { 149 + case 'form': 150 + return `${name}=${joinedValues}`; 151 + case 'label': 152 + return `.${joinedValues}`; 153 + case 'matrix': 154 + return `;${name}=${joinedValues}`; 155 + default: 156 + return joinedValues; 157 + } 158 + } 159 + 160 + const separator = separatorObjectExplode(style); 161 + const joinedValues = Object.entries(value) 162 + .map(([key, v]) => 163 + serializePrimitiveParam({ 164 + allowReserved, 165 + name: style === 'deepObject' ? `${name}[${key}]` : key, 166 + value: v as string, 167 + }), 168 + ) 169 + .join(separator); 170 + return style === 'label' || style === 'matrix' ? separator + joinedValues : joinedValues; 171 + };
+117
packages/api/src/generated/core/queryKeySerializer.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + /** 4 + * JSON-friendly union that mirrors what Pinia Colada can hash. 5 + */ 6 + export type JsonValue = 7 + | null 8 + | string 9 + | number 10 + | boolean 11 + | JsonValue[] 12 + | { [key: string]: JsonValue }; 13 + 14 + /** 15 + * Replacer that converts non-JSON values (bigint, Date, etc.) to safe substitutes. 16 + */ 17 + export const queryKeyJsonReplacer = (_key: string, value: unknown) => { 18 + if (value === undefined || typeof value === 'function' || typeof value === 'symbol') { 19 + return undefined; 20 + } 21 + if (typeof value === 'bigint') { 22 + return value.toString(); 23 + } 24 + if (value instanceof Date) { 25 + return value.toISOString(); 26 + } 27 + return value; 28 + }; 29 + 30 + /** 31 + * Safely stringifies a value and parses it back into a JsonValue. 32 + */ 33 + export const stringifyToJsonValue = (input: unknown): JsonValue | undefined => { 34 + try { 35 + const json = JSON.stringify(input, queryKeyJsonReplacer); 36 + if (json === undefined) { 37 + return undefined; 38 + } 39 + return JSON.parse(json) as JsonValue; 40 + } catch { 41 + return undefined; 42 + } 43 + }; 44 + 45 + /** 46 + * Detects plain objects (including objects with a null prototype). 47 + */ 48 + const isPlainObject = (value: unknown): value is Record<string, unknown> => { 49 + if (value === null || typeof value !== 'object') { 50 + return false; 51 + } 52 + const prototype = Object.getPrototypeOf(value as object); 53 + return prototype === Object.prototype || prototype === null; 54 + }; 55 + 56 + /** 57 + * Turns URLSearchParams into a sorted JSON object for deterministic keys. 58 + */ 59 + const serializeSearchParams = (params: URLSearchParams): JsonValue => { 60 + const entries = Array.from(params.entries()).sort(([a], [b]) => a.localeCompare(b)); 61 + const result: Record<string, JsonValue> = {}; 62 + 63 + for (const [key, value] of entries) { 64 + const existing = result[key]; 65 + if (existing === undefined) { 66 + result[key] = value; 67 + continue; 68 + } 69 + 70 + if (Array.isArray(existing)) { 71 + (existing as string[]).push(value); 72 + } else { 73 + result[key] = [existing, value]; 74 + } 75 + } 76 + 77 + return result; 78 + }; 79 + 80 + /** 81 + * Normalizes any accepted value into a JSON-friendly shape for query keys. 82 + */ 83 + export const serializeQueryKeyValue = (value: unknown): JsonValue | undefined => { 84 + if (value === null) { 85 + return null; 86 + } 87 + 88 + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { 89 + return value; 90 + } 91 + 92 + if (value === undefined || typeof value === 'function' || typeof value === 'symbol') { 93 + return undefined; 94 + } 95 + 96 + if (typeof value === 'bigint') { 97 + return value.toString(); 98 + } 99 + 100 + if (value instanceof Date) { 101 + return value.toISOString(); 102 + } 103 + 104 + if (Array.isArray(value)) { 105 + return stringifyToJsonValue(value); 106 + } 107 + 108 + if (typeof URLSearchParams !== 'undefined' && value instanceof URLSearchParams) { 109 + return serializeSearchParams(value); 110 + } 111 + 112 + if (isPlainObject(value)) { 113 + return stringifyToJsonValue(value); 114 + } 115 + 116 + return undefined; 117 + };
+243
packages/api/src/generated/core/serverSentEvents.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Config } from './types.gen'; 4 + 5 + export type ServerSentEventsOptions<TData = unknown> = Omit<RequestInit, 'method'> & 6 + Pick<Config, 'method' | 'responseTransformer' | 'responseValidator'> & { 7 + /** 8 + * Fetch API implementation. You can use this option to provide a custom 9 + * fetch instance. 10 + * 11 + * @default globalThis.fetch 12 + */ 13 + fetch?: typeof fetch; 14 + /** 15 + * Implementing clients can call request interceptors inside this hook. 16 + */ 17 + onRequest?: (url: string, init: RequestInit) => Promise<Request>; 18 + /** 19 + * Callback invoked when a network or parsing error occurs during streaming. 20 + * 21 + * This option applies only if the endpoint returns a stream of events. 22 + * 23 + * @param error The error that occurred. 24 + */ 25 + onSseError?: (error: unknown) => void; 26 + /** 27 + * Callback invoked when an event is streamed from the server. 28 + * 29 + * This option applies only if the endpoint returns a stream of events. 30 + * 31 + * @param event Event streamed from the server. 32 + * @returns Nothing (void). 33 + */ 34 + onSseEvent?: (event: StreamEvent<TData>) => void; 35 + serializedBody?: RequestInit['body']; 36 + /** 37 + * Default retry delay in milliseconds. 38 + * 39 + * This option applies only if the endpoint returns a stream of events. 40 + * 41 + * @default 3000 42 + */ 43 + sseDefaultRetryDelay?: number; 44 + /** 45 + * Maximum number of retry attempts before giving up. 46 + */ 47 + sseMaxRetryAttempts?: number; 48 + /** 49 + * Maximum retry delay in milliseconds. 50 + * 51 + * Applies only when exponential backoff is used. 52 + * 53 + * This option applies only if the endpoint returns a stream of events. 54 + * 55 + * @default 30000 56 + */ 57 + sseMaxRetryDelay?: number; 58 + /** 59 + * Optional sleep function for retry backoff. 60 + * 61 + * Defaults to using `setTimeout`. 62 + */ 63 + sseSleepFn?: (ms: number) => Promise<void>; 64 + url: string; 65 + }; 66 + 67 + export interface StreamEvent<TData = unknown> { 68 + data: TData; 69 + event?: string; 70 + id?: string; 71 + retry?: number; 72 + } 73 + 74 + export type ServerSentEventsResult<TData = unknown, TReturn = void, TNext = unknown> = { 75 + stream: AsyncGenerator< 76 + TData extends Record<string, unknown> ? TData[keyof TData] : TData, 77 + TReturn, 78 + TNext 79 + >; 80 + }; 81 + 82 + export const createSseClient = <TData = unknown>({ 83 + onRequest, 84 + onSseError, 85 + onSseEvent, 86 + responseTransformer, 87 + responseValidator, 88 + sseDefaultRetryDelay, 89 + sseMaxRetryAttempts, 90 + sseMaxRetryDelay, 91 + sseSleepFn, 92 + url, 93 + ...options 94 + }: ServerSentEventsOptions): ServerSentEventsResult<TData> => { 95 + let lastEventId: string | undefined; 96 + 97 + const sleep = sseSleepFn ?? ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms))); 98 + 99 + const createStream = async function* () { 100 + let retryDelay: number = sseDefaultRetryDelay ?? 3000; 101 + let attempt = 0; 102 + const signal = options.signal ?? new AbortController().signal; 103 + 104 + while (true) { 105 + if (signal.aborted) break; 106 + 107 + attempt++; 108 + 109 + const headers = 110 + options.headers instanceof Headers 111 + ? options.headers 112 + : new Headers(options.headers as Record<string, string> | undefined); 113 + 114 + if (lastEventId !== undefined) { 115 + headers.set('Last-Event-ID', lastEventId); 116 + } 117 + 118 + try { 119 + const requestInit: RequestInit = { 120 + redirect: 'follow', 121 + ...options, 122 + body: options.serializedBody, 123 + headers, 124 + signal, 125 + }; 126 + let request = new Request(url, requestInit); 127 + if (onRequest) { 128 + request = await onRequest(url, requestInit); 129 + } 130 + // fetch must be assigned here, otherwise it would throw the error: 131 + // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation 132 + const _fetch = options.fetch ?? globalThis.fetch; 133 + const response = await _fetch(request); 134 + 135 + if (!response.ok) throw new Error(`SSE failed: ${response.status} ${response.statusText}`); 136 + 137 + if (!response.body) throw new Error('No body in SSE response'); 138 + 139 + const reader = response.body.pipeThrough(new TextDecoderStream()).getReader(); 140 + 141 + let buffer = ''; 142 + 143 + const abortHandler = () => { 144 + try { 145 + reader.cancel(); 146 + } catch { 147 + // noop 148 + } 149 + }; 150 + 151 + signal.addEventListener('abort', abortHandler); 152 + 153 + try { 154 + while (true) { 155 + const { done, value } = await reader.read(); 156 + if (done) break; 157 + buffer += value; 158 + // Normalize line endings: CRLF -> LF, then CR -> LF 159 + buffer = buffer.replace(/\r\n/g, '\n').replace(/\r/g, '\n'); 160 + 161 + const chunks = buffer.split('\n\n'); 162 + buffer = chunks.pop() ?? ''; 163 + 164 + for (const chunk of chunks) { 165 + const lines = chunk.split('\n'); 166 + const dataLines: Array<string> = []; 167 + let eventName: string | undefined; 168 + 169 + for (const line of lines) { 170 + if (line.startsWith('data:')) { 171 + dataLines.push(line.replace(/^data:\s*/, '')); 172 + } else if (line.startsWith('event:')) { 173 + eventName = line.replace(/^event:\s*/, ''); 174 + } else if (line.startsWith('id:')) { 175 + lastEventId = line.replace(/^id:\s*/, ''); 176 + } else if (line.startsWith('retry:')) { 177 + const parsed = Number.parseInt(line.replace(/^retry:\s*/, ''), 10); 178 + if (!Number.isNaN(parsed)) { 179 + retryDelay = parsed; 180 + } 181 + } 182 + } 183 + 184 + let data: unknown; 185 + let parsedJson = false; 186 + 187 + if (dataLines.length) { 188 + const rawData = dataLines.join('\n'); 189 + try { 190 + data = JSON.parse(rawData); 191 + parsedJson = true; 192 + } catch { 193 + data = rawData; 194 + } 195 + } 196 + 197 + if (parsedJson) { 198 + if (responseValidator) { 199 + await responseValidator(data); 200 + } 201 + 202 + if (responseTransformer) { 203 + data = await responseTransformer(data); 204 + } 205 + } 206 + 207 + onSseEvent?.({ 208 + data, 209 + event: eventName, 210 + id: lastEventId, 211 + retry: retryDelay, 212 + }); 213 + 214 + if (dataLines.length) { 215 + yield data as any; 216 + } 217 + } 218 + } 219 + } finally { 220 + signal.removeEventListener('abort', abortHandler); 221 + reader.releaseLock(); 222 + } 223 + 224 + break; // exit loop on normal completion 225 + } catch (error) { 226 + // connection failed or aborted; retry after delay 227 + onSseError?.(error); 228 + 229 + if (sseMaxRetryAttempts !== undefined && attempt >= sseMaxRetryAttempts) { 230 + break; // stop after firing error 231 + } 232 + 233 + // exponential backoff: double retry each attempt, cap at 30s 234 + const backoff = Math.min(retryDelay * 2 ** (attempt - 1), sseMaxRetryDelay ?? 30000); 235 + await sleep(backoff); 236 + } 237 + } 238 + }; 239 + 240 + const stream = createStream(); 241 + 242 + return { stream }; 243 + };
+104
packages/api/src/generated/core/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Auth, AuthToken } from './auth.gen'; 4 + import type { BodySerializer, QuerySerializer, QuerySerializerOptions } from './bodySerializer.gen'; 5 + 6 + export type HttpMethod = 7 + | 'connect' 8 + | 'delete' 9 + | 'get' 10 + | 'head' 11 + | 'options' 12 + | 'patch' 13 + | 'post' 14 + | 'put' 15 + | 'trace'; 16 + 17 + export type Client< 18 + RequestFn = never, 19 + Config = unknown, 20 + MethodFn = never, 21 + BuildUrlFn = never, 22 + SseFn = never, 23 + > = { 24 + /** 25 + * Returns the final request URL. 26 + */ 27 + buildUrl: BuildUrlFn; 28 + getConfig: () => Config; 29 + request: RequestFn; 30 + setConfig: (config: Config) => Config; 31 + } & { 32 + [K in HttpMethod]: MethodFn; 33 + } & ([SseFn] extends [never] ? { sse?: never } : { sse: { [K in HttpMethod]: SseFn } }); 34 + 35 + export interface Config { 36 + /** 37 + * Auth token or a function returning auth token. The resolved value will be 38 + * added to the request payload as defined by its `security` array. 39 + */ 40 + auth?: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken; 41 + /** 42 + * A function for serializing request body parameter. By default, 43 + * {@link JSON.stringify()} will be used. 44 + */ 45 + bodySerializer?: BodySerializer | null; 46 + /** 47 + * An object containing any HTTP headers that you want to pre-populate your 48 + * `Headers` object with. 49 + * 50 + * {@link https://developer.mozilla.org/docs/Web/API/Headers/Headers#init See more} 51 + */ 52 + headers?: 53 + | RequestInit['headers'] 54 + | Record< 55 + string, 56 + string | number | boolean | (string | number | boolean)[] | null | undefined | unknown 57 + >; 58 + /** 59 + * The request method. 60 + * 61 + * {@link https://developer.mozilla.org/docs/Web/API/fetch#method See more} 62 + */ 63 + method?: Uppercase<HttpMethod>; 64 + /** 65 + * A function for serializing request query parameters. By default, arrays 66 + * will be exploded in form style, objects will be exploded in deepObject 67 + * style, and reserved characters are percent-encoded. 68 + * 69 + * This method will have no effect if the native `paramsSerializer()` Axios 70 + * API function is used. 71 + * 72 + * {@link https://swagger.io/docs/specification/serialization/#query View examples} 73 + */ 74 + querySerializer?: QuerySerializer | QuerySerializerOptions; 75 + /** 76 + * A function validating request data. This is useful if you want to ensure 77 + * the request conforms to the desired shape, so it can be safely sent to 78 + * the server. 79 + */ 80 + requestValidator?: (data: unknown) => Promise<unknown>; 81 + /** 82 + * A function transforming response data before it's returned. This is useful 83 + * for post-processing data, e.g. converting ISO strings into Date objects. 84 + */ 85 + responseTransformer?: (data: unknown) => Promise<unknown>; 86 + /** 87 + * A function validating response data. This is useful if you want to ensure 88 + * the response conforms to the desired shape, so it can be safely passed to 89 + * the transformers and returned to the user. 90 + */ 91 + responseValidator?: (data: unknown) => Promise<unknown>; 92 + } 93 + 94 + type IsExactlyNeverOrNeverUndefined<T> = [T] extends [never] 95 + ? true 96 + : [T] extends [never | undefined] 97 + ? [undefined] extends [T] 98 + ? false 99 + : true 100 + : false; 101 + 102 + export type OmitNever<T extends Record<string, unknown>> = { 103 + [K in keyof T as IsExactlyNeverOrNeverUndefined<T[K]> extends true ? never : K]: T[K]; 104 + };
+140
packages/api/src/generated/core/utils.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { BodySerializer, QuerySerializer } from './bodySerializer.gen'; 4 + import { 5 + type ArraySeparatorStyle, 6 + serializeArrayParam, 7 + serializeObjectParam, 8 + serializePrimitiveParam, 9 + } from './pathSerializer.gen'; 10 + 11 + export interface PathSerializer { 12 + path: Record<string, unknown>; 13 + url: string; 14 + } 15 + 16 + export const PATH_PARAM_RE = /\{[^{}]+\}/g; 17 + 18 + export const defaultPathSerializer = ({ path, url: _url }: PathSerializer) => { 19 + let url = _url; 20 + const matches = _url.match(PATH_PARAM_RE); 21 + if (matches) { 22 + for (const match of matches) { 23 + let explode = false; 24 + let name = match.substring(1, match.length - 1); 25 + let style: ArraySeparatorStyle = 'simple'; 26 + 27 + if (name.endsWith('*')) { 28 + explode = true; 29 + name = name.substring(0, name.length - 1); 30 + } 31 + 32 + if (name.startsWith('.')) { 33 + name = name.substring(1); 34 + style = 'label'; 35 + } else if (name.startsWith(';')) { 36 + name = name.substring(1); 37 + style = 'matrix'; 38 + } 39 + 40 + const value = path[name]; 41 + 42 + if (value === undefined || value === null) { 43 + continue; 44 + } 45 + 46 + if (Array.isArray(value)) { 47 + url = url.replace(match, serializeArrayParam({ explode, name, style, value })); 48 + continue; 49 + } 50 + 51 + if (typeof value === 'object') { 52 + url = url.replace( 53 + match, 54 + serializeObjectParam({ 55 + explode, 56 + name, 57 + style, 58 + value: value as Record<string, unknown>, 59 + valueOnly: true, 60 + }), 61 + ); 62 + continue; 63 + } 64 + 65 + if (style === 'matrix') { 66 + url = url.replace( 67 + match, 68 + `;${serializePrimitiveParam({ 69 + name, 70 + value: value as string, 71 + })}`, 72 + ); 73 + continue; 74 + } 75 + 76 + const replaceValue = encodeURIComponent( 77 + style === 'label' ? `.${value as string}` : (value as string), 78 + ); 79 + url = url.replace(match, replaceValue); 80 + } 81 + } 82 + return url; 83 + }; 84 + 85 + export const getUrl = ({ 86 + baseUrl, 87 + path, 88 + query, 89 + querySerializer, 90 + url: _url, 91 + }: { 92 + baseUrl?: string; 93 + path?: Record<string, unknown>; 94 + query?: Record<string, unknown>; 95 + querySerializer: QuerySerializer; 96 + url: string; 97 + }) => { 98 + const pathUrl = _url.startsWith('/') ? _url : `/${_url}`; 99 + let url = (baseUrl ?? '') + pathUrl; 100 + if (path) { 101 + url = defaultPathSerializer({ path, url }); 102 + } 103 + let search = query ? querySerializer(query) : ''; 104 + if (search.startsWith('?')) { 105 + search = search.substring(1); 106 + } 107 + if (search) { 108 + url += `?${search}`; 109 + } 110 + return url; 111 + }; 112 + 113 + export function getValidRequestBody(options: { 114 + body?: unknown; 115 + bodySerializer?: BodySerializer | null; 116 + serializedBody?: unknown; 117 + }) { 118 + const hasBody = options.body !== undefined; 119 + const isSerializedBody = hasBody && options.bodySerializer; 120 + 121 + if (isSerializedBody) { 122 + if ('serializedBody' in options) { 123 + const hasSerializedBody = 124 + options.serializedBody !== undefined && options.serializedBody !== ''; 125 + 126 + return hasSerializedBody ? options.serializedBody : null; 127 + } 128 + 129 + // not all clients implement a serializedBody property (i.e. client-axios) 130 + return options.body !== '' ? options.body : null; 131 + } 132 + 133 + // plain/text body 134 + if (hasBody) { 135 + return options.body; 136 + } 137 + 138 + // no body was provided 139 + return undefined; 140 + }
+4
packages/api/src/generated/index.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export { appControllerGetHello, authControllerCallback, authControllerGetClientMetadata, authControllerLogin, authControllerLogout, authControllerMe, moviesControllerGetMovie, moviesControllerGetMovieDetails, moviesControllerGetUserMovies, moviesControllerMarkWatched, moviesControllerSearchMovies, moviesControllerUnmarkWatched, type Options } from './sdk.gen'; 4 + export type { AppControllerGetHelloData, AppControllerGetHelloResponses, AuthControllerCallbackData, AuthControllerGetClientMetadataData, AuthControllerGetClientMetadataResponses, AuthControllerLoginData, AuthControllerLogoutData, AuthControllerLogoutResponses, AuthControllerMeData, AuthControllerMeErrors, AuthControllerMeResponse, AuthControllerMeResponses, ClientOptions, MarkWatchedDto, MovieDto, MoviesControllerGetMovieData, MoviesControllerGetMovieDetailsData, MoviesControllerGetMovieDetailsResponses, MoviesControllerGetMovieResponse, MoviesControllerGetMovieResponses, MoviesControllerGetUserMoviesData, MoviesControllerGetUserMoviesResponse, MoviesControllerGetUserMoviesResponses, MoviesControllerMarkWatchedData, MoviesControllerMarkWatchedErrors, MoviesControllerMarkWatchedResponse, MoviesControllerMarkWatchedResponses, MoviesControllerSearchMoviesData, MoviesControllerSearchMoviesResponse, MoviesControllerSearchMoviesResponses, MoviesControllerUnmarkWatchedData, MoviesControllerUnmarkWatchedErrors, MoviesControllerUnmarkWatchedResponse, MoviesControllerUnmarkWatchedResponses, SearchResultsDto, TmdbMovieResultDto, TrackedMovieDto, UserDto } from './types.gen';
-529
packages/api/src/generated/schema.ts
··· 1 - /** 2 - * This file was auto-generated by openapi-typescript. 3 - * Do not make direct changes to the file. 4 - */ 5 - 6 - export interface paths { 7 - "/": { 8 - parameters: { 9 - query?: never; 10 - header?: never; 11 - path?: never; 12 - cookie?: never; 13 - }; 14 - get: operations["AppController_getHello"]; 15 - put?: never; 16 - post?: never; 17 - delete?: never; 18 - options?: never; 19 - head?: never; 20 - patch?: never; 21 - trace?: never; 22 - }; 23 - "/movies/search": { 24 - parameters: { 25 - query?: never; 26 - header?: never; 27 - path?: never; 28 - cookie?: never; 29 - }; 30 - /** Search movies from TMDB */ 31 - get: operations["MoviesController_searchMovies"]; 32 - put?: never; 33 - post?: never; 34 - delete?: never; 35 - options?: never; 36 - head?: never; 37 - patch?: never; 38 - trace?: never; 39 - }; 40 - "/movies/tmdb/{movieId}": { 41 - parameters: { 42 - query?: never; 43 - header?: never; 44 - path?: never; 45 - cookie?: never; 46 - }; 47 - /** Get movie details from TMDB */ 48 - get: operations["MoviesController_getMovieDetails"]; 49 - put?: never; 50 - post?: never; 51 - delete?: never; 52 - options?: never; 53 - head?: never; 54 - patch?: never; 55 - trace?: never; 56 - }; 57 - "/movies/user/{userDid}": { 58 - parameters: { 59 - query?: never; 60 - header?: never; 61 - path?: never; 62 - cookie?: never; 63 - }; 64 - /** Get tracked movies for a user */ 65 - get: operations["MoviesController_getUserMovies"]; 66 - put?: never; 67 - post?: never; 68 - delete?: never; 69 - options?: never; 70 - head?: never; 71 - patch?: never; 72 - trace?: never; 73 - }; 74 - "/movies/watched": { 75 - parameters: { 76 - query?: never; 77 - header?: never; 78 - path?: never; 79 - cookie?: never; 80 - }; 81 - get?: never; 82 - put?: never; 83 - /** Mark a movie as watched */ 84 - post: operations["MoviesController_markWatched"]; 85 - delete?: never; 86 - options?: never; 87 - head?: never; 88 - patch?: never; 89 - trace?: never; 90 - }; 91 - "/movies/watched/{movieId}": { 92 - parameters: { 93 - query?: never; 94 - header?: never; 95 - path?: never; 96 - cookie?: never; 97 - }; 98 - get?: never; 99 - put?: never; 100 - post?: never; 101 - /** Unmark a movie as watched */ 102 - delete: operations["MoviesController_unmarkWatched"]; 103 - options?: never; 104 - head?: never; 105 - patch?: never; 106 - trace?: never; 107 - }; 108 - "/movies/{movieId}": { 109 - parameters: { 110 - query?: never; 111 - header?: never; 112 - path?: never; 113 - cookie?: never; 114 - }; 115 - /** Get movie from database */ 116 - get: operations["MoviesController_getMovie"]; 117 - put?: never; 118 - post?: never; 119 - delete?: never; 120 - options?: never; 121 - head?: never; 122 - patch?: never; 123 - trace?: never; 124 - }; 125 - "/.well-known/oauth-client-metadata.json": { 126 - parameters: { 127 - query?: never; 128 - header?: never; 129 - path?: never; 130 - cookie?: never; 131 - }; 132 - /** OAuth client metadata */ 133 - get: operations["AuthController_getClientMetadata"]; 134 - put?: never; 135 - post?: never; 136 - delete?: never; 137 - options?: never; 138 - head?: never; 139 - patch?: never; 140 - trace?: never; 141 - }; 142 - "/auth/login": { 143 - parameters: { 144 - query?: never; 145 - header?: never; 146 - path?: never; 147 - cookie?: never; 148 - }; 149 - /** Start AT Protocol OAuth login */ 150 - get: operations["AuthController_login"]; 151 - put?: never; 152 - post?: never; 153 - delete?: never; 154 - options?: never; 155 - head?: never; 156 - patch?: never; 157 - trace?: never; 158 - }; 159 - "/auth/callback": { 160 - parameters: { 161 - query?: never; 162 - header?: never; 163 - path?: never; 164 - cookie?: never; 165 - }; 166 - /** AT Protocol OAuth callback */ 167 - get: operations["AuthController_callback"]; 168 - put?: never; 169 - post?: never; 170 - delete?: never; 171 - options?: never; 172 - head?: never; 173 - patch?: never; 174 - trace?: never; 175 - }; 176 - "/auth/me": { 177 - parameters: { 178 - query?: never; 179 - header?: never; 180 - path?: never; 181 - cookie?: never; 182 - }; 183 - /** Get current authenticated user */ 184 - get: operations["AuthController_me"]; 185 - put?: never; 186 - post?: never; 187 - delete?: never; 188 - options?: never; 189 - head?: never; 190 - patch?: never; 191 - trace?: never; 192 - }; 193 - "/auth/logout": { 194 - parameters: { 195 - query?: never; 196 - header?: never; 197 - path?: never; 198 - cookie?: never; 199 - }; 200 - get?: never; 201 - put?: never; 202 - /** Logout and clear session */ 203 - post: operations["AuthController_logout"]; 204 - delete?: never; 205 - options?: never; 206 - head?: never; 207 - patch?: never; 208 - trace?: never; 209 - }; 210 - } 211 - export type webhooks = Record<string, never>; 212 - export interface components { 213 - schemas: { 214 - TMDBMovieResultDto: { 215 - id: number; 216 - title: string; 217 - poster_path?: string; 218 - backdrop_path?: string; 219 - release_date?: string; 220 - overview?: string; 221 - }; 222 - SearchResultsDto: { 223 - results: components["schemas"]["TMDBMovieResultDto"][]; 224 - total_results: number; 225 - page: number; 226 - }; 227 - MovieDto: { 228 - movieId: string; 229 - title: string; 230 - posterPath?: string; 231 - backdropPath?: string; 232 - releaseYear?: number; 233 - releaseDate?: string; 234 - overview?: string; 235 - }; 236 - TrackedMovieDto: { 237 - id: string; 238 - rkey: string; 239 - uri: string; 240 - cid: string; 241 - userDid: string; 242 - movieId: string; 243 - status: string; 244 - watchedDate?: string; 245 - createdAt: string; 246 - updatedAt: string; 247 - movie: components["schemas"]["MovieDto"]; 248 - }; 249 - MarkWatchedDto: { 250 - /** @description TMDB movie ID */ 251 - movieId: string; 252 - }; 253 - UserDto: { 254 - /** @description User DID (decentralized identifier) */ 255 - did: string; 256 - /** @description User handle (e.g., user.bsky.social) */ 257 - handle: string; 258 - /** @description Display name */ 259 - displayName: Record<string, never> | null; 260 - /** @description Avatar URL */ 261 - avatar: Record<string, never> | null; 262 - }; 263 - }; 264 - responses: never; 265 - parameters: never; 266 - requestBodies: never; 267 - headers: never; 268 - pathItems: never; 269 - } 270 - export type $defs = Record<string, never>; 271 - export interface operations { 272 - AppController_getHello: { 273 - parameters: { 274 - query?: never; 275 - header?: never; 276 - path?: never; 277 - cookie?: never; 278 - }; 279 - requestBody?: never; 280 - responses: { 281 - 200: { 282 - headers: { 283 - [name: string]: unknown; 284 - }; 285 - content?: never; 286 - }; 287 - }; 288 - }; 289 - MoviesController_searchMovies: { 290 - parameters: { 291 - query: { 292 - /** @description Search term */ 293 - query: string; 294 - }; 295 - header?: never; 296 - path?: never; 297 - cookie?: never; 298 - }; 299 - requestBody?: never; 300 - responses: { 301 - 200: { 302 - headers: { 303 - [name: string]: unknown; 304 - }; 305 - content: { 306 - "application/json": components["schemas"]["SearchResultsDto"]; 307 - }; 308 - }; 309 - }; 310 - }; 311 - MoviesController_getMovieDetails: { 312 - parameters: { 313 - query?: never; 314 - header?: never; 315 - path: { 316 - movieId: string; 317 - }; 318 - cookie?: never; 319 - }; 320 - requestBody?: never; 321 - responses: { 322 - 200: { 323 - headers: { 324 - [name: string]: unknown; 325 - }; 326 - content?: never; 327 - }; 328 - }; 329 - }; 330 - MoviesController_getUserMovies: { 331 - parameters: { 332 - query?: never; 333 - header?: never; 334 - path: { 335 - userDid: string; 336 - }; 337 - cookie?: never; 338 - }; 339 - requestBody?: never; 340 - responses: { 341 - 200: { 342 - headers: { 343 - [name: string]: unknown; 344 - }; 345 - content: { 346 - "application/json": components["schemas"]["TrackedMovieDto"][]; 347 - }; 348 - }; 349 - }; 350 - }; 351 - MoviesController_markWatched: { 352 - parameters: { 353 - query?: never; 354 - header?: never; 355 - path?: never; 356 - cookie?: never; 357 - }; 358 - requestBody: { 359 - content: { 360 - "application/json": components["schemas"]["MarkWatchedDto"]; 361 - }; 362 - }; 363 - responses: { 364 - 201: { 365 - headers: { 366 - [name: string]: unknown; 367 - }; 368 - content: { 369 - "application/json": components["schemas"]["TrackedMovieDto"]; 370 - }; 371 - }; 372 - /** @description Not authenticated */ 373 - 401: { 374 - headers: { 375 - [name: string]: unknown; 376 - }; 377 - content?: never; 378 - }; 379 - }; 380 - }; 381 - MoviesController_unmarkWatched: { 382 - parameters: { 383 - query?: never; 384 - header?: never; 385 - path: { 386 - movieId: string; 387 - }; 388 - cookie?: never; 389 - }; 390 - requestBody?: never; 391 - responses: { 392 - /** @description Movie unmarked as watched */ 393 - 204: { 394 - headers: { 395 - [name: string]: unknown; 396 - }; 397 - content?: never; 398 - }; 399 - /** @description Not authenticated */ 400 - 401: { 401 - headers: { 402 - [name: string]: unknown; 403 - }; 404 - content?: never; 405 - }; 406 - }; 407 - }; 408 - MoviesController_getMovie: { 409 - parameters: { 410 - query?: never; 411 - header?: never; 412 - path: { 413 - movieId: string; 414 - }; 415 - cookie?: never; 416 - }; 417 - requestBody?: never; 418 - responses: { 419 - 200: { 420 - headers: { 421 - [name: string]: unknown; 422 - }; 423 - content: { 424 - "application/json": components["schemas"]["MovieDto"]; 425 - }; 426 - }; 427 - }; 428 - }; 429 - AuthController_getClientMetadata: { 430 - parameters: { 431 - query?: never; 432 - header?: never; 433 - path?: never; 434 - cookie?: never; 435 - }; 436 - requestBody?: never; 437 - responses: { 438 - 200: { 439 - headers: { 440 - [name: string]: unknown; 441 - }; 442 - content?: never; 443 - }; 444 - }; 445 - }; 446 - AuthController_login: { 447 - parameters: { 448 - query?: { 449 - /** @description User handle (e.g., user.bsky.social) */ 450 - handle?: unknown; 451 - }; 452 - header?: never; 453 - path?: never; 454 - cookie?: never; 455 - }; 456 - requestBody?: never; 457 - responses: { 458 - /** @description Redirect to authorization server */ 459 - 302: { 460 - headers: { 461 - [name: string]: unknown; 462 - }; 463 - content?: never; 464 - }; 465 - }; 466 - }; 467 - AuthController_callback: { 468 - parameters: { 469 - query?: never; 470 - header?: never; 471 - path?: never; 472 - cookie?: never; 473 - }; 474 - requestBody?: never; 475 - responses: { 476 - /** @description Redirect to frontend after authentication */ 477 - 302: { 478 - headers: { 479 - [name: string]: unknown; 480 - }; 481 - content?: never; 482 - }; 483 - }; 484 - }; 485 - AuthController_me: { 486 - parameters: { 487 - query?: never; 488 - header?: never; 489 - path?: never; 490 - cookie?: never; 491 - }; 492 - requestBody?: never; 493 - responses: { 494 - 200: { 495 - headers: { 496 - [name: string]: unknown; 497 - }; 498 - content: { 499 - "application/json": components["schemas"]["UserDto"]; 500 - }; 501 - }; 502 - /** @description Not authenticated */ 503 - 401: { 504 - headers: { 505 - [name: string]: unknown; 506 - }; 507 - content?: never; 508 - }; 509 - }; 510 - }; 511 - AuthController_logout: { 512 - parameters: { 513 - query?: never; 514 - header?: never; 515 - path?: never; 516 - cookie?: never; 517 - }; 518 - requestBody?: never; 519 - responses: { 520 - /** @description Logged out successfully */ 521 - 200: { 522 - headers: { 523 - [name: string]: unknown; 524 - }; 525 - content?: never; 526 - }; 527 - }; 528 - }; 529 - }
+83
packages/api/src/generated/sdk.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + import type { Client, Options as Options2, TDataShape } from './client'; 4 + import { client } from './client.gen'; 5 + import type { AppControllerGetHelloData, AppControllerGetHelloResponses, AuthControllerCallbackData, AuthControllerGetClientMetadataData, AuthControllerGetClientMetadataResponses, AuthControllerLoginData, AuthControllerLogoutData, AuthControllerLogoutResponses, AuthControllerMeData, AuthControllerMeErrors, AuthControllerMeResponses, MoviesControllerGetMovieData, MoviesControllerGetMovieDetailsData, MoviesControllerGetMovieDetailsResponses, MoviesControllerGetMovieResponses, MoviesControllerGetUserMoviesData, MoviesControllerGetUserMoviesResponses, MoviesControllerMarkWatchedData, MoviesControllerMarkWatchedErrors, MoviesControllerMarkWatchedResponses, MoviesControllerSearchMoviesData, MoviesControllerSearchMoviesResponses, MoviesControllerUnmarkWatchedData, MoviesControllerUnmarkWatchedErrors, MoviesControllerUnmarkWatchedResponses } from './types.gen'; 6 + 7 + export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean> = Options2<TData, ThrowOnError> & { 8 + /** 9 + * You can provide a client instance returned by `createClient()` instead of 10 + * individual options. This might be also useful if you want to implement a 11 + * custom client. 12 + */ 13 + client?: Client; 14 + /** 15 + * You can pass arbitrary values through the `meta` object. This can be 16 + * used to access values that aren't defined as part of the SDK function. 17 + */ 18 + meta?: Record<string, unknown>; 19 + }; 20 + 21 + export const appControllerGetHello = <ThrowOnError extends boolean = false>(options?: Options<AppControllerGetHelloData, ThrowOnError>) => (options?.client ?? client).get<AppControllerGetHelloResponses, unknown, ThrowOnError>({ url: '/', ...options }); 22 + 23 + /** 24 + * Search movies from TMDB 25 + */ 26 + export const moviesControllerSearchMovies = <ThrowOnError extends boolean = false>(options: Options<MoviesControllerSearchMoviesData, ThrowOnError>) => (options.client ?? client).get<MoviesControllerSearchMoviesResponses, unknown, ThrowOnError>({ url: '/movies/search', ...options }); 27 + 28 + /** 29 + * Get movie details from TMDB 30 + */ 31 + export const moviesControllerGetMovieDetails = <ThrowOnError extends boolean = false>(options: Options<MoviesControllerGetMovieDetailsData, ThrowOnError>) => (options.client ?? client).get<MoviesControllerGetMovieDetailsResponses, unknown, ThrowOnError>({ url: '/movies/tmdb/{movieId}', ...options }); 32 + 33 + /** 34 + * Get tracked movies for a user 35 + */ 36 + export const moviesControllerGetUserMovies = <ThrowOnError extends boolean = false>(options: Options<MoviesControllerGetUserMoviesData, ThrowOnError>) => (options.client ?? client).get<MoviesControllerGetUserMoviesResponses, unknown, ThrowOnError>({ url: '/movies/user/{userDid}', ...options }); 37 + 38 + /** 39 + * Mark a movie as watched 40 + */ 41 + export const moviesControllerMarkWatched = <ThrowOnError extends boolean = false>(options: Options<MoviesControllerMarkWatchedData, ThrowOnError>) => (options.client ?? client).post<MoviesControllerMarkWatchedResponses, MoviesControllerMarkWatchedErrors, ThrowOnError>({ 42 + url: '/movies/watched', 43 + ...options, 44 + headers: { 45 + 'Content-Type': 'application/json', 46 + ...options.headers 47 + } 48 + }); 49 + 50 + /** 51 + * Unmark a movie as watched 52 + */ 53 + export const moviesControllerUnmarkWatched = <ThrowOnError extends boolean = false>(options: Options<MoviesControllerUnmarkWatchedData, ThrowOnError>) => (options.client ?? client).delete<MoviesControllerUnmarkWatchedResponses, MoviesControllerUnmarkWatchedErrors, ThrowOnError>({ url: '/movies/watched/{movieId}', ...options }); 54 + 55 + /** 56 + * Get movie from database 57 + */ 58 + export const moviesControllerGetMovie = <ThrowOnError extends boolean = false>(options: Options<MoviesControllerGetMovieData, ThrowOnError>) => (options.client ?? client).get<MoviesControllerGetMovieResponses, unknown, ThrowOnError>({ url: '/movies/{movieId}', ...options }); 59 + 60 + /** 61 + * OAuth client metadata 62 + */ 63 + export const authControllerGetClientMetadata = <ThrowOnError extends boolean = false>(options?: Options<AuthControllerGetClientMetadataData, ThrowOnError>) => (options?.client ?? client).get<AuthControllerGetClientMetadataResponses, unknown, ThrowOnError>({ url: '/.well-known/oauth-client-metadata.json', ...options }); 64 + 65 + /** 66 + * Start AT Protocol OAuth login 67 + */ 68 + export const authControllerLogin = <ThrowOnError extends boolean = false>(options?: Options<AuthControllerLoginData, ThrowOnError>) => (options?.client ?? client).get<unknown, unknown, ThrowOnError>({ url: '/auth/login', ...options }); 69 + 70 + /** 71 + * AT Protocol OAuth callback 72 + */ 73 + export const authControllerCallback = <ThrowOnError extends boolean = false>(options?: Options<AuthControllerCallbackData, ThrowOnError>) => (options?.client ?? client).get<unknown, unknown, ThrowOnError>({ url: '/auth/callback', ...options }); 74 + 75 + /** 76 + * Get current authenticated user 77 + */ 78 + export const authControllerMe = <ThrowOnError extends boolean = false>(options?: Options<AuthControllerMeData, ThrowOnError>) => (options?.client ?? client).get<AuthControllerMeResponses, AuthControllerMeErrors, ThrowOnError>({ url: '/auth/me', ...options }); 79 + 80 + /** 81 + * Logout and clear session 82 + */ 83 + export const authControllerLogout = <ThrowOnError extends boolean = false>(options?: Options<AuthControllerLogoutData, ThrowOnError>) => (options?.client ?? client).post<AuthControllerLogoutResponses, unknown, ThrowOnError>({ url: '/auth/logout', ...options });
+259
packages/api/src/generated/types.gen.ts
··· 1 + // This file is auto-generated by @hey-api/openapi-ts 2 + 3 + export type ClientOptions = { 4 + baseUrl: string; 5 + }; 6 + 7 + export type TmdbMovieResultDto = { 8 + id: number; 9 + title: string; 10 + poster_path?: string; 11 + backdrop_path?: string; 12 + release_date?: string; 13 + overview?: string; 14 + }; 15 + 16 + export type SearchResultsDto = { 17 + results: Array<TmdbMovieResultDto>; 18 + total_results: number; 19 + page: number; 20 + }; 21 + 22 + export type MovieDto = { 23 + movieId: string; 24 + title: string; 25 + posterPath?: string; 26 + backdropPath?: string; 27 + releaseYear?: number; 28 + releaseDate?: string; 29 + overview?: string; 30 + }; 31 + 32 + export type TrackedMovieDto = { 33 + id: string; 34 + rkey: string; 35 + uri: string; 36 + cid: string; 37 + userDid: string; 38 + movieId: string; 39 + status: string; 40 + watchedDate?: string; 41 + createdAt: string; 42 + updatedAt: string; 43 + movie: MovieDto; 44 + }; 45 + 46 + export type MarkWatchedDto = { 47 + /** 48 + * TMDB movie ID 49 + */ 50 + movieId: string; 51 + }; 52 + 53 + export type UserDto = { 54 + /** 55 + * User DID (decentralized identifier) 56 + */ 57 + did: string; 58 + /** 59 + * User handle (e.g., user.bsky.social) 60 + */ 61 + handle: string; 62 + /** 63 + * Display name 64 + */ 65 + displayName: { 66 + [key: string]: unknown; 67 + } | null; 68 + /** 69 + * Avatar URL 70 + */ 71 + avatar: { 72 + [key: string]: unknown; 73 + } | null; 74 + }; 75 + 76 + export type AppControllerGetHelloData = { 77 + body?: never; 78 + path?: never; 79 + query?: never; 80 + url: '/'; 81 + }; 82 + 83 + export type AppControllerGetHelloResponses = { 84 + 200: unknown; 85 + }; 86 + 87 + export type MoviesControllerSearchMoviesData = { 88 + body?: never; 89 + path?: never; 90 + query: { 91 + /** 92 + * Search term 93 + */ 94 + query: string; 95 + }; 96 + url: '/movies/search'; 97 + }; 98 + 99 + export type MoviesControllerSearchMoviesResponses = { 100 + 200: SearchResultsDto; 101 + }; 102 + 103 + export type MoviesControllerSearchMoviesResponse = MoviesControllerSearchMoviesResponses[keyof MoviesControllerSearchMoviesResponses]; 104 + 105 + export type MoviesControllerGetMovieDetailsData = { 106 + body?: never; 107 + path: { 108 + movieId: string; 109 + }; 110 + query?: never; 111 + url: '/movies/tmdb/{movieId}'; 112 + }; 113 + 114 + export type MoviesControllerGetMovieDetailsResponses = { 115 + 200: unknown; 116 + }; 117 + 118 + export type MoviesControllerGetUserMoviesData = { 119 + body?: never; 120 + path: { 121 + userDid: string; 122 + }; 123 + query?: never; 124 + url: '/movies/user/{userDid}'; 125 + }; 126 + 127 + export type MoviesControllerGetUserMoviesResponses = { 128 + 200: Array<TrackedMovieDto>; 129 + }; 130 + 131 + export type MoviesControllerGetUserMoviesResponse = MoviesControllerGetUserMoviesResponses[keyof MoviesControllerGetUserMoviesResponses]; 132 + 133 + export type MoviesControllerMarkWatchedData = { 134 + body: MarkWatchedDto; 135 + path?: never; 136 + query?: never; 137 + url: '/movies/watched'; 138 + }; 139 + 140 + export type MoviesControllerMarkWatchedErrors = { 141 + /** 142 + * Not authenticated 143 + */ 144 + 401: unknown; 145 + }; 146 + 147 + export type MoviesControllerMarkWatchedResponses = { 148 + 201: TrackedMovieDto; 149 + }; 150 + 151 + export type MoviesControllerMarkWatchedResponse = MoviesControllerMarkWatchedResponses[keyof MoviesControllerMarkWatchedResponses]; 152 + 153 + export type MoviesControllerUnmarkWatchedData = { 154 + body?: never; 155 + path: { 156 + movieId: string; 157 + }; 158 + query?: never; 159 + url: '/movies/watched/{movieId}'; 160 + }; 161 + 162 + export type MoviesControllerUnmarkWatchedErrors = { 163 + /** 164 + * Not authenticated 165 + */ 166 + 401: unknown; 167 + }; 168 + 169 + export type MoviesControllerUnmarkWatchedResponses = { 170 + /** 171 + * Movie unmarked as watched 172 + */ 173 + 204: void; 174 + }; 175 + 176 + export type MoviesControllerUnmarkWatchedResponse = MoviesControllerUnmarkWatchedResponses[keyof MoviesControllerUnmarkWatchedResponses]; 177 + 178 + export type MoviesControllerGetMovieData = { 179 + body?: never; 180 + path: { 181 + movieId: string; 182 + }; 183 + query?: never; 184 + url: '/movies/{movieId}'; 185 + }; 186 + 187 + export type MoviesControllerGetMovieResponses = { 188 + 200: MovieDto; 189 + }; 190 + 191 + export type MoviesControllerGetMovieResponse = MoviesControllerGetMovieResponses[keyof MoviesControllerGetMovieResponses]; 192 + 193 + export type AuthControllerGetClientMetadataData = { 194 + body?: never; 195 + path?: never; 196 + query?: never; 197 + url: '/.well-known/oauth-client-metadata.json'; 198 + }; 199 + 200 + export type AuthControllerGetClientMetadataResponses = { 201 + 200: unknown; 202 + }; 203 + 204 + export type AuthControllerLoginData = { 205 + body?: never; 206 + path?: never; 207 + query?: { 208 + /** 209 + * Platform identifier (e.g., "mobile") for redirect handling 210 + */ 211 + platform?: unknown; 212 + /** 213 + * User handle (e.g., user.bsky.social) 214 + */ 215 + handle?: unknown; 216 + }; 217 + url: '/auth/login'; 218 + }; 219 + 220 + export type AuthControllerCallbackData = { 221 + body?: never; 222 + path?: never; 223 + query?: never; 224 + url: '/auth/callback'; 225 + }; 226 + 227 + export type AuthControllerMeData = { 228 + body?: never; 229 + path?: never; 230 + query?: never; 231 + url: '/auth/me'; 232 + }; 233 + 234 + export type AuthControllerMeErrors = { 235 + /** 236 + * Not authenticated 237 + */ 238 + 401: unknown; 239 + }; 240 + 241 + export type AuthControllerMeResponses = { 242 + 200: UserDto; 243 + }; 244 + 245 + export type AuthControllerMeResponse = AuthControllerMeResponses[keyof AuthControllerMeResponses]; 246 + 247 + export type AuthControllerLogoutData = { 248 + body?: never; 249 + path?: never; 250 + query?: never; 251 + url: '/auth/logout'; 252 + }; 253 + 254 + export type AuthControllerLogoutResponses = { 255 + /** 256 + * Logged out successfully 257 + */ 258 + 200: unknown; 259 + };
+88 -2
packages/api/src/index.ts
··· 1 - export * from './client'; 2 - export type { paths, components } from './generated/schema'; 1 + // Export generated SDK 2 + export * from './generated'; 3 + 4 + // Export TanStack Query hooks 5 + export * from './generated/@tanstack/react-query.gen'; 6 + 7 + // Export client configuration utilities 8 + export { client } from './generated/client.gen'; 9 + export { createClient, createConfig } from './generated/client'; 10 + export type { Client, ClientOptions, Config, Options } from './generated/client'; 11 + 12 + // Re-export auth utilities from custom client wrapper 13 + export { 14 + setOnUnauthorized, 15 + setSessionToken, 16 + getSessionToken, 17 + getAuthUser, 18 + getLoginUrl, 19 + logout, 20 + configureApiClient, 21 + } from './client'; 22 + 23 + // Backward compatibility wrappers for old API functions 24 + import { 25 + moviesControllerSearchMovies, 26 + moviesControllerGetUserMovies, 27 + moviesControllerMarkWatched, 28 + moviesControllerUnmarkWatched, 29 + moviesControllerGetMovieDetails, 30 + } from './generated/sdk.gen'; 31 + 32 + /** 33 + * @deprecated Use moviesControllerSearchMovies with TanStack Query instead 34 + */ 35 + export async function searchMovies(query: string) { 36 + const { data } = await moviesControllerSearchMovies({ 37 + query: { query }, 38 + throwOnError: true, 39 + }); 40 + return data; 41 + } 42 + 43 + /** 44 + * @deprecated Use moviesControllerGetUserMovies with TanStack Query instead 45 + */ 46 + export async function getUserMovies(userDid: string | undefined) { 47 + if (!userDid) { 48 + return []; 49 + } 50 + const { data } = await moviesControllerGetUserMovies({ 51 + path: { userDid }, 52 + throwOnError: true, 53 + }); 54 + return data; 55 + } 56 + 57 + /** 58 + * @deprecated Use moviesControllerMarkWatched with TanStack Query instead 59 + */ 60 + export async function markMovieWatched(movieId: string) { 61 + const { data } = await moviesControllerMarkWatched({ 62 + body: { movieId }, 63 + throwOnError: true, 64 + }); 65 + return data; 66 + } 67 + 68 + /** 69 + * @deprecated Use moviesControllerUnmarkWatched with TanStack Query instead 70 + */ 71 + export async function unmarkMovieWatched(movieId: string) { 72 + const { data } = await moviesControllerUnmarkWatched({ 73 + path: { movieId }, 74 + throwOnError: true, 75 + }); 76 + return data; 77 + } 78 + 79 + /** 80 + * @deprecated Use moviesControllerGetMovieDetails with TanStack Query instead 81 + */ 82 + export async function getMovieDetails(movieId: string) { 83 + const { data } = await moviesControllerGetMovieDetails({ 84 + path: { movieId }, 85 + throwOnError: true, 86 + }); 87 + return data; 88 + }
+291 -165
pnpm-lock.yaml
··· 122 122 version: 0.561.0(react@19.2.4) 123 123 nitro: 124 124 specifier: npm:nitro-nightly@latest 125 - version: nitro-nightly@3.0.1-20260127-164246-ef01b092(@electric-sql/pglite@0.3.15)(chokidar@4.0.3)(lru-cache@11.2.5)(mysql2@3.15.3)(rollup@4.57.0)(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) 125 + version: nitro-nightly@3.0.1-20260127-164246-ef01b092(@electric-sql/pglite@0.3.15)(chokidar@5.0.0)(lru-cache@11.2.5)(mysql2@3.15.3)(rollup@4.57.0)(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) 126 126 react: 127 127 specifier: ^19.2.0 128 128 version: 19.2.4 ··· 320 320 321 321 packages/api: 322 322 dependencies: 323 - openapi-fetch: 324 - specifier: ^0.15.0 325 - version: 0.15.0 323 + '@hey-api/client-fetch': 324 + specifier: ^0.13.1 325 + version: 0.13.1(@hey-api/openapi-ts@0.91.1(typescript@5.9.3)) 326 + '@tanstack/react-query': 327 + specifier: ^5.90.20 328 + version: 5.90.20(react@19.2.4) 326 329 devDependencies: 330 + '@hey-api/openapi-ts': 331 + specifier: ^0.91.1 332 + version: 0.91.1(typescript@5.9.3) 327 333 '@types/node': 328 334 specifier: ^22.10.1 329 335 version: 22.19.7 330 - openapi-typescript: 331 - specifier: ^7.10.1 332 - version: 7.10.1(typescript@5.9.3) 333 336 typescript: 334 337 specifier: ^5.9.3 335 338 version: 5.9.3 ··· 1460 1463 resolution: {integrity: sha512-o2qDlTqJ606h4xR36H2zWTywmZ3v3842K6TU8Ik2n1mfW0S580VHlt3eItVYdLYz+klaPp7CXqanja8eASZjRw==} 1461 1464 hasBin: true 1462 1465 1466 + '@hey-api/client-fetch@0.13.1': 1467 + resolution: {integrity: sha512-29jBRYNdxVGlx5oewFgOrkulZckpIpBIRHth3uHFn1PrL2ucMy52FvWOY3U3dVx2go1Z3kUmMi6lr07iOpUqqA==} 1468 + deprecated: Starting with v0.73.0, this package is bundled directly inside @hey-api/openapi-ts. 1469 + peerDependencies: 1470 + '@hey-api/openapi-ts': < 2 1471 + 1472 + '@hey-api/codegen-core@0.6.1': 1473 + resolution: {integrity: sha512-khTIpxhKEAqmRmeLUnAFJQs4Sbg9RPokovJk9rRcC8B5MWH1j3/BRSqfpAIiJUBDU1+nbVg2RVCV+eQ174cdvw==} 1474 + engines: {node: '>=20.19.0'} 1475 + peerDependencies: 1476 + typescript: '>=5.5.3' 1477 + 1478 + '@hey-api/json-schema-ref-parser@1.2.3': 1479 + resolution: {integrity: sha512-gRbyyTjzpFVNmbD+Upn3w4dWV+bCXGJbff3A7leDO/tfNxSz1xIb6Ad/5UKtvEW9kDt/2Uyc3XkFZ6rpafvbfQ==} 1480 + engines: {node: '>= 16'} 1481 + 1482 + '@hey-api/openapi-ts@0.91.1': 1483 + resolution: {integrity: sha512-d16WR35UtthK/ihAIwJaKxrj/zvb5LbYwtVJCyZFFMin2qzDU8Y3Lpk78ensAykrLoaDLzpd0iIyt9JCP5Qmww==} 1484 + engines: {node: '>=20.19.0'} 1485 + hasBin: true 1486 + peerDependencies: 1487 + typescript: '>=5.5.3' 1488 + 1489 + '@hey-api/shared@0.1.1': 1490 + resolution: {integrity: sha512-/irgNGXw9TL5aKB3S7jCLgh07vgDFkYjSjz7vEWO9xEe6MUhx76zSFzkPspk2UrLghYayvmaKPf1ky4XjNI9ZQ==} 1491 + engines: {node: '>=20.19.0'} 1492 + peerDependencies: 1493 + typescript: '>=5.5.3' 1494 + 1495 + '@hey-api/types@0.1.3': 1496 + resolution: {integrity: sha512-mZaiPOWH761yD4GjDQvtjS2ZYLu5o5pI1TVSvV/u7cmbybv51/FVtinFBeaE1kFQCKZ8OQpn2ezjLBJrKsGATw==} 1497 + peerDependencies: 1498 + typescript: '>=5.5.3' 1499 + 1463 1500 '@hono/node-server@1.19.9': 1464 1501 resolution: {integrity: sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==} 1465 1502 engines: {node: '>=18.14.1'} ··· 1783 1820 1784 1821 '@jridgewell/trace-mapping@0.3.9': 1785 1822 resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} 1823 + 1824 + '@jsdevtools/ono@7.1.3': 1825 + resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==} 1786 1826 1787 1827 '@lukeed/csprng@1.1.0': 1788 1828 resolution: {integrity: sha512-Z7C/xXCiGWsg0KuKsHTKJxbWhpI3Vs5GwLfOean7MGyVFGqdRgBbAjOCh6u4bbjPc/8MJ2pZmK/0DLdCbivLDA==} ··· 2354 2394 '@react-navigation/routers@7.5.3': 2355 2395 resolution: {integrity: sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg==} 2356 2396 2357 - '@redocly/ajv@8.17.2': 2358 - resolution: {integrity: sha512-rcbDZOfXAgGEJeJ30aWCVVJvxV9ooevb/m1/SFblO2qHs4cqTk178gx7T/vdslf57EA4lTofrwsq5K8rxK9g+g==} 2359 - 2360 - '@redocly/config@0.22.2': 2361 - resolution: {integrity: sha512-roRDai8/zr2S9YfmzUfNhKjOF0NdcOIqF7bhf4MVC5UxpjIysDjyudvlAiVbpPHp3eDRWbdzUgtkK1a7YiDNyQ==} 2362 - 2363 - '@redocly/openapi-core@1.34.6': 2364 - resolution: {integrity: sha512-2+O+riuIUgVSuLl3Lyh5AplWZyVMNuG2F98/o6NrutKJfW4/GTZdPpZlIphS0HGgcOHgmWcCSHj+dWFlZaGSHw==} 2365 - engines: {node: '>=18.17.0', npm: '>=9.5.0'} 2366 - 2367 2397 '@rolldown/pluginutils@1.0.0-beta.40': 2368 2398 resolution: {integrity: sha512-s3GeJKSQOwBlzdUrj4ISjJj5SfSh+aqn0wjOar4Bx95iV1ETI7F6S/5hLcfAxZ9kXDcyrAkxPlqmd1ZITttf+w==} 2369 2399 ··· 3723 3753 buffer@6.0.3: 3724 3754 resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} 3725 3755 3756 + bundle-name@4.1.0: 3757 + resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} 3758 + engines: {node: '>=18'} 3759 + 3726 3760 busboy@1.6.0: 3727 3761 resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} 3728 3762 engines: {node: '>=10.16.0'} ··· 3739 3773 magicast: 3740 3774 optional: true 3741 3775 3776 + c12@3.3.3: 3777 + resolution: {integrity: sha512-750hTRvgBy5kcMNPdh95Qo+XUBeGo8C7nsKSmedDmaQI+E0r82DwHeM6vBewDe4rGFbnxoa4V9pw+sPh5+Iz8Q==} 3778 + peerDependencies: 3779 + magicast: '*' 3780 + peerDependenciesMeta: 3781 + magicast: 3782 + optional: true 3783 + 3742 3784 cac@6.7.14: 3743 3785 resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 3744 3786 engines: {node: '>=8'} ··· 3786 3828 resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} 3787 3829 engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} 3788 3830 3789 - change-case@5.4.4: 3790 - resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} 3791 - 3792 3831 char-regex@1.0.2: 3793 3832 resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} 3794 3833 engines: {node: '>=10'} ··· 3817 3856 chokidar@4.0.3: 3818 3857 resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} 3819 3858 engines: {node: '>= 14.16.0'} 3859 + 3860 + chokidar@5.0.0: 3861 + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} 3862 + engines: {node: '>= 20.19.0'} 3820 3863 3821 3864 chownr@3.0.0: 3822 3865 resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} ··· 3918 3961 color-string@1.9.1: 3919 3962 resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} 3920 3963 3964 + color-support@1.1.3: 3965 + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} 3966 + hasBin: true 3967 + 3921 3968 color@4.2.3: 3922 3969 resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} 3923 3970 engines: {node: '>=12.5.0'} 3924 - 3925 - colorette@1.4.0: 3926 - resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} 3927 3971 3928 3972 combined-stream@1.0.8: 3929 3973 resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} ··· 3932 3976 commander@12.1.0: 3933 3977 resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} 3934 3978 engines: {node: '>=18'} 3979 + 3980 + commander@14.0.2: 3981 + resolution: {integrity: sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==} 3982 + engines: {node: '>=20'} 3935 3983 3936 3984 commander@2.20.3: 3937 3985 resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} ··· 4165 4213 resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} 4166 4214 engines: {node: '>=0.10.0'} 4167 4215 4216 + default-browser-id@5.0.1: 4217 + resolution: {integrity: sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==} 4218 + engines: {node: '>=18'} 4219 + 4220 + default-browser@5.4.0: 4221 + resolution: {integrity: sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==} 4222 + engines: {node: '>=18'} 4223 + 4168 4224 defaults@1.0.4: 4169 4225 resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} 4170 4226 4171 4227 define-lazy-prop@2.0.0: 4172 4228 resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} 4173 4229 engines: {node: '>=8'} 4230 + 4231 + define-lazy-prop@3.0.0: 4232 + resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} 4233 + engines: {node: '>=12'} 4174 4234 4175 4235 defu@6.1.4: 4176 4236 resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} ··· 4945 5005 resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 4946 5006 engines: {node: '>=0.8.19'} 4947 5007 4948 - index-to-position@1.2.0: 4949 - resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} 4950 - engines: {node: '>=18'} 4951 - 4952 5008 inflight@1.0.6: 4953 5009 resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 4954 5010 deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. ··· 4989 5045 engines: {node: '>=8'} 4990 5046 hasBin: true 4991 5047 5048 + is-docker@3.0.0: 5049 + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} 5050 + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 5051 + hasBin: true 5052 + 4992 5053 is-extglob@2.1.1: 4993 5054 resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 4994 5055 engines: {node: '>=0.10.0'} ··· 5004 5065 is-glob@4.0.3: 5005 5066 resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 5006 5067 engines: {node: '>=0.10.0'} 5068 + 5069 + is-in-ssh@1.0.0: 5070 + resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} 5071 + engines: {node: '>=20'} 5072 + 5073 + is-inside-container@1.0.0: 5074 + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} 5075 + engines: {node: '>=14.16'} 5076 + hasBin: true 5007 5077 5008 5078 is-interactive@1.0.0: 5009 5079 resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} ··· 5033 5103 is-wsl@2.2.0: 5034 5104 resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} 5035 5105 engines: {node: '>=8'} 5106 + 5107 + is-wsl@3.1.0: 5108 + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} 5109 + engines: {node: '>=16'} 5036 5110 5037 5111 isbot@5.1.34: 5038 5112 resolution: {integrity: sha512-aCMIBSKd/XPRYdiCQTLC8QHH4YT8B3JUADu+7COgYIZPvkeoMcUHMRjZLM9/7V8fCj+l7FSREc1lOPNjzogo/A==} ··· 5253 5327 jose@5.10.0: 5254 5328 resolution: {integrity: sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg==} 5255 5329 5256 - js-levenshtein@1.1.6: 5257 - resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} 5258 - engines: {node: '>=0.10.0'} 5259 - 5260 5330 js-tokens@4.0.0: 5261 5331 resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} 5262 5332 ··· 5727 5797 minimatch@3.1.2: 5728 5798 resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 5729 5799 5730 - minimatch@5.1.6: 5731 - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} 5732 - engines: {node: '>=10'} 5733 - 5734 5800 minimatch@9.0.5: 5735 5801 resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 5736 5802 engines: {node: '>=16 || 14 >=14.17'} ··· 5923 5989 resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} 5924 5990 engines: {node: '>=6'} 5925 5991 5992 + open@11.0.0: 5993 + resolution: {integrity: sha512-smsWv2LzFjP03xmvFoJ331ss6h+jixfA4UUV/Bsiyuu4YJPfN+FIQGOIiv4w9/+MoHkfkJ22UIaQWRVFRfH6Vw==} 5994 + engines: {node: '>=20'} 5995 + 5926 5996 open@7.4.2: 5927 5997 resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} 5928 5998 engines: {node: '>=8'} ··· 5931 6001 resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} 5932 6002 engines: {node: '>=12'} 5933 6003 5934 - openapi-fetch@0.15.0: 5935 - resolution: {integrity: sha512-OjQUdi61WO4HYhr9+byCPMj0+bgste/LtSBEcV6FzDdONTs7x0fWn8/ndoYwzqCsKWIxEZwo4FN/TG1c1rI8IQ==} 5936 - 5937 - openapi-typescript-helpers@0.0.15: 5938 - resolution: {integrity: sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==} 5939 - 5940 - openapi-typescript@7.10.1: 5941 - resolution: {integrity: sha512-rBcU8bjKGGZQT4K2ekSTY2Q5veOQbVG/lTKZ49DeCyT9z62hM2Vj/LLHjDHC9W7LJG8YMHcdXpRZDqC1ojB/lw==} 5942 - hasBin: true 5943 - peerDependencies: 5944 - typescript: ^5.x 5945 - 5946 6004 optionator@0.9.4: 5947 6005 resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} 5948 6006 engines: {node: '>= 0.8.0'} ··· 6006 6064 resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} 6007 6065 engines: {node: '>=8'} 6008 6066 6009 - parse-json@8.3.0: 6010 - resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} 6011 - engines: {node: '>=18'} 6012 - 6013 6067 parse-png@2.1.0: 6014 6068 resolution: {integrity: sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ==} 6015 6069 engines: {node: '>=10'} ··· 6073 6127 perfect-debounce@1.0.0: 6074 6128 resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} 6075 6129 6130 + perfect-debounce@2.1.0: 6131 + resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} 6132 + 6076 6133 pg-cloudflare@1.3.0: 6077 6134 resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} 6078 6135 ··· 6190 6247 postgres@3.4.7: 6191 6248 resolution: {integrity: sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==} 6192 6249 engines: {node: '>=12'} 6250 + 6251 + powershell-utils@0.1.0: 6252 + resolution: {integrity: sha512-dM0jVuXJPsDN6DvRpea484tCUaMiXWjuCn++HGTqUWzGDjv5tZkEZldAJ/UMlqRYGFrD/etByo4/xOuC/snX2A==} 6253 + engines: {node: '>=20'} 6193 6254 6194 6255 prelude-ls@1.2.1: 6195 6256 resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} ··· 6399 6460 resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} 6400 6461 engines: {node: '>= 14.18.0'} 6401 6462 6463 + readdirp@5.0.0: 6464 + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} 6465 + engines: {node: '>= 20.19.0'} 6466 + 6402 6467 real-require@0.2.0: 6403 6468 resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} 6404 6469 engines: {node: '>= 12.13.0'} ··· 6511 6576 router@2.2.0: 6512 6577 resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} 6513 6578 engines: {node: '>= 18'} 6579 + 6580 + run-applescript@7.1.0: 6581 + resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} 6582 + engines: {node: '>=18'} 6514 6583 6515 6584 rxjs@7.8.1: 6516 6585 resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} ··· 6817 6886 resolution: {integrity: sha512-oK8WG9diS3DlhdUkcFn4tkNIiIbBx9lI2ClF8K+b2/m8Eyv47LSawxUzZQSNKUrVb2KsqeTDCcjAAVPYaSLVTA==} 6818 6887 engines: {node: '>=14.18.0'} 6819 6888 6820 - supports-color@10.2.2: 6821 - resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} 6822 - engines: {node: '>=18'} 6823 - 6824 6889 supports-color@5.5.0: 6825 6890 resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} 6826 6891 engines: {node: '>=4'} ··· 7605 7670 utf-8-validate: 7606 7671 optional: true 7607 7672 7673 + wsl-utils@0.3.1: 7674 + resolution: {integrity: sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg==} 7675 + engines: {node: '>=20'} 7676 + 7608 7677 xcode@3.0.1: 7609 7678 resolution: {integrity: sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==} 7610 7679 engines: {node: '>=10.0.0'} ··· 7646 7715 yallist@5.0.0: 7647 7716 resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} 7648 7717 engines: {node: '>=18'} 7649 - 7650 - yaml-ast-parser@0.0.43: 7651 - resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} 7652 7718 7653 7719 yaml@2.8.2: 7654 7720 resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} ··· 8022 8088 '@babel/types': 7.28.6 8023 8089 '@jridgewell/remapping': 2.3.5 8024 8090 convert-source-map: 2.0.0 8025 - debug: 4.4.3(supports-color@10.2.2) 8091 + debug: 4.4.3 8026 8092 gensync: 1.0.0-beta.2 8027 8093 json5: 2.2.3 8028 8094 semver: 6.3.1 ··· 8074 8140 '@babel/core': 7.28.6 8075 8141 '@babel/helper-compilation-targets': 7.28.6 8076 8142 '@babel/helper-plugin-utils': 7.28.6 8077 - debug: 4.4.3(supports-color@10.2.2) 8143 + debug: 4.4.3 8078 8144 lodash.debounce: 4.0.8 8079 8145 resolve: 1.22.11 8080 8146 transitivePeerDependencies: ··· 8593 8659 '@babel/parser': 7.28.6 8594 8660 '@babel/template': 7.28.6 8595 8661 '@babel/types': 7.28.6 8596 - debug: 4.4.3(supports-color@10.2.2) 8662 + debug: 4.4.3 8597 8663 transitivePeerDependencies: 8598 8664 - supports-color 8599 8665 ··· 8799 8865 '@eslint/config-array@0.21.1': 8800 8866 dependencies: 8801 8867 '@eslint/object-schema': 2.1.7 8802 - debug: 4.4.3(supports-color@10.2.2) 8868 + debug: 4.4.3 8803 8869 minimatch: 3.1.2 8804 8870 transitivePeerDependencies: 8805 8871 - supports-color ··· 8815 8881 '@eslint/eslintrc@3.3.3': 8816 8882 dependencies: 8817 8883 ajv: 6.12.6 8818 - debug: 4.4.3(supports-color@10.2.2) 8884 + debug: 4.4.3 8819 8885 espree: 10.4.0 8820 8886 globals: 14.0.0 8821 8887 ignore: 5.3.2 ··· 8871 8937 ci-info: 3.9.0 8872 8938 compression: 1.8.1 8873 8939 connect: 3.7.0 8874 - debug: 4.4.3(supports-color@10.2.2) 8940 + debug: 4.4.3 8875 8941 env-editor: 0.4.2 8876 8942 expo: 54.0.32(@babel/core@7.28.6)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) 8877 8943 expo-server: 1.0.5 ··· 8924 8990 '@expo/plist': 0.3.5 8925 8991 '@expo/sdk-runtime-versions': 1.0.0 8926 8992 chalk: 4.1.2 8927 - debug: 4.4.3(supports-color@10.2.2) 8993 + debug: 4.4.3 8928 8994 getenv: 2.0.0 8929 8995 glob: 10.5.0 8930 8996 resolve-from: 5.0.0 ··· 8943 9009 '@expo/plist': 0.4.8 8944 9010 '@expo/sdk-runtime-versions': 1.0.0 8945 9011 chalk: 4.1.2 8946 - debug: 4.4.3(supports-color@10.2.2) 9012 + debug: 4.4.3 8947 9013 getenv: 2.0.0 8948 9014 glob: 13.0.0 8949 9015 resolve-from: 5.0.0 ··· 9012 9078 '@expo/env@1.0.7': 9013 9079 dependencies: 9014 9080 chalk: 4.1.2 9015 - debug: 4.4.3(supports-color@10.2.2) 9081 + debug: 4.4.3 9016 9082 dotenv: 16.4.7 9017 9083 dotenv-expand: 11.0.7 9018 9084 getenv: 2.0.0 ··· 9022 9088 '@expo/env@2.0.8': 9023 9089 dependencies: 9024 9090 chalk: 4.1.2 9025 - debug: 4.4.3(supports-color@10.2.2) 9091 + debug: 4.4.3 9026 9092 dotenv: 16.4.7 9027 9093 dotenv-expand: 11.0.7 9028 9094 getenv: 2.0.0 ··· 9034 9100 '@expo/spawn-async': 1.7.2 9035 9101 arg: 5.0.2 9036 9102 chalk: 4.1.2 9037 - debug: 4.4.3(supports-color@10.2.2) 9103 + debug: 4.4.3 9038 9104 getenv: 2.0.0 9039 9105 glob: 13.0.0 9040 9106 ignore: 5.3.2 ··· 9080 9146 '@expo/spawn-async': 1.7.2 9081 9147 browserslist: 4.28.1 9082 9148 chalk: 4.1.2 9083 - debug: 4.4.3(supports-color@10.2.2) 9149 + debug: 4.4.3 9084 9150 dotenv: 16.4.7 9085 9151 dotenv-expand: 11.0.7 9086 9152 getenv: 2.0.0 ··· 9153 9219 '@expo/image-utils': 0.8.8 9154 9220 '@expo/json-file': 10.0.8 9155 9221 '@react-native/normalize-colors': 0.81.5 9156 - debug: 4.4.3(supports-color@10.2.2) 9222 + debug: 4.4.3 9157 9223 expo: 54.0.32(@babel/core@7.28.6)(react-native@0.81.5(@babel/core@7.28.6)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) 9158 9224 resolve-from: 5.0.0 9159 9225 semver: 7.7.3 ··· 9185 9251 chalk: 4.1.2 9186 9252 js-yaml: 4.1.1 9187 9253 9254 + '@hey-api/client-fetch@0.13.1(@hey-api/openapi-ts@0.91.1(typescript@5.9.3))': 9255 + dependencies: 9256 + '@hey-api/openapi-ts': 0.91.1(typescript@5.9.3) 9257 + 9258 + '@hey-api/codegen-core@0.6.1(typescript@5.9.3)': 9259 + dependencies: 9260 + '@hey-api/types': 0.1.3(typescript@5.9.3) 9261 + ansi-colors: 4.1.3 9262 + c12: 3.3.3 9263 + color-support: 1.1.3 9264 + typescript: 5.9.3 9265 + transitivePeerDependencies: 9266 + - magicast 9267 + 9268 + '@hey-api/json-schema-ref-parser@1.2.3': 9269 + dependencies: 9270 + '@jsdevtools/ono': 7.1.3 9271 + '@types/json-schema': 7.0.15 9272 + js-yaml: 4.1.1 9273 + lodash: 4.17.21 9274 + 9275 + '@hey-api/openapi-ts@0.91.1(typescript@5.9.3)': 9276 + dependencies: 9277 + '@hey-api/codegen-core': 0.6.1(typescript@5.9.3) 9278 + '@hey-api/json-schema-ref-parser': 1.2.3 9279 + '@hey-api/shared': 0.1.1(typescript@5.9.3) 9280 + '@hey-api/types': 0.1.3(typescript@5.9.3) 9281 + ansi-colors: 4.1.3 9282 + color-support: 1.1.3 9283 + commander: 14.0.2 9284 + typescript: 5.9.3 9285 + transitivePeerDependencies: 9286 + - magicast 9287 + 9288 + '@hey-api/shared@0.1.1(typescript@5.9.3)': 9289 + dependencies: 9290 + '@hey-api/codegen-core': 0.6.1(typescript@5.9.3) 9291 + '@hey-api/json-schema-ref-parser': 1.2.3 9292 + '@hey-api/types': 0.1.3(typescript@5.9.3) 9293 + ansi-colors: 4.1.3 9294 + cross-spawn: 7.0.6 9295 + open: 11.0.0 9296 + semver: 7.7.3 9297 + typescript: 5.9.3 9298 + transitivePeerDependencies: 9299 + - magicast 9300 + 9301 + '@hey-api/types@0.1.3(typescript@5.9.3)': 9302 + dependencies: 9303 + typescript: 5.9.3 9304 + 9188 9305 '@hono/node-server@1.19.9(hono@4.11.4)': 9189 9306 dependencies: 9190 9307 hono: 4.11.4 ··· 9636 9753 dependencies: 9637 9754 '@jridgewell/resolve-uri': 3.1.2 9638 9755 '@jridgewell/sourcemap-codec': 1.5.5 9756 + 9757 + '@jsdevtools/ono@7.1.3': {} 9639 9758 9640 9759 '@lukeed/csprng@1.1.0': {} 9641 9760 ··· 10097 10216 '@react-native/community-cli-plugin@0.81.5': 10098 10217 dependencies: 10099 10218 '@react-native/dev-middleware': 0.81.5 10100 - debug: 4.4.3(supports-color@10.2.2) 10219 + debug: 4.4.3 10101 10220 invariant: 2.2.4 10102 10221 metro: 0.83.3 10103 10222 metro-config: 0.83.3 ··· 10117 10236 chrome-launcher: 0.15.2 10118 10237 chromium-edge-launcher: 0.2.0 10119 10238 connect: 3.7.0 10120 - debug: 4.4.3(supports-color@10.2.2) 10239 + debug: 4.4.3 10121 10240 invariant: 2.2.4 10122 10241 nullthrows: 1.1.1 10123 10242 open: 7.4.2 ··· 10193 10312 dependencies: 10194 10313 nanoid: 3.3.11 10195 10314 10196 - '@redocly/ajv@8.17.2': 10197 - dependencies: 10198 - fast-deep-equal: 3.1.3 10199 - fast-uri: 3.1.0 10200 - json-schema-traverse: 1.0.0 10201 - require-from-string: 2.0.2 10202 - 10203 - '@redocly/config@0.22.2': {} 10204 - 10205 - '@redocly/openapi-core@1.34.6(supports-color@10.2.2)': 10206 - dependencies: 10207 - '@redocly/ajv': 8.17.2 10208 - '@redocly/config': 0.22.2 10209 - colorette: 1.4.0 10210 - https-proxy-agent: 7.0.6(supports-color@10.2.2) 10211 - js-levenshtein: 1.1.6 10212 - js-yaml: 4.1.1 10213 - minimatch: 5.1.6 10214 - pluralize: 8.0.0 10215 - yaml-ast-parser: 0.0.43 10216 - transitivePeerDependencies: 10217 - - supports-color 10218 - 10219 10315 '@rolldown/pluginutils@1.0.0-beta.40': {} 10220 10316 10221 10317 '@rolldown/pluginutils@1.0.0-beta.53': {} ··· 10819 10915 10820 10916 '@tokenizer/inflate@0.4.1': 10821 10917 dependencies: 10822 - debug: 4.4.3(supports-color@10.2.2) 10918 + debug: 4.4.3 10823 10919 token-types: 6.1.2 10824 10920 transitivePeerDependencies: 10825 10921 - supports-color ··· 11007 11103 '@typescript-eslint/types': 8.54.0 11008 11104 '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) 11009 11105 '@typescript-eslint/visitor-keys': 8.54.0 11010 - debug: 4.4.3(supports-color@10.2.2) 11106 + debug: 4.4.3 11011 11107 eslint: 9.39.2(jiti@2.6.1) 11012 11108 typescript: 5.9.3 11013 11109 transitivePeerDependencies: ··· 11017 11113 dependencies: 11018 11114 '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) 11019 11115 '@typescript-eslint/types': 8.54.0 11020 - debug: 4.4.3(supports-color@10.2.2) 11116 + debug: 4.4.3 11021 11117 typescript: 5.9.3 11022 11118 transitivePeerDependencies: 11023 11119 - supports-color ··· 11036 11132 '@typescript-eslint/types': 8.54.0 11037 11133 '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) 11038 11134 '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) 11039 - debug: 4.4.3(supports-color@10.2.2) 11135 + debug: 4.4.3 11040 11136 eslint: 9.39.2(jiti@2.6.1) 11041 11137 ts-api-utils: 2.4.0(typescript@5.9.3) 11042 11138 typescript: 5.9.3 ··· 11051 11147 '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) 11052 11148 '@typescript-eslint/types': 8.54.0 11053 11149 '@typescript-eslint/visitor-keys': 8.54.0 11054 - debug: 4.4.3(supports-color@10.2.2) 11150 + debug: 4.4.3 11055 11151 minimatch: 9.0.5 11056 11152 semver: 7.7.3 11057 11153 tinyglobby: 0.2.15 ··· 11566 11662 babel-plugin-react-native-web: 0.21.2 11567 11663 babel-plugin-syntax-hermes-parser: 0.29.1 11568 11664 babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.28.6) 11569 - debug: 4.4.3(supports-color@10.2.2) 11665 + debug: 4.4.3 11570 11666 react-refresh: 0.14.2 11571 11667 resolve-from: 5.0.0 11572 11668 optionalDependencies: ··· 11633 11729 dependencies: 11634 11730 bytes: 3.1.2 11635 11731 content-type: 1.0.5 11636 - debug: 4.4.3(supports-color@10.2.2) 11732 + debug: 4.4.3 11637 11733 http-errors: 2.0.1 11638 11734 iconv-lite: 0.7.2 11639 11735 on-finished: 2.4.1 ··· 11698 11794 base64-js: 1.5.1 11699 11795 ieee754: 1.2.1 11700 11796 11797 + bundle-name@4.1.0: 11798 + dependencies: 11799 + run-applescript: 7.1.0 11800 + 11701 11801 busboy@1.6.0: 11702 11802 dependencies: 11703 11803 streamsearch: 1.1.0 ··· 11719 11819 pkg-types: 2.3.0 11720 11820 rc9: 2.1.2 11721 11821 11822 + c12@3.3.3: 11823 + dependencies: 11824 + chokidar: 5.0.0 11825 + confbox: 0.2.2 11826 + defu: 6.1.4 11827 + dotenv: 17.2.3 11828 + exsolve: 1.0.8 11829 + giget: 2.0.0 11830 + jiti: 2.6.1 11831 + ohash: 2.0.11 11832 + pathe: 2.0.3 11833 + perfect-debounce: 2.1.0 11834 + pkg-types: 2.3.0 11835 + rc9: 2.1.2 11836 + 11722 11837 cac@6.7.14: {} 11723 11838 11724 11839 call-bind-apply-helpers@1.0.2: ··· 11762 11877 11763 11878 chalk@5.6.2: {} 11764 11879 11765 - change-case@5.4.4: {} 11766 - 11767 11880 char-regex@1.0.2: {} 11768 11881 11769 11882 chardet@2.1.1: {} ··· 11817 11930 chokidar@4.0.3: 11818 11931 dependencies: 11819 11932 readdirp: 4.1.2 11933 + 11934 + chokidar@5.0.0: 11935 + dependencies: 11936 + readdirp: 5.0.0 11820 11937 11821 11938 chownr@3.0.0: {} 11822 11939 ··· 11917 12034 color-name: 1.1.4 11918 12035 simple-swizzle: 0.2.4 11919 12036 12037 + color-support@1.1.3: {} 12038 + 11920 12039 color@4.2.3: 11921 12040 dependencies: 11922 12041 color-convert: 2.0.1 11923 12042 color-string: 1.9.1 11924 - 11925 - colorette@1.4.0: {} 11926 12043 11927 12044 combined-stream@1.0.8: 11928 12045 dependencies: 11929 12046 delayed-stream: 1.0.0 11930 12047 11931 12048 commander@12.1.0: {} 12049 + 12050 + commander@14.0.2: {} 11932 12051 11933 12052 commander@2.20.3: {} 11934 12053 ··· 12089 12208 dependencies: 12090 12209 ms: 2.1.3 12091 12210 12092 - debug@4.4.3(supports-color@10.2.2): 12211 + debug@4.4.3: 12093 12212 dependencies: 12094 12213 ms: 2.1.3 12095 - optionalDependencies: 12096 - supports-color: 10.2.2 12097 12214 12098 12215 decimal.js@10.6.0: {} 12099 12216 ··· 12111 12228 12112 12229 deepmerge@4.3.1: {} 12113 12230 12231 + default-browser-id@5.0.1: {} 12232 + 12233 + default-browser@5.4.0: 12234 + dependencies: 12235 + bundle-name: 4.1.0 12236 + default-browser-id: 5.0.1 12237 + 12114 12238 defaults@1.0.4: 12115 12239 dependencies: 12116 12240 clone: 1.0.4 12117 12241 12118 12242 define-lazy-prop@2.0.0: {} 12243 + 12244 + define-lazy-prop@3.0.0: {} 12119 12245 12120 12246 defu@6.1.4: {} 12121 12247 ··· 12336 12462 ajv: 6.12.6 12337 12463 chalk: 4.1.2 12338 12464 cross-spawn: 7.0.6 12339 - debug: 4.4.3(supports-color@10.2.2) 12465 + debug: 4.4.3 12340 12466 escape-string-regexp: 4.0.0 12341 12467 eslint-scope: 8.4.0 12342 12468 eslint-visitor-keys: 4.2.1 ··· 12586 12712 content-type: 1.0.5 12587 12713 cookie: 0.7.2 12588 12714 cookie-signature: 1.2.2 12589 - debug: 4.4.3(supports-color@10.2.2) 12715 + debug: 4.4.3 12590 12716 depd: 2.0.0 12591 12717 encodeurl: 2.0.0 12592 12718 escape-html: 1.0.3 ··· 12684 12810 12685 12811 finalhandler@2.1.1: 12686 12812 dependencies: 12687 - debug: 4.4.3(supports-color@10.2.2) 12813 + debug: 4.4.3 12688 12814 encodeurl: 2.0.0 12689 12815 escape-html: 1.0.3 12690 12816 on-finished: 2.4.1 ··· 12950 13076 http-proxy-agent@7.0.2: 12951 13077 dependencies: 12952 13078 agent-base: 7.1.4 12953 - debug: 4.4.3(supports-color@10.2.2) 13079 + debug: 4.4.3 12954 13080 transitivePeerDependencies: 12955 13081 - supports-color 12956 13082 12957 13083 http-status-codes@2.3.0: {} 12958 13084 12959 - https-proxy-agent@7.0.6(supports-color@10.2.2): 13085 + https-proxy-agent@7.0.6: 12960 13086 dependencies: 12961 13087 agent-base: 7.1.4 12962 - debug: 4.4.3(supports-color@10.2.2) 13088 + debug: 4.4.3 12963 13089 transitivePeerDependencies: 12964 13090 - supports-color 12965 13091 ··· 12999 13125 13000 13126 imurmurhash@0.1.4: {} 13001 13127 13002 - index-to-position@1.2.0: {} 13003 - 13004 13128 inflight@1.0.6: 13005 13129 dependencies: 13006 13130 once: 1.4.0 ··· 13032 13156 13033 13157 is-docker@2.2.1: {} 13034 13158 13159 + is-docker@3.0.0: {} 13160 + 13035 13161 is-extglob@2.1.1: {} 13036 13162 13037 13163 is-fullwidth-code-point@3.0.0: {} ··· 13042 13168 dependencies: 13043 13169 is-extglob: 2.1.1 13044 13170 13171 + is-in-ssh@1.0.0: {} 13172 + 13173 + is-inside-container@1.0.0: 13174 + dependencies: 13175 + is-docker: 3.0.0 13176 + 13045 13177 is-interactive@1.0.0: {} 13046 13178 13047 13179 is-number@7.0.0: {} ··· 13060 13192 dependencies: 13061 13193 is-docker: 2.2.1 13062 13194 13195 + is-wsl@3.1.0: 13196 + dependencies: 13197 + is-inside-container: 1.0.0 13198 + 13063 13199 isbot@5.1.34: {} 13064 13200 13065 13201 isexe@2.0.0: {} ··· 13097 13233 istanbul-lib-source-maps@5.0.6: 13098 13234 dependencies: 13099 13235 '@jridgewell/trace-mapping': 0.3.31 13100 - debug: 4.4.3(supports-color@10.2.2) 13236 + debug: 4.4.3 13101 13237 istanbul-lib-coverage: 3.2.2 13102 13238 transitivePeerDependencies: 13103 13239 - supports-color ··· 13511 13647 13512 13648 jose@5.10.0: {} 13513 13649 13514 - js-levenshtein@1.1.6: {} 13515 - 13516 13650 js-tokens@4.0.0: {} 13517 13651 13518 13652 js-tokens@9.0.1: {} ··· 13538 13672 decimal.js: 10.6.0 13539 13673 html-encoding-sniffer: 6.0.0(@noble/hashes@1.8.0) 13540 13674 http-proxy-agent: 7.0.2 13541 - https-proxy-agent: 7.0.6(supports-color@10.2.2) 13675 + https-proxy-agent: 7.0.6 13542 13676 is-potential-custom-element-name: 1.0.1 13543 13677 parse5: 8.0.0 13544 13678 saxes: 6.0.0 ··· 13823 13957 dependencies: 13824 13958 exponential-backoff: 3.1.3 13825 13959 flow-enums-runtime: 0.0.6 13826 - https-proxy-agent: 7.0.6(supports-color@10.2.2) 13960 + https-proxy-agent: 7.0.6 13827 13961 metro-core: 0.83.3 13828 13962 transitivePeerDependencies: 13829 13963 - supports-color ··· 13851 13985 13852 13986 metro-file-map@0.83.3: 13853 13987 dependencies: 13854 - debug: 4.4.3(supports-color@10.2.2) 13988 + debug: 4.4.3 13855 13989 fb-watchman: 2.0.2 13856 13990 flow-enums-runtime: 0.0.6 13857 13991 graceful-fs: 4.2.11 ··· 13947 14081 chalk: 4.1.2 13948 14082 ci-info: 2.0.0 13949 14083 connect: 3.7.0 13950 - debug: 4.4.3(supports-color@10.2.2) 14084 + debug: 4.4.3 13951 14085 error-stack-parser: 2.1.4 13952 14086 flow-enums-runtime: 0.0.6 13953 14087 graceful-fs: 4.2.11 ··· 14014 14148 dependencies: 14015 14149 brace-expansion: 1.1.12 14016 14150 14017 - minimatch@5.1.6: 14018 - dependencies: 14019 - brace-expansion: 2.0.2 14020 - 14021 14151 minimatch@9.0.5: 14022 14152 dependencies: 14023 14153 brace-expansion: 2.0.2 ··· 14094 14224 14095 14225 nf3@0.3.7: {} 14096 14226 14097 - nitro-nightly@3.0.1-20260127-164246-ef01b092(@electric-sql/pglite@0.3.15)(chokidar@4.0.3)(lru-cache@11.2.5)(mysql2@3.15.3)(rollup@4.57.0)(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): 14227 + nitro-nightly@3.0.1-20260127-164246-ef01b092(@electric-sql/pglite@0.3.15)(chokidar@5.0.0)(lru-cache@11.2.5)(mysql2@3.15.3)(rollup@4.57.0)(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): 14098 14228 dependencies: 14099 14229 consola: 3.4.2 14100 14230 crossws: 0.4.4(srvx@0.10.1) ··· 14109 14239 srvx: 0.10.1 14110 14240 undici: 7.19.2 14111 14241 unenv: 2.0.0-rc.24 14112 - unstorage: 2.0.0-alpha.5(chokidar@4.0.3)(db0@0.3.4(@electric-sql/pglite@0.3.15)(mysql2@3.15.3))(lru-cache@11.2.5)(ofetch@2.0.0-alpha.3) 14242 + unstorage: 2.0.0-alpha.5(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.3.15)(mysql2@3.15.3))(lru-cache@11.2.5)(ofetch@2.0.0-alpha.3) 14113 14243 optionalDependencies: 14114 14244 rollup: 4.57.0 14115 14245 vite: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) ··· 14217 14347 dependencies: 14218 14348 mimic-fn: 2.1.0 14219 14349 14350 + open@11.0.0: 14351 + dependencies: 14352 + default-browser: 5.4.0 14353 + define-lazy-prop: 3.0.0 14354 + is-in-ssh: 1.0.0 14355 + is-inside-container: 1.0.0 14356 + powershell-utils: 0.1.0 14357 + wsl-utils: 0.3.1 14358 + 14220 14359 open@7.4.2: 14221 14360 dependencies: 14222 14361 is-docker: 2.2.1 ··· 14228 14367 is-docker: 2.2.1 14229 14368 is-wsl: 2.2.0 14230 14369 14231 - openapi-fetch@0.15.0: 14232 - dependencies: 14233 - openapi-typescript-helpers: 0.0.15 14234 - 14235 - openapi-typescript-helpers@0.0.15: {} 14236 - 14237 - openapi-typescript@7.10.1(typescript@5.9.3): 14238 - dependencies: 14239 - '@redocly/openapi-core': 1.34.6(supports-color@10.2.2) 14240 - ansi-colors: 4.1.3 14241 - change-case: 5.4.4 14242 - parse-json: 8.3.0 14243 - supports-color: 10.2.2 14244 - typescript: 5.9.3 14245 - yargs-parser: 21.1.1 14246 - 14247 14370 optionator@0.9.4: 14248 14371 dependencies: 14249 14372 deep-is: 0.1.4 ··· 14362 14485 json-parse-even-better-errors: 2.3.1 14363 14486 lines-and-columns: 1.2.4 14364 14487 14365 - parse-json@8.3.0: 14366 - dependencies: 14367 - '@babel/code-frame': 7.28.6 14368 - index-to-position: 1.2.0 14369 - type-fest: 4.41.0 14370 - 14371 14488 parse-png@2.1.0: 14372 14489 dependencies: 14373 14490 pngjs: 3.4.0 ··· 14420 14537 pathval@2.0.1: {} 14421 14538 14422 14539 perfect-debounce@1.0.0: {} 14540 + 14541 + perfect-debounce@2.1.0: {} 14423 14542 14424 14543 pg-cloudflare@1.3.0: 14425 14544 optional: true ··· 14534 14653 xtend: 4.0.2 14535 14654 14536 14655 postgres@3.4.7: {} 14656 + 14657 + powershell-utils@0.1.0: {} 14537 14658 14538 14659 prelude-ls@1.2.1: {} 14539 14660 ··· 14781 14902 14782 14903 readdirp@4.1.2: {} 14783 14904 14905 + readdirp@5.0.0: {} 14906 + 14784 14907 real-require@0.2.0: {} 14785 14908 14786 14909 recast@0.23.11: ··· 14909 15032 14910 15033 router@2.2.0: 14911 15034 dependencies: 14912 - debug: 4.4.3(supports-color@10.2.2) 15035 + debug: 4.4.3 14913 15036 depd: 2.0.0 14914 15037 is-promise: 4.0.0 14915 15038 parseurl: 1.3.3 14916 15039 path-to-regexp: 8.3.0 14917 15040 transitivePeerDependencies: 14918 15041 - supports-color 15042 + 15043 + run-applescript@7.1.0: {} 14919 15044 14920 15045 rxjs@7.8.1: 14921 15046 dependencies: ··· 14978 15103 14979 15104 send@1.2.1: 14980 15105 dependencies: 14981 - debug: 4.4.3(supports-color@10.2.2) 15106 + debug: 4.4.3 14982 15107 encodeurl: 2.0.0 14983 15108 escape-html: 1.0.3 14984 15109 etag: 1.8.1 ··· 15225 15350 dependencies: 15226 15351 component-emitter: 1.3.1 15227 15352 cookiejar: 2.1.4 15228 - debug: 4.4.3(supports-color@10.2.2) 15353 + debug: 4.4.3 15229 15354 fast-safe-stringify: 2.1.1 15230 15355 form-data: 4.0.5 15231 15356 formidable: 3.5.4 ··· 15242 15367 superagent: 10.3.0 15243 15368 transitivePeerDependencies: 15244 15369 - supports-color 15245 - 15246 - supports-color@10.2.2: {} 15247 15370 15248 15371 supports-color@5.5.0: 15249 15372 dependencies: ··· 15625 15748 '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 15626 15749 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 15627 15750 15628 - unstorage@2.0.0-alpha.5(chokidar@4.0.3)(db0@0.3.4(@electric-sql/pglite@0.3.15)(mysql2@3.15.3))(lru-cache@11.2.5)(ofetch@2.0.0-alpha.3): 15751 + unstorage@2.0.0-alpha.5(chokidar@5.0.0)(db0@0.3.4(@electric-sql/pglite@0.3.15)(mysql2@3.15.3))(lru-cache@11.2.5)(ofetch@2.0.0-alpha.3): 15629 15752 optionalDependencies: 15630 - chokidar: 4.0.3 15753 + chokidar: 5.0.0 15631 15754 db0: 0.3.4(@electric-sql/pglite@0.3.15)(mysql2@3.15.3) 15632 15755 lru-cache: 11.2.5 15633 15756 ofetch: 2.0.0-alpha.3 ··· 15683 15806 vite-node@3.2.4(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2): 15684 15807 dependencies: 15685 15808 cac: 6.7.14 15686 - debug: 4.4.3(supports-color@10.2.2) 15809 + debug: 4.4.3 15687 15810 es-module-lexer: 1.7.0 15688 15811 pathe: 2.0.3 15689 15812 vite: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) ··· 15703 15826 15704 15827 vite-tsconfig-paths@6.0.5(typescript@5.9.3)(vite@7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)): 15705 15828 dependencies: 15706 - debug: 4.4.3(supports-color@10.2.2) 15829 + debug: 4.4.3 15707 15830 globrex: 0.1.2 15708 15831 tsconfck: 3.1.6(typescript@5.9.3) 15709 15832 vite: 7.3.1(@types/node@22.19.7)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) ··· 15743 15866 '@vitest/spy': 3.2.4 15744 15867 '@vitest/utils': 3.2.4 15745 15868 chai: 5.3.3 15746 - debug: 4.4.3(supports-color@10.2.2) 15869 + debug: 4.4.3 15747 15870 expect-type: 1.3.0 15748 15871 magic-string: 0.30.17 15749 15872 pathe: 2.0.3 ··· 15913 16036 15914 16037 ws@8.19.0: {} 15915 16038 16039 + wsl-utils@0.3.1: 16040 + dependencies: 16041 + is-wsl: 3.1.0 16042 + powershell-utils: 0.1.0 16043 + 15916 16044 xcode@3.0.1: 15917 16045 dependencies: 15918 16046 simple-plist: 1.3.1 ··· 15945 16073 yallist@3.1.1: {} 15946 16074 15947 16075 yallist@5.0.0: {} 15948 - 15949 - yaml-ast-parser@0.0.43: {} 15950 16076 15951 16077 yaml@2.8.2: {} 15952 16078