@sylphx/sdk 0.0.1 → 0.2.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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/constants.ts","../src/errors.ts","../src/key-validation.ts","../src/config.ts","../src/debug.ts","../src/rest-client.ts","../src/auth.ts","../src/analytics.ts","../src/ai.ts","../src/billing.ts","../src/storage.ts","../src/notifications.ts","../src/lib/notifications/service-worker.ts","../src/jobs.ts","../src/flags.ts","../src/webhooks.ts","../src/email.ts","../src/consent.ts","../src/referrals.ts","../src/lib/engagement/types.ts","../src/engagement.ts","../src/orgs.ts","../src/secrets.ts","../src/search.ts","../src/database.ts","../src/kv.ts","../src/realtime.ts","../src/deploy.ts","../src/monitoring.ts"],"sourcesContent":["/**\n * SDK Constants — Single Source of Truth\n *\n * Shared constants used across the SDK. Centralizing these\n * prevents magic number duplication and makes changes easier.\n *\n * IMPORTANT: All time-based constants should be used consistently\n * across the SDK. Never hardcode magic numbers like 30000, 5 * 60 * 1000, etc.\n */\n\n// =============================================================================\n// API Configuration\n// =============================================================================\n\n/** Default platform URL */\nexport const DEFAULT_PLATFORM_URL = 'https://sylphx.com'\n\n/**\n * Canonical environment variable name for the platform URL.\n *\n * SDK modules MUST read from this env var (with SYLPHX_URL as legacy fallback).\n * Centralizing the name prevents the same env var being spelled differently\n * across kv.ts, streams.ts, ai.ts, middleware.ts, and server.ts.\n */\nexport const ENV_PLATFORM_URL = 'SYLPHX_PLATFORM_URL'\n\n/**\n * Legacy environment variable name for the platform URL.\n *\n * Supported as a fallback for older deployments that set SYLPHX_URL.\n * New projects should use SYLPHX_PLATFORM_URL.\n */\nexport const ENV_PLATFORM_URL_LEGACY = 'SYLPHX_URL'\n\n/**\n * Canonical environment variable name for the secret key.\n *\n * All server-side SDK modules read from this env var by default.\n */\nexport const ENV_SECRET_KEY = 'SYLPHX_SECRET_KEY'\n\n/**\n * Resolve the platform URL from environment variables.\n *\n * Priority: explicit value > SYLPHX_PLATFORM_URL > SYLPHX_URL (legacy) > default\n */\nexport function resolvePlatformUrl(explicit?: string): string {\n\treturn (\n\t\texplicit ||\n\t\tprocess.env[ENV_PLATFORM_URL] ||\n\t\tprocess.env[ENV_PLATFORM_URL_LEGACY] ||\n\t\tDEFAULT_PLATFORM_URL\n\t).trim()\n}\n\n/**\n * Resolve the secret key from environment variables.\n *\n * Returns the raw value before validation. Callers should pass the result\n * through `validateAndSanitizeSecretKey()`.\n */\nexport function resolveSecretKey(explicit?: string): string | undefined {\n\treturn explicit || process.env[ENV_SECRET_KEY]\n}\n\n/** @deprecated No longer used for path construction. Use SDK_API_PATH directly. */\nexport const SDK_API_VERSION = 'v1'\n\n/**\n * SDK API base path — legacy path served from main Next.js app.\n *\n * Used when `platformUrl` is set to `https://sylphx.com` (default).\n * Points to the runtime Hono app mounted at /api/app/v1 (via Next.js catch-all).\n *\n * @deprecated Prefer project `ref`-based URL: https://{ref}.api.sylphx.com/v1\n */\nexport const SDK_API_PATH = `/api/app/v1`\n\n/**\n * SDK API path for new subdomain-based SDK server.\n *\n * Used when `ref` is provided to `createConfig`.\n * The full base becomes: https://{ref}.api.sylphx.com + SDK_API_PATH_NEW\n */\nexport const SDK_API_PATH_NEW = `/v1`\n\n/**\n * Default SDK API host for new subdomain-based SDK server.\n * The full URL is built as: https://{ref}.api.sylphx.com/v1\n */\nexport const DEFAULT_SDK_API_HOST = 'api.sylphx.com'\n\n/**\n * Default auth route prefix\n *\n * Used for OAuth callbacks and signout routes.\n * Must match the middleware's authPrefix config.\n */\nexport const DEFAULT_AUTH_PREFIX = '/auth'\n\n/**\n * SDK package version\n *\n * Sent in X-SDK-Version header for debugging and analytics.\n * Update this when releasing new SDK versions.\n */\nexport const SDK_VERSION = '0.1.0'\n\n/**\n * SDK platform identifier\n *\n * Sent in X-SDK-Platform header to identify the runtime environment.\n */\nexport const SDK_PLATFORM =\n\ttypeof window !== 'undefined'\n\t\t? 'browser'\n\t\t: typeof process !== 'undefined' && process.versions?.node\n\t\t\t? 'node'\n\t\t\t: 'unknown'\n\n// =============================================================================\n// Timeouts & Durations\n// =============================================================================\n\n/** Default request timeout in milliseconds (30 seconds) */\nexport const DEFAULT_TIMEOUT_MS = 30_000\n\n/**\n * Token expiry buffer in milliseconds (30 seconds)\n *\n * Refresh tokens this many milliseconds BEFORE they expire\n * to account for network latency and clock skew.\n */\nexport const TOKEN_EXPIRY_BUFFER_MS = 30_000\n\n/**\n * Session token lifetime in seconds (5 minutes)\n *\n * Matches Clerk's short-lived access token pattern.\n * Used for cookie maxAge and React Query staleTime.\n */\nexport const SESSION_TOKEN_LIFETIME_SECONDS = 5 * 60\n\n/** Session token lifetime in milliseconds (for React Query staleTime) */\nexport const SESSION_TOKEN_LIFETIME_MS = SESSION_TOKEN_LIFETIME_SECONDS * 1000\n\n/**\n * Refresh token lifetime in seconds (30 days)\n *\n * Long-lived token for silent refresh.\n */\nexport const REFRESH_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60\n\n// =============================================================================\n// Feature Flags Cache\n// =============================================================================\n\n/**\n * Feature flags cache TTL in milliseconds (5 minutes)\n *\n * How long to cache flags before fetching fresh values.\n * Matches LaunchDarkly's default streaming connection behavior.\n */\nexport const FLAGS_CACHE_TTL_MS = 5 * 60 * 1000\n\n/**\n * Feature flags stale-while-revalidate window in milliseconds (1 minute)\n *\n * Allow serving stale flags while fetching fresh values.\n */\nexport const FLAGS_STALE_WHILE_REVALIDATE_MS = 60 * 1000\n\n// =============================================================================\n// Retry & Backoff\n// =============================================================================\n\n/** Maximum retry delay for exponential backoff (30 seconds) */\nexport const MAX_RETRY_DELAY_MS = 30_000\n\n/** Base retry delay for exponential backoff (1 second) */\nexport const BASE_RETRY_DELAY_MS = 1_000\n\n/** Maximum number of retries for network requests */\nexport const MAX_RETRIES = 3\n\n// =============================================================================\n// Analytics\n// =============================================================================\n\n/**\n * Analytics session timeout in milliseconds (30 minutes)\n *\n * After this much inactivity, a new session is started.\n */\nexport const ANALYTICS_SESSION_TIMEOUT_MS = 30 * 60 * 1000\n\n// =============================================================================\n// Webhooks\n// =============================================================================\n\n/**\n * Maximum age for webhook signature validation (5 minutes)\n *\n * Reject webhooks with timestamps older than this.\n */\nexport const WEBHOOK_MAX_AGE_MS = 5 * 60 * 1000\n\n/**\n * Clock skew allowance for webhook validation (30 seconds)\n *\n * Allow timestamps this far in the future.\n */\nexport const WEBHOOK_CLOCK_SKEW_MS = 30 * 1000\n\n// =============================================================================\n// PKCE (OAuth)\n// =============================================================================\n\n/**\n * PKCE code verifier TTL in milliseconds (10 minutes)\n *\n * How long the code verifier is stored during OAuth flow.\n */\nexport const PKCE_CODE_TTL_MS = 10 * 60 * 1000\n\n// =============================================================================\n// Jobs\n// =============================================================================\n\n/**\n * Job dead-letter queue retention in milliseconds (7 days)\n */\nexport const JOBS_DLQ_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000\n\n// =============================================================================\n// Session Replay\n// =============================================================================\n\n/**\n * Maximum session replay recording duration in milliseconds (60 minutes)\n */\nexport const SESSION_REPLAY_MAX_DURATION_MS = 60 * 60 * 1000\n\n/**\n * Session replay upload interval in milliseconds (5 seconds)\n */\nexport const SESSION_REPLAY_UPLOAD_INTERVAL_MS = 5_000\n\n/**\n * Session replay scroll event throttle interval (150 ms)\n */\nexport const SESSION_REPLAY_SCROLL_THROTTLE_MS = 150\n\n/**\n * Session replay media time update throttle interval (800 ms)\n */\nexport const SESSION_REPLAY_MEDIA_THROTTLE_MS = 800\n\n/**\n * Session replay rage click detection window (1 second)\n */\nexport const SESSION_REPLAY_RAGE_CLICK_WINDOW_MS = 1_000\n\n/**\n * Session replay dead click detection timeout (500 ms)\n */\nexport const SESSION_REPLAY_DEAD_CLICK_TIMEOUT_MS = 500\n\n/**\n * Session replay scroll heat detection window (2 seconds)\n */\nexport const SESSION_REPLAY_SCROLL_HEAT_WINDOW_MS = 2_000\n\n/**\n * Session replay status check interval (5 seconds)\n */\nexport const SESSION_REPLAY_STATUS_CHECK_MS = 5_000\n\n// =============================================================================\n// Analytics (Extended)\n// =============================================================================\n\n/**\n * Analytics event flush interval in milliseconds (5 seconds)\n */\nexport const ANALYTICS_FLUSH_INTERVAL_MS = 5_000\n\n/**\n * Analytics maximum text length for autocapture (100 characters)\n */\nexport const ANALYTICS_MAX_TEXT_LENGTH = 100\n\n/**\n * Analytics flush timeout in milliseconds (1 second)\n */\nexport const ANALYTICS_FLUSH_TIMEOUT_MS = 1_000\n\n/**\n * Analytics interval check in milliseconds (1 second)\n */\nexport const ANALYTICS_INTERVAL_CHECK_MS = 1_000\n\n/**\n * Analytics retry base delay in milliseconds (1 second)\n * Exponential backoff: delay = base * 2^retries (with jitter)\n */\nexport const ANALYTICS_RETRY_BASE_DELAY_MS = 1_000\n\n/**\n * Analytics retry max delay in milliseconds (30 seconds)\n */\nexport const ANALYTICS_RETRY_MAX_DELAY_MS = 30_000\n\n/**\n * Analytics retry jitter factor (±20%)\n * Prevents thundering herd when multiple clients retry simultaneously\n */\nexport const ANALYTICS_RETRY_JITTER = 0.2\n\n/**\n * Analytics maximum retries before dropping event (Segment pattern: 10)\n */\nexport const ANALYTICS_MAX_RETRIES = 10\n\n// =============================================================================\n// Feature Flags (Extended)\n// =============================================================================\n\n/**\n * Feature flags exposure deduplication window (1 hour)\n *\n * Prevents duplicate exposure events for A/B tests within this window.\n */\nexport const FLAGS_EXPOSURE_DEDUPE_WINDOW_MS = 60 * 60 * 1000\n\n/**\n * Flag stream initial reconnection delay (1 second)\n */\nexport const FLAGS_STREAM_INITIAL_RECONNECT_MS = 1_000\n\n/**\n * Flag stream maximum reconnection delay (30 seconds)\n */\nexport const FLAGS_STREAM_MAX_RECONNECT_MS = 30_000\n\n/**\n * Flag stream heartbeat timeout (45 seconds)\n */\nexport const FLAGS_STREAM_HEARTBEAT_TIMEOUT_MS = 45_000\n\n/**\n * Flag HTTP polling interval fallback (60 seconds)\n */\nexport const FLAGS_HTTP_POLLING_INTERVAL_MS = 60_000\n\n// =============================================================================\n// Jobs (Extended)\n// =============================================================================\n\n/**\n * Default retry delay sequence for exponential backoff (ms)\n */\nexport const DEFAULT_RETRY_DELAYS_MS = [1_000, 5_000, 15_000, 30_000, 60_000] as const\n\n/**\n * Default job timeout in milliseconds (60 seconds)\n */\nexport const JOB_DEFAULT_TIMEOUT_MS = 60_000\n\n/**\n * Default job status polling interval (2 seconds)\n */\nexport const JOB_POLL_INTERVAL_MS = 2_000\n\n// =============================================================================\n// Storage Keys & Prefixes\n// =============================================================================\n\n/**\n * Storage key prefix for SDK data\n */\nexport const STORAGE_KEY_PREFIX = 'sylphx_'\n\n/**\n * localStorage key for cached feature flags\n */\nexport const FLAGS_CACHE_KEY = 'sylphx_feature_flags'\n\n/**\n * localStorage key for feature flags cache timestamp\n */\nexport const FLAGS_CACHE_TIMESTAMP_KEY = 'sylphx_feature_flags_ts'\n\n/**\n * localStorage key for feature flags overrides\n */\nexport const FLAGS_OVERRIDES_KEY = 'sylphx_feature_flags_overrides'\n\n/**\n * localStorage key for active organization\n */\nexport const ORG_STORAGE_KEY = 'sylphx_active_org'\n\n/**\n * BroadcastChannel name for cross-tab org sync\n */\nexport const ORG_BROADCAST_CHANNEL = 'sylphx_org_sync'\n\n/**\n * Storage prefix for PKCE verifiers\n */\nexport const PKCE_STORAGE_PREFIX = 'sylphx_pkce_'\n\n/**\n * Test key for checking storage availability\n */\nexport const STORAGE_TEST_KEY = '__sylphx_test__'\n\n/**\n * Cookie/storage name for analytics sessions\n */\nexport const ANALYTICS_SESSION_KEY = 'sylphx_session'\n\n/**\n * Default storage key for flags persistence\n */\nexport const FLAGS_STORAGE_KEY = 'sylphx_flags'\n\n// =============================================================================\n// Click ID & Attribution\n// =============================================================================\n\n/**\n * Click ID attribution window in milliseconds (90 days)\n *\n * How long click IDs are stored for conversion attribution.\n */\nexport const CLICK_ID_EXPIRY_MS = 90 * 24 * 60 * 60 * 1000\n\n// =============================================================================\n// React Query Stale Times\n// =============================================================================\n\n/**\n * React Query staleTime for frequently-changing data (1 minute)\n *\n * Use for: real-time metrics, live feeds, active sessions\n */\nexport const STALE_TIME_FREQUENT_MS = 60 * 1_000\n\n/**\n * React Query staleTime for moderately-changing data (2 minutes)\n *\n * Use for: subscriptions, user profiles, preferences\n */\nexport const STALE_TIME_MODERATE_MS = 2 * 60 * 1_000\n\n/**\n * React Query staleTime for stable/config data (5 minutes)\n *\n * Use for: plans, feature flags, app config\n */\nexport const STALE_TIME_STABLE_MS = 5 * 60 * 1_000\n\n/**\n * React Query staleTime for webhook stats (30 seconds)\n */\nexport const STALE_TIME_STATS_MS = 30 * 1_000\n\n// =============================================================================\n// UI Component Timeouts\n// =============================================================================\n\n/**\n * Copy-to-clipboard feedback display duration (2 seconds)\n */\nexport const UI_COPY_FEEDBACK_MS = 2_000\n\n/**\n * Form success message display duration (3 seconds)\n */\nexport const UI_FORM_SUCCESS_MS = 3_000\n\n/**\n * General notification display duration (5 seconds)\n */\nexport const UI_NOTIFICATION_MS = 5_000\n\n/**\n * Prompt auto-show delay (3 seconds)\n */\nexport const UI_PROMPT_DELAY_MS = 3_000\n\n/**\n * Redirect delay after action (3 seconds)\n */\nexport const UI_REDIRECT_DELAY_MS = 3_000\n\n/**\n * Animation out duration (200 ms)\n */\nexport const UI_ANIMATION_OUT_MS = 200\n\n/**\n * Animation in duration (300 ms)\n */\nexport const UI_ANIMATION_IN_MS = 300\n\n// =============================================================================\n// Email & Verification\n// =============================================================================\n\n/**\n * Email resend cooldown tick interval (1 second)\n */\nexport const EMAIL_RESEND_COOLDOWN_TICK_MS = 1_000\n\n/**\n * New user detection threshold (1 minute)\n *\n * Users created within this window are considered \"new\" for signup tracking.\n */\nexport const NEW_USER_THRESHOLD_MS = 60 * 1_000\n\n// =============================================================================\n// Web Vitals Thresholds\n// =============================================================================\n\n/**\n * FCP (First Contentful Paint) \"good\" threshold (1800 ms)\n */\nexport const WEB_VITALS_FCP_GOOD_MS = 1_800\n\n/**\n * FCP (First Contentful Paint) \"poor\" threshold (3000 ms)\n */\nexport const WEB_VITALS_FCP_POOR_MS = 3_000\n\n// =============================================================================\n// Storage Sizes\n// =============================================================================\n\n/**\n * Multipart upload threshold (5 MB)\n *\n * Files larger than this use multipart upload for better reliability.\n */\nexport const STORAGE_MULTIPART_THRESHOLD_BYTES = 5 * 1024 * 1024\n\n/**\n * Default max file size for uploads (5 MB)\n */\nexport const STORAGE_DEFAULT_MAX_SIZE_BYTES = 5 * 1024 * 1024\n\n/**\n * Avatar max file size (2 MB)\n */\nexport const STORAGE_AVATAR_MAX_SIZE_BYTES = 2 * 1024 * 1024\n\n/**\n * Large file max size for file uploads (10 MB)\n */\nexport const STORAGE_LARGE_MAX_SIZE_BYTES = 10 * 1024 * 1024\n\n// =============================================================================\n// Cache TTLs\n// =============================================================================\n\n/**\n * JWK cache TTL (1 hour)\n */\nexport const JWK_CACHE_TTL_MS = 60 * 60 * 1000\n\n// =============================================================================\n// Analytics Event Tracking\n// =============================================================================\n\n/**\n * Max tracked event IDs to keep in memory\n */\nexport const ANALYTICS_MAX_TRACKED_EVENT_IDS = 1000\n\n/**\n * Number of event IDs to keep after cleanup\n */\nexport const ANALYTICS_TRACKED_IDS_KEEP = 500\n\n/**\n * Analytics queue limit before force flush\n */\nexport const ANALYTICS_QUEUE_LIMIT = 100\n\n// =============================================================================\n// Session Replay (Extended)\n// =============================================================================\n\n/**\n * Session replay check interval (1 second)\n */\nexport const SESSION_REPLAY_CHECK_INTERVAL_MS = 1_000\n\n/**\n * Success feedback delay for invite/account actions (1.5 seconds)\n */\nexport const UI_SUCCESS_REDIRECT_MS = 1_500\n\n// =============================================================================\n// String Truncation Limits\n// =============================================================================\n\n/**\n * Max message length for logging (1000 chars)\n */\nexport const LOG_MESSAGE_MAX_LENGTH = 1_000\n\n/**\n * Max DOM snapshot length for debugging (1000 chars)\n */\nexport const DOM_SNAPSHOT_MAX_LENGTH = 1_000\n\n/**\n * Max stack trace length for error tracking (500 chars)\n */\nexport const STACK_TRACE_MAX_LENGTH = 500\n\n/**\n * Google Consent Mode wait for update timeout (500 ms)\n */\nexport const CONSENT_WAIT_FOR_UPDATE_MS = 500\n\n// =============================================================================\n// Time Unit Conversions\n// =============================================================================\n\n/** Milliseconds per minute (60,000) */\nexport const MS_PER_MINUTE = 60_000\n\n/** Milliseconds per hour (3,600,000) */\nexport const MS_PER_HOUR = 3_600_000\n\n/** Milliseconds per day (86,400,000) */\nexport const MS_PER_DAY = 86_400_000\n\n/** Seconds per minute (60) */\nexport const SECONDS_PER_MINUTE = 60\n\n/** Seconds per hour (3,600) */\nexport const SECONDS_PER_HOUR = 3_600\n\n// =============================================================================\n// Z-Index Values\n// =============================================================================\n\n/** Z-index for modal overlays (9999) */\nexport const Z_INDEX_OVERLAY = 9999\n\n/** Z-index for critical overlays like feature gates (99999) */\nexport const Z_INDEX_CRITICAL_OVERLAY = 99999\n\n// =============================================================================\n// API Key Expiry (seconds)\n// =============================================================================\n\n/** API key expiry: 1 day (86,400 seconds) */\nexport const API_KEY_EXPIRY_1_DAY = 86_400\n\n/** API key expiry: 7 days (604,800 seconds) */\nexport const API_KEY_EXPIRY_7_DAYS = 604_800\n\n/** API key expiry: 30 days (2,592,000 seconds) */\nexport const API_KEY_EXPIRY_30_DAYS = 2_592_000\n\n/** API key expiry: 90 days (7,776,000 seconds) */\nexport const API_KEY_EXPIRY_90_DAYS = 7_776_000\n\n/** API key expiry: 1 year (31,536,000 seconds) */\nexport const API_KEY_EXPIRY_1_YEAR = 31_536_000\n\n// =============================================================================\n// Web Vitals Thresholds (Google standards)\n// =============================================================================\n\n/** LCP (Largest Contentful Paint) \"good\" threshold (2500 ms) */\nexport const WEB_VITALS_LCP_GOOD_MS = 2_500\n\n/** LCP (Largest Contentful Paint) \"poor\" threshold (4000 ms) */\nexport const WEB_VITALS_LCP_POOR_MS = 4_000\n\n/** INP (Interaction to Next Paint) \"good\" threshold (200 ms) */\nexport const WEB_VITALS_INP_GOOD_MS = 200\n\n/** INP (Interaction to Next Paint) \"poor\" threshold (500 ms) */\nexport const WEB_VITALS_INP_POOR_MS = 500\n\n/** TTFB (Time to First Byte) \"good\" threshold (800 ms) */\nexport const WEB_VITALS_TTFB_GOOD_MS = 800\n\n/** TTFB (Time to First Byte) \"poor\" threshold (1800 ms) */\nexport const WEB_VITALS_TTFB_POOR_MS = 1_800\n\n// =============================================================================\n// Security\n// =============================================================================\n\n/** Minimum password length (NIST SP 800-63B recommends 12+) */\nexport const MIN_PASSWORD_LENGTH = 12\n\n// =============================================================================\n// AI\n// =============================================================================\n\n/** Default context window for AI models (4096 tokens) */\nexport const DEFAULT_CONTEXT_WINDOW = 4_096\n\n// =============================================================================\n// Circuit Breaker (AWS/Resilience4j pattern)\n// =============================================================================\n\n/**\n * Circuit breaker failure threshold\n *\n * Number of failures in the window before circuit opens.\n */\nexport const CIRCUIT_BREAKER_FAILURE_THRESHOLD = 5\n\n/**\n * Circuit breaker failure window in milliseconds (10 seconds)\n *\n * Time window for counting failures.\n */\nexport const CIRCUIT_BREAKER_WINDOW_MS = 10_000\n\n/**\n * Circuit breaker open duration in milliseconds (30 seconds)\n *\n * How long the circuit stays open before allowing a test request.\n */\nexport const CIRCUIT_BREAKER_OPEN_DURATION_MS = 30_000\n\n// =============================================================================\n// ETag Cache (HTTP conditional requests)\n// =============================================================================\n\n/**\n * Maximum ETag cache entries\n *\n * LRU eviction when exceeded.\n */\nexport const ETAG_CACHE_MAX_ENTRIES = 100\n\n/**\n * ETag cache TTL in milliseconds (5 minutes)\n *\n * How long cached responses are valid.\n */\nexport const ETAG_CACHE_TTL_MS = 5 * 60 * 1000\n","/**\n * Sylphx SDK Error Classes\n *\n * Typed error classes for better error handling and debugging.\n * Compatible with tRPC error codes and provides rich context.\n *\n * @example\n * ```typescript\n * import { SylphxError, isRetryableError, getErrorMessage } from '@sylphx/sdk'\n *\n * try {\n * await sylphx.auth.login.mutate({ email, password })\n * } catch (error) {\n * if (error instanceof SylphxError) {\n * console.log(error.code) // 'UNAUTHORIZED'\n * console.log(error.isRetryable) // false\n * }\n * if (isRetryableError(error)) {\n * // Safe to retry\n * }\n * }\n * ```\n */\n\nimport {\n\tBASE_RETRY_DELAY_MS,\n\tDEFAULT_TIMEOUT_MS,\n\tMAX_RETRY_DELAY_MS,\n} from \"./constants\";\n\n// ============================================================================\n// Error Codes (aligned with tRPC and HTTP semantics)\n// ============================================================================\n\nexport type SylphxErrorCode =\n\t// Client errors (4xx)\n\t| \"BAD_REQUEST\" // 400 - Invalid input\n\t| \"UNAUTHORIZED\" // 401 - Not authenticated\n\t| \"FORBIDDEN\" // 403 - Not authorized\n\t| \"NOT_FOUND\" // 404 - Resource not found\n\t| \"CONFLICT\" // 409 - Resource conflict (e.g., duplicate)\n\t| \"PAYLOAD_TOO_LARGE\" // 413 - Request too large\n\t| \"UNPROCESSABLE_ENTITY\" // 422 - Validation failed\n\t| \"TOO_MANY_REQUESTS\" // 429 - Rate limited\n\t// Server errors (5xx)\n\t| \"INTERNAL_SERVER_ERROR\" // 500 - Server error\n\t| \"NOT_IMPLEMENTED\" // 501 - Feature not available\n\t| \"BAD_GATEWAY\" // 502 - Upstream error\n\t| \"SERVICE_UNAVAILABLE\" // 503 - Temporarily unavailable\n\t| \"GATEWAY_TIMEOUT\" // 504 - Upstream timeout\n\t// Network/Client errors\n\t| \"NETWORK_ERROR\" // Network failure\n\t| \"TIMEOUT\" // Request timeout\n\t| \"ABORTED\" // Request aborted\n\t// SDK-specific\n\t| \"PARSE_ERROR\" // JSON/response parse error\n\t| \"UNKNOWN\"; // Unknown error\n\n/**\n * HTTP status code mapping for error codes\n */\nexport const ERROR_CODE_STATUS: Record<SylphxErrorCode, number> = {\n\tBAD_REQUEST: 400,\n\tUNAUTHORIZED: 401,\n\tFORBIDDEN: 403,\n\tNOT_FOUND: 404,\n\tCONFLICT: 409,\n\tPAYLOAD_TOO_LARGE: 413,\n\tUNPROCESSABLE_ENTITY: 422,\n\tTOO_MANY_REQUESTS: 429,\n\tINTERNAL_SERVER_ERROR: 500,\n\tNOT_IMPLEMENTED: 501,\n\tBAD_GATEWAY: 502,\n\tSERVICE_UNAVAILABLE: 503,\n\tGATEWAY_TIMEOUT: 504,\n\tNETWORK_ERROR: 0,\n\tTIMEOUT: 0,\n\tABORTED: 0,\n\tPARSE_ERROR: 0,\n\tUNKNOWN: 0,\n};\n\n/**\n * Retryable error codes (safe to retry automatically)\n */\nexport const RETRYABLE_CODES: Set<SylphxErrorCode> = new Set([\n\t\"NETWORK_ERROR\",\n\t\"TIMEOUT\",\n\t\"BAD_GATEWAY\",\n\t\"SERVICE_UNAVAILABLE\",\n\t\"GATEWAY_TIMEOUT\",\n\t\"TOO_MANY_REQUESTS\", // With backoff\n\t\"INTERNAL_SERVER_ERROR\", // Sometimes transient\n]);\n\n// ============================================================================\n// Error Classes\n// ============================================================================\n\nexport interface SylphxErrorOptions {\n\t/** Error code for programmatic handling */\n\tcode?: SylphxErrorCode;\n\t/** HTTP status code (inferred from code if not provided) */\n\tstatus?: number;\n\t/** Additional context data */\n\tdata?: Record<string, unknown>;\n\t/** Original error that caused this */\n\tcause?: Error;\n\t/** Retry-After header value (seconds) for rate limiting */\n\tretryAfter?: number;\n}\n\n/**\n * Base error class for all Sylphx SDK errors\n *\n * @example\n * ```typescript\n * throw new SylphxError('Invalid email format', {\n * code: 'BAD_REQUEST',\n * data: { field: 'email' }\n * })\n * ```\n */\nexport class SylphxError extends Error {\n\t/** Error code for programmatic handling */\n\treadonly code: SylphxErrorCode;\n\n\t/** HTTP status code */\n\treadonly status: number;\n\n\t/** Additional context data */\n\treadonly data?: Record<string, unknown>;\n\n\t/** Whether this error is safe to retry */\n\treadonly isRetryable: boolean;\n\n\t/** Retry-After value in seconds (for rate limiting) */\n\treadonly retryAfter?: number;\n\n\t/** Timestamp when error occurred */\n\treadonly timestamp: Date;\n\n\tconstructor(message: string, options: SylphxErrorOptions = {}) {\n\t\tsuper(message, { cause: options.cause });\n\t\tthis.name = \"SylphxError\";\n\t\tthis.code = options.code ?? \"UNKNOWN\";\n\t\tthis.status = options.status ?? ERROR_CODE_STATUS[this.code];\n\t\tthis.data = options.data;\n\t\tthis.isRetryable = RETRYABLE_CODES.has(this.code);\n\t\tthis.retryAfter = options.retryAfter;\n\t\tthis.timestamp = new Date();\n\n\t\t// Maintain proper stack trace in V8\n\t\tif (Error.captureStackTrace) {\n\t\t\tError.captureStackTrace(this, SylphxError);\n\t\t}\n\t}\n\n\t/**\n\t * Convert to JSON-serializable object\n\t */\n\ttoJSON(): Record<string, unknown> {\n\t\treturn {\n\t\t\tname: this.name,\n\t\t\tmessage: this.message,\n\t\t\tcode: this.code,\n\t\t\tstatus: this.status,\n\t\t\tdata: this.data,\n\t\t\tisRetryable: this.isRetryable,\n\t\t\tretryAfter: this.retryAfter,\n\t\t\ttimestamp: this.timestamp.toISOString(),\n\t\t};\n\t}\n}\n\n/**\n * Network-related errors (no response received)\n */\nexport class NetworkError extends SylphxError {\n\tconstructor(\n\t\tmessage = \"Network request failed\",\n\t\toptions?: Omit<SylphxErrorOptions, \"code\">,\n\t) {\n\t\tsuper(message, { ...options, code: \"NETWORK_ERROR\" });\n\t\tthis.name = \"NetworkError\";\n\t}\n}\n\n/**\n * Request timeout errors\n */\nexport class TimeoutError extends SylphxError {\n\t/** Timeout duration in milliseconds */\n\treadonly timeout: number;\n\n\tconstructor(timeout: number, options?: Omit<SylphxErrorOptions, \"code\">) {\n\t\tsuper(`Request timed out after ${timeout}ms`, {\n\t\t\t...options,\n\t\t\tcode: \"TIMEOUT\",\n\t\t});\n\t\tthis.name = \"TimeoutError\";\n\t\tthis.timeout = timeout;\n\t}\n}\n\n/**\n * Authentication errors (401)\n */\nexport class AuthenticationError extends SylphxError {\n\tconstructor(\n\t\tmessage = \"Authentication required\",\n\t\toptions?: Omit<SylphxErrorOptions, \"code\">,\n\t) {\n\t\tsuper(message, { ...options, code: \"UNAUTHORIZED\" });\n\t\tthis.name = \"AuthenticationError\";\n\t}\n}\n\n/**\n * Authorization errors (403)\n */\nexport class AuthorizationError extends SylphxError {\n\tconstructor(\n\t\tmessage = \"Permission denied\",\n\t\toptions?: Omit<SylphxErrorOptions, \"code\">,\n\t) {\n\t\tsuper(message, { ...options, code: \"FORBIDDEN\" });\n\t\tthis.name = \"AuthorizationError\";\n\t}\n}\n\n/**\n * Validation errors (422)\n */\nexport class ValidationError extends SylphxError {\n\t/** Field-specific errors */\n\treadonly fieldErrors?: Record<string, string[]>;\n\n\tconstructor(\n\t\tmessage: string,\n\t\toptions?: Omit<SylphxErrorOptions, \"code\"> & {\n\t\t\tfieldErrors?: Record<string, string[]>;\n\t\t},\n\t) {\n\t\tsuper(message, { ...options, code: \"UNPROCESSABLE_ENTITY\" });\n\t\tthis.name = \"ValidationError\";\n\t\tthis.fieldErrors = options?.fieldErrors;\n\t}\n\n\t/**\n\t * Get error message for a specific field\n\t */\n\tgetFieldError(field: string): string | undefined {\n\t\treturn this.fieldErrors?.[field]?.[0];\n\t}\n}\n\n/**\n * Rate limit metadata (Stripe SDK pattern)\n */\nexport interface RateLimitInfo {\n\t/** Maximum requests allowed in window */\n\tlimit?: number;\n\t/** Remaining requests in current window */\n\tremaining?: number;\n\t/** Unix timestamp (seconds) when limit resets */\n\tresetAt?: number;\n\t/** Seconds until limit resets (Retry-After header) */\n\tretryAfter?: number;\n}\n\n/**\n * Rate limit errors (429)\n *\n * Provides full rate limit metadata for consumer apps to implement\n * proper backoff UI (countdown timers, retry buttons, etc.)\n *\n * @example\n * ```typescript\n * try {\n * await sendEmail(config, options)\n * } catch (error) {\n * if (error instanceof RateLimitError) {\n * const waitSeconds = error.retryAfter ?? 60\n * console.log(`Rate limited. Retry after ${waitSeconds}s`)\n * console.log(`Remaining: ${error.remaining}/${error.limit}`)\n * console.log(`Resets at: ${new Date(error.resetAt! * 1000)}`)\n * }\n * }\n * ```\n */\nexport class RateLimitError extends SylphxError {\n\t/** Maximum requests allowed in window */\n\treadonly limit?: number;\n\n\t/** Remaining requests in current window */\n\treadonly remaining?: number;\n\n\t/** Unix timestamp (seconds) when limit resets */\n\treadonly resetAt?: number;\n\n\tconstructor(\n\t\tmessage = \"Too many requests\",\n\t\toptions?: Omit<SylphxErrorOptions, \"code\"> & RateLimitInfo,\n\t) {\n\t\tsuper(message, { ...options, code: \"TOO_MANY_REQUESTS\" });\n\t\tthis.name = \"RateLimitError\";\n\t\tthis.limit = options?.limit;\n\t\tthis.remaining = options?.remaining;\n\t\tthis.resetAt = options?.resetAt;\n\t}\n\n\t/**\n\t * Get Date when rate limit resets\n\t */\n\tgetResetDate(): Date | undefined {\n\t\treturn this.resetAt ? new Date(this.resetAt * 1000) : undefined;\n\t}\n\n\t/**\n\t * Get human-readable retry message\n\t */\n\tgetRetryMessage(): string {\n\t\tif (this.retryAfter) {\n\t\t\treturn `Please retry after ${this.retryAfter} seconds`;\n\t\t}\n\t\tif (this.resetAt) {\n\t\t\tconst seconds = Math.max(0, this.resetAt - Math.floor(Date.now() / 1000));\n\t\t\treturn `Rate limit resets in ${seconds} seconds`;\n\t\t}\n\t\treturn \"Please wait before retrying\";\n\t}\n}\n\n/**\n * Resource not found errors (404)\n */\nexport class NotFoundError extends SylphxError {\n\t/** Type of resource that wasn't found */\n\treadonly resourceType?: string;\n\n\t/** ID of the resource that wasn't found */\n\treadonly resourceId?: string;\n\n\tconstructor(\n\t\tmessage = \"Resource not found\",\n\t\toptions?: Omit<SylphxErrorOptions, \"code\"> & {\n\t\t\tresourceType?: string;\n\t\t\tresourceId?: string;\n\t\t},\n\t) {\n\t\tsuper(message, { ...options, code: \"NOT_FOUND\" });\n\t\tthis.name = \"NotFoundError\";\n\t\tthis.resourceType = options?.resourceType;\n\t\tthis.resourceId = options?.resourceId;\n\t}\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Check if an error is a Sylphx SDK error\n */\nexport function isSylphxError(error: unknown): error is SylphxError {\n\treturn error instanceof SylphxError;\n}\n\n/**\n * Check if an error is safe to retry\n */\nexport function isRetryableError(error: unknown): boolean {\n\tif (error instanceof SylphxError) {\n\t\treturn error.isRetryable;\n\t}\n\n\t// Check for network errors\n\tif (error instanceof Error) {\n\t\tconst message = error.message.toLowerCase();\n\t\tconst name = error.name.toLowerCase();\n\n\t\t// Network errors\n\t\tif (name === \"typeerror\" && message.includes(\"fetch\")) return true;\n\t\tif (name === \"networkerror\") return true;\n\n\t\t// Timeout patterns\n\t\tif (message.includes(\"timeout\")) return true;\n\t\tif (message.includes(\"timed out\")) return true;\n\n\t\t// Connection errors\n\t\tif (message.includes(\"econnrefused\")) return true;\n\t\tif (message.includes(\"econnreset\")) return true;\n\t\tif (message.includes(\"socket\")) return true;\n\n\t\t// Server errors that might be transient\n\t\tif (message.includes(\"502\")) return true;\n\t\tif (message.includes(\"503\")) return true;\n\t\tif (message.includes(\"504\")) return true;\n\t}\n\n\treturn false;\n}\n\n/**\n * Extract error message from any error type\n */\nexport function getErrorMessage(error: unknown): string {\n\tif (error instanceof Error) {\n\t\treturn error.message;\n\t}\n\tif (typeof error === \"string\") {\n\t\treturn error;\n\t}\n\treturn \"An unknown error occurred\";\n}\n\n/**\n * Get error code from any error type\n */\nexport function getErrorCode(error: unknown): SylphxErrorCode {\n\tif (error instanceof SylphxError) {\n\t\treturn error.code;\n\t}\n\treturn \"UNKNOWN\";\n}\n\n/**\n * Convert any error to SylphxError\n */\nexport function toSylphxError(error: unknown): SylphxError {\n\tif (error instanceof SylphxError) {\n\t\treturn error;\n\t}\n\n\tif (error instanceof Error) {\n\t\t// Try to infer error type from message/name\n\t\tconst message = error.message.toLowerCase();\n\t\tconst name = error.name.toLowerCase();\n\n\t\t// Network errors\n\t\tif (name === \"typeerror\" && message.includes(\"fetch\")) {\n\t\t\treturn new NetworkError(error.message, { cause: error });\n\t\t}\n\t\tif (name === \"aborterror\" || message.includes(\"aborted\")) {\n\t\t\treturn new SylphxError(error.message, { code: \"ABORTED\", cause: error });\n\t\t}\n\n\t\t// Timeout\n\t\tif (message.includes(\"timeout\")) {\n\t\t\treturn new TimeoutError(DEFAULT_TIMEOUT_MS, { cause: error });\n\t\t}\n\n\t\t// HTTP status codes in message\n\t\tif (message.includes(\"401\") || message.includes(\"unauthorized\")) {\n\t\t\treturn new AuthenticationError(error.message, { cause: error });\n\t\t}\n\t\tif (message.includes(\"403\") || message.includes(\"forbidden\")) {\n\t\t\treturn new AuthorizationError(error.message, { cause: error });\n\t\t}\n\t\tif (message.includes(\"404\") || message.includes(\"not found\")) {\n\t\t\treturn new NotFoundError(error.message, { cause: error });\n\t\t}\n\t\tif (message.includes(\"429\") || message.includes(\"rate limit\")) {\n\t\t\treturn new RateLimitError(error.message, { cause: error });\n\t\t}\n\n\t\treturn new SylphxError(error.message, { cause: error });\n\t}\n\n\treturn new SylphxError(getErrorMessage(error));\n}\n\n/**\n * Calculate exponential backoff delay with jitter\n *\n * @param attempt - Retry attempt number (0-indexed)\n * @param baseDelay - Base delay in milliseconds (default: 1000)\n * @param maxDelay - Maximum delay in milliseconds (default: 30000)\n * @returns Delay in milliseconds with jitter\n */\nexport function exponentialBackoff(\n\tattempt: number,\n\tbaseDelay = BASE_RETRY_DELAY_MS,\n\tmaxDelay = MAX_RETRY_DELAY_MS,\n): number {\n\t// Calculate exponential delay: baseDelay * 2^attempt\n\tconst exponentialDelay = baseDelay * Math.pow(2, attempt);\n\n\t// Cap at maxDelay\n\tconst cappedDelay = Math.min(exponentialDelay, maxDelay);\n\n\t// Add jitter (±25% randomness)\n\tconst jitter = cappedDelay * 0.25 * (Math.random() * 2 - 1);\n\n\treturn Math.round(cappedDelay + jitter);\n}\n","/**\n * API Key Validation — Single Source of Truth\n *\n * OAuth 2.0 standard key validation for Sylphx Platform.\n * ALL key validation, sanitization, and environment detection logic lives here.\n *\n * Principles:\n * 1. Fail fast - Invalid input rejected immediately with clear errors\n * 2. Helpful errors - Tell users exactly what's wrong and how to fix it\n * 3. Development warnings - Warn about issues that would fail in production\n * 4. No silent fixes - Transparency over convenience (but warn + continue)\n * 5. Single Source of Truth - All key logic in one place\n *\n * Key Formats (OAuth 2.0 Standard):\n * - App ID: app_(dev|stg|prod)_[identifier] — Public identifier (like OAuth client_id)\n * - Secret Key: sk_(dev|stg|prod)_[identifier] — Server-side only (like OAuth client_secret)\n *\n * Identifier Types:\n * - Customer apps: 32 hex chars\n * - Platform apps: platform_{app-slug} (e.g., platform_sylphx-console)\n */\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/** Environment type derived from key prefix */\nexport type EnvironmentType = \"development\" | \"staging\" | \"production\";\n\n/** Key type - appId (public) or secret (server) */\nexport type KeyType = \"appId\" | \"secret\";\n\n/** Validation result with clear error information */\nexport interface KeyValidationResult {\n\t/** Whether the key is valid (possibly after sanitization) */\n\tvalid: boolean;\n\t/** The sanitized key to use (only if valid) */\n\tsanitizedKey: string;\n\t/** Detected key type */\n\tkeyType?: KeyType;\n\t/** Detected environment */\n\tenvironment?: EnvironmentType;\n\t/** Error message if invalid */\n\terror?: string;\n\t/** Warning message if key was auto-fixed */\n\twarning?: string;\n\t/** Detected issues for debugging */\n\tissues?: string[];\n}\n\n// =============================================================================\n// Patterns — Strict Format Validation\n// =============================================================================\n\n/**\n * App ID pattern: app_(dev|stg|prod)_[identifier]\n * - Prefix: app_ (application identifier, public)\n * - Environment: dev, stg, or prod (NO typos allowed)\n * - Suffix: alphanumeric with underscores/hyphens (hex for apps, or internal identifiers)\n */\nconst APP_ID_PATTERN = /^app_(dev|stg|prod)_[a-z0-9_-]+$/;\n\n/**\n * Secret key pattern: sk_(dev|stg|prod)_[identifier]\n * - Prefix: sk_ (secret key)\n * - Environment: dev, stg, or prod (NO typos allowed)\n * - Suffix: alphanumeric with underscores/hyphens (hex for apps, or internal identifiers)\n */\nconst SECRET_KEY_PATTERN = /^sk_(dev|stg|prod)_[a-z0-9_-]+$/;\n\n/** Environment prefix to type mapping */\nconst ENV_PREFIX_MAP: Record<string, EnvironmentType> = {\n\tdev: \"development\",\n\tstg: \"staging\",\n\tprod: \"production\",\n};\n\n// =============================================================================\n// Core Validation Functions\n// =============================================================================\n\n/**\n * Detect common issues with a key (whitespace, newlines, etc.)\n */\nfunction detectKeyIssues(key: string): string[] {\n\tconst issues: string[] = [];\n\tif (key !== key.trim()) issues.push(\"whitespace\");\n\tif (key.includes(\"\\n\")) issues.push(\"newline\");\n\tif (key.includes(\"\\r\")) issues.push(\"carriage-return\");\n\tif (key.includes(\" \")) issues.push(\"space\");\n\tif (key !== key.toLowerCase()) issues.push(\"uppercase-chars\");\n\treturn issues;\n}\n\n/**\n * Create a helpful warning message for keys that needed sanitization\n */\nfunction createSanitizationWarning(\n\tkeyType: KeyType,\n\tissues: string[],\n\tenvVarName: string,\n): string {\n\tconst keyTypeName = keyType === \"appId\" ? \"App ID\" : \"Secret Key\";\n\treturn (\n\t\t`[Sylphx] ${keyTypeName} contains ${issues.join(\", \")}. ` +\n\t\t`This is commonly caused by Vercel CLI's 'env pull' command.\\n\\n` +\n\t\t`To fix permanently:\\n` +\n\t\t`1. Go to Vercel Dashboard → Your Project → Settings → Environment Variables\\n` +\n\t\t`2. Edit ${envVarName}\\n` +\n\t\t`3. Remove any trailing whitespace or newline characters\\n` +\n\t\t`4. Redeploy your application\\n\\n` +\n\t\t`The SDK will automatically sanitize the key, but fixing the source is recommended.`\n\t);\n}\n\n/**\n * Create a helpful error message for invalid keys\n */\nfunction createInvalidKeyError(\n\tkeyType: KeyType,\n\tkey: string,\n\tenvVarName: string,\n): string {\n\tconst prefix = keyType === \"appId\" ? \"app\" : \"sk\";\n\tconst maskedKey = key.length > 20 ? `${key.slice(0, 20)}...` : key;\n\tconst formatHint = `${prefix}_(dev|stg|prod)_[identifier]`;\n\tconst keyTypeName = keyType === \"appId\" ? \"App ID\" : \"Secret Key\";\n\n\treturn (\n\t\t`[Sylphx] Invalid ${keyTypeName} format.\\n\\n` +\n\t\t`Expected format: ${formatHint}\\n` +\n\t\t`Received: \"${maskedKey}\"\\n\\n` +\n\t\t`Please check your ${envVarName} environment variable.\\n` +\n\t\t`You can find your keys in the Sylphx Console → API Keys.\\n\\n` +\n\t\t`Common issues:\\n` +\n\t\t`• Key has uppercase characters (must be lowercase)\\n` +\n\t\t`• Key has wrong prefix (App ID: app_, Secret Key: sk_)\\n` +\n\t\t`• Key has invalid environment (must be dev, stg, or prod)\\n` +\n\t\t`• Key was copied with extra whitespace`\n\t);\n}\n\n/**\n * Extract environment from a validated key\n */\nfunction extractEnvironment(key: string): EnvironmentType | undefined {\n\t// Match app_ or sk_ prefix followed by environment\n\tconst match = key.match(/^(?:app|sk)_(dev|stg|prod)_/);\n\tif (!match) return undefined;\n\treturn ENV_PREFIX_MAP[match[1]];\n}\n\n/**\n * Internal: Generic key validation logic for specific key types\n */\nfunction validateKeyForType(\n\tkey: string | undefined | null,\n\tkeyType: KeyType,\n\tpattern: RegExp,\n\tenvVarName: string,\n): KeyValidationResult {\n\tconst keyTypeName = keyType === \"appId\" ? \"App ID\" : \"Secret Key\";\n\n\t// Check if key is provided\n\tif (!key) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\tsanitizedKey: \"\",\n\t\t\terror:\n\t\t\t\t`[Sylphx] ${keyTypeName} is required. ` +\n\t\t\t\t`Set ${envVarName} in your environment variables.`,\n\t\t\tissues: [\"missing\"],\n\t\t};\n\t}\n\n\t// Detect issues before validation\n\tconst issues = detectKeyIssues(key);\n\n\t// Check if key matches expected format exactly\n\tif (pattern.test(key)) {\n\t\treturn {\n\t\t\tvalid: true,\n\t\t\tsanitizedKey: key,\n\t\t\tkeyType,\n\t\t\tenvironment: extractEnvironment(key),\n\t\t\tissues: [],\n\t\t};\n\t}\n\n\t// Key doesn't match - try sanitization (trim + lowercase)\n\tconst sanitized = key.trim().toLowerCase();\n\n\tif (pattern.test(sanitized)) {\n\t\t// Sanitization fixes the issue\n\t\treturn {\n\t\t\tvalid: true,\n\t\t\tsanitizedKey: sanitized,\n\t\t\tkeyType,\n\t\t\tenvironment: extractEnvironment(sanitized),\n\t\t\twarning: createSanitizationWarning(keyType, issues, envVarName),\n\t\t\tissues,\n\t\t};\n\t}\n\n\t// Sanitization doesn't fix it - key format is genuinely wrong\n\treturn {\n\t\tvalid: false,\n\t\tsanitizedKey: \"\",\n\t\terror: createInvalidKeyError(keyType, key, envVarName),\n\t\tissues: [...issues, \"invalid-format\"],\n\t};\n}\n\n// =============================================================================\n// Public API — App ID (formerly Publishable Key)\n// =============================================================================\n\n/**\n * Validate an App ID and return detailed results\n *\n * @example\n * ```typescript\n * const result = validateAppId(process.env.NEXT_PUBLIC_SYLPHX_APP_ID)\n * if (!result.valid) {\n * throw new Error(result.error)\n * }\n * if (result.warning) {\n * console.warn(result.warning)\n * }\n * ```\n */\nexport function validateAppId(\n\tkey: string | undefined | null,\n): KeyValidationResult {\n\treturn validateKeyForType(\n\t\tkey,\n\t\t\"appId\",\n\t\tAPP_ID_PATTERN,\n\t\t\"NEXT_PUBLIC_SYLPHX_APP_ID\",\n\t);\n}\n\n/**\n * Validate and sanitize App ID, logging warnings\n *\n * @throws Error if the key is invalid and cannot be sanitized\n * @returns The sanitized App ID\n */\nexport function validateAndSanitizeAppId(\n\tkey: string | undefined | null,\n): string {\n\tconst result = validateAppId(key);\n\n\tif (!result.valid) {\n\t\tthrow new Error(result.error);\n\t}\n\n\tif (result.warning) {\n\t\tconsole.warn(result.warning);\n\t}\n\n\treturn result.sanitizedKey;\n}\n\n// =============================================================================\n// Public API — Secret Keys\n// =============================================================================\n\n/**\n * Validate a secret key and return detailed results\n *\n * @example\n * ```typescript\n * const result = validateSecretKey(process.env.SYLPHX_SECRET_KEY)\n * if (!result.valid) {\n * throw new Error(result.error)\n * }\n * ```\n */\nexport function validateSecretKey(\n\tkey: string | undefined | null,\n): KeyValidationResult {\n\treturn validateKeyForType(\n\t\tkey,\n\t\t\"secret\",\n\t\tSECRET_KEY_PATTERN,\n\t\t\"SYLPHX_SECRET_KEY\",\n\t);\n}\n\n/**\n * Validate and sanitize secret key, logging warnings\n *\n * @throws Error if the key is invalid and cannot be sanitized\n * @returns The sanitized secret key\n */\nexport function validateAndSanitizeSecretKey(\n\tkey: string | undefined | null,\n): string {\n\tconst result = validateSecretKey(key);\n\n\tif (!result.valid) {\n\t\tthrow new Error(result.error);\n\t}\n\n\tif (result.warning) {\n\t\tconsole.warn(result.warning);\n\t}\n\n\treturn result.sanitizedKey;\n}\n\n// =============================================================================\n// Public API — Environment Detection (SSOT)\n// =============================================================================\n\n/**\n * Detect environment type from any key (App ID or Secret Key)\n *\n * @example\n * ```typescript\n * detectEnvironment('sk_dev_abc123') // 'development'\n * detectEnvironment('app_prod_xyz789') // 'production'\n * detectEnvironment('sk_stg_qwe456') // 'staging'\n * ```\n *\n * @throws Error if key format is invalid\n */\nexport function detectEnvironment(key: string): EnvironmentType {\n\t// Validate and sanitize first\n\tconst sanitized = key.trim().toLowerCase();\n\n\t// Check both key types\n\tif (sanitized.startsWith(\"sk_\")) {\n\t\tconst result = validateSecretKey(sanitized);\n\t\tif (!result.valid) {\n\t\t\tthrow new Error(result.error);\n\t\t}\n\t\treturn result.environment!;\n\t}\n\n\tif (sanitized.startsWith(\"app_\")) {\n\t\tconst result = validateAppId(sanitized);\n\t\tif (!result.valid) {\n\t\t\tthrow new Error(result.error);\n\t\t}\n\t\treturn result.environment!;\n\t}\n\n\tthrow new Error(\n\t\t`[Sylphx] Invalid key format. Key must start with 'sk_' (secret) or 'app_' (App ID).`,\n\t);\n}\n\n/**\n * Check if running in development environment based on key\n */\nexport function isDevelopmentKey(key: string): boolean {\n\treturn detectEnvironment(key) === \"development\";\n}\n\n/**\n * Check if running in production environment based on key\n */\nexport function isProductionKey(key: string): boolean {\n\treturn detectEnvironment(key) === \"production\";\n}\n\n// =============================================================================\n// Public API — Cookie Namespace (SSOT)\n// =============================================================================\n\n/**\n * Get the cookie namespace for a given secret key\n *\n * Used by auth middleware to namespace cookies per environment.\n * This prevents dev/staging/prod cookies from conflicting.\n *\n * @example\n * ```typescript\n * getCookieNamespace('sk_dev_abc123') // 'sylphx_dev'\n * getCookieNamespace('sk_prod_xyz789') // 'sylphx_prod'\n * ```\n */\nexport function getCookieNamespace(secretKey: string): string {\n\tconst env = detectEnvironment(secretKey);\n\tconst shortEnv =\n\t\tenv === \"development\" ? \"dev\" : env === \"staging\" ? \"stg\" : \"prod\";\n\treturn `sylphx_${shortEnv}`;\n}\n\n// =============================================================================\n// Public API — Key Type Detection\n// =============================================================================\n\n/**\n * Detect the type of key (App ID or Secret Key)\n *\n * @returns 'appId', 'secret', or null if unknown\n */\nexport function detectKeyType(key: string): KeyType | null {\n\tconst sanitized = key.trim().toLowerCase();\n\tif (sanitized.startsWith(\"app_\")) return \"appId\";\n\tif (sanitized.startsWith(\"sk_\")) return \"secret\";\n\treturn null;\n}\n\n/**\n * Check if a key is an App ID\n */\nexport function isAppId(key: string): boolean {\n\treturn detectKeyType(key) === \"appId\";\n}\n\n/**\n * Check if a key is a secret key\n */\nexport function isSecretKey(key: string): boolean {\n\treturn detectKeyType(key) === \"secret\";\n}\n\n/**\n * Validate any key (auto-detects type)\n *\n * Use this when you accept either App ID or Secret Key.\n * The function auto-detects the key type and validates accordingly.\n *\n * @example\n * ```typescript\n * const result = validateKey(process.env.SYLPHX_SECRET_KEY)\n * if (!result.valid) {\n * throw new Error(result.error)\n * }\n * const sanitizedKey = result.sanitizedKey\n * ```\n */\nexport function validateKey(\n\tkey: string | undefined | null,\n): KeyValidationResult {\n\tconst keyType = key ? detectKeyType(key) : null;\n\n\tif (keyType === \"appId\") {\n\t\treturn validateAppId(key);\n\t}\n\tif (keyType === \"secret\") {\n\t\treturn validateSecretKey(key);\n\t}\n\n\t// Unknown key type - return detailed error\n\treturn {\n\t\tvalid: false,\n\t\tsanitizedKey: \"\",\n\t\terror: key\n\t\t\t? `Invalid key format. Keys must start with 'app_' (App ID) or 'sk_' (Secret Key), followed by environment (dev/stg/prod) and identifier. Got: ${key.slice(0, 20)}...`\n\t\t\t: \"API key is required but was not provided.\",\n\t\tissues: key ? [\"invalid_format\"] : [\"missing\"],\n\t};\n}\n\n/**\n * Validate any key and return sanitized version (throws on error)\n *\n * Use this when you need the key value and want to throw on invalid input.\n */\nexport function validateAndSanitizeKey(key: string | undefined | null): string {\n\tconst result = validateKey(key);\n\tif (!result.valid) {\n\t\tthrow new Error(result.error);\n\t}\n\tif (result.warning) {\n\t\tconsole.warn(`[Sylphx] ${result.warning}`);\n\t}\n\treturn result.sanitizedKey;\n}\n\n// =============================================================================\n// Public API — Runtime Environment Detection\n// =============================================================================\n\n/**\n * Check if we're in development mode (based on NODE_ENV or hostname)\n */\nexport function isDevelopmentRuntime(): boolean {\n\tif (typeof process !== \"undefined\" && process.env) {\n\t\treturn process.env.NODE_ENV === \"development\";\n\t}\n\tif (typeof window !== \"undefined\") {\n\t\treturn (\n\t\t\twindow.location.hostname === \"localhost\" ||\n\t\t\twindow.location.hostname === \"127.0.0.1\"\n\t\t);\n\t}\n\treturn false;\n}\n","/**\n * SDK Configuration\n *\n * Create a config object that can be passed to SDK functions.\n * This is the foundation for the function-based API.\n *\n * Uses appId or secretKey for authentication via x-app-secret header.\n */\n\nimport {\n\tDEFAULT_PLATFORM_URL,\n\tDEFAULT_SDK_API_HOST,\n\tDEFAULT_TIMEOUT_MS,\n\tSDK_API_PATH,\n\tSDK_API_PATH_NEW,\n} from './constants'\nimport {\n\tNetworkError,\n\tRateLimitError,\n\tSylphxError,\n\ttype SylphxErrorCode,\n\tTimeoutError,\n} from './errors'\nimport { validateKey } from './key-validation'\n\n/**\n * Map HTTP status code to SylphxErrorCode\n */\nfunction httpStatusToErrorCode(status: number): SylphxErrorCode {\n\tswitch (status) {\n\t\tcase 400:\n\t\t\treturn 'BAD_REQUEST'\n\t\tcase 401:\n\t\t\treturn 'UNAUTHORIZED'\n\t\tcase 403:\n\t\t\treturn 'FORBIDDEN'\n\t\tcase 404:\n\t\t\treturn 'NOT_FOUND'\n\t\tcase 409:\n\t\t\treturn 'CONFLICT'\n\t\tcase 413:\n\t\t\treturn 'PAYLOAD_TOO_LARGE'\n\t\tcase 422:\n\t\t\treturn 'UNPROCESSABLE_ENTITY'\n\t\tcase 429:\n\t\t\treturn 'TOO_MANY_REQUESTS'\n\t\tcase 500:\n\t\t\treturn 'INTERNAL_SERVER_ERROR'\n\t\tcase 501:\n\t\t\treturn 'NOT_IMPLEMENTED'\n\t\tcase 502:\n\t\t\treturn 'BAD_GATEWAY'\n\t\tcase 503:\n\t\t\treturn 'SERVICE_UNAVAILABLE'\n\t\tcase 504:\n\t\t\treturn 'GATEWAY_TIMEOUT'\n\t\tdefault:\n\t\t\treturn status >= 500 ? 'INTERNAL_SERVER_ERROR' : 'BAD_REQUEST'\n\t}\n}\n\n/**\n * SDK Configuration for Pure Functions\n *\n * This is the configuration object passed to pure SDK functions like\n * `track()`, `signIn()`, `getPlans()`, etc.\n *\n * ## Config Type Hierarchy\n *\n * - `SylphxConfig` (this) — Pure functions, server or client\n * - `SylphxClientConfig` — React hooks return value (appId, platformUrl only)\n * - `SylphxProviderProps` — React provider component props\n * - `SylphxMiddlewareConfig` — Next.js middleware options\n *\n * @example Server-side usage\n * ```ts\n * import { createConfig, track } from '@sylphx/sdk'\n *\n * const config = createConfig({ secretKey: process.env.SYLPHX_SECRET_KEY! })\n * await track(config, { event: 'purchase', properties: { amount: 99 } })\n * ```\n */\nexport interface SylphxConfig {\n\t/**\n\t * Your app key — identifies the app and environment.\n\t *\n\t * Accepts either:\n\t * - Secret key (sk_dev_, sk_stg_, sk_prod_) — full access, server-side only\n\t * - Publishable key (app_dev_, app_stg_, app_prod_) — limited access, safe for client\n\t *\n\t * Get this from Platform Console → Apps → Your App → Environments\n\t */\n\treadonly secretKey?: string\n\t/**\n\t * Platform URL (default: https://sylphx.com)\n\t *\n\t * When `ref` is provided, this is overridden to https://{ref}.api.sylphx.com\n\t * and `apiBasePath` is set to /v1 automatically.\n\t *\n\t * Explicit `platformUrl` takes precedence over `ref`.\n\t */\n\treadonly platformUrl: string\n\t/**\n\t * Project ref — short 16-char alphanumeric nanoid (e.g. \"abc123def456ghij\").\n\t *\n\t * When provided, the SDK targets the new per-project subdomain:\n\t * https://{ref}.api.sylphx.com/v1\n\t *\n\t * Get this from Platform Console → Projects → Your Project → Overview.\n\t */\n\treadonly ref?: string\n\t/**\n\t * API base path appended to platformUrl for all requests.\n\t *\n\t * Default: /api/app/v1 (legacy path via main Next.js app)\n\t * When ref is set: /v1 (new SDK server path)\n\t * When explicit platformUrl is set: /api/app/v1 (unchanged)\n\t */\n\treadonly apiBasePath?: string\n\t/** Optional: Current access token for authenticated requests */\n\treadonly accessToken?: string\n}\n\n/**\n * Configuration input (some fields are optional)\n */\nexport interface SylphxConfigInput {\n\tsecretKey?: string\n\t/**\n\t * Explicit platform URL override.\n\t * Takes precedence over `ref`.\n\t */\n\tplatformUrl?: string\n\t/**\n\t * Project ref — short 16-char alphanumeric nanoid.\n\t *\n\t * When provided (and `platformUrl` is not set), the SDK automatically\n\t * targets: https://{ref}.api.sylphx.com/v1\n\t *\n\t * Get this from Platform Console → Projects → Your Project → Overview.\n\t */\n\tref?: string\n\taccessToken?: string\n}\n\n/**\n * Create a Sylphx configuration object\n *\n * Validates the secretKey (if provided) using the SSOT key-validation module.\n * Sanitizes keys with common issues (whitespace, newlines) and logs warnings.\n * Throws if key format is invalid.\n *\n * @example\n * ```typescript\n * const config = createConfig({\n * secretKey: process.env.SYLPHX_SECRET_KEY!,\n * })\n * ```\n */\nexport function createConfig(input: SylphxConfigInput): SylphxConfig {\n\t// Validate and sanitize secretKey using SSOT if provided\n\tlet secretKey: string | undefined\n\tif (input.secretKey) {\n\t\tconst result = validateKey(input.secretKey)\n\t\tif (!result.valid) {\n\t\t\tthrow new SylphxError(result.error || 'Invalid API key', {\n\t\t\t\tcode: 'BAD_REQUEST',\n\t\t\t\tdata: { issues: result.issues },\n\t\t\t})\n\t\t}\n\t\tif (result.warning) {\n\t\t\tconsole.warn(`[Sylphx] ${result.warning}`)\n\t\t}\n\t\tsecretKey = result.sanitizedKey\n\t}\n\n\t// Resolve platformUrl and apiBasePath\n\t// Priority: explicit platformUrl > ref-based URL > default\n\tlet platformUrl: string\n\tlet apiBasePath: string\n\n\tif (input.platformUrl) {\n\t\t// Explicit override — use as-is with legacy API path\n\t\tplatformUrl = input.platformUrl.trim()\n\t\tapiBasePath = SDK_API_PATH\n\t} else if (input.ref) {\n\t\t// New subdomain-based URL: https://{ref}.api.sylphx.com\n\t\tplatformUrl = `https://${input.ref}.${DEFAULT_SDK_API_HOST}`\n\t\tapiBasePath = SDK_API_PATH_NEW\n\t} else {\n\t\t// Legacy default: https://sylphx.com/api/app/v1\n\t\tplatformUrl = DEFAULT_PLATFORM_URL\n\t\tapiBasePath = SDK_API_PATH\n\t}\n\n\treturn Object.freeze({\n\t\tsecretKey,\n\t\tplatformUrl,\n\t\tref: input.ref,\n\t\tapiBasePath,\n\t\taccessToken: input.accessToken,\n\t})\n}\n\n/**\n * Create a new config with an updated access token\n *\n * @example\n * ```typescript\n * const authenticatedConfig = withToken(config, 'access_token_here')\n * ```\n */\nexport function withToken(config: SylphxConfig, accessToken: string): SylphxConfig {\n\treturn Object.freeze({\n\t\t...config,\n\t\taccessToken,\n\t\t// Preserve apiBasePath and ref from original config\n\t})\n}\n\n/**\n * Internal: Build headers for API requests\n */\nexport function buildHeaders(config: SylphxConfig): Record<string, string> {\n\tconst headers: Record<string, string> = {\n\t\t'Content-Type': 'application/json',\n\t}\n\n\tif (config.secretKey) {\n\t\theaders['x-app-secret'] = config.secretKey\n\t}\n\tif (config.accessToken) {\n\t\theaders['Authorization'] = `Bearer ${config.accessToken}`\n\t}\n\n\treturn headers\n}\n\n/**\n * Internal: Build REST API URL\n *\n * Uses config.apiBasePath which is:\n * - /v1 when targeting the new per-project subdomain ({ref}.api.sylphx.com)\n * - /api/app/v1 when targeting the legacy path (sylphx.com)\n */\nexport function buildApiUrl(config: SylphxConfig, path: string): string {\n\tconst base = config.platformUrl.replace(/\\/$/, '')\n\tconst cleanPath = path.startsWith('/') ? path : `/${path}`\n\treturn `${base}${config.apiBasePath ?? SDK_API_PATH}${cleanPath}`\n}\n\n/**\n * Internal: Call REST API endpoint\n *\n * Features:\n * - Request timeout (default 30s) prevents infinite hangs\n * - Proper HTTP status code mapping to error codes\n * - Safe JSON parsing with error handling\n * - Idempotency key support (Stripe pattern)\n */\nexport async function callApi<TOutput>(\n\tconfig: SylphxConfig,\n\tpath: string,\n\toptions: {\n\t\tmethod?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'\n\t\tbody?: unknown\n\t\tquery?: Record<string, string | number | boolean | undefined>\n\t\t/** Request timeout in milliseconds (default: 30000) */\n\t\ttimeout?: number\n\t\t/** AbortSignal for manual cancellation */\n\t\tsignal?: AbortSignal\n\t\t/**\n\t\t * Idempotency key for safe retries (Stripe pattern)\n\t\t *\n\t\t * When provided, the server will deduplicate requests with the same key\n\t\t * within a 24-hour window. Use for POST/PUT/DELETE operations that\n\t\t * should not be repeated (e.g., email sending, payment processing).\n\t\t *\n\t\t * @example\n\t\t * ```typescript\n\t\t * await sendEmail(config, {\n\t\t * to: 'user@example.com',\n\t\t * subject: 'Welcome!',\n\t\t * idempotencyKey: `welcome-email-${userId}`,\n\t\t * })\n\t\t * ```\n\t\t */\n\t\tidempotencyKey?: string\n\t} = {},\n): Promise<TOutput> {\n\tconst {\n\t\tmethod = 'GET',\n\t\tbody,\n\t\tquery,\n\t\ttimeout = DEFAULT_TIMEOUT_MS,\n\t\tsignal,\n\t\tidempotencyKey,\n\t} = options\n\n\tlet url = buildApiUrl(config, path)\n\n\t// Add query parameters\n\tif (query) {\n\t\tconst params = new URLSearchParams()\n\t\tfor (const [key, value] of Object.entries(query)) {\n\t\t\tif (value !== undefined) {\n\t\t\t\tparams.set(key, String(value))\n\t\t\t}\n\t\t}\n\t\tconst queryString = params.toString()\n\t\tif (queryString) {\n\t\t\turl += `?${queryString}`\n\t\t}\n\t}\n\n\t// Create AbortController for timeout\n\tconst controller = new AbortController()\n\tconst timeoutId = setTimeout(() => controller.abort(), timeout)\n\n\t// Combine user signal with timeout signal\n\tconst combinedSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal\n\n\tconst headers = buildHeaders(config)\n\n\t// Add idempotency key header for safe retries (Stripe pattern)\n\tif (idempotencyKey) {\n\t\theaders['Idempotency-Key'] = idempotencyKey\n\t}\n\n\tconst fetchOptions: RequestInit = {\n\t\tmethod,\n\t\theaders,\n\t\tsignal: combinedSignal,\n\t}\n\n\tif (body) {\n\t\tfetchOptions.body = JSON.stringify(body)\n\t}\n\n\tlet response: Response\n\ttry {\n\t\tresponse = await fetch(url, fetchOptions)\n\t} catch (error) {\n\t\tclearTimeout(timeoutId)\n\n\t\t// Handle abort/timeout\n\t\tif (error instanceof Error) {\n\t\t\tif (error.name === 'AbortError') {\n\t\t\t\t// Check if it was our timeout or user cancellation\n\t\t\t\tif (controller.signal.aborted && !signal?.aborted) {\n\t\t\t\t\tthrow new TimeoutError(timeout)\n\t\t\t\t}\n\t\t\t\tthrow new SylphxError('Request aborted', {\n\t\t\t\t\tcode: 'ABORTED',\n\t\t\t\t\tcause: error,\n\t\t\t\t})\n\t\t\t}\n\t\t\t// Network errors\n\t\t\tthrow new NetworkError(error.message, { cause: error })\n\t\t}\n\t\tthrow new NetworkError('Network request failed')\n\t} finally {\n\t\tclearTimeout(timeoutId)\n\t}\n\n\tif (!response.ok) {\n\t\tconst errorBody = await response.text().catch(() => '')\n\t\tlet errorMessage = 'Request failed'\n\t\tlet errorData: Record<string, unknown> | undefined\n\n\t\t// Safe JSON parsing\n\t\tif (errorBody) {\n\t\t\ttry {\n\t\t\t\tconst parsed = JSON.parse(errorBody) as {\n\t\t\t\t\terror?: { message?: string }\n\t\t\t\t\tmessage?: string\n\t\t\t\t}\n\t\t\t\terrorMessage = parsed.error?.message ?? parsed.message ?? errorMessage\n\t\t\t\terrorData = parsed.error as Record<string, unknown> | undefined\n\t\t\t} catch {\n\t\t\t\t// Not JSON, use status text\n\t\t\t\terrorMessage = response.statusText || errorMessage\n\t\t\t}\n\t\t}\n\n\t\tconst errorCode = httpStatusToErrorCode(response.status)\n\n\t\t// Extract rate limit headers (Stripe SDK pattern)\n\t\tconst retryAfterHeader = response.headers.get('Retry-After')\n\t\tconst rateLimitLimit = response.headers.get('X-RateLimit-Limit')\n\t\tconst rateLimitRemaining = response.headers.get('X-RateLimit-Remaining')\n\t\tconst rateLimitReset = response.headers.get('X-RateLimit-Reset')\n\n\t\tconst retryAfter = retryAfterHeader ? Number.parseInt(retryAfterHeader, 10) : undefined\n\n\t\t// Use specialized RateLimitError for 429 responses\n\t\tif (response.status === 429) {\n\t\t\tthrow new RateLimitError(errorMessage || 'Too many requests', {\n\t\t\t\tstatus: response.status,\n\t\t\t\tdata: errorData,\n\t\t\t\tretryAfter,\n\t\t\t\tlimit: rateLimitLimit ? Number.parseInt(rateLimitLimit, 10) : undefined,\n\t\t\t\tremaining: rateLimitRemaining ? Number.parseInt(rateLimitRemaining, 10) : undefined,\n\t\t\t\tresetAt: rateLimitReset ? Number.parseInt(rateLimitReset, 10) : undefined,\n\t\t\t})\n\t\t}\n\n\t\tthrow new SylphxError(errorMessage, {\n\t\t\tcode: errorCode,\n\t\t\tstatus: response.status,\n\t\t\tdata: errorData,\n\t\t\tretryAfter,\n\t\t})\n\t}\n\n\t// Handle empty responses (204 No Content)\n\tconst text = await response.text()\n\tif (!text) {\n\t\treturn {} as TOutput\n\t}\n\n\t// Safe JSON parsing for response body\n\ttry {\n\t\treturn JSON.parse(text) as TOutput\n\t} catch (error) {\n\t\tthrow new SylphxError('Failed to parse response', {\n\t\t\tcode: 'PARSE_ERROR',\n\t\t\tcause: error instanceof Error ? error : undefined,\n\t\t\tdata: { body: text.slice(0, 200) }, // Include snippet for debugging\n\t\t})\n\t}\n}\n","/**\n * SDK Debug Mode\n *\n * Centralized debug logging for the SDK.\n *\n * Enable via:\n * - Browser: `localStorage.setItem('sylphx_debug', 'true')`\n * - Node.js: `SYLPHX_DEBUG=true`\n *\n * Debug messages are namespaced with [Sylphx] prefix for easy filtering.\n */\n\n// ============================================================================\n// Debug Configuration\n// ============================================================================\n\n/** Storage key for browser-side debug toggle */\nconst DEBUG_STORAGE_KEY = \"sylphx_debug\";\n\n/**\n * Check if debug mode is enabled\n *\n * Checks multiple sources in order:\n * 1. localStorage (browser)\n * 2. SYLPHX_DEBUG environment variable\n * 3. NODE_ENV === 'development' with explicit opt-in\n */\nfunction isDebugEnabled(): boolean {\n\t// Browser environment\n\tif (typeof window !== \"undefined\" && typeof localStorage !== \"undefined\") {\n\t\ttry {\n\t\t\treturn localStorage.getItem(DEBUG_STORAGE_KEY) === \"true\";\n\t\t} catch {\n\t\t\t// localStorage may be blocked in some contexts\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// Node.js environment\n\tif (typeof process !== \"undefined\" && process.env) {\n\t\treturn process.env.SYLPHX_DEBUG === \"true\";\n\t}\n\n\treturn false;\n}\n\n// Cache the debug state to avoid repeated localStorage/env checks\nlet debugModeCache: boolean | null = null;\n\n/**\n * Whether debug mode is currently enabled\n *\n * Cached after first access for performance.\n */\nexport function getDebugMode(): boolean {\n\tif (debugModeCache === null) {\n\t\tdebugModeCache = isDebugEnabled();\n\t}\n\treturn debugModeCache;\n}\n\n/**\n * Reset debug mode cache (for testing)\n */\nexport function resetDebugModeCache(): void {\n\tdebugModeCache = null;\n}\n\n// ============================================================================\n// Debug Logging\n// ============================================================================\n\n/** Debug log categories */\nexport type DebugCategory =\n\t| \"auth\"\n\t| \"api\"\n\t| \"analytics\"\n\t| \"flags\"\n\t| \"storage\"\n\t| \"cache\"\n\t| \"token\"\n\t| \"webhook\"\n\t| \"error\";\n\n/**\n * Log a debug message with category prefix\n *\n * @example\n * ```ts\n * debugLog('auth', 'Token refreshed', { expiresIn: 300 })\n * // [Sylphx auth] Token refreshed { expiresIn: 300 }\n * ```\n */\nexport function debugLog(\n\tcategory: DebugCategory,\n\tmessage: string,\n\tdata?: unknown,\n): void {\n\tif (!getDebugMode()) return;\n\n\tconst prefix = `[Sylphx ${category}]`;\n\n\tif (data !== undefined) {\n\t\tconsole.log(prefix, message, data);\n\t} else {\n\t\tconsole.log(prefix, message);\n\t}\n}\n\n/**\n * Log a debug warning with category prefix\n */\nexport function debugWarn(\n\tcategory: DebugCategory,\n\tmessage: string,\n\tdata?: unknown,\n): void {\n\tif (!getDebugMode()) return;\n\n\tconst prefix = `[Sylphx ${category}]`;\n\n\tif (data !== undefined) {\n\t\tconsole.warn(prefix, message, data);\n\t} else {\n\t\tconsole.warn(prefix, message);\n\t}\n}\n\n/**\n * Log a debug error with category prefix\n *\n * Note: This always logs when debug mode is enabled, regardless of error severity.\n * Production error tracking should use the error tracking service, not this.\n */\nexport function debugError(\n\tcategory: DebugCategory,\n\tmessage: string,\n\terror?: unknown,\n): void {\n\tif (!getDebugMode()) return;\n\n\tconst prefix = `[Sylphx ${category}]`;\n\n\tif (error !== undefined) {\n\t\tconsole.error(prefix, message, error);\n\t} else {\n\t\tconsole.error(prefix, message);\n\t}\n}\n\n// ============================================================================\n// Performance Timing\n// ============================================================================\n\n/**\n * Create a debug timer for measuring operation duration\n *\n * @example\n * ```ts\n * const timer = debugTimer('api', 'Fetching user profile')\n * // ... operation ...\n * timer.end() // Logs duration if debug mode enabled\n * ```\n */\nexport function debugTimer(\n\tcategory: DebugCategory,\n\toperation: string,\n): { end: () => void } {\n\tif (!getDebugMode()) {\n\t\treturn { end: () => {} };\n\t}\n\n\tconst start = performance.now();\n\n\treturn {\n\t\tend() {\n\t\t\tconst duration = performance.now() - start;\n\t\t\tdebugLog(category, `${operation} completed`, {\n\t\t\t\tdurationMs: Math.round(duration),\n\t\t\t});\n\t\t},\n\t};\n}\n\n// ============================================================================\n// Browser Console Helpers\n// ============================================================================\n\n/**\n * Enable debug mode from browser console\n *\n * Call this in the browser console to enable debug logging:\n * ```js\n * window.__sylphx?.enableDebug()\n * ```\n */\nexport function enableDebug(): void {\n\tif (typeof localStorage === \"undefined\") {\n\t\tconsole.warn(\n\t\t\t\"[Sylphx] Debug mode can only be enabled in browser environments\",\n\t\t);\n\t\treturn;\n\t}\n\n\ttry {\n\t\tlocalStorage.setItem(DEBUG_STORAGE_KEY, \"true\");\n\t\tdebugModeCache = true;\n\t\tconsole.log(\n\t\t\t\"[Sylphx] Debug mode enabled. Refresh the page to see debug logs.\",\n\t\t);\n\t} catch (e) {\n\t\tconsole.warn(\"[Sylphx] Failed to enable debug mode:\", e);\n\t}\n}\n\n/**\n * Disable debug mode from browser console\n */\nexport function disableDebug(): void {\n\tif (typeof localStorage === \"undefined\") {\n\t\tconsole.warn(\n\t\t\t\"[Sylphx] Debug mode can only be disabled in browser environments\",\n\t\t);\n\t\treturn;\n\t}\n\n\ttry {\n\t\tlocalStorage.removeItem(DEBUG_STORAGE_KEY);\n\t\tdebugModeCache = false;\n\t\tconsole.log(\"[Sylphx] Debug mode disabled.\");\n\t} catch (e) {\n\t\tconsole.warn(\"[Sylphx] Failed to disable debug mode:\", e);\n\t}\n}\n\n// ============================================================================\n// Global Window Helper (Browser Only)\n// ============================================================================\n\n/**\n * Install debug helpers on window.__sylphx\n *\n * This is called automatically when the SDK is loaded in the browser,\n * providing developers easy console access to debug utilities.\n */\nexport function installGlobalDebugHelpers(): void {\n\tif (typeof window === \"undefined\") return;\n\n\t// Use type assertion to extend window\n\tconst w = window as typeof window & {\n\t\t__sylphx?: {\n\t\t\tenableDebug: typeof enableDebug;\n\t\t\tdisableDebug: typeof disableDebug;\n\t\t\tisDebugEnabled: typeof getDebugMode;\n\t\t};\n\t};\n\n\tw.__sylphx = {\n\t\tenableDebug,\n\t\tdisableDebug,\n\t\tisDebugEnabled: getDebugMode,\n\t};\n}\n","/**\n * REST Client for Sylphx Platform\n *\n * Type-safe REST API client using openapi-fetch with full type inference\n * from OpenAPI specification. No tRPC dependencies.\n *\n * @example\n * ```typescript\n * import { createRestClient } from '@sylphx/sdk'\n *\n * const client = createRestClient({\n * secretKey: process.env.SYLPHX_SECRET_KEY!,\n * })\n *\n * // Full type inference from OpenAPI\n * const { data: user } = await client.GET('/auth/me')\n * const { data: plans } = await client.GET('/billing/plans')\n * const { data: result } = await client.POST('/auth/login', {\n * body: { email, password }\n * })\n * ```\n */\n\nimport createClient, { type Middleware } from \"openapi-fetch\";\nimport {\n\tBASE_RETRY_DELAY_MS,\n\tCIRCUIT_BREAKER_FAILURE_THRESHOLD,\n\tCIRCUIT_BREAKER_OPEN_DURATION_MS,\n\tCIRCUIT_BREAKER_WINDOW_MS,\n\tDEFAULT_PLATFORM_URL,\n\tDEFAULT_TIMEOUT_MS,\n\tETAG_CACHE_MAX_ENTRIES,\n\tETAG_CACHE_TTL_MS,\n\tMAX_RETRY_DELAY_MS,\n\tSDK_API_PATH,\n\tSDK_PLATFORM,\n\tSDK_VERSION,\n} from \"./constants\";\nimport { exponentialBackoff, isRetryableError } from \"./errors\";\nimport type { paths } from \"./generated/api\";\nimport { validateAndSanitizeSecretKey } from \"./key-validation\";\n\n// Re-export types for consumers\nexport type { paths };\n\n/**\n * Retry configuration for automatic request retries\n */\nexport interface RetryConfig {\n\t/** Maximum number of retries (default: 3) */\n\tmaxRetries?: number;\n\t/** Base delay in milliseconds (default: 1000) */\n\tbaseDelay?: number;\n\t/** Maximum delay in milliseconds (default: 30000) */\n\tmaxDelay?: number;\n\t/** Custom function to determine if error is retryable */\n\tshouldRetry?: (status: number, attempt: number) => boolean;\n\t/** Request timeout in milliseconds (default: 30000) */\n\ttimeout?: number;\n}\n\n/**\n * Request deduplication configuration\n */\nexport interface DeduplicationConfig {\n\t/** Enable request deduplication (default: true) */\n\tenabled?: boolean;\n\t/** HTTP methods to deduplicate (default: ['GET']) */\n\tmethods?: (\"GET\" | \"POST\" | \"PUT\" | \"DELETE\")[];\n}\n\n/**\n * Circuit breaker configuration (AWS/Resilience4j pattern)\n *\n * Prevents cascade failures by fast-failing when service is unhealthy.\n * States: CLOSED (normal) → OPEN (failing) → HALF_OPEN (testing)\n */\nexport interface CircuitBreakerConfig {\n\t/** Enable circuit breaker (default: true) */\n\tenabled?: boolean;\n\t/** Number of failures before opening circuit (default: 5) */\n\tfailureThreshold?: number;\n\t/** Time window for counting failures in ms (default: 10000) */\n\twindowMs?: number;\n\t/** How long circuit stays open in ms (default: 30000) */\n\topenDurationMs?: number;\n\t/** Custom function to determine if response is a failure */\n\tisFailure?: (status: number) => boolean;\n}\n\n/**\n * ETag/Conditional request configuration (HTTP caching pattern)\n *\n * Enables HTTP conditional requests with If-None-Match header\n * to avoid re-downloading unchanged data (saves bandwidth).\n */\nexport interface ETagConfig {\n\t/** Enable ETag caching (default: true for GET requests) */\n\tenabled?: boolean;\n\t/** Maximum cache entries (default: 100) */\n\tmaxEntries?: number;\n\t/** Cache TTL in milliseconds (default: 5 minutes) */\n\tttlMs?: number;\n}\n\n/**\n * Configuration for the REST client\n *\n * The app key identifies the app — no separate app ID needed.\n */\nexport interface RestClientConfig {\n\t/**\n\t * Your app key — identifies the app and environment.\n\t *\n\t * Accepts either:\n\t * - Secret key (sk_dev_, sk_stg_, sk_prod_) — full access, server-side only\n\t * - Publishable key (app_dev_, app_stg_, app_prod_) — limited access, safe for client\n\t */\n\tsecretKey: string;\n\t/** Platform URL (default: https://sylphx.com) */\n\tplatformUrl?: string;\n\t/** Retry configuration (default: 3 retries with exponential backoff) */\n\tretry?: RetryConfig | false;\n\t/**\n\t * Request deduplication configuration (default: enabled for GET)\n\t *\n\t * Prevents duplicate concurrent requests for the same resource.\n\t * When multiple components request the same data simultaneously,\n\t * only one API call is made and the result is shared.\n\t */\n\tdeduplication?: DeduplicationConfig | false;\n\t/**\n\t * Circuit breaker configuration (default: enabled)\n\t *\n\t * Prevents cascade failures by fast-failing when service is unhealthy.\n\t * Opens after 5 failures in 10s, stays open for 30s, then allows test request.\n\t */\n\tcircuitBreaker?: CircuitBreakerConfig | false;\n\t/**\n\t * ETag caching configuration (default: enabled for GET)\n\t *\n\t * Uses HTTP conditional requests to avoid re-downloading unchanged data.\n\t * Saves bandwidth by returning 304 Not Modified when content hasn't changed.\n\t */\n\tetag?: ETagConfig | false;\n}\n\n/**\n * Dynamic configuration that can change at runtime (e.g., access token)\n */\nexport interface RestDynamicConfig {\n\t/** Your secret key (sk_* or app_*) — identifies the app */\n\tsecretKey?: string;\n\t/** Platform URL (default: https://sylphx.com) */\n\tplatformUrl?: string;\n\t/** Get the current access token (called on each request) */\n\tgetAccessToken?: () => string | null | undefined;\n\t/** Retry configuration (default: 3 retries with exponential backoff) */\n\tretry?: RetryConfig | false;\n\t/** Request deduplication configuration (default: enabled for GET) */\n\tdeduplication?: DeduplicationConfig | false;\n\t/** Circuit breaker configuration (default: enabled) */\n\tcircuitBreaker?: CircuitBreakerConfig | false;\n\t/** ETag caching configuration (default: enabled for GET) */\n\tetag?: ETagConfig | false;\n}\n\n/**\n * Create auth middleware that adds app credentials, access token, and SDK headers\n */\nfunction createAuthMiddleware(config: RestDynamicConfig): Middleware {\n\treturn {\n\t\tasync onRequest({ request }) {\n\t\t\t// Add SDK identification headers for debugging and analytics\n\t\t\trequest.headers.set(\"X-SDK-Version\", SDK_VERSION);\n\t\t\trequest.headers.set(\"X-SDK-Platform\", SDK_PLATFORM);\n\n\t\t\t// Add secret key if provided — identifies the app\n\t\t\tif (config.secretKey) {\n\t\t\t\trequest.headers.set(\"x-app-secret\", config.secretKey);\n\t\t\t}\n\n\t\t\t// Add access token if available\n\t\t\tconst token = config.getAccessToken?.();\n\t\t\tif (token) {\n\t\t\t\trequest.headers.set(\"Authorization\", `Bearer ${token}`);\n\t\t\t}\n\n\t\t\treturn request;\n\t\t},\n\t};\n}\n\n/**\n * Check if a status code is retryable\n */\nfunction isRetryableStatus(status: number): boolean {\n\treturn status >= 500 || status === 429;\n}\n\n// ============================================================================\n// Request Deduplication (React Query/SWR pattern)\n// ============================================================================\n\n/**\n * In-flight request tracking for deduplication\n *\n * When the same request is made multiple times concurrently,\n * we return the existing promise instead of making a new request.\n * This prevents duplicate API calls and improves efficiency.\n */\nconst inFlightRequests = new Map<string, Promise<Response>>();\n\n/**\n * Generate a unique key for a request (for deduplication)\n */\nasync function getRequestKey(request: Request): Promise<string> {\n\tconst body = request.body ? await request.clone().text() : \"\";\n\treturn `${request.method}:${request.url}:${body}`;\n}\n\n/**\n * Create request deduplication middleware (React Query/SWR pattern)\n *\n * Features:\n * - Deduplicates concurrent identical requests\n * - Only applies to GET requests by default (safe to dedupe)\n * - POST/PUT/DELETE are always executed (mutations must run)\n * - Cleans up in-flight tracking after completion\n *\n * @param config - Whether to enable deduplication (default: GET only)\n */\nfunction createDeduplicationMiddleware(\n\tconfig: {\n\t\tenabled?: boolean;\n\t\tmethods?: (\"GET\" | \"POST\" | \"PUT\" | \"DELETE\")[];\n\t} = {},\n): Middleware {\n\tconst { enabled = true, methods = [\"GET\"] } = config;\n\n\tif (!enabled) {\n\t\treturn {\n\t\t\tasync onRequest({ request }) {\n\t\t\t\treturn request;\n\t\t\t},\n\t\t};\n\t}\n\n\treturn {\n\t\tasync onRequest({ request }) {\n\t\t\t// Only dedupe specified methods (default: GET only)\n\t\t\tif (!methods.includes(request.method as \"GET\")) {\n\t\t\t\treturn request;\n\t\t\t}\n\n\t\t\tconst key = await getRequestKey(request);\n\n\t\t\t// Check if there's an in-flight request\n\t\t\tconst existing = inFlightRequests.get(key);\n\t\t\tif (existing) {\n\t\t\t\t// Return a new Request that will be handled specially in onResponse\n\t\t\t\tconst deduped = request.clone();\n\t\t\t\t(deduped as unknown as { _dedupKey: string })._dedupKey = key;\n\t\t\t\treturn deduped;\n\t\t\t}\n\t\t\t// Mark request key (so onResponse knows to track it)\n\t\t\t(request as unknown as { _dedupKey: string })._dedupKey = key;\n\t\t\treturn request;\n\t\t},\n\t\tasync onResponse({ request, response }) {\n\t\t\tconst key = (request as unknown as { _dedupKey?: string })._dedupKey;\n\t\t\tif (!key) return response;\n\n\t\t\t// If there's already an in-flight request, wait for it\n\t\t\tconst existing = inFlightRequests.get(key);\n\t\t\tif (existing && inFlightRequests.get(key) !== undefined) {\n\t\t\t\t// Another request is in flight, clone its response\n\t\t\t\tconst cachedResponse = await existing;\n\t\t\t\treturn cachedResponse.clone();\n\t\t\t}\n\n\t\t\t// This is the first request, track it\n\t\t\tconst responsePromise = Promise.resolve(response.clone());\n\t\t\tinFlightRequests.set(key, responsePromise);\n\n\t\t\t// Clean up after response is consumed\n\t\t\tresponsePromise.finally(() => {\n\t\t\t\t// Small delay to allow concurrent requests to find the cached response\n\t\t\t\tsetTimeout(() => inFlightRequests.delete(key), 100);\n\t\t\t});\n\n\t\t\treturn response;\n\t\t},\n\t};\n}\n\n// ============================================================================\n// Circuit Breaker (AWS/Resilience4j pattern)\n// ============================================================================\n\n/**\n * Circuit breaker state machine\n *\n * CLOSED: Normal operation, requests pass through\n * OPEN: Service unhealthy, all requests fast-fail\n * HALF_OPEN: Testing recovery, allows one request\n */\nexport type CircuitState = \"CLOSED\" | \"OPEN\" | \"HALF_OPEN\";\n\n/**\n * Error thrown when circuit is open\n */\nexport class CircuitBreakerOpenError extends Error {\n\treadonly remainingMs: number;\n\n\tconstructor(remainingMs: number) {\n\t\tsuper(\n\t\t\t`Circuit breaker is open. Retry after ${Math.ceil(remainingMs / 1000)}s`,\n\t\t);\n\t\tthis.name = \"CircuitBreakerOpenError\";\n\t\tthis.remainingMs = remainingMs;\n\t}\n}\n\n/**\n * Circuit breaker instance with state management\n */\ninterface CircuitBreaker {\n\tstate: CircuitState;\n\tfailures: number[];\n\topenedAt: number | null;\n\tconfig: Required<CircuitBreakerConfig>;\n}\n\n/**\n * Global circuit breaker instance (shared across requests)\n */\nlet circuitBreaker: CircuitBreaker | null = null;\n\n/**\n * Get or create the circuit breaker instance\n */\nfunction getCircuitBreaker(config: CircuitBreakerConfig = {}): CircuitBreaker {\n\tif (!circuitBreaker) {\n\t\tcircuitBreaker = {\n\t\t\tstate: \"CLOSED\",\n\t\t\tfailures: [],\n\t\t\topenedAt: null,\n\t\t\tconfig: {\n\t\t\t\tenabled: config.enabled ?? true,\n\t\t\t\tfailureThreshold:\n\t\t\t\t\tconfig.failureThreshold ?? CIRCUIT_BREAKER_FAILURE_THRESHOLD,\n\t\t\t\twindowMs: config.windowMs ?? CIRCUIT_BREAKER_WINDOW_MS,\n\t\t\t\topenDurationMs:\n\t\t\t\t\tconfig.openDurationMs ?? CIRCUIT_BREAKER_OPEN_DURATION_MS,\n\t\t\t\tisFailure:\n\t\t\t\t\tconfig.isFailure ?? ((status) => status >= 500 || status === 429),\n\t\t\t},\n\t\t};\n\t}\n\treturn circuitBreaker;\n}\n\n/**\n * Record a failure and potentially open the circuit\n */\nfunction recordFailure(cb: CircuitBreaker): void {\n\tconst now = Date.now();\n\n\t// Remove old failures outside the window\n\tcb.failures = cb.failures.filter((t) => now - t < cb.config.windowMs);\n\n\t// Add new failure\n\tcb.failures.push(now);\n\n\t// Check if threshold exceeded\n\tif (cb.failures.length >= cb.config.failureThreshold) {\n\t\tcb.state = \"OPEN\";\n\t\tcb.openedAt = now;\n\t}\n}\n\n/**\n * Record a success and potentially close the circuit\n */\nfunction recordSuccess(cb: CircuitBreaker): void {\n\tif (cb.state === \"HALF_OPEN\") {\n\t\t// Test request succeeded, close the circuit\n\t\tcb.state = \"CLOSED\";\n\t\tcb.failures = [];\n\t\tcb.openedAt = null;\n\t}\n}\n\n/**\n * Check if circuit should allow request\n */\nfunction shouldAllowRequest(cb: CircuitBreaker): {\n\tallowed: boolean;\n\tremainingMs?: number;\n} {\n\tconst now = Date.now();\n\n\tswitch (cb.state) {\n\t\tcase \"CLOSED\":\n\t\t\treturn { allowed: true };\n\n\t\tcase \"OPEN\": {\n\t\t\tconst elapsed = now - (cb.openedAt ?? now);\n\t\t\tif (elapsed >= cb.config.openDurationMs) {\n\t\t\t\t// Timeout expired, transition to half-open\n\t\t\t\tcb.state = \"HALF_OPEN\";\n\t\t\t\treturn { allowed: true };\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tallowed: false,\n\t\t\t\tremainingMs: cb.config.openDurationMs - elapsed,\n\t\t\t};\n\t\t}\n\n\t\tcase \"HALF_OPEN\":\n\t\t\t// Only allow one test request at a time\n\t\t\t// In production, you'd use a flag to track if test is in progress\n\t\t\treturn { allowed: true };\n\n\t\tdefault:\n\t\t\treturn { allowed: true };\n\t}\n}\n\n/**\n * Create circuit breaker middleware (AWS/Resilience4j pattern)\n *\n * Features:\n * - Fast-fails when service is unhealthy (prevents cascade failures)\n * - Auto-recovery with half-open state for testing\n * - Configurable failure threshold and timeout\n * - Only counts server errors (5xx) and rate limits (429)\n */\nfunction createCircuitBreakerMiddleware(\n\tconfig: CircuitBreakerConfig | false | undefined,\n): Middleware {\n\tif (config === false) {\n\t\treturn {\n\t\t\tasync onRequest({ request }) {\n\t\t\t\treturn request;\n\t\t\t},\n\t\t};\n\t}\n\n\tconst cb = getCircuitBreaker(config ?? {});\n\n\treturn {\n\t\tasync onRequest({ request }) {\n\t\t\tif (!cb.config.enabled) {\n\t\t\t\treturn request;\n\t\t\t}\n\n\t\t\tconst check = shouldAllowRequest(cb);\n\t\t\tif (!check.allowed) {\n\t\t\t\tthrow new CircuitBreakerOpenError(check.remainingMs!);\n\t\t\t}\n\n\t\t\treturn request;\n\t\t},\n\t\tasync onResponse({ response }) {\n\t\t\tif (!cb.config.enabled) {\n\t\t\t\treturn response;\n\t\t\t}\n\n\t\t\tif (cb.config.isFailure(response.status)) {\n\t\t\t\trecordFailure(cb);\n\t\t\t} else {\n\t\t\t\trecordSuccess(cb);\n\t\t\t}\n\n\t\t\treturn response;\n\t\t},\n\t};\n}\n\n/**\n * Reset circuit breaker state (for testing)\n */\nexport function resetCircuitBreaker(): void {\n\tcircuitBreaker = null;\n}\n\n/**\n * Get current circuit breaker state (for monitoring)\n */\nexport function getCircuitBreakerState(): {\n\tstate: CircuitState;\n\tfailures: number;\n\topenedAt: number | null;\n} | null {\n\tif (!circuitBreaker) return null;\n\treturn {\n\t\tstate: circuitBreaker.state,\n\t\tfailures: circuitBreaker.failures.length,\n\t\topenedAt: circuitBreaker.openedAt,\n\t};\n}\n\n// ============================================================================\n// ETag Cache (HTTP conditional requests)\n// ============================================================================\n\n/**\n * Cached response entry with ETag\n */\ninterface ETagCacheEntry {\n\tetag: string;\n\tbody: string;\n\ttimestamp: number;\n}\n\n/**\n * ETag cache with LRU eviction\n */\nconst etagCache = new Map<string, ETagCacheEntry>();\n\n/**\n * Generate cache key for request\n */\nfunction getETagCacheKey(request: Request): string {\n\treturn `${request.method}:${request.url}`;\n}\n\n/**\n * Evict oldest entries when cache is full\n */\nfunction evictOldEntries(maxEntries: number, ttlMs: number): void {\n\tconst now = Date.now();\n\n\t// First, remove expired entries\n\tfor (const [key, entry] of etagCache) {\n\t\tif (now - entry.timestamp > ttlMs) {\n\t\t\tetagCache.delete(key);\n\t\t}\n\t}\n\n\t// If still over limit, remove oldest entries (LRU)\n\tif (etagCache.size > maxEntries) {\n\t\tconst entries = Array.from(etagCache.entries());\n\t\tentries.sort((a, b) => a[1].timestamp - b[1].timestamp);\n\n\t\tconst toRemove = entries.slice(0, entries.length - maxEntries);\n\t\tfor (const [key] of toRemove) {\n\t\t\tetagCache.delete(key);\n\t\t}\n\t}\n}\n\n/**\n * Create ETag middleware for HTTP conditional requests\n *\n * Features:\n * - Caches responses with ETag headers\n * - Sends If-None-Match on subsequent requests\n * - Returns cached response on 304 Not Modified\n * - LRU eviction when cache is full\n * - TTL-based expiration\n */\nfunction createETagMiddleware(\n\tconfig: ETagConfig | false | undefined,\n): Middleware {\n\tif (config === false) {\n\t\treturn {\n\t\t\tasync onRequest({ request }) {\n\t\t\t\treturn request;\n\t\t\t},\n\t\t};\n\t}\n\n\tconst {\n\t\tenabled = true,\n\t\tmaxEntries = ETAG_CACHE_MAX_ENTRIES,\n\t\tttlMs = ETAG_CACHE_TTL_MS,\n\t} = config ?? {};\n\n\tif (!enabled) {\n\t\treturn {\n\t\t\tasync onRequest({ request }) {\n\t\t\t\treturn request;\n\t\t\t},\n\t\t};\n\t}\n\n\treturn {\n\t\tasync onRequest({ request }) {\n\t\t\t// Only cache GET requests\n\t\t\tif (request.method !== \"GET\") {\n\t\t\t\treturn request;\n\t\t\t}\n\n\t\t\tconst cacheKey = getETagCacheKey(request);\n\t\t\tconst cached = etagCache.get(cacheKey);\n\n\t\t\tif (cached) {\n\t\t\t\t// Check TTL\n\t\t\t\tif (Date.now() - cached.timestamp > ttlMs) {\n\t\t\t\t\tetagCache.delete(cacheKey);\n\t\t\t\t} else {\n\t\t\t\t\t// Add If-None-Match header\n\t\t\t\t\trequest.headers.set(\"If-None-Match\", cached.etag);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn request;\n\t\t},\n\t\tasync onResponse({ request, response }) {\n\t\t\t// Only cache GET requests\n\t\t\tif (request.method !== \"GET\") {\n\t\t\t\treturn response;\n\t\t\t}\n\n\t\t\tconst cacheKey = getETagCacheKey(request);\n\n\t\t\t// Handle 304 Not Modified\n\t\t\tif (response.status === 304) {\n\t\t\t\tconst cached = etagCache.get(cacheKey);\n\t\t\t\tif (cached) {\n\t\t\t\t\t// Update timestamp (LRU)\n\t\t\t\t\tcached.timestamp = Date.now();\n\n\t\t\t\t\t// Return cached response with original body\n\t\t\t\t\treturn new Response(cached.body, {\n\t\t\t\t\t\tstatus: 200,\n\t\t\t\t\t\theaders: response.headers,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\t// No cache, return original response\n\t\t\t\treturn response;\n\t\t\t}\n\n\t\t\t// Cache successful responses with ETag\n\t\t\tif (response.ok) {\n\t\t\t\tconst etag = response.headers.get(\"ETag\");\n\t\t\t\tif (etag) {\n\t\t\t\t\t// Clone response to read body (can only read once)\n\t\t\t\t\tconst cloned = response.clone();\n\t\t\t\t\tconst body = await cloned.text();\n\n\t\t\t\t\t// Evict old entries if needed\n\t\t\t\t\tevictOldEntries(maxEntries, ttlMs);\n\n\t\t\t\t\t// Cache the response\n\t\t\t\t\tetagCache.set(cacheKey, {\n\t\t\t\t\t\tetag,\n\t\t\t\t\t\tbody,\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn response;\n\t\t},\n\t};\n}\n\n/**\n * Clear ETag cache (for testing)\n */\nexport function clearETagCache(): void {\n\tetagCache.clear();\n}\n\n/**\n * Get ETag cache stats (for monitoring)\n */\nexport function getETagCacheStats(): { size: number; entries: string[] } {\n\treturn {\n\t\tsize: etagCache.size,\n\t\tentries: Array.from(etagCache.keys()),\n\t};\n}\n\n// ============================================================================\n// Retry Middleware\n// ============================================================================\n\n/**\n * Create retry middleware with exponential backoff and timeout\n *\n * Features:\n * - Request timeout (default 30s) prevents infinite hangs\n * - Exponential backoff with jitter for retries\n * - Respects Retry-After header for rate limiting\n * - Stores original body for proper request reconstruction on retry\n */\nfunction createRetryMiddleware(\n\tretryConfig: RetryConfig | false | undefined,\n): Middleware {\n\tif (retryConfig === false) {\n\t\t// No-op middleware - just passes through\n\t\treturn {\n\t\t\tasync onResponse({ response }) {\n\t\t\t\treturn response;\n\t\t\t},\n\t\t};\n\t}\n\n\tconst {\n\t\tmaxRetries = 3,\n\t\tbaseDelay = BASE_RETRY_DELAY_MS,\n\t\tmaxDelay = MAX_RETRY_DELAY_MS,\n\t\tshouldRetry = isRetryableStatus,\n\t\ttimeout = DEFAULT_TIMEOUT_MS,\n\t} = retryConfig ?? {};\n\n\t// Store original body for retries (body can only be read once from Request)\n\tlet originalBody: string | null = null;\n\n\treturn {\n\t\tasync onRequest({ request }) {\n\t\t\t// Store body for potential retries before it gets consumed\n\t\t\tif (request.body) {\n\t\t\t\toriginalBody = await request.clone().text();\n\t\t\t} else {\n\t\t\t\toriginalBody = null;\n\t\t\t}\n\n\t\t\t// Add timeout if not already set\n\t\t\tif (!request.signal) {\n\t\t\t\tconst controller = new AbortController();\n\t\t\t\tsetTimeout(() => controller.abort(), timeout);\n\t\t\t\treturn new Request(request.url, {\n\t\t\t\t\tmethod: request.method,\n\t\t\t\t\theaders: request.headers,\n\t\t\t\t\tbody: originalBody,\n\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn request;\n\t\t},\n\t\tasync onResponse({ response, request }) {\n\t\t\tlet attempt = 0;\n\t\t\tlet currentResponse = response;\n\n\t\t\t// Check if we need to retry using the shouldRetry callback\n\t\t\twhile (\n\t\t\t\tattempt < maxRetries &&\n\t\t\t\tshouldRetry(currentResponse.status, attempt)\n\t\t\t) {\n\t\t\t\tconst retryAfter = currentResponse.headers.get(\"Retry-After\");\n\t\t\t\tconst delay = retryAfter\n\t\t\t\t\t? Number.parseInt(retryAfter, 10) * 1000\n\t\t\t\t\t: exponentialBackoff(attempt, baseDelay, maxDelay);\n\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay));\n\t\t\t\tattempt++;\n\n\t\t\t\t// Create timeout for retry\n\t\t\t\tconst controller = new AbortController();\n\t\t\t\tconst timeoutId = setTimeout(() => controller.abort(), timeout);\n\n\t\t\t\ttry {\n\t\t\t\t\t// Reconstruct request with stored body and new signal\n\t\t\t\t\tconst retryRequest = new Request(request.url, {\n\t\t\t\t\t\tmethod: request.method,\n\t\t\t\t\t\theaders: request.headers,\n\t\t\t\t\t\tbody: originalBody,\n\t\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\t});\n\n\t\t\t\t\tconst newResponse = await fetch(retryRequest);\n\t\t\t\t\tclearTimeout(timeoutId);\n\n\t\t\t\t\t// If successful or non-retryable client error, return\n\t\t\t\t\tif (newResponse.ok || !shouldRetry(newResponse.status, attempt)) {\n\t\t\t\t\t\treturn newResponse;\n\t\t\t\t\t}\n\n\t\t\t\t\tcurrentResponse = newResponse;\n\t\t\t\t} catch (error) {\n\t\t\t\t\tclearTimeout(timeoutId);\n\t\t\t\t\t// On network/timeout error during retry, continue to next attempt\n\t\t\t\t\tif (attempt >= maxRetries) {\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn currentResponse;\n\t\t},\n\t};\n}\n\n/**\n * Create a type-safe REST API client\n *\n * Uses openapi-fetch with full type inference from OpenAPI specification.\n * All endpoints, inputs, and outputs are automatically typed.\n * Includes automatic retry with exponential backoff for transient failures.\n *\n * @example\n * ```typescript\n * const client = createRestClient({\n * secretKey: process.env.SYLPHX_SECRET_KEY!,\n * })\n *\n * // GET requests\n * const { data: user } = await client.GET('/auth/me')\n * const { data: plans } = await client.GET('/billing/plans')\n *\n * // POST requests\n * const { data: result } = await client.POST('/auth/login', {\n * body: { email: 'test@example.com', password: 'secret' }\n * })\n * ```\n */\n/**\n * Validate and sanitize REST client configuration (SSOT helper)\n */\nfunction validateClientConfig(config: {\n\tsecretKey?: string;\n\tplatformUrl?: string;\n}) {\n\treturn {\n\t\tsecretKey: validateAndSanitizeSecretKey(config.secretKey),\n\t\tbaseUrl: (config.platformUrl || DEFAULT_PLATFORM_URL).trim(),\n\t};\n}\n\nexport function createRestClient(config: RestClientConfig) {\n\tconst { secretKey, baseUrl } = validateClientConfig(config);\n\n\tconst client = createClient<paths>({\n\t\tbaseUrl: `${baseUrl}${SDK_API_PATH}`,\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\"x-app-secret\": secretKey,\n\t\t},\n\t});\n\n\t// Add deduplication middleware first (before other middleware)\n\tif (config.deduplication !== false) {\n\t\tclient.use(createDeduplicationMiddleware(config.deduplication));\n\t}\n\n\t// Add circuit breaker middleware (before retry)\n\tif (config.circuitBreaker !== false) {\n\t\tclient.use(createCircuitBreakerMiddleware(config.circuitBreaker));\n\t}\n\n\t// Add ETag caching middleware (before retry, for HTTP conditional requests)\n\tif (config.etag !== false) {\n\t\tclient.use(createETagMiddleware(config.etag));\n\t}\n\n\t// Add retry middleware (last, so it can retry after circuit allows)\n\tclient.use(createRetryMiddleware(config.retry));\n\n\treturn client;\n}\n\n/**\n * Create a dynamic REST client with runtime token injection\n *\n * Use this when you need to inject an access token that may change.\n * Tokens should be read from HttpOnly cookies via a server endpoint,\n * never from localStorage (XSS vulnerability).\n *\n * @example\n * ```typescript\n * // Server-side usage\n * const client = createDynamicRestClient({\n * secretKey: process.env.SYLPHX_SECRET_KEY!,\n * getAccessToken: async () => (await cookies()).get('session')?.value,\n * })\n * ```\n */\nexport function createDynamicRestClient(config: RestDynamicConfig) {\n\tconst { secretKey, baseUrl } = validateClientConfig(config);\n\n\t// Create validated config for middleware\n\tconst validatedConfig: RestDynamicConfig = {\n\t\t...config,\n\t\tsecretKey,\n\t\tplatformUrl: baseUrl,\n\t};\n\n\tconst client = createClient<paths>({\n\t\tbaseUrl: `${baseUrl}${SDK_API_PATH}`,\n\t\theaders: {\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t},\n\t});\n\n\t// Add deduplication middleware first (before other middleware)\n\tif (config.deduplication !== false) {\n\t\tclient.use(createDeduplicationMiddleware(config.deduplication));\n\t}\n\n\t// Add auth middleware (runs on each request)\n\tclient.use(createAuthMiddleware(validatedConfig));\n\n\t// Add circuit breaker middleware (before retry)\n\tif (config.circuitBreaker !== false) {\n\t\tclient.use(createCircuitBreakerMiddleware(config.circuitBreaker));\n\t}\n\n\t// Add ETag caching middleware (before retry, for HTTP conditional requests)\n\tif (config.etag !== false) {\n\t\tclient.use(createETagMiddleware(config.etag));\n\t}\n\n\t// Add retry middleware (last, so it can retry after circuit allows)\n\tclient.use(createRetryMiddleware(config.retry));\n\n\treturn client;\n}\n\n/**\n * Type for the REST client instance\n */\nexport type RestClient = ReturnType<typeof createRestClient>;\nexport type DynamicRestClient = ReturnType<typeof createDynamicRestClient>;\n\n/**\n * Check if a REST response has an error\n */\nexport function hasError<T, E>(response: { data?: T; error?: E }): response is {\n\tdata: undefined;\n\terror: E;\n} {\n\treturn response.error !== undefined;\n}\n\n/**\n * Extract error message from REST error response\n */\nexport function getRestErrorMessage(error: unknown): string {\n\tif (error && typeof error === \"object\" && \"error\" in error) {\n\t\tconst err = error as { error?: { message?: string } };\n\t\treturn err.error?.message ?? \"An unknown error occurred\";\n\t}\n\tif (error instanceof Error) {\n\t\treturn error.message;\n\t}\n\treturn \"An unknown error occurred\";\n}\n","/**\n * Auth Functions\n *\n * Pure functions for authentication - no hidden state.\n * Each function takes config as the first parameter.\n *\n * Uses REST API at /api/sdk/auth/* for all operations.\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { buildHeaders, callApi, type SylphxConfig } from './config'\nimport { SylphxError } from './errors'\nimport type { components } from './generated/api'\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type LoginRequest = components['schemas']['LoginRequest']\nexport type LoginResponse = components['schemas']['LoginResponse']\nexport type RegisterRequest = components['schemas']['RegisterRequest']\nexport type RegisterResponse = components['schemas']['RegisterResponse']\nexport type TokenResponse = components['schemas']['TokenResponse']\nexport type TwoFactorVerifyRequest = components['schemas']['TwoFactorVerifyRequest']\nexport type MeResponse = components['schemas']['MeResponse']\n\n// SDK-specific types (not directly from API schema)\n/**\n * Token introspection result (RFC 7662)\n */\nexport interface TokenIntrospectionResult {\n\t/** Whether the token is active/valid */\n\tactive: boolean\n\t/** Token type (access_token or refresh_token) */\n\ttoken_type?: 'access_token' | 'refresh_token'\n\t/** User ID */\n\tsub?: string\n\t/** User email */\n\temail?: string\n\t/** User name */\n\tname?: string\n\t/** App ID */\n\tclient_id?: string\n\t/** Audience */\n\taud?: string\n\t/** Issuer */\n\tiss?: string\n\t/** Expiration time (Unix timestamp) */\n\texp?: number\n\t/** Issued at time (Unix timestamp) */\n\tiat?: number\n\t/** User role */\n\trole?: string\n\t/** Email verification status */\n\temail_verified?: boolean\n}\n\n/**\n * Token revocation options\n */\nexport interface RevokeTokenOptions {\n\t/** Revoke all tokens for a user in this app */\n\trevokeAll?: boolean\n\t/** User ID (required when revoking all) */\n\tuserId?: string\n}\n\n// SDK-specific types (not in generated API)\nexport interface SessionResult {\n\tuser: {\n\t\tid: string\n\t\temail: string\n\t\tname: string | null\n\t\timage: string | null\n\t\temailVerified: boolean\n\t} | null\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Sign in with email and password\n *\n * @example\n * ```typescript\n * const result = await signIn(config, { email: 'user@example.com', password: 'secret' })\n * if (result.requiresTwoFactor) {\n * // Handle 2FA flow\n * } else {\n * // Save tokens\n * const authenticatedConfig = withToken(config, result.accessToken!)\n * }\n * ```\n */\nexport async function signIn(config: SylphxConfig, input: LoginRequest): Promise<LoginResponse> {\n\treturn callApi<LoginResponse>(config, '/auth/login', {\n\t\tmethod: 'POST',\n\t\tbody: input,\n\t})\n}\n\n/**\n * Sign up with email and password\n *\n * @example\n * ```typescript\n * const result = await signUp(config, {\n * email: 'user@example.com',\n * password: 'secret',\n * name: 'John Doe',\n * })\n * // User needs to verify email\n * ```\n */\nexport async function signUp(\n\tconfig: SylphxConfig,\n\tinput: RegisterRequest,\n): Promise<RegisterResponse> {\n\treturn callApi<RegisterResponse>(config, '/auth/register', {\n\t\tmethod: 'POST',\n\t\tbody: input,\n\t})\n}\n\n/**\n * Sign out (revoke tokens)\n *\n * @example\n * ```typescript\n * await signOut(config)\n * ```\n */\nexport async function signOut(config: SylphxConfig): Promise<void> {\n\tawait callApi<void>(config, '/auth/logout', { method: 'POST' })\n}\n\n/**\n * Refresh access token\n *\n * @example\n * ```typescript\n * const tokens = await refreshToken(config, refreshTokenString)\n * const newConfig = withToken(config, tokens.accessToken)\n * ```\n */\nexport async function refreshToken(config: SylphxConfig, token: string): Promise<TokenResponse> {\n\tconst response = await fetch(`${config.platformUrl}/api/v1/auth/token`, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tbody: JSON.stringify({\n\t\t\tgrant_type: 'refresh_token',\n\t\t\trefresh_token: token,\n\t\t\tclient_secret: config.secretKey,\n\t\t}),\n\t})\n\n\tif (!response.ok) {\n\t\tconst error = await response.json().catch(() => ({ message: 'Token refresh failed' }))\n\t\tthrow new SylphxError(error.message ?? 'Token refresh failed', {\n\t\t\tcode: 'UNAUTHORIZED',\n\t\t})\n\t}\n\n\treturn response.json()\n}\n\n/**\n * Verify email with token\n *\n * @example\n * ```typescript\n * await verifyEmail(config, token)\n * ```\n */\nexport async function verifyEmail(config: SylphxConfig, token: string): Promise<void> {\n\tconst response = await fetch(`${config.platformUrl}/api/v1/auth/verify-email`, {\n\t\tmethod: 'POST',\n\t\theaders: buildHeaders(config),\n\t\tbody: JSON.stringify({ token }),\n\t})\n\n\tif (!response.ok) {\n\t\tconst error = await response.json().catch(() => ({ message: 'Email verification failed' }))\n\t\tthrow new SylphxError(error.message ?? 'Email verification failed', {\n\t\t\tcode: 'BAD_REQUEST',\n\t\t})\n\t}\n}\n\n/**\n * Request password reset email\n *\n * @example\n * ```typescript\n * await forgotPassword(config, 'user@example.com')\n * ```\n */\nexport async function forgotPassword(config: SylphxConfig, email: string): Promise<void> {\n\tawait callApi<{ success: boolean }>(config, '/auth/forgot-password', {\n\t\tmethod: 'POST',\n\t\tbody: { email },\n\t})\n}\n\n/**\n * Reset password with token\n *\n * @example\n * ```typescript\n * await resetPassword(config, { token, password: 'newpassword' })\n * ```\n */\nexport async function resetPassword(\n\tconfig: SylphxConfig,\n\tinput: { token: string; password: string },\n): Promise<void> {\n\tawait callApi<{ success: boolean }>(config, '/auth/reset-password', {\n\t\tmethod: 'POST',\n\t\tbody: { token: input.token, newPassword: input.password },\n\t})\n}\n\n/**\n * Get current session (requires authenticated config)\n *\n * @example\n * ```typescript\n * const session = await getSession(authenticatedConfig)\n * if (session.user) {\n * console.log(`Logged in as ${session.user.email}`)\n * }\n * ```\n */\nexport async function getSession(config: SylphxConfig): Promise<SessionResult> {\n\tif (!config.accessToken) {\n\t\treturn { user: null }\n\t}\n\n\ttry {\n\t\tconst user = await callApi<SessionResult['user']>(config, '/auth/me')\n\t\treturn { user }\n\t} catch {\n\t\treturn { user: null }\n\t}\n}\n\n/**\n * Verify 2FA code (when signIn returns requiresTwoFactor: true)\n *\n * @example\n * ```typescript\n * const result = await signIn(config, credentials)\n * if (result.requiresTwoFactor) {\n * const tokens = await verifyTwoFactor(config, result.userId!, code)\n * }\n * ```\n */\nexport async function verifyTwoFactor(\n\tconfig: SylphxConfig,\n\tuserId: string,\n\tcode: string,\n): Promise<TokenResponse> {\n\treturn callApi<TokenResponse>(config, '/auth/verify-2fa', {\n\t\tmethod: 'POST',\n\t\tbody: { userId, code },\n\t})\n}\n\n/**\n * Introspect a token to check its validity (RFC 7662)\n *\n * Use this to verify token status without decoding. Essential for:\n * - Checking if a token has been revoked\n * - Validating tokens at the edge\n * - Security-critical operations\n *\n * @example\n * ```typescript\n * const result = await introspectToken(config, accessToken)\n * if (!result.active) {\n * // Token is invalid, revoked, or expired\n * await refreshTokens()\n * }\n * ```\n */\nexport async function introspectToken(\n\tconfig: SylphxConfig,\n\ttoken: string,\n\ttokenTypeHint?: 'access_token' | 'refresh_token',\n): Promise<TokenIntrospectionResult> {\n\tconst response = await fetch(`${config.platformUrl}/api/v1/auth/introspect`, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tbody: JSON.stringify({\n\t\t\ttoken,\n\t\t\ttoken_type_hint: tokenTypeHint,\n\t\t\tclient_secret: config.secretKey,\n\t\t}),\n\t})\n\n\tif (!response.ok) {\n\t\t// Per RFC 7662, errors should return inactive\n\t\treturn { active: false }\n\t}\n\n\treturn response.json()\n}\n\n/**\n * Revoke a token (RFC 7009)\n *\n * Use cases:\n * - Sign out user from specific device\n * - Security response to compromised token\n * - User-initiated session termination\n *\n * @example\n * ```typescript\n * // Revoke single refresh token\n * await revokeToken(config, refreshToken)\n *\n * // Revoke all tokens for a user (logout everywhere)\n * await revokeToken(config, '', { revokeAll: true, userId: 'user-123' })\n * ```\n */\nexport async function revokeToken(\n\tconfig: SylphxConfig,\n\ttoken: string,\n\toptions?: RevokeTokenOptions,\n): Promise<void> {\n\tawait fetch(`${config.platformUrl}/api/v1/auth/revoke`, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tbody: JSON.stringify({\n\t\t\ttoken: options?.revokeAll ? undefined : token,\n\t\t\tclient_secret: config.secretKey,\n\t\t\tuser_id: options?.userId,\n\t\t\trevoke_all: options?.revokeAll,\n\t\t}),\n\t})\n\t// Per RFC 7009, always succeeds (200 OK)\n}\n\n/**\n * Revoke all tokens for a user (logout from all devices)\n *\n * Convenience wrapper around revokeToken with revokeAll option.\n *\n * @example\n * ```typescript\n * // After password change, revoke all sessions\n * await revokeAllTokens(config, userId)\n * ```\n */\nexport async function revokeAllTokens(config: SylphxConfig, userId: string): Promise<void> {\n\tawait revokeToken(config, '', { revokeAll: true, userId })\n}\n","/**\n * Analytics Functions\n *\n * Pure functions for event tracking - no hidden state.\n * Events are sent directly to the platform.\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { components } from \"./generated/api\";\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type TrackEventItem = components[\"schemas\"][\"TrackEventItem\"];\nexport type BatchTrackRequest = components[\"schemas\"][\"BatchTrackRequest\"];\nexport type BatchTrackResponse = components[\"schemas\"][\"BatchTrackResponse\"];\nexport type ConversionData = components[\"schemas\"][\"ConversionData\"];\n\n// SDK-specific types for convenience\nexport interface TrackInput {\n\t/** Event name */\n\tevent: string;\n\t/** Event properties */\n\tproperties?: Record<string, unknown>;\n\t/** User ID (optional, for server-side tracking) */\n\tuserId?: string;\n\t/** Anonymous ID (for tracking before user signs in) */\n\tanonymousId?: string;\n\t/** Timestamp (defaults to now) */\n\ttimestamp?: string;\n}\n\nexport interface PageInput {\n\t/** Page name or title */\n\tname: string;\n\t/** Page properties */\n\tproperties?: Record<string, unknown>;\n\t/** User ID (optional) */\n\tuserId?: string;\n\t/** Anonymous ID */\n\tanonymousId?: string;\n}\n\nexport interface IdentifyInput {\n\t/** User ID */\n\tuserId: string;\n\t/** User traits */\n\ttraits?: Record<string, unknown>;\n\t/** Anonymous ID to link */\n\tanonymousId?: string;\n}\n\nexport interface BatchEvent {\n\ttype: \"track\" | \"page\" | \"identify\";\n\tevent?: string;\n\tname?: string;\n\tuserId?: string;\n\tanonymousId?: string;\n\tproperties?: Record<string, unknown>;\n\ttraits?: Record<string, unknown>;\n\ttimestamp?: string;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Track a custom event\n *\n * @example\n * ```typescript\n * await track(config, {\n * event: 'purchase_completed',\n * properties: { amount: 99.99, currency: 'USD' },\n * userId: 'user-123',\n * })\n * ```\n */\nexport async function track(\n\tconfig: SylphxConfig,\n\tinput: TrackInput,\n): Promise<void> {\n\tawait callApi(config, \"/analytics/track\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\tevent: input.event,\n\t\t\tproperties: input.properties ?? {},\n\t\t\tuserId: input.userId,\n\t\t\tanonymousId: input.anonymousId,\n\t\t\ttimestamp: input.timestamp ?? new Date().toISOString(),\n\t\t},\n\t});\n}\n\n/**\n * Track a page view\n *\n * @example\n * ```typescript\n * await page(config, {\n * name: 'Home',\n * properties: { path: '/', referrer: document.referrer },\n * })\n * ```\n */\nexport async function page(\n\tconfig: SylphxConfig,\n\tinput: PageInput,\n): Promise<void> {\n\tawait callApi(config, \"/analytics/page\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\tname: input.name,\n\t\t\tproperties: input.properties ?? {},\n\t\t\tuserId: input.userId,\n\t\t\tanonymousId: input.anonymousId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t},\n\t});\n}\n\n/**\n * Identify a user with traits\n *\n * @example\n * ```typescript\n * await identify(config, {\n * userId: 'user-123',\n * traits: { email: 'user@example.com', plan: 'pro' },\n * anonymousId: 'anon-456', // Links anonymous activity to user\n * })\n * ```\n */\nexport async function identify(\n\tconfig: SylphxConfig,\n\tinput: IdentifyInput,\n): Promise<void> {\n\tawait callApi(config, \"/analytics/identify\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\tuserId: input.userId,\n\t\t\ttraits: input.traits ?? {},\n\t\t\tanonymousId: input.anonymousId,\n\t\t},\n\t});\n}\n\n/**\n * Send multiple events in a single request (batch)\n *\n * @example\n * ```typescript\n * await trackBatch(config, [\n * { type: 'track', event: 'item_viewed', properties: { id: '1' } },\n * { type: 'track', event: 'item_added', properties: { id: '1' } },\n * { type: 'track', event: 'checkout_started' },\n * ])\n * ```\n */\nexport async function trackBatch(\n\tconfig: SylphxConfig,\n\tevents: BatchEvent[],\n): Promise<void> {\n\tawait callApi(config, \"/analytics/batch\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\tevents: events.map((e) => ({\n\t\t\t\tevent:\n\t\t\t\t\te.type === \"track\"\n\t\t\t\t\t\t? e.event\n\t\t\t\t\t\t: e.type === \"page\"\n\t\t\t\t\t\t\t? `$pageview`\n\t\t\t\t\t\t\t: \"$identify\",\n\t\t\t\tproperties: {\n\t\t\t\t\t...e.properties,\n\t\t\t\t\t...(e.type === \"page\" && e.name ? { name: e.name } : {}),\n\t\t\t\t\t...(e.type === \"identify\" && e.traits ? { traits: e.traits } : {}),\n\t\t\t\t},\n\t\t\t\tuserId: e.userId,\n\t\t\t\tanonymousId: e.anonymousId,\n\t\t\t\ttimestamp: e.timestamp ?? new Date().toISOString(),\n\t\t\t})),\n\t\t},\n\t});\n}\n\n// ============================================================================\n// Convenience Functions\n// ============================================================================\n\n/**\n * Generate a random anonymous ID (Segment pattern: pure UUID)\n *\n * Uses UUID v4 format without timestamp component to prevent collision risk\n * in high-traffic applications where multiple users might generate IDs at\n * the same millisecond.\n *\n * @example\n * ```typescript\n * const anonId = generateAnonymousId()\n * await track(config, { event: 'page_view', anonymousId: anonId })\n * ```\n */\nexport function generateAnonymousId(): string {\n\t// Use crypto.randomUUID if available (standard UUID v4)\n\tif (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n\t\treturn crypto.randomUUID();\n\t}\n\t// Fallback for older browsers: generate UUID v4 manually\n\treturn \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n\t\tconst r = (Math.random() * 16) | 0;\n\t\tconst v = c === \"x\" ? r : (r & 0x3) | 0x8;\n\t\treturn v.toString(16);\n\t});\n}\n\n/**\n * Create a tracker bound to a specific config\n *\n * For convenience when making many calls with the same config.\n * This is optional - you can always use the individual functions.\n *\n * @example\n * ```typescript\n * const analytics = createTracker(config)\n *\n * // No need to pass config each time\n * analytics.track('event', { prop: 'value' })\n * analytics.page('Home')\n * analytics.identify('user-123', { email: 'user@example.com' })\n * ```\n */\nexport function createTracker(\n\tconfig: SylphxConfig,\n\tdefaultAnonymousId?: string,\n) {\n\tconst anonymousId = defaultAnonymousId ?? generateAnonymousId();\n\n\treturn {\n\t\ttrack: (\n\t\t\tevent: string,\n\t\t\tproperties?: Record<string, unknown>,\n\t\t\tuserId?: string,\n\t\t) => track(config, { event, properties, userId, anonymousId }),\n\n\t\tpage: (\n\t\t\tname: string,\n\t\t\tproperties?: Record<string, unknown>,\n\t\t\tuserId?: string,\n\t\t) => page(config, { name, properties, userId, anonymousId }),\n\n\t\tidentify: (userId: string, traits?: Record<string, unknown>) =>\n\t\t\tidentify(config, { userId, traits, anonymousId }),\n\n\t\tbatch: (events: BatchEvent[]) =>\n\t\t\ttrackBatch(\n\t\t\t\tconfig,\n\t\t\t\tevents.map((e) => ({\n\t\t\t\t\t...e,\n\t\t\t\t\tanonymousId: e.anonymousId ?? anonymousId,\n\t\t\t\t})),\n\t\t\t),\n\n\t\t/** Get the anonymous ID for this tracker */\n\t\tgetAnonymousId: () => anonymousId,\n\t};\n}\n","/**\n * AI Functions\n *\n * Pure functions for AI completions - Vercel AI SDK style.\n * Direct API calls with natural tree-shaking.\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { type SylphxConfig, buildHeaders } from \"./config\";\nimport { SylphxError } from \"./errors\";\nimport type { components } from \"./generated/api\";\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type AIUsageResponse = components[\"schemas\"][\"AIUsageResponse\"];\nexport type AIRateLimitResponse = components[\"schemas\"][\"AIRateLimitResponse\"];\nexport type AIModelsResponse = components[\"schemas\"][\"AIModelsResponse\"];\nexport type AIModel = components[\"schemas\"][\"AIModel\"];\n\n// ============================================================================\n// SDK-specific Types (OpenAI-compatible chat format)\n// ============================================================================\n\nexport interface ChatMessage {\n\trole: \"system\" | \"user\" | \"assistant\" | \"tool\";\n\tcontent: string | ContentPart[];\n\tname?: string;\n\ttool_call_id?: string;\n\ttool_calls?: ToolCall[];\n\t/** Timestamp for UI display */\n\ttimestamp?: Date;\n}\n\nexport interface ContentPart {\n\ttype: \"text\" | \"image_url\";\n\ttext?: string;\n\timage_url?: { url: string; detail?: \"auto\" | \"low\" | \"high\" };\n}\n\nexport interface ToolCall {\n\tid: string;\n\ttype: \"function\";\n\tfunction: { name: string; arguments: string };\n}\n\nexport interface Tool {\n\ttype: \"function\";\n\tfunction: {\n\t\tname: string;\n\t\tdescription?: string;\n\t\tparameters?: Record<string, unknown>;\n\t};\n}\n\nexport interface ChatInput {\n\t/** Model ID (e.g., 'gpt-4o', 'claude-sonnet-4-20250514') */\n\tmodel: string;\n\t/** Messages */\n\tmessages: ChatMessage[];\n\t/** Temperature (0-2) */\n\ttemperature?: number;\n\t/** Max tokens to generate */\n\tmaxTokens?: number;\n\t/** Top P sampling */\n\ttopP?: number;\n\t/** Frequency penalty */\n\tfrequencyPenalty?: number;\n\t/** Presence penalty */\n\tpresencePenalty?: number;\n\t/** Stop sequences */\n\tstop?: string[];\n\t/** Tools for function calling */\n\ttools?: Tool[];\n\t/** Tool choice */\n\ttoolChoice?:\n\t\t| \"auto\"\n\t\t| \"none\"\n\t\t| { type: \"function\"; function: { name: string } };\n}\n\nexport interface ChatResult {\n\tid: string;\n\tmodel: string;\n\tchoices: Array<{\n\t\tindex: number;\n\t\tmessage: {\n\t\t\trole: \"assistant\";\n\t\t\tcontent: string | null;\n\t\t\ttool_calls?: ToolCall[];\n\t\t};\n\t\tfinishReason: \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\" | null;\n\t}>;\n\tusage: {\n\t\tpromptTokens: number;\n\t\tcompletionTokens: number;\n\t\ttotalTokens: number;\n\t};\n}\n\nexport interface ChatStreamChunk {\n\tid: string;\n\tmodel: string;\n\tchoices: Array<{\n\t\tindex: number;\n\t\tdelta: {\n\t\t\trole?: \"assistant\";\n\t\t\tcontent?: string;\n\t\t\ttool_calls?: ToolCall[];\n\t\t};\n\t\tfinishReason: \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\" | null;\n\t}>;\n}\n\nexport interface EmbedInput {\n\t/** Model ID (e.g., 'text-embedding-3-small') */\n\tmodel: string;\n\t/** Text(s) to embed */\n\tinput: string | string[];\n\t/** Dimensions (for models that support it) */\n\tdimensions?: number;\n}\n\nexport interface EmbedResult {\n\tmodel: string;\n\tdata: Array<{\n\t\tindex: number;\n\t\tembedding: number[];\n\t}>;\n\tusage: {\n\t\tpromptTokens: number;\n\t\ttotalTokens: number;\n\t};\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Create a chat completion\n *\n * @example\n * ```typescript\n * const response = await chat(config, {\n * model: 'gpt-4o',\n * messages: [\n * { role: 'system', content: 'You are a helpful assistant.' },\n * { role: 'user', content: 'Hello!' },\n * ],\n * })\n *\n * console.log(response.choices[0].message.content)\n * ```\n */\nexport async function chat(\n\tconfig: SylphxConfig,\n\tinput: ChatInput,\n): Promise<ChatResult> {\n\tconst response = await fetch(\n\t\t`${config.platformUrl}/api/v1/chat/completions`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t...buildHeaders(config),\n\t\t\t\tAuthorization: `Bearer ${config.secretKey}`,\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\tmodel: input.model,\n\t\t\t\tmessages: input.messages,\n\t\t\t\ttemperature: input.temperature,\n\t\t\t\tmax_tokens: input.maxTokens,\n\t\t\t\ttop_p: input.topP,\n\t\t\t\tfrequency_penalty: input.frequencyPenalty,\n\t\t\t\tpresence_penalty: input.presencePenalty,\n\t\t\t\tstop: input.stop,\n\t\t\t\ttools: input.tools,\n\t\t\t\ttool_choice: input.toolChoice,\n\t\t\t}),\n\t\t},\n\t);\n\n\tif (!response.ok) {\n\t\tconst error = await response\n\t\t\t.json()\n\t\t\t.catch(() => ({ error: { message: \"Chat request failed\" } }));\n\t\tthrow new SylphxError(error?.error?.message ?? \"Chat request failed\", {\n\t\t\tcode: \"BAD_REQUEST\",\n\t\t});\n\t}\n\n\tconst data = await response.json();\n\n\treturn {\n\t\tid: data.id,\n\t\tmodel: data.model,\n\t\tchoices: data.choices.map((c: Record<string, unknown>) => ({\n\t\t\tindex: c.index as number,\n\t\t\tmessage: {\n\t\t\t\trole: \"assistant\" as const,\n\t\t\t\tcontent: (c.message as Record<string, unknown>)?.content as\n\t\t\t\t\t| string\n\t\t\t\t\t| null,\n\t\t\t\ttool_calls: (c.message as Record<string, unknown>)?.tool_calls as\n\t\t\t\t\t| ToolCall[]\n\t\t\t\t\t| undefined,\n\t\t\t},\n\t\t\tfinishReason: c.finish_reason as ChatResult[\"choices\"][0][\"finishReason\"],\n\t\t})),\n\t\tusage: {\n\t\t\tpromptTokens: data.usage.prompt_tokens,\n\t\t\tcompletionTokens: data.usage.completion_tokens,\n\t\t\ttotalTokens: data.usage.total_tokens,\n\t\t},\n\t};\n}\n\n/**\n * Create a streaming chat completion\n *\n * @example\n * ```typescript\n * const stream = chatStream(config, {\n * model: 'gpt-4o',\n * messages: [{ role: 'user', content: 'Write a poem' }],\n * })\n *\n * for await (const chunk of stream) {\n * process.stdout.write(chunk.choices[0].delta.content ?? '')\n * }\n * ```\n */\nexport function chatStream(\n\tconfig: SylphxConfig,\n\tinput: ChatInput,\n): AsyncIterable<ChatStreamChunk> {\n\treturn {\n\t\t[Symbol.asyncIterator]: async function* () {\n\t\t\tconst response = await fetch(\n\t\t\t\t`${config.platformUrl}/api/v1/chat/completions`,\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t...buildHeaders(config),\n\t\t\t\t\t\tAuthorization: `Bearer ${config.secretKey}`,\n\t\t\t\t\t},\n\t\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\t\tmodel: input.model,\n\t\t\t\t\t\tmessages: input.messages,\n\t\t\t\t\t\ttemperature: input.temperature,\n\t\t\t\t\t\tmax_tokens: input.maxTokens,\n\t\t\t\t\t\ttop_p: input.topP,\n\t\t\t\t\t\tfrequency_penalty: input.frequencyPenalty,\n\t\t\t\t\t\tpresence_penalty: input.presencePenalty,\n\t\t\t\t\t\tstop: input.stop,\n\t\t\t\t\t\ttools: input.tools,\n\t\t\t\t\t\ttool_choice: input.toolChoice,\n\t\t\t\t\t\tstream: true,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tif (!response.ok) {\n\t\t\t\tconst error = await response\n\t\t\t\t\t.json()\n\t\t\t\t\t.catch(() => ({ error: { message: \"Stream request failed\" } }));\n\t\t\t\tthrow new SylphxError(error?.error?.message ?? \"Stream request failed\", {\n\t\t\t\t\tcode: \"BAD_REQUEST\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst reader = response.body?.getReader();\n\t\t\tif (!reader) {\n\t\t\t\tthrow new SylphxError(\"No response body\", {\n\t\t\t\t\tcode: \"INTERNAL_SERVER_ERROR\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst decoder = new TextDecoder();\n\t\t\tlet buffer = \"\";\n\n\t\t\ttry {\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\tif (done) break;\n\n\t\t\t\t\tbuffer += decoder.decode(value, { stream: true });\n\t\t\t\t\tconst lines = buffer.split(\"\\n\");\n\t\t\t\t\tbuffer = lines.pop() ?? \"\";\n\n\t\t\t\t\tfor (const line of lines) {\n\t\t\t\t\t\tif (line.startsWith(\"data: \")) {\n\t\t\t\t\t\t\tconst data = line.slice(6).trim();\n\t\t\t\t\t\t\tif (data === \"[DONE]\") return;\n\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst chunk = JSON.parse(data);\n\t\t\t\t\t\t\t\tyield {\n\t\t\t\t\t\t\t\t\tid: chunk.id ?? \"\",\n\t\t\t\t\t\t\t\t\tmodel: chunk.model ?? input.model,\n\t\t\t\t\t\t\t\t\tchoices: (chunk.choices ?? []).map(\n\t\t\t\t\t\t\t\t\t\t(c: Record<string, unknown>) => ({\n\t\t\t\t\t\t\t\t\t\t\tindex: typeof c.index === \"number\" ? c.index : 0,\n\t\t\t\t\t\t\t\t\t\t\tdelta: {\n\t\t\t\t\t\t\t\t\t\t\t\trole: (c.delta as Record<string, unknown>)?.role as\n\t\t\t\t\t\t\t\t\t\t\t\t\t| \"assistant\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t| undefined,\n\t\t\t\t\t\t\t\t\t\t\t\tcontent: (c.delta as Record<string, unknown>)\n\t\t\t\t\t\t\t\t\t\t\t\t\t?.content as string | undefined,\n\t\t\t\t\t\t\t\t\t\t\t\ttool_calls: (c.delta as Record<string, unknown>)\n\t\t\t\t\t\t\t\t\t\t\t\t\t?.tool_calls as ToolCall[] | undefined,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tfinishReason:\n\t\t\t\t\t\t\t\t\t\t\t\t(c.finish_reason as ChatStreamChunk[\"choices\"][0][\"finishReason\"]) ??\n\t\t\t\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// Skip malformed JSON\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\treader.releaseLock();\n\t\t\t}\n\t\t},\n\t};\n}\n\n/**\n * Create embeddings\n *\n * @example\n * ```typescript\n * const result = await embed(config, {\n * model: 'text-embedding-3-small',\n * input: ['Hello world', 'Goodbye world'],\n * })\n *\n * console.log(result.data[0].embedding) // [0.123, -0.456, ...]\n * ```\n */\nexport async function embed(\n\tconfig: SylphxConfig,\n\tinput: EmbedInput,\n): Promise<EmbedResult> {\n\tconst response = await fetch(`${config.platformUrl}/api/v1/embeddings`, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t...buildHeaders(config),\n\t\t\tAuthorization: `Bearer ${config.secretKey}`,\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tmodel: input.model,\n\t\t\tinput: input.input,\n\t\t\tdimensions: input.dimensions,\n\t\t}),\n\t});\n\n\tif (!response.ok) {\n\t\tconst error = await response\n\t\t\t.json()\n\t\t\t.catch(() => ({ error: { message: \"Embedding request failed\" } }));\n\t\tthrow new SylphxError(error?.error?.message ?? \"Embedding request failed\", {\n\t\t\tcode: \"BAD_REQUEST\",\n\t\t});\n\t}\n\n\tconst data = await response.json();\n\n\treturn {\n\t\tmodel: data.model,\n\t\tdata: data.data,\n\t\tusage: {\n\t\t\tpromptTokens: data.usage.prompt_tokens,\n\t\t\ttotalTokens: data.usage.total_tokens,\n\t\t},\n\t};\n}\n\n// ============================================================================\n// Convenience Functions\n// ============================================================================\n\n/**\n * Simple text completion (convenience wrapper)\n *\n * @example\n * ```typescript\n * const text = await complete(config, 'gpt-4o', 'Explain quantum computing in one sentence.')\n * ```\n */\nexport async function complete(\n\tconfig: SylphxConfig,\n\tmodel: string,\n\tprompt: string,\n\toptions?: Omit<ChatInput, \"model\" | \"messages\">,\n): Promise<string> {\n\tconst response = await chat(config, {\n\t\tmodel,\n\t\tmessages: [{ role: \"user\", content: prompt }],\n\t\t...options,\n\t});\n\treturn response.choices[0]?.message.content ?? \"\";\n}\n\n/**\n * Stream text to string (collects all chunks)\n *\n * @example\n * ```typescript\n * const text = await streamToString(config, {\n * model: 'gpt-4o',\n * messages: [{ role: 'user', content: 'Write a haiku' }],\n * })\n * ```\n */\nexport async function streamToString(\n\tconfig: SylphxConfig,\n\tinput: ChatInput,\n): Promise<string> {\n\tlet result = \"\";\n\tfor await (const chunk of chatStream(config, input)) {\n\t\tresult += chunk.choices[0]?.delta.content ?? \"\";\n\t}\n\treturn result;\n}\n","/**\n * Billing Functions\n *\n * Pure functions for billing and subscriptions.\n * Uses REST API at /api/sdk/billing/* for all operations.\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { components } from \"./generated/api\";\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type Plan = components[\"schemas\"][\"Plan\"];\nexport type Subscription = components[\"schemas\"][\"Subscription\"];\nexport type CheckoutRequest = components[\"schemas\"][\"CheckoutRequest\"];\nexport type CheckoutResponse = components[\"schemas\"][\"CheckoutResponse\"];\nexport type PortalRequest = components[\"schemas\"][\"PortalRequest\"];\nexport type PortalResponse = components[\"schemas\"][\"PortalResponse\"];\nexport type BalanceResponse = components[\"schemas\"][\"BalanceResponse\"];\nexport type UsageResponse = components[\"schemas\"][\"UsageResponse\"];\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Get available plans\n *\n * @example\n * ```typescript\n * const plans = await getPlans(config)\n * plans.forEach(plan => console.log(plan.name, plan.priceMonthly))\n * ```\n */\nexport async function getPlans(config: SylphxConfig): Promise<Plan[]> {\n\treturn callApi<Plan[]>(config, \"/billing/plans\");\n}\n\n/**\n * Get user's subscription\n *\n * @example\n * ```typescript\n * const sub = await getSubscription(config, 'user-123')\n * if (sub?.status === 'active') {\n * console.log(`Active plan: ${sub.planSlug}`)\n * }\n * ```\n */\nexport async function getSubscription(\n\tconfig: SylphxConfig,\n\tuserId: string,\n): Promise<Subscription | null> {\n\treturn callApi<Subscription | null>(config, \"/billing/subscription\", {\n\t\tquery: { userId },\n\t});\n}\n\n/**\n * Create a checkout session\n *\n * @example\n * ```typescript\n * const { checkoutUrl } = await createCheckout(config, {\n * userId: 'user-123',\n * planSlug: 'pro',\n * interval: 'monthly',\n * successUrl: 'https://myapp.com/success',\n * cancelUrl: 'https://myapp.com/pricing',\n * })\n *\n * window.location.href = checkoutUrl\n * ```\n */\nexport async function createCheckout(\n\tconfig: SylphxConfig,\n\tinput: CheckoutRequest,\n): Promise<CheckoutResponse> {\n\treturn callApi<CheckoutResponse>(config, \"/billing/checkout\", {\n\t\tmethod: \"POST\",\n\t\tbody: input,\n\t});\n}\n\n/**\n * Create a billing portal session\n *\n * @example\n * ```typescript\n * const { portalUrl } = await createPortalSession(config, {\n * userId: 'user-123',\n * returnUrl: window.location.href,\n * })\n *\n * window.location.href = portalUrl\n * ```\n */\nexport async function createPortalSession(\n\tconfig: SylphxConfig,\n\tinput: PortalRequest,\n): Promise<PortalResponse> {\n\treturn callApi<PortalResponse>(config, \"/billing/portal\", {\n\t\tmethod: \"POST\",\n\t\tbody: input,\n\t});\n}\n\n/**\n * Get billing balance (credits, etc.)\n *\n * @example\n * ```typescript\n * const balance = await getBillingBalance(config)\n * console.log(`Balance: ${balance.balance.currentFormatted}`)\n * ```\n */\nexport async function getBillingBalance(\n\tconfig: SylphxConfig,\n): Promise<BalanceResponse> {\n\treturn callApi<BalanceResponse>(config, \"/billing/balance\");\n}\n\n/**\n * Get billing usage\n *\n * @example\n * ```typescript\n * const usage = await getBillingUsage(config, { month: '2024-01' })\n * ```\n */\nexport async function getBillingUsage(\n\tconfig: SylphxConfig,\n\toptions?: { month?: string },\n): Promise<UsageResponse> {\n\treturn callApi<UsageResponse>(config, \"/billing/usage\", {\n\t\tquery: options?.month ? { month: options.month } : undefined,\n\t});\n}\n","/**\n * Storage Functions\n *\n * Pure functions for file storage operations.\n *\n * ## Industry Patterns Implemented\n * - AbortController cancellation (Vercel Blob pattern)\n * - Exponential backoff with jitter (AWS S3 pattern: 5 retries, 1s base)\n * - Concurrent chunk uploads (Vercel pattern: 3 concurrent)\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { buildHeaders, callApi, type SylphxConfig } from './config'\nimport { BASE_RETRY_DELAY_MS, MAX_RETRY_DELAY_MS } from './constants'\nimport { SylphxError } from './errors'\nimport type { components } from './generated/api'\n\n// Re-export types from SSOT\nexport type {\n\tUploadOptions,\n\tUploadProgressEvent,\n\tUploadResult,\n} from './lib/storage/types'\n\nimport type { UploadProgressEvent, UploadResult } from './lib/storage/types'\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type StorageFile = components['schemas']['StorageFile']\nexport type UploadUrlRequest = components['schemas']['UploadUrlRequest']\nexport type UploadUrlResponse = components['schemas']['UploadUrlResponse']\n\n// ============================================================================\n// Upload Retry Configuration (AWS S3 Pattern)\n// ============================================================================\n\nconst UPLOAD_RETRY_CONFIG = {\n\t/** Maximum number of retry attempts (AWS S3 pattern) */\n\tmaxRetries: 5,\n\t/** Base delay in milliseconds */\n\tbaseDelayMs: BASE_RETRY_DELAY_MS,\n\t/** Maximum delay cap in milliseconds */\n\tmaxDelayMs: MAX_RETRY_DELAY_MS,\n\t/** Jitter type: 'full' for full jitter (AWS recommended) */\n\tjitter: 'full' as const,\n}\n\n/**\n * Calculate exponential backoff delay with full jitter (AWS pattern)\n * Formula: random(0, min(cap, base * 2 ^ attempt))\n */\nfunction calculateBackoffDelay(attempt: number): number {\n\tconst { baseDelayMs, maxDelayMs } = UPLOAD_RETRY_CONFIG\n\tconst exponentialDelay = baseDelayMs * 2 ** attempt\n\tconst cappedDelay = Math.min(exponentialDelay, maxDelayMs)\n\t// Full jitter: random value between 0 and cappedDelay\n\treturn Math.random() * cappedDelay\n}\n\n/**\n * Sleep for a specified duration, respecting AbortSignal\n */\nasync function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(new DOMException('Upload aborted', 'AbortError'))\n\t\t\treturn\n\t\t}\n\n\t\tconst timeoutId = setTimeout(resolve, ms)\n\n\t\tsignal?.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tclearTimeout(timeoutId)\n\t\t\t\treject(new DOMException('Upload aborted', 'AbortError'))\n\t\t\t},\n\t\t\t{ once: true },\n\t\t)\n\t})\n}\n\n/**\n * Check if an error is retryable (network errors, 5xx, 429)\n */\nfunction isRetryableError(error: unknown): boolean {\n\tif (error instanceof DOMException && error.name === 'AbortError') {\n\t\treturn false // Never retry aborted requests\n\t}\n\tif (error instanceof TypeError) {\n\t\treturn true // Network errors\n\t}\n\tif (error instanceof Error && 'status' in error) {\n\t\tconst status = (error as { status: number }).status\n\t\treturn status >= 500 || status === 429 // Server errors or rate limiting\n\t}\n\treturn false\n}\n\n// ============================================================================\n// Types (SDK-specific)\n// ============================================================================\n\nexport interface FileUploadOptions {\n\t/** Folder path */\n\tpath?: string\n\t/** File type (file, avatar, etc.) */\n\ttype?: 'file' | 'avatar'\n\t/** User ID (for avatar uploads) */\n\tuserId?: string\n\t/** Progress callback */\n\tonProgress?: (event: UploadProgressEvent) => void\n\t/**\n\t * Enable multipart upload for large files.\n\t * - `true`: Always use multipart upload\n\t * - `false`: Never use multipart upload\n\t * - `'auto'` (default): Auto-enable for files > 5MB\n\t *\n\t * Multipart uploads support files up to 5TB with better\n\t * reliability for large files.\n\t */\n\tmultipart?: boolean | 'auto'\n\t/**\n\t * AbortSignal to cancel the upload.\n\t * Vercel Blob pattern - enables cancellation of in-progress uploads.\n\t *\n\t * @example\n\t * ```typescript\n\t * const controller = new AbortController()\n\t * // Cancel after 30 seconds\n\t * setTimeout(() => controller.abort(), 30000)\n\t * await uploadFile(config, file, { signal: controller.signal })\n\t * ```\n\t */\n\tsignal?: AbortSignal\n\t/**\n\t * Idempotency key for safe retries (Stripe pattern)\n\t *\n\t * Prevents duplicate uploads if the same request is retried.\n\t * Use a unique key per logical upload operation.\n\t *\n\t * @example `upload-${userId}-${fileName}-${fileHash}`\n\t */\n\tidempotencyKey?: string\n}\n\nexport interface FileInfo {\n\tid: string\n\turl: string\n\tname: string\n\tsize: number\n\tcontentType: string\n\tisPrivate: boolean\n\tcreatedAt: string\n}\n\nexport interface SignedUrlOptions {\n\t/** Expiration in seconds (default: 3600, max: 604800 = 7 days) */\n\texpiresIn?: number\n\t/** Force download (attachment) vs inline display (default: attachment) */\n\tdisposition?: 'attachment' | 'inline'\n\t/** Restrict access to specific user */\n\tuserId?: string\n}\n\nexport interface SignedUrlResult {\n\t/** The signed download URL */\n\turl: string\n\t/** When the URL expires (ISO string) */\n\texpiresAt: string\n\t/** File metadata */\n\tfile: {\n\t\tid: string\n\t\tfilename: string\n\t\tmimeType: string\n\t\tsizeBytes: number\n\t\tisPrivate: boolean\n\t}\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Upload a file to storage\n *\n * Uses client-side upload for optimal performance (direct to CDN).\n *\n * ## Industry-Standard Features\n * - **Cancellation**: AbortController support (Vercel Blob pattern)\n * - **Retry**: Exponential backoff with jitter (AWS S3 pattern: 5 retries)\n * - **Progress**: Real-time upload progress tracking\n *\n * ## File Size Limits\n * - Standard uploads: up to 500MB\n * - For files > 500MB: use React hooks with `multipart: true` (supports up to 5TB)\n *\n * ## Multipart Uploads\n * For large files (> 5MB), multipart uploads provide:\n * - Better reliability with chunked uploads\n * - Resumable upload capability\n * - Progress tracking per chunk\n *\n * Use the `multipart` option or React hooks for large files:\n * ```typescript\n * // React hooks (recommended for large files)\n * const { upload } = useStorage()\n * await upload(file, { multipart: true })\n * ```\n *\n * @example\n * ```typescript\n * const file = new File(['Hello'], 'hello.txt', { type: 'text/plain' })\n * const result = await uploadFile(config, file, {\n * path: 'documents',\n * onProgress: (e) => console.log(`${e.progress}%`),\n * })\n *\n * console.log(result.url)\n * ```\n *\n * @example Cancellation\n * ```typescript\n * const controller = new AbortController()\n * setTimeout(() => controller.abort(), 30000) // Cancel after 30s\n *\n * try {\n * await uploadFile(config, file, { signal: controller.signal })\n * } catch (e) {\n * if (e.name === 'AbortError') {\n * console.log('Upload cancelled')\n * }\n * }\n * ```\n */\nexport async function uploadFile(\n\tconfig: SylphxConfig,\n\tfile: File,\n\toptions?: FileUploadOptions,\n): Promise<UploadResult> {\n\tconst { signal } = options ?? {}\n\n\t// Check if already aborted\n\tif (signal?.aborted) {\n\t\tthrow new DOMException('Upload aborted', 'AbortError')\n\t}\n\n\t// Get upload token from platform (with retry)\n\tlet tokenResponse: Response | null = null\n\tlet lastError: Error | null = null\n\n\tfor (let attempt = 0; attempt <= UPLOAD_RETRY_CONFIG.maxRetries; attempt++) {\n\t\ttry {\n\t\t\ttokenResponse = await fetch(`${config.platformUrl}/api/v1/storage/upload`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: buildHeaders(config),\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tfilename: file.name,\n\t\t\t\t\tcontentType: file.type,\n\t\t\t\t\tsize: file.size,\n\t\t\t\t\tpath: options?.path,\n\t\t\t\t\ttype: options?.type ?? 'file',\n\t\t\t\t\tuserId: options?.userId,\n\t\t\t\t}),\n\t\t\t\tsignal,\n\t\t\t})\n\n\t\t\tif (tokenResponse.ok) {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// Check if error is retryable\n\t\t\tif (tokenResponse.status >= 500 || tokenResponse.status === 429) {\n\t\t\t\tif (attempt < UPLOAD_RETRY_CONFIG.maxRetries) {\n\t\t\t\t\tconst delay = calculateBackoffDelay(attempt)\n\t\t\t\t\tawait sleep(delay, signal)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Non-retryable error\n\t\t\tconst error = await tokenResponse\n\t\t\t\t.json()\n\t\t\t\t.catch(() => ({ message: 'Failed to get upload token' }))\n\t\t\tthrow new SylphxError(error.message ?? 'Failed to get upload token', {\n\t\t\t\tcode: 'BAD_REQUEST',\n\t\t\t})\n\t\t} catch (error) {\n\t\t\tif (error instanceof DOMException && error.name === 'AbortError') {\n\t\t\t\tthrow error // Don't retry aborted requests\n\t\t\t}\n\n\t\t\tlastError = error instanceof Error ? error : new Error(String(error))\n\n\t\t\tif (isRetryableError(error) && attempt < UPLOAD_RETRY_CONFIG.maxRetries) {\n\t\t\t\tconst delay = calculateBackoffDelay(attempt)\n\t\t\t\tawait sleep(delay, signal)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tthrow lastError\n\t\t}\n\t}\n\n\tif (!tokenResponse?.ok) {\n\t\tthrow (\n\t\t\tlastError ??\n\t\t\tnew SylphxError('Failed to get upload token after retries', {\n\t\t\t\tcode: 'BAD_REQUEST',\n\t\t\t})\n\t\t)\n\t}\n\n\tconst { uploadUrl, publicUrl } = await tokenResponse.json()\n\n\t// Upload directly to storage with retry\n\treturn executeUploadWithRetry(file, uploadUrl, publicUrl, options)\n}\n\n/**\n * Execute the actual upload with retry logic\n */\nasync function executeUploadWithRetry(\n\tfile: File,\n\tuploadUrl: string,\n\tpublicUrl: string,\n\toptions?: FileUploadOptions,\n): Promise<UploadResult> {\n\tconst { signal } = options ?? {}\n\tlet lastError: Error | null = null\n\n\tfor (let attempt = 0; attempt <= UPLOAD_RETRY_CONFIG.maxRetries; attempt++) {\n\t\ttry {\n\t\t\treturn await executeUpload(file, uploadUrl, publicUrl, options)\n\t\t} catch (error) {\n\t\t\tif (error instanceof DOMException && error.name === 'AbortError') {\n\t\t\t\tthrow error // Don't retry aborted requests\n\t\t\t}\n\n\t\t\tlastError = error instanceof Error ? error : new Error(String(error))\n\n\t\t\tif (isRetryableError(error) && attempt < UPLOAD_RETRY_CONFIG.maxRetries) {\n\t\t\t\tconst delay = calculateBackoffDelay(attempt)\n\t\t\t\tawait sleep(delay, signal)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tthrow lastError\n\t\t}\n\t}\n\n\tthrow lastError ?? new Error('Upload failed after retries')\n}\n\n/**\n * Execute a single upload attempt with XHR (for progress tracking)\n */\nfunction executeUpload(\n\tfile: File,\n\tuploadUrl: string,\n\tpublicUrl: string,\n\toptions?: FileUploadOptions,\n): Promise<UploadResult> {\n\tconst { signal, onProgress } = options ?? {}\n\n\treturn new Promise<UploadResult>((resolve, reject) => {\n\t\tconst xhr = new XMLHttpRequest()\n\n\t\t// Handle abort signal\n\t\tconst handleAbort = () => {\n\t\t\txhr.abort()\n\t\t\treject(new DOMException('Upload aborted', 'AbortError'))\n\t\t}\n\n\t\tif (signal?.aborted) {\n\t\t\treject(new DOMException('Upload aborted', 'AbortError'))\n\t\t\treturn\n\t\t}\n\n\t\tsignal?.addEventListener('abort', handleAbort, { once: true })\n\n\t\txhr.upload.addEventListener('progress', (event) => {\n\t\t\tif (event.lengthComputable && onProgress) {\n\t\t\t\tonProgress({\n\t\t\t\t\tloaded: event.loaded,\n\t\t\t\t\ttotal: event.total,\n\t\t\t\t\tprogress: Math.round((event.loaded / event.total) * 100),\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\n\t\txhr.addEventListener('load', () => {\n\t\t\tsignal?.removeEventListener('abort', handleAbort)\n\n\t\t\tif (xhr.status >= 200 && xhr.status < 300) {\n\t\t\t\tresolve({\n\t\t\t\t\turl: publicUrl,\n\t\t\t\t\tpathname: options?.path ? `${options.path}/${file.name}` : file.name,\n\t\t\t\t\tcontentType: file.type,\n\t\t\t\t\tsize: file.size,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tconst error = new Error(`Upload failed with status ${xhr.status}`) as Error & {\n\t\t\t\t\tstatus: number\n\t\t\t\t}\n\t\t\t\terror.status = xhr.status\n\t\t\t\treject(error)\n\t\t\t}\n\t\t})\n\n\t\txhr.addEventListener('error', () => {\n\t\t\tsignal?.removeEventListener('abort', handleAbort)\n\t\t\treject(new TypeError('Network error during upload'))\n\t\t})\n\n\t\txhr.addEventListener('abort', () => {\n\t\t\tsignal?.removeEventListener('abort', handleAbort)\n\t\t\treject(new DOMException('Upload aborted', 'AbortError'))\n\t\t})\n\n\t\txhr.open('PUT', uploadUrl)\n\t\txhr.setRequestHeader('Content-Type', file.type)\n\t\txhr.send(file)\n\t})\n}\n\n/**\n * Upload a user avatar\n *\n * @example\n * ```typescript\n * const avatar = await uploadAvatar(config, file, 'user-123')\n * console.log(avatar.url)\n * ```\n */\nexport async function uploadAvatar(\n\tconfig: SylphxConfig,\n\tfile: File,\n\tuserId: string,\n\toptions?: Pick<FileUploadOptions, 'onProgress'>,\n): Promise<UploadResult> {\n\treturn uploadFile(config, file, {\n\t\t...options,\n\t\ttype: 'avatar',\n\t\tuserId,\n\t})\n}\n\n/**\n * Delete a file\n *\n * @example\n * ```typescript\n * await deleteFile(config, 'file-123')\n * ```\n */\nexport async function deleteFile(config: SylphxConfig, fileId: string): Promise<void> {\n\tawait callApi(config, `/storage/files/${fileId}`, { method: 'DELETE' })\n}\n\n/**\n * Get a file's URL by ID\n *\n * @example\n * ```typescript\n * const url = await getFileUrl(config, 'file-123')\n * ```\n */\nexport async function getFileUrl(config: SylphxConfig, fileId: string): Promise<string> {\n\tconst data = await callApi<{ url: string }>(config, `/storage/files/${fileId}`, { method: 'GET' })\n\treturn data.url\n}\n\n/**\n * Get file info by ID\n *\n * @example\n * ```typescript\n * const info = await getFileInfo(config, 'file-123')\n * console.log(info.name, info.size)\n * ```\n */\nexport async function getFileInfo(config: SylphxConfig, fileId: string): Promise<FileInfo> {\n\treturn callApi<FileInfo>(config, `/storage/files/${fileId}`, {\n\t\tmethod: 'GET',\n\t})\n}\n\n/**\n * Generate a signed URL for accessing a private file\n *\n * Signed URLs provide time-limited access to private files without\n * exposing permanent URLs. Useful for:\n * - Secure document downloads\n * - Private media streaming\n * - Temporary file sharing\n *\n * @example\n * ```typescript\n * // Generate a download URL valid for 1 hour\n * const { url, expiresAt } = await getSignedUrl(config, 'file-123')\n *\n * // Generate an inline preview URL valid for 5 minutes\n * const preview = await getSignedUrl(config, 'file-123', {\n * expiresIn: 300,\n * disposition: 'inline',\n * })\n *\n * // Restrict access to a specific user\n * const userOnly = await getSignedUrl(config, 'file-123', {\n * userId: 'user-456',\n * })\n * ```\n */\nexport async function getSignedUrl(\n\tconfig: SylphxConfig,\n\tfileId: string,\n\toptions?: SignedUrlOptions,\n): Promise<SignedUrlResult> {\n\treturn callApi<SignedUrlResult>(config, '/storage/signed-url', {\n\t\tmethod: 'POST',\n\t\tbody: {\n\t\t\tfileId,\n\t\t\t...options,\n\t\t},\n\t})\n}\n","/**\n * Notifications Functions\n *\n * Pure functions for push notifications.\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { components } from \"./generated/api\";\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type RegisterPushRequest = components[\"schemas\"][\"RegisterPushRequest\"];\nexport type RegisterPushResponse =\n\tcomponents[\"schemas\"][\"RegisterPushResponse\"];\nexport type UnregisterPushRequest =\n\tcomponents[\"schemas\"][\"UnregisterPushRequest\"];\nexport type PushPreferencesResponse =\n\tcomponents[\"schemas\"][\"PushPreferencesResponse\"];\nexport type InAppMessage = components[\"schemas\"][\"InAppMessage\"];\nexport type InAppMessagesResponse =\n\tcomponents[\"schemas\"][\"InAppMessagesResponse\"];\nexport type MobileConfigResponse =\n\tcomponents[\"schemas\"][\"MobileConfigResponse\"];\nexport type MobileDevice = components[\"schemas\"][\"MobileDevice\"];\n\n// SDK-specific types for convenience\nexport interface PushSubscription {\n\tendpoint: string;\n\tkeys: {\n\t\tp256dh: string;\n\t\tauth: string;\n\t};\n}\n\nexport interface PushNotification {\n\ttitle: string;\n\tbody: string;\n\ticon?: string;\n\turl?: string;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Register a push subscription\n *\n * @example\n * ```typescript\n * // Get subscription from browser\n * const registration = await navigator.serviceWorker.ready\n * const sub = await registration.pushManager.subscribe({\n * userVisibleOnly: true,\n * applicationServerKey: vapidPublicKey,\n * })\n *\n * // Register with platform\n * await registerPush(config, {\n * endpoint: sub.endpoint,\n * keys: {\n * p256dh: sub.toJSON().keys!.p256dh,\n * auth: sub.toJSON().keys!.auth,\n * },\n * })\n * ```\n */\nexport async function registerPush(\n\tconfig: SylphxConfig,\n\tsubscription: PushSubscription,\n): Promise<void> {\n\tawait callApi(config, \"/notifications/register\", {\n\t\tmethod: \"POST\",\n\t\tbody: { subscription },\n\t});\n}\n\n/**\n * Unregister a push subscription\n *\n * @example\n * ```typescript\n * await unregisterPush(config, subscription.endpoint)\n * ```\n */\nexport async function unregisterPush(\n\tconfig: SylphxConfig,\n\tendpoint: string,\n): Promise<void> {\n\tawait callApi(config, \"/notifications/unregister\", {\n\t\tmethod: \"POST\",\n\t\tbody: { endpoint },\n\t});\n}\n\n/**\n * Send a push notification to a user (admin only)\n *\n * @example\n * ```typescript\n * await sendPush(config, 'user-123', {\n * title: 'New message',\n * body: 'You have a new message',\n * url: '/messages',\n * })\n * ```\n */\nexport async function sendPush(\n\tconfig: SylphxConfig,\n\tuserId: string,\n\tnotification: PushNotification,\n): Promise<{ sentTo: number; expired: number }> {\n\treturn callApi(config, \"/notifications/send\", {\n\t\tmethod: \"POST\",\n\t\tbody: { userId, ...notification },\n\t});\n}\n\n/**\n * Get push notification preferences\n *\n * @example\n * ```typescript\n * const prefs = await getPushPreferences(config)\n * ```\n */\nexport async function getPushPreferences(\n\tconfig: SylphxConfig,\n): Promise<{ enabled: boolean; categories: Record<string, boolean> }> {\n\treturn callApi(config, \"/notifications/preferences\", { method: \"GET\" });\n}\n\n/**\n * Update push notification preferences\n *\n * @example\n * ```typescript\n * await updatePushPreferences(config, {\n * enabled: true,\n * categories: { marketing: false, updates: true },\n * })\n * ```\n */\nexport async function updatePushPreferences(\n\tconfig: SylphxConfig,\n\tpreferences: { enabled?: boolean; categories?: Record<string, boolean> },\n): Promise<void> {\n\tawait callApi(config, \"/notifications/preferences\", {\n\t\tmethod: \"PUT\",\n\t\tbody: preferences,\n\t});\n}\n","/**\n * Push Notification Service Worker Template\n *\n * This module provides a service worker implementation for handling\n * push notifications. Apps should copy or import this into their\n * service worker file.\n *\n * ## Industry Patterns Implemented (OneSignal/FCM)\n * - Push event handling with notification display\n * - Notification click with deep link navigation\n * - Notification close tracking\n * - Token refresh handling\n * - Background sync for offline actions\n *\n * ## Usage\n *\n * Create a service worker file in your app's public directory:\n *\n * ```typescript\n * // public/sw.ts or src/service-worker.ts\n * import { initPushServiceWorker } from '@sylphx/platform-sdk/notifications'\n *\n * initPushServiceWorker({\n * defaultIcon: '/icon-192.png',\n * defaultBadge: '/badge-72.png',\n * onNotificationClick: (data) => {\n * // Custom click handling\n * console.log('Notification clicked:', data)\n * },\n * })\n * ```\n */\n\n/**\n * Service Worker type definitions\n * These are minimal type definitions for Service Worker APIs.\n * Full types available with `lib: [\"WebWorker\"]` in tsconfig.\n */\ninterface PushEventData {\n\tjson(): unknown;\n\ttext(): string;\n}\n\ninterface PushEvent extends ExtendableEvent {\n\tdata: PushEventData | null;\n}\n\ninterface NotificationEvent extends ExtendableEvent {\n\tnotification: Notification & {\n\t\tdata?: Record<string, unknown>;\n\t\tclose(): void;\n\t};\n\taction?: string;\n}\n\ninterface WindowClient {\n\turl: string;\n\tfocus(): Promise<WindowClient>;\n}\n\ninterface Clients {\n\tmatchAll(options: { type: \"window\"; includeUncontrolled: boolean }): Promise<\n\t\tWindowClient[]\n\t>;\n\topenWindow(url: string): Promise<WindowClient | null>;\n\tclaim(): Promise<void>;\n}\n\ninterface ExtendableEvent extends Event {\n\twaitUntil(promise: Promise<unknown>): void;\n}\n\ninterface ServiceWorkerRegistration {\n\tshowNotification(title: string, options?: NotificationOptions): Promise<void>;\n}\n\ninterface ServiceWorkerGlobalScopeSubset {\n\treadonly registration: ServiceWorkerRegistration;\n\treadonly clients: Clients;\n\taddEventListener(type: \"push\", listener: (event: PushEvent) => void): void;\n\taddEventListener(\n\t\ttype: \"notificationclick\" | \"notificationclose\",\n\t\tlistener: (event: NotificationEvent) => void,\n\t): void;\n\taddEventListener(\n\t\ttype: \"activate\",\n\t\tlistener: (event: ExtendableEvent) => void,\n\t): void;\n}\n\ndeclare const self: ServiceWorkerGlobalScopeSubset;\n\n/**\n * Notification payload from Sylphx platform\n */\nexport interface PushNotificationPayload {\n\t/** Notification title */\n\ttitle: string;\n\t/** Notification body text */\n\tbody: string;\n\t/** Icon URL (optional, falls back to default) */\n\ticon?: string;\n\t/** Badge URL for Android (optional) */\n\tbadge?: string;\n\t/** Image URL for expanded notification (optional) */\n\timage?: string;\n\t/** Click action URL (optional) */\n\turl?: string;\n\t/** Action buttons (optional) */\n\tactions?: Array<{\n\t\taction: string;\n\t\ttitle: string;\n\t\ticon?: string;\n\t}>;\n\t/** Custom data payload */\n\tdata?: Record<string, unknown>;\n\t/** Notification tag for grouping (optional) */\n\ttag?: string;\n\t/** Whether to require interaction (optional) */\n\trequireInteraction?: boolean;\n\t/** Vibration pattern (optional) */\n\tvibrate?: number[];\n\t/** Silent notification (optional) */\n\tsilent?: boolean;\n}\n\n/**\n * Service worker configuration options\n */\nexport interface PushServiceWorkerConfig {\n\t/** Default icon for notifications without an icon */\n\tdefaultIcon?: string;\n\t/** Default badge for notifications without a badge */\n\tdefaultBadge?: string;\n\t/** Called when notification is clicked */\n\tonNotificationClick?: (data: PushNotificationPayload) => void;\n\t/** Called when notification is closed without clicking */\n\tonNotificationClose?: (data: PushNotificationPayload) => void;\n\t/** Platform API URL for analytics/token refresh */\n\tplatformUrl?: string;\n\t/** App ID for API calls */\n\tappId?: string;\n}\n\n/**\n * Initialize push notification handling in service worker\n *\n * Call this in your service worker file to enable push notification handling.\n *\n * NOTE: This function should only be called from within a service worker context.\n * The types are loosely defined to work in both browser and service worker contexts.\n *\n * @example\n * ```typescript\n * // In your service worker (e.g., public/sw.ts)\n * initPushServiceWorker({\n * defaultIcon: '/icon-192.png',\n * defaultBadge: '/badge-72.png',\n * })\n * ```\n */\nexport function initPushServiceWorker(\n\tconfig: PushServiceWorkerConfig = {},\n): void {\n\tconst {\n\t\tdefaultIcon,\n\t\tdefaultBadge,\n\t\tonNotificationClick,\n\t\tonNotificationClose,\n\t} = config;\n\n\t// Handle push events (when notification arrives)\n\tself.addEventListener(\"push\", (event) => {\n\t\tif (!event.data) {\n\t\t\tconsole.warn(\"[Sylphx SW] Push event received without data\");\n\t\t\treturn;\n\t\t}\n\n\t\tlet payload: PushNotificationPayload;\n\t\ttry {\n\t\t\tpayload = event.data.json() as PushNotificationPayload;\n\t\t} catch {\n\t\t\t// Fallback for plain text payloads\n\t\t\tpayload = {\n\t\t\t\ttitle: \"Notification\",\n\t\t\t\tbody: event.data.text(),\n\t\t\t};\n\t\t}\n\n\t\t// Build notification options (compatible with both browser and SW contexts)\n\t\tconst notificationOptions = {\n\t\t\tbody: payload.body,\n\t\t\ticon: payload.icon || defaultIcon,\n\t\t\tbadge: payload.badge || defaultBadge,\n\t\t\timage: payload.image,\n\t\t\tdata: {\n\t\t\t\t...payload.data,\n\t\t\t\turl: payload.url,\n\t\t\t\t_sylphxPayload: payload,\n\t\t\t},\n\t\t\ttag: payload.tag,\n\t\t\trequireInteraction: payload.requireInteraction ?? false,\n\t\t\tvibrate: payload.vibrate,\n\t\t\tsilent: payload.silent ?? false,\n\t\t\tactions: payload.actions,\n\t\t};\n\n\t\tevent.waitUntil(\n\t\t\tself.registration.showNotification(payload.title, notificationOptions),\n\t\t);\n\t});\n\n\t// Handle notification click events\n\tself.addEventListener(\"notificationclick\", (event) => {\n\t\tevent.notification.close();\n\n\t\tconst data = event.notification.data;\n\t\tconst payload = data?._sylphxPayload as PushNotificationPayload | undefined;\n\t\tconst url = data?.url as string | undefined;\n\n\t\t// Call custom handler if provided\n\t\tif (onNotificationClick && payload) {\n\t\t\tonNotificationClick(payload);\n\t\t}\n\n\t\t// Handle action button clicks\n\t\tif (event.action) {\n\t\t\t// Custom action handling\n\t\t\tconsole.log(\"[Sylphx SW] Action clicked:\", event.action);\n\t\t}\n\n\t\t// Navigate to URL if provided\n\t\tif (url) {\n\t\t\tevent.waitUntil(\n\t\t\t\tself.clients\n\t\t\t\t\t.matchAll({ type: \"window\", includeUncontrolled: true })\n\t\t\t\t\t.then((clientList) => {\n\t\t\t\t\t\t// Try to focus an existing window with this URL\n\t\t\t\t\t\tfor (const client of clientList) {\n\t\t\t\t\t\t\tif (client.url === url && \"focus\" in client) {\n\t\t\t\t\t\t\t\treturn client.focus();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Open a new window if no existing window found\n\t\t\t\t\t\treturn self.clients.openWindow(url);\n\t\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t});\n\n\t// Handle notification close events (for analytics)\n\tself.addEventListener(\"notificationclose\", (event) => {\n\t\tconst data = event.notification.data;\n\t\tconst payload = data?._sylphxPayload as PushNotificationPayload | undefined;\n\n\t\tif (onNotificationClose && payload) {\n\t\t\tonNotificationClose(payload);\n\t\t}\n\t});\n\n\t// Handle service worker activation\n\tself.addEventListener(\"activate\", (event) => {\n\t\tevent.waitUntil(\n\t\t\t// Claim all clients immediately\n\t\t\tself.clients.claim(),\n\t\t);\n\t});\n\n\tconsole.log(\"[Sylphx SW] Push notification service worker initialized\");\n}\n\n/**\n * Helper to create a simple service worker script content\n *\n * For apps that want to dynamically generate their service worker,\n * this returns the JavaScript content as a string.\n *\n * @example\n * ```typescript\n * // In a route handler\n * export function GET() {\n * const content = createServiceWorkerScript({\n * defaultIcon: '/icon-192.png',\n * })\n * return new Response(content, {\n * headers: { 'Content-Type': 'application/javascript' },\n * })\n * }\n * ```\n */\nexport function createServiceWorkerScript(\n\tconfig: PushServiceWorkerConfig = {},\n): string {\n\tconst { defaultIcon = \"/icon-192.png\", defaultBadge = \"/badge-72.png\" } =\n\t\tconfig;\n\n\treturn `\n// Sylphx Push Notification Service Worker\n// Auto-generated - do not edit directly\n\nconst DEFAULT_ICON = '${defaultIcon}';\nconst DEFAULT_BADGE = '${defaultBadge}';\n\nself.addEventListener('push', (event) => {\n if (!event.data) return;\n\n let payload;\n try {\n payload = event.data.json();\n } catch {\n payload = { title: 'Notification', body: event.data.text() };\n }\n\n const options = {\n body: payload.body,\n icon: payload.icon || DEFAULT_ICON,\n badge: payload.badge || DEFAULT_BADGE,\n image: payload.image,\n data: { ...payload.data, url: payload.url },\n tag: payload.tag,\n requireInteraction: payload.requireInteraction || false,\n vibrate: payload.vibrate,\n silent: payload.silent || false,\n actions: payload.actions,\n };\n\n event.waitUntil(\n self.registration.showNotification(payload.title, options)\n );\n});\n\nself.addEventListener('notificationclick', (event) => {\n event.notification.close();\n const url = event.notification.data?.url;\n\n if (url) {\n event.waitUntil(\n clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => {\n for (const client of clientList) {\n if (client.url === url && 'focus' in client) {\n return client.focus();\n }\n }\n if (clients.openWindow) {\n return clients.openWindow(url);\n }\n })\n );\n }\n});\n\nself.addEventListener('activate', (event) => {\n event.waitUntil(clients.claim());\n});\n\nconsole.log('[Sylphx SW] Push notification service worker active');\n`.trim();\n}\n\n/**\n * Register the service worker from the client side\n *\n * Call this in your app's entry point to register the service worker.\n *\n * @example\n * ```typescript\n * // In your app's entry point (e.g., _app.tsx or layout.tsx)\n * import { registerPushServiceWorker } from '@sylphx/platform-sdk/notifications'\n *\n * useEffect(() => {\n * registerPushServiceWorker('/sw.js')\n * }, [])\n * ```\n */\nexport async function registerPushServiceWorker(\n\tswPath = \"/sw.js\",\n): Promise<ServiceWorkerRegistration | null> {\n\tif (typeof window === \"undefined\") return null;\n\tif (!(\"serviceWorker\" in navigator)) {\n\t\tconsole.warn(\"[Sylphx] Service workers not supported\");\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst registration = await navigator.serviceWorker.register(swPath);\n\t\tconsole.log(\"[Sylphx] Service worker registered:\", registration.scope);\n\t\treturn registration;\n\t} catch (error) {\n\t\tconsole.error(\"[Sylphx] Service worker registration failed:\", error);\n\t\treturn null;\n\t}\n}\n","/**\n * Jobs Functions\n *\n * Pure functions for background job scheduling.\n *\n * ## Industry-Standard Features\n * - **Idempotency Keys**: Stripe/Inngest pattern for safe retries\n * - **Automatic Retries**: Configurable retry count with exponential backoff\n * - **Cron Scheduling**: Recurring jobs with pause/resume support\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { components } from \"./generated/api\";\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type ScheduleJobRequest = components[\"schemas\"][\"ScheduleJobRequest\"];\nexport type ScheduleJobResponse = components[\"schemas\"][\"ScheduleJobResponse\"];\nexport type ScheduleCronRequest = components[\"schemas\"][\"ScheduleCronRequest\"];\nexport type ScheduleCronResponse =\n\tcomponents[\"schemas\"][\"ScheduleCronResponse\"];\nexport type Job = components[\"schemas\"][\"Job\"];\nexport type ListJobsResponse = components[\"schemas\"][\"ListJobsResponse\"];\n\n// SDK-specific types for convenience\nexport interface JobInput {\n\t/** Callback URL to call when job executes */\n\tcallbackUrl: string;\n\t/** Job name/type */\n\tname?: string;\n\t/** Job type for categorization */\n\ttype?: string;\n\t/** Job payload sent to callback */\n\tpayload?: Record<string, unknown>;\n\t/** HTTP method for callback (default: POST) */\n\tmethod?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\";\n\t/** Additional headers for callback */\n\theaders?: Record<string, string>;\n\t/** Delay before executing (in seconds, max 604800 = 7 days) */\n\tdelay?: number;\n\t/** Schedule for later (ISO timestamp) */\n\tscheduledFor?: string;\n\t/** Number of retries on failure (0-5, default: 3) */\n\tretries?: number;\n\t/** Request timeout in seconds (1-300, default: 30) */\n\ttimeout?: number;\n\t/**\n\t * Idempotency key for safe retries (Stripe/Inngest pattern).\n\t *\n\t * When provided, prevents duplicate job execution if the same\n\t * key is used within a 24-hour window. Useful for:\n\t * - Network retry safety\n\t * - At-most-once delivery guarantee\n\t * - Webhook deduplication\n\t *\n\t * @example\n\t * ```typescript\n\t * // Use a unique key derived from the operation\n\t * await scheduleJob(config, {\n\t * callbackUrl: 'https://myapp.com/api/jobs/send-email',\n\t * payload: { userId: 'user-123', template: 'welcome' },\n\t * idempotencyKey: `welcome-email-user-123-${Date.now()}`,\n\t * })\n\t * ```\n\t */\n\tidempotencyKey?: string;\n}\n\nexport interface JobResult {\n\t/** Job ID */\n\tjobId: string;\n\t/** QStash message ID */\n\tmessageId?: string;\n\t/** Scheduled execution time */\n\tscheduledFor?: string;\n}\n\nexport interface JobStatus {\n\tid: string;\n\tname?: string;\n\tstatus:\n\t\t| \"pending\"\n\t\t| \"queued\"\n\t\t| \"running\"\n\t\t| \"completed\"\n\t\t| \"failed\"\n\t\t| \"cancelled\";\n\tpayload?: Record<string, unknown>;\n\tresult?: unknown;\n\terror?: string;\n\tcreatedAt: string;\n\tqueuedAt?: string;\n\tstartedAt?: string;\n\tcompletedAt?: string;\n}\n\nexport interface CronInput {\n\t/** Callback URL to call on each cron trigger */\n\tcallbackUrl: string;\n\t/** Cron expression (e.g., '0 0 * * *' for daily at midnight) */\n\tcron: string;\n\t/** Job name (required, max 200 chars) */\n\tname: string;\n\t/** Job type for categorization */\n\ttype?: string;\n\t/** Job payload sent to callback */\n\tpayload?: Record<string, unknown>;\n\t/** HTTP method for callback (default: POST) */\n\tmethod?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\";\n\t/** Additional headers for callback */\n\theaders?: Record<string, string>;\n\t/** Number of retries on failure (0-5, default: 3) */\n\tretries?: number;\n\t/** Start in paused state */\n\tpaused?: boolean;\n\t/**\n\t * Idempotency key for safe cron creation (Stripe/Inngest pattern).\n\t *\n\t * When provided, prevents duplicate cron schedule creation if\n\t * the same key is used. Useful for deployment scripts that\n\t * may run multiple times.\n\t *\n\t * @example\n\t * ```typescript\n\t * await createCron(config, {\n\t * callbackUrl: 'https://myapp.com/api/jobs/daily-report',\n\t * cron: '0 9 * * *',\n\t * name: 'daily-report',\n\t * idempotencyKey: 'daily-report-cron-v1', // Same key = same cron\n\t * })\n\t * ```\n\t */\n\tidempotencyKey?: string;\n}\n\nexport interface CronSchedule {\n\t/** Internal job ID */\n\tjobId?: string;\n\t/** QStash schedule ID */\n\tscheduleId: string;\n\t/** Cron expression */\n\tcron: string;\n\t/** Whether currently paused */\n\tpaused: boolean;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Schedule a one-time job for execution\n *\n * @example\n * ```typescript\n * const job = await scheduleJob(config, {\n * callbackUrl: 'https://myapp.com/api/jobs/send-email',\n * name: 'send-email',\n * payload: { to: 'user@example.com', template: 'welcome' },\n * delay: 60, // Run in 60 seconds\n * })\n *\n * console.log(`Job scheduled: ${job.jobId}`)\n * ```\n */\nexport async function scheduleJob(\n\tconfig: SylphxConfig,\n\tinput: JobInput,\n): Promise<JobResult> {\n\treturn callApi<JobResult>(config, \"/jobs/schedule\", {\n\t\tmethod: \"POST\",\n\t\tbody: input,\n\t});\n}\n\n/**\n * Get a job's status by ID\n *\n * @example\n * ```typescript\n * const job = await getJob(config, 'job-123')\n * console.log(job.status) // 'completed'\n * ```\n */\nexport async function getJob(\n\tconfig: SylphxConfig,\n\tjobId: string,\n): Promise<JobStatus> {\n\treturn callApi<JobStatus>(config, `/jobs/${jobId}`, { method: \"GET\" });\n}\n\n/**\n * Cancel a pending job\n *\n * @example\n * ```typescript\n * await cancelJob(config, 'job-123')\n * ```\n */\nexport async function cancelJob(\n\tconfig: SylphxConfig,\n\tjobId: string,\n): Promise<boolean> {\n\tconst result = await callApi<{ success: boolean }>(\n\t\tconfig,\n\t\t`/jobs/${jobId}/cancel`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t},\n\t);\n\treturn result.success;\n}\n\n/**\n * List jobs\n *\n * @example\n * ```typescript\n * const jobs = await listJobs(config, { status: 'pending', limit: 10 })\n * ```\n */\nexport async function listJobs(\n\tconfig: SylphxConfig,\n\toptions?: { status?: JobStatus[\"status\"]; limit?: number; offset?: number },\n): Promise<{ jobs: JobStatus[]; total: number }> {\n\treturn callApi(config, \"/jobs\", {\n\t\tmethod: \"GET\",\n\t\tquery: options as Record<string, string | number | undefined>,\n\t});\n}\n\n/**\n * Create a recurring cron job\n *\n * @example\n * ```typescript\n * const cron = await createCron(config, {\n * callbackUrl: 'https://myapp.com/api/webhooks/platform-jobs',\n * cron: '0 9 * * *', // Every day at 9am UTC\n * name: 'daily-report',\n * payload: { type: 'daily' },\n * })\n *\n * console.log(`Cron created: ${cron.scheduleId}`)\n * ```\n */\nexport async function createCron(\n\tconfig: SylphxConfig,\n\tinput: CronInput,\n): Promise<CronSchedule> {\n\treturn callApi<CronSchedule>(config, \"/jobs/cron\", {\n\t\tmethod: \"POST\",\n\t\tbody: input,\n\t});\n}\n\n/**\n * Pause a cron schedule\n *\n * @example\n * ```typescript\n * await pauseCron(config, 'schedule-123')\n * ```\n */\nexport async function pauseCron(\n\tconfig: SylphxConfig,\n\tscheduleId: string,\n): Promise<boolean> {\n\tconst result = await callApi<{ success: boolean }>(\n\t\tconfig,\n\t\t`/jobs/cron/${scheduleId}/pause`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t},\n\t);\n\treturn result.success;\n}\n\n/**\n * Resume a cron schedule\n *\n * @example\n * ```typescript\n * await resumeCron(config, 'schedule-123')\n * ```\n */\nexport async function resumeCron(\n\tconfig: SylphxConfig,\n\tscheduleId: string,\n): Promise<boolean> {\n\tconst result = await callApi<{ success: boolean }>(\n\t\tconfig,\n\t\t`/jobs/cron/${scheduleId}/resume`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t},\n\t);\n\treturn result.success;\n}\n\n/**\n * Delete a cron schedule\n *\n * @example\n * ```typescript\n * await deleteCron(config, 'schedule-123')\n * ```\n */\nexport async function deleteCron(\n\tconfig: SylphxConfig,\n\tscheduleId: string,\n): Promise<boolean> {\n\tconst result = await callApi<{ success: boolean }>(\n\t\tconfig,\n\t\t`/jobs/cron/${scheduleId}`,\n\t\t{\n\t\t\tmethod: \"DELETE\",\n\t\t},\n\t);\n\treturn result.success;\n}\n","/**\n * Feature Flags Functions\n *\n * Pure functions for feature flag evaluation.\n *\n * Pattern: LaunchDarkly/Statsig server-side evaluation\n * - Server-side: POST /flags/evaluate with context\n * - Returns evaluated results (enabled/disabled for this context)\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\n\n// ============================================================================\n// Types (SDK-specific - no direct API schema for flag results)\n// ============================================================================\n\nexport interface FlagResult {\n\t/** Flag key */\n\tkey: string;\n\t/** Whether the flag is enabled for this context */\n\tenabled: boolean;\n\t/** Variant value (for multivariate flags) */\n\tvariant?: string;\n\t/** Reason for the evaluation result */\n\treason?: string;\n\t/** Additional payload data */\n\tpayload?: Record<string, unknown>;\n}\n\nexport interface FlagContext {\n\t/** User ID for consistent targeting */\n\tuserId?: string;\n\t/** Anonymous ID for pre-auth targeting */\n\tanonymousId?: string;\n\t/** User properties for targeting rules (plan, isAdmin, etc.) */\n\tproperties?: Record<string, unknown>;\n}\n\n/** Response from the evaluate endpoint */\ninterface EvaluateFlagsResponse {\n\tdata: Record<string, FlagResult>;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Check a single feature flag (server-side evaluation)\n *\n * Uses POST /flags/evaluate for consistent, server-side targeting.\n * The server evaluates rollout percentage, premium targeting, etc.\n *\n * @example\n * ```typescript\n * const flag = await checkFlag(config, 'new-checkout', {\n * userId: 'user-123',\n * properties: { plan: 'pro' },\n * })\n *\n * if (flag.enabled) {\n * // Show new checkout\n * }\n * ```\n */\nexport async function checkFlag(\n\tconfig: SylphxConfig,\n\tflagKey: string,\n\tcontext?: FlagContext,\n): Promise<FlagResult> {\n\tconst response = await callApi<EvaluateFlagsResponse>(\n\t\tconfig,\n\t\t\"/flags/evaluate\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: {\n\t\t\t\tcontext: {\n\t\t\t\t\tuserId: context?.userId,\n\t\t\t\t\tanonymousId: context?.anonymousId,\n\t\t\t\t\tproperties: context?.properties,\n\t\t\t\t},\n\t\t\t\tkeys: [flagKey],\n\t\t\t},\n\t\t},\n\t);\n\n\t// Return the evaluated flag, or a disabled default if not found\n\treturn (\n\t\tresponse.data[flagKey] ?? {\n\t\t\tkey: flagKey,\n\t\t\tenabled: false,\n\t\t\treason: \"flag_not_found\",\n\t\t}\n\t);\n}\n\n/**\n * Get multiple feature flags at once (batch evaluation)\n *\n * Evaluates all requested flags in a single API call.\n * More efficient than calling checkFlag() multiple times.\n *\n * @example\n * ```typescript\n * const flags = await getFlags(config, ['new-checkout', 'dark-mode', 'ai-features'], {\n * userId: 'user-123',\n * })\n *\n * if (flags['new-checkout'].enabled) {\n * // Show new checkout\n * }\n * ```\n */\nexport async function getFlags(\n\tconfig: SylphxConfig,\n\tflagKeys: string[],\n\tcontext?: FlagContext,\n): Promise<Record<string, FlagResult>> {\n\tconst response = await callApi<EvaluateFlagsResponse>(\n\t\tconfig,\n\t\t\"/flags/evaluate\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: {\n\t\t\t\tcontext: {\n\t\t\t\t\tuserId: context?.userId,\n\t\t\t\t\tanonymousId: context?.anonymousId,\n\t\t\t\t\tproperties: context?.properties,\n\t\t\t\t},\n\t\t\t\tkeys: flagKeys,\n\t\t\t},\n\t\t},\n\t);\n\n\treturn response.data;\n}\n\n/**\n * Get all feature flags for a context (bootstrap)\n *\n * Evaluates ALL flags for the app in a single API call.\n * Useful for bootstrapping the flag state on app load.\n *\n * @example\n * ```typescript\n * // Bootstrap all flags on app load\n * const allFlags = await getAllFlags(config, { userId: 'user-123' })\n *\n * // Use throughout the app\n * if (allFlags['new-checkout']?.enabled) {\n * // Show new checkout\n * }\n * ```\n */\nexport async function getAllFlags(\n\tconfig: SylphxConfig,\n\tcontext?: FlagContext,\n): Promise<Record<string, FlagResult>> {\n\tconst response = await callApi<EvaluateFlagsResponse>(\n\t\tconfig,\n\t\t\"/flags/evaluate\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: {\n\t\t\t\tcontext: {\n\t\t\t\t\tuserId: context?.userId,\n\t\t\t\t\tanonymousId: context?.anonymousId,\n\t\t\t\t\tproperties: context?.properties,\n\t\t\t\t},\n\t\t\t\t// Omit keys to get all flags\n\t\t\t},\n\t\t},\n\t);\n\n\treturn response.data;\n}\n\n/**\n * Check if a flag is enabled (boolean helper)\n *\n * @example\n * ```typescript\n * if (await isEnabled(config, 'new-checkout', { userId: 'user-123' })) {\n * // Show new checkout\n * }\n * ```\n */\nexport async function isEnabled(\n\tconfig: SylphxConfig,\n\tflagKey: string,\n\tcontext?: FlagContext,\n): Promise<boolean> {\n\tconst flag = await checkFlag(config, flagKey, context);\n\treturn flag.enabled;\n}\n\n/**\n * Get flag variant (for A/B tests)\n *\n * @example\n * ```typescript\n * const variant = await getVariant(config, 'checkout-experiment', {\n * userId: 'user-123',\n * })\n *\n * switch (variant) {\n * case 'control':\n * // Show original checkout\n * break\n * case 'variant-a':\n * // Show variant A\n * break\n * case 'variant-b':\n * // Show variant B\n * break\n * }\n * ```\n */\nexport async function getVariant(\n\tconfig: SylphxConfig,\n\tflagKey: string,\n\tcontext?: FlagContext,\n): Promise<string | undefined> {\n\tconst flag = await checkFlag(config, flagKey, context);\n\treturn flag.variant;\n}\n\n/**\n * Get flag payload (for remote config)\n *\n * @example\n * ```typescript\n * const payload = await getFlagPayload<{ maxItems: number }>(config, 'cart-config', {\n * userId: 'user-123',\n * })\n *\n * console.log(payload?.maxItems) // 10\n * ```\n */\nexport async function getFlagPayload<T extends Record<string, unknown>>(\n\tconfig: SylphxConfig,\n\tflagKey: string,\n\tcontext?: FlagContext,\n): Promise<T | undefined> {\n\tconst flag = await checkFlag(config, flagKey, context);\n\treturn flag.payload as T | undefined;\n}\n","/**\n * Webhooks Functions\n *\n * Pure functions for webhook configuration and delivery management.\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { components } from \"./generated/api\";\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type WebhookConfigResponse =\n\tcomponents[\"schemas\"][\"WebhookConfigResponse\"];\nexport type WebhookEnvironmentConfig =\n\tcomponents[\"schemas\"][\"WebhookEnvironmentConfig\"];\nexport type UpdateWebhookConfigRequest =\n\tcomponents[\"schemas\"][\"UpdateWebhookConfigRequest\"];\nexport type UpdateWebhookConfigResponse =\n\tcomponents[\"schemas\"][\"UpdateWebhookConfigResponse\"];\nexport type ListWebhookDeliveriesResponse =\n\tcomponents[\"schemas\"][\"ListWebhookDeliveriesResponse\"];\nexport type WebhookDelivery = components[\"schemas\"][\"WebhookDelivery\"];\nexport type ReplayDeliveryResponse =\n\tcomponents[\"schemas\"][\"ReplayDeliveryResponse\"];\nexport type WebhookStatsResponse =\n\tcomponents[\"schemas\"][\"WebhookStatsResponse\"];\n\n// SDK-specific types for convenience\nexport interface WebhookEnvironment {\n\tid: string;\n\tname: string;\n\twebhookUrl: string | null;\n\twebhookSecret?: string | null;\n\thasSecret?: boolean;\n\tevents?: string[];\n\tcreatedAt: string;\n\tupdatedAt: string | null;\n}\n\nexport interface WebhookConfig {\n\tenvironments: WebhookEnvironment[];\n\tsupportedEvents?: string[];\n\tenabled?: boolean;\n\turl?: string | null;\n\tsecret?: string | null;\n\tevents?: string[];\n}\n\nexport interface WebhookConfigUpdate {\n\tenvironmentId: string;\n\twebhookUrl: string | null;\n}\n\nexport interface WebhookDeliveriesResult {\n\tdeliveries: WebhookDelivery[];\n\ttotal: number;\n\thasMore: boolean;\n}\n\nexport interface WebhookStats {\n\t// Summary totals\n\ttotal: number;\n\tdelivered: number;\n\tfailed: number;\n\tpending: number;\n\tdeliveryRate: number;\n\tavgLatencyMs: number | null;\n\t// Extended stats (for UI)\n\tperiod?: string;\n\ttotals?: {\n\t\ttotal: number;\n\t\tdelivered: number;\n\t\tfailed: number;\n\t\tpending: number;\n\t\tdeliveryRate: number | string;\n\t};\n\tbyEvent?: Array<{ event: string; count: number }>;\n\tbyStatus?: Array<{ status: string; count: number }>;\n}\n\nexport interface ListDeliveriesOptions {\n\tenvironmentId?: string;\n\tstatus?: \"pending\" | \"queued\" | \"delivered\" | \"failed\";\n\tlimit?: number;\n\toffset?: number;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Get webhook configuration for the app\n *\n * @example\n * ```typescript\n * const config = await getWebhookConfig(sylphxConfig)\n * console.log(config.environments)\n * ```\n */\nexport async function getWebhookConfig(\n\tconfig: SylphxConfig,\n): Promise<WebhookConfig> {\n\treturn callApi(config, \"/webhooks/config\", { method: \"GET\" });\n}\n\n/**\n * Update webhook URL for an environment\n *\n * @example\n * ```typescript\n * await updateWebhookConfig(config, {\n * environmentId: 'env-123',\n * webhookUrl: 'https://myapp.com/webhooks',\n * })\n * ```\n */\nexport async function updateWebhookConfig(\n\tconfig: SylphxConfig,\n\tdata: WebhookConfigUpdate,\n): Promise<void> {\n\treturn callApi(config, \"/webhooks/config\", { method: \"PUT\", body: data });\n}\n\n/**\n * Get webhook delivery history\n *\n * @example\n * ```typescript\n * const { deliveries, total } = await getWebhookDeliveries(config, {\n * status: 'failed',\n * limit: 20,\n * })\n * ```\n */\nexport async function getWebhookDeliveries(\n\tconfig: SylphxConfig,\n\toptions?: ListDeliveriesOptions,\n): Promise<WebhookDeliveriesResult> {\n\treturn callApi(config, \"/webhooks/deliveries\", {\n\t\tmethod: \"GET\",\n\t\tquery: options as Record<string, string | number | undefined>,\n\t});\n}\n\n/**\n * Get a single webhook delivery by ID\n *\n * @example\n * ```typescript\n * const delivery = await getWebhookDelivery(config, 'del-123')\n * console.log(delivery.payload)\n * ```\n */\nexport async function getWebhookDelivery(\n\tconfig: SylphxConfig,\n\tdeliveryId: string,\n): Promise<WebhookDelivery> {\n\treturn callApi(config, `/webhooks/deliveries/${deliveryId}`, {\n\t\tmethod: \"GET\",\n\t});\n}\n\n/**\n * Replay a failed webhook delivery\n *\n * @example\n * ```typescript\n * await replayWebhookDelivery(config, 'del-123')\n * ```\n */\nexport async function replayWebhookDelivery(\n\tconfig: SylphxConfig,\n\tdeliveryId: string,\n): Promise<void> {\n\treturn callApi(config, `/webhooks/deliveries/${deliveryId}/replay`, {\n\t\tmethod: \"POST\",\n\t});\n}\n\n/**\n * Get webhook statistics\n *\n * @example\n * ```typescript\n * const stats = await getWebhookStats(config)\n * console.log(`Delivery rate: ${stats.deliveryRate}%`)\n * ```\n */\nexport async function getWebhookStats(\n\tconfig: SylphxConfig,\n\tenvironmentId?: string,\n): Promise<WebhookStats> {\n\treturn callApi(config, \"/webhooks/stats\", {\n\t\tmethod: \"GET\",\n\t\tquery: environmentId ? { environmentId } : undefined,\n\t});\n}\n","/**\n * Email Functions\n *\n * Pure functions for transactional email operations.\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { components } from \"./generated/api\";\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type SendEmailRequest = components[\"schemas\"][\"SendEmailRequest\"];\nexport type SendEmailResponse = components[\"schemas\"][\"SendEmailResponse\"];\nexport type SendTemplatedEmailRequest =\n\tcomponents[\"schemas\"][\"SendTemplatedEmailRequest\"];\nexport type SendTemplatedEmailResponse =\n\tcomponents[\"schemas\"][\"SendTemplatedEmailResponse\"];\nexport type SendToUserRequest = components[\"schemas\"][\"SendToUserRequest\"];\nexport type SendToUserResponse = components[\"schemas\"][\"SendToUserResponse\"];\nexport type NewsletterSubscribeRequest =\n\tcomponents[\"schemas\"][\"NewsletterSubscribeRequest\"];\nexport type NewsletterSubscribeResponse =\n\tcomponents[\"schemas\"][\"NewsletterSubscribeResponse\"];\n\n// SDK-specific types for convenience\nexport interface SendEmailOptions {\n\t/** Recipient email address */\n\tto: string;\n\t/** Email subject line */\n\tsubject: string;\n\t/** HTML content */\n\thtml: string;\n\t/** Plain text content (optional fallback) */\n\ttext?: string;\n\t/** Reply-to address */\n\treplyTo?: string;\n\t/**\n\t * Sender email address (must be from a verified domain).\n\t * Falls back to the app environment default, then the platform FROM_EMAIL.\n\t *\n\t * @example `support@yourdomain.com`\n\t */\n\tfromEmail?: string;\n\t/**\n\t * Sender display name (used together with fromEmail).\n\t *\n\t * @example `Acme Support`\n\t */\n\tfromName?: string;\n\t/**\n\t * Idempotency key for safe retries (Stripe pattern)\n\t *\n\t * When provided, prevents duplicate email sends if the same request\n\t * is retried within 24 hours. Use a unique key per logical operation.\n\t *\n\t * @example `welcome-email-${userId}`\n\t */\n\tidempotencyKey?: string;\n}\n\nexport interface SendTemplatedEmailOptions {\n\t/** Template name: 'welcome', 'verification', 'password_reset', 'security_alert' */\n\ttemplate: \"welcome\" | \"verification\" | \"password_reset\" | \"security_alert\";\n\t/** Recipient email address */\n\tto: string;\n\t/** Template variables */\n\tdata?: Record<string, unknown>;\n\t/**\n\t * Idempotency key for safe retries (Stripe pattern)\n\t *\n\t * @example `verification-email-${userId}`\n\t */\n\tidempotencyKey?: string;\n}\n\nexport interface SendToUserOptions {\n\t/** User ID to send to */\n\tuserId: string;\n\t/** Email subject line */\n\tsubject: string;\n\t/** HTML content */\n\thtml: string;\n\t/** Plain text content (optional fallback) */\n\ttext?: string;\n\t/**\n\t * Idempotency key for safe retries (Stripe pattern)\n\t *\n\t * @example `notification-${userId}-${Date.now()}`\n\t */\n\tidempotencyKey?: string;\n}\n\nexport interface ScheduleEmailOptions {\n\t/** Recipient email address */\n\tto: string;\n\t/** Recipient name (optional) */\n\ttoName?: string;\n\t/** Email subject line */\n\tsubject: string;\n\t/** HTML content */\n\thtml?: string;\n\t/** Plain text content */\n\ttext?: string;\n\t/** Reply-to address */\n\treplyTo?: string;\n\t/** From email (defaults to app's configured sender) */\n\tfromEmail?: string;\n\t/** From name */\n\tfromName?: string;\n\t/** ISO timestamp for when to send */\n\tscheduledFor: string;\n\t/** Template key for templated emails */\n\ttemplateKey?: string;\n\t/** Template variables */\n\ttemplateData?: Record<string, unknown>;\n\t/** Idempotency key to prevent duplicates */\n\tidempotencyKey?: string;\n\t/** Custom metadata */\n\tmetadata?: Record<string, unknown>;\n}\n\nexport interface ScheduledEmail {\n\tid: string;\n\tto: string;\n\ttoName: string | null;\n\tsubject: string;\n\tstatus: \"pending\" | \"queued\" | \"sent\" | \"cancelled\" | \"failed\";\n\tscheduledFor: string;\n\tsentAt: string | null;\n\tcreatedAt: string;\n}\n\nexport interface ScheduledEmailsResult {\n\temails: ScheduledEmail[];\n\ttotal: number;\n\thasMore: boolean;\n}\n\nexport interface ScheduledEmailStats {\n\ttotal: number;\n\tpending: number;\n\tqueued: number;\n\tsent: number;\n\tcancelled: number;\n\tfailed: number;\n}\n\nexport interface ListScheduledEmailsOptions {\n\tstatus?: \"pending\" | \"queued\" | \"sent\" | \"cancelled\" | \"failed\" | \"all\";\n\tlimit?: number;\n\toffset?: number;\n}\n\nexport interface SendResult {\n\tid: string;\n\tsuccess: boolean;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Check if email service is configured for the app\n *\n * @example\n * ```typescript\n * const configured = await isEmailConfigured(config)\n * if (!configured) console.log('Please configure email settings')\n * ```\n */\nexport async function isEmailConfigured(\n\tconfig: SylphxConfig,\n): Promise<boolean> {\n\treturn callApi(config, \"/email/configured\", { method: \"GET\" });\n}\n\n/**\n * Send a custom email\n *\n * @example\n * ```typescript\n * const result = await sendEmail(config, {\n * to: 'user@example.com',\n * subject: 'Hello!',\n * html: '<p>Welcome to our app!</p>',\n * idempotencyKey: `welcome-${userId}`, // Safe retry\n * })\n * ```\n */\nexport async function sendEmail(\n\tconfig: SylphxConfig,\n\toptions: SendEmailOptions,\n): Promise<SendResult> {\n\tconst { idempotencyKey, ...body } = options;\n\treturn callApi(config, \"/email/send\", {\n\t\tmethod: \"POST\",\n\t\tbody,\n\t\tidempotencyKey,\n\t});\n}\n\n/**\n * Send a templated email\n *\n * @example\n * ```typescript\n * await sendTemplatedEmail(config, {\n * template: 'welcome',\n * to: 'user@example.com',\n * data: { name: 'John' },\n * idempotencyKey: `welcome-${userId}`, // Safe retry\n * })\n * ```\n */\nexport async function sendTemplatedEmail(\n\tconfig: SylphxConfig,\n\toptions: SendTemplatedEmailOptions,\n): Promise<SendResult> {\n\tconst { idempotencyKey, ...body } = options;\n\treturn callApi(config, \"/email/send-templated\", {\n\t\tmethod: \"POST\",\n\t\tbody,\n\t\tidempotencyKey,\n\t});\n}\n\n/**\n * Send email to a user by their ID\n *\n * @example\n * ```typescript\n * await sendEmailToUser(config, {\n * userId: 'user-123',\n * subject: 'Account Update',\n * html: '<p>Your account has been updated.</p>',\n * idempotencyKey: `update-${userId}-${timestamp}`, // Safe retry\n * })\n * ```\n */\nexport async function sendEmailToUser(\n\tconfig: SylphxConfig,\n\toptions: SendToUserOptions,\n): Promise<SendResult> {\n\tconst { idempotencyKey, ...body } = options;\n\treturn callApi(config, \"/email/send-to-user\", {\n\t\tmethod: \"POST\",\n\t\tbody,\n\t\tidempotencyKey,\n\t});\n}\n\n/**\n * Schedule an email for future delivery\n *\n * @example\n * ```typescript\n * const scheduled = await scheduleEmail(config, {\n * to: 'user@example.com',\n * subject: 'Reminder',\n * html: '<p>Don\\'t forget!</p>',\n * scheduledFor: new Date(Date.now() + 86400000).toISOString(), // 24 hours\n * })\n * ```\n */\nexport async function scheduleEmail(\n\tconfig: SylphxConfig,\n\toptions: ScheduleEmailOptions,\n): Promise<ScheduledEmail> {\n\treturn callApi(config, \"/email/schedule\", { method: \"POST\", body: options });\n}\n\n/**\n * List scheduled emails\n *\n * @example\n * ```typescript\n * const { emails, total } = await listScheduledEmails(config, {\n * status: 'pending',\n * limit: 20,\n * })\n * ```\n */\nexport async function listScheduledEmails(\n\tconfig: SylphxConfig,\n\toptions?: ListScheduledEmailsOptions,\n): Promise<ScheduledEmailsResult> {\n\treturn callApi(config, \"/email/scheduled\", {\n\t\tmethod: \"GET\",\n\t\tquery: options as Record<string, string | number | undefined>,\n\t});\n}\n\n/**\n * Get a scheduled email by ID\n *\n * @example\n * ```typescript\n * const email = await getScheduledEmail(config, 'email-123')\n * console.log(email.status)\n * ```\n */\nexport async function getScheduledEmail(\n\tconfig: SylphxConfig,\n\temailId: string,\n): Promise<ScheduledEmail> {\n\treturn callApi(config, `/email/scheduled/${emailId}`, { method: \"GET\" });\n}\n\n/**\n * Cancel a scheduled email\n *\n * @example\n * ```typescript\n * await cancelScheduledEmail(config, 'email-123')\n * ```\n */\nexport async function cancelScheduledEmail(\n\tconfig: SylphxConfig,\n\temailId: string,\n): Promise<void> {\n\treturn callApi(config, `/email/scheduled/${emailId}/cancel`, {\n\t\tmethod: \"POST\",\n\t});\n}\n\n/**\n * Reschedule an email\n *\n * @example\n * ```typescript\n * await rescheduleEmail(config, 'email-123', new Date(Date.now() + 3600000).toISOString())\n * ```\n */\nexport async function rescheduleEmail(\n\tconfig: SylphxConfig,\n\temailId: string,\n\tscheduledFor: string,\n): Promise<ScheduledEmail> {\n\treturn callApi(config, `/email/scheduled/${emailId}/reschedule`, {\n\t\tmethod: \"POST\",\n\t\tbody: { scheduledFor },\n\t});\n}\n\n/**\n * Get scheduled email statistics\n *\n * @example\n * ```typescript\n * const stats = await getScheduledEmailStats(config)\n * console.log(`${stats.pending} emails pending`)\n * ```\n */\nexport async function getScheduledEmailStats(\n\tconfig: SylphxConfig,\n): Promise<ScheduledEmailStats> {\n\treturn callApi(config, \"/email/scheduled/stats\", { method: \"GET\" });\n}\n\n// ============================================================================\n// Email Domain Management\n// ============================================================================\n\n/**\n * DNS record required for domain verification\n */\nexport interface DnsRecord {\n\t/** DNS record type */\n\ttype: \"MX\" | \"TXT\" | \"CNAME\";\n\t/** DNS record name (hostname) */\n\tname: string;\n\t/** DNS record value */\n\tvalue: string;\n\t/** MX priority (only for MX records) */\n\tpriority?: number;\n\t/** TTL in seconds */\n\tttl: number;\n}\n\n/**\n * A registered custom sending domain\n */\nexport interface EmailDomain {\n\tid: string;\n\tdomain: string;\n\tstatus: \"pending\" | \"verifying\" | \"verified\" | \"failed\";\n\tdefaultFromEmail: string | null;\n\tdefaultFromName: string | null;\n\tdnsRecords: DnsRecord[];\n\tcreatedAt: string;\n\tverifiedAt: string | null;\n}\n\nexport interface RegisterEmailDomainOptions {\n\t/** Default from address for this domain (e.g. support@cubeage.com) */\n\tdefaultFromEmail?: string;\n\t/** Default sender display name (e.g. Cubeage Support) */\n\tdefaultFromName?: string;\n}\n\nexport interface SetDefaultEmailDomainOptions {\n\t/** Override the default from address */\n\tdefaultFromEmail?: string;\n\t/** Override the default sender name */\n\tdefaultFromName?: string;\n}\n\n/**\n * Register a custom sending domain\n *\n * Creates the domain in Resend and returns DNS records to add.\n * After adding DNS records, call verifyEmailDomain to confirm ownership.\n *\n * @example\n * ```typescript\n * const domain = await registerEmailDomain(config, 'cubeage.com', {\n * defaultFromEmail: 'support@cubeage.com',\n * defaultFromName: 'Cubeage Support',\n * })\n * console.log('Add these DNS records:', domain.dnsRecords)\n * ```\n */\nexport async function registerEmailDomain(\n\tconfig: SylphxConfig,\n\tdomain: string,\n\topts?: RegisterEmailDomainOptions,\n): Promise<EmailDomain> {\n\treturn callApi(config, \"/email/domains\", {\n\t\tmethod: \"POST\",\n\t\tbody: { domain, ...opts },\n\t});\n}\n\n/**\n * List all custom sending domains for this app\n *\n * @example\n * ```typescript\n * const { domains } = await listEmailDomains(config)\n * for (const d of domains) {\n * console.log(d.domain, d.status)\n * }\n * ```\n */\nexport async function listEmailDomains(\n\tconfig: SylphxConfig,\n): Promise<{ domains: EmailDomain[] }> {\n\treturn callApi(config, \"/email/domains\", { method: \"GET\" });\n}\n\n/**\n * Get a specific domain by ID\n *\n * @example\n * ```typescript\n * const domain = await getEmailDomain(config, 'domain-uuid')\n * console.log(domain.dnsRecords)\n * ```\n */\nexport async function getEmailDomain(\n\tconfig: SylphxConfig,\n\tdomainId: string,\n): Promise<EmailDomain> {\n\treturn callApi(config, `/email/domains/${domainId}`, { method: \"GET\" });\n}\n\n/**\n * Delete a custom sending domain\n *\n * Removes the domain from both Resend and the platform.\n *\n * @example\n * ```typescript\n * await deleteEmailDomain(config, 'domain-uuid')\n * ```\n */\nexport async function deleteEmailDomain(\n\tconfig: SylphxConfig,\n\tdomainId: string,\n): Promise<void> {\n\treturn callApi(config, `/email/domains/${domainId}`, { method: \"DELETE\" });\n}\n\n/**\n * Trigger DNS verification for a domain\n *\n * Resend will check if your DNS records have been added correctly.\n * Status changes to 'verified' on success or 'failed' on error.\n *\n * @example\n * ```typescript\n * const domain = await verifyEmailDomain(config, 'domain-uuid')\n * if (domain.status === 'verified') {\n * console.log('Domain verified!')\n * }\n * ```\n */\nexport async function verifyEmailDomain(\n\tconfig: SylphxConfig,\n\tdomainId: string,\n): Promise<EmailDomain> {\n\treturn callApi(config, `/email/domains/${domainId}/verify`, {\n\t\tmethod: \"POST\",\n\t});\n}\n\n/**\n * Set a domain as the default sender for this app\n *\n * @example\n * ```typescript\n * const domain = await setDefaultEmailDomain(config, 'domain-uuid', {\n * defaultFromEmail: 'support@cubeage.com',\n * defaultFromName: 'Cubeage Support',\n * })\n * ```\n */\nexport async function setDefaultEmailDomain(\n\tconfig: SylphxConfig,\n\tdomainId: string,\n\topts?: SetDefaultEmailDomainOptions,\n): Promise<EmailDomain> {\n\treturn callApi(config, `/email/domains/${domainId}/set-default`, {\n\t\tmethod: \"POST\",\n\t\tbody: opts ?? {},\n\t});\n}\n","/**\n * Consent Functions\n *\n * Pure functions for GDPR/CCPA consent management.\n *\n * ## Architecture (ADR-004)\n *\n * Consent uses **Inline Defaults + Auto-Discovery + Console Override**:\n * - Code provides optional inline defaults when checking consent\n * - Platform auto-discovers/creates consent types when first referenced\n * - Console can override names, descriptions, requirements without deployment\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n *\n * @example\n * ```typescript\n * import { hasConsent, getUserConsents, setConsents } from '@sylphx/sdk'\n *\n * // Check consent with inline defaults (auto-discovered if doesn't exist)\n * if (await hasConsent(config, 'analytics', { userId: 'user-123' }, {\n * name: 'Analytics Cookies',\n * description: 'Help us understand how visitors use our site',\n * category: 'analytics',\n * required: false,\n * })) {\n * track('pageview')\n * }\n *\n * // Get user's current consents\n * const consents = await getUserConsents(config, { userId: 'user-123' })\n *\n * // Set specific consents\n * await setConsents(config, {\n * userId: 'user-123',\n * consents: { analytics: true, marketing: false }\n * })\n * ```\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { components } from \"./generated/api\";\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type ConsentType = components[\"schemas\"][\"ConsentType\"];\nexport type UserConsent = components[\"schemas\"][\"UserConsent\"];\nexport type SetConsentRequest = components[\"schemas\"][\"SetConsentRequest\"];\nexport type SetConsentResponse = components[\"schemas\"][\"SetConsentResponse\"];\n\n// SDK-specific types (not directly from API schema)\n/** Consent category for grouping */\nexport type ConsentCategory =\n\t| \"necessary\"\n\t| \"analytics\"\n\t| \"marketing\"\n\t| \"functional\"\n\t| \"preferences\";\n\nexport interface SetConsentsInput {\n\t/** User ID (optional for anonymous users) */\n\tuserId?: string;\n\t/** Anonymous ID (for guest users) */\n\tanonymousId?: string;\n\t/** Consent settings by type slug */\n\tconsents: Record<string, boolean>;\n}\n\nexport interface LinkAnonymousConsentsInput {\n\t/** The authenticated user ID to link to */\n\tuserId: string;\n\t/** The anonymous ID whose consents should be linked */\n\tanonymousId: string;\n}\n\nexport interface GetConsentHistoryInput {\n\t/** User ID (for authenticated users) */\n\tuserId?: string;\n\t/** Anonymous ID (for anonymous users) */\n\tanonymousId?: string;\n\t/** Maximum records to return (default: 50) */\n\tlimit?: number;\n\t/** Offset for pagination (default: 0) */\n\toffset?: number;\n}\n\n/** A single consent change history entry */\nexport interface ConsentHistoryEntry {\n\t/** Unique entry ID */\n\tid: string;\n\t/** Consent type slug (e.g., 'analytics') */\n\tconsentType: string;\n\t/** Display name of the consent type */\n\tconsentTypeName: string;\n\t/** Previous consent state (null = initial consent) */\n\tpreviousGranted: boolean | null;\n\t/** New consent state */\n\tnewGranted: boolean;\n\t/** Source of the change (banner, settings, api) */\n\tsource: string;\n\t/** Reason for change (user_action, policy_update, etc.) */\n\treason: string | null;\n\t/** ISO timestamp when change occurred */\n\tcreatedAt: string;\n}\n\nexport interface ConsentHistoryResult {\n\t/** List of consent change entries */\n\tentries: ConsentHistoryEntry[];\n\t/** Total number of entries */\n\ttotal: number;\n\t/** Whether there are more entries */\n\thasMore: boolean;\n}\n\nexport interface GetConsentsInput {\n\t/** User ID (optional for anonymous users) */\n\tuserId?: string;\n\t/** Anonymous ID (for guest users) */\n\tanonymousId?: string;\n}\n\n/**\n * Inline defaults for consent purpose auto-discovery\n *\n * @example\n * ```typescript\n * await hasConsent(config, 'analytics', { userId: 'user-123' }, {\n * name: 'Analytics Cookies',\n * description: 'Help us understand how visitors use our site',\n * category: 'analytics',\n * required: false,\n * })\n * ```\n */\nexport interface ConsentPurposeDefaults {\n\t/** Display name */\n\tname?: string;\n\t/** Description */\n\tdescription?: string;\n\t/** Category */\n\tcategory?: ConsentCategory;\n\t/** Whether consent is required (always granted) */\n\trequired?: boolean;\n\t/** Whether enabled by default */\n\tdefaultEnabled?: boolean;\n\t/** Sort order in UI */\n\tsortOrder?: number;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Get all consent types configured for the app\n *\n * Returns GDPR-standard defaults if none configured.\n *\n * @example\n * ```typescript\n * const types = await getConsentTypes(config)\n * types.forEach(t => console.log(`${t.name}: ${t.required ? 'Required' : 'Optional'}`))\n * ```\n */\nexport async function getConsentTypes(\n\tconfig: SylphxConfig,\n): Promise<ConsentType[]> {\n\treturn callApi(config, \"/consent/types\", { method: \"GET\" });\n}\n\n/**\n * Check if user has granted consent for a specific purpose\n *\n * If the consent type doesn't exist, it will be auto-discovered with the provided defaults.\n * Console can override any values without deployment.\n *\n * @param config - SDK configuration\n * @param purposeSlug - Consent purpose slug (e.g., 'analytics', 'marketing')\n * @param input - User identification (userId or anonymousId)\n * @param defaults - Optional inline defaults for auto-discovery\n * @returns Whether consent is granted\n *\n * @example\n * ```typescript\n * // Check analytics consent with inline defaults\n * if (await hasConsent(config, 'analytics', { userId: 'user-123' }, {\n * name: 'Analytics Cookies',\n * description: 'Help us understand how visitors use our site',\n * category: 'analytics',\n * required: false,\n * })) {\n * track('pageview')\n * }\n *\n * // Required consent always returns true\n * const hasNecessary = await hasConsent(config, 'necessary', { userId }, {\n * name: 'Essential Cookies',\n * description: 'Required for the website to function',\n * category: 'necessary',\n * required: true,\n * })\n * ```\n */\nexport async function hasConsent(\n\tconfig: SylphxConfig,\n\tpurposeSlug: string,\n\tinput: GetConsentsInput,\n\tdefaults?: ConsentPurposeDefaults,\n): Promise<boolean> {\n\treturn callApi(config, \"/consent/check\", {\n\t\tmethod: \"POST\",\n\t\tbody: { purposeSlug, ...input, defaults },\n\t});\n}\n\n/**\n * Get user's current consent settings\n *\n * @example\n * ```typescript\n * // For authenticated user\n * const consents = await getUserConsents(config, { userId: 'user-123' })\n *\n * // For anonymous user\n * const consents = await getUserConsents(config, { anonymousId: 'anon-456' })\n * ```\n */\nexport async function getUserConsents(\n\tconfig: SylphxConfig,\n\tinput: GetConsentsInput,\n): Promise<UserConsent[]> {\n\treturn callApi(config, \"/consent/user\", {\n\t\tmethod: \"GET\",\n\t\tquery: input as Record<string, string | undefined>,\n\t});\n}\n\n/**\n * Set user's consent preferences\n *\n * @example\n * ```typescript\n * await setConsents(config, {\n * userId: 'user-123',\n * consents: {\n * analytics: true,\n * marketing: false,\n * },\n * })\n * ```\n */\nexport async function setConsents(\n\tconfig: SylphxConfig,\n\tinput: SetConsentsInput,\n): Promise<void> {\n\treturn callApi(config, \"/consent/set\", { method: \"POST\", body: input });\n}\n\n/**\n * Accept all consent types\n *\n * @example\n * ```typescript\n * await acceptAllConsents(config, { userId: 'user-123' })\n * ```\n */\nexport async function acceptAllConsents(\n\tconfig: SylphxConfig,\n\tinput: GetConsentsInput,\n): Promise<void> {\n\treturn callApi(config, \"/consent/accept-all\", {\n\t\tmethod: \"POST\",\n\t\tbody: input,\n\t});\n}\n\n/**\n * Decline all optional consent types (keeps required ones)\n *\n * @example\n * ```typescript\n * await declineOptionalConsents(config, { anonymousId: 'anon-456' })\n * ```\n */\nexport async function declineOptionalConsents(\n\tconfig: SylphxConfig,\n\tinput: GetConsentsInput,\n): Promise<void> {\n\treturn callApi(config, \"/consent/decline-optional\", {\n\t\tmethod: \"POST\",\n\t\tbody: input,\n\t});\n}\n\n/**\n * Link anonymous user's consents to authenticated user\n *\n * Call this after user signs up/logs in to merge their anonymous consent history.\n *\n * @example\n * ```typescript\n * await linkAnonymousConsents(config, {\n * userId: 'user-123',\n * anonymousId: 'anon-456',\n * })\n * ```\n */\nexport async function linkAnonymousConsents(\n\tconfig: SylphxConfig,\n\tinput: LinkAnonymousConsentsInput,\n): Promise<void> {\n\treturn callApi(config, \"/consent/link-anonymous\", {\n\t\tmethod: \"POST\",\n\t\tbody: input,\n\t});\n}\n\n/**\n * Get consent change history for GDPR audit trail\n *\n * Returns a paginated list of all consent state changes for a user.\n * Required for GDPR compliance - provides complete audit trail of consent decisions.\n *\n * @example\n * ```typescript\n * // Get consent history for authenticated user\n * const history = await getConsentHistory(config, { userId: 'user-123' })\n * console.log(`Total changes: ${history.total}`)\n * history.entries.forEach(entry => {\n * console.log(`${entry.consentType}: ${entry.previousGranted} → ${entry.newGranted}`)\n * })\n *\n * // Paginated retrieval\n * const page2 = await getConsentHistory(config, {\n * userId: 'user-123',\n * limit: 20,\n * offset: 20,\n * })\n * ```\n */\nexport async function getConsentHistory(\n\tconfig: SylphxConfig,\n\tinput: GetConsentHistoryInput,\n): Promise<ConsentHistoryResult> {\n\treturn callApi(config, \"/consent/history\", {\n\t\tmethod: \"GET\",\n\t\tquery: {\n\t\t\tuserId: input.userId,\n\t\t\tanonymousId: input.anonymousId,\n\t\t\tlimit: input.limit?.toString(),\n\t\t\toffset: input.offset?.toString(),\n\t\t},\n\t});\n}\n","/**\n * Referrals Functions\n *\n * Pure functions for referral code management and tracking.\n *\n * ## Architecture (ADR-004)\n *\n * Referrals uses **Inline Defaults + Auto-Discovery + Console Override**:\n * - Code provides optional inline defaults when redeeming referral codes\n * - Platform uses defaults if no Console override exists\n * - Console can override reward values without deployment\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n *\n * @example\n * ```typescript\n * import { redeemReferralCode } from '@sylphx/sdk'\n *\n * // Redeem with inline defaults (overridable in Console)\n * const result = await redeemReferralCode(config, {\n * code: 'ABC123',\n * userId: 'new-user-456',\n * }, {\n * referrerReward: { type: 'premium_trial', days: 7 },\n * refereeReward: { type: 'premium_trial', days: 7 },\n * })\n * ```\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { components } from \"./generated/api\";\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type ReferralCodeResponse =\n\tcomponents[\"schemas\"][\"ReferralCodeResponse\"];\nexport type RegenerateCodeResponse =\n\tcomponents[\"schemas\"][\"RegenerateCodeResponse\"];\nexport type ReferralStatsResponse =\n\tcomponents[\"schemas\"][\"ReferralStatsResponse\"];\nexport type RedeemReferralRequest =\n\tcomponents[\"schemas\"][\"RedeemReferralRequest\"];\nexport type RedeemReferralResponse =\n\tcomponents[\"schemas\"][\"RedeemReferralResponse\"];\nexport type ReferralRewardDefaults =\n\tcomponents[\"schemas\"][\"ReferralRewardDefaults\"];\nexport type ReferralRewardConfig =\n\tcomponents[\"schemas\"][\"ReferralRewardConfig\"];\nexport type LeaderboardResponse = components[\"schemas\"][\"LeaderboardResponse\"];\nexport type LeaderboardEntry = components[\"schemas\"][\"LeaderboardEntry\"];\n\n// SDK-specific types for convenience\nexport interface ReferralCode {\n\tcode: string;\n\tcreatedAt: string;\n}\n\nexport interface ReferralStats {\n\t/** User's referral code */\n\tcode: string;\n\t/** Total referrals made */\n\ttotalReferrals: number;\n\t/** Successful (redeemed) referrals */\n\tsuccessfulReferrals: number;\n\t/** Pending referrals */\n\tpendingReferrals: number;\n\t/** Total rewards earned */\n\ttotalRewards: number;\n}\n\ntype LeaderboardPeriod = \"all\" | \"month\" | \"week\";\n\nexport interface LeaderboardResult {\n\t/** Time period for the leaderboard */\n\tperiod?: LeaderboardPeriod;\n\tentries: LeaderboardEntry[];\n\t/** Current user's position (may not be in top entries) */\n\tcurrentUserRank: number | null;\n\t/** Total participants */\n\ttotalParticipants: number;\n}\n\nexport interface RedeemReferralInput {\n\t/** Referral code to redeem */\n\tcode: string;\n\t/** User ID of the person redeeming (optional for anonymous) */\n\tuserId?: string;\n}\n\nexport interface RedeemResult {\n\tsuccess: boolean;\n\t/** Reward type - platform types or app-specific types */\n\trewardType:\n\t\t| \"points\"\n\t\t| \"premium_trial\"\n\t\t| \"discount\"\n\t\t| \"credit\"\n\t\t| (string & {});\n\treferredReward?: Record<string, unknown>;\n\treferrerReward?: Record<string, unknown>;\n}\n\nexport interface LeaderboardOptions {\n\t/** Number of entries to return (default: 10) */\n\tlimit?: number;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Get current user's referral code\n *\n * Creates one if it doesn't exist.\n *\n * @example\n * ```typescript\n * const { code } = await getMyReferralCode(config, 'user-123')\n * console.log(`Share your code: ${code}`)\n * ```\n */\nexport async function getMyReferralCode(\n\tconfig: SylphxConfig,\n\tuserId: string,\n): Promise<ReferralCode> {\n\treturn callApi(config, \"/referrals/code\", {\n\t\tmethod: \"GET\",\n\t\tquery: { userId },\n\t});\n}\n\n/**\n * Get referral statistics for a user\n *\n * @example\n * ```typescript\n * const stats = await getReferralStats(config, 'user-123')\n * console.log(`${stats.successfulReferrals} successful referrals`)\n * ```\n */\nexport async function getReferralStats(\n\tconfig: SylphxConfig,\n\tuserId: string,\n): Promise<ReferralStats> {\n\treturn callApi(config, \"/referrals/stats\", {\n\t\tmethod: \"GET\",\n\t\tquery: { userId },\n\t});\n}\n\n/**\n * Redeem a referral code\n *\n * If the referral program rewards aren't configured in Console, the provided\n * defaults will be used. Console can override any values without deployment.\n *\n * @param config - SDK configuration\n * @param input - Referral redemption input (code, userId)\n * @param defaults - Optional inline defaults for reward configuration\n *\n * @example\n * ```typescript\n * // Basic redemption (uses Console-configured rewards)\n * const result = await redeemReferralCode(config, {\n * code: 'ABC123',\n * userId: 'new-user-456',\n * })\n *\n * // With inline defaults (auto-discovered if not in Console)\n * const result = await redeemReferralCode(config, {\n * code: 'ABC123',\n * userId: 'new-user-456',\n * }, {\n * referrerReward: { type: 'premium_trial', days: 7 },\n * refereeReward: { type: 'premium_trial', days: 7 },\n * })\n *\n * if (result.success) {\n * console.log(`Reward: ${result.reward?.type}`)\n * }\n * ```\n */\nexport async function redeemReferralCode(\n\tconfig: SylphxConfig,\n\tinput: RedeemReferralInput,\n\tdefaults?: ReferralRewardDefaults,\n): Promise<RedeemResult> {\n\treturn callApi(config, \"/referrals/redeem\", {\n\t\tmethod: \"POST\",\n\t\tbody: { ...input, defaults },\n\t});\n}\n\n/**\n * Get referral leaderboard\n *\n * @example\n * ```typescript\n * const { entries, currentUserRank } = await getReferralLeaderboard(config, 'user-123')\n *\n * entries.forEach(e => {\n * console.log(`#${e.rank} ${e.displayName}: ${e.completedReferrals} referrals`)\n * })\n * ```\n */\nexport async function getReferralLeaderboard(\n\tconfig: SylphxConfig,\n\tuserId: string,\n\toptions?: LeaderboardOptions,\n): Promise<LeaderboardResult> {\n\treturn callApi(config, \"/referrals/leaderboard\", {\n\t\tmethod: \"GET\",\n\t\tquery: { userId, ...options } as Record<\n\t\t\tstring,\n\t\t\tstring | number | undefined\n\t\t>,\n\t});\n}\n\n/**\n * Regenerate user's referral code\n *\n * Use this if the current code has been compromised or user wants a fresh start.\n *\n * @example\n * ```typescript\n * const { code } = await regenerateReferralCode(config, 'user-123')\n * console.log(`New code: ${code}`)\n * ```\n */\nexport async function regenerateReferralCode(\n\tconfig: SylphxConfig,\n\tuserId: string,\n): Promise<ReferralCode> {\n\treturn callApi(config, \"/referrals/code/regenerate\", {\n\t\tmethod: \"POST\",\n\t\tbody: { userId },\n\t});\n}\n","/**\n * Engagement Service Types\n *\n * Core types for streaks, leaderboards, and achievements.\n *\n * ## Architecture (ADR-004)\n *\n * Engagement uses **Inline Defaults + Auto-Discovery + Console Override**:\n * - Code provides optional inline defaults when calling APIs\n * - Platform auto-discovers/creates entities when first referenced\n * - Console can override names, descriptions, values without deployment\n */\n\n// ============================================================================\n// Streaks\n// ============================================================================\n\n/** Streak activity frequency */\nexport type StreakFrequency = \"daily\" | \"weekly\" | \"custom\";\n\n/** Streak definition (auto-discovered or from Console) */\nexport interface StreakDefinition {\n\t/** Unique identifier */\n\tid: string;\n\t/** Display name */\n\tname: string;\n\t/** Description */\n\tdescription?: string;\n\t/** Activity frequency */\n\tfrequency: StreakFrequency;\n\t/** Grace period in hours (default: 0) */\n\tgracePeriodHours?: number;\n\t/** Whether streak resets on miss (default: true) */\n\tresetOnMiss?: boolean;\n\t/** Maximum streak value (optional cap) */\n\tmaxValue?: number;\n\t/** Custom interval in hours (only for 'custom' frequency) */\n\tcustomIntervalHours?: number;\n}\n\n/** User's streak state (from platform) */\nexport interface StreakState {\n\t/** Streak definition ID */\n\tstreakId: string;\n\t/** Current streak count */\n\tcurrent: number;\n\t/** Longest streak ever */\n\tlongest: number;\n\t/** Last activity timestamp */\n\tlastActivityAt: string | null;\n\t/** When current streak will expire */\n\texpiresAt: string | null;\n\t/** Whether streak can be recovered (within grace period) */\n\tcanRecover: boolean;\n\t/** Time remaining until expiry in ms */\n\ttimeRemainingMs: number | null;\n\t/** User's timezone preference for streak expiry (IANA timezone, e.g., 'America/New_York') */\n\tuserTimezone: string | null;\n}\n\n/** Activity recording input */\nexport interface RecordActivityInput {\n\t/** Streak ID */\n\tstreakId: string;\n\t/** User's IANA timezone (e.g., 'America/New_York') for calculating streak expiry at user's local midnight */\n\tuserTimezone?: string;\n\t/** Optional metadata */\n\tmetadata?: Record<string, unknown>;\n\t/**\n\t * Idempotency key for safe retries (Stripe pattern)\n\t *\n\t * Prevents duplicate streak recordings if the same request is retried.\n\t * Use a unique key per logical activity (e.g., `streak-${userId}-${date}`).\n\t */\n\tidempotencyKey?: string;\n}\n\n/** Activity recording result */\nexport interface RecordActivityResult {\n\t/** Updated streak state */\n\tstreak: StreakState;\n\t/** Whether this activity extended the streak */\n\textended: boolean;\n\t/** Whether a new personal best was achieved */\n\tnewPersonalBest: boolean;\n\t/** Previous streak value (for animation) */\n\tpreviousValue: number;\n}\n\n// ============================================================================\n// Leaderboards\n// ============================================================================\n\n/** Leaderboard sort direction */\nexport type LeaderboardSortDirection = \"asc\" | \"desc\";\n\n/** Leaderboard reset period */\nexport type LeaderboardResetPeriod =\n\t| \"hourly\"\n\t| \"daily\"\n\t| \"weekly\"\n\t| \"monthly\"\n\t| \"never\";\n\n/** Score aggregation method */\nexport type LeaderboardAggregation =\n\t| \"max\"\n\t| \"sum\"\n\t| \"latest\"\n\t| \"count\"\n\t| \"min\"\n\t| \"avg\";\n\n/** Leaderboard definition (auto-discovered or from Console) */\nexport interface LeaderboardDefinition {\n\t/** Unique identifier */\n\tid: string;\n\t/** Display name */\n\tname: string;\n\t/** Description */\n\tdescription?: string;\n\t/** Sort direction (desc = higher is better) */\n\tsortDirection: LeaderboardSortDirection;\n\t/** Reset period */\n\tresetPeriod: LeaderboardResetPeriod;\n\t/** How to aggregate multiple scores from same user */\n\taggregation: LeaderboardAggregation;\n\t/** Default privacy for entries */\n\tdefaultPrivacy?: \"public\" | \"friends\" | \"anonymous\";\n\t/** Maximum entries to keep per period */\n\tmaxEntries?: number;\n}\n\n/** Leaderboard entry */\nexport interface LeaderboardEntry {\n\t/** Rank (1-indexed) */\n\trank: number;\n\t/** User ID (may be null for anonymous) */\n\tuserId: string | null;\n\t/** Display name */\n\tdisplayName: string;\n\t/** Avatar URL */\n\tavatarUrl: string | null;\n\t/** Score/value */\n\tvalue: number;\n\t/** Whether this is the current user */\n\tisCurrentUser: boolean;\n\t/** Entry metadata */\n\tmetadata?: Record<string, unknown>;\n\t/** When the score was submitted */\n\tsubmittedAt: string;\n}\n\n/** Leaderboard query options */\nexport interface LeaderboardQueryOptions {\n\t/** Number of entries to return (default: 10) */\n\tlimit?: number;\n\t/** Offset for pagination */\n\toffset?: number;\n\t/** Include surrounding entries for current user */\n\tincludeSurrounding?: boolean;\n\t/** Number of surrounding entries (default: 2) */\n\tsurroundingCount?: number;\n}\n\n/** Leaderboard query result */\nexport interface LeaderboardResult {\n\t/** Leaderboard definition ID */\n\tleaderboardId: string;\n\t/** Period (for periodic leaderboards) */\n\tperiod?: string;\n\t/** Entries (top N or paginated) */\n\tentries: LeaderboardEntry[];\n\t/** Current user's entry (may not be in top entries) */\n\tcurrentUserEntry: LeaderboardEntry | null;\n\t/** Entries surrounding the current user (when includeSurrounding=true and user is outside main entries) */\n\tsurroundingEntries?: LeaderboardEntry[];\n\t/** Total participants */\n\ttotalParticipants: number;\n\t/** Next reset time (for periodic leaderboards) */\n\tnextResetAt: string | null;\n}\n\n/** Score submission input */\nexport interface SubmitScoreInput {\n\t/** Leaderboard ID */\n\tleaderboardId: string;\n\t/** Score value */\n\tvalue: number;\n\t/** Optional metadata */\n\tmetadata?: Record<string, unknown>;\n}\n\n/** Score submission result */\nexport interface SubmitScoreResult {\n\t/** Whether submission was accepted */\n\taccepted: boolean;\n\t/** New rank (if in leaderboard) */\n\trank: number | null;\n\t/** Previous best (if any) */\n\tpreviousBest: number | null;\n\t/** Whether this is a new personal best */\n\tnewPersonalBest: boolean;\n\t/** Rank change (positive = improved) */\n\trankChange: number | null;\n}\n\n// ============================================================================\n// Achievements\n// ============================================================================\n\n/** Achievement type */\nexport type AchievementType = \"standard\" | \"hidden\" | \"incremental\";\n\n/** Achievement tier */\nexport type AchievementTier =\n\t| \"bronze\"\n\t| \"silver\"\n\t| \"gold\"\n\t| \"platinum\"\n\t| \"diamond\";\n\n/** Achievement category */\nexport type AchievementCategory = string; // App-defined\n\n/** Achievement criteria operator */\nexport type CriteriaOperator =\n\t| \"eq\"\n\t| \"ne\"\n\t| \"gt\"\n\t| \"gte\"\n\t| \"lt\"\n\t| \"lte\"\n\t| \"in\"\n\t| \"contains\";\n\n/** Single criterion */\nexport interface AchievementCriterion {\n\t/** Property to check (e.g., 'event', 'count', 'streak.daily') */\n\tproperty: string;\n\t/** Comparison operator */\n\toperator: CriteriaOperator;\n\t/** Value to compare against */\n\tvalue: string | number | boolean | string[] | number[];\n}\n\n/** Achievement criteria (AND logic within, OR between arrays) */\nexport interface AchievementCriteria {\n\t/** Event name to track (for event-based achievements) */\n\tevent?: string;\n\t/** Required count of events */\n\tcount?: number;\n\t/** Additional conditions */\n\tconditions?: AchievementCriterion[];\n}\n\n/** Achievement definition (auto-discovered or from Console) */\nexport interface AchievementDefinition {\n\t/** Unique identifier */\n\tid: string;\n\t/** Display name */\n\tname: string;\n\t/** Description (shown before unlock) */\n\tdescription: string;\n\t/** Description shown after unlock (optional) */\n\tunlockedDescription?: string;\n\t/** Achievement type */\n\ttype: AchievementType;\n\t/** Tier/rarity */\n\ttier: AchievementTier;\n\t/** Category (app-defined) */\n\tcategory: AchievementCategory;\n\t/** Icon (Iconify name or URL) */\n\ticon: string;\n\t/** Points awarded */\n\tpoints?: number;\n\t/** Unlock criteria */\n\tcriteria: AchievementCriteria;\n\t/** Target value for incremental achievements */\n\ttarget?: number;\n\t/** Whether to show in list before unlock */\n\tsecret?: boolean;\n\t/** Order in list */\n\torder?: number;\n}\n\n/** User's achievement state */\nexport interface UserAchievement {\n\t/** Achievement definition ID */\n\tachievementId: string;\n\t/** Whether unlocked */\n\tunlocked: boolean;\n\t/** Unlock timestamp */\n\tunlockedAt: string | null;\n\t/** Progress (for incremental) */\n\tprogress: number;\n\t/** Target (for incremental) */\n\ttarget: number | null;\n\t/** Progress percentage (0-100) */\n\tprogressPercent: number;\n}\n\n/** Achievement unlock event */\nexport interface AchievementUnlockEvent {\n\t/** Achievement definition */\n\tachievement: AchievementDefinition;\n\t/** User achievement state */\n\tuserAchievement: UserAchievement;\n\t/** Whether this is a new unlock (vs already unlocked) */\n\tisNew: boolean;\n}\n\n// ============================================================================\n// Engagement Config (from Platform)\n// ============================================================================\n\n/** Complete engagement configuration (fetched from platform) */\ninterface EngagementConfig {\n\t/** Streak definitions */\n\tstreaks?: StreakDefinition[];\n\t/** Leaderboard definitions */\n\tleaderboards?: LeaderboardDefinition[];\n\t/** Achievement definitions */\n\tachievements?: AchievementDefinition[];\n\t/** Achievement categories (for UI grouping) */\n\tachievementCategories?: {\n\t\tid: string;\n\t\tname: string;\n\t\ticon?: string;\n\t\torder?: number;\n\t}[];\n}\n\n// ============================================================================\n// Tier Metadata (for UI)\n// ============================================================================\n\nexport const ACHIEVEMENT_TIER_CONFIG = {\n\tbronze: { color: \"#CD7F32\", points: 10 },\n\tsilver: { color: \"#C0C0C0\", points: 25 },\n\tgold: { color: \"#FFD700\", points: 50 },\n\tplatinum: { color: \"#00CED1\", points: 100 },\n\tdiamond: { color: \"#B9F2FF\", points: 200 },\n} as const;\n\n// ============================================================================\n// Inline Defaults (Auto-Discovery)\n// ============================================================================\n// These types define the optional inline defaults that can be passed when\n// calling engagement functions. If the entity doesn't exist, the platform\n// will auto-create it with these defaults. Console can override any values.\n\n/**\n * Inline defaults for streak auto-discovery\n *\n * @example\n * ```typescript\n * await recordStreakActivity(config, { streakId: 'daily-login' }, userId, {\n * name: 'Daily Login',\n * frequency: 'daily',\n * gracePeriodHours: 12,\n * })\n * ```\n */\nexport interface StreakDefaults {\n\t/** Display name */\n\tname?: string;\n\t/** Description */\n\tdescription?: string;\n\t/** Activity frequency */\n\tfrequency?: StreakFrequency;\n\t/** Grace period in hours (default: 0) */\n\tgracePeriodHours?: number;\n\t/** Whether streak resets on miss (default: true) */\n\tresetOnMiss?: boolean;\n\t/** Maximum streak value (optional cap) */\n\tmaxValue?: number;\n\t/** Custom interval in hours (only for 'custom' frequency) */\n\tcustomIntervalHours?: number;\n}\n\n/**\n * Inline defaults for leaderboard auto-discovery\n *\n * @example\n * ```typescript\n * await submitScore(config, { leaderboardId: 'high-scores', value: 1500 }, userId, {\n * name: 'High Scores',\n * sortDirection: 'desc',\n * resetPeriod: 'weekly',\n * })\n * ```\n */\nexport interface LeaderboardDefaults {\n\t/** Display name */\n\tname?: string;\n\t/** Description */\n\tdescription?: string;\n\t/** Sort direction (desc = higher is better) */\n\tsortDirection?: LeaderboardSortDirection;\n\t/** Reset period */\n\tresetPeriod?: LeaderboardResetPeriod;\n\t/** How to aggregate multiple scores from same user */\n\taggregation?: LeaderboardAggregation;\n\t/** Maximum entries to keep per period */\n\tmaxEntries?: number;\n}\n\n/**\n * Inline defaults for achievement auto-discovery\n *\n * @example\n * ```typescript\n * await unlockAchievement(config, 'first-purchase', userId, {\n * name: 'First Purchase',\n * description: 'Made your first purchase',\n * points: 100,\n * tier: 'bronze',\n * })\n * ```\n */\nexport interface AchievementDefaults {\n\t/** Display name */\n\tname?: string;\n\t/** Description (shown before unlock) */\n\tdescription?: string;\n\t/** Description shown after unlock */\n\tunlockedDescription?: string;\n\t/** Achievement type */\n\ttype?: AchievementType;\n\t/** Tier/rarity */\n\ttier?: AchievementTier;\n\t/** Category (app-defined) */\n\tcategory?: AchievementCategory;\n\t/** Icon (Iconify name or URL) */\n\ticon?: string;\n\t/** Points awarded */\n\tpoints?: number;\n\t/** Target value for incremental achievements */\n\ttarget?: number;\n\t/** Whether to show in list before unlock */\n\tsecret?: boolean;\n}\n","/**\n * Engagement Functions\n *\n * Pure functions for streaks, leaderboards, and achievements.\n *\n * ## Architecture (ADR-004)\n *\n * Engagement uses **Inline Defaults + Auto-Discovery + Console Override**:\n * - Code provides optional inline defaults when calling functions\n * - Platform auto-discovers/creates entities when first referenced\n * - Console can override names, descriptions, values without deployment\n *\n * @example\n * ```typescript\n * import { unlockAchievement, recordStreakActivity, submitScore } from '@sylphx/sdk'\n *\n * // Unlock achievement with inline defaults (auto-discovered if doesn't exist)\n * await unlockAchievement(config, 'first-win', userId, {\n * name: 'First Win',\n * description: 'Won your first game',\n * points: 100,\n * tier: 'bronze',\n * })\n *\n * // Record streak activity with inline defaults\n * await recordStreakActivity(config, { streakId: 'daily-login' }, userId, {\n * name: 'Daily Login',\n * frequency: 'daily',\n * gracePeriodHours: 12,\n * })\n *\n * // Submit leaderboard score with inline defaults\n * await submitScore(config, { leaderboardId: 'high-scores', value: 1500 }, userId, {\n * name: 'High Scores',\n * sortDirection: 'desc',\n * resetPeriod: 'weekly',\n * })\n * ```\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\n\n// Re-export types from types file\nexport type {\n\t// Streaks\n\tStreakDefinition,\n\tStreakState,\n\tStreakFrequency,\n\tRecordActivityInput,\n\tRecordActivityResult,\n\t// Leaderboards\n\tLeaderboardDefinition,\n\tLeaderboardEntry,\n\tLeaderboardResult,\n\tLeaderboardQueryOptions,\n\tLeaderboardSortDirection,\n\tLeaderboardResetPeriod,\n\tLeaderboardAggregation,\n\tSubmitScoreInput,\n\tSubmitScoreResult,\n\t// Achievements\n\tAchievementDefinition,\n\tAchievementType,\n\tAchievementTier,\n\tAchievementCategory,\n\tAchievementCriteria,\n\tAchievementCriterion,\n\tCriteriaOperator,\n\tUserAchievement,\n\tAchievementUnlockEvent,\n} from \"./lib/engagement/types\";\n\nexport { ACHIEVEMENT_TIER_CONFIG } from \"./lib/engagement/types\";\n\n// ============================================================================\n// Streak Functions\n// ============================================================================\n\nimport type {\n\tRecordActivityInput,\n\tRecordActivityResult,\n\tStreakDefaults,\n\tStreakState,\n} from \"./lib/engagement/types\";\n\n/**\n * Get current streak state for a user\n *\n * @example\n * ```typescript\n * const streak = await getStreak(config, 'daily-challenge', userId)\n * console.log(`Current streak: ${streak.current}`)\n * console.log(`Expires in: ${streak.timeRemainingMs}ms`)\n * ```\n */\nexport async function getStreak(\n\tconfig: SylphxConfig,\n\tstreakId: string,\n\tuserId: string,\n): Promise<StreakState> {\n\treturn callApi(config, \"/engagement/streaks/get\", {\n\t\tmethod: \"GET\",\n\t\tquery: { streakId, userId },\n\t});\n}\n\n/**\n * Get all streak states for a user\n *\n * @example\n * ```typescript\n * const streaks = await getAllStreaks(config, userId)\n * for (const streak of streaks) {\n * console.log(`${streak.streakId}: ${streak.current}`)\n * }\n * ```\n */\nexport async function getAllStreaks(\n\tconfig: SylphxConfig,\n\tuserId: string,\n): Promise<StreakState[]> {\n\treturn callApi(config, \"/engagement/streaks\", {\n\t\tmethod: \"GET\",\n\t\tquery: { userId },\n\t});\n}\n\n/**\n * Record an activity to extend/maintain a streak\n *\n * If the streak doesn't exist, it will be auto-discovered with the provided defaults.\n * Console can override any values without deployment.\n *\n * @param config - SDK configuration\n * @param input - Activity input (streakId required)\n * @param userId - User ID\n * @param defaults - Optional inline defaults for auto-discovery\n *\n * @example\n * ```typescript\n * const result = await recordStreakActivity(config, {\n * streakId: 'daily-challenge',\n * }, userId, {\n * name: 'Daily Challenge',\n * frequency: 'daily',\n * gracePeriodHours: 12,\n * })\n *\n * if (result.extended) {\n * console.log(`Streak extended to ${result.streak.current}!`)\n * }\n * if (result.newPersonalBest) {\n * console.log('New personal best!')\n * }\n * ```\n */\nexport async function recordStreakActivity(\n\tconfig: SylphxConfig,\n\tinput: RecordActivityInput,\n\tuserId: string,\n\tdefaults?: StreakDefaults,\n): Promise<RecordActivityResult> {\n\tconst { idempotencyKey, ...inputBody } = input;\n\treturn callApi(config, \"/engagement/streaks/record\", {\n\t\tmethod: \"POST\",\n\t\tbody: { ...inputBody, userId, defaults },\n\t\tidempotencyKey,\n\t});\n}\n\n/**\n * Recover a streak within grace period (may require payment/reward)\n *\n * @example\n * ```typescript\n * const result = await recoverStreak(config, 'daily-challenge', userId)\n * if (result.success) {\n * console.log(`Streak recovered at ${result.streak.current}`)\n * }\n * ```\n */\nexport async function recoverStreak(\n\tconfig: SylphxConfig,\n\tstreakId: string,\n\tuserId: string,\n): Promise<{ success: boolean; streak: StreakState }> {\n\treturn callApi(config, \"/engagement/streaks/recover\", {\n\t\tmethod: \"POST\",\n\t\tbody: { streakId, userId },\n\t});\n}\n\n// ============================================================================\n// Leaderboard Functions\n// ============================================================================\n\nimport type {\n\tLeaderboardDefaults,\n\tLeaderboardQueryOptions,\n\tLeaderboardResult,\n\tSubmitScoreInput,\n\tSubmitScoreResult,\n} from \"./lib/engagement/types\";\n\n/**\n * Get leaderboard entries\n *\n * @example\n * ```typescript\n * const result = await getLeaderboard(config, 'high-scores', userId, {\n * limit: 10,\n * includeSurrounding: true,\n * })\n *\n * for (const entry of result.entries) {\n * console.log(`#${entry.rank} ${entry.displayName}: ${entry.value}`)\n * }\n *\n * if (result.currentUserEntry) {\n * console.log(`Your rank: #${result.currentUserEntry.rank}`)\n * }\n * ```\n */\nexport async function getLeaderboard(\n\tconfig: SylphxConfig,\n\tleaderboardId: string,\n\tuserId: string | null,\n\toptions?: LeaderboardQueryOptions,\n): Promise<LeaderboardResult> {\n\treturn callApi(config, \"/engagement/leaderboards/get\", {\n\t\tmethod: \"GET\",\n\t\tquery: { leaderboardId, userId: userId ?? undefined, ...options } as Record<\n\t\t\tstring,\n\t\t\tstring | number | boolean | undefined\n\t\t>,\n\t});\n}\n\n/**\n * Submit a score to a leaderboard\n *\n * If the leaderboard doesn't exist, it will be auto-discovered with the provided defaults.\n * Console can override any values without deployment.\n *\n * @param config - SDK configuration\n * @param input - Score submission input (leaderboardId, value required)\n * @param userId - User ID\n * @param defaults - Optional inline defaults for auto-discovery\n *\n * @example\n * ```typescript\n * const result = await submitScore(config, {\n * leaderboardId: 'high-scores',\n * value: 1500,\n * metadata: { level: 'hard' },\n * }, userId, {\n * name: 'High Scores',\n * sortDirection: 'desc',\n * resetPeriod: 'weekly',\n * aggregation: 'max',\n * })\n *\n * if (result.newPersonalBest) {\n * console.log('New personal best!')\n * }\n * if (result.rank !== null) {\n * console.log(`Ranked #${result.rank}`)\n * }\n * ```\n */\nexport async function submitScore(\n\tconfig: SylphxConfig,\n\tinput: SubmitScoreInput,\n\tuserId: string,\n\tdefaults?: LeaderboardDefaults,\n): Promise<SubmitScoreResult> {\n\treturn callApi(config, \"/engagement/leaderboards/submit\", {\n\t\tmethod: \"POST\",\n\t\tbody: { ...input, userId, defaults },\n\t});\n}\n\n/**\n * Get user's rank on a leaderboard (even if not in top entries)\n *\n * @example\n * ```typescript\n * const rank = await getUserRank(config, 'high-scores', userId)\n * if (rank) {\n * console.log(`You are ranked #${rank.rank} with score ${rank.value}`)\n * }\n * ```\n */\nexport async function getUserLeaderboardRank(\n\tconfig: SylphxConfig,\n\tleaderboardId: string,\n\tuserId: string,\n): Promise<{ rank: number; value: number } | null> {\n\treturn callApi(config, \"/engagement/leaderboards/rank\", {\n\t\tmethod: \"GET\",\n\t\tquery: { leaderboardId, userId },\n\t});\n}\n\n// ============================================================================\n// Achievement Functions\n// ============================================================================\n\nimport type {\n\tAchievementDefaults,\n\tAchievementUnlockEvent,\n\tUserAchievement,\n} from \"./lib/engagement/types\";\n\n/**\n * Get all achievements with user progress\n *\n * @example\n * ```typescript\n * const achievements = await getAchievements(config, userId)\n *\n * const unlocked = achievements.filter(a => a.unlocked)\n * console.log(`${unlocked.length} achievements unlocked`)\n *\n * const inProgress = achievements.filter(a => !a.unlocked && a.progress > 0)\n * for (const a of inProgress) {\n * console.log(`${a.achievementId}: ${a.progress}/${a.target}`)\n * }\n * ```\n */\nexport async function getAchievements(\n\tconfig: SylphxConfig,\n\tuserId: string,\n): Promise<UserAchievement[]> {\n\treturn callApi(config, \"/engagement/achievements\", {\n\t\tmethod: \"GET\",\n\t\tquery: { userId },\n\t});\n}\n\n/**\n * Get a single achievement with user progress\n *\n * @example\n * ```typescript\n * const achievement = await getAchievement(config, 'first-win', userId)\n * if (achievement?.unlocked) {\n * console.log(`Unlocked at ${achievement.unlockedAt}`)\n * }\n * ```\n */\nexport async function getAchievement(\n\tconfig: SylphxConfig,\n\tachievementId: string,\n\tuserId: string,\n): Promise<UserAchievement | null> {\n\treturn callApi(config, \"/engagement/achievements/get\", {\n\t\tmethod: \"GET\",\n\t\tquery: { achievementId, userId },\n\t});\n}\n\n/**\n * Manually unlock an achievement\n *\n * If the achievement doesn't exist, it will be auto-discovered with the provided defaults.\n * Console can override any values without deployment.\n *\n * @param config - SDK configuration\n * @param achievementId - Achievement ID\n * @param userId - User ID\n * @param defaults - Optional inline defaults for auto-discovery\n *\n * @example\n * ```typescript\n * const result = await unlockAchievement(config, 'first-purchase', userId, {\n * name: 'First Purchase',\n * description: 'Made your first purchase',\n * points: 100,\n * tier: 'bronze',\n * })\n * if (result.isNew) {\n * showAchievementToast(result.achievement)\n * }\n * ```\n */\nexport async function unlockAchievement(\n\tconfig: SylphxConfig,\n\tachievementId: string,\n\tuserId: string,\n\tdefaults?: AchievementDefaults,\n): Promise<AchievementUnlockEvent> {\n\treturn callApi(config, \"/engagement/achievements/unlock\", {\n\t\tmethod: \"POST\",\n\t\tbody: { achievementId, userId, defaults },\n\t});\n}\n\n/**\n * Increment progress on an incremental achievement\n *\n * If the achievement doesn't exist, it will be auto-discovered with the provided defaults.\n * Console can override any values without deployment.\n *\n * @param config - SDK configuration\n * @param achievementId - Achievement ID\n * @param amount - Amount to increment\n * @param userId - User ID\n * @param defaults - Optional inline defaults for auto-discovery\n *\n * @example\n * ```typescript\n * // User collected an item\n * const result = await incrementAchievementProgress(config, 'collector-100', 1, userId, {\n * name: 'Collector',\n * description: 'Collect 100 items',\n * type: 'incremental',\n * target: 100,\n * tier: 'silver',\n * })\n *\n * if (result.unlocked) {\n * console.log('Achievement unlocked!')\n * } else {\n * console.log(`Progress: ${result.progress}/${result.target}`)\n * }\n * ```\n */\nexport async function incrementAchievementProgress(\n\tconfig: SylphxConfig,\n\tachievementId: string,\n\tamount: number,\n\tuserId: string,\n\tdefaults?: AchievementDefaults,\n): Promise<UserAchievement> {\n\treturn callApi(config, \"/engagement/achievements/progress\", {\n\t\tmethod: \"POST\",\n\t\tbody: { achievementId, amount, userId, defaults },\n\t});\n}\n\n/**\n * Get total achievement points for a user\n *\n * @example\n * ```typescript\n * const points = await getAchievementPoints(config, userId)\n * console.log(`Total points: ${points.total}`)\n * console.log(`This month: ${points.thisMonth}`)\n * ```\n */\nexport async function getAchievementPoints(\n\tconfig: SylphxConfig,\n\tuserId: string,\n): Promise<{ total: number; thisMonth: number; rank: number | null }> {\n\treturn callApi(config, \"/engagement/achievements/points\", {\n\t\tmethod: \"GET\",\n\t\tquery: { userId },\n\t});\n}\n","/**\n * Organization Functions\n *\n * Pure functions for organization management - no hidden state.\n * Each function takes config as the first parameter.\n *\n * Uses REST API at /api/sdk/orgs/* for all operations.\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { components } from \"./generated/api\";\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type Organization = components[\"schemas\"][\"Organization\"];\nexport type OrganizationMember = components[\"schemas\"][\"OrganizationMember\"];\nexport type OrganizationInvitation =\n\tcomponents[\"schemas\"][\"OrganizationInvitation\"];\nexport type OrganizationMembership =\n\tcomponents[\"schemas\"][\"OrganizationMembership\"];\nexport type OrgRole = components[\"schemas\"][\"OrgRole\"];\nexport type CreateOrgInput = components[\"schemas\"][\"CreateOrgRequest\"];\nexport type UpdateOrgInput = components[\"schemas\"][\"UpdateOrgRequest\"];\nexport type InviteMemberInput = components[\"schemas\"][\"InviteMemberRequest\"];\n\n// ============================================================================\n// Organization CRUD\n// ============================================================================\n\n/**\n * Get all organizations the current user belongs to\n *\n * @example\n * ```typescript\n * const { organizations } = await getOrganizations(config)\n * ```\n */\nexport async function getOrganizations(\n\tconfig: SylphxConfig,\n): Promise<{ organizations: Organization[] }> {\n\treturn callApi<{ organizations: Organization[] }>(config, \"/orgs\");\n}\n\n/**\n * Get organization by ID or slug\n *\n * @example\n * ```typescript\n * const { organization, membership } = await getOrganization(config, 'my-org')\n * ```\n */\nexport async function getOrganization(\n\tconfig: SylphxConfig,\n\torgIdOrSlug: string,\n): Promise<{\n\torganization: Organization;\n\tmembership: OrganizationMembership | null;\n}> {\n\treturn callApi<{\n\t\torganization: Organization;\n\t\tmembership: OrganizationMembership | null;\n\t}>(config, `/orgs/${orgIdOrSlug}`);\n}\n\n/**\n * Create a new organization\n *\n * @example\n * ```typescript\n * const { organization } = await createOrganization(config, {\n * name: 'My Company',\n * slug: 'my-company',\n * })\n * ```\n */\nexport async function createOrganization(\n\tconfig: SylphxConfig,\n\tinput: CreateOrgInput,\n): Promise<{ organization: Organization }> {\n\treturn callApi<{ organization: Organization }>(config, \"/orgs\", {\n\t\tmethod: \"POST\",\n\t\tbody: input,\n\t});\n}\n\n/**\n * Update an organization\n *\n * @example\n * ```typescript\n * const { organization } = await updateOrganization(config, 'my-org', {\n * name: 'New Name',\n * })\n * ```\n */\nexport async function updateOrganization(\n\tconfig: SylphxConfig,\n\torgIdOrSlug: string,\n\tinput: UpdateOrgInput,\n): Promise<{ organization: Organization }> {\n\treturn callApi<{ organization: Organization }>(\n\t\tconfig,\n\t\t`/orgs/${orgIdOrSlug}`,\n\t\t{\n\t\t\tmethod: \"PUT\",\n\t\t\tbody: input,\n\t\t},\n\t);\n}\n\n/**\n * Delete an organization\n *\n * Requires super_admin role.\n *\n * @example\n * ```typescript\n * await deleteOrganization(config, 'my-org')\n * ```\n */\nexport async function deleteOrganization(\n\tconfig: SylphxConfig,\n\torgIdOrSlug: string,\n): Promise<{ success: boolean }> {\n\treturn callApi<{ success: boolean }>(config, `/orgs/${orgIdOrSlug}`, {\n\t\tmethod: \"DELETE\",\n\t});\n}\n\n// ============================================================================\n// Members\n// ============================================================================\n\n/**\n * Get organization members\n *\n * @example\n * ```typescript\n * const { members } = await getOrganizationMembers(config, 'my-org')\n * ```\n */\nexport async function getOrganizationMembers(\n\tconfig: SylphxConfig,\n\torgIdOrSlug: string,\n): Promise<{ members: OrganizationMember[] }> {\n\treturn callApi<{ members: OrganizationMember[] }>(\n\t\tconfig,\n\t\t`/orgs/${orgIdOrSlug}/members`,\n\t);\n}\n\n/**\n * Invite a member to an organization\n *\n * Requires admin or super_admin role.\n *\n * @example\n * ```typescript\n * const { invitation } = await inviteOrganizationMember(config, 'my-org', {\n * email: 'user@example.com',\n * role: 'developer',\n * })\n * ```\n */\nexport async function inviteOrganizationMember(\n\tconfig: SylphxConfig,\n\torgIdOrSlug: string,\n\tinput: InviteMemberInput,\n): Promise<{ invitation: OrganizationInvitation }> {\n\treturn callApi<{ invitation: OrganizationInvitation }>(\n\t\tconfig,\n\t\t`/orgs/${orgIdOrSlug}/members/invite`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: input,\n\t\t},\n\t);\n}\n\n/**\n * Update a member's role\n *\n * Requires admin or super_admin role.\n *\n * @example\n * ```typescript\n * const { member } = await updateOrganizationMemberRole(config, 'my-org', userId, 'admin')\n * ```\n */\nexport async function updateOrganizationMemberRole(\n\tconfig: SylphxConfig,\n\torgIdOrSlug: string,\n\tmemberId: string,\n\trole: OrgRole,\n): Promise<{ member: OrganizationMember }> {\n\treturn callApi<{ member: OrganizationMember }>(\n\t\tconfig,\n\t\t`/orgs/${orgIdOrSlug}/members/${memberId}/role`,\n\t\t{\n\t\t\tmethod: \"PUT\",\n\t\t\tbody: { role },\n\t\t},\n\t);\n}\n\n/**\n * Remove a member from an organization\n *\n * Requires admin or super_admin role.\n *\n * @example\n * ```typescript\n * await removeOrganizationMember(config, 'my-org', userId)\n * ```\n */\nexport async function removeOrganizationMember(\n\tconfig: SylphxConfig,\n\torgIdOrSlug: string,\n\tmemberId: string,\n): Promise<{ success: boolean }> {\n\treturn callApi<{ success: boolean }>(\n\t\tconfig,\n\t\t`/orgs/${orgIdOrSlug}/members/${memberId}`,\n\t\t{\n\t\t\tmethod: \"DELETE\",\n\t\t},\n\t);\n}\n\n/**\n * Leave an organization\n *\n * @example\n * ```typescript\n * await leaveOrganization(config, 'my-org')\n * ```\n */\nexport async function leaveOrganization(\n\tconfig: SylphxConfig,\n\torgIdOrSlug: string,\n): Promise<{ success: boolean }> {\n\treturn callApi<{ success: boolean }>(config, `/orgs/${orgIdOrSlug}/leave`, {\n\t\tmethod: \"POST\",\n\t});\n}\n\n// ============================================================================\n// Invitations\n// ============================================================================\n\n/**\n * Get pending invitations for an organization\n *\n * Requires admin or super_admin role.\n *\n * @example\n * ```typescript\n * const { invitations } = await getOrganizationInvitations(config, 'my-org')\n * ```\n */\nexport async function getOrganizationInvitations(\n\tconfig: SylphxConfig,\n\torgIdOrSlug: string,\n): Promise<{ invitations: OrganizationInvitation[] }> {\n\treturn callApi<{ invitations: OrganizationInvitation[] }>(\n\t\tconfig,\n\t\t`/orgs/${orgIdOrSlug}/invitations`,\n\t);\n}\n\n/**\n * Accept an organization invitation\n *\n * @example\n * ```typescript\n * const { organization } = await acceptOrganizationInvitation(config, invitationToken)\n * ```\n */\nexport async function acceptOrganizationInvitation(\n\tconfig: SylphxConfig,\n\ttoken: string,\n): Promise<{ organization: Organization }> {\n\treturn callApi<{ organization: Organization }>(\n\t\tconfig,\n\t\t\"/orgs/invitations/accept\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: { token },\n\t\t},\n\t);\n}\n\n/**\n * Revoke a pending invitation\n *\n * Requires admin or super_admin role.\n *\n * @example\n * ```typescript\n * await revokeOrganizationInvitation(config, 'my-org', invitationId)\n * ```\n */\nexport async function revokeOrganizationInvitation(\n\tconfig: SylphxConfig,\n\torgIdOrSlug: string,\n\tinvitationId: string,\n): Promise<{ success: boolean }> {\n\treturn callApi<{ success: boolean }>(\n\t\tconfig,\n\t\t`/orgs/${orgIdOrSlug}/invitations/${invitationId}`,\n\t\t{\n\t\t\tmethod: \"DELETE\",\n\t\t},\n\t);\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Check if user has a specific role or higher in the organization\n */\nexport function hasRole(\n\tmembership: OrganizationMembership | null,\n\tminimumRole: OrgRole,\n): boolean {\n\tif (!membership) return false;\n\n\tconst roleHierarchy: OrgRole[] = [\n\t\t\"viewer\",\n\t\t\"analytics\",\n\t\t\"developer\",\n\t\t\"billing\",\n\t\t\"admin\",\n\t\t\"super_admin\",\n\t];\n\n\tconst userRoleIndex = roleHierarchy.indexOf(membership.role);\n\tconst requiredRoleIndex = roleHierarchy.indexOf(minimumRole);\n\n\treturn userRoleIndex >= requiredRoleIndex;\n}\n\n/**\n * Check if user can manage members (invite, remove, change roles)\n */\nexport function canManageMembers(\n\tmembership: OrganizationMembership | null,\n): boolean {\n\treturn hasRole(membership, \"admin\");\n}\n\n/**\n * Check if user can manage organization settings\n */\nexport function canManageSettings(\n\tmembership: OrganizationMembership | null,\n): boolean {\n\treturn hasRole(membership, \"admin\");\n}\n\n/**\n * Check if user can delete the organization\n */\nexport function canDeleteOrganization(\n\tmembership: OrganizationMembership | null,\n): boolean {\n\treturn hasRole(membership, \"super_admin\");\n}\n","/**\n * Secrets SDK\n *\n * Secure secrets management for applications.\n * Secrets are encrypted at rest with AES-256-GCM.\n *\n * @example\n * ```typescript\n * import { createConfig, getSecret, getSecrets, listSecretKeys } from '@sylphx/sdk'\n *\n * const config = createConfig({\n * secretKey: process.env.SYLPHX_SECRET_KEY!,\n * })\n *\n * // Get a single secret\n * const dbUrl = await getSecret(config, { key: 'DATABASE_URL' })\n * console.log(dbUrl.value) // postgres://...\n *\n * // Get multiple secrets at once\n * const secrets = await getSecrets(config, {\n * keys: ['DATABASE_URL', 'API_KEY', 'JWT_SECRET']\n * })\n * console.log(secrets.DATABASE_URL) // postgres://...\n *\n * // List all secret keys (without values)\n * const keys = await listSecretKeys(config)\n * keys.forEach(k => console.log(k.key, k.description))\n * ```\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface GetSecretInput {\n\t/** Secret key (uppercase, underscores allowed) */\n\tkey: string;\n\t/** Optional environment ID override */\n\tenvironmentId?: string;\n}\n\nexport interface GetSecretResult {\n\t/** Secret key */\n\tkey: string;\n\t/** Decrypted secret value */\n\tvalue: string;\n\t/** Version number */\n\tversion: string;\n}\n\nexport interface GetSecretsInput {\n\t/** Array of secret keys to retrieve */\n\tkeys: string[];\n\t/** Optional environment ID override */\n\tenvironmentId?: string;\n}\n\n/** Map of key -> decrypted value */\nexport type GetSecretsResult = Record<string, string>;\n\nexport interface ListSecretKeysInput {\n\t/** Optional environment ID filter */\n\tenvironmentId?: string;\n}\n\nexport interface SecretKeyInfo {\n\t/** Secret key name */\n\tkey: string;\n\t/** Human-readable description */\n\tdescription: string | null;\n\t/** Current version */\n\tversion: string;\n\t/** Whether this is environment-specific */\n\tisEnvironmentSpecific: boolean;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Get a single secret value by key.\n *\n * @param config - SDK configuration\n * @param input - Secret key and optional environment ID\n * @returns Decrypted secret value\n * @throws Error if secret not found or access denied\n *\n * @example\n * ```typescript\n * const secret = await getSecret(config, { key: 'DATABASE_URL' })\n * const dbConnection = createPool(secret.value)\n * ```\n */\nexport async function getSecret(\n\tconfig: SylphxConfig,\n\tinput: GetSecretInput,\n): Promise<GetSecretResult> {\n\treturn callApi<GetSecretResult>(config, \"/secrets/get\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\tkey: input.key,\n\t\t\tenvironmentId: input.environmentId,\n\t\t},\n\t});\n}\n\n/**\n * Get multiple secrets at once.\n *\n * More efficient than multiple getSecret calls.\n *\n * @param config - SDK configuration\n * @param input - Array of secret keys\n * @returns Map of key -> decrypted value\n *\n * @example\n * ```typescript\n * const secrets = await getSecrets(config, {\n * keys: ['DATABASE_URL', 'REDIS_URL', 'JWT_SECRET']\n * })\n *\n * const db = createPool(secrets.DATABASE_URL)\n * const redis = createClient(secrets.REDIS_URL)\n * ```\n */\nexport async function getSecrets(\n\tconfig: SylphxConfig,\n\tinput: GetSecretsInput,\n): Promise<GetSecretsResult> {\n\treturn callApi<GetSecretsResult>(config, \"/secrets/getMany\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\tkeys: input.keys,\n\t\t\tenvironmentId: input.environmentId,\n\t\t},\n\t});\n}\n\n/**\n * List all secret keys (without values).\n *\n * Useful for showing available secrets in UI or debugging.\n *\n * @param config - SDK configuration\n * @param input - Optional environment filter\n * @returns Array of secret key info\n *\n * @example\n * ```typescript\n * const keys = await listSecretKeys(config)\n * console.log('Available secrets:')\n * keys.forEach(k => console.log(` ${k.key}: ${k.description}`))\n * ```\n */\nexport async function listSecretKeys(\n\tconfig: SylphxConfig,\n\tinput: ListSecretKeysInput = {},\n): Promise<SecretKeyInfo[]> {\n\treturn callApi<SecretKeyInfo[]>(config, \"/secrets/listKeys\", {\n\t\tmethod: \"GET\",\n\t\tquery: input.environmentId\n\t\t\t? { environmentId: input.environmentId }\n\t\t\t: undefined,\n\t});\n}\n\n/**\n * Check if a secret exists without retrieving its value.\n *\n * @param config - SDK configuration\n * @param key - Secret key to check\n * @returns true if the secret exists\n *\n * @example\n * ```typescript\n * if (await hasSecret(config, 'STRIPE_SECRET_KEY')) {\n * // Stripe is configured, enable payment features\n * }\n * ```\n */\nexport async function hasSecret(\n\tconfig: SylphxConfig,\n\tkey: string,\n): Promise<boolean> {\n\ttry {\n\t\tconst keys = await listSecretKeys(config);\n\t\treturn keys.some((k) => k.key === key);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Get all secrets for an environment as an object.\n *\n * Useful for loading all secrets into process.env at startup.\n *\n * @param config - SDK configuration\n * @param environmentId - Optional environment ID\n * @returns Object with all secrets\n *\n * @example\n * ```typescript\n * // Load all secrets into process.env at app startup\n * const secrets = await getAllSecrets(config)\n * Object.assign(process.env, secrets)\n * ```\n */\nexport async function getAllSecrets(\n\tconfig: SylphxConfig,\n\tenvironmentId?: string,\n): Promise<GetSecretsResult> {\n\tconst keys = await listSecretKeys(config, { environmentId });\n\tif (keys.length === 0) {\n\t\treturn {};\n\t}\n\treturn getSecrets(config, { keys: keys.map((k) => k.key), environmentId });\n}\n","/**\n * Search SDK\n *\n * State-of-the-art search with full-text, semantic, and hybrid modes.\n * Powered by PostgreSQL tsvector + pgvector.\n *\n * @example\n * ```typescript\n * import { createConfig, indexDocument, search, batchIndex } from '@sylphx/sdk'\n *\n * const config = createConfig({\n * secretKey: process.env.SYLPHX_SECRET_KEY!,\n * })\n *\n * // Index a document\n * await indexDocument(config, {\n * content: 'How to reset your password...',\n * title: 'Password Reset Guide',\n * namespace: 'help-articles',\n * category: 'account',\n * tags: ['password', 'security'],\n * })\n *\n * // Search (hybrid mode by default)\n * const results = await search(config, {\n * query: 'forgot my login credentials',\n * namespace: 'help-articles',\n * searchType: 'hybrid',\n * highlight: true,\n * })\n *\n * results.results.forEach(r => console.log(r.title, r.score, r.highlight))\n * ```\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface IndexDocumentInput {\n\t/** Document title (weighted higher in search) */\n\ttitle?: string;\n\t/** Document content to index */\n\tcontent: string;\n\t/** Namespace for data isolation (e.g., 'products', 'articles') */\n\tnamespace?: string;\n\t/** External document ID (your system's ID) */\n\texternalId?: string;\n\t/** URL or path */\n\turl?: string;\n\t/** Searchable metadata */\n\tmetadata?: Record<string, unknown>;\n\t/** Category facet for filtering */\n\tcategory?: string;\n\t/** Type facet for filtering */\n\ttype?: string;\n\t/** Tags facet for filtering */\n\ttags?: string[];\n\t/** Language for full-text search (default: english) */\n\tlanguage?: string;\n\t/** Skip embedding generation (for keyword-only search) */\n\tskipEmbedding?: boolean;\n\t/** Embedding model to use */\n\tembeddingModel?: string;\n}\n\nexport interface IndexDocumentResult {\n\t/** Generated document ID */\n\tid: string;\n\t/** External ID if provided */\n\texternalId: string | null;\n\t/** Namespace */\n\tnamespace: string;\n}\n\nexport interface BatchIndexInput {\n\t/** Documents to index (max 100) */\n\tdocuments: Array<{\n\t\ttitle?: string;\n\t\tcontent: string;\n\t\texternalId?: string;\n\t\turl?: string;\n\t\tmetadata?: Record<string, unknown>;\n\t\tcategory?: string;\n\t\ttype?: string;\n\t\ttags?: string[];\n\t}>;\n\t/** Namespace for all documents */\n\tnamespace?: string;\n\t/** Language for full-text search */\n\tlanguage?: string;\n\t/** Skip embedding generation */\n\tskipEmbedding?: boolean;\n\t/** Embedding model to use */\n\tembeddingModel?: string;\n}\n\nexport interface BatchIndexResult {\n\t/** Number of documents indexed */\n\tindexed: number;\n\t/** Generated document IDs */\n\tids: string[];\n}\n\nexport type SearchType = \"keyword\" | \"semantic\" | \"hybrid\";\n\nexport interface SearchInput {\n\t/** Search query text */\n\tquery: string;\n\t/** Namespace to search within */\n\tnamespace?: string;\n\t/** Search type: keyword, semantic, or hybrid (default) */\n\tsearchType?: SearchType;\n\t/** Maximum results to return (default: 10, max: 100) */\n\tlimit?: number;\n\t/** Offset for pagination */\n\toffset?: number;\n\t/** Minimum similarity threshold (0-1) for semantic search */\n\tminSimilarity?: number;\n\t/** Enable typo tolerance (default: true) */\n\ttypoTolerance?: boolean;\n\t/** Language for full-text search */\n\tlanguage?: string;\n\t/** Facet filters */\n\tfilters?: {\n\t\tcategory?: string;\n\t\ttype?: string;\n\t\ttags?: string[];\n\t\tmetadata?: Record<string, unknown>;\n\t};\n\t/** Include highlighted snippets (default: true) */\n\thighlight?: boolean;\n\t/** Embedding model for semantic search */\n\tembeddingModel?: string;\n\t/** Track this query for analytics (default: true) */\n\ttrackQuery?: boolean;\n\t/** Session ID for analytics */\n\tsessionId?: string;\n\t/** User ID for analytics */\n\tuserId?: string;\n}\n\nexport interface SearchResultItem {\n\t/** Document ID */\n\tid: string;\n\t/** External ID if set */\n\texternalId: string | null;\n\t/** Document title */\n\ttitle: string | null;\n\t/** Document content */\n\tcontent: string;\n\t/** Document URL */\n\turl: string | null;\n\t/** Document metadata */\n\tmetadata: Record<string, unknown> | null;\n\t/** Category facet */\n\tcategory: string | null;\n\t/** Type facet */\n\ttype: string | null;\n\t/** Tags facet */\n\ttags: string[] | null;\n\t/** Combined score */\n\tscore: number;\n\t/** Keyword search score (if hybrid) */\n\tkeywordScore?: number;\n\t/** Semantic search score (if hybrid) */\n\tsemanticScore?: number;\n\t/** Highlighted snippet (if enabled) */\n\thighlight?: string;\n}\n\nexport interface SearchResponse {\n\t/** Search results */\n\tresults: SearchResultItem[];\n\t/** Total results found */\n\ttotal: number;\n\t/** Original query */\n\tquery: string;\n\t/** Search type used */\n\tsearchType: SearchType;\n\t/** Query processing time in ms */\n\tlatencyMs: number;\n}\n\nexport interface GetFacetsInput {\n\t/** Namespace to get facets from */\n\tnamespace?: string;\n\t/** Facets to retrieve */\n\tfacets?: Array<\"category\" | \"type\" | \"tags\">;\n\t/** Filter facets by category or type */\n\tfilters?: {\n\t\tcategory?: string;\n\t\ttype?: string;\n\t};\n}\n\nexport interface FacetsResponse {\n\tfacets: {\n\t\tcategory?: Array<{ value: string; count: number }>;\n\t\ttype?: Array<{ value: string; count: number }>;\n\t\ttags?: Array<{ value: string; count: number }>;\n\t};\n}\n\nexport interface DeleteDocumentInput {\n\t/** Document ID to delete */\n\tid?: string;\n\t/** Or delete by external ID */\n\texternalId?: string;\n\t/** Namespace (required if using externalId) */\n\tnamespace?: string;\n}\n\nexport interface UpsertDocumentInput extends IndexDocumentInput {\n\t/** External ID is required for upsert */\n\texternalId: string;\n}\n\nexport interface UpsertDocumentResult extends IndexDocumentResult {\n\t/** Whether the document was created (true) or updated (false) */\n\tcreated: boolean;\n}\n\nexport interface SearchStatsResult {\n\t/** Total documents indexed */\n\ttotalDocuments: number;\n\t/** Documents with embeddings */\n\tdocumentsWithEmbedding: number;\n\t/** Documents by namespace */\n\tbyNamespace: Array<{ namespace: string; count: number }>;\n}\n\nexport interface TrackClickInput {\n\t/** Search query ID */\n\tqueryId: string;\n\t/** Clicked document ID */\n\tdocumentId: string;\n\t/** Position in results (1-indexed) */\n\tposition: number;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Index a document for search.\n *\n * Automatically generates tsvector for full-text search and\n * optional embedding for semantic search.\n *\n * @param config - SDK configuration\n * @param input - Document to index\n * @returns Indexed document info\n *\n * @example\n * ```typescript\n * const result = await indexDocument(config, {\n * title: 'Getting Started Guide',\n * content: 'Welcome to our platform...',\n * namespace: 'docs',\n * category: 'tutorials',\n * tags: ['beginner', 'setup'],\n * })\n * ```\n */\nexport async function indexDocument(\n\tconfig: SylphxConfig,\n\tinput: IndexDocumentInput,\n): Promise<IndexDocumentResult> {\n\treturn callApi<IndexDocumentResult>(config, \"/search/index\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\ttitle: input.title,\n\t\t\tcontent: input.content,\n\t\t\tnamespace: input.namespace ?? \"default\",\n\t\t\texternalId: input.externalId,\n\t\t\turl: input.url,\n\t\t\tmetadata: input.metadata,\n\t\t\tcategory: input.category,\n\t\t\ttype: input.type,\n\t\t\ttags: input.tags,\n\t\t\tlanguage: input.language ?? \"english\",\n\t\t\tskipEmbedding: input.skipEmbedding ?? false,\n\t\t\tembeddingModel: input.embeddingModel ?? \"openai/text-embedding-3-small\",\n\t\t},\n\t});\n}\n\n/**\n * Index multiple documents in a single batch.\n *\n * More efficient than multiple indexDocument calls.\n * Max 100 documents per batch.\n *\n * @param config - SDK configuration\n * @param input - Documents to index\n * @returns Batch index result\n *\n * @example\n * ```typescript\n * const result = await batchIndex(config, {\n * namespace: 'products',\n * documents: products.map(p => ({\n * title: p.name,\n * content: p.description,\n * externalId: p.id,\n * category: p.category,\n * }))\n * })\n * console.log(`Indexed ${result.indexed} products`)\n * ```\n */\nexport async function batchIndex(\n\tconfig: SylphxConfig,\n\tinput: BatchIndexInput,\n): Promise<BatchIndexResult> {\n\treturn callApi<BatchIndexResult>(config, \"/search/batchIndex\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\tdocuments: input.documents,\n\t\t\tnamespace: input.namespace ?? \"default\",\n\t\t\tlanguage: input.language ?? \"english\",\n\t\t\tskipEmbedding: input.skipEmbedding ?? false,\n\t\t\tembeddingModel: input.embeddingModel ?? \"openai/text-embedding-3-small\",\n\t\t},\n\t});\n}\n\n/**\n * Search documents.\n *\n * Supports three search modes:\n * - `keyword`: Full-text search with typo tolerance\n * - `semantic`: AI-powered vector search\n * - `hybrid`: Combined ranking (default, best results)\n *\n * @param config - SDK configuration\n * @param input - Search query and options\n * @returns Search results with scores\n *\n * @example\n * ```typescript\n * // Hybrid search (recommended)\n * const results = await search(config, {\n * query: 'how to change email address',\n * namespace: 'help',\n * searchType: 'hybrid',\n * highlight: true,\n * })\n *\n * results.results.forEach(r => {\n * console.log(`[${r.score.toFixed(3)}] ${r.title}`)\n * console.log(r.highlight)\n * })\n * ```\n */\nexport async function search(\n\tconfig: SylphxConfig,\n\tinput: SearchInput,\n): Promise<SearchResponse> {\n\treturn callApi<SearchResponse>(config, \"/search/search\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\tquery: input.query,\n\t\t\tnamespace: input.namespace ?? \"default\",\n\t\t\tsearchType: input.searchType ?? \"hybrid\",\n\t\t\tlimit: input.limit ?? 10,\n\t\t\toffset: input.offset ?? 0,\n\t\t\tminSimilarity: input.minSimilarity,\n\t\t\ttypoTolerance: input.typoTolerance ?? true,\n\t\t\tlanguage: input.language ?? \"english\",\n\t\t\tfilters: input.filters,\n\t\t\thighlight: input.highlight ?? true,\n\t\t\tembeddingModel: input.embeddingModel ?? \"openai/text-embedding-3-small\",\n\t\t\ttrackQuery: input.trackQuery ?? true,\n\t\t\tsessionId: input.sessionId,\n\t\t\tuserId: input.userId,\n\t\t},\n\t});\n}\n\n/**\n * Get facet counts for filtering.\n *\n * Returns counts of documents by category, type, and tags.\n *\n * @param config - SDK configuration\n * @param input - Facet options\n * @returns Facet counts\n *\n * @example\n * ```typescript\n * const facets = await getFacets(config, {\n * namespace: 'products',\n * facets: ['category', 'type'],\n * })\n *\n * facets.facets.category?.forEach(f => {\n * console.log(`${f.value}: ${f.count} products`)\n * })\n * ```\n */\nexport async function getFacets(\n\tconfig: SylphxConfig,\n\tinput: GetFacetsInput = {},\n): Promise<FacetsResponse> {\n\treturn callApi<FacetsResponse>(config, \"/search/getFacets\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\tnamespace: input.namespace ?? \"default\",\n\t\t\tfacets: input.facets ?? [\"category\", \"type\"],\n\t\t\tfilters: input.filters,\n\t\t},\n\t});\n}\n\n/**\n * Delete a document from the search index.\n *\n * @param config - SDK configuration\n * @param input - Document ID or external ID\n * @returns Deletion result\n *\n * @example\n * ```typescript\n * // Delete by internal ID\n * await deleteDocument(config, { id: 'doc-uuid-123' })\n *\n * // Delete by external ID\n * await deleteDocument(config, {\n * externalId: 'product-456',\n * namespace: 'products'\n * })\n * ```\n */\nexport async function deleteDocument(\n\tconfig: SylphxConfig,\n\tinput: DeleteDocumentInput,\n): Promise<{ deleted: number }> {\n\treturn callApi<{ deleted: number }>(config, \"/search/delete\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\tid: input.id,\n\t\t\texternalId: input.externalId,\n\t\t\tnamespace: input.namespace ?? \"default\",\n\t\t},\n\t});\n}\n\n/**\n * Upsert a document (insert or update by externalId).\n *\n * If a document with the same externalId exists, it will be replaced.\n * Otherwise, a new document is created.\n *\n * @param config - SDK configuration\n * @param input - Document to upsert (externalId required)\n * @returns Upsert result\n *\n * @example\n * ```typescript\n * const result = await upsertDocument(config, {\n * externalId: 'product-123',\n * title: 'Updated Product Name',\n * content: 'New description...',\n * namespace: 'products',\n * })\n * console.log(result.created ? 'Created' : 'Updated')\n * ```\n */\nexport async function upsertDocument(\n\tconfig: SylphxConfig,\n\tinput: UpsertDocumentInput,\n): Promise<UpsertDocumentResult> {\n\treturn callApi<UpsertDocumentResult>(config, \"/search/upsert\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\ttitle: input.title,\n\t\t\tcontent: input.content,\n\t\t\tnamespace: input.namespace ?? \"default\",\n\t\t\texternalId: input.externalId,\n\t\t\turl: input.url,\n\t\t\tmetadata: input.metadata,\n\t\t\tcategory: input.category,\n\t\t\ttype: input.type,\n\t\t\ttags: input.tags,\n\t\t\tlanguage: input.language ?? \"english\",\n\t\t\tskipEmbedding: input.skipEmbedding ?? false,\n\t\t\tembeddingModel: input.embeddingModel ?? \"openai/text-embedding-3-small\",\n\t\t},\n\t});\n}\n\n/**\n * Get search index statistics.\n *\n * @param config - SDK configuration\n * @param namespace - Optional namespace filter\n * @returns Index statistics\n *\n * @example\n * ```typescript\n * const stats = await getSearchStats(config)\n * console.log(`Total docs: ${stats.totalDocuments}`)\n * console.log(`With embeddings: ${stats.documentsWithEmbedding}`)\n * ```\n */\nexport async function getSearchStats(\n\tconfig: SylphxConfig,\n\tnamespace?: string,\n): Promise<SearchStatsResult> {\n\treturn callApi<SearchStatsResult>(config, \"/search/getStats\", {\n\t\tmethod: \"POST\",\n\t\tbody: { namespace },\n\t});\n}\n\n/**\n * Track a click on a search result.\n *\n * Use this to improve search quality over time.\n *\n * @param config - SDK configuration\n * @param input - Click information\n * @returns Success status\n *\n * @example\n * ```typescript\n * await trackClick(config, {\n * queryId: searchResponse.queryId,\n * documentId: clickedResult.id,\n * position: 3,\n * })\n * ```\n */\nexport async function trackClick(\n\tconfig: SylphxConfig,\n\tinput: TrackClickInput,\n): Promise<{ success: boolean }> {\n\treturn callApi<{ success: boolean }>(config, \"/search/trackClick\", {\n\t\tmethod: \"POST\",\n\t\tbody: input,\n\t});\n}\n","/**\n * Database Functions\n *\n * Pure functions for retrieving Platform-provisioned database connection strings.\n * Server-side only (requires secret key `sk_*`).\n *\n * The Platform provisions a PostgreSQL database for each app and encrypts\n * the connection string. These functions retrieve and decrypt the connection\n * string at startup, so your app never needs to store it.\n *\n * @example\n * ```ts\n * import { createConfig, getDatabaseConnectionString } from '@sylphx/sdk'\n *\n * const config = createConfig({ secretKey: process.env.SYLPHX_SECRET_KEY! })\n * const { connectionString } = await getDatabaseConnectionString(config)\n *\n * const pool = new Pool({ connectionString })\n * ```\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type DatabaseStatus =\n\t| \"provisioning\"\n\t| \"ready\"\n\t| \"suspended\"\n\t| \"failed\"\n\t| \"deleted\"\n\t| \"not_provisioned\";\n\nexport interface DatabaseConnectionInfo {\n\t/** Decrypted PostgreSQL connection string */\n\tconnectionString: string;\n\t/** Database name */\n\tdatabaseName: string;\n\t/** Database role/user name */\n\troleName: string | null;\n\t/** Database provisioning status */\n\tstatus: Exclude<DatabaseStatus, \"not_provisioned\">;\n}\n\nexport interface DatabaseStatusInfo {\n\t/** Current database status */\n\tstatus: DatabaseStatus;\n\t/** Provisioned region */\n\tregion: string | null;\n\t/** PostgreSQL major version */\n\tpgVersion: number | null;\n\t/** Database name */\n\tdatabaseName: string | null;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Get the provisioned PostgreSQL connection string for this app.\n *\n * Requires secret key authentication (server-side only).\n * The connection string is decrypted on the Platform and returned in plaintext.\n *\n * @throws `NOT_FOUND` if no database has been provisioned for this app\n * @throws `UNPROCESSABLE_ENTITY` if database is not yet ready\n *\n * @example\n * ```ts\n * const { connectionString } = await getDatabaseConnectionString(config)\n * const pool = new Pool({ connectionString })\n * ```\n */\nexport async function getDatabaseConnectionString(\n\tconfig: SylphxConfig,\n): Promise<DatabaseConnectionInfo> {\n\treturn callApi<DatabaseConnectionInfo>(\n\t\tconfig,\n\t\t\"/sdk/database/connection-string\",\n\t\t{ method: \"GET\" },\n\t);\n}\n\n/**\n * Get the current status of the provisioned database.\n *\n * Use this to check if the database is ready before attempting to connect.\n * Requires secret key authentication (server-side only).\n *\n * @example\n * ```ts\n * const { status } = await getDatabaseStatus(config)\n * if (status === 'ready') {\n * // Connect to database\n * }\n * ```\n */\nexport async function getDatabaseStatus(\n\tconfig: SylphxConfig,\n): Promise<DatabaseStatusInfo> {\n\treturn callApi<DatabaseStatusInfo>(config, \"/sdk/database/status\", {\n\t\tmethod: \"GET\",\n\t});\n}\n","/**\n * KV (Key-Value Store) Functions\n *\n * Pure functions for distributed key-value storage backed by Redis.\n * Supports strings, hashes, lists, sorted sets, and built-in rate limiting.\n *\n * Keys are automatically namespaced per app, so no key collisions occur\n * between different apps on the same Platform.\n *\n * @example\n * ```ts\n * import { createConfig, kvSet, kvGet, kvDelete } from '@sylphx/sdk'\n *\n * const config = createConfig({ secretKey: process.env.SYLPHX_SECRET_KEY! })\n *\n * // Basic key-value operations\n * await kvSet(config, { key: 'user:123', value: { name: 'Alice' }, ex: 3600 })\n * const user = await kvGet(config, 'user:123')\n * await kvDelete(config, 'user:123')\n * ```\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { KvRateLimitResult, KvSetOptions, KvZMember } from \"./kv-types\";\n\n// Re-export shared types\nexport type { KvSetOptions, KvRateLimitResult, KvZMember } from \"./kv-types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface KvSetRequest extends KvSetOptions {\n\t/** Key to store */\n\tkey: string;\n\t/** Value to store (any JSON-serializable value) */\n\tvalue: unknown;\n}\n\nexport interface KvMsetRequest {\n\t/** Key-value pairs to set in a single atomic operation */\n\tentries: Array<{ key: string; value: unknown }>;\n}\n\nexport interface KvMgetRequest {\n\t/** Keys to retrieve */\n\tkeys: string[];\n}\n\nexport interface KvHsetRequest {\n\t/** Hash key */\n\tkey: string;\n\t/** Field-value pairs to set on the hash */\n\tfields: Record<string, unknown>;\n}\n\nexport interface KvHgetRequest {\n\t/** Hash key */\n\tkey: string;\n\t/** Field to get */\n\tfield: string;\n}\n\nexport interface KvHgetallRequest {\n\t/** Hash key */\n\tkey: string;\n}\n\nexport interface KvLpushRequest {\n\t/** List key */\n\tkey: string;\n\t/** Values to prepend (left push) */\n\tvalues: unknown[];\n}\n\nexport interface KvLrangeRequest {\n\t/** List key */\n\tkey: string;\n\t/** Start index (0-based, negative counts from end) */\n\tstart: number;\n\t/** Stop index (inclusive, negative counts from end) */\n\tstop: number;\n}\n\nexport interface KvZaddRequest {\n\t/** Sorted set key */\n\tkey: string;\n\t/** Members with scores to add */\n\tmembers: KvZMember[];\n}\n\nexport interface KvZrangeRequest {\n\t/** Sorted set key */\n\tkey: string;\n\t/** Start index or score */\n\tstart: number | string;\n\t/** Stop index or score */\n\tstop: number | string;\n\t/** Return scores alongside members */\n\twithScores?: boolean;\n\t/** Reverse order */\n\trev?: boolean;\n\t/** Treat start/stop as scores (BYSCORE) */\n\tbyScore?: boolean;\n}\n\nexport interface KvIncrRequest {\n\t/** Key to increment */\n\tkey: string;\n\t/** Amount to increment by (default: 1) */\n\tby?: number;\n}\n\nexport interface KvExpireRequest {\n\t/** Key to set expiry on */\n\tkey: string;\n\t/** TTL in seconds */\n\tseconds: number;\n}\n\nexport interface KvRateLimitRequest {\n\t/** Rate limit identifier (e.g., userId, IP) */\n\tidentifier: string;\n\t/** Maximum requests allowed in the window */\n\tlimit: number;\n\t/** Window duration in seconds */\n\twindow: number;\n}\n\n// ============================================================================\n// Functions — Basic Operations\n// ============================================================================\n\n/**\n * Set a key-value pair, with optional TTL and conditional flags.\n *\n * @example\n * ```ts\n * // Simple set\n * await kvSet(config, { key: 'session:abc', value: { userId: '123' }, ex: 86400 })\n *\n * // Set only if not exists (NX)\n * await kvSet(config, { key: 'lock:task', value: '1', ex: 30, nx: true })\n * ```\n */\nexport async function kvSet(\n\tconfig: SylphxConfig,\n\trequest: KvSetRequest,\n): Promise<{ ok: boolean }> {\n\treturn callApi<{ ok: boolean }>(config, \"/sdk/kv/set\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n\n/**\n * Get a value by key.\n *\n * Returns `null` if the key does not exist or has expired.\n *\n * @example\n * ```ts\n * const session = await kvGet<{ userId: string }>(config, 'session:abc')\n * if (session) {\n * console.log(session.userId)\n * }\n * ```\n */\nexport async function kvGet<T = unknown>(\n\tconfig: SylphxConfig,\n\tkey: string,\n): Promise<T | null> {\n\tconst result = await callApi<{ value: T | null }>(\n\t\tconfig,\n\t\t`/sdk/kv/get/${encodeURIComponent(key)}`,\n\t\t{ method: \"GET\" },\n\t);\n\treturn result.value;\n}\n\n/**\n * Delete one or more keys.\n *\n * @example\n * ```ts\n * const { deleted } = await kvDelete(config, 'session:abc')\n * console.log(`Deleted ${deleted} keys`)\n * ```\n */\nexport async function kvDelete(\n\tconfig: SylphxConfig,\n\tkey: string,\n): Promise<{ deleted: number }> {\n\treturn callApi<{ deleted: number }>(\n\t\tconfig,\n\t\t`/sdk/kv/delete/${encodeURIComponent(key)}`,\n\t\t{ method: \"DELETE\" },\n\t);\n}\n\n/**\n * Check if a key exists.\n *\n * @example\n * ```ts\n * const { exists } = await kvExists(config, 'session:abc')\n * ```\n */\nexport async function kvExists(\n\tconfig: SylphxConfig,\n\tkey: string,\n): Promise<{ exists: boolean }> {\n\treturn callApi<{ exists: boolean }>(\n\t\tconfig,\n\t\t`/sdk/kv/exists/${encodeURIComponent(key)}`,\n\t\t{ method: \"GET\" },\n\t);\n}\n\n/**\n * Set expiry on an existing key.\n *\n * @example\n * ```ts\n * await kvExpire(config, { key: 'session:abc', seconds: 3600 })\n * ```\n */\nexport async function kvExpire(\n\tconfig: SylphxConfig,\n\trequest: KvExpireRequest,\n): Promise<{ ok: boolean }> {\n\treturn callApi<{ ok: boolean }>(config, \"/sdk/kv/expire\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n\n/**\n * Increment a numeric value.\n *\n * @example\n * ```ts\n * const { value } = await kvIncr(config, { key: 'page:views', by: 1 })\n * ```\n */\nexport async function kvIncr(\n\tconfig: SylphxConfig,\n\trequest: KvIncrRequest,\n): Promise<{ value: number }> {\n\treturn callApi<{ value: number }>(config, \"/sdk/kv/incr\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n\n// ============================================================================\n// Functions — Bulk Operations\n// ============================================================================\n\n/**\n * Set multiple key-value pairs atomically.\n */\nexport async function kvMset(\n\tconfig: SylphxConfig,\n\trequest: KvMsetRequest,\n): Promise<{ ok: boolean }> {\n\treturn callApi<{ ok: boolean }>(config, \"/sdk/kv/mset\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n\n/**\n * Get multiple values by keys in a single request.\n *\n * Returns `null` for keys that don't exist.\n */\nexport async function kvMget<T = unknown>(\n\tconfig: SylphxConfig,\n\trequest: KvMgetRequest,\n): Promise<Array<T | null>> {\n\tconst result = await callApi<{ values: Array<T | null> }>(\n\t\tconfig,\n\t\t\"/sdk/kv/mget\",\n\t\t{ method: \"POST\", body: request },\n\t);\n\treturn result.values;\n}\n\n// ============================================================================\n// Functions — Hash Operations\n// ============================================================================\n\n/**\n * Set fields on a hash key.\n *\n * @example\n * ```ts\n * await kvHset(config, { key: 'user:123', fields: { name: 'Alice', age: 30 } })\n * ```\n */\nexport async function kvHset(\n\tconfig: SylphxConfig,\n\trequest: KvHsetRequest,\n): Promise<{ count: number }> {\n\treturn callApi<{ count: number }>(config, \"/sdk/kv/hset\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n\n/**\n * Get a single field from a hash key.\n */\nexport async function kvHget<T = unknown>(\n\tconfig: SylphxConfig,\n\trequest: KvHgetRequest,\n): Promise<T | null> {\n\tconst result = await callApi<{ value: T | null }>(config, \"/sdk/kv/hget\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n\treturn result.value;\n}\n\n/**\n * Get all fields from a hash key.\n */\nexport async function kvHgetall<\n\tT extends Record<string, unknown> = Record<string, unknown>,\n>(config: SylphxConfig, request: KvHgetallRequest): Promise<T | null> {\n\tconst result = await callApi<{ value: T | null }>(config, \"/sdk/kv/hgetall\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n\treturn result.value;\n}\n\n// ============================================================================\n// Functions — List Operations\n// ============================================================================\n\n/**\n * Left-push values onto a list.\n *\n * @example\n * ```ts\n * const { length } = await kvLpush(config, { key: 'events', values: [event] })\n * ```\n */\nexport async function kvLpush(\n\tconfig: SylphxConfig,\n\trequest: KvLpushRequest,\n): Promise<{ length: number }> {\n\treturn callApi<{ length: number }>(config, \"/sdk/kv/lpush\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n\n/**\n * Get a range of elements from a list.\n *\n * @example\n * ```ts\n * // Get last 10 events\n * const items = await kvLrange(config, { key: 'events', start: 0, stop: 9 })\n * ```\n */\nexport async function kvLrange<T = unknown>(\n\tconfig: SylphxConfig,\n\trequest: KvLrangeRequest,\n): Promise<T[]> {\n\tconst result = await callApi<{ items: T[] }>(config, \"/sdk/kv/lrange\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n\treturn result.items;\n}\n\n// ============================================================================\n// Functions — Sorted Set Operations\n// ============================================================================\n\n/**\n * Add members to a sorted set.\n *\n * @example\n * ```ts\n * // Add to leaderboard\n * await kvZadd(config, {\n * key: 'leaderboard',\n * members: [{ member: 'user:123', score: 1500 }],\n * })\n * ```\n */\nexport async function kvZadd(\n\tconfig: SylphxConfig,\n\trequest: KvZaddRequest,\n): Promise<{ added: number }> {\n\treturn callApi<{ added: number }>(config, \"/sdk/kv/zadd\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n\n/**\n * Get a range of members from a sorted set.\n *\n * @example\n * ```ts\n * // Get top 10 leaderboard entries\n * const entries = await kvZrange(config, {\n * key: 'leaderboard',\n * start: 0,\n * stop: 9,\n * rev: true,\n * withScores: true,\n * })\n * ```\n */\nexport async function kvZrange(\n\tconfig: SylphxConfig,\n\trequest: KvZrangeRequest,\n): Promise<Array<{ member: string; score?: number }>> {\n\tconst result = await callApi<{\n\t\tmembers: Array<{ member: string; score?: number }>;\n\t}>(config, \"/sdk/kv/zrange\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n\treturn result.members;\n}\n\n// ============================================================================\n// Functions — Rate Limiting\n// ============================================================================\n\n/**\n * Check and consume a rate limit token using Redis sliding window.\n *\n * This is a built-in rate limiter — no external service needed.\n *\n * @example\n * ```ts\n * // 10 requests per 60 seconds per user\n * const result = await kvRateLimit(config, {\n * identifier: `user:${userId}`,\n * limit: 10,\n * window: 60,\n * })\n *\n * if (!result.success) {\n * return Response.json({ error: 'Rate limit exceeded' }, { status: 429 })\n * }\n * ```\n */\nexport async function kvRateLimit(\n\tconfig: SylphxConfig,\n\trequest: KvRateLimitRequest,\n): Promise<KvRateLimitResult> {\n\treturn callApi<KvRateLimitResult>(config, \"/sdk/kv/ratelimit\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n","/**\n * Realtime Functions\n *\n * Pure functions for real-time messaging via Redis Streams.\n * Supports channel-based pub/sub with SSE delivery to browsers.\n *\n * @example\n * ```ts\n * import { createConfig, realtimeEmit } from '@sylphx/sdk'\n *\n * // Server: emit events to connected clients\n * const config = createConfig({ secretKey: process.env.SYLPHX_SECRET_KEY! })\n * await realtimeEmit(config, {\n * channel: 'orders',\n * event: 'order.created',\n * data: { orderId: '123', amount: 99 },\n * })\n * ```\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { StreamMessage } from \"./realtime-types\";\n\n// Re-export shared types\nexport type { StreamMessage } from \"./realtime-types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface RealtimeEmitRequest {\n\t/** Channel to emit the event to */\n\tchannel: string;\n\t/** Event type/name */\n\tevent: string;\n\t/** Event data (any JSON-serializable value) */\n\tdata: unknown;\n}\n\nexport interface RealtimeEmitResponse {\n\t/** Stream entry ID */\n\tid: string;\n\t/** Channel the event was emitted to */\n\tchannel: string;\n}\n\nexport interface RealtimeHistoryRequest {\n\t/** Channel to get history for */\n\tchannel: string;\n\t/** Maximum number of messages to return (default: 50) */\n\tlimit?: number;\n\t/** Return messages after this stream entry ID */\n\tafter?: string;\n}\n\nexport interface RealtimeHistoryResponse {\n\t/** List of historical messages */\n\tmessages: StreamMessage[];\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Emit an event to a realtime channel.\n *\n * All clients subscribed to the channel (via `useRealtime` hook or SSE)\n * will receive the event instantly.\n *\n * @example\n * ```ts\n * // Notify all clients watching a document\n * await realtimeEmit(config, {\n * channel: `doc:${documentId}`,\n * event: 'doc.updated',\n * data: { updatedBy: userId, timestamp: Date.now() },\n * })\n * ```\n */\nexport async function realtimeEmit(\n\tconfig: SylphxConfig,\n\trequest: RealtimeEmitRequest,\n): Promise<RealtimeEmitResponse> {\n\treturn callApi<RealtimeEmitResponse>(config, \"/sdk/realtime/emit\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n\n/**\n * Get historical messages from a channel.\n *\n * Useful for initializing state when a client first connects,\n * or for resuming from a known stream position.\n *\n * @example\n * ```ts\n * // Get last 20 messages when a user joins a chat\n * const { messages } = await getRealtimeHistory(config, {\n * channel: 'chat:general',\n * limit: 20,\n * })\n * ```\n */\nexport async function getRealtimeHistory(\n\tconfig: SylphxConfig,\n\trequest: RealtimeHistoryRequest,\n): Promise<RealtimeHistoryResponse> {\n\tconst params = new URLSearchParams();\n\tparams.set(\"channel\", request.channel);\n\tif (request.limit !== undefined) params.set(\"limit\", String(request.limit));\n\tif (request.after !== undefined) params.set(\"after\", request.after);\n\n\treturn callApi<RealtimeHistoryResponse>(\n\t\tconfig,\n\t\t`/sdk/realtime/history?${params.toString()}`,\n\t\t{ method: \"GET\" },\n\t);\n}\n","/**\n * Deploy Functions\n *\n * Pure functions for managing app deployments, environment variables,\n * and custom domains via the Platform Deploy API.\n *\n * Requires secret key authentication (`sk_*`).\n *\n * @example\n * ```ts\n * import { createConfig, triggerDeploy, getDeployStatus } from '@sylphx/sdk'\n *\n * const config = createConfig({ secretKey: process.env.SYLPHX_SECRET_KEY! })\n *\n * // Trigger a deployment\n * const deploy = await triggerDeploy(config, { envId: 'env_prod_xxx' })\n *\n * // Poll for completion\n * const status = await getDeployStatus(config, deploy.envId)\n * console.log(status.status) // 'building' | 'deploying' | 'success' | 'failed'\n * ```\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type DeployStatus =\n\t| \"queued\"\n\t| \"building\"\n\t| \"deploying\"\n\t| \"success\"\n\t| \"failed\"\n\t| \"cancelled\";\n\nexport interface DeployInfo {\n\t/** Deployment ID */\n\tdeploymentId: string;\n\t/** Environment ID */\n\tenvId: string;\n\t/** Current deployment status */\n\tstatus: DeployStatus;\n\t/** Deployment URL */\n\turl?: string;\n\t/** Git commit SHA */\n\tcommitSha?: string;\n\t/** Git branch */\n\tbranch?: string;\n\t/** Deployment started at (ISO timestamp) */\n\tstartedAt?: string;\n\t/** Deployment completed at (ISO timestamp) */\n\tcompletedAt?: string;\n\t/** Error message if failed */\n\terror?: string;\n}\n\nexport interface TriggerDeployRequest {\n\t/** Environment ID to deploy */\n\tenvId: string;\n\t/** Force rebuild without cache */\n\tforceRebuild?: boolean;\n}\n\nexport interface RollbackDeployRequest {\n\t/** Environment ID to rollback */\n\tenvId: string;\n\t/** Deployment ID to rollback to */\n\tdeploymentId: string;\n}\n\nexport interface EnvVar {\n\t/** Environment variable key */\n\tkey: string;\n\t/** Environment variable value */\n\tvalue: string;\n\t/** Whether value is sensitive (masked in logs) */\n\tsensitive?: boolean;\n}\n\nexport interface SetEnvVarRequest {\n\t/** Environment variable key */\n\tkey: string;\n\t/** Environment variable value */\n\tvalue: string;\n\t/** Whether value is sensitive/secret */\n\tsensitive?: boolean;\n}\n\nexport interface CustomDomain {\n\t/** Domain name */\n\tdomain: string;\n\t/** Verification status */\n\tstatus: \"pending\" | \"verifying\" | \"active\" | \"failed\";\n\t/** DNS records required for verification */\n\tdnsRecords?: Array<{\n\t\ttype: string;\n\t\tname: string;\n\t\tvalue: string;\n\t}>;\n}\n\nexport interface AddDomainRequest {\n\t/** Domain name to add */\n\tdomain: string;\n}\n\nexport interface DeployHistoryResponse {\n\t/** List of past deployments */\n\tdeployments: DeployInfo[];\n}\n\nexport interface BuildLog {\n\t/** Log line text */\n\ttext: string;\n\t/** Log timestamp (ISO) */\n\ttimestamp?: string;\n\t/** Log level */\n\tlevel?: \"info\" | \"warn\" | \"error\";\n}\n\nexport interface BuildLogHistoryResponse {\n\t/** Log lines */\n\tlogs: BuildLog[];\n}\n\n// ============================================================================\n// Functions — Deployments\n// ============================================================================\n\n/**\n * Trigger a new deployment for an environment.\n *\n * @example\n * ```ts\n * const deploy = await triggerDeploy(config, { envId: 'env_prod_xxx' })\n * console.log(`Deployment ${deploy.deploymentId} started`)\n * ```\n */\nexport async function triggerDeploy(\n\tconfig: SylphxConfig,\n\trequest: TriggerDeployRequest,\n): Promise<DeployInfo> {\n\treturn callApi<DeployInfo>(\n\t\tconfig,\n\t\t`/sdk/deploy/trigger/${encodeURIComponent(request.envId)}`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody:\n\t\t\t\trequest.forceRebuild !== undefined\n\t\t\t\t\t? { forceRebuild: request.forceRebuild }\n\t\t\t\t\t: undefined,\n\t\t},\n\t);\n}\n\n/**\n * Get the current deployment status for an environment.\n *\n * @example\n * ```ts\n * const { status } = await getDeployStatus(config, 'env_prod_xxx')\n * if (status === 'success') {\n * console.log('Deployment succeeded!')\n * }\n * ```\n */\nexport async function getDeployStatus(\n\tconfig: SylphxConfig,\n\tenvId: string,\n): Promise<DeployInfo> {\n\treturn callApi<DeployInfo>(\n\t\tconfig,\n\t\t`/sdk/deploy/status/${encodeURIComponent(envId)}`,\n\t\t{ method: \"GET\" },\n\t);\n}\n\n/**\n * Get deployment history for an environment.\n *\n * @example\n * ```ts\n * const { deployments } = await getDeployHistory(config, 'env_prod_xxx')\n * const lastDeploy = deployments[0]\n * ```\n */\nexport async function getDeployHistory(\n\tconfig: SylphxConfig,\n\tenvId: string,\n): Promise<DeployHistoryResponse> {\n\treturn callApi<DeployHistoryResponse>(\n\t\tconfig,\n\t\t`/sdk/deploy/history/${encodeURIComponent(envId)}`,\n\t\t{ method: \"GET\" },\n\t);\n}\n\n/**\n * Rollback an environment to a previous deployment.\n *\n * @example\n * ```ts\n * await rollbackDeploy(config, {\n * envId: 'env_prod_xxx',\n * deploymentId: 'dep_abc123',\n * })\n * ```\n */\nexport async function rollbackDeploy(\n\tconfig: SylphxConfig,\n\trequest: RollbackDeployRequest,\n): Promise<DeployInfo> {\n\treturn callApi<DeployInfo>(\n\t\tconfig,\n\t\t`/sdk/deploy/rollback/${encodeURIComponent(request.envId)}`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: { deploymentId: request.deploymentId },\n\t\t},\n\t);\n}\n\n/**\n * Get stored build log history for an environment.\n *\n * For live log streaming during an active build, use the SSE endpoint\n * directly or the `useDeployLogs` React hook.\n *\n * @example\n * ```ts\n * const { logs } = await getBuildLogHistory(config, 'env_prod_xxx')\n * for (const log of logs) {\n * console.log(log.text)\n * }\n * ```\n */\nexport async function getBuildLogHistory(\n\tconfig: SylphxConfig,\n\tenvId: string,\n): Promise<BuildLogHistoryResponse> {\n\treturn callApi<BuildLogHistoryResponse>(\n\t\tconfig,\n\t\t`/sdk/deploy/logs/${encodeURIComponent(envId)}/history`,\n\t\t{ method: \"GET\" },\n\t);\n}\n\n// ============================================================================\n// Functions — Environment Variables\n// ============================================================================\n\n/**\n * List environment variables for a deployment environment.\n *\n * Sensitive values are masked in the response.\n *\n * @example\n * ```ts\n * const envVars = await listEnvVars(config, 'env_prod_xxx')\n * ```\n */\nexport async function listEnvVars(\n\tconfig: SylphxConfig,\n\tenvId: string,\n): Promise<EnvVar[]> {\n\tconst result = await callApi<{ envVars: EnvVar[] }>(\n\t\tconfig,\n\t\t`/sdk/deploy/envvars/${encodeURIComponent(envId)}`,\n\t\t{ method: \"GET\" },\n\t);\n\treturn result.envVars;\n}\n\n/**\n * Set (create or update) an environment variable.\n *\n * @example\n * ```ts\n * await setEnvVar(config, 'env_prod_xxx', {\n * key: 'DATABASE_URL',\n * value: 'postgresql://...',\n * sensitive: true,\n * })\n * ```\n */\nexport async function setEnvVar(\n\tconfig: SylphxConfig,\n\tenvId: string,\n\trequest: SetEnvVarRequest,\n): Promise<EnvVar> {\n\treturn callApi<EnvVar>(\n\t\tconfig,\n\t\t`/sdk/deploy/envvars/${encodeURIComponent(envId)}`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: request,\n\t\t},\n\t);\n}\n\n/**\n * Delete an environment variable.\n *\n * @example\n * ```ts\n * await deleteEnvVar(config, 'env_prod_xxx', 'DATABASE_URL')\n * ```\n */\nexport async function deleteEnvVar(\n\tconfig: SylphxConfig,\n\tenvId: string,\n\tkey: string,\n): Promise<{ deleted: boolean }> {\n\treturn callApi<{ deleted: boolean }>(\n\t\tconfig,\n\t\t`/sdk/deploy/envvars/${encodeURIComponent(envId)}/${encodeURIComponent(key)}`,\n\t\t{ method: \"DELETE\" },\n\t);\n}\n\n// ============================================================================\n// Functions — Custom Domains\n// ============================================================================\n\n/**\n * List custom domains for a deployment environment.\n *\n * @example\n * ```ts\n * const domains = await listCustomDomains(config, 'env_prod_xxx')\n * ```\n */\nexport async function listCustomDomains(\n\tconfig: SylphxConfig,\n\tenvId: string,\n): Promise<CustomDomain[]> {\n\tconst result = await callApi<{ domains: CustomDomain[] }>(\n\t\tconfig,\n\t\t`/sdk/deploy/domains/${encodeURIComponent(envId)}`,\n\t\t{ method: \"GET\" },\n\t);\n\treturn result.domains;\n}\n\n/**\n * Add a custom domain to a deployment environment.\n *\n * After adding, you'll need to update your DNS records to complete verification.\n *\n * @example\n * ```ts\n * const domain = await addCustomDomain(config, 'env_prod_xxx', {\n * domain: 'myapp.com',\n * })\n * // domain.dnsRecords contains the DNS records to add\n * ```\n */\nexport async function addCustomDomain(\n\tconfig: SylphxConfig,\n\tenvId: string,\n\trequest: AddDomainRequest,\n): Promise<CustomDomain> {\n\treturn callApi<CustomDomain>(\n\t\tconfig,\n\t\t`/sdk/deploy/domains/${encodeURIComponent(envId)}`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: request,\n\t\t},\n\t);\n}\n\n/**\n * Remove a custom domain from a deployment environment.\n *\n * @example\n * ```ts\n * await removeCustomDomain(config, 'env_prod_xxx', 'myapp.com')\n * ```\n */\nexport async function removeCustomDomain(\n\tconfig: SylphxConfig,\n\tenvId: string,\n\tdomain: string,\n): Promise<{ removed: boolean }> {\n\treturn callApi<{ removed: boolean }>(\n\t\tconfig,\n\t\t`/sdk/deploy/domains/${encodeURIComponent(envId)}/${encodeURIComponent(domain)}`,\n\t\t{ method: \"DELETE\" },\n\t);\n}\n","/**\n * Monitoring Functions\n *\n * Pure functions for error tracking and log capture.\n * Works client-side and server-side.\n *\n * ## Industry Patterns\n * - **Error grouping via fingerprinting** — Same error only billed once (Sentry pattern)\n * - **Adaptive sampling** — Automatic sample rate adjustment based on quota usage\n * - **Breadcrumb trails** — Contextual trail leading to errors\n *\n * @example\n * ```ts\n * import { createConfig, captureException, captureMessage } from '@sylphx/sdk'\n *\n * const config = createConfig({ appId: process.env.NEXT_PUBLIC_SYLPHX_APP_ID! })\n *\n * // Capture errors\n * try {\n * await riskyOperation()\n * } catch (err) {\n * await captureException(config, err as Error)\n * }\n *\n * // Capture log messages\n * await captureMessage(config, 'User completed onboarding', { level: 'info' })\n * ```\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type MonitoringSeverity = \"fatal\" | \"error\" | \"warning\" | \"info\";\n\nexport interface ExceptionFrame {\n\t/** Source filename */\n\tfilename?: string;\n\t/** Function name */\n\tfunction?: string;\n\t/** Line number */\n\tlineno?: number;\n\t/** Column number */\n\tcolno?: number;\n}\n\nexport interface ExceptionValue {\n\t/** Exception class/type (e.g., \"TypeError\") */\n\ttype: string;\n\t/** Exception message */\n\tvalue: string;\n\t/** Stack trace frames (innermost first) */\n\tstacktrace?: { frames?: ExceptionFrame[] };\n}\n\nexport interface Breadcrumb {\n\t/** Breadcrumb type (e.g., \"navigation\", \"http\", \"ui.click\") */\n\ttype?: string;\n\t/** Log level */\n\tlevel?: MonitoringSeverity;\n\t/** Breadcrumb message */\n\tmessage?: string;\n\t/** Breadcrumb data */\n\tdata?: Record<string, unknown>;\n\t/** Unix timestamp (seconds) */\n\ttimestamp?: number;\n}\n\nexport interface CaptureExceptionRequest {\n\t/** Exception value(s) — first is primary, rest are chained causes */\n\texception: { values: ExceptionValue[] };\n\t/** Severity level (default: \"error\") */\n\tlevel?: MonitoringSeverity;\n\t/** Current page route */\n\troute?: string;\n\t/** User agent string */\n\tuserAgent?: string;\n\t/** App release version */\n\trelease?: string;\n\t/** Environment name (e.g., \"production\", \"staging\") */\n\tenvironment?: string;\n\t/** Custom tags for filtering */\n\ttags?: Record<string, string>;\n\t/** Extra context data */\n\textra?: Record<string, unknown>;\n\t/** Breadcrumb trail leading to the error */\n\tbreadcrumbs?: Breadcrumb[];\n\t/** Custom fingerprint for grouping (overrides automatic grouping) */\n\tfingerprint?: string[];\n}\n\nexport interface CaptureMessageRequest {\n\t/** Log message */\n\tmessage: string;\n\t/** Severity level (default: \"info\") */\n\tlevel?: MonitoringSeverity;\n\t/** Current page route */\n\troute?: string;\n\t/** App release version */\n\trelease?: string;\n\t/** Custom tags for filtering */\n\ttags?: Record<string, string>;\n\t/** Extra context data */\n\textra?: Record<string, unknown>;\n}\n\nexport interface MonitoringResponse {\n\t/** Internal event ID */\n\teventId: string;\n\t/** Whether this is a new unique error (true = billed, false = duplicate = free) */\n\tisNewError: boolean;\n\t/** Current quota usage percentage (0-100+). Present when >= 50%. */\n\tquotaUsage?: number;\n\t/**\n\t * Recommended client-side sample rate (0.0-1.0).\n\t * Reduce your error capture rate to this value when quota is high.\n\t * Present when quotaUsage >= 50%.\n\t */\n\trecommendedSampleRate?: number;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Convert a native Error object to ExceptionValue format.\n */\nfunction errorToExceptionValue(error: Error): ExceptionValue {\n\tconst frames: ExceptionFrame[] = [];\n\n\tif (error.stack) {\n\t\t// Parse V8/SpiderMonkey style stack traces\n\t\tconst lines = error.stack.split(\"\\n\").slice(1);\n\t\tfor (const line of lines) {\n\t\t\t// V8: \" at functionName (file:line:col)\"\n\t\t\tconst v8Match = line.match(/^\\s+at\\s+(.+?)\\s+\\((.+):(\\d+):(\\d+)\\)$/);\n\t\t\tif (v8Match) {\n\t\t\t\tframes.push({\n\t\t\t\t\tfunction: v8Match[1],\n\t\t\t\t\tfilename: v8Match[2],\n\t\t\t\t\tlineno: Number(v8Match[3]),\n\t\t\t\t\tcolno: Number(v8Match[4]),\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// V8 (no function): \" at file:line:col\"\n\t\t\tconst v8AnonMatch = line.match(/^\\s+at\\s+(.+):(\\d+):(\\d+)$/);\n\t\t\tif (v8AnonMatch) {\n\t\t\t\tframes.push({\n\t\t\t\t\tfilename: v8AnonMatch[1],\n\t\t\t\t\tlineno: Number(v8AnonMatch[2]),\n\t\t\t\t\tcolno: Number(v8AnonMatch[3]),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\ttype: error.name || \"Error\",\n\t\tvalue: error.message,\n\t\tstacktrace: frames.length > 0 ? { frames } : undefined,\n\t};\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Capture an exception for error tracking.\n *\n * Errors with the same fingerprint are grouped automatically.\n * Duplicate occurrences of the same error are FREE (only new unique\n * errors count against your quota).\n *\n * @example\n * ```ts\n * try {\n * await processPayment(amount)\n * } catch (err) {\n * const result = await captureException(config, err as Error, {\n * tags: { paymentProvider: 'stripe' },\n * extra: { amount, userId },\n * })\n * }\n * ```\n */\nexport async function captureException(\n\tconfig: SylphxConfig,\n\terror: Error,\n\toptions: Omit<CaptureExceptionRequest, \"exception\"> = {},\n): Promise<MonitoringResponse> {\n\tconst exceptionValue = errorToExceptionValue(error);\n\tconst request: CaptureExceptionRequest = {\n\t\t...options,\n\t\texception: { values: [exceptionValue] },\n\t};\n\treturn callApi<MonitoringResponse>(config, \"/sdk/monitoring/exception\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n\n/**\n * Capture an exception with full control over the exception payload.\n *\n * Use this for structured exception capture with custom types, chained\n * causes, or when not working with native Error objects.\n */\nexport async function captureExceptionRaw(\n\tconfig: SylphxConfig,\n\trequest: CaptureExceptionRequest,\n): Promise<MonitoringResponse> {\n\treturn callApi<MonitoringResponse>(config, \"/sdk/monitoring/exception\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n\n/**\n * Capture a log message for monitoring.\n *\n * Like `captureException` but for non-error events (warnings, info, etc.).\n * Messages with the same content are grouped automatically.\n *\n * @example\n * ```ts\n * // Info log\n * await captureMessage(config, 'Payment webhook received', {\n * level: 'info',\n * tags: { provider: 'stripe', event: 'payment.succeeded' },\n * })\n *\n * // Warning\n * await captureMessage(config, 'Slow query detected', {\n * level: 'warning',\n * extra: { queryMs: 2400, query: sql },\n * })\n * ```\n */\nexport async function captureMessage(\n\tconfig: SylphxConfig,\n\tmessage: string,\n\toptions: Omit<CaptureMessageRequest, \"message\"> = {},\n): Promise<MonitoringResponse> {\n\tconst request: CaptureMessageRequest = { ...options, message };\n\treturn callApi<MonitoringResponse>(config, \"/sdk/monitoring/message\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n"],"mappings":";AAeO,IAAM,uBAAuB;AA6D7B,IAAM,eAAe;AAQrB,IAAM,mBAAmB;AAMzB,IAAM,uBAAuB;AAgB7B,IAAM,cAAc;AAOpB,IAAM,eACZ,OAAO,WAAW,cACf,YACA,OAAO,YAAY,eAAe,QAAQ,UAAU,OACnD,SACA;AAOE,IAAM,qBAAqB;AAgB3B,IAAM,iCAAiC,IAAI;AAG3C,IAAM,4BAA4B,iCAAiC;AAOnE,IAAM,iCAAiC,KAAK,KAAK,KAAK;AAYtD,IAAM,qBAAqB,IAAI,KAAK;AAOpC,IAAM,kCAAkC,KAAK;AAO7C,IAAM,qBAAqB;AAG3B,IAAM,sBAAsB;AAc5B,IAAM,+BAA+B,KAAK,KAAK;AAW/C,IAAM,qBAAqB,IAAI,KAAK;AAOpC,IAAM,wBAAwB,KAAK;AAWnC,IAAM,mBAAmB,KAAK,KAAK;AASnC,IAAM,sBAAsB,IAAI,KAAK,KAAK,KAAK;AAS/C,IAAM,iCAAiC,KAAK,KAAK;AA4FjD,IAAM,kCAAkC,KAAK,KAAK;AAwGlD,IAAM,qBAAqB,KAAK,KAAK,KAAK,KAAK;AAW/C,IAAM,yBAAyB,KAAK;AAOpC,IAAM,yBAAyB,IAAI,KAAK;AAOxC,IAAM,uBAAuB,IAAI,KAAK;AAKtC,IAAM,sBAAsB,KAAK;AAuDjC,IAAM,wBAAwB,KAAK;AAyBnC,IAAM,oCAAoC,IAAI,OAAO;AAKrD,IAAM,iCAAiC,IAAI,OAAO;AAKlD,IAAM,gCAAgC,IAAI,OAAO;AAKjD,IAAM,+BAA+B,KAAK,OAAO;AASjD,IAAM,mBAAmB,KAAK,KAAK;AAwJnC,IAAM,oCAAoC;AAO1C,IAAM,4BAA4B;AAOlC,IAAM,mCAAmC;AAWzC,IAAM,yBAAyB;AAO/B,IAAM,oBAAoB,IAAI,KAAK;;;ACtrBnC,IAAM,oBAAqD;AAAA,EACjE,aAAa;AAAA,EACb,cAAc;AAAA,EACd,WAAW;AAAA,EACX,WAAW;AAAA,EACX,UAAU;AAAA,EACV,mBAAmB;AAAA,EACnB,sBAAsB;AAAA,EACtB,mBAAmB;AAAA,EACnB,uBAAuB;AAAA,EACvB,iBAAiB;AAAA,EACjB,aAAa;AAAA,EACb,qBAAqB;AAAA,EACrB,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,SAAS;AAAA,EACT,SAAS;AAAA,EACT,aAAa;AAAA,EACb,SAAS;AACV;AAKO,IAAM,kBAAwC,oBAAI,IAAI;AAAA,EAC5D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EACA;AAAA;AACD,CAAC;AA8BM,IAAM,cAAN,MAAM,qBAAoB,MAAM;AAAA;AAAA,EAE7B;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA,EAET,YAAY,SAAiB,UAA8B,CAAC,GAAG;AAC9D,UAAM,SAAS,EAAE,OAAO,QAAQ,MAAM,CAAC;AACvC,SAAK,OAAO;AACZ,SAAK,OAAO,QAAQ,QAAQ;AAC5B,SAAK,SAAS,QAAQ,UAAU,kBAAkB,KAAK,IAAI;AAC3D,SAAK,OAAO,QAAQ;AACpB,SAAK,cAAc,gBAAgB,IAAI,KAAK,IAAI;AAChD,SAAK,aAAa,QAAQ;AAC1B,SAAK,YAAY,oBAAI,KAAK;AAG1B,QAAI,MAAM,mBAAmB;AAC5B,YAAM,kBAAkB,MAAM,YAAW;AAAA,IAC1C;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,SAAkC;AACjC,WAAO;AAAA,MACN,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,MACd,MAAM,KAAK;AAAA,MACX,QAAQ,KAAK;AAAA,MACb,MAAM,KAAK;AAAA,MACX,aAAa,KAAK;AAAA,MAClB,YAAY,KAAK;AAAA,MACjB,WAAW,KAAK,UAAU,YAAY;AAAA,IACvC;AAAA,EACD;AACD;AAKO,IAAM,eAAN,cAA2B,YAAY;AAAA,EAC7C,YACC,UAAU,0BACV,SACC;AACD,UAAM,SAAS,EAAE,GAAG,SAAS,MAAM,gBAAgB,CAAC;AACpD,SAAK,OAAO;AAAA,EACb;AACD;AAKO,IAAM,eAAN,cAA2B,YAAY;AAAA;AAAA,EAEpC;AAAA,EAET,YAAY,SAAiB,SAA4C;AACxE,UAAM,2BAA2B,OAAO,MAAM;AAAA,MAC7C,GAAG;AAAA,MACH,MAAM;AAAA,IACP,CAAC;AACD,SAAK,OAAO;AACZ,SAAK,UAAU;AAAA,EAChB;AACD;AAKO,IAAM,sBAAN,cAAkC,YAAY;AAAA,EACpD,YACC,UAAU,2BACV,SACC;AACD,UAAM,SAAS,EAAE,GAAG,SAAS,MAAM,eAAe,CAAC;AACnD,SAAK,OAAO;AAAA,EACb;AACD;AAKO,IAAM,qBAAN,cAAiC,YAAY;AAAA,EACnD,YACC,UAAU,qBACV,SACC;AACD,UAAM,SAAS,EAAE,GAAG,SAAS,MAAM,YAAY,CAAC;AAChD,SAAK,OAAO;AAAA,EACb;AACD;AAKO,IAAM,kBAAN,cAA8B,YAAY;AAAA;AAAA,EAEvC;AAAA,EAET,YACC,SACA,SAGC;AACD,UAAM,SAAS,EAAE,GAAG,SAAS,MAAM,uBAAuB,CAAC;AAC3D,SAAK,OAAO;AACZ,SAAK,cAAc,SAAS;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,OAAmC;AAChD,WAAO,KAAK,cAAc,KAAK,IAAI,CAAC;AAAA,EACrC;AACD;AAoCO,IAAM,iBAAN,cAA6B,YAAY;AAAA;AAAA,EAEtC;AAAA;AAAA,EAGA;AAAA;AAAA,EAGA;AAAA,EAET,YACC,UAAU,qBACV,SACC;AACD,UAAM,SAAS,EAAE,GAAG,SAAS,MAAM,oBAAoB,CAAC;AACxD,SAAK,OAAO;AACZ,SAAK,QAAQ,SAAS;AACtB,SAAK,YAAY,SAAS;AAC1B,SAAK,UAAU,SAAS;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAKA,eAAiC;AAChC,WAAO,KAAK,UAAU,IAAI,KAAK,KAAK,UAAU,GAAI,IAAI;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA,EAKA,kBAA0B;AACzB,QAAI,KAAK,YAAY;AACpB,aAAO,sBAAsB,KAAK,UAAU;AAAA,IAC7C;AACA,QAAI,KAAK,SAAS;AACjB,YAAM,UAAU,KAAK,IAAI,GAAG,KAAK,UAAU,KAAK,MAAM,KAAK,IAAI,IAAI,GAAI,CAAC;AACxE,aAAO,wBAAwB,OAAO;AAAA,IACvC;AACA,WAAO;AAAA,EACR;AACD;AAKO,IAAM,gBAAN,cAA4B,YAAY;AAAA;AAAA,EAErC;AAAA;AAAA,EAGA;AAAA,EAET,YACC,UAAU,sBACV,SAIC;AACD,UAAM,SAAS,EAAE,GAAG,SAAS,MAAM,YAAY,CAAC;AAChD,SAAK,OAAO;AACZ,SAAK,eAAe,SAAS;AAC7B,SAAK,aAAa,SAAS;AAAA,EAC5B;AACD;AASO,SAAS,cAAc,OAAsC;AACnE,SAAO,iBAAiB;AACzB;AAKO,SAAS,iBAAiB,OAAyB;AACzD,MAAI,iBAAiB,aAAa;AACjC,WAAO,MAAM;AAAA,EACd;AAGA,MAAI,iBAAiB,OAAO;AAC3B,UAAM,UAAU,MAAM,QAAQ,YAAY;AAC1C,UAAM,OAAO,MAAM,KAAK,YAAY;AAGpC,QAAI,SAAS,eAAe,QAAQ,SAAS,OAAO,EAAG,QAAO;AAC9D,QAAI,SAAS,eAAgB,QAAO;AAGpC,QAAI,QAAQ,SAAS,SAAS,EAAG,QAAO;AACxC,QAAI,QAAQ,SAAS,WAAW,EAAG,QAAO;AAG1C,QAAI,QAAQ,SAAS,cAAc,EAAG,QAAO;AAC7C,QAAI,QAAQ,SAAS,YAAY,EAAG,QAAO;AAC3C,QAAI,QAAQ,SAAS,QAAQ,EAAG,QAAO;AAGvC,QAAI,QAAQ,SAAS,KAAK,EAAG,QAAO;AACpC,QAAI,QAAQ,SAAS,KAAK,EAAG,QAAO;AACpC,QAAI,QAAQ,SAAS,KAAK,EAAG,QAAO;AAAA,EACrC;AAEA,SAAO;AACR;AAKO,SAAS,gBAAgB,OAAwB;AACvD,MAAI,iBAAiB,OAAO;AAC3B,WAAO,MAAM;AAAA,EACd;AACA,MAAI,OAAO,UAAU,UAAU;AAC9B,WAAO;AAAA,EACR;AACA,SAAO;AACR;AAKO,SAAS,aAAa,OAAiC;AAC7D,MAAI,iBAAiB,aAAa;AACjC,WAAO,MAAM;AAAA,EACd;AACA,SAAO;AACR;AAKO,SAAS,cAAc,OAA6B;AAC1D,MAAI,iBAAiB,aAAa;AACjC,WAAO;AAAA,EACR;AAEA,MAAI,iBAAiB,OAAO;AAE3B,UAAM,UAAU,MAAM,QAAQ,YAAY;AAC1C,UAAM,OAAO,MAAM,KAAK,YAAY;AAGpC,QAAI,SAAS,eAAe,QAAQ,SAAS,OAAO,GAAG;AACtD,aAAO,IAAI,aAAa,MAAM,SAAS,EAAE,OAAO,MAAM,CAAC;AAAA,IACxD;AACA,QAAI,SAAS,gBAAgB,QAAQ,SAAS,SAAS,GAAG;AACzD,aAAO,IAAI,YAAY,MAAM,SAAS,EAAE,MAAM,WAAW,OAAO,MAAM,CAAC;AAAA,IACxE;AAGA,QAAI,QAAQ,SAAS,SAAS,GAAG;AAChC,aAAO,IAAI,aAAa,oBAAoB,EAAE,OAAO,MAAM,CAAC;AAAA,IAC7D;AAGA,QAAI,QAAQ,SAAS,KAAK,KAAK,QAAQ,SAAS,cAAc,GAAG;AAChE,aAAO,IAAI,oBAAoB,MAAM,SAAS,EAAE,OAAO,MAAM,CAAC;AAAA,IAC/D;AACA,QAAI,QAAQ,SAAS,KAAK,KAAK,QAAQ,SAAS,WAAW,GAAG;AAC7D,aAAO,IAAI,mBAAmB,MAAM,SAAS,EAAE,OAAO,MAAM,CAAC;AAAA,IAC9D;AACA,QAAI,QAAQ,SAAS,KAAK,KAAK,QAAQ,SAAS,WAAW,GAAG;AAC7D,aAAO,IAAI,cAAc,MAAM,SAAS,EAAE,OAAO,MAAM,CAAC;AAAA,IACzD;AACA,QAAI,QAAQ,SAAS,KAAK,KAAK,QAAQ,SAAS,YAAY,GAAG;AAC9D,aAAO,IAAI,eAAe,MAAM,SAAS,EAAE,OAAO,MAAM,CAAC;AAAA,IAC1D;AAEA,WAAO,IAAI,YAAY,MAAM,SAAS,EAAE,OAAO,MAAM,CAAC;AAAA,EACvD;AAEA,SAAO,IAAI,YAAY,gBAAgB,KAAK,CAAC;AAC9C;AAUO,SAAS,mBACf,SACA,YAAY,qBACZ,WAAW,oBACF;AAET,QAAM,mBAAmB,YAAY,KAAK,IAAI,GAAG,OAAO;AAGxD,QAAM,cAAc,KAAK,IAAI,kBAAkB,QAAQ;AAGvD,QAAM,SAAS,cAAc,QAAQ,KAAK,OAAO,IAAI,IAAI;AAEzD,SAAO,KAAK,MAAM,cAAc,MAAM;AACvC;;;ACpbA,IAAM,iBAAiB;AAQvB,IAAM,qBAAqB;AAG3B,IAAM,iBAAkD;AAAA,EACvD,KAAK;AAAA,EACL,KAAK;AAAA,EACL,MAAM;AACP;AASA,SAAS,gBAAgB,KAAuB;AAC/C,QAAM,SAAmB,CAAC;AAC1B,MAAI,QAAQ,IAAI,KAAK,EAAG,QAAO,KAAK,YAAY;AAChD,MAAI,IAAI,SAAS,IAAI,EAAG,QAAO,KAAK,SAAS;AAC7C,MAAI,IAAI,SAAS,IAAI,EAAG,QAAO,KAAK,iBAAiB;AACrD,MAAI,IAAI,SAAS,GAAG,EAAG,QAAO,KAAK,OAAO;AAC1C,MAAI,QAAQ,IAAI,YAAY,EAAG,QAAO,KAAK,iBAAiB;AAC5D,SAAO;AACR;AAKA,SAAS,0BACR,SACA,QACA,YACS;AACT,QAAM,cAAc,YAAY,UAAU,WAAW;AACrD,SACC,YAAY,WAAW,aAAa,OAAO,KAAK,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA,UAI1C,UAAU;AAAA;AAAA;AAAA;AAAA;AAKvB;AAKA,SAAS,sBACR,SACA,KACA,YACS;AACT,QAAM,SAAS,YAAY,UAAU,QAAQ;AAC7C,QAAM,YAAY,IAAI,SAAS,KAAK,GAAG,IAAI,MAAM,GAAG,EAAE,CAAC,QAAQ;AAC/D,QAAM,aAAa,GAAG,MAAM;AAC5B,QAAM,cAAc,YAAY,UAAU,WAAW;AAErD,SACC,oBAAoB,WAAW;AAAA;AAAA,mBACX,UAAU;AAAA,aAChB,SAAS;AAAA;AAAA,oBACF,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAQjC;AAKA,SAAS,mBAAmB,KAA0C;AAErE,QAAM,QAAQ,IAAI,MAAM,6BAA6B;AACrD,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,eAAe,MAAM,CAAC,CAAC;AAC/B;AAKA,SAAS,mBACR,KACA,SACA,SACA,YACsB;AACtB,QAAM,cAAc,YAAY,UAAU,WAAW;AAGrD,MAAI,CAAC,KAAK;AACT,WAAO;AAAA,MACN,OAAO;AAAA,MACP,cAAc;AAAA,MACd,OACC,YAAY,WAAW,qBAChB,UAAU;AAAA,MAClB,QAAQ,CAAC,SAAS;AAAA,IACnB;AAAA,EACD;AAGA,QAAM,SAAS,gBAAgB,GAAG;AAGlC,MAAI,QAAQ,KAAK,GAAG,GAAG;AACtB,WAAO;AAAA,MACN,OAAO;AAAA,MACP,cAAc;AAAA,MACd;AAAA,MACA,aAAa,mBAAmB,GAAG;AAAA,MACnC,QAAQ,CAAC;AAAA,IACV;AAAA,EACD;AAGA,QAAM,YAAY,IAAI,KAAK,EAAE,YAAY;AAEzC,MAAI,QAAQ,KAAK,SAAS,GAAG;AAE5B,WAAO;AAAA,MACN,OAAO;AAAA,MACP,cAAc;AAAA,MACd;AAAA,MACA,aAAa,mBAAmB,SAAS;AAAA,MACzC,SAAS,0BAA0B,SAAS,QAAQ,UAAU;AAAA,MAC9D;AAAA,IACD;AAAA,EACD;AAGA,SAAO;AAAA,IACN,OAAO;AAAA,IACP,cAAc;AAAA,IACd,OAAO,sBAAsB,SAAS,KAAK,UAAU;AAAA,IACrD,QAAQ,CAAC,GAAG,QAAQ,gBAAgB;AAAA,EACrC;AACD;AAoBO,SAAS,cACf,KACsB;AACtB,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;AAuCO,SAAS,kBACf,KACsB;AACtB,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACD;AAQO,SAAS,6BACf,KACS;AACT,QAAM,SAAS,kBAAkB,GAAG;AAEpC,MAAI,CAAC,OAAO,OAAO;AAClB,UAAM,IAAI,MAAM,OAAO,KAAK;AAAA,EAC7B;AAEA,MAAI,OAAO,SAAS;AACnB,YAAQ,KAAK,OAAO,OAAO;AAAA,EAC5B;AAEA,SAAO,OAAO;AACf;AA0FO,SAAS,cAAc,KAA6B;AAC1D,QAAM,YAAY,IAAI,KAAK,EAAE,YAAY;AACzC,MAAI,UAAU,WAAW,MAAM,EAAG,QAAO;AACzC,MAAI,UAAU,WAAW,KAAK,EAAG,QAAO;AACxC,SAAO;AACR;AA+BO,SAAS,YACf,KACsB;AACtB,QAAM,UAAU,MAAM,cAAc,GAAG,IAAI;AAE3C,MAAI,YAAY,SAAS;AACxB,WAAO,cAAc,GAAG;AAAA,EACzB;AACA,MAAI,YAAY,UAAU;AACzB,WAAO,kBAAkB,GAAG;AAAA,EAC7B;AAGA,SAAO;AAAA,IACN,OAAO;AAAA,IACP,cAAc;AAAA,IACd,OAAO,MACJ,+IAA+I,IAAI,MAAM,GAAG,EAAE,CAAC,QAC/J;AAAA,IACH,QAAQ,MAAM,CAAC,gBAAgB,IAAI,CAAC,SAAS;AAAA,EAC9C;AACD;;;AC7aA,SAAS,sBAAsB,QAAiC;AAC/D,UAAQ,QAAQ;AAAA,IACf,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR,KAAK;AACJ,aAAO;AAAA,IACR;AACC,aAAO,UAAU,MAAM,0BAA0B;AAAA,EACnD;AACD;AAoGO,SAAS,aAAa,OAAwC;AAEpE,MAAI;AACJ,MAAI,MAAM,WAAW;AACpB,UAAM,SAAS,YAAY,MAAM,SAAS;AAC1C,QAAI,CAAC,OAAO,OAAO;AAClB,YAAM,IAAI,YAAY,OAAO,SAAS,mBAAmB;AAAA,QACxD,MAAM;AAAA,QACN,MAAM,EAAE,QAAQ,OAAO,OAAO;AAAA,MAC/B,CAAC;AAAA,IACF;AACA,QAAI,OAAO,SAAS;AACnB,cAAQ,KAAK,YAAY,OAAO,OAAO,EAAE;AAAA,IAC1C;AACA,gBAAY,OAAO;AAAA,EACpB;AAIA,MAAI;AACJ,MAAI;AAEJ,MAAI,MAAM,aAAa;AAEtB,kBAAc,MAAM,YAAY,KAAK;AACrC,kBAAc;AAAA,EACf,WAAW,MAAM,KAAK;AAErB,kBAAc,WAAW,MAAM,GAAG,IAAI,oBAAoB;AAC1D,kBAAc;AAAA,EACf,OAAO;AAEN,kBAAc;AACd,kBAAc;AAAA,EACf;AAEA,SAAO,OAAO,OAAO;AAAA,IACpB;AAAA,IACA;AAAA,IACA,KAAK,MAAM;AAAA,IACX;AAAA,IACA,aAAa,MAAM;AAAA,EACpB,CAAC;AACF;AAUO,SAAS,UAAU,QAAsB,aAAmC;AAClF,SAAO,OAAO,OAAO;AAAA,IACpB,GAAG;AAAA,IACH;AAAA;AAAA,EAED,CAAC;AACF;AAKO,SAAS,aAAa,QAA8C;AAC1E,QAAM,UAAkC;AAAA,IACvC,gBAAgB;AAAA,EACjB;AAEA,MAAI,OAAO,WAAW;AACrB,YAAQ,cAAc,IAAI,OAAO;AAAA,EAClC;AACA,MAAI,OAAO,aAAa;AACvB,YAAQ,eAAe,IAAI,UAAU,OAAO,WAAW;AAAA,EACxD;AAEA,SAAO;AACR;AASO,SAAS,YAAY,QAAsB,MAAsB;AACvE,QAAM,OAAO,OAAO,YAAY,QAAQ,OAAO,EAAE;AACjD,QAAM,YAAY,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI,IAAI;AACxD,SAAO,GAAG,IAAI,GAAG,OAAO,eAAe,YAAY,GAAG,SAAS;AAChE;AAWA,eAAsB,QACrB,QACA,MACA,UAyBI,CAAC,GACc;AACnB,QAAM;AAAA,IACL,SAAS;AAAA,IACT;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,EACD,IAAI;AAEJ,MAAI,MAAM,YAAY,QAAQ,IAAI;AAGlC,MAAI,OAAO;AACV,UAAM,SAAS,IAAI,gBAAgB;AACnC,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,GAAG;AACjD,UAAI,UAAU,QAAW;AACxB,eAAO,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,MAC9B;AAAA,IACD;AACA,UAAM,cAAc,OAAO,SAAS;AACpC,QAAI,aAAa;AAChB,aAAO,IAAI,WAAW;AAAA,IACvB;AAAA,EACD;AAGA,QAAM,aAAa,IAAI,gBAAgB;AACvC,QAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,OAAO;AAG9D,QAAM,iBAAiB,SAAS,YAAY,IAAI,CAAC,QAAQ,WAAW,MAAM,CAAC,IAAI,WAAW;AAE1F,QAAM,UAAU,aAAa,MAAM;AAGnC,MAAI,gBAAgB;AACnB,YAAQ,iBAAiB,IAAI;AAAA,EAC9B;AAEA,QAAM,eAA4B;AAAA,IACjC;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,EACT;AAEA,MAAI,MAAM;AACT,iBAAa,OAAO,KAAK,UAAU,IAAI;AAAA,EACxC;AAEA,MAAI;AACJ,MAAI;AACH,eAAW,MAAM,MAAM,KAAK,YAAY;AAAA,EACzC,SAAS,OAAO;AACf,iBAAa,SAAS;AAGtB,QAAI,iBAAiB,OAAO;AAC3B,UAAI,MAAM,SAAS,cAAc;AAEhC,YAAI,WAAW,OAAO,WAAW,CAAC,QAAQ,SAAS;AAClD,gBAAM,IAAI,aAAa,OAAO;AAAA,QAC/B;AACA,cAAM,IAAI,YAAY,mBAAmB;AAAA,UACxC,MAAM;AAAA,UACN,OAAO;AAAA,QACR,CAAC;AAAA,MACF;AAEA,YAAM,IAAI,aAAa,MAAM,SAAS,EAAE,OAAO,MAAM,CAAC;AAAA,IACvD;AACA,UAAM,IAAI,aAAa,wBAAwB;AAAA,EAChD,UAAE;AACD,iBAAa,SAAS;AAAA,EACvB;AAEA,MAAI,CAAC,SAAS,IAAI;AACjB,UAAM,YAAY,MAAM,SAAS,KAAK,EAAE,MAAM,MAAM,EAAE;AACtD,QAAI,eAAe;AACnB,QAAI;AAGJ,QAAI,WAAW;AACd,UAAI;AACH,cAAM,SAAS,KAAK,MAAM,SAAS;AAInC,uBAAe,OAAO,OAAO,WAAW,OAAO,WAAW;AAC1D,oBAAY,OAAO;AAAA,MACpB,QAAQ;AAEP,uBAAe,SAAS,cAAc;AAAA,MACvC;AAAA,IACD;AAEA,UAAM,YAAY,sBAAsB,SAAS,MAAM;AAGvD,UAAM,mBAAmB,SAAS,QAAQ,IAAI,aAAa;AAC3D,UAAM,iBAAiB,SAAS,QAAQ,IAAI,mBAAmB;AAC/D,UAAM,qBAAqB,SAAS,QAAQ,IAAI,uBAAuB;AACvE,UAAM,iBAAiB,SAAS,QAAQ,IAAI,mBAAmB;AAE/D,UAAM,aAAa,mBAAmB,OAAO,SAAS,kBAAkB,EAAE,IAAI;AAG9E,QAAI,SAAS,WAAW,KAAK;AAC5B,YAAM,IAAI,eAAe,gBAAgB,qBAAqB;AAAA,QAC7D,QAAQ,SAAS;AAAA,QACjB,MAAM;AAAA,QACN;AAAA,QACA,OAAO,iBAAiB,OAAO,SAAS,gBAAgB,EAAE,IAAI;AAAA,QAC9D,WAAW,qBAAqB,OAAO,SAAS,oBAAoB,EAAE,IAAI;AAAA,QAC1E,SAAS,iBAAiB,OAAO,SAAS,gBAAgB,EAAE,IAAI;AAAA,MACjE,CAAC;AAAA,IACF;AAEA,UAAM,IAAI,YAAY,cAAc;AAAA,MACnC,MAAM;AAAA,MACN,QAAQ,SAAS;AAAA,MACjB,MAAM;AAAA,MACN;AAAA,IACD,CAAC;AAAA,EACF;AAGA,QAAM,OAAO,MAAM,SAAS,KAAK;AACjC,MAAI,CAAC,MAAM;AACV,WAAO,CAAC;AAAA,EACT;AAGA,MAAI;AACH,WAAO,KAAK,MAAM,IAAI;AAAA,EACvB,SAAS,OAAO;AACf,UAAM,IAAI,YAAY,4BAA4B;AAAA,MACjD,MAAM;AAAA,MACN,OAAO,iBAAiB,QAAQ,QAAQ;AAAA,MACxC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,GAAG,EAAE;AAAA;AAAA,IAClC,CAAC;AAAA,EACF;AACD;;;AC9ZA,IAAM,oBAAoB;AAU1B,SAAS,iBAA0B;AAElC,MAAI,OAAO,WAAW,eAAe,OAAO,iBAAiB,aAAa;AACzE,QAAI;AACH,aAAO,aAAa,QAAQ,iBAAiB,MAAM;AAAA,IACpD,QAAQ;AAEP,aAAO;AAAA,IACR;AAAA,EACD;AAGA,MAAI,OAAO,YAAY,eAAe,QAAQ,KAAK;AAClD,WAAO,QAAQ,IAAI,iBAAiB;AAAA,EACrC;AAEA,SAAO;AACR;AAGA,IAAI,iBAAiC;AAO9B,SAAS,eAAwB;AACvC,MAAI,mBAAmB,MAAM;AAC5B,qBAAiB,eAAe;AAAA,EACjC;AACA,SAAO;AACR;AAKO,SAAS,sBAA4B;AAC3C,mBAAiB;AAClB;AA2BO,SAAS,SACf,UACA,SACA,MACO;AACP,MAAI,CAAC,aAAa,EAAG;AAErB,QAAM,SAAS,WAAW,QAAQ;AAElC,MAAI,SAAS,QAAW;AACvB,YAAQ,IAAI,QAAQ,SAAS,IAAI;AAAA,EAClC,OAAO;AACN,YAAQ,IAAI,QAAQ,OAAO;AAAA,EAC5B;AACD;AAKO,SAAS,UACf,UACA,SACA,MACO;AACP,MAAI,CAAC,aAAa,EAAG;AAErB,QAAM,SAAS,WAAW,QAAQ;AAElC,MAAI,SAAS,QAAW;AACvB,YAAQ,KAAK,QAAQ,SAAS,IAAI;AAAA,EACnC,OAAO;AACN,YAAQ,KAAK,QAAQ,OAAO;AAAA,EAC7B;AACD;AAQO,SAAS,WACf,UACA,SACA,OACO;AACP,MAAI,CAAC,aAAa,EAAG;AAErB,QAAM,SAAS,WAAW,QAAQ;AAElC,MAAI,UAAU,QAAW;AACxB,YAAQ,MAAM,QAAQ,SAAS,KAAK;AAAA,EACrC,OAAO;AACN,YAAQ,MAAM,QAAQ,OAAO;AAAA,EAC9B;AACD;AAgBO,SAAS,WACf,UACA,WACsB;AACtB,MAAI,CAAC,aAAa,GAAG;AACpB,WAAO,EAAE,KAAK,MAAM;AAAA,IAAC,EAAE;AAAA,EACxB;AAEA,QAAM,QAAQ,YAAY,IAAI;AAE9B,SAAO;AAAA,IACN,MAAM;AACL,YAAM,WAAW,YAAY,IAAI,IAAI;AACrC,eAAS,UAAU,GAAG,SAAS,cAAc;AAAA,QAC5C,YAAY,KAAK,MAAM,QAAQ;AAAA,MAChC,CAAC;AAAA,IACF;AAAA,EACD;AACD;AAcO,SAAS,cAAoB;AACnC,MAAI,OAAO,iBAAiB,aAAa;AACxC,YAAQ;AAAA,MACP;AAAA,IACD;AACA;AAAA,EACD;AAEA,MAAI;AACH,iBAAa,QAAQ,mBAAmB,MAAM;AAC9C,qBAAiB;AACjB,YAAQ;AAAA,MACP;AAAA,IACD;AAAA,EACD,SAAS,GAAG;AACX,YAAQ,KAAK,yCAAyC,CAAC;AAAA,EACxD;AACD;AAKO,SAAS,eAAqB;AACpC,MAAI,OAAO,iBAAiB,aAAa;AACxC,YAAQ;AAAA,MACP;AAAA,IACD;AACA;AAAA,EACD;AAEA,MAAI;AACH,iBAAa,WAAW,iBAAiB;AACzC,qBAAiB;AACjB,YAAQ,IAAI,+BAA+B;AAAA,EAC5C,SAAS,GAAG;AACX,YAAQ,KAAK,0CAA0C,CAAC;AAAA,EACzD;AACD;AAYO,SAAS,4BAAkC;AACjD,MAAI,OAAO,WAAW,YAAa;AAGnC,QAAM,IAAI;AAQV,IAAE,WAAW;AAAA,IACZ;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,EACjB;AACD;;;AC/OA,OAAO,kBAAuC;AAmJ9C,SAAS,qBAAqB,QAAuC;AACpE,SAAO;AAAA,IACN,MAAM,UAAU,EAAE,QAAQ,GAAG;AAE5B,cAAQ,QAAQ,IAAI,iBAAiB,WAAW;AAChD,cAAQ,QAAQ,IAAI,kBAAkB,YAAY;AAGlD,UAAI,OAAO,WAAW;AACrB,gBAAQ,QAAQ,IAAI,gBAAgB,OAAO,SAAS;AAAA,MACrD;AAGA,YAAM,QAAQ,OAAO,iBAAiB;AACtC,UAAI,OAAO;AACV,gBAAQ,QAAQ,IAAI,iBAAiB,UAAU,KAAK,EAAE;AAAA,MACvD;AAEA,aAAO;AAAA,IACR;AAAA,EACD;AACD;AAKA,SAAS,kBAAkB,QAAyB;AACnD,SAAO,UAAU,OAAO,WAAW;AACpC;AAaA,IAAM,mBAAmB,oBAAI,IAA+B;AAK5D,eAAe,cAAc,SAAmC;AAC/D,QAAM,OAAO,QAAQ,OAAO,MAAM,QAAQ,MAAM,EAAE,KAAK,IAAI;AAC3D,SAAO,GAAG,QAAQ,MAAM,IAAI,QAAQ,GAAG,IAAI,IAAI;AAChD;AAaA,SAAS,8BACR,SAGI,CAAC,GACQ;AACb,QAAM,EAAE,UAAU,MAAM,UAAU,CAAC,KAAK,EAAE,IAAI;AAE9C,MAAI,CAAC,SAAS;AACb,WAAO;AAAA,MACN,MAAM,UAAU,EAAE,QAAQ,GAAG;AAC5B,eAAO;AAAA,MACR;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AAAA,IACN,MAAM,UAAU,EAAE,QAAQ,GAAG;AAE5B,UAAI,CAAC,QAAQ,SAAS,QAAQ,MAAe,GAAG;AAC/C,eAAO;AAAA,MACR;AAEA,YAAM,MAAM,MAAM,cAAc,OAAO;AAGvC,YAAM,WAAW,iBAAiB,IAAI,GAAG;AACzC,UAAI,UAAU;AAEb,cAAM,UAAU,QAAQ,MAAM;AAC9B,QAAC,QAA6C,YAAY;AAC1D,eAAO;AAAA,MACR;AAEA,MAAC,QAA6C,YAAY;AAC1D,aAAO;AAAA,IACR;AAAA,IACA,MAAM,WAAW,EAAE,SAAS,SAAS,GAAG;AACvC,YAAM,MAAO,QAA8C;AAC3D,UAAI,CAAC,IAAK,QAAO;AAGjB,YAAM,WAAW,iBAAiB,IAAI,GAAG;AACzC,UAAI,YAAY,iBAAiB,IAAI,GAAG,MAAM,QAAW;AAExD,cAAM,iBAAiB,MAAM;AAC7B,eAAO,eAAe,MAAM;AAAA,MAC7B;AAGA,YAAM,kBAAkB,QAAQ,QAAQ,SAAS,MAAM,CAAC;AACxD,uBAAiB,IAAI,KAAK,eAAe;AAGzC,sBAAgB,QAAQ,MAAM;AAE7B,mBAAW,MAAM,iBAAiB,OAAO,GAAG,GAAG,GAAG;AAAA,MACnD,CAAC;AAED,aAAO;AAAA,IACR;AAAA,EACD;AACD;AAkBO,IAAM,0BAAN,cAAsC,MAAM;AAAA,EACzC;AAAA,EAET,YAAY,aAAqB;AAChC;AAAA,MACC,wCAAwC,KAAK,KAAK,cAAc,GAAI,CAAC;AAAA,IACtE;AACA,SAAK,OAAO;AACZ,SAAK,cAAc;AAAA,EACpB;AACD;AAeA,IAAI,iBAAwC;AAK5C,SAAS,kBAAkB,SAA+B,CAAC,GAAmB;AAC7E,MAAI,CAAC,gBAAgB;AACpB,qBAAiB;AAAA,MAChB,OAAO;AAAA,MACP,UAAU,CAAC;AAAA,MACX,UAAU;AAAA,MACV,QAAQ;AAAA,QACP,SAAS,OAAO,WAAW;AAAA,QAC3B,kBACC,OAAO,oBAAoB;AAAA,QAC5B,UAAU,OAAO,YAAY;AAAA,QAC7B,gBACC,OAAO,kBAAkB;AAAA,QAC1B,WACC,OAAO,cAAc,CAAC,WAAW,UAAU,OAAO,WAAW;AAAA,MAC/D;AAAA,IACD;AAAA,EACD;AACA,SAAO;AACR;AAKA,SAAS,cAAc,IAA0B;AAChD,QAAM,MAAM,KAAK,IAAI;AAGrB,KAAG,WAAW,GAAG,SAAS,OAAO,CAAC,MAAM,MAAM,IAAI,GAAG,OAAO,QAAQ;AAGpE,KAAG,SAAS,KAAK,GAAG;AAGpB,MAAI,GAAG,SAAS,UAAU,GAAG,OAAO,kBAAkB;AACrD,OAAG,QAAQ;AACX,OAAG,WAAW;AAAA,EACf;AACD;AAKA,SAAS,cAAc,IAA0B;AAChD,MAAI,GAAG,UAAU,aAAa;AAE7B,OAAG,QAAQ;AACX,OAAG,WAAW,CAAC;AACf,OAAG,WAAW;AAAA,EACf;AACD;AAKA,SAAS,mBAAmB,IAG1B;AACD,QAAM,MAAM,KAAK,IAAI;AAErB,UAAQ,GAAG,OAAO;AAAA,IACjB,KAAK;AACJ,aAAO,EAAE,SAAS,KAAK;AAAA,IAExB,KAAK,QAAQ;AACZ,YAAM,UAAU,OAAO,GAAG,YAAY;AACtC,UAAI,WAAW,GAAG,OAAO,gBAAgB;AAExC,WAAG,QAAQ;AACX,eAAO,EAAE,SAAS,KAAK;AAAA,MACxB;AACA,aAAO;AAAA,QACN,SAAS;AAAA,QACT,aAAa,GAAG,OAAO,iBAAiB;AAAA,MACzC;AAAA,IACD;AAAA,IAEA,KAAK;AAGJ,aAAO,EAAE,SAAS,KAAK;AAAA,IAExB;AACC,aAAO,EAAE,SAAS,KAAK;AAAA,EACzB;AACD;AAWA,SAAS,+BACR,QACa;AACb,MAAI,WAAW,OAAO;AACrB,WAAO;AAAA,MACN,MAAM,UAAU,EAAE,QAAQ,GAAG;AAC5B,eAAO;AAAA,MACR;AAAA,IACD;AAAA,EACD;AAEA,QAAM,KAAK,kBAAkB,UAAU,CAAC,CAAC;AAEzC,SAAO;AAAA,IACN,MAAM,UAAU,EAAE,QAAQ,GAAG;AAC5B,UAAI,CAAC,GAAG,OAAO,SAAS;AACvB,eAAO;AAAA,MACR;AAEA,YAAM,QAAQ,mBAAmB,EAAE;AACnC,UAAI,CAAC,MAAM,SAAS;AACnB,cAAM,IAAI,wBAAwB,MAAM,WAAY;AAAA,MACrD;AAEA,aAAO;AAAA,IACR;AAAA,IACA,MAAM,WAAW,EAAE,SAAS,GAAG;AAC9B,UAAI,CAAC,GAAG,OAAO,SAAS;AACvB,eAAO;AAAA,MACR;AAEA,UAAI,GAAG,OAAO,UAAU,SAAS,MAAM,GAAG;AACzC,sBAAc,EAAE;AAAA,MACjB,OAAO;AACN,sBAAc,EAAE;AAAA,MACjB;AAEA,aAAO;AAAA,IACR;AAAA,EACD;AACD;AAKO,SAAS,sBAA4B;AAC3C,mBAAiB;AAClB;AAKO,SAAS,yBAIP;AACR,MAAI,CAAC,eAAgB,QAAO;AAC5B,SAAO;AAAA,IACN,OAAO,eAAe;AAAA,IACtB,UAAU,eAAe,SAAS;AAAA,IAClC,UAAU,eAAe;AAAA,EAC1B;AACD;AAkBA,IAAM,YAAY,oBAAI,IAA4B;AAKlD,SAAS,gBAAgB,SAA0B;AAClD,SAAO,GAAG,QAAQ,MAAM,IAAI,QAAQ,GAAG;AACxC;AAKA,SAAS,gBAAgB,YAAoB,OAAqB;AACjE,QAAM,MAAM,KAAK,IAAI;AAGrB,aAAW,CAAC,KAAK,KAAK,KAAK,WAAW;AACrC,QAAI,MAAM,MAAM,YAAY,OAAO;AAClC,gBAAU,OAAO,GAAG;AAAA,IACrB;AAAA,EACD;AAGA,MAAI,UAAU,OAAO,YAAY;AAChC,UAAM,UAAU,MAAM,KAAK,UAAU,QAAQ,CAAC;AAC9C,YAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,SAAS;AAEtD,UAAM,WAAW,QAAQ,MAAM,GAAG,QAAQ,SAAS,UAAU;AAC7D,eAAW,CAAC,GAAG,KAAK,UAAU;AAC7B,gBAAU,OAAO,GAAG;AAAA,IACrB;AAAA,EACD;AACD;AAYA,SAAS,qBACR,QACa;AACb,MAAI,WAAW,OAAO;AACrB,WAAO;AAAA,MACN,MAAM,UAAU,EAAE,QAAQ,GAAG;AAC5B,eAAO;AAAA,MACR;AAAA,IACD;AAAA,EACD;AAEA,QAAM;AAAA,IACL,UAAU;AAAA,IACV,aAAa;AAAA,IACb,QAAQ;AAAA,EACT,IAAI,UAAU,CAAC;AAEf,MAAI,CAAC,SAAS;AACb,WAAO;AAAA,MACN,MAAM,UAAU,EAAE,QAAQ,GAAG;AAC5B,eAAO;AAAA,MACR;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AAAA,IACN,MAAM,UAAU,EAAE,QAAQ,GAAG;AAE5B,UAAI,QAAQ,WAAW,OAAO;AAC7B,eAAO;AAAA,MACR;AAEA,YAAM,WAAW,gBAAgB,OAAO;AACxC,YAAM,SAAS,UAAU,IAAI,QAAQ;AAErC,UAAI,QAAQ;AAEX,YAAI,KAAK,IAAI,IAAI,OAAO,YAAY,OAAO;AAC1C,oBAAU,OAAO,QAAQ;AAAA,QAC1B,OAAO;AAEN,kBAAQ,QAAQ,IAAI,iBAAiB,OAAO,IAAI;AAAA,QACjD;AAAA,MACD;AAEA,aAAO;AAAA,IACR;AAAA,IACA,MAAM,WAAW,EAAE,SAAS,SAAS,GAAG;AAEvC,UAAI,QAAQ,WAAW,OAAO;AAC7B,eAAO;AAAA,MACR;AAEA,YAAM,WAAW,gBAAgB,OAAO;AAGxC,UAAI,SAAS,WAAW,KAAK;AAC5B,cAAM,SAAS,UAAU,IAAI,QAAQ;AACrC,YAAI,QAAQ;AAEX,iBAAO,YAAY,KAAK,IAAI;AAG5B,iBAAO,IAAI,SAAS,OAAO,MAAM;AAAA,YAChC,QAAQ;AAAA,YACR,SAAS,SAAS;AAAA,UACnB,CAAC;AAAA,QACF;AAEA,eAAO;AAAA,MACR;AAGA,UAAI,SAAS,IAAI;AAChB,cAAM,OAAO,SAAS,QAAQ,IAAI,MAAM;AACxC,YAAI,MAAM;AAET,gBAAM,SAAS,SAAS,MAAM;AAC9B,gBAAM,OAAO,MAAM,OAAO,KAAK;AAG/B,0BAAgB,YAAY,KAAK;AAGjC,oBAAU,IAAI,UAAU;AAAA,YACvB;AAAA,YACA;AAAA,YACA,WAAW,KAAK,IAAI;AAAA,UACrB,CAAC;AAAA,QACF;AAAA,MACD;AAEA,aAAO;AAAA,IACR;AAAA,EACD;AACD;AAgCA,SAAS,sBACR,aACa;AACb,MAAI,gBAAgB,OAAO;AAE1B,WAAO;AAAA,MACN,MAAM,WAAW,EAAE,SAAS,GAAG;AAC9B,eAAO;AAAA,MACR;AAAA,IACD;AAAA,EACD;AAEA,QAAM;AAAA,IACL,aAAa;AAAA,IACb,YAAY;AAAA,IACZ,WAAW;AAAA,IACX,cAAc;AAAA,IACd,UAAU;AAAA,EACX,IAAI,eAAe,CAAC;AAGpB,MAAI,eAA8B;AAElC,SAAO;AAAA,IACN,MAAM,UAAU,EAAE,QAAQ,GAAG;AAE5B,UAAI,QAAQ,MAAM;AACjB,uBAAe,MAAM,QAAQ,MAAM,EAAE,KAAK;AAAA,MAC3C,OAAO;AACN,uBAAe;AAAA,MAChB;AAGA,UAAI,CAAC,QAAQ,QAAQ;AACpB,cAAM,aAAa,IAAI,gBAAgB;AACvC,mBAAW,MAAM,WAAW,MAAM,GAAG,OAAO;AAC5C,eAAO,IAAI,QAAQ,QAAQ,KAAK;AAAA,UAC/B,QAAQ,QAAQ;AAAA,UAChB,SAAS,QAAQ;AAAA,UACjB,MAAM;AAAA,UACN,QAAQ,WAAW;AAAA,QACpB,CAAC;AAAA,MACF;AACA,aAAO;AAAA,IACR;AAAA,IACA,MAAM,WAAW,EAAE,UAAU,QAAQ,GAAG;AACvC,UAAI,UAAU;AACd,UAAI,kBAAkB;AAGtB,aACC,UAAU,cACV,YAAY,gBAAgB,QAAQ,OAAO,GAC1C;AACD,cAAM,aAAa,gBAAgB,QAAQ,IAAI,aAAa;AAC5D,cAAM,QAAQ,aACX,OAAO,SAAS,YAAY,EAAE,IAAI,MAClC,mBAAmB,SAAS,WAAW,QAAQ;AAElD,cAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,KAAK,CAAC;AACzD;AAGA,cAAM,aAAa,IAAI,gBAAgB;AACvC,cAAM,YAAY,WAAW,MAAM,WAAW,MAAM,GAAG,OAAO;AAE9D,YAAI;AAEH,gBAAM,eAAe,IAAI,QAAQ,QAAQ,KAAK;AAAA,YAC7C,QAAQ,QAAQ;AAAA,YAChB,SAAS,QAAQ;AAAA,YACjB,MAAM;AAAA,YACN,QAAQ,WAAW;AAAA,UACpB,CAAC;AAED,gBAAM,cAAc,MAAM,MAAM,YAAY;AAC5C,uBAAa,SAAS;AAGtB,cAAI,YAAY,MAAM,CAAC,YAAY,YAAY,QAAQ,OAAO,GAAG;AAChE,mBAAO;AAAA,UACR;AAEA,4BAAkB;AAAA,QACnB,SAAS,OAAO;AACf,uBAAa,SAAS;AAEtB,cAAI,WAAW,YAAY;AAC1B,kBAAM;AAAA,UACP;AAAA,QACD;AAAA,MACD;AAEA,aAAO;AAAA,IACR;AAAA,EACD;AACD;AA4BA,SAAS,qBAAqB,QAG3B;AACF,SAAO;AAAA,IACN,WAAW,6BAA6B,OAAO,SAAS;AAAA,IACxD,UAAU,OAAO,eAAe,sBAAsB,KAAK;AAAA,EAC5D;AACD;AAEO,SAAS,iBAAiB,QAA0B;AAC1D,QAAM,EAAE,WAAW,QAAQ,IAAI,qBAAqB,MAAM;AAE1D,QAAM,SAAS,aAAoB;AAAA,IAClC,SAAS,GAAG,OAAO,GAAG,YAAY;AAAA,IAClC,SAAS;AAAA,MACR,gBAAgB;AAAA,MAChB,gBAAgB;AAAA,IACjB;AAAA,EACD,CAAC;AAGD,MAAI,OAAO,kBAAkB,OAAO;AACnC,WAAO,IAAI,8BAA8B,OAAO,aAAa,CAAC;AAAA,EAC/D;AAGA,MAAI,OAAO,mBAAmB,OAAO;AACpC,WAAO,IAAI,+BAA+B,OAAO,cAAc,CAAC;AAAA,EACjE;AAGA,MAAI,OAAO,SAAS,OAAO;AAC1B,WAAO,IAAI,qBAAqB,OAAO,IAAI,CAAC;AAAA,EAC7C;AAGA,SAAO,IAAI,sBAAsB,OAAO,KAAK,CAAC;AAE9C,SAAO;AACR;AAkBO,SAAS,wBAAwB,QAA2B;AAClE,QAAM,EAAE,WAAW,QAAQ,IAAI,qBAAqB,MAAM;AAG1D,QAAM,kBAAqC;AAAA,IAC1C,GAAG;AAAA,IACH;AAAA,IACA,aAAa;AAAA,EACd;AAEA,QAAM,SAAS,aAAoB;AAAA,IAClC,SAAS,GAAG,OAAO,GAAG,YAAY;AAAA,IAClC,SAAS;AAAA,MACR,gBAAgB;AAAA,IACjB;AAAA,EACD,CAAC;AAGD,MAAI,OAAO,kBAAkB,OAAO;AACnC,WAAO,IAAI,8BAA8B,OAAO,aAAa,CAAC;AAAA,EAC/D;AAGA,SAAO,IAAI,qBAAqB,eAAe,CAAC;AAGhD,MAAI,OAAO,mBAAmB,OAAO;AACpC,WAAO,IAAI,+BAA+B,OAAO,cAAc,CAAC;AAAA,EACjE;AAGA,MAAI,OAAO,SAAS,OAAO;AAC1B,WAAO,IAAI,qBAAqB,OAAO,IAAI,CAAC;AAAA,EAC7C;AAGA,SAAO,IAAI,sBAAsB,OAAO,KAAK,CAAC;AAE9C,SAAO;AACR;AAWO,SAAS,SAAe,UAG7B;AACD,SAAO,SAAS,UAAU;AAC3B;AAKO,SAAS,oBAAoB,OAAwB;AAC3D,MAAI,SAAS,OAAO,UAAU,YAAY,WAAW,OAAO;AAC3D,UAAM,MAAM;AACZ,WAAO,IAAI,OAAO,WAAW;AAAA,EAC9B;AACA,MAAI,iBAAiB,OAAO;AAC3B,WAAO,MAAM;AAAA,EACd;AACA,SAAO;AACR;;;AC50BA,eAAsB,OAAO,QAAsB,OAA6C;AAC/F,SAAO,QAAuB,QAAQ,eAAe;AAAA,IACpD,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;AAeA,eAAsB,OACrB,QACA,OAC4B;AAC5B,SAAO,QAA0B,QAAQ,kBAAkB;AAAA,IAC1D,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;AAUA,eAAsB,QAAQ,QAAqC;AAClE,QAAM,QAAc,QAAQ,gBAAgB,EAAE,QAAQ,OAAO,CAAC;AAC/D;AAWA,eAAsB,aAAa,QAAsB,OAAuC;AAC/F,QAAM,WAAW,MAAM,MAAM,GAAG,OAAO,WAAW,sBAAsB;AAAA,IACvE,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU;AAAA,MACpB,YAAY;AAAA,MACZ,eAAe;AAAA,MACf,eAAe,OAAO;AAAA,IACvB,CAAC;AAAA,EACF,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AACjB,UAAM,QAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,EAAE,SAAS,uBAAuB,EAAE;AACrF,UAAM,IAAI,YAAY,MAAM,WAAW,wBAAwB;AAAA,MAC9D,MAAM;AAAA,IACP,CAAC;AAAA,EACF;AAEA,SAAO,SAAS,KAAK;AACtB;AAUA,eAAsB,YAAY,QAAsB,OAA8B;AACrF,QAAM,WAAW,MAAM,MAAM,GAAG,OAAO,WAAW,6BAA6B;AAAA,IAC9E,QAAQ;AAAA,IACR,SAAS,aAAa,MAAM;AAAA,IAC5B,MAAM,KAAK,UAAU,EAAE,MAAM,CAAC;AAAA,EAC/B,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AACjB,UAAM,QAAQ,MAAM,SAAS,KAAK,EAAE,MAAM,OAAO,EAAE,SAAS,4BAA4B,EAAE;AAC1F,UAAM,IAAI,YAAY,MAAM,WAAW,6BAA6B;AAAA,MACnE,MAAM;AAAA,IACP,CAAC;AAAA,EACF;AACD;AAUA,eAAsB,eAAe,QAAsB,OAA8B;AACxF,QAAM,QAA8B,QAAQ,yBAAyB;AAAA,IACpE,QAAQ;AAAA,IACR,MAAM,EAAE,MAAM;AAAA,EACf,CAAC;AACF;AAUA,eAAsB,cACrB,QACA,OACgB;AAChB,QAAM,QAA8B,QAAQ,wBAAwB;AAAA,IACnE,QAAQ;AAAA,IACR,MAAM,EAAE,OAAO,MAAM,OAAO,aAAa,MAAM,SAAS;AAAA,EACzD,CAAC;AACF;AAaA,eAAsB,WAAW,QAA8C;AAC9E,MAAI,CAAC,OAAO,aAAa;AACxB,WAAO,EAAE,MAAM,KAAK;AAAA,EACrB;AAEA,MAAI;AACH,UAAM,OAAO,MAAM,QAA+B,QAAQ,UAAU;AACpE,WAAO,EAAE,KAAK;AAAA,EACf,QAAQ;AACP,WAAO,EAAE,MAAM,KAAK;AAAA,EACrB;AACD;AAaA,eAAsB,gBACrB,QACA,QACA,MACyB;AACzB,SAAO,QAAuB,QAAQ,oBAAoB;AAAA,IACzD,QAAQ;AAAA,IACR,MAAM,EAAE,QAAQ,KAAK;AAAA,EACtB,CAAC;AACF;AAmBA,eAAsB,gBACrB,QACA,OACA,eACoC;AACpC,QAAM,WAAW,MAAM,MAAM,GAAG,OAAO,WAAW,2BAA2B;AAAA,IAC5E,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU;AAAA,MACpB;AAAA,MACA,iBAAiB;AAAA,MACjB,eAAe,OAAO;AAAA,IACvB,CAAC;AAAA,EACF,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AAEjB,WAAO,EAAE,QAAQ,MAAM;AAAA,EACxB;AAEA,SAAO,SAAS,KAAK;AACtB;AAmBA,eAAsB,YACrB,QACA,OACA,SACgB;AAChB,QAAM,MAAM,GAAG,OAAO,WAAW,uBAAuB;AAAA,IACvD,QAAQ;AAAA,IACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,IAC9C,MAAM,KAAK,UAAU;AAAA,MACpB,OAAO,SAAS,YAAY,SAAY;AAAA,MACxC,eAAe,OAAO;AAAA,MACtB,SAAS,SAAS;AAAA,MAClB,YAAY,SAAS;AAAA,IACtB,CAAC;AAAA,EACF,CAAC;AAEF;AAaA,eAAsB,gBAAgB,QAAsB,QAA+B;AAC1F,QAAM,YAAY,QAAQ,IAAI,EAAE,WAAW,MAAM,OAAO,CAAC;AAC1D;;;ACrRA,eAAsB,MACrB,QACA,OACgB;AAChB,QAAM,QAAQ,QAAQ,oBAAoB;AAAA,IACzC,QAAQ;AAAA,IACR,MAAM;AAAA,MACL,OAAO,MAAM;AAAA,MACb,YAAY,MAAM,cAAc,CAAC;AAAA,MACjC,QAAQ,MAAM;AAAA,MACd,aAAa,MAAM;AAAA,MACnB,WAAW,MAAM,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACtD;AAAA,EACD,CAAC;AACF;AAaA,eAAsB,KACrB,QACA,OACgB;AAChB,QAAM,QAAQ,QAAQ,mBAAmB;AAAA,IACxC,QAAQ;AAAA,IACR,MAAM;AAAA,MACL,MAAM,MAAM;AAAA,MACZ,YAAY,MAAM,cAAc,CAAC;AAAA,MACjC,QAAQ,MAAM;AAAA,MACd,aAAa,MAAM;AAAA,MACnB,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACnC;AAAA,EACD,CAAC;AACF;AAcA,eAAsB,SACrB,QACA,OACgB;AAChB,QAAM,QAAQ,QAAQ,uBAAuB;AAAA,IAC5C,QAAQ;AAAA,IACR,MAAM;AAAA,MACL,QAAQ,MAAM;AAAA,MACd,QAAQ,MAAM,UAAU,CAAC;AAAA,MACzB,aAAa,MAAM;AAAA,IACpB;AAAA,EACD,CAAC;AACF;AAcA,eAAsB,WACrB,QACA,QACgB;AAChB,QAAM,QAAQ,QAAQ,oBAAoB;AAAA,IACzC,QAAQ;AAAA,IACR,MAAM;AAAA,MACL,QAAQ,OAAO,IAAI,CAAC,OAAO;AAAA,QAC1B,OACC,EAAE,SAAS,UACR,EAAE,QACF,EAAE,SAAS,SACV,cACA;AAAA,QACL,YAAY;AAAA,UACX,GAAG,EAAE;AAAA,UACL,GAAI,EAAE,SAAS,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;AAAA,UACtD,GAAI,EAAE,SAAS,cAAc,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,IAAI,CAAC;AAAA,QACjE;AAAA,QACA,QAAQ,EAAE;AAAA,QACV,aAAa,EAAE;AAAA,QACf,WAAW,EAAE,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,MAClD,EAAE;AAAA,IACH;AAAA,EACD,CAAC;AACF;AAmBO,SAAS,sBAA8B;AAE7C,MAAI,OAAO,WAAW,eAAe,OAAO,YAAY;AACvD,WAAO,OAAO,WAAW;AAAA,EAC1B;AAEA,SAAO,uCAAuC,QAAQ,SAAS,CAAC,MAAM;AACrE,UAAM,IAAK,KAAK,OAAO,IAAI,KAAM;AACjC,UAAM,IAAI,MAAM,MAAM,IAAK,IAAI,IAAO;AACtC,WAAO,EAAE,SAAS,EAAE;AAAA,EACrB,CAAC;AACF;AAkBO,SAAS,cACf,QACA,oBACC;AACD,QAAM,cAAc,sBAAsB,oBAAoB;AAE9D,SAAO;AAAA,IACN,OAAO,CACN,OACA,YACA,WACI,MAAM,QAAQ,EAAE,OAAO,YAAY,QAAQ,YAAY,CAAC;AAAA,IAE7D,MAAM,CACL,MACA,YACA,WACI,KAAK,QAAQ,EAAE,MAAM,YAAY,QAAQ,YAAY,CAAC;AAAA,IAE3D,UAAU,CAAC,QAAgB,WAC1B,SAAS,QAAQ,EAAE,QAAQ,QAAQ,YAAY,CAAC;AAAA,IAEjD,OAAO,CAAC,WACP;AAAA,MACC;AAAA,MACA,OAAO,IAAI,CAAC,OAAO;AAAA,QAClB,GAAG;AAAA,QACH,aAAa,EAAE,eAAe;AAAA,MAC/B,EAAE;AAAA,IACH;AAAA;AAAA,IAGD,gBAAgB,MAAM;AAAA,EACvB;AACD;;;ACjHA,eAAsB,KACrB,QACA,OACsB;AACtB,QAAM,WAAW,MAAM;AAAA,IACtB,GAAG,OAAO,WAAW;AAAA,IACrB;AAAA,MACC,QAAQ;AAAA,MACR,SAAS;AAAA,QACR,GAAG,aAAa,MAAM;AAAA,QACtB,eAAe,UAAU,OAAO,SAAS;AAAA,MAC1C;AAAA,MACA,MAAM,KAAK,UAAU;AAAA,QACpB,OAAO,MAAM;AAAA,QACb,UAAU,MAAM;AAAA,QAChB,aAAa,MAAM;AAAA,QACnB,YAAY,MAAM;AAAA,QAClB,OAAO,MAAM;AAAA,QACb,mBAAmB,MAAM;AAAA,QACzB,kBAAkB,MAAM;AAAA,QACxB,MAAM,MAAM;AAAA,QACZ,OAAO,MAAM;AAAA,QACb,aAAa,MAAM;AAAA,MACpB,CAAC;AAAA,IACF;AAAA,EACD;AAEA,MAAI,CAAC,SAAS,IAAI;AACjB,UAAM,QAAQ,MAAM,SAClB,KAAK,EACL,MAAM,OAAO,EAAE,OAAO,EAAE,SAAS,sBAAsB,EAAE,EAAE;AAC7D,UAAM,IAAI,YAAY,OAAO,OAAO,WAAW,uBAAuB;AAAA,MACrE,MAAM;AAAA,IACP,CAAC;AAAA,EACF;AAEA,QAAM,OAAO,MAAM,SAAS,KAAK;AAEjC,SAAO;AAAA,IACN,IAAI,KAAK;AAAA,IACT,OAAO,KAAK;AAAA,IACZ,SAAS,KAAK,QAAQ,IAAI,CAAC,OAAgC;AAAA,MAC1D,OAAO,EAAE;AAAA,MACT,SAAS;AAAA,QACR,MAAM;AAAA,QACN,SAAU,EAAE,SAAqC;AAAA,QAGjD,YAAa,EAAE,SAAqC;AAAA,MAGrD;AAAA,MACA,cAAc,EAAE;AAAA,IACjB,EAAE;AAAA,IACF,OAAO;AAAA,MACN,cAAc,KAAK,MAAM;AAAA,MACzB,kBAAkB,KAAK,MAAM;AAAA,MAC7B,aAAa,KAAK,MAAM;AAAA,IACzB;AAAA,EACD;AACD;AAiBO,SAAS,WACf,QACA,OACiC;AACjC,SAAO;AAAA,IACN,CAAC,OAAO,aAAa,GAAG,mBAAmB;AAC1C,YAAM,WAAW,MAAM;AAAA,QACtB,GAAG,OAAO,WAAW;AAAA,QACrB;AAAA,UACC,QAAQ;AAAA,UACR,SAAS;AAAA,YACR,GAAG,aAAa,MAAM;AAAA,YACtB,eAAe,UAAU,OAAO,SAAS;AAAA,UAC1C;AAAA,UACA,MAAM,KAAK,UAAU;AAAA,YACpB,OAAO,MAAM;AAAA,YACb,UAAU,MAAM;AAAA,YAChB,aAAa,MAAM;AAAA,YACnB,YAAY,MAAM;AAAA,YAClB,OAAO,MAAM;AAAA,YACb,mBAAmB,MAAM;AAAA,YACzB,kBAAkB,MAAM;AAAA,YACxB,MAAM,MAAM;AAAA,YACZ,OAAO,MAAM;AAAA,YACb,aAAa,MAAM;AAAA,YACnB,QAAQ;AAAA,UACT,CAAC;AAAA,QACF;AAAA,MACD;AAEA,UAAI,CAAC,SAAS,IAAI;AACjB,cAAM,QAAQ,MAAM,SAClB,KAAK,EACL,MAAM,OAAO,EAAE,OAAO,EAAE,SAAS,wBAAwB,EAAE,EAAE;AAC/D,cAAM,IAAI,YAAY,OAAO,OAAO,WAAW,yBAAyB;AAAA,UACvE,MAAM;AAAA,QACP,CAAC;AAAA,MACF;AAEA,YAAM,SAAS,SAAS,MAAM,UAAU;AACxC,UAAI,CAAC,QAAQ;AACZ,cAAM,IAAI,YAAY,oBAAoB;AAAA,UACzC,MAAM;AAAA,QACP,CAAC;AAAA,MACF;AAEA,YAAM,UAAU,IAAI,YAAY;AAChC,UAAI,SAAS;AAEb,UAAI;AACH,eAAO,MAAM;AACZ,gBAAM,EAAE,MAAM,MAAM,IAAI,MAAM,OAAO,KAAK;AAC1C,cAAI,KAAM;AAEV,oBAAU,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AAChD,gBAAM,QAAQ,OAAO,MAAM,IAAI;AAC/B,mBAAS,MAAM,IAAI,KAAK;AAExB,qBAAW,QAAQ,OAAO;AACzB,gBAAI,KAAK,WAAW,QAAQ,GAAG;AAC9B,oBAAM,OAAO,KAAK,MAAM,CAAC,EAAE,KAAK;AAChC,kBAAI,SAAS,SAAU;AAEvB,kBAAI;AACH,sBAAM,QAAQ,KAAK,MAAM,IAAI;AAC7B,sBAAM;AAAA,kBACL,IAAI,MAAM,MAAM;AAAA,kBAChB,OAAO,MAAM,SAAS,MAAM;AAAA,kBAC5B,UAAU,MAAM,WAAW,CAAC,GAAG;AAAA,oBAC9B,CAAC,OAAgC;AAAA,sBAChC,OAAO,OAAO,EAAE,UAAU,WAAW,EAAE,QAAQ;AAAA,sBAC/C,OAAO;AAAA,wBACN,MAAO,EAAE,OAAmC;AAAA,wBAG5C,SAAU,EAAE,OACT;AAAA,wBACH,YAAa,EAAE,OACZ;AAAA,sBACJ;AAAA,sBACA,cACE,EAAE,iBACH;AAAA,oBACF;AAAA,kBACD;AAAA,gBACD;AAAA,cACD,QAAQ;AAAA,cAER;AAAA,YACD;AAAA,UACD;AAAA,QACD;AAAA,MACD,UAAE;AACD,eAAO,YAAY;AAAA,MACpB;AAAA,IACD;AAAA,EACD;AACD;AAeA,eAAsB,MACrB,QACA,OACuB;AACvB,QAAM,WAAW,MAAM,MAAM,GAAG,OAAO,WAAW,sBAAsB;AAAA,IACvE,QAAQ;AAAA,IACR,SAAS;AAAA,MACR,GAAG,aAAa,MAAM;AAAA,MACtB,eAAe,UAAU,OAAO,SAAS;AAAA,IAC1C;AAAA,IACA,MAAM,KAAK,UAAU;AAAA,MACpB,OAAO,MAAM;AAAA,MACb,OAAO,MAAM;AAAA,MACb,YAAY,MAAM;AAAA,IACnB,CAAC;AAAA,EACF,CAAC;AAED,MAAI,CAAC,SAAS,IAAI;AACjB,UAAM,QAAQ,MAAM,SAClB,KAAK,EACL,MAAM,OAAO,EAAE,OAAO,EAAE,SAAS,2BAA2B,EAAE,EAAE;AAClE,UAAM,IAAI,YAAY,OAAO,OAAO,WAAW,4BAA4B;AAAA,MAC1E,MAAM;AAAA,IACP,CAAC;AAAA,EACF;AAEA,QAAM,OAAO,MAAM,SAAS,KAAK;AAEjC,SAAO;AAAA,IACN,OAAO,KAAK;AAAA,IACZ,MAAM,KAAK;AAAA,IACX,OAAO;AAAA,MACN,cAAc,KAAK,MAAM;AAAA,MACzB,aAAa,KAAK,MAAM;AAAA,IACzB;AAAA,EACD;AACD;AAcA,eAAsB,SACrB,QACA,OACA,QACA,SACkB;AAClB,QAAM,WAAW,MAAM,KAAK,QAAQ;AAAA,IACnC;AAAA,IACA,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,OAAO,CAAC;AAAA,IAC5C,GAAG;AAAA,EACJ,CAAC;AACD,SAAO,SAAS,QAAQ,CAAC,GAAG,QAAQ,WAAW;AAChD;AAaA,eAAsB,eACrB,QACA,OACkB;AAClB,MAAI,SAAS;AACb,mBAAiB,SAAS,WAAW,QAAQ,KAAK,GAAG;AACpD,cAAU,MAAM,QAAQ,CAAC,GAAG,MAAM,WAAW;AAAA,EAC9C;AACA,SAAO;AACR;;;ACxYA,eAAsB,SAAS,QAAuC;AACrE,SAAO,QAAgB,QAAQ,gBAAgB;AAChD;AAaA,eAAsB,gBACrB,QACA,QAC+B;AAC/B,SAAO,QAA6B,QAAQ,yBAAyB;AAAA,IACpE,OAAO,EAAE,OAAO;AAAA,EACjB,CAAC;AACF;AAkBA,eAAsB,eACrB,QACA,OAC4B;AAC5B,SAAO,QAA0B,QAAQ,qBAAqB;AAAA,IAC7D,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;AAeA,eAAsB,oBACrB,QACA,OAC0B;AAC1B,SAAO,QAAwB,QAAQ,mBAAmB;AAAA,IACzD,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;AAWA,eAAsB,kBACrB,QAC2B;AAC3B,SAAO,QAAyB,QAAQ,kBAAkB;AAC3D;AAUA,eAAsB,gBACrB,QACA,SACyB;AACzB,SAAO,QAAuB,QAAQ,kBAAkB;AAAA,IACvD,OAAO,SAAS,QAAQ,EAAE,OAAO,QAAQ,MAAM,IAAI;AAAA,EACpD,CAAC;AACF;;;ACtGA,IAAM,sBAAsB;AAAA;AAAA,EAE3B,YAAY;AAAA;AAAA,EAEZ,aAAa;AAAA;AAAA,EAEb,YAAY;AAAA;AAAA,EAEZ,QAAQ;AACT;AAMA,SAAS,sBAAsB,SAAyB;AACvD,QAAM,EAAE,aAAa,WAAW,IAAI;AACpC,QAAM,mBAAmB,cAAc,KAAK;AAC5C,QAAM,cAAc,KAAK,IAAI,kBAAkB,UAAU;AAEzD,SAAO,KAAK,OAAO,IAAI;AACxB;AAKA,eAAe,MAAM,IAAY,QAAqC;AACrE,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACvC,QAAI,QAAQ,SAAS;AACpB,aAAO,IAAI,aAAa,kBAAkB,YAAY,CAAC;AACvD;AAAA,IACD;AAEA,UAAM,YAAY,WAAW,SAAS,EAAE;AAExC,YAAQ;AAAA,MACP;AAAA,MACA,MAAM;AACL,qBAAa,SAAS;AACtB,eAAO,IAAI,aAAa,kBAAkB,YAAY,CAAC;AAAA,MACxD;AAAA,MACA,EAAE,MAAM,KAAK;AAAA,IACd;AAAA,EACD,CAAC;AACF;AAKA,SAASA,kBAAiB,OAAyB;AAClD,MAAI,iBAAiB,gBAAgB,MAAM,SAAS,cAAc;AACjE,WAAO;AAAA,EACR;AACA,MAAI,iBAAiB,WAAW;AAC/B,WAAO;AAAA,EACR;AACA,MAAI,iBAAiB,SAAS,YAAY,OAAO;AAChD,UAAM,SAAU,MAA6B;AAC7C,WAAO,UAAU,OAAO,WAAW;AAAA,EACpC;AACA,SAAO;AACR;AA2IA,eAAsB,WACrB,QACA,MACA,SACwB;AACxB,QAAM,EAAE,OAAO,IAAI,WAAW,CAAC;AAG/B,MAAI,QAAQ,SAAS;AACpB,UAAM,IAAI,aAAa,kBAAkB,YAAY;AAAA,EACtD;AAGA,MAAI,gBAAiC;AACrC,MAAI,YAA0B;AAE9B,WAAS,UAAU,GAAG,WAAW,oBAAoB,YAAY,WAAW;AAC3E,QAAI;AACH,sBAAgB,MAAM,MAAM,GAAG,OAAO,WAAW,0BAA0B;AAAA,QAC1E,QAAQ;AAAA,QACR,SAAS,aAAa,MAAM;AAAA,QAC5B,MAAM,KAAK,UAAU;AAAA,UACpB,UAAU,KAAK;AAAA,UACf,aAAa,KAAK;AAAA,UAClB,MAAM,KAAK;AAAA,UACX,MAAM,SAAS;AAAA,UACf,MAAM,SAAS,QAAQ;AAAA,UACvB,QAAQ,SAAS;AAAA,QAClB,CAAC;AAAA,QACD;AAAA,MACD,CAAC;AAED,UAAI,cAAc,IAAI;AACrB;AAAA,MACD;AAGA,UAAI,cAAc,UAAU,OAAO,cAAc,WAAW,KAAK;AAChE,YAAI,UAAU,oBAAoB,YAAY;AAC7C,gBAAM,QAAQ,sBAAsB,OAAO;AAC3C,gBAAM,MAAM,OAAO,MAAM;AACzB;AAAA,QACD;AAAA,MACD;AAGA,YAAM,QAAQ,MAAM,cAClB,KAAK,EACL,MAAM,OAAO,EAAE,SAAS,6BAA6B,EAAE;AACzD,YAAM,IAAI,YAAY,MAAM,WAAW,8BAA8B;AAAA,QACpE,MAAM;AAAA,MACP,CAAC;AAAA,IACF,SAAS,OAAO;AACf,UAAI,iBAAiB,gBAAgB,MAAM,SAAS,cAAc;AACjE,cAAM;AAAA,MACP;AAEA,kBAAY,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAEpE,UAAIA,kBAAiB,KAAK,KAAK,UAAU,oBAAoB,YAAY;AACxE,cAAM,QAAQ,sBAAsB,OAAO;AAC3C,cAAM,MAAM,OAAO,MAAM;AACzB;AAAA,MACD;AAEA,YAAM;AAAA,IACP;AAAA,EACD;AAEA,MAAI,CAAC,eAAe,IAAI;AACvB,UACC,aACA,IAAI,YAAY,4CAA4C;AAAA,MAC3D,MAAM;AAAA,IACP,CAAC;AAAA,EAEH;AAEA,QAAM,EAAE,WAAW,UAAU,IAAI,MAAM,cAAc,KAAK;AAG1D,SAAO,uBAAuB,MAAM,WAAW,WAAW,OAAO;AAClE;AAKA,eAAe,uBACd,MACA,WACA,WACA,SACwB;AACxB,QAAM,EAAE,OAAO,IAAI,WAAW,CAAC;AAC/B,MAAI,YAA0B;AAE9B,WAAS,UAAU,GAAG,WAAW,oBAAoB,YAAY,WAAW;AAC3E,QAAI;AACH,aAAO,MAAM,cAAc,MAAM,WAAW,WAAW,OAAO;AAAA,IAC/D,SAAS,OAAO;AACf,UAAI,iBAAiB,gBAAgB,MAAM,SAAS,cAAc;AACjE,cAAM;AAAA,MACP;AAEA,kBAAY,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,KAAK,CAAC;AAEpE,UAAIA,kBAAiB,KAAK,KAAK,UAAU,oBAAoB,YAAY;AACxE,cAAM,QAAQ,sBAAsB,OAAO;AAC3C,cAAM,MAAM,OAAO,MAAM;AACzB;AAAA,MACD;AAEA,YAAM;AAAA,IACP;AAAA,EACD;AAEA,QAAM,aAAa,IAAI,MAAM,6BAA6B;AAC3D;AAKA,SAAS,cACR,MACA,WACA,WACA,SACwB;AACxB,QAAM,EAAE,QAAQ,WAAW,IAAI,WAAW,CAAC;AAE3C,SAAO,IAAI,QAAsB,CAAC,SAAS,WAAW;AACrD,UAAM,MAAM,IAAI,eAAe;AAG/B,UAAM,cAAc,MAAM;AACzB,UAAI,MAAM;AACV,aAAO,IAAI,aAAa,kBAAkB,YAAY,CAAC;AAAA,IACxD;AAEA,QAAI,QAAQ,SAAS;AACpB,aAAO,IAAI,aAAa,kBAAkB,YAAY,CAAC;AACvD;AAAA,IACD;AAEA,YAAQ,iBAAiB,SAAS,aAAa,EAAE,MAAM,KAAK,CAAC;AAE7D,QAAI,OAAO,iBAAiB,YAAY,CAAC,UAAU;AAClD,UAAI,MAAM,oBAAoB,YAAY;AACzC,mBAAW;AAAA,UACV,QAAQ,MAAM;AAAA,UACd,OAAO,MAAM;AAAA,UACb,UAAU,KAAK,MAAO,MAAM,SAAS,MAAM,QAAS,GAAG;AAAA,QACxD,CAAC;AAAA,MACF;AAAA,IACD,CAAC;AAED,QAAI,iBAAiB,QAAQ,MAAM;AAClC,cAAQ,oBAAoB,SAAS,WAAW;AAEhD,UAAI,IAAI,UAAU,OAAO,IAAI,SAAS,KAAK;AAC1C,gBAAQ;AAAA,UACP,KAAK;AAAA,UACL,UAAU,SAAS,OAAO,GAAG,QAAQ,IAAI,IAAI,KAAK,IAAI,KAAK,KAAK;AAAA,UAChE,aAAa,KAAK;AAAA,UAClB,MAAM,KAAK;AAAA,QACZ,CAAC;AAAA,MACF,OAAO;AACN,cAAM,QAAQ,IAAI,MAAM,6BAA6B,IAAI,MAAM,EAAE;AAGjE,cAAM,SAAS,IAAI;AACnB,eAAO,KAAK;AAAA,MACb;AAAA,IACD,CAAC;AAED,QAAI,iBAAiB,SAAS,MAAM;AACnC,cAAQ,oBAAoB,SAAS,WAAW;AAChD,aAAO,IAAI,UAAU,6BAA6B,CAAC;AAAA,IACpD,CAAC;AAED,QAAI,iBAAiB,SAAS,MAAM;AACnC,cAAQ,oBAAoB,SAAS,WAAW;AAChD,aAAO,IAAI,aAAa,kBAAkB,YAAY,CAAC;AAAA,IACxD,CAAC;AAED,QAAI,KAAK,OAAO,SAAS;AACzB,QAAI,iBAAiB,gBAAgB,KAAK,IAAI;AAC9C,QAAI,KAAK,IAAI;AAAA,EACd,CAAC;AACF;AAWA,eAAsB,aACrB,QACA,MACA,QACA,SACwB;AACxB,SAAO,WAAW,QAAQ,MAAM;AAAA,IAC/B,GAAG;AAAA,IACH,MAAM;AAAA,IACN;AAAA,EACD,CAAC;AACF;AAUA,eAAsB,WAAW,QAAsB,QAA+B;AACrF,QAAM,QAAQ,QAAQ,kBAAkB,MAAM,IAAI,EAAE,QAAQ,SAAS,CAAC;AACvE;AAUA,eAAsB,WAAW,QAAsB,QAAiC;AACvF,QAAM,OAAO,MAAM,QAAyB,QAAQ,kBAAkB,MAAM,IAAI,EAAE,QAAQ,MAAM,CAAC;AACjG,SAAO,KAAK;AACb;AAWA,eAAsB,YAAY,QAAsB,QAAmC;AAC1F,SAAO,QAAkB,QAAQ,kBAAkB,MAAM,IAAI;AAAA,IAC5D,QAAQ;AAAA,EACT,CAAC;AACF;AA4BA,eAAsB,aACrB,QACA,QACA,SAC2B;AAC3B,SAAO,QAAyB,QAAQ,uBAAuB;AAAA,IAC9D,QAAQ;AAAA,IACR,MAAM;AAAA,MACL;AAAA,MACA,GAAG;AAAA,IACJ;AAAA,EACD,CAAC;AACF;;;AC3cA,eAAsB,aACrB,QACA,cACgB;AAChB,QAAM,QAAQ,QAAQ,2BAA2B;AAAA,IAChD,QAAQ;AAAA,IACR,MAAM,EAAE,aAAa;AAAA,EACtB,CAAC;AACF;AAUA,eAAsB,eACrB,QACA,UACgB;AAChB,QAAM,QAAQ,QAAQ,6BAA6B;AAAA,IAClD,QAAQ;AAAA,IACR,MAAM,EAAE,SAAS;AAAA,EAClB,CAAC;AACF;AAcA,eAAsB,SACrB,QACA,QACA,cAC+C;AAC/C,SAAO,QAAQ,QAAQ,uBAAuB;AAAA,IAC7C,QAAQ;AAAA,IACR,MAAM,EAAE,QAAQ,GAAG,aAAa;AAAA,EACjC,CAAC;AACF;AAUA,eAAsB,mBACrB,QACqE;AACrE,SAAO,QAAQ,QAAQ,8BAA8B,EAAE,QAAQ,MAAM,CAAC;AACvE;AAaA,eAAsB,sBACrB,QACA,aACgB;AAChB,QAAM,QAAQ,QAAQ,8BAA8B;AAAA,IACnD,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;;;ACKO,SAAS,sBACf,SAAkC,CAAC,GAC5B;AACP,QAAM;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD,IAAI;AAGJ,OAAK,iBAAiB,QAAQ,CAAC,UAAU;AACxC,QAAI,CAAC,MAAM,MAAM;AAChB,cAAQ,KAAK,8CAA8C;AAC3D;AAAA,IACD;AAEA,QAAI;AACJ,QAAI;AACH,gBAAU,MAAM,KAAK,KAAK;AAAA,IAC3B,QAAQ;AAEP,gBAAU;AAAA,QACT,OAAO;AAAA,QACP,MAAM,MAAM,KAAK,KAAK;AAAA,MACvB;AAAA,IACD;AAGA,UAAM,sBAAsB;AAAA,MAC3B,MAAM,QAAQ;AAAA,MACd,MAAM,QAAQ,QAAQ;AAAA,MACtB,OAAO,QAAQ,SAAS;AAAA,MACxB,OAAO,QAAQ;AAAA,MACf,MAAM;AAAA,QACL,GAAG,QAAQ;AAAA,QACX,KAAK,QAAQ;AAAA,QACb,gBAAgB;AAAA,MACjB;AAAA,MACA,KAAK,QAAQ;AAAA,MACb,oBAAoB,QAAQ,sBAAsB;AAAA,MAClD,SAAS,QAAQ;AAAA,MACjB,QAAQ,QAAQ,UAAU;AAAA,MAC1B,SAAS,QAAQ;AAAA,IAClB;AAEA,UAAM;AAAA,MACL,KAAK,aAAa,iBAAiB,QAAQ,OAAO,mBAAmB;AAAA,IACtE;AAAA,EACD,CAAC;AAGD,OAAK,iBAAiB,qBAAqB,CAAC,UAAU;AACrD,UAAM,aAAa,MAAM;AAEzB,UAAM,OAAO,MAAM,aAAa;AAChC,UAAM,UAAU,MAAM;AACtB,UAAM,MAAM,MAAM;AAGlB,QAAI,uBAAuB,SAAS;AACnC,0BAAoB,OAAO;AAAA,IAC5B;AAGA,QAAI,MAAM,QAAQ;AAEjB,cAAQ,IAAI,+BAA+B,MAAM,MAAM;AAAA,IACxD;AAGA,QAAI,KAAK;AACR,YAAM;AAAA,QACL,KAAK,QACH,SAAS,EAAE,MAAM,UAAU,qBAAqB,KAAK,CAAC,EACtD,KAAK,CAAC,eAAe;AAErB,qBAAW,UAAU,YAAY;AAChC,gBAAI,OAAO,QAAQ,OAAO,WAAW,QAAQ;AAC5C,qBAAO,OAAO,MAAM;AAAA,YACrB;AAAA,UACD;AAEA,iBAAO,KAAK,QAAQ,WAAW,GAAG;AAAA,QACnC,CAAC;AAAA,MACH;AAAA,IACD;AAAA,EACD,CAAC;AAGD,OAAK,iBAAiB,qBAAqB,CAAC,UAAU;AACrD,UAAM,OAAO,MAAM,aAAa;AAChC,UAAM,UAAU,MAAM;AAEtB,QAAI,uBAAuB,SAAS;AACnC,0BAAoB,OAAO;AAAA,IAC5B;AAAA,EACD,CAAC;AAGD,OAAK,iBAAiB,YAAY,CAAC,UAAU;AAC5C,UAAM;AAAA;AAAA,MAEL,KAAK,QAAQ,MAAM;AAAA,IACpB;AAAA,EACD,CAAC;AAED,UAAQ,IAAI,0DAA0D;AACvE;AAqBO,SAAS,0BACf,SAAkC,CAAC,GAC1B;AACT,QAAM,EAAE,cAAc,iBAAiB,eAAe,gBAAgB,IACrE;AAED,SAAO;AAAA;AAAA;AAAA;AAAA,wBAIgB,WAAW;AAAA,yBACV,YAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAuDnC,KAAK;AACP;AAiBA,eAAsB,0BACrB,SAAS,UACmC;AAC5C,MAAI,OAAO,WAAW,YAAa,QAAO;AAC1C,MAAI,EAAE,mBAAmB,YAAY;AACpC,YAAQ,KAAK,wCAAwC;AACrD,WAAO;AAAA,EACR;AAEA,MAAI;AACH,UAAM,eAAe,MAAM,UAAU,cAAc,SAAS,MAAM;AAClE,YAAQ,IAAI,uCAAuC,aAAa,KAAK;AACrE,WAAO;AAAA,EACR,SAAS,OAAO;AACf,YAAQ,MAAM,gDAAgD,KAAK;AACnE,WAAO;AAAA,EACR;AACD;;;AC7NA,eAAsB,YACrB,QACA,OACqB;AACrB,SAAO,QAAmB,QAAQ,kBAAkB;AAAA,IACnD,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;AAWA,eAAsB,OACrB,QACA,OACqB;AACrB,SAAO,QAAmB,QAAQ,SAAS,KAAK,IAAI,EAAE,QAAQ,MAAM,CAAC;AACtE;AAUA,eAAsB,UACrB,QACA,OACmB;AACnB,QAAM,SAAS,MAAM;AAAA,IACpB;AAAA,IACA,SAAS,KAAK;AAAA,IACd;AAAA,MACC,QAAQ;AAAA,IACT;AAAA,EACD;AACA,SAAO,OAAO;AACf;AAUA,eAAsB,SACrB,QACA,SACgD;AAChD,SAAO,QAAQ,QAAQ,SAAS;AAAA,IAC/B,QAAQ;AAAA,IACR,OAAO;AAAA,EACR,CAAC;AACF;AAiBA,eAAsB,WACrB,QACA,OACwB;AACxB,SAAO,QAAsB,QAAQ,cAAc;AAAA,IAClD,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;AAUA,eAAsB,UACrB,QACA,YACmB;AACnB,QAAM,SAAS,MAAM;AAAA,IACpB;AAAA,IACA,cAAc,UAAU;AAAA,IACxB;AAAA,MACC,QAAQ;AAAA,IACT;AAAA,EACD;AACA,SAAO,OAAO;AACf;AAUA,eAAsB,WACrB,QACA,YACmB;AACnB,QAAM,SAAS,MAAM;AAAA,IACpB;AAAA,IACA,cAAc,UAAU;AAAA,IACxB;AAAA,MACC,QAAQ;AAAA,IACT;AAAA,EACD;AACA,SAAO,OAAO;AACf;AAUA,eAAsB,WACrB,QACA,YACmB;AACnB,QAAM,SAAS,MAAM;AAAA,IACpB;AAAA,IACA,cAAc,UAAU;AAAA,IACxB;AAAA,MACC,QAAQ;AAAA,IACT;AAAA,EACD;AACA,SAAO,OAAO;AACf;;;ACjQA,eAAsB,UACrB,QACA,SACA,SACsB;AACtB,QAAM,WAAW,MAAM;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,MACC,QAAQ;AAAA,MACR,MAAM;AAAA,QACL,SAAS;AAAA,UACR,QAAQ,SAAS;AAAA,UACjB,aAAa,SAAS;AAAA,UACtB,YAAY,SAAS;AAAA,QACtB;AAAA,QACA,MAAM,CAAC,OAAO;AAAA,MACf;AAAA,IACD;AAAA,EACD;AAGA,SACC,SAAS,KAAK,OAAO,KAAK;AAAA,IACzB,KAAK;AAAA,IACL,SAAS;AAAA,IACT,QAAQ;AAAA,EACT;AAEF;AAmBA,eAAsB,SACrB,QACA,UACA,SACsC;AACtC,QAAM,WAAW,MAAM;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,MACC,QAAQ;AAAA,MACR,MAAM;AAAA,QACL,SAAS;AAAA,UACR,QAAQ,SAAS;AAAA,UACjB,aAAa,SAAS;AAAA,UACtB,YAAY,SAAS;AAAA,QACtB;AAAA,QACA,MAAM;AAAA,MACP;AAAA,IACD;AAAA,EACD;AAEA,SAAO,SAAS;AACjB;AAmBA,eAAsB,YACrB,QACA,SACsC;AACtC,QAAM,WAAW,MAAM;AAAA,IACtB;AAAA,IACA;AAAA,IACA;AAAA,MACC,QAAQ;AAAA,MACR,MAAM;AAAA,QACL,SAAS;AAAA,UACR,QAAQ,SAAS;AAAA,UACjB,aAAa,SAAS;AAAA,UACtB,YAAY,SAAS;AAAA,QACtB;AAAA;AAAA,MAED;AAAA,IACD;AAAA,EACD;AAEA,SAAO,SAAS;AACjB;AAYA,eAAsB,UACrB,QACA,SACA,SACmB;AACnB,QAAM,OAAO,MAAM,UAAU,QAAQ,SAAS,OAAO;AACrD,SAAO,KAAK;AACb;AAwBA,eAAsB,WACrB,QACA,SACA,SAC8B;AAC9B,QAAM,OAAO,MAAM,UAAU,QAAQ,SAAS,OAAO;AACrD,SAAO,KAAK;AACb;AAcA,eAAsB,eACrB,QACA,SACA,SACyB;AACzB,QAAM,OAAO,MAAM,UAAU,QAAQ,SAAS,OAAO;AACrD,SAAO,KAAK;AACb;;;AChJA,eAAsB,iBACrB,QACyB;AACzB,SAAO,QAAQ,QAAQ,oBAAoB,EAAE,QAAQ,MAAM,CAAC;AAC7D;AAaA,eAAsB,oBACrB,QACA,MACgB;AAChB,SAAO,QAAQ,QAAQ,oBAAoB,EAAE,QAAQ,OAAO,MAAM,KAAK,CAAC;AACzE;AAaA,eAAsB,qBACrB,QACA,SACmC;AACnC,SAAO,QAAQ,QAAQ,wBAAwB;AAAA,IAC9C,QAAQ;AAAA,IACR,OAAO;AAAA,EACR,CAAC;AACF;AAWA,eAAsB,mBACrB,QACA,YAC2B;AAC3B,SAAO,QAAQ,QAAQ,wBAAwB,UAAU,IAAI;AAAA,IAC5D,QAAQ;AAAA,EACT,CAAC;AACF;AAUA,eAAsB,sBACrB,QACA,YACgB;AAChB,SAAO,QAAQ,QAAQ,wBAAwB,UAAU,WAAW;AAAA,IACnE,QAAQ;AAAA,EACT,CAAC;AACF;AAWA,eAAsB,gBACrB,QACA,eACwB;AACxB,SAAO,QAAQ,QAAQ,mBAAmB;AAAA,IACzC,QAAQ;AAAA,IACR,OAAO,gBAAgB,EAAE,cAAc,IAAI;AAAA,EAC5C,CAAC;AACF;;;AC1BA,eAAsB,kBACrB,QACmB;AACnB,SAAO,QAAQ,QAAQ,qBAAqB,EAAE,QAAQ,MAAM,CAAC;AAC9D;AAeA,eAAsB,UACrB,QACA,SACsB;AACtB,QAAM,EAAE,gBAAgB,GAAG,KAAK,IAAI;AACpC,SAAO,QAAQ,QAAQ,eAAe;AAAA,IACrC,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,EACD,CAAC;AACF;AAeA,eAAsB,mBACrB,QACA,SACsB;AACtB,QAAM,EAAE,gBAAgB,GAAG,KAAK,IAAI;AACpC,SAAO,QAAQ,QAAQ,yBAAyB;AAAA,IAC/C,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,EACD,CAAC;AACF;AAeA,eAAsB,gBACrB,QACA,SACsB;AACtB,QAAM,EAAE,gBAAgB,GAAG,KAAK,IAAI;AACpC,SAAO,QAAQ,QAAQ,uBAAuB;AAAA,IAC7C,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,EACD,CAAC;AACF;AAeA,eAAsB,cACrB,QACA,SAC0B;AAC1B,SAAO,QAAQ,QAAQ,mBAAmB,EAAE,QAAQ,QAAQ,MAAM,QAAQ,CAAC;AAC5E;AAaA,eAAsB,oBACrB,QACA,SACiC;AACjC,SAAO,QAAQ,QAAQ,oBAAoB;AAAA,IAC1C,QAAQ;AAAA,IACR,OAAO;AAAA,EACR,CAAC;AACF;AAWA,eAAsB,kBACrB,QACA,SAC0B;AAC1B,SAAO,QAAQ,QAAQ,oBAAoB,OAAO,IAAI,EAAE,QAAQ,MAAM,CAAC;AACxE;AAUA,eAAsB,qBACrB,QACA,SACgB;AAChB,SAAO,QAAQ,QAAQ,oBAAoB,OAAO,WAAW;AAAA,IAC5D,QAAQ;AAAA,EACT,CAAC;AACF;AAUA,eAAsB,gBACrB,QACA,SACA,cAC0B;AAC1B,SAAO,QAAQ,QAAQ,oBAAoB,OAAO,eAAe;AAAA,IAChE,QAAQ;AAAA,IACR,MAAM,EAAE,aAAa;AAAA,EACtB,CAAC;AACF;AAWA,eAAsB,uBACrB,QAC+B;AAC/B,SAAO,QAAQ,QAAQ,0BAA0B,EAAE,QAAQ,MAAM,CAAC;AACnE;;;ACpMA,eAAsB,gBACrB,QACyB;AACzB,SAAO,QAAQ,QAAQ,kBAAkB,EAAE,QAAQ,MAAM,CAAC;AAC3D;AAmCA,eAAsB,WACrB,QACA,aACA,OACA,UACmB;AACnB,SAAO,QAAQ,QAAQ,kBAAkB;AAAA,IACxC,QAAQ;AAAA,IACR,MAAM,EAAE,aAAa,GAAG,OAAO,SAAS;AAAA,EACzC,CAAC;AACF;AAcA,eAAsB,gBACrB,QACA,OACyB;AACzB,SAAO,QAAQ,QAAQ,iBAAiB;AAAA,IACvC,QAAQ;AAAA,IACR,OAAO;AAAA,EACR,CAAC;AACF;AAgBA,eAAsB,YACrB,QACA,OACgB;AAChB,SAAO,QAAQ,QAAQ,gBAAgB,EAAE,QAAQ,QAAQ,MAAM,MAAM,CAAC;AACvE;AAUA,eAAsB,kBACrB,QACA,OACgB;AAChB,SAAO,QAAQ,QAAQ,uBAAuB;AAAA,IAC7C,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;AAUA,eAAsB,wBACrB,QACA,OACgB;AAChB,SAAO,QAAQ,QAAQ,6BAA6B;AAAA,IACnD,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;AAeA,eAAsB,sBACrB,QACA,OACgB;AAChB,SAAO,QAAQ,QAAQ,2BAA2B;AAAA,IACjD,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;AAyBA,eAAsB,kBACrB,QACA,OACgC;AAChC,SAAO,QAAQ,QAAQ,oBAAoB;AAAA,IAC1C,QAAQ;AAAA,IACR,OAAO;AAAA,MACN,QAAQ,MAAM;AAAA,MACd,aAAa,MAAM;AAAA,MACnB,OAAO,MAAM,OAAO,SAAS;AAAA,MAC7B,QAAQ,MAAM,QAAQ,SAAS;AAAA,IAChC;AAAA,EACD,CAAC;AACF;;;ACvOA,eAAsB,kBACrB,QACA,QACwB;AACxB,SAAO,QAAQ,QAAQ,mBAAmB;AAAA,IACzC,QAAQ;AAAA,IACR,OAAO,EAAE,OAAO;AAAA,EACjB,CAAC;AACF;AAWA,eAAsB,iBACrB,QACA,QACyB;AACzB,SAAO,QAAQ,QAAQ,oBAAoB;AAAA,IAC1C,QAAQ;AAAA,IACR,OAAO,EAAE,OAAO;AAAA,EACjB,CAAC;AACF;AAkCA,eAAsB,mBACrB,QACA,OACA,UACwB;AACxB,SAAO,QAAQ,QAAQ,qBAAqB;AAAA,IAC3C,QAAQ;AAAA,IACR,MAAM,EAAE,GAAG,OAAO,SAAS;AAAA,EAC5B,CAAC;AACF;AAcA,eAAsB,uBACrB,QACA,QACA,SAC6B;AAC7B,SAAO,QAAQ,QAAQ,0BAA0B;AAAA,IAChD,QAAQ;AAAA,IACR,OAAO,EAAE,QAAQ,GAAG,QAAQ;AAAA,EAI7B,CAAC;AACF;AAaA,eAAsB,uBACrB,QACA,QACwB;AACxB,SAAO,QAAQ,QAAQ,8BAA8B;AAAA,IACpD,QAAQ;AAAA,IACR,MAAM,EAAE,OAAO;AAAA,EAChB,CAAC;AACF;;;AC+FO,IAAM,0BAA0B;AAAA,EACtC,QAAQ,EAAE,OAAO,WAAW,QAAQ,GAAG;AAAA,EACvC,QAAQ,EAAE,OAAO,WAAW,QAAQ,GAAG;AAAA,EACvC,MAAM,EAAE,OAAO,WAAW,QAAQ,GAAG;AAAA,EACrC,UAAU,EAAE,OAAO,WAAW,QAAQ,IAAI;AAAA,EAC1C,SAAS,EAAE,OAAO,WAAW,QAAQ,IAAI;AAC1C;;;ACxPA,eAAsB,UACrB,QACA,UACA,QACuB;AACvB,SAAO,QAAQ,QAAQ,2BAA2B;AAAA,IACjD,QAAQ;AAAA,IACR,OAAO,EAAE,UAAU,OAAO;AAAA,EAC3B,CAAC;AACF;AAaA,eAAsB,cACrB,QACA,QACyB;AACzB,SAAO,QAAQ,QAAQ,uBAAuB;AAAA,IAC7C,QAAQ;AAAA,IACR,OAAO,EAAE,OAAO;AAAA,EACjB,CAAC;AACF;AA+BA,eAAsB,qBACrB,QACA,OACA,QACA,UACgC;AAChC,QAAM,EAAE,gBAAgB,GAAG,UAAU,IAAI;AACzC,SAAO,QAAQ,QAAQ,8BAA8B;AAAA,IACpD,QAAQ;AAAA,IACR,MAAM,EAAE,GAAG,WAAW,QAAQ,SAAS;AAAA,IACvC;AAAA,EACD,CAAC;AACF;AAaA,eAAsB,cACrB,QACA,UACA,QACqD;AACrD,SAAO,QAAQ,QAAQ,+BAA+B;AAAA,IACrD,QAAQ;AAAA,IACR,MAAM,EAAE,UAAU,OAAO;AAAA,EAC1B,CAAC;AACF;AAiCA,eAAsB,eACrB,QACA,eACA,QACA,SAC6B;AAC7B,SAAO,QAAQ,QAAQ,gCAAgC;AAAA,IACtD,QAAQ;AAAA,IACR,OAAO,EAAE,eAAe,QAAQ,UAAU,QAAW,GAAG,QAAQ;AAAA,EAIjE,CAAC;AACF;AAkCA,eAAsB,YACrB,QACA,OACA,QACA,UAC6B;AAC7B,SAAO,QAAQ,QAAQ,mCAAmC;AAAA,IACzD,QAAQ;AAAA,IACR,MAAM,EAAE,GAAG,OAAO,QAAQ,SAAS;AAAA,EACpC,CAAC;AACF;AAaA,eAAsB,uBACrB,QACA,eACA,QACkD;AAClD,SAAO,QAAQ,QAAQ,iCAAiC;AAAA,IACvD,QAAQ;AAAA,IACR,OAAO,EAAE,eAAe,OAAO;AAAA,EAChC,CAAC;AACF;AA4BA,eAAsB,gBACrB,QACA,QAC6B;AAC7B,SAAO,QAAQ,QAAQ,4BAA4B;AAAA,IAClD,QAAQ;AAAA,IACR,OAAO,EAAE,OAAO;AAAA,EACjB,CAAC;AACF;AAaA,eAAsB,eACrB,QACA,eACA,QACkC;AAClC,SAAO,QAAQ,QAAQ,gCAAgC;AAAA,IACtD,QAAQ;AAAA,IACR,OAAO,EAAE,eAAe,OAAO;AAAA,EAChC,CAAC;AACF;AA0BA,eAAsB,kBACrB,QACA,eACA,QACA,UACkC;AAClC,SAAO,QAAQ,QAAQ,mCAAmC;AAAA,IACzD,QAAQ;AAAA,IACR,MAAM,EAAE,eAAe,QAAQ,SAAS;AAAA,EACzC,CAAC;AACF;AAgCA,eAAsB,6BACrB,QACA,eACA,QACA,QACA,UAC2B;AAC3B,SAAO,QAAQ,QAAQ,qCAAqC;AAAA,IAC3D,QAAQ;AAAA,IACR,MAAM,EAAE,eAAe,QAAQ,QAAQ,SAAS;AAAA,EACjD,CAAC;AACF;AAYA,eAAsB,qBACrB,QACA,QACqE;AACrE,SAAO,QAAQ,QAAQ,mCAAmC;AAAA,IACzD,QAAQ;AAAA,IACR,OAAO,EAAE,OAAO;AAAA,EACjB,CAAC;AACF;;;ACjaA,eAAsB,iBACrB,QAC6C;AAC7C,SAAO,QAA2C,QAAQ,OAAO;AAClE;AAUA,eAAsB,gBACrB,QACA,aAIE;AACF,SAAO,QAGJ,QAAQ,SAAS,WAAW,EAAE;AAClC;AAaA,eAAsB,mBACrB,QACA,OAC0C;AAC1C,SAAO,QAAwC,QAAQ,SAAS;AAAA,IAC/D,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;AAYA,eAAsB,mBACrB,QACA,aACA,OAC0C;AAC1C,SAAO;AAAA,IACN;AAAA,IACA,SAAS,WAAW;AAAA,IACpB;AAAA,MACC,QAAQ;AAAA,MACR,MAAM;AAAA,IACP;AAAA,EACD;AACD;AAYA,eAAsB,mBACrB,QACA,aACgC;AAChC,SAAO,QAA8B,QAAQ,SAAS,WAAW,IAAI;AAAA,IACpE,QAAQ;AAAA,EACT,CAAC;AACF;AAcA,eAAsB,uBACrB,QACA,aAC6C;AAC7C,SAAO;AAAA,IACN;AAAA,IACA,SAAS,WAAW;AAAA,EACrB;AACD;AAeA,eAAsB,yBACrB,QACA,aACA,OACkD;AAClD,SAAO;AAAA,IACN;AAAA,IACA,SAAS,WAAW;AAAA,IACpB;AAAA,MACC,QAAQ;AAAA,MACR,MAAM;AAAA,IACP;AAAA,EACD;AACD;AAYA,eAAsB,6BACrB,QACA,aACA,UACA,MAC0C;AAC1C,SAAO;AAAA,IACN;AAAA,IACA,SAAS,WAAW,YAAY,QAAQ;AAAA,IACxC;AAAA,MACC,QAAQ;AAAA,MACR,MAAM,EAAE,KAAK;AAAA,IACd;AAAA,EACD;AACD;AAYA,eAAsB,yBACrB,QACA,aACA,UACgC;AAChC,SAAO;AAAA,IACN;AAAA,IACA,SAAS,WAAW,YAAY,QAAQ;AAAA,IACxC;AAAA,MACC,QAAQ;AAAA,IACT;AAAA,EACD;AACD;AAUA,eAAsB,kBACrB,QACA,aACgC;AAChC,SAAO,QAA8B,QAAQ,SAAS,WAAW,UAAU;AAAA,IAC1E,QAAQ;AAAA,EACT,CAAC;AACF;AAgBA,eAAsB,2BACrB,QACA,aACqD;AACrD,SAAO;AAAA,IACN;AAAA,IACA,SAAS,WAAW;AAAA,EACrB;AACD;AAUA,eAAsB,6BACrB,QACA,OAC0C;AAC1C,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA;AAAA,MACC,QAAQ;AAAA,MACR,MAAM,EAAE,MAAM;AAAA,IACf;AAAA,EACD;AACD;AAYA,eAAsB,6BACrB,QACA,aACA,cACgC;AAChC,SAAO;AAAA,IACN;AAAA,IACA,SAAS,WAAW,gBAAgB,YAAY;AAAA,IAChD;AAAA,MACC,QAAQ;AAAA,IACT;AAAA,EACD;AACD;AASO,SAAS,QACf,YACA,aACU;AACV,MAAI,CAAC,WAAY,QAAO;AAExB,QAAM,gBAA2B;AAAA,IAChC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AAEA,QAAM,gBAAgB,cAAc,QAAQ,WAAW,IAAI;AAC3D,QAAM,oBAAoB,cAAc,QAAQ,WAAW;AAE3D,SAAO,iBAAiB;AACzB;AAKO,SAAS,iBACf,YACU;AACV,SAAO,QAAQ,YAAY,OAAO;AACnC;AAKO,SAAS,kBACf,YACU;AACV,SAAO,QAAQ,YAAY,OAAO;AACnC;AAKO,SAAS,sBACf,YACU;AACV,SAAO,QAAQ,YAAY,aAAa;AACzC;;;ACtRA,eAAsB,UACrB,QACA,OAC2B;AAC3B,SAAO,QAAyB,QAAQ,gBAAgB;AAAA,IACvD,QAAQ;AAAA,IACR,MAAM;AAAA,MACL,KAAK,MAAM;AAAA,MACX,eAAe,MAAM;AAAA,IACtB;AAAA,EACD,CAAC;AACF;AAqBA,eAAsB,WACrB,QACA,OAC4B;AAC5B,SAAO,QAA0B,QAAQ,oBAAoB;AAAA,IAC5D,QAAQ;AAAA,IACR,MAAM;AAAA,MACL,MAAM,MAAM;AAAA,MACZ,eAAe,MAAM;AAAA,IACtB;AAAA,EACD,CAAC;AACF;AAkBA,eAAsB,eACrB,QACA,QAA6B,CAAC,GACH;AAC3B,SAAO,QAAyB,QAAQ,qBAAqB;AAAA,IAC5D,QAAQ;AAAA,IACR,OAAO,MAAM,gBACV,EAAE,eAAe,MAAM,cAAc,IACrC;AAAA,EACJ,CAAC;AACF;AAgBA,eAAsB,UACrB,QACA,KACmB;AACnB,MAAI;AACH,UAAM,OAAO,MAAM,eAAe,MAAM;AACxC,WAAO,KAAK,KAAK,CAAC,MAAM,EAAE,QAAQ,GAAG;AAAA,EACtC,QAAQ;AACP,WAAO;AAAA,EACR;AACD;AAkBA,eAAsB,cACrB,QACA,eAC4B;AAC5B,QAAM,OAAO,MAAM,eAAe,QAAQ,EAAE,cAAc,CAAC;AAC3D,MAAI,KAAK,WAAW,GAAG;AACtB,WAAO,CAAC;AAAA,EACT;AACA,SAAO,WAAW,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,GAAG,GAAG,cAAc,CAAC;AAC1E;;;ACgDA,eAAsB,cACrB,QACA,OAC+B;AAC/B,SAAO,QAA6B,QAAQ,iBAAiB;AAAA,IAC5D,QAAQ;AAAA,IACR,MAAM;AAAA,MACL,OAAO,MAAM;AAAA,MACb,SAAS,MAAM;AAAA,MACf,WAAW,MAAM,aAAa;AAAA,MAC9B,YAAY,MAAM;AAAA,MAClB,KAAK,MAAM;AAAA,MACX,UAAU,MAAM;AAAA,MAChB,UAAU,MAAM;AAAA,MAChB,MAAM,MAAM;AAAA,MACZ,MAAM,MAAM;AAAA,MACZ,UAAU,MAAM,YAAY;AAAA,MAC5B,eAAe,MAAM,iBAAiB;AAAA,MACtC,gBAAgB,MAAM,kBAAkB;AAAA,IACzC;AAAA,EACD,CAAC;AACF;AA0BA,eAAsB,WACrB,QACA,OAC4B;AAC5B,SAAO,QAA0B,QAAQ,sBAAsB;AAAA,IAC9D,QAAQ;AAAA,IACR,MAAM;AAAA,MACL,WAAW,MAAM;AAAA,MACjB,WAAW,MAAM,aAAa;AAAA,MAC9B,UAAU,MAAM,YAAY;AAAA,MAC5B,eAAe,MAAM,iBAAiB;AAAA,MACtC,gBAAgB,MAAM,kBAAkB;AAAA,IACzC;AAAA,EACD,CAAC;AACF;AA8BA,eAAsB,OACrB,QACA,OAC0B;AAC1B,SAAO,QAAwB,QAAQ,kBAAkB;AAAA,IACxD,QAAQ;AAAA,IACR,MAAM;AAAA,MACL,OAAO,MAAM;AAAA,MACb,WAAW,MAAM,aAAa;AAAA,MAC9B,YAAY,MAAM,cAAc;AAAA,MAChC,OAAO,MAAM,SAAS;AAAA,MACtB,QAAQ,MAAM,UAAU;AAAA,MACxB,eAAe,MAAM;AAAA,MACrB,eAAe,MAAM,iBAAiB;AAAA,MACtC,UAAU,MAAM,YAAY;AAAA,MAC5B,SAAS,MAAM;AAAA,MACf,WAAW,MAAM,aAAa;AAAA,MAC9B,gBAAgB,MAAM,kBAAkB;AAAA,MACxC,YAAY,MAAM,cAAc;AAAA,MAChC,WAAW,MAAM;AAAA,MACjB,QAAQ,MAAM;AAAA,IACf;AAAA,EACD,CAAC;AACF;AAuBA,eAAsB,UACrB,QACA,QAAwB,CAAC,GACC;AAC1B,SAAO,QAAwB,QAAQ,qBAAqB;AAAA,IAC3D,QAAQ;AAAA,IACR,MAAM;AAAA,MACL,WAAW,MAAM,aAAa;AAAA,MAC9B,QAAQ,MAAM,UAAU,CAAC,YAAY,MAAM;AAAA,MAC3C,SAAS,MAAM;AAAA,IAChB;AAAA,EACD,CAAC;AACF;AAqBA,eAAsB,eACrB,QACA,OAC+B;AAC/B,SAAO,QAA6B,QAAQ,kBAAkB;AAAA,IAC7D,QAAQ;AAAA,IACR,MAAM;AAAA,MACL,IAAI,MAAM;AAAA,MACV,YAAY,MAAM;AAAA,MAClB,WAAW,MAAM,aAAa;AAAA,IAC/B;AAAA,EACD,CAAC;AACF;AAuBA,eAAsB,eACrB,QACA,OACgC;AAChC,SAAO,QAA8B,QAAQ,kBAAkB;AAAA,IAC9D,QAAQ;AAAA,IACR,MAAM;AAAA,MACL,OAAO,MAAM;AAAA,MACb,SAAS,MAAM;AAAA,MACf,WAAW,MAAM,aAAa;AAAA,MAC9B,YAAY,MAAM;AAAA,MAClB,KAAK,MAAM;AAAA,MACX,UAAU,MAAM;AAAA,MAChB,UAAU,MAAM;AAAA,MAChB,MAAM,MAAM;AAAA,MACZ,MAAM,MAAM;AAAA,MACZ,UAAU,MAAM,YAAY;AAAA,MAC5B,eAAe,MAAM,iBAAiB;AAAA,MACtC,gBAAgB,MAAM,kBAAkB;AAAA,IACzC;AAAA,EACD,CAAC;AACF;AAgBA,eAAsB,eACrB,QACA,WAC6B;AAC7B,SAAO,QAA2B,QAAQ,oBAAoB;AAAA,IAC7D,QAAQ;AAAA,IACR,MAAM,EAAE,UAAU;AAAA,EACnB,CAAC;AACF;AAoBA,eAAsB,WACrB,QACA,OACgC;AAChC,SAAO,QAA8B,QAAQ,sBAAsB;AAAA,IAClE,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;;;ACtdA,eAAsB,4BACrB,QACkC;AAClC,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA,EAAE,QAAQ,MAAM;AAAA,EACjB;AACD;AAgBA,eAAsB,kBACrB,QAC8B;AAC9B,SAAO,QAA4B,QAAQ,wBAAwB;AAAA,IAClE,QAAQ;AAAA,EACT,CAAC;AACF;;;ACuCA,eAAsB,MACrB,QACA,SAC2B;AAC3B,SAAO,QAAyB,QAAQ,eAAe;AAAA,IACtD,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;AAeA,eAAsB,MACrB,QACA,KACoB;AACpB,QAAM,SAAS,MAAM;AAAA,IACpB;AAAA,IACA,eAAe,mBAAmB,GAAG,CAAC;AAAA,IACtC,EAAE,QAAQ,MAAM;AAAA,EACjB;AACA,SAAO,OAAO;AACf;AAWA,eAAsB,SACrB,QACA,KAC+B;AAC/B,SAAO;AAAA,IACN;AAAA,IACA,kBAAkB,mBAAmB,GAAG,CAAC;AAAA,IACzC,EAAE,QAAQ,SAAS;AAAA,EACpB;AACD;AAUA,eAAsB,SACrB,QACA,KAC+B;AAC/B,SAAO;AAAA,IACN;AAAA,IACA,kBAAkB,mBAAmB,GAAG,CAAC;AAAA,IACzC,EAAE,QAAQ,MAAM;AAAA,EACjB;AACD;AAUA,eAAsB,SACrB,QACA,SAC2B;AAC3B,SAAO,QAAyB,QAAQ,kBAAkB;AAAA,IACzD,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;AAUA,eAAsB,OACrB,QACA,SAC6B;AAC7B,SAAO,QAA2B,QAAQ,gBAAgB;AAAA,IACzD,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;AASA,eAAsB,OACrB,QACA,SAC2B;AAC3B,SAAO,QAAyB,QAAQ,gBAAgB;AAAA,IACvD,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;AAOA,eAAsB,OACrB,QACA,SAC2B;AAC3B,QAAM,SAAS,MAAM;AAAA,IACpB;AAAA,IACA;AAAA,IACA,EAAE,QAAQ,QAAQ,MAAM,QAAQ;AAAA,EACjC;AACA,SAAO,OAAO;AACf;AAcA,eAAsB,OACrB,QACA,SAC6B;AAC7B,SAAO,QAA2B,QAAQ,gBAAgB;AAAA,IACzD,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;AAKA,eAAsB,OACrB,QACA,SACoB;AACpB,QAAM,SAAS,MAAM,QAA6B,QAAQ,gBAAgB;AAAA,IACzE,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACD,SAAO,OAAO;AACf;AAKA,eAAsB,UAEpB,QAAsB,SAA8C;AACrE,QAAM,SAAS,MAAM,QAA6B,QAAQ,mBAAmB;AAAA,IAC5E,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACD,SAAO,OAAO;AACf;AAcA,eAAsB,QACrB,QACA,SAC8B;AAC9B,SAAO,QAA4B,QAAQ,iBAAiB;AAAA,IAC3D,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;AAWA,eAAsB,SACrB,QACA,SACe;AACf,QAAM,SAAS,MAAM,QAAwB,QAAQ,kBAAkB;AAAA,IACtE,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACD,SAAO,OAAO;AACf;AAkBA,eAAsB,OACrB,QACA,SAC6B;AAC7B,SAAO,QAA2B,QAAQ,gBAAgB;AAAA,IACzD,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;AAiBA,eAAsB,SACrB,QACA,SACqD;AACrD,QAAM,SAAS,MAAM,QAElB,QAAQ,kBAAkB;AAAA,IAC5B,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACD,SAAO,OAAO;AACf;AAyBA,eAAsB,YACrB,QACA,SAC6B;AAC7B,SAAO,QAA2B,QAAQ,qBAAqB;AAAA,IAC9D,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;;;ACjYA,eAAsB,aACrB,QACA,SACgC;AAChC,SAAO,QAA8B,QAAQ,sBAAsB;AAAA,IAClE,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;AAiBA,eAAsB,mBACrB,QACA,SACmC;AACnC,QAAM,SAAS,IAAI,gBAAgB;AACnC,SAAO,IAAI,WAAW,QAAQ,OAAO;AACrC,MAAI,QAAQ,UAAU,OAAW,QAAO,IAAI,SAAS,OAAO,QAAQ,KAAK,CAAC;AAC1E,MAAI,QAAQ,UAAU,OAAW,QAAO,IAAI,SAAS,QAAQ,KAAK;AAElE,SAAO;AAAA,IACN;AAAA,IACA,yBAAyB,OAAO,SAAS,CAAC;AAAA,IAC1C,EAAE,QAAQ,MAAM;AAAA,EACjB;AACD;;;ACqBA,eAAsB,cACrB,QACA,SACsB;AACtB,SAAO;AAAA,IACN;AAAA,IACA,uBAAuB,mBAAmB,QAAQ,KAAK,CAAC;AAAA,IACxD;AAAA,MACC,QAAQ;AAAA,MACR,MACC,QAAQ,iBAAiB,SACtB,EAAE,cAAc,QAAQ,aAAa,IACrC;AAAA,IACL;AAAA,EACD;AACD;AAaA,eAAsB,gBACrB,QACA,OACsB;AACtB,SAAO;AAAA,IACN;AAAA,IACA,sBAAsB,mBAAmB,KAAK,CAAC;AAAA,IAC/C,EAAE,QAAQ,MAAM;AAAA,EACjB;AACD;AAWA,eAAsB,iBACrB,QACA,OACiC;AACjC,SAAO;AAAA,IACN;AAAA,IACA,uBAAuB,mBAAmB,KAAK,CAAC;AAAA,IAChD,EAAE,QAAQ,MAAM;AAAA,EACjB;AACD;AAaA,eAAsB,eACrB,QACA,SACsB;AACtB,SAAO;AAAA,IACN;AAAA,IACA,wBAAwB,mBAAmB,QAAQ,KAAK,CAAC;AAAA,IACzD;AAAA,MACC,QAAQ;AAAA,MACR,MAAM,EAAE,cAAc,QAAQ,aAAa;AAAA,IAC5C;AAAA,EACD;AACD;AAgBA,eAAsB,mBACrB,QACA,OACmC;AACnC,SAAO;AAAA,IACN;AAAA,IACA,oBAAoB,mBAAmB,KAAK,CAAC;AAAA,IAC7C,EAAE,QAAQ,MAAM;AAAA,EACjB;AACD;AAgBA,eAAsB,YACrB,QACA,OACoB;AACpB,QAAM,SAAS,MAAM;AAAA,IACpB;AAAA,IACA,uBAAuB,mBAAmB,KAAK,CAAC;AAAA,IAChD,EAAE,QAAQ,MAAM;AAAA,EACjB;AACA,SAAO,OAAO;AACf;AAcA,eAAsB,UACrB,QACA,OACA,SACkB;AAClB,SAAO;AAAA,IACN;AAAA,IACA,uBAAuB,mBAAmB,KAAK,CAAC;AAAA,IAChD;AAAA,MACC,QAAQ;AAAA,MACR,MAAM;AAAA,IACP;AAAA,EACD;AACD;AAUA,eAAsB,aACrB,QACA,OACA,KACgC;AAChC,SAAO;AAAA,IACN;AAAA,IACA,uBAAuB,mBAAmB,KAAK,CAAC,IAAI,mBAAmB,GAAG,CAAC;AAAA,IAC3E,EAAE,QAAQ,SAAS;AAAA,EACpB;AACD;AAcA,eAAsB,kBACrB,QACA,OAC0B;AAC1B,QAAM,SAAS,MAAM;AAAA,IACpB;AAAA,IACA,uBAAuB,mBAAmB,KAAK,CAAC;AAAA,IAChD,EAAE,QAAQ,MAAM;AAAA,EACjB;AACA,SAAO,OAAO;AACf;AAeA,eAAsB,gBACrB,QACA,OACA,SACwB;AACxB,SAAO;AAAA,IACN;AAAA,IACA,uBAAuB,mBAAmB,KAAK,CAAC;AAAA,IAChD;AAAA,MACC,QAAQ;AAAA,MACR,MAAM;AAAA,IACP;AAAA,EACD;AACD;AAUA,eAAsB,mBACrB,QACA,OACA,QACgC;AAChC,SAAO;AAAA,IACN;AAAA,IACA,uBAAuB,mBAAmB,KAAK,CAAC,IAAI,mBAAmB,MAAM,CAAC;AAAA,IAC9E,EAAE,QAAQ,SAAS;AAAA,EACpB;AACD;;;ACtQA,SAAS,sBAAsB,OAA8B;AAC5D,QAAM,SAA2B,CAAC;AAElC,MAAI,MAAM,OAAO;AAEhB,UAAM,QAAQ,MAAM,MAAM,MAAM,IAAI,EAAE,MAAM,CAAC;AAC7C,eAAW,QAAQ,OAAO;AAEzB,YAAM,UAAU,KAAK,MAAM,wCAAwC;AACnE,UAAI,SAAS;AACZ,eAAO,KAAK;AAAA,UACX,UAAU,QAAQ,CAAC;AAAA,UACnB,UAAU,QAAQ,CAAC;AAAA,UACnB,QAAQ,OAAO,QAAQ,CAAC,CAAC;AAAA,UACzB,OAAO,OAAO,QAAQ,CAAC,CAAC;AAAA,QACzB,CAAC;AACD;AAAA,MACD;AAEA,YAAM,cAAc,KAAK,MAAM,4BAA4B;AAC3D,UAAI,aAAa;AAChB,eAAO,KAAK;AAAA,UACX,UAAU,YAAY,CAAC;AAAA,UACvB,QAAQ,OAAO,YAAY,CAAC,CAAC;AAAA,UAC7B,OAAO,OAAO,YAAY,CAAC,CAAC;AAAA,QAC7B,CAAC;AAAA,MACF;AAAA,IACD;AAAA,EACD;AAEA,SAAO;AAAA,IACN,MAAM,MAAM,QAAQ;AAAA,IACpB,OAAO,MAAM;AAAA,IACb,YAAY,OAAO,SAAS,IAAI,EAAE,OAAO,IAAI;AAAA,EAC9C;AACD;AAyBA,eAAsB,iBACrB,QACA,OACA,UAAsD,CAAC,GACzB;AAC9B,QAAM,iBAAiB,sBAAsB,KAAK;AAClD,QAAM,UAAmC;AAAA,IACxC,GAAG;AAAA,IACH,WAAW,EAAE,QAAQ,CAAC,cAAc,EAAE;AAAA,EACvC;AACA,SAAO,QAA4B,QAAQ,6BAA6B;AAAA,IACvE,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;AAQA,eAAsB,oBACrB,QACA,SAC8B;AAC9B,SAAO,QAA4B,QAAQ,6BAA6B;AAAA,IACvE,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;AAuBA,eAAsB,eACrB,QACA,SACA,UAAkD,CAAC,GACrB;AAC9B,QAAM,UAAiC,EAAE,GAAG,SAAS,QAAQ;AAC7D,SAAO,QAA4B,QAAQ,2BAA2B;AAAA,IACrE,QAAQ;AAAA,IACR,MAAM;AAAA,EACP,CAAC;AACF;","names":["isRetryableError"]}
1
+ {"version":3,"sources":["../src/constants.ts","../src/errors.ts","../src/key-validation.ts","../src/config.ts","../src/debug.ts","../src/rest-client.ts","../src/auth.ts","../src/analytics.ts","../src/ai.ts","../src/billing.ts","../src/storage.ts","../src/notifications.ts","../src/lib/notifications/service-worker.ts","../src/jobs.ts","../src/flags.ts","../src/webhooks.ts","../src/email.ts","../src/consent.ts","../src/referrals.ts","../src/lib/engagement/types.ts","../src/engagement.ts","../src/orgs.ts","../src/secrets.ts","../src/search.ts","../src/database.ts","../src/kv.ts","../src/realtime.ts","../src/deploy.ts","../src/monitoring.ts"],"sourcesContent":["/**\n * SDK Constants — Single Source of Truth\n *\n * Shared constants used across the SDK. Centralizing these\n * prevents magic number duplication and makes changes easier.\n *\n * IMPORTANT: All time-based constants should be used consistently\n * across the SDK. Never hardcode magic numbers like 30000, 5 * 60 * 1000, etc.\n */\n\n// =============================================================================\n// API Configuration\n// =============================================================================\n\n/** Default platform URL */\nexport const DEFAULT_PLATFORM_URL = 'https://sylphx.com'\n\n/**\n * Canonical environment variable name for the platform URL.\n *\n * SDK modules MUST read from this env var (with SYLPHX_URL as legacy fallback).\n * Centralizing the name prevents the same env var being spelled differently\n * across kv.ts, streams.ts, ai.ts, middleware.ts, and server.ts.\n */\nexport const ENV_PLATFORM_URL = 'SYLPHX_PLATFORM_URL'\n\n/**\n * Legacy environment variable name for the platform URL.\n *\n * Supported as a fallback for older deployments that set SYLPHX_URL.\n * New projects should use SYLPHX_PLATFORM_URL.\n */\nexport const ENV_PLATFORM_URL_LEGACY = 'SYLPHX_URL'\n\n/**\n * Canonical environment variable name for the secret key.\n *\n * All server-side SDK modules read from this env var by default.\n */\nexport const ENV_SECRET_KEY = 'SYLPHX_SECRET_KEY'\n\n/**\n * Resolve the platform URL from environment variables.\n *\n * Priority: explicit value > SYLPHX_PLATFORM_URL > SYLPHX_URL (legacy) > default\n */\nexport function resolvePlatformUrl(explicit?: string): string {\n\treturn (\n\t\texplicit ||\n\t\tprocess.env[ENV_PLATFORM_URL] ||\n\t\tprocess.env[ENV_PLATFORM_URL_LEGACY] ||\n\t\tDEFAULT_PLATFORM_URL\n\t).trim()\n}\n\n/**\n * Resolve the secret key from environment variables.\n *\n * Returns the raw value before validation. Callers should pass the result\n * through `validateAndSanitizeSecretKey()`.\n */\nexport function resolveSecretKey(explicit?: string): string | undefined {\n\treturn explicit || process.env[ENV_SECRET_KEY]\n}\n\n/** @deprecated No longer used for path construction. Use SDK_API_PATH directly. */\nexport const SDK_API_VERSION = 'v1'\n\n/**\n * SDK API base path — served from the unified platform Hono app.\n *\n * Used when `platformUrl` is set (e.g. `https://sylphx.com`).\n * BaaS routes are mounted at /api/v1/projects/:slug/* on the platform app.\n *\n * @deprecated Prefer project `ref`-based URL: https://{ref}.api.sylphx.com/v1\n */\nexport const SDK_API_PATH = `/api/v1`\n\n/**\n * SDK API path for new subdomain-based SDK server.\n *\n * Used when `ref` is provided to `createConfig`.\n * The full base becomes: https://{ref}.api.sylphx.com + SDK_API_PATH_NEW\n */\nexport const SDK_API_PATH_NEW = `/v1`\n\n/**\n * Default SDK API host for new subdomain-based SDK server.\n * The full URL is built as: https://{ref}.api.sylphx.com/v1\n */\nexport const DEFAULT_SDK_API_HOST = 'api.sylphx.com'\n\n/**\n * Default auth route prefix\n *\n * Used for OAuth callbacks and signout routes.\n * Must match the middleware's authPrefix config.\n */\nexport const DEFAULT_AUTH_PREFIX = '/auth'\n\n/**\n * SDK package version\n *\n * Sent in X-SDK-Version header for debugging and analytics.\n * Update this when releasing new SDK versions.\n */\nexport const SDK_VERSION = '0.1.0'\n\n/**\n * SDK platform identifier\n *\n * Sent in X-SDK-Platform header to identify the runtime environment.\n */\nexport const SDK_PLATFORM =\n\ttypeof window !== 'undefined'\n\t\t? 'browser'\n\t\t: typeof process !== 'undefined' && process.versions?.node\n\t\t\t? 'node'\n\t\t\t: 'unknown'\n\n// =============================================================================\n// Timeouts & Durations\n// =============================================================================\n\n/** Default request timeout in milliseconds (30 seconds) */\nexport const DEFAULT_TIMEOUT_MS = 30_000\n\n/**\n * Token expiry buffer in milliseconds (30 seconds)\n *\n * Refresh tokens this many milliseconds BEFORE they expire\n * to account for network latency and clock skew.\n */\nexport const TOKEN_EXPIRY_BUFFER_MS = 30_000\n\n/**\n * Session token lifetime in seconds (5 minutes)\n *\n * Matches Clerk's short-lived access token pattern.\n * Used for cookie maxAge and React Query staleTime.\n */\nexport const SESSION_TOKEN_LIFETIME_SECONDS = 5 * 60\n\n/** Session token lifetime in milliseconds (for React Query staleTime) */\nexport const SESSION_TOKEN_LIFETIME_MS = SESSION_TOKEN_LIFETIME_SECONDS * 1000\n\n/**\n * Refresh token lifetime in seconds (30 days)\n *\n * Long-lived token for silent refresh.\n */\nexport const REFRESH_TOKEN_LIFETIME_SECONDS = 30 * 24 * 60 * 60\n\n// =============================================================================\n// Feature Flags Cache\n// =============================================================================\n\n/**\n * Feature flags cache TTL in milliseconds (5 minutes)\n *\n * How long to cache flags before fetching fresh values.\n * Matches LaunchDarkly's default streaming connection behavior.\n */\nexport const FLAGS_CACHE_TTL_MS = 5 * 60 * 1000\n\n/**\n * Feature flags stale-while-revalidate window in milliseconds (1 minute)\n *\n * Allow serving stale flags while fetching fresh values.\n */\nexport const FLAGS_STALE_WHILE_REVALIDATE_MS = 60 * 1000\n\n// =============================================================================\n// Retry & Backoff\n// =============================================================================\n\n/** Maximum retry delay for exponential backoff (30 seconds) */\nexport const MAX_RETRY_DELAY_MS = 30_000\n\n/** Base retry delay for exponential backoff (1 second) */\nexport const BASE_RETRY_DELAY_MS = 1_000\n\n/** Maximum number of retries for network requests */\nexport const MAX_RETRIES = 3\n\n// =============================================================================\n// Analytics\n// =============================================================================\n\n/**\n * Analytics session timeout in milliseconds (30 minutes)\n *\n * After this much inactivity, a new session is started.\n */\nexport const ANALYTICS_SESSION_TIMEOUT_MS = 30 * 60 * 1000\n\n// =============================================================================\n// Webhooks\n// =============================================================================\n\n/**\n * Maximum age for webhook signature validation (5 minutes)\n *\n * Reject webhooks with timestamps older than this.\n */\nexport const WEBHOOK_MAX_AGE_MS = 5 * 60 * 1000\n\n/**\n * Clock skew allowance for webhook validation (30 seconds)\n *\n * Allow timestamps this far in the future.\n */\nexport const WEBHOOK_CLOCK_SKEW_MS = 30 * 1000\n\n// =============================================================================\n// PKCE (OAuth)\n// =============================================================================\n\n/**\n * PKCE code verifier TTL in milliseconds (10 minutes)\n *\n * How long the code verifier is stored during OAuth flow.\n */\nexport const PKCE_CODE_TTL_MS = 10 * 60 * 1000\n\n// =============================================================================\n// Jobs\n// =============================================================================\n\n/**\n * Job dead-letter queue retention in milliseconds (7 days)\n */\nexport const JOBS_DLQ_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000\n\n// =============================================================================\n// Session Replay\n// =============================================================================\n\n/**\n * Maximum session replay recording duration in milliseconds (60 minutes)\n */\nexport const SESSION_REPLAY_MAX_DURATION_MS = 60 * 60 * 1000\n\n/**\n * Session replay upload interval in milliseconds (5 seconds)\n */\nexport const SESSION_REPLAY_UPLOAD_INTERVAL_MS = 5_000\n\n/**\n * Session replay scroll event throttle interval (150 ms)\n */\nexport const SESSION_REPLAY_SCROLL_THROTTLE_MS = 150\n\n/**\n * Session replay media time update throttle interval (800 ms)\n */\nexport const SESSION_REPLAY_MEDIA_THROTTLE_MS = 800\n\n/**\n * Session replay rage click detection window (1 second)\n */\nexport const SESSION_REPLAY_RAGE_CLICK_WINDOW_MS = 1_000\n\n/**\n * Session replay dead click detection timeout (500 ms)\n */\nexport const SESSION_REPLAY_DEAD_CLICK_TIMEOUT_MS = 500\n\n/**\n * Session replay scroll heat detection window (2 seconds)\n */\nexport const SESSION_REPLAY_SCROLL_HEAT_WINDOW_MS = 2_000\n\n/**\n * Session replay status check interval (5 seconds)\n */\nexport const SESSION_REPLAY_STATUS_CHECK_MS = 5_000\n\n// =============================================================================\n// Analytics (Extended)\n// =============================================================================\n\n/**\n * Analytics event flush interval in milliseconds (5 seconds)\n */\nexport const ANALYTICS_FLUSH_INTERVAL_MS = 5_000\n\n/**\n * Analytics maximum text length for autocapture (100 characters)\n */\nexport const ANALYTICS_MAX_TEXT_LENGTH = 100\n\n/**\n * Analytics flush timeout in milliseconds (1 second)\n */\nexport const ANALYTICS_FLUSH_TIMEOUT_MS = 1_000\n\n/**\n * Analytics interval check in milliseconds (1 second)\n */\nexport const ANALYTICS_INTERVAL_CHECK_MS = 1_000\n\n/**\n * Analytics retry base delay in milliseconds (1 second)\n * Exponential backoff: delay = base * 2^retries (with jitter)\n */\nexport const ANALYTICS_RETRY_BASE_DELAY_MS = 1_000\n\n/**\n * Analytics retry max delay in milliseconds (30 seconds)\n */\nexport const ANALYTICS_RETRY_MAX_DELAY_MS = 30_000\n\n/**\n * Analytics retry jitter factor (±20%)\n * Prevents thundering herd when multiple clients retry simultaneously\n */\nexport const ANALYTICS_RETRY_JITTER = 0.2\n\n/**\n * Analytics maximum retries before dropping event (Segment pattern: 10)\n */\nexport const ANALYTICS_MAX_RETRIES = 10\n\n// =============================================================================\n// Feature Flags (Extended)\n// =============================================================================\n\n/**\n * Feature flags exposure deduplication window (1 hour)\n *\n * Prevents duplicate exposure events for A/B tests within this window.\n */\nexport const FLAGS_EXPOSURE_DEDUPE_WINDOW_MS = 60 * 60 * 1000\n\n/**\n * Flag stream initial reconnection delay (1 second)\n */\nexport const FLAGS_STREAM_INITIAL_RECONNECT_MS = 1_000\n\n/**\n * Flag stream maximum reconnection delay (30 seconds)\n */\nexport const FLAGS_STREAM_MAX_RECONNECT_MS = 30_000\n\n/**\n * Flag stream heartbeat timeout (45 seconds)\n */\nexport const FLAGS_STREAM_HEARTBEAT_TIMEOUT_MS = 45_000\n\n/**\n * Flag HTTP polling interval fallback (60 seconds)\n */\nexport const FLAGS_HTTP_POLLING_INTERVAL_MS = 60_000\n\n// =============================================================================\n// Jobs (Extended)\n// =============================================================================\n\n/**\n * Default retry delay sequence for exponential backoff (ms)\n */\nexport const DEFAULT_RETRY_DELAYS_MS = [1_000, 5_000, 15_000, 30_000, 60_000] as const\n\n/**\n * Default job timeout in milliseconds (60 seconds)\n */\nexport const JOB_DEFAULT_TIMEOUT_MS = 60_000\n\n/**\n * Default job status polling interval (2 seconds)\n */\nexport const JOB_POLL_INTERVAL_MS = 2_000\n\n// =============================================================================\n// Storage Keys & Prefixes\n// =============================================================================\n\n/**\n * Storage key prefix for SDK data\n */\nexport const STORAGE_KEY_PREFIX = 'sylphx_'\n\n/**\n * localStorage key for cached feature flags\n */\nexport const FLAGS_CACHE_KEY = 'sylphx_feature_flags'\n\n/**\n * localStorage key for feature flags cache timestamp\n */\nexport const FLAGS_CACHE_TIMESTAMP_KEY = 'sylphx_feature_flags_ts'\n\n/**\n * localStorage key for feature flags overrides\n */\nexport const FLAGS_OVERRIDES_KEY = 'sylphx_feature_flags_overrides'\n\n/**\n * localStorage key for active organization\n */\nexport const ORG_STORAGE_KEY = 'sylphx_active_org'\n\n/**\n * BroadcastChannel name for cross-tab org sync\n */\nexport const ORG_BROADCAST_CHANNEL = 'sylphx_org_sync'\n\n/**\n * Storage prefix for PKCE verifiers\n */\nexport const PKCE_STORAGE_PREFIX = 'sylphx_pkce_'\n\n/**\n * Test key for checking storage availability\n */\nexport const STORAGE_TEST_KEY = '__sylphx_test__'\n\n/**\n * Cookie/storage name for analytics sessions\n */\nexport const ANALYTICS_SESSION_KEY = 'sylphx_session'\n\n/**\n * Default storage key for flags persistence\n */\nexport const FLAGS_STORAGE_KEY = 'sylphx_flags'\n\n// =============================================================================\n// Click ID & Attribution\n// =============================================================================\n\n/**\n * Click ID attribution window in milliseconds (90 days)\n *\n * How long click IDs are stored for conversion attribution.\n */\nexport const CLICK_ID_EXPIRY_MS = 90 * 24 * 60 * 60 * 1000\n\n// =============================================================================\n// React Query Stale Times\n// =============================================================================\n\n/**\n * React Query staleTime for frequently-changing data (1 minute)\n *\n * Use for: real-time metrics, live feeds, active sessions\n */\nexport const STALE_TIME_FREQUENT_MS = 60 * 1_000\n\n/**\n * React Query staleTime for moderately-changing data (2 minutes)\n *\n * Use for: subscriptions, user profiles, preferences\n */\nexport const STALE_TIME_MODERATE_MS = 2 * 60 * 1_000\n\n/**\n * React Query staleTime for stable/config data (5 minutes)\n *\n * Use for: plans, feature flags, app config\n */\nexport const STALE_TIME_STABLE_MS = 5 * 60 * 1_000\n\n/**\n * React Query staleTime for webhook stats (30 seconds)\n */\nexport const STALE_TIME_STATS_MS = 30 * 1_000\n\n// =============================================================================\n// UI Component Timeouts\n// =============================================================================\n\n/**\n * Copy-to-clipboard feedback display duration (2 seconds)\n */\nexport const UI_COPY_FEEDBACK_MS = 2_000\n\n/**\n * Form success message display duration (3 seconds)\n */\nexport const UI_FORM_SUCCESS_MS = 3_000\n\n/**\n * General notification display duration (5 seconds)\n */\nexport const UI_NOTIFICATION_MS = 5_000\n\n/**\n * Prompt auto-show delay (3 seconds)\n */\nexport const UI_PROMPT_DELAY_MS = 3_000\n\n/**\n * Redirect delay after action (3 seconds)\n */\nexport const UI_REDIRECT_DELAY_MS = 3_000\n\n/**\n * Animation out duration (200 ms)\n */\nexport const UI_ANIMATION_OUT_MS = 200\n\n/**\n * Animation in duration (300 ms)\n */\nexport const UI_ANIMATION_IN_MS = 300\n\n// =============================================================================\n// Email & Verification\n// =============================================================================\n\n/**\n * Email resend cooldown tick interval (1 second)\n */\nexport const EMAIL_RESEND_COOLDOWN_TICK_MS = 1_000\n\n/**\n * New user detection threshold (1 minute)\n *\n * Users created within this window are considered \"new\" for signup tracking.\n */\nexport const NEW_USER_THRESHOLD_MS = 60 * 1_000\n\n// =============================================================================\n// Web Vitals Thresholds\n// =============================================================================\n\n/**\n * FCP (First Contentful Paint) \"good\" threshold (1800 ms)\n */\nexport const WEB_VITALS_FCP_GOOD_MS = 1_800\n\n/**\n * FCP (First Contentful Paint) \"poor\" threshold (3000 ms)\n */\nexport const WEB_VITALS_FCP_POOR_MS = 3_000\n\n// =============================================================================\n// Storage Sizes\n// =============================================================================\n\n/**\n * Multipart upload threshold (5 MB)\n *\n * Files larger than this use multipart upload for better reliability.\n */\nexport const STORAGE_MULTIPART_THRESHOLD_BYTES = 5 * 1024 * 1024\n\n/**\n * Default max file size for uploads (5 MB)\n */\nexport const STORAGE_DEFAULT_MAX_SIZE_BYTES = 5 * 1024 * 1024\n\n/**\n * Avatar max file size (2 MB)\n */\nexport const STORAGE_AVATAR_MAX_SIZE_BYTES = 2 * 1024 * 1024\n\n/**\n * Large file max size for file uploads (10 MB)\n */\nexport const STORAGE_LARGE_MAX_SIZE_BYTES = 10 * 1024 * 1024\n\n// =============================================================================\n// Cache TTLs\n// =============================================================================\n\n/**\n * JWK cache TTL (1 hour)\n */\nexport const JWK_CACHE_TTL_MS = 60 * 60 * 1000\n\n// =============================================================================\n// Analytics Event Tracking\n// =============================================================================\n\n/**\n * Max tracked event IDs to keep in memory\n */\nexport const ANALYTICS_MAX_TRACKED_EVENT_IDS = 1000\n\n/**\n * Number of event IDs to keep after cleanup\n */\nexport const ANALYTICS_TRACKED_IDS_KEEP = 500\n\n/**\n * Analytics queue limit before force flush\n */\nexport const ANALYTICS_QUEUE_LIMIT = 100\n\n// =============================================================================\n// Session Replay (Extended)\n// =============================================================================\n\n/**\n * Session replay check interval (1 second)\n */\nexport const SESSION_REPLAY_CHECK_INTERVAL_MS = 1_000\n\n/**\n * Success feedback delay for invite/account actions (1.5 seconds)\n */\nexport const UI_SUCCESS_REDIRECT_MS = 1_500\n\n// =============================================================================\n// String Truncation Limits\n// =============================================================================\n\n/**\n * Max message length for logging (1000 chars)\n */\nexport const LOG_MESSAGE_MAX_LENGTH = 1_000\n\n/**\n * Max DOM snapshot length for debugging (1000 chars)\n */\nexport const DOM_SNAPSHOT_MAX_LENGTH = 1_000\n\n/**\n * Max stack trace length for error tracking (500 chars)\n */\nexport const STACK_TRACE_MAX_LENGTH = 500\n\n/**\n * Google Consent Mode wait for update timeout (500 ms)\n */\nexport const CONSENT_WAIT_FOR_UPDATE_MS = 500\n\n// =============================================================================\n// Time Unit Conversions\n// =============================================================================\n\n/** Milliseconds per minute (60,000) */\nexport const MS_PER_MINUTE = 60_000\n\n/** Milliseconds per hour (3,600,000) */\nexport const MS_PER_HOUR = 3_600_000\n\n/** Milliseconds per day (86,400,000) */\nexport const MS_PER_DAY = 86_400_000\n\n/** Seconds per minute (60) */\nexport const SECONDS_PER_MINUTE = 60\n\n/** Seconds per hour (3,600) */\nexport const SECONDS_PER_HOUR = 3_600\n\n// =============================================================================\n// Z-Index Values\n// =============================================================================\n\n/** Z-index for modal overlays (9999) */\nexport const Z_INDEX_OVERLAY = 9999\n\n/** Z-index for critical overlays like feature gates (99999) */\nexport const Z_INDEX_CRITICAL_OVERLAY = 99999\n\n// =============================================================================\n// API Key Expiry (seconds)\n// =============================================================================\n\n/** API key expiry: 1 day (86,400 seconds) */\nexport const API_KEY_EXPIRY_1_DAY = 86_400\n\n/** API key expiry: 7 days (604,800 seconds) */\nexport const API_KEY_EXPIRY_7_DAYS = 604_800\n\n/** API key expiry: 30 days (2,592,000 seconds) */\nexport const API_KEY_EXPIRY_30_DAYS = 2_592_000\n\n/** API key expiry: 90 days (7,776,000 seconds) */\nexport const API_KEY_EXPIRY_90_DAYS = 7_776_000\n\n/** API key expiry: 1 year (31,536,000 seconds) */\nexport const API_KEY_EXPIRY_1_YEAR = 31_536_000\n\n// =============================================================================\n// Web Vitals Thresholds (Google standards)\n// =============================================================================\n\n/** LCP (Largest Contentful Paint) \"good\" threshold (2500 ms) */\nexport const WEB_VITALS_LCP_GOOD_MS = 2_500\n\n/** LCP (Largest Contentful Paint) \"poor\" threshold (4000 ms) */\nexport const WEB_VITALS_LCP_POOR_MS = 4_000\n\n/** INP (Interaction to Next Paint) \"good\" threshold (200 ms) */\nexport const WEB_VITALS_INP_GOOD_MS = 200\n\n/** INP (Interaction to Next Paint) \"poor\" threshold (500 ms) */\nexport const WEB_VITALS_INP_POOR_MS = 500\n\n/** TTFB (Time to First Byte) \"good\" threshold (800 ms) */\nexport const WEB_VITALS_TTFB_GOOD_MS = 800\n\n/** TTFB (Time to First Byte) \"poor\" threshold (1800 ms) */\nexport const WEB_VITALS_TTFB_POOR_MS = 1_800\n\n// =============================================================================\n// Security\n// =============================================================================\n\n/** Minimum password length (NIST SP 800-63B recommends 12+) */\nexport const MIN_PASSWORD_LENGTH = 12\n\n// =============================================================================\n// AI\n// =============================================================================\n\n/** Default context window for AI models (4096 tokens) */\nexport const DEFAULT_CONTEXT_WINDOW = 4_096\n\n// =============================================================================\n// Circuit Breaker (AWS/Resilience4j pattern)\n// =============================================================================\n\n/**\n * Circuit breaker failure threshold\n *\n * Number of failures in the window before circuit opens.\n */\nexport const CIRCUIT_BREAKER_FAILURE_THRESHOLD = 5\n\n/**\n * Circuit breaker failure window in milliseconds (10 seconds)\n *\n * Time window for counting failures.\n */\nexport const CIRCUIT_BREAKER_WINDOW_MS = 10_000\n\n/**\n * Circuit breaker open duration in milliseconds (30 seconds)\n *\n * How long the circuit stays open before allowing a test request.\n */\nexport const CIRCUIT_BREAKER_OPEN_DURATION_MS = 30_000\n\n// =============================================================================\n// ETag Cache (HTTP conditional requests)\n// =============================================================================\n\n/**\n * Maximum ETag cache entries\n *\n * LRU eviction when exceeded.\n */\nexport const ETAG_CACHE_MAX_ENTRIES = 100\n\n/**\n * ETag cache TTL in milliseconds (5 minutes)\n *\n * How long cached responses are valid.\n */\nexport const ETAG_CACHE_TTL_MS = 5 * 60 * 1000\n","/**\n * Sylphx SDK Error Classes\n *\n * Typed error classes for better error handling and debugging.\n * Compatible with tRPC error codes and provides rich context.\n *\n * @example\n * ```typescript\n * import { SylphxError, isRetryableError, getErrorMessage } from '@sylphx/sdk'\n *\n * try {\n * await sylphx.auth.login.mutate({ email, password })\n * } catch (error) {\n * if (error instanceof SylphxError) {\n * console.log(error.code) // 'UNAUTHORIZED'\n * console.log(error.isRetryable) // false\n * }\n * if (isRetryableError(error)) {\n * // Safe to retry\n * }\n * }\n * ```\n */\n\nimport {\n\tBASE_RETRY_DELAY_MS,\n\tDEFAULT_TIMEOUT_MS,\n\tMAX_RETRY_DELAY_MS,\n} from \"./constants\";\n\n// ============================================================================\n// Error Codes (aligned with tRPC and HTTP semantics)\n// ============================================================================\n\nexport type SylphxErrorCode =\n\t// Client errors (4xx)\n\t| \"BAD_REQUEST\" // 400 - Invalid input\n\t| \"UNAUTHORIZED\" // 401 - Not authenticated\n\t| \"FORBIDDEN\" // 403 - Not authorized\n\t| \"NOT_FOUND\" // 404 - Resource not found\n\t| \"CONFLICT\" // 409 - Resource conflict (e.g., duplicate)\n\t| \"PAYLOAD_TOO_LARGE\" // 413 - Request too large\n\t| \"UNPROCESSABLE_ENTITY\" // 422 - Validation failed\n\t| \"TOO_MANY_REQUESTS\" // 429 - Rate limited\n\t// Server errors (5xx)\n\t| \"INTERNAL_SERVER_ERROR\" // 500 - Server error\n\t| \"NOT_IMPLEMENTED\" // 501 - Feature not available\n\t| \"BAD_GATEWAY\" // 502 - Upstream error\n\t| \"SERVICE_UNAVAILABLE\" // 503 - Temporarily unavailable\n\t| \"GATEWAY_TIMEOUT\" // 504 - Upstream timeout\n\t// Network/Client errors\n\t| \"NETWORK_ERROR\" // Network failure\n\t| \"TIMEOUT\" // Request timeout\n\t| \"ABORTED\" // Request aborted\n\t// SDK-specific\n\t| \"PARSE_ERROR\" // JSON/response parse error\n\t| \"UNKNOWN\"; // Unknown error\n\n/**\n * HTTP status code mapping for error codes\n */\nexport const ERROR_CODE_STATUS: Record<SylphxErrorCode, number> = {\n\tBAD_REQUEST: 400,\n\tUNAUTHORIZED: 401,\n\tFORBIDDEN: 403,\n\tNOT_FOUND: 404,\n\tCONFLICT: 409,\n\tPAYLOAD_TOO_LARGE: 413,\n\tUNPROCESSABLE_ENTITY: 422,\n\tTOO_MANY_REQUESTS: 429,\n\tINTERNAL_SERVER_ERROR: 500,\n\tNOT_IMPLEMENTED: 501,\n\tBAD_GATEWAY: 502,\n\tSERVICE_UNAVAILABLE: 503,\n\tGATEWAY_TIMEOUT: 504,\n\tNETWORK_ERROR: 0,\n\tTIMEOUT: 0,\n\tABORTED: 0,\n\tPARSE_ERROR: 0,\n\tUNKNOWN: 0,\n};\n\n/**\n * Retryable error codes (safe to retry automatically)\n */\nexport const RETRYABLE_CODES: Set<SylphxErrorCode> = new Set([\n\t\"NETWORK_ERROR\",\n\t\"TIMEOUT\",\n\t\"BAD_GATEWAY\",\n\t\"SERVICE_UNAVAILABLE\",\n\t\"GATEWAY_TIMEOUT\",\n\t\"TOO_MANY_REQUESTS\", // With backoff\n\t\"INTERNAL_SERVER_ERROR\", // Sometimes transient\n]);\n\n// ============================================================================\n// Error Classes\n// ============================================================================\n\nexport interface SylphxErrorOptions {\n\t/** Error code for programmatic handling */\n\tcode?: SylphxErrorCode;\n\t/** HTTP status code (inferred from code if not provided) */\n\tstatus?: number;\n\t/** Additional context data */\n\tdata?: Record<string, unknown>;\n\t/** Original error that caused this */\n\tcause?: Error;\n\t/** Retry-After header value (seconds) for rate limiting */\n\tretryAfter?: number;\n}\n\n/**\n * Base error class for all Sylphx SDK errors\n *\n * @example\n * ```typescript\n * throw new SylphxError('Invalid email format', {\n * code: 'BAD_REQUEST',\n * data: { field: 'email' }\n * })\n * ```\n */\nexport class SylphxError extends Error {\n\t/** Error code for programmatic handling */\n\treadonly code: SylphxErrorCode;\n\n\t/** HTTP status code */\n\treadonly status: number;\n\n\t/** Additional context data */\n\treadonly data?: Record<string, unknown>;\n\n\t/** Whether this error is safe to retry */\n\treadonly isRetryable: boolean;\n\n\t/** Retry-After value in seconds (for rate limiting) */\n\treadonly retryAfter?: number;\n\n\t/** Timestamp when error occurred */\n\treadonly timestamp: Date;\n\n\tconstructor(message: string, options: SylphxErrorOptions = {}) {\n\t\tsuper(message, { cause: options.cause });\n\t\tthis.name = \"SylphxError\";\n\t\tthis.code = options.code ?? \"UNKNOWN\";\n\t\tthis.status = options.status ?? ERROR_CODE_STATUS[this.code];\n\t\tthis.data = options.data;\n\t\tthis.isRetryable = RETRYABLE_CODES.has(this.code);\n\t\tthis.retryAfter = options.retryAfter;\n\t\tthis.timestamp = new Date();\n\n\t\t// Maintain proper stack trace in V8\n\t\tif (Error.captureStackTrace) {\n\t\t\tError.captureStackTrace(this, SylphxError);\n\t\t}\n\t}\n\n\t/**\n\t * Convert to JSON-serializable object\n\t */\n\ttoJSON(): Record<string, unknown> {\n\t\treturn {\n\t\t\tname: this.name,\n\t\t\tmessage: this.message,\n\t\t\tcode: this.code,\n\t\t\tstatus: this.status,\n\t\t\tdata: this.data,\n\t\t\tisRetryable: this.isRetryable,\n\t\t\tretryAfter: this.retryAfter,\n\t\t\ttimestamp: this.timestamp.toISOString(),\n\t\t};\n\t}\n}\n\n/**\n * Network-related errors (no response received)\n */\nexport class NetworkError extends SylphxError {\n\tconstructor(\n\t\tmessage = \"Network request failed\",\n\t\toptions?: Omit<SylphxErrorOptions, \"code\">,\n\t) {\n\t\tsuper(message, { ...options, code: \"NETWORK_ERROR\" });\n\t\tthis.name = \"NetworkError\";\n\t}\n}\n\n/**\n * Request timeout errors\n */\nexport class TimeoutError extends SylphxError {\n\t/** Timeout duration in milliseconds */\n\treadonly timeout: number;\n\n\tconstructor(timeout: number, options?: Omit<SylphxErrorOptions, \"code\">) {\n\t\tsuper(`Request timed out after ${timeout}ms`, {\n\t\t\t...options,\n\t\t\tcode: \"TIMEOUT\",\n\t\t});\n\t\tthis.name = \"TimeoutError\";\n\t\tthis.timeout = timeout;\n\t}\n}\n\n/**\n * Authentication errors (401)\n */\nexport class AuthenticationError extends SylphxError {\n\tconstructor(\n\t\tmessage = \"Authentication required\",\n\t\toptions?: Omit<SylphxErrorOptions, \"code\">,\n\t) {\n\t\tsuper(message, { ...options, code: \"UNAUTHORIZED\" });\n\t\tthis.name = \"AuthenticationError\";\n\t}\n}\n\n/**\n * Authorization errors (403)\n */\nexport class AuthorizationError extends SylphxError {\n\tconstructor(\n\t\tmessage = \"Permission denied\",\n\t\toptions?: Omit<SylphxErrorOptions, \"code\">,\n\t) {\n\t\tsuper(message, { ...options, code: \"FORBIDDEN\" });\n\t\tthis.name = \"AuthorizationError\";\n\t}\n}\n\n/**\n * Validation errors (422)\n */\nexport class ValidationError extends SylphxError {\n\t/** Field-specific errors */\n\treadonly fieldErrors?: Record<string, string[]>;\n\n\tconstructor(\n\t\tmessage: string,\n\t\toptions?: Omit<SylphxErrorOptions, \"code\"> & {\n\t\t\tfieldErrors?: Record<string, string[]>;\n\t\t},\n\t) {\n\t\tsuper(message, { ...options, code: \"UNPROCESSABLE_ENTITY\" });\n\t\tthis.name = \"ValidationError\";\n\t\tthis.fieldErrors = options?.fieldErrors;\n\t}\n\n\t/**\n\t * Get error message for a specific field\n\t */\n\tgetFieldError(field: string): string | undefined {\n\t\treturn this.fieldErrors?.[field]?.[0];\n\t}\n}\n\n/**\n * Rate limit metadata (Stripe SDK pattern)\n */\nexport interface RateLimitInfo {\n\t/** Maximum requests allowed in window */\n\tlimit?: number;\n\t/** Remaining requests in current window */\n\tremaining?: number;\n\t/** Unix timestamp (seconds) when limit resets */\n\tresetAt?: number;\n\t/** Seconds until limit resets (Retry-After header) */\n\tretryAfter?: number;\n}\n\n/**\n * Rate limit errors (429)\n *\n * Provides full rate limit metadata for consumer apps to implement\n * proper backoff UI (countdown timers, retry buttons, etc.)\n *\n * @example\n * ```typescript\n * try {\n * await sendEmail(config, options)\n * } catch (error) {\n * if (error instanceof RateLimitError) {\n * const waitSeconds = error.retryAfter ?? 60\n * console.log(`Rate limited. Retry after ${waitSeconds}s`)\n * console.log(`Remaining: ${error.remaining}/${error.limit}`)\n * console.log(`Resets at: ${new Date(error.resetAt! * 1000)}`)\n * }\n * }\n * ```\n */\nexport class RateLimitError extends SylphxError {\n\t/** Maximum requests allowed in window */\n\treadonly limit?: number;\n\n\t/** Remaining requests in current window */\n\treadonly remaining?: number;\n\n\t/** Unix timestamp (seconds) when limit resets */\n\treadonly resetAt?: number;\n\n\tconstructor(\n\t\tmessage = \"Too many requests\",\n\t\toptions?: Omit<SylphxErrorOptions, \"code\"> & RateLimitInfo,\n\t) {\n\t\tsuper(message, { ...options, code: \"TOO_MANY_REQUESTS\" });\n\t\tthis.name = \"RateLimitError\";\n\t\tthis.limit = options?.limit;\n\t\tthis.remaining = options?.remaining;\n\t\tthis.resetAt = options?.resetAt;\n\t}\n\n\t/**\n\t * Get Date when rate limit resets\n\t */\n\tgetResetDate(): Date | undefined {\n\t\treturn this.resetAt ? new Date(this.resetAt * 1000) : undefined;\n\t}\n\n\t/**\n\t * Get human-readable retry message\n\t */\n\tgetRetryMessage(): string {\n\t\tif (this.retryAfter) {\n\t\t\treturn `Please retry after ${this.retryAfter} seconds`;\n\t\t}\n\t\tif (this.resetAt) {\n\t\t\tconst seconds = Math.max(0, this.resetAt - Math.floor(Date.now() / 1000));\n\t\t\treturn `Rate limit resets in ${seconds} seconds`;\n\t\t}\n\t\treturn \"Please wait before retrying\";\n\t}\n}\n\n/**\n * Resource not found errors (404)\n */\nexport class NotFoundError extends SylphxError {\n\t/** Type of resource that wasn't found */\n\treadonly resourceType?: string;\n\n\t/** ID of the resource that wasn't found */\n\treadonly resourceId?: string;\n\n\tconstructor(\n\t\tmessage = \"Resource not found\",\n\t\toptions?: Omit<SylphxErrorOptions, \"code\"> & {\n\t\t\tresourceType?: string;\n\t\t\tresourceId?: string;\n\t\t},\n\t) {\n\t\tsuper(message, { ...options, code: \"NOT_FOUND\" });\n\t\tthis.name = \"NotFoundError\";\n\t\tthis.resourceType = options?.resourceType;\n\t\tthis.resourceId = options?.resourceId;\n\t}\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Check if an error is a Sylphx SDK error\n */\nexport function isSylphxError(error: unknown): error is SylphxError {\n\treturn error instanceof SylphxError;\n}\n\n/**\n * Check if an error is safe to retry\n */\nexport function isRetryableError(error: unknown): boolean {\n\tif (error instanceof SylphxError) {\n\t\treturn error.isRetryable;\n\t}\n\n\t// Check for network errors\n\tif (error instanceof Error) {\n\t\tconst message = error.message.toLowerCase();\n\t\tconst name = error.name.toLowerCase();\n\n\t\t// Network errors\n\t\tif (name === \"typeerror\" && message.includes(\"fetch\")) return true;\n\t\tif (name === \"networkerror\") return true;\n\n\t\t// Timeout patterns\n\t\tif (message.includes(\"timeout\")) return true;\n\t\tif (message.includes(\"timed out\")) return true;\n\n\t\t// Connection errors\n\t\tif (message.includes(\"econnrefused\")) return true;\n\t\tif (message.includes(\"econnreset\")) return true;\n\t\tif (message.includes(\"socket\")) return true;\n\n\t\t// Server errors that might be transient\n\t\tif (message.includes(\"502\")) return true;\n\t\tif (message.includes(\"503\")) return true;\n\t\tif (message.includes(\"504\")) return true;\n\t}\n\n\treturn false;\n}\n\n/**\n * Extract error message from any error type\n */\nexport function getErrorMessage(error: unknown): string {\n\tif (error instanceof Error) {\n\t\treturn error.message;\n\t}\n\tif (typeof error === \"string\") {\n\t\treturn error;\n\t}\n\treturn \"An unknown error occurred\";\n}\n\n/**\n * Get error code from any error type\n */\nexport function getErrorCode(error: unknown): SylphxErrorCode {\n\tif (error instanceof SylphxError) {\n\t\treturn error.code;\n\t}\n\treturn \"UNKNOWN\";\n}\n\n/**\n * Convert any error to SylphxError\n */\nexport function toSylphxError(error: unknown): SylphxError {\n\tif (error instanceof SylphxError) {\n\t\treturn error;\n\t}\n\n\tif (error instanceof Error) {\n\t\t// Try to infer error type from message/name\n\t\tconst message = error.message.toLowerCase();\n\t\tconst name = error.name.toLowerCase();\n\n\t\t// Network errors\n\t\tif (name === \"typeerror\" && message.includes(\"fetch\")) {\n\t\t\treturn new NetworkError(error.message, { cause: error });\n\t\t}\n\t\tif (name === \"aborterror\" || message.includes(\"aborted\")) {\n\t\t\treturn new SylphxError(error.message, { code: \"ABORTED\", cause: error });\n\t\t}\n\n\t\t// Timeout\n\t\tif (message.includes(\"timeout\")) {\n\t\t\treturn new TimeoutError(DEFAULT_TIMEOUT_MS, { cause: error });\n\t\t}\n\n\t\t// HTTP status codes in message\n\t\tif (message.includes(\"401\") || message.includes(\"unauthorized\")) {\n\t\t\treturn new AuthenticationError(error.message, { cause: error });\n\t\t}\n\t\tif (message.includes(\"403\") || message.includes(\"forbidden\")) {\n\t\t\treturn new AuthorizationError(error.message, { cause: error });\n\t\t}\n\t\tif (message.includes(\"404\") || message.includes(\"not found\")) {\n\t\t\treturn new NotFoundError(error.message, { cause: error });\n\t\t}\n\t\tif (message.includes(\"429\") || message.includes(\"rate limit\")) {\n\t\t\treturn new RateLimitError(error.message, { cause: error });\n\t\t}\n\n\t\treturn new SylphxError(error.message, { cause: error });\n\t}\n\n\treturn new SylphxError(getErrorMessage(error));\n}\n\n/**\n * Calculate exponential backoff delay with jitter\n *\n * @param attempt - Retry attempt number (0-indexed)\n * @param baseDelay - Base delay in milliseconds (default: 1000)\n * @param maxDelay - Maximum delay in milliseconds (default: 30000)\n * @returns Delay in milliseconds with jitter\n */\nexport function exponentialBackoff(\n\tattempt: number,\n\tbaseDelay = BASE_RETRY_DELAY_MS,\n\tmaxDelay = MAX_RETRY_DELAY_MS,\n): number {\n\t// Calculate exponential delay: baseDelay * 2^attempt\n\tconst exponentialDelay = baseDelay * Math.pow(2, attempt);\n\n\t// Cap at maxDelay\n\tconst cappedDelay = Math.min(exponentialDelay, maxDelay);\n\n\t// Add jitter (±25% randomness)\n\tconst jitter = cappedDelay * 0.25 * (Math.random() * 2 - 1);\n\n\treturn Math.round(cappedDelay + jitter);\n}\n","/**\n * API Key Validation — Single Source of Truth\n *\n * OAuth 2.0 standard key validation for Sylphx Platform.\n * ALL key validation, sanitization, and environment detection logic lives here.\n *\n * Principles:\n * 1. Fail fast - Invalid input rejected immediately with clear errors\n * 2. Helpful errors - Tell users exactly what's wrong and how to fix it\n * 3. Development warnings - Warn about issues that would fail in production\n * 4. No silent fixes - Transparency over convenience (but warn + continue)\n * 5. Single Source of Truth - All key logic in one place\n *\n * Key Formats (OAuth 2.0 Standard):\n * - App ID: app_(dev|stg|prod)_[identifier] — Public identifier (like OAuth client_id)\n * - Secret Key: sk_(dev|stg|prod)_[identifier] — Server-side only (like OAuth client_secret)\n *\n * Identifier Types:\n * - Customer apps: 32 hex chars\n * - Platform apps: platform_{app-slug} (e.g., platform_sylphx-console)\n */\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/** Environment type derived from key prefix */\nexport type EnvironmentType = \"development\" | \"staging\" | \"production\";\n\n/** Key type - appId (public) or secret (server) */\nexport type KeyType = \"appId\" | \"secret\";\n\n/** Validation result with clear error information */\nexport interface KeyValidationResult {\n\t/** Whether the key is valid (possibly after sanitization) */\n\tvalid: boolean;\n\t/** The sanitized key to use (only if valid) */\n\tsanitizedKey: string;\n\t/** Detected key type */\n\tkeyType?: KeyType;\n\t/** Detected environment */\n\tenvironment?: EnvironmentType;\n\t/** Error message if invalid */\n\terror?: string;\n\t/** Warning message if key was auto-fixed */\n\twarning?: string;\n\t/** Detected issues for debugging */\n\tissues?: string[];\n}\n\n// =============================================================================\n// Patterns — Strict Format Validation\n// =============================================================================\n\n/**\n * App ID pattern: app_(dev|stg|prod)_[identifier]\n * - Prefix: app_ (application identifier, public)\n * - Environment: dev, stg, or prod (NO typos allowed)\n * - Suffix: alphanumeric with underscores/hyphens (hex for apps, or internal identifiers)\n */\nconst APP_ID_PATTERN = /^app_(dev|stg|prod)_[a-z0-9_-]+$/;\n\n/**\n * Secret key pattern: sk_(dev|stg|prod)_[identifier]\n * - Prefix: sk_ (secret key)\n * - Environment: dev, stg, or prod (NO typos allowed)\n * - Suffix: alphanumeric with underscores/hyphens (hex for apps, or internal identifiers)\n */\nconst SECRET_KEY_PATTERN = /^sk_(dev|stg|prod)_[a-z0-9_-]+$/;\n\n/** Environment prefix to type mapping */\nconst ENV_PREFIX_MAP: Record<string, EnvironmentType> = {\n\tdev: \"development\",\n\tstg: \"staging\",\n\tprod: \"production\",\n};\n\n// =============================================================================\n// Core Validation Functions\n// =============================================================================\n\n/**\n * Detect common issues with a key (whitespace, newlines, etc.)\n */\nfunction detectKeyIssues(key: string): string[] {\n\tconst issues: string[] = [];\n\tif (key !== key.trim()) issues.push(\"whitespace\");\n\tif (key.includes(\"\\n\")) issues.push(\"newline\");\n\tif (key.includes(\"\\r\")) issues.push(\"carriage-return\");\n\tif (key.includes(\" \")) issues.push(\"space\");\n\tif (key !== key.toLowerCase()) issues.push(\"uppercase-chars\");\n\treturn issues;\n}\n\n/**\n * Create a helpful warning message for keys that needed sanitization\n */\nfunction createSanitizationWarning(\n\tkeyType: KeyType,\n\tissues: string[],\n\tenvVarName: string,\n): string {\n\tconst keyTypeName = keyType === \"appId\" ? \"App ID\" : \"Secret Key\";\n\treturn (\n\t\t`[Sylphx] ${keyTypeName} contains ${issues.join(\", \")}. ` +\n\t\t`This is commonly caused by Vercel CLI's 'env pull' command.\\n\\n` +\n\t\t`To fix permanently:\\n` +\n\t\t`1. Go to Vercel Dashboard → Your Project → Settings → Environment Variables\\n` +\n\t\t`2. Edit ${envVarName}\\n` +\n\t\t`3. Remove any trailing whitespace or newline characters\\n` +\n\t\t`4. Redeploy your application\\n\\n` +\n\t\t`The SDK will automatically sanitize the key, but fixing the source is recommended.`\n\t);\n}\n\n/**\n * Create a helpful error message for invalid keys\n */\nfunction createInvalidKeyError(\n\tkeyType: KeyType,\n\tkey: string,\n\tenvVarName: string,\n): string {\n\tconst prefix = keyType === \"appId\" ? \"app\" : \"sk\";\n\tconst maskedKey = key.length > 20 ? `${key.slice(0, 20)}...` : key;\n\tconst formatHint = `${prefix}_(dev|stg|prod)_[identifier]`;\n\tconst keyTypeName = keyType === \"appId\" ? \"App ID\" : \"Secret Key\";\n\n\treturn (\n\t\t`[Sylphx] Invalid ${keyTypeName} format.\\n\\n` +\n\t\t`Expected format: ${formatHint}\\n` +\n\t\t`Received: \"${maskedKey}\"\\n\\n` +\n\t\t`Please check your ${envVarName} environment variable.\\n` +\n\t\t`You can find your keys in the Sylphx Console → API Keys.\\n\\n` +\n\t\t`Common issues:\\n` +\n\t\t`• Key has uppercase characters (must be lowercase)\\n` +\n\t\t`• Key has wrong prefix (App ID: app_, Secret Key: sk_)\\n` +\n\t\t`• Key has invalid environment (must be dev, stg, or prod)\\n` +\n\t\t`• Key was copied with extra whitespace`\n\t);\n}\n\n/**\n * Extract environment from a validated key\n */\nfunction extractEnvironment(key: string): EnvironmentType | undefined {\n\t// Match app_ or sk_ prefix followed by environment\n\tconst match = key.match(/^(?:app|sk)_(dev|stg|prod)_/);\n\tif (!match) return undefined;\n\treturn ENV_PREFIX_MAP[match[1]];\n}\n\n/**\n * Internal: Generic key validation logic for specific key types\n */\nfunction validateKeyForType(\n\tkey: string | undefined | null,\n\tkeyType: KeyType,\n\tpattern: RegExp,\n\tenvVarName: string,\n): KeyValidationResult {\n\tconst keyTypeName = keyType === \"appId\" ? \"App ID\" : \"Secret Key\";\n\n\t// Check if key is provided\n\tif (!key) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\tsanitizedKey: \"\",\n\t\t\terror:\n\t\t\t\t`[Sylphx] ${keyTypeName} is required. ` +\n\t\t\t\t`Set ${envVarName} in your environment variables.`,\n\t\t\tissues: [\"missing\"],\n\t\t};\n\t}\n\n\t// Detect issues before validation\n\tconst issues = detectKeyIssues(key);\n\n\t// Check if key matches expected format exactly\n\tif (pattern.test(key)) {\n\t\treturn {\n\t\t\tvalid: true,\n\t\t\tsanitizedKey: key,\n\t\t\tkeyType,\n\t\t\tenvironment: extractEnvironment(key),\n\t\t\tissues: [],\n\t\t};\n\t}\n\n\t// Key doesn't match - try sanitization (trim + lowercase)\n\tconst sanitized = key.trim().toLowerCase();\n\n\tif (pattern.test(sanitized)) {\n\t\t// Sanitization fixes the issue\n\t\treturn {\n\t\t\tvalid: true,\n\t\t\tsanitizedKey: sanitized,\n\t\t\tkeyType,\n\t\t\tenvironment: extractEnvironment(sanitized),\n\t\t\twarning: createSanitizationWarning(keyType, issues, envVarName),\n\t\t\tissues,\n\t\t};\n\t}\n\n\t// Sanitization doesn't fix it - key format is genuinely wrong\n\treturn {\n\t\tvalid: false,\n\t\tsanitizedKey: \"\",\n\t\terror: createInvalidKeyError(keyType, key, envVarName),\n\t\tissues: [...issues, \"invalid-format\"],\n\t};\n}\n\n// =============================================================================\n// Public API — App ID (formerly Publishable Key)\n// =============================================================================\n\n/**\n * Validate an App ID and return detailed results\n *\n * @example\n * ```typescript\n * const result = validateAppId(process.env.NEXT_PUBLIC_SYLPHX_APP_ID)\n * if (!result.valid) {\n * throw new Error(result.error)\n * }\n * if (result.warning) {\n * console.warn(result.warning)\n * }\n * ```\n */\nexport function validateAppId(\n\tkey: string | undefined | null,\n): KeyValidationResult {\n\treturn validateKeyForType(\n\t\tkey,\n\t\t\"appId\",\n\t\tAPP_ID_PATTERN,\n\t\t\"NEXT_PUBLIC_SYLPHX_APP_ID\",\n\t);\n}\n\n/**\n * Validate and sanitize App ID, logging warnings\n *\n * @throws Error if the key is invalid and cannot be sanitized\n * @returns The sanitized App ID\n */\nexport function validateAndSanitizeAppId(\n\tkey: string | undefined | null,\n): string {\n\tconst result = validateAppId(key);\n\n\tif (!result.valid) {\n\t\tthrow new Error(result.error);\n\t}\n\n\tif (result.warning) {\n\t\tconsole.warn(result.warning);\n\t}\n\n\treturn result.sanitizedKey;\n}\n\n// =============================================================================\n// Public API — Secret Keys\n// =============================================================================\n\n/**\n * Validate a secret key and return detailed results\n *\n * @example\n * ```typescript\n * const result = validateSecretKey(process.env.SYLPHX_SECRET_KEY)\n * if (!result.valid) {\n * throw new Error(result.error)\n * }\n * ```\n */\nexport function validateSecretKey(\n\tkey: string | undefined | null,\n): KeyValidationResult {\n\treturn validateKeyForType(\n\t\tkey,\n\t\t\"secret\",\n\t\tSECRET_KEY_PATTERN,\n\t\t\"SYLPHX_SECRET_KEY\",\n\t);\n}\n\n/**\n * Validate and sanitize secret key, logging warnings\n *\n * @throws Error if the key is invalid and cannot be sanitized\n * @returns The sanitized secret key\n */\nexport function validateAndSanitizeSecretKey(\n\tkey: string | undefined | null,\n): string {\n\tconst result = validateSecretKey(key);\n\n\tif (!result.valid) {\n\t\tthrow new Error(result.error);\n\t}\n\n\tif (result.warning) {\n\t\tconsole.warn(result.warning);\n\t}\n\n\treturn result.sanitizedKey;\n}\n\n// =============================================================================\n// Public API — Environment Detection (SSOT)\n// =============================================================================\n\n/**\n * Detect environment type from any key (App ID or Secret Key)\n *\n * @example\n * ```typescript\n * detectEnvironment('sk_dev_abc123') // 'development'\n * detectEnvironment('app_prod_xyz789') // 'production'\n * detectEnvironment('sk_stg_qwe456') // 'staging'\n * ```\n *\n * @throws Error if key format is invalid\n */\nexport function detectEnvironment(key: string): EnvironmentType {\n\t// Validate and sanitize first\n\tconst sanitized = key.trim().toLowerCase();\n\n\t// Check both key types\n\tif (sanitized.startsWith(\"sk_\")) {\n\t\tconst result = validateSecretKey(sanitized);\n\t\tif (!result.valid) {\n\t\t\tthrow new Error(result.error);\n\t\t}\n\t\treturn result.environment!;\n\t}\n\n\tif (sanitized.startsWith(\"app_\")) {\n\t\tconst result = validateAppId(sanitized);\n\t\tif (!result.valid) {\n\t\t\tthrow new Error(result.error);\n\t\t}\n\t\treturn result.environment!;\n\t}\n\n\tthrow new Error(\n\t\t`[Sylphx] Invalid key format. Key must start with 'sk_' (secret) or 'app_' (App ID).`,\n\t);\n}\n\n/**\n * Check if running in development environment based on key\n */\nexport function isDevelopmentKey(key: string): boolean {\n\treturn detectEnvironment(key) === \"development\";\n}\n\n/**\n * Check if running in production environment based on key\n */\nexport function isProductionKey(key: string): boolean {\n\treturn detectEnvironment(key) === \"production\";\n}\n\n// =============================================================================\n// Public API — Cookie Namespace (SSOT)\n// =============================================================================\n\n/**\n * Get the cookie namespace for a given secret key\n *\n * Used by auth middleware to namespace cookies per environment.\n * This prevents dev/staging/prod cookies from conflicting.\n *\n * @example\n * ```typescript\n * getCookieNamespace('sk_dev_abc123') // 'sylphx_dev'\n * getCookieNamespace('sk_prod_xyz789') // 'sylphx_prod'\n * ```\n */\nexport function getCookieNamespace(secretKey: string): string {\n\tconst env = detectEnvironment(secretKey);\n\tconst shortEnv =\n\t\tenv === \"development\" ? \"dev\" : env === \"staging\" ? \"stg\" : \"prod\";\n\treturn `sylphx_${shortEnv}`;\n}\n\n// =============================================================================\n// Public API — Key Type Detection\n// =============================================================================\n\n/**\n * Detect the type of key (App ID or Secret Key)\n *\n * @returns 'appId', 'secret', or null if unknown\n */\nexport function detectKeyType(key: string): KeyType | null {\n\tconst sanitized = key.trim().toLowerCase();\n\tif (sanitized.startsWith(\"app_\")) return \"appId\";\n\tif (sanitized.startsWith(\"sk_\")) return \"secret\";\n\treturn null;\n}\n\n/**\n * Check if a key is an App ID\n */\nexport function isAppId(key: string): boolean {\n\treturn detectKeyType(key) === \"appId\";\n}\n\n/**\n * Check if a key is a secret key\n */\nexport function isSecretKey(key: string): boolean {\n\treturn detectKeyType(key) === \"secret\";\n}\n\n/**\n * Validate any key (auto-detects type)\n *\n * Use this when you accept either App ID or Secret Key.\n * The function auto-detects the key type and validates accordingly.\n *\n * @example\n * ```typescript\n * const result = validateKey(process.env.SYLPHX_SECRET_KEY)\n * if (!result.valid) {\n * throw new Error(result.error)\n * }\n * const sanitizedKey = result.sanitizedKey\n * ```\n */\nexport function validateKey(\n\tkey: string | undefined | null,\n): KeyValidationResult {\n\tconst keyType = key ? detectKeyType(key) : null;\n\n\tif (keyType === \"appId\") {\n\t\treturn validateAppId(key);\n\t}\n\tif (keyType === \"secret\") {\n\t\treturn validateSecretKey(key);\n\t}\n\n\t// Unknown key type - return detailed error\n\treturn {\n\t\tvalid: false,\n\t\tsanitizedKey: \"\",\n\t\terror: key\n\t\t\t? `Invalid key format. Keys must start with 'app_' (App ID) or 'sk_' (Secret Key), followed by environment (dev/stg/prod) and identifier. Got: ${key.slice(0, 20)}...`\n\t\t\t: \"API key is required but was not provided.\",\n\t\tissues: key ? [\"invalid_format\"] : [\"missing\"],\n\t};\n}\n\n/**\n * Validate any key and return sanitized version (throws on error)\n *\n * Use this when you need the key value and want to throw on invalid input.\n */\nexport function validateAndSanitizeKey(key: string | undefined | null): string {\n\tconst result = validateKey(key);\n\tif (!result.valid) {\n\t\tthrow new Error(result.error);\n\t}\n\tif (result.warning) {\n\t\tconsole.warn(`[Sylphx] ${result.warning}`);\n\t}\n\treturn result.sanitizedKey;\n}\n\n// =============================================================================\n// Public API — Runtime Environment Detection\n// =============================================================================\n\n/**\n * Check if we're in development mode (based on NODE_ENV or hostname)\n */\nexport function isDevelopmentRuntime(): boolean {\n\tif (typeof process !== \"undefined\" && process.env) {\n\t\treturn process.env.NODE_ENV === \"development\";\n\t}\n\tif (typeof window !== \"undefined\") {\n\t\treturn (\n\t\t\twindow.location.hostname === \"localhost\" ||\n\t\t\twindow.location.hostname === \"127.0.0.1\"\n\t\t);\n\t}\n\treturn false;\n}\n","/**\n * SDK Configuration\n *\n * Create a config object that can be passed to SDK functions.\n * This is the foundation for the function-based API.\n *\n * Uses appId or secretKey for authentication via x-app-secret header.\n */\n\nimport {\n\tDEFAULT_PLATFORM_URL,\n\tDEFAULT_SDK_API_HOST,\n\tDEFAULT_TIMEOUT_MS,\n\tSDK_API_PATH,\n\tSDK_API_PATH_NEW,\n} from './constants'\nimport {\n\tNetworkError,\n\tRateLimitError,\n\tSylphxError,\n\ttype SylphxErrorCode,\n\tTimeoutError,\n} from './errors'\nimport { validateKey } from './key-validation'\n\n/**\n * Map HTTP status code to SylphxErrorCode\n */\nfunction httpStatusToErrorCode(status: number): SylphxErrorCode {\n\tswitch (status) {\n\t\tcase 400:\n\t\t\treturn 'BAD_REQUEST'\n\t\tcase 401:\n\t\t\treturn 'UNAUTHORIZED'\n\t\tcase 403:\n\t\t\treturn 'FORBIDDEN'\n\t\tcase 404:\n\t\t\treturn 'NOT_FOUND'\n\t\tcase 409:\n\t\t\treturn 'CONFLICT'\n\t\tcase 413:\n\t\t\treturn 'PAYLOAD_TOO_LARGE'\n\t\tcase 422:\n\t\t\treturn 'UNPROCESSABLE_ENTITY'\n\t\tcase 429:\n\t\t\treturn 'TOO_MANY_REQUESTS'\n\t\tcase 500:\n\t\t\treturn 'INTERNAL_SERVER_ERROR'\n\t\tcase 501:\n\t\t\treturn 'NOT_IMPLEMENTED'\n\t\tcase 502:\n\t\t\treturn 'BAD_GATEWAY'\n\t\tcase 503:\n\t\t\treturn 'SERVICE_UNAVAILABLE'\n\t\tcase 504:\n\t\t\treturn 'GATEWAY_TIMEOUT'\n\t\tdefault:\n\t\t\treturn status >= 500 ? 'INTERNAL_SERVER_ERROR' : 'BAD_REQUEST'\n\t}\n}\n\n/**\n * SDK Configuration for Pure Functions\n *\n * This is the configuration object passed to pure SDK functions like\n * `track()`, `signIn()`, `getPlans()`, etc.\n *\n * ## Config Type Hierarchy\n *\n * - `SylphxConfig` (this) — Pure functions, server or client\n * - `SylphxClientConfig` — React hooks return value (appId, platformUrl only)\n * - `SylphxProviderProps` — React provider component props\n * - `SylphxMiddlewareConfig` — Next.js middleware options\n *\n * @example Server-side usage\n * ```ts\n * import { createConfig, track } from '@sylphx/sdk'\n *\n * const config = createConfig({ secretKey: process.env.SYLPHX_SECRET_KEY! })\n * await track(config, { event: 'purchase', properties: { amount: 99 } })\n * ```\n */\nexport interface SylphxConfig {\n\t/**\n\t * Your app key — identifies the app and environment.\n\t *\n\t * Accepts either:\n\t * - Secret key (sk_dev_, sk_stg_, sk_prod_) — full access, server-side only\n\t * - Publishable key (app_dev_, app_stg_, app_prod_) — limited access, safe for client\n\t *\n\t * Get this from Platform Console → Apps → Your App → Environments\n\t */\n\treadonly secretKey?: string\n\t/**\n\t * Platform URL (default: https://sylphx.com)\n\t *\n\t * When `ref` is provided, this is overridden to https://{ref}.api.sylphx.com\n\t * and `apiBasePath` is set to /v1 automatically.\n\t *\n\t * Explicit `platformUrl` takes precedence over `ref`.\n\t */\n\treadonly platformUrl: string\n\t/**\n\t * Project ref — short 16-char alphanumeric nanoid (e.g. \"abc123def456ghij\").\n\t *\n\t * When provided, the SDK targets the new per-project subdomain:\n\t * https://{ref}.api.sylphx.com/v1\n\t *\n\t * Get this from Platform Console → Projects → Your Project → Overview.\n\t */\n\treadonly ref?: string\n\t/**\n\t * API base path appended to platformUrl for all requests.\n\t *\n\t * Default: /api/v1 (legacy path via main Next.js app)\n\t * When ref is set: /v1 (new SDK server path)\n\t * When explicit platformUrl is set: /api/v1 (unchanged)\n\t */\n\treadonly apiBasePath?: string\n\t/** Optional: Current access token for authenticated requests */\n\treadonly accessToken?: string\n}\n\n/**\n * Configuration input (some fields are optional)\n */\nexport interface SylphxConfigInput {\n\tsecretKey?: string\n\t/**\n\t * Explicit platform URL override.\n\t * Takes precedence over `ref`.\n\t */\n\tplatformUrl?: string\n\t/**\n\t * Project ref — short 16-char alphanumeric nanoid.\n\t *\n\t * When provided (and `platformUrl` is not set), the SDK automatically\n\t * targets: https://{ref}.api.sylphx.com/v1\n\t *\n\t * Get this from Platform Console → Projects → Your Project → Overview.\n\t */\n\tref?: string\n\taccessToken?: string\n}\n\n/**\n * Valid ref pattern: 16-char lowercase alphanumeric nanoid.\n * Example: \"abc123def456ghij\"\n */\nconst REF_PATTERN = /^[a-z0-9]{16}$/\n\n/**\n * Create a Sylphx configuration object\n *\n * Validates the secretKey (if provided) using the SSOT key-validation module.\n * Validates the ref format (if provided) — must be a 16-char alphanumeric nanoid.\n * Sanitizes keys with common issues (whitespace, newlines) and logs warnings.\n * Throws if key format is invalid.\n *\n * @example\n * ```typescript\n * const config = createConfig({\n * secretKey: process.env.SYLPHX_SECRET_KEY!,\n * })\n * ```\n */\nexport function createConfig(input: SylphxConfigInput): SylphxConfig {\n\t// Validate and sanitize secretKey using SSOT if provided\n\tlet secretKey: string | undefined\n\tif (input.secretKey) {\n\t\tconst result = validateKey(input.secretKey)\n\t\tif (!result.valid) {\n\t\t\tthrow new SylphxError(result.error || 'Invalid API key', {\n\t\t\t\tcode: 'BAD_REQUEST',\n\t\t\t\tdata: { issues: result.issues },\n\t\t\t})\n\t\t}\n\t\tif (result.warning) {\n\t\t\tconsole.warn(`[Sylphx] ${result.warning}`)\n\t\t}\n\t\tsecretKey = result.sanitizedKey\n\t}\n\n\t// Validate ref format if provided\n\tif (input.ref !== undefined) {\n\t\tconst trimmedRef = input.ref.trim()\n\t\tif (!REF_PATTERN.test(trimmedRef)) {\n\t\t\tthrow new SylphxError(\n\t\t\t\t`[Sylphx] Invalid project ref format: \"${input.ref}\". ` +\n\t\t\t\t\t`Expected a 16-character lowercase alphanumeric string (e.g. \"abc123def456ghij\"). ` +\n\t\t\t\t\t`Get your ref from Platform Console → Projects → Your Project → Overview.`,\n\t\t\t\t{ code: 'BAD_REQUEST' },\n\t\t\t)\n\t\t}\n\t}\n\n\t// Resolve platformUrl and apiBasePath\n\t// Priority: explicit platformUrl > ref-based URL > default\n\tlet platformUrl: string\n\tlet apiBasePath: string\n\n\tif (input.platformUrl) {\n\t\t// Explicit override — use as-is with legacy API path\n\t\tplatformUrl = input.platformUrl.trim()\n\t\tapiBasePath = SDK_API_PATH\n\t} else if (input.ref) {\n\t\t// New subdomain-based URL: https://{ref}.api.sylphx.com\n\t\tplatformUrl = `https://${input.ref.trim()}.${DEFAULT_SDK_API_HOST}`\n\t\tapiBasePath = SDK_API_PATH_NEW\n\t} else {\n\t\t// Legacy default: https://sylphx.com/api/v1\n\t\tplatformUrl = DEFAULT_PLATFORM_URL\n\t\tapiBasePath = SDK_API_PATH\n\t}\n\n\treturn Object.freeze({\n\t\tsecretKey,\n\t\tplatformUrl,\n\t\tref: input.ref,\n\t\tapiBasePath,\n\t\taccessToken: input.accessToken,\n\t})\n}\n\n/**\n * Create a new config with an updated access token\n *\n * @example\n * ```typescript\n * const authenticatedConfig = withToken(config, 'access_token_here')\n * ```\n */\nexport function withToken(config: SylphxConfig, accessToken: string): SylphxConfig {\n\treturn Object.freeze({\n\t\t...config,\n\t\taccessToken,\n\t\t// Preserve apiBasePath and ref from original config\n\t})\n}\n\n/**\n * Internal: Build headers for API requests\n */\nexport function buildHeaders(config: SylphxConfig): Record<string, string> {\n\tconst headers: Record<string, string> = {\n\t\t'Content-Type': 'application/json',\n\t}\n\n\tif (config.secretKey) {\n\t\theaders['x-app-secret'] = config.secretKey\n\t}\n\tif (config.accessToken) {\n\t\theaders['Authorization'] = `Bearer ${config.accessToken}`\n\t}\n\n\treturn headers\n}\n\n/**\n * Internal: Build REST API URL\n *\n * Uses config.apiBasePath which is:\n * - /v1 when targeting the new per-project subdomain ({ref}.api.sylphx.com)\n * - /api/v1 when targeting the legacy path (sylphx.com)\n */\nexport function buildApiUrl(config: SylphxConfig, path: string): string {\n\tconst base = config.platformUrl.replace(/\\/$/, '')\n\tconst cleanPath = path.startsWith('/') ? path : `/${path}`\n\treturn `${base}${config.apiBasePath ?? SDK_API_PATH}${cleanPath}`\n}\n\n/**\n * Internal: Call REST API endpoint\n *\n * Features:\n * - Request timeout (default 30s) prevents infinite hangs\n * - Proper HTTP status code mapping to error codes\n * - Safe JSON parsing with error handling\n * - Idempotency key support (Stripe pattern)\n */\nexport async function callApi<TOutput>(\n\tconfig: SylphxConfig,\n\tpath: string,\n\toptions: {\n\t\tmethod?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'\n\t\tbody?: unknown\n\t\tquery?: Record<string, string | number | boolean | undefined>\n\t\t/** Request timeout in milliseconds (default: 30000) */\n\t\ttimeout?: number\n\t\t/** AbortSignal for manual cancellation */\n\t\tsignal?: AbortSignal\n\t\t/**\n\t\t * Idempotency key for safe retries (Stripe pattern)\n\t\t *\n\t\t * When provided, the server will deduplicate requests with the same key\n\t\t * within a 24-hour window. Use for POST/PUT/DELETE operations that\n\t\t * should not be repeated (e.g., email sending, payment processing).\n\t\t *\n\t\t * @example\n\t\t * ```typescript\n\t\t * await sendEmail(config, {\n\t\t * to: 'user@example.com',\n\t\t * subject: 'Welcome!',\n\t\t * idempotencyKey: `welcome-email-${userId}`,\n\t\t * })\n\t\t * ```\n\t\t */\n\t\tidempotencyKey?: string\n\t} = {},\n): Promise<TOutput> {\n\tconst {\n\t\tmethod = 'GET',\n\t\tbody,\n\t\tquery,\n\t\ttimeout = DEFAULT_TIMEOUT_MS,\n\t\tsignal,\n\t\tidempotencyKey,\n\t} = options\n\n\tlet url = buildApiUrl(config, path)\n\n\t// Add query parameters\n\tif (query) {\n\t\tconst params = new URLSearchParams()\n\t\tfor (const [key, value] of Object.entries(query)) {\n\t\t\tif (value !== undefined) {\n\t\t\t\tparams.set(key, String(value))\n\t\t\t}\n\t\t}\n\t\tconst queryString = params.toString()\n\t\tif (queryString) {\n\t\t\turl += `?${queryString}`\n\t\t}\n\t}\n\n\t// Create AbortController for timeout\n\tconst controller = new AbortController()\n\tconst timeoutId = setTimeout(() => controller.abort(), timeout)\n\n\t// Combine user signal with timeout signal\n\tconst combinedSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal\n\n\tconst headers = buildHeaders(config)\n\n\t// Add idempotency key header for safe retries (Stripe pattern)\n\tif (idempotencyKey) {\n\t\theaders['Idempotency-Key'] = idempotencyKey\n\t}\n\n\tconst fetchOptions: RequestInit = {\n\t\tmethod,\n\t\theaders,\n\t\tsignal: combinedSignal,\n\t}\n\n\tif (body) {\n\t\tfetchOptions.body = JSON.stringify(body)\n\t}\n\n\tlet response: Response\n\ttry {\n\t\tresponse = await fetch(url, fetchOptions)\n\t} catch (error) {\n\t\tclearTimeout(timeoutId)\n\n\t\t// Handle abort/timeout\n\t\tif (error instanceof Error) {\n\t\t\tif (error.name === 'AbortError') {\n\t\t\t\t// Check if it was our timeout or user cancellation\n\t\t\t\tif (controller.signal.aborted && !signal?.aborted) {\n\t\t\t\t\tthrow new TimeoutError(timeout)\n\t\t\t\t}\n\t\t\t\tthrow new SylphxError('Request aborted', {\n\t\t\t\t\tcode: 'ABORTED',\n\t\t\t\t\tcause: error,\n\t\t\t\t})\n\t\t\t}\n\t\t\t// Network errors\n\t\t\tthrow new NetworkError(error.message, { cause: error })\n\t\t}\n\t\tthrow new NetworkError('Network request failed')\n\t} finally {\n\t\tclearTimeout(timeoutId)\n\t}\n\n\tif (!response.ok) {\n\t\tconst errorBody = await response.text().catch(() => '')\n\t\tlet errorMessage = 'Request failed'\n\t\tlet errorData: Record<string, unknown> | undefined\n\n\t\t// Safe JSON parsing\n\t\tif (errorBody) {\n\t\t\ttry {\n\t\t\t\tconst parsed = JSON.parse(errorBody) as {\n\t\t\t\t\terror?: { message?: string }\n\t\t\t\t\tmessage?: string\n\t\t\t\t}\n\t\t\t\terrorMessage = parsed.error?.message ?? parsed.message ?? errorMessage\n\t\t\t\terrorData = parsed.error as Record<string, unknown> | undefined\n\t\t\t} catch {\n\t\t\t\t// Not JSON, use status text\n\t\t\t\terrorMessage = response.statusText || errorMessage\n\t\t\t}\n\t\t}\n\n\t\tconst errorCode = httpStatusToErrorCode(response.status)\n\n\t\t// Extract rate limit headers (Stripe SDK pattern)\n\t\tconst retryAfterHeader = response.headers.get('Retry-After')\n\t\tconst rateLimitLimit = response.headers.get('X-RateLimit-Limit')\n\t\tconst rateLimitRemaining = response.headers.get('X-RateLimit-Remaining')\n\t\tconst rateLimitReset = response.headers.get('X-RateLimit-Reset')\n\n\t\tconst retryAfter = retryAfterHeader ? Number.parseInt(retryAfterHeader, 10) : undefined\n\n\t\t// Use specialized RateLimitError for 429 responses\n\t\tif (response.status === 429) {\n\t\t\tthrow new RateLimitError(errorMessage || 'Too many requests', {\n\t\t\t\tstatus: response.status,\n\t\t\t\tdata: errorData,\n\t\t\t\tretryAfter,\n\t\t\t\tlimit: rateLimitLimit ? Number.parseInt(rateLimitLimit, 10) : undefined,\n\t\t\t\tremaining: rateLimitRemaining ? Number.parseInt(rateLimitRemaining, 10) : undefined,\n\t\t\t\tresetAt: rateLimitReset ? Number.parseInt(rateLimitReset, 10) : undefined,\n\t\t\t})\n\t\t}\n\n\t\tthrow new SylphxError(errorMessage, {\n\t\t\tcode: errorCode,\n\t\t\tstatus: response.status,\n\t\t\tdata: errorData,\n\t\t\tretryAfter,\n\t\t})\n\t}\n\n\t// Handle empty responses (204 No Content)\n\tconst text = await response.text()\n\tif (!text) {\n\t\treturn {} as TOutput\n\t}\n\n\t// Safe JSON parsing for response body\n\ttry {\n\t\treturn JSON.parse(text) as TOutput\n\t} catch (error) {\n\t\tthrow new SylphxError('Failed to parse response', {\n\t\t\tcode: 'PARSE_ERROR',\n\t\t\tcause: error instanceof Error ? error : undefined,\n\t\t\tdata: { body: text.slice(0, 200) }, // Include snippet for debugging\n\t\t})\n\t}\n}\n","/**\n * SDK Debug Mode\n *\n * Centralized debug logging for the SDK.\n *\n * Enable via:\n * - Browser: `localStorage.setItem('sylphx_debug', 'true')`\n * - Node.js: `SYLPHX_DEBUG=true`\n *\n * Debug messages are namespaced with [Sylphx] prefix for easy filtering.\n */\n\n// ============================================================================\n// Debug Configuration\n// ============================================================================\n\n/** Storage key for browser-side debug toggle */\nconst DEBUG_STORAGE_KEY = \"sylphx_debug\";\n\n/**\n * Check if debug mode is enabled\n *\n * Checks multiple sources in order:\n * 1. localStorage (browser)\n * 2. SYLPHX_DEBUG environment variable\n * 3. NODE_ENV === 'development' with explicit opt-in\n */\nfunction isDebugEnabled(): boolean {\n\t// Browser environment\n\tif (typeof window !== \"undefined\" && typeof localStorage !== \"undefined\") {\n\t\ttry {\n\t\t\treturn localStorage.getItem(DEBUG_STORAGE_KEY) === \"true\";\n\t\t} catch {\n\t\t\t// localStorage may be blocked in some contexts\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// Node.js environment\n\tif (typeof process !== \"undefined\" && process.env) {\n\t\treturn process.env.SYLPHX_DEBUG === \"true\";\n\t}\n\n\treturn false;\n}\n\n// Cache the debug state to avoid repeated localStorage/env checks\nlet debugModeCache: boolean | null = null;\n\n/**\n * Whether debug mode is currently enabled\n *\n * Cached after first access for performance.\n */\nexport function getDebugMode(): boolean {\n\tif (debugModeCache === null) {\n\t\tdebugModeCache = isDebugEnabled();\n\t}\n\treturn debugModeCache;\n}\n\n/**\n * Reset debug mode cache (for testing)\n */\nexport function resetDebugModeCache(): void {\n\tdebugModeCache = null;\n}\n\n// ============================================================================\n// Debug Logging\n// ============================================================================\n\n/** Debug log categories */\nexport type DebugCategory =\n\t| \"auth\"\n\t| \"api\"\n\t| \"analytics\"\n\t| \"flags\"\n\t| \"storage\"\n\t| \"cache\"\n\t| \"token\"\n\t| \"webhook\"\n\t| \"error\";\n\n/**\n * Log a debug message with category prefix\n *\n * @example\n * ```ts\n * debugLog('auth', 'Token refreshed', { expiresIn: 300 })\n * // [Sylphx auth] Token refreshed { expiresIn: 300 }\n * ```\n */\nexport function debugLog(\n\tcategory: DebugCategory,\n\tmessage: string,\n\tdata?: unknown,\n): void {\n\tif (!getDebugMode()) return;\n\n\tconst prefix = `[Sylphx ${category}]`;\n\n\tif (data !== undefined) {\n\t\tconsole.log(prefix, message, data);\n\t} else {\n\t\tconsole.log(prefix, message);\n\t}\n}\n\n/**\n * Log a debug warning with category prefix\n */\nexport function debugWarn(\n\tcategory: DebugCategory,\n\tmessage: string,\n\tdata?: unknown,\n): void {\n\tif (!getDebugMode()) return;\n\n\tconst prefix = `[Sylphx ${category}]`;\n\n\tif (data !== undefined) {\n\t\tconsole.warn(prefix, message, data);\n\t} else {\n\t\tconsole.warn(prefix, message);\n\t}\n}\n\n/**\n * Log a debug error with category prefix\n *\n * Note: This always logs when debug mode is enabled, regardless of error severity.\n * Production error tracking should use the error tracking service, not this.\n */\nexport function debugError(\n\tcategory: DebugCategory,\n\tmessage: string,\n\terror?: unknown,\n): void {\n\tif (!getDebugMode()) return;\n\n\tconst prefix = `[Sylphx ${category}]`;\n\n\tif (error !== undefined) {\n\t\tconsole.error(prefix, message, error);\n\t} else {\n\t\tconsole.error(prefix, message);\n\t}\n}\n\n// ============================================================================\n// Performance Timing\n// ============================================================================\n\n/**\n * Create a debug timer for measuring operation duration\n *\n * @example\n * ```ts\n * const timer = debugTimer('api', 'Fetching user profile')\n * // ... operation ...\n * timer.end() // Logs duration if debug mode enabled\n * ```\n */\nexport function debugTimer(\n\tcategory: DebugCategory,\n\toperation: string,\n): { end: () => void } {\n\tif (!getDebugMode()) {\n\t\treturn { end: () => {} };\n\t}\n\n\tconst start = performance.now();\n\n\treturn {\n\t\tend() {\n\t\t\tconst duration = performance.now() - start;\n\t\t\tdebugLog(category, `${operation} completed`, {\n\t\t\t\tdurationMs: Math.round(duration),\n\t\t\t});\n\t\t},\n\t};\n}\n\n// ============================================================================\n// Browser Console Helpers\n// ============================================================================\n\n/**\n * Enable debug mode from browser console\n *\n * Call this in the browser console to enable debug logging:\n * ```js\n * window.__sylphx?.enableDebug()\n * ```\n */\nexport function enableDebug(): void {\n\tif (typeof localStorage === \"undefined\") {\n\t\tconsole.warn(\n\t\t\t\"[Sylphx] Debug mode can only be enabled in browser environments\",\n\t\t);\n\t\treturn;\n\t}\n\n\ttry {\n\t\tlocalStorage.setItem(DEBUG_STORAGE_KEY, \"true\");\n\t\tdebugModeCache = true;\n\t\tconsole.log(\n\t\t\t\"[Sylphx] Debug mode enabled. Refresh the page to see debug logs.\",\n\t\t);\n\t} catch (e) {\n\t\tconsole.warn(\"[Sylphx] Failed to enable debug mode:\", e);\n\t}\n}\n\n/**\n * Disable debug mode from browser console\n */\nexport function disableDebug(): void {\n\tif (typeof localStorage === \"undefined\") {\n\t\tconsole.warn(\n\t\t\t\"[Sylphx] Debug mode can only be disabled in browser environments\",\n\t\t);\n\t\treturn;\n\t}\n\n\ttry {\n\t\tlocalStorage.removeItem(DEBUG_STORAGE_KEY);\n\t\tdebugModeCache = false;\n\t\tconsole.log(\"[Sylphx] Debug mode disabled.\");\n\t} catch (e) {\n\t\tconsole.warn(\"[Sylphx] Failed to disable debug mode:\", e);\n\t}\n}\n\n// ============================================================================\n// Global Window Helper (Browser Only)\n// ============================================================================\n\n/**\n * Install debug helpers on window.__sylphx\n *\n * This is called automatically when the SDK is loaded in the browser,\n * providing developers easy console access to debug utilities.\n */\nexport function installGlobalDebugHelpers(): void {\n\tif (typeof window === \"undefined\") return;\n\n\t// Use type assertion to extend window\n\tconst w = window as typeof window & {\n\t\t__sylphx?: {\n\t\t\tenableDebug: typeof enableDebug;\n\t\t\tdisableDebug: typeof disableDebug;\n\t\t\tisDebugEnabled: typeof getDebugMode;\n\t\t};\n\t};\n\n\tw.__sylphx = {\n\t\tenableDebug,\n\t\tdisableDebug,\n\t\tisDebugEnabled: getDebugMode,\n\t};\n}\n","/**\n * REST Client for Sylphx Platform\n *\n * Type-safe REST API client using openapi-fetch with full type inference\n * from OpenAPI specification. No tRPC dependencies.\n *\n * @example\n * ```typescript\n * import { createRestClient } from '@sylphx/sdk'\n *\n * const client = createRestClient({\n * secretKey: process.env.SYLPHX_SECRET_KEY!,\n * })\n *\n * // Full type inference from OpenAPI\n * const { data: user } = await client.GET('/auth/me')\n * const { data: plans } = await client.GET('/billing/plans')\n * const { data: result } = await client.POST('/auth/login', {\n * body: { email, password }\n * })\n * ```\n */\n\nimport createClient, { type Middleware } from 'openapi-fetch'\nimport {\n\tBASE_RETRY_DELAY_MS,\n\tCIRCUIT_BREAKER_FAILURE_THRESHOLD,\n\tCIRCUIT_BREAKER_OPEN_DURATION_MS,\n\tCIRCUIT_BREAKER_WINDOW_MS,\n\tDEFAULT_PLATFORM_URL,\n\tDEFAULT_TIMEOUT_MS,\n\tETAG_CACHE_MAX_ENTRIES,\n\tETAG_CACHE_TTL_MS,\n\tMAX_RETRY_DELAY_MS,\n\tSDK_API_PATH,\n\tSDK_PLATFORM,\n\tSDK_VERSION,\n} from './constants'\nimport { exponentialBackoff, isRetryableError } from './errors'\nimport type { paths } from './generated/api'\nimport { validateAndSanitizeSecretKey } from './key-validation'\n\n// Re-export types for consumers\nexport type { paths }\n\n/**\n * Retry configuration for automatic request retries\n */\nexport interface RetryConfig {\n\t/** Maximum number of retries (default: 3) */\n\tmaxRetries?: number\n\t/** Base delay in milliseconds (default: 1000) */\n\tbaseDelay?: number\n\t/** Maximum delay in milliseconds (default: 30000) */\n\tmaxDelay?: number\n\t/** Custom function to determine if error is retryable */\n\tshouldRetry?: (status: number, attempt: number) => boolean\n\t/** Request timeout in milliseconds (default: 30000) */\n\ttimeout?: number\n}\n\n/**\n * Request deduplication configuration\n */\nexport interface DeduplicationConfig {\n\t/** Enable request deduplication (default: true) */\n\tenabled?: boolean\n\t/** HTTP methods to deduplicate (default: ['GET']) */\n\tmethods?: ('GET' | 'POST' | 'PUT' | 'DELETE')[]\n}\n\n/**\n * Circuit breaker configuration (AWS/Resilience4j pattern)\n *\n * Prevents cascade failures by fast-failing when service is unhealthy.\n * States: CLOSED (normal) → OPEN (failing) → HALF_OPEN (testing)\n */\nexport interface CircuitBreakerConfig {\n\t/** Enable circuit breaker (default: true) */\n\tenabled?: boolean\n\t/** Number of failures before opening circuit (default: 5) */\n\tfailureThreshold?: number\n\t/** Time window for counting failures in ms (default: 10000) */\n\twindowMs?: number\n\t/** How long circuit stays open in ms (default: 30000) */\n\topenDurationMs?: number\n\t/** Custom function to determine if response is a failure */\n\tisFailure?: (status: number) => boolean\n}\n\n/**\n * ETag/Conditional request configuration (HTTP caching pattern)\n *\n * Enables HTTP conditional requests with If-None-Match header\n * to avoid re-downloading unchanged data (saves bandwidth).\n */\nexport interface ETagConfig {\n\t/** Enable ETag caching (default: true for GET requests) */\n\tenabled?: boolean\n\t/** Maximum cache entries (default: 100) */\n\tmaxEntries?: number\n\t/** Cache TTL in milliseconds (default: 5 minutes) */\n\tttlMs?: number\n}\n\n/**\n * Configuration for the REST client\n *\n * The app key identifies the app — no separate app ID needed.\n */\nexport interface RestClientConfig {\n\t/**\n\t * Your app key — identifies the app and environment.\n\t *\n\t * Accepts either:\n\t * - Secret key (sk_dev_, sk_stg_, sk_prod_) — full access, server-side only\n\t * - Publishable key (app_dev_, app_stg_, app_prod_) — limited access, safe for client\n\t */\n\tsecretKey: string\n\t/** Platform URL (default: https://sylphx.com) */\n\tplatformUrl?: string\n\t/** Retry configuration (default: 3 retries with exponential backoff) */\n\tretry?: RetryConfig | false\n\t/**\n\t * Request deduplication configuration (default: enabled for GET)\n\t *\n\t * Prevents duplicate concurrent requests for the same resource.\n\t * When multiple components request the same data simultaneously,\n\t * only one API call is made and the result is shared.\n\t */\n\tdeduplication?: DeduplicationConfig | false\n\t/**\n\t * Circuit breaker configuration (default: enabled)\n\t *\n\t * Prevents cascade failures by fast-failing when service is unhealthy.\n\t * Opens after 5 failures in 10s, stays open for 30s, then allows test request.\n\t */\n\tcircuitBreaker?: CircuitBreakerConfig | false\n\t/**\n\t * ETag caching configuration (default: enabled for GET)\n\t *\n\t * Uses HTTP conditional requests to avoid re-downloading unchanged data.\n\t * Saves bandwidth by returning 304 Not Modified when content hasn't changed.\n\t */\n\tetag?: ETagConfig | false\n}\n\n/**\n * Dynamic configuration that can change at runtime (e.g., access token)\n */\nexport interface RestDynamicConfig {\n\t/** Your secret key (sk_* or app_*) — identifies the app */\n\tsecretKey?: string\n\t/** Platform URL (default: https://sylphx.com) */\n\tplatformUrl?: string\n\t/** Get the current access token (called on each request) */\n\tgetAccessToken?: () => string | null | undefined\n\t/** Retry configuration (default: 3 retries with exponential backoff) */\n\tretry?: RetryConfig | false\n\t/** Request deduplication configuration (default: enabled for GET) */\n\tdeduplication?: DeduplicationConfig | false\n\t/** Circuit breaker configuration (default: enabled) */\n\tcircuitBreaker?: CircuitBreakerConfig | false\n\t/** ETag caching configuration (default: enabled for GET) */\n\tetag?: ETagConfig | false\n}\n\n/**\n * Create auth middleware that adds app credentials, access token, and SDK headers\n */\nfunction createAuthMiddleware(config: RestDynamicConfig): Middleware {\n\treturn {\n\t\tasync onRequest({ request }) {\n\t\t\t// Add SDK identification headers for debugging and analytics\n\t\t\trequest.headers.set('X-SDK-Version', SDK_VERSION)\n\t\t\trequest.headers.set('X-SDK-Platform', SDK_PLATFORM)\n\n\t\t\t// Add secret key if provided — identifies the app\n\t\t\tif (config.secretKey) {\n\t\t\t\trequest.headers.set('x-app-secret', config.secretKey)\n\t\t\t}\n\n\t\t\t// Add access token if available\n\t\t\tconst token = config.getAccessToken?.()\n\t\t\tif (token) {\n\t\t\t\trequest.headers.set('Authorization', `Bearer ${token}`)\n\t\t\t}\n\n\t\t\treturn request\n\t\t},\n\t}\n}\n\n/**\n * Check if a status code is retryable\n */\nfunction isRetryableStatus(status: number): boolean {\n\treturn status >= 500 || status === 429\n}\n\n// ============================================================================\n// Request Deduplication (React Query/SWR pattern)\n// ============================================================================\n\n/**\n * In-flight request tracking for deduplication\n *\n * When the same request is made multiple times concurrently,\n * we return the existing promise instead of making a new request.\n * This prevents duplicate API calls and improves efficiency.\n */\nconst inFlightRequests = new Map<string, Promise<Response>>()\n\n/**\n * Generate a unique key for a request (for deduplication)\n */\nasync function getRequestKey(request: Request): Promise<string> {\n\tconst body = request.body ? await request.clone().text() : ''\n\treturn `${request.method}:${request.url}:${body}`\n}\n\n/**\n * Create request deduplication middleware (React Query/SWR pattern)\n *\n * Features:\n * - Deduplicates concurrent identical requests\n * - Only applies to GET requests by default (safe to dedupe)\n * - POST/PUT/DELETE are always executed (mutations must run)\n * - Cleans up in-flight tracking after completion\n *\n * @param config - Whether to enable deduplication (default: GET only)\n */\nfunction createDeduplicationMiddleware(\n\tconfig: { enabled?: boolean; methods?: ('GET' | 'POST' | 'PUT' | 'DELETE')[] } = {},\n): Middleware {\n\tconst { enabled = true, methods = ['GET'] } = config\n\n\tif (!enabled) {\n\t\treturn {\n\t\t\tasync onRequest({ request }) {\n\t\t\t\treturn request\n\t\t\t},\n\t\t}\n\t}\n\n\treturn {\n\t\tasync onRequest({ request }) {\n\t\t\t// Only dedupe specified methods (default: GET only)\n\t\t\tif (!methods.includes(request.method as 'GET')) {\n\t\t\t\treturn request\n\t\t\t}\n\n\t\t\tconst key = await getRequestKey(request)\n\n\t\t\t// Check if there's an in-flight request\n\t\t\tconst existing = inFlightRequests.get(key)\n\t\t\tif (existing) {\n\t\t\t\t// Return a new Request that will be handled specially in onResponse\n\t\t\t\tconst deduped = request.clone()\n\t\t\t\t;(deduped as unknown as { _dedupKey: string })._dedupKey = key\n\t\t\t\treturn deduped\n\t\t\t}\n\t\t\t// Mark request key (so onResponse knows to track it)\n\t\t\t;(request as unknown as { _dedupKey: string })._dedupKey = key\n\t\t\treturn request\n\t\t},\n\t\tasync onResponse({ request, response }) {\n\t\t\tconst key = (request as unknown as { _dedupKey?: string })._dedupKey\n\t\t\tif (!key) return response\n\n\t\t\t// If there's already an in-flight request, wait for it\n\t\t\tconst existing = inFlightRequests.get(key)\n\t\t\tif (existing && inFlightRequests.get(key) !== undefined) {\n\t\t\t\t// Another request is in flight, clone its response\n\t\t\t\tconst cachedResponse = await existing\n\t\t\t\treturn cachedResponse.clone()\n\t\t\t}\n\n\t\t\t// This is the first request, track it\n\t\t\tconst responsePromise = Promise.resolve(response.clone())\n\t\t\tinFlightRequests.set(key, responsePromise)\n\n\t\t\t// Clean up after response is consumed\n\t\t\tresponsePromise.finally(() => {\n\t\t\t\t// Small delay to allow concurrent requests to find the cached response\n\t\t\t\tsetTimeout(() => inFlightRequests.delete(key), 100)\n\t\t\t})\n\n\t\t\treturn response\n\t\t},\n\t}\n}\n\n// ============================================================================\n// Circuit Breaker (AWS/Resilience4j pattern)\n// ============================================================================\n\n/**\n * Circuit breaker state machine\n *\n * CLOSED: Normal operation, requests pass through\n * OPEN: Service unhealthy, all requests fast-fail\n * HALF_OPEN: Testing recovery, allows one request\n */\nexport type CircuitState = 'CLOSED' | 'OPEN' | 'HALF_OPEN'\n\n/**\n * Error thrown when circuit is open\n */\nexport class CircuitBreakerOpenError extends Error {\n\treadonly remainingMs: number\n\n\tconstructor(remainingMs: number) {\n\t\tsuper(`Circuit breaker is open. Retry after ${Math.ceil(remainingMs / 1000)}s`)\n\t\tthis.name = 'CircuitBreakerOpenError'\n\t\tthis.remainingMs = remainingMs\n\t}\n}\n\n/**\n * Circuit breaker instance with state management\n */\ninterface CircuitBreaker {\n\tstate: CircuitState\n\tfailures: number[]\n\topenedAt: number | null\n\tconfig: Required<CircuitBreakerConfig>\n}\n\n/**\n * Create a fresh circuit breaker instance.\n *\n * Each REST client gets its own instance — no shared module-level singleton.\n * This prevents cross-client state bleed and makes testing reliable.\n */\nfunction createCircuitBreakerInstance(config: CircuitBreakerConfig = {}): CircuitBreaker {\n\treturn {\n\t\tstate: 'CLOSED',\n\t\tfailures: [],\n\t\topenedAt: null,\n\t\tconfig: {\n\t\t\tenabled: config.enabled ?? true,\n\t\t\tfailureThreshold: config.failureThreshold ?? CIRCUIT_BREAKER_FAILURE_THRESHOLD,\n\t\t\twindowMs: config.windowMs ?? CIRCUIT_BREAKER_WINDOW_MS,\n\t\t\topenDurationMs: config.openDurationMs ?? CIRCUIT_BREAKER_OPEN_DURATION_MS,\n\t\t\tisFailure: config.isFailure ?? ((status) => status >= 500 || status === 429),\n\t\t},\n\t}\n}\n\n/**\n * Record a failure and potentially open the circuit\n */\nfunction recordFailure(cb: CircuitBreaker): void {\n\tconst now = Date.now()\n\n\t// Remove old failures outside the window\n\tcb.failures = cb.failures.filter((t) => now - t < cb.config.windowMs)\n\n\t// Add new failure\n\tcb.failures.push(now)\n\n\t// Check if threshold exceeded\n\tif (cb.failures.length >= cb.config.failureThreshold) {\n\t\tcb.state = 'OPEN'\n\t\tcb.openedAt = now\n\t}\n}\n\n/**\n * Record a success and potentially close the circuit\n */\nfunction recordSuccess(cb: CircuitBreaker): void {\n\tif (cb.state === 'HALF_OPEN') {\n\t\t// Test request succeeded, close the circuit\n\t\tcb.state = 'CLOSED'\n\t\tcb.failures = []\n\t\tcb.openedAt = null\n\t}\n}\n\n/**\n * Check if circuit should allow request\n */\nfunction shouldAllowRequest(cb: CircuitBreaker): {\n\tallowed: boolean\n\tremainingMs?: number\n} {\n\tconst now = Date.now()\n\n\tswitch (cb.state) {\n\t\tcase 'CLOSED':\n\t\t\treturn { allowed: true }\n\n\t\tcase 'OPEN': {\n\t\t\tconst elapsed = now - (cb.openedAt ?? now)\n\t\t\tif (elapsed >= cb.config.openDurationMs) {\n\t\t\t\t// Timeout expired, transition to half-open\n\t\t\t\tcb.state = 'HALF_OPEN'\n\t\t\t\treturn { allowed: true }\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tallowed: false,\n\t\t\t\tremainingMs: cb.config.openDurationMs - elapsed,\n\t\t\t}\n\t\t}\n\n\t\tcase 'HALF_OPEN':\n\t\t\t// Only allow one test request at a time\n\t\t\t// In production, you'd use a flag to track if test is in progress\n\t\t\treturn { allowed: true }\n\n\t\tdefault:\n\t\t\treturn { allowed: true }\n\t}\n}\n\n/**\n * Create circuit breaker middleware (AWS/Resilience4j pattern)\n *\n * Features:\n * - Fast-fails when service is unhealthy (prevents cascade failures)\n * - Auto-recovery with half-open state for testing\n * - Configurable failure threshold and timeout\n * - Only counts server errors (5xx) and rate limits (429)\n * - Per-client instance: no shared module-level state\n */\nfunction createCircuitBreakerMiddleware(\n\tconfig: CircuitBreakerConfig | false | undefined,\n): Middleware {\n\tif (config === false) {\n\t\treturn {\n\t\t\tasync onRequest({ request }) {\n\t\t\t\treturn request\n\t\t\t},\n\t\t}\n\t}\n\n\t// Create a fresh circuit breaker per client (not a module-level singleton)\n\tconst cb = createCircuitBreakerInstance(config ?? {})\n\n\t// Track for deprecated getCircuitBreakerState() / resetCircuitBreaker() test helpers\n\t_lastCircuitBreaker = cb\n\n\treturn {\n\t\tasync onRequest({ request }) {\n\t\t\tif (!cb.config.enabled) {\n\t\t\t\treturn request\n\t\t\t}\n\n\t\t\tconst check = shouldAllowRequest(cb)\n\t\t\tif (!check.allowed) {\n\t\t\t\tthrow new CircuitBreakerOpenError(check.remainingMs!)\n\t\t\t}\n\n\t\t\treturn request\n\t\t},\n\t\tasync onResponse({ response }) {\n\t\t\tif (!cb.config.enabled) {\n\t\t\t\treturn response\n\t\t\t}\n\n\t\t\tif (cb.config.isFailure(response.status)) {\n\t\t\t\trecordFailure(cb)\n\t\t\t} else {\n\t\t\t\trecordSuccess(cb)\n\t\t\t}\n\n\t\t\treturn response\n\t\t},\n\t}\n}\n\n/**\n * Module-level reference to the most recently created circuit breaker.\n *\n * Used ONLY by the deprecated `getCircuitBreakerState()` / `resetCircuitBreaker()`\n * helpers (kept for backward compatibility with test suites).\n *\n * Production code should use `client.circuitBreaker.getState()` / `.reset()`\n * from the client object returned by `createRestClient()`.\n */\nlet _lastCircuitBreaker: CircuitBreaker | null = null\n\n/**\n * @deprecated Prefer creating a new `createRestClient()` for isolated state in tests.\n * Resets the most recently created circuit breaker and clears the reference,\n * so `getCircuitBreakerState()` returns null until the next client is created.\n */\nexport function resetCircuitBreaker(): void {\n\tif (_lastCircuitBreaker) {\n\t\t_lastCircuitBreaker.state = 'CLOSED'\n\t\t_lastCircuitBreaker.failures = []\n\t\t_lastCircuitBreaker.openedAt = null\n\t}\n\t_lastCircuitBreaker = null\n}\n\n/**\n * @deprecated Prefer `client.circuitBreaker.getState()` for per-instance state.\n * Returns state of the most recently created circuit breaker (test helper only).\n */\nexport function getCircuitBreakerState(): {\n\tstate: CircuitState\n\tfailures: number\n\topenedAt: number | null\n} | null {\n\tif (!_lastCircuitBreaker) return null\n\treturn {\n\t\tstate: _lastCircuitBreaker.state,\n\t\tfailures: _lastCircuitBreaker.failures.length,\n\t\topenedAt: _lastCircuitBreaker.openedAt,\n\t}\n}\n\n// ============================================================================\n// ETag Cache (HTTP conditional requests)\n// ============================================================================\n\n/**\n * Cached response entry with ETag\n */\ninterface ETagCacheEntry {\n\tetag: string\n\tbody: string\n\ttimestamp: number\n}\n\n/**\n * ETag cache with LRU eviction\n */\nconst etagCache = new Map<string, ETagCacheEntry>()\n\n/**\n * Generate cache key for request\n */\nfunction getETagCacheKey(request: Request): string {\n\treturn `${request.method}:${request.url}`\n}\n\n/**\n * Evict oldest entries when cache is full\n */\nfunction evictOldEntries(maxEntries: number, ttlMs: number): void {\n\tconst now = Date.now()\n\n\t// First, remove expired entries\n\tfor (const [key, entry] of etagCache) {\n\t\tif (now - entry.timestamp > ttlMs) {\n\t\t\tetagCache.delete(key)\n\t\t}\n\t}\n\n\t// If still over limit, remove oldest entries (LRU)\n\tif (etagCache.size > maxEntries) {\n\t\tconst entries = Array.from(etagCache.entries())\n\t\tentries.sort((a, b) => a[1].timestamp - b[1].timestamp)\n\n\t\tconst toRemove = entries.slice(0, entries.length - maxEntries)\n\t\tfor (const [key] of toRemove) {\n\t\t\tetagCache.delete(key)\n\t\t}\n\t}\n}\n\n/**\n * Create ETag middleware for HTTP conditional requests\n *\n * Features:\n * - Caches responses with ETag headers\n * - Sends If-None-Match on subsequent requests\n * - Returns cached response on 304 Not Modified\n * - LRU eviction when cache is full\n * - TTL-based expiration\n */\nfunction createETagMiddleware(config: ETagConfig | false | undefined): Middleware {\n\tif (config === false) {\n\t\treturn {\n\t\t\tasync onRequest({ request }) {\n\t\t\t\treturn request\n\t\t\t},\n\t\t}\n\t}\n\n\tconst {\n\t\tenabled = true,\n\t\tmaxEntries = ETAG_CACHE_MAX_ENTRIES,\n\t\tttlMs = ETAG_CACHE_TTL_MS,\n\t} = config ?? {}\n\n\tif (!enabled) {\n\t\treturn {\n\t\t\tasync onRequest({ request }) {\n\t\t\t\treturn request\n\t\t\t},\n\t\t}\n\t}\n\n\treturn {\n\t\tasync onRequest({ request }) {\n\t\t\t// Only cache GET requests\n\t\t\tif (request.method !== 'GET') {\n\t\t\t\treturn request\n\t\t\t}\n\n\t\t\tconst cacheKey = getETagCacheKey(request)\n\t\t\tconst cached = etagCache.get(cacheKey)\n\n\t\t\tif (cached) {\n\t\t\t\t// Check TTL\n\t\t\t\tif (Date.now() - cached.timestamp > ttlMs) {\n\t\t\t\t\tetagCache.delete(cacheKey)\n\t\t\t\t} else {\n\t\t\t\t\t// Add If-None-Match header\n\t\t\t\t\trequest.headers.set('If-None-Match', cached.etag)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn request\n\t\t},\n\t\tasync onResponse({ request, response }) {\n\t\t\t// Only cache GET requests\n\t\t\tif (request.method !== 'GET') {\n\t\t\t\treturn response\n\t\t\t}\n\n\t\t\tconst cacheKey = getETagCacheKey(request)\n\n\t\t\t// Handle 304 Not Modified\n\t\t\tif (response.status === 304) {\n\t\t\t\tconst cached = etagCache.get(cacheKey)\n\t\t\t\tif (cached) {\n\t\t\t\t\t// Update timestamp (LRU)\n\t\t\t\t\tcached.timestamp = Date.now()\n\n\t\t\t\t\t// Return cached response with original body\n\t\t\t\t\treturn new Response(cached.body, {\n\t\t\t\t\t\tstatus: 200,\n\t\t\t\t\t\theaders: response.headers,\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// No cache, return original response\n\t\t\t\treturn response\n\t\t\t}\n\n\t\t\t// Cache successful responses with ETag\n\t\t\tif (response.ok) {\n\t\t\t\tconst etag = response.headers.get('ETag')\n\t\t\t\tif (etag) {\n\t\t\t\t\t// Clone response to read body (can only read once)\n\t\t\t\t\tconst cloned = response.clone()\n\t\t\t\t\tconst body = await cloned.text()\n\n\t\t\t\t\t// Evict old entries if needed\n\t\t\t\t\tevictOldEntries(maxEntries, ttlMs)\n\n\t\t\t\t\t// Cache the response\n\t\t\t\t\tetagCache.set(cacheKey, {\n\t\t\t\t\t\tetag,\n\t\t\t\t\t\tbody,\n\t\t\t\t\t\ttimestamp: Date.now(),\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn response\n\t\t},\n\t}\n}\n\n/**\n * Clear ETag cache (for testing)\n */\nexport function clearETagCache(): void {\n\tetagCache.clear()\n}\n\n/**\n * Get ETag cache stats (for monitoring)\n */\nexport function getETagCacheStats(): { size: number; entries: string[] } {\n\treturn {\n\t\tsize: etagCache.size,\n\t\tentries: Array.from(etagCache.keys()),\n\t}\n}\n\n// ============================================================================\n// Retry Middleware\n// ============================================================================\n\n/**\n * Per-request body storage for retry middleware.\n *\n * Using a WeakMap<Request, string | null> ensures each request has its own\n * stored body, preventing race conditions when multiple concurrent requests\n * use the same client instance.\n *\n * WeakMap keys are garbage-collected when the Request object is GC'd,\n * so no manual cleanup is needed.\n */\nconst retryBodyMap = new WeakMap<Request, string | null>()\n\n/**\n * Create retry middleware with exponential backoff and timeout\n *\n * Features:\n * - Request timeout (default 30s) prevents infinite hangs\n * - Exponential backoff with jitter for retries\n * - Respects Retry-After header for rate limiting\n * - Per-request body storage (WeakMap) — safe for concurrent requests\n */\nfunction createRetryMiddleware(retryConfig: RetryConfig | false | undefined): Middleware {\n\tif (retryConfig === false) {\n\t\t// No-op middleware - just passes through\n\t\treturn {\n\t\t\tasync onResponse({ response }) {\n\t\t\t\treturn response\n\t\t\t},\n\t\t}\n\t}\n\n\tconst {\n\t\tmaxRetries = 3,\n\t\tbaseDelay = BASE_RETRY_DELAY_MS,\n\t\tmaxDelay = MAX_RETRY_DELAY_MS,\n\t\tshouldRetry = isRetryableStatus,\n\t\ttimeout = DEFAULT_TIMEOUT_MS,\n\t} = retryConfig ?? {}\n\n\treturn {\n\t\tasync onRequest({ request }) {\n\t\t\t// Read body before it's consumed, store per-request in WeakMap\n\t\t\tconst body = request.body ? await request.clone().text() : null\n\n\t\t\t// Add timeout signal\n\t\t\tconst controller = new AbortController()\n\t\t\tsetTimeout(() => controller.abort(), timeout)\n\n\t\t\tconst newRequest = new Request(request.url, {\n\t\t\t\tmethod: request.method,\n\t\t\t\theaders: request.headers,\n\t\t\t\tbody,\n\t\t\t\tsignal: controller.signal,\n\t\t\t})\n\n\t\t\t// Associate body with this specific request object (concurrent-safe)\n\t\t\tretryBodyMap.set(newRequest, body)\n\n\t\t\treturn newRequest\n\t\t},\n\t\tasync onResponse({ response, request }) {\n\t\t\t// Retrieve body for this specific request (not shared across requests)\n\t\t\tconst originalBody = retryBodyMap.get(request) ?? null\n\n\t\t\tlet attempt = 0\n\t\t\tlet currentResponse = response\n\n\t\t\t// Check if we need to retry using the shouldRetry callback\n\t\t\twhile (attempt < maxRetries && shouldRetry(currentResponse.status, attempt)) {\n\t\t\t\tconst retryAfter = currentResponse.headers.get('Retry-After')\n\t\t\t\tconst delay = retryAfter\n\t\t\t\t\t? Number.parseInt(retryAfter, 10) * 1000\n\t\t\t\t\t: exponentialBackoff(attempt, baseDelay, maxDelay)\n\n\t\t\t\tawait new Promise((resolve) => setTimeout(resolve, delay))\n\t\t\t\tattempt++\n\n\t\t\t\t// Create timeout for retry\n\t\t\t\tconst controller = new AbortController()\n\t\t\t\tconst timeoutId = setTimeout(() => controller.abort(), timeout)\n\n\t\t\t\ttry {\n\t\t\t\t\t// Reconstruct request with per-request stored body and new signal\n\t\t\t\t\tconst retryRequest = new Request(request.url, {\n\t\t\t\t\t\tmethod: request.method,\n\t\t\t\t\t\theaders: request.headers,\n\t\t\t\t\t\tbody: originalBody,\n\t\t\t\t\t\tsignal: controller.signal,\n\t\t\t\t\t})\n\n\t\t\t\t\tconst newResponse = await fetch(retryRequest)\n\t\t\t\t\tclearTimeout(timeoutId)\n\n\t\t\t\t\t// If successful or non-retryable client error, return\n\t\t\t\t\tif (newResponse.ok || !shouldRetry(newResponse.status, attempt)) {\n\t\t\t\t\t\tretryBodyMap.delete(request) // cleanup\n\t\t\t\t\t\treturn newResponse\n\t\t\t\t\t}\n\n\t\t\t\t\tcurrentResponse = newResponse\n\t\t\t\t} catch (error) {\n\t\t\t\t\tclearTimeout(timeoutId)\n\t\t\t\t\t// On network/timeout error during retry, continue to next attempt\n\t\t\t\t\tif (attempt >= maxRetries) {\n\t\t\t\t\t\tretryBodyMap.delete(request) // cleanup\n\t\t\t\t\t\tthrow error\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tretryBodyMap.delete(request) // cleanup\n\t\t\treturn currentResponse\n\t\t},\n\t}\n}\n\n/**\n * Create a type-safe REST API client\n *\n * Uses openapi-fetch with full type inference from OpenAPI specification.\n * All endpoints, inputs, and outputs are automatically typed.\n * Includes automatic retry with exponential backoff for transient failures.\n *\n * @example\n * ```typescript\n * const client = createRestClient({\n * secretKey: process.env.SYLPHX_SECRET_KEY!,\n * })\n *\n * // GET requests\n * const { data: user } = await client.GET('/auth/me')\n * const { data: plans } = await client.GET('/billing/plans')\n *\n * // POST requests\n * const { data: result } = await client.POST('/auth/login', {\n * body: { email: 'test@example.com', password: 'secret' }\n * })\n * ```\n */\n/**\n * Validate and sanitize REST client configuration (SSOT helper)\n */\nfunction validateClientConfig(config: { secretKey?: string; platformUrl?: string }) {\n\treturn {\n\t\tsecretKey: validateAndSanitizeSecretKey(config.secretKey),\n\t\tbaseUrl: (config.platformUrl || DEFAULT_PLATFORM_URL).trim(),\n\t}\n}\n\nexport function createRestClient(config: RestClientConfig) {\n\tconst { secretKey, baseUrl } = validateClientConfig(config)\n\n\tconst client = createClient<paths>({\n\t\tbaseUrl: `${baseUrl}${SDK_API_PATH}`,\n\t\theaders: {\n\t\t\t'Content-Type': 'application/json',\n\t\t\t'x-app-secret': secretKey,\n\t\t},\n\t})\n\n\t// Add deduplication middleware first (before other middleware)\n\tif (config.deduplication !== false) {\n\t\tclient.use(createDeduplicationMiddleware(config.deduplication))\n\t}\n\n\t// Add circuit breaker middleware (before retry)\n\tif (config.circuitBreaker !== false) {\n\t\tclient.use(createCircuitBreakerMiddleware(config.circuitBreaker))\n\t}\n\n\t// Add ETag caching middleware (before retry, for HTTP conditional requests)\n\tif (config.etag !== false) {\n\t\tclient.use(createETagMiddleware(config.etag))\n\t}\n\n\t// Add retry middleware (last, so it can retry after circuit allows)\n\tclient.use(createRetryMiddleware(config.retry))\n\n\treturn client\n}\n\n/**\n * Create a dynamic REST client with runtime token injection\n *\n * Use this when you need to inject an access token that may change.\n * Tokens should be read from HttpOnly cookies via a server endpoint,\n * never from localStorage (XSS vulnerability).\n *\n * @example\n * ```typescript\n * // Server-side usage\n * const client = createDynamicRestClient({\n * secretKey: process.env.SYLPHX_SECRET_KEY!,\n * getAccessToken: async () => (await cookies()).get('session')?.value,\n * })\n * ```\n */\nexport function createDynamicRestClient(config: RestDynamicConfig) {\n\tconst { secretKey, baseUrl } = validateClientConfig(config)\n\n\t// Create validated config for middleware\n\tconst validatedConfig: RestDynamicConfig = {\n\t\t...config,\n\t\tsecretKey,\n\t\tplatformUrl: baseUrl,\n\t}\n\n\tconst client = createClient<paths>({\n\t\tbaseUrl: `${baseUrl}${SDK_API_PATH}`,\n\t\theaders: {\n\t\t\t'Content-Type': 'application/json',\n\t\t},\n\t})\n\n\t// Add deduplication middleware first (before other middleware)\n\tif (config.deduplication !== false) {\n\t\tclient.use(createDeduplicationMiddleware(config.deduplication))\n\t}\n\n\t// Add auth middleware (runs on each request)\n\tclient.use(createAuthMiddleware(validatedConfig))\n\n\t// Add circuit breaker middleware (before retry)\n\tif (config.circuitBreaker !== false) {\n\t\tclient.use(createCircuitBreakerMiddleware(config.circuitBreaker))\n\t}\n\n\t// Add ETag caching middleware (before retry, for HTTP conditional requests)\n\tif (config.etag !== false) {\n\t\tclient.use(createETagMiddleware(config.etag))\n\t}\n\n\t// Add retry middleware (last, so it can retry after circuit allows)\n\tclient.use(createRetryMiddleware(config.retry))\n\n\treturn client\n}\n\n/**\n * Type for the REST client instance\n */\nexport type RestClient = ReturnType<typeof createRestClient>\nexport type DynamicRestClient = ReturnType<typeof createDynamicRestClient>\n\n/**\n * Check if a REST response has an error\n */\nexport function hasError<T, E>(response: {\n\tdata?: T\n\terror?: E\n}): response is {\n\tdata: undefined\n\terror: E\n} {\n\treturn response.error !== undefined\n}\n\n/**\n * Extract error message from REST error response\n */\nexport function getRestErrorMessage(error: unknown): string {\n\tif (error && typeof error === 'object' && 'error' in error) {\n\t\tconst err = error as { error?: { message?: string } }\n\t\treturn err.error?.message ?? 'An unknown error occurred'\n\t}\n\tif (error instanceof Error) {\n\t\treturn error.message\n\t}\n\treturn 'An unknown error occurred'\n}\n","/**\n * Auth Functions\n *\n * Pure functions for authentication - no hidden state.\n * Each function takes config as the first parameter.\n *\n * Uses REST API at /api/sdk/auth/* for all operations.\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { callApi, type SylphxConfig } from './config'\nimport type { components } from './generated/api'\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type LoginRequest = components['schemas']['LoginRequest']\nexport type LoginResponse = components['schemas']['LoginResponse']\nexport type RegisterRequest = components['schemas']['RegisterRequest']\nexport type RegisterResponse = components['schemas']['RegisterResponse']\nexport type TokenResponse = components['schemas']['TokenResponse']\nexport type TwoFactorVerifyRequest = components['schemas']['TwoFactorVerifyRequest']\nexport type MeResponse = components['schemas']['MeResponse']\n\n// SDK-specific types (not directly from API schema)\n/**\n * Token introspection result (RFC 7662)\n */\nexport interface TokenIntrospectionResult {\n\t/** Whether the token is active/valid */\n\tactive: boolean\n\t/** Token type (access_token or refresh_token) */\n\ttoken_type?: 'access_token' | 'refresh_token'\n\t/** User ID */\n\tsub?: string\n\t/** User email */\n\temail?: string\n\t/** User name */\n\tname?: string\n\t/** App ID */\n\tclient_id?: string\n\t/** Audience */\n\taud?: string\n\t/** Issuer */\n\tiss?: string\n\t/** Expiration time (Unix timestamp) */\n\texp?: number\n\t/** Issued at time (Unix timestamp) */\n\tiat?: number\n\t/** User role */\n\trole?: string\n\t/** Email verification status */\n\temail_verified?: boolean\n}\n\n/**\n * Token revocation options\n */\nexport interface RevokeTokenOptions {\n\t/** Revoke all tokens for a user in this app */\n\trevokeAll?: boolean\n\t/** User ID (required when revoking all) */\n\tuserId?: string\n}\n\n// SDK-specific types (not in generated API)\nexport interface SessionResult {\n\tuser: {\n\t\tid: string\n\t\temail: string\n\t\tname: string | null\n\t\timage: string | null\n\t\temailVerified: boolean\n\t} | null\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Sign in with email and password\n *\n * @example\n * ```typescript\n * const result = await signIn(config, { email: 'user@example.com', password: 'secret' })\n * if (result.requiresTwoFactor) {\n * // Handle 2FA flow\n * } else {\n * // Save tokens\n * const authenticatedConfig = withToken(config, result.accessToken!)\n * }\n * ```\n */\nexport async function signIn(config: SylphxConfig, input: LoginRequest): Promise<LoginResponse> {\n\treturn callApi<LoginResponse>(config, '/auth/login', {\n\t\tmethod: 'POST',\n\t\tbody: input,\n\t})\n}\n\n/**\n * Sign up with email and password\n *\n * @example\n * ```typescript\n * const result = await signUp(config, {\n * email: 'user@example.com',\n * password: 'secret',\n * name: 'John Doe',\n * })\n * // User needs to verify email\n * ```\n */\nexport async function signUp(\n\tconfig: SylphxConfig,\n\tinput: RegisterRequest,\n): Promise<RegisterResponse> {\n\treturn callApi<RegisterResponse>(config, '/auth/register', {\n\t\tmethod: 'POST',\n\t\tbody: input,\n\t})\n}\n\n/**\n * Sign out (revoke tokens)\n *\n * @example\n * ```typescript\n * await signOut(config)\n * ```\n */\nexport async function signOut(config: SylphxConfig): Promise<void> {\n\tawait callApi<void>(config, '/auth/logout', { method: 'POST' })\n}\n\n/**\n * Refresh access token\n *\n * @example\n * ```typescript\n * const tokens = await refreshToken(config, refreshTokenString)\n * const newConfig = withToken(config, tokens.accessToken)\n * ```\n */\nexport async function refreshToken(config: SylphxConfig, token: string): Promise<TokenResponse> {\n\treturn callApi<TokenResponse>(config, '/auth/token', {\n\t\tmethod: 'POST',\n\t\tbody: {\n\t\t\tgrant_type: 'refresh_token',\n\t\t\trefresh_token: token,\n\t\t\tclient_secret: config.secretKey,\n\t\t},\n\t})\n}\n\n/**\n * Verify email with token\n *\n * @example\n * ```typescript\n * await verifyEmail(config, token)\n * ```\n */\nexport async function verifyEmail(config: SylphxConfig, token: string): Promise<void> {\n\tawait callApi<void>(config, '/auth/verify-email', {\n\t\tmethod: 'POST',\n\t\tbody: { token },\n\t})\n}\n\n/**\n * Request password reset email\n *\n * @example\n * ```typescript\n * await forgotPassword(config, 'user@example.com')\n * ```\n */\nexport async function forgotPassword(config: SylphxConfig, email: string): Promise<void> {\n\tawait callApi<{ success: boolean }>(config, '/auth/forgot-password', {\n\t\tmethod: 'POST',\n\t\tbody: { email },\n\t})\n}\n\n/**\n * Reset password with token\n *\n * @example\n * ```typescript\n * await resetPassword(config, { token, password: 'newpassword' })\n * ```\n */\nexport async function resetPassword(\n\tconfig: SylphxConfig,\n\tinput: { token: string; password: string },\n): Promise<void> {\n\tawait callApi<{ success: boolean }>(config, '/auth/reset-password', {\n\t\tmethod: 'POST',\n\t\tbody: { token: input.token, newPassword: input.password },\n\t})\n}\n\n/**\n * Get current session (requires authenticated config)\n *\n * @example\n * ```typescript\n * const session = await getSession(authenticatedConfig)\n * if (session.user) {\n * console.log(`Logged in as ${session.user.email}`)\n * }\n * ```\n */\nexport async function getSession(config: SylphxConfig): Promise<SessionResult> {\n\tif (!config.accessToken) {\n\t\treturn { user: null }\n\t}\n\n\ttry {\n\t\tconst user = await callApi<SessionResult['user']>(config, '/auth/me')\n\t\treturn { user }\n\t} catch {\n\t\treturn { user: null }\n\t}\n}\n\n/**\n * Verify 2FA code (when signIn returns requiresTwoFactor: true)\n *\n * @example\n * ```typescript\n * const result = await signIn(config, credentials)\n * if (result.requiresTwoFactor) {\n * const tokens = await verifyTwoFactor(config, result.userId!, code)\n * }\n * ```\n */\nexport async function verifyTwoFactor(\n\tconfig: SylphxConfig,\n\tuserId: string,\n\tcode: string,\n): Promise<TokenResponse> {\n\treturn callApi<TokenResponse>(config, '/auth/verify-2fa', {\n\t\tmethod: 'POST',\n\t\tbody: { userId, code },\n\t})\n}\n\n/**\n * Introspect a token to check its validity (RFC 7662)\n *\n * Use this to verify token status without decoding. Essential for:\n * - Checking if a token has been revoked\n * - Validating tokens at the edge\n * - Security-critical operations\n *\n * @example\n * ```typescript\n * const result = await introspectToken(config, accessToken)\n * if (!result.active) {\n * // Token is invalid, revoked, or expired\n * await refreshTokens()\n * }\n * ```\n */\nexport async function introspectToken(\n\tconfig: SylphxConfig,\n\ttoken: string,\n\ttokenTypeHint?: 'access_token' | 'refresh_token',\n): Promise<TokenIntrospectionResult> {\n\tconst response = await fetch(`${config.platformUrl}/api/v1/auth/introspect`, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tbody: JSON.stringify({\n\t\t\ttoken,\n\t\t\ttoken_type_hint: tokenTypeHint,\n\t\t\tclient_secret: config.secretKey,\n\t\t}),\n\t})\n\n\tif (!response.ok) {\n\t\t// Per RFC 7662, errors should return inactive\n\t\treturn { active: false }\n\t}\n\n\treturn response.json()\n}\n\n/**\n * Revoke a token (RFC 7009)\n *\n * Use cases:\n * - Sign out user from specific device\n * - Security response to compromised token\n * - User-initiated session termination\n *\n * @example\n * ```typescript\n * // Revoke single refresh token\n * await revokeToken(config, refreshToken)\n *\n * // Revoke all tokens for a user (logout everywhere)\n * await revokeToken(config, '', { revokeAll: true, userId: 'user-123' })\n * ```\n */\nexport async function revokeToken(\n\tconfig: SylphxConfig,\n\ttoken: string,\n\toptions?: RevokeTokenOptions,\n): Promise<void> {\n\tawait fetch(`${config.platformUrl}/api/v1/auth/revoke`, {\n\t\tmethod: 'POST',\n\t\theaders: { 'Content-Type': 'application/json' },\n\t\tbody: JSON.stringify({\n\t\t\ttoken: options?.revokeAll ? undefined : token,\n\t\t\tclient_secret: config.secretKey,\n\t\t\tuser_id: options?.userId,\n\t\t\trevoke_all: options?.revokeAll,\n\t\t}),\n\t})\n\t// Per RFC 7009, always succeeds (200 OK)\n}\n\n/**\n * Revoke all tokens for a user (logout from all devices)\n *\n * Convenience wrapper around revokeToken with revokeAll option.\n *\n * @example\n * ```typescript\n * // After password change, revoke all sessions\n * await revokeAllTokens(config, userId)\n * ```\n */\nexport async function revokeAllTokens(config: SylphxConfig, userId: string): Promise<void> {\n\tawait revokeToken(config, '', { revokeAll: true, userId })\n}\n","/**\n * Analytics Functions\n *\n * Pure functions for event tracking - no hidden state.\n * Events are sent directly to the platform.\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { components } from \"./generated/api\";\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type TrackEventItem = components[\"schemas\"][\"TrackEventItem\"];\nexport type BatchTrackRequest = components[\"schemas\"][\"BatchTrackRequest\"];\nexport type BatchTrackResponse = components[\"schemas\"][\"BatchTrackResponse\"];\nexport type ConversionData = components[\"schemas\"][\"ConversionData\"];\n\n// SDK-specific types for convenience\nexport interface TrackInput {\n\t/** Event name */\n\tevent: string;\n\t/** Event properties */\n\tproperties?: Record<string, unknown>;\n\t/** User ID (optional, for server-side tracking) */\n\tuserId?: string;\n\t/** Anonymous ID (for tracking before user signs in) */\n\tanonymousId?: string;\n\t/** Timestamp (defaults to now) */\n\ttimestamp?: string;\n}\n\nexport interface PageInput {\n\t/** Page name or title */\n\tname: string;\n\t/** Page properties */\n\tproperties?: Record<string, unknown>;\n\t/** User ID (optional) */\n\tuserId?: string;\n\t/** Anonymous ID */\n\tanonymousId?: string;\n}\n\nexport interface IdentifyInput {\n\t/** User ID */\n\tuserId: string;\n\t/** User traits */\n\ttraits?: Record<string, unknown>;\n\t/** Anonymous ID to link */\n\tanonymousId?: string;\n}\n\nexport interface BatchEvent {\n\ttype: \"track\" | \"page\" | \"identify\";\n\tevent?: string;\n\tname?: string;\n\tuserId?: string;\n\tanonymousId?: string;\n\tproperties?: Record<string, unknown>;\n\ttraits?: Record<string, unknown>;\n\ttimestamp?: string;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Track a custom event\n *\n * @example\n * ```typescript\n * await track(config, {\n * event: 'purchase_completed',\n * properties: { amount: 99.99, currency: 'USD' },\n * userId: 'user-123',\n * })\n * ```\n */\nexport async function track(\n\tconfig: SylphxConfig,\n\tinput: TrackInput,\n): Promise<void> {\n\tawait callApi(config, \"/analytics/track\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\tevent: input.event,\n\t\t\tproperties: input.properties ?? {},\n\t\t\tuserId: input.userId,\n\t\t\tanonymousId: input.anonymousId,\n\t\t\ttimestamp: input.timestamp ?? new Date().toISOString(),\n\t\t},\n\t});\n}\n\n/**\n * Track a page view\n *\n * @example\n * ```typescript\n * await page(config, {\n * name: 'Home',\n * properties: { path: '/', referrer: document.referrer },\n * })\n * ```\n */\nexport async function page(\n\tconfig: SylphxConfig,\n\tinput: PageInput,\n): Promise<void> {\n\tawait callApi(config, \"/analytics/page\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\tname: input.name,\n\t\t\tproperties: input.properties ?? {},\n\t\t\tuserId: input.userId,\n\t\t\tanonymousId: input.anonymousId,\n\t\t\ttimestamp: new Date().toISOString(),\n\t\t},\n\t});\n}\n\n/**\n * Identify a user with traits\n *\n * @example\n * ```typescript\n * await identify(config, {\n * userId: 'user-123',\n * traits: { email: 'user@example.com', plan: 'pro' },\n * anonymousId: 'anon-456', // Links anonymous activity to user\n * })\n * ```\n */\nexport async function identify(\n\tconfig: SylphxConfig,\n\tinput: IdentifyInput,\n): Promise<void> {\n\tawait callApi(config, \"/analytics/identify\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\tuserId: input.userId,\n\t\t\ttraits: input.traits ?? {},\n\t\t\tanonymousId: input.anonymousId,\n\t\t},\n\t});\n}\n\n/**\n * Send multiple events in a single request (batch)\n *\n * @example\n * ```typescript\n * await trackBatch(config, [\n * { type: 'track', event: 'item_viewed', properties: { id: '1' } },\n * { type: 'track', event: 'item_added', properties: { id: '1' } },\n * { type: 'track', event: 'checkout_started' },\n * ])\n * ```\n */\nexport async function trackBatch(\n\tconfig: SylphxConfig,\n\tevents: BatchEvent[],\n): Promise<void> {\n\tawait callApi(config, \"/analytics/batch\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\tevents: events.map((e) => ({\n\t\t\t\tevent:\n\t\t\t\t\te.type === \"track\"\n\t\t\t\t\t\t? e.event\n\t\t\t\t\t\t: e.type === \"page\"\n\t\t\t\t\t\t\t? `$pageview`\n\t\t\t\t\t\t\t: \"$identify\",\n\t\t\t\tproperties: {\n\t\t\t\t\t...e.properties,\n\t\t\t\t\t...(e.type === \"page\" && e.name ? { name: e.name } : {}),\n\t\t\t\t\t...(e.type === \"identify\" && e.traits ? { traits: e.traits } : {}),\n\t\t\t\t},\n\t\t\t\tuserId: e.userId,\n\t\t\t\tanonymousId: e.anonymousId,\n\t\t\t\ttimestamp: e.timestamp ?? new Date().toISOString(),\n\t\t\t})),\n\t\t},\n\t});\n}\n\n// ============================================================================\n// Convenience Functions\n// ============================================================================\n\n/**\n * Generate a random anonymous ID (Segment pattern: pure UUID)\n *\n * Uses UUID v4 format without timestamp component to prevent collision risk\n * in high-traffic applications where multiple users might generate IDs at\n * the same millisecond.\n *\n * @example\n * ```typescript\n * const anonId = generateAnonymousId()\n * await track(config, { event: 'page_view', anonymousId: anonId })\n * ```\n */\nexport function generateAnonymousId(): string {\n\t// Use crypto.randomUUID if available (standard UUID v4)\n\tif (typeof crypto !== \"undefined\" && crypto.randomUUID) {\n\t\treturn crypto.randomUUID();\n\t}\n\t// Fallback for older browsers: generate UUID v4 manually\n\treturn \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n\t\tconst r = (Math.random() * 16) | 0;\n\t\tconst v = c === \"x\" ? r : (r & 0x3) | 0x8;\n\t\treturn v.toString(16);\n\t});\n}\n\n/**\n * Create a tracker bound to a specific config\n *\n * For convenience when making many calls with the same config.\n * This is optional - you can always use the individual functions.\n *\n * @example\n * ```typescript\n * const analytics = createTracker(config)\n *\n * // No need to pass config each time\n * analytics.track('event', { prop: 'value' })\n * analytics.page('Home')\n * analytics.identify('user-123', { email: 'user@example.com' })\n * ```\n */\nexport function createTracker(\n\tconfig: SylphxConfig,\n\tdefaultAnonymousId?: string,\n) {\n\tconst anonymousId = defaultAnonymousId ?? generateAnonymousId();\n\n\treturn {\n\t\ttrack: (\n\t\t\tevent: string,\n\t\t\tproperties?: Record<string, unknown>,\n\t\t\tuserId?: string,\n\t\t) => track(config, { event, properties, userId, anonymousId }),\n\n\t\tpage: (\n\t\t\tname: string,\n\t\t\tproperties?: Record<string, unknown>,\n\t\t\tuserId?: string,\n\t\t) => page(config, { name, properties, userId, anonymousId }),\n\n\t\tidentify: (userId: string, traits?: Record<string, unknown>) =>\n\t\t\tidentify(config, { userId, traits, anonymousId }),\n\n\t\tbatch: (events: BatchEvent[]) =>\n\t\t\ttrackBatch(\n\t\t\t\tconfig,\n\t\t\t\tevents.map((e) => ({\n\t\t\t\t\t...e,\n\t\t\t\t\tanonymousId: e.anonymousId ?? anonymousId,\n\t\t\t\t})),\n\t\t\t),\n\n\t\t/** Get the anonymous ID for this tracker */\n\t\tgetAnonymousId: () => anonymousId,\n\t};\n}\n","/**\n * AI Functions\n *\n * Pure functions for AI completions - Vercel AI SDK style.\n * Direct API calls with natural tree-shaking.\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { type SylphxConfig, buildHeaders } from \"./config\";\nimport { SylphxError } from \"./errors\";\nimport type { components } from \"./generated/api\";\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type AIUsageResponse = components[\"schemas\"][\"AIUsageResponse\"];\nexport type AIRateLimitResponse = components[\"schemas\"][\"AIRateLimitResponse\"];\nexport type AIModelsResponse = components[\"schemas\"][\"AIModelsResponse\"];\nexport type AIModel = components[\"schemas\"][\"AIModel\"];\n\n// ============================================================================\n// SDK-specific Types (OpenAI-compatible chat format)\n// ============================================================================\n\nexport interface ChatMessage {\n\trole: \"system\" | \"user\" | \"assistant\" | \"tool\";\n\tcontent: string | ContentPart[];\n\tname?: string;\n\ttool_call_id?: string;\n\ttool_calls?: ToolCall[];\n\t/** Timestamp for UI display */\n\ttimestamp?: Date;\n}\n\nexport interface ContentPart {\n\ttype: \"text\" | \"image_url\";\n\ttext?: string;\n\timage_url?: { url: string; detail?: \"auto\" | \"low\" | \"high\" };\n}\n\nexport interface ToolCall {\n\tid: string;\n\ttype: \"function\";\n\tfunction: { name: string; arguments: string };\n}\n\nexport interface Tool {\n\ttype: \"function\";\n\tfunction: {\n\t\tname: string;\n\t\tdescription?: string;\n\t\tparameters?: Record<string, unknown>;\n\t};\n}\n\nexport interface ChatInput {\n\t/** Model ID (e.g., 'gpt-4o', 'claude-sonnet-4-20250514') */\n\tmodel: string;\n\t/** Messages */\n\tmessages: ChatMessage[];\n\t/** Temperature (0-2) */\n\ttemperature?: number;\n\t/** Max tokens to generate */\n\tmaxTokens?: number;\n\t/** Top P sampling */\n\ttopP?: number;\n\t/** Frequency penalty */\n\tfrequencyPenalty?: number;\n\t/** Presence penalty */\n\tpresencePenalty?: number;\n\t/** Stop sequences */\n\tstop?: string[];\n\t/** Tools for function calling */\n\ttools?: Tool[];\n\t/** Tool choice */\n\ttoolChoice?:\n\t\t| \"auto\"\n\t\t| \"none\"\n\t\t| { type: \"function\"; function: { name: string } };\n}\n\nexport interface ChatResult {\n\tid: string;\n\tmodel: string;\n\tchoices: Array<{\n\t\tindex: number;\n\t\tmessage: {\n\t\t\trole: \"assistant\";\n\t\t\tcontent: string | null;\n\t\t\ttool_calls?: ToolCall[];\n\t\t};\n\t\tfinishReason: \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\" | null;\n\t}>;\n\tusage: {\n\t\tpromptTokens: number;\n\t\tcompletionTokens: number;\n\t\ttotalTokens: number;\n\t};\n}\n\nexport interface ChatStreamChunk {\n\tid: string;\n\tmodel: string;\n\tchoices: Array<{\n\t\tindex: number;\n\t\tdelta: {\n\t\t\trole?: \"assistant\";\n\t\t\tcontent?: string;\n\t\t\ttool_calls?: ToolCall[];\n\t\t};\n\t\tfinishReason: \"stop\" | \"length\" | \"tool_calls\" | \"content_filter\" | null;\n\t}>;\n}\n\nexport interface EmbedInput {\n\t/** Model ID (e.g., 'text-embedding-3-small') */\n\tmodel: string;\n\t/** Text(s) to embed */\n\tinput: string | string[];\n\t/** Dimensions (for models that support it) */\n\tdimensions?: number;\n}\n\nexport interface EmbedResult {\n\tmodel: string;\n\tdata: Array<{\n\t\tindex: number;\n\t\tembedding: number[];\n\t}>;\n\tusage: {\n\t\tpromptTokens: number;\n\t\ttotalTokens: number;\n\t};\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Create a chat completion\n *\n * @example\n * ```typescript\n * const response = await chat(config, {\n * model: 'gpt-4o',\n * messages: [\n * { role: 'system', content: 'You are a helpful assistant.' },\n * { role: 'user', content: 'Hello!' },\n * ],\n * })\n *\n * console.log(response.choices[0].message.content)\n * ```\n */\nexport async function chat(\n\tconfig: SylphxConfig,\n\tinput: ChatInput,\n): Promise<ChatResult> {\n\tconst response = await fetch(\n\t\t`${config.platformUrl}/api/v1/chat/completions`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\theaders: {\n\t\t\t\t...buildHeaders(config),\n\t\t\t\tAuthorization: `Bearer ${config.secretKey}`,\n\t\t\t},\n\t\t\tbody: JSON.stringify({\n\t\t\t\tmodel: input.model,\n\t\t\t\tmessages: input.messages,\n\t\t\t\ttemperature: input.temperature,\n\t\t\t\tmax_tokens: input.maxTokens,\n\t\t\t\ttop_p: input.topP,\n\t\t\t\tfrequency_penalty: input.frequencyPenalty,\n\t\t\t\tpresence_penalty: input.presencePenalty,\n\t\t\t\tstop: input.stop,\n\t\t\t\ttools: input.tools,\n\t\t\t\ttool_choice: input.toolChoice,\n\t\t\t}),\n\t\t},\n\t);\n\n\tif (!response.ok) {\n\t\tconst error = await response\n\t\t\t.json()\n\t\t\t.catch(() => ({ error: { message: \"Chat request failed\" } }));\n\t\tthrow new SylphxError(error?.error?.message ?? \"Chat request failed\", {\n\t\t\tcode: \"BAD_REQUEST\",\n\t\t});\n\t}\n\n\tconst data = await response.json();\n\n\treturn {\n\t\tid: data.id,\n\t\tmodel: data.model,\n\t\tchoices: data.choices.map((c: Record<string, unknown>) => ({\n\t\t\tindex: c.index as number,\n\t\t\tmessage: {\n\t\t\t\trole: \"assistant\" as const,\n\t\t\t\tcontent: (c.message as Record<string, unknown>)?.content as\n\t\t\t\t\t| string\n\t\t\t\t\t| null,\n\t\t\t\ttool_calls: (c.message as Record<string, unknown>)?.tool_calls as\n\t\t\t\t\t| ToolCall[]\n\t\t\t\t\t| undefined,\n\t\t\t},\n\t\t\tfinishReason: c.finish_reason as ChatResult[\"choices\"][0][\"finishReason\"],\n\t\t})),\n\t\tusage: {\n\t\t\tpromptTokens: data.usage.prompt_tokens,\n\t\t\tcompletionTokens: data.usage.completion_tokens,\n\t\t\ttotalTokens: data.usage.total_tokens,\n\t\t},\n\t};\n}\n\n/**\n * Create a streaming chat completion\n *\n * @example\n * ```typescript\n * const stream = chatStream(config, {\n * model: 'gpt-4o',\n * messages: [{ role: 'user', content: 'Write a poem' }],\n * })\n *\n * for await (const chunk of stream) {\n * process.stdout.write(chunk.choices[0].delta.content ?? '')\n * }\n * ```\n */\nexport function chatStream(\n\tconfig: SylphxConfig,\n\tinput: ChatInput,\n): AsyncIterable<ChatStreamChunk> {\n\treturn {\n\t\t[Symbol.asyncIterator]: async function* () {\n\t\t\tconst response = await fetch(\n\t\t\t\t`${config.platformUrl}/api/v1/chat/completions`,\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t...buildHeaders(config),\n\t\t\t\t\t\tAuthorization: `Bearer ${config.secretKey}`,\n\t\t\t\t\t},\n\t\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\t\tmodel: input.model,\n\t\t\t\t\t\tmessages: input.messages,\n\t\t\t\t\t\ttemperature: input.temperature,\n\t\t\t\t\t\tmax_tokens: input.maxTokens,\n\t\t\t\t\t\ttop_p: input.topP,\n\t\t\t\t\t\tfrequency_penalty: input.frequencyPenalty,\n\t\t\t\t\t\tpresence_penalty: input.presencePenalty,\n\t\t\t\t\t\tstop: input.stop,\n\t\t\t\t\t\ttools: input.tools,\n\t\t\t\t\t\ttool_choice: input.toolChoice,\n\t\t\t\t\t\tstream: true,\n\t\t\t\t\t}),\n\t\t\t\t},\n\t\t\t);\n\n\t\t\tif (!response.ok) {\n\t\t\t\tconst error = await response\n\t\t\t\t\t.json()\n\t\t\t\t\t.catch(() => ({ error: { message: \"Stream request failed\" } }));\n\t\t\t\tthrow new SylphxError(error?.error?.message ?? \"Stream request failed\", {\n\t\t\t\t\tcode: \"BAD_REQUEST\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst reader = response.body?.getReader();\n\t\t\tif (!reader) {\n\t\t\t\tthrow new SylphxError(\"No response body\", {\n\t\t\t\t\tcode: \"INTERNAL_SERVER_ERROR\",\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst decoder = new TextDecoder();\n\t\t\tlet buffer = \"\";\n\n\t\t\ttry {\n\t\t\t\twhile (true) {\n\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\tif (done) break;\n\n\t\t\t\t\tbuffer += decoder.decode(value, { stream: true });\n\t\t\t\t\tconst lines = buffer.split(\"\\n\");\n\t\t\t\t\tbuffer = lines.pop() ?? \"\";\n\n\t\t\t\t\tfor (const line of lines) {\n\t\t\t\t\t\tif (line.startsWith(\"data: \")) {\n\t\t\t\t\t\t\tconst data = line.slice(6).trim();\n\t\t\t\t\t\t\tif (data === \"[DONE]\") return;\n\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst chunk = JSON.parse(data);\n\t\t\t\t\t\t\t\tyield {\n\t\t\t\t\t\t\t\t\tid: chunk.id ?? \"\",\n\t\t\t\t\t\t\t\t\tmodel: chunk.model ?? input.model,\n\t\t\t\t\t\t\t\t\tchoices: (chunk.choices ?? []).map(\n\t\t\t\t\t\t\t\t\t\t(c: Record<string, unknown>) => ({\n\t\t\t\t\t\t\t\t\t\t\tindex: typeof c.index === \"number\" ? c.index : 0,\n\t\t\t\t\t\t\t\t\t\t\tdelta: {\n\t\t\t\t\t\t\t\t\t\t\t\trole: (c.delta as Record<string, unknown>)?.role as\n\t\t\t\t\t\t\t\t\t\t\t\t\t| \"assistant\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t| undefined,\n\t\t\t\t\t\t\t\t\t\t\t\tcontent: (c.delta as Record<string, unknown>)\n\t\t\t\t\t\t\t\t\t\t\t\t\t?.content as string | undefined,\n\t\t\t\t\t\t\t\t\t\t\t\ttool_calls: (c.delta as Record<string, unknown>)\n\t\t\t\t\t\t\t\t\t\t\t\t\t?.tool_calls as ToolCall[] | undefined,\n\t\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t\tfinishReason:\n\t\t\t\t\t\t\t\t\t\t\t\t(c.finish_reason as ChatStreamChunk[\"choices\"][0][\"finishReason\"]) ??\n\t\t\t\t\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\t// Skip malformed JSON\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\treader.releaseLock();\n\t\t\t}\n\t\t},\n\t};\n}\n\n/**\n * Create embeddings\n *\n * @example\n * ```typescript\n * const result = await embed(config, {\n * model: 'text-embedding-3-small',\n * input: ['Hello world', 'Goodbye world'],\n * })\n *\n * console.log(result.data[0].embedding) // [0.123, -0.456, ...]\n * ```\n */\nexport async function embed(\n\tconfig: SylphxConfig,\n\tinput: EmbedInput,\n): Promise<EmbedResult> {\n\tconst response = await fetch(`${config.platformUrl}/api/v1/embeddings`, {\n\t\tmethod: \"POST\",\n\t\theaders: {\n\t\t\t...buildHeaders(config),\n\t\t\tAuthorization: `Bearer ${config.secretKey}`,\n\t\t},\n\t\tbody: JSON.stringify({\n\t\t\tmodel: input.model,\n\t\t\tinput: input.input,\n\t\t\tdimensions: input.dimensions,\n\t\t}),\n\t});\n\n\tif (!response.ok) {\n\t\tconst error = await response\n\t\t\t.json()\n\t\t\t.catch(() => ({ error: { message: \"Embedding request failed\" } }));\n\t\tthrow new SylphxError(error?.error?.message ?? \"Embedding request failed\", {\n\t\t\tcode: \"BAD_REQUEST\",\n\t\t});\n\t}\n\n\tconst data = await response.json();\n\n\treturn {\n\t\tmodel: data.model,\n\t\tdata: data.data,\n\t\tusage: {\n\t\t\tpromptTokens: data.usage.prompt_tokens,\n\t\t\ttotalTokens: data.usage.total_tokens,\n\t\t},\n\t};\n}\n\n// ============================================================================\n// Convenience Functions\n// ============================================================================\n\n/**\n * Simple text completion (convenience wrapper)\n *\n * @example\n * ```typescript\n * const text = await complete(config, 'gpt-4o', 'Explain quantum computing in one sentence.')\n * ```\n */\nexport async function complete(\n\tconfig: SylphxConfig,\n\tmodel: string,\n\tprompt: string,\n\toptions?: Omit<ChatInput, \"model\" | \"messages\">,\n): Promise<string> {\n\tconst response = await chat(config, {\n\t\tmodel,\n\t\tmessages: [{ role: \"user\", content: prompt }],\n\t\t...options,\n\t});\n\treturn response.choices[0]?.message.content ?? \"\";\n}\n\n/**\n * Stream text to string (collects all chunks)\n *\n * @example\n * ```typescript\n * const text = await streamToString(config, {\n * model: 'gpt-4o',\n * messages: [{ role: 'user', content: 'Write a haiku' }],\n * })\n * ```\n */\nexport async function streamToString(\n\tconfig: SylphxConfig,\n\tinput: ChatInput,\n): Promise<string> {\n\tlet result = \"\";\n\tfor await (const chunk of chatStream(config, input)) {\n\t\tresult += chunk.choices[0]?.delta.content ?? \"\";\n\t}\n\treturn result;\n}\n","/**\n * Billing Functions\n *\n * Pure functions for billing and subscriptions.\n * Uses REST API at /api/sdk/billing/* for all operations.\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { components } from \"./generated/api\";\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type Plan = components[\"schemas\"][\"Plan\"];\nexport type Subscription = components[\"schemas\"][\"Subscription\"];\nexport type CheckoutRequest = components[\"schemas\"][\"CheckoutRequest\"];\nexport type CheckoutResponse = components[\"schemas\"][\"CheckoutResponse\"];\nexport type PortalRequest = components[\"schemas\"][\"PortalRequest\"];\nexport type PortalResponse = components[\"schemas\"][\"PortalResponse\"];\nexport type BalanceResponse = components[\"schemas\"][\"BalanceResponse\"];\nexport type UsageResponse = components[\"schemas\"][\"UsageResponse\"];\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Get available plans\n *\n * @example\n * ```typescript\n * const plans = await getPlans(config)\n * plans.forEach(plan => console.log(plan.name, plan.priceMonthly))\n * ```\n */\nexport async function getPlans(config: SylphxConfig): Promise<Plan[]> {\n\treturn callApi<Plan[]>(config, \"/billing/plans\");\n}\n\n/**\n * Get user's subscription\n *\n * @example\n * ```typescript\n * const sub = await getSubscription(config, 'user-123')\n * if (sub?.status === 'active') {\n * console.log(`Active plan: ${sub.planSlug}`)\n * }\n * ```\n */\nexport async function getSubscription(\n\tconfig: SylphxConfig,\n\tuserId: string,\n): Promise<Subscription | null> {\n\treturn callApi<Subscription | null>(config, \"/billing/subscription\", {\n\t\tquery: { userId },\n\t});\n}\n\n/**\n * Create a checkout session\n *\n * @example\n * ```typescript\n * const { checkoutUrl } = await createCheckout(config, {\n * userId: 'user-123',\n * planSlug: 'pro',\n * interval: 'monthly',\n * successUrl: 'https://myapp.com/success',\n * cancelUrl: 'https://myapp.com/pricing',\n * })\n *\n * window.location.href = checkoutUrl\n * ```\n */\nexport async function createCheckout(\n\tconfig: SylphxConfig,\n\tinput: CheckoutRequest,\n): Promise<CheckoutResponse> {\n\treturn callApi<CheckoutResponse>(config, \"/billing/checkout\", {\n\t\tmethod: \"POST\",\n\t\tbody: input,\n\t});\n}\n\n/**\n * Create a billing portal session\n *\n * @example\n * ```typescript\n * const { portalUrl } = await createPortalSession(config, {\n * userId: 'user-123',\n * returnUrl: window.location.href,\n * })\n *\n * window.location.href = portalUrl\n * ```\n */\nexport async function createPortalSession(\n\tconfig: SylphxConfig,\n\tinput: PortalRequest,\n): Promise<PortalResponse> {\n\treturn callApi<PortalResponse>(config, \"/billing/portal\", {\n\t\tmethod: \"POST\",\n\t\tbody: input,\n\t});\n}\n\n/**\n * Get billing balance (credits, etc.)\n *\n * @example\n * ```typescript\n * const balance = await getBillingBalance(config)\n * console.log(`Balance: ${balance.balance.currentFormatted}`)\n * ```\n */\nexport async function getBillingBalance(\n\tconfig: SylphxConfig,\n): Promise<BalanceResponse> {\n\treturn callApi<BalanceResponse>(config, \"/billing/balance\");\n}\n\n/**\n * Get billing usage\n *\n * @example\n * ```typescript\n * const usage = await getBillingUsage(config, { month: '2024-01' })\n * ```\n */\nexport async function getBillingUsage(\n\tconfig: SylphxConfig,\n\toptions?: { month?: string },\n): Promise<UsageResponse> {\n\treturn callApi<UsageResponse>(config, \"/billing/usage\", {\n\t\tquery: options?.month ? { month: options.month } : undefined,\n\t});\n}\n","/**\n * Storage Functions\n *\n * Pure functions for file storage operations.\n *\n * ## Industry Patterns Implemented\n * - AbortController cancellation (Vercel Blob pattern)\n * - Exponential backoff with jitter (AWS S3 pattern: 5 retries, 1s base)\n * - Concurrent chunk uploads (Vercel pattern: 3 concurrent)\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { buildHeaders, callApi, type SylphxConfig } from './config'\nimport { BASE_RETRY_DELAY_MS, MAX_RETRY_DELAY_MS } from './constants'\nimport { SylphxError } from './errors'\nimport type { components } from './generated/api'\n\n// Re-export types from SSOT\nexport type {\n\tUploadOptions,\n\tUploadProgressEvent,\n\tUploadResult,\n} from './lib/storage/types'\n\nimport type { UploadProgressEvent, UploadResult } from './lib/storage/types'\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type StorageFile = components['schemas']['StorageFile']\nexport type UploadUrlRequest = components['schemas']['UploadUrlRequest']\nexport type UploadUrlResponse = components['schemas']['UploadUrlResponse']\n\n// ============================================================================\n// Upload Retry Configuration (AWS S3 Pattern)\n// ============================================================================\n\nconst UPLOAD_RETRY_CONFIG = {\n\t/** Maximum number of retry attempts (AWS S3 pattern) */\n\tmaxRetries: 5,\n\t/** Base delay in milliseconds */\n\tbaseDelayMs: BASE_RETRY_DELAY_MS,\n\t/** Maximum delay cap in milliseconds */\n\tmaxDelayMs: MAX_RETRY_DELAY_MS,\n\t/** Jitter type: 'full' for full jitter (AWS recommended) */\n\tjitter: 'full' as const,\n}\n\n/**\n * Calculate exponential backoff delay with full jitter (AWS pattern)\n * Formula: random(0, min(cap, base * 2 ^ attempt))\n */\nfunction calculateBackoffDelay(attempt: number): number {\n\tconst { baseDelayMs, maxDelayMs } = UPLOAD_RETRY_CONFIG\n\tconst exponentialDelay = baseDelayMs * 2 ** attempt\n\tconst cappedDelay = Math.min(exponentialDelay, maxDelayMs)\n\t// Full jitter: random value between 0 and cappedDelay\n\treturn Math.random() * cappedDelay\n}\n\n/**\n * Sleep for a specified duration, respecting AbortSignal\n */\nasync function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n\treturn new Promise((resolve, reject) => {\n\t\tif (signal?.aborted) {\n\t\t\treject(new DOMException('Upload aborted', 'AbortError'))\n\t\t\treturn\n\t\t}\n\n\t\tconst timeoutId = setTimeout(resolve, ms)\n\n\t\tsignal?.addEventListener(\n\t\t\t'abort',\n\t\t\t() => {\n\t\t\t\tclearTimeout(timeoutId)\n\t\t\t\treject(new DOMException('Upload aborted', 'AbortError'))\n\t\t\t},\n\t\t\t{ once: true },\n\t\t)\n\t})\n}\n\n/**\n * Check if an error is retryable (network errors, 5xx, 429)\n */\nfunction isRetryableError(error: unknown): boolean {\n\tif (error instanceof DOMException && error.name === 'AbortError') {\n\t\treturn false // Never retry aborted requests\n\t}\n\tif (error instanceof TypeError) {\n\t\treturn true // Network errors\n\t}\n\tif (error instanceof Error && 'status' in error) {\n\t\tconst status = (error as { status: number }).status\n\t\treturn status >= 500 || status === 429 // Server errors or rate limiting\n\t}\n\treturn false\n}\n\n// ============================================================================\n// Types (SDK-specific)\n// ============================================================================\n\nexport interface FileUploadOptions {\n\t/** Folder path */\n\tpath?: string\n\t/** File type (file, avatar, etc.) */\n\ttype?: 'file' | 'avatar'\n\t/** User ID (for avatar uploads) */\n\tuserId?: string\n\t/** Progress callback */\n\tonProgress?: (event: UploadProgressEvent) => void\n\t/**\n\t * Enable multipart upload for large files.\n\t * - `true`: Always use multipart upload\n\t * - `false`: Never use multipart upload\n\t * - `'auto'` (default): Auto-enable for files > 5MB\n\t *\n\t * Multipart uploads support files up to 5TB with better\n\t * reliability for large files.\n\t */\n\tmultipart?: boolean | 'auto'\n\t/**\n\t * AbortSignal to cancel the upload.\n\t * Vercel Blob pattern - enables cancellation of in-progress uploads.\n\t *\n\t * @example\n\t * ```typescript\n\t * const controller = new AbortController()\n\t * // Cancel after 30 seconds\n\t * setTimeout(() => controller.abort(), 30000)\n\t * await uploadFile(config, file, { signal: controller.signal })\n\t * ```\n\t */\n\tsignal?: AbortSignal\n\t/**\n\t * Idempotency key for safe retries (Stripe pattern)\n\t *\n\t * Prevents duplicate uploads if the same request is retried.\n\t * Use a unique key per logical upload operation.\n\t *\n\t * @example `upload-${userId}-${fileName}-${fileHash}`\n\t */\n\tidempotencyKey?: string\n}\n\nexport interface FileInfo {\n\tid: string\n\turl: string\n\tname: string\n\tsize: number\n\tcontentType: string\n\tisPrivate: boolean\n\tcreatedAt: string\n}\n\nexport interface SignedUrlOptions {\n\t/** Expiration in seconds (default: 3600, max: 604800 = 7 days) */\n\texpiresIn?: number\n\t/** Force download (attachment) vs inline display (default: attachment) */\n\tdisposition?: 'attachment' | 'inline'\n\t/** Restrict access to specific user */\n\tuserId?: string\n}\n\nexport interface SignedUrlResult {\n\t/** The signed download URL */\n\turl: string\n\t/** When the URL expires (ISO string) */\n\texpiresAt: string\n\t/** File metadata */\n\tfile: {\n\t\tid: string\n\t\tfilename: string\n\t\tmimeType: string\n\t\tsizeBytes: number\n\t\tisPrivate: boolean\n\t}\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Upload a file to storage\n *\n * Uses client-side upload for optimal performance (direct to CDN).\n *\n * ## Industry-Standard Features\n * - **Cancellation**: AbortController support (Vercel Blob pattern)\n * - **Retry**: Exponential backoff with jitter (AWS S3 pattern: 5 retries)\n * - **Progress**: Real-time upload progress tracking\n *\n * ## File Size Limits\n * - Standard uploads: up to 500MB\n * - For files > 500MB: use React hooks with `multipart: true` (supports up to 5TB)\n *\n * ## Multipart Uploads\n * For large files (> 5MB), multipart uploads provide:\n * - Better reliability with chunked uploads\n * - Resumable upload capability\n * - Progress tracking per chunk\n *\n * Use the `multipart` option or React hooks for large files:\n * ```typescript\n * // React hooks (recommended for large files)\n * const { upload } = useStorage()\n * await upload(file, { multipart: true })\n * ```\n *\n * @example\n * ```typescript\n * const file = new File(['Hello'], 'hello.txt', { type: 'text/plain' })\n * const result = await uploadFile(config, file, {\n * path: 'documents',\n * onProgress: (e) => console.log(`${e.progress}%`),\n * })\n *\n * console.log(result.url)\n * ```\n *\n * @example Cancellation\n * ```typescript\n * const controller = new AbortController()\n * setTimeout(() => controller.abort(), 30000) // Cancel after 30s\n *\n * try {\n * await uploadFile(config, file, { signal: controller.signal })\n * } catch (e) {\n * if (e.name === 'AbortError') {\n * console.log('Upload cancelled')\n * }\n * }\n * ```\n */\nexport async function uploadFile(\n\tconfig: SylphxConfig,\n\tfile: File,\n\toptions?: FileUploadOptions,\n): Promise<UploadResult> {\n\tconst { signal } = options ?? {}\n\n\t// Check if already aborted\n\tif (signal?.aborted) {\n\t\tthrow new DOMException('Upload aborted', 'AbortError')\n\t}\n\n\t// Get upload token from platform (with retry)\n\tlet tokenResponse: Response | null = null\n\tlet lastError: Error | null = null\n\n\tfor (let attempt = 0; attempt <= UPLOAD_RETRY_CONFIG.maxRetries; attempt++) {\n\t\ttry {\n\t\t\ttokenResponse = await fetch(`${config.platformUrl}/api/v1/storage/upload`, {\n\t\t\t\tmethod: 'POST',\n\t\t\t\theaders: buildHeaders(config),\n\t\t\t\tbody: JSON.stringify({\n\t\t\t\t\tfilename: file.name,\n\t\t\t\t\tcontentType: file.type,\n\t\t\t\t\tsize: file.size,\n\t\t\t\t\tpath: options?.path,\n\t\t\t\t\ttype: options?.type ?? 'file',\n\t\t\t\t\tuserId: options?.userId,\n\t\t\t\t}),\n\t\t\t\tsignal,\n\t\t\t})\n\n\t\t\tif (tokenResponse.ok) {\n\t\t\t\tbreak\n\t\t\t}\n\n\t\t\t// Check if error is retryable\n\t\t\tif (tokenResponse.status >= 500 || tokenResponse.status === 429) {\n\t\t\t\tif (attempt < UPLOAD_RETRY_CONFIG.maxRetries) {\n\t\t\t\t\tconst delay = calculateBackoffDelay(attempt)\n\t\t\t\t\tawait sleep(delay, signal)\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Non-retryable error\n\t\t\tconst error = await tokenResponse\n\t\t\t\t.json()\n\t\t\t\t.catch(() => ({ message: 'Failed to get upload token' }))\n\t\t\tthrow new SylphxError(error.message ?? 'Failed to get upload token', {\n\t\t\t\tcode: 'BAD_REQUEST',\n\t\t\t})\n\t\t} catch (error) {\n\t\t\tif (error instanceof DOMException && error.name === 'AbortError') {\n\t\t\t\tthrow error // Don't retry aborted requests\n\t\t\t}\n\n\t\t\tlastError = error instanceof Error ? error : new Error(String(error))\n\n\t\t\tif (isRetryableError(error) && attempt < UPLOAD_RETRY_CONFIG.maxRetries) {\n\t\t\t\tconst delay = calculateBackoffDelay(attempt)\n\t\t\t\tawait sleep(delay, signal)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tthrow lastError\n\t\t}\n\t}\n\n\tif (!tokenResponse?.ok) {\n\t\tthrow (\n\t\t\tlastError ??\n\t\t\tnew SylphxError('Failed to get upload token after retries', {\n\t\t\t\tcode: 'BAD_REQUEST',\n\t\t\t})\n\t\t)\n\t}\n\n\tconst { uploadUrl, publicUrl } = await tokenResponse.json()\n\n\t// Upload directly to storage with retry\n\treturn executeUploadWithRetry(file, uploadUrl, publicUrl, options)\n}\n\n/**\n * Execute the actual upload with retry logic\n */\nasync function executeUploadWithRetry(\n\tfile: File,\n\tuploadUrl: string,\n\tpublicUrl: string,\n\toptions?: FileUploadOptions,\n): Promise<UploadResult> {\n\tconst { signal } = options ?? {}\n\tlet lastError: Error | null = null\n\n\tfor (let attempt = 0; attempt <= UPLOAD_RETRY_CONFIG.maxRetries; attempt++) {\n\t\ttry {\n\t\t\treturn await executeUpload(file, uploadUrl, publicUrl, options)\n\t\t} catch (error) {\n\t\t\tif (error instanceof DOMException && error.name === 'AbortError') {\n\t\t\t\tthrow error // Don't retry aborted requests\n\t\t\t}\n\n\t\t\tlastError = error instanceof Error ? error : new Error(String(error))\n\n\t\t\tif (isRetryableError(error) && attempt < UPLOAD_RETRY_CONFIG.maxRetries) {\n\t\t\t\tconst delay = calculateBackoffDelay(attempt)\n\t\t\t\tawait sleep(delay, signal)\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tthrow lastError\n\t\t}\n\t}\n\n\tthrow lastError ?? new Error('Upload failed after retries')\n}\n\n/**\n * Execute a single upload attempt with XHR (for progress tracking)\n */\nfunction executeUpload(\n\tfile: File,\n\tuploadUrl: string,\n\tpublicUrl: string,\n\toptions?: FileUploadOptions,\n): Promise<UploadResult> {\n\tconst { signal, onProgress } = options ?? {}\n\n\treturn new Promise<UploadResult>((resolve, reject) => {\n\t\tconst xhr = new XMLHttpRequest()\n\n\t\t// Handle abort signal\n\t\tconst handleAbort = () => {\n\t\t\txhr.abort()\n\t\t\treject(new DOMException('Upload aborted', 'AbortError'))\n\t\t}\n\n\t\tif (signal?.aborted) {\n\t\t\treject(new DOMException('Upload aborted', 'AbortError'))\n\t\t\treturn\n\t\t}\n\n\t\tsignal?.addEventListener('abort', handleAbort, { once: true })\n\n\t\txhr.upload.addEventListener('progress', (event) => {\n\t\t\tif (event.lengthComputable && onProgress) {\n\t\t\t\tonProgress({\n\t\t\t\t\tloaded: event.loaded,\n\t\t\t\t\ttotal: event.total,\n\t\t\t\t\tprogress: Math.round((event.loaded / event.total) * 100),\n\t\t\t\t})\n\t\t\t}\n\t\t})\n\n\t\txhr.addEventListener('load', () => {\n\t\t\tsignal?.removeEventListener('abort', handleAbort)\n\n\t\t\tif (xhr.status >= 200 && xhr.status < 300) {\n\t\t\t\tresolve({\n\t\t\t\t\turl: publicUrl,\n\t\t\t\t\tpathname: options?.path ? `${options.path}/${file.name}` : file.name,\n\t\t\t\t\tcontentType: file.type,\n\t\t\t\t\tsize: file.size,\n\t\t\t\t})\n\t\t\t} else {\n\t\t\t\tconst error = new Error(`Upload failed with status ${xhr.status}`) as Error & {\n\t\t\t\t\tstatus: number\n\t\t\t\t}\n\t\t\t\terror.status = xhr.status\n\t\t\t\treject(error)\n\t\t\t}\n\t\t})\n\n\t\txhr.addEventListener('error', () => {\n\t\t\tsignal?.removeEventListener('abort', handleAbort)\n\t\t\treject(new TypeError('Network error during upload'))\n\t\t})\n\n\t\txhr.addEventListener('abort', () => {\n\t\t\tsignal?.removeEventListener('abort', handleAbort)\n\t\t\treject(new DOMException('Upload aborted', 'AbortError'))\n\t\t})\n\n\t\txhr.open('PUT', uploadUrl)\n\t\txhr.setRequestHeader('Content-Type', file.type)\n\t\txhr.send(file)\n\t})\n}\n\n/**\n * Upload a user avatar\n *\n * @example\n * ```typescript\n * const avatar = await uploadAvatar(config, file, 'user-123')\n * console.log(avatar.url)\n * ```\n */\nexport async function uploadAvatar(\n\tconfig: SylphxConfig,\n\tfile: File,\n\tuserId: string,\n\toptions?: Pick<FileUploadOptions, 'onProgress'>,\n): Promise<UploadResult> {\n\treturn uploadFile(config, file, {\n\t\t...options,\n\t\ttype: 'avatar',\n\t\tuserId,\n\t})\n}\n\n/**\n * Delete a file\n *\n * @example\n * ```typescript\n * await deleteFile(config, 'file-123')\n * ```\n */\nexport async function deleteFile(config: SylphxConfig, fileId: string): Promise<void> {\n\tawait callApi(config, `/storage/files/${fileId}`, { method: 'DELETE' })\n}\n\n/**\n * Get a file's URL by ID\n *\n * @example\n * ```typescript\n * const url = await getFileUrl(config, 'file-123')\n * ```\n */\nexport async function getFileUrl(config: SylphxConfig, fileId: string): Promise<string> {\n\tconst data = await callApi<{ url: string }>(config, `/storage/files/${fileId}`, { method: 'GET' })\n\treturn data.url\n}\n\n/**\n * Get file info by ID\n *\n * @example\n * ```typescript\n * const info = await getFileInfo(config, 'file-123')\n * console.log(info.name, info.size)\n * ```\n */\nexport async function getFileInfo(config: SylphxConfig, fileId: string): Promise<FileInfo> {\n\treturn callApi<FileInfo>(config, `/storage/files/${fileId}`, {\n\t\tmethod: 'GET',\n\t})\n}\n\n/**\n * Generate a signed URL for accessing a private file\n *\n * Signed URLs provide time-limited access to private files without\n * exposing permanent URLs. Useful for:\n * - Secure document downloads\n * - Private media streaming\n * - Temporary file sharing\n *\n * @example\n * ```typescript\n * // Generate a download URL valid for 1 hour\n * const { url, expiresAt } = await getSignedUrl(config, 'file-123')\n *\n * // Generate an inline preview URL valid for 5 minutes\n * const preview = await getSignedUrl(config, 'file-123', {\n * expiresIn: 300,\n * disposition: 'inline',\n * })\n *\n * // Restrict access to a specific user\n * const userOnly = await getSignedUrl(config, 'file-123', {\n * userId: 'user-456',\n * })\n * ```\n */\nexport async function getSignedUrl(\n\tconfig: SylphxConfig,\n\tfileId: string,\n\toptions?: SignedUrlOptions,\n): Promise<SignedUrlResult> {\n\treturn callApi<SignedUrlResult>(config, '/storage/signed-url', {\n\t\tmethod: 'POST',\n\t\tbody: {\n\t\t\tfileId,\n\t\t\t...options,\n\t\t},\n\t})\n}\n","/**\n * Notifications Functions\n *\n * Pure functions for push notifications.\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { components } from \"./generated/api\";\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type RegisterPushRequest = components[\"schemas\"][\"RegisterPushRequest\"];\nexport type RegisterPushResponse =\n\tcomponents[\"schemas\"][\"RegisterPushResponse\"];\nexport type UnregisterPushRequest =\n\tcomponents[\"schemas\"][\"UnregisterPushRequest\"];\nexport type PushPreferencesResponse =\n\tcomponents[\"schemas\"][\"PushPreferencesResponse\"];\nexport type InAppMessage = components[\"schemas\"][\"InAppMessage\"];\nexport type InAppMessagesResponse =\n\tcomponents[\"schemas\"][\"InAppMessagesResponse\"];\nexport type MobileConfigResponse =\n\tcomponents[\"schemas\"][\"MobileConfigResponse\"];\nexport type MobileDevice = components[\"schemas\"][\"MobileDevice\"];\n\n// SDK-specific types for convenience\nexport interface PushSubscription {\n\tendpoint: string;\n\tkeys: {\n\t\tp256dh: string;\n\t\tauth: string;\n\t};\n}\n\nexport interface PushNotification {\n\ttitle: string;\n\tbody: string;\n\ticon?: string;\n\turl?: string;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Register a push subscription\n *\n * @example\n * ```typescript\n * // Get subscription from browser\n * const registration = await navigator.serviceWorker.ready\n * const sub = await registration.pushManager.subscribe({\n * userVisibleOnly: true,\n * applicationServerKey: vapidPublicKey,\n * })\n *\n * // Register with platform\n * await registerPush(config, {\n * endpoint: sub.endpoint,\n * keys: {\n * p256dh: sub.toJSON().keys!.p256dh,\n * auth: sub.toJSON().keys!.auth,\n * },\n * })\n * ```\n */\nexport async function registerPush(\n\tconfig: SylphxConfig,\n\tsubscription: PushSubscription,\n): Promise<void> {\n\tawait callApi(config, \"/notifications/register\", {\n\t\tmethod: \"POST\",\n\t\tbody: { subscription },\n\t});\n}\n\n/**\n * Unregister a push subscription\n *\n * @example\n * ```typescript\n * await unregisterPush(config, subscription.endpoint)\n * ```\n */\nexport async function unregisterPush(\n\tconfig: SylphxConfig,\n\tendpoint: string,\n): Promise<void> {\n\tawait callApi(config, \"/notifications/unregister\", {\n\t\tmethod: \"POST\",\n\t\tbody: { endpoint },\n\t});\n}\n\n/**\n * Send a push notification to a user (admin only)\n *\n * @example\n * ```typescript\n * await sendPush(config, 'user-123', {\n * title: 'New message',\n * body: 'You have a new message',\n * url: '/messages',\n * })\n * ```\n */\nexport async function sendPush(\n\tconfig: SylphxConfig,\n\tuserId: string,\n\tnotification: PushNotification,\n): Promise<{ sentTo: number; expired: number }> {\n\treturn callApi(config, \"/notifications/send\", {\n\t\tmethod: \"POST\",\n\t\tbody: { userId, ...notification },\n\t});\n}\n\n/**\n * Get push notification preferences\n *\n * @example\n * ```typescript\n * const prefs = await getPushPreferences(config)\n * ```\n */\nexport async function getPushPreferences(\n\tconfig: SylphxConfig,\n): Promise<{ enabled: boolean; categories: Record<string, boolean> }> {\n\treturn callApi(config, \"/notifications/preferences\", { method: \"GET\" });\n}\n\n/**\n * Update push notification preferences\n *\n * @example\n * ```typescript\n * await updatePushPreferences(config, {\n * enabled: true,\n * categories: { marketing: false, updates: true },\n * })\n * ```\n */\nexport async function updatePushPreferences(\n\tconfig: SylphxConfig,\n\tpreferences: { enabled?: boolean; categories?: Record<string, boolean> },\n): Promise<void> {\n\tawait callApi(config, \"/notifications/preferences\", {\n\t\tmethod: \"PUT\",\n\t\tbody: preferences,\n\t});\n}\n","/**\n * Push Notification Service Worker Template\n *\n * This module provides a service worker implementation for handling\n * push notifications. Apps should copy or import this into their\n * service worker file.\n *\n * ## Industry Patterns Implemented (OneSignal/FCM)\n * - Push event handling with notification display\n * - Notification click with deep link navigation\n * - Notification close tracking\n * - Token refresh handling\n * - Background sync for offline actions\n *\n * ## Usage\n *\n * Create a service worker file in your app's public directory:\n *\n * ```typescript\n * // public/sw.ts or src/service-worker.ts\n * import { initPushServiceWorker } from '@sylphx/platform-sdk/notifications'\n *\n * initPushServiceWorker({\n * defaultIcon: '/icon-192.png',\n * defaultBadge: '/badge-72.png',\n * onNotificationClick: (data) => {\n * // Custom click handling\n * console.log('Notification clicked:', data)\n * },\n * })\n * ```\n */\n\n/**\n * Service Worker type definitions\n * These are minimal type definitions for Service Worker APIs.\n * Full types available with `lib: [\"WebWorker\"]` in tsconfig.\n */\ninterface PushEventData {\n\tjson(): unknown;\n\ttext(): string;\n}\n\ninterface PushEvent extends ExtendableEvent {\n\tdata: PushEventData | null;\n}\n\ninterface NotificationEvent extends ExtendableEvent {\n\tnotification: Notification & {\n\t\tdata?: Record<string, unknown>;\n\t\tclose(): void;\n\t};\n\taction?: string;\n}\n\ninterface WindowClient {\n\turl: string;\n\tfocus(): Promise<WindowClient>;\n}\n\ninterface Clients {\n\tmatchAll(options: { type: \"window\"; includeUncontrolled: boolean }): Promise<\n\t\tWindowClient[]\n\t>;\n\topenWindow(url: string): Promise<WindowClient | null>;\n\tclaim(): Promise<void>;\n}\n\ninterface ExtendableEvent extends Event {\n\twaitUntil(promise: Promise<unknown>): void;\n}\n\ninterface ServiceWorkerRegistration {\n\tshowNotification(title: string, options?: NotificationOptions): Promise<void>;\n}\n\ninterface ServiceWorkerGlobalScopeSubset {\n\treadonly registration: ServiceWorkerRegistration;\n\treadonly clients: Clients;\n\taddEventListener(type: \"push\", listener: (event: PushEvent) => void): void;\n\taddEventListener(\n\t\ttype: \"notificationclick\" | \"notificationclose\",\n\t\tlistener: (event: NotificationEvent) => void,\n\t): void;\n\taddEventListener(\n\t\ttype: \"activate\",\n\t\tlistener: (event: ExtendableEvent) => void,\n\t): void;\n}\n\ndeclare const self: ServiceWorkerGlobalScopeSubset;\n\n/**\n * Notification payload from Sylphx platform\n */\nexport interface PushNotificationPayload {\n\t/** Notification title */\n\ttitle: string;\n\t/** Notification body text */\n\tbody: string;\n\t/** Icon URL (optional, falls back to default) */\n\ticon?: string;\n\t/** Badge URL for Android (optional) */\n\tbadge?: string;\n\t/** Image URL for expanded notification (optional) */\n\timage?: string;\n\t/** Click action URL (optional) */\n\turl?: string;\n\t/** Action buttons (optional) */\n\tactions?: Array<{\n\t\taction: string;\n\t\ttitle: string;\n\t\ticon?: string;\n\t}>;\n\t/** Custom data payload */\n\tdata?: Record<string, unknown>;\n\t/** Notification tag for grouping (optional) */\n\ttag?: string;\n\t/** Whether to require interaction (optional) */\n\trequireInteraction?: boolean;\n\t/** Vibration pattern (optional) */\n\tvibrate?: number[];\n\t/** Silent notification (optional) */\n\tsilent?: boolean;\n}\n\n/**\n * Service worker configuration options\n */\nexport interface PushServiceWorkerConfig {\n\t/** Default icon for notifications without an icon */\n\tdefaultIcon?: string;\n\t/** Default badge for notifications without a badge */\n\tdefaultBadge?: string;\n\t/** Called when notification is clicked */\n\tonNotificationClick?: (data: PushNotificationPayload) => void;\n\t/** Called when notification is closed without clicking */\n\tonNotificationClose?: (data: PushNotificationPayload) => void;\n\t/** Platform API URL for analytics/token refresh */\n\tplatformUrl?: string;\n\t/** App ID for API calls */\n\tappId?: string;\n}\n\n/**\n * Initialize push notification handling in service worker\n *\n * Call this in your service worker file to enable push notification handling.\n *\n * NOTE: This function should only be called from within a service worker context.\n * The types are loosely defined to work in both browser and service worker contexts.\n *\n * @example\n * ```typescript\n * // In your service worker (e.g., public/sw.ts)\n * initPushServiceWorker({\n * defaultIcon: '/icon-192.png',\n * defaultBadge: '/badge-72.png',\n * })\n * ```\n */\nexport function initPushServiceWorker(\n\tconfig: PushServiceWorkerConfig = {},\n): void {\n\tconst {\n\t\tdefaultIcon,\n\t\tdefaultBadge,\n\t\tonNotificationClick,\n\t\tonNotificationClose,\n\t} = config;\n\n\t// Handle push events (when notification arrives)\n\tself.addEventListener(\"push\", (event) => {\n\t\tif (!event.data) {\n\t\t\tconsole.warn(\"[Sylphx SW] Push event received without data\");\n\t\t\treturn;\n\t\t}\n\n\t\tlet payload: PushNotificationPayload;\n\t\ttry {\n\t\t\tpayload = event.data.json() as PushNotificationPayload;\n\t\t} catch {\n\t\t\t// Fallback for plain text payloads\n\t\t\tpayload = {\n\t\t\t\ttitle: \"Notification\",\n\t\t\t\tbody: event.data.text(),\n\t\t\t};\n\t\t}\n\n\t\t// Build notification options (compatible with both browser and SW contexts)\n\t\tconst notificationOptions = {\n\t\t\tbody: payload.body,\n\t\t\ticon: payload.icon || defaultIcon,\n\t\t\tbadge: payload.badge || defaultBadge,\n\t\t\timage: payload.image,\n\t\t\tdata: {\n\t\t\t\t...payload.data,\n\t\t\t\turl: payload.url,\n\t\t\t\t_sylphxPayload: payload,\n\t\t\t},\n\t\t\ttag: payload.tag,\n\t\t\trequireInteraction: payload.requireInteraction ?? false,\n\t\t\tvibrate: payload.vibrate,\n\t\t\tsilent: payload.silent ?? false,\n\t\t\tactions: payload.actions,\n\t\t};\n\n\t\tevent.waitUntil(\n\t\t\tself.registration.showNotification(payload.title, notificationOptions),\n\t\t);\n\t});\n\n\t// Handle notification click events\n\tself.addEventListener(\"notificationclick\", (event) => {\n\t\tevent.notification.close();\n\n\t\tconst data = event.notification.data;\n\t\tconst payload = data?._sylphxPayload as PushNotificationPayload | undefined;\n\t\tconst url = data?.url as string | undefined;\n\n\t\t// Call custom handler if provided\n\t\tif (onNotificationClick && payload) {\n\t\t\tonNotificationClick(payload);\n\t\t}\n\n\t\t// Handle action button clicks\n\t\tif (event.action) {\n\t\t\t// Custom action handling\n\t\t\tconsole.log(\"[Sylphx SW] Action clicked:\", event.action);\n\t\t}\n\n\t\t// Navigate to URL if provided\n\t\tif (url) {\n\t\t\tevent.waitUntil(\n\t\t\t\tself.clients\n\t\t\t\t\t.matchAll({ type: \"window\", includeUncontrolled: true })\n\t\t\t\t\t.then((clientList) => {\n\t\t\t\t\t\t// Try to focus an existing window with this URL\n\t\t\t\t\t\tfor (const client of clientList) {\n\t\t\t\t\t\t\tif (client.url === url && \"focus\" in client) {\n\t\t\t\t\t\t\t\treturn client.focus();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Open a new window if no existing window found\n\t\t\t\t\t\treturn self.clients.openWindow(url);\n\t\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t});\n\n\t// Handle notification close events (for analytics)\n\tself.addEventListener(\"notificationclose\", (event) => {\n\t\tconst data = event.notification.data;\n\t\tconst payload = data?._sylphxPayload as PushNotificationPayload | undefined;\n\n\t\tif (onNotificationClose && payload) {\n\t\t\tonNotificationClose(payload);\n\t\t}\n\t});\n\n\t// Handle service worker activation\n\tself.addEventListener(\"activate\", (event) => {\n\t\tevent.waitUntil(\n\t\t\t// Claim all clients immediately\n\t\t\tself.clients.claim(),\n\t\t);\n\t});\n\n\tconsole.log(\"[Sylphx SW] Push notification service worker initialized\");\n}\n\n/**\n * Helper to create a simple service worker script content\n *\n * For apps that want to dynamically generate their service worker,\n * this returns the JavaScript content as a string.\n *\n * @example\n * ```typescript\n * // In a route handler\n * export function GET() {\n * const content = createServiceWorkerScript({\n * defaultIcon: '/icon-192.png',\n * })\n * return new Response(content, {\n * headers: { 'Content-Type': 'application/javascript' },\n * })\n * }\n * ```\n */\nexport function createServiceWorkerScript(\n\tconfig: PushServiceWorkerConfig = {},\n): string {\n\tconst { defaultIcon = \"/icon-192.png\", defaultBadge = \"/badge-72.png\" } =\n\t\tconfig;\n\n\treturn `\n// Sylphx Push Notification Service Worker\n// Auto-generated - do not edit directly\n\nconst DEFAULT_ICON = '${defaultIcon}';\nconst DEFAULT_BADGE = '${defaultBadge}';\n\nself.addEventListener('push', (event) => {\n if (!event.data) return;\n\n let payload;\n try {\n payload = event.data.json();\n } catch {\n payload = { title: 'Notification', body: event.data.text() };\n }\n\n const options = {\n body: payload.body,\n icon: payload.icon || DEFAULT_ICON,\n badge: payload.badge || DEFAULT_BADGE,\n image: payload.image,\n data: { ...payload.data, url: payload.url },\n tag: payload.tag,\n requireInteraction: payload.requireInteraction || false,\n vibrate: payload.vibrate,\n silent: payload.silent || false,\n actions: payload.actions,\n };\n\n event.waitUntil(\n self.registration.showNotification(payload.title, options)\n );\n});\n\nself.addEventListener('notificationclick', (event) => {\n event.notification.close();\n const url = event.notification.data?.url;\n\n if (url) {\n event.waitUntil(\n clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => {\n for (const client of clientList) {\n if (client.url === url && 'focus' in client) {\n return client.focus();\n }\n }\n if (clients.openWindow) {\n return clients.openWindow(url);\n }\n })\n );\n }\n});\n\nself.addEventListener('activate', (event) => {\n event.waitUntil(clients.claim());\n});\n\nconsole.log('[Sylphx SW] Push notification service worker active');\n`.trim();\n}\n\n/**\n * Register the service worker from the client side\n *\n * Call this in your app's entry point to register the service worker.\n *\n * @example\n * ```typescript\n * // In your app's entry point (e.g., _app.tsx or layout.tsx)\n * import { registerPushServiceWorker } from '@sylphx/platform-sdk/notifications'\n *\n * useEffect(() => {\n * registerPushServiceWorker('/sw.js')\n * }, [])\n * ```\n */\nexport async function registerPushServiceWorker(\n\tswPath = \"/sw.js\",\n): Promise<ServiceWorkerRegistration | null> {\n\tif (typeof window === \"undefined\") return null;\n\tif (!(\"serviceWorker\" in navigator)) {\n\t\tconsole.warn(\"[Sylphx] Service workers not supported\");\n\t\treturn null;\n\t}\n\n\ttry {\n\t\tconst registration = await navigator.serviceWorker.register(swPath);\n\t\tconsole.log(\"[Sylphx] Service worker registered:\", registration.scope);\n\t\treturn registration;\n\t} catch (error) {\n\t\tconsole.error(\"[Sylphx] Service worker registration failed:\", error);\n\t\treturn null;\n\t}\n}\n","/**\n * Jobs Functions\n *\n * Pure functions for background job scheduling.\n *\n * ## Industry-Standard Features\n * - **Idempotency Keys**: Stripe/Inngest pattern for safe retries\n * - **Automatic Retries**: Configurable retry count with exponential backoff\n * - **Cron Scheduling**: Recurring jobs with pause/resume support\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { components } from \"./generated/api\";\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type ScheduleJobRequest = components[\"schemas\"][\"ScheduleJobRequest\"];\nexport type ScheduleJobResponse = components[\"schemas\"][\"ScheduleJobResponse\"];\nexport type ScheduleCronRequest = components[\"schemas\"][\"ScheduleCronRequest\"];\nexport type ScheduleCronResponse =\n\tcomponents[\"schemas\"][\"ScheduleCronResponse\"];\nexport type Job = components[\"schemas\"][\"Job\"];\nexport type ListJobsResponse = components[\"schemas\"][\"ListJobsResponse\"];\n\n// SDK-specific types for convenience\nexport interface JobInput {\n\t/** Callback URL to call when job executes */\n\tcallbackUrl: string;\n\t/** Job name/type */\n\tname?: string;\n\t/** Job type for categorization */\n\ttype?: string;\n\t/** Job payload sent to callback */\n\tpayload?: Record<string, unknown>;\n\t/** HTTP method for callback (default: POST) */\n\tmethod?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\";\n\t/** Additional headers for callback */\n\theaders?: Record<string, string>;\n\t/** Delay before executing (in seconds, max 604800 = 7 days) */\n\tdelay?: number;\n\t/** Schedule for later (ISO timestamp) */\n\tscheduledFor?: string;\n\t/** Number of retries on failure (0-5, default: 3) */\n\tretries?: number;\n\t/** Request timeout in seconds (1-300, default: 30) */\n\ttimeout?: number;\n\t/**\n\t * Idempotency key for safe retries (Stripe/Inngest pattern).\n\t *\n\t * When provided, prevents duplicate job execution if the same\n\t * key is used within a 24-hour window. Useful for:\n\t * - Network retry safety\n\t * - At-most-once delivery guarantee\n\t * - Webhook deduplication\n\t *\n\t * @example\n\t * ```typescript\n\t * // Use a unique key derived from the operation\n\t * await scheduleJob(config, {\n\t * callbackUrl: 'https://myapp.com/api/jobs/send-email',\n\t * payload: { userId: 'user-123', template: 'welcome' },\n\t * idempotencyKey: `welcome-email-user-123-${Date.now()}`,\n\t * })\n\t * ```\n\t */\n\tidempotencyKey?: string;\n}\n\nexport interface JobResult {\n\t/** Job ID */\n\tjobId: string;\n\t/** QStash message ID */\n\tmessageId?: string;\n\t/** Scheduled execution time */\n\tscheduledFor?: string;\n}\n\nexport interface JobStatus {\n\tid: string;\n\tname?: string;\n\tstatus:\n\t\t| \"pending\"\n\t\t| \"queued\"\n\t\t| \"running\"\n\t\t| \"completed\"\n\t\t| \"failed\"\n\t\t| \"cancelled\";\n\tpayload?: Record<string, unknown>;\n\tresult?: unknown;\n\terror?: string;\n\tcreatedAt: string;\n\tqueuedAt?: string;\n\tstartedAt?: string;\n\tcompletedAt?: string;\n}\n\nexport interface CronInput {\n\t/** Callback URL to call on each cron trigger */\n\tcallbackUrl: string;\n\t/** Cron expression (e.g., '0 0 * * *' for daily at midnight) */\n\tcron: string;\n\t/** Job name (required, max 200 chars) */\n\tname: string;\n\t/** Job type for categorization */\n\ttype?: string;\n\t/** Job payload sent to callback */\n\tpayload?: Record<string, unknown>;\n\t/** HTTP method for callback (default: POST) */\n\tmethod?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\";\n\t/** Additional headers for callback */\n\theaders?: Record<string, string>;\n\t/** Number of retries on failure (0-5, default: 3) */\n\tretries?: number;\n\t/** Start in paused state */\n\tpaused?: boolean;\n\t/**\n\t * Idempotency key for safe cron creation (Stripe/Inngest pattern).\n\t *\n\t * When provided, prevents duplicate cron schedule creation if\n\t * the same key is used. Useful for deployment scripts that\n\t * may run multiple times.\n\t *\n\t * @example\n\t * ```typescript\n\t * await createCron(config, {\n\t * callbackUrl: 'https://myapp.com/api/jobs/daily-report',\n\t * cron: '0 9 * * *',\n\t * name: 'daily-report',\n\t * idempotencyKey: 'daily-report-cron-v1', // Same key = same cron\n\t * })\n\t * ```\n\t */\n\tidempotencyKey?: string;\n}\n\nexport interface CronSchedule {\n\t/** Internal job ID */\n\tjobId?: string;\n\t/** QStash schedule ID */\n\tscheduleId: string;\n\t/** Cron expression */\n\tcron: string;\n\t/** Whether currently paused */\n\tpaused: boolean;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Schedule a one-time job for execution\n *\n * @example\n * ```typescript\n * const job = await scheduleJob(config, {\n * callbackUrl: 'https://myapp.com/api/jobs/send-email',\n * name: 'send-email',\n * payload: { to: 'user@example.com', template: 'welcome' },\n * delay: 60, // Run in 60 seconds\n * })\n *\n * console.log(`Job scheduled: ${job.jobId}`)\n * ```\n */\nexport async function scheduleJob(\n\tconfig: SylphxConfig,\n\tinput: JobInput,\n): Promise<JobResult> {\n\treturn callApi<JobResult>(config, \"/jobs/schedule\", {\n\t\tmethod: \"POST\",\n\t\tbody: input,\n\t});\n}\n\n/**\n * Get a job's status by ID\n *\n * @example\n * ```typescript\n * const job = await getJob(config, 'job-123')\n * console.log(job.status) // 'completed'\n * ```\n */\nexport async function getJob(\n\tconfig: SylphxConfig,\n\tjobId: string,\n): Promise<JobStatus> {\n\treturn callApi<JobStatus>(config, `/jobs/${jobId}`, { method: \"GET\" });\n}\n\n/**\n * Cancel a pending job\n *\n * @example\n * ```typescript\n * await cancelJob(config, 'job-123')\n * ```\n */\nexport async function cancelJob(\n\tconfig: SylphxConfig,\n\tjobId: string,\n): Promise<boolean> {\n\tconst result = await callApi<{ success: boolean }>(\n\t\tconfig,\n\t\t`/jobs/${jobId}/cancel`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t},\n\t);\n\treturn result.success;\n}\n\n/**\n * List jobs\n *\n * @example\n * ```typescript\n * const jobs = await listJobs(config, { status: 'pending', limit: 10 })\n * ```\n */\nexport async function listJobs(\n\tconfig: SylphxConfig,\n\toptions?: { status?: JobStatus[\"status\"]; limit?: number; offset?: number },\n): Promise<{ jobs: JobStatus[]; total: number }> {\n\treturn callApi(config, \"/jobs\", {\n\t\tmethod: \"GET\",\n\t\tquery: options as Record<string, string | number | undefined>,\n\t});\n}\n\n/**\n * Create a recurring cron job\n *\n * @example\n * ```typescript\n * const cron = await createCron(config, {\n * callbackUrl: 'https://myapp.com/api/webhooks/platform-jobs',\n * cron: '0 9 * * *', // Every day at 9am UTC\n * name: 'daily-report',\n * payload: { type: 'daily' },\n * })\n *\n * console.log(`Cron created: ${cron.scheduleId}`)\n * ```\n */\nexport async function createCron(\n\tconfig: SylphxConfig,\n\tinput: CronInput,\n): Promise<CronSchedule> {\n\treturn callApi<CronSchedule>(config, \"/jobs/cron\", {\n\t\tmethod: \"POST\",\n\t\tbody: input,\n\t});\n}\n\n/**\n * Pause a cron schedule\n *\n * @example\n * ```typescript\n * await pauseCron(config, 'schedule-123')\n * ```\n */\nexport async function pauseCron(\n\tconfig: SylphxConfig,\n\tscheduleId: string,\n): Promise<boolean> {\n\tconst result = await callApi<{ success: boolean }>(\n\t\tconfig,\n\t\t`/jobs/cron/${scheduleId}/pause`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t},\n\t);\n\treturn result.success;\n}\n\n/**\n * Resume a cron schedule\n *\n * @example\n * ```typescript\n * await resumeCron(config, 'schedule-123')\n * ```\n */\nexport async function resumeCron(\n\tconfig: SylphxConfig,\n\tscheduleId: string,\n): Promise<boolean> {\n\tconst result = await callApi<{ success: boolean }>(\n\t\tconfig,\n\t\t`/jobs/cron/${scheduleId}/resume`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t},\n\t);\n\treturn result.success;\n}\n\n/**\n * Delete a cron schedule\n *\n * @example\n * ```typescript\n * await deleteCron(config, 'schedule-123')\n * ```\n */\nexport async function deleteCron(\n\tconfig: SylphxConfig,\n\tscheduleId: string,\n): Promise<boolean> {\n\tconst result = await callApi<{ success: boolean }>(\n\t\tconfig,\n\t\t`/jobs/cron/${scheduleId}`,\n\t\t{\n\t\t\tmethod: \"DELETE\",\n\t\t},\n\t);\n\treturn result.success;\n}\n","/**\n * Feature Flags Functions\n *\n * Pure functions for feature flag evaluation.\n *\n * Pattern: LaunchDarkly/Statsig server-side evaluation\n * - Server-side: POST /flags/evaluate with context\n * - Returns evaluated results (enabled/disabled for this context)\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\n\n// ============================================================================\n// Types (SDK-specific - no direct API schema for flag results)\n// ============================================================================\n\nexport interface FlagResult {\n\t/** Flag key */\n\tkey: string;\n\t/** Whether the flag is enabled for this context */\n\tenabled: boolean;\n\t/** Variant value (for multivariate flags) */\n\tvariant?: string;\n\t/** Reason for the evaluation result */\n\treason?: string;\n\t/** Additional payload data */\n\tpayload?: Record<string, unknown>;\n}\n\nexport interface FlagContext {\n\t/** User ID for consistent targeting */\n\tuserId?: string;\n\t/** Anonymous ID for pre-auth targeting */\n\tanonymousId?: string;\n\t/** User properties for targeting rules (plan, isAdmin, etc.) */\n\tproperties?: Record<string, unknown>;\n}\n\n/** Response from the evaluate endpoint */\ninterface EvaluateFlagsResponse {\n\tdata: Record<string, FlagResult>;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Check a single feature flag (server-side evaluation)\n *\n * Uses POST /flags/evaluate for consistent, server-side targeting.\n * The server evaluates rollout percentage, premium targeting, etc.\n *\n * @example\n * ```typescript\n * const flag = await checkFlag(config, 'new-checkout', {\n * userId: 'user-123',\n * properties: { plan: 'pro' },\n * })\n *\n * if (flag.enabled) {\n * // Show new checkout\n * }\n * ```\n */\nexport async function checkFlag(\n\tconfig: SylphxConfig,\n\tflagKey: string,\n\tcontext?: FlagContext,\n): Promise<FlagResult> {\n\tconst response = await callApi<EvaluateFlagsResponse>(\n\t\tconfig,\n\t\t\"/flags/evaluate\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: {\n\t\t\t\tcontext: {\n\t\t\t\t\tuserId: context?.userId,\n\t\t\t\t\tanonymousId: context?.anonymousId,\n\t\t\t\t\tproperties: context?.properties,\n\t\t\t\t},\n\t\t\t\tkeys: [flagKey],\n\t\t\t},\n\t\t},\n\t);\n\n\t// Return the evaluated flag, or a disabled default if not found\n\treturn (\n\t\tresponse.data[flagKey] ?? {\n\t\t\tkey: flagKey,\n\t\t\tenabled: false,\n\t\t\treason: \"flag_not_found\",\n\t\t}\n\t);\n}\n\n/**\n * Get multiple feature flags at once (batch evaluation)\n *\n * Evaluates all requested flags in a single API call.\n * More efficient than calling checkFlag() multiple times.\n *\n * @example\n * ```typescript\n * const flags = await getFlags(config, ['new-checkout', 'dark-mode', 'ai-features'], {\n * userId: 'user-123',\n * })\n *\n * if (flags['new-checkout'].enabled) {\n * // Show new checkout\n * }\n * ```\n */\nexport async function getFlags(\n\tconfig: SylphxConfig,\n\tflagKeys: string[],\n\tcontext?: FlagContext,\n): Promise<Record<string, FlagResult>> {\n\tconst response = await callApi<EvaluateFlagsResponse>(\n\t\tconfig,\n\t\t\"/flags/evaluate\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: {\n\t\t\t\tcontext: {\n\t\t\t\t\tuserId: context?.userId,\n\t\t\t\t\tanonymousId: context?.anonymousId,\n\t\t\t\t\tproperties: context?.properties,\n\t\t\t\t},\n\t\t\t\tkeys: flagKeys,\n\t\t\t},\n\t\t},\n\t);\n\n\treturn response.data;\n}\n\n/**\n * Get all feature flags for a context (bootstrap)\n *\n * Evaluates ALL flags for the app in a single API call.\n * Useful for bootstrapping the flag state on app load.\n *\n * @example\n * ```typescript\n * // Bootstrap all flags on app load\n * const allFlags = await getAllFlags(config, { userId: 'user-123' })\n *\n * // Use throughout the app\n * if (allFlags['new-checkout']?.enabled) {\n * // Show new checkout\n * }\n * ```\n */\nexport async function getAllFlags(\n\tconfig: SylphxConfig,\n\tcontext?: FlagContext,\n): Promise<Record<string, FlagResult>> {\n\tconst response = await callApi<EvaluateFlagsResponse>(\n\t\tconfig,\n\t\t\"/flags/evaluate\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: {\n\t\t\t\tcontext: {\n\t\t\t\t\tuserId: context?.userId,\n\t\t\t\t\tanonymousId: context?.anonymousId,\n\t\t\t\t\tproperties: context?.properties,\n\t\t\t\t},\n\t\t\t\t// Omit keys to get all flags\n\t\t\t},\n\t\t},\n\t);\n\n\treturn response.data;\n}\n\n/**\n * Check if a flag is enabled (boolean helper)\n *\n * @example\n * ```typescript\n * if (await isEnabled(config, 'new-checkout', { userId: 'user-123' })) {\n * // Show new checkout\n * }\n * ```\n */\nexport async function isEnabled(\n\tconfig: SylphxConfig,\n\tflagKey: string,\n\tcontext?: FlagContext,\n): Promise<boolean> {\n\tconst flag = await checkFlag(config, flagKey, context);\n\treturn flag.enabled;\n}\n\n/**\n * Get flag variant (for A/B tests)\n *\n * @example\n * ```typescript\n * const variant = await getVariant(config, 'checkout-experiment', {\n * userId: 'user-123',\n * })\n *\n * switch (variant) {\n * case 'control':\n * // Show original checkout\n * break\n * case 'variant-a':\n * // Show variant A\n * break\n * case 'variant-b':\n * // Show variant B\n * break\n * }\n * ```\n */\nexport async function getVariant(\n\tconfig: SylphxConfig,\n\tflagKey: string,\n\tcontext?: FlagContext,\n): Promise<string | undefined> {\n\tconst flag = await checkFlag(config, flagKey, context);\n\treturn flag.variant;\n}\n\n/**\n * Get flag payload (for remote config)\n *\n * @example\n * ```typescript\n * const payload = await getFlagPayload<{ maxItems: number }>(config, 'cart-config', {\n * userId: 'user-123',\n * })\n *\n * console.log(payload?.maxItems) // 10\n * ```\n */\nexport async function getFlagPayload<T extends Record<string, unknown>>(\n\tconfig: SylphxConfig,\n\tflagKey: string,\n\tcontext?: FlagContext,\n): Promise<T | undefined> {\n\tconst flag = await checkFlag(config, flagKey, context);\n\treturn flag.payload as T | undefined;\n}\n","/**\n * Webhooks Functions\n *\n * Pure functions for webhook configuration and delivery management.\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { components } from \"./generated/api\";\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type WebhookConfigResponse =\n\tcomponents[\"schemas\"][\"WebhookConfigResponse\"];\nexport type WebhookEnvironmentConfig =\n\tcomponents[\"schemas\"][\"WebhookEnvironmentConfig\"];\nexport type UpdateWebhookConfigRequest =\n\tcomponents[\"schemas\"][\"UpdateWebhookConfigRequest\"];\nexport type UpdateWebhookConfigResponse =\n\tcomponents[\"schemas\"][\"UpdateWebhookConfigResponse\"];\nexport type ListWebhookDeliveriesResponse =\n\tcomponents[\"schemas\"][\"ListWebhookDeliveriesResponse\"];\nexport type WebhookDelivery = components[\"schemas\"][\"WebhookDelivery\"];\nexport type ReplayDeliveryResponse =\n\tcomponents[\"schemas\"][\"ReplayDeliveryResponse\"];\nexport type WebhookStatsResponse =\n\tcomponents[\"schemas\"][\"WebhookStatsResponse\"];\n\n// SDK-specific types for convenience\nexport interface WebhookEnvironment {\n\tid: string;\n\tname: string;\n\twebhookUrl: string | null;\n\twebhookSecret?: string | null;\n\thasSecret?: boolean;\n\tevents?: string[];\n\tcreatedAt: string;\n\tupdatedAt: string | null;\n}\n\nexport interface WebhookConfig {\n\tenvironments: WebhookEnvironment[];\n\tsupportedEvents?: string[];\n\tenabled?: boolean;\n\turl?: string | null;\n\tsecret?: string | null;\n\tevents?: string[];\n}\n\nexport interface WebhookConfigUpdate {\n\tenvironmentId: string;\n\twebhookUrl: string | null;\n}\n\nexport interface WebhookDeliveriesResult {\n\tdeliveries: WebhookDelivery[];\n\ttotal: number;\n\thasMore: boolean;\n}\n\nexport interface WebhookStats {\n\t// Summary totals\n\ttotal: number;\n\tdelivered: number;\n\tfailed: number;\n\tpending: number;\n\tdeliveryRate: number;\n\tavgLatencyMs: number | null;\n\t// Extended stats (for UI)\n\tperiod?: string;\n\ttotals?: {\n\t\ttotal: number;\n\t\tdelivered: number;\n\t\tfailed: number;\n\t\tpending: number;\n\t\tdeliveryRate: number | string;\n\t};\n\tbyEvent?: Array<{ event: string; count: number }>;\n\tbyStatus?: Array<{ status: string; count: number }>;\n}\n\nexport interface ListDeliveriesOptions {\n\tenvironmentId?: string;\n\tstatus?: \"pending\" | \"queued\" | \"delivered\" | \"failed\";\n\tlimit?: number;\n\toffset?: number;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Get webhook configuration for the app\n *\n * @example\n * ```typescript\n * const config = await getWebhookConfig(sylphxConfig)\n * console.log(config.environments)\n * ```\n */\nexport async function getWebhookConfig(\n\tconfig: SylphxConfig,\n): Promise<WebhookConfig> {\n\treturn callApi(config, \"/webhooks/config\", { method: \"GET\" });\n}\n\n/**\n * Update webhook URL for an environment\n *\n * @example\n * ```typescript\n * await updateWebhookConfig(config, {\n * environmentId: 'env-123',\n * webhookUrl: 'https://myapp.com/webhooks',\n * })\n * ```\n */\nexport async function updateWebhookConfig(\n\tconfig: SylphxConfig,\n\tdata: WebhookConfigUpdate,\n): Promise<void> {\n\treturn callApi(config, \"/webhooks/config\", { method: \"PUT\", body: data });\n}\n\n/**\n * Get webhook delivery history\n *\n * @example\n * ```typescript\n * const { deliveries, total } = await getWebhookDeliveries(config, {\n * status: 'failed',\n * limit: 20,\n * })\n * ```\n */\nexport async function getWebhookDeliveries(\n\tconfig: SylphxConfig,\n\toptions?: ListDeliveriesOptions,\n): Promise<WebhookDeliveriesResult> {\n\treturn callApi(config, \"/webhooks/deliveries\", {\n\t\tmethod: \"GET\",\n\t\tquery: options as Record<string, string | number | undefined>,\n\t});\n}\n\n/**\n * Get a single webhook delivery by ID\n *\n * @example\n * ```typescript\n * const delivery = await getWebhookDelivery(config, 'del-123')\n * console.log(delivery.payload)\n * ```\n */\nexport async function getWebhookDelivery(\n\tconfig: SylphxConfig,\n\tdeliveryId: string,\n): Promise<WebhookDelivery> {\n\treturn callApi(config, `/webhooks/deliveries/${deliveryId}`, {\n\t\tmethod: \"GET\",\n\t});\n}\n\n/**\n * Replay a failed webhook delivery\n *\n * @example\n * ```typescript\n * await replayWebhookDelivery(config, 'del-123')\n * ```\n */\nexport async function replayWebhookDelivery(\n\tconfig: SylphxConfig,\n\tdeliveryId: string,\n): Promise<void> {\n\treturn callApi(config, `/webhooks/deliveries/${deliveryId}/replay`, {\n\t\tmethod: \"POST\",\n\t});\n}\n\n/**\n * Get webhook statistics\n *\n * @example\n * ```typescript\n * const stats = await getWebhookStats(config)\n * console.log(`Delivery rate: ${stats.deliveryRate}%`)\n * ```\n */\nexport async function getWebhookStats(\n\tconfig: SylphxConfig,\n\tenvironmentId?: string,\n): Promise<WebhookStats> {\n\treturn callApi(config, \"/webhooks/stats\", {\n\t\tmethod: \"GET\",\n\t\tquery: environmentId ? { environmentId } : undefined,\n\t});\n}\n","/**\n * Email Functions\n *\n * Pure functions for transactional email operations.\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { components } from \"./generated/api\";\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type SendEmailRequest = components[\"schemas\"][\"SendEmailRequest\"];\nexport type SendEmailResponse = components[\"schemas\"][\"SendEmailResponse\"];\nexport type SendTemplatedEmailRequest =\n\tcomponents[\"schemas\"][\"SendTemplatedEmailRequest\"];\nexport type SendTemplatedEmailResponse =\n\tcomponents[\"schemas\"][\"SendTemplatedEmailResponse\"];\nexport type SendToUserRequest = components[\"schemas\"][\"SendToUserRequest\"];\nexport type SendToUserResponse = components[\"schemas\"][\"SendToUserResponse\"];\nexport type NewsletterSubscribeRequest =\n\tcomponents[\"schemas\"][\"NewsletterSubscribeRequest\"];\nexport type NewsletterSubscribeResponse =\n\tcomponents[\"schemas\"][\"NewsletterSubscribeResponse\"];\n\n// SDK-specific types for convenience\nexport interface SendEmailOptions {\n\t/** Recipient email address */\n\tto: string;\n\t/** Email subject line */\n\tsubject: string;\n\t/** HTML content */\n\thtml: string;\n\t/** Plain text content (optional fallback) */\n\ttext?: string;\n\t/** Reply-to address */\n\treplyTo?: string;\n\t/**\n\t * Sender email address (must be from a verified domain).\n\t * Falls back to the app environment default, then the platform FROM_EMAIL.\n\t *\n\t * @example `support@yourdomain.com`\n\t */\n\tfromEmail?: string;\n\t/**\n\t * Sender display name (used together with fromEmail).\n\t *\n\t * @example `Acme Support`\n\t */\n\tfromName?: string;\n\t/**\n\t * Idempotency key for safe retries (Stripe pattern)\n\t *\n\t * When provided, prevents duplicate email sends if the same request\n\t * is retried within 24 hours. Use a unique key per logical operation.\n\t *\n\t * @example `welcome-email-${userId}`\n\t */\n\tidempotencyKey?: string;\n}\n\nexport interface SendTemplatedEmailOptions {\n\t/** Template name: 'welcome', 'verification', 'password_reset', 'security_alert' */\n\ttemplate: \"welcome\" | \"verification\" | \"password_reset\" | \"security_alert\";\n\t/** Recipient email address */\n\tto: string;\n\t/** Template variables */\n\tdata?: Record<string, unknown>;\n\t/**\n\t * Idempotency key for safe retries (Stripe pattern)\n\t *\n\t * @example `verification-email-${userId}`\n\t */\n\tidempotencyKey?: string;\n}\n\nexport interface SendToUserOptions {\n\t/** User ID to send to */\n\tuserId: string;\n\t/** Email subject line */\n\tsubject: string;\n\t/** HTML content */\n\thtml: string;\n\t/** Plain text content (optional fallback) */\n\ttext?: string;\n\t/**\n\t * Idempotency key for safe retries (Stripe pattern)\n\t *\n\t * @example `notification-${userId}-${Date.now()}`\n\t */\n\tidempotencyKey?: string;\n}\n\nexport interface ScheduleEmailOptions {\n\t/** Recipient email address */\n\tto: string;\n\t/** Recipient name (optional) */\n\ttoName?: string;\n\t/** Email subject line */\n\tsubject: string;\n\t/** HTML content */\n\thtml?: string;\n\t/** Plain text content */\n\ttext?: string;\n\t/** Reply-to address */\n\treplyTo?: string;\n\t/** From email (defaults to app's configured sender) */\n\tfromEmail?: string;\n\t/** From name */\n\tfromName?: string;\n\t/** ISO timestamp for when to send */\n\tscheduledFor: string;\n\t/** Template key for templated emails */\n\ttemplateKey?: string;\n\t/** Template variables */\n\ttemplateData?: Record<string, unknown>;\n\t/** Idempotency key to prevent duplicates */\n\tidempotencyKey?: string;\n\t/** Custom metadata */\n\tmetadata?: Record<string, unknown>;\n}\n\nexport interface ScheduledEmail {\n\tid: string;\n\tto: string;\n\ttoName: string | null;\n\tsubject: string;\n\tstatus: \"pending\" | \"queued\" | \"sent\" | \"cancelled\" | \"failed\";\n\tscheduledFor: string;\n\tsentAt: string | null;\n\tcreatedAt: string;\n}\n\nexport interface ScheduledEmailsResult {\n\temails: ScheduledEmail[];\n\ttotal: number;\n\thasMore: boolean;\n}\n\nexport interface ScheduledEmailStats {\n\ttotal: number;\n\tpending: number;\n\tqueued: number;\n\tsent: number;\n\tcancelled: number;\n\tfailed: number;\n}\n\nexport interface ListScheduledEmailsOptions {\n\tstatus?: \"pending\" | \"queued\" | \"sent\" | \"cancelled\" | \"failed\" | \"all\";\n\tlimit?: number;\n\toffset?: number;\n}\n\nexport interface SendResult {\n\tid: string;\n\tsuccess: boolean;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Check if email service is configured for the app\n *\n * @example\n * ```typescript\n * const configured = await isEmailConfigured(config)\n * if (!configured) console.log('Please configure email settings')\n * ```\n */\nexport async function isEmailConfigured(\n\tconfig: SylphxConfig,\n): Promise<boolean> {\n\treturn callApi(config, \"/email/configured\", { method: \"GET\" });\n}\n\n/**\n * Send a custom email\n *\n * @example\n * ```typescript\n * const result = await sendEmail(config, {\n * to: 'user@example.com',\n * subject: 'Hello!',\n * html: '<p>Welcome to our app!</p>',\n * idempotencyKey: `welcome-${userId}`, // Safe retry\n * })\n * ```\n */\nexport async function sendEmail(\n\tconfig: SylphxConfig,\n\toptions: SendEmailOptions,\n): Promise<SendResult> {\n\tconst { idempotencyKey, ...body } = options;\n\treturn callApi(config, \"/email/send\", {\n\t\tmethod: \"POST\",\n\t\tbody,\n\t\tidempotencyKey,\n\t});\n}\n\n/**\n * Send a templated email\n *\n * @example\n * ```typescript\n * await sendTemplatedEmail(config, {\n * template: 'welcome',\n * to: 'user@example.com',\n * data: { name: 'John' },\n * idempotencyKey: `welcome-${userId}`, // Safe retry\n * })\n * ```\n */\nexport async function sendTemplatedEmail(\n\tconfig: SylphxConfig,\n\toptions: SendTemplatedEmailOptions,\n): Promise<SendResult> {\n\tconst { idempotencyKey, ...body } = options;\n\treturn callApi(config, \"/email/send-templated\", {\n\t\tmethod: \"POST\",\n\t\tbody,\n\t\tidempotencyKey,\n\t});\n}\n\n/**\n * Send email to a user by their ID\n *\n * @example\n * ```typescript\n * await sendEmailToUser(config, {\n * userId: 'user-123',\n * subject: 'Account Update',\n * html: '<p>Your account has been updated.</p>',\n * idempotencyKey: `update-${userId}-${timestamp}`, // Safe retry\n * })\n * ```\n */\nexport async function sendEmailToUser(\n\tconfig: SylphxConfig,\n\toptions: SendToUserOptions,\n): Promise<SendResult> {\n\tconst { idempotencyKey, ...body } = options;\n\treturn callApi(config, \"/email/send-to-user\", {\n\t\tmethod: \"POST\",\n\t\tbody,\n\t\tidempotencyKey,\n\t});\n}\n\n/**\n * Schedule an email for future delivery\n *\n * @example\n * ```typescript\n * const scheduled = await scheduleEmail(config, {\n * to: 'user@example.com',\n * subject: 'Reminder',\n * html: '<p>Don\\'t forget!</p>',\n * scheduledFor: new Date(Date.now() + 86400000).toISOString(), // 24 hours\n * })\n * ```\n */\nexport async function scheduleEmail(\n\tconfig: SylphxConfig,\n\toptions: ScheduleEmailOptions,\n): Promise<ScheduledEmail> {\n\treturn callApi(config, \"/email/schedule\", { method: \"POST\", body: options });\n}\n\n/**\n * List scheduled emails\n *\n * @example\n * ```typescript\n * const { emails, total } = await listScheduledEmails(config, {\n * status: 'pending',\n * limit: 20,\n * })\n * ```\n */\nexport async function listScheduledEmails(\n\tconfig: SylphxConfig,\n\toptions?: ListScheduledEmailsOptions,\n): Promise<ScheduledEmailsResult> {\n\treturn callApi(config, \"/email/scheduled\", {\n\t\tmethod: \"GET\",\n\t\tquery: options as Record<string, string | number | undefined>,\n\t});\n}\n\n/**\n * Get a scheduled email by ID\n *\n * @example\n * ```typescript\n * const email = await getScheduledEmail(config, 'email-123')\n * console.log(email.status)\n * ```\n */\nexport async function getScheduledEmail(\n\tconfig: SylphxConfig,\n\temailId: string,\n): Promise<ScheduledEmail> {\n\treturn callApi(config, `/email/scheduled/${emailId}`, { method: \"GET\" });\n}\n\n/**\n * Cancel a scheduled email\n *\n * @example\n * ```typescript\n * await cancelScheduledEmail(config, 'email-123')\n * ```\n */\nexport async function cancelScheduledEmail(\n\tconfig: SylphxConfig,\n\temailId: string,\n): Promise<void> {\n\treturn callApi(config, `/email/scheduled/${emailId}/cancel`, {\n\t\tmethod: \"POST\",\n\t});\n}\n\n/**\n * Reschedule an email\n *\n * @example\n * ```typescript\n * await rescheduleEmail(config, 'email-123', new Date(Date.now() + 3600000).toISOString())\n * ```\n */\nexport async function rescheduleEmail(\n\tconfig: SylphxConfig,\n\temailId: string,\n\tscheduledFor: string,\n): Promise<ScheduledEmail> {\n\treturn callApi(config, `/email/scheduled/${emailId}/reschedule`, {\n\t\tmethod: \"POST\",\n\t\tbody: { scheduledFor },\n\t});\n}\n\n/**\n * Get scheduled email statistics\n *\n * @example\n * ```typescript\n * const stats = await getScheduledEmailStats(config)\n * console.log(`${stats.pending} emails pending`)\n * ```\n */\nexport async function getScheduledEmailStats(\n\tconfig: SylphxConfig,\n): Promise<ScheduledEmailStats> {\n\treturn callApi(config, \"/email/scheduled/stats\", { method: \"GET\" });\n}\n\n// ============================================================================\n// Email Domain Management\n// ============================================================================\n\n/**\n * DNS record required for domain verification\n */\nexport interface DnsRecord {\n\t/** DNS record type */\n\ttype: \"MX\" | \"TXT\" | \"CNAME\";\n\t/** DNS record name (hostname) */\n\tname: string;\n\t/** DNS record value */\n\tvalue: string;\n\t/** MX priority (only for MX records) */\n\tpriority?: number;\n\t/** TTL in seconds */\n\tttl: number;\n}\n\n/**\n * A registered custom sending domain\n */\nexport interface EmailDomain {\n\tid: string;\n\tdomain: string;\n\tstatus: \"pending\" | \"verifying\" | \"verified\" | \"failed\";\n\tdefaultFromEmail: string | null;\n\tdefaultFromName: string | null;\n\tdnsRecords: DnsRecord[];\n\tcreatedAt: string;\n\tverifiedAt: string | null;\n}\n\nexport interface RegisterEmailDomainOptions {\n\t/** Default from address for this domain (e.g. support@cubeage.com) */\n\tdefaultFromEmail?: string;\n\t/** Default sender display name (e.g. Cubeage Support) */\n\tdefaultFromName?: string;\n}\n\nexport interface SetDefaultEmailDomainOptions {\n\t/** Override the default from address */\n\tdefaultFromEmail?: string;\n\t/** Override the default sender name */\n\tdefaultFromName?: string;\n}\n\n/**\n * Register a custom sending domain\n *\n * Creates the domain in Resend and returns DNS records to add.\n * After adding DNS records, call verifyEmailDomain to confirm ownership.\n *\n * @example\n * ```typescript\n * const domain = await registerEmailDomain(config, 'cubeage.com', {\n * defaultFromEmail: 'support@cubeage.com',\n * defaultFromName: 'Cubeage Support',\n * })\n * console.log('Add these DNS records:', domain.dnsRecords)\n * ```\n */\nexport async function registerEmailDomain(\n\tconfig: SylphxConfig,\n\tdomain: string,\n\topts?: RegisterEmailDomainOptions,\n): Promise<EmailDomain> {\n\treturn callApi(config, \"/email/domains\", {\n\t\tmethod: \"POST\",\n\t\tbody: { domain, ...opts },\n\t});\n}\n\n/**\n * List all custom sending domains for this app\n *\n * @example\n * ```typescript\n * const { domains } = await listEmailDomains(config)\n * for (const d of domains) {\n * console.log(d.domain, d.status)\n * }\n * ```\n */\nexport async function listEmailDomains(\n\tconfig: SylphxConfig,\n): Promise<{ domains: EmailDomain[] }> {\n\treturn callApi(config, \"/email/domains\", { method: \"GET\" });\n}\n\n/**\n * Get a specific domain by ID\n *\n * @example\n * ```typescript\n * const domain = await getEmailDomain(config, 'domain-uuid')\n * console.log(domain.dnsRecords)\n * ```\n */\nexport async function getEmailDomain(\n\tconfig: SylphxConfig,\n\tdomainId: string,\n): Promise<EmailDomain> {\n\treturn callApi(config, `/email/domains/${domainId}`, { method: \"GET\" });\n}\n\n/**\n * Delete a custom sending domain\n *\n * Removes the domain from both Resend and the platform.\n *\n * @example\n * ```typescript\n * await deleteEmailDomain(config, 'domain-uuid')\n * ```\n */\nexport async function deleteEmailDomain(\n\tconfig: SylphxConfig,\n\tdomainId: string,\n): Promise<void> {\n\treturn callApi(config, `/email/domains/${domainId}`, { method: \"DELETE\" });\n}\n\n/**\n * Trigger DNS verification for a domain\n *\n * Resend will check if your DNS records have been added correctly.\n * Status changes to 'verified' on success or 'failed' on error.\n *\n * @example\n * ```typescript\n * const domain = await verifyEmailDomain(config, 'domain-uuid')\n * if (domain.status === 'verified') {\n * console.log('Domain verified!')\n * }\n * ```\n */\nexport async function verifyEmailDomain(\n\tconfig: SylphxConfig,\n\tdomainId: string,\n): Promise<EmailDomain> {\n\treturn callApi(config, `/email/domains/${domainId}/verify`, {\n\t\tmethod: \"POST\",\n\t});\n}\n\n/**\n * Set a domain as the default sender for this app\n *\n * @example\n * ```typescript\n * const domain = await setDefaultEmailDomain(config, 'domain-uuid', {\n * defaultFromEmail: 'support@cubeage.com',\n * defaultFromName: 'Cubeage Support',\n * })\n * ```\n */\nexport async function setDefaultEmailDomain(\n\tconfig: SylphxConfig,\n\tdomainId: string,\n\topts?: SetDefaultEmailDomainOptions,\n): Promise<EmailDomain> {\n\treturn callApi(config, `/email/domains/${domainId}/set-default`, {\n\t\tmethod: \"POST\",\n\t\tbody: opts ?? {},\n\t});\n}\n","/**\n * Consent Functions\n *\n * Pure functions for GDPR/CCPA consent management.\n *\n * ## Architecture (ADR-004)\n *\n * Consent uses **Inline Defaults + Auto-Discovery + Console Override**:\n * - Code provides optional inline defaults when checking consent\n * - Platform auto-discovers/creates consent types when first referenced\n * - Console can override names, descriptions, requirements without deployment\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n *\n * @example\n * ```typescript\n * import { hasConsent, getUserConsents, setConsents } from '@sylphx/sdk'\n *\n * // Check consent with inline defaults (auto-discovered if doesn't exist)\n * if (await hasConsent(config, 'analytics', { userId: 'user-123' }, {\n * name: 'Analytics Cookies',\n * description: 'Help us understand how visitors use our site',\n * category: 'analytics',\n * required: false,\n * })) {\n * track('pageview')\n * }\n *\n * // Get user's current consents\n * const consents = await getUserConsents(config, { userId: 'user-123' })\n *\n * // Set specific consents\n * await setConsents(config, {\n * userId: 'user-123',\n * consents: { analytics: true, marketing: false }\n * })\n * ```\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { components } from \"./generated/api\";\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type ConsentType = components[\"schemas\"][\"ConsentType\"];\nexport type UserConsent = components[\"schemas\"][\"UserConsent\"];\nexport type SetConsentRequest = components[\"schemas\"][\"SetConsentRequest\"];\nexport type SetConsentResponse = components[\"schemas\"][\"SetConsentResponse\"];\n\n// SDK-specific types (not directly from API schema)\n/** Consent category for grouping */\nexport type ConsentCategory =\n\t| \"necessary\"\n\t| \"analytics\"\n\t| \"marketing\"\n\t| \"functional\"\n\t| \"preferences\";\n\nexport interface SetConsentsInput {\n\t/** User ID (optional for anonymous users) */\n\tuserId?: string;\n\t/** Anonymous ID (for guest users) */\n\tanonymousId?: string;\n\t/** Consent settings by type slug */\n\tconsents: Record<string, boolean>;\n}\n\nexport interface LinkAnonymousConsentsInput {\n\t/** The authenticated user ID to link to */\n\tuserId: string;\n\t/** The anonymous ID whose consents should be linked */\n\tanonymousId: string;\n}\n\nexport interface GetConsentHistoryInput {\n\t/** User ID (for authenticated users) */\n\tuserId?: string;\n\t/** Anonymous ID (for anonymous users) */\n\tanonymousId?: string;\n\t/** Maximum records to return (default: 50) */\n\tlimit?: number;\n\t/** Offset for pagination (default: 0) */\n\toffset?: number;\n}\n\n/** A single consent change history entry */\nexport interface ConsentHistoryEntry {\n\t/** Unique entry ID */\n\tid: string;\n\t/** Consent type slug (e.g., 'analytics') */\n\tconsentType: string;\n\t/** Display name of the consent type */\n\tconsentTypeName: string;\n\t/** Previous consent state (null = initial consent) */\n\tpreviousGranted: boolean | null;\n\t/** New consent state */\n\tnewGranted: boolean;\n\t/** Source of the change (banner, settings, api) */\n\tsource: string;\n\t/** Reason for change (user_action, policy_update, etc.) */\n\treason: string | null;\n\t/** ISO timestamp when change occurred */\n\tcreatedAt: string;\n}\n\nexport interface ConsentHistoryResult {\n\t/** List of consent change entries */\n\tentries: ConsentHistoryEntry[];\n\t/** Total number of entries */\n\ttotal: number;\n\t/** Whether there are more entries */\n\thasMore: boolean;\n}\n\nexport interface GetConsentsInput {\n\t/** User ID (optional for anonymous users) */\n\tuserId?: string;\n\t/** Anonymous ID (for guest users) */\n\tanonymousId?: string;\n}\n\n/**\n * Inline defaults for consent purpose auto-discovery\n *\n * @example\n * ```typescript\n * await hasConsent(config, 'analytics', { userId: 'user-123' }, {\n * name: 'Analytics Cookies',\n * description: 'Help us understand how visitors use our site',\n * category: 'analytics',\n * required: false,\n * })\n * ```\n */\nexport interface ConsentPurposeDefaults {\n\t/** Display name */\n\tname?: string;\n\t/** Description */\n\tdescription?: string;\n\t/** Category */\n\tcategory?: ConsentCategory;\n\t/** Whether consent is required (always granted) */\n\trequired?: boolean;\n\t/** Whether enabled by default */\n\tdefaultEnabled?: boolean;\n\t/** Sort order in UI */\n\tsortOrder?: number;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Get all consent types configured for the app\n *\n * Returns GDPR-standard defaults if none configured.\n *\n * @example\n * ```typescript\n * const types = await getConsentTypes(config)\n * types.forEach(t => console.log(`${t.name}: ${t.required ? 'Required' : 'Optional'}`))\n * ```\n */\nexport async function getConsentTypes(\n\tconfig: SylphxConfig,\n): Promise<ConsentType[]> {\n\treturn callApi(config, \"/consent/types\", { method: \"GET\" });\n}\n\n/**\n * Check if user has granted consent for a specific purpose\n *\n * If the consent type doesn't exist, it will be auto-discovered with the provided defaults.\n * Console can override any values without deployment.\n *\n * @param config - SDK configuration\n * @param purposeSlug - Consent purpose slug (e.g., 'analytics', 'marketing')\n * @param input - User identification (userId or anonymousId)\n * @param defaults - Optional inline defaults for auto-discovery\n * @returns Whether consent is granted\n *\n * @example\n * ```typescript\n * // Check analytics consent with inline defaults\n * if (await hasConsent(config, 'analytics', { userId: 'user-123' }, {\n * name: 'Analytics Cookies',\n * description: 'Help us understand how visitors use our site',\n * category: 'analytics',\n * required: false,\n * })) {\n * track('pageview')\n * }\n *\n * // Required consent always returns true\n * const hasNecessary = await hasConsent(config, 'necessary', { userId }, {\n * name: 'Essential Cookies',\n * description: 'Required for the website to function',\n * category: 'necessary',\n * required: true,\n * })\n * ```\n */\nexport async function hasConsent(\n\tconfig: SylphxConfig,\n\tpurposeSlug: string,\n\tinput: GetConsentsInput,\n\tdefaults?: ConsentPurposeDefaults,\n): Promise<boolean> {\n\treturn callApi(config, \"/consent/check\", {\n\t\tmethod: \"POST\",\n\t\tbody: { purposeSlug, ...input, defaults },\n\t});\n}\n\n/**\n * Get user's current consent settings\n *\n * @example\n * ```typescript\n * // For authenticated user\n * const consents = await getUserConsents(config, { userId: 'user-123' })\n *\n * // For anonymous user\n * const consents = await getUserConsents(config, { anonymousId: 'anon-456' })\n * ```\n */\nexport async function getUserConsents(\n\tconfig: SylphxConfig,\n\tinput: GetConsentsInput,\n): Promise<UserConsent[]> {\n\treturn callApi(config, \"/consent/user\", {\n\t\tmethod: \"GET\",\n\t\tquery: input as Record<string, string | undefined>,\n\t});\n}\n\n/**\n * Set user's consent preferences\n *\n * @example\n * ```typescript\n * await setConsents(config, {\n * userId: 'user-123',\n * consents: {\n * analytics: true,\n * marketing: false,\n * },\n * })\n * ```\n */\nexport async function setConsents(\n\tconfig: SylphxConfig,\n\tinput: SetConsentsInput,\n): Promise<void> {\n\treturn callApi(config, \"/consent/set\", { method: \"POST\", body: input });\n}\n\n/**\n * Accept all consent types\n *\n * @example\n * ```typescript\n * await acceptAllConsents(config, { userId: 'user-123' })\n * ```\n */\nexport async function acceptAllConsents(\n\tconfig: SylphxConfig,\n\tinput: GetConsentsInput,\n): Promise<void> {\n\treturn callApi(config, \"/consent/accept-all\", {\n\t\tmethod: \"POST\",\n\t\tbody: input,\n\t});\n}\n\n/**\n * Decline all optional consent types (keeps required ones)\n *\n * @example\n * ```typescript\n * await declineOptionalConsents(config, { anonymousId: 'anon-456' })\n * ```\n */\nexport async function declineOptionalConsents(\n\tconfig: SylphxConfig,\n\tinput: GetConsentsInput,\n): Promise<void> {\n\treturn callApi(config, \"/consent/decline-optional\", {\n\t\tmethod: \"POST\",\n\t\tbody: input,\n\t});\n}\n\n/**\n * Link anonymous user's consents to authenticated user\n *\n * Call this after user signs up/logs in to merge their anonymous consent history.\n *\n * @example\n * ```typescript\n * await linkAnonymousConsents(config, {\n * userId: 'user-123',\n * anonymousId: 'anon-456',\n * })\n * ```\n */\nexport async function linkAnonymousConsents(\n\tconfig: SylphxConfig,\n\tinput: LinkAnonymousConsentsInput,\n): Promise<void> {\n\treturn callApi(config, \"/consent/link-anonymous\", {\n\t\tmethod: \"POST\",\n\t\tbody: input,\n\t});\n}\n\n/**\n * Get consent change history for GDPR audit trail\n *\n * Returns a paginated list of all consent state changes for a user.\n * Required for GDPR compliance - provides complete audit trail of consent decisions.\n *\n * @example\n * ```typescript\n * // Get consent history for authenticated user\n * const history = await getConsentHistory(config, { userId: 'user-123' })\n * console.log(`Total changes: ${history.total}`)\n * history.entries.forEach(entry => {\n * console.log(`${entry.consentType}: ${entry.previousGranted} → ${entry.newGranted}`)\n * })\n *\n * // Paginated retrieval\n * const page2 = await getConsentHistory(config, {\n * userId: 'user-123',\n * limit: 20,\n * offset: 20,\n * })\n * ```\n */\nexport async function getConsentHistory(\n\tconfig: SylphxConfig,\n\tinput: GetConsentHistoryInput,\n): Promise<ConsentHistoryResult> {\n\treturn callApi(config, \"/consent/history\", {\n\t\tmethod: \"GET\",\n\t\tquery: {\n\t\t\tuserId: input.userId,\n\t\t\tanonymousId: input.anonymousId,\n\t\t\tlimit: input.limit?.toString(),\n\t\t\toffset: input.offset?.toString(),\n\t\t},\n\t});\n}\n","/**\n * Referrals Functions\n *\n * Pure functions for referral code management and tracking.\n *\n * ## Architecture (ADR-004)\n *\n * Referrals uses **Inline Defaults + Auto-Discovery + Console Override**:\n * - Code provides optional inline defaults when redeeming referral codes\n * - Platform uses defaults if no Console override exists\n * - Console can override reward values without deployment\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n *\n * @example\n * ```typescript\n * import { redeemReferralCode } from '@sylphx/sdk'\n *\n * // Redeem with inline defaults (overridable in Console)\n * const result = await redeemReferralCode(config, {\n * code: 'ABC123',\n * userId: 'new-user-456',\n * }, {\n * referrerReward: { type: 'premium_trial', days: 7 },\n * refereeReward: { type: 'premium_trial', days: 7 },\n * })\n * ```\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { components } from \"./generated/api\";\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type ReferralCodeResponse =\n\tcomponents[\"schemas\"][\"ReferralCodeResponse\"];\nexport type RegenerateCodeResponse =\n\tcomponents[\"schemas\"][\"RegenerateCodeResponse\"];\nexport type ReferralStatsResponse =\n\tcomponents[\"schemas\"][\"ReferralStatsResponse\"];\nexport type RedeemReferralRequest =\n\tcomponents[\"schemas\"][\"RedeemReferralRequest\"];\nexport type RedeemReferralResponse =\n\tcomponents[\"schemas\"][\"RedeemReferralResponse\"];\nexport type ReferralRewardDefaults =\n\tcomponents[\"schemas\"][\"ReferralRewardDefaults\"];\nexport type ReferralRewardConfig =\n\tcomponents[\"schemas\"][\"ReferralRewardConfig\"];\nexport type LeaderboardResponse = components[\"schemas\"][\"LeaderboardResponse\"];\nexport type LeaderboardEntry = components[\"schemas\"][\"LeaderboardEntry\"];\n\n// SDK-specific types for convenience\nexport interface ReferralCode {\n\tcode: string;\n\tcreatedAt: string;\n}\n\nexport interface ReferralStats {\n\t/** User's referral code */\n\tcode: string;\n\t/** Total referrals made */\n\ttotalReferrals: number;\n\t/** Successful (redeemed) referrals */\n\tsuccessfulReferrals: number;\n\t/** Pending referrals */\n\tpendingReferrals: number;\n\t/** Total rewards earned */\n\ttotalRewards: number;\n}\n\ntype LeaderboardPeriod = \"all\" | \"month\" | \"week\";\n\nexport interface LeaderboardResult {\n\t/** Time period for the leaderboard */\n\tperiod?: LeaderboardPeriod;\n\tentries: LeaderboardEntry[];\n\t/** Current user's position (may not be in top entries) */\n\tcurrentUserRank: number | null;\n\t/** Total participants */\n\ttotalParticipants: number;\n}\n\nexport interface RedeemReferralInput {\n\t/** Referral code to redeem */\n\tcode: string;\n\t/** User ID of the person redeeming (optional for anonymous) */\n\tuserId?: string;\n}\n\nexport interface RedeemResult {\n\tsuccess: boolean;\n\t/** Reward type - platform types or app-specific types */\n\trewardType:\n\t\t| \"points\"\n\t\t| \"premium_trial\"\n\t\t| \"discount\"\n\t\t| \"credit\"\n\t\t| (string & {});\n\treferredReward?: Record<string, unknown>;\n\treferrerReward?: Record<string, unknown>;\n}\n\nexport interface LeaderboardOptions {\n\t/** Number of entries to return (default: 10) */\n\tlimit?: number;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Get current user's referral code\n *\n * Creates one if it doesn't exist.\n *\n * @example\n * ```typescript\n * const { code } = await getMyReferralCode(config, 'user-123')\n * console.log(`Share your code: ${code}`)\n * ```\n */\nexport async function getMyReferralCode(\n\tconfig: SylphxConfig,\n\tuserId: string,\n): Promise<ReferralCode> {\n\treturn callApi(config, \"/referrals/code\", {\n\t\tmethod: \"GET\",\n\t\tquery: { userId },\n\t});\n}\n\n/**\n * Get referral statistics for a user\n *\n * @example\n * ```typescript\n * const stats = await getReferralStats(config, 'user-123')\n * console.log(`${stats.successfulReferrals} successful referrals`)\n * ```\n */\nexport async function getReferralStats(\n\tconfig: SylphxConfig,\n\tuserId: string,\n): Promise<ReferralStats> {\n\treturn callApi(config, \"/referrals/stats\", {\n\t\tmethod: \"GET\",\n\t\tquery: { userId },\n\t});\n}\n\n/**\n * Redeem a referral code\n *\n * If the referral program rewards aren't configured in Console, the provided\n * defaults will be used. Console can override any values without deployment.\n *\n * @param config - SDK configuration\n * @param input - Referral redemption input (code, userId)\n * @param defaults - Optional inline defaults for reward configuration\n *\n * @example\n * ```typescript\n * // Basic redemption (uses Console-configured rewards)\n * const result = await redeemReferralCode(config, {\n * code: 'ABC123',\n * userId: 'new-user-456',\n * })\n *\n * // With inline defaults (auto-discovered if not in Console)\n * const result = await redeemReferralCode(config, {\n * code: 'ABC123',\n * userId: 'new-user-456',\n * }, {\n * referrerReward: { type: 'premium_trial', days: 7 },\n * refereeReward: { type: 'premium_trial', days: 7 },\n * })\n *\n * if (result.success) {\n * console.log(`Reward: ${result.reward?.type}`)\n * }\n * ```\n */\nexport async function redeemReferralCode(\n\tconfig: SylphxConfig,\n\tinput: RedeemReferralInput,\n\tdefaults?: ReferralRewardDefaults,\n): Promise<RedeemResult> {\n\treturn callApi(config, \"/referrals/redeem\", {\n\t\tmethod: \"POST\",\n\t\tbody: { ...input, defaults },\n\t});\n}\n\n/**\n * Get referral leaderboard\n *\n * @example\n * ```typescript\n * const { entries, currentUserRank } = await getReferralLeaderboard(config, 'user-123')\n *\n * entries.forEach(e => {\n * console.log(`#${e.rank} ${e.displayName}: ${e.completedReferrals} referrals`)\n * })\n * ```\n */\nexport async function getReferralLeaderboard(\n\tconfig: SylphxConfig,\n\tuserId: string,\n\toptions?: LeaderboardOptions,\n): Promise<LeaderboardResult> {\n\treturn callApi(config, \"/referrals/leaderboard\", {\n\t\tmethod: \"GET\",\n\t\tquery: { userId, ...options } as Record<\n\t\t\tstring,\n\t\t\tstring | number | undefined\n\t\t>,\n\t});\n}\n\n/**\n * Regenerate user's referral code\n *\n * Use this if the current code has been compromised or user wants a fresh start.\n *\n * @example\n * ```typescript\n * const { code } = await regenerateReferralCode(config, 'user-123')\n * console.log(`New code: ${code}`)\n * ```\n */\nexport async function regenerateReferralCode(\n\tconfig: SylphxConfig,\n\tuserId: string,\n): Promise<ReferralCode> {\n\treturn callApi(config, \"/referrals/code/regenerate\", {\n\t\tmethod: \"POST\",\n\t\tbody: { userId },\n\t});\n}\n","/**\n * Engagement Service Types\n *\n * Core types for streaks, leaderboards, and achievements.\n *\n * ## Architecture (ADR-004)\n *\n * Engagement uses **Inline Defaults + Auto-Discovery + Console Override**:\n * - Code provides optional inline defaults when calling APIs\n * - Platform auto-discovers/creates entities when first referenced\n * - Console can override names, descriptions, values without deployment\n */\n\n// ============================================================================\n// Streaks\n// ============================================================================\n\n/** Streak activity frequency */\nexport type StreakFrequency = \"daily\" | \"weekly\" | \"custom\";\n\n/** Streak definition (auto-discovered or from Console) */\nexport interface StreakDefinition {\n\t/** Unique identifier */\n\tid: string;\n\t/** Display name */\n\tname: string;\n\t/** Description */\n\tdescription?: string;\n\t/** Activity frequency */\n\tfrequency: StreakFrequency;\n\t/** Grace period in hours (default: 0) */\n\tgracePeriodHours?: number;\n\t/** Whether streak resets on miss (default: true) */\n\tresetOnMiss?: boolean;\n\t/** Maximum streak value (optional cap) */\n\tmaxValue?: number;\n\t/** Custom interval in hours (only for 'custom' frequency) */\n\tcustomIntervalHours?: number;\n}\n\n/** User's streak state (from platform) */\nexport interface StreakState {\n\t/** Streak definition ID */\n\tstreakId: string;\n\t/** Current streak count */\n\tcurrent: number;\n\t/** Longest streak ever */\n\tlongest: number;\n\t/** Last activity timestamp */\n\tlastActivityAt: string | null;\n\t/** When current streak will expire */\n\texpiresAt: string | null;\n\t/** Whether streak can be recovered (within grace period) */\n\tcanRecover: boolean;\n\t/** Time remaining until expiry in ms */\n\ttimeRemainingMs: number | null;\n\t/** User's timezone preference for streak expiry (IANA timezone, e.g., 'America/New_York') */\n\tuserTimezone: string | null;\n}\n\n/** Activity recording input */\nexport interface RecordActivityInput {\n\t/** Streak ID */\n\tstreakId: string;\n\t/** User's IANA timezone (e.g., 'America/New_York') for calculating streak expiry at user's local midnight */\n\tuserTimezone?: string;\n\t/** Optional metadata */\n\tmetadata?: Record<string, unknown>;\n\t/**\n\t * Idempotency key for safe retries (Stripe pattern)\n\t *\n\t * Prevents duplicate streak recordings if the same request is retried.\n\t * Use a unique key per logical activity (e.g., `streak-${userId}-${date}`).\n\t */\n\tidempotencyKey?: string;\n}\n\n/** Activity recording result */\nexport interface RecordActivityResult {\n\t/** Updated streak state */\n\tstreak: StreakState;\n\t/** Whether this activity extended the streak */\n\textended: boolean;\n\t/** Whether a new personal best was achieved */\n\tnewPersonalBest: boolean;\n\t/** Previous streak value (for animation) */\n\tpreviousValue: number;\n}\n\n// ============================================================================\n// Leaderboards\n// ============================================================================\n\n/** Leaderboard sort direction */\nexport type LeaderboardSortDirection = \"asc\" | \"desc\";\n\n/** Leaderboard reset period */\nexport type LeaderboardResetPeriod =\n\t| \"hourly\"\n\t| \"daily\"\n\t| \"weekly\"\n\t| \"monthly\"\n\t| \"never\";\n\n/** Score aggregation method */\nexport type LeaderboardAggregation =\n\t| \"max\"\n\t| \"sum\"\n\t| \"latest\"\n\t| \"count\"\n\t| \"min\"\n\t| \"avg\";\n\n/** Leaderboard definition (auto-discovered or from Console) */\nexport interface LeaderboardDefinition {\n\t/** Unique identifier */\n\tid: string;\n\t/** Display name */\n\tname: string;\n\t/** Description */\n\tdescription?: string;\n\t/** Sort direction (desc = higher is better) */\n\tsortDirection: LeaderboardSortDirection;\n\t/** Reset period */\n\tresetPeriod: LeaderboardResetPeriod;\n\t/** How to aggregate multiple scores from same user */\n\taggregation: LeaderboardAggregation;\n\t/** Default privacy for entries */\n\tdefaultPrivacy?: \"public\" | \"friends\" | \"anonymous\";\n\t/** Maximum entries to keep per period */\n\tmaxEntries?: number;\n}\n\n/** Leaderboard entry */\nexport interface LeaderboardEntry {\n\t/** Rank (1-indexed) */\n\trank: number;\n\t/** User ID (may be null for anonymous) */\n\tuserId: string | null;\n\t/** Display name */\n\tdisplayName: string;\n\t/** Avatar URL */\n\tavatarUrl: string | null;\n\t/** Score/value */\n\tvalue: number;\n\t/** Whether this is the current user */\n\tisCurrentUser: boolean;\n\t/** Entry metadata */\n\tmetadata?: Record<string, unknown>;\n\t/** When the score was submitted */\n\tsubmittedAt: string;\n}\n\n/** Leaderboard query options */\nexport interface LeaderboardQueryOptions {\n\t/** Number of entries to return (default: 10) */\n\tlimit?: number;\n\t/** Offset for pagination */\n\toffset?: number;\n\t/** Include surrounding entries for current user */\n\tincludeSurrounding?: boolean;\n\t/** Number of surrounding entries (default: 2) */\n\tsurroundingCount?: number;\n}\n\n/** Leaderboard query result */\nexport interface LeaderboardResult {\n\t/** Leaderboard definition ID */\n\tleaderboardId: string;\n\t/** Period (for periodic leaderboards) */\n\tperiod?: string;\n\t/** Entries (top N or paginated) */\n\tentries: LeaderboardEntry[];\n\t/** Current user's entry (may not be in top entries) */\n\tcurrentUserEntry: LeaderboardEntry | null;\n\t/** Entries surrounding the current user (when includeSurrounding=true and user is outside main entries) */\n\tsurroundingEntries?: LeaderboardEntry[];\n\t/** Total participants */\n\ttotalParticipants: number;\n\t/** Next reset time (for periodic leaderboards) */\n\tnextResetAt: string | null;\n}\n\n/** Score submission input */\nexport interface SubmitScoreInput {\n\t/** Leaderboard ID */\n\tleaderboardId: string;\n\t/** Score value */\n\tvalue: number;\n\t/** Optional metadata */\n\tmetadata?: Record<string, unknown>;\n}\n\n/** Score submission result */\nexport interface SubmitScoreResult {\n\t/** Whether submission was accepted */\n\taccepted: boolean;\n\t/** New rank (if in leaderboard) */\n\trank: number | null;\n\t/** Previous best (if any) */\n\tpreviousBest: number | null;\n\t/** Whether this is a new personal best */\n\tnewPersonalBest: boolean;\n\t/** Rank change (positive = improved) */\n\trankChange: number | null;\n}\n\n// ============================================================================\n// Achievements\n// ============================================================================\n\n/** Achievement type */\nexport type AchievementType = \"standard\" | \"hidden\" | \"incremental\";\n\n/** Achievement tier */\nexport type AchievementTier =\n\t| \"bronze\"\n\t| \"silver\"\n\t| \"gold\"\n\t| \"platinum\"\n\t| \"diamond\";\n\n/** Achievement category */\nexport type AchievementCategory = string; // App-defined\n\n/** Achievement criteria operator */\nexport type CriteriaOperator =\n\t| \"eq\"\n\t| \"ne\"\n\t| \"gt\"\n\t| \"gte\"\n\t| \"lt\"\n\t| \"lte\"\n\t| \"in\"\n\t| \"contains\";\n\n/** Single criterion */\nexport interface AchievementCriterion {\n\t/** Property to check (e.g., 'event', 'count', 'streak.daily') */\n\tproperty: string;\n\t/** Comparison operator */\n\toperator: CriteriaOperator;\n\t/** Value to compare against */\n\tvalue: string | number | boolean | string[] | number[];\n}\n\n/** Achievement criteria (AND logic within, OR between arrays) */\nexport interface AchievementCriteria {\n\t/** Event name to track (for event-based achievements) */\n\tevent?: string;\n\t/** Required count of events */\n\tcount?: number;\n\t/** Additional conditions */\n\tconditions?: AchievementCriterion[];\n}\n\n/** Achievement definition (auto-discovered or from Console) */\nexport interface AchievementDefinition {\n\t/** Unique identifier */\n\tid: string;\n\t/** Display name */\n\tname: string;\n\t/** Description (shown before unlock) */\n\tdescription: string;\n\t/** Description shown after unlock (optional) */\n\tunlockedDescription?: string;\n\t/** Achievement type */\n\ttype: AchievementType;\n\t/** Tier/rarity */\n\ttier: AchievementTier;\n\t/** Category (app-defined) */\n\tcategory: AchievementCategory;\n\t/** Icon (Iconify name or URL) */\n\ticon: string;\n\t/** Points awarded */\n\tpoints?: number;\n\t/** Unlock criteria */\n\tcriteria: AchievementCriteria;\n\t/** Target value for incremental achievements */\n\ttarget?: number;\n\t/** Whether to show in list before unlock */\n\tsecret?: boolean;\n\t/** Order in list */\n\torder?: number;\n}\n\n/** User's achievement state */\nexport interface UserAchievement {\n\t/** Achievement definition ID */\n\tachievementId: string;\n\t/** Whether unlocked */\n\tunlocked: boolean;\n\t/** Unlock timestamp */\n\tunlockedAt: string | null;\n\t/** Progress (for incremental) */\n\tprogress: number;\n\t/** Target (for incremental) */\n\ttarget: number | null;\n\t/** Progress percentage (0-100) */\n\tprogressPercent: number;\n}\n\n/** Achievement unlock event */\nexport interface AchievementUnlockEvent {\n\t/** Achievement definition */\n\tachievement: AchievementDefinition;\n\t/** User achievement state */\n\tuserAchievement: UserAchievement;\n\t/** Whether this is a new unlock (vs already unlocked) */\n\tisNew: boolean;\n}\n\n// ============================================================================\n// Engagement Config (from Platform)\n// ============================================================================\n\n/** Complete engagement configuration (fetched from platform) */\ninterface EngagementConfig {\n\t/** Streak definitions */\n\tstreaks?: StreakDefinition[];\n\t/** Leaderboard definitions */\n\tleaderboards?: LeaderboardDefinition[];\n\t/** Achievement definitions */\n\tachievements?: AchievementDefinition[];\n\t/** Achievement categories (for UI grouping) */\n\tachievementCategories?: {\n\t\tid: string;\n\t\tname: string;\n\t\ticon?: string;\n\t\torder?: number;\n\t}[];\n}\n\n// ============================================================================\n// Tier Metadata (for UI)\n// ============================================================================\n\nexport const ACHIEVEMENT_TIER_CONFIG = {\n\tbronze: { color: \"#CD7F32\", points: 10 },\n\tsilver: { color: \"#C0C0C0\", points: 25 },\n\tgold: { color: \"#FFD700\", points: 50 },\n\tplatinum: { color: \"#00CED1\", points: 100 },\n\tdiamond: { color: \"#B9F2FF\", points: 200 },\n} as const;\n\n// ============================================================================\n// Inline Defaults (Auto-Discovery)\n// ============================================================================\n// These types define the optional inline defaults that can be passed when\n// calling engagement functions. If the entity doesn't exist, the platform\n// will auto-create it with these defaults. Console can override any values.\n\n/**\n * Inline defaults for streak auto-discovery\n *\n * @example\n * ```typescript\n * await recordStreakActivity(config, { streakId: 'daily-login' }, userId, {\n * name: 'Daily Login',\n * frequency: 'daily',\n * gracePeriodHours: 12,\n * })\n * ```\n */\nexport interface StreakDefaults {\n\t/** Display name */\n\tname?: string;\n\t/** Description */\n\tdescription?: string;\n\t/** Activity frequency */\n\tfrequency?: StreakFrequency;\n\t/** Grace period in hours (default: 0) */\n\tgracePeriodHours?: number;\n\t/** Whether streak resets on miss (default: true) */\n\tresetOnMiss?: boolean;\n\t/** Maximum streak value (optional cap) */\n\tmaxValue?: number;\n\t/** Custom interval in hours (only for 'custom' frequency) */\n\tcustomIntervalHours?: number;\n}\n\n/**\n * Inline defaults for leaderboard auto-discovery\n *\n * @example\n * ```typescript\n * await submitScore(config, { leaderboardId: 'high-scores', value: 1500 }, userId, {\n * name: 'High Scores',\n * sortDirection: 'desc',\n * resetPeriod: 'weekly',\n * })\n * ```\n */\nexport interface LeaderboardDefaults {\n\t/** Display name */\n\tname?: string;\n\t/** Description */\n\tdescription?: string;\n\t/** Sort direction (desc = higher is better) */\n\tsortDirection?: LeaderboardSortDirection;\n\t/** Reset period */\n\tresetPeriod?: LeaderboardResetPeriod;\n\t/** How to aggregate multiple scores from same user */\n\taggregation?: LeaderboardAggregation;\n\t/** Maximum entries to keep per period */\n\tmaxEntries?: number;\n}\n\n/**\n * Inline defaults for achievement auto-discovery\n *\n * @example\n * ```typescript\n * await unlockAchievement(config, 'first-purchase', userId, {\n * name: 'First Purchase',\n * description: 'Made your first purchase',\n * points: 100,\n * tier: 'bronze',\n * })\n * ```\n */\nexport interface AchievementDefaults {\n\t/** Display name */\n\tname?: string;\n\t/** Description (shown before unlock) */\n\tdescription?: string;\n\t/** Description shown after unlock */\n\tunlockedDescription?: string;\n\t/** Achievement type */\n\ttype?: AchievementType;\n\t/** Tier/rarity */\n\ttier?: AchievementTier;\n\t/** Category (app-defined) */\n\tcategory?: AchievementCategory;\n\t/** Icon (Iconify name or URL) */\n\ticon?: string;\n\t/** Points awarded */\n\tpoints?: number;\n\t/** Target value for incremental achievements */\n\ttarget?: number;\n\t/** Whether to show in list before unlock */\n\tsecret?: boolean;\n}\n","/**\n * Engagement Functions\n *\n * Pure functions for streaks, leaderboards, and achievements.\n *\n * ## Architecture (ADR-004)\n *\n * Engagement uses **Inline Defaults + Auto-Discovery + Console Override**:\n * - Code provides optional inline defaults when calling functions\n * - Platform auto-discovers/creates entities when first referenced\n * - Console can override names, descriptions, values without deployment\n *\n * @example\n * ```typescript\n * import { unlockAchievement, recordStreakActivity, submitScore } from '@sylphx/sdk'\n *\n * // Unlock achievement with inline defaults (auto-discovered if doesn't exist)\n * await unlockAchievement(config, 'first-win', userId, {\n * name: 'First Win',\n * description: 'Won your first game',\n * points: 100,\n * tier: 'bronze',\n * })\n *\n * // Record streak activity with inline defaults\n * await recordStreakActivity(config, { streakId: 'daily-login' }, userId, {\n * name: 'Daily Login',\n * frequency: 'daily',\n * gracePeriodHours: 12,\n * })\n *\n * // Submit leaderboard score with inline defaults\n * await submitScore(config, { leaderboardId: 'high-scores', value: 1500 }, userId, {\n * name: 'High Scores',\n * sortDirection: 'desc',\n * resetPeriod: 'weekly',\n * })\n * ```\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\n\n// Re-export types from types file\nexport type {\n\t// Streaks\n\tStreakDefinition,\n\tStreakState,\n\tStreakFrequency,\n\tRecordActivityInput,\n\tRecordActivityResult,\n\t// Leaderboards\n\tLeaderboardDefinition,\n\tLeaderboardEntry,\n\tLeaderboardResult,\n\tLeaderboardQueryOptions,\n\tLeaderboardSortDirection,\n\tLeaderboardResetPeriod,\n\tLeaderboardAggregation,\n\tSubmitScoreInput,\n\tSubmitScoreResult,\n\t// Achievements\n\tAchievementDefinition,\n\tAchievementType,\n\tAchievementTier,\n\tAchievementCategory,\n\tAchievementCriteria,\n\tAchievementCriterion,\n\tCriteriaOperator,\n\tUserAchievement,\n\tAchievementUnlockEvent,\n} from \"./lib/engagement/types\";\n\nexport { ACHIEVEMENT_TIER_CONFIG } from \"./lib/engagement/types\";\n\n// ============================================================================\n// Streak Functions\n// ============================================================================\n\nimport type {\n\tRecordActivityInput,\n\tRecordActivityResult,\n\tStreakDefaults,\n\tStreakState,\n} from \"./lib/engagement/types\";\n\n/**\n * Get current streak state for a user\n *\n * @example\n * ```typescript\n * const streak = await getStreak(config, 'daily-challenge', userId)\n * console.log(`Current streak: ${streak.current}`)\n * console.log(`Expires in: ${streak.timeRemainingMs}ms`)\n * ```\n */\nexport async function getStreak(\n\tconfig: SylphxConfig,\n\tstreakId: string,\n\tuserId: string,\n): Promise<StreakState> {\n\treturn callApi(config, \"/engagement/streaks/get\", {\n\t\tmethod: \"GET\",\n\t\tquery: { streakId, userId },\n\t});\n}\n\n/**\n * Get all streak states for a user\n *\n * @example\n * ```typescript\n * const streaks = await getAllStreaks(config, userId)\n * for (const streak of streaks) {\n * console.log(`${streak.streakId}: ${streak.current}`)\n * }\n * ```\n */\nexport async function getAllStreaks(\n\tconfig: SylphxConfig,\n\tuserId: string,\n): Promise<StreakState[]> {\n\treturn callApi(config, \"/engagement/streaks\", {\n\t\tmethod: \"GET\",\n\t\tquery: { userId },\n\t});\n}\n\n/**\n * Record an activity to extend/maintain a streak\n *\n * If the streak doesn't exist, it will be auto-discovered with the provided defaults.\n * Console can override any values without deployment.\n *\n * @param config - SDK configuration\n * @param input - Activity input (streakId required)\n * @param userId - User ID\n * @param defaults - Optional inline defaults for auto-discovery\n *\n * @example\n * ```typescript\n * const result = await recordStreakActivity(config, {\n * streakId: 'daily-challenge',\n * }, userId, {\n * name: 'Daily Challenge',\n * frequency: 'daily',\n * gracePeriodHours: 12,\n * })\n *\n * if (result.extended) {\n * console.log(`Streak extended to ${result.streak.current}!`)\n * }\n * if (result.newPersonalBest) {\n * console.log('New personal best!')\n * }\n * ```\n */\nexport async function recordStreakActivity(\n\tconfig: SylphxConfig,\n\tinput: RecordActivityInput,\n\tuserId: string,\n\tdefaults?: StreakDefaults,\n): Promise<RecordActivityResult> {\n\tconst { idempotencyKey, ...inputBody } = input;\n\treturn callApi(config, \"/engagement/streaks/record\", {\n\t\tmethod: \"POST\",\n\t\tbody: { ...inputBody, userId, defaults },\n\t\tidempotencyKey,\n\t});\n}\n\n/**\n * Recover a streak within grace period (may require payment/reward)\n *\n * @example\n * ```typescript\n * const result = await recoverStreak(config, 'daily-challenge', userId)\n * if (result.success) {\n * console.log(`Streak recovered at ${result.streak.current}`)\n * }\n * ```\n */\nexport async function recoverStreak(\n\tconfig: SylphxConfig,\n\tstreakId: string,\n\tuserId: string,\n): Promise<{ success: boolean; streak: StreakState }> {\n\treturn callApi(config, \"/engagement/streaks/recover\", {\n\t\tmethod: \"POST\",\n\t\tbody: { streakId, userId },\n\t});\n}\n\n// ============================================================================\n// Leaderboard Functions\n// ============================================================================\n\nimport type {\n\tLeaderboardDefaults,\n\tLeaderboardQueryOptions,\n\tLeaderboardResult,\n\tSubmitScoreInput,\n\tSubmitScoreResult,\n} from \"./lib/engagement/types\";\n\n/**\n * Get leaderboard entries\n *\n * @example\n * ```typescript\n * const result = await getLeaderboard(config, 'high-scores', userId, {\n * limit: 10,\n * includeSurrounding: true,\n * })\n *\n * for (const entry of result.entries) {\n * console.log(`#${entry.rank} ${entry.displayName}: ${entry.value}`)\n * }\n *\n * if (result.currentUserEntry) {\n * console.log(`Your rank: #${result.currentUserEntry.rank}`)\n * }\n * ```\n */\nexport async function getLeaderboard(\n\tconfig: SylphxConfig,\n\tleaderboardId: string,\n\tuserId: string | null,\n\toptions?: LeaderboardQueryOptions,\n): Promise<LeaderboardResult> {\n\treturn callApi(config, \"/engagement/leaderboards/get\", {\n\t\tmethod: \"GET\",\n\t\tquery: { leaderboardId, userId: userId ?? undefined, ...options } as Record<\n\t\t\tstring,\n\t\t\tstring | number | boolean | undefined\n\t\t>,\n\t});\n}\n\n/**\n * Submit a score to a leaderboard\n *\n * If the leaderboard doesn't exist, it will be auto-discovered with the provided defaults.\n * Console can override any values without deployment.\n *\n * @param config - SDK configuration\n * @param input - Score submission input (leaderboardId, value required)\n * @param userId - User ID\n * @param defaults - Optional inline defaults for auto-discovery\n *\n * @example\n * ```typescript\n * const result = await submitScore(config, {\n * leaderboardId: 'high-scores',\n * value: 1500,\n * metadata: { level: 'hard' },\n * }, userId, {\n * name: 'High Scores',\n * sortDirection: 'desc',\n * resetPeriod: 'weekly',\n * aggregation: 'max',\n * })\n *\n * if (result.newPersonalBest) {\n * console.log('New personal best!')\n * }\n * if (result.rank !== null) {\n * console.log(`Ranked #${result.rank}`)\n * }\n * ```\n */\nexport async function submitScore(\n\tconfig: SylphxConfig,\n\tinput: SubmitScoreInput,\n\tuserId: string,\n\tdefaults?: LeaderboardDefaults,\n): Promise<SubmitScoreResult> {\n\treturn callApi(config, \"/engagement/leaderboards/submit\", {\n\t\tmethod: \"POST\",\n\t\tbody: { ...input, userId, defaults },\n\t});\n}\n\n/**\n * Get user's rank on a leaderboard (even if not in top entries)\n *\n * @example\n * ```typescript\n * const rank = await getUserRank(config, 'high-scores', userId)\n * if (rank) {\n * console.log(`You are ranked #${rank.rank} with score ${rank.value}`)\n * }\n * ```\n */\nexport async function getUserLeaderboardRank(\n\tconfig: SylphxConfig,\n\tleaderboardId: string,\n\tuserId: string,\n): Promise<{ rank: number; value: number } | null> {\n\treturn callApi(config, \"/engagement/leaderboards/rank\", {\n\t\tmethod: \"GET\",\n\t\tquery: { leaderboardId, userId },\n\t});\n}\n\n// ============================================================================\n// Achievement Functions\n// ============================================================================\n\nimport type {\n\tAchievementDefaults,\n\tAchievementUnlockEvent,\n\tUserAchievement,\n} from \"./lib/engagement/types\";\n\n/**\n * Get all achievements with user progress\n *\n * @example\n * ```typescript\n * const achievements = await getAchievements(config, userId)\n *\n * const unlocked = achievements.filter(a => a.unlocked)\n * console.log(`${unlocked.length} achievements unlocked`)\n *\n * const inProgress = achievements.filter(a => !a.unlocked && a.progress > 0)\n * for (const a of inProgress) {\n * console.log(`${a.achievementId}: ${a.progress}/${a.target}`)\n * }\n * ```\n */\nexport async function getAchievements(\n\tconfig: SylphxConfig,\n\tuserId: string,\n): Promise<UserAchievement[]> {\n\treturn callApi(config, \"/engagement/achievements\", {\n\t\tmethod: \"GET\",\n\t\tquery: { userId },\n\t});\n}\n\n/**\n * Get a single achievement with user progress\n *\n * @example\n * ```typescript\n * const achievement = await getAchievement(config, 'first-win', userId)\n * if (achievement?.unlocked) {\n * console.log(`Unlocked at ${achievement.unlockedAt}`)\n * }\n * ```\n */\nexport async function getAchievement(\n\tconfig: SylphxConfig,\n\tachievementId: string,\n\tuserId: string,\n): Promise<UserAchievement | null> {\n\treturn callApi(config, \"/engagement/achievements/get\", {\n\t\tmethod: \"GET\",\n\t\tquery: { achievementId, userId },\n\t});\n}\n\n/**\n * Manually unlock an achievement\n *\n * If the achievement doesn't exist, it will be auto-discovered with the provided defaults.\n * Console can override any values without deployment.\n *\n * @param config - SDK configuration\n * @param achievementId - Achievement ID\n * @param userId - User ID\n * @param defaults - Optional inline defaults for auto-discovery\n *\n * @example\n * ```typescript\n * const result = await unlockAchievement(config, 'first-purchase', userId, {\n * name: 'First Purchase',\n * description: 'Made your first purchase',\n * points: 100,\n * tier: 'bronze',\n * })\n * if (result.isNew) {\n * showAchievementToast(result.achievement)\n * }\n * ```\n */\nexport async function unlockAchievement(\n\tconfig: SylphxConfig,\n\tachievementId: string,\n\tuserId: string,\n\tdefaults?: AchievementDefaults,\n): Promise<AchievementUnlockEvent> {\n\treturn callApi(config, \"/engagement/achievements/unlock\", {\n\t\tmethod: \"POST\",\n\t\tbody: { achievementId, userId, defaults },\n\t});\n}\n\n/**\n * Increment progress on an incremental achievement\n *\n * If the achievement doesn't exist, it will be auto-discovered with the provided defaults.\n * Console can override any values without deployment.\n *\n * @param config - SDK configuration\n * @param achievementId - Achievement ID\n * @param amount - Amount to increment\n * @param userId - User ID\n * @param defaults - Optional inline defaults for auto-discovery\n *\n * @example\n * ```typescript\n * // User collected an item\n * const result = await incrementAchievementProgress(config, 'collector-100', 1, userId, {\n * name: 'Collector',\n * description: 'Collect 100 items',\n * type: 'incremental',\n * target: 100,\n * tier: 'silver',\n * })\n *\n * if (result.unlocked) {\n * console.log('Achievement unlocked!')\n * } else {\n * console.log(`Progress: ${result.progress}/${result.target}`)\n * }\n * ```\n */\nexport async function incrementAchievementProgress(\n\tconfig: SylphxConfig,\n\tachievementId: string,\n\tamount: number,\n\tuserId: string,\n\tdefaults?: AchievementDefaults,\n): Promise<UserAchievement> {\n\treturn callApi(config, \"/engagement/achievements/progress\", {\n\t\tmethod: \"POST\",\n\t\tbody: { achievementId, amount, userId, defaults },\n\t});\n}\n\n/**\n * Get total achievement points for a user\n *\n * @example\n * ```typescript\n * const points = await getAchievementPoints(config, userId)\n * console.log(`Total points: ${points.total}`)\n * console.log(`This month: ${points.thisMonth}`)\n * ```\n */\nexport async function getAchievementPoints(\n\tconfig: SylphxConfig,\n\tuserId: string,\n): Promise<{ total: number; thisMonth: number; rank: number | null }> {\n\treturn callApi(config, \"/engagement/achievements/points\", {\n\t\tmethod: \"GET\",\n\t\tquery: { userId },\n\t});\n}\n","/**\n * Organization Functions\n *\n * Pure functions for organization management - no hidden state.\n * Each function takes config as the first parameter.\n *\n * Uses REST API at /api/sdk/orgs/* for all operations.\n *\n * Types are derived from the OpenAPI spec (generated/api.d.ts).\n * Run `bun run generate:types:local` to regenerate after API changes.\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { components } from \"./generated/api\";\n\n// ============================================================================\n// Types (re-exported from generated OpenAPI spec)\n// ============================================================================\n\nexport type Organization = components[\"schemas\"][\"Organization\"];\nexport type OrganizationMember = components[\"schemas\"][\"OrganizationMember\"];\nexport type OrganizationInvitation =\n\tcomponents[\"schemas\"][\"OrganizationInvitation\"];\nexport type OrganizationMembership =\n\tcomponents[\"schemas\"][\"OrganizationMembership\"];\nexport type OrgRole = components[\"schemas\"][\"OrgRole\"];\nexport type CreateOrgInput = components[\"schemas\"][\"CreateOrgRequest\"];\nexport type UpdateOrgInput = components[\"schemas\"][\"UpdateOrgRequest\"];\nexport type InviteMemberInput = components[\"schemas\"][\"InviteMemberRequest\"];\n\n// ============================================================================\n// Organization CRUD\n// ============================================================================\n\n/**\n * Get all organizations the current user belongs to\n *\n * @example\n * ```typescript\n * const { organizations } = await getOrganizations(config)\n * ```\n */\nexport async function getOrganizations(\n\tconfig: SylphxConfig,\n): Promise<{ organizations: Organization[] }> {\n\treturn callApi<{ organizations: Organization[] }>(config, \"/orgs\");\n}\n\n/**\n * Get organization by ID or slug\n *\n * @example\n * ```typescript\n * const { organization, membership } = await getOrganization(config, 'my-org')\n * ```\n */\nexport async function getOrganization(\n\tconfig: SylphxConfig,\n\torgIdOrSlug: string,\n): Promise<{\n\torganization: Organization;\n\tmembership: OrganizationMembership | null;\n}> {\n\treturn callApi<{\n\t\torganization: Organization;\n\t\tmembership: OrganizationMembership | null;\n\t}>(config, `/orgs/${orgIdOrSlug}`);\n}\n\n/**\n * Create a new organization\n *\n * @example\n * ```typescript\n * const { organization } = await createOrganization(config, {\n * name: 'My Company',\n * slug: 'my-company',\n * })\n * ```\n */\nexport async function createOrganization(\n\tconfig: SylphxConfig,\n\tinput: CreateOrgInput,\n): Promise<{ organization: Organization }> {\n\treturn callApi<{ organization: Organization }>(config, \"/orgs\", {\n\t\tmethod: \"POST\",\n\t\tbody: input,\n\t});\n}\n\n/**\n * Update an organization\n *\n * @example\n * ```typescript\n * const { organization } = await updateOrganization(config, 'my-org', {\n * name: 'New Name',\n * })\n * ```\n */\nexport async function updateOrganization(\n\tconfig: SylphxConfig,\n\torgIdOrSlug: string,\n\tinput: UpdateOrgInput,\n): Promise<{ organization: Organization }> {\n\treturn callApi<{ organization: Organization }>(\n\t\tconfig,\n\t\t`/orgs/${orgIdOrSlug}`,\n\t\t{\n\t\t\tmethod: \"PUT\",\n\t\t\tbody: input,\n\t\t},\n\t);\n}\n\n/**\n * Delete an organization\n *\n * Requires super_admin role.\n *\n * @example\n * ```typescript\n * await deleteOrganization(config, 'my-org')\n * ```\n */\nexport async function deleteOrganization(\n\tconfig: SylphxConfig,\n\torgIdOrSlug: string,\n): Promise<{ success: boolean }> {\n\treturn callApi<{ success: boolean }>(config, `/orgs/${orgIdOrSlug}`, {\n\t\tmethod: \"DELETE\",\n\t});\n}\n\n// ============================================================================\n// Members\n// ============================================================================\n\n/**\n * Get organization members\n *\n * @example\n * ```typescript\n * const { members } = await getOrganizationMembers(config, 'my-org')\n * ```\n */\nexport async function getOrganizationMembers(\n\tconfig: SylphxConfig,\n\torgIdOrSlug: string,\n): Promise<{ members: OrganizationMember[] }> {\n\treturn callApi<{ members: OrganizationMember[] }>(\n\t\tconfig,\n\t\t`/orgs/${orgIdOrSlug}/members`,\n\t);\n}\n\n/**\n * Invite a member to an organization\n *\n * Requires admin or super_admin role.\n *\n * @example\n * ```typescript\n * const { invitation } = await inviteOrganizationMember(config, 'my-org', {\n * email: 'user@example.com',\n * role: 'developer',\n * })\n * ```\n */\nexport async function inviteOrganizationMember(\n\tconfig: SylphxConfig,\n\torgIdOrSlug: string,\n\tinput: InviteMemberInput,\n): Promise<{ invitation: OrganizationInvitation }> {\n\treturn callApi<{ invitation: OrganizationInvitation }>(\n\t\tconfig,\n\t\t`/orgs/${orgIdOrSlug}/members/invite`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: input,\n\t\t},\n\t);\n}\n\n/**\n * Update a member's role\n *\n * Requires admin or super_admin role.\n *\n * @example\n * ```typescript\n * const { member } = await updateOrganizationMemberRole(config, 'my-org', userId, 'admin')\n * ```\n */\nexport async function updateOrganizationMemberRole(\n\tconfig: SylphxConfig,\n\torgIdOrSlug: string,\n\tmemberId: string,\n\trole: OrgRole,\n): Promise<{ member: OrganizationMember }> {\n\treturn callApi<{ member: OrganizationMember }>(\n\t\tconfig,\n\t\t`/orgs/${orgIdOrSlug}/members/${memberId}/role`,\n\t\t{\n\t\t\tmethod: \"PUT\",\n\t\t\tbody: { role },\n\t\t},\n\t);\n}\n\n/**\n * Remove a member from an organization\n *\n * Requires admin or super_admin role.\n *\n * @example\n * ```typescript\n * await removeOrganizationMember(config, 'my-org', userId)\n * ```\n */\nexport async function removeOrganizationMember(\n\tconfig: SylphxConfig,\n\torgIdOrSlug: string,\n\tmemberId: string,\n): Promise<{ success: boolean }> {\n\treturn callApi<{ success: boolean }>(\n\t\tconfig,\n\t\t`/orgs/${orgIdOrSlug}/members/${memberId}`,\n\t\t{\n\t\t\tmethod: \"DELETE\",\n\t\t},\n\t);\n}\n\n/**\n * Leave an organization\n *\n * @example\n * ```typescript\n * await leaveOrganization(config, 'my-org')\n * ```\n */\nexport async function leaveOrganization(\n\tconfig: SylphxConfig,\n\torgIdOrSlug: string,\n): Promise<{ success: boolean }> {\n\treturn callApi<{ success: boolean }>(config, `/orgs/${orgIdOrSlug}/leave`, {\n\t\tmethod: \"POST\",\n\t});\n}\n\n// ============================================================================\n// Invitations\n// ============================================================================\n\n/**\n * Get pending invitations for an organization\n *\n * Requires admin or super_admin role.\n *\n * @example\n * ```typescript\n * const { invitations } = await getOrganizationInvitations(config, 'my-org')\n * ```\n */\nexport async function getOrganizationInvitations(\n\tconfig: SylphxConfig,\n\torgIdOrSlug: string,\n): Promise<{ invitations: OrganizationInvitation[] }> {\n\treturn callApi<{ invitations: OrganizationInvitation[] }>(\n\t\tconfig,\n\t\t`/orgs/${orgIdOrSlug}/invitations`,\n\t);\n}\n\n/**\n * Accept an organization invitation\n *\n * @example\n * ```typescript\n * const { organization } = await acceptOrganizationInvitation(config, invitationToken)\n * ```\n */\nexport async function acceptOrganizationInvitation(\n\tconfig: SylphxConfig,\n\ttoken: string,\n): Promise<{ organization: Organization }> {\n\treturn callApi<{ organization: Organization }>(\n\t\tconfig,\n\t\t\"/orgs/invitations/accept\",\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: { token },\n\t\t},\n\t);\n}\n\n/**\n * Revoke a pending invitation\n *\n * Requires admin or super_admin role.\n *\n * @example\n * ```typescript\n * await revokeOrganizationInvitation(config, 'my-org', invitationId)\n * ```\n */\nexport async function revokeOrganizationInvitation(\n\tconfig: SylphxConfig,\n\torgIdOrSlug: string,\n\tinvitationId: string,\n): Promise<{ success: boolean }> {\n\treturn callApi<{ success: boolean }>(\n\t\tconfig,\n\t\t`/orgs/${orgIdOrSlug}/invitations/${invitationId}`,\n\t\t{\n\t\t\tmethod: \"DELETE\",\n\t\t},\n\t);\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Check if user has a specific role or higher in the organization\n */\nexport function hasRole(\n\tmembership: OrganizationMembership | null,\n\tminimumRole: OrgRole,\n): boolean {\n\tif (!membership) return false;\n\n\tconst roleHierarchy: OrgRole[] = [\n\t\t\"viewer\",\n\t\t\"analytics\",\n\t\t\"developer\",\n\t\t\"billing\",\n\t\t\"admin\",\n\t\t\"super_admin\",\n\t];\n\n\tconst userRoleIndex = roleHierarchy.indexOf(membership.role);\n\tconst requiredRoleIndex = roleHierarchy.indexOf(minimumRole);\n\n\treturn userRoleIndex >= requiredRoleIndex;\n}\n\n/**\n * Check if user can manage members (invite, remove, change roles)\n */\nexport function canManageMembers(\n\tmembership: OrganizationMembership | null,\n): boolean {\n\treturn hasRole(membership, \"admin\");\n}\n\n/**\n * Check if user can manage organization settings\n */\nexport function canManageSettings(\n\tmembership: OrganizationMembership | null,\n): boolean {\n\treturn hasRole(membership, \"admin\");\n}\n\n/**\n * Check if user can delete the organization\n */\nexport function canDeleteOrganization(\n\tmembership: OrganizationMembership | null,\n): boolean {\n\treturn hasRole(membership, \"super_admin\");\n}\n","/**\n * Secrets SDK\n *\n * Secure secrets management for applications.\n * Secrets are encrypted at rest with AES-256-GCM.\n *\n * @example\n * ```typescript\n * import { createConfig, getSecret, getSecrets, listSecretKeys } from '@sylphx/sdk'\n *\n * const config = createConfig({\n * secretKey: process.env.SYLPHX_SECRET_KEY!,\n * })\n *\n * // Get a single secret\n * const dbUrl = await getSecret(config, { key: 'DATABASE_URL' })\n * console.log(dbUrl.value) // postgres://...\n *\n * // Get multiple secrets at once\n * const secrets = await getSecrets(config, {\n * keys: ['DATABASE_URL', 'API_KEY', 'JWT_SECRET']\n * })\n * console.log(secrets.DATABASE_URL) // postgres://...\n *\n * // List all secret keys (without values)\n * const keys = await listSecretKeys(config)\n * keys.forEach(k => console.log(k.key, k.description))\n * ```\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface GetSecretInput {\n\t/** Secret key (uppercase, underscores allowed) */\n\tkey: string;\n\t/** Optional environment ID override */\n\tenvironmentId?: string;\n}\n\nexport interface GetSecretResult {\n\t/** Secret key */\n\tkey: string;\n\t/** Decrypted secret value */\n\tvalue: string;\n\t/** Version number */\n\tversion: string;\n}\n\nexport interface GetSecretsInput {\n\t/** Array of secret keys to retrieve */\n\tkeys: string[];\n\t/** Optional environment ID override */\n\tenvironmentId?: string;\n}\n\n/** Map of key -> decrypted value */\nexport type GetSecretsResult = Record<string, string>;\n\nexport interface ListSecretKeysInput {\n\t/** Optional environment ID filter */\n\tenvironmentId?: string;\n}\n\nexport interface SecretKeyInfo {\n\t/** Secret key name */\n\tkey: string;\n\t/** Human-readable description */\n\tdescription: string | null;\n\t/** Current version */\n\tversion: string;\n\t/** Whether this is environment-specific */\n\tisEnvironmentSpecific: boolean;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Get a single secret value by key.\n *\n * @param config - SDK configuration\n * @param input - Secret key and optional environment ID\n * @returns Decrypted secret value\n * @throws Error if secret not found or access denied\n *\n * @example\n * ```typescript\n * const secret = await getSecret(config, { key: 'DATABASE_URL' })\n * const dbConnection = createPool(secret.value)\n * ```\n */\nexport async function getSecret(\n\tconfig: SylphxConfig,\n\tinput: GetSecretInput,\n): Promise<GetSecretResult> {\n\treturn callApi<GetSecretResult>(config, \"/secrets/get\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\tkey: input.key,\n\t\t\tenvironmentId: input.environmentId,\n\t\t},\n\t});\n}\n\n/**\n * Get multiple secrets at once.\n *\n * More efficient than multiple getSecret calls.\n *\n * @param config - SDK configuration\n * @param input - Array of secret keys\n * @returns Map of key -> decrypted value\n *\n * @example\n * ```typescript\n * const secrets = await getSecrets(config, {\n * keys: ['DATABASE_URL', 'REDIS_URL', 'JWT_SECRET']\n * })\n *\n * const db = createPool(secrets.DATABASE_URL)\n * const redis = createClient(secrets.REDIS_URL)\n * ```\n */\nexport async function getSecrets(\n\tconfig: SylphxConfig,\n\tinput: GetSecretsInput,\n): Promise<GetSecretsResult> {\n\treturn callApi<GetSecretsResult>(config, \"/secrets/getMany\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\tkeys: input.keys,\n\t\t\tenvironmentId: input.environmentId,\n\t\t},\n\t});\n}\n\n/**\n * List all secret keys (without values).\n *\n * Useful for showing available secrets in UI or debugging.\n *\n * @param config - SDK configuration\n * @param input - Optional environment filter\n * @returns Array of secret key info\n *\n * @example\n * ```typescript\n * const keys = await listSecretKeys(config)\n * console.log('Available secrets:')\n * keys.forEach(k => console.log(` ${k.key}: ${k.description}`))\n * ```\n */\nexport async function listSecretKeys(\n\tconfig: SylphxConfig,\n\tinput: ListSecretKeysInput = {},\n): Promise<SecretKeyInfo[]> {\n\treturn callApi<SecretKeyInfo[]>(config, \"/secrets/listKeys\", {\n\t\tmethod: \"GET\",\n\t\tquery: input.environmentId\n\t\t\t? { environmentId: input.environmentId }\n\t\t\t: undefined,\n\t});\n}\n\n/**\n * Check if a secret exists without retrieving its value.\n *\n * @param config - SDK configuration\n * @param key - Secret key to check\n * @returns true if the secret exists\n *\n * @example\n * ```typescript\n * if (await hasSecret(config, 'STRIPE_SECRET_KEY')) {\n * // Stripe is configured, enable payment features\n * }\n * ```\n */\nexport async function hasSecret(\n\tconfig: SylphxConfig,\n\tkey: string,\n): Promise<boolean> {\n\ttry {\n\t\tconst keys = await listSecretKeys(config);\n\t\treturn keys.some((k) => k.key === key);\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Get all secrets for an environment as an object.\n *\n * Useful for loading all secrets into process.env at startup.\n *\n * @param config - SDK configuration\n * @param environmentId - Optional environment ID\n * @returns Object with all secrets\n *\n * @example\n * ```typescript\n * // Load all secrets into process.env at app startup\n * const secrets = await getAllSecrets(config)\n * Object.assign(process.env, secrets)\n * ```\n */\nexport async function getAllSecrets(\n\tconfig: SylphxConfig,\n\tenvironmentId?: string,\n): Promise<GetSecretsResult> {\n\tconst keys = await listSecretKeys(config, { environmentId });\n\tif (keys.length === 0) {\n\t\treturn {};\n\t}\n\treturn getSecrets(config, { keys: keys.map((k) => k.key), environmentId });\n}\n","/**\n * Search SDK\n *\n * State-of-the-art search with full-text, semantic, and hybrid modes.\n * Powered by PostgreSQL tsvector + pgvector.\n *\n * @example\n * ```typescript\n * import { createConfig, indexDocument, search, batchIndex } from '@sylphx/sdk'\n *\n * const config = createConfig({\n * secretKey: process.env.SYLPHX_SECRET_KEY!,\n * })\n *\n * // Index a document\n * await indexDocument(config, {\n * content: 'How to reset your password...',\n * title: 'Password Reset Guide',\n * namespace: 'help-articles',\n * category: 'account',\n * tags: ['password', 'security'],\n * })\n *\n * // Search (hybrid mode by default)\n * const results = await search(config, {\n * query: 'forgot my login credentials',\n * namespace: 'help-articles',\n * searchType: 'hybrid',\n * highlight: true,\n * })\n *\n * results.results.forEach(r => console.log(r.title, r.score, r.highlight))\n * ```\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface IndexDocumentInput {\n\t/** Document title (weighted higher in search) */\n\ttitle?: string;\n\t/** Document content to index */\n\tcontent: string;\n\t/** Namespace for data isolation (e.g., 'products', 'articles') */\n\tnamespace?: string;\n\t/** External document ID (your system's ID) */\n\texternalId?: string;\n\t/** URL or path */\n\turl?: string;\n\t/** Searchable metadata */\n\tmetadata?: Record<string, unknown>;\n\t/** Category facet for filtering */\n\tcategory?: string;\n\t/** Type facet for filtering */\n\ttype?: string;\n\t/** Tags facet for filtering */\n\ttags?: string[];\n\t/** Language for full-text search (default: english) */\n\tlanguage?: string;\n\t/** Skip embedding generation (for keyword-only search) */\n\tskipEmbedding?: boolean;\n\t/** Embedding model to use */\n\tembeddingModel?: string;\n}\n\nexport interface IndexDocumentResult {\n\t/** Generated document ID */\n\tid: string;\n\t/** External ID if provided */\n\texternalId: string | null;\n\t/** Namespace */\n\tnamespace: string;\n}\n\nexport interface BatchIndexInput {\n\t/** Documents to index (max 100) */\n\tdocuments: Array<{\n\t\ttitle?: string;\n\t\tcontent: string;\n\t\texternalId?: string;\n\t\turl?: string;\n\t\tmetadata?: Record<string, unknown>;\n\t\tcategory?: string;\n\t\ttype?: string;\n\t\ttags?: string[];\n\t}>;\n\t/** Namespace for all documents */\n\tnamespace?: string;\n\t/** Language for full-text search */\n\tlanguage?: string;\n\t/** Skip embedding generation */\n\tskipEmbedding?: boolean;\n\t/** Embedding model to use */\n\tembeddingModel?: string;\n}\n\nexport interface BatchIndexResult {\n\t/** Number of documents indexed */\n\tindexed: number;\n\t/** Generated document IDs */\n\tids: string[];\n}\n\nexport type SearchType = \"keyword\" | \"semantic\" | \"hybrid\";\n\nexport interface SearchInput {\n\t/** Search query text */\n\tquery: string;\n\t/** Namespace to search within */\n\tnamespace?: string;\n\t/** Search type: keyword, semantic, or hybrid (default) */\n\tsearchType?: SearchType;\n\t/** Maximum results to return (default: 10, max: 100) */\n\tlimit?: number;\n\t/** Offset for pagination */\n\toffset?: number;\n\t/** Minimum similarity threshold (0-1) for semantic search */\n\tminSimilarity?: number;\n\t/** Enable typo tolerance (default: true) */\n\ttypoTolerance?: boolean;\n\t/** Language for full-text search */\n\tlanguage?: string;\n\t/** Facet filters */\n\tfilters?: {\n\t\tcategory?: string;\n\t\ttype?: string;\n\t\ttags?: string[];\n\t\tmetadata?: Record<string, unknown>;\n\t};\n\t/** Include highlighted snippets (default: true) */\n\thighlight?: boolean;\n\t/** Embedding model for semantic search */\n\tembeddingModel?: string;\n\t/** Track this query for analytics (default: true) */\n\ttrackQuery?: boolean;\n\t/** Session ID for analytics */\n\tsessionId?: string;\n\t/** User ID for analytics */\n\tuserId?: string;\n}\n\nexport interface SearchResultItem {\n\t/** Document ID */\n\tid: string;\n\t/** External ID if set */\n\texternalId: string | null;\n\t/** Document title */\n\ttitle: string | null;\n\t/** Document content */\n\tcontent: string;\n\t/** Document URL */\n\turl: string | null;\n\t/** Document metadata */\n\tmetadata: Record<string, unknown> | null;\n\t/** Category facet */\n\tcategory: string | null;\n\t/** Type facet */\n\ttype: string | null;\n\t/** Tags facet */\n\ttags: string[] | null;\n\t/** Combined score */\n\tscore: number;\n\t/** Keyword search score (if hybrid) */\n\tkeywordScore?: number;\n\t/** Semantic search score (if hybrid) */\n\tsemanticScore?: number;\n\t/** Highlighted snippet (if enabled) */\n\thighlight?: string;\n}\n\nexport interface SearchResponse {\n\t/** Search results */\n\tresults: SearchResultItem[];\n\t/** Total results found */\n\ttotal: number;\n\t/** Original query */\n\tquery: string;\n\t/** Search type used */\n\tsearchType: SearchType;\n\t/** Query processing time in ms */\n\tlatencyMs: number;\n}\n\nexport interface GetFacetsInput {\n\t/** Namespace to get facets from */\n\tnamespace?: string;\n\t/** Facets to retrieve */\n\tfacets?: Array<\"category\" | \"type\" | \"tags\">;\n\t/** Filter facets by category or type */\n\tfilters?: {\n\t\tcategory?: string;\n\t\ttype?: string;\n\t};\n}\n\nexport interface FacetsResponse {\n\tfacets: {\n\t\tcategory?: Array<{ value: string; count: number }>;\n\t\ttype?: Array<{ value: string; count: number }>;\n\t\ttags?: Array<{ value: string; count: number }>;\n\t};\n}\n\nexport interface DeleteDocumentInput {\n\t/** Document ID to delete */\n\tid?: string;\n\t/** Or delete by external ID */\n\texternalId?: string;\n\t/** Namespace (required if using externalId) */\n\tnamespace?: string;\n}\n\nexport interface UpsertDocumentInput extends IndexDocumentInput {\n\t/** External ID is required for upsert */\n\texternalId: string;\n}\n\nexport interface UpsertDocumentResult extends IndexDocumentResult {\n\t/** Whether the document was created (true) or updated (false) */\n\tcreated: boolean;\n}\n\nexport interface SearchStatsResult {\n\t/** Total documents indexed */\n\ttotalDocuments: number;\n\t/** Documents with embeddings */\n\tdocumentsWithEmbedding: number;\n\t/** Documents by namespace */\n\tbyNamespace: Array<{ namespace: string; count: number }>;\n}\n\nexport interface TrackClickInput {\n\t/** Search query ID */\n\tqueryId: string;\n\t/** Clicked document ID */\n\tdocumentId: string;\n\t/** Position in results (1-indexed) */\n\tposition: number;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Index a document for search.\n *\n * Automatically generates tsvector for full-text search and\n * optional embedding for semantic search.\n *\n * @param config - SDK configuration\n * @param input - Document to index\n * @returns Indexed document info\n *\n * @example\n * ```typescript\n * const result = await indexDocument(config, {\n * title: 'Getting Started Guide',\n * content: 'Welcome to our platform...',\n * namespace: 'docs',\n * category: 'tutorials',\n * tags: ['beginner', 'setup'],\n * })\n * ```\n */\nexport async function indexDocument(\n\tconfig: SylphxConfig,\n\tinput: IndexDocumentInput,\n): Promise<IndexDocumentResult> {\n\treturn callApi<IndexDocumentResult>(config, \"/search/index\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\ttitle: input.title,\n\t\t\tcontent: input.content,\n\t\t\tnamespace: input.namespace ?? \"default\",\n\t\t\texternalId: input.externalId,\n\t\t\turl: input.url,\n\t\t\tmetadata: input.metadata,\n\t\t\tcategory: input.category,\n\t\t\ttype: input.type,\n\t\t\ttags: input.tags,\n\t\t\tlanguage: input.language ?? \"english\",\n\t\t\tskipEmbedding: input.skipEmbedding ?? false,\n\t\t\tembeddingModel: input.embeddingModel ?? \"openai/text-embedding-3-small\",\n\t\t},\n\t});\n}\n\n/**\n * Index multiple documents in a single batch.\n *\n * More efficient than multiple indexDocument calls.\n * Max 100 documents per batch.\n *\n * @param config - SDK configuration\n * @param input - Documents to index\n * @returns Batch index result\n *\n * @example\n * ```typescript\n * const result = await batchIndex(config, {\n * namespace: 'products',\n * documents: products.map(p => ({\n * title: p.name,\n * content: p.description,\n * externalId: p.id,\n * category: p.category,\n * }))\n * })\n * console.log(`Indexed ${result.indexed} products`)\n * ```\n */\nexport async function batchIndex(\n\tconfig: SylphxConfig,\n\tinput: BatchIndexInput,\n): Promise<BatchIndexResult> {\n\treturn callApi<BatchIndexResult>(config, \"/search/batchIndex\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\tdocuments: input.documents,\n\t\t\tnamespace: input.namespace ?? \"default\",\n\t\t\tlanguage: input.language ?? \"english\",\n\t\t\tskipEmbedding: input.skipEmbedding ?? false,\n\t\t\tembeddingModel: input.embeddingModel ?? \"openai/text-embedding-3-small\",\n\t\t},\n\t});\n}\n\n/**\n * Search documents.\n *\n * Supports three search modes:\n * - `keyword`: Full-text search with typo tolerance\n * - `semantic`: AI-powered vector search\n * - `hybrid`: Combined ranking (default, best results)\n *\n * @param config - SDK configuration\n * @param input - Search query and options\n * @returns Search results with scores\n *\n * @example\n * ```typescript\n * // Hybrid search (recommended)\n * const results = await search(config, {\n * query: 'how to change email address',\n * namespace: 'help',\n * searchType: 'hybrid',\n * highlight: true,\n * })\n *\n * results.results.forEach(r => {\n * console.log(`[${r.score.toFixed(3)}] ${r.title}`)\n * console.log(r.highlight)\n * })\n * ```\n */\nexport async function search(\n\tconfig: SylphxConfig,\n\tinput: SearchInput,\n): Promise<SearchResponse> {\n\treturn callApi<SearchResponse>(config, \"/search/search\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\tquery: input.query,\n\t\t\tnamespace: input.namespace ?? \"default\",\n\t\t\tsearchType: input.searchType ?? \"hybrid\",\n\t\t\tlimit: input.limit ?? 10,\n\t\t\toffset: input.offset ?? 0,\n\t\t\tminSimilarity: input.minSimilarity,\n\t\t\ttypoTolerance: input.typoTolerance ?? true,\n\t\t\tlanguage: input.language ?? \"english\",\n\t\t\tfilters: input.filters,\n\t\t\thighlight: input.highlight ?? true,\n\t\t\tembeddingModel: input.embeddingModel ?? \"openai/text-embedding-3-small\",\n\t\t\ttrackQuery: input.trackQuery ?? true,\n\t\t\tsessionId: input.sessionId,\n\t\t\tuserId: input.userId,\n\t\t},\n\t});\n}\n\n/**\n * Get facet counts for filtering.\n *\n * Returns counts of documents by category, type, and tags.\n *\n * @param config - SDK configuration\n * @param input - Facet options\n * @returns Facet counts\n *\n * @example\n * ```typescript\n * const facets = await getFacets(config, {\n * namespace: 'products',\n * facets: ['category', 'type'],\n * })\n *\n * facets.facets.category?.forEach(f => {\n * console.log(`${f.value}: ${f.count} products`)\n * })\n * ```\n */\nexport async function getFacets(\n\tconfig: SylphxConfig,\n\tinput: GetFacetsInput = {},\n): Promise<FacetsResponse> {\n\treturn callApi<FacetsResponse>(config, \"/search/getFacets\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\tnamespace: input.namespace ?? \"default\",\n\t\t\tfacets: input.facets ?? [\"category\", \"type\"],\n\t\t\tfilters: input.filters,\n\t\t},\n\t});\n}\n\n/**\n * Delete a document from the search index.\n *\n * @param config - SDK configuration\n * @param input - Document ID or external ID\n * @returns Deletion result\n *\n * @example\n * ```typescript\n * // Delete by internal ID\n * await deleteDocument(config, { id: 'doc-uuid-123' })\n *\n * // Delete by external ID\n * await deleteDocument(config, {\n * externalId: 'product-456',\n * namespace: 'products'\n * })\n * ```\n */\nexport async function deleteDocument(\n\tconfig: SylphxConfig,\n\tinput: DeleteDocumentInput,\n): Promise<{ deleted: number }> {\n\treturn callApi<{ deleted: number }>(config, \"/search/delete\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\tid: input.id,\n\t\t\texternalId: input.externalId,\n\t\t\tnamespace: input.namespace ?? \"default\",\n\t\t},\n\t});\n}\n\n/**\n * Upsert a document (insert or update by externalId).\n *\n * If a document with the same externalId exists, it will be replaced.\n * Otherwise, a new document is created.\n *\n * @param config - SDK configuration\n * @param input - Document to upsert (externalId required)\n * @returns Upsert result\n *\n * @example\n * ```typescript\n * const result = await upsertDocument(config, {\n * externalId: 'product-123',\n * title: 'Updated Product Name',\n * content: 'New description...',\n * namespace: 'products',\n * })\n * console.log(result.created ? 'Created' : 'Updated')\n * ```\n */\nexport async function upsertDocument(\n\tconfig: SylphxConfig,\n\tinput: UpsertDocumentInput,\n): Promise<UpsertDocumentResult> {\n\treturn callApi<UpsertDocumentResult>(config, \"/search/upsert\", {\n\t\tmethod: \"POST\",\n\t\tbody: {\n\t\t\ttitle: input.title,\n\t\t\tcontent: input.content,\n\t\t\tnamespace: input.namespace ?? \"default\",\n\t\t\texternalId: input.externalId,\n\t\t\turl: input.url,\n\t\t\tmetadata: input.metadata,\n\t\t\tcategory: input.category,\n\t\t\ttype: input.type,\n\t\t\ttags: input.tags,\n\t\t\tlanguage: input.language ?? \"english\",\n\t\t\tskipEmbedding: input.skipEmbedding ?? false,\n\t\t\tembeddingModel: input.embeddingModel ?? \"openai/text-embedding-3-small\",\n\t\t},\n\t});\n}\n\n/**\n * Get search index statistics.\n *\n * @param config - SDK configuration\n * @param namespace - Optional namespace filter\n * @returns Index statistics\n *\n * @example\n * ```typescript\n * const stats = await getSearchStats(config)\n * console.log(`Total docs: ${stats.totalDocuments}`)\n * console.log(`With embeddings: ${stats.documentsWithEmbedding}`)\n * ```\n */\nexport async function getSearchStats(\n\tconfig: SylphxConfig,\n\tnamespace?: string,\n): Promise<SearchStatsResult> {\n\treturn callApi<SearchStatsResult>(config, \"/search/getStats\", {\n\t\tmethod: \"POST\",\n\t\tbody: { namespace },\n\t});\n}\n\n/**\n * Track a click on a search result.\n *\n * Use this to improve search quality over time.\n *\n * @param config - SDK configuration\n * @param input - Click information\n * @returns Success status\n *\n * @example\n * ```typescript\n * await trackClick(config, {\n * queryId: searchResponse.queryId,\n * documentId: clickedResult.id,\n * position: 3,\n * })\n * ```\n */\nexport async function trackClick(\n\tconfig: SylphxConfig,\n\tinput: TrackClickInput,\n): Promise<{ success: boolean }> {\n\treturn callApi<{ success: boolean }>(config, \"/search/trackClick\", {\n\t\tmethod: \"POST\",\n\t\tbody: input,\n\t});\n}\n","/**\n * Database Functions\n *\n * Pure functions for retrieving Platform-provisioned database connection strings.\n * Server-side only (requires secret key `sk_*`).\n *\n * The Platform provisions a PostgreSQL database for each app and encrypts\n * the connection string. These functions retrieve and decrypt the connection\n * string at startup, so your app never needs to store it.\n *\n * @example\n * ```ts\n * import { createConfig, getDatabaseConnectionString } from '@sylphx/sdk'\n *\n * const config = createConfig({ secretKey: process.env.SYLPHX_SECRET_KEY! })\n * const { connectionString } = await getDatabaseConnectionString(config)\n *\n * const pool = new Pool({ connectionString })\n * ```\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type DatabaseStatus =\n\t| \"provisioning\"\n\t| \"ready\"\n\t| \"suspended\"\n\t| \"failed\"\n\t| \"deleted\"\n\t| \"not_provisioned\";\n\nexport interface DatabaseConnectionInfo {\n\t/** Decrypted PostgreSQL connection string */\n\tconnectionString: string;\n\t/** Database name */\n\tdatabaseName: string;\n\t/** Database role/user name */\n\troleName: string | null;\n\t/** Database provisioning status */\n\tstatus: Exclude<DatabaseStatus, \"not_provisioned\">;\n}\n\nexport interface DatabaseStatusInfo {\n\t/** Current database status */\n\tstatus: DatabaseStatus;\n\t/** Provisioned region */\n\tregion: string | null;\n\t/** PostgreSQL major version */\n\tpgVersion: number | null;\n\t/** Database name */\n\tdatabaseName: string | null;\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Get the provisioned PostgreSQL connection string for this app.\n *\n * Requires secret key authentication (server-side only).\n * The connection string is decrypted on the Platform and returned in plaintext.\n *\n * @throws `NOT_FOUND` if no database has been provisioned for this app\n * @throws `UNPROCESSABLE_ENTITY` if database is not yet ready\n *\n * @example\n * ```ts\n * const { connectionString } = await getDatabaseConnectionString(config)\n * const pool = new Pool({ connectionString })\n * ```\n */\nexport async function getDatabaseConnectionString(\n\tconfig: SylphxConfig,\n): Promise<DatabaseConnectionInfo> {\n\treturn callApi<DatabaseConnectionInfo>(\n\t\tconfig,\n\t\t\"/sdk/database/connection-string\",\n\t\t{ method: \"GET\" },\n\t);\n}\n\n/**\n * Get the current status of the provisioned database.\n *\n * Use this to check if the database is ready before attempting to connect.\n * Requires secret key authentication (server-side only).\n *\n * @example\n * ```ts\n * const { status } = await getDatabaseStatus(config)\n * if (status === 'ready') {\n * // Connect to database\n * }\n * ```\n */\nexport async function getDatabaseStatus(\n\tconfig: SylphxConfig,\n): Promise<DatabaseStatusInfo> {\n\treturn callApi<DatabaseStatusInfo>(config, \"/sdk/database/status\", {\n\t\tmethod: \"GET\",\n\t});\n}\n","/**\n * KV (Key-Value Store) Functions\n *\n * Pure functions for distributed key-value storage backed by Redis.\n * Supports strings, hashes, lists, sorted sets, and built-in rate limiting.\n *\n * Keys are automatically namespaced per app, so no key collisions occur\n * between different apps on the same Platform.\n *\n * @example\n * ```ts\n * import { createConfig, kvSet, kvGet, kvDelete } from '@sylphx/sdk'\n *\n * const config = createConfig({ secretKey: process.env.SYLPHX_SECRET_KEY! })\n *\n * // Basic key-value operations\n * await kvSet(config, { key: 'user:123', value: { name: 'Alice' }, ex: 3600 })\n * const user = await kvGet(config, 'user:123')\n * await kvDelete(config, 'user:123')\n * ```\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { KvRateLimitResult, KvSetOptions, KvZMember } from \"./kv-types\";\n\n// Re-export shared types\nexport type { KvSetOptions, KvRateLimitResult, KvZMember } from \"./kv-types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface KvSetRequest extends KvSetOptions {\n\t/** Key to store */\n\tkey: string;\n\t/** Value to store (any JSON-serializable value) */\n\tvalue: unknown;\n}\n\nexport interface KvMsetRequest {\n\t/** Key-value pairs to set in a single atomic operation */\n\tentries: Array<{ key: string; value: unknown }>;\n}\n\nexport interface KvMgetRequest {\n\t/** Keys to retrieve */\n\tkeys: string[];\n}\n\nexport interface KvHsetRequest {\n\t/** Hash key */\n\tkey: string;\n\t/** Field-value pairs to set on the hash */\n\tfields: Record<string, unknown>;\n}\n\nexport interface KvHgetRequest {\n\t/** Hash key */\n\tkey: string;\n\t/** Field to get */\n\tfield: string;\n}\n\nexport interface KvHgetallRequest {\n\t/** Hash key */\n\tkey: string;\n}\n\nexport interface KvLpushRequest {\n\t/** List key */\n\tkey: string;\n\t/** Values to prepend (left push) */\n\tvalues: unknown[];\n}\n\nexport interface KvLrangeRequest {\n\t/** List key */\n\tkey: string;\n\t/** Start index (0-based, negative counts from end) */\n\tstart: number;\n\t/** Stop index (inclusive, negative counts from end) */\n\tstop: number;\n}\n\nexport interface KvZaddRequest {\n\t/** Sorted set key */\n\tkey: string;\n\t/** Members with scores to add */\n\tmembers: KvZMember[];\n}\n\nexport interface KvZrangeRequest {\n\t/** Sorted set key */\n\tkey: string;\n\t/** Start index or score */\n\tstart: number | string;\n\t/** Stop index or score */\n\tstop: number | string;\n\t/** Return scores alongside members */\n\twithScores?: boolean;\n\t/** Reverse order */\n\trev?: boolean;\n\t/** Treat start/stop as scores (BYSCORE) */\n\tbyScore?: boolean;\n}\n\nexport interface KvIncrRequest {\n\t/** Key to increment */\n\tkey: string;\n\t/** Amount to increment by (default: 1) */\n\tby?: number;\n}\n\nexport interface KvExpireRequest {\n\t/** Key to set expiry on */\n\tkey: string;\n\t/** TTL in seconds */\n\tseconds: number;\n}\n\nexport interface KvRateLimitRequest {\n\t/** Rate limit identifier (e.g., userId, IP) */\n\tidentifier: string;\n\t/** Maximum requests allowed in the window */\n\tlimit: number;\n\t/** Window duration in seconds */\n\twindow: number;\n}\n\n// ============================================================================\n// Functions — Basic Operations\n// ============================================================================\n\n/**\n * Set a key-value pair, with optional TTL and conditional flags.\n *\n * @example\n * ```ts\n * // Simple set\n * await kvSet(config, { key: 'session:abc', value: { userId: '123' }, ex: 86400 })\n *\n * // Set only if not exists (NX)\n * await kvSet(config, { key: 'lock:task', value: '1', ex: 30, nx: true })\n * ```\n */\nexport async function kvSet(\n\tconfig: SylphxConfig,\n\trequest: KvSetRequest,\n): Promise<{ ok: boolean }> {\n\treturn callApi<{ ok: boolean }>(config, \"/sdk/kv/set\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n\n/**\n * Get a value by key.\n *\n * Returns `null` if the key does not exist or has expired.\n *\n * @example\n * ```ts\n * const session = await kvGet<{ userId: string }>(config, 'session:abc')\n * if (session) {\n * console.log(session.userId)\n * }\n * ```\n */\nexport async function kvGet<T = unknown>(\n\tconfig: SylphxConfig,\n\tkey: string,\n): Promise<T | null> {\n\tconst result = await callApi<{ value: T | null }>(\n\t\tconfig,\n\t\t`/sdk/kv/get/${encodeURIComponent(key)}`,\n\t\t{ method: \"GET\" },\n\t);\n\treturn result.value;\n}\n\n/**\n * Delete one or more keys.\n *\n * @example\n * ```ts\n * const { deleted } = await kvDelete(config, 'session:abc')\n * console.log(`Deleted ${deleted} keys`)\n * ```\n */\nexport async function kvDelete(\n\tconfig: SylphxConfig,\n\tkey: string,\n): Promise<{ deleted: number }> {\n\treturn callApi<{ deleted: number }>(\n\t\tconfig,\n\t\t`/sdk/kv/delete/${encodeURIComponent(key)}`,\n\t\t{ method: \"DELETE\" },\n\t);\n}\n\n/**\n * Check if a key exists.\n *\n * @example\n * ```ts\n * const { exists } = await kvExists(config, 'session:abc')\n * ```\n */\nexport async function kvExists(\n\tconfig: SylphxConfig,\n\tkey: string,\n): Promise<{ exists: boolean }> {\n\treturn callApi<{ exists: boolean }>(\n\t\tconfig,\n\t\t`/sdk/kv/exists/${encodeURIComponent(key)}`,\n\t\t{ method: \"GET\" },\n\t);\n}\n\n/**\n * Set expiry on an existing key.\n *\n * @example\n * ```ts\n * await kvExpire(config, { key: 'session:abc', seconds: 3600 })\n * ```\n */\nexport async function kvExpire(\n\tconfig: SylphxConfig,\n\trequest: KvExpireRequest,\n): Promise<{ ok: boolean }> {\n\treturn callApi<{ ok: boolean }>(config, \"/sdk/kv/expire\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n\n/**\n * Increment a numeric value.\n *\n * @example\n * ```ts\n * const { value } = await kvIncr(config, { key: 'page:views', by: 1 })\n * ```\n */\nexport async function kvIncr(\n\tconfig: SylphxConfig,\n\trequest: KvIncrRequest,\n): Promise<{ value: number }> {\n\treturn callApi<{ value: number }>(config, \"/sdk/kv/incr\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n\n// ============================================================================\n// Functions — Bulk Operations\n// ============================================================================\n\n/**\n * Set multiple key-value pairs atomically.\n */\nexport async function kvMset(\n\tconfig: SylphxConfig,\n\trequest: KvMsetRequest,\n): Promise<{ ok: boolean }> {\n\treturn callApi<{ ok: boolean }>(config, \"/sdk/kv/mset\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n\n/**\n * Get multiple values by keys in a single request.\n *\n * Returns `null` for keys that don't exist.\n */\nexport async function kvMget<T = unknown>(\n\tconfig: SylphxConfig,\n\trequest: KvMgetRequest,\n): Promise<Array<T | null>> {\n\tconst result = await callApi<{ values: Array<T | null> }>(\n\t\tconfig,\n\t\t\"/sdk/kv/mget\",\n\t\t{ method: \"POST\", body: request },\n\t);\n\treturn result.values;\n}\n\n// ============================================================================\n// Functions — Hash Operations\n// ============================================================================\n\n/**\n * Set fields on a hash key.\n *\n * @example\n * ```ts\n * await kvHset(config, { key: 'user:123', fields: { name: 'Alice', age: 30 } })\n * ```\n */\nexport async function kvHset(\n\tconfig: SylphxConfig,\n\trequest: KvHsetRequest,\n): Promise<{ count: number }> {\n\treturn callApi<{ count: number }>(config, \"/sdk/kv/hset\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n\n/**\n * Get a single field from a hash key.\n */\nexport async function kvHget<T = unknown>(\n\tconfig: SylphxConfig,\n\trequest: KvHgetRequest,\n): Promise<T | null> {\n\tconst result = await callApi<{ value: T | null }>(config, \"/sdk/kv/hget\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n\treturn result.value;\n}\n\n/**\n * Get all fields from a hash key.\n */\nexport async function kvHgetall<\n\tT extends Record<string, unknown> = Record<string, unknown>,\n>(config: SylphxConfig, request: KvHgetallRequest): Promise<T | null> {\n\tconst result = await callApi<{ value: T | null }>(config, \"/sdk/kv/hgetall\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n\treturn result.value;\n}\n\n// ============================================================================\n// Functions — List Operations\n// ============================================================================\n\n/**\n * Left-push values onto a list.\n *\n * @example\n * ```ts\n * const { length } = await kvLpush(config, { key: 'events', values: [event] })\n * ```\n */\nexport async function kvLpush(\n\tconfig: SylphxConfig,\n\trequest: KvLpushRequest,\n): Promise<{ length: number }> {\n\treturn callApi<{ length: number }>(config, \"/sdk/kv/lpush\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n\n/**\n * Get a range of elements from a list.\n *\n * @example\n * ```ts\n * // Get last 10 events\n * const items = await kvLrange(config, { key: 'events', start: 0, stop: 9 })\n * ```\n */\nexport async function kvLrange<T = unknown>(\n\tconfig: SylphxConfig,\n\trequest: KvLrangeRequest,\n): Promise<T[]> {\n\tconst result = await callApi<{ items: T[] }>(config, \"/sdk/kv/lrange\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n\treturn result.items;\n}\n\n// ============================================================================\n// Functions — Sorted Set Operations\n// ============================================================================\n\n/**\n * Add members to a sorted set.\n *\n * @example\n * ```ts\n * // Add to leaderboard\n * await kvZadd(config, {\n * key: 'leaderboard',\n * members: [{ member: 'user:123', score: 1500 }],\n * })\n * ```\n */\nexport async function kvZadd(\n\tconfig: SylphxConfig,\n\trequest: KvZaddRequest,\n): Promise<{ added: number }> {\n\treturn callApi<{ added: number }>(config, \"/sdk/kv/zadd\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n\n/**\n * Get a range of members from a sorted set.\n *\n * @example\n * ```ts\n * // Get top 10 leaderboard entries\n * const entries = await kvZrange(config, {\n * key: 'leaderboard',\n * start: 0,\n * stop: 9,\n * rev: true,\n * withScores: true,\n * })\n * ```\n */\nexport async function kvZrange(\n\tconfig: SylphxConfig,\n\trequest: KvZrangeRequest,\n): Promise<Array<{ member: string; score?: number }>> {\n\tconst result = await callApi<{\n\t\tmembers: Array<{ member: string; score?: number }>;\n\t}>(config, \"/sdk/kv/zrange\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n\treturn result.members;\n}\n\n// ============================================================================\n// Functions — Rate Limiting\n// ============================================================================\n\n/**\n * Check and consume a rate limit token using Redis sliding window.\n *\n * This is a built-in rate limiter — no external service needed.\n *\n * @example\n * ```ts\n * // 10 requests per 60 seconds per user\n * const result = await kvRateLimit(config, {\n * identifier: `user:${userId}`,\n * limit: 10,\n * window: 60,\n * })\n *\n * if (!result.success) {\n * return Response.json({ error: 'Rate limit exceeded' }, { status: 429 })\n * }\n * ```\n */\nexport async function kvRateLimit(\n\tconfig: SylphxConfig,\n\trequest: KvRateLimitRequest,\n): Promise<KvRateLimitResult> {\n\treturn callApi<KvRateLimitResult>(config, \"/sdk/kv/ratelimit\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n","/**\n * Realtime Functions\n *\n * Pure functions for real-time messaging via Redis Streams.\n * Supports channel-based pub/sub with SSE delivery to browsers.\n *\n * @example\n * ```ts\n * import { createConfig, realtimeEmit } from '@sylphx/sdk'\n *\n * // Server: emit events to connected clients\n * const config = createConfig({ secretKey: process.env.SYLPHX_SECRET_KEY! })\n * await realtimeEmit(config, {\n * channel: 'orders',\n * event: 'order.created',\n * data: { orderId: '123', amount: 99 },\n * })\n * ```\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\nimport type { StreamMessage } from \"./realtime-types\";\n\n// Re-export shared types\nexport type { StreamMessage } from \"./realtime-types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface RealtimeEmitRequest {\n\t/** Channel to emit the event to */\n\tchannel: string;\n\t/** Event type/name */\n\tevent: string;\n\t/** Event data (any JSON-serializable value) */\n\tdata: unknown;\n}\n\nexport interface RealtimeEmitResponse {\n\t/** Stream entry ID */\n\tid: string;\n\t/** Channel the event was emitted to */\n\tchannel: string;\n}\n\nexport interface RealtimeHistoryRequest {\n\t/** Channel to get history for */\n\tchannel: string;\n\t/** Maximum number of messages to return (default: 50) */\n\tlimit?: number;\n\t/** Return messages after this stream entry ID */\n\tafter?: string;\n}\n\nexport interface RealtimeHistoryResponse {\n\t/** List of historical messages */\n\tmessages: StreamMessage[];\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Emit an event to a realtime channel.\n *\n * All clients subscribed to the channel (via `useRealtime` hook or SSE)\n * will receive the event instantly.\n *\n * @example\n * ```ts\n * // Notify all clients watching a document\n * await realtimeEmit(config, {\n * channel: `doc:${documentId}`,\n * event: 'doc.updated',\n * data: { updatedBy: userId, timestamp: Date.now() },\n * })\n * ```\n */\nexport async function realtimeEmit(\n\tconfig: SylphxConfig,\n\trequest: RealtimeEmitRequest,\n): Promise<RealtimeEmitResponse> {\n\treturn callApi<RealtimeEmitResponse>(config, \"/sdk/realtime/emit\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n\n/**\n * Get historical messages from a channel.\n *\n * Useful for initializing state when a client first connects,\n * or for resuming from a known stream position.\n *\n * @example\n * ```ts\n * // Get last 20 messages when a user joins a chat\n * const { messages } = await getRealtimeHistory(config, {\n * channel: 'chat:general',\n * limit: 20,\n * })\n * ```\n */\nexport async function getRealtimeHistory(\n\tconfig: SylphxConfig,\n\trequest: RealtimeHistoryRequest,\n): Promise<RealtimeHistoryResponse> {\n\tconst params = new URLSearchParams();\n\tparams.set(\"channel\", request.channel);\n\tif (request.limit !== undefined) params.set(\"limit\", String(request.limit));\n\tif (request.after !== undefined) params.set(\"after\", request.after);\n\n\treturn callApi<RealtimeHistoryResponse>(\n\t\tconfig,\n\t\t`/sdk/realtime/history?${params.toString()}`,\n\t\t{ method: \"GET\" },\n\t);\n}\n","/**\n * Deploy Functions\n *\n * Pure functions for managing app deployments, environment variables,\n * and custom domains via the Platform Deploy API.\n *\n * Requires secret key authentication (`sk_*`).\n *\n * @example\n * ```ts\n * import { createConfig, triggerDeploy, getDeployStatus } from '@sylphx/sdk'\n *\n * const config = createConfig({ secretKey: process.env.SYLPHX_SECRET_KEY! })\n *\n * // Trigger a deployment\n * const deploy = await triggerDeploy(config, { envId: 'env_prod_xxx' })\n *\n * // Poll for completion\n * const status = await getDeployStatus(config, deploy.envId)\n * console.log(status.status) // 'building' | 'deploying' | 'success' | 'failed'\n * ```\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type DeployStatus =\n\t| \"queued\"\n\t| \"building\"\n\t| \"deploying\"\n\t| \"success\"\n\t| \"failed\"\n\t| \"cancelled\";\n\nexport interface DeployInfo {\n\t/** Deployment ID */\n\tdeploymentId: string;\n\t/** Environment ID */\n\tenvId: string;\n\t/** Current deployment status */\n\tstatus: DeployStatus;\n\t/** Deployment URL */\n\turl?: string;\n\t/** Git commit SHA */\n\tcommitSha?: string;\n\t/** Git branch */\n\tbranch?: string;\n\t/** Deployment started at (ISO timestamp) */\n\tstartedAt?: string;\n\t/** Deployment completed at (ISO timestamp) */\n\tcompletedAt?: string;\n\t/** Error message if failed */\n\terror?: string;\n}\n\nexport interface TriggerDeployRequest {\n\t/** Environment ID to deploy */\n\tenvId: string;\n\t/** Force rebuild without cache */\n\tforceRebuild?: boolean;\n}\n\nexport interface RollbackDeployRequest {\n\t/** Environment ID to rollback */\n\tenvId: string;\n\t/** Deployment ID to rollback to */\n\tdeploymentId: string;\n}\n\nexport interface EnvVar {\n\t/** Environment variable key */\n\tkey: string;\n\t/** Environment variable value */\n\tvalue: string;\n\t/** Whether value is sensitive (masked in logs) */\n\tsensitive?: boolean;\n}\n\nexport interface SetEnvVarRequest {\n\t/** Environment variable key */\n\tkey: string;\n\t/** Environment variable value */\n\tvalue: string;\n\t/** Whether value is sensitive/secret */\n\tsensitive?: boolean;\n}\n\nexport interface CustomDomain {\n\t/** Domain name */\n\tdomain: string;\n\t/** Verification status */\n\tstatus: \"pending\" | \"verifying\" | \"active\" | \"failed\";\n\t/** DNS records required for verification */\n\tdnsRecords?: Array<{\n\t\ttype: string;\n\t\tname: string;\n\t\tvalue: string;\n\t}>;\n}\n\nexport interface AddDomainRequest {\n\t/** Domain name to add */\n\tdomain: string;\n}\n\nexport interface DeployHistoryResponse {\n\t/** List of past deployments */\n\tdeployments: DeployInfo[];\n}\n\nexport interface BuildLog {\n\t/** Log line text */\n\ttext: string;\n\t/** Log timestamp (ISO) */\n\ttimestamp?: string;\n\t/** Log level */\n\tlevel?: \"info\" | \"warn\" | \"error\";\n}\n\nexport interface BuildLogHistoryResponse {\n\t/** Log lines */\n\tlogs: BuildLog[];\n}\n\n// ============================================================================\n// Functions — Deployments\n// ============================================================================\n\n/**\n * Trigger a new deployment for an environment.\n *\n * @example\n * ```ts\n * const deploy = await triggerDeploy(config, { envId: 'env_prod_xxx' })\n * console.log(`Deployment ${deploy.deploymentId} started`)\n * ```\n */\nexport async function triggerDeploy(\n\tconfig: SylphxConfig,\n\trequest: TriggerDeployRequest,\n): Promise<DeployInfo> {\n\treturn callApi<DeployInfo>(\n\t\tconfig,\n\t\t`/sdk/deploy/trigger/${encodeURIComponent(request.envId)}`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody:\n\t\t\t\trequest.forceRebuild !== undefined\n\t\t\t\t\t? { forceRebuild: request.forceRebuild }\n\t\t\t\t\t: undefined,\n\t\t},\n\t);\n}\n\n/**\n * Get the current deployment status for an environment.\n *\n * @example\n * ```ts\n * const { status } = await getDeployStatus(config, 'env_prod_xxx')\n * if (status === 'success') {\n * console.log('Deployment succeeded!')\n * }\n * ```\n */\nexport async function getDeployStatus(\n\tconfig: SylphxConfig,\n\tenvId: string,\n): Promise<DeployInfo> {\n\treturn callApi<DeployInfo>(\n\t\tconfig,\n\t\t`/sdk/deploy/status/${encodeURIComponent(envId)}`,\n\t\t{ method: \"GET\" },\n\t);\n}\n\n/**\n * Get deployment history for an environment.\n *\n * @example\n * ```ts\n * const { deployments } = await getDeployHistory(config, 'env_prod_xxx')\n * const lastDeploy = deployments[0]\n * ```\n */\nexport async function getDeployHistory(\n\tconfig: SylphxConfig,\n\tenvId: string,\n): Promise<DeployHistoryResponse> {\n\treturn callApi<DeployHistoryResponse>(\n\t\tconfig,\n\t\t`/sdk/deploy/history/${encodeURIComponent(envId)}`,\n\t\t{ method: \"GET\" },\n\t);\n}\n\n/**\n * Rollback an environment to a previous deployment.\n *\n * @example\n * ```ts\n * await rollbackDeploy(config, {\n * envId: 'env_prod_xxx',\n * deploymentId: 'dep_abc123',\n * })\n * ```\n */\nexport async function rollbackDeploy(\n\tconfig: SylphxConfig,\n\trequest: RollbackDeployRequest,\n): Promise<DeployInfo> {\n\treturn callApi<DeployInfo>(\n\t\tconfig,\n\t\t`/sdk/deploy/rollback/${encodeURIComponent(request.envId)}`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: { deploymentId: request.deploymentId },\n\t\t},\n\t);\n}\n\n/**\n * Get stored build log history for an environment.\n *\n * For live log streaming during an active build, use the SSE endpoint\n * directly or the `useDeployLogs` React hook.\n *\n * @example\n * ```ts\n * const { logs } = await getBuildLogHistory(config, 'env_prod_xxx')\n * for (const log of logs) {\n * console.log(log.text)\n * }\n * ```\n */\nexport async function getBuildLogHistory(\n\tconfig: SylphxConfig,\n\tenvId: string,\n): Promise<BuildLogHistoryResponse> {\n\treturn callApi<BuildLogHistoryResponse>(\n\t\tconfig,\n\t\t`/sdk/deploy/logs/${encodeURIComponent(envId)}/history`,\n\t\t{ method: \"GET\" },\n\t);\n}\n\n// ============================================================================\n// Functions — Environment Variables\n// ============================================================================\n\n/**\n * List environment variables for a deployment environment.\n *\n * Sensitive values are masked in the response.\n *\n * @example\n * ```ts\n * const envVars = await listEnvVars(config, 'env_prod_xxx')\n * ```\n */\nexport async function listEnvVars(\n\tconfig: SylphxConfig,\n\tenvId: string,\n): Promise<EnvVar[]> {\n\tconst result = await callApi<{ envVars: EnvVar[] }>(\n\t\tconfig,\n\t\t`/sdk/deploy/envvars/${encodeURIComponent(envId)}`,\n\t\t{ method: \"GET\" },\n\t);\n\treturn result.envVars;\n}\n\n/**\n * Set (create or update) an environment variable.\n *\n * @example\n * ```ts\n * await setEnvVar(config, 'env_prod_xxx', {\n * key: 'DATABASE_URL',\n * value: 'postgresql://...',\n * sensitive: true,\n * })\n * ```\n */\nexport async function setEnvVar(\n\tconfig: SylphxConfig,\n\tenvId: string,\n\trequest: SetEnvVarRequest,\n): Promise<EnvVar> {\n\treturn callApi<EnvVar>(\n\t\tconfig,\n\t\t`/sdk/deploy/envvars/${encodeURIComponent(envId)}`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: request,\n\t\t},\n\t);\n}\n\n/**\n * Delete an environment variable.\n *\n * @example\n * ```ts\n * await deleteEnvVar(config, 'env_prod_xxx', 'DATABASE_URL')\n * ```\n */\nexport async function deleteEnvVar(\n\tconfig: SylphxConfig,\n\tenvId: string,\n\tkey: string,\n): Promise<{ deleted: boolean }> {\n\treturn callApi<{ deleted: boolean }>(\n\t\tconfig,\n\t\t`/sdk/deploy/envvars/${encodeURIComponent(envId)}/${encodeURIComponent(key)}`,\n\t\t{ method: \"DELETE\" },\n\t);\n}\n\n// ============================================================================\n// Functions — Custom Domains\n// ============================================================================\n\n/**\n * List custom domains for a deployment environment.\n *\n * @example\n * ```ts\n * const domains = await listCustomDomains(config, 'env_prod_xxx')\n * ```\n */\nexport async function listCustomDomains(\n\tconfig: SylphxConfig,\n\tenvId: string,\n): Promise<CustomDomain[]> {\n\tconst result = await callApi<{ domains: CustomDomain[] }>(\n\t\tconfig,\n\t\t`/sdk/deploy/domains/${encodeURIComponent(envId)}`,\n\t\t{ method: \"GET\" },\n\t);\n\treturn result.domains;\n}\n\n/**\n * Add a custom domain to a deployment environment.\n *\n * After adding, you'll need to update your DNS records to complete verification.\n *\n * @example\n * ```ts\n * const domain = await addCustomDomain(config, 'env_prod_xxx', {\n * domain: 'myapp.com',\n * })\n * // domain.dnsRecords contains the DNS records to add\n * ```\n */\nexport async function addCustomDomain(\n\tconfig: SylphxConfig,\n\tenvId: string,\n\trequest: AddDomainRequest,\n): Promise<CustomDomain> {\n\treturn callApi<CustomDomain>(\n\t\tconfig,\n\t\t`/sdk/deploy/domains/${encodeURIComponent(envId)}`,\n\t\t{\n\t\t\tmethod: \"POST\",\n\t\t\tbody: request,\n\t\t},\n\t);\n}\n\n/**\n * Remove a custom domain from a deployment environment.\n *\n * @example\n * ```ts\n * await removeCustomDomain(config, 'env_prod_xxx', 'myapp.com')\n * ```\n */\nexport async function removeCustomDomain(\n\tconfig: SylphxConfig,\n\tenvId: string,\n\tdomain: string,\n): Promise<{ removed: boolean }> {\n\treturn callApi<{ removed: boolean }>(\n\t\tconfig,\n\t\t`/sdk/deploy/domains/${encodeURIComponent(envId)}/${encodeURIComponent(domain)}`,\n\t\t{ method: \"DELETE\" },\n\t);\n}\n","/**\n * Monitoring Functions\n *\n * Pure functions for error tracking and log capture.\n * Works client-side and server-side.\n *\n * ## Industry Patterns\n * - **Error grouping via fingerprinting** — Same error only billed once (Sentry pattern)\n * - **Adaptive sampling** — Automatic sample rate adjustment based on quota usage\n * - **Breadcrumb trails** — Contextual trail leading to errors\n *\n * @example\n * ```ts\n * import { createConfig, captureException, captureMessage } from '@sylphx/sdk'\n *\n * const config = createConfig({ appId: process.env.NEXT_PUBLIC_SYLPHX_APP_ID! })\n *\n * // Capture errors\n * try {\n * await riskyOperation()\n * } catch (err) {\n * await captureException(config, err as Error)\n * }\n *\n * // Capture log messages\n * await captureMessage(config, 'User completed onboarding', { level: 'info' })\n * ```\n */\n\nimport { type SylphxConfig, callApi } from \"./config\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type MonitoringSeverity = \"fatal\" | \"error\" | \"warning\" | \"info\";\n\nexport interface ExceptionFrame {\n\t/** Source filename */\n\tfilename?: string;\n\t/** Function name */\n\tfunction?: string;\n\t/** Line number */\n\tlineno?: number;\n\t/** Column number */\n\tcolno?: number;\n}\n\nexport interface ExceptionValue {\n\t/** Exception class/type (e.g., \"TypeError\") */\n\ttype: string;\n\t/** Exception message */\n\tvalue: string;\n\t/** Stack trace frames (innermost first) */\n\tstacktrace?: { frames?: ExceptionFrame[] };\n}\n\nexport interface Breadcrumb {\n\t/** Breadcrumb type (e.g., \"navigation\", \"http\", \"ui.click\") */\n\ttype?: string;\n\t/** Log level */\n\tlevel?: MonitoringSeverity;\n\t/** Breadcrumb message */\n\tmessage?: string;\n\t/** Breadcrumb data */\n\tdata?: Record<string, unknown>;\n\t/** Unix timestamp (seconds) */\n\ttimestamp?: number;\n}\n\nexport interface CaptureExceptionRequest {\n\t/** Exception value(s) — first is primary, rest are chained causes */\n\texception: { values: ExceptionValue[] };\n\t/** Severity level (default: \"error\") */\n\tlevel?: MonitoringSeverity;\n\t/** Current page route */\n\troute?: string;\n\t/** User agent string */\n\tuserAgent?: string;\n\t/** App release version */\n\trelease?: string;\n\t/** Environment name (e.g., \"production\", \"staging\") */\n\tenvironment?: string;\n\t/** Custom tags for filtering */\n\ttags?: Record<string, string>;\n\t/** Extra context data */\n\textra?: Record<string, unknown>;\n\t/** Breadcrumb trail leading to the error */\n\tbreadcrumbs?: Breadcrumb[];\n\t/** Custom fingerprint for grouping (overrides automatic grouping) */\n\tfingerprint?: string[];\n}\n\nexport interface CaptureMessageRequest {\n\t/** Log message */\n\tmessage: string;\n\t/** Severity level (default: \"info\") */\n\tlevel?: MonitoringSeverity;\n\t/** Current page route */\n\troute?: string;\n\t/** App release version */\n\trelease?: string;\n\t/** Custom tags for filtering */\n\ttags?: Record<string, string>;\n\t/** Extra context data */\n\textra?: Record<string, unknown>;\n}\n\nexport interface MonitoringResponse {\n\t/** Internal event ID */\n\teventId: string;\n\t/** Whether this is a new unique error (true = billed, false = duplicate = free) */\n\tisNewError: boolean;\n\t/** Current quota usage percentage (0-100+). Present when >= 50%. */\n\tquotaUsage?: number;\n\t/**\n\t * Recommended client-side sample rate (0.0-1.0).\n\t * Reduce your error capture rate to this value when quota is high.\n\t * Present when quotaUsage >= 50%.\n\t */\n\trecommendedSampleRate?: number;\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * Convert a native Error object to ExceptionValue format.\n */\nfunction errorToExceptionValue(error: Error): ExceptionValue {\n\tconst frames: ExceptionFrame[] = [];\n\n\tif (error.stack) {\n\t\t// Parse V8/SpiderMonkey style stack traces\n\t\tconst lines = error.stack.split(\"\\n\").slice(1);\n\t\tfor (const line of lines) {\n\t\t\t// V8: \" at functionName (file:line:col)\"\n\t\t\tconst v8Match = line.match(/^\\s+at\\s+(.+?)\\s+\\((.+):(\\d+):(\\d+)\\)$/);\n\t\t\tif (v8Match) {\n\t\t\t\tframes.push({\n\t\t\t\t\tfunction: v8Match[1],\n\t\t\t\t\tfilename: v8Match[2],\n\t\t\t\t\tlineno: Number(v8Match[3]),\n\t\t\t\t\tcolno: Number(v8Match[4]),\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t// V8 (no function): \" at file:line:col\"\n\t\t\tconst v8AnonMatch = line.match(/^\\s+at\\s+(.+):(\\d+):(\\d+)$/);\n\t\t\tif (v8AnonMatch) {\n\t\t\t\tframes.push({\n\t\t\t\t\tfilename: v8AnonMatch[1],\n\t\t\t\t\tlineno: Number(v8AnonMatch[2]),\n\t\t\t\t\tcolno: Number(v8AnonMatch[3]),\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\treturn {\n\t\ttype: error.name || \"Error\",\n\t\tvalue: error.message,\n\t\tstacktrace: frames.length > 0 ? { frames } : undefined,\n\t};\n}\n\n// ============================================================================\n// Functions\n// ============================================================================\n\n/**\n * Capture an exception for error tracking.\n *\n * Errors with the same fingerprint are grouped automatically.\n * Duplicate occurrences of the same error are FREE (only new unique\n * errors count against your quota).\n *\n * @example\n * ```ts\n * try {\n * await processPayment(amount)\n * } catch (err) {\n * const result = await captureException(config, err as Error, {\n * tags: { paymentProvider: 'stripe' },\n * extra: { amount, userId },\n * })\n * }\n * ```\n */\nexport async function captureException(\n\tconfig: SylphxConfig,\n\terror: Error,\n\toptions: Omit<CaptureExceptionRequest, \"exception\"> = {},\n): Promise<MonitoringResponse> {\n\tconst exceptionValue = errorToExceptionValue(error);\n\tconst request: CaptureExceptionRequest = {\n\t\t...options,\n\t\texception: { values: [exceptionValue] },\n\t};\n\treturn callApi<MonitoringResponse>(config, \"/sdk/monitoring/exception\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n\n/**\n * Capture an exception with full control over the exception payload.\n *\n * Use this for structured exception capture with custom types, chained\n * causes, or when not working with native Error objects.\n */\nexport async function captureExceptionRaw(\n\tconfig: SylphxConfig,\n\trequest: CaptureExceptionRequest,\n): Promise<MonitoringResponse> {\n\treturn callApi<MonitoringResponse>(config, \"/sdk/monitoring/exception\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n\n/**\n * Capture a log message for monitoring.\n *\n * Like `captureException` but for non-error events (warnings, info, etc.).\n * Messages with the same content are grouped automatically.\n *\n * @example\n * ```ts\n * // Info log\n * await captureMessage(config, 'Payment webhook received', {\n * level: 'info',\n * tags: { provider: 'stripe', event: 'payment.succeeded' },\n * })\n *\n * // Warning\n * await captureMessage(config, 'Slow query detected', {\n * level: 'warning',\n * extra: { queryMs: 2400, query: sql },\n * })\n * ```\n */\nexport async function captureMessage(\n\tconfig: SylphxConfig,\n\tmessage: string,\n\toptions: Omit<CaptureMessageRequest, \"message\"> = {},\n): Promise<MonitoringResponse> {\n\tconst request: CaptureMessageRequest = { ...options, message };\n\treturn callApi<MonitoringResponse>(config, \"/sdk/monitoring/message\", {\n\t\tmethod: \"POST\",\n\t\tbody: request,\n\t});\n}\n"],"mappings":"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","names":["isRetryableError"]}