@blinkdotnew/sdk 2.0.3 → 2.1.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/dist/index.d.mts CHANGED
@@ -61,6 +61,39 @@ interface BlinkClientConfig {
61
61
  projectId: string;
62
62
  authRequired?: boolean;
63
63
  auth?: BlinkAuthConfig;
64
+ /**
65
+ * Publishable key (client-safe).
66
+ *
67
+ * Used for **public endpoints** when no user JWT is present (e.g. analytics ingest, storage upload,
68
+ * optional public DB reads). Never use for privileged operations.
69
+ */
70
+ publishableKey?: string;
71
+ /**
72
+ * Secret key (server-only, privileged). Permanent, never expires.
73
+ *
74
+ * Used in **server runtimes** (Edge Functions, Workers) for privileged operations that require
75
+ * service-role access (e.g. raw SQL, bypassing row-level security).
76
+ *
77
+ * Format: `blnk_sk_{projectId-last-8}_{random}` (similar to Stripe's `sk_live_...`)
78
+ *
79
+ * **Security**: Never expose this key in client-side code. It is injected by the platform
80
+ * into edge function environments as `BLINK_SECRET_KEY`.
81
+ *
82
+ * When present, this key takes precedence over user JWTs for all requests.
83
+ *
84
+ * @example
85
+ * // Edge function (Deno)
86
+ * const blink = createClient({
87
+ * projectId: Deno.env.get('BLINK_PROJECT_ID')!,
88
+ * secretKey: Deno.env.get('BLINK_SECRET_KEY'),
89
+ * })
90
+ */
91
+ secretKey?: string;
92
+ /**
93
+ * @deprecated Use `secretKey` instead. Service tokens are JWT-based and expire after 365 days.
94
+ * Secret keys are permanent and never expire.
95
+ */
96
+ serviceToken?: string;
64
97
  /**
65
98
  * Storage adapter for cross-platform token persistence
66
99
  *
@@ -281,6 +314,9 @@ declare class BlinkError extends Error {
281
314
  constructor(message: string, code?: string | undefined, status?: number | undefined, details?: any);
282
315
  }
283
316
  interface StorageUploadOptions {
317
+ /**
318
+ * @deprecated Blink storage uploads are add-only by default. This option is ignored.
319
+ */
284
320
  upsert?: boolean;
285
321
  onProgress?: (percent: number) => void;
286
322
  }
