@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 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 FfiError extends Error {
7
- readonly name = "FfiError";
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): FfiError;
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(dll: string);
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<GetRemoteWorldLimitResponse>;
19
- getRemoteWorldLimitPerUser(): Promise<GetRemoteWorldLimitPerUserResponse>;
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
- var FfiError = class FfiError extends Error {
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 = "FfiError";
46
+ this.name = "NilError";
40
47
  this.status = status;
41
48
  }
42
49
  static fromStatus(status) {
43
- return new FfiError(null, { status });
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.pending.set(requestId, {
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.pending.delete(requestId);
113
+ this.pendingRequests.delete(requestId);
84
114
  reject(err);
85
- return;
86
115
  }
87
- this.startPolling();
88
116
  });
89
117
  }
90
- startPolling() {
91
- if (isNil(this.timer) && !this.disposed) this.timer = setTimeout(() => this.poll(), 0);
92
- }
93
- poll() {
94
- this.timer = null;
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.rejectAll(err);
99
- return;
124
+ this.emitter.emit("error", err);
100
125
  }
101
- if (!this.disposed && this.pending.size > 0) this.timer = setTimeout(() => this.poll(), Queue.POLL_INTERVAL);
126
+ this.timer = setTimeout(() => this.drain(), Queue.POLL_INTERVAL);
102
127
  }
103
- pollOnce() {
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 FfiError.fromStatus(status);
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
- for (const { reject } of this.pending.values()) reject(error);
131
- this.pending.clear();
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
- var VERSION = version;
688
- var Nil = class {
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
- this.setUserAgent(`nil-ffi-node/${version}`).catch(console.error);
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 { FfiError, HandleClosedError, Nil, VERSION, definitions };
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
- constructor(handle: Handle);
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 startPolling;
17
+ private drain;
16
18
  private poll;
17
- private pollOnce;
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
@@ -1,4 +1,5 @@
1
+ import { ffi_RequestId } from '@tsukilabs/nil-bindings';
1
2
  export declare class RequestId {
2
3
  #private;
3
- next(): number;
4
+ next(): ffi_RequestId;
4
5
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tsukilabs/nil-ffi",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "type": "module",
5
5
  "packageManager": "pnpm@11.17.0",
6
6
  "license": "AGPL-3.0-only",