local-serverless-stack 0.6.0 → 0.8.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.
- package/CHANGELOG.md +54 -0
- package/README.md +99 -11
- package/bin/cli.js +31 -3
- package/dist/server/engine/backends/localstack-backend.d.ts +25 -0
- package/dist/server/engine/backends/localstack-backend.d.ts.map +1 -0
- package/dist/server/engine/backends/localstack-backend.js +199 -0
- package/dist/server/engine/backends/localstack-backend.js.map +1 -0
- package/dist/server/engine/backends/self-backend.d.ts +32 -0
- package/dist/server/engine/backends/self-backend.d.ts.map +1 -0
- package/dist/server/engine/backends/self-backend.js +205 -0
- package/dist/server/engine/backends/self-backend.js.map +1 -0
- package/dist/server/engine/bus.d.ts +10 -0
- package/dist/server/engine/bus.d.ts.map +1 -0
- package/dist/server/engine/bus.js +24 -0
- package/dist/server/engine/bus.js.map +1 -0
- package/dist/server/engine/dispatch/dispatcher.d.ts +46 -0
- package/dist/server/engine/dispatch/dispatcher.d.ts.map +1 -0
- package/dist/server/engine/dispatch/dispatcher.js +312 -0
- package/dist/server/engine/dispatch/dispatcher.js.map +1 -0
- package/dist/server/engine/dispatch/scheduler.d.ts +47 -0
- package/dist/server/engine/dispatch/scheduler.d.ts.map +1 -0
- package/dist/server/engine/dispatch/scheduler.js +328 -0
- package/dist/server/engine/dispatch/scheduler.js.map +1 -0
- package/dist/server/engine/dispatch/sqs-poller.d.ts +25 -0
- package/dist/server/engine/dispatch/sqs-poller.d.ts.map +1 -0
- package/dist/server/engine/dispatch/sqs-poller.js +242 -0
- package/dist/server/engine/dispatch/sqs-poller.js.map +1 -0
- package/dist/server/engine/dispatch/stream-tailer.d.ts +22 -0
- package/dist/server/engine/dispatch/stream-tailer.d.ts.map +1 -0
- package/dist/server/engine/dispatch/stream-tailer.js +205 -0
- package/dist/server/engine/dispatch/stream-tailer.js.map +1 -0
- package/dist/server/engine/emulators/dynamodb/expressions/decimal.d.ts +5 -0
- package/dist/server/engine/emulators/dynamodb/expressions/decimal.d.ts.map +1 -0
- package/dist/server/engine/emulators/dynamodb/expressions/decimal.js +91 -0
- package/dist/server/engine/emulators/dynamodb/expressions/decimal.js.map +1 -0
- package/dist/server/engine/emulators/dynamodb/expressions/evaluate.d.ts +24 -0
- package/dist/server/engine/emulators/dynamodb/expressions/evaluate.d.ts.map +1 -0
- package/dist/server/engine/emulators/dynamodb/expressions/evaluate.js +183 -0
- package/dist/server/engine/emulators/dynamodb/expressions/evaluate.js.map +1 -0
- package/dist/server/engine/emulators/dynamodb/expressions/expression-types.d.ts +29 -0
- package/dist/server/engine/emulators/dynamodb/expressions/expression-types.d.ts.map +1 -0
- package/dist/server/engine/emulators/dynamodb/expressions/expression-types.js +12 -0
- package/dist/server/engine/emulators/dynamodb/expressions/expression-types.js.map +1 -0
- package/dist/server/engine/emulators/dynamodb/expressions/index.d.ts +13 -0
- package/dist/server/engine/emulators/dynamodb/expressions/index.d.ts.map +1 -0
- package/dist/server/engine/emulators/dynamodb/expressions/index.js +244 -0
- package/dist/server/engine/emulators/dynamodb/expressions/index.js.map +1 -0
- package/dist/server/engine/emulators/dynamodb/expressions/lexer.d.ts +11 -0
- package/dist/server/engine/emulators/dynamodb/expressions/lexer.d.ts.map +1 -0
- package/dist/server/engine/emulators/dynamodb/expressions/lexer.js +109 -0
- package/dist/server/engine/emulators/dynamodb/expressions/lexer.js.map +1 -0
- package/dist/server/engine/emulators/dynamodb/expressions/parser.d.ts +23 -0
- package/dist/server/engine/emulators/dynamodb/expressions/parser.d.ts.map +1 -0
- package/dist/server/engine/emulators/dynamodb/expressions/parser.js +541 -0
- package/dist/server/engine/emulators/dynamodb/expressions/parser.js.map +1 -0
- package/dist/server/engine/emulators/dynamodb/index.d.ts +46 -0
- package/dist/server/engine/emulators/dynamodb/index.d.ts.map +1 -0
- package/dist/server/engine/emulators/dynamodb/index.js +572 -0
- package/dist/server/engine/emulators/dynamodb/index.js.map +1 -0
- package/dist/server/engine/emulators/dynamodb/query.d.ts +12 -0
- package/dist/server/engine/emulators/dynamodb/query.d.ts.map +1 -0
- package/dist/server/engine/emulators/dynamodb/query.js +237 -0
- package/dist/server/engine/emulators/dynamodb/query.js.map +1 -0
- package/dist/server/engine/emulators/dynamodb/schema.d.ts +58 -0
- package/dist/server/engine/emulators/dynamodb/schema.d.ts.map +1 -0
- package/dist/server/engine/emulators/dynamodb/schema.js +284 -0
- package/dist/server/engine/emulators/dynamodb/schema.js.map +1 -0
- package/dist/server/engine/emulators/dynamodb/streams.d.ts +28 -0
- package/dist/server/engine/emulators/dynamodb/streams.d.ts.map +1 -0
- package/dist/server/engine/emulators/dynamodb/streams.js +100 -0
- package/dist/server/engine/emulators/dynamodb/streams.js.map +1 -0
- package/dist/server/engine/emulators/events/index.d.ts +53 -0
- package/dist/server/engine/emulators/events/index.d.ts.map +1 -0
- package/dist/server/engine/emulators/events/index.js +417 -0
- package/dist/server/engine/emulators/events/index.js.map +1 -0
- package/dist/server/engine/emulators/events/pattern.d.ts +3 -0
- package/dist/server/engine/emulators/events/pattern.d.ts.map +1 -0
- package/dist/server/engine/emulators/events/pattern.js +125 -0
- package/dist/server/engine/emulators/events/pattern.js.map +1 -0
- package/dist/server/engine/emulators/lambda-ctl/index.d.ts +55 -0
- package/dist/server/engine/emulators/lambda-ctl/index.d.ts.map +1 -0
- package/dist/server/engine/emulators/lambda-ctl/index.js +461 -0
- package/dist/server/engine/emulators/lambda-ctl/index.js.map +1 -0
- package/dist/server/engine/emulators/s3/index.d.ts +45 -0
- package/dist/server/engine/emulators/s3/index.d.ts.map +1 -0
- package/dist/server/engine/emulators/s3/index.js +591 -0
- package/dist/server/engine/emulators/s3/index.js.map +1 -0
- package/dist/server/engine/emulators/sns/index.d.ts +22 -0
- package/dist/server/engine/emulators/sns/index.d.ts.map +1 -0
- package/dist/server/engine/emulators/sns/index.js +135 -0
- package/dist/server/engine/emulators/sns/index.js.map +1 -0
- package/dist/server/engine/emulators/sqs/index.d.ts +66 -0
- package/dist/server/engine/emulators/sqs/index.d.ts.map +1 -0
- package/dist/server/engine/emulators/sqs/index.js +552 -0
- package/dist/server/engine/emulators/sqs/index.js.map +1 -0
- package/dist/server/engine/emulators/sqs/md5.d.ts +8 -0
- package/dist/server/engine/emulators/sqs/md5.d.ts.map +1 -0
- package/dist/server/engine/emulators/sqs/md5.js +36 -0
- package/dist/server/engine/emulators/sqs/md5.js.map +1 -0
- package/dist/server/engine/emulators/sqs/queue.d.ts +105 -0
- package/dist/server/engine/emulators/sqs/queue.d.ts.map +1 -0
- package/dist/server/engine/emulators/sqs/queue.js +371 -0
- package/dist/server/engine/emulators/sqs/queue.js.map +1 -0
- package/dist/server/engine/emulators/sts.d.ts +8 -0
- package/dist/server/engine/emulators/sts.d.ts.map +1 -0
- package/dist/server/engine/emulators/sts.js +23 -0
- package/dist/server/engine/emulators/sts.js.map +1 -0
- package/dist/server/engine/engine-backend.d.ts +17 -0
- package/dist/server/engine/engine-backend.d.ts.map +1 -0
- package/dist/server/engine/engine-backend.js +7 -0
- package/dist/server/engine/engine-backend.js.map +1 -0
- package/dist/server/engine/engine-manager.d.ts +22 -0
- package/dist/server/engine/engine-manager.d.ts.map +1 -0
- package/dist/server/engine/engine-manager.js +74 -0
- package/dist/server/engine/engine-manager.js.map +1 -0
- package/dist/server/engine/http/aws-chunked.d.ts +3 -0
- package/dist/server/engine/http/aws-chunked.d.ts.map +1 -0
- package/dist/server/engine/http/aws-chunked.js +50 -0
- package/dist/server/engine/http/aws-chunked.js.map +1 -0
- package/dist/server/engine/http/errors.d.ts +20 -0
- package/dist/server/engine/http/errors.d.ts.map +1 -0
- package/dist/server/engine/http/errors.js +43 -0
- package/dist/server/engine/http/errors.js.map +1 -0
- package/dist/server/engine/http/protocols/aws-json.d.ts +8 -0
- package/dist/server/engine/http/protocols/aws-json.d.ts.map +1 -0
- package/dist/server/engine/http/protocols/aws-json.js +42 -0
- package/dist/server/engine/http/protocols/aws-json.js.map +1 -0
- package/dist/server/engine/http/protocols/query-xml.d.ts +7 -0
- package/dist/server/engine/http/protocols/query-xml.d.ts.map +1 -0
- package/dist/server/engine/http/protocols/query-xml.js +35 -0
- package/dist/server/engine/http/protocols/query-xml.js.map +1 -0
- package/dist/server/engine/http/router.d.ts +8 -0
- package/dist/server/engine/http/router.d.ts.map +1 -0
- package/dist/server/engine/http/router.js +391 -0
- package/dist/server/engine/http/router.js.map +1 -0
- package/dist/server/engine/http/sigv4.d.ts +9 -0
- package/dist/server/engine/http/sigv4.d.ts.map +1 -0
- package/dist/server/engine/http/sigv4.js +33 -0
- package/dist/server/engine/http/sigv4.js.map +1 -0
- package/dist/server/engine/http/xml.d.ts +16 -0
- package/dist/server/engine/http/xml.d.ts.map +1 -0
- package/dist/server/engine/http/xml.js +92 -0
- package/dist/server/engine/http/xml.js.map +1 -0
- package/dist/server/engine/store/atomic.d.ts +4 -0
- package/dist/server/engine/store/atomic.d.ts.map +1 -0
- package/dist/server/engine/store/atomic.js +35 -0
- package/dist/server/engine/store/atomic.js.map +1 -0
- package/dist/server/engine/store/engine-store.d.ts +9 -0
- package/dist/server/engine/store/engine-store.d.ts.map +1 -0
- package/dist/server/engine/store/engine-store.js +218 -0
- package/dist/server/engine/store/engine-store.js.map +1 -0
- package/dist/server/engine/store/store-types.d.ts +37 -0
- package/dist/server/engine/store/store-types.d.ts.map +1 -0
- package/dist/server/engine/store/store-types.js +17 -0
- package/dist/server/engine/store/store-types.js.map +1 -0
- package/dist/server/engine/store/wal.d.ts +49 -0
- package/dist/server/engine/store/wal.d.ts.map +1 -0
- package/dist/server/engine/store/wal.js +328 -0
- package/dist/server/engine/store/wal.js.map +1 -0
- package/dist/server/engine/types.d.ts +137 -0
- package/dist/server/engine/types.d.ts.map +1 -0
- package/dist/server/engine/types.js +8 -0
- package/dist/server/engine/types.js.map +1 -0
- package/dist/server/index.js +12 -9
- package/dist/server/index.js.map +1 -1
- package/dist/server/routes/services.d.ts.map +1 -1
- package/dist/server/routes/services.js +2 -0
- package/dist/server/routes/services.js.map +1 -1
- package/dist/server/services/cloudformation-parser.d.ts +26 -2
- package/dist/server/services/cloudformation-parser.d.ts.map +1 -1
- package/dist/server/services/cloudformation-parser.js +60 -3
- package/dist/server/services/cloudformation-parser.js.map +1 -1
- package/dist/server/services/config-manager.d.ts +27 -0
- package/dist/server/services/config-manager.d.ts.map +1 -1
- package/dist/server/services/config-manager.js +67 -7
- package/dist/server/services/config-manager.js.map +1 -1
- package/dist/server/services/localstack-manager.d.ts +0 -7
- package/dist/server/services/localstack-manager.d.ts.map +1 -1
- package/dist/server/services/localstack-manager.js +11 -173
- package/dist/server/services/localstack-manager.js.map +1 -1
- package/dist/server/services/resource-provisioner.d.ts +6 -0
- package/dist/server/services/resource-provisioner.d.ts.map +1 -1
- package/dist/server/services/resource-provisioner.js +171 -7
- package/dist/server/services/resource-provisioner.js.map +1 -1
- package/dist/server/services/service-registrar.js +2 -2
- package/dist/server/services/service-registrar.js.map +1 -1
- package/dist/ui/assets/ApisPage-a9i2qD0Z.js +1 -0
- package/dist/ui/assets/BucketDetailPage-Cz27N0KV.js +1 -0
- package/dist/ui/assets/BucketsPage-CZ7wGmUN.js +1 -0
- package/dist/ui/assets/{DynamoPage-B1__Rjr2.js → DynamoPage-BZMs1iW8.js} +1 -1
- package/dist/ui/assets/DynamoTablePage-BQsGkhtO.js +3 -0
- package/dist/ui/assets/LambdaDetailPage-DJb7xqRB.js +4 -0
- package/dist/ui/assets/LambdasPage-Ctg1l-Lg.js +1 -0
- package/dist/ui/assets/OverviewPage-DiijblCA.js +1 -0
- package/dist/ui/assets/QueueDetailPage-DRpSqost.js +1 -0
- package/dist/ui/assets/QueuesPage-CY0bDGwV.js +1 -0
- package/dist/ui/assets/ServiceDetailPage-CYTEdtts.js +2 -0
- package/dist/ui/assets/ServicesPage-OlV_rr-q.js +2 -0
- package/dist/ui/assets/index-C1KdKPDS.css +1 -0
- package/dist/ui/assets/index-DA_C1HOg.js +36 -0
- package/dist/ui/index.html +2 -2
- package/package.json +2 -1
- package/dist/ui/assets/ApisPage-DdgKjEdS.js +0 -1
- package/dist/ui/assets/BucketDetailPage-DZQGxm0y.js +0 -1
- package/dist/ui/assets/BucketsPage-xYXzxCvd.js +0 -1
- package/dist/ui/assets/DynamoTablePage-BCoxPdXW.js +0 -3
- package/dist/ui/assets/LambdaDetailPage-B85CA8tb.js +0 -4
- package/dist/ui/assets/LambdasPage-_TtxQMT_.js +0 -1
- package/dist/ui/assets/OverviewPage-CqTKTY64.js +0 -1
- package/dist/ui/assets/QueueDetailPage-BOr_sImA.js +0 -1
- package/dist/ui/assets/QueuesPage-DpByfcM5.js +0 -1
- package/dist/ui/assets/ServiceDetailPage-DsWgV-ad.js +0 -2
- package/dist/ui/assets/ServicesPage-CGlpsj98.js +0 -2
- package/dist/ui/assets/index-BWlB542w.js +0 -36
- package/dist/ui/assets/index-EYnsoJOq.css +0 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { EngineContext, EngineInvokeResult, EventRuleTarget } from '../types.js';
|
|
2
|
+
import type { EventsEmulator } from '../emulators/events/index.js';
|
|
3
|
+
export declare function resolveTargetInput(target: EventRuleTarget, envelope: Record<string, unknown>): unknown;
|
|
4
|
+
interface CronField {
|
|
5
|
+
kind: 'any' | 'question' | 'set';
|
|
6
|
+
values?: Set<number>;
|
|
7
|
+
}
|
|
8
|
+
export type ParsedSchedule = {
|
|
9
|
+
kind: 'rate';
|
|
10
|
+
intervalMs: number;
|
|
11
|
+
} | {
|
|
12
|
+
kind: 'cron';
|
|
13
|
+
minute: CronField;
|
|
14
|
+
hour: CronField;
|
|
15
|
+
dayOfMonth: CronField;
|
|
16
|
+
month: CronField;
|
|
17
|
+
dayOfWeek: CronField;
|
|
18
|
+
year: CronField;
|
|
19
|
+
};
|
|
20
|
+
export declare function parseScheduleExpression(expression: string): ParsedSchedule | undefined;
|
|
21
|
+
export declare function nextFireAt(schedule: ParsedSchedule, fromMs: number): number | undefined;
|
|
22
|
+
export interface EngineSchedulerDeps {
|
|
23
|
+
ctx: EngineContext;
|
|
24
|
+
events: EventsEmulator;
|
|
25
|
+
invoke: (ref: string, event: unknown, opts?: {
|
|
26
|
+
async?: boolean;
|
|
27
|
+
}) => Promise<EngineInvokeResult>;
|
|
28
|
+
}
|
|
29
|
+
export declare class EngineScheduler {
|
|
30
|
+
private readonly deps;
|
|
31
|
+
private entries;
|
|
32
|
+
private timer;
|
|
33
|
+
private stopped;
|
|
34
|
+
private readonly warnedUnsupported;
|
|
35
|
+
private resyncChain;
|
|
36
|
+
constructor(deps: EngineSchedulerDeps);
|
|
37
|
+
resync(region: string): Promise<void>;
|
|
38
|
+
stop(): void;
|
|
39
|
+
scheduleCount(): number;
|
|
40
|
+
private doResync;
|
|
41
|
+
private arm;
|
|
42
|
+
private clearTimer;
|
|
43
|
+
private fireDue;
|
|
44
|
+
private deliver;
|
|
45
|
+
}
|
|
46
|
+
export {};
|
|
47
|
+
//# sourceMappingURL=scheduler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../../../src/server/engine/dispatch/scheduler.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACtF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAUnE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAWtG;AAkBD,UAAU,SAAS;IACjB,IAAI,EAAE,KAAK,GAAG,UAAU,GAAG,KAAK,CAAC;IACjC,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACtB;AAED,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAuBzI,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CA6BtF;AA4DD,wBAAgB,UAAU,CAAC,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAWvF;AA+BD,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,aAAa,CAAC;IACnB,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;CAClG;AAWD,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAsB;IAC3C,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAqB;IAEvD,OAAO,CAAC,WAAW,CAAoC;gBAE3C,IAAI,EAAE,mBAAmB;IASrC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKrC,IAAI,IAAI,IAAI;IAOZ,aAAa,IAAI,MAAM;YAIT,QAAQ;IAkCtB,OAAO,CAAC,GAAG;IAWX,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,OAAO;IAgBf,OAAO,CAAC,OAAO;CA6BhB"}
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
// EventBridge schedule rules (rate + 6-field AWS cron) for the self engine.
|
|
2
|
+
// Timer discipline (PRD RF6.3): ONE unref'd setTimeout armed for the earliest
|
|
3
|
+
// next-fire across every schedule rule — and zero timers when there are no
|
|
4
|
+
// ENABLED schedule rules. resync() re-reads the rules catalog; the events
|
|
5
|
+
// emulator's onRulesChanged hook calls it, so there is no polling anywhere.
|
|
6
|
+
import { randomUUID } from 'crypto';
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Target payload resolution (shared with the dispatcher's rule-matched path).
|
|
9
|
+
// Lives here because this module has no heavy imports, so tests of either
|
|
10
|
+
// consumer can use it without mocking the Lambda runtime modules.
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// EventBridge target semantics: Input (literal JSON) wins over InputPath
|
|
13
|
+
// (dot-path subset: '$', '$.detail', '$.detail.x.y'); neither → full envelope.
|
|
14
|
+
export function resolveTargetInput(target, envelope) {
|
|
15
|
+
if (target.input !== undefined) {
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(target.input);
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
console.warn(`[engine-scheduler] target ${target.id}: Input is not valid JSON, delivering null`);
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (target.inputPath !== undefined)
|
|
25
|
+
return extractInputPath(envelope, target.inputPath);
|
|
26
|
+
return envelope;
|
|
27
|
+
}
|
|
28
|
+
function extractInputPath(envelope, path) {
|
|
29
|
+
if (path === '$')
|
|
30
|
+
return envelope;
|
|
31
|
+
if (!path.startsWith('$.'))
|
|
32
|
+
return null;
|
|
33
|
+
let current = envelope;
|
|
34
|
+
for (const segment of path.slice(2).split('.')) {
|
|
35
|
+
if (segment === '' || current === null || typeof current !== 'object' || Array.isArray(current))
|
|
36
|
+
return null;
|
|
37
|
+
current = current[segment];
|
|
38
|
+
if (current === undefined)
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
return current ?? null;
|
|
42
|
+
}
|
|
43
|
+
const RATE_UNIT_MS = {
|
|
44
|
+
minute: 60_000,
|
|
45
|
+
minutes: 60_000,
|
|
46
|
+
hour: 3_600_000,
|
|
47
|
+
hours: 3_600_000,
|
|
48
|
+
day: 86_400_000,
|
|
49
|
+
days: 86_400_000,
|
|
50
|
+
};
|
|
51
|
+
const MONTH_NAMES = {
|
|
52
|
+
JAN: 1, FEB: 2, MAR: 3, APR: 4, MAY: 5, JUN: 6,
|
|
53
|
+
JUL: 7, AUG: 8, SEP: 9, OCT: 10, NOV: 11, DEC: 12,
|
|
54
|
+
};
|
|
55
|
+
// AWS cron day-of-week numbering: 1 = SUN … 7 = SAT.
|
|
56
|
+
const DOW_NAMES = {
|
|
57
|
+
SUN: 1, MON: 2, TUE: 3, WED: 4, THU: 5, FRI: 6, SAT: 7,
|
|
58
|
+
};
|
|
59
|
+
// Returns undefined for anything unsupported (bad syntax, L/W/# operators);
|
|
60
|
+
// the caller warns and skips the rule.
|
|
61
|
+
export function parseScheduleExpression(expression) {
|
|
62
|
+
const rate = /^rate\((\d+)\s+(minutes?|hours?|days?)\)$/.exec(expression.trim());
|
|
63
|
+
if (rate) {
|
|
64
|
+
const count = parseInt(rate[1], 10);
|
|
65
|
+
if (count < 1)
|
|
66
|
+
return undefined;
|
|
67
|
+
// AWS also rejects "rate(1 minutes)" / "rate(5 minute)" plural mismatches.
|
|
68
|
+
const singular = count === 1;
|
|
69
|
+
if (singular !== !rate[2].endsWith('s'))
|
|
70
|
+
return undefined;
|
|
71
|
+
return { kind: 'rate', intervalMs: count * RATE_UNIT_MS[rate[2]] };
|
|
72
|
+
}
|
|
73
|
+
const cron = /^cron\((.+)\)$/.exec(expression.trim());
|
|
74
|
+
if (!cron)
|
|
75
|
+
return undefined;
|
|
76
|
+
const fields = cron[1].trim().split(/\s+/);
|
|
77
|
+
if (fields.length !== 6)
|
|
78
|
+
return undefined;
|
|
79
|
+
const [minute, hour, dayOfMonth, month, dayOfWeek, year] = fields;
|
|
80
|
+
try {
|
|
81
|
+
return {
|
|
82
|
+
kind: 'cron',
|
|
83
|
+
minute: parseCronField(minute, 0, 59),
|
|
84
|
+
hour: parseCronField(hour, 0, 23),
|
|
85
|
+
dayOfMonth: parseCronField(dayOfMonth, 1, 31),
|
|
86
|
+
month: parseCronField(month, 1, 12, MONTH_NAMES),
|
|
87
|
+
dayOfWeek: parseCronField(dayOfWeek, 1, 7, DOW_NAMES),
|
|
88
|
+
year: parseCronField(year, 1970, 2199),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// L/W/# operator forms ("L", "3L", "FRIL", "15W", "6#2") — matched precisely
|
|
96
|
+
// so month/day names containing those letters (JUL, WED) stay valid.
|
|
97
|
+
const UNSUPPORTED_CRON_OPERATOR = /^(?:\d+|[A-Z]{3})?[LW]$/i;
|
|
98
|
+
function parseCronField(raw, min, max, names) {
|
|
99
|
+
if (raw.includes('#'))
|
|
100
|
+
throw new Error(`unsupported cron operator in "${raw}"`);
|
|
101
|
+
if (raw === '*')
|
|
102
|
+
return { kind: 'any' };
|
|
103
|
+
if (raw === '?')
|
|
104
|
+
return { kind: 'question' };
|
|
105
|
+
const values = new Set();
|
|
106
|
+
for (const part of raw.split(',')) {
|
|
107
|
+
if (part === '')
|
|
108
|
+
throw new Error('empty cron list entry');
|
|
109
|
+
if (UNSUPPORTED_CRON_OPERATOR.test(part))
|
|
110
|
+
throw new Error(`unsupported cron operator in "${part}"`);
|
|
111
|
+
const step = part.split('/');
|
|
112
|
+
if (step.length > 2)
|
|
113
|
+
throw new Error(`bad step in "${part}"`);
|
|
114
|
+
const stepBy = step.length === 2 ? parseInt(step[1], 10) : 1;
|
|
115
|
+
if (!Number.isInteger(stepBy) || stepBy < 1)
|
|
116
|
+
throw new Error(`bad step in "${part}"`);
|
|
117
|
+
let start;
|
|
118
|
+
let end;
|
|
119
|
+
const base = step[0];
|
|
120
|
+
if (base === '*') {
|
|
121
|
+
start = min;
|
|
122
|
+
end = max;
|
|
123
|
+
}
|
|
124
|
+
else if (base.includes('-')) {
|
|
125
|
+
const [rawFrom, rawTo] = base.split('-');
|
|
126
|
+
start = parseCronValue(rawFrom, names);
|
|
127
|
+
end = parseCronValue(rawTo, names);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
start = parseCronValue(base, names);
|
|
131
|
+
// "a/n" steps from a to the field max; a bare value is just itself.
|
|
132
|
+
end = step.length === 2 ? max : start;
|
|
133
|
+
}
|
|
134
|
+
if (start < min || end > max || start > end)
|
|
135
|
+
throw new Error(`out of range: "${part}"`);
|
|
136
|
+
for (let value = start; value <= end; value += stepBy)
|
|
137
|
+
values.add(value);
|
|
138
|
+
}
|
|
139
|
+
return { kind: 'set', values };
|
|
140
|
+
}
|
|
141
|
+
function parseCronValue(raw, names) {
|
|
142
|
+
if (names) {
|
|
143
|
+
const named = names[raw.toUpperCase()];
|
|
144
|
+
if (named !== undefined)
|
|
145
|
+
return named;
|
|
146
|
+
}
|
|
147
|
+
const value = parseInt(raw, 10);
|
|
148
|
+
if (!Number.isInteger(value) || String(value) !== raw)
|
|
149
|
+
throw new Error(`bad cron value "${raw}"`);
|
|
150
|
+
return value;
|
|
151
|
+
}
|
|
152
|
+
// ---------------------------------------------------------------------------
|
|
153
|
+
// Next-fire computation
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
const MINUTE_MS = 60_000;
|
|
156
|
+
const SEARCH_WINDOW_MS = 366 * 24 * 3_600_000;
|
|
157
|
+
// Strictly-after-`fromMs` next fire time (epoch ms), or undefined when the
|
|
158
|
+
// schedule never fires inside the 366-day search window.
|
|
159
|
+
export function nextFireAt(schedule, fromMs) {
|
|
160
|
+
if (schedule.kind === 'rate')
|
|
161
|
+
return fromMs + schedule.intervalMs;
|
|
162
|
+
// Simple and correct beats clever: walk minute boundaries in UTC.
|
|
163
|
+
let candidate = Math.floor(fromMs / MINUTE_MS) * MINUTE_MS + MINUTE_MS;
|
|
164
|
+
const end = fromMs + SEARCH_WINDOW_MS;
|
|
165
|
+
while (candidate <= end) {
|
|
166
|
+
if (cronMatches(schedule, candidate))
|
|
167
|
+
return candidate;
|
|
168
|
+
candidate += MINUTE_MS;
|
|
169
|
+
}
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
function fieldMatches(field, value) {
|
|
173
|
+
if (field.kind === 'set')
|
|
174
|
+
return field.values.has(value);
|
|
175
|
+
return true; // 'any' and 'question' match everything
|
|
176
|
+
}
|
|
177
|
+
function cronMatches(schedule, epochMs) {
|
|
178
|
+
const date = new Date(epochMs);
|
|
179
|
+
if (!fieldMatches(schedule.minute, date.getUTCMinutes()))
|
|
180
|
+
return false;
|
|
181
|
+
if (!fieldMatches(schedule.hour, date.getUTCHours()))
|
|
182
|
+
return false;
|
|
183
|
+
if (!fieldMatches(schedule.month, date.getUTCMonth() + 1))
|
|
184
|
+
return false;
|
|
185
|
+
if (!fieldMatches(schedule.year, date.getUTCFullYear()))
|
|
186
|
+
return false;
|
|
187
|
+
const domMatch = fieldMatches(schedule.dayOfMonth, date.getUTCDate());
|
|
188
|
+
const dowMatch = fieldMatches(schedule.dayOfWeek, date.getUTCDay() + 1);
|
|
189
|
+
// AWS requires one of dom/dow to be '?': that one is ignored. Both
|
|
190
|
+
// unrestricted → any day; both restricted (rejected by AWS at PutRule) → OR,
|
|
191
|
+
// the classic cron semantics.
|
|
192
|
+
if (schedule.dayOfMonth.kind === 'question')
|
|
193
|
+
return dowMatch;
|
|
194
|
+
if (schedule.dayOfWeek.kind === 'question')
|
|
195
|
+
return domMatch;
|
|
196
|
+
if (schedule.dayOfMonth.kind === 'any' && schedule.dayOfWeek.kind === 'any')
|
|
197
|
+
return true;
|
|
198
|
+
if (schedule.dayOfMonth.kind === 'any')
|
|
199
|
+
return dowMatch;
|
|
200
|
+
if (schedule.dayOfWeek.kind === 'any')
|
|
201
|
+
return domMatch;
|
|
202
|
+
return domMatch || dowMatch;
|
|
203
|
+
}
|
|
204
|
+
export class EngineScheduler {
|
|
205
|
+
deps;
|
|
206
|
+
entries = [];
|
|
207
|
+
timer = null;
|
|
208
|
+
stopped = false;
|
|
209
|
+
warnedUnsupported = new Set();
|
|
210
|
+
// Serializes overlapping resyncs (rule churn during a slow catalog load).
|
|
211
|
+
resyncChain = Promise.resolve();
|
|
212
|
+
constructor(deps) {
|
|
213
|
+
this.deps = deps;
|
|
214
|
+
deps.events.onRulesChanged(region => {
|
|
215
|
+
void this.resync(region).catch(err => {
|
|
216
|
+
console.warn('[engine-scheduler] resync after rule change failed:', err);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
resync(region) {
|
|
221
|
+
this.resyncChain = this.resyncChain.then(() => this.doResync(region));
|
|
222
|
+
return this.resyncChain;
|
|
223
|
+
}
|
|
224
|
+
stop() {
|
|
225
|
+
this.stopped = true;
|
|
226
|
+
this.entries = [];
|
|
227
|
+
this.clearTimer();
|
|
228
|
+
}
|
|
229
|
+
// For health/introspection: number of armed schedule rules.
|
|
230
|
+
scheduleCount() {
|
|
231
|
+
return this.entries.length;
|
|
232
|
+
}
|
|
233
|
+
async doResync(region) {
|
|
234
|
+
if (this.stopped)
|
|
235
|
+
return;
|
|
236
|
+
const rules = await this.deps.events.listScheduleRules(region);
|
|
237
|
+
const now = Date.now();
|
|
238
|
+
const fresh = [];
|
|
239
|
+
for (const rule of rules) {
|
|
240
|
+
if (rule.state !== 'ENABLED')
|
|
241
|
+
continue;
|
|
242
|
+
const schedule = parseScheduleExpression(rule.scheduleExpression);
|
|
243
|
+
const key = `${region}|${rule.busName}|${rule.ruleName}`;
|
|
244
|
+
if (!schedule) {
|
|
245
|
+
if (!this.warnedUnsupported.has(key)) {
|
|
246
|
+
this.warnedUnsupported.add(key);
|
|
247
|
+
console.warn(`[engine-scheduler] rule ${rule.ruleName}: unsupported schedule expression ` +
|
|
248
|
+
`"${rule.scheduleExpression}" (L/W/# and malformed expressions are not supported) — rule skipped`);
|
|
249
|
+
}
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const nextFireAtMs = nextFireAt(schedule, now);
|
|
253
|
+
if (nextFireAtMs === undefined)
|
|
254
|
+
continue;
|
|
255
|
+
fresh.push({
|
|
256
|
+
region,
|
|
257
|
+
busName: rule.busName,
|
|
258
|
+
ruleName: rule.ruleName,
|
|
259
|
+
targets: rule.targets,
|
|
260
|
+
schedule,
|
|
261
|
+
nextFireAtMs,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
this.entries = [...this.entries.filter(entry => entry.region !== region), ...fresh];
|
|
265
|
+
this.arm();
|
|
266
|
+
}
|
|
267
|
+
arm() {
|
|
268
|
+
this.clearTimer();
|
|
269
|
+
if (this.stopped || this.entries.length === 0)
|
|
270
|
+
return;
|
|
271
|
+
const earliest = Math.min(...this.entries.map(entry => entry.nextFireAtMs));
|
|
272
|
+
this.timer = setTimeout(() => {
|
|
273
|
+
this.timer = null;
|
|
274
|
+
this.fireDue();
|
|
275
|
+
}, Math.max(0, earliest - Date.now()));
|
|
276
|
+
this.timer.unref();
|
|
277
|
+
}
|
|
278
|
+
clearTimer() {
|
|
279
|
+
if (this.timer) {
|
|
280
|
+
clearTimeout(this.timer);
|
|
281
|
+
this.timer = null;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
fireDue() {
|
|
285
|
+
const now = Date.now();
|
|
286
|
+
const surviving = [];
|
|
287
|
+
for (const entry of this.entries) {
|
|
288
|
+
if (entry.nextFireAtMs <= now) {
|
|
289
|
+
this.deliver(entry);
|
|
290
|
+
const next = nextFireAt(entry.schedule, now);
|
|
291
|
+
if (next === undefined)
|
|
292
|
+
continue; // never fires again inside the window
|
|
293
|
+
entry.nextFireAtMs = next;
|
|
294
|
+
}
|
|
295
|
+
surviving.push(entry);
|
|
296
|
+
}
|
|
297
|
+
this.entries = surviving;
|
|
298
|
+
this.arm();
|
|
299
|
+
}
|
|
300
|
+
deliver(entry) {
|
|
301
|
+
const { account } = this.deps.ctx.config;
|
|
302
|
+
// Matches the ARN shape the events emulator builds (default bus omits the
|
|
303
|
+
// bus segment).
|
|
304
|
+
const busSegment = entry.busName === 'default' ? '' : `${entry.busName}/`;
|
|
305
|
+
const ruleArn = `arn:aws:events:${entry.region}:${account}:rule/${busSegment}${entry.ruleName}`;
|
|
306
|
+
const envelope = {
|
|
307
|
+
version: '0',
|
|
308
|
+
id: randomUUID(),
|
|
309
|
+
'detail-type': 'Scheduled Event',
|
|
310
|
+
source: 'aws.events',
|
|
311
|
+
account,
|
|
312
|
+
time: new Date().toISOString().replace(/\.\d{3}Z$/, 'Z'),
|
|
313
|
+
region: entry.region,
|
|
314
|
+
resources: [ruleArn],
|
|
315
|
+
detail: {},
|
|
316
|
+
};
|
|
317
|
+
for (const target of entry.targets) {
|
|
318
|
+
const payload = resolveTargetInput(target, envelope);
|
|
319
|
+
void this.deps.invoke(target.arn, payload, { async: true }).then(result => {
|
|
320
|
+
if (!result.ok) {
|
|
321
|
+
console.warn(`[engine-scheduler] rule ${entry.ruleName}: delivery to ${target.arn} failed: ` +
|
|
322
|
+
`${result.errorType ?? 'Error'}: ${result.errorMessage ?? ''}`);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
//# sourceMappingURL=scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../../../src/server/engine/dispatch/scheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { EngineContext, EngineInvokeResult } from '../types.js';
|
|
2
|
+
import type { DeliveredMessage, SqsEmulator } from '../emulators/sqs/index.js';
|
|
3
|
+
import type { LambdaCtlEmulator } from '../emulators/lambda-ctl/index.js';
|
|
4
|
+
export type InvokeFn = (ref: string, event: unknown, opts?: {
|
|
5
|
+
async?: boolean;
|
|
6
|
+
}) => Promise<EngineInvokeResult>;
|
|
7
|
+
export interface SqsPollerDeps {
|
|
8
|
+
ctx: EngineContext;
|
|
9
|
+
sqs: SqsEmulator;
|
|
10
|
+
lambdaCtl: LambdaCtlEmulator;
|
|
11
|
+
invoke: InvokeFn;
|
|
12
|
+
runtimeRetryBackoffMs?: number[];
|
|
13
|
+
}
|
|
14
|
+
export declare function buildSqsLambdaEvent(batch: DeliveredMessage[], eventSourceArn: string, region: string): {
|
|
15
|
+
Records: Record<string, unknown>[];
|
|
16
|
+
};
|
|
17
|
+
export declare class SqsPollerSet {
|
|
18
|
+
private readonly deps;
|
|
19
|
+
private readonly loops;
|
|
20
|
+
constructor(deps: SqsPollerDeps);
|
|
21
|
+
sync(region: string): void;
|
|
22
|
+
stopAll(): void;
|
|
23
|
+
loopCount(): number;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=sqs-poller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqs-poller.d.ts","sourceRoot":"","sources":["../../../../src/server/engine/dispatch/sqs-poller.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAkC,kBAAkB,EAA0B,MAAM,aAAa,CAAC;AAC7H,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAE/E,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAE1E,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAEhH,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,aAAa,CAAC;IACnB,GAAG,EAAE,WAAW,CAAC;IACjB,SAAS,EAAE,iBAAiB,CAAC;IAC7B,MAAM,EAAE,QAAQ,CAAC;IAEjB,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;CAClC;AAOD,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,gBAAgB,EAAE,EACzB,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,MAAM,GACb;IAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAA;CAAE,CAsCxC;AAwJD,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAgB;IACrC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmC;gBAE7C,IAAI,EAAE,aAAa;IAI/B,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAwB1B,OAAO,IAAI,IAAI;IAKf,SAAS,IAAI,MAAM;CAGpB"}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
// SQS → Lambda event source mapping delivery. One DeliveryLoop per ENABLED
|
|
2
|
+
// ESM whose eventSourceArn is an SQS queue. Event-driven (PRD RF4.3): the loop
|
|
3
|
+
// wakes on the engine bus 'sqs:message-visible' event for its queue — never a
|
|
4
|
+
// busy poll — plus a 1 s unref'd safety tick armed only while a batch might be
|
|
5
|
+
// in flight. Delivery semantics are AWS's: success deletes the batch, failure
|
|
6
|
+
// leaves it to the visibility timeout (no retry machinery of our own), and
|
|
7
|
+
// only 'RuntimeUnavailable' (worker restarting) retries the same batch with a
|
|
8
|
+
// capped backoff.
|
|
9
|
+
import { queueNameFromArn } from '../emulators/sqs/index.js';
|
|
10
|
+
const DEFAULT_RUNTIME_RETRY_BACKOFF_MS = [1000, 2000, 4000];
|
|
11
|
+
const SAFETY_TICK_MS = 1000;
|
|
12
|
+
// The Lambda SQS event for one delivered batch. Message attributes use the
|
|
13
|
+
// lowercase keys real AWS Lambda events carry (aws-lambda's SQSRecord shape).
|
|
14
|
+
export function buildSqsLambdaEvent(batch, eventSourceArn, region) {
|
|
15
|
+
return {
|
|
16
|
+
Records: batch.map(message => {
|
|
17
|
+
const attributes = {
|
|
18
|
+
ApproximateReceiveCount: String(message.approximateReceiveCount),
|
|
19
|
+
SentTimestamp: String(message.sentTimestamp),
|
|
20
|
+
SenderId: 'AIDASELFENGINE',
|
|
21
|
+
ApproximateFirstReceiveTimestamp: message.attributes.ApproximateFirstReceiveTimestamp ?? String(message.sentTimestamp),
|
|
22
|
+
};
|
|
23
|
+
if (message.messageGroupId !== undefined)
|
|
24
|
+
attributes.MessageGroupId = message.messageGroupId;
|
|
25
|
+
if (message.sequenceNumber !== undefined)
|
|
26
|
+
attributes.SequenceNumber = message.sequenceNumber;
|
|
27
|
+
if (message.attributes.MessageDeduplicationId !== undefined) {
|
|
28
|
+
attributes.MessageDeduplicationId = message.attributes.MessageDeduplicationId;
|
|
29
|
+
}
|
|
30
|
+
const messageAttributes = {};
|
|
31
|
+
for (const [name, value] of Object.entries(message.messageAttributes)) {
|
|
32
|
+
messageAttributes[name] = {
|
|
33
|
+
...(value.StringValue !== undefined ? { stringValue: value.StringValue } : {}),
|
|
34
|
+
...(value.BinaryValue !== undefined ? { binaryValue: value.BinaryValue } : {}),
|
|
35
|
+
stringListValues: [],
|
|
36
|
+
binaryListValues: [],
|
|
37
|
+
dataType: value.DataType,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
messageId: message.messageId,
|
|
42
|
+
receiptHandle: message.receiptHandle,
|
|
43
|
+
body: message.body,
|
|
44
|
+
attributes,
|
|
45
|
+
messageAttributes,
|
|
46
|
+
md5OfBody: message.md5OfBody,
|
|
47
|
+
eventSource: 'aws:sqs',
|
|
48
|
+
eventSourceARN: eventSourceArn,
|
|
49
|
+
awsRegion: region,
|
|
50
|
+
};
|
|
51
|
+
}),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
class DeliveryLoop {
|
|
55
|
+
region;
|
|
56
|
+
queueName;
|
|
57
|
+
esm;
|
|
58
|
+
deps;
|
|
59
|
+
stopped = false;
|
|
60
|
+
delivering = false;
|
|
61
|
+
pending = false;
|
|
62
|
+
// Set after a failed delivery: the batch is waiting out its visibility
|
|
63
|
+
// timeout, so the safety tick stays armed until redelivery succeeds.
|
|
64
|
+
awaitingRedelivery = false;
|
|
65
|
+
tick = null;
|
|
66
|
+
windowWaiter = null;
|
|
67
|
+
busListener;
|
|
68
|
+
constructor(deps, esm, region) {
|
|
69
|
+
this.deps = deps;
|
|
70
|
+
this.esm = esm;
|
|
71
|
+
this.region = region;
|
|
72
|
+
this.queueName = queueNameFromArn(esm.eventSourceArn);
|
|
73
|
+
this.busListener = payload => {
|
|
74
|
+
if (payload.region === this.region && payload.queueName === this.queueName)
|
|
75
|
+
this.wake();
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
start() {
|
|
79
|
+
this.deps.ctx.bus.on('sqs:message-visible', this.busListener);
|
|
80
|
+
// Drain anything already visible (boot rearm, restored snapshots).
|
|
81
|
+
this.wake();
|
|
82
|
+
}
|
|
83
|
+
update(esm) {
|
|
84
|
+
this.esm = esm;
|
|
85
|
+
this.wake();
|
|
86
|
+
}
|
|
87
|
+
stop() {
|
|
88
|
+
this.stopped = true;
|
|
89
|
+
this.deps.ctx.bus.off('sqs:message-visible', this.busListener);
|
|
90
|
+
this.clearTick();
|
|
91
|
+
this.windowWaiter?.();
|
|
92
|
+
}
|
|
93
|
+
wake() {
|
|
94
|
+
if (this.stopped || !this.esm.enabled)
|
|
95
|
+
return;
|
|
96
|
+
if (this.delivering) {
|
|
97
|
+
this.pending = true;
|
|
98
|
+
// Interrupt a batching-window wait so new arrivals join the batch now.
|
|
99
|
+
this.windowWaiter?.();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
this.delivering = true;
|
|
103
|
+
void this.run().catch(err => {
|
|
104
|
+
console.warn(`[engine-sqs-poller] delivery loop for ${this.queueName} crashed:`, err);
|
|
105
|
+
this.delivering = false;
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
// Single-flight drain: one batch in flight per ESM; wakes during a batch
|
|
109
|
+
// mark `pending` and the loop re-checks before going idle.
|
|
110
|
+
async run() {
|
|
111
|
+
do {
|
|
112
|
+
this.pending = false;
|
|
113
|
+
while (!this.stopped && this.esm.enabled && this.deps.sqs.hasVisibleMessages(this.region, this.queueName)) {
|
|
114
|
+
const progressed = await this.deliverBatch();
|
|
115
|
+
if (!progressed)
|
|
116
|
+
break; // paranoia: never hot-loop on an empty receive
|
|
117
|
+
}
|
|
118
|
+
} while (this.pending && !this.stopped && this.esm.enabled);
|
|
119
|
+
this.delivering = false;
|
|
120
|
+
if (!this.awaitingRedelivery)
|
|
121
|
+
this.clearTick();
|
|
122
|
+
}
|
|
123
|
+
async deliverBatch() {
|
|
124
|
+
const batchSize = this.esm.batchSize;
|
|
125
|
+
let batch = this.deps.sqs.receiveForDelivery(this.region, this.queueName, batchSize);
|
|
126
|
+
if (batch.length === 0)
|
|
127
|
+
return false;
|
|
128
|
+
// maximumBatchingWindowInSeconds: with a short batch, wait (once) up to
|
|
129
|
+
// the window for more messages — but never delay a full batch.
|
|
130
|
+
const windowMs = (this.esm.maximumBatchingWindowInSeconds ?? 0) * 1000;
|
|
131
|
+
if (windowMs > 0 && batch.length < batchSize) {
|
|
132
|
+
const deadline = Date.now() + windowMs;
|
|
133
|
+
while (batch.length < batchSize && !this.stopped) {
|
|
134
|
+
const remaining = deadline - Date.now();
|
|
135
|
+
if (remaining <= 0)
|
|
136
|
+
break;
|
|
137
|
+
await this.waitForWake(remaining);
|
|
138
|
+
if (this.stopped)
|
|
139
|
+
break;
|
|
140
|
+
batch = batch.concat(this.deps.sqs.receiveForDelivery(this.region, this.queueName, batchSize - batch.length));
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
if (this.stopped)
|
|
144
|
+
return true; // in-flight messages redeliver via visibility
|
|
145
|
+
this.armTick();
|
|
146
|
+
const event = buildSqsLambdaEvent(batch, this.esm.eventSourceArn, this.region);
|
|
147
|
+
const backoffs = this.deps.runtimeRetryBackoffMs ?? DEFAULT_RUNTIME_RETRY_BACKOFF_MS;
|
|
148
|
+
let attempt = 0;
|
|
149
|
+
for (;;) {
|
|
150
|
+
const result = await this.deps.invoke(this.esm.functionName, event);
|
|
151
|
+
if (result.ok) {
|
|
152
|
+
this.deps.sqs.deleteDelivered(this.region, this.queueName, batch.map(message => message.receiptHandle));
|
|
153
|
+
this.awaitingRedelivery = false;
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
if (result.errorType === 'RuntimeUnavailable' && attempt < backoffs.length && !this.stopped) {
|
|
157
|
+
await sleep(backoffs[attempt]);
|
|
158
|
+
attempt += 1;
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
// AWS semantics: the batch is NOT deleted; visibility expiry redelivers.
|
|
162
|
+
this.awaitingRedelivery = true;
|
|
163
|
+
return true;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
waitForWake(ms) {
|
|
167
|
+
return new Promise(resolve => {
|
|
168
|
+
const timer = setTimeout(() => {
|
|
169
|
+
this.windowWaiter = null;
|
|
170
|
+
resolve();
|
|
171
|
+
}, ms);
|
|
172
|
+
timer.unref();
|
|
173
|
+
this.windowWaiter = () => {
|
|
174
|
+
clearTimeout(timer);
|
|
175
|
+
this.windowWaiter = null;
|
|
176
|
+
resolve();
|
|
177
|
+
};
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
armTick() {
|
|
181
|
+
if (this.tick)
|
|
182
|
+
return;
|
|
183
|
+
this.tick = setInterval(() => this.wake(), SAFETY_TICK_MS);
|
|
184
|
+
this.tick.unref();
|
|
185
|
+
}
|
|
186
|
+
clearTick() {
|
|
187
|
+
if (this.tick) {
|
|
188
|
+
clearInterval(this.tick);
|
|
189
|
+
this.tick = null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Owns every SQS delivery loop; sync() reconciles them against the current
|
|
194
|
+
// ESM catalog (called at boot and from lambda-ctl's onEsmChanged hook).
|
|
195
|
+
export class SqsPollerSet {
|
|
196
|
+
deps;
|
|
197
|
+
loops = new Map();
|
|
198
|
+
constructor(deps) {
|
|
199
|
+
this.deps = deps;
|
|
200
|
+
}
|
|
201
|
+
sync(region) {
|
|
202
|
+
const alive = new Set();
|
|
203
|
+
for (const esm of this.deps.lambdaCtl.listEventSourceMappings(region)) {
|
|
204
|
+
if (!esm.eventSourceArn.startsWith('arn:aws:sqs:'))
|
|
205
|
+
continue;
|
|
206
|
+
// Enabled=false is the QueueInspector hold: its loop must stop promptly.
|
|
207
|
+
if (!esm.enabled)
|
|
208
|
+
continue;
|
|
209
|
+
alive.add(esm.uuid);
|
|
210
|
+
const existing = this.loops.get(esm.uuid);
|
|
211
|
+
if (existing) {
|
|
212
|
+
existing.update(esm);
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
const loop = new DeliveryLoop(this.deps, esm, region);
|
|
216
|
+
this.loops.set(esm.uuid, loop);
|
|
217
|
+
loop.start();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
for (const [uuid, loop] of this.loops) {
|
|
221
|
+
if (loop.region === region && !alive.has(uuid)) {
|
|
222
|
+
loop.stop();
|
|
223
|
+
this.loops.delete(uuid);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
stopAll() {
|
|
228
|
+
for (const loop of this.loops.values())
|
|
229
|
+
loop.stop();
|
|
230
|
+
this.loops.clear();
|
|
231
|
+
}
|
|
232
|
+
loopCount() {
|
|
233
|
+
return this.loops.size;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function sleep(ms) {
|
|
237
|
+
return new Promise(resolve => {
|
|
238
|
+
const timer = setTimeout(resolve, ms);
|
|
239
|
+
timer.unref();
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
//# sourceMappingURL=sqs-poller.js.map
|