lakutata 3.1.5 → 3.1.6

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 (41) hide show
  1. package/README.md +6 -6
  2. package/README.zh-CN.md +3 -3
  3. package/dist/cjs/exports/ReactNativeAdapter.d.ts +1 -0
  4. package/dist/cjs/exports/ReactNativeAdapter.js +20 -0
  5. package/dist/esm/exports/ReactNativeAdapter.js +1 -0
  6. package/dist/types/exports/ReactNativeAdapter.d.ts +1 -0
  7. package/doc/en/Application.md +5 -0
  8. package/doc/en/Barcode.md +386 -0
  9. package/doc/en/Command.md +11 -1
  10. package/doc/en/Database.md +4 -0
  11. package/doc/en/DatabaseDrivers.md +72 -2
  12. package/doc/en/Entrypoint.md +1 -0
  13. package/doc/en/GettingStarted.md +1 -0
  14. package/doc/en/I18n.md +1 -1
  15. package/doc/en/Migrations.md +2 -2
  16. package/doc/en/NativeAddons.md +3 -0
  17. package/doc/en/Overview.md +10 -7
  18. package/doc/en/Packaging.md +4 -0
  19. package/doc/en/ProjectStructure.md +10 -0
  20. package/doc/en/ServiceCalls.md +41 -5
  21. package/doc/en/examples/MobileApp.md +730 -0
  22. package/doc/en/examples/README.md +2 -0
  23. package/doc/en/examples/TrayApp.md +1061 -0
  24. package/doc/zh/Application.md +2 -0
  25. package/doc/zh/Barcode.md +313 -0
  26. package/doc/zh/Command.md +6 -1
  27. package/doc/zh/Database.md +2 -0
  28. package/doc/zh/DatabaseDrivers.md +51 -1
  29. package/doc/zh/Entrypoint.md +1 -0
  30. package/doc/zh/GettingStarted.md +1 -0
  31. package/doc/zh/I18n.md +1 -1
  32. package/doc/zh/Migrations.md +1 -1
  33. package/doc/zh/NativeAddons.md +1 -0
  34. package/doc/zh/Overview.md +4 -3
  35. package/doc/zh/Packaging.md +2 -0
  36. package/doc/zh/ProjectStructure.md +1 -0
  37. package/doc/zh/ServiceCalls.md +28 -2
  38. package/doc/zh/examples/MobileApp.md +671 -0
  39. package/doc/zh/examples/README.md +2 -0
  40. package/doc/zh/examples/TrayApp.md +980 -0
  41. package/package.json +15 -5
package/README.md CHANGED
@@ -21,8 +21,8 @@ English | [中文](README.zh-CN.md)
21
21
  An IoC-based application framework for Node.js and TypeScript, with one programming model for web services, CLI tools,
22
22
  desktop (Electron, NW.js) applications and embedded devices. An application is a tree of objects (components, providers,
23
23
  controllers) that a container creates. DTOs validate their options and inputs, and transports expose the controllers:
24
- HTTP (Express, Fastify), CLI (Commander), service RPC (HTTP, Socket.IO, JSON-RPC 2.0, gRPC, NATS, Electron IPC, NW.js),
25
- schedules and message queues. One controller method can serve all of them at once.
24
+ HTTP (Express, Fastify), CLI (Commander), service RPC (HTTP, Socket.IO, JSON-RPC 2.0, gRPC, NATS, Electron IPC, NW.js,
25
+ React Native), schedules and message queues. One controller method can serve all of them at once.
26
26
 
27
27
  This package is the framework itself: it re-exports the `@lakutata/*` packages under the subpaths listed below and
28
28
  presets two components in every application, `log` and `entrypoint`. The typings of each subpath document every
@@ -108,7 +108,7 @@ Application
108
108
  | `lakutata/decorator/ctrl` | `@lakutata/entrypoint` | `@HTTPAction`, `@GET`…`@OPTIONS`, `@CLIAction`, `@ServiceAction`, `@Schedule`, `@Prefix`, `@UseMiddlewares` |
109
109
  | `lakutata/adapter/express`, `lakutata/adapter/fastify` | `@lakutata/entrypoint` | The HTTP transports, `staticUI` (Express only) |
110
110
  | `lakutata/adapter/commander` | `@lakutata/entrypoint` | The CLI transport |
111
- | `lakutata/adapter/socket.io`, `lakutata/adapter/jsonrpc`, `lakutata/adapter/electron`, `lakutata/adapter/nwjs` | `@lakutata/entrypoint` | The service transports |
111
+ | `lakutata/adapter/socket.io`, `lakutata/adapter/jsonrpc`, `lakutata/adapter/electron`, `lakutata/adapter/nwjs`, `lakutata/adapter/react-native` | `@lakutata/entrypoint` | The service transports |
112
112
  | `lakutata/com/logger` | `@lakutata/logger` | The `Logger` component preset as `log` (pino-compatible JSON lines) |
113
113
  | `lakutata/orm`, `lakutata/decorator/orm` | `@lakutata/orm` | TypeORM, its decorators |
114
114
  | `lakutata/com/database`, `lakutata/provider/database` | `@lakutata/orm` | The `Database` component and provider, the migration commands |
