@contractspec/lib.progressive-delivery 11.0.0 → 13.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,208 @@
1
+ // src/events.ts
2
+ class DeploymentEventBus {
3
+ listeners = 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) {
10
+ try {
11
+ listener(event);
12
+ } catch (error) {
13
+ console.error("[progressive-delivery] listener error", error);
14
+ }
15
+ }
16
+ }
17
+ }
18
+ // src/traffic-shifter.ts
19
+ class TrafficShifter {
20
+ mode;
21
+ constructor(mode) {
22
+ this.mode = mode;
23
+ }
24
+ computeSplit(stage) {
25
+ if (this.mode === "blue-green") {
26
+ return stage.percentage >= 100 ? { stable: 0, candidate: 1 } : { stable: 1, candidate: 0 };
27
+ }
28
+ const candidate = Math.min(Math.max(stage.percentage / 100, 0), 1);
29
+ return {
30
+ candidate,
31
+ stable: 1 - candidate
32
+ };
33
+ }
34
+ }
35
+ // src/canary-analyzer.ts
36
+ class CanaryAnalyzer {
37
+ defaults;
38
+ constructor(defaults) {
39
+ this.defaults = defaults;
40
+ }
41
+ evaluate(stage, metrics) {
42
+ const thresholds = { ...this.defaults, ...stage.thresholds };
43
+ const reasons = [];
44
+ if (metrics.errorRate > thresholds.errorRate) {
45
+ reasons.push(`errorRate ${metrics.errorRate.toFixed(4)} > ${thresholds.errorRate}`);
46
+ }
47
+ if (typeof thresholds.latencyP50 === "number" && metrics.latencyP50 > thresholds.latencyP50) {
48
+ reasons.push(`latencyP50 ${metrics.latencyP50}ms > ${thresholds.latencyP50}ms`);
49
+ }
50
+ if (typeof thresholds.latencyP95 === "number" && metrics.latencyP95 > thresholds.latencyP95) {
51
+ reasons.push(`latencyP95 ${metrics.latencyP95}ms > ${thresholds.latencyP95}ms`);
52
+ }
53
+ if (typeof thresholds.latencyP99 === "number" && metrics.latencyP99 > thresholds.latencyP99) {
54
+ reasons.push(`latencyP99 ${metrics.latencyP99}ms > ${thresholds.latencyP99}ms`);
55
+ }
56
+ if (typeof thresholds.throughputDrop === "number" && metrics.throughput < thresholds.throughputDrop) {
57
+ reasons.push(`throughput ${metrics.throughput} < ${thresholds.throughputDrop}`);
58
+ }
59
+ if (thresholds.customEvaluator && !thresholds.customEvaluator(metrics)) {
60
+ reasons.push("custom evaluator reported failure");
61
+ }
62
+ return {
63
+ status: reasons.length === 0 ? "pass" : "fail",
64
+ reasons,
65
+ metrics
66
+ };
67
+ }
68
+ }
69
+ // src/canary-controller.ts
70
+ var DEFAULT_STAGES = [
71
+ { percentage: 1, minDurationMs: 5 * 60 * 1000, label: "1%" },
72
+ { percentage: 10, minDurationMs: 5 * 60 * 1000, label: "10%" },
73
+ { percentage: 50, minDurationMs: 10 * 60 * 1000, label: "50%" },
74
+ { percentage: 100, minDurationMs: 15 * 60 * 1000, label: "100%" }
75
+ ];
76
+
77
+ class CanaryController {
78
+ options;
79
+ stages;
80
+ constructor(options) {
81
+ this.options = options;
82
+ this.stages = options.strategy.stages && options.strategy.stages.length > 0 ? options.strategy.stages : DEFAULT_STAGES;
83
+ }
84
+ getStageList() {
85
+ return [...this.stages];
86
+ }
87
+ async runStage(stage) {
88
+ this.options.eventBus?.emit({
89
+ type: "stage_started",
90
+ timestamp: new Date,
91
+ payload: { stage }
92
+ });
93
+ const metrics = await this.options.metricsProvider(stage, stage.minDurationMs);
94
+ const analysis = this.options.analyzer.evaluate(stage, metrics);
95
+ this.options.eventBus?.emit({
96
+ type: analysis.status === "pass" ? "stage_passed" : "stage_failed",
97
+ timestamp: new Date,
98
+ payload: { stage, metrics, analysis }
99
+ });
100
+ return analysis;
101
+ }
102
+ }
103
+ function createDefaultCanaryController(strategy, metricsProvider, eventBus) {
104
+ const analyzer = new CanaryAnalyzer(strategy.thresholds);
105
+ return new CanaryController({
106
+ strategy,
107
+ analyzer,
108
+ metricsProvider,
109
+ eventBus
110
+ });
111
+ }
112
+ // src/rollback-manager.ts
113
+ class RollbackManager {
114
+ options;
115
+ constructor(options) {
116
+ this.options = options;
117
+ }
118
+ async execute(stage, reason) {
119
+ await this.options.rollback(stage, reason);
120
+ const action = {
121
+ reason,
122
+ stage,
123
+ triggeredAt: new Date
124
+ };
125
+ this.options.onRollback?.(action);
126
+ return action;
127
+ }
128
+ }
129
+ // src/deployment-coordinator.ts
130
+ class DeploymentCoordinator {
131
+ options;
132
+ constructor(options) {
133
+ this.options = options;
134
+ }
135
+ async run() {
136
+ const stages = this.options.controller.getStageList();
137
+ for (const stage of stages) {
138
+ const split = this.options.trafficShifter.computeSplit(stage);
139
+ await this.options.applyTrafficSplit(stage, split);
140
+ const analysis = await this.options.controller.runStage(stage);
141
+ if (analysis.status === "fail") {
142
+ const action = await this.options.rollbackManager.execute(stage, analysis.reasons.join(", "));
143
+ this.options.eventBus?.emit({
144
+ type: "rolled_back",
145
+ timestamp: action.triggeredAt,
146
+ payload: { stage, reasons: analysis.reasons }
147
+ });
148
+ return {
149
+ status: "rolled_back",
150
+ failedStage: stage,
151
+ reasons: analysis.reasons
152
+ };
153
+ }
154
+ }
155
+ if (this.options.strategy.mode === "blue-green") {
156
+ this.options.eventBus?.emit({
157
+ type: "blue_green_swapped",
158
+ timestamp: new Date,
159
+ payload: { strategy: this.options.strategy }
160
+ });
161
+ }
162
+ this.options.eventBus?.emit({
163
+ type: "completed",
164
+ timestamp: new Date,
165
+ payload: { strategy: this.options.strategy }
166
+ });
167
+ return { status: "completed" };
168
+ }
169
+ }
170
+ // src/feature-flags.ts
171
+ var ContractSpecFeatureFlags = {
172
+ LIFECYCLE_DETECTION_ALPHA: "lifecycle_detection_alpha",
173
+ LIFECYCLE_ADVISOR_ALPHA: "lifecycle_advisor_alpha",
174
+ LIFECYCLE_MANAGED_SERVICE: "lifecycle_managed_service",
175
+ STUDIO_VISUAL_BUILDER: "studio_visual_builder",
176
+ STUDIO_AUTO_EVOLUTION: "studio_auto_evolution",
177
+ STUDIO_BYOK: "studio_byok",
178
+ STUDIO_DEDICATED_DEPLOYMENT: "studio_dedicated_deployment",
179
+ STUDIO_INTEGRATION_HUB: "studio_integration_hub",
180
+ STUDIO_KNOWLEDGE_HUB: "studio_knowledge_hub",
181
+ STUDIO_TEMPLATES: "studio_templates"
182
+ };
183
+ var lifecycleFlags = [
184
+ ContractSpecFeatureFlags.LIFECYCLE_DETECTION_ALPHA,
185
+ ContractSpecFeatureFlags.LIFECYCLE_ADVISOR_ALPHA,
186
+ ContractSpecFeatureFlags.LIFECYCLE_MANAGED_SERVICE
187
+ ];
188
+ var studioFlags = [
189
+ ContractSpecFeatureFlags.STUDIO_VISUAL_BUILDER,
190
+ ContractSpecFeatureFlags.STUDIO_AUTO_EVOLUTION,
191
+ ContractSpecFeatureFlags.STUDIO_BYOK,
192
+ ContractSpecFeatureFlags.STUDIO_DEDICATED_DEPLOYMENT,
193
+ ContractSpecFeatureFlags.STUDIO_INTEGRATION_HUB,
194
+ ContractSpecFeatureFlags.STUDIO_KNOWLEDGE_HUB,
195
+ ContractSpecFeatureFlags.STUDIO_TEMPLATES
196
+ ];
197
+ export {
198
+ studioFlags,
199
+ lifecycleFlags,
200
+ createDefaultCanaryController,
201
+ TrafficShifter,
202
+ RollbackManager,
203
+ DeploymentEventBus,
204
+ DeploymentCoordinator,
205
+ ContractSpecFeatureFlags,
206
+ CanaryController,
207
+ CanaryAnalyzer
208
+ };
@@ -1,15 +1,11 @@
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;
1
+ import type { CanaryStage, RollbackAction } from './types';
2
+ export interface RollbackManagerOptions {
3
+ rollback(stage: CanaryStage, reason: string): Promise<void> | void;
4
+ onRollback?: (action: RollbackAction) => void;
7
5
  }
