From 3946c7b907f908009d017375a445e37bfba29174 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:30:22 +0000 Subject: [PATCH 1/3] Initial plan From 1d07ae45d79f494c7605b387c3c9d775db8c94d1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:37:44 +0000 Subject: [PATCH 2/3] fix: detect serverless runtime in auto mode, skip file persistence with warning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In serverless environments (Vercel, AWS Lambda, Netlify, Azure Functions, Google Cloud Functions, Deno Deploy), auto persistence mode now disables file-system persistence and emits a warning instead of silently choosing an adapter that will lose data. - Add isServerlessEnvironment() detection via well-known env vars - Auto mode: browser → localStorage, serverless → disabled+warn, Node.js → file - Explicit 'file' or custom adapter still works in serverless (user's choice) - Update schema JSDoc to document serverless behavior - Add 4 new persistence tests for serverless scenarios Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- .../driver-memory/src/memory-driver.ts | 53 +++++++++++- .../src/persistence/persistence.test.ts | 83 +++++++++++++++++++ packages/spec/src/data/driver/memory.zod.ts | 21 ++++- 3 files changed, 153 insertions(+), 4 deletions(-) diff --git a/packages/plugins/driver-memory/src/memory-driver.ts b/packages/plugins/driver-memory/src/memory-driver.ts index 61e1a1aa29..ef687c4a28 100644 --- a/packages/plugins/driver-memory/src/memory-driver.ts +++ b/packages/plugins/driver-memory/src/memory-driver.ts @@ -33,7 +33,7 @@ export interface InMemoryDriverConfig { logger?: Logger; /** * Persistence configuration. Defaults to `'auto'`. - * - `'auto'` (default) — Auto-detect environment (browser → localStorage, Node.js → file) + * - `'auto'` (default) — Auto-detect environment (browser → localStorage, Node.js → file, serverless → disabled) * - `'file'` — File-system persistence with defaults (Node.js only) * - `'local'` — localStorage persistence with defaults (Browser only) * - `{ type: 'file', path?: string, autoSaveInterval?: number }` — File-system with options @@ -41,6 +41,10 @@ export interface InMemoryDriverConfig { * - `{ type: 'auto', path?: string, key?: string, autoSaveInterval?: number }` — Auto-detect with options * - `{ adapter: PersistenceAdapterInterface }` — Custom adapter * - `false` — Disable persistence (pure in-memory) + * + * ⚠️ In serverless environments (Vercel, AWS Lambda, Netlify, etc.), + * auto mode disables file persistence to prevent silent data loss. + * Use `persistence: false` or supply a custom adapter for serverless deployments. */ persistence?: string | false | { type?: 'file' | 'local' | 'auto'; @@ -932,10 +936,43 @@ export class InMemoryDriver implements DriverInterface { return typeof globalThis.localStorage !== 'undefined'; } + /** + * Detect whether the current runtime is a serverless/edge environment. + * + * Checks well-known environment variables set by serverless platforms: + * - `VERCEL` / `VERCEL_ENV` — Vercel Functions / Edge + * - `AWS_LAMBDA_FUNCTION_NAME` — AWS Lambda + * - `NETLIFY` — Netlify Functions + * - `FUNCTIONS_WORKER_RUNTIME` — Azure Functions + * - `K_SERVICE` — Google Cloud Run / Cloud Functions + * - `FUNCTION_TARGET` — Google Cloud Functions (Node.js) + * - `DENO_DEPLOYMENT_ID` — Deno Deploy + */ + private isServerlessEnvironment(): boolean { + if (typeof globalThis.process === 'undefined' || !globalThis.process.env) { + return false; + } + const env = globalThis.process.env; + return !!( + env.VERCEL || + env.VERCEL_ENV || + env.AWS_LAMBDA_FUNCTION_NAME || + env.NETLIFY || + env.FUNCTIONS_WORKER_RUNTIME || + env.K_SERVICE || + env.FUNCTION_TARGET || + env.DENO_DEPLOYMENT_ID + ); + } + /** * Initialize the persistence adapter based on configuration. * Defaults to 'auto' when persistence is not specified. * Use `persistence: false` to explicitly disable persistence. + * + * In serverless environments (Vercel, AWS Lambda, etc.), auto mode disables + * file-system persistence and emits a warning. Use `persistence: false` or + * supply a custom adapter for serverless-safe operation. */ private async initPersistence(): Promise { const persistence = this.config.persistence === undefined ? 'auto' : this.config.persistence; @@ -947,6 +984,13 @@ export class InMemoryDriver implements DriverInterface { const { LocalStoragePersistenceAdapter } = await import('./persistence/local-storage-adapter.js'); this.persistenceAdapter = new LocalStoragePersistenceAdapter(); this.logger.debug('Auto-detected browser environment, using localStorage persistence'); + } else if (this.isServerlessEnvironment()) { + this.logger.warn( + 'Serverless environment detected — file-system persistence is disabled in auto mode. ' + + 'Data will NOT be persisted across function invocations. ' + + 'Set persistence: false to silence this warning, or provide a custom adapter ' + + '(e.g. Upstash Redis, Vercel KV) via persistence: { adapter: yourAdapter }.' + ); } else { const { FileSystemPersistenceAdapter } = await import('./persistence/file-adapter.js'); this.persistenceAdapter = new FileSystemPersistenceAdapter(); @@ -971,6 +1015,13 @@ export class InMemoryDriver implements DriverInterface { key: persistence.key, }); this.logger.debug('Auto-detected browser environment, using localStorage persistence'); + } else if (this.isServerlessEnvironment()) { + this.logger.warn( + 'Serverless environment detected — file-system persistence is disabled in auto mode. ' + + 'Data will NOT be persisted across function invocations. ' + + 'Set persistence: false to silence this warning, or provide a custom adapter ' + + '(e.g. Upstash Redis, Vercel KV) via persistence: { adapter: yourAdapter }.' + ); } else { const { FileSystemPersistenceAdapter } = await import('./persistence/file-adapter.js'); this.persistenceAdapter = new FileSystemPersistenceAdapter({ diff --git a/packages/plugins/driver-memory/src/persistence/persistence.test.ts b/packages/plugins/driver-memory/src/persistence/persistence.test.ts index 1c24da1eea..073ebd3775 100644 --- a/packages/plugins/driver-memory/src/persistence/persistence.test.ts +++ b/packages/plugins/driver-memory/src/persistence/persistence.test.ts @@ -212,4 +212,87 @@ describe('InMemoryDriver Persistence', () => { await driver2.disconnect(); }); }); + + describe('Serverless Environment Detection', () => { + const serverlessEnvVars = [ + 'VERCEL', + 'VERCEL_ENV', + 'AWS_LAMBDA_FUNCTION_NAME', + 'NETLIFY', + 'FUNCTIONS_WORKER_RUNTIME', + 'K_SERVICE', + 'FUNCTION_TARGET', + 'DENO_DEPLOYMENT_ID', + ]; + + afterEach(() => { + // Clean up all serverless env vars after each test + for (const key of serverlessEnvVars) { + delete process.env[key]; + } + }); + + it('should disable file persistence in auto mode when VERCEL env is set', async () => { + process.env.VERCEL = '1'; + const filePath = path.join(TEST_DATA_DIR, 'serverless-test.json'); + const driver = new InMemoryDriver({ + persistence: { type: 'auto', path: filePath }, + }); + await driver.connect(); + await driver.create('items', { id: '1', name: 'Widget' }); + await driver.flush(); + await driver.disconnect(); + + // File should NOT have been created because auto mode skips file persistence in serverless + expect(fs.existsSync(filePath)).toBe(false); + }); + + it('should disable file persistence in auto shorthand mode when AWS_LAMBDA_FUNCTION_NAME is set', async () => { + process.env.AWS_LAMBDA_FUNCTION_NAME = 'my-function'; + const driver = new InMemoryDriver({ persistence: 'auto' }); + await driver.connect(); + await driver.create('items', { id: '1', name: 'Widget' }); + + // Should work as pure in-memory without errors + const items = await driver.find('items', { object: 'items' }); + expect(items).toHaveLength(1); + + await driver.disconnect(); + }); + + it('should still allow explicit file persistence in serverless if user requests it', async () => { + process.env.VERCEL = '1'; + const filePath = path.join(TEST_DATA_DIR, 'explicit-file-serverless.json'); + const driver = new InMemoryDriver({ + persistence: { type: 'file', path: filePath, autoSaveInterval: 100 }, + }); + await driver.connect(); + await driver.create('items', { id: '1', name: 'Widget' }); + await driver.flush(); + await driver.disconnect(); + + // Explicit 'file' type should still create the file even in serverless + expect(fs.existsSync(filePath)).toBe(true); + }); + + it('should still allow custom adapter in serverless', async () => { + process.env.NETLIFY = 'true'; + const stored: Record = {}; + const customAdapter = { + load: async () => Object.keys(stored).length > 0 ? { ...stored } : null, + save: async (db: Record) => { + for (const [k, v] of Object.entries(db)) { stored[k] = [...v]; } + }, + flush: async () => {}, + }; + + const driver = new InMemoryDriver({ persistence: { adapter: customAdapter } }); + await driver.connect(); + await driver.create('items', { id: '1', name: 'Widget' }); + await driver.disconnect(); + + expect(stored.items).toBeDefined(); + expect(stored.items).toHaveLength(1); + }); + }); }); diff --git a/packages/spec/src/data/driver/memory.zod.ts b/packages/spec/src/data/driver/memory.zod.ts index c714499fad..c323c1b6ea 100644 --- a/packages/spec/src/data/driver/memory.zod.ts +++ b/packages/spec/src/data/driver/memory.zod.ts @@ -43,7 +43,8 @@ export type PersistenceAdapter = z.infer; * - `file`: Persist to disk file (Node.js only). * - `local`: Persist to localStorage (Browser only). * - `auto`: Auto-detect environment and choose the best strategy. - * Uses `localStorage` in browser environments and `file` in Node.js. + * Uses `localStorage` in browser environments, `file` in standard Node.js, + * and disables persistence with a warning in serverless/edge runtimes. */ export const PersistenceTypeSchema = z.enum(['file', 'local', 'auto']).describe('Persistence backend type'); @@ -89,7 +90,14 @@ export type CustomPersistenceConfig = z.infer; * Supports shorthand strings and detailed object configs: * - `'file'` — File-system persistence with defaults (Node.js) * - `'local'` — localStorage persistence with defaults (Browser) - * - `'auto'` — Auto-detect environment (browser → localStorage, Node.js → file) + * - `'auto'` — Auto-detect environment (browser → localStorage, Node.js → file, serverless → disabled) * - `{ type: 'file', path?: string }` — File-system with custom path * - `{ type: 'local', key?: string }` — localStorage with custom key * - `{ type: 'auto', path?: string, key?: string }` — Auto-detect with overrides @@ -191,6 +199,11 @@ export const MemoryConfigSchema = z.object({ * - `{ adapter: PersistenceAdapter }`: Custom persistence adapter * - `false`: Disable persistence (pure in-memory, data lost on disconnect) * + * **⚠️ Serverless / Edge environments (Vercel, AWS Lambda, Netlify, etc.):** + * Auto mode detects serverless runtimes and disables file persistence to prevent + * silent data loss. Set `persistence: false` to opt-in to pure in-memory mode, + * or supply a custom adapter (e.g. Upstash Redis, Vercel KV) for durable storage. + * * @example * // Auto-detect environment (default) * new InMemoryDriver() @@ -200,6 +213,8 @@ export const MemoryConfigSchema = z.object({ * new InMemoryDriver({ persistence: 'local' }) * // Pure memory (no persistence) * new InMemoryDriver({ persistence: false }) + * // Custom adapter for serverless + * new InMemoryDriver({ persistence: { adapter: upstashAdapter } }) */ persistence: MemoryPersistenceConfigSchema.or(z.literal(false)).default('auto').describe('Persistence configuration (defaults to auto-detect)'), From ebb267299fd2b27157cc16bea12bf403aa6b7f47 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:40:58 +0000 Subject: [PATCH 3/3] refactor: extract serverless warning to static constant, improve JSDoc Address code review feedback: - Extract duplicated warning message to static SERVERLESS_PERSISTENCE_WARNING constant - Add JSDoc note about process/env unavailability in non-Node.js runtimes Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com> --- .../driver-memory/src/memory-driver.ts | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/plugins/driver-memory/src/memory-driver.ts b/packages/plugins/driver-memory/src/memory-driver.ts index ef687c4a28..dadfddcd22 100644 --- a/packages/plugins/driver-memory/src/memory-driver.ts +++ b/packages/plugins/driver-memory/src/memory-driver.ts @@ -947,6 +947,9 @@ export class InMemoryDriver implements DriverInterface { * - `K_SERVICE` — Google Cloud Run / Cloud Functions * - `FUNCTION_TARGET` — Google Cloud Functions (Node.js) * - `DENO_DEPLOYMENT_ID` — Deno Deploy + * + * Returns `false` when `process` or `process.env` is unavailable + * (e.g. browser or edge runtimes without a Node.js process object). */ private isServerlessEnvironment(): boolean { if (typeof globalThis.process === 'undefined' || !globalThis.process.env) { @@ -965,6 +968,12 @@ export class InMemoryDriver implements DriverInterface { ); } + private static readonly SERVERLESS_PERSISTENCE_WARNING = + 'Serverless environment detected — file-system persistence is disabled in auto mode. ' + + 'Data will NOT be persisted across function invocations. ' + + 'Set persistence: false to silence this warning, or provide a custom adapter ' + + '(e.g. Upstash Redis, Vercel KV) via persistence: { adapter: yourAdapter }.'; + /** * Initialize the persistence adapter based on configuration. * Defaults to 'auto' when persistence is not specified. @@ -985,12 +994,7 @@ export class InMemoryDriver implements DriverInterface { this.persistenceAdapter = new LocalStoragePersistenceAdapter(); this.logger.debug('Auto-detected browser environment, using localStorage persistence'); } else if (this.isServerlessEnvironment()) { - this.logger.warn( - 'Serverless environment detected — file-system persistence is disabled in auto mode. ' + - 'Data will NOT be persisted across function invocations. ' + - 'Set persistence: false to silence this warning, or provide a custom adapter ' + - '(e.g. Upstash Redis, Vercel KV) via persistence: { adapter: yourAdapter }.' - ); + this.logger.warn(InMemoryDriver.SERVERLESS_PERSISTENCE_WARNING); } else { const { FileSystemPersistenceAdapter } = await import('./persistence/file-adapter.js'); this.persistenceAdapter = new FileSystemPersistenceAdapter(); @@ -1016,12 +1020,7 @@ export class InMemoryDriver implements DriverInterface { }); this.logger.debug('Auto-detected browser environment, using localStorage persistence'); } else if (this.isServerlessEnvironment()) { - this.logger.warn( - 'Serverless environment detected — file-system persistence is disabled in auto mode. ' + - 'Data will NOT be persisted across function invocations. ' + - 'Set persistence: false to silence this warning, or provide a custom adapter ' + - '(e.g. Upstash Redis, Vercel KV) via persistence: { adapter: yourAdapter }.' - ); + this.logger.warn(InMemoryDriver.SERVERLESS_PERSISTENCE_WARNING); } else { const { FileSystemPersistenceAdapter } = await import('./persistence/file-adapter.js'); this.persistenceAdapter = new FileSystemPersistenceAdapter({