Skip to content

Commit 24758e3

Browse files
VirtueMeclaude
andauthored
refactor(apiGateway): replace .then() chain with CompilationPipeline (#765)
- Add CompilationPipeline class that runs an ordered list of step method names sequentially on a given context (uses native Promise, not Bluebird) - Replace 12-step hardcoded .then() chain in index.js with apiGatewayPipeline.run(this) — steps are now a named string array - Add 4 unit tests covering ordering, context binding, async sequencing, and empty pipeline Part of #761 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
1 parent fbd6cc0 commit 24758e3

3 files changed

Lines changed: 107 additions & 13 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
'use strict';
2+
3+
/**
4+
* Runs an ordered list of compiler steps sequentially on a given context.
5+
* Each step is a method name that exists on the context object.
6+
* Steps are reorderable and conditionally includable without modifying callers.
7+
*/
8+
class CompilationPipeline {
9+
constructor(steps) {
10+
this.steps = steps;
11+
}
12+
13+
run(context) {
14+
return this.steps.reduce(
15+
(promise, step) => promise.then(() => context[step]()),
16+
Promise.resolve(),
17+
);
18+
}
19+
}
20+
21+
module.exports = CompilationPipeline;
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
'use strict';
2+
3+
const expect = require('chai').expect;
4+
const sinon = require('sinon');
5+
const CompilationPipeline = require('./compilationPipeline');
6+
7+
describe('CompilationPipeline', () => {
8+
it('should call all steps in order', async () => {
9+
const callOrder = [];
10+
const context = {
11+
stepA: sinon.stub().callsFake(() => {
12+
callOrder.push('A');
13+
return Promise.resolve();
14+
}),
15+
stepB: sinon.stub().callsFake(() => {
16+
callOrder.push('B');
17+
return Promise.resolve();
18+
}),
19+
stepC: sinon.stub().callsFake(() => {
20+
callOrder.push('C');
21+
return Promise.resolve();
22+
}),
23+
};
24+
25+
const pipeline = new CompilationPipeline(['stepA', 'stepB', 'stepC']);
26+
await pipeline.run(context);
27+
28+
expect(callOrder).to.deep.equal(['A', 'B', 'C']);
29+
});
30+
31+
it('should call each step on the given context', async () => {
32+
const context = {
33+
step: sinon.stub().returns(Promise.resolve()),
34+
};
35+
36+
const pipeline = new CompilationPipeline(['step']);
37+
await pipeline.run(context);
38+
39+
expect(context.step.callCount).to.equal(1);
40+
});
41+
42+
it('should wait for an async step to resolve before calling the next', async () => {
43+
let firstResolved = false;
44+
const context = {
45+
stepA: sinon.stub().returns(
46+
new Promise((resolve) => {
47+
setTimeout(() => {
48+
firstResolved = true;
49+
resolve();
50+
}, 10);
51+
}),
52+
),
53+
stepB: sinon.stub().callsFake(() => {
54+
expect(firstResolved).to.equal(true);
55+
return Promise.resolve();
56+
}),
57+
};
58+
59+
const pipeline = new CompilationPipeline(['stepA', 'stepB']);
60+
await pipeline.run(context);
61+
62+
expect(context.stepB.callCount).to.equal(1);
63+
});
64+
65+
it('should resolve immediately with an empty steps array', async () => {
66+
const pipeline = new CompilationPipeline([]);
67+
await pipeline.run({});
68+
});
69+
});

lib/index.js

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const httpLambdaPermissions = require('./deploy/events/apiGateway/lambdaPermissi
2323
const httpDeployment = require('./deploy/events/apiGateway/deployment');
2424
const httpRestApi = require('./deploy/events/apiGateway/restApi');
2525
const httpInfo = require('./deploy/events/apiGateway/endpointInfo');
26+
const CompilationPipeline = require('./deploy/events/apiGateway/compilationPipeline');
2627
const compileScheduledEvents = require('./deploy/events/schedule/compileScheduledEvents');
2728
const compileCloudWatchEventEvents = require('./deploy/events/cloudWatchEvent/compileCloudWatchEventEvents');
2829
const invoke = require('./invoke/invoke');
@@ -31,6 +32,21 @@ const naming = require('./naming');
3132

3233
const logger = require('./utils/logger');
3334

35+
const apiGatewayPipeline = new CompilationPipeline([
36+
'compileRestApi',
37+
'compileResources',
38+
'compileMethods',
39+
'compileRequestValidators',
40+
'compileAuthorizers',
41+
'compileHttpLambdaPermissions',
42+
'compileCors',
43+
'compileHttpIamRole',
44+
'compileDeployment',
45+
'compileApiKeys',
46+
'compileUsagePlan',
47+
'compileUsagePlanKeys',
48+
]);
49+
3450
class ServerlessStepFunctions {
3551
constructor(serverless, options, v3Api) {
3652
this.serverless = serverless;
@@ -135,19 +151,7 @@ class ServerlessStepFunctions {
135151
return BbPromise.resolve();
136152
}
137153

138-
return BbPromise.bind(this)
139-
.then(this.compileRestApi)
140-
.then(this.compileResources)
141-
.then(this.compileMethods)
142-
.then(this.compileRequestValidators)
143-
.then(this.compileAuthorizers)
144-
.then(this.compileHttpLambdaPermissions)
145-
.then(this.compileCors)
146-
.then(this.compileHttpIamRole)
147-
.then(this.compileDeployment)
148-
.then(this.compileApiKeys)
149-
.then(this.compileUsagePlan)
150-
.then(this.compileUsagePlanKeys);
154+
return apiGatewayPipeline.run(this);
151155
}).then(() => this.compileCloudWatchEventEvents()),
152156
'after:deploy:deploy': () => BbPromise.bind(this)
153157
.then(this.getEndpointInfo)

0 commit comments

Comments
 (0)