cooper-stack 0.1.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/dist/ai.d.ts +60 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +66 -0
- package/dist/ai.js.map +1 -0
- package/dist/api.d.ts +31 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +40 -0
- package/dist/api.js.map +1 -0
- package/dist/auth.d.ts +13 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +16 -0
- package/dist/auth.js.map +1 -0
- package/dist/bridge.d.ts +15 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.js +217 -0
- package/dist/bridge.js.map +1 -0
- package/dist/cache.d.ts +27 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +69 -0
- package/dist/cache.js.map +1 -0
- package/dist/cron.d.ts +22 -0
- package/dist/cron.d.ts.map +1 -0
- package/dist/cron.js +26 -0
- package/dist/cron.js.map +1 -0
- package/dist/db.d.ts +46 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +158 -0
- package/dist/db.js.map +1 -0
- package/dist/error.d.ts +18 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +30 -0
- package/dist/error.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/islands.d.ts +16 -0
- package/dist/islands.d.ts.map +1 -0
- package/dist/islands.js +23 -0
- package/dist/islands.js.map +1 -0
- package/dist/middleware.d.ts +20 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +26 -0
- package/dist/middleware.js.map +1 -0
- package/dist/nats.d.ts +46 -0
- package/dist/nats.d.ts.map +1 -0
- package/dist/nats.js +157 -0
- package/dist/nats.js.map +1 -0
- package/dist/pubsub.d.ts +27 -0
- package/dist/pubsub.d.ts.map +1 -0
- package/dist/pubsub.js +152 -0
- package/dist/pubsub.js.map +1 -0
- package/dist/queue.d.ts +39 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +298 -0
- package/dist/queue.js.map +1 -0
- package/dist/rateLimit.d.ts +29 -0
- package/dist/rateLimit.d.ts.map +1 -0
- package/dist/rateLimit.js +70 -0
- package/dist/rateLimit.js.map +1 -0
- package/dist/registry.d.ts +75 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +41 -0
- package/dist/registry.js.map +1 -0
- package/dist/secrets.d.ts +10 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +35 -0
- package/dist/secrets.js.map +1 -0
- package/dist/ssr.d.ts +53 -0
- package/dist/ssr.d.ts.map +1 -0
- package/dist/ssr.js +39 -0
- package/dist/ssr.js.map +1 -0
- package/dist/storage.d.ts +28 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +61 -0
- package/dist/storage.js.map +1 -0
- package/package.json +40 -0
- package/src/ai.ts +99 -0
- package/src/api.ts +56 -0
- package/src/auth.ts +16 -0
- package/src/bridge.ts +267 -0
- package/src/cache.ts +86 -0
- package/src/cron.ts +32 -0
- package/src/db.ts +211 -0
- package/src/error.ts +44 -0
- package/src/index.ts +17 -0
- package/src/islands.ts +28 -0
- package/src/middleware.ts +27 -0
- package/src/nats.ts +186 -0
- package/src/pubsub.ts +208 -0
- package/src/queue.ts +414 -0
- package/src/rateLimit.ts +89 -0
- package/src/registry.ts +98 -0
- package/src/secrets.ts +40 -0
- package/src/ssr.ts +58 -0
- package/src/storage.ts +79 -0
- package/tsconfig.json +17 -0
package/dist/nats.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NATS Connection Manager — singleton lazy connection to embedded NATS.
|
|
3
|
+
*
|
|
4
|
+
* JetStream is used for durable pub/sub with delivery guarantees.
|
|
5
|
+
* Falls back gracefully if NATS is unavailable (logs warning once).
|
|
6
|
+
*/
|
|
7
|
+
import { connect, JSONCodec, RetentionPolicy, StorageType, } from "nats";
|
|
8
|
+
let nc = null;
|
|
9
|
+
let js = null;
|
|
10
|
+
let jsm = null;
|
|
11
|
+
let connectPromise = null;
|
|
12
|
+
let warnedOnce = false;
|
|
13
|
+
const jc = JSONCodec();
|
|
14
|
+
function getNatsUrl() {
|
|
15
|
+
return process.env.COOPER_NATS_URL ?? "nats://localhost:4222";
|
|
16
|
+
}
|
|
17
|
+
async function doConnect() {
|
|
18
|
+
try {
|
|
19
|
+
nc = await connect({ servers: getNatsUrl(), maxReconnectAttempts: 5 });
|
|
20
|
+
js = nc.jetstream();
|
|
21
|
+
jsm = await nc.jetstreamManager();
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
if (!warnedOnce) {
|
|
26
|
+
console.warn(`[cooper] NATS unavailable at ${getNatsUrl()} — pub/sub will use in-memory fallback. ${err.message}`);
|
|
27
|
+
warnedOnce = true;
|
|
28
|
+
}
|
|
29
|
+
nc = null;
|
|
30
|
+
js = null;
|
|
31
|
+
jsm = null;
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export async function ensureConnected() {
|
|
36
|
+
if (nc && !nc.isClosed())
|
|
37
|
+
return true;
|
|
38
|
+
if (connectPromise)
|
|
39
|
+
return connectPromise;
|
|
40
|
+
connectPromise = doConnect().finally(() => {
|
|
41
|
+
connectPromise = null;
|
|
42
|
+
});
|
|
43
|
+
return connectPromise;
|
|
44
|
+
}
|
|
45
|
+
export function getJetStream() {
|
|
46
|
+
return js;
|
|
47
|
+
}
|
|
48
|
+
export function getJetStreamManager() {
|
|
49
|
+
return jsm;
|
|
50
|
+
}
|
|
51
|
+
export function getConnection() {
|
|
52
|
+
return nc;
|
|
53
|
+
}
|
|
54
|
+
export { jc as jsonCodec };
|
|
55
|
+
/**
|
|
56
|
+
* Sanitize a topic name into a valid NATS stream name.
|
|
57
|
+
* NATS streams: alphanumeric + dash + underscore only.
|
|
58
|
+
*/
|
|
59
|
+
export function streamName(topicName) {
|
|
60
|
+
return "COOPER_" + topicName.replace(/[^a-zA-Z0-9_-]/g, "_").toUpperCase();
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Sanitize a subscriber name into a valid NATS durable consumer name.
|
|
64
|
+
*/
|
|
65
|
+
export function consumerName(subscriberName) {
|
|
66
|
+
return subscriberName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Ensure a JetStream stream exists for a topic.
|
|
70
|
+
* Creates it if missing, no-ops if it already exists.
|
|
71
|
+
*/
|
|
72
|
+
export async function ensureStream(topicName, config) {
|
|
73
|
+
if (!jsm)
|
|
74
|
+
return;
|
|
75
|
+
const name = streamName(topicName);
|
|
76
|
+
const subject = `cooper.topic.${topicName}`;
|
|
77
|
+
try {
|
|
78
|
+
await jsm.streams.info(name);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
await jsm.streams.add({
|
|
82
|
+
name,
|
|
83
|
+
subjects: [subject],
|
|
84
|
+
retention: RetentionPolicy.Interest,
|
|
85
|
+
max_msgs: -1,
|
|
86
|
+
max_bytes: -1,
|
|
87
|
+
max_age: 7 * 24 * 60 * 60 * 1_000_000_000, // 7 days in nanos
|
|
88
|
+
storage: StorageType.File,
|
|
89
|
+
num_replicas: 1,
|
|
90
|
+
duplicate_window: config?.dedup
|
|
91
|
+
? 2 * 60 * 1_000_000_000 // 2 min dedup window
|
|
92
|
+
: 0,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Ensure a JetStream stream exists for a job queue.
|
|
98
|
+
* Uses WorkQueue retention — each message consumed by exactly one worker.
|
|
99
|
+
*/
|
|
100
|
+
export async function ensureQueueStream(queueName, config) {
|
|
101
|
+
if (!jsm)
|
|
102
|
+
return;
|
|
103
|
+
const name = "QUEUE_" + queueName.replace(/[^a-zA-Z0-9_-]/g, "_").toUpperCase();
|
|
104
|
+
const subject = `cooper.queue.${queueName}`;
|
|
105
|
+
try {
|
|
106
|
+
await jsm.streams.info(name);
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
await jsm.streams.add({
|
|
110
|
+
name,
|
|
111
|
+
subjects: [subject],
|
|
112
|
+
retention: RetentionPolicy.Workqueue,
|
|
113
|
+
max_msgs: -1,
|
|
114
|
+
max_bytes: -1,
|
|
115
|
+
max_age: 7 * 24 * 60 * 60 * 1_000_000_000, // 7 days in nanos
|
|
116
|
+
storage: StorageType.File,
|
|
117
|
+
num_replicas: 1,
|
|
118
|
+
duplicate_window: config?.dedup
|
|
119
|
+
? 5 * 60 * 1_000_000_000 // 5 min dedup window for queues
|
|
120
|
+
: 0,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Ensure a JetStream stream exists for a dead-letter queue.
|
|
126
|
+
* Uses Limits retention — messages stay until explicitly purged.
|
|
127
|
+
*/
|
|
128
|
+
export async function ensureDLQStream(dlqName) {
|
|
129
|
+
if (!jsm)
|
|
130
|
+
return;
|
|
131
|
+
const name = "DLQ_" + dlqName.replace(/[^a-zA-Z0-9_-]/g, "_").toUpperCase();
|
|
132
|
+
const subject = `cooper.dlq.${dlqName}`;
|
|
133
|
+
try {
|
|
134
|
+
await jsm.streams.info(name);
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
await jsm.streams.add({
|
|
138
|
+
name,
|
|
139
|
+
subjects: [subject],
|
|
140
|
+
retention: RetentionPolicy.Limits,
|
|
141
|
+
max_msgs: 10000,
|
|
142
|
+
max_bytes: -1,
|
|
143
|
+
max_age: 30 * 24 * 60 * 60 * 1_000_000_000, // 30 days
|
|
144
|
+
storage: StorageType.File,
|
|
145
|
+
num_replicas: 1,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Graceful shutdown — drain and close the connection.
|
|
151
|
+
*/
|
|
152
|
+
export async function closeNats() {
|
|
153
|
+
if (nc && !nc.isClosed()) {
|
|
154
|
+
await nc.drain();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=nats.js.map
|
package/dist/nats.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nats.js","sourceRoot":"","sources":["../src/nats.ts"],"names":[],"mappings":"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"}
|
package/dist/pubsub.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface TopicConfig {
|
|
2
|
+
deliveryGuarantee?: "at-least-once" | "exactly-once";
|
|
3
|
+
orderBy?: string;
|
|
4
|
+
}
|
|
5
|
+
export interface SubscribeConfig {
|
|
6
|
+
concurrency?: number;
|
|
7
|
+
handler: (data: any) => Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
export interface Topic<T> {
|
|
10
|
+
publish(data: T): Promise<void>;
|
|
11
|
+
subscribe(name: string, config: SubscribeConfig): any;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Declare a typed Pub/Sub topic.
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* export const UserSignedUp = topic<{ userId: string; email: string }>(
|
|
18
|
+
* "user-signed-up",
|
|
19
|
+
* { deliveryGuarantee: "at-least-once" }
|
|
20
|
+
* );
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* Local dev uses embedded NATS with JetStream for durable delivery.
|
|
24
|
+
* Falls back to in-memory if NATS is unavailable.
|
|
25
|
+
*/
|
|
26
|
+
export declare function topic<T = any>(name: string, config?: TopicConfig): Topic<T>;
|
|
27
|
+
//# sourceMappingURL=pubsub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pubsub.d.ts","sourceRoot":"","sources":["../src/pubsub.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,WAAW;IAC1B,iBAAiB,CAAC,EAAE,eAAe,GAAG,cAAc,CAAC;IACrD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,KAAK,CAAC,CAAC;IACtB,OAAO,CAAC,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,GAAG,GAAG,CAAC;CACvD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,KAAK,CAAC,CAAC,GAAG,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,KAAK,CAAC,CAAC,CAAC,CA2I3E"}
|
package/dist/pubsub.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { registry } from "./registry.js";
|
|
2
|
+
import { ensureConnected, getJetStream, getJetStreamManager, jsonCodec, streamName, consumerName, ensureStream, } from "./nats.js";
|
|
3
|
+
import { headers as natsHeaders, AckPolicy } from "nats";
|
|
4
|
+
/**
|
|
5
|
+
* Declare a typed Pub/Sub topic.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* export const UserSignedUp = topic<{ userId: string; email: string }>(
|
|
9
|
+
* "user-signed-up",
|
|
10
|
+
* { deliveryGuarantee: "at-least-once" }
|
|
11
|
+
* );
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Local dev uses embedded NATS with JetStream for durable delivery.
|
|
15
|
+
* Falls back to in-memory if NATS is unavailable.
|
|
16
|
+
*/
|
|
17
|
+
export function topic(name, config) {
|
|
18
|
+
const subscribers = new Map();
|
|
19
|
+
const useDedup = config?.deliveryGuarantee === "exactly-once";
|
|
20
|
+
const subject = `cooper.topic.${name}`;
|
|
21
|
+
// Track whether JetStream consumers have been started
|
|
22
|
+
const activeConsumers = new Set();
|
|
23
|
+
/**
|
|
24
|
+
* Start a JetStream pull consumer for a subscriber.
|
|
25
|
+
* Runs in the background, processing messages until the connection closes.
|
|
26
|
+
*/
|
|
27
|
+
async function startConsumer(subName, handler, concurrency) {
|
|
28
|
+
if (activeConsumers.has(subName))
|
|
29
|
+
return;
|
|
30
|
+
activeConsumers.add(subName);
|
|
31
|
+
const js = getJetStream();
|
|
32
|
+
const jsm = getJetStreamManager();
|
|
33
|
+
if (!js || !jsm)
|
|
34
|
+
return;
|
|
35
|
+
const stream = streamName(name);
|
|
36
|
+
const durable = consumerName(subName);
|
|
37
|
+
// Ensure consumer exists
|
|
38
|
+
try {
|
|
39
|
+
await jsm.consumers.info(stream, durable);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
await jsm.consumers.add(stream, {
|
|
43
|
+
durable_name: durable,
|
|
44
|
+
ack_policy: AckPolicy.Explicit,
|
|
45
|
+
max_ack_pending: concurrency,
|
|
46
|
+
filter_subject: subject,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const consumer = await js.consumers.get(stream, durable);
|
|
50
|
+
// Process messages in the background
|
|
51
|
+
(async () => {
|
|
52
|
+
try {
|
|
53
|
+
const messages = await consumer.consume();
|
|
54
|
+
for await (const msg of messages) {
|
|
55
|
+
try {
|
|
56
|
+
const data = jsonCodec.decode(msg.data);
|
|
57
|
+
await handler(data);
|
|
58
|
+
msg.ack();
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
console.error(`[cooper] Subscriber "${subName}" on topic "${name}" failed:`, err);
|
|
62
|
+
// NAK with delay for retry (5 second backoff)
|
|
63
|
+
msg.nak(5000);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
// Consumer iteration ended (connection closed, etc.)
|
|
69
|
+
activeConsumers.delete(subName);
|
|
70
|
+
if (!err.message?.includes("closed")) {
|
|
71
|
+
console.error(`[cooper] Consumer "${subName}" on topic "${name}" stopped:`, err);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
})();
|
|
75
|
+
}
|
|
76
|
+
const t = {
|
|
77
|
+
async publish(data) {
|
|
78
|
+
const connected = await ensureConnected();
|
|
79
|
+
if (connected) {
|
|
80
|
+
const js = getJetStream();
|
|
81
|
+
if (js) {
|
|
82
|
+
await ensureStream(name, { dedup: useDedup });
|
|
83
|
+
const headers = useDedup && data && typeof data === "object"
|
|
84
|
+
? createDedup(data, config?.orderBy)
|
|
85
|
+
: undefined;
|
|
86
|
+
await js.publish(subject, jsonCodec.encode(data), { headers });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Fallback: in-memory delivery
|
|
91
|
+
for (const [subName, sub] of subscribers) {
|
|
92
|
+
try {
|
|
93
|
+
await sub.handler(data);
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
console.error(`[cooper] Subscriber "${subName}" failed:`, err);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
subscribe(subName, subConfig) {
|
|
101
|
+
subscribers.set(subName, {
|
|
102
|
+
handler: subConfig.handler,
|
|
103
|
+
options: subConfig,
|
|
104
|
+
});
|
|
105
|
+
registry.registerTopic(name, {
|
|
106
|
+
name,
|
|
107
|
+
subscribers,
|
|
108
|
+
});
|
|
109
|
+
// Start JetStream consumer in the background
|
|
110
|
+
const concurrency = subConfig.concurrency ?? 1;
|
|
111
|
+
ensureConnected().then(async (connected) => {
|
|
112
|
+
if (connected) {
|
|
113
|
+
await ensureStream(name, { dedup: useDedup });
|
|
114
|
+
startConsumer(subName, subConfig.handler, concurrency).catch((err) => {
|
|
115
|
+
console.error(`[cooper] Failed to start consumer "${subName}":`, err);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
return {
|
|
120
|
+
_cooper_type: "subscription",
|
|
121
|
+
topic: name,
|
|
122
|
+
name: subName,
|
|
123
|
+
};
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
registry.registerTopic(name, { name, subscribers });
|
|
127
|
+
return t;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Create NATS headers for exactly-once dedup.
|
|
131
|
+
* Uses Nats-Msg-Id header — JetStream deduplicates within the stream's
|
|
132
|
+
* duplicate_window based on this ID.
|
|
133
|
+
*/
|
|
134
|
+
function createDedup(data, orderBy) {
|
|
135
|
+
const h = natsHeaders();
|
|
136
|
+
// Generate a deterministic dedup ID.
|
|
137
|
+
// If an ordering key is set, use that field's value as the dedup key.
|
|
138
|
+
// Otherwise, hash the entire payload for content-based dedup.
|
|
139
|
+
if (orderBy && data[orderBy] !== undefined) {
|
|
140
|
+
h.set("Nats-Msg-Id", `${orderBy}-${String(data[orderBy])}`);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
const payload = JSON.stringify(data);
|
|
144
|
+
let hash = 0;
|
|
145
|
+
for (let i = 0; i < payload.length; i++) {
|
|
146
|
+
hash = ((hash << 5) - hash + payload.charCodeAt(i)) | 0;
|
|
147
|
+
}
|
|
148
|
+
h.set("Nats-Msg-Id", `msg-${Math.abs(hash)}`);
|
|
149
|
+
}
|
|
150
|
+
return h;
|
|
151
|
+
}
|
|
152
|
+
//# sourceMappingURL=pubsub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pubsub.js","sourceRoot":"","sources":["../src/pubsub.ts"],"names":[],"mappings":"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"}
|
package/dist/queue.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export interface QueueConfig {
|
|
2
|
+
concurrency?: number;
|
|
3
|
+
retries?: number;
|
|
4
|
+
retryDelay?: "fixed" | "exponential";
|
|
5
|
+
timeout?: string;
|
|
6
|
+
deadLetter?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface EnqueueOptions {
|
|
9
|
+
delay?: string;
|
|
10
|
+
priority?: "low" | "normal" | "high";
|
|
11
|
+
dedupeKey?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface QueueClient<T> {
|
|
14
|
+
enqueue(data: T, opts?: EnqueueOptions): Promise<void>;
|
|
15
|
+
worker(name: string, config: {
|
|
16
|
+
handler: (data: T) => Promise<void>;
|
|
17
|
+
onFailure?: (data: T, error: Error) => Promise<void>;
|
|
18
|
+
}): any;
|
|
19
|
+
list(): Promise<{
|
|
20
|
+
id: string;
|
|
21
|
+
data: T;
|
|
22
|
+
}[]>;
|
|
23
|
+
delete(id: string): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Declare a job queue.
|
|
27
|
+
*
|
|
28
|
+
* ```ts
|
|
29
|
+
* export const EmailQueue = queue<{ to: string; subject: string; body: string }>(
|
|
30
|
+
* "email-queue",
|
|
31
|
+
* { concurrency: 10, retries: 3, retryDelay: "exponential", timeout: "30s", deadLetter: "email-dlq" }
|
|
32
|
+
* );
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* Local dev uses NATS JetStream for persistent, durable queues.
|
|
36
|
+
* Falls back to in-memory if NATS is unavailable.
|
|
37
|
+
*/
|
|
38
|
+
export declare function queue<T = any>(name: string, config?: QueueConfig): QueueClient<T>;
|
|
39
|
+
//# sourceMappingURL=queue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"queue.d.ts","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,GAAG,aAAa,CAAC;IACrC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,OAAO,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,MAAM,CACJ,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE;QACN,OAAO,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;QACpC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;KACtD,GACA,GAAG,CAAC;IACP,IAAI,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,CAAC,CAAA;KAAE,EAAE,CAAC,CAAC;IAC3C,MAAM,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC;AAcD;;;;;;;;;;;;GAYG;AACH,wBAAgB,KAAK,CAAC,CAAC,GAAG,GAAG,EAC3B,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,WAAW,GACnB,WAAW,CAAC,CAAC,CAAC,CAmUhB"}
|