@friggframework/admin-scripts 2.0.0-next.103 → 2.0.0-next.105
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/index.js +14 -0
- package/package.json +6 -6
- package/src/adapters/__tests__/aws-scheduler-adapter.test.js +54 -0
- package/src/adapters/__tests__/scheduler-adapter-factory.test.js +49 -0
- package/src/adapters/aws-scheduler-adapter.js +21 -9
- package/src/adapters/scheduler-adapter-factory.js +40 -0
- package/src/application/__tests__/report-runner.test.js +428 -0
- package/src/application/admin-script-context.js +12 -0
- package/src/application/report-runner.js +273 -0
- package/src/infrastructure/__tests__/report-executor-handler.test.js +314 -0
- package/src/infrastructure/__tests__/report-router.test.js +525 -0
- package/src/infrastructure/bootstrap.js +95 -15
- package/src/infrastructure/report-executor-handler.js +202 -0
- package/src/infrastructure/report-router.js +421 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
const { createReportRunner } = require('../report-runner');
|
|
2
|
+
const { ScriptFactory } = require('../script-factory');
|
|
3
|
+
|
|
4
|
+
class FakeReport {
|
|
5
|
+
static Definition = {
|
|
6
|
+
name: 'fake',
|
|
7
|
+
version: '1.0.0',
|
|
8
|
+
runModes: ['live', 'recorded', 'snapshot'],
|
|
9
|
+
output: { format: 'json' },
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: { n: { type: 'integer' } },
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
constructor(params = {}) {
|
|
17
|
+
this.context = params.context || null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async execute(frigg, params) {
|
|
21
|
+
return { ok: true, n: params.n ?? 0, friggReceived: frigg };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class CsvReport {
|
|
26
|
+
static Definition = {
|
|
27
|
+
name: 'csv',
|
|
28
|
+
version: '1.0.0',
|
|
29
|
+
runModes: ['recorded'],
|
|
30
|
+
output: { format: 'csv' },
|
|
31
|
+
};
|
|
32
|
+
async execute() {
|
|
33
|
+
return { file: 'a,b\n1,2\n', summary: { rows: 1 }, fileType: 'csv' };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const FRIGG = { integrations: {}, integrationMappings: {}, usage: {} };
|
|
38
|
+
|
|
39
|
+
function makeRunner() {
|
|
40
|
+
const reportCommands = {
|
|
41
|
+
createExecution: jest.fn(),
|
|
42
|
+
completeExecution: jest.fn(),
|
|
43
|
+
updateExecutionState: jest.fn(),
|
|
44
|
+
};
|
|
45
|
+
const runner = createReportRunner({
|
|
46
|
+
reportFactory: new ScriptFactory([FakeReport, CsvReport]),
|
|
47
|
+
reportCommands,
|
|
48
|
+
friggCommands: FRIGG,
|
|
49
|
+
});
|
|
50
|
+
return { runner, reportCommands };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe('ReportRunner', () => {
|
|
54
|
+
it('requires a reportFactory', () => {
|
|
55
|
+
expect(() => createReportRunner({})).toThrow(/reportFactory/);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe('live mode', () => {
|
|
59
|
+
it('computes inline, returns COMPLETED, and persists NOTHING', async () => {
|
|
60
|
+
const { runner, reportCommands } = makeRunner();
|
|
61
|
+
|
|
62
|
+
const result = await runner.execute('fake', { n: 5 }, { mode: 'live' });
|
|
63
|
+
|
|
64
|
+
expect(result.status).toBe('COMPLETED');
|
|
65
|
+
expect(result.mode).toBe('live');
|
|
66
|
+
expect(result.output).toMatchObject({ ok: true, n: 5 });
|
|
67
|
+
// The report reads via the injected frigg command bundle.
|
|
68
|
+
expect(result.output.friggReceived).toBe(FRIGG);
|
|
69
|
+
// The core invariant: live creates no execution record.
|
|
70
|
+
expect(reportCommands.createExecution).not.toHaveBeenCalled();
|
|
71
|
+
expect(reportCommands.completeExecution).not.toHaveBeenCalled();
|
|
72
|
+
expect(reportCommands.updateExecutionState).not.toHaveBeenCalled();
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('defaults to the first runMode when none is given (live)', async () => {
|
|
76
|
+
const { runner, reportCommands } = makeRunner();
|
|
77
|
+
|
|
78
|
+
const result = await runner.execute('fake', {});
|
|
79
|
+
|
|
80
|
+
expect(result.mode).toBe('live');
|
|
81
|
+
expect(reportCommands.createExecution).not.toHaveBeenCalled();
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('rejects a mode the report does not allow', async () => {
|
|
86
|
+
const { runner } = makeRunner();
|
|
87
|
+
await expect(
|
|
88
|
+
runner.execute('fake', {}, { mode: 'bogus' })
|
|
89
|
+
).rejects.toMatchObject({ code: 'INVALID_MODE' });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it('rejects input that violates the inputSchema', async () => {
|
|
93
|
+
const { runner } = makeRunner();
|
|
94
|
+
await expect(
|
|
95
|
+
runner.execute('fake', { n: 'not-a-number' }, { mode: 'live' })
|
|
96
|
+
).rejects.toMatchObject({ code: 'INVALID_INPUT' });
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('rejects a non-JSON output format in live mode (JSON-only inline)', async () => {
|
|
100
|
+
class LiveCsv {
|
|
101
|
+
static Definition = {
|
|
102
|
+
name: 'live-csv',
|
|
103
|
+
version: '1.0.0',
|
|
104
|
+
runModes: ['live'],
|
|
105
|
+
output: { format: 'csv' },
|
|
106
|
+
};
|
|
107
|
+
async execute() {
|
|
108
|
+
return {};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const runner = createReportRunner({
|
|
112
|
+
reportFactory: new ScriptFactory([LiveCsv]),
|
|
113
|
+
friggCommands: FRIGG,
|
|
114
|
+
});
|
|
115
|
+
await expect(
|
|
116
|
+
runner.execute('live-csv', {}, { mode: 'live' })
|
|
117
|
+
).rejects.toMatchObject({ code: 'ARTIFACT_STORAGE_UNAVAILABLE' });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('non-JSON artifact output (recorded / snapshot)', () => {
|
|
121
|
+
it('stores the file and completes with summary + artifact ref', async () => {
|
|
122
|
+
const reportCommands = {
|
|
123
|
+
createExecution: jest.fn().mockResolvedValue({ id: 'exec-a' }),
|
|
124
|
+
updateExecutionState: jest.fn().mockResolvedValue({}),
|
|
125
|
+
completeExecution: jest.fn().mockResolvedValue({ success: true }),
|
|
126
|
+
};
|
|
127
|
+
const artifactRepository = {
|
|
128
|
+
put: jest.fn().mockResolvedValue({
|
|
129
|
+
bucket: 'bkt',
|
|
130
|
+
key: 'reports/exec-a/csv.csv',
|
|
131
|
+
}),
|
|
132
|
+
};
|
|
133
|
+
const runner = createReportRunner({
|
|
134
|
+
reportFactory: new ScriptFactory([CsvReport]),
|
|
135
|
+
reportCommands,
|
|
136
|
+
friggCommands: FRIGG,
|
|
137
|
+
artifactRepository,
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const result = await runner.execute(
|
|
141
|
+
'csv',
|
|
142
|
+
{},
|
|
143
|
+
{ mode: 'recorded', trigger: 'MANUAL' }
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
expect(result.status).toBe('COMPLETED');
|
|
147
|
+
expect(result.mode).toBe('recorded');
|
|
148
|
+
expect(result.artifact).toEqual({
|
|
149
|
+
bucket: 'bkt',
|
|
150
|
+
key: 'reports/exec-a/csv.csv',
|
|
151
|
+
});
|
|
152
|
+
expect(result.summary).toEqual({ rows: 1 });
|
|
153
|
+
// No inline output for non-json.
|
|
154
|
+
expect(result.output).toBeUndefined();
|
|
155
|
+
|
|
156
|
+
expect(artifactRepository.put).toHaveBeenCalledTimes(1);
|
|
157
|
+
const [key, body, contentType] =
|
|
158
|
+
artifactRepository.put.mock.calls[0];
|
|
159
|
+
expect(key).toMatch(/^reports\/exec-a\/csv-.+\.csv$/);
|
|
160
|
+
expect(body).toBe('a,b\n1,2\n');
|
|
161
|
+
expect(contentType).toBe('text/csv');
|
|
162
|
+
|
|
163
|
+
expect(reportCommands.completeExecution).toHaveBeenCalledWith(
|
|
164
|
+
'exec-a',
|
|
165
|
+
expect.objectContaining({
|
|
166
|
+
state: 'COMPLETED',
|
|
167
|
+
summary: { rows: 1 },
|
|
168
|
+
artifact: { bucket: 'bkt', key: 'reports/exec-a/csv.csv' },
|
|
169
|
+
})
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('records FAILED when artifact storage throws', async () => {
|
|
174
|
+
const reportCommands = {
|
|
175
|
+
createExecution: jest.fn().mockResolvedValue({ id: 'exec-b' }),
|
|
176
|
+
updateExecutionState: jest.fn().mockResolvedValue({}),
|
|
177
|
+
completeExecution: jest.fn().mockResolvedValue({ success: true }),
|
|
178
|
+
};
|
|
179
|
+
const artifactRepository = {
|
|
180
|
+
put: jest.fn().mockRejectedValue(new Error('S3 down')),
|
|
181
|
+
};
|
|
182
|
+
const runner = createReportRunner({
|
|
183
|
+
reportFactory: new ScriptFactory([CsvReport]),
|
|
184
|
+
reportCommands,
|
|
185
|
+
friggCommands: FRIGG,
|
|
186
|
+
artifactRepository,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const result = await runner.execute(
|
|
190
|
+
'csv',
|
|
191
|
+
{},
|
|
192
|
+
{ mode: 'recorded', trigger: 'MANUAL' }
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
expect(result.status).toBe('FAILED');
|
|
196
|
+
expect(result.error.message).toBe('S3 down');
|
|
197
|
+
expect(reportCommands.completeExecution).toHaveBeenCalledWith(
|
|
198
|
+
'exec-b',
|
|
199
|
+
expect.objectContaining({ state: 'FAILED' })
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
describe('recorded / snapshot modes', () => {
|
|
205
|
+
it('creates a record, marks RUNNING, then completes COMPLETED', async () => {
|
|
206
|
+
const { runner, reportCommands } = makeRunner();
|
|
207
|
+
reportCommands.createExecution.mockResolvedValue({ id: 'exec-1' });
|
|
208
|
+
reportCommands.updateExecutionState.mockResolvedValue({});
|
|
209
|
+
reportCommands.completeExecution.mockResolvedValue({ success: true });
|
|
210
|
+
|
|
211
|
+
const result = await runner.execute(
|
|
212
|
+
'fake',
|
|
213
|
+
{ n: 2 },
|
|
214
|
+
{ mode: 'recorded', trigger: 'MANUAL' }
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
expect(result.status).toBe('COMPLETED');
|
|
218
|
+
expect(result.mode).toBe('recorded');
|
|
219
|
+
expect(result.executionId).toBe('exec-1');
|
|
220
|
+
expect(result.output).toMatchObject({ ok: true, n: 2 });
|
|
221
|
+
|
|
222
|
+
expect(reportCommands.createExecution).toHaveBeenCalledTimes(1);
|
|
223
|
+
expect(reportCommands.updateExecutionState).toHaveBeenCalledWith(
|
|
224
|
+
'exec-1',
|
|
225
|
+
'RUNNING'
|
|
226
|
+
);
|
|
227
|
+
expect(reportCommands.completeExecution).toHaveBeenCalledWith(
|
|
228
|
+
'exec-1',
|
|
229
|
+
expect.objectContaining({ state: 'COMPLETED', output: result.output })
|
|
230
|
+
);
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('passes seriesName for snapshot mode (defaulting to report name)', async () => {
|
|
234
|
+
const { runner, reportCommands } = makeRunner();
|
|
235
|
+
reportCommands.createExecution.mockResolvedValue({ id: 'exec-2' });
|
|
236
|
+
reportCommands.updateExecutionState.mockResolvedValue({});
|
|
237
|
+
reportCommands.completeExecution.mockResolvedValue({ success: true });
|
|
238
|
+
|
|
239
|
+
await runner.execute('fake', {}, { mode: 'snapshot', trigger: 'SCHEDULED' });
|
|
240
|
+
|
|
241
|
+
expect(reportCommands.createExecution).toHaveBeenCalledWith(
|
|
242
|
+
expect.objectContaining({ mode: 'snapshot', seriesName: 'fake' })
|
|
243
|
+
);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('does NOT set seriesName for recorded mode', async () => {
|
|
247
|
+
const { runner, reportCommands } = makeRunner();
|
|
248
|
+
reportCommands.createExecution.mockResolvedValue({ id: 'exec-3' });
|
|
249
|
+
reportCommands.updateExecutionState.mockResolvedValue({});
|
|
250
|
+
reportCommands.completeExecution.mockResolvedValue({ success: true });
|
|
251
|
+
|
|
252
|
+
await runner.execute('fake', {}, { mode: 'recorded', trigger: 'MANUAL' });
|
|
253
|
+
|
|
254
|
+
const arg = reportCommands.createExecution.mock.calls[0][0];
|
|
255
|
+
expect(arg.seriesName).toBeUndefined();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('resumes an existing record without creating a new one', async () => {
|
|
259
|
+
const { runner, reportCommands } = makeRunner();
|
|
260
|
+
reportCommands.updateExecutionState.mockResolvedValue({});
|
|
261
|
+
reportCommands.completeExecution.mockResolvedValue({ success: true });
|
|
262
|
+
|
|
263
|
+
const result = await runner.execute(
|
|
264
|
+
'fake',
|
|
265
|
+
{},
|
|
266
|
+
{ mode: 'recorded', trigger: 'QUEUE', executionId: 'given-1' }
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
expect(reportCommands.createExecution).not.toHaveBeenCalled();
|
|
270
|
+
expect(result.executionId).toBe('given-1');
|
|
271
|
+
expect(reportCommands.updateExecutionState).toHaveBeenCalledWith(
|
|
272
|
+
'given-1',
|
|
273
|
+
'RUNNING'
|
|
274
|
+
);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it('records FAILED when the report throws', async () => {
|
|
278
|
+
class BoomReport {
|
|
279
|
+
static Definition = {
|
|
280
|
+
name: 'boom',
|
|
281
|
+
version: '1.0.0',
|
|
282
|
+
runModes: ['recorded'],
|
|
283
|
+
output: { format: 'json' },
|
|
284
|
+
};
|
|
285
|
+
async execute() {
|
|
286
|
+
throw new Error('kaboom');
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const reportCommands = {
|
|
290
|
+
createExecution: jest.fn().mockResolvedValue({ id: 'exec-9' }),
|
|
291
|
+
updateExecutionState: jest.fn().mockResolvedValue({}),
|
|
292
|
+
completeExecution: jest.fn().mockResolvedValue({ success: true }),
|
|
293
|
+
};
|
|
294
|
+
const runner = createReportRunner({
|
|
295
|
+
reportFactory: new ScriptFactory([BoomReport]),
|
|
296
|
+
reportCommands,
|
|
297
|
+
friggCommands: FRIGG,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const result = await runner.execute(
|
|
301
|
+
'boom',
|
|
302
|
+
{},
|
|
303
|
+
{ mode: 'recorded', trigger: 'MANUAL' }
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
expect(result.status).toBe('FAILED');
|
|
307
|
+
expect(result.error.message).toBe('kaboom');
|
|
308
|
+
expect(reportCommands.completeExecution).toHaveBeenCalledWith(
|
|
309
|
+
'exec-9',
|
|
310
|
+
expect.objectContaining({ state: 'FAILED' })
|
|
311
|
+
);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('throws when the execution record cannot be created', async () => {
|
|
315
|
+
const { runner, reportCommands } = makeRunner();
|
|
316
|
+
reportCommands.createExecution.mockResolvedValue({
|
|
317
|
+
error: 500,
|
|
318
|
+
reason: 'DB down',
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
await expect(
|
|
322
|
+
runner.execute('fake', {}, { mode: 'recorded', trigger: 'MANUAL' })
|
|
323
|
+
).rejects.toThrow('DB down');
|
|
324
|
+
});
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
describe('self-requeue continuation', () => {
|
|
328
|
+
// Opts into chunking: yields a continuation marker while the Lambda is
|
|
329
|
+
// low on time and no resume state has arrived yet; otherwise finishes.
|
|
330
|
+
class ChunkedReport {
|
|
331
|
+
static Definition = {
|
|
332
|
+
name: 'chunked',
|
|
333
|
+
version: '1.0.0',
|
|
334
|
+
runModes: ['recorded'],
|
|
335
|
+
output: { format: 'json' },
|
|
336
|
+
};
|
|
337
|
+
async execute(frigg, params, context) {
|
|
338
|
+
const remaining = context.getRemainingTimeInMillis();
|
|
339
|
+
if (remaining < 60000 && !params.__resume) {
|
|
340
|
+
return { __continuation: { cursor: 42 } };
|
|
341
|
+
}
|
|
342
|
+
return { done: true };
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function makeChunkedRunner() {
|
|
347
|
+
const reportCommands = {
|
|
348
|
+
createExecution: jest
|
|
349
|
+
.fn()
|
|
350
|
+
.mockResolvedValue({ id: 'exec-c' }),
|
|
351
|
+
updateExecutionState: jest.fn().mockResolvedValue({}),
|
|
352
|
+
completeExecution: jest.fn().mockResolvedValue({ success: true }),
|
|
353
|
+
appendExecutionLog: jest.fn().mockResolvedValue({}),
|
|
354
|
+
};
|
|
355
|
+
const runner = createReportRunner({
|
|
356
|
+
reportFactory: new ScriptFactory([ChunkedReport]),
|
|
357
|
+
reportCommands,
|
|
358
|
+
friggCommands: FRIGG,
|
|
359
|
+
});
|
|
360
|
+
return { runner, reportCommands };
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
it('returns CONTINUE without completing when the report yields on low time', async () => {
|
|
364
|
+
const { runner, reportCommands } = makeChunkedRunner();
|
|
365
|
+
const lambdaContext = {
|
|
366
|
+
getRemainingTimeInMillis: jest.fn().mockReturnValue(5000),
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const result = await runner.execute(
|
|
370
|
+
'chunked',
|
|
371
|
+
{},
|
|
372
|
+
{ mode: 'recorded', trigger: 'QUEUE', lambdaContext }
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
expect(result.status).toBe('CONTINUE');
|
|
376
|
+
expect(result.executionId).toBe('exec-c');
|
|
377
|
+
expect(result.continuation).toEqual({ cursor: 42 });
|
|
378
|
+
// Stays RUNNING — not completed — between hops.
|
|
379
|
+
expect(reportCommands.completeExecution).not.toHaveBeenCalled();
|
|
380
|
+
expect(reportCommands.appendExecutionLog).toHaveBeenCalledWith(
|
|
381
|
+
'exec-c',
|
|
382
|
+
expect.objectContaining({
|
|
383
|
+
message: expect.stringMatching(/continuation/i),
|
|
384
|
+
})
|
|
385
|
+
);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it('completes normally when time is ample (no continuation)', async () => {
|
|
389
|
+
const { runner, reportCommands } = makeChunkedRunner();
|
|
390
|
+
const lambdaContext = {
|
|
391
|
+
getRemainingTimeInMillis: jest.fn().mockReturnValue(900000),
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const result = await runner.execute(
|
|
395
|
+
'chunked',
|
|
396
|
+
{},
|
|
397
|
+
{ mode: 'recorded', trigger: 'QUEUE', lambdaContext }
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
expect(result.status).toBe('COMPLETED');
|
|
401
|
+
expect(reportCommands.completeExecution).toHaveBeenCalledWith(
|
|
402
|
+
'exec-c',
|
|
403
|
+
expect.objectContaining({ state: 'COMPLETED' })
|
|
404
|
+
);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
it('finishes when resumed with the prior continuation state', async () => {
|
|
408
|
+
const { runner, reportCommands } = makeChunkedRunner();
|
|
409
|
+
const lambdaContext = {
|
|
410
|
+
getRemainingTimeInMillis: jest.fn().mockReturnValue(5000),
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
const result = await runner.execute(
|
|
414
|
+
'chunked',
|
|
415
|
+
{ __resume: { cursor: 42 } },
|
|
416
|
+
{
|
|
417
|
+
mode: 'recorded',
|
|
418
|
+
trigger: 'QUEUE',
|
|
419
|
+
executionId: 'exec-c',
|
|
420
|
+
lambdaContext,
|
|
421
|
+
}
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
expect(result.status).toBe('COMPLETED');
|
|
425
|
+
expect(reportCommands.createExecution).not.toHaveBeenCalled();
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
});
|
|
@@ -35,6 +35,18 @@ class AdminScriptContext {
|
|
|
35
35
|
// Injected by bootstrap.js — the context never reaches for repositories
|
|
36
36
|
// or command factories itself.
|
|
37
37
|
this.commands = params.commands || null;
|
|
38
|
+
|
|
39
|
+
// Set by the executor; reports read getRemainingTimeInMillis() to yield
|
|
40
|
+
// before the Lambda timeout and self-requeue.
|
|
41
|
+
this.lambdaContext = params.lambdaContext || null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Infinity when there is no Lambda context (live runs, local dev, tests).
|
|
45
|
+
getRemainingTimeInMillis() {
|
|
46
|
+
return this.lambdaContext &&
|
|
47
|
+
typeof this.lambdaContext.getRemainingTimeInMillis === 'function'
|
|
48
|
+
? this.lambdaContext.getRemainingTimeInMillis()
|
|
49
|
+
: Infinity;
|
|
38
50
|
}
|
|
39
51
|
|
|
40
52
|
// ==================== INTEGRATION INSTANTIATION ====================
|