@alzulejos/laranja-decorators 0.2.4
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/README.md +33 -0
- package/dist/index.d.ts +151 -0
- package/dist/index.js +131 -0
- package/dist/index.js.map +1 -0
- package/dist/producer.d.ts +36 -0
- package/dist/producer.js +46 -0
- package/dist/producer.js.map +1 -0
- package/package.json +24 -0
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# @alzulejos/laranja-decorators
|
|
2
|
+
|
|
3
|
+
Decorators and markers for [laranja](https://laranja.io) — mark your HTTP app, scheduled jobs, and queue consumers in your Node.js app. laranja scans these statically and provisions the matching AWS infrastructure.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @alzulejos/laranja-decorators
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { http, Cron, Queue, rate, every } from "@alzulejos/laranja-decorators";
|
|
11
|
+
|
|
12
|
+
export default http(app); // mark your Express/NestJS app
|
|
13
|
+
|
|
14
|
+
export class Jobs {
|
|
15
|
+
@Cron(rate(5, "minutes"))
|
|
16
|
+
async refreshCache() {}
|
|
17
|
+
|
|
18
|
+
@Cron(every("day"))
|
|
19
|
+
async nightlyCleanup() {}
|
|
20
|
+
|
|
21
|
+
@Cron({ schedule: "cron(0 12 * * ? *)", id: "daily-report" })
|
|
22
|
+
async dailyReport() {}
|
|
23
|
+
|
|
24
|
+
@Queue({ name: "emails", batchSize: 10 })
|
|
25
|
+
async sendEmails(body: unknown) {}
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- **`http(app)`** → one Lambda behind a Function URL serving all your routes. The sole way to declare your HTTP app; exactly one per project.
|
|
30
|
+
- **`@Cron(schedule)`** → EventBridge rule + Lambda. `schedule` is an AWS expression; use `rate(n, unit)` / `every(unit)` or a raw `"cron(...)"`/`"rate(...)"` string. Pass `{ schedule, id }` to set a name.
|
|
31
|
+
- **`@Queue({ name, batchSize?, fifo? })`** → SQS queue + consumer Lambda, called once per message with the JSON-parsed body.
|
|
32
|
+
|
|
33
|
+
📖 **Full docs:** https://laranja.io/docs
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The decorators the user applies to their job classes.
|
|
3
|
+
*
|
|
4
|
+
* These are intentionally near-no-ops at runtime: the *scanner* discovers them
|
|
5
|
+
* statically (via ts-morph) and bakes class + method names into generated Lambda
|
|
6
|
+
* entry shims. The runtime metadata registry below exists only so that future
|
|
7
|
+
* runtime-reflection paths (and tooling/tests) can enumerate handlers too.
|
|
8
|
+
*/
|
|
9
|
+
export { rate, every, CronExpression } from "@alzulejos/laranja-core";
|
|
10
|
+
export type { RateUnit, Schedule, ScheduleInput } from "@alzulejos/laranja-core";
|
|
11
|
+
export type { LaranjaConfig, ComputeConfig, ResourceConfig, } from "@alzulejos/laranja-core";
|
|
12
|
+
export { getQueue } from "./producer.js";
|
|
13
|
+
export type { LaranjaQueue, SendOptions } from "./producer.js";
|
|
14
|
+
import type { ScheduleInput } from "@alzulejos/laranja-core";
|
|
15
|
+
/** Options object `@nestjs/schedule`'s `@Cron` accepts as its SECOND argument. */
|
|
16
|
+
export interface NestCronOptions {
|
|
17
|
+
/** Stable name for the job — laranja uses it as the resource id. */
|
|
18
|
+
name?: string;
|
|
19
|
+
/** IANA timezone the schedule is evaluated in. */
|
|
20
|
+
timeZone?: string;
|
|
21
|
+
/** Present for signature compatibility; ignored by laranja's static scan. */
|
|
22
|
+
utcOffset?: number | string;
|
|
23
|
+
disabled?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface CronOptions {
|
|
26
|
+
/** A `rate(...)`/`every(...)` builder result, or a raw provider string. */
|
|
27
|
+
schedule: ScheduleInput;
|
|
28
|
+
/** Stable logical id. Defaults to "<Class>-<method>". */
|
|
29
|
+
id?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface QueueOptions {
|
|
32
|
+
/** Queue name. A ".fifo" suffix (or `fifo: true`) marks a FIFO queue. */
|
|
33
|
+
name: string;
|
|
34
|
+
batchSize?: number;
|
|
35
|
+
fifo?: boolean;
|
|
36
|
+
}
|
|
37
|
+
export type HandlerKind = "cron" | "queue";
|
|
38
|
+
export interface RegisteredHandler {
|
|
39
|
+
kind: HandlerKind;
|
|
40
|
+
className: string;
|
|
41
|
+
method: string;
|
|
42
|
+
options: CronOptions | QueueOptions;
|
|
43
|
+
}
|
|
44
|
+
/** Module-level registry, populated when decorated classes are imported. */
|
|
45
|
+
export declare const handlerRegistry: RegisteredHandler[];
|
|
46
|
+
/** The shape of a standalone handler passed to `cron()` / `queue()`. */
|
|
47
|
+
export type JobHandler = (...args: any[]) => unknown | Promise<unknown>;
|
|
48
|
+
/**
|
|
49
|
+
* Schedules a method on an EventBridge rule. Each `@Cron` becomes its own Lambda.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* @Cron("rate(5 minutes)")
|
|
53
|
+
* async refreshCache() {}
|
|
54
|
+
*
|
|
55
|
+
* @Cron({ schedule: "cron(0 12 * * ? *)", id: "daily-report" })
|
|
56
|
+
* async dailyReport() {}
|
|
57
|
+
*/
|
|
58
|
+
export declare function Cron(schedule: ScheduleInput): MethodDecorator;
|
|
59
|
+
export declare function Cron(options: CronOptions): MethodDecorator;
|
|
60
|
+
/** `@nestjs/schedule`-compatible form: a cron string/expression + optional options. */
|
|
61
|
+
export declare function Cron(expression: string, options?: NestCronOptions): MethodDecorator;
|
|
62
|
+
/**
|
|
63
|
+
* `@nestjs/schedule`-compatible `@Interval`. Runs a method every N milliseconds;
|
|
64
|
+
* laranja lowers it to an EventBridge `rate(...)`, so the interval must be a whole
|
|
65
|
+
* number of minutes (EventBridge's floor). Discovered statically by the scanner.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* @Interval(300000) // every 5 minutes
|
|
69
|
+
* @Interval("poll", 300000) // named
|
|
70
|
+
*/
|
|
71
|
+
export declare function Interval(milliseconds: number): MethodDecorator;
|
|
72
|
+
export declare function Interval(name: string, milliseconds: number): MethodDecorator;
|
|
73
|
+
/**
|
|
74
|
+
* `@nestjs/schedule`-compatible `@Timeout`. Present so swapped imports compile,
|
|
75
|
+
* but a one-shot timer relative to process start has no serverless equivalent —
|
|
76
|
+
* the scanner rejects it at build time with a clear message.
|
|
77
|
+
*/
|
|
78
|
+
export declare function Timeout(milliseconds: number): MethodDecorator;
|
|
79
|
+
export declare function Timeout(name: string, milliseconds: number): MethodDecorator;
|
|
80
|
+
/**
|
|
81
|
+
* Consumes messages from an SQS queue. Each `@Queue` becomes its own Lambda.
|
|
82
|
+
*
|
|
83
|
+
* @example
|
|
84
|
+
* @Queue({ name: "emails", batchSize: 10 })
|
|
85
|
+
* async sendEmails(event: unknown) {}
|
|
86
|
+
*/
|
|
87
|
+
export declare function Queue(options: QueueOptions): MethodDecorator;
|
|
88
|
+
/**
|
|
89
|
+
* Function-style counterpart to `@Cron` — for codebases that don't use classes.
|
|
90
|
+
* Register a standalone exported function on a schedule. The function's name
|
|
91
|
+
* becomes the resource id (unless you pass an explicit `id`). Like the
|
|
92
|
+
* decorators, this is a near-no-op at runtime: the scanner reads it statically.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* export async function refreshCache() {}
|
|
96
|
+
* cron(rate(5, "minutes"), refreshCache);
|
|
97
|
+
*/
|
|
98
|
+
export declare function cron(schedule: ScheduleInput, handler: JobHandler): void;
|
|
99
|
+
export declare function cron(options: CronOptions, handler: JobHandler): void;
|
|
100
|
+
/**
|
|
101
|
+
* Function-style counterpart to `@Queue`. Register a standalone exported function
|
|
102
|
+
* as an SQS consumer.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* export async function sendEmails(body: unknown) {}
|
|
106
|
+
* queue({ name: "emails", batchSize: 10 }, sendEmails);
|
|
107
|
+
*/
|
|
108
|
+
export declare function queue(options: QueueOptions, handler: JobHandler): void;
|
|
109
|
+
/**
|
|
110
|
+
* Marks the HTTP app (the proxy target) for laranja, code-first. Export the
|
|
111
|
+
* result so the scanner (and the generated shim) can find it:
|
|
112
|
+
*
|
|
113
|
+
* export default http(app); // or
|
|
114
|
+
* export const api = http(app);
|
|
115
|
+
*
|
|
116
|
+
* Identity at runtime: it returns the app untouched. The scanner reads it
|
|
117
|
+
* statically; omit it entirely for a workers-only deployment.
|
|
118
|
+
*/
|
|
119
|
+
export declare function http<T>(app: T): T;
|
|
120
|
+
/**
|
|
121
|
+
* Declare the Nest module laranja resolves background workers (@Cron / @Queue)
|
|
122
|
+
* against — code-first, the DI counterpart to `http()`. Export the result so the
|
|
123
|
+
* scanner (and the generated worker shims) can find it:
|
|
124
|
+
*
|
|
125
|
+
* export default workers(AppModule); // or
|
|
126
|
+
* export const jobs = workers(AppModule);
|
|
127
|
+
*
|
|
128
|
+
* At runtime laranja builds a standalone Nest context from this module
|
|
129
|
+
* (`NestFactory.createApplicationContext`) so each worker Lambda resolves its
|
|
130
|
+
* provider — and that provider's injected dependencies — through real DI instead
|
|
131
|
+
* of a bare `new`. Pass `AppModule` for the whole graph, or a leaner module you
|
|
132
|
+
* compose if you want a smaller cold start. Identity at runtime: returns the
|
|
133
|
+
* module untouched; only Nest projects with crons/queues need it.
|
|
134
|
+
*/
|
|
135
|
+
export declare function workers<T>(module: T): T;
|
|
136
|
+
/**
|
|
137
|
+
* Declare an environment variable your code needs at runtime — code-first.
|
|
138
|
+
*
|
|
139
|
+
* At runtime this is nothing more than a read of `process.env[name]`. Its value
|
|
140
|
+
* to laranja is *static discovery*: the scanner finds every `env("NAME")` call
|
|
141
|
+
* (NAME must be a string literal) and records the name in the IR. The deploy
|
|
142
|
+
* step then resolves each name from your shell / CI `process.env` and populates
|
|
143
|
+
* the Lambda's environment for you — no more filling vars in the console.
|
|
144
|
+
*
|
|
145
|
+
* Only the NAME crosses the wire to the server; the VALUE is resolved on your
|
|
146
|
+
* machine at deploy time and never leaves it.
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* const dbUrl = env("DATABASE_URL");
|
|
150
|
+
*/
|
|
151
|
+
export declare function env(name: string): string | undefined;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The decorators the user applies to their job classes.
|
|
3
|
+
*
|
|
4
|
+
* These are intentionally near-no-ops at runtime: the *scanner* discovers them
|
|
5
|
+
* statically (via ts-morph) and bakes class + method names into generated Lambda
|
|
6
|
+
* entry shims. The runtime metadata registry below exists only so that future
|
|
7
|
+
* runtime-reflection paths (and tooling/tests) can enumerate handlers too.
|
|
8
|
+
*/
|
|
9
|
+
// Re-export the schedule builders + types so users import them alongside @Cron.
|
|
10
|
+
// `CronExpression` is the `@nestjs/schedule` enum, mirrored so a Nest user can
|
|
11
|
+
// repoint their import at laranja and keep their existing @Cron(CronExpression.X).
|
|
12
|
+
export { rate, every, CronExpression } from "@alzulejos/laranja-core";
|
|
13
|
+
// The queue PRODUCER (`getQueue(name).send(...)`) — the counterpart to the
|
|
14
|
+
// @Queue / queue() consumers below. Unlike the markers this one does real work at
|
|
15
|
+
// runtime (an SQS SendMessage), but it lives here so users have a single import
|
|
16
|
+
// surface for everything queue-related.
|
|
17
|
+
export { getQueue } from "./producer.js";
|
|
18
|
+
/** Module-level registry, populated when decorated classes are imported. */
|
|
19
|
+
export const handlerRegistry = [];
|
|
20
|
+
function register(kind, target, method, options) {
|
|
21
|
+
handlerRegistry.push({
|
|
22
|
+
kind,
|
|
23
|
+
className: target.constructor.name,
|
|
24
|
+
method: String(method),
|
|
25
|
+
options,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
function registerFunction(kind, handler, options) {
|
|
29
|
+
const name = handler.name || "(anonymous)";
|
|
30
|
+
handlerRegistry.push({ kind, className: name, method: name, options });
|
|
31
|
+
}
|
|
32
|
+
export function Cron(arg, _nestOptions) {
|
|
33
|
+
// The second (Nest) argument is read statically by the scanner (name -> id,
|
|
34
|
+
// timeZone -> timezone); at runtime this decorator is a near-no-op registry write.
|
|
35
|
+
const options = toCronOptions(arg);
|
|
36
|
+
return (target, propertyKey) => {
|
|
37
|
+
register("cron", target, propertyKey, options);
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function Interval(_a, _b) {
|
|
41
|
+
return (target, propertyKey) => {
|
|
42
|
+
register("cron", target, propertyKey, { schedule: "" });
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
export function Timeout(_a, _b) {
|
|
46
|
+
return () => { };
|
|
47
|
+
}
|
|
48
|
+
/** Normalize the `Cron`/`cron` first argument (raw string, Schedule, or full options) into CronOptions. */
|
|
49
|
+
function toCronOptions(arg) {
|
|
50
|
+
if (typeof arg === "string")
|
|
51
|
+
return { schedule: arg };
|
|
52
|
+
if ("kind" in arg)
|
|
53
|
+
return { schedule: arg }; // a Schedule object
|
|
54
|
+
return arg; // already CronOptions
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Consumes messages from an SQS queue. Each `@Queue` becomes its own Lambda.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* @Queue({ name: "emails", batchSize: 10 })
|
|
61
|
+
* async sendEmails(event: unknown) {}
|
|
62
|
+
*/
|
|
63
|
+
export function Queue(options) {
|
|
64
|
+
return (target, propertyKey) => {
|
|
65
|
+
register("queue", target, propertyKey, options);
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
export function cron(arg, handler) {
|
|
69
|
+
registerFunction("cron", handler, toCronOptions(arg));
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Function-style counterpart to `@Queue`. Register a standalone exported function
|
|
73
|
+
* as an SQS consumer.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* export async function sendEmails(body: unknown) {}
|
|
77
|
+
* queue({ name: "emails", batchSize: 10 }, sendEmails);
|
|
78
|
+
*/
|
|
79
|
+
export function queue(options, handler) {
|
|
80
|
+
registerFunction("queue", handler, options);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Marks the HTTP app (the proxy target) for laranja, code-first. Export the
|
|
84
|
+
* result so the scanner (and the generated shim) can find it:
|
|
85
|
+
*
|
|
86
|
+
* export default http(app); // or
|
|
87
|
+
* export const api = http(app);
|
|
88
|
+
*
|
|
89
|
+
* Identity at runtime: it returns the app untouched. The scanner reads it
|
|
90
|
+
* statically; omit it entirely for a workers-only deployment.
|
|
91
|
+
*/
|
|
92
|
+
export function http(app) {
|
|
93
|
+
return app;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Declare the Nest module laranja resolves background workers (@Cron / @Queue)
|
|
97
|
+
* against — code-first, the DI counterpart to `http()`. Export the result so the
|
|
98
|
+
* scanner (and the generated worker shims) can find it:
|
|
99
|
+
*
|
|
100
|
+
* export default workers(AppModule); // or
|
|
101
|
+
* export const jobs = workers(AppModule);
|
|
102
|
+
*
|
|
103
|
+
* At runtime laranja builds a standalone Nest context from this module
|
|
104
|
+
* (`NestFactory.createApplicationContext`) so each worker Lambda resolves its
|
|
105
|
+
* provider — and that provider's injected dependencies — through real DI instead
|
|
106
|
+
* of a bare `new`. Pass `AppModule` for the whole graph, or a leaner module you
|
|
107
|
+
* compose if you want a smaller cold start. Identity at runtime: returns the
|
|
108
|
+
* module untouched; only Nest projects with crons/queues need it.
|
|
109
|
+
*/
|
|
110
|
+
export function workers(module) {
|
|
111
|
+
return module;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Declare an environment variable your code needs at runtime — code-first.
|
|
115
|
+
*
|
|
116
|
+
* At runtime this is nothing more than a read of `process.env[name]`. Its value
|
|
117
|
+
* to laranja is *static discovery*: the scanner finds every `env("NAME")` call
|
|
118
|
+
* (NAME must be a string literal) and records the name in the IR. The deploy
|
|
119
|
+
* step then resolves each name from your shell / CI `process.env` and populates
|
|
120
|
+
* the Lambda's environment for you — no more filling vars in the console.
|
|
121
|
+
*
|
|
122
|
+
* Only the NAME crosses the wire to the server; the VALUE is resolved on your
|
|
123
|
+
* machine at deploy time and never leaves it.
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* const dbUrl = env("DATABASE_URL");
|
|
127
|
+
*/
|
|
128
|
+
export function env(name) {
|
|
129
|
+
return process.env[name];
|
|
130
|
+
}
|
|
131
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-message options for `getQueue(name).send()`. FIFO queues REQUIRE `groupId`
|
|
3
|
+
* (SQS rejects a FIFO send without a MessageGroupId); `dedupId` is only needed
|
|
4
|
+
* when the queue doesn't use content-based deduplication. `delaySeconds` is
|
|
5
|
+
* ignored by FIFO queues (SQS limitation), so it's a standard-queue knob.
|
|
6
|
+
*/
|
|
7
|
+
export interface SendOptions {
|
|
8
|
+
/** MessageGroupId — required for FIFO queues, ignored for standard. */
|
|
9
|
+
groupId?: string;
|
|
10
|
+
/** MessageDeduplicationId — FIFO only, when content-based dedup is off. */
|
|
11
|
+
dedupId?: string;
|
|
12
|
+
/** Delay before the message becomes visible (0–900s). Standard queues only. */
|
|
13
|
+
delaySeconds?: number;
|
|
14
|
+
}
|
|
15
|
+
/** A minimal producer handle for one declared queue. Returned by `getQueue()`. */
|
|
16
|
+
export interface LaranjaQueue {
|
|
17
|
+
/** The resolved SQS URL this handle sends to. */
|
|
18
|
+
readonly url: string;
|
|
19
|
+
/** Enqueue a message. Objects are JSON-serialized; strings are sent as-is. */
|
|
20
|
+
send(payload: unknown, options?: SendOptions): Promise<{
|
|
21
|
+
messageId?: string;
|
|
22
|
+
}>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Producer counterpart to the `@Queue` / `queue()` consumer: get a handle to a
|
|
26
|
+
* declared queue and `.send()` messages to it. laranja provisions the wire — the
|
|
27
|
+
* SQS URL is injected into every function's env at deploy and `sqs:SendMessage`
|
|
28
|
+
* is granted — so this is pure infra glue, not a job framework: it resolves the
|
|
29
|
+
* URL and makes one `SendMessage` call, nothing more.
|
|
30
|
+
*
|
|
31
|
+
* @param name The queue's declared `name` (as in `queue({ name })`).
|
|
32
|
+
* @example
|
|
33
|
+
* await getQueue("emails").send({ to, subject });
|
|
34
|
+
* await getQueue("orders.fifo").send(order, { groupId: order.customerId });
|
|
35
|
+
*/
|
|
36
|
+
export declare function getQueue(name: string): LaranjaQueue;
|
package/dist/producer.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { SQSClient, SendMessageCommand } from "@aws-sdk/client-sqs";
|
|
2
|
+
import { queueUrlEnvName } from "@alzulejos/laranja-core";
|
|
3
|
+
// One SQS client for the whole Lambda invocation environment — created lazily so
|
|
4
|
+
// importing this module has no cost for functions that never produce, and reused
|
|
5
|
+
// across warm invocations. Region comes from the Lambda's AWS_REGION env.
|
|
6
|
+
let client;
|
|
7
|
+
function sqs() {
|
|
8
|
+
return (client ??= new SQSClient({}));
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Producer counterpart to the `@Queue` / `queue()` consumer: get a handle to a
|
|
12
|
+
* declared queue and `.send()` messages to it. laranja provisions the wire — the
|
|
13
|
+
* SQS URL is injected into every function's env at deploy and `sqs:SendMessage`
|
|
14
|
+
* is granted — so this is pure infra glue, not a job framework: it resolves the
|
|
15
|
+
* URL and makes one `SendMessage` call, nothing more.
|
|
16
|
+
*
|
|
17
|
+
* @param name The queue's declared `name` (as in `queue({ name })`).
|
|
18
|
+
* @example
|
|
19
|
+
* await getQueue("emails").send({ to, subject });
|
|
20
|
+
* await getQueue("orders.fifo").send(order, { groupId: order.customerId });
|
|
21
|
+
*/
|
|
22
|
+
export function getQueue(name) {
|
|
23
|
+
const url = process.env[queueUrlEnvName(name)];
|
|
24
|
+
if (!url) {
|
|
25
|
+
throw new Error(`getQueue("${name}"): no queue URL in env. Is "${name}" a declared queue in this project?`);
|
|
26
|
+
}
|
|
27
|
+
const isFifo = url.endsWith(".fifo");
|
|
28
|
+
return {
|
|
29
|
+
url,
|
|
30
|
+
async send(payload, options = {}) {
|
|
31
|
+
if (isFifo && !options.groupId) {
|
|
32
|
+
throw new Error(`getQueue("${name}").send: FIFO queue requires a groupId.`);
|
|
33
|
+
}
|
|
34
|
+
const body = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
35
|
+
const out = await sqs().send(new SendMessageCommand({
|
|
36
|
+
QueueUrl: url,
|
|
37
|
+
MessageBody: body,
|
|
38
|
+
MessageGroupId: isFifo ? options.groupId : undefined,
|
|
39
|
+
MessageDeduplicationId: isFifo ? options.dedupId : undefined,
|
|
40
|
+
DelaySeconds: !isFifo ? options.delaySeconds : undefined,
|
|
41
|
+
}));
|
|
42
|
+
return { messageId: out.MessageId };
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=producer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"producer.js","sourceRoot":"","sources":["../src/producer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAyB1D,iFAAiF;AACjF,iFAAiF;AACjF,0EAA0E;AAC1E,IAAI,MAA6B,CAAC;AAClC,SAAS,GAAG;IACV,OAAO,CAAC,MAAM,KAAK,IAAI,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC;AACxC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC;IAC/C,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,aAAa,IAAI,gCAAgC,IAAI,qCAAqC,CAC3F,CAAC;IACJ,CAAC;IACD,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;IAErC,OAAO;QACL,GAAG;QACH,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,EAAE;YAC9B,IAAI,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;gBAC/B,MAAM,IAAI,KAAK,CAAC,aAAa,IAAI,yCAAyC,CAAC,CAAC;YAC9E,CAAC;YACD,MAAM,IAAI,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YAC7E,MAAM,GAAG,GAAG,MAAM,GAAG,EAAE,CAAC,IAAI,CAC1B,IAAI,kBAAkB,CAAC;gBACrB,QAAQ,EAAE,GAAG;gBACb,WAAW,EAAE,IAAI;gBACjB,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;gBACpD,sBAAsB,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;gBAC5D,YAAY,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;aACzD,CAAC,CACH,CAAC;YACF,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC;QACtC,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alzulejos/laranja-decorators",
|
|
3
|
+
"version": "0.2.4",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@aws-sdk/client-sqs": "^3.658.0",
|
|
22
|
+
"@alzulejos/laranja-core": "0.2.4"
|
|
23
|
+
}
|
|
24
|
+
}
|