@contractspec/lib.progressive-delivery 0.0.0-canary-20260113162409
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 +32 -0
- package/dist/canary-analyzer.d.ts +16 -0
- package/dist/canary-analyzer.d.ts.map +1 -0
- package/dist/canary-analyzer.js +28 -0
- package/dist/canary-analyzer.js.map +1 -0
- package/dist/canary-controller.d.ts +22 -0
- package/dist/canary-controller.d.ts.map +1 -0
- package/dist/canary-controller.js +66 -0
- package/dist/canary-controller.js.map +1 -0
- package/dist/deployment-coordinator.d.ts +28 -0
- package/dist/deployment-coordinator.d.ts.map +1 -0
- package/dist/deployment-coordinator.js +47 -0
- package/dist/deployment-coordinator.js.map +1 -0
- package/dist/events.d.ts +11 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +19 -0
- package/dist/events.js.map +1 -0
- package/dist/feature-flags.d.ts +19 -0
- package/dist/feature-flags.d.ts.map +1 -0
- package/dist/feature-flags.js +31 -0
- package/dist/feature-flags.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +9 -0
- package/dist/rollback-manager.d.ts +15 -0
- package/dist/rollback-manager.d.ts.map +1 -0
- package/dist/rollback-manager.js +20 -0
- package/dist/rollback-manager.js.map +1 -0
- package/dist/traffic-shifter.d.ts +11 -0
- package/dist/traffic-shifter.d.ts.map +1 -0
- package/dist/traffic-shifter.js +24 -0
- package/dist/traffic-shifter.js.map +1 -0
- package/dist/types.d.ts +60 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# @contractspec/lib.progressive-delivery
|
|
2
|
+
|
|
3
|
+
Progressive delivery primitives for ContractSpec apps. Manage canary and blue-green deployments with guardrails, integration hooks, and automatic rollback.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Declarative deployment strategies (canary, blue-green, hybrid)
|
|
8
|
+
- Stage-based traffic shifting with configurable durations
|
|
9
|
+
- Metric-driven guardrails (error rate, latency percentiles, throughput)
|
|
10
|
+
- Automatic rollback with structured events
|
|
11
|
+
- Event hooks for logging, auditing, and UI updates
|
|
12
|
+
- Works with `@contractspec/lib.observability` and feature flag pipelines
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import {
|
|
18
|
+
DeploymentCoordinator,
|
|
19
|
+
createDefaultCanaryController,
|
|
20
|
+
} from '@contractspec/lib.progressive-delivery';
|
|
21
|
+
|
|
22
|
+
const coordinator = new DeploymentCoordinator({
|
|
23
|
+
strategy,
|
|
24
|
+
controller: createDefaultCanaryController(strategy),
|
|
25
|
+
metricsProvider: fetchMetricsFromOtEL,
|
|
26
|
+
rollbackManager,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
await coordinator.run();
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
See the docs in `docs/tech/PHASE_5_ZERO_TOUCH_OPERATIONS.md` for full workflows.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CanaryStage, DeploymentMetrics, DeploymentThresholds } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/canary-analyzer.d.ts
|
|
4
|
+
interface AnalysisResult {
|
|
5
|
+
status: 'pass' | 'fail';
|
|
6
|
+
reasons: string[];
|
|
7
|
+
metrics: DeploymentMetrics;
|
|
8
|
+
}
|
|
9
|
+
declare class CanaryAnalyzer {
|
|
10
|
+
private readonly defaults;
|
|
11
|
+
constructor(defaults: DeploymentThresholds);
|
|
12
|
+
evaluate(stage: CanaryStage, metrics: DeploymentMetrics): AnalysisResult;
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { AnalysisResult, CanaryAnalyzer };
|
|
16
|
+
//# sourceMappingURL=canary-analyzer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canary-analyzer.d.ts","names":[],"sources":["../src/canary-analyzer.ts"],"sourcesContent":[],"mappings":";;;UAMiB,cAAA;;EAAA,OAAA,EAAA,MAAA,EAAc;EAMlB,OAAA,EAHF,iBAGgB;;AAGT,cAHL,cAAA,CAGK;EAAsB,iBAAA,QAAA;EAAoB,WAAA,CAAA,QAAA,EAFnB,oBAEmB;EAAc,QAAA,CAAA,KAAA,EAAxD,WAAwD,EAAA,OAAA,EAAlC,iBAAkC,CAAA,EAAd,cAAc"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
//#region src/canary-analyzer.ts
|
|
2
|
+
var CanaryAnalyzer = class {
|
|
3
|
+
constructor(defaults) {
|
|
4
|
+
this.defaults = defaults;
|
|
5
|
+
}
|
|
6
|
+
evaluate(stage, metrics) {
|
|
7
|
+
const thresholds = {
|
|
8
|
+
...this.defaults,
|
|
9
|
+
...stage.thresholds
|
|
10
|
+
};
|
|
11
|
+
const reasons = [];
|
|
12
|
+
if (metrics.errorRate > thresholds.errorRate) reasons.push(`errorRate ${metrics.errorRate.toFixed(4)} > ${thresholds.errorRate}`);
|
|
13
|
+
if (typeof thresholds.latencyP50 === "number" && metrics.latencyP50 > thresholds.latencyP50) reasons.push(`latencyP50 ${metrics.latencyP50}ms > ${thresholds.latencyP50}ms`);
|
|
14
|
+
if (typeof thresholds.latencyP95 === "number" && metrics.latencyP95 > thresholds.latencyP95) reasons.push(`latencyP95 ${metrics.latencyP95}ms > ${thresholds.latencyP95}ms`);
|
|
15
|
+
if (typeof thresholds.latencyP99 === "number" && metrics.latencyP99 > thresholds.latencyP99) reasons.push(`latencyP99 ${metrics.latencyP99}ms > ${thresholds.latencyP99}ms`);
|
|
16
|
+
if (typeof thresholds.throughputDrop === "number" && metrics.throughput < thresholds.throughputDrop) reasons.push(`throughput ${metrics.throughput} < ${thresholds.throughputDrop}`);
|
|
17
|
+
if (thresholds.customEvaluator && !thresholds.customEvaluator(metrics)) reasons.push("custom evaluator reported failure");
|
|
18
|
+
return {
|
|
19
|
+
status: reasons.length === 0 ? "pass" : "fail",
|
|
20
|
+
reasons,
|
|
21
|
+
metrics
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { CanaryAnalyzer };
|
|
28
|
+
//# sourceMappingURL=canary-analyzer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canary-analyzer.js","names":[],"sources":["../src/canary-analyzer.ts"],"sourcesContent":["import type {\n CanaryStage,\n DeploymentMetrics,\n DeploymentThresholds,\n} from './types';\n\nexport interface AnalysisResult {\n status: 'pass' | 'fail';\n reasons: string[];\n metrics: DeploymentMetrics;\n}\n\nexport class CanaryAnalyzer {\n constructor(private readonly defaults: DeploymentThresholds) {}\n\n evaluate(stage: CanaryStage, metrics: DeploymentMetrics): AnalysisResult {\n const thresholds = { ...this.defaults, ...stage.thresholds };\n const reasons: string[] = [];\n\n if (metrics.errorRate > thresholds.errorRate) {\n reasons.push(\n `errorRate ${metrics.errorRate.toFixed(4)} > ${thresholds.errorRate}`\n );\n }\n\n if (\n typeof thresholds.latencyP50 === 'number' &&\n metrics.latencyP50 > thresholds.latencyP50\n ) {\n reasons.push(\n `latencyP50 ${metrics.latencyP50}ms > ${thresholds.latencyP50}ms`\n );\n }\n\n if (\n typeof thresholds.latencyP95 === 'number' &&\n metrics.latencyP95 > thresholds.latencyP95\n ) {\n reasons.push(\n `latencyP95 ${metrics.latencyP95}ms > ${thresholds.latencyP95}ms`\n );\n }\n\n if (\n typeof thresholds.latencyP99 === 'number' &&\n metrics.latencyP99 > thresholds.latencyP99\n ) {\n reasons.push(\n `latencyP99 ${metrics.latencyP99}ms > ${thresholds.latencyP99}ms`\n );\n }\n\n if (\n typeof thresholds.throughputDrop === 'number' &&\n metrics.throughput < thresholds.throughputDrop\n ) {\n reasons.push(\n `throughput ${metrics.throughput} < ${thresholds.throughputDrop}`\n );\n }\n\n if (thresholds.customEvaluator && !thresholds.customEvaluator(metrics)) {\n reasons.push('custom evaluator reported failure');\n }\n\n return {\n status: reasons.length === 0 ? 'pass' : 'fail',\n reasons,\n metrics,\n };\n }\n}\n"],"mappings":";AAYA,IAAa,iBAAb,MAA4B;CAC1B,YAAY,AAAiB,UAAgC;EAAhC;;CAE7B,SAAS,OAAoB,SAA4C;EACvE,MAAM,aAAa;GAAE,GAAG,KAAK;GAAU,GAAG,MAAM;GAAY;EAC5D,MAAM,UAAoB,EAAE;AAE5B,MAAI,QAAQ,YAAY,WAAW,UACjC,SAAQ,KACN,aAAa,QAAQ,UAAU,QAAQ,EAAE,CAAC,KAAK,WAAW,YAC3D;AAGH,MACE,OAAO,WAAW,eAAe,YACjC,QAAQ,aAAa,WAAW,WAEhC,SAAQ,KACN,cAAc,QAAQ,WAAW,OAAO,WAAW,WAAW,IAC/D;AAGH,MACE,OAAO,WAAW,eAAe,YACjC,QAAQ,aAAa,WAAW,WAEhC,SAAQ,KACN,cAAc,QAAQ,WAAW,OAAO,WAAW,WAAW,IAC/D;AAGH,MACE,OAAO,WAAW,eAAe,YACjC,QAAQ,aAAa,WAAW,WAEhC,SAAQ,KACN,cAAc,QAAQ,WAAW,OAAO,WAAW,WAAW,IAC/D;AAGH,MACE,OAAO,WAAW,mBAAmB,YACrC,QAAQ,aAAa,WAAW,eAEhC,SAAQ,KACN,cAAc,QAAQ,WAAW,KAAK,WAAW,iBAClD;AAGH,MAAI,WAAW,mBAAmB,CAAC,WAAW,gBAAgB,QAAQ,CACpE,SAAQ,KAAK,oCAAoC;AAGnD,SAAO;GACL,QAAQ,QAAQ,WAAW,IAAI,SAAS;GACxC;GACA;GACD"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { CanaryStage, DeploymentStrategy, MetricsProvider } from "./types.js";
|
|
2
|
+
import { DeploymentEventBus } from "./events.js";
|
|
3
|
+
import { AnalysisResult, CanaryAnalyzer } from "./canary-analyzer.js";
|
|
4
|
+
|
|
5
|
+
//#region src/canary-controller.d.ts
|
|
6
|
+
interface CanaryControllerOptions {
|
|
7
|
+
strategy: DeploymentStrategy;
|
|
8
|
+
analyzer: CanaryAnalyzer;
|
|
9
|
+
metricsProvider: MetricsProvider;
|
|
10
|
+
eventBus?: DeploymentEventBus;
|
|
11
|
+
}
|
|
12
|
+
declare class CanaryController {
|
|
13
|
+
private readonly options;
|
|
14
|
+
private readonly stages;
|
|
15
|
+
constructor(options: CanaryControllerOptions);
|
|
16
|
+
getStageList(): CanaryStage[];
|
|
17
|
+
runStage(stage: CanaryStage): Promise<AnalysisResult>;
|
|
18
|
+
}
|
|
19
|
+
declare function createDefaultCanaryController(strategy: DeploymentStrategy, metricsProvider: MetricsProvider, eventBus?: DeploymentEventBus): CanaryController;
|
|
20
|
+
//#endregion
|
|
21
|
+
export { CanaryController, CanaryControllerOptions, createDefaultCanaryController };
|
|
22
|
+
//# sourceMappingURL=canary-controller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canary-controller.d.ts","names":[],"sources":["../src/canary-controller.ts"],"sourcesContent":[],"mappings":";;;;;UAWiB,uBAAA;YACL;EADK,QAAA,EAEL,cAFK;EACL,eAAA,EAEO,eAFP;EACA,QAAA,CAAA,EAEC,kBAFD;;AAEC,cAGA,gBAAA,CAHA;EAAkB,iBAAA,OAAA;EAGlB,iBAAA,MAAgB;EAGW,WAAA,CAAA,OAAA,EAAA,uBAAA;EAO1B,YAAA,CAAA,CAAA,EAAA,WAAA,EAAA;EAIU,QAAA,CAAA,KAAA,EAAA,WAAA,CAAA,EAAc,OAAd,CAAsB,cAAtB,CAAA;;AAAc,iBAuBtB,6BAAA,CAvBsB,QAAA,EAwB1B,kBAxB0B,EAAA,eAAA,EAyBnB,eAzBmB,EAAA,QAAA,CAAA,EA0BzB,kBA1ByB,CAAA,EA0BP,gBA1BO"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { CanaryAnalyzer } from "./canary-analyzer.js";
|
|
2
|
+
|
|
3
|
+
//#region src/canary-controller.ts
|
|
4
|
+
const DEFAULT_STAGES = [
|
|
5
|
+
{
|
|
6
|
+
percentage: 1,
|
|
7
|
+
minDurationMs: 300 * 1e3,
|
|
8
|
+
label: "1%"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
percentage: 10,
|
|
12
|
+
minDurationMs: 300 * 1e3,
|
|
13
|
+
label: "10%"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
percentage: 50,
|
|
17
|
+
minDurationMs: 600 * 1e3,
|
|
18
|
+
label: "50%"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
percentage: 100,
|
|
22
|
+
minDurationMs: 900 * 1e3,
|
|
23
|
+
label: "100%"
|
|
24
|
+
}
|
|
25
|
+
];
|
|
26
|
+
var CanaryController = class {
|
|
27
|
+
stages;
|
|
28
|
+
constructor(options) {
|
|
29
|
+
this.options = options;
|
|
30
|
+
this.stages = options.strategy.stages && options.strategy.stages.length > 0 ? options.strategy.stages : DEFAULT_STAGES;
|
|
31
|
+
}
|
|
32
|
+
getStageList() {
|
|
33
|
+
return [...this.stages];
|
|
34
|
+
}
|
|
35
|
+
async runStage(stage) {
|
|
36
|
+
this.options.eventBus?.emit({
|
|
37
|
+
type: "stage_started",
|
|
38
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
39
|
+
payload: { stage }
|
|
40
|
+
});
|
|
41
|
+
const metrics = await this.options.metricsProvider(stage, stage.minDurationMs);
|
|
42
|
+
const analysis = this.options.analyzer.evaluate(stage, metrics);
|
|
43
|
+
this.options.eventBus?.emit({
|
|
44
|
+
type: analysis.status === "pass" ? "stage_passed" : "stage_failed",
|
|
45
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
46
|
+
payload: {
|
|
47
|
+
stage,
|
|
48
|
+
metrics,
|
|
49
|
+
analysis
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return analysis;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
function createDefaultCanaryController(strategy, metricsProvider, eventBus) {
|
|
56
|
+
return new CanaryController({
|
|
57
|
+
strategy,
|
|
58
|
+
analyzer: new CanaryAnalyzer(strategy.thresholds),
|
|
59
|
+
metricsProvider,
|
|
60
|
+
eventBus
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//#endregion
|
|
65
|
+
export { CanaryController, createDefaultCanaryController };
|
|
66
|
+
//# sourceMappingURL=canary-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canary-controller.js","names":[],"sources":["../src/canary-controller.ts"],"sourcesContent":["import { CanaryAnalyzer, type AnalysisResult } from './canary-analyzer';\nimport { DeploymentEventBus } from './events';\nimport type { CanaryStage, DeploymentStrategy, MetricsProvider } from './types';\n\nconst DEFAULT_STAGES: CanaryStage[] = [\n { percentage: 1, minDurationMs: 5 * 60 * 1000, label: '1%' },\n { percentage: 10, minDurationMs: 5 * 60 * 1000, label: '10%' },\n { percentage: 50, minDurationMs: 10 * 60 * 1000, label: '50%' },\n { percentage: 100, minDurationMs: 15 * 60 * 1000, label: '100%' },\n];\n\nexport interface CanaryControllerOptions {\n strategy: DeploymentStrategy;\n analyzer: CanaryAnalyzer;\n metricsProvider: MetricsProvider;\n eventBus?: DeploymentEventBus;\n}\n\nexport class CanaryController {\n private readonly stages: CanaryStage[];\n\n constructor(private readonly options: CanaryControllerOptions) {\n this.stages =\n options.strategy.stages && options.strategy.stages.length > 0\n ? options.strategy.stages\n : DEFAULT_STAGES;\n }\n\n getStageList() {\n return [...this.stages];\n }\n\n async runStage(stage: CanaryStage): Promise<AnalysisResult> {\n this.options.eventBus?.emit({\n type: 'stage_started',\n timestamp: new Date(),\n payload: { stage },\n });\n\n const metrics = await this.options.metricsProvider(\n stage,\n stage.minDurationMs\n );\n const analysis = this.options.analyzer.evaluate(stage, metrics);\n\n this.options.eventBus?.emit({\n type: analysis.status === 'pass' ? 'stage_passed' : 'stage_failed',\n timestamp: new Date(),\n payload: { stage, metrics, analysis },\n });\n\n return analysis;\n }\n}\n\nexport function createDefaultCanaryController(\n strategy: DeploymentStrategy,\n metricsProvider: MetricsProvider,\n eventBus?: DeploymentEventBus\n) {\n const analyzer = new CanaryAnalyzer(strategy.thresholds);\n return new CanaryController({\n strategy,\n analyzer,\n metricsProvider,\n eventBus,\n });\n}\n"],"mappings":";;;AAIA,MAAM,iBAAgC;CACpC;EAAE,YAAY;EAAG,eAAe,MAAS;EAAM,OAAO;EAAM;CAC5D;EAAE,YAAY;EAAI,eAAe,MAAS;EAAM,OAAO;EAAO;CAC9D;EAAE,YAAY;EAAI,eAAe,MAAU;EAAM,OAAO;EAAO;CAC/D;EAAE,YAAY;EAAK,eAAe,MAAU;EAAM,OAAO;EAAQ;CAClE;AASD,IAAa,mBAAb,MAA8B;CAC5B,AAAiB;CAEjB,YAAY,AAAiB,SAAkC;EAAlC;AAC3B,OAAK,SACH,QAAQ,SAAS,UAAU,QAAQ,SAAS,OAAO,SAAS,IACxD,QAAQ,SAAS,SACjB;;CAGR,eAAe;AACb,SAAO,CAAC,GAAG,KAAK,OAAO;;CAGzB,MAAM,SAAS,OAA6C;AAC1D,OAAK,QAAQ,UAAU,KAAK;GAC1B,MAAM;GACN,2BAAW,IAAI,MAAM;GACrB,SAAS,EAAE,OAAO;GACnB,CAAC;EAEF,MAAM,UAAU,MAAM,KAAK,QAAQ,gBACjC,OACA,MAAM,cACP;EACD,MAAM,WAAW,KAAK,QAAQ,SAAS,SAAS,OAAO,QAAQ;AAE/D,OAAK,QAAQ,UAAU,KAAK;GAC1B,MAAM,SAAS,WAAW,SAAS,iBAAiB;GACpD,2BAAW,IAAI,MAAM;GACrB,SAAS;IAAE;IAAO;IAAS;IAAU;GACtC,CAAC;AAEF,SAAO;;;AAIX,SAAgB,8BACd,UACA,iBACA,UACA;AAEA,QAAO,IAAI,iBAAiB;EAC1B;EACA,UAHe,IAAI,eAAe,SAAS,WAAW;EAItD;EACA;EACD,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { CanaryStage, DeploymentStrategy, TrafficSplit } from "./types.js";
|
|
2
|
+
import { DeploymentEventBus } from "./events.js";
|
|
3
|
+
import { TrafficShifter } from "./traffic-shifter.js";
|
|
4
|
+
import { CanaryController } from "./canary-controller.js";
|
|
5
|
+
import { RollbackManager } from "./rollback-manager.js";
|
|
6
|
+
|
|
7
|
+
//#region src/deployment-coordinator.d.ts
|
|
8
|
+
interface DeploymentCoordinatorOptions {
|
|
9
|
+
strategy: DeploymentStrategy;
|
|
10
|
+
controller: CanaryController;
|
|
11
|
+
trafficShifter: TrafficShifter;
|
|
12
|
+
rollbackManager: RollbackManager;
|
|
13
|
+
applyTrafficSplit: (stage: CanaryStage, split: TrafficSplit) => void | Promise<void>;
|
|
14
|
+
eventBus?: DeploymentEventBus;
|
|
15
|
+
}
|
|
16
|
+
interface DeploymentRunResult {
|
|
17
|
+
status: 'completed' | 'rolled_back';
|
|
18
|
+
failedStage?: CanaryStage;
|
|
19
|
+
reasons?: string[];
|
|
20
|
+
}
|
|
21
|
+
declare class DeploymentCoordinator {
|
|
22
|
+
private readonly options;
|
|
23
|
+
constructor(options: DeploymentCoordinatorOptions);
|
|
24
|
+
run(): Promise<DeploymentRunResult>;
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
export { DeploymentCoordinator, DeploymentCoordinatorOptions, DeploymentRunResult };
|
|
28
|
+
//# sourceMappingURL=deployment-coordinator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deployment-coordinator.d.ts","names":[],"sources":["../src/deployment-coordinator.ts"],"sourcesContent":[],"mappings":";;;;;;;UAMiB,4BAAA;YACL;EADK,UAAA,EAEH,gBAFG;EACL,cAAA,EAEM,cAFN;EACE,eAAA,EAEK,eAFL;EACI,iBAAA,EAAA,CAAA,KAAA,EAGP,WAHO,EAAA,KAAA,EAIP,YAJO,EAAA,GAAA,IAAA,GAKJ,OALI,CAAA,IAAA,CAAA;EACC,QAAA,CAAA,EAKN,kBALM;;AAGR,UAKM,mBAAA,CALN;EACG,MAAA,EAAA,WAAA,GAAA,aAAA;EACD,WAAA,CAAA,EAKG,WALH;EAAkB,OAAA,CAAA,EAAA,MAAA,EAAA;AAG/B;AAMa,cAAA,qBAAA,CAAqB;EACM,iBAAA,OAAA;EAEjB,WAAA,CAAA,OAAA,EAFiB,4BAEjB;EAAR,GAAA,CAAA,CAAA,EAAA,OAAA,CAAQ,mBAAR,CAAA"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import "./canary-controller.js";
|
|
2
|
+
|
|
3
|
+
//#region src/deployment-coordinator.ts
|
|
4
|
+
var DeploymentCoordinator = class {
|
|
5
|
+
constructor(options) {
|
|
6
|
+
this.options = options;
|
|
7
|
+
}
|
|
8
|
+
async run() {
|
|
9
|
+
const stages = this.options.controller.getStageList();
|
|
10
|
+
for (const stage of stages) {
|
|
11
|
+
const split = this.options.trafficShifter.computeSplit(stage);
|
|
12
|
+
await this.options.applyTrafficSplit(stage, split);
|
|
13
|
+
const analysis = await this.options.controller.runStage(stage);
|
|
14
|
+
if (analysis.status === "fail") {
|
|
15
|
+
const action = await this.options.rollbackManager.execute(stage, analysis.reasons.join(", "));
|
|
16
|
+
this.options.eventBus?.emit({
|
|
17
|
+
type: "rolled_back",
|
|
18
|
+
timestamp: action.triggeredAt,
|
|
19
|
+
payload: {
|
|
20
|
+
stage,
|
|
21
|
+
reasons: analysis.reasons
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return {
|
|
25
|
+
status: "rolled_back",
|
|
26
|
+
failedStage: stage,
|
|
27
|
+
reasons: analysis.reasons
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (this.options.strategy.mode === "blue-green") this.options.eventBus?.emit({
|
|
32
|
+
type: "blue_green_swapped",
|
|
33
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
34
|
+
payload: { strategy: this.options.strategy }
|
|
35
|
+
});
|
|
36
|
+
this.options.eventBus?.emit({
|
|
37
|
+
type: "completed",
|
|
38
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
39
|
+
payload: { strategy: this.options.strategy }
|
|
40
|
+
});
|
|
41
|
+
return { status: "completed" };
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
export { DeploymentCoordinator };
|
|
47
|
+
//# sourceMappingURL=deployment-coordinator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deployment-coordinator.js","names":[],"sources":["../src/deployment-coordinator.ts"],"sourcesContent":["import { CanaryController } from './canary-controller';\nimport { DeploymentEventBus } from './events';\nimport { RollbackManager } from './rollback-manager';\nimport { TrafficShifter } from './traffic-shifter';\nimport type { CanaryStage, DeploymentStrategy, TrafficSplit } from './types';\n\nexport interface DeploymentCoordinatorOptions {\n strategy: DeploymentStrategy;\n controller: CanaryController;\n trafficShifter: TrafficShifter;\n rollbackManager: RollbackManager;\n applyTrafficSplit: (\n stage: CanaryStage,\n split: TrafficSplit\n ) => void | Promise<void>;\n eventBus?: DeploymentEventBus;\n}\n\nexport interface DeploymentRunResult {\n status: 'completed' | 'rolled_back';\n failedStage?: CanaryStage;\n reasons?: string[];\n}\n\nexport class DeploymentCoordinator {\n constructor(private readonly options: DeploymentCoordinatorOptions) {}\n\n async run(): Promise<DeploymentRunResult> {\n const stages = this.options.controller.getStageList();\n\n for (const stage of stages) {\n const split = this.options.trafficShifter.computeSplit(stage);\n await this.options.applyTrafficSplit(stage, split);\n\n const analysis = await this.options.controller.runStage(stage);\n if (analysis.status === 'fail') {\n const action = await this.options.rollbackManager.execute(\n stage,\n analysis.reasons.join(', ')\n );\n\n this.options.eventBus?.emit({\n type: 'rolled_back',\n timestamp: action.triggeredAt,\n payload: { stage, reasons: analysis.reasons },\n });\n\n return {\n status: 'rolled_back',\n failedStage: stage,\n reasons: analysis.reasons,\n };\n }\n }\n\n if (this.options.strategy.mode === 'blue-green') {\n this.options.eventBus?.emit({\n type: 'blue_green_swapped',\n timestamp: new Date(),\n payload: { strategy: this.options.strategy },\n });\n }\n\n this.options.eventBus?.emit({\n type: 'completed',\n timestamp: new Date(),\n payload: { strategy: this.options.strategy },\n });\n\n return { status: 'completed' };\n }\n}\n"],"mappings":";;;AAwBA,IAAa,wBAAb,MAAmC;CACjC,YAAY,AAAiB,SAAuC;EAAvC;;CAE7B,MAAM,MAAoC;EACxC,MAAM,SAAS,KAAK,QAAQ,WAAW,cAAc;AAErD,OAAK,MAAM,SAAS,QAAQ;GAC1B,MAAM,QAAQ,KAAK,QAAQ,eAAe,aAAa,MAAM;AAC7D,SAAM,KAAK,QAAQ,kBAAkB,OAAO,MAAM;GAElD,MAAM,WAAW,MAAM,KAAK,QAAQ,WAAW,SAAS,MAAM;AAC9D,OAAI,SAAS,WAAW,QAAQ;IAC9B,MAAM,SAAS,MAAM,KAAK,QAAQ,gBAAgB,QAChD,OACA,SAAS,QAAQ,KAAK,KAAK,CAC5B;AAED,SAAK,QAAQ,UAAU,KAAK;KAC1B,MAAM;KACN,WAAW,OAAO;KAClB,SAAS;MAAE;MAAO,SAAS,SAAS;MAAS;KAC9C,CAAC;AAEF,WAAO;KACL,QAAQ;KACR,aAAa;KACb,SAAS,SAAS;KACnB;;;AAIL,MAAI,KAAK,QAAQ,SAAS,SAAS,aACjC,MAAK,QAAQ,UAAU,KAAK;GAC1B,MAAM;GACN,2BAAW,IAAI,MAAM;GACrB,SAAS,EAAE,UAAU,KAAK,QAAQ,UAAU;GAC7C,CAAC;AAGJ,OAAK,QAAQ,UAAU,KAAK;GAC1B,MAAM;GACN,2BAAW,IAAI,MAAM;GACrB,SAAS,EAAE,UAAU,KAAK,QAAQ,UAAU;GAC7C,CAAC;AAEF,SAAO,EAAE,QAAQ,aAAa"}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { DeploymentEvent, DeploymentEventListener } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/events.d.ts
|
|
4
|
+
declare class DeploymentEventBus {
|
|
5
|
+
private listeners;
|
|
6
|
+
on(listener: DeploymentEventListener): () => boolean;
|
|
7
|
+
emit(event: DeploymentEvent): void;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { DeploymentEventBus };
|
|
11
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","names":[],"sources":["../src/events.ts"],"sourcesContent":[],"mappings":";;;cAEa,kBAAA;;EAAA,EAAA,CAAA,QAAA,EAGE,uBAAA,CAAA,EAAA,GAAA,GAAA,OAAA;cAKD"}
|
package/dist/events.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/events.ts
|
|
2
|
+
var DeploymentEventBus = class {
|
|
3
|
+
listeners = /* @__PURE__ */ new Set();
|
|
4
|
+
on(listener) {
|
|
5
|
+
this.listeners.add(listener);
|
|
6
|
+
return () => this.listeners.delete(listener);
|
|
7
|
+
}
|
|
8
|
+
emit(event) {
|
|
9
|
+
for (const listener of this.listeners) try {
|
|
10
|
+
listener(event);
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.error("[progressive-delivery] listener error", error);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
//#endregion
|
|
18
|
+
export { DeploymentEventBus };
|
|
19
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","names":[],"sources":["../src/events.ts"],"sourcesContent":["import type { DeploymentEvent, DeploymentEventListener } from './types';\n\nexport class DeploymentEventBus {\n private listeners = new Set<DeploymentEventListener>();\n\n on(listener: DeploymentEventListener) {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n emit(event: DeploymentEvent) {\n for (const listener of this.listeners) {\n try {\n listener(event);\n } catch (error) {\n console.error('[progressive-delivery] listener error', error);\n }\n }\n }\n}\n"],"mappings":";AAEA,IAAa,qBAAb,MAAgC;CAC9B,AAAQ,4BAAY,IAAI,KAA8B;CAEtD,GAAG,UAAmC;AACpC,OAAK,UAAU,IAAI,SAAS;AAC5B,eAAa,KAAK,UAAU,OAAO,SAAS;;CAG9C,KAAK,OAAwB;AAC3B,OAAK,MAAM,YAAY,KAAK,UAC1B,KAAI;AACF,YAAS,MAAM;WACR,OAAO;AACd,WAAQ,MAAM,yCAAyC,MAAM"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
//#region src/feature-flags.d.ts
|
|
2
|
+
declare const ContractSpecFeatureFlags: {
|
|
3
|
+
readonly LIFECYCLE_DETECTION_ALPHA: "lifecycle_detection_alpha";
|
|
4
|
+
readonly LIFECYCLE_ADVISOR_ALPHA: "lifecycle_advisor_alpha";
|
|
5
|
+
readonly LIFECYCLE_MANAGED_SERVICE: "lifecycle_managed_service";
|
|
6
|
+
readonly STUDIO_VISUAL_BUILDER: "studio_visual_builder";
|
|
7
|
+
readonly STUDIO_AUTO_EVOLUTION: "studio_auto_evolution";
|
|
8
|
+
readonly STUDIO_BYOK: "studio_byok";
|
|
9
|
+
readonly STUDIO_DEDICATED_DEPLOYMENT: "studio_dedicated_deployment";
|
|
10
|
+
readonly STUDIO_INTEGRATION_HUB: "studio_integration_hub";
|
|
11
|
+
readonly STUDIO_KNOWLEDGE_HUB: "studio_knowledge_hub";
|
|
12
|
+
readonly STUDIO_TEMPLATES: "studio_templates";
|
|
13
|
+
};
|
|
14
|
+
type ContractSpecFeatureFlag = keyof typeof ContractSpecFeatureFlags;
|
|
15
|
+
declare const lifecycleFlags: ("lifecycle_detection_alpha" | "lifecycle_advisor_alpha" | "lifecycle_managed_service")[];
|
|
16
|
+
declare const studioFlags: ("studio_visual_builder" | "studio_auto_evolution" | "studio_byok" | "studio_dedicated_deployment" | "studio_integration_hub" | "studio_knowledge_hub" | "studio_templates")[];
|
|
17
|
+
//#endregion
|
|
18
|
+
export { ContractSpecFeatureFlag, ContractSpecFeatureFlags, lifecycleFlags, studioFlags };
|
|
19
|
+
//# sourceMappingURL=feature-flags.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-flags.d.ts","names":[],"sources":["../src/feature-flags.ts"],"sourcesContent":[],"mappings":";cAAa;EAAA,SAAA,yBAWH,EAAA,2BAAA;EAEE,SAAA,uBAAuB,EAAA,yBAAgB;EAEtC,SAAA,yBAIZ,EAAA,2BAAA;EAEY,SAAA,qBAQZ,EAAA,uBAAA;;;;;;;;KAhBW,uBAAA,gBAAuC;cAEtC;cAMA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
//#region src/feature-flags.ts
|
|
2
|
+
const ContractSpecFeatureFlags = {
|
|
3
|
+
LIFECYCLE_DETECTION_ALPHA: "lifecycle_detection_alpha",
|
|
4
|
+
LIFECYCLE_ADVISOR_ALPHA: "lifecycle_advisor_alpha",
|
|
5
|
+
LIFECYCLE_MANAGED_SERVICE: "lifecycle_managed_service",
|
|
6
|
+
STUDIO_VISUAL_BUILDER: "studio_visual_builder",
|
|
7
|
+
STUDIO_AUTO_EVOLUTION: "studio_auto_evolution",
|
|
8
|
+
STUDIO_BYOK: "studio_byok",
|
|
9
|
+
STUDIO_DEDICATED_DEPLOYMENT: "studio_dedicated_deployment",
|
|
10
|
+
STUDIO_INTEGRATION_HUB: "studio_integration_hub",
|
|
11
|
+
STUDIO_KNOWLEDGE_HUB: "studio_knowledge_hub",
|
|
12
|
+
STUDIO_TEMPLATES: "studio_templates"
|
|
13
|
+
};
|
|
14
|
+
const lifecycleFlags = [
|
|
15
|
+
ContractSpecFeatureFlags.LIFECYCLE_DETECTION_ALPHA,
|
|
16
|
+
ContractSpecFeatureFlags.LIFECYCLE_ADVISOR_ALPHA,
|
|
17
|
+
ContractSpecFeatureFlags.LIFECYCLE_MANAGED_SERVICE
|
|
18
|
+
];
|
|
19
|
+
const studioFlags = [
|
|
20
|
+
ContractSpecFeatureFlags.STUDIO_VISUAL_BUILDER,
|
|
21
|
+
ContractSpecFeatureFlags.STUDIO_AUTO_EVOLUTION,
|
|
22
|
+
ContractSpecFeatureFlags.STUDIO_BYOK,
|
|
23
|
+
ContractSpecFeatureFlags.STUDIO_DEDICATED_DEPLOYMENT,
|
|
24
|
+
ContractSpecFeatureFlags.STUDIO_INTEGRATION_HUB,
|
|
25
|
+
ContractSpecFeatureFlags.STUDIO_KNOWLEDGE_HUB,
|
|
26
|
+
ContractSpecFeatureFlags.STUDIO_TEMPLATES
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
//#endregion
|
|
30
|
+
export { ContractSpecFeatureFlags, lifecycleFlags, studioFlags };
|
|
31
|
+
//# sourceMappingURL=feature-flags.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"feature-flags.js","names":[],"sources":["../src/feature-flags.ts"],"sourcesContent":["export const ContractSpecFeatureFlags = {\n LIFECYCLE_DETECTION_ALPHA: 'lifecycle_detection_alpha',\n LIFECYCLE_ADVISOR_ALPHA: 'lifecycle_advisor_alpha',\n LIFECYCLE_MANAGED_SERVICE: 'lifecycle_managed_service',\n STUDIO_VISUAL_BUILDER: 'studio_visual_builder',\n STUDIO_AUTO_EVOLUTION: 'studio_auto_evolution',\n STUDIO_BYOK: 'studio_byok',\n STUDIO_DEDICATED_DEPLOYMENT: 'studio_dedicated_deployment',\n STUDIO_INTEGRATION_HUB: 'studio_integration_hub',\n STUDIO_KNOWLEDGE_HUB: 'studio_knowledge_hub',\n STUDIO_TEMPLATES: 'studio_templates',\n} as const;\n\nexport type ContractSpecFeatureFlag = keyof typeof ContractSpecFeatureFlags;\n\nexport const lifecycleFlags = [\n ContractSpecFeatureFlags.LIFECYCLE_DETECTION_ALPHA,\n ContractSpecFeatureFlags.LIFECYCLE_ADVISOR_ALPHA,\n ContractSpecFeatureFlags.LIFECYCLE_MANAGED_SERVICE,\n];\n\nexport const studioFlags = [\n ContractSpecFeatureFlags.STUDIO_VISUAL_BUILDER,\n ContractSpecFeatureFlags.STUDIO_AUTO_EVOLUTION,\n ContractSpecFeatureFlags.STUDIO_BYOK,\n ContractSpecFeatureFlags.STUDIO_DEDICATED_DEPLOYMENT,\n ContractSpecFeatureFlags.STUDIO_INTEGRATION_HUB,\n ContractSpecFeatureFlags.STUDIO_KNOWLEDGE_HUB,\n ContractSpecFeatureFlags.STUDIO_TEMPLATES,\n];\n"],"mappings":";AAAA,MAAa,2BAA2B;CACtC,2BAA2B;CAC3B,yBAAyB;CACzB,2BAA2B;CAC3B,uBAAuB;CACvB,uBAAuB;CACvB,aAAa;CACb,6BAA6B;CAC7B,wBAAwB;CACxB,sBAAsB;CACtB,kBAAkB;CACnB;AAID,MAAa,iBAAiB;CAC5B,yBAAyB;CACzB,yBAAyB;CACzB,yBAAyB;CAC1B;AAED,MAAa,cAAc;CACzB,yBAAyB;CACzB,yBAAyB;CACzB,yBAAyB;CACzB,yBAAyB;CACzB,yBAAyB;CACzB,yBAAyB;CACzB,yBAAyB;CAC1B"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { CanaryStage, DeploymentEvent, DeploymentEventListener, DeploymentMetrics, DeploymentMode, DeploymentStrategy, DeploymentThresholds, MetricsProvider, OperationTarget, RollbackAction, TrafficSplit } from "./types.js";
|
|
2
|
+
import { DeploymentEventBus } from "./events.js";
|
|
3
|
+
import { TrafficShifter } from "./traffic-shifter.js";
|
|
4
|
+
import { AnalysisResult, CanaryAnalyzer } from "./canary-analyzer.js";
|
|
5
|
+
import { CanaryController, CanaryControllerOptions, createDefaultCanaryController } from "./canary-controller.js";
|
|
6
|
+
import { RollbackManager, RollbackManagerOptions } from "./rollback-manager.js";
|
|
7
|
+
import { DeploymentCoordinator, DeploymentCoordinatorOptions, DeploymentRunResult } from "./deployment-coordinator.js";
|
|
8
|
+
import { ContractSpecFeatureFlag, ContractSpecFeatureFlags, lifecycleFlags, studioFlags } from "./feature-flags.js";
|
|
9
|
+
export { AnalysisResult, CanaryAnalyzer, CanaryController, CanaryControllerOptions, CanaryStage, ContractSpecFeatureFlag, ContractSpecFeatureFlags, DeploymentCoordinator, DeploymentCoordinatorOptions, DeploymentEvent, DeploymentEventBus, DeploymentEventListener, DeploymentMetrics, DeploymentMode, DeploymentRunResult, DeploymentStrategy, DeploymentThresholds, MetricsProvider, OperationTarget, RollbackAction, RollbackManager, RollbackManagerOptions, TrafficShifter, TrafficSplit, createDefaultCanaryController, lifecycleFlags, studioFlags };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { DeploymentEventBus } from "./events.js";
|
|
2
|
+
import { TrafficShifter } from "./traffic-shifter.js";
|
|
3
|
+
import { CanaryAnalyzer } from "./canary-analyzer.js";
|
|
4
|
+
import { CanaryController, createDefaultCanaryController } from "./canary-controller.js";
|
|
5
|
+
import { RollbackManager } from "./rollback-manager.js";
|
|
6
|
+
import { DeploymentCoordinator } from "./deployment-coordinator.js";
|
|
7
|
+
import { ContractSpecFeatureFlags, lifecycleFlags, studioFlags } from "./feature-flags.js";
|
|
8
|
+
|
|
9
|
+
export { CanaryAnalyzer, CanaryController, ContractSpecFeatureFlags, DeploymentCoordinator, DeploymentEventBus, RollbackManager, TrafficShifter, createDefaultCanaryController, lifecycleFlags, studioFlags };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { CanaryStage, RollbackAction } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/rollback-manager.d.ts
|
|
4
|
+
interface RollbackManagerOptions {
|
|
5
|
+
rollback(stage: CanaryStage, reason: string): Promise<void> | void;
|
|
6
|
+
onRollback?: (action: RollbackAction) => void;
|
|
7
|
+
}
|
|
8
|
+
declare class RollbackManager {
|
|
9
|
+
private readonly options;
|
|
10
|
+
constructor(options: RollbackManagerOptions);
|
|
11
|
+
execute(stage: CanaryStage, reason: string): Promise<RollbackAction>;
|
|
12
|
+
}
|
|
13
|
+
//#endregion
|
|
14
|
+
export { RollbackManager, RollbackManagerOptions };
|
|
15
|
+
//# sourceMappingURL=rollback-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rollback-manager.d.ts","names":[],"sources":["../src/rollback-manager.ts"],"sourcesContent":[],"mappings":";;;UAEiB,sBAAA;kBACC,8BAA8B;EAD/B,UAAA,CAAA,EAAA,CAAA,MAAA,EAEO,cAFe,EAAA,GAAA,IAAA;;AACS,cAInC,eAAA,CAJmC;EACxB,iBAAA,OAAA;EAAc,WAAA,CAAA,OAAA,EAIE,sBAJF;EAGzB,OAAA,CAAA,KAAA,EAGU,WAHK,EAAA,MAAA,EAAA,MAAA,CAAA,EAGsB,OAHtB,CAGsB,cAHtB,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/rollback-manager.ts
|
|
2
|
+
var RollbackManager = class {
|
|
3
|
+
constructor(options) {
|
|
4
|
+
this.options = options;
|
|
5
|
+
}
|
|
6
|
+
async execute(stage, reason) {
|
|
7
|
+
await this.options.rollback(stage, reason);
|
|
8
|
+
const action = {
|
|
9
|
+
reason,
|
|
10
|
+
stage,
|
|
11
|
+
triggeredAt: /* @__PURE__ */ new Date()
|
|
12
|
+
};
|
|
13
|
+
this.options.onRollback?.(action);
|
|
14
|
+
return action;
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
//#endregion
|
|
19
|
+
export { RollbackManager };
|
|
20
|
+
//# sourceMappingURL=rollback-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rollback-manager.js","names":[],"sources":["../src/rollback-manager.ts"],"sourcesContent":["import type { CanaryStage, RollbackAction } from './types';\n\nexport interface RollbackManagerOptions {\n rollback(stage: CanaryStage, reason: string): Promise<void> | void;\n onRollback?: (action: RollbackAction) => void;\n}\n\nexport class RollbackManager {\n constructor(private readonly options: RollbackManagerOptions) {}\n\n async execute(stage: CanaryStage, reason: string) {\n await this.options.rollback(stage, reason);\n const action: RollbackAction = {\n reason,\n stage,\n triggeredAt: new Date(),\n };\n this.options.onRollback?.(action);\n return action;\n }\n}\n"],"mappings":";AAOA,IAAa,kBAAb,MAA6B;CAC3B,YAAY,AAAiB,SAAiC;EAAjC;;CAE7B,MAAM,QAAQ,OAAoB,QAAgB;AAChD,QAAM,KAAK,QAAQ,SAAS,OAAO,OAAO;EAC1C,MAAM,SAAyB;GAC7B;GACA;GACA,6BAAa,IAAI,MAAM;GACxB;AACD,OAAK,QAAQ,aAAa,OAAO;AACjC,SAAO"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CanaryStage, DeploymentMode, TrafficSplit } from "./types.js";
|
|
2
|
+
|
|
3
|
+
//#region src/traffic-shifter.d.ts
|
|
4
|
+
declare class TrafficShifter {
|
|
5
|
+
private readonly mode;
|
|
6
|
+
constructor(mode: DeploymentMode);
|
|
7
|
+
computeSplit(stage: CanaryStage): TrafficSplit;
|
|
8
|
+
}
|
|
9
|
+
//#endregion
|
|
10
|
+
export { TrafficShifter };
|
|
11
|
+
//# sourceMappingURL=traffic-shifter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traffic-shifter.d.ts","names":[],"sources":["../src/traffic-shifter.ts"],"sourcesContent":[],"mappings":";;;cAEa,cAAA;;EAAA,WAAA,CAAA,IAAA,EACwB,cADV;EACU,YAAA,CAAA,KAAA,EAEf,WAFe,CAAA,EAED,YAFC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/traffic-shifter.ts
|
|
2
|
+
var TrafficShifter = class {
|
|
3
|
+
constructor(mode) {
|
|
4
|
+
this.mode = mode;
|
|
5
|
+
}
|
|
6
|
+
computeSplit(stage) {
|
|
7
|
+
if (this.mode === "blue-green") return stage.percentage >= 100 ? {
|
|
8
|
+
stable: 0,
|
|
9
|
+
candidate: 1
|
|
10
|
+
} : {
|
|
11
|
+
stable: 1,
|
|
12
|
+
candidate: 0
|
|
13
|
+
};
|
|
14
|
+
const candidate = Math.min(Math.max(stage.percentage / 100, 0), 1);
|
|
15
|
+
return {
|
|
16
|
+
candidate,
|
|
17
|
+
stable: 1 - candidate
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
export { TrafficShifter };
|
|
24
|
+
//# sourceMappingURL=traffic-shifter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traffic-shifter.js","names":[],"sources":["../src/traffic-shifter.ts"],"sourcesContent":["import type { CanaryStage, DeploymentMode, TrafficSplit } from './types';\n\nexport class TrafficShifter {\n constructor(private readonly mode: DeploymentMode) {}\n\n computeSplit(stage: CanaryStage): TrafficSplit {\n if (this.mode === 'blue-green') {\n return stage.percentage >= 100\n ? { stable: 0, candidate: 1 }\n : { stable: 1, candidate: 0 };\n }\n\n const candidate = Math.min(Math.max(stage.percentage / 100, 0), 1);\n return {\n candidate,\n stable: 1 - candidate,\n };\n }\n}\n"],"mappings":";AAEA,IAAa,iBAAb,MAA4B;CAC1B,YAAY,AAAiB,MAAsB;EAAtB;;CAE7B,aAAa,OAAkC;AAC7C,MAAI,KAAK,SAAS,aAChB,QAAO,MAAM,cAAc,MACvB;GAAE,QAAQ;GAAG,WAAW;GAAG,GAC3B;GAAE,QAAQ;GAAG,WAAW;GAAG;EAGjC,MAAM,YAAY,KAAK,IAAI,KAAK,IAAI,MAAM,aAAa,KAAK,EAAE,EAAE,EAAE;AAClE,SAAO;GACL;GACA,QAAQ,IAAI;GACb"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
//#region src/types.d.ts
|
|
2
|
+
type DeploymentMode = 'canary' | 'blue-green';
|
|
3
|
+
interface OperationTarget {
|
|
4
|
+
name: string;
|
|
5
|
+
version: number;
|
|
6
|
+
namespace?: string;
|
|
7
|
+
description?: string;
|
|
8
|
+
}
|
|
9
|
+
interface DeploymentThresholds {
|
|
10
|
+
/** e.g. 0.01 = 1% */
|
|
11
|
+
errorRate: number;
|
|
12
|
+
latencyP50?: number;
|
|
13
|
+
latencyP95?: number;
|
|
14
|
+
latencyP99?: number;
|
|
15
|
+
throughputDrop?: number;
|
|
16
|
+
customEvaluator?: (metrics: DeploymentMetrics) => boolean;
|
|
17
|
+
}
|
|
18
|
+
interface CanaryStage {
|
|
19
|
+
percentage: number;
|
|
20
|
+
minDurationMs: number;
|
|
21
|
+
holdAfterMs?: number;
|
|
22
|
+
/** optional override thresholds */
|
|
23
|
+
thresholds?: Partial<DeploymentThresholds>;
|
|
24
|
+
label?: string;
|
|
25
|
+
}
|
|
26
|
+
interface DeploymentStrategy {
|
|
27
|
+
target: OperationTarget;
|
|
28
|
+
mode: DeploymentMode;
|
|
29
|
+
stages?: CanaryStage[];
|
|
30
|
+
thresholds: DeploymentThresholds;
|
|
31
|
+
metadata?: Record<string, unknown>;
|
|
32
|
+
}
|
|
33
|
+
interface DeploymentMetrics {
|
|
34
|
+
errorRate: number;
|
|
35
|
+
latencyP50: number;
|
|
36
|
+
latencyP95: number;
|
|
37
|
+
latencyP99: number;
|
|
38
|
+
throughput: number;
|
|
39
|
+
sampleSize?: number;
|
|
40
|
+
timestamp?: Date;
|
|
41
|
+
}
|
|
42
|
+
type MetricsProvider = (stage: CanaryStage, windowMs: number) => Promise<DeploymentMetrics>;
|
|
43
|
+
interface TrafficSplit {
|
|
44
|
+
stable: number;
|
|
45
|
+
candidate: number;
|
|
46
|
+
}
|
|
47
|
+
interface RollbackAction {
|
|
48
|
+
reason: string;
|
|
49
|
+
stage: CanaryStage;
|
|
50
|
+
triggeredAt: Date;
|
|
51
|
+
}
|
|
52
|
+
interface DeploymentEvent<TPayload = Record<string, unknown>> {
|
|
53
|
+
type: 'stage_started' | 'stage_passed' | 'stage_failed' | 'rolled_back' | 'completed' | 'blue_green_swapped';
|
|
54
|
+
payload?: TPayload;
|
|
55
|
+
timestamp: Date;
|
|
56
|
+
}
|
|
57
|
+
type DeploymentEventListener = (event: DeploymentEvent) => void;
|
|
58
|
+
//#endregion
|
|
59
|
+
export { CanaryStage, DeploymentEvent, DeploymentEventListener, DeploymentMetrics, DeploymentMode, DeploymentStrategy, DeploymentThresholds, MetricsProvider, OperationTarget, RollbackAction, TrafficSplit };
|
|
60
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"sourcesContent":[],"mappings":";KAAY,cAAA;AAAA,UAEK,eAAA,CAFS;EAET,IAAA,EAAA,MAAA;EAOA,OAAA,EAAA,MAAA;EAUA,SAAA,CAAA,EAAA,MAAW;EASX,WAAA,CAAA,EAAA,MAAA;;AAET,UArBS,oBAAA,CAqBT;EACG;EACG,SAAA,EAAA,MAAA;EACD,UAAA,CAAA,EAAA,MAAA;EAAM,UAAA,CAAA,EAAA,MAAA;EAGF,UAAA,CAAA,EAAA,MAAA;EAUL,cAAA,CAAA,EAAA,MAAe;EAClB,eAAA,CAAA,EAAA,CAAA,OAAA,EA/BqB,iBA+BrB,EAAA,GAAA,OAAA;;AAEJ,UA9BY,WAAA,CA8BZ;EAAO,UAAA,EAAA,MAAA;EAEK,aAAA,EAAY,MAAA;EAKZ,WAAA,CAAA,EAAA,MAAc;EAMd;EAA2B,UAAA,CAAA,EAtC7B,OAsC6B,CAtCrB,oBAsCqB,CAAA;EAQhC,KAAA,CAAA,EAAA,MAAA;;AACK,UA3CA,kBAAA,CA2CA;EAGL,MAAA,EA7CF,eA6CE;QA5CJ;WACG;cACG;aACD;;UAGI,iBAAA;;;;;;;cAOH;;KAGF,eAAA,WACH,kCAEJ,QAAQ;UAEI,YAAA;;;;UAKA,cAAA;;SAER;eACM;;UAGE,2BAA2B;;YAQhC;aACC;;KAGD,uBAAA,WAAkC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@contractspec/lib.progressive-delivery",
|
|
3
|
+
"version": "0.0.0-canary-20260113162409",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"types": "./dist/index.d.ts",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"README.md"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"publish:pkg": "bun publish --tolerate-republish --ignore-scripts --verbose",
|
|
12
|
+
"publish:pkg:canary": "bun publish:pkg --tag canary",
|
|
13
|
+
"build": "bun build:types && bun build:bundle",
|
|
14
|
+
"build:bundle": "tsdown",
|
|
15
|
+
"build:types": "tsc --noEmit",
|
|
16
|
+
"dev": "bun build:bundle --watch",
|
|
17
|
+
"clean": "rimraf dist .turbo",
|
|
18
|
+
"lint": "bun lint:fix",
|
|
19
|
+
"lint:fix": "eslint src --fix",
|
|
20
|
+
"lint:check": "eslint src"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"@contractspec/lib.observability": "0.0.0-canary-20260113162409"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@contractspec/tool.tsdown": "0.0.0-canary-20260113162409",
|
|
27
|
+
"@contractspec/tool.typescript": "0.0.0-canary-20260113162409",
|
|
28
|
+
"tsdown": "^0.19.0",
|
|
29
|
+
"typescript": "^5.9.3"
|
|
30
|
+
},
|
|
31
|
+
"exports": {
|
|
32
|
+
".": "./dist/index.js",
|
|
33
|
+
"./*": "./*"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public",
|
|
37
|
+
"exports": {
|
|
38
|
+
".": "./dist/index.js",
|
|
39
|
+
"./*": "./*"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|