Skip to content

feat: add server-side TestHook and migrate server-node/shopify-oxygen to shared utils [SDK-2009]#1168

Draft
joker23 wants to merge 13 commits intomainfrom
devin/1773165219-sdk-2009-server-testhook-migrations
Draft

feat: add server-side TestHook and migrate server-node/shopify-oxygen to shared utils [SDK-2009]#1168
joker23 wants to merge 13 commits intomainfrom
devin/1773165219-sdk-2009-server-testhook-migrations

Conversation

@joker23
Copy link
Contributor

@joker23 joker23 commented Mar 10, 2026

Requirements

  • I have added test coverage for new or changed functionality
  • I have followed the repository's pull request submission guidelines
  • I have validated my changes against all supported platform versions

Related issues

  • SDK-2009 — Add server-side TestHook and ClientPool to contract-test-utils
  • Subtask 2 of SDK-1866

Describe the solution you've provided

Moves the server-node TestHook (which uses got for HTTP callbacks) into the shared @launchdarkly/js-contract-test-utils package 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 ServerSideTestHook, ClientPool, and all shared types
  • tsconfig.server.json — server-only TypeScript config
  • src/types/compat.ts — minimal LDContext and LDEvaluationReason type definitions compatible with both client and server SDKs, allowing shared types to avoid importing from either SDK package directly
  • src/types/CommandParams.ts — now imports from compat.ts instead of @launchdarkly/js-client-sdk-common; includes both client and server command fields (e.g. migrationVariation, registerFlagChangeListener)
  • src/types/ConfigParams.ts — same pattern; includes both clientSide and server-specific fields (bigSegments, dataSystem)
  • src/index.ts / src/client.ts — simplified barrel exports; client.ts re-exports from index
  • package.json:
    • "./server" subpath export pointing to compiled dist/server.js
    • got and @launchdarkly/node-server-sdk added as optional peer dependencies
    • build script changed to no-op; build:server script added for explicit server compilation
  • tsconfig.json — added got path mapping for TypeScript resolution

Migrations:

  • server-node:
    • Deleted local TestHook.ts; imports ServerSideTestHook from shared package
    • Replaced inline clientCounter + Record<string, SdkClientEntity> in index.ts with shared ClientPool<SdkClientEntity>
    • Imported CommandParams, CreateInstanceParams, SDKConfigParams from shared package (~130 lines of duplicated local type definitions removed)
    • Added type casts where shared types use unknown or string but SDK expects specific types (e.g., pe.defaultValue as boolean, migrationVariation.defaultStage as LDMigrationStage)
  • Shopify Oxygen:
    • Replaced inline Record<string, LDClient> + counter with shared ClientPool<LDClient>
    • Added noExternal to tsup.config.ts to bundle shared package inline
    • Client IDs changed from "client-1" to "1" (auto-generated by ClientPool.add())

CI:

  • .github/workflows/server-node.yml — added build:server step before contract test build so dist/server.js exists at import time

⚠️ Items for reviewer attention:

  1. Compat types pattern: Types use minimal compatible definitions (LDContext = Record<string, any>, LDEvaluationReason = { kind: string; ... }) 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 (e.g., defaultValue as boolean).

  2. Type safety tradeoff: The casts in sdkClientEntity.ts (e.g., pe.defaultValue as boolean, migrationVariation.defaultStage as LDMigrationStage) could 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.

  3. 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.

  4. got path mapping: tsconfig.json uses 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-common doesn't work with node16 module resolution while got v14 requires it.

  5. build script is now a no-op: The ./ and ./client exports serve source .ts files and don't need compilation. Only the ./server subpath needs compiled output via build:server.

  6. Optional peer deps: Consumers of the ./server subpath must have got and @launchdarkly/node-server-sdk installed. The server-node and shopify-oxygen contract test packages already do.

Describe alternatives you've considered

  • Three-tier type architecture (universal/client/server split into separate directories) — initially implemented but reverted in favor of the simpler compat.ts pattern from PR feat: create shared contract test utilities package (SDK-1866) #1151, which uses one unified set of type files with minimal compatible type definitions
  • Keeping ./server as source files (like ./client) — rejected because server-side code has dependencies that may not resolve correctly in all consumer contexts
  • Adding got and node-server-sdk as regular dependencies — rejected because transitive types break React SDK compilation

Additional context

Link to Devin Session: https://app.devin.ai/sessions/f0a2bd3c755e49c3b639bd3ee770effe
Requested by: @joker23

All CI checks pass (40/40 checks passing).

No test coverage added in this PR — the existing contract test suites serve as integration tests for the shared utilities.

… 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-integration
Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@github-actions
Copy link
Contributor

@launchdarkly/js-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 25566 bytes
Compressed size limit: 26000
Uncompressed size: 125383 bytes

@github-actions
Copy link
Contributor

@launchdarkly/js-client-sdk size report
This is the brotli compressed size of the ESM build.
Compressed size: 24212 bytes
Compressed size limit: 25000
Uncompressed size: 83755 bytes

@github-actions
Copy link
Contributor

@launchdarkly/browser size report
This is the brotli compressed size of the ESM build.
Compressed size: 172130 bytes
Compressed size limit: 200000
Uncompressed size: 800872 bytes

@github-actions
Copy link
Contributor

@launchdarkly/js-client-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 21281 bytes
Compressed size limit: 24000
Uncompressed size: 110213 bytes

devin-ai-integration bot and others added 12 commits March 10, 2026 18:08
- 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant