@loutrejs/loutre 0.6.0 → 0.7.0

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.
@@ -0,0 +1,117 @@
1
+ export function discoverProviderMethods(instance) {
2
+ const methods = [];
3
+ const seen = new Set();
4
+ const collect = (target) => {
5
+ for (const key of Reflect.ownKeys(target)) {
6
+ if (key === 'constructor' || seen.has(key))
7
+ continue;
8
+ const descriptor = Object.getOwnPropertyDescriptor(target, key);
9
+ if (!descriptor || !('value' in descriptor))
10
+ continue;
11
+ if (typeof descriptor.value !== 'function')
12
+ continue;
13
+ seen.add(key);
14
+ methods.push({
15
+ key,
16
+ name: methodName(key),
17
+ original: descriptor.value,
18
+ });
19
+ }
20
+ };
21
+ collect(instance);
22
+ let prototype = Object.getPrototypeOf(instance);
23
+ while (prototype && prototype !== Object.prototype) {
24
+ collect(prototype);
25
+ prototype = Object.getPrototypeOf(prototype);
26
+ }
27
+ return methods;
28
+ }
29
+ export function instrumentProviderMethods(instance, invoke) {
30
+ const seen = new Set();
31
+ const restore = [];
32
+ for (const key of Reflect.ownKeys(instance)) {
33
+ const descriptor = Object.getOwnPropertyDescriptor(instance, key);
34
+ if (!descriptor || !('value' in descriptor))
35
+ continue;
36
+ if (typeof descriptor.value !== 'function')
37
+ continue;
38
+ if (!descriptor.configurable && descriptor.writable !== true)
39
+ continue;
40
+ seen.add(key);
41
+ const cleanup = defineWrapper(instance, key, descriptor, descriptor.value, invoke);
42
+ if (cleanup)
43
+ restore.push(cleanup);
44
+ }
45
+ if (!Object.isExtensible(instance)) {
46
+ return () => {
47
+ for (const cleanup of restore.toReversed())
48
+ cleanup();
49
+ };
50
+ }
51
+ let prototype = Object.getPrototypeOf(instance);
52
+ while (prototype && prototype !== Object.prototype) {
53
+ for (const key of Reflect.ownKeys(prototype)) {
54
+ if (key === 'constructor' || seen.has(key))
55
+ continue;
56
+ const descriptor = Object.getOwnPropertyDescriptor(prototype, key);
57
+ if (!descriptor || !('value' in descriptor))
58
+ continue;
59
+ if (typeof descriptor.value !== 'function')
60
+ continue;
61
+ seen.add(key);
62
+ const cleanup = defineWrapper(instance, key, {
63
+ configurable: true,
64
+ enumerable: descriptor.enumerable ?? false,
65
+ writable: true,
66
+ value: descriptor.value,
67
+ }, descriptor.value, invoke, true);
68
+ if (cleanup)
69
+ restore.push(cleanup);
70
+ }
71
+ prototype = Object.getPrototypeOf(prototype);
72
+ }
73
+ return () => {
74
+ for (const cleanup of restore.toReversed())
75
+ cleanup();
76
+ };
77
+ }
78
+ function defineWrapper(instance, key, descriptor, original, invoke, shadowsPrototype = false) {
79
+ const wrapper = function (...args) {
80
+ return invoke({
81
+ key,
82
+ name: methodName(key),
83
+ original,
84
+ receiver: this,
85
+ args,
86
+ });
87
+ };
88
+ try {
89
+ Object.defineProperty(instance, key, {
90
+ ...descriptor,
91
+ value: wrapper,
92
+ });
93
+ }
94
+ catch {
95
+ // instrumentationのために変更不能なProviderを壊してはならない。
96
+ return undefined;
97
+ }
98
+ return () => {
99
+ try {
100
+ const current = Object.getOwnPropertyDescriptor(instance, key);
101
+ if (!current || !('value' in current) || current.value !== wrapper)
102
+ return;
103
+ if (shadowsPrototype)
104
+ delete instance[key];
105
+ else
106
+ Object.defineProperty(instance, key, descriptor);
107
+ }
108
+ catch {
109
+ // Application側の変更をcleanupで上書きしてはならない。
110
+ }
111
+ };
112
+ }
113
+ function methodName(key) {
114
+ if (typeof key === 'symbol')
115
+ return key.description ?? key.toString();
116
+ return String(key);
117
+ }
@@ -0,0 +1,20 @@
1
+ import type { RuntimeExecutionMetadata, RuntimeInstrumentation, RuntimeInstrumentationScope, RuntimeInvocationRegistration, RuntimeProviderMetadata, RuntimeOperationMetadata } from '../core/instrumentation.js';
2
+ import type { ProviderMethodInvocationRequest, ProviderPlaygroundDescriptor, ProviderPlaygroundRequest, ReplayCapsuleRequest, RuntimeDevtoolsContextStorage, RuntimeInspector, RuntimeInvocationResult } from './inspector.js';
3
+ import type { DevtoolsModuleOptions } from './module.js';
4
+ import { type RuntimeObserver } from './observer.js';
5
+ export interface RuntimeDevtoolsInstrumentationOptions {
6
+ readonly options: Readonly<DevtoolsModuleOptions>;
7
+ readonly observer: RuntimeObserver;
8
+ readonly context: RuntimeDevtoolsContextStorage;
9
+ }
10
+ export declare class RuntimeDevtoolsInstrumentation implements RuntimeInstrumentation, RuntimeInspector {
11
+ #private;
12
+ constructor(options: RuntimeDevtoolsInstrumentationOptions);
13
+ beginExecution(metadata: RuntimeExecutionMetadata, invocation?: RuntimeInvocationRegistration): RuntimeInstrumentationScope;
14
+ beginOperation(metadata: RuntimeOperationMetadata, invocation?: RuntimeInvocationRegistration): RuntimeInstrumentationScope | undefined;
15
+ providerCreated(metadata: RuntimeProviderMetadata, value: unknown): void;
16
+ providerPlayground(request: ProviderPlaygroundRequest): Promise<ProviderPlaygroundDescriptor>;
17
+ invokeProviderMethod(request: ProviderMethodInvocationRequest): Promise<RuntimeInvocationResult>;
18
+ replayCapsule(request: ReplayCapsuleRequest): Promise<RuntimeInvocationResult>;
19
+ close(): void;
20
+ }
@@ -0,0 +1,519 @@
1
+ import { ReplayCapsuleStore } from './capsule.js';
2
+ import { serializeDevtoolsError, } from './observer.js';
3
+ import { discoverProviderMethods, instrumentProviderMethods, } from './provider-instrumentation.js';
4
+ import { previewDevtoolsValue } from './values.js';
5
+ export class RuntimeDevtoolsInstrumentation {
6
+ #options;
7
+ #observer;
8
+ #context;
9
+ #captureProviderMethods;
10
+ #captureResults;
11
+ #runId;
12
+ #capsules;
13
+ #providers = new Map();
14
+ #providerMethods = new WeakMap();
15
+ #instrumentedProviderIds = new WeakMap();
16
+ #externalProviderValues = new WeakSet();
17
+ #ambiguousProviders = new WeakSet();
18
+ #providerRestorers = new WeakMap();
19
+ #applicationProviderRestorers = new Map();
20
+ #seq = 0;
21
+ constructor(options) {
22
+ this.#options = options.options;
23
+ this.#observer = options.observer;
24
+ this.#context = options.context;
25
+ this.#captureProviderMethods =
26
+ options.options.capture?.providerMethods !== false;
27
+ const captureResults = options.options.capture?.results;
28
+ this.#captureResults =
29
+ captureResults !== false && captureResults !== 'errors';
30
+ this.#runId = options.observer.runId ?? `run_${crypto.randomUUID()}`;
31
+ const replay = options.options.replay;
32
+ this.#capsules =
33
+ replay?.enabled === false
34
+ ? undefined
35
+ : new ReplayCapsuleStore(this.#runId, {
36
+ ...(replay?.capsuleTtlMs === undefined
37
+ ? {}
38
+ : { ttlMs: replay.capsuleTtlMs }),
39
+ ...(replay?.maxCapsules === undefined
40
+ ? {}
41
+ : { maxCapsules: replay.maxCapsules }),
42
+ ...(options.options.capture === undefined
43
+ ? {}
44
+ : { capture: options.options.capture }),
45
+ ...(options.options.redact === undefined
46
+ ? {}
47
+ : { redact: options.options.redact }),
48
+ });
49
+ }
50
+ beginExecution(metadata, invocation) {
51
+ const traceId = `trace_${crypto.randomUUID()}`;
52
+ const spanId = `span_${crypto.randomUUID()}`;
53
+ const startedAt = performance.now();
54
+ const startedTimestamp = performance.timeOrigin + startedAt;
55
+ const name = metadata.name ?? metadata.executionId;
56
+ const replaySource = this.#context.current()?.replay;
57
+ const capsule = invocation && this.#capsules
58
+ ? this.#capsules.createExecution(traceId, spanId, metadata, {
59
+ input: invocation.input,
60
+ replay: (input, context) => this.#runReplayContext(context, () => invocation.invoke(input)),
61
+ })
62
+ : undefined;
63
+ let completed = false;
64
+ let failed = false;
65
+ let failureReason;
66
+ let cancelled = false;
67
+ const attributes = {};
68
+ this.#notify('executionStarted', {
69
+ type: 'execution.started',
70
+ ...this.#identity(startedTimestamp, traceId, spanId, metadata.graphNodeId),
71
+ executionId: metadata.executionId,
72
+ executionKind: metadata.executionKind,
73
+ name,
74
+ ...(capsule === undefined
75
+ ? {}
76
+ : {
77
+ input: capsule.input,
78
+ capsuleId: capsule.id,
79
+ replayable: true,
80
+ replayModes: capsule.replayModes,
81
+ }),
82
+ ...(replaySource === undefined
83
+ ? {}
84
+ : {
85
+ replayedFromTraceId: replaySource.sourceTraceId,
86
+ replayedFromCapsuleId: replaySource.sourceCapsuleId,
87
+ }),
88
+ });
89
+ this.#notify('spanStarted', {
90
+ type: 'span.started',
91
+ ...this.#identity(startedTimestamp, traceId, spanId, metadata.graphNodeId),
92
+ kind: 'execution',
93
+ name,
94
+ attributes: { executionKind: metadata.executionKind },
95
+ });
96
+ return {
97
+ run: (operation) => this.#context.run({
98
+ trace: { traceId, spanId },
99
+ ...(replaySource === undefined ? {} : { replay: replaySource }),
100
+ }, operation),
101
+ abort() {
102
+ cancelled = true;
103
+ },
104
+ fail(reason) {
105
+ failed = true;
106
+ failureReason = reason;
107
+ },
108
+ annotate(nextAttributes) {
109
+ Object.assign(attributes, nextAttributes);
110
+ },
111
+ complete: (...resultValues) => {
112
+ if (completed)
113
+ return;
114
+ completed = true;
115
+ const status = failed ? 'error' : cancelled ? 'cancelled' : 'ok';
116
+ const error = failed ? serializeDevtoolsError(failureReason) : undefined;
117
+ const result = resultValues.length === 0
118
+ ? undefined
119
+ : this.#previewResult(resultValues[0]);
120
+ const endedAt = performance.now();
121
+ const timestamp = performance.timeOrigin + endedAt;
122
+ this.#notify('spanEnded', {
123
+ type: 'span.ended',
124
+ ...this.#identity(timestamp, traceId, spanId, metadata.graphNodeId),
125
+ durationMs: endedAt - startedAt,
126
+ status,
127
+ ...(Object.keys(attributes).length === 0 ? {} : { attributes }),
128
+ ...(result === undefined ? {} : { result }),
129
+ ...(error === undefined ? {} : { error }),
130
+ });
131
+ this.#notify('executionEnded', {
132
+ type: 'execution.ended',
133
+ ...this.#identity(timestamp, traceId, spanId, metadata.graphNodeId),
134
+ executionId: metadata.executionId,
135
+ durationMs: endedAt - startedAt,
136
+ status,
137
+ ...(Object.keys(attributes).length === 0 ? {} : { attributes }),
138
+ ...(result === undefined ? {} : { result }),
139
+ ...(error === undefined ? {} : { error }),
140
+ });
141
+ },
142
+ };
143
+ }
144
+ beginOperation(metadata, invocation) {
145
+ const current = this.#context.current();
146
+ if (!current?.trace)
147
+ return undefined;
148
+ const spanId = `span_${crypto.randomUUID()}`;
149
+ const startedAt = performance.now();
150
+ const capsule = invocation && this.#capsules
151
+ ? this.#capsules.createExecution(current.trace.traceId, spanId, {
152
+ executionId: `span:${metadata.name}`,
153
+ executionKind: metadata.kind,
154
+ name: metadata.name,
155
+ ...(metadata.graphNodeId === undefined
156
+ ? {}
157
+ : { graphNodeId: metadata.graphNodeId }),
158
+ }, {
159
+ input: invocation.input,
160
+ replay: (input, context) => this.#runReplayContext(context, () => invocation.invoke(input)),
161
+ })
162
+ : undefined;
163
+ let completed = false;
164
+ let failed = false;
165
+ let failureReason;
166
+ const attributes = {};
167
+ this.#notify('spanStarted', {
168
+ type: 'span.started',
169
+ runId: this.#runId,
170
+ seq: ++this.#seq,
171
+ timestamp: performance.timeOrigin + startedAt,
172
+ traceId: current.trace.traceId,
173
+ spanId,
174
+ parentSpanId: current.trace.spanId,
175
+ ...(metadata.graphNodeId === undefined
176
+ ? {}
177
+ : { graphNodeId: metadata.graphNodeId }),
178
+ kind: metadata.kind,
179
+ name: metadata.name,
180
+ ...(capsule === undefined
181
+ ? {}
182
+ : {
183
+ input: capsule.input,
184
+ capsuleId: capsule.id,
185
+ replayable: true,
186
+ replayModes: capsule.replayModes,
187
+ }),
188
+ });
189
+ return {
190
+ run: (operation) => this.#context.run({
191
+ trace: { traceId: current.trace.traceId, spanId },
192
+ ...(current.replay === undefined ? {} : { replay: current.replay }),
193
+ }, operation),
194
+ fail(reason) {
195
+ failed = true;
196
+ failureReason = reason;
197
+ },
198
+ annotate(nextAttributes) {
199
+ Object.assign(attributes, nextAttributes);
200
+ },
201
+ complete: (...resultValues) => {
202
+ if (completed)
203
+ return;
204
+ completed = true;
205
+ const result = resultValues.length === 0
206
+ ? undefined
207
+ : this.#previewResult(resultValues[0]);
208
+ const error = failed ? serializeDevtoolsError(failureReason) : undefined;
209
+ const endedAt = performance.now();
210
+ this.#notify('spanEnded', {
211
+ type: 'span.ended',
212
+ runId: this.#runId,
213
+ seq: ++this.#seq,
214
+ timestamp: performance.timeOrigin + endedAt,
215
+ traceId: current.trace.traceId,
216
+ spanId,
217
+ parentSpanId: current.trace.spanId,
218
+ ...(metadata.graphNodeId === undefined
219
+ ? {}
220
+ : { graphNodeId: metadata.graphNodeId }),
221
+ durationMs: endedAt - startedAt,
222
+ status: failed ? 'error' : 'ok',
223
+ ...(Object.keys(attributes).length === 0 ? {} : { attributes }),
224
+ ...(result === undefined ? {} : { result }),
225
+ ...(error === undefined ? {} : { error }),
226
+ });
227
+ },
228
+ };
229
+ }
230
+ providerCreated(metadata, value) {
231
+ if (typeof value !== 'object' || value === null)
232
+ return;
233
+ let methods = this.#providerMethods.get(value);
234
+ if (!methods) {
235
+ methods = discoverProviderMethods(value);
236
+ this.#providerMethods.set(value, methods);
237
+ }
238
+ if (metadata.scope === 'application') {
239
+ this.#providers.set(metadata.graphNodeId, {
240
+ metadata,
241
+ instance: value,
242
+ methods: new Map(methods.flatMap((method) => typeof method.key === 'string' ? [[method.name, method]] : [])),
243
+ });
244
+ }
245
+ if (!this.#captureProviderMethods)
246
+ return;
247
+ if (metadata.providerKind === 'value' ||
248
+ metadata.providerKind === 'factory') {
249
+ this.#providerRestorers.get(value)?.();
250
+ this.#providerRestorers.delete(value);
251
+ this.#applicationProviderRestorers.delete(value);
252
+ this.#externalProviderValues.add(value);
253
+ return;
254
+ }
255
+ if (this.#externalProviderValues.has(value) ||
256
+ this.#ambiguousProviders.has(value)) {
257
+ return;
258
+ }
259
+ const instrumentedProviderId = this.#instrumentedProviderIds.get(value);
260
+ if (instrumentedProviderId !== undefined) {
261
+ if (instrumentedProviderId !== metadata.graphNodeId) {
262
+ this.#providerRestorers.get(value)?.();
263
+ this.#providerRestorers.delete(value);
264
+ this.#applicationProviderRestorers.delete(value);
265
+ this.#ambiguousProviders.add(value);
266
+ }
267
+ return;
268
+ }
269
+ const restore = instrumentProviderMethods(value, (invocation) => this.#invokeProviderMethod(metadata, value, invocation));
270
+ this.#instrumentedProviderIds.set(value, metadata.graphNodeId);
271
+ this.#providerRestorers.set(value, restore);
272
+ if (metadata.scope === 'application') {
273
+ this.#applicationProviderRestorers.set(value, restore);
274
+ }
275
+ }
276
+ async providerPlayground(request) {
277
+ const provider = this.#providers.get(request.graphNodeId);
278
+ if (!provider) {
279
+ throw new Error(`LUTRE_DEVTOOLS_PROVIDER_UNAVAILABLE: ${request.graphNodeId}`);
280
+ }
281
+ return {
282
+ requestId: request.requestId,
283
+ graphNodeId: request.graphNodeId,
284
+ providerName: provider.metadata.name,
285
+ methods: [...provider.methods.values()]
286
+ .map((method) => ({ name: method.name, arity: method.original.length }))
287
+ .toSorted((left, right) => left.name.localeCompare(right.name)),
288
+ };
289
+ }
290
+ async invokeProviderMethod(request) {
291
+ const provider = this.#providers.get(request.graphNodeId);
292
+ if (!provider) {
293
+ return {
294
+ requestId: request.requestId,
295
+ status: 'error',
296
+ error: {
297
+ name: 'ProviderUnavailableError',
298
+ message: `LUTRE_DEVTOOLS_PROVIDER_UNAVAILABLE: ${request.graphNodeId}`,
299
+ },
300
+ };
301
+ }
302
+ const method = provider.methods.get(request.method);
303
+ if (!method) {
304
+ return {
305
+ requestId: request.requestId,
306
+ status: 'error',
307
+ error: {
308
+ name: 'ProviderMethodUnavailableError',
309
+ message: `LUTRE_DEVTOOLS_PROVIDER_METHOD_UNAVAILABLE: ${request.method}`,
310
+ },
311
+ };
312
+ }
313
+ let traceId;
314
+ try {
315
+ const result = await this.#runProviderMethodExecution(provider.metadata, provider.instance, method.original, method.name, request.args, (nextTraceId) => {
316
+ traceId = nextTraceId;
317
+ });
318
+ return {
319
+ requestId: request.requestId,
320
+ status: 'ok',
321
+ ...(traceId === undefined ? {} : { traceId }),
322
+ result: this.#previewValue(result),
323
+ };
324
+ }
325
+ catch (error) {
326
+ return {
327
+ requestId: request.requestId,
328
+ status: 'error',
329
+ ...(traceId === undefined ? {} : { traceId }),
330
+ error: serializeDevtoolsError(error),
331
+ };
332
+ }
333
+ }
334
+ async replayCapsule(request) {
335
+ if (!this.#capsules) {
336
+ return {
337
+ requestId: request.requestId,
338
+ status: 'error',
339
+ error: {
340
+ name: 'ReplayDisabledError',
341
+ message: 'LUTRE_DEVTOOLS_REPLAY_DISABLED: Replay is not enabled.',
342
+ },
343
+ };
344
+ }
345
+ return this.#capsules.replay(request);
346
+ }
347
+ close() {
348
+ this.#capsules?.clear();
349
+ this.#providers.clear();
350
+ for (const restore of this.#applicationProviderRestorers.values())
351
+ restore();
352
+ this.#applicationProviderRestorers.clear();
353
+ }
354
+ #invokeProviderMethod(metadata, instance, invocation) {
355
+ const current = this.#context.current();
356
+ if (!current?.trace) {
357
+ return Reflect.apply(invocation.original, invocation.receiver, invocation.args);
358
+ }
359
+ const spanId = `span_${crypto.randomUUID()}`;
360
+ const startedAt = performance.now();
361
+ const name = `${metadata.name}.${invocation.name}`;
362
+ const capsule = metadata.scope === 'transient'
363
+ ? undefined
364
+ : this.#capsules?.createExecution(current.trace.traceId, spanId, {
365
+ executionId: `${metadata.providerId}.${invocation.name}`,
366
+ executionKind: 'provider.method',
367
+ graphNodeId: metadata.graphNodeId,
368
+ name,
369
+ }, {
370
+ input: invocation.args,
371
+ replay: (input, context) => this.#runReplayContext(context, () => this.#replayProviderMethod(metadata, instance, invocation.original, invocation.name, input)),
372
+ });
373
+ let result;
374
+ try {
375
+ result = Reflect.apply(invocation.original, invocation.receiver, invocation.args);
376
+ }
377
+ catch (error) {
378
+ this.#recordProviderMethodSpan(metadata, invocation, current.trace, spanId, startedAt, capsule, { status: 'error', error });
379
+ throw error;
380
+ }
381
+ // Promiseのsettlementを観測するとrejection handlerが追加され、Applicationの
382
+ // unhandled-rejection semanticsを変え得る。自動Traceは同期的に完結する呼び出しへ
383
+ // 限定し、明示実行であるPlaygroundとReplayだけがPromiseをawaitする。
384
+ if (result instanceof Promise) {
385
+ if (capsule)
386
+ this.#capsules?.discard(capsule.id);
387
+ return result;
388
+ }
389
+ this.#recordProviderMethodSpan(metadata, invocation, current.trace, spanId, startedAt, capsule, { status: 'ok', result });
390
+ return result;
391
+ }
392
+ #recordProviderMethodSpan(metadata, invocation, parent, spanId, startedAt, capsule, outcome) {
393
+ const name = `${metadata.name}.${invocation.name}`;
394
+ this.#notify('spanStarted', {
395
+ type: 'span.started',
396
+ runId: this.#runId,
397
+ seq: ++this.#seq,
398
+ timestamp: performance.timeOrigin + startedAt,
399
+ traceId: parent.traceId,
400
+ spanId,
401
+ parentSpanId: parent.spanId,
402
+ graphNodeId: metadata.graphNodeId,
403
+ kind: 'provider.method',
404
+ name,
405
+ ...(capsule === undefined
406
+ ? {}
407
+ : {
408
+ input: capsule.input,
409
+ capsuleId: capsule.id,
410
+ replayable: true,
411
+ replayModes: capsule.replayModes,
412
+ }),
413
+ });
414
+ const endedAt = performance.now();
415
+ const result = outcome.status === 'ok' ? this.#previewResult(outcome.result) : undefined;
416
+ this.#notify('spanEnded', {
417
+ type: 'span.ended',
418
+ runId: this.#runId,
419
+ seq: ++this.#seq,
420
+ timestamp: performance.timeOrigin + endedAt,
421
+ traceId: parent.traceId,
422
+ spanId,
423
+ parentSpanId: parent.spanId,
424
+ graphNodeId: metadata.graphNodeId,
425
+ durationMs: endedAt - startedAt,
426
+ status: outcome.status,
427
+ ...(result === undefined ? {} : { result }),
428
+ ...(outcome.status === 'error'
429
+ ? { error: serializeDevtoolsError(outcome.error) }
430
+ : {}),
431
+ });
432
+ }
433
+ async #replayProviderMethod(metadata, instance, original, method, input) {
434
+ return this.#runProviderMethodExecution(metadata, instance, original, method, input);
435
+ }
436
+ async #runProviderMethodExecution(metadata, instance, original, method, input, onTraceId) {
437
+ if (!Array.isArray(input)) {
438
+ throw new TypeError('LUTRE_DEVTOOLS_PROVIDER_ARGS_REQUIRED');
439
+ }
440
+ const scope = this.beginExecution({
441
+ executionId: `${metadata.providerId}.${method}`,
442
+ executionKind: 'provider.method',
443
+ graphNodeId: metadata.graphNodeId,
444
+ name: `${metadata.name}.${method}`,
445
+ }, {
446
+ input,
447
+ invoke: (nextInput) => this.#runProviderMethodExecution(metadata, instance, original, method, nextInput),
448
+ });
449
+ let result;
450
+ let hasResult = false;
451
+ try {
452
+ const invoke = () => {
453
+ const traceId = this.#context.current()?.trace?.traceId;
454
+ if (traceId)
455
+ onTraceId?.(traceId);
456
+ return Reflect.apply(original, instance, input);
457
+ };
458
+ result = await (scope.run ? scope.run(invoke) : invoke());
459
+ hasResult = true;
460
+ return result;
461
+ }
462
+ catch (error) {
463
+ scope.fail?.(error);
464
+ throw error;
465
+ }
466
+ finally {
467
+ if (hasResult)
468
+ scope.complete(result);
469
+ else
470
+ scope.complete();
471
+ }
472
+ }
473
+ #runReplayContext(context, operation) {
474
+ const current = this.#context.current();
475
+ const next = {
476
+ ...(current?.trace === undefined ? {} : { trace: current.trace }),
477
+ replay: context,
478
+ };
479
+ return this.#context.run(next, operation);
480
+ }
481
+ #previewResult(value) {
482
+ if (!this.#captureResults)
483
+ return undefined;
484
+ return this.#previewValue(value);
485
+ }
486
+ #previewValue(value) {
487
+ const capture = this.#options.capture;
488
+ return previewDevtoolsValue(value, {
489
+ ...(capture?.maxValueBytes === undefined
490
+ ? {}
491
+ : { maxValueBytes: capture.maxValueBytes }),
492
+ ...(capture?.maxDepth === undefined
493
+ ? {}
494
+ : { maxDepth: capture.maxDepth }),
495
+ ...(this.#options.redact === undefined
496
+ ? {}
497
+ : { redact: this.#options.redact }),
498
+ });
499
+ }
500
+ #identity(timestamp, traceId, spanId, graphNodeId) {
501
+ return {
502
+ runId: this.#runId,
503
+ seq: ++this.#seq,
504
+ timestamp,
505
+ traceId,
506
+ spanId,
507
+ ...(graphNodeId === undefined ? {} : { graphNodeId }),
508
+ };
509
+ }
510
+ #notify(key, event) {
511
+ try {
512
+ const callback = this.#observer[key];
513
+ callback?.(event);
514
+ }
515
+ catch {
516
+ // observerの失敗をApplicationの挙動へ伝播させてはならない。
517
+ }
518
+ }
519
+ }
@@ -0,0 +1,35 @@
1
+ export type DevtoolsValue = null | boolean | number | string | {
2
+ readonly $type: 'undefined';
3
+ } | {
4
+ readonly $type: 'bigint';
5
+ readonly value: string;
6
+ } | {
7
+ readonly $type: 'date';
8
+ readonly value: string;
9
+ } | {
10
+ readonly $type: 'bytes';
11
+ readonly base64: string;
12
+ } | {
13
+ readonly $type: 'map';
14
+ readonly entries: readonly [DevtoolsValue, DevtoolsValue][];
15
+ } | {
16
+ readonly $type: 'set';
17
+ readonly values: readonly DevtoolsValue[];
18
+ } | {
19
+ readonly $type: 'redacted';
20
+ } | readonly DevtoolsValue[] | {
21
+ readonly [key: string]: DevtoolsValue;
22
+ };
23
+ export interface DevtoolsValuePreview {
24
+ readonly value?: DevtoolsValue;
25
+ readonly preview: string;
26
+ readonly truncated?: boolean;
27
+ readonly redacted?: boolean;
28
+ readonly type?: string;
29
+ }
30
+ export interface DevtoolsValuePreviewOptions {
31
+ readonly maxValueBytes?: number;
32
+ readonly maxDepth?: number;
33
+ readonly redact?: readonly string[];
34
+ }
35
+ export declare function previewDevtoolsValue(value: unknown, options?: DevtoolsValuePreviewOptions): DevtoolsValuePreview;