@dxos/context 0.5.2 → 0.5.3-main.088a2c8

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.
@@ -25,25 +25,23 @@ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/common/context/src/cont
25
25
  var MAX_SAFE_DISPOSE_CALLBACKS = 300;
26
26
  var _a, _b;
27
27
  var Context = class _Context {
28
- constructor({ onError = (error) => {
28
+ constructor({ name, parent, attributes = {}, onError = (error) => {
29
29
  if (error instanceof ContextDisposedError) {
30
30
  return;
31
31
  }
32
32
  void this.dispose();
33
33
  throw error;
34
- }, attributes = {}, parent } = {}) {
34
+ } } = {}) {
35
35
  this._disposeCallbacks = [];
36
36
  this._isDisposed = false;
37
37
  this._disposePromise = void 0;
38
- this._parent = null;
39
38
  this.maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
40
39
  this[_a] = "Context";
41
40
  this[_b] = () => this.toString();
42
- this._onError = onError;
41
+ this._name = name;
42
+ this._parent = parent;
43
43
  this._attributes = attributes;
44
- if (parent !== void 0) {
45
- this._parent = parent;
46
- }
44
+ this._onError = onError;
47
45
  }
48
46
  static {
49
47
  _a = Symbol.toStringTag, _b = inspect.custom;
@@ -74,7 +72,7 @@ var Context = class _Context {
74
72
  } catch (error) {
75
73
  log.catch(error, void 0, {
76
74
  F: __dxlog_file,
77
- L: 88,
75
+ L: 91,
78
76
  S: this,
79
77
  C: (f, a) => f(...a)
80
78
  });
@@ -83,12 +81,11 @@ var Context = class _Context {
83
81
  }
84
82
  this._disposeCallbacks.push(callback);
85
83
  if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
86
- log.warn("Context has a large number of dispose callbacks. This might be a memory leak.", {
87
- count: this._disposeCallbacks.length,
88
- safeThreshold: this.maxSafeDisposeCallbacks
84
+ log.warn("Context has a large number of dispose callbacks (this might be a memory leak).", {
85
+ count: this._disposeCallbacks.length
89
86
  }, {
90
87
  F: __dxlog_file,
91
- L: 95,
88
+ L: 98,
92
89
  S: this,
93
90
  C: (f, a) => f(...a)
94
91
  });
@@ -107,7 +104,7 @@ var Context = class _Context {
107
104
  * It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.
108
105
  * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.
109
106
  */
110
- async dispose() {
107
+ async dispose(throwOnError = false) {
111
108
  if (this._disposePromise) {
112
109
  return this._disposePromise;
113
110
  }
@@ -118,19 +115,52 @@ var Context = class _Context {
118
115
  });
119
116
  const callbacks = Array.from(this._disposeCallbacks).reverse();
120
117
  this._disposeCallbacks.length = 0;
118
+ if (this._name) {
119
+ log.info("disposing", {
120
+ context: this._name,
121
+ count: callbacks.length
122
+ }, {
123
+ F: __dxlog_file,
124
+ L: 139,
125
+ S: this,
126
+ C: (f, a) => f(...a)
127
+ });
128
+ }
129
+ let i = 0;
130
+ let clean = true;
121
131
  for (const callback of callbacks) {
122
132
  try {
123
133
  await callback();
124
- } catch (error) {
125
- log.catch(error, void 0, {
134
+ i++;
135
+ } catch (err) {
136
+ log.catch(err, {
137
+ context: this._name,
138
+ callback: i,
139
+ count: callbacks.length
140
+ }, {
126
141
  F: __dxlog_file,
127
- L: 136,
142
+ L: 149,
128
143
  S: this,
129
144
  C: (f, a) => f(...a)
130
145
  });
146
+ clean = false;
147
+ if (throwOnError) {
148
+ throw err;
149
+ }
131
150
  }
132
151
  }
133
- resolveDispose();
152
+ resolveDispose(clean);
153
+ if (this._name) {
154
+ log.info("disposed", {
155
+ context: this._name
156
+ }, {
157
+ F: __dxlog_file,
158
+ L: 159,
159
+ S: this,
160
+ C: (f, a) => f(...a)
161
+ });
162
+ }
163
+ return clean;
134
164
  }
135
165
  /**
136
166
  * Raise the error inside the context.
@@ -171,7 +201,7 @@ var Context = class _Context {
171
201
  if (key in this._attributes) {
172
202
  return this._attributes[key];
173
203
  }
174
- if (this._parent !== null) {
204
+ if (this._parent) {
175
205
  return this._parent.getAttribute(key);
176
206
  }
177
207
  return void 0;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/context.ts", "../../../src/context-disposed-error.ts", "../../../src/promise-utils.ts", "../../../src/resource.ts"],
4
- "sourcesContent": ["//\n// Copyright 2022 DXOS.org\n//\n\nimport { inspect } from 'node:util';\n\nimport { log } from '@dxos/log';\nimport { safeInstanceof } from '@dxos/util';\n\nimport { ContextDisposedError } from './context-disposed-error';\n\nexport type ContextErrorHandler = (error: Error) => void;\n\nexport type DisposeCallback = () => any | Promise<any>;\n\nexport type CreateContextParams = {\n onError?: ContextErrorHandler;\n attributes?: Record<string, any>;\n parent?: Context;\n};\n\n/**\n * Maximum number of dispose callbacks before we start logging warnings.\n */\nconst MAX_SAFE_DISPOSE_CALLBACKS = 300;\n\n@safeInstanceof('Context')\nexport class Context {\n static default() {\n return new Context();\n }\n\n private readonly _onError: ContextErrorHandler;\n private readonly _disposeCallbacks: DisposeCallback[] = [];\n private _isDisposed = false;\n private _disposePromise?: Promise<void> = undefined;\n private _parent: Context | null = null;\n\n private _attributes: Record<string, any>;\n\n public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;\n\n constructor({\n onError = (error) => {\n if (error instanceof ContextDisposedError) {\n return;\n }\n\n void this.dispose();\n\n // Will generate an unhandled rejection.\n throw error;\n },\n attributes = {},\n parent,\n }: CreateContextParams = {}) {\n this._onError = onError;\n this._attributes = attributes;\n if (parent !== undefined) {\n this._parent = parent;\n }\n }\n\n get disposed() {\n return this._isDisposed;\n }\n\n get disposeCallbacksLength() {\n return this._disposeCallbacks.length;\n }\n\n /**\n * Schedules a callback to run when the context is disposed.\n * May be async, in this case the disposer might choose to wait for all resource to released.\n * Throwing an error inside the callback will result in the error being logged, but not re-thrown.\n *\n * NOTE: Will call the callback immediately if the context is already disposed.\n *\n * @returns A function that can be used to remove the callback from the dispose list.\n */\n onDispose(callback: DisposeCallback) {\n if (this._isDisposed) {\n // Call the callback immediately if the context is already disposed.\n void (async () => {\n try {\n await callback();\n } catch (error: any) {\n log.catch(error);\n }\n })();\n }\n\n this._disposeCallbacks.push(callback);\n if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {\n log.warn('Context has a large number of dispose callbacks. This might be a memory leak.', {\n count: this._disposeCallbacks.length,\n safeThreshold: this.maxSafeDisposeCallbacks,\n });\n }\n\n return () => {\n const index = this._disposeCallbacks.indexOf(callback);\n if (index !== -1) {\n this._disposeCallbacks.splice(index, 1);\n }\n };\n }\n\n /**\n * Runs all dispose callbacks.\n * Callbacks are run in the reverse order they were added.\n * This function never throws.\n * It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.\n * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.\n */\n async dispose(): Promise<void> {\n if (this._disposePromise) {\n return this._disposePromise;\n }\n this._isDisposed = true;\n\n // Set the promise before running the callbacks.\n let resolveDispose!: () => void;\n this._disposePromise = new Promise<void>((resolve) => {\n resolveDispose = resolve;\n });\n\n // Clone the array so that any mutations to the original array don't affect the dispose process.\n const callbacks = Array.from(this._disposeCallbacks).reverse();\n this._disposeCallbacks.length = 0;\n\n for (const callback of callbacks) {\n try {\n await callback();\n } catch (error: any) {\n log.catch(error);\n }\n }\n resolveDispose();\n }\n\n /**\n * Raise the error inside the context.\n * The error will be propagated to the error handler.\n * IF the error handler is not set, the error will dispose the context and cause an unhandled rejection.\n */\n raise(error: Error): void {\n if (this._isDisposed) {\n // TODO(dmaretskyi): Don't log those.\n // log.warn('Error in disposed context', error);\n return;\n }\n\n try {\n this._onError(error);\n } catch (err) {\n // Generate an unhandled rejection and stop the error propagation.\n void Promise.reject(err);\n }\n }\n\n derive({ onError, attributes }: CreateContextParams = {}): Context {\n const newCtx = new Context({\n // TODO(dmaretskyi): Optimize to not require allocating a new closure for every context.\n onError: async (error) => {\n if (!onError) {\n this.raise(error);\n } else {\n try {\n await onError(error);\n } catch {\n this.raise(error);\n }\n }\n },\n attributes,\n });\n const clearDispose = this.onDispose(() => newCtx.dispose());\n newCtx.onDispose(clearDispose);\n return newCtx;\n }\n\n getAttribute(key: string): any {\n if (key in this._attributes) {\n return this._attributes[key];\n }\n if (this._parent !== null) {\n return this._parent.getAttribute(key);\n }\n return undefined;\n }\n\n [Symbol.toStringTag] = 'Context';\n\n [inspect.custom] = () => this.toString();\n\n toString() {\n return `Context(${this._isDisposed ? 'disposed' : 'active'})`;\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nexport class ContextDisposedError extends Error {\n constructor() {\n super('Context disposed.');\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { type Context } from './context';\nimport { ContextDisposedError } from './context-disposed-error';\n\n/**\n * @returns A promise that rejects when the context is disposed.\n */\n// TODO(dmaretskyi): Memory leak.\nexport const rejectOnDispose = (ctx: Context, error = new ContextDisposedError()): Promise<never> =>\n new Promise((resolve, reject) => {\n ctx.onDispose(() => reject(error));\n });\n\n/**\n * Rejects the promise if the context is disposed.\n */\nexport const cancelWithContext = <T>(ctx: Context, promise: Promise<T>): Promise<T> => {\n let clearDispose: () => void;\n return Promise.race([\n promise,\n new Promise<never>((resolve, reject) => {\n // Will be called before .finally() handlers.\n clearDispose = ctx.onDispose(() => reject(new ContextDisposedError()));\n }),\n ]).finally(() => clearDispose?.());\n};\n", "//\n// Copyright 2024 DXOS.org\n//\n\nimport '@dxos/util';\n\nimport { Context } from './context';\n\nexport enum LifecycleState {\n CLOSED = 'CLOSED',\n OPEN = 'OPEN',\n ERROR = 'ERROR',\n}\n\nexport interface Lifecycle {\n open?(ctx?: Context): Promise<any> | any;\n close?(): Promise<any> | any;\n}\n\n/**\n * Base class for resources that need to be opened and closed.\n */\nexport abstract class Resource implements Lifecycle {\n #lifecycleState = LifecycleState.CLOSED;\n #openPromise: Promise<void> | null = null;\n #closePromise: Promise<void> | null = null;\n\n /**\n * Managed internally by the resource.\n * Recreated on close.\n * Errors are propagated to the `_catch` method and the parent context.\n */\n #internalCtx: Context = this.#createContext();\n\n /**\n * Context that is used to bubble up errors that are not handled by the resource.\n * Provided in the open method.\n */\n #parentCtx: Context = new Context();\n\n protected get _lifecycleState() {\n return this.#lifecycleState;\n }\n\n protected get _ctx() {\n return this.#internalCtx;\n }\n\n /**\n * To be overridden by subclasses.\n */\n protected async _open(ctx: Context): Promise<void> {}\n\n /**\n * To be overridden by subclasses.\n */\n protected async _close(ctx: Context): Promise<void> {}\n\n /**\n * Error handler for errors that are caught by the context.\n * By default, errors are bubbled up to the parent context which is passed to the open method.\n */\n protected async _catch(err: Error): Promise<void> {\n throw err;\n }\n\n /**\n * Opens the resource.\n * If the resource is already open, it does nothing.\n * If the resource is in an error state, it throws an error.\n * If the resource is closed, it waits for it to close and then opens it.\n * @param ctx - Context to use for opening the resource. This context will receive errors that are not handled in `_catch`.\n */\n async open(ctx?: Context): Promise<this> {\n switch (this.#lifecycleState) {\n case LifecycleState.OPEN:\n return this;\n case LifecycleState.ERROR:\n throw new Error(`Invalid state: ${this.#lifecycleState}`);\n default:\n }\n\n await this.#closePromise;\n await (this.#openPromise ??= this.#open(ctx));\n\n return this;\n }\n\n /**\n * Closes the resource.\n * If the resource is already closed, it does nothing.\n */\n async close(ctx?: Context): Promise<this> {\n if (this.#lifecycleState === LifecycleState.CLOSED) {\n return this;\n }\n await this.#openPromise;\n await (this.#closePromise ??= this.#close(ctx));\n\n return this;\n }\n\n async [Symbol.asyncDispose]() {\n await this.close();\n }\n\n async #open(ctx?: Context) {\n this.#closePromise = null;\n if (ctx) {\n this.#parentCtx = ctx;\n }\n await this._open(this.#parentCtx);\n this.#lifecycleState = LifecycleState.OPEN;\n }\n\n async #close(ctx = new Context()) {\n this.#openPromise = null;\n await this.#internalCtx.dispose();\n await this._close(ctx);\n this.#internalCtx = this.#createContext();\n this.#lifecycleState = LifecycleState.CLOSED;\n }\n\n #createContext() {\n return new Context({\n onError: (error) =>\n queueMicrotask(async () => {\n try {\n await this._catch(error);\n } catch (err: any) {\n this.#lifecycleState = LifecycleState.ERROR;\n this.#parentCtx.raise(err);\n }\n }),\n });\n }\n}\n\nexport const openInContext = async <T extends Lifecycle>(ctx: Context, resource: T): Promise<T> => {\n await resource.open?.(ctx);\n ctx.onDispose(() => resource.close?.());\n\n return resource;\n};\n"],
5
- "mappings": "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",
6
- "names": ["inspect", "log", "safeInstanceof", "ContextDisposedError", "Error", "constructor", "MAX_SAFE_DISPOSE_CALLBACKS", "Context", "constructor", "onError", "error", "ContextDisposedError", "dispose", "attributes", "parent", "_disposeCallbacks", "_isDisposed", "_disposePromise", "undefined", "_parent", "maxSafeDisposeCallbacks", "Symbol", "inspect", "toString", "_onError", "_attributes", "toStringTag", "custom", "default", "disposed", "disposeCallbacksLength", "length", "onDispose", "callback", "log", "catch", "push", "warn", "count", "safeThreshold", "index", "indexOf", "splice", "resolveDispose", "Promise", "resolve", "callbacks", "Array", "from", "reverse", "raise", "err", "reject", "derive", "newCtx", "clearDispose", "getAttribute", "key", "safeInstanceof", "rejectOnDispose", "ctx", "error", "ContextDisposedError", "Promise", "resolve", "reject", "onDispose", "cancelWithContext", "promise", "clearDispose", "race", "finally", "LifecycleState", "Resource", "Context", "_lifecycleState", "_ctx", "_open", "ctx", "_close", "_catch", "err", "open", "Error", "close", "Symbol", "asyncDispose", "dispose", "onError", "error", "queueMicrotask", "raise", "openInContext", "resource", "onDispose"]
4
+ "sourcesContent": ["//\n// Copyright 2022 DXOS.org\n//\n\nimport { inspect } from 'node:util';\n\nimport { log } from '@dxos/log';\nimport { safeInstanceof } from '@dxos/util';\n\nimport { ContextDisposedError } from './context-disposed-error';\n\nexport type ContextErrorHandler = (error: Error) => 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.info('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.info('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": ";AAIA,SAASA,eAAe;AAExB,SAASC,WAAW;AACpB,SAASC,sBAAsB;;;ACHxB,IAAMC,uBAAN,cAAmCC,MAAAA;EACxCC,cAAc;AACZ,UAAM,mBAAA;EACR;AACF;;;;;;;;;;;;;;ADiBA,IAAMC,6BAA6B;AAzBnC;AA4BO,IAAMC,UAAN,MAAMA,SAAAA;EAiBXC,YAAY,EACVC,MACAC,QACAC,aAAa,CAAC,GACdC,UAAU,CAACC,UAAAA;AACT,QAAIA,iBAAiBC,sBAAsB;AACzC;IACF;AAEA,SAAK,KAAKC,QAAO;AAGjB,UAAMF;EACR,EAAC,IACsB,CAAC,GAAG;AA1BZG,6BAAuC,CAAA;AAOhDC,uBAAc;AACdC,2BAAqCC;AAEtCC,mCAA0Bd;AA8KjC,SAACe,MAAsB;AACvB,SAACC,MAAkB,MAAM,KAAKC,SAAQ;AA9JpC,SAAKC,QAAQf;AACb,SAAKgB,UAAUf;AACf,SAAKgB,cAAcf;AACnB,SAAKgB,WAAWf;EAClB;EAyJCS;gBAAOO,aACPN,aAAQO;;EA7LT,OAAOC,UAAU;AACf,WAAO,IAAIvB,SAAAA;EACb;EAmCA,IAAIwB,WAAW;AACb,WAAO,KAAKd;EACd;EAEA,IAAIe,yBAAyB;AAC3B,WAAO,KAAKhB,kBAAkBiB;EAChC;;;;;;;;;;EAWAC,UAAUC,UAAuC;AAC/C,QAAI,KAAKlB,aAAa;AAEpB,YAAM,YAAA;AACJ,YAAI;AACF,gBAAMkB,SAAAA;QACR,SAAStB,OAAY;AACnBuB,cAAIC,MAAMxB,OAAAA,QAAAA;;;;;;QACZ;MACF,GAAA;IACF;AAEA,SAAKG,kBAAkBsB,KAAKH,QAAAA;AAC5B,QAAI,KAAKnB,kBAAkBiB,SAAS,KAAKb,yBAAyB;AAChEgB,UAAIG,KAAK,kFAAkF;QACzFC,OAAO,KAAKxB,kBAAkBiB;MAChC,GAAA;;;;;;IACF;AAGA,WAAO,MAAA;AACL,YAAMQ,QAAQ,KAAKzB,kBAAkB0B,QAAQP,QAAAA;AAC7C,UAAIM,UAAU,IAAI;AAChB,aAAKzB,kBAAkB2B,OAAOF,OAAO,CAAA;MACvC;IACF;EACF;;;;;;;;EASA,MAAM1B,QAAQ6B,eAAe,OAAyB;AACpD,QAAI,KAAK1B,iBAAiB;AACxB,aAAO,KAAKA;IACd;AAGA,SAAKD,cAAc;AAGnB,QAAI4B;AACJ,SAAK3B,kBAAkB,IAAI4B,QAAiB,CAACC,YAAAA;AAC3CF,uBAAiBE;IACnB,CAAA;AAIA,UAAMC,YAAYC,MAAMC,KAAK,KAAKlC,iBAAiB,EAAEmC,QAAO;AAC5D,SAAKnC,kBAAkBiB,SAAS;AAEhC,QAAI,KAAKT,OAAO;AACdY,UAAIgB,KAAK,aAAa;QAAEC,SAAS,KAAK7B;QAAOgB,OAAOQ,UAAUf;MAAO,GAAA;;;;;;IACvE;AAEA,QAAIqB,IAAI;AACR,QAAIC,QAAQ;AACZ,eAAWpB,YAAYa,WAAW;AAChC,UAAI;AACF,cAAMb,SAAAA;AACNmB;MACF,SAASE,KAAU;AACjBpB,YAAIC,MAAMmB,KAAK;UAAEH,SAAS,KAAK7B;UAAOW,UAAUmB;UAAGd,OAAOQ,UAAUf;QAAO,GAAA;;;;;;AAC3EsB,gBAAQ;AACR,YAAIX,cAAc;AAChB,gBAAMY;QACR;MACF;IACF;AAEAX,mBAAeU,KAAAA;AACf,QAAI,KAAK/B,OAAO;AACdY,UAAIgB,KAAK,YAAY;QAAEC,SAAS,KAAK7B;MAAM,GAAA;;;;;;IAC7C;AAEA,WAAO+B;EACT;;;;;;EAOAE,MAAM5C,OAAoB;AACxB,QAAI,KAAKI,aAAa;AAGpB;IACF;AAEA,QAAI;AACF,WAAKU,SAASd,KAAAA;IAChB,SAAS2C,KAAK;AAEZ,WAAKV,QAAQY,OAAOF,GAAAA;IACtB;EACF;EAEAG,OAAO,EAAE/C,SAASD,WAAU,IAA0B,CAAC,GAAY;AACjE,UAAMiD,SAAS,IAAIrD,SAAQ;;MAEzBK,SAAS,OAAOC,UAAAA;AACd,YAAI,CAACD,SAAS;AACZ,eAAK6C,MAAM5C,KAAAA;QACb,OAAO;AACL,cAAI;AACF,kBAAMD,QAAQC,KAAAA;UAChB,QAAQ;AACN,iBAAK4C,MAAM5C,KAAAA;UACb;QACF;MACF;MACAF;IACF,CAAA;AAEA,UAAMkD,eAAe,KAAK3B,UAAU,MAAM0B,OAAO7C,QAAO,CAAA;AACxD6C,WAAO1B,UAAU2B,YAAAA;AACjB,WAAOD;EACT;EAEAE,aAAaC,KAAkB;AAC7B,QAAIA,OAAO,KAAKrC,aAAa;AAC3B,aAAO,KAAKA,YAAYqC,GAAAA;IAC1B;AACA,QAAI,KAAKtC,SAAS;AAChB,aAAO,KAAKA,QAAQqC,aAAaC,GAAAA;IACnC;AAEA,WAAO5C;EACT;EAKAI,WAAW;AACT,WAAO,WAAW,KAAKN,cAAc,aAAa,QAAA;EACpD;AACF;AAnMaV,UAAAA,aAAAA;EADZyD,eAAe,SAAA;GACHzD,OAAAA;;;AEjBN,IAAM0D,kBAAkB,CAACC,KAAcC,QAAQ,IAAIC,qBAAAA,MACxD,IAAIC,QAAQ,CAACC,SAASC,WAAAA;AACpBL,MAAIM,UAAU,MAAMD,OAAOJ,KAAAA,CAAAA;AAC7B,CAAA;AAKK,IAAMM,oBAAoB,CAAIP,KAAcQ,YAAAA;AACjD,MAAIC;AACJ,SAAON,QAAQO,KAAK;IAClBF;IACA,IAAIL,QAAe,CAACC,SAASC,WAAAA;AAE3BI,qBAAeT,IAAIM,UAAU,MAAMD,OAAO,IAAIH,qBAAAA,CAAAA,CAAAA;IAChD,CAAA;GACD,EAAES,QAAQ,MAAMF,eAAAA,CAAAA;AACnB;;;ACxBA,OAAO;;UAIKG,iBAAAA;;;;GAAAA,mBAAAA,iBAAAA,CAAAA,EAAAA;AAcL,IAAeC,WAAf,MAAeA;EACpB,kBAAe;EACf,eAAqC;EACrC,gBAAsC;;;;;;EAOtC,eAAwB,KAAK,eAAc;;;;;EAM3C,aAAsB,IAAIC,QAAAA;EAE1B,IAAcC,kBAAkB;AAC9B,WAAO,KAAK;EACd;EAEA,IAAcC,OAAO;AACnB,WAAO,KAAK;EACd;;;;EAKA,MAAgBC,MAAMC,KAA6B;EAAC;;;;EAKpD,MAAgBC,OAAOD,KAA6B;EAAC;;;;;EAMrD,MAAgBE,OAAOC,KAA2B;AAChD,UAAMA;EACR;;;;;;;;EASA,MAAMC,KAAKJ,KAA8B;AACvC,YAAQ,KAAK,iBAAe;MAC1B,KAAA;AACE,eAAO;MACT,KAAA;AACE,cAAM,IAAIK,MAAM,kBAAkB,KAAK,eAAe,EAAE;MAC1D;IACF;AAEA,UAAM,KAAK;AACX,WAAO,KAAK,iBAAiB,KAAK,MAAML,GAAAA;AAExC,WAAO;EACT;;;;;EAMA,MAAMM,MAAMN,KAA8B;AACxC,QAAI,KAAK,oBAAe,UAA4B;AAClD,aAAO;IACT;AACA,UAAM,KAAK;AACX,WAAO,KAAK,kBAAkB,KAAK,OAAOA,GAAAA;AAE1C,WAAO;EACT;EAEA,OAAOO,OAAOC,YAAY,IAAI;AAC5B,UAAM,KAAKF,MAAK;EAClB;EAEA,MAAM,MAAMN,KAAa;AACvB,SAAK,gBAAgB;AACrB,QAAIA,KAAK;AACP,WAAK,aAAaA;IACpB;AACA,UAAM,KAAKD,MAAM,KAAK,UAAU;AAChC,SAAK,kBAAe;EACtB;EAEA,MAAM,OAAOC,MAAM,IAAIJ,QAAAA,GAAS;AAC9B,SAAK,eAAe;AACpB,UAAM,KAAK,aAAaa,QAAO;AAC/B,UAAM,KAAKR,OAAOD,GAAAA;AAClB,SAAK,eAAe,KAAK,eAAc;AACvC,SAAK,kBAAe;EACtB;EAEA,iBAAc;AACZ,WAAO,IAAIJ,QAAQ;MACjBc,SAAS,CAACC,UACRC,eAAe,YAAA;AACb,YAAI;AACF,gBAAM,KAAKV,OAAOS,KAAAA;QACpB,SAASR,KAAU;AACjB,eAAK,kBAAe;AACpB,eAAK,WAAWU,MAAMV,GAAAA;QACxB;MACF,CAAA;IACJ,CAAA;EACF;AACF;AAEO,IAAMW,gBAAgB,OAA4Bd,KAAce,aAAAA;AACrE,QAAMA,SAASX,OAAOJ,GAAAA;AACtBA,MAAIgB,UAAU,MAAMD,SAAST,QAAK,CAAA;AAElC,SAAOS;AACT;",
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", "info", "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"]
7
7
  }
@@ -1 +1 @@
1
- {"inputs":{"packages/common/context/src/context-disposed-error.ts":{"bytes":817,"imports":[],"format":"esm"},"packages/common/context/src/context.ts":{"bytes":18959,"imports":[{"path":"@dxos/node-std/util","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"packages/common/context/src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"}],"format":"esm"},"packages/common/context/src/promise-utils.ts":{"bytes":3040,"imports":[{"path":"packages/common/context/src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"}],"format":"esm"},"packages/common/context/src/resource.ts":{"bytes":11744,"imports":[{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"packages/common/context/src/context.ts","kind":"import-statement","original":"./context"}],"format":"esm"},"packages/common/context/src/index.ts":{"bytes":777,"imports":[{"path":"packages/common/context/src/context.ts","kind":"import-statement","original":"./context"},{"path":"packages/common/context/src/promise-utils.ts","kind":"import-statement","original":"./promise-utils"},{"path":"packages/common/context/src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"},{"path":"packages/common/context/src/resource.ts","kind":"import-statement","original":"./resource"}],"format":"esm"}},"outputs":{"packages/common/context/dist/lib/browser/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":16013},"packages/common/context/dist/lib/browser/index.mjs":{"imports":[{"path":"@dxos/node-std/util","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"exports":["Context","ContextDisposedError","LifecycleState","Resource","cancelWithContext","openInContext","rejectOnDispose"],"entryPoint":"packages/common/context/src/index.ts","inputs":{"packages/common/context/src/context.ts":{"bytesInOutput":5323},"packages/common/context/src/context-disposed-error.ts":{"bytesInOutput":106},"packages/common/context/src/index.ts":{"bytesInOutput":0},"packages/common/context/src/promise-utils.ts":{"bytesInOutput":410},"packages/common/context/src/resource.ts":{"bytesInOutput":2979}},"bytes":9222}}}
1
+ {"inputs":{"packages/common/context/src/context-disposed-error.ts":{"bytes":817,"imports":[],"format":"esm"},"packages/common/context/src/context.ts":{"bytes":21533,"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":17046},"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":5913},"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":9812}}}
@@ -50,25 +50,23 @@ var __dxlog_file = "/home/runner/work/dxos/dxos/packages/common/context/src/cont
50
50
  var MAX_SAFE_DISPOSE_CALLBACKS = 300;
51
51
  var _a, _b;
52
52
  var Context = class _Context {
53
- constructor({ onError = (error) => {
53
+ constructor({ name, parent, attributes = {}, onError = (error) => {
54
54
  if (error instanceof ContextDisposedError) {
55
55
  return;
56
56
  }
57
57
  void this.dispose();
58
58
  throw error;
59
- }, attributes = {}, parent } = {}) {
59
+ } } = {}) {
60
60
  this._disposeCallbacks = [];
61
61
  this._isDisposed = false;
62
62
  this._disposePromise = void 0;
63
- this._parent = null;
64
63
  this.maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
65
64
  this[_a] = "Context";
66
65
  this[_b] = () => this.toString();
67
- this._onError = onError;
66
+ this._name = name;
67
+ this._parent = parent;
68
68
  this._attributes = attributes;
69
- if (parent !== void 0) {
70
- this._parent = parent;
71
- }
69
+ this._onError = onError;
72
70
  }
73
71
  static {
74
72
  _a = Symbol.toStringTag, _b = import_node_util.inspect.custom;
@@ -99,7 +97,7 @@ var Context = class _Context {
99
97
  } catch (error) {
100
98
  import_log.log.catch(error, void 0, {
101
99
  F: __dxlog_file,
102
- L: 88,
100
+ L: 91,
103
101
  S: this,
104
102
  C: (f, a) => f(...a)
105
103
  });
@@ -108,12 +106,11 @@ var Context = class _Context {
108
106
  }
109
107
  this._disposeCallbacks.push(callback);
110
108
  if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
111
- import_log.log.warn("Context has a large number of dispose callbacks. This might be a memory leak.", {
112
- count: this._disposeCallbacks.length,
113
- safeThreshold: this.maxSafeDisposeCallbacks
109
+ import_log.log.warn("Context has a large number of dispose callbacks (this might be a memory leak).", {
110
+ count: this._disposeCallbacks.length
114
111
  }, {
115
112
  F: __dxlog_file,
116
- L: 95,
113
+ L: 98,
117
114
  S: this,
118
115
  C: (f, a) => f(...a)
119
116
  });
@@ -132,7 +129,7 @@ var Context = class _Context {
132
129
  * It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.
133
130
  * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.
134
131
  */
135
- async dispose() {
132
+ async dispose(throwOnError = false) {
136
133
  if (this._disposePromise) {
137
134
  return this._disposePromise;
138
135
  }
@@ -143,19 +140,52 @@ var Context = class _Context {
143
140
  });
144
141
  const callbacks = Array.from(this._disposeCallbacks).reverse();
145
142
  this._disposeCallbacks.length = 0;
143
+ if (this._name) {
144
+ import_log.log.info("disposing", {
145
+ context: this._name,
146
+ count: callbacks.length
147
+ }, {
148
+ F: __dxlog_file,
149
+ L: 139,
150
+ S: this,
151
+ C: (f, a) => f(...a)
152
+ });
153
+ }
154
+ let i = 0;
155
+ let clean = true;
146
156
  for (const callback of callbacks) {
147
157
  try {
148
158
  await callback();
149
- } catch (error) {
150
- import_log.log.catch(error, void 0, {
159
+ i++;
160
+ } catch (err) {
161
+ import_log.log.catch(err, {
162
+ context: this._name,
163
+ callback: i,
164
+ count: callbacks.length
165
+ }, {
151
166
  F: __dxlog_file,
152
- L: 136,
167
+ L: 149,
153
168
  S: this,
154
169
  C: (f, a) => f(...a)
155
170
  });
171
+ clean = false;
172
+ if (throwOnError) {
173
+ throw err;
174
+ }
156
175
  }
157
176
  }
158
- resolveDispose();
177
+ resolveDispose(clean);
178
+ if (this._name) {
179
+ import_log.log.info("disposed", {
180
+ context: this._name
181
+ }, {
182
+ F: __dxlog_file,
183
+ L: 159,
184
+ S: this,
185
+ C: (f, a) => f(...a)
186
+ });
187
+ }
188
+ return clean;
159
189
  }
160
190
  /**
161
191
  * Raise the error inside the context.
@@ -196,7 +226,7 @@ var Context = class _Context {
196
226
  if (key in this._attributes) {
197
227
  return this._attributes[key];
198
228
  }
199
- if (this._parent !== null) {
229
+ if (this._parent) {
200
230
  return this._parent.getAttribute(key);
201
231
  }
202
232
  return void 0;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/context.ts", "../../../src/context-disposed-error.ts", "../../../src/promise-utils.ts", "../../../src/resource.ts"],
4
- "sourcesContent": ["//\n// Copyright 2022 DXOS.org\n//\n\nimport { inspect } from 'node:util';\n\nimport { log } from '@dxos/log';\nimport { safeInstanceof } from '@dxos/util';\n\nimport { ContextDisposedError } from './context-disposed-error';\n\nexport type ContextErrorHandler = (error: Error) => void;\n\nexport type DisposeCallback = () => any | Promise<any>;\n\nexport type CreateContextParams = {\n onError?: ContextErrorHandler;\n attributes?: Record<string, any>;\n parent?: Context;\n};\n\n/**\n * Maximum number of dispose callbacks before we start logging warnings.\n */\nconst MAX_SAFE_DISPOSE_CALLBACKS = 300;\n\n@safeInstanceof('Context')\nexport class Context {\n static default() {\n return new Context();\n }\n\n private readonly _onError: ContextErrorHandler;\n private readonly _disposeCallbacks: DisposeCallback[] = [];\n private _isDisposed = false;\n private _disposePromise?: Promise<void> = undefined;\n private _parent: Context | null = null;\n\n private _attributes: Record<string, any>;\n\n public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;\n\n constructor({\n onError = (error) => {\n if (error instanceof ContextDisposedError) {\n return;\n }\n\n void this.dispose();\n\n // Will generate an unhandled rejection.\n throw error;\n },\n attributes = {},\n parent,\n }: CreateContextParams = {}) {\n this._onError = onError;\n this._attributes = attributes;\n if (parent !== undefined) {\n this._parent = parent;\n }\n }\n\n get disposed() {\n return this._isDisposed;\n }\n\n get disposeCallbacksLength() {\n return this._disposeCallbacks.length;\n }\n\n /**\n * Schedules a callback to run when the context is disposed.\n * May be async, in this case the disposer might choose to wait for all resource to released.\n * Throwing an error inside the callback will result in the error being logged, but not re-thrown.\n *\n * NOTE: Will call the callback immediately if the context is already disposed.\n *\n * @returns A function that can be used to remove the callback from the dispose list.\n */\n onDispose(callback: DisposeCallback) {\n if (this._isDisposed) {\n // Call the callback immediately if the context is already disposed.\n void (async () => {\n try {\n await callback();\n } catch (error: any) {\n log.catch(error);\n }\n })();\n }\n\n this._disposeCallbacks.push(callback);\n if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {\n log.warn('Context has a large number of dispose callbacks. This might be a memory leak.', {\n count: this._disposeCallbacks.length,\n safeThreshold: this.maxSafeDisposeCallbacks,\n });\n }\n\n return () => {\n const index = this._disposeCallbacks.indexOf(callback);\n if (index !== -1) {\n this._disposeCallbacks.splice(index, 1);\n }\n };\n }\n\n /**\n * Runs all dispose callbacks.\n * Callbacks are run in the reverse order they were added.\n * This function never throws.\n * It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.\n * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.\n */\n async dispose(): Promise<void> {\n if (this._disposePromise) {\n return this._disposePromise;\n }\n this._isDisposed = true;\n\n // Set the promise before running the callbacks.\n let resolveDispose!: () => void;\n this._disposePromise = new Promise<void>((resolve) => {\n resolveDispose = resolve;\n });\n\n // Clone the array so that any mutations to the original array don't affect the dispose process.\n const callbacks = Array.from(this._disposeCallbacks).reverse();\n this._disposeCallbacks.length = 0;\n\n for (const callback of callbacks) {\n try {\n await callback();\n } catch (error: any) {\n log.catch(error);\n }\n }\n resolveDispose();\n }\n\n /**\n * Raise the error inside the context.\n * The error will be propagated to the error handler.\n * IF the error handler is not set, the error will dispose the context and cause an unhandled rejection.\n */\n raise(error: Error): void {\n if (this._isDisposed) {\n // TODO(dmaretskyi): Don't log those.\n // log.warn('Error in disposed context', error);\n return;\n }\n\n try {\n this._onError(error);\n } catch (err) {\n // Generate an unhandled rejection and stop the error propagation.\n void Promise.reject(err);\n }\n }\n\n derive({ onError, attributes }: CreateContextParams = {}): Context {\n const newCtx = new Context({\n // TODO(dmaretskyi): Optimize to not require allocating a new closure for every context.\n onError: async (error) => {\n if (!onError) {\n this.raise(error);\n } else {\n try {\n await onError(error);\n } catch {\n this.raise(error);\n }\n }\n },\n attributes,\n });\n const clearDispose = this.onDispose(() => newCtx.dispose());\n newCtx.onDispose(clearDispose);\n return newCtx;\n }\n\n getAttribute(key: string): any {\n if (key in this._attributes) {\n return this._attributes[key];\n }\n if (this._parent !== null) {\n return this._parent.getAttribute(key);\n }\n return undefined;\n }\n\n [Symbol.toStringTag] = 'Context';\n\n [inspect.custom] = () => this.toString();\n\n toString() {\n return `Context(${this._isDisposed ? 'disposed' : 'active'})`;\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nexport class ContextDisposedError extends Error {\n constructor() {\n super('Context disposed.');\n }\n}\n", "//\n// Copyright 2023 DXOS.org\n//\n\nimport { type Context } from './context';\nimport { ContextDisposedError } from './context-disposed-error';\n\n/**\n * @returns A promise that rejects when the context is disposed.\n */\n// TODO(dmaretskyi): Memory leak.\nexport const rejectOnDispose = (ctx: Context, error = new ContextDisposedError()): Promise<never> =>\n new Promise((resolve, reject) => {\n ctx.onDispose(() => reject(error));\n });\n\n/**\n * Rejects the promise if the context is disposed.\n */\nexport const cancelWithContext = <T>(ctx: Context, promise: Promise<T>): Promise<T> => {\n let clearDispose: () => void;\n return Promise.race([\n promise,\n new Promise<never>((resolve, reject) => {\n // Will be called before .finally() handlers.\n clearDispose = ctx.onDispose(() => reject(new ContextDisposedError()));\n }),\n ]).finally(() => clearDispose?.());\n};\n", "//\n// Copyright 2024 DXOS.org\n//\n\nimport '@dxos/util';\n\nimport { Context } from './context';\n\nexport enum LifecycleState {\n CLOSED = 'CLOSED',\n OPEN = 'OPEN',\n ERROR = 'ERROR',\n}\n\nexport interface Lifecycle {\n open?(ctx?: Context): Promise<any> | any;\n close?(): Promise<any> | any;\n}\n\n/**\n * Base class for resources that need to be opened and closed.\n */\nexport abstract class Resource implements Lifecycle {\n #lifecycleState = LifecycleState.CLOSED;\n #openPromise: Promise<void> | null = null;\n #closePromise: Promise<void> | null = null;\n\n /**\n * Managed internally by the resource.\n * Recreated on close.\n * Errors are propagated to the `_catch` method and the parent context.\n */\n #internalCtx: Context = this.#createContext();\n\n /**\n * Context that is used to bubble up errors that are not handled by the resource.\n * Provided in the open method.\n */\n #parentCtx: Context = new Context();\n\n protected get _lifecycleState() {\n return this.#lifecycleState;\n }\n\n protected get _ctx() {\n return this.#internalCtx;\n }\n\n /**\n * To be overridden by subclasses.\n */\n protected async _open(ctx: Context): Promise<void> {}\n\n /**\n * To be overridden by subclasses.\n */\n protected async _close(ctx: Context): Promise<void> {}\n\n /**\n * Error handler for errors that are caught by the context.\n * By default, errors are bubbled up to the parent context which is passed to the open method.\n */\n protected async _catch(err: Error): Promise<void> {\n throw err;\n }\n\n /**\n * Opens the resource.\n * If the resource is already open, it does nothing.\n * If the resource is in an error state, it throws an error.\n * If the resource is closed, it waits for it to close and then opens it.\n * @param ctx - Context to use for opening the resource. This context will receive errors that are not handled in `_catch`.\n */\n async open(ctx?: Context): Promise<this> {\n switch (this.#lifecycleState) {\n case LifecycleState.OPEN:\n return this;\n case LifecycleState.ERROR:\n throw new Error(`Invalid state: ${this.#lifecycleState}`);\n default:\n }\n\n await this.#closePromise;\n await (this.#openPromise ??= this.#open(ctx));\n\n return this;\n }\n\n /**\n * Closes the resource.\n * If the resource is already closed, it does nothing.\n */\n async close(ctx?: Context): Promise<this> {\n if (this.#lifecycleState === LifecycleState.CLOSED) {\n return this;\n }\n await this.#openPromise;\n await (this.#closePromise ??= this.#close(ctx));\n\n return this;\n }\n\n async [Symbol.asyncDispose]() {\n await this.close();\n }\n\n async #open(ctx?: Context) {\n this.#closePromise = null;\n if (ctx) {\n this.#parentCtx = ctx;\n }\n await this._open(this.#parentCtx);\n this.#lifecycleState = LifecycleState.OPEN;\n }\n\n async #close(ctx = new Context()) {\n this.#openPromise = null;\n await this.#internalCtx.dispose();\n await this._close(ctx);\n this.#internalCtx = this.#createContext();\n this.#lifecycleState = LifecycleState.CLOSED;\n }\n\n #createContext() {\n return new Context({\n onError: (error) =>\n queueMicrotask(async () => {\n try {\n await this._catch(error);\n } catch (err: any) {\n this.#lifecycleState = LifecycleState.ERROR;\n this.#parentCtx.raise(err);\n }\n }),\n });\n }\n}\n\nexport const openInContext = async <T extends Lifecycle>(ctx: Context, resource: T): Promise<T> => {\n await resource.open?.(ctx);\n ctx.onDispose(() => resource.close?.());\n\n return resource;\n};\n"],
5
- "mappings": "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",
6
- "names": ["import_util", "ContextDisposedError", "Error", "constructor", "MAX_SAFE_DISPOSE_CALLBACKS", "Context", "onError", "error", "dispose", "attributes", "parent", "_disposeCallbacks", "_isDisposed", "_disposePromise", "undefined", "_parent", "maxSafeDisposeCallbacks", "Symbol", "inspect", "toString", "_onError", "_attributes", "toStringTag", "custom", "default", "disposed", "disposeCallbacksLength", "length", "onDispose", "callback", "log", "catch", "push", "warn", "count", "safeThreshold", "index", "indexOf", "splice", "resolveDispose", "Promise", "resolve", "callbacks", "Array", "from", "reverse", "raise", "err", "reject", "derive", "newCtx", "clearDispose", "getAttribute", "key", "safeInstanceof", "rejectOnDispose", "ctx", "cancelWithContext", "promise", "race", "finally", "LifecycleState", "Resource", "_lifecycleState", "_ctx", "_open", "_close", "_catch", "open", "close", "asyncDispose", "queueMicrotask", "openInContext", "resource"]
4
+ "sourcesContent": ["//\n// Copyright 2022 DXOS.org\n//\n\nimport { inspect } from 'node:util';\n\nimport { log } from '@dxos/log';\nimport { safeInstanceof } from '@dxos/util';\n\nimport { ContextDisposedError } from './context-disposed-error';\n\nexport type ContextErrorHandler = (error: Error) => 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.info('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.info('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", "info", "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"]
7
7
  }
@@ -1 +1 @@
1
- {"inputs":{"packages/common/context/src/context-disposed-error.ts":{"bytes":817,"imports":[],"format":"esm"},"packages/common/context/src/context.ts":{"bytes":18959,"imports":[{"path":"node:util","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"packages/common/context/src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"}],"format":"esm"},"packages/common/context/src/promise-utils.ts":{"bytes":3040,"imports":[{"path":"packages/common/context/src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"}],"format":"esm"},"packages/common/context/src/resource.ts":{"bytes":11744,"imports":[{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"packages/common/context/src/context.ts","kind":"import-statement","original":"./context"}],"format":"esm"},"packages/common/context/src/index.ts":{"bytes":777,"imports":[{"path":"packages/common/context/src/context.ts","kind":"import-statement","original":"./context"},{"path":"packages/common/context/src/promise-utils.ts","kind":"import-statement","original":"./promise-utils"},{"path":"packages/common/context/src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"},{"path":"packages/common/context/src/resource.ts","kind":"import-statement","original":"./resource"}],"format":"esm"}},"outputs":{"packages/common/context/dist/lib/node/index.cjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":16013},"packages/common/context/dist/lib/node/index.cjs":{"imports":[{"path":"node:util","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"exports":["Context","ContextDisposedError","LifecycleState","Resource","cancelWithContext","openInContext","rejectOnDispose"],"entryPoint":"packages/common/context/src/index.ts","inputs":{"packages/common/context/src/context.ts":{"bytesInOutput":5313},"packages/common/context/src/context-disposed-error.ts":{"bytesInOutput":106},"packages/common/context/src/index.ts":{"bytesInOutput":0},"packages/common/context/src/promise-utils.ts":{"bytesInOutput":410},"packages/common/context/src/resource.ts":{"bytesInOutput":2979}},"bytes":9212}}}
1
+ {"inputs":{"packages/common/context/src/context-disposed-error.ts":{"bytes":817,"imports":[],"format":"esm"},"packages/common/context/src/context.ts":{"bytes":21533,"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":17046},"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":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}}}
@@ -3,20 +3,23 @@ import { inspect } from 'node:util';
3
3
  export type ContextErrorHandler = (error: Error) => void;
4
4
  export type DisposeCallback = () => any | Promise<any>;
5
5
  export type CreateContextParams = {
6
- onError?: ContextErrorHandler;
7
- attributes?: Record<string, any>;
6
+ name?: string;
8
7
  parent?: Context;
8
+ attributes?: Record<string, any>;
9
+ onError?: ContextErrorHandler;
9
10
  };
10
11
  export declare class Context {
11
12
  static default(): Context;
12
- private readonly _onError;
13
13
  private readonly _disposeCallbacks;
14
+ private readonly _name?;
15
+ private readonly _parent?;
16
+ private readonly _attributes;
17
+ private readonly _onError;
14
18
  private _isDisposed;
15
19
  private _disposePromise?;
16
- private _parent;
17
- private _attributes;
18
20
  maxSafeDisposeCallbacks: number;
19
- constructor({ onError, attributes, parent, }?: CreateContextParams);
21
+ constructor({ name, // TODO(burdon): Automate?
22
+ parent, attributes, onError, }?: CreateContextParams);
20
23
  get disposed(): boolean;
21
24
  get disposeCallbacksLength(): number;
22
25
  /**
@@ -36,7 +39,7 @@ export declare class Context {
36
39
  * It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.
37
40
  * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.
38
41
  */
39
- dispose(): Promise<void>;
42
+ dispose(throwOnError?: boolean): Promise<boolean>;
40
43
  /**
41
44
  * Raise the error inside the context.
42
45
  * The error will be propagated to the error handler.
@@ -1 +1 @@
1
- {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/context.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;AAEzD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;AAEvD,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAOF,qBACa,OAAO;IAClB,MAAM,CAAC,OAAO;IAId,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsB;IAC/C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAyB;IAC3D,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAC,CAA4B;IACpD,OAAO,CAAC,OAAO,CAAwB;IAEvC,OAAO,CAAC,WAAW,CAAsB;IAElC,uBAAuB,SAA8B;gBAEhD,EACV,OASC,EACD,UAAe,EACf,MAAM,GACP,GAAE,mBAAwB;IAQ3B,IAAI,QAAQ,YAEX;IAED,IAAI,sBAAsB,WAEzB;IAED;;;;;;;;OAQG;IACH,SAAS,CAAC,QAAQ,EAAE,eAAe;IA4BnC;;;;;;OAMG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA0B9B;;;;OAIG;IACH,KAAK,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI;IAezB,MAAM,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,GAAE,mBAAwB,GAAG,OAAO;IAqBlE,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG;IAU9B,CAAC,MAAM,CAAC,WAAW,CAAC,SAAa;IAEjC,CAAC,OAAO,CAAC,MAAM,CAAC,eAAyB;IAEzC,QAAQ;CAGT"}
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../../src/context.ts"],"names":[],"mappings":";AAIA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,MAAM,MAAM,mBAAmB,GAAG,CAAC,KAAK,EAAE,KAAK,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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/context",
3
- "version": "0.5.2",
3
+ "version": "0.5.3-main.088a2c8",
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.2",
20
- "@dxos/util": "0.5.2",
21
- "@dxos/node-std": "0.5.2"
19
+ "@dxos/util": "0.5.3-main.088a2c8",
20
+ "@dxos/log": "0.5.3-main.088a2c8",
21
+ "@dxos/node-std": "0.5.3-main.088a2c8"
22
22
  },
23
23
  "publishConfig": {
24
24
  "access": "public"
package/src/context.ts CHANGED
@@ -14,9 +14,10 @@ export type ContextErrorHandler = (error: Error) => void;
14
14
  export type DisposeCallback = () => any | Promise<any>;
15
15
 
16
16
  export type CreateContextParams = {
17
- onError?: ContextErrorHandler;
18
- attributes?: Record<string, any>;
17
+ name?: string;
19
18
  parent?: Context;
19
+ attributes?: Record<string, any>;
20
+ onError?: ContextErrorHandler;
20
21
  };
21
22
 
22
23
  /**
@@ -30,17 +31,22 @@ export class Context {
30
31
  return new Context();
31
32
  }
32
33
 
33
- private readonly _onError: ContextErrorHandler;
34
34
  private readonly _disposeCallbacks: DisposeCallback[] = [];
35
- private _isDisposed = false;
36
- private _disposePromise?: Promise<void> = undefined;
37
- private _parent: Context | null = null;
38
35
 
39
- private _attributes: Record<string, any>;
36
+ private readonly _name?: string;
37
+ private readonly _parent?: Context;
38
+ private readonly _attributes: Record<string, any>;
39
+ private readonly _onError: ContextErrorHandler;
40
+
41
+ private _isDisposed = false;
42
+ private _disposePromise?: Promise<boolean> = undefined;
40
43
 
41
44
  public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
42
45
 
43
46
  constructor({
47
+ name, // TODO(burdon): Automate?
48
+ parent,
49
+ attributes = {},
44
50
  onError = (error) => {
45
51
  if (error instanceof ContextDisposedError) {
46
52
  return;
@@ -51,14 +57,11 @@ export class Context {
51
57
  // Will generate an unhandled rejection.
52
58
  throw error;
53
59
  },
54
- attributes = {},
55
- parent,
56
60
  }: CreateContextParams = {}) {
57
- this._onError = onError;
61
+ this._name = name;
62
+ this._parent = parent;
58
63
  this._attributes = attributes;
59
- if (parent !== undefined) {
60
- this._parent = parent;
61
- }
64
+ this._onError = onError;
62
65
  }
63
66
 
64
67
  get disposed() {
@@ -78,7 +81,7 @@ export class Context {
78
81
  *
79
82
  * @returns A function that can be used to remove the callback from the dispose list.
80
83
  */
81
- onDispose(callback: DisposeCallback) {
84
+ onDispose(callback: DisposeCallback): () => void {
82
85
  if (this._isDisposed) {
83
86
  // Call the callback immediately if the context is already disposed.
84
87
  void (async () => {
@@ -92,12 +95,12 @@ export class Context {
92
95
 
93
96
  this._disposeCallbacks.push(callback);
94
97
  if (this._disposeCallbacks.length > this.maxSafeDisposeCallbacks) {
95
- log.warn('Context has a large number of dispose callbacks. This might be a memory leak.', {
98
+ log.warn('Context has a large number of dispose callbacks (this might be a memory leak).', {
96
99
  count: this._disposeCallbacks.length,
97
- safeThreshold: this.maxSafeDisposeCallbacks,
98
100
  });
99
101
  }
100
102
 
103
+ // Remove handler.
101
104
  return () => {
102
105
  const index = this._disposeCallbacks.indexOf(callback);
103
106
  if (index !== -1) {
@@ -113,30 +116,50 @@ export class Context {
113
116
  * It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.
114
117
  * Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.
115
118
  */
116
- async dispose(): Promise<void> {
119
+ async dispose(throwOnError = false): Promise<boolean> {
117
120
  if (this._disposePromise) {
118
121
  return this._disposePromise;
119
122
  }
123
+
124
+ // TODO(burdon): Probably should not be set until the dispose is complete, but causes tests to fail if moved.
120
125
  this._isDisposed = true;
121
126
 
122
127
  // Set the promise before running the callbacks.
123
- let resolveDispose!: () => void;
124
- this._disposePromise = new Promise<void>((resolve) => {
128
+ let resolveDispose!: (value: boolean) => void;
129
+ this._disposePromise = new Promise<boolean>((resolve) => {
125
130
  resolveDispose = resolve;
126
131
  });
127
132
 
133
+ // Process last first.
128
134
  // Clone the array so that any mutations to the original array don't affect the dispose process.
129
135
  const callbacks = Array.from(this._disposeCallbacks).reverse();
130
136
  this._disposeCallbacks.length = 0;
131
137
 
138
+ if (this._name) {
139
+ log.info('disposing', { context: this._name, count: callbacks.length });
140
+ }
141
+
142
+ let i = 0;
143
+ let clean = true;
132
144
  for (const callback of callbacks) {
133
145
  try {
134
146
  await callback();
135
- } catch (error: any) {
136
- log.catch(error);
147
+ i++;
148
+ } catch (err: any) {
149
+ log.catch(err, { context: this._name, callback: i, count: callbacks.length });
150
+ clean = false;
151
+ if (throwOnError) {
152
+ throw err;
153
+ }
137
154
  }
138
155
  }
139
- resolveDispose();
156
+
157
+ resolveDispose(clean);
158
+ if (this._name) {
159
+ log.info('disposed', { context: this._name });
160
+ }
161
+
162
+ return clean;
140
163
  }
141
164
 
142
165
  /**
@@ -175,6 +198,7 @@ export class Context {
175
198
  },
176
199
  attributes,
177
200
  });
201
+
178
202
  const clearDispose = this.onDispose(() => newCtx.dispose());
179
203
  newCtx.onDispose(clearDispose);
180
204
  return newCtx;
@@ -184,14 +208,14 @@ export class Context {
184
208
  if (key in this._attributes) {
185
209
  return this._attributes[key];
186
210
  }
187
- if (this._parent !== null) {
211
+ if (this._parent) {
188
212
  return this._parent.getAttribute(key);
189
213
  }
214
+
190
215
  return undefined;
191
216
  }
192
217
 
193
218
  [Symbol.toStringTag] = 'Context';
194
-
195
219
  [inspect.custom] = () => this.toString();
196
220
 
197
221
  toString() {