@ventually/ui 0.0.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 +51 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +163 -0
- package/dist/context.d.ts +20 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +76 -0
- package/dist/context.test.d.ts +2 -0
- package/dist/context.test.d.ts.map +1 -0
- package/dist/context.test.js +26 -0
- package/dist/dashboard.d.ts +3 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +273 -0
- package/dist/index.cjs +15 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/runtime.d.ts +47 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +303 -0
- package/dist/shared-types.d.ts +71 -0
- package/dist/shared-types.d.ts.map +1 -0
- package/dist/shared-types.js +1 -0
- package/dist/standalone/assets/index-BM0MHT5H.js +18 -0
- package/dist/standalone/assets/index-CvxuexK-.css +1 -0
- package/dist/standalone/index.html +13 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +59 -0
- package/src/cli.ts +215 -0
- package/src/context.test.tsx +47 -0
- package/src/context.tsx +130 -0
- package/src/dashboard.tsx +835 -0
- package/src/index.css +1 -0
- package/src/index.ts +11 -0
- package/src/runtime.ts +414 -0
- package/src/shared-types.ts +67 -0
- package/src/standalone/App.tsx +31 -0
- package/src/standalone/index.html +12 -0
- package/src/standalone/main.tsx +10 -0
- package/src/types.ts +9 -0
package/src/index.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "tailwindcss";
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { EventuallyUIProvider, useEventuallyUI } from "./context.js";
|
|
2
|
+
export { EventuallyDashboard } from "./dashboard.js";
|
|
3
|
+
export type {
|
|
4
|
+
EventuallyUIAction,
|
|
5
|
+
EventuallyUIEvent,
|
|
6
|
+
EventuallyUIJob,
|
|
7
|
+
EventuallyUIJobState,
|
|
8
|
+
EventuallyUIQueueSnapshot,
|
|
9
|
+
EventuallyUISnapshot,
|
|
10
|
+
EventuallyUIWorker,
|
|
11
|
+
} from "./shared-types.js";
|
package/src/runtime.ts
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FlowProducer,
|
|
3
|
+
Queue,
|
|
4
|
+
Scheduler,
|
|
5
|
+
UnrecoverableError,
|
|
6
|
+
Worker,
|
|
7
|
+
} from "@ventually/core";
|
|
8
|
+
import { MemoryAdapter, type MemoryQueueSnapshot } from "@ventually/memory";
|
|
9
|
+
|
|
10
|
+
import type { EventuallyUISnapshot } from "./types.js";
|
|
11
|
+
|
|
12
|
+
type EmailJob = {
|
|
13
|
+
to: string;
|
|
14
|
+
subject: string;
|
|
15
|
+
html: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
type EmailResult = {
|
|
19
|
+
messageId: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
type ImportJob = {
|
|
23
|
+
value: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type EventEntry = EventuallyUISnapshot["events"][number];
|
|
27
|
+
|
|
28
|
+
const QUEUE_CONFIG = {
|
|
29
|
+
email: {
|
|
30
|
+
description: "Transactional email delivery with retries and recurring heartbeats.",
|
|
31
|
+
concurrency: 3,
|
|
32
|
+
pollIntervalMs: 20,
|
|
33
|
+
attempts: 3,
|
|
34
|
+
removeOnCompleteCount: 30,
|
|
35
|
+
removeOnFailCount: 30,
|
|
36
|
+
recurringEnabled: true,
|
|
37
|
+
},
|
|
38
|
+
fetcher: {
|
|
39
|
+
description: "Fetches raw document content before downstream processing.",
|
|
40
|
+
concurrency: 1,
|
|
41
|
+
pollIntervalMs: 20,
|
|
42
|
+
attempts: 1,
|
|
43
|
+
removeOnCompleteCount: 0,
|
|
44
|
+
removeOnFailCount: 0,
|
|
45
|
+
recurringEnabled: false,
|
|
46
|
+
},
|
|
47
|
+
chunker: {
|
|
48
|
+
description: "Splits fetched content into smaller processing chunks.",
|
|
49
|
+
concurrency: 1,
|
|
50
|
+
pollIntervalMs: 20,
|
|
51
|
+
attempts: 1,
|
|
52
|
+
removeOnCompleteCount: 0,
|
|
53
|
+
removeOnFailCount: 0,
|
|
54
|
+
recurringEnabled: false,
|
|
55
|
+
},
|
|
56
|
+
embeddings: {
|
|
57
|
+
description: "Generates embeddings for prepared chunks.",
|
|
58
|
+
concurrency: 1,
|
|
59
|
+
pollIntervalMs: 20,
|
|
60
|
+
attempts: 1,
|
|
61
|
+
removeOnCompleteCount: 0,
|
|
62
|
+
removeOnFailCount: 0,
|
|
63
|
+
recurringEnabled: false,
|
|
64
|
+
},
|
|
65
|
+
vectorstore: {
|
|
66
|
+
description: "Persists vectors into the final storage layer.",
|
|
67
|
+
concurrency: 1,
|
|
68
|
+
pollIntervalMs: 20,
|
|
69
|
+
attempts: 1,
|
|
70
|
+
removeOnCompleteCount: 0,
|
|
71
|
+
removeOnFailCount: 0,
|
|
72
|
+
recurringEnabled: false,
|
|
73
|
+
},
|
|
74
|
+
} satisfies Record<string, EventuallyUISnapshot["queues"][number]["config"]>;
|
|
75
|
+
|
|
76
|
+
export class EventuallyUIRuntime {
|
|
77
|
+
private offsetMs = 0;
|
|
78
|
+
readonly adapter = new MemoryAdapter();
|
|
79
|
+
readonly events: EventEntry[] = [];
|
|
80
|
+
readonly workers = new Map<
|
|
81
|
+
string,
|
|
82
|
+
Worker<unknown, unknown, unknown> & { __paused?: boolean }
|
|
83
|
+
>();
|
|
84
|
+
readonly scheduleHandles = new Map<string, { cancel(): void }>();
|
|
85
|
+
readonly emailQueue = new Queue<EmailJob, EmailResult>("email", {
|
|
86
|
+
adapter: this.adapter,
|
|
87
|
+
now: () => this.now(),
|
|
88
|
+
defaultJobOptions: {
|
|
89
|
+
attempts: 3,
|
|
90
|
+
removeOnComplete: { count: 30 },
|
|
91
|
+
removeOnFail: { count: 30 },
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
readonly scheduler = new Scheduler<EmailJob, EmailResult>("email", {
|
|
95
|
+
adapter: this.adapter,
|
|
96
|
+
now: () => this.now(),
|
|
97
|
+
defaultJobOptions: {
|
|
98
|
+
removeOnComplete: { count: 30 },
|
|
99
|
+
removeOnFail: { count: 30 },
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
readonly flowProducer = new FlowProducer({
|
|
103
|
+
adapter: this.adapter,
|
|
104
|
+
now: () => this.now(),
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
constructor() {
|
|
108
|
+
this.registerWorker(
|
|
109
|
+
"email",
|
|
110
|
+
new Worker<EmailJob, EmailResult>(
|
|
111
|
+
"email",
|
|
112
|
+
async (job) => {
|
|
113
|
+
await job.updateProgress(25);
|
|
114
|
+
await this.sleep(50);
|
|
115
|
+
if (job.data.subject.toLowerCase().includes("fail")) {
|
|
116
|
+
throw new UnrecoverableError("Simulated delivery failure");
|
|
117
|
+
}
|
|
118
|
+
await job.updateProgress(100);
|
|
119
|
+
return {
|
|
120
|
+
messageId: `msg_${job.id.slice(0, 8)}`,
|
|
121
|
+
};
|
|
122
|
+
},
|
|
123
|
+
{ adapter: this.adapter, concurrency: 3, pollInterval: 20, now: () => this.now() },
|
|
124
|
+
),
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
this.registerWorker(
|
|
128
|
+
"fetcher",
|
|
129
|
+
new Worker<ImportJob, { body: string }>(
|
|
130
|
+
"fetcher",
|
|
131
|
+
async (job) => {
|
|
132
|
+
await this.sleep(30);
|
|
133
|
+
return { body: `Fetched: ${job.data.value}` };
|
|
134
|
+
},
|
|
135
|
+
{ adapter: this.adapter, pollInterval: 20, now: () => this.now() },
|
|
136
|
+
),
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
this.registerWorker(
|
|
140
|
+
"chunker",
|
|
141
|
+
new Worker<{ body: string }, { chunks: string[] }>(
|
|
142
|
+
"chunker",
|
|
143
|
+
async (job) => {
|
|
144
|
+
await this.sleep(30);
|
|
145
|
+
return { chunks: job.data.body.split(/\s+/).filter(Boolean) };
|
|
146
|
+
},
|
|
147
|
+
{ adapter: this.adapter, pollInterval: 20, now: () => this.now() },
|
|
148
|
+
),
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
this.registerWorker(
|
|
152
|
+
"embeddings",
|
|
153
|
+
new Worker<{ chunks: string[] }, { vectors: number }>(
|
|
154
|
+
"embeddings",
|
|
155
|
+
async (job) => {
|
|
156
|
+
await this.sleep(40);
|
|
157
|
+
return { vectors: job.data.chunks.length || 4 };
|
|
158
|
+
},
|
|
159
|
+
{ adapter: this.adapter, pollInterval: 20, now: () => this.now() },
|
|
160
|
+
),
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
this.registerWorker(
|
|
164
|
+
"vectorstore",
|
|
165
|
+
new Worker<{ vectors: number }, { upserted: number }>(
|
|
166
|
+
"vectorstore",
|
|
167
|
+
async (job) => {
|
|
168
|
+
await this.sleep(40);
|
|
169
|
+
return { upserted: job.data.vectors || 4 };
|
|
170
|
+
},
|
|
171
|
+
{ adapter: this.adapter, pollInterval: 20, now: () => this.now() },
|
|
172
|
+
),
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
start(): void {
|
|
177
|
+
for (const worker of this.workers.values()) {
|
|
178
|
+
void worker.run();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
snapshot(): EventuallyUISnapshot {
|
|
183
|
+
const queues = this.adapter.getAllQueuesSnapshot().map((queue) =>
|
|
184
|
+
this.toQueueSnapshot(queue),
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
now: this.now(),
|
|
189
|
+
queues,
|
|
190
|
+
events: this.events.slice(-80).reverse(),
|
|
191
|
+
workers: [...this.workers.entries()].map(([queue, worker]) => ({
|
|
192
|
+
queue,
|
|
193
|
+
paused: worker.__paused ?? false,
|
|
194
|
+
})),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async enqueueEmail(kind: "welcome" | "digest" | "failure"): Promise<void> {
|
|
199
|
+
const subjectMap = {
|
|
200
|
+
welcome: "Welcome aboard",
|
|
201
|
+
digest: "Daily digest",
|
|
202
|
+
failure: "Fail this message",
|
|
203
|
+
} as const;
|
|
204
|
+
|
|
205
|
+
await this.emailQueue.add(
|
|
206
|
+
kind,
|
|
207
|
+
{
|
|
208
|
+
to: "alice@example.com",
|
|
209
|
+
subject: subjectMap[kind],
|
|
210
|
+
html: `<p>${subjectMap[kind]}</p>`,
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
priority: kind === "welcome" ? 10 : 3,
|
|
214
|
+
delay: kind === "digest" ? "5s" : 0,
|
|
215
|
+
},
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async enqueueFlow(): Promise<void> {
|
|
220
|
+
await this.flowProducer.add({
|
|
221
|
+
name: "upsert-vectors",
|
|
222
|
+
queueName: "vectorstore",
|
|
223
|
+
data: { vectors: 0 },
|
|
224
|
+
children: [
|
|
225
|
+
{
|
|
226
|
+
name: "embed-chunks",
|
|
227
|
+
queueName: "embeddings",
|
|
228
|
+
data: { chunks: [] },
|
|
229
|
+
children: [
|
|
230
|
+
{
|
|
231
|
+
name: "chunk-document",
|
|
232
|
+
queueName: "chunker",
|
|
233
|
+
data: { body: "" },
|
|
234
|
+
children: [
|
|
235
|
+
{
|
|
236
|
+
name: "fetch-document",
|
|
237
|
+
queueName: "fetcher",
|
|
238
|
+
data: { value: "https://example.com/doc.pdf" },
|
|
239
|
+
},
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
],
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
createRecurringEmail(): string {
|
|
249
|
+
const handle = this.scheduler.every(
|
|
250
|
+
"heartbeat",
|
|
251
|
+
{
|
|
252
|
+
to: "ops@example.com",
|
|
253
|
+
subject: "Heartbeat",
|
|
254
|
+
html: "<p>System pulse</p>",
|
|
255
|
+
},
|
|
256
|
+
"15s",
|
|
257
|
+
{ jitter: "2s" },
|
|
258
|
+
);
|
|
259
|
+
this.scheduleHandles.set(handle.id, handle);
|
|
260
|
+
this.pushEvent({
|
|
261
|
+
queue: "email",
|
|
262
|
+
type: "scheduled",
|
|
263
|
+
jobId: handle.id,
|
|
264
|
+
name: "heartbeat",
|
|
265
|
+
detail: "Recurring email schedule started",
|
|
266
|
+
});
|
|
267
|
+
return handle.id;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
cancelSchedule(id: string): void {
|
|
271
|
+
this.scheduleHandles.get(id)?.cancel();
|
|
272
|
+
this.scheduleHandles.delete(id);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
pauseWorker(queue: string): void {
|
|
276
|
+
const worker = this.workers.get(queue);
|
|
277
|
+
if (!worker) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
worker.pause();
|
|
281
|
+
worker.__paused = true;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
resumeWorker(queue: string): void {
|
|
285
|
+
const worker = this.workers.get(queue);
|
|
286
|
+
if (!worker) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
worker.resume();
|
|
290
|
+
worker.__paused = false;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
advanceTime(ms: number): void {
|
|
294
|
+
this.offsetMs += ms;
|
|
295
|
+
this.adapter.advanceTime(ms);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private toQueueSnapshot(queue: MemoryQueueSnapshot) {
|
|
299
|
+
const config =
|
|
300
|
+
QUEUE_CONFIG[queue.name as keyof typeof QUEUE_CONFIG] ?? {
|
|
301
|
+
description: "Queue configuration is not available.",
|
|
302
|
+
concurrency: 1,
|
|
303
|
+
pollIntervalMs: 20,
|
|
304
|
+
attempts: 1,
|
|
305
|
+
removeOnCompleteCount: 0,
|
|
306
|
+
removeOnFailCount: 0,
|
|
307
|
+
recurringEnabled: false,
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
return {
|
|
311
|
+
name: queue.name,
|
|
312
|
+
config,
|
|
313
|
+
counts: queue.counts,
|
|
314
|
+
jobs: queue.jobs.map((job) => ({
|
|
315
|
+
id: job.id,
|
|
316
|
+
name: job.name,
|
|
317
|
+
state: job.state,
|
|
318
|
+
attempt: job.attempt,
|
|
319
|
+
attempts: job.attempts,
|
|
320
|
+
createdAt: job.createdAt,
|
|
321
|
+
processedAt: job.processedAt,
|
|
322
|
+
finishedAt: job.finishedAt,
|
|
323
|
+
availableAt: job.availableAt,
|
|
324
|
+
failedReason: job.failedReason,
|
|
325
|
+
})),
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
private registerWorker<TData, TResult>(
|
|
330
|
+
queue: string,
|
|
331
|
+
worker: Worker<TData, TResult>,
|
|
332
|
+
): void {
|
|
333
|
+
worker.on("active", (job) => {
|
|
334
|
+
this.pushEvent({
|
|
335
|
+
queue,
|
|
336
|
+
type: "active",
|
|
337
|
+
jobId: job.id,
|
|
338
|
+
name: job.name,
|
|
339
|
+
detail: `attempt ${job.attempt}/${job.attempts}`,
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
worker.on("progress", (job, progress) => {
|
|
343
|
+
this.pushEvent({
|
|
344
|
+
queue,
|
|
345
|
+
type: "progress",
|
|
346
|
+
jobId: job.id,
|
|
347
|
+
name: job.name,
|
|
348
|
+
detail: JSON.stringify(progress),
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
worker.on("completed", (job, result) => {
|
|
352
|
+
this.pushEvent({
|
|
353
|
+
queue,
|
|
354
|
+
type: "completed",
|
|
355
|
+
jobId: job.id,
|
|
356
|
+
name: job.name,
|
|
357
|
+
detail: JSON.stringify(result),
|
|
358
|
+
});
|
|
359
|
+
});
|
|
360
|
+
worker.on("failed", (job, error) => {
|
|
361
|
+
this.pushEvent({
|
|
362
|
+
queue,
|
|
363
|
+
type: "failed",
|
|
364
|
+
jobId: job.id,
|
|
365
|
+
name: job.name,
|
|
366
|
+
detail: error.message,
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
worker.on("retrying", (job, error, nextRetryAt) => {
|
|
370
|
+
this.pushEvent({
|
|
371
|
+
queue,
|
|
372
|
+
type: "retrying",
|
|
373
|
+
jobId: job.id,
|
|
374
|
+
name: job.name,
|
|
375
|
+
detail: `${error.message} -> ${new Date(nextRetryAt).toLocaleTimeString()}`,
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
this.workers.set(
|
|
379
|
+
queue,
|
|
380
|
+
worker as Worker<unknown, unknown, unknown> & { __paused?: boolean },
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
private pushEvent(entry: Omit<EventEntry, "id" | "at">): void {
|
|
385
|
+
this.events.push({
|
|
386
|
+
...entry,
|
|
387
|
+
id: `${entry.queue}:${entry.jobId}:${this.now()}:${this.events.length}`,
|
|
388
|
+
at: this.now(),
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
if (this.events.length > 200) {
|
|
392
|
+
this.events.splice(0, this.events.length - 200);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
private now(): number {
|
|
397
|
+
return Date.now() + this.offsetMs;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
private async sleep(ms: number): Promise<void> {
|
|
401
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
declare global {
|
|
406
|
+
// eslint-disable-next-line no-var
|
|
407
|
+
var __eventuallyUIRuntime: EventuallyUIRuntime | undefined;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
export function getEventuallyUIRuntime(): EventuallyUIRuntime {
|
|
411
|
+
globalThis.__eventuallyUIRuntime ??= new EventuallyUIRuntime();
|
|
412
|
+
globalThis.__eventuallyUIRuntime.start();
|
|
413
|
+
return globalThis.__eventuallyUIRuntime;
|
|
414
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export type EventuallyUIJobState =
|
|
2
|
+
| "waiting"
|
|
3
|
+
| "blocked"
|
|
4
|
+
| "active"
|
|
5
|
+
| "delayed"
|
|
6
|
+
| "completed"
|
|
7
|
+
| "failed";
|
|
8
|
+
|
|
9
|
+
export interface EventuallyUIJob {
|
|
10
|
+
id: string;
|
|
11
|
+
name: string;
|
|
12
|
+
state: EventuallyUIJobState;
|
|
13
|
+
attempt: number;
|
|
14
|
+
attempts: number;
|
|
15
|
+
createdAt: number;
|
|
16
|
+
processedAt: number | null;
|
|
17
|
+
finishedAt: number | null;
|
|
18
|
+
availableAt: number;
|
|
19
|
+
failedReason: string | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface EventuallyUIQueueSnapshot {
|
|
23
|
+
name: string;
|
|
24
|
+
config: {
|
|
25
|
+
description: string;
|
|
26
|
+
concurrency: number;
|
|
27
|
+
pollIntervalMs: number;
|
|
28
|
+
attempts: number;
|
|
29
|
+
removeOnCompleteCount: number;
|
|
30
|
+
removeOnFailCount: number;
|
|
31
|
+
recurringEnabled: boolean;
|
|
32
|
+
};
|
|
33
|
+
counts: Record<EventuallyUIJobState, number>;
|
|
34
|
+
jobs: EventuallyUIJob[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface EventuallyUIEvent {
|
|
38
|
+
id: string;
|
|
39
|
+
queue: string;
|
|
40
|
+
type: string;
|
|
41
|
+
jobId: string;
|
|
42
|
+
name: string;
|
|
43
|
+
at: number;
|
|
44
|
+
detail: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface EventuallyUIWorker {
|
|
48
|
+
queue: string;
|
|
49
|
+
paused: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface EventuallyUISnapshot {
|
|
53
|
+
now: number;
|
|
54
|
+
queues: EventuallyUIQueueSnapshot[];
|
|
55
|
+
events: EventuallyUIEvent[];
|
|
56
|
+
workers: EventuallyUIWorker[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type EventuallyUIAction =
|
|
60
|
+
| { action: "enqueue-job"; queue: string; payload: unknown }
|
|
61
|
+
| { action: "enqueue-email"; kind: "welcome" | "digest" | "failure" }
|
|
62
|
+
| { action: "enqueue-flow" }
|
|
63
|
+
| { action: "schedule-heartbeat" }
|
|
64
|
+
| { action: "cancel-schedule"; id: string }
|
|
65
|
+
| { action: "pause-worker"; queue: string }
|
|
66
|
+
| { action: "resume-worker"; queue: string }
|
|
67
|
+
| { action: "advance-time"; ms: number };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { EventuallyDashboard, EventuallyUIProvider } from "../index.js";
|
|
2
|
+
import "../../dist/index.css"
|
|
3
|
+
|
|
4
|
+
declare global {
|
|
5
|
+
interface Window {
|
|
6
|
+
__EVENTUALLY_UI_ENDPOINT__?: string;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const DEFAULT_ENDPOINT = "/api/playground";
|
|
11
|
+
|
|
12
|
+
export function App() {
|
|
13
|
+
const endpoint =
|
|
14
|
+
typeof window !== "undefined"
|
|
15
|
+
? (window.__EVENTUALLY_UI_ENDPOINT__ ?? DEFAULT_ENDPOINT)
|
|
16
|
+
: DEFAULT_ENDPOINT;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<div
|
|
20
|
+
style={{
|
|
21
|
+
minHeight: "100vh",
|
|
22
|
+
background:
|
|
23
|
+
"radial-gradient(circle at top, rgba(245, 158, 11, 0.1), transparent 24%), linear-gradient(180deg, #090a0d 0%, #0b0b0f 100%)",
|
|
24
|
+
}}
|
|
25
|
+
>
|
|
26
|
+
<EventuallyUIProvider endpoint={endpoint}>
|
|
27
|
+
<EventuallyDashboard />
|
|
28
|
+
</EventuallyUIProvider>
|
|
29
|
+
</div>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>@ventually/ui</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body style="margin:0">
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="./main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|