@loutrejs/loutre 0.6.0 → 0.8.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/adapters/bun.d.ts +2 -4
- package/dist/adapters/bun.js +36 -28
- package/dist/adapters/cloudflare-workers.d.ts +2 -4
- package/dist/adapters/cloudflare-workers.js +6 -11
- package/dist/adapters/server-transports.d.ts +4 -0
- package/dist/adapters/server-transports.js +18 -0
- package/dist/adapters/websocket-native.d.ts +24 -0
- package/dist/adapters/websocket-native.js +150 -0
- package/dist/application/kernel.d.ts +2 -1
- package/dist/core/extension.d.ts +26 -3
- package/dist/core/extension.js +19 -0
- 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/core/model/modules.js +13 -17
- package/dist/core/model.js +14 -4
- package/dist/core/module.d.ts +3 -3
- package/dist/devtools/capsule.d.ts +51 -0
- package/dist/devtools/capsule.js +247 -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 +172 -0
- package/dist/generated/version.d.ts +1 -1
- package/dist/generated/version.js +1 -1
- package/dist/graph/model.js +37 -40
- package/dist/http/auth.d.ts +2 -2
- package/dist/http/auth.js +9 -0
- package/dist/http/client.js +5 -5
- package/dist/http/extension.d.ts +37 -10
- package/dist/http/extension.js +249 -57
- package/dist/http/index.d.ts +3 -2
- package/dist/http/index.js +1 -1
- package/dist/http/openapi.d.ts +3 -4
- package/dist/http/openapi.js +45 -26
- package/dist/http/request-head.d.ts +6 -0
- package/dist/http/request-head.js +1 -0
- package/dist/http/route-validation.d.ts +9 -0
- package/dist/http/route-validation.js +18 -0
- package/dist/http/runtime/response.js +10 -10
- package/dist/message-port/extension.js +20 -4
- package/dist/runtime/di.d.ts +4 -0
- package/dist/runtime/di.js +42 -31
- package/dist/runtime/engine.d.ts +1 -1
- package/dist/runtime/engine.js +1 -2
- package/dist/runtime/kernel.d.ts +4 -2
- package/dist/runtime/kernel.js +96 -3
- package/dist/runtime/node-capabilities.d.ts +1 -1
- package/dist/runtime/node-capabilities.js +2 -1
- package/dist/tasks/extension.js +133 -118
- package/dist/websocket/driver.d.ts +30 -0
- package/dist/websocket/driver.js +96 -0
- package/dist/websocket/extension.d.ts +33 -12
- package/dist/websocket/extension.js +72 -47
- package/dist/websocket/index.d.ts +3 -1
- package/dist/websocket/index.js +1 -0
- package/package.json +7 -6
- package/dist/adapters/deno.d.ts +0 -35
- package/dist/adapters/deno.js +0 -231
package/dist/core/module.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EnvClass } from './env.js';
|
|
2
|
-
import type {
|
|
2
|
+
import type { ExecutionDeclaration, ExtensionOfDefinition, HostApiOfExtension } from './extension.js';
|
|
3
3
|
import type { ModuleLifecycle } from './lifecycle.js';
|
|
4
4
|
import { type ProviderDeclaration } from './provider.js';
|
|
5
5
|
export interface ModuleDefinition {
|
|
@@ -8,7 +8,7 @@ export interface ModuleDefinition {
|
|
|
8
8
|
readonly imports?: readonly ModuleInstance[];
|
|
9
9
|
readonly environment?: readonly EnvClass[];
|
|
10
10
|
readonly providers?: readonly ProviderDeclaration[];
|
|
11
|
-
readonly executions?: readonly
|
|
11
|
+
readonly executions?: readonly ExecutionDeclaration[];
|
|
12
12
|
readonly exports?: readonly unknown[];
|
|
13
13
|
readonly lifecycle?: ModuleLifecycle;
|
|
14
14
|
}
|
|
@@ -16,7 +16,7 @@ export declare const moduleTypeInfo: unique symbol;
|
|
|
16
16
|
export interface ModuleTypeInfo<TDefinition extends ModuleDefinition = ModuleDefinition> {
|
|
17
17
|
readonly extensions: ExtensionsOfModuleDefinition<TDefinition>;
|
|
18
18
|
}
|
|
19
|
-
type DirectExtensions<TDefinition extends ModuleDefinition> = TDefinition['executions'] extends readonly
|
|
19
|
+
type DirectExtensions<TDefinition extends ModuleDefinition> = TDefinition['executions'] extends readonly ExecutionDeclaration[] ? ExtensionOfDefinition<TDefinition['executions'][number]> : never;
|
|
20
20
|
type ImportedExtensions<TDefinition extends ModuleDefinition> = TDefinition['imports'] extends readonly ModuleInstance[] ? ModuleExtensions<TDefinition['imports'][number]> : never;
|
|
21
21
|
export type ExtensionsOfModuleDefinition<TDefinition extends ModuleDefinition> = DirectExtensions<TDefinition> | ImportedExtensions<TDefinition>;
|
|
22
22
|
export type ModuleExtensions<TModule> = TModule extends ModuleInstance<infer TDefinition> ? ExtensionsOfModuleDefinition<TDefinition> : never;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { RuntimeExecutionMetadata } from '../core/instrumentation.js';
|
|
2
|
+
import type { DevtoolsModuleOptions } from './module.js';
|
|
3
|
+
import type { ReplayCapsuleRequest, ReplayExecutionContext, RuntimeInvocationResult } from './inspector.js';
|
|
4
|
+
import { type DevtoolsValuePreview } from './values.js';
|
|
5
|
+
export interface ReplayRegistration {
|
|
6
|
+
readonly input: unknown;
|
|
7
|
+
replay(input: unknown, context: ReplayExecutionContext): unknown | Promise<unknown>;
|
|
8
|
+
}
|
|
9
|
+
export type CapturedValue = {
|
|
10
|
+
readonly mode: 'snapshot';
|
|
11
|
+
readonly value: unknown;
|
|
12
|
+
} | {
|
|
13
|
+
readonly mode: 'reference';
|
|
14
|
+
readonly handleId: string;
|
|
15
|
+
} | {
|
|
16
|
+
readonly mode: 'unavailable';
|
|
17
|
+
readonly reason: string;
|
|
18
|
+
};
|
|
19
|
+
export interface ExecutionReplayCapsulePreview {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly kind: 'execution';
|
|
22
|
+
readonly runId: string;
|
|
23
|
+
readonly traceId: string;
|
|
24
|
+
readonly spanId: string;
|
|
25
|
+
readonly createdAt: number;
|
|
26
|
+
readonly expiresAt: number;
|
|
27
|
+
readonly execution: {
|
|
28
|
+
readonly executionId: string;
|
|
29
|
+
readonly executionKind: string;
|
|
30
|
+
readonly graphNodeId?: string;
|
|
31
|
+
readonly name: string;
|
|
32
|
+
};
|
|
33
|
+
readonly input: DevtoolsValuePreview;
|
|
34
|
+
readonly fidelity: 'snapshot' | 'reference' | 'unavailable';
|
|
35
|
+
readonly replayModes: readonly ['direct'];
|
|
36
|
+
}
|
|
37
|
+
export interface ReplayCapsuleStoreOptions {
|
|
38
|
+
readonly ttlMs?: number;
|
|
39
|
+
readonly maxCapsules?: number;
|
|
40
|
+
readonly capture?: DevtoolsModuleOptions['capture'];
|
|
41
|
+
readonly redact?: readonly string[];
|
|
42
|
+
}
|
|
43
|
+
export declare class ReplayCapsuleStore {
|
|
44
|
+
#private;
|
|
45
|
+
constructor(runId: string, options?: ReplayCapsuleStoreOptions);
|
|
46
|
+
createExecution(traceId: string, spanId: string, observation: RuntimeExecutionMetadata, registration: ReplayRegistration): ExecutionReplayCapsulePreview;
|
|
47
|
+
preview(capsuleId: string): ExecutionReplayCapsulePreview | undefined;
|
|
48
|
+
replay(request: ReplayCapsuleRequest): Promise<RuntimeInvocationResult>;
|
|
49
|
+
discard(capsuleId: string): void;
|
|
50
|
+
clear(): void;
|
|
51
|
+
}
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { serializeDevtoolsError } from './observer.js';
|
|
2
|
+
import { previewDevtoolsValue } from './values.js';
|
|
3
|
+
import { match } from 'ts-pattern';
|
|
4
|
+
const defaultCapsuleTtlMs = 5 * 60_000;
|
|
5
|
+
const defaultMaxCapsules = 1_000;
|
|
6
|
+
export class ReplayCapsuleStore {
|
|
7
|
+
#runId;
|
|
8
|
+
#ttlMs;
|
|
9
|
+
#maxCapsules;
|
|
10
|
+
#capture;
|
|
11
|
+
#redact;
|
|
12
|
+
#capsules = new Map();
|
|
13
|
+
#references = new Map();
|
|
14
|
+
constructor(runId, options = {}) {
|
|
15
|
+
this.#runId = runId;
|
|
16
|
+
this.#ttlMs = options.ttlMs ?? defaultCapsuleTtlMs;
|
|
17
|
+
this.#maxCapsules = options.maxCapsules ?? defaultMaxCapsules;
|
|
18
|
+
this.#capture = options.capture;
|
|
19
|
+
this.#redact = options.redact ?? [];
|
|
20
|
+
if (!Number.isFinite(this.#ttlMs) || this.#ttlMs <= 0) {
|
|
21
|
+
throw new TypeError('capsuleTtlMs must be a positive finite number.');
|
|
22
|
+
}
|
|
23
|
+
if (!Number.isInteger(this.#maxCapsules) || this.#maxCapsules < 1) {
|
|
24
|
+
throw new TypeError('maxCapsules must be a positive integer.');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
createExecution(traceId, spanId, observation, registration) {
|
|
28
|
+
this.#purgeExpired();
|
|
29
|
+
const id = `capsule_${crypto.randomUUID()}`;
|
|
30
|
+
const createdAt = Date.now();
|
|
31
|
+
const captured = this.#captureValue(registration.input);
|
|
32
|
+
const preview = {
|
|
33
|
+
id,
|
|
34
|
+
kind: 'execution',
|
|
35
|
+
runId: this.#runId,
|
|
36
|
+
traceId,
|
|
37
|
+
spanId,
|
|
38
|
+
createdAt,
|
|
39
|
+
expiresAt: createdAt + this.#ttlMs,
|
|
40
|
+
execution: {
|
|
41
|
+
executionId: observation.executionId,
|
|
42
|
+
executionKind: observation.executionKind,
|
|
43
|
+
name: observation.name ?? observation.executionId,
|
|
44
|
+
...(observation.graphNodeId === undefined
|
|
45
|
+
? {}
|
|
46
|
+
: { graphNodeId: observation.graphNodeId }),
|
|
47
|
+
},
|
|
48
|
+
input: previewDevtoolsValue(registration.input, {
|
|
49
|
+
...(this.#capture?.maxValueBytes === undefined
|
|
50
|
+
? {}
|
|
51
|
+
: { maxValueBytes: this.#capture.maxValueBytes }),
|
|
52
|
+
...(this.#capture?.maxDepth === undefined
|
|
53
|
+
? {}
|
|
54
|
+
: { maxDepth: this.#capture.maxDepth }),
|
|
55
|
+
redact: this.#redact,
|
|
56
|
+
}),
|
|
57
|
+
fidelity: captured.mode,
|
|
58
|
+
replayModes: ['direct'],
|
|
59
|
+
};
|
|
60
|
+
this.#capsules.set(id, { preview, captured, replay: registration.replay });
|
|
61
|
+
while (this.#capsules.size > this.#maxCapsules) {
|
|
62
|
+
const oldest = this.#capsules.keys().next().value;
|
|
63
|
+
if (!oldest)
|
|
64
|
+
break;
|
|
65
|
+
this.#delete(oldest);
|
|
66
|
+
}
|
|
67
|
+
return preview;
|
|
68
|
+
}
|
|
69
|
+
preview(capsuleId) {
|
|
70
|
+
this.#purgeExpired();
|
|
71
|
+
return this.#capsules.get(capsuleId)?.preview;
|
|
72
|
+
}
|
|
73
|
+
async replay(request) {
|
|
74
|
+
this.#purgeExpired();
|
|
75
|
+
const capsule = this.#capsules.get(request.capsuleId);
|
|
76
|
+
if (!capsule) {
|
|
77
|
+
return {
|
|
78
|
+
requestId: request.requestId,
|
|
79
|
+
status: 'error',
|
|
80
|
+
error: {
|
|
81
|
+
name: 'ReplayCapsuleUnavailableError',
|
|
82
|
+
message: `LUTRE_DEVTOOLS_CAPSULE_UNAVAILABLE: ${request.capsuleId}`,
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
let input;
|
|
87
|
+
try {
|
|
88
|
+
input =
|
|
89
|
+
'inputOverride' in request
|
|
90
|
+
? request.inputOverride
|
|
91
|
+
: this.#restore(capsule.captured);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
return {
|
|
95
|
+
requestId: request.requestId,
|
|
96
|
+
status: 'error',
|
|
97
|
+
error: serializeDevtoolsError(error),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
const context = {
|
|
101
|
+
sourceTraceId: capsule.preview.traceId,
|
|
102
|
+
sourceCapsuleId: capsule.preview.id,
|
|
103
|
+
...(request.parentTraceId === undefined
|
|
104
|
+
? {}
|
|
105
|
+
: { parentTraceId: request.parentTraceId }),
|
|
106
|
+
};
|
|
107
|
+
try {
|
|
108
|
+
const result = await capsule.replay(input, context);
|
|
109
|
+
return {
|
|
110
|
+
requestId: request.requestId,
|
|
111
|
+
status: 'ok',
|
|
112
|
+
result: previewDevtoolsValue(result, {
|
|
113
|
+
...(this.#capture?.maxValueBytes === undefined
|
|
114
|
+
? {}
|
|
115
|
+
: { maxValueBytes: this.#capture.maxValueBytes }),
|
|
116
|
+
...(this.#capture?.maxDepth === undefined
|
|
117
|
+
? {}
|
|
118
|
+
: { maxDepth: this.#capture.maxDepth }),
|
|
119
|
+
redact: this.#redact,
|
|
120
|
+
}),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
return {
|
|
125
|
+
requestId: request.requestId,
|
|
126
|
+
status: 'error',
|
|
127
|
+
error: serializeDevtoolsError(error),
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
discard(capsuleId) {
|
|
132
|
+
this.#delete(capsuleId);
|
|
133
|
+
}
|
|
134
|
+
clear() {
|
|
135
|
+
this.#capsules.clear();
|
|
136
|
+
this.#references.clear();
|
|
137
|
+
}
|
|
138
|
+
#captureValue(value) {
|
|
139
|
+
try {
|
|
140
|
+
if (isSnapshotCandidate(value)) {
|
|
141
|
+
return { mode: 'snapshot', value: structuredClone(value) };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// cloneできない値を失うより、同一process内だけで有効な参照として保持する。
|
|
146
|
+
}
|
|
147
|
+
if (value !== undefined) {
|
|
148
|
+
const handleId = `ref_${crypto.randomUUID()}`;
|
|
149
|
+
this.#references.set(handleId, value);
|
|
150
|
+
return { mode: 'reference', handleId };
|
|
151
|
+
}
|
|
152
|
+
return { mode: 'snapshot', value: undefined };
|
|
153
|
+
}
|
|
154
|
+
#restore(captured) {
|
|
155
|
+
return match(captured)
|
|
156
|
+
.with({ mode: 'snapshot' }, (value) => structuredClone(value.value))
|
|
157
|
+
.with({ mode: 'reference' }, (value) => {
|
|
158
|
+
if (this.#references.has(value.handleId)) {
|
|
159
|
+
return this.#references.get(value.handleId);
|
|
160
|
+
}
|
|
161
|
+
throw new Error(`LUTRE_DEVTOOLS_REFERENCE_UNAVAILABLE: ${value.handleId}`);
|
|
162
|
+
})
|
|
163
|
+
.with({ mode: 'unavailable' }, (value) => {
|
|
164
|
+
throw new Error(`LUTRE_DEVTOOLS_CAPTURE_UNAVAILABLE: ${value.reason}`);
|
|
165
|
+
})
|
|
166
|
+
.exhaustive();
|
|
167
|
+
}
|
|
168
|
+
#purgeExpired() {
|
|
169
|
+
const now = Date.now();
|
|
170
|
+
for (const [id, capsule] of this.#capsules) {
|
|
171
|
+
if (capsule.preview.expiresAt > now)
|
|
172
|
+
continue;
|
|
173
|
+
this.#delete(id);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
#delete(id) {
|
|
177
|
+
const capsule = this.#capsules.get(id);
|
|
178
|
+
if (!capsule)
|
|
179
|
+
return;
|
|
180
|
+
if (capsule.captured.mode === 'reference') {
|
|
181
|
+
this.#references.delete(capsule.captured.handleId);
|
|
182
|
+
}
|
|
183
|
+
this.#capsules.delete(id);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function isSnapshotCandidate(value, seen = new WeakSet()) {
|
|
187
|
+
if (value === null)
|
|
188
|
+
return true;
|
|
189
|
+
if (typeof value === 'undefined' ||
|
|
190
|
+
typeof value === 'string' ||
|
|
191
|
+
typeof value === 'number' ||
|
|
192
|
+
typeof value === 'boolean' ||
|
|
193
|
+
typeof value === 'bigint') {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
if (typeof value !== 'object')
|
|
197
|
+
return false;
|
|
198
|
+
if (seen.has(value))
|
|
199
|
+
return true;
|
|
200
|
+
seen.add(value);
|
|
201
|
+
try {
|
|
202
|
+
if (value instanceof Date ||
|
|
203
|
+
value instanceof ArrayBuffer ||
|
|
204
|
+
ArrayBuffer.isView(value)) {
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
if (Array.isArray(value)) {
|
|
208
|
+
return dataPropertiesAreSnapshotCandidates(value, seen, 'length');
|
|
209
|
+
}
|
|
210
|
+
if (value instanceof Map) {
|
|
211
|
+
for (const [key, item] of Map.prototype.entries.call(value)) {
|
|
212
|
+
if (!isSnapshotCandidate(key, seen) ||
|
|
213
|
+
!isSnapshotCandidate(item, seen)) {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
if (value instanceof Set) {
|
|
220
|
+
for (const item of Set.prototype.values.call(value)) {
|
|
221
|
+
if (!isSnapshotCandidate(item, seen))
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
return true;
|
|
225
|
+
}
|
|
226
|
+
const prototype = Object.getPrototypeOf(value);
|
|
227
|
+
if (prototype !== Object.prototype && prototype !== null)
|
|
228
|
+
return false;
|
|
229
|
+
return dataPropertiesAreSnapshotCandidates(value, seen);
|
|
230
|
+
}
|
|
231
|
+
finally {
|
|
232
|
+
seen.delete(value);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function dataPropertiesAreSnapshotCandidates(value, seen, ignoredKey) {
|
|
236
|
+
const descriptors = Object.getOwnPropertyDescriptors(value);
|
|
237
|
+
for (const key of Reflect.ownKeys(descriptors)) {
|
|
238
|
+
if (key === ignoredKey)
|
|
239
|
+
continue;
|
|
240
|
+
const descriptor = descriptors[key];
|
|
241
|
+
if (!descriptor || !('value' in descriptor))
|
|
242
|
+
return false;
|
|
243
|
+
if (!isSnapshotCandidate(descriptor.value, seen))
|
|
244
|
+
return false;
|
|
245
|
+
}
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { DEVTOOLS_PROTOCOL_VERSION } from './protocol.js';
|
|
2
|
+
export { DevtoolsModule, devtoolsOptionsOf } from './module.js';
|
|
3
|
+
export type { DevtoolsModuleOptions } from './module.js';
|
|
4
|
+
export { ReplayCapsuleStore, type CapturedValue, type ExecutionReplayCapsulePreview, type ReplayCapsuleStoreOptions, } from './capsule.js';
|
|
5
|
+
export type { ProviderMethodInvocationRequest, ProviderPlaygroundDescriptor, ProviderPlaygroundMethod, ProviderPlaygroundRequest, ReplayCapsuleRequest, ReplayExecutionContext, RuntimeDevtoolsContext, RuntimeDevtoolsContextStorage, RuntimeInspector, RuntimeInvocationResult, RuntimeTraceContext, } from './inspector.js';
|
|
6
|
+
export { RuntimeDevtoolsInstrumentation } from './runtime.js';
|
|
7
|
+
export type { RuntimeDevtoolsInstrumentationOptions } from './runtime.js';
|
|
8
|
+
export type { ExecutionEndedEvent, ExecutionStartedEvent, RuntimeEvent, RuntimeEventBase, RuntimeObserver, RuntimeSpanKind, SerializedDevtoolsError, SpanEndedEvent, SpanStartedEvent, } from './observer.js';
|
|
9
|
+
export { serializeDevtoolsError } from './observer.js';
|
|
10
|
+
export type { DevtoolsValue, DevtoolsValuePreview, DevtoolsValuePreviewOptions, } from './values.js';
|
|
11
|
+
export { previewDevtoolsValue } from './values.js';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { DEVTOOLS_PROTOCOL_VERSION } from './protocol.js';
|
|
2
|
+
export { DevtoolsModule, devtoolsOptionsOf } from './module.js';
|
|
3
|
+
export { ReplayCapsuleStore, } from './capsule.js';
|
|
4
|
+
export { RuntimeDevtoolsInstrumentation } from './runtime.js';
|
|
5
|
+
export { serializeDevtoolsError } from './observer.js';
|
|
6
|
+
export { previewDevtoolsValue } from './values.js';
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { SerializedDevtoolsError } from './observer.js';
|
|
2
|
+
import type { DevtoolsValuePreview } from './values.js';
|
|
3
|
+
export interface ReplayExecutionContext {
|
|
4
|
+
readonly sourceTraceId: string;
|
|
5
|
+
readonly sourceCapsuleId: string;
|
|
6
|
+
readonly parentTraceId?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface ReplayCapsuleRequest {
|
|
9
|
+
readonly requestId: string;
|
|
10
|
+
readonly capsuleId: string;
|
|
11
|
+
readonly inputOverride?: unknown;
|
|
12
|
+
readonly parentTraceId?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface ProviderPlaygroundRequest {
|
|
15
|
+
readonly requestId: string;
|
|
16
|
+
readonly graphNodeId: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ProviderMethodInvocationRequest {
|
|
19
|
+
readonly requestId: string;
|
|
20
|
+
readonly graphNodeId: string;
|
|
21
|
+
readonly method: string;
|
|
22
|
+
readonly args: readonly unknown[];
|
|
23
|
+
}
|
|
24
|
+
export interface ProviderPlaygroundMethod {
|
|
25
|
+
readonly name: string;
|
|
26
|
+
readonly arity: number;
|
|
27
|
+
}
|
|
28
|
+
export interface ProviderPlaygroundDescriptor {
|
|
29
|
+
readonly requestId: string;
|
|
30
|
+
readonly graphNodeId: string;
|
|
31
|
+
readonly providerName: string;
|
|
32
|
+
readonly methods: readonly ProviderPlaygroundMethod[];
|
|
33
|
+
}
|
|
34
|
+
export interface RuntimeInvocationResult {
|
|
35
|
+
readonly requestId: string;
|
|
36
|
+
readonly status: 'ok' | 'error';
|
|
37
|
+
readonly traceId?: string;
|
|
38
|
+
readonly result?: DevtoolsValuePreview;
|
|
39
|
+
readonly error?: SerializedDevtoolsError;
|
|
40
|
+
}
|
|
41
|
+
export interface RuntimeInspector {
|
|
42
|
+
replayCapsule(request: ReplayCapsuleRequest): Promise<RuntimeInvocationResult>;
|
|
43
|
+
providerPlayground(request: ProviderPlaygroundRequest): Promise<ProviderPlaygroundDescriptor>;
|
|
44
|
+
invokeProviderMethod(request: ProviderMethodInvocationRequest): Promise<RuntimeInvocationResult>;
|
|
45
|
+
}
|
|
46
|
+
export interface RuntimeTraceContext {
|
|
47
|
+
readonly traceId: string;
|
|
48
|
+
readonly spanId: string;
|
|
49
|
+
}
|
|
50
|
+
export interface RuntimeDevtoolsContext {
|
|
51
|
+
readonly trace?: RuntimeTraceContext;
|
|
52
|
+
readonly replay?: ReplayExecutionContext;
|
|
53
|
+
}
|
|
54
|
+
export interface RuntimeDevtoolsContextStorage {
|
|
55
|
+
run<T>(context: RuntimeDevtoolsContext, operation: () => T): T;
|
|
56
|
+
current(): RuntimeDevtoolsContext | undefined;
|
|
57
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ApplicationModel } from '../core/model/types.js';
|
|
2
|
+
export interface DevtoolsModuleOptions {
|
|
3
|
+
readonly enabled?: boolean;
|
|
4
|
+
readonly capture?: {
|
|
5
|
+
readonly providerMethods?: boolean;
|
|
6
|
+
readonly results?: false | 'errors' | 'all';
|
|
7
|
+
readonly maxValueBytes?: number;
|
|
8
|
+
readonly maxDepth?: number;
|
|
9
|
+
};
|
|
10
|
+
readonly replay?: {
|
|
11
|
+
readonly enabled?: boolean;
|
|
12
|
+
readonly capsuleTtlMs?: number;
|
|
13
|
+
readonly maxCapsules?: number;
|
|
14
|
+
};
|
|
15
|
+
readonly redact?: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
export declare function DevtoolsModule(options?: DevtoolsModuleOptions): import("../core/module.js").ModuleInstance<import("../core/module.js").ModuleDefinition>;
|
|
18
|
+
export declare function devtoolsOptionsOf(model: ApplicationModel): Readonly<DevtoolsModuleOptions> | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { defineModule } from '../core/module.js';
|
|
2
|
+
import { provide } from '../core/provider.js';
|
|
3
|
+
import { token } from '../core/token.js';
|
|
4
|
+
const devtoolsOptionsToken = token('loutre.devtools.options');
|
|
5
|
+
const devtoolsModuleTemplate = defineModule((options = {}) => ({
|
|
6
|
+
name: 'LoutreDevtoolsModule',
|
|
7
|
+
providers: [
|
|
8
|
+
provide(devtoolsOptionsToken).useValue(Object.freeze({ ...options })),
|
|
9
|
+
],
|
|
10
|
+
}));
|
|
11
|
+
export function DevtoolsModule(options) {
|
|
12
|
+
return devtoolsModuleTemplate(options);
|
|
13
|
+
}
|
|
14
|
+
export function devtoolsOptionsOf(model) {
|
|
15
|
+
const provider = model.providers.find((candidate) => {
|
|
16
|
+
if (candidate.kind !== 'value')
|
|
17
|
+
return false;
|
|
18
|
+
const provided = candidate.provide;
|
|
19
|
+
return (typeof provided !== 'function' &&
|
|
20
|
+
provided.kind === 'token' &&
|
|
21
|
+
provided.id === devtoolsOptionsToken.id);
|
|
22
|
+
});
|
|
23
|
+
return provider?.kind === 'value'
|
|
24
|
+
? provider.useValue
|
|
25
|
+
: undefined;
|
|
26
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { RuntimeExecutionMetadata } from '../core/instrumentation.js';
|
|
2
|
+
import type { DevtoolsValuePreview } from './values.js';
|
|
3
|
+
export type RuntimeSpanKind = 'execution' | 'http.request' | 'http.middleware' | 'http.handler' | 'task.handler' | 'message-port.handler' | 'websocket.handler' | 'di.resolve' | 'provider.construct' | 'provider.method' | 'response' | 'dependency' | 'custom';
|
|
4
|
+
export interface SerializedDevtoolsError {
|
|
5
|
+
readonly name?: string;
|
|
6
|
+
readonly message: string;
|
|
7
|
+
readonly stack?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface RuntimeEventBase {
|
|
10
|
+
readonly runId: string;
|
|
11
|
+
readonly seq: number;
|
|
12
|
+
readonly timestamp: number;
|
|
13
|
+
readonly traceId?: string;
|
|
14
|
+
readonly spanId?: string;
|
|
15
|
+
readonly parentSpanId?: string;
|
|
16
|
+
readonly graphNodeId?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ExecutionStartedEvent extends RuntimeEventBase {
|
|
19
|
+
readonly type: 'execution.started';
|
|
20
|
+
readonly traceId: string;
|
|
21
|
+
readonly spanId: string;
|
|
22
|
+
readonly executionId: string;
|
|
23
|
+
readonly executionKind: string;
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly input?: DevtoolsValuePreview;
|
|
26
|
+
readonly capsuleId?: string;
|
|
27
|
+
readonly replayable?: boolean;
|
|
28
|
+
readonly replayModes?: readonly ('direct' | 'transport')[];
|
|
29
|
+
readonly replayedFromTraceId?: string;
|
|
30
|
+
readonly replayedFromCapsuleId?: string;
|
|
31
|
+
}
|
|
32
|
+
export interface ExecutionEndedEvent extends RuntimeEventBase {
|
|
33
|
+
readonly type: 'execution.ended';
|
|
34
|
+
readonly traceId: string;
|
|
35
|
+
readonly spanId: string;
|
|
36
|
+
readonly executionId: string;
|
|
37
|
+
readonly durationMs: number;
|
|
38
|
+
readonly status: 'ok' | 'error' | 'cancelled';
|
|
39
|
+
readonly attributes?: Readonly<Record<string, unknown>>;
|
|
40
|
+
readonly result?: DevtoolsValuePreview;
|
|
41
|
+
readonly error?: SerializedDevtoolsError;
|
|
42
|
+
}
|
|
43
|
+
export interface SpanStartedEvent extends RuntimeEventBase {
|
|
44
|
+
readonly type: 'span.started';
|
|
45
|
+
readonly spanId: string;
|
|
46
|
+
readonly traceId: string;
|
|
47
|
+
readonly kind: RuntimeSpanKind;
|
|
48
|
+
readonly name: string;
|
|
49
|
+
readonly attributes?: Readonly<Record<string, unknown>>;
|
|
50
|
+
readonly input?: DevtoolsValuePreview;
|
|
51
|
+
readonly capsuleId?: string;
|
|
52
|
+
readonly replayable?: boolean;
|
|
53
|
+
readonly replayModes?: readonly ('direct' | 'transport')[];
|
|
54
|
+
}
|
|
55
|
+
export interface SpanEndedEvent extends RuntimeEventBase {
|
|
56
|
+
readonly type: 'span.ended';
|
|
57
|
+
readonly spanId: string;
|
|
58
|
+
readonly traceId: string;
|
|
59
|
+
readonly durationMs: number;
|
|
60
|
+
readonly status: 'ok' | 'error' | 'cancelled';
|
|
61
|
+
readonly attributes?: Readonly<Record<string, unknown>>;
|
|
62
|
+
readonly result?: DevtoolsValuePreview;
|
|
63
|
+
readonly error?: SerializedDevtoolsError;
|
|
64
|
+
}
|
|
65
|
+
export type RuntimeEvent = ExecutionStartedEvent | ExecutionEndedEvent | SpanStartedEvent | SpanEndedEvent;
|
|
66
|
+
export interface RuntimeObserver {
|
|
67
|
+
readonly runId?: string;
|
|
68
|
+
executionStarted?(event: ExecutionStartedEvent): void;
|
|
69
|
+
executionEnded?(event: ExecutionEndedEvent): void;
|
|
70
|
+
spanStarted?(event: SpanStartedEvent): void;
|
|
71
|
+
spanEnded?(event: SpanEndedEvent): void;
|
|
72
|
+
}
|
|
73
|
+
export interface RuntimeExecutionContext extends RuntimeExecutionMetadata {
|
|
74
|
+
readonly traceId: string;
|
|
75
|
+
readonly spanId: string;
|
|
76
|
+
}
|
|
77
|
+
export declare function serializeDevtoolsError(error: unknown): SerializedDevtoolsError;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const DEVTOOLS_PROTOCOL_VERSION = 1;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const DEVTOOLS_PROTOCOL_VERSION = 1;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface ProviderMethodInvocation {
|
|
2
|
+
readonly key: PropertyKey;
|
|
3
|
+
readonly name: string;
|
|
4
|
+
readonly original: Function;
|
|
5
|
+
readonly receiver: unknown;
|
|
6
|
+
readonly args: readonly unknown[];
|
|
7
|
+
}
|
|
8
|
+
export interface ProviderMethodDefinition {
|
|
9
|
+
readonly key: PropertyKey;
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly original: Function;
|
|
12
|
+
}
|
|
13
|
+
export declare function discoverProviderMethods(instance: object): readonly ProviderMethodDefinition[];
|
|
14
|
+
export declare function instrumentProviderMethods(instance: object, invoke: (invocation: ProviderMethodInvocation) => unknown): () => void;
|
|
@@ -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
|
+
}
|