UNPKG

@blinkdotnew/sdk

Version:

Blink TypeScript SDK for client-side applications - Zero-boilerplate CRUD + auth + AI + analytics + notifications for modern SaaS/AI apps

1,485 lines (1,228 loc) β€’ 83.6 kB
# @blinkdotnew/sdk [![npm version](https://badge.fury.io/js/%40blinkdotnew%2Fsdk.svg)](https://badge.fury.io/js/%40blinkdotnew%2Fsdk) [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/) **The full-stack TypeScript SDK that powers Blink AI-generated apps** Blink is an AI App Builder that builds fully functional apps in seconds. This SDK (`@blinkdotnew/sdk`) is the TypeScript foundation that powers every Blink app natively, providing zero-boilerplate authentication, database operations, AI capabilities, and file storage. Works seamlessly on both client-side (React, Vue, etc.) and server-side (Node.js, Deno, Edge functions). ## πŸš€ Quick Start ### **Step 1: Create a Blink Project** Visit [blink.new](https://blink.new) and create a new project. Blink's AI agent will build your app in seconds. ### **Step 2: Install the SDK** Use Blink's AI agent to automatically install this SDK in your Vite React TypeScript client, or install manually: ```bash npm install @blinkdotnew/sdk ``` ### **Step 3: Use Your Project ID** Get your project ID from your Blink dashboard and start building: ```typescript import { createClient } from '@blinkdotnew/sdk' const blink = createClient({ projectId: 'your-blink-project-id', // From blink.new dashboard authRequired: false // Don't force immediate auth - let users browse first }) // Authentication - Choose your mode: // 🎯 MANAGED MODE: Quick setup with hosted auth page const blink = createClient({ projectId: 'your-project', auth: { mode: 'managed' } }) // Use: blink.auth.login() - redirects to blink.new auth // 🎨 HEADLESS MODE: Custom UI with full control const blink = createClient({ projectId: 'your-project', auth: { mode: 'headless' } }) // Use: blink.auth.signInWithEmail(), blink.auth.signInWithGoogle(), etc. // Current user (works in both modes) const user = await blink.auth.me() // Database operations (zero config) const todos = await blink.db.todos.list({ where: { userId: user.id }, orderBy: { createdAt: 'desc' }, limit: 20 }) // AI operations (native) const { text } = await blink.ai.generateText({ prompt: "Write a summary of the user's todos" }) // Data operations (extract text from documents) const text = await blink.data.extractFromUrl("https://example.com/document.pdf") // Website scraping and screenshots (crystal clear results!) const { markdown, metadata, links } = await blink.data.scrape("https://competitor.com") const screenshotUrl = await blink.data.screenshot("https://competitor.com") // Web search (get real-time information) const searchResults = await blink.data.search("chatgpt latest news", { type: 'news' }) const localResults = await blink.data.search("best restaurants", { location: "San Francisco,CA,United States" }) // Notifications (NEW!) const { success } = await blink.notifications.email({ to: 'customer@example.com', subject: 'Your order has shipped!', html: '<h1>Order Confirmation</h1><p>Your order #12345 is on its way.</p>' }) // Secure API proxy (call external APIs with secret substitution) const response = await blink.data.fetch({ url: "https://api.sendgrid.com/v3/mail/send", method: "POST", headers: { "Authorization": "Bearer {{sendgrid_api_key}}" }, body: { /* email data */ } }) // Realtime operations (live messaging and presence) const unsubscribe = await blink.realtime.subscribe('chat-room', (message) => { console.log('New message:', message.data) }) await blink.realtime.publish('chat-room', 'message', { text: 'Hello world!' }) // Get presence - returns array of PresenceUser objects directly const users = await blink.realtime.presence('chat-room') console.log('Online users:', users.length) // users is PresenceUser[] format: // [ // { // userId: 'user123', // metadata: { displayName: 'Alice', status: 'online' }, // joinedAt: 1640995200000, // lastSeen: 1640995230000 // } // ] // Analytics operations (automatic pageview tracking + custom events) // Pageviews are tracked automatically on initialization and route changes blink.analytics.log('button_clicked', { button_id: 'signup', page: '/pricing' }) // Check if analytics is enabled if (blink.analytics.isEnabled()) { console.log('Analytics is active') } // Disable/enable analytics blink.analytics.disable() blink.analytics.enable() // Storage operations (instant - returns public URL directly) const { publicUrl } = await blink.storage.upload( file, `avatars/${user.id}-${Date.now()}.${file.name.split('.').pop()}`, // βœ… Extract original extension { upsert: true } ) ``` ## πŸ€– What is Blink? **Blink is an AI App Builder** that creates fully functional applications in seconds. Simply describe what you want to build, and Blink's AI agent will: - πŸ—οΈ **Generate complete apps** with React + TypeScript + Vite - πŸ”§ **Auto-install this SDK** with zero configuration - 🎨 **Create beautiful UIs** with Tailwind CSS - πŸš€ **Deploy instantly** with authentication, database, AI, and storage built-in ## πŸ“š SDK Features This SDK powers every Blink-generated app with: - **πŸ” Authentication**: Flexible auth system with managed (redirect) and headless (custom UI) modes, email/password, social providers (Google, GitHub, Apple, Microsoft), magic links, RBAC, and custom email branding - **πŸ—„οΈ Database**: PostgREST-compatible CRUD operations with advanced filtering - **πŸ€– AI**: Multi-model image generation & editing (10 models), text generation with web search, object generation, speech synthesis, and transcription - **πŸ“„ Data**: Extract text content from documents, secure API proxy with secret substitution, web scraping, screenshots, and web search - **πŸ“ Storage**: File upload, download, and management - **πŸ“§ Notifications**: Email sending with attachments, custom branding, and delivery tracking - **⚑ Realtime**: WebSocket-based pub/sub messaging, presence tracking, and live updates - **πŸ“Š Analytics**: Automatic pageview tracking, custom event logging, session management, and privacy-first design - **🌐 Universal**: Works on client-side and server-side - **πŸ“± Framework Agnostic**: React, Vue, Svelte, vanilla JS, Node.js, Deno, **React Native** - **πŸ“± React Native**: First-class mobile support with AsyncStorage integration and platform-aware features - **πŸ”„ Real-time**: Built-in auth state management and token refresh - **⚑ Zero Boilerplate**: Everything works out of the box ## πŸ› οΈ Manual Installation & Setup > **πŸ’‘ Tip**: If you're using Blink's AI agent, this is all done automatically for you! ### Client-side (React, Vue, etc.) ```typescript import { createClient } from '@blinkdotnew/sdk' const blink = createClient({ projectId: 'your-blink-project-id', // From blink.new dashboard authRequired: false, // Let users browse first - require auth only for protected areas auth: { mode: 'managed' // Use new explicit configuration } }) ``` > **⚠️ Version Requirement**: The flexible authentication system (managed vs headless modes) requires SDK version **0.18.0 or higher**. If you're using version 0.17.x or below, you'll only have access to the legacy authentication system. Please upgrade to access the new authentication features. ### Server-side (Node.js, Deno, Edge functions) ```typescript import { createClient } from '@blinkdotnew/sdk' const blink = createClient({ projectId: 'your-blink-project-id', // From blink.new dashboard auth: { mode: 'managed' } // Manual token management }) // Token injection is only needed when calling blink.auth.* methods on the server ``` ### πŸ“± React Native (iOS & Android) The SDK has **first-class React Native support** with platform-aware features that automatically adapt to mobile environments. #### Step 1: Install Dependencies ```bash npm install @blinkdotnew/sdk @react-native-async-storage/async-storage expo-web-browser ``` **Required packages:** - `@blinkdotnew/sdk` - The Blink SDK - `@react-native-async-storage/async-storage` - For token persistence - `expo-web-browser` - For OAuth authentication (Google, GitHub, Apple, etc.) #### Step 2: Create Client (`lib/blink.ts`) **⚠️ IMPORTANT: You MUST pass `webBrowser: WebBrowser` to enable OAuth on mobile!** ```typescript // lib/blink.ts import { createClient, AsyncStorageAdapter } from '@blinkdotnew/sdk' import AsyncStorage from '@react-native-async-storage/async-storage' import * as WebBrowser from 'expo-web-browser' // ← Import this export const blink = createClient({ projectId: 'your-project-id', authRequired: false, auth: { mode: 'headless', webBrowser: WebBrowser // ← Pass it here! Required for OAuth }, storage: new AsyncStorageAdapter(AsyncStorage) }) ``` #### Step 3: Use Authentication **Email/Password (works immediately):** ```typescript // Sign up const user = await blink.auth.signUp({ email: 'user@example.com', password: 'SecurePass123' }) // Sign in const user = await blink.auth.signInWithEmail('user@example.com', 'SecurePass123') ``` **OAuth (Google, GitHub, Apple, Microsoft):** ```typescript // βœ… Same code works on web, iOS, AND Android! const user = await blink.auth.signInWithGoogle() const user = await blink.auth.signInWithGitHub() const user = await blink.auth.signInWithApple() const user = await blink.auth.signInWithMicrosoft() ``` #### Step 4: Create Auth Hook (`hooks/useAuth.ts`) ```typescript // hooks/useAuth.ts import { useEffect, useState } from 'react' import { blink } from '@/lib/blink' import type { BlinkUser } from '@blinkdotnew/sdk' export function useAuth() { const [user, setUser] = useState<BlinkUser | null>(null) const [isLoading, setIsLoading] = useState(true) useEffect(() => { const unsubscribe = blink.auth.onAuthStateChanged((state) => { setUser(state.user) setIsLoading(state.isLoading) }) return unsubscribe }, []) return { user, isLoading, isAuthenticated: !!user, signInWithGoogle: () => blink.auth.signInWithGoogle(), signInWithGitHub: () => blink.auth.signInWithGitHub(), signInWithApple: () => blink.auth.signInWithApple(), signOut: () => blink.auth.signOut(), } } ``` #### Step 5: Use in Components ```typescript import { useAuth } from '@/hooks/useAuth' import { View, Text, Button } from 'react-native' function App() { const { user, isLoading, signInWithGoogle, signOut } = useAuth() if (isLoading) return <Text>Loading...</Text> if (!user) { return <Button onPress={signInWithGoogle} title="Sign in with Google" /> } return ( <View> <Text>Welcome, {user.email}!</Text> <Button onPress={signOut} title="Sign Out" /> </View> ) } ``` #### Common Mistakes ```typescript // ❌ WRONG: Missing webBrowser - OAuth won't work on mobile! const blink = createClient({ projectId: 'your-project-id', auth: { mode: 'headless' }, // Missing webBrowser! storage: new AsyncStorageAdapter(AsyncStorage) }) // βœ… CORRECT: Include webBrowser for OAuth support import * as WebBrowser from 'expo-web-browser' const blink = createClient({ projectId: 'your-project-id', auth: { mode: 'headless', webBrowser: WebBrowser // ← Required for OAuth! }, storage: new AsyncStorageAdapter(AsyncStorage) }) ``` #### Low-Level OAuth (For Custom Control) If you need custom polling timeouts or manual browser handling: ```typescript // Get auth URL and authenticate function separately const { authUrl, authenticate } = await blink.auth.signInWithProviderMobile('google') // Open browser manually await WebBrowser.openAuthSessionAsync(authUrl) // Poll with custom options const user = await authenticate({ maxAttempts: 120, // 60 seconds (default: 60 = 30 seconds) intervalMs: 500 // Check every 500ms }) ``` #### Platform Features - βœ… **AsyncStorage** - Secure token persistence - βœ… **Universal OAuth** - Same code works on web + mobile - βœ… **expo-web-browser** - Native browser UI - βœ… **No deep linking** - Session-based polling - βœ… **Works in Expo Go** - No custom dev client needed - βœ… **Auto token refresh** - Seamless sessions ## πŸ“– API Reference ### Authentication > **⚠️ Version Requirement**: The flexible authentication system requires SDK version **0.18.0 or higher**. Version 0.17.x and below only support the legacy authentication system. Blink provides **two authentication modes**: ## 🎯 Managed Mode (Redirect-based) **Perfect for:** Quick setup, minimal code **Best for:** Websites, simple apps, MVP development ```typescript const blink = createClient({ projectId: 'your-project', auth: { mode: 'managed' } }) // ONE METHOD: Redirect to hosted auth page blink.auth.login() // β†’ Redirects to blink.new/auth blink.auth.logout() // Clear tokens and redirect // User state (automatic after redirect) const user = await blink.auth.me() ``` ## 🎨 Headless Mode (Custom UI) **Perfect for:** Custom branding, advanced UX, mobile apps **Best for:** Production apps, branded experiences ```typescript const blink = createClient({ projectId: 'your-project', auth: { mode: 'headless' } }) // MULTIPLE METHODS: Build your own UI const user = await blink.auth.signUp({ email, password }) const user = await blink.auth.signInWithEmail(email, password) const user = await blink.auth.signInWithGoogle() const user = await blink.auth.signInWithGitHub() const user = await blink.auth.signInWithApple() const user = await blink.auth.signInWithMicrosoft() // βœ… Store custom signup fields inside metadata await blink.auth.signUp({ email: 'founder@example.com', password: 'SuperSecret123', displayName: 'Alex Founder', role: 'operations', metadata: { company: 'Acme Freight', marketingConsent: true } }) // `displayName`, `avatar`, and `role` map to dedicated auth columns. // Everything else goes into auth.users.metadata automatically. // Keep custom fields in metadata or your own profile tableβ€”avoid adding NOT NULL // columns directly to auth tables. // Magic links (passwordless) await blink.auth.sendMagicLink(email) // Password management await blink.auth.sendPasswordResetEmail(email) await blink.auth.sendPasswordResetEmail(email, { redirectUrl: 'https://myapp.com/reset-password' }) await blink.auth.changePassword(oldPass, newPass) // Email verification await blink.auth.sendEmailVerification() ``` ### ⚑ Quick Mode Comparison | Feature | **Managed Mode** | **Headless Mode** | |---------|------------------|-------------------| | **Setup** | 1 line of code | Custom UI required | | **Methods** | `login()` only | `signInWith*()` methods | | **UI** | Hosted auth page | Your custom forms | | **Branding** | Blink-branded | Fully customizable | | **Mobile** | Web redirects | Native integration | ### 🚨 **Common Mistake** ```typescript // ❌ WRONG: Using managed method in headless mode const blink = createClient({ auth: { mode: 'headless' } }) blink.auth.login() // Still redirects! Wrong method for headless // βœ… CORRECT: Use headless methods await blink.auth.signInWithEmail(email, password) await blink.auth.signInWithGoogle() ``` #### πŸ”§ Provider Configuration **Step 1: Enable Providers in Your Project** 1. Go to [blink.new](https://blink.new) and open your project 2. Navigate to **Project β†’ Workspace β†’ Authentication** 3. Toggle providers on/off: - **Email** βœ… (enabled by default) - Includes email/password, magic links, and verification - **Google** βœ… (enabled by default) - **GitHub** βšͺ (disabled by default) - **Apple** βšͺ (disabled by default) - **Microsoft** βšͺ (disabled by default) 4. Configure email settings: - **Require email verification**: Off by default (easier implementation) - **Allow user signup**: On by default **Step 2: Discover Available Providers** ```typescript // Get providers enabled for your project const availableProviders = await blink.auth.getAvailableProviders() // Returns: ['email', 'google'] (based on your project settings) // Use in your UI to show only enabled providers const showGoogleButton = availableProviders.includes('google') const showGitHubButton = availableProviders.includes('github') ``` **Step 3: Client-Side Filtering (Headless Mode)** ```typescript const blink = createClient({ projectId: 'your-project', auth: { mode: 'headless' // All providers controlled via project settings } }) ``` **Managed Mode: Automatic Provider Display** ```typescript const blink = createClient({ projectId: 'your-project', auth: { mode: 'managed' } }) // The hosted auth page automatically shows only enabled providers blink.auth.login() // Shows Email + Google by default ``` #### πŸ“§ Email Verification Flow **By default, email verification is NOT required** for easier implementation. Enable it only if needed: **Step 1: Configure Verification (Optional)** 1. Go to [blink.new](https://blink.new) β†’ Project β†’ Workspace β†’ Authentication 2. Toggle **"Require email verification"** ON (disabled by default) **Step 2: Handle the Complete Flow** ```typescript // User signup - always send verification email for security try { const user = await blink.auth.signUp({ email, password }) await blink.auth.sendEmailVerification() setMessage('Account created! Check your email to verify.') } catch (error) { setError(error.message) } // User signin - handle verification requirement try { await blink.auth.signInWithEmail(email, password) // Success - user is signed in } catch (error) { if (error.code === 'EMAIL_NOT_VERIFIED') { setError('Please verify your email first') await blink.auth.sendEmailVerification() // Resend verification } else { setError(error.message) } } // Manual verification resend await blink.auth.sendEmailVerification() ``` **What Happens:** 1. **Signup**: User account created, `email_verified = false` 2. **Verification Email**: User clicks link β†’ `email_verified = true` 3. **Signin Check**: If verification required AND not verified β†’ `EMAIL_NOT_VERIFIED` error 4. **Success**: User can sign in once verified (or if verification not required) #### Flexible Email System **Maximum flexibility** - you control email branding while Blink handles secure tokens: ```typescript // Option A: Custom email delivery (full branding control) const resetData = await blink.auth.generatePasswordResetToken('user@example.com') // Returns: { token, expiresAt, resetUrl } // Send with your email service and branding await yourEmailService.send({ to: 'user@example.com', subject: 'Reset your YourApp password', html: ` <div style="font-family: Arial, sans-serif;"> <img src="https://yourapp.com/logo.png" alt="YourApp" /> <h1>Reset Your Password</h1> <a href="${resetData.resetUrl}" style="background: #0070f3; color: white; padding: 16px 32px; text-decoration: none; border-radius: 8px;"> Reset Password </a> </div> ` }) // Option B: Blink default email (zero setup) await blink.auth.sendPasswordResetEmail('user@example.com') // Same flexibility for email verification and magic links const verifyData = await blink.auth.generateEmailVerificationToken() const magicData = await blink.auth.generateMagicLinkToken('user@example.com') ``` #### Role-Based Access Control (RBAC) ```typescript // Configure roles and permissions const blink = createClient({ projectId: 'your-project', auth: { mode: 'headless', roles: { admin: { permissions: ['*'] }, editor: { permissions: ['posts.create', 'posts.update'] }, viewer: { permissions: ['posts.read'] } } } }) // Check permissions const canEdit = blink.auth.can('posts.update') const isAdmin = blink.auth.hasRole('admin') const isStaff = blink.auth.hasRole(['admin', 'editor']) // Use in components function EditButton() { if (!blink.auth.can('posts.update')) return null return <button onClick={editPost}>Edit Post</button> } ``` #### Core Methods > ⚠️ Tokens are managed automatically for Blink APIs. Use `getValidToken()` only if you must manually pass a token to your own backend or third-party services. ```typescript // User management const user = await blink.auth.me() await blink.auth.updateMe({ displayName: 'New Name' }) // Token management blink.auth.setToken(jwt, persist?) const isAuth = blink.auth.isAuthenticated() const token = await blink.auth.getValidToken() // Get valid token (auto-refreshes) // Password management await blink.auth.sendPasswordResetEmail('user@example.com') await blink.auth.sendPasswordResetEmail('user@example.com', { redirectUrl: 'https://myapp.com/reset-password' // Custom reset page }) await blink.auth.changePassword('oldPass', 'newPass') await blink.auth.confirmPasswordReset(token, newPassword) // Email verification await blink.auth.verifyEmail(token) // Provider discovery const providers = await blink.auth.getAvailableProviders() // Auth state listener (REQUIRED for React apps!) const unsubscribe = blink.auth.onAuthStateChanged((state) => { console.log('Auth state:', state) // state.user - current user or null // state.isLoading - true while auth is initializing // state.isAuthenticated - true if user is logged in // state.tokens - current auth tokens }) ``` #### Login Redirect Behavior When `login()` is called, the SDK automatically determines where to redirect after authentication: ```typescript // Automatic redirect (uses current page URL) blink.auth.login() // β†’ Redirects to: blink.new/auth?redirect_url=https://yourapp.com/current-page // Custom redirect URL blink.auth.login('https://yourapp.com/dashboard') // β†’ Redirects to: blink.new/auth?redirect_url=https://yourapp.com/dashboard // Manual login button example const handleLogin = () => { // The SDK will automatically use the current page URL blink.auth.login() // Or specify a custom redirect // blink.auth.login('https://yourapp.com/welcome') } ``` **βœ… Fixed in v1.x**: The SDK now ensures redirect URLs are always absolute, preventing broken redirects when `window.location.href` returns relative paths. ### Database Operations **πŸŽ‰ NEW: Automatic Case Conversion!** The SDK now automatically converts between JavaScript camelCase and SQL snake_case: - **Table names**: `blink.db.emailDrafts` β†’ `email_drafts` table - **Field names**: `userId`, `createdAt`, `isCompleted` β†’ `user_id`, `created_at`, `is_completed` - **No manual conversion needed!** **⚠️ Important: Always Use camelCase in Your Code** - βœ… **Correct**: `blink.db.emailDrafts.create({ userId: user.id, createdAt: new Date() })` - ❌ **Wrong**: `blink.db.email_drafts.create({ user_id: user.id, created_at: new Date() })` ```typescript // Create (ID auto-generated if not provided) const todo = await blink.db.todos.create({ id: 'todo_12345', // Optional - auto-generated if not provided title: 'Learn Blink SDK', userId: user.id, // camelCase in code createdAt: new Date(), // camelCase in code isCompleted: false // camelCase in code }) // Read with filtering - returns camelCase fields const todos = await blink.db.todos.list({ where: { AND: [ { userId: user.id }, // camelCase in filters { OR: [{ status: 'open' }, { priority: 'high' }] } ] }, orderBy: { createdAt: 'desc' }, // camelCase in orderBy limit: 20 }) // `todos` is a direct array: Todo[] // Note: Boolean fields are returned as "0"/"1" strings from SQLite // Check boolean values using Number(value) > 0 const completedTodos = todos.filter(todo => Number(todo.isCompleted) > 0) const incompleteTodos = todos.filter(todo => Number(todo.isCompleted) === 0) // Update await blink.db.todos.update(todo.id, { isCompleted: true }) // Delete await blink.db.todos.delete(todo.id) // Bulk operations (IDs auto-generated if not provided) await blink.db.todos.createMany([ { title: 'Task 1', userId: user.id }, // ID will be auto-generated { id: 'custom_id', title: 'Task 2', userId: user.id } // Custom ID provided ]) await blink.db.todos.upsertMany([...]) ``` ### AI Operations ```typescript // Text generation (simple prompt) const { text } = await blink.ai.generateText({ prompt: 'Write a poem about coding', maxTokens: 150 }) // Web search (OpenAI only) - get real-time information const { text, sources } = await blink.ai.generateText({ prompt: 'Who is the current US president?', search: true // Returns current info + source URLs }) // Multi-step reasoning - for complex analysis const { text } = await blink.ai.generateText({ prompt: 'Research and analyze tech trends', search: true, maxSteps: 10, // Override default (25 when tools used) experimental_continueSteps: true // Override default (true when tools used) }) // Text generation with image content // ⚠️ IMPORTANT: Images must be HTTPS URLs with file extensions (.jpg, .jpeg, .png, .gif, .webp) // For file uploads, use blink.storage.upload() first to get public HTTPS URLs // 🚫 CRITICAL: When uploading files, NEVER hardcode extensions - use file.name or auto-detection const { text } = await blink.ai.generateText({ messages: [ { role: "user", content: [ { type: "text", text: "What do you see in this image?" }, { type: "image", image: "https://storage.googleapis.com/.../.../photo.jpg" } ] } ] }) // Mixed content with multiple images const { text } = await blink.ai.generateText({ messages: [ { role: "user", content: [ { type: "text", text: "Compare these two images:" }, { type: "image", image: "https://storage.googleapis.com/.../.../image1.jpg" }, { type: "image", image: "https://cdn.example.com/image2.png" } ] } ] }) // Structured object generation const { object } = await blink.ai.generateObject({ prompt: 'Generate a user profile', schema: { type: 'object', properties: { name: { type: 'string' }, age: { type: 'number' } } } }) // ⚠️ IMPORTANT: Schema Rule for generateObject() // The top-level schema MUST use type: "object" - you cannot use type: "array" at the top level // This ensures clear, robust, and extensible API calls with named parameters // βœ… Correct: Array inside object const { object: todoList } = await blink.ai.generateObject({ prompt: 'Generate a list of 5 daily tasks', schema: { type: 'object', properties: { tasks: { type: 'array', items: { type: 'object', properties: { title: { type: 'string' }, priority: { type: 'string', enum: ['low', 'medium', 'high'] } } } } }, required: ['tasks'] } }) // Result: { tasks: [{ title: "Exercise", priority: "high" }, ...] } // ❌ Wrong: Top-level array (will fail) // const { object } = await blink.ai.generateObject({ // prompt: 'Generate tasks', // schema: { // type: 'array', // ❌ This will throw an error // items: { type: 'string' } // } // }) // Error: "schema must be a JSON Schema of 'type: \"object\"', got 'type: \"array\"'" // Generate and modify images with AI - Multi-Model Support (10 models available) // πŸ”₯ Choose between fast generation or high-quality results // 🎨 For style transfer: provide ALL images in the images array, don't reference URLs in prompts // Basic image generation (uses default fast model: fal-ai/nano-banana) const { data } = await blink.ai.generateImage({ prompt: 'A serene landscape with mountains and a lake at sunset' }) console.log('Image URL:', data[0].url) // High-quality generation with Pro model const { data: proImage } = await blink.ai.generateImage({ prompt: 'A detailed infographic about AI with charts and diagrams', model: 'fal-ai/nano-banana-pro', // High quality model n: 1, size: '1792x1024' // Custom size }) // Generate multiple variations const { data } = await blink.ai.generateImage({ prompt: 'A futuristic robot in different poses', model: 'fal-ai/nano-banana', // Fast model n: 3 }) data.forEach((img, i) => console.log(`Image ${i+1}:`, img.url)) **Available Models for Text-to-Image:** | Model | Speed | Quality | Best For | |-------|-------|---------|----------| | `fal-ai/nano-banana` (default) | ⚑ Fast | Good | Prototypes, high-volume generation | | `fal-ai/nano-banana-pro` | Standard | ⭐ Excellent | Marketing materials, high-fidelity visuals | | `fal-ai/gemini-25-flash-image` | ⚑ Fast | Good | Alias for `nano-banana` | | `fal-ai/gemini-3-pro-image-preview` | Standard | ⭐ Excellent | Alias for `nano-banana-pro` | | `gemini-2.5-flash-image-preview` | ⚑ Fast | Good | Legacy - Direct Gemini API | | `gemini-3-pro-image-preview` | Standard | ⭐ Excellent | Legacy - Direct Gemini API | // Image editing - transform existing images with prompts (uses default fast model) const { data: headshots } = await blink.ai.modifyImage({ images: ['https://storage.example.com/user-photo.jpg'], // Up to 16 images supported! prompt: 'Transform into professional business headshot with studio lighting' }) // High-quality editing with Pro model const { data: proEdited } = await blink.ai.modifyImage({ images: ['https://storage.example.com/portrait.jpg'], prompt: 'Add a majestic ancient tree in the background with glowing leaves', model: 'fal-ai/nano-banana-pro/edit' // High quality editing }) // Advanced image editing with multiple input images const { data } = await blink.ai.modifyImage({ images: [ 'https://storage.example.com/photo1.jpg', 'https://storage.example.com/photo2.jpg', 'https://storage.example.com/photo3.jpg' ], prompt: 'Combine these architectural styles into a futuristic building design', model: 'fal-ai/nano-banana/edit', // Fast editing n: 2 }) **Available Models for Image Editing:** | Model | Speed | Quality | Best For | |-------|-------|---------|----------| | `fal-ai/nano-banana/edit` (default) | ⚑ Fast | Good | Quick adjustments, style transfers | | `fal-ai/nano-banana-pro/edit` | Standard | ⭐ Excellent | Detailed retouching, complex edits | | `fal-ai/gemini-25-flash-image/edit` | ⚑ Fast | Good | Alias for `nano-banana/edit` | | `fal-ai/gemini-3-pro-image-preview/edit` | Standard | ⭐ Excellent | Alias for `nano-banana-pro/edit` | // 🎨 Style Transfer & Feature Application // ⚠️ IMPORTANT: When applying styles/features from one image to another, // provide ALL images in the array - don't reference images in the prompt text // ❌ WRONG - Don't reference images in prompt const wrongWay = await blink.ai.modifyImage({ images: [userPhotoUrl], prompt: `Apply this hairstyle from the reference image to the person's head. Reference hairstyle: ${hairstyleUrl}` }) // βœ… CORRECT - Provide all images in the array const { data } = await blink.ai.modifyImage({ images: [userPhotoUrl, hairstyleUrl], // Both images provided prompt: 'Apply the hairstyle from the second image to the person in the first image. Blend naturally with face shape.' }) // More style transfer examples const { data } = await blink.ai.modifyImage({ images: [portraitUrl, artworkUrl], prompt: 'Apply the artistic style and color palette from the second image to the portrait in the first image' }) const { data } = await blink.ai.modifyImage({ images: [roomPhotoUrl, designReferenceUrl], prompt: 'Redesign the room in the first image using the interior design style shown in the second image' }) // Multiple reference images for complex transformations const { data } = await blink.ai.modifyImage({ images: [ originalPhotoUrl, lightingReferenceUrl, colorPaletteReferenceUrl, compositionReferenceUrl ], prompt: 'Transform the first image using the lighting style from image 2, color palette from image 3, and composition from image 4' }) // πŸ“± File Upload + Style Transfer // ⚠️ Extract file extension properly - never hardcode .jpg/.png // ❌ WRONG - Hardcoded extension const userUpload = await blink.storage.upload(file, `photos/${Date.now()}.jpg`) // Breaks HEIC/PNG files // βœ… CORRECT - Extract original extension const userUpload = await blink.storage.upload( userPhoto.file, `photos/${Date.now()}.${userPhoto.file.name.split('.').pop()}` ) const hairstyleUpload = await blink.storage.upload( hairstylePhoto.file, `haircuts/${Date.now()}.${hairstylePhoto.file.name.split('.').pop()}` ) const { data } = await blink.ai.modifyImage({ images: [userUpload.publicUrl, hairstyleUpload.publicUrl], prompt: 'Apply hairstyle from second image to person in first image' }) // Speech synthesis const { url } = await blink.ai.generateSpeech({ text: 'Hello, world!', voice: 'nova' }) // Audio transcription - Multiple input formats supported // πŸ”₯ Most common: Browser audio recording β†’ Base64 β†’ Transcription let mediaRecorder: MediaRecorder; let audioChunks: Blob[] = []; // Step 1: Start recording const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); mediaRecorder = new MediaRecorder(stream); mediaRecorder.ondataavailable = (event) => { audioChunks.push(event.data); }; mediaRecorder.start(); // Step 2: Stop recording and transcribe mediaRecorder.onstop = async () => { const audioBlob = new Blob(audioChunks, { type: 'audio/wav' }); // SAFE method for large files - use FileReader (recommended) const base64 = await new Promise<string>((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { const dataUrl = reader.result as string; const base64Data = dataUrl.split(',')[1]; // Extract base64 part resolve(base64Data); }; reader.onerror = reject; reader.readAsDataURL(audioBlob); }); // Transcribe using base64 (preferred method) const { text } = await blink.ai.transcribeAudio({ audio: base64, // Raw base64 string language: 'en' }); console.log('Transcription:', text); audioChunks = []; // Reset for next recording }; // Alternative: Data URL format (also supported) const reader = new FileReader(); reader.onload = async () => { const dataUrl = reader.result as string; const { text } = await blink.ai.transcribeAudio({ audio: dataUrl, // Data URL format language: 'en' }); }; reader.readAsDataURL(audioBlob); // File upload transcription const fileInput = document.getElementById('audioFile') as HTMLInputElement; const file = fileInput.files[0]; // Option 1: Convert to base64 using FileReader (recommended for large files) const base64Audio = await new Promise<string>((resolve, reject) => { const reader = new FileReader(); reader.onload = () => { const dataUrl = reader.result as string; const base64Data = dataUrl.split(',')[1]; // Extract base64 part resolve(base64Data); }; reader.onerror = reject; reader.readAsDataURL(file); }); const { text } = await blink.ai.transcribeAudio({ audio: base64Audio, language: 'en' }); // Option 2: Use ArrayBuffer directly (works for any file size) const arrayBuffer = await file.arrayBuffer(); const { text } = await blink.ai.transcribeAudio({ audio: arrayBuffer, language: 'en' }); // Option 3: Use Uint8Array directly (works for any file size) const arrayBuffer = await file.arrayBuffer(); const uint8Array = new Uint8Array(arrayBuffer); const { text } = await blink.ai.transcribeAudio({ audio: uint8Array, language: 'en' }); // Public URL transcription (for hosted audio files) const { text } = await blink.ai.transcribeAudio({ audio: 'https://example.com/audio/meeting.mp3', language: 'en' }); // Advanced options const { text } = await blink.ai.transcribeAudio({ audio: base64Audio, language: 'en', model: 'whisper-1', response_format: 'verbose_json' // Get timestamps and confidence scores }); // Supported audio formats: MP3, WAV, M4A, FLAC, OGG, WebM // Supported input types: // - string: Base64 data, Data URL (data:audio/...;base64,...), or public URL (https://...) // - ArrayBuffer: Raw audio buffer (works for any file size) // - Uint8Array: Audio data as byte array (works for any file size) // - number[]: Audio data as number array // ⚠️ IMPORTANT: For large audio files, use FileReader or ArrayBuffer/Uint8Array // Avoid btoa(String.fromCharCode(...array)) as it crashes with large files // Streaming support await blink.ai.streamText( { prompt: 'Write a story...' }, (chunk) => console.log(chunk) ) // Streaming with web search await blink.ai.streamText( { prompt: 'Latest AI news', search: true }, (chunk) => console.log(chunk) ) // React streaming example - parse chunks for immediate UI display const [streamingText, setStreamingText] = useState('') await blink.ai.streamText( { prompt: 'Write a story about AI...' }, (chunk) => { setStreamingText(prev => prev + chunk) // chunk is a string } ) ``` ### Data Operations ```typescript // Simple text extraction (default - returns single string) const text = await blink.data.extractFromUrl('https://example.com/document.pdf'); console.log(typeof text); // 'string' // Extract with chunking enabled const chunks = await blink.data.extractFromUrl('https://example.com/document.pdf', { chunking: true, chunkSize: 2000 }); console.log(Array.isArray(chunks)); // true // Extract from different file types const csvText = await blink.data.extractFromUrl('https://example.com/data.csv'); const htmlText = await blink.data.extractFromUrl('https://example.com/page.html'); const jsonText = await blink.data.extractFromUrl('https://example.com/config.json'); // Extract from uploaded file blob (simple) const fileInput = document.getElementById('fileInput') as HTMLInputElement; const file = fileInput.files[0]; const extractedText = await blink.data.extractFromBlob(file); // Extract from uploaded file blob (with chunking) const chunks = await blink.data.extractFromBlob(file, { chunking: true, chunkSize: 3000 }); // Website scraping (NEW!) - Crystal clear destructuring const { markdown, metadata, links, extract } = await blink.data.scrape('https://example.com'); console.log(markdown); // Clean markdown content console.log(metadata.title); // Page title console.log(links.length); // Number of links found // Even cleaner - destructure only what you need const { metadata, extract } = await blink.data.scrape('https://blog.example.com/article'); console.log(metadata.title); // Always available console.log(extract.headings); // Always an array // Website screenshots (NEW!) const screenshotUrl = await blink.data.screenshot('https://example.com'); console.log(screenshotUrl); // Direct URL to screenshot image // Full-page screenshot with custom dimensions const fullPageUrl = await blink.data.screenshot('https://example.com', { fullPage: true, width: 1920, height: 1080 }); // πŸ”₯ Web Search (NEW!) - Google search results with clean structure // Perfect for getting real-time information and current data // Basic web search - just provide a query const searchResults = await blink.data.search('chatgpt'); console.log(searchResults.organic_results); // Main search results console.log(searchResults.related_searches); // Related search suggestions console.log(searchResults.people_also_ask); // People also ask questions // Search with location for local results const localResults = await blink.data.search('best restaurants', { location: 'San Francisco,CA,United States' }); console.log(localResults.local_results); // Local business results console.log(localResults.organic_results); // Regular web results // News search - get latest news articles const newsResults = await blink.data.search('artificial intelligence', { type: 'news' }); console.log(newsResults.news_results); // News articles with dates and sources // Image search - find images const imageResults = await blink.data.search('elon musk', { type: 'images', limit: 20 }); console.log(imageResults.image_results); // Image results with thumbnails // Search in different languages const spanishResults = await blink.data.search('noticias tecnologΓ­a', { language: 'es', type: 'news' }); // Shopping search - find products const shoppingResults = await blink.data.search('macbook pro', { type: 'shopping' }); console.log(shoppingResults.shopping_results); // Product results with prices // All search types return consistent, structured data: // - organic_results: Main search results (always included) // - related_searches: Related search suggestions // - people_also_ask: FAQ-style questions and answers // - local_results: Local businesses (when location provided) // - news_results: News articles (when type='news') // - image_results: Images (when type='images') // - shopping_results: Products (when type='shopping') // - ads: Sponsored results (when present) // πŸ”₯ Secure API Proxy (NEW!) - Make API calls with secret substitution // Basic API call with secret substitution const response = await blink.data.fetch({ url: 'https://api.sendgrid.com/v3/mail/send', method: 'POST', headers: { 'Authorization': 'Bearer {{sendgrid_api_key}}', // Secret replaced server-side 'Content-Type': 'application/json' }, body: { from: { email: 'me@example.com' }, personalizations: [{ to: [{ email: 'user@example.com' }] }], subject: 'Hello from Blink', content: [{ type: 'text/plain', value: 'Sent securely through Blink!' }] } }); console.log('Email sent:', response.status === 200); console.log('Response:', response.body); console.log('Took:', response.durationMs, 'ms'); // GET request with secret in URL and query params const weatherData = await blink.data.fetch({ url: 'https://api.openweathermap.org/data/2.5/weather', method: 'GET', query: { q: 'London', appid: '{{openweather_api_key}}', // Secret replaced in query params units: 'metric' } }); console.log('Weather:', weatherData.body.main.temp, 'Β°C'); // Async/background requests (fire-and-forget) const asyncResponse = await blink.data.fetchAsync({ url: 'https://api.stripe.com/v1/customers', method: 'POST', headers: { 'Authorization': 'Bearer {{stripe_secret_key}}', 'Content-Type': 'application/x-www-form-urlencoded' }, body: 'email=customer@example.com&name=John Doe' }); console.log(asyncResponse.status); // 'triggered' console.log(asyncResponse.message); // 'Request triggered in background' // Multiple secrets in different places const complexRequest = await blink.data.fetch({ url: 'https://api.github.com/repos/{{github_username}}/{{repo_name}}/issues', method: 'POST', headers: { 'Authorization': 'token {{github_token}}', 'Accept': 'application/vnd.github.v3+json', 'User-Agent': '{{app_name}}' }, body: { title: 'Bug Report', body: 'Found via {{app_name}} monitoring' } }); // Secret substitution works everywhere: // - URL path: /api/{{version}}/users // - Query params: ?key={{api_key}}&user={{user_id}} // - Headers: Authorization: Bearer {{token}} // - Body: { "apiKey": "{{secret}}", "data": "{{value}}" } // Error handling for data extraction try { const result = await blink.data.extractFromUrl('https://example.com/huge-file.pdf'); } catch (error) { if (error instanceof BlinkDataError) { console.error('Data processing error:', error.message); } } ``` ### Storage Operations ```typescript // Upload files (returns public URL directly) const { publicUrl } = await blink.storage.upload( file, `uploads/${Date.now()}.${file.name.split('.').pop()}`, // βœ… Extract original extension { upsert: true, onProgress: (percent) => console.log(`${percent}%`) } ) // ❌ WRONG - Hardcoded extensions break HEIC/PNG/WebP files const wrong = await blink.storage.upload(file, `uploads/${Date.now()}.jpg`) // Corrupts non-JPG files // βœ… CORRECT - Extract file extension const correct = await blink.storage.upload(file, `uploads/${Date.now()}.${file.name.split('.').pop()}`) // Remove files await blink.storage.remove('file1.jpg', 'file2.jpg') ``` ### Notifications Operations ```typescript // πŸ”₯ Email Notifications (NEW!) - Send emails with attachments, custom branding, and delivery tracking // Send a simple email - returns success status and message ID const result = await blink.notifications.email({ to: 'customer@example.com', subject: 'Your order has shipped!', html: '<h1>Order Confirmation</h1><p>Your order #12345 is on its way.</p>' }) console.log(result.success) // true/false - whether email was sent console.log(result.messageId) // "msg_abc123..." - unique message identifier // Send with plain text fallback (recommended for better deliverability) const { success, messageId } = await blink.notifications.email({ to: 'customer@example.com', subject: 'Welcome to our platform!', html: '<h1>Welcome!</h1><p>Thanks for joining us.</p>', text: 'Welcome!\n\nThanks for joining us.' // Plain text version }) // Send an email with attachments and custom branding const result = await blink.notifications.email({ to: ['team@example.com', 'manager@example.com'], from: 'invoices@mycompany.com', // Must be valid email address replyTo: 'support@mycompany.com', subject: 'New Invoice #12345', html: ` <div style="font-family: Arial, sans-serif;"> <h2>Invoice Ready</h2> <p>Please find the invoice attached.</p> </div> `, text: 'Invoice Ready\n\nPlease find the invoice attached.', cc: 'accounting@mycompany.com', bcc: 'archive@mycompany.com', attachments: [ { url: 'https://mycompany.com/invoices/12345.pdf', filename: 'Invoice-12345.pdf', // Custom filename type: 'application/pdf' // MIME type (optional) }, { url: 'https://mycompany.com/terms.pdf', filename: 'Terms-of-Service.pdf' } ] }) console.log(`Email ${result.success ? 'sent' : 'failed'}`) console.log(`Message ID: ${result.messageId}`) // Send to multiple recipients with different recipient types const { success, messageId } = await blink.notifications.email({ to: ['customer1@example.com', 'customer2@example.com'], cc: ['manager@example.com'], bcc: ['audit@example.com', 'backup@example.com'], from: 'notifications@mycompany.com', subject: 'Monthly Newsletter', html: '<h2>This Month\'s Updates</h2><p>Here are the highlights...</p>' }) // Dynamic email content with user data const user = await blink.auth.me() const welcomeEmail = await blink.notifications.email({ to: user.email, from: 'welcome@mycompany.com', subject: `Welcome ${user.displayName}!`, html: ` <h1>Hi ${user.displayName}!</h1> <p>Welcome to our platform. Your account is now active.</p> <p>Account ID: ${user.id}</p> <a href="https://myapp.com/dashboard">Get Started</a> `, text: `Hi ${user.displayName}!\n\nWelcome to our platform. Your account is now active.\nAccount ID: ${user.id}\n\nGet Started: https://myapp.com/dashboard` }) // Comprehensive error handling with detailed error information try { const result = await blink.notifications.email({ to: 'customer@example.com', subject: 'Important Update', html: '<p>This is an important update about your account.</p>' }) if (result.success) { console.log('βœ… Email sent successfully!') console.log('πŸ“§ Message ID:', result.messageId) } else { console.error('❌ Email failed to send') // Handle failed send (retry logic, fallback notification, etc.) } } catch (error) { if (error instanceof BlinkNotificationsError) { console.error('❌ Email error:', error.message) // Common error scenarios: // - "The 'to', 'subject', and either 'html' or 'text' fields are required." // - "Invalid email address format" // - "Attachment URL must be accessible" // - "Failed to send email: Rate limit exceeded" // Handle specific error types if (error.message.includes('Rate limit')) { // Implement retry with backoff console.log('⏳ Rate limited, will retry later') } else if (error.message.includes('Invalid email')) { // Log invalid email for cleanup console.log('πŸ“§ Invalid email address, removing from list') } } else { console.error('❌ Unexpected error:', error) } } // Email validation and best practices const validateAndSendEmail = async (recipient: string, subject: string, content: string) => { // Basic validation if (!recipient.includes('@') || !subject.trim() || !content.trim()) { throw new Error('Invalid email parameters') } try { const result = await blink.notifications.email({ to: recipient, from: 'noreply@mycompany.com', subject: subject, html: ` <div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;"> <div style="background: #f8f9fa; padding: 20px; text-align: center;"> <h1 style="color: #333; margin: 0;">My Company</h1> </div> <div style="padding: 20px;"> ${content} </div> <div style="background: #f8f9fa; padding: 15px; text-align: center; font-size: 12px; color: #666;"> <p>Β© 2024 My Company. All rights reserved.</p> <p><a href="https://mycompany.com/unsubscribe">Unsubscribe</a></p> </div> </div> `, text: content.replace(/<[^>]*>/g, '') // Strip HTML for text version }) return result } catch (error) { console.error(`Failed to send email to ${recipient}:`, error) throw error } } // Usage with validation try { const result = await validateAndSendEmail( 'customer@example.com', 'Account Verification Required', '<p>Please verify your account by clicking the link below.</p><a href="https://myapp.com/verify">Verify Account</a>' ) console.log('Email sent with ID:', result.messageId) } catch (error) { console.error('Email validation or sending failed:', error.message) } // Bulk email sending with error handling const sendBulkEmails = async (recipients: string[], subject: string, htmlContent: string) => { const results = [] for (const recipient of recipients) { try { const result = await blink.notifications.email({ to: recipient, from: 'newsletter@mycompany.com', subject, html: htmlContent, text: htmlContent.replace(/<[^>]*>/g, '') })