@stuntman/server 0.1.0 → 0.1.1

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.
@@ -1,23 +0,0 @@
1
- import type { Request } from 'express';
2
- import { v4 as uuidv4 } from 'uuid';
3
-
4
- export default class RequestContext {
5
- static _bindings: WeakMap<Request, RequestContext> = new WeakMap<Request, RequestContext>();
6
-
7
- public readonly mockUuid;
8
- public readonly uuid;
9
-
10
- constructor(mockUuid: string) {
11
- this.uuid = uuidv4();
12
- this.mockUuid = mockUuid;
13
- }
14
-
15
- static bind(req: Request, mockUuid: string): void {
16
- const ctx = new RequestContext(mockUuid);
17
- RequestContext._bindings.set(req, ctx);
18
- }
19
-
20
- static get(req: Request): RequestContext | null {
21
- return RequestContext._bindings.get(req) || null;
22
- }
23
- }
@@ -1,193 +0,0 @@
1
- import AwaitLock from 'await-lock';
2
- import { AppError, DEFAULT_RULE_PRIORITY, HttpCode, logger } from '@stuntman/shared';
3
- import type * as Stuntman from '@stuntman/shared';
4
- import { DEFAULT_RULES } from './rules';
5
-
6
- const rulesLock = new AwaitLock();
7
-
8
- const transformMockRuleToLive = (rule: Stuntman.Rule): Stuntman.LiveRule => {
9
- return {
10
- ...rule,
11
- counter: 0,
12
- isEnabled: rule.isEnabled ?? true,
13
- createdTimestamp: Date.now(),
14
- };
15
- };
16
-
17
- class RuleExecutor {
18
- // TODO persistent rule storage maybe
19
- private _rules: Stuntman.LiveRule[];
20
-
21
- private get enabledRules() {
22
- if (!this._rules) {
23
- return new Array<Stuntman.LiveRule>();
24
- }
25
- const now = Date.now();
26
- return this._rules
27
- .filter((r) => (r.isEnabled && !Number.isFinite(r.ttlSeconds)) || r.createdTimestamp + r.ttlSeconds * 1000 > now)
28
- .sort((a, b) => (a.priority ?? DEFAULT_RULE_PRIORITY) - (b.priority ?? DEFAULT_RULE_PRIORITY));
29
- }
30
-
31
- constructor(rules?: Stuntman.Rule[]) {
32
- this._rules = (rules || []).map(transformMockRuleToLive);
33
- }
34
-
35
- private hasExpired() {
36
- const now = Date.now();
37
- return this._rules.some((r) => Number.isFinite(r.ttlSeconds) && r.createdTimestamp + r.ttlSeconds * 1000 < now);
38
- }
39
-
40
- private async cleanUpExpired() {
41
- if (!this.hasExpired()) {
42
- return;
43
- }
44
- await rulesLock.acquireAsync();
45
- const now = Date.now();
46
- try {
47
- this._rules = this._rules.filter((r) => {
48
- const shouldKeep = !Number.isFinite(r.ttlSeconds) || r.createdTimestamp + r.ttlSeconds * 1000 > now;
49
- if (!shouldKeep) {
50
- logger.debug({ ruleId: r.id }, 'removing expired rule');
51
- }
52
- return shouldKeep;
53
- });
54
- } finally {
55
- await rulesLock.release();
56
- }
57
- }
58
-
59
- async addRule(rule: Stuntman.Rule, overwrite?: boolean): Promise<Stuntman.LiveRule> {
60
- await this.cleanUpExpired();
61
- await rulesLock.acquireAsync();
62
- try {
63
- if (this._rules.some((r) => r.id === rule.id)) {
64
- if (!overwrite) {
65
- throw new AppError({ httpCode: HttpCode.CONFLICT, message: 'rule with given ID already exists' });
66
- }
67
- this._removeRule(rule.id);
68
- }
69
- const liveRule = transformMockRuleToLive(rule);
70
- this._rules.push(liveRule);
71
- logger.debug(liveRule, 'rule added');
72
- return liveRule;
73
- } finally {
74
- await rulesLock.release();
75
- }
76
- }
77
-
78
- private _removeRule(ruleOrId: string | Stuntman.Rule) {
79
- this._rules = this._rules.filter((r) => {
80
- const notFound = r.id !== (typeof ruleOrId === 'string' ? ruleOrId : ruleOrId.id);
81
- if (!notFound) {
82
- logger.debug({ ruleId: r.id }, 'rule removed');
83
- }
84
- return notFound;
85
- });
86
- }
87
-
88
- async removeRule(id: string): Promise<void>;
89
- async removeRule(rule: Stuntman.Rule): Promise<void>;
90
- async removeRule(ruleOrId: string | Stuntman.Rule): Promise<void> {
91
- await this.cleanUpExpired();
92
- await rulesLock.acquireAsync();
93
- try {
94
- this._removeRule(ruleOrId);
95
- } finally {
96
- await rulesLock.release();
97
- }
98
- }
99
-
100
- enableRule(id: string): void;
101
- enableRule(rule: Stuntman.Rule): void;
102
- enableRule(ruleOrId: string | Stuntman.Rule): void {
103
- this._rules.forEach((r) => {
104
- if (r.id === (typeof ruleOrId === 'string' ? ruleOrId : ruleOrId.id)) {
105
- r.isEnabled = true;
106
- logger.debug({ ruleId: r.id }, 'rule enabled');
107
- }
108
- });
109
- }
110
-
111
- disableRule(id: string): void;
112
- disableRule(rule: Stuntman.Rule): void;
113
- disableRule(ruleOrId: string | Stuntman.Rule): void {
114
- this._rules.forEach((r) => {
115
- if (r.id === (typeof ruleOrId === 'string' ? ruleOrId : ruleOrId.id)) {
116
- r.isEnabled = false;
117
- logger.debug({ ruleId: r.id }, 'rule disabled');
118
- }
119
- });
120
- }
121
-
122
- async findMatchingRule(request: Stuntman.Request): Promise<Stuntman.LiveRule | null> {
123
- const logContext: Record<string, any> = {
124
- requestId: request.id,
125
- };
126
- const matchingRule = this.enabledRules.find((rule) => {
127
- const matchResult = rule.matches(request);
128
- if (typeof matchResult === 'boolean') {
129
- return matchResult;
130
- }
131
- return matchResult.result;
132
- });
133
- if (!matchingRule) {
134
- logger.debug(logContext, 'no matching rule found');
135
- return null;
136
- }
137
- const matchResult: Stuntman.RuleMatchResult = matchingRule.matches(request);
138
- logContext.ruleId = matchingRule.id;
139
- logger.debug(logContext, 'matching rule found');
140
- const matchingRuleClone = Object.freeze(Object.assign({}, matchingRule));
141
- ++matchingRule.counter;
142
- logContext.ruleCounter = matchingRule.counter;
143
- if (Number.isNaN(matchingRule.counter) || !Number.isFinite(matchingRule.counter)) {
144
- matchingRule.counter = 0;
145
- logger.warn(logContext, "it's over 9000!!!");
146
- }
147
- if (matchingRule.disableAfterUse) {
148
- if (typeof matchingRule.disableAfterUse === 'boolean' || matchingRule.disableAfterUse <= matchingRule.counter) {
149
- logger.debug(logContext, 'disabling rule for future requests');
150
- this.disableRule(matchingRule);
151
- }
152
- }
153
- if (matchingRule.removeAfterUse) {
154
- if (typeof matchingRule.removeAfterUse === 'boolean' || matchingRule.removeAfterUse <= matchingRule.counter) {
155
- logger.debug(logContext, 'removing rule for future requests');
156
- this.removeRule(matchingRule);
157
- }
158
- }
159
- if (typeof matchResult !== 'boolean') {
160
- if (matchResult.disableRuleIds && matchResult.disableRuleIds.length > 0) {
161
- logger.debug(
162
- { ...logContext, disableRuleIds: matchResult.disableRuleIds },
163
- 'disabling rules based on matchResult'
164
- );
165
- for (const ruleId of matchResult.disableRuleIds) {
166
- this.disableRule(ruleId);
167
- }
168
- }
169
- if (matchResult.enableRuleIds && matchResult.enableRuleIds.length > 0) {
170
- logger.debug(
171
- { ...logContext, disableRuleIds: matchResult.disableRuleIds },
172
- 'enabling rules based on matchResult'
173
- );
174
- for (const ruleId of matchResult.enableRuleIds) {
175
- this.enableRule(ruleId);
176
- }
177
- }
178
- }
179
- return matchingRuleClone;
180
- }
181
-
182
- async getRules(): Promise<readonly Stuntman.LiveRule[]> {
183
- await this.cleanUpExpired();
184
- return this._rules;
185
- }
186
-
187
- async getRule(id: string): Promise<Stuntman.LiveRule | undefined> {
188
- await this.cleanUpExpired();
189
- return this._rules.find((r) => r.id === id);
190
- }
191
- }
192
-
193
- export const ruleExecutor = new RuleExecutor(DEFAULT_RULES.map((r) => ({ ...r, ttlSeconds: Infinity })));
@@ -1,14 +0,0 @@
1
- import { CATCH_ALL_RULE_PRIORITY, CATCH_RULE_NAME } from '@stuntman/shared';
2
- import type * as Stuntman from '@stuntman/shared';
3
-
4
- export const catchAllRule: Stuntman.DeployedRule = {
5
- id: CATCH_RULE_NAME,
6
- matches: () => true,
7
- priority: CATCH_ALL_RULE_PRIORITY,
8
- actions: {
9
- mockResponse: (req: Stuntman.Request) => ({
10
- body: `Request received by Stuntman mock <pre>${JSON.stringify(req, null, 4)}</pre>`,
11
- status: 200,
12
- }),
13
- },
14
- };
package/src/rules/echo.ts DELETED
@@ -1,14 +0,0 @@
1
- import { DEFAULT_RULE_PRIORITY } from '@stuntman/shared';
2
- import type * as Stuntman from '@stuntman/shared';
3
-
4
- export const echoRule: Stuntman.DeployedRule = {
5
- id: 'internal/echo',
6
- priority: DEFAULT_RULE_PRIORITY + 1,
7
- matches: (req: Stuntman.Request) => /https?:\/\/echo\/.*/.test(req.url),
8
- actions: {
9
- mockResponse: (req: Stuntman.Request) => ({
10
- body: req,
11
- status: 200,
12
- }),
13
- },
14
- };
@@ -1,7 +0,0 @@
1
- import { catchAllRule } from './catchAll';
2
- import { echoRule } from './echo';
3
- import type * as Stuntman from '@stuntman/shared';
4
-
5
- // TODO add option to load rules additional default rules from some nice configurable folder
6
-
7
- export const DEFAULT_RULES: Stuntman.DeployedRule[] = [catchAllRule, echoRule];
package/src/storage.ts DELETED
@@ -1,39 +0,0 @@
1
- import LRUCache from 'lru-cache';
2
- import type * as Stuntman from '@stuntman/shared';
3
- import sizeof from 'object-sizeof';
4
-
5
- const DNS_CACHE_OPTIONS: LRUCache.Options<string, string> = {
6
- max: 1000,
7
- ttl: 1000 * 60 * 15,
8
- allowStale: false,
9
- updateAgeOnGet: false,
10
- updateAgeOnHas: false,
11
- };
12
-
13
- const trafficStoreInstances: Record<string, LRUCache<string, Stuntman.LogEntry>> = {};
14
- const dnsResolutionCacheInstances: Record<string, LRUCache<string, string>> = {};
15
-
16
- export const getTrafficStore = (key: string, options?: Stuntman.StorageConfig) => {
17
- if (!(key in trafficStoreInstances)) {
18
- if (!options) {
19
- throw new Error('initialize with options first');
20
- }
21
- trafficStoreInstances[key] = new LRUCache<string, Stuntman.LogEntry>({
22
- max: options.limitCount,
23
- maxSize: options.limitSize,
24
- ttl: options.ttl,
25
- allowStale: false,
26
- updateAgeOnGet: false,
27
- updateAgeOnHas: false,
28
- sizeCalculation: (value) => sizeof(value),
29
- });
30
- }
31
- return trafficStoreInstances[key];
32
- };
33
-
34
- export const getDnsResolutionCache = (key: string) => {
35
- if (!(key in dnsResolutionCacheInstances)) {
36
- dnsResolutionCacheInstances[key] = new LRUCache<string, string>(DNS_CACHE_OPTIONS);
37
- }
38
- return dnsResolutionCacheInstances[key];
39
- };
package/tsconfig.json DELETED
@@ -1,16 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2018",
4
- "module": "commonjs",
5
- "rootDir": "src",
6
- "declaration": true,
7
- "outDir": "dist",
8
- "esModuleInterop": true,
9
- "forceConsistentCasingInFileNames": true,
10
- "strict": true,
11
- "skipLibCheck": true,
12
- "typeRoots": ["node_modules/@types"],
13
- "allowJs": true,
14
- "moduleResolution": "node16"
15
- }
16
- }