@sylphx/sdk 0.4.0 → 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.
@@ -1126,13 +1126,13 @@ async function jwtVerify(jwt, key, options) {
1126
1126
  }
1127
1127
 
1128
1128
  // src/constants.ts
1129
- var ENV_SECRET_KEY = "SYLPHX_SECRET_KEY";
1130
- function resolveSecretKey(explicit) {
1131
- return explicit || process.env[ENV_SECRET_KEY];
1129
+ var ENV_SECRET_URL = "SYLPHX_SECRET_URL";
1130
+ function resolveSecretUrl(explicit) {
1131
+ return explicit || process.env[ENV_SECRET_URL];
1132
1132
  }
1133
1133
  var SDK_API_PATH = `/v1`;
1134
1134
  var DEFAULT_SDK_API_HOST = "api.sylphx.com";
1135
- var SDK_VERSION = "0.1.0";
1135
+ var SDK_VERSION = "0.5.0";
1136
1136
  var SDK_PLATFORM = typeof window !== "undefined" ? "browser" : typeof process !== "undefined" && process.versions?.node ? "node" : "unknown";
1137
1137
  var DEFAULT_TIMEOUT_MS = 3e4;
1138
1138
  var SESSION_TOKEN_LIFETIME_SECONDS = 5 * 60;
@@ -1303,6 +1303,24 @@ var SylphxError = class _SylphxError extends Error {
1303
1303
  };
1304
1304
  }
1305
1305
  };
