From 842b76465dd2e6e4701b9a01fc56b487375ef2df Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Fri, 14 Nov 2025 11:01:01 -0600 Subject: [PATCH 1/6] chore: adding shopify oxygen example --- package.json | 3 +- packages/sdk/shopify-oxygen/example/README.md | 30 +++++++ packages/sdk/shopify-oxygen/example/app.js | 86 +++++++++++++++++++ .../sdk/shopify-oxygen/example/package.json | 17 ++++ .../sdk/shopify-oxygen/example/src/index.ts | 27 ++++++ .../sdk/shopify-oxygen/example/tsconfig.json | 20 +++++ .../sdk/shopify-oxygen/example/tsup.config.ts | 16 ++++ 7 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 packages/sdk/shopify-oxygen/example/README.md create mode 100644 packages/sdk/shopify-oxygen/example/app.js create mode 100644 packages/sdk/shopify-oxygen/example/package.json create mode 100644 packages/sdk/shopify-oxygen/example/src/index.ts create mode 100644 packages/sdk/shopify-oxygen/example/tsconfig.json create mode 100644 packages/sdk/shopify-oxygen/example/tsup.config.ts diff --git a/package.json b/package.json index 12adfd2f6..87317618e 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,8 @@ "contract-tests", "packages/sdk/combined-browser", "packages/sdk/shopify-oxygen", - "packages/sdk/shopify-oxygen/contract-tests" + "packages/sdk/shopify-oxygen/contract-tests", + "packages/sdk/shopify-oxygen/example" ], "private": true, "scripts": { diff --git a/packages/sdk/shopify-oxygen/example/README.md b/packages/sdk/shopify-oxygen/example/README.md new file mode 100644 index 000000000..a81c77e44 --- /dev/null +++ b/packages/sdk/shopify-oxygen/example/README.md @@ -0,0 +1,30 @@ +# LaunchDarkly sample Shopify Oxygen application + +We've built a simple console application that demonstrates how this LaunchDarkly SDK works. + +Below, you'll find the build procedure. For more comprehensive instructions, you can visit your [Quickstart page](https://app.launchdarkly.com/quickstart#/). + + +This demo requires `Node >= 22.0.0` and `yarn@^3.4.1` + +## Build instructions + +1. Edit [`src/index.ts`](src/index.ts) and set the value of `sdkKey` to your LaunchDarkly SDK key. + ``` + const sdkKey = "1234567890abcdef"; + ``` + +2. If there is an existing boolean feature flag in your LaunchDarkly project that + you want to evaluate, set `flagKey` to the flag key: + ``` + const flagKey = "my-flag-key"; + ``` + > Otherwise, `sample-feature` will be used by default. + +3. On the command line, run `yarn start`, You should receive the message: + ``` + The {flagKey} feature flag evaluates to {flagValue}. + ``` +The application will run continuously and react to the flag changes in LaunchDarkly. \ No newline at end of file diff --git a/packages/sdk/shopify-oxygen/example/app.js b/packages/sdk/shopify-oxygen/example/app.js new file mode 100644 index 000000000..a0b45cd2b --- /dev/null +++ b/packages/sdk/shopify-oxygen/example/app.js @@ -0,0 +1,86 @@ +import {createMiniOxygen} from '@shopify/mini-oxygen'; + +/** + * This is script is a simple runner for our example app. This script will run + * the compiled example on a local worker implementation to emulate a Oxygen worker runtime. + * + * For the actual example implementation, see the src/index.ts file. + */ + +const printValueAndBanner = (flagKey, flagValue) => { + console.log(`*** The '${flagKey}' feature flag evaluates to ${flagValue}.`); + + if (flagValue) { + console.log( + ` ██ + ██ + ████████ + ███████ + ██ LAUNCHDARKLY █ + ███████ + ████████ + ██ + ██ + `, + ); + } +} + +const main = async () => { + // NOTE: you will see logging coming from mini-oxygen's default request hook. + // https://github.com/Shopify/hydrogen/blob/5a38948133766e358c5f357f52562f6fdcfe7969/packages/mini-oxygen/src/worker/index.ts#L225 + const miniOxygen = createMiniOxygen({ + debug: false, + workers: [ + { + name: 'main', + modules: true, + scriptPath: 'dist/index.js' + }, + ], + }); + + miniOxygen.ready.then(() => { + console.log('Oxygen worker is started...'); + console.log('Dispatching fetch every 5 seconds. Press "q" or Ctrl+C to quit...'); + + // Dispatch fetch every 5 seconds + const interval = setInterval(() => { + // NOTE: This is a bogus URL and will not be used in the actual fetch handler. + // please see the src/index.ts file for the actual fetch handler. + miniOxygen.dispatchFetch('https://localhost:8000') + .then(d => d.json()) + .then(({flagValue, flagKey}) => { + console.clear(); + printValueAndBanner(flagKey, flagValue); + console.log('Press "q" or Ctrl+C to quit...') + }).catch((err) => { + console.log('Error dispatching fetch:', err.message); + console.log('Press "q" or Ctrl+C to quit...') + }); + }, 1000); + + // Handle keypresses for cleanup + process.stdin.setRawMode(true); + process.stdin.resume(); + process.stdin.setEncoding('utf8'); + + process.stdin.on('data', async (key) => { + // Handle Ctrl+C + if (key === '\u0003') { + clearInterval(interval); + await miniOxygen.dispose(); + process.exit(); + } + + // Handle 'q' key + if (key === 'q' || key === 'Q') { + clearInterval(interval); + await miniOxygen.dispose(); + process.exit(); + } + }); + }); +} + +main().catch(console.error); diff --git a/packages/sdk/shopify-oxygen/example/package.json b/packages/sdk/shopify-oxygen/example/package.json new file mode 100644 index 000000000..6938f6b97 --- /dev/null +++ b/packages/sdk/shopify-oxygen/example/package.json @@ -0,0 +1,17 @@ +{ + "name": "shopify-oxygen-sdk-example", + "packageManager": "yarn@3.4.1", + "scripts": { + "build": "tsup", + "clean": "rm -rf dist", + "start": "yarn clean && yarn build && yarn node app.js" + }, + "type": "module", + "dependencies": { + "@launchdarkly/shopify-oxygen-sdk": "latest", + "@shopify/mini-oxygen": "^4.0.0" + }, + "devDependencies": { + "tsup": "^8.5.1" + } +} diff --git a/packages/sdk/shopify-oxygen/example/src/index.ts b/packages/sdk/shopify-oxygen/example/src/index.ts new file mode 100644 index 000000000..4e0467e21 --- /dev/null +++ b/packages/sdk/shopify-oxygen/example/src/index.ts @@ -0,0 +1,27 @@ +import { init } from '@launchdarkly/shopify-oxygen-sdk'; + +// Set sdkKey to your LaunchDarkly SDK key. +const sdkKey = 'sample-sdk-key'; + +// Set featureFlagKey to the feature flag key you want to evaluate. +const flagKey = 'sample-feature'; + +const context = { + kind: 'user', + key: 'example-user-key', + name: 'Sandy' +}; + +const sdkOptions = { + // See the README.md file for more information on the options. +}; + +export default { + async fetch() { + const ldClient = await init(sdkKey, sdkOptions); + await ldClient.waitForInitialization({ timeout: 10 }); + const flagValue = await ldClient.variation(flagKey, context, false); + + return new Response(JSON.stringify({ flagKey, flagValue }), { status: 200 }); + } +}; diff --git a/packages/sdk/shopify-oxygen/example/tsconfig.json b/packages/sdk/shopify-oxygen/example/tsconfig.json new file mode 100644 index 000000000..4e0b21e2a --- /dev/null +++ b/packages/sdk/shopify-oxygen/example/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "declaration": true, + "declarationMap": true, + "lib": ["es6"], + "module": "es2022", + "moduleResolution": "node", + "noImplicitOverride": true, + "outDir": "dist", + "resolveJsonModule": true, + "rootDir": ".", + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "stripInternal": true, + "target": "ES2022", + }, + "exclude": ["tsup.config.ts"] +} diff --git a/packages/sdk/shopify-oxygen/example/tsup.config.ts b/packages/sdk/shopify-oxygen/example/tsup.config.ts new file mode 100644 index 000000000..28a2fa346 --- /dev/null +++ b/packages/sdk/shopify-oxygen/example/tsup.config.ts @@ -0,0 +1,16 @@ +// It is a dev dependency and the linter doesn't understand. +// @ts-ignore - tsup is a dev dependency installed at runtime +// eslint-disable-next-line import/no-extraneous-dependencies +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: { + index: 'src/index.ts', + }, + minify: true, + format: ['esm'], + splitting: false, + clean: true, + noExternal: ['@launchdarkly/shopify-oxygen-sdk'], + dts: true, +}); From 52a06ebc43c512944b981393826e34513891838a Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Fri, 14 Nov 2025 11:33:56 -0600 Subject: [PATCH 2/6] style: lint fixes --- packages/sdk/shopify-oxygen/example/src/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sdk/shopify-oxygen/example/src/index.ts b/packages/sdk/shopify-oxygen/example/src/index.ts index 4e0467e21..0702ef290 100644 --- a/packages/sdk/shopify-oxygen/example/src/index.ts +++ b/packages/sdk/shopify-oxygen/example/src/index.ts @@ -9,7 +9,7 @@ const flagKey = 'sample-feature'; const context = { kind: 'user', key: 'example-user-key', - name: 'Sandy' + name: 'Sandy', }; const sdkOptions = { @@ -23,5 +23,5 @@ export default { const flagValue = await ldClient.variation(flagKey, context, false); return new Response(JSON.stringify({ flagKey, flagValue }), { status: 200 }); - } + }, }; From 571a063147e1023b82ab72174cb6cb62a8f9b2b7 Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Fri, 14 Nov 2025 11:59:29 -0600 Subject: [PATCH 3/6] chore: [sdk-1556] addressing some PR comments --- packages/sdk/shopify-oxygen/example/app.js | 2 +- packages/sdk/shopify-oxygen/example/src/index.ts | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/sdk/shopify-oxygen/example/app.js b/packages/sdk/shopify-oxygen/example/app.js index a0b45cd2b..7bdf4a979 100644 --- a/packages/sdk/shopify-oxygen/example/app.js +++ b/packages/sdk/shopify-oxygen/example/app.js @@ -42,7 +42,7 @@ const main = async () => { miniOxygen.ready.then(() => { console.log('Oxygen worker is started...'); - console.log('Dispatching fetch every 5 seconds. Press "q" or Ctrl+C to quit...'); + console.log('Press "q" or Ctrl+C to quit...'); // Dispatch fetch every 5 seconds const interval = setInterval(() => { diff --git a/packages/sdk/shopify-oxygen/example/src/index.ts b/packages/sdk/shopify-oxygen/example/src/index.ts index 0702ef290..c7e0d1f2d 100644 --- a/packages/sdk/shopify-oxygen/example/src/index.ts +++ b/packages/sdk/shopify-oxygen/example/src/index.ts @@ -22,6 +22,10 @@ export default { await ldClient.waitForInitialization({ timeout: 10 }); const flagValue = await ldClient.variation(flagKey, context, false); + // Flush events and close the client + ldClient.flush(); + ldClient.close(); + return new Response(JSON.stringify({ flagKey, flagValue }), { status: 200 }); }, }; From b45215af4e6614e0c3d8754fef9b21e3c517be45 Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Tue, 18 Nov 2025 13:49:11 -0600 Subject: [PATCH 4/6] chore: addressing pr comment --- packages/sdk/shopify-oxygen/example/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/shopify-oxygen/example/src/index.ts b/packages/sdk/shopify-oxygen/example/src/index.ts index c7e0d1f2d..c743627b6 100644 --- a/packages/sdk/shopify-oxygen/example/src/index.ts +++ b/packages/sdk/shopify-oxygen/example/src/index.ts @@ -23,7 +23,7 @@ export default { const flagValue = await ldClient.variation(flagKey, context, false); // Flush events and close the client - ldClient.flush(); + await ldClient.flush(); ldClient.close(); return new Response(JSON.stringify({ flagKey, flagValue }), { status: 200 }); From 594ef58def9c021b71e562230ae80ff07e55e391 Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Wed, 19 Nov 2025 09:10:22 -0600 Subject: [PATCH 5/6] style: [sdk-1556] convert example to ts --- .../shopify-oxygen/example/{app.js => app.ts} | 38 ++++++++++--------- .../sdk/shopify-oxygen/example/package.json | 6 ++- 2 files changed, 25 insertions(+), 19 deletions(-) rename packages/sdk/shopify-oxygen/example/{app.js => app.ts} (77%) diff --git a/packages/sdk/shopify-oxygen/example/app.js b/packages/sdk/shopify-oxygen/example/app.ts similarity index 77% rename from packages/sdk/shopify-oxygen/example/app.js rename to packages/sdk/shopify-oxygen/example/app.ts index 7bdf4a979..79f7bcb0c 100644 --- a/packages/sdk/shopify-oxygen/example/app.js +++ b/packages/sdk/shopify-oxygen/example/app.ts @@ -1,13 +1,15 @@ -import {createMiniOxygen} from '@shopify/mini-oxygen'; +import { createMiniOxygen } from '@shopify/mini-oxygen'; + +/* eslint-disable no-console */ /** * This is script is a simple runner for our example app. This script will run * the compiled example on a local worker implementation to emulate a Oxygen worker runtime. - * + * * For the actual example implementation, see the src/index.ts file. */ -const printValueAndBanner = (flagKey, flagValue) => { +const printValueAndBanner = (flagKey: string, flagValue: string) => { console.log(`*** The '${flagKey}' feature flag evaluates to ${flagValue}.`); if (flagValue) { @@ -24,7 +26,7 @@ const printValueAndBanner = (flagKey, flagValue) => { `, ); } -} +}; const main = async () => { // NOTE: you will see logging coming from mini-oxygen's default request hook. @@ -35,7 +37,7 @@ const main = async () => { { name: 'main', modules: true, - scriptPath: 'dist/index.js' + scriptPath: 'dist/index.js', }, ], }); @@ -43,36 +45,38 @@ const main = async () => { miniOxygen.ready.then(() => { console.log('Oxygen worker is started...'); console.log('Press "q" or Ctrl+C to quit...'); - + // Dispatch fetch every 5 seconds const interval = setInterval(() => { // NOTE: This is a bogus URL and will not be used in the actual fetch handler. // please see the src/index.ts file for the actual fetch handler. - miniOxygen.dispatchFetch('https://localhost:8000') - .then(d => d.json()) - .then(({flagValue, flagKey}) => { + miniOxygen + .dispatchFetch('https://localhost:8000' as any) + .then((d) => d.json() as Promise) + .then(({ flagValue, flagKey }) => { console.clear(); printValueAndBanner(flagKey, flagValue); - console.log('Press "q" or Ctrl+C to quit...') - }).catch((err) => { + console.log('Press "q" or Ctrl+C to quit...'); + }) + .catch((err) => { console.log('Error dispatching fetch:', err.message); - console.log('Press "q" or Ctrl+C to quit...') + console.log('Press "q" or Ctrl+C to quit...'); }); }, 1000); - + // Handle keypresses for cleanup process.stdin.setRawMode(true); process.stdin.resume(); process.stdin.setEncoding('utf8'); - - process.stdin.on('data', async (key) => { + + process.stdin.on('data', async (key: string) => { // Handle Ctrl+C if (key === '\u0003') { clearInterval(interval); await miniOxygen.dispose(); process.exit(); } - + // Handle 'q' key if (key === 'q' || key === 'Q') { clearInterval(interval); @@ -81,6 +85,6 @@ const main = async () => { } }); }); -} +}; main().catch(console.error); diff --git a/packages/sdk/shopify-oxygen/example/package.json b/packages/sdk/shopify-oxygen/example/package.json index 6938f6b97..e9fa468ac 100644 --- a/packages/sdk/shopify-oxygen/example/package.json +++ b/packages/sdk/shopify-oxygen/example/package.json @@ -4,12 +4,14 @@ "scripts": { "build": "tsup", "clean": "rm -rf dist", - "start": "yarn clean && yarn build && yarn node app.js" + "start": "yarn clean && yarn build && ts-node app.ts" }, "type": "module", "dependencies": { "@launchdarkly/shopify-oxygen-sdk": "latest", - "@shopify/mini-oxygen": "^4.0.0" + "@shopify/mini-oxygen": "^4.0.0", + "ts-node": "^10.9.2", + "typescript": "^5.9.3" }, "devDependencies": { "tsup": "^8.5.1" From 8cc1f7edecfa9a05215aa741e7f16f9d9b7b508a Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Fri, 21 Nov 2025 14:32:23 -0600 Subject: [PATCH 6/6] chore: fix PR comment --- packages/sdk/shopify-oxygen/example/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/shopify-oxygen/example/package.json b/packages/sdk/shopify-oxygen/example/package.json index e9fa468ac..1f539d0ac 100644 --- a/packages/sdk/shopify-oxygen/example/package.json +++ b/packages/sdk/shopify-oxygen/example/package.json @@ -8,7 +8,7 @@ }, "type": "module", "dependencies": { - "@launchdarkly/shopify-oxygen-sdk": "latest", + "@launchdarkly/shopify-oxygen-sdk": "*", "@shopify/mini-oxygen": "^4.0.0", "ts-node": "^10.9.2", "typescript": "^5.9.3"