@dxos/context 0.5.3-next.57eca40 → 0.5.3

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,25 +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({ name, parent, attributes = {}, onError = (error) => {
29
- if (error instanceof ContextDisposedError) {
30
- return;
31
- }
32
- void this.dispose();
33
- throw error;
34
- } } = {}) {
35
- this._disposeCallbacks = [];
36
- this._isDisposed = false;
37
- this._disposePromise = void 0;
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;
38
41
  this.maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
39
42
  this[_a] = "Context";
40
43
  this[_b] = () => this.toString();
41
- this._name = name;
42
- this._parent = parent;
43
- this._attributes = attributes;
44
- this._onError = onError;
44
+ this.#name = name;
45
+ this.#parent = parent;
46
+ this.#attributes = attributes;
47
+ this.#onError = onError;
45
48
  }
46
49
  static {
47
50
  _a = Symbol.toStringTag, _b = inspect.custom;
@@ -49,11 +52,18 @@ var Context = class _Context {
49
52
  static default() {
50
53
  return new _Context();
51
54
  }
55
+ #disposeCallbacks;
56
+ #name;
57
+ #parent;
58
+ #attributes;
59
+ #onError;
60
+ #isDisposed;
61
+ #disposePromise;
52
62
  get disposed() {
53
- return this._isDisposed;
63
+ return this.#isDisposed;
54
64
  }
55
65
  get disposeCallbacksLength() {
56
- return this._disposeCallbacks.length;
66
+ return this.#disposeCallbacks.length;
57
67
  }
58
68
  /**
59
69
  * Schedules a callback to run when the context is disposed.
@@ -65,35 +75,35 @@ var Context = class _Context {
65
75
  * @returns A function that can be used to remove the callback from the dispose list.
66
76
  */
67
77
  onDispose(callback) {
68
- if (this._isDisposed) {
78
+ if (this.#isDisposed) {
69
79
  void (async () => {
70
80
  try {
71
81
  await callback();
72
82
  } catch (error) {
73
83
  log.catch(error, void 0, {
74
84
  F: __dxlog_file,
75
- L: 91,
85
+ L: 88,
76
86
  S: this,
77
87
  C: (f, a) => f(...a)
78
88
  });
79
89
  }
80
90
  })();
81
91
  }
82
- this._disposeCallbacks.push(callback);
83
- if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
92
+ this.#disposeCallbacks.push(callback);
93
+ if (this.#disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
84
94
  log.warn("Context has a large number of dispose callbacks (this might be a memory leak).", {
85
- count: this._disposeCallbacks.length
95
+ count: this.#disposeCallbacks.length
86
96
  }, {
87
97
  F: __dxlog_file,
88
- L: 98,
98
+ L: 95,
89
99
  S: this,
90
100
  C: (f, a) => f(...a)
91
101
  });
92
102
  }
93
103
  return () => {
94
- const index = this._disposeCallbacks.indexOf(callback);
104
+ const index = this.#disposeCallbacks.indexOf(callback);
95
105
  if (index !== -1) {
96
- this._disposeCallbacks.splice(index, 1);
106
+ this.#disposeCallbacks.splice(index, 1);
97
107
  }
98
108
  };
99
109
  }
@@ -105,57 +115,63 @@ var Context = class _Context {
105
115
  * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.
106
116
  */
107
117
  async dispose(throwOnError = false) {
108
- if (this._disposePromise) {
109
- return this._disposePromise;
118
+ if (this.#disposePromise) {
119
+ return this.#disposePromise;
110
120
  }
111
- this._isDisposed = true;
121
+ this.#isDisposed = true;
112
122
  let resolveDispose;
113
- this._disposePromise = new Promise((resolve) => {
123
+ const promise = new Promise((resolve) => {
114
124
  resolveDispose = resolve;
115
125
  });
116
- const callbacks = Array.from(this._disposeCallbacks).reverse();
117
- this._disposeCallbacks.length = 0;
118
- if (this._name) {
126
+ this.#disposePromise = promise;
127
+ const callbacks = Array.from(this.#disposeCallbacks).reverse();
128
+ this.#disposeCallbacks.length = 0;
129
+ if (this.#name) {
119
130
  log("disposing", {
120
- context: this._name,
131
+ context: this.#name,
121
132
  count: callbacks.length
122
133
  }, {
123
134
  F: __dxlog_file,
124
- L: 139,
135
+ L: 137,
125
136
  S: this,
126
137
  C: (f, a) => f(...a)
127
138
  });
128
139
  }
129
140
  let i = 0;
130
141
  let clean = true;
142
+ const errors = [];
131
143
  for (const callback of callbacks) {
132
144
  try {
133
145
  await callback();
134
146
  i++;
135
147
  } catch (err) {
136
- log.catch(err, {
137
- context: this._name,
138
- callback: i,
139
- count: callbacks.length
140
- }, {
141
- F: __dxlog_file,
142
- L: 149,
143
- S: this,
144
- C: (f, a) => f(...a)
145
- });
146
148
  clean = false;
147
149
  if (throwOnError) {
148
- throw err;
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
+ });
149
162
  }
150
163
  }
151
164
  }
165
+ if (errors.length > 0) {
166
+ throw new AggregateError(errors);
167
+ }
152
168
  resolveDispose(clean);
153
- if (this._name) {
169
+ if (this.#name) {
154
170
  log("disposed", {
155
- context: this._name
171
+ context: this.#name
156
172
  }, {
157
173
  F: __dxlog_file,
158
- L: 159,
174
+ L: 163,
159
175
  S: this,
160
176
  C: (f, a) => f(...a)
161
177
  });
@@ -168,11 +184,11 @@ var Context = class _Context {
168
184
  * IF the error handler is not set, the error will dispose the context and cause an unhandled rejection.
169
185
  */
170
186
  raise(error) {
171
- if (this._isDisposed) {
187
+ if (this.#isDisposed) {
172
188
  return;
173
189
  }
174
190
  try {
175
- this._onError(error);
191
+ this.#onError(error, this);
176
192
  } catch (err) {
177
193
  void Promise.reject(err);
178
194
  }
@@ -185,7 +201,7 @@ var Context = class _Context {
185
201
  this.raise(error);
186
202
  } else {
187
203
  try {
188
- await onError(error);
204
+ await onError(error, this);
189
205
  } catch {
190
206
  this.raise(error);
191
207
  }
@@ -198,16 +214,16 @@ var Context = class _Context {
198
214
  return newCtx;
199
215
  }
200
216
  getAttribute(key) {
201
- if (key in this._attributes) {
202
- return this._attributes[key];
217
+ if (key in this.#attributes) {
218
+ return this.#attributes[key];
203
219
  }
204
- if (this._parent) {
205
- return this._parent.getAttribute(key);
220
+ if (this.#parent) {
221
+ return this.#parent.getAttribute(key);
206
222
  }
207
223
  return void 0;
208
224
  }
209
225
  toString() {
210
- return `Context(${this._isDisposed ? "disposed" : "active"})`;
226
+ return `Context(${this.#isDisposed ? "disposed" : "active"})`;
211
227
  }
212
228
  };
213
229
  Context = _ts_decorate([
@@ -229,13 +245,14 @@ var cancelWithContext = (ctx, promise) => {
229
245
  };
230
246
 
231
247
  // packages/common/context/src/resource.ts
232
- import "@dxos/util";
248
+ import { throwUnhandledError } from "@dxos/util";
233
249
  var LifecycleState;
234
250
  (function(LifecycleState2) {
235
251
  LifecycleState2["CLOSED"] = "CLOSED";
236
252
  LifecycleState2["OPEN"] = "OPEN";
237
253
  LifecycleState2["ERROR"] = "ERROR";
238
254
  })(LifecycleState || (LifecycleState = {}));
255
+ var CLOSE_RESOURCE_ON_UNHANDLED_ERROR = false;
239
256
  var Resource = class {
240
257
  #lifecycleState = "CLOSED";
241
258
  #openPromise = null;
@@ -250,7 +267,12 @@ var Resource = class {
250
267
  * Context that is used to bubble up errors that are not handled by the resource.
251
268
  * Provided in the open method.
252
269
  */
253
- #parentCtx = new Context();
270
+ #parentCtx = new Context({
271
+ name: this.#name
272
+ });
273
+ get #name() {
274
+ return Object.getPrototypeOf(this).constructor.name;
275
+ }
254
276
  get _lifecycleState() {
255
277
  return this.#lifecycleState;
256
278
  }
@@ -272,6 +294,13 @@ var Resource = class {
272
294
  * By default, errors are bubbled up to the parent context which is passed to the open method.
273
295
  */
274
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
+ }
275
304
  throw err;
276
305
  }
277
306
  /**
@@ -311,12 +340,14 @@ var Resource = class {
311
340
  async #open(ctx) {
312
341
  this.#closePromise = null;
313
342
  if (ctx) {
314
- this.#parentCtx = ctx;
343
+ this.#parentCtx = ctx.derive({
344
+ name: this.#name
345
+ });
315
346
  }
316
347
  await this._open(this.#parentCtx);
317
348
  this.#lifecycleState = "OPEN";
318
349
  }
319
- async #close(ctx = new Context()) {
350
+ async #close(ctx = Context.default()) {
320
351
  this.#openPromise = null;
321
352
  await this.#internalCtx.dispose();
322
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 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\n@safeInstanceof('Context')\nexport class Context {\n static default() {\n return new Context();\n }\n\n private readonly _disposeCallbacks: DisposeCallback[] = [];\n\n private readonly _name?: string;\n private readonly _parent?: Context;\n private readonly _attributes: Record<string, any>;\n private readonly _onError: ContextErrorHandler;\n\n private _isDisposed = false;\n private _disposePromise?: Promise<boolean> = undefined;\n\n public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;\n\n constructor({\n name, // TODO(burdon): Automate?\n parent,\n attributes = {},\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 }: 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 this._disposePromise = new Promise<boolean>((resolve) => {\n resolveDispose = resolve;\n });\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 for (const callback of callbacks) {\n try {\n await callback();\n i++;\n } catch (err: any) {\n log.catch(err, { context: this._name, callback: i, count: callbacks.length });\n clean = false;\n if (throwOnError) {\n throw err;\n }\n }\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);\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\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 '@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", "name", "parent", "attributes", "onError", "error", "ContextDisposedError", "dispose", "_disposeCallbacks", "_isDisposed", "_disposePromise", "undefined", "maxSafeDisposeCallbacks", "Symbol", "inspect", "toString", "_name", "_parent", "_attributes", "_onError", "toStringTag", "custom", "default", "disposed", "disposeCallbacksLength", "length", "onDispose", "callback", "log", "catch", "push", "warn", "count", "index", "indexOf", "splice", "throwOnError", "resolveDispose", "Promise", "resolve", "callbacks", "Array", "from", "reverse", "context", "i", "clean", "err", "raise", "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":21471,"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":17016},"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":5903},"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":9802}}}
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,25 +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({ name, parent, attributes = {}, onError = (error) => {
54
- if (error instanceof ContextDisposedError) {
55
- return;
56
- }
57
- void this.dispose();
58
- throw error;
59
- } } = {}) {
60
- this._disposeCallbacks = [];
61
- this._isDisposed = false;
62
- this._disposePromise = void 0;
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;
63
66
  this.maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
64
67
  this[_a] = "Context";
65
68
  this[_b] = () => this.toString();
66
- this._name = name;
67
- this._parent = parent;
68
- this._attributes = attributes;
69
- this._onError = onError;
69
+ this.#name = name;
70
+ this.#parent = parent;
71
+ this.#attributes = attributes;
72
+ this.#onError = onError;
70
73
  }
71
74
  static {
72
75
  _a = Symbol.toStringTag, _b = import_node_util.inspect.custom;
@@ -74,11 +77,18 @@ var Context = class _Context {
74
77
  static default() {
75
78
  return new _Context();
76
79
  }
80
+ #disposeCallbacks;
81
+ #name;
82
+ #parent;
83
+ #attributes;
84
+ #onError;
85
+ #isDisposed;
86
+ #disposePromise;
77
87
  get disposed() {
78
- return this._isDisposed;
88
+ return this.#isDisposed;
79
89
  }
80
90
  get disposeCallbacksLength() {
81
- return this._disposeCallbacks.length;
91
+ return this.#disposeCallbacks.length;
82
92
  }
83
93
  /**
84
94
  * Schedules a callback to run when the context is disposed.
@@ -90,35 +100,35 @@ var Context = class _Context {
90
100
  * @returns A function that can be used to remove the callback from the dispose list.
91
101
  */
92
102
  onDispose(callback) {
93
- if (this._isDisposed) {
103
+ if (this.#isDisposed) {
94
104
  void (async () => {
95
105
  try {
96
106
  await callback();
97
107
  } catch (error) {
98
108
  import_log.log.catch(error, void 0, {
99
109
  F: __dxlog_file,
100
- L: 91,
110
+ L: 88,
101
111
  S: this,
102
112
  C: (f, a) => f(...a)
103
113
  });
104
114
  }
105
115
  })();
106
116
  }
107
- this._disposeCallbacks.push(callback);
108
- if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
117
+ this.#disposeCallbacks.push(callback);
118
+ if (this.#disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
109
119
  import_log.log.warn("Context has a large number of dispose callbacks (this might be a memory leak).", {
110
- count: this._disposeCallbacks.length
120
+ count: this.#disposeCallbacks.length
111
121
  }, {
112
122
  F: __dxlog_file,
113
- L: 98,
123
+ L: 95,
114
124
  S: this,
115
125
  C: (f, a) => f(...a)
116
126
  });
117
127
  }
118
128
  return () => {
119
- const index = this._disposeCallbacks.indexOf(callback);
129
+ const index = this.#disposeCallbacks.indexOf(callback);
120
130
  if (index !== -1) {
121
- this._disposeCallbacks.splice(index, 1);
131
+ this.#disposeCallbacks.splice(index, 1);
122
132
  }
123
133
  };
124
134
  }
@@ -130,57 +140,63 @@ var Context = class _Context {
130
140
  * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.
131
141
  */
132
142
  async dispose(throwOnError = false) {
133
- if (this._disposePromise) {
134
- return this._disposePromise;
143
+ if (this.#disposePromise) {
144
+ return this.#disposePromise;
135
145
  }
136
- this._isDisposed = true;
146
+ this.#isDisposed = true;
137
147
  let resolveDispose;
138
- this._disposePromise = new Promise((resolve) => {
148
+ const promise = new Promise((resolve) => {
139
149
  resolveDispose = resolve;
140
150
  });
141
- const callbacks = Array.from(this._disposeCallbacks).reverse();
142
- this._disposeCallbacks.length = 0;
143
- if (this._name) {
151
+ this.#disposePromise = promise;
152
+ const callbacks = Array.from(this.#disposeCallbacks).reverse();
153
+ this.#disposeCallbacks.length = 0;
154
+ if (this.#name) {
144
155
  (0, import_log.log)("disposing", {
145
- context: this._name,
156
+ context: this.#name,
146
157
  count: callbacks.length
147
158
  }, {
148
159
  F: __dxlog_file,
149
- L: 139,
160
+ L: 137,
150
161
  S: this,
151
162
  C: (f, a) => f(...a)
152
163
  });
153
164
  }
154
165
  let i = 0;
155
166
  let clean = true;
167
+ const errors = [];
156
168
  for (const callback of callbacks) {
157
169
  try {
158
170
  await callback();
159
171
  i++;
160
172
  } catch (err) {
161
- import_log.log.catch(err, {
162
- context: this._name,
163
- callback: i,
164
- count: callbacks.length
165
- }, {
166
- F: __dxlog_file,
167
- L: 149,
168
- S: this,
169
- C: (f, a) => f(...a)
170
- });
171
173
  clean = false;
172
174
  if (throwOnError) {
173
- throw err;
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
+ });
174
187
  }
175
188
  }
176
189
  }
190
+ if (errors.length > 0) {
191
+ throw new AggregateError(errors);
192
+ }
177
193
  resolveDispose(clean);
178
- if (this._name) {
194
+ if (this.#name) {
179
195
  (0, import_log.log)("disposed", {
180
- context: this._name
196
+ context: this.#name
181
197
  }, {
182
198
  F: __dxlog_file,
183
- L: 159,
199
+ L: 163,
184
200
  S: this,
185
201
  C: (f, a) => f(...a)
186
202
  });
@@ -193,11 +209,11 @@ var Context = class _Context {
193
209
  * IF the error handler is not set, the error will dispose the context and cause an unhandled rejection.
194
210
  */
195
211
  raise(error) {
196
- if (this._isDisposed) {
212
+ if (this.#isDisposed) {
197
213
  return;
198
214
  }
199
215
  try {
200
- this._onError(error);
216
+ this.#onError(error, this);
201
217
  } catch (err) {
202
218
  void Promise.reject(err);
203
219
  }
@@ -210,7 +226,7 @@ var Context = class _Context {
210
226
  this.raise(error);
211
227
  } else {
212
228
  try {
213
- await onError(error);
229
+ await onError(error, this);
214
230
  } catch {
215
231
  this.raise(error);
216
232
  }
@@ -223,16 +239,16 @@ var Context = class _Context {
223
239
  return newCtx;
224
240
  }
225
241
  getAttribute(key) {
226
- if (key in this._attributes) {
227
- return this._attributes[key];
242
+ if (key in this.#attributes) {
243
+ return this.#attributes[key];
228
244
  }
229
- if (this._parent) {
230
- return this._parent.getAttribute(key);
245
+ if (this.#parent) {
246
+ return this.#parent.getAttribute(key);
231
247
  }
232
248
  return void 0;
233
249
  }
234
250
  toString() {
235
- return `Context(${this._isDisposed ? "disposed" : "active"})`;
251
+ return `Context(${this.#isDisposed ? "disposed" : "active"})`;
236
252
  }
237
253
  };
238
254
  Context = _ts_decorate([
@@ -256,6 +272,7 @@ var LifecycleState;
256
272
  LifecycleState2["OPEN"] = "OPEN";
257
273
  LifecycleState2["ERROR"] = "ERROR";
258
274
  })(LifecycleState || (LifecycleState = {}));
275
+ var CLOSE_RESOURCE_ON_UNHANDLED_ERROR = false;
259
276
  var Resource = class {
260
277
  #lifecycleState = "CLOSED";
261
278
  #openPromise = null;
@@ -270,7 +287,12 @@ var Resource = class {
270
287
  * Context that is used to bubble up errors that are not handled by the resource.
271
288
  * Provided in the open method.
272
289
  */
273
- #parentCtx = new Context();
290
+ #parentCtx = new Context({
291
+ name: this.#name
292
+ });
293
+ get #name() {
294
+ return Object.getPrototypeOf(this).constructor.name;
295
+ }
274
296
  get _lifecycleState() {
275
297
  return this.#lifecycleState;
276
298
  }
@@ -292,6 +314,13 @@ var Resource = class {
292
314
  * By default, errors are bubbled up to the parent context which is passed to the open method.
293
315
  */
294
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
+ }
295
324
  throw err;
296
325
  }
297
326
  /**
@@ -331,12 +360,14 @@ var Resource = class {
331
360
  async #open(ctx) {
332
361
  this.#closePromise = null;
333
362
  if (ctx) {
334
- this.#parentCtx = ctx;
363
+ this.#parentCtx = ctx.derive({
364
+ name: this.#name
365
+ });
335
366
  }
336
367
  await this._open(this.#parentCtx);
337
368
  this.#lifecycleState = "OPEN";
338
369
  }
339
- async #close(ctx = new Context()) {
370
+ async #close(ctx = Context.default()) {
340
371
  this.#openPromise = null;
341
372
  await this.#internalCtx.dispose();
342
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 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\n@safeInstanceof('Context')\nexport class Context {\n static default() {\n return new Context();\n }\n\n private readonly _disposeCallbacks: DisposeCallback[] = [];\n\n private readonly _name?: string;\n private readonly _parent?: Context;\n private readonly _attributes: Record<string, any>;\n private readonly _onError: ContextErrorHandler;\n\n private _isDisposed = false;\n private _disposePromise?: Promise<boolean> = undefined;\n\n public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;\n\n constructor({\n name, // TODO(burdon): Automate?\n parent,\n attributes = {},\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 }: 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 this._disposePromise = new Promise<boolean>((resolve) => {\n resolveDispose = resolve;\n });\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 for (const callback of callbacks) {\n try {\n await callback();\n i++;\n } catch (err: any) {\n log.catch(err, { context: this._name, callback: i, count: callbacks.length });\n clean = false;\n if (throwOnError) {\n throw err;\n }\n }\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);\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\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 '@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", "name", "parent", "attributes", "onError", "error", "dispose", "_disposeCallbacks", "_isDisposed", "_disposePromise", "undefined", "maxSafeDisposeCallbacks", "Symbol", "inspect", "toString", "_name", "_parent", "_attributes", "_onError", "toStringTag", "custom", "default", "disposed", "disposeCallbacksLength", "length", "onDispose", "callback", "log", "catch", "push", "warn", "count", "index", "indexOf", "splice", "throwOnError", "resolveDispose", "Promise", "resolve", "callbacks", "Array", "from", "reverse", "context", "i", "clean", "err", "raise", "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": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIA,uBAAwB;AAExB,iBAAoB;AACpB,kBAA+B;AGH/B,IAAAA,eAAoC;AFA7B,IAAMC,uBAAN,cAAmCC,MAAAA;EACxCC,cAAc;AACZ,UAAM,mBAAA;EACR;AACF;;;;;;;;;;;;ADiBA,IAAMC,6BAA6B;AAEnC,IAAMC,wBAA6C,CAACC,OAAOC,QAAAA;AACzD,MAAID,iBAAiBL,sBAAsB;AACzC;EACF;AAEA,OAAKM,IAAIC,QAAO;AAGhB,QAAMF;AACR;AApCA,IAAA,IAAA;AAuCO,IAAMG,UAAN,MAAMA,SAAAA;EAiBXN,YAAY,EAAEO,MAAMC,QAAQC,aAAa,CAAC,GAAGC,UAAUR,sBAAqB,IAA0B,CAAC,GAAG;AAZjG,SAAA,oBAAuC,CAAA;AAEvC,SAAA,QAAiBS;AACjB,SAAA,UAAoBA;AAI7B,SAAA,cAAc;AACd,SAAA,kBAAqCA;AAE9BC,SAAAA,0BAA0BX;AAuKjC,SAACY,EAAAA,IAAsB;AACvB,SAACC,EAAAA,IAAkB,MAAM,KAAKC,SAAQ;AArKpC,SAAK,QAAQR;AACb,SAAK,UAAUC;AACf,SAAK,cAAcC;AACnB,SAAK,WAAWC;EAClB;EAgKCG,OAAAA;gBAAOG,aACPF,KAAAA,yBAAQG;;EAtLT,OAAOC,UAAU;AACf,WAAO,IAAIZ,SAAAA;EACb;EAES;EAEA;EACA;EACA;EACA;EAET;EACA;EAWA,IAAIa,WAAW;AACb,WAAO,KAAK;EACd;EAEA,IAAIC,yBAAyB;AAC3B,WAAO,KAAK,kBAAkBC;EAChC;;;;;;;;;;EAWAC,UAAUC,UAAuC;AAC/C,QAAI,KAAK,aAAa;AAEpB,YAAM,YAAA;AACJ,YAAI;AACF,gBAAMA,SAAAA;QACR,SAASpB,OAAY;AACnBqB,yBAAIC,MAAMtB,OAAAA,QAAAA;;;;;;QACZ;MACF,GAAA;IACF;AAEA,SAAK,kBAAkBuB,KAAKH,QAAAA;AAC5B,QAAI,KAAK,kBAAkBF,SAAS,KAAKT,yBAAyB;AAChEY,qBAAIG,KAAK,kFAAkF;QACzFC,OAAO,KAAK,kBAAkBP;MAChC,GAAA;;;;;;IACF;AAGA,WAAO,MAAA;AACL,YAAMQ,QAAQ,KAAK,kBAAkBC,QAAQP,QAAAA;AAC7C,UAAIM,UAAU,IAAI;AAChB,aAAK,kBAAkBE,OAAOF,OAAO,CAAA;MACvC;IACF;EACF;;;;;;;;EASA,MAAMxB,QAAQ2B,eAAe,OAAyB;AACpD,QAAI,KAAK,iBAAiB;AACxB,aAAO,KAAK;IACd;AAGA,SAAK,cAAc;AAGnB,QAAIC;AACJ,UAAMC,UAAU,IAAIC,QAAiB,CAACC,YAAAA;AACpCH,uBAAiBG;IACnB,CAAA;AACA,SAAK,kBAAkBF;AAIvB,UAAMG,YAAYC,MAAMC,KAAK,KAAK,iBAAiB,EAAEC,QAAO;AAC5D,SAAK,kBAAkBnB,SAAS;AAEhC,QAAI,KAAK,OAAO;AACdG,0BAAI,aAAa;QAAEiB,SAAS,KAAK;QAAOb,OAAOS,UAAUhB;MAAO,GAAA;;;;;;IAClE;AAEA,QAAIqB,IAAI;AACR,QAAIC,QAAQ;AACZ,UAAMC,SAAkB,CAAA;AACxB,eAAWrB,YAAYc,WAAW;AAChC,UAAI;AACF,cAAMd,SAAAA;AACNmB;MACF,SAASG,KAAU;AACjBF,gBAAQ;AACR,YAAIX,cAAc;AAChBY,iBAAOlB,KAAKmB,GAAAA;QACd,OAAO;AACLrB,yBAAIC,MAAMoB,KAAK;YAAEJ,SAAS,KAAK;YAAOlB,UAAUmB;YAAGd,OAAOS,UAAUhB;UAAO,GAAA;;;;;;QAC7E;MACF;IACF;AAEA,QAAIuB,OAAOvB,SAAS,GAAG;AACrB,YAAM,IAAIyB,eAAeF,MAAAA;IAC3B;AAEAX,mBAAeU,KAAAA;AACf,QAAI,KAAK,OAAO;AACdnB,0BAAI,YAAY;QAAEiB,SAAS,KAAK;MAAM,GAAA;;;;;;IACxC;AAEA,WAAOE;EACT;;;;;;EAOAI,MAAM5C,OAAoB;AACxB,QAAI,KAAK,aAAa;AAGpB;IACF;AAEA,QAAI;AACF,WAAK,SAASA,OAAO,IAAI;IAC3B,SAAS0C,KAAK;AAEZ,WAAKV,QAAQa,OAAOH,GAAAA;IACtB;EACF;EAEAI,OAAO,EAAEvC,SAASD,WAAU,IAA0B,CAAC,GAAY;AACjE,UAAMyC,SAAS,IAAI5C,SAAQ;;MAEzBI,SAAS,OAAOP,UAAAA;AACd,YAAI,CAACO,SAAS;AACZ,eAAKqC,MAAM5C,KAAAA;QACb,OAAO;AACL,cAAI;AACF,kBAAMO,QAAQP,OAAO,IAAI;UAC3B,QAAQ;AACN,iBAAK4C,MAAM5C,KAAAA;UACb;QACF;MACF;MACAM;IACF,CAAA;AAEA,UAAM0C,eAAe,KAAK7B,UAAU,MAAM4B,OAAO7C,QAAO,CAAA;AACxD6C,WAAO5B,UAAU6B,YAAAA;AACjB,WAAOD;EACT;EAEAE,aAAaC,KAAkB;AAC7B,QAAIA,OAAO,KAAK,aAAa;AAC3B,aAAO,KAAK,YAAYA,GAAAA;IAC1B;AACA,QAAI,KAAK,SAAS;AAChB,aAAO,KAAK,QAAQD,aAAaC,GAAAA;IACnC;AAEA,WAAO1C;EACT;EAKAI,WAAW;AACT,WAAO,WAAW,KAAK,cAAc,aAAa,QAAA;EACpD;AACF;AA5LaT,UAAAA,aAAAA;MADZgD,4BAAe,SAAA;GACHhD,OAAAA;AE5BN,IAAMiD,kBAAkB,CAACnD,KAAcD,QAAQ,IAAIL,qBAAAA,MACxD,IAAIqC,QAAQ,CAACC,SAASY,WAAAA;AACpB5C,MAAIkB,UAAU,MAAM0B,OAAO7C,KAAAA,CAAAA;AAC7B,CAAA;AAKK,IAAMqD,oBAAoB,CAAIpD,KAAc8B,YAAAA;AACjD,MAAIiB;AACJ,SAAOhB,QAAQsB,KAAK;IAClBvB;IACA,IAAIC,QAAe,CAACC,SAASY,WAAAA;AAE3BG,qBAAe/C,IAAIkB,UAAU,MAAM0B,OAAO,IAAIlD,qBAAAA,CAAAA,CAAAA;IAChD,CAAA;GACD,EAAE4D,QAAQ,MAAMP,eAAAA,CAAAA;AACnB;;UCpBYQ,iBAAAA;;;;GAAAA,mBAAAA,iBAAAA,CAAAA,EAAAA;AAYZ,IAAMC,oCAAoC;AAKnC,IAAeC,WAAf,MAAeA;EACpB,kBAAe;EACf,eAAqC;EACrC,gBAAsC;;;;;;EAOtC,eAAwB,KAAK,eAAc;;;;;EAM3C,aAAsB,IAAIvD,QAAQ;IAAEC,MAAM,KAAK;EAAM,CAAA;EAErD,IAAI,QAAK;AACP,WAAOuD,OAAOC,eAAe,IAAI,EAAE/D,YAAYO;EACjD;EAEA,IAAcyD,kBAAkB;AAC9B,WAAO,KAAK;EACd;EAEA,IAAcC,OAAO;AACnB,WAAO,KAAK;EACd;;;;EAKA,MAAgBC,MAAM9D,KAA6B;EAAC;;;;EAKpD,MAAgB+D,OAAO/D,KAA6B;EAAC;;;;;EAMrD,MAAgBgE,OAAOvB,KAA2B;AAChD,QAAIe,mCAAmC;AACrC,UAAI;AACF,cAAM,KAAKS,MAAK;MAClB,SAASC,WAAgB;AACvBC,8CAAoBD,SAAAA;MACtB;IACF;AACA,UAAMzB;EACR;;;;;;;;EASA,MAAM2B,KAAKpE,KAA8B;AACvC,YAAQ,KAAK,iBAAe;MAC1B,KAAA;AACE,eAAO;MACT,KAAA;AACE,cAAM,IAAIL,MAAM,kBAAkB,KAAK,eAAe,EAAE;MAC1D;IACF;AAEA,UAAM,KAAK;AACX,WAAO,KAAK,iBAAiB,KAAK,MAAMK,GAAAA;AAExC,WAAO;EACT;;;;;EAMA,MAAMiE,MAAMjE,KAA8B;AACxC,QAAI,KAAK,oBAAe,UAA4B;AAClD,aAAO;IACT;AACA,UAAM,KAAK;AACX,WAAO,KAAK,kBAAkB,KAAK,OAAOA,GAAAA;AAE1C,WAAO;EACT;EAEA,OAAOS,OAAO4D,YAAY,IAAI;AAC5B,UAAM,KAAKJ,MAAK;EAClB;EAEA,MAAM,MAAMjE,KAAa;AACvB,SAAK,gBAAgB;AACrB,QAAIA,KAAK;AACP,WAAK,aAAaA,IAAI6C,OAAO;QAAE1C,MAAM,KAAK;MAAM,CAAA;IAClD;AACA,UAAM,KAAK2D,MAAM,KAAK,UAAU;AAChC,SAAK,kBAAe;EACtB;EAEA,MAAM,OAAO9D,MAAME,QAAQY,QAAO,GAAE;AAClC,SAAK,eAAe;AACpB,UAAM,KAAK,aAAab,QAAO;AAC/B,UAAM,KAAK8D,OAAO/D,GAAAA;AAClB,SAAK,eAAe,KAAK,eAAc;AACvC,SAAK,kBAAe;EACtB;EAEA,iBAAc;AACZ,WAAO,IAAIE,QAAQ;MACjBI,SAAS,CAACP,UACRuE,eAAe,YAAA;AACb,YAAI;AACF,gBAAM,KAAKN,OAAOjE,KAAAA;QACpB,SAAS0C,KAAU;AACjB,eAAK,kBAAe;AACpB,eAAK,WAAWE,MAAMF,GAAAA;QACxB;MACF,CAAA;IACJ,CAAA;EACF;AACF;AAEO,IAAM8B,gBAAgB,OAA4BvE,KAAcwE,aAAAA;AACrE,QAAMA,SAASJ,OAAOpE,GAAAA;AACtBA,MAAIkB,UAAU,MAAMsD,SAASP,QAAK,CAAA;AAElC,SAAOO;AACT;",
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":21471,"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":17016},"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":5893},"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":9792}}}
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,6 +1,6 @@
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
6
  name?: string;
@@ -9,17 +9,10 @@ export type CreateContextParams = {
9
9
  onError?: ContextErrorHandler;
10
10
  };
11
11
  export declare class Context {
12
+ #private;
12
13
  static default(): Context;
13
- private readonly _disposeCallbacks;
14
- private readonly _name?;
15
- private readonly _parent?;
16
- private readonly _attributes;
17
- private readonly _onError;
18
- private _isDisposed;
19
- private _disposePromise?;
20
14
  maxSafeDisposeCallbacks: number;
21
- constructor({ name, // TODO(burdon): Automate?
22
- parent, attributes, onError, }?: CreateContextParams);
15
+ constructor({ name, parent, attributes, onError }?: CreateContextParams);
23
16
  get disposed(): boolean;
24
17
  get disposeCallbacksLength(): number;
25
18
  /**
@@ -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,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;AAOF,qBACa,OAAO;IAClB,MAAM,CAAC,OAAO;IAId,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAyB;IAE3D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAsB;IAClD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsB;IAE/C,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAC,CAA+B;IAEhD,uBAAuB,SAA8B;gBAEhD,EACV,IAAI,EAAE,0BAA0B;IAChC,MAAM,EACN,UAAe,EACf,OASC,GACF,GAAE,mBAAwB;IAO3B,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;IA8CrD;;;;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
+ {"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-next.57eca40",
3
+ "version": "0.5.3",
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/log": "0.5.3-next.57eca40",
20
- "@dxos/node-std": "0.5.3-next.57eca40",
21
- "@dxos/util": "0.5.3-next.57eca40"
19
+ "@dxos/log": "0.5.3",
20
+ "@dxos/util": "0.5.3",
21
+ "@dxos/node-std": "0.5.3"
22
22
  },
23
23
  "publishConfig": {
24
24
  "access": "public"
package/src/context.ts CHANGED
@@ -9,7 +9,7 @@ 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
 
@@ -25,51 +25,48 @@ export type CreateContextParams = {
25
25
  */
26
26
  const MAX_SAFE_DISPOSE_CALLBACKS = 300;
27
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
+
28
39
  @safeInstanceof('Context')
29
40
  export class Context {
30
41
  static default() {
31
42
  return new Context();
32
43
  }
33
44
 
34
- private readonly _disposeCallbacks: DisposeCallback[] = [];
45
+ readonly #disposeCallbacks: DisposeCallback[] = [];
35
46
 
36
- private readonly _name?: string;
37
- private readonly _parent?: Context;
38
- private readonly _attributes: Record<string, any>;
39
- private readonly _onError: ContextErrorHandler;
47
+ readonly #name?: string = undefined;
48
+ readonly #parent?: Context = undefined;
49
+ readonly #attributes: Record<string, any>;
50
+ readonly #onError: ContextErrorHandler;
40
51
 
41
- private _isDisposed = false;
42
- private _disposePromise?: Promise<boolean> = undefined;
52
+ #isDisposed = false;
53
+ #disposePromise?: Promise<boolean> = undefined;
43
54
 
44
55
  public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
45
56
 
46
- constructor({
47
- name, // TODO(burdon): Automate?
48
- parent,
49
- attributes = {},
50
- onError = (error) => {
51
- if (error instanceof ContextDisposedError) {
52
- return;
53
- }
54
-
55
- void this.dispose();
56
-
57
- // Will generate an unhandled rejection.
58
- throw error;
59
- },
60
- }: CreateContextParams = {}) {
61
- this._name = name;
62
- this._parent = parent;
63
- this._attributes = attributes;
64
- this._onError = onError;
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;
65
62
  }
66
63
 
67
64
  get disposed() {
68
- return this._isDisposed;
65
+ return this.#isDisposed;
69
66
  }
70
67
 
71
68
  get disposeCallbacksLength() {
72
- return this._disposeCallbacks.length;
69
+ return this.#disposeCallbacks.length;
73
70
  }
74
71
 
75
72
  /**
@@ -82,7 +79,7 @@ export class Context {
82
79
  * @returns A function that can be used to remove the callback from the dispose list.
83
80
  */
84
81
  onDispose(callback: DisposeCallback): () => void {
85
- if (this._isDisposed) {
82
+ if (this.#isDisposed) {
86
83
  // Call the callback immediately if the context is already disposed.
87
84
  void (async () => {
88
85
  try {
@@ -93,18 +90,18 @@ export class Context {
93
90
  })();
94
91
  }
95
92
 
96
- this._disposeCallbacks.push(callback);
97
- if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
93
+ this.#disposeCallbacks.push(callback);
94
+ if (this.#disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
98
95
  log.warn('Context has a large number of dispose callbacks (this might be a memory leak).', {
99
- count: this._disposeCallbacks.length,
96
+ count: this.#disposeCallbacks.length,
100
97
  });
101
98
  }
102
99
 
103
100
  // Remove handler.
104
101
  return () => {
105
- const index = this._disposeCallbacks.indexOf(callback);
102
+ const index = this.#disposeCallbacks.indexOf(callback);
106
103
  if (index !== -1) {
107
- this._disposeCallbacks.splice(index, 1);
104
+ this.#disposeCallbacks.splice(index, 1);
108
105
  }
109
106
  };
110
107
  }
@@ -117,46 +114,53 @@ export class Context {
117
114
  * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.
118
115
  */
119
116
  async dispose(throwOnError = false): Promise<boolean> {
120
- if (this._disposePromise) {
121
- return this._disposePromise;
117
+ if (this.#disposePromise) {
118
+ return this.#disposePromise;
122
119
  }
123
120
 
124
121
  // TODO(burdon): Probably should not be set until the dispose is complete, but causes tests to fail if moved.
125
- this._isDisposed = true;
122
+ this.#isDisposed = true;
126
123
 
127
124
  // Set the promise before running the callbacks.
128
125
  let resolveDispose!: (value: boolean) => void;
129
- this._disposePromise = new Promise<boolean>((resolve) => {
126
+ const promise = new Promise<boolean>((resolve) => {
130
127
  resolveDispose = resolve;
131
128
  });
129
+ this.#disposePromise = promise;
132
130
 
133
131
  // Process last first.
134
132
  // Clone the array so that any mutations to the original array don't affect the dispose process.
135
- const callbacks = Array.from(this._disposeCallbacks).reverse();
136
- this._disposeCallbacks.length = 0;
133
+ const callbacks = Array.from(this.#disposeCallbacks).reverse();
134
+ this.#disposeCallbacks.length = 0;
137
135
 
138
- if (this._name) {
139
- log('disposing', { context: this._name, count: callbacks.length });
136
+ if (this.#name) {
137
+ log('disposing', { context: this.#name, count: callbacks.length });
140
138
  }
141
139
 
142
140
  let i = 0;
143
141
  let clean = true;
142
+ const errors: Error[] = [];
144
143
  for (const callback of callbacks) {
145
144
  try {
146
145
  await callback();
147
146
  i++;
148
147
  } catch (err: any) {
149
- log.catch(err, { context: this._name, callback: i, count: callbacks.length });
150
148
  clean = false;
151
149
  if (throwOnError) {
152
- throw err;
150
+ errors.push(err);
151
+ } else {
152
+ log.catch(err, { context: this.#name, callback: i, count: callbacks.length });
153
153
  }
154
154
  }
155
155
  }
156
156
 
157
+ if (errors.length > 0) {
158
+ throw new AggregateError(errors);
159
+ }
160
+
157
161
  resolveDispose(clean);
158
- if (this._name) {
159
- log('disposed', { context: this._name });
162
+ if (this.#name) {
163
+ log('disposed', { context: this.#name });
160
164
  }
161
165
 
162
166
  return clean;
@@ -168,14 +172,14 @@ export class Context {
168
172
  * IF the error handler is not set, the error will dispose the context and cause an unhandled rejection.
169
173
  */
170
174
  raise(error: Error): void {
171
- if (this._isDisposed) {
175
+ if (this.#isDisposed) {
172
176
  // TODO(dmaretskyi): Don't log those.
173
177
  // log.warn('Error in disposed context', error);
174
178
  return;
175
179
  }
176
180
 
177
181
  try {
178
- this._onError(error);
182
+ this.#onError(error, this);
179
183
  } catch (err) {
180
184
  // Generate an unhandled rejection and stop the error propagation.
181
185
  void Promise.reject(err);
@@ -190,7 +194,7 @@ export class Context {
190
194
  this.raise(error);
191
195
  } else {
192
196
  try {
193
- await onError(error);
197
+ await onError(error, this);
194
198
  } catch {
195
199
  this.raise(error);
196
200
  }
@@ -205,11 +209,11 @@ export class Context {
205
209
  }
206
210
 
207
211
  getAttribute(key: string): any {
208
- if (key in this._attributes) {
209
- return this._attributes[key];
212
+ if (key in this.#attributes) {
213
+ return this.#attributes[key];
210
214
  }
211
- if (this._parent) {
212
- return this._parent.getAttribute(key);
215
+ if (this.#parent) {
216
+ return this.#parent.getAttribute(key);
213
217
  }
214
218
 
215
219
  return undefined;
@@ -219,6 +223,6 @@ export class Context {
219
223
  [inspect.custom] = () => this.toString();
220
224
 
221
225
  toString() {
222
- return `Context(${this._isDisposed ? 'disposed' : 'active'})`;
226
+ return `Context(${this.#isDisposed ? 'disposed' : 'active'})`;
223
227
  }
224
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);