@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,68 @@
|
|
|
1
|
+
import { SmrtObject } from '@happyvertical/smrt-core';
|
|
2
|
+
import { JobBuilder } from './job-builder.js';
|
|
3
|
+
import { JobHandle } from './job-handle.js';
|
|
4
|
+
/**
|
|
5
|
+
* Options for the simple .bg() method
|
|
6
|
+
*/
|
|
7
|
+
export interface BgOptions {
|
|
8
|
+
/** Queue name */
|
|
9
|
+
queue?: string;
|
|
10
|
+
/** Priority level */
|
|
11
|
+
priority?: 'critical' | 'high' | 'normal' | 'low' | number;
|
|
12
|
+
/** Delay before running (ms or string like '5m') */
|
|
13
|
+
delay?: string | number;
|
|
14
|
+
/** Maximum retries */
|
|
15
|
+
retries?: number;
|
|
16
|
+
/** Timeout in milliseconds */
|
|
17
|
+
timeout?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Type for the extended SmrtObject with background job methods
|
|
21
|
+
*/
|
|
22
|
+
export interface BackgroundCapable {
|
|
23
|
+
/**
|
|
24
|
+
* Simple background job submission
|
|
25
|
+
*
|
|
26
|
+
* @param method - Method name to invoke
|
|
27
|
+
* @param args - Arguments to pass to the method
|
|
28
|
+
* @param options - Job options
|
|
29
|
+
* @returns JobHandle for tracking the job
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* const handle = await doc.bg('generateSummary', { format: 'md' });
|
|
33
|
+
*/
|
|
34
|
+
bg<T = unknown>(method: string, args?: Record<string, unknown>, options?: BgOptions): Promise<JobHandle<T>>;
|
|
35
|
+
/**
|
|
36
|
+
* Fluent job builder for advanced options
|
|
37
|
+
*
|
|
38
|
+
* @param method - Method name to invoke
|
|
39
|
+
* @param args - Arguments to pass to the method
|
|
40
|
+
* @returns JobBuilder for fluent configuration
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* const handle = await doc.background('generateSummary', { format: 'md' })
|
|
44
|
+
* .delay('5m')
|
|
45
|
+
* .retries(5)
|
|
46
|
+
* .priority('high')
|
|
47
|
+
* .enqueue();
|
|
48
|
+
*/
|
|
49
|
+
background<T = unknown>(method: string, args?: Record<string, unknown>): JobBuilder<T>;
|
|
50
|
+
}
|
|
51
|
+
type SmrtObjectConstructor = new (...args: any[]) => SmrtObject;
|
|
52
|
+
type BackgroundCapableConstructor<T extends SmrtObjectConstructor> = T & {
|
|
53
|
+
new (...args: ConstructorParameters<T>): InstanceType<T> & BackgroundCapable;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Extend a SmrtObject class with background job methods
|
|
57
|
+
*
|
|
58
|
+
* @param BaseClass - The SmrtObject class to extend
|
|
59
|
+
* @returns Extended class with .bg() and .background() methods
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* const BackgroundDocument = withBackgroundJobs(Document);
|
|
63
|
+
* const doc = new BackgroundDocument({ ... });
|
|
64
|
+
* const handle = await doc.bg('generateSummary', { format: 'md' });
|
|
65
|
+
*/
|
|
66
|
+
export declare function withBackgroundJobs<T extends SmrtObjectConstructor>(BaseClass: T): BackgroundCapableConstructor<T>;
|
|
67
|
+
export default withBackgroundJobs;
|
|
68
|
+
//# sourceMappingURL=object-extension.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"object-extension.d.ts","sourceRoot":"","sources":["../src/object-extension.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE3E,OAAO,EACL,UAAU,EAIX,MAAM,kBAAkB,CAAC;AAC1B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAGjD;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,iBAAiB;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,QAAQ,CAAC,EAAE,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;IAC3D,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,sBAAsB;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;;;;;;OAUG;IACH,EAAE,CAAC,CAAC,GAAG,OAAO,EACZ,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,OAAO,CAAC,EAAE,SAAS,GAClB,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;IAEzB;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,CAAC,GAAG,OAAO,EACpB,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,UAAU,CAAC,CAAC,CAAC,CAAC;CAClB;AAkCD,KAAK,qBAAqB,GAAG,KAAK,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,UAAU,CAAC;AAChE,KAAK,4BAA4B,CAAC,CAAC,SAAS,qBAAqB,IAAI,CAAC,GAAG;IACvE,KAAK,GAAG,IAAI,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC;CAC9E,CAAC;AAiIF;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,qBAAqB,EAChE,SAAS,EAAE,CAAC,GACX,4BAA4B,CAAC,CAAC,CAAC,CAqBjC;AAED,eAAe,kBAAkB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playground.d.ts","sourceRoot":"","sources":["../src/playground.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { JOBS_MODULE_META } from "./ui.js";
|
|
2
|
+
const noop = () => {
|
|
3
|
+
};
|
|
4
|
+
const sampleJobs = [
|
|
5
|
+
{
|
|
6
|
+
id: "job-publish-1",
|
|
7
|
+
queue: "content",
|
|
8
|
+
objectType: "@happyvertical/smrt-content:Article",
|
|
9
|
+
objectId: "article-aurora-kitchen",
|
|
10
|
+
method: "publish",
|
|
11
|
+
args: { force: false },
|
|
12
|
+
status: "running",
|
|
13
|
+
priority: 80,
|
|
14
|
+
attempts: 1,
|
|
15
|
+
maxAttempts: 3,
|
|
16
|
+
timeout: 6e5,
|
|
17
|
+
timeoutBehavior: "fail",
|
|
18
|
+
runAt: "2026-03-21T16:15:00.000Z",
|
|
19
|
+
startedAt: "2026-03-21T16:15:15.000Z",
|
|
20
|
+
completedAt: null,
|
|
21
|
+
lastError: null,
|
|
22
|
+
resultPointer: null,
|
|
23
|
+
workerId: "worker-content-1",
|
|
24
|
+
createdAt: "2026-03-21T16:14:50.000Z",
|
|
25
|
+
updatedAt: "2026-03-21T16:15:20.000Z"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "job-review-2",
|
|
29
|
+
queue: "governance",
|
|
30
|
+
objectType: "@happyvertical/smrt-content:GovernancePolicy",
|
|
31
|
+
objectId: "policy-facts",
|
|
32
|
+
method: "recalculateReadiness",
|
|
33
|
+
args: { profileKey: "publication" },
|
|
34
|
+
status: "failed",
|
|
35
|
+
priority: 95,
|
|
36
|
+
attempts: 3,
|
|
37
|
+
maxAttempts: 3,
|
|
38
|
+
timeout: 3e5,
|
|
39
|
+
timeoutBehavior: "fail",
|
|
40
|
+
runAt: "2026-03-21T15:40:00.000Z",
|
|
41
|
+
startedAt: "2026-03-21T15:40:02.000Z",
|
|
42
|
+
completedAt: "2026-03-21T15:41:11.000Z",
|
|
43
|
+
lastError: "Relationship column missing during readiness sync.",
|
|
44
|
+
resultPointer: null,
|
|
45
|
+
workerId: "worker-governance-2",
|
|
46
|
+
createdAt: "2026-03-21T15:39:30.000Z",
|
|
47
|
+
updatedAt: "2026-03-21T15:41:11.000Z"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: "job-export-3",
|
|
51
|
+
queue: "exports",
|
|
52
|
+
objectType: "@happyvertical/smrt-content:ArticleCollection",
|
|
53
|
+
objectId: null,
|
|
54
|
+
method: "buildStaticFeed",
|
|
55
|
+
args: { destination: "cdn" },
|
|
56
|
+
status: "completed",
|
|
57
|
+
priority: 40,
|
|
58
|
+
attempts: 1,
|
|
59
|
+
maxAttempts: 2,
|
|
60
|
+
timeout: 9e5,
|
|
61
|
+
timeoutBehavior: "warn",
|
|
62
|
+
runAt: "2026-03-21T14:30:00.000Z",
|
|
63
|
+
startedAt: "2026-03-21T14:30:00.000Z",
|
|
64
|
+
completedAt: "2026-03-21T14:31:25.000Z",
|
|
65
|
+
lastError: null,
|
|
66
|
+
resultPointer: "results/exports/build-static-feed-3.json",
|
|
67
|
+
workerId: "worker-export-1",
|
|
68
|
+
createdAt: "2026-03-21T14:29:52.000Z",
|
|
69
|
+
updatedAt: "2026-03-21T14:31:25.000Z"
|
|
70
|
+
}
|
|
71
|
+
];
|
|
72
|
+
const sampleStats = {
|
|
73
|
+
total: 3421,
|
|
74
|
+
pending: 24,
|
|
75
|
+
ready: 13,
|
|
76
|
+
running: 4,
|
|
77
|
+
completed: 3340,
|
|
78
|
+
failed: 31,
|
|
79
|
+
cancelled: 9,
|
|
80
|
+
avgDuration: 18450,
|
|
81
|
+
successRate: 0.976
|
|
82
|
+
};
|
|
83
|
+
const sampleQueues = [
|
|
84
|
+
{
|
|
85
|
+
name: "content",
|
|
86
|
+
pending: 8,
|
|
87
|
+
running: 2,
|
|
88
|
+
completed24h: 412,
|
|
89
|
+
failed24h: 4,
|
|
90
|
+
avgDuration: 22800
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "governance",
|
|
94
|
+
pending: 5,
|
|
95
|
+
running: 1,
|
|
96
|
+
completed24h: 188,
|
|
97
|
+
failed24h: 7,
|
|
98
|
+
avgDuration: 31200
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "exports",
|
|
102
|
+
pending: 11,
|
|
103
|
+
running: 1,
|
|
104
|
+
completed24h: 94,
|
|
105
|
+
failed24h: 1,
|
|
106
|
+
avgDuration: 52200
|
|
107
|
+
}
|
|
108
|
+
];
|
|
109
|
+
const loadJobDashboard = () => import("./svelte/components/JobDashboard.svelte");
|
|
110
|
+
const loadJobDetail = () => import("./svelte/components/JobDetail.svelte");
|
|
111
|
+
const loadJobStatusBadge = () => import("./svelte/components/JobStatusBadge.svelte");
|
|
112
|
+
const playground = {
|
|
113
|
+
packageName: "@happyvertical/smrt-jobs",
|
|
114
|
+
displayName: JOBS_MODULE_META.displayName,
|
|
115
|
+
description: JOBS_MODULE_META.description,
|
|
116
|
+
moduleMeta: JOBS_MODULE_META,
|
|
117
|
+
entries: [
|
|
118
|
+
{
|
|
119
|
+
id: "job-dashboard",
|
|
120
|
+
title: "Job Dashboard",
|
|
121
|
+
description: "Operational overview for background job queues with recent and failed work.",
|
|
122
|
+
loadComponent: loadJobDashboard,
|
|
123
|
+
order: 1,
|
|
124
|
+
props: {
|
|
125
|
+
stats: sampleStats,
|
|
126
|
+
queues: sampleQueues,
|
|
127
|
+
recentJobs: sampleJobs,
|
|
128
|
+
failedJobs: sampleJobs.filter((job) => job.status === "failed"),
|
|
129
|
+
onJobClick: noop,
|
|
130
|
+
onRetry: noop,
|
|
131
|
+
onCancel: noop,
|
|
132
|
+
onViewAll: noop,
|
|
133
|
+
onViewFailed: noop
|
|
134
|
+
},
|
|
135
|
+
modes: {
|
|
136
|
+
mock: {
|
|
137
|
+
label: "Mock"
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
id: "job-detail",
|
|
143
|
+
title: "Job Detail",
|
|
144
|
+
description: "Detailed execution view for a single background job with retry and cancellation actions.",
|
|
145
|
+
loadComponent: loadJobDetail,
|
|
146
|
+
order: 2,
|
|
147
|
+
props: {
|
|
148
|
+
job: sampleJobs[1],
|
|
149
|
+
onRetry: noop,
|
|
150
|
+
onDelete: noop
|
|
151
|
+
},
|
|
152
|
+
modes: {
|
|
153
|
+
mock: {
|
|
154
|
+
label: "Mock"
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
id: "job-status-badge",
|
|
160
|
+
title: "Job Status Badge",
|
|
161
|
+
description: "Compact status treatment used throughout the jobs surface.",
|
|
162
|
+
loadComponent: loadJobStatusBadge,
|
|
163
|
+
order: 3,
|
|
164
|
+
props: {
|
|
165
|
+
status: "running",
|
|
166
|
+
size: "md"
|
|
167
|
+
},
|
|
168
|
+
modes: {
|
|
169
|
+
mock: {
|
|
170
|
+
label: "Mock"
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
]
|
|
175
|
+
};
|
|
176
|
+
export {
|
|
177
|
+
playground as default
|
|
178
|
+
};
|
|
179
|
+
//# sourceMappingURL=playground.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playground.js","sources":["../src/svelte/playground.ts"],"sourcesContent":["import { JOBS_MODULE_META } from '../ui.js';\n\nconst noop = () => {};\n\nconst sampleJobs = [\n {\n id: 'job-publish-1',\n queue: 'content',\n objectType: '@happyvertical/smrt-content:Article',\n objectId: 'article-aurora-kitchen',\n method: 'publish',\n args: { force: false },\n status: 'running',\n priority: 80,\n attempts: 1,\n maxAttempts: 3,\n timeout: 600000,\n timeoutBehavior: 'fail',\n runAt: '2026-03-21T16:15:00.000Z',\n startedAt: '2026-03-21T16:15:15.000Z',\n completedAt: null,\n lastError: null,\n resultPointer: null,\n workerId: 'worker-content-1',\n createdAt: '2026-03-21T16:14:50.000Z',\n updatedAt: '2026-03-21T16:15:20.000Z',\n },\n {\n id: 'job-review-2',\n queue: 'governance',\n objectType: '@happyvertical/smrt-content:GovernancePolicy',\n objectId: 'policy-facts',\n method: 'recalculateReadiness',\n args: { profileKey: 'publication' },\n status: 'failed',\n priority: 95,\n attempts: 3,\n maxAttempts: 3,\n timeout: 300000,\n timeoutBehavior: 'fail',\n runAt: '2026-03-21T15:40:00.000Z',\n startedAt: '2026-03-21T15:40:02.000Z',\n completedAt: '2026-03-21T15:41:11.000Z',\n lastError: 'Relationship column missing during readiness sync.',\n resultPointer: null,\n workerId: 'worker-governance-2',\n createdAt: '2026-03-21T15:39:30.000Z',\n updatedAt: '2026-03-21T15:41:11.000Z',\n },\n {\n id: 'job-export-3',\n queue: 'exports',\n objectType: '@happyvertical/smrt-content:ArticleCollection',\n objectId: null,\n method: 'buildStaticFeed',\n args: { destination: 'cdn' },\n status: 'completed',\n priority: 40,\n attempts: 1,\n maxAttempts: 2,\n timeout: 900000,\n timeoutBehavior: 'warn',\n runAt: '2026-03-21T14:30:00.000Z',\n startedAt: '2026-03-21T14:30:00.000Z',\n completedAt: '2026-03-21T14:31:25.000Z',\n lastError: null,\n resultPointer: 'results/exports/build-static-feed-3.json',\n workerId: 'worker-export-1',\n createdAt: '2026-03-21T14:29:52.000Z',\n updatedAt: '2026-03-21T14:31:25.000Z',\n },\n];\n\nconst sampleStats = {\n total: 3421,\n pending: 24,\n ready: 13,\n running: 4,\n completed: 3340,\n failed: 31,\n cancelled: 9,\n avgDuration: 18450,\n successRate: 0.976,\n};\n\nconst sampleQueues = [\n {\n name: 'content',\n pending: 8,\n running: 2,\n completed24h: 412,\n failed24h: 4,\n avgDuration: 22800,\n },\n {\n name: 'governance',\n pending: 5,\n running: 1,\n completed24h: 188,\n failed24h: 7,\n avgDuration: 31200,\n },\n {\n name: 'exports',\n pending: 11,\n running: 1,\n completed24h: 94,\n failed24h: 1,\n avgDuration: 52200,\n },\n];\n\nconst loadJobDashboard = () => import('./components/JobDashboard.svelte');\nconst loadJobDetail = () => import('./components/JobDetail.svelte');\nconst loadJobStatusBadge = () => import('./components/JobStatusBadge.svelte');\n\nexport default {\n packageName: '@happyvertical/smrt-jobs',\n displayName: JOBS_MODULE_META.displayName,\n description: JOBS_MODULE_META.description,\n moduleMeta: JOBS_MODULE_META,\n entries: [\n {\n id: 'job-dashboard',\n title: 'Job Dashboard',\n description:\n 'Operational overview for background job queues with recent and failed work.',\n loadComponent: loadJobDashboard,\n order: 1,\n props: {\n stats: sampleStats,\n queues: sampleQueues,\n recentJobs: sampleJobs,\n failedJobs: sampleJobs.filter((job) => job.status === 'failed'),\n onJobClick: noop,\n onRetry: noop,\n onCancel: noop,\n onViewAll: noop,\n onViewFailed: noop,\n },\n modes: {\n mock: {\n label: 'Mock',\n },\n },\n },\n {\n id: 'job-detail',\n title: 'Job Detail',\n description:\n 'Detailed execution view for a single background job with retry and cancellation actions.',\n loadComponent: loadJobDetail,\n order: 2,\n props: {\n job: sampleJobs[1],\n onRetry: noop,\n onDelete: noop,\n },\n modes: {\n mock: {\n label: 'Mock',\n },\n },\n },\n {\n id: 'job-status-badge',\n title: 'Job Status Badge',\n description: 'Compact status treatment used throughout the jobs surface.',\n loadComponent: loadJobStatusBadge,\n order: 3,\n props: {\n status: 'running',\n size: 'md',\n },\n modes: {\n mock: {\n label: 'Mock',\n },\n },\n },\n ],\n};\n"],"names":[],"mappings":"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"}
|
package/dist/runner.d.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { DatabaseInterface } from '@happyvertical/sql';
|
|
3
|
+
import { SmrtJob } from './smrt-job.js';
|
|
4
|
+
import { SmrtJobEvent } from './smrt-job-event.js';
|
|
5
|
+
/**
|
|
6
|
+
* TaskRunner configuration
|
|
7
|
+
*/
|
|
8
|
+
export interface TaskRunnerConfig {
|
|
9
|
+
/** Worker ID (auto-generated if not provided) */
|
|
10
|
+
id?: string;
|
|
11
|
+
/** Number of concurrent jobs to process */
|
|
12
|
+
concurrency?: number;
|
|
13
|
+
/** Queues to process (default: ['default']) */
|
|
14
|
+
queues?: string[];
|
|
15
|
+
/** Polling interval in milliseconds */
|
|
16
|
+
pollInterval?: number;
|
|
17
|
+
/** Heartbeat interval in milliseconds */
|
|
18
|
+
heartbeatInterval?: number;
|
|
19
|
+
/** Maximum time to wait for jobs to complete on shutdown */
|
|
20
|
+
shutdownTimeout?: number;
|
|
21
|
+
/**
|
|
22
|
+
* @deprecated No longer used. Recovery keys on worker liveness, not per-job
|
|
23
|
+
* heartbeat staleness (#1474). Use {@link leaseTtlMs} / {@link leaseTickMs}.
|
|
24
|
+
*/
|
|
25
|
+
staleJobThresholdMs?: number;
|
|
26
|
+
/** Worker liveness lease time-to-live in milliseconds */
|
|
27
|
+
leaseTtlMs?: number;
|
|
28
|
+
/** How often to renew the worker liveness lease, in milliseconds */
|
|
29
|
+
leaseTickMs?: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* TaskRunner events
|
|
33
|
+
*/
|
|
34
|
+
export interface TaskRunnerEvents {
|
|
35
|
+
'job:started': (job: SmrtJob) => void;
|
|
36
|
+
'job:event': (job: SmrtJob, event: SmrtJobEvent) => void;
|
|
37
|
+
'job:progress': (job: SmrtJob, event: SmrtJobEvent) => void;
|
|
38
|
+
'job:completed': (job: SmrtJob, result: unknown) => void;
|
|
39
|
+
'job:failed': (job: SmrtJob, error: Error) => void;
|
|
40
|
+
'job:retrying': (job: SmrtJob, error: Error, delay: number) => void;
|
|
41
|
+
'runner:started': () => void;
|
|
42
|
+
'runner:stopped': () => void;
|
|
43
|
+
'runner:error': (error: Error) => void;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* TaskRunner processes SMRT jobs by invoking methods on SmrtObjects
|
|
47
|
+
*
|
|
48
|
+
* Features:
|
|
49
|
+
* - Executes jobs via SmrtObject method invocation
|
|
50
|
+
* - Configurable concurrency and timeout behavior
|
|
51
|
+
* - Automatic retry with configurable strategies
|
|
52
|
+
* - Job context logging for visibility
|
|
53
|
+
* - Embedded mode (in-process) or standalone (CLI)
|
|
54
|
+
*/
|
|
55
|
+
export declare class TaskRunner extends EventEmitter {
|
|
56
|
+
readonly id: string;
|
|
57
|
+
/**
|
|
58
|
+
* Per-incarnation-unique worker key. Stored as the `worker_id` on claimed
|
|
59
|
+
* jobs and in `_smrt_workers`, so a restart of a runner sharing the same
|
|
60
|
+
* configured `id` does not look like it still owns the previous
|
|
61
|
+
* incarnation's orphaned jobs. The human-facing {@link id} stays stable for
|
|
62
|
+
* events/logs.
|
|
63
|
+
*/
|
|
64
|
+
private readonly workerKey;
|
|
65
|
+
private readonly config;
|
|
66
|
+
private readonly effectiveLeaseTtlMs;
|
|
67
|
+
private collection;
|
|
68
|
+
private eventCollection;
|
|
69
|
+
private workerCollection;
|
|
70
|
+
private workersTableVerified;
|
|
71
|
+
private lastRecoverySweepAt;
|
|
72
|
+
private running;
|
|
73
|
+
private activeJobs;
|
|
74
|
+
private pollTimer;
|
|
75
|
+
private heartbeatTimer;
|
|
76
|
+
private leaseTimer;
|
|
77
|
+
private livenessWorker;
|
|
78
|
+
private shutdownPromise;
|
|
79
|
+
private db;
|
|
80
|
+
private logger;
|
|
81
|
+
constructor(config?: TaskRunnerConfig);
|
|
82
|
+
/**
|
|
83
|
+
* Initialize the runner with database connection
|
|
84
|
+
*/
|
|
85
|
+
initialize(db: DatabaseInterface): Promise<void>;
|
|
86
|
+
/**
|
|
87
|
+
* Start processing jobs
|
|
88
|
+
*/
|
|
89
|
+
start(): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Stop processing jobs (graceful shutdown)
|
|
92
|
+
*/
|
|
93
|
+
stop(): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Check if runner is running
|
|
96
|
+
*/
|
|
97
|
+
isRunning(): boolean;
|
|
98
|
+
/**
|
|
99
|
+
* Get count of active jobs
|
|
100
|
+
*/
|
|
101
|
+
activeJobCount(): number;
|
|
102
|
+
/**
|
|
103
|
+
* Start the polling loop
|
|
104
|
+
*/
|
|
105
|
+
private startPolling;
|
|
106
|
+
/**
|
|
107
|
+
* Poll for and process jobs
|
|
108
|
+
*/
|
|
109
|
+
private poll;
|
|
110
|
+
/**
|
|
111
|
+
* Process a single job
|
|
112
|
+
*/
|
|
113
|
+
private processJob;
|
|
114
|
+
/**
|
|
115
|
+
* Apply a terminal/retry state transition to a job only if this worker still
|
|
116
|
+
* owns it and it is still `running`. Returns whether the write applied.
|
|
117
|
+
*
|
|
118
|
+
* This closes the completion-vs-recovery race: if recovery already failed a
|
|
119
|
+
* job out from under a finishing handler (a genuine zombie), the handler's
|
|
120
|
+
* outcome is dropped rather than resurrecting the row.
|
|
121
|
+
*/
|
|
122
|
+
private writeOwnedJob;
|
|
123
|
+
/**
|
|
124
|
+
* Execute a job by invoking the method on the SmrtObject
|
|
125
|
+
*/
|
|
126
|
+
private executeJob;
|
|
127
|
+
/**
|
|
128
|
+
* Handle job execution error
|
|
129
|
+
*/
|
|
130
|
+
private handleJobError;
|
|
131
|
+
private createExecutionContext;
|
|
132
|
+
private appendJobEvent;
|
|
133
|
+
/**
|
|
134
|
+
* Whether the `_smrt_workers` table exists. Cached once positive — the table
|
|
135
|
+
* never disappears mid-run, so this avoids a probe query on every poll.
|
|
136
|
+
*/
|
|
137
|
+
private workersTableReady;
|
|
138
|
+
/**
|
|
139
|
+
* Recover jobs orphaned by dead/restarted workers.
|
|
140
|
+
*
|
|
141
|
+
* A `running` job is recovered only when its owning worker is *not alive*
|
|
142
|
+
* (issue #1474): not live in this process and holding no fresh lease in
|
|
143
|
+
* `_smrt_workers`. This is independent of the handler event loop, so a worker
|
|
144
|
+
* whose handler holds the loop synchronously keeps a fresh lease (renewed off
|
|
145
|
+
* the loop by the liveness thread) or stays in this process's live set, and is
|
|
146
|
+
* never false-recovered. The live set takes precedence over a stale lease, and
|
|
147
|
+
* a runner never recovers its own active jobs.
|
|
148
|
+
*
|
|
149
|
+
* Recovery is swept at most once per lease tick (not every poll), since
|
|
150
|
+
* detection is TTL-bound anyway — this bounds the per-poll database load.
|
|
151
|
+
*/
|
|
152
|
+
private recoverStaleJobs;
|
|
153
|
+
/**
|
|
154
|
+
* Renew this worker's liveness lease.
|
|
155
|
+
*
|
|
156
|
+
* In Stage 1 this runs on the main event loop, so it provides cross-process
|
|
157
|
+
* detection no weaker than the old per-job heartbeat. Stage 2 moves the
|
|
158
|
+
* renewal to an off-loop worker thread so a synchronous handler can no longer
|
|
159
|
+
* starve it. Same-process correctness never depends on this timer — the
|
|
160
|
+
* in-memory live set covers it.
|
|
161
|
+
*/
|
|
162
|
+
private startLeaseRenewal;
|
|
163
|
+
/**
|
|
164
|
+
* Spawn the off-loop liveness thread. It opens its own connection and renews
|
|
165
|
+
* this worker's lease on its own thread (unstarvable by handler CPU). Returns
|
|
166
|
+
* false if the thread can't be resolved or fails to start, so the caller can
|
|
167
|
+
* fall back to main-loop renewal.
|
|
168
|
+
*/
|
|
169
|
+
private startLivenessThread;
|
|
170
|
+
private handleLivenessThreadLoss;
|
|
171
|
+
/** Stop the liveness thread (graceful, with a short bound), if running. */
|
|
172
|
+
private stopLivenessThread;
|
|
173
|
+
/**
|
|
174
|
+
* Per-job heartbeat loop — telemetry only ("last activity" for the UI). It no
|
|
175
|
+
* longer gates recovery (that is the worker lease), so a blocked loop missing
|
|
176
|
+
* a heartbeat is harmless.
|
|
177
|
+
*/
|
|
178
|
+
private startHeartbeat;
|
|
179
|
+
/**
|
|
180
|
+
* Wait for active jobs to complete with timeout
|
|
181
|
+
*/
|
|
182
|
+
private waitForActiveJobs;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Create a TaskRunner instance
|
|
186
|
+
*/
|
|
187
|
+
export declare function createTaskRunner(config?: TaskRunnerConfig): TaskRunner;
|
|
188
|
+
export default TaskRunner;
|
|
189
|
+
//# sourceMappingURL=runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}
|
package/dist/runner.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { T, l, T as T2 } from "./chunks/runner-DV8FBO0y.js";
|
|
2
|
+
import "node:events";
|
|
3
|
+
import "node:worker_threads";
|
|
4
|
+
import "@happyvertical/jobs";
|
|
5
|
+
import "@happyvertical/logger";
|
|
6
|
+
import "@happyvertical/smrt-core";
|
|
7
|
+
import "@happyvertical/smrt-tenancy";
|
|
8
|
+
import "@happyvertical/utils";
|
|
9
|
+
import "./chunks/worker-liveness-DOTjoIjr.js";
|
|
10
|
+
export {
|
|
11
|
+
T as TaskRunner,
|
|
12
|
+
l as createTaskRunner,
|
|
13
|
+
T2 as default
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runner.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { DatabaseInterface } from '@happyvertical/sql';
|
|
3
|
+
/**
|
|
4
|
+
* ScheduleRunner configuration
|
|
5
|
+
*/
|
|
6
|
+
export interface ScheduleRunnerConfig {
|
|
7
|
+
/** Runner ID (auto-generated if not provided) */
|
|
8
|
+
id?: string;
|
|
9
|
+
/** Polling interval in milliseconds (default: 60000 - 1 minute) */
|
|
10
|
+
pollInterval?: number;
|
|
11
|
+
/** Maximum schedules to process per poll */
|
|
12
|
+
batchSize?: number;
|
|
13
|
+
/**
|
|
14
|
+
* @deprecated No longer used. Slot reconciliation keys on worker liveness
|
|
15
|
+
* (the `_smrt_workers` lease), not per-job heartbeat staleness (#1474).
|
|
16
|
+
*/
|
|
17
|
+
staleJobThresholdMs?: number;
|
|
18
|
+
/**
|
|
19
|
+
* @deprecated No longer used. See {@link staleJobThresholdMs}.
|
|
20
|
+
*/
|
|
21
|
+
taskHeartbeatInterval?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* ScheduleRunner events
|
|
25
|
+
*/
|
|
26
|
+
export interface ScheduleRunnerEvents {
|
|
27
|
+
'schedule:triggered': (schedule: ScheduleInfo) => void;
|
|
28
|
+
'schedule:error': (schedule: ScheduleInfo, error: Error) => void;
|
|
29
|
+
'schedule:completed': (scheduleId: string) => void;
|
|
30
|
+
'schedule:failed': (scheduleId: string, error: string) => void;
|
|
31
|
+
'runner:started': () => void;
|
|
32
|
+
'runner:stopped': () => void;
|
|
33
|
+
'runner:error': (error: Error) => void;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Schedule info for events
|
|
37
|
+
*/
|
|
38
|
+
export interface ScheduleInfo {
|
|
39
|
+
id: string;
|
|
40
|
+
agentType: string;
|
|
41
|
+
agentId: string | null;
|
|
42
|
+
cron: string;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* ScheduleRunner polls for due agent schedules and creates jobs for them
|
|
46
|
+
*
|
|
47
|
+
* This runner works in conjunction with TaskRunner:
|
|
48
|
+
* 1. ScheduleRunner checks for due schedules based on cron expressions
|
|
49
|
+
* 2. When a schedule is due, it creates a SmrtJob for the agent
|
|
50
|
+
* 3. TaskRunner picks up and executes the job
|
|
51
|
+
* 4. On job completion/failure, call handleJobCompletion() to update the schedule
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* const scheduleRunner = new ScheduleRunner({ pollInterval: 30000 });
|
|
56
|
+
* await scheduleRunner.initialize(db);
|
|
57
|
+
* await scheduleRunner.start();
|
|
58
|
+
*
|
|
59
|
+
* // Wire up TaskRunner events to update schedule state
|
|
60
|
+
* taskRunner.on('job:completed', (job) => {
|
|
61
|
+
* const scheduleId = job.args?._scheduleId;
|
|
62
|
+
* if (scheduleId) scheduleRunner.handleJobCompletion(scheduleId, true);
|
|
63
|
+
* });
|
|
64
|
+
* taskRunner.on('job:failed', (job, error) => {
|
|
65
|
+
* const scheduleId = job.args?._scheduleId;
|
|
66
|
+
* if (scheduleId) scheduleRunner.handleJobCompletion(scheduleId, false, error.message);
|
|
67
|
+
* });
|
|
68
|
+
*
|
|
69
|
+
* // Graceful shutdown
|
|
70
|
+
* process.on('SIGTERM', () => scheduleRunner.stop());
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
export declare class ScheduleRunner extends EventEmitter {
|
|
74
|
+
readonly id: string;
|
|
75
|
+
private readonly config;
|
|
76
|
+
private jobCollection;
|
|
77
|
+
private workerCollection;
|
|
78
|
+
private running;
|
|
79
|
+
private pollTimer;
|
|
80
|
+
private db;
|
|
81
|
+
private logger;
|
|
82
|
+
constructor(config?: ScheduleRunnerConfig);
|
|
83
|
+
/**
|
|
84
|
+
* Initialize the runner with database connection
|
|
85
|
+
*/
|
|
86
|
+
initialize(db: DatabaseInterface): Promise<void>;
|
|
87
|
+
/**
|
|
88
|
+
* Start processing schedules
|
|
89
|
+
*/
|
|
90
|
+
start(): Promise<void>;
|
|
91
|
+
/**
|
|
92
|
+
* Stop processing schedules
|
|
93
|
+
*/
|
|
94
|
+
stop(): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* Check if runner is running
|
|
97
|
+
*/
|
|
98
|
+
isRunning(): boolean;
|
|
99
|
+
/**
|
|
100
|
+
* Handle job completion for a scheduled job.
|
|
101
|
+
*
|
|
102
|
+
* Call this from TaskRunner's job:completed / job:failed events
|
|
103
|
+
* when the job has a `_scheduleId` in its args.
|
|
104
|
+
*/
|
|
105
|
+
handleJobCompletion(scheduleId: string, success: boolean, errorMessage?: string): Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* Start the polling loop
|
|
108
|
+
*/
|
|
109
|
+
private startPolling;
|
|
110
|
+
/**
|
|
111
|
+
* Poll for due schedules and create jobs
|
|
112
|
+
*/
|
|
113
|
+
private poll;
|
|
114
|
+
/**
|
|
115
|
+
* Reconcile stuck schedule slots against running jobs.
|
|
116
|
+
*
|
|
117
|
+
* This handles two failure modes:
|
|
118
|
+
* - a running job's owning worker is no longer alive (dead/restarted)
|
|
119
|
+
* - a schedule slot remains occupied even though no running job still exists
|
|
120
|
+
*
|
|
121
|
+
* Staleness keys on worker *liveness* (issue #1474), not per-job heartbeat
|
|
122
|
+
* freshness: a job whose `worker_id` is live in this process or holds a fresh
|
|
123
|
+
* lease in `_smrt_workers` is healthy even if its handler is holding the loop
|
|
124
|
+
* synchronously. ScheduleRunner has no in-process active-job set, so this is
|
|
125
|
+
* its entire correctness mechanism.
|
|
126
|
+
*/
|
|
127
|
+
private recoverStaleScheduleState;
|
|
128
|
+
private getScheduleIdFromJobArgs;
|
|
129
|
+
/**
|
|
130
|
+
* Trigger a schedule by creating a job
|
|
131
|
+
*/
|
|
132
|
+
private triggerSchedule;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Validate a standard 5-field cron expression: field count plus per-field
|
|
136
|
+
* value ranges. Throws a descriptive `Error` on the first invalid field.
|
|
137
|
+
*
|
|
138
|
+
* Exposed so callers (and the agents package, which owns schedule creation)
|
|
139
|
+
* can reject a bad cron at write time rather than letting an out-of-range
|
|
140
|
+
* field silently never match (S5 audit #1402).
|
|
141
|
+
*
|
|
142
|
+
* @param cron - The cron expression to validate.
|
|
143
|
+
* @returns The trimmed, whitespace-split fields when valid.
|
|
144
|
+
*/
|
|
145
|
+
export declare function validateCronExpression(cron: string): string[];
|
|
146
|
+
/**
|
|
147
|
+
* Create a ScheduleRunner instance
|
|
148
|
+
*/
|
|
149
|
+
export declare function createScheduleRunner(config?: ScheduleRunnerConfig): ScheduleRunner;
|
|
150
|
+
export default ScheduleRunner;
|
|
151
|
+
//# sourceMappingURL=schedule-runner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schedule-runner.d.ts","sourceRoot":"","sources":["../src/schedule-runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAW5D;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,iDAAiD;IACjD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,mEAAmE;IACnE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;OAEG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,oBAAoB,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC;IACvD,gBAAgB,EAAE,CAAC,QAAQ,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;IACjE,oBAAoB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,iBAAiB,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/D,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,cAAc,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC;CACxC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;CACd;AAaD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,qBAAa,cAAe,SAAQ,YAAY;IAC9C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAiC;IACxD,OAAO,CAAC,aAAa,CAAkC;IACvD,OAAO,CAAC,gBAAgB,CAAqC;IAC7D,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAA+B;IAChD,OAAO,CAAC,EAAE,CAAkC;IAC5C,OAAO,CAAC,MAAM,CAAsB;gBAExB,MAAM,GAAE,oBAAyB;IAU7C;;OAEG;IACG,UAAU,CAAC,EAAE,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMtD;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAiB5B;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAc3B;;OAEG;IACH,SAAS,IAAI,OAAO;IAIpB;;;;;OAKG;IACG,mBAAmB,CACvB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,IAAI,CAAC;IAgDhB;;OAEG;IACH,OAAO,CAAC,YAAY;IAqBpB;;OAEG;YACW,IAAI;IAyBlB;;;;;;;;;;;;OAYG;YACW,yBAAyB;IA4IvC,OAAO,CAAC,wBAAwB;IA0BhC;;OAEG;YACW,eAAe;CA0G9B;AAwGD;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAa7D;AAwGD;;GAEG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,CAAC,EAAE,oBAAoB,GAC5B,cAAc,CAEhB;AAED,eAAe,cAAc,CAAC"}
|