@@ -737,7 +773,7 @@ interface BlinkNotifications {
737
773
  */
738
774
 
739
775
  interface RequestOptions {
740
- method?: 'GET' | 'POST' | 'PATCH' | 'DELETE';
776
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
741
777
  headers?: Record<string, string>;
742
778
  body?: any;
743
779
  searchParams?: Record<string, string>;
@@ -752,9 +788,14 @@ declare class HttpClient {
752
788
  private readonly authUrl;
753
789
  private readonly coreUrl;
754
790
  readonly projectId: string;
791
+ private readonly publishableKey?;
792
+ private readonly secretKey?;
755
793
  private getToken;
756
794
  private getValidToken?;
757
795
  constructor(config: BlinkClientConfig, getToken: () => string | null, getValidToken?: () => Promise<string | null>);
796
+ private shouldAttachPublishableKey;
797
+ private shouldSkipSecretKey;
798
+ private getAuthorizationHeader;
758
799
  /**
759
800
  * Make an authenticated request to the Blink API
760
801
  */
@@ -1633,6 +1674,50 @@ declare class BlinkAnalyticsImpl implements BlinkAnalytics {
1633
1674
  private detectChannel;
1634
1675
  }
1635
1676
 
1677
+ /**
1678
+ * Blink Functions - Edge function invocation helper
1679
+ * Provides a simple interface for calling Blink Edge Functions with automatic JWT attachment
1680
+ */
1681
+
1682
+ interface FunctionsInvokeOptions {
1683
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
1684
+ body?: any;
1685
+ headers?: Record<string, string>;
1686
+ searchParams?: Record<string, string>;
1687
+ }
1688
+ interface FunctionsInvokeResponse<T = any> {
1689
+ data: T;
1690
+ status: number;
1691
+ headers: Headers;
1692
+ }
1693
+ interface BlinkFunctions {
1694
+ /**
1695
+ * Invoke a Blink Edge Function.
1696
+ *
1697
+ * Automatically attaches the user's JWT for authenticated requests.
1698
+ * The function URL is constructed as: https://{projectSuffix}--{functionSlug}.functions.blink.new
1699
+ *
1700
+ * @param functionSlug - The slug of the edge function to invoke
1701
+ * @param options - Request options (method, body, headers, etc.)
1702
+ * @returns The function response
1703
+ *
1704
+ * @example
1705
+ * // Simple POST request
1706
+ * const { data } = await blink.functions.invoke('my-function', {
1707
+ * method: 'POST',
1708
+ * body: { message: 'Hello' }
1709
+ * })
1710
+ *
1711
+ * @example
1712
+ * // GET request with query params
1713
+ * const { data } = await blink.functions.invoke('my-function', {
1714
+ * method: 'GET',
1715
+ * searchParams: { limit: '10' }
1716
+ * })
1717
+ */
1718
+ invoke<T = any>(functionSlug: string, options?: FunctionsInvokeOptions): Promise<FunctionsInvokeResponse<T>>;
1719
+ }
1720
+
1636
1721
  /**
1637
1722
  * Blink Client - Main SDK entry point
1638
1723
  * Factory function and client class for the Blink SDK
@@ -1647,6 +1732,7 @@ interface BlinkClient {
1647
1732
  realtime: BlinkRealtime;
1648
1733
  notifications: BlinkNotifications;
1649
1734
  analytics: BlinkAnalytics;
1735
+ functions: BlinkFunctions;
1650
1736
  }
1651
1737
  /**
1652
1738
  * Create a new Blink client instance
package/dist/index.d.ts CHANGED
@@ -61,6 +61,39 @@ interface BlinkClientConfig {
61
61
  projectId: string;
62
62
  authRequired?: boolean;
63
63
  auth?: BlinkAuthConfig;
64
+ /**
65
+ * Publishable key (client-safe).
66
+ *
67
+ * Used for **public endpoints** when no user JWT is present (e.g. analytics ingest, storage upload,
68
+ * optional public DB reads). Never use for privileged operations.
69
+ */
70
+ publishableKey?: string;
71
+ /**
72
+ * Secret key (server-only, privileged). Permanent, never expires.
73
+ *
74
+ * Used in **server runtimes** (Edge Functions, Workers) for privileged operations that require
75
+ * service-role access (e.g. raw SQL, bypassing row-level security).
76
+ *
77
+ * Format: `blnk_sk_{projectId-last-8}_{random}` (similar to Stripe's `sk_live_...`)
78
+ *
79
+ * **Security**: Never expose this key in client-side code. It is injected by the platform
80
+ * into edge function environments as `BLINK_SECRET_KEY`.
81
+ *
82
+ * When present, this key takes precedence over user JWTs for all requests.
83
+ *
84
+ * @example
85
+ * // Edge function (Deno)
86
+ * const blink = createClient({
87
+ * projectId: Deno.env.get('BLINK_PROJECT_ID')!,
88
+ * secretKey: Deno.env.get('BLINK_SECRET_KEY'),
89
+ * })
90
+ */
91
+ secretKey?: string;
92
+ /**
93
+ * @deprecated Use `secretKey` instead. Service tokens are JWT-based and expire after 365 days.
94
+ * Secret keys are permanent and never expire.
95
+ */
96
+ serviceToken?: string;
64
97
  /**
65
98
  * Storage adapter for cross-platform token persistence
66
99
  *
@@ -281,6 +314,9 @@ declare class BlinkError extends Error {
281
314
  constructor(message: string, code?: string | undefined, status?: number | undefined, details?: any);
282
315
  }
283
316
  interface StorageUploadOptions {
317
+ /**
318
+ * @deprecated Blink storage uploads are add-only by default. This option is ignored.
319
+ */
284
320
  upsert?: boolean;
285
321
  onProgress?: (percent: number) => void;
286
322
  }
@@ -737,7 +773,7 @@ interface BlinkNotifications {
737
773
  */
738
774
 
739
775
  interface RequestOptions {
740
- method?: 'GET' | 'POST' | 'PATCH' | 'DELETE';
776
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
741
777
  headers?: Record<string, string>;
742
778
  body?: any;
743
779
  searchParams?: Record<string, string>;
@@ -752,9 +788,14 @@ declare class HttpClient {
752
788
  private readonly authUrl;
753
789
  private readonly coreUrl;
754
790
  readonly projectId: string;
791
+ private readonly publishableKey?;
792
+ private readonly secretKey?;
755
793
  private getToken;
756
794
  private getValidToken?;
757
795
  constructor(config: BlinkClientConfig, getToken: () => string | null, getValidToken?: () => Promise<string | null>);
796
+ private shouldAttachPublishableKey;
797
+ private shouldSkipSecretKey;
798
+ private getAuthorizationHeader;
758
799
  /**
759
800
  * Make an authenticated request to the Blink API
760
801
  */
@@ -1633,6 +1674,50 @@ declare class BlinkAnalyticsImpl implements BlinkAnalytics {
1633
1674
  private detectChannel;
1634
1675
  }
1635
1676
 
1677
+ /**
1678
+ * Blink Functions - Edge function invocation helper
1679
+ * Provides a simple interface for calling Blink Edge Functions with automatic JWT attachment
1680
+ */
1681
+
1682
+ interface FunctionsInvokeOptions {
1683
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
1684
+ body?: any;
1685
+ headers?: Record<string, string>;
1686
+ searchParams?: Record<string, string>;
1687
+ }
1688
+ interface FunctionsInvokeResponse<T = any> {
1689
+ data: T;
1690
+ status: number;
1691
+ headers: Headers;
1692
+ }
1693
+ interface BlinkFunctions {
1694
+ /**
1695
+ * Invoke a Blink Edge Function.
1696
+ *
1697
+ * Automatically attaches the user's JWT for authenticated requests.
1698
+ * The function URL is constructed as: https://{projectSuffix}--{functionSlug}.functions.blink.new
1699
+ *
1700
+ * @param functionSlug - The slug of the edge function to invoke
1701
+ * @param options - Request options (method, body, headers, etc.)
1702
+ * @returns The function response
1703
+ *
1704
+ * @example
1705
+ * // Simple POST request
1706
+ * const { data } = await blink.functions.invoke('my-function', {
1707
+ * method: 'POST',
1708
+ * body: { message: 'Hello' }
1709
+ * })
1710
+ *
1711
+ * @example
1712
+ * // GET request with query params
1713
+ * const { data } = await blink.functions.invoke('my-function', {
1714
+ * method: 'GET',
1715
+ * searchParams: { limit: '10' }
1716
+ * })
1717
+ */
1718
+ invoke<T = any>(functionSlug: string, options?: FunctionsInvokeOptions): Promise<FunctionsInvokeResponse<T>>;
1719
+ }
1720
+
1636
1721
  /**
1637
1722
  * Blink Client - Main SDK entry point
1638
1723
  * Factory function and client class for the Blink SDK
@@ -1647,6 +1732,7 @@ interface BlinkClient {
1647
1732
  realtime: BlinkRealtime;
1648
1733
  notifications: BlinkNotifications;
1649
1734
  analytics: BlinkAnalytics;
1735
+ functions: BlinkFunctions;
1650
1736
  }
1651
1737
  /**
1652
1738
  * Create a new Blink client instance
package/dist/index.js CHANGED
@@ -391,32 +391,65 @@ var HttpClient = class {
391
391
  authUrl = "https://blink.new";
392
392
  coreUrl = "https://core.blink.new";
393
393
  projectId;
394
+ publishableKey;
395
+ secretKey;
396
+ // Permanent, non-expiring key (like Stripe's sk_live_...)
394
397
  getToken;
395
398
  getValidToken;
396
399
  constructor(config, getToken, getValidToken) {
397
400
  this.projectId = config.projectId;
401
+ this.publishableKey = config.publishableKey;
402
+ this.secretKey = config.secretKey || config.serviceToken;
398
403
  this.getToken = getToken;
399
404
  this.getValidToken = getValidToken;
400
405
  }
406
+ shouldAttachPublishableKey(path, method) {
407
+ if (method !== "GET" && method !== "POST") return false;
408
+ if (path.includes("/api/analytics/")) return true;
409
+ if (path.includes("/api/storage/")) return true;
410
+ if (path.includes("/api/db/") && path.includes("/rest/v1/")) return method === "GET";
411
+ return false;
412
+ }
413
+ shouldSkipSecretKey(url) {
414
+ try {
415
+ const parsed = new URL(url);
416
+ return parsed.hostname.endsWith(".functions.blink.new");
417
+ } catch {
418
+ return false;
419
+ }
420
+ }
421
+ getAuthorizationHeader(url, token) {
422
+ if (this.secretKey && !this.shouldSkipSecretKey(url)) {
423
+ return `Bearer ${this.secretKey}`;
424
+ }
425
+ if (token) {
426
+ return `Bearer ${token}`;
427
+ }
428
+ return null;
429
+ }
401
430
  /**
402
431
  * Make an authenticated request to the Blink API
403
432
  */
404
433
  async request(path, options = {}) {
405
434
  const url = this.buildUrl(path, options.searchParams);
406
435
  const token = this.getValidToken ? await this.getValidToken() : this.getToken();
436
+ const method = options.method || "GET";
407
437
  const headers = {
408
438
  "Content-Type": "application/json",
409
439
  ...options.headers
410
440
  };
411
- if (token) {
412
- headers.Authorization = `Bearer ${token}`;
441
+ const auth = this.getAuthorizationHeader(url, token);
442
+ if (auth) {
443
+ headers.Authorization = auth;
444
+ } else if (this.publishableKey && !headers["x-blink-publishable-key"] && this.shouldAttachPublishableKey(path, method)) {
445
+ headers["x-blink-publishable-key"] = this.publishableKey;
413
446
  }
414
447
  const requestInit = {
415
- method: options.method || "GET",
448
+ method,
416
449
  headers,
417
450
  signal: options.signal
418
451
  };
419
- if (options.body && options.method !== "GET") {
452
+ if (options.body && method !== "GET") {
420
453
  requestInit.body = typeof options.body === "string" ? options.body : JSON.stringify(options.body);
421
454
  }
422
455
  try {
@@ -570,12 +603,12 @@ var HttpClient = class {
570
603
  throw new BlinkValidationError("Unsupported file type");
571
604
  }
572
605
  formData.append("path", filePath);
573
- if (options.upsert !== void 0) {
574
- formData.append("options", JSON.stringify({ upsert: options.upsert }));
575
- }
576
606
  const headers = {};
577
- if (token) {
578
- headers.Authorization = `Bearer ${token}`;
607
+ const auth = this.getAuthorizationHeader(url, token);
608
+ if (auth) {
609
+ headers.Authorization = auth;
610
+ } else if (this.publishableKey && path.includes("/api/storage/") && !headers["x-blink-publishable-key"]) {
611
+ headers["x-blink-publishable-key"] = this.publishableKey;
579
612
  }
580
613
  try {
581
614
  if (typeof XMLHttpRequest !== "undefined" && options.onProgress) {
@@ -684,9 +717,8 @@ var HttpClient = class {
684
717
  const headers = {
685
718
  "Content-Type": "application/json"
686
719
  };
687
- if (token) {
688
- headers.Authorization = `Bearer ${token}`;
689
- }
720
+ const auth = this.getAuthorizationHeader(url, token);
721
+ if (auth) headers.Authorization = auth;
690
722
  const body = {
691
723
  prompt,
692
724
  stream: true,
@@ -739,9 +771,8 @@ var HttpClient = class {
739
771
  const headers = {
740
772
  "Content-Type": "application/json"
741
773
  };
742
- if (token) {
743
- headers.Authorization = `Bearer ${token}`;
744
- }
774
+ const auth = this.getAuthorizationHeader(url, token);
775
+ if (auth) headers.Authorization = auth;
745
776
  const body = {
746
777
  prompt,
747
778
  stream: true,
@@ -2966,6 +2997,11 @@ var BlinkAuth = class {
2966
2997
  };
2967
2998
 
2968
2999
  // src/database.ts
3000
+ function assertServerOnly(methodName) {
3001
+ if (typeof window !== "undefined") {
3002
+ throw new Error(`${methodName} is server-only. Use Blink CRUD methods (blink.db.<table>.*) instead.`);
3003
+ }
3004
+ }
2969
3005
  function camelToSnake3(str) {
2970
3006
  return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
2971
3007
  }
@@ -3184,6 +3220,7 @@ var BlinkTable = class {
3184
3220
  * Raw SQL query on this table (for advanced use cases)
3185
3221
  */
3186
3222
  async sql(query, params) {
3223
+ assertServerOnly("blink.db.<table>.sql");
3187
3224
  const response = await this.httpClient.dbSql(query, params);
3188
3225
  return response.data;
3189
3226
  }
@@ -3232,6 +3269,7 @@ var BlinkDatabase = class {
3232
3269
  * Execute raw SQL query
3233
3270
  */
3234
3271
  async sql(query, params) {
3272
+ assertServerOnly("blink.db.sql");
3235
3273
  const response = await this.httpClient.dbSql(query, params);
3236
3274
  return response.data;
3237
3275
  }
@@ -3239,6 +3277,7 @@ var BlinkDatabase = class {
3239
3277
  * Execute batch SQL operations
3240
3278
  */
3241
3279
  async batch(statements, mode = "write") {
3280
+ assertServerOnly("blink.db.batch");
3242
3281
  const response = await this.httpClient.dbBatch(statements, mode);
3243
3282
  return response.data;
3244
3283
  }
@@ -3301,7 +3340,6 @@ var BlinkStorageImpl = class {
3301
3340
  correctedPath,
3302
3341
  // Use corrected path with proper extension
3303
3342
  {
3304
- upsert: options.upsert,
3305
3343
  onProgress: options.onProgress,
3306
3344
  contentType: detectedContentType
3307
3345
  // Pass detected content type
@@ -3321,7 +3359,7 @@ var BlinkStorageImpl = class {
3321
3359
  if (error instanceof Error && "status" in error) {
3322
3360
  const status = error.status;
3323
3361
  if (status === 409) {
3324
- throw new BlinkStorageError("File already exists. Set upsert: true to overwrite.", 409);
3362
+ throw new BlinkStorageError("File already exists.", 409);
3325
3363
  }
3326
3364
  if (status === 400) {
3327
3365
  throw new BlinkStorageError("Invalid request parameters", 400);
@@ -3366,7 +3404,6 @@ var BlinkStorageImpl = class {
3366
3404
  detectedContentType
3367
3405
  };
3368
3406
  } catch (error) {
3369
- console.warn("File type detection failed, using original path:", error);
3370
3407
  return {
3371
3408
  correctedPath: originalPath,
3372
3409
  detectedContentType: "application/octet-stream"
@@ -5292,7 +5329,6 @@ var BlinkAnalyticsImpl = class {
5292
5329
  } catch (error) {
5293
5330
  this.queue = [...events, ...this.queue];
5294
5331
  this.persistQueue();
5295
- console.error("Failed to send analytics events:", error);
5296
5332
  }
5297
5333
  if (this.queue.length > 0) {
5298
5334
  this.timer = setTimeout(() => this.flush(), BATCH_TIMEOUT);
@@ -5483,6 +5519,45 @@ var BlinkAnalyticsImpl = class {
5483
5519
  }
5484
5520
  };
5485
5521
 
5522
+ // src/functions.ts
5523
+ var BlinkFunctionsImpl = class {
5524
+ httpClient;
5525
+ projectId;
5526
+ constructor(httpClient, projectId, _getToken) {
5527
+ this.httpClient = httpClient;
5528
+ this.projectId = projectId;
5529
+ }
5530
+ /**
5531
+ * Get the project suffix from the full project ID.
5532
+ * Project IDs are formatted as: prj_xxxxx
5533
+ * The suffix is the last 8 characters used in function URLs.
5534
+ */
5535
+ getProjectSuffix() {
5536
+ return this.projectId.slice(-8);
5537
+ }
5538
+ /**
5539
+ * Build the full function URL
5540
+ */
5541
+ buildFunctionUrl(functionSlug, searchParams) {
5542
+ const suffix = this.getProjectSuffix();
5543
+ const baseUrl = `https://${suffix}--${functionSlug}.functions.blink.new`;
5544
+ if (!searchParams || Object.keys(searchParams).length === 0) {
5545
+ return baseUrl;
5546
+ }
5547
+ const url = new URL(baseUrl);
5548
+ Object.entries(searchParams).forEach(([key, value]) => {
5549
+ url.searchParams.set(key, value);
5550
+ });
5551
+ return url.toString();
5552
+ }
5553
+ async invoke(functionSlug, options = {}) {
5554
+ const { method = "POST", body, headers = {}, searchParams } = options;
5555
+ const url = this.buildFunctionUrl(functionSlug, searchParams);
5556
+ const res = await this.httpClient.request(url, { method, body, headers });
5557
+ return { data: res.data, status: res.status, headers: res.headers };
5558
+ }
5559
+ };
5560
+
5486
5561
  // src/client.ts
5487
5562
  var BlinkClientImpl = class {
5488
5563
  auth;
@@ -5493,8 +5568,12 @@ var BlinkClientImpl = class {
5493
5568
  realtime;
5494
5569
  notifications;
5495
5570
  analytics;
5571
+ functions;
5496
5572
  httpClient;
5497
5573
  constructor(config) {
5574
+ if ((config.secretKey || config.serviceToken) && isBrowser) {
5575
+ throw new Error("secretKey/serviceToken is server-only. Do not provide it in browser/React Native clients.");
5576
+ }
5498
5577
  this.auth = new BlinkAuth(config);
5499
5578
  this.httpClient = new HttpClient(
5500
5579
  config,
@@ -5508,6 +5587,11 @@ var BlinkClientImpl = class {
5508
5587
  this.realtime = new BlinkRealtimeImpl(this.httpClient, config.projectId);
5509
5588
  this.notifications = new BlinkNotificationsImpl(this.httpClient);
5510
5589
  this.analytics = new BlinkAnalyticsImpl(this.httpClient, config.projectId);
5590
+ this.functions = new BlinkFunctionsImpl(
5591
+ this.httpClient,
5592
+ config.projectId,
5593
+ () => this.auth.getValidToken()
5594
+ );
5511
5595
  this.auth.onAuthStateChanged((state) => {
5512
5596
  if (state.isAuthenticated && state.user) {
5513
5597
  this.analytics.setUserId(state.user.id);
package/dist/index.mjs CHANGED
@@ -389,32 +389,65 @@ var HttpClient = class {
389
389
  authUrl = "https://blink.new";
390
390
  coreUrl = "https://core.blink.new";
391
391
  projectId;
392
+ publishableKey;
393
+ secretKey;
394
+ // Permanent, non-expiring key (like Stripe's sk_live_...)
392
395
  getToken;
393
396
  getValidToken;
394
397
  constructor(config, getToken, getValidToken) {
395
398
  this.projectId = config.projectId;
399
+ this.publishableKey = config.publishableKey;
400
+ this.secretKey = config.secretKey || config.serviceToken;
396
401
  this.getToken = getToken;
397
402
  this.getValidToken = getValidToken;
398
403
  }
404
+ shouldAttachPublishableKey(path, method) {
405
+ if (method !== "GET" && method !== "POST") return false;
406
+ if (path.includes("/api/analytics/")) return true;
407
+ if (path.includes("/api/storage/")) return true;
408
+ if (path.includes("/api/db/") && path.includes("/rest/v1/")) return method === "GET";
409
+ return false;
410
+ }
411
+ shouldSkipSecretKey(url) {
412
+ try {
413
+ const parsed = new URL(url);
414
+ return parsed.hostname.endsWith(".functions.blink.new");
415
+ } catch {
416
+ return false;
417
+ }
418
+ }
419
+ getAuthorizationHeader(url, token) {
420
+ if (this.secretKey && !this.shouldSkipSecretKey(url)) {
421
+ return `Bearer ${this.secretKey}`;
422
+ }
423
+ if (token) {
424
+ return `Bearer ${token}`;
425
+ }
426
+ return null;
427
+ }
399
428
  /**
400
429
  * Make an authenticated request to the Blink API
401
430
  */
402
431
  async request(path, options = {}) {
403
432
  const url = this.buildUrl(path, options.searchParams);
404
433
  const token = this.getValidToken ? await this.getValidToken() : this.getToken();
434
+ const method = options.method || "GET";
405
435
  const headers = {
406
436
  "Content-Type": "application/json",
407
437
  ...options.headers
408
438
  };
409
- if (token) {
410
- headers.Authorization = `Bearer ${token}`;
439
+ const auth = this.getAuthorizationHeader(url, token);
440
+ if (auth) {
441
+ headers.Authorization = auth;
442
+ } else if (this.publishableKey && !headers["x-blink-publishable-key"] && this.shouldAttachPublishableKey(path, method)) {
443
+ headers["x-blink-publishable-key"] = this.publishableKey;
411
444
  }
412
445
  const requestInit = {
413
- method: options.method || "GET",
446
+ method,
414
447
  headers,
415
448
  signal: options.signal
416
449
  };
417
- if (options.body && options.method !== "GET") {
450
+ if (options.body && method !== "GET") {
418
451
  requestInit.body = typeof options.body === "string" ? options.body : JSON.stringify(options.body);
419
452
  }
420
453
  try {
@@ -568,12 +601,12 @@ var HttpClient = class {
568
601
  throw new BlinkValidationError("Unsupported file type");
569
602
  }
570
603
  formData.append("path", filePath);
571
- if (options.upsert !== void 0) {
572
- formData.append("options", JSON.stringify({ upsert: options.upsert }));
573
- }
574
604
  const headers = {};
575
- if (token) {
576
- headers.Authorization = `Bearer ${token}`;
605
+ const auth = this.getAuthorizationHeader(url, token);
606
+ if (auth) {
607
+ headers.Authorization = auth;
608
+ } else if (this.publishableKey && path.includes("/api/storage/") && !headers["x-blink-publishable-key"]) {
609
+ headers["x-blink-publishable-key"] = this.publishableKey;
577
610
  }
578
611
  try {
579
612
  if (typeof XMLHttpRequest !== "undefined" && options.onProgress) {
@@ -682,9 +715,8 @@ var HttpClient = class {
682
715
  const headers = {
683
716
  "Content-Type": "application/json"
684
717
  };
685
- if (token) {
686
- headers.Authorization = `Bearer ${token}`;
687
- }
718
+ const auth = this.getAuthorizationHeader(url, token);
719
+ if (auth) headers.Authorization = auth;
688
720
  const body = {
689
721
  prompt,
690
722
  stream: true,
@@ -737,9 +769,8 @@ var HttpClient = class {
737
769
  const headers = {
738
770
  "Content-Type": "application/json"
739
771
  };
740
- if (token) {
741
- headers.Authorization = `Bearer ${token}`;
742
- }
772
+ const auth = this.getAuthorizationHeader(url, token);
773
+ if (auth) headers.Authorization = auth;
743
774
  const body = {
744
775
  prompt,
745
776
  stream: true,
@@ -2964,6 +2995,11 @@ var BlinkAuth = class {
2964
2995
  };
2965
2996
 
2966
2997
  // src/database.ts
2998
+ function assertServerOnly(methodName) {
2999
+ if (typeof window !== "undefined") {
3000
+ throw new Error(`${methodName} is server-only. Use Blink CRUD methods (blink.db.<table>.*) instead.`);
3001
+ }
3002
+ }
2967
3003
  function camelToSnake3(str) {
2968
3004
  return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
2969
3005
  }
@@ -3182,6 +3218,7 @@ var BlinkTable = class {
3182
3218
  * Raw SQL query on this table (for advanced use cases)
3183
3219
  */
3184
3220
  async sql(query, params) {
3221
+ assertServerOnly("blink.db.<table>.sql");
3185
3222
  const response = await this.httpClient.dbSql(query, params);
3186
3223
  return response.data;
3187
3224
  }
@@ -3230,6 +3267,7 @@ var BlinkDatabase = class {
3230
3267
  * Execute raw SQL query
3231
3268
  */
3232
3269
  async sql(query, params) {
3270
+ assertServerOnly("blink.db.sql");
3233
3271
  const response = await this.httpClient.dbSql(query, params);
3234
3272
  return response.data;
3235
3273
  }
@@ -3237,6 +3275,7 @@ var BlinkDatabase = class {
3237
3275
  * Execute batch SQL operations
3238
3276
  */
3239
3277
  async batch(statements, mode = "write") {
3278
+ assertServerOnly("blink.db.batch");
3240
3279
  const response = await this.httpClient.dbBatch(statements, mode);
3241
3280
  return response.data;
3242
3281
  }
@@ -3299,7 +3338,6 @@ var BlinkStorageImpl = class {
3299
3338
  correctedPath,
3300
3339
  // Use corrected path with proper extension
3301
3340
  {
3302
- upsert: options.upsert,
3303
3341
  onProgress: options.onProgress,
3304
3342
  contentType: detectedContentType
3305
3343
  // Pass detected content type
@@ -3319,7 +3357,7 @@ var BlinkStorageImpl = class {
3319
3357
  if (error instanceof Error && "status" in error) {
3320
3358
  const status = error.status;
3321
3359
  if (status === 409) {
3322
- throw new BlinkStorageError("File already exists. Set upsert: true to overwrite.", 409);
3360
+ throw new BlinkStorageError("File already exists.", 409);
3323
3361
  }
3324
3362
  if (status === 400) {
3325
3363
  throw new BlinkStorageError("Invalid request parameters", 400);
@@ -3364,7 +3402,6 @@ var BlinkStorageImpl = class {
3364
3402
  detectedContentType
3365
3403
  };
3366
3404
  } catch (error) {
3367
- console.warn("File type detection failed, using original path:", error);
3368
3405
  return {
3369
3406
  correctedPath: originalPath,
3370
3407
  detectedContentType: "application/octet-stream"
@@ -5290,7 +5327,6 @@ var BlinkAnalyticsImpl = class {
5290
5327
  } catch (error) {
5291
5328
  this.queue = [...events, ...this.queue];
5292
5329
  this.persistQueue();
5293
- console.error("Failed to send analytics events:", error);
5294
5330
  }
5295
5331
  if (this.queue.length > 0) {
5296
5332
  this.timer = setTimeout(() => this.flush(), BATCH_TIMEOUT);
@@ -5481,6 +5517,45 @@ var BlinkAnalyticsImpl = class {
5481
5517
  }
5482
5518
  };
5483
5519
 
5520
+ // src/functions.ts
5521
+ var BlinkFunctionsImpl = class {
5522
+ httpClient;
5523
+ projectId;
5524
+ constructor(httpClient, projectId, _getToken) {
5525
+ this.httpClient = httpClient;
5526
+ this.projectId = projectId;
5527
+ }
5528
+ /**
5529
+ * Get the project suffix from the full project ID.
5530
+ * Project IDs are formatted as: prj_xxxxx
5531
+ * The suffix is the last 8 characters used in function URLs.
5532
+ */
5533
+ getProjectSuffix() {
5534
+ return this.projectId.slice(-8);
5535
+ }
5536
+ /**
5537
+ * Build the full function URL
5538
+ */
5539
+ buildFunctionUrl(functionSlug, searchParams) {
5540
+ const suffix = this.getProjectSuffix();
5541
+ const baseUrl = `https://${suffix}--${functionSlug}.functions.blink.new`;
5542
+ if (!searchParams || Object.keys(searchParams).length === 0) {
5543
+ return baseUrl;
5544
+ }
5545
+ const url = new URL(baseUrl);
5546
+ Object.entries(searchParams).forEach(([key, value]) => {
5547
+ url.searchParams.set(key, value);
5548
+ });
5549
+ return url.toString();
5550
+ }
5551
+ async invoke(functionSlug, options = {}) {
5552
+ const { method = "POST", body, headers = {}, searchParams } = options;
5553
+ const url = this.buildFunctionUrl(functionSlug, searchParams);
5554
+ const res = await this.httpClient.request(url, { method, body, headers });
5555
+ return { data: res.data, status: res.status, headers: res.headers };
5556
+ }
5557
+ };
5558
+
5484
5559
  // src/client.ts
5485
5560
  var BlinkClientImpl = class {
5486
5561
  auth;
@@ -5491,8 +5566,12 @@ var BlinkClientImpl = class {
5491
5566
  realtime;
5492
5567
  notifications;
5493
5568
  analytics;
5569
+ functions;
5494
5570
  httpClient;
5495
5571
  constructor(config) {
5572
+ if ((config.secretKey || config.serviceToken) && isBrowser) {
5573
+ throw new Error("secretKey/serviceToken is server-only. Do not provide it in browser/React Native clients.");
5574
+ }
5496
5575
  this.auth = new BlinkAuth(config);
5497
5576
  this.httpClient = new HttpClient(
5498
5577
  config,
@@ -5506,6 +5585,11 @@ var BlinkClientImpl = class {
5506
5585
  this.realtime = new BlinkRealtimeImpl(this.httpClient, config.projectId);
5507
5586
  this.notifications = new BlinkNotificationsImpl(this.httpClient);
5508
5587
  this.analytics = new BlinkAnalyticsImpl(this.httpClient, config.projectId);
5588
+ this.functions = new BlinkFunctionsImpl(
5589
+ this.httpClient,
5590
+ config.projectId,
5591
+ () => this.auth.getValidToken()
5592
+ );
5509
5593
  this.auth.onAuthStateChanged((state) => {
5510
5594
  if (state.isAuthenticated && state.user) {
5511
5595
  this.analytics.setUserId(state.user.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blinkdotnew/sdk",
3
- "version": "2.0.3",
3
+ "version": "2.1.0",
4
4
  "description": "Blink TypeScript SDK for client-side applications - Zero-boilerplate CRUD + auth + AI + analytics + notifications for modern SaaS/AI apps",
5
5
  "keywords": [
6
6
  "blink",
@@ -46,7 +46,8 @@
46
46
  "build": "tsup",
47
47
  "dev": "tsup --watch",
48
48
  "type-check": "tsc --noEmit",
49
- "clean": "rm -rf dist"
49
+ "clean": "rm -rf dist",
50
+ "prepublishOnly": "npm run build"
50
51
  },
51
52
  "dependencies": {},
52
53
  "devDependencies": {