mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-13 15:17:55 +00:00
nodejs: download prebuilt add-on at runtime
This commit is contained in:
@@ -55,9 +55,11 @@ If you cloned this repository, you can:
|
||||
cd ./packages/simplex-chat-nodejs
|
||||
npm install
|
||||
npm run build
|
||||
node ./examples/squaring-bot-readme.js
|
||||
SIMPLEX_ADDON_PATH=build/Release/simplex.node node ./examples/squaring-bot-readme.js
|
||||
```
|
||||
|
||||
Building the Node.js add-on from source needs [node-gyp](https://github.com/nodejs/node-gyp) build tools.
|
||||
|
||||
There is an example with more options in [./examples/squaring-bot.ts](./examples/squaring-bot.ts).
|
||||
|
||||
You can run it with: `npx ts-node ./examples/squaring-bot.ts`
|
||||
@@ -66,14 +68,14 @@ A larger example, with a business address and commands as keys: [SimpleX Calcula
|
||||
|
||||
## Native library
|
||||
|
||||
`libsimplex` is downloaded on first use into the user cache (`$XDG_CACHE_HOME/simplex-chat` or `~/.cache/simplex-chat` on Linux, `~/Library/Caches/simplex-chat` on macOS, `%LOCALAPPDATA%\simplex-chat` on Windows), shared with the Python library. To download it ahead of time, for example in a Dockerfile, run as the user that runs the app:
|
||||
`libsimplex` and the prebuilt Node.js add-on are downloaded on first use into the user cache (`$XDG_CACHE_HOME/simplex-chat` or `~/.cache/simplex-chat` on Linux, `~/Library/Caches/simplex-chat` on macOS, `%LOCALAPPDATA%\simplex-chat` on Windows), shared with the Python library. To download them ahead of time, for example in a Dockerfile, run as the user that runs the app:
|
||||
|
||||
```bash
|
||||
npx simplex-chat install # sqlite (default)
|
||||
npx simplex-chat install --backend postgres # Linux x86_64 only
|
||||
```
|
||||
|
||||
Set `SIMPLEX_LIBS_DIR` to a directory with a local build of `libsimplex` and its dependencies to use it instead; it must be built for the backend in use.
|
||||
Set `SIMPLEX_LIBS_DIR` to a directory with a local build of `libsimplex` and its dependencies to use it instead; it must be built for the backend in use. Set `SIMPLEX_ADDON_PATH` to a locally built add-on (`build/Release/simplex.node`) to use it instead of the downloaded one.
|
||||
|
||||
`ChatApi.init` loads the library for `DbConfig.type`; the low-level `core` functions require `core.loadLibrary(backend)` first.
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Class: ChatAPIError
|
||||
|
||||
Defined in: [src/core.ts:116](../src/core.ts#L116)
|
||||
Defined in: [src/core.ts:117](../src/core.ts#L117)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:116](../src/core.ts#L116)
|
||||
|
||||
> **new ChatAPIError**(`message`, `chatError?`): `ChatAPIError`
|
||||
|
||||
Defined in: [src/core.ts:117](../src/core.ts#L117)
|
||||
Defined in: [src/core.ts:118](../src/core.ts#L118)
|
||||
|
||||
#### Parameters
|
||||
|
||||
@@ -44,7 +44,7 @@ Defined in: [src/core.ts:117](../src/core.ts#L117)
|
||||
|
||||
> **chatError**: `ChatError` \| `undefined` = `undefined`
|
||||
|
||||
Defined in: [src/core.ts:117](../src/core.ts#L117)
|
||||
Defined in: [src/core.ts:118](../src/core.ts#L118)
|
||||
|
||||
***
|
||||
|
||||
@@ -52,7 +52,7 @@ Defined in: [src/core.ts:117](../src/core.ts#L117)
|
||||
|
||||
> **message**: `string`
|
||||
|
||||
Defined in: [src/core.ts:117](../src/core.ts#L117)
|
||||
Defined in: [src/core.ts:118](../src/core.ts#L118)
|
||||
|
||||
#### Inherited from
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Class: ChatInitError
|
||||
|
||||
Defined in: [src/core.ts:140](../src/core.ts#L140)
|
||||
Defined in: [src/core.ts:141](../src/core.ts#L141)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:140](../src/core.ts#L140)
|
||||
|
||||
> **new ChatInitError**(`message`, `dbMigrationError`): `ChatInitError`
|
||||
|
||||
Defined in: [src/core.ts:141](../src/core.ts#L141)
|
||||
Defined in: [src/core.ts:142](../src/core.ts#L142)
|
||||
|
||||
#### Parameters
|
||||
|
||||
@@ -44,7 +44,7 @@ Defined in: [src/core.ts:141](../src/core.ts#L141)
|
||||
|
||||
> **dbMigrationError**: [`DBMigrationError`](core.TypeAlias.DBMigrationError.md)
|
||||
|
||||
Defined in: [src/core.ts:141](../src/core.ts#L141)
|
||||
Defined in: [src/core.ts:142](../src/core.ts#L142)
|
||||
|
||||
***
|
||||
|
||||
@@ -52,7 +52,7 @@ Defined in: [src/core.ts:141](../src/core.ts#L141)
|
||||
|
||||
> **message**: `string`
|
||||
|
||||
Defined in: [src/core.ts:141](../src/core.ts#L141)
|
||||
Defined in: [src/core.ts:142](../src/core.ts#L142)
|
||||
|
||||
#### Inherited from
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: ErrorMigration
|
||||
|
||||
Defined in: [src/core.ts:173](../src/core.ts#L173)
|
||||
Defined in: [src/core.ts:174](../src/core.ts#L174)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:173](../src/core.ts#L173)
|
||||
|
||||
> **dbFile**: `string`
|
||||
|
||||
Defined in: [src/core.ts:175](../src/core.ts#L175)
|
||||
Defined in: [src/core.ts:176](../src/core.ts#L176)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:175](../src/core.ts#L175)
|
||||
|
||||
> **migrationError**: [`MigrationError`](core.TypeAlias.MigrationError.md)
|
||||
|
||||
Defined in: [src/core.ts:176](../src/core.ts#L176)
|
||||
Defined in: [src/core.ts:177](../src/core.ts#L177)
|
||||
|
||||
***
|
||||
|
||||
@@ -34,7 +34,7 @@ Defined in: [src/core.ts:176](../src/core.ts#L176)
|
||||
|
||||
> **type**: `"errorMigration"`
|
||||
|
||||
Defined in: [src/core.ts:174](../src/core.ts#L174)
|
||||
Defined in: [src/core.ts:175](../src/core.ts#L175)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
+3
-3
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: ErrorNotADatabase
|
||||
|
||||
Defined in: [src/core.ts:168](../src/core.ts#L168)
|
||||
Defined in: [src/core.ts:169](../src/core.ts#L169)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:168](../src/core.ts#L168)
|
||||
|
||||
> **dbFile**: `string`
|
||||
|
||||
Defined in: [src/core.ts:170](../src/core.ts#L170)
|
||||
Defined in: [src/core.ts:171](../src/core.ts#L171)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:170](../src/core.ts#L170)
|
||||
|
||||
> **type**: `"errorNotADatabase"`
|
||||
|
||||
Defined in: [src/core.ts:169](../src/core.ts#L169)
|
||||
Defined in: [src/core.ts:170](../src/core.ts#L170)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: ErrorSQL
|
||||
|
||||
Defined in: [src/core.ts:179](../src/core.ts#L179)
|
||||
Defined in: [src/core.ts:180](../src/core.ts#L180)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:179](../src/core.ts#L179)
|
||||
|
||||
> **dbFile**: `string`
|
||||
|
||||
Defined in: [src/core.ts:181](../src/core.ts#L181)
|
||||
Defined in: [src/core.ts:182](../src/core.ts#L182)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:181](../src/core.ts#L181)
|
||||
|
||||
> **migrationSQLError**: `string`
|
||||
|
||||
Defined in: [src/core.ts:182](../src/core.ts#L182)
|
||||
Defined in: [src/core.ts:183](../src/core.ts#L183)
|
||||
|
||||
***
|
||||
|
||||
@@ -34,7 +34,7 @@ Defined in: [src/core.ts:182](../src/core.ts#L182)
|
||||
|
||||
> **type**: `"errorSQL"`
|
||||
|
||||
Defined in: [src/core.ts:180](../src/core.ts#L180)
|
||||
Defined in: [src/core.ts:181](../src/core.ts#L181)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: InvalidConfirmation
|
||||
|
||||
Defined in: [src/core.ts:160](../src/core.ts#L160)
|
||||
Defined in: [src/core.ts:161](../src/core.ts#L161)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:160](../src/core.ts#L160)
|
||||
|
||||
> **type**: `"invalidConfirmation"`
|
||||
|
||||
Defined in: [src/core.ts:161](../src/core.ts#L161)
|
||||
Defined in: [src/core.ts:162](../src/core.ts#L162)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: InvalidQueueSize
|
||||
|
||||
Defined in: [src/core.ts:164](../src/core.ts#L164)
|
||||
Defined in: [src/core.ts:165](../src/core.ts#L165)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:164](../src/core.ts#L164)
|
||||
|
||||
> **type**: `"invalidQueueSize"`
|
||||
|
||||
Defined in: [src/core.ts:165](../src/core.ts#L165)
|
||||
Defined in: [src/core.ts:166](../src/core.ts#L166)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> **Tag** = `"invalidConfirmation"` \| `"invalidQueueSize"` \| `"errorNotADatabase"` \| `"errorMigration"` \| `"errorSQL"`
|
||||
|
||||
Defined in: [src/core.ts:154](../src/core.ts#L154)
|
||||
Defined in: [src/core.ts:155](../src/core.ts#L155)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Enumeration: MigrationConfirmation
|
||||
|
||||
Defined in: [src/core.ts:125](../src/core.ts#L125)
|
||||
Defined in: [src/core.ts:126](../src/core.ts#L126)
|
||||
|
||||
Migration confirmation mode
|
||||
|
||||
@@ -16,7 +16,7 @@ Migration confirmation mode
|
||||
|
||||
> **Console**: `"console"`
|
||||
|
||||
Defined in: [src/core.ts:128](../src/core.ts#L128)
|
||||
Defined in: [src/core.ts:129](../src/core.ts#L129)
|
||||
|
||||
***
|
||||
|
||||
@@ -24,7 +24,7 @@ Defined in: [src/core.ts:128](../src/core.ts#L128)
|
||||
|
||||
> **Error**: `"error"`
|
||||
|
||||
Defined in: [src/core.ts:129](../src/core.ts#L129)
|
||||
Defined in: [src/core.ts:130](../src/core.ts#L130)
|
||||
|
||||
***
|
||||
|
||||
@@ -32,7 +32,7 @@ Defined in: [src/core.ts:129](../src/core.ts#L129)
|
||||
|
||||
> **YesUp**: `"yesUp"`
|
||||
|
||||
Defined in: [src/core.ts:126](../src/core.ts#L126)
|
||||
Defined in: [src/core.ts:127](../src/core.ts#L127)
|
||||
|
||||
***
|
||||
|
||||
@@ -40,4 +40,4 @@ Defined in: [src/core.ts:126](../src/core.ts#L126)
|
||||
|
||||
> **YesUpDown**: `"yesUpDown"`
|
||||
|
||||
Defined in: [src/core.ts:127](../src/core.ts#L127)
|
||||
Defined in: [src/core.ts:128](../src/core.ts#L128)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatCloseStore**(`ctrl`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/core.ts:41](../src/core.ts#L41)
|
||||
Defined in: [src/core.ts:42](../src/core.ts#L42)
|
||||
|
||||
Close chat store
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatDecryptFile**(`fromPath`, `__namedParameters`, `toPath`): `Promise`\<`void`\>
|
||||
|
||||
Defined in: [src/core.ts:97](../src/core.ts#L97)
|
||||
Defined in: [src/core.ts:98](../src/core.ts#L98)
|
||||
|
||||
Decrypt file
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatEncryptFile**(`ctrl`, `fromPath`, `toPath`): `Promise`\<[`CryptoArgs`](core.Interface.CryptoArgs.md)\>
|
||||
|
||||
Defined in: [src/core.ts:89](../src/core.ts#L89)
|
||||
Defined in: [src/core.ts:90](../src/core.ts#L90)
|
||||
|
||||
Encrypt file
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatMigrateInit**(`dbPath`, `dbKey`, `confirm`, `queueSize?`): `Promise`\<`bigint`\>
|
||||
|
||||
Defined in: [src/core.ts:29](../src/core.ts#L29)
|
||||
Defined in: [src/core.ts:30](../src/core.ts#L30)
|
||||
|
||||
Initialize chat controller
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatReadFile**(`path`, `__namedParameters`): `Promise`\<`Buffer`\<`ArrayBufferLike`\>\>
|
||||
|
||||
Defined in: [src/core.ts:82](../src/core.ts#L82)
|
||||
Defined in: [src/core.ts:83](../src/core.ts#L83)
|
||||
|
||||
Read buffer from encrypted file
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatRecvMsgWait**(`ctrl`, `wait`): `Promise`\<`ChatEvent` \| `undefined`\>
|
||||
|
||||
Defined in: [src/core.ts:61](../src/core.ts#L61)
|
||||
Defined in: [src/core.ts:62](../src/core.ts#L62)
|
||||
|
||||
Receive chat event
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatSendCmd**(`ctrl`, `cmd`): `Promise`\<`ChatResponse`\>
|
||||
|
||||
Defined in: [src/core.ts:49](../src/core.ts#L49)
|
||||
Defined in: [src/core.ts:50](../src/core.ts#L50)
|
||||
|
||||
Send chat command as string
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
> **chatWriteFile**(`ctrl`, `path`, `buffer`): `Promise`\<[`CryptoArgs`](core.Interface.CryptoArgs.md)\>
|
||||
|
||||
Defined in: [src/core.ts:74](../src/core.ts#L74)
|
||||
Defined in: [src/core.ts:75](../src/core.ts#L75)
|
||||
|
||||
Write buffer to encrypted file
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: APIResult\<R\>
|
||||
|
||||
Defined in: [src/core.ts:111](../src/core.ts#L111)
|
||||
Defined in: [src/core.ts:112](../src/core.ts#L112)
|
||||
|
||||
## Type Parameters
|
||||
|
||||
@@ -20,7 +20,7 @@ Defined in: [src/core.ts:111](../src/core.ts#L111)
|
||||
|
||||
> `optional` **error?**: `ChatError`
|
||||
|
||||
Defined in: [src/core.ts:113](../src/core.ts#L113)
|
||||
Defined in: [src/core.ts:114](../src/core.ts#L114)
|
||||
|
||||
***
|
||||
|
||||
@@ -28,4 +28,4 @@ Defined in: [src/core.ts:113](../src/core.ts#L113)
|
||||
|
||||
> `optional` **result?**: `R`
|
||||
|
||||
Defined in: [src/core.ts:112](../src/core.ts#L112)
|
||||
Defined in: [src/core.ts:113](../src/core.ts#L113)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: CryptoArgs
|
||||
|
||||
Defined in: [src/core.ts:135](../src/core.ts#L135)
|
||||
Defined in: [src/core.ts:136](../src/core.ts#L136)
|
||||
|
||||
File encryption key and nonce
|
||||
|
||||
@@ -16,7 +16,7 @@ File encryption key and nonce
|
||||
|
||||
> **fileKey**: `string`
|
||||
|
||||
Defined in: [src/core.ts:136](../src/core.ts#L136)
|
||||
Defined in: [src/core.ts:137](../src/core.ts#L137)
|
||||
|
||||
***
|
||||
|
||||
@@ -24,4 +24,4 @@ Defined in: [src/core.ts:136](../src/core.ts#L136)
|
||||
|
||||
> **fileNonce**: `string`
|
||||
|
||||
Defined in: [src/core.ts:137](../src/core.ts#L137)
|
||||
Defined in: [src/core.ts:138](../src/core.ts#L138)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: UpMigration
|
||||
|
||||
Defined in: [src/core.ts:214](../src/core.ts#L214)
|
||||
Defined in: [src/core.ts:215](../src/core.ts#L215)
|
||||
|
||||
## Properties
|
||||
|
||||
@@ -14,7 +14,7 @@ Defined in: [src/core.ts:214](../src/core.ts#L214)
|
||||
|
||||
> **upName**: `string`
|
||||
|
||||
Defined in: [src/core.ts:215](../src/core.ts#L215)
|
||||
Defined in: [src/core.ts:216](../src/core.ts#L216)
|
||||
|
||||
***
|
||||
|
||||
@@ -22,4 +22,4 @@ Defined in: [src/core.ts:215](../src/core.ts#L215)
|
||||
|
||||
> **withDown**: `boolean`
|
||||
|
||||
Defined in: [src/core.ts:216](../src/core.ts#L216)
|
||||
Defined in: [src/core.ts:217](../src/core.ts#L217)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: MTREDifferent
|
||||
|
||||
Defined in: [src/core.ts:235](../src/core.ts#L235)
|
||||
Defined in: [src/core.ts:236](../src/core.ts#L236)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:235](../src/core.ts#L235)
|
||||
|
||||
> **downMigrations**: `string`[]
|
||||
|
||||
Defined in: [src/core.ts:237](../src/core.ts#L237)
|
||||
Defined in: [src/core.ts:238](../src/core.ts#L238)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:237](../src/core.ts#L237)
|
||||
|
||||
> **type**: `"different"`
|
||||
|
||||
Defined in: [src/core.ts:236](../src/core.ts#L236)
|
||||
Defined in: [src/core.ts:237](../src/core.ts#L237)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: MTRENoDown
|
||||
|
||||
Defined in: [src/core.ts:230](../src/core.ts#L230)
|
||||
Defined in: [src/core.ts:231](../src/core.ts#L231)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:230](../src/core.ts#L230)
|
||||
|
||||
> **dbMigrations**: `string`[]
|
||||
|
||||
Defined in: [src/core.ts:232](../src/core.ts#L232)
|
||||
Defined in: [src/core.ts:233](../src/core.ts#L233)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:232](../src/core.ts#L232)
|
||||
|
||||
> **type**: `"noDown"`
|
||||
|
||||
Defined in: [src/core.ts:231](../src/core.ts#L231)
|
||||
Defined in: [src/core.ts:232](../src/core.ts#L232)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> **Tag** = `"noDown"` \| `"different"`
|
||||
|
||||
Defined in: [src/core.ts:224](../src/core.ts#L224)
|
||||
Defined in: [src/core.ts:225](../src/core.ts#L225)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: MEDowngrade
|
||||
|
||||
Defined in: [src/core.ts:203](../src/core.ts#L203)
|
||||
Defined in: [src/core.ts:204](../src/core.ts#L204)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:203](../src/core.ts#L203)
|
||||
|
||||
> **downMigrations**: `string`[]
|
||||
|
||||
Defined in: [src/core.ts:205](../src/core.ts#L205)
|
||||
Defined in: [src/core.ts:206](../src/core.ts#L206)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:205](../src/core.ts#L205)
|
||||
|
||||
> **type**: `"downgrade"`
|
||||
|
||||
Defined in: [src/core.ts:204](../src/core.ts#L204)
|
||||
Defined in: [src/core.ts:205](../src/core.ts#L205)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: MEUpgrade
|
||||
|
||||
Defined in: [src/core.ts:198](../src/core.ts#L198)
|
||||
Defined in: [src/core.ts:199](../src/core.ts#L199)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:198](../src/core.ts#L198)
|
||||
|
||||
> **type**: `"upgrade"`
|
||||
|
||||
Defined in: [src/core.ts:199](../src/core.ts#L199)
|
||||
Defined in: [src/core.ts:200](../src/core.ts#L200)
|
||||
|
||||
#### Overrides
|
||||
|
||||
@@ -30,4 +30,4 @@ Defined in: [src/core.ts:199](../src/core.ts#L199)
|
||||
|
||||
> **upMigrations**: [`UpMigration`](core.Interface.UpMigration.md)[]
|
||||
|
||||
Defined in: [src/core.ts:200](../src/core.ts#L200)
|
||||
Defined in: [src/core.ts:201](../src/core.ts#L201)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
# Interface: MigrationError
|
||||
|
||||
Defined in: [src/core.ts:208](../src/core.ts#L208)
|
||||
Defined in: [src/core.ts:209](../src/core.ts#L209)
|
||||
|
||||
## Extends
|
||||
|
||||
@@ -18,7 +18,7 @@ Defined in: [src/core.ts:208](../src/core.ts#L208)
|
||||
|
||||
> **mtrError**: [`MTRError`](core.TypeAlias.MTRError.md)
|
||||
|
||||
Defined in: [src/core.ts:210](../src/core.ts#L210)
|
||||
Defined in: [src/core.ts:211](../src/core.ts#L211)
|
||||
|
||||
***
|
||||
|
||||
@@ -26,7 +26,7 @@ Defined in: [src/core.ts:210](../src/core.ts#L210)
|
||||
|
||||
> **type**: `"migrationError"`
|
||||
|
||||
Defined in: [src/core.ts:209](../src/core.ts#L209)
|
||||
Defined in: [src/core.ts:210](../src/core.ts#L210)
|
||||
|
||||
#### Overrides
|
||||
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> **Tag** = `"upgrade"` \| `"downgrade"` \| `"migrationError"`
|
||||
|
||||
Defined in: [src/core.ts:192](../src/core.ts#L192)
|
||||
Defined in: [src/core.ts:193](../src/core.ts#L193)
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> **DBMigrationError** = [`InvalidConfirmation`](core.DBMigrationError.Interface.InvalidConfirmation.md) \| [`InvalidQueueSize`](core.DBMigrationError.Interface.InvalidQueueSize.md) \| [`ErrorNotADatabase`](core.DBMigrationError.Interface.ErrorNotADatabase.md) \| [`ErrorMigration`](core.DBMigrationError.Interface.ErrorMigration.md) \| [`ErrorSQL`](core.DBMigrationError.Interface.ErrorSQL.md)
|
||||
|
||||
Defined in: [src/core.ts:146](../src/core.ts#L146)
|
||||
Defined in: [src/core.ts:147](../src/core.ts#L147)
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> **MTRError** = [`MTRENoDown`](core.MTRError.Interface.MTRENoDown.md) \| [`MTREDifferent`](core.MTRError.Interface.MTREDifferent.md)
|
||||
|
||||
Defined in: [src/core.ts:219](../src/core.ts#L219)
|
||||
Defined in: [src/core.ts:220](../src/core.ts#L220)
|
||||
|
||||
@@ -8,4 +8,4 @@
|
||||
|
||||
> **MigrationError** = [`MEUpgrade`](core.MigrationError.Interface.MEUpgrade.md) \| [`MEDowngrade`](core.MigrationError.Interface.MEDowngrade.md) \| [`MigrationError`](core.MigrationError.Interface.MigrationError.md)
|
||||
|
||||
Defined in: [src/core.ts:186](../src/core.ts#L186)
|
||||
Defined in: [src/core.ts:187](../src/core.ts#L187)
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
const path = require("path")
|
||||
|
||||
// Tests load the locally built add-on instead of downloading the released one.
|
||||
process.env.SIMPLEX_ADDON_PATH ??= path.join(__dirname, "build", "Release", "simplex.node")
|
||||
|
||||
module.exports = {
|
||||
preset: "ts-jest",
|
||||
maxWorkers: 1,
|
||||
|
||||
@@ -8,15 +8,12 @@
|
||||
},
|
||||
"files": [
|
||||
"src",
|
||||
"cpp",
|
||||
"dist",
|
||||
"binding.gyp",
|
||||
"tsconfig.json",
|
||||
"docs",
|
||||
"examples"
|
||||
],
|
||||
"scripts": {
|
||||
"install": "node-gyp configure; node-gyp rebuild --release",
|
||||
"install-tools": "npm install -g node-gyp",
|
||||
"configure": "node-gyp configure",
|
||||
"build": "node-gyp rebuild && tsc && cp ./src/simplex.* ./dist",
|
||||
@@ -28,13 +25,13 @@
|
||||
"dependencies": {
|
||||
"@simplex-chat/types": "^0.11.4",
|
||||
"extract-zip": "^2.0.1",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"node-addon-api": "^8.5.0"
|
||||
"fast-deep-equal": "^3.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/node": "^25.0.5",
|
||||
"jest": "^30.2.0",
|
||||
"node-addon-api": "^8.5.0",
|
||||
"ts-jest": "^29.4.6",
|
||||
"typedoc": "^0.28.15",
|
||||
"typedoc-plugin-markdown": "^4.9.0",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
import {parseArgs} from "util"
|
||||
import type {Backend} from "./core"
|
||||
import {resolveLibsDir} from "./libs"
|
||||
import {resolveAddonPath, resolveLibsDir} from "./libs"
|
||||
|
||||
const USAGE = "usage: simplex-chat install [--backend sqlite|postgres]"
|
||||
const EXIT_INSTALL_FAILED = 1
|
||||
@@ -27,8 +27,9 @@ export async function main(argv: string[]): Promise<number> {
|
||||
return EXIT_USAGE
|
||||
}
|
||||
try {
|
||||
const dir = await resolveLibsDir(backend)
|
||||
const [dir, addon] = await Promise.all([resolveLibsDir(backend), resolveAddonPath()])
|
||||
console.log(`libsimplex installed at: ${dir}`)
|
||||
console.log(`Node.js add-on installed at: ${addon}`)
|
||||
return 0
|
||||
} catch (e) {
|
||||
console.error(`install failed: ${(e as Error).message}`)
|
||||
|
||||
@@ -15,7 +15,8 @@ export function loadLibrary(backend: Backend): Promise<void> {
|
||||
if (loading.backend === backend) return loading.promise
|
||||
return Promise.reject(new Error(`libsimplex already loaded with backend=${loading.backend}; cannot switch to ${backend} in the same process`))
|
||||
}
|
||||
const promise = libs.resolveLibsDir(backend).then(dir => simplex.load(libs.libPath(dir)))
|
||||
const promise = Promise.all([libs.resolveLibsDir(backend), libs.resolveAddonPath()])
|
||||
.then(([dir, addon]) => simplex.load(addon, libs.libPath(dir)))
|
||||
loading = {backend, promise}
|
||||
// a failed download or load can be retried
|
||||
promise.catch(() => { loading = undefined })
|
||||
|
||||
@@ -45,9 +45,21 @@ export function cacheRoot(platform: string, env: NodeJS.ProcessEnv, home: string
|
||||
return path.join(env.XDG_CACHE_HOME || path.join(home, ".cache"), "simplex-chat")
|
||||
}
|
||||
|
||||
function releaseUrl(asset: string): string {
|
||||
return `https://github.com/${GITHUB_REPO}/releases/download/v${LIBS_VERSION}/${asset}`
|
||||
}
|
||||
|
||||
export function libsUrl(backend: Backend, tag: string): string {
|
||||
const suffix = backend === "postgres" ? "-postgres" : ""
|
||||
return `https://github.com/${GITHUB_REPO}/releases/download/v${LIBS_VERSION}/simplex-chat-libs-${tag}${suffix}.zip`
|
||||
return releaseUrl(`simplex-chat-libs-${tag}${suffix}.zip`)
|
||||
}
|
||||
|
||||
function addonName(tag: string): string {
|
||||
return `simplex-chat-nodejs-${tag}.node`
|
||||
}
|
||||
|
||||
export function addonUrl(tag: string): string {
|
||||
return releaseUrl(addonName(tag))
|
||||
}
|
||||
|
||||
export function libPath(dir: string): string {
|
||||
@@ -75,34 +87,70 @@ export async function resolveLibsDir(
|
||||
return target
|
||||
}
|
||||
|
||||
export async function installLibs(url: string, target: string, lib: string, timeoutMs: number = REQUEST_TIMEOUT_MS): Promise<void> {
|
||||
const parent = path.dirname(target)
|
||||
await fs.promises.mkdir(parent, {recursive: true})
|
||||
const tmp = await fs.promises.mkdtemp(path.join(parent, ".download-"))
|
||||
try {
|
||||
export async function resolveAddonPath(
|
||||
env: NodeJS.ProcessEnv = process.env,
|
||||
platform: string = process.platform,
|
||||
arch: string = process.arch
|
||||
): Promise<string> {
|
||||
if (env.SIMPLEX_ADDON_PATH) {
|
||||
const file = path.resolve(env.SIMPLEX_ADDON_PATH)
|
||||
if (!fs.existsSync(file)) throw new Error(`SIMPLEX_ADDON_PATH does not exist: ${file}`)
|
||||
return file
|
||||
}
|
||||
const tag = platformTag(platform, arch)
|
||||
const target = path.resolve(cacheRoot(platform, env), `v${LIBS_VERSION}`, addonName(tag))
|
||||
if (!fs.existsSync(target)) await installAddon(addonUrl(tag), target)
|
||||
return target
|
||||
}
|
||||
|
||||
export function installLibs(url: string, target: string, lib: string, timeoutMs: number = REQUEST_TIMEOUT_MS): Promise<void> {
|
||||
return inTempDir(target, async tmp => {
|
||||
console.error(`Downloading libsimplex from ${url} ...`)
|
||||
const zipPath = path.join(tmp, "libs.zip")
|
||||
await download(url, zipPath, timeoutMs)
|
||||
await extract(zipPath, {dir: tmp})
|
||||
const extracted = path.join(tmp, "libs")
|
||||
if (!fs.existsSync(path.join(extracted, lib))) throw new Error(`libs/${lib} missing from ${url}`)
|
||||
try {
|
||||
await fs.promises.rename(extracted, target)
|
||||
} catch (e) {
|
||||
// Another process installed the same version first; its files are identical.
|
||||
// Windows reports renaming onto an existing directory as EPERM.
|
||||
const code = (e as NodeJS.ErrnoException).code
|
||||
const lost = (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM") && fs.existsSync(target)
|
||||
if (!lost) throw e
|
||||
if (!fs.existsSync(path.join(target, lib))) {
|
||||
throw new Error(`another process partially populated ${target} but libsimplex is missing; remove the directory manually and retry`)
|
||||
}
|
||||
}
|
||||
await renameInto(extracted, target, path.join(target, lib))
|
||||
})
|
||||
}
|
||||
|
||||
export function installAddon(url: string, target: string, timeoutMs: number = REQUEST_TIMEOUT_MS): Promise<void> {
|
||||
return inTempDir(target, async tmp => {
|
||||
console.error(`Downloading the Node.js add-on from ${url} ...`)
|
||||
const file = path.join(tmp, path.basename(target))
|
||||
await download(url, file, timeoutMs)
|
||||
await renameInto(file, target, target)
|
||||
})
|
||||
}
|
||||
|
||||
// The temp dir is next to the target, so the final rename stays on one filesystem.
|
||||
async function inTempDir(target: string, install: (tmp: string) => Promise<void>): Promise<void> {
|
||||
const parent = path.dirname(target)
|
||||
await fs.promises.mkdir(parent, {recursive: true})
|
||||
const tmp = await fs.promises.mkdtemp(path.join(parent, ".download-"))
|
||||
try {
|
||||
await install(tmp)
|
||||
} finally {
|
||||
await fs.promises.rm(tmp, {recursive: true, force: true})
|
||||
}
|
||||
}
|
||||
|
||||
async function renameInto(from: string, target: string, installed: string): Promise<void> {
|
||||
try {
|
||||
await fs.promises.rename(from, target)
|
||||
} catch (e) {
|
||||
// Another process installed the same version first; its files are identical.
|
||||
// Windows reports renaming onto an existing directory or a loaded file as EPERM.
|
||||
const code = (e as NodeJS.ErrnoException).code
|
||||
const lost = (code === "EEXIST" || code === "ENOTEMPTY" || code === "EPERM") && fs.existsSync(target)
|
||||
if (!lost) throw e
|
||||
if (!fs.existsSync(installed)) {
|
||||
throw new Error(`another process partially populated ${target} but ${path.basename(installed)} is missing; remove it manually and retry`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function download(url: string, dest: string, timeoutMs: number, redirects = 0): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const get = url.startsWith("https:") ? https.get : http.get
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
// These functions are defined in CPP add-on ../cpp/simplex.cc
|
||||
|
||||
export function load(libPath: string): void
|
||||
export function load(addonPath: string, libPath: string): void
|
||||
export function chat_migrate_init(dbPath: string, dbKey: string, confirm: string): Promise<[bigint, string]>
|
||||
export function chat_migrate_init_queue(dbPath: string, dbKey: string, confirm: string, queueSize: number): Promise<[bigint, string]>
|
||||
export function chat_close_store(ctrl: bigint): Promise<string>
|
||||
|
||||
@@ -1 +1,22 @@
|
||||
module.exports = require("../build/Release/simplex")
|
||||
// The add-on is downloaded from the libsimplex release, so it is loaded at runtime by core.loadLibrary.
|
||||
const NOT_LOADED = "libsimplex is not loaded, call core.loadLibrary(backend) first"
|
||||
const BINDINGS = ["chat_migrate_init", "chat_migrate_init_queue", "chat_close_store", "chat_send_cmd", "chat_recv_msg_wait",
|
||||
"chat_write_file", "chat_read_file", "chat_encrypt_file", "chat_decrypt_file"]
|
||||
|
||||
let addon
|
||||
|
||||
exports.load = (addonPath, libPath) => {
|
||||
if (!addon) {
|
||||
const addonModule = {exports: {}}
|
||||
process.dlopen(addonModule, addonPath)
|
||||
addon = addonModule.exports
|
||||
}
|
||||
addon.load(libPath)
|
||||
}
|
||||
|
||||
for (const name of BINDINGS) {
|
||||
exports[name] = (...args) => {
|
||||
if (!addon) throw new Error(NOT_LOADED)
|
||||
return addon[name](...args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,12 @@ import * as simplex from "../src/simplex";
|
||||
|
||||
describe("Core tests", () => {
|
||||
let libPath: string;
|
||||
let addonPath: string;
|
||||
// the first run downloads libsimplex
|
||||
beforeAll(async () => {
|
||||
libPath = libs.libPath(await libs.resolveLibsDir("sqlite"));
|
||||
const libsDir = await libs.resolveLibsDir("sqlite");
|
||||
libPath = libs.libPath(libsDir);
|
||||
addonPath = await libs.resolveAddonPath();
|
||||
await core.loadLibrary("sqlite");
|
||||
}, 300000);
|
||||
const tmpDir = "./tests/tmp";
|
||||
@@ -200,11 +203,11 @@ describe("Core tests", () => {
|
||||
}, 10000);
|
||||
|
||||
it("should accept loading libsimplex again from the same path", () => {
|
||||
expect(() => simplex.load(libPath)).not.toThrow();
|
||||
expect(() => simplex.load(addonPath, libPath)).not.toThrow();
|
||||
});
|
||||
|
||||
it("should refuse to load libsimplex from another path", () => {
|
||||
expect(() => simplex.load(path.resolve("other", "libsimplex.so"))).toThrow(`libsimplex already loaded from ${libPath}`);
|
||||
expect(() => simplex.load(addonPath, path.resolve("other", "libsimplex.so"))).toThrow(`libsimplex already loaded from ${libPath}`);
|
||||
});
|
||||
|
||||
it("should load libsimplex after a failed load and in several workers", () => {
|
||||
|
||||
@@ -4,14 +4,14 @@ import * as https from "https"
|
||||
import * as os from "os"
|
||||
import * as path from "path"
|
||||
import {AddressInfo} from "net"
|
||||
import {cacheRoot, installLibs, LIBS_VERSION, libPath, libsUrl, MAX_REDIRECTS, platformTag, resolveLibsDir} from "../src/libs"
|
||||
import {addonUrl, cacheRoot, installAddon, installLibs, LIBS_VERSION, libPath, libsUrl, MAX_REDIRECTS, platformTag, resolveAddonPath, resolveLibsDir} from "../src/libs"
|
||||
import {main, parseInstallArgs} from "../src/cli"
|
||||
import * as libs from "../src/libs"
|
||||
import {storedZip} from "./zip"
|
||||
|
||||
const LIB = "libtest.so"
|
||||
const LINUX_LIB = "libsimplex.so"
|
||||
const {XDG_CACHE_HOME, LOCALAPPDATA, SIMPLEX_LIBS_DIR} = process.env
|
||||
const {XDG_CACHE_HOME, LOCALAPPDATA, SIMPLEX_LIBS_DIR, SIMPLEX_ADDON_PATH} = process.env
|
||||
|
||||
function setEnv(name: string, value: string | undefined): void {
|
||||
if (value === undefined) delete process.env[name]
|
||||
@@ -27,12 +27,14 @@ beforeEach(() => {
|
||||
setEnv("XDG_CACHE_HOME", home)
|
||||
setEnv("LOCALAPPDATA", home)
|
||||
setEnv("SIMPLEX_LIBS_DIR", undefined)
|
||||
setEnv("SIMPLEX_ADDON_PATH", undefined)
|
||||
})
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks()
|
||||
setEnv("XDG_CACHE_HOME", XDG_CACHE_HOME)
|
||||
setEnv("LOCALAPPDATA", LOCALAPPDATA)
|
||||
setEnv("SIMPLEX_LIBS_DIR", SIMPLEX_LIBS_DIR)
|
||||
setEnv("SIMPLEX_ADDON_PATH", SIMPLEX_ADDON_PATH)
|
||||
fs.rmSync(home, {recursive: true, force: true})
|
||||
})
|
||||
|
||||
@@ -75,6 +77,7 @@ describe("paths", () => {
|
||||
const release = `https://github.com/simplex-chat/simplex-chat-libs/releases/download/v${LIBS_VERSION}`
|
||||
expect(libsUrl("sqlite", "macos-aarch64")).toBe(`${release}/simplex-chat-libs-macos-aarch64.zip`)
|
||||
expect(libsUrl("postgres", "linux-x86_64")).toBe(`${release}/simplex-chat-libs-linux-x86_64-postgres.zip`)
|
||||
expect(addonUrl("windows-x86_64")).toBe(`${release}/simplex-chat-nodejs-windows-x86_64.node`)
|
||||
})
|
||||
|
||||
it("resolves an absolute SIMPLEX_LIBS_DIR", async () => {
|
||||
@@ -104,6 +107,17 @@ describe("paths", () => {
|
||||
await expect(resolveLibsDir("sqlite", {SIMPLEX_LIBS_DIR: libsDir}, platform, arch)).rejects.toThrow(`SIMPLEX_LIBS_DIR has no ${lib}: `)
|
||||
})
|
||||
|
||||
it("returns a relative SIMPLEX_ADDON_PATH as absolute", async () => {
|
||||
const addon = path.join(libsDir, "simplex.node")
|
||||
fs.writeFileSync(addon, "addon")
|
||||
await expect(resolveAddonPath({SIMPLEX_ADDON_PATH: path.relative(process.cwd(), addon)})).resolves.toBe(addon)
|
||||
})
|
||||
|
||||
it("rejects a missing SIMPLEX_ADDON_PATH", async () => {
|
||||
const addon = path.join(libsDir, "simplex.node")
|
||||
await expect(resolveAddonPath({SIMPLEX_ADDON_PATH: addon})).rejects.toThrow(`SIMPLEX_ADDON_PATH does not exist: ${addon}`)
|
||||
})
|
||||
|
||||
it("rejects a SIMPLEX_LIBS_DIR without libsimplex", async () => {
|
||||
fs.rmSync(libPath(libsDir))
|
||||
await expect(resolveLibsDir("sqlite", {SIMPLEX_LIBS_DIR: libsDir})).rejects.toThrow(`SIMPLEX_LIBS_DIR has no ${path.basename(libPath(libsDir))}: ${libsDir}`)
|
||||
@@ -246,6 +260,41 @@ describe("installLibs", () => {
|
||||
expect(fs.readFileSync(path.join(dir, LINUX_LIB), "utf8")).toBe("lib")
|
||||
})
|
||||
|
||||
it("downloads the add-on into the cache", async () => {
|
||||
routes["/addon.node"] = res => res.writeHead(200).end("addon")
|
||||
const requested = mockHttps("/addon.node")
|
||||
const addon = await resolveAddonPath({XDG_CACHE_HOME: tmp}, "linux", "x64")
|
||||
const version = path.join(tmp, "simplex-chat", `v${LIBS_VERSION}`)
|
||||
expect(requested).toEqual([addonUrl("linux-x86_64")])
|
||||
expect(addon).toBe(path.join(version, "simplex-chat-nodejs-linux-x86_64.node"))
|
||||
expect(fs.readFileSync(addon, "utf8")).toBe("addon")
|
||||
expect(fs.readdirSync(version)).toEqual(["simplex-chat-nodejs-linux-x86_64.node"])
|
||||
})
|
||||
|
||||
it("does not download a cached add-on", async () => {
|
||||
const requested = mockHttps("/addon.node")
|
||||
const cached = path.join(cacheRoot("darwin", {}), `v${LIBS_VERSION}`, "simplex-chat-nodejs-macos-aarch64.node")
|
||||
fs.mkdirSync(path.dirname(cached), {recursive: true})
|
||||
fs.writeFileSync(cached, "addon")
|
||||
await expect(resolveAddonPath({}, "darwin", "arm64")).resolves.toBe(cached)
|
||||
expect(requested).toEqual([])
|
||||
})
|
||||
|
||||
it("rejects a missing add-on and cleans up", async () => {
|
||||
const target = path.join(tmp, "simplex.node")
|
||||
await expect(installAddon(`${base}/missing.node`, target)).rejects.toThrow("HTTP 404")
|
||||
expect(fs.readdirSync(tmp)).toEqual([])
|
||||
})
|
||||
|
||||
it("accepts EPERM from rename onto an installed add-on", async () => {
|
||||
routes["/addon.node"] = res => res.writeHead(200).end("addon")
|
||||
const target = path.join(tmp, "simplex.node")
|
||||
fs.writeFileSync(target, "addon")
|
||||
renameFails("EPERM")
|
||||
await expect(installAddon(`${base}/addon.node`, target)).resolves.toBeUndefined()
|
||||
expect(leftovers()).toEqual([])
|
||||
})
|
||||
|
||||
it("does not download a cached lib", async () => {
|
||||
const requested = mockHttps("/good.zip")
|
||||
const cached = path.join(tmp, "simplex-chat", `v${LIBS_VERSION}`, "postgres")
|
||||
@@ -296,8 +345,9 @@ describe("installLibs", () => {
|
||||
})
|
||||
|
||||
describe("cli", () => {
|
||||
function installWithLibsDir(dir: string): Promise<number> {
|
||||
setEnv("SIMPLEX_LIBS_DIR", dir)
|
||||
function installWith(libsDir: string, addon: string): Promise<number> {
|
||||
setEnv("SIMPLEX_LIBS_DIR", libsDir)
|
||||
setEnv("SIMPLEX_ADDON_PATH", addon)
|
||||
return main(["install"])
|
||||
}
|
||||
|
||||
@@ -313,13 +363,29 @@ describe("cli", () => {
|
||||
expect(() => parseInstallArgs([])).toThrow("expected command: install")
|
||||
})
|
||||
|
||||
it("prints the libs directory", async () => {
|
||||
it("prints the libs directory and the add-on", async () => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "libs-cli-"))
|
||||
const addon = path.join(dir, "simplex.node")
|
||||
fs.writeFileSync(libPath(dir), "lib")
|
||||
fs.writeFileSync(addon, "addon")
|
||||
const log = jest.spyOn(console, "log").mockImplementation(() => {})
|
||||
try {
|
||||
await expect(installWith(dir, addon)).resolves.toBe(0)
|
||||
expect(log.mock.calls).toEqual([[`libsimplex installed at: ${dir}`], [`Node.js add-on installed at: ${addon}`]])
|
||||
} finally {
|
||||
fs.rmSync(dir, {recursive: true, force: true})
|
||||
}
|
||||
})
|
||||
|
||||
it("returns 1 without the add-on", async () => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "libs-cli-"))
|
||||
fs.writeFileSync(libPath(dir), "lib")
|
||||
const log = jest.spyOn(console, "log").mockImplementation(() => {})
|
||||
const error = jest.spyOn(console, "error").mockImplementation(() => {})
|
||||
try {
|
||||
await expect(installWithLibsDir(dir)).resolves.toBe(0)
|
||||
expect(log).toHaveBeenCalledWith(`libsimplex installed at: ${dir}`)
|
||||
await expect(installWith(dir, path.join(dir, "simplex.node"))).resolves.toBe(1)
|
||||
expect(log).not.toHaveBeenCalled()
|
||||
expect(error).toHaveBeenCalledWith(expect.stringContaining("install failed: SIMPLEX_ADDON_PATH does not exist"))
|
||||
} finally {
|
||||
fs.rmSync(dir, {recursive: true, force: true})
|
||||
}
|
||||
@@ -327,9 +393,11 @@ describe("cli", () => {
|
||||
|
||||
it("returns 1 when install fails", async () => {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "libs-cli-"))
|
||||
const addon = path.join(dir, "simplex.node")
|
||||
fs.writeFileSync(addon, "addon")
|
||||
const error = jest.spyOn(console, "error").mockImplementation(() => {})
|
||||
try {
|
||||
await expect(installWithLibsDir(dir)).resolves.toBe(1)
|
||||
await expect(installWith(dir, addon)).resolves.toBe(1)
|
||||
expect(error).toHaveBeenCalledWith(expect.stringContaining("install failed: SIMPLEX_LIBS_DIR has no"))
|
||||
} finally {
|
||||
fs.rmSync(dir, {recursive: true, force: true})
|
||||
@@ -338,6 +406,7 @@ describe("cli", () => {
|
||||
|
||||
it("installs the requested backend", async () => {
|
||||
const resolve = jest.spyOn(libs, "resolveLibsDir").mockResolvedValue("/libs")
|
||||
jest.spyOn(libs, "resolveAddonPath").mockResolvedValue("/addon.node")
|
||||
jest.spyOn(console, "log").mockImplementation(() => {})
|
||||
await expect(main(["install", "--backend", "postgres"])).resolves.toBe(0)
|
||||
expect(resolve).toHaveBeenCalledWith("postgres")
|
||||
|
||||
@@ -2,13 +2,14 @@ import {spawnSync} from "child_process"
|
||||
import * as path from "path"
|
||||
|
||||
jest.mock("../src/simplex", () => ({load: jest.fn()}))
|
||||
jest.mock("../src/libs", () => ({libPath: jest.requireActual("../src/libs").libPath, resolveLibsDir: jest.fn()}))
|
||||
jest.mock("../src/libs", () => ({libPath: jest.requireActual("../src/libs").libPath, resolveLibsDir: jest.fn(), resolveAddonPath: jest.fn()}))
|
||||
|
||||
const PACKAGE_DIR = path.join(__dirname, "..")
|
||||
const ADDON = path.join(PACKAGE_DIR, "build", "Release", "simplex.node")
|
||||
const CHILD_TIMEOUT_MS = 10000
|
||||
const LIBS_DIR = path.resolve("libs")
|
||||
const SWITCH_ERROR = "libsimplex already loaded with backend=sqlite; cannot switch to postgres in the same process"
|
||||
const ADDON_PATH = path.resolve("simplex.node")
|
||||
const {libPath} = jest.requireActual<typeof import("../src/libs")>("../src/libs")
|
||||
|
||||
function runAddon(script: string): string {
|
||||
@@ -19,9 +20,11 @@ function runAddon(script: string): string {
|
||||
function freshCore(): {core: typeof import("../src/core"), resolveLibsDir: jest.Mock, load: jest.Mock} {
|
||||
let modules!: ReturnType<typeof freshCore>
|
||||
jest.isolateModules(() => {
|
||||
const libs = require("../src/libs")
|
||||
libs.resolveAddonPath.mockResolvedValue(ADDON_PATH)
|
||||
modules = {
|
||||
core: require("../src/core"),
|
||||
resolveLibsDir: require("../src/libs").resolveLibsDir,
|
||||
resolveLibsDir: libs.resolveLibsDir,
|
||||
load: require("../src/simplex").load,
|
||||
}
|
||||
})
|
||||
@@ -56,6 +59,13 @@ describe("native load", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("add-on module", () => {
|
||||
it("rejects FFI calls before the add-on is loaded", () => {
|
||||
const simplex = jest.requireActual<typeof import("../src/simplex")>("../src/simplex")
|
||||
expect(() => simplex.chat_send_cmd(BigInt(1), "/v")).toThrow("libsimplex is not loaded, call core.loadLibrary(backend) first")
|
||||
})
|
||||
})
|
||||
|
||||
describe("loadLibrary", () => {
|
||||
it("shares one load between concurrent calls", async () => {
|
||||
const {core, resolveLibsDir, load} = freshCore()
|
||||
@@ -65,7 +75,7 @@ describe("loadLibrary", () => {
|
||||
expect(second).toBe(first)
|
||||
await Promise.all([first, second])
|
||||
expect(resolveLibsDir).toHaveBeenCalledTimes(1)
|
||||
expect(load.mock.calls).toEqual([[libPath(LIBS_DIR)]])
|
||||
expect(load.mock.calls).toEqual([[ADDON_PATH, libPath(LIBS_DIR)]])
|
||||
})
|
||||
|
||||
it("refuses to switch backend while the first load is in progress", async () => {
|
||||
|
||||
Reference in New Issue
Block a user