8
- declare class RollbackManager {
9
- private readonly options;
10
- constructor(options: RollbackManagerOptions);
11
- execute(stage: CanaryStage, reason: string): Promise<RollbackAction>;
6
+ export declare class RollbackManager {
7
+ private readonly options;
8
+ constructor(options: RollbackManagerOptions);
9
+ execute(stage: CanaryStage, reason: string): Promise<RollbackAction>;
12
10
  }
13
- //#endregion
14
- export { RollbackManager, RollbackManagerOptions };
15
11
  //# sourceMappingURL=rollback-manager.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rollback-manager.d.ts","names":[],"sources":["../src/rollback-manager.ts"],"mappings":";;;UAEiB,sBAAA;EACf,QAAA,CAAS,KAAA,EAAO,WAAA,EAAa,MAAA,WAAiB,OAAA;EAC9C,UAAA,IAAc,MAAA,EAAQ,cAAA;AAAA;AAAA,cAGX,eAAA;EAAA,iBACkB,OAAA;cAAA,OAAA,EAAS,sBAAA;EAEhC,OAAA,CAAQ,KAAA,EAAO,WAAA,EAAa,MAAA,WAAc,OAAA,CAAA,cAAA;AAAA"}
1
+ {"version":3,"file":"rollback-manager.d.ts","sourceRoot":"","sources":["../src/rollback-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAE3D,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACnE,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,cAAc,KAAK,IAAI,CAAC;CAC/C;AAED,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,OAAO;gBAAP,OAAO,EAAE,sBAAsB;IAEtD,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM;CAUjD"}
@@ -1,11 +1,7 @@
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;
1
+ import type { CanaryStage, DeploymentMode, TrafficSplit } from './types';
2
+ export declare class TrafficShifter {
3
+ private readonly mode;
4
+ constructor(mode: DeploymentMode);
5
+ computeSplit(stage: CanaryStage): TrafficSplit;
8
6
  }
9
- //#endregion
10
- export { TrafficShifter };
11
7
  //# sourceMappingURL=traffic-shifter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"traffic-shifter.d.ts","names":[],"sources":["../src/traffic-shifter.ts"],"mappings":";;;cAEa,cAAA;EAAA,iBACkB,IAAA;cAAA,IAAA,EAAM,cAAA;EAEnC,YAAA,CAAa,KAAA,EAAO,WAAA,GAAc,YAAA;AAAA"}
1
+ {"version":3,"file":"traffic-shifter.d.ts","sourceRoot":"","sources":["../src/traffic-shifter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEzE,qBAAa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,cAAc;IAEjD,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,YAAY;CAa/C"}
package/dist/types.d.ts CHANGED
@@ -1,60 +1,57 @@
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 };
1
+ export type DeploymentMode = 'canary' | 'blue-green';
2
+ export interface OperationTarget {
3
+ name: string;
4
+ version: number;
5
+ namespace?: string;
6
+ description?: string;
7
+ }
8
+ export interface DeploymentThresholds {
9
+ /** e.g. 0.01 = 1% */
10
+ errorRate: number;
11
+ latencyP50?: number;
12
+ latencyP95?: number;
13
+ latencyP99?: number;
14
+ throughputDrop?: number;
15
+ customEvaluator?: (metrics: DeploymentMetrics) => boolean;
16
+ }
17
+ export interface CanaryStage {
18
+ percentage: number;
19
+ minDurationMs: number;
20
+ holdAfterMs?: number;
21
+ /** optional override thresholds */
22
+ thresholds?: Partial<DeploymentThresholds>;
23
+ label?: string;
24
+ }
25
+ export interface DeploymentStrategy {
26
+ target: OperationTarget;
27
+ mode: DeploymentMode;
28
+ stages?: CanaryStage[];
29
+ thresholds: DeploymentThresholds;
30
+ metadata?: Record<string, unknown>;
31
+ }
32
+ export interface DeploymentMetrics {
33
+ errorRate: number;
34
+ latencyP50: number;
35
+ latencyP95: number;
36
+ latencyP99: number;
37
+ throughput: number;
38
+ sampleSize?: number;
39
+ timestamp?: Date;
40
+ }
41
+ export type MetricsProvider = (stage: CanaryStage, windowMs: number) => Promise<DeploymentMetrics>;
42
+ export interface TrafficSplit {
43
+ stable: number;
44
+ candidate: number;
45
+ }
46
+ export interface RollbackAction {
47
+ reason: string;
48
+ stage: CanaryStage;
49
+ triggeredAt: Date;
50
+ }
51
+ export interface DeploymentEvent<TPayload = Record<string, unknown>> {
52
+ type: 'stage_started' | 'stage_passed' | 'stage_failed' | 'rolled_back' | 'completed' | 'blue_green_swapped';
53
+ payload?: TPayload;
54
+ timestamp: Date;
55
+ }
56
+ export type DeploymentEventListener = (event: DeploymentEvent) => void;
60
57
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";KAAY,cAAA;AAAA,UAEK,eAAA;EACf,IAAA;EACA,OAAA;EACA,SAAA;EACA,WAAA;AAAA;AAAA,UAGe,oBAAA;;EAEf,SAAA;EACA,UAAA;EACA,UAAA;EACA,UAAA;EACA,cAAA;EACA,eAAA,IAAmB,OAAA,EAAS,iBAAA;AAAA;AAAA,UAGb,WAAA;EACf,UAAA;EACA,aAAA;EACA,WAAA;EAXA;EAaA,UAAA,GAAa,OAAA,CAAQ,oBAAA;EACrB,KAAA;AAAA;AAAA,UAGe,kBAAA;EACf,MAAA,EAAQ,eAAA;EACR,IAAA,EAAM,cAAA;EACN,MAAA,GAAS,WAAA;EACT,UAAA,EAAY,oBAAA;EACZ,QAAA,GAAW,MAAA;AAAA;AAAA,UAGI,iBAAA;EACf,SAAA;EACA,UAAA;EACA,UAAA;EACA,UAAA;EACA,UAAA;EACA,UAAA;EACA,SAAA,GAAY,IAAA;AAAA;AAAA,KAGF,eAAA,IACV,KAAA,EAAO,WAAA,EACP,QAAA,aACG,OAAA,CAAQ,iBAAA;AAAA,UAEI,YAAA;EACf,MAAA;EACA,SAAA;AAAA;AAAA,UAGe,cAAA;EACf,MAAA;EACA,KAAA,EAAO,WAAA;EACP,WAAA,EAAa,IAAA;AAAA;AAAA,UAGE,eAAA,YAA2B,MAAA;EAC1C,IAAA;EAOA,OAAA,GAAU,QAAA;EACV,SAAA,EAAW,IAAA;AAAA;AAAA,KAGD,uBAAA,IAA2B,KAAA,EAAO,eAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG,QAAQ,GAAG,YAAY,CAAC;AAErD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,qBAAqB;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,KAAK,OAAO,CAAC;CAC3D;AAED,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mCAAmC;IACnC,UAAU,CAAC,EAAE,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAC3C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,eAAe,CAAC;IACxB,IAAI,EAAE,cAAc,CAAC;IACrB,MAAM,CAAC,EAAE,WAAW,EAAE,CAAC;IACvB,UAAU,EAAE,oBAAoB,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,IAAI,CAAC;CAClB;AAED,MAAM,MAAM,eAAe,GAAG,CAC5B,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,MAAM,KACb,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAEhC,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,WAAW,CAAC;IACnB,WAAW,EAAE,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,eAAe,CAAC,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACjE,IAAI,EACA,eAAe,GACf,cAAc,GACd,cAAc,GACd,aAAa,GACb,WAAW,GACX,oBAAoB,CAAC;IACzB,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB,SAAS,EAAE,IAAI,CAAC;CACjB;AAED,MAAM,MAAM,uBAAuB,GAAG,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contractspec/lib.progressive-delivery",
3
- "version": "11.0.0",
3
+ "version": "13.0.0",
4
4
  "type": "module",
5
5
  "types": "./dist/index.d.ts",
6
6
  "files": [
@@ -10,34 +10,39 @@
10
10
  "scripts": {
11
11
  "publish:pkg": "bun publish --tolerate-republish --ignore-scripts --verbose",
12
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",
13
+ "build": "bun run prebuild && bun run build:bundle && bun run build:types",
14
+ "build:bundle": "contractspec-bun-build transpile",
15
+ "build:types": "contractspec-bun-build types",
16
+ "dev": "contractspec-bun-build dev",
17
17
  "clean": "rimraf dist .turbo",
18
18
  "lint": "bun lint:fix",
19
19
  "lint:fix": "eslint src --fix",
20
- "lint:check": "eslint src"
20
+ "lint:check": "eslint src",
21
+ "prebuild": "contractspec-bun-build prebuild",
22
+ "typecheck": "tsc --noEmit"
21
23
  },
22
24
  "peerDependencies": {
23
- "@contractspec/lib.observability": "1.57.0"
25
+ "@contractspec/lib.observability": "1.59.0"
24
26
  },
25
27
  "devDependencies": {
26
- "@contractspec/tool.tsdown": "1.57.0",
27
- "@contractspec/tool.typescript": "1.57.0",
28
- "tsdown": "^0.20.3",
29
- "typescript": "^5.9.3"
28
+ "@contractspec/tool.typescript": "1.59.0",
29
+ "typescript": "^5.9.3",
30
+ "@contractspec/tool.bun": "1.58.0"
30
31
  },
31
32
  "exports": {
32
- ".": "./dist/index.js",
33
- "./*": "./*"
33
+ ".": "./src/index.ts"
34
34
  },
35
35
  "publishConfig": {
36
36
  "registry": "https://registry.npmjs.org/",
37
37
  "access": "public",
38
38
  "exports": {
39
- ".": "./dist/index.js",
40
- "./*": "./*"
39
+ ".": {
40
+ "types": "./dist/index.d.ts",
41
+ "bun": "./dist/index.js",
42
+ "node": "./dist/node/index.mjs",
43
+ "browser": "./dist/browser/index.js",
44
+ "default": "./dist/index.js"
45
+ }
41
46
  }
42
47
  }
43
48
  }
@@ -1,28 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,66 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,47 +0,0 @@
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
@@ -1 +0,0 @@
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.js DELETED
@@ -1,19 +0,0 @@
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
@@ -1 +0,0 @@
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"}