@dxos/context 0.4.10-main.fa5a270 → 0.4.10-main.fd4f2a3
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/browser/index.mjs +134 -4
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node/index.cjs +134 -3
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/meta.json +1 -1
- package/dist/types/src/{context-disposed.d.ts → context-disposed-error.d.ts} +1 -1
- package/dist/types/src/context-disposed-error.d.ts.map +1 -0
- package/dist/types/src/context.d.ts +7 -1
- package/dist/types/src/context.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +2 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/promise-utils.d.ts +1 -1
- package/dist/types/src/promise-utils.d.ts.map +1 -1
- package/dist/types/src/resource.d.ts +48 -0
- package/dist/types/src/resource.d.ts.map +1 -0
- package/package.json +4 -3
- package/src/context.ts +16 -2
- package/src/index.ts +2 -1
- package/src/promise-utils.ts +1 -1
- package/src/resource.ts +142 -0
- package/dist/types/src/context-disposed.d.ts.map +0 -1
- /package/src/{context-disposed.ts → context-disposed-error.ts} +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// packages/common/context/src/context.ts
|
|
2
|
+
import { inspect } from "@dxos/node-std/util";
|
|
2
3
|
import { log } from "@dxos/log";
|
|
3
4
|
import { safeInstanceof } from "@dxos/util";
|
|
4
5
|
|
|
5
|
-
// packages/common/context/src/context-disposed.ts
|
|
6
|
+
// packages/common/context/src/context-disposed-error.ts
|
|
6
7
|
var ContextDisposedError = class extends Error {
|
|
7
8
|
constructor() {
|
|
8
9
|
super("Context disposed.");
|
|
@@ -22,6 +23,7 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
22
23
|
}
|
|
23
24
|
var __dxlog_file = "/home/runner/work/dxos/dxos/packages/common/context/src/context.ts";
|
|
24
25
|
var MAX_SAFE_DISPOSE_CALLBACKS = 300;
|
|
26
|
+
var _a, _b;
|
|
25
27
|
var Context = class _Context {
|
|
26
28
|
constructor({ onError = (error) => {
|
|
27
29
|
if (error instanceof ContextDisposedError) {
|
|
@@ -35,12 +37,20 @@ var Context = class _Context {
|
|
|
35
37
|
this._disposePromise = void 0;
|
|
36
38
|
this._parent = null;
|
|
37
39
|
this.maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
|
|
40
|
+
this[_a] = "Context";
|
|
41
|
+
this[_b] = () => this.toString();
|
|
38
42
|
this._onError = onError;
|
|
39
43
|
this._attributes = attributes;
|
|
40
44
|
if (parent !== void 0) {
|
|
41
45
|
this._parent = parent;
|
|
42
46
|
}
|
|
43
47
|
}
|
|
48
|
+
static {
|
|
49
|
+
_a = Symbol.toStringTag, _b = inspect.custom;
|
|
50
|
+
}
|
|
51
|
+
static default() {
|
|
52
|
+
return new _Context();
|
|
53
|
+
}
|
|
44
54
|
get disposed() {
|
|
45
55
|
return this._isDisposed;
|
|
46
56
|
}
|
|
@@ -64,7 +74,7 @@ var Context = class _Context {
|
|
|
64
74
|
} catch (error) {
|
|
65
75
|
log.catch(error, void 0, {
|
|
66
76
|
F: __dxlog_file,
|
|
67
|
-
L:
|
|
77
|
+
L: 88,
|
|
68
78
|
S: this,
|
|
69
79
|
C: (f, a) => f(...a)
|
|
70
80
|
});
|
|
@@ -78,7 +88,7 @@ var Context = class _Context {
|
|
|
78
88
|
safeThreshold: this.maxSafeDisposeCallbacks
|
|
79
89
|
}, {
|
|
80
90
|
F: __dxlog_file,
|
|
81
|
-
L:
|
|
91
|
+
L: 95,
|
|
82
92
|
S: this,
|
|
83
93
|
C: (f, a) => f(...a)
|
|
84
94
|
});
|
|
@@ -116,7 +126,7 @@ var Context = class _Context {
|
|
|
116
126
|
} catch (error) {
|
|
117
127
|
log.catch(error, void 0, {
|
|
118
128
|
F: __dxlog_file,
|
|
119
|
-
L:
|
|
129
|
+
L: 138,
|
|
120
130
|
S: this,
|
|
121
131
|
C: (f, a) => f(...a)
|
|
122
132
|
});
|
|
@@ -173,6 +183,9 @@ var Context = class _Context {
|
|
|
173
183
|
}
|
|
174
184
|
return void 0;
|
|
175
185
|
}
|
|
186
|
+
toString() {
|
|
187
|
+
return `Context(${this._isDisposed ? "disposed" : "active"})`;
|
|
188
|
+
}
|
|
176
189
|
};
|
|
177
190
|
Context = _ts_decorate([
|
|
178
191
|
safeInstanceof("Context")
|
|
@@ -191,10 +204,127 @@ var cancelWithContext = (ctx, promise) => {
|
|
|
191
204
|
})
|
|
192
205
|
]).finally(() => clearDispose?.());
|
|
193
206
|
};
|
|
207
|
+
|
|
208
|
+
// packages/common/context/src/resource.ts
|
|
209
|
+
import "@dxos/util";
|
|
210
|
+
var LifecycleState;
|
|
211
|
+
(function(LifecycleState2) {
|
|
212
|
+
LifecycleState2["CLOSED"] = "CLOSED";
|
|
213
|
+
LifecycleState2["OPEN"] = "OPEN";
|
|
214
|
+
LifecycleState2["ERROR"] = "ERROR";
|
|
215
|
+
})(LifecycleState || (LifecycleState = {}));
|
|
216
|
+
var Resource = class {
|
|
217
|
+
#lifecycleState = "CLOSED";
|
|
218
|
+
#openPromise = null;
|
|
219
|
+
#closePromise = null;
|
|
220
|
+
/**
|
|
221
|
+
* Managed internally by the resource.
|
|
222
|
+
* Recreated on close.
|
|
223
|
+
* Errors are propagated to the `_catch` method and the parent context.
|
|
224
|
+
*/
|
|
225
|
+
#internalCtx = this.#createContext();
|
|
226
|
+
/**
|
|
227
|
+
* Context that is used to bubble up errors that are not handled by the resource.
|
|
228
|
+
* Provided in the open method.
|
|
229
|
+
*/
|
|
230
|
+
#parentCtx = new Context();
|
|
231
|
+
get _lifecycleState() {
|
|
232
|
+
return this.#lifecycleState;
|
|
233
|
+
}
|
|
234
|
+
get _ctx() {
|
|
235
|
+
return this.#internalCtx;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* To be overridden by subclasses.
|
|
239
|
+
*/
|
|
240
|
+
async _open(ctx) {
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* To be overridden by subclasses.
|
|
244
|
+
*/
|
|
245
|
+
async _close(ctx) {
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Error handler for errors that are caught by the context.
|
|
249
|
+
* By default errors are bubbled up to the parent context which is passed to the open method.
|
|
250
|
+
*/
|
|
251
|
+
async _catch(err) {
|
|
252
|
+
throw err;
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Opens the resource.
|
|
256
|
+
* If the resource is already open, it does nothing.
|
|
257
|
+
* If the resource is in an error state, it throws an error.
|
|
258
|
+
* If the resource is closed, it waits for it to close and then opens it.
|
|
259
|
+
* @param ctx - Context to use for opening the resource. This context will receive errors that are not handled in `_catch`.
|
|
260
|
+
*/
|
|
261
|
+
async open(ctx) {
|
|
262
|
+
switch (this.#lifecycleState) {
|
|
263
|
+
case "OPEN":
|
|
264
|
+
return this;
|
|
265
|
+
case "ERROR":
|
|
266
|
+
throw new Error(`Invalid state: ${this.#lifecycleState}`);
|
|
267
|
+
default:
|
|
268
|
+
}
|
|
269
|
+
await this.#closePromise;
|
|
270
|
+
await (this.#openPromise ??= this.#open(ctx));
|
|
271
|
+
return this;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Closes the resource.
|
|
275
|
+
* If the resource is already closed, it does nothing.
|
|
276
|
+
*/
|
|
277
|
+
async close(ctx) {
|
|
278
|
+
if (this.#lifecycleState === "CLOSED") {
|
|
279
|
+
return this;
|
|
280
|
+
}
|
|
281
|
+
await this.#openPromise;
|
|
282
|
+
await (this.#closePromise ??= this.#close(ctx));
|
|
283
|
+
return this;
|
|
284
|
+
}
|
|
285
|
+
async [Symbol.asyncDispose]() {
|
|
286
|
+
await this.close();
|
|
287
|
+
}
|
|
288
|
+
async #open(ctx) {
|
|
289
|
+
this.#closePromise = null;
|
|
290
|
+
if (ctx) {
|
|
291
|
+
this.#parentCtx = ctx;
|
|
292
|
+
}
|
|
293
|
+
await this._open(this.#parentCtx);
|
|
294
|
+
this.#lifecycleState = "OPEN";
|
|
295
|
+
}
|
|
296
|
+
async #close(ctx = new Context()) {
|
|
297
|
+
this.#openPromise = null;
|
|
298
|
+
await this.#internalCtx.dispose();
|
|
299
|
+
await this._close(ctx);
|
|
300
|
+
this.#internalCtx = this.#createContext();
|
|
301
|
+
this.#lifecycleState = "CLOSED";
|
|
302
|
+
}
|
|
303
|
+
#createContext() {
|
|
304
|
+
return new Context({
|
|
305
|
+
onError: (error) => queueMicrotask(async () => {
|
|
306
|
+
try {
|
|
307
|
+
await this._catch(error);
|
|
308
|
+
} catch (err) {
|
|
309
|
+
this.#lifecycleState = "ERROR";
|
|
310
|
+
this.#parentCtx.raise(err);
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
};
|
|
316
|
+
var openInContext = async (ctx, resource) => {
|
|
317
|
+
await resource.open?.(ctx);
|
|
318
|
+
ctx.onDispose(() => resource.close?.());
|
|
319
|
+
return resource;
|
|
320
|
+
};
|
|
194
321
|
export {
|
|
195
322
|
Context,
|
|
196
323
|
ContextDisposedError,
|
|
324
|
+
LifecycleState,
|
|
325
|
+
Resource,
|
|
197
326
|
cancelWithContext,
|
|
327
|
+
openInContext,
|
|
198
328
|
rejectOnDispose
|
|
199
329
|
};
|
|
200
330
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../../src/context.ts", "../../../src/context-disposed.ts", "../../../src/promise-utils.ts"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2022 DXOS.org\n//\n\nimport { log } from '@dxos/log';\nimport { safeInstanceof } from '@dxos/util';\n\nimport { ContextDisposedError } from './context-disposed';\n\nexport type ContextErrorHandler = (error: Error) => void;\n\nexport type DisposeCallback = () => void | Promise<void>;\n\nexport type CreateContextParams = {\n onError?: ContextErrorHandler;\n attributes?: Record<string, any>;\n parent?: Context;\n};\n\n/**\n * Maximum number of dispose callbacks before we start logging warnings.\n */\nconst MAX_SAFE_DISPOSE_CALLBACKS = 300;\n\n@safeInstanceof('Context')\nexport class Context {\n private readonly _onError: ContextErrorHandler;\n private readonly _disposeCallbacks: DisposeCallback[] = [];\n private _isDisposed = false;\n private _disposePromise?: Promise<void> = undefined;\n private _parent: Context | null = null;\n\n private _attributes: Record<string, any>;\n\n public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;\n\n constructor({\n onError = (error) => {\n if (error instanceof ContextDisposedError) {\n return;\n }\n\n void this.dispose();\n\n // Will generate an unhandled rejection.\n throw error;\n },\n attributes = {},\n parent,\n }: CreateContextParams = {}) {\n this._onError = onError;\n this._attributes = attributes;\n if (parent !== undefined) {\n this._parent = parent;\n }\n }\n\n get disposed() {\n return this._isDisposed;\n }\n\n get disposeCallbacksLength() {\n return this._disposeCallbacks.length;\n }\n\n /**\n * Schedules a callback to run when the context is disposed.\n * May be async, in this case the disposer might choose to wait for all resource to released.\n * Throwing an error inside the callback will result in the error being logged, but not re-thrown.\n *\n * NOTE: Will call the callback immediately if the context is already disposed.\n *\n * @returns A function that can be used to remove the callback from the dispose list.\n */\n onDispose(callback: DisposeCallback) {\n if (this._isDisposed) {\n // Call the callback immediately if the context is already disposed.\n void (async () => {\n try {\n await callback();\n } catch (error: any) {\n log.catch(error);\n }\n })();\n }\n\n this._disposeCallbacks.push(callback);\n if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {\n log.warn('Context has a large number of dispose callbacks. This might be a memory leak.', {\n count: this._disposeCallbacks.length,\n safeThreshold: this.maxSafeDisposeCallbacks,\n });\n }\n\n return () => {\n const index = this._disposeCallbacks.indexOf(callback);\n if (index !== -1) {\n this._disposeCallbacks.splice(index, 1);\n }\n };\n }\n\n /**\n * Runs all dispose callbacks.\n * Sync callbacks are run in the reverse order they were added.\n * Async callbacks are run in parallel.\n * This function never throws.\n * It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.\n * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.\n */\n dispose(): Promise<void> {\n if (this._disposePromise) {\n return this._disposePromise;\n }\n this._isDisposed = true;\n\n // Set the promise before running the callbacks.\n let resolveDispose: () => void;\n this._disposePromise = new Promise<void>((resolve) => {\n resolveDispose = resolve;\n });\n\n const promises = [];\n // Clone the array so that any mutations to the original array don't affect the dispose process.\n const callbacks = Array.from(this._disposeCallbacks).reverse();\n for (const callback of callbacks) {\n promises.push(\n (async () => {\n try {\n await callback();\n } catch (error: any) {\n log.catch(error);\n }\n })(),\n );\n }\n this._disposeCallbacks.length = 0;\n\n void Promise.all(promises).then(() => {\n resolveDispose();\n });\n\n return this._disposePromise;\n }\n\n /**\n * Raise the error inside the context.\n * The error will be propagated to the error handler.\n * IF the error handler is not set, the error will dispose the context and cause an unhandled rejection.\n */\n raise(error: Error): void {\n if (this._isDisposed) {\n // TODO(dmaretskyi): Don't log those.\n // log.warn('Error in disposed context', error);\n return;\n }\n\n try {\n this._onError(error);\n } catch (err) {\n // Generate an unhandled rejection and stop the error propagation.\n void Promise.reject(err);\n }\n }\n\n derive({ onError, attributes }: CreateContextParams = {}): Context {\n const newCtx = new Context({\n // TODO(dmaretskyi): Optimize to not require allocating a new closure for every context.\n onError: async (error) => {\n if (!onError) {\n this.raise(error);\n } else {\n try {\n await onError(error);\n } catch {\n this.raise(error);\n }\n }\n },\n attributes,\n });\n const clearDispose = this.onDispose(() => newCtx.dispose());\n newCtx.onDispose(clearDispose);\n return newCtx;\n }\n\n getAttribute(key: string): any {\n if (key in this._attributes) {\n return this._attributes[key];\n }\n if (this._parent !== null) {\n return this._parent.getAttribute(key);\n }\n return undefined;\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nexport class ContextDisposedError extends Error {\n constructor() {\n super('Context disposed.');\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { type Context } from './context';\nimport { ContextDisposedError } from './context-disposed';\n\n/**\n * @returns A promise that rejects when the context is disposed.\n */\n// TODO(dmaretskyi): Memory leak.\nexport const rejectOnDispose = (ctx: Context, error = new ContextDisposedError()): Promise<never> =>\n new Promise((resolve, reject) => {\n ctx.onDispose(() => reject(error));\n });\n\n/**\n * Rejects the promise if the context is disposed.\n */\nexport const cancelWithContext = <T>(ctx: Context, promise: Promise<T>): Promise<T> => {\n let clearDispose: () => void;\n return Promise.race([\n promise,\n new Promise<never>((resolve, reject) => {\n // Will be called before .finally() handlers.\n clearDispose = ctx.onDispose(() => reject(new ContextDisposedError()));\n }),\n ]).finally(() => clearDispose?.());\n};\n"],
|
|
5
|
-
"mappings": ";AAIA,SAASA,WAAW;AACpB,SAASC,sBAAsB;;;
|
|
6
|
-
"names": ["log", "safeInstanceof", "ContextDisposedError", "Error", "constructor", "MAX_SAFE_DISPOSE_CALLBACKS", "Context", "constructor", "onError", "error", "ContextDisposedError", "dispose", "attributes", "parent", "_disposeCallbacks", "_isDisposed", "_disposePromise", "undefined", "_parent", "maxSafeDisposeCallbacks", "_onError", "_attributes", "disposed", "disposeCallbacksLength", "length", "onDispose", "callback", "log", "catch", "push", "warn", "count", "safeThreshold", "index", "indexOf", "splice", "resolveDispose", "Promise", "resolve", "promises", "callbacks", "Array", "from", "reverse", "all", "then", "raise", "err", "reject", "derive", "newCtx", "clearDispose", "getAttribute", "key", "safeInstanceof", "rejectOnDispose", "ctx", "error", "ContextDisposedError", "Promise", "resolve", "reject", "onDispose", "cancelWithContext", "promise", "clearDispose", "race", "finally"]
|
|
3
|
+
"sources": ["../../../src/context.ts", "../../../src/context-disposed-error.ts", "../../../src/promise-utils.ts", "../../../src/resource.ts"],
|
|
4
|
+
"sourcesContent": ["//\n// Copyright 2022 DXOS.org\n//\n\nimport { inspect } from 'node:util';\n\nimport { log } from '@dxos/log';\nimport { safeInstanceof } from '@dxos/util';\n\nimport { ContextDisposedError } from './context-disposed-error';\n\nexport type ContextErrorHandler = (error: Error) => void;\n\nexport type DisposeCallback = () => any | Promise<any>;\n\nexport type CreateContextParams = {\n onError?: ContextErrorHandler;\n attributes?: Record<string, any>;\n parent?: Context;\n};\n\n/**\n * Maximum number of dispose callbacks before we start logging warnings.\n */\nconst MAX_SAFE_DISPOSE_CALLBACKS = 300;\n\n@safeInstanceof('Context')\nexport class Context {\n static default() {\n return new Context();\n }\n\n private readonly _onError: ContextErrorHandler;\n private readonly _disposeCallbacks: DisposeCallback[] = [];\n private _isDisposed = false;\n private _disposePromise?: Promise<void> = undefined;\n private _parent: Context | null = null;\n\n private _attributes: Record<string, any>;\n\n public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;\n\n constructor({\n onError = (error) => {\n if (error instanceof ContextDisposedError) {\n return;\n }\n\n void this.dispose();\n\n // Will generate an unhandled rejection.\n throw error;\n },\n attributes = {},\n parent,\n }: CreateContextParams = {}) {\n this._onError = onError;\n this._attributes = attributes;\n if (parent !== undefined) {\n this._parent = parent;\n }\n }\n\n get disposed() {\n return this._isDisposed;\n }\n\n get disposeCallbacksLength() {\n return this._disposeCallbacks.length;\n }\n\n /**\n * Schedules a callback to run when the context is disposed.\n * May be async, in this case the disposer might choose to wait for all resource to released.\n * Throwing an error inside the callback will result in the error being logged, but not re-thrown.\n *\n * NOTE: Will call the callback immediately if the context is already disposed.\n *\n * @returns A function that can be used to remove the callback from the dispose list.\n */\n onDispose(callback: DisposeCallback) {\n if (this._isDisposed) {\n // Call the callback immediately if the context is already disposed.\n void (async () => {\n try {\n await callback();\n } catch (error: any) {\n log.catch(error);\n }\n })();\n }\n\n this._disposeCallbacks.push(callback);\n if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {\n log.warn('Context has a large number of dispose callbacks. This might be a memory leak.', {\n count: this._disposeCallbacks.length,\n safeThreshold: this.maxSafeDisposeCallbacks,\n });\n }\n\n return () => {\n const index = this._disposeCallbacks.indexOf(callback);\n if (index !== -1) {\n this._disposeCallbacks.splice(index, 1);\n }\n };\n }\n\n /**\n * Runs all dispose callbacks.\n * Sync callbacks are run in the reverse order they were added.\n * Async callbacks are run in parallel.\n * This function never throws.\n * It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.\n * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.\n */\n dispose(): Promise<void> {\n if (this._disposePromise) {\n return this._disposePromise;\n }\n this._isDisposed = true;\n\n // Set the promise before running the callbacks.\n let resolveDispose: () => void;\n this._disposePromise = new Promise<void>((resolve) => {\n resolveDispose = resolve;\n });\n\n const promises = [];\n // Clone the array so that any mutations to the original array don't affect the dispose process.\n const callbacks = Array.from(this._disposeCallbacks).reverse();\n for (const callback of callbacks) {\n promises.push(\n (async () => {\n try {\n await callback();\n } catch (error: any) {\n log.catch(error);\n }\n })(),\n );\n }\n this._disposeCallbacks.length = 0;\n\n void Promise.all(promises).then(() => {\n resolveDispose();\n });\n\n return this._disposePromise;\n }\n\n /**\n * Raise the error inside the context.\n * The error will be propagated to the error handler.\n * IF the error handler is not set, the error will dispose the context and cause an unhandled rejection.\n */\n raise(error: Error): void {\n if (this._isDisposed) {\n // TODO(dmaretskyi): Don't log those.\n // log.warn('Error in disposed context', error);\n return;\n }\n\n try {\n this._onError(error);\n } catch (err) {\n // Generate an unhandled rejection and stop the error propagation.\n void Promise.reject(err);\n }\n }\n\n derive({ onError, attributes }: CreateContextParams = {}): Context {\n const newCtx = new Context({\n // TODO(dmaretskyi): Optimize to not require allocating a new closure for every context.\n onError: async (error) => {\n if (!onError) {\n this.raise(error);\n } else {\n try {\n await onError(error);\n } catch {\n this.raise(error);\n }\n }\n },\n attributes,\n });\n const clearDispose = this.onDispose(() => newCtx.dispose());\n newCtx.onDispose(clearDispose);\n return newCtx;\n }\n\n getAttribute(key: string): any {\n if (key in this._attributes) {\n return this._attributes[key];\n }\n if (this._parent !== null) {\n return this._parent.getAttribute(key);\n }\n return undefined;\n }\n\n [Symbol.toStringTag] = 'Context';\n\n [inspect.custom] = () => this.toString();\n\n toString() {\n return `Context(${this._isDisposed ? 'disposed' : 'active'})`;\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nexport class ContextDisposedError extends Error {\n constructor() {\n super('Context disposed.');\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { type Context } from './context';\nimport { ContextDisposedError } from './context-disposed-error';\n\n/**\n * @returns A promise that rejects when the context is disposed.\n */\n// TODO(dmaretskyi): Memory leak.\nexport const rejectOnDispose = (ctx: Context, error = new ContextDisposedError()): Promise<never> =>\n new Promise((resolve, reject) => {\n ctx.onDispose(() => reject(error));\n });\n\n/**\n * Rejects the promise if the context is disposed.\n */\nexport const cancelWithContext = <T>(ctx: Context, promise: Promise<T>): Promise<T> => {\n let clearDispose: () => void;\n return Promise.race([\n promise,\n new Promise<never>((resolve, reject) => {\n // Will be called before .finally() handlers.\n clearDispose = ctx.onDispose(() => reject(new ContextDisposedError()));\n }),\n ]).finally(() => clearDispose?.());\n};\n", "//\n// Copyright 2024 DXOS.org\n//\n\nimport '@dxos/util';\nimport { Context } from './context';\n\nexport interface Lifecycle {\n open?(ctx?: Context): Promise<any> | any;\n close?(): Promise<any> | any;\n}\n\nexport enum LifecycleState {\n CLOSED = 'CLOSED',\n OPEN = 'OPEN',\n ERROR = 'ERROR',\n}\n\n/**\n * Base class for resources that need to be opened and closed.\n */\nexport class Resource implements Lifecycle {\n #lifecycleState = LifecycleState.CLOSED;\n #openPromise: Promise<void> | null = null;\n #closePromise: Promise<void> | null = null;\n\n /**\n * Managed internally by the resource.\n * Recreated on close.\n * Errors are propagated to the `_catch` method and the parent context.\n */\n #internalCtx: Context = this.#createContext();\n\n /**\n * Context that is used to bubble up errors that are not handled by the resource.\n * Provided in the open method.\n */\n #parentCtx: Context = new Context();\n\n protected get _lifecycleState() {\n return this.#lifecycleState;\n }\n\n protected get _ctx() {\n return this.#internalCtx;\n }\n\n /**\n * To be overridden by subclasses.\n */\n protected async _open(ctx: Context): Promise<void> {}\n\n /**\n * To be overridden by subclasses.\n */\n protected async _close(ctx: Context): Promise<void> {}\n\n /**\n * Error handler for errors that are caught by the context.\n * By default errors are bubbled up to the parent context which is passed to the open method.\n */\n protected async _catch(err: Error): Promise<void> {\n throw err;\n }\n\n /**\n * Opens the resource.\n * If the resource is already open, it does nothing.\n * If the resource is in an error state, it throws an error.\n * If the resource is closed, it waits for it to close and then opens it.\n * @param ctx - Context to use for opening the resource. This context will receive errors that are not handled in `_catch`.\n */\n async open(ctx?: Context): Promise<this> {\n switch (this.#lifecycleState) {\n case LifecycleState.OPEN:\n return this;\n case LifecycleState.ERROR:\n throw new Error(`Invalid state: ${this.#lifecycleState}`);\n default:\n }\n await this.#closePromise;\n await (this.#openPromise ??= this.#open(ctx));\n\n return this;\n }\n\n /**\n * Closes the resource.\n * If the resource is already closed, it does nothing.\n */\n async close(ctx?: Context): Promise<this> {\n if (this.#lifecycleState === LifecycleState.CLOSED) {\n return this;\n }\n await this.#openPromise;\n await (this.#closePromise ??= this.#close(ctx));\n\n return this;\n }\n\n async [Symbol.asyncDispose]() {\n await this.close();\n }\n\n async #open(ctx?: Context) {\n this.#closePromise = null;\n if (ctx) {\n this.#parentCtx = ctx;\n }\n await this._open(this.#parentCtx);\n this.#lifecycleState = LifecycleState.OPEN;\n }\n\n async #close(ctx = new Context()) {\n this.#openPromise = null;\n await this.#internalCtx.dispose();\n await this._close(ctx);\n this.#internalCtx = this.#createContext();\n this.#lifecycleState = LifecycleState.CLOSED;\n }\n\n #createContext() {\n return new Context({\n onError: (error) =>\n queueMicrotask(async () => {\n try {\n await this._catch(error);\n } catch (err: any) {\n this.#lifecycleState = LifecycleState.ERROR;\n this.#parentCtx.raise(err);\n }\n }),\n });\n }\n}\n\nexport const openInContext = async <T extends Lifecycle>(ctx: Context, resource: T): Promise<T> => {\n await resource.open?.(ctx);\n ctx.onDispose(() => resource.close?.());\n\n return resource;\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["inspect", "log", "safeInstanceof", "ContextDisposedError", "Error", "constructor", "MAX_SAFE_DISPOSE_CALLBACKS", "Context", "constructor", "onError", "error", "ContextDisposedError", "dispose", "attributes", "parent", "_disposeCallbacks", "_isDisposed", "_disposePromise", "undefined", "_parent", "maxSafeDisposeCallbacks", "Symbol", "inspect", "toString", "_onError", "_attributes", "toStringTag", "custom", "default", "disposed", "disposeCallbacksLength", "length", "onDispose", "callback", "log", "catch", "push", "warn", "count", "safeThreshold", "index", "indexOf", "splice", "resolveDispose", "Promise", "resolve", "promises", "callbacks", "Array", "from", "reverse", "all", "then", "raise", "err", "reject", "derive", "newCtx", "clearDispose", "getAttribute", "key", "safeInstanceof", "rejectOnDispose", "ctx", "error", "ContextDisposedError", "Promise", "resolve", "reject", "onDispose", "cancelWithContext", "promise", "clearDispose", "race", "finally", "LifecycleState", "Resource", "Context", "_lifecycleState", "_ctx", "_open", "ctx", "_close", "_catch", "err", "open", "Error", "close", "Symbol", "asyncDispose", "dispose", "onError", "error", "queueMicrotask", "raise", "openInContext", "resource", "onDispose"]
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"inputs":{"packages/common/context/src/context-disposed.ts":{"bytes":
|
|
1
|
+
{"inputs":{"packages/common/context/src/context-disposed-error.ts":{"bytes":817,"imports":[],"format":"esm"},"packages/common/context/src/context.ts":{"bytes":19782,"imports":[{"path":"@dxos/node-std/util","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"packages/common/context/src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"}],"format":"esm"},"packages/common/context/src/promise-utils.ts":{"bytes":3040,"imports":[{"path":"packages/common/context/src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"}],"format":"esm"},"packages/common/context/src/resource.ts":{"bytes":11723,"imports":[{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"packages/common/context/src/context.ts","kind":"import-statement","original":"./context"}],"format":"esm"},"packages/common/context/src/index.ts":{"bytes":777,"imports":[{"path":"packages/common/context/src/context.ts","kind":"import-statement","original":"./context"},{"path":"packages/common/context/src/promise-utils.ts","kind":"import-statement","original":"./promise-utils"},{"path":"packages/common/context/src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"},{"path":"packages/common/context/src/resource.ts","kind":"import-statement","original":"./resource"}],"format":"esm"}},"outputs":{"packages/common/context/dist/lib/browser/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":16404},"packages/common/context/dist/lib/browser/index.mjs":{"imports":[{"path":"@dxos/node-std/util","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"exports":["Context","ContextDisposedError","LifecycleState","Resource","cancelWithContext","openInContext","rejectOnDispose"],"entryPoint":"packages/common/context/src/index.ts","inputs":{"packages/common/context/src/context.ts":{"bytesInOutput":5543},"packages/common/context/src/context-disposed-error.ts":{"bytesInOutput":106},"packages/common/context/src/index.ts":{"bytesInOutput":0},"packages/common/context/src/promise-utils.ts":{"bytesInOutput":410},"packages/common/context/src/resource.ts":{"bytesInOutput":2978}},"bytes":9441}}}
|
package/dist/lib/node/index.cjs
CHANGED
|
@@ -20,12 +20,17 @@ var node_exports = {};
|
|
|
20
20
|
__export(node_exports, {
|
|
21
21
|
Context: () => Context,
|
|
22
22
|
ContextDisposedError: () => ContextDisposedError,
|
|
23
|
+
LifecycleState: () => LifecycleState,
|
|
24
|
+
Resource: () => Resource,
|
|
23
25
|
cancelWithContext: () => cancelWithContext,
|
|
26
|
+
openInContext: () => openInContext,
|
|
24
27
|
rejectOnDispose: () => rejectOnDispose
|
|
25
28
|
});
|
|
26
29
|
module.exports = __toCommonJS(node_exports);
|
|
30
|
+
var import_node_util = require("node:util");
|
|
27
31
|
var import_log = require("@dxos/log");
|
|
28
32
|
var import_util = require("@dxos/util");
|
|
33
|
+
var import_util2 = require("@dxos/util");
|
|
29
34
|
var ContextDisposedError = class extends Error {
|
|
30
35
|
constructor() {
|
|
31
36
|
super("Context disposed.");
|
|
@@ -43,6 +48,7 @@ function _ts_decorate(decorators, target, key, desc) {
|
|
|
43
48
|
}
|
|
44
49
|
var __dxlog_file = "/home/runner/work/dxos/dxos/packages/common/context/src/context.ts";
|
|
45
50
|
var MAX_SAFE_DISPOSE_CALLBACKS = 300;
|
|
51
|
+
var _a, _b;
|
|
46
52
|
var Context = class _Context {
|
|
47
53
|
constructor({ onError = (error) => {
|
|
48
54
|
if (error instanceof ContextDisposedError) {
|
|
@@ -56,12 +62,20 @@ var Context = class _Context {
|
|
|
56
62
|
this._disposePromise = void 0;
|
|
57
63
|
this._parent = null;
|
|
58
64
|
this.maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
|
|
65
|
+
this[_a] = "Context";
|
|
66
|
+
this[_b] = () => this.toString();
|
|
59
67
|
this._onError = onError;
|
|
60
68
|
this._attributes = attributes;
|
|
61
69
|
if (parent !== void 0) {
|
|
62
70
|
this._parent = parent;
|
|
63
71
|
}
|
|
64
72
|
}
|
|
73
|
+
static {
|
|
74
|
+
_a = Symbol.toStringTag, _b = import_node_util.inspect.custom;
|
|
75
|
+
}
|
|
76
|
+
static default() {
|
|
77
|
+
return new _Context();
|
|
78
|
+
}
|
|
65
79
|
get disposed() {
|
|
66
80
|
return this._isDisposed;
|
|
67
81
|
}
|
|
@@ -85,7 +99,7 @@ var Context = class _Context {
|
|
|
85
99
|
} catch (error) {
|
|
86
100
|
import_log.log.catch(error, void 0, {
|
|
87
101
|
F: __dxlog_file,
|
|
88
|
-
L:
|
|
102
|
+
L: 88,
|
|
89
103
|
S: this,
|
|
90
104
|
C: (f, a) => f(...a)
|
|
91
105
|
});
|
|
@@ -99,7 +113,7 @@ var Context = class _Context {
|
|
|
99
113
|
safeThreshold: this.maxSafeDisposeCallbacks
|
|
100
114
|
}, {
|
|
101
115
|
F: __dxlog_file,
|
|
102
|
-
L:
|
|
116
|
+
L: 95,
|
|
103
117
|
S: this,
|
|
104
118
|
C: (f, a) => f(...a)
|
|
105
119
|
});
|
|
@@ -137,7 +151,7 @@ var Context = class _Context {
|
|
|
137
151
|
} catch (error) {
|
|
138
152
|
import_log.log.catch(error, void 0, {
|
|
139
153
|
F: __dxlog_file,
|
|
140
|
-
L:
|
|
154
|
+
L: 138,
|
|
141
155
|
S: this,
|
|
142
156
|
C: (f, a) => f(...a)
|
|
143
157
|
});
|
|
@@ -194,6 +208,9 @@ var Context = class _Context {
|
|
|
194
208
|
}
|
|
195
209
|
return void 0;
|
|
196
210
|
}
|
|
211
|
+
toString() {
|
|
212
|
+
return `Context(${this._isDisposed ? "disposed" : "active"})`;
|
|
213
|
+
}
|
|
197
214
|
};
|
|
198
215
|
Context = _ts_decorate([
|
|
199
216
|
(0, import_util.safeInstanceof)("Context")
|
|
@@ -210,11 +227,125 @@ var cancelWithContext = (ctx, promise) => {
|
|
|
210
227
|
})
|
|
211
228
|
]).finally(() => clearDispose?.());
|
|
212
229
|
};
|
|
230
|
+
var LifecycleState;
|
|
231
|
+
(function(LifecycleState2) {
|
|
232
|
+
LifecycleState2["CLOSED"] = "CLOSED";
|
|
233
|
+
LifecycleState2["OPEN"] = "OPEN";
|
|
234
|
+
LifecycleState2["ERROR"] = "ERROR";
|
|
235
|
+
})(LifecycleState || (LifecycleState = {}));
|
|
236
|
+
var Resource = class {
|
|
237
|
+
#lifecycleState = "CLOSED";
|
|
238
|
+
#openPromise = null;
|
|
239
|
+
#closePromise = null;
|
|
240
|
+
/**
|
|
241
|
+
* Managed internally by the resource.
|
|
242
|
+
* Recreated on close.
|
|
243
|
+
* Errors are propagated to the `_catch` method and the parent context.
|
|
244
|
+
*/
|
|
245
|
+
#internalCtx = this.#createContext();
|
|
246
|
+
/**
|
|
247
|
+
* Context that is used to bubble up errors that are not handled by the resource.
|
|
248
|
+
* Provided in the open method.
|
|
249
|
+
*/
|
|
250
|
+
#parentCtx = new Context();
|
|
251
|
+
get _lifecycleState() {
|
|
252
|
+
return this.#lifecycleState;
|
|
253
|
+
}
|
|
254
|
+
get _ctx() {
|
|
255
|
+
return this.#internalCtx;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* To be overridden by subclasses.
|
|
259
|
+
*/
|
|
260
|
+
async _open(ctx) {
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* To be overridden by subclasses.
|
|
264
|
+
*/
|
|
265
|
+
async _close(ctx) {
|
|
266
|
+
}
|
|
267
|
+
/**
|
|
268
|
+
* Error handler for errors that are caught by the context.
|
|
269
|
+
* By default errors are bubbled up to the parent context which is passed to the open method.
|
|
270
|
+
*/
|
|
271
|
+
async _catch(err) {
|
|
272
|
+
throw err;
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Opens the resource.
|
|
276
|
+
* If the resource is already open, it does nothing.
|
|
277
|
+
* If the resource is in an error state, it throws an error.
|
|
278
|
+
* If the resource is closed, it waits for it to close and then opens it.
|
|
279
|
+
* @param ctx - Context to use for opening the resource. This context will receive errors that are not handled in `_catch`.
|
|
280
|
+
*/
|
|
281
|
+
async open(ctx) {
|
|
282
|
+
switch (this.#lifecycleState) {
|
|
283
|
+
case "OPEN":
|
|
284
|
+
return this;
|
|
285
|
+
case "ERROR":
|
|
286
|
+
throw new Error(`Invalid state: ${this.#lifecycleState}`);
|
|
287
|
+
default:
|
|
288
|
+
}
|
|
289
|
+
await this.#closePromise;
|
|
290
|
+
await (this.#openPromise ??= this.#open(ctx));
|
|
291
|
+
return this;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Closes the resource.
|
|
295
|
+
* If the resource is already closed, it does nothing.
|
|
296
|
+
*/
|
|
297
|
+
async close(ctx) {
|
|
298
|
+
if (this.#lifecycleState === "CLOSED") {
|
|
299
|
+
return this;
|
|
300
|
+
}
|
|
301
|
+
await this.#openPromise;
|
|
302
|
+
await (this.#closePromise ??= this.#close(ctx));
|
|
303
|
+
return this;
|
|
304
|
+
}
|
|
305
|
+
async [Symbol.asyncDispose]() {
|
|
306
|
+
await this.close();
|
|
307
|
+
}
|
|
308
|
+
async #open(ctx) {
|
|
309
|
+
this.#closePromise = null;
|
|
310
|
+
if (ctx) {
|
|
311
|
+
this.#parentCtx = ctx;
|
|
312
|
+
}
|
|
313
|
+
await this._open(this.#parentCtx);
|
|
314
|
+
this.#lifecycleState = "OPEN";
|
|
315
|
+
}
|
|
316
|
+
async #close(ctx = new Context()) {
|
|
317
|
+
this.#openPromise = null;
|
|
318
|
+
await this.#internalCtx.dispose();
|
|
319
|
+
await this._close(ctx);
|
|
320
|
+
this.#internalCtx = this.#createContext();
|
|
321
|
+
this.#lifecycleState = "CLOSED";
|
|
322
|
+
}
|
|
323
|
+
#createContext() {
|
|
324
|
+
return new Context({
|
|
325
|
+
onError: (error) => queueMicrotask(async () => {
|
|
326
|
+
try {
|
|
327
|
+
await this._catch(error);
|
|
328
|
+
} catch (err) {
|
|
329
|
+
this.#lifecycleState = "ERROR";
|
|
330
|
+
this.#parentCtx.raise(err);
|
|
331
|
+
}
|
|
332
|
+
})
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
var openInContext = async (ctx, resource) => {
|
|
337
|
+
await resource.open?.(ctx);
|
|
338
|
+
ctx.onDispose(() => resource.close?.());
|
|
339
|
+
return resource;
|
|
340
|
+
};
|
|
213
341
|
// Annotate the CommonJS export names for ESM import in node:
|
|
214
342
|
0 && (module.exports = {
|
|
215
343
|
Context,
|
|
216
344
|
ContextDisposedError,
|
|
345
|
+
LifecycleState,
|
|
346
|
+
Resource,
|
|
217
347
|
cancelWithContext,
|
|
348
|
+
openInContext,
|
|
218
349
|
rejectOnDispose
|
|
219
350
|
});
|
|
220
351
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../../../src/context.ts", "../../../src/context-disposed.ts", "../../../src/promise-utils.ts"],
|
|
4
|
-
"sourcesContent": ["//\n// Copyright 2022 DXOS.org\n//\n\nimport { log } from '@dxos/log';\nimport { safeInstanceof } from '@dxos/util';\n\nimport { ContextDisposedError } from './context-disposed';\n\nexport type ContextErrorHandler = (error: Error) => void;\n\nexport type DisposeCallback = () => void | Promise<void>;\n\nexport type CreateContextParams = {\n onError?: ContextErrorHandler;\n attributes?: Record<string, any>;\n parent?: Context;\n};\n\n/**\n * Maximum number of dispose callbacks before we start logging warnings.\n */\nconst MAX_SAFE_DISPOSE_CALLBACKS = 300;\n\n@safeInstanceof('Context')\nexport class Context {\n private readonly _onError: ContextErrorHandler;\n private readonly _disposeCallbacks: DisposeCallback[] = [];\n private _isDisposed = false;\n private _disposePromise?: Promise<void> = undefined;\n private _parent: Context | null = null;\n\n private _attributes: Record<string, any>;\n\n public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;\n\n constructor({\n onError = (error) => {\n if (error instanceof ContextDisposedError) {\n return;\n }\n\n void this.dispose();\n\n // Will generate an unhandled rejection.\n throw error;\n },\n attributes = {},\n parent,\n }: CreateContextParams = {}) {\n this._onError = onError;\n this._attributes = attributes;\n if (parent !== undefined) {\n this._parent = parent;\n }\n }\n\n get disposed() {\n return this._isDisposed;\n }\n\n get disposeCallbacksLength() {\n return this._disposeCallbacks.length;\n }\n\n /**\n * Schedules a callback to run when the context is disposed.\n * May be async, in this case the disposer might choose to wait for all resource to released.\n * Throwing an error inside the callback will result in the error being logged, but not re-thrown.\n *\n * NOTE: Will call the callback immediately if the context is already disposed.\n *\n * @returns A function that can be used to remove the callback from the dispose list.\n */\n onDispose(callback: DisposeCallback) {\n if (this._isDisposed) {\n // Call the callback immediately if the context is already disposed.\n void (async () => {\n try {\n await callback();\n } catch (error: any) {\n log.catch(error);\n }\n })();\n }\n\n this._disposeCallbacks.push(callback);\n if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {\n log.warn('Context has a large number of dispose callbacks. This might be a memory leak.', {\n count: this._disposeCallbacks.length,\n safeThreshold: this.maxSafeDisposeCallbacks,\n });\n }\n\n return () => {\n const index = this._disposeCallbacks.indexOf(callback);\n if (index !== -1) {\n this._disposeCallbacks.splice(index, 1);\n }\n };\n }\n\n /**\n * Runs all dispose callbacks.\n * Sync callbacks are run in the reverse order they were added.\n * Async callbacks are run in parallel.\n * This function never throws.\n * It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.\n * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.\n */\n dispose(): Promise<void> {\n if (this._disposePromise) {\n return this._disposePromise;\n }\n this._isDisposed = true;\n\n // Set the promise before running the callbacks.\n let resolveDispose: () => void;\n this._disposePromise = new Promise<void>((resolve) => {\n resolveDispose = resolve;\n });\n\n const promises = [];\n // Clone the array so that any mutations to the original array don't affect the dispose process.\n const callbacks = Array.from(this._disposeCallbacks).reverse();\n for (const callback of callbacks) {\n promises.push(\n (async () => {\n try {\n await callback();\n } catch (error: any) {\n log.catch(error);\n }\n })(),\n );\n }\n this._disposeCallbacks.length = 0;\n\n void Promise.all(promises).then(() => {\n resolveDispose();\n });\n\n return this._disposePromise;\n }\n\n /**\n * Raise the error inside the context.\n * The error will be propagated to the error handler.\n * IF the error handler is not set, the error will dispose the context and cause an unhandled rejection.\n */\n raise(error: Error): void {\n if (this._isDisposed) {\n // TODO(dmaretskyi): Don't log those.\n // log.warn('Error in disposed context', error);\n return;\n }\n\n try {\n this._onError(error);\n } catch (err) {\n // Generate an unhandled rejection and stop the error propagation.\n void Promise.reject(err);\n }\n }\n\n derive({ onError, attributes }: CreateContextParams = {}): Context {\n const newCtx = new Context({\n // TODO(dmaretskyi): Optimize to not require allocating a new closure for every context.\n onError: async (error) => {\n if (!onError) {\n this.raise(error);\n } else {\n try {\n await onError(error);\n } catch {\n this.raise(error);\n }\n }\n },\n attributes,\n });\n const clearDispose = this.onDispose(() => newCtx.dispose());\n newCtx.onDispose(clearDispose);\n return newCtx;\n }\n\n getAttribute(key: string): any {\n if (key in this._attributes) {\n return this._attributes[key];\n }\n if (this._parent !== null) {\n return this._parent.getAttribute(key);\n }\n return undefined;\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nexport class ContextDisposedError extends Error {\n constructor() {\n super('Context disposed.');\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { type Context } from './context';\nimport { ContextDisposedError } from './context-disposed';\n\n/**\n * @returns A promise that rejects when the context is disposed.\n */\n// TODO(dmaretskyi): Memory leak.\nexport const rejectOnDispose = (ctx: Context, error = new ContextDisposedError()): Promise<never> =>\n new Promise((resolve, reject) => {\n ctx.onDispose(() => reject(error));\n });\n\n/**\n * Rejects the promise if the context is disposed.\n */\nexport const cancelWithContext = <T>(ctx: Context, promise: Promise<T>): Promise<T> => {\n let clearDispose: () => void;\n return Promise.race([\n promise,\n new Promise<never>((resolve, reject) => {\n // Will be called before .finally() handlers.\n clearDispose = ctx.onDispose(() => reject(new ContextDisposedError()));\n }),\n ]).finally(() => clearDispose?.());\n};\n"],
|
|
5
|
-
"mappings": "
|
|
6
|
-
"names": ["ContextDisposedError", "Error", "constructor", "MAX_SAFE_DISPOSE_CALLBACKS", "Context", "onError", "error", "dispose", "attributes", "parent", "_disposeCallbacks", "_isDisposed", "_disposePromise", "undefined", "_parent", "maxSafeDisposeCallbacks", "_onError", "_attributes", "disposed", "disposeCallbacksLength", "length", "onDispose", "callback", "log", "catch", "push", "warn", "count", "safeThreshold", "index", "indexOf", "splice", "resolveDispose", "Promise", "resolve", "promises", "callbacks", "Array", "from", "reverse", "all", "then", "raise", "err", "reject", "derive", "newCtx", "clearDispose", "getAttribute", "key", "safeInstanceof", "rejectOnDispose", "ctx", "cancelWithContext", "promise", "race", "finally"]
|
|
3
|
+
"sources": ["../../../src/context.ts", "../../../src/context-disposed-error.ts", "../../../src/promise-utils.ts", "../../../src/resource.ts"],
|
|
4
|
+
"sourcesContent": ["//\n// Copyright 2022 DXOS.org\n//\n\nimport { inspect } from 'node:util';\n\nimport { log } from '@dxos/log';\nimport { safeInstanceof } from '@dxos/util';\n\nimport { ContextDisposedError } from './context-disposed-error';\n\nexport type ContextErrorHandler = (error: Error) => void;\n\nexport type DisposeCallback = () => any | Promise<any>;\n\nexport type CreateContextParams = {\n onError?: ContextErrorHandler;\n attributes?: Record<string, any>;\n parent?: Context;\n};\n\n/**\n * Maximum number of dispose callbacks before we start logging warnings.\n */\nconst MAX_SAFE_DISPOSE_CALLBACKS = 300;\n\n@safeInstanceof('Context')\nexport class Context {\n static default() {\n return new Context();\n }\n\n private readonly _onError: ContextErrorHandler;\n private readonly _disposeCallbacks: DisposeCallback[] = [];\n private _isDisposed = false;\n private _disposePromise?: Promise<void> = undefined;\n private _parent: Context | null = null;\n\n private _attributes: Record<string, any>;\n\n public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;\n\n constructor({\n onError = (error) => {\n if (error instanceof ContextDisposedError) {\n return;\n }\n\n void this.dispose();\n\n // Will generate an unhandled rejection.\n throw error;\n },\n attributes = {},\n parent,\n }: CreateContextParams = {}) {\n this._onError = onError;\n this._attributes = attributes;\n if (parent !== undefined) {\n this._parent = parent;\n }\n }\n\n get disposed() {\n return this._isDisposed;\n }\n\n get disposeCallbacksLength() {\n return this._disposeCallbacks.length;\n }\n\n /**\n * Schedules a callback to run when the context is disposed.\n * May be async, in this case the disposer might choose to wait for all resource to released.\n * Throwing an error inside the callback will result in the error being logged, but not re-thrown.\n *\n * NOTE: Will call the callback immediately if the context is already disposed.\n *\n * @returns A function that can be used to remove the callback from the dispose list.\n */\n onDispose(callback: DisposeCallback) {\n if (this._isDisposed) {\n // Call the callback immediately if the context is already disposed.\n void (async () => {\n try {\n await callback();\n } catch (error: any) {\n log.catch(error);\n }\n })();\n }\n\n this._disposeCallbacks.push(callback);\n if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {\n log.warn('Context has a large number of dispose callbacks. This might be a memory leak.', {\n count: this._disposeCallbacks.length,\n safeThreshold: this.maxSafeDisposeCallbacks,\n });\n }\n\n return () => {\n const index = this._disposeCallbacks.indexOf(callback);\n if (index !== -1) {\n this._disposeCallbacks.splice(index, 1);\n }\n };\n }\n\n /**\n * Runs all dispose callbacks.\n * Sync callbacks are run in the reverse order they were added.\n * Async callbacks are run in parallel.\n * This function never throws.\n * It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.\n * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.\n */\n dispose(): Promise<void> {\n if (this._disposePromise) {\n return this._disposePromise;\n }\n this._isDisposed = true;\n\n // Set the promise before running the callbacks.\n let resolveDispose: () => void;\n this._disposePromise = new Promise<void>((resolve) => {\n resolveDispose = resolve;\n });\n\n const promises = [];\n // Clone the array so that any mutations to the original array don't affect the dispose process.\n const callbacks = Array.from(this._disposeCallbacks).reverse();\n for (const callback of callbacks) {\n promises.push(\n (async () => {\n try {\n await callback();\n } catch (error: any) {\n log.catch(error);\n }\n })(),\n );\n }\n this._disposeCallbacks.length = 0;\n\n void Promise.all(promises).then(() => {\n resolveDispose();\n });\n\n return this._disposePromise;\n }\n\n /**\n * Raise the error inside the context.\n * The error will be propagated to the error handler.\n * IF the error handler is not set, the error will dispose the context and cause an unhandled rejection.\n */\n raise(error: Error): void {\n if (this._isDisposed) {\n // TODO(dmaretskyi): Don't log those.\n // log.warn('Error in disposed context', error);\n return;\n }\n\n try {\n this._onError(error);\n } catch (err) {\n // Generate an unhandled rejection and stop the error propagation.\n void Promise.reject(err);\n }\n }\n\n derive({ onError, attributes }: CreateContextParams = {}): Context {\n const newCtx = new Context({\n // TODO(dmaretskyi): Optimize to not require allocating a new closure for every context.\n onError: async (error) => {\n if (!onError) {\n this.raise(error);\n } else {\n try {\n await onError(error);\n } catch {\n this.raise(error);\n }\n }\n },\n attributes,\n });\n const clearDispose = this.onDispose(() => newCtx.dispose());\n newCtx.onDispose(clearDispose);\n return newCtx;\n }\n\n getAttribute(key: string): any {\n if (key in this._attributes) {\n return this._attributes[key];\n }\n if (this._parent !== null) {\n return this._parent.getAttribute(key);\n }\n return undefined;\n }\n\n [Symbol.toStringTag] = 'Context';\n\n [inspect.custom] = () => this.toString();\n\n toString() {\n return `Context(${this._isDisposed ? 'disposed' : 'active'})`;\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nexport class ContextDisposedError extends Error {\n constructor() {\n super('Context disposed.');\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { type Context } from './context';\nimport { ContextDisposedError } from './context-disposed-error';\n\n/**\n * @returns A promise that rejects when the context is disposed.\n */\n// TODO(dmaretskyi): Memory leak.\nexport const rejectOnDispose = (ctx: Context, error = new ContextDisposedError()): Promise<never> =>\n new Promise((resolve, reject) => {\n ctx.onDispose(() => reject(error));\n });\n\n/**\n * Rejects the promise if the context is disposed.\n */\nexport const cancelWithContext = <T>(ctx: Context, promise: Promise<T>): Promise<T> => {\n let clearDispose: () => void;\n return Promise.race([\n promise,\n new Promise<never>((resolve, reject) => {\n // Will be called before .finally() handlers.\n clearDispose = ctx.onDispose(() => reject(new ContextDisposedError()));\n }),\n ]).finally(() => clearDispose?.());\n};\n", "//\n// Copyright 2024 DXOS.org\n//\n\nimport '@dxos/util';\nimport { Context } from './context';\n\nexport interface Lifecycle {\n open?(ctx?: Context): Promise<any> | any;\n close?(): Promise<any> | any;\n}\n\nexport enum LifecycleState {\n CLOSED = 'CLOSED',\n OPEN = 'OPEN',\n ERROR = 'ERROR',\n}\n\n/**\n * Base class for resources that need to be opened and closed.\n */\nexport class Resource implements Lifecycle {\n #lifecycleState = LifecycleState.CLOSED;\n #openPromise: Promise<void> | null = null;\n #closePromise: Promise<void> | null = null;\n\n /**\n * Managed internally by the resource.\n * Recreated on close.\n * Errors are propagated to the `_catch` method and the parent context.\n */\n #internalCtx: Context = this.#createContext();\n\n /**\n * Context that is used to bubble up errors that are not handled by the resource.\n * Provided in the open method.\n */\n #parentCtx: Context = new Context();\n\n protected get _lifecycleState() {\n return this.#lifecycleState;\n }\n\n protected get _ctx() {\n return this.#internalCtx;\n }\n\n /**\n * To be overridden by subclasses.\n */\n protected async _open(ctx: Context): Promise<void> {}\n\n /**\n * To be overridden by subclasses.\n */\n protected async _close(ctx: Context): Promise<void> {}\n\n /**\n * Error handler for errors that are caught by the context.\n * By default errors are bubbled up to the parent context which is passed to the open method.\n */\n protected async _catch(err: Error): Promise<void> {\n throw err;\n }\n\n /**\n * Opens the resource.\n * If the resource is already open, it does nothing.\n * If the resource is in an error state, it throws an error.\n * If the resource is closed, it waits for it to close and then opens it.\n * @param ctx - Context to use for opening the resource. This context will receive errors that are not handled in `_catch`.\n */\n async open(ctx?: Context): Promise<this> {\n switch (this.#lifecycleState) {\n case LifecycleState.OPEN:\n return this;\n case LifecycleState.ERROR:\n throw new Error(`Invalid state: ${this.#lifecycleState}`);\n default:\n }\n await this.#closePromise;\n await (this.#openPromise ??= this.#open(ctx));\n\n return this;\n }\n\n /**\n * Closes the resource.\n * If the resource is already closed, it does nothing.\n */\n async close(ctx?: Context): Promise<this> {\n if (this.#lifecycleState === LifecycleState.CLOSED) {\n return this;\n }\n await this.#openPromise;\n await (this.#closePromise ??= this.#close(ctx));\n\n return this;\n }\n\n async [Symbol.asyncDispose]() {\n await this.close();\n }\n\n async #open(ctx?: Context) {\n this.#closePromise = null;\n if (ctx) {\n this.#parentCtx = ctx;\n }\n await this._open(this.#parentCtx);\n this.#lifecycleState = LifecycleState.OPEN;\n }\n\n async #close(ctx = new Context()) {\n this.#openPromise = null;\n await this.#internalCtx.dispose();\n await this._close(ctx);\n this.#internalCtx = this.#createContext();\n this.#lifecycleState = LifecycleState.CLOSED;\n }\n\n #createContext() {\n return new Context({\n onError: (error) =>\n queueMicrotask(async () => {\n try {\n await this._catch(error);\n } catch (err: any) {\n this.#lifecycleState = LifecycleState.ERROR;\n this.#parentCtx.raise(err);\n }\n }),\n });\n }\n}\n\nexport const openInContext = async <T extends Lifecycle>(ctx: Context, resource: T): Promise<T> => {\n await resource.open?.(ctx);\n ctx.onDispose(() => resource.close?.());\n\n return resource;\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["import_util", "ContextDisposedError", "Error", "constructor", "MAX_SAFE_DISPOSE_CALLBACKS", "Context", "onError", "error", "dispose", "attributes", "parent", "_disposeCallbacks", "_isDisposed", "_disposePromise", "undefined", "_parent", "maxSafeDisposeCallbacks", "Symbol", "inspect", "toString", "_onError", "_attributes", "toStringTag", "custom", "default", "disposed", "disposeCallbacksLength", "length", "onDispose", "callback", "log", "catch", "push", "warn", "count", "safeThreshold", "index", "indexOf", "splice", "resolveDispose", "Promise", "resolve", "promises", "callbacks", "Array", "from", "reverse", "all", "then", "raise", "err", "reject", "derive", "newCtx", "clearDispose", "getAttribute", "key", "safeInstanceof", "rejectOnDispose", "ctx", "cancelWithContext", "promise", "race", "finally", "LifecycleState", "Resource", "_lifecycleState", "_ctx", "_open", "_close", "_catch", "open", "close", "asyncDispose", "queueMicrotask", "openInContext", "resource"]
|
|
7
7
|
}
|
package/dist/lib/node/meta.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"inputs":{"packages/common/context/src/context-disposed.ts":{"bytes":
|
|
1
|
+
{"inputs":{"packages/common/context/src/context-disposed-error.ts":{"bytes":817,"imports":[],"format":"esm"},"packages/common/context/src/context.ts":{"bytes":19782,"imports":[{"path":"node:util","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"packages/common/context/src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"}],"format":"esm"},"packages/common/context/src/promise-utils.ts":{"bytes":3040,"imports":[{"path":"packages/common/context/src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"}],"format":"esm"},"packages/common/context/src/resource.ts":{"bytes":11723,"imports":[{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"packages/common/context/src/context.ts","kind":"import-statement","original":"./context"}],"format":"esm"},"packages/common/context/src/index.ts":{"bytes":777,"imports":[{"path":"packages/common/context/src/context.ts","kind":"import-statement","original":"./context"},{"path":"packages/common/context/src/promise-utils.ts","kind":"import-statement","original":"./promise-utils"},{"path":"packages/common/context/src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"},{"path":"packages/common/context/src/resource.ts","kind":"import-statement","original":"./resource"}],"format":"esm"}},"outputs":{"packages/common/context/dist/lib/node/index.cjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":16404},"packages/common/context/dist/lib/node/index.cjs":{"imports":[{"path":"node:util","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"exports":["Context","ContextDisposedError","LifecycleState","Resource","cancelWithContext","openInContext","rejectOnDispose"],"entryPoint":"packages/common/context/src/index.ts","inputs":{"packages/common/context/src/context.ts":{"bytesInOutput":5533},"packages/common/context/src/context-disposed-error.ts":{"bytesInOutput":106},"packages/common/context/src/index.ts":{"bytesInOutput":0},"packages/common/context/src/promise-utils.ts":{"bytesInOutput":410},"packages/common/context/src/resource.ts":{"bytesInOutput":2978}},"bytes":9431}}}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-disposed-error.d.ts","sourceRoot":"","sources":["../../../src/context-disposed-error.ts"],"names":[],"mappings":"AAIA,qBAAa,oBAAqB,SAAQ,KAAK;;CAI9C"}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { inspect } from 'node:util';
|
|
1
3
|
export type ContextErrorHandler = (error: Error) => void;
|
|
2
|
-
export type DisposeCallback = () =>
|
|
4
|
+
export type DisposeCallback = () => any | Promise<any>;
|
|
3
5
|
export type CreateContextParams = {
|
|
4
6
|
onError?: ContextErrorHandler;
|
|
5
7
|
attributes?: Record<string, any>;
|
|
6
8
|
parent?: Context;
|
|
7
9
|
};
|
|
8
10
|
export declare class Context {
|
|
11
|
+
static default(): Context;
|
|
9
12
|
private readonly _onError;
|
|
10
13
|
private readonly _disposeCallbacks;
|
|
11
14
|
private _isDisposed;
|
|
@@ -43,5 +46,8 @@ export declare class Context {
|
|
|
43
46
|
raise(error: Error): void;
|
|
44
47
|
derive({ onError, attributes }?: CreateContextParams): Context;
|
|
45
48
|
getAttribute(key: string): any;
|
|
49
|
+
[Symbol.toStringTag]: string;
|
|
50
|
+
[inspect.custom]: () => string;
|
|
51
|
+
toString(): string;
|
|
46
52
|
}
|
|
47
53
|
//# sourceMappingURL=context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/context.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/context.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;AAEzD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;AAEvD,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAOF,qBACa,OAAO;IAClB,MAAM,CAAC,OAAO;IAId,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsB;IAC/C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAyB;IAC3D,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAC,CAA4B;IACpD,OAAO,CAAC,OAAO,CAAwB;IAEvC,OAAO,CAAC,WAAW,CAAsB;IAElC,uBAAuB,SAA8B;gBAEhD,EACV,OASC,EACD,UAAe,EACf,MAAM,GACP,GAAE,mBAAwB;IAQ3B,IAAI,QAAQ,YAEX;IAED,IAAI,sBAAsB,WAEzB;IAED;;;;;;;;OAQG;IACH,SAAS,CAAC,QAAQ,EAAE,eAAe;IA4BnC;;;;;;;OAOG;IACH,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAmCxB;;;;OAIG;IACH,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAezB,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,GAAE,mBAAwB,GAAG,OAAO;IAqBlE,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG;IAU9B,CAAC,MAAM,CAAC,WAAW,CAAC,SAAa;IAEjC,CAAC,OAAO,CAAC,MAAM,CAAC,eAAyB;IAEzC,QAAQ;CAGT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAIA,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAIA,cAAc,WAAW,CAAC;AAC1B,cAAc,iBAAiB,CAAC;AAChC,cAAc,0BAA0B,CAAC;AACzC,cAAc,YAAY,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"promise-utils.d.ts","sourceRoot":"","sources":["../../../src/promise-utils.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"promise-utils.d.ts","sourceRoot":"","sources":["../../../src/promise-utils.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAEhE;;GAEG;AAEH,eAAO,MAAM,eAAe,QAAS,OAAO,mCAAuC,QAAQ,KAAK,CAG5F,CAAC;AAEL;;GAEG;AACH,eAAO,MAAM,iBAAiB,WAAY,OAAO,oCAShD,CAAC"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import '@dxos/util';
|
|
2
|
+
import { Context } from './context';
|
|
3
|
+
export interface Lifecycle {
|
|
4
|
+
open?(ctx?: Context): Promise<any> | any;
|
|
5
|
+
close?(): Promise<any> | any;
|
|
6
|
+
}
|
|
7
|
+
export declare enum LifecycleState {
|
|
8
|
+
CLOSED = "CLOSED",
|
|
9
|
+
OPEN = "OPEN",
|
|
10
|
+
ERROR = "ERROR"
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Base class for resources that need to be opened and closed.
|
|
14
|
+
*/
|
|
15
|
+
export declare class Resource implements Lifecycle {
|
|
16
|
+
#private;
|
|
17
|
+
protected get _lifecycleState(): LifecycleState;
|
|
18
|
+
protected get _ctx(): Context;
|
|
19
|
+
/**
|
|
20
|
+
* To be overridden by subclasses.
|
|
21
|
+
*/
|
|
22
|
+
protected _open(ctx: Context): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* To be overridden by subclasses.
|
|
25
|
+
*/
|
|
26
|
+
protected _close(ctx: Context): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Error handler for errors that are caught by the context.
|
|
29
|
+
* By default errors are bubbled up to the parent context which is passed to the open method.
|
|
30
|
+
*/
|
|
31
|
+
protected _catch(err: Error): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Opens the resource.
|
|
34
|
+
* If the resource is already open, it does nothing.
|
|
35
|
+
* If the resource is in an error state, it throws an error.
|
|
36
|
+
* If the resource is closed, it waits for it to close and then opens it.
|
|
37
|
+
* @param ctx - Context to use for opening the resource. This context will receive errors that are not handled in `_catch`.
|
|
38
|
+
*/
|
|
39
|
+
open(ctx?: Context): Promise<this>;
|
|
40
|
+
/**
|
|
41
|
+
* Closes the resource.
|
|
42
|
+
* If the resource is already closed, it does nothing.
|
|
43
|
+
*/
|
|
44
|
+
close(ctx?: Context): Promise<this>;
|
|
45
|
+
[Symbol.asyncDispose](): Promise<void>;
|
|
46
|
+
}
|
|
47
|
+
export declare const openInContext: <T extends Lifecycle>(ctx: Context, resource: T) => Promise<T>;
|
|
48
|
+
//# sourceMappingURL=resource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../../src/resource.ts"],"names":[],"mappings":"AAIA,OAAO,YAAY,CAAC;AACpB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACzC,KAAK,CAAC,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;CAC9B;AAED,oBAAY,cAAc;IACxB,MAAM,WAAW;IACjB,IAAI,SAAS;IACb,KAAK,UAAU;CAChB;AAED;;GAEG;AACH,qBAAa,QAAS,YAAW,SAAS;;IAkBxC,SAAS,KAAK,eAAe,mBAE5B;IAED,SAAS,KAAK,IAAI,YAEjB;IAED;;OAEG;cACa,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAElD;;OAEG;cACa,MAAM,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAEnD;;;OAGG;cACa,MAAM,CAAC,GAAG,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC;IAIjD;;;;;;OAMG;IACG,IAAI,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAcxC;;;OAGG;IACG,KAAK,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAUnC,CAAC,MAAM,CAAC,YAAY,CAAC;CAkC5B;AAED,eAAO,MAAM,aAAa,6BAAoC,OAAO,4BAKpE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dxos/context",
|
|
3
|
-
"version": "0.4.10-main.
|
|
3
|
+
"version": "0.4.10-main.fd4f2a3",
|
|
4
4
|
"description": "Async utils.",
|
|
5
5
|
"homepage": "https://dxos.org",
|
|
6
6
|
"bugs": "https://github.com/dxos/dxos/issues",
|
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
"src"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@dxos/log": "0.4.10-main.
|
|
20
|
-
"@dxos/util": "0.4.10-main.
|
|
19
|
+
"@dxos/log": "0.4.10-main.fd4f2a3",
|
|
20
|
+
"@dxos/util": "0.4.10-main.fd4f2a3",
|
|
21
|
+
"@dxos/node-std": "0.4.10-main.fd4f2a3"
|
|
21
22
|
},
|
|
22
23
|
"publishConfig": {
|
|
23
24
|
"access": "public"
|
package/src/context.ts
CHANGED
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
// Copyright 2022 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import { inspect } from 'node:util';
|
|
6
|
+
|
|
5
7
|
import { log } from '@dxos/log';
|
|
6
8
|
import { safeInstanceof } from '@dxos/util';
|
|
7
9
|
|
|
8
|
-
import { ContextDisposedError } from './context-disposed';
|
|
10
|
+
import { ContextDisposedError } from './context-disposed-error';
|
|
9
11
|
|
|
10
12
|
export type ContextErrorHandler = (error: Error) => void;
|
|
11
13
|
|
|
12
|
-
export type DisposeCallback = () =>
|
|
14
|
+
export type DisposeCallback = () => any | Promise<any>;
|
|
13
15
|
|
|
14
16
|
export type CreateContextParams = {
|
|
15
17
|
onError?: ContextErrorHandler;
|
|
@@ -24,6 +26,10 @@ const MAX_SAFE_DISPOSE_CALLBACKS = 300;
|
|
|
24
26
|
|
|
25
27
|
@safeInstanceof('Context')
|
|
26
28
|
export class Context {
|
|
29
|
+
static default() {
|
|
30
|
+
return new Context();
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
private readonly _onError: ContextErrorHandler;
|
|
28
34
|
private readonly _disposeCallbacks: DisposeCallback[] = [];
|
|
29
35
|
private _isDisposed = false;
|
|
@@ -193,4 +199,12 @@ export class Context {
|
|
|
193
199
|
}
|
|
194
200
|
return undefined;
|
|
195
201
|
}
|
|
202
|
+
|
|
203
|
+
[Symbol.toStringTag] = 'Context';
|
|
204
|
+
|
|
205
|
+
[inspect.custom] = () => this.toString();
|
|
206
|
+
|
|
207
|
+
toString() {
|
|
208
|
+
return `Context(${this._isDisposed ? 'disposed' : 'active'})`;
|
|
209
|
+
}
|
|
196
210
|
}
|
package/src/index.ts
CHANGED
package/src/promise-utils.ts
CHANGED
package/src/resource.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2024 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import '@dxos/util';
|
|
6
|
+
import { Context } from './context';
|
|
7
|
+
|
|
8
|
+
export interface Lifecycle {
|
|
9
|
+
open?(ctx?: Context): Promise<any> | any;
|
|
10
|
+
close?(): Promise<any> | any;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export enum LifecycleState {
|
|
14
|
+
CLOSED = 'CLOSED',
|
|
15
|
+
OPEN = 'OPEN',
|
|
16
|
+
ERROR = 'ERROR',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Base class for resources that need to be opened and closed.
|
|
21
|
+
*/
|
|
22
|
+
export class Resource implements Lifecycle {
|
|
23
|
+
#lifecycleState = LifecycleState.CLOSED;
|
|
24
|
+
#openPromise: Promise<void> | null = null;
|
|
25
|
+
#closePromise: Promise<void> | null = null;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Managed internally by the resource.
|
|
29
|
+
* Recreated on close.
|
|
30
|
+
* Errors are propagated to the `_catch` method and the parent context.
|
|
31
|
+
*/
|
|
32
|
+
#internalCtx: Context = this.#createContext();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Context that is used to bubble up errors that are not handled by the resource.
|
|
36
|
+
* Provided in the open method.
|
|
37
|
+
*/
|
|
38
|
+
#parentCtx: Context = new Context();
|
|
39
|
+
|
|
40
|
+
protected get _lifecycleState() {
|
|
41
|
+
return this.#lifecycleState;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
protected get _ctx() {
|
|
45
|
+
return this.#internalCtx;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* To be overridden by subclasses.
|
|
50
|
+
*/
|
|
51
|
+
protected async _open(ctx: Context): Promise<void> {}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* To be overridden by subclasses.
|
|
55
|
+
*/
|
|
56
|
+
protected async _close(ctx: Context): Promise<void> {}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Error handler for errors that are caught by the context.
|
|
60
|
+
* By default errors are bubbled up to the parent context which is passed to the open method.
|
|
61
|
+
*/
|
|
62
|
+
protected async _catch(err: Error): Promise<void> {
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Opens the resource.
|
|
68
|
+
* If the resource is already open, it does nothing.
|
|
69
|
+
* If the resource is in an error state, it throws an error.
|
|
70
|
+
* If the resource is closed, it waits for it to close and then opens it.
|
|
71
|
+
* @param ctx - Context to use for opening the resource. This context will receive errors that are not handled in `_catch`.
|
|
72
|
+
*/
|
|
73
|
+
async open(ctx?: Context): Promise<this> {
|
|
74
|
+
switch (this.#lifecycleState) {
|
|
75
|
+
case LifecycleState.OPEN:
|
|
76
|
+
return this;
|
|
77
|
+
case LifecycleState.ERROR:
|
|
78
|
+
throw new Error(`Invalid state: ${this.#lifecycleState}`);
|
|
79
|
+
default:
|
|
80
|
+
}
|
|
81
|
+
await this.#closePromise;
|
|
82
|
+
await (this.#openPromise ??= this.#open(ctx));
|
|
83
|
+
|
|
84
|
+
return this;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Closes the resource.
|
|
89
|
+
* If the resource is already closed, it does nothing.
|
|
90
|
+
*/
|
|
91
|
+
async close(ctx?: Context): Promise<this> {
|
|
92
|
+
if (this.#lifecycleState === LifecycleState.CLOSED) {
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
await this.#openPromise;
|
|
96
|
+
await (this.#closePromise ??= this.#close(ctx));
|
|
97
|
+
|
|
98
|
+
return this;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async [Symbol.asyncDispose]() {
|
|
102
|
+
await this.close();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async #open(ctx?: Context) {
|
|
106
|
+
this.#closePromise = null;
|
|
107
|
+
if (ctx) {
|
|
108
|
+
this.#parentCtx = ctx;
|
|
109
|
+
}
|
|
110
|
+
await this._open(this.#parentCtx);
|
|
111
|
+
this.#lifecycleState = LifecycleState.OPEN;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async #close(ctx = new Context()) {
|
|
115
|
+
this.#openPromise = null;
|
|
116
|
+
await this.#internalCtx.dispose();
|
|
117
|
+
await this._close(ctx);
|
|
118
|
+
this.#internalCtx = this.#createContext();
|
|
119
|
+
this.#lifecycleState = LifecycleState.CLOSED;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#createContext() {
|
|
123
|
+
return new Context({
|
|
124
|
+
onError: (error) =>
|
|
125
|
+
queueMicrotask(async () => {
|
|
126
|
+
try {
|
|
127
|
+
await this._catch(error);
|
|
128
|
+
} catch (err: any) {
|
|
129
|
+
this.#lifecycleState = LifecycleState.ERROR;
|
|
130
|
+
this.#parentCtx.raise(err);
|
|
131
|
+
}
|
|
132
|
+
}),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export const openInContext = async <T extends Lifecycle>(ctx: Context, resource: T): Promise<T> => {
|
|
138
|
+
await resource.open?.(ctx);
|
|
139
|
+
ctx.onDispose(() => resource.close?.());
|
|
140
|
+
|
|
141
|
+
return resource;
|
|
142
|
+
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"context-disposed.d.ts","sourceRoot":"","sources":["../../../src/context-disposed.ts"],"names":[],"mappings":"AAIA,qBAAa,oBAAqB,SAAQ,KAAK;;CAI9C"}
|
|
File without changes
|