@@ -144,9 +144,9 @@ 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`, `WebRTC.md`, `FFI.md`, `Math.md`, `Netif.md`, `Command.md`, `Workspaces.md`, `NativeAddons.md`,
148
- `Packaging.md`, `Installers.md`; and the examples, in `doc/en/examples` and `doc/zh/examples`: whole applications
149
- built from the templates.
147
+ `MCP.md`, `WebRTC.md`, `FFI.md`, `Barcode.md`, `Math.md`, `Netif.md`, `Command.md`, `Workspaces.md`,
148
+ `NativeAddons.md`, `Packaging.md`, `Installers.md`; and the examples, in `doc/en/examples` and `doc/zh/examples`:
149
+ whole applications built from the templates.
150
150
  - `@lakutata/cli`: creating, building and packaging applications.
151
151
  - The official Agent Skill for AI coding assistants that work on lakutata projects:
152
152
  [lakutata.com/skill](https://lakutata.com/skill/).
package/README.zh-CN.md CHANGED
@@ -18,7 +18,7 @@
18
18
 
19
19
  # lakutata
20
20
 
21
- 一个基于 IoC 的 Node.js 和 TypeScript 应用框架,让 Web 服务、命令行工具、桌面(Electron、NW.js)应用和嵌入式设备共用同一套编程模型。应用是一棵由容器创建的对象树(组件、提供者、控制器)。DTO 校验对象的选项和输入,各种传输方式把控制器暴露出去:HTTP(Express、Fastify)、CLI(Commander)、服务调用(HTTP、Socket.IO、JSON-RPC 2.0、gRPC、NATS、Electron IPC、NW.js)、定时任务和消息队列。同一个控制器方法可以同时通过所有这些方式调用。
21
+ 一个基于 IoC 的 Node.js 和 TypeScript 应用框架,让 Web 服务、命令行工具、桌面(Electron、NW.js)应用和嵌入式设备共用同一套编程模型。应用是一棵由容器创建的对象树(组件、提供者、控制器)。DTO 校验对象的选项和输入,各种传输方式把控制器暴露出去:HTTP(Express、Fastify)、CLI(Commander)、服务调用(HTTP、Socket.IO、JSON-RPC 2.0、gRPC、NATS、Electron IPC、NW.js、React Native)、定时任务和消息队列。同一个控制器方法可以同时通过所有这些方式调用。
22
22
 
23
23
  这个包就是框架本身:它通过下面列出的子路径重新导出各个 `@lakutata/*` 包,并为每个应用预置两个组件:`log` 和 `entrypoint`。各子路径的类型声明中,每一项都附有说明和示例,每个 `@lakutata/*` 包也都有自己的 README。
24
24
 
@@ -97,7 +97,7 @@ Application
97
97
  | `lakutata/decorator/ctrl` | `@lakutata/entrypoint` | `@HTTPAction`、`@GET`…`@OPTIONS`、`@CLIAction`、`@ServiceAction`、`@Schedule`、`@Prefix`、`@UseMiddlewares` |
98
98
  | `lakutata/adapter/express`、`lakutata/adapter/fastify` | `@lakutata/entrypoint` | HTTP 传输、`staticUI`(仅 Express) |
99
99
  | `lakutata/adapter/commander` | `@lakutata/entrypoint` | CLI 传输 |
100
- | `lakutata/adapter/socket.io`、`lakutata/adapter/jsonrpc`、`lakutata/adapter/electron`、`lakutata/adapter/nwjs` | `@lakutata/entrypoint` | 服务传输 |
100
+ | `lakutata/adapter/socket.io`、`lakutata/adapter/jsonrpc`、`lakutata/adapter/electron`、`lakutata/adapter/nwjs`、`lakutata/adapter/react-native` | `@lakutata/entrypoint` | 服务传输 |
101
101
  | `lakutata/com/logger` | `@lakutata/logger` | `Logger` 组件,预置为 `log`(输出与 pino 兼容的 JSON 行) |
102
102
  | `lakutata/orm`、`lakutata/decorator/orm` | `@lakutata/orm` | TypeORM 及其装饰器 |
103
103
  | `lakutata/com/database`、`lakutata/provider/database` | `@lakutata/orm` | `Database` 组件和提供者、迁移命令 |
@@ -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`、`WebRTC.md`、`FFI.md`、`Math.md`、`Netif.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`、`Barcode.md`、`Math.md`、`Netif.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
 
@@ -0,0 +1 @@
1
+ export * from '@lakutata/entrypoint/adapter/react-native';
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("@lakutata/entrypoint/adapter/react-native"), exports);
18
+
19
+ //The default of the entry: the entry itself (scripts/package-build/cjs-package.mjs)
20
+ if (!Object.prototype.hasOwnProperty.call(exports, 'default')) Object.defineProperty(exports, 'default', {value: exports})
@@ -0,0 +1 @@
1
+ export * from '@lakutata/entrypoint/adapter/react-native';
@@ -0,0 +1 @@
1
+ export * from '@lakutata/entrypoint/adapter/react-native';
@@ -55,6 +55,11 @@ Application
55
55
  })
56
56
  ```
57
57
 
58
+ The environment variables and the aliases are set when the application launches, before its options are read. An option
59
+ that uses an alias, such as `path.resolve('@data/app.sqlite')`, is therefore written in an options function given to
60
+ `run()`, as above: an object given to `run()` is made before the launch, when `@data` is not declared yet, and
61
+ `path.resolve()` gives `app.sqlite` in a directory named `@data` of the working directory.
62
+
58
63
  `app.exit(code)` ends the application gracefully with that exit code; `app.exit(true)` ends the process immediately with
59
64
  exit code 2, skipping the done handlers and the teardown.
60
65
 
@@ -0,0 +1,386 @@
1
+ # Lakutata Barcode
2
+
3
+ The package `@lakutata/barcode` generates barcodes and QR codes and reads them from images: product labels, shipping
4
+ labels, tickets and links printed as QR codes, scanned from uploaded photos or from the frames of a camera. It is not
5
+ part of `lakutata`: install it on its own. What it is like:
6
+
7
+ - **What it generates, it reads back**: 32 linear and matrix formats, from EAN-13, UPC, Code 128 and the GS1 DataBar to
8
+ QR Code, Data Matrix, PDF417, Aztec and MaxiCode. The formats only one side handles (the postal codes, Han Xin,
9
+ DotCode…) are left out.
10
+ - **Generated** as a PNG, an SVG or pixels, at the size, in the colors and the rotation, with the error correction asked
11
+ for.
12
+ - **Read** from PNG, JPEG, GIF, BMP, WebP and TGA images, or from pixels; every code of an image, with its position.
13
+ - **A component**, `Barcode`, injected without being registered, with the default options; the components registered
14
+ under names in the configuration have defaults, a time limit and a concurrency of their own. Every call can take a
15
+ time limit, and can be cancelled.
16
+ - **A native addon** built on [zint](https://zint.org.uk) and [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp),
17
+ compiled when the package is installed on Linux, macOS and Windows, and by `lakutata mobile stage` for the mobile
18
+ applications of Android, iOS and HarmonyOS; it also runs in Bun and in the main process of Electron. Its work runs in
19
+ its own threads, never in the thread of JavaScript.
20
+
21
+ The README of `@lakutata/barcode` describes every option; this guide goes through what the package is for, with an
22
+ example of each use.
23
+
24
+ ```bash
25
+ npm install @lakutata/barcode
26
+ ```
27
+
28
+ The addon is compiled when the package is installed, so the machine needs Python 3 and a C++20 compiler (the Command
29
+ Line Tools of Xcode, `build-essential` or `build-base` on Linux, the Build Tools of Visual Studio on Windows). The
30
+ package manager has to run the install script of the package: npm runs it, and may warn that the script is "not yet
31
+ covered by allowScripts" (`npm install-scripts approve @lakutata/barcode` records that you allow it); Bun and pnpm run
32
+ it only for the packages they are told to trust (`trustedDependencies` in the package.json for Bun, `pnpm
33
+ approve-builds` for pnpm). The component needs `@lakutata/core`, a peer dependency that `lakutata` brings.
34
+
35
+ ## 1. Generating a code
36
+
37
+ `generate(format, content, options)` takes the content as text, encoded in UTF-8, or as bytes (a `Uint8Array`), and
38
+ gives a PNG by default:
39
+
40
+ ```typescript
41
+ import {Container} from 'lakutata'
42
+ import {Barcode, type RawImage} from '@lakutata/barcode'
43
+
44
+ const container: Container = new Container()
45
+ const barcode: Barcode = await container.get(Barcode)
46
+
47
+ const png: Buffer = await barcode.generate('QRCode', 'https://example.com/orders/123')
48
+ const svg: string = await barcode.generate('DataMatrix', 'LKT-1001', {output: 'svg'})
49
+ const pixels: RawImage = await barcode.generate('Code128', 'LKT-1001', {output: 'raw', scale: 2})
50
+ console.log(png.length, svg.length) //704 1008
51
+ console.log(pixels.width, pixels.height, pixels.channels) //264 116 1
52
+
53
+ await container.destroy()
54
+ ```
55
+
56
+ - The size of a matrix code (its version) and the way its content is encoded are chosen for the content: the smallest
57
+ symbol that holds it, unless `version` asks for one.
58
+ - The pixels are gray in black and white (1 byte a pixel), RGB with colors (3), RGBA with transparency (4): what a
59
+ canvas, an image library or a printer driver takes.
60
+ - A content the format cannot encode (letters in an EAN-13, too long for the version asked) throws
61
+ `BarcodeContentException`, with the reason given by zint.
62
+
63
+ In an application, the component is injected instead of taken from a container; for options of your own, register a
64
+ component under a name in the configuration and inject it by that name (section 5).
65
+
66
+ ## 2. Size, colors and printing
67
+
68
+ `scale` is the size of a module, the narrowest bar or a square of a matrix code: pixels in a PNG and in pixels, units
69
+ in an SVG. It is a whole number, so the bars of a PNG keep sharp edges. For print, the size of a module in millimetres
70
+ gives the scale: `scale = mm × dpi / 25.4`, rounded. An EAN-13 has modules of 0.33 mm at its nominal size, and may be
71
+ printed from 80% to 200% of it: on a thermal label printer of 203 dpi, a scale of 3 gives modules of 0.375 mm (114%);
72
+ at 300 dpi, a scale of 4 gives 0.34 mm. An SVG prints at any size: its scale only sets its units.
73
+
74
+ - `height` sets the height of the bars of a linear format, in modules, so it follows the scale.
75
+ - `showText` prints the content under the bars of a linear format, from a scale of 2 in a PNG or in pixels (the font
76
+ needs 2 pixels a module).
77
+ - `quietZone` keeps the margins the standard of the format asks for (by default); a scanner needs them to find the code,
78
+ so a label without them must leave the space around the code blank.
79
+ - `foreground` and `background` take `#RRGGBB` and `#RRGGBBAA`. Keep dark bars on a light background: the package
80
+ reads light on dark too (`tryInvert`), but many scanners do not. A transparent background is read as if it were white.
81
+ - `rotate` turns the code by 90, 180 or 270 degrees, clockwise, for a label printed sideways.
82
+ - `errorCorrection` lets a matrix code be read with part of it damaged or covered: from `'L'` (about 7%) to `'H'`
83
+ (about 30%) for QR Code. A higher level makes a larger code for the same content.
84
+
85
+ ## 3. EAN, UPC and GS1
86
+
87
+ The retail formats compute their check digit, and verify the one they are given:
88
+
89
+ ```typescript
90
+ import {Container} from 'lakutata'
91
+ import {Barcode, type BarcodeResult, BarcodeContentException} from '@lakutata/barcode'
92
+
93
+ const container: Container = new Container()
94
+ const barcode: Barcode = await container.get(Barcode)
95
+
96
+ //12 digits: the check digit is computed
97
+ const ean: Buffer = await barcode.generate('EAN13', '590123412345', {addOn: '12'})
98
+ const [product]: BarcodeResult[] = await barcode.read(ean)
99
+ console.log(product.format, product.text, product.addOn) //EAN13 5901234123457 12
100
+
101
+ //13 digits: the check digit is verified
102
+ await barcode.generate('EAN13', '5901234123458').catch((error: BarcodeContentException) => console.log(error.message))
103
+ //Error 275: Invalid check digit '8', expecting '7'
104
+
105
+ //A logistics label: the GTIN, the expiry date and the batch
106
+ const gs1: Buffer = await barcode.generate('Code128', '[01]09506000134352[17]261231[10]ABC123', {gs1: true})
107
+ const [label]: BarcodeResult[] = await barcode.read(gs1)
108
+ console.log(label.text, label.gs1, label.symbologyIdentifier) //(01)09506000134352(17)261231(10)ABC123 true ]C1
109
+
110
+ await container.destroy()
111
+ ```
112
+
113
+ - **UPC.** A UPC-A is an EAN-13 that starts with 0, and is read as an `EAN13` of 13 digits: `01234567890` gives
114
+ `0012345678905`. Asking for `formats: ['UPCA']` without `EAN13` gives the format `UPCA`, with the same 13 digits. An
115
+ ISBN is read as an `EAN13` too.
116
+ - **Add-ons.** The add-on of 2 or 5 digits (an issue number, a price) is in `addOn`, apart from the text. With
117
+ `addOn: 'require'`, a read keeps only the codes that have one.
118
+ - **GS1.** The application identifiers are written in brackets or in parentheses; with `gs1: true`, Code 128 becomes
119
+ GS1-128, and QR Code, Data Matrix and Aztec take GS1 data too. The DataBar Expanded formats always hold GS1 data; the
120
+ other DataBar formats hold a GTIN, given with or without its check digit. A read gives the data in parentheses, with
121
+ `gs1: true`.
122
+
123
+ The README of the package lists, format by format, the format it is read as and the text a read gives.
124
+
125
+ ## 4. Reading images
126
+
127
+ `read(image, options)` takes an image file (its bytes, a `Buffer` or a `Uint8Array`) or pixels, and gives every code it
128
+ finds, an empty array when there is none:
129
+
130
+ ```typescript
131
+ import {readFileSync} from 'node:fs'
132
+ import {Container} from 'lakutata'
133
+ import {Barcode, type BarcodeResult, type RawImage} from '@lakutata/barcode'
134
+
135
+ const container: Container = new Container()
136
+ const barcode: Barcode = await container.get(Barcode)
137
+
138
+ const photo: Buffer = readFileSync('qrcode.jpeg')
139
+ for (const result of await barcode.read(photo, {formats: ['QRCode', 'DataMatrix']})) {
140
+ console.log(result.format, result.text, result.position.topLeft, result.orientation)
141
+ //QRCode https://lakutata.com/fixtures { x: 16, y: 16 } 0
142
+ }
143
+
144
+ //Pixels: 1 byte a pixel in gray, 3 in RGB, 4 in RGBA (the ImageData of a canvas, a decoded video frame)
145
+ const frame: RawImage = await barcode.generate('DataMatrix', 'LKT-1001', {output: 'raw'})
146
+ const rgba: Uint8Array = new Uint8Array(frame.width * frame.height * 4)
147
+ frame.data.forEach((gray: number, index: number): void => rgba.set([gray, gray, gray, 255], index * 4))
148
+ const results: BarcodeResult[] = await barcode.read({data: rgba, width: frame.width, height: frame.height, channels: 4})
149
+ console.log(results.map((result: BarcodeResult): string => result.text)) //[ 'LKT-1001' ]
150
+
151
+ await container.destroy()
152
+ ```
153
+
154
+ - **Faster reads.** `formats` limits the search to the formats expected: reading a QR Code in a 1920×1080 frame takes
155
+ about 8 ms looking for every format, 1.4 ms looking for QR codes only (section 7). `pure: true` reads an image that
156
+ holds one code, straight, with nothing around it, such as an image made by `generate()`.
157
+ - **Harder images.** `tryHarder`, `tryRotate`, `tryInvert` and `tryDownscale` are on by default: they look again for
158
+ codes that are small, turned, light on dark or in large images. Turned off, a read of images known to be easy is
159
+ faster.
160
+ - **What is not read.** A code whose modules are less than about 2 pixels wide, a blurred or a glaring photo, a code cut
161
+ by the edge of the image. The read then gives fewer codes, or none: it throws only for an image it cannot decode
162
+ (`BarcodeImageException`).
163
+ - **Large images** of more than 50 million pixels are refused before their pixels are decoded, so an uploaded file
164
+ cannot make the process allocate gigabytes.
165
+ - **The bytes.** `bytes` holds the content as it is encoded; `text` is its readable form (with the check digits, GS1 in
166
+ parentheses), and `symbologyIdentifier` tells how to read the bytes (`]Q1` a QR Code, `]C1` GS1-128…).
167
+
168
+ ## 5. In an application
169
+
170
+ The component is injected without being registered (`@Inject(Barcode)`), with the default options. For options of your
171
+ own, register a component in the `components` of the configuration: `buildBarcodeOptions()` declares it under a name,
172
+ with defaults for `generate()` and `read()`, a time limit and a concurrency; an application may register several, under
173
+ different names, each with its own options. The application below registers two, `labels` to print labels and
174
+ `scanner` to read photos:
175
+
176
+ ```typescript
177
+ import {Application, Component} from 'lakutata'
178
+ import {Inject} from 'lakutata/decorator/di'
179
+ import {type Barcode, type BarcodeResult, buildBarcodeOptions} from '@lakutata/barcode'
180
+
181
+ class Warehouse extends Component {
182
+ @Inject('labels')
183
+ protected readonly labels: Barcode
184
+
185
+ @Inject('scanner')
186
+ protected readonly scanner: Barcode
187
+
188
+ //The label of an item: a Code 128 with its number printed under the bars
189
+ public async label(sku: string): Promise<Buffer> {
190
+ return await this.labels.generate('Code128', sku)
191
+ }
192
+
193
+ //The codes of a photo: Code 128 and QR codes only
194
+ public async scan(photo: Buffer): Promise<BarcodeResult[]> {
195
+ return await this.scanner.read(photo)
196
+ }
197
+ }
198
+
199
+ Application
200
+ .run(() => ({
201
+ id: 'warehouse.app',
202
+ name: 'Warehouse',
203
+ components: {
204
+ //A label printer of 203 dpi: modules of 3 pixels, the content under the bars
205
+ labels: buildBarcodeOptions({defaults: {generate: {scale: 3, showText: true}}}),
206
+ //At most 2 seconds a photo
207
+ scanner: buildBarcodeOptions({defaults: {read: {formats: ['Code128', 'QRCode']}}, timeout: 2000}),
208
+ warehouse: {class: Warehouse}
209
+ }
210
+ }))
211
+ .onLaunched(async (app: Application): Promise<void> => {
212
+ const warehouse: Warehouse = await app.getObject<Warehouse>('warehouse')
213
+ const [code]: BarcodeResult[] = await warehouse.scan(await warehouse.label('LKT-1001'))
214
+ console.log(code.format, code.text) //Code128 LKT-1001
215
+ })
216
+ ```
217
+
218
+ - `buildBarcodeOptions()` declares the component under the name of your choice; the other objects inject it by that name
219
+ (`@Inject('labels')`) or get it with `getObject('labels')`.
220
+ - A registered component is injected by its name only: `@Inject(Barcode)` gives another component, autoloaded with the
221
+ default options; the options of the configuration do not apply to it.
222
+
223
+ A controller that serves the label of a product as a PNG and reads the codes of an uploaded image:
224
+
225
+ ```typescript
226
+ import express, {type Express, type Request} from 'express'
227
+ import {Application, DTO} from 'lakutata'
228
+ import {buildEntrypoints, Controller, type FileResult, Result} from 'lakutata/com/entrypoint'
229
+ import {GET, POST} from 'lakutata/decorator/ctrl'
230
+ import {Inject} from 'lakutata/decorator/di'
231
+ import {Expect} from 'lakutata/decorator/dto'
232
+ import {expressHTTPEntrypoint} from 'lakutata/adapter/express'
233
+ import {Barcode, type BarcodeResult, buildBarcodeOptions} from '@lakutata/barcode'
234
+
235
+ class LabelDTO extends DTO {
236
+ @Expect(DTO.String().pattern(/^\d{12,13}$/).required())
237
+ public ean: string
238
+ }
239
+
240
+ class ScanDTO extends DTO {
241
+ @Expect(DTO.Binary().required())
242
+ public image: Buffer
243
+ }
244
+
245
+ class LabelController extends Controller {
246
+ @Inject('labels')
247
+ protected readonly labels: Barcode
248
+
249
+ //GET /labels/590123412345: the EAN-13 of a product, as a PNG
250
+ @GET('/labels/:ean', LabelDTO)
251
+ public async label(input: LabelDTO): Promise<FileResult> {
252
+ return Result.file(await this.labels.generate('EAN13', input.ean), {contentType: 'image/png'})
253
+ }
254
+
255
+ //POST /scans, an image as its body: the codes found in it
256
+ @POST('/scans', ScanDTO)
257
+ public async scan(input: ScanDTO): Promise<{ format: string, text: string }[]> {
258
+ const results: BarcodeResult[] = await this.labels.read(input.image)
259
+ return results.map((result: BarcodeResult) => ({format: result.format, text: result.text}))
260
+ }
261
+ }
262
+
263
+ Application.run(() => ({
264
+ id: 'shop.app',
265
+ name: 'Shop',
266
+ components: {
267
+ labels: buildBarcodeOptions({defaults: {generate: {scale: 3}, read: {formats: ['EAN13', 'QRCode']}}, timeout: 10000}),
268
+ entrypoint: buildEntrypoints({
269
+ controllers: [LabelController],
270
+ http: expressHTTPEntrypoint({
271
+ port: 8080,
272
+ before: (app: Express): void => {
273
+ //The body of POST /scans is the image
274
+ app.post('/scans', express.raw({type: 'image/*', limit: '10mb'}))
275
+ },
276
+ input: (request: Request): Record<string, unknown> => Buffer.isBuffer(request.body) ? {image: request.body} : {...request.query, ...request.body}
277
+ })
278
+ })
279
+ },
280
+ bootstrap: ['entrypoint']
281
+ }))
282
+ ```
283
+
284
+ ```text
285
+ $ curl -s -o label.png localhost:8080/labels/590123412345
286
+ $ curl -s --data-binary @label.png -H 'Content-Type: image/png' localhost:8080/scans
287
+ [{"format":"EAN13","text":"5901234123457"}]
288
+ ```
289
+
290
+ - The options of a call go over the defaults of the component: `this.labels.generate('QRCode', url, {scale: 6})` keeps
291
+ the other defaults.
292
+ - The arguments are validated: an unknown format or a color not written `#RRGGBB` is refused with
293
+ `InvalidMethodAcceptException` before the addon is called.
294
+ - One component serves every controller at once: its calls do not depend on one another, except for its
295
+ `concurrency`.
296
+ - In a mobile application of React Native (the mobile template), the component runs in the Node.js of the application in
297
+ the same way: `lakutata mobile stage` compiles the addon from its sources for Android (with the NDK), iOS (Xcode) and
298
+ HarmonyOS (DevEco Studio).
299
+
300
+ ## 6. Time limits, cancellation and concurrency
301
+
302
+ Every call takes a `signal` (an `AbortSignal`) and a `timeout` in milliseconds, which count from the call; the
303
+ `timeout` of the component applies to the calls without one.
304
+
305
+ ```typescript
306
+ import {readFileSync} from 'node:fs'
307
+ import {Container} from 'lakutata'
308
+ import {Barcode, type BarcodeResult} from '@lakutata/barcode'
309
+
310
+ const container: Container = new Container()
311
+ const barcode: Barcode = await container.get(Barcode)
312
+ const photo: Buffer = readFileSync('qrcode.jpeg')
313
+
314
+ //At most 2 seconds, the wait for its turn included
315
+ const results: BarcodeResult[] = await barcode.read(photo, {timeout: 2000})
316
+
317
+ //Until the scan is cancelled
318
+ const scan: AbortController = new AbortController()
319
+ const reading: Promise<BarcodeResult[]> = barcode.read(photo, {signal: scan.signal})
320
+ scan.abort()
321
+ await reading.catch((error: Error) => console.log(error.constructor.name)) //BarcodeCancelledException
322
+
323
+ await container.destroy()
324
+ ```
325
+
326
+ - An operation that has not started yet is dropped. One that is running cannot be stopped, since zint and zxing-cpp
327
+ have no point where they could be interrupted: its promise is rejected at once (`BarcodeTimeoutException`,
328
+ `BarcodeCancelledException`), and its result is discarded when it ends. The operations are short (section 7); the
329
+ time limits are there for the large photos and the hostile uploads.
330
+ - `concurrency` limits the operations of a component running at once; the others wait for their turn, and their time
331
+ limit runs while they wait. Without it, the operations queue in the threads of the addon.
332
+ - The addon runs `LAKUTATA_BARCODE_THREADS` threads, by default one less than the processors, shared by every
333
+ component; it does not use the thread pool of libuv, so file and DNS operations are not held up.
334
+ - When the application stops, the component waits for its operations running, at most `drainTimeout` milliseconds
335
+ (10000 by default); the operations still waiting are rejected with `BarcodeCancelledException`, and so is every call
336
+ after that.
337
+
338
+ ## 7. Speed
339
+
340
+ Measured on an Apple M4 (Node.js 24, one call at a time unless stated):
341
+
342
+ | Operation | Time |
343
+ |---|---|
344
+ | A QR Code of a URL, as a PNG (148×148) | 0.19 ms |
345
+ | The same QR Code as pixels / as an SVG | 0.08 ms / 0.17 ms |
346
+ | An EAN-13 / a Code 128 as a PNG, with its text | 0.62 ms / 1.6 ms |
347
+ | Reading that QR Code PNG, every format / `formats: ['QRCode']` | 0.33 ms / 0.11 ms |
348
+ | Reading its pixels, `formats: ['QRCode']`, `pure: true` | 0.06 ms |
349
+ | Reading a QR Code in a 1920×1080 frame, every format / `formats: ['QRCode']` | 7.8 ms / 1.4 ms |
350
+ | 1000 QR Code PNGs generated at once / read at once (9 threads) | 63 ms / 33 ms |
351
+
352
+ A photo taken by a phone is slower to read than these images made by `generate()`: its code is smaller, turned and
353
+ blurred, and the `try…` options work harder on it.
354
+
355
+ ## 8. How it is checked, and what it does not do
356
+
357
+ The tests generate every format and read it back from its PNG and from its pixels, with random contents (text in several
358
+ scripts, and bytes), check the effect of each option, read images in the seven file formats, and read 240 damaged
359
+ images, which must give codes or `BarcodeImageException` and nothing else. They run on macOS (arm64: Node.js 24 and
360
+ 18.10, Bun 1.4), on Linux (arm64: Node.js 22 on Debian 12, Node.js 24 on Alpine) and on Windows 11 (arm64: Node.js 24);
361
+ the package also runs in the main process of Electron 44, and in the mobile applications of Android, iOS and HarmonyOS,
362
+ where every format and the seven file formats are read back on their emulators.
363
+
364
+ What it does not do, whether by design or not yet:
365
+
366
+ - The formats zxing-cpp cannot read back are not generated: the four-state postal codes, Han Xin, DotCode, Code One,
367
+ Grid Matrix, Codablock-F, Code 16K, Code 49 and the other stacked formats of zint. Telepen Numeric is left out too,
368
+ since a read takes it for Telepen unless it looks for nothing else.
369
+ - A code split over several symbols (structured append) is read symbol by symbol: `structuredAppend` gives the place of
370
+ each, and joining their contents is left to the application.
371
+ - The pixels are gray, RGB or RGBA; BGR and BGRA (some camera APIs) are converted by the application. A GIF is read
372
+ from its first frame.
373
+ - The output is PNG, SVG or pixels: EPS, EMF and PDF are not offered.
374
+
375
+ ## 9. Exceptions
376
+
377
+ All of them extend `BarcodeException`, which extends the `Exception` of lakutata.
378
+
379
+ | Exception | `errno` | When |
380
+ |---|---|---|
381
+ | `BarcodeContentException` | `E_BARCODE_CONTENT` | A content the format cannot encode, with the reason given by zint |
382
+ | `BarcodeOptionException` | `E_BARCODE_OPTION` | An option of the symbol the format does not take (`errorCorrection` for Code 128), or a value out of its range (`version: 41`) |
383
+ | `BarcodeImageException` | `E_BARCODE_IMAGE` | An image that cannot be decoded or has more than 50 million pixels; pixels that are not `width × height × channels` bytes |
384
+ | `BarcodeTimeoutException` | `E_BARCODE_TIMEOUT` | An operation past its time limit |
385
+ | `BarcodeCancelledException` | `E_BARCODE_CANCELLED` | An operation whose signal aborted, or a call to a destroyed component |
386
+ | `BarcodeException` | `E_BARCODE` | An internal error of the addon |
package/doc/en/Command.md CHANGED
@@ -41,6 +41,7 @@ lakutata create --name tool --template cli --package-manager none --no-git
41
41
  |---|---|---|
42
42
  | `--name` | asked in a terminal | The name of the application, and of its directory |
43
43
  | `--id` | the name in lower case | The ID of the application and the name of its package |
44
+ | `--bundle-id` | `com.example.<name>` | The id of the application on the mobile systems (reverse domain name), for the mobile templates; the stores refuse `com.example` |
44
45
  | `--template` | `default` | An official template (`lakutata templates`), `npm:<package>[@<version>]`, `github:<owner>/<repo>[#<ref>]`, or a directory |
45
46
  | `--path` | `.` | Where the project directory is created (`<path>/<name>`) |
46
47
  | `--init` | | Create the project in `--path` itself, a directory that already exists |
@@ -103,6 +104,13 @@ An NW.js application gets its version of NW.js from `lakutata nwjs use <version>
103
104
  application in the sdk flavor of that NW.js (with the DevTools), its native addons made to load in it, and gives it the
104
105
  arguments. Its installers hold the normal flavor ([Installers](Installers.md), section 9).
105
106
 
107
+ A mobile application of React Native laid out as the `mobile-react` template runs in a libnode that its native module
108
+ embeds. `lakutata mobile node <android|ios|harmony>` downloads the libnode of a system from the releases of
109
+ lakutata/libnode; `lakutata mobile stage <platform>` makes the Node.js project of the application where the native
110
+ project of the system takes it; `lakutata mobile run <platform>` builds the application and runs it on an emulator, a
111
+ device or a simulator; `lakutata mobile deploy <platform>` puts the Node.js project into the application installed there
112
+ and starts the application again.
113
+
106
114
  ## 6. Environment variables
107
115
 
108
116
  | Variable | Meaning |
@@ -110,9 +118,11 @@ arguments. Its installers hold the normal flavor ([Installers](Installers.md), s
110
118
  | `LAKUTATA_LANG` | The language of the tool (`en`, `zh-CN`, `zh-TW`) |
111
119
  | `LAKUTATA_NPM_REGISTRY` | The registry the templates come from (else `npm_config_registry`, else `https://registry.npmjs.org`) |
112
120
  | `LAKUTATA_GITHUB_DOWNLOAD` | Where the `github:` templates are downloaded from (`https://github.com` by default) |
113
- | `LAKUTATA_CACHE_DIR` | The cache of the tool: the templates, the tools that make installers and the runtimes of NW.js (`$XDG_CACHE_HOME/lakutata`, else `~/.cache/lakutata`) |
121
+ | `LAKUTATA_CACHE_DIR` | The cache of the tool: the templates, the tools that make installers, the runtimes of NW.js, and the libnode of the mobile applications with their native addons (`$XDG_CACHE_HOME/lakutata`, else `~/.cache/lakutata`) |
114
122
  | `LAKUTATA_NWJS_MIRROR` | Where the runtimes of NW.js are downloaded from (`https://dl.nwjs.io` by default) |
115
123
  | `LAKUTATA_NWJS_VERSIONS` | The list of the releases of NW.js (`https://nwjs.io/versions.json` by default) |
124
+ | `LAKUTATA_LIBNODE_MIRROR` | A mirror of the releases of lakutata/libnode, which the libnode of the mobile applications is downloaded from (`<mirror>/v<version>/libnode_<platform>_<arch>.tar.gz`) |
125
+ | `LAKUTATA_MOBILE_NODE_DIR` | A libnode of your own for the mobile applications, built with the repository of lakutata/libnode (a directory per system: `android`, `ios`, `harmony`), used instead of the releases |
116
126
  | `NO_COLOR` | Turns the colors off |
117
127
 
118
128
  The scripts and the applications the tool runs get the environment the tool was started with. The variables of the
@@ -103,6 +103,10 @@ The options are the ones of TypeORM, given to it as they are. The options of eac
103
103
  finds the first row of the table: check the values taken from a request before querying with them, or set
104
104
  `invalidWhereValuesBehavior: {undefined: 'throw'}` to throw on them (each key given replaces its default).
105
105
 
106
+ **Paths with aliases.** A path given with an alias of the application (`@data/app.sqlite`, `@app/migrations/*.js`) is
107
+ resolved when the database connects: the file of the database (`database`, the `location` of sql.js) and the globs of
108
+ the entities, the migrations and the subscribers. The aliases are declared by `.alias()` ([Application](Application.md), section 2).
109
+
106
110
  **Databases created with TypeORM 0.3.** If a table or column name has characters outside ASCII, TypeORM 1 names its
107
111
  indices and constraints differently, and generated migrations would rename them.
108
112
  `namingStrategy: new NamingStrategyV03()` (from `lakutata/orm`) keeps the names of TypeORM 0.3.
@@ -23,8 +23,9 @@ npm install lakutata pg
23
23
  ```
24
24
 
25
25
  There is no `sqlite` type: SQLite files use `better-sqlite3`, or `sqljs` where a native module cannot run. TypeORM also
26
- has drivers for the mobile platforms (`capacitor`, `cordova`, `expo`, `nativescript`, `react-native`); lakutata does
27
- not target them.
26
+ has drivers for the JavaScript of the mobile platforms (`capacitor`, `cordova`, `expo`, `nativescript`, `react-native`);
27
+ lakutata does not use them. A mobile application of the `mobile-react` template runs the lakutata application in a
28
+ Node.js instead, with the drivers of this page (see [The mobile applications](#the-mobile-applications)).
28
29
 
29
30
  ## What applies to every driver
30
31
 
@@ -139,6 +140,11 @@ export const db = buildDatabaseOptions({
139
140
  its Electron (`electron-rebuild`, or the prebuilt binaries of better-sqlite3 for Electron); an NW.js application has
140
141
  it built again with the headers of NW.js (0.111.3 or later: use `sqljs` with the earlier releases); Bun cannot load it
141
142
  (use `sqljs` there).
143
+ - Built from its sources against the headers of Node.js 24.19 or later, it aborts the process when the garbage collector
144
+ frees one of its objects (`Assertion failed: (env) != nullptr`, in `RemoveEnvironmentCleanupHook`:
145
+ [nodejs/node#65446](https://github.com/nodejs/node/issues/65446)), as every addon built on `node::ObjectWrap` does.
146
+ Its prebuilt binaries are built against earlier headers; `lakutata mobile stage` builds the addons for the mobile
147
+ systems without the cleanup hooks of `node::ObjectWrap` that cause it.
142
148
 
143
149
  ## SQLite: sql.js
144
150
 
@@ -165,6 +171,7 @@ export const db = buildDatabaseOptions({
165
171
  | `database` | | The content of a database to load (a `Uint8Array`), instead of `location` |
166
172
  | `useLocalForage` | `false` | In a browser, save to IndexedDB (through `localforage`) instead of `localStorage` |
167
173
  | `sqlJsConfig` | | The options of the initialization of sql.js (`locateFile` of its `.wasm` file) |
174
+ | `driver` | | A build of sql.js to use instead of the package's: its asm.js build (`sql.js/dist/sql-asm.js`) needs no WebAssembly |
168
175
 
169
176
  - Without `autoSave`, nothing is written to disk until `saveDatabase()` of the entity manager (`SqljsEntityManager`)
170
177
  is called. With `autoSave`, the changes of a transaction are saved once it is committed.
@@ -286,3 +293,66 @@ These drivers come from TypeORM as they are:
286
293
  MySQL, are not read.
287
294
  - **Amazon Aurora through the Data API** (`aurora-mysql`, `aurora-postgres`): `region`, `secretArn`, `resourceArn` and
288
295
  `database` are required; the queries go through the HTTP API of AWS, not through a connection.
296
+
297
+ ## The mobile applications
298
+
299
+ A mobile application of the `mobile-react` template runs its lakutata application in the Node.js it embeds (Node.js
300
+ 24.20), where the drivers of this page work as on a computer. The table gives what was verified on the emulators of
301
+ Android (API 36), iOS (27.2) and HarmonyOS: a database written, closed, then opened again.
302
+
303
+ | Driver | Android | iOS | HarmonyOS |
304
+ |---|---|---|---|
305
+ | `better-sqlite3` | Yes | Yes | Yes |
306
+ | `sqljs`, its asm.js build | Yes | Yes | Yes |
307
+ | `sqljs`, its WebAssembly | Yes | Yes, much slower (the polyfill: 1.5 s where the asm.js build took 0.09 s) | No: `ReferenceError: WebAssembly is not defined` |
308
+ | `mongo-embedded` | Yes | Yes | Yes |
309
+ | `postgres`, `mysql`, `mongodb` (a server on the network) | Yes | Yes | Yes |
310
+
311
+ SQL Server and Oracle have not been checked there.
312
+
313
+ - `lakutata mobile stage` builds `better-sqlite3` from its sources for each system, with the NDK, Xcode or DevEco Studio
314
+ (see the `lakutata mobile` commands in the README of `@lakutata/cli`).
315
+ - The file of a database has to last, and `os.tmpdir()` is the cache of the application, which the system may empty. The
316
+ `App.ts` of the template declares the data directory of the application as the alias `@data`: the `files` directory of
317
+ the application on Android, `Library/Application Support` on iOS, `/data/storage/el2/base/files` on HarmonyOS, and
318
+ `packages/app/data` on the computer of the development. The options of the database name a file there as
319
+ `@data/app.sqlite`: the alias is resolved when the database connects.
320
+ - The asm.js build of sql.js is SQLite compiled to JavaScript: no WebAssembly and no other file, so it runs the same on
321
+ the three systems. The Node.js of HarmonyOS has no WebAssembly, and the one of iOS has only the polyfill of
322
+ nodejs-mobile, which is much slower:
323
+
324
+ ```typescript
325
+ import type {ApplicationOptions} from 'lakutata'
326
+ import {buildDatabaseOptions} from 'lakutata/com/database'
327
+ import {createRequire} from 'node:module'
328
+
329
+ /**
330
+ * The options of the application
331
+ */
332
+ export function config(): ApplicationOptions {
333
+ return {
334
+ id: 'app',
335
+ name: 'app',
336
+ components: {
337
+ db: buildDatabaseOptions({
338
+ type: 'sqljs',
339
+ location: '@data/app.sqlite',
340
+ autoSave: true,
341
+ //The asm.js build of sql.js, instead of its WebAssembly
342
+ driver: createRequire(import.meta.url)('sql.js/dist/sql-asm.js'),
343
+ entities: []
344
+ })
345
+ }
346
+ }
347
+ }
348
+ ```
349
+
350
+ - sql.js 1.15 marks its asm.js build as deprecated: the build prints a warning each time it is loaded, and the authors
351
+ ask the users who cannot leave it to say so ([sql-js/sql.js#635](https://github.com/sql-js/sql.js/issues/635)). The
352
+ build they recommend instead, `sql-wasm-inline.js`, needs WebAssembly, which the Node.js of HarmonyOS does not have.
353
+ For a database that the application keeps, `better-sqlite3`, which works on the three systems, is the better choice.
354
+ - The WebAssembly of sql.js is read from `sql-wasm.wasm`, next to its module, which the bundle of the application does
355
+ not keep: declare the file as a resource in `lakutata.resources` (its `from` is where npm installed `sql.js` in the
356
+ workspace), and give its path among the resources to `sqlJsConfig.locateFile` ([Packaging](Packaging.md), section 6).
357
+ - A database server is reached over the network of the device; from an emulator, the computer is reached through
358
+ `adb reverse` (Android) or `hdc rport` (HarmonyOS).