@livequery/rpc 2.0.91 → 2.0.96
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 +250 -102
- package/dist/LimitConcurrency.js +36 -0
- package/dist/LimitConcurrency.js.map +1 -0
- package/dist/RpcChannel.js +4 -0
- package/dist/RpcChannel.js.map +1 -0
- package/dist/RxjsQueue.js +30 -0
- package/dist/RxjsQueue.js.map +1 -0
- package/dist/ServiceLinker.d.ts +2 -4
- package/dist/ServiceLinker.d.ts.map +1 -1
- package/dist/ServiceLinker.js +99 -0
- package/dist/ServiceLinker.js.map +1 -0
- package/dist/SharedWorkerChannel.d.ts +1 -1
- package/dist/SharedWorkerChannel.d.ts.map +1 -1
- package/dist/SharedWorkerChannel.js +58 -0
- package/dist/SharedWorkerChannel.js.map +1 -0
- package/dist/StorageBehaviorSubject.js +19 -0
- package/dist/StorageBehaviorSubject.js.map +1 -0
- package/dist/WorkerManager.d.ts +1 -1
- package/dist/WorkerManager.d.ts.map +1 -1
- package/dist/WorkerManager.js +73 -0
- package/dist/WorkerManager.js.map +1 -0
- package/dist/WorkerService.js +2 -0
- package/dist/WorkerService.js.map +1 -0
- package/dist/index.d.ts +8 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -2245
- package/dist/index.js.map +1 -81
- package/package.json +51 -12
package/README.md
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
# @livequery/rpc
|
|
2
2
|
|
|
3
|
-
Lightweight RxJS-based RPC utilities for
|
|
3
|
+
Lightweight RxJS-based RPC utilities for exposing services from a `SharedWorker` and consuming them from the main thread with typed proxies.
|
|
4
4
|
|
|
5
|
-
This package
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
5
|
+
This package is built for a simple model:
|
|
6
|
+
|
|
7
|
+
- Expose plain classes as worker services.
|
|
8
|
+
- Call worker methods from the UI as `await`-able functions.
|
|
9
|
+
- Stream worker `Observable`s back to the client.
|
|
10
|
+
- Mirror `BehaviorSubject`-style state across the boundary.
|
|
11
|
+
- Cancel long-running streams when the client unsubscribes.
|
|
12
12
|
|
|
13
13
|
## Installation
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
npm install @livequery/rpc rxjs
|
|
17
|
-
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or with Bun:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
18
22
|
bun add @livequery/rpc rxjs
|
|
19
23
|
```
|
|
20
24
|
|
|
21
|
-
## Exports
|
|
25
|
+
## What It Exports
|
|
22
26
|
|
|
23
27
|
```ts
|
|
24
28
|
export * from "./RpcChannel"
|
|
@@ -28,9 +32,10 @@ export * from "./WorkerService"
|
|
|
28
32
|
export * from "./WorkerManager"
|
|
29
33
|
export * from "./LimitConcurrency"
|
|
30
34
|
export * from "./StorageBehaviorSubject"
|
|
35
|
+
export * from "./RxjsQueue"
|
|
31
36
|
```
|
|
32
37
|
|
|
33
|
-
##
|
|
38
|
+
## Mental Model
|
|
34
39
|
|
|
35
40
|
```text
|
|
36
41
|
Main Thread Shared Worker
|
|
@@ -40,9 +45,18 @@ ServiceLinker --(RpcMessage)--> SharedWorkerChannel --> WorkerManager --> your s
|
|
|
40
45
|
|--------------------(response stream)-----------------------|
|
|
41
46
|
```
|
|
42
47
|
|
|
43
|
-
##
|
|
48
|
+
## When To Use This Package
|
|
49
|
+
|
|
50
|
+
Use it when you want to:
|
|
51
|
+
|
|
52
|
+
- move logic or shared state into a `SharedWorker`
|
|
53
|
+
- keep a typed service-style API instead of manually handling `postMessage`
|
|
54
|
+
- return one-shot values, promises, or `Observable`s from worker methods
|
|
55
|
+
- expose `BehaviorSubject` properties as client-consumable reactive state
|
|
44
56
|
|
|
45
|
-
|
|
57
|
+
## End-To-End Example
|
|
58
|
+
|
|
59
|
+
### 1. Define a worker service
|
|
46
60
|
|
|
47
61
|
```ts
|
|
48
62
|
import { BehaviorSubject, interval, map } from "rxjs"
|
|
@@ -51,8 +65,9 @@ export class CounterService {
|
|
|
51
65
|
value = new BehaviorSubject(0)
|
|
52
66
|
|
|
53
67
|
increment(by = 1) {
|
|
54
|
-
this.value.
|
|
55
|
-
|
|
68
|
+
const nextValue = this.value.getValue() + by
|
|
69
|
+
this.value.next(nextValue)
|
|
70
|
+
return nextValue
|
|
56
71
|
}
|
|
57
72
|
|
|
58
73
|
getCurrent() {
|
|
@@ -60,15 +75,14 @@ export class CounterService {
|
|
|
60
75
|
}
|
|
61
76
|
|
|
62
77
|
ticker() {
|
|
63
|
-
return interval(1000).pipe(map((
|
|
78
|
+
return interval(1000).pipe(map((index) => `tick-${index}`))
|
|
64
79
|
}
|
|
65
80
|
}
|
|
66
81
|
```
|
|
67
82
|
|
|
68
|
-
### 2. Expose
|
|
83
|
+
### 2. Expose it inside the `SharedWorker`
|
|
69
84
|
|
|
70
85
|
```ts
|
|
71
|
-
// worker.ts
|
|
72
86
|
import { SharedWorkerChannel, WorkerManager } from "@livequery/rpc"
|
|
73
87
|
import { CounterService } from "./CounterService"
|
|
74
88
|
|
|
@@ -78,11 +92,10 @@ const manager = new WorkerManager(channel)
|
|
|
78
92
|
manager.exposeService("counter", new CounterService())
|
|
79
93
|
```
|
|
80
94
|
|
|
81
|
-
### 3.
|
|
95
|
+
### 3. Connect from the main thread
|
|
82
96
|
|
|
83
97
|
```ts
|
|
84
|
-
|
|
85
|
-
import { SharedWorkerChannel, ServiceLinker, type WorkerService } from "@livequery/rpc"
|
|
98
|
+
import { ServiceLinker, SharedWorkerChannel, type WorkerService } from "@livequery/rpc"
|
|
86
99
|
import type { CounterService } from "./CounterService"
|
|
87
100
|
|
|
88
101
|
const worker = new SharedWorker(new URL("./worker.ts", import.meta.url), { type: "module" })
|
|
@@ -90,24 +103,139 @@ const channel = new SharedWorkerChannel(worker)
|
|
|
90
103
|
const linker = new ServiceLinker(channel)
|
|
91
104
|
|
|
92
105
|
const counter = linker.linkService<WorkerService<CounterService>>("counter")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 4. Call methods with `await`
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
const nextValue = await counter.increment(2)
|
|
112
|
+
const currentValue = await counter.getCurrent()
|
|
113
|
+
|
|
114
|
+
console.log({ nextValue, currentValue })
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 5. Subscribe to streamed responses
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
const tickerSubscription = counter.ticker().subscribe((value) => {
|
|
121
|
+
console.log("tick", value)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
setTimeout(() => {
|
|
125
|
+
tickerSubscription.unsubscribe()
|
|
126
|
+
}, 5000)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### 6. Consume `BehaviorSubject` state
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
const stateSubscription = counter.value.subscribe((value) => {
|
|
133
|
+
console.log("counter value", value)
|
|
134
|
+
})
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## How Calls Behave
|
|
138
|
+
|
|
139
|
+
Every service method call is exposed on the client as an `Observable` that is also `PromiseLike`.
|
|
140
|
+
|
|
141
|
+
That means you can do either of these:
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
const result = await service.someMethod()
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
service.someMethod().subscribe((value) => {
|
|
149
|
+
console.log(value)
|
|
150
|
+
})
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
In practice:
|
|
154
|
+
|
|
155
|
+
- use `await` for one-shot values or promise-returning methods
|
|
156
|
+
- use `subscribe()` for streaming results returned from worker `Observable`s
|
|
157
|
+
|
|
158
|
+
## BehaviorSubject Mirroring
|
|
159
|
+
|
|
160
|
+
If your service exposes a property with a `getValue()` method, `WorkerManager` will include its initial value during service initialization.
|
|
161
|
+
|
|
162
|
+
This is what allows a worker-side `BehaviorSubject` to feel usable on the client:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
class SettingsService {
|
|
166
|
+
theme = new BehaviorSubject("light")
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
const settings = linker.linkService<WorkerService<SettingsService>>("settings")
|
|
172
|
+
|
|
173
|
+
settings.theme.subscribe((theme) => {
|
|
174
|
+
console.log(theme)
|
|
175
|
+
})
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Notes:
|
|
179
|
+
|
|
180
|
+
- the initial snapshot is fetched through an internal `____initialize____` call
|
|
181
|
+
- later updates are streamed by subscribing to the remote property
|
|
182
|
+
- this pattern is designed around `BehaviorSubject`-like objects
|
|
183
|
+
|
|
184
|
+
## Waiting For Multiple Services To Initialize
|
|
185
|
+
|
|
186
|
+
`ServiceLinker` exposes a helper for waiting until all linked services have loaded their initial state.
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
const counter = linker.linkService<WorkerService<CounterService>>("counter")
|
|
190
|
+
const settings = linker.linkService<WorkerService<SettingsService>>("settings")
|
|
191
|
+
|
|
192
|
+
ServiceLinker.ready$({ counter, settings }).subscribe((ready) => {
|
|
193
|
+
if (ready) {
|
|
194
|
+
console.log("all services initialized")
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
```
|
|
93
198
|
|
|
94
|
-
|
|
95
|
-
const next = await counter.increment(2)
|
|
96
|
-
console.log(next)
|
|
199
|
+
## Nested Access
|
|
97
200
|
|
|
98
|
-
|
|
99
|
-
|
|
201
|
+
The client proxy supports nested member access by path.
|
|
202
|
+
|
|
203
|
+
For example, a worker service like this:
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
class UserService {
|
|
207
|
+
profile = {
|
|
208
|
+
getName: () => "Ada",
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
100
212
|
|
|
101
|
-
|
|
102
|
-
const sub = counter.ticker().subscribe((v) => console.log(v))
|
|
213
|
+
can be consumed like this:
|
|
103
214
|
|
|
104
|
-
|
|
105
|
-
|
|
215
|
+
```ts
|
|
216
|
+
const user = linker.linkService<WorkerService<UserService>>("user")
|
|
217
|
+
const name = await user.profile.getName()
|
|
106
218
|
```
|
|
107
219
|
|
|
108
|
-
##
|
|
220
|
+
## Cancellation Model
|
|
109
221
|
|
|
110
|
-
|
|
222
|
+
If a client unsubscribes from an in-flight request before it completes, `ServiceLinker` sends a cancellation message:
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
{ id: 0, cancel: { id: requestId } }
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`WorkerManager` uses that request id to unsubscribe from the worker-side stream.
|
|
229
|
+
|
|
230
|
+
This mainly matters for:
|
|
231
|
+
|
|
232
|
+
- infinite or long-lived `Observable`s
|
|
233
|
+
- expensive operations you no longer need
|
|
234
|
+
- UI screens that mount and unmount frequently
|
|
235
|
+
|
|
236
|
+
## API Reference
|
|
237
|
+
|
|
238
|
+
### `RpcMessage`
|
|
111
239
|
|
|
112
240
|
```ts
|
|
113
241
|
type RpcMessage = {
|
|
@@ -126,32 +254,38 @@ type RpcMessage = {
|
|
|
126
254
|
}
|
|
127
255
|
```
|
|
128
256
|
|
|
129
|
-
|
|
257
|
+
### `RpcChannel`
|
|
130
258
|
|
|
131
|
-
Abstract
|
|
259
|
+
Abstract transport used by both client and worker.
|
|
132
260
|
|
|
133
261
|
```ts
|
|
134
|
-
abstract class RpcChannel extends Subject<
|
|
135
|
-
respond: (msg: RpcMessage["response"]) => void
|
|
136
|
-
|
|
262
|
+
abstract class RpcChannel extends Subject<
|
|
263
|
+
RpcMessage & { respond: (msg: RpcMessage["response"]) => void }
|
|
264
|
+
> {
|
|
137
265
|
abstract send(message: RpcMessage): void
|
|
138
266
|
}
|
|
139
267
|
```
|
|
140
268
|
|
|
141
|
-
|
|
269
|
+
### `SharedWorkerChannel`
|
|
142
270
|
|
|
143
|
-
|
|
144
|
-
- Worker context: `new SharedWorkerChannel()`
|
|
145
|
-
- Main thread: `new SharedWorkerChannel(sharedWorker)`
|
|
271
|
+
Concrete `RpcChannel` implementation for a `SharedWorker` transport.
|
|
146
272
|
|
|
147
|
-
|
|
148
|
-
- Incoming requests/responses via MessagePort events
|
|
149
|
-
- Respond function wiring
|
|
150
|
-
- Message dispatch with `send`
|
|
273
|
+
Worker side:
|
|
151
274
|
|
|
152
|
-
|
|
275
|
+
```ts
|
|
276
|
+
const channel = new SharedWorkerChannel()
|
|
277
|
+
```
|
|
153
278
|
|
|
154
|
-
|
|
279
|
+
Main thread side:
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
const worker = new SharedWorker(new URL("./worker.ts", import.meta.url), { type: "module" })
|
|
283
|
+
const channel = new SharedWorkerChannel(worker)
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### `WorkerManager`
|
|
287
|
+
|
|
288
|
+
Registers named services and routes incoming RPC requests.
|
|
155
289
|
|
|
156
290
|
```ts
|
|
157
291
|
class WorkerManager {
|
|
@@ -161,47 +295,52 @@ class WorkerManager {
|
|
|
161
295
|
```
|
|
162
296
|
|
|
163
297
|
Behavior:
|
|
164
|
-
- Resolves nested method paths sent by the client
|
|
165
|
-
- Calls functions with args, or returns property values
|
|
166
|
-
- Streams Observable-like results back until completion
|
|
167
|
-
- Supports cancellation through a `cancel` message
|
|
168
|
-
- Adds an internal `____initialize____` method to gather initial values from properties that expose `getValue()`
|
|
169
298
|
|
|
170
|
-
|
|
299
|
+
- resolves nested property or method paths
|
|
300
|
+
- invokes functions with arguments
|
|
301
|
+
- returns plain values for property access
|
|
302
|
+
- streams observable-like results until completion
|
|
303
|
+
- unsubscribes worker-side streams when cancellation arrives
|
|
304
|
+
|
|
305
|
+
### `ServiceLinker`
|
|
171
306
|
|
|
172
|
-
|
|
307
|
+
Creates and caches client-side proxies.
|
|
173
308
|
|
|
174
309
|
```ts
|
|
175
310
|
class ServiceLinker {
|
|
176
311
|
constructor(channel: RpcChannel)
|
|
177
|
-
linkService<T>(name: string): T
|
|
312
|
+
linkService<T>(name: string): WorkerService<T>
|
|
313
|
+
static ready$(services: Record<string, any>): Observable<boolean>
|
|
178
314
|
}
|
|
179
315
|
```
|
|
180
316
|
|
|
181
317
|
Behavior:
|
|
182
|
-
- Caches proxies by service name
|
|
183
|
-
- Sends RPC requests with incrementing request ids
|
|
184
|
-
- Returns an Observable for each call
|
|
185
|
-
- Returned Observable is Promise-like, so you can use `await`
|
|
186
|
-
- Subscriptions automatically send cancellation when unsubscribed
|
|
187
|
-
- Initializes BehaviorSubject-like remote state via `____initialize____`
|
|
188
318
|
|
|
189
|
-
|
|
319
|
+
- caches proxies by service name
|
|
320
|
+
- assigns incrementing request ids
|
|
321
|
+
- returns an `Observable` that is also `PromiseLike`
|
|
322
|
+
- sends cancellation when a request is unsubscribed early
|
|
323
|
+
- initializes `BehaviorSubject`-style values via `____initialize____`
|
|
324
|
+
|
|
325
|
+
### `WorkerService<T>`
|
|
326
|
+
|
|
327
|
+
Type helper that converts a worker-side contract into a client-side contract.
|
|
328
|
+
|
|
329
|
+
Rules:
|
|
190
330
|
|
|
191
|
-
|
|
192
|
-
- `
|
|
193
|
-
-
|
|
194
|
-
|
|
331
|
+
- `BehaviorSubject<T>` stays `BehaviorSubject<T>`
|
|
332
|
+
- `Observable<T>` stays `Observable<T>`
|
|
333
|
+
- methods become async call signatures
|
|
334
|
+
|
|
335
|
+
Example:
|
|
195
336
|
|
|
196
337
|
```ts
|
|
197
|
-
type WorkerService<
|
|
198
|
-
[K in keyof T]: ...
|
|
199
|
-
}
|
|
338
|
+
type CounterClient = WorkerService<CounterService>
|
|
200
339
|
```
|
|
201
340
|
|
|
202
|
-
|
|
341
|
+
## Utility Helpers
|
|
203
342
|
|
|
204
|
-
|
|
343
|
+
### `StorageBehaviorSubject<T>`
|
|
205
344
|
|
|
206
345
|
`BehaviorSubject` with persistence hooks.
|
|
207
346
|
|
|
@@ -217,55 +356,62 @@ class StorageBehaviorSubject<T> extends BehaviorSubject<T> {
|
|
|
217
356
|
}
|
|
218
357
|
```
|
|
219
358
|
|
|
220
|
-
Behavior:
|
|
221
|
-
- Reads initial value from storage
|
|
222
|
-
- Supports sync or async `getItem`
|
|
223
|
-
- Writes on every `next`
|
|
224
|
-
|
|
225
359
|
Example:
|
|
226
360
|
|
|
227
361
|
```ts
|
|
228
362
|
const storage = {
|
|
229
363
|
getItem: <T>(key: string) => JSON.parse(localStorage.getItem(key) || "null") as T | undefined,
|
|
230
|
-
setItem: <T>(key: string, value: T) =>
|
|
364
|
+
setItem: <T>(key: string, value: T) => {
|
|
365
|
+
localStorage.setItem(key, JSON.stringify(value))
|
|
366
|
+
},
|
|
231
367
|
}
|
|
232
368
|
|
|
233
369
|
const theme$ = new StorageBehaviorSubject(storage, "theme", "light")
|
|
234
370
|
theme$.next("dark")
|
|
235
371
|
```
|
|
236
372
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
Decorator factory for queuing decorated method calls and executing them through an internal stream.
|
|
373
|
+
### `LimitConcurrency`
|
|
240
374
|
|
|
241
|
-
|
|
242
|
-
const LimitConcurrency = (limit = 1) => (target, propertyKey, descriptor) => { ... }
|
|
243
|
-
```
|
|
244
|
-
|
|
245
|
-
Usage:
|
|
375
|
+
Decorator factory that queues method calls and executes them with a concurrency cap.
|
|
246
376
|
|
|
247
377
|
```ts
|
|
248
|
-
class
|
|
249
|
-
@LimitConcurrency(
|
|
250
|
-
async
|
|
378
|
+
class ApiService {
|
|
379
|
+
@LimitConcurrency(2)
|
|
380
|
+
async fetchItem(id: string) {
|
|
251
381
|
return { id }
|
|
252
382
|
}
|
|
253
383
|
}
|
|
254
384
|
```
|
|
255
385
|
|
|
256
|
-
|
|
257
|
-
- Works with values, Promises, and Observables
|
|
258
|
-
- Returns an Observable that is also Promise-like
|
|
386
|
+
Useful when you want a service method to:
|
|
259
387
|
|
|
260
|
-
|
|
388
|
+
- limit concurrent async work
|
|
389
|
+
- preserve a simple call API
|
|
390
|
+
- still return something `await`-able or subscribable
|
|
391
|
+
|
|
392
|
+
### `RxjsQueue`
|
|
261
393
|
|
|
262
|
-
|
|
394
|
+
Simple promise queue built on RxJS operators.
|
|
263
395
|
|
|
264
396
|
```ts
|
|
265
|
-
|
|
397
|
+
const queue = new RxjsQueue(2)
|
|
398
|
+
|
|
399
|
+
const result = await queue.run(async () => {
|
|
400
|
+
return doWork()
|
|
401
|
+
})
|
|
266
402
|
```
|
|
267
403
|
|
|
268
|
-
`
|
|
404
|
+
Use `updateLimit()` to change concurrency at runtime.
|
|
405
|
+
|
|
406
|
+
## Constraints And Assumptions
|
|
407
|
+
|
|
408
|
+
Keep these implementation details in mind:
|
|
409
|
+
|
|
410
|
+
- transport is specifically designed around `SharedWorker`
|
|
411
|
+
- service member paths beginning with `#` are treated as invalid
|
|
412
|
+
- missing services return an RPC error response
|
|
413
|
+
- errors are propagated back as `Error(message)` on the client
|
|
414
|
+
- worker-to-client state mirroring is optimized for `BehaviorSubject`-style fields
|
|
269
415
|
|
|
270
416
|
## Build
|
|
271
417
|
|
|
@@ -273,13 +419,15 @@ If a client unsubscribes from an in-flight call, `ServiceLinker` sends:
|
|
|
273
419
|
bun run build
|
|
274
420
|
```
|
|
275
421
|
|
|
276
|
-
|
|
277
|
-
|
|
422
|
+
Other scripts:
|
|
423
|
+
|
|
278
424
|
- `bun run clean`
|
|
425
|
+
- `bun run build:js`
|
|
426
|
+
- `bun run build:types`
|
|
427
|
+
- `bun run build:watch`
|
|
279
428
|
|
|
280
|
-
## Package
|
|
429
|
+
## Package Output
|
|
281
430
|
|
|
282
|
-
-
|
|
283
|
-
-
|
|
284
|
-
-
|
|
285
|
-
- Peer runtime dependency: `rxjs`
|
|
431
|
+
- ESM entry: `dist/index.js`
|
|
432
|
+
- type declarations: `dist/index.d.ts`
|
|
433
|
+
- package name: `@livequery/rpc`
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { catchError, EMPTY, finalize, from, lastValueFrom, mergeMap, Observable, of, Subject, Subscriber, tap } from "rxjs";
|
|
2
|
+
export const LimitConcurrency = (limit = 1) => (target, propertyKey, descriptor) => {
|
|
3
|
+
const originalMethod = descriptor.value;
|
|
4
|
+
const sj = new Subject();
|
|
5
|
+
sj.pipe(mergeMap(async ({ args, o }) => {
|
|
6
|
+
try {
|
|
7
|
+
const result = await originalMethod.apply(target, args);
|
|
8
|
+
const observable = result instanceof Promise ? from(result) : (result instanceof Observable ? result : of(result));
|
|
9
|
+
await lastValueFrom(observable.pipe(tap(data => o.next(data)), catchError(e => {
|
|
10
|
+
o.error(e);
|
|
11
|
+
return EMPTY;
|
|
12
|
+
}), finalize(() => o.complete())), { defaultValue: null });
|
|
13
|
+
}
|
|
14
|
+
catch (e) {
|
|
15
|
+
o.error(e);
|
|
16
|
+
}
|
|
17
|
+
}, limit)).subscribe();
|
|
18
|
+
const ovf = (...args) => {
|
|
19
|
+
const o = new Observable(o => {
|
|
20
|
+
sj.next({ args, o });
|
|
21
|
+
});
|
|
22
|
+
return Object.assign(o, {
|
|
23
|
+
async then(resolve, reject) {
|
|
24
|
+
try {
|
|
25
|
+
const r = await lastValueFrom(o, { defaultValue: null });
|
|
26
|
+
resolve(r);
|
|
27
|
+
}
|
|
28
|
+
catch (e) {
|
|
29
|
+
reject(e);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
return ovf;
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=LimitConcurrency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LimitConcurrency.js","sourceRoot":"","sources":["../src/LimitConcurrency.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,MAAM,CAAA;AAI3H,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAA6C,QAAgB,CAAC,EAAE,EAAE,CAAC,CAAC,MAAW,EAAE,WAAmB,EAAE,UAAsC,EAAE,EAAE;IAC5K,MAAM,cAAc,GAAG,UAAU,CAAC,KAAU,CAAA;IAC5C,MAAM,EAAE,GAAG,IAAI,OAAO,EAAqC,CAAA;IAC3D,EAAE,CAAC,IAAI,CACH,QAAQ,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE;QAC3B,IAAI,CAAC;YACD,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;YACvD,MAAM,UAAU,GAAG,MAAM,YAAY,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,YAAY,UAAU,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAA;YAClH,MAAM,aAAa,CAAC,UAAU,CAAC,IAAI,CAC/B,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,EACzB,UAAU,CAAC,CAAC,CAAC,EAAE;gBACX,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;gBACV,OAAO,KAAK,CAAA;YAChB,CAAC,CAAC,EACF,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAC/B,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAA;QAC9B,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QACd,CAAC;IACL,CAAC,EAAE,KAAK,CAAC,CACZ,CAAC,SAAS,EAAE,CAAA;IAEb,MAAM,GAAG,GAAG,CAAC,GAAG,IAAW,EAAE,EAAE;QAC3B,MAAM,CAAC,GAAG,IAAI,UAAU,CAAC,CAAC,CAAC,EAAE;YACzB,EAAE,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAA;QACxB,CAAC,CAAC,CAAA;QACF,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE;YACpB,KAAK,CAAC,IAAI,CAAC,OAA6B,EAAE,MAA8B;gBACpE,IAAI,CAAC;oBACD,MAAM,CAAC,GAAG,MAAM,aAAa,CAAC,CAAC,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC,CAAA;oBACxD,OAAO,CAAC,CAAC,CAAC,CAAA;gBACd,CAAC;gBAAC,OAAO,CAAC,EAAE,CAAC;oBACT,MAAM,CAAC,CAAC,CAAC,CAAA;gBACb,CAAC;YACL,CAAC;SACJ,CAAC,CAAA;IACN,CAAC,CAAA;IAED,OAAO,GAAU,CAAA;AACrB,CAAC,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RpcChannel.js","sourceRoot":"","sources":["../src/RpcChannel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAmB9B,MAAM,OAAgB,UAAW,SAAQ,OAAwE;CAEhH"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { mergeMap, Subject, switchMap } from "rxjs";
|
|
2
|
+
export class RxjsQueue {
|
|
3
|
+
#limit$ = new Subject();
|
|
4
|
+
#task = new Subject();
|
|
5
|
+
constructor(limit) {
|
|
6
|
+
this.#limit$.pipe(switchMap(limit => (this.#task.pipe(mergeMap(async ({ fn, s, r }) => {
|
|
7
|
+
try {
|
|
8
|
+
const data = await fn();
|
|
9
|
+
s(data);
|
|
10
|
+
}
|
|
11
|
+
catch (e) {
|
|
12
|
+
r(e);
|
|
13
|
+
}
|
|
14
|
+
}, limit))))).subscribe();
|
|
15
|
+
limit && limit >= 1 && this.#limit$.next(limit);
|
|
16
|
+
}
|
|
17
|
+
updateLimit(limit) {
|
|
18
|
+
this.#limit$.next(limit);
|
|
19
|
+
}
|
|
20
|
+
run(fn) {
|
|
21
|
+
return new Promise((s, r) => {
|
|
22
|
+
this.#task.next({
|
|
23
|
+
fn,
|
|
24
|
+
s,
|
|
25
|
+
r
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=RxjsQueue.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RxjsQueue.js","sourceRoot":"","sources":["../src/RxjsQueue.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAIpD,MAAM,OAAO,SAAS;IAElB,OAAO,GAAG,IAAI,OAAO,EAAU,CAAA;IAE/B,KAAK,GAAG,IAAI,OAAO,EAIf,CAAA;IACJ,YAAY,KAAc;QACtB,IAAI,CAAC,OAAO,CAAC,IAAI,CACb,SAAS,CAAC,KAAK,CAAC,EAAE,CAAC,CACf,IAAI,CAAC,KAAK,CAAC,IAAI,CACX,QAAQ,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE;YAC5B,IAAI,CAAC;gBACD,MAAM,IAAI,GAAG,MAAM,EAAE,EAAE,CAAA;gBACvB,CAAC,CAAC,IAAI,CAAC,CAAA;YACX,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACT,CAAC,CAAC,CAAC,CAAC,CAAA;YACR,CAAC;QACL,CAAC,EAAE,KAAK,CAAC,CACZ,CACJ,CAAC,CACL,CAAC,SAAS,EAAE,CAAA;QACb,KAAK,IAAI,KAAK,IAAI,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IACnD,CAAC;IAED,WAAW,CAAC,KAAa;QACrB,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;IAC5B,CAAC;IAED,GAAG,CAAC,EAAsB;QACtB,OAAO,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;YACxB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;gBACZ,EAAE;gBACF,CAAC;gBACD,CAAC;aACJ,CAAC,CAAA;QACN,CAAC,CAAC,CAAA;IACN,CAAC;CACJ"}
|
package/dist/ServiceLinker.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
3
|
-
import type { WorkerService } from "./WorkerService";
|
|
1
|
+
import type { RpcChannel } from "./RpcChannel.js";
|
|
2
|
+
import type { WorkerService } from "./WorkerService.js";
|
|
4
3
|
export declare class ServiceLinker {
|
|
5
4
|
#private;
|
|
6
5
|
private channel;
|
|
7
6
|
constructor(channel: RpcChannel);
|
|
8
7
|
linkService<T>(name: string): WorkerService<T>;
|
|
9
|
-
static ready$(services: any): Observable<boolean>;
|
|
10
8
|
}
|
|
11
9
|
//# sourceMappingURL=ServiceLinker.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ServiceLinker.d.ts","sourceRoot":"","sources":["../src/ServiceLinker.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ServiceLinker.d.ts","sourceRoot":"","sources":["../src/ServiceLinker.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAKxD,qBAAa,aAAa;;IAUV,OAAO,CAAC,OAAO;gBAAP,OAAO,EAAE,UAAU;IAkBvC,WAAW,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC,CAAC,CAAC;CAiFjD"}
|