diff --git a/clientlibs/js/package.json b/clientlibs/js/package.json index e76e9936e..ae89f8cc4 100644 --- a/clientlibs/js/package.json +++ b/clientlibs/js/package.json @@ -50,8 +50,9 @@ "docs:markdown": "typedoc --options typedoc.json", "test": "jest", "test:coverage": "jest --coverage", - "quicktest": "([ -d dist ] || yarn build) && ts-node quickTest.ts", - "quicktest:adaptive": "([ -d dist ] || yarn build) && ts-node quickTestAdaptive.ts" + "quicktest": "([ -f dist/node/index.js ] || yarn build) && ts-node quickTest.ts", + "quicktest:lite": "([ -f dist/lite/index.js ] || yarn build) && ts-node quickTest.ts lite", + "quicktest:adaptive": "([ -f dist/node/index.js ] || yarn build) && ts-node quickTestAdaptive.ts" }, "keywords": [], "author": "", diff --git a/clientlibs/js/quickTest.ts b/clientlibs/js/quickTest.ts index ad0e38cbe..905e142c3 100644 --- a/clientlibs/js/quickTest.ts +++ b/clientlibs/js/quickTest.ts @@ -1,7 +1,21 @@ -// to run: npx ts-node clientlibs/js/quickTest.ts +// to run against the full (axios-bundled) build: npx ts-node clientlibs/js/quickTest.ts +// to run against the "lite" build (BYO http client): npx ts-node clientlibs/js/quickTest.ts lite -import { AxiosError } from 'axios'; -import UpgradeClient, { MARKED_DECISION_POINT_STATUS, UpGradeClientInterfaces } from './dist/node'; +import type { UpGradeClientInterfaces } from './dist/node'; +import { FetchHttpClient } from './quickTestLiteHttpClient'; + +const variant = process.argv[2] === 'lite' ? 'lite' : 'node'; +console.log(`\n[quickTest] running against the "${variant}" build\n`); + +// dynamic require so the unused variant's bundle (and, for "node", its bundled axios) is never +// loaded -- that would defeat the point of smoke-testing "lite" in isolation. +// webpack's `libraryExport: 'default'` UMD setting makes the required module *be* the default +// export (the UpgradeClient class) directly, with MARKED_DECISION_POINT_STATUS/etc. reachable +// only as static properties on it -- there is no `.default` to unwrap. +// eslint-disable-next-line @typescript-eslint/no-var-requires +const UpgradeClient = require(`./dist/${variant}`) as typeof import('./dist/node').default; +const { MARKED_DECISION_POINT_STATUS } = UpgradeClient; +type UpgradeClientInstance = InstanceType; const URL = { LOCAL: 'http://localhost:3030', @@ -39,6 +53,9 @@ const options: UpGradeClientInterfaces.IConfigOptions = { includeStoredUserGroups, } : null, + // the "lite" build ships with no bundled http client and throws unless one is provided; + // the "node"/"browser" builds throw if one *is* provided, since they own the default (axios) client + httpClient: variant === 'lite' ? new FetchHttpClient() : undefined, }; const logRequest = [ @@ -100,65 +117,65 @@ async function quickTest() { /** test functions *******************************************************************************/ -async function doInit(client: UpgradeClient) { +async function doInit(client: UpgradeClientInstance) { try { const response = await client.init(); console.log('\n[Init response]:', JSON.stringify(response)); } catch (error) { - logAxiosError('Init', error); + logRequestError('Init', error); } } -async function doGroupMembership(client: UpgradeClient) { +async function doGroupMembership(client: UpgradeClientInstance) { const groupRequest: UpGradeClientInterfaces.IExperimentUserGroup = group; try { const response = await client.setGroupMembership(groupRequest); console.log('\n[Group response]:', JSON.stringify(response)); } catch (error) { - logAxiosError('Group', error); + logRequestError('Group', error); } } -async function doWorkingGroupMembership(client: UpgradeClient) { +async function doWorkingGroupMembership(client: UpgradeClientInstance) { const workingGroupRequest: UpGradeClientInterfaces.IExperimentUserWorkingGroup = { workingGroup }; try { const response = await client.setWorkingGroup(workingGroupRequest); console.log('\n[Working Group response]:', JSON.stringify(response)); } catch (error) { - logAxiosError('Working Group', error); + logRequestError('Working Group', error); } } -async function doAliases(client: UpgradeClient) { +async function doAliases(client: UpgradeClientInstance) { const aliasRequest = [alias]; try { const response = await client.setAltUserIds(aliasRequest); console.log('\n[Aliases response]:', JSON.stringify(response)); } catch (error) { - logAxiosError('Aliases', error); + logRequestError('Aliases', error); } } -async function doAssign(client: UpgradeClient) { +async function doAssign(client: UpgradeClientInstance) { try { const response = await client.getAllExperimentConditions(); console.log('\n[Assign response]:', JSON.stringify(response)); } catch (error) { - logAxiosError('Assign', error); + logRequestError('Assign', error); } } -async function doAssignIgnoreCache(client: UpgradeClient) { +async function doAssignIgnoreCache(client: UpgradeClientInstance) { try { const response = await client.getAllExperimentConditions({ ignoreCache: true }); console.log('\n[Assign response]:', JSON.stringify(response)); } catch (error) { - logAxiosError('Assign', error); + logRequestError('Assign', error); } } -async function doGetDecisionPointAssignment(client: UpgradeClient): Promise { +async function doGetDecisionPointAssignment(client: UpgradeClientInstance): Promise { try { const response = await client.getDecisionPointAssignment(site, target); console.log('\n[Decision Point Assignment response]:', JSON.stringify(response)); @@ -176,65 +193,65 @@ async function doGetDecisionPointAssignment(client: UpgradeClient): Promise( + url: string, + options: UpGradeClientInterfaces.IHttpClientWrapperRequestConfig + ): Promise { + return this.request('GET', url, undefined, options); + } + + public async doPost( + url: string, + body: RequestBodyType, + options: UpGradeClientInterfaces.IHttpClientWrapperRequestConfig + ): Promise { + return this.request('POST', url, body, options); + } + + public async doPatch( + url: string, + body: RequestBodyType, + options: UpGradeClientInterfaces.IHttpClientWrapperRequestConfig + ): Promise { + return this.request('PATCH', url, body, options); + } + + private async request( + method: 'GET' | 'POST' | 'PATCH', + url: string, + body: unknown, + options: UpGradeClientInterfaces.IHttpClientWrapperRequestConfig + ): Promise { + const response = await fetch(url, { + method, + headers: options.headers as Record, + credentials: options.withCredentials ? 'include' : 'same-origin', + body: body === undefined ? undefined : JSON.stringify(body), + }); + + // matches DefaultHttpClient's shape so the existing quickTest error logging works unchanged + let responseBody: unknown; + try { + responseBody = await response.json(); + } catch { + responseBody = undefined; + } + if (!response.ok) { + throw new Error(JSON.stringify({ statusCode: response.status, response: responseBody })); + } + + return responseBody as ResponseType; + } +} diff --git a/clientlibs/js/src/ApiService/ApiService.ts b/clientlibs/js/src/ApiService/ApiService.ts index c853bd2b2..b48887205 100644 --- a/clientlibs/js/src/ApiService/ApiService.ts +++ b/clientlibs/js/src/ApiService/ApiService.ts @@ -1,5 +1,11 @@ import { UpGradeClientEnums, UpGradeClientInterfaces, UpGradeClientRequests } from '../types'; -import { CaliperEnvelope, IExperimentAssignment, ILogInput, IUserAliases, ILogRequestBody } from 'upgrade_types'; +import { + CaliperEnvelope, + IExperimentAssignment, + ILogInput, + IUserAliases, + ILogRequestBody, +} from 'upgrade_types/Experiment/interfaces'; import { DataService } from 'DataService/DataService'; import { IApiServiceRequestParams, IEndpoints } from './ApiService.types'; diff --git a/clientlibs/js/src/Assignment/Assignment.ts b/clientlibs/js/src/Assignment/Assignment.ts index 18ce9b9ee..720a996be 100644 --- a/clientlibs/js/src/Assignment/Assignment.ts +++ b/clientlibs/js/src/Assignment/Assignment.ts @@ -1,10 +1,5 @@ -import { - IExperimentAssignment, - PAYLOAD_TYPE, - EXPERIMENT_TYPE, - IPayload, - MARKED_DECISION_POINT_STATUS, -} from 'upgrade_types'; +import { IExperimentAssignment, IPayload } from 'upgrade_types/Experiment/interfaces'; +import { PAYLOAD_TYPE, EXPERIMENT_TYPE, MARKED_DECISION_POINT_STATUS } from 'upgrade_types/Experiment/enums'; import { UpGradeClientInterfaces } from '../types'; import ApiService from '../ApiService/ApiService'; diff --git a/clientlibs/js/src/DataService/DataService.ts b/clientlibs/js/src/DataService/DataService.ts index b8bc0edec..90f01a99b 100644 --- a/clientlibs/js/src/DataService/DataService.ts +++ b/clientlibs/js/src/DataService/DataService.ts @@ -1,5 +1,5 @@ import { UpGradeClientInterfaces } from '../types'; -import { IExperimentAssignment } from 'upgrade_types'; +import { IExperimentAssignment } from 'upgrade_types/Experiment/interfaces'; /** * Synchronous data store diff --git a/clientlibs/js/src/UpGradeClient/UpgradeClient.ts b/clientlibs/js/src/UpGradeClient/UpgradeClient.ts index 6740d58c4..674525a36 100644 --- a/clientlibs/js/src/UpGradeClient/UpgradeClient.ts +++ b/clientlibs/js/src/UpGradeClient/UpgradeClient.ts @@ -3,10 +3,10 @@ import { ILogInput, CaliperEnvelope, IExperimentAssignment, - MARKED_DECISION_POINT_STATUS, IUserAliases, BinaryRewardAllowedValue, -} from 'upgrade_types'; +} from 'upgrade_types/Experiment/interfaces'; +import { MARKED_DECISION_POINT_STATUS } from 'upgrade_types/Experiment/enums'; import Assignment from '../Assignment/Assignment'; import ApiService from '../ApiService/ApiService'; import { DataService } from '../DataService/DataService'; diff --git a/clientlibs/js/src/index.ts b/clientlibs/js/src/index.ts index e5e68c25e..9242c28b1 100644 --- a/clientlibs/js/src/index.ts +++ b/clientlibs/js/src/index.ts @@ -1,8 +1,8 @@ import UpgradeClient from './UpGradeClient/UpgradeClient'; import Assignment from './Assignment/Assignment'; import { UpGradeClientEnums, UpGradeClientInterfaces, UpGradeClientRequests } from './types'; -import { MARKED_DECISION_POINT_STATUS } from 'upgrade_types'; -import type { IExperimentAssignment, IExperimentAssignmentv5 } from 'upgrade_types'; +import { MARKED_DECISION_POINT_STATUS } from 'upgrade_types/Experiment/enums'; +import type { IExperimentAssignment, IExperimentAssignmentv5 } from 'upgrade_types/Experiment/interfaces'; export default UpgradeClient; diff --git a/clientlibs/js/src/types/Interfaces.ts b/clientlibs/js/src/types/Interfaces.ts index f6c83340b..acf2bfd9c 100644 --- a/clientlibs/js/src/types/Interfaces.ts +++ b/clientlibs/js/src/types/Interfaces.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-namespace */ -import { IMetricMetaData, MARKED_DECISION_POINT_STATUS } from 'upgrade_types'; +import { IMetricMetaData, MARKED_DECISION_POINT_STATUS } from 'upgrade_types/Experiment/enums'; export namespace UpGradeClientInterfaces { // this namespace should be for consumer facing interface diff --git a/clientlibs/js/src/types/requests.ts b/clientlibs/js/src/types/requests.ts index 87efddfcb..74695e1d2 100644 --- a/clientlibs/js/src/types/requests.ts +++ b/clientlibs/js/src/types/requests.ts @@ -1,4 +1,4 @@ -import { MARKED_DECISION_POINT_STATUS } from 'upgrade_types'; +import { MARKED_DECISION_POINT_STATUS } from 'upgrade_types/Experiment/enums'; import { UpGradeClientInterfaces } from './Interfaces'; /* eslint-disable @typescript-eslint/no-namespace */ diff --git a/clientlibs/js/tsconfig.json b/clientlibs/js/tsconfig.json index 070288b7b..0c25408a4 100644 --- a/clientlibs/js/tsconfig.json +++ b/clientlibs/js/tsconfig.json @@ -2,6 +2,7 @@ "compileOnSave": false, "compilerOptions": { "baseUrl": "./src", + "rootDir": "../..", "outDir": "./dist", "sourceMap": false, "declaration": true, @@ -18,12 +19,11 @@ "target": "es6", "ignoreDeprecations": "6.0", "typeRoots": ["./node_modules/@types"], - "types": ["node"], - "ignoreDeprecations": "6.0", "lib": ["es2018", "dom"], "types": ["jest", "node"], "paths": { - "upgrade_types": ["../../../packages/types"] + "upgrade_types": ["../../../packages/types"], + "upgrade_types/*": ["../../../packages/types/src/*"] }, "esModuleInterop": true }, diff --git a/clientlibs/js/webpack.config.ts b/clientlibs/js/webpack.config.ts index 9078e268b..b168e07ca 100644 --- a/clientlibs/js/webpack.config.ts +++ b/clientlibs/js/webpack.config.ts @@ -20,6 +20,11 @@ const generalConfiguration = { resolve: { alias: { upgrade_types: path.resolve(__dirname, '../../packages/types/src'), + // packages/types is outside this package's node_modules, so its own + // tslib resolves from the repo-root install while files under src/ + // resolve tslib from this package's local install. Force both to the + // same physical module so webpack doesn't bundle it twice. + tslib: path.resolve(__dirname, 'node_modules/tslib'), }, extensions: ['.tsx', '.ts', '.js'], },