@sylphx/sdk 0.10.0 → 0.10.1

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.
@@ -15,7 +15,7 @@ import { SdkBillingPlan, SdkConsentType } from '@sylphx/contract';
15
15
  * import { createRestClient } from '@sylphx/sdk'
16
16
  *
17
17
  * const client = createRestClient({
18
- * secretKey: process.env.SYLPHX_SECRET_KEY!,
18
+ * secretKey: createServerClient(process.env.SYLPHX_SECRET_URL!).secretKey!,
19
19
  * })
20
20
  *
21
21
  * const { data: user, error } = await client.GET('/auth/me')
@@ -130,6 +130,10 @@ interface Middleware {
130
130
  onRequest?: (ctx: {
131
131
  request: Request;
132
132
  }) => Promise<Request | undefined> | Request | undefined;
133
+ onFetch?: (ctx: {
134
+ request: Request;
135
+ next: (request: Request) => Promise<Response>;
136
+ }) => Promise<Response | undefined> | Response | undefined;
133
137
  onResponse?: (ctx: {
134
138
  request: Request;
135
139
  response: Response;
@@ -192,7 +196,7 @@ declare class InvalidConnectionUrlError extends Error {
192
196
  * import { createClient } from '@sylphx/sdk'
193
197
  *
194
198
  * const sylphx = createClient(process.env.SYLPHX_URL!)
195
- * // Parses: sylphx://pk_prod_{hex}@bold-river-a1b2c3.api.sylphx.com
199
+ * // Parses: sylphx://pk_prod_{ref?}_{hex}@bold-river-a1b2c3.api.sylphx.com
196
200
  * ```
197
201
  */
198
202
 
@@ -264,7 +268,7 @@ interface SylphxClientInput {
264
268
  * @example Connection URL (recommended)
265
269
  * ```typescript
266
270
  * const sylphx = createClient(process.env.NEXT_PUBLIC_SYLPHX_URL!)
267
- * // Parses: sylphx://pk_prod_{hex}@bold-river-a1b2c3.api.sylphx.com
271
+ * // Parses: sylphx://pk_prod_{ref?}_{hex}@bold-river-a1b2c3.api.sylphx.com
268
272
  * ```
269
273
  *
270
274
  * @example Explicit components
@@ -285,7 +289,7 @@ declare function createClient(input: string | SylphxClientInput): SylphxConfig;
285
289
  * @example Connection URL (recommended)
286
290
  * ```typescript
287
291
  * const sylphx = createServerClient(process.env.SYLPHX_SECRET_URL!)
288
- * // Parses: sylphx://sk_prod_{hex}@bold-river-a1b2c3.api.sylphx.com
292
+ * // Parses: sylphx://sk_prod_{ref?}_{hex}@bold-river-a1b2c3.api.sylphx.com
289
293
  * ```
290
294
  *
291
295
  * @example Explicit components
@@ -469,17 +473,17 @@ interface AccessTokenPayload {
469
473
  * 5. Single Source of Truth - All key logic in one place
470
474
  *
471
475
  * Key Formats (ADR-021):
472
- * - Publishable key: pk_(dev|stg|prod)_{ref}_{32hex} — client-safe (new)
473
- * - Secret Key: sk_(dev|stg|prod)_{ref}_{64hex} — server-side only
476
+ * - Publishable key: pk_(dev|stg|prod|prev)_{ref}_{32hex} — client-safe (new)
477
+ * - Secret Key: sk_(dev|stg|prod|prev)_{ref}_{64hex} — server-side only
474
478
  *
475
479
  * Legacy Key Formats (backward-compat):
476
- * - App ID (old): app_(dev|stg|prod)_[identifier] — Public identifier
480
+ * - App ID (old): app_(dev|stg|prod|prev)_[identifier] — Public identifier
477
481
  *
478
482
  * Special Internal Formats (NOT rotated):
479
483
  * - Console bootstrap: app_prod_platform_{slug} / sk_prod_platform_{slug}
480
484
  */
481
485
  /** Environment type derived from key prefix */
482
- type EnvironmentType = 'development' | 'staging' | 'production';
486
+ type EnvironmentType = 'development' | 'staging' | 'production' | 'preview';
483
487
  /** Key type - publicKey (pk_*), appId (legacy app_*), or secret (sk_*) */
484
488
  type KeyType = 'publicKey' | 'appId' | 'secret';
485
489
  /** Validation result with clear error information */
@@ -529,7 +533,7 @@ declare function validateAndSanitizeAppId(key: string | undefined | null): strin
529
533
  *
530
534
  * @example
531
535
  * ```typescript
532
- * const result = validateSecretKey(process.env.SYLPHX_SECRET_KEY)
536
+ * const result = validateSecretKey('sk_prod_...')
533
537
  * if (!result.valid) {
534
538
  * throw new Error(result.error)
535
539
  * }
@@ -601,7 +605,7 @@ declare function isSecretKey(key: string): boolean;
601
605
  *
602
606
  * @example
603
607
  * ```typescript
604
- * const result = validateKey(process.env.SYLPHX_SECRET_KEY)
608
+ * const result = validateKey('sk_prod_...')
605
609
  * if (!result.valid) {
606
610
  * throw new Error(result.error)
607
611
  * }
@@ -671,17 +675,11 @@ type OAuthProvider = OAuthProviderId | (string & {});
671
675
  * }
672
676
  * ```
673
677
  *
674
- * ```bash
675
- * # Deploy via CLI
676
- * sylphx functions deploy hello-world
677
- * # → https://my-project.sylphx.app/functions/hello-world
678
- * ```
679
- *
680
678
  * ```typescript
681
- * // Or deploy programmatically via SDK
679
+ * // Deploy programmatically from trusted server-side tooling
682
680
  * import { createConfig, FunctionsClient } from '@sylphx/sdk'
683
681
  *
684
- * const config = createConfig({ secretKey: process.env.SYLPHX_SECRET_KEY! })
682
+ * const config = createConfig({ secretKey: createServerClient(process.env.SYLPHX_SECRET_URL!).secretKey! })
685
683
  *
686
684
  * await FunctionsClient.deploy(config, {
687
685
  * name: 'hello-world',
@@ -723,7 +721,8 @@ interface FunctionDeployOptions {
723
721
  * Must export a default handler function:
724
722
  * export default async function(req: Request): Promise<Response> { ... }
725
723
  *
726
- * For functions > 100KB, use the CLI: sylphx functions deploy
724
+ * For larger bundles, use a production-backed deployment workflow that
725
+ * uploads source as an artifact instead of embedding it in JSON.
727
726
  */
728
727
  code: string;
729
728
  /** Human-readable display name */
@@ -942,7 +941,9 @@ declare function decodeUserId(prefixedId: string): string | null;
942
941
  * ```
943
942
  */
944
943
  interface AIClientOptions {
945
- /** Secret key for authentication (default: SYLPHX_SECRET_KEY env var) */
944
+ /** Server connection URL. Defaults to SYLPHX_SECRET_URL. */
945
+ secretUrl?: string;
946
+ /** @deprecated Use secretUrl. */
946
947
  secretKey?: string;
947
948
  /** Platform URL (default: https://api.sylphx.com) */
948
949
  platformUrl?: string;
@@ -1047,7 +1048,7 @@ interface AIClient {
1047
1048
  *
1048
1049
  * Uses environment variables by default:
1049
1050
  *
1050
- * - SYLPHX_SECRET_KEY: Your app's secret key (sk_dev_xxx, sk_stg_xxx, sk_prod_xxx)
1051
+ * - SYLPHX_SECRET_URL: Your app's server connection URL
1051
1052
  */
1052
1053
  declare function createAI(options?: AIClientOptions): AIClient;
1053
1054
  /**
@@ -1126,7 +1127,9 @@ interface KvZMember {
1126
1127
  */
1127
1128
 
1128
1129
  interface KvClientOptions {
1129
- /** Secret key for authentication (default: SYLPHX_SECRET_KEY env var) */
1130
+ /** Server connection URL. Defaults to SYLPHX_SECRET_URL. */
1131
+ secretUrl?: string;
1132
+ /** @deprecated Use secretUrl. */
1130
1133
  secretKey?: string;
1131
1134
  /** Platform URL (default: https://api.sylphx.com) */
1132
1135
  platformUrl?: string;
@@ -1292,7 +1295,7 @@ interface KvClient {
1292
1295
  * Create a server-side KV client
1293
1296
  *
1294
1297
  * Uses environment variables by default:
1295
- * - SYLPHX_SECRET_KEY: Your app's secret key (sk_dev_xxx, sk_stg_xxx, sk_prod_xxx)
1298
+ * - SYLPHX_SECRET_URL: Your app's server connection URL
1296
1299
  */
1297
1300
  declare function createKv(options?: KvClientOptions): KvClient;
1298
1301
  /**
@@ -1351,7 +1354,9 @@ interface StreamMessage<T = unknown> {
1351
1354
  */
1352
1355
 
1353
1356
  interface StreamsClientOptions {
1354
- /** Secret key for authentication (default: SYLPHX_SECRET_KEY env var) */
1357
+ /** Server connection URL. Defaults to SYLPHX_SECRET_URL. */
1358
+ secretUrl?: string;
1359
+ /** @deprecated Use secretUrl. */
1355
1360
  secretKey?: string;
1356
1361
  /** Platform URL (default: https://api.sylphx.com) */
1357
1362
  platformUrl?: string;
@@ -1403,7 +1408,7 @@ interface StreamsClient {
1403
1408
  *
1404
1409
  * Uses environment variables by default:
1405
1410
  *
1406
- * - SYLPHX_SECRET_KEY: Your app's secret key (sk_dev_xxx, sk_stg_xxx, sk_prod_xxx)
1411
+ * - SYLPHX_SECRET_URL: Your app's server connection URL
1407
1412
  */
1408
1413
  declare function createStreams(options?: StreamsClientOptions): StreamsClient;
1409
1414
  /**
@@ -1422,8 +1427,10 @@ declare function getStreams(): StreamsClient;
1422
1427
  * ```typescript
1423
1428
  * import { createServerClient, verifyWebhook } from '@sylphx/sdk/server'
1424
1429
  *
1430
+ * const sylphx = createServerClient(process.env.SYLPHX_SECRET_URL!)
1425
1431
  * const client = createServerRestClient({
1426
- * secretKey: process.env.SYLPHX_SECRET_URL!,
1432
+ * secretKey: sylphx.secretKey!,
1433
+ * platformUrl: sylphx.baseUrl.replace(/\/v1$/, ''),
1427
1434
  * })
1428
1435
  *
1429
1436
  * // REST API calls
@@ -1525,7 +1532,7 @@ interface WebhookVerifyOptions {
1525
1532
  * const result = await verifyWebhook({
1526
1533
  * payload: body,
1527
1534
  * signatureHeader: request.headers.get('x-webhook-signature'),
1528
- * secret: process.env.SYLPHX_SECRET_KEY!,
1535
+ * secret: process.env.SYLPHX_WEBHOOK_SECRET!,
1529
1536
  * })
1530
1537
  *
1531
1538
  * if (!result.valid) {
@@ -1555,7 +1562,7 @@ declare function verifyWebhook(options: {
1555
1562
  * import { createWebhookHandler } from '@sylphx/sdk/server'
1556
1563
  *
1557
1564
  * const handler = createWebhookHandler({
1558
- * secret: process.env.SYLPHX_SECRET_KEY!,
1565
+ * secret: process.env.SYLPHX_WEBHOOK_SECRET!,
1559
1566
  * handlers: {
1560
1567
  * 'user.created': async (data) => {
1561
1568
  * console.log('New user:', data)
@@ -1656,7 +1663,7 @@ interface FeatureFlagDefinition {
1656
1663
  * import { getFeatureFlags } from '@sylphx/sdk/server'
1657
1664
  *
1658
1665
  * export default async function RootLayout({ children }) {
1659
- * const sylphx = createServerClient(process.env.SYLPHX_URL!)
1666
+ * const sylphx = createServerClient(process.env.SYLPHX_SECRET_URL!)
1660
1667
  * const flags = await getFeatureFlags({
1661
1668
  * secretKey: sylphx.secretKey!,
1662
1669
  * platformUrl: sylphx.baseUrl.replace(/\/v[0-9]+$/, ''),
@@ -1713,7 +1720,7 @@ interface GetAppConfigOptions {
1713
1720
  * import { getAppConfig } from '@sylphx/sdk/server'
1714
1721
  *
1715
1722
  * export default async function RootLayout({ children }) {
1716
- * const sylphx = createServerClient(process.env.SYLPHX_URL!)
1723
+ * const sylphx = createServerClient(process.env.SYLPHX_SECRET_URL!)
1717
1724
  * const config = await getAppConfig({
1718
1725
  * secretKey: sylphx.secretKey!,
1719
1726
  * appId: process.env.NEXT_PUBLIC_SYLPHX_APP_ID!,
@@ -1761,7 +1768,7 @@ interface ReferralLeaderboardResult {
1761
1768
  *
1762
1769
  * export default async function ReferralsPage() {
1763
1770
  * const leaderboard = await getReferralLeaderboard({
1764
- * secretKey: process.env.SYLPHX_SECRET_KEY!,
1771
+ * secretKey: createServerClient(process.env.SYLPHX_SECRET_URL!).secretKey!,
1765
1772
  * limit: 10,
1766
1773
  * period: 'month',
1767
1774
  * })
@@ -1798,7 +1805,7 @@ interface EngagementLeaderboardResult {
1798
1805
  *
1799
1806
  * export default async function LeaderboardPage() {
1800
1807
  * const leaderboard = await getEngagementLeaderboard({
1801
- * secretKey: process.env.SYLPHX_SECRET_KEY!,
1808
+ * secretKey: createServerClient(process.env.SYLPHX_SECRET_URL!).secretKey!,
1802
1809
  * leaderboardId: 'high-scores',
1803
1810
  * })
1804
1811
  * return <Leaderboard initialData={leaderboard} />
@@ -1838,7 +1845,7 @@ interface DatabaseStatusInfo {
1838
1845
  *
1839
1846
  * // In your database initialization
1840
1847
  * const dbInfo = await getDatabaseConnection({
1841
- * secretKey: process.env.SYLPHX_SECRET_KEY!,
1848
+ * secretKey: createServerClient(process.env.SYLPHX_SECRET_URL!).secretKey!,
1842
1849
  * })
1843
1850
  *
1844
1851
  * if (dbInfo) {
@@ -1857,7 +1864,7 @@ declare function getDatabaseConnection(options: AuthenticatedFetchOptions): Prom
1857
1864
  * import { getDatabaseStatus } from '@sylphx/sdk/server'
1858
1865
  *
1859
1866
  * const status = await getDatabaseStatus({
1860
- * secretKey: process.env.SYLPHX_SECRET_KEY!,
1867
+ * secretKey: createServerClient(process.env.SYLPHX_SECRET_URL!).secretKey!,
1861
1868
  * })
1862
1869
  *
1863
1870
  * if (status?.status === 'ready') {
@@ -1360,13 +1360,14 @@ function exponentialBackoff(attempt, baseDelay = BASE_RETRY_DELAY_MS, maxDelay =
1360
1360
  }
1361
1361
 
1362
1362
  // src/key-validation.ts
1363
- var PUBLIC_KEY_PATTERN = /^pk_(dev|stg|prod)_[a-z0-9]{12}_[a-f0-9]{32}$/;
1363
+ var PUBLIC_KEY_PATTERN = /^pk_(dev|stg|prod|prev)(?:_[a-z0-9]{12})?_[a-f0-9]{32}$/;
1364
1364
  var APP_ID_PATTERN = /^(app|pk)_(dev|stg|prod|prev)_[a-z0-9_-]+$/;
1365
- var SECRET_KEY_PATTERN = /^sk_(dev|stg|prod)_[a-z0-9_-]+$/;
1365
+ var SECRET_KEY_PATTERN = /^sk_(dev|stg|prod|prev)_[a-z0-9_-]+$/;
1366
1366
  var ENV_PREFIX_MAP = {
1367
1367
  dev: "development",
1368
1368
  stg: "staging",
1369
- prod: "production"
1369
+ prod: "production",
1370
+ prev: "preview"
1370
1371
  };
1371
1372
  function detectKeyIssues(key) {
1372
1373
  const issues = [];
@@ -1391,7 +1392,7 @@ The SDK will automatically sanitize the key, but fixing the source is recommende
1391
1392
  }
1392
1393
  function createInvalidKeyError(keyType, key, envVarName) {
1393
1394
  const maskedKey = key.length > 20 ? `${key.slice(0, 20)}...` : key;
1394
- const formatHint = keyType === "appId" ? "pk_(dev|stg|prod)_{ref}_{hex} or app_(dev|stg|prod)_[id]" : "sk_(dev|stg|prod)_{ref}_{hex}";
1395
+ const formatHint = keyType === "appId" ? "pk_(dev|stg|prod|prev)_{ref}_{hex} or app_(dev|stg|prod|prev)_[id]" : "sk_(dev|stg|prod|prev)_{ref}_{hex}";
1395
1396
  const keyTypeName = keyType === "appId" ? "App ID" : "Secret Key";
1396
1397
  return `[Sylphx] Invalid ${keyTypeName} format.
1397
1398
 
@@ -1404,7 +1405,7 @@ You can find your keys in the Sylphx Console \u2192 API Keys.
1404
1405
  Common issues:
1405
1406
  \u2022 Key has uppercase characters (must be lowercase)
1406
1407
  \u2022 Key has wrong prefix (App ID: pk_ or app_, Secret Key: sk_)
1407
- \u2022 Key has invalid environment (must be dev, stg, or prod)
1408
+ \u2022 Key has invalid environment (must be dev, stg, prod, or prev)
1408
1409
  \u2022 Key was copied with extra whitespace`;
1409
1410
  }
1410
1411
  function extractEnvironment(key) {
@@ -1451,7 +1452,7 @@ function validateKeyForType(key, keyType, pattern, envVarName) {
1451
1452
  };
1452
1453
  }
1453
1454
  function validatePublicKey(key) {
1454
- return validateKeyForType(key, "publicKey", PUBLIC_KEY_PATTERN, "NEXT_PUBLIC_SYLPHX_KEY");
1455
+ return validateKeyForType(key, "publicKey", PUBLIC_KEY_PATTERN, "publishable credential");
1455
1456
  }
1456
1457
  function validateAppId(key) {
1457
1458
  return validateKeyForType(key, "appId", APP_ID_PATTERN, "NEXT_PUBLIC_SYLPHX_APP_ID");
@@ -1467,7 +1468,7 @@ function validateAndSanitizeAppId(key) {
1467
1468
  return result.sanitizedKey;
1468
1469
  }
1469
1470
  function validateSecretKey(key) {
1470
- return validateKeyForType(key, "secret", SECRET_KEY_PATTERN, "SYLPHX_SECRET_KEY");
1471
+ return validateKeyForType(key, "secret", SECRET_KEY_PATTERN, "secret credential");
1471
1472
  }
1472
1473
  function validateAndSanitizeSecretKey(key) {
1473
1474
  const result = validateSecretKey(key);
@@ -1508,7 +1509,7 @@ function isProductionKey(key) {
1508
1509
  }
1509
1510
  function getCookieNamespace(secretKey) {
1510
1511
  const env = detectEnvironment(secretKey);
1511
- const shortEnv = env === "development" ? "dev" : env === "staging" ? "stg" : "prod";
1512
+ const shortEnv = env === "development" ? "dev" : env === "staging" ? "stg" : env === "preview" ? "prev" : "prod";
1512
1513
  return `sylphx_${shortEnv}`;
1513
1514
  }
1514
1515
  function detectKeyType(key) {
@@ -1538,7 +1539,7 @@ function validateKey(key) {
1538
1539
  return {
1539
1540
  valid: false,
1540
1541
  sanitizedKey: "",
1541
- error: key ? `Invalid key format. Keys must start with 'pk_' (publishable), 'app_' (legacy), or 'sk_' (secret), followed by environment (dev/stg/prod). Got: ${key.slice(0, 20)}...` : "API key is required but was not provided.",
1542
+ error: key ? `Invalid key format. Keys must start with 'pk_' (publishable), 'app_' (legacy), or 'sk_' (secret), followed by environment (dev/stg/prod/prev). Got: ${key.slice(0, 20)}...` : "API key is required but was not provided.",
1542
1543
  issues: key ? ["invalid_format"] : ["missing"]
1543
1544
  };
1544
1545
  }
@@ -1571,7 +1572,14 @@ async function runPipeline(middlewares, initial) {
1571
1572
  if (next) request = next;
1572
1573
  }
1573
1574
  }
1574
- let response = await fetch(request);
1575
+ const fetchWithMiddleware = middlewares.reduceRight(
1576
+ (next, mw) => mw.onFetch ? async (request2) => {
1577
+ const response2 = await mw.onFetch?.({ request: request2, next });
1578
+ return response2 ?? next(request2);
1579
+ } : next,
1580
+ (request2) => fetch(request2)
1581
+ );
1582
+ let response = await fetchWithMiddleware(request);
1575
1583
  for (const mw of middlewares) {
1576
1584
  if (mw.onResponse) {
1577
1585
  const next = await mw.onResponse({ request, response });
@@ -1590,6 +1598,11 @@ function buildUrl(baseUrl, path, params) {
1590
1598
  ).toString();
1591
1599
  return `${url}?${search}`;
1592
1600
  }
1601
+ function cloneRequestWithHeaders(request, updateHeaders) {
1602
+ const headers = new Headers(request.headers);
1603
+ updateHeaders(headers);
1604
+ return new Request(request, { headers });
1605
+ }
1593
1606
  function interpolatePath(path, pathParams) {
1594
1607
  if (!pathParams) return path;
1595
1608
  return path.replace(/\{(\w+)\}/g, (_match, key) => {
@@ -1652,66 +1665,51 @@ function buildClient(baseUrl, baseHeaders) {
1652
1665
  function createAuthMiddleware(config) {
1653
1666
  return {
1654
1667
  async onRequest({ request }) {
1655
- request.headers.set("X-SDK-Version", SDK_VERSION);
1656
- request.headers.set("X-SDK-Platform", SDK_PLATFORM);
1657
- if (config.secretKey) {
1658
- request.headers.set("x-app-secret", config.secretKey);
1659
- }
1660
- const token = config.getAccessToken?.();
1661
- if (token) {
1662
- request.headers.set("Authorization", `Bearer ${token}`);
1663
- }
1664
- return request;
1668
+ return cloneRequestWithHeaders(request, (headers) => {
1669
+ headers.set("X-SDK-Version", SDK_VERSION);
1670
+ headers.set("X-SDK-Platform", SDK_PLATFORM);
1671
+ if (config.secretKey) {
1672
+ headers.set("x-app-secret", config.secretKey);
1673
+ }
1674
+ const token = config.getAccessToken?.();
1675
+ if (token) {
1676
+ headers.set("Authorization", `Bearer ${token}`);
1677
+ }
1678
+ });
1665
1679
  }
1666
1680
  };
1667
1681
  }
1668
1682
  function isRetryableStatus(status) {
1669
1683
  return status >= 500 || status === 429;
1670
1684
  }
1671
- var inFlightRequests = /* @__PURE__ */ new Map();
1672
1685
  async function getRequestKey(request) {
1673
1686
  const body = request.body ? await request.clone().text() : "";
1674
1687
  return `${request.method}:${request.url}:${body}`;
1675
1688
  }
1676
1689
  function createDeduplicationMiddleware(config = {}) {
1677
1690
  const { enabled = true, methods = ["GET"] } = config;
1678
- if (!enabled) {
1679
- return {
1680
- async onRequest({ request }) {
1681
- return request;
1682
- }
1683
- };
1684
- }
1691
+ if (!enabled) return {};
1692
+ const inFlightRequests = /* @__PURE__ */ new Map();
1685
1693
  return {
1686
- async onRequest({ request }) {
1687
- if (!methods.includes(request.method)) {
1688
- return request;
1694
+ async onFetch({ request, next }) {
1695
+ const method = request.method.toUpperCase();
1696
+ if (!methods.includes(method)) {
1697
+ return next(request);
1689
1698
  }
1690
1699
  const key = await getRequestKey(request);
1691
1700
  const existing = inFlightRequests.get(key);
1692
1701
  if (existing) {
1693
- const deduped = request.clone();
1694
- deduped._dedupKey = key;
1695
- return deduped;
1696
- }
1697
- ;
1698
- request._dedupKey = key;
1699
- return request;
1700
- },
1701
- async onResponse({ request, response }) {
1702
- const key = request._dedupKey;
1703
- if (!key) return response;
1704
- const existing = inFlightRequests.get(key);
1705
- if (existing && inFlightRequests.get(key) !== void 0) {
1706
1702
  const cachedResponse = await existing;
1707
1703
  return cachedResponse.clone();
1708
1704
  }
1709
- const responsePromise = Promise.resolve(response.clone());
1705
+ const responsePromise = next(request).then((response) => response.clone());
1710
1706
  inFlightRequests.set(key, responsePromise);
1711
- responsePromise.finally(() => {
1712
- setTimeout(() => inFlightRequests.delete(key), 100);
1713
- });
1714
- return response;
1707
+ try {
1708
+ const response = await responsePromise;
1709
+ return response.clone();
1710
+ } finally {
1711
+ inFlightRequests.delete(key);
1712
+ }
1715
1713
  }
1716
1714
  };
1717
1715
  }
@@ -1861,7 +1859,9 @@ function createETagMiddleware(config) {
1861
1859
  if (Date.now() - cached.timestamp > ttlMs) {
1862
1860
  etagCache.delete(cacheKey);
1863
1861
  } else {
1864
- request.headers.set("If-None-Match", cached.etag);
1862
+ return cloneRequestWithHeaders(request, (headers) => {
1863
+ headers.set("If-None-Match", cached.etag);
1864
+ });
1865
1865
  }
1866
1866
  }
1867
1867
  return request;
@@ -2018,7 +2018,7 @@ function createDynamicRestClient(config) {
2018
2018
  // src/connection-url.ts
2019
2019
  var SYLPHX_PROTOCOL = "sylphx:";
2020
2020
  var DEFAULT_VERSION = "v1";
2021
- var CREDENTIAL_REGEX = /^(pk|sk)_(dev|stg|prod|prev)_[a-f0-9]{32,64}$/;
2021
+ var CREDENTIAL_REGEX = /^(pk|sk)_(dev|stg|prod|prev)(?:_[a-z0-9]{12})?_[a-f0-9]{32,64}$/;
2022
2022
  var VERSION_REGEX = /^v[0-9]+$/;
2023
2023
  var SLUG_REGEX = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/;
2024
2024
  var InvalidConnectionUrlError = class _InvalidConnectionUrlError extends Error {
@@ -2035,7 +2035,7 @@ function fail(reason) {
2035
2035
  function parseCredential(raw) {
2036
2036
  const match = CREDENTIAL_REGEX.exec(raw);
2037
2037
  if (!match) {
2038
- fail(`credential must match (pk|sk)_(dev|stg|prod|prev)_[a-f0-9]{32,64}, got "${raw}"`);
2038
+ fail(`credential must match (pk|sk)_(dev|stg|prod|prev)(_{ref})?_{hex}, got "${raw}"`);
2039
2039
  }
2040
2040
  return {
2041
2041
  credentialType: match[1],
@@ -2112,7 +2112,7 @@ function parseConnectionUrl(url) {
2112
2112
  // src/config.ts
2113
2113
  var LEGACY_EMBEDDED_REF_PATTERN = /^(pk|sk)_(dev|stg|prod|prev)_[a-z0-9]{12}_[a-f0-9]+$/;
2114
2114
  var LEGACY_APP_KEY_PATTERN = /^app_(dev|stg|prod|prev)_/;
2115
- var MIGRATION_MESSAGE = "API key format has changed. Use a sylphx:// connection URL instead.\n\nNew format: sylphx://pk_prod_{hex}@your-slug.api.sylphx.com\n\nGenerate new credentials from the Sylphx Console \u2192 Your App \u2192 Environments.\nSee https://docs.sylphx.com/migration for details.";
2115
+ var MIGRATION_MESSAGE = "API key format has changed. Use a sylphx:// connection URL instead.\n\nNew format: sylphx://pk_prod_{ref?}_{hex}@your-slug.api.sylphx.com\n\nGenerate new credentials from the Sylphx Console \u2192 Your App \u2192 Environments.\nSee https://docs.sylphx.com/migration for details.";
2116
2116
  function rejectLegacyKeyFormat(input) {
2117
2117
  const trimmed = input.trim().toLowerCase();
2118
2118
  if (LEGACY_APP_KEY_PATTERN.test(trimmed)) {
@@ -2245,7 +2245,7 @@ function createConfigFromComponents(input) {
2245
2245
  });
2246
2246
  }
2247
2247
  throw new SylphxError(
2248
- `[Sylphx] Invalid credential format. Expected (pk|sk)_(dev|stg|prod|prev)_[a-f0-9]{32,64}. Got: "${trimmedCred.slice(0, 30)}..."`,
2248
+ `[Sylphx] Invalid credential format. Expected (pk|sk)_(dev|stg|prod|prev)(_{ref})?_{hex}. Got: "${trimmedCred.slice(0, 30)}..."`,
2249
2249
  { code: "BAD_REQUEST" }
2250
2250
  );
2251
2251
  }
@@ -2571,14 +2571,32 @@ function decodeUserId(prefixedId) {
2571
2571
  return null;
2572
2572
  }
2573
2573
 
2574
+ // src/server/secret-url.ts
2575
+ function resolveServerConnection(options = {}) {
2576
+ const configuredSecret = resolveSecretUrl(options.secretUrl ?? options.secretKey);
2577
+ if (!configuredSecret) {
2578
+ throw new Error("SYLPHX_SECRET_URL is required for server-side SDK calls");
2579
+ }
2580
+ if (configuredSecret.trim().startsWith("sylphx://")) {
2581
+ const config = createServerClient(configuredSecret);
2582
+ return {
2583
+ secretKey: config.credential,
2584
+ baseUrl: options.platformUrl?.trim() || config.baseUrl.replace(/\/v1\/?$/, "")
2585
+ };
2586
+ }
2587
+ return {
2588
+ secretKey: validateAndSanitizeSecretKey(configuredSecret),
2589
+ baseUrl: options.platformUrl?.trim() || `https://${DEFAULT_SDK_API_HOST}`
2590
+ };
2591
+ }
2592
+
2574
2593
  // src/server/ai.ts
2575
2594
  function createAI(options = {}) {
2576
2595
  const baseURL = (options.platformUrl || `https://${DEFAULT_SDK_API_HOST}`).trim();
2577
- const rawApiKey = options.secretKey || process.env.SYLPHX_SECRET_KEY;
2578
- const apiKey = validateAndSanitizeSecretKey(rawApiKey);
2596
+ const { secretKey } = resolveServerConnection(options);
2579
2597
  const headers = {
2580
2598
  "Content-Type": "application/json",
2581
- Authorization: `Bearer ${apiKey}`
2599
+ Authorization: `Bearer ${secretKey}`
2582
2600
  };
2583
2601
  async function chat(opts) {
2584
2602
  const response = await fetch(`${baseURL}/api/v1/chat/completions`, {
@@ -2666,8 +2684,7 @@ function getAI() {
2666
2684
 
2667
2685
  // src/server/kv.ts
2668
2686
  function createKv(options = {}) {
2669
- const platformUrl = (options.platformUrl || `https://${DEFAULT_SDK_API_HOST}`).trim();
2670
- const secretKey = validateAndSanitizeSecretKey(resolveSecretUrl(options.secretKey));
2687
+ const { baseUrl, secretKey } = resolveServerConnection(options);
2671
2688
  const headers = {
2672
2689
  "Content-Type": "application/json",
2673
2690
  "x-app-secret": secretKey,
@@ -2678,7 +2695,7 @@ function createKv(options = {}) {
2678
2695
  let lastError;
2679
2696
  for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
2680
2697
  try {
2681
- const response = await fetch(`${platformUrl}${SDK_API_PATH}/kv${path}`, {
2698
+ const response = await fetch(`${baseUrl}${SDK_API_PATH}/kv${path}`, {
2682
2699
  method,
2683
2700
  headers,
2684
2701
  body: body ? JSON.stringify(body) : void 0
@@ -2855,15 +2872,13 @@ function getKv() {
2855
2872
 
2856
2873
  // src/server/streams.ts
2857
2874
  function createStreams(options = {}) {
2858
- const baseURL = (options.platformUrl || `https://${DEFAULT_SDK_API_HOST}`).trim();
2859
- const rawApiKey = options.secretKey || process.env.SYLPHX_SECRET_KEY;
2860
- const apiKey = validateAndSanitizeSecretKey(rawApiKey);
2875
+ const { baseUrl, secretKey } = resolveServerConnection(options);
2861
2876
  const headers = {
2862
2877
  "Content-Type": "application/json",
2863
- "x-app-secret": apiKey
2878
+ "x-app-secret": secretKey
2864
2879
  };
2865
2880
  async function emit(channel, event, data) {
2866
- const response = await fetch(`${baseURL}${SDK_API_PATH}/realtime/emit`, {
2881
+ const response = await fetch(`${baseUrl}${SDK_API_PATH}/realtime/emit`, {
2867
2882
  method: "POST",
2868
2883
  headers,
2869
2884
  body: JSON.stringify({ channel, event, data })
@@ -2876,7 +2891,7 @@ function createStreams(options = {}) {
2876
2891
  return result.id;
2877
2892
  }
2878
2893
  async function history(channel, options2) {
2879
- const response = await fetch(`${baseURL}${SDK_API_PATH}/realtime/history`, {
2894
+ const response = await fetch(`${baseUrl}${SDK_API_PATH}/realtime/history`, {
2880
2895
  method: "POST",
2881
2896
  headers,
2882
2897
  body: JSON.stringify({