@scenetest/dashboard 0.11.0 → 0.12.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/README.md +87 -35
- package/dist/app.d.ts +10 -7
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +12 -130
- package/dist/app.js.map +1 -1
- package/dist/collections/index.d.ts +19 -0
- package/dist/collections/index.d.ts.map +1 -0
- package/dist/collections/index.js +18 -0
- package/dist/collections/index.js.map +1 -0
- package/dist/collections/options.d.ts +29 -0
- package/dist/collections/options.d.ts.map +1 -0
- package/dist/collections/options.js +68 -0
- package/dist/collections/options.js.map +1 -0
- package/dist/collections/projections.d.ts +117 -0
- package/dist/collections/projections.d.ts.map +1 -0
- package/dist/collections/projections.js +228 -0
- package/dist/collections/projections.js.map +1 -0
- package/dist/collections/source.d.ts +32 -0
- package/dist/collections/source.d.ts.map +1 -0
- package/dist/collections/source.js +84 -0
- package/dist/collections/source.js.map +1 -0
- package/dist/collections/types.d.ts +87 -0
- package/dist/collections/types.d.ts.map +1 -0
- package/dist/collections/types.js +2 -0
- package/dist/collections/types.js.map +1 -0
- package/dist/dashboard.d.ts +20 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +114 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/dev-transport.d.ts +3 -3
- package/dist/dev-transport.d.ts.map +1 -1
- package/dist/dev-transport.js +3 -7
- package/dist/dev-transport.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/runner.d.ts +8 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +249 -0
- package/dist/runner.js.map +1 -0
- package/dist/select-helpers.d.ts +50 -0
- package/dist/select-helpers.d.ts.map +1 -0
- package/dist/select-helpers.js +48 -0
- package/dist/select-helpers.js.map +1 -0
- package/dist/select-runner.d.ts +55 -0
- package/dist/select-runner.d.ts.map +1 -0
- package/dist/select-runner.js +108 -0
- package/dist/select-runner.js.map +1 -0
- package/dist/select-waterfall.d.ts +12 -0
- package/dist/select-waterfall.d.ts.map +1 -0
- package/dist/select-waterfall.js +79 -0
- package/dist/select-waterfall.js.map +1 -0
- package/dist/style.css +878 -0
- package/dist/types.d.ts +6 -20
- package/dist/types.d.ts.map +1 -1
- package/dist/use-live-query.d.ts +28 -0
- package/dist/use-live-query.d.ts.map +1 -0
- package/dist/use-live-query.js +25 -0
- package/dist/use-live-query.js.map +1 -0
- package/dist/use-run-slice.d.ts +25 -0
- package/dist/use-run-slice.d.ts.map +1 -0
- package/dist/use-run-slice.js +48 -0
- package/dist/use-run-slice.js.map +1 -0
- package/package.json +10 -5
- package/dist/mount.d.ts +0 -14
- package/dist/mount.d.ts.map +0 -1
- package/dist/mount.js +0 -50
- package/dist/mount.js.map +0 -1
- package/dist/store.d.ts +0 -19
- package/dist/store.d.ts.map +0 -1
- package/dist/store.js +0 -164
- package/dist/store.js.map +0 -1
- package/dist/styles.d.ts +0 -10
- package/dist/styles.d.ts.map +0 -1
- package/dist/styles.js +0 -152
- package/dist/styles.js.map +0 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import type { TeamMeta } from '@scenetest/protocol';
|
|
2
|
+
import type { RunProjection } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Every event carries its own `runId` (producers stamp it; see
|
|
5
|
+
* `@scenetest/protocol`). Rows from every run of a PR live in one collection,
|
|
6
|
+
* partitioned by that id, so the picker / rollups / flaky detection are
|
|
7
|
+
* queries over the rows rather than separate fetches. A new `run:start` opens
|
|
8
|
+
* a new partition — it does **not** truncate (that was the single-run
|
|
9
|
+
* behaviour; see `docs/public/design/unified-console.md`). Because the id is
|
|
10
|
+
* on the event, projections are stateless and a collection can attach
|
|
11
|
+
* mid-stream without having seen the run's `run:start`.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Derived current-state of one scene, scoped to its run. Folded from a
|
|
15
|
+
* scene's `scene:start` / `scene:end` pair. The live timeline is
|
|
16
|
+
* `where runId = <latest>`.
|
|
17
|
+
*/
|
|
18
|
+
export interface SceneRow {
|
|
19
|
+
/** `${runId}:${teamIndex}:${name}` — stable across the scene's start/end. */
|
|
20
|
+
id: string;
|
|
21
|
+
runId: string;
|
|
22
|
+
name: string;
|
|
23
|
+
file: string;
|
|
24
|
+
actors: string[];
|
|
25
|
+
status: 'running' | 'completed' | 'failed' | 'timeout' | string;
|
|
26
|
+
startTime: number;
|
|
27
|
+
endTime: number | null;
|
|
28
|
+
duration: number | null;
|
|
29
|
+
error: string | null;
|
|
30
|
+
team: TeamMeta;
|
|
31
|
+
teamIndex: number;
|
|
32
|
+
}
|
|
33
|
+
export declare function scenesProjection(): RunProjection<SceneRow, string>;
|
|
34
|
+
/**
|
|
35
|
+
* One row per inline assertion — the append-only end of the stream, scoped
|
|
36
|
+
* to its run. Keyed by `${runId}:${n}` (a per-projection monotonic index;
|
|
37
|
+
* the stream carries no assertion id).
|
|
38
|
+
*
|
|
39
|
+
* `sceneId` is the owning scene's {@link SceneRow.id} when the producer stamped
|
|
40
|
+
* `scene`/`teamIndex` on the event (the precise path); it's `null` for runs
|
|
41
|
+
* recorded before producers did so, where the consumer attributes by
|
|
42
|
+
* actor+time-window instead (see {@link attributeToScene}).
|
|
43
|
+
*/
|
|
44
|
+
export interface AssertionRecord {
|
|
45
|
+
id: string;
|
|
46
|
+
runId: string;
|
|
47
|
+
actor: string | null;
|
|
48
|
+
description: string;
|
|
49
|
+
result: boolean;
|
|
50
|
+
timestamp: number;
|
|
51
|
+
sceneId: string | null;
|
|
52
|
+
}
|
|
53
|
+
export declare function assertionsProjection(): RunProjection<AssertionRecord, string>;
|
|
54
|
+
/**
|
|
55
|
+
* Attribute an event row to a scene. Prefers the stamped `sceneId` (precise,
|
|
56
|
+
* concurrency-safe); falls back to actor + time-window for rows from older
|
|
57
|
+
* producers: the scene of the same run whose `actors` include `actor` and
|
|
58
|
+
* whose `[startTime, endTime]` window contains `timestamp`. The window is
|
|
59
|
+
* open-ended while a scene is still running (`endTime === null`). Returns the
|
|
60
|
+
* matching scene's id, or `null` when nothing attributes (e.g. an actor-less
|
|
61
|
+
* inline assertion on an old run).
|
|
62
|
+
*/
|
|
63
|
+
export declare function attributeToScene(row: {
|
|
64
|
+
sceneId: string | null;
|
|
65
|
+
runId: string;
|
|
66
|
+
actor: string | null;
|
|
67
|
+
timestamp: number;
|
|
68
|
+
}, scenes: Iterable<SceneRow>): string | null;
|
|
69
|
+
/**
|
|
70
|
+
* One row per DSL action, paired from its `action:start` / `action:end`. The
|
|
71
|
+
* projection mints an id on start and updates the same row on end, tracking
|
|
72
|
+
* open actions per `actor+action` (the stream carries no action id). `sceneId`
|
|
73
|
+
* comes from a stamped `scene`/`teamIndex` on the start event when present;
|
|
74
|
+
* otherwise the consumer attributes by actor+time-window like assertions.
|
|
75
|
+
*/
|
|
76
|
+
export interface ActionRecord {
|
|
77
|
+
id: string;
|
|
78
|
+
runId: string;
|
|
79
|
+
actor: string;
|
|
80
|
+
action: string;
|
|
81
|
+
target: string | null;
|
|
82
|
+
startTime: number;
|
|
83
|
+
endTime: number | null;
|
|
84
|
+
duration: number | null;
|
|
85
|
+
error: string | null;
|
|
86
|
+
status: 'running' | 'success' | 'error';
|
|
87
|
+
sceneId: string | null;
|
|
88
|
+
/** Start timestamp — the time used for actor+time-window attribution. */
|
|
89
|
+
timestamp: number;
|
|
90
|
+
}
|
|
91
|
+
export declare function actionsProjection(): RunProjection<ActionRecord, string>;
|
|
92
|
+
/**
|
|
93
|
+
* One row per run — the picker, the "most recent" rollup, and (later) flaky
|
|
94
|
+
* detection are all live queries over this table. Folded from `run:start`
|
|
95
|
+
* (insert), `scene:end` (incremental pass/fail counts so the rollup is live),
|
|
96
|
+
* and `run:end` (authoritative counts + duration from the summary).
|
|
97
|
+
*
|
|
98
|
+
* `pr` / `branch` are stamped by the report loader from the filename
|
|
99
|
+
* (`pr-{num}-{timestamp}-…`), not the event stream, so they're absent for a
|
|
100
|
+
* live local run.
|
|
101
|
+
*/
|
|
102
|
+
export interface RunRow {
|
|
103
|
+
/** runId — the `run:start` timestamp. */
|
|
104
|
+
id: string;
|
|
105
|
+
startTime: number;
|
|
106
|
+
endTime: number | null;
|
|
107
|
+
duration: number | null;
|
|
108
|
+
status: 'running' | 'finished' | string;
|
|
109
|
+
/** Expected scene count from `run:start`. */
|
|
110
|
+
sceneCount: number;
|
|
111
|
+
completed: number;
|
|
112
|
+
failed: number;
|
|
113
|
+
pr?: number;
|
|
114
|
+
branch?: string;
|
|
115
|
+
}
|
|
116
|
+
export declare function runsProjection(): RunProjection<RunRow, string>;
|
|
117
|
+
//# sourceMappingURL=projections.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projections.d.ts","sourceRoot":"","sources":["../../src/collections/projections.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
function sceneKey(runId, teamIndex, name) {
|
|
2
|
+
return `${runId}:${teamIndex}:${name}`;
|
|
3
|
+
}
|
|
4
|
+
export function scenesProjection() {
|
|
5
|
+
return {
|
|
6
|
+
id: 'scenes',
|
|
7
|
+
getKey: (row) => row.id,
|
|
8
|
+
project(event, get) {
|
|
9
|
+
switch (event.type) {
|
|
10
|
+
case 'scene:start': {
|
|
11
|
+
const row = {
|
|
12
|
+
id: sceneKey(event.runId, event.teamIndex, event.name),
|
|
13
|
+
runId: event.runId,
|
|
14
|
+
name: event.name,
|
|
15
|
+
file: event.file,
|
|
16
|
+
actors: event.actors.slice(),
|
|
17
|
+
status: 'running',
|
|
18
|
+
startTime: event.timestamp,
|
|
19
|
+
endTime: null,
|
|
20
|
+
duration: null,
|
|
21
|
+
error: null,
|
|
22
|
+
team: event.team,
|
|
23
|
+
teamIndex: event.teamIndex,
|
|
24
|
+
};
|
|
25
|
+
return [{ type: 'insert', value: row }];
|
|
26
|
+
}
|
|
27
|
+
case 'scene:end': {
|
|
28
|
+
const prev = get(sceneKey(event.runId, event.teamIndex, event.name));
|
|
29
|
+
if (!prev)
|
|
30
|
+
return [];
|
|
31
|
+
return [
|
|
32
|
+
{
|
|
33
|
+
type: 'update',
|
|
34
|
+
value: {
|
|
35
|
+
...prev,
|
|
36
|
+
status: event.status,
|
|
37
|
+
endTime: event.timestamp,
|
|
38
|
+
duration: event.duration,
|
|
39
|
+
error: event.error ?? null,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
}
|
|
44
|
+
default:
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export function assertionsProjection() {
|
|
51
|
+
let next = 0;
|
|
52
|
+
return {
|
|
53
|
+
id: 'assertions',
|
|
54
|
+
getKey: (row) => row.id,
|
|
55
|
+
project(event) {
|
|
56
|
+
if (event.type === 'assertion') {
|
|
57
|
+
return [
|
|
58
|
+
{
|
|
59
|
+
type: 'insert',
|
|
60
|
+
value: {
|
|
61
|
+
id: `${event.runId}:${next++}`,
|
|
62
|
+
runId: event.runId,
|
|
63
|
+
actor: event.actor ?? null,
|
|
64
|
+
description: event.description,
|
|
65
|
+
result: event.result,
|
|
66
|
+
timestamp: event.timestamp,
|
|
67
|
+
sceneId: event.scene !== undefined && event.teamIndex !== undefined
|
|
68
|
+
? sceneKey(event.runId, event.teamIndex, event.scene)
|
|
69
|
+
: null,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
return [];
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Attribute an event row to a scene. Prefers the stamped `sceneId` (precise,
|
|
80
|
+
* concurrency-safe); falls back to actor + time-window for rows from older
|
|
81
|
+
* producers: the scene of the same run whose `actors` include `actor` and
|
|
82
|
+
* whose `[startTime, endTime]` window contains `timestamp`. The window is
|
|
83
|
+
* open-ended while a scene is still running (`endTime === null`). Returns the
|
|
84
|
+
* matching scene's id, or `null` when nothing attributes (e.g. an actor-less
|
|
85
|
+
* inline assertion on an old run).
|
|
86
|
+
*/
|
|
87
|
+
export function attributeToScene(row, scenes) {
|
|
88
|
+
if (row.sceneId)
|
|
89
|
+
return row.sceneId;
|
|
90
|
+
if (!row.actor)
|
|
91
|
+
return null;
|
|
92
|
+
for (const scene of scenes) {
|
|
93
|
+
if (scene.runId !== row.runId)
|
|
94
|
+
continue;
|
|
95
|
+
if (!scene.actors.includes(row.actor))
|
|
96
|
+
continue;
|
|
97
|
+
if (row.timestamp < scene.startTime)
|
|
98
|
+
continue;
|
|
99
|
+
if (scene.endTime !== null && row.timestamp > scene.endTime)
|
|
100
|
+
continue;
|
|
101
|
+
return scene.id;
|
|
102
|
+
}
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
export function actionsProjection() {
|
|
106
|
+
let next = 0;
|
|
107
|
+
const open = new Map();
|
|
108
|
+
const openKey = (actor, action) => JSON.stringify([actor, action]);
|
|
109
|
+
return {
|
|
110
|
+
id: 'actions',
|
|
111
|
+
getKey: (row) => row.id,
|
|
112
|
+
project(event, get) {
|
|
113
|
+
if (event.type === 'action:start') {
|
|
114
|
+
const id = `${event.runId}:${next++}`;
|
|
115
|
+
open.set(openKey(event.actor, event.action), id);
|
|
116
|
+
const sceneId = event.scene !== undefined && event.teamIndex !== undefined
|
|
117
|
+
? sceneKey(event.runId, event.teamIndex, event.scene)
|
|
118
|
+
: null;
|
|
119
|
+
return [
|
|
120
|
+
{
|
|
121
|
+
type: 'insert',
|
|
122
|
+
value: {
|
|
123
|
+
id,
|
|
124
|
+
runId: event.runId,
|
|
125
|
+
actor: event.actor,
|
|
126
|
+
action: event.action,
|
|
127
|
+
target: event.target ?? null,
|
|
128
|
+
startTime: event.timestamp,
|
|
129
|
+
endTime: null,
|
|
130
|
+
duration: null,
|
|
131
|
+
error: null,
|
|
132
|
+
status: 'running',
|
|
133
|
+
sceneId,
|
|
134
|
+
timestamp: event.timestamp,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
];
|
|
138
|
+
}
|
|
139
|
+
if (event.type === 'action:end') {
|
|
140
|
+
const key = openKey(event.actor, event.action);
|
|
141
|
+
const id = open.get(key);
|
|
142
|
+
if (id === undefined)
|
|
143
|
+
return [];
|
|
144
|
+
open.delete(key);
|
|
145
|
+
const prev = get(id);
|
|
146
|
+
if (!prev)
|
|
147
|
+
return [];
|
|
148
|
+
return [
|
|
149
|
+
{
|
|
150
|
+
type: 'update',
|
|
151
|
+
value: {
|
|
152
|
+
...prev,
|
|
153
|
+
endTime: event.timestamp,
|
|
154
|
+
duration: event.duration,
|
|
155
|
+
error: event.error ?? null,
|
|
156
|
+
status: event.error ? 'error' : 'success',
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
];
|
|
160
|
+
}
|
|
161
|
+
return [];
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
export function runsProjection() {
|
|
166
|
+
return {
|
|
167
|
+
id: 'runs',
|
|
168
|
+
getKey: (row) => row.id,
|
|
169
|
+
project(event, get) {
|
|
170
|
+
switch (event.type) {
|
|
171
|
+
case 'run:start': {
|
|
172
|
+
return [
|
|
173
|
+
{
|
|
174
|
+
type: 'insert',
|
|
175
|
+
value: {
|
|
176
|
+
id: event.runId,
|
|
177
|
+
startTime: event.timestamp,
|
|
178
|
+
endTime: null,
|
|
179
|
+
duration: null,
|
|
180
|
+
status: 'running',
|
|
181
|
+
sceneCount: event.sceneCount,
|
|
182
|
+
completed: 0,
|
|
183
|
+
failed: 0,
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
];
|
|
187
|
+
}
|
|
188
|
+
case 'scene:end': {
|
|
189
|
+
const prev = get(event.runId);
|
|
190
|
+
if (!prev)
|
|
191
|
+
return [];
|
|
192
|
+
const passed = event.status === 'completed';
|
|
193
|
+
return [
|
|
194
|
+
{
|
|
195
|
+
type: 'update',
|
|
196
|
+
value: {
|
|
197
|
+
...prev,
|
|
198
|
+
completed: prev.completed + (passed ? 1 : 0),
|
|
199
|
+
failed: prev.failed + (passed ? 0 : 1),
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
];
|
|
203
|
+
}
|
|
204
|
+
case 'run:end': {
|
|
205
|
+
const prev = get(event.runId);
|
|
206
|
+
if (!prev)
|
|
207
|
+
return [];
|
|
208
|
+
return [
|
|
209
|
+
{
|
|
210
|
+
type: 'update',
|
|
211
|
+
value: {
|
|
212
|
+
...prev,
|
|
213
|
+
status: 'finished',
|
|
214
|
+
endTime: event.timestamp,
|
|
215
|
+
duration: event.duration,
|
|
216
|
+
completed: event.summary?.completed ?? prev.completed,
|
|
217
|
+
failed: event.summary?.failed ?? prev.failed,
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
];
|
|
221
|
+
}
|
|
222
|
+
default:
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
//# sourceMappingURL=projections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projections.js","sourceRoot":"","sources":["../../src/collections/projections.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Transport } from '../types.js';
|
|
2
|
+
import type { RunSource } from './types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Wrap a {@link Transport} as a shared {@link RunSource}: one
|
|
5
|
+
* `transport.subscribe` fanned out to many collections.
|
|
6
|
+
*
|
|
7
|
+
* Why a layer over the transport at all? Each `createDevTransport()`
|
|
8
|
+
* subscription opens its own `EventSource`; a per-collection subscription
|
|
9
|
+
* would mean N SSE/WebSocket connections for N tables of the same run. The
|
|
10
|
+
* source opens exactly one and multiplexes it, and buffers the current run
|
|
11
|
+
* so a collection created mid-run replays the events it missed — the same
|
|
12
|
+
* catch-up the transports give a fresh connection, but shared.
|
|
13
|
+
*
|
|
14
|
+
* The underlying transport subscription starts lazily on the first
|
|
15
|
+
* `subscribe` and stays open (it does not drop when the last collection
|
|
16
|
+
* detaches, since TanStack DB pauses sync when a collection has no active
|
|
17
|
+
* subscribers, and we want the run to keep accumulating across those gaps).
|
|
18
|
+
* Call {@link RunSource.close} to tear it down for good.
|
|
19
|
+
*
|
|
20
|
+
* The buffer holds **every** event of the connection — across runs — so a
|
|
21
|
+
* collection attaching mid-session replays the full history (the multi-run
|
|
22
|
+
* read model wants prior runs, not just the current one). `run:start` does
|
|
23
|
+
* not clear it; it just marks a new run partition downstream. Cap/eviction
|
|
24
|
+
* is a future concern; today a session's history is bounded.
|
|
25
|
+
*
|
|
26
|
+
* History/ordering/de-duplication are the transport's contract — both the
|
|
27
|
+
* dev (SSE) and cloud (WebSocket) adapters replay on connect and deliver
|
|
28
|
+
* events in order. The source does not re-derive `seq`; it consumes whatever
|
|
29
|
+
* the subscription delivers.
|
|
30
|
+
*/
|
|
31
|
+
export declare function createRunSource(transport: Transport): RunSource;
|
|
32
|
+
//# sourceMappingURL=source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../../src/collections/source.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAoB,SAAS,EAAE,MAAM,aAAa,CAAA;AAC9D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAK3C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,eAAe,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,CAwD/D"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrap a {@link Transport} as a shared {@link RunSource}: one
|
|
3
|
+
* `transport.subscribe` fanned out to many collections.
|
|
4
|
+
*
|
|
5
|
+
* Why a layer over the transport at all? Each `createDevTransport()`
|
|
6
|
+
* subscription opens its own `EventSource`; a per-collection subscription
|
|
7
|
+
* would mean N SSE/WebSocket connections for N tables of the same run. The
|
|
8
|
+
* source opens exactly one and multiplexes it, and buffers the current run
|
|
9
|
+
* so a collection created mid-run replays the events it missed — the same
|
|
10
|
+
* catch-up the transports give a fresh connection, but shared.
|
|
11
|
+
*
|
|
12
|
+
* The underlying transport subscription starts lazily on the first
|
|
13
|
+
* `subscribe` and stays open (it does not drop when the last collection
|
|
14
|
+
* detaches, since TanStack DB pauses sync when a collection has no active
|
|
15
|
+
* subscribers, and we want the run to keep accumulating across those gaps).
|
|
16
|
+
* Call {@link RunSource.close} to tear it down for good.
|
|
17
|
+
*
|
|
18
|
+
* The buffer holds **every** event of the connection — across runs — so a
|
|
19
|
+
* collection attaching mid-session replays the full history (the multi-run
|
|
20
|
+
* read model wants prior runs, not just the current one). `run:start` does
|
|
21
|
+
* not clear it; it just marks a new run partition downstream. Cap/eviction
|
|
22
|
+
* is a future concern; today a session's history is bounded.
|
|
23
|
+
*
|
|
24
|
+
* History/ordering/de-duplication are the transport's contract — both the
|
|
25
|
+
* dev (SSE) and cloud (WebSocket) adapters replay on connect and deliver
|
|
26
|
+
* events in order. The source does not re-derive `seq`; it consumes whatever
|
|
27
|
+
* the subscription delivers.
|
|
28
|
+
*/
|
|
29
|
+
export function createRunSource(transport) {
|
|
30
|
+
const eventListeners = new Set();
|
|
31
|
+
const statusListeners = new Set();
|
|
32
|
+
// Buffered events for the whole session, replayed to late subscribers.
|
|
33
|
+
let buffer = [];
|
|
34
|
+
let status = 'connecting';
|
|
35
|
+
let unsubscribeTransport = null;
|
|
36
|
+
let closed = false;
|
|
37
|
+
function onTransportEvent(event) {
|
|
38
|
+
buffer.push(event);
|
|
39
|
+
for (const listener of [...eventListeners])
|
|
40
|
+
listener(event);
|
|
41
|
+
}
|
|
42
|
+
function onTransportStatus(next) {
|
|
43
|
+
status = next;
|
|
44
|
+
for (const listener of [...statusListeners])
|
|
45
|
+
listener(next);
|
|
46
|
+
}
|
|
47
|
+
function ensureStarted() {
|
|
48
|
+
if (unsubscribeTransport || closed)
|
|
49
|
+
return;
|
|
50
|
+
unsubscribeTransport = transport.subscribe(onTransportEvent, onTransportStatus);
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
get status() {
|
|
54
|
+
return status;
|
|
55
|
+
},
|
|
56
|
+
subscribe(onEvent, onStatus) {
|
|
57
|
+
if (closed)
|
|
58
|
+
return () => { };
|
|
59
|
+
eventListeners.add(onEvent);
|
|
60
|
+
if (onStatus)
|
|
61
|
+
statusListeners.add(onStatus);
|
|
62
|
+
// Replay the current run so a collection attaching mid-run catches up,
|
|
63
|
+
// then report the current liveness.
|
|
64
|
+
for (const event of buffer)
|
|
65
|
+
onEvent(event);
|
|
66
|
+
onStatus?.(status);
|
|
67
|
+
ensureStarted();
|
|
68
|
+
return () => {
|
|
69
|
+
eventListeners.delete(onEvent);
|
|
70
|
+
if (onStatus)
|
|
71
|
+
statusListeners.delete(onStatus);
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
close() {
|
|
75
|
+
closed = true;
|
|
76
|
+
unsubscribeTransport?.();
|
|
77
|
+
unsubscribeTransport = null;
|
|
78
|
+
eventListeners.clear();
|
|
79
|
+
statusListeners.clear();
|
|
80
|
+
buffer = [];
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source.js","sourceRoot":"","sources":["../../src/collections/source.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,eAAe,CAAC,SAAoB;IAClD,MAAM,cAAc,GAAG,IAAI,GAAG,EAAiB,CAAA;IAC/C,MAAM,eAAe,GAAG,IAAI,GAAG,EAAkB,CAAA;IACjD,uEAAuE;IACvE,IAAI,MAAM,GAAe,EAAE,CAAA;IAC3B,IAAI,MAAM,GAAqB,YAAY,CAAA;IAC3C,IAAI,oBAAoB,GAAwB,IAAI,CAAA;IACpD,IAAI,MAAM,GAAG,KAAK,CAAA;IAElB,SAAS,gBAAgB,CAAC,KAAe;QACvC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;QAClB,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,cAAc,CAAC;YAAE,QAAQ,CAAC,KAAK,CAAC,CAAA;IAC7D,CAAC;IAED,SAAS,iBAAiB,CAAC,IAAsB;QAC/C,MAAM,GAAG,IAAI,CAAA;QACb,KAAK,MAAM,QAAQ,IAAI,CAAC,GAAG,eAAe,CAAC;YAAE,QAAQ,CAAC,IAAI,CAAC,CAAA;IAC7D,CAAC;IAED,SAAS,aAAa;QACpB,IAAI,oBAAoB,IAAI,MAAM;YAAE,OAAM;QAC1C,oBAAoB,GAAG,SAAS,CAAC,SAAS,CAAC,gBAAgB,EAAE,iBAAiB,CAAC,CAAA;IACjF,CAAC;IAED,OAAO;QACL,IAAI,MAAM;YACR,OAAO,MAAM,CAAA;QACf,CAAC;QAED,SAAS,CAAC,OAAO,EAAE,QAAQ;YACzB,IAAI,MAAM;gBAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;YAC3B,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;YAC3B,IAAI,QAAQ;gBAAE,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;YAE3C,uEAAuE;YACvE,oCAAoC;YACpC,KAAK,MAAM,KAAK,IAAI,MAAM;gBAAE,OAAO,CAAC,KAAK,CAAC,CAAA;YAC1C,QAAQ,EAAE,CAAC,MAAM,CAAC,CAAA;YAElB,aAAa,EAAE,CAAA;YAEf,OAAO,GAAG,EAAE;gBACV,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,CAAA;gBAC9B,IAAI,QAAQ;oBAAE,eAAe,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;YAChD,CAAC,CAAA;QACH,CAAC;QAED,KAAK;YACH,MAAM,GAAG,IAAI,CAAA;YACb,oBAAoB,EAAE,EAAE,CAAA;YACxB,oBAAoB,GAAG,IAAI,CAAA;YAC3B,cAAc,CAAC,KAAK,EAAE,CAAA;YACtB,eAAe,CAAC,KAAK,EAAE,CAAA;YACvB,MAAM,GAAG,EAAE,CAAA;QACb,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { RunEvent } from '@scenetest/protocol';
|
|
2
|
+
import type { ConnectionStatus, Transport } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* One row operation a projection emits for a single event. The collection
|
|
5
|
+
* options factory translates these into TanStack DB sync writes:
|
|
6
|
+
*
|
|
7
|
+
* - `insert` / `update` → `write({ type, value })` (key derived via `getKey`)
|
|
8
|
+
* - `delete` → `write({ type: 'delete', key })`
|
|
9
|
+
* - `reset` → `truncate()` (drop every row)
|
|
10
|
+
*
|
|
11
|
+
* The multi-run projections don't emit `reset` (a new `run:start` opens a new
|
|
12
|
+
* partition rather than wiping), but it stays in the vocabulary for projections
|
|
13
|
+
* that need a hard clear — e.g. switching the PR/context a collection tracks.
|
|
14
|
+
*
|
|
15
|
+
* Projections never touch TanStack DB directly; they speak this small,
|
|
16
|
+
* pure vocabulary, which keeps them trivially testable.
|
|
17
|
+
*/
|
|
18
|
+
export type RowOp<T extends object, TKey extends string | number> = {
|
|
19
|
+
type: 'insert' | 'update';
|
|
20
|
+
value: T;
|
|
21
|
+
} | {
|
|
22
|
+
type: 'delete';
|
|
23
|
+
key: TKey;
|
|
24
|
+
} | {
|
|
25
|
+
type: 'reset';
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* A projection folds the read-only `RunEvent` stream into the rows of one
|
|
29
|
+
* collection — the "sync reducer" the proposal describes. It is the only
|
|
30
|
+
* writer for its table, and it is pure given the event and a reader for the
|
|
31
|
+
* rows it has already produced (`get`), so an `update` can be computed from
|
|
32
|
+
* the prior value.
|
|
33
|
+
*
|
|
34
|
+
* A projection may carry internal counter state (e.g. to mint ids for
|
|
35
|
+
* append-only rows), so create one per collection via a factory rather than
|
|
36
|
+
* sharing a singleton.
|
|
37
|
+
*/
|
|
38
|
+
export interface RunProjection<T extends object, TKey extends string | number = string> {
|
|
39
|
+
/** Stable identifier, used as the collection's default `id`. */
|
|
40
|
+
readonly id: string;
|
|
41
|
+
/** Key extractor for rows of this collection. */
|
|
42
|
+
getKey(row: T): TKey;
|
|
43
|
+
/**
|
|
44
|
+
* Fold one event into row operations. `get` reads a row this projection
|
|
45
|
+
* has already emitted (the synced state), so updates merge onto it.
|
|
46
|
+
* Returns an empty array for events this collection does not care about.
|
|
47
|
+
*/
|
|
48
|
+
project(event: RunEvent, get: (key: TKey) => T | undefined): Array<RowOp<T, TKey>>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* A shared, read-only handle on a single run's event stream. It wraps one
|
|
52
|
+
* `Transport.subscribe` and fans it out to any number of collections, so
|
|
53
|
+
* several collections can "attach to the same websocket, different table"
|
|
54
|
+
* off one underlying connection.
|
|
55
|
+
*
|
|
56
|
+
* Late subscribers are replayed the current run's buffered events on
|
|
57
|
+
* `subscribe`, so a collection created after the run started still catches
|
|
58
|
+
* up. The buffer resets on `run:start`.
|
|
59
|
+
*/
|
|
60
|
+
export interface RunSource {
|
|
61
|
+
/**
|
|
62
|
+
* Attach a consumer. `onEvent` is first called synchronously for every
|
|
63
|
+
* buffered event of the current run, then for each live event.
|
|
64
|
+
* `onStatus`, if given, receives connection-liveness changes (and the
|
|
65
|
+
* current status immediately). Returns an unsubscribe function.
|
|
66
|
+
*/
|
|
67
|
+
subscribe(onEvent: (event: RunEvent) => void, onStatus?: (status: ConnectionStatus) => void): () => void;
|
|
68
|
+
/** Latest connection liveness reported by the transport. */
|
|
69
|
+
readonly status: ConnectionStatus;
|
|
70
|
+
/** Tear down the underlying transport subscription and drop the buffer. */
|
|
71
|
+
close(): void;
|
|
72
|
+
}
|
|
73
|
+
/** Options for {@link runCollectionOptions}. */
|
|
74
|
+
export interface RunCollectionOptions<T extends object, TKey extends string | number> {
|
|
75
|
+
/** The shared stream this collection reads from. */
|
|
76
|
+
source: RunSource;
|
|
77
|
+
/** The projection that folds events into this collection's rows. */
|
|
78
|
+
projection: RunProjection<T, TKey>;
|
|
79
|
+
/**
|
|
80
|
+
* Whether to begin syncing on collection creation rather than on first
|
|
81
|
+
* query. Defaults to `true` so the read model captures the run from the
|
|
82
|
+
* moment it is created, not from the first `useLiveQuery`.
|
|
83
|
+
*/
|
|
84
|
+
startSync?: boolean;
|
|
85
|
+
}
|
|
86
|
+
export type { Transport };
|
|
87
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/collections/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE9D;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,KAAK,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM,GAAG,MAAM,IAC5D;IAAE,IAAI,EAAE,QAAQ,GAAG,QAAQ,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,GAAG,EAAE,IAAI,CAAA;CAAE,GAC7B;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAA;AAErB;;;;;;;;;;GAUG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM,GAAG,MAAM,GAAG,MAAM;IACpF,gEAAgE;IAChE,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,iDAAiD;IACjD,MAAM,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAA;IACpB;;;;OAIG;IACH,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,CAAC,GAAG,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAA;CACnF;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,SAAS;IACxB;;;;;OAKG;IACH,SAAS,CACP,OAAO,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,EAClC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,GAC5C,MAAM,IAAI,CAAA;IACb,4DAA4D;IAC5D,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAA;IACjC,2EAA2E;IAC3E,KAAK,IAAI,IAAI,CAAA;CACd;AAED,gDAAgD;AAChD,MAAM,WAAW,oBAAoB,CAAC,CAAC,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM,GAAG,MAAM;IAClF,oDAAoD;IACpD,MAAM,EAAE,SAAS,CAAA;IACjB,oEAAoE;IACpE,UAAU,EAAE,aAAa,CAAC,CAAC,EAAE,IAAI,CAAC,CAAA;IAClC;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED,YAAY,EAAE,SAAS,EAAE,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/collections/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { DashboardTheme, Transport } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The Dashboard app — Home / Runner / Waterfall views over one read-only
|
|
4
|
+
* `@tanstack/db` read model, routed on `location.pathname`. A plain Preact
|
|
5
|
+
* component rendering into the light DOM under a single `.scenetest-dashboard`
|
|
6
|
+
* root class; the shipped stylesheet scopes everything to it. Dev and cloud
|
|
7
|
+
* render the same component — only the injected `transport` differs.
|
|
8
|
+
*
|
|
9
|
+
* /__scenetest → Home
|
|
10
|
+
* /__scenetest/runner → Runner (filterable scene log)
|
|
11
|
+
* /__scenetest/waterfall → Waterfall (live timeline)
|
|
12
|
+
*
|
|
13
|
+
* The collections are the single store: the component builds them from the run
|
|
14
|
+
* stream and every view reads from them (`selectWaterfall`, `selectSnapshot`).
|
|
15
|
+
*/
|
|
16
|
+
export declare function Dashboard({ transport, theme, }: {
|
|
17
|
+
transport: Transport;
|
|
18
|
+
theme?: DashboardTheme;
|
|
19
|
+
}): import("preact").JSX.Element;
|
|
20
|
+
//# sourceMappingURL=dashboard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../src/dashboard.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAoB,cAAc,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAiE7E;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,EACxB,SAAS,EACT,KAAK,GACN,EAAE;IACD,SAAS,EAAE,SAAS,CAAA;IACpB,KAAK,CAAC,EAAE,cAAc,CAAA;CACvB,gCAyDA"}
|