@nosnibor89/svelte-client-sdk 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
package/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # Launch Darkly Svelte SDK
2
+
3
+ This is a Svelte library for Launch Darkly. It is a wrapper around the official Launch Darkly JavaScript SDK but with a Svelte-friendly API.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Getting Started](#getting-started)
8
+ - [Advanced Usage](#advanced-usage)
9
+ - [Changing user context](#changing-user-context)
10
+ - [Getting feature flag values](#getting-feature-flag-values)
11
+ - [Getting immediate flag value](#getting-immediate-flag-value)
12
+ - [Watching flag value changes](#watching-flag-value-changes)
13
+ - [Getting all flag values](#getting-all-flag-values)
14
+
15
+ ## Getting started
16
+
17
+ First, install the package:
18
+
19
+ ```bash
20
+ npm install @launchdarkly/svelte-client-sdk # or use yarn or pnpm
21
+ ```
22
+
23
+ Then, initialize the SDK with your client-side ID using the `LDProvider` component:
24
+
25
+ ```svelte
26
+ <script>
27
+ import { LDProvider } from '@launchdarkly/svelte-client-sdk';
28
+ import App from './App.svelte';
29
+ </script>
30
+
31
+ // Use context relevant to your application
32
+ const context = {
33
+ user: {
34
+ key: 'user-key',
35
+ },
36
+ };
37
+
38
+ <LDProvider clientID="your-client-side-id" {context}>
39
+ <App />
40
+ </LDProvider>
41
+ ```
42
+
43
+ Now you can use the `LDFlag` component to conditionally render content based on feature flags:
44
+
45
+ ```svelte
46
+ <script>
47
+ import { LDFlag } from '@launchdarkly/svelte-client-sdk';
48
+ </script>
49
+
50
+ <LDFlag flag={'my-feature-flag'}>
51
+ <div slot="on">
52
+ <p>this will render if the feature flag is on</p>
53
+ </div>
54
+ <div slot="off">
55
+ <p>this will render if the feature flag is off</p>
56
+ </div>
57
+ </LDFlag>
58
+ ```
59
+
60
+ ## Advanced usage
61
+
62
+ ### Changing user context
63
+
64
+ You can change the user context by using the `identify` function from the `LD` object:
65
+
66
+ ```svelte
67
+ <script>
68
+ import { LD } from '@launchdarkly/svelte-client-sdk';
69
+
70
+ function handleLogin() {
71
+ LD.identify({ key: 'new-user-key' });
72
+ }
73
+ </script>
74
+
75
+ <button on:click={handleLogin}>Login</button>
76
+ ```
77
+
78
+ ### Getting feature flag values
79
+
80
+ #### Getting immediate flag value
81
+
82
+ If you need to get the value of a flag at time of evaluation you can use the `useFlag` function:
83
+
84
+ ```svelte
85
+ <script>
86
+ import { LD } from '@launchdarkly/svelte-client-sdk';
87
+
88
+ function handleClick() {
89
+ const isFeatureFlagOn = LD.useFlag('my-feature-flag', false);
90
+ console.log(isFeatureFlagOn);
91
+ }
92
+ </script>
93
+
94
+ <button on:click={handleClick}>Check flag value</button>
95
+ ```
96
+
97
+ **Note:** Please note that `useFlag` function will return the current value of the flag at the time of evaluation, which means you won't get notified if the flag value changes. This is useful for cases where you need to get the value of a flag at a specific time like a function call. If you need to get notified when the flag value changes, you should use the `LDFlag` component, the `watch` function or the `flags` object depending on your use case.
98
+
99
+ #### Watching flag value changes
100
+
101
+ If you need to get notified when a flag value changes you can use the `watch` function. The `watch` function is an instance of [Svelte Store](https://svelte.dev/docs/svelte-store), which means you can use it with the `$` store subscriber syntax or the `subscribe` method. Here is an example of how to use the `watch` function:
102
+
103
+ ```svelte
104
+ <script>
105
+ import { LD } from '@launchdarkly/svelte-client-sdk';
106
+
107
+ $: flagValue = LD.watch('my-feature-flag');
108
+ </script>
109
+
110
+ <p>{$flagValue}</p>
111
+ ```
112
+
113
+ #### Getting all flag values
114
+
115
+ If you need to get all flag values you can use the `flags` object. The `flags` object is an instance of [Svelte Store](https://svelte.dev/docs/svelte-store), which means you can use it with the `$` store subscriber syntax or the `subscribe` method. Here is an example of how to use the `flags` object:
116
+
117
+ ```svelte
118
+ <script>
119
+ import { LD } from '@launchdarkly/svelte-client-sdk';
120
+
121
+ $: allFlags = LD.flags;
122
+ </script>
123
+
124
+ {#each Object.keys($allFlags) as flagName}
125
+ <p>{flagName}: {$allFlags[flagName]}</p>
126
+ {/each}
127
+ ```
128
+
129
+ ## Credits
130
+
131
+ - Original code by [Robinson Marquez](https://github.com/nosnibor89)
@@ -0,0 +1,21 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import { LD, type LDFlagValue } from './client/SvelteLDClient.js';
4
+
5
+ interface LDFlagProps {
6
+ on: Snippet;
7
+ off: Snippet;
8
+ flag: string;
9
+ matches?: LDFlagValue;
10
+ }
11
+
12
+ let { on, off, flag, matches = true }: LDFlagProps = $props();
13
+
14
+ const flagValue = $derived(LD.watch(flag));
15
+ </script>
16
+
17
+ {#if $flagValue === matches}
18
+ {@render on()}
19
+ {:else}
20
+ {@render off()}
21
+ {/if}
@@ -0,0 +1,11 @@
1
+ import type { Snippet } from 'svelte';
2
+ import { type LDFlagValue } from './client/SvelteLDClient.js';
3
+ interface LDFlagProps {
4
+ on: Snippet;
5
+ off: Snippet;
6
+ flag: string;
7
+ matches?: LDFlagValue;
8
+ }
9
+ declare const LdFlag: import("svelte").Component<LDFlagProps, {}, "">;
10
+ type LdFlag = ReturnType<typeof LdFlag>;
11
+ export default LdFlag;
@@ -0,0 +1,19 @@
1
+ import { type Readable, type Writable } from 'svelte/store';
2
+ import type { LDFlagSet, LDOptions } from '@launchdarkly/js-client-sdk';
3
+ import { type LDContext, type LDFlagValue } from '@launchdarkly/js-client-sdk/compat';
4
+ export type { LDContext, LDFlagValue, LDOptions };
5
+ /** Client ID for LaunchDarkly */
6
+ export type LDClientID = string;
7
+ /** Flags for LaunchDarkly */
8
+ export type LDFlags = LDFlagSet;
9
+ /** The LaunchDarkly instance */
10
+ export declare const LD: {
11
+ identify: (context: LDContext) => Promise<any>;
12
+ flags: Readable<LDFlagSet>;
13
+ initialize: (clientId: LDClientID, context: LDContext, options?: LDOptions) => {
14
+ initializing: Writable<boolean>;
15
+ };
16
+ initializing: Readable<boolean>;
17
+ watch: (flagKey: string) => Readable<LDFlagValue>;
18
+ useFlag: <TFlag extends LDFlagValue>(flagKey: string, defaultValue: TFlag) => TFlag;
19
+ };
@@ -0,0 +1,107 @@
1
+ import { derived, readonly, writable } from 'svelte/store';
2
+ import { initialize, } from '@launchdarkly/js-client-sdk/compat';
3
+ /**
4
+ * Checks if the LaunchDarkly client is initialized.
5
+ * @param {LDClient | undefined} client - The LaunchDarkly client.
6
+ * @throws {Error} If the client is not initialized.
7
+ */
8
+ function isClientInitialized(client) {
9
+ if (!client) {
10
+ throw new Error('LaunchDarkly client not initialized');
11
+ }
12
+ }
13
+ /**
14
+ * Creates a proxy for the given flags object that intercepts access to flag values.
15
+ * When a flag value is accessed, it checks if the flag key exists in the target object.
16
+ * If the flag key exists, it returns the variation of the flag from the client.
17
+ * Otherwise, it returns the current value of the flag.
18
+ *
19
+ * @param client - The LaunchDarkly client instance used to get flag variations.
20
+ * @param flags - The initial flags object to be proxied.
21
+ * @returns A proxy object that intercepts access to flag values and returns the appropriate variation.
22
+ */
23
+ function toFlagsProxy(client, flags) {
24
+ return new Proxy(flags, {
25
+ get(target, prop, receiver) {
26
+ const currentValue = Reflect.get(target, prop, receiver);
27
+ // only process flag keys and ignore symbols and native Object functions
28
+ if (typeof prop === 'symbol') {
29
+ return currentValue;
30
+ }
31
+ // check if flag key exists
32
+ const validFlagKey = Object.hasOwn(target, prop);
33
+ if (!validFlagKey) {
34
+ return currentValue;
35
+ }
36
+ return client.variation(prop, currentValue);
37
+ },
38
+ });
39
+ }
40
+ /**
41
+ * Creates a LaunchDarkly instance.
42
+ * @returns {Object} The LaunchDarkly instance object.
43
+ */
44
+ function createLD() {
45
+ let coreLdClient;
46
+ const loading = writable(true);
47
+ const flagsWritable = writable({});
48
+ /**
49
+ * Initializes the LaunchDarkly client.
50
+ * @param {LDClientID} clientId - The client ID.
51
+ * @param {LDContext} context - The user context.
52
+ * @param {LDOptions} options - The client options.
53
+ * @returns {Object} An object with the initialization status store.
54
+ */
55
+ function LDInitialize(clientId, context, options) {
56
+ coreLdClient = initialize(clientId, context, options);
57
+ coreLdClient.on('ready', () => {
58
+ loading.set(false);
59
+ const rawFlags = coreLdClient.allFlags();
60
+ const allFlags = toFlagsProxy(coreLdClient, rawFlags);
61
+ flagsWritable.set(allFlags);
62
+ });
63
+ coreLdClient.on('change', () => {
64
+ const rawFlags = coreLdClient.allFlags();
65
+ const allFlags = toFlagsProxy(coreLdClient, rawFlags);
66
+ flagsWritable.set(allFlags);
67
+ });
68
+ return {
69
+ initializing: loading,
70
+ };
71
+ }
72
+ /**
73
+ * Identifies the user context.
74
+ * @param {LDContext} context - The user context.
75
+ * @returns {Promise} A promise that resolves when the user is identified.
76
+ */
77
+ async function identify(context) {
78
+ isClientInitialized(coreLdClient);
79
+ return coreLdClient.identify(context);
80
+ }
81
+ /**
82
+ * Watches a flag for changes.
83
+ * @param {string} flagKey - The key of the flag to watch.
84
+ * @returns {Readable<LDFlagsValue>} A readable store of the flag value.
85
+ */
86
+ const watch = (flagKey) => derived(flagsWritable, ($flags) => $flags[flagKey]);
87
+ /**
88
+ * Gets the current value of a flag.
89
+ * @param {string} flagKey - The key of the flag to get.
90
+ * @param {TFlag} defaultValue - The default value of the flag.
91
+ * @returns {TFlag} The current value of the flag.
92
+ */
93
+ function useFlag(flagKey, defaultValue) {
94
+ isClientInitialized(coreLdClient);
95
+ return coreLdClient.variation(flagKey, defaultValue);
96
+ }
97
+ return {
98
+ identify,
99
+ flags: readonly(flagsWritable),
100
+ initialize: LDInitialize,
101
+ initializing: readonly(loading),
102
+ watch,
103
+ useFlag,
104
+ };
105
+ }
106
+ /** The LaunchDarkly instance */
107
+ export const LD = createLD();
@@ -0,0 +1 @@
1
+ export * from './SvelteLDClient';
@@ -0,0 +1 @@
1
+ export * from './SvelteLDClient';
@@ -0,0 +1,4 @@
1
+ export * as LDClient from './client/SvelteLDClient.js';
2
+ export type { LDOptions } from './client/SvelteLDClient.js';
3
+ export { default as LDProvider } from './provider/LDProvider.svelte';
4
+ export { default as LDFlag } from './LDFlag.svelte';
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ // Reexport your entry components here
2
+ export * as LDClient from './client/SvelteLDClient.js';
3
+ // Export Components
4
+ export { default as LDProvider } from './provider/LDProvider.svelte';
5
+ export { default as LDFlag } from './LDFlag.svelte';
@@ -0,0 +1,26 @@
1
+ <script lang="ts">
2
+ import { onMount, type Snippet } from 'svelte';
3
+ import { LD } from '../client/SvelteLDClient.js';
4
+ import type { LDClientID, LDContext, LDOptions } from '../client/SvelteLDClient.js';
5
+
6
+ interface LDProviderProps {
7
+ clientID: LDClientID;
8
+ context: LDContext;
9
+ options: LDOptions;
10
+ initializing?: Snippet;
11
+ children: Snippet;
12
+ }
13
+
14
+ let { clientID, context, initializing, children, options }: LDProviderProps = $props();
15
+ const { initialize, initializing: isClientInitializing } = LD;
16
+
17
+ onMount(() => {
18
+ initialize(clientID, context, options);
19
+ });
20
+ </script>
21
+
22
+ {#if initializing && $isClientInitializing}
23
+ {@render initializing()}
24
+ {:else}
25
+ {@render children()}
26
+ {/if}
@@ -0,0 +1,12 @@
1
+ import { type Snippet } from 'svelte';
2
+ import type { LDClientID, LDContext, LDOptions } from '../client/SvelteLDClient.js';
3
+ interface LDProviderProps {
4
+ clientID: LDClientID;
5
+ context: LDContext;
6
+ options: LDOptions;
7
+ initializing?: Snippet;
8
+ children: Snippet;
9
+ }
10
+ declare const LdProvider: import("svelte").Component<LDProviderProps, {}, "">;
11
+ type LdProvider = ReturnType<typeof LdProvider>;
12
+ export default LdProvider;
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@nosnibor89/svelte-client-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Svelte LaunchDarkly SDK",
5
+ "homepage": "https://github.com/launchdarkly/js-core/tree/main/packages/sdk/svelte",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/launchdarkly/js-core.git"
9
+ },
10
+ "license": "Apache-2.0",
11
+ "packageManager": "yarn@3.4.1",
12
+ "keywords": [
13
+ "launchdarkly",
14
+ "svelte"
15
+ ],
16
+ "type": "module",
17
+ "svelte": "./dist/index.js",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "svelte": "./dist/index.js",
23
+ "default": "./dist/index.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "!dist/**/*.test.*",
29
+ "!dist/**/*.spec.*"
30
+ ],
31
+ "scripts": {
32
+ "clean": "rimraf dist",
33
+ "dev": "vite dev",
34
+ "build": "vite build && npm run package",
35
+ "preview": "vite preview",
36
+ "package": "svelte-kit sync && svelte-package && publint",
37
+ "prepublishOnly": "npm run package",
38
+ "lint": "eslint . --ext .ts,.tsx",
39
+ "prettier": "prettier --write '**/*.@(js|ts|tsx|json|css)' --ignore-path ../../../.prettierignore",
40
+ "check": "yarn prettier && yarn lint && yarn build && yarn test",
41
+ "test:unit": "vitest",
42
+ "test:unit-ui": "vitest --ui",
43
+ "test:unit-coverage": "vitest --coverage"
44
+ },
45
+ "peerDependencies": {
46
+ "svelte": "^4.0.0"
47
+ },
48
+ "dependencies": {
49
+ "@launchdarkly/js-client-sdk": "^0.3.3",
50
+ "esm-env": "^1.0.0"
51
+ },
52
+ "devDependencies": {
53
+ "@sveltejs/adapter-auto": "^3.0.0",
54
+ "@sveltejs/kit": "^2.5.27",
55
+ "@sveltejs/package": "^2.3.7",
56
+ "@sveltejs/vite-plugin-svelte": "^5.0.1",
57
+ "@testing-library/jest-dom": "^6.6.3",
58
+ "@testing-library/svelte": "next",
59
+ "@types/jest": "^29.5.11",
60
+ "@typescript-eslint/eslint-plugin": "^6.20.0",
61
+ "@typescript-eslint/parser": "^6.20.0",
62
+ "@vitest/coverage-v8": "^2.1.8",
63
+ "@vitest/ui": "^2.1.8",
64
+ "eslint": "^8.45.0",
65
+ "eslint-config-airbnb-base": "^15.0.0",
66
+ "eslint-config-airbnb-typescript": "^17.1.0",
67
+ "eslint-config-prettier": "^8.8.0",
68
+ "eslint-plugin-import": "^2.27.5",
69
+ "eslint-plugin-jest": "^27.6.3",
70
+ "eslint-plugin-prettier": "^5.0.0",
71
+ "eslint-plugin-svelte": "^2.45.1",
72
+ "jsdom": "^25.0.1",
73
+ "prettier": "^3.1.0",
74
+ "prettier-plugin-svelte": "^3.2.6",
75
+ "publint": "^0.1.9",
76
+ "rimraf": "^5.0.5",
77
+ "svelte": "^5.4.0",
78
+ "svelte-check": "^4.0.0",
79
+ "ts-jest": "^29.1.1",
80
+ "ts-node": "^10.9.2",
81
+ "typedoc": "0.25.0",
82
+ "typescript": "^5.5.0",
83
+ "vite": "^6.0.2",
84
+ "vitest": "^2.1.8"
85
+ }
86
+ }