@tsukilabs/nil-ffi 0.7.1 → 0.7.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/error.d.ts +9 -4
- package/dist/index.d.ts +6 -3
- package/dist/index.js +88 -40
- package/dist/queue.d.ts +16 -7
- package/dist/request.d.ts +2 -1
- package/package.json +1 -1
package/dist/error.d.ts
CHANGED
|
@@ -1,15 +1,20 @@
|
|
|
1
1
|
import { Option } from '@tb-dev/utils';
|
|
2
|
-
import { ffi_Status } from '@tsukilabs/nil-bindings';
|
|
2
|
+
import { ffi_Response, ffi_Status } from '@tsukilabs/nil-bindings';
|
|
3
3
|
export interface FfiErrorOptions extends ErrorOptions {
|
|
4
4
|
readonly status?: Option<ffi_Status>;
|
|
5
5
|
}
|
|
6
|
-
export declare class
|
|
7
|
-
readonly name = "
|
|
6
|
+
export declare class NilError extends Error {
|
|
7
|
+
readonly name = "NilError";
|
|
8
8
|
readonly status: ffi_Status;
|
|
9
9
|
constructor(message: Option<string>, options?: FfiErrorOptions);
|
|
10
|
-
static fromStatus(status: ffi_Status):
|
|
10
|
+
static fromStatus(status: ffi_Status): NilError;
|
|
11
11
|
}
|
|
12
12
|
export declare class HandleClosedError extends Error {
|
|
13
13
|
readonly name = "HandleClosedError";
|
|
14
14
|
constructor(options?: ErrorOptions);
|
|
15
15
|
}
|
|
16
|
+
export declare class UnknownResponseError extends Error {
|
|
17
|
+
readonly name = "UnknownResponseError";
|
|
18
|
+
readonly response: ffi_Response;
|
|
19
|
+
constructor(response: ffi_Response, options?: ErrorOptions);
|
|
20
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
import { definitions } from './def';
|
|
2
2
|
import { Option } from '@tb-dev/utils';
|
|
3
|
+
import { GetRemoteWorldsResponse } from '@tsukilabs/nil-bindings';
|
|
3
4
|
import * as ffi from "node:ffi";
|
|
4
5
|
export * from './def';
|
|
5
6
|
export * from './error';
|
|
6
7
|
export type Handle = ffi.DynamicLibraryResult<typeof definitions>;
|
|
7
8
|
export declare const VERSION: string;
|
|
9
|
+
export declare const USER_AGENT: string;
|
|
8
10
|
export declare class Nil implements Disposable {
|
|
9
11
|
private readonly handle;
|
|
10
12
|
readonly functions: Handle["functions"];
|
|
11
13
|
private readonly queue;
|
|
12
14
|
private disposed;
|
|
13
|
-
constructor(
|
|
15
|
+
private constructor();
|
|
16
|
+
static init(dll: string): Promise<Nil>;
|
|
14
17
|
[Symbol.dispose](): void;
|
|
15
18
|
close(): void;
|
|
16
19
|
getClientVersion(): Promise<string>;
|
|
17
20
|
getFfiVersion(): Promise<string>;
|
|
18
|
-
getRemoteWorldLimit(): Promise<
|
|
19
|
-
getRemoteWorldLimitPerUser(): Promise<
|
|
21
|
+
getRemoteWorldLimit(): Promise<number>;
|
|
22
|
+
getRemoteWorldLimitPerUser(): Promise<number>;
|
|
20
23
|
getRemoteWorlds(): Promise<GetRemoteWorldsResponse>;
|
|
21
24
|
getUserAgent(): Promise<string>;
|
|
22
25
|
getWorld(): Promise<Option<string>>;
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import * as ffi from "node:ffi";
|
|
2
|
+
import { EventEmitter } from "node:events";
|
|
2
3
|
import { isNil } from "es-toolkit/predicate";
|
|
3
4
|
import * as process from "node:process";
|
|
4
5
|
import { ffi_Status } from "@tsukilabs/nil-bindings";
|
|
5
6
|
//#region src/request.ts
|
|
7
|
+
//! Copyright (C) Call of Nil contributors
|
|
8
|
+
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
6
9
|
var RequestId = class {
|
|
7
10
|
#current = 0;
|
|
8
11
|
next() {
|
|
@@ -12,6 +15,8 @@ var RequestId = class {
|
|
|
12
15
|
};
|
|
13
16
|
//#endregion
|
|
14
17
|
//#region src/ptr.ts
|
|
18
|
+
//! Copyright (C) Call of Nil contributors
|
|
19
|
+
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
15
20
|
function allocBuffer(size) {
|
|
16
21
|
return Buffer.alloc(size ?? pointerSize());
|
|
17
22
|
}
|
|
@@ -25,7 +30,9 @@ function pointerSize() {
|
|
|
25
30
|
}
|
|
26
31
|
//#endregion
|
|
27
32
|
//#region src/error.ts
|
|
28
|
-
|
|
33
|
+
//! Copyright (C) Call of Nil contributors
|
|
34
|
+
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
35
|
+
var NilError = class NilError extends Error {
|
|
29
36
|
constructor(message, options) {
|
|
30
37
|
const status = options?.status ?? ffi_Status.ERR_UNKNOWN;
|
|
31
38
|
if (!message && status !== ffi_Status.OK) {
|
|
@@ -36,11 +43,11 @@ var FfiError = class FfiError extends Error {
|
|
|
36
43
|
}
|
|
37
44
|
message ??= "ERR_UNKNOWN";
|
|
38
45
|
super(message, options);
|
|
39
|
-
this.name = "
|
|
46
|
+
this.name = "NilError";
|
|
40
47
|
this.status = status;
|
|
41
48
|
}
|
|
42
49
|
static fromStatus(status) {
|
|
43
|
-
return new
|
|
50
|
+
return new NilError(null, { status });
|
|
44
51
|
}
|
|
45
52
|
};
|
|
46
53
|
var HandleClosedError = class extends Error {
|
|
@@ -49,8 +56,25 @@ var HandleClosedError = class extends Error {
|
|
|
49
56
|
this.name = "HandleClosedError";
|
|
50
57
|
}
|
|
51
58
|
};
|
|
59
|
+
var UnknownResponseError = class extends Error {
|
|
60
|
+
constructor(response, options) {
|
|
61
|
+
super(`got unknown response with id ${response.id}`, options);
|
|
62
|
+
this.name = "UnknownResponseError";
|
|
63
|
+
this.response = response;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
52
66
|
//#endregion
|
|
53
67
|
//#region src/queue.ts
|
|
68
|
+
//! Copyright (C) Call of Nil contributors
|
|
69
|
+
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
70
|
+
var QueueEmitter = class extends EventEmitter {
|
|
71
|
+
emit(event, ...args) {
|
|
72
|
+
return super.emit(event, ...args);
|
|
73
|
+
}
|
|
74
|
+
on(event, listener) {
|
|
75
|
+
return super.on(event, listener);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
54
78
|
var Queue = class Queue {
|
|
55
79
|
static {
|
|
56
80
|
this.POLL_INTERVAL = 16;
|
|
@@ -58,81 +82,101 @@ var Queue = class Queue {
|
|
|
58
82
|
static {
|
|
59
83
|
this.LIMIT_PER_TICK = 200;
|
|
60
84
|
}
|
|
61
|
-
constructor(handle) {
|
|
62
|
-
this.pending = /* @__PURE__ */ new Map();
|
|
85
|
+
constructor(handle, options) {
|
|
63
86
|
this.disposed = false;
|
|
87
|
+
this.emitter = new QueueEmitter();
|
|
64
88
|
this.requestId = new RequestId();
|
|
89
|
+
this.pendingRequests = /* @__PURE__ */ new Map();
|
|
65
90
|
this.functions = handle.functions;
|
|
91
|
+
this.errorHandler = options?.onError ?? console.error;
|
|
92
|
+
this.emitter.on("error", this.errorHandler);
|
|
93
|
+
this.emitter.on("response", this.onResponse.bind(this));
|
|
94
|
+
this.timer = setTimeout(() => this.drain(), 0);
|
|
66
95
|
}
|
|
67
96
|
[Symbol.dispose]() {
|
|
68
97
|
if (this.disposed) return;
|
|
69
98
|
this.disposed = true;
|
|
70
99
|
this.rejectAll(new HandleClosedError());
|
|
100
|
+
this.emitter.removeAllListeners();
|
|
71
101
|
}
|
|
72
102
|
request(start) {
|
|
73
103
|
if (this.disposed) return Promise.reject(new HandleClosedError());
|
|
74
104
|
const requestId = this.requestId.next();
|
|
75
105
|
return new Promise((resolve, reject) => {
|
|
76
|
-
this.
|
|
106
|
+
this.pendingRequests.set(requestId, {
|
|
77
107
|
resolve,
|
|
78
108
|
reject
|
|
79
109
|
});
|
|
80
110
|
try {
|
|
81
111
|
start(requestId);
|
|
82
112
|
} catch (err) {
|
|
83
|
-
this.
|
|
113
|
+
this.pendingRequests.delete(requestId);
|
|
84
114
|
reject(err);
|
|
85
|
-
return;
|
|
86
115
|
}
|
|
87
|
-
this.startPolling();
|
|
88
116
|
});
|
|
89
117
|
}
|
|
90
|
-
|
|
91
|
-
if (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (!this.disposed && this.pending.size > 0) try {
|
|
96
|
-
for (let i = 0; i < Queue.LIMIT_PER_TICK; i++) if (!this.pollOnce()) break;
|
|
118
|
+
drain() {
|
|
119
|
+
if (this.disposed) return;
|
|
120
|
+
this.clearTimeout();
|
|
121
|
+
try {
|
|
122
|
+
for (let i = 0; i < Queue.LIMIT_PER_TICK; i++) if (!this.poll()) break;
|
|
97
123
|
} catch (err) {
|
|
98
|
-
this.
|
|
99
|
-
return;
|
|
124
|
+
this.emitter.emit("error", err);
|
|
100
125
|
}
|
|
101
|
-
|
|
126
|
+
this.timer = setTimeout(() => this.drain(), Queue.POLL_INTERVAL);
|
|
102
127
|
}
|
|
103
|
-
|
|
128
|
+
poll() {
|
|
104
129
|
const out = allocBuffer();
|
|
105
130
|
const status = this.functions.nil_ffi_poll(out);
|
|
106
131
|
if (status === ffi_Status.ERR_NOTHING_TO_POLL) return false;
|
|
107
|
-
if (status !== ffi_Status.OK) throw
|
|
132
|
+
if (status !== ffi_Status.OK) throw NilError.fromStatus(status);
|
|
108
133
|
const ptr = readBufferPtr(out);
|
|
109
|
-
if (ptr === 0n) throw new Error("unexpected null pointer");
|
|
110
|
-
let json;
|
|
134
|
+
if (ptr === 0n) throw new Error("failed to poll: unexpected null pointer");
|
|
111
135
|
try {
|
|
112
|
-
json = ffi.toString(ptr);
|
|
136
|
+
const json = ffi.toString(ptr) ?? "";
|
|
137
|
+
const entry = JSON.parse(json);
|
|
138
|
+
this.dispatch(entry);
|
|
113
139
|
} finally {
|
|
114
140
|
this.functions.nil_ffi_free_str(ptr);
|
|
115
141
|
}
|
|
116
|
-
const entry = JSON.parse(json);
|
|
117
|
-
const response = JSON.parse(entry.json_str);
|
|
118
|
-
const pending = this.pending.get(response.id);
|
|
119
|
-
if (!pending) return true;
|
|
120
|
-
this.pending.delete(response.id);
|
|
121
|
-
if (response.kind === "ok") pending.resolve(response.data);
|
|
122
|
-
else pending.reject(new FfiError(response.error, { status: response.status }));
|
|
123
142
|
return true;
|
|
124
143
|
}
|
|
144
|
+
dispatch(entry) {
|
|
145
|
+
switch (entry.kind) {
|
|
146
|
+
case "response": {
|
|
147
|
+
const response = JSON.parse(entry.json_str);
|
|
148
|
+
this.emitter.emit("response", response);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
125
153
|
rejectAll(error) {
|
|
154
|
+
this.clearTimeout();
|
|
155
|
+
this.pendingRequests.values().forEach(({ reject }) => reject(error));
|
|
156
|
+
this.pendingRequests.clear();
|
|
157
|
+
}
|
|
158
|
+
clearTimeout() {
|
|
126
159
|
if (!isNil(this.timer)) {
|
|
127
160
|
clearTimeout(this.timer);
|
|
128
161
|
this.timer = null;
|
|
129
162
|
}
|
|
130
|
-
|
|
131
|
-
|
|
163
|
+
}
|
|
164
|
+
onResponse(response) {
|
|
165
|
+
const pending = this.pendingRequests.get(response.id);
|
|
166
|
+
if (pending) {
|
|
167
|
+
this.pendingRequests.delete(response.id);
|
|
168
|
+
if (response.kind === "ok") pending.resolve(response.data);
|
|
169
|
+
else {
|
|
170
|
+
const { status, error } = response;
|
|
171
|
+
pending.reject(new NilError(error, { status }));
|
|
172
|
+
}
|
|
173
|
+
} else this.emitter.emit("error", new UnknownResponseError(response));
|
|
132
174
|
}
|
|
133
175
|
};
|
|
134
176
|
//#endregion
|
|
135
177
|
//#region src/def.ts
|
|
178
|
+
//! Copyright (C) Call of Nil contributors
|
|
179
|
+
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
136
180
|
var definitions = {
|
|
137
181
|
nil_ffi_free_str: {
|
|
138
182
|
arguments: [ffi.types.POINTER],
|
|
@@ -680,19 +724,23 @@ var definitions = {
|
|
|
680
724
|
}
|
|
681
725
|
};
|
|
682
726
|
//#endregion
|
|
683
|
-
//#region package.json
|
|
684
|
-
var version = "0.7.1";
|
|
685
|
-
//#endregion
|
|
686
727
|
//#region src/index.ts
|
|
687
|
-
|
|
688
|
-
|
|
728
|
+
//! Copyright (C) Call of Nil contributors
|
|
729
|
+
//! SPDX-License-Identifier: AGPL-3.0-only
|
|
730
|
+
var VERSION = "0.7.2";
|
|
731
|
+
var USER_AGENT = `nil-ffi-node/${VERSION}`;
|
|
732
|
+
var Nil = class Nil {
|
|
689
733
|
constructor(dll) {
|
|
690
734
|
this.disposed = false;
|
|
691
735
|
if (!dll.endsWith(ffi.suffix)) dll = `${dll}.${ffi.suffix}`;
|
|
692
736
|
this.handle = ffi.dlopen(dll, definitions);
|
|
693
737
|
this.functions = this.handle.functions;
|
|
694
738
|
this.queue = new Queue(this.handle);
|
|
695
|
-
|
|
739
|
+
}
|
|
740
|
+
static async init(dll) {
|
|
741
|
+
const nil = new Nil(dll);
|
|
742
|
+
await nil.setUserAgent(USER_AGENT);
|
|
743
|
+
return nil;
|
|
696
744
|
}
|
|
697
745
|
[Symbol.dispose]() {
|
|
698
746
|
this.close();
|
|
@@ -758,4 +806,4 @@ var Nil = class {
|
|
|
758
806
|
}
|
|
759
807
|
};
|
|
760
808
|
//#endregion
|
|
761
|
-
export {
|
|
809
|
+
export { HandleClosedError, Nil, NilError, USER_AGENT, UnknownResponseError, VERSION, definitions };
|
package/dist/queue.d.ts
CHANGED
|
@@ -1,20 +1,29 @@
|
|
|
1
1
|
import { Handle } from '.';
|
|
2
|
+
import { Option } from '@tb-dev/utils';
|
|
2
3
|
import { ffi_RequestId } from '@tsukilabs/nil-bindings';
|
|
3
|
-
type RequestFn = (requestId: ffi_RequestId) => void;
|
|
4
4
|
export declare class Queue implements Disposable {
|
|
5
5
|
private static readonly POLL_INTERVAL;
|
|
6
6
|
private static readonly LIMIT_PER_TICK;
|
|
7
|
-
readonly functions: Handle["functions"];
|
|
8
|
-
private timer;
|
|
9
|
-
private readonly pending;
|
|
10
7
|
private disposed;
|
|
8
|
+
private readonly functions;
|
|
9
|
+
private readonly emitter;
|
|
11
10
|
private readonly requestId;
|
|
12
|
-
|
|
11
|
+
private readonly pendingRequests;
|
|
12
|
+
private timer;
|
|
13
|
+
private readonly errorHandler;
|
|
14
|
+
constructor(handle: Handle, options?: QueueOptions);
|
|
13
15
|
[Symbol.dispose](): void;
|
|
14
16
|
request<T = unknown>(start: RequestFn): Promise<T>;
|
|
15
|
-
private
|
|
17
|
+
private drain;
|
|
16
18
|
private poll;
|
|
17
|
-
private
|
|
19
|
+
private dispatch;
|
|
18
20
|
private rejectAll;
|
|
21
|
+
private clearTimeout;
|
|
22
|
+
private onResponse;
|
|
23
|
+
}
|
|
24
|
+
type RequestFn = (requestId: ffi_RequestId) => void;
|
|
25
|
+
type ErrorHandler = (error: unknown) => void;
|
|
26
|
+
export interface QueueOptions {
|
|
27
|
+
onError?: Option<ErrorHandler>;
|
|
19
28
|
}
|
|
20
29
|
export {};
|
package/dist/request.d.ts
CHANGED