From 12b4489a2285121b38d36b61cfcb9adcd98ea68c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 17:57:57 +0000 Subject: [PATCH 01/21] feat: add server-side TestHook and migrate server-node/shopify-oxygen to shared utils [SDK-2009] - Add ServerSideTestHook using got for HTTP callbacks in contract-test-utils - Create src/server.ts barrel re-exporting index + ServerSideTestHook - Add tsconfig.server.json excluding client-side sources - Add ./server subpath export pointing to compiled dist/server.js - Add got and @launchdarkly/node-server-sdk as runtime dependencies - Migrate server-node contract tests to import from shared package - Migrate Shopify Oxygen to use shared ClientPool - Update Shopify Oxygen tsup.config.ts with noExternal for shared package Co-Authored-By: Steven Zhang --- .../server-node/contract-tests/package.json | 1 + .../contract-tests/src/sdkClientEntity.ts | 2 +- .../contract-tests/package.json | 1 + .../contract-tests/src/utils/clientPool.ts | 38 +++++-------------- .../contract-tests/tsup.config.ts | 1 + .../tooling/contract-test-utils/package.json | 13 ++++++- .../src/server-side}/TestHook.ts | 0 .../tooling/contract-test-utils/src/server.ts | 9 +++++ .../tooling/contract-test-utils/tsconfig.json | 6 ++- .../contract-test-utils/tsconfig.server.json | 5 +++ 10 files changed, 44 insertions(+), 32 deletions(-) rename packages/{sdk/server-node/contract-tests/src => tooling/contract-test-utils/src/server-side}/TestHook.ts (100%) create mode 100644 packages/tooling/contract-test-utils/src/server.ts create mode 100644 packages/tooling/contract-test-utils/tsconfig.server.json diff --git a/packages/sdk/server-node/contract-tests/package.json b/packages/sdk/server-node/contract-tests/package.json index 2b00e78f3f..061721a5b6 100644 --- a/packages/sdk/server-node/contract-tests/package.json +++ b/packages/sdk/server-node/contract-tests/package.json @@ -12,6 +12,7 @@ "license": "Apache-2.0", "private": true, "dependencies": { + "@launchdarkly/js-contract-test-utils": "workspace:^", "@launchdarkly/node-server-sdk": "workspace:^", "body-parser": "^1.19.0", "express": "^4.17.1", diff --git a/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts b/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts index f1fcfabe2b..90c8684c8f 100644 --- a/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts +++ b/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts @@ -1,5 +1,6 @@ import got from 'got'; +import { ServerSideTestHook as TestHook } from '@launchdarkly/js-contract-test-utils/server'; import ld, { createMigration, DataSourceOptions, @@ -20,7 +21,6 @@ import ld, { import BigSegmentTestStore from './BigSegmentTestStore.js'; import { Log, sdkLogger } from './log.js'; -import TestHook from './TestHook.js'; const badCommandError = new Error('unsupported command'); export { badCommandError }; diff --git a/packages/sdk/shopify-oxygen/contract-tests/package.json b/packages/sdk/shopify-oxygen/contract-tests/package.json index 30d72f702e..f2124f2020 100644 --- a/packages/sdk/shopify-oxygen/contract-tests/package.json +++ b/packages/sdk/shopify-oxygen/contract-tests/package.json @@ -11,6 +11,7 @@ "license": "Apache-2.0", "private": true, "dependencies": { + "@launchdarkly/js-contract-test-utils": "workspace:^", "@launchdarkly/js-server-sdk-common": "workspace:^", "@launchdarkly/shopify-oxygen-sdk": "workspace:^", "express": "^5.0.1" diff --git a/packages/sdk/shopify-oxygen/contract-tests/src/utils/clientPool.ts b/packages/sdk/shopify-oxygen/contract-tests/src/utils/clientPool.ts index f509631aee..5c6b38039d 100644 --- a/packages/sdk/shopify-oxygen/contract-tests/src/utils/clientPool.ts +++ b/packages/sdk/shopify-oxygen/contract-tests/src/utils/clientPool.ts @@ -1,42 +1,23 @@ import { Response } from 'express'; +import { ClientPool as GenericClientPool } from '@launchdarkly/js-contract-test-utils'; import { LDClient } from '@launchdarkly/js-server-sdk-common'; import { init } from '@launchdarkly/shopify-oxygen-sdk'; /* eslint-disable no-console */ -// NOTE: Currently, this is a very simple client pool that only really handles the -// very limited Oxygen specific use cases... we should be expand this to be more -// general purpose in the future and maybe even come up with some shared ts interface -// to facilitate future contract testing. - -// TODO: currently this class will handle the response sending as well, which may technically -// sit outside the scope of what it SHOULD be doing. We should refactor this to be more -// general purpose and allow the caller to handle the response sending. - /** - * ClientPool is a singleton that manages a pool of LDClient instances. Currently there is - * no separation between a managed client and this pool. Which means all of the client specs - * will be implemented in this class. + * ClientPool manages a pool of LDClient instances for Shopify Oxygen contract tests. + * It uses the shared generic ClientPool for client storage and ID generation, and + * handles the Oxygen-specific client creation, command execution, and response sending. * * @see https://github.com/launchdarkly/sdk-test-harness/blob/v2/docs/service_spec.md */ export default class ClientPool { - private _clients: Record = {}; - private _clientCounter = 0; - - constructor() { - this._clients = {}; - this._clientCounter = 0; - } - - private _makeId(): string { - this._clientCounter += 1; - return `client-${this._clientCounter}`; - } + private _pool = new GenericClientPool(); public async runCommand(id: string, body: any, res: Response): Promise { - const client = this._clients[id]; + const client = this._pool.get(id); // TODO: handle the 'itCanFailCase' if (client) { try { @@ -69,10 +50,10 @@ export default class ClientPool { } public async deleteClient(id: string, res: Response): Promise { - const client = this._clients[id]; + const client = this._pool.get(id); if (client) { client.close(); - delete this._clients[id]; + this._pool.remove(id); res.status(204); res.send(); } else { @@ -83,7 +64,6 @@ export default class ClientPool { public async createClient(options: any, res: Response): Promise { try { - const id = this._makeId(); const { configuration: { credential = 'unknown-sdk-key', polling }, } = options; @@ -101,7 +81,7 @@ export default class ClientPool { }); await client.waitForInitialization({ timeout: 10 }); - this._clients[id] = client; + const id = this._pool.add(client); res.status(201); res.set('Location', `/clients/${id}`); if (!client.initialized()) { diff --git a/packages/sdk/shopify-oxygen/contract-tests/tsup.config.ts b/packages/sdk/shopify-oxygen/contract-tests/tsup.config.ts index 0758f21c40..300be1bd30 100644 --- a/packages/sdk/shopify-oxygen/contract-tests/tsup.config.ts +++ b/packages/sdk/shopify-oxygen/contract-tests/tsup.config.ts @@ -7,6 +7,7 @@ export default defineConfig({ entry: { index: 'src/index.ts', }, + noExternal: ['@launchdarkly/js-contract-test-utils'], minify: true, format: ['esm', 'cjs'], splitting: false, diff --git a/packages/tooling/contract-test-utils/package.json b/packages/tooling/contract-test-utils/package.json index de1dd22804..0273942430 100644 --- a/packages/tooling/contract-test-utils/package.json +++ b/packages/tooling/contract-test-utils/package.json @@ -15,21 +15,32 @@ "types": "./src/client.ts", "import": "./src/client.ts", "default": "./src/client.ts" + }, + "./server": { + "types": "./dist/server.d.ts", + "import": "./dist/server.js", + "default": "./dist/server.js" } }, "typesVersions": { "*": { "client": [ "./src/client.ts" + ], + "server": [ + "./dist/server.d.ts" ] } }, "scripts": { "build": "tsc", + "build:server": "tsc -p tsconfig.server.json", "clean": "rimraf dist" }, "dependencies": { - "@launchdarkly/js-client-sdk-common": "workspace:^" + "@launchdarkly/js-client-sdk-common": "workspace:^", + "@launchdarkly/node-server-sdk": "workspace:^", + "got": "14.4.7" }, "devDependencies": { "@types/node": "^18.11.9", diff --git a/packages/sdk/server-node/contract-tests/src/TestHook.ts b/packages/tooling/contract-test-utils/src/server-side/TestHook.ts similarity index 100% rename from packages/sdk/server-node/contract-tests/src/TestHook.ts rename to packages/tooling/contract-test-utils/src/server-side/TestHook.ts diff --git a/packages/tooling/contract-test-utils/src/server.ts b/packages/tooling/contract-test-utils/src/server.ts new file mode 100644 index 0000000000..3b2a7c9893 --- /dev/null +++ b/packages/tooling/contract-test-utils/src/server.ts @@ -0,0 +1,9 @@ +// Re-export universal exports (includes ClientPool) +export * from './index.js'; + +// Server-side exports +export { default as ServerSideTestHook } from './server-side/TestHook.js'; +export type { + HookData as ServerSideHookData, + HookErrors as ServerSideHookErrors, +} from './server-side/TestHook.js'; diff --git a/packages/tooling/contract-test-utils/tsconfig.json b/packages/tooling/contract-test-utils/tsconfig.json index 166160dbfa..3c47238f16 100644 --- a/packages/tooling/contract-test-utils/tsconfig.json +++ b/packages/tooling/contract-test-utils/tsconfig.json @@ -14,7 +14,11 @@ "declarationMap": true, "stripInternal": true, "esModuleInterop": true, - "skipLibCheck": true + "skipLibCheck": true, + "baseUrl": ".", + "paths": { + "got": ["../../../node_modules/got/dist/source"] + } }, "include": ["src/**/*"], "exclude": ["dist", "node_modules"] diff --git a/packages/tooling/contract-test-utils/tsconfig.server.json b/packages/tooling/contract-test-utils/tsconfig.server.json new file mode 100644 index 0000000000..8b221e028d --- /dev/null +++ b/packages/tooling/contract-test-utils/tsconfig.server.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*"], + "exclude": ["src/client-side/**", "src/client.ts", "dist", "node_modules"] +} From 353db640ff36ac393cbf620ebb9986f0ed880397 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:08:01 +0000 Subject: [PATCH 02/21] fix: use source exports for ./server subpath and fix module resolution - Change ./server export to point to source .ts files (like ./client) so contract-test-utils doesn't need a separate build step in CI - Restore .js extensions in relative imports for node16 moduleResolution - server.ts only exports ServerSideTestHook and ClientPool (not index) to avoid pulling in client-sdk-common deps in server context Co-Authored-By: Steven Zhang --- packages/tooling/contract-test-utils/package.json | 8 ++++---- packages/tooling/contract-test-utils/src/index.ts | 8 ++++---- packages/tooling/contract-test-utils/src/server.ts | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/tooling/contract-test-utils/package.json b/packages/tooling/contract-test-utils/package.json index 0273942430..f015a23892 100644 --- a/packages/tooling/contract-test-utils/package.json +++ b/packages/tooling/contract-test-utils/package.json @@ -17,9 +17,9 @@ "default": "./src/client.ts" }, "./server": { - "types": "./dist/server.d.ts", - "import": "./dist/server.js", - "default": "./dist/server.js" + "types": "./src/server.ts", + "import": "./src/server.ts", + "default": "./src/server.ts" } }, "typesVersions": { @@ -28,7 +28,7 @@ "./src/client.ts" ], "server": [ - "./dist/server.d.ts" + "./src/server.ts" ] } }, diff --git a/packages/tooling/contract-test-utils/src/index.ts b/packages/tooling/contract-test-utils/src/index.ts index 82c54b0dcb..41510d7c88 100644 --- a/packages/tooling/contract-test-utils/src/index.ts +++ b/packages/tooling/contract-test-utils/src/index.ts @@ -1,5 +1,5 @@ // Universal exports (no SDK dependency) -export * from './types/CommandParams'; +export * from './types/CommandParams.js'; export { type CreateInstanceParams, type SDKConfigParams, @@ -15,6 +15,6 @@ export { type SDKConfigHooksParams, type SDKConfigProxyParams, type SDKConfigWrapper, -} from './types/ConfigParams'; -export { makeLogger } from './logging/makeLogger'; -export { ClientPool } from './server-side/ClientPool'; +} from './types/ConfigParams.js'; +export { makeLogger } from './logging/makeLogger.js'; +export { ClientPool } from './server-side/ClientPool.js'; diff --git a/packages/tooling/contract-test-utils/src/server.ts b/packages/tooling/contract-test-utils/src/server.ts index 3b2a7c9893..d86f9cb928 100644 --- a/packages/tooling/contract-test-utils/src/server.ts +++ b/packages/tooling/contract-test-utils/src/server.ts @@ -1,9 +1,9 @@ -// Re-export universal exports (includes ClientPool) -export * from './index.js'; - // Server-side exports export { default as ServerSideTestHook } from './server-side/TestHook.js'; export type { HookData as ServerSideHookData, HookErrors as ServerSideHookErrors, } from './server-side/TestHook.js'; + +// Re-export shared utilities that don't depend on client-side packages +export { ClientPool } from './server-side/ClientPool.js'; From 3800e73fb63f0d073f038c72782add44b8f008c6 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:17:39 +0000 Subject: [PATCH 03/21] fix: revert ./server to compiled dist exports, add build:server step to CI - Revert ./server subpath export to point to compiled dist/server.js (source exports don't work at runtime with node16 module resolution) - Add 'Build shared contract test utils (server)' step to server-node CI workflow so dist/ files exist before contract tests build - server.ts only re-exports ServerSideTestHook and ClientPool to avoid pulling in client-sdk-common deps in server context Co-Authored-By: Steven Zhang --- .github/workflows/server-node.yml | 2 ++ packages/tooling/contract-test-utils/package.json | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/server-node.yml b/.github/workflows/server-node.yml index 46c225f8fd..85abb74a32 100644 --- a/.github/workflows/server-node.yml +++ b/.github/workflows/server-node.yml @@ -33,6 +33,8 @@ jobs: workspace_path: packages/sdk/server-node - name: Install contract test service dependencies run: yarn workspace node-server-sdk-contract-tests install --no-immutable + - name: Build shared contract test utils (server) + run: yarn workspace @launchdarkly/js-contract-test-utils build:server - name: Build the test service run: yarn workspace node-server-sdk-contract-tests build - name: Launch the test service in the background diff --git a/packages/tooling/contract-test-utils/package.json b/packages/tooling/contract-test-utils/package.json index f015a23892..0273942430 100644 --- a/packages/tooling/contract-test-utils/package.json +++ b/packages/tooling/contract-test-utils/package.json @@ -17,9 +17,9 @@ "default": "./src/client.ts" }, "./server": { - "types": "./src/server.ts", - "import": "./src/server.ts", - "default": "./src/server.ts" + "types": "./dist/server.d.ts", + "import": "./dist/server.js", + "default": "./dist/server.js" } }, "typesVersions": { @@ -28,7 +28,7 @@ "./src/client.ts" ], "server": [ - "./src/server.ts" + "./dist/server.d.ts" ] } }, From e513780e4d921d363bbef31ec257138b301b0793 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 18:25:07 +0000 Subject: [PATCH 04/21] fix: narrow tsconfig.server.json to only server-side files - Only include src/server.ts and src/server-side/* to avoid compiling files that depend on @launchdarkly/js-client-sdk-common which isn't available in the server-node CI environment Co-Authored-By: Steven Zhang --- packages/tooling/contract-test-utils/tsconfig.server.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tooling/contract-test-utils/tsconfig.server.json b/packages/tooling/contract-test-utils/tsconfig.server.json index 8b221e028d..26b0e07212 100644 --- a/packages/tooling/contract-test-utils/tsconfig.server.json +++ b/packages/tooling/contract-test-utils/tsconfig.server.json @@ -1,5 +1,5 @@ { "extends": "./tsconfig.json", - "include": ["src/**/*"], - "exclude": ["src/client-side/**", "src/client.ts", "dist", "node_modules"] + "include": ["src/server.ts", "src/server-side/*"], + "exclude": ["dist", "node_modules"] } From b0c27807d755431a789009041cecff2c1b60af13 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 19:52:56 +0000 Subject: [PATCH 05/21] fix: move got and node-server-sdk to optional peerDependencies These are only needed by the ./server subpath. Making them optional peer dependencies prevents them from being hoisted into unrelated workspaces (e.g. React SDK) where @types/cacheable-request conflicts with TypeScript compilation. Co-Authored-By: Steven Zhang --- packages/tooling/contract-test-utils/package.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/tooling/contract-test-utils/package.json b/packages/tooling/contract-test-utils/package.json index 0273942430..1b29fafa1b 100644 --- a/packages/tooling/contract-test-utils/package.json +++ b/packages/tooling/contract-test-utils/package.json @@ -38,10 +38,20 @@ "clean": "rimraf dist" }, "dependencies": { - "@launchdarkly/js-client-sdk-common": "workspace:^", + "@launchdarkly/js-client-sdk-common": "workspace:^" + }, + "peerDependencies": { "@launchdarkly/node-server-sdk": "workspace:^", "got": "14.4.7" }, + "peerDependenciesMeta": { + "@launchdarkly/node-server-sdk": { + "optional": true + }, + "got": { + "optional": true + } + }, "devDependencies": { "@types/node": "^18.11.9", "typescript": "^4.9.0" From c0c27e7997c6fe87054b59294261d2feebd9c45d Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 20:01:49 +0000 Subject: [PATCH 06/21] fix: make build script no-op to prevent server deps from breaking unrelated CI The default 'build' script was running 'tsc' which compiled server-side files (TestHook.ts) that depend on @launchdarkly/node-server-sdk and got. When other CI jobs (e.g. React SDK) run 'yarn workspaces foreach ... build', this compilation fails because those optional peer deps aren't installed. Since ./ and ./client exports point to source .ts files (no compilation needed), the build script is now a no-op. Server-side dist files are produced by 'build:server' which is explicitly called in server-node CI. Co-Authored-By: Steven Zhang --- packages/tooling/contract-test-utils/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/tooling/contract-test-utils/package.json b/packages/tooling/contract-test-utils/package.json index 1b29fafa1b..1fb416087a 100644 --- a/packages/tooling/contract-test-utils/package.json +++ b/packages/tooling/contract-test-utils/package.json @@ -33,7 +33,7 @@ } }, "scripts": { - "build": "tsc", + "build": "echo 'Client exports use source .ts files; use build:server for ./server subpath'", "build:server": "tsc -p tsconfig.server.json", "clean": "rimraf dist" }, From 5f85bf7bf173b2d66a5d98808a376b9ecf627812 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 20:10:55 +0000 Subject: [PATCH 07/21] fix: revert .js extensions in index.ts to fix Turbopack/Next.js resolution index.ts is consumed as source .ts (not compiled), so .js extensions break Turbopack module resolution in the React SDK contract tests. Only server.ts (compiled to dist/) needs .js extensions. Co-Authored-By: Steven Zhang --- packages/tooling/contract-test-utils/src/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/tooling/contract-test-utils/src/index.ts b/packages/tooling/contract-test-utils/src/index.ts index 41510d7c88..82c54b0dcb 100644 --- a/packages/tooling/contract-test-utils/src/index.ts +++ b/packages/tooling/contract-test-utils/src/index.ts @@ -1,5 +1,5 @@ // Universal exports (no SDK dependency) -export * from './types/CommandParams.js'; +export * from './types/CommandParams'; export { type CreateInstanceParams, type SDKConfigParams, @@ -15,6 +15,6 @@ export { type SDKConfigHooksParams, type SDKConfigProxyParams, type SDKConfigWrapper, -} from './types/ConfigParams.js'; -export { makeLogger } from './logging/makeLogger.js'; -export { ClientPool } from './server-side/ClientPool.js'; +} from './types/ConfigParams'; +export { makeLogger } from './logging/makeLogger'; +export { ClientPool } from './server-side/ClientPool'; From 746e3bb54710cd07f74e0514ce2ff3a0db34ba51 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 20:38:03 +0000 Subject: [PATCH 08/21] refactor: deduplicate server-node contract tests with shared types and ClientPool - Replace inline clientCounter + Record with shared ClientPool - Replace local SdkConfigOptions with shared SDKConfigParams (extended for server) - Type newSdkClientEntity options with ServerCreateInstanceParams - Re-export shared config types through ./server barrel for node16 consumers - Import TestHook, SDKConfigParams, CreateInstanceParams from shared ./server Co-Authored-By: Steven Zhang --- .../server-node/contract-tests/src/index.ts | 19 +++--- .../contract-tests/src/sdkClientEntity.ts | 65 ++++++------------- .../tooling/contract-test-utils/src/server.ts | 17 +++++ 3 files changed, 46 insertions(+), 55 deletions(-) diff --git a/packages/sdk/server-node/contract-tests/src/index.ts b/packages/sdk/server-node/contract-tests/src/index.ts index f366bf9ca4..8265479343 100644 --- a/packages/sdk/server-node/contract-tests/src/index.ts +++ b/packages/sdk/server-node/contract-tests/src/index.ts @@ -2,6 +2,8 @@ import bodyParser from 'body-parser'; import express, { Request, Response } from 'express'; import { Server } from 'http'; +import { ClientPool } from '@launchdarkly/js-contract-test-utils/server'; + import { Log } from './log.js'; import { badCommandError, newSdkClientEntity, SdkClientEntity } from './sdkClientEntity.js'; @@ -10,8 +12,7 @@ let server: Server | null = null; const port = 8000; -let clientCounter = 0; -const clients: Record = {}; +const clients = new ClientPool(); const mainLog = Log('service'); @@ -66,16 +67,12 @@ app.delete('/', (req: Request, res: Response) => { app.post('/', async (req: Request, res: Response) => { const options = req.body; - clientCounter += 1; - const clientId = clientCounter.toString(); - const resourceUrl = `/clients/${clientId}`; - try { const client = await newSdkClientEntity(options); - clients[clientId] = client; + const clientId = clients.add(client); res.status(201); - res.set('Location', resourceUrl); + res.set('Location', `/clients/${clientId}`); } catch (e) { res.status(500); const message = e instanceof Error ? e.message : JSON.stringify(e); @@ -86,7 +83,7 @@ app.post('/', async (req: Request, res: Response) => { }); app.post('/clients/:id', async (req: Request, res: Response) => { - const client = clients[req.params.id]; + const client = clients.get(req.params.id); if (!client) { res.status(404); } else { @@ -113,13 +110,13 @@ app.post('/clients/:id', async (req: Request, res: Response) => { }); app.delete('/clients/:id', async (req: Request, res: Response) => { - const client = clients[req.params.id]; + const client = clients.get(req.params.id); if (!client) { res.status(404); res.send(); } else { client.close(); - delete clients[req.params.id]; + clients.remove(req.params.id); res.status(204); res.send(); } diff --git a/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts b/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts index 90c8684c8f..47ee5b33c2 100644 --- a/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts +++ b/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts @@ -1,6 +1,10 @@ import got from 'got'; -import { ServerSideTestHook as TestHook } from '@launchdarkly/js-contract-test-utils/server'; +import { + CreateInstanceParams, + SDKConfigParams, + ServerSideTestHook as TestHook, +} from '@launchdarkly/js-contract-test-utils/server'; import ld, { createMigration, DataSourceOptions, @@ -25,35 +29,11 @@ import { Log, sdkLogger } from './log.js'; const badCommandError = new Error('unsupported command'); export { badCommandError }; -interface SdkConfigOptions { - streaming?: { - baseUri: string; - initialRetryDelayMs?: number; - filter?: string; - }; - polling?: { - baseUri: string; - pollIntervalMs: number; - filter?: string; - }; - dataSystem?: { - initializers?: SDKDataSystemInitializerParams[]; - synchronizers?: SDKDataSystemSynchronizerParams[]; - payloadFilter?: string; - }; - events?: { - allAttributesPrivate?: boolean; - baseUri: string; - capacity?: number; - enableDiagnostics?: boolean; - flushIntervalMs?: number; - globalPrivateAttributes?: string[]; - enableGzip?: boolean; - }; - tags?: { - applicationId: string; - applicationVersion: string; - }; +/** + * Server-specific extensions to the shared SDKConfigParams. + * Adds bigSegments, dataSystem, and other server-only configuration. + */ +interface ServerSDKConfigParams extends SDKConfigParams { bigSegments?: { callbackUri: string; userCacheSize?: number; @@ -61,20 +41,17 @@ interface SdkConfigOptions { statusPollIntervalMs?: number; staleAfterMs?: number; }; - hooks?: { - hooks: { - name: string; - callbackUri: string; - data: any; - errors: any; - }[]; - }; - wrapper?: { - name?: string; - version?: string; + dataSystem?: { + initializers?: SDKDataSystemInitializerParams[]; + synchronizers?: SDKDataSystemSynchronizerParams[]; + payloadFilter?: string; }; } +interface ServerCreateInstanceParams extends CreateInstanceParams { + configuration: ServerSDKConfigParams; +} + export interface SDKDataSystemSynchronizerParams { streaming?: SDKDataSourceStreamingParams; polling?: SDKDataSourcePollingParams; @@ -149,7 +126,7 @@ interface CommandParams { }; } -export function makeSdkConfig(options: SdkConfigOptions, tag: string): LDOptions { +export function makeSdkConfig(options: ServerSDKConfigParams, tag: string): LDOptions { const cf: LDOptions = { logger: sdkLogger(tag), diagnosticOptOut: true, @@ -169,7 +146,7 @@ export function makeSdkConfig(options: SdkConfigOptions, tag: string): LDOptions if (options.polling) { cf.stream = false; cf.baseUri = options.polling.baseUri; - cf.pollInterval = options.polling.pollIntervalMs / 1000; + cf.pollInterval = maybeTime(options.polling.pollIntervalMs); if (options.polling.filter) { cf.payloadFilterKey = options.polling.filter; } @@ -328,7 +305,7 @@ interface ListenerEntry { handler: (...args: any[]) => void; } -export async function newSdkClientEntity(options: any): Promise { +export async function newSdkClientEntity(options: ServerCreateInstanceParams): Promise { const c: any = {}; const log = Log(options.tag); const listeners = new Map(); diff --git a/packages/tooling/contract-test-utils/src/server.ts b/packages/tooling/contract-test-utils/src/server.ts index d86f9cb928..d218c1730d 100644 --- a/packages/tooling/contract-test-utils/src/server.ts +++ b/packages/tooling/contract-test-utils/src/server.ts @@ -7,3 +7,20 @@ export type { // Re-export shared utilities that don't depend on client-side packages export { ClientPool } from './server-side/ClientPool.js'; + +// Re-export shared types for server consumers that use node16 moduleResolution +// (they cannot import from the '.' subpath which serves extensionless .ts source) +export type { + CreateInstanceParams, + SDKConfigParams, + SDKConfigStreamingParams, + SDKConfigPollingParams, + SDKConfigEventParams, + SDKConfigTagsParams, + SDKConfigHooksParams, + SDKConfigHookInstance, + SDKConfigWrapper, + SDKConfigServiceEndpointsParams, + SDKConfigTLSParams, + SDKConfigProxyParams, +} from './types/ConfigParams.js'; From aac306c94019b98fa1d54bd954b3b2e2030d90ea Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 20:40:02 +0000 Subject: [PATCH 09/21] fix: prettier formatting for newSdkClientEntity function signature Co-Authored-By: Steven Zhang --- .../sdk/server-node/contract-tests/src/sdkClientEntity.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts b/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts index 47ee5b33c2..c79f50f2e7 100644 --- a/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts +++ b/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts @@ -305,7 +305,9 @@ interface ListenerEntry { handler: (...args: any[]) => void; } -export async function newSdkClientEntity(options: ServerCreateInstanceParams): Promise { +export async function newSdkClientEntity( + options: ServerCreateInstanceParams, +): Promise { const c: any = {}; const log = Log(options.tag); const listeners = new Map(); From 1d7581b321be2a8a2986aad2cacd1c381d6befd8 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 20:48:34 +0000 Subject: [PATCH 10/21] fix: revert shared type re-exports from server.ts to avoid js-client-sdk-common dep in CI The build:server compilation in CI doesn't have @launchdarkly/js-client-sdk-common available (focused install for server-node only). Reverting type re-exports from server.ts and keeping server-node config types local. ClientPool deduplication in index.ts is preserved. Co-Authored-By: Steven Zhang --- .../contract-tests/src/sdkClientEntity.ts | 72 +++++++++++++++---- .../tooling/contract-test-utils/src/server.ts | 17 ----- 2 files changed, 58 insertions(+), 31 deletions(-) diff --git a/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts b/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts index c79f50f2e7..2b2bf36e95 100644 --- a/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts +++ b/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts @@ -1,10 +1,6 @@ import got from 'got'; -import { - CreateInstanceParams, - SDKConfigParams, - ServerSideTestHook as TestHook, -} from '@launchdarkly/js-contract-test-utils/server'; +import { ServerSideTestHook as TestHook } from '@launchdarkly/js-contract-test-utils/server'; import ld, { createMigration, DataSourceOptions, @@ -29,11 +25,43 @@ import { Log, sdkLogger } from './log.js'; const badCommandError = new Error('unsupported command'); export { badCommandError }; -/** - * Server-specific extensions to the shared SDKConfigParams. - * Adds bigSegments, dataSystem, and other server-only configuration. - */ -interface ServerSDKConfigParams extends SDKConfigParams { +interface SdkConfigOptions { + credential: string; + startWaitTimeMs?: number; + initCanFail?: boolean; + serviceEndpoints?: { + streaming?: string; + polling?: string; + events?: string; + }; + tls?: { + skipVerifyPeer?: boolean; + customCAFile?: string; + }; + streaming?: { + baseUri?: string; + initialRetryDelayMs?: number; + filter?: string; + }; + polling?: { + baseUri?: string; + pollIntervalMs?: number; + filter?: string; + }; + events?: { + baseUri?: string; + capacity?: number; + enableDiagnostics: boolean; + allAttributesPrivate?: boolean; + globalPrivateAttributes?: string[]; + flushIntervalMs?: number; + omitAnonymousContexts?: boolean; + enableGzip?: boolean; + }; + tags?: { + applicationId?: string; + applicationVersion?: string; + }; bigSegments?: { callbackUri: string; userCacheSize?: number; @@ -41,6 +69,21 @@ interface ServerSDKConfigParams extends SDKConfigParams { statusPollIntervalMs?: number; staleAfterMs?: number; }; + hooks?: { + hooks: { + name: string; + callbackUri: string; + data?: Record>; + errors?: Record; + }[]; + }; + wrapper?: { + name: string; + version: string; + }; + proxy?: { + httpProxy?: string; + }; dataSystem?: { initializers?: SDKDataSystemInitializerParams[]; synchronizers?: SDKDataSystemSynchronizerParams[]; @@ -48,8 +91,9 @@ interface ServerSDKConfigParams extends SDKConfigParams { }; } -interface ServerCreateInstanceParams extends CreateInstanceParams { - configuration: ServerSDKConfigParams; +interface CreateInstanceParams { + configuration: SdkConfigOptions; + tag: string; } export interface SDKDataSystemSynchronizerParams { @@ -126,7 +170,7 @@ interface CommandParams { }; } -export function makeSdkConfig(options: ServerSDKConfigParams, tag: string): LDOptions { +export function makeSdkConfig(options: SdkConfigOptions, tag: string): LDOptions { const cf: LDOptions = { logger: sdkLogger(tag), diagnosticOptOut: true, @@ -306,7 +350,7 @@ interface ListenerEntry { } export async function newSdkClientEntity( - options: ServerCreateInstanceParams, + options: CreateInstanceParams, ): Promise { const c: any = {}; const log = Log(options.tag); diff --git a/packages/tooling/contract-test-utils/src/server.ts b/packages/tooling/contract-test-utils/src/server.ts index d218c1730d..d86f9cb928 100644 --- a/packages/tooling/contract-test-utils/src/server.ts +++ b/packages/tooling/contract-test-utils/src/server.ts @@ -7,20 +7,3 @@ export type { // Re-export shared utilities that don't depend on client-side packages export { ClientPool } from './server-side/ClientPool.js'; - -// Re-export shared types for server consumers that use node16 moduleResolution -// (they cannot import from the '.' subpath which serves extensionless .ts source) -export type { - CreateInstanceParams, - SDKConfigParams, - SDKConfigStreamingParams, - SDKConfigPollingParams, - SDKConfigEventParams, - SDKConfigTagsParams, - SDKConfigHooksParams, - SDKConfigHookInstance, - SDKConfigWrapper, - SDKConfigServiceEndpointsParams, - SDKConfigTLSParams, - SDKConfigProxyParams, -} from './types/ConfigParams.js'; From 6cf71df25e4f92db8fc13c0df348f9f3dc2cf8b2 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 20:50:36 +0000 Subject: [PATCH 11/21] fix: prettier formatting for newSdkClientEntity with shorter type name Co-Authored-By: Steven Zhang --- .../sdk/server-node/contract-tests/src/sdkClientEntity.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts b/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts index 2b2bf36e95..de285d22e5 100644 --- a/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts +++ b/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts @@ -349,9 +349,7 @@ interface ListenerEntry { handler: (...args: any[]) => void; } -export async function newSdkClientEntity( - options: CreateInstanceParams, -): Promise { +export async function newSdkClientEntity(options: CreateInstanceParams): Promise { const c: any = {}; const log = Log(options.tag); const listeners = new Map(); From 6d0ee27d73251bcc55683b062ba50e997bb621f9 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 21:34:34 +0000 Subject: [PATCH 12/21] refactor: split types into shared/client/server modules for proper SDK dependency isolation - src/types/ now contains only SDK-independent types (no LDContext/LDEvaluationReason) - src/client-side/types/ has client-specific types importing from js-client-sdk-common - src/server-side/types/ has server-specific types importing from js-server-sdk-common - server.ts barrel re-exports shared + server types (bigSegments, dataSystem, migrations) - client.ts barrel re-exports shared + client types (SDKConfigClientSideParams) - index.ts exports only universal types with no SDK dependency - server-node contract tests now import CommandParams, CreateInstanceParams, SDKConfigParams from shared package - Added @launchdarkly/js-server-sdk-common as optional peer dependency Co-Authored-By: Steven Zhang --- .../contract-tests/src/sdkClientEntity.ts | 156 +----------------- .../tooling/contract-test-utils/package.json | 4 + .../src/client-side/types/CommandParams.ts | 96 +++++++++++ .../src/client-side/types/ConfigParams.ts | 57 +++++++ .../tooling/contract-test-utils/src/client.ts | 13 +- .../tooling/contract-test-utils/src/index.ts | 19 +-- .../src/server-side/types/CommandParams.ts | 133 +++++++++++++++ .../src/server-side/types/ConfigParams.ts | 84 ++++++++++ .../tooling/contract-test-utils/src/server.ts | 10 +- .../src/types/CommandParams.ts | 72 +------- .../src/types/ConfigParams.ts | 33 +--- .../contract-test-utils/tsconfig.server.json | 2 +- 12 files changed, 411 insertions(+), 268 deletions(-) create mode 100644 packages/tooling/contract-test-utils/src/client-side/types/CommandParams.ts create mode 100644 packages/tooling/contract-test-utils/src/client-side/types/ConfigParams.ts create mode 100644 packages/tooling/contract-test-utils/src/server-side/types/CommandParams.ts create mode 100644 packages/tooling/contract-test-utils/src/server-side/types/ConfigParams.ts diff --git a/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts b/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts index de285d22e5..e1739a1e06 100644 --- a/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts +++ b/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts @@ -1,6 +1,11 @@ import got from 'got'; -import { ServerSideTestHook as TestHook } from '@launchdarkly/js-contract-test-utils/server'; +import { + CommandParams, + CreateInstanceParams, + SDKConfigParams, + ServerSideTestHook as TestHook, +} from '@launchdarkly/js-contract-test-utils/server'; import ld, { createMigration, DataSourceOptions, @@ -8,9 +13,7 @@ import ld, { LDConcurrentExecution, LDContext, LDExecutionOrdering, - LDFlagValue, LDMigrationError, - LDMigrationStage, LDMigrationSuccess, LDOptions, LDSerialExecution, @@ -25,152 +28,7 @@ import { Log, sdkLogger } from './log.js'; const badCommandError = new Error('unsupported command'); export { badCommandError }; -interface SdkConfigOptions { - credential: string; - startWaitTimeMs?: number; - initCanFail?: boolean; - serviceEndpoints?: { - streaming?: string; - polling?: string; - events?: string; - }; - tls?: { - skipVerifyPeer?: boolean; - customCAFile?: string; - }; - streaming?: { - baseUri?: string; - initialRetryDelayMs?: number; - filter?: string; - }; - polling?: { - baseUri?: string; - pollIntervalMs?: number; - filter?: string; - }; - events?: { - baseUri?: string; - capacity?: number; - enableDiagnostics: boolean; - allAttributesPrivate?: boolean; - globalPrivateAttributes?: string[]; - flushIntervalMs?: number; - omitAnonymousContexts?: boolean; - enableGzip?: boolean; - }; - tags?: { - applicationId?: string; - applicationVersion?: string; - }; - bigSegments?: { - callbackUri: string; - userCacheSize?: number; - userCacheTimeMs?: number; - statusPollIntervalMs?: number; - staleAfterMs?: number; - }; - hooks?: { - hooks: { - name: string; - callbackUri: string; - data?: Record>; - errors?: Record; - }[]; - }; - wrapper?: { - name: string; - version: string; - }; - proxy?: { - httpProxy?: string; - }; - dataSystem?: { - initializers?: SDKDataSystemInitializerParams[]; - synchronizers?: SDKDataSystemSynchronizerParams[]; - payloadFilter?: string; - }; -} - -interface CreateInstanceParams { - configuration: SdkConfigOptions; - tag: string; -} - -export interface SDKDataSystemSynchronizerParams { - streaming?: SDKDataSourceStreamingParams; - polling?: SDKDataSourcePollingParams; -} - -export interface SDKDataSystemInitializerParams { - polling?: SDKDataSourcePollingParams; -} - -export interface SDKDataSourceStreamingParams { - baseUri?: string; - initialRetryDelayMs?: number; -} - -export interface SDKDataSourcePollingParams { - baseUri?: string; - pollIntervalMs?: number; -} - -interface CommandParams { - command: string; - evaluate?: { - flagKey: string; - context?: LDContext; - user?: LDUser; - defaultValue: LDFlagValue; - detail?: boolean; - valueType?: string; - }; - evaluateAll?: { - context?: LDContext; - user?: LDUser; - clientSideOnly?: boolean; - detailsOnlyForTrackedFlags?: boolean; - withReasons?: boolean; - }; - identifyEvent?: { - context?: LDContext; - user?: LDUser; - }; - customEvent?: { - eventKey: string; - context?: LDContext; - user?: LDUser; - data?: any; - metricValue?: number; - }; - migrationVariation?: { - key: string; - context: LDContext; - defaultStage: LDMigrationStage; - }; - migrationOperation?: { - operation: string; - key: string; - context: LDContext; - defaultStage: LDMigrationStage; - payload: any; - readExecutionOrder: string; - trackLatency?: boolean; - trackErrors?: boolean; - trackConsistency?: boolean; - newEndpoint: string; - oldEndpoint: string; - }; - registerFlagChangeListener?: { - listenerId: string; - callbackUri: string; - }; - unregisterListener?: { - listenerId: string; - }; -} - -export function makeSdkConfig(options: SdkConfigOptions, tag: string): LDOptions { +export function makeSdkConfig(options: SDKConfigParams, tag: string): LDOptions { const cf: LDOptions = { logger: sdkLogger(tag), diagnosticOptOut: true, diff --git a/packages/tooling/contract-test-utils/package.json b/packages/tooling/contract-test-utils/package.json index 1fb416087a..79ee984abb 100644 --- a/packages/tooling/contract-test-utils/package.json +++ b/packages/tooling/contract-test-utils/package.json @@ -41,10 +41,14 @@ "@launchdarkly/js-client-sdk-common": "workspace:^" }, "peerDependencies": { + "@launchdarkly/js-server-sdk-common": "workspace:^", "@launchdarkly/node-server-sdk": "workspace:^", "got": "14.4.7" }, "peerDependenciesMeta": { + "@launchdarkly/js-server-sdk-common": { + "optional": true + }, "@launchdarkly/node-server-sdk": { "optional": true }, diff --git a/packages/tooling/contract-test-utils/src/client-side/types/CommandParams.ts b/packages/tooling/contract-test-utils/src/client-side/types/CommandParams.ts new file mode 100644 index 0000000000..dc8b4a656e --- /dev/null +++ b/packages/tooling/contract-test-utils/src/client-side/types/CommandParams.ts @@ -0,0 +1,96 @@ +import { LDContext, LDEvaluationReason } from '@launchdarkly/js-client-sdk-common'; + +import { + CommandType, + ContextBuildParams, + ContextComparisonPairParams, + ContextConvertParams, + HookStage, + ValueType, +} from '../../types/CommandParams'; + +// Re-export shared types for convenience +export { CommandType, ValueType } from '../../types/CommandParams'; +export type { + EvaluateAllFlagsResponse, + ContextBuildParams, + ContextBuildSingleParams, + ContextBuildResponse, + ContextConvertParams, + ContextComparisonPairParams, + ContextComparisonParams, + ContextComparisonSingleParams, + AttributeDefinition, + PrivateAttribute, + ContextComparisonResponse, + SecureModeHashResponse, +} from '../../types/CommandParams'; +export { HookStage } from '../../types/CommandParams'; + +export interface CommandParams { + command: CommandType; + evaluate?: EvaluateFlagParams; + evaluateAll?: EvaluateAllFlagsParams; + customEvent?: CustomEventParams; + identifyEvent?: IdentifyEventParams; + contextBuild?: ContextBuildParams; + contextConvert?: ContextConvertParams; + contextComparison?: ContextComparisonPairParams; + secureModeHash?: SecureModeHashParams; +} + +export interface EvaluateFlagParams { + flagKey: string; + context?: LDContext; + user?: any; + valueType: ValueType; + defaultValue: unknown; + detail: boolean; +} + +export interface EvaluateFlagResponse { + value: unknown; + variationIndex?: number; + reason?: LDEvaluationReason; +} + +export interface EvaluateAllFlagsParams { + context?: LDContext; + user?: any; + withReasons: boolean; + clientSideOnly: boolean; + detailsOnlyForTrackedFlags: boolean; +} + +export interface CustomEventParams { + eventKey: string; + context?: LDContext; + user?: any; + data?: unknown; + omitNullData: boolean; + metricValue?: number; +} + +export interface IdentifyEventParams { + context?: LDContext; + user?: any; +} + +export interface SecureModeHashParams { + context?: LDContext; + user?: any; +} + +export interface EvaluationSeriesContext { + flagKey: string; + context: LDContext; + defaultValue: unknown; + method: string; +} + +export interface HookExecutionPayload { + evaluationSeriesContext?: EvaluationSeriesContext; + evaluationSeriesData?: Record; + evaluationDetail?: EvaluateFlagResponse; + stage?: HookStage; +} diff --git a/packages/tooling/contract-test-utils/src/client-side/types/ConfigParams.ts b/packages/tooling/contract-test-utils/src/client-side/types/ConfigParams.ts new file mode 100644 index 0000000000..4b76e5a47f --- /dev/null +++ b/packages/tooling/contract-test-utils/src/client-side/types/ConfigParams.ts @@ -0,0 +1,57 @@ +import { LDContext } from '@launchdarkly/js-client-sdk-common'; + +import { + SDKConfigEventParams, + SDKConfigHooksParams, + SDKConfigPollingParams, + SDKConfigProxyParams, + SDKConfigServiceEndpointsParams, + SDKConfigStreamingParams, + SDKConfigTagsParams, + SDKConfigTLSParams, + SDKConfigWrapper, +} from '../../types/ConfigParams'; + +// Re-export shared config types for convenience +export type { + SDKConfigTLSParams, + SDKConfigServiceEndpointsParams, + SDKConfigStreamingParams, + SDKConfigPollingParams, + SDKConfigEventParams, + SDKConfigTagsParams, + SDKConfigEvaluationHookData, + SDKConfigHookInstance, + SDKConfigHooksParams, + SDKConfigProxyParams, + SDKConfigWrapper, +} from '../../types/ConfigParams'; + +export interface SDKConfigClientSideParams { + initialContext?: LDContext; + initialUser?: any; + evaluationReasons?: boolean; + useReport?: boolean; + includeEnvironmentAttributes?: boolean; +} + +export interface SDKConfigParams { + credential: string; + startWaitTimeMs?: number; // UnixMillisecondTime + initCanFail?: boolean; + serviceEndpoints?: SDKConfigServiceEndpointsParams; + tls?: SDKConfigTLSParams; + streaming?: SDKConfigStreamingParams; + polling?: SDKConfigPollingParams; + events?: SDKConfigEventParams; + tags?: SDKConfigTagsParams; + clientSide?: SDKConfigClientSideParams; + hooks?: SDKConfigHooksParams; + wrapper?: SDKConfigWrapper; + proxy?: SDKConfigProxyParams; +} + +export interface CreateInstanceParams { + configuration: SDKConfigParams; + tag: string; +} diff --git a/packages/tooling/contract-test-utils/src/client.ts b/packages/tooling/contract-test-utils/src/client.ts index d0a167b519..90471f4e6e 100644 --- a/packages/tooling/contract-test-utils/src/client.ts +++ b/packages/tooling/contract-test-utils/src/client.ts @@ -1,5 +1,14 @@ -// Re-export universal exports -export * from './index'; +// Re-export shared base types (no SDK dependency) +export * from './types/CommandParams'; +export * from './types/ConfigParams'; + +// Re-export client-specific types (uses @launchdarkly/js-client-sdk-common) +export * from './client-side/types/CommandParams'; +export * from './client-side/types/ConfigParams'; + +// Re-export shared utilities +export { makeLogger } from './logging/makeLogger'; +export { ClientPool } from './server-side/ClientPool'; // Client-side exports export { default as ClientSideTestHook } from './client-side/TestHook'; diff --git a/packages/tooling/contract-test-utils/src/index.ts b/packages/tooling/contract-test-utils/src/index.ts index 82c54b0dcb..a11b0da647 100644 --- a/packages/tooling/contract-test-utils/src/index.ts +++ b/packages/tooling/contract-test-utils/src/index.ts @@ -1,20 +1,7 @@ // Universal exports (no SDK dependency) +// These types have no references to LDContext or LDEvaluationReason. +// SDK-specific types are exported from ./client and ./server subpaths. export * from './types/CommandParams'; -export { - type CreateInstanceParams, - type SDKConfigParams, - type SDKConfigTLSParams, - type SDKConfigServiceEndpointsParams, - type SDKConfigStreamingParams, - type SDKConfigPollingParams, - type SDKConfigEventParams, - type SDKConfigTagsParams, - type SDKConfigClientSideParams, - type SDKConfigEvaluationHookData, - type SDKConfigHookInstance, - type SDKConfigHooksParams, - type SDKConfigProxyParams, - type SDKConfigWrapper, -} from './types/ConfigParams'; +export * from './types/ConfigParams'; export { makeLogger } from './logging/makeLogger'; export { ClientPool } from './server-side/ClientPool'; diff --git a/packages/tooling/contract-test-utils/src/server-side/types/CommandParams.ts b/packages/tooling/contract-test-utils/src/server-side/types/CommandParams.ts new file mode 100644 index 0000000000..3501099107 --- /dev/null +++ b/packages/tooling/contract-test-utils/src/server-side/types/CommandParams.ts @@ -0,0 +1,133 @@ +import { LDContext, LDEvaluationReason, LDMigrationStage } from '@launchdarkly/js-server-sdk-common'; + +import { + CommandType, + ContextBuildParams, + ContextComparisonPairParams, + ContextConvertParams, + HookStage, + ValueType, +} from '../../types/CommandParams.js'; + +// Re-export shared types for convenience +export { + CommandType, + ValueType, + EvaluateAllFlagsResponse, + ContextBuildParams, + ContextBuildSingleParams, + ContextBuildResponse, + ContextConvertParams, + ContextComparisonPairParams, + ContextComparisonParams, + ContextComparisonSingleParams, + AttributeDefinition, + PrivateAttribute, + ContextComparisonResponse, + SecureModeHashResponse, + HookStage, +} from '../../types/CommandParams.js'; + +export interface CommandParams { + command: CommandType | string; + evaluate?: EvaluateFlagParams; + evaluateAll?: EvaluateAllFlagsParams; + customEvent?: CustomEventParams; + identifyEvent?: IdentifyEventParams; + contextBuild?: ContextBuildParams; + contextConvert?: ContextConvertParams; + contextComparison?: ContextComparisonPairParams; + secureModeHash?: SecureModeHashParams; + // Server-specific command fields + migrationVariation?: MigrationVariationParams; + migrationOperation?: MigrationOperationParams; + registerFlagChangeListener?: RegisterFlagChangeListenerParams; + unregisterListener?: UnregisterListenerParams; +} + +export interface EvaluateFlagParams { + flagKey: string; + context?: LDContext; + user?: any; + valueType: ValueType; + defaultValue: any; + detail: boolean; +} + +export interface EvaluateFlagResponse { + value: unknown; + variationIndex?: number; + reason?: LDEvaluationReason; +} + +export interface EvaluateAllFlagsParams { + context?: LDContext; + user?: any; + withReasons: boolean; + clientSideOnly: boolean; + detailsOnlyForTrackedFlags: boolean; +} + +export interface CustomEventParams { + eventKey: string; + context?: LDContext; + user?: any; + data?: unknown; + omitNullData: boolean; + metricValue?: number; +} + +export interface IdentifyEventParams { + context?: LDContext; + user?: any; +} + +export interface SecureModeHashParams { + context?: LDContext; + user?: any; +} + +export interface EvaluationSeriesContext { + flagKey: string; + context: LDContext; + defaultValue: unknown; + method: string; +} + +export interface HookExecutionPayload { + evaluationSeriesContext?: EvaluationSeriesContext; + evaluationSeriesData?: Record; + evaluationDetail?: EvaluateFlagResponse; + stage?: HookStage; +} + +// Server-specific command parameter types + +export interface MigrationVariationParams { + key: string; + context: LDContext; + defaultStage: LDMigrationStage; +} + +export interface MigrationOperationParams { + operation: string; + key: string; + context: LDContext; + defaultStage: LDMigrationStage; + payload: any; + readExecutionOrder: string; + trackLatency?: boolean; + trackErrors?: boolean; + trackConsistency?: boolean; + newEndpoint: string; + oldEndpoint: string; +} + +export interface RegisterFlagChangeListenerParams { + listenerId: string; + callbackUri: string; +} + +export interface UnregisterListenerParams { + listenerId: string; +} diff --git a/packages/tooling/contract-test-utils/src/server-side/types/ConfigParams.ts b/packages/tooling/contract-test-utils/src/server-side/types/ConfigParams.ts new file mode 100644 index 0000000000..9dd49ad159 --- /dev/null +++ b/packages/tooling/contract-test-utils/src/server-side/types/ConfigParams.ts @@ -0,0 +1,84 @@ +import { + SDKConfigEventParams, + SDKConfigHooksParams, + SDKConfigPollingParams, + SDKConfigProxyParams, + SDKConfigServiceEndpointsParams, + SDKConfigStreamingParams, + SDKConfigTagsParams, + SDKConfigTLSParams, + SDKConfigWrapper, +} from '../../types/ConfigParams.js'; + +// Re-export shared config types for convenience +export { + SDKConfigTLSParams, + SDKConfigServiceEndpointsParams, + SDKConfigStreamingParams, + SDKConfigPollingParams, + SDKConfigEventParams, + SDKConfigTagsParams, + SDKConfigEvaluationHookData, + SDKConfigHookInstance, + SDKConfigHooksParams, + SDKConfigProxyParams, + SDKConfigWrapper, +} from '../../types/ConfigParams.js'; + +// Server-specific config types + +export interface SDKConfigBigSegmentsParams { + callbackUri: string; + userCacheSize?: number; + userCacheTimeMs?: number; + statusPollIntervalMs?: number; + staleAfterMs?: number; +} + +export interface SDKDataSourceStreamingParams { + baseUri?: string; + initialRetryDelayMs?: number; +} + +export interface SDKDataSourcePollingParams { + baseUri?: string; + pollIntervalMs?: number; +} + +export interface SDKDataSystemSynchronizerParams { + streaming?: SDKDataSourceStreamingParams; + polling?: SDKDataSourcePollingParams; +} + +export interface SDKDataSystemInitializerParams { + polling?: SDKDataSourcePollingParams; +} + +export interface SDKDataSystemParams { + initializers?: SDKDataSystemInitializerParams[]; + synchronizers?: SDKDataSystemSynchronizerParams[]; + payloadFilter?: string; +} + +export interface SDKConfigParams { + credential: string; + startWaitTimeMs?: number; // UnixMillisecondTime + initCanFail?: boolean; + serviceEndpoints?: SDKConfigServiceEndpointsParams; + tls?: SDKConfigTLSParams; + streaming?: SDKConfigStreamingParams; + polling?: SDKConfigPollingParams; + events?: SDKConfigEventParams; + tags?: SDKConfigTagsParams; + hooks?: SDKConfigHooksParams; + wrapper?: SDKConfigWrapper; + proxy?: SDKConfigProxyParams; + // Server-specific config fields + bigSegments?: SDKConfigBigSegmentsParams; + dataSystem?: SDKDataSystemParams; +} + +export interface CreateInstanceParams { + configuration: SDKConfigParams; + tag: string; +} diff --git a/packages/tooling/contract-test-utils/src/server.ts b/packages/tooling/contract-test-utils/src/server.ts index d86f9cb928..6f0ef46c35 100644 --- a/packages/tooling/contract-test-utils/src/server.ts +++ b/packages/tooling/contract-test-utils/src/server.ts @@ -5,5 +5,13 @@ export type { HookErrors as ServerSideHookErrors, } from './server-side/TestHook.js'; -// Re-export shared utilities that don't depend on client-side packages +// Re-export shared utilities export { ClientPool } from './server-side/ClientPool.js'; + +// Re-export shared base types (no SDK dependency) +export * from './types/CommandParams.js'; +export * from './types/ConfigParams.js'; + +// Re-export server-specific types (uses @launchdarkly/js-server-sdk-common) +export * from './server-side/types/CommandParams.js'; +export * from './server-side/types/ConfigParams.js'; diff --git a/packages/tooling/contract-test-utils/src/types/CommandParams.ts b/packages/tooling/contract-test-utils/src/types/CommandParams.ts index 5d6dcd27d1..add4af28bd 100644 --- a/packages/tooling/contract-test-utils/src/types/CommandParams.ts +++ b/packages/tooling/contract-test-utils/src/types/CommandParams.ts @@ -1,4 +1,6 @@ -import { LDContext, LDEvaluationReason } from '@launchdarkly/js-client-sdk-common'; +// Shared command types with no SDK-specific dependencies. +// SDK-specific types (those referencing LDContext/LDEvaluationReason) are in +// client-side/types/CommandParams.ts and server-side/types/CommandParams.ts. export const CommandType = { EvaluateFlag: 'evaluate', @@ -23,59 +25,10 @@ export const ValueType = { } as const; export type ValueType = (typeof ValueType)[keyof typeof ValueType]; -export interface CommandParams { - command: CommandType; - evaluate?: EvaluateFlagParams; - evaluateAll?: EvaluateAllFlagsParams; - customEvent?: CustomEventParams; - identifyEvent?: IdentifyEventParams; - contextBuild?: ContextBuildParams; - contextConvert?: ContextConvertParams; - contextComparison?: ContextComparisonPairParams; - secureModeHash?: SecureModeHashParams; -} - -export interface EvaluateFlagParams { - flagKey: string; - context?: LDContext; - user?: any; - valueType: ValueType; - defaultValue: unknown; - detail: boolean; -} - -export interface EvaluateFlagResponse { - value: unknown; - variationIndex?: number; - reason?: LDEvaluationReason; -} - -export interface EvaluateAllFlagsParams { - context?: LDContext; - user?: any; - withReasons: boolean; - clientSideOnly: boolean; - detailsOnlyForTrackedFlags: boolean; -} - export interface EvaluateAllFlagsResponse { state: Record; } -export interface CustomEventParams { - eventKey: string; - context?: LDContext; - user?: any; - data?: unknown; - omitNullData: boolean; - metricValue?: number; -} - -export interface IdentifyEventParams { - context?: LDContext; - user?: any; -} - export interface ContextBuildParams { single?: ContextBuildSingleParams; multi?: ContextBuildSingleParams[]; @@ -130,11 +83,6 @@ export interface ContextComparisonResponse { equals: boolean; } -export interface SecureModeHashParams { - context?: LDContext; - user?: any; -} - export interface SecureModeHashResponse { result: string; } @@ -144,17 +92,3 @@ export const HookStage = { AfterEvaluation: 'afterEvaluation', } as const; export type HookStage = (typeof HookStage)[keyof typeof HookStage]; - -export interface EvaluationSeriesContext { - flagKey: string; - context: LDContext; - defaultValue: unknown; - method: string; -} - -export interface HookExecutionPayload { - evaluationSeriesContext?: EvaluationSeriesContext; - evaluationSeriesData?: Record; - evaluationDetail?: EvaluateFlagResponse; - stage?: HookStage; -} diff --git a/packages/tooling/contract-test-utils/src/types/ConfigParams.ts b/packages/tooling/contract-test-utils/src/types/ConfigParams.ts index 6727ff5e0b..a16338f590 100644 --- a/packages/tooling/contract-test-utils/src/types/ConfigParams.ts +++ b/packages/tooling/contract-test-utils/src/types/ConfigParams.ts @@ -1,28 +1,9 @@ -import { LDContext } from '@launchdarkly/js-client-sdk-common'; +// Shared config types with no SDK-specific dependencies. +// SDK-specific types (those referencing LDContext) are in +// client-side/types/ConfigParams.ts and server-side/types/ConfigParams.ts. import { HookStage } from './CommandParams'; -export interface CreateInstanceParams { - configuration: SDKConfigParams; - tag: string; -} - -export interface SDKConfigParams { - credential: string; - startWaitTimeMs?: number; // UnixMillisecondTime - initCanFail?: boolean; - serviceEndpoints?: SDKConfigServiceEndpointsParams; - tls?: SDKConfigTLSParams; - streaming?: SDKConfigStreamingParams; - polling?: SDKConfigPollingParams; - events?: SDKConfigEventParams; - tags?: SDKConfigTagsParams; - clientSide?: SDKConfigClientSideParams; - hooks?: SDKConfigHooksParams; - wrapper?: SDKConfigWrapper; - proxy?: SDKConfigProxyParams; -} - export interface SDKConfigTLSParams { skipVerifyPeer?: boolean; customCAFile?: string; @@ -62,14 +43,6 @@ export interface SDKConfigTagsParams { applicationVersion?: string; } -export interface SDKConfigClientSideParams { - initialContext?: LDContext; - initialUser?: any; - evaluationReasons?: boolean; - useReport?: boolean; - includeEnvironmentAttributes?: boolean; -} - export interface SDKConfigEvaluationHookData { [key: string]: unknown; } diff --git a/packages/tooling/contract-test-utils/tsconfig.server.json b/packages/tooling/contract-test-utils/tsconfig.server.json index 26b0e07212..c72ddb656d 100644 --- a/packages/tooling/contract-test-utils/tsconfig.server.json +++ b/packages/tooling/contract-test-utils/tsconfig.server.json @@ -1,5 +1,5 @@ { "extends": "./tsconfig.json", - "include": ["src/server.ts", "src/server-side/*"], + "include": ["src/server.ts", "src/server-side/*", "src/server-side/types/*", "src/types/*"], "exclude": ["dist", "node_modules"] } From be05c461e4e85994ed2a4068da4a31325d31efa8 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 10 Mar 2026 23:11:31 +0000 Subject: [PATCH 13/21] refactor: simplify type deduplication using compat.ts pattern from PR #1151 - Add src/types/compat.ts with minimal LDContext and LDEvaluationReason types compatible with both client and server SDKs (no SDK imports needed) - Merge all types back into unified CommandParams.ts and ConfigParams.ts (includes both client-specific and server-specific fields) - Delete src/client-side/types/ and src/server-side/types/ directories (no longer needed with compat types approach) - Simplify barrel exports: client.ts re-exports from index, server.ts re-exports from types/ directly - Remove @launchdarkly/js-server-sdk-common peer dependency - Add necessary casts in server-node consumer for SDK-specific types Co-Authored-By: Steven Zhang --- .../contract-tests/src/sdkClientEntity.ts | 19 +-- .../tooling/contract-test-utils/package.json | 4 - .../src/client-side/types/CommandParams.ts | 96 ------------- .../src/client-side/types/ConfigParams.ts | 57 -------- .../tooling/contract-test-utils/src/client.ts | 13 +- .../tooling/contract-test-utils/src/index.ts | 4 +- .../src/server-side/types/CommandParams.ts | 133 ------------------ .../src/server-side/types/ConfigParams.ts | 84 ----------- .../tooling/contract-test-utils/src/server.ts | 6 +- .../src/types/CommandParams.ts | 108 +++++++++++++- .../src/types/ConfigParams.ts | 72 +++++++++- .../contract-test-utils/src/types/compat.ts | 22 +++ .../contract-test-utils/tsconfig.server.json | 2 +- 13 files changed, 210 insertions(+), 410 deletions(-) delete mode 100644 packages/tooling/contract-test-utils/src/client-side/types/CommandParams.ts delete mode 100644 packages/tooling/contract-test-utils/src/client-side/types/ConfigParams.ts delete mode 100644 packages/tooling/contract-test-utils/src/server-side/types/CommandParams.ts delete mode 100644 packages/tooling/contract-test-utils/src/server-side/types/ConfigParams.ts create mode 100644 packages/tooling/contract-test-utils/src/types/compat.ts diff --git a/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts b/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts index e1739a1e06..270d5a3f46 100644 --- a/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts +++ b/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts @@ -14,6 +14,7 @@ import ld, { LDContext, LDExecutionOrdering, LDMigrationError, + LDMigrationStage, LDMigrationSuccess, LDOptions, LDSerialExecution, @@ -251,12 +252,12 @@ export async function newSdkClientEntity(options: CreateInstanceParams): Promise if (pe.detail) { switch (pe.valueType) { case 'bool': - return client.boolVariationDetail(pe.flagKey, context, pe.defaultValue); + return client.boolVariationDetail(pe.flagKey, context, pe.defaultValue as boolean); case 'int': // Intentional fallthrough. case 'double': - return client.numberVariationDetail(pe.flagKey, context, pe.defaultValue); + return client.numberVariationDetail(pe.flagKey, context, pe.defaultValue as number); case 'string': - return client.stringVariationDetail(pe.flagKey, context, pe.defaultValue); + return client.stringVariationDetail(pe.flagKey, context, pe.defaultValue as string); default: return client.variationDetail( pe.flagKey, @@ -268,16 +269,16 @@ export async function newSdkClientEntity(options: CreateInstanceParams): Promise switch (pe.valueType) { case 'bool': return { - value: await client.boolVariation(pe.flagKey, context, pe.defaultValue), + value: await client.boolVariation(pe.flagKey, context, pe.defaultValue as boolean), }; case 'int': // Intentional fallthrough. case 'double': return { - value: await client.numberVariation(pe.flagKey, context, pe.defaultValue), + value: await client.numberVariation(pe.flagKey, context, pe.defaultValue as number), }; case 'string': return { - value: await client.stringVariation(pe.flagKey, context, pe.defaultValue), + value: await client.stringVariation(pe.flagKey, context, pe.defaultValue as string), }; default: return { @@ -319,7 +320,7 @@ export async function newSdkClientEntity(options: CreateInstanceParams): Promise const res = await client.migrationVariation( migrationVariation.key, migrationVariation.context, - migrationVariation.defaultStage, + migrationVariation.defaultStage as LDMigrationStage, ); return { result: res.value }; } @@ -384,7 +385,7 @@ export async function newSdkClientEntity(options: CreateInstanceParams): Promise const res = await migration.read( migrationOperation.key, migrationOperation.context, - migrationOperation.defaultStage, + migrationOperation.defaultStage as LDMigrationStage, migrationOperation.payload, ); if (res.success) { @@ -396,7 +397,7 @@ export async function newSdkClientEntity(options: CreateInstanceParams): Promise const res = await migration.write( migrationOperation.key, migrationOperation.context, - migrationOperation.defaultStage, + migrationOperation.defaultStage as LDMigrationStage, migrationOperation.payload, ); diff --git a/packages/tooling/contract-test-utils/package.json b/packages/tooling/contract-test-utils/package.json index 79ee984abb..1fb416087a 100644 --- a/packages/tooling/contract-test-utils/package.json +++ b/packages/tooling/contract-test-utils/package.json @@ -41,14 +41,10 @@ "@launchdarkly/js-client-sdk-common": "workspace:^" }, "peerDependencies": { - "@launchdarkly/js-server-sdk-common": "workspace:^", "@launchdarkly/node-server-sdk": "workspace:^", "got": "14.4.7" }, "peerDependenciesMeta": { - "@launchdarkly/js-server-sdk-common": { - "optional": true - }, "@launchdarkly/node-server-sdk": { "optional": true }, diff --git a/packages/tooling/contract-test-utils/src/client-side/types/CommandParams.ts b/packages/tooling/contract-test-utils/src/client-side/types/CommandParams.ts deleted file mode 100644 index dc8b4a656e..0000000000 --- a/packages/tooling/contract-test-utils/src/client-side/types/CommandParams.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { LDContext, LDEvaluationReason } from '@launchdarkly/js-client-sdk-common'; - -import { - CommandType, - ContextBuildParams, - ContextComparisonPairParams, - ContextConvertParams, - HookStage, - ValueType, -} from '../../types/CommandParams'; - -// Re-export shared types for convenience -export { CommandType, ValueType } from '../../types/CommandParams'; -export type { - EvaluateAllFlagsResponse, - ContextBuildParams, - ContextBuildSingleParams, - ContextBuildResponse, - ContextConvertParams, - ContextComparisonPairParams, - ContextComparisonParams, - ContextComparisonSingleParams, - AttributeDefinition, - PrivateAttribute, - ContextComparisonResponse, - SecureModeHashResponse, -} from '../../types/CommandParams'; -export { HookStage } from '../../types/CommandParams'; - -export interface CommandParams { - command: CommandType; - evaluate?: EvaluateFlagParams; - evaluateAll?: EvaluateAllFlagsParams; - customEvent?: CustomEventParams; - identifyEvent?: IdentifyEventParams; - contextBuild?: ContextBuildParams; - contextConvert?: ContextConvertParams; - contextComparison?: ContextComparisonPairParams; - secureModeHash?: SecureModeHashParams; -} - -export interface EvaluateFlagParams { - flagKey: string; - context?: LDContext; - user?: any; - valueType: ValueType; - defaultValue: unknown; - detail: boolean; -} - -export interface EvaluateFlagResponse { - value: unknown; - variationIndex?: number; - reason?: LDEvaluationReason; -} - -export interface EvaluateAllFlagsParams { - context?: LDContext; - user?: any; - withReasons: boolean; - clientSideOnly: boolean; - detailsOnlyForTrackedFlags: boolean; -} - -export interface CustomEventParams { - eventKey: string; - context?: LDContext; - user?: any; - data?: unknown; - omitNullData: boolean; - metricValue?: number; -} - -export interface IdentifyEventParams { - context?: LDContext; - user?: any; -} - -export interface SecureModeHashParams { - context?: LDContext; - user?: any; -} - -export interface EvaluationSeriesContext { - flagKey: string; - context: LDContext; - defaultValue: unknown; - method: string; -} - -export interface HookExecutionPayload { - evaluationSeriesContext?: EvaluationSeriesContext; - evaluationSeriesData?: Record; - evaluationDetail?: EvaluateFlagResponse; - stage?: HookStage; -} diff --git a/packages/tooling/contract-test-utils/src/client-side/types/ConfigParams.ts b/packages/tooling/contract-test-utils/src/client-side/types/ConfigParams.ts deleted file mode 100644 index 4b76e5a47f..0000000000 --- a/packages/tooling/contract-test-utils/src/client-side/types/ConfigParams.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { LDContext } from '@launchdarkly/js-client-sdk-common'; - -import { - SDKConfigEventParams, - SDKConfigHooksParams, - SDKConfigPollingParams, - SDKConfigProxyParams, - SDKConfigServiceEndpointsParams, - SDKConfigStreamingParams, - SDKConfigTagsParams, - SDKConfigTLSParams, - SDKConfigWrapper, -} from '../../types/ConfigParams'; - -// Re-export shared config types for convenience -export type { - SDKConfigTLSParams, - SDKConfigServiceEndpointsParams, - SDKConfigStreamingParams, - SDKConfigPollingParams, - SDKConfigEventParams, - SDKConfigTagsParams, - SDKConfigEvaluationHookData, - SDKConfigHookInstance, - SDKConfigHooksParams, - SDKConfigProxyParams, - SDKConfigWrapper, -} from '../../types/ConfigParams'; - -export interface SDKConfigClientSideParams { - initialContext?: LDContext; - initialUser?: any; - evaluationReasons?: boolean; - useReport?: boolean; - includeEnvironmentAttributes?: boolean; -} - -export interface SDKConfigParams { - credential: string; - startWaitTimeMs?: number; // UnixMillisecondTime - initCanFail?: boolean; - serviceEndpoints?: SDKConfigServiceEndpointsParams; - tls?: SDKConfigTLSParams; - streaming?: SDKConfigStreamingParams; - polling?: SDKConfigPollingParams; - events?: SDKConfigEventParams; - tags?: SDKConfigTagsParams; - clientSide?: SDKConfigClientSideParams; - hooks?: SDKConfigHooksParams; - wrapper?: SDKConfigWrapper; - proxy?: SDKConfigProxyParams; -} - -export interface CreateInstanceParams { - configuration: SDKConfigParams; - tag: string; -} diff --git a/packages/tooling/contract-test-utils/src/client.ts b/packages/tooling/contract-test-utils/src/client.ts index 90471f4e6e..d0a167b519 100644 --- a/packages/tooling/contract-test-utils/src/client.ts +++ b/packages/tooling/contract-test-utils/src/client.ts @@ -1,14 +1,5 @@ -// Re-export shared base types (no SDK dependency) -export * from './types/CommandParams'; -export * from './types/ConfigParams'; - -// Re-export client-specific types (uses @launchdarkly/js-client-sdk-common) -export * from './client-side/types/CommandParams'; -export * from './client-side/types/ConfigParams'; - -// Re-export shared utilities -export { makeLogger } from './logging/makeLogger'; -export { ClientPool } from './server-side/ClientPool'; +// Re-export universal exports +export * from './index'; // Client-side exports export { default as ClientSideTestHook } from './client-side/TestHook'; diff --git a/packages/tooling/contract-test-utils/src/index.ts b/packages/tooling/contract-test-utils/src/index.ts index a11b0da647..06af41c87e 100644 --- a/packages/tooling/contract-test-utils/src/index.ts +++ b/packages/tooling/contract-test-utils/src/index.ts @@ -1,6 +1,4 @@ -// Universal exports (no SDK dependency) -// These types have no references to LDContext or LDEvaluationReason. -// SDK-specific types are exported from ./client and ./server subpaths. +// Universal exports (types use minimal compat types, no SDK dependency) export * from './types/CommandParams'; export * from './types/ConfigParams'; export { makeLogger } from './logging/makeLogger'; diff --git a/packages/tooling/contract-test-utils/src/server-side/types/CommandParams.ts b/packages/tooling/contract-test-utils/src/server-side/types/CommandParams.ts deleted file mode 100644 index 3501099107..0000000000 --- a/packages/tooling/contract-test-utils/src/server-side/types/CommandParams.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { LDContext, LDEvaluationReason, LDMigrationStage } from '@launchdarkly/js-server-sdk-common'; - -import { - CommandType, - ContextBuildParams, - ContextComparisonPairParams, - ContextConvertParams, - HookStage, - ValueType, -} from '../../types/CommandParams.js'; - -// Re-export shared types for convenience -export { - CommandType, - ValueType, - EvaluateAllFlagsResponse, - ContextBuildParams, - ContextBuildSingleParams, - ContextBuildResponse, - ContextConvertParams, - ContextComparisonPairParams, - ContextComparisonParams, - ContextComparisonSingleParams, - AttributeDefinition, - PrivateAttribute, - ContextComparisonResponse, - SecureModeHashResponse, - HookStage, -} from '../../types/CommandParams.js'; - -export interface CommandParams { - command: CommandType | string; - evaluate?: EvaluateFlagParams; - evaluateAll?: EvaluateAllFlagsParams; - customEvent?: CustomEventParams; - identifyEvent?: IdentifyEventParams; - contextBuild?: ContextBuildParams; - contextConvert?: ContextConvertParams; - contextComparison?: ContextComparisonPairParams; - secureModeHash?: SecureModeHashParams; - // Server-specific command fields - migrationVariation?: MigrationVariationParams; - migrationOperation?: MigrationOperationParams; - registerFlagChangeListener?: RegisterFlagChangeListenerParams; - unregisterListener?: UnregisterListenerParams; -} - -export interface EvaluateFlagParams { - flagKey: string; - context?: LDContext; - user?: any; - valueType: ValueType; - defaultValue: any; - detail: boolean; -} - -export interface EvaluateFlagResponse { - value: unknown; - variationIndex?: number; - reason?: LDEvaluationReason; -} - -export interface EvaluateAllFlagsParams { - context?: LDContext; - user?: any; - withReasons: boolean; - clientSideOnly: boolean; - detailsOnlyForTrackedFlags: boolean; -} - -export interface CustomEventParams { - eventKey: string; - context?: LDContext; - user?: any; - data?: unknown; - omitNullData: boolean; - metricValue?: number; -} - -export interface IdentifyEventParams { - context?: LDContext; - user?: any; -} - -export interface SecureModeHashParams { - context?: LDContext; - user?: any; -} - -export interface EvaluationSeriesContext { - flagKey: string; - context: LDContext; - defaultValue: unknown; - method: string; -} - -export interface HookExecutionPayload { - evaluationSeriesContext?: EvaluationSeriesContext; - evaluationSeriesData?: Record; - evaluationDetail?: EvaluateFlagResponse; - stage?: HookStage; -} - -// Server-specific command parameter types - -export interface MigrationVariationParams { - key: string; - context: LDContext; - defaultStage: LDMigrationStage; -} - -export interface MigrationOperationParams { - operation: string; - key: string; - context: LDContext; - defaultStage: LDMigrationStage; - payload: any; - readExecutionOrder: string; - trackLatency?: boolean; - trackErrors?: boolean; - trackConsistency?: boolean; - newEndpoint: string; - oldEndpoint: string; -} - -export interface RegisterFlagChangeListenerParams { - listenerId: string; - callbackUri: string; -} - -export interface UnregisterListenerParams { - listenerId: string; -} diff --git a/packages/tooling/contract-test-utils/src/server-side/types/ConfigParams.ts b/packages/tooling/contract-test-utils/src/server-side/types/ConfigParams.ts deleted file mode 100644 index 9dd49ad159..0000000000 --- a/packages/tooling/contract-test-utils/src/server-side/types/ConfigParams.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { - SDKConfigEventParams, - SDKConfigHooksParams, - SDKConfigPollingParams, - SDKConfigProxyParams, - SDKConfigServiceEndpointsParams, - SDKConfigStreamingParams, - SDKConfigTagsParams, - SDKConfigTLSParams, - SDKConfigWrapper, -} from '../../types/ConfigParams.js'; - -// Re-export shared config types for convenience -export { - SDKConfigTLSParams, - SDKConfigServiceEndpointsParams, - SDKConfigStreamingParams, - SDKConfigPollingParams, - SDKConfigEventParams, - SDKConfigTagsParams, - SDKConfigEvaluationHookData, - SDKConfigHookInstance, - SDKConfigHooksParams, - SDKConfigProxyParams, - SDKConfigWrapper, -} from '../../types/ConfigParams.js'; - -// Server-specific config types - -export interface SDKConfigBigSegmentsParams { - callbackUri: string; - userCacheSize?: number; - userCacheTimeMs?: number; - statusPollIntervalMs?: number; - staleAfterMs?: number; -} - -export interface SDKDataSourceStreamingParams { - baseUri?: string; - initialRetryDelayMs?: number; -} - -export interface SDKDataSourcePollingParams { - baseUri?: string; - pollIntervalMs?: number; -} - -export interface SDKDataSystemSynchronizerParams { - streaming?: SDKDataSourceStreamingParams; - polling?: SDKDataSourcePollingParams; -} - -export interface SDKDataSystemInitializerParams { - polling?: SDKDataSourcePollingParams; -} - -export interface SDKDataSystemParams { - initializers?: SDKDataSystemInitializerParams[]; - synchronizers?: SDKDataSystemSynchronizerParams[]; - payloadFilter?: string; -} - -export interface SDKConfigParams { - credential: string; - startWaitTimeMs?: number; // UnixMillisecondTime - initCanFail?: boolean; - serviceEndpoints?: SDKConfigServiceEndpointsParams; - tls?: SDKConfigTLSParams; - streaming?: SDKConfigStreamingParams; - polling?: SDKConfigPollingParams; - events?: SDKConfigEventParams; - tags?: SDKConfigTagsParams; - hooks?: SDKConfigHooksParams; - wrapper?: SDKConfigWrapper; - proxy?: SDKConfigProxyParams; - // Server-specific config fields - bigSegments?: SDKConfigBigSegmentsParams; - dataSystem?: SDKDataSystemParams; -} - -export interface CreateInstanceParams { - configuration: SDKConfigParams; - tag: string; -} diff --git a/packages/tooling/contract-test-utils/src/server.ts b/packages/tooling/contract-test-utils/src/server.ts index 6f0ef46c35..858862f5ee 100644 --- a/packages/tooling/contract-test-utils/src/server.ts +++ b/packages/tooling/contract-test-utils/src/server.ts @@ -8,10 +8,6 @@ export type { // Re-export shared utilities export { ClientPool } from './server-side/ClientPool.js'; -// Re-export shared base types (no SDK dependency) +// Re-export all types (uses minimal compat types, no SDK dependency) export * from './types/CommandParams.js'; export * from './types/ConfigParams.js'; - -// Re-export server-specific types (uses @launchdarkly/js-server-sdk-common) -export * from './server-side/types/CommandParams.js'; -export * from './server-side/types/ConfigParams.js'; diff --git a/packages/tooling/contract-test-utils/src/types/CommandParams.ts b/packages/tooling/contract-test-utils/src/types/CommandParams.ts index add4af28bd..db1254c6a9 100644 --- a/packages/tooling/contract-test-utils/src/types/CommandParams.ts +++ b/packages/tooling/contract-test-utils/src/types/CommandParams.ts @@ -1,6 +1,4 @@ -// Shared command types with no SDK-specific dependencies. -// SDK-specific types (those referencing LDContext/LDEvaluationReason) are in -// client-side/types/CommandParams.ts and server-side/types/CommandParams.ts. +import { LDContext, LDEvaluationReason } from './compat'; export const CommandType = { EvaluateFlag: 'evaluate', @@ -25,10 +23,64 @@ export const ValueType = { } as const; export type ValueType = (typeof ValueType)[keyof typeof ValueType]; +export interface CommandParams { + command: CommandType | string; + evaluate?: EvaluateFlagParams; + evaluateAll?: EvaluateAllFlagsParams; + customEvent?: CustomEventParams; + identifyEvent?: IdentifyEventParams; + contextBuild?: ContextBuildParams; + contextConvert?: ContextConvertParams; + contextComparison?: ContextComparisonPairParams; + secureModeHash?: SecureModeHashParams; + // Server-specific command fields + migrationVariation?: MigrationVariationParams; + migrationOperation?: MigrationOperationParams; + registerFlagChangeListener?: RegisterFlagChangeListenerParams; + unregisterListener?: UnregisterListenerParams; +} + +export interface EvaluateFlagParams { + flagKey: string; + context?: LDContext; + user?: any; + valueType: ValueType; + defaultValue: unknown; + detail: boolean; +} + +export interface EvaluateFlagResponse { + value: unknown; + variationIndex?: number; + reason?: LDEvaluationReason; +} + +export interface EvaluateAllFlagsParams { + context?: LDContext; + user?: any; + withReasons: boolean; + clientSideOnly: boolean; + detailsOnlyForTrackedFlags: boolean; +} + export interface EvaluateAllFlagsResponse { state: Record; } +export interface CustomEventParams { + eventKey: string; + context?: LDContext; + user?: any; + data?: unknown; + omitNullData: boolean; + metricValue?: number; +} + +export interface IdentifyEventParams { + context?: LDContext; + user?: any; +} + export interface ContextBuildParams { single?: ContextBuildSingleParams; multi?: ContextBuildSingleParams[]; @@ -83,6 +135,11 @@ export interface ContextComparisonResponse { equals: boolean; } +export interface SecureModeHashParams { + context?: LDContext; + user?: any; +} + export interface SecureModeHashResponse { result: string; } @@ -92,3 +149,48 @@ export const HookStage = { AfterEvaluation: 'afterEvaluation', } as const; export type HookStage = (typeof HookStage)[keyof typeof HookStage]; + +export interface EvaluationSeriesContext { + flagKey: string; + context: LDContext; + defaultValue: unknown; + method: string; +} + +export interface HookExecutionPayload { + evaluationSeriesContext?: EvaluationSeriesContext; + evaluationSeriesData?: Record; + evaluationDetail?: EvaluateFlagResponse; + stage?: HookStage; +} + +// Server-specific command parameter types + +export interface MigrationVariationParams { + key: string; + context: LDContext; + defaultStage: string; +} + +export interface MigrationOperationParams { + operation: string; + key: string; + context: LDContext; + defaultStage: string; + payload: any; + readExecutionOrder: string; + trackLatency?: boolean; + trackErrors?: boolean; + trackConsistency?: boolean; + newEndpoint: string; + oldEndpoint: string; +} + +export interface RegisterFlagChangeListenerParams { + listenerId: string; + callbackUri: string; +} + +export interface UnregisterListenerParams { + listenerId: string; +} diff --git a/packages/tooling/contract-test-utils/src/types/ConfigParams.ts b/packages/tooling/contract-test-utils/src/types/ConfigParams.ts index a16338f590..af992c2e9d 100644 --- a/packages/tooling/contract-test-utils/src/types/ConfigParams.ts +++ b/packages/tooling/contract-test-utils/src/types/ConfigParams.ts @@ -1,9 +1,30 @@ -// Shared config types with no SDK-specific dependencies. -// SDK-specific types (those referencing LDContext) are in -// client-side/types/ConfigParams.ts and server-side/types/ConfigParams.ts. - +import { LDContext } from './compat'; import { HookStage } from './CommandParams'; +export interface CreateInstanceParams { + configuration: SDKConfigParams; + tag: string; +} + +export interface SDKConfigParams { + credential: string; + startWaitTimeMs?: number; // UnixMillisecondTime + initCanFail?: boolean; + serviceEndpoints?: SDKConfigServiceEndpointsParams; + tls?: SDKConfigTLSParams; + streaming?: SDKConfigStreamingParams; + polling?: SDKConfigPollingParams; + events?: SDKConfigEventParams; + tags?: SDKConfigTagsParams; + clientSide?: SDKConfigClientSideParams; + hooks?: SDKConfigHooksParams; + wrapper?: SDKConfigWrapper; + proxy?: SDKConfigProxyParams; + // Server-specific config fields + bigSegments?: SDKConfigBigSegmentsParams; + dataSystem?: SDKDataSystemParams; +} + export interface SDKConfigTLSParams { skipVerifyPeer?: boolean; customCAFile?: string; @@ -43,6 +64,14 @@ export interface SDKConfigTagsParams { applicationVersion?: string; } +export interface SDKConfigClientSideParams { + initialContext?: LDContext; + initialUser?: any; + evaluationReasons?: boolean; + useReport?: boolean; + includeEnvironmentAttributes?: boolean; +} + export interface SDKConfigEvaluationHookData { [key: string]: unknown; } @@ -66,3 +95,38 @@ export interface SDKConfigWrapper { name: string; version: string; } + +// Server-specific config types + +export interface SDKConfigBigSegmentsParams { + callbackUri: string; + userCacheSize?: number; + userCacheTimeMs?: number; + statusPollIntervalMs?: number; + staleAfterMs?: number; +} + +export interface SDKDataSourceStreamingParams { + baseUri?: string; + initialRetryDelayMs?: number; +} + +export interface SDKDataSourcePollingParams { + baseUri?: string; + pollIntervalMs?: number; +} + +export interface SDKDataSystemSynchronizerParams { + streaming?: SDKDataSourceStreamingParams; + polling?: SDKDataSourcePollingParams; +} + +export interface SDKDataSystemInitializerParams { + polling?: SDKDataSourcePollingParams; +} + +export interface SDKDataSystemParams { + initializers?: SDKDataSystemInitializerParams[]; + synchronizers?: SDKDataSystemSynchronizerParams[]; + payloadFilter?: string; +} diff --git a/packages/tooling/contract-test-utils/src/types/compat.ts b/packages/tooling/contract-test-utils/src/types/compat.ts new file mode 100644 index 0000000000..bdaa308fc6 --- /dev/null +++ b/packages/tooling/contract-test-utils/src/types/compat.ts @@ -0,0 +1,22 @@ +/** + * Minimal type definitions for contract test utilities. + * + * These are compatible with the corresponding types from both + * @launchdarkly/js-client-sdk-common and @launchdarkly/js-server-sdk-common, + * allowing the shared package to work without depending on either SDK directly. + */ + +/** + * A minimal LDContext type compatible with both client and server SDKs. + * Contract test harness passes context objects through without deep inspection. + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type LDContext = Record; + +/** + * A minimal LDEvaluationReason type compatible with both client and server SDKs. + */ +export interface LDEvaluationReason { + kind: string; + [key: string]: unknown; +} diff --git a/packages/tooling/contract-test-utils/tsconfig.server.json b/packages/tooling/contract-test-utils/tsconfig.server.json index c72ddb656d..344bee78fa 100644 --- a/packages/tooling/contract-test-utils/tsconfig.server.json +++ b/packages/tooling/contract-test-utils/tsconfig.server.json @@ -1,5 +1,5 @@ { "extends": "./tsconfig.json", - "include": ["src/server.ts", "src/server-side/*", "src/server-side/types/*", "src/types/*"], + "include": ["src/server.ts", "src/server-side/*", "src/types/*"], "exclude": ["dist", "node_modules"] } From 5a0fcf6ad579b567d1129fad8ebe6497c1639266 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 15:52:03 +0000 Subject: [PATCH 14/21] refactor: make ./client and ./server exports consistent (both from dist/) Co-Authored-By: Steven Zhang --- .../contract-tests/src/sdkClientEntity.ts | 14 +++++++------ .../contract-tests/src/utils/clientPool.ts | 4 ++-- .../tooling/contract-test-utils/package.json | 20 +++++++++---------- .../tooling/contract-test-utils/src/client.ts | 6 +++--- .../tooling/contract-test-utils/src/index.ts | 8 ++++---- .../tooling/contract-test-utils/src/server.ts | 10 +++------- .../src/types/CommandParams.ts | 2 +- .../src/types/ConfigParams.ts | 4 ++-- .../tooling/contract-test-utils/tsconfig.json | 2 +- .../contract-test-utils/tsconfig.server.json | 2 +- 10 files changed, 35 insertions(+), 37 deletions(-) diff --git a/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts b/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts index 270d5a3f46..717039d823 100644 --- a/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts +++ b/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts @@ -192,10 +192,10 @@ function makeMigrationPostOptions(payload: any) { } function contextOrUser( - context: LDContext | undefined, + context: Record | undefined, user: LDUser | undefined, ): LDContext | LDUser { - return (context || user)!; + return (context as LDContext | undefined) || user!; } export interface SdkClientEntity { @@ -299,7 +299,9 @@ export async function newSdkClientEntity(options: CreateInstanceParams): Promise } case 'identifyEvent': - client.identify(params.identifyEvent!.context || params.identifyEvent!.user!); + client.identify( + (params.identifyEvent!.context as LDContext) || params.identifyEvent!.user!, + ); return undefined; case 'customEvent': { @@ -319,7 +321,7 @@ export async function newSdkClientEntity(options: CreateInstanceParams): Promise const migrationVariation = params.migrationVariation!; const res = await client.migrationVariation( migrationVariation.key, - migrationVariation.context, + migrationVariation.context as LDContext, migrationVariation.defaultStage as LDMigrationStage, ); return { result: res.value }; @@ -384,7 +386,7 @@ export async function newSdkClientEntity(options: CreateInstanceParams): Promise case 'read': { const res = await migration.read( migrationOperation.key, - migrationOperation.context, + migrationOperation.context as LDContext, migrationOperation.defaultStage as LDMigrationStage, migrationOperation.payload, ); @@ -396,7 +398,7 @@ export async function newSdkClientEntity(options: CreateInstanceParams): Promise case 'write': { const res = await migration.write( migrationOperation.key, - migrationOperation.context, + migrationOperation.context as LDContext, migrationOperation.defaultStage as LDMigrationStage, migrationOperation.payload, ); diff --git a/packages/sdk/shopify-oxygen/contract-tests/src/utils/clientPool.ts b/packages/sdk/shopify-oxygen/contract-tests/src/utils/clientPool.ts index 5c6b38039d..d0ca93dcdb 100644 --- a/packages/sdk/shopify-oxygen/contract-tests/src/utils/clientPool.ts +++ b/packages/sdk/shopify-oxygen/contract-tests/src/utils/clientPool.ts @@ -7,9 +7,9 @@ import { init } from '@launchdarkly/shopify-oxygen-sdk'; /* eslint-disable no-console */ /** - * ClientPool manages a pool of LDClient instances for Shopify Oxygen contract tests. + * ClientPool manages a pool of LDClient instances for contract tests. * It uses the shared generic ClientPool for client storage and ID generation, and - * handles the Oxygen-specific client creation, command execution, and response sending. + * handles SDK-specific client creation, command execution, and response sending. * * @see https://github.com/launchdarkly/sdk-test-harness/blob/v2/docs/service_spec.md */ diff --git a/packages/tooling/contract-test-utils/package.json b/packages/tooling/contract-test-utils/package.json index 1fb416087a..8262e25337 100644 --- a/packages/tooling/contract-test-utils/package.json +++ b/packages/tooling/contract-test-utils/package.json @@ -3,18 +3,18 @@ "version": "0.0.0", "private": true, "type": "module", - "main": "./src/index.ts", - "types": "./src/index.ts", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { - "types": "./src/index.ts", - "import": "./src/index.ts", - "default": "./src/index.ts" + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" }, "./client": { - "types": "./src/client.ts", - "import": "./src/client.ts", - "default": "./src/client.ts" + "types": "./dist/client.d.ts", + "import": "./dist/client.js", + "default": "./dist/client.js" }, "./server": { "types": "./dist/server.d.ts", @@ -25,7 +25,7 @@ "typesVersions": { "*": { "client": [ - "./src/client.ts" + "./dist/client.d.ts" ], "server": [ "./dist/server.d.ts" @@ -33,7 +33,7 @@ } }, "scripts": { - "build": "echo 'Client exports use source .ts files; use build:server for ./server subpath'", + "build": "tsc", "build:server": "tsc -p tsconfig.server.json", "clean": "rimraf dist" }, diff --git a/packages/tooling/contract-test-utils/src/client.ts b/packages/tooling/contract-test-utils/src/client.ts index d0a167b519..bb03bb1a4c 100644 --- a/packages/tooling/contract-test-utils/src/client.ts +++ b/packages/tooling/contract-test-utils/src/client.ts @@ -1,9 +1,9 @@ // Re-export universal exports -export * from './index'; +export * from './index.js'; // Client-side exports -export { default as ClientSideTestHook } from './client-side/TestHook'; +export { default as ClientSideTestHook } from './client-side/TestHook.js'; export type { HookData as ClientSideHookData, HookErrors as ClientSideHookErrors, -} from './client-side/TestHook'; +} from './client-side/TestHook.js'; diff --git a/packages/tooling/contract-test-utils/src/index.ts b/packages/tooling/contract-test-utils/src/index.ts index 06af41c87e..f227ed20f3 100644 --- a/packages/tooling/contract-test-utils/src/index.ts +++ b/packages/tooling/contract-test-utils/src/index.ts @@ -1,5 +1,5 @@ // Universal exports (types use minimal compat types, no SDK dependency) -export * from './types/CommandParams'; -export * from './types/ConfigParams'; -export { makeLogger } from './logging/makeLogger'; -export { ClientPool } from './server-side/ClientPool'; +export * from './types/CommandParams.js'; +export * from './types/ConfigParams.js'; +export { makeLogger } from './logging/makeLogger.js'; +export { ClientPool } from './server-side/ClientPool.js'; diff --git a/packages/tooling/contract-test-utils/src/server.ts b/packages/tooling/contract-test-utils/src/server.ts index 858862f5ee..a3cbeafced 100644 --- a/packages/tooling/contract-test-utils/src/server.ts +++ b/packages/tooling/contract-test-utils/src/server.ts @@ -1,13 +1,9 @@ +// Re-export universal exports +export * from './index.js'; + // Server-side exports export { default as ServerSideTestHook } from './server-side/TestHook.js'; export type { HookData as ServerSideHookData, HookErrors as ServerSideHookErrors, } from './server-side/TestHook.js'; - -// Re-export shared utilities -export { ClientPool } from './server-side/ClientPool.js'; - -// Re-export all types (uses minimal compat types, no SDK dependency) -export * from './types/CommandParams.js'; -export * from './types/ConfigParams.js'; diff --git a/packages/tooling/contract-test-utils/src/types/CommandParams.ts b/packages/tooling/contract-test-utils/src/types/CommandParams.ts index db1254c6a9..6fe84c7da6 100644 --- a/packages/tooling/contract-test-utils/src/types/CommandParams.ts +++ b/packages/tooling/contract-test-utils/src/types/CommandParams.ts @@ -1,4 +1,4 @@ -import { LDContext, LDEvaluationReason } from './compat'; +import { LDContext, LDEvaluationReason } from './compat.js'; export const CommandType = { EvaluateFlag: 'evaluate', diff --git a/packages/tooling/contract-test-utils/src/types/ConfigParams.ts b/packages/tooling/contract-test-utils/src/types/ConfigParams.ts index af992c2e9d..33eae95723 100644 --- a/packages/tooling/contract-test-utils/src/types/ConfigParams.ts +++ b/packages/tooling/contract-test-utils/src/types/ConfigParams.ts @@ -1,5 +1,5 @@ -import { LDContext } from './compat'; -import { HookStage } from './CommandParams'; +import { HookStage } from './CommandParams.js'; +import { LDContext } from './compat.js'; export interface CreateInstanceParams { configuration: SDKConfigParams; diff --git a/packages/tooling/contract-test-utils/tsconfig.json b/packages/tooling/contract-test-utils/tsconfig.json index 3c47238f16..42b587636e 100644 --- a/packages/tooling/contract-test-utils/tsconfig.json +++ b/packages/tooling/contract-test-utils/tsconfig.json @@ -21,5 +21,5 @@ } }, "include": ["src/**/*"], - "exclude": ["dist", "node_modules"] + "exclude": ["dist", "node_modules", "src/server.ts", "src/server-side/TestHook.ts"] } diff --git a/packages/tooling/contract-test-utils/tsconfig.server.json b/packages/tooling/contract-test-utils/tsconfig.server.json index 344bee78fa..7642d6e970 100644 --- a/packages/tooling/contract-test-utils/tsconfig.server.json +++ b/packages/tooling/contract-test-utils/tsconfig.server.json @@ -1,5 +1,5 @@ { "extends": "./tsconfig.json", - "include": ["src/server.ts", "src/server-side/*", "src/types/*"], + "include": ["src/**/*"], "exclude": ["dist", "node_modules"] } From 0e2c2330e41a6c074dad78285e1dea0594439e2c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 15:59:54 +0000 Subject: [PATCH 15/21] fix: revert ./client and ./ exports to source, keep ./server at dist/ Client-side consumers (browser, electron, react) use bundlers that resolve .ts source directly - no build step needed. Server-side consumers (server-node) compile with tsc and run with node - need compiled .js output. Mixed export strategy is a technical constraint, not an inconsistency. server.ts now explicitly lists all exports with .js extensions instead of re-exporting from ./index.js to avoid pulling in extensionless imports in the compiled output. Co-Authored-By: Steven Zhang --- .../tooling/contract-test-utils/package.json | 20 +++++++++---------- .../tooling/contract-test-utils/src/client.ts | 6 +++--- .../tooling/contract-test-utils/src/index.ts | 8 ++++---- .../tooling/contract-test-utils/src/server.ts | 11 +++++++--- 4 files changed, 25 insertions(+), 20 deletions(-) diff --git a/packages/tooling/contract-test-utils/package.json b/packages/tooling/contract-test-utils/package.json index 8262e25337..1fb416087a 100644 --- a/packages/tooling/contract-test-utils/package.json +++ b/packages/tooling/contract-test-utils/package.json @@ -3,18 +3,18 @@ "version": "0.0.0", "private": true, "type": "module", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", + "main": "./src/index.ts", + "types": "./src/index.ts", "exports": { ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "default": "./dist/index.js" + "types": "./src/index.ts", + "import": "./src/index.ts", + "default": "./src/index.ts" }, "./client": { - "types": "./dist/client.d.ts", - "import": "./dist/client.js", - "default": "./dist/client.js" + "types": "./src/client.ts", + "import": "./src/client.ts", + "default": "./src/client.ts" }, "./server": { "types": "./dist/server.d.ts", @@ -25,7 +25,7 @@ "typesVersions": { "*": { "client": [ - "./dist/client.d.ts" + "./src/client.ts" ], "server": [ "./dist/server.d.ts" @@ -33,7 +33,7 @@ } }, "scripts": { - "build": "tsc", + "build": "echo 'Client exports use source .ts files; use build:server for ./server subpath'", "build:server": "tsc -p tsconfig.server.json", "clean": "rimraf dist" }, diff --git a/packages/tooling/contract-test-utils/src/client.ts b/packages/tooling/contract-test-utils/src/client.ts index bb03bb1a4c..d0a167b519 100644 --- a/packages/tooling/contract-test-utils/src/client.ts +++ b/packages/tooling/contract-test-utils/src/client.ts @@ -1,9 +1,9 @@ // Re-export universal exports -export * from './index.js'; +export * from './index'; // Client-side exports -export { default as ClientSideTestHook } from './client-side/TestHook.js'; +export { default as ClientSideTestHook } from './client-side/TestHook'; export type { HookData as ClientSideHookData, HookErrors as ClientSideHookErrors, -} from './client-side/TestHook.js'; +} from './client-side/TestHook'; diff --git a/packages/tooling/contract-test-utils/src/index.ts b/packages/tooling/contract-test-utils/src/index.ts index f227ed20f3..06af41c87e 100644 --- a/packages/tooling/contract-test-utils/src/index.ts +++ b/packages/tooling/contract-test-utils/src/index.ts @@ -1,5 +1,5 @@ // Universal exports (types use minimal compat types, no SDK dependency) -export * from './types/CommandParams.js'; -export * from './types/ConfigParams.js'; -export { makeLogger } from './logging/makeLogger.js'; -export { ClientPool } from './server-side/ClientPool.js'; +export * from './types/CommandParams'; +export * from './types/ConfigParams'; +export { makeLogger } from './logging/makeLogger'; +export { ClientPool } from './server-side/ClientPool'; diff --git a/packages/tooling/contract-test-utils/src/server.ts b/packages/tooling/contract-test-utils/src/server.ts index a3cbeafced..01ad5240df 100644 --- a/packages/tooling/contract-test-utils/src/server.ts +++ b/packages/tooling/contract-test-utils/src/server.ts @@ -1,9 +1,14 @@ -// Re-export universal exports -export * from './index.js'; - // Server-side exports export { default as ServerSideTestHook } from './server-side/TestHook.js'; export type { HookData as ServerSideHookData, HookErrors as ServerSideHookErrors, } from './server-side/TestHook.js'; + +// Re-export shared utilities +export { ClientPool } from './server-side/ClientPool.js'; +export { makeLogger } from './logging/makeLogger.js'; + +// Re-export all types (uses minimal compat types, no SDK dependency) +export * from './types/CommandParams.js'; +export * from './types/ConfigParams.js'; From 7d1a9807bc7e2366b222bd531d1cc5e091c5b2fa Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 19:56:55 +0000 Subject: [PATCH 16/21] refactor: make all exports consistent from dist/, add build steps to CI workflows - build script now runs tsc (compiles client + types + utilities, excludes server files) - build:server compiles everything including server files - All three subpath exports (., ./client, ./server) consistently point to dist/ - server.ts now re-exports from ./index.js like client.ts (consistent pattern) - Added explicit contract-test-utils build steps to browser, electron, shopify-oxygen, and react-native CI workflows Co-Authored-By: Steven Zhang --- .github/workflows/browser.yml | 3 +++ .github/workflows/electron.yaml | 1 + .../workflows/react-native-contract-tests.yml | 3 +++ .github/workflows/shopify-oxygen.yml | 2 ++ .../tooling/contract-test-utils/package.json | 20 +++++++++---------- .../tooling/contract-test-utils/src/client.ts | 6 +++--- .../tooling/contract-test-utils/src/index.ts | 8 ++++---- .../tooling/contract-test-utils/src/server.ts | 11 +++------- 8 files changed, 29 insertions(+), 25 deletions(-) diff --git a/.github/workflows/browser.yml b/.github/workflows/browser.yml index a292fc341c..ed9686b656 100644 --- a/.github/workflows/browser.yml +++ b/.github/workflows/browser.yml @@ -50,6 +50,9 @@ jobs: - name: Install Playwright browsers run: yarn workspace browser-contract-test-service install-playwright-browsers + - name: Build shared contract test utils + run: yarn workspace @launchdarkly/js-contract-test-utils build + - name: Build contract test adapter run: yarn workspace browser-contract-test-adapter run build diff --git a/.github/workflows/electron.yaml b/.github/workflows/electron.yaml index 20bfaae11f..7a966c97d4 100644 --- a/.github/workflows/electron.yaml +++ b/.github/workflows/electron.yaml @@ -40,6 +40,7 @@ jobs: ELECTRON_DISABLE_SANDBOX: '1' run: | yarn workspaces focus @internal/electron-contract-tests-entity + yarn workspace @launchdarkly/js-contract-test-utils build yarn workspace @internal/electron-contract-tests-entity build sudo apt-get install -y xvfb Xvfb :99 -screen 0 1024x768x24 > /tmp/xvfb.log 2>&1 & diff --git a/.github/workflows/react-native-contract-tests.yml b/.github/workflows/react-native-contract-tests.yml index 687c965736..b40f94423e 100644 --- a/.github/workflows/react-native-contract-tests.yml +++ b/.github/workflows/react-native-contract-tests.yml @@ -33,6 +33,9 @@ jobs: - name: Build SDK and dependencies run: yarn workspaces foreach -pR --topological-dev --from '@launchdarkly/react-native-client-sdk' run build + - name: Build shared contract test utils + run: yarn workspace @launchdarkly/js-contract-test-utils build + - name: Build contract test adapter run: yarn workspace react-native-contract-test-adapter run build diff --git a/.github/workflows/shopify-oxygen.yml b/.github/workflows/shopify-oxygen.yml index 80190925fb..a0b93399ea 100644 --- a/.github/workflows/shopify-oxygen.yml +++ b/.github/workflows/shopify-oxygen.yml @@ -28,6 +28,8 @@ jobs: workspace_path: packages/sdk/shopify-oxygen - name: Install contract test service dependencies run: yarn workspace @launchdarkly/shopify-oxygen-contract-tests install --no-immutable + - name: Build shared contract test utils + run: yarn workspace @launchdarkly/js-contract-test-utils build - name: Build the test service run: yarn workspace @launchdarkly/shopify-oxygen-contract-tests build - name: Launch the test service in the background diff --git a/packages/tooling/contract-test-utils/package.json b/packages/tooling/contract-test-utils/package.json index 1fb416087a..8262e25337 100644 --- a/packages/tooling/contract-test-utils/package.json +++ b/packages/tooling/contract-test-utils/package.json @@ -3,18 +3,18 @@ "version": "0.0.0", "private": true, "type": "module", - "main": "./src/index.ts", - "types": "./src/index.ts", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "exports": { ".": { - "types": "./src/index.ts", - "import": "./src/index.ts", - "default": "./src/index.ts" + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "default": "./dist/index.js" }, "./client": { - "types": "./src/client.ts", - "import": "./src/client.ts", - "default": "./src/client.ts" + "types": "./dist/client.d.ts", + "import": "./dist/client.js", + "default": "./dist/client.js" }, "./server": { "types": "./dist/server.d.ts", @@ -25,7 +25,7 @@ "typesVersions": { "*": { "client": [ - "./src/client.ts" + "./dist/client.d.ts" ], "server": [ "./dist/server.d.ts" @@ -33,7 +33,7 @@ } }, "scripts": { - "build": "echo 'Client exports use source .ts files; use build:server for ./server subpath'", + "build": "tsc", "build:server": "tsc -p tsconfig.server.json", "clean": "rimraf dist" }, diff --git a/packages/tooling/contract-test-utils/src/client.ts b/packages/tooling/contract-test-utils/src/client.ts index d0a167b519..bb03bb1a4c 100644 --- a/packages/tooling/contract-test-utils/src/client.ts +++ b/packages/tooling/contract-test-utils/src/client.ts @@ -1,9 +1,9 @@ // Re-export universal exports -export * from './index'; +export * from './index.js'; // Client-side exports -export { default as ClientSideTestHook } from './client-side/TestHook'; +export { default as ClientSideTestHook } from './client-side/TestHook.js'; export type { HookData as ClientSideHookData, HookErrors as ClientSideHookErrors, -} from './client-side/TestHook'; +} from './client-side/TestHook.js'; diff --git a/packages/tooling/contract-test-utils/src/index.ts b/packages/tooling/contract-test-utils/src/index.ts index 06af41c87e..f227ed20f3 100644 --- a/packages/tooling/contract-test-utils/src/index.ts +++ b/packages/tooling/contract-test-utils/src/index.ts @@ -1,5 +1,5 @@ // Universal exports (types use minimal compat types, no SDK dependency) -export * from './types/CommandParams'; -export * from './types/ConfigParams'; -export { makeLogger } from './logging/makeLogger'; -export { ClientPool } from './server-side/ClientPool'; +export * from './types/CommandParams.js'; +export * from './types/ConfigParams.js'; +export { makeLogger } from './logging/makeLogger.js'; +export { ClientPool } from './server-side/ClientPool.js'; diff --git a/packages/tooling/contract-test-utils/src/server.ts b/packages/tooling/contract-test-utils/src/server.ts index 01ad5240df..a3cbeafced 100644 --- a/packages/tooling/contract-test-utils/src/server.ts +++ b/packages/tooling/contract-test-utils/src/server.ts @@ -1,14 +1,9 @@ +// Re-export universal exports +export * from './index.js'; + // Server-side exports export { default as ServerSideTestHook } from './server-side/TestHook.js'; export type { HookData as ServerSideHookData, HookErrors as ServerSideHookErrors, } from './server-side/TestHook.js'; - -// Re-export shared utilities -export { ClientPool } from './server-side/ClientPool.js'; -export { makeLogger } from './logging/makeLogger.js'; - -// Re-export all types (uses minimal compat types, no SDK dependency) -export * from './types/CommandParams.js'; -export * from './types/ConfigParams.js'; From 2d3b75a168703c732d0f12ea0d6a219c673dabf8 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 20:05:49 +0000 Subject: [PATCH 17/21] fix: use compat LDLogger type and exclude client files from server build - makeLogger.ts now imports LDLogger from compat.ts instead of @launchdarkly/js-client-sdk-common - tsconfig.server.json excludes src/client.ts and src/client-side/** (mirrors tsconfig.json's server exclusion) - shopify-oxygen CI uses build:server instead of build (server CI lacks client SDK deps) Co-Authored-By: Steven Zhang --- .github/workflows/shopify-oxygen.yml | 2 +- .../contract-test-utils/src/logging/makeLogger.ts | 2 +- .../tooling/contract-test-utils/src/types/compat.ts | 10 ++++++++++ .../tooling/contract-test-utils/tsconfig.server.json | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/shopify-oxygen.yml b/.github/workflows/shopify-oxygen.yml index a0b93399ea..e00ea7900e 100644 --- a/.github/workflows/shopify-oxygen.yml +++ b/.github/workflows/shopify-oxygen.yml @@ -29,7 +29,7 @@ jobs: - name: Install contract test service dependencies run: yarn workspace @launchdarkly/shopify-oxygen-contract-tests install --no-immutable - name: Build shared contract test utils - run: yarn workspace @launchdarkly/js-contract-test-utils build + run: yarn workspace @launchdarkly/js-contract-test-utils build:server - name: Build the test service run: yarn workspace @launchdarkly/shopify-oxygen-contract-tests build - name: Launch the test service in the background diff --git a/packages/tooling/contract-test-utils/src/logging/makeLogger.ts b/packages/tooling/contract-test-utils/src/logging/makeLogger.ts index ad01caf65d..65954ad83f 100644 --- a/packages/tooling/contract-test-utils/src/logging/makeLogger.ts +++ b/packages/tooling/contract-test-utils/src/logging/makeLogger.ts @@ -1,4 +1,4 @@ -import { LDLogger } from '@launchdarkly/js-client-sdk-common'; +import { LDLogger } from '../types/compat.js'; export function makeLogger(tag: string): LDLogger { return { diff --git a/packages/tooling/contract-test-utils/src/types/compat.ts b/packages/tooling/contract-test-utils/src/types/compat.ts index bdaa308fc6..c673f35786 100644 --- a/packages/tooling/contract-test-utils/src/types/compat.ts +++ b/packages/tooling/contract-test-utils/src/types/compat.ts @@ -20,3 +20,13 @@ export interface LDEvaluationReason { kind: string; [key: string]: unknown; } + +/** + * A minimal LDLogger type compatible with both client and server SDKs. + */ +export interface LDLogger { + error(...args: any[]): void; + warn(...args: any[]): void; + info(...args: any[]): void; + debug(...args: any[]): void; +} diff --git a/packages/tooling/contract-test-utils/tsconfig.server.json b/packages/tooling/contract-test-utils/tsconfig.server.json index 7642d6e970..2039e745e5 100644 --- a/packages/tooling/contract-test-utils/tsconfig.server.json +++ b/packages/tooling/contract-test-utils/tsconfig.server.json @@ -1,5 +1,5 @@ { "extends": "./tsconfig.json", "include": ["src/**/*"], - "exclude": ["dist", "node_modules"] + "exclude": ["dist", "node_modules", "src/client.ts", "src/client-side/**"] } From 473dc3555d0ba0175f63285de9f51c1cc8482d3b Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 20:33:18 +0000 Subject: [PATCH 18/21] fix: split builds into universal/client/server for different CI environments - build (tsconfig.json): universal only - excludes both TestHook files (no SDK deps needed) - build:client (tsconfig.client.json): client + universal - excludes server files - build:server (tsconfig.server.json): server + universal - excludes client files - shopify-oxygen CI uses build (universal), browser/electron/react-native use build:client, server-node uses build:server Co-Authored-By: Steven Zhang --- .github/workflows/browser.yml | 2 +- .github/workflows/electron.yaml | 2 +- .github/workflows/react-native-contract-tests.yml | 2 +- .github/workflows/shopify-oxygen.yml | 2 +- packages/tooling/contract-test-utils/package.json | 1 + packages/tooling/contract-test-utils/tsconfig.client.json | 5 +++++ packages/tooling/contract-test-utils/tsconfig.json | 2 +- 7 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 packages/tooling/contract-test-utils/tsconfig.client.json diff --git a/.github/workflows/browser.yml b/.github/workflows/browser.yml index ed9686b656..e43eaf5464 100644 --- a/.github/workflows/browser.yml +++ b/.github/workflows/browser.yml @@ -51,7 +51,7 @@ jobs: run: yarn workspace browser-contract-test-service install-playwright-browsers - name: Build shared contract test utils - run: yarn workspace @launchdarkly/js-contract-test-utils build + run: yarn workspace @launchdarkly/js-contract-test-utils build:client - name: Build contract test adapter run: yarn workspace browser-contract-test-adapter run build diff --git a/.github/workflows/electron.yaml b/.github/workflows/electron.yaml index 7a966c97d4..264b0a24cc 100644 --- a/.github/workflows/electron.yaml +++ b/.github/workflows/electron.yaml @@ -40,7 +40,7 @@ jobs: ELECTRON_DISABLE_SANDBOX: '1' run: | yarn workspaces focus @internal/electron-contract-tests-entity - yarn workspace @launchdarkly/js-contract-test-utils build + yarn workspace @launchdarkly/js-contract-test-utils build:client yarn workspace @internal/electron-contract-tests-entity build sudo apt-get install -y xvfb Xvfb :99 -screen 0 1024x768x24 > /tmp/xvfb.log 2>&1 & diff --git a/.github/workflows/react-native-contract-tests.yml b/.github/workflows/react-native-contract-tests.yml index b40f94423e..fb6265b82c 100644 --- a/.github/workflows/react-native-contract-tests.yml +++ b/.github/workflows/react-native-contract-tests.yml @@ -34,7 +34,7 @@ jobs: run: yarn workspaces foreach -pR --topological-dev --from '@launchdarkly/react-native-client-sdk' run build - name: Build shared contract test utils - run: yarn workspace @launchdarkly/js-contract-test-utils build + run: yarn workspace @launchdarkly/js-contract-test-utils build:client - name: Build contract test adapter run: yarn workspace react-native-contract-test-adapter run build diff --git a/.github/workflows/shopify-oxygen.yml b/.github/workflows/shopify-oxygen.yml index e00ea7900e..a0b93399ea 100644 --- a/.github/workflows/shopify-oxygen.yml +++ b/.github/workflows/shopify-oxygen.yml @@ -29,7 +29,7 @@ jobs: - name: Install contract test service dependencies run: yarn workspace @launchdarkly/shopify-oxygen-contract-tests install --no-immutable - name: Build shared contract test utils - run: yarn workspace @launchdarkly/js-contract-test-utils build:server + run: yarn workspace @launchdarkly/js-contract-test-utils build - name: Build the test service run: yarn workspace @launchdarkly/shopify-oxygen-contract-tests build - name: Launch the test service in the background diff --git a/packages/tooling/contract-test-utils/package.json b/packages/tooling/contract-test-utils/package.json index 8262e25337..6c54df4a94 100644 --- a/packages/tooling/contract-test-utils/package.json +++ b/packages/tooling/contract-test-utils/package.json @@ -34,6 +34,7 @@ }, "scripts": { "build": "tsc", + "build:client": "tsc -p tsconfig.client.json", "build:server": "tsc -p tsconfig.server.json", "clean": "rimraf dist" }, diff --git a/packages/tooling/contract-test-utils/tsconfig.client.json b/packages/tooling/contract-test-utils/tsconfig.client.json new file mode 100644 index 0000000000..57aed148f3 --- /dev/null +++ b/packages/tooling/contract-test-utils/tsconfig.client.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*"], + "exclude": ["dist", "node_modules", "src/server.ts", "src/server-side/TestHook.ts"] +} diff --git a/packages/tooling/contract-test-utils/tsconfig.json b/packages/tooling/contract-test-utils/tsconfig.json index 42b587636e..4c06c12a8f 100644 --- a/packages/tooling/contract-test-utils/tsconfig.json +++ b/packages/tooling/contract-test-utils/tsconfig.json @@ -21,5 +21,5 @@ } }, "include": ["src/**/*"], - "exclude": ["dist", "node_modules", "src/server.ts", "src/server-side/TestHook.ts"] + "exclude": ["dist", "node_modules", "src/client.ts", "src/client-side/TestHook.ts", "src/server.ts", "src/server-side/TestHook.ts"] } From 49a0e277533b81587e50c782aa9d2db32ec7e9f1 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 20:42:04 +0000 Subject: [PATCH 19/21] fix: add build:client step to React CI workflow for contract-test-utils The React contract tests import from @launchdarkly/js-contract-test-utils/client which requires dist/client.js. The topological build only runs the default 'build' script (universal only), so an explicit build:client step is needed. Co-Authored-By: Steven Zhang --- .github/workflows/react.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/react.yaml b/.github/workflows/react.yaml index 43ccb518c7..457ba23c1a 100644 --- a/.github/workflows/react.yaml +++ b/.github/workflows/react.yaml @@ -39,6 +39,8 @@ jobs: yarn workspaces foreach -pR --topological-dev --from "@launchdarkly/react-sdk-contract-tests" install yarn workspaces foreach -pR --topological-dev --from 'browser-contract-test-adapter' run build yarn workspaces foreach -pR --topological-dev --from "@launchdarkly/react-sdk-contract-tests" run build + - name: Build shared contract test utils (client) + run: yarn workspace @launchdarkly/js-contract-test-utils build:client - name: Install Playwright browsers run: yarn workspace @launchdarkly/react-sdk-contract-tests install-playwright-browsers - name: Run test adapter From 2540561ca9ed317d50ed223199e36ddc2776835c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 11 Mar 2026 20:50:07 +0000 Subject: [PATCH 20/21] fix: move build:client before topological build in React CI The React contract tests' Next.js/Turbopack build happens during the topological 'yarn workspaces foreach ... run build' step. The build:client step must run before that so dist/client.js exists when Turbopack resolves the @launchdarkly/js-contract-test-utils/client import. Co-Authored-By: Steven Zhang --- .github/workflows/react.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/react.yaml b/.github/workflows/react.yaml index 457ba23c1a..2d1343dc25 100644 --- a/.github/workflows/react.yaml +++ b/.github/workflows/react.yaml @@ -37,10 +37,9 @@ jobs: run: | yarn workspaces foreach -pR --topological-dev --from 'browser-contract-test-adapter' install yarn workspaces foreach -pR --topological-dev --from "@launchdarkly/react-sdk-contract-tests" install + yarn workspace @launchdarkly/js-contract-test-utils build:client yarn workspaces foreach -pR --topological-dev --from 'browser-contract-test-adapter' run build yarn workspaces foreach -pR --topological-dev --from "@launchdarkly/react-sdk-contract-tests" run build - - name: Build shared contract test utils (client) - run: yarn workspace @launchdarkly/js-contract-test-utils build:client - name: Install Playwright browsers run: yarn workspace @launchdarkly/react-sdk-contract-tests install-playwright-browsers - name: Run test adapter From 850f7d53c2ebe0b051c0a19461af254521a60799 Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Thu, 12 Mar 2026 18:55:46 -0500 Subject: [PATCH 21/21] chore: consolidating shared implementation --- .github/workflows/react.yaml | 4 +- .github/workflows/shopify-oxygen.yml | 2 +- .../contract-tests/src/sdkClientEntity.ts | 12 ++-- .../tooling/contract-test-utils/package.json | 14 ++-- .../src/client-side/TestHook.ts | 63 ++++-------------- .../tooling/contract-test-utils/src/client.ts | 4 -- .../src/server-side/TestHook.ts | 65 +++++-------------- .../tooling/contract-test-utils/src/server.ts | 5 +- .../src/shared/BaseTestHook.ts | 53 +++++++++++++++ .../src/types/CommandParams.ts | 19 +++++- .../src/types/ConfigParams.ts | 4 +- .../tooling/contract-test-utils/tsconfig.json | 6 +- .../contract-test-utils/tsconfig.server.json | 8 +++ 13 files changed, 131 insertions(+), 128 deletions(-) create mode 100644 packages/tooling/contract-test-utils/src/shared/BaseTestHook.ts diff --git a/.github/workflows/react.yaml b/.github/workflows/react.yaml index 5dd7d9cc07..4766d60a7f 100644 --- a/.github/workflows/react.yaml +++ b/.github/workflows/react.yaml @@ -37,11 +37,9 @@ jobs: run: | yarn workspaces foreach -pR --topological-dev --from 'browser-contract-test-adapter' install yarn workspaces foreach -pR --topological-dev --from "@launchdarkly/react-sdk-contract-tests" install - yarn workspace @launchdarkly/js-contract-test-utils build:client yarn workspaces foreach -pR --topological-dev --from 'browser-contract-test-adapter' run build yarn workspaces foreach -pR --topological-dev --from "@launchdarkly/react-sdk-contract-tests" run build - - name: Install Playwright browsers - run: yarn workspace @launchdarkly/react-sdk-contract-tests install-playwright-browsers + yarn workspace @launchdarkly/react-sdk-contract-tests install-playwright-browsers - name: Run test adapter run: | yarn workspace @launchdarkly/react-sdk-contract-tests run start:adapter > /tmp/adapter.log 2>&1 & diff --git a/.github/workflows/shopify-oxygen.yml b/.github/workflows/shopify-oxygen.yml index 9c772af52c..26c224b809 100644 --- a/.github/workflows/shopify-oxygen.yml +++ b/.github/workflows/shopify-oxygen.yml @@ -29,7 +29,7 @@ jobs: - name: Install contract test service dependencies run: yarn workspace @launchdarkly/shopify-oxygen-contract-tests install --no-immutable - name: Build shared contract test utils - run: yarn workspace @launchdarkly/js-contract-test-utils build + run: yarn workspace @launchdarkly/js-contract-test-utils build:server - name: Build the test service run: yarn workspace @launchdarkly/shopify-oxygen-contract-tests build - name: Launch the test service in the background diff --git a/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts b/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts index 717039d823..2b99380531 100644 --- a/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts +++ b/packages/sdk/server-node/contract-tests/src/sdkClientEntity.ts @@ -3,7 +3,7 @@ import got from 'got'; import { CommandParams, CreateInstanceParams, - SDKConfigParams, + ServerSDKConfigParams, ServerSideTestHook as TestHook, } from '@launchdarkly/js-contract-test-utils/server'; import ld, { @@ -29,7 +29,7 @@ import { Log, sdkLogger } from './log.js'; const badCommandError = new Error('unsupported command'); export { badCommandError }; -export function makeSdkConfig(options: SDKConfigParams, tag: string): LDOptions { +export function makeSdkConfig(options: ServerSDKConfigParams, tag: string): LDOptions { const cf: LDOptions = { logger: sdkLogger(tag), diagnosticOptOut: true, @@ -195,7 +195,11 @@ function contextOrUser( context: Record | undefined, user: LDUser | undefined, ): LDContext | LDUser { - return (context as LDContext | undefined) || user!; + const result = (context as LDContext | undefined) ?? user; + if (!result) { + throw new Error('Neither context nor user provided'); + } + return result; } export interface SdkClientEntity { @@ -221,7 +225,7 @@ export async function newSdkClientEntity(options: CreateInstanceParams): Promise : 5000; const client: LDClient = ld.init( options.configuration.credential || 'unknown-sdk-key', - makeSdkConfig(options.configuration, options.tag), + makeSdkConfig(options.configuration as ServerSDKConfigParams, options.tag), ); try { await client.waitForInitialization({ timeout }); diff --git a/packages/tooling/contract-test-utils/package.json b/packages/tooling/contract-test-utils/package.json index 6c54df4a94..6f07b01152 100644 --- a/packages/tooling/contract-test-utils/package.json +++ b/packages/tooling/contract-test-utils/package.json @@ -33,20 +33,21 @@ } }, "scripts": { - "build": "tsc", + "build": "tsc && tsc -p tsconfig.client.json && tsc -p tsconfig.server.json", "build:client": "tsc -p tsconfig.client.json", "build:server": "tsc -p tsconfig.server.json", "clean": "rimraf dist" }, - "dependencies": { - "@launchdarkly/js-client-sdk-common": "workspace:^" - }, "peerDependencies": { - "@launchdarkly/node-server-sdk": "workspace:^", + "@launchdarkly/js-client-sdk-common": "workspace:^", + "@launchdarkly/js-server-sdk-common": "workspace:^", "got": "14.4.7" }, "peerDependenciesMeta": { - "@launchdarkly/node-server-sdk": { + "@launchdarkly/js-client-sdk-common": { + "optional": true + }, + "@launchdarkly/js-server-sdk-common": { "optional": true }, "got": { @@ -55,6 +56,7 @@ }, "devDependencies": { "@types/node": "^18.11.9", + "got": "14.4.7", "typescript": "^4.9.0" } } diff --git a/packages/tooling/contract-test-utils/src/client-side/TestHook.ts b/packages/tooling/contract-test-utils/src/client-side/TestHook.ts index 9a107f420f..b9072ca6b0 100644 --- a/packages/tooling/contract-test-utils/src/client-side/TestHook.ts +++ b/packages/tooling/contract-test-utils/src/client-side/TestHook.ts @@ -6,32 +6,10 @@ import { LDEvaluationDetail, TrackSeriesContext, } from '@launchdarkly/js-client-sdk-common'; +import { BaseTestHook } from '../shared/BaseTestHook.js'; -export interface HookData { - beforeEvaluation?: Record; - afterEvaluation?: Record; -} - -export interface HookErrors { - beforeEvaluation?: string; - afterEvaluation?: string; - afterTrack?: string; -} - -export default class TestHook implements Hook { - private _name: string; - private _endpoint: string; - private _data?: HookData; - private _errors?: HookErrors; - - constructor(name: string, endpoint: string, data?: HookData, errors?: HookErrors) { - this._name = name; - this._endpoint = endpoint; - this._data = data; - this._errors = errors; - } - - private async _safePost(body: unknown): Promise { +export default class TestHook extends BaseTestHook implements Hook { + protected async _safePost(body: unknown): Promise { try { await fetch(this._endpoint, { method: 'POST', @@ -43,25 +21,18 @@ export default class TestHook implements Hook { } } - getMetadata(): HookMetadata { - return { - name: this._name, - }; + override getMetadata(): HookMetadata { + return super.getMetadata(); } beforeEvaluation( hookContext: EvaluationSeriesContext, data: EvaluationSeriesData, ): EvaluationSeriesData { - if (this._errors?.beforeEvaluation) { - throw new Error(this._errors.beforeEvaluation); - } - this._safePost({ - evaluationSeriesContext: hookContext, - evaluationSeriesData: data, - stage: 'beforeEvaluation', - }); - return { ...data, ...(this._data?.beforeEvaluation || {}) }; + return this._beforeEvaluationImpl( + hookContext as unknown as Record, + data, + ) as EvaluationSeriesData; } afterEvaluation( @@ -69,17 +40,11 @@ export default class TestHook implements Hook { data: EvaluationSeriesData, detail: LDEvaluationDetail, ): EvaluationSeriesData { - if (this._errors?.afterEvaluation) { - throw new Error(this._errors.afterEvaluation); - } - this._safePost({ - evaluationSeriesContext: hookContext, - evaluationSeriesData: data, - stage: 'afterEvaluation', - evaluationDetail: detail, - }); - - return { ...data, ...(this._data?.afterEvaluation || {}) }; + return this._afterEvaluationImpl( + hookContext as unknown as Record, + data, + detail, + ) as EvaluationSeriesData; } afterTrack(hookContext: TrackSeriesContext): void { diff --git a/packages/tooling/contract-test-utils/src/client.ts b/packages/tooling/contract-test-utils/src/client.ts index bb03bb1a4c..80469be6b1 100644 --- a/packages/tooling/contract-test-utils/src/client.ts +++ b/packages/tooling/contract-test-utils/src/client.ts @@ -3,7 +3,3 @@ export * from './index.js'; // Client-side exports export { default as ClientSideTestHook } from './client-side/TestHook.js'; -export type { - HookData as ClientSideHookData, - HookErrors as ClientSideHookErrors, -} from './client-side/TestHook.js'; diff --git a/packages/tooling/contract-test-utils/src/server-side/TestHook.ts b/packages/tooling/contract-test-utils/src/server-side/TestHook.ts index abad14331d..37348117a3 100644 --- a/packages/tooling/contract-test-utils/src/server-side/TestHook.ts +++ b/packages/tooling/contract-test-utils/src/server-side/TestHook.ts @@ -1,31 +1,9 @@ import got from 'got'; +import { integrations, LDEvaluationDetail } from '@launchdarkly/js-server-sdk-common'; +import { BaseTestHook } from '../shared/BaseTestHook.js'; -import { integrations, LDEvaluationDetail } from '@launchdarkly/node-server-sdk'; - -export interface HookData { - beforeEvaluation?: Record; - afterEvaluation?: Record; -} - -export interface HookErrors { - beforeEvaluation?: string; - afterEvaluation?: string; -} - -export default class TestHook implements integrations.Hook { - private _name: string; - private _endpoint: string; - private _data?: HookData; - private _errors?: HookErrors; - - constructor(name: string, endpoint: string, data?: HookData, errors?: HookErrors) { - this._name = name; - this._endpoint = endpoint; - this._data = data; - this._errors = errors; - } - - private async _safePost(body: unknown): Promise { +export default class TestHook extends BaseTestHook implements integrations.Hook { + protected async _safePost(body: unknown): Promise { try { await got.post(this._endpoint, { json: body }); } catch { @@ -34,25 +12,18 @@ export default class TestHook implements integrations.Hook { } } - getMetadata(): integrations.HookMetadata { - return { - name: this._name, - }; + override getMetadata(): integrations.HookMetadata { + return super.getMetadata(); } beforeEvaluation( hookContext: integrations.EvaluationSeriesContext, data: integrations.EvaluationSeriesData, ): integrations.EvaluationSeriesData { - if (this._errors?.beforeEvaluation) { - throw new Error(this._errors.beforeEvaluation); - } - this._safePost({ - evaluationSeriesContext: hookContext, - evaluationSeriesData: data, - stage: 'beforeEvaluation', - }); - return { ...data, ...(this._data?.beforeEvaluation || {}) }; + return this._beforeEvaluationImpl( + hookContext as unknown as Record, + data, + ) as integrations.EvaluationSeriesData; } afterEvaluation( @@ -60,16 +31,10 @@ export default class TestHook implements integrations.Hook { data: integrations.EvaluationSeriesData, detail: LDEvaluationDetail, ): integrations.EvaluationSeriesData { - if (this._errors?.afterEvaluation) { - throw new Error(this._errors.afterEvaluation); - } - this._safePost({ - evaluationSeriesContext: hookContext, - evaluationSeriesData: data, - stage: 'afterEvaluation', - evaluationDetail: detail, - }); - - return { ...data, ...(this._data?.afterEvaluation || {}) }; + return this._afterEvaluationImpl( + hookContext as unknown as Record, + data, + detail, + ) as integrations.EvaluationSeriesData; } } diff --git a/packages/tooling/contract-test-utils/src/server.ts b/packages/tooling/contract-test-utils/src/server.ts index a3cbeafced..4c18533942 100644 --- a/packages/tooling/contract-test-utils/src/server.ts +++ b/packages/tooling/contract-test-utils/src/server.ts @@ -3,7 +3,4 @@ export * from './index.js'; // Server-side exports export { default as ServerSideTestHook } from './server-side/TestHook.js'; -export type { - HookData as ServerSideHookData, - HookErrors as ServerSideHookErrors, -} from './server-side/TestHook.js'; +export type { ServerSDKConfigParams } from './types/ConfigParams.js'; diff --git a/packages/tooling/contract-test-utils/src/shared/BaseTestHook.ts b/packages/tooling/contract-test-utils/src/shared/BaseTestHook.ts new file mode 100644 index 0000000000..8df84059eb --- /dev/null +++ b/packages/tooling/contract-test-utils/src/shared/BaseTestHook.ts @@ -0,0 +1,53 @@ +import { HookData, HookErrors } from '../types/CommandParams.js'; + +export abstract class BaseTestHook { + protected readonly _name: string; + protected readonly _endpoint: string; + protected readonly _data?: HookData; + protected readonly _errors?: HookErrors; + + constructor(name: string, endpoint: string, data?: HookData, errors?: HookErrors) { + this._name = name; + this._endpoint = endpoint; + this._data = data; + this._errors = errors; + } + + protected abstract _safePost(body: unknown): Promise; + + getMetadata() { + return { name: this._name }; + } + + protected _beforeEvaluationImpl( + hookContext: Record, + data: Record, + ): Record { + if (this._errors?.beforeEvaluation) { + throw new Error(this._errors.beforeEvaluation); + } + this._safePost({ + evaluationSeriesContext: hookContext, + evaluationSeriesData: data, + stage: 'beforeEvaluation', + }); + return { ...data, ...(this._data?.beforeEvaluation ?? {}) }; + } + + protected _afterEvaluationImpl( + hookContext: Record, + data: Record, + detail: unknown, + ): Record { + if (this._errors?.afterEvaluation) { + throw new Error(this._errors.afterEvaluation); + } + this._safePost({ + evaluationSeriesContext: hookContext, + evaluationSeriesData: data, + stage: 'afterEvaluation', + evaluationDetail: detail, + }); + return { ...data, ...(this._data?.afterEvaluation ?? {}) }; + } +} diff --git a/packages/tooling/contract-test-utils/src/types/CommandParams.ts b/packages/tooling/contract-test-utils/src/types/CommandParams.ts index c2ee468b3b..dccf9f3928 100644 --- a/packages/tooling/contract-test-utils/src/types/CommandParams.ts +++ b/packages/tooling/contract-test-utils/src/types/CommandParams.ts @@ -11,6 +11,12 @@ export const CommandType = { ContextConvert: 'contextConvert', ContextComparison: 'contextComparison', SecureModeHash: 'secureModeHash', + // Server-specific commands + GetBigSegmentStoreStatus: 'getBigSegmentStoreStatus', + MigrationVariation: 'migrationVariation', + MigrationOperation: 'migrationOperation', + RegisterFlagChangeListener: 'registerFlagChangeListener', + UnregisterListener: 'unregisterListener', } as const; // eslint-disable-next-line @typescript-eslint/no-redeclare export type CommandType = (typeof CommandType)[keyof typeof CommandType]; @@ -26,7 +32,7 @@ export const ValueType = { export type ValueType = (typeof ValueType)[keyof typeof ValueType]; export interface CommandParams { - command: CommandType | string; + command: CommandType; evaluate?: EvaluateFlagParams; evaluateAll?: EvaluateAllFlagsParams; customEvent?: CustomEventParams; @@ -167,6 +173,17 @@ export interface HookExecutionPayload { stage?: HookStage; } +export interface HookData { + beforeEvaluation?: Record; + afterEvaluation?: Record; +} + +export interface HookErrors { + beforeEvaluation?: string; + afterEvaluation?: string; + afterTrack?: string; // client-only; server ignores this field +} + // Server-specific command parameter types export interface MigrationVariationParams { diff --git a/packages/tooling/contract-test-utils/src/types/ConfigParams.ts b/packages/tooling/contract-test-utils/src/types/ConfigParams.ts index 33eae95723..971aa67bf0 100644 --- a/packages/tooling/contract-test-utils/src/types/ConfigParams.ts +++ b/packages/tooling/contract-test-utils/src/types/ConfigParams.ts @@ -20,7 +20,9 @@ export interface SDKConfigParams { hooks?: SDKConfigHooksParams; wrapper?: SDKConfigWrapper; proxy?: SDKConfigProxyParams; - // Server-specific config fields +} + +export interface ServerSDKConfigParams extends SDKConfigParams { bigSegments?: SDKConfigBigSegmentsParams; dataSystem?: SDKDataSystemParams; } diff --git a/packages/tooling/contract-test-utils/tsconfig.json b/packages/tooling/contract-test-utils/tsconfig.json index 4c06c12a8f..079ac2afc6 100644 --- a/packages/tooling/contract-test-utils/tsconfig.json +++ b/packages/tooling/contract-test-utils/tsconfig.json @@ -14,11 +14,7 @@ "declarationMap": true, "stripInternal": true, "esModuleInterop": true, - "skipLibCheck": true, - "baseUrl": ".", - "paths": { - "got": ["../../../node_modules/got/dist/source"] - } + "skipLibCheck": true }, "include": ["src/**/*"], "exclude": ["dist", "node_modules", "src/client.ts", "src/client-side/TestHook.ts", "src/server.ts", "src/server-side/TestHook.ts"] diff --git a/packages/tooling/contract-test-utils/tsconfig.server.json b/packages/tooling/contract-test-utils/tsconfig.server.json index 2039e745e5..fb3066bda8 100644 --- a/packages/tooling/contract-test-utils/tsconfig.server.json +++ b/packages/tooling/contract-test-utils/tsconfig.server.json @@ -1,5 +1,13 @@ { "extends": "./tsconfig.json", + "compilerOptions": { + // 'got' is a devDependency; this path alias is needed because TypeScript's + // 'node' moduleResolution does not follow exports maps for ESM-only packages. + "baseUrl": ".", + "paths": { + "got": ["../../../node_modules/got/dist/source"] + } + }, "include": ["src/**/*"], "exclude": ["dist", "node_modules", "src/client.ts", "src/client-side/**"] }