@heojeongbo/wirestage 0.1.0 → 0.2.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/entities/plugin/model/http.d.ts +12 -3
- package/dist/entities/plugin/model/http.d.ts.map +1 -1
- package/dist/entities/plugin/model/socket.d.ts +12 -7
- package/dist/entities/plugin/model/socket.d.ts.map +1 -1
- package/dist/entities/plugin/model/stream.d.ts +7 -4
- package/dist/entities/plugin/model/stream.d.ts.map +1 -1
- package/dist/features/sse/lib/create-sse.d.ts.map +1 -1
- package/dist/features/sse/lib/create-sse.js +2 -1
- package/dist/features/sse/lib/create-sse.js.map +1 -1
- package/dist/features/sse/model/types.d.ts +8 -2
- package/dist/features/sse/model/types.d.ts.map +1 -1
- package/dist/features/websocket/lib/proxy.d.ts.map +1 -1
- package/dist/features/websocket/lib/proxy.js +16 -3
- package/dist/features/websocket/lib/proxy.js.map +1 -1
- package/dist/features/websocket/model/types.d.ts +5 -1
- package/dist/features/websocket/model/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/shared/api/playwright/ports.d.ts +10 -1
- package/dist/shared/api/playwright/ports.d.ts.map +1 -1
- package/dist/shared/api/stream-server/stream-server.d.ts +3 -1
- package/dist/shared/api/stream-server/stream-server.d.ts.map +1 -1
- package/dist/shared/api/stream-server/stream-server.js +10 -3
- package/dist/shared/api/stream-server/stream-server.js.map +1 -1
- package/dist/widgets/stage/index.d.ts +1 -1
- package/dist/widgets/stage/index.d.ts.map +1 -1
- package/dist/widgets/stage/lib/create-stage.d.ts.map +1 -1
- package/dist/widgets/stage/lib/create-stage.js +4 -0
- package/dist/widgets/stage/lib/create-stage.js.map +1 -1
- package/dist/widgets/stage/lib/http-router.d.ts +11 -2
- package/dist/widgets/stage/lib/http-router.d.ts.map +1 -1
- package/dist/widgets/stage/lib/http-router.js +317 -76
- package/dist/widgets/stage/lib/http-router.js.map +1 -1
- package/dist/widgets/stage/lib/reads.d.ts +4 -1
- package/dist/widgets/stage/lib/reads.d.ts.map +1 -1
- package/dist/widgets/stage/lib/reads.js +32 -1
- package/dist/widgets/stage/lib/reads.js.map +1 -1
- package/dist/widgets/stage/lib/response-stream.d.ts +17 -3
- package/dist/widgets/stage/lib/response-stream.d.ts.map +1 -1
- package/dist/widgets/stage/lib/response-stream.js +88 -36
- package/dist/widgets/stage/lib/response-stream.js.map +1 -1
- package/dist/widgets/stage/lib/socket-router.d.ts.map +1 -1
- package/dist/widgets/stage/lib/socket-router.js +44 -15
- package/dist/widgets/stage/lib/socket-router.js.map +1 -1
- package/dist/widgets/stage/model/stage.d.ts +40 -7
- package/dist/widgets/stage/model/stage.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/entities/plugin/model/http.ts +12 -3
- package/src/entities/plugin/model/socket.ts +12 -7
- package/src/entities/plugin/model/stream.ts +7 -4
- package/src/features/sse/lib/create-sse.ts +2 -1
- package/src/features/sse/model/types.ts +8 -2
- package/src/features/websocket/lib/proxy.ts +15 -3
- package/src/features/websocket/model/types.ts +5 -1
- package/src/index.ts +2 -0
- package/src/shared/api/playwright/ports.ts +7 -2
- package/src/shared/api/stream-server/stream-server.ts +11 -3
- package/src/widgets/stage/index.ts +2 -0
- package/src/widgets/stage/lib/create-stage.ts +4 -0
- package/src/widgets/stage/lib/http-router.ts +425 -91
- package/src/widgets/stage/lib/reads.ts +44 -2
- package/src/widgets/stage/lib/response-stream.ts +110 -37
- package/src/widgets/stage/lib/socket-router.ts +45 -16
- package/src/widgets/stage/model/stage.ts +42 -7
|
@@ -4,6 +4,7 @@ import type {
|
|
|
4
4
|
Timeline,
|
|
5
5
|
} from "../../../entities/frame/index.js";
|
|
6
6
|
import type { WaitForOptions } from "../../../entities/plugin/index.js";
|
|
7
|
+
import type { PagePort } from "../../../shared/api/playwright/index.js";
|
|
7
8
|
import type { Clock } from "../../../shared/lib/clock/index.js";
|
|
8
9
|
import { WirestageError } from "../../../shared/lib/errors/index.js";
|
|
9
10
|
import type { StageCore } from "../model/stage.js";
|
|
@@ -13,6 +14,8 @@ export interface ReadsDeps {
|
|
|
13
14
|
readonly clock: Clock;
|
|
14
15
|
/** Flush every page buffer into the timeline. */
|
|
15
16
|
readonly sync: () => Promise<void>;
|
|
17
|
+
/** The id frames use for a page. */
|
|
18
|
+
readonly pageId: (page: PagePort) => string;
|
|
16
19
|
/** Defaults for `waitFor`, in milliseconds. */
|
|
17
20
|
readonly wait: { readonly timeout: number; readonly interval: number };
|
|
18
21
|
}
|
|
@@ -26,7 +29,14 @@ const REPORT_LIMIT = 1_000;
|
|
|
26
29
|
|
|
27
30
|
export type StageReads = Pick<
|
|
28
31
|
StageCore,
|
|
29
|
-
|
|
32
|
+
| "frames"
|
|
33
|
+
| "drain"
|
|
34
|
+
| "mark"
|
|
35
|
+
| "cursor"
|
|
36
|
+
| "since"
|
|
37
|
+
| "between"
|
|
38
|
+
| "waitFor"
|
|
39
|
+
| "report"
|
|
30
40
|
>;
|
|
31
41
|
|
|
32
42
|
/**
|
|
@@ -58,17 +68,49 @@ export function createReads(deps: ReadsDeps): StageReads {
|
|
|
58
68
|
await sync();
|
|
59
69
|
return drainCursor.take(filter);
|
|
60
70
|
},
|
|
61
|
-
async mark(name,
|
|
71
|
+
async mark(name, options = {}) {
|
|
62
72
|
await sync();
|
|
73
|
+
const { page, meta } = options;
|
|
63
74
|
return timeline.append({
|
|
64
75
|
protocol: "wirestage",
|
|
65
76
|
kind: "marker",
|
|
66
77
|
direction: "none",
|
|
67
78
|
endpoint: name,
|
|
68
79
|
source: "test",
|
|
80
|
+
...(page === undefined
|
|
81
|
+
? {}
|
|
82
|
+
: { page: typeof page === "string" ? page : deps.pageId(page) }),
|
|
69
83
|
...(meta === undefined ? {} : { meta }),
|
|
70
84
|
});
|
|
71
85
|
},
|
|
86
|
+
cursor(base = {}) {
|
|
87
|
+
let position = 0;
|
|
88
|
+
const read = async (filter: FrameFilter) => {
|
|
89
|
+
await sync();
|
|
90
|
+
const where = filter.where;
|
|
91
|
+
return timeline.frames({
|
|
92
|
+
...base,
|
|
93
|
+
...filter,
|
|
94
|
+
after: Math.max(position, base.after ?? 0, filter.after ?? 0),
|
|
95
|
+
where: (frame) =>
|
|
96
|
+
(base.where === undefined || base.where(frame)) &&
|
|
97
|
+
(where === undefined || where(frame)),
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
return {
|
|
101
|
+
async take(filter = {}) {
|
|
102
|
+
const frames = await read(filter);
|
|
103
|
+
// Past everything that arrived, not just what matched: the next
|
|
104
|
+
// take starts where this one looked.
|
|
105
|
+
position = Math.max(position, timeline.last()?.seq ?? 0);
|
|
106
|
+
return frames;
|
|
107
|
+
},
|
|
108
|
+
peek: (filter = {}) => read(filter),
|
|
109
|
+
get position() {
|
|
110
|
+
return position;
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
},
|
|
72
114
|
async since(marker, filter) {
|
|
73
115
|
await sync();
|
|
74
116
|
const after = requireMarker(marker).seq;
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
IncomingMessage,
|
|
3
|
+
OutgoingHttpHeader,
|
|
4
|
+
ServerResponse,
|
|
5
|
+
} from "node:http";
|
|
2
6
|
|
|
3
7
|
import type {
|
|
4
8
|
ResponseStream,
|
|
@@ -8,59 +12,128 @@ import { createDeferred } from "../../../shared/lib/async/index.js";
|
|
|
8
12
|
import { toBytes } from "../../../shared/lib/bytes/index.js";
|
|
9
13
|
import { WirestageError } from "../../../shared/lib/errors/index.js";
|
|
10
14
|
|
|
15
|
+
/** A stream as the stage serves it: the rule's end of it, and the stage's. */
|
|
16
|
+
export interface ServedStream {
|
|
17
|
+
/** What the rule writes. */
|
|
18
|
+
readonly stream: ResponseStream;
|
|
19
|
+
/** The stub asked for the end, and `closed` is on its way. */
|
|
20
|
+
readonly closing: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* The rule writing the stream failed: answer 500 with `body` while
|
|
23
|
+
* nothing has gone out, and cut the connection after. False when the
|
|
24
|
+
* response was over first — ended, cut, or let go of by the page.
|
|
25
|
+
*/
|
|
26
|
+
fail(body: string): boolean;
|
|
27
|
+
}
|
|
28
|
+
|
|
11
29
|
/**
|
|
12
30
|
* A Node response as the stream a rule writes: the adapter between the
|
|
13
31
|
* stream server's sockets and the plugin contract, which knows nothing of
|
|
14
|
-
* Node.
|
|
32
|
+
* Node. `onClose` hears how the response ended before `closed` resolves, so
|
|
33
|
+
* whoever awaits `closed` finds the end already on record.
|
|
15
34
|
*/
|
|
16
35
|
export function createResponseStream(
|
|
17
36
|
request: IncomingMessage,
|
|
18
37
|
response: ServerResponse,
|
|
19
|
-
|
|
38
|
+
onClose: (end: StreamEnd, failed: boolean) => void,
|
|
39
|
+
): ServedStream {
|
|
20
40
|
let state: "open" | "ended" | "cut" = "open";
|
|
41
|
+
let failed = false;
|
|
21
42
|
const closed = createDeferred<StreamEnd>();
|
|
22
|
-
response.once("close", () =>
|
|
43
|
+
response.once("close", () => {
|
|
23
44
|
// Not ended and not cut: the page let go of it.
|
|
24
|
-
|
|
25
|
-
|
|
45
|
+
const end = state === "open" ? "cancelled" : state;
|
|
46
|
+
onClose(end, failed);
|
|
47
|
+
closed.resolve(end);
|
|
48
|
+
});
|
|
49
|
+
/** The stream server's own headers (CORS, `connection`), before the rule's. */
|
|
50
|
+
const own = Object.entries(response.getHeaders());
|
|
51
|
+
/** Back to those: what a rule's head set is gone, what it replaced is back. */
|
|
52
|
+
const restore = () => {
|
|
53
|
+
for (const name of response.getHeaderNames()) response.removeHeader(name);
|
|
54
|
+
for (const [name, value] of own)
|
|
55
|
+
response.setHeader(name, value as OutgoingHttpHeader);
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Whether a write can reach the page. Node drops a write — callback and
|
|
59
|
+
* all — into a socket destroyed or closed for writing, which is what the
|
|
60
|
+
* page letting go leaves, well before the response reports its close.
|
|
61
|
+
*/
|
|
62
|
+
const writable = () => state === "open" && response.socket?.writable === true;
|
|
26
63
|
const requestHeaders: Record<string, string> = {};
|
|
27
64
|
for (const [name, value] of Object.entries(request.headers))
|
|
28
65
|
requestHeaders[name] = String(value);
|
|
29
66
|
return {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
67
|
+
stream: {
|
|
68
|
+
requestHeaders,
|
|
69
|
+
head(status, headers = {}) {
|
|
70
|
+
// Node checks a status only as the head goes out, and refused by
|
|
71
|
+
// `end()` it leaves the response broken: a 500 after it has no body.
|
|
72
|
+
if (!Number.isInteger(status) || status < 100 || status > 999)
|
|
73
|
+
throw new WirestageError(
|
|
74
|
+
"INVALID_ARGUMENT",
|
|
75
|
+
`a response status is an integer from 100 to 999, got ${status}`,
|
|
76
|
+
);
|
|
77
|
+
// Kept off the wire until the first write or flush, so a rule that
|
|
78
|
+
// fails before then can still be answered with its 500.
|
|
79
|
+
restore();
|
|
80
|
+
response.statusCode = status;
|
|
81
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
82
|
+
// The stream server's to say: one response per connection.
|
|
83
|
+
if (name.toLowerCase() === "connection") continue;
|
|
84
|
+
// Several values, one per line (`set-cookie`): Node sends a
|
|
85
|
+
// header line for each, and refuses a value with a newline.
|
|
86
|
+
const values = value.split("\n");
|
|
87
|
+
response.setHeader(name, values.length > 1 ? values : value);
|
|
42
88
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
response.
|
|
56
|
-
|
|
89
|
+
},
|
|
90
|
+
flush() {
|
|
91
|
+
response.flushHeaders();
|
|
92
|
+
},
|
|
93
|
+
write(chunk) {
|
|
94
|
+
return new Promise<void>((resolve, reject) => {
|
|
95
|
+
if (!writable()) {
|
|
96
|
+
reject(
|
|
97
|
+
new WirestageError("CLOSED", "the response stream is closed"),
|
|
98
|
+
);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
response.write(toBytes(chunk), (error) =>
|
|
102
|
+
error ? reject(error) : resolve(),
|
|
103
|
+
);
|
|
57
104
|
});
|
|
105
|
+
},
|
|
106
|
+
async end() {
|
|
107
|
+
if (writable()) {
|
|
108
|
+
state = "ended";
|
|
109
|
+
response.end();
|
|
110
|
+
}
|
|
111
|
+
await closed.promise;
|
|
112
|
+
},
|
|
113
|
+
cut() {
|
|
114
|
+
if (!writable()) return;
|
|
115
|
+
state = "cut";
|
|
116
|
+
response.destroy();
|
|
117
|
+
},
|
|
118
|
+
closed: closed.promise,
|
|
58
119
|
},
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
120
|
+
get closing() {
|
|
121
|
+
return state !== "open" && !closed.settled;
|
|
122
|
+
},
|
|
123
|
+
fail(body) {
|
|
124
|
+
if (!writable()) return false;
|
|
125
|
+
failed = true;
|
|
126
|
+
if (response.headersSent) {
|
|
127
|
+
state = "cut";
|
|
128
|
+
response.destroy();
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
state = "ended";
|
|
132
|
+
restore();
|
|
133
|
+
response
|
|
134
|
+
.writeHead(500, { "content-type": "text/plain; charset=utf-8" })
|
|
135
|
+
.end(body);
|
|
136
|
+
return true;
|
|
63
137
|
},
|
|
64
|
-
closed: closed.promise,
|
|
65
138
|
};
|
|
66
139
|
}
|
|
@@ -108,24 +108,44 @@ export function createSocketRouter(deps: SocketRouterDeps): SocketRouterEngine {
|
|
|
108
108
|
entries.splice(entries.indexOf(entry), 1);
|
|
109
109
|
};
|
|
110
110
|
|
|
111
|
+
/**
|
|
112
|
+
* One end, as a rule drives it. Playwright calls the rule's handlers from
|
|
113
|
+
* its own event dispatch, where a throw is an uncaught exception and a
|
|
114
|
+
* rejection an unhandled one — either surfacing in whichever test runs
|
|
115
|
+
* next. A handler that fails fails the rule instead, as `open` does.
|
|
116
|
+
*/
|
|
111
117
|
const peer = (
|
|
112
118
|
route: WebSocketRoutePort,
|
|
119
|
+
onFailure: (error: unknown) => Promise<void>,
|
|
113
120
|
close: (options: CloseOptions) => CloseOptions = (options) => options,
|
|
114
|
-
): SocketPeer =>
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
+
): SocketPeer => {
|
|
122
|
+
const guarded =
|
|
123
|
+
<Args extends unknown[]>(handler: (...args: Args) => void) =>
|
|
124
|
+
(...args: Args): void => {
|
|
125
|
+
try {
|
|
126
|
+
// Typed `void`, yet an async function is a valid handler.
|
|
127
|
+
const result: unknown = handler(...args);
|
|
128
|
+
if (result instanceof Promise) result.catch(onFailure);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
void onFailure(error);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
return {
|
|
134
|
+
send: (data) =>
|
|
135
|
+
route.send(typeof data === "string" ? data : toBuffer(data)),
|
|
136
|
+
close: (options) => route.close(close(options)),
|
|
137
|
+
onMessage: (handler) => route.onMessage(guarded(handler)),
|
|
138
|
+
onClose: (handler) => route.onClose(guarded(handler)),
|
|
139
|
+
};
|
|
140
|
+
};
|
|
121
141
|
|
|
122
142
|
/**
|
|
123
|
-
* A rule that throws
|
|
124
|
-
* log, on the timeline, and in the
|
|
125
|
-
* and close both ends: the page's
|
|
126
|
-
* nobody answering it, and a server
|
|
127
|
-
* connected. 1011 is a server's "internal
|
|
128
|
-
* 1000, as a script may not send 1011.
|
|
143
|
+
* A rule that throws, in `open` or in a handler it set, is a bug in the
|
|
144
|
+
* test. Say which rule failed — in the log, on the timeline, and in the
|
|
145
|
+
* reason the page's socket closes with — and close both ends: the page's
|
|
146
|
+
* socket would otherwise sit open with nobody answering it, and a server
|
|
147
|
+
* the rule connected would stay connected. 1011 is a server's "internal
|
|
148
|
+
* error"; the server's end gets 1000, as a script may not send 1011.
|
|
129
149
|
*/
|
|
130
150
|
const fail = async (
|
|
131
151
|
rule: SocketRule,
|
|
@@ -178,16 +198,25 @@ export function createSocketRouter(deps: SocketRouterDeps): SocketRouterEngine {
|
|
|
178
198
|
// the rule's own replaces.
|
|
179
199
|
route.onMessage(drop);
|
|
180
200
|
let server: SocketPeer | undefined;
|
|
201
|
+
let failed = false;
|
|
202
|
+
// Once per socket, for whatever fails first. Marked before failing:
|
|
203
|
+
// the closes that follow are reported to the rule's handlers, and a
|
|
204
|
+
// broken one throws again.
|
|
205
|
+
const failOnce = async (error: unknown): Promise<void> => {
|
|
206
|
+
if (failed) return;
|
|
207
|
+
failed = true;
|
|
208
|
+
await fail(entry.rule, view, server, error);
|
|
209
|
+
};
|
|
181
210
|
const view: SocketView = {
|
|
182
211
|
correlation: deps.correlation(),
|
|
183
212
|
url,
|
|
184
213
|
context: attachment.id,
|
|
185
|
-
page: peer(route),
|
|
214
|
+
page: peer(route, failOnce),
|
|
186
215
|
connectToServer: () => {
|
|
187
216
|
if (server === undefined) {
|
|
188
217
|
const upstream = route.connectToServer();
|
|
189
218
|
upstream.onMessage(drop);
|
|
190
|
-
server = peer(upstream, forServer);
|
|
219
|
+
server = peer(upstream, failOnce, forServer);
|
|
191
220
|
}
|
|
192
221
|
return server;
|
|
193
222
|
},
|
|
@@ -200,7 +229,7 @@ export function createSocketRouter(deps: SocketRouterDeps): SocketRouterEngine {
|
|
|
200
229
|
} catch (error) {
|
|
201
230
|
// Never rejected to Playwright, where it would surface as an
|
|
202
231
|
// unhandled rejection in whichever test runs next.
|
|
203
|
-
await
|
|
232
|
+
await failOnce(error);
|
|
204
233
|
}
|
|
205
234
|
};
|
|
206
235
|
|
|
@@ -30,9 +30,11 @@ export interface StageOptions<Plugins extends readonly AnyWirePlugin[]> {
|
|
|
30
30
|
*/
|
|
31
31
|
readonly pageFlushMs?: number;
|
|
32
32
|
/**
|
|
33
|
-
* How long a page may take to answer a flush or a command
|
|
34
|
-
*
|
|
35
|
-
*
|
|
33
|
+
* How long a page may take to answer a flush or a command, or the
|
|
34
|
+
* browser to hand over a response's body, before a read goes on without
|
|
35
|
+
* it; how long a live stream's request may take to reach the stream
|
|
36
|
+
* server before the stream is given up; and how long Playwright may take
|
|
37
|
+
* to take the page hooks out before a detach goes on. Default 2000 ms.
|
|
36
38
|
*/
|
|
37
39
|
readonly pageTimeoutMs?: number;
|
|
38
40
|
/** TLS for live streams on https pages, and the bind host. */
|
|
@@ -53,6 +55,34 @@ export interface StageReport {
|
|
|
53
55
|
readonly text: string;
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
export interface MarkOptions {
|
|
59
|
+
/**
|
|
60
|
+
* The page the marker is about. The marker carries its id, so a read
|
|
61
|
+
* narrowed to that page (`{ page }`) includes it.
|
|
62
|
+
*/
|
|
63
|
+
readonly page?: PagePort | string;
|
|
64
|
+
/** Anything else worth keeping on the marker. */
|
|
65
|
+
readonly meta?: Readonly<Record<string, unknown>>;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* A reading position of its own on the timeline: what a helper that keeps a
|
|
70
|
+
* log per page, per channel or per check reads from, without moving the
|
|
71
|
+
* stage's `drain` or another cursor.
|
|
72
|
+
*/
|
|
73
|
+
export interface StageCursor {
|
|
74
|
+
/**
|
|
75
|
+
* Bring the timeline up to date, then take what reached it since this
|
|
76
|
+
* cursor last took — narrowed by the cursor's filter and `filter` — and
|
|
77
|
+
* move past all of it, matching or not.
|
|
78
|
+
*/
|
|
79
|
+
take(filter?: FrameFilter): Promise<Frame[]>;
|
|
80
|
+
/** The same frames as `take`, without moving. */
|
|
81
|
+
peek(filter?: FrameFilter): Promise<Frame[]>;
|
|
82
|
+
/** The `seq` the cursor has read up to; 0 before its first take. */
|
|
83
|
+
readonly position: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
56
86
|
export interface ReportOptions {
|
|
57
87
|
/** Frames kept, counting back from the newest. Default 1,000. */
|
|
58
88
|
readonly limit?: number;
|
|
@@ -79,16 +109,21 @@ export interface StageCore {
|
|
|
79
109
|
/** The id frames use for this page. */
|
|
80
110
|
pageId(page: PagePort): string;
|
|
81
111
|
/**
|
|
82
|
-
* Bring the timeline up to date: flush every page's buffer and wait for
|
|
83
|
-
* the responses already back from the network
|
|
84
|
-
* you.
|
|
112
|
+
* Bring the timeline up to date: flush every page's buffer, and wait for
|
|
113
|
+
* the responses already back from the network and the ends of the
|
|
114
|
+
* streams a stub closed. The reads below do it for you.
|
|
85
115
|
*/
|
|
86
116
|
sync(): Promise<void>;
|
|
87
117
|
frames(filter?: FrameFilter): Promise<Frame[]>;
|
|
88
118
|
/** Frames since the previous `drain` (any filter), narrowed by `filter`. */
|
|
89
119
|
drain(filter?: FrameFilter): Promise<Frame[]>;
|
|
90
120
|
/** Put a named marker on the timeline, after everything that happened so far. */
|
|
91
|
-
mark(name: string,
|
|
121
|
+
mark(name: string, options?: MarkOptions): Promise<Frame>;
|
|
122
|
+
/**
|
|
123
|
+
* A cursor of its own, from the start of the timeline: every `take`
|
|
124
|
+
* returns what is new since the previous one.
|
|
125
|
+
*/
|
|
126
|
+
cursor(filter?: FrameFilter): StageCursor;
|
|
92
127
|
/** Frames after the most recent marker called `marker`. */
|
|
93
128
|
since(marker: string, filter?: FrameFilter): Promise<Frame[]>;
|
|
94
129
|
/** Frames between two markers, exclusive. */
|