@dxos/context 0.8.3 → 0.8.4-main.16b68245aa
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 +98 -59
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/node-esm/index.mjs +98 -59
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/types/src/context-disposed-error.d.ts.map +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/promise-utils.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 +13 -11
- 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
- package/dist/lib/node/index.cjs +0 -454
- package/dist/lib/node/index.cjs.map +0 -7
- package/dist/lib/node/meta.json +0 -1
package/src/context.ts
CHANGED
|
@@ -14,7 +14,7 @@ export type ContextErrorHandler = (error: Error, ctx: Context) => void;
|
|
|
14
14
|
|
|
15
15
|
export type DisposeCallback = () => any | Promise<any>;
|
|
16
16
|
|
|
17
|
-
export type
|
|
17
|
+
export type CreateContextProps = {
|
|
18
18
|
name?: string;
|
|
19
19
|
parent?: Context;
|
|
20
20
|
attributes?: Record<string, any>;
|
|
@@ -67,9 +67,11 @@ export class Context {
|
|
|
67
67
|
#flags: ContextFlags = 0;
|
|
68
68
|
#disposePromise?: Promise<boolean> = undefined;
|
|
69
69
|
|
|
70
|
+
#signal: AbortSignal | undefined = undefined;
|
|
71
|
+
|
|
70
72
|
public maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
|
|
71
73
|
|
|
72
|
-
constructor(params:
|
|
74
|
+
constructor(params: CreateContextProps = {}, callMeta?: Partial<CallMetadata>) {
|
|
73
75
|
this.#name = getContextName(params, callMeta);
|
|
74
76
|
this.#parent = params.parent;
|
|
75
77
|
this.#attributes = params.attributes ?? {};
|
|
@@ -100,6 +102,16 @@ export class Context {
|
|
|
100
102
|
return this.#disposeCallbacks.length;
|
|
101
103
|
}
|
|
102
104
|
|
|
105
|
+
get signal(): AbortSignal {
|
|
106
|
+
if (this.#signal) {
|
|
107
|
+
return this.#signal;
|
|
108
|
+
}
|
|
109
|
+
const controller = new AbortController();
|
|
110
|
+
this.#signal = controller.signal;
|
|
111
|
+
this.onDispose(() => controller.abort());
|
|
112
|
+
return this.#signal;
|
|
113
|
+
}
|
|
114
|
+
|
|
103
115
|
/**
|
|
104
116
|
* Schedules a callback to run when the context is disposed.
|
|
105
117
|
* May be async, in this case the disposer might choose to wait for all resource to released.
|
|
@@ -222,8 +234,9 @@ export class Context {
|
|
|
222
234
|
}
|
|
223
235
|
}
|
|
224
236
|
|
|
225
|
-
derive({ onError, attributes }:
|
|
237
|
+
derive({ onError, attributes }: CreateContextProps = {}): Context {
|
|
226
238
|
const newCtx = new Context({
|
|
239
|
+
parent: this,
|
|
227
240
|
// TODO(dmaretskyi): Optimize to not require allocating a new closure for every context.
|
|
228
241
|
onError: async (error) => {
|
|
229
242
|
if (!onError) {
|
|
@@ -267,7 +280,7 @@ export class Context {
|
|
|
267
280
|
}
|
|
268
281
|
}
|
|
269
282
|
|
|
270
|
-
const getContextName = (params:
|
|
283
|
+
const getContextName = (params: CreateContextProps, callMeta?: Partial<CallMetadata>): string | undefined => {
|
|
271
284
|
if (params.name) {
|
|
272
285
|
return params.name;
|
|
273
286
|
}
|
package/src/index.ts
CHANGED
package/src/resource.ts
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
// Copyright 2024 DXOS.org
|
|
3
3
|
//
|
|
4
4
|
|
|
5
|
+
import '@hazae41/symbol-dispose-polyfill';
|
|
6
|
+
|
|
5
7
|
import { throwUnhandledError } from '@dxos/util';
|
|
6
8
|
|
|
7
9
|
import { Context } from './context';
|
|
@@ -25,6 +27,7 @@ const CLOSE_RESOURCE_ON_UNHANDLED_ERROR = false;
|
|
|
25
27
|
*/
|
|
26
28
|
export abstract class Resource implements Lifecycle {
|
|
27
29
|
#lifecycleState = LifecycleState.CLOSED;
|
|
30
|
+
|
|
28
31
|
#openPromise: Promise<void> | null = null;
|
|
29
32
|
#closePromise: Promise<void> | null = null;
|
|
30
33
|
|
|
@@ -41,6 +44,17 @@ export abstract class Resource implements Lifecycle {
|
|
|
41
44
|
*/
|
|
42
45
|
#parentCtx: Context = this.#createParentContext();
|
|
43
46
|
|
|
47
|
+
/**
|
|
48
|
+
* ```ts
|
|
49
|
+
* await using resource = new Resource();
|
|
50
|
+
* await resource.open();
|
|
51
|
+
* ```
|
|
52
|
+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/using
|
|
53
|
+
*/
|
|
54
|
+
async [Symbol.asyncDispose](): Promise<void> {
|
|
55
|
+
await this.close();
|
|
56
|
+
}
|
|
57
|
+
|
|
44
58
|
get #name() {
|
|
45
59
|
return Object.getPrototypeOf(this).constructor.name;
|
|
46
60
|
}
|
|
@@ -60,12 +74,12 @@ export abstract class Resource implements Lifecycle {
|
|
|
60
74
|
/**
|
|
61
75
|
* To be overridden by subclasses.
|
|
62
76
|
*/
|
|
63
|
-
protected async _open(
|
|
77
|
+
protected async _open(_ctx: Context): Promise<void> {}
|
|
64
78
|
|
|
65
79
|
/**
|
|
66
80
|
* To be overridden by subclasses.
|
|
67
81
|
*/
|
|
68
|
-
protected async _close(
|
|
82
|
+
protected async _close(_ctx: Context): Promise<void> {}
|
|
69
83
|
|
|
70
84
|
/**
|
|
71
85
|
* Error handler for errors that are caught by the context.
|
|
@@ -82,6 +96,20 @@ export abstract class Resource implements Lifecycle {
|
|
|
82
96
|
throw err;
|
|
83
97
|
}
|
|
84
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Calls the provided function, opening and closing the resource.
|
|
101
|
+
* NOTE: Consider using `using` instead.
|
|
102
|
+
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/using
|
|
103
|
+
*/
|
|
104
|
+
async use<T>(fn: (resource: this) => Promise<T>): Promise<T> {
|
|
105
|
+
try {
|
|
106
|
+
await this.open();
|
|
107
|
+
return await fn(this);
|
|
108
|
+
} finally {
|
|
109
|
+
await this.close();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
85
113
|
/**
|
|
86
114
|
* Opens the resource.
|
|
87
115
|
* If the resource is already open, it does nothing.
|
|
@@ -100,7 +128,6 @@ export abstract class Resource implements Lifecycle {
|
|
|
100
128
|
|
|
101
129
|
await this.#closePromise;
|
|
102
130
|
await (this.#openPromise ??= this.#open(ctx));
|
|
103
|
-
|
|
104
131
|
return this;
|
|
105
132
|
}
|
|
106
133
|
|
|
@@ -114,7 +141,6 @@ export abstract class Resource implements Lifecycle {
|
|
|
114
141
|
}
|
|
115
142
|
await this.#openPromise;
|
|
116
143
|
await (this.#closePromise ??= this.#close(ctx));
|
|
117
|
-
|
|
118
144
|
return this;
|
|
119
145
|
}
|
|
120
146
|
|
|
@@ -135,13 +161,10 @@ export abstract class Resource implements Lifecycle {
|
|
|
135
161
|
await this.#openPromise;
|
|
136
162
|
}
|
|
137
163
|
|
|
138
|
-
async [Symbol.asyncDispose](): Promise<void> {
|
|
139
|
-
await this.close();
|
|
140
|
-
}
|
|
141
|
-
|
|
142
164
|
async #open(ctx?: Context): Promise<void> {
|
|
143
165
|
this.#closePromise = null;
|
|
144
166
|
this.#parentCtx = ctx?.derive({ name: this.#name }) ?? this.#createParentContext();
|
|
167
|
+
this.#internalCtx = this.#createContext(this.#parentCtx);
|
|
145
168
|
await this._open(this.#parentCtx);
|
|
146
169
|
this.#lifecycleState = LifecycleState.OPEN;
|
|
147
170
|
}
|
|
@@ -154,9 +177,10 @@ export abstract class Resource implements Lifecycle {
|
|
|
154
177
|
this.#lifecycleState = LifecycleState.CLOSED;
|
|
155
178
|
}
|
|
156
179
|
|
|
157
|
-
#createContext(): Context {
|
|
180
|
+
#createContext(attributeParent?: Context): Context {
|
|
158
181
|
return new Context({
|
|
159
182
|
name: this.#name,
|
|
183
|
+
parent: attributeParent,
|
|
160
184
|
onError: (error) =>
|
|
161
185
|
queueMicrotask(async () => {
|
|
162
186
|
try {
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2026 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { Context } from './context';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Context attribute key for trace context data.
|
|
9
|
+
* Stores {@link TraceContextData} (W3C traceparent/tracestate strings).
|
|
10
|
+
*/
|
|
11
|
+
export const TRACE_SPAN_ATTRIBUTE = 'dxos.trace-span';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* W3C Trace Context wire format for propagating trace identity.
|
|
15
|
+
* Stored on DXOS {@link Context} attributes and carried across RPC boundaries.
|
|
16
|
+
*
|
|
17
|
+
* Because these are plain strings (not live runtime objects), they remain valid
|
|
18
|
+
* after the originating span ends — enabling long-lived contexts (`this._ctx`)
|
|
19
|
+
* to serve as parents for later child spans without a retention cache.
|
|
20
|
+
*
|
|
21
|
+
* @see https://www.w3.org/TR/trace-context/
|
|
22
|
+
*/
|
|
23
|
+
export type TraceContextData = {
|
|
24
|
+
/**
|
|
25
|
+
* W3C `traceparent` header value.
|
|
26
|
+
* Format: `{version}-{traceId}-{spanId}-{traceFlags}` (e.g., `00-abc...def-012...789-01`).
|
|
27
|
+
*/
|
|
28
|
+
traceparent: string;
|
|
29
|
+
/** Optional W3C `tracestate` header value carrying vendor-specific trace data. */
|
|
30
|
+
tracestate?: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Codec for propagating trace identity across RPC boundaries.
|
|
35
|
+
*
|
|
36
|
+
* Hardcoded in `RpcPeer` — every outgoing request calls {@link encode} to
|
|
37
|
+
* extract W3C trace context from the DXOS `Context`, and every incoming
|
|
38
|
+
* request calls {@link decode} to reconstruct a DXOS `Context` carrying the
|
|
39
|
+
* caller's trace context.
|
|
40
|
+
*
|
|
41
|
+
* This works because `TRACE_SPAN_ATTRIBUTE` stores serializable
|
|
42
|
+
* {@link TraceContextData} strings, not opaque runtime objects.
|
|
43
|
+
*/
|
|
44
|
+
export class ContextRpcCodec {
|
|
45
|
+
/**
|
|
46
|
+
* Read the W3C trace context from a DXOS `Context` for an outgoing RPC.
|
|
47
|
+
*
|
|
48
|
+
* @returns `TraceContextData` to attach to the wire message, or `undefined`
|
|
49
|
+
* if the context has no active trace.
|
|
50
|
+
*/
|
|
51
|
+
static encode(ctx: Context): TraceContextData | undefined {
|
|
52
|
+
const traceCtx = ctx.getAttribute(TRACE_SPAN_ATTRIBUTE);
|
|
53
|
+
if (traceCtx == null || typeof traceCtx.traceparent !== 'string') {
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
return traceCtx as TraceContextData;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Reconstruct a DXOS `Context` from W3C trace context received in an
|
|
61
|
+
* incoming RPC request.
|
|
62
|
+
*
|
|
63
|
+
* @returns A `Context` carrying the trace context, or `Context.default()`
|
|
64
|
+
* if the data is missing/invalid.
|
|
65
|
+
*/
|
|
66
|
+
static decode(traceContext: TraceContextData): Context {
|
|
67
|
+
if (typeof traceContext.traceparent !== 'string' || traceContext.traceparent.length === 0) {
|
|
68
|
+
return Context.default();
|
|
69
|
+
}
|
|
70
|
+
return new Context({ attributes: { [TRACE_SPAN_ATTRIBUTE]: traceContext } });
|
|
71
|
+
}
|
|
72
|
+
}
|
package/dist/lib/node/index.cjs
DELETED
|
@@ -1,454 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
-
var __export = (target, all) => {
|
|
7
|
-
for (var name in all)
|
|
8
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
-
};
|
|
10
|
-
var __copyProps = (to, from, except, desc) => {
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
-
for (let key of __getOwnPropNames(from))
|
|
13
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
-
}
|
|
16
|
-
return to;
|
|
17
|
-
};
|
|
18
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
-
var node_exports = {};
|
|
20
|
-
__export(node_exports, {
|
|
21
|
-
Context: () => Context,
|
|
22
|
-
ContextDisposedError: () => ContextDisposedError,
|
|
23
|
-
LifecycleState: () => LifecycleState,
|
|
24
|
-
Resource: () => Resource,
|
|
25
|
-
cancelWithContext: () => cancelWithContext,
|
|
26
|
-
openInContext: () => openInContext,
|
|
27
|
-
rejectOnDispose: () => rejectOnDispose
|
|
28
|
-
});
|
|
29
|
-
module.exports = __toCommonJS(node_exports);
|
|
30
|
-
var import_node_util = require("node:util");
|
|
31
|
-
var import_debug = require("@dxos/debug");
|
|
32
|
-
var import_log = require("@dxos/log");
|
|
33
|
-
var import_util = require("@dxos/util");
|
|
34
|
-
var import_util2 = require("@dxos/util");
|
|
35
|
-
var ContextDisposedError = class extends Error {
|
|
36
|
-
constructor() {
|
|
37
|
-
super("Context disposed.");
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
|
-
function _ts_decorate(decorators, target, key, desc) {
|
|
41
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
42
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
43
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
44
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
45
|
-
}
|
|
46
|
-
var __dxlog_file = "/home/runner/work/dxos/dxos/packages/common/context/src/context.ts";
|
|
47
|
-
var DEBUG_LOG_DISPOSE = false;
|
|
48
|
-
var MAX_SAFE_DISPOSE_CALLBACKS = 300;
|
|
49
|
-
var DEFAULT_ERROR_HANDLER = (error, ctx) => {
|
|
50
|
-
if (error instanceof ContextDisposedError) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
void ctx.dispose();
|
|
54
|
-
throw error;
|
|
55
|
-
};
|
|
56
|
-
var CONTEXT_FLAG_IS_DISPOSED = 1 << 0;
|
|
57
|
-
var CONTEXT_FLAG_LEAK_DETECTED = 1 << 1;
|
|
58
|
-
var _a, _b;
|
|
59
|
-
var Context = class _Context {
|
|
60
|
-
constructor(params = {}, callMeta) {
|
|
61
|
-
this.#disposeCallbacks = [];
|
|
62
|
-
this.#name = void 0;
|
|
63
|
-
this.#parent = void 0;
|
|
64
|
-
this.#flags = 0;
|
|
65
|
-
this.#disposePromise = void 0;
|
|
66
|
-
this.maxSafeDisposeCallbacks = MAX_SAFE_DISPOSE_CALLBACKS;
|
|
67
|
-
this[_b] = "Context";
|
|
68
|
-
this[_a] = () => this.toString();
|
|
69
|
-
this.#name = getContextName(params, callMeta);
|
|
70
|
-
this.#parent = params.parent;
|
|
71
|
-
this.#attributes = params.attributes ?? {};
|
|
72
|
-
this.#onError = params.onError ?? DEFAULT_ERROR_HANDLER;
|
|
73
|
-
}
|
|
74
|
-
static default() {
|
|
75
|
-
return new _Context();
|
|
76
|
-
}
|
|
77
|
-
#disposeCallbacks;
|
|
78
|
-
#name;
|
|
79
|
-
#parent;
|
|
80
|
-
#attributes;
|
|
81
|
-
#onError;
|
|
82
|
-
#flags;
|
|
83
|
-
#disposePromise;
|
|
84
|
-
get #isDisposed() {
|
|
85
|
-
return !!(this.#flags & CONTEXT_FLAG_IS_DISPOSED);
|
|
86
|
-
}
|
|
87
|
-
set #isDisposed(value) {
|
|
88
|
-
this.#flags = value ? this.#flags | CONTEXT_FLAG_IS_DISPOSED : this.#flags & ~CONTEXT_FLAG_IS_DISPOSED;
|
|
89
|
-
}
|
|
90
|
-
get #leakDetected() {
|
|
91
|
-
return !!(this.#flags & CONTEXT_FLAG_LEAK_DETECTED);
|
|
92
|
-
}
|
|
93
|
-
set #leakDetected(value) {
|
|
94
|
-
this.#flags = value ? this.#flags | CONTEXT_FLAG_LEAK_DETECTED : this.#flags & ~CONTEXT_FLAG_LEAK_DETECTED;
|
|
95
|
-
}
|
|
96
|
-
get disposed() {
|
|
97
|
-
return this.#isDisposed;
|
|
98
|
-
}
|
|
99
|
-
get disposeCallbacksLength() {
|
|
100
|
-
return this.#disposeCallbacks.length;
|
|
101
|
-
}
|
|
102
|
-
/**
|
|
103
|
-
* Schedules a callback to run when the context is disposed.
|
|
104
|
-
* May be async, in this case the disposer might choose to wait for all resource to released.
|
|
105
|
-
* Throwing an error inside the callback will result in the error being logged, but not re-thrown.
|
|
106
|
-
*
|
|
107
|
-
* NOTE: Will call the callback immediately if the context is already disposed.
|
|
108
|
-
*
|
|
109
|
-
* @returns A function that can be used to remove the callback from the dispose list.
|
|
110
|
-
*/
|
|
111
|
-
onDispose(callback) {
|
|
112
|
-
if (this.#isDisposed) {
|
|
113
|
-
void (async () => {
|
|
114
|
-
try {
|
|
115
|
-
await callback();
|
|
116
|
-
} catch (error) {
|
|
117
|
-
import_log.log.catch(error, {
|
|
118
|
-
context: this.#name
|
|
119
|
-
}, {
|
|
120
|
-
F: __dxlog_file,
|
|
121
|
-
L: 119,
|
|
122
|
-
S: this,
|
|
123
|
-
C: (f, a) => f(...a)
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
})();
|
|
127
|
-
}
|
|
128
|
-
this.#disposeCallbacks.push(callback);
|
|
129
|
-
if (this.#disposeCallbacks.length > this.maxSafeDisposeCallbacks && !this.#leakDetected) {
|
|
130
|
-
this.#leakDetected = true;
|
|
131
|
-
const callSite = new import_debug.StackTrace().getStackArray(1)[0].trim();
|
|
132
|
-
import_log.log.warn("Context has a large number of dispose callbacks (this might be a memory leak).", {
|
|
133
|
-
context: this.#name,
|
|
134
|
-
callSite,
|
|
135
|
-
count: this.#disposeCallbacks.length
|
|
136
|
-
}, {
|
|
137
|
-
F: __dxlog_file,
|
|
138
|
-
L: 128,
|
|
139
|
-
S: this,
|
|
140
|
-
C: (f, a) => f(...a)
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
return () => {
|
|
144
|
-
const index = this.#disposeCallbacks.indexOf(callback);
|
|
145
|
-
if (index !== -1) {
|
|
146
|
-
this.#disposeCallbacks.splice(index, 1);
|
|
147
|
-
}
|
|
148
|
-
};
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Runs all dispose callbacks.
|
|
152
|
-
* Callbacks are run in the reverse order they were added.
|
|
153
|
-
* This function never throws.
|
|
154
|
-
* It is safe to ignore the returned promise if the caller does not wish to wait for callbacks to complete.
|
|
155
|
-
* Disposing context means that onDispose will throw an error and any errors raised will be logged and not propagated.
|
|
156
|
-
* @returns true if there were no errors during the dispose process.
|
|
157
|
-
*/
|
|
158
|
-
async dispose(throwOnError = false) {
|
|
159
|
-
if (this.#disposePromise) {
|
|
160
|
-
return this.#disposePromise;
|
|
161
|
-
}
|
|
162
|
-
this.#isDisposed = true;
|
|
163
|
-
let resolveDispose;
|
|
164
|
-
const promise = new Promise((resolve) => {
|
|
165
|
-
resolveDispose = resolve;
|
|
166
|
-
});
|
|
167
|
-
this.#disposePromise = promise;
|
|
168
|
-
const callbacks = Array.from(this.#disposeCallbacks).reverse();
|
|
169
|
-
this.#disposeCallbacks.length = 0;
|
|
170
|
-
if (DEBUG_LOG_DISPOSE) {
|
|
171
|
-
(0, import_log.log)("disposing", {
|
|
172
|
-
context: this.#name,
|
|
173
|
-
count: callbacks.length
|
|
174
|
-
}, {
|
|
175
|
-
F: __dxlog_file,
|
|
176
|
-
L: 173,
|
|
177
|
-
S: this,
|
|
178
|
-
C: (f, a) => f(...a)
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
let i = 0;
|
|
182
|
-
let clean = true;
|
|
183
|
-
const errors = [];
|
|
184
|
-
for (const callback of callbacks) {
|
|
185
|
-
try {
|
|
186
|
-
await callback();
|
|
187
|
-
i++;
|
|
188
|
-
} catch (err) {
|
|
189
|
-
clean = false;
|
|
190
|
-
if (throwOnError) {
|
|
191
|
-
errors.push(err);
|
|
192
|
-
} else {
|
|
193
|
-
import_log.log.catch(err, {
|
|
194
|
-
context: this.#name,
|
|
195
|
-
callback: i,
|
|
196
|
-
count: callbacks.length
|
|
197
|
-
}, {
|
|
198
|
-
F: __dxlog_file,
|
|
199
|
-
L: 188,
|
|
200
|
-
S: this,
|
|
201
|
-
C: (f, a) => f(...a)
|
|
202
|
-
});
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
if (errors.length > 0) {
|
|
207
|
-
throw new AggregateError(errors);
|
|
208
|
-
}
|
|
209
|
-
resolveDispose(clean);
|
|
210
|
-
if (DEBUG_LOG_DISPOSE) {
|
|
211
|
-
(0, import_log.log)("disposed", {
|
|
212
|
-
context: this.#name
|
|
213
|
-
}, {
|
|
214
|
-
F: __dxlog_file,
|
|
215
|
-
L: 199,
|
|
216
|
-
S: this,
|
|
217
|
-
C: (f, a) => f(...a)
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
return clean;
|
|
221
|
-
}
|
|
222
|
-
/**
|
|
223
|
-
* Raise the error inside the context.
|
|
224
|
-
* The error will be propagated to the error handler.
|
|
225
|
-
* IF the error handler is not set, the error will dispose the context and cause an unhandled rejection.
|
|
226
|
-
*/
|
|
227
|
-
raise(error) {
|
|
228
|
-
if (this.#isDisposed) {
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
try {
|
|
232
|
-
this.#onError(error, this);
|
|
233
|
-
} catch (err) {
|
|
234
|
-
void Promise.reject(err);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
derive({ onError, attributes } = {}) {
|
|
238
|
-
const newCtx = new _Context({
|
|
239
|
-
// TODO(dmaretskyi): Optimize to not require allocating a new closure for every context.
|
|
240
|
-
onError: async (error) => {
|
|
241
|
-
if (!onError) {
|
|
242
|
-
this.raise(error);
|
|
243
|
-
} else {
|
|
244
|
-
try {
|
|
245
|
-
await onError(error, this);
|
|
246
|
-
} catch {
|
|
247
|
-
this.raise(error);
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
},
|
|
251
|
-
attributes
|
|
252
|
-
});
|
|
253
|
-
const clearDispose = this.onDispose(() => newCtx.dispose());
|
|
254
|
-
newCtx.onDispose(clearDispose);
|
|
255
|
-
return newCtx;
|
|
256
|
-
}
|
|
257
|
-
getAttribute(key) {
|
|
258
|
-
if (key in this.#attributes) {
|
|
259
|
-
return this.#attributes[key];
|
|
260
|
-
}
|
|
261
|
-
if (this.#parent) {
|
|
262
|
-
return this.#parent.getAttribute(key);
|
|
263
|
-
}
|
|
264
|
-
return void 0;
|
|
265
|
-
}
|
|
266
|
-
toString() {
|
|
267
|
-
return `Context(${this.#isDisposed ? "disposed" : "active"})`;
|
|
268
|
-
}
|
|
269
|
-
async [(_b = Symbol.toStringTag, _a = import_node_util.inspect.custom, Symbol.asyncDispose)]() {
|
|
270
|
-
await this.dispose();
|
|
271
|
-
}
|
|
272
|
-
};
|
|
273
|
-
Context = _ts_decorate([
|
|
274
|
-
(0, import_util.safeInstanceof)("Context")
|
|
275
|
-
], Context);
|
|
276
|
-
var getContextName = (params, callMeta) => {
|
|
277
|
-
if (params.name) {
|
|
278
|
-
return params.name;
|
|
279
|
-
}
|
|
280
|
-
if (callMeta?.F?.length) {
|
|
281
|
-
const pathSegments = callMeta?.F.split("/");
|
|
282
|
-
return `${pathSegments[pathSegments.length - 1]}#${callMeta?.L ?? 0}`;
|
|
283
|
-
}
|
|
284
|
-
return void 0;
|
|
285
|
-
};
|
|
286
|
-
var rejectOnDispose = (ctx, error = new ContextDisposedError()) => new Promise((resolve, reject) => {
|
|
287
|
-
ctx.onDispose(() => reject(error));
|
|
288
|
-
});
|
|
289
|
-
var cancelWithContext = (ctx, promise) => {
|
|
290
|
-
let clearDispose;
|
|
291
|
-
return Promise.race([
|
|
292
|
-
promise,
|
|
293
|
-
new Promise((resolve, reject) => {
|
|
294
|
-
clearDispose = ctx.onDispose(() => reject(new ContextDisposedError()));
|
|
295
|
-
})
|
|
296
|
-
]).finally(() => clearDispose?.());
|
|
297
|
-
};
|
|
298
|
-
var LifecycleState = /* @__PURE__ */ function(LifecycleState2) {
|
|
299
|
-
LifecycleState2["CLOSED"] = "CLOSED";
|
|
300
|
-
LifecycleState2["OPEN"] = "OPEN";
|
|
301
|
-
LifecycleState2["ERROR"] = "ERROR";
|
|
302
|
-
return LifecycleState2;
|
|
303
|
-
}({});
|
|
304
|
-
var CLOSE_RESOURCE_ON_UNHANDLED_ERROR = false;
|
|
305
|
-
var Resource = class {
|
|
306
|
-
#lifecycleState = "CLOSED";
|
|
307
|
-
#openPromise = null;
|
|
308
|
-
#closePromise = null;
|
|
309
|
-
/**
|
|
310
|
-
* Managed internally by the resource.
|
|
311
|
-
* Recreated on close.
|
|
312
|
-
* Errors are propagated to the `_catch` method and the parent context.
|
|
313
|
-
*/
|
|
314
|
-
#internalCtx = this.#createContext();
|
|
315
|
-
/**
|
|
316
|
-
* Context that is used to bubble up errors that are not handled by the resource.
|
|
317
|
-
* Provided in the open method.
|
|
318
|
-
*/
|
|
319
|
-
#parentCtx = this.#createParentContext();
|
|
320
|
-
get #name() {
|
|
321
|
-
return Object.getPrototypeOf(this).constructor.name;
|
|
322
|
-
}
|
|
323
|
-
get isOpen() {
|
|
324
|
-
return this.#lifecycleState === "OPEN" && this.#closePromise == null;
|
|
325
|
-
}
|
|
326
|
-
get _lifecycleState() {
|
|
327
|
-
return this.#lifecycleState;
|
|
328
|
-
}
|
|
329
|
-
get _ctx() {
|
|
330
|
-
return this.#internalCtx;
|
|
331
|
-
}
|
|
332
|
-
/**
|
|
333
|
-
* To be overridden by subclasses.
|
|
334
|
-
*/
|
|
335
|
-
async _open(ctx) {
|
|
336
|
-
}
|
|
337
|
-
/**
|
|
338
|
-
* To be overridden by subclasses.
|
|
339
|
-
*/
|
|
340
|
-
async _close(ctx) {
|
|
341
|
-
}
|
|
342
|
-
/**
|
|
343
|
-
* Error handler for errors that are caught by the context.
|
|
344
|
-
* By default, errors are bubbled up to the parent context which is passed to the open method.
|
|
345
|
-
*/
|
|
346
|
-
async _catch(err) {
|
|
347
|
-
if (CLOSE_RESOURCE_ON_UNHANDLED_ERROR) {
|
|
348
|
-
try {
|
|
349
|
-
await this.close();
|
|
350
|
-
} catch (doubleErr) {
|
|
351
|
-
(0, import_util2.throwUnhandledError)(doubleErr);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
throw err;
|
|
355
|
-
}
|
|
356
|
-
/**
|
|
357
|
-
* Opens the resource.
|
|
358
|
-
* If the resource is already open, it does nothing.
|
|
359
|
-
* If the resource is in an error state, it throws an error.
|
|
360
|
-
* If the resource is closed, it waits for it to close and then opens it.
|
|
361
|
-
* @param ctx - Context to use for opening the resource. This context will receive errors that are not handled in `_catch`.
|
|
362
|
-
*/
|
|
363
|
-
async open(ctx) {
|
|
364
|
-
switch (this.#lifecycleState) {
|
|
365
|
-
case "OPEN":
|
|
366
|
-
return this;
|
|
367
|
-
case "ERROR":
|
|
368
|
-
throw new Error(`Invalid state: ${this.#lifecycleState}`);
|
|
369
|
-
default:
|
|
370
|
-
}
|
|
371
|
-
await this.#closePromise;
|
|
372
|
-
await (this.#openPromise ??= this.#open(ctx));
|
|
373
|
-
return this;
|
|
374
|
-
}
|
|
375
|
-
/**
|
|
376
|
-
* Closes the resource.
|
|
377
|
-
* If the resource is already closed, it does nothing.
|
|
378
|
-
*/
|
|
379
|
-
async close(ctx) {
|
|
380
|
-
if (this.#lifecycleState === "CLOSED") {
|
|
381
|
-
return this;
|
|
382
|
-
}
|
|
383
|
-
await this.#openPromise;
|
|
384
|
-
await (this.#closePromise ??= this.#close(ctx));
|
|
385
|
-
return this;
|
|
386
|
-
}
|
|
387
|
-
/**
|
|
388
|
-
* Waits until the resource is open.
|
|
389
|
-
*/
|
|
390
|
-
async waitUntilOpen() {
|
|
391
|
-
switch (this.#lifecycleState) {
|
|
392
|
-
case "OPEN":
|
|
393
|
-
return;
|
|
394
|
-
case "ERROR":
|
|
395
|
-
throw new Error(`Invalid state: ${this.#lifecycleState}`);
|
|
396
|
-
}
|
|
397
|
-
if (!this.#openPromise) {
|
|
398
|
-
throw new Error("Resource is not being opened");
|
|
399
|
-
}
|
|
400
|
-
await this.#openPromise;
|
|
401
|
-
}
|
|
402
|
-
async [Symbol.asyncDispose]() {
|
|
403
|
-
await this.close();
|
|
404
|
-
}
|
|
405
|
-
async #open(ctx) {
|
|
406
|
-
this.#closePromise = null;
|
|
407
|
-
this.#parentCtx = ctx?.derive({
|
|
408
|
-
name: this.#name
|
|
409
|
-
}) ?? this.#createParentContext();
|
|
410
|
-
await this._open(this.#parentCtx);
|
|
411
|
-
this.#lifecycleState = "OPEN";
|
|
412
|
-
}
|
|
413
|
-
async #close(ctx = Context.default()) {
|
|
414
|
-
this.#openPromise = null;
|
|
415
|
-
await this.#internalCtx.dispose();
|
|
416
|
-
await this._close(ctx);
|
|
417
|
-
this.#internalCtx = this.#createContext();
|
|
418
|
-
this.#lifecycleState = "CLOSED";
|
|
419
|
-
}
|
|
420
|
-
#createContext() {
|
|
421
|
-
return new Context({
|
|
422
|
-
name: this.#name,
|
|
423
|
-
onError: (error) => queueMicrotask(async () => {
|
|
424
|
-
try {
|
|
425
|
-
await this._catch(error);
|
|
426
|
-
} catch (err) {
|
|
427
|
-
this.#lifecycleState = "ERROR";
|
|
428
|
-
this.#parentCtx.raise(err);
|
|
429
|
-
}
|
|
430
|
-
})
|
|
431
|
-
});
|
|
432
|
-
}
|
|
433
|
-
#createParentContext() {
|
|
434
|
-
return new Context({
|
|
435
|
-
name: this.#name
|
|
436
|
-
});
|
|
437
|
-
}
|
|
438
|
-
};
|
|
439
|
-
var openInContext = async (ctx, resource) => {
|
|
440
|
-
await resource.open?.(ctx);
|
|
441
|
-
ctx.onDispose(() => resource.close?.());
|
|
442
|
-
return resource;
|
|
443
|
-
};
|
|
444
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
445
|
-
0 && (module.exports = {
|
|
446
|
-
Context,
|
|
447
|
-
ContextDisposedError,
|
|
448
|
-
LifecycleState,
|
|
449
|
-
Resource,
|
|
450
|
-
cancelWithContext,
|
|
451
|
-
openInContext,
|
|
452
|
-
rejectOnDispose
|
|
453
|
-
});
|
|
454
|
-
//# sourceMappingURL=index.cjs.map
|