@dxos/context 0.5.3-main.f752aaa → 0.5.3-main.fffc127

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.
@@ -23,27 +23,28 @@ function _ts_decorate(decorators, target, key, desc) {
23
23
  }
24
24
  var __dxlog_file = "/home/runner/work/dxos/dxos/packages/common/context/src/context.ts";
25
25
  var MAX_SAFE_DISPOSE_CALLBACKS = 300;
26
+ var DEFAULT_ERROR_HANDLER = (error, ctx) => {
27
+ if (error instanceof ContextDisposedError) {
28
+ return;
29
+ }
30
+ void ctx.dispose();
31
+ throw error;
32
+ };
26
33
  var _a, _b;
27
34
  var Context = class _Context {
28
- constructor({ onError = (error) => {
29
- if (error instanceof ContextDisposedError) {
30
- return;
31
- }
32
- void this.dispose();
33
- throw error;
34
- }, attributes = {}, parent } = {}) {
35
- this._disposeCallbacks = [];
36
- this._isDisposed = false;
37
- this._disposePromise = void 0;
38
- this._parent = null;
35
+ constructor({ name, parent, attributes = {}, onError = DEFAULT_ERROR_HANDLER } = {}) {
36
+ this.#disposeCallbacks = [];
37
+ this.#name = void 0;
38
+ this.#parent = void 0;
39
+ this.#isDisposed = false;
40
+ this.#disposePromise = void 0;
39
41
  this.maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
40
42
  this[_a] = "Context";
41
43
  this[_b] = () => this.toString();
42
- this._onError = onError;
43
- this._attributes = attributes;
44
- if (parent !== void 0) {
45
- this._parent = parent;
46
- }
44
+ this.#name = name;
45
+ this.#parent = parent;
46
+ this.#attributes = attributes;
47
+ this.#onError = onError;
47
48
  }
48
49
  static {
49
50
  _a = Symbol.toStringTag, _b = inspect.custom;
@@ -51,11 +52,18 @@ var Context = class _Context {
51
52
  static default() {
52
53
  return new _Context();
53
54
  }
55
+ #disposeCallbacks;
56
+ #name;
57
+ #parent;
58
+ #attributes;
59
+ #onError;
60
+ #isDisposed;
61
+ #disposePromise;
54
62
  get disposed() {
55
- return this._isDisposed;
63
+ return this.#isDisposed;
56
64
  }
57
65
  get disposeCallbacksLength() {
58
- return this._disposeCallbacks.length;
66
+ return this.#disposeCallbacks.length;
59
67
  }
60
68
  /**
61
69
  * Schedules a callback to run when the context is disposed.
@@ -67,7 +75,7 @@ var Context = class _Context {
67
75
  * @returns A function that can be used to remove the callback from the dispose list.
68
76
  */
69
77
  onDispose(callback) {
70
- if (this._isDisposed) {
78
+ if (this.#isDisposed) {
71
79
  void (async () => {
72
80
  try {
73
81
  await callback();
@@ -81,11 +89,10 @@ var Context = class _Context {
81
89
  }
82
90
  })();
83
91
  }
84
- this._disposeCallbacks.push(callback);
85
- if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
86
- log.warn("Context has a large number of dispose callbacks. This might be a memory leak.", {
87
- count: this._disposeCallbacks.length,
88
- safeThreshold: this.maxSafeDisposeCallbacks
92
+ this.#disposeCallbacks.push(callback);
93
+ if (this.#disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
94
+ log.warn("Context has a large number of dispose callbacks (this might be a memory leak).", {
95
+ count: this.#disposeCallbacks.length
89
96
  }, {
90
97
  F: __dxlog_file,
91
98
  L: 95,
@@ -94,9 +101,9 @@ var Context = class _Context {
94
101
  });
95
102
  }
96
103
  return () => {
97
- const index = this._disposeCallbacks.indexOf(callback);
104
+ const index = this.#disposeCallbacks.indexOf(callback);
98
105
  if (index !== -1) {
99
- this._disposeCallbacks.splice(index, 1);
106
+ this.#disposeCallbacks.splice(index, 1);
100
107
  }
101
108
  };
102
109
  }
@@ -107,30 +114,69 @@ var Context = class _Context {
107
114
  * It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.
108
115
  * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.
109
116
  */
110
- async dispose() {
111
- if (this._disposePromise) {
112
- return this._disposePromise;
117
+ async dispose(throwOnError = false) {
118
+ if (this.#disposePromise) {
119
+ return this.#disposePromise;
113
120
  }
114
- this._isDisposed = true;
121
+ this.#isDisposed = true;
115
122
  let resolveDispose;
116
- this._disposePromise = new Promise((resolve) => {
123
+ const promise = new Promise((resolve) => {
117
124
  resolveDispose = resolve;
118
125
  });
119
- const callbacks = Array.from(this._disposeCallbacks).reverse();
120
- this._disposeCallbacks.length = 0;
126
+ this.#disposePromise = promise;
127
+ const callbacks = Array.from(this.#disposeCallbacks).reverse();
128
+ this.#disposeCallbacks.length = 0;
129
+ if (this.#name) {
130
+ log("disposing", {
131
+ context: this.#name,
132
+ count: callbacks.length
133
+ }, {
134
+ F: __dxlog_file,
135
+ L: 137,
136
+ S: this,
137
+ C: (f, a) => f(...a)
138
+ });
139
+ }
140
+ let i = 0;
141
+ let clean = true;
142
+ const errors = [];
121
143
  for (const callback of callbacks) {
122
144
  try {
123
145
  await callback();
124
- } catch (error) {
125
- log.catch(error, void 0, {
126
- F: __dxlog_file,
127
- L: 136,
128
- S: this,
129
- C: (f, a) => f(...a)
130
- });
146
+ i++;
147
+ } catch (err) {
148
+ clean = false;
149
+ if (throwOnError) {
150
+ errors.push(err);
151
+ } else {
152
+ log.catch(err, {
153
+ context: this.#name,
154
+ callback: i,
155
+ count: callbacks.length
156
+ }, {
157
+ F: __dxlog_file,
158
+ L: 152,
159
+ S: this,
160
+ C: (f, a) => f(...a)
161
+ });
162
+ }
131
163
  }
132
164
  }
133
- resolveDispose();
165
+ if (errors.length > 0) {
166
+ throw new AggregateError(errors);
167
+ }
168
+ resolveDispose(clean);
169
+ if (this.#name) {
170
+ log("disposed", {
171
+ context: this.#name
172
+ }, {
173
+ F: __dxlog_file,
174
+ L: 163,
175
+ S: this,
176
+ C: (f, a) => f(...a)
177
+ });
178
+ }
179
+ return clean;
134
180
  }
135
181
  /**
136
182
  * Raise the error inside the context.
@@ -138,11 +184,11 @@ var Context = class _Context {
138
184
  * IF the error handler is not set, the error will dispose the context and cause an unhandled rejection.
139
185
  */
140
186
  raise(error) {
141
- if (this._isDisposed) {
187
+ if (this.#isDisposed) {
142
188
  return;
143
189
  }
144
190
  try {
145
- this._onError(error);
191
+ this.#onError(error, this);
146
192
  } catch (err) {
147
193
  void Promise.reject(err);
148
194
  }
@@ -155,7 +201,7 @@ var Context = class _Context {
155
201
  this.raise(error);
156
202
  } else {
157
203
  try {
158
- await onError(error);
204
+ await onError(error, this);
159
205
  } catch {
160
206
  this.raise(error);
161
207
  }
@@ -168,16 +214,16 @@ var Context = class _Context {
168
214
  return newCtx;
169
215
  }
170
216
  getAttribute(key) {
171
- if (key in this._attributes) {
172
- return this._attributes[key];
217
+ if (key in this.#attributes) {
218
+ return this.#attributes[key];
173
219
  }
174
- if (this._parent !== null) {
175
- return this._parent.getAttribute(key);
220
+ if (this.#parent) {
221
+ return this.#parent.getAttribute(key);
176
222
  }
177
223
  return void 0;
178
224
  }
179
225
  toString() {
180
- return `Context(${this._isDisposed ? "disposed" : "active"})`;
226
+ return `Context(${this.#isDisposed ? "disposed" : "active"})`;
181
227
  }
182
228
  };
183
229
  Context = _ts_decorate([
@@ -199,13 +245,14 @@ var cancelWithContext = (ctx, promise) => {
199
245
  };
200
246
 
201
247
  // packages/common/context/src/resource.ts
202
- import "@dxos/util";
248
+ import { throwUnhandledError } from "@dxos/util";
203
249
  var LifecycleState;
204
250
  (function(LifecycleState2) {
205
251
  LifecycleState2["CLOSED"] = "CLOSED";
206
252
  LifecycleState2["OPEN"] = "OPEN";
207
253
  LifecycleState2["ERROR"] = "ERROR";
208
254
  })(LifecycleState || (LifecycleState = {}));
255
+ var CLOSE_RESOURCE_ON_UNHANDLED_ERROR = false;
209
256
  var Resource = class {
210
257
  #lifecycleState = "CLOSED";
211
258
  #openPromise = null;
@@ -220,7 +267,12 @@ var Resource = class {
220
267
  * Context that is used to bubble up errors that are not handled by the resource.
221
268
  * Provided in the open method.
222
269
  */
223
- #parentCtx = new Context();
270
+ #parentCtx = new Context({
271
+ name: this.#name
272
+ });
273
+ get #name() {
274
+ return Object.getPrototypeOf(this).constructor.name;
275
+ }
224
276
  get _lifecycleState() {
225
277
  return this.#lifecycleState;
226
278
  }
@@ -242,6 +294,13 @@ var Resource = class {
242
294
  * By default, errors are bubbled up to the parent context which is passed to the open method.
243
295
  */
244
296
  async _catch(err) {
297
+ if (CLOSE_RESOURCE_ON_UNHANDLED_ERROR) {
298
+ try {
299
+ await this.close();
300
+ } catch (doubleErr) {
301
+ throwUnhandledError(doubleErr);
302
+ }
303
+ }
245
304
  throw err;
246
305
  }
247
306
  /**
@@ -281,12 +340,14 @@ var Resource = class {
281
340
  async #open(ctx) {
282
341
  this.#closePromise = null;
283
342
  if (ctx) {
284
- this.#parentCtx = ctx;
343
+ this.#parentCtx = ctx.derive({
344
+ name: this.#name
345
+ });
285
346
  }
286
347
  await this._open(this.#parentCtx);
287
348
  this.#lifecycleState = "OPEN";
288
349
  }
289
- async #close(ctx = new Context()) {
350
+ async #close(ctx = Context.default()) {
290
351
  this.#openPromise = null;
291
352
  await this.#internalCtx.dispose();
292
353
  await this._close(ctx);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
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 * Callbacks are run in the reverse order they were added.\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 async 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 // 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 this._disposeCallbacks.length = 0;\n\n for (const callback of callbacks) {\n try {\n await callback();\n } catch (error: any) {\n log.catch(error);\n }\n }\n resolveDispose();\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';\n\nimport { Context } from './context';\n\nexport enum LifecycleState {\n CLOSED = 'CLOSED',\n OPEN = 'OPEN',\n ERROR = 'ERROR',\n}\n\nexport interface Lifecycle {\n open?(ctx?: Context): Promise<any> | any;\n close?(): Promise<any> | any;\n}\n\n/**\n * Base class for resources that need to be opened and closed.\n */\nexport abstract 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\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", "callbacks", "Array", "from", "reverse", "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"]
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, ctx: Context) => void;\n\nexport type DisposeCallback = () => any | Promise<any>;\n\nexport type CreateContextParams = {\n name?: string;\n parent?: Context;\n attributes?: Record<string, any>;\n onError?: ContextErrorHandler;\n};\n\n/**\n * Maximum number of dispose callbacks before we start logging warnings.\n */\nconst MAX_SAFE_DISPOSE_CALLBACKS = 300;\n\nconst DEFAULT_ERROR_HANDLER: ContextErrorHandler = (error, ctx) => {\n if (error instanceof ContextDisposedError) {\n return;\n }\n\n void ctx.dispose();\n\n // Will generate an unhandled rejection.\n throw error;\n};\n\n@safeInstanceof('Context')\nexport class Context {\n static default() {\n return new Context();\n }\n\n readonly #disposeCallbacks: DisposeCallback[] = [];\n\n readonly #name?: string = undefined;\n readonly #parent?: Context = undefined;\n readonly #attributes: Record<string, any>;\n readonly #onError: ContextErrorHandler;\n\n #isDisposed = false;\n #disposePromise?: Promise<boolean> = undefined;\n\n public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;\n\n constructor({ name, parent, attributes = {}, onError = DEFAULT_ERROR_HANDLER }: CreateContextParams = {}) {\n this.#name = name;\n this.#parent = parent;\n this.#attributes = attributes;\n this.#onError = onError;\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): () => void {\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 });\n }\n\n // Remove handler.\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 * Callbacks are run in the reverse order they were added.\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 async dispose(throwOnError = false): Promise<boolean> {\n if (this.#disposePromise) {\n return this.#disposePromise;\n }\n\n // TODO(burdon): Probably should not be set until the dispose is complete, but causes tests to fail if moved.\n this.#isDisposed = true;\n\n // Set the promise before running the callbacks.\n let resolveDispose!: (value: boolean) => void;\n const promise = new Promise<boolean>((resolve) => {\n resolveDispose = resolve;\n });\n this.#disposePromise = promise;\n\n // Process last first.\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 this.#disposeCallbacks.length = 0;\n\n if (this.#name) {\n log('disposing', { context: this.#name, count: callbacks.length });\n }\n\n let i = 0;\n let clean = true;\n const errors: Error[] = [];\n for (const callback of callbacks) {\n try {\n await callback();\n i++;\n } catch (err: any) {\n clean = false;\n if (throwOnError) {\n errors.push(err);\n } else {\n log.catch(err, { context: this.#name, callback: i, count: callbacks.length });\n }\n }\n }\n\n if (errors.length > 0) {\n throw new AggregateError(errors);\n }\n\n resolveDispose(clean);\n if (this.#name) {\n log('disposed', { context: this.#name });\n }\n\n return clean;\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, this);\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, this);\n } catch {\n this.raise(error);\n }\n }\n },\n attributes,\n });\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) {\n return this.#parent.getAttribute(key);\n }\n\n return undefined;\n }\n\n [Symbol.toStringTag] = 'Context';\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 { throwUnhandledError } from '@dxos/util';\n\nimport { Context } from './context';\n\nexport enum LifecycleState {\n CLOSED = 'CLOSED',\n OPEN = 'OPEN',\n ERROR = 'ERROR',\n}\n\nexport interface Lifecycle {\n open?(ctx?: Context): Promise<any> | any;\n close?(): Promise<any> | any;\n}\n\n// Feature flag to be enabled later.\nconst CLOSE_RESOURCE_ON_UNHANDLED_ERROR = false;\n\n/**\n * Base class for resources that need to be opened and closed.\n */\nexport abstract 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({ name: this.#name });\n\n get #name() {\n return Object.getPrototypeOf(this).constructor.name;\n }\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 if (CLOSE_RESOURCE_ON_UNHANDLED_ERROR) {\n try {\n await this.close();\n } catch (doubleErr: any) {\n throwUnhandledError(doubleErr);\n }\n }\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\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.derive({ name: this.#name });\n }\n await this._open(this.#parentCtx);\n this.#lifecycleState = LifecycleState.OPEN;\n }\n\n async #close(ctx = Context.default()) {\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", "DEFAULT_ERROR_HANDLER", "error", "ctx", "ContextDisposedError", "dispose", "Context", "constructor", "name", "parent", "attributes", "onError", "undefined", "maxSafeDisposeCallbacks", "Symbol", "inspect", "toString", "toStringTag", "custom", "default", "disposed", "disposeCallbacksLength", "length", "onDispose", "callback", "log", "catch", "push", "warn", "count", "index", "indexOf", "splice", "throwOnError", "resolveDispose", "promise", "Promise", "resolve", "callbacks", "Array", "from", "reverse", "context", "i", "clean", "errors", "err", "AggregateError", "raise", "reject", "derive", "newCtx", "clearDispose", "getAttribute", "key", "safeInstanceof", "rejectOnDispose", "ctx", "error", "ContextDisposedError", "Promise", "resolve", "reject", "onDispose", "cancelWithContext", "promise", "clearDispose", "race", "finally", "throwUnhandledError", "LifecycleState", "CLOSE_RESOURCE_ON_UNHANDLED_ERROR", "Resource", "Context", "name", "Object", "getPrototypeOf", "constructor", "_lifecycleState", "_ctx", "_open", "ctx", "_close", "_catch", "err", "close", "doubleErr", "throwUnhandledError", "open", "Error", "Symbol", "asyncDispose", "derive", "default", "dispose", "onError", "error", "queueMicrotask", "raise", "openInContext", "resource", "onDispose"]
7
7
  }
@@ -1 +1 @@
1
- {"inputs":{"packages/common/context/src/context-disposed-error.ts":{"bytes":817,"imports":[],"format":"esm"},"packages/common/context/src/context.ts":{"bytes":18959,"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":11744,"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":16013},"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":5323},"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":2979}},"bytes":9222}}}
1
+ {"inputs":{"packages/common/context/src/context-disposed-error.ts":{"bytes":853,"imports":[],"format":"esm"},"packages/common/context/src/context.ts":{"bytes":22853,"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":3096,"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":13662,"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":809,"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":18172},"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":6283},"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":3372}},"bytes":10575}}}
@@ -48,27 +48,28 @@ function _ts_decorate(decorators, target, key, desc) {
48
48
  }
49
49
  var __dxlog_file = "/home/runner/work/dxos/dxos/packages/common/context/src/context.ts";
50
50
  var MAX_SAFE_DISPOSE_CALLBACKS = 300;
51
+ var DEFAULT_ERROR_HANDLER = (error, ctx) => {
52
+ if (error instanceof ContextDisposedError) {
53
+ return;
54
+ }
55
+ void ctx.dispose();
56
+ throw error;
57
+ };
51
58
  var _a, _b;
52
59
  var Context = class _Context {
53
- constructor({ onError = (error) => {
54
- if (error instanceof ContextDisposedError) {
55
- return;
56
- }
57
- void this.dispose();
58
- throw error;
59
- }, attributes = {}, parent } = {}) {
60
- this._disposeCallbacks = [];
61
- this._isDisposed = false;
62
- this._disposePromise = void 0;
63
- this._parent = null;
60
+ constructor({ name, parent, attributes = {}, onError = DEFAULT_ERROR_HANDLER } = {}) {
61
+ this.#disposeCallbacks = [];
62
+ this.#name = void 0;
63
+ this.#parent = void 0;
64
+ this.#isDisposed = false;
65
+ this.#disposePromise = void 0;
64
66
  this.maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
65
67
  this[_a] = "Context";
66
68
  this[_b] = () => this.toString();
67
- this._onError = onError;
68
- this._attributes = attributes;
69
- if (parent !== void 0) {
70
- this._parent = parent;
71
- }
69
+ this.#name = name;
70
+ this.#parent = parent;
71
+ this.#attributes = attributes;
72
+ this.#onError = onError;
72
73
  }
73
74
  static {
74
75
  _a = Symbol.toStringTag, _b = import_node_util.inspect.custom;
@@ -76,11 +77,18 @@ var Context = class _Context {
76
77
  static default() {
77
78
  return new _Context();
78
79
  }
80
+ #disposeCallbacks;
81
+ #name;
82
+ #parent;
83
+ #attributes;
84
+ #onError;
85
+ #isDisposed;
86
+ #disposePromise;
79
87
  get disposed() {
80
- return this._isDisposed;
88
+ return this.#isDisposed;
81
89
  }
82
90
  get disposeCallbacksLength() {
83
- return this._disposeCallbacks.length;
91
+ return this.#disposeCallbacks.length;
84
92
  }
85
93
  /**
86
94
  * Schedules a callback to run when the context is disposed.
@@ -92,7 +100,7 @@ var Context = class _Context {
92
100
  * @returns A function that can be used to remove the callback from the dispose list.
93
101
  */
94
102
  onDispose(callback) {
95
- if (this._isDisposed) {
103
+ if (this.#isDisposed) {
96
104
  void (async () => {
97
105
  try {
98
106
  await callback();
@@ -106,11 +114,10 @@ var Context = class _Context {
106
114
  }
107
115
  })();
108
116
  }
109
- this._disposeCallbacks.push(callback);
110
- if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
111
- import_log.log.warn("Context has a large number of dispose callbacks. This might be a memory leak.", {
112
- count: this._disposeCallbacks.length,
113
- safeThreshold: this.maxSafeDisposeCallbacks
117
+ this.#disposeCallbacks.push(callback);
118
+ if (this.#disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
119
+ import_log.log.warn("Context has a large number of dispose callbacks (this might be a memory leak).", {
120
+ count: this.#disposeCallbacks.length
114
121
  }, {
115
122
  F: __dxlog_file,
116
123
  L: 95,
@@ -119,9 +126,9 @@ var Context = class _Context {
119
126
  });
120
127
  }
121
128
  return () => {
122
- const index = this._disposeCallbacks.indexOf(callback);
129
+ const index = this.#disposeCallbacks.indexOf(callback);
123
130
  if (index !== -1) {
124
- this._disposeCallbacks.splice(index, 1);
131
+ this.#disposeCallbacks.splice(index, 1);
125
132
  }
126
133
  };
127
134
  }
@@ -132,30 +139,69 @@ var Context = class _Context {
132
139
  * It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.
133
140
  * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.
134
141
  */
135
- async dispose() {
136
- if (this._disposePromise) {
137
- return this._disposePromise;
142
+ async dispose(throwOnError = false) {
143
+ if (this.#disposePromise) {
144
+ return this.#disposePromise;
138
145
  }
139
- this._isDisposed = true;
146
+ this.#isDisposed = true;
140
147
  let resolveDispose;
141
- this._disposePromise = new Promise((resolve) => {
148
+ const promise = new Promise((resolve) => {
142
149
  resolveDispose = resolve;
143
150
  });
144
- const callbacks = Array.from(this._disposeCallbacks).reverse();
145
- this._disposeCallbacks.length = 0;
151
+ this.#disposePromise = promise;
152
+ const callbacks = Array.from(this.#disposeCallbacks).reverse();
153
+ this.#disposeCallbacks.length = 0;
154
+ if (this.#name) {
155
+ (0, import_log.log)("disposing", {
156
+ context: this.#name,
157
+ count: callbacks.length
158
+ }, {
159
+ F: __dxlog_file,
160
+ L: 137,
161
+ S: this,
162
+ C: (f, a) => f(...a)
163
+ });
164
+ }
165
+ let i = 0;
166
+ let clean = true;
167
+ const errors = [];
146
168
  for (const callback of callbacks) {
147
169
  try {
148
170
  await callback();
149
- } catch (error) {
150
- import_log.log.catch(error, void 0, {
151
- F: __dxlog_file,
152
- L: 136,
153
- S: this,
154
- C: (f, a) => f(...a)
155
- });
171
+ i++;
172
+ } catch (err) {
173
+ clean = false;
174
+ if (throwOnError) {
175
+ errors.push(err);
176
+ } else {
177
+ import_log.log.catch(err, {
178
+ context: this.#name,
179
+ callback: i,
180
+ count: callbacks.length
181
+ }, {
182
+ F: __dxlog_file,
183
+ L: 152,
184
+ S: this,
185
+ C: (f, a) => f(...a)
186
+ });
187
+ }
156
188
  }
157
189
  }
158
- resolveDispose();
190
+ if (errors.length > 0) {
191
+ throw new AggregateError(errors);
192
+ }
193
+ resolveDispose(clean);
194
+ if (this.#name) {
195
+ (0, import_log.log)("disposed", {
196
+ context: this.#name
197
+ }, {
198
+ F: __dxlog_file,
199
+ L: 163,
200
+ S: this,
201
+ C: (f, a) => f(...a)
202
+ });
203
+ }
204
+ return clean;
159
205
  }
160
206
  /**
161
207
  * Raise the error inside the context.
@@ -163,11 +209,11 @@ var Context = class _Context {
163
209
  * IF the error handler is not set, the error will dispose the context and cause an unhandled rejection.
164
210
  */
165
211
  raise(error) {
166
- if (this._isDisposed) {
212
+ if (this.#isDisposed) {
167
213
  return;
168
214
  }
169
215
  try {
170
- this._onError(error);
216
+ this.#onError(error, this);
171
217
  } catch (err) {
172
218
  void Promise.reject(err);
173
219
  }
@@ -180,7 +226,7 @@ var Context = class _Context {
180
226
  this.raise(error);
181
227
  } else {
182
228
  try {
183
- await onError(error);
229
+ await onError(error, this);
184
230
  } catch {
185
231
  this.raise(error);
186
232
  }
@@ -193,16 +239,16 @@ var Context = class _Context {
193
239
  return newCtx;
194
240
  }
195
241
  getAttribute(key) {
196
- if (key in this._attributes) {
197
- return this._attributes[key];
242
+ if (key in this.#attributes) {
243
+ return this.#attributes[key];
198
244
  }
199
- if (this._parent !== null) {
200
- return this._parent.getAttribute(key);
245
+ if (this.#parent) {
246
+ return this.#parent.getAttribute(key);
201
247
  }
202
248
  return void 0;
203
249
  }
204
250
  toString() {
205
- return `Context(${this._isDisposed ? "disposed" : "active"})`;
251
+ return `Context(${this.#isDisposed ? "disposed" : "active"})`;
206
252
  }
207
253
  };
208
254
  Context = _ts_decorate([
@@ -226,6 +272,7 @@ var LifecycleState;
226
272
  LifecycleState2["OPEN"] = "OPEN";
227
273
  LifecycleState2["ERROR"] = "ERROR";
228
274
  })(LifecycleState || (LifecycleState = {}));
275
+ var CLOSE_RESOURCE_ON_UNHANDLED_ERROR = false;
229
276
  var Resource = class {
230
277
  #lifecycleState = "CLOSED";
231
278
  #openPromise = null;
@@ -240,7 +287,12 @@ var Resource = class {
240
287
  * Context that is used to bubble up errors that are not handled by the resource.
241
288
  * Provided in the open method.
242
289
  */
243
- #parentCtx = new Context();
290
+ #parentCtx = new Context({
291
+ name: this.#name
292
+ });
293
+ get #name() {
294
+ return Object.getPrototypeOf(this).constructor.name;
295
+ }
244
296
  get _lifecycleState() {
245
297
  return this.#lifecycleState;
246
298
  }
@@ -262,6 +314,13 @@ var Resource = class {
262
314
  * By default, errors are bubbled up to the parent context which is passed to the open method.
263
315
  */
264
316
  async _catch(err) {
317
+ if (CLOSE_RESOURCE_ON_UNHANDLED_ERROR) {
318
+ try {
319
+ await this.close();
320
+ } catch (doubleErr) {
321
+ (0, import_util2.throwUnhandledError)(doubleErr);
322
+ }
323
+ }
265
324
  throw err;
266
325
  }
267
326
  /**
@@ -301,12 +360,14 @@ var Resource = class {
301
360
  async #open(ctx) {
302
361
  this.#closePromise = null;
303
362
  if (ctx) {
304
- this.#parentCtx = ctx;
363
+ this.#parentCtx = ctx.derive({
364
+ name: this.#name
365
+ });
305
366
  }
306
367
  await this._open(this.#parentCtx);
307
368
  this.#lifecycleState = "OPEN";
308
369
  }
309
- async #close(ctx = new Context()) {
370
+ async #close(ctx = Context.default()) {
310
371
  this.#openPromise = null;
311
372
  await this.#internalCtx.dispose();
312
373
  await this._close(ctx);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
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 * Callbacks are run in the reverse order they were added.\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 async 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 // 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 this._disposeCallbacks.length = 0;\n\n for (const callback of callbacks) {\n try {\n await callback();\n } catch (error: any) {\n log.catch(error);\n }\n }\n resolveDispose();\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';\n\nimport { Context } from './context';\n\nexport enum LifecycleState {\n CLOSED = 'CLOSED',\n OPEN = 'OPEN',\n ERROR = 'ERROR',\n}\n\nexport interface Lifecycle {\n open?(ctx?: Context): Promise<any> | any;\n close?(): Promise<any> | any;\n}\n\n/**\n * Base class for resources that need to be opened and closed.\n */\nexport abstract 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\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", "callbacks", "Array", "from", "reverse", "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"]
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, ctx: Context) => void;\n\nexport type DisposeCallback = () => any | Promise<any>;\n\nexport type CreateContextParams = {\n name?: string;\n parent?: Context;\n attributes?: Record<string, any>;\n onError?: ContextErrorHandler;\n};\n\n/**\n * Maximum number of dispose callbacks before we start logging warnings.\n */\nconst MAX_SAFE_DISPOSE_CALLBACKS = 300;\n\nconst DEFAULT_ERROR_HANDLER: ContextErrorHandler = (error, ctx) => {\n if (error instanceof ContextDisposedError) {\n return;\n }\n\n void ctx.dispose();\n\n // Will generate an unhandled rejection.\n throw error;\n};\n\n@safeInstanceof('Context')\nexport class Context {\n static default() {\n return new Context();\n }\n\n readonly #disposeCallbacks: DisposeCallback[] = [];\n\n readonly #name?: string = undefined;\n readonly #parent?: Context = undefined;\n readonly #attributes: Record<string, any>;\n readonly #onError: ContextErrorHandler;\n\n #isDisposed = false;\n #disposePromise?: Promise<boolean> = undefined;\n\n public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;\n\n constructor({ name, parent, attributes = {}, onError = DEFAULT_ERROR_HANDLER }: CreateContextParams = {}) {\n this.#name = name;\n this.#parent = parent;\n this.#attributes = attributes;\n this.#onError = onError;\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): () => void {\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 });\n }\n\n // Remove handler.\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 * Callbacks are run in the reverse order they were added.\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 async dispose(throwOnError = false): Promise<boolean> {\n if (this.#disposePromise) {\n return this.#disposePromise;\n }\n\n // TODO(burdon): Probably should not be set until the dispose is complete, but causes tests to fail if moved.\n this.#isDisposed = true;\n\n // Set the promise before running the callbacks.\n let resolveDispose!: (value: boolean) => void;\n const promise = new Promise<boolean>((resolve) => {\n resolveDispose = resolve;\n });\n this.#disposePromise = promise;\n\n // Process last first.\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 this.#disposeCallbacks.length = 0;\n\n if (this.#name) {\n log('disposing', { context: this.#name, count: callbacks.length });\n }\n\n let i = 0;\n let clean = true;\n const errors: Error[] = [];\n for (const callback of callbacks) {\n try {\n await callback();\n i++;\n } catch (err: any) {\n clean = false;\n if (throwOnError) {\n errors.push(err);\n } else {\n log.catch(err, { context: this.#name, callback: i, count: callbacks.length });\n }\n }\n }\n\n if (errors.length > 0) {\n throw new AggregateError(errors);\n }\n\n resolveDispose(clean);\n if (this.#name) {\n log('disposed', { context: this.#name });\n }\n\n return clean;\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, this);\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, this);\n } catch {\n this.raise(error);\n }\n }\n },\n attributes,\n });\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) {\n return this.#parent.getAttribute(key);\n }\n\n return undefined;\n }\n\n [Symbol.toStringTag] = 'Context';\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 { throwUnhandledError } from '@dxos/util';\n\nimport { Context } from './context';\n\nexport enum LifecycleState {\n CLOSED = 'CLOSED',\n OPEN = 'OPEN',\n ERROR = 'ERROR',\n}\n\nexport interface Lifecycle {\n open?(ctx?: Context): Promise<any> | any;\n close?(): Promise<any> | any;\n}\n\n// Feature flag to be enabled later.\nconst CLOSE_RESOURCE_ON_UNHANDLED_ERROR = false;\n\n/**\n * Base class for resources that need to be opened and closed.\n */\nexport abstract 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({ name: this.#name });\n\n get #name() {\n return Object.getPrototypeOf(this).constructor.name;\n }\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 if (CLOSE_RESOURCE_ON_UNHANDLED_ERROR) {\n try {\n await this.close();\n } catch (doubleErr: any) {\n throwUnhandledError(doubleErr);\n }\n }\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\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.derive({ name: this.#name });\n }\n await this._open(this.#parentCtx);\n this.#lifecycleState = LifecycleState.OPEN;\n }\n\n async #close(ctx = Context.default()) {\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", "DEFAULT_ERROR_HANDLER", "error", "ctx", "dispose", "Context", "name", "parent", "attributes", "onError", "undefined", "maxSafeDisposeCallbacks", "Symbol", "inspect", "toString", "toStringTag", "custom", "default", "disposed", "disposeCallbacksLength", "length", "onDispose", "callback", "log", "catch", "push", "warn", "count", "index", "indexOf", "splice", "throwOnError", "resolveDispose", "promise", "Promise", "resolve", "callbacks", "Array", "from", "reverse", "context", "i", "clean", "errors", "err", "AggregateError", "raise", "reject", "derive", "newCtx", "clearDispose", "getAttribute", "key", "safeInstanceof", "rejectOnDispose", "cancelWithContext", "race", "finally", "LifecycleState", "CLOSE_RESOURCE_ON_UNHANDLED_ERROR", "Resource", "Object", "getPrototypeOf", "_lifecycleState", "_ctx", "_open", "_close", "_catch", "close", "doubleErr", "throwUnhandledError", "open", "asyncDispose", "queueMicrotask", "openInContext", "resource"]
7
7
  }
@@ -1 +1 @@
1
- {"inputs":{"packages/common/context/src/context-disposed-error.ts":{"bytes":817,"imports":[],"format":"esm"},"packages/common/context/src/context.ts":{"bytes":18959,"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":11744,"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":16013},"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":5313},"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":2979}},"bytes":9212}}}
1
+ {"inputs":{"packages/common/context/src/context-disposed-error.ts":{"bytes":853,"imports":[],"format":"esm"},"packages/common/context/src/context.ts":{"bytes":22853,"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":3096,"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":13662,"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":809,"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":18172},"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":6273},"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":3372}},"bytes":10565}}}
@@ -1,22 +1,18 @@
1
1
  /// <reference types="node" />
2
2
  import { inspect } from 'node:util';
3
- export type ContextErrorHandler = (error: Error) => void;
3
+ export type ContextErrorHandler = (error: Error, ctx: Context) => void;
4
4
  export type DisposeCallback = () => any | Promise<any>;
5
5
  export type CreateContextParams = {
6
- onError?: ContextErrorHandler;
7
- attributes?: Record<string, any>;
6
+ name?: string;
8
7
  parent?: Context;
8
+ attributes?: Record<string, any>;
9
+ onError?: ContextErrorHandler;
9
10
  };
10
11
  export declare class Context {
12
+ #private;
11
13
  static default(): Context;
12
- private readonly _onError;
13
- private readonly _disposeCallbacks;
14
- private _isDisposed;
15
- private _disposePromise?;
16
- private _parent;
17
- private _attributes;
18
14
  maxSafeDisposeCallbacks: number;
19
- constructor({ onError, attributes, parent, }?: CreateContextParams);
15
+ constructor({ name, parent, attributes, onError }?: CreateContextParams);
20
16
  get disposed(): boolean;
21
17
  get disposeCallbacksLength(): number;
22
18
  /**
@@ -36,7 +32,7 @@ export declare class Context {
36
32
  * It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.
37
33
  * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.
38
34
  */
39
- dispose(): Promise<void>;
35
+ dispose(throwOnError?: boolean): Promise<boolean>;
40
36
  /**
41
37
  * Raise the error inside the context.
42
38
  * The error will be propagated to the error handler.
@@ -1 +1 @@
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;;;;;;OAMG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA0B9B;;;;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
+ {"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,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAC;AAEvE,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;AAEvD,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,mBAAmB,CAAC;CAC/B,CAAC;AAkBF,qBACa,OAAO;;IAClB,MAAM,CAAC,OAAO;IAcP,uBAAuB,SAA8B;gBAEhD,EAAE,IAAI,EAAE,MAAM,EAAE,UAAe,EAAE,OAA+B,EAAE,GAAE,mBAAwB;IAOxG,IAAI,QAAQ,YAEX;IAED,IAAI,sBAAsB,WAEzB;IAED;;;;;;;;OAQG;IACH,SAAS,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,IAAI;IA4BhD;;;;;;OAMG;IACG,OAAO,CAAC,YAAY,UAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;IAqDrD;;;;OAIG;IACH,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAezB,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,GAAE,mBAAwB,GAAG,OAAO;IAsBlE,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG;IAW9B,CAAC,MAAM,CAAC,WAAW,CAAC,SAAa;IACjC,CAAC,OAAO,CAAC,MAAM,CAAC,eAAyB;IAEzC,QAAQ;CAGT"}
@@ -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,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"}
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,WAAW,QAAQ,CAAC,CAAC,KAAG,QAAQ,CAAC,CASjF,CAAC"}
@@ -1,4 +1,3 @@
1
- import '@dxos/util';
2
1
  import { Context } from './context';
3
2
  export declare enum LifecycleState {
4
3
  CLOSED = "CLOSED",
@@ -1 +1 @@
1
- {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../../src/resource.ts"],"names":[],"mappings":"AAIA,OAAO,YAAY,CAAC;AAEpB,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,oBAAY,cAAc;IACxB,MAAM,WAAW;IACjB,IAAI,SAAS;IACb,KAAK,UAAU;CAChB;AAED,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;;GAEG;AACH,8BAAsB,QAAS,YAAW,SAAS;;IAkBjD,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;IAexC;;;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"}
1
+ {"version":3,"file":"resource.d.ts","sourceRoot":"","sources":["../../../src/resource.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,oBAAY,cAAc;IACxB,MAAM,WAAW;IACjB,IAAI,SAAS;IACb,KAAK,UAAU;CAChB;AAED,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;AAKD;;GAEG;AACH,8BAAsB,QAAS,YAAW,SAAS;;IAsBjD,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;IAWjD;;;;;;OAMG;IACG,IAAI,CAAC,GAAG,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAexC;;;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,YAAY,CAAC,KAAG,QAAQ,CAAC,CAK7F,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/context",
3
- "version": "0.5.3-main.f752aaa",
3
+ "version": "0.5.3-main.fffc127",
4
4
  "description": "Async utils.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -16,9 +16,9 @@
16
16
  "src"
17
17
  ],
18
18
  "dependencies": {
19
- "@dxos/util": "0.5.3-main.f752aaa",
20
- "@dxos/node-std": "0.5.3-main.f752aaa",
21
- "@dxos/log": "0.5.3-main.f752aaa"
19
+ "@dxos/log": "0.5.3-main.fffc127",
20
+ "@dxos/node-std": "0.5.3-main.fffc127",
21
+ "@dxos/util": "0.5.3-main.fffc127"
22
22
  },
23
23
  "publishConfig": {
24
24
  "access": "public"
package/src/context.ts CHANGED
@@ -9,14 +9,15 @@ import { safeInstanceof } from '@dxos/util';
9
9
 
10
10
  import { ContextDisposedError } from './context-disposed-error';
11
11
 
12
- export type ContextErrorHandler = (error: Error) => void;
12
+ export type ContextErrorHandler = (error: Error, ctx: Context) => void;
13
13
 
14
14
  export type DisposeCallback = () => any | Promise<any>;
15
15
 
16
16
  export type CreateContextParams = {
17
- onError?: ContextErrorHandler;
18
- attributes?: Record<string, any>;
17
+ name?: string;
19
18
  parent?: Context;
19
+ attributes?: Record<string, any>;
20
+ onError?: ContextErrorHandler;
20
21
  };
21
22
 
22
23
  /**
@@ -24,49 +25,48 @@ export type CreateContextParams = {
24
25
  */
25
26
  const MAX_SAFE_DISPOSE_CALLBACKS = 300;
26
27
 
28
+ const DEFAULT_ERROR_HANDLER: ContextErrorHandler = (error, ctx) => {
29
+ if (error instanceof ContextDisposedError) {
30
+ return;
31
+ }
32
+
33
+ void ctx.dispose();
34
+
35
+ // Will generate an unhandled rejection.
36
+ throw error;
37
+ };
38
+
27
39
  @safeInstanceof('Context')
28
40
  export class Context {
29
41
  static default() {
30
42
  return new Context();
31
43
  }
32
44
 
33
- private readonly _onError: ContextErrorHandler;
34
- private readonly _disposeCallbacks: DisposeCallback[] = [];
35
- private _isDisposed = false;
36
- private _disposePromise?: Promise<void> = undefined;
37
- private _parent: Context | null = null;
45
+ readonly #disposeCallbacks: DisposeCallback[] = [];
38
46
 
39
- private _attributes: Record<string, any>;
47
+ readonly #name?: string = undefined;
48
+ readonly #parent?: Context = undefined;
49
+ readonly #attributes: Record<string, any>;
50
+ readonly #onError: ContextErrorHandler;
40
51
 
41
- public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
52
+ #isDisposed = false;
53
+ #disposePromise?: Promise<boolean> = undefined;
42
54
 
43
- constructor({
44
- onError = (error) => {
45
- if (error instanceof ContextDisposedError) {
46
- return;
47
- }
55
+ public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
48
56
 
49
- void this.dispose();
50
-
51
- // Will generate an unhandled rejection.
52
- throw error;
53
- },
54
- attributes = {},
55
- parent,
56
- }: CreateContextParams = {}) {
57
- this._onError = onError;
58
- this._attributes = attributes;
59
- if (parent !== undefined) {
60
- this._parent = parent;
61
- }
57
+ constructor({ name, parent, attributes = {}, onError = DEFAULT_ERROR_HANDLER }: CreateContextParams = {}) {
58
+ this.#name = name;
59
+ this.#parent = parent;
60
+ this.#attributes = attributes;
61
+ this.#onError = onError;
62
62
  }
63
63
 
64
64
  get disposed() {
65
- return this._isDisposed;
65
+ return this.#isDisposed;
66
66
  }
67
67
 
68
68
  get disposeCallbacksLength() {
69
- return this._disposeCallbacks.length;
69
+ return this.#disposeCallbacks.length;
70
70
  }
71
71
 
72
72
  /**
@@ -78,8 +78,8 @@ export class Context {
78
78
  *
79
79
  * @returns A function that can be used to remove the callback from the dispose list.
80
80
  */
81
- onDispose(callback: DisposeCallback) {
82
- if (this._isDisposed) {
81
+ onDispose(callback: DisposeCallback): () => void {
82
+ if (this.#isDisposed) {
83
83
  // Call the callback immediately if the context is already disposed.
84
84
  void (async () => {
85
85
  try {
@@ -90,18 +90,18 @@ export class Context {
90
90
  })();
91
91
  }
92
92
 
93
- this._disposeCallbacks.push(callback);
94
- if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
95
- log.warn('Context has a large number of dispose callbacks. This might be a memory leak.', {
96
- count: this._disposeCallbacks.length,
97
- safeThreshold: this.maxSafeDisposeCallbacks,
93
+ this.#disposeCallbacks.push(callback);
94
+ if (this.#disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
95
+ log.warn('Context has a large number of dispose callbacks (this might be a memory leak).', {
96
+ count: this.#disposeCallbacks.length,
98
97
  });
99
98
  }
100
99
 
100
+ // Remove handler.
101
101
  return () => {
102
- const index = this._disposeCallbacks.indexOf(callback);
102
+ const index = this.#disposeCallbacks.indexOf(callback);
103
103
  if (index !== -1) {
104
- this._disposeCallbacks.splice(index, 1);
104
+ this.#disposeCallbacks.splice(index, 1);
105
105
  }
106
106
  };
107
107
  }
@@ -113,30 +113,57 @@ export class Context {
113
113
  * It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.
114
114
  * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.
115
115
  */
116
- async dispose(): Promise<void> {
117
- if (this._disposePromise) {
118
- return this._disposePromise;
116
+ async dispose(throwOnError = false): Promise<boolean> {
117
+ if (this.#disposePromise) {
118
+ return this.#disposePromise;
119
119
  }
120
- this._isDisposed = true;
120
+
121
+ // TODO(burdon): Probably should not be set until the dispose is complete, but causes tests to fail if moved.
122
+ this.#isDisposed = true;
121
123
 
122
124
  // Set the promise before running the callbacks.
123
- let resolveDispose!: () => void;
124
- this._disposePromise = new Promise<void>((resolve) => {
125
+ let resolveDispose!: (value: boolean) => void;
126
+ const promise = new Promise<boolean>((resolve) => {
125
127
  resolveDispose = resolve;
126
128
  });
129
+ this.#disposePromise = promise;
127
130
 
131
+ // Process last first.
128
132
  // Clone the array so that any mutations to the original array don't affect the dispose process.
129
- const callbacks = Array.from(this._disposeCallbacks).reverse();
130
- this._disposeCallbacks.length = 0;
133
+ const callbacks = Array.from(this.#disposeCallbacks).reverse();
134
+ this.#disposeCallbacks.length = 0;
135
+
136
+ if (this.#name) {
137
+ log('disposing', { context: this.#name, count: callbacks.length });
138
+ }
131
139
 
140
+ let i = 0;
141
+ let clean = true;
142
+ const errors: Error[] = [];
132
143
  for (const callback of callbacks) {
133
144
  try {
134
145
  await callback();
135
- } catch (error: any) {
136
- log.catch(error);
146
+ i++;
147
+ } catch (err: any) {
148
+ clean = false;
149
+ if (throwOnError) {
150
+ errors.push(err);
151
+ } else {
152
+ log.catch(err, { context: this.#name, callback: i, count: callbacks.length });
153
+ }
137
154
  }
138
155
  }
139
- resolveDispose();
156
+
157
+ if (errors.length > 0) {
158
+ throw new AggregateError(errors);
159
+ }
160
+
161
+ resolveDispose(clean);
162
+ if (this.#name) {
163
+ log('disposed', { context: this.#name });
164
+ }
165
+
166
+ return clean;
140
167
  }
141
168
 
142
169
  /**
@@ -145,14 +172,14 @@ export class Context {
145
172
  * IF the error handler is not set, the error will dispose the context and cause an unhandled rejection.
146
173
  */
147
174
  raise(error: Error): void {
148
- if (this._isDisposed) {
175
+ if (this.#isDisposed) {
149
176
  // TODO(dmaretskyi): Don't log those.
150
177
  // log.warn('Error in disposed context', error);
151
178
  return;
152
179
  }
153
180
 
154
181
  try {
155
- this._onError(error);
182
+ this.#onError(error, this);
156
183
  } catch (err) {
157
184
  // Generate an unhandled rejection and stop the error propagation.
158
185
  void Promise.reject(err);
@@ -167,7 +194,7 @@ export class Context {
167
194
  this.raise(error);
168
195
  } else {
169
196
  try {
170
- await onError(error);
197
+ await onError(error, this);
171
198
  } catch {
172
199
  this.raise(error);
173
200
  }
@@ -175,26 +202,27 @@ export class Context {
175
202
  },
176
203
  attributes,
177
204
  });
205
+
178
206
  const clearDispose = this.onDispose(() => newCtx.dispose());
179
207
  newCtx.onDispose(clearDispose);
180
208
  return newCtx;
181
209
  }
182
210
 
183
211
  getAttribute(key: string): any {
184
- if (key in this._attributes) {
185
- return this._attributes[key];
212
+ if (key in this.#attributes) {
213
+ return this.#attributes[key];
186
214
  }
187
- if (this._parent !== null) {
188
- return this._parent.getAttribute(key);
215
+ if (this.#parent) {
216
+ return this.#parent.getAttribute(key);
189
217
  }
218
+
190
219
  return undefined;
191
220
  }
192
221
 
193
222
  [Symbol.toStringTag] = 'Context';
194
-
195
223
  [inspect.custom] = () => this.toString();
196
224
 
197
225
  toString() {
198
- return `Context(${this._isDisposed ? 'disposed' : 'active'})`;
226
+ return `Context(${this.#isDisposed ? 'disposed' : 'active'})`;
199
227
  }
200
228
  }
package/src/resource.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- import '@dxos/util';
5
+ import { throwUnhandledError } from '@dxos/util';
6
6
 
7
7
  import { Context } from './context';
8
8
 
@@ -17,6 +17,9 @@ export interface Lifecycle {
17
17
  close?(): Promise<any> | any;
18
18
  }
19
19
 
20
+ // Feature flag to be enabled later.
21
+ const CLOSE_RESOURCE_ON_UNHANDLED_ERROR = false;
22
+
20
23
  /**
21
24
  * Base class for resources that need to be opened and closed.
22
25
  */
@@ -36,7 +39,11 @@ export abstract class Resource implements Lifecycle {
36
39
  * Context that is used to bubble up errors that are not handled by the resource.
37
40
  * Provided in the open method.
38
41
  */
39
- #parentCtx: Context = new Context();
42
+ #parentCtx: Context = new Context({ name: this.#name });
43
+
44
+ get #name() {
45
+ return Object.getPrototypeOf(this).constructor.name;
46
+ }
40
47
 
41
48
  protected get _lifecycleState() {
42
49
  return this.#lifecycleState;
@@ -61,6 +68,13 @@ export abstract class Resource implements Lifecycle {
61
68
  * By default, errors are bubbled up to the parent context which is passed to the open method.
62
69
  */
63
70
  protected async _catch(err: Error): Promise<void> {
71
+ if (CLOSE_RESOURCE_ON_UNHANDLED_ERROR) {
72
+ try {
73
+ await this.close();
74
+ } catch (doubleErr: any) {
75
+ throwUnhandledError(doubleErr);
76
+ }
77
+ }
64
78
  throw err;
65
79
  }
66
80
 
@@ -107,13 +121,13 @@ export abstract class Resource implements Lifecycle {
107
121
  async #open(ctx?: Context) {
108
122
  this.#closePromise = null;
109
123
  if (ctx) {
110
- this.#parentCtx = ctx;
124
+ this.#parentCtx = ctx.derive({ name: this.#name });
111
125
  }
112
126
  await this._open(this.#parentCtx);
113
127
  this.#lifecycleState = LifecycleState.OPEN;
114
128
  }
115
129
 
116
- async #close(ctx = new Context()) {
130
+ async #close(ctx = Context.default()) {
117
131
  this.#openPromise = null;
118
132
  await this.#internalCtx.dispose();
119
133
  await this._close(ctx);