lakutata 3.1.0 → 3.1.2
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 +2 -2
- package/README.zh-CN.md +1 -1
- package/doc/en/Command.md +3 -0
- package/doc/en/FFI.md +458 -0
- package/doc/en/HTTP.md +8 -4
- package/doc/en/NativeAddons.md +2 -1
- package/doc/en/Overview.md +5 -2
- package/doc/en/ServiceCalls.md +8 -4
- package/doc/en/UpgradingFrom2.md +4 -2
- package/doc/en/WebRTC.md +1364 -0
- package/doc/en/examples/Meeting.md +573 -0
- package/doc/en/examples/README.md +1 -0
- package/doc/zh/Command.md +2 -0
- package/doc/zh/FFI.md +337 -0
- package/doc/zh/HTTP.md +3 -2
- package/doc/zh/NativeAddons.md +1 -1
- package/doc/zh/Overview.md +3 -1
- package/doc/zh/ServiceCalls.md +5 -2
- package/doc/zh/UpgradingFrom2.md +1 -1
- package/doc/zh/WebRTC.md +1049 -0
- package/doc/zh/examples/Meeting.md +524 -0
- package/doc/zh/examples/README.md +1 -0
- package/package.json +14 -14
package/README.md
CHANGED
|
@@ -144,8 +144,8 @@ on. Each package's README lists its exceptions.
|
|
|
144
144
|
`Entrypoint.md`, `Middlewares.md`, `HTTP.md`, `CLI.md`, `ServiceCalls.md`, `GRPC.md`, `NATS.md`, `Schedules.md`,
|
|
145
145
|
`Queue.md`, `Database.md`, `DatabaseDrivers.md`, `MongoEmbedded.md`, `Entities.md`, `Migrations.md`, `Cache.md`,
|
|
146
146
|
`Mutex.md`, `I18n.md`, `DockerIntegration.md`, `ContainerdIntegration.md`, `Monitor.md`, `Security.md`, `DBus.md`,
|
|
147
|
-
`MCP.md`, `Command.md`, `Workspaces.md`, `NativeAddons.md`, `Packaging.md`, `Installers.md`;
|
|
148
|
-
`doc/en/examples` and `doc/zh/examples`: whole applications built from the templates.
|
|
147
|
+
`MCP.md`, `WebRTC.md`, `FFI.md`, `Command.md`, `Workspaces.md`, `NativeAddons.md`, `Packaging.md`, `Installers.md`;
|
|
148
|
+
and the examples, in `doc/en/examples` and `doc/zh/examples`: whole applications built from the templates.
|
|
149
149
|
- `@lakutata/cli`: creating, building and packaging applications.
|
|
150
150
|
- The official Agent Skill for AI coding assistants that work on lakutata projects:
|
|
151
151
|
[lakutata.com/skill](https://lakutata.com/skill/).
|
package/README.zh-CN.md
CHANGED
|
@@ -121,7 +121,7 @@ Application
|
|
|
121
121
|
## 另见
|
|
122
122
|
|
|
123
123
|
- 网站 <https://lakutata.com/zh/>:指南、示例和 API 参考,中英文都有。
|
|
124
|
-
- 指南在这个包的 `doc/zh`(中文)和 `doc/en`(英文)中:`Overview.md`、`GettingStarted.md`、`ProjectStructure.md`、`UpgradingFrom2.md`、`UpgradingFrom1.md`、`Application.md`、`Objects.md`、`Injection.md`、`DTOValidation.md`、`Exceptions.md`、`Logger.md`、`Decorator.md`、`Time.md`、`HelperFunctions.md`、`Controllers.md`、`Entrypoint.md`、`Middlewares.md`、`HTTP.md`、`CLI.md`、`ServiceCalls.md`、`GRPC.md`、`NATS.md`、`Schedules.md`、`Queue.md`、`Database.md`、`DatabaseDrivers.md`、`MongoEmbedded.md`、`Entities.md`、`Migrations.md`、`Cache.md`、`Mutex.md`、`I18n.md`、`DockerIntegration.md`、`ContainerdIntegration.md`、`Monitor.md`、`Security.md`、`DBus.md`、`MCP.md`、`Command.md`、`Workspaces.md`、`NativeAddons.md`、`Packaging.md`、`Installers.md`;示例在 `doc/zh/examples` 和 `doc/en/examples` 中:从模板构建的完整应用。
|
|
124
|
+
- 指南在这个包的 `doc/zh`(中文)和 `doc/en`(英文)中:`Overview.md`、`GettingStarted.md`、`ProjectStructure.md`、`UpgradingFrom2.md`、`UpgradingFrom1.md`、`Application.md`、`Objects.md`、`Injection.md`、`DTOValidation.md`、`Exceptions.md`、`Logger.md`、`Decorator.md`、`Time.md`、`HelperFunctions.md`、`Controllers.md`、`Entrypoint.md`、`Middlewares.md`、`HTTP.md`、`CLI.md`、`ServiceCalls.md`、`GRPC.md`、`NATS.md`、`Schedules.md`、`Queue.md`、`Database.md`、`DatabaseDrivers.md`、`MongoEmbedded.md`、`Entities.md`、`Migrations.md`、`Cache.md`、`Mutex.md`、`I18n.md`、`DockerIntegration.md`、`ContainerdIntegration.md`、`Monitor.md`、`Security.md`、`DBus.md`、`MCP.md`、`WebRTC.md`、`FFI.md`、`Command.md`、`Workspaces.md`、`NativeAddons.md`、`Packaging.md`、`Installers.md`;示例在 `doc/zh/examples` 和 `doc/en/examples` 中:从模板构建的完整应用。
|
|
125
125
|
- `@lakutata/cli`:创建、构建和打包应用。
|
|
126
126
|
- 官方 Agent Skill(智能体技能),供 AI 编程助手在 lakutata 项目中使用:<https://lakutata.com/zh/skill/>。
|
|
127
127
|
|
package/doc/en/Command.md
CHANGED
|
@@ -81,6 +81,9 @@ packages, run a script in each of them in the order of their dependencies, give
|
|
|
81
81
|
`lakutata add <name> --native gyp|cmake|rust` adds a native addon written in C++ or Rust, and
|
|
82
82
|
`lakutata native <link|include|define> <value>` links libraries to it. See [NativeAddons](NativeAddons.md).
|
|
83
83
|
|
|
84
|
+
`lakutata ffi generate <header>` writes the class of a native library of `@lakutata/ffi` from its C header. See
|
|
85
|
+
[FFI](FFI.md), section 9.
|
|
86
|
+
|
|
84
87
|
## 4. Migrations and translations
|
|
85
88
|
|
|
86
89
|
`lakutata migration <action>` generates, creates, runs, reverts and shows the migrations of the databases of an
|
package/doc/en/FFI.md
ADDED
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
# Lakutata FFI
|
|
2
|
+
|
|
3
|
+
The package `@lakutata/ffi` calls the C functions of native libraries (`.so`, `.dylib`, `.dll`) from Node.js, the main
|
|
4
|
+
process of Electron and Bun: the APIs of the system, the SDKs of devices, existing C libraries. Nothing is
|
|
5
|
+
compiled and no addon is written: a library is declared with the C declarations of its headers, and each library is a
|
|
6
|
+
component of the application. It is built on [koffi](https://koffi.dev/). It is not part of `lakutata`:
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
npm install lakutata @lakutata/ffi
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
koffi comes with a package of prebuilt binaries for each platform, installed with it. npm may warn that the install
|
|
13
|
+
script of koffi was not run: it is not needed.
|
|
14
|
+
|
|
15
|
+
A C function called with a wrong prototype, or given a pointer it should not get, ends the process: no exception can
|
|
16
|
+
catch that. Copy the declarations from the headers of the library, and test them.
|
|
17
|
+
|
|
18
|
+
## 1. A library
|
|
19
|
+
|
|
20
|
+
A library is a subclass of `NativeLibrary`: `@Library` gives its file names and its C declarations, `@Native` binds a
|
|
21
|
+
function to a property, `@NativeVariable` a variable. The TypeScript type of each property is the type of the
|
|
22
|
+
function.
|
|
23
|
+
|
|
24
|
+
```typescript
|
|
25
|
+
import {Application, Component} from 'lakutata'
|
|
26
|
+
import {Inject} from 'lakutata/decorator/di'
|
|
27
|
+
import {buildNativeLibraryOptions, Library, Native, NativeLibrary, NativeVariable, type Out, type Pointer, readArray} from '@lakutata/ffi'
|
|
28
|
+
|
|
29
|
+
type RowCallback = (context: Pointer | null, count: number, values: Pointer, names: Pointer) => number
|
|
30
|
+
|
|
31
|
+
@Library({
|
|
32
|
+
names: {darwin: 'libsqlite3.dylib', linux: 'libsqlite3.so.0', win32: 'winsqlite3.dll'},
|
|
33
|
+
declarations: `
|
|
34
|
+
typedef struct sqlite3 sqlite3;
|
|
35
|
+
typedef int (*sqlite3_callback)(void *context, int count, char **values, char **names);
|
|
36
|
+
#define SQLITE_OK 0
|
|
37
|
+
int sqlite3_close(sqlite3 *db);
|
|
38
|
+
`
|
|
39
|
+
})
|
|
40
|
+
export class SQLite extends NativeLibrary {
|
|
41
|
+
@Native('int sqlite3_open(const char *filename, _Out_ sqlite3 **db)')
|
|
42
|
+
public readonly open: (filename: string, db: Out<Pointer>) => number
|
|
43
|
+
|
|
44
|
+
//Runs in a thread of the pool; the error message it gives is freed by sqlite3_free
|
|
45
|
+
@Native('int sqlite3_exec(sqlite3 *db, const char *sql, sqlite3_callback callback, void *context, _Out_ char **error)', {async: true, free: 'sqlite3_free'})
|
|
46
|
+
public readonly exec: (db: Pointer, sql: string, callback: RowCallback | null, context: null, error: Out<string>) => Promise<number>
|
|
47
|
+
|
|
48
|
+
//The function of the declarations named as the property
|
|
49
|
+
@Native()
|
|
50
|
+
public readonly sqlite3_close: (db: Pointer) => number
|
|
51
|
+
|
|
52
|
+
@NativeVariable('extern const char sqlite3_version[]')
|
|
53
|
+
public readonly version: string
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
class Store extends Component {
|
|
57
|
+
@Inject('sqlite')
|
|
58
|
+
protected readonly sqlite: SQLite
|
|
59
|
+
|
|
60
|
+
public async tables(): Promise<string[]> {
|
|
61
|
+
const db: Out<Pointer> = [null]
|
|
62
|
+
if (this.sqlite.open(':memory:', db) !== Number(this.sqlite.constants.SQLITE_OK)) throw new Error('open failed')
|
|
63
|
+
const names: string[] = []
|
|
64
|
+
const error: Out<string> = [null]
|
|
65
|
+
await this.sqlite.exec(db[0]!, 'SELECT name FROM sqlite_master', (context: Pointer | null, count: number, values: Pointer): number => {
|
|
66
|
+
names.push(...readArray(values, 'char *', count) as string[])
|
|
67
|
+
return 0
|
|
68
|
+
}, null, error)
|
|
69
|
+
this.sqlite.sqlite3_close(db[0]!)
|
|
70
|
+
return names
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
Application.run(() => ({
|
|
75
|
+
id: 'store.app',
|
|
76
|
+
name: 'Store',
|
|
77
|
+
components: {
|
|
78
|
+
sqlite: buildNativeLibraryOptions(SQLite),
|
|
79
|
+
store: {class: Store}
|
|
80
|
+
}
|
|
81
|
+
}))
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- **The names**: one name (`sqlite3` is completed as each platform names its libraries: `libsqlite3.dylib`,
|
|
85
|
+
`libsqlite3.so`, `sqlite3.dll`), a file, or one by platform (the names of `process.platform`, `default` for the
|
|
86
|
+
others). `c` is the C library and `m` its mathematical functions, found where each system has them: `libSystem` on
|
|
87
|
+
macOS, `libc.so.6` with glibc, the dynamic loader of musl on Alpine, `ucrtbase.dll` on Windows.
|
|
88
|
+
- **The declarations** are written as the headers write them, and can be copied from them: typedefs, structs and unions
|
|
89
|
+
(defined later, referring to themselves, anonymous members), enums (their values can be constant expressions),
|
|
90
|
+
pointers to functions, prototypes, variables, `#define` without arguments (an empty one is removed where it is used,
|
|
91
|
+
as the markers of exported functions; one whose value is an integer is also a constant), `#pragma pack`, the packed
|
|
92
|
+
and aligned attributes. The other attributes, the bodies of inline functions and the `extern "C"` blocks are skipped.
|
|
93
|
+
Each library has its own declarations: two libraries can both declare `HANDLE`.
|
|
94
|
+
- **What is refused**, with its line and its column: `#if` and its kind (run the header through the preprocessor first:
|
|
95
|
+
`cc -E`, `cl /P`), the macros with arguments, `long double`, `__int128`, and the declarations of the old style. The
|
|
96
|
+
types of `stdint.h`, of POSIX (`time_t`, `pid_t`…), of BSD (`u_int`…) and of the Windows API (`DWORD`, `HANDLE`,
|
|
97
|
+
`LPCWSTR`…) are known without being declared, with their size on each platform.
|
|
98
|
+
- **`@Native`** takes a prototype, the name of a function of the declarations, or nothing (the function named as the
|
|
99
|
+
property). The property gets the function when the component is created: the function of koffi itself when nothing
|
|
100
|
+
has to be converted, so a call costs no more than through koffi (about 20 ns).
|
|
101
|
+
- **`@NativeVariable`** takes a declaration, a name, or nothing: reading the property reads the variable, writing it
|
|
102
|
+
writes the variable (refused for a const one). An array of `char` of unknown length is read as a string.
|
|
103
|
+
- A subclass inherits the library of its parent class and can add functions.
|
|
104
|
+
- For a library with many functions, the class can be generated from its header (section 9).
|
|
105
|
+
|
|
106
|
+
## 2. Where the library is found
|
|
107
|
+
|
|
108
|
+
When the component is created, the library is looked for:
|
|
109
|
+
|
|
110
|
+
1. at the `path` of its configuration, when given (the only file tried then);
|
|
111
|
+
2. in the directories of `search` in the configuration, then in the ones of `@Library`;
|
|
112
|
+
3. in a packaged application, in the directory `native` of its resources;
|
|
113
|
+
4. where the system looks for libraries (`dlopen`, `LoadLibrary`).
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import {Application} from 'lakutata'
|
|
117
|
+
import {buildNativeLibraryOptions, Library, Native, NativeLibrary} from '@lakutata/ffi'
|
|
118
|
+
|
|
119
|
+
@Library({names: {darwin: 'libvendor.dylib', linux: 'libvendor.so.2', win32: 'vendor.dll'}})
|
|
120
|
+
class Vendor extends NativeLibrary {
|
|
121
|
+
@Native('int vendor_version(void)')
|
|
122
|
+
public readonly version: () => number
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
Application.run(() => ({
|
|
126
|
+
id: 'devices.app',
|
|
127
|
+
name: 'Devices',
|
|
128
|
+
components: {
|
|
129
|
+
vendor: buildNativeLibraryOptions(Vendor, {search: ['./native']})
|
|
130
|
+
}
|
|
131
|
+
}))
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
When it cannot be loaded, `NativeLibraryNotFoundException` lists each file tried and why it was not loaded (not found,
|
|
135
|
+
built for another architecture, a library it needs missing). On Linux, the libraries of the system are often installed
|
|
136
|
+
with their version only (`libsqlite3.so.0`): give that name.
|
|
137
|
+
|
|
138
|
+
To ship a library with a packaged application, put it in the resources of the package.json, in `native`:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"lakutata": {
|
|
143
|
+
"resources": [{"from": "vendor/lib", "to": "native"}]
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The bundles, the executables (of Node.js and of Bun) and the installers of Electron made by `lakutata package` carry it,
|
|
149
|
+
and koffi with its binary for the platform. On Alpine, make the executable with Bun (`--runtime bun`): the executables
|
|
150
|
+
of Node.js do not load native addons on musl. Under the permission model of Node.js, run with `--allow-addons`.
|
|
151
|
+
|
|
152
|
+
## 3. Calling functions
|
|
153
|
+
|
|
154
|
+
| C | JavaScript |
|
|
155
|
+
|---|---|
|
|
156
|
+
| integers, `float`, `double`, `bool`, enums | numbers, booleans |
|
|
157
|
+
| 64-bit integers (`int64_t`, `long long`, `size_t`, `long`, `time_t`…) | bigints (numbers are accepted as arguments) |
|
|
158
|
+
| `const char *`, `char *` | strings (a `Buffer` is accepted too, and the function writes into it); `char16_t *`, `wchar_t *` too |
|
|
159
|
+
| other pointers | bigints (`Pointer`), `null` for NULL; a `Buffer` or a typed array can be given |
|
|
160
|
+
| structs, unions | objects |
|
|
161
|
+
| pointers to functions | functions (see section 5) |
|
|
162
|
+
|
|
163
|
+
- **The 64-bit integers** are bigints everywhere: results, output parameters, members of structs, arguments of
|
|
164
|
+
callbacks. `long` is 64-bit on Linux and macOS, 32-bit on Windows: it is a bigint on every platform. A function
|
|
165
|
+
declared with `{int64: 'number'}` gives numbers instead, and throws a `RangeError` for a value that is not a safe
|
|
166
|
+
integer.
|
|
167
|
+
- **Output parameters**: a parameter marked `_Out_` (or `_Inout_`) is written by the function. An integer or a pointer
|
|
168
|
+
is given as an array of one element (`Out<T>`, `[null]` before the call); a struct as an object, whose members are
|
|
169
|
+
set once the function returns. A pointer without a mark is given to the function only: a struct passed through it is
|
|
170
|
+
not read back.
|
|
171
|
+
- **A buffer given to a function** is valid during the call only: memory the library keeps must be allocated with
|
|
172
|
+
`alloc()` (section 6).
|
|
173
|
+
- **Variadic functions** (`printf`, `curl_easy_setopt`) take the type of each extra argument by its C name, before it:
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import {loadNativeLibrary} from '@lakutata/ffi'
|
|
177
|
+
|
|
178
|
+
const libc = loadNativeLibrary('c')
|
|
179
|
+
const snprintf = libc.func<(buffer: Buffer, size: number, format: string, ...args: unknown[]) => number>('int snprintf(char *buffer, size_t size, const char *format, ...)')
|
|
180
|
+
const buffer: Buffer = Buffer.alloc(64)
|
|
181
|
+
snprintf(buffer, buffer.length, '%s: %d', 'const char *', 'count', 'int', 42)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
- **Strings to free**: a function returning a string the caller must free (or giving one through an output parameter)
|
|
185
|
+
is declared with `{free: 'sqlite3_free'}`, the function of the library that frees it, or `{free: true}` for `free()`
|
|
186
|
+
of the C library. The string is read, then freed. On Windows, a library built with another C library must be given its
|
|
187
|
+
own function.
|
|
188
|
+
- **Asynchronous calls**: a function declared with `{async: true}` runs in another thread and returns a promise; the
|
|
189
|
+
event loop goes on during the call. By default it runs in the thread pool of the runtime: Node.js runs them in the
|
|
190
|
+
pool of libuv, 4 threads by default, shared with the file system, DNS and crypto; raise `UV_THREADPOOL_SIZE` for many
|
|
191
|
+
calls, or long ones. A variadic function cannot be asynchronous there. A library can have threads of its own instead
|
|
192
|
+
(section 10).
|
|
193
|
+
|
|
194
|
+
## 4. Structs, unions, enums and constants
|
|
195
|
+
|
|
196
|
+
A struct is an object of its members. Its nested structs are objects; its arrays of `char` are strings, its arrays of
|
|
197
|
+
numbers typed arrays (`Uint8Array`, `Int32Array`…), its arrays of 64-bit integers arrays of bigints. An anonymous
|
|
198
|
+
struct or union inside a struct is the member `_0` (then `_1`…).
|
|
199
|
+
|
|
200
|
+
- **Bitfields** are members like the others, read and written by their names: the package lays them out as the
|
|
201
|
+
compilers of the platform do (the rules of GCC and Clang, of ARM, of Microsoft). A bitfield of a 64-bit type is a
|
|
202
|
+
bigint.
|
|
203
|
+
- **Packed structs** (`#pragma pack`, the packed attribute) and the aligned attribute (1, 2, 4 or 8 bytes) are laid out
|
|
204
|
+
as the compilers do.
|
|
205
|
+
- **A union** is given as an object setting one of its members (`{i: 42}`).
|
|
206
|
+
- **Enums** are integers; their values, the static constants and the integer macros of the declarations are in
|
|
207
|
+
`constants`, as bigints (`Number(sqlite.constants.SQLITE_OK)`).
|
|
208
|
+
|
|
209
|
+
The structs and the memory of a library are read with its types: `read(pointer, 'struct timeval')`,
|
|
210
|
+
`sizeOf('struct timeval')`, `offsetOf('struct timeval', 'tv_usec')`.
|
|
211
|
+
|
|
212
|
+
## 5. Callbacks
|
|
213
|
+
|
|
214
|
+
A parameter whose type is a pointer to a function takes a JS function: a **transient callback**, valid during the call
|
|
215
|
+
only (`qsort`, `sqlite3_exec`). An exception it throws makes the function receive 0 (or NULL), then is thrown by the
|
|
216
|
+
call once it returns.
|
|
217
|
+
|
|
218
|
+
A library that keeps a callback to call it later (a log handler, an event handler) gets a **persistent callback**:
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
import {Library, Native, NativeLibrary, type NativeCallback} from '@lakutata/ffi'
|
|
222
|
+
|
|
223
|
+
@Library({
|
|
224
|
+
names: 'vendor',
|
|
225
|
+
declarations: 'typedef void (*vendor_log)(int level, const char *message);'
|
|
226
|
+
})
|
|
227
|
+
export class Vendor extends NativeLibrary {
|
|
228
|
+
@Native('void vendor_set_logger(vendor_log logger)')
|
|
229
|
+
public readonly setLogger: (logger: bigint | null) => void
|
|
230
|
+
|
|
231
|
+
#logger?: NativeCallback
|
|
232
|
+
|
|
233
|
+
public listen(): void {
|
|
234
|
+
this.#logger = this.callback('vendor_log', (level: number, message: string): void => console.log(level, message))
|
|
235
|
+
this.setLogger(this.#logger.pointer)
|
|
236
|
+
this.on('error', (error: unknown): void => console.error('the logger threw', error))
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
public stop(): void {
|
|
240
|
+
this.setLogger(null)
|
|
241
|
+
this.#logger?.release()
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
- `callback(type, fn)` takes the name of a pointer-to-function type of the declarations, or a prototype. Give its
|
|
247
|
+
`pointer` to the library, and `release()` it when the library no longer calls it; the callbacks of a component are
|
|
248
|
+
released when it is destroyed. A process has at most 8192 at a time.
|
|
249
|
+
- Called from another thread of the library, it runs in the JS thread once the event loop gets to it. A function that
|
|
250
|
+
waits for a thread calling back must be asynchronous: called synchronously, the JS thread would wait for the thread
|
|
251
|
+
that waits for it.
|
|
252
|
+
- An exception it throws is an `error` event of the component (written to the log of the application when nothing
|
|
253
|
+
listens to them), and the library gets 0 (or NULL) as its result.
|
|
254
|
+
|
|
255
|
+
## 6. Memory
|
|
256
|
+
|
|
257
|
+
| Function | What it gives |
|
|
258
|
+
|---|---|
|
|
259
|
+
| `alloc(type, count)`, `free(pointer)` | memory for values of a type of the library, which does not move and which the library can keep; freed when the component is destroyed if not before |
|
|
260
|
+
| `read(pointer, type, count?)`, `write(pointer, type, value)` | a value (or an array) of a type of the library |
|
|
261
|
+
| `readCString(pointer)`, `readWString(pointer)` | a string given as a pointer (a `void *` result, a member, an argument of a callback) |
|
|
262
|
+
| `readBuffer(pointer, length)` | a copy, valid once the native memory is freed (about 10 GB/s) |
|
|
263
|
+
| `viewBuffer(pointer, length)` | the memory itself, without copy |
|
|
264
|
+
| `readValue`, `readArray`, `writeValue`, `allocMemory`, `freeMemory`, `sizeOf` | the same for the built-in types (`int`, `uint64_t`, `char *`…), without a library |
|
|
265
|
+
|
|
266
|
+
A copy is as fast as a view below 64 KiB; a view is worth it for large blocks read often (8 MiB: about 0.13 ms
|
|
267
|
+
rather than 1 ms). A view must not be used once the native memory is freed: the process would crash. Electron forbids
|
|
268
|
+
views of native memory: `viewBuffer` throws a `NativeMemoryException` there.
|
|
269
|
+
|
|
270
|
+
## 7. The errors of the C functions
|
|
271
|
+
|
|
272
|
+
`lastErrno()` (or `errno()` of the component) gives the `errno` of the last call of the thread (for a library with
|
|
273
|
+
threads of its own, see section 10). On Windows, each C library has its own `errno`: the one of the C library of
|
|
274
|
+
Microsoft (`ucrtbase.dll`) is read by default, another one given by `crt` of `@Library` (`msvcrt.dll` for the old
|
|
275
|
+
libraries). `lastWindowsError()` gives the error of the Windows API (`GetLastError`), kept after each call.
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
import {lastErrno, loadNativeLibrary} from '@lakutata/ffi'
|
|
279
|
+
|
|
280
|
+
const libc = loadNativeLibrary('c')
|
|
281
|
+
const open = libc.func<(path: string, flags: number) => number>('int open(const char *path, int flags, ...)')
|
|
282
|
+
if (open('/missing', 0) < 0) console.log('errno', lastErrno())
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## 8. Without the container
|
|
286
|
+
|
|
287
|
+
Scripts and tests load a library with `loadNativeLibrary(names, declarations, options)`: the same declarations, the
|
|
288
|
+
same rules, the same methods as a component (`func`, `function`, `variable`, `callback`, `read`…), and `close()`.
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
import {loadNativeLibrary} from '@lakutata/ffi'
|
|
292
|
+
|
|
293
|
+
const libc = loadNativeLibrary('c', 'typedef struct { int quot; int rem; } div_t;')
|
|
294
|
+
const div = libc.func<(numerator: number, denominator: number) => {quot: number, rem: number}>('div_t div(int numerator, int denominator)')
|
|
295
|
+
console.log(div(17, 5))
|
|
296
|
+
await libc.close()
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## 9. Generating the class from the header
|
|
300
|
+
|
|
301
|
+
For a library with many functions, `lakutata ffi generate` writes its class from its header: its declarations, a
|
|
302
|
+
property by function with its TypeScript type, an interface by struct and union, its integer macros as constants.
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
lakutata ffi generate /usr/include/sqlite3.h --class-name SQLite3 --out src/native/SQLite3.ts
|
|
306
|
+
lakutata ffi generate sdk/include/vendor.h --library sdk/lib/vendor.dll --out src/native/Vendor.ts
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
- **The header** is run through the preprocessor of the C compiler (`cc -E`, `cl /P`): its `#include`, its `#if` and its
|
|
310
|
+
macros are resolved as for a build on this platform (`--include` and `--define` give the directories and the macros
|
|
311
|
+
of a build). On Windows, run it in the Developer Command Prompt of Visual Studio, which has `cl`; `--compiler` (or
|
|
312
|
+
`CC`) chooses another compiler.
|
|
313
|
+
- **What is kept**: the declarations of the header, and those of the headers of its directory unless it is a directory
|
|
314
|
+
of the system (`--own` adds the directories of the other headers of the library), with the types of the system they
|
|
315
|
+
use. A declaration the package cannot read (`long double`, a macro with arguments) is left out, and listed with its
|
|
316
|
+
file and its line.
|
|
317
|
+
- **Checked against the library**: the library named as the header (`sqlite3.h`: `sqlite3`), or the one given by
|
|
318
|
+
`--library` (a name or a file), is loaded, and the functions it does not export or that cannot be called (a
|
|
319
|
+
`va_list` parameter) are left out and listed. When it cannot be loaded, the class is generated without the check, with
|
|
320
|
+
a warning (zlib needs `--library z`); `--no-check` skips the check.
|
|
321
|
+
- **The class** gets a property by function, named as the function, with its C prototype above it; a function named as
|
|
322
|
+
a member of the component (`free`, `read`) gets another property name. The TypeScript types follow section 3:
|
|
323
|
+
64-bit integers are bigints, strings are `string | null`, other pointers `Pointer | null` (a parameter also takes a
|
|
324
|
+
`Buffer` or a typed array).
|
|
325
|
+
- **What to edit**: a header does not say which pointers are output parameters, nor which functions block, and the
|
|
326
|
+
generated file is meant to be edited. Mark an output parameter `_Out_` in the prototype of its function, in the
|
|
327
|
+
declarations, and change the type of its parameter to `Out<T>` (to the interface of the struct, for a pointer to a
|
|
328
|
+
struct). A function that blocks gets `{async: true}` in `@Native`, which then names the function. For a new version
|
|
329
|
+
of the header, generate the class again and carry those changes over.
|
|
330
|
+
- **For one platform**: the declarations are the ones of the platform the generator ran on; a header whose declarations
|
|
331
|
+
differ by platform (`#ifdef _WIN32`) is generated on each of them. Another build of the library may lack some of the
|
|
332
|
+
functions: the component then refuses to start, naming the missing function. On Linux, the library is found through
|
|
333
|
+
the link its development package installs (`libsqlite3.so`): for the machines without that package, give `names`
|
|
334
|
+
the file with its version (`libsqlite3.so.0`, section 2).
|
|
335
|
+
|
|
336
|
+
What the generator wrote for `sqlite3_open` and `sqlite3_exec`, edited:
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
import {Library, Native, NativeLibrary, type Out, type Pointer} from '@lakutata/ffi'
|
|
340
|
+
|
|
341
|
+
@Library({
|
|
342
|
+
names: 'sqlite3',
|
|
343
|
+
declarations: `
|
|
344
|
+
typedef struct sqlite3 sqlite3;
|
|
345
|
+
int sqlite3_open(const char *filename, _Out_ sqlite3 **ppDb);
|
|
346
|
+
int sqlite3_exec(sqlite3 *, const char *sql, int (*callback)(void *, int, char **, char **), void *, _Out_ char **errmsg);
|
|
347
|
+
`
|
|
348
|
+
})
|
|
349
|
+
export class SQLite3 extends NativeLibrary {
|
|
350
|
+
//Generated with ppDb: Pointer | Buffer | ArrayBufferView | null; ppDb marked _Out_ in the declarations
|
|
351
|
+
@Native()
|
|
352
|
+
public readonly sqlite3_open: (filename: string | null, ppDb: Out<Pointer>) => number
|
|
353
|
+
|
|
354
|
+
//It blocks: asynchronous; errmsg marked _Out_, the message freed by sqlite3_free
|
|
355
|
+
@Native('sqlite3_exec', {async: true, free: 'sqlite3_free'})
|
|
356
|
+
public readonly sqlite3_exec: (db: Pointer | null, sql: string | null, callback: null, context: null, errmsg: Out<string>) => Promise<number>
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
The generator can also be called from a script: `generateNativeLibrary()` of `@lakutata/ffi/generator` returns the
|
|
361
|
+
code of the class, its declarations and the report.
|
|
362
|
+
|
|
363
|
+
## 10. The threads of a library
|
|
364
|
+
|
|
365
|
+
By default, the asynchronous functions run in the thread pool of the runtime. With `threads`, a library has worker
|
|
366
|
+
threads of its own instead: each one loads the library, and the asynchronous functions run in them.
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
import {Library, Native, NativeLibrary} from '@lakutata/ffi'
|
|
370
|
+
|
|
371
|
+
@Library({names: 'vendor', threads: 1})
|
|
372
|
+
export class Vendor extends NativeLibrary {
|
|
373
|
+
@Native('int vendor_read(int device, unsigned char *buffer, size_t size)', {async: true})
|
|
374
|
+
public readonly read: (device: number, buffer: Buffer, size: number) => Promise<number>
|
|
375
|
+
|
|
376
|
+
@Native('int vendor_version(void)')
|
|
377
|
+
public readonly version: () => number
|
|
378
|
+
}
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
- **The number of threads**: with one thread, the asynchronous calls run one after the other, in the order they were
|
|
382
|
+
made: for a library that is not thread-safe, or a device that serves one request at a time. With several, each call
|
|
383
|
+
goes to the least busy thread. The configuration can give another number
|
|
384
|
+
(`buildNativeLibraryOptions(Vendor, {threads: 2})`), `loadNativeLibrary` takes it in its options, and `0` keeps the
|
|
385
|
+
thread pool.
|
|
386
|
+
- **What runs there**: the asynchronous functions only. The synchronous ones still run in the thread calling them, so
|
|
387
|
+
the library is used by two threads at once: for a library that is not thread-safe, declare all its functions
|
|
388
|
+
asynchronous. A variadic function can be asynchronous here. The threads of a library are not taken from the thread
|
|
389
|
+
pool: long calls do not hold up the file system, and `UV_THREADPOOL_SIZE` does not matter for them.
|
|
390
|
+
- **The arguments are copied** to the thread. The ones the function writes are copied back once it returns: the
|
|
391
|
+
`_Out_` and `_Inout_` parameters, and the buffers and typed arrays given for a pointer that is not `const`. A
|
|
392
|
+
buffer is copied to the thread at each call, and back when the function may write it: for large buffers, the thread
|
|
393
|
+
pool costs less. Pointers are passed as they are: the memory of `alloc()` is shared by all the threads.
|
|
394
|
+
- **Callbacks**: a JS function given for a pointer to a function runs in the thread that made the call, while the
|
|
395
|
+
thread of the library waits for it; an exception it throws rejects the promise. Persistent callbacks work as in
|
|
396
|
+
section 5.
|
|
397
|
+
- **Cost**: about 11 µs for a call awaited before the next one, against 7 µs in the thread pool (Node.js on Apple
|
|
398
|
+
silicon).
|
|
399
|
+
- **errno**: `errno()` and `windowsError()` of the library give the values of the thread of the last asynchronous call
|
|
400
|
+
completed. They are reliable with one thread, or when each call is awaited before the next. From the first
|
|
401
|
+
asynchronous call completed on, they no longer give the values of the synchronous calls: `lastErrno()` and
|
|
402
|
+
`lastWindowsError()` read those (section 7).
|
|
403
|
+
- **Starting and closing**: the threads start with the library and take a few milliseconds to load it. A component is
|
|
404
|
+
created once they have loaded it; for a library loaded by `loadNativeLibrary`, `ready` settles then (the calls wait
|
|
405
|
+
for it anyway). When the library is closed, the calls running are awaited, then the threads unload the library and
|
|
406
|
+
end. A thread that ends unexpectedly fails the calls it was running, and the next call starts a new one.
|
|
407
|
+
|
|
408
|
+
## 11. Destruction and worker threads
|
|
409
|
+
|
|
410
|
+
When the application stops, a library component makes its properties throw `NativeLibraryClosedException` (its
|
|
411
|
+
asynchronous functions reject), waits for its asynchronous calls running (`drainTimeout`, 10 seconds by default),
|
|
412
|
+
releases its callbacks, frees the memory of `alloc()` and unloads the library (`keepLoaded` keeps it loaded; musl never
|
|
413
|
+
unloads). A function copied out of its property (`const abs = libc.abs`) must not be called after that: a function of an
|
|
414
|
+
unloaded library ends the process.
|
|
415
|
+
|
|
416
|
+
A native call cannot be interrupted. When calls are still running after `drainTimeout`, the library, its callbacks and
|
|
417
|
+
its memory are left as they are, so that those calls can return safely. The calls in the thread pool go on, and settle
|
|
418
|
+
when they return. The ones in the threads of the library are rejected with `NativeLibraryClosedException`, and each of
|
|
419
|
+
those threads ends once its call returns. Node.js waits for such calls to return even on `process.exit()`;
|
|
420
|
+
`process.exit()` in Bun does not wait for them.
|
|
421
|
+
|
|
422
|
+
A worker thread loads the library for itself (a component created in the worker). Stop the application running in the
|
|
423
|
+
worker before stopping the worker: the application waits for the calls and closes its libraries. Stopping a worker
|
|
424
|
+
while it uses koffi can end the whole process:
|
|
425
|
+
|
|
426
|
+
- while koffi loads in the worker, in Node.js and in Bun: when the worker imports the package, or while the threads of
|
|
427
|
+
a library created in it start (a few milliseconds). koffi 3.3.4 does not survive being stopped while it loads.
|
|
428
|
+
Exiting the process while the threads of a library start can do the same;
|
|
429
|
+
- in Bun, also while asynchronous calls run in the thread pool (a bug of Bun, in 1.4.4 as well), and while the threads
|
|
430
|
+
of a library bind its functions.
|
|
431
|
+
|
|
432
|
+
Stopping a worker while asynchronous calls run in the threads of its libraries does not crash the process.
|
|
433
|
+
|
|
434
|
+
## 12. Runtimes
|
|
435
|
+
|
|
436
|
+
Node.js `^22.13.0 || >=24.11.0`, the main process of Electron and Bun; not the renderers of Electron nor the browsers.
|
|
437
|
+
|
|
438
|
+
The tests of the package, including those of the threads of a library, pass on macOS (arm64: Node.js 24, Bun 1.4,
|
|
439
|
+
Electron 44), on Linux (arm64, glibc and musl: Node.js 24, Bun 1.4) and on Windows 11 (arm64, and x64 under emulation:
|
|
440
|
+
Node.js 24, Bun 1.4). On macOS, an application whose libraries run in threads of their own was also packed by
|
|
441
|
+
`lakutata package` into a bundle, executables of Node.js and of Bun, and an Electron application: each one ran, and the
|
|
442
|
+
threads loaded the library shipped in its resources.
|
|
443
|
+
|
|
444
|
+
The generator of section 9 was run with clang on macOS, gcc on Linux (glibc and musl) and cl on Windows 11 (arm64 and
|
|
445
|
+
x64). The classes it generated from the headers of sqlite3, zlib, pcap and curl on macOS and Linux, and from
|
|
446
|
+
winsqlite3.h and the header of a library built for the test on Windows, compiled under the strict mode of TypeScript
|
|
447
|
+
and ran in Node.js and Bun.
|
|
448
|
+
|
|
449
|
+
## 13. Exceptions
|
|
450
|
+
|
|
451
|
+
| Exception | When |
|
|
452
|
+
|---|---|
|
|
453
|
+
| `NativeDeclarationException` | Declarations or a prototype cannot be read (with the line and the column), a function is not in the library, a type cannot be passed (`va_list`), a class has no `@Library` |
|
|
454
|
+
| `NativeLibraryNotFoundException` | The library cannot be loaded: each file tried and why |
|
|
455
|
+
| `NativeLibraryClosedException` | A function, a variable or a callback of a library used once it is closed; a call still running in the threads of a library when the library is closed |
|
|
456
|
+
| `NativeMemoryException` | A null pointer, a negative length, a view in Electron |
|
|
457
|
+
| `TypeError` (from koffi) | An argument of the wrong type |
|
|
458
|
+
| `RangeError` | A 64-bit integer that is not a safe integer, for a function declared with `{int64: 'number'}` |
|
package/doc/en/HTTP.md
CHANGED
|
@@ -113,7 +113,7 @@ The options of `expressHTTPEntrypoint()`:
|
|
|
113
113
|
| `port` | `80` | The port; `0` picks a free port. A port already in use fails the application's start |
|
|
114
114
|
| `host` | all the interfaces | The address to listen on, such as `'127.0.0.1'` for the local machine only |
|
|
115
115
|
| `name` | | The name of the adapter, to tell several adapters apart |
|
|
116
|
-
| `body` | `{limit: '1mb'}` | The size limit of the
|
|
116
|
+
| `body` | `{limit: '1mb'}` | The size limit of the bodies (`'5mb'`, or a number of bytes); `text`: the content types read as text (section 4); `false` parses no body |
|
|
117
117
|
| `server` | | The options of the Node.js HTTP server: `keepAliveTimeout`, `headersTimeout`, `requestTimeout`… |
|
|
118
118
|
| `app` | a new application | An existing Express application to add the actions to |
|
|
119
119
|
| `input` | the query and the body | Builds the input from the request (section 4) |
|
|
@@ -124,9 +124,10 @@ The options of `expressHTTPEntrypoint()`:
|
|
|
124
124
|
| `stop` | | Called when the adapter stops, once its server is closed |
|
|
125
125
|
| `heartbeat` | `15000` | How often a comment keeps the event streams alive, in milliseconds (section 5) |
|
|
126
126
|
|
|
127
|
-
`fastifyHTTPEntrypoint()` takes the same options, except `
|
|
128
|
-
|
|
129
|
-
`keepAliveTimeout`, `requestTimeout`; `logger`…).
|
|
127
|
+
`fastifyHTTPEntrypoint()` takes the same options, except `server` and `app`, replaced by `fastify`: a Fastify instance,
|
|
128
|
+
or the options of the instance to create (`bodyLimit`, 1 MiB by default; `trustProxy`; `connectionTimeout`,
|
|
129
|
+
`keepAliveTimeout`, `requestTimeout`; `logger`…). Its `body` option has only `text`, the size limit being the
|
|
130
|
+
`bodyLimit` of the instance.
|
|
130
131
|
|
|
131
132
|
Where they differ:
|
|
132
133
|
|
|
@@ -209,6 +210,9 @@ before the validation, without the route parameters.
|
|
|
209
210
|
send a list inside an object (`{"items": [...]}`).
|
|
210
211
|
- **Forms.** Express parses the URL-encoded bodies, the brackets nested (`a[b]=1` → `a: {b: '1'}`); Fastify refuses them
|
|
211
212
|
with 415. Neither parses `multipart/form-data`, the file uploads (section 7).
|
|
213
|
+
- **Other text.** The content types listed in `body.text` (`['application/sdp']`, or `'text/*'` for a whole type) are
|
|
214
|
+
read as text, given to the action in the `body` property of its input. Fastify refuses the other types it does not
|
|
215
|
+
parse with 415.
|
|
212
216
|
- **Limits.** A body over the limit is refused with 413, malformed JSON with 400.
|
|
213
217
|
|
|
214
218
|
The `input` option builds the input from the request. For example, to give the actions the address of the client and
|
package/doc/en/NativeAddons.md
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
A native addon is a library written in C++ or Rust that Node.js loads as a `.node` file: for drivers and devices,
|
|
4
4
|
codecs, system APIs, or code that must run fast. `lakutata add --native` creates an addon package in a workspace,
|
|
5
5
|
ready to build and test; `lakutata native` links libraries to it; and `lakutata package` ships it with the application.
|
|
6
|
-
The addons use Node-API, so one build runs in all the supported versions of Node.js and in Electron.
|
|
6
|
+
The addons use Node-API, so one build runs in all the supported versions of Node.js and in Electron. To call the C
|
|
7
|
+
functions of a library without writing an addon, see [FFI](FFI.md).
|
|
7
8
|
|
|
8
9
|
## 1. Creating an addon
|
|
9
10
|
|
package/doc/en/Overview.md
CHANGED
|
@@ -46,8 +46,9 @@ tool, a web and a desktop application, calls between services, background jobs,
|
|
|
46
46
|
|
|
47
47
|
Not part of this package: `@lakutata/cli` (the `lakutata` command for creating projects, running migrations and packing
|
|
48
48
|
applications, see [Command](Command.md)), `@lakutata/grpc` (a gRPC client, server and service transport),
|
|
49
|
-
`@lakutata/nats` (a NATS component and service transport), `@lakutata/dbus` (D-Bus)
|
|
50
|
-
Context Protocol)
|
|
49
|
+
`@lakutata/nats` (a NATS component and service transport), `@lakutata/dbus` (D-Bus), `@lakutata/mcp` (the Model
|
|
50
|
+
Context Protocol), `@lakutata/webrtc` (WebRTC: the signaling, a TURN server, a media server, peer connections) and
|
|
51
|
+
`@lakutata/ffi` (the C functions of native libraries).
|
|
51
52
|
|
|
52
53
|
## The guides
|
|
53
54
|
|
|
@@ -90,6 +91,8 @@ Context Protocol).
|
|
|
90
91
|
| [Security](Security.md) | Hashing, ciphers, signatures, passwords |
|
|
91
92
|
| [DBus](DBus.md) | A D-Bus service and client (`@lakutata/dbus`): methods, objects, properties, signals |
|
|
92
93
|
| [MCP](MCP.md) | The tools, resources and prompts of the Model Context Protocol (`@lakutata/mcp`), over stdio and HTTP |
|
|
94
|
+
| [WebRTC](WebRTC.md) | Peer-to-peer connections and a media server (`@lakutata/webrtc`): the signaling, the TURN server, browsers and applications, the actions of an application called through data channels, WHIP and WHEP |
|
|
95
|
+
| [FFI](FFI.md) | The C functions of native libraries (`@lakutata/ffi`): declarations copied from headers, structs, callbacks, memory, packaging |
|
|
93
96
|
| [Command](Command.md) | The `lakutata` command: creating projects, migrations, translations, environment variables |
|
|
94
97
|
| [Workspaces](Workspaces.md) | Projects of several packages: running their scripts, giving them a version, adding packages |
|
|
95
98
|
| [NativeAddons](NativeAddons.md) | Native addons in C++ or Rust: creating them, linking libraries, shipping them |
|
package/doc/en/ServiceCalls.md
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
A service action (`@ServiceAction`) is called with a message rather than a URL: an object whose keys select the action,
|
|
4
4
|
its **pattern**, and whose other keys are the input. The same actions can be served over several transports at once:
|
|
5
5
|
plain HTTP, Socket.IO, JSON-RPC 2.0 (over HTTP, WebSocket or the standard input and output) and the IPC of Electron, as
|
|
6
|
-
this guide describes, and gRPC and
|
|
7
|
-
in [Controllers](Controllers.md), the entrypoint and its adapters in
|
|
6
|
+
this guide describes, and gRPC, NATS and the data channels of WebRTC, which have their own guides. The controllers and
|
|
7
|
+
their actions are described in [Controllers](Controllers.md), the entrypoint and its adapters in
|
|
8
|
+
[Entrypoint](Entrypoint.md).
|
|
8
9
|
|
|
9
10
|
## 1. A first service
|
|
10
11
|
|
|
@@ -180,6 +181,7 @@ export async function call<T>(message: Record<string, unknown>): Promise<T> {
|
|
|
180
181
|
| `electronIPCServiceEntrypoint()` | `lakutata/adapter/electron` | (Electron) | `ipcRenderer.invoke()` | IPC messages |
|
|
181
182
|
| `grpcServiceEntrypoint()` | `@lakutata/grpc/service` | `@lakutata/grpc` | See [GRPC](GRPC.md) | gRPC streams |
|
|
182
183
|
| `natsServiceEntrypoint()` | `@lakutata/nats` | `@lakutata/nats` | See [NATS](NATS.md) | NATS messages |
|
|
184
|
+
| `webrtcServiceEntrypoint()` | `@lakutata/webrtc` | `@lakutata/webrtc` | A JSON-RPC 2.0 request on a data channel, see [WebRTC](WebRTC.md) | Notifications |
|
|
183
185
|
|
|
184
186
|
Each adapter also takes `name`, to tell several adapters apart, and `middlewares`, run after those of the entrypoint
|
|
185
187
|
(see [Middlewares](Middlewares.md)).
|
|
@@ -459,6 +461,7 @@ The transports that keep their clients connected can push events to them. A clie
|
|
|
459
461
|
| JSON-RPC over WebSocket | The `id` header or `id` query parameter of the connection, else a random id |
|
|
460
462
|
| JSON-RPC over stdio | `'stdio'` |
|
|
461
463
|
| Electron | The id the page gives when it subscribes or invokes |
|
|
464
|
+
| WebRTC | The member id of the caller in its room |
|
|
462
465
|
|
|
463
466
|
An action reads the id of its client in `this.context.clientId`. The `entrypoint` component pushes the events:
|
|
464
467
|
|
|
@@ -493,12 +496,13 @@ A client receives `notify(clientId, 'progress', 50, 'half')` as:
|
|
|
493
496
|
| Socket.IO | `socket.on('progress', (percent, label) => …)` |
|
|
494
497
|
| JSON-RPC (WebSocket, stdio) | `{"jsonrpc": "2.0", "method": "progress", "params": [50, "half"]}` |
|
|
495
498
|
| Electron | `('progress', [50, 'half'])`, through the `subscribe` of the preload script above |
|
|
499
|
+
| WebRTC | `proxy.on('progress', (percent, label) => …)` of a `PeerServiceProxy` |
|
|
496
500
|
|
|
497
501
|
## 6. Stopping and leaving
|
|
498
502
|
|
|
499
503
|
- When the application stops, the new requests fail with `EntrypointStoppedException` (`E_ENTRYPOINT_STOPPED`; the
|
|
500
504
|
code `-32001` over JSON-RPC), and the requests in progress get the `shutdownTimeout` of the entrypoint (see
|
|
501
505
|
[Entrypoint](Entrypoint.md), section 4).
|
|
502
|
-
- When a client leaves before its response (a socket disconnected, a window closed, an HTTP connection closed
|
|
503
|
-
`this.signal` of its request is aborted.
|
|
506
|
+
- When a client leaves before its response (a socket disconnected, a window closed, an HTTP connection closed, a data
|
|
507
|
+
channel closed), the `this.signal` of its request is aborted.
|
|
504
508
|
- The transports set no time limit to the calls: a client sets its own, such as `socket.timeout(5000)` above.
|
package/doc/en/UpgradingFrom2.md
CHANGED
|
@@ -28,8 +28,10 @@ that the project declares and the one installed. There is no upgrade command: th
|
|
|
28
28
|
matrix and the base images of the Dockerfiles.
|
|
29
29
|
- **A TypeScript that knows `"module": "Node20"`** (the templates use TypeScript 6): `lakutata/tsconfig.json` now sets
|
|
30
30
|
it (2.x: `Node16`), and older compilers reject it.
|
|
31
|
-
- **`package.json`**: `"type": "module"`, `lakutata` `^3.0.0`,
|
|
32
|
-
|
|
31
|
+
- **`package.json`**: `"type": "module"`, `lakutata` `^3.0.0`, the `@lakutata/*` packages at `^3.0.0` too, and
|
|
32
|
+
`@lakutata/cli` in `devDependencies`. Do not give the other packages the version of `lakutata`: a patch release
|
|
33
|
+
publishes only the packages it changes, so their latest versions can differ (`lakutata` 3.1.4 with
|
|
34
|
+
`@lakutata/ffi` 3.1.2).
|
|
33
35
|
- **A clean install**: remove `node_modules` and the lock file, then install. Upgrading in place can leave several
|
|
34
36
|
copies of `@lakutata/core`, whose classes and decorator metadata do not match (`instanceof` and injection fail across
|
|
35
37
|
them). `npm ls @lakutata/core` must print one version.
|