Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add initial SDK library boilerplate and basic svelte LD SDK #632

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

nosnibor89
Copy link

@nosnibor89 nosnibor89 commented Oct 23, 2024

Requirements

  • I have added test coverage for new or changed functionality
  • I have followed the repository's pull request submission guidelines (TBH yarn run contract-tests failed for me even in main)
  • I have validated my changes against all supported platform versions

Related issues

No issue

Describe the solution you've provided

Introducing the new @launchdarkly/svelte-client-sdk package. Some of the details included in this PR are

  1. Svelte Library Boilerplate
  2. Basic Svelte SDK functionality:
    2.1 LDProvider component
    2.2 LDFlag component
    2.3 Svelte-compatible LD instance (exposes API to work with feature flags)

Describe alternatives you've considered

I don't know what to write here.

Additional context

This is the first of a series of PRs. Some of the following PR should be about

  1. Adding Documentation for @launchdarkly/svelte-client-sdk
  2. Adding Example project that uses @launchdarkly/svelte-client-sdk

@nosnibor89 nosnibor89 changed the title add initial SDK library boilerplate and basic svelte LD SDK feat: add initial SDK library boilerplate and basic svelte LD SDK Oct 23, 2024
@nosnibor89 nosnibor89 marked this pull request as ready for review October 23, 2024 21:50
@nosnibor89 nosnibor89 requested a review from a team as a code owner October 23, 2024 21:50
@kinyoklion
Copy link
Member

Hello @nosnibor89,

Thank you for the contribution.

Those contract tests aren't relevant to client-side SDKs, but they should all work correctly (and do run in CI and passed for your PR). When developing locally running yarn build in the root of the repo should get everything into a functioning state. (Builds all packages, and everything in the workspace will use local packages.) The contract tests do run a go binary downloaded from github, so that could cause problems for some configurations.

I won't be able to review this deeply this week, but hopefully I can next week.

Thank you,
Ryan

* @param {string} flagKey - The key of the flag to watch.
* @returns {Readable<LDFlagsValue>} A readable store of the flag value.
*/
const watch = (flagKey: string): Readable<LDFlagsValue> =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure how it needs to be handled with Svelte, but when a flag is accessed we have to make sure an event is sent to launchdarkly. This is accomplished with a variation/variation detail call.

Part of the SDKs job is providing access to flags, but the other part is driving product features.

Flag insights and experimentation, for instance, require those events to function.

For react we use a Proxy: https://github.com/launchdarkly/react-client-sdk/blob/21c2402f6d591caf61764ba35263ff1e4cb18ae7/src/getFlagsProxy.ts#L61

Ideally though we would listen to specific flags.

Copy link
Author

@nosnibor89 nosnibor89 Nov 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense. I just added a similar function toFlagsProxy to track flag reads and use the client.variation(prop, currentValue); behind the scenes.

Let me know what you think.

* @param {string} flagKey - The key of the flag to check.
* @returns {boolean} True if the flag is on, false otherwise.
*/
const isOn = (flagKey: string): boolean => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, we could have something like this that was useValue, but we wouldn't want a pure boolean result.

Ideally each flag access takes a default, and that default goes to variation, and then that can be included in events to drive product features. So ideally we would focus the interface on individual flag access.

On means something different though. Lets imagine a flag called backgroundColor, when it is on it serves 5% of users blue, and the rest yellow, but when it is off it serves white.

On/Off is part of the reason for an evaluation, but not derived from its value.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kinyoklion Jum! 🤔

I think I get the part where we don't want to restrict ourselves to booleans only. I could refactor this to be something like:

 const isValue = (flagKey: string, value: LDFlagsValue = true): boolean => {
    isClientInitialized(jsSdk);
    const currentFlags = get(flagsWritable);
    return currentFlags[flagKey] === value;
  };

so later I could do isValue('backgroundColor', true), assuming "backgroundColor" is a boolean flag (if I understand this correctly)

However, I'm not sure I understand how to handle default values. Can you clarify that a little bit better for me?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anywhere we "use" a flag, we need to use the variation method and provide it with a default value.

So, for example:

function useFlag<TFlag extends LDFlagsValue>(flagKey: string, defaultValue: TFlag): TFlag => {
 isClientInitialized(jsSdk);
  // Ideally we would add some real type safety here.
 return jsSdk.variation(flagKey, defaultValue) as TFlag;
}
if(useFlag('my-flag', false)) { // the generic should cause this to be a boolean

}

Though you could be explicit as well:

if(useFlag<boolean>('my-flag', false)) { // the generic should cause this to be a boolean

}

Or use other types:

if(useFlag('my-string-flag', 'cheese') === 'potato')) {
 // I am a potato.
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do have individually type safe methods as well, so those could be directly proxies as an alternative to a generic. Or in addition to the generic.

useBooleanFlag

For example. The thing is that in the implementation we can check if the value was actually a boolean. If it is not a boolean, then we use the default value.

We also report this back to launchdarkly so that there are analytics that the wrong type is being used.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @kinyoklion . I used your suggestion, please take a look whenever you have a chance.

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.

4 participants