@checkstack/queue-api 0.0.2
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/CHANGELOG.md +88 -0
- package/package.json +19 -0
- package/src/index.ts +2 -0
- package/src/queue-plugin.ts +104 -0
- package/src/queue.ts +160 -0
- package/tsconfig.json +6 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# @checkstack/queue-api
|
|
2
|
+
|
|
3
|
+
## 0.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- d20d274: Initial release of all @checkstack packages. Rebranded from Checkmate to Checkstack with new npm organization @checkstack and domain checkstack.dev.
|
|
8
|
+
- Updated dependencies [d20d274]
|
|
9
|
+
- @checkstack/backend-api@0.0.2
|
|
10
|
+
|
|
11
|
+
## 1.0.1
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [b4eb432]
|
|
16
|
+
- Updated dependencies [a65e002]
|
|
17
|
+
- @checkstack/backend-api@1.1.0
|
|
18
|
+
|
|
19
|
+
## 1.0.0
|
|
20
|
+
|
|
21
|
+
### Major Changes
|
|
22
|
+
|
|
23
|
+
- 8e889b4: Add consumer group support to Queue API for distributed event system. BREAKING: consume() now requires ConsumeOptions with consumerGroup parameter.
|
|
24
|
+
|
|
25
|
+
### Minor Changes
|
|
26
|
+
|
|
27
|
+
- e4d83fc: Add BullMQ queue plugin with orphaned job cleanup
|
|
28
|
+
|
|
29
|
+
- **queue-api**: Added `listRecurringJobs()` method to Queue interface for detecting orphaned jobs
|
|
30
|
+
- **queue-bullmq-backend**: New plugin implementing BullMQ (Redis) queue backend with job schedulers, consumer groups, and distributed job persistence
|
|
31
|
+
- **queue-bullmq-common**: New common package with queue permissions
|
|
32
|
+
- **queue-memory-backend**: Implemented `listRecurringJobs()` for in-memory queue
|
|
33
|
+
- **healthcheck-backend**: Enhanced `bootstrapHealthChecks` to clean up orphaned job schedulers using `listRecurringJobs()`
|
|
34
|
+
- **test-utils-backend**: Added `listRecurringJobs()` to mock queue factory
|
|
35
|
+
|
|
36
|
+
This enables production-ready distributed queue processing with Redis persistence and automatic cleanup of orphaned jobs when health checks are deleted.
|
|
37
|
+
|
|
38
|
+
### Patch Changes
|
|
39
|
+
|
|
40
|
+
- 81f3f85: ## Breaking: Unified Versioned<T> Architecture
|
|
41
|
+
|
|
42
|
+
Refactored the versioning system to use a unified `Versioned<T>` class instead of separate `VersionedSchema`, `VersionedData`, and `VersionedConfig` types.
|
|
43
|
+
|
|
44
|
+
### Breaking Changes
|
|
45
|
+
|
|
46
|
+
- **`VersionedSchema<T>`** is replaced by `Versioned<T>` class
|
|
47
|
+
- **`VersionedData<T>`** is replaced by `VersionedRecord<T>` interface
|
|
48
|
+
- **`VersionedConfig<T>`** is replaced by `VersionedPluginRecord<T>` interface
|
|
49
|
+
- **`ConfigMigration<F, T>`** is replaced by `Migration<F, T>` interface
|
|
50
|
+
- **`MigrationChain<T>`** is removed (use `Migration<unknown, unknown>[]`)
|
|
51
|
+
- **`migrateVersionedData()`** is removed (use `versioned.parse()`)
|
|
52
|
+
- **`ConfigMigrationRunner`** is removed (migrations are internal to Versioned)
|
|
53
|
+
|
|
54
|
+
### Migration Guide
|
|
55
|
+
|
|
56
|
+
Before:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
const strategy: HealthCheckStrategy = {
|
|
60
|
+
config: {
|
|
61
|
+
version: 1,
|
|
62
|
+
schema: mySchema,
|
|
63
|
+
migrations: [],
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
const data = await migrateVersionedData(stored, 1, migrations);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
After:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const strategy: HealthCheckStrategy = {
|
|
73
|
+
config: new Versioned({
|
|
74
|
+
version: 1,
|
|
75
|
+
schema: mySchema,
|
|
76
|
+
migrations: [],
|
|
77
|
+
}),
|
|
78
|
+
};
|
|
79
|
+
const data = await strategy.config.parse(stored);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- Updated dependencies [ffc28f6]
|
|
83
|
+
- Updated dependencies [71275dd]
|
|
84
|
+
- Updated dependencies [ae19ff6]
|
|
85
|
+
- Updated dependencies [b55fae6]
|
|
86
|
+
- Updated dependencies [b354ab3]
|
|
87
|
+
- Updated dependencies [81f3f85]
|
|
88
|
+
- @checkstack/backend-api@1.0.0
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@checkstack/queue-api",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@checkstack/backend-api": "workspace:*",
|
|
8
|
+
"zod": "^4.0.0"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@checkstack/tsconfig": "workspace:*",
|
|
12
|
+
"@checkstack/scripts": "workspace:*"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
16
|
+
"lint": "bun run lint:code",
|
|
17
|
+
"lint:code": "eslint . --max-warnings 0"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import type { Queue } from "./queue";
|
|
3
|
+
import type { Migration } from "@checkstack/backend-api";
|
|
4
|
+
|
|
5
|
+
export interface QueuePlugin<Config = unknown> {
|
|
6
|
+
id: string;
|
|
7
|
+
displayName: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
|
|
10
|
+
/** Current version of the configuration schema */
|
|
11
|
+
configVersion: number;
|
|
12
|
+
|
|
13
|
+
/** Validation schema for the plugin-specific config */
|
|
14
|
+
configSchema: z.ZodType<Config>;
|
|
15
|
+
|
|
16
|
+
/** Optional migrations for backward compatibility */
|
|
17
|
+
migrations?: Migration<unknown, unknown>[];
|
|
18
|
+
|
|
19
|
+
createQueue<T>(name: string, config: Config): Queue<T>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface QueuePluginRegistry {
|
|
23
|
+
register(plugin: QueuePlugin<unknown>): void;
|
|
24
|
+
getPlugin(id: string): QueuePlugin<unknown> | undefined;
|
|
25
|
+
getPlugins(): QueuePlugin<unknown>[];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Result of switching queue backends
|
|
30
|
+
*/
|
|
31
|
+
export interface SwitchResult {
|
|
32
|
+
success: boolean;
|
|
33
|
+
migratedRecurringJobs: number;
|
|
34
|
+
warnings: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Info about a recurring job across all queues
|
|
39
|
+
*/
|
|
40
|
+
export interface RecurringJobInfo {
|
|
41
|
+
queueName: string;
|
|
42
|
+
jobId: string;
|
|
43
|
+
intervalSeconds: number;
|
|
44
|
+
nextRunAt?: Date;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* QueueManager handles queue creation, backend switching, and multi-instance coordination.
|
|
49
|
+
*
|
|
50
|
+
* Key features:
|
|
51
|
+
* - Returns stable QueueProxy instances that survive backend switches
|
|
52
|
+
* - Polls config for changes to support multi-instance coordination
|
|
53
|
+
* - Handles graceful migration of recurring jobs when switching backends
|
|
54
|
+
*/
|
|
55
|
+
export interface QueueManager {
|
|
56
|
+
/**
|
|
57
|
+
* Get or create a queue proxy.
|
|
58
|
+
* Returns a stable reference that survives backend switches.
|
|
59
|
+
* Subscriptions are automatically re-applied when backend changes.
|
|
60
|
+
*/
|
|
61
|
+
getQueue<T>(name: string): Queue<T>;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get the currently active queue plugin ID
|
|
65
|
+
*/
|
|
66
|
+
getActivePlugin(): string;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Get the currently active queue plugin configuration
|
|
70
|
+
*/
|
|
71
|
+
getActiveConfig(): unknown;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Switch to a different queue backend with job migration.
|
|
75
|
+
*
|
|
76
|
+
* @throws Error if connection test fails
|
|
77
|
+
* @throws Error if migration fails
|
|
78
|
+
*/
|
|
79
|
+
setActiveBackend(pluginId: string, config: unknown): Promise<SwitchResult>;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Get total number of in-flight jobs across all queues.
|
|
83
|
+
* Used to warn users before switching backends.
|
|
84
|
+
*/
|
|
85
|
+
getInFlightJobCount(): Promise<number>;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* List all recurring jobs across all queues.
|
|
89
|
+
* Used for migration preview.
|
|
90
|
+
*/
|
|
91
|
+
listAllRecurringJobs(): Promise<RecurringJobInfo[]>;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Start polling for configuration changes.
|
|
95
|
+
* Required for multi-instance coordination.
|
|
96
|
+
* @param intervalMs - Polling interval in milliseconds (default: 5000)
|
|
97
|
+
*/
|
|
98
|
+
startPolling(intervalMs?: number): void;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Stop polling and gracefully shutdown all queues.
|
|
102
|
+
*/
|
|
103
|
+
shutdown(): Promise<void>;
|
|
104
|
+
}
|
package/src/queue.ts
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
export interface QueueJob<T = unknown> {
|
|
2
|
+
id: string;
|
|
3
|
+
data: T;
|
|
4
|
+
priority?: number;
|
|
5
|
+
timestamp: Date;
|
|
6
|
+
/**
|
|
7
|
+
* Number of processing attempts (for retry logic)
|
|
8
|
+
*/
|
|
9
|
+
attempts?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Details of a recurring job for migration purposes
|
|
14
|
+
*/
|
|
15
|
+
export interface RecurringJobDetails<T = unknown> {
|
|
16
|
+
jobId: string;
|
|
17
|
+
data: T;
|
|
18
|
+
intervalSeconds: number;
|
|
19
|
+
priority?: number;
|
|
20
|
+
nextRunAt?: Date;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface QueueConsumer<T = unknown> {
|
|
24
|
+
(job: QueueJob<T>): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ConsumeOptions {
|
|
28
|
+
/**
|
|
29
|
+
* Consumer group ID for load balancing.
|
|
30
|
+
*
|
|
31
|
+
* Messages are distributed across consumers in the same group (competing consumers).
|
|
32
|
+
* Each consumer group receives a copy of each message (broadcast).
|
|
33
|
+
*
|
|
34
|
+
* Example:
|
|
35
|
+
* - Same group ID = work queue (only one consumer per group gets the message)
|
|
36
|
+
* - Unique group ID per instance = broadcast (all instances get the message)
|
|
37
|
+
*/
|
|
38
|
+
consumerGroup: string;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Maximum retry attempts on failure
|
|
42
|
+
* @default 3
|
|
43
|
+
*/
|
|
44
|
+
maxRetries?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface Queue<T = unknown> {
|
|
48
|
+
/**
|
|
49
|
+
* Enqueue a job for processing
|
|
50
|
+
*/
|
|
51
|
+
enqueue(
|
|
52
|
+
data: T,
|
|
53
|
+
options?: {
|
|
54
|
+
priority?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Delay in seconds before the job becomes available for processing
|
|
57
|
+
* Queue backends should not make this job available until the delay expires
|
|
58
|
+
*/
|
|
59
|
+
startDelay?: number;
|
|
60
|
+
/**
|
|
61
|
+
* Optional unique job ID for deduplication
|
|
62
|
+
* If provided and a job with this ID already exists, behavior depends on queue implementation
|
|
63
|
+
*/
|
|
64
|
+
jobId?: string;
|
|
65
|
+
}
|
|
66
|
+
): Promise<string>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Register a consumer to process jobs
|
|
70
|
+
*
|
|
71
|
+
* BREAKING CHANGE: Now requires ConsumeOptions with consumerGroup
|
|
72
|
+
*/
|
|
73
|
+
consume(consumer: QueueConsumer<T>, options: ConsumeOptions): Promise<void>;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Schedule a recurring job that executes at regular intervals.
|
|
77
|
+
*
|
|
78
|
+
* UPDATE SEMANTICS: Calling this method with an existing jobId will UPDATE
|
|
79
|
+
* the recurring job configuration (interval, payload, priority, etc.).
|
|
80
|
+
* The queue implementation MUST:
|
|
81
|
+
* 1. Cancel any pending scheduled execution of the old job
|
|
82
|
+
* 2. Apply the new configuration
|
|
83
|
+
* 3. Schedule the next execution with the new startDelay (or immediately if not provided)
|
|
84
|
+
*
|
|
85
|
+
* @param data - Job payload
|
|
86
|
+
* @param options - Recurring job options
|
|
87
|
+
* @returns Job ID
|
|
88
|
+
*/
|
|
89
|
+
scheduleRecurring(
|
|
90
|
+
data: T,
|
|
91
|
+
options: {
|
|
92
|
+
/**
|
|
93
|
+
* Unique job ID for deduplication and updates.
|
|
94
|
+
* Calling scheduleRecurring with the same jobId updates the existing job.
|
|
95
|
+
*/
|
|
96
|
+
jobId: string;
|
|
97
|
+
/** Interval in seconds between executions */
|
|
98
|
+
intervalSeconds: number;
|
|
99
|
+
/** Optional delay before first execution (for delta-based scheduling) */
|
|
100
|
+
startDelay?: number;
|
|
101
|
+
/** Optional priority */
|
|
102
|
+
priority?: number;
|
|
103
|
+
}
|
|
104
|
+
): Promise<string>;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Cancel a recurring job
|
|
108
|
+
* @param jobId - Job ID to cancel
|
|
109
|
+
*/
|
|
110
|
+
cancelRecurring(jobId: string): Promise<void>;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* List all recurring job IDs
|
|
114
|
+
* Used for reconciliation to detect orphaned jobs
|
|
115
|
+
* @returns Array of job IDs for all recurring jobs
|
|
116
|
+
*/
|
|
117
|
+
listRecurringJobs(): Promise<string[]>;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get details of a recurring job for migration purposes
|
|
121
|
+
* @param jobId - Job ID
|
|
122
|
+
* @returns Job details or undefined if not found
|
|
123
|
+
*/
|
|
124
|
+
getRecurringJobDetails(
|
|
125
|
+
jobId: string
|
|
126
|
+
): Promise<RecurringJobDetails<T> | undefined>;
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get the number of jobs currently being processed
|
|
130
|
+
* Used to warn users before switching backends
|
|
131
|
+
*/
|
|
132
|
+
getInFlightCount(): Promise<number>;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Test connection to the queue backend
|
|
136
|
+
* @throws Error if connection fails
|
|
137
|
+
*/
|
|
138
|
+
testConnection(): Promise<void>;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Stop consuming jobs and cleanup
|
|
142
|
+
*/
|
|
143
|
+
stop(): Promise<void>;
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Get queue statistics
|
|
147
|
+
*/
|
|
148
|
+
getStats(): Promise<QueueStats>;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export interface QueueStats {
|
|
152
|
+
pending: number;
|
|
153
|
+
processing: number;
|
|
154
|
+
completed: number;
|
|
155
|
+
failed: number;
|
|
156
|
+
/**
|
|
157
|
+
* Number of active consumer groups
|
|
158
|
+
*/
|
|
159
|
+
consumerGroups: number;
|
|
160
|
+
}
|