-
Notifications
You must be signed in to change notification settings - Fork 31
#1 Work on initial scaffold. #4
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
Changes from all commits
a090170
0b470ea
93fbac6
8be08da
5302683
263fccd
831b964
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import { BigSegmentStoreStatusProviderImpl, interfaces } from '@launchdarkly/js-server-sdk-common'; | ||
| import EventEmitter from 'events'; | ||
| import { Emits } from './Emits'; | ||
|
|
||
| class BigSegmentStoreStatusProviderNode extends BigSegmentStoreStatusProviderImpl { | ||
| emitter: EventEmitter = new EventEmitter(); | ||
|
|
||
| override dispatch(eventType: string, status: interfaces.BigSegmentStoreStatus) { | ||
| this.emitter.emit(eventType, status); | ||
| } | ||
| } | ||
|
|
||
| export default Emits(BigSegmentStoreStatusProviderNode); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| import EventEmitter from 'events'; | ||
|
|
||
| export type EventableConstructor<T = {}> = new (...args: any[]) => T; | ||
| export type Eventable = EventableConstructor<{ emitter: EventEmitter }>; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For more information you can look here: https://www.typescriptlang.org/docs/handbook/mixins.html This is the constrained variety: https://www.typescriptlang.org/docs/handbook/mixins.html#constrained-mixins |
||
|
|
||
| /** | ||
| * Adds the implementation of an event emitter to something that contains | ||
| * a field of `emitter` with type `EventEmitter`. | ||
| * @param Base The class to derive the mixin from. | ||
| * @returns A class extending the base with an event emitter. | ||
| */ | ||
| export function Emits<TBase extends Eventable>(Base: TBase) { | ||
| return class WithEvents extends Base { | ||
| addListener(eventName: string | symbol, listener: (...args: any[]) => void): this { | ||
| this.emitter.addListener(eventName, listener); | ||
| return this; | ||
| } | ||
|
|
||
| once(eventName: string | symbol, listener: (...args: any[]) => void): this { | ||
| this.emitter.once(eventName, listener); | ||
| return this; | ||
| } | ||
|
|
||
| removeListener(eventName: string | symbol, listener: ( | ||
| ...args: any[]) => void): this { | ||
| this.emitter.removeListener(eventName, listener); | ||
| return this; | ||
| } | ||
|
|
||
| off(eventName: string | symbol, listener: (...args: any) => void): this { | ||
| this.emitter.off(eventName, listener); | ||
| return this; | ||
| } | ||
|
|
||
| removeAllListeners(event?: string | symbol): this { | ||
| this.emitter.removeAllListeners(event); | ||
| return this; | ||
| } | ||
|
|
||
| setMaxListeners(n: number): this { | ||
| this.emitter.setMaxListeners(n); | ||
| return this; | ||
| } | ||
|
|
||
| getMaxListeners(): number { | ||
| return this.emitter.getMaxListeners(); | ||
| } | ||
|
|
||
| listeners(eventName: string | symbol): Function[] { | ||
| return this.emitter.listeners(eventName); | ||
| } | ||
|
|
||
| rawListeners(eventName: string | symbol): Function[] { | ||
| return this.emitter.rawListeners(eventName); | ||
| } | ||
|
|
||
| emit(eventName: string | symbol, ...args: any[]): boolean { | ||
| return this.emitter.emit(eventName, args); | ||
| } | ||
|
|
||
| listenerCount(eventName: string | symbol): number { | ||
| return this.emitter.listenerCount(eventName); | ||
| } | ||
|
|
||
| prependListener(eventName: string | symbol, listener: ( | ||
| ...args: any[]) => void): this { | ||
| this.emitter.prependListener(eventName, listener); | ||
| return this; | ||
| } | ||
|
|
||
| prependOnceListener(eventName: string | symbol, listener: (...args: any[]) => void): this { | ||
| this.emitter.prependOnceListener(eventName, listener); | ||
| return this; | ||
| } | ||
|
|
||
| eventNames(): (string | symbol)[] { | ||
| return this.emitter.eventNames(); | ||
| } | ||
| }; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { | ||
| LDClientImpl, LDOptions, | ||
| } from '@launchdarkly/js-server-sdk-common'; | ||
|
|
||
| import EventEmitter from 'events'; | ||
| import NodePlatform from './platform/NodePlatform'; | ||
| import { Emits } from './Emits'; | ||
| import BigSegmentStoreStatusProviderNode from './BigSegmentsStoreStatusProviderNode'; | ||
|
|
||
| class LDClientNode extends LDClientImpl { | ||
| emitter: EventEmitter = new EventEmitter(); | ||
|
|
||
| override bigSegmentStoreStatusProvider: | ||
| InstanceType<typeof BigSegmentStoreStatusProviderNode>; | ||
|
|
||
| constructor(options: LDOptions) { | ||
| super(new NodePlatform(options)); | ||
| this.bigSegmentStoreStatusProvider = new BigSegmentStoreStatusProviderNode(); | ||
| } | ||
| } | ||
|
|
||
| export default Emits(LDClientNode); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When we actually create one of these, then we will need to return an interface. This will currently act as a value instead of a type, which we do not want. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { LDClient as LDClientCommon } from '@launchdarkly/js-server-sdk-common'; | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When we add an init method the LDClient interfaces exported by this class is the one we will want to use. This will hide the complexity of our internal type implementation and mixins. |
||
| import EventEmitter from 'events'; | ||
| import { BigSegmentStoreStatusProvider } from './interfaces'; | ||
|
|
||
| /** | ||
| * The LaunchDarkly SDK client object. | ||
| * | ||
| * Create this object with [[init]]. Applications should configure the client at startup time and | ||
| * continue to use it throughout the lifetime of the application, rather than creating instances on | ||
| * the fly. | ||
| * | ||
| */ | ||
| export interface LDClient extends LDClientCommon, EventEmitter { | ||
| /** | ||
| * A mechanism for tracking the status of a Big Segment store. | ||
| * | ||
| * This object has methods for checking whether the Big Segment store is (as far as the SDK | ||
| * knows) currently operational and tracking changes in this status. See | ||
| * {@link interfaces.BigSegmentStoreStatusProvider} for more about this functionality. | ||
| */ | ||
| readonly bigSegmentStoreStatusProvider: BigSegmentStoreStatusProvider; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| export * from './LDClient'; | ||
| export * from './interfaces'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| import EventEmitter from 'events'; | ||
| import { interfaces } from '@launchdarkly/js-server-sdk-common'; | ||
|
|
||
| /** | ||
| * An interface for querying the status of a Big Segment store. | ||
| * | ||
| * The Big Segment store is the component that receives information about Big Segments, normally | ||
| * from a database populated by the LaunchDarkly Relay Proxy. Big Segments are a specific type of | ||
| * user segments. For more information, read the LaunchDarkly documentation: | ||
| * https://docs.launchdarkly.com/home/users/big-segments | ||
| * | ||
| * An implementation of this interface is returned by | ||
| * {@link LDClient.bigSegmentStoreStatusProvider}. Application code never needs to implement this | ||
| * interface. | ||
| * | ||
| * Note that this type inherits from `EventEmitter`, so you can use the standard `on()`, `once()`, | ||
| * and `off()` methods to receive status change events. The standard `EventEmitter` methods are not | ||
| * documented here; see the | ||
| * {@link https://nodejs.org/api/events.html#events_class_eventemitter|Node API documentation}. The | ||
| * type of the status change event is `"change"`, and its value is the same value that would be | ||
| * returned by {@link getStatus}. | ||
| */ | ||
| export interface BigSegmentStoreStatusProvider extends EventEmitter { | ||
| /** | ||
| * Gets the current status of the store, if known. | ||
| * | ||
| * @returns a {@link BigSegmentStoreStatus}, or `undefined` if the SDK has not yet queried the | ||
| * Big Segment store status | ||
| */ | ||
| getStatus(): interfaces.BigSegmentStoreStatus | undefined; | ||
|
|
||
| /** | ||
| * Gets the current status of the store, querying it if the status has not already been queried. | ||
| * | ||
| * @returns a Promise for the status of the store | ||
| */ | ||
| requireStatus(): Promise<interfaces.BigSegmentStoreStatus>; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export * from './BigSegmentStatusProvider'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,24 +1,10 @@ | ||
| /* eslint-disable no-console */ | ||
| // This file contains temporary code for testing. | ||
| import NodePlatform from './NodePlatform'; | ||
| import LDClientImpl from './LDClientNode'; | ||
| import BigSegmentStoreStatusProviderNode from './BigSegmentsStoreStatusProviderNode'; | ||
|
|
||
| const platform = new NodePlatform({ | ||
| tlsParams: { | ||
| checkServerIdentity: (name) => { | ||
| console.log('GOT A IDENTITY CHECK', name); | ||
| return undefined; | ||
| }, | ||
| }, | ||
| }); | ||
| export * from '@launchdarkly/js-server-sdk-common'; | ||
|
|
||
| async function runTest() { | ||
| console.log('Making request'); | ||
| const res = await platform.requests.fetch('https://www.google.com'); | ||
| console.log('Got res', res); | ||
| const body = await res.text(); | ||
| console.log('Body', body); | ||
| } | ||
| // To replace the exports from `export *` we need to name them. | ||
| // So the below exports replace them with the Node specific variants. | ||
|
|
||
| runTest(); | ||
|
|
||
| console.log(platform.info.sdkData()); | ||
| export { LDClient } from './api'; | ||
| export { LDClientImpl, BigSegmentStoreStatusProviderNode }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| /* eslint-disable class-methods-use-this */ | ||
| import { BigSegmentStoreStatusProvider, BigSegmentStoreStatus } from './api/interfaces'; | ||
|
|
||
| export default class BigSegmentStoreStatusProviderImpl implements BigSegmentStoreStatusProvider { | ||
| /** | ||
| * Gets the current status of the store, if known. | ||
| * | ||
| * @returns a {@link BigSegmentStoreStatus}, or `undefined` if the SDK has not yet queried the | ||
| * Big Segment store status | ||
| */ | ||
| getStatus(): BigSegmentStoreStatus | undefined { | ||
| return undefined; | ||
| } | ||
|
|
||
| /** | ||
| * Gets the current status of the store, querying it if the status has not already been queried. | ||
| * | ||
| * @returns a Promise for the status of the store | ||
| */ | ||
| async requireStatus(): Promise<BigSegmentStoreStatus> { | ||
| return Promise.reject(); | ||
| } | ||
|
|
||
| /** | ||
| * This should be overridden by derived implementations. | ||
| * @param eventType | ||
| * @param status | ||
| */ | ||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
| dispatch(eventType: string, status: BigSegmentStoreStatus) {} | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| /* eslint-disable @typescript-eslint/no-unused-vars */ | ||
| /* eslint-disable class-methods-use-this */ | ||
| import { | ||
| LDClient, LDContext, LDEvaluationDetail, LDFlagsState, LDFlagsStateOptions, | ||
| } from './api'; | ||
| import BigSegmentStoreStatusProvider from './BigSegmentStatusProviderImpl'; | ||
| import { Platform } from './platform'; | ||
|
|
||
| enum EventTypes { | ||
| /** | ||
| * Emitted on errors other than a failure to initialize. | ||
| */ | ||
| Error = 'error', | ||
| /** | ||
| * Emitted when the SDK fails to initialize. | ||
| */ | ||
| Failed = 'failed', | ||
| /** | ||
| * Emitted when the SDK is ready to use. | ||
| */ | ||
| Ready = 'ready', | ||
| } | ||
|
|
||
| enum InitState { | ||
| Initializing, | ||
| Initialized, | ||
| Failed, | ||
| } | ||
|
|
||
| export default class LDClientImpl implements LDClient { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I may rename this 'Impl' classes to common or something later. 'Impl' feels off. The typescript gods decided interfaces do not start with |
||
| private platform: Platform; | ||
|
|
||
| private initState: InitState = InitState.Initializing; | ||
|
|
||
| /** | ||
| * Intended for use by platform specific client implementations. | ||
| * | ||
| * It is not included in the main interface because it requires the use of | ||
| * a platform event system. For node this would be an EventEmitter, for other | ||
| * platforms it would likely be an EventTarget. | ||
| */ | ||
| bigSegmentStoreStatusProvider = new BigSegmentStoreStatusProvider(); | ||
|
|
||
| constructor(targetPlatform: Platform) { | ||
| this.platform = targetPlatform; | ||
| } | ||
|
|
||
| initialized(): boolean { | ||
| return this.initState === InitState.Initialized; | ||
| } | ||
|
|
||
| waitForInitialization(): Promise<LDClient> { | ||
| throw new Error('Method not implemented.'); | ||
| } | ||
|
|
||
| variation( | ||
| key: string, | ||
| context: LDContext, | ||
| defaultValue: any, | ||
| callback?: (err: any, res: any) => void, | ||
| ): Promise<any> { | ||
| throw new Error('Method not implemented.'); | ||
| } | ||
|
|
||
| variationDetail( | ||
| key: string, | ||
| context: LDContext, | ||
| defaultValue: any, | ||
| callback?: (err: any, res: LDEvaluationDetail) => void, | ||
| ): Promise<LDEvaluationDetail> { | ||
| throw new Error('Method not implemented.'); | ||
| } | ||
|
|
||
| allFlagsState( | ||
| context: LDContext, | ||
| options?: LDFlagsStateOptions, | ||
| callback?: (err: Error, res: LDFlagsState) => void, | ||
| ): Promise<LDFlagsState> { | ||
| throw new Error('Method not implemented.'); | ||
| } | ||
|
|
||
| secureModeHash(context: LDContext): string { | ||
| throw new Error('Method not implemented.'); | ||
| } | ||
|
|
||
| close(): void { | ||
| throw new Error('Method not implemented.'); | ||
| } | ||
|
|
||
| isOffline(): boolean { | ||
| throw new Error('Method not implemented.'); | ||
| } | ||
|
|
||
| track(key: string, context: LDContext, data?: any, metricValue?: number): void { | ||
| throw new Error('Method not implemented.'); | ||
| } | ||
|
|
||
| identify(context: LDContext): void { | ||
| throw new Error('Method not implemented.'); | ||
| } | ||
|
|
||
| flush(callback?: (err: Error, res: boolean) => void): Promise<void> { | ||
| throw new Error('Method not implemented.'); | ||
| } | ||
|
|
||
| on(event: string | symbol, listener: (...args: any[]) => void): this { | ||
| throw new Error('Method not implemented.'); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation here allows you to 'mix' an emitter into any type which contains an emitter.