1306
+ var NetworkError = class extends SylphxError {
1307
+ constructor(message2 = "Network request failed", options) {
1308
+ super(message2, { ...options, code: "NETWORK_ERROR" });
1309
+ this.name = "NetworkError";
1310
+ }
1311
+ };
1312
+ var TimeoutError = class extends SylphxError {
1313
+ /** Timeout duration in milliseconds */
1314
+ timeout;
1315
+ constructor(timeout, options) {
1316
+ super(`Request timed out after ${timeout}ms`, {
1317
+ ...options,
1318
+ code: "TIMEOUT"
1319
+ });
1320
+ this.name = "TimeoutError";
1321
+ this.timeout = timeout;
1322
+ }
1323
+ };
1306
1324
  var RateLimitError = class extends SylphxError {
1307
1325
  /** Maximum requests allowed in window */
1308
1326
  limit;
@@ -1346,7 +1364,7 @@ function exponentialBackoff(attempt, baseDelay = BASE_RETRY_DELAY_MS, maxDelay =
1346
1364
 
1347
1365
  // src/key-validation.ts
1348
1366
  var PUBLIC_KEY_PATTERN = /^pk_(dev|stg|prod)_[a-z0-9]{12}_[a-f0-9]{32}$/;
1349
- var APP_ID_PATTERN = /^app_(dev|stg|prod)_[a-z0-9_-]+$/;
1367
+ var APP_ID_PATTERN = /^(app|pk)_(dev|stg|prod|prev)_[a-z0-9_-]+$/;
1350
1368
  var SECRET_KEY_PATTERN = /^sk_(dev|stg|prod)_[a-z0-9_-]+$/;
1351
1369
  var ENV_PREFIX_MAP = {
1352
1370
  dev: "development",
@@ -1375,9 +1393,8 @@ To fix permanently:
1375
1393
  The SDK will automatically sanitize the key, but fixing the source is recommended.`;
1376
1394
  }
1377
1395
  function createInvalidKeyError(keyType, key, envVarName) {
1378
- const prefix = keyType === "appId" ? "app" : "sk";
1379
1396
  const maskedKey = key.length > 20 ? `${key.slice(0, 20)}...` : key;
1380
- const formatHint = `${prefix}_(dev|stg|prod)_[identifier]`;
1397
+ const formatHint = keyType === "appId" ? "pk_(dev|stg|prod)_{ref}_{hex} or app_(dev|stg|prod)_[id]" : "sk_(dev|stg|prod)_{ref}_{hex}";
1381
1398
  const keyTypeName = keyType === "appId" ? "App ID" : "Secret Key";
1382
1399
  return `[Sylphx] Invalid ${keyTypeName} format.
1383
1400
 
@@ -1389,12 +1406,12 @@ You can find your keys in the Sylphx Console \u2192 API Keys.
1389
1406
 
1390
1407
  Common issues:
1391
1408
  \u2022 Key has uppercase characters (must be lowercase)
1392
- \u2022 Key has wrong prefix (App ID: app_, Secret Key: sk_)
1409
+ \u2022 Key has wrong prefix (App ID: pk_ or app_, Secret Key: sk_)
1393
1410
  \u2022 Key has invalid environment (must be dev, stg, or prod)
1394
1411
  \u2022 Key was copied with extra whitespace`;
1395
1412
  }
1396
1413
  function extractEnvironment(key) {
1397
- const match = key.match(/^(?:app|sk)_(dev|stg|prod)_/);
1414
+ const match = key.match(/^(?:app|pk|sk)_(dev|stg|prod|prev)_/);
1398
1415
  if (!match) return void 0;
1399
1416
  return ENV_PREFIX_MAP[match[1]];
1400
1417
  }
@@ -1921,6 +1938,499 @@ function createDynamicRestClient(config) {
1921
1938
  return client;
1922
1939
  }
1923
1940
 
1941
+ // src/connection-url.ts
1942
+ var SYLPHX_PROTOCOL = "sylphx:";
1943
+ var DEFAULT_VERSION = "v1";
1944
+ var CREDENTIAL_REGEX = /^(pk|sk)_(dev|stg|prod|prev)_[a-f0-9]{32,64}$/;
1945
+ var VERSION_REGEX = /^v[0-9]+$/;
1946
+ var SLUG_REGEX = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
1947
+ var InvalidConnectionUrlError = class _InvalidConnectionUrlError extends Error {
1948
+ code = "INVALID_CONNECTION_URL";
1949
+ constructor(message2) {
1950
+ super(message2);
1951
+ this.name = "InvalidConnectionUrlError";
1952
+ Object.setPrototypeOf(this, _InvalidConnectionUrlError.prototype);
1953
+ }
1954
+ };
1955
+ function fail(reason) {
1956
+ throw new InvalidConnectionUrlError(`Invalid Sylphx connection URL: ${reason}`);
1957
+ }
1958
+ function parseCredential(raw) {
1959
+ const match = CREDENTIAL_REGEX.exec(raw);
1960
+ if (!match) {
1961
+ fail(`credential must match (pk|sk)_(dev|stg|prod|prev)_[a-f0-9]{32,64}, got "${raw}"`);
1962
+ }
1963
+ return {
1964
+ credentialType: match[1],
1965
+ env: match[2]
1966
+ };
1967
+ }
1968
+ function validateSlug(candidate) {
1969
+ if (!candidate || candidate.length > 63 || !SLUG_REGEX.test(candidate)) {
1970
+ fail(`slug "${candidate}" is not a valid DNS label (lowercase alnum + hyphens, 1-63 chars)`);
1971
+ }
1972
+ return candidate;
1973
+ }
1974
+ function parseConnectionUrl(url) {
1975
+ if (typeof url !== "string" || url.length === 0) {
1976
+ fail("url must be a non-empty string");
1977
+ }
1978
+ let parsed;
1979
+ try {
1980
+ parsed = new URL(url);
1981
+ } catch {
1982
+ fail(`not a valid URL: "${url}"`);
1983
+ }
1984
+ if (parsed.protocol !== SYLPHX_PROTOCOL) {
1985
+ fail(`protocol must be "sylphx:", got "${parsed.protocol}"`);
1986
+ }
1987
+ const credential = decodeURIComponent(parsed.username);
1988
+ if (!credential) {
1989
+ fail("missing credential (expected `sylphx://<credential>@<host>`)");
1990
+ }
1991
+ if (parsed.password) {
1992
+ fail("connection URL must not contain a password component");
1993
+ }
1994
+ const { credentialType, env } = parseCredential(credential);
1995
+ const host = parsed.host;
1996
+ if (!host) {
1997
+ fail("missing host");
1998
+ }
1999
+ const hostname = parsed.hostname;
2000
+ const firstDot = hostname.indexOf(".");
2001
+ if (firstDot <= 0) {
2002
+ fail(`host "${hostname}" must contain at least one dot (slug.domain)`);
2003
+ }
2004
+ const slugCandidate = hostname.slice(0, firstDot);
2005
+ const domainSuffix = hostname.slice(firstDot + 1);
2006
+ if (!domainSuffix) {
2007
+ fail(`host "${hostname}" has empty domain suffix`);
2008
+ }
2009
+ const slug = validateSlug(slugCandidate);
2010
+ const rawPath = parsed.pathname.replace(/^\/+/, "").replace(/\/+$/, "");
2011
+ let version = DEFAULT_VERSION;
2012
+ if (rawPath !== "") {
2013
+ if (!VERSION_REGEX.test(rawPath)) {
2014
+ fail(`path "${parsed.pathname}" must be empty or match /v{N}`);
2015
+ }
2016
+ version = rawPath;
2017
+ }
2018
+ if (parsed.search) {
2019
+ fail("connection URL must not contain a query string");
2020
+ }
2021
+ if (parsed.hash) {
2022
+ fail("connection URL must not contain a fragment");
2023
+ }
2024
+ const apiBaseUrl = `https://${host}/${version}`;
2025
+ return {
2026
+ credential,
2027
+ credentialType,
2028
+ env,
2029
+ slug,
2030
+ host,
2031
+ apiBaseUrl
2032
+ };
2033
+ }
2034
+
2035
+ // src/config.ts
2036
+ var LEGACY_EMBEDDED_REF_PATTERN = /^(pk|sk)_(dev|stg|prod|prev)_[a-z0-9]{12}_[a-f0-9]+$/;
2037
+ var LEGACY_APP_KEY_PATTERN = /^app_(dev|stg|prod|prev)_/;
2038
+ 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.";
2039
+ function rejectLegacyKeyFormat(input) {
2040
+ const trimmed = input.trim().toLowerCase();
2041
+ if (LEGACY_APP_KEY_PATTERN.test(trimmed)) {
2042
+ throw new SylphxError(`[Sylphx] ${MIGRATION_MESSAGE}`, { code: "BAD_REQUEST" });
2043
+ }
2044
+ if (LEGACY_EMBEDDED_REF_PATTERN.test(trimmed)) {
2045
+ throw new SylphxError(`[Sylphx] ${MIGRATION_MESSAGE}`, { code: "BAD_REQUEST" });
2046
+ }
2047
+ }
2048
+ function freezeConfig(opts) {
2049
+ return Object.freeze({
2050
+ credential: opts.credential,
2051
+ credentialType: opts.credentialType,
2052
+ env: opts.env,
2053
+ slug: opts.slug,
2054
+ baseUrl: opts.baseUrl,
2055
+ accessToken: opts.accessToken,
2056
+ // Backward-compat aliases
2057
+ secretKey: opts.credentialType === "sk" ? opts.credential : void 0,
2058
+ publicKey: opts.credentialType === "pk" ? opts.credential : void 0,
2059
+ ref: opts.slug
2060
+ });
2061
+ }
2062
+ function createClient2(input) {
2063
+ if (typeof input === "string") {
2064
+ return createConfigFromUrl(input);
2065
+ }
2066
+ return createConfigFromComponents(input);
2067
+ }
2068
+ function createServerClient(input) {
2069
+ const config = createClient2(input);
2070
+ if (config.credentialType !== "sk") {
2071
+ throw new SylphxError(
2072
+ "[Sylphx] createServerClient() requires a secret key (sk_*). Use a SYLPHX_SECRET_URL with an sk_ credential, or pass { secretKey } in the components object.",
2073
+ { code: "BAD_REQUEST" }
2074
+ );
2075
+ }
2076
+ return config;
2077
+ }
2078
+ function createConfigFromUrl(url) {
2079
+ if (!url || typeof url !== "string") {
2080
+ throw new SylphxError(
2081
+ "[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",
2082
+ { code: "BAD_REQUEST" }
2083
+ );
2084
+ }
2085
+ const trimmed = url.trim();
2086
+ rejectLegacyKeyFormat(trimmed);
2087
+ if (!trimmed.startsWith("sylphx://")) {
2088
+ if (CREDENTIAL_REGEX.test(trimmed)) {
2089
+ throw new SylphxError(
2090
+ "[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.",
2091
+ { code: "BAD_REQUEST" }
2092
+ );
2093
+ }
2094
+ throw new SylphxError(
2095
+ `[Sylphx] Invalid connection URL \u2014 must start with "sylphx://". Got: "${trimmed.slice(0, 30)}..."`,
2096
+ { code: "BAD_REQUEST" }
2097
+ );
2098
+ }
2099
+ let parsed;
2100
+ try {
2101
+ parsed = parseConnectionUrl(trimmed);
2102
+ } catch (err) {
2103
+ if (err instanceof InvalidConnectionUrlError) {
2104
+ throw new SylphxError(err.message, { code: "BAD_REQUEST", cause: err });
2105
+ }
2106
+ throw err;
2107
+ }
2108
+ return freezeConfig({
2109
+ credential: parsed.credential,
2110
+ credentialType: parsed.credentialType,
2111
+ env: parsed.env,
2112
+ slug: parsed.slug,
2113
+ baseUrl: parsed.apiBaseUrl
2114
+ });
2115
+ }
2116
+ function createConfigFromComponents(input) {
2117
+ const credential = input.secretKey || input.publicKey;
2118
+ if (!credential) {
2119
+ throw new SylphxError("[Sylphx] Either publicKey or secretKey must be provided.", {
2120
+ code: "BAD_REQUEST"
2121
+ });
2122
+ }
2123
+ const resolvedSlug = input.slug || input.ref;
2124
+ if (!resolvedSlug) {
2125
+ throw new SylphxError("[Sylphx] slug is required when using explicit components.", {
2126
+ code: "BAD_REQUEST"
2127
+ });
2128
+ }
2129
+ const trimmedCred = credential.trim().toLowerCase();
2130
+ if (CREDENTIAL_REGEX.test(trimmedCred)) {
2131
+ const match = CREDENTIAL_REGEX.exec(trimmedCred);
2132
+ const credentialType = match[1];
2133
+ const env = match[2];
2134
+ const slug = resolvedSlug.trim().toLowerCase();
2135
+ const domain = input.domain?.trim() || "sylphx.com";
2136
+ const baseUrl = `https://${slug}.${domain}/v1`;
2137
+ return freezeConfig({
2138
+ credential: trimmedCred,
2139
+ credentialType,
2140
+ env,
2141
+ slug,
2142
+ baseUrl,
2143
+ accessToken: input.accessToken
2144
+ });
2145
+ }
2146
+ const parts = trimmedCred.split("_");
2147
+ const prefix = parts[0];
2148
+ if ((prefix === "pk" || prefix === "sk") && parts.length >= 3) {
2149
+ const envSegment = parts[1];
2150
+ const validEnvs = ["dev", "stg", "prod", "prev"];
2151
+ const env = validEnvs.includes(envSegment) ? envSegment : "prod";
2152
+ const slug = resolvedSlug.trim().toLowerCase();
2153
+ let baseUrl;
2154
+ if (input.platformUrl) {
2155
+ const platform = input.platformUrl.trim().replace(/\/$/, "");
2156
+ baseUrl = platform.includes("/v1") ? platform : `${platform}/v1`;
2157
+ } else {
2158
+ const domain = input.domain?.trim() || "api.sylphx.com";
2159
+ baseUrl = `https://${slug}.${domain}/v1`;
2160
+ }
2161
+ return freezeConfig({
2162
+ credential: trimmedCred,
2163
+ credentialType: prefix,
2164
+ env,
2165
+ slug,
2166
+ baseUrl,
2167
+ accessToken: input.accessToken
2168
+ });
2169
+ }
2170
+ throw new SylphxError(
2171
+ `[Sylphx] Invalid credential format. Expected (pk|sk)_(dev|stg|prod|prev)_[a-f0-9]{32,64}. Got: "${trimmedCred.slice(0, 30)}..."`,
2172
+ { code: "BAD_REQUEST" }
2173
+ );
2174
+ }
2175
+ function httpStatusToErrorCode(status) {
2176
+ switch (status) {
2177
+ case 400:
2178
+ return "BAD_REQUEST";
2179
+ case 401:
2180
+ return "UNAUTHORIZED";
2181
+ case 403:
2182
+ return "FORBIDDEN";
2183
+ case 404:
2184
+ return "NOT_FOUND";
2185
+ case 409:
2186
+ return "CONFLICT";
2187
+ case 413:
2188
+ return "PAYLOAD_TOO_LARGE";
2189
+ case 422:
2190
+ return "UNPROCESSABLE_ENTITY";
2191
+ case 429:
2192
+ return "TOO_MANY_REQUESTS";
2193
+ case 500:
2194
+ return "INTERNAL_SERVER_ERROR";
2195
+ case 501:
2196
+ return "NOT_IMPLEMENTED";
2197
+ case 502:
2198
+ return "BAD_GATEWAY";
2199
+ case 503:
2200
+ return "SERVICE_UNAVAILABLE";
2201
+ case 504:
2202
+ return "GATEWAY_TIMEOUT";
2203
+ default:
2204
+ return status >= 500 ? "INTERNAL_SERVER_ERROR" : "BAD_REQUEST";
2205
+ }
2206
+ }
2207
+ function buildHeaders(config) {
2208
+ const headers = {
2209
+ "Content-Type": "application/json"
2210
+ };
2211
+ if (config.credential) {
2212
+ headers["x-app-secret"] = config.credential;
2213
+ }
2214
+ if (config.accessToken) {
2215
+ headers.Authorization = `Bearer ${config.accessToken}`;
2216
+ }
2217
+ return headers;
2218
+ }
2219
+ function buildApiUrl(config, path) {
2220
+ const base = config.baseUrl.replace(/\/$/, "");
2221
+ const cleanPath = path.startsWith("/") ? path : `/${path}`;
2222
+ return `${base}${cleanPath}`;
2223
+ }
2224
+ async function callApi(config, path, options = {}) {
2225
+ const {
2226
+ method = "GET",
2227
+ body,
2228
+ query,
2229
+ timeout = DEFAULT_TIMEOUT_MS,
2230
+ signal,
2231
+ idempotencyKey,
2232
+ headers: extraHeaders
2233
+ } = options;
2234
+ let url = buildApiUrl(config, path);
2235
+ if (query) {
2236
+ const params = new URLSearchParams();
2237
+ for (const [key, value] of Object.entries(query)) {
2238
+ if (value !== void 0) {
2239
+ params.set(key, String(value));
2240
+ }
2241
+ }
2242
+ const queryString = params.toString();
2243
+ if (queryString) {
2244
+ url += `?${queryString}`;
2245
+ }
2246
+ }
2247
+ const controller = new AbortController();
2248
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
2249
+ const combinedSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
2250
+ const headers = buildHeaders(config);
2251
+ if (idempotencyKey) {
2252
+ headers["Idempotency-Key"] = idempotencyKey;
2253
+ }
2254
+ if (extraHeaders) {
2255
+ for (const [k, v] of Object.entries(extraHeaders)) {
2256
+ headers[k] = v;
2257
+ }
2258
+ }
2259
+ const fetchOptions = {
2260
+ method,
2261
+ headers,
2262
+ signal: combinedSignal
2263
+ };
2264
+ if (body) {
2265
+ fetchOptions.body = JSON.stringify(body);
2266
+ }
2267
+ let response;
2268
+ try {
2269
+ response = await fetch(url, fetchOptions);
2270
+ } catch (error) {
2271
+ clearTimeout(timeoutId);
2272
+ if (error instanceof Error) {
2273
+ if (error.name === "AbortError") {
2274
+ if (controller.signal.aborted && !signal?.aborted) {
2275
+ throw new TimeoutError(timeout);
2276
+ }
2277
+ throw new SylphxError("Request aborted", {
2278
+ code: "ABORTED",
2279
+ cause: error
2280
+ });
2281
+ }
2282
+ throw new NetworkError(error.message, { cause: error });
2283
+ }
2284
+ throw new NetworkError("Network request failed");
2285
+ } finally {
2286
+ clearTimeout(timeoutId);
2287
+ }
2288
+ if (!response.ok) {
2289
+ const errorBody = await response.text().catch(() => "");
2290
+ let errorMessage = "Request failed";
2291
+ let errorData;
2292
+ if (errorBody) {
2293
+ try {
2294
+ const parsed = JSON.parse(errorBody);
2295
+ errorMessage = parsed.error?.message ?? parsed.message ?? errorMessage;
2296
+ errorData = parsed.error;
2297
+ } catch {
2298
+ errorMessage = response.statusText || errorMessage;
2299
+ }
2300
+ }
2301
+ const errorCode = httpStatusToErrorCode(response.status);
2302
+ const retryAfterHeader = response.headers.get("Retry-After");
2303
+ const rateLimitLimit = response.headers.get("X-RateLimit-Limit");
2304
+ const rateLimitRemaining = response.headers.get("X-RateLimit-Remaining");
2305
+ const rateLimitReset = response.headers.get("X-RateLimit-Reset");
2306
+ const retryAfter = retryAfterHeader ? Number.parseInt(retryAfterHeader, 10) : void 0;
2307
+ if (response.status === 429) {
2308
+ throw new RateLimitError(errorMessage || "Too many requests", {
2309
+ status: response.status,
2310
+ data: errorData,
2311
+ retryAfter,
2312
+ limit: rateLimitLimit ? Number.parseInt(rateLimitLimit, 10) : void 0,
2313
+ remaining: rateLimitRemaining ? Number.parseInt(rateLimitRemaining, 10) : void 0,
2314
+ resetAt: rateLimitReset ? Number.parseInt(rateLimitReset, 10) : void 0
2315
+ });
2316
+ }
2317
+ throw new SylphxError(errorMessage, {
2318
+ code: errorCode,
2319
+ status: response.status,
2320
+ data: errorData,
2321
+ retryAfter
2322
+ });
2323
+ }
2324
+ const text = await response.text();
2325
+ if (!text) {
2326
+ return {};
2327
+ }
2328
+ try {
2329
+ return JSON.parse(text);
2330
+ } catch (error) {
2331
+ throw new SylphxError("Failed to parse response", {
2332
+ code: "PARSE_ERROR",
2333
+ cause: error instanceof Error ? error : void 0,
2334
+ data: { body: text.slice(0, 200) }
2335
+ });
2336
+ }
2337
+ }
2338
+
2339
+ // src/lib/functions/index.ts
2340
+ var FunctionsClient = {
2341
+ /**
2342
+ * Deploy (create or update) a function.
2343
+ *
2344
+ * If a function with the same name already exists, it is updated
2345
+ * (version number incremented, code replaced atomically).
2346
+ *
2347
+ * @example
2348
+ * ```typescript
2349
+ * const fn = await FunctionsClient.deploy(config, {
2350
+ * name: 'send-webhook',
2351
+ * code: `
2352
+ * export default async (req: Request) => {
2353
+ * const body = await req.json()
2354
+ * await fetch(body.url, { method: 'POST', body: JSON.stringify(body.payload) })
2355
+ * return Response.json({ ok: true })
2356
+ * }
2357
+ * `,
2358
+ * timeoutSeconds: 10,
2359
+ * })
2360
+ * console.log('Deployed:', fn.url)
2361
+ * ```
2362
+ */
2363
+ async deploy(config, options) {
2364
+ return callApi(config, "/functions", {
2365
+ method: "POST",
2366
+ body: options
2367
+ });
2368
+ },
2369
+ /**
2370
+ * Get a function by name.
2371
+ */
2372
+ async get(config, name) {
2373
+ return callApi(config, `/functions/${encodeURIComponent(name)}`, { method: "GET" });
2374
+ },
2375
+ /**
2376
+ * List all functions for this project.
2377
+ */
2378
+ async list(config, options = {}) {
2379
+ const params = new URLSearchParams();
2380
+ if (options.status) params.set("status", options.status);
2381
+ if (options.environment) params.set("environment", options.environment);
2382
+ if (options.limit) params.set("limit", String(options.limit));
2383
+ const qs = params.toString();
2384
+ return callApi(config, `/functions${qs ? `?${qs}` : ""}`, { method: "GET" });
2385
+ },
2386
+ /**
2387
+ * Delete a function permanently.
2388
+ */
2389
+ async delete(config, name) {
2390
+ return callApi(config, `/functions/${encodeURIComponent(name)}`, { method: "DELETE" });
2391
+ },
2392
+ /**
2393
+ * Invoke a function from server-side code.
2394
+ *
2395
+ * For client-side invocations, call the function URL directly via fetch().
2396
+ *
2397
+ * @example
2398
+ * ```typescript
2399
+ * const result = await FunctionsClient.invoke(config, 'process-order', {
2400
+ * body: { orderId: '123', userId: 'user_abc' },
2401
+ * })
2402
+ * ```
2403
+ */
2404
+ async invoke(config, name, body, options = {}) {
2405
+ return callApi(config, `/functions/${encodeURIComponent(name)}/invoke`, {
2406
+ method: options.method ?? "POST",
2407
+ body: body ?? null,
2408
+ headers: options.headers
2409
+ });
2410
+ },
2411
+ /**
2412
+ * Retrieve recent invocation logs for a function.
2413
+ *
2414
+ * @example
2415
+ * ```typescript
2416
+ * const logs = await FunctionsClient.logs(config, 'send-webhook', { errorsOnly: true })
2417
+ * for (const entry of logs) {
2418
+ * console.log(`${entry.status} ${entry.durationMs}ms`, entry.errorMessage)
2419
+ * }
2420
+ * ```
2421
+ */
2422
+ async logs(config, name, options = {}) {
2423
+ const params = new URLSearchParams();
2424
+ if (options.limit) params.set("limit", String(options.limit));
2425
+ if (options.errorsOnly) params.set("errors_only", "1");
2426
+ if (options.since) params.set("since", options.since);
2427
+ const qs = params.toString();
2428
+ return callApi(config, `/functions/${encodeURIComponent(name)}/logs${qs ? `?${qs}` : ""}`, {
2429
+ method: "GET"
2430
+ });
2431
+ }
2432
+ };
2433
+
1924
2434
  // src/lib/ids.ts
1925
2435
  var CB32 = "0123456789abcdefghjkmnpqrstvwxyz";
1926
2436
  var CB32_MAP = Object.fromEntries([...CB32].map((c, i) => [c, i]));
@@ -2080,7 +2590,7 @@ function getAI() {
2080
2590
  // src/server/kv.ts
2081
2591
  function createKv(options = {}) {
2082
2592
  const platformUrl = (options.platformUrl || `https://${DEFAULT_SDK_API_HOST}`).trim();
2083
- const secretKey = validateAndSanitizeSecretKey(resolveSecretKey(options.secretKey));
2593
+ const secretKey = validateAndSanitizeSecretKey(resolveSecretUrl(options.secretKey));
2084
2594
  const headers = {
2085
2595
  "Content-Type": "application/json",
2086
2596
  "x-app-secret": secretKey,
@@ -2327,7 +2837,7 @@ function getStreams() {
2327
2837
  }
2328
2838
 
2329
2839
  // src/server/index.ts
2330
- function createServerClient(config) {
2840
+ function createServerRestClient(config) {
2331
2841
  const secretKey = validateAndSanitizeSecretKey(config.secretKey);
2332
2842
  return createRestClient({
2333
2843
  secretKey,
@@ -2725,10 +3235,14 @@ async function getDatabaseStatus(options) {
2725
3235
  });
2726
3236
  }
2727
3237
  export {
3238
+ FunctionsClient,
3239
+ InvalidConnectionUrlError,
2728
3240
  createAI,
2729
3241
  createAuthenticatedServerClient,
3242
+ createClient2 as createClient,
2730
3243
  createKv,
2731
3244
  createServerClient,
3245
+ createServerRestClient,
2732
3246
  createStreams,
2733
3247
  createWebhookHandler,
2734
3248
  decodeUserId,