@dxos/context 0.8.4-main.ae835ea → 0.8.4-main.bbf232bc24

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/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "@dxos/context",
3
- "version": "0.8.4-main.ae835ea",
3
+ "version": "0.8.4-main.bbf232bc24",
4
4
  "description": "Async utils.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/dxos/dxos"
10
+ },
7
11
  "license": "MIT",
8
12
  "author": "DXOS.org",
9
- "sideEffects": true,
13
+ "sideEffects": false,
10
14
  "type": "module",
11
15
  "exports": {
12
16
  ".": {
@@ -19,18 +23,16 @@
19
23
  }
20
24
  },
21
25
  "types": "dist/types/src/index.d.ts",
22
- "typesVersions": {
23
- "*": {}
24
- },
25
26
  "files": [
26
27
  "dist",
27
28
  "src"
28
29
  ],
29
30
  "dependencies": {
30
- "@dxos/debug": "0.8.4-main.ae835ea",
31
- "@dxos/log": "0.8.4-main.ae835ea",
32
- "@dxos/node-std": "0.8.4-main.ae835ea",
33
- "@dxos/util": "0.8.4-main.ae835ea"
31
+ "@hazae41/symbol-dispose-polyfill": "^1.0.2",
32
+ "@dxos/debug": "0.8.4-main.bbf232bc24",
33
+ "@dxos/log": "0.8.4-main.bbf232bc24",
34
+ "@dxos/node-std": "0.8.4-main.bbf232bc24",
35
+ "@dxos/util": "0.8.4-main.bbf232bc24"
34
36
  },
35
37
  "publishConfig": {
36
38
  "access": "public"
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 CreateContextParams = {
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: CreateContextParams = {}, callMeta?: Partial<CallMetadata>) {
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 }: CreateContextParams = {}): Context {
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: CreateContextParams, callMeta?: Partial<CallMetadata>): string | undefined => {
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
@@ -6,3 +6,4 @@ export * from './context';
6
6
  export * from './context-disposed-error';
7
7
  export * from './promise-utils';
8
8
  export * from './resource';
9
+ export * from './trace-context';
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
  }
@@ -84,6 +98,8 @@ export abstract class Resource implements Lifecycle {
84
98
 
85
99
  /**
86
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
87
103
  */
88
104
  async use<T>(fn: (resource: this) => Promise<T>): Promise<T> {
89
105
  try {
@@ -145,13 +161,10 @@ export abstract class Resource implements Lifecycle {
145
161
  await this.#openPromise;
146
162
  }
147
163
 
148
- async [Symbol.asyncDispose](): Promise<void> {
149
- await this.close();
150
- }
151
-
152
164
  async #open(ctx?: Context): Promise<void> {
153
165
  this.#closePromise = null;
154
166
  this.#parentCtx = ctx?.derive({ name: this.#name }) ?? this.#createParentContext();
167
+ this.#internalCtx = this.#createContext(this.#parentCtx);
155
168
  await this._open(this.#parentCtx);
156
169
  this.#lifecycleState = LifecycleState.OPEN;
157
170
  }
@@ -164,9 +177,10 @@ export abstract class Resource implements Lifecycle {
164
177
  this.#lifecycleState = LifecycleState.CLOSED;
165
178
  }
166
179
 
167
- #createContext(): Context {
180
+ #createContext(attributeParent?: Context): Context {
168
181
  return new Context({
169
182
  name: this.#name,
183
+ parent: attributeParent,
170
184
  onError: (error) =>
171
185
  queueMicrotask(async () => {
172
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
+ }