Skip to content

Commit 5ad5685

Browse files
authored
feat: add callback for invocation end with streaming functions (#245)
* feat: add callback for invocation end with streaming functions * add docs * call onInvocationEnd also if streaming is unused * ensure invocationend isn't called early for streams * remove unused symbol * fix last failing test
1 parent 95fb3df commit 5ad5685

File tree

6 files changed

+46
-5
lines changed

6 files changed

+46
-5
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ Executes a lambda given the `options` object, which is a dictionary where the ke
7070
| `envdestroy`|optional, destroy added environment on closing, default to false|
7171
| `verboseLevel`|optional, default 3. Level 2 dismiss handler() text, level 1 dismiss lambda-local text and level 0 dismiss also the result.|
7272
| `callback`|optional, lambda third parameter [callback][1]. When left out a Promise is returned|
73+
| `onInvocationEnd`|optional. called once the invocation ended. useful when awslambda.streamifyResponse is used to distinguish between end of response stream and end of invocation. |
7374
| `clientContext`|optional, used to populated clientContext property of lambda second parameter (context)
7475

7576
#### `lambdaLocal.setLogger(logger)`

src/lambdalocal.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ function _executeSync(opts) {
187187
timeoutMs = opts.timeoutMs || 3000,
188188
verboseLevel = opts.verboseLevel,
189189
callback = opts.callback,
190+
onInvocationEnd = opts.onInvocationEnd,
190191
clientContext = null;
191192

192193
if (opts.clientContext) {
@@ -295,7 +296,8 @@ function _executeSync(opts) {
295296
});
296297
}
297298
},
298-
clientContext: clientContext
299+
clientContext: clientContext,
300+
onInvocationEnd: onInvocationEnd,
299301
});
300302

301303
if(callback) context.callback = callback;

src/lib/context.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import utils = require('./utils.js');
1010
import mute = require('./mute.js');
11+
import { StreamingBody } from './streaming.js';
1112

1213
function Context() {
1314
this.logger = null;
@@ -39,6 +40,7 @@ function Context() {
3940
this.logStreamName = 'Stream name';
4041
this.identity = null;
4142
this.clientContext = null;
43+
this.onInvocationEnd = null;
4244

4345
/*
4446
* callback function called after done
@@ -112,6 +114,7 @@ Context.prototype._initialize = function(options) {
112114
this.unmute = mute();
113115
}
114116
this.clientContext = options.clientContext;
117+
this.onInvocationEnd = options.onInvocationEnd;
115118

116119
return;
117120
};
@@ -149,7 +152,12 @@ Context.prototype.generate_context = function(){
149152
logStreamName: this.logStreamName,
150153
identity: this.identity,
151154
clientContext: this.clientContext,
152-
_stopped: false
155+
_stopped: false,
156+
157+
// INTERNAL
158+
__lambdaLocal: {
159+
onInvocationEnd: this.onInvocationEnd
160+
},
153161
};
154162
return ctx;
155163
}
@@ -207,6 +215,12 @@ Context.prototype.done = function(err, message) {
207215
}
208216
}
209217
this.finalCallback(); //Destroy env...
218+
219+
const isStream = typeof message === "object" && message?.body instanceof StreamingBody
220+
if (!isStream) {
221+
this.onInvocationEnd?.();
222+
}
223+
210224
/*
211225
The finalCallback method will be instantly called if 'this.callbackWaitsForEmptyEventLoop' is False
212226
Otherwise, lambda-local will wait for an empty loop then call it.

src/lib/streaming.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@ function streamifyResponse(handler) {
1919
if (!body.headersSent) {
2020
body.sendHeader(metadata)
2121
}
22+
context.__lambdaLocal.onInvocationEnd?.();
2223
} catch (error) {
2324
reject(error);
25+
context.__lambdaLocal.onInvocationEnd?.(error);
2426
}
2527
});
2628
}
2729

28-
class StreamingBody extends PassThrough {
30+
export class StreamingBody extends PassThrough {
2931
constructor(private readonly resolve: (metadata) => void) {
3032
super();
3133
}

test/functs/test-func-streaming.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ exports.handler = awslambda.streamifyResponse(
1818
responseStream.write("bar");
1919
responseStream.end();
2020
}, 100);
21+
22+
await new Promise(resolve => setTimeout(resolve, 200));
2123
}
2224
);
2325

test/test.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,16 @@ describe("- Testing lambdalocal.js", function () {
364364
assert.equal(data.result, "testvar");
365365
});
366366
});
367+
it('should call onInvocationEnd', function () {
368+
var lambdalocal = require(lambdalocal_path);
369+
lambdalocal.setLogger(winston);
370+
let invocationEnded = 0
371+
opts.onInvocationEnd = () => invocationEnded++
372+
return lambdalocal.execute(opts).then(function (data) {
373+
assert.equal(data.result, "testvar");
374+
assert.equal(invocationEnded, 1)
375+
});
376+
});
367377
it('should be stateless', function () {
368378
var lambdalocal = require(lambdalocal_path);
369379
lambdalocal.setLogger(winston);
@@ -425,13 +435,17 @@ describe("- Testing lambdalocal.js", function () {
425435
it('should return a readable stream as `body`', function () {
426436
var lambdalocal = require(lambdalocal_path);
427437
lambdalocal.setLogger(winston);
438+
let invocationEnded = 0
428439
return lambdalocal.execute({
429440
event: require(path.join(__dirname, "./events/test-event.js")),
430441
lambdaPath: path.join(__dirname, "./functs/test-func-streaming.js"),
431442
lambdaHandler: functionName,
432443
callbackWaitsForEmptyEventLoop: false,
433444
timeoutMs: timeoutMs,
434-
verboseLevel: 1
445+
verboseLevel: 1,
446+
onInvocationEnd() {
447+
invocationEnded++
448+
}
435449
}).then(function (data) {
436450
assert.deepEqual(
437451
data.headers,
@@ -448,7 +462,13 @@ describe("- Testing lambdalocal.js", function () {
448462
data.body.on("end", () => {
449463
assert.deepEqual(chunks, ["foo", "bar"])
450464
assert.closeTo(times[1] - times[0], 100, 50)
451-
resolve()
465+
466+
assert.equal(invocationEnded, 0)
467+
468+
setTimeout(() => {
469+
assert.equal(invocationEnded, 1)
470+
resolve()
471+
}, 200)
452472
});
453473
})
454474
})

0 commit comments

Comments
 (0)