@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.
- package/dist/index.d.cts +938 -140
- package/dist/index.d.ts +938 -140
- package/dist/index.js +810 -267
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +791 -269
- package/dist/index.mjs.map +1 -1
- package/dist/nextjs/index.js +44 -20
- package/dist/nextjs/index.js.map +1 -1
- package/dist/nextjs/index.mjs +44 -20
- package/dist/nextjs/index.mjs.map +1 -1
- package/dist/react/index.d.cts +389 -32
- package/dist/react/index.d.ts +389 -32
- package/dist/react/index.js +1610 -1285
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +1284 -963
- package/dist/react/index.mjs.map +1 -1
- package/dist/server/index.d.cts +355 -18
- package/dist/server/index.d.ts +355 -18
- package/dist/server/index.js +559 -22
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +555 -22
- package/dist/server/index.mjs.map +1 -1
- package/dist/web-analytics.js.map +1 -1
- package/dist/web-analytics.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/web-analytics.d.cts +0 -90
- package/dist/web-analytics.d.ts +0 -90
package/dist/server/index.mjs
CHANGED
|
@@ -1126,13 +1126,13 @@ async function jwtVerify(jwt, key, options) {
|
|
|
1126
1126
|
}
|
|
1127
1127
|
|
|
1128
1128
|
// src/constants.ts
|
|
1129
|
-
var
|
|
1130
|
-
function
|
|
1131
|
-
return explicit || process.env[
|
|
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.
|
|
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;
|
|
@@ -1238,6 +1238,13 @@ var SylphxError = class _SylphxError extends Error {
|
|
|
1238
1238
|
static isRateLimited(err) {
|
|
1239
1239
|
return err instanceof _SylphxError && err.code === "TOO_MANY_REQUESTS";
|
|
1240
1240
|
}
|
|
1241
|
+
/**
|
|
1242
|
+
* Check if error is an account lockout error (too many failed login attempts).
|
|
1243
|
+
* When true, `error.data?.lockoutUntil` contains the ISO 8601 timestamp when the lockout expires.
|
|
1244
|
+
*/
|
|
1245
|
+
static isAccountLocked(err) {
|
|
1246
|
+
return err instanceof _SylphxError && err.code === "TOO_MANY_REQUESTS" && err.data?.code === "ACCOUNT_LOCKED";
|
|
1247
|
+
}
|
|
1241
1248
|
/**
|
|
1242
1249
|
* Check if error is a quota exceeded error (plan limit reached)
|
|
1243
1250
|
*/
|
|
@@ -1296,6 +1303,24 @@ var SylphxError = class _SylphxError extends Error {
|
|
|
1296
1303
|
};
|
|
1297
1304
|
}
|
|
1298
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
|
+
};
|
|
1299
1324
|
var RateLimitError = class extends SylphxError {
|
|
1300
1325
|
/** Maximum requests allowed in window */
|
|
1301
1326
|
limit;
|
|
@@ -1339,7 +1364,7 @@ function exponentialBackoff(attempt, baseDelay = BASE_RETRY_DELAY_MS, maxDelay =
|
|
|
1339
1364
|
|
|
1340
1365
|
// src/key-validation.ts
|
|
1341
1366
|
var PUBLIC_KEY_PATTERN = /^pk_(dev|stg|prod)_[a-z0-9]{12}_[a-f0-9]{32}$/;
|
|
1342
|
-
var APP_ID_PATTERN = /^
|
|
1367
|
+
var APP_ID_PATTERN = /^(app|pk)_(dev|stg|prod|prev)_[a-z0-9_-]+$/;
|
|
1343
1368
|
var SECRET_KEY_PATTERN = /^sk_(dev|stg|prod)_[a-z0-9_-]+$/;
|
|
1344
1369
|
var ENV_PREFIX_MAP = {
|
|
1345
1370
|
dev: "development",
|
|
@@ -1368,9 +1393,8 @@ To fix permanently:
|
|
|
1368
1393
|
The SDK will automatically sanitize the key, but fixing the source is recommended.`;
|
|
1369
1394
|
}
|
|
1370
1395
|
function createInvalidKeyError(keyType, key, envVarName) {
|
|
1371
|
-
const prefix = keyType === "appId" ? "app" : "sk";
|
|
1372
1396
|
const maskedKey = key.length > 20 ? `${key.slice(0, 20)}...` : key;
|
|
1373
|
-
const formatHint =
|
|
1397
|
+
const formatHint = keyType === "appId" ? "pk_(dev|stg|prod)_{ref}_{hex} or app_(dev|stg|prod)_[id]" : "sk_(dev|stg|prod)_{ref}_{hex}";
|
|
1374
1398
|
const keyTypeName = keyType === "appId" ? "App ID" : "Secret Key";
|
|
1375
1399
|
return `[Sylphx] Invalid ${keyTypeName} format.
|
|
1376
1400
|
|
|
@@ -1382,12 +1406,12 @@ You can find your keys in the Sylphx Console \u2192 API Keys.
|
|
|
1382
1406
|
|
|
1383
1407
|
Common issues:
|
|
1384
1408
|
\u2022 Key has uppercase characters (must be lowercase)
|
|
1385
|
-
\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_)
|
|
1386
1410
|
\u2022 Key has invalid environment (must be dev, stg, or prod)
|
|
1387
1411
|
\u2022 Key was copied with extra whitespace`;
|
|
1388
1412
|
}
|
|
1389
1413
|
function extractEnvironment(key) {
|
|
1390
|
-
const match = key.match(/^(?:app|sk)_(dev|stg|prod)_/);
|
|
1414
|
+
const match = key.match(/^(?:app|pk|sk)_(dev|stg|prod|prev)_/);
|
|
1391
1415
|
if (!match) return void 0;
|
|
1392
1416
|
return ENV_PREFIX_MAP[match[1]];
|
|
1393
1417
|
}
|
|
@@ -1914,6 +1938,499 @@ function createDynamicRestClient(config) {
|
|
|
1914
1938
|
return client;
|
|
1915
1939
|
}
|
|
1916
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
|
+
|
|
1917
2434
|
// src/lib/ids.ts
|
|
1918
2435
|
var CB32 = "0123456789abcdefghjkmnpqrstvwxyz";
|
|
1919
2436
|
var CB32_MAP = Object.fromEntries([...CB32].map((c, i) => [c, i]));
|
|
@@ -2073,7 +2590,7 @@ function getAI() {
|
|
|
2073
2590
|
// src/server/kv.ts
|
|
2074
2591
|
function createKv(options = {}) {
|
|
2075
2592
|
const platformUrl = (options.platformUrl || `https://${DEFAULT_SDK_API_HOST}`).trim();
|
|
2076
|
-
const secretKey = validateAndSanitizeSecretKey(
|
|
2593
|
+
const secretKey = validateAndSanitizeSecretKey(resolveSecretUrl(options.secretKey));
|
|
2077
2594
|
const headers = {
|
|
2078
2595
|
"Content-Type": "application/json",
|
|
2079
2596
|
"x-app-secret": secretKey,
|
|
@@ -2320,7 +2837,7 @@ function getStreams() {
|
|
|
2320
2837
|
}
|
|
2321
2838
|
|
|
2322
2839
|
// src/server/index.ts
|
|
2323
|
-
function
|
|
2840
|
+
function createServerRestClient(config) {
|
|
2324
2841
|
const secretKey = validateAndSanitizeSecretKey(config.secretKey);
|
|
2325
2842
|
return createRestClient({
|
|
2326
2843
|
secretKey,
|
|
@@ -2544,11 +3061,16 @@ async function cachedFetch(params) {
|
|
|
2544
3061
|
}
|
|
2545
3062
|
}
|
|
2546
3063
|
function sanitizeOptions(options) {
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
platformUrl
|
|
2551
|
-
}
|
|
3064
|
+
const secretKey = validateAndSanitizeSecretKey(options.secretKey);
|
|
3065
|
+
let platformUrl;
|
|
3066
|
+
if (options.platformUrl) {
|
|
3067
|
+
platformUrl = options.platformUrl.trim();
|
|
3068
|
+
} else {
|
|
3069
|
+
const parts = secretKey.split("_");
|
|
3070
|
+
const ref = parts.length === 4 ? parts[2] : null;
|
|
3071
|
+
platformUrl = ref ? `https://${ref}.${DEFAULT_SDK_API_HOST}` : `https://${DEFAULT_SDK_API_HOST}`;
|
|
3072
|
+
}
|
|
3073
|
+
return { ...options, secretKey, platformUrl };
|
|
2552
3074
|
}
|
|
2553
3075
|
function sdkHeaders(secretKey) {
|
|
2554
3076
|
return { "x-app-secret": secretKey };
|
|
@@ -2608,13 +3130,20 @@ async function getAppMetadata(options) {
|
|
|
2608
3130
|
});
|
|
2609
3131
|
}
|
|
2610
3132
|
async function getAppConfig(options) {
|
|
2611
|
-
const { secretKey, appId
|
|
3133
|
+
const { secretKey, appId } = options;
|
|
3134
|
+
const resolvedPlatformUrl = (() => {
|
|
3135
|
+
if (options.platformUrl) return options.platformUrl.trim();
|
|
3136
|
+
const keyForRef = secretKey || appId;
|
|
3137
|
+
const parts = keyForRef?.split("_") ?? [];
|
|
3138
|
+
const ref = parts.length === 4 ? parts[2] : null;
|
|
3139
|
+
return ref ? `https://${ref}.${DEFAULT_SDK_API_HOST}` : `https://${DEFAULT_SDK_API_HOST}`;
|
|
3140
|
+
})();
|
|
2612
3141
|
const [plans, consentTypes, oauthProviders, featureFlags, app] = await Promise.all([
|
|
2613
|
-
getPlans({ secretKey, platformUrl }),
|
|
2614
|
-
getConsentTypes({ secretKey, platformUrl }),
|
|
2615
|
-
getOAuthProvidersWithInfo({ appId, platformUrl }),
|
|
2616
|
-
getFeatureFlags({ secretKey, platformUrl }),
|
|
2617
|
-
getAppMetadata({ secretKey, platformUrl })
|
|
3142
|
+
getPlans({ secretKey, platformUrl: resolvedPlatformUrl }),
|
|
3143
|
+
getConsentTypes({ secretKey, platformUrl: resolvedPlatformUrl }),
|
|
3144
|
+
getOAuthProvidersWithInfo({ appId, platformUrl: resolvedPlatformUrl }),
|
|
3145
|
+
getFeatureFlags({ secretKey, platformUrl: resolvedPlatformUrl }),
|
|
3146
|
+
getAppMetadata({ secretKey, platformUrl: resolvedPlatformUrl })
|
|
2618
3147
|
]);
|
|
2619
3148
|
return {
|
|
2620
3149
|
plans,
|
|
@@ -2706,10 +3235,14 @@ async function getDatabaseStatus(options) {
|
|
|
2706
3235
|
});
|
|
2707
3236
|
}
|
|
2708
3237
|
export {
|
|
3238
|
+
FunctionsClient,
|
|
3239
|
+
InvalidConnectionUrlError,
|
|
2709
3240
|
createAI,
|
|
2710
3241
|
createAuthenticatedServerClient,
|
|
3242
|
+
createClient2 as createClient,
|
|
2711
3243
|
createKv,
|
|
2712
3244
|
createServerClient,
|
|
3245
|
+
createServerRestClient,
|
|
2713
3246
|
createStreams,
|
|
2714
3247
|
createWebhookHandler,
|
|
2715
3248
|
decodeUserId,
|