@buildspacestudio/sdk 0.2.2 → 0.3.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/auth/index.cjs +259 -0
- package/dist/auth/index.cjs.map +1 -0
- package/dist/auth/{server.d.ts → index.d.cts} +9 -6
- package/dist/auth/index.d.ts +118 -5
- package/dist/auth/index.js +255 -2
- package/dist/auth/index.js.map +1 -1
- package/dist/client/index.cjs +428 -0
- package/dist/client/index.cjs.map +1 -0
- package/dist/client/index.d.cts +85 -0
- package/dist/client/index.d.ts +8 -7
- package/dist/client/index.js +416 -105
- package/dist/client/index.js.map +1 -1
- package/dist/{auth/client.d.ts → client-BH7LbrKM.d.ts} +7 -5
- package/dist/client-C67hy1kt.d.cts +58 -0
- package/dist/{events/client.d.ts → client-Dlif1JBf.d.ts} +5 -3
- package/dist/{auth/client.js → client-DqWXAwCr.d.cts} +27 -30
- package/dist/events/index.cjs +131 -0
- package/dist/events/index.cjs.map +1 -0
- package/dist/events/{server.d.ts → index.d.cts} +7 -4
- package/dist/events/index.d.ts +59 -4
- package/dist/events/index.js +127 -2
- package/dist/events/index.js.map +1 -1
- package/dist/http-U-zzKmFF.d.cts +100 -0
- package/dist/http-U-zzKmFF.d.ts +100 -0
- package/dist/index.cjs +801 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +87 -0
- package/dist/index.d.ts +16 -15
- package/dist/index.js +785 -106
- package/dist/index.js.map +1 -1
- package/dist/notifications/index.cjs +56 -0
- package/dist/notifications/index.cjs.map +1 -0
- package/dist/notifications/{server.d.ts → index.d.cts} +8 -6
- package/dist/notifications/index.d.ts +79 -3
- package/dist/notifications/index.js +53 -1
- package/dist/notifications/index.js.map +1 -1
- package/dist/storage/index.cjs +213 -0
- package/dist/storage/index.cjs.map +1 -0
- package/dist/storage/index.d.cts +195 -0
- package/dist/storage/index.d.ts +195 -5
- package/dist/storage/index.js +209 -2
- package/dist/storage/index.js.map +1 -1
- package/package.json +75 -16
- package/dist/auth/client.d.ts.map +0 -1
- package/dist/auth/client.js.map +0 -1
- package/dist/auth/index.d.ts.map +0 -1
- package/dist/auth/server.d.ts.map +0 -1
- package/dist/auth/server.js +0 -148
- package/dist/auth/server.js.map +0 -1
- package/dist/client/index.d.ts.map +0 -1
- package/dist/config.d.ts +0 -40
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -27
- package/dist/config.js.map +0 -1
- package/dist/errors.d.ts +0 -31
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -30
- package/dist/errors.js.map +0 -1
- package/dist/events/client.d.ts.map +0 -1
- package/dist/events/client.js +0 -97
- package/dist/events/client.js.map +0 -1
- package/dist/events/index.d.ts.map +0 -1
- package/dist/events/server.d.ts.map +0 -1
- package/dist/events/server.js +0 -65
- package/dist/events/server.js.map +0 -1
- package/dist/http.d.ts +0 -39
- package/dist/http.d.ts.map +0 -1
- package/dist/http.js +0 -74
- package/dist/http.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/notifications/index.d.ts.map +0 -1
- package/dist/notifications/server.d.ts.map +0 -1
- package/dist/notifications/server.js +0 -73
- package/dist/notifications/server.js.map +0 -1
- package/dist/storage/client.d.ts +0 -91
- package/dist/storage/client.d.ts.map +0 -1
- package/dist/storage/client.js +0 -117
- package/dist/storage/client.js.map +0 -1
- package/dist/storage/index.d.ts.map +0 -1
- package/dist/storage/server.d.ts +0 -104
- package/dist/storage/server.d.ts.map +0 -1
- package/dist/storage/server.js +0 -104
- package/dist/storage/server.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,109 +1,788 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
* import Buildspace from "@buildspacestudio/sdk";
|
|
22
|
-
*
|
|
23
|
-
* const buildspace = new Buildspace(process.env.BUILDSPACE_SECRET_KEY!);
|
|
24
|
-
*
|
|
25
|
-
* // Verify a session
|
|
26
|
-
* const session = await buildspace.auth.getSession(sessionToken);
|
|
27
|
-
*
|
|
28
|
-
* // Track an event
|
|
29
|
-
* await buildspace.events.track("user.signed_up", { plan: "pro" }, userId);
|
|
30
|
-
*
|
|
31
|
-
* // Upload a file
|
|
32
|
-
* const { upload_url } = await buildspace.storage.getUploadUrl({
|
|
33
|
-
* key: "avatars/user-123.png",
|
|
34
|
-
* contentType: "image/png",
|
|
35
|
-
* size: fileSize,
|
|
36
|
-
* });
|
|
37
|
-
*
|
|
38
|
-
* // Send an email
|
|
39
|
-
* await buildspace.notifications.send({
|
|
40
|
-
* to: "user@example.com",
|
|
41
|
-
* subject: "Welcome!",
|
|
42
|
-
* html: "<h1>You're in!</h1>",
|
|
43
|
-
* });
|
|
44
|
-
* ```
|
|
45
|
-
*/
|
|
46
|
-
class Buildspace {
|
|
47
|
-
transport;
|
|
48
|
-
_auth = null;
|
|
49
|
-
_events = null;
|
|
50
|
-
_notifications = null;
|
|
51
|
-
_publicAuth = null;
|
|
52
|
-
_storage = null;
|
|
53
|
-
/**
|
|
54
|
-
* Create a new server SDK instance.
|
|
55
|
-
*
|
|
56
|
-
* @param secretKey - Your Buildspace secret key (starts with `bs_sec_`).
|
|
57
|
-
* @param config - Optional configuration overrides.
|
|
58
|
-
*/
|
|
59
|
-
constructor(secretKey, config = {}) {
|
|
60
|
-
this.transport = new HttpTransport({
|
|
61
|
-
key: secretKey,
|
|
62
|
-
resolvedConfig: resolveConfig("server", config),
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
/** Server-side auth: session verification, OAuth callback handling, sign-out. */
|
|
66
|
-
get auth() {
|
|
67
|
-
this._auth ??= new AuthServerNamespace(this.transport);
|
|
68
|
-
return this._auth;
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Client-style auth helpers available on the server (e.g. generating sign-in URLs
|
|
72
|
-
* in server-rendered pages). For full server auth, use {@link auth}.
|
|
73
|
-
*/
|
|
74
|
-
get authClient() {
|
|
75
|
-
this._publicAuth ??= new AuthClientNamespace(this.transport);
|
|
76
|
-
return this._publicAuth;
|
|
77
|
-
}
|
|
78
|
-
/** Server-side event tracking. */
|
|
79
|
-
get events() {
|
|
80
|
-
this._events ??= new EventsServerNamespace(this.transport);
|
|
81
|
-
return this._events;
|
|
82
|
-
}
|
|
83
|
-
/** Server-side file storage: upload URLs, signed downloads, listing, deletion. */
|
|
84
|
-
get storage() {
|
|
85
|
-
this._storage ??= new StorageServerNamespace(this.transport);
|
|
86
|
-
return this._storage;
|
|
87
|
-
}
|
|
88
|
-
/** Server-side transactional email notifications. */
|
|
89
|
-
get notifications() {
|
|
90
|
-
this._notifications ??= new NotificationsServerNamespace(this.transport);
|
|
91
|
-
return this._notifications;
|
|
92
|
-
}
|
|
93
|
-
/** Clear the stored session token. */
|
|
94
|
-
clearSession() {
|
|
95
|
-
this.transport.clearSession();
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Set a session token that will be sent as `X-Session-Token` on all
|
|
99
|
-
* subsequent API requests.
|
|
100
|
-
*
|
|
101
|
-
* @param sessionToken - The session token to attach to requests.
|
|
102
|
-
*/
|
|
103
|
-
setSession(sessionToken) {
|
|
104
|
-
this.transport.setSession(sessionToken);
|
|
1
|
+
// src/auth/client.ts
|
|
2
|
+
var AuthClientNamespace = class {
|
|
3
|
+
transport;
|
|
4
|
+
constructor(transport) {
|
|
5
|
+
this.transport = transport;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Build a URL that redirects the user to the Buildspace sign-in page.
|
|
9
|
+
*
|
|
10
|
+
* After the user authenticates, they are redirected back to `redirectUri`
|
|
11
|
+
* with a `?code=` parameter that can be exchanged for tokens via
|
|
12
|
+
* `buildspace.auth.handleCallback()` on the server.
|
|
13
|
+
*
|
|
14
|
+
* @param opts - Sign-in URL options.
|
|
15
|
+
* @returns The full sign-in URL as a string.
|
|
16
|
+
*/
|
|
17
|
+
getSignInUrl(opts) {
|
|
18
|
+
const url = new URL("/sign-in", this.transport.loginUrl);
|
|
19
|
+
if (opts.appSlug) {
|
|
20
|
+
url.searchParams.set("app", opts.appSlug);
|
|
105
21
|
}
|
|
22
|
+
url.searchParams.set("redirect_uri", opts.redirectUri);
|
|
23
|
+
url.searchParams.set("client_id", this.transport.key);
|
|
24
|
+
if (opts.env) {
|
|
25
|
+
url.searchParams.set("env", opts.env);
|
|
26
|
+
}
|
|
27
|
+
return url.toString();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Build a URL that redirects the user to the Buildspace sign-up page.
|
|
31
|
+
*
|
|
32
|
+
* Works identically to {@link getSignInUrl} but directs the user to the
|
|
33
|
+
* registration flow instead.
|
|
34
|
+
*
|
|
35
|
+
* @param opts - Sign-up URL options.
|
|
36
|
+
* @returns The full sign-up URL as a string.
|
|
37
|
+
*/
|
|
38
|
+
getSignUpUrl(opts) {
|
|
39
|
+
const url = new URL("/sign-up", this.transport.loginUrl);
|
|
40
|
+
if (opts.appSlug) {
|
|
41
|
+
url.searchParams.set("app", opts.appSlug);
|
|
42
|
+
}
|
|
43
|
+
url.searchParams.set("redirect_uri", opts.redirectUri);
|
|
44
|
+
url.searchParams.set("client_id", this.transport.key);
|
|
45
|
+
if (opts.env) {
|
|
46
|
+
url.searchParams.set("env", opts.env);
|
|
47
|
+
}
|
|
48
|
+
return url.toString();
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// src/errors.ts
|
|
53
|
+
var BuildspaceError = class extends Error {
|
|
54
|
+
/** Machine-readable error code, e.g. `"auth/invalid-token"`. */
|
|
55
|
+
code;
|
|
56
|
+
/** Which service produced the error. */
|
|
57
|
+
service;
|
|
58
|
+
/** HTTP status code from the API. */
|
|
59
|
+
status;
|
|
60
|
+
constructor({
|
|
61
|
+
code,
|
|
62
|
+
message,
|
|
63
|
+
service,
|
|
64
|
+
status
|
|
65
|
+
}) {
|
|
66
|
+
super(message);
|
|
67
|
+
this.name = "BuildspaceError";
|
|
68
|
+
this.code = code;
|
|
69
|
+
this.service = service;
|
|
70
|
+
this.status = status;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// src/auth/server.ts
|
|
75
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "::1", "0.0.0.0"]);
|
|
76
|
+
var FORWARDED_HOST_RE = /host="?([^;"]+)"?/i;
|
|
77
|
+
var FORWARDED_PROTO_RE = /proto="?([^;"]+)"?/i;
|
|
78
|
+
function getFirstForwardedValue(value) {
|
|
79
|
+
if (!value) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
return value.split(",").map((part) => part.trim()).find(Boolean) ?? null;
|
|
83
|
+
}
|
|
84
|
+
function stripPort(host) {
|
|
85
|
+
if (host.startsWith("[") && host.includes("]")) {
|
|
86
|
+
return host.slice(1, host.indexOf("]"));
|
|
87
|
+
}
|
|
88
|
+
const colonIndex = host.indexOf(":");
|
|
89
|
+
return colonIndex >= 0 ? host.slice(0, colonIndex) : host;
|
|
90
|
+
}
|
|
91
|
+
function isPrivateIpv4(hostname) {
|
|
92
|
+
const parts = hostname.split(".").map((part) => Number(part));
|
|
93
|
+
if (parts.length !== 4 || parts.some((part) => Number.isNaN(part) || part < 0 || part > 255)) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
return parts[0] === 10 || parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31 || parts[0] === 192 && parts[1] === 168;
|
|
97
|
+
}
|
|
98
|
+
function isInternalHostname(hostname) {
|
|
99
|
+
const normalized = hostname.toLowerCase();
|
|
100
|
+
return LOOPBACK_HOSTS.has(normalized) || normalized.endsWith(".local") || normalized.endsWith(".internal") || isPrivateIpv4(normalized);
|
|
101
|
+
}
|
|
102
|
+
function getForwardedOrigin(request) {
|
|
103
|
+
const forwarded = getFirstForwardedValue(request.headers.get("forwarded"));
|
|
104
|
+
if (forwarded) {
|
|
105
|
+
const forwardedHost2 = forwarded.match(FORWARDED_HOST_RE)?.[1]?.trim();
|
|
106
|
+
const forwardedProto2 = forwarded.match(FORWARDED_PROTO_RE)?.[1]?.trim();
|
|
107
|
+
if (forwardedHost2 && forwardedProto2) {
|
|
108
|
+
return `${forwardedProto2}://${forwardedHost2}`;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const forwardedHost = getFirstForwardedValue(request.headers.get("x-forwarded-host"));
|
|
112
|
+
const forwardedProto = getFirstForwardedValue(request.headers.get("x-forwarded-proto"));
|
|
113
|
+
if (forwardedHost && forwardedProto) {
|
|
114
|
+
return `${forwardedProto}://${forwardedHost}`;
|
|
115
|
+
}
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
function resolveDefaultRedirectUri(request, url) {
|
|
119
|
+
if (!(request instanceof Request)) {
|
|
120
|
+
return `${url.origin}${url.pathname}`;
|
|
121
|
+
}
|
|
122
|
+
const forwardedOrigin = getForwardedOrigin(request);
|
|
123
|
+
if (forwardedOrigin) {
|
|
124
|
+
return `${forwardedOrigin}${url.pathname}`;
|
|
125
|
+
}
|
|
126
|
+
const hostHeader = request.headers.get("host")?.trim();
|
|
127
|
+
if (hostHeader && !isInternalHostname(stripPort(hostHeader))) {
|
|
128
|
+
return `${url.protocol}//${hostHeader}${url.pathname}`;
|
|
129
|
+
}
|
|
130
|
+
if (!isInternalHostname(url.hostname)) {
|
|
131
|
+
return `${url.origin}${url.pathname}`;
|
|
132
|
+
}
|
|
133
|
+
return `${url.origin}${url.pathname}`;
|
|
134
|
+
}
|
|
135
|
+
var AuthServerNamespace = class {
|
|
136
|
+
transport;
|
|
137
|
+
constructor(transport) {
|
|
138
|
+
this.transport = transport;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Retrieve the session associated with a token.
|
|
142
|
+
*
|
|
143
|
+
* Returns the {@link AuthSession} if valid, or `null` if the token is
|
|
144
|
+
* expired, revoked, or invalid (401/404).
|
|
145
|
+
*
|
|
146
|
+
* @throws {BuildspaceError} On unexpected server errors (5xx, network issues).
|
|
147
|
+
*/
|
|
148
|
+
async getSession(sessionToken) {
|
|
149
|
+
try {
|
|
150
|
+
return await this.transport.request({
|
|
151
|
+
service: "auth",
|
|
152
|
+
path: "/v1/auth/session",
|
|
153
|
+
headers: { "X-Session-Token": sessionToken }
|
|
154
|
+
});
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (error instanceof BuildspaceError && (error.status === 401 || error.status === 404)) {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
throw error;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Exchange an OAuth authorization code for tokens.
|
|
164
|
+
*
|
|
165
|
+
* Extracts the `code` query parameter from the callback URL and exchanges
|
|
166
|
+
* it with the Buildspace API for an access token and user info.
|
|
167
|
+
*
|
|
168
|
+
* @param request - The incoming callback request. Accepts a `Request` object,
|
|
169
|
+
* a `URL`, or a raw URL string containing the `?code=` parameter.
|
|
170
|
+
* @param opts - Optional settings.
|
|
171
|
+
* @param opts.redirectUri - Override the redirect URI sent to the token endpoint.
|
|
172
|
+
* Defaults to the origin + pathname of the callback URL.
|
|
173
|
+
* @returns The token response containing `access_token`, `expires_in`, and `user`.
|
|
174
|
+
*
|
|
175
|
+
* @throws {BuildspaceError} If the code exchange fails (invalid code, expired, etc.).
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* ```ts
|
|
179
|
+
* // Next.js Route Handler
|
|
180
|
+
* export async function GET(request: Request) {
|
|
181
|
+
* const { access_token, user } = await buildspace.auth.handleCallback(request);
|
|
182
|
+
* // Store access_token as a session cookie
|
|
183
|
+
* }
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
handleCallback(request, opts) {
|
|
187
|
+
let url;
|
|
188
|
+
if (typeof request === "string") {
|
|
189
|
+
url = new URL(request);
|
|
190
|
+
} else if (request instanceof URL) {
|
|
191
|
+
url = request;
|
|
192
|
+
} else {
|
|
193
|
+
url = new URL(request.url);
|
|
194
|
+
}
|
|
195
|
+
const code = url.searchParams.get("code");
|
|
196
|
+
if (!code) {
|
|
197
|
+
throw new BuildspaceError({
|
|
198
|
+
service: "auth",
|
|
199
|
+
status: 400,
|
|
200
|
+
code: "auth/missing-code",
|
|
201
|
+
message: "No auth code found in callback URL. Expected ?code= parameter."
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
const redirectUri = opts?.redirectUri ?? resolveDefaultRedirectUri(request, url);
|
|
205
|
+
return this.transport.request({
|
|
206
|
+
service: "auth",
|
|
207
|
+
path: "/v1/auth/token",
|
|
208
|
+
method: "POST",
|
|
209
|
+
body: {
|
|
210
|
+
code,
|
|
211
|
+
redirect_uri: redirectUri
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Revoke a specific session token.
|
|
217
|
+
*
|
|
218
|
+
* @param sessionToken - The token to revoke.
|
|
219
|
+
* @throws {BuildspaceError} If the revocation fails.
|
|
220
|
+
*/
|
|
221
|
+
async revokeSession(sessionToken) {
|
|
222
|
+
await this.transport.request({
|
|
223
|
+
service: "auth",
|
|
224
|
+
path: "/v1/auth/session",
|
|
225
|
+
method: "DELETE",
|
|
226
|
+
headers: { "X-Session-Token": sessionToken }
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Sign the user out by revoking the session and clearing the SDK's stored token.
|
|
231
|
+
*
|
|
232
|
+
* If the session is already expired or not found, the error is silently
|
|
233
|
+
* ignored and the local session is still cleared.
|
|
234
|
+
*
|
|
235
|
+
* @param sessionToken - Token to revoke. If omitted, uses the token
|
|
236
|
+
* previously set via `buildspace.setSession()`.
|
|
237
|
+
*/
|
|
238
|
+
async signOut(sessionToken) {
|
|
239
|
+
const token = sessionToken ?? this.transport.getSessionToken();
|
|
240
|
+
try {
|
|
241
|
+
if (token) {
|
|
242
|
+
await this.revokeSession(token);
|
|
243
|
+
}
|
|
244
|
+
} catch (error) {
|
|
245
|
+
if (!(error instanceof BuildspaceError && error.service === "auth" && (error.status === 401 || error.status === 404))) {
|
|
246
|
+
throw error;
|
|
247
|
+
}
|
|
248
|
+
} finally {
|
|
249
|
+
this.transport.clearSession();
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// src/config.ts
|
|
255
|
+
var DEFAULT_BASE_URL = "https://api.buildspace.studio";
|
|
256
|
+
var DEFAULT_LOGIN_URL = "https://login.buildspace.studio";
|
|
257
|
+
var DEFAULT_API_VERSION = "2025-06-01";
|
|
258
|
+
function resolveConfig(mode, config = {}) {
|
|
259
|
+
return {
|
|
260
|
+
mode,
|
|
261
|
+
baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
|
|
262
|
+
loginUrl: config.loginUrl ?? DEFAULT_LOGIN_URL,
|
|
263
|
+
version: config.version ?? DEFAULT_API_VERSION,
|
|
264
|
+
fetch: config.fetch ?? fetch
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function formatMode(mode) {
|
|
268
|
+
return mode === "server" ? "server SDK" : "client SDK";
|
|
106
269
|
}
|
|
107
|
-
|
|
108
|
-
|
|
270
|
+
function validateApiKey({ mode, key }) {
|
|
271
|
+
if (!key) {
|
|
272
|
+
throw new Error(`Missing API key. The ${formatMode(mode)} requires a valid Buildspace key.`);
|
|
273
|
+
}
|
|
274
|
+
if (mode === "server" && !key.startsWith("bs_sec_")) {
|
|
275
|
+
throw new Error(
|
|
276
|
+
"The server SDK requires a secret key (bs_sec_*). Use createClient() from '@buildspacestudio/sdk/client' for browser usage with publishable keys."
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
if (mode === "client" && !key.startsWith("bs_pub_")) {
|
|
280
|
+
throw new Error(
|
|
281
|
+
"The client SDK requires a publishable key (bs_pub_*). Use new Buildspace() from '@buildspacestudio/sdk' for server usage with secret keys."
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// src/events/client.ts
|
|
287
|
+
var EventsClientNamespace = class {
|
|
288
|
+
queue = [];
|
|
289
|
+
transport;
|
|
290
|
+
timer = null;
|
|
291
|
+
constructor(transport, config = {}) {
|
|
292
|
+
this.transport = transport;
|
|
293
|
+
const flushInterval = config.flushInterval ?? 5e3;
|
|
294
|
+
this.maxBatchSize = config.maxBatchSize ?? 20;
|
|
295
|
+
this.timer = setInterval(() => {
|
|
296
|
+
this.flush().catch(() => {
|
|
297
|
+
});
|
|
298
|
+
}, flushInterval);
|
|
299
|
+
}
|
|
300
|
+
maxBatchSize;
|
|
301
|
+
/**
|
|
302
|
+
* Queue an event for batched delivery.
|
|
303
|
+
*
|
|
304
|
+
* This method is synchronous and returns immediately. Events are
|
|
305
|
+
* automatically flushed when the batch size is reached or on the
|
|
306
|
+
* configured interval.
|
|
307
|
+
*
|
|
308
|
+
* @param event - Event name, e.g. `"button.clicked"`.
|
|
309
|
+
* @param properties - Arbitrary key-value data to attach.
|
|
310
|
+
* @param actorId - Identifier of the user or entity that triggered the event.
|
|
311
|
+
*/
|
|
312
|
+
track(event, properties, actorId) {
|
|
313
|
+
this.queue.push({
|
|
314
|
+
event,
|
|
315
|
+
properties,
|
|
316
|
+
actor_id: actorId,
|
|
317
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
318
|
+
});
|
|
319
|
+
if (this.queue.length >= this.maxBatchSize) {
|
|
320
|
+
this.flush().catch(() => {
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Send all queued events to the API immediately.
|
|
326
|
+
*
|
|
327
|
+
* If the flush fails, events are re-queued for the next attempt.
|
|
328
|
+
*/
|
|
329
|
+
async flush() {
|
|
330
|
+
if (this.queue.length === 0) {
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
const batch = this.queue.splice(0, this.queue.length);
|
|
334
|
+
try {
|
|
335
|
+
await this.transport.request({
|
|
336
|
+
service: "events",
|
|
337
|
+
path: "/v1/events/batch",
|
|
338
|
+
method: "POST",
|
|
339
|
+
keepalive: true,
|
|
340
|
+
body: { events: batch }
|
|
341
|
+
});
|
|
342
|
+
} catch {
|
|
343
|
+
this.queue.unshift(...batch);
|
|
344
|
+
throw new Error("Failed to flush events batch.");
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Stop the automatic flush timer and send any remaining events.
|
|
349
|
+
*
|
|
350
|
+
* Call this during app shutdown or cleanup to ensure no events are lost.
|
|
351
|
+
*/
|
|
352
|
+
async shutdown() {
|
|
353
|
+
if (this.timer) {
|
|
354
|
+
clearInterval(this.timer);
|
|
355
|
+
this.timer = null;
|
|
356
|
+
}
|
|
357
|
+
await this.flush();
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
// src/events/server.ts
|
|
362
|
+
var EventsServerNamespace = class {
|
|
363
|
+
transport;
|
|
364
|
+
constructor(transport) {
|
|
365
|
+
this.transport = transport;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Track a single event.
|
|
369
|
+
*
|
|
370
|
+
* @param event - Event name, e.g. `"user.signed_up"`.
|
|
371
|
+
* @param properties - Arbitrary key-value data to attach to the event.
|
|
372
|
+
* @param actorId - Identifier of the user or entity that triggered the event.
|
|
373
|
+
* @returns The ID of the created event.
|
|
374
|
+
*/
|
|
375
|
+
track(event, properties, actorId) {
|
|
376
|
+
return this.transport.request({
|
|
377
|
+
service: "events",
|
|
378
|
+
path: "/v1/events",
|
|
379
|
+
method: "POST",
|
|
380
|
+
body: {
|
|
381
|
+
event,
|
|
382
|
+
properties,
|
|
383
|
+
actor_id: actorId,
|
|
384
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Track multiple events in a single request.
|
|
390
|
+
*
|
|
391
|
+
* Events without a `timestamp` are automatically timestamped.
|
|
392
|
+
*
|
|
393
|
+
* @param events - Array of events to track.
|
|
394
|
+
* @returns The count and IDs of created events.
|
|
395
|
+
*/
|
|
396
|
+
batchTrack(events) {
|
|
397
|
+
return this.transport.request({
|
|
398
|
+
service: "events",
|
|
399
|
+
path: "/v1/events/batch",
|
|
400
|
+
method: "POST",
|
|
401
|
+
body: {
|
|
402
|
+
events: events.map((event) => ({
|
|
403
|
+
...event,
|
|
404
|
+
timestamp: event.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
405
|
+
}))
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
// src/http.ts
|
|
412
|
+
var HttpTransport = class {
|
|
413
|
+
baseUrl;
|
|
414
|
+
fetcher;
|
|
415
|
+
key;
|
|
416
|
+
loginUrl;
|
|
417
|
+
mode;
|
|
418
|
+
version;
|
|
419
|
+
sessionToken = null;
|
|
420
|
+
constructor({ key, resolvedConfig }) {
|
|
421
|
+
validateApiKey({ mode: resolvedConfig.mode, key });
|
|
422
|
+
this.key = key;
|
|
423
|
+
this.mode = resolvedConfig.mode;
|
|
424
|
+
this.baseUrl = resolvedConfig.baseUrl;
|
|
425
|
+
this.loginUrl = resolvedConfig.loginUrl;
|
|
426
|
+
this.version = resolvedConfig.version;
|
|
427
|
+
this.fetcher = resolvedConfig.fetch;
|
|
428
|
+
}
|
|
429
|
+
/** Remove the stored session token. */
|
|
430
|
+
clearSession() {
|
|
431
|
+
this.sessionToken = null;
|
|
432
|
+
}
|
|
433
|
+
/** Return the current session token, or `null` if none is set. */
|
|
434
|
+
getSessionToken() {
|
|
435
|
+
return this.sessionToken;
|
|
436
|
+
}
|
|
437
|
+
/** Store a session token that will be sent as `X-Session-Token` on subsequent requests. */
|
|
438
|
+
setSession(sessionToken) {
|
|
439
|
+
this.sessionToken = sessionToken;
|
|
440
|
+
}
|
|
441
|
+
async request({
|
|
442
|
+
service,
|
|
443
|
+
path,
|
|
444
|
+
method = "GET",
|
|
445
|
+
query,
|
|
446
|
+
body,
|
|
447
|
+
headers,
|
|
448
|
+
keepalive
|
|
449
|
+
}) {
|
|
450
|
+
const url = new URL(path, this.baseUrl);
|
|
451
|
+
if (query) {
|
|
452
|
+
for (const [key, value] of Object.entries(query)) {
|
|
453
|
+
if (value !== void 0) {
|
|
454
|
+
url.searchParams.set(key, String(value));
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
const response = await this.fetcher(url.toString(), {
|
|
459
|
+
method,
|
|
460
|
+
keepalive,
|
|
461
|
+
headers: {
|
|
462
|
+
Authorization: `Bearer ${this.key}`,
|
|
463
|
+
"Content-Type": "application/json",
|
|
464
|
+
"X-Buildspace-Version": this.version,
|
|
465
|
+
...this.sessionToken ? { "X-Session-Token": this.sessionToken } : {},
|
|
466
|
+
...headers
|
|
467
|
+
},
|
|
468
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
469
|
+
});
|
|
470
|
+
if (!response.ok) {
|
|
471
|
+
const payload = await response.json().catch(() => null);
|
|
472
|
+
throw new BuildspaceError({
|
|
473
|
+
service,
|
|
474
|
+
status: response.status,
|
|
475
|
+
code: payload?.code ?? `${service}/http-${response.status}`,
|
|
476
|
+
message: payload?.error ?? payload?.message ?? response.statusText
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
if (response.status === 204) {
|
|
480
|
+
return void 0;
|
|
481
|
+
}
|
|
482
|
+
return response.json();
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
// src/notifications/server.ts
|
|
487
|
+
var NotificationsServerNamespace = class {
|
|
488
|
+
transport;
|
|
489
|
+
constructor(transport) {
|
|
490
|
+
this.transport = transport;
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Send a custom email with inline HTML content.
|
|
494
|
+
*
|
|
495
|
+
* @param opts - Email content and recipient options.
|
|
496
|
+
* @returns The notification ID and optional provider message ID.
|
|
497
|
+
*/
|
|
498
|
+
send(opts) {
|
|
499
|
+
return this.transport.request({
|
|
500
|
+
service: "notifications",
|
|
501
|
+
path: "/v1/notifications/send",
|
|
502
|
+
method: "POST",
|
|
503
|
+
body: {
|
|
504
|
+
to: opts.to,
|
|
505
|
+
subject: opts.subject,
|
|
506
|
+
html: opts.html,
|
|
507
|
+
text: opts.text,
|
|
508
|
+
reply_to: opts.replyTo,
|
|
509
|
+
metadata: opts.metadata
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Send an email using a pre-configured template.
|
|
515
|
+
*
|
|
516
|
+
* Templates are created in the Buildspace Creator Studio.
|
|
517
|
+
*
|
|
518
|
+
* @param templateSlug - The slug of the template to use, e.g. `"welcome-email"`.
|
|
519
|
+
* @param opts - Recipient and template variable options.
|
|
520
|
+
* @returns The notification ID and optional provider message ID.
|
|
521
|
+
*/
|
|
522
|
+
sendTemplate(templateSlug, opts) {
|
|
523
|
+
return this.transport.request({
|
|
524
|
+
service: "notifications",
|
|
525
|
+
path: "/v1/notifications/send-template",
|
|
526
|
+
method: "POST",
|
|
527
|
+
body: {
|
|
528
|
+
template: templateSlug,
|
|
529
|
+
to: opts.to,
|
|
530
|
+
variables: opts.variables,
|
|
531
|
+
metadata: opts.metadata
|
|
532
|
+
}
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
// src/storage/client.ts
|
|
538
|
+
var StorageClientNamespace = class {
|
|
539
|
+
transport;
|
|
540
|
+
constructor(transport) {
|
|
541
|
+
this.transport = transport;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Upload a `File` or `Blob` to Buildspace storage.
|
|
545
|
+
*
|
|
546
|
+
* This method handles the full upload flow:
|
|
547
|
+
* 1. Requests a pre-signed upload URL from the API
|
|
548
|
+
* 2. Uploads the file directly to the storage provider
|
|
549
|
+
* 3. Returns a signed download URL for the uploaded file
|
|
550
|
+
*
|
|
551
|
+
* @param file - The file or blob to upload.
|
|
552
|
+
* @param opts - Upload options including the storage path.
|
|
553
|
+
* @returns The storage key, file size, and a signed download URL.
|
|
554
|
+
*
|
|
555
|
+
* @throws {BuildspaceError} If the upload URL request or the direct upload fails.
|
|
556
|
+
*/
|
|
557
|
+
async upload(file, opts) {
|
|
558
|
+
const contentType = opts.contentType ?? (file instanceof File ? file.type : "application/octet-stream");
|
|
559
|
+
const signed = await this.transport.request({
|
|
560
|
+
service: "storage",
|
|
561
|
+
path: "/v1/storage/upload",
|
|
562
|
+
method: "POST",
|
|
563
|
+
body: {
|
|
564
|
+
path: opts.path,
|
|
565
|
+
content_type: contentType,
|
|
566
|
+
size: file.size
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
const response = await this.transport.fetcher(signed.upload_url, {
|
|
570
|
+
method: "PUT",
|
|
571
|
+
headers: { "Content-Type": contentType },
|
|
572
|
+
body: file
|
|
573
|
+
});
|
|
574
|
+
if (!response.ok) {
|
|
575
|
+
throw new BuildspaceError({
|
|
576
|
+
service: "storage",
|
|
577
|
+
status: response.status,
|
|
578
|
+
code: "storage/upload-failed",
|
|
579
|
+
message: `Direct upload failed: ${response.statusText}`
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
const { url } = await this.getUrl(signed.key);
|
|
583
|
+
return {
|
|
584
|
+
key: signed.key,
|
|
585
|
+
size: file.size,
|
|
586
|
+
url
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Get a time-limited signed URL for downloading a stored file.
|
|
591
|
+
*
|
|
592
|
+
* @param key - The storage key of the object.
|
|
593
|
+
* @param opts - Optional settings.
|
|
594
|
+
* @param opts.expiresIn - URL lifetime in seconds.
|
|
595
|
+
*/
|
|
596
|
+
getUrl(key, opts) {
|
|
597
|
+
return this.transport.request({
|
|
598
|
+
service: "storage",
|
|
599
|
+
path: "/v1/storage/url",
|
|
600
|
+
query: {
|
|
601
|
+
key,
|
|
602
|
+
expires_in: opts?.expiresIn
|
|
603
|
+
}
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* List stored objects, optionally filtered by key prefix.
|
|
608
|
+
*
|
|
609
|
+
* @param prefix - Only return objects whose key starts with this prefix.
|
|
610
|
+
* @param opts - Pagination options.
|
|
611
|
+
*/
|
|
612
|
+
list(prefix, opts) {
|
|
613
|
+
return this.transport.request({
|
|
614
|
+
service: "storage",
|
|
615
|
+
path: "/v1/storage/objects",
|
|
616
|
+
query: {
|
|
617
|
+
prefix,
|
|
618
|
+
limit: opts?.limit,
|
|
619
|
+
offset: opts?.offset
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Delete a stored object by key.
|
|
625
|
+
*
|
|
626
|
+
* @param key - The storage key of the object to delete.
|
|
627
|
+
*/
|
|
628
|
+
async delete(key) {
|
|
629
|
+
await this.transport.request({
|
|
630
|
+
service: "storage",
|
|
631
|
+
path: "/v1/storage/object",
|
|
632
|
+
method: "DELETE",
|
|
633
|
+
body: { key }
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
// src/storage/server.ts
|
|
639
|
+
var StorageServerNamespace = class {
|
|
640
|
+
transport;
|
|
641
|
+
constructor(transport) {
|
|
642
|
+
this.transport = transport;
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Request a pre-signed URL for direct file upload.
|
|
646
|
+
*
|
|
647
|
+
* @param opts - Upload options including the storage key, content type, and file size.
|
|
648
|
+
* @returns The signed upload URL, the resolved key, and expiry time in seconds.
|
|
649
|
+
*/
|
|
650
|
+
getUploadUrl(opts) {
|
|
651
|
+
return this.transport.request({
|
|
652
|
+
service: "storage",
|
|
653
|
+
path: "/v1/storage/upload",
|
|
654
|
+
method: "POST",
|
|
655
|
+
body: {
|
|
656
|
+
path: opts.key,
|
|
657
|
+
content_type: opts.contentType,
|
|
658
|
+
size: opts.size
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Get a time-limited signed URL for downloading a stored file.
|
|
664
|
+
*
|
|
665
|
+
* @param key - The storage key of the object.
|
|
666
|
+
* @param opts - Optional settings.
|
|
667
|
+
* @param opts.expiresIn - URL lifetime in seconds. Uses server default if omitted.
|
|
668
|
+
*/
|
|
669
|
+
getSignedUrl(key, opts) {
|
|
670
|
+
return this.transport.request({
|
|
671
|
+
service: "storage",
|
|
672
|
+
path: "/v1/storage/url",
|
|
673
|
+
query: {
|
|
674
|
+
key,
|
|
675
|
+
expires_in: opts?.expiresIn
|
|
676
|
+
}
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* List stored objects, optionally filtered by key prefix.
|
|
681
|
+
*
|
|
682
|
+
* @param prefix - Only return objects whose key starts with this prefix, e.g. `"avatars/"`.
|
|
683
|
+
* @param opts - Pagination options.
|
|
684
|
+
*/
|
|
685
|
+
list(prefix, opts) {
|
|
686
|
+
return this.transport.request({
|
|
687
|
+
service: "storage",
|
|
688
|
+
path: "/v1/storage/objects",
|
|
689
|
+
query: {
|
|
690
|
+
prefix,
|
|
691
|
+
limit: opts?.limit,
|
|
692
|
+
offset: opts?.offset
|
|
693
|
+
}
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
/**
|
|
697
|
+
* Delete a stored object by key.
|
|
698
|
+
*
|
|
699
|
+
* @param key - The storage key of the object to delete.
|
|
700
|
+
*/
|
|
701
|
+
async delete(key) {
|
|
702
|
+
await this.transport.request({
|
|
703
|
+
service: "storage",
|
|
704
|
+
path: "/v1/storage/object",
|
|
705
|
+
method: "DELETE",
|
|
706
|
+
body: { key }
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
/**
|
|
710
|
+
* Get current storage usage statistics for the app.
|
|
711
|
+
*
|
|
712
|
+
* @returns Byte counts and object count.
|
|
713
|
+
*/
|
|
714
|
+
getUsage() {
|
|
715
|
+
return this.transport.request({
|
|
716
|
+
service: "storage",
|
|
717
|
+
path: "/v1/storage/usage"
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
// src/index.ts
|
|
723
|
+
var Buildspace = class {
|
|
724
|
+
transport;
|
|
725
|
+
_auth = null;
|
|
726
|
+
_events = null;
|
|
727
|
+
_notifications = null;
|
|
728
|
+
_publicAuth = null;
|
|
729
|
+
_storage = null;
|
|
730
|
+
/**
|
|
731
|
+
* Create a new server SDK instance.
|
|
732
|
+
*
|
|
733
|
+
* @param secretKey - Your Buildspace secret key (starts with `bs_sec_`).
|
|
734
|
+
* @param config - Optional configuration overrides.
|
|
735
|
+
*/
|
|
736
|
+
constructor(secretKey, config = {}) {
|
|
737
|
+
this.transport = new HttpTransport({
|
|
738
|
+
key: secretKey,
|
|
739
|
+
resolvedConfig: resolveConfig("server", config)
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
/** Server-side auth: session verification, OAuth callback handling, sign-out. */
|
|
743
|
+
get auth() {
|
|
744
|
+
this._auth ??= new AuthServerNamespace(this.transport);
|
|
745
|
+
return this._auth;
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Client-style auth helpers available on the server (e.g. generating sign-in URLs
|
|
749
|
+
* in server-rendered pages). For full server auth, use {@link auth}.
|
|
750
|
+
*/
|
|
751
|
+
get authClient() {
|
|
752
|
+
this._publicAuth ??= new AuthClientNamespace(this.transport);
|
|
753
|
+
return this._publicAuth;
|
|
754
|
+
}
|
|
755
|
+
/** Server-side event tracking. */
|
|
756
|
+
get events() {
|
|
757
|
+
this._events ??= new EventsServerNamespace(this.transport);
|
|
758
|
+
return this._events;
|
|
759
|
+
}
|
|
760
|
+
/** Server-side file storage: upload URLs, signed downloads, listing, deletion. */
|
|
761
|
+
get storage() {
|
|
762
|
+
this._storage ??= new StorageServerNamespace(this.transport);
|
|
763
|
+
return this._storage;
|
|
764
|
+
}
|
|
765
|
+
/** Server-side transactional email notifications. */
|
|
766
|
+
get notifications() {
|
|
767
|
+
this._notifications ??= new NotificationsServerNamespace(this.transport);
|
|
768
|
+
return this._notifications;
|
|
769
|
+
}
|
|
770
|
+
/** Clear the stored session token. */
|
|
771
|
+
clearSession() {
|
|
772
|
+
this.transport.clearSession();
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
775
|
+
* Set a session token that will be sent as `X-Session-Token` on all
|
|
776
|
+
* subsequent API requests.
|
|
777
|
+
*
|
|
778
|
+
* @param sessionToken - The session token to attach to requests.
|
|
779
|
+
*/
|
|
780
|
+
setSession(sessionToken) {
|
|
781
|
+
this.transport.setSession(sessionToken);
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
var src_default = Buildspace;
|
|
785
|
+
|
|
786
|
+
export { AuthClientNamespace, AuthServerNamespace, Buildspace, BuildspaceError, EventsClientNamespace, EventsServerNamespace, NotificationsServerNamespace, StorageClientNamespace, StorageServerNamespace, src_default as default };
|
|
787
|
+
//# sourceMappingURL=index.js.map
|
|
109
788
|
//# sourceMappingURL=index.js.map
|