@delight-rpc/piscina 0.5.6 → 0.6.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 CHANGED
@@ -22,7 +22,9 @@ const api: IAPI = {
22
22
  }
23
23
  }
24
24
 
25
- export default createServer(api)
25
+ const [handler] = createServer(api)
26
+
27
+ export default handler
26
28
 
27
29
  // main.ts
28
30
  import { createClient } from '@delight-rpc/piscina'
@@ -30,7 +32,7 @@ import { createClient } from '@delight-rpc/piscina'
30
32
  const piscina = new Piscina({
31
33
  filename: new URL('./worker.js', import.meta.url).href
32
34
  })
33
- const client = createClient<IAPI>(piscina)
35
+ const [client] = createClient<IAPI>(piscina)
34
36
 
35
37
  await client.echo('hello world')
36
38
  ```
@@ -44,8 +46,9 @@ function createClient<IAPI extends object>(
44
46
  parameterValidators?: DelightRPC.ParameterValidators<IAPI>
45
47
  expectedVersion?: string
46
48
  channel?: string
49
+ timeout?: number
47
50
  }
48
- ): DelightRPC.ClientProxy<IAPI>
51
+ ): [client: DelightRPC.ClientProxy<IAPI>, close: () => void]
49
52
  ```
50
53
 
51
54
  ### createBatchClient
@@ -55,8 +58,9 @@ function createBatchClient<DataType>(
55
58
  , options?: {
56
59
  expectedVersion?: string
57
60
  channel?: string
61
+ timeout?: number
58
62
  }
59
- ): DelightRPC.BatchClient<DataType>
63
+ ): [client: DelightRPC.BatchClient<DataType>, close: () => void]
60
64
  ```
61
65
 
62
66
  ### createServer
@@ -70,5 +74,5 @@ function createServer<IAPI extends object>(
70
74
  ownPropsOnly?: boolean
71
75
  channel?: string | RegExp | AnyChannel
72
76
  }
73
- ): (req: unknown) => Promise<unknown>
77
+ ): [handler: (message: unknown) => Promise<unknown>, close: () => void]
74
78
  ```
package/lib/client.d.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  import * as DelightRPC from 'delight-rpc';
2
- import Piscina from 'piscina';
3
- export declare function createClient<IAPI extends object>(piscina: Piscina, { parameterValidators, expectedVersion, channel }?: {
2
+ import { Piscina } from 'piscina';
3
+ export declare function createClient<IAPI extends object>(piscina: Piscina, { parameterValidators, expectedVersion, channel, timeout }?: {
4
4
  parameterValidators?: DelightRPC.ParameterValidators<IAPI>;
5
5
  expectedVersion?: string;
6
6
  channel?: string;
7
- }): DelightRPC.ClientProxy<IAPI>;
8
- export declare function createBatchClient<DataType>(piscina: Piscina, { expectedVersion, channel }?: {
7
+ timeout?: number;
8
+ }): [client: DelightRPC.ClientProxy<IAPI>, close: () => void];
9
+ export declare function createBatchClient<DataType>(piscina: Piscina, { expectedVersion, channel, timeout }?: {
9
10
  expectedVersion?: string;
10
11
  channel?: string;
11
- }): DelightRPC.BatchClient<DataType>;
12
+ timeout?: number;
13
+ }): [client: DelightRPC.BatchClient<DataType>, close: () => void];
package/lib/client.js CHANGED
@@ -1,20 +1,75 @@
1
1
  import * as DelightRPC from 'delight-rpc';
