@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 +87 -1
- package/dist/index.d.ts +87 -1
- package/dist/index.js +103 -19
- package/dist/index.mjs +103 -19
- package/package.json +3 -2
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
|
-
|
|
412
|
-
|
|
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
|
|
448
|
+
method,
|
|
416
449
|
headers,
|
|
417
450
|
signal: options.signal
|
|
418
451
|
};
|
|
419
|
-
if (options.body &&
|
|
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
|
-
|
|
578
|
-
|
|
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
|
-
|
|
688
|
-
|
|
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
|
-
|
|
743
|
-
|
|
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.
|
|
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
|
-
|
|
410
|
-
|
|
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
|
|
446
|
+
method,
|
|
414
447
|
headers,
|
|
415
448
|
signal: options.signal
|
|
416
449
|
};
|
|
417
|
-
if (options.body &&
|
|
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
|
-
|
|
576
|
-
|
|
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
|
-
|
|
686
|
-
|
|
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
|
-
|
|
741
|
-
|
|
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.
|
|
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
|
+
"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": {
|