readiness-manager 1.2.0-rc.xf50ab1c7f → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -83
- package/dist/src/error.d.ts.map +1 -1
- package/dist/src/error.js.map +1 -1
- package/dist/src/index.d.ts +1 -5
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +63 -57
- package/dist/src/index.js.map +1 -1
- package/dist/src/types.d.ts +17 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +3 -0
- package/dist/src/types.js.map +1 -0
- package/dist/tests/mock.d.ts.map +1 -1
- package/dist/tests/mock.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/error.ts +8 -0
- package/src/index.ts +54 -68
- package/src/spec.ts +10 -17
- package/src/types.ts +38 -0
- package/tests/mock.ts +3 -2
- package/dist/src/constants.d.ts +0 -3
- package/dist/src/constants.d.ts.map +0 -1
- package/dist/src/constants.js +0 -10
- package/dist/src/constants.js.map +0 -1
- package/src/constants.ts +0 -11
package/package.json
CHANGED
package/src/error.ts
CHANGED
|
@@ -9,7 +9,15 @@ export const ERRORS = {
|
|
|
9
9
|
BEACON_EXECUTION_FAILED: 'Beacon execution failed',
|
|
10
10
|
} as const;
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* The retry function to be triggered upon beacon errors.
|
|
14
|
+
* @returns {Promise<unknown>}
|
|
15
|
+
*/
|
|
12
16
|
export type ActionRetry = () => Promise<unknown>;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The error handler to be triggered upon beacon errors.
|
|
20
|
+
*/
|
|
13
21
|
export type ActionErrorHandler = (error: ActionExecutionError, retry: ActionRetry) => void;
|
|
14
22
|
|
|
15
23
|
/**
|
package/src/index.ts
CHANGED
|
@@ -1,35 +1,6 @@
|
|
|
1
1
|
import { logger } from '@fiverr-private/obs';
|
|
2
2
|
import { ERRORS, ActionExecutionError, defaultErrorHandler, ActionErrorHandler } from './error';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
export type ReadyCallback = () => void;
|
|
6
|
-
export type ReadyAction = () => unknown | Promise<unknown>;
|
|
7
|
-
|
|
8
|
-
export interface ActionsStatus {
|
|
9
|
-
[status: string]: string[];
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface Beacon {
|
|
13
|
-
name: string;
|
|
14
|
-
action: ReadyAction;
|
|
15
|
-
status: ActionStatus;
|
|
16
|
-
callbacks: ReadyCallback[];
|
|
17
|
-
debug?: boolean;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface BeaconsMap {
|
|
21
|
-
[name: string]: Beacon;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Sets of private attributes which should not be exposed / accessed by consumer.
|
|
26
|
-
*/
|
|
27
|
-
const beaconsSymbol = Symbol('beacons');
|
|
28
|
-
const callbacksSymbol = Symbol('callbacks');
|
|
29
|
-
const executeSymbol = Symbol('execute');
|
|
30
|
-
const trackBeaconUpdateSymbol = Symbol('trackBeaconUpdate');
|
|
31
|
-
const updateBeaconSymbol = Symbol('updateBeacon');
|
|
32
|
-
const errorHandlerSymbol = Symbol('errorHandler');
|
|
3
|
+
import type { ActionStatus, ReadyAction, ReadyCallback, ActionsStatus, BeaconsMap, Beacon } from './types';
|
|
33
4
|
|
|
34
5
|
/**
|
|
35
6
|
* The internal "ready" state to determine process readiness.
|
|
@@ -46,21 +17,29 @@ export interface ReadinessManager {
|
|
|
46
17
|
ready: boolean;
|
|
47
18
|
}
|
|
48
19
|
|
|
20
|
+
/**
|
|
21
|
+
* The internal implementation of the ReadinessManager.
|
|
22
|
+
* @example
|
|
23
|
+
* const readinessManager = new ReadinessManager();
|
|
24
|
+
* readinessManager.register('database', () => connectToDB());
|
|
25
|
+
* readinessManager.register('redis', () => connectToRedis());
|
|
26
|
+
* readinessManager.run();
|
|
27
|
+
* readinessManager.onReady(() => console.log('App is ready! o/'));
|
|
28
|
+
* @returns {ReadinessManager}
|
|
29
|
+
*/
|
|
49
30
|
class ReadinessManagerImpl implements ReadinessManager {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
31
|
+
#beacons: BeaconsMap;
|
|
32
|
+
#callbacks: ReadyCallback[];
|
|
33
|
+
#errorHandler: ActionErrorHandler;
|
|
53
34
|
|
|
54
35
|
constructor() {
|
|
55
|
-
this
|
|
56
|
-
this
|
|
57
|
-
this
|
|
58
|
-
|
|
59
|
-
this[executeSymbol] = this[executeSymbol].bind(this);
|
|
36
|
+
this.#beacons = {};
|
|
37
|
+
this.#callbacks = [];
|
|
38
|
+
this.#errorHandler = defaultErrorHandler;
|
|
60
39
|
}
|
|
61
40
|
|
|
62
41
|
/**
|
|
63
|
-
* Returns true if
|
|
42
|
+
* Returns `true` if all the registered actions has been ran without exceptions/rejections.
|
|
64
43
|
*/
|
|
65
44
|
get ready(): boolean {
|
|
66
45
|
return readyState;
|
|
@@ -70,7 +49,7 @@ class ReadinessManagerImpl implements ReadinessManager {
|
|
|
70
49
|
* Returns current registered actions status
|
|
71
50
|
*/
|
|
72
51
|
status(): ActionsStatus {
|
|
73
|
-
return Object.values(this
|
|
52
|
+
return Object.values(this.#beacons).reduce((acc, { name, status }) => {
|
|
74
53
|
const current = acc[status] || [];
|
|
75
54
|
return Object.assign(acc, { [status]: [...current, name] });
|
|
76
55
|
}, {} as ActionsStatus);
|
|
@@ -78,16 +57,18 @@ class ReadinessManagerImpl implements ReadinessManager {
|
|
|
78
57
|
|
|
79
58
|
/**
|
|
80
59
|
* Registers a given name and action as beacon under manager.
|
|
60
|
+
* registered actions will be observed once ran in order to determine the process readiness state.
|
|
81
61
|
* @param name - The operation name to register with.
|
|
82
62
|
* @param action - The ready emitter to condition the process readiness with.
|
|
83
|
-
* @param debug - Optional flag to enable
|
|
63
|
+
* @param debug - (Optional) flag to enable timing logs (default: `false`).
|
|
64
|
+
* @throws {Error} If the action name is already registered.
|
|
84
65
|
*/
|
|
85
66
|
register(name: string, action: ReadyAction, debug = false): void {
|
|
86
67
|
if (this.ready) {
|
|
87
68
|
return;
|
|
88
69
|
}
|
|
89
70
|
|
|
90
|
-
if (this[
|
|
71
|
+
if (this.#beacons[name]) {
|
|
91
72
|
throw new Error(ERRORS.BEACON_ALREADY_EXISTS);
|
|
92
73
|
}
|
|
93
74
|
|
|
@@ -95,27 +76,30 @@ class ReadinessManagerImpl implements ReadinessManager {
|
|
|
95
76
|
name,
|
|
96
77
|
action,
|
|
97
78
|
callbacks: [],
|
|
98
|
-
status:
|
|
79
|
+
status: 'not_started',
|
|
99
80
|
debug,
|
|
100
81
|
};
|
|
101
82
|
|
|
102
|
-
Object.assign(this
|
|
83
|
+
Object.assign(this.#beacons, { [name]: beacon });
|
|
103
84
|
}
|
|
104
85
|
|
|
105
86
|
/**
|
|
106
|
-
*
|
|
87
|
+
*
|
|
88
|
+
* Runs all registered actions. Once all actions are resolved successfully, your app state will be determined as `ready`.
|
|
89
|
+
* @returns {ReadinessManager}
|
|
107
90
|
*/
|
|
108
91
|
run(): ReadinessManager {
|
|
109
92
|
readyState = false;
|
|
110
93
|
|
|
111
|
-
Object.values(this
|
|
94
|
+
Object.values(this.#beacons).forEach((beacon) => this.#execute(beacon));
|
|
112
95
|
|
|
113
96
|
return this;
|
|
114
97
|
}
|
|
115
98
|
|
|
116
99
|
/**
|
|
117
|
-
* Registers a given callback
|
|
118
|
-
* @param callback
|
|
100
|
+
* Registers a given callback on the global manager ready event.
|
|
101
|
+
* @param callback - The callback to run upon manager ready event.
|
|
102
|
+
* @returns {ReadinessManager}
|
|
119
103
|
*/
|
|
120
104
|
onReady(callback: ReadyCallback): ReadinessManager {
|
|
121
105
|
if (readyState) {
|
|
@@ -124,7 +108,7 @@ class ReadinessManagerImpl implements ReadinessManager {
|
|
|
124
108
|
return this;
|
|
125
109
|
}
|
|
126
110
|
|
|
127
|
-
this
|
|
111
|
+
this.#callbacks.push(callback);
|
|
128
112
|
return this;
|
|
129
113
|
}
|
|
130
114
|
|
|
@@ -132,20 +116,22 @@ class ReadinessManagerImpl implements ReadinessManager {
|
|
|
132
116
|
* Registers a given callback on a specific action resolved.
|
|
133
117
|
* @param name - The name of the action the callback should be attached to.
|
|
134
118
|
* @param callback - The callback to run upon beacon resolved.
|
|
119
|
+
* @returns {ReadinessManager}
|
|
120
|
+
* @throws {Error} If the action name does not exists.
|
|
135
121
|
*/
|
|
136
122
|
onActionReady(name: string, callback: ReadyCallback): ReadinessManager {
|
|
137
|
-
const beacon = this[
|
|
123
|
+
const beacon = this.#beacons[name];
|
|
138
124
|
if (!beacon) {
|
|
139
125
|
throw new Error(ERRORS.BEACON_DOES_NOT_EXISTS);
|
|
140
126
|
}
|
|
141
127
|
|
|
142
|
-
if (beacon.status ===
|
|
128
|
+
if (beacon.status === 'resolved') {
|
|
143
129
|
// Invokes immediately given callback if beacon status is already resolved.
|
|
144
130
|
callback();
|
|
145
131
|
return this;
|
|
146
132
|
}
|
|
147
133
|
|
|
148
|
-
this[
|
|
134
|
+
this.#beacons[name].callbacks.push(callback);
|
|
149
135
|
return this;
|
|
150
136
|
}
|
|
151
137
|
|
|
@@ -154,7 +140,7 @@ class ReadinessManagerImpl implements ReadinessManager {
|
|
|
154
140
|
* @param errorHandler - The handler to trigger upon action errors.
|
|
155
141
|
*/
|
|
156
142
|
onError(errorHandler: ActionErrorHandler): ReadinessManager {
|
|
157
|
-
this
|
|
143
|
+
this.#errorHandler = errorHandler;
|
|
158
144
|
return this;
|
|
159
145
|
}
|
|
160
146
|
|
|
@@ -165,12 +151,12 @@ class ReadinessManagerImpl implements ReadinessManager {
|
|
|
165
151
|
* @throws {ActionExecutionError}
|
|
166
152
|
* @private
|
|
167
153
|
*/
|
|
168
|
-
|
|
154
|
+
async #execute(beacon: Beacon, attempt = 1): Promise<void> {
|
|
169
155
|
const { name, action, debug } = beacon;
|
|
170
156
|
|
|
171
|
-
const update = (status: ActionStatus) => this
|
|
157
|
+
const update = (status: ActionStatus) => this.#updateBeacon(name, status);
|
|
172
158
|
|
|
173
|
-
update(
|
|
159
|
+
update('pending');
|
|
174
160
|
|
|
175
161
|
const startTime = debug ? Date.now() : null;
|
|
176
162
|
|
|
@@ -187,18 +173,18 @@ class ReadinessManagerImpl implements ReadinessManager {
|
|
|
187
173
|
logger.debug(`[ReadinessManager] Action "${name}" completed in ${duration}ms`);
|
|
188
174
|
}
|
|
189
175
|
|
|
190
|
-
update(
|
|
176
|
+
update('resolved');
|
|
191
177
|
} catch (error) {
|
|
192
178
|
if (debug && startTime !== null) {
|
|
193
179
|
const duration = Date.now() - startTime;
|
|
194
180
|
logger.debug(`[ReadinessManager] Action "${name}" failed after ${duration}ms`);
|
|
195
181
|
}
|
|
196
182
|
|
|
197
|
-
update(
|
|
183
|
+
update('rejected');
|
|
198
184
|
|
|
199
185
|
// Invokes consumer error hook with a retry method and the beacon error.
|
|
200
|
-
this
|
|
201
|
-
this
|
|
186
|
+
this.#errorHandler(new ActionExecutionError(name, attempt, error as Error), () =>
|
|
187
|
+
this.#execute(beacon, attempt + 1)
|
|
202
188
|
);
|
|
203
189
|
}
|
|
204
190
|
}
|
|
@@ -209,15 +195,15 @@ class ReadinessManagerImpl implements ReadinessManager {
|
|
|
209
195
|
* @param status - The beacon status to set.
|
|
210
196
|
* @private
|
|
211
197
|
*/
|
|
212
|
-
|
|
213
|
-
Object.assign(this[
|
|
198
|
+
#updateBeacon(name: string, status: ActionStatus): void {
|
|
199
|
+
Object.assign(this.#beacons[name], { status });
|
|
214
200
|
|
|
215
201
|
// Resolves specific beacon ready listeners.
|
|
216
|
-
if (status ===
|
|
217
|
-
this[
|
|
202
|
+
if (status === 'resolved') {
|
|
203
|
+
this.#beacons[name].callbacks.forEach((callback) => callback());
|
|
218
204
|
}
|
|
219
205
|
|
|
220
|
-
this
|
|
206
|
+
this.#trackBeaconUpdate();
|
|
221
207
|
}
|
|
222
208
|
|
|
223
209
|
/**
|
|
@@ -225,15 +211,15 @@ class ReadinessManagerImpl implements ReadinessManager {
|
|
|
225
211
|
* If so, will trigger registered `onReady` callbacks.
|
|
226
212
|
* @private
|
|
227
213
|
*/
|
|
228
|
-
|
|
214
|
+
#trackBeaconUpdate(): void {
|
|
229
215
|
if (readyState) {
|
|
230
216
|
return;
|
|
231
217
|
}
|
|
232
218
|
|
|
233
|
-
readyState = Object.values(this
|
|
219
|
+
readyState = Object.values(this.#beacons).every((beacon) => beacon.status === 'resolved');
|
|
234
220
|
|
|
235
221
|
if (readyState) {
|
|
236
|
-
this
|
|
222
|
+
this.#callbacks.forEach((callback) => callback());
|
|
237
223
|
}
|
|
238
224
|
}
|
|
239
225
|
}
|
package/src/spec.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { BeaconStatus } from './constants';
|
|
2
1
|
import { ERRORS, ActionExecutionError } from './error';
|
|
3
2
|
import { ReadinessManager } from './index';
|
|
4
3
|
|
|
@@ -8,12 +7,6 @@ import { ReadinessManager } from './index';
|
|
|
8
7
|
*/
|
|
9
8
|
const wait = (time = 0): Promise<unknown> => new Promise((resolve) => setTimeout(resolve, time));
|
|
10
9
|
|
|
11
|
-
/**
|
|
12
|
-
* Delays a promise resolve with a given time.
|
|
13
|
-
* @param time - The time to wait.
|
|
14
|
-
*/
|
|
15
|
-
const delayResolve = (time = 100): Promise<unknown> => new Promise((resolve) => setTimeout(resolve, time));
|
|
16
|
-
|
|
17
10
|
describe('lib/readinessManager', () => {
|
|
18
11
|
let readyManager: ReadinessManager;
|
|
19
12
|
|
|
@@ -59,8 +52,8 @@ describe('lib/readinessManager', () => {
|
|
|
59
52
|
|
|
60
53
|
const status = readyManager.status();
|
|
61
54
|
|
|
62
|
-
expect(status
|
|
63
|
-
expect(status
|
|
55
|
+
expect(status.resolved).toEqual(['success', 'other']);
|
|
56
|
+
expect(status.rejected).toEqual(['fail']);
|
|
64
57
|
});
|
|
65
58
|
});
|
|
66
59
|
});
|
|
@@ -70,7 +63,7 @@ describe('lib/readinessManager', () => {
|
|
|
70
63
|
it('Registers action and name as beacon with not_started status', () => {
|
|
71
64
|
readyManager.register('action', () => true);
|
|
72
65
|
|
|
73
|
-
expect(readyManager.status()
|
|
66
|
+
expect(readyManager.status().not_started).toHaveLength(1);
|
|
74
67
|
});
|
|
75
68
|
});
|
|
76
69
|
|
|
@@ -85,18 +78,18 @@ describe('lib/readinessManager', () => {
|
|
|
85
78
|
|
|
86
79
|
describe('run', () => {
|
|
87
80
|
beforeEach(() => {
|
|
88
|
-
readyManager.register('action1',
|
|
89
|
-
readyManager.register('action2',
|
|
81
|
+
readyManager.register('action1', wait);
|
|
82
|
+
readyManager.register('action2', wait);
|
|
90
83
|
});
|
|
91
84
|
|
|
92
85
|
it('Executes all given actions', async () => {
|
|
93
|
-
expect(readyManager.status()
|
|
86
|
+
expect(readyManager.status().not_started).toHaveLength(2);
|
|
94
87
|
|
|
95
88
|
readyManager.run();
|
|
96
89
|
|
|
97
|
-
expect(readyManager.status()
|
|
90
|
+
expect(readyManager.status().not_started).toBeUndefined();
|
|
98
91
|
|
|
99
|
-
expect(readyManager.status()
|
|
92
|
+
expect(readyManager.status().pending).toHaveLength(2);
|
|
100
93
|
});
|
|
101
94
|
});
|
|
102
95
|
|
|
@@ -176,8 +169,8 @@ describe('lib/readinessManager', () => {
|
|
|
176
169
|
describe('When action is not ready', () => {
|
|
177
170
|
it('Triggers callback once desired aciton is ready', async () => {
|
|
178
171
|
readyManager.register('some', () => true);
|
|
179
|
-
readyManager.register('thing', () =>
|
|
180
|
-
readyManager.register('other', () =>
|
|
172
|
+
readyManager.register('thing', () => wait(500));
|
|
173
|
+
readyManager.register('other', () => wait(1000));
|
|
181
174
|
|
|
182
175
|
readyManager.onActionReady('thing', callback);
|
|
183
176
|
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A status of an action.
|
|
3
|
+
*/
|
|
4
|
+
export type ActionStatus = 'not_started' | 'pending' | 'resolved' | 'rejected';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A callback to be executed when an action is ready.
|
|
8
|
+
*/
|
|
9
|
+
export type ReadyCallback = () => void;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* An action is a single function that needs to be executed to determine if the app is ready.
|
|
13
|
+
*/
|
|
14
|
+
export type ReadyAction = () => unknown | Promise<unknown>;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* A map of action statuses by their name.
|
|
18
|
+
*/
|
|
19
|
+
export type ActionsStatus = {
|
|
20
|
+
[key in ActionStatus]?: string[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A beacon is a single action that needs to be executed to determine if the app is ready.
|
|
25
|
+
*/
|
|
26
|
+
export interface Beacon {
|
|
27
|
+
name: string;
|
|
28
|
+
action: ReadyAction;
|
|
29
|
+
status: ActionStatus;
|
|
30
|
+
callbacks: ReadyCallback[];
|
|
31
|
+
debug?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* A map of beacons by their name.
|
|
35
|
+
*/
|
|
36
|
+
export interface BeaconsMap {
|
|
37
|
+
[name: string]: Beacon;
|
|
38
|
+
}
|
package/tests/mock.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { ReadinessManager
|
|
2
|
-
import {
|
|
1
|
+
import { ReadinessManager } from '../src/index';
|
|
2
|
+
import type { ReadyCallback, ReadyAction, ActionsStatus } from '../src/types';
|
|
3
|
+
import type { ActionErrorHandler } from '../src/error';
|
|
3
4
|
|
|
4
5
|
export interface ReadinessManagerMock extends ReadinessManager {
|
|
5
6
|
setReady: (isReady: boolean) => void;
|
package/dist/src/constants.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,aAAa,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,CAAC;AAK/E,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAK5C,CAAC"}
|
package/dist/src/constants.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.BeaconStatus = void 0;
|
|
4
|
-
exports.BeaconStatus = {
|
|
5
|
-
NOT_STARTED: 'not_started',
|
|
6
|
-
PENDING: 'pending',
|
|
7
|
-
RESOLVED: 'resolved',
|
|
8
|
-
REJECTED: 'rejected',
|
|
9
|
-
};
|
|
10
|
-
//# sourceMappingURL=constants.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":";;;AAKa,QAAA,YAAY,GAAiC;IACtD,WAAW,EAAE,aAAa;IAC1B,OAAO,EAAE,SAAS;IAClB,QAAQ,EAAE,UAAU;IACpB,QAAQ,EAAE,UAAU;CACd,CAAC"}
|
package/src/constants.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export type ActionStatus = 'not_started' | 'pending' | 'resolved' | 'rejected';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The possible beacon status.
|
|
5
|
-
*/
|
|
6
|
-
export const BeaconStatus: Record<string, ActionStatus> = {
|
|
7
|
-
NOT_STARTED: 'not_started',
|
|
8
|
-
PENDING: 'pending',
|
|
9
|
-
RESOLVED: 'resolved',
|
|
10
|
-
REJECTED: 'rejected',
|
|
11
|
-
} as const;
|