@dxos/context 0.8.4-main.fd6878d → 0.8.4-staging.ac66bdf99f
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/index.mjs +84 -13
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +84 -13
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/context.d.ts +4 -3
- package/dist/types/src/context.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/resource.d.ts +17 -3
- package/dist/types/src/resource.d.ts.map +1 -1
- package/dist/types/src/trace-context.d.ts +54 -0
- package/dist/types/src/trace-context.d.ts.map +1 -0
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +11 -6
- package/src/context.ts +17 -4
- package/src/index.ts +1 -0
- package/src/resource.ts +33 -9
- package/src/trace-context.ts +72 -0
|
@@ -41,6 +41,7 @@ var Context = class _Context {
|
|
|
41
41
|
#onError;
|
|
42
42
|
#flags = 0;
|
|
43
43
|
#disposePromise = void 0;
|
|
44
|
+
#signal = void 0;
|
|
44
45
|
maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
|
|
45
46
|
constructor(params = {}, callMeta) {
|
|
46
47
|
this.#name = getContextName(params, callMeta);
|
|
@@ -66,6 +67,15 @@ var Context = class _Context {
|
|
|
66
67
|
get disposeCallbacksLength() {
|
|
67
68
|
return this.#disposeCallbacks.length;
|
|
68
69
|
}
|
|
70
|
+
get signal() {
|
|
71
|
+
if (this.#signal) {
|
|
72
|
+
return this.#signal;
|
|
73
|
+
}
|
|
74
|
+
const controller = new AbortController();
|
|
75
|
+
this.#signal = controller.signal;
|
|
76
|
+
this.onDispose(() => controller.abort());
|
|
77
|
+
return this.#signal;
|
|
78
|
+
}
|
|
69
79
|
/**
|
|
70
80
|
* Schedules a callback to run when the context is disposed.
|
|
71
81
|
* May be async, in this case the disposer might choose to wait for all resource to released.
|
|
@@ -85,7 +95,7 @@ var Context = class _Context {
|
|
|
85
95
|
context: this.#name
|
|
86
96
|
}, {
|
|
87
97
|
F: __dxlog_file,
|
|
88
|
-
L:
|
|
98
|
+
L: 131,
|
|
89
99
|
S: this,
|
|
90
100
|
C: (f, a) => f(...a)
|
|
91
101
|
});
|
|
@@ -102,7 +112,7 @@ var Context = class _Context {
|
|
|
102
112
|
count: this.#disposeCallbacks.length
|
|
103
113
|
}, {
|
|
104
114
|
F: __dxlog_file,
|
|
105
|
-
L:
|
|
115
|
+
L: 140,
|
|
106
116
|
S: this,
|
|
107
117
|
C: (f, a) => f(...a)
|
|
108
118
|
});
|
|
@@ -140,7 +150,7 @@ var Context = class _Context {
|
|
|
140
150
|
count: callbacks.length
|
|
141
151
|
}, {
|
|
142
152
|
F: __dxlog_file,
|
|
143
|
-
L:
|
|
153
|
+
L: 185,
|
|
144
154
|
S: this,
|
|
145
155
|
C: (f, a) => f(...a)
|
|
146
156
|
});
|
|
@@ -163,7 +173,7 @@ var Context = class _Context {
|
|
|
163
173
|
count: callbacks.length
|
|
164
174
|
}, {
|
|
165
175
|
F: __dxlog_file,
|
|
166
|
-
L:
|
|
176
|
+
L: 200,
|
|
167
177
|
S: this,
|
|
168
178
|
C: (f, a) => f(...a)
|
|
169
179
|
});
|
|
@@ -179,7 +189,7 @@ var Context = class _Context {
|
|
|
179
189
|
context: this.#name
|
|
180
190
|
}, {
|
|
181
191
|
F: __dxlog_file,
|
|
182
|
-
L:
|
|
192
|
+
L: 211,
|
|
183
193
|
S: this,
|
|
184
194
|
C: (f, a) => f(...a)
|
|
185
195
|
});
|
|
@@ -203,6 +213,7 @@ var Context = class _Context {
|
|
|
203
213
|
}
|
|
204
214
|
derive({ onError, attributes } = {}) {
|
|
205
215
|
const newCtx = new _Context({
|
|
216
|
+
parent: this,
|
|
206
217
|
// TODO(dmaretskyi): Optimize to not require allocating a new closure for every context.
|
|
207
218
|
onError: async (error) => {
|
|
208
219
|
if (!onError) {
|
|
@@ -268,13 +279,14 @@ var cancelWithContext = (ctx, promise) => {
|
|
|
268
279
|
};
|
|
269
280
|
|
|
270
281
|
// src/resource.ts
|
|
282
|
+
import "@hazae41/symbol-dispose-polyfill";
|
|
271
283
|
import { throwUnhandledError } from "@dxos/util";
|
|
272
|
-
var LifecycleState = /* @__PURE__ */ function(LifecycleState2) {
|
|
284
|
+
var LifecycleState = /* @__PURE__ */ (function(LifecycleState2) {
|
|
273
285
|
LifecycleState2["CLOSED"] = "CLOSED";
|
|
274
286
|
LifecycleState2["OPEN"] = "OPEN";
|
|
275
287
|
LifecycleState2["ERROR"] = "ERROR";
|
|
276
288
|
return LifecycleState2;
|
|
277
|
-
}({});
|
|
289
|
+
})({});
|
|
278
290
|
var CLOSE_RESOURCE_ON_UNHANDLED_ERROR = false;
|
|
279
291
|
var Resource = class {
|
|
280
292
|
#lifecycleState = "CLOSED";
|
|
@@ -291,6 +303,16 @@ var Resource = class {
|
|
|
291
303
|
* Provided in the open method.
|
|
292
304
|
*/
|
|
293
305
|
#parentCtx = this.#createParentContext();
|
|
306
|
+
/**
|
|
307
|
+
* ```ts
|
|
308
|
+
* await using resource = new Resource();
|
|
309
|
+
* await resource.open();
|
|
310
|
+
* ```
|
|
311
|
+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/using
|
|
312
|
+
*/
|
|
313
|
+
async [Symbol.asyncDispose]() {
|
|
314
|
+
await this.close();
|
|
315
|
+
}
|
|
294
316
|
get #name() {
|
|
295
317
|
return Object.getPrototypeOf(this).constructor.name;
|
|
296
318
|
}
|
|
@@ -306,12 +328,12 @@ var Resource = class {
|
|
|
306
328
|
/**
|
|
307
329
|
* To be overridden by subclasses.
|
|
308
330
|
*/
|
|
309
|
-
async _open(
|
|
331
|
+
async _open(_ctx) {
|
|
310
332
|
}
|
|
311
333
|
/**
|
|
312
334
|
* To be overridden by subclasses.
|
|
313
335
|
*/
|
|
314
|
-
async _close(
|
|
336
|
+
async _close(_ctx) {
|
|
315
337
|
}
|
|
316
338
|
/**
|
|
317
339
|
* Error handler for errors that are caught by the context.
|
|
@@ -328,6 +350,19 @@ var Resource = class {
|
|
|
328
350
|
throw err;
|
|
329
351
|
}
|
|
330
352
|
/**
|
|
353
|
+
* Calls the provided function, opening and closing the resource.
|
|
354
|
+
* NOTE: Consider using `using` instead.
|
|
355
|
+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/using
|
|
356
|
+
*/
|
|
357
|
+
async use(fn) {
|
|
358
|
+
try {
|
|
359
|
+
await this.open();
|
|
360
|
+
return await fn(this);
|
|
361
|
+
} finally {
|
|
362
|
+
await this.close();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
331
366
|
* Opens the resource.
|
|
332
367
|
* If the resource is already open, it does nothing.
|
|
333
368
|
* If the resource is in an error state, it throws an error.
|
|
@@ -373,14 +408,12 @@ var Resource = class {
|
|
|
373
408
|
}
|
|
374
409
|
await this.#openPromise;
|
|
375
410
|
}
|
|
376
|
-
async [Symbol.asyncDispose]() {
|
|
377
|
-
await this.close();
|
|
378
|
-
}
|
|
379
411
|
async #open(ctx) {
|
|
380
412
|
this.#closePromise = null;
|
|
381
413
|
this.#parentCtx = ctx?.derive({
|
|
382
414
|
name: this.#name
|
|
383
415
|
}) ?? this.#createParentContext();
|
|
416
|
+
this.#internalCtx = this.#createContext(this.#parentCtx);
|
|
384
417
|
await this._open(this.#parentCtx);
|
|
385
418
|
this.#lifecycleState = "OPEN";
|
|
386
419
|
}
|
|
@@ -391,9 +424,10 @@ var Resource = class {
|
|
|
391
424
|
this.#internalCtx = this.#createContext();
|
|
392
425
|
this.#lifecycleState = "CLOSED";
|
|
393
426
|
}
|
|
394
|
-
#createContext() {
|
|
427
|
+
#createContext(attributeParent) {
|
|
395
428
|
return new Context({
|
|
396
429
|
name: this.#name,
|
|
430
|
+
parent: attributeParent,
|
|
397
431
|
onError: (error) => queueMicrotask(async () => {
|
|
398
432
|
try {
|
|
399
433
|
await this._catch(error);
|
|
@@ -415,11 +449,48 @@ var openInContext = async (ctx, resource) => {
|
|
|
415
449
|
ctx.onDispose(() => resource.close?.());
|
|
416
450
|
return resource;
|
|
417
451
|
};
|
|
452
|
+
|
|
453
|
+
// src/trace-context.ts
|
|
454
|
+
var TRACE_SPAN_ATTRIBUTE = "dxos.trace-span";
|
|
455
|
+
var ContextRpcCodec = class {
|
|
456
|
+
/**
|
|
457
|
+
* Read the W3C trace context from a DXOS `Context` for an outgoing RPC.
|
|
458
|
+
*
|
|
459
|
+
* @returns `TraceContextData` to attach to the wire message, or `undefined`
|
|
460
|
+
* if the context has no active trace.
|
|
461
|
+
*/
|
|
462
|
+
static encode(ctx) {
|
|
463
|
+
const traceCtx = ctx.getAttribute(TRACE_SPAN_ATTRIBUTE);
|
|
464
|
+
if (traceCtx == null || typeof traceCtx.traceparent !== "string") {
|
|
465
|
+
return void 0;
|
|
466
|
+
}
|
|
467
|
+
return traceCtx;
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Reconstruct a DXOS `Context` from W3C trace context received in an
|
|
471
|
+
* incoming RPC request.
|
|
472
|
+
*
|
|
473
|
+
* @returns A `Context` carrying the trace context, or `Context.default()`
|
|
474
|
+
* if the data is missing/invalid.
|
|
475
|
+
*/
|
|
476
|
+
static decode(traceContext) {
|
|
477
|
+
if (typeof traceContext.traceparent !== "string" || traceContext.traceparent.length === 0) {
|
|
478
|
+
return Context.default();
|
|
479
|
+
}
|
|
480
|
+
return new Context({
|
|
481
|
+
attributes: {
|
|
482
|
+
[TRACE_SPAN_ATTRIBUTE]: traceContext
|
|
483
|
+
}
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
};
|
|
418
487
|
export {
|
|
419
488
|
Context,
|
|
420
489
|
ContextDisposedError,
|
|
490
|
+
ContextRpcCodec,
|
|
421
491
|
LifecycleState,
|
|
422
492
|
Resource,
|
|
493
|
+
TRACE_SPAN_ATTRIBUTE,
|
|
423
494
|
cancelWithContext,
|
|
424
495
|
openInContext,
|
|
425
496
|
rejectOnDispose
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 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 { StackTrace } from '@dxos/debug';\nimport { type CallMetadata, 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\nconst DEBUG_LOG_DISPOSE = false;\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\ntype ContextFlags = number;\n\nconst CONTEXT_FLAG_IS_DISPOSED: ContextFlags = 1 << 0;\n\n/**\n * Whether the dispose callback leak was detected.\n */\nconst CONTEXT_FLAG_LEAK_DETECTED: ContextFlags = 1 << 1;\n\n/**\n * NOTE: Context is not reusable after it is disposed.\n */\n@safeInstanceof('Context')\nexport class Context {\n static default(): Context {\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 #flags: ContextFlags = 0;\n #disposePromise?: Promise<boolean> = undefined;\n\n public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;\n\n constructor(params: CreateContextParams = {}, callMeta?: Partial<CallMetadata>) {\n this.#name = getContextName(params, callMeta);\n this.#parent = params.parent;\n this.#attributes = params.attributes ?? {};\n this.#onError = params.onError ?? DEFAULT_ERROR_HANDLER;\n }\n\n get #isDisposed() {\n return !!(this.#flags & CONTEXT_FLAG_IS_DISPOSED);\n }\n\n set #isDisposed(value: boolean) {\n this.#flags = value ? this.#flags | CONTEXT_FLAG_IS_DISPOSED : this.#flags & ~CONTEXT_FLAG_IS_DISPOSED;\n }\n\n get #leakDetected() {\n return !!(this.#flags & CONTEXT_FLAG_LEAK_DETECTED);\n }\n\n set #leakDetected(value: boolean) {\n this.#flags = value ? this.#flags | CONTEXT_FLAG_LEAK_DETECTED : this.#flags & ~CONTEXT_FLAG_LEAK_DETECTED;\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, { context: this.#name });\n }\n })();\n }\n\n this.#disposeCallbacks.push(callback);\n if (this.#disposeCallbacks.length > this.maxSafeDisposeCallbacks && !this.#leakDetected) {\n this.#leakDetected = true;\n const callSite = new StackTrace().getStackArray(1)[0].trim();\n log.warn('Context has a large number of dispose callbacks (this might be a memory leak).', {\n context: this.#name,\n callSite,\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 * @returns true if there were no errors during the dispose process.\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 (DEBUG_LOG_DISPOSE) {\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 (DEBUG_LOG_DISPOSE) {\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(): string {\n return `Context(${this.#isDisposed ? 'disposed' : 'active'})`;\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n await this.dispose();\n }\n}\n\nconst getContextName = (params: CreateContextParams, callMeta?: Partial<CallMetadata>): string | undefined => {\n if (params.name) {\n return params.name;\n }\n if (callMeta?.F?.length) {\n const pathSegments = callMeta?.F.split('/');\n return `${pathSegments[pathSegments.length - 1]}#${callMeta?.L ?? 0}`;\n }\n return undefined;\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 = this.#createParentContext();\n\n get #name() {\n return Object.getPrototypeOf(this).constructor.name;\n }\n\n get isOpen() {\n return this.#lifecycleState === LifecycleState.OPEN && this.#closePromise == null;\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 /**\n * Waits until the resource is open.\n */\n async waitUntilOpen(): Promise<void> {\n switch (this.#lifecycleState) {\n case LifecycleState.OPEN:\n return;\n case LifecycleState.ERROR:\n throw new Error(`Invalid state: ${this.#lifecycleState}`);\n }\n\n if (!this.#openPromise) {\n throw new Error('Resource is not being opened');\n }\n await this.#openPromise;\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n await this.close();\n }\n\n async #open(ctx?: Context): Promise<void> {\n this.#closePromise = null;\n this.#parentCtx = ctx?.derive({ name: this.#name }) ?? this.#createParentContext();\n await this._open(this.#parentCtx);\n this.#lifecycleState = LifecycleState.OPEN;\n }\n\n async #close(ctx = Context.default()): Promise<void> {\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(): Context {\n return new Context({\n name: this.#name,\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 #createParentContext(): Context {\n return new Context({ name: this.#name });\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 return resource;\n};\n"],
|
|
5
|
-
"mappings": ";AAIA,SAASA,eAAe;AAExB,SAASC,kBAAkB;AAC3B,SAA4BC,WAAW;AACvC,SAASC,sBAAsB;;;ACJxB,IAAMC,uBAAN,cAAmCC,MAAAA;EACxC,cAAc;AACZ,UAAM,mBAAA;EACR;AACF;;;;;;;;;;ADeA,IAAMC,oBAAoB;AAK1B,IAAMC,6BAA6B;AAEnC,IAAMC,wBAA6C,CAACC,OAAOC,QAAAA;AACzD,MAAID,iBAAiBE,sBAAsB;AACzC;EACF;AAEA,OAAKD,IAAIE,QAAO;AAGhB,QAAMH;AACR;AAIA,IAAMI,2BAAyC,KAAK;AAKpD,IAAMC,6BAA2C,KAAK;AAM/C,IAAMC,UAAN,MAAMA,SAAAA;EACX,OAAOC,UAAmB;AACxB,WAAO,IAAID,SAAAA;EACb;EAES,oBAAuC,CAAA;EAEvC,QAAiBE;EACjB,UAAoBA;EACpB;EACA;EAET,SAAuB;EACvB,kBAAqCA;
|
|
6
|
-
"names": ["inspect", "StackTrace", "log", "safeInstanceof", "ContextDisposedError", "Error", "DEBUG_LOG_DISPOSE", "MAX_SAFE_DISPOSE_CALLBACKS", "DEFAULT_ERROR_HANDLER", "error", "ctx", "ContextDisposedError", "dispose", "CONTEXT_FLAG_IS_DISPOSED", "CONTEXT_FLAG_LEAK_DETECTED", "Context", "default", "undefined", "maxSafeDisposeCallbacks", "params", "callMeta", "getContextName", "parent", "attributes", "onError", "value", "disposed", "disposeCallbacksLength", "length", "onDispose", "callback", "log", "catch", "context", "push", "callSite", "StackTrace", "getStackArray", "trim", "warn", "count", "index", "indexOf", "splice", "throwOnError", "resolveDispose", "promise", "Promise", "resolve", "callbacks", "Array", "from", "reverse", "i", "clean", "errors", "err", "AggregateError", "raise", "reject", "derive", "newCtx", "clearDispose", "getAttribute", "key", "Symbol", "toStringTag", "inspect", "custom", "toString", "asyncDispose", "name", "F", "pathSegments", "split", "L", "rejectOnDispose", "ctx", "error", "ContextDisposedError", "Promise", "resolve", "reject", "onDispose", "cancelWithContext", "promise", "clearDispose", "race", "finally", "throwUnhandledError", "LifecycleState", "CLOSE_RESOURCE_ON_UNHANDLED_ERROR", "Resource", "Object", "getPrototypeOf", "name", "isOpen", "_lifecycleState", "_ctx", "_open", "
|
|
3
|
+
"sources": ["../../../src/context.ts", "../../../src/context-disposed-error.ts", "../../../src/promise-utils.ts", "../../../src/resource.ts", "../../../src/trace-context.ts"],
|
|
4
|
+
"sourcesContent": ["//\n// Copyright 2022 DXOS.org\n//\n\nimport { inspect } from 'node:util';\n\nimport { StackTrace } from '@dxos/debug';\nimport { type CallMetadata, 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 CreateContextProps = {\n name?: string;\n parent?: Context;\n attributes?: Record<string, any>;\n onError?: ContextErrorHandler;\n};\n\nconst DEBUG_LOG_DISPOSE = false;\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\ntype ContextFlags = number;\n\nconst CONTEXT_FLAG_IS_DISPOSED: ContextFlags = 1 << 0;\n\n/**\n * Whether the dispose callback leak was detected.\n */\nconst CONTEXT_FLAG_LEAK_DETECTED: ContextFlags = 1 << 1;\n\n/**\n * NOTE: Context is not reusable after it is disposed.\n */\n@safeInstanceof('Context')\nexport class Context {\n static default(): Context {\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 #flags: ContextFlags = 0;\n #disposePromise?: Promise<boolean> = undefined;\n\n #signal: AbortSignal | undefined = undefined;\n\n public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;\n\n constructor(params: CreateContextProps = {}, callMeta?: Partial<CallMetadata>) {\n this.#name = getContextName(params, callMeta);\n this.#parent = params.parent;\n this.#attributes = params.attributes ?? {};\n this.#onError = params.onError ?? DEFAULT_ERROR_HANDLER;\n }\n\n get #isDisposed() {\n return !!(this.#flags & CONTEXT_FLAG_IS_DISPOSED);\n }\n\n set #isDisposed(value: boolean) {\n this.#flags = value ? this.#flags | CONTEXT_FLAG_IS_DISPOSED : this.#flags & ~CONTEXT_FLAG_IS_DISPOSED;\n }\n\n get #leakDetected() {\n return !!(this.#flags & CONTEXT_FLAG_LEAK_DETECTED);\n }\n\n set #leakDetected(value: boolean) {\n this.#flags = value ? this.#flags | CONTEXT_FLAG_LEAK_DETECTED : this.#flags & ~CONTEXT_FLAG_LEAK_DETECTED;\n }\n\n get disposed() {\n return this.#isDisposed;\n }\n\n get disposeCallbacksLength() {\n return this.#disposeCallbacks.length;\n }\n\n get signal(): AbortSignal {\n if (this.#signal) {\n return this.#signal;\n }\n const controller = new AbortController();\n this.#signal = controller.signal;\n this.onDispose(() => controller.abort());\n return this.#signal;\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, { context: this.#name });\n }\n })();\n }\n\n this.#disposeCallbacks.push(callback);\n if (this.#disposeCallbacks.length > this.maxSafeDisposeCallbacks && !this.#leakDetected) {\n this.#leakDetected = true;\n const callSite = new StackTrace().getStackArray(1)[0].trim();\n log.warn('Context has a large number of dispose callbacks (this might be a memory leak).', {\n context: this.#name,\n callSite,\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 * @returns true if there were no errors during the dispose process.\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 (DEBUG_LOG_DISPOSE) {\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 (DEBUG_LOG_DISPOSE) {\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 }: CreateContextProps = {}): Context {\n const newCtx = new Context({\n parent: this,\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(): string {\n return `Context(${this.#isDisposed ? 'disposed' : 'active'})`;\n }\n\n async [Symbol.asyncDispose](): Promise<void> {\n await this.dispose();\n }\n}\n\nconst getContextName = (params: CreateContextProps, callMeta?: Partial<CallMetadata>): string | undefined => {\n if (params.name) {\n return params.name;\n }\n if (callMeta?.F?.length) {\n const pathSegments = callMeta?.F.split('/');\n return `${pathSegments[pathSegments.length - 1]}#${callMeta?.L ?? 0}`;\n }\n return undefined;\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 '@hazae41/symbol-dispose-polyfill';\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\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 = this.#createParentContext();\n\n /**\n * ```ts\n * await using resource = new Resource();\n * await resource.open();\n * ```\n * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/using\n */\n async [Symbol.asyncDispose](): Promise<void> {\n await this.close();\n }\n\n get #name() {\n return Object.getPrototypeOf(this).constructor.name;\n }\n\n get isOpen() {\n return this.#lifecycleState === LifecycleState.OPEN && this.#closePromise == null;\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 * Calls the provided function, opening and closing the resource.\n * NOTE: Consider using `using` instead.\n * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/using\n */\n async use<T>(fn: (resource: this) => Promise<T>): Promise<T> {\n try {\n await this.open();\n return await fn(this);\n } finally {\n await this.close();\n }\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 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 return this;\n }\n\n /**\n * Waits until the resource is open.\n */\n async waitUntilOpen(): Promise<void> {\n switch (this.#lifecycleState) {\n case LifecycleState.OPEN:\n return;\n case LifecycleState.ERROR:\n throw new Error(`Invalid state: ${this.#lifecycleState}`);\n }\n\n if (!this.#openPromise) {\n throw new Error('Resource is not being opened');\n }\n await this.#openPromise;\n }\n\n async #open(ctx?: Context): Promise<void> {\n this.#closePromise = null;\n this.#parentCtx = ctx?.derive({ name: this.#name }) ?? this.#createParentContext();\n this.#internalCtx = this.#createContext(this.#parentCtx);\n await this._open(this.#parentCtx);\n this.#lifecycleState = LifecycleState.OPEN;\n }\n\n async #close(ctx = Context.default()): Promise<void> {\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(attributeParent?: Context): Context {\n return new Context({\n name: this.#name,\n parent: attributeParent,\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 #createParentContext(): Context {\n return new Context({ name: this.#name });\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 return resource;\n};\n", "//\n// Copyright 2026 DXOS.org\n//\n\nimport { Context } from './context';\n\n/**\n * Context attribute key for trace context data.\n * Stores {@link TraceContextData} (W3C traceparent/tracestate strings).\n */\nexport const TRACE_SPAN_ATTRIBUTE = 'dxos.trace-span';\n\n/**\n * W3C Trace Context wire format for propagating trace identity.\n * Stored on DXOS {@link Context} attributes and carried across RPC boundaries.\n *\n * Because these are plain strings (not live runtime objects), they remain valid\n * after the originating span ends — enabling long-lived contexts (`this._ctx`)\n * to serve as parents for later child spans without a retention cache.\n *\n * @see https://www.w3.org/TR/trace-context/\n */\nexport type TraceContextData = {\n /**\n * W3C `traceparent` header value.\n * Format: `{version}-{traceId}-{spanId}-{traceFlags}` (e.g., `00-abc...def-012...789-01`).\n */\n traceparent: string;\n /** Optional W3C `tracestate` header value carrying vendor-specific trace data. */\n tracestate?: string;\n};\n\n/**\n * Codec for propagating trace identity across RPC boundaries.\n *\n * Hardcoded in `RpcPeer` — every outgoing request calls {@link encode} to\n * extract W3C trace context from the DXOS `Context`, and every incoming\n * request calls {@link decode} to reconstruct a DXOS `Context` carrying the\n * caller's trace context.\n *\n * This works because `TRACE_SPAN_ATTRIBUTE` stores serializable\n * {@link TraceContextData} strings, not opaque runtime objects.\n */\nexport class ContextRpcCodec {\n /**\n * Read the W3C trace context from a DXOS `Context` for an outgoing RPC.\n *\n * @returns `TraceContextData` to attach to the wire message, or `undefined`\n * if the context has no active trace.\n */\n static encode(ctx: Context): TraceContextData | undefined {\n const traceCtx = ctx.getAttribute(TRACE_SPAN_ATTRIBUTE);\n if (traceCtx == null || typeof traceCtx.traceparent !== 'string') {\n return undefined;\n }\n return traceCtx as TraceContextData;\n }\n\n /**\n * Reconstruct a DXOS `Context` from W3C trace context received in an\n * incoming RPC request.\n *\n * @returns A `Context` carrying the trace context, or `Context.default()`\n * if the data is missing/invalid.\n */\n static decode(traceContext: TraceContextData): Context {\n if (typeof traceContext.traceparent !== 'string' || traceContext.traceparent.length === 0) {\n return Context.default();\n }\n return new Context({ attributes: { [TRACE_SPAN_ATTRIBUTE]: traceContext } });\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["inspect", "StackTrace", "log", "safeInstanceof", "ContextDisposedError", "Error", "DEBUG_LOG_DISPOSE", "MAX_SAFE_DISPOSE_CALLBACKS", "DEFAULT_ERROR_HANDLER", "error", "ctx", "ContextDisposedError", "dispose", "CONTEXT_FLAG_IS_DISPOSED", "CONTEXT_FLAG_LEAK_DETECTED", "Context", "default", "undefined", "maxSafeDisposeCallbacks", "params", "callMeta", "getContextName", "parent", "attributes", "onError", "value", "disposed", "disposeCallbacksLength", "length", "signal", "controller", "AbortController", "onDispose", "abort", "callback", "log", "catch", "context", "push", "callSite", "StackTrace", "getStackArray", "trim", "warn", "count", "index", "indexOf", "splice", "throwOnError", "resolveDispose", "promise", "Promise", "resolve", "callbacks", "Array", "from", "reverse", "i", "clean", "errors", "err", "AggregateError", "raise", "reject", "derive", "newCtx", "clearDispose", "getAttribute", "key", "Symbol", "toStringTag", "inspect", "custom", "toString", "asyncDispose", "name", "F", "pathSegments", "split", "L", "rejectOnDispose", "ctx", "error", "ContextDisposedError", "Promise", "resolve", "reject", "onDispose", "cancelWithContext", "promise", "clearDispose", "race", "finally", "throwUnhandledError", "LifecycleState", "CLOSE_RESOURCE_ON_UNHANDLED_ERROR", "Resource", "Symbol", "asyncDispose", "close", "Object", "getPrototypeOf", "name", "isOpen", "_lifecycleState", "_ctx", "_open", "_close", "_catch", "err", "doubleErr", "throwUnhandledError", "use", "fn", "open", "ctx", "Error", "waitUntilOpen", "derive", "Context", "default", "dispose", "attributeParent", "parent", "onError", "error", "queueMicrotask", "raise", "openInContext", "resource", "onDispose", "TRACE_SPAN_ATTRIBUTE", "ContextRpcCodec", "encode", "ctx", "traceCtx", "getAttribute", "traceparent", "undefined", "decode", "traceContext", "length", "Context", "default", "attributes"]
|
|
7
7
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"inputs":{"src/context-disposed-error.ts":{"bytes":784,"imports":[],"format":"esm"},"src/context.ts":{"bytes":
|
|
1
|
+
{"inputs":{"src/context-disposed-error.ts":{"bytes":784,"imports":[],"format":"esm"},"src/context.ts":{"bytes":28908,"imports":[{"path":"@dxos/node-std/util","kind":"import-statement","external":true},{"path":"@dxos/debug","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"}],"format":"esm"},"src/promise-utils.ts":{"bytes":3027,"imports":[{"path":"src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"}],"format":"esm"},"src/resource.ts":{"bytes":17647,"imports":[{"path":"@hazae41/symbol-dispose-polyfill","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"src/context.ts","kind":"import-statement","original":"./context"}],"format":"esm"},"src/trace-context.ts":{"bytes":6390,"imports":[{"path":"src/context.ts","kind":"import-statement","original":"./context"}],"format":"esm"},"src/index.ts":{"bytes":865,"imports":[{"path":"src/context.ts","kind":"import-statement","original":"./context"},{"path":"src/context-disposed-error.ts","kind":"import-statement","original":"./context-disposed-error"},{"path":"src/promise-utils.ts","kind":"import-statement","original":"./promise-utils"},{"path":"src/resource.ts","kind":"import-statement","original":"./resource"},{"path":"src/trace-context.ts","kind":"import-statement","original":"./trace-context"}],"format":"esm"}},"outputs":{"dist/lib/browser/index.mjs.map":{"imports":[],"exports":[],"inputs":{},"bytes":26587},"dist/lib/browser/index.mjs":{"imports":[{"path":"@dxos/node-std/util","kind":"import-statement","external":true},{"path":"@dxos/debug","kind":"import-statement","external":true},{"path":"@dxos/log","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true},{"path":"@hazae41/symbol-dispose-polyfill","kind":"import-statement","external":true},{"path":"@dxos/util","kind":"import-statement","external":true}],"exports":["Context","ContextDisposedError","ContextRpcCodec","LifecycleState","Resource","TRACE_SPAN_ATTRIBUTE","cancelWithContext","openInContext","rejectOnDispose"],"entryPoint":"src/index.ts","inputs":{"src/context.ts":{"bytesInOutput":7598},"src/context-disposed-error.ts":{"bytesInOutput":106},"src/index.ts":{"bytesInOutput":0},"src/promise-utils.ts":{"bytesInOutput":410},"src/resource.ts":{"bytesInOutput":4620},"src/trace-context.ts":{"bytesInOutput":1013}},"bytes":14099}}}
|
|
@@ -43,6 +43,7 @@ var Context = class _Context {
|
|
|
43
43
|
#onError;
|
|
44
44
|
#flags = 0;
|
|
45
45
|
#disposePromise = void 0;
|
|
46
|
+
#signal = void 0;
|
|
46
47
|
maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
|
|
47
48
|
constructor(params = {}, callMeta) {
|
|
48
49
|
this.#name = getContextName(params, callMeta);
|
|
@@ -68,6 +69,15 @@ var Context = class _Context {
|
|
|
68
69
|
get disposeCallbacksLength() {
|
|
69
70
|
return this.#disposeCallbacks.length;
|
|
70
71
|
}
|
|
72
|
+
get signal() {
|
|
73
|
+
if (this.#signal) {
|
|
74
|
+
return this.#signal;
|
|
75
|
+
}
|
|
76
|
+
const controller = new AbortController();
|
|
77
|
+
this.#signal = controller.signal;
|
|
78
|
+
this.onDispose(() => controller.abort());
|
|
79
|
+
return this.#signal;
|
|
80
|
+
}
|
|
71
81
|
/**
|
|
72
82
|
* Schedules a callback to run when the context is disposed.
|
|
73
83
|
* May be async, in this case the disposer might choose to wait for all resource to released.
|
|
@@ -87,7 +97,7 @@ var Context = class _Context {
|
|
|
87
97
|
context: this.#name
|
|
88
98
|
}, {
|
|
89
99
|
F: __dxlog_file,
|
|
90
|
-
L:
|
|
100
|
+
L: 131,
|
|
91
101
|
S: this,
|
|
92
102
|
C: (f, a) => f(...a)
|
|
93
103
|
});
|
|
@@ -104,7 +114,7 @@ var Context = class _Context {
|
|
|
104
114
|
count: this.#disposeCallbacks.length
|
|
105
115
|
}, {
|
|
106
116
|
F: __dxlog_file,
|
|
107
|
-
L:
|
|
117
|
+
L: 140,
|
|
108
118
|
S: this,
|
|
109
119
|
C: (f, a) => f(...a)
|
|
110
120
|
});
|
|
@@ -142,7 +152,7 @@ var Context = class _Context {
|
|
|
142
152
|
count: callbacks.length
|
|
143
153
|
}, {
|
|
144
154
|
F: __dxlog_file,
|
|
145
|
-
L:
|
|
155
|
+
L: 185,
|
|
146
156
|
S: this,
|
|
147
157
|
C: (f, a) => f(...a)
|
|
148
158
|
});
|
|
@@ -165,7 +175,7 @@ var Context = class _Context {
|
|
|
165
175
|
count: callbacks.length
|
|
166
176
|
}, {
|
|
167
177
|
F: __dxlog_file,
|
|
168
|
-
L:
|
|
178
|
+
L: 200,
|
|
169
179
|
S: this,
|
|
170
180
|
C: (f, a) => f(...a)
|
|
171
181
|
});
|
|
@@ -181,7 +191,7 @@ var Context = class _Context {
|
|
|
181
191
|
context: this.#name
|
|
182
192
|
}, {
|
|
183
193
|
F: __dxlog_file,
|
|
184
|
-
L:
|
|
194
|
+
L: 211,
|
|
185
195
|
S: this,
|
|
186
196
|
C: (f, a) => f(...a)
|
|
187
197
|
});
|
|
@@ -205,6 +215,7 @@ var Context = class _Context {
|
|
|
205
215
|
}
|
|
206
216
|
derive({ onError, attributes } = {}) {
|
|
207
217
|
const newCtx = new _Context({
|
|
218
|
+
parent: this,
|
|
208
219
|
// TODO(dmaretskyi): Optimize to not require allocating a new closure for every context.
|
|
209
220
|
onError: async (error) => {
|
|
210
221
|
if (!onError) {
|
|
@@ -270,13 +281,14 @@ var cancelWithContext = (ctx, promise) => {
|
|
|
270
281
|
};
|
|
271
282
|
|
|
272
283
|
// src/resource.ts
|
|
284
|
+
import "@hazae41/symbol-dispose-polyfill";
|
|
273
285
|
import { throwUnhandledError } from "@dxos/util";
|
|
274
|
-
var LifecycleState = /* @__PURE__ */ function(LifecycleState2) {
|
|
286
|
+
var LifecycleState = /* @__PURE__ */ (function(LifecycleState2) {
|
|
275
287
|
LifecycleState2["CLOSED"] = "CLOSED";
|
|
276
288
|
LifecycleState2["OPEN"] = "OPEN";
|
|
277
289
|
LifecycleState2["ERROR"] = "ERROR";
|
|
278
290
|
return LifecycleState2;
|
|
279
|
-
}({});
|
|
291
|
+
})({});
|
|
280
292
|
var CLOSE_RESOURCE_ON_UNHANDLED_ERROR = false;
|
|
281
293
|
var Resource = class {
|
|
282
294
|
#lifecycleState = "CLOSED";
|
|
@@ -293,6 +305,16 @@ var Resource = class {
|
|
|
293
305
|
* Provided in the open method.
|
|
294
306
|
*/
|
|
295
307
|
#parentCtx = this.#createParentContext();
|
|
308
|
+
/**
|
|
309
|
+
* ```ts
|
|
310
|
+
* await using resource = new Resource();
|
|
311
|
+
* await resource.open();
|
|
312
|
+
* ```
|
|
313
|
+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/using
|
|
314
|
+
*/
|
|
315
|
+
async [Symbol.asyncDispose]() {
|
|
316
|
+
await this.close();
|
|
317
|
+
}
|
|
296
318
|
get #name() {
|
|
297
319
|
return Object.getPrototypeOf(this).constructor.name;
|
|
298
320
|
}
|
|
@@ -308,12 +330,12 @@ var Resource = class {
|
|
|
308
330
|
/**
|
|
309
331
|
* To be overridden by subclasses.
|
|
310
332
|
*/
|
|
311
|
-
async _open(
|
|
333
|
+
async _open(_ctx) {
|
|
312
334
|
}
|
|
313
335
|
/**
|
|
314
336
|
* To be overridden by subclasses.
|
|
315
337
|
*/
|
|
316
|
-
async _close(
|
|
338
|
+
async _close(_ctx) {
|
|
317
339
|
}
|
|
318
340
|
/**
|
|
319
341
|
* Error handler for errors that are caught by the context.
|
|
@@ -330,6 +352,19 @@ var Resource = class {
|
|
|
330
352
|
throw err;
|
|
331
353
|
}
|
|
332
354
|
/**
|
|
355
|
+
* Calls the provided function, opening and closing the resource.
|
|
356
|
+
* NOTE: Consider using `using` instead.
|
|
357
|
+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/using
|
|
358
|
+
*/
|
|
359
|
+
async use(fn) {
|
|
360
|
+
try {
|
|
361
|
+
await this.open();
|
|
362
|
+
return await fn(this);
|
|
363
|
+
} finally {
|
|
364
|
+
await this.close();
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
333
368
|
* Opens the resource.
|
|
334
369
|
* If the resource is already open, it does nothing.
|
|
335
370
|
* If the resource is in an error state, it throws an error.
|
|
@@ -375,14 +410,12 @@ var Resource = class {
|
|
|
375
410
|
}
|
|
376
411
|
await this.#openPromise;
|
|
377
412
|
}
|
|
378
|
-
async [Symbol.asyncDispose]() {
|
|
379
|
-
await this.close();
|
|
380
|
-
}
|
|
381
413
|
async #open(ctx) {
|
|
382
414
|
this.#closePromise = null;
|
|
383
415
|
this.#parentCtx = ctx?.derive({
|
|
384
416
|
name: this.#name
|
|
385
417
|
}) ?? this.#createParentContext();
|
|
418
|
+
this.#internalCtx = this.#createContext(this.#parentCtx);
|
|
386
419
|
await this._open(this.#parentCtx);
|
|
387
420
|
this.#lifecycleState = "OPEN";
|
|
388
421
|
}
|
|
@@ -393,9 +426,10 @@ var Resource = class {
|
|
|
393
426
|
this.#internalCtx = this.#createContext();
|
|
394
427
|
this.#lifecycleState = "CLOSED";
|
|
395
428
|
}
|
|
396
|
-
#createContext() {
|
|
429
|
+
#createContext(attributeParent) {
|
|
397
430
|
return new Context({
|
|
398
431
|
name: this.#name,
|
|
432
|
+
parent: attributeParent,
|
|
399
433
|
onError: (error) => queueMicrotask(async () => {
|
|
400
434
|
try {
|
|
401
435
|
await this._catch(error);
|
|
@@ -417,11 +451,48 @@ var openInContext = async (ctx, resource) => {
|
|
|
417
451
|
ctx.onDispose(() => resource.close?.());
|
|
418
452
|
return resource;
|
|
419
453
|
};
|
|
454
|
+
|
|
455
|
+
// src/trace-context.ts
|
|
456
|
+
var TRACE_SPAN_ATTRIBUTE = "dxos.trace-span";
|
|
457
|
+
var ContextRpcCodec = class {
|
|
458
|
+
/**
|
|
459
|
+
* Read the W3C trace context from a DXOS `Context` for an outgoing RPC.
|
|
460
|
+
*
|
|
461
|
+
* @returns `TraceContextData` to attach to the wire message, or `undefined`
|
|
462
|
+
* if the context has no active trace.
|
|
463
|
+
*/
|
|
464
|
+
static encode(ctx) {
|
|
465
|
+
const traceCtx = ctx.getAttribute(TRACE_SPAN_ATTRIBUTE);
|
|
466
|
+
if (traceCtx == null || typeof traceCtx.traceparent !== "string") {
|
|
467
|
+
return void 0;
|
|
468
|
+
}
|
|
469
|
+
return traceCtx;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Reconstruct a DXOS `Context` from W3C trace context received in an
|
|
473
|
+
* incoming RPC request.
|
|
474
|
+
*
|
|
475
|
+
* @returns A `Context` carrying the trace context, or `Context.default()`
|
|
476
|
+
* if the data is missing/invalid.
|
|
477
|
+
*/
|
|
478
|
+
static decode(traceContext) {
|
|
479
|
+
if (typeof traceContext.traceparent !== "string" || traceContext.traceparent.length === 0) {
|
|
480
|
+
return Context.default();
|
|
481
|
+
}
|
|
482
|
+
return new Context({
|
|
483
|
+
attributes: {
|
|
484
|
+
[TRACE_SPAN_ATTRIBUTE]: traceContext
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
};
|
|
420
489
|
export {
|
|
421
490
|
Context,
|
|
422
491
|
ContextDisposedError,
|
|
492
|
+
ContextRpcCodec,
|
|
423
493
|
LifecycleState,
|
|
424
494
|
Resource,
|
|
495
|
+
TRACE_SPAN_ATTRIBUTE,
|
|
425
496
|
cancelWithContext,
|
|
426
497
|
openInContext,
|
|
427
498
|
rejectOnDispose
|