veryfront 0.1.580 → 0.1.581
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 +1 -1
- package/esm/cli/templates/manifest.js +1 -1
- package/esm/deno.js +3 -3
- package/esm/src/integrations/remote-tools.d.ts +1 -2
- package/esm/src/integrations/remote-tools.d.ts.map +1 -1
- package/esm/src/integrations/remote-tools.js +40 -12
- package/esm/src/schemas/index.d.ts +2 -4
- package/esm/src/schemas/index.d.ts.map +1 -1
- package/esm/src/schemas/index.js +2 -4
- package/esm/src/server/handlers/request/agent-stream.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/request/agent-stream.handler.js +68 -1
- package/esm/src/server/index.d.ts +3 -4
- package/esm/src/server/index.d.ts.map +1 -1
- package/esm/src/server/index.js +4 -6
- package/esm/src/tool/types.d.ts +1 -1
- package/esm/src/transforms/esm/http-cache-state.d.ts.map +1 -1
- package/esm/src/transforms/esm/http-cache-state.js +19 -0
- package/esm/src/transforms/mdx/esm-module-loader/cache/index.d.ts.map +1 -1
- package/esm/src/transforms/mdx/esm-module-loader/cache/index.js +17 -0
- package/esm/src/utils/index.d.ts +1 -1
- package/esm/src/utils/index.js +1 -1
- package/esm/src/utils/memory/index.d.ts +1 -1
- package/esm/src/utils/memory/index.d.ts.map +1 -1
- package/esm/src/utils/memory/index.js +1 -1
- package/esm/src/utils/memory/profiler.d.ts +18 -0
- package/esm/src/utils/memory/profiler.d.ts.map +1 -1
- package/esm/src/utils/memory/profiler.js +34 -8
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ brew install veryfront/tap/veryfront
|
|
|
68
68
|
|
|
69
69
|
</details>
|
|
70
70
|
|
|
71
|
-
Follow the [Quickstart guide](https://veryfront.com/docs/code/
|
|
71
|
+
Follow the [Quickstart guide](https://veryfront.com/docs/code/getting-started/quickstart) for step-by-step setup, or use [Create a project](https://veryfront.com/docs/code/getting-started/create-a-project) to compare templates before you scaffold. For the full documentation, visit [veryfront.com/docs/code](https://veryfront.com/docs/code).
|
|
72
72
|
|
|
73
73
|
## Project Structure
|
|
74
74
|
|
|
@@ -104,7 +104,7 @@ export default {
|
|
|
104
104
|
"lib/oauth.ts": "import { type OAuthToken, tokenStore } from \"./token-store.ts\";\n\nexport interface OAuthProvider {\n name: string;\n authorizationUrl: string;\n tokenUrl: string;\n clientId: string;\n clientSecret: string;\n scopes: string[];\n callbackPath: string;\n}\n\nfunction getExpiresAt(expiresIn: unknown): number | undefined {\n if (typeof expiresIn !== \"number\" || expiresIn <= 0) return undefined;\n return Date.now() + expiresIn * 1000;\n}\n\nasync function postTokenRequest(\n provider: OAuthProvider,\n body: Record<string, string>,\n errorPrefix: string,\n): Promise<any> {\n const response = await fetch(provider.tokenUrl, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body: new URLSearchParams(body),\n });\n\n if (response.ok) return response.json();\n\n const error = await response.text();\n throw new Error(`${errorPrefix}: ${response.status} - ${error}`);\n}\n\nexport function getAuthorizationUrl(\n provider: OAuthProvider,\n state: string,\n redirectUri: string,\n): string {\n const params = new URLSearchParams({\n client_id: provider.clientId,\n redirect_uri: redirectUri,\n response_type: \"code\",\n scope: provider.scopes.join(\" \"),\n state,\n access_type: \"offline\",\n prompt: \"consent\",\n });\n\n return `${provider.authorizationUrl}?${params.toString()}`;\n}\n\nexport async function exchangeCodeForTokens(\n provider: OAuthProvider,\n code: string,\n redirectUri: string,\n): Promise<OAuthToken> {\n const data = await postTokenRequest(\n provider,\n {\n client_id: provider.clientId,\n client_secret: provider.clientSecret,\n code,\n grant_type: \"authorization_code\",\n redirect_uri: redirectUri,\n },\n \"Token exchange failed\",\n );\n\n return {\n accessToken: data.access_token,\n refreshToken: data.refresh_token,\n expiresAt: getExpiresAt(data.expires_in),\n tokenType: data.token_type ?? \"Bearer\",\n scope: data.scope,\n };\n}\n\nexport async function refreshAccessToken(\n provider: OAuthProvider,\n refreshToken: string,\n): Promise<OAuthToken> {\n const data = await postTokenRequest(\n provider,\n {\n client_id: provider.clientId,\n client_secret: provider.clientSecret,\n refresh_token: refreshToken,\n grant_type: \"refresh_token\",\n },\n \"Token refresh failed\",\n );\n\n return {\n accessToken: data.access_token,\n refreshToken: data.refresh_token ?? refreshToken,\n expiresAt: getExpiresAt(data.expires_in),\n tokenType: data.token_type ?? \"Bearer\",\n scope: data.scope,\n };\n}\n\nexport async function getValidToken(\n provider: OAuthProvider,\n userId: string,\n service: string,\n): Promise<string | null> {\n const token = await tokenStore.getToken(userId, service);\n if (!token) return null;\n\n const isExpired = token.expiresAt\n ? token.expiresAt < Date.now() + 5 * 60 * 1000\n : false;\n\n if (!isExpired || !token.refreshToken) return token.accessToken;\n\n try {\n const newToken = await refreshAccessToken(provider, token.refreshToken);\n await tokenStore.setToken(userId, service, newToken);\n return newToken.accessToken;\n } catch {\n await tokenStore.revokeToken(userId, service);\n return null;\n }\n}\n",
|
|
105
105
|
"lib/token-store-examples.ts": "/****\n * Production Token Store Examples\n *\n * Copy-paste implementations for different storage backends.\n * Each example includes encryption support via TOKEN_ENCRYPTION_KEY.\n *\n * @module\n */\n\nimport { createTokenStore, tokenStore, type TokenStore } from \"./token-store.ts\";\n\n// ============================================================================\n// Vercel KV Store\n// ============================================================================\n\n/**\n * Token store using Vercel KV (Redis-compatible)\n *\n * Required environment variables:\n * - KV_REST_API_URL\n * - KV_REST_API_TOKEN\n * - TOKEN_ENCRYPTION_KEY (recommended)\n *\n * @example\n * ```typescript\n * // lib/token-store.ts\n * import { createVercelKVStore } from './token-store-examples';\n * export const tokenStore = createVercelKVStore();\n * ```\n */\nexport function createVercelKVStore(): TokenStore {\n type VercelKV = typeof import(\"@vercel/kv\");\n let kvPromise: Promise<VercelKV> | null = null;\n\n async function getKV(): Promise<VercelKV[\"kv\"]> {\n kvPromise ??= import(\"@vercel/kv\");\n return (await kvPromise).kv;\n }\n\n return createTokenStore({\n async get(key: string): Promise<string | null> {\n const kv = await getKV();\n return kv.get<string>(key);\n },\n async set(key: string, value: string): Promise<void> {\n const kv = await getKV();\n await kv.set(key, value);\n },\n async delete(key: string): Promise<void> {\n const kv = await getKV();\n await kv.del(key);\n },\n });\n}\n\n// ============================================================================\n// Redis Store\n// ============================================================================\n\n/**\n * Token store using Redis\n *\n * Required environment variables:\n * - REDIS_URL (e.g., redis://localhost:6379)\n * - TOKEN_ENCRYPTION_KEY (recommended)\n *\n * @example\n * ```typescript\n * // lib/token-store.ts\n * import { createRedisStore } from './token-store-examples';\n * export const tokenStore = createRedisStore();\n * ```\n */\nexport function createRedisStore(): TokenStore {\n let clientPromise: Promise<ReturnType<(typeof import(\"redis\"))[\"createClient\"]>> | null = null;\n\n async function getClient(): Promise<ReturnType<(typeof import(\"redis\"))[\"createClient\"]>> {\n clientPromise ??= (async () => {\n const { createClient } = await import(\"redis\");\n const client = createClient({ url: process.env.REDIS_URL });\n await client.connect();\n return client;\n })();\n\n return clientPromise;\n }\n\n return createTokenStore({\n async get(key: string): Promise<string | null> {\n const client = await getClient();\n return client.get(key);\n },\n async set(key: string, value: string): Promise<void> {\n const client = await getClient();\n await client.set(key, value);\n },\n async delete(key: string): Promise<void> {\n const client = await getClient();\n await client.del(key);\n },\n });\n}\n\n// ============================================================================\n// PostgreSQL Store\n// ============================================================================\n\n/**\n * Token store using PostgreSQL\n *\n * Required environment variables:\n * - DATABASE_URL (e.g., postgres://user:pass@host:5432/db)\n * - TOKEN_ENCRYPTION_KEY (recommended)\n *\n * Required table (create with migration):\n * ```sql\n * CREATE TABLE oauth_tokens (\n * key VARCHAR(255) PRIMARY KEY,\n * value TEXT NOT NULL,\n * created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n * updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n * );\n * CREATE INDEX idx_oauth_tokens_key ON oauth_tokens(key);\n * ```\n *\n * @example\n * ```typescript\n * // lib/token-store.ts\n * import { createPostgresStore } from './token-store-examples';\n * export const tokenStore = createPostgresStore();\n * ```\n */\nexport function createPostgresStore(): TokenStore {\n let poolPromise: Promise<import(\"pg\").Pool> | null = null;\n\n async function getPool(): Promise<import(\"pg\").Pool> {\n poolPromise ??= (async () => {\n const { Pool } = await import(\"pg\");\n return new Pool({ connectionString: process.env.DATABASE_URL });\n })();\n\n return poolPromise;\n }\n\n return createTokenStore({\n async get(key: string): Promise<string | null> {\n const pool = await getPool();\n const result = await pool.query(\"SELECT value FROM oauth_tokens WHERE key = $1\", [key]);\n return result.rows[0]?.value ?? null;\n },\n async set(key: string, value: string): Promise<void> {\n const pool = await getPool();\n await pool.query(\n `INSERT INTO oauth_tokens (key, value, updated_at)\n VALUES ($1, $2, NOW())\n ON CONFLICT (key) DO UPDATE SET value = $2, updated_at = NOW()`,\n [key, value],\n );\n },\n async delete(key: string): Promise<void> {\n const pool = await getPool();\n await pool.query(\"DELETE FROM oauth_tokens WHERE key = $1\", [key]);\n },\n });\n}\n\n// ============================================================================\n// SQLite Store (for edge/serverless with D1, Turso, etc.)\n// ============================================================================\n\n/**\n * Token store using SQLite (Cloudflare D1, Turso, better-sqlite3)\n *\n * Required table:\n * ```sql\n * CREATE TABLE oauth_tokens (\n * key TEXT PRIMARY KEY,\n * value TEXT NOT NULL,\n * updated_at INTEGER DEFAULT (strftime('%s', 'now'))\n * );\n * ```\n *\n * @param db - SQLite database instance (D1Database, Connection, or Database)\n *\n * @example With Cloudflare D1\n * ```typescript\n * // In your API route\n * export async function GET(request: Request, { env }) {\n * const tokenStore = createSQLiteStore(env.DB);\n * // ...\n * }\n * ```\n *\n * @example With Turso\n * ```typescript\n * import { createClient } from '@libsql/client';\n * const db = createClient({ url: process.env.TURSO_URL, authToken: process.env.TURSO_AUTH_TOKEN });\n * export const tokenStore = createSQLiteStore(db);\n * ```\n */\nexport function createSQLiteStore(db: {\n prepare(sql: string): {\n bind(...args: unknown[]): { first(): Promise<{ value?: string } | null>; run(): Promise<void> };\n };\n}): TokenStore {\n return createTokenStore({\n async get(key: string): Promise<string | null> {\n const result = await db.prepare(\"SELECT value FROM oauth_tokens WHERE key = ?\").bind(key).first();\n return result?.value ?? null;\n },\n async set(key: string, value: string): Promise<void> {\n await db\n .prepare(\n `INSERT OR REPLACE INTO oauth_tokens (key, value, updated_at)\n VALUES (?, ?, strftime('%s', 'now'))`,\n )\n .bind(key, value)\n .run();\n },\n async delete(key: string): Promise<void> {\n await db.prepare(\"DELETE FROM oauth_tokens WHERE key = ?\").bind(key).run();\n },\n });\n}\n\n// ============================================================================\n// Cloudflare Workers KV Store\n// ============================================================================\n\n/**\n * Token store using Cloudflare Workers KV\n *\n * @param kv - KV namespace binding from worker environment\n *\n * @example\n * ```typescript\n * // In your worker\n * export default {\n * async fetch(request, env) {\n * const tokenStore = createWorkersKVStore(env.OAUTH_TOKENS);\n * // ...\n * }\n * };\n * ```\n */\nexport function createWorkersKVStore(kv: {\n get(key: string): Promise<string | null>;\n put(key: string, value: string): Promise<void>;\n delete(key: string): Promise<void>;\n}): TokenStore {\n return createTokenStore({\n get(key: string): Promise<string | null> {\n return kv.get(key);\n },\n set(key: string, value: string): Promise<void> {\n return kv.put(key, value);\n },\n delete(key: string): Promise<void> {\n return kv.delete(key);\n },\n });\n}\n\n// ============================================================================\n// Prisma Store\n// ============================================================================\n\n/**\n * Token store using Prisma ORM\n *\n * Required Prisma schema:\n * ```prisma\n * model OAuthToken {\n * key String @id\n * value String\n * updatedAt DateTime @updatedAt\n * }\n * ```\n *\n * @example\n * ```typescript\n * import { PrismaClient } from '@prisma/client';\n * const prisma = new PrismaClient();\n * export const tokenStore = createPrismaStore(prisma);\n * ```\n */\nexport function createPrismaStore(prisma: {\n oAuthToken: {\n findUnique(args: { where: { key: string } }): Promise<{ value: string } | null>;\n upsert(args: {\n where: { key: string };\n update: { value: string };\n create: { key: string; value: string };\n }): Promise<unknown>;\n delete(args: { where: { key: string } }): Promise<unknown>;\n };\n}): TokenStore {\n return createTokenStore({\n async get(key: string): Promise<string | null> {\n const record = await prisma.oAuthToken.findUnique({ where: { key } });\n return record?.value ?? null;\n },\n async set(key: string, value: string): Promise<void> {\n await prisma.oAuthToken.upsert({\n where: { key },\n update: { value },\n create: { key, value },\n });\n },\n async delete(key: string): Promise<void> {\n try {\n await prisma.oAuthToken.delete({ where: { key } });\n } catch {\n // Ignore if not found\n }\n },\n });\n}\n\n// ============================================================================\n// Drizzle ORM Store\n// ============================================================================\n\n/**\n * Token store using Drizzle ORM\n *\n * Required schema:\n * ```typescript\n * import { pgTable, text, timestamp } from 'drizzle-orm/pg-core';\n *\n * export const oauthTokens = pgTable('oauth_tokens', {\n * key: text('key').primaryKey(),\n * value: text('value').notNull(),\n * updatedAt: timestamp('updated_at').defaultNow(),\n * });\n * ```\n *\n * @example\n * ```typescript\n * import { drizzle } from 'drizzle-orm/postgres-js';\n * import postgres from 'postgres';\n * import { oauthTokens } from './schema';\n *\n * const client = postgres(process.env.DATABASE_URL!);\n * const db = drizzle(client);\n * export const tokenStore = createDrizzleStore(db, oauthTokens);\n * ```\n */\nexport function createDrizzleStore<T extends { key: unknown; value: unknown }>(\n db: {\n select(): {\n from(table: T): { where(condition: unknown): { get(): Promise<{ value: string } | undefined> } };\n };\n insert(table: T): {\n values(data: { key: string; value: string }): {\n onConflictDoUpdate(args: { target: unknown; set: { value: string } }): { execute(): Promise<void> };\n };\n };\n delete(table: T): { where(condition: unknown): { execute(): Promise<void> } };\n },\n table: T & { key: unknown; value: unknown },\n eq: (col: unknown, val: unknown) => unknown,\n): TokenStore {\n return createTokenStore({\n async get(key: string): Promise<string | null> {\n const result = await db.select().from(table).where(eq(table.key, key)).get();\n return result?.value ?? null;\n },\n async set(key: string, value: string): Promise<void> {\n await db\n .insert(table)\n .values({ key, value })\n .onConflictDoUpdate({ target: table.key, set: { value } })\n .execute();\n },\n async delete(key: string): Promise<void> {\n await db.delete(table).where(eq(table.key, key)).execute();\n },\n });\n}\n\n// ============================================================================\n// Auto-Select Store (Recommended)\n// ============================================================================\n\n/**\n * Automatically selects the appropriate token store based on environment\n *\n * Detection order:\n * 1. DATABASE_URL -> PostgreSQL\n * 2. KV_REST_API_URL -> Vercel KV\n * 3. REDIS_URL -> Redis\n * 4. Fallback -> In-memory (development only)\n *\n * @example\n * ```typescript\n * // lib/token-store.ts\n * import { createAutoStore } from './token-store-examples';\n * export const tokenStore = createAutoStore();\n * ```\n */\nexport function createAutoStore(): TokenStore {\n const env = process.env;\n\n if (env.DATABASE_URL) {\n console.log(\"[Token Store] Using PostgreSQL storage\");\n return createPostgresStore();\n }\n\n if (env.KV_REST_API_URL && env.KV_REST_API_TOKEN) {\n console.log(\"[Token Store] Using Vercel KV storage\");\n return createVercelKVStore();\n }\n\n if (env.REDIS_URL) {\n console.log(\"[Token Store] Using Redis storage\");\n return createRedisStore();\n }\n\n console.warn(\n \"[Token Store] No production storage configured. \" +\n \"Using in-memory storage (tokens will be lost on restart). \" +\n \"Set DATABASE_URL, KV_REST_API_URL, or REDIS_URL for production.\",\n );\n\n return tokenStore;\n}\n",
|
|
106
106
|
"lib/token-store.ts": "/********************************************************************************\n * OAuth Token Store\n *\n * Manages OAuth tokens for connected services.\n *\n * ## Storage Modes\n *\n * **Development (default)**: In-memory storage - tokens are lost on restart.\n * **Production**: Configure via environment variables:\n * - DATABASE_URL: Uses database storage (Postgres, SQLite, MySQL)\n * - KV_REST_API_URL + KV_REST_API_TOKEN: Uses Vercel KV\n * - REDIS_URL: Uses Redis\n * - TOKEN_ENCRYPTION_KEY: Enables AES-256-GCM encryption (recommended)\n *\n * ## Security\n *\n * Tokens contain sensitive OAuth credentials. In production:\n * 1. Always use encrypted storage (set TOKEN_ENCRYPTION_KEY)\n * 2. Use HTTPS for all connections\n * 3. Implement proper access control\n * 4. Rotate encryption keys periodically\n *\n * @see lib/token-store-examples.ts for complete production implementations\n ********************************************************************************/\n\nexport interface OAuthToken {\n accessToken: string;\n refreshToken?: string;\n expiresAt?: number;\n tokenType?: string;\n scope?: string;\n}\n\nexport interface TokenStore {\n getToken(userId: string, service: string): Promise<OAuthToken | null>;\n setToken(userId: string, service: string, token: OAuthToken): Promise<void>;\n revokeToken(userId: string, service: string): Promise<void>;\n isConnected(userId: string, service: string): Promise<boolean>;\n}\n\n/** Token store configuration for production backends */\nexport interface TokenStoreConfig {\n get: (key: string) => Promise<string | null>;\n set: (key: string, value: string) => Promise<void>;\n delete: (key: string) => Promise<void>;\n}\n\nconst AUTO_KEY_STORAGE = \"__veryfront_auto_encryption_key__\";\nconst TOKENS_KEY = \"__veryfront_oauth_tokens__\";\n\nconst globalStore = globalThis as Record<string, unknown>;\n\n// ============================================================================\n// Encryption Utilities\n// ============================================================================\n\nexport async function encryptToken(token: OAuthToken): Promise<string> {\n const key = getEncryptionKey();\n if (!key) return JSON.stringify(token);\n\n const data = new TextEncoder().encode(JSON.stringify(token));\n const iv = crypto.getRandomValues(new Uint8Array(12));\n const rawKey = new Uint8Array(key).buffer;\n\n const cryptoKey = await crypto.subtle.importKey(\"raw\", rawKey, \"AES-GCM\", false, [\"encrypt\"]);\n const encrypted = await crypto.subtle.encrypt({ name: \"AES-GCM\", iv }, cryptoKey, data);\n\n const combined = new Uint8Array(iv.length + encrypted.byteLength);\n combined.set(iv);\n combined.set(new Uint8Array(encrypted), iv.length);\n\n return `encrypted:${btoa(String.fromCharCode(...combined))}`;\n}\n\nexport async function decryptToken(encrypted: string): Promise<OAuthToken | null> {\n if (!encrypted.startsWith(\"encrypted:\")) {\n try {\n return JSON.parse(encrypted) as OAuthToken;\n } catch {\n return null;\n }\n }\n\n const key = getEncryptionKey();\n if (!key) {\n console.error(\"[Token Store] Cannot decrypt: TOKEN_ENCRYPTION_KEY not set\");\n return null;\n }\n\n try {\n const base64 = encrypted.slice(\"encrypted:\".length);\n const combined = Uint8Array.from(atob(base64), (c) => c.charCodeAt(0));\n\n const iv = combined.slice(0, 12);\n const ciphertext = combined.slice(12);\n const rawKey = new Uint8Array(key).buffer;\n\n const cryptoKey = await crypto.subtle.importKey(\"raw\", rawKey, \"AES-GCM\", false, [\"decrypt\"]);\n const decrypted = await crypto.subtle.decrypt({ name: \"AES-GCM\", iv }, cryptoKey, ciphertext);\n\n return JSON.parse(new TextDecoder().decode(decrypted)) as OAuthToken;\n } catch {\n console.error(\"[Token Store] Decryption failed\");\n return null;\n }\n}\n\nexport function generateEncryptionKey(): string {\n const bytes = crypto.getRandomValues(new Uint8Array(32));\n return Array.from(bytes)\n .map((b) => b.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\nfunction getEnvVar(name: string): string | undefined {\n if (typeof process !== \"undefined\") return process.env?.[name];\n return (globalThis as { Deno?: { env?: { get?: (key: string) => string | undefined } } }).Deno\n ?.env?.get?.(name);\n}\n\nfunction hexToKeyBytes(keyHex: string): Uint8Array | null {\n if (keyHex.length !== 64) {\n console.error(\"[Token Store] TOKEN_ENCRYPTION_KEY must be 64 hex characters (32 bytes)\");\n return null;\n }\n\n const key = new Uint8Array(32);\n for (let i = 0; i < 32; i++) {\n key[i] = parseInt(keyHex.slice(i * 2, i * 2 + 2), 16);\n }\n return key;\n}\n\n/** Get encryption key from environment or auto-generate for development */\nfunction getEncryptionKey(): Uint8Array | null {\n const keyHex = getEnvVar(\"TOKEN_ENCRYPTION_KEY\");\n if (keyHex) return hexToKeyBytes(keyHex);\n\n if (!globalStore[AUTO_KEY_STORAGE]) {\n globalStore[AUTO_KEY_STORAGE] = generateEncryptionKey();\n }\n\n return hexToKeyBytes(globalStore[AUTO_KEY_STORAGE] as string);\n}\n\n// ============================================================================\n// Storage Mode Detection\n// ============================================================================\n\nexport type StorageMode = \"memory\" | \"database\" | \"kv\" | \"redis\" | \"custom\";\n\nexport function getStorageMode(): StorageMode {\n const env = typeof process !== \"undefined\"\n ? process.env\n : (globalThis as { Deno?: { env?: { toObject?: () => Record<string, string> } } }).Deno?.env\n ?.toObject?.() ?? {};\n\n if (env.DATABASE_URL) return \"database\";\n if (env.KV_REST_API_URL) return \"kv\";\n if (env.REDIS_URL) return \"redis\";\n return \"memory\";\n}\n\nexport function isEncryptionEnabled(): boolean {\n return getEncryptionKey() !== null;\n}\n\nfunction isProductionRuntime(): boolean {\n return getEnvVar(\"NODE_ENV\") === \"production\";\n}\n\n// ============================================================================\n// In-Memory Store (Development)\n// ============================================================================\n\nconst tokens = (globalStore[TOKENS_KEY] as Map<string, OAuthToken> | undefined) ??\n new Map<string, OAuthToken>();\nglobalStore[TOKENS_KEY] = tokens;\n\nfunction getKey(userId: string, service: string): string {\n return `${userId}:${service}`;\n}\n\nasync function isConnected(\n store: Pick<TokenStore, \"getToken\">,\n userId: string,\n service: string,\n): Promise<boolean> {\n const token = await store.getToken(userId, service);\n return !!token && (!token.expiresAt || token.expiresAt > Date.now());\n}\n\nconst inMemoryStore: TokenStore = {\n async getToken(userId: string, service: string): Promise<OAuthToken | null> {\n return tokens.get(getKey(userId, service)) ?? null;\n },\n\n async setToken(userId: string, service: string, token: OAuthToken): Promise<void> {\n tokens.set(getKey(userId, service), token);\n },\n\n async revokeToken(userId: string, service: string): Promise<void> {\n tokens.delete(getKey(userId, service));\n },\n\n async isConnected(userId: string, service: string): Promise<boolean> {\n return isConnected(this, userId, service);\n },\n};\n\n// ============================================================================\n// Token Store Factory\n// ============================================================================\n\nexport function createTokenStore(config: TokenStoreConfig): TokenStore {\n return {\n async getToken(userId: string, service: string): Promise<OAuthToken | null> {\n const data = await config.get(getKey(userId, service));\n if (!data) return null;\n return decryptToken(data);\n },\n\n async setToken(userId: string, service: string, token: OAuthToken): Promise<void> {\n await config.set(getKey(userId, service), await encryptToken(token));\n },\n\n async revokeToken(userId: string, service: string): Promise<void> {\n await config.delete(getKey(userId, service));\n },\n\n async isConnected(userId: string, service: string): Promise<boolean> {\n return isConnected(this, userId, service);\n },\n };\n}\n\n// ============================================================================\n// Default Export (Auto-detects environment)\n// ============================================================================\n\nexport function createDefaultTokenStore(): TokenStore {\n if (isProductionRuntime()) {\n throw new Error(\n \"In-memory token storage is not allowed in production. \" +\n \"Configure DATABASE_URL, KV_REST_API_URL, or REDIS_URL and wire a durable store from \" +\n \"lib/token-store-examples.ts.\",\n );\n }\n\n // The starter keeps the development store explicit. Production adapters in\n // token-store-examples.ts require provider-specific clients and credentials.\n return inMemoryStore;\n}\n\nlet defaultTokenStore: TokenStore | null = null;\n\nfunction getDefaultTokenStore(): TokenStore {\n defaultTokenStore ??= createDefaultTokenStore();\n return defaultTokenStore;\n}\n\nexport const tokenStore: TokenStore = {\n getToken(userId: string, service: string): Promise<OAuthToken | null> {\n return getDefaultTokenStore().getToken(userId, service);\n },\n\n setToken(userId: string, service: string, token: OAuthToken): Promise<void> {\n return getDefaultTokenStore().setToken(userId, service, token);\n },\n\n revokeToken(userId: string, service: string): Promise<void> {\n return getDefaultTokenStore().revokeToken(userId, service);\n },\n\n isConnected(userId: string, service: string): Promise<boolean> {\n return getDefaultTokenStore().isConnected(userId, service);\n },\n};\n\nif (\n !isProductionRuntime() &&\n getStorageMode() === \"memory\"\n) {\n console.warn(\n \"[Token Store] Using in-memory storage (development mode). \" +\n \"Tokens will be lost on restart. \" +\n \"Set DATABASE_URL, KV_REST_API_URL, or REDIS_URL for production.\",\n );\n}\n",
|
|
107
|
-
"lib/user-id.ts": "import type { ToolExecutionContext } from \"veryfront/tool\";\n\nfunction isProductionRuntime(): boolean {\n return Deno.env.get(\"NODE_ENV\") === \"production\";\n}\n\nfunction devUserId(): string {\n return Deno.env.get(\"VERYFRONT_DEV_USER_ID\") ?? \"dev-user\";\n}\n\nfunction requireUserId(value: string | null | undefined): string {\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n\n if (!isProductionRuntime()) {\n return devUserId();\n }\n\n throw new Error(\n \"Authenticated user id is required in production. \" +\n \"Pass the authenticated user's id from your session, JWT, or auth provider.\",\n );\n}\n\nexport function requireUserIdFromRequest(request: Request): string {\n return requireUserId(\n request.headers.get(\"x-veryfront-user-id\") ?? request.headers.get(\"x-user-id\"),\n );\n}\n\nexport function requireUserIdFromContext(context?: ToolExecutionContext): string {\n return requireUserId(context?.
|
|
107
|
+
"lib/user-id.ts": "import type { ToolExecutionContext } from \"veryfront/tool\";\n\nfunction isProductionRuntime(): boolean {\n return Deno.env.get(\"NODE_ENV\") === \"production\";\n}\n\nfunction devUserId(): string {\n return Deno.env.get(\"VERYFRONT_DEV_USER_ID\") ?? \"dev-user\";\n}\n\nfunction requireUserId(value: string | null | undefined): string {\n if (typeof value === \"string\" && value.length > 0) {\n return value;\n }\n\n if (!isProductionRuntime()) {\n return devUserId();\n }\n\n throw new Error(\n \"Authenticated user id is required in production. \" +\n \"Pass the authenticated user's id from your session, JWT, or auth provider.\",\n );\n}\n\nexport function requireUserIdFromRequest(request: Request): string {\n return requireUserId(\n request.headers.get(\"x-veryfront-user-id\") ?? request.headers.get(\"x-user-id\"),\n );\n}\n\nexport function requireUserIdFromContext(context?: ToolExecutionContext): string {\n return requireUserId(context?.userId);\n}\n",
|
|
108
108
|
"SETUP.md": "# Integration Setup Guide\n\nThis guide helps you set up credentials for all 50+ service integrations available in Veryfront.\n\n## Quick Start\n\n```bash\n# Create a new project with integrations\nveryfront init my-app --with ai --integrations slack,github,notion\n\n# Start development\ncd my-app\nveryfront dev\n```\n\nVisit `http://localhost:3000/api/auth/{service}` to connect each service.\n\n---\n\n## Table of Contents\n\n- [Google Services](#google-services) (Gmail, Calendar, Drive, Docs, Sheets)\n- [Microsoft Services](#microsoft-services) (Outlook, Teams, SharePoint, OneDrive)\n- [Atlassian Services](#atlassian-services) (Jira, Confluence)\n- [Communication](#communication) (Slack, Discord, Twilio, Zoom, Webex)\n- [Project Management](#project-management) (Asana, Monday, Trello, ClickUp, Linear, Notion)\n- [Developer Tools](#developer-tools) (GitHub, GitLab, Bitbucket, Figma, Sentry, PostHog)\n- [CRM & Sales](#crm--sales) (Salesforce, HubSpot, Pipedrive, Intercom, Zendesk, Freshdesk)\n- [Databases](#databases) (Supabase, Neon, Airtable, Snowflake)\n- [Cloud & Storage](#cloud--storage) (AWS, Dropbox, Box)\n- [Finance](#finance) (Stripe, QuickBooks, Xero)\n- [Marketing](#marketing) (Mailchimp, Twitter)\n- [E-commerce](#e-commerce) (Shopify)\n- [AI & Analytics](#ai--analytics) (Anthropic, Mixpanel)\n\n---\n\n## Google Services\n\n**Gmail, Calendar, Drive, Docs, Sheets** all use the same Google OAuth credentials.\n\n### Setup Steps\n\n1. Go to [Google Cloud Console](https://console.cloud.google.com/apis/credentials)\n2. Create a new project or select existing\n3. Enable required APIs:\n - Gmail API\n - Google Calendar API\n - Google Drive API\n - Google Docs API\n - Google Sheets API\n4. Go to **OAuth consent screen**:\n - User Type: External (or Internal for Workspace)\n - Add scopes for each API you need\n5. Go to **Credentials** > **Create Credentials** > **OAuth client ID**:\n - Application type: Web application\n - Authorized redirect URIs:\n ```\n http://localhost:3000/api/auth/gmail/callback\n http://localhost:3000/api/auth/calendar/callback\n http://localhost:3000/api/auth/drive/callback\n http://localhost:3000/api/auth/docs-google/callback\n http://localhost:3000/api/auth/sheets/callback\n ```\n\n### Environment Variables\n\n```env\nGOOGLE_CLIENT_ID=your-client-id.apps.googleusercontent.com\nGOOGLE_CLIENT_SECRET=your-client-secret\n```\n\n### Required Scopes by Service\n\n| Service | Scopes |\n| -------- | -------------------------------------------------------------------------------------------------------------------------------- |\n| Gmail | `gmail.readonly`, `gmail.send`, `gmail.modify`, `gmail.labels`, `gmail.compose`, `https://mail.google.com/` for permanent delete |\n| Calendar | `calendar.readonly`, `calendar.events` |\n| Drive | `drive.readonly`, `drive.file` |\n| Docs | `documents.readonly`, `documents` |\n| Sheets | `spreadsheets.readonly`, `spreadsheets` |\n\n---\n\n## Microsoft Services\n\n**Outlook, Teams, SharePoint, OneDrive** use Microsoft OAuth (Azure AD).\n\n### Setup Steps\n\n1. Go to [Azure Portal](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade)\n2. Click **New registration**:\n - Name: Your app name\n - Supported account types: Accounts in any organizational directory\n - Redirect URI: Web, `http://localhost:3000/api/auth/outlook/callback`\n3. After creation, go to **Certificates & secrets**:\n - Create a new client secret\n4. Go to **API permissions**:\n - Add Microsoft Graph permissions\n\n### Environment Variables\n\n```env\nMICROSOFT_CLIENT_ID=your-application-client-id\nMICROSOFT_CLIENT_SECRET=your-client-secret\nMICROSOFT_TENANT_ID=common\n```\n\n### Required Scopes by Service\n\n| Service | Scopes |\n| ---------- | ------------------------------------------------------------- |\n| Outlook | `Mail.Read`, `Mail.Send`, `Calendars.ReadWrite` |\n| Teams | `Team.ReadBasic.All`, `Chat.ReadWrite`, `ChannelMessage.Send` |\n| SharePoint | `Sites.Read.All`, `Files.ReadWrite.All` |\n| OneDrive | `Files.Read`, `Files.ReadWrite` |\n\n---\n\n## Atlassian Services\n\n**Jira and Confluence** use Atlassian OAuth 2.0 (3LO).\n\n### Setup Steps\n\n1. Go to [Atlassian Developer Console](https://developer.atlassian.com/console/myapps/)\n2. Click **Create** > **OAuth 2.0 integration**\n3. Configure:\n - Name: Your app name\n - Callback URL: `http://localhost:3000/api/auth/jira/callback`\n4. Add required scopes in **Permissions**\n5. Get your Cloud ID: Visit `https://your-domain.atlassian.net/_edge/tenant_info`\n\n### Environment Variables\n\n```env\nATLASSIAN_CLIENT_ID=your-client-id\nATLASSIAN_CLIENT_SECRET=your-client-secret\nATLASSIAN_CLOUD_ID=your-cloud-id\n```\n\n### Required Scopes\n\n| Service | Scopes |\n| ---------- | --------------------------------------------------------- |\n| Jira | `read:jira-work`, `write:jira-work`, `read:jira-user` |\n| Confluence | `read:confluence-content.all`, `write:confluence-content` |\n\n---\n\n## Communication\n\n### Slack\n\n1. Go to [Slack API Apps](https://api.slack.com/apps)\n2. Click **Create New App** > **From scratch**\n3. Go to **OAuth & Permissions**:\n - Add redirect URL: `http://localhost:3000/api/auth/slack/callback`\n - Add scopes: `channels:history`, `channels:read`, `chat:write`, `groups:history`, `groups:read`, `im:history`, `im:read`, `mpim:history`, `mpim:read`, `users:read`\n4. **Install to Workspace**\n\n```env\nSLACK_CLIENT_ID=your-client-id\nSLACK_CLIENT_SECRET=your-client-secret\n```\n\n### Discord\n\n1. Go to [Discord Developer Portal](https://discord.com/developers/applications)\n2. Create **New Application**\n3. Go to **OAuth2**:\n - Add redirect: `http://localhost:3000/api/auth/discord/callback`\n - Scopes: `identify`, `guilds`, `messages.read`\n\n```env\nDISCORD_CLIENT_ID=your-client-id\nDISCORD_CLIENT_SECRET=your-client-secret\n```\n\n### Twilio (SMS/WhatsApp)\n\n1. Go to [Twilio Console](https://console.twilio.com/)\n2. Get Account SID and Auth Token from dashboard\n3. Get or buy a phone number for sending\n\n```env\nTWILIO_ACCOUNT_SID=your-account-sid\nTWILIO_AUTH_TOKEN=your-auth-token\nTWILIO_PHONE_NUMBER=+1234567890\n```\n\n### Zoom\n\n1. Go to [Zoom App Marketplace](https://marketplace.zoom.us/develop/create)\n2. Create **OAuth App**\n3. Configure redirect: `http://localhost:3000/api/auth/zoom/callback`\n4. Add scopes: `meeting:read`, `meeting:write`, `user:read`\n\n```env\nZOOM_CLIENT_ID=your-client-id\nZOOM_CLIENT_SECRET=your-client-secret\n```\n\n### Webex\n\n1. Go to [Webex for Developers](https://developer.webex.com/my-apps)\n2. Create new integration\n3. Redirect URI: `http://localhost:3000/api/auth/webex/callback`\n4. Scopes: `spark:messages_read`, `spark:messages_write`, `spark:rooms_read`\n\n```env\nWEBEX_CLIENT_ID=your-client-id\nWEBEX_CLIENT_SECRET=your-client-secret\n```\n\n---\n\n## Project Management\n\n### Asana\n\n1. Go to [Asana Developer Console](https://app.asana.com/0/developer-console)\n2. Create new app\n3. Set redirect URL: `http://localhost:3000/api/auth/asana/callback`\n\n```env\nASANA_CLIENT_ID=your-client-id\nASANA_CLIENT_SECRET=your-client-secret\n```\n\n### Monday.com\n\n1. Go to [Monday Apps](https://auth.monday.com/oauth2/authorize)\n2. Create new app in your account's Developer section\n3. Configure OAuth with redirect: `http://localhost:3000/api/auth/monday/callback`\n\n```env\nMONDAY_CLIENT_ID=your-client-id\nMONDAY_CLIENT_SECRET=your-client-secret\n```\n\n### Trello\n\n1. Go to [Trello Power-Ups Admin](https://trello.com/power-ups/admin)\n2. Create new Power-Up\n3. Configure OAuth redirect: `http://localhost:3000/api/auth/trello/callback`\n\n```env\nTRELLO_API_KEY=your-api-key\nTRELLO_API_SECRET=your-api-secret\n```\n\n### ClickUp\n\n1. Go to [ClickUp API Settings](https://app.clickup.com/settings/apps)\n2. Create new app\n3. Redirect URL: `http://localhost:3000/api/auth/clickup/callback`\n\n```env\nCLICKUP_CLIENT_ID=your-client-id\nCLICKUP_CLIENT_SECRET=your-client-secret\n```\n\n### Linear\n\n1. Go to [Linear Settings > API](https://linear.app/settings/api)\n2. Create OAuth application\n3. Callback URL: `http://localhost:3000/api/auth/linear/callback`\n\n```env\nLINEAR_CLIENT_ID=your-client-id\nLINEAR_CLIENT_SECRET=your-client-secret\n```\n\n### Notion\n\n1. Go to [Notion Integrations](https://www.notion.so/my-integrations)\n2. Create new **public** integration (for OAuth)\n3. Set redirect URI: `http://localhost:3000/api/auth/notion/callback`\n4. **Important**: Share pages with your integration\n\n```env\nNOTION_CLIENT_ID=your-oauth-client-id\nNOTION_CLIENT_SECRET=your-oauth-client-secret\n```\n\n---\n\n## Developer Tools\n\n### GitHub\n\n1. Go to [GitHub Developer Settings](https://github.com/settings/developers)\n2. Create **New OAuth App**\n3. Authorization callback: `http://localhost:3000/api/auth/github/callback`\n\n```env\nGITHUB_CLIENT_ID=your-client-id\nGITHUB_CLIENT_SECRET=your-client-secret\n```\n\n### GitLab\n\n1. Go to [GitLab Applications](https://gitlab.com/-/profile/applications)\n2. Create new application\n3. Redirect URI: `http://localhost:3000/api/auth/gitlab/callback`\n4. Scopes: `read_user`, `read_api`, `read_repository`\n\n```env\nGITLAB_CLIENT_ID=your-application-id\nGITLAB_CLIENT_SECRET=your-secret\n```\n\n### Bitbucket\n\n1. Go to [Bitbucket App Passwords](https://bitbucket.org/account/settings/app-passwords/) or create OAuth consumer\n2. For OAuth: Workspace settings > OAuth consumers\n3. Callback URL: `http://localhost:3000/api/auth/bitbucket/callback`\n\n```env\nBITBUCKET_CLIENT_ID=your-client-id\nBITBUCKET_CLIENT_SECRET=your-client-secret\n```\n\n### Figma\n\n1. Go to [Figma Developers](https://www.figma.com/developers/apps)\n2. Create new app\n3. Callback URL: `http://localhost:3000/api/auth/figma/callback`\n\n```env\nFIGMA_CLIENT_ID=your-client-id\nFIGMA_CLIENT_SECRET=your-client-secret\n```\n\n### Sentry\n\n1. Go to [Sentry Developer Settings](https://sentry.io/settings/developer-settings/)\n2. Create new public integration\n3. Redirect URL: `http://localhost:3000/api/auth/sentry/callback`\n\n```env\nSENTRY_CLIENT_ID=your-client-id\nSENTRY_CLIENT_SECRET=your-client-secret\n```\n\n### PostHog\n\nUses API key authentication (no OAuth).\n\n1. Go to your PostHog project settings\n2. Create a personal API key\n\n```env\nPOSTHOG_API_KEY=phx_your-api-key\nPOSTHOG_HOST=https://app.posthog.com\n```\n\n---\n\n## CRM & Sales\n\n### Salesforce\n\n1. Go to [Salesforce Setup](https://login.salesforce.com/) > App Manager\n2. Create **New Connected App**\n3. Enable OAuth, add callback: `http://localhost:3000/api/auth/salesforce/callback`\n4. Required scopes: `api`, `refresh_token`\n\n```env\nSALESFORCE_CLIENT_ID=your-consumer-key\nSALESFORCE_CLIENT_SECRET=your-consumer-secret\n```\n\n### HubSpot\n\n1. Go to [HubSpot Developers](https://developers.hubspot.com/)\n2. Create app in your developer account\n3. Configure OAuth redirect: `http://localhost:3000/api/auth/hubspot/callback`\n4. Select required scopes\n\n```env\nHUBSPOT_CLIENT_ID=your-client-id\nHUBSPOT_CLIENT_SECRET=your-client-secret\n```\n\n### Pipedrive\n\n1. Go to [Pipedrive Marketplace Manager](https://developers.pipedrive.com/)\n2. Create new app\n3. OAuth redirect: `http://localhost:3000/api/auth/pipedrive/callback`\n\n```env\nPIPEDRIVE_CLIENT_ID=your-client-id\nPIPEDRIVE_CLIENT_SECRET=your-client-secret\n```\n\n### Intercom\n\n1. Go to [Intercom Developer Hub](https://developers.intercom.com/)\n2. Create new app\n3. Configure OAuth: `http://localhost:3000/api/auth/intercom/callback`\n\n```env\nINTERCOM_CLIENT_ID=your-client-id\nINTERCOM_CLIENT_SECRET=your-client-secret\n```\n\n### Zendesk\n\n1. Go to Admin Center > Apps and integrations > APIs > Zendesk API\n2. Create OAuth client\n3. Redirect URL: `http://localhost:3000/api/auth/zendesk/callback`\n\n```env\nZENDESK_CLIENT_ID=your-client-id\nZENDESK_CLIENT_SECRET=your-client-secret\nZENDESK_SUBDOMAIN=your-subdomain\n```\n\n### Freshdesk\n\nUses API key authentication.\n\n1. Go to Profile Settings in Freshdesk\n2. Find your API Key\n\n```env\nFRESHDESK_API_KEY=your-api-key\nFRESHDESK_DOMAIN=your-domain.freshdesk.com\n```\n\n---\n\n## Databases\n\n### Supabase\n\nUses API key (no OAuth needed).\n\n1. Go to your Supabase project dashboard\n2. Go to Settings > API\n3. Copy the `anon` or `service_role` key\n\n```env\nSUPABASE_URL=https://your-project.supabase.co\nSUPABASE_ANON_KEY=your-anon-key\nSUPABASE_SERVICE_ROLE_KEY=your-service-role-key\n```\n\n### Neon\n\nUses API key authentication.\n\n1. Go to [Neon Console](https://console.neon.tech/)\n2. Create API key in Account Settings\n\n```env\nNEON_API_KEY=your-api-key\nNEON_PROJECT_ID=your-project-id\n```\n\n### Airtable\n\n1. Go to [Airtable Account](https://airtable.com/account)\n2. Create personal access token or OAuth app\n3. For OAuth: [Airtable OAuth](https://airtable.com/create/oauth)\n\n```env\nAIRTABLE_API_KEY=your-api-key\n# Or for OAuth:\nAIRTABLE_CLIENT_ID=your-client-id\nAIRTABLE_CLIENT_SECRET=your-client-secret\n```\n\n### Snowflake\n\nUses account credentials (key-pair or password).\n\n1. Get your Snowflake account identifier\n2. Create a user with appropriate permissions\n3. (Optional) Set up key-pair authentication\n\n```env\nSNOWFLAKE_ACCOUNT=your-account-identifier\nSNOWFLAKE_USERNAME=your-username\nSNOWFLAKE_PASSWORD=your-password\nSNOWFLAKE_WAREHOUSE=your-warehouse\nSNOWFLAKE_DATABASE=your-database\n```\n\n---\n\n## Cloud & Storage\n\n### AWS\n\nUses IAM credentials.\n\n1. Go to [AWS IAM Console](https://console.aws.amazon.com/iam/)\n2. Create a new IAM user with programmatic access\n3. Attach policies for services you need (S3, EC2, Lambda, etc.)\n\n```env\nAWS_ACCESS_KEY_ID=your-access-key\nAWS_SECRET_ACCESS_KEY=your-secret-key\nAWS_REGION=us-east-1\n```\n\n### Dropbox\n\n1. Go to [Dropbox App Console](https://www.dropbox.com/developers/apps)\n2. Create app with Full Dropbox or App folder access\n3. OAuth2 redirect: `http://localhost:3000/api/auth/dropbox/callback`\n\n```env\nDROPBOX_CLIENT_ID=your-app-key\nDROPBOX_CLIENT_SECRET=your-app-secret\n```\n\n### Box\n\n1. Go to [Box Developer Console](https://app.box.com/developers/console)\n2. Create new app with OAuth 2.0\n3. Redirect URI: `http://localhost:3000/api/auth/box/callback`\n\n```env\nBOX_CLIENT_ID=your-client-id\nBOX_CLIENT_SECRET=your-client-secret\n```\n\n---\n\n## Finance\n\n### Stripe\n\nUses API key (no OAuth for basic usage).\n\n1. Go to [Stripe Dashboard](https://dashboard.stripe.com/apikeys)\n2. Get your secret key (use test key for development)\n\n```env\nSTRIPE_SECRET_KEY=sk_test_your-secret-key\nSTRIPE_PUBLISHABLE_KEY=pk_test_your-publishable-key\n```\n\n### QuickBooks\n\n1. Go to [Intuit Developer](https://developer.intuit.com/)\n2. Create app and get OAuth credentials\n3. Redirect URI: `http://localhost:3000/api/auth/quickbooks/callback`\n\n```env\nQUICKBOOKS_CLIENT_ID=your-client-id\nQUICKBOOKS_CLIENT_SECRET=your-client-secret\n```\n\n### Xero\n\n1. Go to [Xero Developer](https://developer.xero.com/app/manage)\n2. Create app\n3. Redirect URI: `http://localhost:3000/api/auth/xero/callback`\n\n```env\nXERO_CLIENT_ID=your-client-id\nXERO_CLIENT_SECRET=your-client-secret\n```\n\n---\n\n## Marketing\n\n### Mailchimp\n\n1. Go to [Mailchimp Account API Keys](https://us1.admin.mailchimp.com/account/api/)\n2. For OAuth: Register app at [Mailchimp OAuth](https://admin.mailchimp.com/account/oauth2/)\n3. Redirect: `http://localhost:3000/api/auth/mailchimp/callback`\n\n```env\nMAILCHIMP_CLIENT_ID=your-client-id\nMAILCHIMP_CLIENT_SECRET=your-client-secret\n# Or API key:\nMAILCHIMP_API_KEY=your-api-key-us1\n```\n\n### Twitter/X\n\n1. Go to [Twitter Developer Portal](https://developer.twitter.com/en/portal/dashboard)\n2. Create project and app\n3. Enable OAuth 2.0\n4. Callback URL: `http://localhost:3000/api/auth/twitter/callback`\n\n```env\nTWITTER_CLIENT_ID=your-client-id\nTWITTER_CLIENT_SECRET=your-client-secret\n```\n\n---\n\n## E-commerce\n\n### Shopify\n\n1. Go to [Shopify Partners](https://partners.shopify.com/)\n2. Create new app\n3. App URL and redirect: `http://localhost:3000/api/auth/shopify/callback`\n\n```env\nSHOPIFY_CLIENT_ID=your-api-key\nSHOPIFY_CLIENT_SECRET=your-api-secret\nSHOPIFY_SHOP_NAME=your-store.myshopify.com\n```\n\n---\n\n## AI & Analytics\n\n### Anthropic (Admin API)\n\nFor organization management and usage tracking.\n\n1. Go to [Anthropic Console](https://console.anthropic.com/)\n2. Create Admin API key (requires admin access)\n\n```env\nANTHROPIC_ADMIN_API_KEY=your-admin-api-key\n```\n\n### Mixpanel\n\nUses API key/secret for data export.\n\n1. Go to [Mixpanel Project Settings](https://mixpanel.com/settings/project)\n2. Get Project Token for tracking\n3. Get API Secret for data export\n\n```env\nMIXPANEL_PROJECT_TOKEN=your-project-token\nMIXPANEL_API_SECRET=your-api-secret\n```\n\n---\n\n## Testing Your Setup\n\nAfter configuring credentials:\n\n```bash\n# Start the dev server\nveryfront dev\n\n# Test each integration by visiting:\n# http://localhost:3000/api/auth/{service}\n\n# Check connection status\ncurl http://localhost:3000/api/connections\n```\n\n## Troubleshooting\n\n### Common Issues\n\n| Error | Solution |\n| ---------------------- | -------------------------------------------------------------- |\n| \"Invalid redirect URI\" | Ensure callback URL matches exactly (including trailing slash) |\n| \"Invalid client\" | Check CLIENT_ID is correct and app is published |\n| \"Access denied\" | Verify all required scopes are added |\n| \"Token expired\" | Implement refresh token flow or re-authenticate |\n\n### Debug Mode\n\nEnable debug logging:\n\n```bash\nDEBUG=veryfront:oauth veryfront dev\n```\n\n### Token Storage\n\nBy default, tokens are stored in memory. For production:\n\n1. Implement `TokenStore` interface in `lib/token-store.ts`\n2. Use Redis, database, or encrypted file storage\n3. Handle token refresh automatically\n\n## Production Checklist\n\n- [ ] Update all redirect URIs to production domain\n- [ ] Implement persistent token storage\n- [ ] Set up token encryption\n- [ ] Configure rate limiting\n- [ ] Add error monitoring (Sentry)\n- [ ] Test OAuth flows end-to-end\n- [ ] Review and minimize required scopes\n\n## Need Help?\n\n- Run `veryfront doctor` to diagnose issues\n- Check the [Veryfront Documentation](https://veryfront.com/docs)\n- Join our [Discord community](https://discord.gg/veryfront)\n"
|
|
109
109
|
}
|
|
110
110
|
},
|
package/esm/deno.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
"name": "veryfront",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.581",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"nodeModulesDir": "auto",
|
|
6
6
|
"workspace": [
|
|
@@ -337,8 +337,8 @@ export default {
|
|
|
337
337
|
"verify:quick": "deno task generate:manifests:check && deno fmt --check src/ cli/ react/ && DENO_NO_PACKAGE_JSON=1 deno lint src/ cli/ react/ && deno task lint:style && deno task lint:cli-boundary && deno task lint:wildcard-exports && deno task lint:barrel-jsdoc && deno task lint:ban-zod && deno task lint:core-deps && deno task lint:dependency-boundaries && deno task lint:extension-contracts && deno task lint:extension-capabilities && deno task docs:validate && deno task typecheck",
|
|
338
338
|
"docs": "deno run --allow-read --allow-write --allow-run --allow-env scripts/docs/generate-api-reference.ts",
|
|
339
339
|
"docs:coverage": "deno run --allow-read scripts/docs/docs-coverage.ts",
|
|
340
|
-
"docs:copy": "rm -rf ../../docs/docs/code/reference && cp -r docs/reference/ ../../docs/docs/code/reference/",
|
|
341
|
-
"docs:validate": "deno run --allow-read scripts/docs/validate-api-reference.ts && deno run --allow-read scripts/docs/validate-guides.ts && deno test --config=scripts/test.deno.json --no-check --allow-read scripts/docs/docs-coverage.test.ts && deno test --no-check --allow-read tests/docs/guide-contracts.test.ts tests/docs/guide-content.test.ts && deno test --no-check --allow-all tests/docs/guide-examples.test.ts tests/docs/guide-code-examples.test.ts && deno run -A scripts/lint/check-doc-links.ts",
|
|
340
|
+
"docs:copy": "rm -rf ../../docs/docs/code/api-reference && cp -r docs/api-reference/ ../../docs/docs/code/api-reference/",
|
|
341
|
+
"docs:validate": "deno run --allow-read scripts/docs/validate-api-reference.ts && deno run --allow-read scripts/docs/validate-guides.ts && deno run --allow-read scripts/docs/validate-public-docs.ts && deno test --config=scripts/test.deno.json --no-check --allow-read scripts/docs/docs-coverage.test.ts && deno test --no-check --allow-read tests/docs/guide-contracts.test.ts tests/docs/guide-content.test.ts && deno test --no-check --allow-all tests/docs/guide-examples.test.ts tests/docs/guide-code-examples.test.ts && deno run -A scripts/lint/check-doc-links.ts",
|
|
342
342
|
"docs:verify-npm": "node scripts/docs/verify-npm-exports.mjs && node scripts/docs/verify-npm-node.mjs",
|
|
343
343
|
"docs:check-links": "deno run -A scripts/lint/check-doc-links.ts",
|
|
344
344
|
"lint:ban-zod": "deno run --allow-read scripts/lint/ban-zod-imports.ts",
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { ToolDefinition } from "../tool/index.js";
|
|
2
2
|
type RemoteIntegrationToolExecutionContext = {
|
|
3
|
-
endUserId?: string;
|
|
4
3
|
runId?: string;
|
|
5
4
|
agentId?: string;
|
|
6
5
|
};
|
|
@@ -22,7 +21,7 @@ export declare function isRemoteIntegrationTool(toolName: string): boolean;
|
|
|
22
21
|
* Execute a remote integration tool via the API.
|
|
23
22
|
* Called by the agent runtime when a tool isn't found in the local registry.
|
|
24
23
|
*/
|
|
25
|
-
export declare function executeRemoteIntegrationTool(toolName: string, args: Record<string, unknown>,
|
|
24
|
+
export declare function executeRemoteIntegrationTool(toolName: string, args: Record<string, unknown>, context?: RemoteIntegrationToolExecutionContext): Promise<unknown>;
|
|
26
25
|
/**
|
|
27
26
|
* Sync integration config from veryfront.config.ts to the API.
|
|
28
27
|
* This is a full-replace operation. Called by the MCP server path
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"remote-tools.d.ts","sourceRoot":"","sources":["../../../src/src/integrations/remote-tools.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAgBvD,KAAK,qCAAqC,GAAG;IAC3C,
|
|
1
|
+
{"version":3,"file":"remote-tools.d.ts","sourceRoot":"","sources":["../../../src/src/integrations/remote-tools.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAgBvD,KAAK,qCAAqC,GAAG;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AA+JF;;;;;;;GAOG;AACH,wBAAsB,mCAAmC,IAAI,OAAO,CAClE,cAAc,EAAE,CACjB,CAoBA;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAKjE;AAED;;;GAGG;AACH,wBAAsB,4BAA4B,CAChD,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE,qCAAqC,GAC9C,OAAO,CAAC,OAAO,CAAC,CAclB;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,GACjE,OAAO,CAAC,IAAI,CAAC,CAwBf"}
|
|
@@ -11,12 +11,6 @@
|
|
|
11
11
|
import * as dntShim from "../../_dnt.shims.js";
|
|
12
12
|
import { logger } from "../utils/index.js";
|
|
13
13
|
import { getApiBaseUrlEnv, getApiTokenEnv } from "../config/env.js";
|
|
14
|
-
function normalizeRemoteExecutionContext(contextOrEndUserId) {
|
|
15
|
-
if (typeof contextOrEndUserId === "string") {
|
|
16
|
-
return { endUserId: contextOrEndUserId };
|
|
17
|
-
}
|
|
18
|
-
return contextOrEndUserId;
|
|
19
|
-
}
|
|
20
14
|
// ---------------------------------------------------------------------------
|
|
21
15
|
// Per-request token resolution
|
|
22
16
|
// ---------------------------------------------------------------------------
|
|
@@ -54,6 +48,39 @@ function parseJsonText(text) {
|
|
|
54
48
|
return undefined;
|
|
55
49
|
}
|
|
56
50
|
}
|
|
51
|
+
function stripLegacyEndUserIdFromConnectUrl(connectUrl) {
|
|
52
|
+
try {
|
|
53
|
+
const isAbsolute = /^[a-zA-Z][a-zA-Z\d+.-]*:/.test(connectUrl);
|
|
54
|
+
const isProtocolRelative = connectUrl.startsWith("//");
|
|
55
|
+
const isRootRelative = connectUrl.startsWith("/") && !isProtocolRelative;
|
|
56
|
+
const url = new URL(connectUrl, "https://veryfront.invalid");
|
|
57
|
+
if (!url.searchParams.has("endUserId"))
|
|
58
|
+
return connectUrl;
|
|
59
|
+
url.searchParams.delete("endUserId");
|
|
60
|
+
if (isAbsolute)
|
|
61
|
+
return url.toString();
|
|
62
|
+
if (isProtocolRelative)
|
|
63
|
+
return `//${url.host}${url.pathname}${url.search}${url.hash}`;
|
|
64
|
+
const relativeUrl = `${url.pathname}${url.search}${url.hash}`;
|
|
65
|
+
return isRootRelative ? relativeUrl : relativeUrl.slice(1);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return connectUrl;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function stripLegacyEndUserIdFromToolResult(value) {
|
|
72
|
+
if (Array.isArray(value)) {
|
|
73
|
+
return value.map(stripLegacyEndUserIdFromToolResult);
|
|
74
|
+
}
|
|
75
|
+
if (!value || typeof value !== "object")
|
|
76
|
+
return value;
|
|
77
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
|
|
78
|
+
key,
|
|
79
|
+
key === "connectUrl" && typeof entry === "string"
|
|
80
|
+
? stripLegacyEndUserIdFromConnectUrl(entry)
|
|
81
|
+
: stripLegacyEndUserIdFromToolResult(entry),
|
|
82
|
+
]));
|
|
83
|
+
}
|
|
57
84
|
async function fetchToolList(baseUrl, token) {
|
|
58
85
|
const response = await fetch(`${baseUrl}/integrations/tools/list`, {
|
|
59
86
|
method: "POST",
|
|
@@ -93,18 +120,19 @@ async function callRemoteTool(baseUrl, token, toolName, args, context) {
|
|
|
93
120
|
// If MCP CallToolResult format, extract content
|
|
94
121
|
if (result?.content && Array.isArray(result.content)) {
|
|
95
122
|
const text = joinCallToolText(result.content);
|
|
96
|
-
if (result.structuredContent)
|
|
97
|
-
return result.structuredContent;
|
|
123
|
+
if (result.structuredContent) {
|
|
124
|
+
return stripLegacyEndUserIdFromToolResult(result.structuredContent);
|
|
125
|
+
}
|
|
98
126
|
if (result.isError) {
|
|
99
127
|
// Try to preserve structured error data (e.g., authentication_required with connectUrl)
|
|
100
128
|
const parsed = parseJsonText(text);
|
|
101
129
|
if (parsed && typeof parsed === "object")
|
|
102
|
-
return parsed;
|
|
130
|
+
return stripLegacyEndUserIdFromToolResult(parsed);
|
|
103
131
|
return { error: "tool_error", message: text };
|
|
104
132
|
}
|
|
105
133
|
return parseJsonText(text) ?? text;
|
|
106
134
|
}
|
|
107
|
-
return result;
|
|
135
|
+
return stripLegacyEndUserIdFromToolResult(result);
|
|
108
136
|
}
|
|
109
137
|
// ---------------------------------------------------------------------------
|
|
110
138
|
// Public API — called by agent runtime per-request
|
|
@@ -153,13 +181,13 @@ export function isRemoteIntegrationTool(toolName) {
|
|
|
153
181
|
* Execute a remote integration tool via the API.
|
|
154
182
|
* Called by the agent runtime when a tool isn't found in the local registry.
|
|
155
183
|
*/
|
|
156
|
-
export async function executeRemoteIntegrationTool(toolName, args,
|
|
184
|
+
export async function executeRemoteIntegrationTool(toolName, args, context) {
|
|
157
185
|
const baseUrl = getApiBaseUrlEnv();
|
|
158
186
|
const token = resolveRequestToken();
|
|
159
187
|
if (!baseUrl || !token) {
|
|
160
188
|
return { error: "no_api_token", message: "No API token available" };
|
|
161
189
|
}
|
|
162
|
-
return callRemoteTool(baseUrl, token, toolName, args,
|
|
190
|
+
return callRemoteTool(baseUrl, token, toolName, args, context);
|
|
163
191
|
}
|
|
164
192
|
/**
|
|
165
193
|
* Sync integration config from veryfront.config.ts to the API.
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Reusable validation schemas
|
|
2
|
+
* Reusable validation schemas: common types (email, slug, URL, UUID,
|
|
3
3
|
* pagination) and primitives (file paths, hex colors, semver, timestamps),
|
|
4
4
|
* plus the `defineSchema` lazy-factory helper.
|
|
5
5
|
*
|
|
6
6
|
* `defineSchema` resolves the `SchemaValidator` contract on first use. The
|
|
7
7
|
* default zod-backed implementation lives in `@veryfront/ext-schema-zod` and is
|
|
8
|
-
* registered at app bootstrap by `createBuiltinExtensions()`.
|
|
9
|
-
* exercise schemas without going through full bootstrap import
|
|
10
|
-
* `./_test-setup.ts` to register the adapter directly.
|
|
8
|
+
* registered at app bootstrap by `createBuiltinExtensions()`.
|
|
11
9
|
*
|
|
12
10
|
* @example
|
|
13
11
|
* ```ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/src/schemas/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/src/schemas/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,yBAAyB,CAAC;AAGjC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvC,OAAO,EACL,gBAAgB,IAAI,gBAAgB,EACpC,KAAK,UAAU,EACf,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EACL,aAAa,EACb,KAAK,SAAS,EACd,KAAK,KAAK,EACV,kBAAkB,EAClB,cAAc,EACd,mBAAmB,EACnB,oBAAoB,EACpB,aAAa,EACb,uBAAuB,EACvB,YAAY,EACZ,aAAa,EACb,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,IAAI,EACT,KAAK,cAAc,EACnB,KAAK,GAAG,EACR,KAAK,IAAI,GACV,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,QAAQ,EACb,qBAAqB,EACrB,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,uBAAuB,EACvB,uBAAuB,EACvB,mBAAmB,EACnB,oBAAoB,EACpB,eAAe,EACf,kBAAkB,EAClB,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,MAAM,EACX,KAAK,SAAS,GACf,MAAM,iBAAiB,CAAC"}
|
package/esm/src/schemas/index.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Reusable validation schemas
|
|
2
|
+
* Reusable validation schemas: common types (email, slug, URL, UUID,
|
|
3
3
|
* pagination) and primitives (file paths, hex colors, semver, timestamps),
|
|
4
4
|
* plus the `defineSchema` lazy-factory helper.
|
|
5
5
|
*
|
|
6
6
|
* `defineSchema` resolves the `SchemaValidator` contract on first use. The
|
|
7
7
|
* default zod-backed implementation lives in `@veryfront/ext-schema-zod` and is
|
|
8
|
-
* registered at app bootstrap by `createBuiltinExtensions()`.
|
|
9
|
-
* exercise schemas without going through full bootstrap import
|
|
10
|
-
* `./_test-setup.ts` to register the adapter directly.
|
|
8
|
+
* registered at app bootstrap by `createBuiltinExtensions()`.
|
|
11
9
|
*
|
|
12
10
|
* @example
|
|
13
11
|
* ```ts
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-stream.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/request/agent-stream.handler.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AACpF,OAAO,EAEL,KAAK,+BAA+B,EACrC,MAAM,wCAAwC,CAAC;AAChD,OAAO,EACL,4BAA4B,EAE7B,MAAM,2CAA2C,CAAC;AAwBnD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"agent-stream.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/request/agent-stream.handler.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,yBAAyB,EAAE,MAAM,oCAAoC,CAAC;AACpF,OAAO,EAEL,KAAK,+BAA+B,EACrC,MAAM,wCAAwC,CAAC;AAChD,OAAO,EACL,4BAA4B,EAE7B,MAAM,2CAA2C,CAAC;AAwBnD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAC;AAUnG,MAAM,WAAW,sBACf,SAAQ,yBAAyB,EAAE,+BAA+B;IAClE,4BAA4B,CAAC,EAAE,OAAO,4BAA4B,CAAC;CACpE;AAmJD,qBAAa,kBAAmB,SAAQ,WAAW;IASrC,OAAO,CAAC,QAAQ,CAAC,IAAI;IARjC,QAAQ,EAAE,eAAe,CAMvB;gBAE2B,IAAI,GAAE,sBAAoC;IAIvE,OAAO,CAAC,sBAAsB;IAwBxB,MAAM,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CAoJxE"}
|
|
@@ -10,6 +10,7 @@ import { BaseHandler } from "../response/base.js";
|
|
|
10
10
|
import { PRIORITY_MEDIUM_API } from "../../../utils/constants/index.js";
|
|
11
11
|
import { getHostEnv } from "../../../platform/compat/process.js";
|
|
12
12
|
import { serverLogger } from "../../../utils/index.js";
|
|
13
|
+
import { EnvironmentVariableCache, fetchProjectEnvVars, runWithProjectEnv, } from "../../project-env/index.js";
|
|
13
14
|
const defaultDeps = {
|
|
14
15
|
...defaultChannelInvokeDeps,
|
|
15
16
|
sessionManager: agentRunSessionManager,
|
|
@@ -17,6 +18,46 @@ const defaultDeps = {
|
|
|
17
18
|
};
|
|
18
19
|
const logger = serverLogger.component("agent-stream-handler");
|
|
19
20
|
const RUN_STREAM_PATH_REGEX = /^\/api\/control-plane\/runs\/([^/]+)\/stream$/;
|
|
21
|
+
// Per-environment env var cache shared across all agent stream requests (60s TTL)
|
|
22
|
+
const _agentEnvVarCache = new EnvironmentVariableCache((environmentId, token, projectSlug) => {
|
|
23
|
+
const apiBaseUrl = getHostEnv("VERYFRONT_API_URL") ?? "https://api.veryfront.org";
|
|
24
|
+
return fetchProjectEnvVars(apiBaseUrl, projectSlug, environmentId, token);
|
|
25
|
+
});
|
|
26
|
+
// Cache: projectSlug → production environmentId (stable across restarts)
|
|
27
|
+
const _productionEnvIdCache = new Map();
|
|
28
|
+
async function _resolveProductionEnvironmentId(projectSlug, token) {
|
|
29
|
+
const cached = _productionEnvIdCache.get(projectSlug);
|
|
30
|
+
if (cached)
|
|
31
|
+
return cached;
|
|
32
|
+
const apiBaseUrl = getHostEnv("VERYFRONT_API_URL") ?? "https://api.veryfront.org";
|
|
33
|
+
try {
|
|
34
|
+
const res = await fetch(`${apiBaseUrl}/projects/${encodeURIComponent(projectSlug)}/environments`, { headers: { Authorization: `Bearer ${token}`, Accept: "application/json" } });
|
|
35
|
+
if (!res.ok) {
|
|
36
|
+
await res.body?.cancel();
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const body = await res.json();
|
|
40
|
+
const env = body.data?.find((e) => e.name === "production") ?? body.data?.[0];
|
|
41
|
+
if (!env?.id)
|
|
42
|
+
return null;
|
|
43
|
+
_productionEnvIdCache.set(projectSlug, env.id);
|
|
44
|
+
return env.id;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function buildAgentStreamEnv(input) {
|
|
51
|
+
const apiUrl = getHostEnv("VERYFRONT_API_URL") ?? "https://api.veryfront.org";
|
|
52
|
+
return {
|
|
53
|
+
...input.envVars,
|
|
54
|
+
// Framework-owned values must override project env to keep request-scoped
|
|
55
|
+
// credentials bound to trusted Veryfront endpoints and the current project.
|
|
56
|
+
...(input.proxyToken ? { VERYFRONT_API_TOKEN: input.proxyToken } : {}),
|
|
57
|
+
VERYFRONT_API_URL: apiUrl,
|
|
58
|
+
...(input.projectSlug ? { VERYFRONT_PROJECT_SLUG: input.projectSlug } : {}),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
20
61
|
function buildAgentSourceRunOptions(sourceContext) {
|
|
21
62
|
switch (sourceContext.type) {
|
|
22
63
|
case "branch":
|
|
@@ -137,7 +178,33 @@ export class AgentStreamHandler extends BaseHandler {
|
|
|
137
178
|
return this.respond(builder.json({ error: "Agent not found" }, 404));
|
|
138
179
|
}
|
|
139
180
|
const runtimeInput = toRuntimeRunAgentInput(payload);
|
|
140
|
-
|
|
181
|
+
// Load project env vars so source-defined MCP tool headers resolve
|
|
182
|
+
// via _getProjectEnv(). Control-plane requests don't go through the proxy and
|
|
183
|
+
// therefore don't carry x-environment-id, so we discover the production env ID
|
|
184
|
+
// from the API (one fetch per project per server lifetime, then cached).
|
|
185
|
+
let envVarsForAgent = {};
|
|
186
|
+
if (ctx.projectSlug && ctx.proxyToken) {
|
|
187
|
+
const environmentId = ctx.environmentId ??
|
|
188
|
+
await _resolveProductionEnvironmentId(ctx.projectSlug, ctx.proxyToken);
|
|
189
|
+
if (environmentId) {
|
|
190
|
+
envVarsForAgent = await _agentEnvVarCache.get(environmentId, ctx.proxyToken, ctx.projectSlug);
|
|
191
|
+
logger.debug("Agent stream env vars loaded", {
|
|
192
|
+
runId: payload.runId,
|
|
193
|
+
projectSlug: ctx.projectSlug,
|
|
194
|
+
environmentId,
|
|
195
|
+
count: Object.keys(envVarsForAgent).length,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
const runAgentStream = () => createRuntimeAgentStreamResponse(runtimeInput, agent, this.deps);
|
|
200
|
+
const shouldIsolateEnv = !!ctx.proxyToken;
|
|
201
|
+
const response = shouldIsolateEnv
|
|
202
|
+
? await runWithProjectEnv(buildAgentStreamEnv({
|
|
203
|
+
envVars: envVarsForAgent,
|
|
204
|
+
proxyToken: ctx.proxyToken,
|
|
205
|
+
projectSlug: ctx.projectSlug,
|
|
206
|
+
}), runAgentStream)
|
|
207
|
+
: await runAgentStream();
|
|
141
208
|
logger.info("Internal agent stream response created", {
|
|
142
209
|
runId: payload.runId,
|
|
143
210
|
threadId: payload.threadId,
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Server
|
|
2
|
+
* Server runtime APIs.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* For observability utilities, import from "#veryfront/observability" directly.
|
|
4
|
+
* Creates and runs a Veryfront server in tests, custom runtimes, and
|
|
5
|
+
* production adapters.
|
|
7
6
|
*
|
|
8
7
|
* @module server
|
|
9
8
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/src/server/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,yBAAyB,CAAC;AAKjC,OAAO,EACL,SAAS,EACT,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,cAAc,EACf,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,KAAK,gBAAgB,EACrB,KAAK,YAAY,EACjB,qBAAqB,EACrB,KAAK,4BAA4B,EAClC,MAAM,wBAAwB,CAAC;AAahC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,qBAAqB,EAAE,CAAC;AAC5D,OAAO,EACL,qBAAqB,EACrB,KAAK,4BAA4B,EACjC,KAAK,0BAA0B,EAC/B,wBAAwB,EACxB,KAAK,+BAA+B,EACpC,oBAAoB,EACpB,KAAK,2BAA2B,EAChC,KAAK,sBAAsB,EAC3B,KAAK,2BAA2B,EAChC,KAAK,4BAA4B,EACjC,KAAK,4BAA4B,EACjC,KAAK,oCAAoC,EACzC,KAAK,6BAA6B,EAClC,KAAK,iCAAiC,GACvC,MAAM,qBAAqB,CAAC;AAC7B,YAAY,EACV,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,YAAY,EACZ,4BAA4B,GAC7B,CAAC;AACF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,CAAC;AAC1B,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEjE,uEAAuE;AACvE,UAAU,iBAAiB;IACzB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2DAA2D;IAC3D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,sFAAsF;IACtF,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oFAAoF;IACpF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACnE;AAED,0CAA0C;AAC1C,MAAM,WAAW,mBAAoB,SAAQ,iBAAiB;IAC5D,IAAI,CAAC,EAAE,aAAa,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,iDAAiD;AACjD,MAAM,WAAW,0BAA2B,SAAQ,iBAAiB;IACnE,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,kDAAkD;IAClD,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,uGAAuG;IACvG,kBAAkB,CAAC,EAAE,SAAS,GAAG,YAAY,CAAC;IAC9C,oFAAoF;IACpF,eAAe,CAAC,EAAE,gBAAgB,CAAC;IACnC,4DAA4D;IAC5D,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED;;;GAGG;AACH,MAAM,MAAM,kBAAkB,GAAG,mBAAmB,GAAG,0BAA0B,CAAC;AAElF,uDAAuD;AACvD,MAAM,WAAW,eAAe;IAC9B,4DAA4D;IAC5D,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACrB,kCAAkC;IAClC,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;CACb;AAED,sEAAsE;AACtE,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG;IACrE;;;OAGG;IACH,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC;;;;OAIG;IACH,UAAU,EAAE,CAAC,EAAE,EAAE,SAAS,KAAK,IAAI,CAAC;CACrC,CAAC;AAwCF,wEAAwE;AACxE,wBAAsB,aAAa,CACjC,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,aAAa,GAAG,YAAY,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GACxF,OAAO,CAAC,gBAAgB,CAAC,CA8G3B;AAED;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD;;;;;GAKG;AACH,wBAAsB,WAAW,CAC/B,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,eAAe,CAAC,CAkD1B"}
|
package/esm/src/server/index.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Server
|
|
2
|
+
* Server runtime APIs.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* For observability utilities, import from "#veryfront/observability" directly.
|
|
4
|
+
* Creates and runs a Veryfront server in tests, custom runtimes, and
|
|
5
|
+
* production adapters.
|
|
7
6
|
*
|
|
8
7
|
* @module server
|
|
9
8
|
*
|
|
@@ -251,5 +250,4 @@ export async function startServer(options = {}) {
|
|
|
251
250
|
};
|
|
252
251
|
}
|
|
253
252
|
// Note: Wildcard re-exports removed to prevent circular dependency risks.
|
|
254
|
-
//
|
|
255
|
-
// Import from "#veryfront/observability" for tracing and metrics utilities.
|
|
253
|
+
// Use public routing, middleware, and observability modules for those surfaces.
|
package/esm/src/tool/types.d.ts
CHANGED
|
@@ -59,7 +59,7 @@ export interface ToolExecutionContext {
|
|
|
59
59
|
toolCallId?: string;
|
|
60
60
|
/** Project identity used by integration token resolution */
|
|
61
61
|
projectId?: string;
|
|
62
|
-
/**
|
|
62
|
+
/** Trusted runtime user identity supplied by hosted integration tooling */
|
|
63
63
|
endUserId?: string;
|
|
64
64
|
/** Abort signal for cooperative cancellation during long-running tool execution */
|
|
65
65
|
abortSignal?: AbortSignal;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-cache-state.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/http-cache-state.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"http-cache-state.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/http-cache-state.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAwCtE,wBAAgB,cAAc,IAAI,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,CAE9D;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAGpD;AAED,wBAAgB,yBAAyB,IAAI,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,CAEzE;AAED,gGAAgG;AAChG,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE;IACN,WAAW,CAAC,EAAE,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;IACnD,eAAe,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IACzC,sBAAsB,CAAC,EAAE,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;CAC/D,GAAG,IAAI,GACP,IAAI,CAcN"}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { LRUCache } from "../../utils/lru-wrapper.js";
|
|
10
10
|
import { HTTP_MODULE_CACHE_MAX_ENTRIES } from "../../utils/constants/cache.js";
|
|
11
|
+
import { registerCache } from "../../utils/memory/index.js";
|
|
11
12
|
import { inFlightHttpFetches, processingStackStorage } from "./in-flight-manager.js";
|
|
12
13
|
const defaultCachedPaths = new LRUCache({
|
|
13
14
|
maxEntries: HTTP_MODULE_CACHE_MAX_ENTRIES,
|
|
@@ -21,6 +22,24 @@ const defaultLastDistributedRefresh = new LRUCache({
|
|
|
21
22
|
let injectedCachedPaths = null;
|
|
22
23
|
let injectedProcessingStack = null;
|
|
23
24
|
let injectedLastDistributedRefresh = null;
|
|
25
|
+
function getCacheEntryCount(cache) {
|
|
26
|
+
const size = cache.size;
|
|
27
|
+
return typeof size === "number" ? size : -1;
|
|
28
|
+
}
|
|
29
|
+
registerCache("http-bundle-paths", () => ({
|
|
30
|
+
name: "http-bundle-paths",
|
|
31
|
+
entries: getCacheEntryCount(getCachedPaths()),
|
|
32
|
+
maxEntries: HTTP_MODULE_CACHE_MAX_ENTRIES,
|
|
33
|
+
}));
|
|
34
|
+
registerCache("http-bundle-ttl-refreshes", () => ({
|
|
35
|
+
name: "http-bundle-ttl-refreshes",
|
|
36
|
+
entries: getCacheEntryCount(getLastDistributedRefresh()),
|
|
37
|
+
maxEntries: HTTP_MODULE_CACHE_MAX_ENTRIES,
|
|
38
|
+
}));
|
|
39
|
+
registerCache("http-bundle-in-flight", () => ({
|
|
40
|
+
name: "http-bundle-in-flight",
|
|
41
|
+
entries: inFlightHttpFetches.size,
|
|
42
|
+
}));
|
|
24
43
|
export function getCachedPaths() {
|
|
25
44
|
return injectedCachedPaths ?? defaultCachedPaths;
|
|
26
45
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/mdx/esm-module-loader/cache/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAYH,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/mdx/esm-module-loader/cache/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAYH,OAAO,EAAE,QAAQ,EAAE,MAAM,kCAAkC,CAAC;AAI5D,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAG3C,MAAM,MAAM,iBAAiB,GACzB;IAAE,MAAM,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC/B;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,GAClB;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAI9D,eAAO,MAAM,kBAAkB,wBAE7B,CAAC;AA2HH,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAsBvF;AAED,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAezE;AAED,wBAAgB,oBAAoB,IAAI,IAAI,CAK3C;AAQD,2DAA2D;AAC3D,wBAAgB,kBAAkB,IAAI,OAAO,CAAC,IAAI,CAAC,CAElD;AAED,wBAAgB,qBAAqB,CAAC,YAAY,EAAE,MAAM,EAAE,GAAG,IAAI,CA0ElE;AAOD,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAevD;AAED,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAc1D;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAIzD;AAmBD,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,MAAM,EACnB,YAAY,CAAC,EAAE,MAAM,EAAE,oDAAoD;AAC3E,eAAe,CAAC,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,CAAA;CAAE,EAChE,YAAY,SAAwB,GACnC,OAAO,CAAC,iBAAiB,CAAC,CAwH5B"}
|
|
@@ -12,6 +12,7 @@ import { REACT_DEFAULT_VERSION } from "../../../../utils/constants/cdn.js";
|
|
|
12
12
|
import { isNotFoundError } from "../../../../platform/compat/fs.js";
|
|
13
13
|
import { LOG_PREFIX_MDX_LOADER } from "../constants.js";
|
|
14
14
|
import { LRUCache } from "../../../../utils/lru-wrapper.js";
|
|
15
|
+
import { registerCache } from "../../../../utils/memory/index.js";
|
|
15
16
|
import { buildMdxEsmPathCacheKey, MDX_ESM_ALL_FILE_URL_PATTERN_SOURCE } from "../cache-format.js";
|
|
16
17
|
import { ensureMdxModuleDependencies } from "../module-fetcher/dependency-recovery.js";
|
|
17
18
|
export { getLocalFs } from "./local-fs.js";
|
|
@@ -115,6 +116,22 @@ function hasUnresolvedVfModules(code) {
|
|
|
115
116
|
}
|
|
116
117
|
const modulePathCaches = new Map();
|
|
117
118
|
const modulePathCacheLoaded = new Set();
|
|
119
|
+
function getModulePathCacheEntryCount() {
|
|
120
|
+
let entries = 0;
|
|
121
|
+
for (const cache of modulePathCaches.values())
|
|
122
|
+
entries += cache.size;
|
|
123
|
+
return entries;
|
|
124
|
+
}
|
|
125
|
+
registerCache("mdx-esm-path-caches", () => ({
|
|
126
|
+
name: "mdx-esm-path-caches",
|
|
127
|
+
entries: getModulePathCacheEntryCount(),
|
|
128
|
+
cacheDirs: modulePathCaches.size,
|
|
129
|
+
}));
|
|
130
|
+
registerCache("mdx-esm-verified-deps", () => ({
|
|
131
|
+
name: "mdx-esm-verified-deps",
|
|
132
|
+
entries: verifiedModuleDeps.size,
|
|
133
|
+
maxEntries: MAX_VERIFIED_MODULE_DEPS,
|
|
134
|
+
}));
|
|
118
135
|
export async function getModulePathCache(cacheDir) {
|
|
119
136
|
const existing = modulePathCaches.get(cacheDir);
|
|
120
137
|
if (existing && modulePathCacheLoaded.has(cacheDir))
|
package/esm/src/utils/index.d.ts
CHANGED
package/esm/src/utils/index.js
CHANGED
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module utils/memory
|
|
5
5
|
*/
|
|
6
|
-
export { type CacheStats, checkMemoryPressure, clearAllCaches, forceGC, type GCStats, getCacheStats, getHeapStats, getMemorySnapshot, type HeapStats, type MemorySnapshot, registerCache, setHeapWarningThreshold, startMemoryMonitoring, stopMemoryMonitoring, unregisterCache, } from "./profiler.js";
|
|
6
|
+
export { type CacheStats, checkMemoryPressure, clearAllCaches, forceGC, type GCStats, getCacheStats, getHeapStats, getMemoryMonitoringLogContext, getMemorySnapshot, getTopCacheStats, type HeapStats, type MemoryMonitoringLogContext, type MemorySnapshot, type MonitoringCacheStats, registerCache, setHeapWarningThreshold, startMemoryMonitoring, stopMemoryMonitoring, unregisterCache, } from "./profiler.js";
|
|
7
7
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/utils/memory/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,KAAK,UAAU,EACf,mBAAmB,EACnB,cAAc,EACd,OAAO,EACP,KAAK,OAAO,EACZ,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,aAAa,EACb,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,EACpB,eAAe,GAChB,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/utils/memory/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EACL,KAAK,UAAU,EACf,mBAAmB,EACnB,cAAc,EACd,OAAO,EACP,KAAK,OAAO,EACZ,aAAa,EACb,YAAY,EACZ,6BAA6B,EAC7B,iBAAiB,EACjB,gBAAgB,EAChB,KAAK,SAAS,EACd,KAAK,0BAA0B,EAC/B,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,aAAa,EACb,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,EACpB,eAAe,GAChB,MAAM,eAAe,CAAC"}
|
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
*
|
|
4
4
|
* @module utils/memory
|
|
5
5
|
*/
|
|
6
|
-
export { checkMemoryPressure, clearAllCaches, forceGC, getCacheStats, getHeapStats, getMemorySnapshot, registerCache, setHeapWarningThreshold, startMemoryMonitoring, stopMemoryMonitoring, unregisterCache, } from "./profiler.js";
|
|
6
|
+
export { checkMemoryPressure, clearAllCaches, forceGC, getCacheStats, getHeapStats, getMemoryMonitoringLogContext, getMemorySnapshot, getTopCacheStats, registerCache, setHeapWarningThreshold, startMemoryMonitoring, stopMemoryMonitoring, unregisterCache, } from "./profiler.js";
|
|
@@ -21,6 +21,22 @@ export interface MemorySnapshot {
|
|
|
21
21
|
totalCacheEntries: number;
|
|
22
22
|
gcStats?: GCStats;
|
|
23
23
|
}
|
|
24
|
+
export interface MonitoringCacheStats {
|
|
25
|
+
name: string;
|
|
26
|
+
entries: number;
|
|
27
|
+
maxEntries?: number;
|
|
28
|
+
estimatedSizeBytes?: number;
|
|
29
|
+
backend?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface MemoryMonitoringLogContext {
|
|
32
|
+
heapUsedMB: number;
|
|
33
|
+
heapTotalMB: number;
|
|
34
|
+
heapLimitMB: number;
|
|
35
|
+
heapUsedPercent: number;
|
|
36
|
+
rssMB?: number;
|
|
37
|
+
totalCacheEntries: number;
|
|
38
|
+
topCaches: MonitoringCacheStats[];
|
|
39
|
+
}
|
|
24
40
|
export interface GCStats {
|
|
25
41
|
majorGCs: number;
|
|
26
42
|
minorGCs: number;
|
|
@@ -31,6 +47,8 @@ export declare function unregisterCache(name: string): void;
|
|
|
31
47
|
export declare function getHeapStats(): HeapStats;
|
|
32
48
|
export declare function getCacheStats(): CacheStats[];
|
|
33
49
|
export declare function getMemorySnapshot(): MemorySnapshot;
|
|
50
|
+
export declare function getTopCacheStats(caches: CacheStats[], limit?: number): MonitoringCacheStats[];
|
|
51
|
+
export declare function getMemoryMonitoringLogContext(snapshot: MemorySnapshot, topCacheLimit?: number): MemoryMonitoringLogContext;
|
|
34
52
|
export declare function forceGC(): Promise<boolean>;
|
|
35
53
|
export declare function startMemoryMonitoring(intervalMs?: number): void;
|
|
36
54
|
export declare function stopMemoryMonitoring(): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profiler.d.ts","sourceRoot":"","sources":["../../../../src/src/utils/memory/profiler.ts"],"names":[],"mappings":"AAyBA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAMD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,UAAU,GAAG,IAAI,CAG5E;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAElD;AAED,wBAAgB,YAAY,IAAI,SAAS,CAiBxC;AAkBD,wBAAgB,aAAa,IAAI,UAAU,EAAE,CAa5C;AAED,wBAAgB,iBAAiB,IAAI,cAAc,CAWlD;AAED,wBAAsB,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAUhD;AAED,wBAAgB,qBAAqB,CAAC,UAAU,SAAwC,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"profiler.d.ts","sourceRoot":"","sources":["../../../../src/src/utils/memory/profiler.ts"],"names":[],"mappings":"AAyBA,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,SAAS,CAAC;IAChB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,0BAA0B;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,EAAE,oBAAoB,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,OAAO;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAMD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,UAAU,GAAG,IAAI,CAG5E;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAElD;AAED,wBAAgB,YAAY,IAAI,SAAS,CAiBxC;AAkBD,wBAAgB,aAAa,IAAI,UAAU,EAAE,CAa5C;AAED,wBAAgB,iBAAiB,IAAI,cAAc,CAWlD;AAcD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,EAAE,KAAK,SAAI,GAAG,oBAAoB,EAAE,CAMxF;AAED,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,cAAc,EACxB,aAAa,SAAI,GAChB,0BAA0B,CAY5B;AAED,wBAAsB,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,CAUhD;AAED,wBAAgB,qBAAqB,CAAC,UAAU,SAAwC,GAAG,IAAI,CAiC9F;AAED,wBAAgB,oBAAoB,IAAI,IAAI,CAM3C;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE/D;AAED,wBAAgB,cAAc,IAAI,IAAI,CAMrC;AAwBD,wBAAgB,mBAAmB,IAAI;IACrC,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;CACzB,CAiBA;AAED,YAAY,EAAE,cAAc,IAAI,kBAAkB,EAAE,CAAC"}
|
|
@@ -79,6 +79,36 @@ export function getMemorySnapshot() {
|
|
|
79
79
|
totalCacheEntries,
|
|
80
80
|
};
|
|
81
81
|
}
|
|
82
|
+
function toMonitoringCacheStats(cache) {
|
|
83
|
+
return {
|
|
84
|
+
name: cache.name,
|
|
85
|
+
entries: cache.entries,
|
|
86
|
+
...(cache.maxEntries !== undefined ? { maxEntries: cache.maxEntries } : {}),
|
|
87
|
+
...(cache.estimatedSizeBytes !== undefined
|
|
88
|
+
? { estimatedSizeBytes: cache.estimatedSizeBytes }
|
|
89
|
+
: {}),
|
|
90
|
+
...(cache.backend !== undefined ? { backend: cache.backend } : {}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export function getTopCacheStats(caches, limit = 8) {
|
|
94
|
+
return [...caches]
|
|
95
|
+
.filter((cache) => cache.entries > 0)
|
|
96
|
+
.sort((a, b) => b.entries - a.entries || a.name.localeCompare(b.name))
|
|
97
|
+
.slice(0, limit)
|
|
98
|
+
.map(toMonitoringCacheStats);
|
|
99
|
+
}
|
|
100
|
+
export function getMemoryMonitoringLogContext(snapshot, topCacheLimit = 8) {
|
|
101
|
+
const { heap } = snapshot;
|
|
102
|
+
return {
|
|
103
|
+
heapUsedMB: heap.usedHeapSizeMB,
|
|
104
|
+
heapTotalMB: heap.totalHeapSizeMB,
|
|
105
|
+
heapLimitMB: heap.heapSizeLimitMB,
|
|
106
|
+
heapUsedPercent: heap.heapUsedPercent,
|
|
107
|
+
rssMB: heap.rss,
|
|
108
|
+
totalCacheEntries: snapshot.totalCacheEntries,
|
|
109
|
+
topCaches: getTopCacheStats(snapshot.caches, topCacheLimit),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
82
112
|
export async function forceGC() {
|
|
83
113
|
try {
|
|
84
114
|
const buffer = new Uint8Array(100 * 1024 * 1024);
|
|
@@ -98,19 +128,14 @@ export function startMemoryMonitoring(intervalMs = DEFAULT_MEMORY_MONITORING_INT
|
|
|
98
128
|
memoryCheckInterval = dntShim.setInterval(() => {
|
|
99
129
|
const snapshot = getMemorySnapshot();
|
|
100
130
|
const { heap } = snapshot;
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
heapTotalMB: heap.totalHeapSizeMB,
|
|
104
|
-
heapLimitMB: heap.heapSizeLimitMB,
|
|
105
|
-
heapUsedPercent: heap.heapUsedPercent,
|
|
106
|
-
rssMB: heap.rss,
|
|
107
|
-
totalCacheEntries: snapshot.totalCacheEntries,
|
|
108
|
-
});
|
|
131
|
+
const monitoringContext = getMemoryMonitoringLogContext(snapshot);
|
|
132
|
+
logger.info("Memory status", monitoringContext);
|
|
109
133
|
const thresholdPercent = heapGrowthWarningThreshold * 100;
|
|
110
134
|
if (heap.heapUsedPercent > thresholdPercent) {
|
|
111
135
|
logger.warn("HIGH MEMORY USAGE", {
|
|
112
136
|
heapUsedPercent: heap.heapUsedPercent,
|
|
113
137
|
threshold: thresholdPercent,
|
|
138
|
+
topCaches: monitoringContext.topCaches,
|
|
114
139
|
caches: snapshot.caches.map((c) => `${c.name}: ${c.entries}`).join(", "),
|
|
115
140
|
});
|
|
116
141
|
}
|
|
@@ -119,6 +144,7 @@ export function startMemoryMonitoring(intervalMs = DEFAULT_MEMORY_MONITORING_INT
|
|
|
119
144
|
logger.warn("Rapid heap growth detected", {
|
|
120
145
|
growthMB: heapGrowthMB,
|
|
121
146
|
intervalMs,
|
|
147
|
+
topCaches: monitoringContext.topCaches,
|
|
122
148
|
});
|
|
123
149
|
}
|
|
124
150
|
lastHeapUsed = heap.usedHeapSizeMB;
|