@01.software/sdk 0.2.7 → 0.2.9-dev.260305.4f1735b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -2
- package/dist/{auth-ZBsN9vPn.d.cts → auth-3qWmYUln.d.cts} +1 -1
- package/dist/{auth-D-_Ju7m5.d.ts → auth-BfGmV75P.d.ts} +1 -1
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +2 -2
- package/dist/auth.d.ts +2 -2
- package/dist/auth.js.map +1 -1
- package/dist/components.d.cts +1 -1
- package/dist/components.d.ts +1 -1
- package/dist/index.cjs +68 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -21
- package/dist/index.d.ts +48 -21
- package/dist/index.js +68 -15
- package/dist/index.js.map +1 -1
- package/dist/{payload-types-CRSz8jfP.d.cts → payload-types-Bpi16SHO.d.cts} +79 -79
- package/dist/{payload-types-CRSz8jfP.d.ts → payload-types-Bpi16SHO.d.ts} +79 -79
- package/dist/{webhook-C_DHB0Sw.d.ts → webhook-Bk-i0A59.d.ts} +1 -1
- package/dist/{webhook-Dn5o0LXq.d.cts → webhook-DMN-IPra.d.cts} +1 -1
- package/dist/webhook.d.cts +2 -2
- package/dist/webhook.d.ts +2 -2
- package/package.json +12 -3
package/README.md
CHANGED
|
@@ -125,9 +125,18 @@ const product = await client.from('products').findById('id')
|
|
|
125
125
|
// Create (server only) - returns PayloadMutationResponse
|
|
126
126
|
const { doc, message } = await client.from('products').create({ name: 'Product' })
|
|
127
127
|
|
|
128
|
+
// Create with file upload (server only) - uses multipart/form-data
|
|
129
|
+
const { doc } = await client.from('product-images').create(
|
|
130
|
+
{ product: 'product_id', alt: 'Hero image' },
|
|
131
|
+
{ file: imageFile, filename: 'hero.jpg' },
|
|
132
|
+
)
|
|
133
|
+
|
|
128
134
|
// Update (server only) - returns PayloadMutationResponse
|
|
129
135
|
const { doc } = await client.from('products').update('id', { name: 'Updated' })
|
|
130
136
|
|
|
137
|
+
// Update with file replacement (server only)
|
|
138
|
+
await client.from('media').update('id', { alt: 'New alt' }, { file: newFile })
|
|
139
|
+
|
|
131
140
|
// Delete (server only) - returns document directly
|
|
132
141
|
const deletedDoc = await client.from('products').remove('id')
|
|
133
142
|
|
|
@@ -219,11 +228,11 @@ const { data, fetchNextPage, hasNextPage } = client.query.useInfiniteQuery({
|
|
|
219
228
|
})
|
|
220
229
|
|
|
221
230
|
// Mutation hooks — ServerClient only (auto-invalidate cache on success)
|
|
222
|
-
const { mutate: create } = client.query.useCreate({ collection: '
|
|
231
|
+
const { mutate: create } = client.query.useCreate({ collection: 'product-images' })
|
|
223
232
|
const { mutate: update } = client.query.useUpdate({ collection: 'products' })
|
|
224
233
|
const { mutate: remove } = client.query.useRemove({ collection: 'products' })
|
|
225
234
|
|
|
226
|
-
create({
|
|
235
|
+
create({ data: { alt: 'Hero' }, file: imageFile, filename: 'hero.jpg' })
|
|
227
236
|
update({ id: 'product_id', data: { title: 'Updated' } })
|
|
228
237
|
remove('product_id')
|
|
229
238
|
|
package/dist/auth.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/auth.ts","../src/core/internal/utils/index.ts"],"sourcesContent":["export type { JwtPayload } from './core/internal/utils'\nexport {\n createServerToken,\n verifyServerToken,\n decodeServerToken,\n createApiKey,\n parseApiKey,\n} from './core/internal/utils'\n","import { SignJWT, jwtVerify, decodeJwt } from 'jose'\nimport { createNetworkError, createUsageLimitError, createTimeoutError, TimeoutError, NetworkError } from '../errors'\nimport type { DebugConfig, RetryConfig } from '../../client/types'\nimport { API_URLS } from '../../client/types'\n\nconst DEFAULT_TIMEOUT = 30000\nconst DEFAULT_RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504]\nconst NON_RETRYABLE_STATUSES = [401, 403, 404, 422]\nconst SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']\n\nexport interface FetchOptions extends RequestInit {\n clientKey?: string\n secretKey?: string\n customerToken?: string\n timeout?: number\n baseUrl?: string\n debug?: boolean | DebugConfig\n retry?: RetryConfig\n}\n\nexport interface JwtPayload {\n clientKey: string\n iat?: number\n exp?: number\n}\n\n/**\n * Creates a JWT token for server-side authentication.\n * The token is valid for 1 hour by default.\n *\n * @param clientKey - Client API key\n * @param secretKey - Secret key used for signing\n * @param expiresIn - Token expiration time (default: '1h')\n * @returns Promise<string> JWT token\n *\n * @example\n * ```typescript\n * const token = await createServerToken('client-key', 'secret-key')\n * // Use in Authorization header: `Bearer ${token}`\n * ```\n */\nexport async function createServerToken(\n clientKey: string,\n secretKey: string,\n expiresIn: string = '1h',\n): Promise<string> {\n if (!clientKey || !secretKey) {\n throw new Error('clientKey and secretKey are required.')\n }\n\n const secret = new TextEncoder().encode(secretKey)\n return new SignJWT({ clientKey })\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(expiresIn)\n .sign(secret)\n}\n\n/**\n * Verifies a JWT token and returns the payload.\n *\n * @param token - JWT token to verify\n * @param secretKey - Secret key used for verification\n * @returns Promise<JwtPayload> Verified payload containing clientKey\n * @throws Error if token is invalid or expired\n *\n * @example\n * ```typescript\n * const payload = await verifyServerToken(token, 'secret-key')\n * console.log(payload.clientKey)\n * ```\n */\nexport async function verifyServerToken(\n token: string,\n secretKey: string,\n): Promise<JwtPayload> {\n if (!token || !secretKey) {\n throw new Error('token and secretKey are required.')\n }\n\n const secret = new TextEncoder().encode(secretKey)\n const { payload } = await jwtVerify(token, secret, {\n algorithms: ['HS256'],\n })\n\n if (!payload.clientKey || typeof payload.clientKey !== 'string') {\n throw new Error('Invalid token payload: clientKey is missing')\n }\n\n return {\n clientKey: payload.clientKey as string,\n iat: payload.iat,\n exp: payload.exp,\n }\n}\n\n/**\n * Decodes a JWT token without verification.\n * WARNING: Use this only when you need to inspect token contents.\n * Always use verifyServerToken for authentication.\n *\n * @param token - JWT token to decode\n * @returns JwtPayload Decoded payload (unverified)\n *\n * @example\n * ```typescript\n * const payload = decodeServerToken(token)\n * console.log(payload.clientKey) // Unverified!\n * ```\n */\nexport function decodeServerToken(token: string): JwtPayload {\n if (!token) {\n throw new Error('token is required.')\n }\n\n const payload = decodeJwt(token)\n\n if (!payload.clientKey || typeof payload.clientKey !== 'string') {\n throw new Error('Invalid token payload: clientKey is missing')\n }\n\n return {\n clientKey: payload.clientKey as string,\n iat: payload.iat,\n exp: payload.exp,\n }\n}\n\n// ============================================================================\n// API Key Utilities\n// ============================================================================\n\n/**\n * Creates a Base64-encoded API key from clientKey and secretKey.\n * Use this for MCP server authentication.\n *\n * @param clientKey - Client API key\n * @param secretKey - Secret key\n * @returns Base64-encoded API key\n *\n * @example\n * ```typescript\n * const apiKey = createApiKey('client-key', 'secret-key')\n * // Use in x-api-key header\n * ```\n */\nexport function createApiKey(clientKey: string, secretKey: string): string {\n if (!clientKey || !secretKey) {\n throw new Error('clientKey and secretKey are required.')\n }\n\n // Browser와 Node.js 모두 지원\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(`${clientKey}:${secretKey}`).toString('base64')\n }\n return btoa(`${clientKey}:${secretKey}`)\n}\n\n/**\n * Parses a Base64-encoded API key to extract clientKey and secretKey.\n *\n * @param apiKey - Base64-encoded API key\n * @returns Object containing clientKey and secretKey\n * @throws Error if API key is invalid\n *\n * @example\n * ```typescript\n * const { clientKey, secretKey } = parseApiKey(apiKey)\n * ```\n */\nexport function parseApiKey(apiKey: string): {\n clientKey: string\n secretKey: string\n} {\n if (!apiKey) {\n throw new Error('apiKey is required.')\n }\n\n try {\n let decoded: string\n if (typeof Buffer !== 'undefined') {\n decoded = Buffer.from(apiKey, 'base64').toString('utf-8')\n } else {\n decoded = atob(apiKey)\n }\n\n const colonIndex = decoded.indexOf(':')\n if (colonIndex === -1) {\n throw new Error('Invalid format: missing colon separator')\n }\n\n const clientKey = decoded.substring(0, colonIndex)\n const secretKey = decoded.substring(colonIndex + 1)\n\n if (!clientKey || !secretKey) {\n throw new Error('Invalid format: empty clientKey or secretKey')\n }\n\n return { clientKey, secretKey }\n } catch {\n throw new Error('Invalid API key. Expected Base64 encoded \"clientKey:secretKey\"')\n }\n}\n\nfunction debugLog(\n debug: boolean | DebugConfig | undefined,\n type: 'request' | 'response' | 'error',\n message: string,\n data?: unknown,\n) {\n if (!debug) return\n\n const shouldLog =\n debug === true ||\n (type === 'request' && (debug as DebugConfig).logRequests) ||\n (type === 'response' && (debug as DebugConfig).logResponses) ||\n (type === 'error' && (debug as DebugConfig).logErrors)\n\n if (shouldLog) {\n console.group(`[SDK ${type.toUpperCase()}] ${message}`)\n if (data) console.log(data)\n console.groupEnd()\n }\n}\n\nfunction getErrorSuggestion(status: number): string | undefined {\n if (status === 401) return 'Please check your authentication credentials.'\n if (status === 404) return 'The requested resource was not found.'\n if (status >= 500)\n return 'A server error occurred. Please try again later.'\n return undefined\n}\n\nasync function delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\nexport async function _fetch(\n url: string,\n options?: FetchOptions,\n): Promise<Response> {\n const {\n clientKey,\n secretKey,\n customerToken,\n timeout = DEFAULT_TIMEOUT,\n baseUrl = API_URLS.production,\n debug,\n retry,\n ...requestInit\n } = options || {}\n\n const retryConfig = {\n maxRetries: retry?.maxRetries ?? 3,\n retryableStatuses: retry?.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES,\n retryDelay:\n retry?.retryDelay ??\n ((attempt: number) => Math.min(1000 * 2 ** attempt, 10000)),\n }\n\n // Generate JWT once before retry loop (token valid for 1h)\n let authToken: string | undefined\n if (secretKey && clientKey) {\n authToken = await createServerToken(clientKey, secretKey)\n } else if (customerToken) {\n authToken = customerToken\n }\n\n let lastError: Error | undefined\n\n for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {\n try {\n const headers = new Headers(requestInit.headers)\n\n if (clientKey) {\n headers.set('X-Client-Key', clientKey)\n }\n\n if (authToken) {\n headers.set('Authorization', `Bearer ${authToken}`)\n }\n\n if (!headers.has('Content-Type') && requestInit.body) {\n headers.set('Content-Type', 'application/json')\n }\n\n // Redact sensitive headers for debug logging\n const redactedHeaders = Object.fromEntries(headers.entries())\n if (redactedHeaders['authorization']) {\n const token = redactedHeaders['authorization']\n // Show only \"Bearer eyJ...****\" to aid debugging without exposing the full token\n redactedHeaders['authorization'] = token.length > 15\n ? `${token.slice(0, 15)}...****`\n : '****'\n }\n\n debugLog(debug, 'request', url, {\n method: requestInit.method || 'GET',\n headers: redactedHeaders,\n attempt: attempt + 1,\n })\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n const response = await fetch(`${baseUrl}${url}`, {\n ...requestInit,\n headers,\n signal: controller.signal,\n })\n\n clearTimeout(timeoutId)\n\n debugLog(debug, 'response', url, {\n status: response.status,\n statusText: response.statusText,\n headers: Object.fromEntries(response.headers.entries()),\n })\n\n if (!response.ok) {\n // Usage limit 429 — never retry (distinguished by X-Usage-Limit header)\n if (\n response.status === 429 &&\n response.headers.get('X-Usage-Limit')\n ) {\n const limit = parseInt(response.headers.get('X-Usage-Limit') || '0', 10)\n const current = parseInt(response.headers.get('X-Usage-Current') || '0', 10)\n const remaining = parseInt(response.headers.get('X-Usage-Remaining') || '0', 10)\n\n throw createUsageLimitError(\n `Monthly API usage limit exceeded (${current.toLocaleString()}/${limit.toLocaleString()})`,\n { limit, current, remaining },\n { url, method: requestInit.method || 'GET', attempt: attempt + 1 },\n 'Monthly API call limit exceeded. Please upgrade your plan.',\n 'Upgrade your tenant plan to increase the monthly API call limit.',\n )\n }\n\n // Never retry non-retryable statuses regardless of user config\n if (NON_RETRYABLE_STATUSES.includes(response.status)) {\n throw createNetworkError(\n `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n { url, method: requestInit.method || 'GET', attempt: attempt + 1 },\n `Request failed (status: ${response.status})`,\n getErrorSuggestion(response.status),\n )\n }\n\n const error = createNetworkError(\n `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n { url, method: requestInit.method || 'GET', attempt: attempt + 1 },\n `Request failed (status: ${response.status})`,\n getErrorSuggestion(response.status),\n )\n\n const method = (requestInit.method || 'GET').toUpperCase()\n if (\n attempt < retryConfig.maxRetries &&\n SAFE_METHODS.includes(method) &&\n retryConfig.retryableStatuses.includes(response.status)\n ) {\n lastError = error\n const retryDelay = retryConfig.retryDelay(attempt)\n debugLog(debug, 'error', `Retrying in ${retryDelay}ms...`, error)\n await delay(retryDelay)\n continue\n }\n\n throw error\n }\n\n return response\n } catch (error) {\n debugLog(debug, 'error', url, error)\n\n const method = (requestInit.method || 'GET').toUpperCase()\n const isSafe = SAFE_METHODS.includes(method)\n\n if (error instanceof Error && error.name === 'AbortError') {\n const timeoutError = createTimeoutError(\n `Request timed out after ${timeout}ms.`,\n { url, timeout, attempt: attempt + 1 },\n 'The request timed out.',\n 'Please check your network connection or try again later.',\n )\n\n if (isSafe && attempt < retryConfig.maxRetries) {\n lastError = timeoutError\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw timeoutError\n }\n\n if (error instanceof TypeError) {\n const networkError = createNetworkError(\n 'Network connection failed.',\n undefined,\n { url, originalError: error.message, attempt: attempt + 1 },\n 'Network connection failed.',\n 'Please check your internet connection and try again.',\n )\n\n if (isSafe && attempt < retryConfig.maxRetries) {\n lastError = networkError\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw networkError\n }\n\n if (error instanceof NetworkError || error instanceof TimeoutError) {\n if (\n isSafe &&\n attempt < retryConfig.maxRetries &&\n error.status &&\n !NON_RETRYABLE_STATUSES.includes(error.status) &&\n retryConfig.retryableStatuses.includes(error.status)\n ) {\n lastError = error\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw error\n }\n\n const unknownError = createNetworkError(\n error instanceof Error\n ? error.message\n : 'An unknown network error occurred.',\n undefined,\n { url, originalError: error, attempt: attempt + 1 },\n 'An unknown error occurred.',\n 'Please try again later.',\n )\n\n if (isSafe && attempt < retryConfig.maxRetries) {\n lastError = unknownError\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw unknownError\n }\n }\n\n throw lastError ?? new NetworkError('Request failed after retries')\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAA8C;AAyC9C,SAAsB,kBACpB,WACA,WACA,YAAoB,MACH;AAAA;AACjB,QAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAEA,UAAM,SAAS,IAAI,YAAY,EAAE,OAAO,SAAS;AACjD,WAAO,IAAI,oBAAQ,EAAE,UAAU,CAAC,EAC7B,mBAAmB,EAAE,KAAK,QAAQ,CAAC,EACnC,YAAY,EACZ,kBAAkB,SAAS,EAC3B,KAAK,MAAM;AAAA,EAChB;AAAA;AAgBA,SAAsB,kBACpB,OACA,WACqB;AAAA;AACrB,QAAI,CAAC,SAAS,CAAC,WAAW;AACxB,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAEA,UAAM,SAAS,IAAI,YAAY,EAAE,OAAO,SAAS;AACjD,UAAM,EAAE,QAAQ,IAAI,UAAM,uBAAU,OAAO,QAAQ;AAAA,MACjD,YAAY,CAAC,OAAO;AAAA,IACtB,CAAC;AAED,QAAI,CAAC,QAAQ,aAAa,OAAO,QAAQ,cAAc,UAAU;AAC/D,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,WAAO;AAAA,MACL,WAAW,QAAQ;AAAA,MACnB,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAAA;AAgBO,SAAS,kBAAkB,OAA2B;AAC3D,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,oBAAoB;AAAA,EACtC;AAEA,QAAM,cAAU,uBAAU,KAAK;AAE/B,MAAI,CAAC,QAAQ,aAAa,OAAO,QAAQ,cAAc,UAAU;AAC/D,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AAEA,SAAO;AAAA,IACL,WAAW,QAAQ;AAAA,IACnB,KAAK,QAAQ;AAAA,IACb,KAAK,QAAQ;AAAA,EACf;AACF;AAoBO,SAAS,aAAa,WAAmB,WAA2B;AACzE,MAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACzD;AAGA,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,OAAO,KAAK,GAAG,SAAS,IAAI,SAAS,EAAE,EAAE,SAAS,QAAQ;AAAA,EACnE;AACA,SAAO,KAAK,GAAG,SAAS,IAAI,SAAS,EAAE;AACzC;AAcO,SAAS,YAAY,QAG1B;AACA,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,qBAAqB;AAAA,EACvC;AAEA,MAAI;AACF,QAAI;AACJ,QAAI,OAAO,WAAW,aAAa;AACjC,gBAAU,OAAO,KAAK,QAAQ,QAAQ,EAAE,SAAS,OAAO;AAAA,IAC1D,OAAO;AACL,gBAAU,KAAK,MAAM;AAAA,IACvB;AAEA,UAAM,aAAa,QAAQ,QAAQ,GAAG;AACtC,QAAI,eAAe,IAAI;AACrB,YAAM,IAAI,MAAM,yCAAyC;AAAA,IAC3D;AAEA,UAAM,YAAY,QAAQ,UAAU,GAAG,UAAU;AACjD,UAAM,YAAY,QAAQ,UAAU,aAAa,CAAC;AAElD,QAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAChE;AAEA,WAAO,EAAE,WAAW,UAAU;AAAA,EAChC,SAAQ;AACN,UAAM,IAAI,MAAM,gEAAgE;AAAA,EAClF;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/auth.ts","../src/core/internal/utils/index.ts"],"sourcesContent":["export type { JwtPayload } from './core/internal/utils'\nexport {\n createServerToken,\n verifyServerToken,\n decodeServerToken,\n createApiKey,\n parseApiKey,\n} from './core/internal/utils'\n","import { SignJWT, jwtVerify, decodeJwt } from 'jose'\nimport { createNetworkError, createUsageLimitError, createTimeoutError, TimeoutError, NetworkError } from '../errors'\nimport type { DebugConfig, RetryConfig } from '../../client/types'\nimport { API_URLS } from '../../client/types'\n\nconst DEFAULT_TIMEOUT = 30000\nconst DEFAULT_RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504]\nconst NON_RETRYABLE_STATUSES = [401, 403, 404, 422]\nconst SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']\n\nexport interface FetchOptions extends RequestInit {\n clientKey?: string\n secretKey?: string\n customerToken?: string\n timeout?: number\n baseUrl?: string\n debug?: boolean | DebugConfig\n retry?: RetryConfig\n}\n\nexport interface JwtPayload {\n clientKey: string\n iat?: number\n exp?: number\n}\n\n/**\n * Creates a JWT token for server-side authentication.\n * The token is valid for 1 hour by default.\n *\n * @param clientKey - Client API key\n * @param secretKey - Secret key used for signing\n * @param expiresIn - Token expiration time (default: '1h')\n * @returns Promise<string> JWT token\n *\n * @example\n * ```typescript\n * const token = await createServerToken('client-key', 'secret-key')\n * // Use in Authorization header: `Bearer ${token}`\n * ```\n */\nexport async function createServerToken(\n clientKey: string,\n secretKey: string,\n expiresIn: string = '1h',\n): Promise<string> {\n if (!clientKey || !secretKey) {\n throw new Error('clientKey and secretKey are required.')\n }\n\n const secret = new TextEncoder().encode(secretKey)\n return new SignJWT({ clientKey })\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(expiresIn)\n .sign(secret)\n}\n\n/**\n * Verifies a JWT token and returns the payload.\n *\n * @param token - JWT token to verify\n * @param secretKey - Secret key used for verification\n * @returns Promise<JwtPayload> Verified payload containing clientKey\n * @throws Error if token is invalid or expired\n *\n * @example\n * ```typescript\n * const payload = await verifyServerToken(token, 'secret-key')\n * console.log(payload.clientKey)\n * ```\n */\nexport async function verifyServerToken(\n token: string,\n secretKey: string,\n): Promise<JwtPayload> {\n if (!token || !secretKey) {\n throw new Error('token and secretKey are required.')\n }\n\n const secret = new TextEncoder().encode(secretKey)\n const { payload } = await jwtVerify(token, secret, {\n algorithms: ['HS256'],\n })\n\n if (!payload.clientKey || typeof payload.clientKey !== 'string') {\n throw new Error('Invalid token payload: clientKey is missing')\n }\n\n return {\n clientKey: payload.clientKey as string,\n iat: payload.iat,\n exp: payload.exp,\n }\n}\n\n/**\n * Decodes a JWT token without verification.\n * WARNING: Use this only when you need to inspect token contents.\n * Always use verifyServerToken for authentication.\n *\n * @param token - JWT token to decode\n * @returns JwtPayload Decoded payload (unverified)\n *\n * @example\n * ```typescript\n * const payload = decodeServerToken(token)\n * console.log(payload.clientKey) // Unverified!\n * ```\n */\nexport function decodeServerToken(token: string): JwtPayload {\n if (!token) {\n throw new Error('token is required.')\n }\n\n const payload = decodeJwt(token)\n\n if (!payload.clientKey || typeof payload.clientKey !== 'string') {\n throw new Error('Invalid token payload: clientKey is missing')\n }\n\n return {\n clientKey: payload.clientKey as string,\n iat: payload.iat,\n exp: payload.exp,\n }\n}\n\n// ============================================================================\n// API Key Utilities\n// ============================================================================\n\n/**\n * Creates a Base64-encoded API key from clientKey and secretKey.\n * Use this for MCP server authentication.\n *\n * @param clientKey - Client API key\n * @param secretKey - Secret key\n * @returns Base64-encoded API key\n *\n * @example\n * ```typescript\n * const apiKey = createApiKey('client-key', 'secret-key')\n * // Use in x-api-key header\n * ```\n */\nexport function createApiKey(clientKey: string, secretKey: string): string {\n if (!clientKey || !secretKey) {\n throw new Error('clientKey and secretKey are required.')\n }\n\n // Browser와 Node.js 모두 지원\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(`${clientKey}:${secretKey}`).toString('base64')\n }\n return btoa(`${clientKey}:${secretKey}`)\n}\n\n/**\n * Parses a Base64-encoded API key to extract clientKey and secretKey.\n *\n * @param apiKey - Base64-encoded API key\n * @returns Object containing clientKey and secretKey\n * @throws Error if API key is invalid\n *\n * @example\n * ```typescript\n * const { clientKey, secretKey } = parseApiKey(apiKey)\n * ```\n */\nexport function parseApiKey(apiKey: string): {\n clientKey: string\n secretKey: string\n} {\n if (!apiKey) {\n throw new Error('apiKey is required.')\n }\n\n try {\n let decoded: string\n if (typeof Buffer !== 'undefined') {\n decoded = Buffer.from(apiKey, 'base64').toString('utf-8')\n } else {\n decoded = atob(apiKey)\n }\n\n const colonIndex = decoded.indexOf(':')\n if (colonIndex === -1) {\n throw new Error('Invalid format: missing colon separator')\n }\n\n const clientKey = decoded.substring(0, colonIndex)\n const secretKey = decoded.substring(colonIndex + 1)\n\n if (!clientKey || !secretKey) {\n throw new Error('Invalid format: empty clientKey or secretKey')\n }\n\n return { clientKey, secretKey }\n } catch {\n throw new Error('Invalid API key. Expected Base64 encoded \"clientKey:secretKey\"')\n }\n}\n\nfunction debugLog(\n debug: boolean | DebugConfig | undefined,\n type: 'request' | 'response' | 'error',\n message: string,\n data?: unknown,\n) {\n if (!debug) return\n\n const shouldLog =\n debug === true ||\n (type === 'request' && (debug as DebugConfig).logRequests) ||\n (type === 'response' && (debug as DebugConfig).logResponses) ||\n (type === 'error' && (debug as DebugConfig).logErrors)\n\n if (shouldLog) {\n console.group(`[SDK ${type.toUpperCase()}] ${message}`)\n if (data) console.log(data)\n console.groupEnd()\n }\n}\n\nfunction getErrorSuggestion(status: number): string | undefined {\n if (status === 401) return 'Please check your authentication credentials.'\n if (status === 404) return 'The requested resource was not found.'\n if (status >= 500)\n return 'A server error occurred. Please try again later.'\n return undefined\n}\n\nasync function delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\nexport async function _fetch(\n url: string,\n options?: FetchOptions,\n): Promise<Response> {\n const {\n clientKey,\n secretKey,\n customerToken,\n timeout = DEFAULT_TIMEOUT,\n baseUrl = API_URLS.production,\n debug,\n retry,\n ...requestInit\n } = options || {}\n\n const retryConfig = {\n maxRetries: retry?.maxRetries ?? 3,\n retryableStatuses: retry?.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES,\n retryDelay:\n retry?.retryDelay ??\n ((attempt: number) => Math.min(1000 * 2 ** attempt, 10000)),\n }\n\n // Generate JWT once before retry loop (token valid for 1h)\n let authToken: string | undefined\n if (secretKey && clientKey) {\n authToken = await createServerToken(clientKey, secretKey)\n } else if (customerToken) {\n authToken = customerToken\n }\n\n let lastError: Error | undefined\n\n for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {\n try {\n const headers = new Headers(requestInit.headers)\n\n if (clientKey) {\n headers.set('X-Client-Key', clientKey)\n }\n\n if (authToken) {\n headers.set('Authorization', `Bearer ${authToken}`)\n }\n\n if (!headers.has('Content-Type') && requestInit.body && !(requestInit.body instanceof FormData)) {\n headers.set('Content-Type', 'application/json')\n }\n\n // Redact sensitive headers for debug logging\n const redactedHeaders = Object.fromEntries(headers.entries())\n if (redactedHeaders['authorization']) {\n const token = redactedHeaders['authorization']\n // Show only \"Bearer eyJ...****\" to aid debugging without exposing the full token\n redactedHeaders['authorization'] = token.length > 15\n ? `${token.slice(0, 15)}...****`\n : '****'\n }\n\n debugLog(debug, 'request', url, {\n method: requestInit.method || 'GET',\n headers: redactedHeaders,\n attempt: attempt + 1,\n })\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n const response = await fetch(`${baseUrl}${url}`, {\n ...requestInit,\n headers,\n signal: controller.signal,\n })\n\n clearTimeout(timeoutId)\n\n debugLog(debug, 'response', url, {\n status: response.status,\n statusText: response.statusText,\n headers: Object.fromEntries(response.headers.entries()),\n })\n\n if (!response.ok) {\n // Usage limit 429 — never retry (distinguished by X-Usage-Limit header)\n if (\n response.status === 429 &&\n response.headers.get('X-Usage-Limit')\n ) {\n const limit = parseInt(response.headers.get('X-Usage-Limit') || '0', 10)\n const current = parseInt(response.headers.get('X-Usage-Current') || '0', 10)\n const remaining = parseInt(response.headers.get('X-Usage-Remaining') || '0', 10)\n\n throw createUsageLimitError(\n `Monthly API usage limit exceeded (${current.toLocaleString()}/${limit.toLocaleString()})`,\n { limit, current, remaining },\n { url, method: requestInit.method || 'GET', attempt: attempt + 1 },\n 'Monthly API call limit exceeded. Please upgrade your plan.',\n 'Upgrade your tenant plan to increase the monthly API call limit.',\n )\n }\n\n // Never retry non-retryable statuses regardless of user config\n if (NON_RETRYABLE_STATUSES.includes(response.status)) {\n throw createNetworkError(\n `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n { url, method: requestInit.method || 'GET', attempt: attempt + 1 },\n `Request failed (status: ${response.status})`,\n getErrorSuggestion(response.status),\n )\n }\n\n const error = createNetworkError(\n `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n { url, method: requestInit.method || 'GET', attempt: attempt + 1 },\n `Request failed (status: ${response.status})`,\n getErrorSuggestion(response.status),\n )\n\n const method = (requestInit.method || 'GET').toUpperCase()\n if (\n attempt < retryConfig.maxRetries &&\n SAFE_METHODS.includes(method) &&\n retryConfig.retryableStatuses.includes(response.status)\n ) {\n lastError = error\n const retryDelay = retryConfig.retryDelay(attempt)\n debugLog(debug, 'error', `Retrying in ${retryDelay}ms...`, error)\n await delay(retryDelay)\n continue\n }\n\n throw error\n }\n\n return response\n } catch (error) {\n debugLog(debug, 'error', url, error)\n\n const method = (requestInit.method || 'GET').toUpperCase()\n const isSafe = SAFE_METHODS.includes(method)\n\n if (error instanceof Error && error.name === 'AbortError') {\n const timeoutError = createTimeoutError(\n `Request timed out after ${timeout}ms.`,\n { url, timeout, attempt: attempt + 1 },\n 'The request timed out.',\n 'Please check your network connection or try again later.',\n )\n\n if (isSafe && attempt < retryConfig.maxRetries) {\n lastError = timeoutError\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw timeoutError\n }\n\n if (error instanceof TypeError) {\n const networkError = createNetworkError(\n 'Network connection failed.',\n undefined,\n { url, originalError: error.message, attempt: attempt + 1 },\n 'Network connection failed.',\n 'Please check your internet connection and try again.',\n )\n\n if (isSafe && attempt < retryConfig.maxRetries) {\n lastError = networkError\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw networkError\n }\n\n if (error instanceof NetworkError || error instanceof TimeoutError) {\n if (\n isSafe &&\n attempt < retryConfig.maxRetries &&\n error.status &&\n !NON_RETRYABLE_STATUSES.includes(error.status) &&\n retryConfig.retryableStatuses.includes(error.status)\n ) {\n lastError = error\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw error\n }\n\n const unknownError = createNetworkError(\n error instanceof Error\n ? error.message\n : 'An unknown network error occurred.',\n undefined,\n { url, originalError: error, attempt: attempt + 1 },\n 'An unknown error occurred.',\n 'Please try again later.',\n )\n\n if (isSafe && attempt < retryConfig.maxRetries) {\n lastError = unknownError\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw unknownError\n }\n }\n\n throw lastError ?? new NetworkError('Request failed after retries')\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,kBAA8C;AAyC9C,SAAsB,kBACpB,WACA,WACA,YAAoB,MACH;AAAA;AACjB,QAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAEA,UAAM,SAAS,IAAI,YAAY,EAAE,OAAO,SAAS;AACjD,WAAO,IAAI,oBAAQ,EAAE,UAAU,CAAC,EAC7B,mBAAmB,EAAE,KAAK,QAAQ,CAAC,EACnC,YAAY,EACZ,kBAAkB,SAAS,EAC3B,KAAK,MAAM;AAAA,EAChB;AAAA;AAgBA,SAAsB,kBACpB,OACA,WACqB;AAAA;AACrB,QAAI,CAAC,SAAS,CAAC,WAAW;AACxB,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAEA,UAAM,SAAS,IAAI,YAAY,EAAE,OAAO,SAAS;AACjD,UAAM,EAAE,QAAQ,IAAI,UAAM,uBAAU,OAAO,QAAQ;AAAA,MACjD,YAAY,CAAC,OAAO;AAAA,IACtB,CAAC;AAED,QAAI,CAAC,QAAQ,aAAa,OAAO,QAAQ,cAAc,UAAU;AAC/D,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,WAAO;AAAA,MACL,WAAW,QAAQ;AAAA,MACnB,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAAA;AAgBO,SAAS,kBAAkB,OAA2B;AAC3D,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,oBAAoB;AAAA,EACtC;AAEA,QAAM,cAAU,uBAAU,KAAK;AAE/B,MAAI,CAAC,QAAQ,aAAa,OAAO,QAAQ,cAAc,UAAU;AAC/D,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AAEA,SAAO;AAAA,IACL,WAAW,QAAQ;AAAA,IACnB,KAAK,QAAQ;AAAA,IACb,KAAK,QAAQ;AAAA,EACf;AACF;AAoBO,SAAS,aAAa,WAAmB,WAA2B;AACzE,MAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACzD;AAGA,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,OAAO,KAAK,GAAG,SAAS,IAAI,SAAS,EAAE,EAAE,SAAS,QAAQ;AAAA,EACnE;AACA,SAAO,KAAK,GAAG,SAAS,IAAI,SAAS,EAAE;AACzC;AAcO,SAAS,YAAY,QAG1B;AACA,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,qBAAqB;AAAA,EACvC;AAEA,MAAI;AACF,QAAI;AACJ,QAAI,OAAO,WAAW,aAAa;AACjC,gBAAU,OAAO,KAAK,QAAQ,QAAQ,EAAE,SAAS,OAAO;AAAA,IAC1D,OAAO;AACL,gBAAU,KAAK,MAAM;AAAA,IACvB;AAEA,UAAM,aAAa,QAAQ,QAAQ,GAAG;AACtC,QAAI,eAAe,IAAI;AACrB,YAAM,IAAI,MAAM,yCAAyC;AAAA,IAC3D;AAEA,UAAM,YAAY,QAAQ,UAAU,GAAG,UAAU;AACjD,UAAM,YAAY,QAAQ,UAAU,aAAa,CAAC;AAElD,QAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAChE;AAEA,WAAO,EAAE,WAAW,UAAU;AAAA,EAChC,SAAQ;AACN,UAAM,IAAI,MAAM,gEAAgE;AAAA,EAClF;AACF;","names":[]}
|
package/dist/auth.d.cts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { J as JwtPayload, q as createApiKey, o as createServerToken, p as decodeServerToken, r as parseApiKey, v as verifyServerToken } from './auth-
|
|
1
|
+
export { J as JwtPayload, q as createApiKey, o as createServerToken, p as decodeServerToken, r as parseApiKey, v as verifyServerToken } from './auth-3qWmYUln.cjs';
|
|
2
2
|
import 'payload';
|
|
3
|
-
import './payload-types-
|
|
3
|
+
import './payload-types-Bpi16SHO.cjs';
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { J as JwtPayload, q as createApiKey, o as createServerToken, p as decodeServerToken, r as parseApiKey, v as verifyServerToken } from './auth-
|
|
1
|
+
export { J as JwtPayload, q as createApiKey, o as createServerToken, p as decodeServerToken, r as parseApiKey, v as verifyServerToken } from './auth-BfGmV75P.js';
|
|
2
2
|
import 'payload';
|
|
3
|
-
import './payload-types-
|
|
3
|
+
import './payload-types-Bpi16SHO.js';
|
package/dist/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/internal/utils/index.ts"],"sourcesContent":["import { SignJWT, jwtVerify, decodeJwt } from 'jose'\nimport { createNetworkError, createUsageLimitError, createTimeoutError, TimeoutError, NetworkError } from '../errors'\nimport type { DebugConfig, RetryConfig } from '../../client/types'\nimport { API_URLS } from '../../client/types'\n\nconst DEFAULT_TIMEOUT = 30000\nconst DEFAULT_RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504]\nconst NON_RETRYABLE_STATUSES = [401, 403, 404, 422]\nconst SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']\n\nexport interface FetchOptions extends RequestInit {\n clientKey?: string\n secretKey?: string\n customerToken?: string\n timeout?: number\n baseUrl?: string\n debug?: boolean | DebugConfig\n retry?: RetryConfig\n}\n\nexport interface JwtPayload {\n clientKey: string\n iat?: number\n exp?: number\n}\n\n/**\n * Creates a JWT token for server-side authentication.\n * The token is valid for 1 hour by default.\n *\n * @param clientKey - Client API key\n * @param secretKey - Secret key used for signing\n * @param expiresIn - Token expiration time (default: '1h')\n * @returns Promise<string> JWT token\n *\n * @example\n * ```typescript\n * const token = await createServerToken('client-key', 'secret-key')\n * // Use in Authorization header: `Bearer ${token}`\n * ```\n */\nexport async function createServerToken(\n clientKey: string,\n secretKey: string,\n expiresIn: string = '1h',\n): Promise<string> {\n if (!clientKey || !secretKey) {\n throw new Error('clientKey and secretKey are required.')\n }\n\n const secret = new TextEncoder().encode(secretKey)\n return new SignJWT({ clientKey })\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(expiresIn)\n .sign(secret)\n}\n\n/**\n * Verifies a JWT token and returns the payload.\n *\n * @param token - JWT token to verify\n * @param secretKey - Secret key used for verification\n * @returns Promise<JwtPayload> Verified payload containing clientKey\n * @throws Error if token is invalid or expired\n *\n * @example\n * ```typescript\n * const payload = await verifyServerToken(token, 'secret-key')\n * console.log(payload.clientKey)\n * ```\n */\nexport async function verifyServerToken(\n token: string,\n secretKey: string,\n): Promise<JwtPayload> {\n if (!token || !secretKey) {\n throw new Error('token and secretKey are required.')\n }\n\n const secret = new TextEncoder().encode(secretKey)\n const { payload } = await jwtVerify(token, secret, {\n algorithms: ['HS256'],\n })\n\n if (!payload.clientKey || typeof payload.clientKey !== 'string') {\n throw new Error('Invalid token payload: clientKey is missing')\n }\n\n return {\n clientKey: payload.clientKey as string,\n iat: payload.iat,\n exp: payload.exp,\n }\n}\n\n/**\n * Decodes a JWT token without verification.\n * WARNING: Use this only when you need to inspect token contents.\n * Always use verifyServerToken for authentication.\n *\n * @param token - JWT token to decode\n * @returns JwtPayload Decoded payload (unverified)\n *\n * @example\n * ```typescript\n * const payload = decodeServerToken(token)\n * console.log(payload.clientKey) // Unverified!\n * ```\n */\nexport function decodeServerToken(token: string): JwtPayload {\n if (!token) {\n throw new Error('token is required.')\n }\n\n const payload = decodeJwt(token)\n\n if (!payload.clientKey || typeof payload.clientKey !== 'string') {\n throw new Error('Invalid token payload: clientKey is missing')\n }\n\n return {\n clientKey: payload.clientKey as string,\n iat: payload.iat,\n exp: payload.exp,\n }\n}\n\n// ============================================================================\n// API Key Utilities\n// ============================================================================\n\n/**\n * Creates a Base64-encoded API key from clientKey and secretKey.\n * Use this for MCP server authentication.\n *\n * @param clientKey - Client API key\n * @param secretKey - Secret key\n * @returns Base64-encoded API key\n *\n * @example\n * ```typescript\n * const apiKey = createApiKey('client-key', 'secret-key')\n * // Use in x-api-key header\n * ```\n */\nexport function createApiKey(clientKey: string, secretKey: string): string {\n if (!clientKey || !secretKey) {\n throw new Error('clientKey and secretKey are required.')\n }\n\n // Browser와 Node.js 모두 지원\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(`${clientKey}:${secretKey}`).toString('base64')\n }\n return btoa(`${clientKey}:${secretKey}`)\n}\n\n/**\n * Parses a Base64-encoded API key to extract clientKey and secretKey.\n *\n * @param apiKey - Base64-encoded API key\n * @returns Object containing clientKey and secretKey\n * @throws Error if API key is invalid\n *\n * @example\n * ```typescript\n * const { clientKey, secretKey } = parseApiKey(apiKey)\n * ```\n */\nexport function parseApiKey(apiKey: string): {\n clientKey: string\n secretKey: string\n} {\n if (!apiKey) {\n throw new Error('apiKey is required.')\n }\n\n try {\n let decoded: string\n if (typeof Buffer !== 'undefined') {\n decoded = Buffer.from(apiKey, 'base64').toString('utf-8')\n } else {\n decoded = atob(apiKey)\n }\n\n const colonIndex = decoded.indexOf(':')\n if (colonIndex === -1) {\n throw new Error('Invalid format: missing colon separator')\n }\n\n const clientKey = decoded.substring(0, colonIndex)\n const secretKey = decoded.substring(colonIndex + 1)\n\n if (!clientKey || !secretKey) {\n throw new Error('Invalid format: empty clientKey or secretKey')\n }\n\n return { clientKey, secretKey }\n } catch {\n throw new Error('Invalid API key. Expected Base64 encoded \"clientKey:secretKey\"')\n }\n}\n\nfunction debugLog(\n debug: boolean | DebugConfig | undefined,\n type: 'request' | 'response' | 'error',\n message: string,\n data?: unknown,\n) {\n if (!debug) return\n\n const shouldLog =\n debug === true ||\n (type === 'request' && (debug as DebugConfig).logRequests) ||\n (type === 'response' && (debug as DebugConfig).logResponses) ||\n (type === 'error' && (debug as DebugConfig).logErrors)\n\n if (shouldLog) {\n console.group(`[SDK ${type.toUpperCase()}] ${message}`)\n if (data) console.log(data)\n console.groupEnd()\n }\n}\n\nfunction getErrorSuggestion(status: number): string | undefined {\n if (status === 401) return 'Please check your authentication credentials.'\n if (status === 404) return 'The requested resource was not found.'\n if (status >= 500)\n return 'A server error occurred. Please try again later.'\n return undefined\n}\n\nasync function delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\nexport async function _fetch(\n url: string,\n options?: FetchOptions,\n): Promise<Response> {\n const {\n clientKey,\n secretKey,\n customerToken,\n timeout = DEFAULT_TIMEOUT,\n baseUrl = API_URLS.production,\n debug,\n retry,\n ...requestInit\n } = options || {}\n\n const retryConfig = {\n maxRetries: retry?.maxRetries ?? 3,\n retryableStatuses: retry?.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES,\n retryDelay:\n retry?.retryDelay ??\n ((attempt: number) => Math.min(1000 * 2 ** attempt, 10000)),\n }\n\n // Generate JWT once before retry loop (token valid for 1h)\n let authToken: string | undefined\n if (secretKey && clientKey) {\n authToken = await createServerToken(clientKey, secretKey)\n } else if (customerToken) {\n authToken = customerToken\n }\n\n let lastError: Error | undefined\n\n for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {\n try {\n const headers = new Headers(requestInit.headers)\n\n if (clientKey) {\n headers.set('X-Client-Key', clientKey)\n }\n\n if (authToken) {\n headers.set('Authorization', `Bearer ${authToken}`)\n }\n\n if (!headers.has('Content-Type') && requestInit.body) {\n headers.set('Content-Type', 'application/json')\n }\n\n // Redact sensitive headers for debug logging\n const redactedHeaders = Object.fromEntries(headers.entries())\n if (redactedHeaders['authorization']) {\n const token = redactedHeaders['authorization']\n // Show only \"Bearer eyJ...****\" to aid debugging without exposing the full token\n redactedHeaders['authorization'] = token.length > 15\n ? `${token.slice(0, 15)}...****`\n : '****'\n }\n\n debugLog(debug, 'request', url, {\n method: requestInit.method || 'GET',\n headers: redactedHeaders,\n attempt: attempt + 1,\n })\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n const response = await fetch(`${baseUrl}${url}`, {\n ...requestInit,\n headers,\n signal: controller.signal,\n })\n\n clearTimeout(timeoutId)\n\n debugLog(debug, 'response', url, {\n status: response.status,\n statusText: response.statusText,\n headers: Object.fromEntries(response.headers.entries()),\n })\n\n if (!response.ok) {\n // Usage limit 429 — never retry (distinguished by X-Usage-Limit header)\n if (\n response.status === 429 &&\n response.headers.get('X-Usage-Limit')\n ) {\n const limit = parseInt(response.headers.get('X-Usage-Limit') || '0', 10)\n const current = parseInt(response.headers.get('X-Usage-Current') || '0', 10)\n const remaining = parseInt(response.headers.get('X-Usage-Remaining') || '0', 10)\n\n throw createUsageLimitError(\n `Monthly API usage limit exceeded (${current.toLocaleString()}/${limit.toLocaleString()})`,\n { limit, current, remaining },\n { url, method: requestInit.method || 'GET', attempt: attempt + 1 },\n 'Monthly API call limit exceeded. Please upgrade your plan.',\n 'Upgrade your tenant plan to increase the monthly API call limit.',\n )\n }\n\n // Never retry non-retryable statuses regardless of user config\n if (NON_RETRYABLE_STATUSES.includes(response.status)) {\n throw createNetworkError(\n `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n { url, method: requestInit.method || 'GET', attempt: attempt + 1 },\n `Request failed (status: ${response.status})`,\n getErrorSuggestion(response.status),\n )\n }\n\n const error = createNetworkError(\n `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n { url, method: requestInit.method || 'GET', attempt: attempt + 1 },\n `Request failed (status: ${response.status})`,\n getErrorSuggestion(response.status),\n )\n\n const method = (requestInit.method || 'GET').toUpperCase()\n if (\n attempt < retryConfig.maxRetries &&\n SAFE_METHODS.includes(method) &&\n retryConfig.retryableStatuses.includes(response.status)\n ) {\n lastError = error\n const retryDelay = retryConfig.retryDelay(attempt)\n debugLog(debug, 'error', `Retrying in ${retryDelay}ms...`, error)\n await delay(retryDelay)\n continue\n }\n\n throw error\n }\n\n return response\n } catch (error) {\n debugLog(debug, 'error', url, error)\n\n const method = (requestInit.method || 'GET').toUpperCase()\n const isSafe = SAFE_METHODS.includes(method)\n\n if (error instanceof Error && error.name === 'AbortError') {\n const timeoutError = createTimeoutError(\n `Request timed out after ${timeout}ms.`,\n { url, timeout, attempt: attempt + 1 },\n 'The request timed out.',\n 'Please check your network connection or try again later.',\n )\n\n if (isSafe && attempt < retryConfig.maxRetries) {\n lastError = timeoutError\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw timeoutError\n }\n\n if (error instanceof TypeError) {\n const networkError = createNetworkError(\n 'Network connection failed.',\n undefined,\n { url, originalError: error.message, attempt: attempt + 1 },\n 'Network connection failed.',\n 'Please check your internet connection and try again.',\n )\n\n if (isSafe && attempt < retryConfig.maxRetries) {\n lastError = networkError\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw networkError\n }\n\n if (error instanceof NetworkError || error instanceof TimeoutError) {\n if (\n isSafe &&\n attempt < retryConfig.maxRetries &&\n error.status &&\n !NON_RETRYABLE_STATUSES.includes(error.status) &&\n retryConfig.retryableStatuses.includes(error.status)\n ) {\n lastError = error\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw error\n }\n\n const unknownError = createNetworkError(\n error instanceof Error\n ? error.message\n : 'An unknown network error occurred.',\n undefined,\n { url, originalError: error, attempt: attempt + 1 },\n 'An unknown error occurred.',\n 'Please try again later.',\n )\n\n if (isSafe && attempt < retryConfig.maxRetries) {\n lastError = unknownError\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw unknownError\n }\n }\n\n throw lastError ?? new NetworkError('Request failed after retries')\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,SAAS,WAAW,iBAAiB;AAyC9C,SAAsB,kBACpB,WACA,WACA,YAAoB,MACH;AAAA;AACjB,QAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAEA,UAAM,SAAS,IAAI,YAAY,EAAE,OAAO,SAAS;AACjD,WAAO,IAAI,QAAQ,EAAE,UAAU,CAAC,EAC7B,mBAAmB,EAAE,KAAK,QAAQ,CAAC,EACnC,YAAY,EACZ,kBAAkB,SAAS,EAC3B,KAAK,MAAM;AAAA,EAChB;AAAA;AAgBA,SAAsB,kBACpB,OACA,WACqB;AAAA;AACrB,QAAI,CAAC,SAAS,CAAC,WAAW;AACxB,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAEA,UAAM,SAAS,IAAI,YAAY,EAAE,OAAO,SAAS;AACjD,UAAM,EAAE,QAAQ,IAAI,MAAM,UAAU,OAAO,QAAQ;AAAA,MACjD,YAAY,CAAC,OAAO;AAAA,IACtB,CAAC;AAED,QAAI,CAAC,QAAQ,aAAa,OAAO,QAAQ,cAAc,UAAU;AAC/D,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,WAAO;AAAA,MACL,WAAW,QAAQ;AAAA,MACnB,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAAA;AAgBO,SAAS,kBAAkB,OAA2B;AAC3D,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,oBAAoB;AAAA,EACtC;AAEA,QAAM,UAAU,UAAU,KAAK;AAE/B,MAAI,CAAC,QAAQ,aAAa,OAAO,QAAQ,cAAc,UAAU;AAC/D,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AAEA,SAAO;AAAA,IACL,WAAW,QAAQ;AAAA,IACnB,KAAK,QAAQ;AAAA,IACb,KAAK,QAAQ;AAAA,EACf;AACF;AAoBO,SAAS,aAAa,WAAmB,WAA2B;AACzE,MAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACzD;AAGA,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,OAAO,KAAK,GAAG,SAAS,IAAI,SAAS,EAAE,EAAE,SAAS,QAAQ;AAAA,EACnE;AACA,SAAO,KAAK,GAAG,SAAS,IAAI,SAAS,EAAE;AACzC;AAcO,SAAS,YAAY,QAG1B;AACA,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,qBAAqB;AAAA,EACvC;AAEA,MAAI;AACF,QAAI;AACJ,QAAI,OAAO,WAAW,aAAa;AACjC,gBAAU,OAAO,KAAK,QAAQ,QAAQ,EAAE,SAAS,OAAO;AAAA,IAC1D,OAAO;AACL,gBAAU,KAAK,MAAM;AAAA,IACvB;AAEA,UAAM,aAAa,QAAQ,QAAQ,GAAG;AACtC,QAAI,eAAe,IAAI;AACrB,YAAM,IAAI,MAAM,yCAAyC;AAAA,IAC3D;AAEA,UAAM,YAAY,QAAQ,UAAU,GAAG,UAAU;AACjD,UAAM,YAAY,QAAQ,UAAU,aAAa,CAAC;AAElD,QAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAChE;AAEA,WAAO,EAAE,WAAW,UAAU;AAAA,EAChC,SAAQ;AACN,UAAM,IAAI,MAAM,gEAAgE;AAAA,EAClF;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/core/internal/utils/index.ts"],"sourcesContent":["import { SignJWT, jwtVerify, decodeJwt } from 'jose'\nimport { createNetworkError, createUsageLimitError, createTimeoutError, TimeoutError, NetworkError } from '../errors'\nimport type { DebugConfig, RetryConfig } from '../../client/types'\nimport { API_URLS } from '../../client/types'\n\nconst DEFAULT_TIMEOUT = 30000\nconst DEFAULT_RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504]\nconst NON_RETRYABLE_STATUSES = [401, 403, 404, 422]\nconst SAFE_METHODS = ['GET', 'HEAD', 'OPTIONS']\n\nexport interface FetchOptions extends RequestInit {\n clientKey?: string\n secretKey?: string\n customerToken?: string\n timeout?: number\n baseUrl?: string\n debug?: boolean | DebugConfig\n retry?: RetryConfig\n}\n\nexport interface JwtPayload {\n clientKey: string\n iat?: number\n exp?: number\n}\n\n/**\n * Creates a JWT token for server-side authentication.\n * The token is valid for 1 hour by default.\n *\n * @param clientKey - Client API key\n * @param secretKey - Secret key used for signing\n * @param expiresIn - Token expiration time (default: '1h')\n * @returns Promise<string> JWT token\n *\n * @example\n * ```typescript\n * const token = await createServerToken('client-key', 'secret-key')\n * // Use in Authorization header: `Bearer ${token}`\n * ```\n */\nexport async function createServerToken(\n clientKey: string,\n secretKey: string,\n expiresIn: string = '1h',\n): Promise<string> {\n if (!clientKey || !secretKey) {\n throw new Error('clientKey and secretKey are required.')\n }\n\n const secret = new TextEncoder().encode(secretKey)\n return new SignJWT({ clientKey })\n .setProtectedHeader({ alg: 'HS256' })\n .setIssuedAt()\n .setExpirationTime(expiresIn)\n .sign(secret)\n}\n\n/**\n * Verifies a JWT token and returns the payload.\n *\n * @param token - JWT token to verify\n * @param secretKey - Secret key used for verification\n * @returns Promise<JwtPayload> Verified payload containing clientKey\n * @throws Error if token is invalid or expired\n *\n * @example\n * ```typescript\n * const payload = await verifyServerToken(token, 'secret-key')\n * console.log(payload.clientKey)\n * ```\n */\nexport async function verifyServerToken(\n token: string,\n secretKey: string,\n): Promise<JwtPayload> {\n if (!token || !secretKey) {\n throw new Error('token and secretKey are required.')\n }\n\n const secret = new TextEncoder().encode(secretKey)\n const { payload } = await jwtVerify(token, secret, {\n algorithms: ['HS256'],\n })\n\n if (!payload.clientKey || typeof payload.clientKey !== 'string') {\n throw new Error('Invalid token payload: clientKey is missing')\n }\n\n return {\n clientKey: payload.clientKey as string,\n iat: payload.iat,\n exp: payload.exp,\n }\n}\n\n/**\n * Decodes a JWT token without verification.\n * WARNING: Use this only when you need to inspect token contents.\n * Always use verifyServerToken for authentication.\n *\n * @param token - JWT token to decode\n * @returns JwtPayload Decoded payload (unverified)\n *\n * @example\n * ```typescript\n * const payload = decodeServerToken(token)\n * console.log(payload.clientKey) // Unverified!\n * ```\n */\nexport function decodeServerToken(token: string): JwtPayload {\n if (!token) {\n throw new Error('token is required.')\n }\n\n const payload = decodeJwt(token)\n\n if (!payload.clientKey || typeof payload.clientKey !== 'string') {\n throw new Error('Invalid token payload: clientKey is missing')\n }\n\n return {\n clientKey: payload.clientKey as string,\n iat: payload.iat,\n exp: payload.exp,\n }\n}\n\n// ============================================================================\n// API Key Utilities\n// ============================================================================\n\n/**\n * Creates a Base64-encoded API key from clientKey and secretKey.\n * Use this for MCP server authentication.\n *\n * @param clientKey - Client API key\n * @param secretKey - Secret key\n * @returns Base64-encoded API key\n *\n * @example\n * ```typescript\n * const apiKey = createApiKey('client-key', 'secret-key')\n * // Use in x-api-key header\n * ```\n */\nexport function createApiKey(clientKey: string, secretKey: string): string {\n if (!clientKey || !secretKey) {\n throw new Error('clientKey and secretKey are required.')\n }\n\n // Browser와 Node.js 모두 지원\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(`${clientKey}:${secretKey}`).toString('base64')\n }\n return btoa(`${clientKey}:${secretKey}`)\n}\n\n/**\n * Parses a Base64-encoded API key to extract clientKey and secretKey.\n *\n * @param apiKey - Base64-encoded API key\n * @returns Object containing clientKey and secretKey\n * @throws Error if API key is invalid\n *\n * @example\n * ```typescript\n * const { clientKey, secretKey } = parseApiKey(apiKey)\n * ```\n */\nexport function parseApiKey(apiKey: string): {\n clientKey: string\n secretKey: string\n} {\n if (!apiKey) {\n throw new Error('apiKey is required.')\n }\n\n try {\n let decoded: string\n if (typeof Buffer !== 'undefined') {\n decoded = Buffer.from(apiKey, 'base64').toString('utf-8')\n } else {\n decoded = atob(apiKey)\n }\n\n const colonIndex = decoded.indexOf(':')\n if (colonIndex === -1) {\n throw new Error('Invalid format: missing colon separator')\n }\n\n const clientKey = decoded.substring(0, colonIndex)\n const secretKey = decoded.substring(colonIndex + 1)\n\n if (!clientKey || !secretKey) {\n throw new Error('Invalid format: empty clientKey or secretKey')\n }\n\n return { clientKey, secretKey }\n } catch {\n throw new Error('Invalid API key. Expected Base64 encoded \"clientKey:secretKey\"')\n }\n}\n\nfunction debugLog(\n debug: boolean | DebugConfig | undefined,\n type: 'request' | 'response' | 'error',\n message: string,\n data?: unknown,\n) {\n if (!debug) return\n\n const shouldLog =\n debug === true ||\n (type === 'request' && (debug as DebugConfig).logRequests) ||\n (type === 'response' && (debug as DebugConfig).logResponses) ||\n (type === 'error' && (debug as DebugConfig).logErrors)\n\n if (shouldLog) {\n console.group(`[SDK ${type.toUpperCase()}] ${message}`)\n if (data) console.log(data)\n console.groupEnd()\n }\n}\n\nfunction getErrorSuggestion(status: number): string | undefined {\n if (status === 401) return 'Please check your authentication credentials.'\n if (status === 404) return 'The requested resource was not found.'\n if (status >= 500)\n return 'A server error occurred. Please try again later.'\n return undefined\n}\n\nasync function delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\nexport async function _fetch(\n url: string,\n options?: FetchOptions,\n): Promise<Response> {\n const {\n clientKey,\n secretKey,\n customerToken,\n timeout = DEFAULT_TIMEOUT,\n baseUrl = API_URLS.production,\n debug,\n retry,\n ...requestInit\n } = options || {}\n\n const retryConfig = {\n maxRetries: retry?.maxRetries ?? 3,\n retryableStatuses: retry?.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES,\n retryDelay:\n retry?.retryDelay ??\n ((attempt: number) => Math.min(1000 * 2 ** attempt, 10000)),\n }\n\n // Generate JWT once before retry loop (token valid for 1h)\n let authToken: string | undefined\n if (secretKey && clientKey) {\n authToken = await createServerToken(clientKey, secretKey)\n } else if (customerToken) {\n authToken = customerToken\n }\n\n let lastError: Error | undefined\n\n for (let attempt = 0; attempt <= retryConfig.maxRetries; attempt++) {\n try {\n const headers = new Headers(requestInit.headers)\n\n if (clientKey) {\n headers.set('X-Client-Key', clientKey)\n }\n\n if (authToken) {\n headers.set('Authorization', `Bearer ${authToken}`)\n }\n\n if (!headers.has('Content-Type') && requestInit.body && !(requestInit.body instanceof FormData)) {\n headers.set('Content-Type', 'application/json')\n }\n\n // Redact sensitive headers for debug logging\n const redactedHeaders = Object.fromEntries(headers.entries())\n if (redactedHeaders['authorization']) {\n const token = redactedHeaders['authorization']\n // Show only \"Bearer eyJ...****\" to aid debugging without exposing the full token\n redactedHeaders['authorization'] = token.length > 15\n ? `${token.slice(0, 15)}...****`\n : '****'\n }\n\n debugLog(debug, 'request', url, {\n method: requestInit.method || 'GET',\n headers: redactedHeaders,\n attempt: attempt + 1,\n })\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n const response = await fetch(`${baseUrl}${url}`, {\n ...requestInit,\n headers,\n signal: controller.signal,\n })\n\n clearTimeout(timeoutId)\n\n debugLog(debug, 'response', url, {\n status: response.status,\n statusText: response.statusText,\n headers: Object.fromEntries(response.headers.entries()),\n })\n\n if (!response.ok) {\n // Usage limit 429 — never retry (distinguished by X-Usage-Limit header)\n if (\n response.status === 429 &&\n response.headers.get('X-Usage-Limit')\n ) {\n const limit = parseInt(response.headers.get('X-Usage-Limit') || '0', 10)\n const current = parseInt(response.headers.get('X-Usage-Current') || '0', 10)\n const remaining = parseInt(response.headers.get('X-Usage-Remaining') || '0', 10)\n\n throw createUsageLimitError(\n `Monthly API usage limit exceeded (${current.toLocaleString()}/${limit.toLocaleString()})`,\n { limit, current, remaining },\n { url, method: requestInit.method || 'GET', attempt: attempt + 1 },\n 'Monthly API call limit exceeded. Please upgrade your plan.',\n 'Upgrade your tenant plan to increase the monthly API call limit.',\n )\n }\n\n // Never retry non-retryable statuses regardless of user config\n if (NON_RETRYABLE_STATUSES.includes(response.status)) {\n throw createNetworkError(\n `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n { url, method: requestInit.method || 'GET', attempt: attempt + 1 },\n `Request failed (status: ${response.status})`,\n getErrorSuggestion(response.status),\n )\n }\n\n const error = createNetworkError(\n `HTTP ${response.status}: ${response.statusText}`,\n response.status,\n { url, method: requestInit.method || 'GET', attempt: attempt + 1 },\n `Request failed (status: ${response.status})`,\n getErrorSuggestion(response.status),\n )\n\n const method = (requestInit.method || 'GET').toUpperCase()\n if (\n attempt < retryConfig.maxRetries &&\n SAFE_METHODS.includes(method) &&\n retryConfig.retryableStatuses.includes(response.status)\n ) {\n lastError = error\n const retryDelay = retryConfig.retryDelay(attempt)\n debugLog(debug, 'error', `Retrying in ${retryDelay}ms...`, error)\n await delay(retryDelay)\n continue\n }\n\n throw error\n }\n\n return response\n } catch (error) {\n debugLog(debug, 'error', url, error)\n\n const method = (requestInit.method || 'GET').toUpperCase()\n const isSafe = SAFE_METHODS.includes(method)\n\n if (error instanceof Error && error.name === 'AbortError') {\n const timeoutError = createTimeoutError(\n `Request timed out after ${timeout}ms.`,\n { url, timeout, attempt: attempt + 1 },\n 'The request timed out.',\n 'Please check your network connection or try again later.',\n )\n\n if (isSafe && attempt < retryConfig.maxRetries) {\n lastError = timeoutError\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw timeoutError\n }\n\n if (error instanceof TypeError) {\n const networkError = createNetworkError(\n 'Network connection failed.',\n undefined,\n { url, originalError: error.message, attempt: attempt + 1 },\n 'Network connection failed.',\n 'Please check your internet connection and try again.',\n )\n\n if (isSafe && attempt < retryConfig.maxRetries) {\n lastError = networkError\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw networkError\n }\n\n if (error instanceof NetworkError || error instanceof TimeoutError) {\n if (\n isSafe &&\n attempt < retryConfig.maxRetries &&\n error.status &&\n !NON_RETRYABLE_STATUSES.includes(error.status) &&\n retryConfig.retryableStatuses.includes(error.status)\n ) {\n lastError = error\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw error\n }\n\n const unknownError = createNetworkError(\n error instanceof Error\n ? error.message\n : 'An unknown network error occurred.',\n undefined,\n { url, originalError: error, attempt: attempt + 1 },\n 'An unknown error occurred.',\n 'Please try again later.',\n )\n\n if (isSafe && attempt < retryConfig.maxRetries) {\n lastError = unknownError\n await delay(retryConfig.retryDelay(attempt))\n continue\n }\n\n throw unknownError\n }\n }\n\n throw lastError ?? new NetworkError('Request failed after retries')\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,SAAS,SAAS,WAAW,iBAAiB;AAyC9C,SAAsB,kBACpB,WACA,WACA,YAAoB,MACH;AAAA;AACjB,QAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,YAAM,IAAI,MAAM,uCAAuC;AAAA,IACzD;AAEA,UAAM,SAAS,IAAI,YAAY,EAAE,OAAO,SAAS;AACjD,WAAO,IAAI,QAAQ,EAAE,UAAU,CAAC,EAC7B,mBAAmB,EAAE,KAAK,QAAQ,CAAC,EACnC,YAAY,EACZ,kBAAkB,SAAS,EAC3B,KAAK,MAAM;AAAA,EAChB;AAAA;AAgBA,SAAsB,kBACpB,OACA,WACqB;AAAA;AACrB,QAAI,CAAC,SAAS,CAAC,WAAW;AACxB,YAAM,IAAI,MAAM,mCAAmC;AAAA,IACrD;AAEA,UAAM,SAAS,IAAI,YAAY,EAAE,OAAO,SAAS;AACjD,UAAM,EAAE,QAAQ,IAAI,MAAM,UAAU,OAAO,QAAQ;AAAA,MACjD,YAAY,CAAC,OAAO;AAAA,IACtB,CAAC;AAED,QAAI,CAAC,QAAQ,aAAa,OAAO,QAAQ,cAAc,UAAU;AAC/D,YAAM,IAAI,MAAM,6CAA6C;AAAA,IAC/D;AAEA,WAAO;AAAA,MACL,WAAW,QAAQ;AAAA,MACnB,KAAK,QAAQ;AAAA,MACb,KAAK,QAAQ;AAAA,IACf;AAAA,EACF;AAAA;AAgBO,SAAS,kBAAkB,OAA2B;AAC3D,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,oBAAoB;AAAA,EACtC;AAEA,QAAM,UAAU,UAAU,KAAK;AAE/B,MAAI,CAAC,QAAQ,aAAa,OAAO,QAAQ,cAAc,UAAU;AAC/D,UAAM,IAAI,MAAM,6CAA6C;AAAA,EAC/D;AAEA,SAAO;AAAA,IACL,WAAW,QAAQ;AAAA,IACnB,KAAK,QAAQ;AAAA,IACb,KAAK,QAAQ;AAAA,EACf;AACF;AAoBO,SAAS,aAAa,WAAmB,WAA2B;AACzE,MAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACzD;AAGA,MAAI,OAAO,WAAW,aAAa;AACjC,WAAO,OAAO,KAAK,GAAG,SAAS,IAAI,SAAS,EAAE,EAAE,SAAS,QAAQ;AAAA,EACnE;AACA,SAAO,KAAK,GAAG,SAAS,IAAI,SAAS,EAAE;AACzC;AAcO,SAAS,YAAY,QAG1B;AACA,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI,MAAM,qBAAqB;AAAA,EACvC;AAEA,MAAI;AACF,QAAI;AACJ,QAAI,OAAO,WAAW,aAAa;AACjC,gBAAU,OAAO,KAAK,QAAQ,QAAQ,EAAE,SAAS,OAAO;AAAA,IAC1D,OAAO;AACL,gBAAU,KAAK,MAAM;AAAA,IACvB;AAEA,UAAM,aAAa,QAAQ,QAAQ,GAAG;AACtC,QAAI,eAAe,IAAI;AACrB,YAAM,IAAI,MAAM,yCAAyC;AAAA,IAC3D;AAEA,UAAM,YAAY,QAAQ,UAAU,GAAG,UAAU;AACjD,UAAM,YAAY,QAAQ,UAAU,aAAa,CAAC;AAElD,QAAI,CAAC,aAAa,CAAC,WAAW;AAC5B,YAAM,IAAI,MAAM,8CAA8C;AAAA,IAChE;AAEA,WAAO,EAAE,WAAW,UAAU;AAAA,EAChC,SAAQ;AACN,UAAM,IAAI,MAAM,gEAAgE;AAAA,EAClF;AACF;","names":[]}
|
package/dist/components.d.cts
CHANGED
|
@@ -2,7 +2,7 @@ import React, { CSSProperties } from 'react';
|
|
|
2
2
|
import { SerializedLinkNode, SerializedBlockNode } from '@payloadcms/richtext-lexical';
|
|
3
3
|
import { SerializedEditorState, SerializedLexicalNode } from '@payloadcms/richtext-lexical/lexical';
|
|
4
4
|
import { JSXConverter } from '@payloadcms/richtext-lexical/react';
|
|
5
|
-
import { F as Form } from './payload-types-
|
|
5
|
+
import { F as Form } from './payload-types-Bpi16SHO.cjs';
|
|
6
6
|
import { I as ImageData } from './image-TT8lTsk5.cjs';
|
|
7
7
|
|
|
8
8
|
type RichTextData = SerializedEditorState<SerializedLexicalNode>;
|
package/dist/components.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import React, { CSSProperties } from 'react';
|
|
|
2
2
|
import { SerializedLinkNode, SerializedBlockNode } from '@payloadcms/richtext-lexical';
|
|
3
3
|
import { SerializedEditorState, SerializedLexicalNode } from '@payloadcms/richtext-lexical/lexical';
|
|
4
4
|
import { JSXConverter } from '@payloadcms/richtext-lexical/react';
|
|
5
|
-
import { F as Form } from './payload-types-
|
|
5
|
+
import { F as Form } from './payload-types-Bpi16SHO.js';
|
|
6
6
|
import { I as ImageData } from './image-TT8lTsk5.js';
|
|
7
7
|
|
|
8
8
|
type RichTextData = SerializedEditorState<SerializedLexicalNode>;
|
package/dist/index.cjs
CHANGED
|
@@ -385,7 +385,7 @@ function _fetch(url, options) {
|
|
|
385
385
|
if (authToken) {
|
|
386
386
|
headers.set("Authorization", `Bearer ${authToken}`);
|
|
387
387
|
}
|
|
388
|
-
if (!headers.has("Content-Type") && requestInit.body) {
|
|
388
|
+
if (!headers.has("Content-Type") && requestInit.body && !(requestInit.body instanceof FormData)) {
|
|
389
389
|
headers.set("Content-Type", "application/json");
|
|
390
390
|
}
|
|
391
391
|
const redactedHeaders = Object.fromEntries(headers.entries());
|
|
@@ -701,7 +701,7 @@ var ProductApi = class {
|
|
|
701
701
|
|
|
702
702
|
// src/utils/types.ts
|
|
703
703
|
var resolveRelation = (ref) => {
|
|
704
|
-
if (typeof ref === "string" ||
|
|
704
|
+
if (typeof ref === "string" || ref === null || ref === void 0)
|
|
705
705
|
return null;
|
|
706
706
|
return ref;
|
|
707
707
|
};
|
|
@@ -789,12 +789,18 @@ var CollectionQueryBuilder = class {
|
|
|
789
789
|
* POST /api/{collection}
|
|
790
790
|
* @returns Payload CMS mutation response with doc and message
|
|
791
791
|
*/
|
|
792
|
-
create(data) {
|
|
792
|
+
create(data, options) {
|
|
793
793
|
return __async(this, null, function* () {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
794
|
+
const endpoint = `/api/${String(this.collection)}`;
|
|
795
|
+
if (options == null ? void 0 : options.file) {
|
|
796
|
+
return this.api.requestCreateWithFile(
|
|
797
|
+
endpoint,
|
|
798
|
+
data,
|
|
799
|
+
options.file,
|
|
800
|
+
options.filename
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
return this.api.requestCreate(endpoint, data);
|
|
798
804
|
});
|
|
799
805
|
}
|
|
800
806
|
/**
|
|
@@ -802,12 +808,18 @@ var CollectionQueryBuilder = class {
|
|
|
802
808
|
* PATCH /api/{collection}/{id}
|
|
803
809
|
* @returns Payload CMS mutation response with doc and message
|
|
804
810
|
*/
|
|
805
|
-
update(id, data) {
|
|
811
|
+
update(id, data, options) {
|
|
806
812
|
return __async(this, null, function* () {
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
813
|
+
const endpoint = `/api/${String(this.collection)}/${String(id)}`;
|
|
814
|
+
if (options == null ? void 0 : options.file) {
|
|
815
|
+
return this.api.requestUpdateWithFile(
|
|
816
|
+
endpoint,
|
|
817
|
+
data,
|
|
818
|
+
options.file,
|
|
819
|
+
options.filename
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
return this.api.requestUpdate(endpoint, data);
|
|
811
823
|
});
|
|
812
824
|
}
|
|
813
825
|
/**
|
|
@@ -1007,6 +1019,14 @@ var HttpClient = class {
|
|
|
1007
1019
|
};
|
|
1008
1020
|
|
|
1009
1021
|
// src/core/collection/collection-client.ts
|
|
1022
|
+
function buildPayloadFormData(data, file, filename) {
|
|
1023
|
+
const formData = new FormData();
|
|
1024
|
+
formData.append("file", file, filename);
|
|
1025
|
+
if (data != null) {
|
|
1026
|
+
formData.append("_payload", JSON.stringify(data));
|
|
1027
|
+
}
|
|
1028
|
+
return formData;
|
|
1029
|
+
}
|
|
1010
1030
|
var CollectionClient = class extends HttpClient {
|
|
1011
1031
|
constructor(clientKey, secretKey, baseUrl, getCustomerToken) {
|
|
1012
1032
|
super(clientKey, secretKey, baseUrl, getCustomerToken);
|
|
@@ -1114,6 +1134,32 @@ var CollectionClient = class extends HttpClient {
|
|
|
1114
1134
|
return this.parseFindResponse(response);
|
|
1115
1135
|
});
|
|
1116
1136
|
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Create document with file upload
|
|
1139
|
+
* POST /api/{collection} (multipart/form-data)
|
|
1140
|
+
*/
|
|
1141
|
+
requestCreateWithFile(endpoint, data, file, filename) {
|
|
1142
|
+
return __async(this, null, function* () {
|
|
1143
|
+
const response = yield _fetch(endpoint, __spreadProps(__spreadValues({}, this.defaultOptions), {
|
|
1144
|
+
method: "POST",
|
|
1145
|
+
body: buildPayloadFormData(data, file, filename)
|
|
1146
|
+
}));
|
|
1147
|
+
return this.parseMutationResponse(response);
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
/**
|
|
1151
|
+
* Update document with file upload
|
|
1152
|
+
* PATCH /api/{collection}/{id} (multipart/form-data)
|
|
1153
|
+
*/
|
|
1154
|
+
requestUpdateWithFile(endpoint, data, file, filename) {
|
|
1155
|
+
return __async(this, null, function* () {
|
|
1156
|
+
const response = yield _fetch(endpoint, __spreadProps(__spreadValues({}, this.defaultOptions), {
|
|
1157
|
+
method: "PATCH",
|
|
1158
|
+
body: buildPayloadFormData(data, file, filename)
|
|
1159
|
+
}));
|
|
1160
|
+
return this.parseMutationResponse(response);
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1117
1163
|
};
|
|
1118
1164
|
|
|
1119
1165
|
// src/core/collection/const.ts
|
|
@@ -1556,8 +1602,11 @@ var QueryHooks = class {
|
|
|
1556
1602
|
useCreate(params, options) {
|
|
1557
1603
|
const { collection } = params;
|
|
1558
1604
|
return (0, import_react_query2.useMutation)({
|
|
1559
|
-
mutationFn: (
|
|
1560
|
-
return yield this.collectionClient.from(collection).create(
|
|
1605
|
+
mutationFn: (variables) => __async(this, null, function* () {
|
|
1606
|
+
return yield this.collectionClient.from(collection).create(
|
|
1607
|
+
variables.data,
|
|
1608
|
+
variables.file ? { file: variables.file, filename: variables.filename } : void 0
|
|
1609
|
+
);
|
|
1561
1610
|
}),
|
|
1562
1611
|
onSuccess: (data) => {
|
|
1563
1612
|
var _a;
|
|
@@ -1572,7 +1621,11 @@ var QueryHooks = class {
|
|
|
1572
1621
|
const { collection } = params;
|
|
1573
1622
|
return (0, import_react_query2.useMutation)({
|
|
1574
1623
|
mutationFn: (variables) => __async(this, null, function* () {
|
|
1575
|
-
return yield this.collectionClient.from(collection).update(
|
|
1624
|
+
return yield this.collectionClient.from(collection).update(
|
|
1625
|
+
variables.id,
|
|
1626
|
+
variables.data,
|
|
1627
|
+
variables.file ? { file: variables.file, filename: variables.filename } : void 0
|
|
1628
|
+
);
|
|
1576
1629
|
}),
|
|
1577
1630
|
onSuccess: (data) => {
|
|
1578
1631
|
var _a;
|