@dxos/async 0.10.0 → 0.11.1
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/lib/chunk-events.mjs +384 -0
- package/dist/lib/chunk-events.mjs.map +1 -0
- package/dist/lib/index.mjs +1327 -0
- package/dist/lib/index.mjs.map +1 -0
- package/dist/lib/testing.mjs +30 -0
- package/dist/lib/testing.mjs.map +1 -0
- package/dist/types/src/index.d.ts +0 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/task-scheduling.d.ts.map +1 -1
- package/dist/types/src/update-scheduler.d.ts +40 -6
- package/dist/types/src/update-scheduler.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +14 -12
- package/src/index.ts +0 -1
- package/src/task-scheduling.ts +4 -0
- package/src/update-scheduler.test.ts +192 -2
- package/src/update-scheduler.ts +104 -30
- package/dist/lib/browser/index.mjs +0 -1677
- package/dist/lib/browser/index.mjs.map +0 -7
- package/dist/lib/browser/meta.json +0 -1
- package/dist/lib/node-esm/index.mjs +0 -1679
- package/dist/lib/node-esm/index.mjs.map +0 -7
- package/dist/lib/node-esm/meta.json +0 -1
|
@@ -0,0 +1,1327 @@
|
|
|
1
|
+
import { a as sleepWithContext, c as observableError, i as sleep, l as toError, n as asyncReturn, o as unrefTimeout, r as asyncTimeout, s as TimeoutError, t as Event } from "./chunk-events.mjs";
|
|
2
|
+
import { ComplexMap, createSetDispatch } from "@dxos/util";
|
|
3
|
+
import { Context, ContextDisposedError, LifecycleState, Resource, cancelWithContext } from "@dxos/context";
|
|
4
|
+
import { StackTrace, warnAfterTimeout } from "@dxos/debug";
|
|
5
|
+
import Observable from "zen-observable";
|
|
6
|
+
import PushStream from "zen-push";
|
|
7
|
+
import { invariant } from "@dxos/invariant";
|
|
8
|
+
import { log } from "@dxos/log";
|
|
9
|
+
//#region src/callback.ts
|
|
10
|
+
/**
|
|
11
|
+
* Helper to convert a callback based API into a promise based API.
|
|
12
|
+
*/
|
|
13
|
+
var promiseFromCallback = (run) => new Promise((resolve, reject) => {
|
|
14
|
+
run((error, value) => {
|
|
15
|
+
if (error) reject(error);
|
|
16
|
+
else resolve(value);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
//#endregion
|
|
20
|
+
//#region src/chain.ts
|
|
21
|
+
/**
|
|
22
|
+
* Async reducer iteratively applies functions to the given array of elements.
|
|
23
|
+
*/
|
|
24
|
+
var chain = (chain) => async (elements) => {
|
|
25
|
+
let result = await elements;
|
|
26
|
+
for (const part of chain) result = await Promise.all(result.map(async (element) => await part(element)));
|
|
27
|
+
return result;
|
|
28
|
+
};
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/cleanup.ts
|
|
31
|
+
/**
|
|
32
|
+
* Combine multiple cleanup functions into a single cleanup function.
|
|
33
|
+
* Can be used in effect hooks in conjunction with `addEventListener`.
|
|
34
|
+
*/
|
|
35
|
+
var combine = (...cleanupFns) => {
|
|
36
|
+
return () => {
|
|
37
|
+
cleanupFns.flat().filter((f) => typeof f === "function").forEach((cleanupFn) => cleanupFn());
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
var timeout = (cb, ms = 0) => {
|
|
41
|
+
const t = setTimeout(cb, ms);
|
|
42
|
+
return () => clearTimeout(t);
|
|
43
|
+
};
|
|
44
|
+
var interval = (cb, ms) => {
|
|
45
|
+
const t = setInterval(cb, ms);
|
|
46
|
+
return () => clearInterval(t);
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* Add the event listener and return a cleanup function.
|
|
50
|
+
* Can be used in effect hooks in conjunction with `combine`.
|
|
51
|
+
*/
|
|
52
|
+
var addEventListener = (target, type, listener, options) => {
|
|
53
|
+
target.addEventListener(type, listener, options);
|
|
54
|
+
return () => target.removeEventListener(type, listener, options);
|
|
55
|
+
};
|
|
56
|
+
var SubscriptionList = class {
|
|
57
|
+
_cleanups = [];
|
|
58
|
+
add(...cb) {
|
|
59
|
+
this._cleanups.push(...cb);
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
clear() {
|
|
63
|
+
this._cleanups.forEach((cb) => cb());
|
|
64
|
+
this._cleanups.length = 0;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
var SubscriptionSet = class {
|
|
68
|
+
_cleanupMap;
|
|
69
|
+
constructor(keyProjection) {
|
|
70
|
+
this._cleanupMap = new ComplexMap(keyProjection);
|
|
71
|
+
}
|
|
72
|
+
set(key, cb) {
|
|
73
|
+
this._cleanupMap.set(key, cb);
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
clear() {
|
|
77
|
+
this._cleanupMap.forEach((cb) => cb());
|
|
78
|
+
this._cleanupMap.clear();
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
//#endregion
|
|
82
|
+
//#region src/debounce.ts
|
|
83
|
+
/**
|
|
84
|
+
* Delay callback execution by a specified time.
|
|
85
|
+
* Unlike debounce, subsequent calls during the delay period are ignored.
|
|
86
|
+
*
|
|
87
|
+
* @param cb Callback to invoke.
|
|
88
|
+
* @param delay Time to wait before invoking the callback.
|
|
89
|
+
* @returns A new function that schedules the callback once and ignores subsequent calls until executed.
|
|
90
|
+
*/
|
|
91
|
+
var delay = (cb, delay = 100) => {
|
|
92
|
+
let pending = false;
|
|
93
|
+
return ((...args) => {
|
|
94
|
+
if (pending) return;
|
|
95
|
+
pending = true;
|
|
96
|
+
setTimeout(() => {
|
|
97
|
+
try {
|
|
98
|
+
cb(...args);
|
|
99
|
+
} finally {
|
|
100
|
+
pending = false;
|
|
101
|
+
}
|
|
102
|
+
}, delay);
|
|
103
|
+
});
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* Debounce callback: delays execution until calls stop.
|
|
107
|
+
* Each new call resets the timer, so the callback fires only after the delay elapses with no further calls (trailing-edge).
|
|
108
|
+
* Use when you want to react to the end of a burst of events (e.g. user stops typing).
|
|
109
|
+
*
|
|
110
|
+
* @param cb Callback to invoke.
|
|
111
|
+
* @param delay Idle time (ms) to wait after the last call before invoking.
|
|
112
|
+
* @returns Wrapped function that postpones invocation until activity ceases.
|
|
113
|
+
*/
|
|
114
|
+
var debounce = (cb, delay = 100) => {
|
|
115
|
+
let t;
|
|
116
|
+
return ((...args) => {
|
|
117
|
+
clearTimeout(t);
|
|
118
|
+
t = setTimeout(() => cb(...args), delay);
|
|
119
|
+
});
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Throttle callback: limits execution to at most once per interval.
|
|
123
|
+
* The callback fires immediately on the first call;
|
|
124
|
+
* subsequent calls within the same interval are dropped (leading-edge).
|
|
125
|
+
* Use when you need regular updates at a bounded rate (e.g. scroll or resize handlers).
|
|
126
|
+
*
|
|
127
|
+
* @param cb Callback to invoke.
|
|
128
|
+
* @param delay Minimum interval (ms) between successive invocations.
|
|
129
|
+
* @returns Wrapped function that rate-limits invocations.
|
|
130
|
+
*/
|
|
131
|
+
var throttle = (cb, delay = 100) => {
|
|
132
|
+
let lastCall = 0;
|
|
133
|
+
return ((...args) => {
|
|
134
|
+
const now = Date.now();
|
|
135
|
+
if (now - lastCall >= delay) {
|
|
136
|
+
cb(...args);
|
|
137
|
+
lastCall = now;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* Debounce and throttle callback.
|
|
143
|
+
* Executes immediately on the first call (throttle), prevents calls during the throttle window,
|
|
144
|
+
* and ensures a final call happens after activity stops (debounce).
|
|
145
|
+
*
|
|
146
|
+
* @param cb Callback to invoke.
|
|
147
|
+
* @param delay Time window for both throttle and debounce.
|
|
148
|
+
* @returns A new function that combines throttle and debounce behavior.
|
|
149
|
+
*/
|
|
150
|
+
var debounceAndThrottle = (cb, delay = 100) => {
|
|
151
|
+
let timeout;
|
|
152
|
+
let lastCall = 0;
|
|
153
|
+
return ((...args) => {
|
|
154
|
+
const now = Date.now();
|
|
155
|
+
const delta = now - lastCall;
|
|
156
|
+
clearTimeout(timeout);
|
|
157
|
+
if (delta >= delay) {
|
|
158
|
+
cb(...args);
|
|
159
|
+
lastCall = now;
|
|
160
|
+
} else timeout = setTimeout(() => {
|
|
161
|
+
cb(...args);
|
|
162
|
+
lastCall = Date.now();
|
|
163
|
+
}, delay - delta);
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
//#endregion
|
|
167
|
+
//#region src/event-emitter.ts
|
|
168
|
+
/**
|
|
169
|
+
* Adds the listener and returns a function to remove it.
|
|
170
|
+
* Promotes removing listeners when cleaning up objects (to prevent leaks).
|
|
171
|
+
*/
|
|
172
|
+
var onEvent = (eventEmitter, eventName, callback) => {
|
|
173
|
+
eventEmitter.on(eventName, callback);
|
|
174
|
+
return () => eventEmitter.off(eventName, callback);
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* @deprecated
|
|
178
|
+
*/
|
|
179
|
+
var addListener = (eventEmitter, eventName, callback) => {
|
|
180
|
+
const off = onEvent(eventEmitter, eventName, callback);
|
|
181
|
+
return { remove: () => off() };
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* Waits for an event with an optional test condition.
|
|
185
|
+
*/
|
|
186
|
+
var waitForEvent = (eventEmitter, eventName, test, timeout, error) => {
|
|
187
|
+
let off;
|
|
188
|
+
const promise = new Promise((resolve) => {
|
|
189
|
+
off = onEvent(eventEmitter, eventName, (...args) => {
|
|
190
|
+
if (!test || test(...args)) resolve(...args);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
return timeout ? asyncTimeout(promise, timeout, error ?? /* @__PURE__ */ new Error()).finally(off) : promise.finally(off);
|
|
194
|
+
};
|
|
195
|
+
//#endregion
|
|
196
|
+
//#region src/mutex.ts
|
|
197
|
+
/**
|
|
198
|
+
* A locking mechanism to ensure that a given section of the code is executed by only one single "thread" at a time.
|
|
199
|
+
*
|
|
200
|
+
* Functions are chained in a structure similar to a linked list.
|
|
201
|
+
* `_lastPromise` always contains the function that will finish executing last.
|
|
202
|
+
*
|
|
203
|
+
* Initially it is set to `Promise.resolve()` -- a promise that resolves immediately.
|
|
204
|
+
* Enqueuing is done by attaching provided function to the `_lastPromise` via a `.then()` call
|
|
205
|
+
* then updating the `_lastPromise` variable.
|
|
206
|
+
*
|
|
207
|
+
* It is important that enqueuing is done atomically:
|
|
208
|
+
* there are no `await`s in `executeSynchronized` and it's not async while still returning a promise.
|
|
209
|
+
*
|
|
210
|
+
* Java docs reference on synchronized sections:
|
|
211
|
+
* https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html
|
|
212
|
+
*/
|
|
213
|
+
var Mutex = class {
|
|
214
|
+
_queue = Promise.resolve();
|
|
215
|
+
_queueLength = 0;
|
|
216
|
+
_tag = null;
|
|
217
|
+
get tag() {
|
|
218
|
+
return this._tag;
|
|
219
|
+
}
|
|
220
|
+
isLocked() {
|
|
221
|
+
return this._queueLength > 0;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Acquires the lock.
|
|
225
|
+
* Caller is responsible for releasing the lock using the returned callback.
|
|
226
|
+
* NOTE: Using `executeSynchronized` is preferred over using `acquire` directly.
|
|
227
|
+
* @returns Release callback
|
|
228
|
+
*/
|
|
229
|
+
async acquire(tag) {
|
|
230
|
+
const prev = this._queue;
|
|
231
|
+
let guard;
|
|
232
|
+
this._queueLength++;
|
|
233
|
+
this._queue = new Promise((resolve) => {
|
|
234
|
+
guard = new MutexGuard(() => {
|
|
235
|
+
this._queueLength--;
|
|
236
|
+
this._tag = null;
|
|
237
|
+
resolve();
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
await prev;
|
|
241
|
+
if (tag !== void 0) this._tag = tag;
|
|
242
|
+
return guard;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Waits for all previous executions to complete and then executes a given function.
|
|
246
|
+
* Only a single function can be executed at a time.
|
|
247
|
+
* Function are executed in the same order as `executeSynchronized` is called.
|
|
248
|
+
* WARNING: Calling `executeSynchronized` inside of `executeSynchronized` on the same lock instance is a deadlock.
|
|
249
|
+
*/
|
|
250
|
+
async executeSynchronized(fun) {
|
|
251
|
+
const guard = await this.acquire();
|
|
252
|
+
try {
|
|
253
|
+
return await fun();
|
|
254
|
+
} finally {
|
|
255
|
+
guard.release();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
var MutexGuard = class {
|
|
260
|
+
_release;
|
|
261
|
+
constructor(_release) {
|
|
262
|
+
this._release = _release;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Releases the lock.
|
|
266
|
+
*/
|
|
267
|
+
release() {
|
|
268
|
+
this._release();
|
|
269
|
+
}
|
|
270
|
+
[Symbol.dispose]() {
|
|
271
|
+
this.release();
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
var classMutexSymbol = Symbol("class-mutex");
|
|
275
|
+
var enableWarning = globalThis.mochaExecutor;
|
|
276
|
+
/**
|
|
277
|
+
* Same as `synchronized` in Java.
|
|
278
|
+
* Uses a lock global to the current class instance.
|
|
279
|
+
* This way every synchronized method on the same instance will share a single lock.
|
|
280
|
+
*/
|
|
281
|
+
var synchronized = (target, propertyName, descriptor) => {
|
|
282
|
+
const method = descriptor.value;
|
|
283
|
+
descriptor.value = async function synchronizedMethod(...args) {
|
|
284
|
+
const mutex = this[classMutexSymbol] ??= new Mutex();
|
|
285
|
+
const tag = `${target.constructor.name}.${propertyName}`;
|
|
286
|
+
let guard;
|
|
287
|
+
if (!enableWarning) guard = await mutex.acquire(tag);
|
|
288
|
+
else guard = await warnAfterTimeout(1e4, `lock on ${tag} (taken by ${mutex.tag})`, () => mutex.acquire(tag));
|
|
289
|
+
try {
|
|
290
|
+
return await method.apply(this, args);
|
|
291
|
+
} finally {
|
|
292
|
+
guard.release();
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
Object.defineProperty(descriptor.value, "name", { value: propertyName + "$synchronized" });
|
|
296
|
+
};
|
|
297
|
+
//#endregion
|
|
298
|
+
//#region src/trigger.ts
|
|
299
|
+
var __dxlog_file$4 = "/__w/dxos/dxos/packages/common/async/src/trigger.ts";
|
|
300
|
+
/**
|
|
301
|
+
* Returns a tuple containing a Promise that will be resolved when the resolver function is called.
|
|
302
|
+
* @deprecated Use `Trigger` instead.
|
|
303
|
+
*/
|
|
304
|
+
var trigger = (timeout) => {
|
|
305
|
+
let callback;
|
|
306
|
+
const promise = new Promise((resolve, reject) => {
|
|
307
|
+
if (timeout) setTimeout(() => reject(/* @__PURE__ */ new Error(`Timed out after ${timeout.toLocaleString()}ms`)), timeout);
|
|
308
|
+
callback = resolve;
|
|
309
|
+
});
|
|
310
|
+
const provider = () => promise;
|
|
311
|
+
const resolver = (value) => callback(value);
|
|
312
|
+
return [provider, resolver];
|
|
313
|
+
};
|
|
314
|
+
var TriggerState = /* @__PURE__ */ function(TriggerState) {
|
|
315
|
+
TriggerState["WAITING"] = "WAITING";
|
|
316
|
+
TriggerState["RESOLVED"] = "RESOLVED";
|
|
317
|
+
TriggerState["REJECTED"] = "REJECTED";
|
|
318
|
+
return TriggerState;
|
|
319
|
+
}({});
|
|
320
|
+
/**
|
|
321
|
+
* Enables blocked listeners to be awakened with optional timeouts.
|
|
322
|
+
*
|
|
323
|
+
* Has two states:
|
|
324
|
+
* - WAITING: promise is in pending state and will be resolved once `wake()` is called.
|
|
325
|
+
* - RESOLVED: promise is already resolved, and all calls to `wait()` resolve immediately.
|
|
326
|
+
* - REJECTED: promise is rejected, and all calls to `wait()` return rejected promise.
|
|
327
|
+
*
|
|
328
|
+
* Trigger starts in WAITING state initially.
|
|
329
|
+
* Use `reset()` to switch resolved trigger back to WAITING state.
|
|
330
|
+
*/
|
|
331
|
+
var Trigger = class {
|
|
332
|
+
_options;
|
|
333
|
+
_promise;
|
|
334
|
+
_resolve;
|
|
335
|
+
_reject;
|
|
336
|
+
_state = "WAITING";
|
|
337
|
+
constructor(_options = { autoReset: false }) {
|
|
338
|
+
this._options = _options;
|
|
339
|
+
this.reset();
|
|
340
|
+
}
|
|
341
|
+
get state() {
|
|
342
|
+
return this._state;
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Wait until wake is called, with optional timeout.
|
|
346
|
+
*/
|
|
347
|
+
async wait({ timeout } = {}) {
|
|
348
|
+
if (timeout) return asyncTimeout(this._promise, timeout, new TimeoutError(timeout));
|
|
349
|
+
else return this._promise;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Wake blocked callers (if any).
|
|
353
|
+
* NOOP if the trigger is already resolved.
|
|
354
|
+
*/
|
|
355
|
+
wake(value) {
|
|
356
|
+
if (this._state !== "WAITING") return this;
|
|
357
|
+
this._state = "RESOLVED";
|
|
358
|
+
this._resolve(value);
|
|
359
|
+
if (this._options.autoReset) return this.reset();
|
|
360
|
+
return this;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Reset promise (new waiters will wait).
|
|
364
|
+
*/
|
|
365
|
+
reset() {
|
|
366
|
+
this._state = "WAITING";
|
|
367
|
+
this._promise = new Promise((resolve, reject) => {
|
|
368
|
+
this._resolve = resolve;
|
|
369
|
+
this._reject = reject;
|
|
370
|
+
});
|
|
371
|
+
this._promise.catch(() => {});
|
|
372
|
+
return this;
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Throw error to blocked callers (if any).
|
|
376
|
+
* NOOP if the trigger is already resolved.
|
|
377
|
+
*/
|
|
378
|
+
throw(error) {
|
|
379
|
+
if (this._state !== "WAITING") return this;
|
|
380
|
+
this._state = "REJECTED";
|
|
381
|
+
this._reject(error);
|
|
382
|
+
if (this._options.autoReset) return this.reset();
|
|
383
|
+
return this;
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
/**
|
|
387
|
+
* Returns a callback and a promise that's resolved when the callback is called n times.
|
|
388
|
+
* @deprecated Use `Trigger` instead.
|
|
389
|
+
*/
|
|
390
|
+
var latch = ({ count = 1, timeout } = {}) => {
|
|
391
|
+
invariant(count >= 0, void 0, {
|
|
392
|
+
"~LogMeta": "~LogMeta",
|
|
393
|
+
F: __dxlog_file$4,
|
|
394
|
+
L: 139,
|
|
395
|
+
S: void 0,
|
|
396
|
+
A: ["count >= 0", ""]
|
|
397
|
+
});
|
|
398
|
+
let t;
|
|
399
|
+
let doResolve;
|
|
400
|
+
let doReject;
|
|
401
|
+
const promise = new Promise((resolve, reject) => {
|
|
402
|
+
doResolve = (value) => {
|
|
403
|
+
clearTimeout(t);
|
|
404
|
+
resolve(value);
|
|
405
|
+
};
|
|
406
|
+
doReject = (err) => {
|
|
407
|
+
clearTimeout(t);
|
|
408
|
+
reject(err);
|
|
409
|
+
};
|
|
410
|
+
});
|
|
411
|
+
if (count === 0) setTimeout(() => {
|
|
412
|
+
doResolve(0);
|
|
413
|
+
});
|
|
414
|
+
else if (timeout) t = setTimeout(() => {
|
|
415
|
+
doReject(/* @__PURE__ */ new Error(`Timed out after ${timeout.toLocaleString()}ms`));
|
|
416
|
+
}, timeout);
|
|
417
|
+
let i = 0;
|
|
418
|
+
return [
|
|
419
|
+
async () => await promise,
|
|
420
|
+
() => {
|
|
421
|
+
if (++i === count) doResolve(i);
|
|
422
|
+
return i;
|
|
423
|
+
},
|
|
424
|
+
(err) => doReject(err)
|
|
425
|
+
];
|
|
426
|
+
};
|
|
427
|
+
//#endregion
|
|
428
|
+
//#region src/observable.ts
|
|
429
|
+
/**
|
|
430
|
+
* Observable which supports multiple subscribers and stores the current value.
|
|
431
|
+
*
|
|
432
|
+
* The current value is emitted to new subscribers on subscription.
|
|
433
|
+
*/
|
|
434
|
+
var MulticastObservable = class MulticastObservable extends Observable {
|
|
435
|
+
_value;
|
|
436
|
+
_observers = /* @__PURE__ */ new Set();
|
|
437
|
+
_observable;
|
|
438
|
+
_completed = new Trigger();
|
|
439
|
+
constructor(subscriber, _value) {
|
|
440
|
+
super((observer) => this._subscribe(observer));
|
|
441
|
+
this._value = _value;
|
|
442
|
+
this._observable = typeof subscriber === "function" ? new Observable(subscriber) : subscriber;
|
|
443
|
+
this._observable.subscribe(this._handlers);
|
|
444
|
+
}
|
|
445
|
+
static from(value, initialValue) {
|
|
446
|
+
if ("emit" in value) return new MulticastObservable((observer) => {
|
|
447
|
+
value.on((data) => {
|
|
448
|
+
observer.next(data);
|
|
449
|
+
});
|
|
450
|
+
}, initialValue);
|
|
451
|
+
const observable = Observable.from(value);
|
|
452
|
+
return new MulticastObservable(observable, initialValue);
|
|
453
|
+
}
|
|
454
|
+
static of(...items) {
|
|
455
|
+
return new MulticastObservable(Observable.of(...items.slice(1)), items[0]);
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* @returns Stable reference to an observable that always returns `undefined`.
|
|
459
|
+
*/
|
|
460
|
+
static empty() {
|
|
461
|
+
return EMPTY_OBSERVABLE;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Get the current value of the observable.
|
|
465
|
+
*/
|
|
466
|
+
get() {
|
|
467
|
+
if (this._value === void 0) throw new Error("MulticastObservable is not initialized.");
|
|
468
|
+
return this._value;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* Wait for the observable to complete.
|
|
472
|
+
*
|
|
473
|
+
* @returns Promise that resolves to the value of the observable at the time of completion.
|
|
474
|
+
*/
|
|
475
|
+
async wait({ timeout } = {}) {
|
|
476
|
+
await this._completed.wait({ timeout });
|
|
477
|
+
return this.get();
|
|
478
|
+
}
|
|
479
|
+
forEach(callback) {
|
|
480
|
+
return this._observable.forEach(callback);
|
|
481
|
+
}
|
|
482
|
+
map(callback) {
|
|
483
|
+
return new MulticastObservable(this._observable.map(callback), this._value && callback(this._value));
|
|
484
|
+
}
|
|
485
|
+
filter(callback) {
|
|
486
|
+
return new MulticastObservable(this._observable.filter(callback), this._value && callback(this._value) ? this._value : void 0);
|
|
487
|
+
}
|
|
488
|
+
reduce(callback, initialValue) {
|
|
489
|
+
return new MulticastObservable(initialValue ? this._observable.reduce(callback, initialValue) : this._observable.reduce(callback), initialValue ?? this._value);
|
|
490
|
+
}
|
|
491
|
+
flatMap(callback) {
|
|
492
|
+
return new MulticastObservable(this._observable.flatMap(callback), this._value && callback(this._value).get());
|
|
493
|
+
}
|
|
494
|
+
concat(...observables) {
|
|
495
|
+
return new MulticastObservable(this._observable.concat(...observables), this._value);
|
|
496
|
+
}
|
|
497
|
+
/**
|
|
498
|
+
* Concatenates multicast observables without losing the current value.
|
|
499
|
+
* @param reducer reduces the values of any multicast observables being concatenated into a single value
|
|
500
|
+
* @param observables observables to concatenate
|
|
501
|
+
* @returns concatenated observable
|
|
502
|
+
*/
|
|
503
|
+
losslessConcat(reducer, ...observables) {
|
|
504
|
+
const multicast = observables.filter((observable) => observable instanceof MulticastObservable);
|
|
505
|
+
const value = reducer(this._value, multicast.map((observable) => observable.get()));
|
|
506
|
+
return new MulticastObservable(this._observable.concat(...observables), value);
|
|
507
|
+
}
|
|
508
|
+
_subscribe(observer) {
|
|
509
|
+
if (!this._observers.has(observer)) this._observers.add(observer);
|
|
510
|
+
if (this._value !== void 0) observer.next?.(this._value);
|
|
511
|
+
return () => {
|
|
512
|
+
this._observers.delete(observer);
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
_handlers = {
|
|
516
|
+
next: (value) => {
|
|
517
|
+
this._value = value;
|
|
518
|
+
this._observers.forEach((observer) => observer.next?.(value));
|
|
519
|
+
},
|
|
520
|
+
error: (err) => {
|
|
521
|
+
this._observers.forEach((observer) => observer.error?.(err));
|
|
522
|
+
},
|
|
523
|
+
complete: () => {
|
|
524
|
+
this._completed.wake();
|
|
525
|
+
this._observers.forEach((observer) => observer.complete?.());
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
};
|
|
529
|
+
var EMPTY_OBSERVABLE = MulticastObservable.of(null);
|
|
530
|
+
//#endregion
|
|
531
|
+
//#region src/observable-value.ts
|
|
532
|
+
/**
|
|
533
|
+
* Provider that manages a set of subscriptions.
|
|
534
|
+
* @deprecated
|
|
535
|
+
*/
|
|
536
|
+
var ObservableProvider = class {
|
|
537
|
+
_handlers = /* @__PURE__ */ new Set();
|
|
538
|
+
_proxy = createSetDispatch({ handlers: this._handlers });
|
|
539
|
+
_value;
|
|
540
|
+
/**
|
|
541
|
+
* Proxy used to dispatch callbacks to each subscription.
|
|
542
|
+
*/
|
|
543
|
+
get callback() {
|
|
544
|
+
return this._proxy;
|
|
545
|
+
}
|
|
546
|
+
get value() {
|
|
547
|
+
return this._value;
|
|
548
|
+
}
|
|
549
|
+
setValue(value) {
|
|
550
|
+
this._value = value;
|
|
551
|
+
}
|
|
552
|
+
subscribe(handler) {
|
|
553
|
+
this._handlers.add(handler);
|
|
554
|
+
return () => {
|
|
555
|
+
this._handlers.delete(handler);
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
/**
|
|
560
|
+
* Implements subscriptions with ability to be cancelled.
|
|
561
|
+
* @deprecated
|
|
562
|
+
*/
|
|
563
|
+
var CancellableObservableProvider = class extends ObservableProvider {
|
|
564
|
+
_handleCancel;
|
|
565
|
+
_cancelled = false;
|
|
566
|
+
constructor(_handleCancel) {
|
|
567
|
+
super();
|
|
568
|
+
this._handleCancel = _handleCancel;
|
|
569
|
+
}
|
|
570
|
+
get cancelled() {
|
|
571
|
+
return this._cancelled;
|
|
572
|
+
}
|
|
573
|
+
async cancel() {
|
|
574
|
+
if (this._cancelled) return;
|
|
575
|
+
this._cancelled = true;
|
|
576
|
+
await this._handleCancel?.();
|
|
577
|
+
this.callback.onCancelled?.();
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
//#endregion
|
|
581
|
+
//#region src/track-leaks.ts
|
|
582
|
+
var __dxlog_file$3 = "/__w/dxos/dxos/packages/common/async/src/track-leaks.ts";
|
|
583
|
+
var enabled = typeof process !== "undefined" && !!process.env.DX_TRACK_LEAKS;
|
|
584
|
+
var openResources = /* @__PURE__ */ new Set();
|
|
585
|
+
var handleSymbol = Symbol("checkLeaksHandle");
|
|
586
|
+
var trackResource = (resourceProvider) => {
|
|
587
|
+
if (!enabled) return () => {};
|
|
588
|
+
const resource = resourceProvider();
|
|
589
|
+
openResources.add(resource);
|
|
590
|
+
return () => {
|
|
591
|
+
openResources.delete(resource);
|
|
592
|
+
};
|
|
593
|
+
};
|
|
594
|
+
/**
|
|
595
|
+
* Makes sure that the resource is at the end of the test.
|
|
596
|
+
*
|
|
597
|
+
* Example:
|
|
598
|
+
*
|
|
599
|
+
* ```typescript
|
|
600
|
+
* @trackLeaks('open', 'close')
|
|
601
|
+
* class Resource {}
|
|
602
|
+
* ```
|
|
603
|
+
*/
|
|
604
|
+
var trackLeaks = (open, close) => (target) => {
|
|
605
|
+
if (!enabled) return;
|
|
606
|
+
const openMethod = target.prototype[open];
|
|
607
|
+
const closeMethod = target.prototype[close];
|
|
608
|
+
if (!openMethod || !closeMethod) throw new Error(`Cannot find ${open} or ${close} method in ${target.name}`);
|
|
609
|
+
target.prototype[open] = async function(...args) {
|
|
610
|
+
this[handleSymbol] = trackResource(() => ({
|
|
611
|
+
name: target.name,
|
|
612
|
+
openStack: new StackTrace()
|
|
613
|
+
}));
|
|
614
|
+
return openMethod.apply(this, args);
|
|
615
|
+
};
|
|
616
|
+
Object.defineProperty(target.prototype[open], "name", { value: open + "$checkLeaks" });
|
|
617
|
+
target.prototype[close] = async function(...args) {
|
|
618
|
+
this[handleSymbol]?.();
|
|
619
|
+
return closeMethod.apply(this, args);
|
|
620
|
+
};
|
|
621
|
+
Object.defineProperty(target.prototype[close], "name", { value: close + "$checkLeaks" });
|
|
622
|
+
};
|
|
623
|
+
var dumpLeaks = () => {
|
|
624
|
+
if (!enabled) return;
|
|
625
|
+
log.info(`Leaked resources ${openResources.size}:`, void 0, {
|
|
626
|
+
"~LogMeta": "~LogMeta",
|
|
627
|
+
F: __dxlog_file$3,
|
|
628
|
+
L: 82,
|
|
629
|
+
S: void 0
|
|
630
|
+
});
|
|
631
|
+
for (const resource of openResources) {
|
|
632
|
+
log.info(`- ${resource.name} at`, void 0, {
|
|
633
|
+
"~LogMeta": "~LogMeta",
|
|
634
|
+
F: __dxlog_file$3,
|
|
635
|
+
L: 84,
|
|
636
|
+
S: void 0
|
|
637
|
+
});
|
|
638
|
+
log.info(resource.openStack.getStack(1), void 0, {
|
|
639
|
+
"~LogMeta": "~LogMeta",
|
|
640
|
+
F: __dxlog_file$3,
|
|
641
|
+
L: 85,
|
|
642
|
+
S: void 0
|
|
643
|
+
});
|
|
644
|
+
log.info("\n", void 0, {
|
|
645
|
+
"~LogMeta": "~LogMeta",
|
|
646
|
+
F: __dxlog_file$3,
|
|
647
|
+
L: 86,
|
|
648
|
+
S: void 0
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
if (enabled) global.dxDumpLeaks = dumpLeaks;
|
|
653
|
+
//#endregion
|
|
654
|
+
//#region src/task-scheduling.ts
|
|
655
|
+
var __dxlog_file$2 = "/__w/dxos/dxos/packages/common/async/src/task-scheduling.ts";
|
|
656
|
+
/**
|
|
657
|
+
* A task that can be scheduled to run in the next event loop iteration.
|
|
658
|
+
* Could be triggered multiple times, but only runs once.
|
|
659
|
+
* If a new task is triggered while a previous one is running, the next run would occur immediately after the current run has finished.
|
|
660
|
+
*/
|
|
661
|
+
var DeferredTask = class {
|
|
662
|
+
_ctx;
|
|
663
|
+
_callback;
|
|
664
|
+
_scheduled = false;
|
|
665
|
+
_currentTask = null;
|
|
666
|
+
_nextTask = new Trigger();
|
|
667
|
+
constructor(_ctx, _callback) {
|
|
668
|
+
this._ctx = _ctx;
|
|
669
|
+
this._callback = _callback;
|
|
670
|
+
}
|
|
671
|
+
get scheduled() {
|
|
672
|
+
return this._scheduled;
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* Schedule the task to run asynchronously.
|
|
676
|
+
*/
|
|
677
|
+
schedule() {
|
|
678
|
+
if (this._scheduled) return;
|
|
679
|
+
scheduleTask(this._ctx, async () => {
|
|
680
|
+
await this._currentTask;
|
|
681
|
+
this._scheduled = false;
|
|
682
|
+
const completionTrigger = this._nextTask;
|
|
683
|
+
this._nextTask = new Trigger();
|
|
684
|
+
this._currentTask = runInContextAsync(this._ctx, () => this._callback()).then(() => {
|
|
685
|
+
completionTrigger.wake();
|
|
686
|
+
});
|
|
687
|
+
});
|
|
688
|
+
this._scheduled = true;
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Schedule the task to run and wait for it to finish.
|
|
692
|
+
*/
|
|
693
|
+
async runBlocking() {
|
|
694
|
+
if (this._ctx.disposed) throw new ContextDisposedError();
|
|
695
|
+
this.schedule();
|
|
696
|
+
await this._nextTask.wait();
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Waits for the current task to finish if it is running.
|
|
700
|
+
* Does not schedule a new task.
|
|
701
|
+
*/
|
|
702
|
+
async join() {
|
|
703
|
+
await this._currentTask;
|
|
704
|
+
}
|
|
705
|
+
};
|
|
706
|
+
var AsyncTask = class {
|
|
707
|
+
#callback;
|
|
708
|
+
#ctx = void 0;
|
|
709
|
+
#scheduled = false;
|
|
710
|
+
#currentTask = null;
|
|
711
|
+
#nextTask = new Trigger();
|
|
712
|
+
constructor(callback) {
|
|
713
|
+
this.#callback = callback;
|
|
714
|
+
}
|
|
715
|
+
get scheduled() {
|
|
716
|
+
return this.#scheduled;
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Context of the resource that owns the task.
|
|
720
|
+
* When the context is disposed, the task is cancelled and cannot be scheduled again.
|
|
721
|
+
*/
|
|
722
|
+
open() {
|
|
723
|
+
this.#ctx = new Context(void 0, {
|
|
724
|
+
"~LogMeta": "~LogMeta",
|
|
725
|
+
F: __dxlog_file$2,
|
|
726
|
+
L: 106
|
|
727
|
+
});
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Closes the task and waits for it to finish if it is running.
|
|
731
|
+
*/
|
|
732
|
+
async close() {
|
|
733
|
+
await this.#ctx?.dispose();
|
|
734
|
+
await this.join();
|
|
735
|
+
this.#ctx = void 0;
|
|
736
|
+
}
|
|
737
|
+
[Symbol.asyncDispose]() {
|
|
738
|
+
return this.close();
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Schedule the task to run asynchronously.
|
|
742
|
+
*/
|
|
743
|
+
schedule() {
|
|
744
|
+
if (!this.#ctx || this.#ctx.disposed) throw new Error("AsyncTask not open");
|
|
745
|
+
if (this.#scheduled) return;
|
|
746
|
+
scheduleTask(this.#ctx, async () => {
|
|
747
|
+
await this.#currentTask;
|
|
748
|
+
if (!this.#ctx || this.#ctx.disposed) return;
|
|
749
|
+
this.#scheduled = false;
|
|
750
|
+
const completionTrigger = this.#nextTask;
|
|
751
|
+
this.#nextTask = new Trigger();
|
|
752
|
+
this.#currentTask = runInContextAsync(this.#ctx, () => this.#callback()).then(() => {
|
|
753
|
+
completionTrigger.wake();
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
this.#scheduled = true;
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Schedule the task to run and wait for it to finish.
|
|
760
|
+
*/
|
|
761
|
+
async runBlocking() {
|
|
762
|
+
if (this.#ctx?.disposed) throw new ContextDisposedError();
|
|
763
|
+
this.schedule();
|
|
764
|
+
await this.#nextTask.wait();
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Waits for the current task to finish if it is running.
|
|
768
|
+
* Does not schedule a new task.
|
|
769
|
+
*/
|
|
770
|
+
async join() {
|
|
771
|
+
await this.#currentTask;
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
var runInContext = (ctx, fn) => {
|
|
775
|
+
try {
|
|
776
|
+
fn();
|
|
777
|
+
} catch (err) {
|
|
778
|
+
ctx.raise(err);
|
|
779
|
+
}
|
|
780
|
+
};
|
|
781
|
+
var runInContextAsync = async (ctx, fn) => {
|
|
782
|
+
try {
|
|
783
|
+
await fn();
|
|
784
|
+
} catch (err) {
|
|
785
|
+
ctx.raise(err);
|
|
786
|
+
}
|
|
787
|
+
};
|
|
788
|
+
var scheduleMicroTask = (ctx, fn) => {
|
|
789
|
+
queueMicrotask(async () => {
|
|
790
|
+
if (ctx.disposed) return;
|
|
791
|
+
await runInContextAsync(ctx, fn);
|
|
792
|
+
});
|
|
793
|
+
};
|
|
794
|
+
var scheduleTask = (ctx, fn, afterMs) => {
|
|
795
|
+
const clearTracking = trackResource(() => ({
|
|
796
|
+
name: `task (${fn.name || "anonymous"})`,
|
|
797
|
+
openStack: new StackTrace()
|
|
798
|
+
}));
|
|
799
|
+
const timeout = setTimeout(async () => {
|
|
800
|
+
clearDispose();
|
|
801
|
+
await runInContextAsync(ctx, fn);
|
|
802
|
+
clearTracking();
|
|
803
|
+
}, afterMs);
|
|
804
|
+
const clearDispose = ctx.onDispose(() => {
|
|
805
|
+
clearTracking();
|
|
806
|
+
clearTimeout(timeout);
|
|
807
|
+
});
|
|
808
|
+
};
|
|
809
|
+
/**
|
|
810
|
+
* Run the task in the next event loop iteration, and then repeat in `interval` ms after the previous iteration completes.
|
|
811
|
+
*/
|
|
812
|
+
var scheduleTaskInterval = (ctx, task, interval) => {
|
|
813
|
+
const clearTracking = trackResource(() => ({
|
|
814
|
+
name: `repeating task (${task.name || "anonymous"})`,
|
|
815
|
+
openStack: new StackTrace()
|
|
816
|
+
}));
|
|
817
|
+
let timeoutId;
|
|
818
|
+
const run = async () => {
|
|
819
|
+
await runInContextAsync(ctx, task);
|
|
820
|
+
if (ctx.disposed) return;
|
|
821
|
+
timeoutId = setTimeout(run, interval);
|
|
822
|
+
};
|
|
823
|
+
timeoutId = setTimeout(run, interval);
|
|
824
|
+
ctx.onDispose(() => {
|
|
825
|
+
clearTracking();
|
|
826
|
+
clearTimeout(timeoutId);
|
|
827
|
+
});
|
|
828
|
+
};
|
|
829
|
+
var scheduleExponentialBackoffTaskInterval = (ctx, task, initialInterval) => {
|
|
830
|
+
const clearTracking = trackResource(() => ({
|
|
831
|
+
name: `repeating task (${task.name || "anonymous"})`,
|
|
832
|
+
openStack: new StackTrace()
|
|
833
|
+
}));
|
|
834
|
+
let timeoutId;
|
|
835
|
+
let interval = initialInterval;
|
|
836
|
+
const repeat = async () => {
|
|
837
|
+
await runInContextAsync(ctx, task);
|
|
838
|
+
if (ctx.disposed) return;
|
|
839
|
+
interval *= 2;
|
|
840
|
+
timeoutId = setTimeout(repeat, interval);
|
|
841
|
+
};
|
|
842
|
+
timeoutId = setTimeout(repeat, interval);
|
|
843
|
+
ctx.onDispose(() => {
|
|
844
|
+
clearTracking();
|
|
845
|
+
clearTimeout(timeoutId);
|
|
846
|
+
});
|
|
847
|
+
};
|
|
848
|
+
//#endregion
|
|
849
|
+
//#region \0@oxc-project+runtime@0.139.0/helpers/esm/decorate.js
|
|
850
|
+
function __decorate(decorators, target, key, desc) {
|
|
851
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
852
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
853
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
854
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
855
|
+
}
|
|
856
|
+
//#endregion
|
|
857
|
+
//#region src/persistent-lifecycle.ts
|
|
858
|
+
var __dxlog_file$1 = "/__w/dxos/dxos/packages/common/async/src/persistent-lifecycle.ts";
|
|
859
|
+
var INIT_RESTART_DELAY = 100;
|
|
860
|
+
var DEFAULT_MAX_RESTART_DELAY = 5e3;
|
|
861
|
+
/**
|
|
862
|
+
* Minimum duration a connection must stay up before it is considered stable and the backoff is
|
|
863
|
+
* reset. A connection that drops sooner keeps escalating the delay, so an endpoint that accepts
|
|
864
|
+
* then immediately closes the connection cannot produce a zero-delay reconnect loop.
|
|
865
|
+
*/
|
|
866
|
+
var STABLE_CONNECTION_THRESHOLD = 5e3;
|
|
867
|
+
/**
|
|
868
|
+
* Handles restarts (e.g. persists connection).
|
|
869
|
+
* Restarts are scheduled with exponential backoff.
|
|
870
|
+
*/
|
|
871
|
+
var PersistentLifecycle = class extends Resource {
|
|
872
|
+
_start;
|
|
873
|
+
_stop;
|
|
874
|
+
_onRestart;
|
|
875
|
+
_maxRestartDelay;
|
|
876
|
+
_currentState = void 0;
|
|
877
|
+
_restartTask = void 0;
|
|
878
|
+
_restartAfter = 0;
|
|
879
|
+
_connectedAt = void 0;
|
|
880
|
+
constructor({ start, stop, onRestart, maxRestartDelay = DEFAULT_MAX_RESTART_DELAY }) {
|
|
881
|
+
super();
|
|
882
|
+
this._start = start;
|
|
883
|
+
this._stop = stop;
|
|
884
|
+
this._onRestart = onRestart;
|
|
885
|
+
this._maxRestartDelay = maxRestartDelay;
|
|
886
|
+
}
|
|
887
|
+
get state() {
|
|
888
|
+
return this._currentState;
|
|
889
|
+
}
|
|
890
|
+
async _open() {
|
|
891
|
+
this._restartTask = new DeferredTask(this._ctx, async () => {
|
|
892
|
+
try {
|
|
893
|
+
await this._restart();
|
|
894
|
+
} catch (err) {
|
|
895
|
+
if (this._ctx?.disposed) return;
|
|
896
|
+
log.warn("Restart failed", { err }, {
|
|
897
|
+
"~LogMeta": "~LogMeta",
|
|
898
|
+
F: __dxlog_file$1,
|
|
899
|
+
L: 84,
|
|
900
|
+
S: this
|
|
901
|
+
});
|
|
902
|
+
this._restartTask?.schedule();
|
|
903
|
+
}
|
|
904
|
+
});
|
|
905
|
+
try {
|
|
906
|
+
this._currentState = await this._start();
|
|
907
|
+
this._connectedAt = Date.now();
|
|
908
|
+
} catch (err) {
|
|
909
|
+
if (this._ctx?.disposed) return;
|
|
910
|
+
log.warn("Start failed", { err }, {
|
|
911
|
+
"~LogMeta": "~LogMeta",
|
|
912
|
+
F: __dxlog_file$1,
|
|
913
|
+
L: 97,
|
|
914
|
+
S: this
|
|
915
|
+
});
|
|
916
|
+
this._restartTask?.schedule();
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
async _close() {
|
|
920
|
+
await this._restartTask?.join();
|
|
921
|
+
await this._stopCurrentState();
|
|
922
|
+
this._restartTask = void 0;
|
|
923
|
+
}
|
|
924
|
+
async _restart() {
|
|
925
|
+
log(`restarting in ${this._restartAfter}ms`, { state: this._lifecycleState }, {
|
|
926
|
+
"~LogMeta": "~LogMeta",
|
|
927
|
+
F: __dxlog_file$1,
|
|
928
|
+
L: 109,
|
|
929
|
+
S: this
|
|
930
|
+
});
|
|
931
|
+
if (this._connectedAt !== void 0 && Date.now() - this._connectedAt >= STABLE_CONNECTION_THRESHOLD) this._restartAfter = 0;
|
|
932
|
+
this._connectedAt = void 0;
|
|
933
|
+
await this._stopCurrentState();
|
|
934
|
+
if (this._lifecycleState !== LifecycleState.OPEN) return;
|
|
935
|
+
await cancelWithContext(this._ctx, sleep(this._restartAfter));
|
|
936
|
+
this._restartAfter = Math.min(Math.max(this._restartAfter * 2, INIT_RESTART_DELAY), this._maxRestartDelay);
|
|
937
|
+
await warnAfterTimeout(5e3, "Connection establishment takes too long", async () => {
|
|
938
|
+
this._currentState = await this._start();
|
|
939
|
+
});
|
|
940
|
+
this._connectedAt = Date.now();
|
|
941
|
+
await this._onRestart?.();
|
|
942
|
+
}
|
|
943
|
+
async _stopCurrentState() {
|
|
944
|
+
if (this._currentState) {
|
|
945
|
+
try {
|
|
946
|
+
await this._stop(this._currentState);
|
|
947
|
+
} catch (err) {
|
|
948
|
+
log.catch(err, void 0, {
|
|
949
|
+
"~LogMeta": "~LogMeta",
|
|
950
|
+
F: __dxlog_file$1,
|
|
951
|
+
L: 140,
|
|
952
|
+
S: this
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
this._currentState = void 0;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Scheduling restart should be done from outside.
|
|
960
|
+
*/
|
|
961
|
+
async scheduleRestart() {
|
|
962
|
+
if (this._lifecycleState !== LifecycleState.OPEN) return;
|
|
963
|
+
this._restartTask.schedule();
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
__decorate([synchronized], PersistentLifecycle.prototype, "_open", null);
|
|
967
|
+
__decorate([synchronized], PersistentLifecycle.prototype, "scheduleRestart", null);
|
|
968
|
+
//#endregion
|
|
969
|
+
//#region src/push-iterable.ts
|
|
970
|
+
var __dxlog_file = "/__w/dxos/dxos/packages/common/async/src/push-iterable.ts";
|
|
971
|
+
/**
|
|
972
|
+
* Creates an async iterable where the data is pushed by calling methods on the iterable.
|
|
973
|
+
*/
|
|
974
|
+
var makePushIterable = () => {
|
|
975
|
+
const buf = [];
|
|
976
|
+
const trigger = new Trigger({ autoReset: true });
|
|
977
|
+
return {
|
|
978
|
+
[Symbol.asyncIterator]() {
|
|
979
|
+
return { next: async () => {
|
|
980
|
+
while (buf.length === 0) await trigger.wait();
|
|
981
|
+
const item = buf.shift();
|
|
982
|
+
invariant(item, void 0, {
|
|
983
|
+
"~LogMeta": "~LogMeta",
|
|
984
|
+
F: __dxlog_file,
|
|
985
|
+
L: 42,
|
|
986
|
+
S: this,
|
|
987
|
+
A: ["item", ""]
|
|
988
|
+
});
|
|
989
|
+
switch (item.kind) {
|
|
990
|
+
case "next": return {
|
|
991
|
+
value: item.value,
|
|
992
|
+
done: false
|
|
993
|
+
};
|
|
994
|
+
case "return": return {
|
|
995
|
+
value: item.value,
|
|
996
|
+
done: true
|
|
997
|
+
};
|
|
998
|
+
case "throw": throw item.value;
|
|
999
|
+
}
|
|
1000
|
+
} };
|
|
1001
|
+
},
|
|
1002
|
+
next: (value) => {
|
|
1003
|
+
buf.push({
|
|
1004
|
+
kind: "next",
|
|
1005
|
+
value
|
|
1006
|
+
});
|
|
1007
|
+
trigger.wake();
|
|
1008
|
+
},
|
|
1009
|
+
return: (value) => {
|
|
1010
|
+
buf.push({
|
|
1011
|
+
kind: "return",
|
|
1012
|
+
value
|
|
1013
|
+
});
|
|
1014
|
+
trigger.wake();
|
|
1015
|
+
},
|
|
1016
|
+
throw: (value) => {
|
|
1017
|
+
buf.push({
|
|
1018
|
+
kind: "throw",
|
|
1019
|
+
value
|
|
1020
|
+
});
|
|
1021
|
+
trigger.wake();
|
|
1022
|
+
}
|
|
1023
|
+
};
|
|
1024
|
+
};
|
|
1025
|
+
//#endregion
|
|
1026
|
+
//#region src/stream-to-array.ts
|
|
1027
|
+
/**
|
|
1028
|
+
* Concatenate a readable stream's data into a single array.
|
|
1029
|
+
*
|
|
1030
|
+
* From https://github.com/stream-utils/stream-to-array, but does not depend on
|
|
1031
|
+
* any-promise which does not work in web workers due to using window in its
|
|
1032
|
+
* browser shim.
|
|
1033
|
+
*/
|
|
1034
|
+
var streamToArray = (stream) => {
|
|
1035
|
+
let deferred;
|
|
1036
|
+
if (!stream.readable) deferred = Promise.resolve([]);
|
|
1037
|
+
else deferred = new Promise((resolve, reject) => {
|
|
1038
|
+
if (!stream.readable) return resolve([]);
|
|
1039
|
+
let arr = [];
|
|
1040
|
+
const onData = (doc) => {
|
|
1041
|
+
arr?.push(doc);
|
|
1042
|
+
};
|
|
1043
|
+
const onEnd = (err) => {
|
|
1044
|
+
if (err) reject(err);
|
|
1045
|
+
else resolve(arr);
|
|
1046
|
+
cleanup();
|
|
1047
|
+
};
|
|
1048
|
+
const onClose = () => {
|
|
1049
|
+
resolve(arr);
|
|
1050
|
+
cleanup();
|
|
1051
|
+
};
|
|
1052
|
+
const cleanup = () => {
|
|
1053
|
+
arr = [];
|
|
1054
|
+
stream.removeListener("data", onData);
|
|
1055
|
+
stream.removeListener("end", onEnd);
|
|
1056
|
+
stream.removeListener("error", onEnd);
|
|
1057
|
+
stream.removeListener("close", onClose);
|
|
1058
|
+
};
|
|
1059
|
+
stream.on("data", onData);
|
|
1060
|
+
stream.on("end", onEnd);
|
|
1061
|
+
stream.on("error", onEnd);
|
|
1062
|
+
stream.on("close", onClose);
|
|
1063
|
+
});
|
|
1064
|
+
return deferred;
|
|
1065
|
+
};
|
|
1066
|
+
//#endregion
|
|
1067
|
+
//#region src/testing.ts
|
|
1068
|
+
/**
|
|
1069
|
+
* NOTE: THIS SHOULD ONLY BE USED IN TESTS.
|
|
1070
|
+
*
|
|
1071
|
+
* Returns a Promise which resolves when `condFn` returns truthy.
|
|
1072
|
+
* The value returned by `condFn` is used to resolve the Promise.
|
|
1073
|
+
* @param condition Function to call.
|
|
1074
|
+
* @param [timeout] How long to wait, in milliseconds (0 = no timeout).
|
|
1075
|
+
* @param [interval=10] How frequently to check, in milliseconds.
|
|
1076
|
+
*/
|
|
1077
|
+
var waitForCondition = ({ condition, timeout = 0, interval = 10, error, breakOnError = false }) => {
|
|
1078
|
+
const stopTime = timeout ? Date.now() + timeout : 0;
|
|
1079
|
+
const trigger = new Trigger();
|
|
1080
|
+
const waiter = async () => {
|
|
1081
|
+
while (!stopTime || Date.now() < stopTime) {
|
|
1082
|
+
try {
|
|
1083
|
+
const value = await condition();
|
|
1084
|
+
if (value) {
|
|
1085
|
+
trigger.wake(value);
|
|
1086
|
+
break;
|
|
1087
|
+
}
|
|
1088
|
+
} catch (err) {
|
|
1089
|
+
if (breakOnError === true) trigger.throw(err);
|
|
1090
|
+
}
|
|
1091
|
+
await sleep(interval);
|
|
1092
|
+
}
|
|
1093
|
+
};
|
|
1094
|
+
setTimeout(waiter, 0);
|
|
1095
|
+
return trigger.wait({ timeout });
|
|
1096
|
+
};
|
|
1097
|
+
/**
|
|
1098
|
+
* Awaits promise.
|
|
1099
|
+
*/
|
|
1100
|
+
var until = (cb, timeout) => {
|
|
1101
|
+
return new Promise((resolve, reject) => {
|
|
1102
|
+
const t = timeout && setTimeout(() => {
|
|
1103
|
+
reject(/* @__PURE__ */ new Error(`Timeout after ${t}ms`));
|
|
1104
|
+
}, timeout);
|
|
1105
|
+
setTimeout(async () => {
|
|
1106
|
+
try {
|
|
1107
|
+
await cb((value) => {
|
|
1108
|
+
t && clearTimeout(t);
|
|
1109
|
+
resolve(value);
|
|
1110
|
+
}, (error) => {
|
|
1111
|
+
t && clearTimeout(t);
|
|
1112
|
+
reject(error);
|
|
1113
|
+
});
|
|
1114
|
+
} catch (err) {
|
|
1115
|
+
reject(err);
|
|
1116
|
+
}
|
|
1117
|
+
});
|
|
1118
|
+
});
|
|
1119
|
+
};
|
|
1120
|
+
/**
|
|
1121
|
+
* Wait until promise resolves.
|
|
1122
|
+
*/
|
|
1123
|
+
var untilPromise = (cb) => cb();
|
|
1124
|
+
/**
|
|
1125
|
+
* Wait until error is thrown.
|
|
1126
|
+
*/
|
|
1127
|
+
var untilError = (cb) => {
|
|
1128
|
+
return new Promise((resolve, reject) => {
|
|
1129
|
+
setTimeout(async () => {
|
|
1130
|
+
try {
|
|
1131
|
+
await cb();
|
|
1132
|
+
reject(/* @__PURE__ */ new Error("No error was thrown."));
|
|
1133
|
+
} catch (err) {
|
|
1134
|
+
resolve(err);
|
|
1135
|
+
}
|
|
1136
|
+
});
|
|
1137
|
+
});
|
|
1138
|
+
};
|
|
1139
|
+
//#endregion
|
|
1140
|
+
//#region src/timer.ts
|
|
1141
|
+
/**
|
|
1142
|
+
* Manages callback invocations at a interval with a possible jitter.
|
|
1143
|
+
* Note: The interval excludes the running time of the callback.
|
|
1144
|
+
*/
|
|
1145
|
+
var Timer = class {
|
|
1146
|
+
_callback;
|
|
1147
|
+
_state = new Event();
|
|
1148
|
+
_timer;
|
|
1149
|
+
_count = 0;
|
|
1150
|
+
constructor(_callback) {
|
|
1151
|
+
this._callback = _callback;
|
|
1152
|
+
}
|
|
1153
|
+
get state() {
|
|
1154
|
+
return this._state;
|
|
1155
|
+
}
|
|
1156
|
+
get running() {
|
|
1157
|
+
return !!this._timer;
|
|
1158
|
+
}
|
|
1159
|
+
start(options, cb) {
|
|
1160
|
+
if (isNaN(options.count) || isNaN(options.interval)) throw new Error(`Invalid options: ${JSON.stringify(options)}`);
|
|
1161
|
+
if (this.running) this.stop();
|
|
1162
|
+
const stop = () => {
|
|
1163
|
+
this.stop();
|
|
1164
|
+
cb?.();
|
|
1165
|
+
};
|
|
1166
|
+
const run = () => {
|
|
1167
|
+
if (this._count >= (options.count ?? 0)) stop();
|
|
1168
|
+
else {
|
|
1169
|
+
const interval = (options.interval ?? 0) + Math.random() * (options.jitter ?? 0);
|
|
1170
|
+
this._timer = setTimeout(async () => {
|
|
1171
|
+
await this._callback(this._count++);
|
|
1172
|
+
run();
|
|
1173
|
+
}, interval);
|
|
1174
|
+
}
|
|
1175
|
+
};
|
|
1176
|
+
this._state.emit(true);
|
|
1177
|
+
this._count = 0;
|
|
1178
|
+
setTimeout(run);
|
|
1179
|
+
return this;
|
|
1180
|
+
}
|
|
1181
|
+
stop() {
|
|
1182
|
+
clearInterval(this._timer);
|
|
1183
|
+
this._timer = void 0;
|
|
1184
|
+
this._state.emit(false);
|
|
1185
|
+
return this;
|
|
1186
|
+
}
|
|
1187
|
+
};
|
|
1188
|
+
//#endregion
|
|
1189
|
+
//#region src/update-scheduler.ts
|
|
1190
|
+
/**
|
|
1191
|
+
* Time period for update counting.
|
|
1192
|
+
*/
|
|
1193
|
+
var TIME_PERIOD = 1e3;
|
|
1194
|
+
/**
|
|
1195
|
+
* Runs a non-reentrant callback at most once at a time, coalescing triggers (optionally rate-limited
|
|
1196
|
+
* via {@link UpdateSchedulerOptions.maxFrequency}).
|
|
1197
|
+
*
|
|
1198
|
+
* The runner inside {@link trigger} is the only place the callback starts; `runBlocking` and
|
|
1199
|
+
* `forceTrigger` funnel into it. That, plus `_scheduled` collapsing triggers into one pending
|
|
1200
|
+
* runner, is what makes mutual exclusion hold by construction (dxos/edge#758).
|
|
1201
|
+
*/
|
|
1202
|
+
var UpdateScheduler = class {
|
|
1203
|
+
_ctx;
|
|
1204
|
+
_callback;
|
|
1205
|
+
_params;
|
|
1206
|
+
/** The running pass; never rejects, non-null exactly while a run is executing. */
|
|
1207
|
+
_currentTask = null;
|
|
1208
|
+
_scheduled = false;
|
|
1209
|
+
/**
|
|
1210
|
+
* Armed by the `trigger` that schedules a runner, woken by that runner when it claims — with the
|
|
1211
|
+
* run's promise, which `Trigger` adopts, so waiters resume when the run *finishes*. Lets
|
|
1212
|
+
* `runBlocking` await a run that does not exist in `_currentTask` yet.
|
|
1213
|
+
*
|
|
1214
|
+
* `reset()` abandons parked waiters, so it may only be armed in `trigger`'s non-coalesced branch:
|
|
1215
|
+
* waiters park only while `_scheduled` is true, and that branch requires it false.
|
|
1216
|
+
*/
|
|
1217
|
+
_runOutcome = new Trigger();
|
|
1218
|
+
/** Woken to make the pending runner skip its throttle delay. Replaced at claim time. */
|
|
1219
|
+
_skipDelay = new Trigger();
|
|
1220
|
+
/**
|
|
1221
|
+
* `runBlocking` callers in flight, incremented synchronously before triggering. A failing run uses
|
|
1222
|
+
* it to tell whether someone will throw the error; counting microtask hops to a waiter's resume
|
|
1223
|
+
* would be a race.
|
|
1224
|
+
*/
|
|
1225
|
+
_observers = 0;
|
|
1226
|
+
_lastUpdateTime = -1e3;
|
|
1227
|
+
constructor(_ctx, _callback, _params = {}) {
|
|
1228
|
+
this._ctx = _ctx;
|
|
1229
|
+
this._callback = _callback;
|
|
1230
|
+
this._params = _params;
|
|
1231
|
+
_ctx.onDispose(async () => {
|
|
1232
|
+
await this._currentTask;
|
|
1233
|
+
this._runOutcome.wake(Promise.resolve(void 0));
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1236
|
+
get scheduled() {
|
|
1237
|
+
return this._scheduled;
|
|
1238
|
+
}
|
|
1239
|
+
/**
|
|
1240
|
+
* Schedule the callback to run asynchronously. Triggers issued while a run is pending or in flight
|
|
1241
|
+
* coalesce into the next run.
|
|
1242
|
+
*/
|
|
1243
|
+
trigger() {
|
|
1244
|
+
if (this._scheduled) return;
|
|
1245
|
+
this._scheduled = true;
|
|
1246
|
+
this._runOutcome.reset();
|
|
1247
|
+
scheduleMicroTask(this._ctx, async () => {
|
|
1248
|
+
await this._currentTask;
|
|
1249
|
+
if (this._params.maxFrequency) {
|
|
1250
|
+
const now = performance.now();
|
|
1251
|
+
const delay = this._lastUpdateTime + TIME_PERIOD / this._params.maxFrequency - now;
|
|
1252
|
+
if (delay > 0) {
|
|
1253
|
+
const skipDelay = this._skipDelay;
|
|
1254
|
+
await new Promise((resolve) => {
|
|
1255
|
+
const timeoutId = setTimeout(() => {
|
|
1256
|
+
clearContext();
|
|
1257
|
+
resolve();
|
|
1258
|
+
}, delay);
|
|
1259
|
+
const clearContext = this._ctx.onDispose(() => {
|
|
1260
|
+
clearTimeout(timeoutId);
|
|
1261
|
+
resolve();
|
|
1262
|
+
});
|
|
1263
|
+
skipDelay.wait().then(() => {
|
|
1264
|
+
clearTimeout(timeoutId);
|
|
1265
|
+
clearContext();
|
|
1266
|
+
resolve();
|
|
1267
|
+
});
|
|
1268
|
+
});
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
if (this._ctx.disposed) {
|
|
1272
|
+
this._runOutcome.wake(Promise.resolve(void 0));
|
|
1273
|
+
return;
|
|
1274
|
+
}
|
|
1275
|
+
this._scheduled = false;
|
|
1276
|
+
this._skipDelay = new Trigger();
|
|
1277
|
+
this._lastUpdateTime = performance.now();
|
|
1278
|
+
const run = this._callback().then(() => void 0, (error) => {
|
|
1279
|
+
const failure = error;
|
|
1280
|
+
if (this._observers === 0) this._ctx.raise(failure);
|
|
1281
|
+
return failure;
|
|
1282
|
+
});
|
|
1283
|
+
const task = run.then(() => {
|
|
1284
|
+
if (this._currentTask === task) this._currentTask = null;
|
|
1285
|
+
});
|
|
1286
|
+
this._currentTask = task;
|
|
1287
|
+
this._runOutcome.wake(run);
|
|
1288
|
+
});
|
|
1289
|
+
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Run as soon as possible: schedules (coalescing with any pending run) and skips the throttle
|
|
1292
|
+
* delay. Does not start a concurrent callback.
|
|
1293
|
+
*/
|
|
1294
|
+
forceTrigger() {
|
|
1295
|
+
this.trigger();
|
|
1296
|
+
this._skipDelay.wake();
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Waits for the current task to finish if it is running.
|
|
1300
|
+
* Does not schedule a new task, and does not wait for one that is merely scheduled.
|
|
1301
|
+
*/
|
|
1302
|
+
async join() {
|
|
1303
|
+
await this._currentTask;
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Ensure a run that observes everything enqueued so far, and wait for it to finish. Waits for the
|
|
1307
|
+
* pending run to claim, then for its result; that run necessarily claims after this call, so its
|
|
1308
|
+
* drain includes everything enqueued before it. Rejects with the run's error so a flush barrier can
|
|
1309
|
+
* see its batch was not handed off. No-op once disposed.
|
|
1310
|
+
*/
|
|
1311
|
+
async runBlocking() {
|
|
1312
|
+
if (this._ctx.disposed) return;
|
|
1313
|
+
this._observers++;
|
|
1314
|
+
try {
|
|
1315
|
+
this.trigger();
|
|
1316
|
+
this._skipDelay.wake();
|
|
1317
|
+
const error = await this._runOutcome.wait();
|
|
1318
|
+
if (error !== void 0) throw error;
|
|
1319
|
+
} finally {
|
|
1320
|
+
this._observers--;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
};
|
|
1324
|
+
//#endregion
|
|
1325
|
+
export { AsyncTask, CancellableObservableProvider, DeferredTask, Event, MulticastObservable, Mutex, MutexGuard, Observable, ObservableProvider, PersistentLifecycle, PushStream, SubscriptionList, SubscriptionSet, TimeoutError, Timer, Trigger, TriggerState, UpdateScheduler, addEventListener, addListener, asyncReturn, asyncTimeout, chain, combine, debounce, debounceAndThrottle, delay, dumpLeaks, interval, latch, makePushIterable, observableError, onEvent, promiseFromCallback, runInContext, runInContextAsync, scheduleExponentialBackoffTaskInterval, scheduleMicroTask, scheduleTask, scheduleTaskInterval, sleep, sleepWithContext, streamToArray, synchronized, throttle, timeout, toError, trackLeaks, trackResource, trigger, unrefTimeout, until, untilError, untilPromise, waitForCondition, waitForEvent };
|
|
1326
|
+
|
|
1327
|
+
//# sourceMappingURL=index.mjs.map
|