@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,29 @@
1
+ export interface DocPage {
2
+ /** Route slug relative to the docs root ("" is the home page). */
3
+ slug: string;
4
+ /** Top-level folder ("" for top-level files). */
5
+ section: string;
6
+ title: string;
7
+ description: string;
8
+ order: number;
9
+ /** Markdown body with frontmatter stripped. */
10
+ body: string;
11
+ }
12
+
13
+ export interface NavSection {
14
+ id: string;
15
+ title: string;
16
+ items: DocPage[];
17
+ }
18
+
19
+ export interface TocItem {
20
+ depth: number;
21
+ text: string;
22
+ id: string;
23
+ }
24
+
25
+ export declare const pages: DocPage[];
26
+ export declare const nav: NavSection[];
27
+ export declare const orderedPages: DocPage[];
28
+ export declare function getPage(slug: string): DocPage | undefined;
29
+ export declare function tableOfContents(body: string): TocItem[];
package/dist/index.js ADDED
@@ -0,0 +1,38 @@
1
+ // GENERATED by scripts/build.mjs — do not edit. Run `npm run build`.
2
+ import GithubSlugger from "github-slugger";
3
+
4
+ export const pages = [{"slug":"","section":"","title":"Documentation","description":"Code-first deploys for Node.js apps to your own AWS account.","order":0,"body":"\n# laranja docs\n\n**laranja** deploys your Node.js app to your own AWS account from your code — no\nYAML, no console clicking, no separate infrastructure project. You write an\nExpress app plus a few functions or decorators; laranja reads the code, figures\nout the infrastructure, and ships it.\n\n```bash\nnpm install -D @alzulejos/laranja\nnpx laranja deploy\n```\n\n## Start here\n\n- **[Introduction](./getting-started/introduction.md)** — what laranja is and the ideas behind it.\n- **[Installation](./getting-started/installation.md)** — prerequisites and setup.\n- **[Quickstart](./getting-started/quickstart.md)** — from zero to a live URL.\n- **[How it works](./getting-started/how-it-works.md)** — how your code becomes a running app on AWS.\n\n## Guides\n\n- **[HTTP apps](./guides/http-apps.md)** — deploy your app behind a public URL with the `http()` marker.\n- **[Cron jobs](./guides/cron-jobs.md)** — scheduled functions with `@Cron` / `cron()`.\n- **[Queues](./guides/queues.md)** — SQS consumers with `@Queue` / `queue()`.\n- **[Schedules](./guides/schedules.md)** — the `rate()` / `every()` builders and raw expressions.\n- **[Environment variables](./guides/environment-variables.md)** — `env`, `STAGE`, and resolution.\n- **[Stages & environments](./guides/stages-and-environments.md)** — dev / staging / prod with one codebase.\n\n## Reference\n\n- **[CLI commands](./reference/commands.md)** — `init`, `logout`, `plan`, `deploy`, `destroy`, `logs`, `eject`.\n- **[Config file](./reference/config-file.md)** — every field in `laranja.config.ts`.\n- **[Decorators & markers](./reference/decorators-and-markers.md)** — `@Cron`, `@Queue`, `cron`, `queue`, `http`, `env`.\n- **[What gets deployed](./reference/what-gets-deployed.md)** — the AWS resources and how they're named.\n\n> **Status:** v1 targets **AWS** with **Express** today; **NestJS support is\n> coming**. The internal model is provider- and framework-neutral, so new clouds\n> and frameworks land without changing your app code.\n"},{"slug":"getting-started/how-it-works","section":"getting-started","title":"How it works","description":"How laranja turns your code into a running app on AWS.","order":4,"body":"\n# How it works\n\nYou write your app; laranja deploys it to your own AWS account. Two things are\nworth knowing about how it does that.\n\n## It reads your code — it never runs it\n\nlaranja discovers your infrastructure by **reading** your source: your HTTP app\nand its routes, your `@Cron` / `cron()` jobs and their schedules, your\n`@Queue` / `queue()` consumers, and the env vars you wrap with `env()`. It does\nthis without executing your code, so planning a deploy is always safe — nothing\nof yours runs just to figure out what to deploy.\n\nThat's also why a few things must be written so laranja can see them: schedules\nuse literal builders like `rate(5, \"minutes\")`, and `env(\"…\")` takes a string\nliteral.\n\n## It deploys into your AWS account\n\nlaranja turns what it found into AWS resources — a Lambda for your app, an\nEventBridge rule per cron, an SQS queue per consumer — and deploys them with\n**your** credentials into **your** account. See\n[what gets deployed](../reference/what-gets-deployed.md) for the full mapping.\n\n- The AWS CDK toolkit is embedded, so there's nothing extra to install.\n- The first deploy to a new account/region runs a one-time **bootstrap**.\n- [`plan`](../reference/commands.md#plan) previews what a deploy would change\n (created/changed/unchanged); [`destroy`](../reference/commands.md#destroy)\n tears the stack down.\n- Outputs (your HTTPS URL, queue URLs) are printed when the deploy finishes.\n\n## The template is built on the server — your code stays local\n\n[`plan`](../reference/commands.md#plan) and [`deploy`](../reference/commands.md#deploy)\nsynthesize the deployment template on the **laranja server**. laranja scans and\nbundles your code locally, then sends only a **description** of your\ninfrastructure (the internal model plus the asset hashes of your bundles) — your\nsource code and bundles never leave your machine. The returned template is then\napplied to AWS with your **own** credentials. This is why these commands need a\n`LARANJA_API_KEY` and a `projectId` (run [`laranja init`](../reference/commands.md#init)\nonce to set both up).\n\n## Related\n\n- [What gets deployed](../reference/what-gets-deployed.md)\n- [Stages & environments](../guides/stages-and-environments.md)\n"},{"slug":"getting-started/installation","section":"getting-started","title":"Installation","description":"Prerequisites and how to add laranja to a project.","order":2,"body":"\n# Installation\n\n## Prerequisites\n\n- **Node.js 20 or newer.** Deployed Lambdas run on the Node.js 20 runtime, and\n the CLI targets the same.\n- **A laranja account + API key.** laranja synthesizes your deployment template on\n its server, so `plan`, `deploy`, and `eject` need an API key (created in the\n dashboard) and a project to link to. You connect both by running\n [`laranja init`](./quickstart.md#3-sign-in-and-configure) — see the Quickstart.\n- **An AWS account** plus credentials on the standard AWS chain — any of:\n - `aws configure` (a shared credentials file),\n - AWS SSO (`aws sso login`),\n - environment variables (`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN`),\n - a named profile (set [`profile`](../reference/config-file.md) in config).\n- **A region**, from `region` in your config or the `AWS_REGION` /\n `AWS_DEFAULT_REGION` environment variable.\n\nYou do **not** need to install the AWS CDK or the AWS CLI separately — the CDK\ntoolkit is embedded in laranja.\n\n## Install\n\nAdd the CLI as a dev dependency:\n\n```bash\nnpm install -D @alzulejos/laranja\n```\n\nIf you use decorators or function markers for jobs and queues, also install:\n\n```bash\nnpm install @alzulejos/laranja-decorators\n```\n\n> `@alzulejos/laranja-decorators` is a regular dependency (not dev-only) because your\n> application imports `@Cron`, `@Queue`, `rate`, etc. at runtime.\n\n## First-time AWS setup (bootstrap)\n\nThe first time you deploy to a given account + region, laranja runs a one-time\n**bootstrap** that creates a small set of shared resources in _your_ account (an\nS3 asset bucket and a few IAM roles). `laranja deploy` detects this and prompts\nyou before doing it — see [deploy](../reference/commands.md#deploy).\n\n## Verify\n\n```bash\nnpx laranja --help\n```\n\nYou're ready for the **[Quickstart](./quickstart.md)**.\n"},{"slug":"getting-started/introduction","section":"getting-started","title":"Introduction","description":"What laranja is, the problem it solves, and the principles behind it.","order":1,"body":"\n# Introduction\n\nlaranja is a **code-first deploy tool** for Node.js apps. You write your\napplication — an Express app, some scheduled jobs, some queue consumers — and\nlaranja deploys it to **your own AWS account**. There is no infrastructure\nproject to maintain, no YAML to write, and no cloud console to click through.\n\n> laranja supports **Express** and **NestJS** — and because your app is reduced to\n> a framework-neutral description internally, more frameworks can follow without\n> changing how you write your code.\n\n## The problem\n\nShipping a small Node service to AWS usually means choosing between:\n\n- **A platform** (managed hosting) — fast, but you don't own the infrastructure\n and you pay a markup to run in someone else's account.\n- **Infrastructure-as-code** (CDK / Terraform / CloudFormation) — you own\n everything, but now you maintain a second codebase that drifts from the app it\n describes.\n\nlaranja takes a third path: **your application code _is_ the source of truth for\nthe infrastructure.** A route is an HTTP endpoint. A `@Cron` method is a\nscheduled function. A `@Queue` method is an SQS consumer. laranja reads those\nfacts out of your code and provisions exactly what they imply.\n\n## How it feels\n\n```ts\n// src/app.ts — mark your HTTP app, code-first\nimport express from \"express\";\nimport { http } from \"@alzulejos/laranja-decorators\";\n\nconst app = express();\napp.get(\"/health\", (_req, res) => res.json({ ok: true }));\n\nexport default http(app);\n```\n\nJobs and queues come in two styles — plain **functions** or **classes** with\ndecorators. Use whichever you prefer:\n\n```ts tab=\"Function\"\n// src/jobs.ts\nimport { cron, rate } from \"@alzulejos/laranja-decorators\";\n\nexport async function refreshCache() { /* ... */ }\ncron(rate(5, \"minutes\"), refreshCache);\n```\n\n```ts tab=\"Class\"\n// src/jobs.ts\nimport { Cron, rate } from \"@alzulejos/laranja-decorators\";\n\nexport class Jobs {\n @Cron(rate(5, \"minutes\"))\n async refreshCache() { /* ... */ }\n}\n```\n\n```bash\nnpx laranja deploy\n```\n\nYou get a live HTTPS URL, an EventBridge schedule, and an SQS queue with a\nconsumer — all in your own account, named deterministically.\n\n## Principles\n\n- **Code is the source of truth.** Infrastructure is _derived_ from the app, not\n declared alongside it. There's nothing to keep in sync.\n- **Your account, your resources.** laranja deploys with your AWS credentials\n into your account. You own every resource and can inspect it in the console.\n- **Deterministic, no magic names.** Resources are named `‹app›-‹fn›-‹stage›` —\n predictable and greppable, with no random suffixes.\n- **Provider-neutral by design.** AWS is the first target, but laranja is built\n so other clouds can follow without changing your app code.\n- **An escape hatch when you need it.** Outgrow the abstraction? `laranja eject`\n hands you a real, owned CDK project (see [eject](../reference/commands.md#eject)).\n\n## What's in scope (v1)\n\n| Capability | How you declare it |\n|---|---|\n| HTTP API | An Express or NestJS app, marked with the [`http()`](../reference/decorators-and-markers.md#http) marker. |\n| Scheduled jobs | [`@Cron`](../reference/decorators-and-markers.md#cron) (class) or [`cron()`](../reference/decorators-and-markers.md#cron-marker) (function) |\n| Queue consumers | [`@Queue`](../reference/decorators-and-markers.md#queue) (class) or [`queue()`](../reference/decorators-and-markers.md#queue-marker) (function) |\n| Per-environment deploys | [Stages](../guides/stages-and-environments.md) (`--stage`) |\n| Env vars | [`env`](../guides/environment-variables.md) in config |\n\nNext: **[Installation](./installation.md)**.\n"},{"slug":"getting-started/quickstart","section":"getting-started","title":"Quickstart","description":"From an empty folder to a live HTTPS endpoint in a few minutes.","order":3,"body":"\n# Quickstart\n\nThis walks you from zero to a deployed app with an HTTP endpoint, a scheduled\njob, and a queue consumer.\n\n## 1. Create a project\n\n```bash\nmkdir my-api && cd my-api\nnpm init -y\nnpm install express\nnpm install -D @alzulejos/laranja typescript\nnpm install @alzulejos/laranja-decorators\n```\n\n> laranja supports **Express** and **NestJS**. For Nest, see the\n> [HTTP apps guide](../guides/http-apps.md#nestjs).\n\n## 2. Write your app\n\nMark your app with [`http()`](../reference/decorators-and-markers.md#http) and\nexport it. laranja finds it by reading your code — so there's nothing to wire up\nin config:\n\n```ts\n// src/app.ts\nimport express from \"express\";\nimport { http } from \"@alzulejos/laranja-decorators\";\n\nconst app = express();\napp.get(\"/\", (_req, res) => res.json({ ok: true, stage: process.env.STAGE }));\napp.get(\"/users/:id\", (req, res) => res.json({ id: req.params.id }));\n\nexport default http(app);\n```\n\nAdd a scheduled job and a queue consumer (optional). There are two styles — pick\nwhichever fits your codebase.\n\n```ts tab=\"Function\"\n// src/jobs.ts\nimport { cron, queue, rate } from \"@alzulejos/laranja-decorators\";\n\nexport async function refreshCache() {\n console.log(\"refreshing cache…\");\n}\ncron(rate(5, \"minutes\"), refreshCache);\n\nexport async function sendEmail(body: unknown) {\n console.log(\"sending\", body);\n}\nqueue({ name: \"emails\", batchSize: 10 }, sendEmail);\n```\n\n```ts tab=\"Class\"\n// src/jobs.ts\nimport { Cron, Queue, rate } from \"@alzulejos/laranja-decorators\";\n\nexport class Jobs {\n @Cron(rate(5, \"minutes\"))\n async refreshCache() {}\n\n @Queue({ name: \"emails\", batchSize: 10 })\n async sendEmail(body: unknown) {}\n}\n```\n\n## 3. Sign in and configure\n\nRun the scaffolder. It prompts for your **laranja API key** (from the dashboard),\nvalidates it, stores it in `~/.laranja/auth.json`, and lets you pick or create a\n**dashboard project** — filling in `name` and `projectId` for you:\n\n```bash\nnpx laranja init\n```\n\nThe generated `laranja.config.ts` looks like this (edit `region`, `env`, and\n`compute` to taste):\n\n```ts\n// laranja.config.ts\nimport type { LaranjaConfig } from \"@alzulejos/laranja-decorators\";\n\nconst config: LaranjaConfig = {\n name: \"my-api\",\n // From your laranja dashboard — identifies this project on the server.\n projectId: \"proj_…\",\n region: \"us-east-1\",\n env: { LOG_LEVEL: \"info\" },\n // Default compute for every function (the HTTP proxy + each cron/queue).\n compute: { memory: 256, timeout: 30 },\n};\n\nexport default config;\n```\n\nBecause the app is marked with `http()` in code, the config stays minimal — the\nHTTP app is declared there, not in config. See the\n**[config reference](../reference/config-file.md)**.\n\n## 4. Preview the plan\n\n`plan` shows what a deploy would do: it synthesizes your template on the server,\ndiffs it against what's deployed in your AWS account, and tags each resource\n**created / changed / unchanged**. It's **read-only** — nothing is applied, and it\nnever counts against your deploy limit.\n\n```bash\nnpx laranja plan\n```\n\n```\nPlan for \"my-api-dev\"\n\n+ http HTTP 2 routes → proxy Lambda + Function URL\n+ daily Cron Lambda + EventBridge rule\n+ emails Queue SQS + consumer Lambda\n\n8 AWS resources +8 created =0 unchanged\n```\n\nOn this first run nothing is deployed yet, so everything shows as `+` created.\n\n## 5. Deploy\n\n```bash\nnpx laranja deploy\n```\n\nThe first deploy to a new account/region prompts you to **bootstrap** (a\none-time setup in your account). When it finishes you'll see your live URL:\n\n```\n🌐 http https://abc123.lambda-url.us-east-1.on.aws/\n```\n\nHit it:\n\n```bash\ncurl https://abc123.lambda-url.us-east-1.on.aws/\n# {\"ok\":true,\"stage\":\"dev\"}\n```\n\n## 6. Iterate\n\n```bash\nnpx laranja logs # tail CloudWatch logs (pick a function)\nnpx laranja plan # see what a deploy would change\nnpx laranja deploy # ship again\nnpx laranja destroy # tear it all down\n```\n\n## Next steps\n\n- Ship to multiple environments: **[Stages & environments](../guides/stages-and-environments.md)**.\n- Understand what was created: **[What gets deployed](../reference/what-gets-deployed.md)**.\n- Go deeper on jobs and queues: **[Cron jobs](../guides/cron-jobs.md)**, **[Queues](../guides/queues.md)**.\n"},{"slug":"guides/cron-jobs","section":"guides","title":"Cron jobs","description":"Run functions on a schedule with @Cron or cron().","order":2,"body":"\n# Cron jobs\n\nA cron job is a function that runs on a schedule. Each one becomes\n[its own Lambda plus an EventBridge rule](../reference/what-gets-deployed.md#cron--lambda--eventbridge-rule).\n\n## Class style — `@Cron`\n\nDecorate a method with [`@Cron`](../reference/decorators-and-markers.md#cron) and\ngive it a [schedule](./schedules.md):\n\n```ts\nimport { Cron, rate, every } from \"@alzulejos/laranja-decorators\";\n\nexport class Jobs {\n @Cron(rate(5, \"minutes\"))\n async refreshCache() {\n // …\n }\n\n @Cron(every(\"day\"))\n async nightlyCleanup() {\n // …\n }\n\n @Cron({ schedule: \"cron(0 12 * * ? *)\", id: \"daily-report\" })\n async sendReport() {\n // …\n }\n}\n```\n\nThe handler's logical id defaults to `‹Class›-‹method›`; pass `id` to set a\nstable, explicit name (which also drives the Lambda's name).\n\n## Function style — `cron()`\n\nIf you don't use classes, register a standalone exported function with\n[`cron()`](../reference/decorators-and-markers.md#cron-marker):\n\n```ts\nimport { cron, rate } from \"@alzulejos/laranja-decorators\";\n\nexport async function refreshCache() {\n // …\n}\n\ncron(rate(5, \"minutes\"), refreshCache);\n```\n\nThe function's name becomes the resource id unless you pass an explicit `id`:\n\n```ts\ncron({ schedule: every(\"hour\"), id: \"hourly-sync\" }, refreshCache);\n```\n\n## NestJS\n\nIn a Nest app, `@Cron` goes on a normal provider — with injected dependencies —\nand you can keep the schedule syntax you already use (a\n[node-cron string or `CronExpression`](./schedules.md#node-cron-expressions-nestjsschedule-compatibility)).\nSwapping the import from `@nestjs/schedule` is usually the only change:\n\n```ts\n// tasks.service.ts\nimport { Injectable } from \"@nestjs/common\";\nimport { Cron, CronExpression } from \"@alzulejos/laranja-decorators\"; // ← was @nestjs/schedule\n\n@Injectable()\nexport class TasksService {\n constructor(private readonly reports: ReportsService) {} // real DI\n\n @Cron(CronExpression.EVERY_30_MINUTES)\n async sweep() {\n await this.reports.rebuild(); // `this.reports` is injected\n }\n}\n```\n\nBecause the method runs on a real provider, laranja resolves it through your\napp's dependency-injection container instead of `new`-ing the class. Point it at\nyour module **once** with the [`workers()`](../reference/decorators-and-markers.md#workers)\nmarker:\n\n```ts\n// src/main.ts (or a dedicated file)\nimport { workers } from \"@alzulejos/laranja-decorators\";\nimport { AppModule } from \"./app.module\";\n\nexport default workers(AppModule); // build a DI context from this module\n```\n\nPass `AppModule` for the whole graph, or a leaner module you compose if you want\na smaller cold start. Like the Nest [HTTP path](./http-apps.md#nestjs), laranja\npackages your **compiled** `dist/` output — run `nest build` before deploying so\nthe DI metadata exists.\n\n## Schedules\n\nSchedules are written with the portable `rate()` / `every()` builders, or as a\nraw expression string. See the **[Schedules reference](./schedules.md)** for the\nfull set of options.\n\n```ts\n@Cron(rate(30, \"minutes\")) // every 30 minutes\n@Cron(every(\"hour\")) // every hour (shorthand for rate(1, \"hour\"))\n@Cron({ schedule: \"cron(0 9 * * ? *)\" }) // raw AWS cron: 09:00 UTC daily\n```\n\n## Runtime behavior\n\n- Each cron runs in its **own Lambda**, isolated from your HTTP app and other\n jobs.\n- Memory and timeout come from [`compute`](../reference/config-file.md#compute)\n (default `{ memory: 256, timeout: 30 }`) and can be overridden per cron id in\n [`resources`](../reference/config-file.md#resources).\n- All [config `env`](./environment-variables.md) and `STAGE` are\n available via `process.env`.\n\n## Related\n\n- [Schedules](./schedules.md)\n- [`@Cron` / `cron()` reference](../reference/decorators-and-markers.md#cron)\n- [Queues](./queues.md)\n"},{"slug":"guides/environment-variables","section":"guides","title":"Environment variables","description":"Declare env vars in config or in code, and supply their values per stage.","order":5,"body":"\n# Environment variables\n\nEvery Lambda laranja deploys receives a set of environment variables, available\nthrough `process.env` as usual. There are two ways to declare them.\n\n## Static values in config\n\nPut plain, commit-safe values in the `env` map in your config. They're injected\ninto **every** function (HTTP proxy, cron, and queue consumers):\n\n```ts\n// laranja.config.ts\nconst config: LaranjaConfig = {\n name: \"my-api\",\n env: {\n LOG_LEVEL: \"info\",\n API_BASE_URL: \"https://api.example.com\",\n },\n};\n```\n\n```ts\n// anywhere in your app\nconst level = process.env.LOG_LEVEL; // \"info\"\n```\n\nUse this for non-sensitive configuration that's the same everywhere: log levels,\npublic URLs, feature flags.\n\n## Values from your environment — `env()`\n\nWhen a value should come from your shell or CI instead of your repo, wrap the\nvariable name with the `env()` helper where you read it:\n\n```ts\nimport { env } from \"@alzulejos/laranja-decorators\";\n\nconst dbUrl = env(\"DATABASE_URL\"); // same as process.env.DATABASE_URL at runtime\n```\n\nlaranja finds every `env(\"…\")` in your code and makes sure that variable is set\non **every** deployed function — no more filling them in by hand in the AWS\nconsole. At deploy time it reads each value from your own environment and sends\nit straight to the function; the value is never written into your repo.\n\nThe name must be a **string literal** — `env(\"DATABASE_URL\")`, not\n`env(someVariable)` — so laranja can discover it just by reading your code.\n\n### Supplying the values\n\nSet the variables in the shell or CI job you deploy from:\n\n```bash\nDATABASE_URL=postgres://… laranja deploy --stage prod\n```\n\n- **Missing a value?** By default laranja deploys anyway and warns you which ones\n were empty — a typo never blocks a deploy. Pass `--strict` to fail the deploy\n instead.\n- **Re-deploying without re-supplying a value?** The previously deployed value is\n kept, so you don't have to pass every variable on every deploy.\n\n## The `STAGE` variable\n\nlaranja always injects `STAGE`, set to the active [stage](./stages-and-environments.md)\n(`\"dev\"` by default, or whatever `--stage` resolved to). You don't declare it:\n\n```ts\napp.get(\"/\", (_req, res) => res.json({ stage: process.env.STAGE }));\n```\n\nIf you also define `STAGE` in `env`, your value wins.\n\n## Per-stage values\n\nBecause [`--stage`](./stages-and-environments.md) selects the\nenvironment at deploy time, the common pattern is one pipeline per stage, each\nsupplying its own values:\n\n- **Shared, non-sensitive defaults** → the `env` map in config.\n- **Per-environment values** → declare them with `env()` and provide them from\n each pipeline's environment.\n\n```bash\n# dev pipeline\nLOG_LEVEL=debug laranja deploy --stage dev\n# prod pipeline\nLOG_LEVEL=warn laranja deploy --stage prod\n```\n\n## Secrets\n\n`env()` keeps values out of your repo, but they still land in the Lambda's plain\nenvironment — readable by anyone with access to the function's configuration. For\ntrue secrets (API keys, DB passwords), that's not enough. First-class secrets\nsupport is on the roadmap; until then, read them at runtime from a secret store\n(e.g. AWS SSM Parameter Store / Secrets Manager) inside your handler.\n\n## Related\n\n- [Config file](../reference/config-file.md)\n- [Stages & environments](./stages-and-environments.md)\n"},{"slug":"guides/http-apps","section":"guides","title":"HTTP apps","description":"Deploy your HTTP app behind a public HTTPS endpoint.","order":1,"body":"\n# HTTP apps\n\nlaranja deploys your whole HTTP app as a single proxy Lambda behind a public\n[Function URL](../reference/what-gets-deployed.md#http-app--proxy-lambda--function-url).\nlaranja supports **Express** and **NestJS**.\n\n## Declaring your app (the `http()` marker)\n\nThe code-first way: mark your app with the\n[`http()`](../reference/decorators-and-markers.md#http) marker and export it.\nlaranja finds it by scanning your code — there's nothing to configure.\n\n```ts\n// src/app.ts\nimport express from \"express\";\nimport { http } from \"@alzulejos/laranja-decorators\";\n\nconst app = express();\napp.use(express.json());\n\napp.get(\"/\", (_req, res) => res.json({ ok: true }));\napp.post(\"/users\", (req, res) => res.status(201).json(req.body));\n\nexport default http(app); // or: export const api = http(app);\n```\n\n`http()` returns the app untouched — it's a static marker, not a wrapper, so it\nhas no runtime effect. That's all you need: every route you register is served by\nthe deployed proxy. The marker is the only way to declare an HTTP app — there's\nexactly one per project, and it must be exported so the scanner can find it.\n\n## NestJS\n\nNest apps work the same way, with one difference: a Nest app only exists after an\nasync `NestFactory.create(...)`, so instead of a ready app object you wrap your\n**bootstrap function** and have it `return` the app:\n\n```ts\n// src/main.ts\nimport { NestFactory } from \"@nestjs/core\";\nimport { http } from \"@alzulejos/laranja-decorators\";\nimport { AppModule } from \"./app.module\";\n\nexport async function bootstrap() {\n const app = await NestFactory.create(AppModule);\n // configure however you like — pipes, guards, middleware, raw body, cookies…\n app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));\n await app.listen(process.env.PORT ?? 3000); // fine to keep for local dev\n return app; // ← the only change laranja needs\n}\n\n// Run locally with `npm run start`; skipped when laranja imports this file.\nif (require.main === module) void bootstrap();\n\nexport default http(bootstrap); // wrap the factory, not a module\n```\n\nlaranja runs your `bootstrap()` verbatim, so every pipe, guard, and piece of\nmiddleware you configure is preserved — nothing is re-derived. You keep your\nnormal Nest project (`@nestjs/platform-express`); no laranja-specific\nrestructuring.\n\nTwo things to know:\n\n- **Build before you deploy.** laranja packages your compiled output (`nest build`\n → `dist/`), because Nest's dependency injection relies on the decorator metadata\n your own TypeScript build emits. laranja deploys what you build — it doesn't run\n your build for you — so run `nest build` yourself after every code change.\n Deploying without a `dist/` fails with a clear message, but a **stale** `dist/`\n (source edited since your last build) deploys silently as outdated code, so make\n the build part of your deploy step (e.g. `nest build && laranja deploy`).\n- **Use the default Express platform.** The Fastify adapter isn't supported yet.\n\n## Routing, middleware, and `STAGE`\n\nYour app runs as-is inside Lambda. Standard Express features work — routing,\nmiddleware, JSON bodies, route params. The active\n[stage](./stages-and-environments.md) is available as\n`process.env.STAGE`:\n\n```ts\napp.get(\"/whoami\", (_req, res) => res.json({ stage: process.env.STAGE }));\n```\n\n## CORS and auth\n\nThe Function URL is public with permissive CORS (all origins/methods/headers).\nHandle authentication and any stricter CORS rules **inside your app**, the same\nway you would anywhere else.\n\n## Compute (memory & timeout)\n\nThe HTTP proxy Lambda's memory and timeout come from\n[`compute`](../reference/config-file.md#compute) in your config — the scaffold\ndefault is `{ memory: 256, timeout: 30 }`, and you can override it under the `http`\nkey in [`resources`](../reference/config-file.md#resources). Long-running work\nbelongs in a [cron job](./cron-jobs.md) or behind a [queue](./queues.md), not a\nrequest.\n\n## Workers-only deployments\n\nIf your HTTP API is hosted elsewhere and you only want to deploy scheduled jobs\nand queue consumers, just don't add an `http()` marker — there's nothing to set\nin config:\n\n```ts\n// laranja.config.ts\nconst config: LaranjaConfig = {\n name: \"my-workers\",\n env: { LOG_LEVEL: \"info\" },\n};\n```\n\nWith no marker, only your [`@Cron`](./cron-jobs.md) / [`@Queue`](./queues.md)\nhandlers are deployed — no HTTP proxy, no Function URL.\n\nFor a **workers-only Nest** app, there's no `http(bootstrap)` to build the DI\ncontainer from, so declare your module with the\n[`workers()`](../reference/decorators-and-markers.md#workers) marker instead\n(`export default workers(AppModule)`) — see [Cron jobs → NestJS](./cron-jobs.md#nestjs).\n\n## Related\n\n- [What gets deployed](../reference/what-gets-deployed.md)\n- [Cron jobs](./cron-jobs.md) · [Queues](./queues.md)\n"},{"slug":"guides/queues","section":"guides","title":"Queues","description":"Process SQS messages with @Queue or queue(), including FIFO.","order":3,"body":"\n# Queues\n\nA queue consumer processes messages from an SQS queue. Each one becomes\n[an SQS queue plus a consumer Lambda](../reference/what-gets-deployed.md#queue--sqs-queue--consumer-lambda).\n\n## Class style — `@Queue`\n\nDecorate a method with [`@Queue`](../reference/decorators-and-markers.md#queue):\n\n```ts\nimport { Queue } from \"@alzulejos/laranja-decorators\";\n\nexport class Workers {\n @Queue({ name: \"emails\", batchSize: 10 })\n async sendEmail(body: unknown) {\n // `body` is the JSON-parsed message\n }\n}\n```\n\n## Function style — `queue()`\n\n```ts\nimport { queue } from \"@alzulejos/laranja-decorators\";\n\nexport async function sendEmail(body: unknown) {\n // …\n}\n\nqueue({ name: \"emails\", batchSize: 10 }, sendEmail);\n```\n\n## NestJS\n\n`@Queue` works on a Nest provider with injected dependencies. As with\n[cron jobs](./cron-jobs.md#nestjs), laranja resolves the consumer through your\nDI container, so declare your module once with the\n[`workers()`](../reference/decorators-and-markers.md#workers) marker\n(`export default workers(AppModule)`) and deploy your compiled `dist/` output.\nStandalone `queue()` functions don't need it.\n\n## Options\n\n| Option | Default | Description |\n|---|---|---|\n| `name` | _required_ | Queue name. A `.fifo` suffix marks a FIFO queue. |\n| `batchSize` | `10` | Max messages delivered to the consumer per invocation. |\n| `fifo` | `false` | Force a FIFO queue (or end `name` with `.fifo`). When set, laranja appends `.fifo` to `name` if you left it off. |\n\n## How messages are delivered\n\n- Your handler is invoked **once per message**, with the message **body already\n JSON-parsed**.\n- **Partial-batch failures** are enabled: if your handler throws for one message,\n only that message is retried — the rest of the batch is still acknowledged.\n- Consumer memory/timeout come from [`compute`](../reference/config-file.md#compute)\n (default `{ memory: 256, timeout: 30 }`); the queue's visibility timeout is\n derived to stay ≥ the consumer timeout (override it per queue via\n [`resources`](../reference/config-file.md#resources)).\n\n```ts\n@Queue({ name: \"orders\" })\nasync processOrder(body: unknown) {\n const order = body as { id: string };\n if (!order.id) throw new Error(\"bad message\"); // only THIS message is retried\n // …\n}\n```\n\n## FIFO queues\n\nEnd the name with `.fifo` (or set `fifo: true`) for ordered, exactly-once\nprocessing. Content-based deduplication is enabled automatically:\n\n```ts\n@Queue({ name: \"orders.fifo\", fifo: true })\nasync processOrder(body: unknown) {\n // …\n}\n```\n\nAWS requires FIFO queue names to end in `.fifo`. If you set `fifo: true` but\nleave the suffix off, laranja appends it for you — so `{ name: \"orders\", fifo: true }`\ndeploys a queue named `orders.fifo`. The normalized name is what appears in\n`laranja plan` and in the AWS console, so there's no surprise at deploy time.\n\n## Sending messages\n\nConsuming is only half the loop — to **produce** a message, call\n[`getQueue`](../reference/decorators-and-markers.md#getqueue) with the queue's\n`name` and `.send()` a payload:\n\n```ts\nimport { getQueue } from \"@alzulejos/laranja-decorators\";\n\napp.post(\"/signup\", async (req, res) => {\n await getQueue(\"emails\").send({ to: req.body.email, template: \"welcome\" });\n res.sendStatus(202);\n});\n```\n\nObjects are JSON-serialized for you (strings are sent as-is), so the consumer\nreceives them already parsed — `getQueue(\"emails\").send({ to })` on one end,\n`async sendEmail(body)` on the other.\n\nYou can produce from **anywhere** in a deployed app — an HTTP route, a\n[cron job](./cron-jobs.md), or another queue's consumer fanning out. laranja\ninjects each queue's URL into every function's environment at deploy and grants\n`sqs:SendMessage`, so there's no client to configure, no URL to look up, and no\nIAM to wire. It's a thin wrapper over one SQS `SendMessage` call — laranja\nprovisions the infrastructure; it deliberately does **not** add a job framework\n(retries, scheduling, and job state stay with SQS and your consumer).\n\n### FIFO and options\n\n`.send()` takes a second options argument:\n\n| Option | Applies to | Description |\n|---|---|---|\n| `groupId` | FIFO (**required**) | `MessageGroupId` — messages with the same group are ordered. |\n| `dedupId` | FIFO | `MessageDeduplicationId` — only needed when content-based dedup is off. |\n| `delaySeconds` | Standard | Delay (0–900s) before the message becomes visible. Ignored by FIFO. |\n\n```ts\n// FIFO queues require a groupId — the send throws without one.\nawait getQueue(\"orders.fifo\").send(order, { groupId: order.customerId });\n```\n\n> Prefer the raw SDK? The queue URL is also emitted as a stack output after\n> deploy and visible in the AWS console — send with `@aws-sdk/client-sqs`\n> directly if you'd rather.\n\n## Related\n\n- [`@Queue` / `queue()` reference](../reference/decorators-and-markers.md#queue)\n- [What gets deployed](../reference/what-gets-deployed.md#queue--sqs-queue--consumer-lambda)\n- [Cron jobs](./cron-jobs.md)\n"},{"slug":"guides/schedules","section":"guides","title":"Schedules","description":"The rate() and every() builders, and raw schedule expressions.","order":4,"body":"\n# Schedules\n\nSchedules drive [cron jobs](./cron-jobs.md). laranja stores them in a\n**provider-neutral** form, so prefer the builders — they're portable across\nclouds. A raw expression string is available as an escape hatch.\n\n## `rate(value, unit)`\n\n\"Every N units.\" Portable everywhere.\n\n```ts\nimport { rate } from \"@alzulejos/laranja-decorators\";\n\nrate(5, \"minutes\") // every 5 minutes\nrate(1, \"hour\") // every hour\nrate(2, \"days\") // every 2 days\n```\n\n- `value` must be a **positive integer** (≥ 1).\n- `unit` is one of `\"minute\"`, `\"minutes\"`, `\"hour\"`, `\"hours\"`, `\"day\"`,\n `\"days\"` — singular or plural, your choice.\n\n## `every(unit)`\n\nShorthand for `rate(1, unit)`. Takes a singular unit:\n\n```ts\nimport { every } from \"@alzulejos/laranja-decorators\";\n\nevery(\"minute\") // = rate(1, \"minute\")\nevery(\"hour\") // = rate(1, \"hour\")\nevery(\"day\") // = rate(1, \"day\")\n```\n\n## Raw AWS expressions (escape hatch)\n\nWhen you need something the builders can't express (e.g. \"noon UTC every day\"),\npass a **wrapped** AWS schedule string — `cron(...)` or `rate(...)`:\n\n```ts\n@Cron({ schedule: \"cron(0 12 * * ? *)\", id: \"daily-report\" }) // 12:00 UTC daily\n@Cron(\"rate(5 minutes)\") // raw rate string\n```\n\nAWS cron has **six fields**: `cron(Minutes Hours Day-of-month Month Day-of-week\nYear)`.\n\nA few examples:\n\n| Expression | Meaning |\n|---|---|\n| `cron(0 12 * * ? *)` | 12:00 UTC every day |\n| `cron(0/15 * * * ? *)` | every 15 minutes |\n| `cron(0 8 ? * MON-FRI *)` | 08:00 UTC on weekdays |\n| `cron(0 0 1 * ? *)` | midnight UTC on the 1st of each month |\n\n## node-cron expressions (`@nestjs/schedule` compatibility)\n\nA **bare** (unwrapped) string is read as a standard 5- or 6-field\n[node-cron](https://github.com/kelektiv/node-cron) expression — the same syntax\n`@nestjs/schedule`'s `@Cron` takes. laranja translates it to the AWS dialect for\nyou, so a Nest app can swap the import and keep its existing schedules:\n\n```ts\n@Cron(\"0 12 * * *\") // noon every day\n@Cron(\"*/5 * * * *\") // every 5 minutes\n@Cron(\"0 0 * * 1-5\") // midnight on weekdays\n@Cron(CronExpression.EVERY_30_MINUTES) // the enum works too\n```\n\n`CronExpression` (mirrored from `@nestjs/schedule`) is re-exported from\n`@alzulejos/laranja-decorators`. Translation handles the day-of-week numbering difference\n(Unix `0`=Sun → AWS `1`=Sun) and the day-of-month/day-of-week rule for you.\n\n**What can't be translated is rejected at build time — never silently rounded:**\n\n| Input | Why it's rejected |\n|---|---|\n| `\"*/30 * * * * *\"`, `CronExpression.EVERY_30_SECONDS` | Sub-minute — EventBridge's floor is **1 minute**. |\n| A seconds field other than `0` | Second-level offsets can't be expressed. |\n| A cron constraining **both** day-of-month and day-of-week | EventBridge requires one to be `*`. |\n\n## `@Interval(ms)`\n\n`@nestjs/schedule`'s `@Interval` is supported and lowers to a `rate(...)`. The\ninterval must be a whole number of minutes (EventBridge's floor):\n\n```ts\n@Interval(300000) // every 5 minutes → rate(5, \"minutes\")\n@Interval(\"poll\", 300000) // named\n```\n\n> `@Timeout` (a one-shot timer relative to process start) has no serverless\n> equivalent and is rejected with a clear message — use `@Cron` or `@Interval`.\n\n## Where you can use them\n\nAnywhere a schedule is expected — the decorator or the function marker — accepts\na builder result, a `Schedule` object, or a raw string:\n\n```ts\n@Cron(rate(5, \"minutes\")) // builder\n@Cron(\"rate(5 minutes)\") // raw string\n@Cron({ schedule: every(\"day\"), id: \"nightly\" })// builder + explicit id\ncron(rate(1, \"hour\"), refreshCache); // function marker\n```\n\n## Related\n\n- [Cron jobs](./cron-jobs.md)\n- [`@Cron` / `cron()` reference](../reference/decorators-and-markers.md#cron)\n"},{"slug":"guides/stages-and-environments","section":"guides","title":"Stages & environments","description":"Run dev, staging, and prod from one codebase with --stage.","order":6,"body":"\n# Stages & environments\n\nA **stage** is a named environment — `dev`, `staging`, `prod`, or anything you\nlike. laranja makes each stage a fully independent deployment from the same\ncodebase.\n\n## Setting the stage\n\nThe default stage is `dev`. Set it in config:\n\n```ts\n// laranja.config.ts\nconst config: LaranjaConfig = { name: \"my-api\", stage: \"dev\" };\n```\n\n…or override it per command with `--stage` (alias `-s`):\n\n```bash\nlaranja deploy --stage prod\nlaranja deploy -s staging\n```\n\nThe flag wins over the config value, which is why the recommended setup keeps\n`stage` at its default in config and lets each pipeline pass `--stage`.\n\n`--stage` applies to every environment-aware command: `deploy`, `plan`,\n`destroy`, `logs`, and `eject`.\n\n## Each stage is its own stack\n\nThe stage is part of the **stack name** (`‹name›-‹stage›`) and every resource\nname (`‹name›-‹fn›-‹stage›`). So `--stage dev` and `--stage prod` produce two\n**independent CloudFormation stacks** that never collide — even in the same AWS\naccount.\n\n```\nmy-api-dev ← laranja deploy --stage dev\nmy-api-prod ← laranja deploy --stage prod\n```\n\n## Two ways to isolate environments\n\nBoth work, and they compose:\n\n1. **One account, multiple stages.** The stage suffix keeps the stacks separate.\n Good for small projects or non-prod environments.\n2. **Separate accounts per stage.** Point each pipeline at different AWS\n credentials (a dev account and a prod account). Here your **AWS credentials\n are the real boundary**; the stack name can even repeat across accounts\n without conflict.\n\n## One pipeline per stage\n\nThe canonical CI/CD setup is one pipeline per environment, each running the same\ncommand with a different flag:\n\n```yaml\n# dev pipeline → laranja deploy --stage dev\n# staging pipeline → laranja deploy --stage staging\n# prod pipeline → laranja deploy --stage prod\n```\n\nSame repo, same command — only the flag differs. No per-environment config files\nto keep in sync. Pair this with [per-stage env values](./environment-variables.md#per-stage-values)\nto supply each environment's configuration.\n\n> Consistency matters: a pipeline's `destroy`, `logs`, and `plan` must use the\n> **same `--stage`** as its `deploy`, or they'll target a different stack.\n\n## The `STAGE` env var\n\nThe active stage is injected into every Lambda as `process.env.STAGE`, so your\ncode can branch on it:\n\n```ts\nconst isProd = process.env.STAGE === \"prod\";\n```\n\n## Related\n\n- [Config file](../reference/config-file.md)\n- [Environment variables](./environment-variables.md)\n"},{"slug":"reference/commands","section":"reference","title":"CLI commands","description":"Every laranja command and flag.","order":1,"body":"\n# CLI commands\n\n```\nlaranja <command> [project-dir] [flags]\n```\n\n`project-dir` defaults to the current directory, so most of the time you just run\n`laranja deploy`. Run `laranja --help` for a summary.\n\n> **Most commands need your account.** `plan`, `deploy`, and `eject` build your\n> template on the laranja server, so they need a `LARANJA_API_KEY` and a\n> `projectId` in your config. Run [`laranja init`](#init) once to sign in and link\n> a project; the key is stored in `~/.laranja/auth.json` so you don't re-export\n> it. Your **source code never leaves your machine** — only a description of your\n> infrastructure does\n> (see [how it works](../getting-started/how-it-works.md)).\n\n## Global flags\n\n| Flag | Applies to | Description |\n|---|---|---|\n| `--stage`, `-s <name>` | deploy, plan, destroy, logs, eject | Target [stage](../guides/stages-and-environments.md); overrides `config.stage`. |\n| `--verbose`, `-v` | deploy | Stream full CDK/CloudFormation output instead of the compact UI. |\n| `--strict` | deploy | Fail if any [`env()`](../guides/environment-variables.md#values-from-your-environment--env) value is unset (default: warn). |\n\n---\n\n## `init`\n\nSign in and scaffold a `laranja.config.ts` in the project directory.\n\n```bash\nlaranja init\n```\n\n`init` prompts for your **laranja API key** (from the dashboard) and validates it\nagainst the server before writing anything, then stores it in\n`~/.laranja/auth.json` so later commands don't need it re-exported. It then lets\nyou **pick or create a dashboard project** and fills the scaffolded config's\n`name` and `projectId` for you. Edit the file afterwards to set your `region`,\n`env`, and `compute`. See the [config reference](./config-file.md).\n\n---\n\n## `logout`\n\nRemove the stored API key (`~/.laranja/auth.json`).\n\n```bash\nlaranja logout\n```\n\nAfter this, commands that talk to the server (`init`, `plan`, `deploy`, `eject`)\nneed `LARANJA_API_KEY` in the environment again, or another `laranja init`.\n\n---\n\n## `plan`\n\nPreview what a deploy would do — laranja synthesizes your template on the server,\ndiffs it against the stack **currently deployed** in your AWS account, and prints\nyour app's resources tagged **created / changed / unchanged**. Nothing is applied.\n\n```bash\nlaranja plan\nlaranja plan --stage prod\n```\n\n```\nPlan for \"my-api-dev\"\n\n= http HTTP 2 routes → proxy Lambda + Function URL\n+ daily Cron Lambda + EventBridge rule\n~ emails Queue SQS + consumer Lambda\n\n8 AWS resources +3 created ~2 changed =3 unchanged\n```\n\n`+` is new, `~` changed, `=` unchanged. The bottom line tallies the underlying\nAWS resources.\n\n`plan` needs `LARANJA_API_KEY` (run [`laranja init`](#init) first) to synthesize,\nand a working **AWS credential chain** to read your live stack. It is\n**read-only** — it never creates a deployment or counts against your deploy limit.\n\n| Flag | Description |\n|---|---|\n| `--stage`, `-s` | Target stage. |\n\n---\n\n## `deploy`\n\nDeploy into your AWS account using your **local** AWS credentials. The template is\nsynthesized on the laranja server first, then applied with your own credentials —\nso deploy needs both `LARANJA_API_KEY` (run [`laranja init`](#init) first) and a\nworking AWS credential chain.\n\n```bash\nlaranja deploy\nlaranja deploy --stage prod\nlaranja deploy --verbose\n```\n\n- The first deploy to a new account/region prompts to **bootstrap** (a one-time\n setup in your account).\n- On success it prints your outputs — the HTTPS URL, queue URLs, and the cron\n jobs deployed.\n\n| Flag | Description |\n|---|---|\n| `--stage`, `-s` | Target stage. |\n| `--verbose`, `-v` | Stream full CDK output. |\n| `--strict` | Fail the deploy if any [`env()`](../guides/environment-variables.md#values-from-your-environment--env) value is unset. By default these are deployed with a warning. |\n\n---\n\n## `destroy`\n\nTear down the deployed stack and all its resources. Prompts for confirmation.\n\n```bash\nlaranja destroy\nlaranja destroy --stage prod\n```\n\n> Targets the stack for the resolved stage — make sure `--stage` matches the\n> environment you intend to remove.\n\n| Flag | Description |\n|---|---|\n| `--stage`, `-s` | Target stage. |\n\n---\n\n## `logs`\n\nTail CloudWatch logs for your deployed functions. The live stack is the source of\ntruth — no local state needed.\n\n```bash\nlaranja logs # interactive picker (TTY)\nlaranja logs sendEmail # tail a specific function by name\nlaranja logs --all # tail every function, multiplexed\nlaranja logs --no-follow # print recent history and exit\nlaranja logs --since 30m # history look-back window\nlaranja logs --stage prod # functions for the prod stack\n```\n\n| Flag / arg | Description |\n|---|---|\n| `<name>` (positional) | Function to tail (matched against its short label or full name). |\n| `--all` | Tail every function in the stack, multiplexed. |\n| `--no-follow` | Print the recent history window and exit (no live tail). |\n| `--since <dur>` | History look-back, e.g. `30s`, `15m`, `1h`, `2d` (default `1h`). |\n| `--stage`, `-s` | Target stage. |\n\nBoth a directory and a function name can be passed as positionals —\n`laranja logs ./app sendEmail` works.\n\n---\n\n## `eject`\n\nGenerate a standalone, owned **CDK project** from your app and stop — for when\nyou've outgrown the abstraction and want full control. **Paid feature.**\n\n```bash\nlaranja eject\nlaranja eject --force # overwrite an existing ./infra\nlaranja eject --stage prod\n```\n\nThe CDK project is generated **on the laranja server** (which gates the paid\nentitlement) and written to `./infra` — a complete project you own and run\nyourself (`cd infra && npm install && npm run deploy`). Requires `LARANJA_API_KEY`\nand a `projectId`; if your account can't eject, the server returns a clear error.\n\n| Flag | Description |\n|---|---|\n| `--force` | Overwrite an existing `infra/` directory. |\n| `--stage`, `-s` | Target stage (baked into the generated project). |\n\n## Related\n\n- [Stages & environments](../guides/stages-and-environments.md)\n- [How it works](../getting-started/how-it-works.md)\n"},{"slug":"reference/config-file","section":"reference","title":"Config file","description":"Every field in laranja.config.ts, with defaults and behavior.","order":2,"body":"\n# `laranja.config.ts`\n\nEvery project has a `laranja.config.ts` at its root that `export default`s a\nconfig object. It's a TypeScript module (loaded via `tsx`), so you get full type\nchecking and can compute values if you need to.\n\n```ts\nimport type { LaranjaConfig } from \"@alzulejos/laranja-decorators\";\n\nconst config: LaranjaConfig = {\n name: \"my-api\",\n // From your laranja dashboard — identifies this project on the server.\n projectId: \"proj_…\",\n region: \"us-east-1\",\n env: { LOG_LEVEL: \"info\" },\n // Default compute for every function (the HTTP proxy + each cron/queue).\n compute: { memory: 256, timeout: 30 },\n};\n\nexport default config;\n```\n\nThe config stays minimal because the HTTP app is declared in code with the\n[`http()`](./decorators-and-markers.md#http) marker — there's no config field for\nit. For a deploy with no HTTP app, just omit the marker (see\n[workers-only deployments](../guides/http-apps.md#workers-only-deployments)). Run\n[`laranja init`](./commands.md#init) to scaffold this file — it fills in\n`name` and `projectId` from the dashboard project you pick.\n\n## Fields\n\n| Field | Required | Default | Description |\n|---|---|---|---|\n| `name` | ✅ | — | App name. Used for the CloudFormation stack and all resource names. |\n| `region` | | `AWS_REGION` / `AWS_DEFAULT_REGION` | AWS region to deploy to. |\n| `stage` | | `\"dev\"` | Deployment stage. Part of the stack + resource names and injected as the `STAGE` env var. Override per-run with [`--stage`](../guides/stages-and-environments.md). |\n| `profile` | | — | AWS named profile to deploy with. |\n| `framework` | | _auto-detected_ | Override framework detection (e.g. `\"express\"`). |\n| `env` | | `{}` | Plain environment variables injected into every Lambda. See [Environment variables](../guides/environment-variables.md). |\n| `compute` | | `{ memory: 256, timeout: 30 }` | Default memory (MB) and timeout (s) for **every** function. See [compute](#compute). |\n| `resources` | | `{}` | Per-resource overrides keyed by resource id (`http`, or a cron/queue id). See [resources](#resources). |\n| `projectId` | ✅ | — | Project id from the laranja dashboard. Required by the server-side build (`plan`/`deploy`/`eject`); `laranja init` fills it in. |\n| `provider` | | `\"aws\"` | Target cloud. Only `\"aws\"` is implemented today. |\n\n### `name`\n\nDrives the stack name (`‹name›-‹stage›`) and every resource name\n(`‹name›-‹fn›-‹stage›`). Choose something short and stable — renaming it after a\ndeploy creates a _new_ stack rather than renaming the old one.\n\n### `region` and `profile`\n\n`region` falls back to `AWS_REGION`, then `AWS_DEFAULT_REGION`. If none is set,\nthe CLI errors with a clear message. `profile` selects a named profile from your\nAWS credentials file; otherwise the default credential chain is used.\n\n### `stage`\n\nThe default is `\"dev\"`. It's part of resource names and is injected into every\nLambda as `process.env.STAGE`. The [`--stage`](../guides/stages-and-environments.md)\nflag overrides it per command — the recommended way to drive multiple\nenvironments from one config.\n\n### `compute`\n\nThe default **memory** (MB) and **timeout** (seconds) applied to every function —\nthe HTTP proxy and each cron/queue consumer. The scaffold sets\n`{ memory: 256, timeout: 30 }`:\n\n```ts\nconst config: LaranjaConfig = {\n name: \"my-api\",\n projectId: \"proj_…\",\n compute: { memory: 512, timeout: 20 },\n};\n```\n\n### `resources`\n\nPer-resource overrides, keyed by **resource id** — `http` for the proxy, or the\n[`id`](./decorators-and-markers.md#cron) of a cron/queue. Each entry\nmerges field-by-field on top of `compute`, and queue/cron entries also accept\ntheir kind-specific knobs (e.g. a queue's `visibilityTimeout`). An unknown id is\na hard error, so a typo can't silently no-op:\n\n```ts\nconst config: LaranjaConfig = {\n name: \"my-api\",\n projectId: \"proj_…\",\n compute: { memory: 256, timeout: 30 },\n resources: {\n http: { memory: 512 }, // beefier proxy\n cleanup: { timeout: 60 }, // a slow cron by its id\n emails: { visibilityTimeout: 180 }, // a queue by its name/id\n },\n};\n```\n\n### Workers-only deployments\n\nTo deploy only your workers (`@Cron` / `@Queue`) with no HTTP app — for teams\nwhose API is hosted elsewhere — simply don't add an `http()` marker. No flag is\nneeded. See [workers-only deployments](../guides/http-apps.md#workers-only-deployments).\n\n## Related\n\n- [Environment variables](../guides/environment-variables.md)\n- [Stages & environments](../guides/stages-and-environments.md)\n"},{"slug":"reference/decorators-and-markers","section":"reference","title":"Decorators & markers","description":"API reference for @Cron, @Queue, cron, queue, getQueue, and http.","order":3,"body":"\n# Decorators & markers\n\nAll of these are imported from `@alzulejos/laranja-decorators`. They are **static markers**\n— the [scanner](../getting-started/how-it-works.md#1-scan) reads them at build time to\nshape your infrastructure. At runtime they are near-no-ops (they don't wrap or\nintercept your functions), so they're safe to leave in place.\n\n```bash\nnpm install @alzulejos/laranja-decorators\n```\n\nThe schedule builders [`rate`](../guides/schedules.md#ratevalue-unit) and\n[`every`](../guides/schedules.md#everyunit) are re-exported here too, so you can\nimport them alongside `@Cron`. For `@nestjs/schedule` compatibility, the\n`CronExpression` enum, `@Interval`, and `@Timeout` are re-exported as well — so a\nNest app can repoint its import at `@alzulejos/laranja-decorators` unchanged.\n\n---\n\n## `@Cron`\n\nSchedules a class method. Each `@Cron` becomes [its own Lambda + EventBridge\nrule](./what-gets-deployed.md#cron--lambda--eventbridge-rule).\n\n```ts\nfunction Cron(schedule: ScheduleInput): MethodDecorator\nfunction Cron(options: CronOptions): MethodDecorator\nfunction Cron(expression: string, options?: NestCronOptions): MethodDecorator // @nestjs/schedule form\n```\n\n```ts\nimport { Cron, rate, CronExpression } from \"@alzulejos/laranja-decorators\";\n\nexport class Jobs {\n @Cron(rate(5, \"minutes\"))\n async refreshCache() {}\n\n @Cron({ schedule: \"cron(0 12 * * ? *)\", id: \"daily-report\" })\n async report() {}\n\n // @nestjs/schedule style — a node-cron string or CronExpression, translated for you\n @Cron(\"0 3 * * *\", { name: \"nightly\", timeZone: \"Europe/Lisbon\" })\n async nightly() {}\n\n @Cron(CronExpression.EVERY_30_MINUTES)\n async sweep() {}\n}\n```\n\n**`CronOptions`** (laranja form)\n\n| Field | Type | Description |\n|---|---|---|\n| `schedule` | `ScheduleInput` | A [`rate()`/`every()`](../guides/schedules.md) result, a `Schedule`, or a raw string. |\n| `id` | `string` _(optional)_ | Stable logical id. Defaults to `‹Class›-‹method›`; also drives the Lambda name. |\n\n**`NestCronOptions`** (the second argument in the `@nestjs/schedule` form)\n\n| Field | Type | Description |\n|---|---|---|\n| `name` | `string` _(optional)_ | Used as the resource `id`. |\n| `timeZone` | `string` _(optional)_ | IANA timezone the schedule is evaluated in. |\n\nSee [Schedules → node-cron expressions](../guides/schedules.md#node-cron-expressions-nestjsschedule-compatibility)\nfor the accepted syntax and what's rejected. Nest providers resolve through DI —\ndeclare your module with [`workers()`](#workers).\n\n---\n\n## `cron()` marker\n\nFunction-style counterpart to `@Cron`, for codebases that don't use classes.\nRegisters a standalone exported function on a schedule.\n\n```ts\nfunction cron(schedule: ScheduleInput, handler: JobHandler): void\nfunction cron(options: CronOptions, handler: JobHandler): void\n```\n\n```ts\nimport { cron, rate } from \"@alzulejos/laranja-decorators\";\n\nexport async function refreshCache() {}\n\ncron(rate(5, \"minutes\"), refreshCache);\ncron({ schedule: rate(1, \"hour\"), id: \"hourly-sync\" }, refreshCache);\n```\n\nThe function's name becomes the resource id unless you pass an explicit `id`.\n\n---\n\n## `@Interval`\n\n`@nestjs/schedule`-compatible. Runs a method every _N_ milliseconds; laranja\nlowers it to a `rate(...)`, so the interval must be a whole number of minutes\n(EventBridge's 1-minute floor).\n\n```ts\nfunction Interval(milliseconds: number): MethodDecorator\nfunction Interval(name: string, milliseconds: number): MethodDecorator\n```\n\n```ts\nimport { Interval } from \"@alzulejos/laranja-decorators\";\n\nexport class Jobs {\n @Interval(300000) // every 5 minutes\n async poll() {}\n}\n```\n\n---\n\n## `@Timeout`\n\nRe-exported for `@nestjs/schedule` source compatibility, but a one-shot timer\nrelative to process start has no serverless equivalent — laranja **rejects it at\nbuild time** with a clear message. Use [`@Cron`](#cron) or [`@Interval`](#interval)\ninstead.\n\n---\n\n## `@Queue`\n\nConsumes messages from an SQS queue. Each `@Queue` becomes [an SQS queue +\nconsumer Lambda](./what-gets-deployed.md#queue--sqs-queue--consumer-lambda).\nThe handler is called once per message with the JSON-parsed body.\n\n```ts\nfunction Queue(options: QueueOptions): MethodDecorator\n```\n\n```ts\nimport { Queue } from \"@alzulejos/laranja-decorators\";\n\nexport class Workers {\n @Queue({ name: \"emails\", batchSize: 10 })\n async sendEmail(body: unknown) {}\n\n @Queue({ name: \"orders.fifo\", fifo: true })\n async processOrder(body: unknown) {}\n}\n```\n\n**`QueueOptions`**\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `name` | `string` | _required_ | Queue name. A `.fifo` suffix marks a FIFO queue. |\n| `batchSize` | `number` | `10` | Max messages per consumer invocation. |\n| `fifo` | `boolean` | `false` | Force a FIFO queue (or end `name` with `.fifo`). When set, laranja appends `.fifo` to `name` if you left it off. |\n\n---\n\n## `queue()` marker\n\nFunction-style counterpart to `@Queue`.\n\n```ts\nfunction queue(options: QueueOptions, handler: JobHandler): void\n```\n\n```ts\nimport { queue } from \"@alzulejos/laranja-decorators\";\n\nexport async function sendEmail(body: unknown) {}\n\nqueue({ name: \"emails\", batchSize: 10 }, sendEmail);\n```\n\n---\n\n## `getQueue()`\n\nThe queue **producer** — get a handle to a declared queue and `.send()` messages\nto it. The counterpart to the [`@Queue`](#queue) / [`queue()`](#queue-marker)\nconsumers. Unlike the markers, this does real work at runtime (a single SQS\n`SendMessage`); laranja injects the queue URL and grants `sqs:SendMessage` to\nevery function, so there's nothing to configure.\n\n```ts\nfunction getQueue(name: string): LaranjaQueue\n\ninterface LaranjaQueue {\n readonly url: string;\n send(payload: unknown, options?: SendOptions): Promise<{ messageId?: string }>;\n}\n```\n\n```ts\nimport { getQueue } from \"@alzulejos/laranja-decorators\";\n\nawait getQueue(\"emails\").send({ to, subject });\nawait getQueue(\"orders.fifo\").send(order, { groupId: order.customerId });\n```\n\n`payload` is JSON-serialized (strings are sent as-is). `name` is the queue's\ndeclared `name`; a send to an undeclared queue throws.\n\n**`SendOptions`**\n\n| Field | Type | Applies to | Description |\n|---|---|---|---|\n| `groupId` | `string` | FIFO (**required**) | `MessageGroupId` — orders messages within a group. A FIFO send throws without it. |\n| `dedupId` | `string` | FIFO | `MessageDeduplicationId` — only needed when content-based dedup is off. |\n| `delaySeconds` | `number` | Standard | Delay (0–900s) before the message becomes visible. Ignored by FIFO. |\n\nSee [Queues → Sending messages](../guides/queues.md#sending-messages).\n\n---\n\n## `http()`\n\nMarks the HTTP app (the proxy target) in code — the only way to declare one.\nExport the result so the scanner can find it.\n\n```ts\nfunction http<T>(app: T): T\n```\n\n```ts\nimport express from \"express\";\nimport { http } from \"@alzulejos/laranja-decorators\";\n\nconst app = express();\nexport default http(app); // or: export const api = http(app);\n```\n\nIt returns its argument untouched — purely a static marker. Omit it for a\n[workers-only](../guides/http-apps.md#workers-only-deployments) deployment.\n\nFor **NestJS**, wrap your async `bootstrap` factory (which `return`s the app)\ninstead of an app instance — see [HTTP apps → NestJS](../guides/http-apps.md#nestjs):\n\n```ts\nexport default http(bootstrap); // bootstrap: () => Promise<INestApplication>\n```\n\n---\n\n## `workers()`\n\n**NestJS only.** Declares the module laranja builds a dependency-injection\ncontext from, so class-based [`@Cron`](#cron) / [`@Queue`](#queue) providers\nresolve their injected dependencies at runtime (via\n`NestFactory.createApplicationContext`) instead of a bare `new`. The DI\ncounterpart to [`http()`](#http); export it so the scanner can find it.\n\n```ts\nfunction workers<T>(module: T): T\n```\n\n```ts\nimport { workers } from \"@alzulejos/laranja-decorators\";\nimport { AppModule } from \"./app.module\";\n\nexport default workers(AppModule); // or: export const jobs = workers(AppModule);\n```\n\nPass `AppModule` for the whole graph, or a leaner module for a smaller cold\nstart. There's exactly one per project. Required when a Nest project has\nclass-based workers; standalone [`cron()`](#cron-marker)/[`queue()`](#queue-marker)\nfunctions don't need it (no DI). Returns its argument untouched — a static marker.\n\n---\n\n## `env()`\n\nDeclares an environment variable your code needs. At runtime it just returns\n`process.env[name]`; laranja discovers each call and populates that variable on\nevery deployed function, with the value supplied from your shell or CI at deploy\ntime.\n\n```ts\nfunction env(name: string): string | undefined\n```\n\n```ts\nimport { env } from \"@alzulejos/laranja-decorators\";\n\nconst dbUrl = env(\"DATABASE_URL\");\n```\n\nThe name must be a **string literal** so it can be found statically. See\n[environment variables](../guides/environment-variables.md#values-from-your-environment--env)\nfor supplying values, the `--strict` flag, and per-stage usage.\n\n---\n\n## Types\n\n| Type | Description |\n|---|---|\n| `ScheduleInput` | `Schedule \\| string` — anything accepted where a schedule is expected. |\n| `Schedule` | Provider-neutral schedule: `{ kind: \"rate\", value, unit }` or `{ kind: \"cron\", expression, dialect }`. |\n| `RateUnit` | `\"minute\" \\| \"minutes\" \\| \"hour\" \\| \"hours\" \\| \"day\" \\| \"days\"`. |\n| `CronExpression` | Enum of common cron strings, mirrored from `@nestjs/schedule`. |\n| `JobHandler` | `(...args) => unknown \\| Promise<unknown>` — a `cron()`/`queue()` handler. |\n\n## Related\n\n- [Cron jobs](../guides/cron-jobs.md) · [Queues](../guides/queues.md) · [Schedules](../guides/schedules.md)\n- [HTTP apps](../guides/http-apps.md)\n"},{"slug":"reference/what-gets-deployed","section":"reference","title":"What gets deployed","description":"The exact AWS resources laranja creates and how they're named.","order":4,"body":"\n# What gets deployed\n\nlaranja maps each thing it finds in your code to a small, predictable set of AWS\nresources — all in your own account.\n\n## HTTP app → proxy Lambda + Function URL\n\nYour entire Express app is deployed as **one Lambda function** fronted by a\n**Lambda Function URL**. There is no API Gateway.\n\n- All routes are served by this single proxy Lambda.\n- The Function URL is **public** (`authType: NONE`) and CORS is open\n (`*` origins, all methods, all headers) — your app handles auth/CORS as it\n sees fit.\n- Memory and timeout come from [`compute`](./config-file.md#compute)\n in your config (the scaffold sets `{ memory: 256, timeout: 30 }`), overridable\n per-resource under the `http` key in [`resources`](./config-file.md#resources).\n- The public HTTPS URL is emitted as the `HttpUrl` stack output.\n\n```\nGET https://‹id›.lambda-url.‹region›.on.aws/\n```\n\n## `@Cron` → Lambda + EventBridge rule\n\nEach cron handler becomes **its own Lambda** plus an **EventBridge rule** that\ninvokes it on schedule.\n\n- Memory and timeout come from [`compute`](./config-file.md#compute)\n (default `{ memory: 256, timeout: 30 }`), overridable per cron id in\n [`resources`](./config-file.md#resources).\n- The schedule comes from your [`rate()`/`every()`](../guides/schedules.md)\n builder or raw expression.\n\n## `@Queue` → SQS queue + consumer Lambda\n\nEach queue handler becomes an **SQS queue** and a **consumer Lambda** wired to it.\n\n- Encryption: **SQS-managed (SSE-SQS)**.\n- **FIFO** queues are created when the name ends in `.fifo` or `fifo: true` is\n set (content-based deduplication is enabled for FIFO).\n- Default **batch size**: 10. Your handler is invoked per message with the\n JSON-parsed body.\n- **Partial-batch failures** are enabled: throwing for one message fails only\n that message; the rest of the batch still succeeds.\n- Consumer memory/timeout come from [`compute`](./config-file.md#compute)\n (default `{ memory: 256, timeout: 30 }`). The queue's **visibility timeout** is\n derived to satisfy AWS's rule that it be ≥ the consumer timeout, and can be set\n explicitly per queue via [`resources`](./config-file.md#resources).\n- The queue URL is emitted as a stack output.\n\n## Naming\n\nEverything is named deterministically — no random suffixes:\n\n| Resource | Pattern | Example |\n|---|---|---|\n| CloudFormation stack | `‹name›-‹stage›` | `my-api-prod` |\n| Lambda functions | `‹name›-‹fn›-‹stage›` | `my-api-app-prod`, `my-api-sendEmail-prod` |\n\n`‹fn›` is the handler's name (the method/function name), or the explicit `id` you\nset on `@Cron`/`@Queue`. The HTTP proxy uses `app`. Names are truncated to AWS's\n64-character limit and sanitized to allowed characters.\n\nBecause the stage is part of the names, multiple stages can live in one account\nwithout colliding — see [Stages & environments](../guides/stages-and-environments.md).\n\n## Supporting resources\n\nEach Lambda gets an **IAM execution role** (and the queue consumers get the\npermissions to read their queue / EventBridge to invoke crons). The first deploy\nto an account/region also creates the one-time CDK **bootstrap** resources (an S3\nasset bucket and roles).\n\n## Related\n\n- [How it works](../getting-started/how-it-works.md)\n- [HTTP apps](../guides/http-apps.md) · [Cron jobs](../guides/cron-jobs.md) · [Queues](../guides/queues.md)\n"}];
5
+ export const nav = [{"id":"","title":"Overview","items":[{"slug":"","section":"","title":"Documentation","description":"Code-first deploys for Node.js apps to your own AWS account.","order":0,"body":"\n# laranja docs\n\n**laranja** deploys your Node.js app to your own AWS account from your code — no\nYAML, no console clicking, no separate infrastructure project. You write an\nExpress app plus a few functions or decorators; laranja reads the code, figures\nout the infrastructure, and ships it.\n\n```bash\nnpm install -D @alzulejos/laranja\nnpx laranja deploy\n```\n\n## Start here\n\n- **[Introduction](./getting-started/introduction.md)** — what laranja is and the ideas behind it.\n- **[Installation](./getting-started/installation.md)** — prerequisites and setup.\n- **[Quickstart](./getting-started/quickstart.md)** — from zero to a live URL.\n- **[How it works](./getting-started/how-it-works.md)** — how your code becomes a running app on AWS.\n\n## Guides\n\n- **[HTTP apps](./guides/http-apps.md)** — deploy your app behind a public URL with the `http()` marker.\n- **[Cron jobs](./guides/cron-jobs.md)** — scheduled functions with `@Cron` / `cron()`.\n- **[Queues](./guides/queues.md)** — SQS consumers with `@Queue` / `queue()`.\n- **[Schedules](./guides/schedules.md)** — the `rate()` / `every()` builders and raw expressions.\n- **[Environment variables](./guides/environment-variables.md)** — `env`, `STAGE`, and resolution.\n- **[Stages & environments](./guides/stages-and-environments.md)** — dev / staging / prod with one codebase.\n\n## Reference\n\n- **[CLI commands](./reference/commands.md)** — `init`, `logout`, `plan`, `deploy`, `destroy`, `logs`, `eject`.\n- **[Config file](./reference/config-file.md)** — every field in `laranja.config.ts`.\n- **[Decorators & markers](./reference/decorators-and-markers.md)** — `@Cron`, `@Queue`, `cron`, `queue`, `http`, `env`.\n- **[What gets deployed](./reference/what-gets-deployed.md)** — the AWS resources and how they're named.\n\n> **Status:** v1 targets **AWS** with **Express** today; **NestJS support is\n> coming**. The internal model is provider- and framework-neutral, so new clouds\n> and frameworks land without changing your app code.\n"}]},{"id":"getting-started","title":"Start here","items":[{"slug":"getting-started/introduction","section":"getting-started","title":"Introduction","description":"What laranja is, the problem it solves, and the principles behind it.","order":1,"body":"\n# Introduction\n\nlaranja is a **code-first deploy tool** for Node.js apps. You write your\napplication — an Express app, some scheduled jobs, some queue consumers — and\nlaranja deploys it to **your own AWS account**. There is no infrastructure\nproject to maintain, no YAML to write, and no cloud console to click through.\n\n> laranja supports **Express** and **NestJS** — and because your app is reduced to\n> a framework-neutral description internally, more frameworks can follow without\n> changing how you write your code.\n\n## The problem\n\nShipping a small Node service to AWS usually means choosing between:\n\n- **A platform** (managed hosting) — fast, but you don't own the infrastructure\n and you pay a markup to run in someone else's account.\n- **Infrastructure-as-code** (CDK / Terraform / CloudFormation) — you own\n everything, but now you maintain a second codebase that drifts from the app it\n describes.\n\nlaranja takes a third path: **your application code _is_ the source of truth for\nthe infrastructure.** A route is an HTTP endpoint. A `@Cron` method is a\nscheduled function. A `@Queue` method is an SQS consumer. laranja reads those\nfacts out of your code and provisions exactly what they imply.\n\n## How it feels\n\n```ts\n// src/app.ts — mark your HTTP app, code-first\nimport express from \"express\";\nimport { http } from \"@alzulejos/laranja-decorators\";\n\nconst app = express();\napp.get(\"/health\", (_req, res) => res.json({ ok: true }));\n\nexport default http(app);\n```\n\nJobs and queues come in two styles — plain **functions** or **classes** with\ndecorators. Use whichever you prefer:\n\n```ts tab=\"Function\"\n// src/jobs.ts\nimport { cron, rate } from \"@alzulejos/laranja-decorators\";\n\nexport async function refreshCache() { /* ... */ }\ncron(rate(5, \"minutes\"), refreshCache);\n```\n\n```ts tab=\"Class\"\n// src/jobs.ts\nimport { Cron, rate } from \"@alzulejos/laranja-decorators\";\n\nexport class Jobs {\n @Cron(rate(5, \"minutes\"))\n async refreshCache() { /* ... */ }\n}\n```\n\n```bash\nnpx laranja deploy\n```\n\nYou get a live HTTPS URL, an EventBridge schedule, and an SQS queue with a\nconsumer — all in your own account, named deterministically.\n\n## Principles\n\n- **Code is the source of truth.** Infrastructure is _derived_ from the app, not\n declared alongside it. There's nothing to keep in sync.\n- **Your account, your resources.** laranja deploys with your AWS credentials\n into your account. You own every resource and can inspect it in the console.\n- **Deterministic, no magic names.** Resources are named `‹app›-‹fn›-‹stage›` —\n predictable and greppable, with no random suffixes.\n- **Provider-neutral by design.** AWS is the first target, but laranja is built\n so other clouds can follow without changing your app code.\n- **An escape hatch when you need it.** Outgrow the abstraction? `laranja eject`\n hands you a real, owned CDK project (see [eject](../reference/commands.md#eject)).\n\n## What's in scope (v1)\n\n| Capability | How you declare it |\n|---|---|\n| HTTP API | An Express or NestJS app, marked with the [`http()`](../reference/decorators-and-markers.md#http) marker. |\n| Scheduled jobs | [`@Cron`](../reference/decorators-and-markers.md#cron) (class) or [`cron()`](../reference/decorators-and-markers.md#cron-marker) (function) |\n| Queue consumers | [`@Queue`](../reference/decorators-and-markers.md#queue) (class) or [`queue()`](../reference/decorators-and-markers.md#queue-marker) (function) |\n| Per-environment deploys | [Stages](../guides/stages-and-environments.md) (`--stage`) |\n| Env vars | [`env`](../guides/environment-variables.md) in config |\n\nNext: **[Installation](./installation.md)**.\n"},{"slug":"getting-started/installation","section":"getting-started","title":"Installation","description":"Prerequisites and how to add laranja to a project.","order":2,"body":"\n# Installation\n\n## Prerequisites\n\n- **Node.js 20 or newer.** Deployed Lambdas run on the Node.js 20 runtime, and\n the CLI targets the same.\n- **A laranja account + API key.** laranja synthesizes your deployment template on\n its server, so `plan`, `deploy`, and `eject` need an API key (created in the\n dashboard) and a project to link to. You connect both by running\n [`laranja init`](./quickstart.md#3-sign-in-and-configure) — see the Quickstart.\n- **An AWS account** plus credentials on the standard AWS chain — any of:\n - `aws configure` (a shared credentials file),\n - AWS SSO (`aws sso login`),\n - environment variables (`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN`),\n - a named profile (set [`profile`](../reference/config-file.md) in config).\n- **A region**, from `region` in your config or the `AWS_REGION` /\n `AWS_DEFAULT_REGION` environment variable.\n\nYou do **not** need to install the AWS CDK or the AWS CLI separately — the CDK\ntoolkit is embedded in laranja.\n\n## Install\n\nAdd the CLI as a dev dependency:\n\n```bash\nnpm install -D @alzulejos/laranja\n```\n\nIf you use decorators or function markers for jobs and queues, also install:\n\n```bash\nnpm install @alzulejos/laranja-decorators\n```\n\n> `@alzulejos/laranja-decorators` is a regular dependency (not dev-only) because your\n> application imports `@Cron`, `@Queue`, `rate`, etc. at runtime.\n\n## First-time AWS setup (bootstrap)\n\nThe first time you deploy to a given account + region, laranja runs a one-time\n**bootstrap** that creates a small set of shared resources in _your_ account (an\nS3 asset bucket and a few IAM roles). `laranja deploy` detects this and prompts\nyou before doing it — see [deploy](../reference/commands.md#deploy).\n\n## Verify\n\n```bash\nnpx laranja --help\n```\n\nYou're ready for the **[Quickstart](./quickstart.md)**.\n"},{"slug":"getting-started/quickstart","section":"getting-started","title":"Quickstart","description":"From an empty folder to a live HTTPS endpoint in a few minutes.","order":3,"body":"\n# Quickstart\n\nThis walks you from zero to a deployed app with an HTTP endpoint, a scheduled\njob, and a queue consumer.\n\n## 1. Create a project\n\n```bash\nmkdir my-api && cd my-api\nnpm init -y\nnpm install express\nnpm install -D @alzulejos/laranja typescript\nnpm install @alzulejos/laranja-decorators\n```\n\n> laranja supports **Express** and **NestJS**. For Nest, see the\n> [HTTP apps guide](../guides/http-apps.md#nestjs).\n\n## 2. Write your app\n\nMark your app with [`http()`](../reference/decorators-and-markers.md#http) and\nexport it. laranja finds it by reading your code — so there's nothing to wire up\nin config:\n\n```ts\n// src/app.ts\nimport express from \"express\";\nimport { http } from \"@alzulejos/laranja-decorators\";\n\nconst app = express();\napp.get(\"/\", (_req, res) => res.json({ ok: true, stage: process.env.STAGE }));\napp.get(\"/users/:id\", (req, res) => res.json({ id: req.params.id }));\n\nexport default http(app);\n```\n\nAdd a scheduled job and a queue consumer (optional). There are two styles — pick\nwhichever fits your codebase.\n\n```ts tab=\"Function\"\n// src/jobs.ts\nimport { cron, queue, rate } from \"@alzulejos/laranja-decorators\";\n\nexport async function refreshCache() {\n console.log(\"refreshing cache…\");\n}\ncron(rate(5, \"minutes\"), refreshCache);\n\nexport async function sendEmail(body: unknown) {\n console.log(\"sending\", body);\n}\nqueue({ name: \"emails\", batchSize: 10 }, sendEmail);\n```\n\n```ts tab=\"Class\"\n// src/jobs.ts\nimport { Cron, Queue, rate } from \"@alzulejos/laranja-decorators\";\n\nexport class Jobs {\n @Cron(rate(5, \"minutes\"))\n async refreshCache() {}\n\n @Queue({ name: \"emails\", batchSize: 10 })\n async sendEmail(body: unknown) {}\n}\n```\n\n## 3. Sign in and configure\n\nRun the scaffolder. It prompts for your **laranja API key** (from the dashboard),\nvalidates it, stores it in `~/.laranja/auth.json`, and lets you pick or create a\n**dashboard project** — filling in `name` and `projectId` for you:\n\n```bash\nnpx laranja init\n```\n\nThe generated `laranja.config.ts` looks like this (edit `region`, `env`, and\n`compute` to taste):\n\n```ts\n// laranja.config.ts\nimport type { LaranjaConfig } from \"@alzulejos/laranja-decorators\";\n\nconst config: LaranjaConfig = {\n name: \"my-api\",\n // From your laranja dashboard — identifies this project on the server.\n projectId: \"proj_…\",\n region: \"us-east-1\",\n env: { LOG_LEVEL: \"info\" },\n // Default compute for every function (the HTTP proxy + each cron/queue).\n compute: { memory: 256, timeout: 30 },\n};\n\nexport default config;\n```\n\nBecause the app is marked with `http()` in code, the config stays minimal — the\nHTTP app is declared there, not in config. See the\n**[config reference](../reference/config-file.md)**.\n\n## 4. Preview the plan\n\n`plan` shows what a deploy would do: it synthesizes your template on the server,\ndiffs it against what's deployed in your AWS account, and tags each resource\n**created / changed / unchanged**. It's **read-only** — nothing is applied, and it\nnever counts against your deploy limit.\n\n```bash\nnpx laranja plan\n```\n\n```\nPlan for \"my-api-dev\"\n\n+ http HTTP 2 routes → proxy Lambda + Function URL\n+ daily Cron Lambda + EventBridge rule\n+ emails Queue SQS + consumer Lambda\n\n8 AWS resources +8 created =0 unchanged\n```\n\nOn this first run nothing is deployed yet, so everything shows as `+` created.\n\n## 5. Deploy\n\n```bash\nnpx laranja deploy\n```\n\nThe first deploy to a new account/region prompts you to **bootstrap** (a\none-time setup in your account). When it finishes you'll see your live URL:\n\n```\n🌐 http https://abc123.lambda-url.us-east-1.on.aws/\n```\n\nHit it:\n\n```bash\ncurl https://abc123.lambda-url.us-east-1.on.aws/\n# {\"ok\":true,\"stage\":\"dev\"}\n```\n\n## 6. Iterate\n\n```bash\nnpx laranja logs # tail CloudWatch logs (pick a function)\nnpx laranja plan # see what a deploy would change\nnpx laranja deploy # ship again\nnpx laranja destroy # tear it all down\n```\n\n## Next steps\n\n- Ship to multiple environments: **[Stages & environments](../guides/stages-and-environments.md)**.\n- Understand what was created: **[What gets deployed](../reference/what-gets-deployed.md)**.\n- Go deeper on jobs and queues: **[Cron jobs](../guides/cron-jobs.md)**, **[Queues](../guides/queues.md)**.\n"},{"slug":"getting-started/how-it-works","section":"getting-started","title":"How it works","description":"How laranja turns your code into a running app on AWS.","order":4,"body":"\n# How it works\n\nYou write your app; laranja deploys it to your own AWS account. Two things are\nworth knowing about how it does that.\n\n## It reads your code — it never runs it\n\nlaranja discovers your infrastructure by **reading** your source: your HTTP app\nand its routes, your `@Cron` / `cron()` jobs and their schedules, your\n`@Queue` / `queue()` consumers, and the env vars you wrap with `env()`. It does\nthis without executing your code, so planning a deploy is always safe — nothing\nof yours runs just to figure out what to deploy.\n\nThat's also why a few things must be written so laranja can see them: schedules\nuse literal builders like `rate(5, \"minutes\")`, and `env(\"…\")` takes a string\nliteral.\n\n## It deploys into your AWS account\n\nlaranja turns what it found into AWS resources — a Lambda for your app, an\nEventBridge rule per cron, an SQS queue per consumer — and deploys them with\n**your** credentials into **your** account. See\n[what gets deployed](../reference/what-gets-deployed.md) for the full mapping.\n\n- The AWS CDK toolkit is embedded, so there's nothing extra to install.\n- The first deploy to a new account/region runs a one-time **bootstrap**.\n- [`plan`](../reference/commands.md#plan) previews what a deploy would change\n (created/changed/unchanged); [`destroy`](../reference/commands.md#destroy)\n tears the stack down.\n- Outputs (your HTTPS URL, queue URLs) are printed when the deploy finishes.\n\n## The template is built on the server — your code stays local\n\n[`plan`](../reference/commands.md#plan) and [`deploy`](../reference/commands.md#deploy)\nsynthesize the deployment template on the **laranja server**. laranja scans and\nbundles your code locally, then sends only a **description** of your\ninfrastructure (the internal model plus the asset hashes of your bundles) — your\nsource code and bundles never leave your machine. The returned template is then\napplied to AWS with your **own** credentials. This is why these commands need a\n`LARANJA_API_KEY` and a `projectId` (run [`laranja init`](../reference/commands.md#init)\nonce to set both up).\n\n## Related\n\n- [What gets deployed](../reference/what-gets-deployed.md)\n- [Stages & environments](../guides/stages-and-environments.md)\n"}]},{"id":"guides","title":"Guides","items":[{"slug":"guides/http-apps","section":"guides","title":"HTTP apps","description":"Deploy your HTTP app behind a public HTTPS endpoint.","order":1,"body":"\n# HTTP apps\n\nlaranja deploys your whole HTTP app as a single proxy Lambda behind a public\n[Function URL](../reference/what-gets-deployed.md#http-app--proxy-lambda--function-url).\nlaranja supports **Express** and **NestJS**.\n\n## Declaring your app (the `http()` marker)\n\nThe code-first way: mark your app with the\n[`http()`](../reference/decorators-and-markers.md#http) marker and export it.\nlaranja finds it by scanning your code — there's nothing to configure.\n\n```ts\n// src/app.ts\nimport express from \"express\";\nimport { http } from \"@alzulejos/laranja-decorators\";\n\nconst app = express();\napp.use(express.json());\n\napp.get(\"/\", (_req, res) => res.json({ ok: true }));\napp.post(\"/users\", (req, res) => res.status(201).json(req.body));\n\nexport default http(app); // or: export const api = http(app);\n```\n\n`http()` returns the app untouched — it's a static marker, not a wrapper, so it\nhas no runtime effect. That's all you need: every route you register is served by\nthe deployed proxy. The marker is the only way to declare an HTTP app — there's\nexactly one per project, and it must be exported so the scanner can find it.\n\n## NestJS\n\nNest apps work the same way, with one difference: a Nest app only exists after an\nasync `NestFactory.create(...)`, so instead of a ready app object you wrap your\n**bootstrap function** and have it `return` the app:\n\n```ts\n// src/main.ts\nimport { NestFactory } from \"@nestjs/core\";\nimport { http } from \"@alzulejos/laranja-decorators\";\nimport { AppModule } from \"./app.module\";\n\nexport async function bootstrap() {\n const app = await NestFactory.create(AppModule);\n // configure however you like — pipes, guards, middleware, raw body, cookies…\n app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));\n await app.listen(process.env.PORT ?? 3000); // fine to keep for local dev\n return app; // ← the only change laranja needs\n}\n\n// Run locally with `npm run start`; skipped when laranja imports this file.\nif (require.main === module) void bootstrap();\n\nexport default http(bootstrap); // wrap the factory, not a module\n```\n\nlaranja runs your `bootstrap()` verbatim, so every pipe, guard, and piece of\nmiddleware you configure is preserved — nothing is re-derived. You keep your\nnormal Nest project (`@nestjs/platform-express`); no laranja-specific\nrestructuring.\n\nTwo things to know:\n\n- **Build before you deploy.** laranja packages your compiled output (`nest build`\n → `dist/`), because Nest's dependency injection relies on the decorator metadata\n your own TypeScript build emits. laranja deploys what you build — it doesn't run\n your build for you — so run `nest build` yourself after every code change.\n Deploying without a `dist/` fails with a clear message, but a **stale** `dist/`\n (source edited since your last build) deploys silently as outdated code, so make\n the build part of your deploy step (e.g. `nest build && laranja deploy`).\n- **Use the default Express platform.** The Fastify adapter isn't supported yet.\n\n## Routing, middleware, and `STAGE`\n\nYour app runs as-is inside Lambda. Standard Express features work — routing,\nmiddleware, JSON bodies, route params. The active\n[stage](./stages-and-environments.md) is available as\n`process.env.STAGE`:\n\n```ts\napp.get(\"/whoami\", (_req, res) => res.json({ stage: process.env.STAGE }));\n```\n\n## CORS and auth\n\nThe Function URL is public with permissive CORS (all origins/methods/headers).\nHandle authentication and any stricter CORS rules **inside your app**, the same\nway you would anywhere else.\n\n## Compute (memory & timeout)\n\nThe HTTP proxy Lambda's memory and timeout come from\n[`compute`](../reference/config-file.md#compute) in your config — the scaffold\ndefault is `{ memory: 256, timeout: 30 }`, and you can override it under the `http`\nkey in [`resources`](../reference/config-file.md#resources). Long-running work\nbelongs in a [cron job](./cron-jobs.md) or behind a [queue](./queues.md), not a\nrequest.\n\n## Workers-only deployments\n\nIf your HTTP API is hosted elsewhere and you only want to deploy scheduled jobs\nand queue consumers, just don't add an `http()` marker — there's nothing to set\nin config:\n\n```ts\n// laranja.config.ts\nconst config: LaranjaConfig = {\n name: \"my-workers\",\n env: { LOG_LEVEL: \"info\" },\n};\n```\n\nWith no marker, only your [`@Cron`](./cron-jobs.md) / [`@Queue`](./queues.md)\nhandlers are deployed — no HTTP proxy, no Function URL.\n\nFor a **workers-only Nest** app, there's no `http(bootstrap)` to build the DI\ncontainer from, so declare your module with the\n[`workers()`](../reference/decorators-and-markers.md#workers) marker instead\n(`export default workers(AppModule)`) — see [Cron jobs → NestJS](./cron-jobs.md#nestjs).\n\n## Related\n\n- [What gets deployed](../reference/what-gets-deployed.md)\n- [Cron jobs](./cron-jobs.md) · [Queues](./queues.md)\n"},{"slug":"guides/cron-jobs","section":"guides","title":"Cron jobs","description":"Run functions on a schedule with @Cron or cron().","order":2,"body":"\n# Cron jobs\n\nA cron job is a function that runs on a schedule. Each one becomes\n[its own Lambda plus an EventBridge rule](../reference/what-gets-deployed.md#cron--lambda--eventbridge-rule).\n\n## Class style — `@Cron`\n\nDecorate a method with [`@Cron`](../reference/decorators-and-markers.md#cron) and\ngive it a [schedule](./schedules.md):\n\n```ts\nimport { Cron, rate, every } from \"@alzulejos/laranja-decorators\";\n\nexport class Jobs {\n @Cron(rate(5, \"minutes\"))\n async refreshCache() {\n // …\n }\n\n @Cron(every(\"day\"))\n async nightlyCleanup() {\n // …\n }\n\n @Cron({ schedule: \"cron(0 12 * * ? *)\", id: \"daily-report\" })\n async sendReport() {\n // …\n }\n}\n```\n\nThe handler's logical id defaults to `‹Class›-‹method›`; pass `id` to set a\nstable, explicit name (which also drives the Lambda's name).\n\n## Function style — `cron()`\n\nIf you don't use classes, register a standalone exported function with\n[`cron()`](../reference/decorators-and-markers.md#cron-marker):\n\n```ts\nimport { cron, rate } from \"@alzulejos/laranja-decorators\";\n\nexport async function refreshCache() {\n // …\n}\n\ncron(rate(5, \"minutes\"), refreshCache);\n```\n\nThe function's name becomes the resource id unless you pass an explicit `id`:\n\n```ts\ncron({ schedule: every(\"hour\"), id: \"hourly-sync\" }, refreshCache);\n```\n\n## NestJS\n\nIn a Nest app, `@Cron` goes on a normal provider — with injected dependencies —\nand you can keep the schedule syntax you already use (a\n[node-cron string or `CronExpression`](./schedules.md#node-cron-expressions-nestjsschedule-compatibility)).\nSwapping the import from `@nestjs/schedule` is usually the only change:\n\n```ts\n// tasks.service.ts\nimport { Injectable } from \"@nestjs/common\";\nimport { Cron, CronExpression } from \"@alzulejos/laranja-decorators\"; // ← was @nestjs/schedule\n\n@Injectable()\nexport class TasksService {\n constructor(private readonly reports: ReportsService) {} // real DI\n\n @Cron(CronExpression.EVERY_30_MINUTES)\n async sweep() {\n await this.reports.rebuild(); // `this.reports` is injected\n }\n}\n```\n\nBecause the method runs on a real provider, laranja resolves it through your\napp's dependency-injection container instead of `new`-ing the class. Point it at\nyour module **once** with the [`workers()`](../reference/decorators-and-markers.md#workers)\nmarker:\n\n```ts\n// src/main.ts (or a dedicated file)\nimport { workers } from \"@alzulejos/laranja-decorators\";\nimport { AppModule } from \"./app.module\";\n\nexport default workers(AppModule); // build a DI context from this module\n```\n\nPass `AppModule` for the whole graph, or a leaner module you compose if you want\na smaller cold start. Like the Nest [HTTP path](./http-apps.md#nestjs), laranja\npackages your **compiled** `dist/` output — run `nest build` before deploying so\nthe DI metadata exists.\n\n## Schedules\n\nSchedules are written with the portable `rate()` / `every()` builders, or as a\nraw expression string. See the **[Schedules reference](./schedules.md)** for the\nfull set of options.\n\n```ts\n@Cron(rate(30, \"minutes\")) // every 30 minutes\n@Cron(every(\"hour\")) // every hour (shorthand for rate(1, \"hour\"))\n@Cron({ schedule: \"cron(0 9 * * ? *)\" }) // raw AWS cron: 09:00 UTC daily\n```\n\n## Runtime behavior\n\n- Each cron runs in its **own Lambda**, isolated from your HTTP app and other\n jobs.\n- Memory and timeout come from [`compute`](../reference/config-file.md#compute)\n (default `{ memory: 256, timeout: 30 }`) and can be overridden per cron id in\n [`resources`](../reference/config-file.md#resources).\n- All [config `env`](./environment-variables.md) and `STAGE` are\n available via `process.env`.\n\n## Related\n\n- [Schedules](./schedules.md)\n- [`@Cron` / `cron()` reference](../reference/decorators-and-markers.md#cron)\n- [Queues](./queues.md)\n"},{"slug":"guides/queues","section":"guides","title":"Queues","description":"Process SQS messages with @Queue or queue(), including FIFO.","order":3,"body":"\n# Queues\n\nA queue consumer processes messages from an SQS queue. Each one becomes\n[an SQS queue plus a consumer Lambda](../reference/what-gets-deployed.md#queue--sqs-queue--consumer-lambda).\n\n## Class style — `@Queue`\n\nDecorate a method with [`@Queue`](../reference/decorators-and-markers.md#queue):\n\n```ts\nimport { Queue } from \"@alzulejos/laranja-decorators\";\n\nexport class Workers {\n @Queue({ name: \"emails\", batchSize: 10 })\n async sendEmail(body: unknown) {\n // `body` is the JSON-parsed message\n }\n}\n```\n\n## Function style — `queue()`\n\n```ts\nimport { queue } from \"@alzulejos/laranja-decorators\";\n\nexport async function sendEmail(body: unknown) {\n // …\n}\n\nqueue({ name: \"emails\", batchSize: 10 }, sendEmail);\n```\n\n## NestJS\n\n`@Queue` works on a Nest provider with injected dependencies. As with\n[cron jobs](./cron-jobs.md#nestjs), laranja resolves the consumer through your\nDI container, so declare your module once with the\n[`workers()`](../reference/decorators-and-markers.md#workers) marker\n(`export default workers(AppModule)`) and deploy your compiled `dist/` output.\nStandalone `queue()` functions don't need it.\n\n## Options\n\n| Option | Default | Description |\n|---|---|---|\n| `name` | _required_ | Queue name. A `.fifo` suffix marks a FIFO queue. |\n| `batchSize` | `10` | Max messages delivered to the consumer per invocation. |\n| `fifo` | `false` | Force a FIFO queue (or end `name` with `.fifo`). When set, laranja appends `.fifo` to `name` if you left it off. |\n\n## How messages are delivered\n\n- Your handler is invoked **once per message**, with the message **body already\n JSON-parsed**.\n- **Partial-batch failures** are enabled: if your handler throws for one message,\n only that message is retried — the rest of the batch is still acknowledged.\n- Consumer memory/timeout come from [`compute`](../reference/config-file.md#compute)\n (default `{ memory: 256, timeout: 30 }`); the queue's visibility timeout is\n derived to stay ≥ the consumer timeout (override it per queue via\n [`resources`](../reference/config-file.md#resources)).\n\n```ts\n@Queue({ name: \"orders\" })\nasync processOrder(body: unknown) {\n const order = body as { id: string };\n if (!order.id) throw new Error(\"bad message\"); // only THIS message is retried\n // …\n}\n```\n\n## FIFO queues\n\nEnd the name with `.fifo` (or set `fifo: true`) for ordered, exactly-once\nprocessing. Content-based deduplication is enabled automatically:\n\n```ts\n@Queue({ name: \"orders.fifo\", fifo: true })\nasync processOrder(body: unknown) {\n // …\n}\n```\n\nAWS requires FIFO queue names to end in `.fifo`. If you set `fifo: true` but\nleave the suffix off, laranja appends it for you — so `{ name: \"orders\", fifo: true }`\ndeploys a queue named `orders.fifo`. The normalized name is what appears in\n`laranja plan` and in the AWS console, so there's no surprise at deploy time.\n\n## Sending messages\n\nConsuming is only half the loop — to **produce** a message, call\n[`getQueue`](../reference/decorators-and-markers.md#getqueue) with the queue's\n`name` and `.send()` a payload:\n\n```ts\nimport { getQueue } from \"@alzulejos/laranja-decorators\";\n\napp.post(\"/signup\", async (req, res) => {\n await getQueue(\"emails\").send({ to: req.body.email, template: \"welcome\" });\n res.sendStatus(202);\n});\n```\n\nObjects are JSON-serialized for you (strings are sent as-is), so the consumer\nreceives them already parsed — `getQueue(\"emails\").send({ to })` on one end,\n`async sendEmail(body)` on the other.\n\nYou can produce from **anywhere** in a deployed app — an HTTP route, a\n[cron job](./cron-jobs.md), or another queue's consumer fanning out. laranja\ninjects each queue's URL into every function's environment at deploy and grants\n`sqs:SendMessage`, so there's no client to configure, no URL to look up, and no\nIAM to wire. It's a thin wrapper over one SQS `SendMessage` call — laranja\nprovisions the infrastructure; it deliberately does **not** add a job framework\n(retries, scheduling, and job state stay with SQS and your consumer).\n\n### FIFO and options\n\n`.send()` takes a second options argument:\n\n| Option | Applies to | Description |\n|---|---|---|\n| `groupId` | FIFO (**required**) | `MessageGroupId` — messages with the same group are ordered. |\n| `dedupId` | FIFO | `MessageDeduplicationId` — only needed when content-based dedup is off. |\n| `delaySeconds` | Standard | Delay (0–900s) before the message becomes visible. Ignored by FIFO. |\n\n```ts\n// FIFO queues require a groupId — the send throws without one.\nawait getQueue(\"orders.fifo\").send(order, { groupId: order.customerId });\n```\n\n> Prefer the raw SDK? The queue URL is also emitted as a stack output after\n> deploy and visible in the AWS console — send with `@aws-sdk/client-sqs`\n> directly if you'd rather.\n\n## Related\n\n- [`@Queue` / `queue()` reference](../reference/decorators-and-markers.md#queue)\n- [What gets deployed](../reference/what-gets-deployed.md#queue--sqs-queue--consumer-lambda)\n- [Cron jobs](./cron-jobs.md)\n"},{"slug":"guides/schedules","section":"guides","title":"Schedules","description":"The rate() and every() builders, and raw schedule expressions.","order":4,"body":"\n# Schedules\n\nSchedules drive [cron jobs](./cron-jobs.md). laranja stores them in a\n**provider-neutral** form, so prefer the builders — they're portable across\nclouds. A raw expression string is available as an escape hatch.\n\n## `rate(value, unit)`\n\n\"Every N units.\" Portable everywhere.\n\n```ts\nimport { rate } from \"@alzulejos/laranja-decorators\";\n\nrate(5, \"minutes\") // every 5 minutes\nrate(1, \"hour\") // every hour\nrate(2, \"days\") // every 2 days\n```\n\n- `value` must be a **positive integer** (≥ 1).\n- `unit` is one of `\"minute\"`, `\"minutes\"`, `\"hour\"`, `\"hours\"`, `\"day\"`,\n `\"days\"` — singular or plural, your choice.\n\n## `every(unit)`\n\nShorthand for `rate(1, unit)`. Takes a singular unit:\n\n```ts\nimport { every } from \"@alzulejos/laranja-decorators\";\n\nevery(\"minute\") // = rate(1, \"minute\")\nevery(\"hour\") // = rate(1, \"hour\")\nevery(\"day\") // = rate(1, \"day\")\n```\n\n## Raw AWS expressions (escape hatch)\n\nWhen you need something the builders can't express (e.g. \"noon UTC every day\"),\npass a **wrapped** AWS schedule string — `cron(...)` or `rate(...)`:\n\n```ts\n@Cron({ schedule: \"cron(0 12 * * ? *)\", id: \"daily-report\" }) // 12:00 UTC daily\n@Cron(\"rate(5 minutes)\") // raw rate string\n```\n\nAWS cron has **six fields**: `cron(Minutes Hours Day-of-month Month Day-of-week\nYear)`.\n\nA few examples:\n\n| Expression | Meaning |\n|---|---|\n| `cron(0 12 * * ? *)` | 12:00 UTC every day |\n| `cron(0/15 * * * ? *)` | every 15 minutes |\n| `cron(0 8 ? * MON-FRI *)` | 08:00 UTC on weekdays |\n| `cron(0 0 1 * ? *)` | midnight UTC on the 1st of each month |\n\n## node-cron expressions (`@nestjs/schedule` compatibility)\n\nA **bare** (unwrapped) string is read as a standard 5- or 6-field\n[node-cron](https://github.com/kelektiv/node-cron) expression — the same syntax\n`@nestjs/schedule`'s `@Cron` takes. laranja translates it to the AWS dialect for\nyou, so a Nest app can swap the import and keep its existing schedules:\n\n```ts\n@Cron(\"0 12 * * *\") // noon every day\n@Cron(\"*/5 * * * *\") // every 5 minutes\n@Cron(\"0 0 * * 1-5\") // midnight on weekdays\n@Cron(CronExpression.EVERY_30_MINUTES) // the enum works too\n```\n\n`CronExpression` (mirrored from `@nestjs/schedule`) is re-exported from\n`@alzulejos/laranja-decorators`. Translation handles the day-of-week numbering difference\n(Unix `0`=Sun → AWS `1`=Sun) and the day-of-month/day-of-week rule for you.\n\n**What can't be translated is rejected at build time — never silently rounded:**\n\n| Input | Why it's rejected |\n|---|---|\n| `\"*/30 * * * * *\"`, `CronExpression.EVERY_30_SECONDS` | Sub-minute — EventBridge's floor is **1 minute**. |\n| A seconds field other than `0` | Second-level offsets can't be expressed. |\n| A cron constraining **both** day-of-month and day-of-week | EventBridge requires one to be `*`. |\n\n## `@Interval(ms)`\n\n`@nestjs/schedule`'s `@Interval` is supported and lowers to a `rate(...)`. The\ninterval must be a whole number of minutes (EventBridge's floor):\n\n```ts\n@Interval(300000) // every 5 minutes → rate(5, \"minutes\")\n@Interval(\"poll\", 300000) // named\n```\n\n> `@Timeout` (a one-shot timer relative to process start) has no serverless\n> equivalent and is rejected with a clear message — use `@Cron` or `@Interval`.\n\n## Where you can use them\n\nAnywhere a schedule is expected — the decorator or the function marker — accepts\na builder result, a `Schedule` object, or a raw string:\n\n```ts\n@Cron(rate(5, \"minutes\")) // builder\n@Cron(\"rate(5 minutes)\") // raw string\n@Cron({ schedule: every(\"day\"), id: \"nightly\" })// builder + explicit id\ncron(rate(1, \"hour\"), refreshCache); // function marker\n```\n\n## Related\n\n- [Cron jobs](./cron-jobs.md)\n- [`@Cron` / `cron()` reference](../reference/decorators-and-markers.md#cron)\n"},{"slug":"guides/environment-variables","section":"guides","title":"Environment variables","description":"Declare env vars in config or in code, and supply their values per stage.","order":5,"body":"\n# Environment variables\n\nEvery Lambda laranja deploys receives a set of environment variables, available\nthrough `process.env` as usual. There are two ways to declare them.\n\n## Static values in config\n\nPut plain, commit-safe values in the `env` map in your config. They're injected\ninto **every** function (HTTP proxy, cron, and queue consumers):\n\n```ts\n// laranja.config.ts\nconst config: LaranjaConfig = {\n name: \"my-api\",\n env: {\n LOG_LEVEL: \"info\",\n API_BASE_URL: \"https://api.example.com\",\n },\n};\n```\n\n```ts\n// anywhere in your app\nconst level = process.env.LOG_LEVEL; // \"info\"\n```\n\nUse this for non-sensitive configuration that's the same everywhere: log levels,\npublic URLs, feature flags.\n\n## Values from your environment — `env()`\n\nWhen a value should come from your shell or CI instead of your repo, wrap the\nvariable name with the `env()` helper where you read it:\n\n```ts\nimport { env } from \"@alzulejos/laranja-decorators\";\n\nconst dbUrl = env(\"DATABASE_URL\"); // same as process.env.DATABASE_URL at runtime\n```\n\nlaranja finds every `env(\"…\")` in your code and makes sure that variable is set\non **every** deployed function — no more filling them in by hand in the AWS\nconsole. At deploy time it reads each value from your own environment and sends\nit straight to the function; the value is never written into your repo.\n\nThe name must be a **string literal** — `env(\"DATABASE_URL\")`, not\n`env(someVariable)` — so laranja can discover it just by reading your code.\n\n### Supplying the values\n\nSet the variables in the shell or CI job you deploy from:\n\n```bash\nDATABASE_URL=postgres://… laranja deploy --stage prod\n```\n\n- **Missing a value?** By default laranja deploys anyway and warns you which ones\n were empty — a typo never blocks a deploy. Pass `--strict` to fail the deploy\n instead.\n- **Re-deploying without re-supplying a value?** The previously deployed value is\n kept, so you don't have to pass every variable on every deploy.\n\n## The `STAGE` variable\n\nlaranja always injects `STAGE`, set to the active [stage](./stages-and-environments.md)\n(`\"dev\"` by default, or whatever `--stage` resolved to). You don't declare it:\n\n```ts\napp.get(\"/\", (_req, res) => res.json({ stage: process.env.STAGE }));\n```\n\nIf you also define `STAGE` in `env`, your value wins.\n\n## Per-stage values\n\nBecause [`--stage`](./stages-and-environments.md) selects the\nenvironment at deploy time, the common pattern is one pipeline per stage, each\nsupplying its own values:\n\n- **Shared, non-sensitive defaults** → the `env` map in config.\n- **Per-environment values** → declare them with `env()` and provide them from\n each pipeline's environment.\n\n```bash\n# dev pipeline\nLOG_LEVEL=debug laranja deploy --stage dev\n# prod pipeline\nLOG_LEVEL=warn laranja deploy --stage prod\n```\n\n## Secrets\n\n`env()` keeps values out of your repo, but they still land in the Lambda's plain\nenvironment — readable by anyone with access to the function's configuration. For\ntrue secrets (API keys, DB passwords), that's not enough. First-class secrets\nsupport is on the roadmap; until then, read them at runtime from a secret store\n(e.g. AWS SSM Parameter Store / Secrets Manager) inside your handler.\n\n## Related\n\n- [Config file](../reference/config-file.md)\n- [Stages & environments](./stages-and-environments.md)\n"},{"slug":"guides/stages-and-environments","section":"guides","title":"Stages & environments","description":"Run dev, staging, and prod from one codebase with --stage.","order":6,"body":"\n# Stages & environments\n\nA **stage** is a named environment — `dev`, `staging`, `prod`, or anything you\nlike. laranja makes each stage a fully independent deployment from the same\ncodebase.\n\n## Setting the stage\n\nThe default stage is `dev`. Set it in config:\n\n```ts\n// laranja.config.ts\nconst config: LaranjaConfig = { name: \"my-api\", stage: \"dev\" };\n```\n\n…or override it per command with `--stage` (alias `-s`):\n\n```bash\nlaranja deploy --stage prod\nlaranja deploy -s staging\n```\n\nThe flag wins over the config value, which is why the recommended setup keeps\n`stage` at its default in config and lets each pipeline pass `--stage`.\n\n`--stage` applies to every environment-aware command: `deploy`, `plan`,\n`destroy`, `logs`, and `eject`.\n\n## Each stage is its own stack\n\nThe stage is part of the **stack name** (`‹name›-‹stage›`) and every resource\nname (`‹name›-‹fn›-‹stage›`). So `--stage dev` and `--stage prod` produce two\n**independent CloudFormation stacks** that never collide — even in the same AWS\naccount.\n\n```\nmy-api-dev ← laranja deploy --stage dev\nmy-api-prod ← laranja deploy --stage prod\n```\n\n## Two ways to isolate environments\n\nBoth work, and they compose:\n\n1. **One account, multiple stages.** The stage suffix keeps the stacks separate.\n Good for small projects or non-prod environments.\n2. **Separate accounts per stage.** Point each pipeline at different AWS\n credentials (a dev account and a prod account). Here your **AWS credentials\n are the real boundary**; the stack name can even repeat across accounts\n without conflict.\n\n## One pipeline per stage\n\nThe canonical CI/CD setup is one pipeline per environment, each running the same\ncommand with a different flag:\n\n```yaml\n# dev pipeline → laranja deploy --stage dev\n# staging pipeline → laranja deploy --stage staging\n# prod pipeline → laranja deploy --stage prod\n```\n\nSame repo, same command — only the flag differs. No per-environment config files\nto keep in sync. Pair this with [per-stage env values](./environment-variables.md#per-stage-values)\nto supply each environment's configuration.\n\n> Consistency matters: a pipeline's `destroy`, `logs`, and `plan` must use the\n> **same `--stage`** as its `deploy`, or they'll target a different stack.\n\n## The `STAGE` env var\n\nThe active stage is injected into every Lambda as `process.env.STAGE`, so your\ncode can branch on it:\n\n```ts\nconst isProd = process.env.STAGE === \"prod\";\n```\n\n## Related\n\n- [Config file](../reference/config-file.md)\n- [Environment variables](./environment-variables.md)\n"}]},{"id":"reference","title":"Reference","items":[{"slug":"reference/commands","section":"reference","title":"CLI commands","description":"Every laranja command and flag.","order":1,"body":"\n# CLI commands\n\n```\nlaranja <command> [project-dir] [flags]\n```\n\n`project-dir` defaults to the current directory, so most of the time you just run\n`laranja deploy`. Run `laranja --help` for a summary.\n\n> **Most commands need your account.** `plan`, `deploy`, and `eject` build your\n> template on the laranja server, so they need a `LARANJA_API_KEY` and a\n> `projectId` in your config. Run [`laranja init`](#init) once to sign in and link\n> a project; the key is stored in `~/.laranja/auth.json` so you don't re-export\n> it. Your **source code never leaves your machine** — only a description of your\n> infrastructure does\n> (see [how it works](../getting-started/how-it-works.md)).\n\n## Global flags\n\n| Flag | Applies to | Description |\n|---|---|---|\n| `--stage`, `-s <name>` | deploy, plan, destroy, logs, eject | Target [stage](../guides/stages-and-environments.md); overrides `config.stage`. |\n| `--verbose`, `-v` | deploy | Stream full CDK/CloudFormation output instead of the compact UI. |\n| `--strict` | deploy | Fail if any [`env()`](../guides/environment-variables.md#values-from-your-environment--env) value is unset (default: warn). |\n\n---\n\n## `init`\n\nSign in and scaffold a `laranja.config.ts` in the project directory.\n\n```bash\nlaranja init\n```\n\n`init` prompts for your **laranja API key** (from the dashboard) and validates it\nagainst the server before writing anything, then stores it in\n`~/.laranja/auth.json` so later commands don't need it re-exported. It then lets\nyou **pick or create a dashboard project** and fills the scaffolded config's\n`name` and `projectId` for you. Edit the file afterwards to set your `region`,\n`env`, and `compute`. See the [config reference](./config-file.md).\n\n---\n\n## `logout`\n\nRemove the stored API key (`~/.laranja/auth.json`).\n\n```bash\nlaranja logout\n```\n\nAfter this, commands that talk to the server (`init`, `plan`, `deploy`, `eject`)\nneed `LARANJA_API_KEY` in the environment again, or another `laranja init`.\n\n---\n\n## `plan`\n\nPreview what a deploy would do — laranja synthesizes your template on the server,\ndiffs it against the stack **currently deployed** in your AWS account, and prints\nyour app's resources tagged **created / changed / unchanged**. Nothing is applied.\n\n```bash\nlaranja plan\nlaranja plan --stage prod\n```\n\n```\nPlan for \"my-api-dev\"\n\n= http HTTP 2 routes → proxy Lambda + Function URL\n+ daily Cron Lambda + EventBridge rule\n~ emails Queue SQS + consumer Lambda\n\n8 AWS resources +3 created ~2 changed =3 unchanged\n```\n\n`+` is new, `~` changed, `=` unchanged. The bottom line tallies the underlying\nAWS resources.\n\n`plan` needs `LARANJA_API_KEY` (run [`laranja init`](#init) first) to synthesize,\nand a working **AWS credential chain** to read your live stack. It is\n**read-only** — it never creates a deployment or counts against your deploy limit.\n\n| Flag | Description |\n|---|---|\n| `--stage`, `-s` | Target stage. |\n\n---\n\n## `deploy`\n\nDeploy into your AWS account using your **local** AWS credentials. The template is\nsynthesized on the laranja server first, then applied with your own credentials —\nso deploy needs both `LARANJA_API_KEY` (run [`laranja init`](#init) first) and a\nworking AWS credential chain.\n\n```bash\nlaranja deploy\nlaranja deploy --stage prod\nlaranja deploy --verbose\n```\n\n- The first deploy to a new account/region prompts to **bootstrap** (a one-time\n setup in your account).\n- On success it prints your outputs — the HTTPS URL, queue URLs, and the cron\n jobs deployed.\n\n| Flag | Description |\n|---|---|\n| `--stage`, `-s` | Target stage. |\n| `--verbose`, `-v` | Stream full CDK output. |\n| `--strict` | Fail the deploy if any [`env()`](../guides/environment-variables.md#values-from-your-environment--env) value is unset. By default these are deployed with a warning. |\n\n---\n\n## `destroy`\n\nTear down the deployed stack and all its resources. Prompts for confirmation.\n\n```bash\nlaranja destroy\nlaranja destroy --stage prod\n```\n\n> Targets the stack for the resolved stage — make sure `--stage` matches the\n> environment you intend to remove.\n\n| Flag | Description |\n|---|---|\n| `--stage`, `-s` | Target stage. |\n\n---\n\n## `logs`\n\nTail CloudWatch logs for your deployed functions. The live stack is the source of\ntruth — no local state needed.\n\n```bash\nlaranja logs # interactive picker (TTY)\nlaranja logs sendEmail # tail a specific function by name\nlaranja logs --all # tail every function, multiplexed\nlaranja logs --no-follow # print recent history and exit\nlaranja logs --since 30m # history look-back window\nlaranja logs --stage prod # functions for the prod stack\n```\n\n| Flag / arg | Description |\n|---|---|\n| `<name>` (positional) | Function to tail (matched against its short label or full name). |\n| `--all` | Tail every function in the stack, multiplexed. |\n| `--no-follow` | Print the recent history window and exit (no live tail). |\n| `--since <dur>` | History look-back, e.g. `30s`, `15m`, `1h`, `2d` (default `1h`). |\n| `--stage`, `-s` | Target stage. |\n\nBoth a directory and a function name can be passed as positionals —\n`laranja logs ./app sendEmail` works.\n\n---\n\n## `eject`\n\nGenerate a standalone, owned **CDK project** from your app and stop — for when\nyou've outgrown the abstraction and want full control. **Paid feature.**\n\n```bash\nlaranja eject\nlaranja eject --force # overwrite an existing ./infra\nlaranja eject --stage prod\n```\n\nThe CDK project is generated **on the laranja server** (which gates the paid\nentitlement) and written to `./infra` — a complete project you own and run\nyourself (`cd infra && npm install && npm run deploy`). Requires `LARANJA_API_KEY`\nand a `projectId`; if your account can't eject, the server returns a clear error.\n\n| Flag | Description |\n|---|---|\n| `--force` | Overwrite an existing `infra/` directory. |\n| `--stage`, `-s` | Target stage (baked into the generated project). |\n\n## Related\n\n- [Stages & environments](../guides/stages-and-environments.md)\n- [How it works](../getting-started/how-it-works.md)\n"},{"slug":"reference/config-file","section":"reference","title":"Config file","description":"Every field in laranja.config.ts, with defaults and behavior.","order":2,"body":"\n# `laranja.config.ts`\n\nEvery project has a `laranja.config.ts` at its root that `export default`s a\nconfig object. It's a TypeScript module (loaded via `tsx`), so you get full type\nchecking and can compute values if you need to.\n\n```ts\nimport type { LaranjaConfig } from \"@alzulejos/laranja-decorators\";\n\nconst config: LaranjaConfig = {\n name: \"my-api\",\n // From your laranja dashboard — identifies this project on the server.\n projectId: \"proj_…\",\n region: \"us-east-1\",\n env: { LOG_LEVEL: \"info\" },\n // Default compute for every function (the HTTP proxy + each cron/queue).\n compute: { memory: 256, timeout: 30 },\n};\n\nexport default config;\n```\n\nThe config stays minimal because the HTTP app is declared in code with the\n[`http()`](./decorators-and-markers.md#http) marker — there's no config field for\nit. For a deploy with no HTTP app, just omit the marker (see\n[workers-only deployments](../guides/http-apps.md#workers-only-deployments)). Run\n[`laranja init`](./commands.md#init) to scaffold this file — it fills in\n`name` and `projectId` from the dashboard project you pick.\n\n## Fields\n\n| Field | Required | Default | Description |\n|---|---|---|---|\n| `name` | ✅ | — | App name. Used for the CloudFormation stack and all resource names. |\n| `region` | | `AWS_REGION` / `AWS_DEFAULT_REGION` | AWS region to deploy to. |\n| `stage` | | `\"dev\"` | Deployment stage. Part of the stack + resource names and injected as the `STAGE` env var. Override per-run with [`--stage`](../guides/stages-and-environments.md). |\n| `profile` | | — | AWS named profile to deploy with. |\n| `framework` | | _auto-detected_ | Override framework detection (e.g. `\"express\"`). |\n| `env` | | `{}` | Plain environment variables injected into every Lambda. See [Environment variables](../guides/environment-variables.md). |\n| `compute` | | `{ memory: 256, timeout: 30 }` | Default memory (MB) and timeout (s) for **every** function. See [compute](#compute). |\n| `resources` | | `{}` | Per-resource overrides keyed by resource id (`http`, or a cron/queue id). See [resources](#resources). |\n| `projectId` | ✅ | — | Project id from the laranja dashboard. Required by the server-side build (`plan`/`deploy`/`eject`); `laranja init` fills it in. |\n| `provider` | | `\"aws\"` | Target cloud. Only `\"aws\"` is implemented today. |\n\n### `name`\n\nDrives the stack name (`‹name›-‹stage›`) and every resource name\n(`‹name›-‹fn›-‹stage›`). Choose something short and stable — renaming it after a\ndeploy creates a _new_ stack rather than renaming the old one.\n\n### `region` and `profile`\n\n`region` falls back to `AWS_REGION`, then `AWS_DEFAULT_REGION`. If none is set,\nthe CLI errors with a clear message. `profile` selects a named profile from your\nAWS credentials file; otherwise the default credential chain is used.\n\n### `stage`\n\nThe default is `\"dev\"`. It's part of resource names and is injected into every\nLambda as `process.env.STAGE`. The [`--stage`](../guides/stages-and-environments.md)\nflag overrides it per command — the recommended way to drive multiple\nenvironments from one config.\n\n### `compute`\n\nThe default **memory** (MB) and **timeout** (seconds) applied to every function —\nthe HTTP proxy and each cron/queue consumer. The scaffold sets\n`{ memory: 256, timeout: 30 }`:\n\n```ts\nconst config: LaranjaConfig = {\n name: \"my-api\",\n projectId: \"proj_…\",\n compute: { memory: 512, timeout: 20 },\n};\n```\n\n### `resources`\n\nPer-resource overrides, keyed by **resource id** — `http` for the proxy, or the\n[`id`](./decorators-and-markers.md#cron) of a cron/queue. Each entry\nmerges field-by-field on top of `compute`, and queue/cron entries also accept\ntheir kind-specific knobs (e.g. a queue's `visibilityTimeout`). An unknown id is\na hard error, so a typo can't silently no-op:\n\n```ts\nconst config: LaranjaConfig = {\n name: \"my-api\",\n projectId: \"proj_…\",\n compute: { memory: 256, timeout: 30 },\n resources: {\n http: { memory: 512 }, // beefier proxy\n cleanup: { timeout: 60 }, // a slow cron by its id\n emails: { visibilityTimeout: 180 }, // a queue by its name/id\n },\n};\n```\n\n### Workers-only deployments\n\nTo deploy only your workers (`@Cron` / `@Queue`) with no HTTP app — for teams\nwhose API is hosted elsewhere — simply don't add an `http()` marker. No flag is\nneeded. See [workers-only deployments](../guides/http-apps.md#workers-only-deployments).\n\n## Related\n\n- [Environment variables](../guides/environment-variables.md)\n- [Stages & environments](../guides/stages-and-environments.md)\n"},{"slug":"reference/decorators-and-markers","section":"reference","title":"Decorators & markers","description":"API reference for @Cron, @Queue, cron, queue, getQueue, and http.","order":3,"body":"\n# Decorators & markers\n\nAll of these are imported from `@alzulejos/laranja-decorators`. They are **static markers**\n— the [scanner](../getting-started/how-it-works.md#1-scan) reads them at build time to\nshape your infrastructure. At runtime they are near-no-ops (they don't wrap or\nintercept your functions), so they're safe to leave in place.\n\n```bash\nnpm install @alzulejos/laranja-decorators\n```\n\nThe schedule builders [`rate`](../guides/schedules.md#ratevalue-unit) and\n[`every`](../guides/schedules.md#everyunit) are re-exported here too, so you can\nimport them alongside `@Cron`. For `@nestjs/schedule` compatibility, the\n`CronExpression` enum, `@Interval`, and `@Timeout` are re-exported as well — so a\nNest app can repoint its import at `@alzulejos/laranja-decorators` unchanged.\n\n---\n\n## `@Cron`\n\nSchedules a class method. Each `@Cron` becomes [its own Lambda + EventBridge\nrule](./what-gets-deployed.md#cron--lambda--eventbridge-rule).\n\n```ts\nfunction Cron(schedule: ScheduleInput): MethodDecorator\nfunction Cron(options: CronOptions): MethodDecorator\nfunction Cron(expression: string, options?: NestCronOptions): MethodDecorator // @nestjs/schedule form\n```\n\n```ts\nimport { Cron, rate, CronExpression } from \"@alzulejos/laranja-decorators\";\n\nexport class Jobs {\n @Cron(rate(5, \"minutes\"))\n async refreshCache() {}\n\n @Cron({ schedule: \"cron(0 12 * * ? *)\", id: \"daily-report\" })\n async report() {}\n\n // @nestjs/schedule style — a node-cron string or CronExpression, translated for you\n @Cron(\"0 3 * * *\", { name: \"nightly\", timeZone: \"Europe/Lisbon\" })\n async nightly() {}\n\n @Cron(CronExpression.EVERY_30_MINUTES)\n async sweep() {}\n}\n```\n\n**`CronOptions`** (laranja form)\n\n| Field | Type | Description |\n|---|---|---|\n| `schedule` | `ScheduleInput` | A [`rate()`/`every()`](../guides/schedules.md) result, a `Schedule`, or a raw string. |\n| `id` | `string` _(optional)_ | Stable logical id. Defaults to `‹Class›-‹method›`; also drives the Lambda name. |\n\n**`NestCronOptions`** (the second argument in the `@nestjs/schedule` form)\n\n| Field | Type | Description |\n|---|---|---|\n| `name` | `string` _(optional)_ | Used as the resource `id`. |\n| `timeZone` | `string` _(optional)_ | IANA timezone the schedule is evaluated in. |\n\nSee [Schedules → node-cron expressions](../guides/schedules.md#node-cron-expressions-nestjsschedule-compatibility)\nfor the accepted syntax and what's rejected. Nest providers resolve through DI —\ndeclare your module with [`workers()`](#workers).\n\n---\n\n## `cron()` marker\n\nFunction-style counterpart to `@Cron`, for codebases that don't use classes.\nRegisters a standalone exported function on a schedule.\n\n```ts\nfunction cron(schedule: ScheduleInput, handler: JobHandler): void\nfunction cron(options: CronOptions, handler: JobHandler): void\n```\n\n```ts\nimport { cron, rate } from \"@alzulejos/laranja-decorators\";\n\nexport async function refreshCache() {}\n\ncron(rate(5, \"minutes\"), refreshCache);\ncron({ schedule: rate(1, \"hour\"), id: \"hourly-sync\" }, refreshCache);\n```\n\nThe function's name becomes the resource id unless you pass an explicit `id`.\n\n---\n\n## `@Interval`\n\n`@nestjs/schedule`-compatible. Runs a method every _N_ milliseconds; laranja\nlowers it to a `rate(...)`, so the interval must be a whole number of minutes\n(EventBridge's 1-minute floor).\n\n```ts\nfunction Interval(milliseconds: number): MethodDecorator\nfunction Interval(name: string, milliseconds: number): MethodDecorator\n```\n\n```ts\nimport { Interval } from \"@alzulejos/laranja-decorators\";\n\nexport class Jobs {\n @Interval(300000) // every 5 minutes\n async poll() {}\n}\n```\n\n---\n\n## `@Timeout`\n\nRe-exported for `@nestjs/schedule` source compatibility, but a one-shot timer\nrelative to process start has no serverless equivalent — laranja **rejects it at\nbuild time** with a clear message. Use [`@Cron`](#cron) or [`@Interval`](#interval)\ninstead.\n\n---\n\n## `@Queue`\n\nConsumes messages from an SQS queue. Each `@Queue` becomes [an SQS queue +\nconsumer Lambda](./what-gets-deployed.md#queue--sqs-queue--consumer-lambda).\nThe handler is called once per message with the JSON-parsed body.\n\n```ts\nfunction Queue(options: QueueOptions): MethodDecorator\n```\n\n```ts\nimport { Queue } from \"@alzulejos/laranja-decorators\";\n\nexport class Workers {\n @Queue({ name: \"emails\", batchSize: 10 })\n async sendEmail(body: unknown) {}\n\n @Queue({ name: \"orders.fifo\", fifo: true })\n async processOrder(body: unknown) {}\n}\n```\n\n**`QueueOptions`**\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `name` | `string` | _required_ | Queue name. A `.fifo` suffix marks a FIFO queue. |\n| `batchSize` | `number` | `10` | Max messages per consumer invocation. |\n| `fifo` | `boolean` | `false` | Force a FIFO queue (or end `name` with `.fifo`). When set, laranja appends `.fifo` to `name` if you left it off. |\n\n---\n\n## `queue()` marker\n\nFunction-style counterpart to `@Queue`.\n\n```ts\nfunction queue(options: QueueOptions, handler: JobHandler): void\n```\n\n```ts\nimport { queue } from \"@alzulejos/laranja-decorators\";\n\nexport async function sendEmail(body: unknown) {}\n\nqueue({ name: \"emails\", batchSize: 10 }, sendEmail);\n```\n\n---\n\n## `getQueue()`\n\nThe queue **producer** — get a handle to a declared queue and `.send()` messages\nto it. The counterpart to the [`@Queue`](#queue) / [`queue()`](#queue-marker)\nconsumers. Unlike the markers, this does real work at runtime (a single SQS\n`SendMessage`); laranja injects the queue URL and grants `sqs:SendMessage` to\nevery function, so there's nothing to configure.\n\n```ts\nfunction getQueue(name: string): LaranjaQueue\n\ninterface LaranjaQueue {\n readonly url: string;\n send(payload: unknown, options?: SendOptions): Promise<{ messageId?: string }>;\n}\n```\n\n```ts\nimport { getQueue } from \"@alzulejos/laranja-decorators\";\n\nawait getQueue(\"emails\").send({ to, subject });\nawait getQueue(\"orders.fifo\").send(order, { groupId: order.customerId });\n```\n\n`payload` is JSON-serialized (strings are sent as-is). `name` is the queue's\ndeclared `name`; a send to an undeclared queue throws.\n\n**`SendOptions`**\n\n| Field | Type | Applies to | Description |\n|---|---|---|---|\n| `groupId` | `string` | FIFO (**required**) | `MessageGroupId` — orders messages within a group. A FIFO send throws without it. |\n| `dedupId` | `string` | FIFO | `MessageDeduplicationId` — only needed when content-based dedup is off. |\n| `delaySeconds` | `number` | Standard | Delay (0–900s) before the message becomes visible. Ignored by FIFO. |\n\nSee [Queues → Sending messages](../guides/queues.md#sending-messages).\n\n---\n\n## `http()`\n\nMarks the HTTP app (the proxy target) in code — the only way to declare one.\nExport the result so the scanner can find it.\n\n```ts\nfunction http<T>(app: T): T\n```\n\n```ts\nimport express from \"express\";\nimport { http } from \"@alzulejos/laranja-decorators\";\n\nconst app = express();\nexport default http(app); // or: export const api = http(app);\n```\n\nIt returns its argument untouched — purely a static marker. Omit it for a\n[workers-only](../guides/http-apps.md#workers-only-deployments) deployment.\n\nFor **NestJS**, wrap your async `bootstrap` factory (which `return`s the app)\ninstead of an app instance — see [HTTP apps → NestJS](../guides/http-apps.md#nestjs):\n\n```ts\nexport default http(bootstrap); // bootstrap: () => Promise<INestApplication>\n```\n\n---\n\n## `workers()`\n\n**NestJS only.** Declares the module laranja builds a dependency-injection\ncontext from, so class-based [`@Cron`](#cron) / [`@Queue`](#queue) providers\nresolve their injected dependencies at runtime (via\n`NestFactory.createApplicationContext`) instead of a bare `new`. The DI\ncounterpart to [`http()`](#http); export it so the scanner can find it.\n\n```ts\nfunction workers<T>(module: T): T\n```\n\n```ts\nimport { workers } from \"@alzulejos/laranja-decorators\";\nimport { AppModule } from \"./app.module\";\n\nexport default workers(AppModule); // or: export const jobs = workers(AppModule);\n```\n\nPass `AppModule` for the whole graph, or a leaner module for a smaller cold\nstart. There's exactly one per project. Required when a Nest project has\nclass-based workers; standalone [`cron()`](#cron-marker)/[`queue()`](#queue-marker)\nfunctions don't need it (no DI). Returns its argument untouched — a static marker.\n\n---\n\n## `env()`\n\nDeclares an environment variable your code needs. At runtime it just returns\n`process.env[name]`; laranja discovers each call and populates that variable on\nevery deployed function, with the value supplied from your shell or CI at deploy\ntime.\n\n```ts\nfunction env(name: string): string | undefined\n```\n\n```ts\nimport { env } from \"@alzulejos/laranja-decorators\";\n\nconst dbUrl = env(\"DATABASE_URL\");\n```\n\nThe name must be a **string literal** so it can be found statically. See\n[environment variables](../guides/environment-variables.md#values-from-your-environment--env)\nfor supplying values, the `--strict` flag, and per-stage usage.\n\n---\n\n## Types\n\n| Type | Description |\n|---|---|\n| `ScheduleInput` | `Schedule \\| string` — anything accepted where a schedule is expected. |\n| `Schedule` | Provider-neutral schedule: `{ kind: \"rate\", value, unit }` or `{ kind: \"cron\", expression, dialect }`. |\n| `RateUnit` | `\"minute\" \\| \"minutes\" \\| \"hour\" \\| \"hours\" \\| \"day\" \\| \"days\"`. |\n| `CronExpression` | Enum of common cron strings, mirrored from `@nestjs/schedule`. |\n| `JobHandler` | `(...args) => unknown \\| Promise<unknown>` — a `cron()`/`queue()` handler. |\n\n## Related\n\n- [Cron jobs](../guides/cron-jobs.md) · [Queues](../guides/queues.md) · [Schedules](../guides/schedules.md)\n- [HTTP apps](../guides/http-apps.md)\n"},{"slug":"reference/what-gets-deployed","section":"reference","title":"What gets deployed","description":"The exact AWS resources laranja creates and how they're named.","order":4,"body":"\n# What gets deployed\n\nlaranja maps each thing it finds in your code to a small, predictable set of AWS\nresources — all in your own account.\n\n## HTTP app → proxy Lambda + Function URL\n\nYour entire Express app is deployed as **one Lambda function** fronted by a\n**Lambda Function URL**. There is no API Gateway.\n\n- All routes are served by this single proxy Lambda.\n- The Function URL is **public** (`authType: NONE`) and CORS is open\n (`*` origins, all methods, all headers) — your app handles auth/CORS as it\n sees fit.\n- Memory and timeout come from [`compute`](./config-file.md#compute)\n in your config (the scaffold sets `{ memory: 256, timeout: 30 }`), overridable\n per-resource under the `http` key in [`resources`](./config-file.md#resources).\n- The public HTTPS URL is emitted as the `HttpUrl` stack output.\n\n```\nGET https://‹id›.lambda-url.‹region›.on.aws/\n```\n\n## `@Cron` → Lambda + EventBridge rule\n\nEach cron handler becomes **its own Lambda** plus an **EventBridge rule** that\ninvokes it on schedule.\n\n- Memory and timeout come from [`compute`](./config-file.md#compute)\n (default `{ memory: 256, timeout: 30 }`), overridable per cron id in\n [`resources`](./config-file.md#resources).\n- The schedule comes from your [`rate()`/`every()`](../guides/schedules.md)\n builder or raw expression.\n\n## `@Queue` → SQS queue + consumer Lambda\n\nEach queue handler becomes an **SQS queue** and a **consumer Lambda** wired to it.\n\n- Encryption: **SQS-managed (SSE-SQS)**.\n- **FIFO** queues are created when the name ends in `.fifo` or `fifo: true` is\n set (content-based deduplication is enabled for FIFO).\n- Default **batch size**: 10. Your handler is invoked per message with the\n JSON-parsed body.\n- **Partial-batch failures** are enabled: throwing for one message fails only\n that message; the rest of the batch still succeeds.\n- Consumer memory/timeout come from [`compute`](./config-file.md#compute)\n (default `{ memory: 256, timeout: 30 }`). The queue's **visibility timeout** is\n derived to satisfy AWS's rule that it be ≥ the consumer timeout, and can be set\n explicitly per queue via [`resources`](./config-file.md#resources).\n- The queue URL is emitted as a stack output.\n\n## Naming\n\nEverything is named deterministically — no random suffixes:\n\n| Resource | Pattern | Example |\n|---|---|---|\n| CloudFormation stack | `‹name›-‹stage›` | `my-api-prod` |\n| Lambda functions | `‹name›-‹fn›-‹stage›` | `my-api-app-prod`, `my-api-sendEmail-prod` |\n\n`‹fn›` is the handler's name (the method/function name), or the explicit `id` you\nset on `@Cron`/`@Queue`. The HTTP proxy uses `app`. Names are truncated to AWS's\n64-character limit and sanitized to allowed characters.\n\nBecause the stage is part of the names, multiple stages can live in one account\nwithout colliding — see [Stages & environments](../guides/stages-and-environments.md).\n\n## Supporting resources\n\nEach Lambda gets an **IAM execution role** (and the queue consumers get the\npermissions to read their queue / EventBridge to invoke crons). The first deploy\nto an account/region also creates the one-time CDK **bootstrap** resources (an S3\nasset bucket and roles).\n\n## Related\n\n- [How it works](../getting-started/how-it-works.md)\n- [HTTP apps](../guides/http-apps.md) · [Cron jobs](../guides/cron-jobs.md) · [Queues](../guides/queues.md)\n"}]}];
6
+ export const orderedPages = [{"slug":"","section":"","title":"Documentation","description":"Code-first deploys for Node.js apps to your own AWS account.","order":0,"body":"\n# laranja docs\n\n**laranja** deploys your Node.js app to your own AWS account from your code — no\nYAML, no console clicking, no separate infrastructure project. You write an\nExpress app plus a few functions or decorators; laranja reads the code, figures\nout the infrastructure, and ships it.\n\n```bash\nnpm install -D @alzulejos/laranja\nnpx laranja deploy\n```\n\n## Start here\n\n- **[Introduction](./getting-started/introduction.md)** — what laranja is and the ideas behind it.\n- **[Installation](./getting-started/installation.md)** — prerequisites and setup.\n- **[Quickstart](./getting-started/quickstart.md)** — from zero to a live URL.\n- **[How it works](./getting-started/how-it-works.md)** — how your code becomes a running app on AWS.\n\n## Guides\n\n- **[HTTP apps](./guides/http-apps.md)** — deploy your app behind a public URL with the `http()` marker.\n- **[Cron jobs](./guides/cron-jobs.md)** — scheduled functions with `@Cron` / `cron()`.\n- **[Queues](./guides/queues.md)** — SQS consumers with `@Queue` / `queue()`.\n- **[Schedules](./guides/schedules.md)** — the `rate()` / `every()` builders and raw expressions.\n- **[Environment variables](./guides/environment-variables.md)** — `env`, `STAGE`, and resolution.\n- **[Stages & environments](./guides/stages-and-environments.md)** — dev / staging / prod with one codebase.\n\n## Reference\n\n- **[CLI commands](./reference/commands.md)** — `init`, `logout`, `plan`, `deploy`, `destroy`, `logs`, `eject`.\n- **[Config file](./reference/config-file.md)** — every field in `laranja.config.ts`.\n- **[Decorators & markers](./reference/decorators-and-markers.md)** — `@Cron`, `@Queue`, `cron`, `queue`, `http`, `env`.\n- **[What gets deployed](./reference/what-gets-deployed.md)** — the AWS resources and how they're named.\n\n> **Status:** v1 targets **AWS** with **Express** today; **NestJS support is\n> coming**. The internal model is provider- and framework-neutral, so new clouds\n> and frameworks land without changing your app code.\n"},{"slug":"getting-started/introduction","section":"getting-started","title":"Introduction","description":"What laranja is, the problem it solves, and the principles behind it.","order":1,"body":"\n# Introduction\n\nlaranja is a **code-first deploy tool** for Node.js apps. You write your\napplication — an Express app, some scheduled jobs, some queue consumers — and\nlaranja deploys it to **your own AWS account**. There is no infrastructure\nproject to maintain, no YAML to write, and no cloud console to click through.\n\n> laranja supports **Express** and **NestJS** — and because your app is reduced to\n> a framework-neutral description internally, more frameworks can follow without\n> changing how you write your code.\n\n## The problem\n\nShipping a small Node service to AWS usually means choosing between:\n\n- **A platform** (managed hosting) — fast, but you don't own the infrastructure\n and you pay a markup to run in someone else's account.\n- **Infrastructure-as-code** (CDK / Terraform / CloudFormation) — you own\n everything, but now you maintain a second codebase that drifts from the app it\n describes.\n\nlaranja takes a third path: **your application code _is_ the source of truth for\nthe infrastructure.** A route is an HTTP endpoint. A `@Cron` method is a\nscheduled function. A `@Queue` method is an SQS consumer. laranja reads those\nfacts out of your code and provisions exactly what they imply.\n\n## How it feels\n\n```ts\n// src/app.ts — mark your HTTP app, code-first\nimport express from \"express\";\nimport { http } from \"@alzulejos/laranja-decorators\";\n\nconst app = express();\napp.get(\"/health\", (_req, res) => res.json({ ok: true }));\n\nexport default http(app);\n```\n\nJobs and queues come in two styles — plain **functions** or **classes** with\ndecorators. Use whichever you prefer:\n\n```ts tab=\"Function\"\n// src/jobs.ts\nimport { cron, rate } from \"@alzulejos/laranja-decorators\";\n\nexport async function refreshCache() { /* ... */ }\ncron(rate(5, \"minutes\"), refreshCache);\n```\n\n```ts tab=\"Class\"\n// src/jobs.ts\nimport { Cron, rate } from \"@alzulejos/laranja-decorators\";\n\nexport class Jobs {\n @Cron(rate(5, \"minutes\"))\n async refreshCache() { /* ... */ }\n}\n```\n\n```bash\nnpx laranja deploy\n```\n\nYou get a live HTTPS URL, an EventBridge schedule, and an SQS queue with a\nconsumer — all in your own account, named deterministically.\n\n## Principles\n\n- **Code is the source of truth.** Infrastructure is _derived_ from the app, not\n declared alongside it. There's nothing to keep in sync.\n- **Your account, your resources.** laranja deploys with your AWS credentials\n into your account. You own every resource and can inspect it in the console.\n- **Deterministic, no magic names.** Resources are named `‹app›-‹fn›-‹stage›` —\n predictable and greppable, with no random suffixes.\n- **Provider-neutral by design.** AWS is the first target, but laranja is built\n so other clouds can follow without changing your app code.\n- **An escape hatch when you need it.** Outgrow the abstraction? `laranja eject`\n hands you a real, owned CDK project (see [eject](../reference/commands.md#eject)).\n\n## What's in scope (v1)\n\n| Capability | How you declare it |\n|---|---|\n| HTTP API | An Express or NestJS app, marked with the [`http()`](../reference/decorators-and-markers.md#http) marker. |\n| Scheduled jobs | [`@Cron`](../reference/decorators-and-markers.md#cron) (class) or [`cron()`](../reference/decorators-and-markers.md#cron-marker) (function) |\n| Queue consumers | [`@Queue`](../reference/decorators-and-markers.md#queue) (class) or [`queue()`](../reference/decorators-and-markers.md#queue-marker) (function) |\n| Per-environment deploys | [Stages](../guides/stages-and-environments.md) (`--stage`) |\n| Env vars | [`env`](../guides/environment-variables.md) in config |\n\nNext: **[Installation](./installation.md)**.\n"},{"slug":"getting-started/installation","section":"getting-started","title":"Installation","description":"Prerequisites and how to add laranja to a project.","order":2,"body":"\n# Installation\n\n## Prerequisites\n\n- **Node.js 20 or newer.** Deployed Lambdas run on the Node.js 20 runtime, and\n the CLI targets the same.\n- **A laranja account + API key.** laranja synthesizes your deployment template on\n its server, so `plan`, `deploy`, and `eject` need an API key (created in the\n dashboard) and a project to link to. You connect both by running\n [`laranja init`](./quickstart.md#3-sign-in-and-configure) — see the Quickstart.\n- **An AWS account** plus credentials on the standard AWS chain — any of:\n - `aws configure` (a shared credentials file),\n - AWS SSO (`aws sso login`),\n - environment variables (`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN`),\n - a named profile (set [`profile`](../reference/config-file.md) in config).\n- **A region**, from `region` in your config or the `AWS_REGION` /\n `AWS_DEFAULT_REGION` environment variable.\n\nYou do **not** need to install the AWS CDK or the AWS CLI separately — the CDK\ntoolkit is embedded in laranja.\n\n## Install\n\nAdd the CLI as a dev dependency:\n\n```bash\nnpm install -D @alzulejos/laranja\n```\n\nIf you use decorators or function markers for jobs and queues, also install:\n\n```bash\nnpm install @alzulejos/laranja-decorators\n```\n\n> `@alzulejos/laranja-decorators` is a regular dependency (not dev-only) because your\n> application imports `@Cron`, `@Queue`, `rate`, etc. at runtime.\n\n## First-time AWS setup (bootstrap)\n\nThe first time you deploy to a given account + region, laranja runs a one-time\n**bootstrap** that creates a small set of shared resources in _your_ account (an\nS3 asset bucket and a few IAM roles). `laranja deploy` detects this and prompts\nyou before doing it — see [deploy](../reference/commands.md#deploy).\n\n## Verify\n\n```bash\nnpx laranja --help\n```\n\nYou're ready for the **[Quickstart](./quickstart.md)**.\n"},{"slug":"getting-started/quickstart","section":"getting-started","title":"Quickstart","description":"From an empty folder to a live HTTPS endpoint in a few minutes.","order":3,"body":"\n# Quickstart\n\nThis walks you from zero to a deployed app with an HTTP endpoint, a scheduled\njob, and a queue consumer.\n\n## 1. Create a project\n\n```bash\nmkdir my-api && cd my-api\nnpm init -y\nnpm install express\nnpm install -D @alzulejos/laranja typescript\nnpm install @alzulejos/laranja-decorators\n```\n\n> laranja supports **Express** and **NestJS**. For Nest, see the\n> [HTTP apps guide](../guides/http-apps.md#nestjs).\n\n## 2. Write your app\n\nMark your app with [`http()`](../reference/decorators-and-markers.md#http) and\nexport it. laranja finds it by reading your code — so there's nothing to wire up\nin config:\n\n```ts\n// src/app.ts\nimport express from \"express\";\nimport { http } from \"@alzulejos/laranja-decorators\";\n\nconst app = express();\napp.get(\"/\", (_req, res) => res.json({ ok: true, stage: process.env.STAGE }));\napp.get(\"/users/:id\", (req, res) => res.json({ id: req.params.id }));\n\nexport default http(app);\n```\n\nAdd a scheduled job and a queue consumer (optional). There are two styles — pick\nwhichever fits your codebase.\n\n```ts tab=\"Function\"\n// src/jobs.ts\nimport { cron, queue, rate } from \"@alzulejos/laranja-decorators\";\n\nexport async function refreshCache() {\n console.log(\"refreshing cache…\");\n}\ncron(rate(5, \"minutes\"), refreshCache);\n\nexport async function sendEmail(body: unknown) {\n console.log(\"sending\", body);\n}\nqueue({ name: \"emails\", batchSize: 10 }, sendEmail);\n```\n\n```ts tab=\"Class\"\n// src/jobs.ts\nimport { Cron, Queue, rate } from \"@alzulejos/laranja-decorators\";\n\nexport class Jobs {\n @Cron(rate(5, \"minutes\"))\n async refreshCache() {}\n\n @Queue({ name: \"emails\", batchSize: 10 })\n async sendEmail(body: unknown) {}\n}\n```\n\n## 3. Sign in and configure\n\nRun the scaffolder. It prompts for your **laranja API key** (from the dashboard),\nvalidates it, stores it in `~/.laranja/auth.json`, and lets you pick or create a\n**dashboard project** — filling in `name` and `projectId` for you:\n\n```bash\nnpx laranja init\n```\n\nThe generated `laranja.config.ts` looks like this (edit `region`, `env`, and\n`compute` to taste):\n\n```ts\n// laranja.config.ts\nimport type { LaranjaConfig } from \"@alzulejos/laranja-decorators\";\n\nconst config: LaranjaConfig = {\n name: \"my-api\",\n // From your laranja dashboard — identifies this project on the server.\n projectId: \"proj_…\",\n region: \"us-east-1\",\n env: { LOG_LEVEL: \"info\" },\n // Default compute for every function (the HTTP proxy + each cron/queue).\n compute: { memory: 256, timeout: 30 },\n};\n\nexport default config;\n```\n\nBecause the app is marked with `http()` in code, the config stays minimal — the\nHTTP app is declared there, not in config. See the\n**[config reference](../reference/config-file.md)**.\n\n## 4. Preview the plan\n\n`plan` shows what a deploy would do: it synthesizes your template on the server,\ndiffs it against what's deployed in your AWS account, and tags each resource\n**created / changed / unchanged**. It's **read-only** — nothing is applied, and it\nnever counts against your deploy limit.\n\n```bash\nnpx laranja plan\n```\n\n```\nPlan for \"my-api-dev\"\n\n+ http HTTP 2 routes → proxy Lambda + Function URL\n+ daily Cron Lambda + EventBridge rule\n+ emails Queue SQS + consumer Lambda\n\n8 AWS resources +8 created =0 unchanged\n```\n\nOn this first run nothing is deployed yet, so everything shows as `+` created.\n\n## 5. Deploy\n\n```bash\nnpx laranja deploy\n```\n\nThe first deploy to a new account/region prompts you to **bootstrap** (a\none-time setup in your account). When it finishes you'll see your live URL:\n\n```\n🌐 http https://abc123.lambda-url.us-east-1.on.aws/\n```\n\nHit it:\n\n```bash\ncurl https://abc123.lambda-url.us-east-1.on.aws/\n# {\"ok\":true,\"stage\":\"dev\"}\n```\n\n## 6. Iterate\n\n```bash\nnpx laranja logs # tail CloudWatch logs (pick a function)\nnpx laranja plan # see what a deploy would change\nnpx laranja deploy # ship again\nnpx laranja destroy # tear it all down\n```\n\n## Next steps\n\n- Ship to multiple environments: **[Stages & environments](../guides/stages-and-environments.md)**.\n- Understand what was created: **[What gets deployed](../reference/what-gets-deployed.md)**.\n- Go deeper on jobs and queues: **[Cron jobs](../guides/cron-jobs.md)**, **[Queues](../guides/queues.md)**.\n"},{"slug":"getting-started/how-it-works","section":"getting-started","title":"How it works","description":"How laranja turns your code into a running app on AWS.","order":4,"body":"\n# How it works\n\nYou write your app; laranja deploys it to your own AWS account. Two things are\nworth knowing about how it does that.\n\n## It reads your code — it never runs it\n\nlaranja discovers your infrastructure by **reading** your source: your HTTP app\nand its routes, your `@Cron` / `cron()` jobs and their schedules, your\n`@Queue` / `queue()` consumers, and the env vars you wrap with `env()`. It does\nthis without executing your code, so planning a deploy is always safe — nothing\nof yours runs just to figure out what to deploy.\n\nThat's also why a few things must be written so laranja can see them: schedules\nuse literal builders like `rate(5, \"minutes\")`, and `env(\"…\")` takes a string\nliteral.\n\n## It deploys into your AWS account\n\nlaranja turns what it found into AWS resources — a Lambda for your app, an\nEventBridge rule per cron, an SQS queue per consumer — and deploys them with\n**your** credentials into **your** account. See\n[what gets deployed](../reference/what-gets-deployed.md) for the full mapping.\n\n- The AWS CDK toolkit is embedded, so there's nothing extra to install.\n- The first deploy to a new account/region runs a one-time **bootstrap**.\n- [`plan`](../reference/commands.md#plan) previews what a deploy would change\n (created/changed/unchanged); [`destroy`](../reference/commands.md#destroy)\n tears the stack down.\n- Outputs (your HTTPS URL, queue URLs) are printed when the deploy finishes.\n\n## The template is built on the server — your code stays local\n\n[`plan`](../reference/commands.md#plan) and [`deploy`](../reference/commands.md#deploy)\nsynthesize the deployment template on the **laranja server**. laranja scans and\nbundles your code locally, then sends only a **description** of your\ninfrastructure (the internal model plus the asset hashes of your bundles) — your\nsource code and bundles never leave your machine. The returned template is then\napplied to AWS with your **own** credentials. This is why these commands need a\n`LARANJA_API_KEY` and a `projectId` (run [`laranja init`](../reference/commands.md#init)\nonce to set both up).\n\n## Related\n\n- [What gets deployed](../reference/what-gets-deployed.md)\n- [Stages & environments](../guides/stages-and-environments.md)\n"},{"slug":"guides/http-apps","section":"guides","title":"HTTP apps","description":"Deploy your HTTP app behind a public HTTPS endpoint.","order":1,"body":"\n# HTTP apps\n\nlaranja deploys your whole HTTP app as a single proxy Lambda behind a public\n[Function URL](../reference/what-gets-deployed.md#http-app--proxy-lambda--function-url).\nlaranja supports **Express** and **NestJS**.\n\n## Declaring your app (the `http()` marker)\n\nThe code-first way: mark your app with the\n[`http()`](../reference/decorators-and-markers.md#http) marker and export it.\nlaranja finds it by scanning your code — there's nothing to configure.\n\n```ts\n// src/app.ts\nimport express from \"express\";\nimport { http } from \"@alzulejos/laranja-decorators\";\n\nconst app = express();\napp.use(express.json());\n\napp.get(\"/\", (_req, res) => res.json({ ok: true }));\napp.post(\"/users\", (req, res) => res.status(201).json(req.body));\n\nexport default http(app); // or: export const api = http(app);\n```\n\n`http()` returns the app untouched — it's a static marker, not a wrapper, so it\nhas no runtime effect. That's all you need: every route you register is served by\nthe deployed proxy. The marker is the only way to declare an HTTP app — there's\nexactly one per project, and it must be exported so the scanner can find it.\n\n## NestJS\n\nNest apps work the same way, with one difference: a Nest app only exists after an\nasync `NestFactory.create(...)`, so instead of a ready app object you wrap your\n**bootstrap function** and have it `return` the app:\n\n```ts\n// src/main.ts\nimport { NestFactory } from \"@nestjs/core\";\nimport { http } from \"@alzulejos/laranja-decorators\";\nimport { AppModule } from \"./app.module\";\n\nexport async function bootstrap() {\n const app = await NestFactory.create(AppModule);\n // configure however you like — pipes, guards, middleware, raw body, cookies…\n app.useGlobalPipes(new ValidationPipe({ transform: true, whitelist: true }));\n await app.listen(process.env.PORT ?? 3000); // fine to keep for local dev\n return app; // ← the only change laranja needs\n}\n\n// Run locally with `npm run start`; skipped when laranja imports this file.\nif (require.main === module) void bootstrap();\n\nexport default http(bootstrap); // wrap the factory, not a module\n```\n\nlaranja runs your `bootstrap()` verbatim, so every pipe, guard, and piece of\nmiddleware you configure is preserved — nothing is re-derived. You keep your\nnormal Nest project (`@nestjs/platform-express`); no laranja-specific\nrestructuring.\n\nTwo things to know:\n\n- **Build before you deploy.** laranja packages your compiled output (`nest build`\n → `dist/`), because Nest's dependency injection relies on the decorator metadata\n your own TypeScript build emits. laranja deploys what you build — it doesn't run\n your build for you — so run `nest build` yourself after every code change.\n Deploying without a `dist/` fails with a clear message, but a **stale** `dist/`\n (source edited since your last build) deploys silently as outdated code, so make\n the build part of your deploy step (e.g. `nest build && laranja deploy`).\n- **Use the default Express platform.** The Fastify adapter isn't supported yet.\n\n## Routing, middleware, and `STAGE`\n\nYour app runs as-is inside Lambda. Standard Express features work — routing,\nmiddleware, JSON bodies, route params. The active\n[stage](./stages-and-environments.md) is available as\n`process.env.STAGE`:\n\n```ts\napp.get(\"/whoami\", (_req, res) => res.json({ stage: process.env.STAGE }));\n```\n\n## CORS and auth\n\nThe Function URL is public with permissive CORS (all origins/methods/headers).\nHandle authentication and any stricter CORS rules **inside your app**, the same\nway you would anywhere else.\n\n## Compute (memory & timeout)\n\nThe HTTP proxy Lambda's memory and timeout come from\n[`compute`](../reference/config-file.md#compute) in your config — the scaffold\ndefault is `{ memory: 256, timeout: 30 }`, and you can override it under the `http`\nkey in [`resources`](../reference/config-file.md#resources). Long-running work\nbelongs in a [cron job](./cron-jobs.md) or behind a [queue](./queues.md), not a\nrequest.\n\n## Workers-only deployments\n\nIf your HTTP API is hosted elsewhere and you only want to deploy scheduled jobs\nand queue consumers, just don't add an `http()` marker — there's nothing to set\nin config:\n\n```ts\n// laranja.config.ts\nconst config: LaranjaConfig = {\n name: \"my-workers\",\n env: { LOG_LEVEL: \"info\" },\n};\n```\n\nWith no marker, only your [`@Cron`](./cron-jobs.md) / [`@Queue`](./queues.md)\nhandlers are deployed — no HTTP proxy, no Function URL.\n\nFor a **workers-only Nest** app, there's no `http(bootstrap)` to build the DI\ncontainer from, so declare your module with the\n[`workers()`](../reference/decorators-and-markers.md#workers) marker instead\n(`export default workers(AppModule)`) — see [Cron jobs → NestJS](./cron-jobs.md#nestjs).\n\n## Related\n\n- [What gets deployed](../reference/what-gets-deployed.md)\n- [Cron jobs](./cron-jobs.md) · [Queues](./queues.md)\n"},{"slug":"guides/cron-jobs","section":"guides","title":"Cron jobs","description":"Run functions on a schedule with @Cron or cron().","order":2,"body":"\n# Cron jobs\n\nA cron job is a function that runs on a schedule. Each one becomes\n[its own Lambda plus an EventBridge rule](../reference/what-gets-deployed.md#cron--lambda--eventbridge-rule).\n\n## Class style — `@Cron`\n\nDecorate a method with [`@Cron`](../reference/decorators-and-markers.md#cron) and\ngive it a [schedule](./schedules.md):\n\n```ts\nimport { Cron, rate, every } from \"@alzulejos/laranja-decorators\";\n\nexport class Jobs {\n @Cron(rate(5, \"minutes\"))\n async refreshCache() {\n // …\n }\n\n @Cron(every(\"day\"))\n async nightlyCleanup() {\n // …\n }\n\n @Cron({ schedule: \"cron(0 12 * * ? *)\", id: \"daily-report\" })\n async sendReport() {\n // …\n }\n}\n```\n\nThe handler's logical id defaults to `‹Class›-‹method›`; pass `id` to set a\nstable, explicit name (which also drives the Lambda's name).\n\n## Function style — `cron()`\n\nIf you don't use classes, register a standalone exported function with\n[`cron()`](../reference/decorators-and-markers.md#cron-marker):\n\n```ts\nimport { cron, rate } from \"@alzulejos/laranja-decorators\";\n\nexport async function refreshCache() {\n // …\n}\n\ncron(rate(5, \"minutes\"), refreshCache);\n```\n\nThe function's name becomes the resource id unless you pass an explicit `id`:\n\n```ts\ncron({ schedule: every(\"hour\"), id: \"hourly-sync\" }, refreshCache);\n```\n\n## NestJS\n\nIn a Nest app, `@Cron` goes on a normal provider — with injected dependencies —\nand you can keep the schedule syntax you already use (a\n[node-cron string or `CronExpression`](./schedules.md#node-cron-expressions-nestjsschedule-compatibility)).\nSwapping the import from `@nestjs/schedule` is usually the only change:\n\n```ts\n// tasks.service.ts\nimport { Injectable } from \"@nestjs/common\";\nimport { Cron, CronExpression } from \"@alzulejos/laranja-decorators\"; // ← was @nestjs/schedule\n\n@Injectable()\nexport class TasksService {\n constructor(private readonly reports: ReportsService) {} // real DI\n\n @Cron(CronExpression.EVERY_30_MINUTES)\n async sweep() {\n await this.reports.rebuild(); // `this.reports` is injected\n }\n}\n```\n\nBecause the method runs on a real provider, laranja resolves it through your\napp's dependency-injection container instead of `new`-ing the class. Point it at\nyour module **once** with the [`workers()`](../reference/decorators-and-markers.md#workers)\nmarker:\n\n```ts\n// src/main.ts (or a dedicated file)\nimport { workers } from \"@alzulejos/laranja-decorators\";\nimport { AppModule } from \"./app.module\";\n\nexport default workers(AppModule); // build a DI context from this module\n```\n\nPass `AppModule` for the whole graph, or a leaner module you compose if you want\na smaller cold start. Like the Nest [HTTP path](./http-apps.md#nestjs), laranja\npackages your **compiled** `dist/` output — run `nest build` before deploying so\nthe DI metadata exists.\n\n## Schedules\n\nSchedules are written with the portable `rate()` / `every()` builders, or as a\nraw expression string. See the **[Schedules reference](./schedules.md)** for the\nfull set of options.\n\n```ts\n@Cron(rate(30, \"minutes\")) // every 30 minutes\n@Cron(every(\"hour\")) // every hour (shorthand for rate(1, \"hour\"))\n@Cron({ schedule: \"cron(0 9 * * ? *)\" }) // raw AWS cron: 09:00 UTC daily\n```\n\n## Runtime behavior\n\n- Each cron runs in its **own Lambda**, isolated from your HTTP app and other\n jobs.\n- Memory and timeout come from [`compute`](../reference/config-file.md#compute)\n (default `{ memory: 256, timeout: 30 }`) and can be overridden per cron id in\n [`resources`](../reference/config-file.md#resources).\n- All [config `env`](./environment-variables.md) and `STAGE` are\n available via `process.env`.\n\n## Related\n\n- [Schedules](./schedules.md)\n- [`@Cron` / `cron()` reference](../reference/decorators-and-markers.md#cron)\n- [Queues](./queues.md)\n"},{"slug":"guides/queues","section":"guides","title":"Queues","description":"Process SQS messages with @Queue or queue(), including FIFO.","order":3,"body":"\n# Queues\n\nA queue consumer processes messages from an SQS queue. Each one becomes\n[an SQS queue plus a consumer Lambda](../reference/what-gets-deployed.md#queue--sqs-queue--consumer-lambda).\n\n## Class style — `@Queue`\n\nDecorate a method with [`@Queue`](../reference/decorators-and-markers.md#queue):\n\n```ts\nimport { Queue } from \"@alzulejos/laranja-decorators\";\n\nexport class Workers {\n @Queue({ name: \"emails\", batchSize: 10 })\n async sendEmail(body: unknown) {\n // `body` is the JSON-parsed message\n }\n}\n```\n\n## Function style — `queue()`\n\n```ts\nimport { queue } from \"@alzulejos/laranja-decorators\";\n\nexport async function sendEmail(body: unknown) {\n // …\n}\n\nqueue({ name: \"emails\", batchSize: 10 }, sendEmail);\n```\n\n## NestJS\n\n`@Queue` works on a Nest provider with injected dependencies. As with\n[cron jobs](./cron-jobs.md#nestjs), laranja resolves the consumer through your\nDI container, so declare your module once with the\n[`workers()`](../reference/decorators-and-markers.md#workers) marker\n(`export default workers(AppModule)`) and deploy your compiled `dist/` output.\nStandalone `queue()` functions don't need it.\n\n## Options\n\n| Option | Default | Description |\n|---|---|---|\n| `name` | _required_ | Queue name. A `.fifo` suffix marks a FIFO queue. |\n| `batchSize` | `10` | Max messages delivered to the consumer per invocation. |\n| `fifo` | `false` | Force a FIFO queue (or end `name` with `.fifo`). When set, laranja appends `.fifo` to `name` if you left it off. |\n\n## How messages are delivered\n\n- Your handler is invoked **once per message**, with the message **body already\n JSON-parsed**.\n- **Partial-batch failures** are enabled: if your handler throws for one message,\n only that message is retried — the rest of the batch is still acknowledged.\n- Consumer memory/timeout come from [`compute`](../reference/config-file.md#compute)\n (default `{ memory: 256, timeout: 30 }`); the queue's visibility timeout is\n derived to stay ≥ the consumer timeout (override it per queue via\n [`resources`](../reference/config-file.md#resources)).\n\n```ts\n@Queue({ name: \"orders\" })\nasync processOrder(body: unknown) {\n const order = body as { id: string };\n if (!order.id) throw new Error(\"bad message\"); // only THIS message is retried\n // …\n}\n```\n\n## FIFO queues\n\nEnd the name with `.fifo` (or set `fifo: true`) for ordered, exactly-once\nprocessing. Content-based deduplication is enabled automatically:\n\n```ts\n@Queue({ name: \"orders.fifo\", fifo: true })\nasync processOrder(body: unknown) {\n // …\n}\n```\n\nAWS requires FIFO queue names to end in `.fifo`. If you set `fifo: true` but\nleave the suffix off, laranja appends it for you — so `{ name: \"orders\", fifo: true }`\ndeploys a queue named `orders.fifo`. The normalized name is what appears in\n`laranja plan` and in the AWS console, so there's no surprise at deploy time.\n\n## Sending messages\n\nConsuming is only half the loop — to **produce** a message, call\n[`getQueue`](../reference/decorators-and-markers.md#getqueue) with the queue's\n`name` and `.send()` a payload:\n\n```ts\nimport { getQueue } from \"@alzulejos/laranja-decorators\";\n\napp.post(\"/signup\", async (req, res) => {\n await getQueue(\"emails\").send({ to: req.body.email, template: \"welcome\" });\n res.sendStatus(202);\n});\n```\n\nObjects are JSON-serialized for you (strings are sent as-is), so the consumer\nreceives them already parsed — `getQueue(\"emails\").send({ to })` on one end,\n`async sendEmail(body)` on the other.\n\nYou can produce from **anywhere** in a deployed app — an HTTP route, a\n[cron job](./cron-jobs.md), or another queue's consumer fanning out. laranja\ninjects each queue's URL into every function's environment at deploy and grants\n`sqs:SendMessage`, so there's no client to configure, no URL to look up, and no\nIAM to wire. It's a thin wrapper over one SQS `SendMessage` call — laranja\nprovisions the infrastructure; it deliberately does **not** add a job framework\n(retries, scheduling, and job state stay with SQS and your consumer).\n\n### FIFO and options\n\n`.send()` takes a second options argument:\n\n| Option | Applies to | Description |\n|---|---|---|\n| `groupId` | FIFO (**required**) | `MessageGroupId` — messages with the same group are ordered. |\n| `dedupId` | FIFO | `MessageDeduplicationId` — only needed when content-based dedup is off. |\n| `delaySeconds` | Standard | Delay (0–900s) before the message becomes visible. Ignored by FIFO. |\n\n```ts\n// FIFO queues require a groupId — the send throws without one.\nawait getQueue(\"orders.fifo\").send(order, { groupId: order.customerId });\n```\n\n> Prefer the raw SDK? The queue URL is also emitted as a stack output after\n> deploy and visible in the AWS console — send with `@aws-sdk/client-sqs`\n> directly if you'd rather.\n\n## Related\n\n- [`@Queue` / `queue()` reference](../reference/decorators-and-markers.md#queue)\n- [What gets deployed](../reference/what-gets-deployed.md#queue--sqs-queue--consumer-lambda)\n- [Cron jobs](./cron-jobs.md)\n"},{"slug":"guides/schedules","section":"guides","title":"Schedules","description":"The rate() and every() builders, and raw schedule expressions.","order":4,"body":"\n# Schedules\n\nSchedules drive [cron jobs](./cron-jobs.md). laranja stores them in a\n**provider-neutral** form, so prefer the builders — they're portable across\nclouds. A raw expression string is available as an escape hatch.\n\n## `rate(value, unit)`\n\n\"Every N units.\" Portable everywhere.\n\n```ts\nimport { rate } from \"@alzulejos/laranja-decorators\";\n\nrate(5, \"minutes\") // every 5 minutes\nrate(1, \"hour\") // every hour\nrate(2, \"days\") // every 2 days\n```\n\n- `value` must be a **positive integer** (≥ 1).\n- `unit` is one of `\"minute\"`, `\"minutes\"`, `\"hour\"`, `\"hours\"`, `\"day\"`,\n `\"days\"` — singular or plural, your choice.\n\n## `every(unit)`\n\nShorthand for `rate(1, unit)`. Takes a singular unit:\n\n```ts\nimport { every } from \"@alzulejos/laranja-decorators\";\n\nevery(\"minute\") // = rate(1, \"minute\")\nevery(\"hour\") // = rate(1, \"hour\")\nevery(\"day\") // = rate(1, \"day\")\n```\n\n## Raw AWS expressions (escape hatch)\n\nWhen you need something the builders can't express (e.g. \"noon UTC every day\"),\npass a **wrapped** AWS schedule string — `cron(...)` or `rate(...)`:\n\n```ts\n@Cron({ schedule: \"cron(0 12 * * ? *)\", id: \"daily-report\" }) // 12:00 UTC daily\n@Cron(\"rate(5 minutes)\") // raw rate string\n```\n\nAWS cron has **six fields**: `cron(Minutes Hours Day-of-month Month Day-of-week\nYear)`.\n\nA few examples:\n\n| Expression | Meaning |\n|---|---|\n| `cron(0 12 * * ? *)` | 12:00 UTC every day |\n| `cron(0/15 * * * ? *)` | every 15 minutes |\n| `cron(0 8 ? * MON-FRI *)` | 08:00 UTC on weekdays |\n| `cron(0 0 1 * ? *)` | midnight UTC on the 1st of each month |\n\n## node-cron expressions (`@nestjs/schedule` compatibility)\n\nA **bare** (unwrapped) string is read as a standard 5- or 6-field\n[node-cron](https://github.com/kelektiv/node-cron) expression — the same syntax\n`@nestjs/schedule`'s `@Cron` takes. laranja translates it to the AWS dialect for\nyou, so a Nest app can swap the import and keep its existing schedules:\n\n```ts\n@Cron(\"0 12 * * *\") // noon every day\n@Cron(\"*/5 * * * *\") // every 5 minutes\n@Cron(\"0 0 * * 1-5\") // midnight on weekdays\n@Cron(CronExpression.EVERY_30_MINUTES) // the enum works too\n```\n\n`CronExpression` (mirrored from `@nestjs/schedule`) is re-exported from\n`@alzulejos/laranja-decorators`. Translation handles the day-of-week numbering difference\n(Unix `0`=Sun → AWS `1`=Sun) and the day-of-month/day-of-week rule for you.\n\n**What can't be translated is rejected at build time — never silently rounded:**\n\n| Input | Why it's rejected |\n|---|---|\n| `\"*/30 * * * * *\"`, `CronExpression.EVERY_30_SECONDS` | Sub-minute — EventBridge's floor is **1 minute**. |\n| A seconds field other than `0` | Second-level offsets can't be expressed. |\n| A cron constraining **both** day-of-month and day-of-week | EventBridge requires one to be `*`. |\n\n## `@Interval(ms)`\n\n`@nestjs/schedule`'s `@Interval` is supported and lowers to a `rate(...)`. The\ninterval must be a whole number of minutes (EventBridge's floor):\n\n```ts\n@Interval(300000) // every 5 minutes → rate(5, \"minutes\")\n@Interval(\"poll\", 300000) // named\n```\n\n> `@Timeout` (a one-shot timer relative to process start) has no serverless\n> equivalent and is rejected with a clear message — use `@Cron` or `@Interval`.\n\n## Where you can use them\n\nAnywhere a schedule is expected — the decorator or the function marker — accepts\na builder result, a `Schedule` object, or a raw string:\n\n```ts\n@Cron(rate(5, \"minutes\")) // builder\n@Cron(\"rate(5 minutes)\") // raw string\n@Cron({ schedule: every(\"day\"), id: \"nightly\" })// builder + explicit id\ncron(rate(1, \"hour\"), refreshCache); // function marker\n```\n\n## Related\n\n- [Cron jobs](./cron-jobs.md)\n- [`@Cron` / `cron()` reference](../reference/decorators-and-markers.md#cron)\n"},{"slug":"guides/environment-variables","section":"guides","title":"Environment variables","description":"Declare env vars in config or in code, and supply their values per stage.","order":5,"body":"\n# Environment variables\n\nEvery Lambda laranja deploys receives a set of environment variables, available\nthrough `process.env` as usual. There are two ways to declare them.\n\n## Static values in config\n\nPut plain, commit-safe values in the `env` map in your config. They're injected\ninto **every** function (HTTP proxy, cron, and queue consumers):\n\n```ts\n// laranja.config.ts\nconst config: LaranjaConfig = {\n name: \"my-api\",\n env: {\n LOG_LEVEL: \"info\",\n API_BASE_URL: \"https://api.example.com\",\n },\n};\n```\n\n```ts\n// anywhere in your app\nconst level = process.env.LOG_LEVEL; // \"info\"\n```\n\nUse this for non-sensitive configuration that's the same everywhere: log levels,\npublic URLs, feature flags.\n\n## Values from your environment — `env()`\n\nWhen a value should come from your shell or CI instead of your repo, wrap the\nvariable name with the `env()` helper where you read it:\n\n```ts\nimport { env } from \"@alzulejos/laranja-decorators\";\n\nconst dbUrl = env(\"DATABASE_URL\"); // same as process.env.DATABASE_URL at runtime\n```\n\nlaranja finds every `env(\"…\")` in your code and makes sure that variable is set\non **every** deployed function — no more filling them in by hand in the AWS\nconsole. At deploy time it reads each value from your own environment and sends\nit straight to the function; the value is never written into your repo.\n\nThe name must be a **string literal** — `env(\"DATABASE_URL\")`, not\n`env(someVariable)` — so laranja can discover it just by reading your code.\n\n### Supplying the values\n\nSet the variables in the shell or CI job you deploy from:\n\n```bash\nDATABASE_URL=postgres://… laranja deploy --stage prod\n```\n\n- **Missing a value?** By default laranja deploys anyway and warns you which ones\n were empty — a typo never blocks a deploy. Pass `--strict` to fail the deploy\n instead.\n- **Re-deploying without re-supplying a value?** The previously deployed value is\n kept, so you don't have to pass every variable on every deploy.\n\n## The `STAGE` variable\n\nlaranja always injects `STAGE`, set to the active [stage](./stages-and-environments.md)\n(`\"dev\"` by default, or whatever `--stage` resolved to). You don't declare it:\n\n```ts\napp.get(\"/\", (_req, res) => res.json({ stage: process.env.STAGE }));\n```\n\nIf you also define `STAGE` in `env`, your value wins.\n\n## Per-stage values\n\nBecause [`--stage`](./stages-and-environments.md) selects the\nenvironment at deploy time, the common pattern is one pipeline per stage, each\nsupplying its own values:\n\n- **Shared, non-sensitive defaults** → the `env` map in config.\n- **Per-environment values** → declare them with `env()` and provide them from\n each pipeline's environment.\n\n```bash\n# dev pipeline\nLOG_LEVEL=debug laranja deploy --stage dev\n# prod pipeline\nLOG_LEVEL=warn laranja deploy --stage prod\n```\n\n## Secrets\n\n`env()` keeps values out of your repo, but they still land in the Lambda's plain\nenvironment — readable by anyone with access to the function's configuration. For\ntrue secrets (API keys, DB passwords), that's not enough. First-class secrets\nsupport is on the roadmap; until then, read them at runtime from a secret store\n(e.g. AWS SSM Parameter Store / Secrets Manager) inside your handler.\n\n## Related\n\n- [Config file](../reference/config-file.md)\n- [Stages & environments](./stages-and-environments.md)\n"},{"slug":"guides/stages-and-environments","section":"guides","title":"Stages & environments","description":"Run dev, staging, and prod from one codebase with --stage.","order":6,"body":"\n# Stages & environments\n\nA **stage** is a named environment — `dev`, `staging`, `prod`, or anything you\nlike. laranja makes each stage a fully independent deployment from the same\ncodebase.\n\n## Setting the stage\n\nThe default stage is `dev`. Set it in config:\n\n```ts\n// laranja.config.ts\nconst config: LaranjaConfig = { name: \"my-api\", stage: \"dev\" };\n```\n\n…or override it per command with `--stage` (alias `-s`):\n\n```bash\nlaranja deploy --stage prod\nlaranja deploy -s staging\n```\n\nThe flag wins over the config value, which is why the recommended setup keeps\n`stage` at its default in config and lets each pipeline pass `--stage`.\n\n`--stage` applies to every environment-aware command: `deploy`, `plan`,\n`destroy`, `logs`, and `eject`.\n\n## Each stage is its own stack\n\nThe stage is part of the **stack name** (`‹name›-‹stage›`) and every resource\nname (`‹name›-‹fn›-‹stage›`). So `--stage dev` and `--stage prod` produce two\n**independent CloudFormation stacks** that never collide — even in the same AWS\naccount.\n\n```\nmy-api-dev ← laranja deploy --stage dev\nmy-api-prod ← laranja deploy --stage prod\n```\n\n## Two ways to isolate environments\n\nBoth work, and they compose:\n\n1. **One account, multiple stages.** The stage suffix keeps the stacks separate.\n Good for small projects or non-prod environments.\n2. **Separate accounts per stage.** Point each pipeline at different AWS\n credentials (a dev account and a prod account). Here your **AWS credentials\n are the real boundary**; the stack name can even repeat across accounts\n without conflict.\n\n## One pipeline per stage\n\nThe canonical CI/CD setup is one pipeline per environment, each running the same\ncommand with a different flag:\n\n```yaml\n# dev pipeline → laranja deploy --stage dev\n# staging pipeline → laranja deploy --stage staging\n# prod pipeline → laranja deploy --stage prod\n```\n\nSame repo, same command — only the flag differs. No per-environment config files\nto keep in sync. Pair this with [per-stage env values](./environment-variables.md#per-stage-values)\nto supply each environment's configuration.\n\n> Consistency matters: a pipeline's `destroy`, `logs`, and `plan` must use the\n> **same `--stage`** as its `deploy`, or they'll target a different stack.\n\n## The `STAGE` env var\n\nThe active stage is injected into every Lambda as `process.env.STAGE`, so your\ncode can branch on it:\n\n```ts\nconst isProd = process.env.STAGE === \"prod\";\n```\n\n## Related\n\n- [Config file](../reference/config-file.md)\n- [Environment variables](./environment-variables.md)\n"},{"slug":"reference/commands","section":"reference","title":"CLI commands","description":"Every laranja command and flag.","order":1,"body":"\n# CLI commands\n\n```\nlaranja <command> [project-dir] [flags]\n```\n\n`project-dir` defaults to the current directory, so most of the time you just run\n`laranja deploy`. Run `laranja --help` for a summary.\n\n> **Most commands need your account.** `plan`, `deploy`, and `eject` build your\n> template on the laranja server, so they need a `LARANJA_API_KEY` and a\n> `projectId` in your config. Run [`laranja init`](#init) once to sign in and link\n> a project; the key is stored in `~/.laranja/auth.json` so you don't re-export\n> it. Your **source code never leaves your machine** — only a description of your\n> infrastructure does\n> (see [how it works](../getting-started/how-it-works.md)).\n\n## Global flags\n\n| Flag | Applies to | Description |\n|---|---|---|\n| `--stage`, `-s <name>` | deploy, plan, destroy, logs, eject | Target [stage](../guides/stages-and-environments.md); overrides `config.stage`. |\n| `--verbose`, `-v` | deploy | Stream full CDK/CloudFormation output instead of the compact UI. |\n| `--strict` | deploy | Fail if any [`env()`](../guides/environment-variables.md#values-from-your-environment--env) value is unset (default: warn). |\n\n---\n\n## `init`\n\nSign in and scaffold a `laranja.config.ts` in the project directory.\n\n```bash\nlaranja init\n```\n\n`init` prompts for your **laranja API key** (from the dashboard) and validates it\nagainst the server before writing anything, then stores it in\n`~/.laranja/auth.json` so later commands don't need it re-exported. It then lets\nyou **pick or create a dashboard project** and fills the scaffolded config's\n`name` and `projectId` for you. Edit the file afterwards to set your `region`,\n`env`, and `compute`. See the [config reference](./config-file.md).\n\n---\n\n## `logout`\n\nRemove the stored API key (`~/.laranja/auth.json`).\n\n```bash\nlaranja logout\n```\n\nAfter this, commands that talk to the server (`init`, `plan`, `deploy`, `eject`)\nneed `LARANJA_API_KEY` in the environment again, or another `laranja init`.\n\n---\n\n## `plan`\n\nPreview what a deploy would do — laranja synthesizes your template on the server,\ndiffs it against the stack **currently deployed** in your AWS account, and prints\nyour app's resources tagged **created / changed / unchanged**. Nothing is applied.\n\n```bash\nlaranja plan\nlaranja plan --stage prod\n```\n\n```\nPlan for \"my-api-dev\"\n\n= http HTTP 2 routes → proxy Lambda + Function URL\n+ daily Cron Lambda + EventBridge rule\n~ emails Queue SQS + consumer Lambda\n\n8 AWS resources +3 created ~2 changed =3 unchanged\n```\n\n`+` is new, `~` changed, `=` unchanged. The bottom line tallies the underlying\nAWS resources.\n\n`plan` needs `LARANJA_API_KEY` (run [`laranja init`](#init) first) to synthesize,\nand a working **AWS credential chain** to read your live stack. It is\n**read-only** — it never creates a deployment or counts against your deploy limit.\n\n| Flag | Description |\n|---|---|\n| `--stage`, `-s` | Target stage. |\n\n---\n\n## `deploy`\n\nDeploy into your AWS account using your **local** AWS credentials. The template is\nsynthesized on the laranja server first, then applied with your own credentials —\nso deploy needs both `LARANJA_API_KEY` (run [`laranja init`](#init) first) and a\nworking AWS credential chain.\n\n```bash\nlaranja deploy\nlaranja deploy --stage prod\nlaranja deploy --verbose\n```\n\n- The first deploy to a new account/region prompts to **bootstrap** (a one-time\n setup in your account).\n- On success it prints your outputs — the HTTPS URL, queue URLs, and the cron\n jobs deployed.\n\n| Flag | Description |\n|---|---|\n| `--stage`, `-s` | Target stage. |\n| `--verbose`, `-v` | Stream full CDK output. |\n| `--strict` | Fail the deploy if any [`env()`](../guides/environment-variables.md#values-from-your-environment--env) value is unset. By default these are deployed with a warning. |\n\n---\n\n## `destroy`\n\nTear down the deployed stack and all its resources. Prompts for confirmation.\n\n```bash\nlaranja destroy\nlaranja destroy --stage prod\n```\n\n> Targets the stack for the resolved stage — make sure `--stage` matches the\n> environment you intend to remove.\n\n| Flag | Description |\n|---|---|\n| `--stage`, `-s` | Target stage. |\n\n---\n\n## `logs`\n\nTail CloudWatch logs for your deployed functions. The live stack is the source of\ntruth — no local state needed.\n\n```bash\nlaranja logs # interactive picker (TTY)\nlaranja logs sendEmail # tail a specific function by name\nlaranja logs --all # tail every function, multiplexed\nlaranja logs --no-follow # print recent history and exit\nlaranja logs --since 30m # history look-back window\nlaranja logs --stage prod # functions for the prod stack\n```\n\n| Flag / arg | Description |\n|---|---|\n| `<name>` (positional) | Function to tail (matched against its short label or full name). |\n| `--all` | Tail every function in the stack, multiplexed. |\n| `--no-follow` | Print the recent history window and exit (no live tail). |\n| `--since <dur>` | History look-back, e.g. `30s`, `15m`, `1h`, `2d` (default `1h`). |\n| `--stage`, `-s` | Target stage. |\n\nBoth a directory and a function name can be passed as positionals —\n`laranja logs ./app sendEmail` works.\n\n---\n\n## `eject`\n\nGenerate a standalone, owned **CDK project** from your app and stop — for when\nyou've outgrown the abstraction and want full control. **Paid feature.**\n\n```bash\nlaranja eject\nlaranja eject --force # overwrite an existing ./infra\nlaranja eject --stage prod\n```\n\nThe CDK project is generated **on the laranja server** (which gates the paid\nentitlement) and written to `./infra` — a complete project you own and run\nyourself (`cd infra && npm install && npm run deploy`). Requires `LARANJA_API_KEY`\nand a `projectId`; if your account can't eject, the server returns a clear error.\n\n| Flag | Description |\n|---|---|\n| `--force` | Overwrite an existing `infra/` directory. |\n| `--stage`, `-s` | Target stage (baked into the generated project). |\n\n## Related\n\n- [Stages & environments](../guides/stages-and-environments.md)\n- [How it works](../getting-started/how-it-works.md)\n"},{"slug":"reference/config-file","section":"reference","title":"Config file","description":"Every field in laranja.config.ts, with defaults and behavior.","order":2,"body":"\n# `laranja.config.ts`\n\nEvery project has a `laranja.config.ts` at its root that `export default`s a\nconfig object. It's a TypeScript module (loaded via `tsx`), so you get full type\nchecking and can compute values if you need to.\n\n```ts\nimport type { LaranjaConfig } from \"@alzulejos/laranja-decorators\";\n\nconst config: LaranjaConfig = {\n name: \"my-api\",\n // From your laranja dashboard — identifies this project on the server.\n projectId: \"proj_…\",\n region: \"us-east-1\",\n env: { LOG_LEVEL: \"info\" },\n // Default compute for every function (the HTTP proxy + each cron/queue).\n compute: { memory: 256, timeout: 30 },\n};\n\nexport default config;\n```\n\nThe config stays minimal because the HTTP app is declared in code with the\n[`http()`](./decorators-and-markers.md#http) marker — there's no config field for\nit. For a deploy with no HTTP app, just omit the marker (see\n[workers-only deployments](../guides/http-apps.md#workers-only-deployments)). Run\n[`laranja init`](./commands.md#init) to scaffold this file — it fills in\n`name` and `projectId` from the dashboard project you pick.\n\n## Fields\n\n| Field | Required | Default | Description |\n|---|---|---|---|\n| `name` | ✅ | — | App name. Used for the CloudFormation stack and all resource names. |\n| `region` | | `AWS_REGION` / `AWS_DEFAULT_REGION` | AWS region to deploy to. |\n| `stage` | | `\"dev\"` | Deployment stage. Part of the stack + resource names and injected as the `STAGE` env var. Override per-run with [`--stage`](../guides/stages-and-environments.md). |\n| `profile` | | — | AWS named profile to deploy with. |\n| `framework` | | _auto-detected_ | Override framework detection (e.g. `\"express\"`). |\n| `env` | | `{}` | Plain environment variables injected into every Lambda. See [Environment variables](../guides/environment-variables.md). |\n| `compute` | | `{ memory: 256, timeout: 30 }` | Default memory (MB) and timeout (s) for **every** function. See [compute](#compute). |\n| `resources` | | `{}` | Per-resource overrides keyed by resource id (`http`, or a cron/queue id). See [resources](#resources). |\n| `projectId` | ✅ | — | Project id from the laranja dashboard. Required by the server-side build (`plan`/`deploy`/`eject`); `laranja init` fills it in. |\n| `provider` | | `\"aws\"` | Target cloud. Only `\"aws\"` is implemented today. |\n\n### `name`\n\nDrives the stack name (`‹name›-‹stage›`) and every resource name\n(`‹name›-‹fn›-‹stage›`). Choose something short and stable — renaming it after a\ndeploy creates a _new_ stack rather than renaming the old one.\n\n### `region` and `profile`\n\n`region` falls back to `AWS_REGION`, then `AWS_DEFAULT_REGION`. If none is set,\nthe CLI errors with a clear message. `profile` selects a named profile from your\nAWS credentials file; otherwise the default credential chain is used.\n\n### `stage`\n\nThe default is `\"dev\"`. It's part of resource names and is injected into every\nLambda as `process.env.STAGE`. The [`--stage`](../guides/stages-and-environments.md)\nflag overrides it per command — the recommended way to drive multiple\nenvironments from one config.\n\n### `compute`\n\nThe default **memory** (MB) and **timeout** (seconds) applied to every function —\nthe HTTP proxy and each cron/queue consumer. The scaffold sets\n`{ memory: 256, timeout: 30 }`:\n\n```ts\nconst config: LaranjaConfig = {\n name: \"my-api\",\n projectId: \"proj_…\",\n compute: { memory: 512, timeout: 20 },\n};\n```\n\n### `resources`\n\nPer-resource overrides, keyed by **resource id** — `http` for the proxy, or the\n[`id`](./decorators-and-markers.md#cron) of a cron/queue. Each entry\nmerges field-by-field on top of `compute`, and queue/cron entries also accept\ntheir kind-specific knobs (e.g. a queue's `visibilityTimeout`). An unknown id is\na hard error, so a typo can't silently no-op:\n\n```ts\nconst config: LaranjaConfig = {\n name: \"my-api\",\n projectId: \"proj_…\",\n compute: { memory: 256, timeout: 30 },\n resources: {\n http: { memory: 512 }, // beefier proxy\n cleanup: { timeout: 60 }, // a slow cron by its id\n emails: { visibilityTimeout: 180 }, // a queue by its name/id\n },\n};\n```\n\n### Workers-only deployments\n\nTo deploy only your workers (`@Cron` / `@Queue`) with no HTTP app — for teams\nwhose API is hosted elsewhere — simply don't add an `http()` marker. No flag is\nneeded. See [workers-only deployments](../guides/http-apps.md#workers-only-deployments).\n\n## Related\n\n- [Environment variables](../guides/environment-variables.md)\n- [Stages & environments](../guides/stages-and-environments.md)\n"},{"slug":"reference/decorators-and-markers","section":"reference","title":"Decorators & markers","description":"API reference for @Cron, @Queue, cron, queue, getQueue, and http.","order":3,"body":"\n# Decorators & markers\n\nAll of these are imported from `@alzulejos/laranja-decorators`. They are **static markers**\n— the [scanner](../getting-started/how-it-works.md#1-scan) reads them at build time to\nshape your infrastructure. At runtime they are near-no-ops (they don't wrap or\nintercept your functions), so they're safe to leave in place.\n\n```bash\nnpm install @alzulejos/laranja-decorators\n```\n\nThe schedule builders [`rate`](../guides/schedules.md#ratevalue-unit) and\n[`every`](../guides/schedules.md#everyunit) are re-exported here too, so you can\nimport them alongside `@Cron`. For `@nestjs/schedule` compatibility, the\n`CronExpression` enum, `@Interval`, and `@Timeout` are re-exported as well — so a\nNest app can repoint its import at `@alzulejos/laranja-decorators` unchanged.\n\n---\n\n## `@Cron`\n\nSchedules a class method. Each `@Cron` becomes [its own Lambda + EventBridge\nrule](./what-gets-deployed.md#cron--lambda--eventbridge-rule).\n\n```ts\nfunction Cron(schedule: ScheduleInput): MethodDecorator\nfunction Cron(options: CronOptions): MethodDecorator\nfunction Cron(expression: string, options?: NestCronOptions): MethodDecorator // @nestjs/schedule form\n```\n\n```ts\nimport { Cron, rate, CronExpression } from \"@alzulejos/laranja-decorators\";\n\nexport class Jobs {\n @Cron(rate(5, \"minutes\"))\n async refreshCache() {}\n\n @Cron({ schedule: \"cron(0 12 * * ? *)\", id: \"daily-report\" })\n async report() {}\n\n // @nestjs/schedule style — a node-cron string or CronExpression, translated for you\n @Cron(\"0 3 * * *\", { name: \"nightly\", timeZone: \"Europe/Lisbon\" })\n async nightly() {}\n\n @Cron(CronExpression.EVERY_30_MINUTES)\n async sweep() {}\n}\n```\n\n**`CronOptions`** (laranja form)\n\n| Field | Type | Description |\n|---|---|---|\n| `schedule` | `ScheduleInput` | A [`rate()`/`every()`](../guides/schedules.md) result, a `Schedule`, or a raw string. |\n| `id` | `string` _(optional)_ | Stable logical id. Defaults to `‹Class›-‹method›`; also drives the Lambda name. |\n\n**`NestCronOptions`** (the second argument in the `@nestjs/schedule` form)\n\n| Field | Type | Description |\n|---|---|---|\n| `name` | `string` _(optional)_ | Used as the resource `id`. |\n| `timeZone` | `string` _(optional)_ | IANA timezone the schedule is evaluated in. |\n\nSee [Schedules → node-cron expressions](../guides/schedules.md#node-cron-expressions-nestjsschedule-compatibility)\nfor the accepted syntax and what's rejected. Nest providers resolve through DI —\ndeclare your module with [`workers()`](#workers).\n\n---\n\n## `cron()` marker\n\nFunction-style counterpart to `@Cron`, for codebases that don't use classes.\nRegisters a standalone exported function on a schedule.\n\n```ts\nfunction cron(schedule: ScheduleInput, handler: JobHandler): void\nfunction cron(options: CronOptions, handler: JobHandler): void\n```\n\n```ts\nimport { cron, rate } from \"@alzulejos/laranja-decorators\";\n\nexport async function refreshCache() {}\n\ncron(rate(5, \"minutes\"), refreshCache);\ncron({ schedule: rate(1, \"hour\"), id: \"hourly-sync\" }, refreshCache);\n```\n\nThe function's name becomes the resource id unless you pass an explicit `id`.\n\n---\n\n## `@Interval`\n\n`@nestjs/schedule`-compatible. Runs a method every _N_ milliseconds; laranja\nlowers it to a `rate(...)`, so the interval must be a whole number of minutes\n(EventBridge's 1-minute floor).\n\n```ts\nfunction Interval(milliseconds: number): MethodDecorator\nfunction Interval(name: string, milliseconds: number): MethodDecorator\n```\n\n```ts\nimport { Interval } from \"@alzulejos/laranja-decorators\";\n\nexport class Jobs {\n @Interval(300000) // every 5 minutes\n async poll() {}\n}\n```\n\n---\n\n## `@Timeout`\n\nRe-exported for `@nestjs/schedule` source compatibility, but a one-shot timer\nrelative to process start has no serverless equivalent — laranja **rejects it at\nbuild time** with a clear message. Use [`@Cron`](#cron) or [`@Interval`](#interval)\ninstead.\n\n---\n\n## `@Queue`\n\nConsumes messages from an SQS queue. Each `@Queue` becomes [an SQS queue +\nconsumer Lambda](./what-gets-deployed.md#queue--sqs-queue--consumer-lambda).\nThe handler is called once per message with the JSON-parsed body.\n\n```ts\nfunction Queue(options: QueueOptions): MethodDecorator\n```\n\n```ts\nimport { Queue } from \"@alzulejos/laranja-decorators\";\n\nexport class Workers {\n @Queue({ name: \"emails\", batchSize: 10 })\n async sendEmail(body: unknown) {}\n\n @Queue({ name: \"orders.fifo\", fifo: true })\n async processOrder(body: unknown) {}\n}\n```\n\n**`QueueOptions`**\n\n| Field | Type | Default | Description |\n|---|---|---|---|\n| `name` | `string` | _required_ | Queue name. A `.fifo` suffix marks a FIFO queue. |\n| `batchSize` | `number` | `10` | Max messages per consumer invocation. |\n| `fifo` | `boolean` | `false` | Force a FIFO queue (or end `name` with `.fifo`). When set, laranja appends `.fifo` to `name` if you left it off. |\n\n---\n\n## `queue()` marker\n\nFunction-style counterpart to `@Queue`.\n\n```ts\nfunction queue(options: QueueOptions, handler: JobHandler): void\n```\n\n```ts\nimport { queue } from \"@alzulejos/laranja-decorators\";\n\nexport async function sendEmail(body: unknown) {}\n\nqueue({ name: \"emails\", batchSize: 10 }, sendEmail);\n```\n\n---\n\n## `getQueue()`\n\nThe queue **producer** — get a handle to a declared queue and `.send()` messages\nto it. The counterpart to the [`@Queue`](#queue) / [`queue()`](#queue-marker)\nconsumers. Unlike the markers, this does real work at runtime (a single SQS\n`SendMessage`); laranja injects the queue URL and grants `sqs:SendMessage` to\nevery function, so there's nothing to configure.\n\n```ts\nfunction getQueue(name: string): LaranjaQueue\n\ninterface LaranjaQueue {\n readonly url: string;\n send(payload: unknown, options?: SendOptions): Promise<{ messageId?: string }>;\n}\n```\n\n```ts\nimport { getQueue } from \"@alzulejos/laranja-decorators\";\n\nawait getQueue(\"emails\").send({ to, subject });\nawait getQueue(\"orders.fifo\").send(order, { groupId: order.customerId });\n```\n\n`payload` is JSON-serialized (strings are sent as-is). `name` is the queue's\ndeclared `name`; a send to an undeclared queue throws.\n\n**`SendOptions`**\n\n| Field | Type | Applies to | Description |\n|---|---|---|---|\n| `groupId` | `string` | FIFO (**required**) | `MessageGroupId` — orders messages within a group. A FIFO send throws without it. |\n| `dedupId` | `string` | FIFO | `MessageDeduplicationId` — only needed when content-based dedup is off. |\n| `delaySeconds` | `number` | Standard | Delay (0–900s) before the message becomes visible. Ignored by FIFO. |\n\nSee [Queues → Sending messages](../guides/queues.md#sending-messages).\n\n---\n\n## `http()`\n\nMarks the HTTP app (the proxy target) in code — the only way to declare one.\nExport the result so the scanner can find it.\n\n```ts\nfunction http<T>(app: T): T\n```\n\n```ts\nimport express from \"express\";\nimport { http } from \"@alzulejos/laranja-decorators\";\n\nconst app = express();\nexport default http(app); // or: export const api = http(app);\n```\n\nIt returns its argument untouched — purely a static marker. Omit it for a\n[workers-only](../guides/http-apps.md#workers-only-deployments) deployment.\n\nFor **NestJS**, wrap your async `bootstrap` factory (which `return`s the app)\ninstead of an app instance — see [HTTP apps → NestJS](../guides/http-apps.md#nestjs):\n\n```ts\nexport default http(bootstrap); // bootstrap: () => Promise<INestApplication>\n```\n\n---\n\n## `workers()`\n\n**NestJS only.** Declares the module laranja builds a dependency-injection\ncontext from, so class-based [`@Cron`](#cron) / [`@Queue`](#queue) providers\nresolve their injected dependencies at runtime (via\n`NestFactory.createApplicationContext`) instead of a bare `new`. The DI\ncounterpart to [`http()`](#http); export it so the scanner can find it.\n\n```ts\nfunction workers<T>(module: T): T\n```\n\n```ts\nimport { workers } from \"@alzulejos/laranja-decorators\";\nimport { AppModule } from \"./app.module\";\n\nexport default workers(AppModule); // or: export const jobs = workers(AppModule);\n```\n\nPass `AppModule` for the whole graph, or a leaner module for a smaller cold\nstart. There's exactly one per project. Required when a Nest project has\nclass-based workers; standalone [`cron()`](#cron-marker)/[`queue()`](#queue-marker)\nfunctions don't need it (no DI). Returns its argument untouched — a static marker.\n\n---\n\n## `env()`\n\nDeclares an environment variable your code needs. At runtime it just returns\n`process.env[name]`; laranja discovers each call and populates that variable on\nevery deployed function, with the value supplied from your shell or CI at deploy\ntime.\n\n```ts\nfunction env(name: string): string | undefined\n```\n\n```ts\nimport { env } from \"@alzulejos/laranja-decorators\";\n\nconst dbUrl = env(\"DATABASE_URL\");\n```\n\nThe name must be a **string literal** so it can be found statically. See\n[environment variables](../guides/environment-variables.md#values-from-your-environment--env)\nfor supplying values, the `--strict` flag, and per-stage usage.\n\n---\n\n## Types\n\n| Type | Description |\n|---|---|\n| `ScheduleInput` | `Schedule \\| string` — anything accepted where a schedule is expected. |\n| `Schedule` | Provider-neutral schedule: `{ kind: \"rate\", value, unit }` or `{ kind: \"cron\", expression, dialect }`. |\n| `RateUnit` | `\"minute\" \\| \"minutes\" \\| \"hour\" \\| \"hours\" \\| \"day\" \\| \"days\"`. |\n| `CronExpression` | Enum of common cron strings, mirrored from `@nestjs/schedule`. |\n| `JobHandler` | `(...args) => unknown \\| Promise<unknown>` — a `cron()`/`queue()` handler. |\n\n## Related\n\n- [Cron jobs](../guides/cron-jobs.md) · [Queues](../guides/queues.md) · [Schedules](../guides/schedules.md)\n- [HTTP apps](../guides/http-apps.md)\n"},{"slug":"reference/what-gets-deployed","section":"reference","title":"What gets deployed","description":"The exact AWS resources laranja creates and how they're named.","order":4,"body":"\n# What gets deployed\n\nlaranja maps each thing it finds in your code to a small, predictable set of AWS\nresources — all in your own account.\n\n## HTTP app → proxy Lambda + Function URL\n\nYour entire Express app is deployed as **one Lambda function** fronted by a\n**Lambda Function URL**. There is no API Gateway.\n\n- All routes are served by this single proxy Lambda.\n- The Function URL is **public** (`authType: NONE`) and CORS is open\n (`*` origins, all methods, all headers) — your app handles auth/CORS as it\n sees fit.\n- Memory and timeout come from [`compute`](./config-file.md#compute)\n in your config (the scaffold sets `{ memory: 256, timeout: 30 }`), overridable\n per-resource under the `http` key in [`resources`](./config-file.md#resources).\n- The public HTTPS URL is emitted as the `HttpUrl` stack output.\n\n```\nGET https://‹id›.lambda-url.‹region›.on.aws/\n```\n\n## `@Cron` → Lambda + EventBridge rule\n\nEach cron handler becomes **its own Lambda** plus an **EventBridge rule** that\ninvokes it on schedule.\n\n- Memory and timeout come from [`compute`](./config-file.md#compute)\n (default `{ memory: 256, timeout: 30 }`), overridable per cron id in\n [`resources`](./config-file.md#resources).\n- The schedule comes from your [`rate()`/`every()`](../guides/schedules.md)\n builder or raw expression.\n\n## `@Queue` → SQS queue + consumer Lambda\n\nEach queue handler becomes an **SQS queue** and a **consumer Lambda** wired to it.\n\n- Encryption: **SQS-managed (SSE-SQS)**.\n- **FIFO** queues are created when the name ends in `.fifo` or `fifo: true` is\n set (content-based deduplication is enabled for FIFO).\n- Default **batch size**: 10. Your handler is invoked per message with the\n JSON-parsed body.\n- **Partial-batch failures** are enabled: throwing for one message fails only\n that message; the rest of the batch still succeeds.\n- Consumer memory/timeout come from [`compute`](./config-file.md#compute)\n (default `{ memory: 256, timeout: 30 }`). The queue's **visibility timeout** is\n derived to satisfy AWS's rule that it be ≥ the consumer timeout, and can be set\n explicitly per queue via [`resources`](./config-file.md#resources).\n- The queue URL is emitted as a stack output.\n\n## Naming\n\nEverything is named deterministically — no random suffixes:\n\n| Resource | Pattern | Example |\n|---|---|---|\n| CloudFormation stack | `‹name›-‹stage›` | `my-api-prod` |\n| Lambda functions | `‹name›-‹fn›-‹stage›` | `my-api-app-prod`, `my-api-sendEmail-prod` |\n\n`‹fn›` is the handler's name (the method/function name), or the explicit `id` you\nset on `@Cron`/`@Queue`. The HTTP proxy uses `app`. Names are truncated to AWS's\n64-character limit and sanitized to allowed characters.\n\nBecause the stage is part of the names, multiple stages can live in one account\nwithout colliding — see [Stages & environments](../guides/stages-and-environments.md).\n\n## Supporting resources\n\nEach Lambda gets an **IAM execution role** (and the queue consumers get the\npermissions to read their queue / EventBridge to invoke crons). The first deploy\nto an account/region also creates the one-time CDK **bootstrap** resources (an S3\nasset bucket and roles).\n\n## Related\n\n- [How it works](../getting-started/how-it-works.md)\n- [HTTP apps](../guides/http-apps.md) · [Cron jobs](../guides/cron-jobs.md) · [Queues](../guides/queues.md)\n"}];
7
+
8
+ const _bySlug = new Map(pages.map((p) => [p.slug, p]));
9
+
10
+ /** Look up a page by its slug ("" is the docs home). */
11
+ export function getPage(slug) {
12
+ return _bySlug.get(slug);
13
+ }
14
+
15
+ /**
16
+ * Build an h2/h3 table of contents whose ids match what rehype-slug emits.
17
+ * Every heading (incl. h1) is fed through the slugger so dedup state stays in
18
+ * lockstep with the rendered DOM; only h2/h3 are surfaced.
19
+ */
20
+ export function tableOfContents(body) {
21
+ const slugger = new GithubSlugger();
22
+ const items = [];
23
+ let inFence = false;
24
+ for (const line of body.split(/\r?\n/)) {
25
+ if (/^\s*```/.test(line)) {
26
+ inFence = !inFence;
27
+ continue;
28
+ }
29
+ if (inFence) continue;
30
+ const m = /^(#{1,3})\s+(.*)$/.exec(line);
31
+ if (!m) continue;
32
+ const depth = m[1].length;
33
+ const text = m[2].replace(/`/g, "").replace(/\[([^\]]+)\]\([^)]*\)/g, "$1").trim();
34
+ const id = slugger.slug(text);
35
+ if (depth >= 2) items.push({ depth, text, id });
36
+ }
37
+ return items;
38
+ }
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@alzulejos/laranja-docs",
3
+ "version": "0.2.4",
4
+ "description": "Canonical laranja documentation, parsed and exported as a module for any frontend to render.",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ },
15
+ "./content/*": "./content/*"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "content"
20
+ ],
21
+ "publishConfig": {
22
+ "access": "public"
23
+ },
24
+ "scripts": {
25
+ "build": "node scripts/build.mjs"
26
+ },
27
+ "dependencies": {
28
+ "github-slugger": "^2.0.0"
29
+ }
30
+ }