Skip to content

Commit 94334bd

Browse files
feat: DVC-9059 pull out EnvironmentConfigManager to its own library, change nodejs build to define external pacakges (#564)
1 parent 083a5e3 commit 94334bd

24 files changed

+282
-58
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"extends": ["../../../.eslintrc.json"],
3+
"ignorePatterns": ["!**/*"],
4+
"overrides": [
5+
{
6+
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7+
"rules": {}
8+
},
9+
{
10+
"files": ["*.ts", "*.tsx"],
11+
"rules": {}
12+
},
13+
{
14+
"files": ["*.js", "*.jsx"],
15+
"rules": {}
16+
}
17+
]
18+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# config-manager
2+
3+
This library extracts the `EnvironmentConfigManager` Server SDK logic to be used across the NodeJS SDK
4+
and Edge Worker SDKs.
5+
6+
## Building
7+
8+
Run `nx build config-manager` to build the library.
9+
10+
## Running unit tests
11+
12+
Run `nx test config-manager` to execute the unit tests via [Jest](https://jestjs.io).

sdk/nodejs/__tests__/environmentConfigManager.spec.ts renamed to lib/shared/config-manager/__tests__/environmentConfigManager.spec.ts

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,40 @@
11
jest.mock('../src/request')
22
jest.useFakeTimers()
33
jest.spyOn(global, 'setInterval')
4-
jest.mock('../src/bucketing')
54

6-
import { EnvironmentConfigManager } from '../src/environmentConfigManager'
7-
import { importBucketingLib, getBucketingLib } from '../src/bucketing'
5+
import { EnvironmentConfigManager } from '../src'
86
import { mocked } from 'jest-mock'
97
import { Response } from 'cross-fetch'
10-
import { dvcDefaultLogger, ResponseError } from '@devcycle/js-cloud-server-sdk'
8+
import {
9+
DevCycleOptions,
10+
dvcDefaultLogger,
11+
ResponseError,
12+
} from '@devcycle/js-cloud-server-sdk'
13+
import { DVCLogger } from '@devcycle/types'
1114
import { getEnvironmentConfig } from '../src/request'
1215

1316
const setInterval_mock = mocked(setInterval)
1417
const getEnvironmentConfig_mock = mocked(getEnvironmentConfig)
1518
const logger = dvcDefaultLogger()
1619

20+
const mockSDKConfig = jest.fn()
21+
22+
function getConfigManager(
23+
logger: DVCLogger,
24+
sdkKey: string,
25+
options: DevCycleOptions,
26+
) {
27+
return new EnvironmentConfigManager(
28+
logger,
29+
sdkKey,
30+
mockSDKConfig,
31+
setInterval,
32+
clearInterval,
33+
options,
34+
)
35+
}
36+
1737
describe('EnvironmentConfigManager Unit Tests', () => {
18-
beforeAll(async () => {
19-
await importBucketingLib()
20-
})
2138
beforeEach(() => {
2239
getEnvironmentConfig_mock.mockReset()
2340
setInterval_mock.mockReset()
@@ -43,18 +60,15 @@ describe('EnvironmentConfigManager Unit Tests', () => {
4360
mockFetchResponse({ status: 200 }),
4461
)
4562

46-
const envConfig = new EnvironmentConfigManager(logger, 'sdkKey', {
63+
const envConfig = getConfigManager(logger, 'sdkKey', {
4764
configPollingIntervalMS: 1000,
4865
configPollingTimeoutMS: 1000,
4966
})
5067
await envConfig.fetchConfigPromise
5168
expect(setInterval_mock).toHaveBeenCalledTimes(1)
5269

5370
await envConfig._fetchConfig()
54-
expect(getBucketingLib().setConfigDataUTF8).toHaveBeenCalledWith(
55-
'sdkKey',
56-
Buffer.from('{}', 'utf8'),
57-
)
71+
expect(mockSDKConfig).toHaveBeenCalledWith('sdkKey', '{}')
5872

5973
expect(envConfig).toEqual(
6074
expect.objectContaining({
@@ -72,7 +86,7 @@ describe('EnvironmentConfigManager Unit Tests', () => {
7286
mockFetchResponse({ status: 200 }),
7387
)
7488

75-
const envConfig = new EnvironmentConfigManager(logger, 'sdkKey', {
89+
const envConfig = getConfigManager(logger, 'sdkKey', {
7690
configPollingIntervalMS: 10,
7791
configPollingTimeoutMS: 10000,
7892
})
@@ -97,7 +111,7 @@ describe('EnvironmentConfigManager Unit Tests', () => {
97111
mockFetchResponse({ status: 200 }),
98112
)
99113

100-
const envConfig = new EnvironmentConfigManager(logger, 'sdkKey', {
114+
const envConfig = getConfigManager(logger, 'sdkKey', {
101115
configPollingIntervalMS: 1000,
102116
configPollingTimeoutMS: 1000,
103117
})
@@ -129,7 +143,7 @@ describe('EnvironmentConfigManager Unit Tests', () => {
129143
mockFetchResponse({ status: 500 }),
130144
)
131145

132-
const envConfig = new EnvironmentConfigManager(logger, 'sdkKey', {})
146+
const envConfig = getConfigManager(logger, 'sdkKey', {})
133147
expect(envConfig.fetchConfigPromise).rejects.toThrow(
134148
'Failed to download DevCycle config.',
135149
)
@@ -140,7 +154,7 @@ describe('EnvironmentConfigManager Unit Tests', () => {
140154
mockFetchResponse({ status: 403 }),
141155
)
142156

143-
const envConfig = new EnvironmentConfigManager(logger, 'sdkKey', {})
157+
const envConfig = getConfigManager(logger, 'sdkKey', {})
144158
expect(envConfig.fetchConfigPromise).rejects.toThrow(
145159
'Invalid SDK key provided:',
146160
)
@@ -150,7 +164,7 @@ describe('EnvironmentConfigManager Unit Tests', () => {
150164
it('should throw error fetching config throws', () => {
151165
getEnvironmentConfig_mock.mockRejectedValue(new Error('Error'))
152166

153-
const envConfig = new EnvironmentConfigManager(logger, 'sdkKey', {})
167+
const envConfig = getConfigManager(logger, 'sdkKey', {})
154168
expect(envConfig.fetchConfigPromise).rejects.toThrow(
155169
'Failed to download DevCycle config.',
156170
)
@@ -162,7 +176,7 @@ describe('EnvironmentConfigManager Unit Tests', () => {
162176
mockFetchResponse({ status: 200, data: config }),
163177
)
164178

165-
const envConfig = new EnvironmentConfigManager(logger, 'sdkKey', {
179+
const envConfig = getConfigManager(logger, 'sdkKey', {
166180
configPollingIntervalMS: 1000,
167181
configPollingTimeoutMS: 1000,
168182
})
@@ -184,7 +198,7 @@ describe('EnvironmentConfigManager Unit Tests', () => {
184198
mockFetchResponse({ status: 500 }),
185199
)
186200

187-
const envConfig = new EnvironmentConfigManager(logger, 'sdkKey', {})
201+
const envConfig = getConfigManager(logger, 'sdkKey', {})
188202
await expect(envConfig.fetchConfigPromise).rejects.toThrow()
189203
expect(setInterval_mock).toHaveBeenCalledTimes(1)
190204
})

sdk/nodejs/__tests__/request.spec.ts renamed to lib/shared/config-manager/__tests__/request.spec.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,9 @@ import fetch, { Response } from 'cross-fetch'
33

44
global.fetch = fetch
55

6+
import { getEnvironmentConfig } from '../src/request'
67
const fetchRequestMock = fetch as jest.MockedFn<typeof fetch>
78

8-
import { publishEvents, getEnvironmentConfig } from '../src/request'
9-
import { dvcDefaultLogger } from '@devcycle/js-cloud-server-sdk'
10-
const logger = dvcDefaultLogger()
11-
129
describe('request.ts Unit Tests', () => {
1310
beforeEach(() => {
1411
fetchRequestMock.mockReset()
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/* eslint-disable */
2+
export default {
3+
displayName: 'config-manager',
4+
preset: '../../../jest.preset.js',
5+
transform: {
6+
'^.+\\.[tj]s$': [
7+
'ts-jest',
8+
{ tsconfig: '<rootDir>/tsconfig.spec.json' },
9+
],
10+
},
11+
moduleFileExtensions: ['ts', 'js', 'html'],
12+
coverageDirectory: '../../../coverage/lib/shared/config-manager',
13+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "@devcycle/config-manager",
3+
"private": true,
4+
"version": "1.0.0",
5+
"type": "commonjs",
6+
"dependencies": {
7+
"@devcycle/js-cloud-server-sdk": "^1.0.0",
8+
"@devcycle/types": "^1.1.15"
9+
}
10+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
{
2+
"name": "config-manager",
3+
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
4+
"sourceRoot": "lib/shared/config-manager/src",
5+
"projectType": "library",
6+
"targets": {
7+
"build": {
8+
"executor": "@nx/js:tsc",
9+
"outputs": ["{options.outputPath}"],
10+
"options": {
11+
"outputPath": "dist/lib/shared/config-manager",
12+
"main": "lib/shared/config-manager/src/index.ts",
13+
"tsConfig": "lib/shared/config-manager/tsconfig.lib.json",
14+
"assets": ["lib/shared/config-manager/*.md"],
15+
"external": ["shared-types", "js-cloud-server-sdk"]
16+
}
17+
},
18+
"lint": {
19+
"executor": "@nx/linter:eslint",
20+
"outputs": ["{options.outputFile}"],
21+
"options": {
22+
"lintFilePatterns": ["lib/shared/config-manager/**/*.ts"]
23+
}
24+
},
25+
"test": {
26+
"executor": "@nx/jest:jest",
27+
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
28+
"options": {
29+
"jestConfig": "lib/shared/config-manager/jest.config.ts",
30+
"passWithNoTests": true
31+
},
32+
"configurations": {
33+
"ci": {
34+
"ci": true,
35+
"codeCoverage": true
36+
}
37+
}
38+
}
39+
},
40+
"tags": []
41+
}

sdk/nodejs/src/environmentConfigManager.ts renamed to lib/shared/config-manager/src/index.ts

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,25 @@
11
import { DVCLogger } from '@devcycle/types'
2-
import { getBucketingLib } from './bucketing'
3-
import { UserError } from './utils/userError'
2+
// import { UserError } from './utils/userError'
43
import { getEnvironmentConfig } from './request'
54
import { ResponseError, DevCycleOptions } from '@devcycle/js-cloud-server-sdk'
65

76
type ConfigPollingOptions = DevCycleOptions & {
87
cdnURI?: string
98
}
109

10+
type SetIntervalInterface = (handler: () => void, timeout?: number) => any
11+
type ClearIntervalInterface = (intervalTimeout: any) => void
12+
13+
type SetConfigBuffer = (sdkKey: string, projectConfig: string) => void
14+
15+
export class UserError extends Error {
16+
constructor(error: Error | string) {
17+
super(error instanceof Error ? error.message : error)
18+
this.name = 'UserError'
19+
this.stack = error instanceof Error ? error.stack : undefined
20+
}
21+
}
22+
1123
export class EnvironmentConfigManager {
1224
private readonly logger: DVCLogger
1325
private readonly sdkKey: string
@@ -17,12 +29,18 @@ export class EnvironmentConfigManager {
1729
private readonly requestTimeoutMS: number
1830
private readonly cdnURI: string
1931
fetchConfigPromise: Promise<void>
20-
private intervalTimeout?: NodeJS.Timeout
32+
private intervalTimeout?: any
2133
private disablePolling = false
34+
private readonly setConfigBuffer: SetConfigBuffer
35+
private readonly setInterval: SetIntervalInterface
36+
private readonly clearInterval: ClearIntervalInterface
2237

2338
constructor(
2439
logger: DVCLogger,
2540
sdkKey: string,
41+
setConfigBuffer: SetConfigBuffer,
42+
setInterval: SetIntervalInterface,
43+
clearInterval: ClearIntervalInterface,
2644
{
2745
configPollingIntervalMS = 10000,
2846
configPollingTimeoutMS = 5000,
@@ -32,6 +50,11 @@ export class EnvironmentConfigManager {
3250
) {
3351
this.logger = logger
3452
this.sdkKey = sdkKey
53+
54+
this.setConfigBuffer = setConfigBuffer
55+
this.setInterval = setInterval
56+
this.clearInterval = clearInterval
57+
3558
this.pollingIntervalMS =
3659
configPollingIntervalMS >= 1000 ? configPollingIntervalMS : 1000
3760
this.requestTimeoutMS =
@@ -48,19 +71,19 @@ export class EnvironmentConfigManager {
4871
if (this.disablePolling) {
4972
return
5073
}
51-
this.intervalTimeout = setInterval(async () => {
74+
this.intervalTimeout = this.setInterval(async () => {
5275
try {
5376
await this._fetchConfig()
5477
} catch (ex) {
55-
this.logger.error(ex.message)
78+
this.logger.error((ex as Error).message)
5679
}
5780
}, this.pollingIntervalMS)
5881
})
5982
}
6083

6184
stopPolling(): void {
6285
this.disablePolling = true
63-
clearInterval(this.intervalTimeout)
86+
this.clearInterval(this.intervalTimeout)
6487
}
6588

6689
cleanup(): void {
@@ -119,8 +142,7 @@ export class EnvironmentConfigManager {
119142
} else if (res?.status === 200 && projectConfig) {
120143
try {
121144
const etag = res?.headers.get('etag') || ''
122-
const configBuffer = Buffer.from(projectConfig, 'utf8')
123-
getBucketingLib().setConfigDataUTF8(this.sdkKey, configBuffer)
145+
this.setConfigBuffer(this.sdkKey, projectConfig)
124146
this.hasConfig = true
125147
this.configEtag = etag
126148
return

0 commit comments

Comments
 (0)