@slatedb/uniffi 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +211 -0
- package/index.d.ts +1 -0
- package/index.js +7 -0
- package/package.json +47 -0
- package/prebuilds/darwin-arm64/libslatedb_uniffi.dylib +0 -0
- package/prebuilds/darwin-x64/libslatedb_uniffi.dylib +0 -0
- package/prebuilds/linux-arm64-gnu/libslatedb_uniffi.so +0 -0
- package/prebuilds/linux-x64-gnu/libslatedb_uniffi.so +0 -0
- package/prebuilds/win32-arm64/slatedb_uniffi.dll +0 -0
- package/prebuilds/win32-x64/slatedb_uniffi.dll +0 -0
- package/runtime/async-rust-call.d.ts +86 -0
- package/runtime/async-rust-call.js +217 -0
- package/runtime/callbacks.d.ts +122 -0
- package/runtime/callbacks.js +392 -0
- package/runtime/errors.d.ts +120 -0
- package/runtime/errors.js +274 -0
- package/runtime/ffi-converters.d.ts +100 -0
- package/runtime/ffi-converters.js +758 -0
- package/runtime/ffi-types.d.ts +120 -0
- package/runtime/ffi-types.js +456 -0
- package/runtime/handle-map.d.ts +35 -0
- package/runtime/handle-map.js +137 -0
- package/runtime/objects.d.ts +68 -0
- package/runtime/objects.js +469 -0
- package/runtime/rust-call.d.ts +77 -0
- package/runtime/rust-call.js +233 -0
- package/slatedb-ffi.d.ts +732 -0
- package/slatedb-ffi.js +11480 -0
- package/slatedb.d.ts +1393 -0
- package/slatedb.js +5873 -0
package/README.md
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# SlateDB Node Binding
|
|
2
|
+
|
|
3
|
+
`bindings/node` contains the official Node.js package for SlateDB.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
Package:
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
@slatedb/uniffi
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Requirements:
|
|
14
|
+
|
|
15
|
+
- Node.js 20 or newer
|
|
16
|
+
|
|
17
|
+
Install from npm:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @slatedb/uniffi
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## API Model
|
|
24
|
+
|
|
25
|
+
- `ObjectStore.resolve(...)` opens an object store from a URL such as `memory:///` or `file:///...`
|
|
26
|
+
- `DbBuilder` opens a writable database and `DbReaderBuilder` opens a read-only reader
|
|
27
|
+
- keys and values are binary; pass `Buffer` or `Uint8Array`
|
|
28
|
+
- most database operations are async and should be awaited
|
|
29
|
+
- builders are single-use; `Db` and `DbReader` stay open until `shutdown()` resolves
|
|
30
|
+
- native-backed handles also expose `dispose()` for deterministic cleanup after `shutdown()` or when abandoning a builder
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
```js
|
|
35
|
+
import assert from "node:assert/strict";
|
|
36
|
+
import { DbBuilder, ObjectStore } from "@slatedb/uniffi";
|
|
37
|
+
|
|
38
|
+
async function main() {
|
|
39
|
+
const store = ObjectStore.resolve("memory:///");
|
|
40
|
+
let db;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
const builder = new DbBuilder("demo-db", store);
|
|
44
|
+
try {
|
|
45
|
+
db = await builder.build();
|
|
46
|
+
} finally {
|
|
47
|
+
builder.dispose();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const key = Buffer.from("hello");
|
|
51
|
+
const value = Buffer.from("world");
|
|
52
|
+
|
|
53
|
+
await db.put(key, value);
|
|
54
|
+
|
|
55
|
+
const read = await db.get(key);
|
|
56
|
+
assert.deepEqual(read, value);
|
|
57
|
+
|
|
58
|
+
console.log(Buffer.from(read).toString("utf8"));
|
|
59
|
+
} finally {
|
|
60
|
+
if (db != null) {
|
|
61
|
+
await db.shutdown();
|
|
62
|
+
db.dispose();
|
|
63
|
+
}
|
|
64
|
+
store.dispose();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
main().catch((error) => {
|
|
69
|
+
console.error(error);
|
|
70
|
+
process.exitCode = 1;
|
|
71
|
+
});
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Replace `memory:///` with any object store URL supported by Rust's [`object_store`](https://docs.rs/object_store/latest/object_store/fn.parse_url_opts.html) crate.
|
|
75
|
+
|
|
76
|
+
## Metrics
|
|
77
|
+
|
|
78
|
+
The Node binding exposes both application-provided metrics recorders and the built-in
|
|
79
|
+
`DefaultMetricsRecorder`:
|
|
80
|
+
|
|
81
|
+
- `DbBuilder.with_metrics_recorder(...)`
|
|
82
|
+
- `DbReaderBuilder.with_metrics_recorder(...)`
|
|
83
|
+
- `DefaultMetricsRecorder.snapshot()`
|
|
84
|
+
- `DefaultMetricsRecorder.metrics_by_name(...)`
|
|
85
|
+
- `DefaultMetricsRecorder.metric_by_name_and_labels(...)`
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
import { DbBuilder, DefaultMetricsRecorder, ObjectStore } from "@slatedb/uniffi";
|
|
91
|
+
|
|
92
|
+
const store = ObjectStore.resolve("memory:///");
|
|
93
|
+
const recorder = new DefaultMetricsRecorder();
|
|
94
|
+
const builder = new DbBuilder("metrics-demo", store);
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
builder.with_metrics_recorder(recorder);
|
|
98
|
+
const db = await builder.build();
|
|
99
|
+
try {
|
|
100
|
+
await db.put(Buffer.from("hello"), Buffer.from("world"));
|
|
101
|
+
|
|
102
|
+
const metric = recorder.metric_by_name_and_labels("slatedb.db.write_ops", []);
|
|
103
|
+
if (metric?.value.tag === "Counter") {
|
|
104
|
+
console.log(metric.value[""]);
|
|
105
|
+
}
|
|
106
|
+
} finally {
|
|
107
|
+
await db.shutdown();
|
|
108
|
+
db.dispose();
|
|
109
|
+
}
|
|
110
|
+
} finally {
|
|
111
|
+
builder.dispose();
|
|
112
|
+
recorder.dispose();
|
|
113
|
+
store.dispose();
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Local Development
|
|
118
|
+
|
|
119
|
+
The package is generated from the UniFFI `slatedb-uniffi` cdylib using [`uniffi-bindgen-node-js`](https://crates.io/crates/uniffi-bindgen-node-js).
|
|
120
|
+
|
|
121
|
+
You only need these tools when regenerating bindings, running tests from this repository, or packing the npm artifact locally:
|
|
122
|
+
|
|
123
|
+
- Node.js 20 or newer
|
|
124
|
+
- Rust toolchain for this repository
|
|
125
|
+
- `uniffi-bindgen-node-js` on `PATH`
|
|
126
|
+
|
|
127
|
+
Install the generator with:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
cargo install uniffi-bindgen-node-js --version 0.0.7
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Install the package dependency used by the generated bindings with:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
npm --prefix bindings/node install
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Regenerate Bindings
|
|
140
|
+
|
|
141
|
+
From the repository root:
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npm --prefix bindings/node run build
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
This command:
|
|
148
|
+
|
|
149
|
+
1. builds the host `slatedb-uniffi` library
|
|
150
|
+
2. runs `uniffi-bindgen-node-js`
|
|
151
|
+
3. copies the generated package files into `bindings/node`
|
|
152
|
+
4. stages the host native library under `bindings/node/prebuilds/<target>/`
|
|
153
|
+
|
|
154
|
+
Generated API files are written into `bindings/node` and are not committed. `package.json`, `build.mjs`, and this `README.md` are maintained by hand.
|
|
155
|
+
|
|
156
|
+
### Run Tests
|
|
157
|
+
|
|
158
|
+
From the repository root:
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npm --prefix bindings/node test
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
The test script rebuilds the package and then runs `node --test` inside `bindings/node`.
|
|
165
|
+
|
|
166
|
+
### Reproduce The PR CI Flow
|
|
167
|
+
|
|
168
|
+
From the repository root, this mirrors the Node validation done in `.github/workflows/pr.yaml`:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
npm --prefix bindings/node ci
|
|
172
|
+
npm --prefix bindings/node run build
|
|
173
|
+
(cd bindings/node && node --test)
|
|
174
|
+
git diff --exit-code -- bindings/node
|
|
175
|
+
rm -rf /tmp/slatedb-node-pack
|
|
176
|
+
mkdir -p /tmp/slatedb-node-pack
|
|
177
|
+
(cd bindings/node && npm pack --pack-destination /tmp/slatedb-node-pack)
|
|
178
|
+
TARBALL="$(find /tmp/slatedb-node-pack -maxdepth 1 -name '*.tgz' | head -n 1)"
|
|
179
|
+
test -n "${TARBALL}"
|
|
180
|
+
tar -tf "${TARBALL}" | grep -Fx 'package/index.js'
|
|
181
|
+
tar -tf "${TARBALL}" | grep -Fx 'package/index.d.ts'
|
|
182
|
+
tar -tf "${TARBALL}" | grep -Fx 'package/slatedb.js'
|
|
183
|
+
tar -tf "${TARBALL}" | grep -Fx 'package/slatedb.d.ts'
|
|
184
|
+
tar -tf "${TARBALL}" | grep -Fx 'package/slatedb-ffi.js'
|
|
185
|
+
tar -tf "${TARBALL}" | grep -Fx 'package/slatedb-ffi.d.ts'
|
|
186
|
+
tar -tf "${TARBALL}" | grep -Fx 'package/runtime/ffi-types.js'
|
|
187
|
+
tar -tf "${TARBALL}" | grep -Fx 'package/prebuilds/linux-x64-gnu/libslatedb_uniffi.so'
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
## Packaging And Runtime Notes
|
|
191
|
+
|
|
192
|
+
The published `@slatedb/uniffi` tarball contains generated JavaScript and TypeScript bindings plus bundled native libraries. Consumers install one npm package; there is no separate Rust build step or native download in normal usage.
|
|
193
|
+
|
|
194
|
+
At runtime, the package loads the native library that matches the current host from `prebuilds/<target>/`. The published package currently includes:
|
|
195
|
+
|
|
196
|
+
- `linux-x64-gnu`
|
|
197
|
+
- `linux-arm64-gnu`
|
|
198
|
+
- `darwin-x64`
|
|
199
|
+
- `darwin-arm64`
|
|
200
|
+
- `win32-x64`
|
|
201
|
+
- `win32-arm64`
|
|
202
|
+
|
|
203
|
+
Linux musl targets are not packaged today. Local builds stage only the host native library; release builds stage all supported targets into the published npm package.
|
|
204
|
+
|
|
205
|
+
When assembling a release package from a prebuilt native directory, run:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
npm --prefix bindings/node run build -- --prebuilt-dir <dir>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The generated API files stay the same; only the packaged native libraries change between local and release builds.
|
package/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./slatedb.js";
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@slatedb/uniffi",
|
|
3
|
+
"version": "0.12.0",
|
|
4
|
+
"description": "Node.js bindings for SlateDB generated from UniFFI and packaged with native libraries.",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"types": "./index.d.ts",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=20"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"README.md",
|
|
13
|
+
"index.js",
|
|
14
|
+
"index.d.ts",
|
|
15
|
+
"slatedb.js",
|
|
16
|
+
"slatedb.d.ts",
|
|
17
|
+
"slatedb-ffi.js",
|
|
18
|
+
"slatedb-ffi.d.ts",
|
|
19
|
+
"runtime/",
|
|
20
|
+
"prebuilds/"
|
|
21
|
+
],
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./index.d.ts",
|
|
25
|
+
"import": "./index.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/slatedb/slatedb.git",
|
|
31
|
+
"directory": "bindings/node"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://slatedb.io",
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/slatedb/slatedb/issues"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "node ./build.mjs",
|
|
42
|
+
"test": "npm run build && node --test"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"koffi": "^2.0.0"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { RustCallStatusStruct } from "./ffi-types.js";
|
|
2
|
+
import type { RustCallOptions, UniffiRustCaller } from "./rust-call.js";
|
|
3
|
+
|
|
4
|
+
export declare const RUST_FUTURE_POLL_READY: 0;
|
|
5
|
+
export declare const RUST_FUTURE_POLL_WAKE: 1;
|
|
6
|
+
|
|
7
|
+
export interface AsyncCallState {
|
|
8
|
+
resolverHandle: bigint | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type RustFutureContinuationCallback = (
|
|
12
|
+
handle: bigint | number,
|
|
13
|
+
pollResult: bigint | number,
|
|
14
|
+
) => boolean;
|
|
15
|
+
|
|
16
|
+
export interface PollRustFutureOptions {
|
|
17
|
+
continuationCallback?: RustFutureContinuationCallback;
|
|
18
|
+
state?: AsyncCallState;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CompleteRustFutureOptions<
|
|
22
|
+
Status = RustCallStatusStruct,
|
|
23
|
+
E extends Error = Error,
|
|
24
|
+
> extends RustCallOptions<E> {
|
|
25
|
+
rustCaller?: UniffiRustCaller<Status>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface RustCallAsyncOptions<
|
|
29
|
+
Lowered,
|
|
30
|
+
Result = Lowered,
|
|
31
|
+
Status = RustCallStatusStruct,
|
|
32
|
+
E extends Error = Error,
|
|
33
|
+
> extends CompleteRustFutureOptions<Status, E> {
|
|
34
|
+
cancelFunc?: ((rustFuture: bigint) => void) | null;
|
|
35
|
+
completeFunc: (rustFuture: bigint, status: Status) => Lowered;
|
|
36
|
+
continuationCallback?: RustFutureContinuationCallback;
|
|
37
|
+
freeFunc?: ((rustFuture: bigint) => void) | null;
|
|
38
|
+
liftFunc?: (value: Lowered) => Result;
|
|
39
|
+
pollFunc: (
|
|
40
|
+
rustFuture: bigint,
|
|
41
|
+
continuationCallback: RustFutureContinuationCallback,
|
|
42
|
+
continuationHandle: bigint,
|
|
43
|
+
) => void;
|
|
44
|
+
rustFutureFunc: () => bigint;
|
|
45
|
+
signal?: AbortSignal | null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export declare function decodeRustFuturePoll(value: bigint | number): number;
|
|
49
|
+
export declare function createAsyncCallState(): AsyncCallState;
|
|
50
|
+
export declare function cleanupAsyncCallState(state: AsyncCallState): void;
|
|
51
|
+
export declare const rustFutureContinuationCallback: RustFutureContinuationCallback;
|
|
52
|
+
export declare function pollRustFuture(
|
|
53
|
+
rustFuture: bigint,
|
|
54
|
+
pollFunc: (
|
|
55
|
+
rustFuture: bigint,
|
|
56
|
+
continuationCallback: RustFutureContinuationCallback,
|
|
57
|
+
continuationHandle: bigint,
|
|
58
|
+
) => void,
|
|
59
|
+
options?: PollRustFutureOptions,
|
|
60
|
+
): Promise<number>;
|
|
61
|
+
export declare function completeRustFuture<
|
|
62
|
+
Lowered,
|
|
63
|
+
Status = RustCallStatusStruct,
|
|
64
|
+
E extends Error = Error,
|
|
65
|
+
>(
|
|
66
|
+
rustFuture: bigint,
|
|
67
|
+
completeFunc: (rustFuture: bigint, status: Status) => Lowered,
|
|
68
|
+
options?: CompleteRustFutureOptions<Status, E>,
|
|
69
|
+
): Lowered;
|
|
70
|
+
export declare function cancelRustFuture(
|
|
71
|
+
rustFuture: bigint,
|
|
72
|
+
cancelFunc?: ((rustFuture: bigint) => void) | null,
|
|
73
|
+
): void;
|
|
74
|
+
export declare function freeRustFuture(
|
|
75
|
+
rustFuture: bigint,
|
|
76
|
+
freeFunc?: ((rustFuture: bigint) => void) | null,
|
|
77
|
+
): void;
|
|
78
|
+
export declare function rustCallAsync<
|
|
79
|
+
Lowered,
|
|
80
|
+
Result = Lowered,
|
|
81
|
+
Status = RustCallStatusStruct,
|
|
82
|
+
E extends Error = Error,
|
|
83
|
+
>(
|
|
84
|
+
options: RustCallAsyncOptions<Lowered, Result, Status, E>,
|
|
85
|
+
): Promise<Result>;
|
|
86
|
+
export declare function rustFutureHandleCount(): number;
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { AbortError, ConverterRangeError } from "./errors.js";
|
|
2
|
+
import { UniffiHandleMap } from "./handle-map.js";
|
|
3
|
+
import { defaultRustCaller } from "./rust-call.js";
|
|
4
|
+
|
|
5
|
+
export const RUST_FUTURE_POLL_READY = 0;
|
|
6
|
+
export const RUST_FUTURE_POLL_WAKE = 1;
|
|
7
|
+
const RUST_FUTURE_KEEPALIVE_DELAY_MS = 0x7fffffff;
|
|
8
|
+
|
|
9
|
+
const MIN_I8 = -0x80;
|
|
10
|
+
const MAX_I8 = 0x7f;
|
|
11
|
+
const MAX_U8 = 0xff;
|
|
12
|
+
const RUST_FUTURE_RESOLVER_MAP = new UniffiHandleMap();
|
|
13
|
+
const RUST_FUTURE_KEEPALIVE_MAP = new Map();
|
|
14
|
+
|
|
15
|
+
function identity(value) {
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function decodeRustFuturePoll(value) {
|
|
20
|
+
const numericValue =
|
|
21
|
+
typeof value === "bigint"
|
|
22
|
+
? Number(value)
|
|
23
|
+
: value;
|
|
24
|
+
if (!Number.isInteger(numericValue) || numericValue < MIN_I8 || numericValue > MAX_U8) {
|
|
25
|
+
throw new ConverterRangeError(
|
|
26
|
+
`RustFuturePoll must be an integer between ${MIN_I8} and ${MAX_U8}.`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
return numericValue > MAX_I8
|
|
30
|
+
? numericValue - 0x100
|
|
31
|
+
: numericValue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function createAsyncCallState() {
|
|
35
|
+
return {
|
|
36
|
+
resolverHandle: null,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function clearRustFutureKeepAlive(handle) {
|
|
41
|
+
const keepAlive = RUST_FUTURE_KEEPALIVE_MAP.get(handle);
|
|
42
|
+
if (keepAlive === undefined) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
RUST_FUTURE_KEEPALIVE_MAP.delete(handle);
|
|
47
|
+
clearTimeout(keepAlive);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function cleanupAsyncCallState(state) {
|
|
51
|
+
if (!state || state.resolverHandle == null) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
clearRustFutureKeepAlive(state.resolverHandle);
|
|
56
|
+
RUST_FUTURE_RESOLVER_MAP.remove(state.resolverHandle);
|
|
57
|
+
state.resolverHandle = null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const rustFutureContinuationCallback = (handle, pollResult) => {
|
|
61
|
+
clearRustFutureKeepAlive(handle);
|
|
62
|
+
const resolve = RUST_FUTURE_RESOLVER_MAP.remove(handle);
|
|
63
|
+
if (resolve === undefined) {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
queueMicrotask(() => {
|
|
68
|
+
resolve(decodeRustFuturePoll(pollResult));
|
|
69
|
+
});
|
|
70
|
+
return true;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export function pollRustFuture(
|
|
74
|
+
rustFuture,
|
|
75
|
+
pollFunc,
|
|
76
|
+
{
|
|
77
|
+
continuationCallback = rustFutureContinuationCallback,
|
|
78
|
+
state = createAsyncCallState(),
|
|
79
|
+
} = {},
|
|
80
|
+
) {
|
|
81
|
+
cleanupAsyncCallState(state);
|
|
82
|
+
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
// Node 22 can tear down the event loop while this promise is still waiting
|
|
85
|
+
// on a native callback. Keep a ref'ed timer alive until the callback
|
|
86
|
+
// settles so Rust future completions arrive before environment cleanup.
|
|
87
|
+
const keepAlive = setTimeout(() => {}, RUST_FUTURE_KEEPALIVE_DELAY_MS);
|
|
88
|
+
let resolverHandle = null;
|
|
89
|
+
const settle = (callback, value) => {
|
|
90
|
+
clearRustFutureKeepAlive(resolverHandle);
|
|
91
|
+
callback(value);
|
|
92
|
+
};
|
|
93
|
+
resolverHandle = RUST_FUTURE_RESOLVER_MAP.insert((pollCode) => {
|
|
94
|
+
state.resolverHandle = null;
|
|
95
|
+
settle(resolve, pollCode);
|
|
96
|
+
});
|
|
97
|
+
RUST_FUTURE_KEEPALIVE_MAP.set(resolverHandle, keepAlive);
|
|
98
|
+
state.resolverHandle = resolverHandle;
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
pollFunc(rustFuture, continuationCallback, resolverHandle);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
cleanupAsyncCallState(state);
|
|
104
|
+
settle(reject, error);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function completeRustFuture(
|
|
110
|
+
rustFuture,
|
|
111
|
+
completeFunc,
|
|
112
|
+
{
|
|
113
|
+
errorHandler,
|
|
114
|
+
freeRustBuffer,
|
|
115
|
+
liftString,
|
|
116
|
+
rustCaller = defaultRustCaller,
|
|
117
|
+
} = {},
|
|
118
|
+
) {
|
|
119
|
+
return rustCaller.makeRustCall(
|
|
120
|
+
(status) => completeFunc(rustFuture, status),
|
|
121
|
+
{
|
|
122
|
+
errorHandler,
|
|
123
|
+
freeRustBuffer,
|
|
124
|
+
liftString,
|
|
125
|
+
},
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function cancelRustFuture(rustFuture, cancelFunc = undefined) {
|
|
130
|
+
if (typeof cancelFunc === "function") {
|
|
131
|
+
cancelFunc(rustFuture);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function freeRustFuture(rustFuture, freeFunc = undefined) {
|
|
136
|
+
if (typeof freeFunc === "function") {
|
|
137
|
+
freeFunc(rustFuture);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function rustCallAsync({
|
|
142
|
+
cancelFunc,
|
|
143
|
+
completeFunc,
|
|
144
|
+
continuationCallback = rustFutureContinuationCallback,
|
|
145
|
+
errorHandler,
|
|
146
|
+
freeFunc,
|
|
147
|
+
freeRustBuffer,
|
|
148
|
+
liftFunc = identity,
|
|
149
|
+
liftString,
|
|
150
|
+
pollFunc,
|
|
151
|
+
rustCaller = defaultRustCaller,
|
|
152
|
+
rustFutureFunc,
|
|
153
|
+
signal = undefined,
|
|
154
|
+
}) {
|
|
155
|
+
if (signal?.aborted) {
|
|
156
|
+
throw new AbortError();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const rustFuture = rustFutureFunc();
|
|
160
|
+
const state = createAsyncCallState();
|
|
161
|
+
const abortListener =
|
|
162
|
+
signal == null
|
|
163
|
+
? null
|
|
164
|
+
: () => {
|
|
165
|
+
try {
|
|
166
|
+
cancelRustFuture(rustFuture, cancelFunc);
|
|
167
|
+
} catch {
|
|
168
|
+
// Preserve the original async failure path.
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
if (signal && abortListener) {
|
|
173
|
+
if (signal.aborted) {
|
|
174
|
+
abortListener();
|
|
175
|
+
} else {
|
|
176
|
+
signal.addEventListener("abort", abortListener, { once: true });
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
while (true) {
|
|
182
|
+
const pollCode = await pollRustFuture(rustFuture, pollFunc, {
|
|
183
|
+
continuationCallback,
|
|
184
|
+
state,
|
|
185
|
+
});
|
|
186
|
+
if (pollCode === RUST_FUTURE_POLL_READY) {
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
if (pollCode !== RUST_FUTURE_POLL_WAKE) {
|
|
190
|
+
throw new ConverterRangeError(
|
|
191
|
+
`Unexpected RustFuturePoll value ${String(pollCode)}.`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const completed = completeRustFuture(rustFuture, completeFunc, {
|
|
197
|
+
errorHandler,
|
|
198
|
+
freeRustBuffer,
|
|
199
|
+
liftString,
|
|
200
|
+
rustCaller,
|
|
201
|
+
});
|
|
202
|
+
return liftFunc(completed);
|
|
203
|
+
} finally {
|
|
204
|
+
if (signal && abortListener) {
|
|
205
|
+
signal.removeEventListener("abort", abortListener);
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
freeRustFuture(rustFuture, freeFunc);
|
|
209
|
+
} finally {
|
|
210
|
+
cleanupAsyncCallState(state);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function rustFutureHandleCount() {
|
|
216
|
+
return RUST_FUTURE_RESOLVER_MAP.size;
|
|
217
|
+
}
|