@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.
- package/dist/application/kernel.d.ts +2 -1
- package/dist/core/extension.d.ts +13 -2
- package/dist/core/index.d.ts +2 -1
- package/dist/core/instrumentation.d.ts +36 -0
- package/dist/core/instrumentation.js +1 -0
- package/dist/devtools/capsule.d.ts +51 -0
- package/dist/devtools/capsule.js +245 -0
- package/dist/devtools/index.d.ts +11 -0
- package/dist/devtools/index.js +6 -0
- package/dist/devtools/inspector.d.ts +57 -0
- package/dist/devtools/inspector.js +1 -0
- package/dist/devtools/module.d.ts +18 -0
- package/dist/devtools/module.js +26 -0
- package/dist/devtools/observer.d.ts +77 -0
- package/dist/devtools/observer.js +10 -0
- package/dist/devtools/protocol.d.ts +1 -0
- package/dist/devtools/protocol.js +1 -0
- package/dist/devtools/provider-instrumentation.d.ts +14 -0
- package/dist/devtools/provider-instrumentation.js +117 -0
- package/dist/devtools/runtime.d.ts +20 -0
- package/dist/devtools/runtime.js +519 -0
- package/dist/devtools/values.d.ts +35 -0
- package/dist/devtools/values.js +175 -0
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/http/extension.js +137 -23
- package/dist/message-port/extension.js +20 -4
- package/dist/runtime/di.d.ts +4 -0
- package/dist/runtime/di.js +14 -4
- package/dist/runtime/kernel.d.ts +4 -2
- package/dist/runtime/kernel.js +96 -3
- package/dist/tasks/extension.js +30 -4
- package/dist/websocket/extension.js +14 -5
- package/package.json +5 -1
|
@@ -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;
|