@windrun-huaiin/backend-core 17.0.0 → 20.0.1
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/index.js +2 -0
- package/dist/index.mjs +1 -1
- package/dist/lib/index.js +2 -0
- package/dist/lib/index.mjs +1 -1
- package/dist/lib/upstash/qstash.d.ts +25 -0
- package/dist/lib/upstash/qstash.d.ts.map +1 -1
- package/dist/lib/upstash/qstash.js +74 -0
- package/dist/lib/upstash/qstash.mjs +73 -1
- package/dist/lib/upstash-config.d.ts.map +1 -1
- package/dist/lib/upstash-config.js +17 -5
- package/dist/lib/upstash-config.mjs +17 -5
- package/package.json +5 -5
- package/src/lib/upstash/qstash.ts +108 -0
- package/src/lib/upstash-config.ts +18 -6
package/dist/index.js
CHANGED
|
@@ -171,7 +171,9 @@ exports.smembers = redisStructures.smembers;
|
|
|
171
171
|
exports.srem = redisStructures.srem;
|
|
172
172
|
exports.ttl = redisStructures.ttl;
|
|
173
173
|
exports.cancelSchedule = qstash.cancelSchedule;
|
|
174
|
+
exports.publishBroadcastMessage = qstash.publishBroadcastMessage;
|
|
174
175
|
exports.publishDelayedMessage = qstash.publishDelayedMessage;
|
|
176
|
+
exports.publishFIFOQueueMessage = qstash.publishFIFOQueueMessage;
|
|
175
177
|
exports.publishMessage = qstash.publishMessage;
|
|
176
178
|
exports.scheduleMessage = qstash.scheduleMessage;
|
|
177
179
|
exports.verifyQstashSignature = qstash.verifyQstashSignature;
|
package/dist/index.mjs
CHANGED
|
@@ -32,4 +32,4 @@ export { getTargetLikeCount, getUserLikedTargets, isTargetLiked, likeTarget, unl
|
|
|
32
32
|
export { addFavorite, getFavoriteCount, getUserFavorites, isFavorited, removeFavorite } from './lib/upstash/redis-favorite.mjs';
|
|
33
33
|
export { getCounter, getUniqueCounter, incrCounter, incrUniqueCounter } from './lib/upstash/redis-counter.mjs';
|
|
34
34
|
export { del, deleteHashField, deleteKey, exists, expire, getHashAll, getHashField, getHashJson, getJson, getString, hexists, hkeys, hlen, hmget, hmset, listLength, mget, mgetJson, mset, msetJson, pipeline, popList, pushList, rangeList, sadd, scard, setHashField, setHashJson, setJson, setString, sismember, smembers, srem, ttl } from './lib/upstash/redis-structures.mjs';
|
|
35
|
-
export { cancelSchedule, publishDelayedMessage, publishMessage, scheduleMessage, verifyQstashSignature } from './lib/upstash/qstash.mjs';
|
|
35
|
+
export { cancelSchedule, publishBroadcastMessage, publishDelayedMessage, publishFIFOQueueMessage, publishMessage, scheduleMessage, verifyQstashSignature } from './lib/upstash/qstash.mjs';
|
package/dist/lib/index.js
CHANGED
|
@@ -91,7 +91,9 @@ exports.smembers = redisStructures.smembers;
|
|
|
91
91
|
exports.srem = redisStructures.srem;
|
|
92
92
|
exports.ttl = redisStructures.ttl;
|
|
93
93
|
exports.cancelSchedule = qstash.cancelSchedule;
|
|
94
|
+
exports.publishBroadcastMessage = qstash.publishBroadcastMessage;
|
|
94
95
|
exports.publishDelayedMessage = qstash.publishDelayedMessage;
|
|
96
|
+
exports.publishFIFOQueueMessage = qstash.publishFIFOQueueMessage;
|
|
95
97
|
exports.publishMessage = qstash.publishMessage;
|
|
96
98
|
exports.scheduleMessage = qstash.scheduleMessage;
|
|
97
99
|
exports.verifyQstashSignature = qstash.verifyQstashSignature;
|
package/dist/lib/index.mjs
CHANGED
|
@@ -9,4 +9,4 @@ export { getTargetLikeCount, getUserLikedTargets, isTargetLiked, likeTarget, unl
|
|
|
9
9
|
export { addFavorite, getFavoriteCount, getUserFavorites, isFavorited, removeFavorite } from './upstash/redis-favorite.mjs';
|
|
10
10
|
export { getCounter, getUniqueCounter, incrCounter, incrUniqueCounter } from './upstash/redis-counter.mjs';
|
|
11
11
|
export { del, deleteHashField, deleteKey, exists, expire, getHashAll, getHashField, getHashJson, getJson, getString, hexists, hkeys, hlen, hmget, hmset, listLength, mget, mgetJson, mset, msetJson, pipeline, popList, pushList, rangeList, sadd, scard, setHashField, setHashJson, setJson, setString, sismember, smembers, srem, ttl } from './upstash/redis-structures.mjs';
|
|
12
|
-
export { cancelSchedule, publishDelayedMessage, publishMessage, scheduleMessage, verifyQstashSignature } from './upstash/qstash.mjs';
|
|
12
|
+
export { cancelSchedule, publishBroadcastMessage, publishDelayedMessage, publishFIFOQueueMessage, publishMessage, scheduleMessage, verifyQstashSignature } from './upstash/qstash.mjs';
|
|
@@ -7,6 +7,15 @@ export interface PublishMessageOptions<TBody extends PublishBody = PublishBody>
|
|
|
7
7
|
url: string;
|
|
8
8
|
body: TBody;
|
|
9
9
|
}
|
|
10
|
+
export interface PublishBroadcastMessageOptions<TBody extends PublishBody = PublishBody> {
|
|
11
|
+
urlGroup: string;
|
|
12
|
+
body: TBody;
|
|
13
|
+
}
|
|
14
|
+
export interface PublishFIFOQueueMessageOptions<TBody extends PublishBody = PublishBody> {
|
|
15
|
+
queueName: string;
|
|
16
|
+
url: string;
|
|
17
|
+
body: TBody;
|
|
18
|
+
}
|
|
10
19
|
/**
|
|
11
20
|
* Publish a message. Returns message id or null if QStash is unavailable.
|
|
12
21
|
*/
|
|
@@ -14,6 +23,22 @@ export declare const publishMessage: <TBody extends PublishBody>(options: Publis
|
|
|
14
23
|
messageId: string | null;
|
|
15
24
|
message: QstashEnvelope<TBody>;
|
|
16
25
|
} | null>;
|
|
26
|
+
/**
|
|
27
|
+
* Publish a broadcast message to a QStash URL Group.
|
|
28
|
+
* Returns message ids or null if QStash is unavailable.
|
|
29
|
+
*/
|
|
30
|
+
export declare const publishBroadcastMessage: <TBody extends PublishBody>(options: PublishBroadcastMessageOptions<TBody>) => Promise<{
|
|
31
|
+
messageIds: string[];
|
|
32
|
+
message: QstashEnvelope<TBody>;
|
|
33
|
+
} | null>;
|
|
34
|
+
/**
|
|
35
|
+
* Publish a single-recipient message into a QStash FIFO queue.
|
|
36
|
+
* Returns message id or null if QStash is unavailable.
|
|
37
|
+
*/
|
|
38
|
+
export declare const publishFIFOQueueMessage: <TBody extends PublishBody>(options: PublishFIFOQueueMessageOptions<TBody>) => Promise<{
|
|
39
|
+
messageId: string | null;
|
|
40
|
+
message: QstashEnvelope<TBody>;
|
|
41
|
+
} | null>;
|
|
17
42
|
/**
|
|
18
43
|
* Publish a delayed message. Returns message id or null if QStash is unavailable.
|
|
19
44
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qstash.d.ts","sourceRoot":"","sources":["../../../src/lib/upstash/qstash.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"qstash.d.ts","sourceRoot":"","sources":["../../../src/lib/upstash/qstash.ts"],"names":[],"mappings":"AAgFA,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;AAErF,MAAM,WAAW,cAAc,CAAC,KAAK,SAAS,WAAW,GAAG,WAAW;IACrE,aAAa,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,KAAK,CAAC;CAChB;AAED,MAAM,WAAW,qBAAqB,CAAC,KAAK,SAAS,WAAW,GAAG,WAAW;IAC5E,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,KAAK,CAAC;CACb;AAED,MAAM,WAAW,8BAA8B,CAAC,KAAK,SAAS,WAAW,GAAG,WAAW;IACrF,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACb;AAED,MAAM,WAAW,8BAA8B,CAAC,KAAK,SAAS,WAAW,GAAG,WAAW;IACrF,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,KAAK,CAAC;CACb;AAiBD;;GAEG;AACH,eAAO,MAAM,cAAc,GAAU,KAAK,SAAS,WAAW,EAC5D,SAAS,qBAAqB,CAAC,KAAK,CAAC,KACpC,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC,CAAA;CAAE,GAAG,IAAI,CAa7E,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GAAU,KAAK,SAAS,WAAW,EACrE,SAAS,8BAA8B,CAAC,KAAK,CAAC,KAC7C,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC,CAAA;CAAE,GAAG,IAAI,CA0BzE,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,uBAAuB,GAAU,KAAK,SAAS,WAAW,EACrE,SAAS,8BAA8B,CAAC,KAAK,CAAC,KAC7C,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC,CAAA;CAAE,GAAG,IAAI,CAqB7E,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAU,KAAK,SAAS,WAAW,EACnE,SAAS,qBAAqB,CAAC,KAAK,CAAC,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,KAC3D,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC,CAAA;CAAE,GAAG,IAAI,CAc7E,CAAC;AAEF,MAAM,WAAW,sBAAsB,CAAC,KAAK,SAAS,WAAW,GAAG,WAAW,CAC7E,SAAQ,qBAAqB,CAAC,KAAK,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,GAAU,KAAK,SAAS,WAAW,EAC7D,SAAS,sBAAsB,CAAC,KAAK,CAAC,KACrC,OAAO,CAAC;IAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,EAAE,cAAc,CAAC,KAAK,CAAC,CAAA;CAAE,GAAG,IAAI,CAsB9E,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,cAAc,GAAU,YAAY,MAAM,KAAG,OAAO,CAAC,OAAO,CAexE,CAAC;AAEF,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAAU,SAAS,mBAAmB,KAAG,OAAO,CAAC,OAAO,CAqBzF,CAAC"}
|
|
@@ -7,8 +7,30 @@ var upstashConfig = require('../upstash-config.js');
|
|
|
7
7
|
let cachedReceiver = null;
|
|
8
8
|
let receiverWarnedMissingEnv = false;
|
|
9
9
|
let receiverWarnedInitError = false;
|
|
10
|
+
const isNonEmpty = (value) => typeof value === 'string' && value.trim().length > 0;
|
|
10
11
|
const isTruthy = (value) => value === '1' || value === 'true' || value === 'TRUE';
|
|
11
12
|
const shouldSkipVerify = () => process.env.NODE_ENV === 'development' && isTruthy(process.env.SKIP_UPSTASH_QSTASH_VERIFY);
|
|
13
|
+
const getRequiredAppName = () => {
|
|
14
|
+
const appName = process.env.NEXT_PUBLIC_APP_NAME;
|
|
15
|
+
if (!isNonEmpty(appName)) {
|
|
16
|
+
throw new Error('[Upstash QStash] NEXT_PUBLIC_APP_NAME is required for QStash naming and must not be empty');
|
|
17
|
+
}
|
|
18
|
+
const normalized = appName.replace(/\s+/g, '').toLowerCase();
|
|
19
|
+
if (!normalized) {
|
|
20
|
+
throw new Error('[Upstash QStash] NEXT_PUBLIC_APP_NAME must contain non-whitespace characters for QStash naming');
|
|
21
|
+
}
|
|
22
|
+
return normalized;
|
|
23
|
+
};
|
|
24
|
+
const getQstashNamePrefix = () => {
|
|
25
|
+
const envSuffix = process.env.NODE_ENV === 'production' ? 'live' : 'test';
|
|
26
|
+
return `${getRequiredAppName()}_${envSuffix}`;
|
|
27
|
+
};
|
|
28
|
+
const prefixQstashName = (resourceType, name) => {
|
|
29
|
+
if (!isNonEmpty(name)) {
|
|
30
|
+
throw new Error(`[Upstash QStash] ${resourceType} name must not be empty`);
|
|
31
|
+
}
|
|
32
|
+
return `${getQstashNamePrefix()}_${resourceType}_${name}`;
|
|
33
|
+
};
|
|
12
34
|
const getReceiver = () => {
|
|
13
35
|
if (cachedReceiver) {
|
|
14
36
|
return cachedReceiver;
|
|
@@ -69,6 +91,56 @@ const publishMessage = (options) => tslib.__awaiter(void 0, void 0, void 0, func
|
|
|
69
91
|
};
|
|
70
92
|
}));
|
|
71
93
|
});
|
|
94
|
+
/**
|
|
95
|
+
* Publish a broadcast message to a QStash URL Group.
|
|
96
|
+
* Returns message ids or null if QStash is unavailable.
|
|
97
|
+
*/
|
|
98
|
+
const publishBroadcastMessage = (options) => tslib.__awaiter(void 0, void 0, void 0, function* () {
|
|
99
|
+
const message = createEnvelope(options.body);
|
|
100
|
+
return upstashConfig.withQstash((client) => tslib.__awaiter(void 0, void 0, void 0, function* () {
|
|
101
|
+
const result = yield client.publishJSON({
|
|
102
|
+
urlGroup: options.urlGroup,
|
|
103
|
+
body: message,
|
|
104
|
+
});
|
|
105
|
+
const messageIds = Array.isArray(result)
|
|
106
|
+
? result
|
|
107
|
+
.map((item) => typeof item === 'string' ? item : typeof (item === null || item === void 0 ? void 0 : item.messageId) === 'string' ? item.messageId : null)
|
|
108
|
+
.filter((messageId) => typeof messageId === 'string')
|
|
109
|
+
: typeof result === 'string'
|
|
110
|
+
? [result]
|
|
111
|
+
: typeof (result === null || result === void 0 ? void 0 : result.messageId) === 'string'
|
|
112
|
+
? [result.messageId]
|
|
113
|
+
: [];
|
|
114
|
+
return {
|
|
115
|
+
messageIds,
|
|
116
|
+
message,
|
|
117
|
+
};
|
|
118
|
+
}));
|
|
119
|
+
});
|
|
120
|
+
/**
|
|
121
|
+
* Publish a single-recipient message into a QStash FIFO queue.
|
|
122
|
+
* Returns message id or null if QStash is unavailable.
|
|
123
|
+
*/
|
|
124
|
+
const publishFIFOQueueMessage = (options) => tslib.__awaiter(void 0, void 0, void 0, function* () {
|
|
125
|
+
const message = createEnvelope(options.body);
|
|
126
|
+
const queueName = prefixQstashName('queue', options.queueName);
|
|
127
|
+
return upstashConfig.withQstash((client) => tslib.__awaiter(void 0, void 0, void 0, function* () {
|
|
128
|
+
var _a, _b;
|
|
129
|
+
const anyClient = client;
|
|
130
|
+
const queueClient = (_a = anyClient.queue) === null || _a === void 0 ? void 0 : _a.call(anyClient, { queueName });
|
|
131
|
+
if (!(queueClient === null || queueClient === void 0 ? void 0 : queueClient.enqueueJSON)) {
|
|
132
|
+
throw new Error('QStash queue enqueueJSON API is unavailable');
|
|
133
|
+
}
|
|
134
|
+
const result = yield queueClient.enqueueJSON({
|
|
135
|
+
url: options.url,
|
|
136
|
+
body: message,
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
messageId: typeof result === 'string' ? result : (_b = result === null || result === void 0 ? void 0 : result.messageId) !== null && _b !== void 0 ? _b : null,
|
|
140
|
+
message,
|
|
141
|
+
};
|
|
142
|
+
}));
|
|
143
|
+
});
|
|
72
144
|
/**
|
|
73
145
|
* Publish a delayed message. Returns message id or null if QStash is unavailable.
|
|
74
146
|
*/
|
|
@@ -154,7 +226,9 @@ const verifyQstashSignature = (options) => tslib.__awaiter(void 0, void 0, void
|
|
|
154
226
|
});
|
|
155
227
|
|
|
156
228
|
exports.cancelSchedule = cancelSchedule;
|
|
229
|
+
exports.publishBroadcastMessage = publishBroadcastMessage;
|
|
157
230
|
exports.publishDelayedMessage = publishDelayedMessage;
|
|
231
|
+
exports.publishFIFOQueueMessage = publishFIFOQueueMessage;
|
|
158
232
|
exports.publishMessage = publishMessage;
|
|
159
233
|
exports.scheduleMessage = scheduleMessage;
|
|
160
234
|
exports.verifyQstashSignature = verifyQstashSignature;
|
|
@@ -5,8 +5,30 @@ import { withQstash } from '../upstash-config.mjs';
|
|
|
5
5
|
let cachedReceiver = null;
|
|
6
6
|
let receiverWarnedMissingEnv = false;
|
|
7
7
|
let receiverWarnedInitError = false;
|
|
8
|
+
const isNonEmpty = (value) => typeof value === 'string' && value.trim().length > 0;
|
|
8
9
|
const isTruthy = (value) => value === '1' || value === 'true' || value === 'TRUE';
|
|
9
10
|
const shouldSkipVerify = () => process.env.NODE_ENV === 'development' && isTruthy(process.env.SKIP_UPSTASH_QSTASH_VERIFY);
|
|
11
|
+
const getRequiredAppName = () => {
|
|
12
|
+
const appName = process.env.NEXT_PUBLIC_APP_NAME;
|
|
13
|
+
if (!isNonEmpty(appName)) {
|
|
14
|
+
throw new Error('[Upstash QStash] NEXT_PUBLIC_APP_NAME is required for QStash naming and must not be empty');
|
|
15
|
+
}
|
|
16
|
+
const normalized = appName.replace(/\s+/g, '').toLowerCase();
|
|
17
|
+
if (!normalized) {
|
|
18
|
+
throw new Error('[Upstash QStash] NEXT_PUBLIC_APP_NAME must contain non-whitespace characters for QStash naming');
|
|
19
|
+
}
|
|
20
|
+
return normalized;
|
|
21
|
+
};
|
|
22
|
+
const getQstashNamePrefix = () => {
|
|
23
|
+
const envSuffix = process.env.NODE_ENV === 'production' ? 'live' : 'test';
|
|
24
|
+
return `${getRequiredAppName()}_${envSuffix}`;
|
|
25
|
+
};
|
|
26
|
+
const prefixQstashName = (resourceType, name) => {
|
|
27
|
+
if (!isNonEmpty(name)) {
|
|
28
|
+
throw new Error(`[Upstash QStash] ${resourceType} name must not be empty`);
|
|
29
|
+
}
|
|
30
|
+
return `${getQstashNamePrefix()}_${resourceType}_${name}`;
|
|
31
|
+
};
|
|
10
32
|
const getReceiver = () => {
|
|
11
33
|
if (cachedReceiver) {
|
|
12
34
|
return cachedReceiver;
|
|
@@ -67,6 +89,56 @@ const publishMessage = (options) => __awaiter(void 0, void 0, void 0, function*
|
|
|
67
89
|
};
|
|
68
90
|
}));
|
|
69
91
|
});
|
|
92
|
+
/**
|
|
93
|
+
* Publish a broadcast message to a QStash URL Group.
|
|
94
|
+
* Returns message ids or null if QStash is unavailable.
|
|
95
|
+
*/
|
|
96
|
+
const publishBroadcastMessage = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
97
|
+
const message = createEnvelope(options.body);
|
|
98
|
+
return withQstash((client) => __awaiter(void 0, void 0, void 0, function* () {
|
|
99
|
+
const result = yield client.publishJSON({
|
|
100
|
+
urlGroup: options.urlGroup,
|
|
101
|
+
body: message,
|
|
102
|
+
});
|
|
103
|
+
const messageIds = Array.isArray(result)
|
|
104
|
+
? result
|
|
105
|
+
.map((item) => typeof item === 'string' ? item : typeof (item === null || item === void 0 ? void 0 : item.messageId) === 'string' ? item.messageId : null)
|
|
106
|
+
.filter((messageId) => typeof messageId === 'string')
|
|
107
|
+
: typeof result === 'string'
|
|
108
|
+
? [result]
|
|
109
|
+
: typeof (result === null || result === void 0 ? void 0 : result.messageId) === 'string'
|
|
110
|
+
? [result.messageId]
|
|
111
|
+
: [];
|
|
112
|
+
return {
|
|
113
|
+
messageIds,
|
|
114
|
+
message,
|
|
115
|
+
};
|
|
116
|
+
}));
|
|
117
|
+
});
|
|
118
|
+
/**
|
|
119
|
+
* Publish a single-recipient message into a QStash FIFO queue.
|
|
120
|
+
* Returns message id or null if QStash is unavailable.
|
|
121
|
+
*/
|
|
122
|
+
const publishFIFOQueueMessage = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
123
|
+
const message = createEnvelope(options.body);
|
|
124
|
+
const queueName = prefixQstashName('queue', options.queueName);
|
|
125
|
+
return withQstash((client) => __awaiter(void 0, void 0, void 0, function* () {
|
|
126
|
+
var _a, _b;
|
|
127
|
+
const anyClient = client;
|
|
128
|
+
const queueClient = (_a = anyClient.queue) === null || _a === void 0 ? void 0 : _a.call(anyClient, { queueName });
|
|
129
|
+
if (!(queueClient === null || queueClient === void 0 ? void 0 : queueClient.enqueueJSON)) {
|
|
130
|
+
throw new Error('QStash queue enqueueJSON API is unavailable');
|
|
131
|
+
}
|
|
132
|
+
const result = yield queueClient.enqueueJSON({
|
|
133
|
+
url: options.url,
|
|
134
|
+
body: message,
|
|
135
|
+
});
|
|
136
|
+
return {
|
|
137
|
+
messageId: typeof result === 'string' ? result : (_b = result === null || result === void 0 ? void 0 : result.messageId) !== null && _b !== void 0 ? _b : null,
|
|
138
|
+
message,
|
|
139
|
+
};
|
|
140
|
+
}));
|
|
141
|
+
});
|
|
70
142
|
/**
|
|
71
143
|
* Publish a delayed message. Returns message id or null if QStash is unavailable.
|
|
72
144
|
*/
|
|
@@ -151,4 +223,4 @@ const verifyQstashSignature = (options) => __awaiter(void 0, void 0, void 0, fun
|
|
|
151
223
|
return true;
|
|
152
224
|
});
|
|
153
225
|
|
|
154
|
-
export { cancelSchedule, publishDelayedMessage, publishMessage, scheduleMessage, verifyQstashSignature };
|
|
226
|
+
export { cancelSchedule, publishBroadcastMessage, publishDelayedMessage, publishFIFOQueueMessage, publishMessage, scheduleMessage, verifyQstashSignature };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upstash-config.d.ts","sourceRoot":"","sources":["../../src/lib/upstash-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAE,MAAM,IAAI,YAAY,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"upstash-config.d.ts","sourceRoot":"","sources":["../../src/lib/upstash-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AACvC,OAAO,EAAE,MAAM,IAAI,YAAY,EAAE,MAAM,iBAAiB,CAAC;AA8OzD;;;;;GAKG;AACH,eAAO,MAAM,QAAQ,QAAO,KAAK,GAAG,IAEnC,CAAC;AAwDF;;;;;GAKG;AACH,eAAO,MAAM,SAAS,QAAO,YAAY,GAAG,IAE3C,CAAC;AA+CF,eAAO,MAAM,SAAS,GAAU,CAAC,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,KAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAMzF,CAAC;AAEF,eAAO,MAAM,UAAU,GAAU,CAAC,EAChC,IAAI,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,KAC3C,OAAO,CAAC,CAAC,GAAG,IAAI,CAMlB,CAAC"}
|
|
@@ -111,7 +111,10 @@ const parseMinutes = (value, fallback) => {
|
|
|
111
111
|
};
|
|
112
112
|
const getRedisHealthIntervalMinutes = () => parseMinutes(process.env.UPSTASH_REDIS_HEALTHCHECK_INTERVAL_MINUTES, 10);
|
|
113
113
|
const getQstashHealthIntervalMinutes = () => parseMinutes(process.env.UPSTASH_QSTASH_HEALTHCHECK_INTERVAL_MINUTES, 10);
|
|
114
|
-
const getQstashHealthcheckUrl = () => {
|
|
114
|
+
const getQstashHealthcheckUrl = () => {
|
|
115
|
+
const url = process.env.UPSTASH_QSTASH_HEALTHCHECK_URL;
|
|
116
|
+
return isNonEmpty(url) ? url : null;
|
|
117
|
+
};
|
|
115
118
|
const scheduleRedisHealthCheck = () => {
|
|
116
119
|
if (redisHealthTimer || !cachedRedis) {
|
|
117
120
|
return;
|
|
@@ -148,7 +151,11 @@ const scheduleRedisHealthCheck = () => {
|
|
|
148
151
|
}), delayMs);
|
|
149
152
|
};
|
|
150
153
|
const checkQstashHealth = (token) => tslib.__awaiter(void 0, void 0, void 0, function* () {
|
|
151
|
-
const
|
|
154
|
+
const healthcheckUrl = getQstashHealthcheckUrl();
|
|
155
|
+
if (!healthcheckUrl) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
const response = yield fetch(healthcheckUrl, {
|
|
152
159
|
method: 'GET',
|
|
153
160
|
headers: {
|
|
154
161
|
Authorization: `Bearer ${token}`,
|
|
@@ -160,7 +167,7 @@ const checkQstashHealth = (token) => tslib.__awaiter(void 0, void 0, void 0, fun
|
|
|
160
167
|
}
|
|
161
168
|
});
|
|
162
169
|
const scheduleQstashHealthCheck = (token) => {
|
|
163
|
-
if (qstashHealthTimer || !cachedQstash) {
|
|
170
|
+
if (qstashHealthTimer || !cachedQstash || !getQstashHealthcheckUrl()) {
|
|
164
171
|
return;
|
|
165
172
|
}
|
|
166
173
|
const delayMs = getQstashHealthIntervalMinutes() * 60000;
|
|
@@ -173,7 +180,6 @@ const scheduleQstashHealthCheck = (token) => {
|
|
|
173
180
|
yield checkQstashHealth(token);
|
|
174
181
|
}
|
|
175
182
|
catch (error) {
|
|
176
|
-
cachedQstash = null;
|
|
177
183
|
if (!qstashWarnedHealthCheck) {
|
|
178
184
|
qstashWarnedHealthCheck = true;
|
|
179
185
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -279,8 +285,14 @@ const ensureQstash = () => tslib.__awaiter(void 0, void 0, void 0, function* ()
|
|
|
279
285
|
}
|
|
280
286
|
try {
|
|
281
287
|
const client = new qstash.Client({ token: QSTASH_TOKEN });
|
|
282
|
-
yield checkQstashHealth(QSTASH_TOKEN);
|
|
283
288
|
cachedQstash = client;
|
|
289
|
+
checkQstashHealth(QSTASH_TOKEN).catch((error) => {
|
|
290
|
+
if (!qstashWarnedHealthCheck) {
|
|
291
|
+
qstashWarnedHealthCheck = true;
|
|
292
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
293
|
+
console.warn(`[Upstash Config] QStash health check failed: ${message}`);
|
|
294
|
+
}
|
|
295
|
+
});
|
|
284
296
|
scheduleQstashHealthCheck(QSTASH_TOKEN);
|
|
285
297
|
return cachedQstash;
|
|
286
298
|
}
|
|
@@ -109,7 +109,10 @@ const parseMinutes = (value, fallback) => {
|
|
|
109
109
|
};
|
|
110
110
|
const getRedisHealthIntervalMinutes = () => parseMinutes(process.env.UPSTASH_REDIS_HEALTHCHECK_INTERVAL_MINUTES, 10);
|
|
111
111
|
const getQstashHealthIntervalMinutes = () => parseMinutes(process.env.UPSTASH_QSTASH_HEALTHCHECK_INTERVAL_MINUTES, 10);
|
|
112
|
-
const getQstashHealthcheckUrl = () => {
|
|
112
|
+
const getQstashHealthcheckUrl = () => {
|
|
113
|
+
const url = process.env.UPSTASH_QSTASH_HEALTHCHECK_URL;
|
|
114
|
+
return isNonEmpty(url) ? url : null;
|
|
115
|
+
};
|
|
113
116
|
const scheduleRedisHealthCheck = () => {
|
|
114
117
|
if (redisHealthTimer || !cachedRedis) {
|
|
115
118
|
return;
|
|
@@ -146,7 +149,11 @@ const scheduleRedisHealthCheck = () => {
|
|
|
146
149
|
}), delayMs);
|
|
147
150
|
};
|
|
148
151
|
const checkQstashHealth = (token) => __awaiter(void 0, void 0, void 0, function* () {
|
|
149
|
-
const
|
|
152
|
+
const healthcheckUrl = getQstashHealthcheckUrl();
|
|
153
|
+
if (!healthcheckUrl) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const response = yield fetch(healthcheckUrl, {
|
|
150
157
|
method: 'GET',
|
|
151
158
|
headers: {
|
|
152
159
|
Authorization: `Bearer ${token}`,
|
|
@@ -158,7 +165,7 @@ const checkQstashHealth = (token) => __awaiter(void 0, void 0, void 0, function*
|
|
|
158
165
|
}
|
|
159
166
|
});
|
|
160
167
|
const scheduleQstashHealthCheck = (token) => {
|
|
161
|
-
if (qstashHealthTimer || !cachedQstash) {
|
|
168
|
+
if (qstashHealthTimer || !cachedQstash || !getQstashHealthcheckUrl()) {
|
|
162
169
|
return;
|
|
163
170
|
}
|
|
164
171
|
const delayMs = getQstashHealthIntervalMinutes() * 60000;
|
|
@@ -171,7 +178,6 @@ const scheduleQstashHealthCheck = (token) => {
|
|
|
171
178
|
yield checkQstashHealth(token);
|
|
172
179
|
}
|
|
173
180
|
catch (error) {
|
|
174
|
-
cachedQstash = null;
|
|
175
181
|
if (!qstashWarnedHealthCheck) {
|
|
176
182
|
qstashWarnedHealthCheck = true;
|
|
177
183
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -277,8 +283,14 @@ const ensureQstash = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
|
277
283
|
}
|
|
278
284
|
try {
|
|
279
285
|
const client = new Client({ token: QSTASH_TOKEN });
|
|
280
|
-
yield checkQstashHealth(QSTASH_TOKEN);
|
|
281
286
|
cachedQstash = client;
|
|
287
|
+
checkQstashHealth(QSTASH_TOKEN).catch((error) => {
|
|
288
|
+
if (!qstashWarnedHealthCheck) {
|
|
289
|
+
qstashWarnedHealthCheck = true;
|
|
290
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
291
|
+
console.warn(`[Upstash Config] QStash health check failed: ${message}`);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
282
294
|
scheduleQstashHealthCheck(QSTASH_TOKEN);
|
|
283
295
|
return cachedQstash;
|
|
284
296
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windrun-huaiin/backend-core",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "20.0.1",
|
|
4
4
|
"description": "Shared backend primitives: Prisma schema/client, database services, routing helpers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -112,9 +112,9 @@
|
|
|
112
112
|
"svix": "^1.86.0",
|
|
113
113
|
"tslib": "^2.8.1",
|
|
114
114
|
"zod": "^4.3.6",
|
|
115
|
-
"@windrun-huaiin/contracts": "^
|
|
116
|
-
"@windrun-huaiin/
|
|
117
|
-
"@windrun-huaiin/
|
|
115
|
+
"@windrun-huaiin/contracts": "^20.0.0",
|
|
116
|
+
"@windrun-huaiin/third-ui": "^20.0.0",
|
|
117
|
+
"@windrun-huaiin/lib": "^20.0.0"
|
|
118
118
|
},
|
|
119
119
|
"devDependencies": {
|
|
120
120
|
"@rollup/plugin-alias": "^5.1.1",
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
"prisma": "^6.19.0",
|
|
133
133
|
"stripe": "20.0.0",
|
|
134
134
|
"svix": "^1.86.0",
|
|
135
|
-
"@windrun-huaiin/contracts": "^
|
|
135
|
+
"@windrun-huaiin/contracts": "^20.0.0"
|
|
136
136
|
},
|
|
137
137
|
"publishConfig": {
|
|
138
138
|
"access": "public"
|
|
@@ -5,12 +5,46 @@ let cachedReceiver: Receiver | null = null;
|
|
|
5
5
|
let receiverWarnedMissingEnv = false;
|
|
6
6
|
let receiverWarnedInitError = false;
|
|
7
7
|
|
|
8
|
+
const isNonEmpty = (value: string | undefined): value is string =>
|
|
9
|
+
typeof value === 'string' && value.trim().length > 0;
|
|
10
|
+
|
|
8
11
|
const isTruthy = (value: string | undefined): boolean =>
|
|
9
12
|
value === '1' || value === 'true' || value === 'TRUE';
|
|
10
13
|
|
|
11
14
|
const shouldSkipVerify = (): boolean =>
|
|
12
15
|
process.env.NODE_ENV === 'development' && isTruthy(process.env.SKIP_UPSTASH_QSTASH_VERIFY);
|
|
13
16
|
|
|
17
|
+
const getRequiredAppName = (): string => {
|
|
18
|
+
const appName = process.env.NEXT_PUBLIC_APP_NAME;
|
|
19
|
+
if (!isNonEmpty(appName)) {
|
|
20
|
+
throw new Error(
|
|
21
|
+
'[Upstash QStash] NEXT_PUBLIC_APP_NAME is required for QStash naming and must not be empty'
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const normalized = appName.replace(/\s+/g, '').toLowerCase();
|
|
26
|
+
if (!normalized) {
|
|
27
|
+
throw new Error(
|
|
28
|
+
'[Upstash QStash] NEXT_PUBLIC_APP_NAME must contain non-whitespace characters for QStash naming'
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return normalized;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const getQstashNamePrefix = (): string => {
|
|
36
|
+
const envSuffix = process.env.NODE_ENV === 'production' ? 'live' : 'test';
|
|
37
|
+
return `${getRequiredAppName()}_${envSuffix}`;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const prefixQstashName = (resourceType: 'queue', name: string): string => {
|
|
41
|
+
if (!isNonEmpty(name)) {
|
|
42
|
+
throw new Error(`[Upstash QStash] ${resourceType} name must not be empty`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return `${getQstashNamePrefix()}_${resourceType}_${name}`;
|
|
46
|
+
};
|
|
47
|
+
|
|
14
48
|
const getReceiver = (): Receiver | null => {
|
|
15
49
|
if (cachedReceiver) {
|
|
16
50
|
return cachedReceiver;
|
|
@@ -56,6 +90,17 @@ export interface PublishMessageOptions<TBody extends PublishBody = PublishBody>
|
|
|
56
90
|
body: TBody;
|
|
57
91
|
}
|
|
58
92
|
|
|
93
|
+
export interface PublishBroadcastMessageOptions<TBody extends PublishBody = PublishBody> {
|
|
94
|
+
urlGroup: string;
|
|
95
|
+
body: TBody;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface PublishFIFOQueueMessageOptions<TBody extends PublishBody = PublishBody> {
|
|
99
|
+
queueName: string;
|
|
100
|
+
url: string;
|
|
101
|
+
body: TBody;
|
|
102
|
+
}
|
|
103
|
+
|
|
59
104
|
const generateSourceMessageId = (): string => {
|
|
60
105
|
try {
|
|
61
106
|
return crypto.randomUUID();
|
|
@@ -91,6 +136,69 @@ export const publishMessage = async <TBody extends PublishBody>(
|
|
|
91
136
|
});
|
|
92
137
|
};
|
|
93
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Publish a broadcast message to a QStash URL Group.
|
|
141
|
+
* Returns message ids or null if QStash is unavailable.
|
|
142
|
+
*/
|
|
143
|
+
export const publishBroadcastMessage = async <TBody extends PublishBody>(
|
|
144
|
+
options: PublishBroadcastMessageOptions<TBody>
|
|
145
|
+
): Promise<{ messageIds: string[]; message: QstashEnvelope<TBody> } | null> => {
|
|
146
|
+
const message = createEnvelope(options.body);
|
|
147
|
+
|
|
148
|
+
return withQstash(async (client) => {
|
|
149
|
+
const result = await (client as any).publishJSON({
|
|
150
|
+
urlGroup: options.urlGroup,
|
|
151
|
+
body: message,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
const messageIds = Array.isArray(result)
|
|
155
|
+
? result
|
|
156
|
+
.map((item) =>
|
|
157
|
+
typeof item === 'string' ? item : typeof item?.messageId === 'string' ? item.messageId : null
|
|
158
|
+
)
|
|
159
|
+
.filter((messageId): messageId is string => typeof messageId === 'string')
|
|
160
|
+
: typeof result === 'string'
|
|
161
|
+
? [result]
|
|
162
|
+
: typeof result?.messageId === 'string'
|
|
163
|
+
? [result.messageId]
|
|
164
|
+
: [];
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
messageIds,
|
|
168
|
+
message,
|
|
169
|
+
};
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Publish a single-recipient message into a QStash FIFO queue.
|
|
175
|
+
* Returns message id or null if QStash is unavailable.
|
|
176
|
+
*/
|
|
177
|
+
export const publishFIFOQueueMessage = async <TBody extends PublishBody>(
|
|
178
|
+
options: PublishFIFOQueueMessageOptions<TBody>
|
|
179
|
+
): Promise<{ messageId: string | null; message: QstashEnvelope<TBody> } | null> => {
|
|
180
|
+
const message = createEnvelope(options.body);
|
|
181
|
+
const queueName = prefixQstashName('queue', options.queueName);
|
|
182
|
+
|
|
183
|
+
return withQstash(async (client) => {
|
|
184
|
+
const anyClient = client as any;
|
|
185
|
+
const queueClient = anyClient.queue?.({ queueName });
|
|
186
|
+
if (!queueClient?.enqueueJSON) {
|
|
187
|
+
throw new Error('QStash queue enqueueJSON API is unavailable');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const result = await queueClient.enqueueJSON({
|
|
191
|
+
url: options.url,
|
|
192
|
+
body: message,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
messageId: typeof result === 'string' ? result : result?.messageId ?? null,
|
|
197
|
+
message,
|
|
198
|
+
};
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
|
|
94
202
|
/**
|
|
95
203
|
* Publish a delayed message. Returns message id or null if QStash is unavailable.
|
|
96
204
|
*/
|
|
@@ -149,8 +149,10 @@ const getRedisHealthIntervalMinutes = (): number =>
|
|
|
149
149
|
const getQstashHealthIntervalMinutes = (): number =>
|
|
150
150
|
parseMinutes(process.env.UPSTASH_QSTASH_HEALTHCHECK_INTERVAL_MINUTES, 10);
|
|
151
151
|
|
|
152
|
-
const getQstashHealthcheckUrl = (): string =>
|
|
153
|
-
process.env.UPSTASH_QSTASH_HEALTHCHECK_URL
|
|
152
|
+
const getQstashHealthcheckUrl = (): string | null => {
|
|
153
|
+
const url = process.env.UPSTASH_QSTASH_HEALTHCHECK_URL;
|
|
154
|
+
return isNonEmpty(url) ? url : null;
|
|
155
|
+
};
|
|
154
156
|
|
|
155
157
|
const scheduleRedisHealthCheck = (): void => {
|
|
156
158
|
if (redisHealthTimer || !cachedRedis) {
|
|
@@ -186,7 +188,12 @@ const scheduleRedisHealthCheck = (): void => {
|
|
|
186
188
|
};
|
|
187
189
|
|
|
188
190
|
const checkQstashHealth = async (token: string): Promise<void> => {
|
|
189
|
-
const
|
|
191
|
+
const healthcheckUrl = getQstashHealthcheckUrl();
|
|
192
|
+
if (!healthcheckUrl) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const response = await fetch(healthcheckUrl, {
|
|
190
197
|
method: 'GET',
|
|
191
198
|
headers: {
|
|
192
199
|
Authorization: `Bearer ${token}`,
|
|
@@ -199,7 +206,7 @@ const checkQstashHealth = async (token: string): Promise<void> => {
|
|
|
199
206
|
};
|
|
200
207
|
|
|
201
208
|
const scheduleQstashHealthCheck = (token: string): void => {
|
|
202
|
-
if (qstashHealthTimer || !cachedQstash) {
|
|
209
|
+
if (qstashHealthTimer || !cachedQstash || !getQstashHealthcheckUrl()) {
|
|
203
210
|
return;
|
|
204
211
|
}
|
|
205
212
|
const delayMs = getQstashHealthIntervalMinutes() * 60_000;
|
|
@@ -211,7 +218,6 @@ const scheduleQstashHealthCheck = (token: string): void => {
|
|
|
211
218
|
try {
|
|
212
219
|
await checkQstashHealth(token);
|
|
213
220
|
} catch (error) {
|
|
214
|
-
cachedQstash = null;
|
|
215
221
|
if (!qstashWarnedHealthCheck) {
|
|
216
222
|
qstashWarnedHealthCheck = true;
|
|
217
223
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -325,8 +331,14 @@ const ensureQstash = async (): Promise<QstashClient | null> => {
|
|
|
325
331
|
|
|
326
332
|
try {
|
|
327
333
|
const client = new QstashClient({ token: QSTASH_TOKEN });
|
|
328
|
-
await checkQstashHealth(QSTASH_TOKEN);
|
|
329
334
|
cachedQstash = client;
|
|
335
|
+
checkQstashHealth(QSTASH_TOKEN).catch((error) => {
|
|
336
|
+
if (!qstashWarnedHealthCheck) {
|
|
337
|
+
qstashWarnedHealthCheck = true;
|
|
338
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
339
|
+
console.warn(`[Upstash Config] QStash health check failed: ${message}`);
|
|
340
|
+
}
|
|
341
|
+
});
|
|
330
342
|
scheduleQstashHealthCheck(QSTASH_TOKEN);
|
|
331
343
|
return cachedQstash;
|
|
332
344
|
} catch (error) {
|