@blinkdotnew/sdk 0.4.1 β†’ 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -52,6 +52,18 @@ const text = await blink.data.extractFromUrl("https://example.com/document.pdf")
52
52
  const { markdown, metadata, links } = await blink.data.scrape("https://competitor.com")
53
53
  const screenshotUrl = await blink.data.screenshot("https://competitor.com")
54
54
 
55
+ // Web search (get real-time information)
56
+ const searchResults = await blink.data.search("chatgpt latest news", { type: 'news' })
57
+ const localResults = await blink.data.search("best restaurants", { location: "San Francisco,CA,United States" })
58
+
59
+ // Secure API proxy (call external APIs with secret substitution)
60
+ const response = await blink.data.fetch({
61
+ url: "https://api.sendgrid.com/v3/mail/send",
62
+ method: "POST",
63
+ headers: { "Authorization": "Bearer {{sendgrid_api_key}}" },
64
+ body: { /* email data */ }
65
+ })
66
+
55
67
  // Storage operations (instant - returns public URL directly)
56
68
  const { publicUrl } = await blink.storage.upload(
57
69
  file,
@@ -76,7 +88,7 @@ This SDK powers every Blink-generated app with:
76
88
  - **πŸ” Authentication**: JWT-based auth with automatic token management
77
89
  - **πŸ—„οΈ Database**: PostgREST-compatible CRUD operations with advanced filtering
78
90
  - **πŸ€– AI**: Text generation with web search, object generation, image creation, speech synthesis, and transcription
79
- - **πŸ“„ Data**: Extract text content from documents, spreadsheets, and more
91
+ - **πŸ“„ Data**: Extract text content from documents, secure API proxy with secret substitution, web scraping, screenshots, and web search
80
92
  - **πŸ“ Storage**: File upload, download, and management
81
93
  - **🌐 Universal**: Works on client-side and server-side
82
94
  - **πŸ“± Framework Agnostic**: React, Vue, Svelte, vanilla JS, Node.js, Deno
@@ -321,6 +333,127 @@ const fullPageUrl = await blink.data.screenshot('https://example.com', {
321
333
  height: 1080
322
334
  });
323
335
 
336
+ // πŸ”₯ Web Search (NEW!) - Google search results with clean structure
337
+ // Perfect for getting real-time information and current data
338
+
339
+ // Basic web search - just provide a query
340
+ const searchResults = await blink.data.search('chatgpt');
341
+ console.log(searchResults.organic_results); // Main search results
342
+ console.log(searchResults.related_searches); // Related search suggestions
343
+ console.log(searchResults.people_also_ask); // People also ask questions
344
+
345
+ // Search with location for local results
346
+ const localResults = await blink.data.search('best restaurants', {
347
+ location: 'San Francisco,CA,United States'
348
+ });
349
+ console.log(localResults.local_results); // Local business results
350
+ console.log(localResults.organic_results); // Regular web results
351
+
352
+ // News search - get latest news articles
353
+ const newsResults = await blink.data.search('artificial intelligence', {
354
+ type: 'news'
355
+ });
356
+ console.log(newsResults.news_results); // News articles with dates and sources
357
+
358
+ // Image search - find images
359
+ const imageResults = await blink.data.search('elon musk', {
360
+ type: 'images',
361
+ limit: 20
362
+ });
363
+ console.log(imageResults.image_results); // Image results with thumbnails
364
+
365
+ // Search in different languages
366
+ const spanishResults = await blink.data.search('noticias tecnologΓ­a', {
367
+ language: 'es',
368
+ type: 'news'
369
+ });
370
+
371
+ // Shopping search - find products
372
+ const shoppingResults = await blink.data.search('macbook pro', {
373
+ type: 'shopping'
374
+ });
375
+ console.log(shoppingResults.shopping_results); // Product results with prices
376
+
377
+ // All search types return consistent, structured data:
378
+ // - organic_results: Main search results (always included)
379
+ // - related_searches: Related search suggestions
380
+ // - people_also_ask: FAQ-style questions and answers
381
+ // - local_results: Local businesses (when location provided)
382
+ // - news_results: News articles (when type='news')
383
+ // - image_results: Images (when type='images')
384
+ // - shopping_results: Products (when type='shopping')
385
+ // - ads: Sponsored results (when present)
386
+
387
+ // πŸ”₯ Secure API Proxy (NEW!) - Make API calls with secret substitution
388
+
389
+ // Basic API call with secret substitution
390
+ const response = await blink.data.fetch({
391
+ url: 'https://api.sendgrid.com/v3/mail/send',
392
+ method: 'POST',
393
+ headers: {
394
+ 'Authorization': 'Bearer {{sendgrid_api_key}}', // Secret replaced server-side
395
+ 'Content-Type': 'application/json'
396
+ },
397
+ body: {
398
+ from: { email: 'me@example.com' },
399
+ personalizations: [{ to: [{ email: 'user@example.com' }] }],
400
+ subject: 'Hello from Blink',
401
+ content: [{ type: 'text/plain', value: 'Sent securely through Blink!' }]
402
+ }
403
+ });
404
+
405
+ console.log('Email sent:', response.status === 200);
406
+ console.log('Response:', response.body);
407
+ console.log('Took:', response.durationMs, 'ms');
408
+
409
+ // GET request with secret in URL and query params
410
+ const weatherData = await blink.data.fetch({
411
+ url: 'https://api.openweathermap.org/data/2.5/weather',
412
+ method: 'GET',
413
+ query: {
414
+ q: 'London',
415
+ appid: '{{openweather_api_key}}', // Secret replaced in query params
416
+ units: 'metric'
417
+ }
418
+ });
419
+
420
+ console.log('Weather:', weatherData.body.main.temp, 'Β°C');
421
+
422
+ // Async/background requests (fire-and-forget)
423
+ const asyncResponse = await blink.data.fetchAsync({
424
+ url: 'https://api.stripe.com/v1/customers',
425
+ method: 'POST',
426
+ headers: {
427
+ 'Authorization': 'Bearer {{stripe_secret_key}}',
428
+ 'Content-Type': 'application/x-www-form-urlencoded'
429
+ },
430
+ body: 'email=customer@example.com&name=John Doe'
431
+ });
432
+
433
+ console.log(asyncResponse.status); // 'triggered'
434
+ console.log(asyncResponse.message); // 'Request triggered in background'
435
+
436
+ // Multiple secrets in different places
437
+ const complexRequest = await blink.data.fetch({
438
+ url: 'https://api.github.com/repos/{{github_username}}/{{repo_name}}/issues',
439
+ method: 'POST',
440
+ headers: {
441
+ 'Authorization': 'token {{github_token}}',
442
+ 'Accept': 'application/vnd.github.v3+json',
443
+ 'User-Agent': '{{app_name}}'
444
+ },
445
+ body: {
446
+ title: 'Bug Report',
447
+ body: 'Found via {{app_name}} monitoring'
448
+ }
449
+ });
450
+
451
+ // Secret substitution works everywhere:
452
+ // - URL path: /api/{{version}}/users
453
+ // - Query params: ?key={{api_key}}&user={{user_id}}
454
+ // - Headers: Authorization: Bearer {{token}}
455
+ // - Body: { "apiKey": "{{secret}}", "data": "{{value}}" }
456
+
324
457
  // Error handling for data extraction
325
458
  try {
326
459
  const result = await blink.data.extractFromUrl('https://example.com/huge-file.pdf');
@@ -418,6 +551,47 @@ const todos = await blink.db.todos.list<Todo>({
418
551
  // todos is fully typed as ListResponse<Todo>
419
552
  ```
420
553
 
554
+ ### Secret Management for API Proxy
555
+
556
+ The `blink.data.fetch()` method allows you to make secure API calls with automatic secret substitution. Here's how to set it up:
557
+
558
+ **Step 1: Store your secrets in your Blink project**
559
+ Visit your project dashboard at [blink.new](https://blink.new) and add your API keys in the "Secrets" section:
560
+ - `sendgrid_api_key` β†’ `SG.abc123...`
561
+ - `openweather_api_key` β†’ `d4f5g6h7...`
562
+ - `stripe_secret_key` β†’ `sk_live_abc123...`
563
+
564
+ **Step 2: Use secrets in your API calls**
565
+ ```typescript
566
+ // Secrets are automatically substituted server-side - never exposed to frontend
567
+ const result = await blink.data.fetch({
568
+ url: 'https://api.example.com/endpoint',
569
+ headers: {
570
+ 'Authorization': 'Bearer {{your_secret_key}}' // Replaced with actual value
571
+ }
572
+ })
573
+ ```
574
+
575
+ **Step 3: Secret substitution works everywhere**
576
+ ```typescript
577
+ await blink.data.fetch({
578
+ url: 'https://api.{{service_domain}}/v{{api_version}}/users/{{user_id}}',
579
+ query: {
580
+ key: '{{api_key}}',
581
+ format: 'json'
582
+ },
583
+ headers: {
584
+ 'X-API-Key': '{{secondary_key}}'
585
+ },
586
+ body: {
587
+ token: '{{auth_token}}',
588
+ data: 'regular string data'
589
+ }
590
+ })
591
+ ```
592
+
593
+ All `{{secret_name}}` placeholders are replaced with encrypted values from your project's secret store. Secrets never leave the server and are never visible to your frontend code.
594
+
421
595
  ## 🌍 Framework Examples
422
596
 
423
597
  ### React
@@ -442,6 +616,100 @@ function App() {
442
616
 
443
617
  return <div>Welcome, {user.email}!</div>
444
618
  }
619
+
620
+ // React example with search functionality
621
+ function SearchResults() {
622
+ const [query, setQuery] = useState('')
623
+ const [results, setResults] = useState(null)
624
+ const [loading, setLoading] = useState(false)
625
+
626
+ const handleSearch = async () => {
627
+ if (!query.trim()) return
628
+
629
+ setLoading(true)
630
+ try {
631
+ const searchResults = await blink.data.search(query, {
632
+ type: 'news', // Get latest news
633
+ limit: 10
634
+ })
635
+ setResults(searchResults)
636
+ } catch (error) {
637
+ console.error('Search failed:', error)
638
+ } finally {
639
+ setLoading(false)
640
+ }
641
+ }
642
+
643
+ return (
644
+ <div>
645
+ <input
646
+ value={query}
647
+ onChange={(e) => setQuery(e.target.value)}
648
+ placeholder="Search for news..."
649
+ onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
650
+ />
651
+ <button onClick={handleSearch} disabled={loading}>
652
+ {loading ? 'Searching...' : 'Search'}
653
+ </button>
654
+
655
+ {results && (
656
+ <div>
657
+ <h3>News Results:</h3>
658
+ {results.news_results?.map((article, i) => (
659
+ <div key={i}>
660
+ <h4><a href={article.link}>{article.title}</a></h4>
661
+ <p>{article.snippet}</p>
662
+ <small>{article.source} - {article.date}</small>
663
+ </div>
664
+ ))}
665
+
666
+ <h3>Related Searches:</h3>
667
+ {results.related_searches?.map((suggestion, i) => (
668
+ <button key={i} onClick={() => setQuery(suggestion)}>
669
+ {suggestion}
670
+ </button>
671
+ ))}
672
+ </div>
673
+ )}
674
+ </div>
675
+ )
676
+ }
677
+
678
+ // React example with secure API calls
679
+ function EmailSender() {
680
+ const [status, setStatus] = useState('')
681
+
682
+ const sendEmail = async () => {
683
+ setStatus('Sending...')
684
+ try {
685
+ const response = await blink.data.fetch({
686
+ url: 'https://api.sendgrid.com/v3/mail/send',
687
+ method: 'POST',
688
+ headers: {
689
+ 'Authorization': 'Bearer {{sendgrid_api_key}}', // Secret safe on server
690
+ 'Content-Type': 'application/json'
691
+ },
692
+ body: {
693
+ from: { email: 'app@example.com' },
694
+ personalizations: [{ to: [{ email: user.email }] }],
695
+ subject: 'Welcome to our app!',
696
+ content: [{ type: 'text/plain', value: 'Thanks for signing up!' }]
697
+ }
698
+ })
699
+
700
+ setStatus(response.status === 202 ? 'Email sent!' : 'Failed to send')
701
+ } catch (error) {
702
+ setStatus('Error: ' + error.message)
703
+ }
704
+ }
705
+
706
+ return (
707
+ <div>
708
+ <button onClick={sendEmail}>Send Welcome Email</button>
709
+ <p>{status}</p>
710
+ </div>
711
+ )
712
+ }
445
713
  ```
446
714
 
447
715
  ### Next.js API Routes
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { BlinkClientConfig, BlinkUser, AuthState, HttpClient, TableOperations, CreateOptions, UpsertOptions, QueryOptions, ListResponse, UpdateOptions, FilterCondition, ScrapeResult, BlinkStorage, BlinkAI, StorageUploadOptions, StorageUploadResponse, TextGenerationRequest, TextGenerationResponse, ObjectGenerationRequest, ObjectGenerationResponse, ImageGenerationRequest, ImageGenerationResponse, SpeechGenerationRequest, SpeechGenerationResponse, TranscriptionRequest, TranscriptionResponse } from '@blink/core';
1
+ import { BlinkClientConfig, BlinkUser, AuthState, HttpClient, TableOperations, CreateOptions, UpsertOptions, QueryOptions, ListResponse, UpdateOptions, FilterCondition, ScrapeResult, FetchRequest, FetchResponse, AsyncFetchResponse, SearchResponse, BlinkStorage, BlinkAI, StorageUploadOptions, StorageUploadResponse, TextGenerationRequest, TextGenerationResponse, ObjectGenerationRequest, ObjectGenerationResponse, ImageGenerationRequest, ImageGenerationResponse, SpeechGenerationRequest, SpeechGenerationResponse, TranscriptionRequest, TranscriptionResponse } from '@blink/core';
2
2
  export { AuthState, AuthTokens, BlinkAI, BlinkClientConfig, BlinkData, BlinkStorage, BlinkUser, CreateOptions, DataExtraction, FileObject, FilterCondition, ImageGenerationRequest, ImageGenerationResponse, ListResponse, Message, ObjectGenerationRequest, ObjectGenerationResponse, QueryOptions, SpeechGenerationRequest, SpeechGenerationResponse, StorageUploadOptions, StorageUploadResponse, TableOperations, TextGenerationRequest, TextGenerationResponse, TokenUsage, TranscriptionRequest, TranscriptionResponse, UpdateOptions, UpsertOptions } from '@blink/core';
3
3
 
4
4
  /**
@@ -211,6 +211,14 @@ interface BlinkData {
211
211
  width?: number;
212
212
  height?: number;
213
213
  }): Promise<string>;
214
+ fetch(request: FetchRequest): Promise<FetchResponse>;
215
+ fetchAsync(request: Omit<FetchRequest, 'async'>): Promise<AsyncFetchResponse>;
216
+ search(query: string, options?: {
217
+ location?: string;
218
+ type?: 'news' | 'images' | 'videos' | 'shopping';
219
+ language?: string;
220
+ limit?: number;
221
+ }): Promise<SearchResponse>;
214
222
  }
215
223
  declare class BlinkDataImpl implements BlinkData {
216
224
  private httpClient;
@@ -230,6 +238,14 @@ declare class BlinkDataImpl implements BlinkData {
230
238
  width?: number;
231
239
  height?: number;
232
240
  }): Promise<string>;
241
+ fetch(request: FetchRequest): Promise<FetchResponse>;
242
+ fetchAsync(request: Omit<FetchRequest, 'async'>): Promise<AsyncFetchResponse>;
243
+ search(query: string, options?: {
244
+ location?: string;
245
+ type?: 'news' | 'images' | 'videos' | 'shopping';
246
+ language?: string;
247
+ limit?: number;
248
+ }): Promise<SearchResponse>;
233
249
  }
234
250
 
235
251
  /**
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { BlinkClientConfig, BlinkUser, AuthState, HttpClient, TableOperations, CreateOptions, UpsertOptions, QueryOptions, ListResponse, UpdateOptions, FilterCondition, ScrapeResult, BlinkStorage, BlinkAI, StorageUploadOptions, StorageUploadResponse, TextGenerationRequest, TextGenerationResponse, ObjectGenerationRequest, ObjectGenerationResponse, ImageGenerationRequest, ImageGenerationResponse, SpeechGenerationRequest, SpeechGenerationResponse, TranscriptionRequest, TranscriptionResponse } from '@blink/core';
1
+ import { BlinkClientConfig, BlinkUser, AuthState, HttpClient, TableOperations, CreateOptions, UpsertOptions, QueryOptions, ListResponse, UpdateOptions, FilterCondition, ScrapeResult, FetchRequest, FetchResponse, AsyncFetchResponse, SearchResponse, BlinkStorage, BlinkAI, StorageUploadOptions, StorageUploadResponse, TextGenerationRequest, TextGenerationResponse, ObjectGenerationRequest, ObjectGenerationResponse, ImageGenerationRequest, ImageGenerationResponse, SpeechGenerationRequest, SpeechGenerationResponse, TranscriptionRequest, TranscriptionResponse } from '@blink/core';
2
2
  export { AuthState, AuthTokens, BlinkAI, BlinkClientConfig, BlinkData, BlinkStorage, BlinkUser, CreateOptions, DataExtraction, FileObject, FilterCondition, ImageGenerationRequest, ImageGenerationResponse, ListResponse, Message, ObjectGenerationRequest, ObjectGenerationResponse, QueryOptions, SpeechGenerationRequest, SpeechGenerationResponse, StorageUploadOptions, StorageUploadResponse, TableOperations, TextGenerationRequest, TextGenerationResponse, TokenUsage, TranscriptionRequest, TranscriptionResponse, UpdateOptions, UpsertOptions } from '@blink/core';
3
3
 
4
4
  /**
@@ -211,6 +211,14 @@ interface BlinkData {
211
211
  width?: number;
212
212
  height?: number;
213
213
  }): Promise<string>;
214
+ fetch(request: FetchRequest): Promise<FetchResponse>;
215
+ fetchAsync(request: Omit<FetchRequest, 'async'>): Promise<AsyncFetchResponse>;
216
+ search(query: string, options?: {
217
+ location?: string;
218
+ type?: 'news' | 'images' | 'videos' | 'shopping';
219
+ language?: string;
220
+ limit?: number;
221
+ }): Promise<SearchResponse>;
214
222
  }
215
223
  declare class BlinkDataImpl implements BlinkData {
216
224
  private httpClient;
@@ -230,6 +238,14 @@ declare class BlinkDataImpl implements BlinkData {
230
238
  width?: number;
231
239
  height?: number;
232
240
  }): Promise<string>;
241
+ fetch(request: FetchRequest): Promise<FetchResponse>;
242
+ fetchAsync(request: Omit<FetchRequest, 'async'>): Promise<AsyncFetchResponse>;
243
+ search(query: string, options?: {
244
+ location?: string;
245
+ type?: 'news' | 'images' | 'videos' | 'shopping';
246
+ language?: string;
247
+ limit?: number;
248
+ }): Promise<SearchResponse>;
233
249
  }
234
250
 
235
251
  /**
package/dist/index.js CHANGED
@@ -40,6 +40,12 @@ var BlinkAIError = class extends BlinkError {
40
40
  this.name = "BlinkAIError";
41
41
  }
42
42
  };
43
+ var BlinkDataError = class extends BlinkError {
44
+ constructor(message, status, details) {
45
+ super(message, "DATA_ERROR", status, details);
46
+ this.name = "BlinkDataError";
47
+ }
48
+ };
43
49
 
44
50
  // ../core/src/query-builder.ts
45
51
  function buildFilterQuery(condition) {
@@ -564,6 +570,12 @@ var HttpClient = class {
564
570
  body: JSON.stringify(request)
565
571
  });
566
572
  }
573
+ async dataFetch(projectId, request) {
574
+ return this.post(`/api/data/${projectId}/fetch`, request);
575
+ }
576
+ async dataSearch(projectId, request) {
577
+ return this.post(`/api/data/${projectId}/search`, request);
578
+ }
567
579
  /**
568
580
  * Private helper methods
569
581
  */
@@ -2392,6 +2404,32 @@ var BlinkDataImpl = class {
2392
2404
  const response = await this.httpClient.dataScreenshot(this.projectId, request);
2393
2405
  return response.data.url;
2394
2406
  }
2407
+ async fetch(request) {
2408
+ const response = await this.httpClient.dataFetch(this.projectId, request);
2409
+ if ("status" in response.data && "headers" in response.data) {
2410
+ return response.data;
2411
+ }
2412
+ throw new BlinkDataError("Unexpected response format from fetch endpoint");
2413
+ }
2414
+ async fetchAsync(request) {
2415
+ const asyncRequest = { ...request, async: true };
2416
+ const response = await this.httpClient.dataFetch(this.projectId, asyncRequest);
2417
+ if ("status" in response.data && response.data.status === "triggered") {
2418
+ return response.data;
2419
+ }
2420
+ throw new BlinkDataError("Unexpected response format from async fetch endpoint");
2421
+ }
2422
+ async search(query, options) {
2423
+ const request = {
2424
+ q: query,
2425
+ location: options?.location,
2426
+ hl: options?.language || "en",
2427
+ tbm: options?.type === "news" ? "nws" : options?.type === "images" ? "isch" : options?.type === "videos" ? "vid" : options?.type === "shopping" ? "shop" : void 0,
2428
+ num: options?.limit
2429
+ };
2430
+ const response = await this.httpClient.dataSearch(this.projectId, request);
2431
+ return response.data;
2432
+ }
2395
2433
  };
2396
2434
 
2397
2435
  // src/client.ts