From 82595b730860dc3b8a6eb8f1175c28a26108bb4b Mon Sep 17 00:00:00 2001 From: Justin Date: Tue, 22 Sep 2026 10:56:23 -0400 Subject: [PATCH] feat: add InMemoryHandler for offline handlers without a filesystem Custom offline handlers are documented, but building one is only possible if the environment document comes from disk: the engine needs the API's JSON rehydrated by buildEnvironmentModel, which is not exported from the package root and cannot be deep-imported because the exports map has no subpaths. The only public route is LocalFileHandler. InMemoryHandler takes an already-parsed environment document, so a handler can load it from anywhere - an object store, a cache, a database, or a document embedded in the deployment - and hand it straight to the SDK. LocalFileHandler now extends it and keeps its existing behaviour and public surface, so reading from a file is the filesystem-shaped case of loading a document that is already in memory. Co-Authored-By: Claude Fable 5 --- index.ts | 2 +- sdk/offline_handlers.ts | 17 +++++++++++++---- tests/sdk/offline-handlers.test.ts | 14 +++++++++++++- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/index.ts b/index.ts index cf24b7c..cc719b8 100644 --- a/index.ts +++ b/index.ts @@ -11,7 +11,7 @@ export { Flagsmith } from './sdk/index.js'; -export { BaseOfflineHandler, LocalFileHandler } from './sdk/offline_handlers.js'; +export { BaseOfflineHandler, InMemoryHandler, LocalFileHandler } from './sdk/offline_handlers.js'; export { FlagsmithConfig, FlagsmithValue, TraitConfig } from './sdk/types.js'; diff --git a/sdk/offline_handlers.ts b/sdk/offline_handlers.ts index 1407460..fa9918a 100644 --- a/sdk/offline_handlers.ts +++ b/sdk/offline_handlers.ts @@ -8,15 +8,24 @@ export class BaseOfflineHandler { } } -export class LocalFileHandler extends BaseOfflineHandler { +/** + * Handler for an environment document already in memory, as returned by the + * `/api/v1/environment-document` endpoint and parsed. + */ +export class InMemoryHandler extends BaseOfflineHandler { environment: EnvironmentModel; - constructor(environment_document_path: string) { + constructor(environment_document: object) { super(); - const environment_document = fs.readFileSync(environment_document_path, 'utf8'); - this.environment = buildEnvironmentModel(JSON.parse(environment_document)); + this.environment = buildEnvironmentModel(environment_document); } getEnvironment(): EnvironmentModel { return this.environment; } } + +export class LocalFileHandler extends InMemoryHandler { + constructor(environment_document_path: string) { + super(JSON.parse(fs.readFileSync(environment_document_path, 'utf8'))); + } +} diff --git a/tests/sdk/offline-handlers.test.ts b/tests/sdk/offline-handlers.test.ts index 1362ceb..a6c06c6 100644 --- a/tests/sdk/offline-handlers.test.ts +++ b/tests/sdk/offline-handlers.test.ts @@ -1,5 +1,5 @@ import * as fs from 'fs'; -import { LocalFileHandler } from '../../sdk/offline_handlers.js'; +import { InMemoryHandler, LocalFileHandler } from '../../sdk/offline_handlers.js'; import { EnvironmentModel } from '../../flagsmith-engine/index.js'; import * as offlineEnvironment from './data/offline-environment.json'; @@ -32,3 +32,15 @@ test.skipIf(isEsmBuild)('local file handler', () => { // Restore the original implementation of fs.readFileSync readFileSyncMock.mockRestore(); }); + +test.skipIf(isEsmBuild)('in memory handler', () => { + // Given + const inMemoryHandler = new InMemoryHandler(offlineEnvironment); + + // When + const environmentModel = inMemoryHandler.getEnvironment(); + + // Then + expect(environmentModel).toBeInstanceOf(EnvironmentModel); + expect(environmentModel.apiKey).toBe('B62qaMZNwfiqT76p38ggrQ'); +});