2
- export function createClient(piscina, { parameterValidators, expectedVersion, channel } = {}) {
3
- const client = DelightRPC.createClient(createSend(piscina), {
2
+ import { SyncDestructor } from 'extra-defer';
3
+ import { AbortController, raceAbortSignals, timeoutSignal } from 'extra-abort';
4
+ import { isntUndefined, pass } from '@blackglory/prelude';
5
+ export function createClient(piscina, { parameterValidators, expectedVersion, channel, timeout } = {}) {
6
+ const destructor = new SyncDestructor();
7
+ const controller = new AbortController();
8
+ destructor.defer(abortAllPendings);
9
+ const client = DelightRPC.createClient(async function send(request, signal) {
10
+ const destructor = new SyncDestructor();
11
+ try {
12
+ const mergedSignal = raceAbortSignals([
13
+ isntUndefined(timeout) && timeoutSignal(timeout),
14
+ signal,
15
+ controller.signal
16
+ ]);
17
+ mergedSignal.addEventListener('abort', sendAbort);
18
+ destructor.defer(() => mergedSignal.removeEventListener('abort', sendAbort));
19
+ return await piscina.run(request, { signal: mergedSignal });
20
+ }
21
+ finally {
22
+ destructor.execute();
23
+ }
24
+ async function sendAbort() {
25
+ const abort = DelightRPC.createAbort(request.id, channel);
26
+ await piscina.run(abort).catch(pass);
27
+ }
28
+ }, {
4
29
  parameterValidators,
5
30
  expectedVersion,
6
31
  channel
7
32
  });
8
- return client;
33
+ return [client, close];
34
+ function close() {
35
+ destructor.execute();
36
+ }
37
+ function abortAllPendings() {
38
+ controller.abort();
39
+ }
9
40
  }
10
- export function createBatchClient(piscina, { expectedVersion, channel } = {}) {
11
- const client = new DelightRPC.BatchClient(createSend(piscina), {
41
+ export function createBatchClient(piscina, { expectedVersion, channel, timeout } = {}) {
42
+ const destructor = new SyncDestructor();
43
+ const controller = new AbortController();
44
+ destructor.defer(abortAllPendings);
45
+ const client = new DelightRPC.BatchClient(async function send(request) {
46
+ const destructor = new SyncDestructor();
47
+ try {
48
+ const mergedSignal = raceAbortSignals([
49
+ isntUndefined(timeout) && timeoutSignal(timeout),
50
+ controller.signal
51
+ ]);
52
+ mergedSignal.addEventListener('abort', sendAbort);
53
+ destructor.defer(() => mergedSignal.removeEventListener('abort', sendAbort));
54
+ return await piscina.run(request, { signal: mergedSignal });
55
+ }
56
+ finally {
57
+ destructor.execute();
58
+ }
59
+ async function sendAbort() {
60
+ const abort = DelightRPC.createAbort(request.id, channel);
61
+ await piscina.run(abort).catch(pass);
62
+ }
63
+ }, {
12
64
  expectedVersion,
13
65
  channel
14
66
  });
15
- return client;
16
- }
17
- function createSend(piscina) {
18
- return async (request) => await piscina.run(request);
67
+ return [client, close];
68
+ function close() {
69
+ destructor.execute();
70
+ }
71
+ function abortAllPendings() {
72
+ controller.abort();
73
+ }
19
74
  }
20
75
  //# sourceMappingURL=client.js.map
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,UAAU,MAAM,aAAa,CAAA;AAIzC,MAAM,UAAU,YAAY,CAC1B,OAAgB,EAChB,EAAE,mBAAmB,EAAE,eAAe,EAAE,OAAO,KAI3C,EAAE;IAEN,MAAM,MAAM,GAAG,UAAU,CAAC,YAAY,CACpC,UAAU,CAAC,OAAO,CAAC,EACnB;QACE,mBAAmB;QACnB,eAAe;QACf,OAAO;KACR,CACF,CAAA;IAED,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,OAAgB,EAChB,EAAE,eAAe,EAAE,OAAO,KAGtB,EAAE;IAEN,MAAM,MAAM,GAAG,IAAI,UAAU,CAAC,WAAW,CACvC,UAAU,CAAC,OAAO,CAAC,EACnB;QACE,eAAe;QACf,OAAO;KACR,CACF,CAAA;IAED,OAAO,MAAM,CAAA;AACf,CAAC;AAED,SAAS,UAAU,CACjB,OAAgB;IAEhB,OAAO,KAAK,EAAC,OAAO,EAAC,EAAE,CAAC,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;AACpD,CAAC"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
package/lib/server.d.ts CHANGED
@@ -4,4 +4,4 @@ export declare function createServer<IAPI extends object>(api: DelightRPC.Implem
4
4
  version?: `${number}.${number}.${number}`;
5
5
  channel?: string | RegExp | typeof DelightRPC.AnyChannel;
6
6
  ownPropsOnly?: boolean;
7
- }): (req: unknown) => Promise<unknown>;
7
+ }): [handler: (req: unknown) => Promise<unknown>, close: () => void];
package/lib/server.js CHANGED
@@ -1,18 +1,49 @@
1
1
  import * as DelightRPC from 'delight-rpc';
2
2
  import { isntNull } from '@blackglory/prelude';
3
+ import { SyncDestructor } from 'extra-defer';
4
+ import { HashMap } from '@blackglory/structures';
3
5
  export function createServer(api, { parameterValidators, version, channel, ownPropsOnly } = {}) {
4
- return async function handler(req) {
5
- if (DelightRPC.isRequest(req) || DelightRPC.isBatchRequest(req)) {
6
- const response = await DelightRPC.createResponse(api, req, {
7
- parameterValidators,
8
- version,
9
- channel,
10
- ownPropsOnly
11
- });
12
- if (isntNull(response)) {
13
- return response;
6
+ const destructor = new SyncDestructor();
7
+ const channelIdToController = new HashMap(({ channel, id }) => JSON.stringify([channel, id]));
8
+ destructor.defer(abortAllPendings);
9
+ return [handler, close];
10
+ function close() {
11
+ destructor.execute();
12
+ }
13
+ function abortAllPendings() {
14
+ for (const controller of channelIdToController.values()) {
15
+ controller.abort();
16
+ }
17
+ channelIdToController.clear();
18
+ }
19
+ async function handler(message) {
20
+ var _a;
21
+ if (DelightRPC.isRequest(message) || DelightRPC.isBatchRequest(message)) {
22
+ const destructor = new SyncDestructor();
23
+ const controller = new AbortController();
24
+ channelIdToController.set(message, controller);
25
+ destructor.defer(() => channelIdToController.delete(message));
26
+ try {
27
+ const response = await DelightRPC.createResponse(api, message, {
28
+ parameterValidators,
29
+ version,
30
+ channel,
31
+ ownPropsOnly
32
+ });
33
+ if (isntNull(response)) {
34
+ return response;
35
+ }
36
+ }
37
+ finally {
38
+ destructor.execute();
39
+ }
40
+ }
41
+ else if (DelightRPC.isAbort(message)) {
42
+ if (DelightRPC.matchChannel(message, channel)) {
43
+ (_a = channelIdToController.get(message)) === null || _a === void 0 ? void 0 : _a.abort();
44
+ channelIdToController.delete(message);
14
45
  }
15
46
  }
16
- };
47
+ }
17
48
  }
18
49
  //# sourceMappingURL=server.js.map
package/lib/server.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,UAAU,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAE9C,MAAM,UAAU,YAAY,CAC1B,GAAsC,EACtC,EAAE,mBAAmB,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,KAKjD,EAAE;IAEN,OAAO,KAAK,UAAU,OAAO,CAAC,GAAY;QACxC,IAAI,UAAU,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,cAAc,CAAC,GAAG,CAAC,EAAE;YAC/D,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,cAAc,CAC9C,GAAG,EACH,GAAG,EACH;gBACE,mBAAmB;gBACnB,OAAO;gBACP,OAAO;gBACP,YAAY;aACb,CACF,CAAA;YAED,IAAI,QAAQ,CAAC,QAAQ,CAAC,EAAE;gBACtB,OAAO,QAAQ,CAAA;aAChB;SACF;IACH,CAAC,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,UAAU,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAA;AAEhD,MAAM,UAAU,YAAY,CAC1B,GAAsC,EACtC,EAAE,mBAAmB,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,KAKjD,EAAE;IAEN,MAAM,UAAU,GAAG,IAAI,cAAc,EAAE,CAAA;IAEvC,MAAM,qBAAqB,GAMvB,IAAI,OAAO,CAAC,CAAC,EAAE,OAAO,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;IACnE,UAAU,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAA;IAElC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;IAEvB,SAAS,KAAK;QACZ,UAAU,CAAC,OAAO,EAAE,CAAA;IACtB,CAAC;IAED,SAAS,gBAAgB;QACvB,KAAK,MAAM,UAAU,IAAI,qBAAqB,CAAC,MAAM,EAAE,EAAE,CAAC;YACxD,UAAU,CAAC,KAAK,EAAE,CAAA;QACpB,CAAC;QAED,qBAAqB,CAAC,KAAK,EAAE,CAAA;IAC/B,CAAC;IAED,KAAK,UAAU,OAAO,CAAC,OAAgB;;QACrC,IAAI,UAAU,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,UAAU,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;YACxE,MAAM,UAAU,GAAG,IAAI,cAAc,EAAE,CAAA;YAEvC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAA;YACxC,qBAAqB,CAAC,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,CAAA;YAC9C,UAAU,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,qBAAqB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAA;YAE7D,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,cAAc,CAC9C,GAAG,EACH,OAAO,EACP;oBACE,mBAAmB;oBACnB,OAAO;oBACP,OAAO;oBACP,YAAY;iBACb,CACF,CAAA;gBAED,IAAI,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;oBACvB,OAAO,QAAQ,CAAA;gBACjB,CAAC;YACH,CAAC;oBAAS,CAAC;gBACT,UAAU,CAAC,OAAO,EAAE,CAAA;YACtB,CAAC;QACH,CAAC;aAAM,IAAI,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YACvC,IAAI,UAAU,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;gBAC9C,MAAA,qBAAqB,CAAC,GAAG,CAAC,OAAO,CAAC,0CAAE,KAAK,EAAE,CAAA;gBAC3C,qBAAqB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;YACvC,CAAC;QACH,CAAC;IACH,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delight-rpc/piscina",
3
- "version": "0.5.6",
3
+ "version": "0.6.0",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "files": [
@@ -12,7 +12,7 @@
12
12
  "types": "lib/index.d.ts",
13
13
  "sideEffects": false,
14
14
  "engines": {
15
- "node": ">=16"
15
+ "node": ">=22"
16
16
  },
17
17
  "repository": "git@github.com:delight-rpc/piscina.git",
18
18
  "author": "BlackGlory <woshenmedoubuzhidao@blackglory.me>",
@@ -20,8 +20,7 @@
20
20
  "scripts": {
21
21
  "prepare": "ts-patch install -s",
22
22
  "lint": "eslint --ext .js,.jsx,.ts,.tsx --quiet src __tests__",
23
- "test": "cross-env NODE_OPTIONS='--experimental-vm-modules --loader ts-node/esm' jest --runInBand --config jest.config.cjs",
24
- "test:debug": "cross-env NODE_OPTIONS='--experimental-vm-modules --loader ts-node/esm' node --inspect-brk node_modules/.bin/jest --runInBand --config jest.config.cjs",
23
+ "test": "vitest run",
25
24
  "prepublishOnly": "run-s prepare clean build",
26
25
  "clean": "rimraf lib",
27
26
  "build": "tsc --project tsconfig.build.json",
@@ -34,37 +33,37 @@
34
33
  }
35
34
  },
36
35
  "devDependencies": {
37
- "@blackglory/jest-resolver": "^0.3.0",
38
- "@commitlint/cli": "^17.4.4",
39
- "@commitlint/config-conventional": "^17.4.4",
40
- "@types/jest": "^29.5.0",
41
- "@types/node": "16",
42
- "@typescript-eslint/eslint-plugin": "^5.55.0",
43
- "@typescript-eslint/parser": "^5.55.0",
44
- "cross-env": "^7.0.3",
45
- "delight-rpc": "^6.0.0",
46
- "eslint": "8.36.0",
36
+ "@commitlint/cli": "^20.4.2",
37
+ "@commitlint/config-conventional": "^20.4.2",
38
+ "@eslint/js": "^10.0.1",
39
+ "@types/node": "22",
40
+ "cross-env": "^10.1.0",
41
+ "delight-rpc": "^7.0.0",
42
+ "eslint": "10.0.2",
43
+ "extra-promise": "^7.1.1",
47
44
  "husky": "4",
48
- "jest": "^29.5.0",
49
- "jest-environment-jsdom": "^29.5.0",
50
- "jest-resolve": "^29.5.0",
51
45
  "npm-run-all": "^4.1.5",
52
46
  "piscina": "^4.2.0",
53
- "return-style": "^3.0.0",
54
- "rimraf": "^3.0.2",
47
+ "return-style": "^4.0.0",
48
+ "rimraf": "^6.1.3",
55
49
  "standard-version": "^9.5.0",
56
- "ts-jest": "^29.0.5",
57
- "ts-node": "^10.9.1",
58
- "ts-patch": "^2.1.0",
59
- "typescript": "^4.7.4",
60
- "typescript-transform-paths": "^3.4.6"
50
+ "ts-patch": "^3.3.0",
51
+ "typescript": "^5.9.3",
52
+ "typescript-eslint": "^8.56.1",
53
+ "typescript-transform-paths": "^3.5.6",
54
+ "vite": "^7.3.1",
55
+ "vite-tsconfig-paths": "^6.1.1",
56
+ "vitest": "^4.0.18"
61
57
  },
62
58
  "dependencies": {
63
- "@blackglory/prelude": "^0.3.1",
64
- "@delight-rpc/protocol": "^4.0.0"
59
+ "@blackglory/prelude": "^0.4.0",
60
+ "@blackglory/structures": "^0.14.13",
61
+ "@delight-rpc/protocol": "^4.1.1",
62
+ "extra-abort": "^0.4.1",
63
+ "extra-defer": "^0.3.1"
65
64
  },
66
65
  "peerDependencies": {
67
- "delight-rpc": "^5.0.0 || ^6.0.0",
66
+ "delight-rpc": "^5.0.0 || ^6.0.0 || ^7.0.0",
68
67
  "piscina": "^3.2.0 || ^4.0.0"
69
68
  }
70
69
  }
package/src/client.ts CHANGED
@@ -1,17 +1,46 @@
1
1
  import * as DelightRPC from 'delight-rpc'
2
- import Piscina from 'piscina'
3
- import { IRequest, IBatchRequest } from '@delight-rpc/protocol'
2
+ import { Piscina } from 'piscina'
3
+ import { SyncDestructor } from 'extra-defer'
4
+ import { AbortController, raceAbortSignals, timeoutSignal } from 'extra-abort'
5
+ import { isntUndefined, pass } from '@blackglory/prelude'
4
6
 
5
7
  export function createClient<IAPI extends object>(
6
8
  piscina: Piscina
7
- , { parameterValidators, expectedVersion, channel }: {
9
+ , { parameterValidators, expectedVersion, channel, timeout }: {
8
10
  parameterValidators?: DelightRPC.ParameterValidators<IAPI>
9
11
  expectedVersion?: string
10
12
  channel?: string
13
+ timeout?: number
11
14
  } = {}
12
- ): DelightRPC.ClientProxy<IAPI> {
15
+ ): [client: DelightRPC.ClientProxy<IAPI>, close: () => void] {
16
+ const destructor = new SyncDestructor()
17
+
18
+ const controller = new AbortController()
19
+ destructor.defer(abortAllPendings)
20
+
13
21
  const client = DelightRPC.createClient<IAPI>(
14
- createSend(piscina)
22
+ async function send(request, signal) {
23
+ const destructor = new SyncDestructor()
24
+
25
+ try {
26
+ const mergedSignal = raceAbortSignals([
27
+ isntUndefined(timeout) && timeoutSignal(timeout)
28
+ , signal
29
+ , controller.signal
30
+ ])
31
+ mergedSignal.addEventListener('abort', sendAbort)
32
+ destructor.defer(() => mergedSignal.removeEventListener('abort', sendAbort))
33
+
34
+ return await piscina.run(request, { signal: mergedSignal })
35
+ } finally {
36
+ destructor.execute()
37
+ }
38
+
39
+ async function sendAbort(): Promise<void> {
40
+ const abort = DelightRPC.createAbort(request.id, channel)
41
+ await piscina.run(abort).catch(pass)
42
+ }
43
+ }
15
44
  , {
16
45
  parameterValidators
17
46
  , expectedVersion
@@ -19,29 +48,65 @@ export function createClient<IAPI extends object>(
19
48
  }
20
49
  )
21
50
 
22
- return client
51
+ return [client, close]
52
+
53
+ function close(): void {
54
+ destructor.execute()
55
+ }
56
+
57
+ function abortAllPendings(): void {
58
+ controller.abort()
59
+ }
23
60
  }
24
61
 
25
62
  export function createBatchClient<DataType>(
26
63
  piscina: Piscina
27
- , { expectedVersion, channel }: {
64
+ , { expectedVersion, channel, timeout }: {
28
65
  expectedVersion?: string
29
66
  channel?: string
67
+ timeout?: number
30
68
  } = {}
31
- ): DelightRPC.BatchClient<DataType> {
69
+ ): [client: DelightRPC.BatchClient<DataType>, close: () => void] {
70
+ const destructor = new SyncDestructor()
71
+
72
+ const controller = new AbortController()
73
+ destructor.defer(abortAllPendings)
74
+
32
75
  const client = new DelightRPC.BatchClient<DataType>(
33
- createSend(piscina)
76
+ async function send(request) {
77
+ const destructor = new SyncDestructor()
78
+
79
+ try {
80
+ const mergedSignal = raceAbortSignals([
81
+ isntUndefined(timeout) && timeoutSignal(timeout)
82
+ , controller.signal
83
+ ])
84
+ mergedSignal.addEventListener('abort', sendAbort)
85
+ destructor.defer(() => mergedSignal.removeEventListener('abort', sendAbort))
86
+
87
+ return await piscina.run(request, { signal: mergedSignal })
88
+ } finally {
89
+ destructor.execute()
90
+ }
91
+
92
+ async function sendAbort(): Promise<void> {
93
+ const abort = DelightRPC.createAbort(request.id, channel)
94
+ await piscina.run(abort).catch(pass)
95
+ }
96
+ }
34
97
  , {
35
98
  expectedVersion
36
99
  , channel
37
100
  }
38
101
  )
39
102
 
40
- return client
41
- }
103
+ return [client, close]
42
104
 
43
- function createSend<T>(
44
- piscina: Piscina
45
- ): (request: IRequest<unknown> | IBatchRequest<unknown>) => Promise<T> {
46
- return async request => await piscina.run(request)
105
+ function close(): void {
106
+ destructor.execute()
107
+ }
108
+
109
+ function abortAllPendings(): void {
110
+ controller.abort()
111
+ }
47
112
  }
package/src/server.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import * as DelightRPC from 'delight-rpc'
2
2
  import { isntNull } from '@blackglory/prelude'
3
+ import { SyncDestructor } from 'extra-defer'
4
+ import { HashMap } from '@blackglory/structures'
3
5
 
4
6
  export function createServer<IAPI extends object>(
5
7
  api: DelightRPC.ImplementationOf<IAPI>
@@ -9,22 +11,62 @@ export function createServer<IAPI extends object>(
9
11
  channel?: string | RegExp | typeof DelightRPC.AnyChannel
10
12
  ownPropsOnly?: boolean
11
13
  } = {}
12
- ): (req: unknown) => Promise<unknown> {
13
- return async function handler(req: unknown): Promise<unknown> {
14
- if (DelightRPC.isRequest(req) || DelightRPC.isBatchRequest(req)) {
15
- const response = await DelightRPC.createResponse(
16
- api
17
- , req
18
- , {
19
- parameterValidators
20
- , version
21
- , channel
22
- , ownPropsOnly
23
- }
24
- )
14
+ ): [handler: (req: unknown) => Promise<unknown>, close: () => void] {
15
+ const destructor = new SyncDestructor()
16
+
17
+ const channelIdToController: HashMap<
18
+ {
19
+ channel?: string
20
+ , id: string
21
+ }
22
+ , AbortController
23
+ > = new HashMap(({ channel, id }) => JSON.stringify([channel, id]))
24
+ destructor.defer(abortAllPendings)
25
+
26
+ return [handler, close]
27
+
28
+ function close(): void {
29
+ destructor.execute()
30
+ }
31
+
32
+ function abortAllPendings(): void {
33
+ for (const controller of channelIdToController.values()) {
34
+ controller.abort()
35
+ }
25
36
 
26
- if (isntNull(response)) {
27
- return response
37
+ channelIdToController.clear()
38
+ }
39
+
40
+ async function handler(message: unknown): Promise<unknown> {
41
+ if (DelightRPC.isRequest(message) || DelightRPC.isBatchRequest(message)) {
42
+ const destructor = new SyncDestructor()
43
+
44
+ const controller = new AbortController()
45
+ channelIdToController.set(message, controller)
46
+ destructor.defer(() => channelIdToController.delete(message))
47
+
48
+ try {
49
+ const response = await DelightRPC.createResponse(
50
+ api
51
+ , message
52
+ , {
53
+ parameterValidators
54
+ , version
55
+ , channel
56
+ , ownPropsOnly
57
+ }
58
+ )
59
+
60
+ if (isntNull(response)) {
61
+ return response
62
+ }
63
+ } finally {
64
+ destructor.execute()
65
+ }
66
+ } else if (DelightRPC.isAbort(message)) {
67
+ if (DelightRPC.matchChannel(message, channel)) {
68
+ channelIdToController.get(message)?.abort()
69
+ channelIdToController.delete(message)
28
70
  }
29
71
  }
30
72
  }