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
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.
|