refactor(stalker): extract replay fixture schema

This commit is contained in:
4gray committed 2026-07-27 09:28:01 +02:00
1 parent af0f210ea5
commit 8009233220
16 files changed
+1896 -1782

No files matched your search

File diff suppressed because it is too large. Load diff
@@ -1,39 +1 @@
export const REPLAY_SCHEMA_VERSION = 1 as const;
export const REPLAY_MAX_FIXTURE_BYTES = 1024 * 1024;
export const REPLAY_MAX_PHASES = 128;
export const REPLAY_MAX_EXPECTATIONS = 512;
export const REPLAY_MAX_REQUESTS = 512;
export const REPLAY_MAX_REQUEST_BODY_BYTES = 1024 * 1024;
export const REPLAY_MAX_GENERATED_BODY_BYTES = 16 * 1024 * 1024;
export const REPLAY_RUN_HARD_LIFETIME_MS = 10 * 60 * 1000;
export const REPLAY_RUN_INACTIVITY_MS = 2 * 60 * 1000;
export const REPLAY_SYMBOL_VALUE_KINDS = [
'mac',
'credential',
'token',
'random',
'cookie',
'correlation',
] as const;
export const REPLAY_HTTP_METHODS = [
'GET',
'POST',
'PUT',
'PATCH',
'DELETE',
'HEAD',
'OPTIONS',
] as const;
export const REPLAY_COOKIE_ATTRIBUTE_NAMES = [
'domain',
'path',
'secure',
'httpOnly',
'sameSite',
'maxAge',
'expires',
] as const;
export * from '@iptvnator/portal/stalker/replay-fixtures/constants';
@@ -1,204 +1 @@
import {
REPLAY_HTTP_METHODS,
REPLAY_SYMBOL_VALUE_KINDS,
} from './replay.constants.js';
export type ReplayHttpMethod = (typeof REPLAY_HTTP_METHODS)[number];
export type ReplaySymbolValueKind = (typeof REPLAY_SYMBOL_VALUE_KINDS)[number];
export type ReplayJsonPrimitive = string | number | boolean | null;
export type ReplayJsonValue =
| ReplayJsonPrimitive
| ReplayJsonValue[]
| { [key: string]: ReplayJsonValue };
export interface ReplayGenerateNode {
kind: 'generate';
symbol: string;
valueKind: ReplaySymbolValueKind;
}
export interface ReplayRefNode {
kind: 'ref';
symbol: string;
}
export interface ReplayLiteralPart {
kind: 'literal';
value: string;
}
export interface ReplayPartsNode {
kind: 'parts';
parts: Array<ReplayLiteralPart | ReplayRefNode>;
}
export interface ReplayOriginUrlNode {
kind: 'origin-url';
origin: string;
path: string;
}
export type ReplayTemplateString =
| string
| ReplayGenerateNode
| ReplayRefNode
| ReplayPartsNode;
export type ReplayResponseHeaderValue =
| ReplayTemplateString
| ReplayOriginUrlNode;
export type ReplayTemplateValue =
| ReplayJsonPrimitive
| ReplayGenerateNode
| ReplayRefNode
| ReplayPartsNode
| ReplayTemplateValue[]
| { [key: string]: ReplayTemplateValue };
export type ReplayMatchValue =
| ReplayJsonPrimitive
| ReplayRefNode
| ReplayPartsNode;
export interface ReplayFieldMatchers<T = ReplayMatchValue> {
exact: Record<string, T>;
present: string[];
absent: string[];
}
export type ReplayRequestBodyMatcher =
| { kind: 'absent' }
| ({ kind: 'json' } & ReplayFieldMatchers<ReplayTemplateValue>)
| ({ kind: 'form' } & ReplayFieldMatchers<ReplayTemplateString>)
| { kind: 'text'; exact: ReplayTemplateString };
export interface ReplayCookieAttributeMatchers {
domain?: ReplayTemplateString;
path?: ReplayTemplateString;
secure?: boolean;
httpOnly?: boolean;
sameSite?: 'strict' | 'lax' | 'none';
maxAge?: number;
expires?: ReplayTemplateString;
}
export interface ReplayCookieMatchers extends ReplayFieldMatchers<ReplayTemplateString> {
attributes: Record<string, ReplayCookieAttributeMatchers>;
}
export interface ReplayRequestMatcher {
query: ReplayFieldMatchers<
ReplayTemplateString | ReplayTemplateString[]
>;
headers: ReplayFieldMatchers<ReplayTemplateString | ReplayTemplateString[]>;
cookies: ReplayCookieMatchers;
body: ReplayRequestBodyMatcher;
}
export type ReplayResponseBody =
| { kind: 'empty' }
| { kind: 'json'; value: ReplayTemplateValue }
| {
kind: 'jsonp';
callback: string;
value: ReplayTemplateValue;
}
| { kind: 'text'; value: ReplayTemplateString }
| { kind: 'generated'; byteLength: number; byte: number };
export interface ReplayResponseDefinition {
status: number;
headers: Record<string, ReplayResponseHeaderValue[]>;
body: ReplayResponseBody;
}
export interface ReplayCardinality {
min: number;
max: number;
}
export interface ReplayExpectation {
id: string;
operation: string;
origin: string;
method: ReplayHttpMethod;
path: string;
request: ReplayRequestMatcher;
response: ReplayResponseDefinition;
cardinality: ReplayCardinality;
}
export interface ReplayBarrier {
name: string;
releaseWhenMatched: number;
}
export interface ReplayPhase {
name: string;
state: string;
nextState: string;
mode: 'ordered' | 'unordered';
barrier?: ReplayBarrier;
expectations: ReplayExpectation[];
}
export interface ReplayOriginDefinition {
description?: string;
}
export interface ReplayEndpointDefinition {
origin: string;
path: string;
}
export interface ReplayFixtureV1 {
schemaVersion: 1;
scenarioId: string;
description?: string;
origins: Record<string, ReplayOriginDefinition>;
entry: ReplayEndpointDefinition;
expectedEndpoint: ReplayEndpointDefinition;
initialState: string;
terminalState: string;
failOnUnexpectedRequest: boolean;
symbols: ReplayGenerateNode[];
phases: ReplayPhase[];
}
export interface ReplayObservedCookie {
value: string;
attributes?: Partial<{
domain: string;
path: string;
secure: boolean;
httpOnly: boolean;
sameSite: 'strict' | 'lax' | 'none';
maxAge: number;
expires: string;
}>;
}
export type ReplayObservedRequestBody =
| { kind: 'absent' }
| { kind: 'json'; value: ReplayJsonValue }
| { kind: 'form'; value: Record<string, string> }
| { kind: 'text'; value: string };
export interface ReplayObservedRequest {
origin: string;
method: ReplayHttpMethod;
path: string;
query: Record<string, string[]>;
headers: Record<string, string[]>;
cookies: Record<string, ReplayObservedCookie>;
body: ReplayObservedRequestBody;
}
export interface ReplayRenderedResponse {
status: number;
headers: Record<string, string[]>;
body: Buffer;
}
export type * from '@iptvnator/portal/stalker/replay-fixtures/types';
+1 -1
View File
@@ -1,11 +1,11 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",
"moduleResolution": "node",
"lib": ["ES2022"],
"outDir": "../../dist/apps/stalker-mock-server",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
+7
View File
@@ -59,6 +59,13 @@ export default [
'type:util',
],
},
{
sourceTag: 'type:tool',
onlyDependOnLibsWithTags: [
'type:tool',
'type:util',
],
},
{
sourceTag: 'type:website',
onlyDependOnLibsWithTags: ['type:ui', 'type:util'],
@@ -0,0 +1,17 @@
export default {
displayName: 'portal-stalker-replay-fixtures',
preset: '../../../../jest.preset.js',
testEnvironment: 'node',
transform: {
'^.+\\.[tj]s$': [
'ts-jest',
{ tsconfig: '<rootDir>/tsconfig.spec.json' },
],
},
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
moduleFileExtensions: ['ts', 'js'],
coverageDirectory:
'../../../../coverage/libs/portal/stalker/replay-fixtures',
};
@@ -0,0 +1,20 @@
{
"name": "portal-stalker-replay-fixtures",
"$schema": "../../../../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/portal/stalker/replay-fixtures/src",
"projectType": "library",
"tags": ["scope:portal", "domain:stalker", "type:util"],
"targets": {
"test": {
"executor": "@nx/jest:jest",
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
"options": {
"jestConfig": "libs/portal/stalker/replay-fixtures/jest.config.ts",
"tsConfig": "libs/portal/stalker/replay-fixtures/tsconfig.spec.json"
}
},
"lint": {
"executor": "@nx/eslint:lint"
}
}
}
@@ -0,0 +1,3 @@
export * from './lib/replay.constants';
export * from './lib/replay-schema';
export * from './lib/replay.types';
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,39 @@
export const REPLAY_SCHEMA_VERSION = 1 as const;
export const REPLAY_MAX_FIXTURE_BYTES = 1024 * 1024;
export const REPLAY_MAX_PHASES = 128;
export const REPLAY_MAX_EXPECTATIONS = 512;
export const REPLAY_MAX_REQUESTS = 512;
export const REPLAY_MAX_REQUEST_BODY_BYTES = 1024 * 1024;
export const REPLAY_MAX_GENERATED_BODY_BYTES = 16 * 1024 * 1024;
export const REPLAY_RUN_HARD_LIFETIME_MS = 10 * 60 * 1000;
export const REPLAY_RUN_INACTIVITY_MS = 2 * 60 * 1000;
export const REPLAY_SYMBOL_VALUE_KINDS = [
'mac',
'credential',
'token',
'random',
'cookie',
'correlation',
] as const;
export const REPLAY_HTTP_METHODS = [
'GET',
'POST',
'PUT',
'PATCH',
'DELETE',
'HEAD',
'OPTIONS',
] as const;
export const REPLAY_COOKIE_ATTRIBUTE_NAMES = [
'domain',
'path',
'secure',
'httpOnly',
'sameSite',
'maxAge',
'expires',
] as const;
@@ -0,0 +1,204 @@
import {
REPLAY_HTTP_METHODS,
REPLAY_SYMBOL_VALUE_KINDS,
} from './replay.constants.js';
export type ReplayHttpMethod = (typeof REPLAY_HTTP_METHODS)[number];
export type ReplaySymbolValueKind = (typeof REPLAY_SYMBOL_VALUE_KINDS)[number];
export type ReplayJsonPrimitive = string | number | boolean | null;
export type ReplayJsonValue =
| ReplayJsonPrimitive
| ReplayJsonValue[]
| { [key: string]: ReplayJsonValue };
export interface ReplayGenerateNode {
kind: 'generate';
symbol: string;
valueKind: ReplaySymbolValueKind;
}
export interface ReplayRefNode {
kind: 'ref';
symbol: string;
}
export interface ReplayLiteralPart {
kind: 'literal';
value: string;
}
export interface ReplayPartsNode {
kind: 'parts';
parts: Array<ReplayLiteralPart | ReplayRefNode>;
}
export interface ReplayOriginUrlNode {
kind: 'origin-url';
origin: string;
path: string;
}
export type ReplayTemplateString =
| string
| ReplayGenerateNode
| ReplayRefNode
| ReplayPartsNode;
export type ReplayResponseHeaderValue =
| ReplayTemplateString
| ReplayOriginUrlNode;
export type ReplayTemplateValue =
| ReplayJsonPrimitive
| ReplayGenerateNode
| ReplayRefNode
| ReplayPartsNode
| ReplayTemplateValue[]
| { [key: string]: ReplayTemplateValue };
export type ReplayMatchValue =
| ReplayJsonPrimitive
| ReplayRefNode
| ReplayPartsNode;
export interface ReplayFieldMatchers<T = ReplayMatchValue> {
exact: Record<string, T>;
present: string[];
absent: string[];
}
export type ReplayRequestBodyMatcher =
| { kind: 'absent' }
| ({ kind: 'json' } & ReplayFieldMatchers<ReplayTemplateValue>)
| ({ kind: 'form' } & ReplayFieldMatchers<ReplayTemplateString>)
| { kind: 'text'; exact: ReplayTemplateString };
export interface ReplayCookieAttributeMatchers {
domain?: ReplayTemplateString;
path?: ReplayTemplateString;
secure?: boolean;
httpOnly?: boolean;
sameSite?: 'strict' | 'lax' | 'none';
maxAge?: number;
expires?: ReplayTemplateString;
}
export interface ReplayCookieMatchers extends ReplayFieldMatchers<ReplayTemplateString> {
attributes: Record<string, ReplayCookieAttributeMatchers>;
}
export interface ReplayRequestMatcher {
query: ReplayFieldMatchers<
ReplayTemplateString | ReplayTemplateString[]
>;
headers: ReplayFieldMatchers<ReplayTemplateString | ReplayTemplateString[]>;
cookies: ReplayCookieMatchers;
body: ReplayRequestBodyMatcher;
}
export type ReplayResponseBody =
| { kind: 'empty' }
| { kind: 'json'; value: ReplayTemplateValue }
| {
kind: 'jsonp';
callback: string;
value: ReplayTemplateValue;
}
| { kind: 'text'; value: ReplayTemplateString }
| { kind: 'generated'; byteLength: number; byte: number };
export interface ReplayResponseDefinition {
status: number;
headers: Record<string, ReplayResponseHeaderValue[]>;
body: ReplayResponseBody;
}
export interface ReplayCardinality {
min: number;
max: number;
}
export interface ReplayExpectation {
id: string;
operation: string;
origin: string;
method: ReplayHttpMethod;
path: string;
request: ReplayRequestMatcher;
response: ReplayResponseDefinition;
cardinality: ReplayCardinality;
}
export interface ReplayBarrier {
name: string;
releaseWhenMatched: number;
}
export interface ReplayPhase {
name: string;
state: string;
nextState: string;
mode: 'ordered' | 'unordered';
barrier?: ReplayBarrier;
expectations: ReplayExpectation[];
}
export interface ReplayOriginDefinition {
description?: string;
}
export interface ReplayEndpointDefinition {
origin: string;
path: string;
}
export interface ReplayFixtureV1 {
schemaVersion: 1;
scenarioId: string;
description?: string;
origins: Record<string, ReplayOriginDefinition>;
entry: ReplayEndpointDefinition;
expectedEndpoint: ReplayEndpointDefinition;
initialState: string;
terminalState: string;
failOnUnexpectedRequest: boolean;
symbols: ReplayGenerateNode[];
phases: ReplayPhase[];
}
export interface ReplayObservedCookie {
value: string;
attributes?: Partial<{
domain: string;
path: string;
secure: boolean;
httpOnly: boolean;
sameSite: 'strict' | 'lax' | 'none';
maxAge: number;
expires: string;
}>;
}
export type ReplayObservedRequestBody =
| { kind: 'absent' }
| { kind: 'json'; value: ReplayJsonValue }
| { kind: 'form'; value: Record<string, string> }
| { kind: 'text'; value: string };
export interface ReplayObservedRequest {
origin: string;
method: ReplayHttpMethod;
path: string;
query: Record<string, string[]>;
headers: Record<string, string[]>;
cookies: Record<string, ReplayObservedCookie>;
body: ReplayObservedRequestBody;
}
export interface ReplayRenderedResponse {
status: number;
headers: Record<string, string[]>;
body: Buffer;
}
@@ -0,0 +1,25 @@
{
"extends": "../../../../tsconfig.base.json",
"compilerOptions": {
"isolatedModules": true,
"target": "es2022",
"moduleResolution": "bundler",
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"module": "preserve",
"types": ["node"]
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}
@@ -0,0 +1,12 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../../dist/out-tsc",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": ["node"]
},
"include": ["src/**/*.ts"],
"exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"]
}
@@ -0,0 +1,15 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../../../dist/out-tsc",
"module": "commonjs",
"moduleResolution": "node10",
"types": ["jest", "node"]
},
"include": [
"jest.config.ts",
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}
+12
View File
@@ -64,6 +64,18 @@
"@iptvnator/portal/stalker/protocol": [
"libs/portal/stalker/protocol/src/index.ts"
],
"@iptvnator/portal/stalker/replay-fixtures": [
"libs/portal/stalker/replay-fixtures/src/index.ts"
],
"@iptvnator/portal/stalker/replay-fixtures/constants": [
"libs/portal/stalker/replay-fixtures/src/lib/replay.constants.ts"
],
"@iptvnator/portal/stalker/replay-fixtures/schema": [
"libs/portal/stalker/replay-fixtures/src/lib/replay-schema.ts"
],
"@iptvnator/portal/stalker/replay-fixtures/types": [
"libs/portal/stalker/replay-fixtures/src/lib/replay.types.ts"
],
"@iptvnator/portal/xtream/feature": [
"libs/portal/xtream/feature/src/index.ts"
],