@scenarist/core 0.2.1 → 0.3.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.
- package/dist/adapters/console-logger.d.ts +40 -0
- package/dist/adapters/console-logger.d.ts.map +1 -0
- package/dist/adapters/console-logger.js +193 -0
- package/dist/adapters/in-memory-state-manager.d.ts.map +1 -1
- package/dist/adapters/in-memory-state-manager.js +3 -0
- package/dist/adapters/index.d.ts +2 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +2 -0
- package/dist/adapters/noop-logger.d.ts +26 -0
- package/dist/adapters/noop-logger.d.ts.map +1 -0
- package/dist/adapters/noop-logger.js +26 -0
- package/dist/contracts/framework-adapter.d.ts +18 -0
- package/dist/contracts/framework-adapter.d.ts.map +1 -1
- package/dist/domain/config-builder.d.ts.map +1 -1
- package/dist/domain/config-builder.js +13 -0
- package/dist/domain/deep-equals.d.ts.map +1 -1
- package/dist/domain/deep-equals.js +2 -0
- package/dist/domain/index.d.ts +1 -0
- package/dist/domain/index.d.ts.map +1 -1
- package/dist/domain/index.js +1 -0
- package/dist/domain/log-events.d.ts +71 -0
- package/dist/domain/log-events.d.ts.map +1 -0
- package/dist/domain/log-events.js +74 -0
- package/dist/domain/path-extraction.js +1 -0
- package/dist/domain/regex-matching.d.ts.map +1 -1
- package/dist/domain/regex-matching.js +1 -0
- package/dist/domain/response-selector.d.ts +2 -1
- package/dist/domain/response-selector.d.ts.map +1 -1
- package/dist/domain/response-selector.js +120 -13
- package/dist/domain/scenario-manager.d.ts +4 -2
- package/dist/domain/scenario-manager.d.ts.map +1 -1
- package/dist/domain/scenario-manager.js +42 -24
- package/dist/domain/state-condition-evaluator.js +1 -0
- package/dist/domain/template-replacement.d.ts.map +1 -1
- package/dist/domain/template-replacement.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/ports/driven/logger.d.ts +119 -0
- package/dist/ports/driven/logger.d.ts.map +1 -0
- package/dist/ports/driven/logger.js +1 -0
- package/dist/ports/driven/response-selector.d.ts +3 -1
- package/dist/ports/driven/response-selector.d.ts.map +1 -1
- package/dist/ports/driven/response-selector.js +1 -0
- package/dist/ports/index.d.ts +1 -0
- package/dist/ports/index.d.ts.map +1 -1
- package/dist/types/config.d.ts +29 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/errors.d.ts +49 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/errors.js +26 -0
- package/dist/types/index.d.ts +3 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +2 -1
- package/package.json +3 -3
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ScenaristError, ErrorCodes } from "../types/errors.js";
|
|
2
2
|
import { extractFromPath } from "./path-extraction.js";
|
|
3
3
|
import { applyTemplates } from "./template-replacement.js";
|
|
4
4
|
import { matchesRegex } from "./regex-matching.js";
|
|
5
5
|
import { createStateResponseResolver } from "./state-response-resolver.js";
|
|
6
6
|
import { deepEquals } from "./deep-equals.js";
|
|
7
|
+
import { noOpLogger } from "../adapters/index.js";
|
|
8
|
+
import { LogCategories, LogEvents } from "./log-events.js";
|
|
7
9
|
const SPECIFICITY_RANGES = {
|
|
8
10
|
MATCH_CRITERIA_BASE: 100,
|
|
9
11
|
SEQUENCE_FALLBACK: 1,
|
|
@@ -21,26 +23,41 @@ const SPECIFICITY_RANGES = {
|
|
|
21
23
|
* @param options.stateManager - Optional state manager for capture/injection (Phase 3)
|
|
22
24
|
*/
|
|
23
25
|
export const createResponseSelector = (options = {}) => {
|
|
24
|
-
const { sequenceTracker, stateManager } = options;
|
|
26
|
+
const { sequenceTracker, stateManager, logger = noOpLogger } = options;
|
|
25
27
|
return {
|
|
26
28
|
selectResponse(testId, scenarioId, context, mocks) {
|
|
29
|
+
const logContext = { testId, scenarioId };
|
|
30
|
+
// Log the number of candidate mocks
|
|
31
|
+
logger.debug(LogCategories.MATCHING, LogEvents.MOCK_CANDIDATES_FOUND, logContext, {
|
|
32
|
+
count: mocks.length,
|
|
33
|
+
});
|
|
27
34
|
let bestMatch = null;
|
|
35
|
+
// Track if we skipped any exhausted sequences (for better error messages)
|
|
36
|
+
let skippedExhaustedSequences = false;
|
|
28
37
|
// Find all matching mocks and score them by specificity
|
|
29
38
|
for (let mockIndex = 0; mockIndex < mocks.length; mockIndex++) {
|
|
30
|
-
//
|
|
39
|
+
// eslint-disable-next-line security/detect-object-injection -- Index bounded by loop (0 <= i < length)
|
|
31
40
|
const mockWithParams = mocks[mockIndex];
|
|
32
41
|
const mock = mockWithParams.mock;
|
|
33
42
|
// Skip exhausted sequences (repeat: 'none' that have been exhausted)
|
|
34
43
|
if (mock.sequence && sequenceTracker) {
|
|
35
44
|
const { exhausted } = sequenceTracker.getPosition(testId, scenarioId, mockIndex);
|
|
36
45
|
if (exhausted) {
|
|
46
|
+
skippedExhaustedSequences = true;
|
|
37
47
|
continue; // Skip to next mock, allowing fallback to be selected
|
|
38
48
|
}
|
|
39
49
|
}
|
|
40
50
|
// Check if this mock has match criteria
|
|
41
51
|
if (mock.match) {
|
|
42
52
|
// If match criteria exists, check if it matches the request
|
|
43
|
-
|
|
53
|
+
const matched = matchesCriteria(context, mock.match, testId, stateManager);
|
|
54
|
+
// Log the evaluation result
|
|
55
|
+
logger.debug(LogCategories.MATCHING, LogEvents.MOCK_MATCH_EVALUATED, logContext, {
|
|
56
|
+
mockIndex,
|
|
57
|
+
matched,
|
|
58
|
+
hasCriteria: true,
|
|
59
|
+
});
|
|
60
|
+
if (matched) {
|
|
44
61
|
// Match criteria always have higher priority than fallbacks
|
|
45
62
|
// Base specificity ensures even 1 field beats any fallback
|
|
46
63
|
const specificity = SPECIFICITY_RANGES.MATCH_CRITERIA_BASE +
|
|
@@ -55,6 +72,15 @@ export const createResponseSelector = (options = {}) => {
|
|
|
55
72
|
continue;
|
|
56
73
|
}
|
|
57
74
|
// No match criteria = fallback mock (always matches)
|
|
75
|
+
// Log fallback evaluation with response type info for debugging Issue #328
|
|
76
|
+
logger.debug(LogCategories.MATCHING, LogEvents.MOCK_MATCH_EVALUATED, logContext, {
|
|
77
|
+
mockIndex,
|
|
78
|
+
matched: true,
|
|
79
|
+
hasCriteria: false,
|
|
80
|
+
hasSequence: !!mock.sequence,
|
|
81
|
+
hasStateResponse: !!mock.stateResponse,
|
|
82
|
+
hasResponse: !!mock.response,
|
|
83
|
+
});
|
|
58
84
|
// Dynamic response types (sequence, stateResponse) get higher priority than simple responses
|
|
59
85
|
// This ensures they are selected over simple fallback responses
|
|
60
86
|
// Both sequence and stateResponse get the same specificity (Issue #316 fix)
|
|
@@ -74,14 +100,29 @@ export const createResponseSelector = (options = {}) => {
|
|
|
74
100
|
}
|
|
75
101
|
// Return the best matching mock
|
|
76
102
|
if (bestMatch) {
|
|
77
|
-
const { mockWithParams, mockIndex } = bestMatch;
|
|
103
|
+
const { mockWithParams, mockIndex, specificity } = bestMatch;
|
|
78
104
|
const mock = mockWithParams.mock;
|
|
105
|
+
// Log successful selection
|
|
106
|
+
logger.info(LogCategories.MATCHING, LogEvents.MOCK_SELECTED, logContext, {
|
|
107
|
+
mockIndex,
|
|
108
|
+
specificity,
|
|
109
|
+
});
|
|
79
110
|
// Select response (single, sequence, or stateResponse)
|
|
80
|
-
const response = selectResponseFromMock(testId, scenarioId, mockIndex, mock, sequenceTracker, stateManager);
|
|
111
|
+
const response = selectResponseFromMock(testId, scenarioId, mockIndex, mock, sequenceTracker, stateManager, logger);
|
|
81
112
|
if (!response) {
|
|
82
113
|
return {
|
|
83
114
|
success: false,
|
|
84
|
-
error: new
|
|
115
|
+
error: new ScenaristError(`Mock has neither response nor sequence field`, {
|
|
116
|
+
code: ErrorCodes.VALIDATION_ERROR,
|
|
117
|
+
context: {
|
|
118
|
+
testId,
|
|
119
|
+
scenarioId,
|
|
120
|
+
mockInfo: {
|
|
121
|
+
index: mockIndex,
|
|
122
|
+
},
|
|
123
|
+
hint: "Each mock must have a 'response', 'sequence', or 'stateResponse' field.",
|
|
124
|
+
},
|
|
125
|
+
}),
|
|
85
126
|
};
|
|
86
127
|
}
|
|
87
128
|
// Phase 3: Capture state from request if configured
|
|
@@ -103,13 +144,54 @@ export const createResponseSelector = (options = {}) => {
|
|
|
103
144
|
// Apply afterResponse.setState to mutate state for subsequent requests
|
|
104
145
|
if (mock.afterResponse?.setState && stateManager) {
|
|
105
146
|
stateManager.merge(testId, mock.afterResponse.setState);
|
|
147
|
+
logger.debug(LogCategories.STATE, LogEvents.STATE_SET, logContext, {
|
|
148
|
+
setState: mock.afterResponse.setState,
|
|
149
|
+
});
|
|
106
150
|
}
|
|
107
151
|
return { success: true, data: finalResponse };
|
|
108
152
|
}
|
|
109
|
-
// No mock matched
|
|
153
|
+
// No mock matched - determine specific error type
|
|
154
|
+
if (skippedExhaustedSequences) {
|
|
155
|
+
// All matching mocks were exhausted sequences
|
|
156
|
+
logger.warn(LogCategories.SEQUENCE, LogEvents.SEQUENCE_EXHAUSTED, logContext, {
|
|
157
|
+
url: context.url,
|
|
158
|
+
method: context.method,
|
|
159
|
+
});
|
|
160
|
+
return {
|
|
161
|
+
success: false,
|
|
162
|
+
error: new ScenaristError(`Sequence exhausted for ${context.method} ${context.url}. All responses have been consumed and repeat mode is 'none'.`, {
|
|
163
|
+
code: ErrorCodes.SEQUENCE_EXHAUSTED,
|
|
164
|
+
context: {
|
|
165
|
+
testId,
|
|
166
|
+
scenarioId,
|
|
167
|
+
requestInfo: {
|
|
168
|
+
method: context.method,
|
|
169
|
+
url: context.url,
|
|
170
|
+
},
|
|
171
|
+
hint: "Add a fallback mock to handle requests after sequence exhaustion, or use repeat: 'last' or 'cycle' instead of 'none'.",
|
|
172
|
+
},
|
|
173
|
+
}),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
logger.warn(LogCategories.MATCHING, LogEvents.MOCK_NO_MATCH, logContext, {
|
|
177
|
+
url: context.url,
|
|
178
|
+
method: context.method,
|
|
179
|
+
candidateCount: 0,
|
|
180
|
+
});
|
|
110
181
|
return {
|
|
111
182
|
success: false,
|
|
112
|
-
error: new
|
|
183
|
+
error: new ScenaristError(`No mock matched for ${context.method} ${context.url}`, {
|
|
184
|
+
code: ErrorCodes.NO_MOCK_FOUND,
|
|
185
|
+
context: {
|
|
186
|
+
testId,
|
|
187
|
+
scenarioId,
|
|
188
|
+
requestInfo: {
|
|
189
|
+
method: context.method,
|
|
190
|
+
url: context.url,
|
|
191
|
+
},
|
|
192
|
+
hint: "Add a fallback mock (without match criteria) to handle unmatched requests, or add a mock with matching criteria.",
|
|
193
|
+
},
|
|
194
|
+
}),
|
|
113
195
|
};
|
|
114
196
|
},
|
|
115
197
|
};
|
|
@@ -123,9 +205,11 @@ export const createResponseSelector = (options = {}) => {
|
|
|
123
205
|
* @param mock - The mock definition
|
|
124
206
|
* @param sequenceTracker - Optional sequence tracker for Phase 2
|
|
125
207
|
* @param stateManager - Optional state manager for stateResponse
|
|
208
|
+
* @param logger - Logger for debugging
|
|
126
209
|
* @returns ScenaristResponse or null if mock has no response type
|
|
127
210
|
*/
|
|
128
|
-
const selectResponseFromMock = (testId, scenarioId, mockIndex, mock, sequenceTracker, stateManager) => {
|
|
211
|
+
const selectResponseFromMock = (testId, scenarioId, mockIndex, mock, sequenceTracker, stateManager, logger) => {
|
|
212
|
+
const logContext = { testId, scenarioId };
|
|
129
213
|
// Phase 2: If mock has a sequence, use sequence tracker
|
|
130
214
|
if (mock.sequence) {
|
|
131
215
|
if (!sequenceTracker) {
|
|
@@ -137,6 +221,7 @@ const selectResponseFromMock = (testId, scenarioId, mockIndex, mock, sequenceTra
|
|
|
137
221
|
// Get response at current position
|
|
138
222
|
// Note: Exhausted sequences are skipped during matching phase,
|
|
139
223
|
// so position should always be valid here
|
|
224
|
+
// eslint-disable-next-line security/detect-object-injection -- Position bounded by sequence tracker
|
|
140
225
|
const response = mock.sequence.responses[position];
|
|
141
226
|
// Advance position for next call
|
|
142
227
|
const repeatMode = mock.sequence.repeat || "last";
|
|
@@ -145,7 +230,7 @@ const selectResponseFromMock = (testId, scenarioId, mockIndex, mock, sequenceTra
|
|
|
145
230
|
}
|
|
146
231
|
// State-aware response: evaluate conditions against current state
|
|
147
232
|
if (mock.stateResponse) {
|
|
148
|
-
return resolveStateResponse(testId, mock.stateResponse, stateManager);
|
|
233
|
+
return resolveStateResponse(testId, mock.stateResponse, stateManager, logger, logContext);
|
|
149
234
|
}
|
|
150
235
|
// Phase 1: Single response
|
|
151
236
|
if (mock.response) {
|
|
@@ -163,18 +248,36 @@ const selectResponseFromMock = (testId, scenarioId, mockIndex, mock, sequenceTra
|
|
|
163
248
|
* @param testId - Test ID for state isolation
|
|
164
249
|
* @param stateResponse - The stateResponse configuration
|
|
165
250
|
* @param stateManager - Optional state manager for state lookup
|
|
251
|
+
* @param logger - Logger for debugging
|
|
252
|
+
* @param logContext - Context for log messages
|
|
166
253
|
* @returns The resolved response (matching condition or default)
|
|
167
254
|
*/
|
|
168
|
-
const resolveStateResponse = (testId, stateResponse, stateManager) => {
|
|
255
|
+
const resolveStateResponse = (testId, stateResponse, stateManager, logger, logContext) => {
|
|
169
256
|
// Without stateManager, always return default
|
|
170
257
|
if (!stateManager) {
|
|
258
|
+
logger.debug(LogCategories.STATE, LogEvents.STATE_RESPONSE_RESOLVED, logContext, {
|
|
259
|
+
result: "default",
|
|
260
|
+
reason: "no_state_manager",
|
|
261
|
+
});
|
|
171
262
|
return stateResponse.default;
|
|
172
263
|
}
|
|
173
264
|
// Get current state for this test
|
|
174
265
|
const currentState = stateManager.getAll(testId);
|
|
175
266
|
// Create resolver and evaluate conditions
|
|
176
267
|
const resolver = createStateResponseResolver();
|
|
177
|
-
|
|
268
|
+
const response = resolver.resolveResponse(stateResponse, currentState);
|
|
269
|
+
// Log which response was selected and why
|
|
270
|
+
const isDefault = response === stateResponse.default;
|
|
271
|
+
const matchedCondition = isDefault
|
|
272
|
+
? null
|
|
273
|
+
: stateResponse.conditions.find((c) => resolver.resolveResponse({ default: stateResponse.default, conditions: [c] }, currentState) !== stateResponse.default);
|
|
274
|
+
logger.debug(LogCategories.STATE, LogEvents.STATE_RESPONSE_RESOLVED, logContext, {
|
|
275
|
+
result: isDefault ? "default" : "condition",
|
|
276
|
+
currentState,
|
|
277
|
+
conditionsCount: stateResponse.conditions.length,
|
|
278
|
+
matchedWhen: matchedCondition?.when ?? null,
|
|
279
|
+
});
|
|
280
|
+
return response;
|
|
178
281
|
};
|
|
179
282
|
/**
|
|
180
283
|
* Calculate specificity score for match criteria.
|
|
@@ -276,6 +379,7 @@ const matchesState = (stateCriteria, testId, stateManager) => {
|
|
|
276
379
|
return false;
|
|
277
380
|
}
|
|
278
381
|
// Deep equality check for values (handles primitives, null, objects)
|
|
382
|
+
// eslint-disable-next-line security/detect-object-injection -- Key from Object.entries iteration
|
|
279
383
|
if (!deepEquals(currentState[key], expectedValue)) {
|
|
280
384
|
return false;
|
|
281
385
|
}
|
|
@@ -296,6 +400,7 @@ const matchesBody = (requestBody, criteriaBody) => {
|
|
|
296
400
|
const body = requestBody;
|
|
297
401
|
// Check all required fields exist in request body with matching values
|
|
298
402
|
for (const [key, criteriaValue] of Object.entries(criteriaBody)) {
|
|
403
|
+
// eslint-disable-next-line security/detect-object-injection -- Key from Object.entries iteration
|
|
299
404
|
const requestValue = body[key];
|
|
300
405
|
// Convert to string for matching (type coercion like headers/query)
|
|
301
406
|
const stringValue = requestValue == null ? "" : String(requestValue);
|
|
@@ -378,6 +483,7 @@ const matchesHeaders = (requestHeaders, criteriaHeaders) => {
|
|
|
378
483
|
const normalizedRequest = createNormalizedHeaderMap(requestHeaders);
|
|
379
484
|
for (const [key, value] of Object.entries(criteriaHeaders)) {
|
|
380
485
|
const normalizedKey = normalizeHeaderName(key);
|
|
486
|
+
// eslint-disable-next-line security/detect-object-injection -- Key normalized from Object.entries iteration
|
|
381
487
|
const requestValue = normalizedRequest[normalizedKey];
|
|
382
488
|
if (!requestValue || !matchesValue(requestValue, value)) {
|
|
383
489
|
return false;
|
|
@@ -392,6 +498,7 @@ const matchesHeaders = (requestHeaders, criteriaHeaders) => {
|
|
|
392
498
|
const matchesQuery = (requestQuery, criteriaQuery) => {
|
|
393
499
|
// Check all required query params exist with exact matching values
|
|
394
500
|
for (const [key, value] of Object.entries(criteriaQuery)) {
|
|
501
|
+
// eslint-disable-next-line security/detect-object-injection -- Key from Object.entries iteration
|
|
395
502
|
const requestValue = requestQuery[key];
|
|
396
503
|
if (!requestValue || !matchesValue(requestValue, value)) {
|
|
397
504
|
return false;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ScenarioManager, ScenarioRegistry, ScenarioStore, SequenceTracker, StateManager } from "../ports/index.js";
|
|
1
|
+
import type { ScenarioManager, ScenarioRegistry, ScenarioStore, SequenceTracker, StateManager, Logger } from "../ports/index.js";
|
|
2
2
|
/**
|
|
3
3
|
* Factory function to create a ScenarioManager implementation.
|
|
4
4
|
*
|
|
@@ -8,13 +8,15 @@ import type { ScenarioManager, ScenarioRegistry, ScenarioStore, SequenceTracker,
|
|
|
8
8
|
* - Any registry implementation (in-memory, Redis, files, remote)
|
|
9
9
|
* - Any store implementation (in-memory, Redis, database)
|
|
10
10
|
* - Any state manager implementation (in-memory, Redis, database)
|
|
11
|
+
* - Any logger implementation (console, file, remote)
|
|
11
12
|
* - Proper testing with mock dependencies
|
|
12
13
|
* - True hexagonal architecture
|
|
13
14
|
*/
|
|
14
|
-
export declare const createScenarioManager: ({ registry, store, stateManager, sequenceTracker, }: {
|
|
15
|
+
export declare const createScenarioManager: ({ registry, store, stateManager, sequenceTracker, logger, }: {
|
|
15
16
|
registry: ScenarioRegistry;
|
|
16
17
|
store: ScenarioStore;
|
|
17
18
|
stateManager?: StateManager;
|
|
18
19
|
sequenceTracker?: SequenceTracker;
|
|
20
|
+
logger?: Logger;
|
|
19
21
|
}) => ScenarioManager;
|
|
20
22
|
//# sourceMappingURL=scenario-manager.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scenario-manager.d.ts","sourceRoot":"","sources":["../../src/domain/scenario-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,YAAY,
|
|
1
|
+
{"version":3,"file":"scenario-manager.d.ts","sourceRoot":"","sources":["../../src/domain/scenario-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,YAAY,EACZ,MAAM,EACP,MAAM,mBAAmB,CAAC;AAwC3B;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB,GAAI,6DAMnC;IACD,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,KAAK,EAAE,aAAa,CAAC;IACrB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,KAAG,eAwHH,CAAC"}
|
|
@@ -1,24 +1,25 @@
|
|
|
1
|
+
import { noOpLogger } from "../adapters/index.js";
|
|
1
2
|
import { ScenaristScenarioSchema } from "../schemas/index.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
}
|
|
3
|
+
import { ScenaristError, ErrorCodes } from "../types/errors.js";
|
|
4
|
+
import { LogCategories, LogEvents } from "./log-events.js";
|
|
5
|
+
const createDuplicateScenarioError = (scenarioId) => {
|
|
6
|
+
return new ScenaristError(`Scenario '${scenarioId}' is already registered. Each scenario must have a unique ID.`, {
|
|
7
|
+
code: ErrorCodes.DUPLICATE_SCENARIO,
|
|
8
|
+
context: {
|
|
9
|
+
scenarioId,
|
|
10
|
+
hint: "Use a different scenario ID, or remove the existing scenario before registering a new one.",
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
const createScenarioValidationError = (scenarioId, validationErrors) => {
|
|
15
|
+
return new ScenaristError(`Invalid scenario definition for '${scenarioId}': ${validationErrors.join(", ")}`, {
|
|
16
|
+
code: ErrorCodes.VALIDATION_ERROR,
|
|
17
|
+
context: {
|
|
18
|
+
scenarioId,
|
|
19
|
+
hint: `Check your scenario definition. Validation errors: ${validationErrors.join("; ")}`,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
};
|
|
22
23
|
/**
|
|
23
24
|
* Factory function to create a ScenarioManager implementation.
|
|
24
25
|
*
|
|
@@ -28,10 +29,11 @@ class ScenarioValidationError extends Error {
|
|
|
28
29
|
* - Any registry implementation (in-memory, Redis, files, remote)
|
|
29
30
|
* - Any store implementation (in-memory, Redis, database)
|
|
30
31
|
* - Any state manager implementation (in-memory, Redis, database)
|
|
32
|
+
* - Any logger implementation (console, file, remote)
|
|
31
33
|
* - Proper testing with mock dependencies
|
|
32
34
|
* - True hexagonal architecture
|
|
33
35
|
*/
|
|
34
|
-
export const createScenarioManager = ({ registry, store, stateManager, sequenceTracker, }) => {
|
|
36
|
+
export const createScenarioManager = ({ registry, store, stateManager, sequenceTracker, logger = noOpLogger, }) => {
|
|
35
37
|
return {
|
|
36
38
|
registerScenario(definition) {
|
|
37
39
|
// Validate scenario definition at trust boundary
|
|
@@ -39,7 +41,7 @@ export const createScenarioManager = ({ registry, store, stateManager, sequenceT
|
|
|
39
41
|
if (!validationResult.success) {
|
|
40
42
|
const errorMessages = validationResult.error.issues.map((err) => `${err.path.join(".")}: ${err.message}`);
|
|
41
43
|
const scenarioId = definition?.id || "<unknown>";
|
|
42
|
-
throw
|
|
44
|
+
throw createScenarioValidationError(scenarioId, errorMessages);
|
|
43
45
|
}
|
|
44
46
|
const existing = registry.get(definition.id);
|
|
45
47
|
// Allow re-registering the exact same scenario object (idempotent)
|
|
@@ -48,16 +50,30 @@ export const createScenarioManager = ({ registry, store, stateManager, sequenceT
|
|
|
48
50
|
}
|
|
49
51
|
// Prevent registering a different scenario with the same ID
|
|
50
52
|
if (existing) {
|
|
51
|
-
throw
|
|
53
|
+
throw createDuplicateScenarioError(definition.id);
|
|
52
54
|
}
|
|
53
55
|
registry.register(definition);
|
|
56
|
+
logger.debug(LogCategories.SCENARIO, LogEvents.SCENARIO_REGISTERED, {}, {
|
|
57
|
+
scenarioId: definition.id,
|
|
58
|
+
mockCount: definition.mocks.length, // mocks is required by ScenaristScenarioSchema
|
|
59
|
+
});
|
|
54
60
|
},
|
|
55
61
|
switchScenario(testId, scenarioId) {
|
|
56
62
|
const definition = registry.get(scenarioId);
|
|
57
63
|
if (!definition) {
|
|
64
|
+
logger.error(LogCategories.SCENARIO, LogEvents.SCENARIO_NOT_FOUND, { testId }, {
|
|
65
|
+
requestedScenarioId: scenarioId,
|
|
66
|
+
});
|
|
58
67
|
return {
|
|
59
68
|
success: false,
|
|
60
|
-
error: new
|
|
69
|
+
error: new ScenaristError(`Scenario '${scenarioId}' not found. Did you forget to register it?`, {
|
|
70
|
+
code: ErrorCodes.SCENARIO_NOT_FOUND,
|
|
71
|
+
context: {
|
|
72
|
+
testId,
|
|
73
|
+
scenarioId,
|
|
74
|
+
hint: "Make sure to register the scenario before switching to it. Use manager.registerScenario(definition) first.",
|
|
75
|
+
},
|
|
76
|
+
}),
|
|
61
77
|
};
|
|
62
78
|
}
|
|
63
79
|
const activeScenario = {
|
|
@@ -72,6 +88,7 @@ export const createScenarioManager = ({ registry, store, stateManager, sequenceT
|
|
|
72
88
|
if (stateManager) {
|
|
73
89
|
stateManager.reset(testId);
|
|
74
90
|
}
|
|
91
|
+
logger.info(LogCategories.SCENARIO, LogEvents.SCENARIO_SWITCHED, { testId, scenarioId }, {});
|
|
75
92
|
return { success: true, data: undefined };
|
|
76
93
|
},
|
|
77
94
|
getActiveScenario(testId) {
|
|
@@ -82,6 +99,7 @@ export const createScenarioManager = ({ registry, store, stateManager, sequenceT
|
|
|
82
99
|
},
|
|
83
100
|
clearScenario(testId) {
|
|
84
101
|
store.delete(testId);
|
|
102
|
+
logger.debug(LogCategories.SCENARIO, LogEvents.SCENARIO_CLEARED, { testId }, {});
|
|
85
103
|
},
|
|
86
104
|
getScenarioById(id) {
|
|
87
105
|
return registry.get(id);
|
|
@@ -32,6 +32,7 @@ const stateMatchesCondition = (state, when) => {
|
|
|
32
32
|
if (!(key in state)) {
|
|
33
33
|
return false;
|
|
34
34
|
}
|
|
35
|
+
// eslint-disable-next-line security/detect-object-injection -- Key from Object.entries iteration
|
|
35
36
|
const actualValue = state[key];
|
|
36
37
|
if (!deepEquals(actualValue, expectedValue)) {
|
|
37
38
|
return false;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"template-replacement.d.ts","sourceRoot":"","sources":["../../src/domain/template-replacement.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GACzB,OAAO,OAAO,EACd,cAAc,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACpC,
|
|
1
|
+
{"version":3,"file":"template-replacement.d.ts","sourceRoot":"","sources":["../../src/domain/template-replacement.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,GACzB,OAAO,OAAO,EACd,cAAc,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KACpC,OA+DF,CAAC"}
|
|
@@ -49,6 +49,7 @@ export const applyTemplates = (value, templateData) => {
|
|
|
49
49
|
if (typeof value === "object" && value !== null) {
|
|
50
50
|
const result = {};
|
|
51
51
|
for (const [key, val] of Object.entries(value)) {
|
|
52
|
+
// eslint-disable-next-line security/detect-object-injection -- Key from Object.entries iteration
|
|
52
53
|
result[key] = applyTemplates(val, normalizedData);
|
|
53
54
|
}
|
|
54
55
|
return result;
|
|
@@ -67,6 +68,7 @@ export const applyTemplates = (value, templateData) => {
|
|
|
67
68
|
*/
|
|
68
69
|
const resolveTemplatePath = (templateData, prefix, path) => {
|
|
69
70
|
// Get the root object (state or params)
|
|
71
|
+
// eslint-disable-next-line security/detect-object-injection -- Prefix validated as 'state' or 'params' by regex
|
|
70
72
|
const root = templateData[prefix];
|
|
71
73
|
// Guard: Prefix doesn't exist (e.g., no params provided)
|
|
72
74
|
if (root === undefined || typeof root !== "object" || root === null) {
|
|
@@ -86,6 +88,7 @@ const resolveTemplatePath = (templateData, prefix, path) => {
|
|
|
86
88
|
}
|
|
87
89
|
// Traverse object
|
|
88
90
|
const record = current;
|
|
91
|
+
// eslint-disable-next-line security/detect-object-injection -- Segment from split() iteration
|
|
89
92
|
current = record[segment];
|
|
90
93
|
// Guard: Return undefined if property doesn't exist
|
|
91
94
|
if (current === undefined) {
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,mBAAmB,kBAAkB,CAAC;AAGtC,cAAc,oBAAoB,CAAC;AAGnC,cAAc,sBAAsB,CAAC;AAGrC,mBAAmB,kBAAkB,CAAC;AAGtC,mBAAmB,sBAAsB,CAAC;AAG1C,cAAc,mBAAmB,CAAC;AAGlC,cAAc,qBAAqB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,mBAAmB,kBAAkB,CAAC;AAGtC,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAG/D,cAAc,oBAAoB,CAAC;AAGnC,cAAc,sBAAsB,CAAC;AAGrC,mBAAmB,kBAAkB,CAAC;AAGtC,mBAAmB,sBAAsB,CAAC;AAG1C,cAAc,mBAAmB,CAAC;AAGlC,cAAc,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log levels ordered by verbosity (ascending).
|
|
3
|
+
* Each level includes all less verbose levels.
|
|
4
|
+
*
|
|
5
|
+
* - silent: No output (production default)
|
|
6
|
+
* - error: Critical failures preventing operation
|
|
7
|
+
* - warn: Potential issues that may cause problems
|
|
8
|
+
* - info: Operation flow and key events
|
|
9
|
+
* - debug: Detailed decision logic
|
|
10
|
+
* - trace: Request/response bodies, verbose details
|
|
11
|
+
*/
|
|
12
|
+
export type LogLevel = "silent" | "error" | "warn" | "info" | "debug" | "trace";
|
|
13
|
+
/**
|
|
14
|
+
* Log categories for filtering specific areas of concern.
|
|
15
|
+
* Users can enable/disable categories independently.
|
|
16
|
+
*/
|
|
17
|
+
export type LogCategory = "lifecycle" | "scenario" | "matching" | "sequence" | "state" | "template" | "request";
|
|
18
|
+
/**
|
|
19
|
+
* Base context included in all log events.
|
|
20
|
+
* Enables filtering and correlation by test ID.
|
|
21
|
+
*/
|
|
22
|
+
export type LogContext = {
|
|
23
|
+
readonly testId?: string;
|
|
24
|
+
readonly scenarioId?: string;
|
|
25
|
+
readonly requestUrl?: string;
|
|
26
|
+
readonly requestMethod?: string;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Structured log entry for capture and serialization.
|
|
30
|
+
*/
|
|
31
|
+
export type LogEntry = {
|
|
32
|
+
readonly level: Exclude<LogLevel, "silent">;
|
|
33
|
+
readonly category: LogCategory;
|
|
34
|
+
readonly message: string;
|
|
35
|
+
readonly context: LogContext;
|
|
36
|
+
readonly data?: Record<string, unknown>;
|
|
37
|
+
readonly timestamp: number;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Logger port for structured logging.
|
|
41
|
+
*
|
|
42
|
+
* This is a driven (secondary) port - domain logic calls out to it,
|
|
43
|
+
* implementations are injected via dependency injection.
|
|
44
|
+
*
|
|
45
|
+
* Implementations must handle:
|
|
46
|
+
* - Level filtering (only emit logs at or above configured level)
|
|
47
|
+
* - Category filtering (optionally filter by category)
|
|
48
|
+
* - Output formatting (console, JSON, custom)
|
|
49
|
+
*
|
|
50
|
+
* This port enables:
|
|
51
|
+
* - NoOpLogger: Zero overhead when disabled (production, silent mode)
|
|
52
|
+
* - ConsoleLogger: Human-readable or JSON output (development)
|
|
53
|
+
* - TestLogger: Capture logs for assertion in tests
|
|
54
|
+
* - Custom loggers: User-provided implementations (Winston, Pino, etc.)
|
|
55
|
+
*
|
|
56
|
+
* All methods return void - logging is fire-and-forget.
|
|
57
|
+
* Implementations should never throw.
|
|
58
|
+
*/
|
|
59
|
+
export interface Logger {
|
|
60
|
+
/**
|
|
61
|
+
* Log at error level - critical failures preventing operation.
|
|
62
|
+
*
|
|
63
|
+
* @param category - Area of concern for filtering
|
|
64
|
+
* @param message - Human-readable event description
|
|
65
|
+
* @param context - Request context for correlation (testId, scenarioId, etc.)
|
|
66
|
+
* @param data - Optional structured data for the event
|
|
67
|
+
*/
|
|
68
|
+
error(category: LogCategory, message: string, context: LogContext, data?: Record<string, unknown>): void;
|
|
69
|
+
/**
|
|
70
|
+
* Log at warn level - potential issues that may cause problems.
|
|
71
|
+
*
|
|
72
|
+
* @param category - Area of concern for filtering
|
|
73
|
+
* @param message - Human-readable event description
|
|
74
|
+
* @param context - Request context for correlation
|
|
75
|
+
* @param data - Optional structured data for the event
|
|
76
|
+
*/
|
|
77
|
+
warn(category: LogCategory, message: string, context: LogContext, data?: Record<string, unknown>): void;
|
|
78
|
+
/**
|
|
79
|
+
* Log at info level - operation flow and key events.
|
|
80
|
+
*
|
|
81
|
+
* @param category - Area of concern for filtering
|
|
82
|
+
* @param message - Human-readable event description
|
|
83
|
+
* @param context - Request context for correlation
|
|
84
|
+
* @param data - Optional structured data for the event
|
|
85
|
+
*/
|
|
86
|
+
info(category: LogCategory, message: string, context: LogContext, data?: Record<string, unknown>): void;
|
|
87
|
+
/**
|
|
88
|
+
* Log at debug level - detailed decision logic.
|
|
89
|
+
*
|
|
90
|
+
* @param category - Area of concern for filtering
|
|
91
|
+
* @param message - Human-readable event description
|
|
92
|
+
* @param context - Request context for correlation
|
|
93
|
+
* @param data - Optional structured data for the event
|
|
94
|
+
*/
|
|
95
|
+
debug(category: LogCategory, message: string, context: LogContext, data?: Record<string, unknown>): void;
|
|
96
|
+
/**
|
|
97
|
+
* Log at trace level - request/response bodies, verbose details.
|
|
98
|
+
*
|
|
99
|
+
* @param category - Area of concern for filtering
|
|
100
|
+
* @param message - Human-readable event description
|
|
101
|
+
* @param context - Request context for correlation
|
|
102
|
+
* @param data - Optional structured data for the event
|
|
103
|
+
*/
|
|
104
|
+
trace(category: LogCategory, message: string, context: LogContext, data?: Record<string, unknown>): void;
|
|
105
|
+
/**
|
|
106
|
+
* Check if a specific level would be logged.
|
|
107
|
+
* Enables conditional expensive operations before logging.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* if (logger.isEnabled('trace')) {
|
|
111
|
+
* logger.trace('request', 'body', ctx, { body: JSON.stringify(large) });
|
|
112
|
+
* }
|
|
113
|
+
*
|
|
114
|
+
* @param level - The log level to check
|
|
115
|
+
* @returns true if logging at this level is enabled
|
|
116
|
+
*/
|
|
117
|
+
isEnabled(level: Exclude<LogLevel, "silent">): boolean;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../../src/ports/driven/logger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;AAEhF;;;GAGG;AACH,MAAM,MAAM,WAAW,GACnB,WAAW,GACX,UAAU,GACV,UAAU,GACV,UAAU,GACV,OAAO,GACP,UAAU,GACV,SAAS,CAAC;AAEd;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC5C,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,UAAU,CAAC;IAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,MAAM;IACrB;;;;;;;OAOG;IACH,KAAK,CACH,QAAQ,EAAE,WAAW,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,UAAU,EACnB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,IAAI,CAAC;IAER;;;;;;;OAOG;IACH,IAAI,CACF,QAAQ,EAAE,WAAW,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,UAAU,EACnB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,IAAI,CAAC;IAER;;;;;;;OAOG;IACH,IAAI,CACF,QAAQ,EAAE,WAAW,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,UAAU,EACnB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,IAAI,CAAC;IAER;;;;;;;OAOG;IACH,KAAK,CACH,QAAQ,EAAE,WAAW,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,UAAU,EACnB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,IAAI,CAAC;IAER;;;;;;;OAOG;IACH,KAAK,CACH,QAAQ,EAAE,WAAW,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,UAAU,EACnB,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,IAAI,CAAC;IAER;;;;;;;;;;;OAWG;IACH,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,EAAE,QAAQ,CAAC,GAAG,OAAO,CAAC;CACxD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { ScenaristMockWithParams, ScenaristResponse, HttpRequestContext, ScenaristResult } from "../../types/index.js";
|
|
2
|
+
import { ScenaristError } from "../../types/errors.js";
|
|
2
3
|
/**
|
|
3
4
|
* Error type for response selection failures.
|
|
5
|
+
* @deprecated Use ScenaristError with ErrorCodes.NO_MOCK_FOUND instead
|
|
4
6
|
*/
|
|
5
7
|
export declare class ResponseSelectionError extends Error {
|
|
6
8
|
constructor(message: string);
|
|
@@ -29,6 +31,6 @@ export interface ResponseSelector {
|
|
|
29
31
|
* @param mocks - Candidate mocks with extracted params (already filtered by URL/method)
|
|
30
32
|
* @returns ScenaristResult with selected ScenaristResponse or error if no match found
|
|
31
33
|
*/
|
|
32
|
-
selectResponse(testId: string, scenarioId: string, context: HttpRequestContext, mocks: ReadonlyArray<ScenaristMockWithParams>): ScenaristResult<ScenaristResponse,
|
|
34
|
+
selectResponse(testId: string, scenarioId: string, context: HttpRequestContext, mocks: ReadonlyArray<ScenaristMockWithParams>): ScenaristResult<ScenaristResponse, ScenaristError>;
|
|
33
35
|
}
|
|
34
36
|
//# sourceMappingURL=response-selector.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response-selector.d.ts","sourceRoot":"","sources":["../../../src/ports/driven/response-selector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EAChB,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"response-selector.d.ts","sourceRoot":"","sources":["../../../src/ports/driven/response-selector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,uBAAuB,EACvB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAEvD;;;GAGG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,gBAAgB;IAC/B;;;;;;;;OAQG;IACH,cAAc,CACZ,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,aAAa,CAAC,uBAAuB,CAAC,GAC5C,eAAe,CAAC,iBAAiB,EAAE,cAAc,CAAC,CAAC;CACvD"}
|
package/dist/ports/index.d.ts
CHANGED
|
@@ -5,4 +5,5 @@ export type { RequestContext } from "./driven/request-context.js";
|
|
|
5
5
|
export type { ResponseSelector } from "./driven/response-selector.js";
|
|
6
6
|
export type { SequenceTracker, SequencePosition, } from "./driven/sequence-tracker.js";
|
|
7
7
|
export type { StateManager } from "./driven/state-manager.js";
|
|
8
|
+
export type { Logger, LogLevel, LogCategory, LogContext, LogEntry, } from "./driven/logger.js";
|
|
8
9
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ports/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAGrE,YAAY,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,YAAY,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,YAAY,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAClE,YAAY,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,YAAY,EACV,eAAe,EACf,gBAAgB,GACjB,MAAM,8BAA8B,CAAC;AACtC,YAAY,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ports/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAGrE,YAAY,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,YAAY,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAChE,YAAY,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAClE,YAAY,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACtE,YAAY,EACV,eAAe,EACf,gBAAgB,GACjB,MAAM,8BAA8B,CAAC;AACtC,YAAY,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAC9D,YAAY,EACV,MAAM,EACN,QAAQ,EACR,WAAW,EACX,UAAU,EACV,QAAQ,GACT,MAAM,oBAAoB,CAAC"}
|