feat: add server-side TestHook and migrate server-node/shopify-oxygen to shared utils [SDK-2009]#1168
feat: add server-side TestHook and migrate server-node/shopify-oxygen to shared utils [SDK-2009]#1168
Conversation
… 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<LDClient> - Update Shopify Oxygen tsup.config.ts with noExternal for shared package Co-Authored-By: Steven Zhang <szhang@launchdarkly.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
@launchdarkly/js-sdk-common size report |
|
@launchdarkly/js-client-sdk size report |
|
@launchdarkly/browser size report |
|
@launchdarkly/js-client-sdk-common size report |
- 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 <szhang@launchdarkly.com>
…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 <szhang@launchdarkly.com>
- 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 <szhang@launchdarkly.com>
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 <szhang@launchdarkly.com>
…elated 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 <szhang@launchdarkly.com>
…ution 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 <szhang@launchdarkly.com>
…d ClientPool - Replace inline clientCounter + Record with shared ClientPool<SdkClientEntity> - 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 <szhang@launchdarkly.com>
Co-Authored-By: Steven Zhang <szhang@launchdarkly.com>
…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 <szhang@launchdarkly.com>
Co-Authored-By: Steven Zhang <szhang@launchdarkly.com>
…K 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 <szhang@launchdarkly.com>
…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 <szhang@launchdarkly.com>
packages/sdk/shopify-oxygen/contract-tests/src/utils/clientPool.ts
Outdated
Show resolved
Hide resolved
…st/) Co-Authored-By: Steven Zhang <szhang@launchdarkly.com>
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 <szhang@launchdarkly.com>
…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 <szhang@launchdarkly.com>
- 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 <szhang@launchdarkly.com>
…onments - 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 <szhang@launchdarkly.com>
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 <szhang@launchdarkly.com>
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 <szhang@launchdarkly.com>
Requirements
Related issues
Describe the solution you've provided
Moves the server-node
TestHook(which usesgotfor HTTP callbacks) into the shared@launchdarkly/js-contract-test-utilspackage and migrates both server-node and Shopify Oxygen contract tests to use shared infrastructure. Also deduplicates shared types (command params, config params) so both client and server contract tests consume them from the shared package.Changes to
packages/tooling/contract-test-utils/:src/server-side/TestHook.ts— moved from server-node contract tests (exact rename, no content changes)src/server.ts— barrel re-exporting from./index.jsplusServerSideTestHook(mirrorsclient.tspattern)src/client.ts/src/index.ts— barrel exports using.jsextensions (required for compiled ESM output)src/types/compat.ts— minimalLDContext,LDEvaluationReason, andLDLoggertype definitions compatible with both client and server SDKs, allowing shared code to avoid importing from either SDK directlysrc/types/CommandParams.ts— imports fromcompat.ts; includes both client and server command fields (e.g.migrationVariation,registerFlagChangeListener)src/types/ConfigParams.ts— same pattern; includes bothclientSideand server-specific fields (bigSegments,dataSystem)src/logging/makeLogger.ts— now importsLDLoggerfromcompat.tsinstead of@launchdarkly/js-client-sdk-commonpackage.json:.,./client,./server) point to compileddist/outputbuild(universal only),build:client(client + universal),build:server(server + universal)gotand@launchdarkly/node-server-sdkadded as optional peer dependenciesThree-tier TypeScript build configuration:
tsconfig.json(base) — excludes both TestHook files and barrel files (client.ts,server.ts). Compiles only universal code with zero SDK dependencies. Used bybuildscript.tsconfig.client.json— extends base, re-includes client files, excludes server files. Requires@launchdarkly/js-client-sdk-common. Used bybuild:clientscript.tsconfig.server.json— extends base, re-includes server files, excludes client files. Requires@launchdarkly/node-server-sdk+got. Used bybuild:serverscript.Migrations:
TestHook.ts; importsServerSideTestHookfrom shared packageclientCounter+Record<string, SdkClientEntity>inindex.tswith sharedClientPool<SdkClientEntity>CommandParams,CreateInstanceParams,SDKConfigParamsfrom shared package (~130 lines of duplicated local type definitions removed)unknownorstringbut SDK expects specific types (e.g.,pe.defaultValue as boolean,migrationVariation.context as LDContext,migrationVariation.defaultStage as LDMigrationStage)Record<string, LDClient>+ counter with sharedClientPool<LDClient>noExternaltotsup.config.tsto bundle shared package inline"client-1"to"1"(auto-generated byClientPool.add())CI Workflow Updates:
.github/workflows/browser.yml— addedbuild:clientstep before contract test adapter build.github/workflows/electron.yaml— addedbuild:clientstep before contract tests entity build.github/workflows/react-native-contract-tests.yml— addedbuild:clientstep before contract test adapter build.github/workflows/react.yaml— addedbuild:clientstep before topological build (must run before Next.js/Turbopack resolves imports).github/workflows/server-node.yml— addedbuild:serverstep before contract test build.github/workflows/shopify-oxygen.yml— addedbuildstep before contract test buildBuild command matrix by CI environment:
build): shopify-oxygen — has no SDK dependenciesbuild:client): browser, electron, react-native, react — have@launchdarkly/js-client-sdk-commonbut notgot/node-server-sdkbuild:server): server-node — has@launchdarkly/node-server-sdk+gotbut may lack client SDKUpdates since last revision:
Implemented three-tier TypeScript build configuration to handle different SDK dependency profiles across CI environments:
Split
tsconfig.jsoninto three configs: Base config now excludes both client and server TestHook files, making it universal-only (zero SDK dependencies). Newtsconfig.client.jsonandtsconfig.server.jsonextend base and selectively re-include their respective files.Added
build:clientscript: Browser/Electron/React-Native/React CI workflows now explicitly runbuild:clientto compiledist/client.js, which was missing when they ran the defaultbuildscript (universal only).Shopify Oxygen uses universal build: Changed from
build:servertobuildsince Shopify Oxygen only imports from the base package (no subpath), not/server.Fixed React workflow build ordering: Moved
build:clientto run before the topologicalyarn workspaces foreach ... run buildbecause the React contract tests' Next.js/Turbopack build happens during that step and needsdist/client.jsto already exist when resolving@launchdarkly/js-contract-test-utils/clientimports.This fixes CI failures where different environments tried to compile code that imported SDKs not installed in their focused dependency trees (e.g., React CI trying to compile server-side TestHook which imports
got).Compat types pattern: Types use minimal compatible definitions (
LDContext = Record<string, any>,LDLogger = { error(...), warn(...), ... }) to avoid importing from SDK packages directly. This allows one unified set of type files to work for both client and server, but requires consumers to add explicit casts when passing values to SDK methods.Type safety tradeoff: The casts in
sdkClientEntity.tscould mask real type errors if the contract test harness sends incorrect types. However, this follows the pattern from PR feat: create shared contract test utilities package (SDK-1866) #1151 which was already merged and validated. Notable casts:pe.defaultValue as boolean | number | string(3 locations)migrationVariation.context as LDContext(1 location)migrationOperation.context as LDContext(2 locations)params.identifyEvent!.context as LDContext(1 location)migrationVariation.defaultStage as LDMigrationStage(3 locations)Three-tsconfig approach: Each CI environment must use its appropriate build script. Using the wrong script causes "Cannot find module" errors. Future files must be added to the appropriate tsconfig exclusion lists.
CI environment requirements:
build:client(client deps available, server deps missing)build:server(server deps available, client deps may be missing)build(universal only, minimal deps)ClientPool API change:
ClientPool.add(client)now auto-generates and returns the client ID instead of requiring an explicit ID. Shopify Oxygen client IDs changed from"client-1"to"1". CI contract tests pass, confirming the test harness handles this correctly.gotpath mapping:tsconfig.jsonuses a hardcoded relative path ("../../../node_modules/got/dist/source") which is fragile and depends on Yarn's hoisting layout. This is a workaround because@launchdarkly/js-client-sdk-commondoesn't work withnode16module resolution whilegotv14 requires it.React workflow build ordering is critical: The
build:clientstep must run before the topologicalyarn workspaces foreach ... run buildbecause Next.js/Turbopack resolves the@launchdarkly/js-contract-test-utils/clientimport during the contract tests build. Ifdist/client.jsdoesn't exist yet, Turbopack fails with "Module not found".Describe alternatives you've considered
@launchdarkly/js-client-sdk-common, causing peer dependency warnings and potential issues. Three tiers (universal/client/server) cleanly separates concerns.Additional context
Link to Devin Session: https://app.devin.ai/sessions/f0a2bd3c755e49c3b639bd3ee770effe
Requested by: @joker23
No test coverage added in this PR — the existing contract test suites serve as integration tests for the shared utilities.
CI Status: ✅ All 41/41 checks passing