Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion platform-node/__tests__/NodeInfo_test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as os from 'os';
import NodeInfo from '../src/NodeInfo';
import NodeInfo from '../src/platform/NodeInfo';

describe('given an information instance', () => {
const info = new NodeInfo();
Expand Down
2 changes: 1 addition & 1 deletion platform-node/__tests__/NodeRequests_test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as http from 'http';

import NodeRequests from '../src/NodeRequests';
import NodeRequests from '../src/platform/NodeRequests';

const PORT = '3000';
const TEXT_RESPONSE = 'Test Text';
Expand Down
13 changes: 13 additions & 0 deletions platform-node/src/BigSegmentsStoreStatusProviderNode.ts
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);
80 changes: 80 additions & 0 deletions platform-node/src/Emits.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import EventEmitter from 'events';
Copy link
Member Author

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.


export type EventableConstructor<T = {}> = new (...args: any[]) => T;
export type Eventable = EventableConstructor<{ emitter: EventEmitter }>;
Copy link
Member Author

Choose a reason for hiding this comment

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


/**
* 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();
}
};
}
22 changes: 22 additions & 0 deletions platform-node/src/LDClientNode.ts
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);
Copy link
Member Author

Choose a reason for hiding this comment

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

22 changes: 22 additions & 0 deletions platform-node/src/api/LDClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { LDClient as LDClientCommon } from '@launchdarkly/js-server-sdk-common';
Copy link
Member Author

Choose a reason for hiding this comment

The 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;
}
2 changes: 2 additions & 0 deletions platform-node/src/api/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './LDClient';
export * from './interfaces';
38 changes: 38 additions & 0 deletions platform-node/src/api/interfaces/BigSegmentStatusProvider.ts
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>;
}
1 change: 1 addition & 0 deletions platform-node/src/api/interfaces/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './BigSegmentStatusProvider';
28 changes: 7 additions & 21 deletions platform-node/src/index.ts
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
Expand Up @@ -4,7 +4,7 @@ import { PlatformData, SdkData } from '@launchdarkly/js-server-sdk-common/dist/p

import * as os from 'os';

import * as packageJson from '../package.json';
import * as packageJson from '../../package.json';

function processPlatformName(name: string): string {
switch (name) {
Expand Down
31 changes: 31 additions & 0 deletions server-sdk-common/src/BigSegmentStatusProviderImpl.ts
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) {}
}
109 changes: 109 additions & 0 deletions server-sdk-common/src/LDClientImpl.ts
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 {
Copy link
Member Author

Choose a reason for hiding this comment

The 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 I so naming becomes harder.

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.');
}
}
Loading