lakutata 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -30
- package/README.zh-CN.md +130 -0
- package/dist/cjs/exports/QueueComponent.d.ts +1 -0
- package/dist/cjs/exports/QueueComponent.js +20 -0
- package/dist/esm/exports/QueueComponent.js +1 -0
- package/dist/types/exports/QueueComponent.d.ts +1 -0
- package/doc/en/Application.md +107 -0
- package/doc/en/CLI.md +274 -0
- package/doc/en/Cache.md +379 -0
- package/doc/en/Command.md +110 -0
- package/doc/en/ContainerdIntegration.md +17 -16
- package/doc/en/Controllers.md +256 -0
- package/doc/en/DBus.md +291 -0
- package/doc/en/DTOValidation.md +72 -43
- package/doc/en/Database.md +188 -0
- package/doc/en/DatabaseDrivers.md +287 -0
- package/doc/en/Decorator.md +52 -40
- package/doc/en/DockerIntegration.md +7 -6
- package/doc/en/Entities.md +240 -0
- package/doc/en/Entrypoint.md +216 -0
- package/doc/en/Exceptions.md +174 -0
- package/doc/en/GRPC.md +251 -0
- package/doc/en/GettingStarted.md +165 -0
- package/doc/en/HTTP.md +507 -0
- package/doc/en/HelperFunctions.md +11 -11
- package/doc/en/I18n.md +119 -115
- package/doc/en/Injection.md +144 -0
- package/doc/en/Installers.md +280 -0
- package/doc/en/Logger.md +206 -0
- package/doc/en/MCP.md +293 -0
- package/doc/en/Middlewares.md +213 -0
- package/doc/en/Migrations.md +202 -0
- package/doc/en/MongoEmbedded.md +149 -0
- package/doc/en/Monitor.md +164 -0
- package/doc/en/Mutex.md +27 -25
- package/doc/en/NATS.md +246 -0
- package/doc/en/NativeAddons.md +212 -0
- package/doc/en/Objects.md +143 -0
- package/doc/en/Overview.md +62 -85
- package/doc/en/Packaging.md +193 -0
- package/doc/en/ProjectStructure.md +124 -0
- package/doc/en/Queue.md +269 -0
- package/doc/en/Schedules.md +217 -0
- package/doc/en/Security.md +10 -9
- package/doc/en/ServiceCalls.md +504 -0
- package/doc/en/Time.md +19 -20
- package/doc/en/UpgradingFrom1.md +435 -0
- package/doc/en/UpgradingFrom2.md +315 -0
- package/doc/en/Workspaces.md +189 -0
- package/doc/en/examples/BackgroundJobs.md +677 -0
- package/doc/en/examples/CommandLineTool.md +664 -0
- package/doc/en/examples/DesktopApp.md +809 -0
- package/doc/en/examples/DeviceService.md +607 -0
- package/doc/en/examples/MCPServer.md +653 -0
- package/doc/en/examples/README.md +16 -0
- package/doc/en/examples/RestAPI.md +490 -0
- package/doc/en/examples/ServiceCalls.md +829 -0
- package/doc/en/examples/WebApp.md +568 -0
- package/doc/zh/Application.md +75 -0
- package/doc/zh/CLI.md +250 -0
- package/doc/zh/Cache.md +328 -0
- package/doc/zh/Command.md +78 -0
- package/doc/zh/ContainerdIntegration.md +9 -9
- package/doc/zh/Controllers.md +228 -0
- package/doc/zh/DBus.md +257 -0
- package/doc/zh/DTOValidation.md +49 -19
- package/doc/zh/Database.md +175 -0
- package/doc/zh/DatabaseDrivers.md +261 -0
- package/doc/zh/Decorator.md +25 -18
- package/doc/zh/DockerIntegration.md +6 -6
- package/doc/zh/Entities.md +224 -0
- package/doc/zh/Entrypoint.md +193 -0
- package/doc/zh/Exceptions.md +158 -0
- package/doc/zh/GRPC.md +226 -0
- package/doc/zh/GettingStarted.md +148 -0
- package/doc/zh/HTTP.md +445 -0
- package/doc/zh/HelperFunctions.md +2 -2
- package/doc/zh/I18n.md +26 -26
- package/doc/zh/Injection.md +129 -0
- package/doc/zh/Installers.md +232 -0
- package/doc/zh/Logger.md +188 -0
- package/doc/zh/MCP.md +265 -0
- package/doc/zh/Middlewares.md +198 -0
- package/doc/zh/Migrations.md +178 -0
- package/doc/zh/MongoEmbedded.md +118 -0
- package/doc/zh/Monitor.md +145 -0
- package/doc/zh/Mutex.md +10 -10
- package/doc/zh/NATS.md +207 -0
- package/doc/zh/NativeAddons.md +180 -0
- package/doc/zh/Objects.md +119 -0
- package/doc/zh/Overview.md +46 -71
- package/doc/zh/Packaging.md +154 -0
- package/doc/zh/ProjectStructure.md +102 -0
- package/doc/zh/Queue.md +214 -0
- package/doc/zh/Schedules.md +193 -0
- package/doc/zh/Security.md +6 -6
- package/doc/zh/ServiceCalls.md +459 -0
- package/doc/zh/Time.md +2 -2
- package/doc/zh/UpgradingFrom1.md +347 -0
- package/doc/zh/UpgradingFrom2.md +230 -0
- package/doc/zh/Workspaces.md +150 -0
- package/doc/zh/examples/BackgroundJobs.md +618 -0
- package/doc/zh/examples/CommandLineTool.md +618 -0
- package/doc/zh/examples/DesktopApp.md +748 -0
- package/doc/zh/examples/DeviceService.md +546 -0
- package/doc/zh/examples/MCPServer.md +604 -0
- package/doc/zh/examples/README.md +14 -0
- package/doc/zh/examples/RestAPI.md +458 -0
- package/doc/zh/examples/ServiceCalls.md +765 -0
- package/doc/zh/examples/WebApp.md +520 -0
- package/package.json +46 -17
- package/doc/en/ActionPattern.md +0 -408
- package/doc/en/Core.md +0 -312
- package/doc/en/ORMIntegration.md +0 -341
- package/doc/zh/ActionPattern.md +0 -334
- package/doc/zh/Core.md +0 -256
- package/doc/zh/ORMIntegration.md +0 -319
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Lakutata Command Line Tool
|
|
2
|
+
|
|
3
|
+
The **`lakutata` command** (package `@lakutata/cli`) creates projects from templates, runs scripts across the packages
|
|
4
|
+
of a workspace and gives them versions, adds packages and native addons, runs the migrations of an application, checks
|
|
5
|
+
its translations, and packs it into bundles, executables and installers. The README of `@lakutata/cli` lists every
|
|
6
|
+
option.
|
|
7
|
+
|
|
8
|
+
## 1. Installing and running it
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx @lakutata/cli create --name my-app # without installing it
|
|
12
|
+
npm install --global @lakutata/cli # or once for the machine, then: lakutata create --name my-app
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The templates add `@lakutata/cli` to the `devDependencies` of the project, so its scripts call `lakutata` directly and
|
|
16
|
+
`npx lakutata` works inside it. The tool needs Node.js `^22.13.0 || >=24.11.0`: 22.13 or a later 22.x, or 24.11 and
|
|
17
|
+
later (not 23).
|
|
18
|
+
|
|
19
|
+
- `lakutata --help` lists the commands, `lakutata help <command>` (or `lakutata <command> --help`) describes one, and
|
|
20
|
+
`lakutata --version` prints the version of the tool. Without a command, the tool prints its help and exits with code
|
|
21
|
+
1.
|
|
22
|
+
- Options are written in kebab case (`--package-manager`). A boolean option that is on by default is turned off with
|
|
23
|
+
`--no-` (`--no-git`, `--no-bail`).
|
|
24
|
+
- Results go to standard output, warnings (`! …`) to standard error. A failure prints `error: <message>` on standard
|
|
25
|
+
error and exits with code 1. Colors are used on a terminal only, and `NO_COLOR` turns them off.
|
|
26
|
+
- **Language**: the help, the prompts and the validation errors are in English, Simplified Chinese or Traditional
|
|
27
|
+
Chinese, chosen from `LAKUTATA_LANG`, then `LANGUAGE`, `LC_ALL`, `LC_MESSAGES`, `LANG`, then the language of the
|
|
28
|
+
system (`LAKUTATA_LANG=zh-CN lakutata --help`). The names of commands and options, and the other messages of the
|
|
29
|
+
tool, stay in English.
|
|
30
|
+
|
|
31
|
+
## 2. Projects
|
|
32
|
+
|
|
33
|
+
### `lakutata create`
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
lakutata create --name my-app # the default template, in ./my-app
|
|
37
|
+
lakutata create --name shop --template service --with i18n,installer --yes
|
|
38
|
+
lakutata create --name tool --template cli --package-manager none --no-git
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
| Option | Default | Meaning |
|
|
42
|
+
|---|---|---|
|
|
43
|
+
| `--name` | asked in a terminal | The name of the application, and of its directory |
|
|
44
|
+
| `--id` | the name in lower case | The ID of the application and the name of its package |
|
|
45
|
+
| `--template` | `default` | An official template (`lakutata templates`), `npm:<package>[@<version>]`, `github:<owner>/<repo>[#<ref>]`, or a directory |
|
|
46
|
+
| `--path` | `.` | Where the project directory is created (`<path>/<name>`) |
|
|
47
|
+
| `--init` | | Create the project in `--path` itself, a directory that already exists |
|
|
48
|
+
| `--description`, `--author`, `--license` | license `UNLICENSED` | The information of the package |
|
|
49
|
+
| `--with` | | The optional features of the template, separated by commas (`i18n`, `installer`) |
|
|
50
|
+
| `--package-manager` | `npm`, or pnpm or yarn when one of them started the tool | `npm`, `pnpm`, `yarn`, or `none` to skip the installation |
|
|
51
|
+
| `--no-git` | | Do not create a git repository |
|
|
52
|
+
| `--yes` | | Do not ask for the features or for a confirmation |
|
|
53
|
+
|
|
54
|
+
In a terminal, the tool asks for a missing name, offers the features of the template, shows what it is going to create
|
|
55
|
+
and asks for a confirmation. It then copies the template, fills in the name, the ID and the other information of the
|
|
56
|
+
package, installs the dependencies (`<manager> install`) and runs `git init` (without a commit). An official template
|
|
57
|
+
is the newest release for the major version of the tool, downloaded from the npm registry and kept in the cache of the
|
|
58
|
+
tool; without a network, a template downloaded earlier is used when there is one.
|
|
59
|
+
|
|
60
|
+
With `--init`, the files the directory already has are kept: a file of the template with the same name is not copied,
|
|
61
|
+
and a warning lists these files so that you can merge what you need by hand (for `package.json`, its scripts and
|
|
62
|
+
dependencies).
|
|
63
|
+
|
|
64
|
+
### `lakutata templates`, `info` and `clean`
|
|
65
|
+
|
|
66
|
+
- `lakutata templates` lists the official templates and their features. The list comes from the npm registry and is
|
|
67
|
+
kept for a day (`--refresh` fetches it again). [ProjectStructure](ProjectStructure.md) describes what the templates
|
|
68
|
+
contain.
|
|
69
|
+
- `lakutata info` shows the versions of the tool, of the framework (with the latest ones when the registry can be
|
|
70
|
+
reached) and of Node.js, and the version of the framework that the project of the current directory uses.
|
|
71
|
+
- `lakutata clean [paths...]` removes paths of the package in the current directory, on every platform; without paths,
|
|
72
|
+
the `outDir` and `declarationDir` of its `tsconfig.json`. It refuses paths outside the package, and `src`,
|
|
73
|
+
`node_modules`, `package.json` and `.git`. The templates call it from their `build` or `rebuild` scripts.
|
|
74
|
+
|
|
75
|
+
## 3. Workspaces and native addons
|
|
76
|
+
|
|
77
|
+
`lakutata list`, `run`, `version` and `add` work on a workspace, a repository of several packages: they list its
|
|
78
|
+
packages, run a script in each of them in the order of their dependencies, give them one version, and add packages. See
|
|
79
|
+
[Workspaces](Workspaces.md).
|
|
80
|
+
|
|
81
|
+
`lakutata add <name> --native gyp|cmake|rust` adds a native addon written in C++ or Rust, and
|
|
82
|
+
`lakutata native <link|include|define> <value>` links libraries to it. See [NativeAddons](NativeAddons.md).
|
|
83
|
+
|
|
84
|
+
## 4. Migrations and translations
|
|
85
|
+
|
|
86
|
+
`lakutata migration <action>` generates, creates, runs, reverts and shows the migrations of the databases of an
|
|
87
|
+
application. It builds and starts the application with its `dev` script, in a mode where only the databases are
|
|
88
|
+
connected. See [Migrations](Migrations.md), section 2.
|
|
89
|
+
|
|
90
|
+
`lakutata i18n <action>` checks the messages of the languages of an application, declares the types of their keys, and
|
|
91
|
+
adds a language. See [I18n](I18n.md#command-line-tools).
|
|
92
|
+
|
|
93
|
+
## 5. Packaging
|
|
94
|
+
|
|
95
|
+
`lakutata package bundle` and `package exe` pack a compiled application into one JavaScript file, or into an executable
|
|
96
|
+
that runs without Node.js: see [Packaging](Packaging.md). `lakutata package installer` makes its installers, which can
|
|
97
|
+
register it as a service: see [Installers](Installers.md).
|
|
98
|
+
|
|
99
|
+
## 6. Environment variables
|
|
100
|
+
|
|
101
|
+
| Variable | Meaning |
|
|
102
|
+
|---|---|
|
|
103
|
+
| `LAKUTATA_LANG` | The language of the tool (`en`, `zh-CN`, `zh-TW`) |
|
|
104
|
+
| `LAKUTATA_NPM_REGISTRY` | The registry the templates come from (else `npm_config_registry`, else `https://registry.npmjs.org`) |
|
|
105
|
+
| `LAKUTATA_GITHUB_DOWNLOAD` | Where the `github:` templates are downloaded from (`https://github.com` by default) |
|
|
106
|
+
| `LAKUTATA_CACHE_DIR` | The cache of the tool: the templates, and the tools that make installers (`$XDG_CACHE_HOME/lakutata`, else `~/.cache/lakutata`) |
|
|
107
|
+
| `NO_COLOR` | Turns the colors off |
|
|
108
|
+
|
|
109
|
+
The scripts and the applications the tool runs get the environment the tool was started with. The variables of the
|
|
110
|
+
packed applications are described in [Packaging](Packaging.md), section 6.
|
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
# Lakutata
|
|
1
|
+
# Lakutata Containerd Integration
|
|
2
2
|
|
|
3
3
|
The **containerd component** (`lakutata/com/containerd`, package `@lakutata/containerd`) manages the images, containers,
|
|
4
4
|
CNI networks and named volumes of a [containerd](https://containerd.io) namespace over the gRPC API of containerd,
|
|
5
5
|
without Docker and without nerdctl. It targets the Linux machines that run containerd alone, such as embedded devices:
|
|
6
6
|
the component talks to the socket of containerd (its gRPC client is written in TypeScript, no native addon), and does
|
|
7
7
|
by itself what nerdctl does on top of it (pulling from the registries, unpacking the layers, generating the OCI
|
|
8
|
-
specifications of the containers, their logs). Its API follows
|
|
8
|
+
specifications of the containers, their logs). Its API follows that of the Docker component
|
|
9
9
|
([DockerIntegration](DockerIntegration.md)). The README of `@lakutata/containerd` gives every option.
|
|
10
10
|
|
|
11
|
-
The containers it creates are the
|
|
12
|
-
`nerdctl ps` and `ctr containers ls` list them.
|
|
11
|
+
The containers it creates are the same as the containers nerdctl creates: they carry its labels (`nerdctl/name`,
|
|
12
|
+
`nerdctl/hostname`…), so `nerdctl ps` and `ctr containers ls` list them.
|
|
13
13
|
|
|
14
14
|
## 1. Requirements
|
|
15
15
|
|
|
16
16
|
| Feature | Needs |
|
|
17
17
|
|---|---|
|
|
18
|
-
| Everything | Linux, containerd 1.6 or later with runc, Node.js 22
|
|
18
|
+
| Everything | Linux, containerd 1.6 or later with runc, Node.js `^22.13.0 || >=24.11.0`; the application runs as root or as a member of the group owning the socket of containerd |
|
|
19
19
|
| CNI networks (the default `bridge` included), published ports | The CNI plugins (`bridge`, `portmap`, `host-local`… in `/opt/cni/bin`, `/usr/lib/cni` or `/usr/libexec/cni`, the `cniPath` option) and iptables |
|
|
20
20
|
| `exec()` with `stdin`, separate `stdout`/`stderr`, terminals | `mkfifo` on the host (without it, `exec()` puts both outputs in `stdout`) |
|
|
21
21
|
| Builds | A BuildKit daemon (`buildkitd`), local or remote |
|
|
@@ -42,7 +42,7 @@ Application
|
|
|
42
42
|
cgroupManager: 'cgroupfs',
|
|
43
43
|
//The logs, hosts, resolv.conf and settings of the containers, and the named volumes
|
|
44
44
|
dataRoot: '/var/lib/lakutata/containerd',
|
|
45
|
-
//The log file of a container rotated beyond 10 MiB when the component starts it (0: never)
|
|
45
|
+
//The log file of a container rotated beyond 10 MiB when the component starts it (default 0: never)
|
|
46
46
|
logMaxSize: 10 * 1024 * 1024,
|
|
47
47
|
registryAuths: {'registry.example.com': {username: 'deploy', password: 'secret'}},
|
|
48
48
|
//The registries served over HTTP
|
|
@@ -63,9 +63,10 @@ Application
|
|
|
63
63
|
})
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
The component connects when it is created: without containerd on `socketPath`,
|
|
67
|
-
`ContainerdConnectionException
|
|
68
|
-
namespace; declare one component per namespace
|
|
66
|
+
The component connects when it is created: without containerd on `socketPath`, its creation fails with a
|
|
67
|
+
`ContainerdConnectionException`; when the component is listed in `bootstrap`, the application then fails to start. Its
|
|
68
|
+
namespace is created when it does not exist. Each component works in one namespace; declare one component per namespace
|
|
69
|
+
to manage several.
|
|
69
70
|
|
|
70
71
|
### What differs from the Docker component
|
|
71
72
|
|
|
@@ -178,7 +179,7 @@ async function containers(containerd: Containerd): Promise<void> {
|
|
|
178
179
|
//Its snapshot and its state removed
|
|
179
180
|
await container.remove({force: true})
|
|
180
181
|
|
|
181
|
-
//
|
|
182
|
+
//The containers with these labels; getContainer() finds one by its name, its id or a prefix of its id
|
|
182
183
|
console.log(await containerd.listContainers({app: 'worker'}))
|
|
183
184
|
}
|
|
184
185
|
```
|
|
@@ -193,8 +194,8 @@ async function containers(containerd: Containerd): Promise<void> {
|
|
|
193
194
|
`seccompProfile: 'unconfined'` disables it, the path of a file gives an OCI profile; the privileged containers are
|
|
194
195
|
unconfined.
|
|
195
196
|
- `privileged`, `capabilities` / `capabilitiesDrop`, `readonlyRootfs`, `pidMode: 'host'`, `ipcMode: 'host'`, `sysctls`,
|
|
196
|
-
`extraHosts`, `dns`, `shmSize`, `pidsLimit`, `cpuSet`, `cpuShares`, `memorySwap`, `OOMKillDisable`
|
|
197
|
-
Docker
|
|
197
|
+
`extraHosts`, `dns`, `shmSize`, `pidsLimit`, `cpuSet`, `cpuShares`, `memorySwap`, `OOMKillDisable` work as the options
|
|
198
|
+
of Docker with the same names.
|
|
198
199
|
- `update()` with other settings than the resources and the restart policy only works for the containers the component
|
|
199
200
|
created (it keeps their settings in `dataRoot`).
|
|
200
201
|
|
|
@@ -253,8 +254,8 @@ async function networks(containerd: Containerd): Promise<void> {
|
|
|
253
254
|
```
|
|
254
255
|
|
|
255
256
|
The networks of a container are set up by its runc hooks, as nerdctl does: they are added when its task is created and
|
|
256
|
-
deleted when it stops, also when containerd restarts it
|
|
257
|
-
application). Its address is given at each start and written in its `/etc/hosts`.
|
|
257
|
+
deleted when it stops, also when containerd restarts it under its restart policy (after a reboot, for example, while the
|
|
258
|
+
application is not running). Its address is given at each start and written in its `/etc/hosts`.
|
|
258
259
|
|
|
259
260
|
## 6. Builds
|
|
260
261
|
|
|
@@ -291,7 +292,7 @@ BuildKit pulls the base images with the registry credentials of the component (o
|
|
|
291
292
|
|
|
292
293
|
## 7. Platforms and events
|
|
293
294
|
|
|
294
|
-
`containerd.supportedPlatforms()` gives the platforms the machine
|
|
295
|
+
`containerd.supportedPlatforms()` gives the platforms the machine can run: its own, and the ones of the emulators
|
|
295
296
|
registered in `binfmt_misc` (QEMU, Rosetta). A container of another platform runs once its emulator is registered:
|
|
296
297
|
`createContainer({image: 'alpine:3.20', platform: 'linux/amd64', cmd: ['uname', '-m']})` after pulling that platform.
|
|
297
298
|
|
|
@@ -324,5 +325,5 @@ network exists, its subnet overlaps another one, or a container uses it), `Conta
|
|
|
324
325
|
|
|
325
326
|
## 9. Limits of this version
|
|
326
327
|
|
|
327
|
-
-
|
|
328
|
+
- Compose projects are not supported.
|
|
328
329
|
- The logs of the containers have no timestamps.
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# Lakutata Controllers and Actions
|
|
2
|
+
|
|
3
|
+
A controller is a class whose methods answer requests. Each of these methods is an **action**: a decorator gives it an
|
|
4
|
+
address (an HTTP route, a command, a service pattern, a schedule, a queue), and the `entrypoint` component calls it when
|
|
5
|
+
a request arrives at that address. One method can have several addresses: the same code then answers over HTTP, on the
|
|
6
|
+
command line and over a service transport, with the same validation, middlewares and access control.
|
|
7
|
+
|
|
8
|
+
## 1. A controller
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
import {Application, DTO} from 'lakutata'
|
|
12
|
+
import {buildEntrypoints, Controller, httpServiceEntrypoint} from 'lakutata/com/entrypoint'
|
|
13
|
+
import {CLIAction, GET, POST, ServiceAction} from 'lakutata/decorator/ctrl'
|
|
14
|
+
import {Expect} from 'lakutata/decorator/dto'
|
|
15
|
+
import {expressHTTPEntrypoint} from 'lakutata/adapter/express'
|
|
16
|
+
import {commanderCLIEntrypoint} from 'lakutata/adapter/commander'
|
|
17
|
+
|
|
18
|
+
class OrderIdDTO extends DTO {
|
|
19
|
+
//A route parameter is text: strict(false) converts it (the command line converts its arguments itself)
|
|
20
|
+
@Expect(DTO.Number().integer().strict(false).required())
|
|
21
|
+
public id: number
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
class CreateOrderDTO extends DTO {
|
|
25
|
+
@Expect(DTO.String().required())
|
|
26
|
+
public product: string
|
|
27
|
+
|
|
28
|
+
@Expect(DTO.Number().integer().min(1).default(1))
|
|
29
|
+
public quantity: number
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class OrderController extends Controller {
|
|
33
|
+
//GET /orders/42, the command "order 42", the service message {cmd: 'order.get', id: 42}
|
|
34
|
+
@GET('/orders/:id', OrderIdDTO)
|
|
35
|
+
@CLIAction('order <id>', OrderIdDTO, {description: 'Show an order'})
|
|
36
|
+
@ServiceAction({cmd: 'order.get'}, OrderIdDTO)
|
|
37
|
+
public async get(input: OrderIdDTO): Promise<{ id: number, status: string }> {
|
|
38
|
+
return {id: input.id, status: 'paid'}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
//POST /orders with a JSON body
|
|
42
|
+
@POST('/orders', CreateOrderDTO)
|
|
43
|
+
public async create(input: CreateOrderDTO): Promise<CreateOrderDTO> {
|
|
44
|
+
return input
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
Application.run(() => ({
|
|
49
|
+
id: 'orders.app',
|
|
50
|
+
name: 'Orders',
|
|
51
|
+
components: {
|
|
52
|
+
entrypoint: buildEntrypoints({
|
|
53
|
+
controllers: [OrderController],
|
|
54
|
+
http: expressHTTPEntrypoint({port: 8080}),
|
|
55
|
+
service: httpServiceEntrypoint({port: 8081}),
|
|
56
|
+
cli: commanderCLIEntrypoint({bin: 'orders', noCommand: 'ignore'})
|
|
57
|
+
})
|
|
58
|
+
},
|
|
59
|
+
bootstrap: ['entrypoint']
|
|
60
|
+
}))
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
$ curl localhost:8080/orders/42
|
|
65
|
+
{"id":42,"status":"paid"}
|
|
66
|
+
$ curl -X POST localhost:8080/orders -H 'Content-Type: application/json' -d '{"product":"tea","extra":1}'
|
|
67
|
+
{"product":"tea","quantity":1}
|
|
68
|
+
$ curl -X POST localhost:8081 -H 'Content-Type: application/json' -d '{"cmd":"order.get","id":42}'
|
|
69
|
+
{"success":true,"error":null,"payload":{"id":42,"status":"paid"}}
|
|
70
|
+
$ node dist/index.js order 42
|
|
71
|
+
{
|
|
72
|
+
"id": 42,
|
|
73
|
+
"status": "paid"
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
- A controller extends `Controller`, from `lakutata/com/entrypoint`.
|
|
78
|
+
- The controllers are listed in the `controllers` option of `buildEntrypoints`, next to the adapters of the transports
|
|
79
|
+
(see [Entrypoint](Entrypoint.md)). The entrypoint must be listed in `bootstrap`, or nothing starts it.
|
|
80
|
+
- The decorator of a transport has no effect without its adapter: here, without `cli`, the `@CLIAction` would be
|
|
81
|
+
ignored.
|
|
82
|
+
|
|
83
|
+
## 2. How a request is handled
|
|
84
|
+
|
|
85
|
+
1. The adapter receives the request and looks for the action at its address. When there is none, the transport answers
|
|
86
|
+
that it is not found (404 over HTTP).
|
|
87
|
+
2. A new scope is created for the request.
|
|
88
|
+
3. The middlewares run (see [Middlewares](Middlewares.md)).
|
|
89
|
+
4. A new instance of the controller is created in the scope.
|
|
90
|
+
5. The DTO of the action validates the input (section 4).
|
|
91
|
+
6. The access control checks the request (see [Middlewares](Middlewares.md)).
|
|
92
|
+
7. The method is called with the input, and what it returns is the response (section 5).
|
|
93
|
+
8. The scope is destroyed, with the objects created in it.
|
|
94
|
+
|
|
95
|
+
So a controller lives as long as one request. The providers it injects that are `@Scoped` (the default for a provider)
|
|
96
|
+
are created once for the request and shared by everything that takes part in it; the components are shared by the whole
|
|
97
|
+
application.
|
|
98
|
+
|
|
99
|
+
Inside the method, two properties describe the request:
|
|
100
|
+
|
|
101
|
+
- `this.context` is the context of the request. Its `type` names the transport, and its class gives the details of that
|
|
102
|
+
transport:
|
|
103
|
+
|
|
104
|
+
| Class | `type` | Specific properties |
|
|
105
|
+
|---|---|---|
|
|
106
|
+
| `HTTPContext` | `'HTTP'` | `request`, `response`, `route` (the route as declared), `method` |
|
|
107
|
+
| `CLIContext` | `'CLI'` | `command` (the command as declared) |
|
|
108
|
+
| `ServiceContext` | `'SERVICE'` | `clientId`, `input` (the whole message) |
|
|
109
|
+
| `ScheduleContext` | `'SCHEDULE'` | `schedule`, `scheduledAt`, `run` |
|
|
110
|
+
| `QueueContext` (`lakutata/com/queue`) | `'QUEUE'` | See [Queue](Queue.md) |
|
|
111
|
+
|
|
112
|
+
Every context also has `data`, the input as it arrived (before validation), and `locale`, the language of the
|
|
113
|
+
request when the I18n middleware is used.
|
|
114
|
+
- `this.signal` is an `AbortSignal` (its type allows `undefined`: write `this.signal?.`). It is aborted when the
|
|
115
|
+
request is: the client went away, the deadline of a gRPC call passed, or the application is stopping and its
|
|
116
|
+
`shutdownTimeout` ran out. Once it is aborted, the result of the action is dropped; a long action should check it and
|
|
117
|
+
stop early.
|
|
118
|
+
|
|
119
|
+
## 3. The action decorators
|
|
120
|
+
|
|
121
|
+
| Decorator | Address | Guide |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `@GET(route)`, `@POST`, `@PUT`, `@PATCH`, `@DELETE`, `@HEAD`, `@OPTIONS`, `@HTTPAction(route, methods)` | An HTTP route | [HTTP](HTTP.md) |
|
|
124
|
+
| `@CLIAction(command)` | A command | [CLI](CLI.md) |
|
|
125
|
+
| `@ServiceAction(pattern)` | A service pattern | [ServiceCalls](ServiceCalls.md), [GRPC](GRPC.md), [NATS](NATS.md) |
|
|
126
|
+
| `@Schedule(schedule)` | An interval or a cron expression | [Schedules](Schedules.md) |
|
|
127
|
+
| `@QueueAction(queue)` (`lakutata/com/queue`) | A message queue | [Queue](Queue.md) |
|
|
128
|
+
| `@MCPTool(name)` (`@lakutata/mcp`), `@DBusMethod(...)` (`@lakutata/dbus`) | An MCP tool, a D-Bus method | [MCP](MCP.md), [DBus](DBus.md) |
|
|
129
|
+
|
|
130
|
+
The decorators of the first five rows come from `lakutata/decorator/ctrl`, unless the table names another subpath.
|
|
131
|
+
They take the address first, then the DTO class of the input, then the action options; the DTO and the options can be
|
|
132
|
+
left out. `@Schedule` is the exception: it takes no DTO, as a scheduled job has no input.
|
|
133
|
+
|
|
134
|
+
The action options:
|
|
135
|
+
|
|
136
|
+
| Option | Default | Meaning |
|
|
137
|
+
|---|---|---|
|
|
138
|
+
| `name` | `'ControllerClass:method'` | The name of the action, unique for each transport |
|
|
139
|
+
| `description` | | A description: the help of a command, the listing of the actions |
|
|
140
|
+
| `middlewares` | | The middlewares of this action (see [Middlewares](Middlewares.md)) |
|
|
141
|
+
| `acl` | `false` | Check the request with the access rules of the entrypoint |
|
|
142
|
+
| `rule` | | An access rule of this action: `(context, input) => Promise<boolean>` |
|
|
143
|
+
| `groups` | | The groups of the action, used by the access rules and the listings (see [Entrypoint](Entrypoint.md)) |
|
|
144
|
+
|
|
145
|
+
## 4. The input
|
|
146
|
+
|
|
147
|
+
Each transport builds the input from its request:
|
|
148
|
+
|
|
149
|
+
| Transport | Input |
|
|
150
|
+
|---|---|
|
|
151
|
+
| HTTP | The query and the body, with the route parameters over them |
|
|
152
|
+
| Command line | The arguments and the options |
|
|
153
|
+
| Service | The message, without the keys of the pattern |
|
|
154
|
+
| Schedule | Nothing |
|
|
155
|
+
| Queue | The message |
|
|
156
|
+
|
|
157
|
+
The DTO of the action then validates it:
|
|
158
|
+
|
|
159
|
+
- the values of the query and of the route parameters are text: a schema that expects a number or a boolean needs
|
|
160
|
+
`.strict(false)` to convert `'42'` or `'true'`. The command line gives its arguments and options the type of their
|
|
161
|
+
property itself ([CLI](CLI.md));
|
|
162
|
+
- the defaults are applied, and the keys the DTO does not declare are removed (`extra` above);
|
|
163
|
+
- an invalid input stops the request with an `InvalidValueException`, answered with the status 400 over HTTP. The
|
|
164
|
+
message names the first invalid value: `"product" is required`. With the I18n middleware, it is in the language of
|
|
165
|
+
the request.
|
|
166
|
+
|
|
167
|
+
The method receives the validated input as a plain object, a copy: not an instance of the DTO class, which only gives it
|
|
168
|
+
its type. An action without a DTO receives the input as it arrived, with all its keys.
|
|
169
|
+
|
|
170
|
+
## 5. The result
|
|
171
|
+
|
|
172
|
+
What the method returns is sent back by the transport:
|
|
173
|
+
|
|
174
|
+
| Transport | A value | `undefined` | An error thrown |
|
|
175
|
+
|---|---|---|---|
|
|
176
|
+
| HTTP | JSON | Status 204 | Its `statusCode` (500 by default) and `{errno, errMsg}` |
|
|
177
|
+
| Command line | On stdout: text as it is, other values as JSON | Nothing | `error: <message>` on stderr, exit code 1 |
|
|
178
|
+
| Service | `{success: true, error: null, payload}` | `payload` left out | `{success: false, error, payload: null}`, the error with all its properties |
|
|
179
|
+
| Schedule | Ignored | | Given to `onError`, or logged |
|
|
180
|
+
|
|
181
|
+
`Result`, from `lakutata/com/entrypoint`, controls the response more closely:
|
|
182
|
+
|
|
183
|
+
- `Result.status(status, data, headers)`: an HTTP status and headers with the data;
|
|
184
|
+
- `Result.file(path)` (or a `Buffer`, or a stream, with `{filename, contentType, etag, headers}`): a file, as a download
|
|
185
|
+
when `filename` is given;
|
|
186
|
+
- `Result.events(producer)`: Server-Sent Events over HTTP, the data of each event on the command line. The producer
|
|
187
|
+
`(send, signal) => …` runs after the scope of the request is destroyed, so it should use components rather than
|
|
188
|
+
request-scoped providers.
|
|
189
|
+
|
|
190
|
+
The service transports cannot send a file or events: such a result fails the request.
|
|
191
|
+
|
|
192
|
+
## 6. Prefixes
|
|
193
|
+
|
|
194
|
+
`@Prefix` on a controller adds a prefix to the addresses of all its actions:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
import {DTO} from 'lakutata'
|
|
198
|
+
import {Controller} from 'lakutata/com/entrypoint'
|
|
199
|
+
import {CLIAction, GET, Prefix, ServiceAction} from 'lakutata/decorator/ctrl'
|
|
200
|
+
import {Expect} from 'lakutata/decorator/dto'
|
|
201
|
+
|
|
202
|
+
class UserIdDTO extends DTO {
|
|
203
|
+
@Expect(DTO.String().required())
|
|
204
|
+
public id: string
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
@Prefix({route: '/users', command: 'user', pattern: {resource: 'user'}})
|
|
208
|
+
class UserController extends Controller {
|
|
209
|
+
//GET /users/:id, the command "user:get <id>" and the message {resource: 'user', act: 'get', id}
|
|
210
|
+
@GET('/:id', UserIdDTO)
|
|
211
|
+
@CLIAction('get <id>', UserIdDTO)
|
|
212
|
+
@ServiceAction({act: 'get'}, UserIdDTO)
|
|
213
|
+
public async get(input: UserIdDTO): Promise<{ id: string }> {
|
|
214
|
+
return {id: input.id}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
- `route` is put before the HTTP routes, `command` before the commands (joined by a colon), and `pattern` is merged
|
|
220
|
+
into the service patterns. A key set to different values in the prefix and in an action is an error
|
|
221
|
+
(`InvalidPrefixException`).
|
|
222
|
+
- The scheduled jobs have no prefix.
|
|
223
|
+
- A controller takes the prefix of the nearest class that declares one: its own, or else the one of its parent.
|
|
224
|
+
|
|
225
|
+
## 7. Inheritance
|
|
226
|
+
|
|
227
|
+
A controller inherits the actions of its parent classes, so common actions can be written once in a base class. A
|
|
228
|
+
registered subclass serves them under its own prefix:
|
|
229
|
+
|
|
230
|
+
```typescript
|
|
231
|
+
import {Controller} from 'lakutata/com/entrypoint'
|
|
232
|
+
import {GET, Prefix} from 'lakutata/decorator/ctrl'
|
|
233
|
+
|
|
234
|
+
abstract class HealthController extends Controller {
|
|
235
|
+
@GET('/health')
|
|
236
|
+
public async health(): Promise<string> {
|
|
237
|
+
return 'ok'
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
//GET /v1/health
|
|
242
|
+
@Prefix({route: '/v1'})
|
|
243
|
+
class V1Controller extends HealthController {
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
//GET /v2/health
|
|
247
|
+
@Prefix({route: '/v2'})
|
|
248
|
+
class V2Controller extends HealthController {
|
|
249
|
+
}
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
- Register the subclasses only (`controllers: [V1Controller, V2Controller]`). A parent and a subclass registered
|
|
253
|
+
together, with the same address for an action, would conflict: the one registered last wins.
|
|
254
|
+
- A subclass can override an inherited method: the action keeps its address and runs the new code. A decorator on the
|
|
255
|
+
overriding method adds an address; the inherited one stays.
|
|
256
|
+
- The base class can be abstract.
|