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.
Files changed (117) hide show
  1. package/README.md +50 -30
  2. package/README.zh-CN.md +130 -0
  3. package/dist/cjs/exports/QueueComponent.d.ts +1 -0
  4. package/dist/cjs/exports/QueueComponent.js +20 -0
  5. package/dist/esm/exports/QueueComponent.js +1 -0
  6. package/dist/types/exports/QueueComponent.d.ts +1 -0
  7. package/doc/en/Application.md +107 -0
  8. package/doc/en/CLI.md +274 -0
  9. package/doc/en/Cache.md +379 -0
  10. package/doc/en/Command.md +110 -0
  11. package/doc/en/ContainerdIntegration.md +17 -16
  12. package/doc/en/Controllers.md +256 -0
  13. package/doc/en/DBus.md +291 -0
  14. package/doc/en/DTOValidation.md +72 -43
  15. package/doc/en/Database.md +188 -0
  16. package/doc/en/DatabaseDrivers.md +287 -0
  17. package/doc/en/Decorator.md +52 -40
  18. package/doc/en/DockerIntegration.md +7 -6
  19. package/doc/en/Entities.md +240 -0
  20. package/doc/en/Entrypoint.md +216 -0
  21. package/doc/en/Exceptions.md +174 -0
  22. package/doc/en/GRPC.md +251 -0
  23. package/doc/en/GettingStarted.md +165 -0
  24. package/doc/en/HTTP.md +507 -0
  25. package/doc/en/HelperFunctions.md +11 -11
  26. package/doc/en/I18n.md +119 -115
  27. package/doc/en/Injection.md +144 -0
  28. package/doc/en/Installers.md +280 -0
  29. package/doc/en/Logger.md +206 -0
  30. package/doc/en/MCP.md +293 -0
  31. package/doc/en/Middlewares.md +213 -0
  32. package/doc/en/Migrations.md +202 -0
  33. package/doc/en/MongoEmbedded.md +149 -0
  34. package/doc/en/Monitor.md +164 -0
  35. package/doc/en/Mutex.md +27 -25
  36. package/doc/en/NATS.md +246 -0
  37. package/doc/en/NativeAddons.md +212 -0
  38. package/doc/en/Objects.md +143 -0
  39. package/doc/en/Overview.md +62 -85
  40. package/doc/en/Packaging.md +193 -0
  41. package/doc/en/ProjectStructure.md +124 -0
  42. package/doc/en/Queue.md +269 -0
  43. package/doc/en/Schedules.md +217 -0
  44. package/doc/en/Security.md +10 -9
  45. package/doc/en/ServiceCalls.md +504 -0
  46. package/doc/en/Time.md +19 -20
  47. package/doc/en/UpgradingFrom1.md +435 -0
  48. package/doc/en/UpgradingFrom2.md +315 -0
  49. package/doc/en/Workspaces.md +189 -0
  50. package/doc/en/examples/BackgroundJobs.md +677 -0
  51. package/doc/en/examples/CommandLineTool.md +664 -0
  52. package/doc/en/examples/DesktopApp.md +809 -0
  53. package/doc/en/examples/DeviceService.md +607 -0
  54. package/doc/en/examples/MCPServer.md +653 -0
  55. package/doc/en/examples/README.md +16 -0
  56. package/doc/en/examples/RestAPI.md +490 -0
  57. package/doc/en/examples/ServiceCalls.md +829 -0
  58. package/doc/en/examples/WebApp.md +568 -0
  59. package/doc/zh/Application.md +75 -0
  60. package/doc/zh/CLI.md +250 -0
  61. package/doc/zh/Cache.md +328 -0
  62. package/doc/zh/Command.md +78 -0
  63. package/doc/zh/ContainerdIntegration.md +9 -9
  64. package/doc/zh/Controllers.md +228 -0
  65. package/doc/zh/DBus.md +257 -0
  66. package/doc/zh/DTOValidation.md +49 -19
  67. package/doc/zh/Database.md +175 -0
  68. package/doc/zh/DatabaseDrivers.md +261 -0
  69. package/doc/zh/Decorator.md +25 -18
  70. package/doc/zh/DockerIntegration.md +6 -6
  71. package/doc/zh/Entities.md +224 -0
  72. package/doc/zh/Entrypoint.md +193 -0
  73. package/doc/zh/Exceptions.md +158 -0
  74. package/doc/zh/GRPC.md +226 -0
  75. package/doc/zh/GettingStarted.md +148 -0
  76. package/doc/zh/HTTP.md +445 -0
  77. package/doc/zh/HelperFunctions.md +2 -2
  78. package/doc/zh/I18n.md +26 -26
  79. package/doc/zh/Injection.md +129 -0
  80. package/doc/zh/Installers.md +232 -0
  81. package/doc/zh/Logger.md +188 -0
  82. package/doc/zh/MCP.md +265 -0
  83. package/doc/zh/Middlewares.md +198 -0
  84. package/doc/zh/Migrations.md +178 -0
  85. package/doc/zh/MongoEmbedded.md +118 -0
  86. package/doc/zh/Monitor.md +145 -0
  87. package/doc/zh/Mutex.md +10 -10
  88. package/doc/zh/NATS.md +207 -0
  89. package/doc/zh/NativeAddons.md +180 -0
  90. package/doc/zh/Objects.md +119 -0
  91. package/doc/zh/Overview.md +46 -71
  92. package/doc/zh/Packaging.md +154 -0
  93. package/doc/zh/ProjectStructure.md +102 -0
  94. package/doc/zh/Queue.md +214 -0
  95. package/doc/zh/Schedules.md +193 -0
  96. package/doc/zh/Security.md +6 -6
  97. package/doc/zh/ServiceCalls.md +459 -0
  98. package/doc/zh/Time.md +2 -2
  99. package/doc/zh/UpgradingFrom1.md +347 -0
  100. package/doc/zh/UpgradingFrom2.md +230 -0
  101. package/doc/zh/Workspaces.md +150 -0
  102. package/doc/zh/examples/BackgroundJobs.md +618 -0
  103. package/doc/zh/examples/CommandLineTool.md +618 -0
  104. package/doc/zh/examples/DesktopApp.md +748 -0
  105. package/doc/zh/examples/DeviceService.md +546 -0
  106. package/doc/zh/examples/MCPServer.md +604 -0
  107. package/doc/zh/examples/README.md +14 -0
  108. package/doc/zh/examples/RestAPI.md +458 -0
  109. package/doc/zh/examples/ServiceCalls.md +765 -0
  110. package/doc/zh/examples/WebApp.md +520 -0
  111. package/package.json +46 -17
  112. package/doc/en/ActionPattern.md +0 -408
  113. package/doc/en/Core.md +0 -312
  114. package/doc/en/ORMIntegration.md +0 -341
  115. package/doc/zh/ActionPattern.md +0 -334
  116. package/doc/zh/Core.md +0 -256
  117. package/doc/zh/ORMIntegration.md +0 -319
