@sylphx/sdk 0.3.7 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -30,10 +30,14 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/server/index.ts
31
31
  var server_exports = {};
32
32
  __export(server_exports, {
33
+ FunctionsClient: () => FunctionsClient,
34
+ InvalidConnectionUrlError: () => InvalidConnectionUrlError,
33
35
  createAI: () => createAI,
34
36
  createAuthenticatedServerClient: () => createAuthenticatedServerClient,
37
+ createClient: () => createClient2,
35
38
  createKv: () => createKv,
36
39
  createServerClient: () => createServerClient,
40
+ createServerRestClient: () => createServerRestClient,
37
41
  createStreams: () => createStreams,
38
42
  createWebhookHandler: () => createWebhookHandler,
39
43
  decodeUserId: () => decodeUserId,
@@ -1201,13 +1205,13 @@ async function jwtVerify(jwt, key, options) {
1201
1205
  }
1202
1206
 
1203
1207
  // src/constants.ts
1204
- var ENV_SECRET_KEY = "SYLPHX_SECRET_KEY";
1205
- function resolveSecretKey(explicit) {
1206
- return explicit || process.env[ENV_SECRET_KEY];
1208
+ var ENV_SECRET_URL = "SYLPHX_SECRET_URL";
1209
+ function resolveSecretUrl(explicit) {
1210
+ return explicit || process.env[ENV_SECRET_URL];
1207
1211
  }
1208
1212
  var SDK_API_PATH = `/v1`;
1209
1213
  var DEFAULT_SDK_API_HOST = "api.sylphx.com";
1210
- var SDK_VERSION = "0.1.0";
1214
+ var SDK_VERSION = "0.5.0";
1211
1215
  var SDK_PLATFORM = typeof window !== "undefined" ? "browser" : typeof process !== "undefined" && process.versions?.node ? "node" : "unknown";
1212
1216
  var DEFAULT_TIMEOUT_MS = 3e4;
1213
1217
  var SESSION_TOKEN_LIFETIME_SECONDS = 5 * 60;
@@ -1313,6 +1317,13 @@ var SylphxError = class _SylphxError extends Error {
1313
1317
  static isRateLimited(err) {
1314
1318
  return err instanceof _SylphxError && err.code === "TOO_MANY_REQUESTS";
1315
1319
  }
1320
+ /**
1321
+ * Check if error is an account lockout error (too many failed login attempts).
1322
+ * When true, `error.data?.lockoutUntil` contains the ISO 8601 timestamp when the lockout expires.
1323
+ */
1324
+ static isAccountLocked(err) {
1325
+ return err instanceof _SylphxError && err.code === "TOO_MANY_REQUESTS" && err.data?.code === "ACCOUNT_LOCKED";
1326
+ }
1316
1327
  /**
1317
1328
  * Check if error is a quota exceeded error (plan limit reached)
1318
1329
  */
@@ -1371,6 +1382,24 @@ var SylphxError = class _SylphxError extends Error {
1371
1382
  };
1372
1383
  }
1373
1384
  };
1385
+ var NetworkError = class extends SylphxError {
1386
+ constructor(message2 = "Network request failed", options) {
1387
+ super(message2, { ...options, code: "NETWORK_ERROR" });
1388
+ this.name = "NetworkError";
1389
+ }
1390
+ };
1391
+ var TimeoutError = class extends SylphxError {
1392
+ /** Timeout duration in milliseconds */
1393
+ timeout;
1394
+ constructor(timeout, options) {
1395
+ super(`Request timed out after ${timeout}ms`, {
1396
+ ...options,
1397
+ code: "TIMEOUT"
1398
+ });
1399
+ this.name = "TimeoutError";
1400
+ this.timeout = timeout;
1401
+ }
1402
+ };
1374
1403
  var RateLimitError = class extends SylphxError {
1375
1404
  /** Maximum requests allowed in window */
1376
1405
  limit;
@@ -1414,7 +1443,7 @@ function exponentialBackoff(attempt, baseDelay = BASE_RETRY_DELAY_MS, maxDelay =
1414
1443
 
1415
1444
  // src/key-validation.ts
1416
1445
  var PUBLIC_KEY_PATTERN = /^pk_(dev|stg|prod)_[a-z0-9]{12}_[a-f0-9]{32}$/;
1417
- var APP_ID_PATTERN = /^app_(dev|stg|prod)_[a-z0-9_-]+$/;
1446
+ var APP_ID_PATTERN = /^(app|pk)_(dev|stg|prod|prev)_[a-z0-9_-]+$/;
1418
1447
  var SECRET_KEY_PATTERN = /^sk_(dev|stg|prod)_[a-z0-9_-]+$/;
1419
1448
  var ENV_PREFIX_MAP = {
1420
1449
  dev: "development",
@@ -1443,9 +1472,8 @@ To fix permanently:
1443
1472
  The SDK will automatically sanitize the key, but fixing the source is recommended.`;
1444
1473
  }
1445
1474
  function createInvalidKeyError(keyType, key, envVarName) {
1446
- const prefix = keyType === "appId" ? "app" : "sk";
1447
1475
  const maskedKey = key.length > 20 ? `${key.slice(0, 20)}...` : key;
1448
- const formatHint = `${prefix}_(dev|stg|prod)_[identifier]`;
1476
+ const formatHint = keyType === "appId" ? "pk_(dev|stg|prod)_{ref}_{hex} or app_(dev|stg|prod)_[id]" : "sk_(dev|stg|prod)_{ref}_{hex}";
1449
1477
  const keyTypeName = keyType === "appId" ? "App ID" : "Secret Key";
1450
1478
  return `[Sylphx] Invalid ${keyTypeName} format.
1451
1479
 
@@ -1457,12 +1485,12 @@ You can find your keys in the Sylphx Console \u2192 API Keys.
1457
1485
 
1458
1486
  Common issues:
1459
1487
  \u2022 Key has uppercase characters (must be lowercase)
1460
- \u2022 Key has wrong prefix (App ID: app_, Secret Key: sk_)
1488
+ \u2022 Key has wrong prefix (App ID: pk_ or app_, Secret Key: sk_)
1461
1489
  \u2022 Key has invalid environment (must be dev, stg, or prod)
1462
1490
  \u2022 Key was copied with extra whitespace`;
1463
1491
  }
1464
1492
  function extractEnvironment(key) {
1465
- const match = key.match(/^(?:app|sk)_(dev|stg|prod)_/);
1493
+ const match = key.match(/^(?:app|pk|sk)_(dev|stg|prod|prev)_/);
1466
1494
  if (!match) return void 0;
1467
1495
  return ENV_PREFIX_MAP[match[1]];
1468
1496
  }
@@ -1989,6 +2017,499 @@ function createDynamicRestClient(config) {
1989
2017
  return client;
1990
2018
  }
1991
2019
 
2020
+ // src/connection-url.ts
2021
+ var SYLPHX_PROTOCOL = "sylphx:";
2022
+ var DEFAULT_VERSION = "v1";
2023
+ var CREDENTIAL_REGEX = /^(pk|sk)_(dev|stg|prod|prev)_[a-f0-9]{32,64}$/;
2024
+ var VERSION_REGEX = /^v[0-9]+$/;
2025
+ var SLUG_REGEX = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
2026
+ var InvalidConnectionUrlError = class _InvalidConnectionUrlError extends Error {
2027
+ code = "INVALID_CONNECTION_URL";
2028
+ constructor(message2) {
2029
+ super(message2);
2030
+ this.name = "InvalidConnectionUrlError";
2031
+ Object.setPrototypeOf(this, _InvalidConnectionUrlError.prototype);
2032
+ }
2033
+ };
2034
+ function fail(reason) {
2035
+ throw new InvalidConnectionUrlError(`Invalid Sylphx connection URL: ${reason}`);
2036
+ }
2037
+ function parseCredential(raw) {
2038
+ const match = CREDENTIAL_REGEX.exec(raw);
2039
+ if (!match) {
2040
+ fail(`credential must match (pk|sk)_(dev|stg|prod|prev)_[a-f0-9]{32,64}, got "${raw}"`);
2041
+ }
2042
+ return {
2043
+ credentialType: match[1],
2044
+ env: match[2]
2045
+ };
2046
+ }
2047
+ function validateSlug(candidate) {
2048
+ if (!candidate || candidate.length > 63 || !SLUG_REGEX.test(candidate)) {
2049
+ fail(`slug "${candidate}" is not a valid DNS label (lowercase alnum + hyphens, 1-63 chars)`);
2050
+ }
2051
+ return candidate;
2052
+ }
2053
+ function parseConnectionUrl(url) {
2054
+ if (typeof url !== "string" || url.length === 0) {
2055
+ fail("url must be a non-empty string");
2056
+ }
2057
+ let parsed;
2058
+ try {
2059
+ parsed = new URL(url);
2060
+ } catch {
2061
+ fail(`not a valid URL: "${url}"`);
2062
+ }
2063
+ if (parsed.protocol !== SYLPHX_PROTOCOL) {
2064
+ fail(`protocol must be "sylphx:", got "${parsed.protocol}"`);
2065
+ }
2066
+ const credential = decodeURIComponent(parsed.username);
2067
+ if (!credential) {
2068
+ fail("missing credential (expected `sylphx://<credential>@<host>`)");
2069
+ }
2070
+ if (parsed.password) {
2071
+ fail("connection URL must not contain a password component");
2072
+ }
2073
+ const { credentialType, env } = parseCredential(credential);
2074
+ const host = parsed.host;
2075
+ if (!host) {
2076
+ fail("missing host");
2077
+ }
2078
+ const hostname = parsed.hostname;
2079
+ const firstDot = hostname.indexOf(".");
2080
+ if (firstDot <= 0) {
2081
+ fail(`host "${hostname}" must contain at least one dot (slug.domain)`);
2082
+ }
2083
+ const slugCandidate = hostname.slice(0, firstDot);
2084
+ const domainSuffix = hostname.slice(firstDot + 1);
2085
+ if (!domainSuffix) {
2086
+ fail(`host "${hostname}" has empty domain suffix`);
2087
+ }
2088
+ const slug = validateSlug(slugCandidate);
2089
+ const rawPath = parsed.pathname.replace(/^\/+/, "").replace(/\/+$/, "");
2090
+ let version = DEFAULT_VERSION;
2091
+ if (rawPath !== "") {
2092
+ if (!VERSION_REGEX.test(rawPath)) {
2093
+ fail(`path "${parsed.pathname}" must be empty or match /v{N}`);
2094
+ }
2095
+ version = rawPath;
2096
+ }
2097
+ if (parsed.search) {
2098
+ fail("connection URL must not contain a query string");
2099
+ }
2100
+ if (parsed.hash) {
2101
+ fail("connection URL must not contain a fragment");
2102
+ }
2103
+ const apiBaseUrl = `https://${host}/${version}`;
2104
+ return {
2105
+ credential,
2106
+ credentialType,
2107
+ env,
2108
+ slug,
2109
+ host,
2110
+ apiBaseUrl
2111
+ };
2112
+ }
2113
+
2114
+ // src/config.ts
2115
+ var LEGACY_EMBEDDED_REF_PATTERN = /^(pk|sk)_(dev|stg|prod|prev)_[a-z0-9]{12}_[a-f0-9]+$/;
2116
+ var LEGACY_APP_KEY_PATTERN = /^app_(dev|stg|prod|prev)_/;
2117
+ var MIGRATION_MESSAGE = "API key format has changed. Use a sylphx:// connection URL instead.\n\nNew format: sylphx://pk_prod_{hex}@your-slug.sylphx.com\n\nGenerate new credentials from the Sylphx Console \u2192 Your App \u2192 Environments.\nSee https://docs.sylphx.com/migration for details.";
2118
+ function rejectLegacyKeyFormat(input) {
2119
+ const trimmed = input.trim().toLowerCase();
2120
+ if (LEGACY_APP_KEY_PATTERN.test(trimmed)) {
2121
+ throw new SylphxError(`[Sylphx] ${MIGRATION_MESSAGE}`, { code: "BAD_REQUEST" });
2122
+ }
2123
+ if (LEGACY_EMBEDDED_REF_PATTERN.test(trimmed)) {
2124
+ throw new SylphxError(`[Sylphx] ${MIGRATION_MESSAGE}`, { code: "BAD_REQUEST" });
2125
+ }
2126
+ }
2127
+ function freezeConfig(opts) {
2128
+ return Object.freeze({
2129
+ credential: opts.credential,
2130
+ credentialType: opts.credentialType,
2131
+ env: opts.env,
2132
+ slug: opts.slug,
2133
+ baseUrl: opts.baseUrl,
2134
+ accessToken: opts.accessToken,
2135
+ // Backward-compat aliases
2136
+ secretKey: opts.credentialType === "sk" ? opts.credential : void 0,
2137
+ publicKey: opts.credentialType === "pk" ? opts.credential : void 0,
2138
+ ref: opts.slug
2139
+ });
2140
+ }
2141
+ function createClient2(input) {
2142
+ if (typeof input === "string") {
2143
+ return createConfigFromUrl(input);
2144
+ }
2145
+ return createConfigFromComponents(input);
2146
+ }
2147
+ function createServerClient(input) {
2148
+ const config = createClient2(input);
2149
+ if (config.credentialType !== "sk") {
2150
+ throw new SylphxError(
2151
+ "[Sylphx] createServerClient() requires a secret key (sk_*). Use a SYLPHX_SECRET_URL with an sk_ credential, or pass { secretKey } in the components object.",
2152
+ { code: "BAD_REQUEST" }
2153
+ );
2154
+ }
2155
+ return config;
2156
+ }
2157
+ function createConfigFromUrl(url) {
2158
+ if (!url || typeof url !== "string") {
2159
+ throw new SylphxError(
2160
+ "[Sylphx] Connection URL is required. Set SYLPHX_URL or NEXT_PUBLIC_SYLPHX_URL environment variable.\n\nFormat: sylphx://pk_prod_{hex}@your-slug.sylphx.com",
2161
+ { code: "BAD_REQUEST" }
2162
+ );
2163
+ }
2164
+ const trimmed = url.trim();
2165
+ rejectLegacyKeyFormat(trimmed);
2166
+ if (!trimmed.startsWith("sylphx://")) {
2167
+ if (CREDENTIAL_REGEX.test(trimmed)) {
2168
+ throw new SylphxError(
2169
+ "[Sylphx] Received a bare credential instead of a connection URL.\n\nWrap it in a connection URL: sylphx://<credential>@<slug>.sylphx.com\nOr use createClient({ slug, publicKey }) for explicit components.",
2170
+ { code: "BAD_REQUEST" }
2171
+ );
2172
+ }
2173
+ throw new SylphxError(
2174
+ `[Sylphx] Invalid connection URL \u2014 must start with "sylphx://". Got: "${trimmed.slice(0, 30)}..."`,
2175
+ { code: "BAD_REQUEST" }
2176
+ );
2177
+ }
2178
+ let parsed;
2179
+ try {
2180
+ parsed = parseConnectionUrl(trimmed);
2181
+ } catch (err) {
2182
+ if (err instanceof InvalidConnectionUrlError) {
2183
+ throw new SylphxError(err.message, { code: "BAD_REQUEST", cause: err });
2184
+ }
2185
+ throw err;
2186
+ }
2187
+ return freezeConfig({
2188
+ credential: parsed.credential,
2189
+ credentialType: parsed.credentialType,
2190
+ env: parsed.env,
2191
+ slug: parsed.slug,
2192
+ baseUrl: parsed.apiBaseUrl
2193
+ });
2194
+ }
2195
+ function createConfigFromComponents(input) {
2196
+ const credential = input.secretKey || input.publicKey;
2197
+ if (!credential) {
2198
+ throw new SylphxError("[Sylphx] Either publicKey or secretKey must be provided.", {
2199
+ code: "BAD_REQUEST"
2200
+ });
2201
+ }
2202
+ const resolvedSlug = input.slug || input.ref;
2203
+ if (!resolvedSlug) {
2204
+ throw new SylphxError("[Sylphx] slug is required when using explicit components.", {
2205
+ code: "BAD_REQUEST"
2206
+ });
2207
+ }
2208
+ const trimmedCred = credential.trim().toLowerCase();
2209
+ if (CREDENTIAL_REGEX.test(trimmedCred)) {
2210
+ const match = CREDENTIAL_REGEX.exec(trimmedCred);
2211
+ const credentialType = match[1];
2212
+ const env = match[2];
2213
+ const slug = resolvedSlug.trim().toLowerCase();
2214
+ const domain = input.domain?.trim() || "sylphx.com";
2215
+ const baseUrl = `https://${slug}.${domain}/v1`;
2216
+ return freezeConfig({
2217
+ credential: trimmedCred,
2218
+ credentialType,
2219
+ env,
2220
+ slug,
2221
+ baseUrl,
2222
+ accessToken: input.accessToken
2223
+ });
2224
+ }
2225
+ const parts = trimmedCred.split("_");
2226
+ const prefix = parts[0];
2227
+ if ((prefix === "pk" || prefix === "sk") && parts.length >= 3) {
2228
+ const envSegment = parts[1];
2229
+ const validEnvs = ["dev", "stg", "prod", "prev"];
2230
+ const env = validEnvs.includes(envSegment) ? envSegment : "prod";
2231
+ const slug = resolvedSlug.trim().toLowerCase();
2232
+ let baseUrl;
2233
+ if (input.platformUrl) {
2234
+ const platform = input.platformUrl.trim().replace(/\/$/, "");
2235
+ baseUrl = platform.includes("/v1") ? platform : `${platform}/v1`;
2236
+ } else {
2237
+ const domain = input.domain?.trim() || "api.sylphx.com";
2238
+ baseUrl = `https://${slug}.${domain}/v1`;
2239
+ }
2240
+ return freezeConfig({
2241
+ credential: trimmedCred,
2242
+ credentialType: prefix,
2243
+ env,
2244
+ slug,
2245
+ baseUrl,
2246
+ accessToken: input.accessToken
2247
+ });
2248
+ }
2249
+ throw new SylphxError(
2250
+ `[Sylphx] Invalid credential format. Expected (pk|sk)_(dev|stg|prod|prev)_[a-f0-9]{32,64}. Got: "${trimmedCred.slice(0, 30)}..."`,
2251
+ { code: "BAD_REQUEST" }
2252
+ );
2253
+ }
2254
+ function httpStatusToErrorCode(status) {
2255
+ switch (status) {
2256
+ case 400:
2257
+ return "BAD_REQUEST";
2258
+ case 401:
2259
+ return "UNAUTHORIZED";
2260
+ case 403:
2261
+ return "FORBIDDEN";
2262
+ case 404:
2263
+ return "NOT_FOUND";
2264
+ case 409:
2265
+ return "CONFLICT";
2266
+ case 413:
2267
+ return "PAYLOAD_TOO_LARGE";
2268
+ case 422:
2269
+ return "UNPROCESSABLE_ENTITY";
2270
+ case 429:
2271
+ return "TOO_MANY_REQUESTS";
2272
+ case 500:
2273
+ return "INTERNAL_SERVER_ERROR";
2274
+ case 501:
2275
+ return "NOT_IMPLEMENTED";
2276
+ case 502:
2277
+ return "BAD_GATEWAY";
2278
+ case 503:
2279
+ return "SERVICE_UNAVAILABLE";
2280
+ case 504:
2281
+ return "GATEWAY_TIMEOUT";
2282
+ default:
2283
+ return status >= 500 ? "INTERNAL_SERVER_ERROR" : "BAD_REQUEST";
2284
+ }
2285
+ }
2286
+ function buildHeaders(config) {
2287
+ const headers = {
2288
+ "Content-Type": "application/json"
2289
+ };
2290
+ if (config.credential) {
2291
+ headers["x-app-secret"] = config.credential;
2292
+ }
2293
+ if (config.accessToken) {
2294
+ headers.Authorization = `Bearer ${config.accessToken}`;
2295
+ }
2296
+ return headers;
2297
+ }
2298
+ function buildApiUrl(config, path) {
2299
+ const base = config.baseUrl.replace(/\/$/, "");
2300
+ const cleanPath = path.startsWith("/") ? path : `/${path}`;
2301
+ return `${base}${cleanPath}`;
2302
+ }
2303
+ async function callApi(config, path, options = {}) {
2304
+ const {
2305
+ method = "GET",
2306
+ body,
2307
+ query,
2308
+ timeout = DEFAULT_TIMEOUT_MS,
2309
+ signal,
2310
+ idempotencyKey,
2311
+ headers: extraHeaders
2312
+ } = options;
2313
+ let url = buildApiUrl(config, path);
2314
+ if (query) {
2315
+ const params = new URLSearchParams();
2316
+ for (const [key, value] of Object.entries(query)) {
2317
+ if (value !== void 0) {
2318
+ params.set(key, String(value));
2319
+ }
2320
+ }
2321
+ const queryString = params.toString();
2322
+ if (queryString) {
2323
+ url += `?${queryString}`;
2324
+ }
2325
+ }
2326
+ const controller = new AbortController();
2327
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
2328
+ const combinedSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
2329
+ const headers = buildHeaders(config);
2330
+ if (idempotencyKey) {
2331
+ headers["Idempotency-Key"] = idempotencyKey;
2332
+ }
2333
+ if (extraHeaders) {
2334
+ for (const [k, v] of Object.entries(extraHeaders)) {
2335
+ headers[k] = v;
2336
+ }
2337
+ }
2338
+ const fetchOptions = {
2339
+ method,
2340
+ headers,
2341
+ signal: combinedSignal
2342
+ };
2343
+ if (body) {
2344
+ fetchOptions.body = JSON.stringify(body);
2345
+ }
2346
+ let response;
2347
+ try {
2348
+ response = await fetch(url, fetchOptions);
2349
+ } catch (error) {
2350
+ clearTimeout(timeoutId);
2351
+ if (error instanceof Error) {
2352
+ if (error.name === "AbortError") {
2353
+ if (controller.signal.aborted && !signal?.aborted) {
2354
+ throw new TimeoutError(timeout);
2355
+ }
2356
+ throw new SylphxError("Request aborted", {
2357
+ code: "ABORTED",
2358
+ cause: error
2359
+ });
2360
+ }
2361
+ throw new NetworkError(error.message, { cause: error });
2362
+ }
2363
+ throw new NetworkError("Network request failed");
2364
+ } finally {
2365
+ clearTimeout(timeoutId);
2366
+ }
2367
+ if (!response.ok) {
2368
+ const errorBody = await response.text().catch(() => "");
2369
+ let errorMessage = "Request failed";
2370
+ let errorData;
2371
+ if (errorBody) {
2372
+ try {
2373
+ const parsed = JSON.parse(errorBody);
2374
+ errorMessage = parsed.error?.message ?? parsed.message ?? errorMessage;
2375
+ errorData = parsed.error;
2376
+ } catch {
2377
+ errorMessage = response.statusText || errorMessage;
2378
+ }
2379
+ }
2380
+ const errorCode = httpStatusToErrorCode(response.status);
2381
+ const retryAfterHeader = response.headers.get("Retry-After");
2382
+ const rateLimitLimit = response.headers.get("X-RateLimit-Limit");
2383
+ const rateLimitRemaining = response.headers.get("X-RateLimit-Remaining");
2384
+ const rateLimitReset = response.headers.get("X-RateLimit-Reset");
2385
+ const retryAfter = retryAfterHeader ? Number.parseInt(retryAfterHeader, 10) : void 0;
2386
+ if (response.status === 429) {
2387
+ throw new RateLimitError(errorMessage || "Too many requests", {
2388
+ status: response.status,
2389
+ data: errorData,
2390
+ retryAfter,
2391
+ limit: rateLimitLimit ? Number.parseInt(rateLimitLimit, 10) : void 0,
2392
+ remaining: rateLimitRemaining ? Number.parseInt(rateLimitRemaining, 10) : void 0,
2393
+ resetAt: rateLimitReset ? Number.parseInt(rateLimitReset, 10) : void 0
2394
+ });
2395
+ }
2396
+ throw new SylphxError(errorMessage, {
2397
+ code: errorCode,
2398
+ status: response.status,
2399
+ data: errorData,
2400
+ retryAfter
2401
+ });
2402
+ }
2403
+ const text = await response.text();
2404
+ if (!text) {
2405
+ return {};
2406
+ }
2407
+ try {
2408
+ return JSON.parse(text);
2409
+ } catch (error) {
2410
+ throw new SylphxError("Failed to parse response", {
2411
+ code: "PARSE_ERROR",
2412
+ cause: error instanceof Error ? error : void 0,
2413
+ data: { body: text.slice(0, 200) }
2414
+ });
2415
+ }
2416
+ }
2417
+
2418
+ // src/lib/functions/index.ts
2419
+ var FunctionsClient = {
2420
+ /**
2421
+ * Deploy (create or update) a function.
2422
+ *
2423
+ * If a function with the same name already exists, it is updated
2424
+ * (version number incremented, code replaced atomically).
2425
+ *
2426
+ * @example
2427
+ * ```typescript
2428
+ * const fn = await FunctionsClient.deploy(config, {
2429
+ * name: 'send-webhook',
2430
+ * code: `
2431
+ * export default async (req: Request) => {
2432
+ * const body = await req.json()
2433
+ * await fetch(body.url, { method: 'POST', body: JSON.stringify(body.payload) })
2434
+ * return Response.json({ ok: true })
2435
+ * }
2436
+ * `,
2437
+ * timeoutSeconds: 10,
2438
+ * })
2439
+ * console.log('Deployed:', fn.url)
2440
+ * ```
2441
+ */
2442
+ async deploy(config, options) {
2443
+ return callApi(config, "/functions", {
2444
+ method: "POST",
2445
+ body: options
2446
+ });
2447
+ },
2448
+ /**
2449
+ * Get a function by name.
2450
+ */
2451
+ async get(config, name) {
2452
+ return callApi(config, `/functions/${encodeURIComponent(name)}`, { method: "GET" });
2453
+ },
2454
+ /**
2455
+ * List all functions for this project.
2456
+ */
2457
+ async list(config, options = {}) {
2458
+ const params = new URLSearchParams();
2459
+ if (options.status) params.set("status", options.status);
2460
+ if (options.environment) params.set("environment", options.environment);
2461
+ if (options.limit) params.set("limit", String(options.limit));
2462
+ const qs = params.toString();
2463
+ return callApi(config, `/functions${qs ? `?${qs}` : ""}`, { method: "GET" });
2464
+ },
2465
+ /**
2466
+ * Delete a function permanently.
2467
+ */
2468
+ async delete(config, name) {
2469
+ return callApi(config, `/functions/${encodeURIComponent(name)}`, { method: "DELETE" });
2470
+ },
2471
+ /**
2472
+ * Invoke a function from server-side code.
2473
+ *
2474
+ * For client-side invocations, call the function URL directly via fetch().
2475
+ *
2476
+ * @example
2477
+ * ```typescript
2478
+ * const result = await FunctionsClient.invoke(config, 'process-order', {
2479
+ * body: { orderId: '123', userId: 'user_abc' },
2480
+ * })
2481
+ * ```
2482
+ */
2483
+ async invoke(config, name, body, options = {}) {
2484
+ return callApi(config, `/functions/${encodeURIComponent(name)}/invoke`, {
2485
+ method: options.method ?? "POST",
2486
+ body: body ?? null,
2487
+ headers: options.headers
2488
+ });
2489
+ },
2490
+ /**
2491
+ * Retrieve recent invocation logs for a function.
2492
+ *
2493
+ * @example
2494
+ * ```typescript
2495
+ * const logs = await FunctionsClient.logs(config, 'send-webhook', { errorsOnly: true })
2496
+ * for (const entry of logs) {
2497
+ * console.log(`${entry.status} ${entry.durationMs}ms`, entry.errorMessage)
2498
+ * }
2499
+ * ```
2500
+ */
2501
+ async logs(config, name, options = {}) {
2502
+ const params = new URLSearchParams();
2503
+ if (options.limit) params.set("limit", String(options.limit));
2504
+ if (options.errorsOnly) params.set("errors_only", "1");
2505
+ if (options.since) params.set("since", options.since);
2506
+ const qs = params.toString();
2507
+ return callApi(config, `/functions/${encodeURIComponent(name)}/logs${qs ? `?${qs}` : ""}`, {
2508
+ method: "GET"
2509
+ });
2510
+ }
2511
+ };
2512
+
1992
2513
  // src/lib/ids.ts
1993
2514
  var CB32 = "0123456789abcdefghjkmnpqrstvwxyz";
1994
2515
  var CB32_MAP = Object.fromEntries([...CB32].map((c, i) => [c, i]));
@@ -2148,7 +2669,7 @@ function getAI() {
2148
2669
  // src/server/kv.ts
2149
2670
  function createKv(options = {}) {
2150
2671
  const platformUrl = (options.platformUrl || `https://${DEFAULT_SDK_API_HOST}`).trim();
2151
- const secretKey = validateAndSanitizeSecretKey(resolveSecretKey(options.secretKey));
2672
+ const secretKey = validateAndSanitizeSecretKey(resolveSecretUrl(options.secretKey));
2152
2673
  const headers = {
2153
2674
  "Content-Type": "application/json",
2154
2675
  "x-app-secret": secretKey,
@@ -2395,7 +2916,7 @@ function getStreams() {
2395
2916
  }
2396
2917
 
2397
2918
  // src/server/index.ts
2398
- function createServerClient(config) {
2919
+ function createServerRestClient(config) {
2399
2920
  const secretKey = validateAndSanitizeSecretKey(config.secretKey);
2400
2921
  return createRestClient({
2401
2922
  secretKey,
@@ -2619,11 +3140,16 @@ async function cachedFetch(params) {
2619
3140
  }
2620
3141
  }
2621
3142
  function sanitizeOptions(options) {
2622
- return {
2623
- ...options,
2624
- secretKey: validateAndSanitizeSecretKey(options.secretKey),
2625
- platformUrl: (options.platformUrl ?? `https://${DEFAULT_SDK_API_HOST}`).trim()
2626
- };
3143
+ const secretKey = validateAndSanitizeSecretKey(options.secretKey);
3144
+ let platformUrl;
3145
+ if (options.platformUrl) {
3146
+ platformUrl = options.platformUrl.trim();
3147
+ } else {
3148
+ const parts = secretKey.split("_");
3149
+ const ref = parts.length === 4 ? parts[2] : null;
3150
+ platformUrl = ref ? `https://${ref}.${DEFAULT_SDK_API_HOST}` : `https://${DEFAULT_SDK_API_HOST}`;
3151
+ }
3152
+ return { ...options, secretKey, platformUrl };
2627
3153
  }
2628
3154
  function sdkHeaders(secretKey) {
2629
3155
  return { "x-app-secret": secretKey };
@@ -2683,13 +3209,20 @@ async function getAppMetadata(options) {
2683
3209
  });
2684
3210
  }
2685
3211
  async function getAppConfig(options) {
2686
- const { secretKey, appId, platformUrl } = options;
3212
+ const { secretKey, appId } = options;
3213
+ const resolvedPlatformUrl = (() => {
3214
+ if (options.platformUrl) return options.platformUrl.trim();
3215
+ const keyForRef = secretKey || appId;
3216
+ const parts = keyForRef?.split("_") ?? [];
3217
+ const ref = parts.length === 4 ? parts[2] : null;
3218
+ return ref ? `https://${ref}.${DEFAULT_SDK_API_HOST}` : `https://${DEFAULT_SDK_API_HOST}`;
3219
+ })();
2687
3220
  const [plans, consentTypes, oauthProviders, featureFlags, app] = await Promise.all([
2688
- getPlans({ secretKey, platformUrl }),
2689
- getConsentTypes({ secretKey, platformUrl }),
2690
- getOAuthProvidersWithInfo({ appId, platformUrl }),
2691
- getFeatureFlags({ secretKey, platformUrl }),
2692
- getAppMetadata({ secretKey, platformUrl })
3221
+ getPlans({ secretKey, platformUrl: resolvedPlatformUrl }),
3222
+ getConsentTypes({ secretKey, platformUrl: resolvedPlatformUrl }),
3223
+ getOAuthProvidersWithInfo({ appId, platformUrl: resolvedPlatformUrl }),
3224
+ getFeatureFlags({ secretKey, platformUrl: resolvedPlatformUrl }),
3225
+ getAppMetadata({ secretKey, platformUrl: resolvedPlatformUrl })
2693
3226
  ]);
2694
3227
  return {
2695
3228
  plans,
@@ -2782,10 +3315,14 @@ async function getDatabaseStatus(options) {
2782
3315
  }
2783
3316
  // Annotate the CommonJS export names for ESM import in node:
2784
3317
  0 && (module.exports = {
3318
+ FunctionsClient,
3319
+ InvalidConnectionUrlError,
2785
3320
  createAI,
2786
3321
  createAuthenticatedServerClient,
3322
+ createClient,
2787
3323
  createKv,
2788
3324
  createServerClient,
3325
+ createServerRestClient,
2789
3326
  createStreams,
2790
3327
  createWebhookHandler,
2791
3328
  decodeUserId,