@happyvertical/smrt-jobs 0.30.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/AGENTS.md +71 -0
- package/CLAUDE.md +1 -0
- package/LICENSE +7 -0
- package/README.md +151 -0
- package/dist/__smrt-register__.d.ts +2 -0
- package/dist/__smrt-register__.d.ts.map +1 -0
- package/dist/background-policy.d.ts +121 -0
- package/dist/background-policy.d.ts.map +1 -0
- package/dist/chunks/runner-DV8FBO0y.js +1642 -0
- package/dist/chunks/runner-DV8FBO0y.js.map +1 -0
- package/dist/chunks/worker-liveness-DOTjoIjr.js +65 -0
- package/dist/chunks/worker-liveness-DOTjoIjr.js.map +1 -0
- package/dist/error-redaction.d.ts +48 -0
- package/dist/error-redaction.d.ts.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +926 -0
- package/dist/index.js.map +1 -0
- package/dist/job-builder.d.ts +94 -0
- package/dist/job-builder.d.ts.map +1 -0
- package/dist/job-handle.d.ts +71 -0
- package/dist/job-handle.d.ts.map +1 -0
- package/dist/logger-extension.d.ts +58 -0
- package/dist/logger-extension.d.ts.map +1 -0
- package/dist/manifest.json +1327 -0
- package/dist/object-extension.d.ts +68 -0
- package/dist/object-extension.d.ts.map +1 -0
- package/dist/playground.d.ts +2 -0
- package/dist/playground.d.ts.map +1 -0
- package/dist/playground.js +179 -0
- package/dist/playground.js.map +1 -0
- package/dist/runner.d.ts +189 -0
- package/dist/runner.d.ts.map +1 -0
- package/dist/runner.js +15 -0
- package/dist/runner.js.map +1 -0
- package/dist/schedule-runner.d.ts +151 -0
- package/dist/schedule-runner.d.ts.map +1 -0
- package/dist/smrt-job-event.d.ts +54 -0
- package/dist/smrt-job-event.d.ts.map +1 -0
- package/dist/smrt-job.d.ts +215 -0
- package/dist/smrt-job.d.ts.map +1 -0
- package/dist/smrt-knowledge.json +508 -0
- package/dist/smrt-worker.d.ts +72 -0
- package/dist/smrt-worker.d.ts.map +1 -0
- package/dist/stale-recovery.d.ts +34 -0
- package/dist/stale-recovery.d.ts.map +1 -0
- package/dist/svelte/components/JobActions.svelte +103 -0
- package/dist/svelte/components/JobActions.svelte.d.ts +23 -0
- package/dist/svelte/components/JobActions.svelte.d.ts.map +1 -0
- package/dist/svelte/components/JobDashboard.svelte +199 -0
- package/dist/svelte/components/JobDashboard.svelte.d.ts +27 -0
- package/dist/svelte/components/JobDashboard.svelte.d.ts.map +1 -0
- package/dist/svelte/components/JobDetail.svelte +256 -0
- package/dist/svelte/components/JobDetail.svelte.d.ts +17 -0
- package/dist/svelte/components/JobDetail.svelte.d.ts.map +1 -0
- package/dist/svelte/components/JobList.svelte +360 -0
- package/dist/svelte/components/JobList.svelte.d.ts +28 -0
- package/dist/svelte/components/JobList.svelte.d.ts.map +1 -0
- package/dist/svelte/components/JobStats.svelte +242 -0
- package/dist/svelte/components/JobStats.svelte.d.ts +15 -0
- package/dist/svelte/components/JobStats.svelte.d.ts.map +1 -0
- package/dist/svelte/components/JobStatusBadge.svelte +23 -0
- package/dist/svelte/components/JobStatusBadge.svelte.d.ts +9 -0
- package/dist/svelte/components/JobStatusBadge.svelte.d.ts.map +1 -0
- package/dist/svelte/components/types.d.ts +9 -0
- package/dist/svelte/components/types.d.ts.map +1 -0
- package/dist/svelte/components/types.js +8 -0
- package/dist/svelte/i18n.d.ts +22 -0
- package/dist/svelte/i18n.d.ts.map +1 -0
- package/dist/svelte/i18n.js +22 -0
- package/dist/svelte/index.d.ts +25 -0
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +28 -0
- package/dist/svelte/playground.d.ts +329 -0
- package/dist/svelte/playground.d.ts.map +1 -0
- package/dist/svelte/playground.js +174 -0
- package/dist/svelte/types.d.ts +191 -0
- package/dist/svelte/types.d.ts.map +1 -0
- package/dist/svelte/types.js +87 -0
- package/dist/ui.d.ts +10 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +69 -0
- package/dist/ui.js.map +1 -0
- package/dist/worker-liveness-thread.d.ts +2 -0
- package/dist/worker-liveness-thread.d.ts.map +1 -0
- package/dist/worker-liveness-thread.js +66 -0
- package/dist/worker-liveness-thread.js.map +1 -0
- package/dist/worker-liveness-ticker.d.ts +30 -0
- package/dist/worker-liveness-ticker.d.ts.map +1 -0
- package/dist/worker-liveness.d.ts +71 -0
- package/dist/worker-liveness.d.ts.map +1 -0
- package/package.json +93 -0
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for background job UI components
|
|
3
|
+
*/
|
|
4
|
+
import type { Snippet } from 'svelte';
|
|
5
|
+
export type JobStatus = 'pending' | 'ready' | 'running' | 'completed' | 'failed' | 'cancelled';
|
|
6
|
+
export type JobPriority = 'low' | 'normal' | 'high' | 'critical';
|
|
7
|
+
export type TimeoutBehavior = 'fail' | 'kill' | 'warn';
|
|
8
|
+
/**
|
|
9
|
+
* Job data structure for display
|
|
10
|
+
*/
|
|
11
|
+
export interface JobData {
|
|
12
|
+
id: string;
|
|
13
|
+
queue: string;
|
|
14
|
+
objectType: string;
|
|
15
|
+
objectId: string | null;
|
|
16
|
+
method: string;
|
|
17
|
+
args: Record<string, unknown>;
|
|
18
|
+
status: JobStatus;
|
|
19
|
+
priority: number;
|
|
20
|
+
attempts: number;
|
|
21
|
+
maxAttempts: number;
|
|
22
|
+
timeout: number;
|
|
23
|
+
timeoutBehavior: TimeoutBehavior;
|
|
24
|
+
runAt: Date | string;
|
|
25
|
+
startedAt: Date | string | null;
|
|
26
|
+
completedAt: Date | string | null;
|
|
27
|
+
lastError: string | null;
|
|
28
|
+
resultPointer: string | null;
|
|
29
|
+
workerId: string | null;
|
|
30
|
+
createdAt: Date | string;
|
|
31
|
+
updatedAt: Date | string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Job statistics for dashboard
|
|
35
|
+
*/
|
|
36
|
+
export interface JobStats {
|
|
37
|
+
total: number;
|
|
38
|
+
pending: number;
|
|
39
|
+
ready: number;
|
|
40
|
+
running: number;
|
|
41
|
+
completed: number;
|
|
42
|
+
failed: number;
|
|
43
|
+
cancelled: number;
|
|
44
|
+
avgDuration: number | null;
|
|
45
|
+
successRate: number | null;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Queue statistics
|
|
49
|
+
*/
|
|
50
|
+
export interface QueueStats {
|
|
51
|
+
name: string;
|
|
52
|
+
pending: number;
|
|
53
|
+
running: number;
|
|
54
|
+
completed24h: number;
|
|
55
|
+
failed24h: number;
|
|
56
|
+
avgDuration: number | null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Filter options for job list
|
|
60
|
+
*/
|
|
61
|
+
export interface JobFilter {
|
|
62
|
+
status?: JobStatus | JobStatus[] | 'all';
|
|
63
|
+
queue?: string;
|
|
64
|
+
objectType?: string;
|
|
65
|
+
search?: string;
|
|
66
|
+
limit?: number;
|
|
67
|
+
offset?: number;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Sort options for job list
|
|
71
|
+
*/
|
|
72
|
+
export interface JobSort {
|
|
73
|
+
field: 'createdAt' | 'runAt' | 'priority' | 'status';
|
|
74
|
+
direction: 'asc' | 'desc';
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* JobList component props
|
|
78
|
+
*/
|
|
79
|
+
export interface JobListProps {
|
|
80
|
+
/** Jobs to display */
|
|
81
|
+
jobs: JobData[];
|
|
82
|
+
/** Loading state */
|
|
83
|
+
loading?: boolean;
|
|
84
|
+
/** Filter state */
|
|
85
|
+
filter?: JobFilter;
|
|
86
|
+
/** Sort state */
|
|
87
|
+
sort?: JobSort;
|
|
88
|
+
/** Selectable rows */
|
|
89
|
+
selectable?: boolean;
|
|
90
|
+
/** Selected job IDs */
|
|
91
|
+
selected?: Set<string>;
|
|
92
|
+
/** Callback when selection changes */
|
|
93
|
+
onSelectionChange?: (selected: Set<string>) => void;
|
|
94
|
+
/** Callback when job is clicked */
|
|
95
|
+
onJobClick?: (job: JobData) => void;
|
|
96
|
+
/** Callback when retry is clicked */
|
|
97
|
+
onRetry?: (job: JobData) => void;
|
|
98
|
+
/** Callback when cancel is clicked */
|
|
99
|
+
onCancel?: (job: JobData) => void;
|
|
100
|
+
/** Empty state snippet */
|
|
101
|
+
empty?: Snippet;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* JobDetail component props
|
|
105
|
+
*/
|
|
106
|
+
export interface JobDetailProps {
|
|
107
|
+
/** Job to display */
|
|
108
|
+
job: JobData;
|
|
109
|
+
/** Show result data */
|
|
110
|
+
showResult?: boolean;
|
|
111
|
+
/** Callback when retry is clicked */
|
|
112
|
+
onRetry?: (job: JobData) => void;
|
|
113
|
+
/** Callback when cancel is clicked */
|
|
114
|
+
onCancel?: (job: JobData) => void;
|
|
115
|
+
/** Callback when delete is clicked */
|
|
116
|
+
onDelete?: (job: JobData) => void;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* JobStats component props
|
|
120
|
+
*/
|
|
121
|
+
export interface JobStatsProps {
|
|
122
|
+
/** Statistics data */
|
|
123
|
+
stats: JobStats;
|
|
124
|
+
/** Queue breakdown */
|
|
125
|
+
queues?: QueueStats[];
|
|
126
|
+
/** Loading state */
|
|
127
|
+
loading?: boolean;
|
|
128
|
+
/** Auto-refresh interval (ms, 0 = disabled) */
|
|
129
|
+
refreshInterval?: number;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* JobActions component props
|
|
133
|
+
*/
|
|
134
|
+
export interface JobActionsProps {
|
|
135
|
+
/** Job to perform actions on */
|
|
136
|
+
job: JobData;
|
|
137
|
+
/** Show retry button */
|
|
138
|
+
showRetry?: boolean;
|
|
139
|
+
/** Show cancel button */
|
|
140
|
+
showCancel?: boolean;
|
|
141
|
+
/** Show delete button */
|
|
142
|
+
showDelete?: boolean;
|
|
143
|
+
/** Callback when retry is clicked */
|
|
144
|
+
onRetry?: (job: JobData) => void;
|
|
145
|
+
/** Callback when cancel is clicked */
|
|
146
|
+
onCancel?: (job: JobData) => void;
|
|
147
|
+
/** Callback when delete is clicked */
|
|
148
|
+
onDelete?: (job: JobData) => void;
|
|
149
|
+
/** Compact mode */
|
|
150
|
+
compact?: boolean;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* JobDashboard component props
|
|
154
|
+
*/
|
|
155
|
+
export interface JobDashboardProps {
|
|
156
|
+
/** Statistics data */
|
|
157
|
+
stats: JobStats;
|
|
158
|
+
/** Queue breakdown */
|
|
159
|
+
queues?: QueueStats[];
|
|
160
|
+
/** Recent jobs */
|
|
161
|
+
recentJobs?: JobData[];
|
|
162
|
+
/** Failed jobs */
|
|
163
|
+
failedJobs?: JobData[];
|
|
164
|
+
/** Loading state */
|
|
165
|
+
loading?: boolean;
|
|
166
|
+
/** Callback when job is clicked */
|
|
167
|
+
onJobClick?: (job: JobData) => void;
|
|
168
|
+
/** Callback when retry is clicked */
|
|
169
|
+
onRetry?: (job: JobData) => void;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Helper function to get status variant for Badge
|
|
173
|
+
*/
|
|
174
|
+
export declare function getStatusVariant(status: JobStatus): 'default' | 'primary' | 'success' | 'warning' | 'error' | 'info';
|
|
175
|
+
/**
|
|
176
|
+
* Helper function to format duration
|
|
177
|
+
*/
|
|
178
|
+
export declare function formatDuration(ms: number | null): string;
|
|
179
|
+
/**
|
|
180
|
+
* Helper function to format relative time
|
|
181
|
+
*/
|
|
182
|
+
export declare function formatRelativeTime(date: Date | string | null): string;
|
|
183
|
+
/**
|
|
184
|
+
* Helper function to get priority label
|
|
185
|
+
*/
|
|
186
|
+
export declare function getPriorityLabel(priority: number): string;
|
|
187
|
+
/**
|
|
188
|
+
* Helper function to get priority color class
|
|
189
|
+
*/
|
|
190
|
+
export declare function getPriorityClass(priority: number): string;
|
|
191
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/svelte/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper function to get status variant for Badge
|
|
3
|
+
*/
|
|
4
|
+
export function getStatusVariant(status) {
|
|
5
|
+
switch (status) {
|
|
6
|
+
case 'pending':
|
|
7
|
+
return 'default';
|
|
8
|
+
case 'ready':
|
|
9
|
+
return 'info';
|
|
10
|
+
case 'running':
|
|
11
|
+
return 'primary';
|
|
12
|
+
case 'completed':
|
|
13
|
+
return 'success';
|
|
14
|
+
case 'failed':
|
|
15
|
+
return 'error';
|
|
16
|
+
case 'cancelled':
|
|
17
|
+
return 'warning';
|
|
18
|
+
default:
|
|
19
|
+
return 'default';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Helper function to format duration
|
|
24
|
+
*/
|
|
25
|
+
export function formatDuration(ms) {
|
|
26
|
+
if (ms === null || ms === undefined)
|
|
27
|
+
return '-';
|
|
28
|
+
if (ms < 1000)
|
|
29
|
+
return `${ms}ms`;
|
|
30
|
+
if (ms < 60000)
|
|
31
|
+
return `${(ms / 1000).toFixed(1)}s`;
|
|
32
|
+
if (ms < 3600000)
|
|
33
|
+
return `${(ms / 60000).toFixed(1)}m`;
|
|
34
|
+
return `${(ms / 3600000).toFixed(1)}h`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Helper function to format relative time
|
|
38
|
+
*/
|
|
39
|
+
export function formatRelativeTime(date) {
|
|
40
|
+
if (!date)
|
|
41
|
+
return '-';
|
|
42
|
+
const d = typeof date === 'string' ? new Date(date) : date;
|
|
43
|
+
const now = new Date();
|
|
44
|
+
const diff = now.getTime() - d.getTime();
|
|
45
|
+
if (diff < 0) {
|
|
46
|
+
// Future
|
|
47
|
+
const absDiff = Math.abs(diff);
|
|
48
|
+
if (absDiff < 60000)
|
|
49
|
+
return 'in a moment';
|
|
50
|
+
if (absDiff < 3600000)
|
|
51
|
+
return `in ${Math.round(absDiff / 60000)}m`;
|
|
52
|
+
if (absDiff < 86400000)
|
|
53
|
+
return `in ${Math.round(absDiff / 3600000)}h`;
|
|
54
|
+
return `in ${Math.round(absDiff / 86400000)}d`;
|
|
55
|
+
}
|
|
56
|
+
if (diff < 60000)
|
|
57
|
+
return 'just now';
|
|
58
|
+
if (diff < 3600000)
|
|
59
|
+
return `${Math.round(diff / 60000)}m ago`;
|
|
60
|
+
if (diff < 86400000)
|
|
61
|
+
return `${Math.round(diff / 3600000)}h ago`;
|
|
62
|
+
return `${Math.round(diff / 86400000)}d ago`;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Helper function to get priority label
|
|
66
|
+
*/
|
|
67
|
+
export function getPriorityLabel(priority) {
|
|
68
|
+
if (priority >= 90)
|
|
69
|
+
return 'Critical';
|
|
70
|
+
if (priority >= 75)
|
|
71
|
+
return 'High';
|
|
72
|
+
if (priority >= 25)
|
|
73
|
+
return 'Normal';
|
|
74
|
+
return 'Low';
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Helper function to get priority color class
|
|
78
|
+
*/
|
|
79
|
+
export function getPriorityClass(priority) {
|
|
80
|
+
if (priority >= 90)
|
|
81
|
+
return 'priority-critical';
|
|
82
|
+
if (priority >= 75)
|
|
83
|
+
return 'priority-high';
|
|
84
|
+
if (priority >= 25)
|
|
85
|
+
return 'priority-normal';
|
|
86
|
+
return 'priority-low';
|
|
87
|
+
}
|
package/dist/ui.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { ModuleUISlot, SmrtModuleMeta } from '@happyvertical/smrt-types';
|
|
2
|
+
/**
|
|
3
|
+
* Jobs module UI slots
|
|
4
|
+
*/
|
|
5
|
+
export declare const JOBS_UI_SLOTS: Record<string, ModuleUISlot>;
|
|
6
|
+
/**
|
|
7
|
+
* Jobs module metadata
|
|
8
|
+
*/
|
|
9
|
+
export declare const JOBS_MODULE_META: SmrtModuleMeta;
|
|
10
|
+
//# sourceMappingURL=ui.d.ts.map
|
package/dist/ui.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAE9E;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAuDtD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,gBAAgB,EAAE,cAO9B,CAAC"}
|
package/dist/ui.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const JOBS_UI_SLOTS = {
|
|
2
|
+
"job-dashboard": {
|
|
3
|
+
id: "job-dashboard",
|
|
4
|
+
label: "Job Dashboard",
|
|
5
|
+
description: "Combined overview panel for background jobs",
|
|
6
|
+
icon: "briefcase",
|
|
7
|
+
category: "admin",
|
|
8
|
+
order: 1,
|
|
9
|
+
propsInterface: "JobDashboardProps"
|
|
10
|
+
},
|
|
11
|
+
"job-list": {
|
|
12
|
+
id: "job-list",
|
|
13
|
+
label: "Job List",
|
|
14
|
+
description: "Filterable, sortable list of background jobs",
|
|
15
|
+
icon: "list",
|
|
16
|
+
category: "list",
|
|
17
|
+
order: 2,
|
|
18
|
+
propsInterface: "JobListProps"
|
|
19
|
+
},
|
|
20
|
+
"job-detail": {
|
|
21
|
+
id: "job-detail",
|
|
22
|
+
label: "Job Detail",
|
|
23
|
+
description: "Detailed view of a single job",
|
|
24
|
+
icon: "file-text",
|
|
25
|
+
category: "detail",
|
|
26
|
+
order: 3,
|
|
27
|
+
propsInterface: "JobDetailProps"
|
|
28
|
+
},
|
|
29
|
+
"job-stats": {
|
|
30
|
+
id: "job-stats",
|
|
31
|
+
label: "Job Stats",
|
|
32
|
+
description: "Statistics dashboard for job queues",
|
|
33
|
+
icon: "bar-chart",
|
|
34
|
+
category: "display",
|
|
35
|
+
order: 4,
|
|
36
|
+
propsInterface: "JobStatsProps"
|
|
37
|
+
},
|
|
38
|
+
"job-actions": {
|
|
39
|
+
id: "job-actions",
|
|
40
|
+
label: "Job Actions",
|
|
41
|
+
description: "Action buttons for job operations",
|
|
42
|
+
icon: "more-horizontal",
|
|
43
|
+
category: "action",
|
|
44
|
+
order: 5,
|
|
45
|
+
propsInterface: "JobActionsProps"
|
|
46
|
+
},
|
|
47
|
+
"job-status-badge": {
|
|
48
|
+
id: "job-status-badge",
|
|
49
|
+
label: "Job Status Badge",
|
|
50
|
+
description: "Status indicator for job states",
|
|
51
|
+
icon: "tag",
|
|
52
|
+
category: "display",
|
|
53
|
+
order: 6,
|
|
54
|
+
propsInterface: "JobStatusBadgeProps"
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
const JOBS_MODULE_META = {
|
|
58
|
+
name: "@happyvertical/smrt-jobs",
|
|
59
|
+
displayName: "Jobs",
|
|
60
|
+
description: "Background job processing with persistence and scheduling",
|
|
61
|
+
uiSlots: JOBS_UI_SLOTS,
|
|
62
|
+
models: ["Job", "JobQueue"],
|
|
63
|
+
collections: ["JobCollection", "JobQueueCollection"]
|
|
64
|
+
};
|
|
65
|
+
export {
|
|
66
|
+
JOBS_MODULE_META,
|
|
67
|
+
JOBS_UI_SLOTS
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=ui.js.map
|
package/dist/ui.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.js","sources":["../src/ui.ts"],"sourcesContent":["/**\n * Jobs Module UI Slot Declarations\n *\n * This file defines the UI extension points for the jobs module.\n * UI components are implemented in the ./svelte subpath.\n */\n\nimport type { ModuleUISlot, SmrtModuleMeta } from '@happyvertical/smrt-types';\n\n/**\n * Jobs module UI slots\n */\nexport const JOBS_UI_SLOTS: Record<string, ModuleUISlot> = {\n 'job-dashboard': {\n id: 'job-dashboard',\n label: 'Job Dashboard',\n description: 'Combined overview panel for background jobs',\n icon: 'briefcase',\n category: 'admin',\n order: 1,\n propsInterface: 'JobDashboardProps',\n },\n 'job-list': {\n id: 'job-list',\n label: 'Job List',\n description: 'Filterable, sortable list of background jobs',\n icon: 'list',\n category: 'list',\n order: 2,\n propsInterface: 'JobListProps',\n },\n 'job-detail': {\n id: 'job-detail',\n label: 'Job Detail',\n description: 'Detailed view of a single job',\n icon: 'file-text',\n category: 'detail',\n order: 3,\n propsInterface: 'JobDetailProps',\n },\n 'job-stats': {\n id: 'job-stats',\n label: 'Job Stats',\n description: 'Statistics dashboard for job queues',\n icon: 'bar-chart',\n category: 'display',\n order: 4,\n propsInterface: 'JobStatsProps',\n },\n 'job-actions': {\n id: 'job-actions',\n label: 'Job Actions',\n description: 'Action buttons for job operations',\n icon: 'more-horizontal',\n category: 'action',\n order: 5,\n propsInterface: 'JobActionsProps',\n },\n 'job-status-badge': {\n id: 'job-status-badge',\n label: 'Job Status Badge',\n description: 'Status indicator for job states',\n icon: 'tag',\n category: 'display',\n order: 6,\n propsInterface: 'JobStatusBadgeProps',\n },\n};\n\n/**\n * Jobs module metadata\n */\nexport const JOBS_MODULE_META: SmrtModuleMeta = {\n name: '@happyvertical/smrt-jobs',\n displayName: 'Jobs',\n description: 'Background job processing with persistence and scheduling',\n uiSlots: JOBS_UI_SLOTS,\n models: ['Job', 'JobQueue'],\n collections: ['JobCollection', 'JobQueueCollection'],\n};\n"],"names":[],"mappings":"AAYO,MAAM,gBAA8C;AAAA,EACzD,iBAAiB;AAAA,IACf,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,MAAM;AAAA,IACN,UAAU;AAAA,IACV,OAAO;AAAA,IACP,gBAAgB;AAAA,EAAA;AAAA,EAElB,YAAY;AAAA,IACV,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,MAAM;AAAA,IACN,UAAU;AAAA,IACV,OAAO;AAAA,IACP,gBAAgB;AAAA,EAAA;AAAA,EAElB,cAAc;AAAA,IACZ,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,MAAM;AAAA,IACN,UAAU;AAAA,IACV,OAAO;AAAA,IACP,gBAAgB;AAAA,EAAA;AAAA,EAElB,aAAa;AAAA,IACX,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,MAAM;AAAA,IACN,UAAU;AAAA,IACV,OAAO;AAAA,IACP,gBAAgB;AAAA,EAAA;AAAA,EAElB,eAAe;AAAA,IACb,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,MAAM;AAAA,IACN,UAAU;AAAA,IACV,OAAO;AAAA,IACP,gBAAgB;AAAA,EAAA;AAAA,EAElB,oBAAoB;AAAA,IAClB,IAAI;AAAA,IACJ,OAAO;AAAA,IACP,aAAa;AAAA,IACb,MAAM;AAAA,IACN,UAAU;AAAA,IACV,OAAO;AAAA,IACP,gBAAgB;AAAA,EAAA;AAEpB;AAKO,MAAM,mBAAmC;AAAA,EAC9C,MAAM;AAAA,EACN,aAAa;AAAA,EACb,aAAa;AAAA,EACb,SAAS;AAAA,EACT,QAAQ,CAAC,OAAO,UAAU;AAAA,EAC1B,aAAa,CAAC,iBAAiB,oBAAoB;AACrD;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-liveness-thread.d.ts","sourceRoot":"","sources":["../src/worker-liveness-thread.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
2
|
+
import { getDatabase } from "@happyvertical/sql";
|
|
3
|
+
import { t as tuneSqliteForConcurrency } from "./chunks/worker-liveness-DOTjoIjr.js";
|
|
4
|
+
async function runLivenessTicker(options) {
|
|
5
|
+
const db = await getDatabase({
|
|
6
|
+
url: options.url,
|
|
7
|
+
type: options.type
|
|
8
|
+
});
|
|
9
|
+
const looksSqlite = (options.type ?? "").toLowerCase() === "sqlite" || !options.type && /^file:|\.(db|sqlite)\b|:memory:/i.test(options.url);
|
|
10
|
+
if (looksSqlite) await tuneSqliteForConcurrency(db);
|
|
11
|
+
const renew = async () => {
|
|
12
|
+
const now = /* @__PURE__ */ new Date();
|
|
13
|
+
const expiresAt = new Date(
|
|
14
|
+
now.getTime() + options.leaseTtlMs
|
|
15
|
+
).toISOString();
|
|
16
|
+
await db.query(
|
|
17
|
+
`UPDATE _smrt_workers
|
|
18
|
+
SET lease_expires_at = ?,
|
|
19
|
+
heartbeat_at = ?
|
|
20
|
+
WHERE worker_id = ?`,
|
|
21
|
+
expiresAt,
|
|
22
|
+
now.toISOString(),
|
|
23
|
+
options.workerKey
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
try {
|
|
27
|
+
await renew();
|
|
28
|
+
} catch (error) {
|
|
29
|
+
await db.close?.().catch(() => {
|
|
30
|
+
});
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
const timer = setInterval(() => {
|
|
34
|
+
renew().catch(() => {
|
|
35
|
+
});
|
|
36
|
+
}, options.leaseTickMs);
|
|
37
|
+
let stopped = false;
|
|
38
|
+
return {
|
|
39
|
+
stop: async () => {
|
|
40
|
+
if (stopped) return;
|
|
41
|
+
stopped = true;
|
|
42
|
+
clearInterval(timer);
|
|
43
|
+
try {
|
|
44
|
+
await db.close?.();
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
async function main() {
|
|
51
|
+
const ticker = await runLivenessTicker(workerData);
|
|
52
|
+
parentPort?.on("message", async (message) => {
|
|
53
|
+
if (message === "stop") {
|
|
54
|
+
await ticker.stop();
|
|
55
|
+
parentPort?.postMessage("stopped");
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
parentPort?.postMessage("ready");
|
|
59
|
+
}
|
|
60
|
+
main().catch((error) => {
|
|
61
|
+
parentPort?.postMessage({
|
|
62
|
+
type: "error",
|
|
63
|
+
message: error instanceof Error ? error.message : String(error)
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
//# sourceMappingURL=worker-liveness-thread.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-liveness-thread.js","sources":["../src/worker-liveness-ticker.ts","../src/worker-liveness-thread.ts"],"sourcesContent":["import { getDatabase } from '@happyvertical/sql';\nimport { tuneSqliteForConcurrency } from './worker-liveness.js';\n\n/**\n * Configuration for an off-loop liveness ticker (passed via `workerData`).\n */\nexport interface LivenessTickerOptions {\n /** Database URL the owning runner is using. */\n url: string;\n /** Optional explicit engine/type hint. */\n type?: string;\n /** This runner incarnation's worker key (the `_smrt_workers.worker_id`). */\n workerKey: string;\n /** Lease time-to-live in milliseconds. */\n leaseTtlMs: number;\n /** Renewal cadence in milliseconds. */\n leaseTickMs: number;\n}\n\nexport interface LivenessTicker {\n /** Stop renewing and close the connection. */\n stop: () => Promise<void>;\n}\n\n/**\n * Renew a worker's liveness lease from *outside* the handler event loop.\n *\n * Runs on a `node:worker_threads` thread (or, in tests, directly) with its own\n * database connection, so a CPU-bound synchronous handler on the main thread\n * cannot starve the renewal — the failure mode that made the old heartbeat\n * false-recover running jobs (#1474). A dead process stops renewing and the\n * lease expires within its TTL, which is how recovery detects death.\n */\nexport async function runLivenessTicker(\n options: LivenessTickerOptions,\n): Promise<LivenessTicker> {\n const db = await getDatabase({\n url: options.url,\n type: options.type as 'sqlite' | 'postgres' | 'duckdb' | undefined,\n });\n\n // The ticker's connection shares the SQLite file with the runner's main\n // connection; enable WAL + a busy timeout so renewals don't lose a lock race.\n const looksSqlite =\n (options.type ?? '').toLowerCase() === 'sqlite' ||\n (!options.type && /^file:|\\.(db|sqlite)\\b|:memory:/i.test(options.url));\n if (looksSqlite) await tuneSqliteForConcurrency(db);\n\n const renew = async (): Promise<void> => {\n const now = new Date();\n const expiresAt = new Date(\n now.getTime() + options.leaseTtlMs,\n ).toISOString();\n await db.query(\n `UPDATE _smrt_workers\n SET lease_expires_at = ?,\n heartbeat_at = ?\n WHERE worker_id = ?`,\n expiresAt,\n now.toISOString(),\n options.workerKey,\n );\n };\n\n try {\n // Renew immediately so the lease is fresh the moment the ticker starts.\n await renew();\n } catch (error) {\n // Don't leak the connection if the very first renewal fails.\n await db.close?.().catch(() => {});\n throw error;\n }\n\n const timer = setInterval(() => {\n renew().catch(() => {\n // Transient renewal failures are tolerated; the next tick retries and\n // the lease TTL absorbs a missed beat (TTL >= 3x tick).\n });\n }, options.leaseTickMs);\n\n let stopped = false;\n return {\n stop: async () => {\n if (stopped) return;\n stopped = true;\n clearInterval(timer);\n try {\n await db.close?.();\n } catch {\n // Best-effort connection teardown.\n }\n },\n };\n}\n","/**\n * Worker-thread entry for the off-loop liveness ticker.\n *\n * Thin shell: read {@link LivenessTickerOptions} from `workerData`, start the\n * ticker, and stop it on a `'stop'` message from the parent. All real logic\n * lives in {@link runLivenessTicker} so it can be unit-tested without spawning\n * a thread. Keep this file decorator-free so Node type-stripping can run the\n * `.ts` directly under vitest.\n */\nimport { parentPort, workerData } from 'node:worker_threads';\nimport {\n type LivenessTickerOptions,\n runLivenessTicker,\n} from './worker-liveness-ticker.js';\n\nasync function main(): Promise<void> {\n const ticker = await runLivenessTicker(workerData as LivenessTickerOptions);\n\n parentPort?.on('message', async (message) => {\n if (message === 'stop') {\n await ticker.stop();\n parentPort?.postMessage('stopped');\n }\n });\n\n // Signal the parent that the lease is seeded and renewal is running.\n parentPort?.postMessage('ready');\n}\n\nmain().catch((error) => {\n // Surface startup failure to the parent so the runner can fall back to\n // main-loop renewal rather than silently losing its lease.\n parentPort?.postMessage({\n type: 'error',\n message: error instanceof Error ? error.message : String(error),\n });\n});\n"],"names":[],"mappings":";;;AAiCA,eAAsB,kBACpB,SACyB;AACzB,QAAM,KAAK,MAAM,YAAY;AAAA,IAC3B,KAAK,QAAQ;AAAA,IACb,MAAM,QAAQ;AAAA,EAAA,CACf;AAID,QAAM,eACH,QAAQ,QAAQ,IAAI,YAAA,MAAkB,YACtC,CAAC,QAAQ,QAAQ,mCAAmC,KAAK,QAAQ,GAAG;AACvE,MAAI,YAAa,OAAM,yBAAyB,EAAE;AAElD,QAAM,QAAQ,YAA2B;AACvC,UAAM,0BAAU,KAAA;AAChB,UAAM,YAAY,IAAI;AAAA,MACpB,IAAI,QAAA,IAAY,QAAQ;AAAA,IAAA,EACxB,YAAA;AACF,UAAM,GAAG;AAAA,MACP;AAAA;AAAA;AAAA;AAAA,MAIA;AAAA,MACA,IAAI,YAAA;AAAA,MACJ,QAAQ;AAAA,IAAA;AAAA,EAEZ;AAEA,MAAI;AAEF,UAAM,MAAA;AAAA,EACR,SAAS,OAAO;AAEd,UAAM,GAAG,UAAU,MAAM,MAAM;AAAA,IAAC,CAAC;AACjC,UAAM;AAAA,EACR;AAEA,QAAM,QAAQ,YAAY,MAAM;AAC9B,UAAA,EAAQ,MAAM,MAAM;AAAA,IAGpB,CAAC;AAAA,EACH,GAAG,QAAQ,WAAW;AAEtB,MAAI,UAAU;AACd,SAAO;AAAA,IACL,MAAM,YAAY;AAChB,UAAI,QAAS;AACb,gBAAU;AACV,oBAAc,KAAK;AACnB,UAAI;AACF,cAAM,GAAG,QAAA;AAAA,MACX,QAAQ;AAAA,MAER;AAAA,IACF;AAAA,EAAA;AAEJ;AC9EA,eAAe,OAAsB;AACnC,QAAM,SAAS,MAAM,kBAAkB,UAAmC;AAE1E,cAAY,GAAG,WAAW,OAAO,YAAY;AAC3C,QAAI,YAAY,QAAQ;AACtB,YAAM,OAAO,KAAA;AACb,kBAAY,YAAY,SAAS;AAAA,IACnC;AAAA,EACF,CAAC;AAGD,cAAY,YAAY,OAAO;AACjC;AAEA,OAAO,MAAM,CAAC,UAAU;AAGtB,cAAY,YAAY;AAAA,IACtB,MAAM;AAAA,IACN,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAAA,EAAA,CAC/D;AACH,CAAC;"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Configuration for an off-loop liveness ticker (passed via `workerData`).
|
|
3
|
+
*/
|
|
4
|
+
export interface LivenessTickerOptions {
|
|
5
|
+
/** Database URL the owning runner is using. */
|
|
6
|
+
url: string;
|
|
7
|
+
/** Optional explicit engine/type hint. */
|
|
8
|
+
type?: string;
|
|
9
|
+
/** This runner incarnation's worker key (the `_smrt_workers.worker_id`). */
|
|
10
|
+
workerKey: string;
|
|
11
|
+
/** Lease time-to-live in milliseconds. */
|
|
12
|
+
leaseTtlMs: number;
|
|
13
|
+
/** Renewal cadence in milliseconds. */
|
|
14
|
+
leaseTickMs: number;
|
|
15
|
+
}
|
|
16
|
+
export interface LivenessTicker {
|
|
17
|
+
/** Stop renewing and close the connection. */
|
|
18
|
+
stop: () => Promise<void>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Renew a worker's liveness lease from *outside* the handler event loop.
|
|
22
|
+
*
|
|
23
|
+
* Runs on a `node:worker_threads` thread (or, in tests, directly) with its own
|
|
24
|
+
* database connection, so a CPU-bound synchronous handler on the main thread
|
|
25
|
+
* cannot starve the renewal — the failure mode that made the old heartbeat
|
|
26
|
+
* false-recover running jobs (#1474). A dead process stops renewing and the
|
|
27
|
+
* lease expires within its TTL, which is how recovery detects death.
|
|
28
|
+
*/
|
|
29
|
+
export declare function runLivenessTicker(options: LivenessTickerOptions): Promise<LivenessTicker>;
|
|
30
|
+
//# sourceMappingURL=worker-liveness-ticker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-liveness-ticker.d.ts","sourceRoot":"","sources":["../src/worker-liveness-ticker.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,0CAA0C;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,UAAU,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,8CAA8C;IAC9C,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CACrC,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,cAAc,CAAC,CA0DzB"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { detectEngine } from '@happyvertical/smrt-core';
|
|
2
|
+
import { DatabaseInterface } from '@happyvertical/sql';
|
|
3
|
+
type DatabaseEngine = ReturnType<typeof detectEngine>;
|
|
4
|
+
/** Mark a worker key as live in this process. */
|
|
5
|
+
export declare function registerLiveWorker(workerKey: string): void;
|
|
6
|
+
/** Remove a worker key from this process's live set. */
|
|
7
|
+
export declare function unregisterLiveWorker(workerKey: string): void;
|
|
8
|
+
/** Whether a worker key is live in this process. */
|
|
9
|
+
export declare function isLiveWorker(workerKey: string): boolean;
|
|
10
|
+
/** Snapshot of every worker key live in this process. */
|
|
11
|
+
export declare function liveWorkerKeys(): Set<string>;
|
|
12
|
+
/**
|
|
13
|
+
* Whether a worker is alive: live in *this* process (synchronous truth, never
|
|
14
|
+
* starved), or holding a fresh database lease in some process. `null`/unknown
|
|
15
|
+
* worker keys are never alive.
|
|
16
|
+
*/
|
|
17
|
+
export declare function isWorkerAlive(workerKey: string | null | undefined, freshLeaseKeys: Set<string>): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Build a per-incarnation-unique worker key.
|
|
20
|
+
*
|
|
21
|
+
* Recovery treats a worker key as the unit of liveness, so the key must be
|
|
22
|
+
* unique per process incarnation: a runner that crashes and restarts under the
|
|
23
|
+
* same configured `id` must get a *new* key, otherwise its orphaned `running`
|
|
24
|
+
* jobs would look owned by the live restart and never be recovered. We append
|
|
25
|
+
* a random token to the (optional) configured id, which also keeps the
|
|
26
|
+
* human-facing runner id stable for logs/events.
|
|
27
|
+
*/
|
|
28
|
+
export declare function createWorkerKey(baseId: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* The connection URL, honoring adapters that leave `db.url` empty and carry the
|
|
31
|
+
* real URL on `db.config.url`. Used consistently for engine detection, the
|
|
32
|
+
* in-memory check, and the URL handed to the off-loop thread so they never
|
|
33
|
+
* disagree.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveUrl(db: DatabaseInterface): string;
|
|
36
|
+
/**
|
|
37
|
+
* Tune a SQLite connection for the off-loop liveness topology.
|
|
38
|
+
*
|
|
39
|
+
* The runner's main connection and the off-loop ticker's connection both touch
|
|
40
|
+
* the same file. Stock libsql/SQLite opens in rollback-journal mode with no busy
|
|
41
|
+
* timeout, so the instant two connections contend it returns `SQLITE_BUSY` —
|
|
42
|
+
* surfacing as a flaky "Failed to execute raw query" under CI load. WAL lets
|
|
43
|
+
* readers run concurrently with the single writer, and `busy_timeout` makes a
|
|
44
|
+
* contended writer wait for the lock instead of failing immediately. Both are
|
|
45
|
+
* idempotent and file-level (WAL persists for every connection to the file).
|
|
46
|
+
*
|
|
47
|
+
* Best-effort: a PRAGMA failure must never break startup — the in-process live
|
|
48
|
+
* set keeps same-process recovery correct regardless. Callers gate this on a
|
|
49
|
+
* SQLite engine so it is never issued to Postgres.
|
|
50
|
+
*/
|
|
51
|
+
export declare function tuneSqliteForConcurrency(db: DatabaseInterface): Promise<void>;
|
|
52
|
+
/** Resolve the database engine for a connection. */
|
|
53
|
+
export declare function resolveEngine(db: DatabaseInterface): DatabaseEngine;
|
|
54
|
+
/**
|
|
55
|
+
* Whether a connection points at an in-memory SQLite database. In-memory
|
|
56
|
+
* databases are single-process (nothing to recover cross-process) and a second
|
|
57
|
+
* connection cannot see the same data, so the off-loop liveness thread is
|
|
58
|
+
* skipped for them.
|
|
59
|
+
*/
|
|
60
|
+
export declare function isInMemory(db: DatabaseInterface): boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Whether the off-loop liveness thread can run against this connection.
|
|
63
|
+
*
|
|
64
|
+
* Requires a second independent connection to the same data: true for Postgres
|
|
65
|
+
* and file-backed SQLite. In-memory SQLite cannot be reached from another
|
|
66
|
+
* connection, and DuckDB is single-writer per file — both fall back to
|
|
67
|
+
* main-loop lease renewal + the in-process live set.
|
|
68
|
+
*/
|
|
69
|
+
export declare function offLoopEligible(db: DatabaseInterface): boolean;
|
|
70
|
+
export {};
|
|
71
|
+
//# sourceMappingURL=worker-liveness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-liveness.d.ts","sourceRoot":"","sources":["../src/worker-liveness.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AACxD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAG5D,KAAK,cAAc,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AAgDtD,iDAAiD;AACjD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE1D;AAED,wDAAwD;AACxD,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAE5D;AAED,oDAAoD;AACpD,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED,yDAAyD;AACzD,wBAAgB,cAAc,IAAI,GAAG,CAAC,MAAM,CAAC,CAE5C;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACpC,cAAc,EAAE,GAAG,CAAC,MAAM,CAAC,GAC1B,OAAO,CAGT;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,iBAAiB,GAAG,MAAM,CAGxD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,wBAAwB,CAC5C,EAAE,EAAE,iBAAiB,GACpB,OAAO,CAAC,IAAI,CAAC,CAOf;AAED,oDAAoD;AACpD,wBAAgB,aAAa,CAAC,EAAE,EAAE,iBAAiB,GAAG,cAAc,CAOnE;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,iBAAiB,GAAG,OAAO,CAOzD;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,iBAAiB,GAAG,OAAO,CAK9D"}
|