package/doc/en/GRPC.md ADDED
@@ -0,0 +1,251 @@
1
+ # Lakutata gRPC
2
+
3
+ The package `@lakutata/grpc` serves the service actions of an application over gRPC, and calls them from another
4
+ application. Any language with a gRPC library can call them too. It is written in TypeScript over the HTTP/2 module of
5
+ Node.js (`^22.13.0 || >=24.11.0`): no native addon and no generated code. `lakutata` does not include it, so install it
6
+ next to it:
7
+
8
+ ```bash
9
+ npm install lakutata @lakutata/grpc
10
+ ```
11
+
12
+ ## 1. Serving the service actions
13
+
14
+ `grpcServiceEntrypoint()`, from `@lakutata/grpc/service`, is a `service` adapter of the entrypoint. The `@ServiceAction`
15
+ methods of the controllers become callable over gRPC by their patterns, as over the other service transports.
16
+
17
+ ```typescript
18
+ import {Application, Exception} from 'lakutata'
19
+ import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
20
+ import {ServiceAction} from 'lakutata/decorator/ctrl'
21
+ import {grpcServiceEntrypoint} from '@lakutata/grpc/service'
22
+
23
+ class OrderNotFoundException extends Exception {
24
+ public errno: string = 'E_ORDER_NOT_FOUND'
25
+ }
26
+
27
+ class OrderController extends Controller {
28
+ //Called with {cmd: 'order.get', id: 1}
29
+ @ServiceAction({cmd: 'order.get'})
30
+ public async get(input: { id: number }): Promise<{ id: number, status: string }> {
31
+ if (input.id === 0) throw new OrderNotFoundException('Order {id} not found', {id: input.id})
32
+ return {id: input.id, status: 'paid'}
33
+ }
34
+ }
35
+
36
+ Application.run(() => ({
37
+ id: 'orders.service',
38
+ name: 'Orders',
39
+ components: {
40
+ entrypoint: buildEntrypoints({
41
+ controllers: [OrderController],
42
+ service: grpcServiceEntrypoint({port: 50051})
43
+ })
44
+ },
45
+ bootstrap: ['entrypoint']
46
+ }))
47
+ ```
48
+
49
+ The options of `grpcServiceEntrypoint`:
50
+
51
+ | Option | Default | Meaning |
52
+ |---|---|---|
53
+ | `port` | `50051` | The port; `0` picks a free one |
54
+ | `host` | All the interfaces | The address to listen on |
55
+ | `socketPath` | | A unix socket, instead of the port and the host |
56
+ | `tls` | None (plain HTTP/2) | `{cert, key}` for TLS; with `ca` too, the clients must present a certificate signed by it (mutual TLS) |
57
+ | `maxMessageSize` | 4 MiB | The largest request, before and after decompression; a larger one fails with `RESOURCE_EXHAUSTED` |
58
+ | `compression` | `'identity'` | `'gzip'` or `'deflate'` to compress the responses for the clients that accept it. Compressed requests are always accepted |
59
+ | `listening` | | `(address) => void`, called once the server listens; the start waits for it |
60
+ | `middlewares` | | The middlewares of the actions called over gRPC, run after the ones of the entrypoint |
61
+ | `name` | | The name of the adapter, to tell several apart |
62
+
63
+ The server has no reflection service and no health service.
64
+
65
+ ## 2. Calling them from another application
66
+
67
+ `GRPCServiceProxy` calls the actions of a remote application. It is a component: declare it under `components` with
68
+ `buildGRPCServiceProxy`, one per remote application.
69
+
70
+ ```typescript
71
+ import {Application} from 'lakutata'
72
+ import {ServiceInvokeException} from 'lakutata/com/entrypoint'
73
+ import {GRPCError, GRPCStatus} from '@lakutata/grpc'
74
+ import {buildGRPCServiceProxy, type GRPCServiceProxy} from '@lakutata/grpc/service'
75
+
76
+ Application
77
+ .run(() => ({
78
+ id: 'shop.app',
79
+ name: 'Shop',
80
+ components: {
81
+ orders: buildGRPCServiceProxy({address: 'orders.internal:50051'})
82
+ }
83
+ }))
84
+ .onLaunched(async (app: Application): Promise<void> => {
85
+ const orders: GRPCServiceProxy = await app.getObject<GRPCServiceProxy>('orders')
86
+ try {
87
+ //The pattern and the input in one object, and a deadline of 5 seconds
88
+ console.log(await orders.invoke({cmd: 'order.get', id: 1}, 5000))
89
+ } catch (error) {
90
+ if (error instanceof ServiceInvokeException) {
91
+ //The action failed: its errno, its message and its other properties
92
+ console.log(error.errno, error.message)
93
+ } else if (error instanceof GRPCError && error.code === GRPCStatus.UNAVAILABLE) {
94
+ console.log('The orders service is unreachable')
95
+ } else {
96
+ throw error
97
+ }
98
+ }
99
+ })
100
+ ```
101
+
102
+ `invoke(input, timeout)` returns the result of the action (`null` when the action returns nothing). The timeout, 10
103
+ minutes by default, is the deadline of the gRPC call: when it passes, the call fails and the action is aborted on the
104
+ service as well (its `this.signal` fires).
105
+
106
+ The options of `buildGRPCServiceProxy`:
107
+
108
+ | Option | Default | Meaning |
109
+ |---|---|---|
110
+ | `address` | Required | `host:port`, `tcp://host:port`, or a unix socket: `/path` or `unix:///path` |
111
+ | `tls` | None | `{ca, cert, key, servername}`, all optional. Any value, even `{}`, turns TLS on; `cert` and `key` are the client's certificate for mutual TLS |
112
+ | `clientId` | A random UUID | The id of this client, sent with each call (see section 3) |
113
+ | `compression` | `'identity'` | `'gzip'` or `'deflate'` to compress the requests |
114
+ | `maxMessageSize` | No limit | The largest response accepted |
115
+ | `reconnectDelay` | `1000` | The milliseconds to wait before opening the event stream again after it ended (see section 4) |
116
+
117
+ The proxy connects on its first call and never retries a call by itself.
118
+
119
+ ## 3. Client ids, metadata and deadlines
120
+
121
+ Each call carries the metadata `id`, the id of the client: the `clientId` of the proxy, or the `id` header a client in
122
+ another language sends. The action reads it as `this.context.clientId`.
123
+
124
+ `grpcCallOfContext(context)` gives an action the whole gRPC call: the metadata the client sent, its deadline, its peer.
125
+ It returns `undefined` when the request came over another transport.
126
+
127
+ ```typescript
128
+ import {Controller, type ServiceContext} from 'lakutata/com/entrypoint'
129
+ import {ServiceAction} from 'lakutata/decorator/ctrl'
130
+ import {grpcCallOfContext} from '@lakutata/grpc/service'
131
+ import type {GRPCServerCall} from '@lakutata/grpc'
132
+
133
+ class OrderController extends Controller {
134
+ @ServiceAction({cmd: 'order.get'})
135
+ public async get(input: { id: number }): Promise<{ id: number, tenant: string }> {
136
+ const call: GRPCServerCall | undefined = grpcCallOfContext(this.context as ServiceContext)
137
+ //Metadata sent by a client: grpcurl -H 'tenant: acme' ...
138
+ const tenant: string = call?.metadata('tenant')[0] ?? 'default'
139
+ return {id: input.id, tenant: tenant}
140
+ }
141
+ }
142
+ ```
143
+
144
+ - `GRPCServiceProxy` sends no metadata other than `id`. A client that needs other metadata (an authorization token) uses
145
+ `GRPCConnection` (see section 8) or a client in another language.
146
+ - The values of a metadata key are split on commas, so an id should not contain one.
147
+ - When the client cancels the call or its deadline passes, the action's `this.signal` is aborted and its result is
148
+ dropped. A long action should watch the signal.
149
+
150
+ ## 4. Events
151
+
152
+ An action pushes events to a client with `entrypoint.notify(clientId, event, ...args)`, or to every connected client
153
+ with `entrypoint.broadcast(event, ...args)`. `notify` returns whether the client received the event, `broadcast` the
154
+ number of clients.
155
+
156
+ ```typescript
157
+ import {Controller, type Entrypoint, type ServiceContext} from 'lakutata/com/entrypoint'
158
+ import {ServiceAction} from 'lakutata/decorator/ctrl'
159
+ import {Inject} from 'lakutata/decorator/di'
160
+
161
+ class OrderController extends Controller {
162
+ @Inject('entrypoint')
163
+ protected readonly entrypoint: Entrypoint
164
+
165
+ @ServiceAction({cmd: 'order.ship'})
166
+ public async ship(input: { id: number }): Promise<void> {
167
+ const clientId: string | undefined = (this.context as ServiceContext).clientId
168
+ if (clientId) this.entrypoint.notify(clientId, 'orderChanged', {id: input.id, status: 'shipped'})
169
+ }
170
+ }
171
+ ```
172
+
173
+ On the other side, `orders.on('orderChanged', listener)` listens to them; `once` and `off` work as on an event
174
+ emitter. The proxy opens its event stream with the first listener and closes it with the last one. When the stream
175
+ ends (the service restarted), it is opened again after `reconnectDelay`.
176
+
177
+ The events are not stored: a client that is not connected when an event is sent misses it.
178
+
179
+ ## 5. Errors
180
+
181
+ A failed action does not make the gRPC call fail: the call succeeds, and its response carries the error, with its
182
+ message and all its properties (`errno`, `statusCode`, and the stack). `GRPCServiceProxy` throws it as a
183
+ `ServiceInvokeException` from `lakutata/com/entrypoint`: its message is the remote message, its `errno` and other
184
+ properties are copied, and its `service` is the address of the proxy.
185
+
186
+ A call that fails at the gRPC level is thrown as a `GRPCError` from `@lakutata/grpc`, whose `code` is a `GRPCStatus`:
187
+
188
+ | Status | When |
189
+ |---|---|
190
+ | `UNAVAILABLE` | The service cannot be reached |
191
+ | `DEADLINE_EXCEEDED` | The deadline passed |
192
+ | `CANCELLED` | The call was cancelled |
193
+ | `RESOURCE_EXHAUSTED` | A message is larger than `maxMessageSize` |
194
+ | `UNIMPLEMENTED` | An unknown method, or a compression the server does not support |
195
+
196
+ An action that no pattern matches fails with the errno `E_CONTROLLER_ACTION_NOT_FOUND`, as over the other service
197
+ transports.
198
+
199
+ ## 6. Calling from other languages
200
+
201
+ `SERVICE_PROTO` holds the `.proto` file of the service. Generate the clients of other languages from it:
202
+
203
+ ```typescript
204
+ import {writeFileSync} from 'node:fs'
205
+ import {SERVICE_PROTO} from '@lakutata/grpc/service'
206
+
207
+ writeFileSync('service.proto', SERVICE_PROTO)
208
+ ```
209
+
210
+ ```text
211
+ service Service { // package lakutata.service.v1
212
+ rpc Invoke (InvokeRequest) returns (InvokeResponse); // data: the JSON of the pattern and the input
213
+ rpc Events (EventsRequest) returns (stream Event); // the events of the client, "client" (its id) first
214
+ }
215
+ message InvokeResponse { bool success = 1; bytes payload = 2; bytes error = 3; } // payload, error: JSON
216
+ message Event { string event = 1; bytes args = 2; } // args: a JSON array
217
+ ```
218
+
219
+ A call sends the JSON of the pattern and the input in `InvokeRequest.data`, and the id of the client in the metadata
220
+ `id`. The response holds the JSON of the result in `payload`, or the JSON of the error in `error`. With grpcurl (it
221
+ shows the `bytes` fields in base64):
222
+
223
+ ```bash
224
+ DATA=$(printf '{"cmd":"order.get","id":7}' | base64)
225
+ grpcurl -plaintext -proto service.proto -H 'id: go-1' -d "{\"data\": \"$DATA\"}" localhost:50051 lakutata.service.v1.Service/Invoke
226
+ ```
227
+
228
+ With Python (the `grpcio` package, the stubs generated by `grpcio-tools`):
229
+
230
+ ```python
231
+ import json
232
+ import grpc
233
+ import service_pb2 as pb
234
+ import service_pb2_grpc as rpc
235
+
236
+ stub = rpc.ServiceStub(grpc.insecure_channel('localhost:50051'))
237
+ response = stub.Invoke(pb.InvokeRequest(data=json.dumps({'cmd': 'order.get', 'id': 5}).encode()), metadata=[('id', 'python-1')], timeout=5)
238
+ print(json.loads(response.payload) if response.success else json.loads(response.error))
239
+ ```
240
+
241
+ ## 7. Stopping
242
+
243
+ When the application stops, the service refuses the new calls: the proxy throws them as a `ServiceInvokeException`
244
+ whose `errno` is `E_ENTRYPOINT_STOPPED`. The calls in progress are given the `shutdownTimeout` of the entrypoint (30
245
+ seconds by default) to complete. Then the event streams end and the server closes.
246
+
247
+ ## 8. The lower layers
248
+
249
+ The package also has the layers the service is built on, usable without lakutata: `GRPCConnection`, a client for any
250
+ gRPC server (unary, server streaming and bidirectional calls, metadata, TLS); `GRPCServer`; and `encode`/`decode`, a
251
+ protobuf codec driven by schemas. The README of `@lakutata/grpc` describes them.
@@ -0,0 +1,165 @@
1
+ # Getting Started
2
+
3
+ This page creates a first Lakutata project from a template, then shows how to add the framework to an existing project
4
+ and write an application by hand.
5
+
6
+ ## 1. Requirements
7
+
8
+ - Node.js `^22.13.0 || >=24.11.0`: 22.13 or a later 22.x, or 24.11 and later (not 23).
9
+ - TypeScript, with its decorators (`experimentalDecorators`) and their metadata (`emitDecoratorMetadata`). The framework
10
+ publishes the compiler options it needs as `lakutata/tsconfig.json`; the templates use TypeScript 6.
11
+ - ES modules (`"type": "module"`), as in the templates; the package also ships CommonJS builds.
12
+
13
+ ## 2. Creating a project
14
+
15
+ ```bash
16
+ npx @lakutata/cli create --name my-app
17
+ cd my-app
18
+ npm run dev
19
+ ```
20
+
21
+ `npm run dev` compiles the project and starts it; the log shows `my-app started in development mode`, and the
22
+ application answers on port 8080:
23
+
24
+ ```bash
25
+ curl 'http://localhost:8080/example?name=you'
26
+ # {"message":"Hello, you!"}
27
+ ```
28
+
29
+ The `default` template is a small HTTP application. Other templates start from another kind of application:
30
+
31
+ | Template | Application |
32
+ |---|---|
33
+ | `default` | A minimal application serving HTTP actions |
34
+ | `cli` | A command line tool built into executables that run without Node.js |
35
+ | `service` | An HTTP API deployed as a Docker image |
36
+ | `web-react`, `web-vue` | An HTTP API serving a React or Vue single-page application (Vite) |
37
+ | `web-next`, `web-nuxt` | An HTTP API serving a Next.js or Nuxt application from the same process |
38
+ | `electron-react`, `electron-vue` | A desktop application: Electron, its main process serving a React or Vue renderer over IPC |
39
+
40
+ ```bash
41
+ npx @lakutata/cli create --name shop --template service --with i18n,installer
42
+ ```
43
+
44
+ `--with` adds optional features: `i18n` (messages in several languages) to every template, `installer` (installers
45
+ and a system service) to `cli`, `service` and the `web-*` templates. In a terminal, the tool offers them when `--with`
46
+ is not given. [ProjectStructure](ProjectStructure.md) describes the files of the templates, and
47
+ [Command](Command.md) the options of `lakutata create`.
48
+
49
+ The scripts of the `default` template:
50
+
51
+ | Script | What it does |
52
+ |---|---|
53
+ | `npm run dev` | Compiles the project into `build/` and starts it |
54
+ | `npm run build` | Compiles the project into `build/` |
55
+ | `npm start` | Starts the compiled application |
56
+ | `npm test` | Compiles the project and runs its tests (`node --test`) |
57
+
58
+ The other templates are workspaces of several packages; their scripts work on every package, and `npm test` builds them
59
+ before running their tests ([ProjectStructure](ProjectStructure.md), section 2).
60
+
61
+ ## 3. Adding Lakutata to an existing project
62
+
63
+ ```bash
64
+ npm install lakutata express
65
+ npm install --save-dev typescript @types/node @types/express @lakutata/cli
66
+ ```
67
+
68
+ `express` is the library of the HTTP adapter used below. The adapters and drivers are optional dependencies, installed
69
+ only when they are used: `fastify`, `commander` and `@inquirer/prompts` (command line tools), `socket.io`, `ws`, the
70
+ database drivers (`pg`, `mysql2`, `better-sqlite3`…), the cache stores (`redis`…).
71
+
72
+ `tsconfig.json`:
73
+
74
+ ```json
75
+ {
76
+ "extends": "lakutata/tsconfig.json",
77
+ "compilerOptions": {
78
+ "rootDir": "src",
79
+ "outDir": "build"
80
+ },
81
+ "include": ["src"]
82
+ }
83
+ ```
84
+
85
+ Keep `include`: the published options compile JavaScript files too (`allowJs`), so without it a configuration file at
86
+ the root of the project (`eslint.config.js`, `vite.config.ts`) breaks the build. In the `package.json`, set
87
+ `"type": "module"` and add scripts such as those of the templates:
88
+
89
+ ```json
90
+ {
91
+ "type": "module",
92
+ "main": "build/App.js",
93
+ "scripts": {
94
+ "clean": "lakutata clean",
95
+ "build": "npm run clean && tsc",
96
+ "start": "node build/App.js"
97
+ }
98
+ }
99
+ ```
100
+
101
+ The relative imports of the sources end in `.js` (`import {config} from './config/Config.js'`), as Node.js resolves
102
+ them in the compiled code.
103
+
104
+ ## 4. A first application
105
+
106
+ `src/App.ts`:
107
+
108
+ ```typescript
109
+ import {Application, DTO, type ILogger} from 'lakutata'
110
+ import {buildEntrypoints, Controller} from 'lakutata/com/entrypoint'
111
+ import {GET, ServiceAction} from 'lakutata/decorator/ctrl'
112
+ import {Expect} from 'lakutata/decorator/dto'
113
+ import {expressHTTPEntrypoint} from 'lakutata/adapter/express'
114
+
115
+ class GreetDTO extends DTO {
116
+ @Expect(DTO.String().optional().default('world'))
117
+ public name: string
118
+ }
119
+
120
+ class GreetController extends Controller {
121
+ //GET /greet?name=you, and the service action {cmd: 'greet', name: 'you'}
122
+ @GET('/greet', GreetDTO)
123
+ @ServiceAction({cmd: 'greet'}, GreetDTO)
124
+ public async greet(input: GreetDTO): Promise<{ message: string }> {
125
+ return {message: `Hello, ${input.name}!`}
126
+ }
127
+ }
128
+
129
+ Application
130
+ .run(() => ({
131
+ id: 'com.example.greeter',
132
+ name: 'Greeter',
133
+ components: {
134
+ entrypoint: buildEntrypoints({
135
+ controllers: [GreetController],
136
+ http: expressHTTPEntrypoint({port: 8080})
137
+ })
138
+ },
139
+ bootstrap: ['entrypoint']
140
+ }))
141
+ .onLaunched((app: Application, log: ILogger): void => log.info('%s started', app.appName))
142
+ ```
143
+
144
+ ```bash
145
+ npm run build && npm start
146
+ curl 'http://localhost:8080/greet?name=you'
147
+ # {"message":"Hello, you!"}
148
+ ```
149
+
150
+ - `GreetDTO` validates the input: `name` is optional, `world` by default ([DTOValidation](DTOValidation.md)).
151
+ - `GreetController` declares an action, here served over HTTP; the same method could also be a command of a command
152
+ line tool or a service action ([Controllers](Controllers.md)).
153
+ - `Application.run()` declares the components of the application: the `entrypoint` component serves the controllers
154
+ through its adapters, and `bootstrap` creates it at launch ([Application](Application.md),
155
+ [Entrypoint](Entrypoint.md)).
156
+
157
+ ## 5. Next
158
+
159
+ - [Examples](examples/README.md): whole applications built from the templates, step by step.
160
+ - [ProjectStructure](ProjectStructure.md): the files of a project and its scripts.
161
+ - [Application](Application.md), [Objects](Objects.md) and [Injection](Injection.md): how an application is declared,
162
+ and how its objects are created and obtain one another.
163
+ - [Controllers](Controllers.md) and [HTTP](HTTP.md): actions, their input and their responses.
164
+ - [Command](Command.md), [Workspaces](Workspaces.md), [NativeAddons](NativeAddons.md), [Packaging](Packaging.md) and
165
+ [Installers](Installers.md): the `lakutata` command, the packages of a project, and the packed applications.