@alzulejos/laranja-docs 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.
@@ -0,0 +1,131 @@
1
+ ---
2
+ title: HTTP apps
3
+ description: Deploy your HTTP app behind a public HTTPS endpoint.
4
+ order: 1
5
+ ---
6
+
7
+ # HTTP apps
8
+
9
+ laranja deploys your whole HTTP app as a single proxy Lambda behind a public
10
+ [Function URL](../reference/what-gets-deployed.md#http-app--proxy-lambda--function-url).
11
+ laranja supports **Express** and **NestJS**.
12
+
13
+ ## Declaring your app (the `http()` marker)
14
+
15
+ The code-first way: mark your app with the
16
+ [`http()`](../reference/decorators-and-markers.md#http) marker and export it.
17
+ laranja finds it by scanning your code — there's nothing to configure.
18
+
19
+ ```ts
20
+ // src/app.ts
21
+ import express from "express";
22
+ import { http } from "@alzulejos/laranja-decorators";
23
+
24
+ const app = express();
25
+ app.use(express.json());
26
+
27
+ app.get("/", (_req, res) => res.json({ ok: true }));
28
+ app.post("/users", (req, res) => res.status(201).json(req.body));
29
+
30
+ export default http(app); // or: export const api = http(app);
31
+ ```
32
+
33
+ `http()` returns the app untouched — it's a static marker, not a wrapper, so it
34
+ has no runtime effect. That's all you need: every route you register is served by
35
+ the deployed proxy. The marker is the only way to declare an HTTP app — there's
36
+ exactly one per project, and it must be exported so the scanner can find it.
37
+
38
+ ## NestJS
39
+
40
+ Nest apps work the same way, with one difference: a Nest app only exists after an
41
+ async `NestFactory.create(...)`, so instead of a ready app object you wrap your
42
+ **bootstrap function** and have it `return` the app:
43
+
44
+ ```ts
45
+ // src/main.ts
46
+ import { NestFactory } from "@nestjs/core";
47
+ import { http } from "@alzulejos/laranja-decorators";
48
+ import { AppModule } from "./app.module";
49
+
50
+ export async function bootstrap() {
51
+ const app = await NestFactory.create(AppModule);
52
+ // configure however you like — pipes, guards, middleware, raw body, cookies…
53
+ app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));
54
+ await app.listen(process.env.PORT ?? 3000); // fine to keep for local dev
55
+ return app; // ← the only change laranja needs
56
+ }
57
+
58
+ // Run locally with `npm run start`; skipped when laranja imports this file.
59
+ if (require.main === module) void bootstrap();
60
+
61
+ export default http(bootstrap); // wrap the factory, not a module
62
+ ```
63
+
64
+ laranja runs your `bootstrap()` verbatim, so every pipe, guard, and piece of
65
+ middleware you configure is preserved — nothing is re-derived. You keep your
66
+ normal Nest project (`@nestjs/platform-express`); no laranja-specific
67
+ restructuring.
68
+
69
+ Two things to know:
70
+
71
+ - **Build before you deploy.** laranja packages your compiled output (`nest build`
72
+ → `dist/`), because Nest's dependency injection relies on the decorator metadata
73
+ your own TypeScript build emits. laranja deploys what you build — it doesn't run
74
+ your build for you — so run `nest build` yourself after every code change.
75
+ Deploying without a `dist/` fails with a clear message, but a **stale** `dist/`
76
+ (source edited since your last build) deploys silently as outdated code, so make
77
+ the build part of your deploy step (e.g. `nest build && laranja deploy`).
78
+ - **Use the default Express platform.** The Fastify adapter isn't supported yet.
79
+
80
+ ## Routing, middleware, and `STAGE`
81
+
82
+ Your app runs as-is inside Lambda. Standard Express features work — routing,
83
+ middleware, JSON bodies, route params. The active
84
+ [stage](./stages-and-environments.md) is available as
85
+ `process.env.STAGE`:
86
+
87
+ ```ts
88
+ app.get("/whoami", (_req, res) => res.json({ stage: process.env.STAGE }));
89
+ ```
90
+
91
+ ## CORS and auth
92
+
93
+ The Function URL is public with permissive CORS (all origins/methods/headers).
94
+ Handle authentication and any stricter CORS rules **inside your app**, the same
95
+ way you would anywhere else.
96
+
97
+ ## Compute (memory & timeout)
98
+
99
+ The HTTP proxy Lambda's memory and timeout come from
100
+ [`compute`](../reference/config-file.md#compute) in your config — the scaffold
101
+ default is `{ memory: 256, timeout: 30 }`, and you can override it under the `http`
102
+ key in [`resources`](../reference/config-file.md#resources). Long-running work
103
+ belongs in a [cron job](./cron-jobs.md) or behind a [queue](./queues.md), not a
104
+ request.
105
+
106
+ ## Workers-only deployments
107
+
108
+ If your HTTP API is hosted elsewhere and you only want to deploy scheduled jobs
109
+ and queue consumers, just don't add an `http()` marker — there's nothing to set
110
+ in config:
111
+
112
+ ```ts
113
+ // laranja.config.ts
114
+ const config: LaranjaConfig = {
115
+ name: "my-workers",
116
+ env: { LOG_LEVEL: "info" },
117
+ };
118
+ ```
119
+
120
+ With no marker, only your [`@Cron`](./cron-jobs.md) / [`@Queue`](./queues.md)
121
+ handlers are deployed — no HTTP proxy, no Function URL.
122
+
123
+ For a **workers-only Nest** app, there's no `http(bootstrap)` to build the DI
124
+ container from, so declare your module with the
125
+ [`workers()`](../reference/decorators-and-markers.md#workers) marker instead
126
+ (`export default workers(AppModule)`) — see [Cron jobs → NestJS](./cron-jobs.md#nestjs).
127
+
128
+ ## Related
129
+
130
+ - [What gets deployed](../reference/what-gets-deployed.md)
131
+ - [Cron jobs](./cron-jobs.md) · [Queues](./queues.md)
@@ -0,0 +1,143 @@
1
+ ---
2
+ title: Queues
3
+ description: Process SQS messages with @Queue or queue(), including FIFO.
4
+ order: 3
5
+ ---
6
+
7
+ # Queues
8
+
9
+ A queue consumer processes messages from an SQS queue. Each one becomes
10
+ [an SQS queue plus a consumer Lambda](../reference/what-gets-deployed.md#queue--sqs-queue--consumer-lambda).
11
+
12
+ ## Class style — `@Queue`
13
+
14
+ Decorate a method with [`@Queue`](../reference/decorators-and-markers.md#queue):
15
+
16
+ ```ts
17
+ import { Queue } from "@alzulejos/laranja-decorators";
18
+
19
+ export class Workers {
20
+ @Queue({ name: "emails", batchSize: 10 })
21
+ async sendEmail(body: unknown) {
22
+ // `body` is the JSON-parsed message
23
+ }
24
+ }
25
+ ```
26
+
27
+ ## Function style — `queue()`
28
+
29
+ ```ts
30
+ import { queue } from "@alzulejos/laranja-decorators";
31
+
32
+ export async function sendEmail(body: unknown) {
33
+ // …
34
+ }
35
+
36
+ queue({ name: "emails", batchSize: 10 }, sendEmail);
37
+ ```
38
+
39
+ ## NestJS
40
+
41
+ `@Queue` works on a Nest provider with injected dependencies. As with
42
+ [cron jobs](./cron-jobs.md#nestjs), laranja resolves the consumer through your
43
+ DI container, so declare your module once with the
44
+ [`workers()`](../reference/decorators-and-markers.md#workers) marker
45
+ (`export default workers(AppModule)`) and deploy your compiled `dist/` output.
46
+ Standalone `queue()` functions don't need it.
47
+
48
+ ## Options
49
+
50
+ | Option | Default | Description |
51
+ |---|---|---|
52
+ | `name` | _required_ | Queue name. A `.fifo` suffix marks a FIFO queue. |
53
+ | `batchSize` | `10` | Max messages delivered to the consumer per invocation. |
54
+ | `fifo` | `false` | Force a FIFO queue (or end `name` with `.fifo`). When set, laranja appends `.fifo` to `name` if you left it off. |
55
+
56
+ ## How messages are delivered
57
+
58
+ - Your handler is invoked **once per message**, with the message **body already
59
+ JSON-parsed**.
60
+ - **Partial-batch failures** are enabled: if your handler throws for one message,
61
+ only that message is retried — the rest of the batch is still acknowledged.
62
+ - Consumer memory/timeout come from [`compute`](../reference/config-file.md#compute)
63
+ (default `{ memory: 256, timeout: 30 }`); the queue's visibility timeout is
64
+ derived to stay ≥ the consumer timeout (override it per queue via
65
+ [`resources`](../reference/config-file.md#resources)).
66
+
67
+ ```ts
68
+ @Queue({ name: "orders" })
69
+ async processOrder(body: unknown) {
70
+ const order = body as { id: string };
71
+ if (!order.id) throw new Error("bad message"); // only THIS message is retried
72
+ // …
73
+ }
74
+ ```
75
+
76
+ ## FIFO queues
77
+
78
+ End the name with `.fifo` (or set `fifo: true`) for ordered, exactly-once
79
+ processing. Content-based deduplication is enabled automatically:
80
+
81
+ ```ts
82
+ @Queue({ name: "orders.fifo", fifo: true })
83
+ async processOrder(body: unknown) {
84
+ // …
85
+ }
86
+ ```
87
+
88
+ AWS requires FIFO queue names to end in `.fifo`. If you set `fifo: true` but
89
+ leave the suffix off, laranja appends it for you — so `{ name: "orders", fifo: true }`
90
+ deploys a queue named `orders.fifo`. The normalized name is what appears in
91
+ `laranja plan` and in the AWS console, so there's no surprise at deploy time.
92
+
93
+ ## Sending messages
94
+
95
+ Consuming is only half the loop — to **produce** a message, call
96
+ [`getQueue`](../reference/decorators-and-markers.md#getqueue) with the queue's
97
+ `name` and `.send()` a payload:
98
+
99
+ ```ts
100
+ import { getQueue } from "@alzulejos/laranja-decorators";
101
+
102
+ app.post("/signup", async (req, res) => {
103
+ await getQueue("emails").send({ to: req.body.email, template: "welcome" });
104
+ res.sendStatus(202);
105
+ });
106
+ ```
107
+
108
+ Objects are JSON-serialized for you (strings are sent as-is), so the consumer
109
+ receives them already parsed — `getQueue("emails").send({ to })` on one end,
110
+ `async sendEmail(body)` on the other.
111
+
112
+ You can produce from **anywhere** in a deployed app — an HTTP route, a
113
+ [cron job](./cron-jobs.md), or another queue's consumer fanning out. laranja
114
+ injects each queue's URL into every function's environment at deploy and grants
115
+ `sqs:SendMessage`, so there's no client to configure, no URL to look up, and no
116
+ IAM to wire. It's a thin wrapper over one SQS `SendMessage` call — laranja
117
+ provisions the infrastructure; it deliberately does **not** add a job framework
118
+ (retries, scheduling, and job state stay with SQS and your consumer).
119
+
120
+ ### FIFO and options
121
+
122
+ `.send()` takes a second options argument:
123
+
124
+ | Option | Applies to | Description |
125
+ |---|---|---|
126
+ | `groupId` | FIFO (**required**) | `MessageGroupId` — messages with the same group are ordered. |
127
+ | `dedupId` | FIFO | `MessageDeduplicationId` — only needed when content-based dedup is off. |
128
+ | `delaySeconds` | Standard | Delay (0–900s) before the message becomes visible. Ignored by FIFO. |
129
+
130
+ ```ts
131
+ // FIFO queues require a groupId — the send throws without one.
132
+ await getQueue("orders.fifo").send(order, { groupId: order.customerId });
133
+ ```
134
+
135
+ > Prefer the raw SDK? The queue URL is also emitted as a stack output after
136
+ > deploy and visible in the AWS console — send with `@aws-sdk/client-sqs`
137
+ > directly if you'd rather.
138
+
139
+ ## Related
140
+
141
+ - [`@Queue` / `queue()` reference](../reference/decorators-and-markers.md#queue)
142
+ - [What gets deployed](../reference/what-gets-deployed.md#queue--sqs-queue--consumer-lambda)
143
+ - [Cron jobs](./cron-jobs.md)
@@ -0,0 +1,117 @@
1
+ ---
2
+ title: Schedules
3
+ description: The rate() and every() builders, and raw schedule expressions.
4
+ order: 4
5
+ ---
6
+
7
+ # Schedules
8
+
9
+ Schedules drive [cron jobs](./cron-jobs.md). laranja stores them in a
10
+ **provider-neutral** form, so prefer the builders — they're portable across
11
+ clouds. A raw expression string is available as an escape hatch.
12
+
13
+ ## `rate(value, unit)`
14
+
15
+ "Every N units." Portable everywhere.
16
+
17
+ ```ts
18
+ import { rate } from "@alzulejos/laranja-decorators";
19
+
20
+ rate(5, "minutes") // every 5 minutes
21
+ rate(1, "hour") // every hour
22
+ rate(2, "days") // every 2 days
23
+ ```
24
+
25
+ - `value` must be a **positive integer** (≥ 1).
26
+ - `unit` is one of `"minute"`, `"minutes"`, `"hour"`, `"hours"`, `"day"`,
27
+ `"days"` — singular or plural, your choice.
28
+
29
+ ## `every(unit)`
30
+
31
+ Shorthand for `rate(1, unit)`. Takes a singular unit:
32
+
33
+ ```ts
34
+ import { every } from "@alzulejos/laranja-decorators";
35
+
36
+ every("minute") // = rate(1, "minute")
37
+ every("hour") // = rate(1, "hour")
38
+ every("day") // = rate(1, "day")
39
+ ```
40
+
41
+ ## Raw AWS expressions (escape hatch)
42
+
43
+ When you need something the builders can't express (e.g. "noon UTC every day"),
44
+ pass a **wrapped** AWS schedule string — `cron(...)` or `rate(...)`:
45
+
46
+ ```ts
47
+ @Cron({ schedule: "cron(0 12 * * ? *)", id: "daily-report" }) // 12:00 UTC daily
48
+ @Cron("rate(5 minutes)") // raw rate string
49
+ ```
50
+
51
+ AWS cron has **six fields**: `cron(Minutes Hours Day-of-month Month Day-of-week
52
+ Year)`.
53
+
54
+ A few examples:
55
+
56
+ | Expression | Meaning |
57
+ |---|---|
58
+ | `cron(0 12 * * ? *)` | 12:00 UTC every day |
59
+ | `cron(0/15 * * * ? *)` | every 15 minutes |
60
+ | `cron(0 8 ? * MON-FRI *)` | 08:00 UTC on weekdays |
61
+ | `cron(0 0 1 * ? *)` | midnight UTC on the 1st of each month |
62
+
63
+ ## node-cron expressions (`@nestjs/schedule` compatibility)
64
+
65
+ A **bare** (unwrapped) string is read as a standard 5- or 6-field
66
+ [node-cron](https://github.com/kelektiv/node-cron) expression — the same syntax
67
+ `@nestjs/schedule`'s `@Cron` takes. laranja translates it to the AWS dialect for
68
+ you, so a Nest app can swap the import and keep its existing schedules:
69
+
70
+ ```ts
71
+ @Cron("0 12 * * *") // noon every day
72
+ @Cron("*/5 * * * *") // every 5 minutes
73
+ @Cron("0 0 * * 1-5") // midnight on weekdays
74
+ @Cron(CronExpression.EVERY_30_MINUTES) // the enum works too
75
+ ```
76
+
77
+ `CronExpression` (mirrored from `@nestjs/schedule`) is re-exported from
78
+ `@alzulejos/laranja-decorators`. Translation handles the day-of-week numbering difference
79
+ (Unix `0`=Sun → AWS `1`=Sun) and the day-of-month/day-of-week rule for you.
80
+
81
+ **What can't be translated is rejected at build time — never silently rounded:**
82
+
83
+ | Input | Why it's rejected |
84
+ |---|---|
85
+ | `"*/30 * * * * *"`, `CronExpression.EVERY_30_SECONDS` | Sub-minute — EventBridge's floor is **1 minute**. |
86
+ | A seconds field other than `0` | Second-level offsets can't be expressed. |
87
+ | A cron constraining **both** day-of-month and day-of-week | EventBridge requires one to be `*`. |
88
+
89
+ ## `@Interval(ms)`
90
+
91
+ `@nestjs/schedule`'s `@Interval` is supported and lowers to a `rate(...)`. The
92
+ interval must be a whole number of minutes (EventBridge's floor):
93
+
94
+ ```ts
95
+ @Interval(300000) // every 5 minutes → rate(5, "minutes")
96
+ @Interval("poll", 300000) // named
97
+ ```
98
+
99
+ > `@Timeout` (a one-shot timer relative to process start) has no serverless
100
+ > equivalent and is rejected with a clear message — use `@Cron` or `@Interval`.
101
+
102
+ ## Where you can use them
103
+
104
+ Anywhere a schedule is expected — the decorator or the function marker — accepts
105
+ a builder result, a `Schedule` object, or a raw string:
106
+
107
+ ```ts
108
+ @Cron(rate(5, "minutes")) // builder
109
+ @Cron("rate(5 minutes)") // raw string
110
+ @Cron({ schedule: every("day"), id: "nightly" })// builder + explicit id
111
+ cron(rate(1, "hour"), refreshCache); // function marker
112
+ ```
113
+
114
+ ## Related
115
+
116
+ - [Cron jobs](./cron-jobs.md)
117
+ - [`@Cron` / `cron()` reference](../reference/decorators-and-markers.md#cron)
@@ -0,0 +1,88 @@
1
+ ---
2
+ title: Stages & environments
3
+ description: Run dev, staging, and prod from one codebase with --stage.
4
+ order: 6
5
+ ---
6
+
7
+ # Stages & environments
8
+
9
+ A **stage** is a named environment — `dev`, `staging`, `prod`, or anything you
10
+ like. laranja makes each stage a fully independent deployment from the same
11
+ codebase.
12
+
13
+ ## Setting the stage
14
+
15
+ The default stage is `dev`. Set it in config:
16
+
17
+ ```ts
18
+ // laranja.config.ts
19
+ const config: LaranjaConfig = { name: "my-api", stage: "dev" };
20
+ ```
21
+
22
+ …or override it per command with `--stage` (alias `-s`):
23
+
24
+ ```bash
25
+ laranja deploy --stage prod
26
+ laranja deploy -s staging
27
+ ```
28
+
29
+ The flag wins over the config value, which is why the recommended setup keeps
30
+ `stage` at its default in config and lets each pipeline pass `--stage`.
31
+
32
+ `--stage` applies to every environment-aware command: `deploy`, `plan`,
33
+ `destroy`, `logs`, and `eject`.
34
+
35
+ ## Each stage is its own stack
36
+
37
+ The stage is part of the **stack name** (`‹name›-‹stage›`) and every resource
38
+ name (`‹name›-‹fn›-‹stage›`). So `--stage dev` and `--stage prod` produce two
39
+ **independent CloudFormation stacks** that never collide — even in the same AWS
40
+ account.
41
+
42
+ ```
43
+ my-api-dev ← laranja deploy --stage dev
44
+ my-api-prod ← laranja deploy --stage prod
45
+ ```
46
+
47
+ ## Two ways to isolate environments
48
+
49
+ Both work, and they compose:
50
+
51
+ 1. **One account, multiple stages.** The stage suffix keeps the stacks separate.
52
+ Good for small projects or non-prod environments.
53
+ 2. **Separate accounts per stage.** Point each pipeline at different AWS
54
+ credentials (a dev account and a prod account). Here your **AWS credentials
55
+ are the real boundary**; the stack name can even repeat across accounts
56
+ without conflict.
57
+
58
+ ## One pipeline per stage
59
+
60
+ The canonical CI/CD setup is one pipeline per environment, each running the same
61
+ command with a different flag:
62
+
63
+ ```yaml
64
+ # dev pipeline → laranja deploy --stage dev
65
+ # staging pipeline → laranja deploy --stage staging
66
+ # prod pipeline → laranja deploy --stage prod
67
+ ```
68
+
69
+ Same repo, same command — only the flag differs. No per-environment config files
70
+ to keep in sync. Pair this with [per-stage env values](./environment-variables.md#per-stage-values)
71
+ to supply each environment's configuration.
72
+
73
+ > Consistency matters: a pipeline's `destroy`, `logs`, and `plan` must use the
74
+ > **same `--stage`** as its `deploy`, or they'll target a different stack.
75
+
76
+ ## The `STAGE` env var
77
+
78
+ The active stage is injected into every Lambda as `process.env.STAGE`, so your
79
+ code can branch on it:
80
+
81
+ ```ts
82
+ const isProd = process.env.STAGE === "prod";
83
+ ```
84
+
85
+ ## Related
86
+
87
+ - [Config file](../reference/config-file.md)
88
+ - [Environment variables](./environment-variables.md)
@@ -0,0 +1,44 @@
1
+ ---
2
+ title: Documentation
3
+ description: Code-first deploys for Node.js apps to your own AWS account.
4
+ order: 0
5
+ ---
6
+
7
+ # laranja docs
8
+
9
+ **laranja** deploys your Node.js app to your own AWS account from your code — no
10
+ YAML, no console clicking, no separate infrastructure project. You write an
11
+ Express app plus a few functions or decorators; laranja reads the code, figures
12
+ out the infrastructure, and ships it.
13
+
14
+ ```bash
15
+ npm install -D @alzulejos/laranja
16
+ npx laranja deploy
17
+ ```
18
+
19
+ ## Start here
20
+
21
+ - **[Introduction](./getting-started/introduction.md)** — what laranja is and the ideas behind it.
22
+ - **[Installation](./getting-started/installation.md)** — prerequisites and setup.
23
+ - **[Quickstart](./getting-started/quickstart.md)** — from zero to a live URL.
24
+ - **[How it works](./getting-started/how-it-works.md)** — how your code becomes a running app on AWS.
25
+
26
+ ## Guides
27
+
28
+ - **[HTTP apps](./guides/http-apps.md)** — deploy your app behind a public URL with the `http()` marker.
29
+ - **[Cron jobs](./guides/cron-jobs.md)** — scheduled functions with `@Cron` / `cron()`.
30
+ - **[Queues](./guides/queues.md)** — SQS consumers with `@Queue` / `queue()`.
31
+ - **[Schedules](./guides/schedules.md)** — the `rate()` / `every()` builders and raw expressions.
32
+ - **[Environment variables](./guides/environment-variables.md)** — `env`, `STAGE`, and resolution.
33
+ - **[Stages & environments](./guides/stages-and-environments.md)** — dev / staging / prod with one codebase.
34
+
35
+ ## Reference
36
+
37
+ - **[CLI commands](./reference/commands.md)** — `init`, `logout`, `plan`, `deploy`, `destroy`, `logs`, `eject`.
38
+ - **[Config file](./reference/config-file.md)** — every field in `laranja.config.ts`.
39
+ - **[Decorators & markers](./reference/decorators-and-markers.md)** — `@Cron`, `@Queue`, `cron`, `queue`, `http`, `env`.
40
+ - **[What gets deployed](./reference/what-gets-deployed.md)** — the AWS resources and how they're named.
41
+
42
+ > **Status:** v1 targets **AWS** with **Express** today; **NestJS support is
43
+ > coming**. The internal model is provider- and framework-neutral, so new clouds
44
+ > and frameworks land without changing your app code.