@windrun-huaiin/backend-core 13.0.0 → 14.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/_virtual/index.js +7 -3
- package/dist/_virtual/index.mjs +5 -3
- package/dist/_virtual/index2.js +2 -6
- package/dist/_virtual/index2.mjs +2 -6
- package/dist/index.js +3 -1
- package/dist/index.mjs +2 -2
- package/dist/lib/index.js +3 -1
- package/dist/lib/index.mjs +2 -2
- package/dist/lib/stripe-config.d.ts +1 -1
- package/dist/lib/stripe-config.d.ts.map +1 -1
- package/dist/lib/stripe-config.js +25 -16
- package/dist/lib/stripe-config.mjs +25 -16
- package/dist/lib/upstash/qstash.d.ts.map +1 -1
- package/dist/lib/upstash/qstash.js +66 -62
- package/dist/lib/upstash/qstash.mjs +67 -63
- package/dist/lib/upstash/redis-counter.d.ts.map +1 -1
- package/dist/lib/upstash/redis-counter.js +9 -24
- package/dist/lib/upstash/redis-counter.mjs +10 -25
- package/dist/lib/upstash/redis-favorite.d.ts.map +1 -1
- package/dist/lib/upstash/redis-favorite.js +22 -36
- package/dist/lib/upstash/redis-favorite.mjs +23 -37
- package/dist/lib/upstash/redis-like.d.ts.map +1 -1
- package/dist/lib/upstash/redis-like.js +22 -36
- package/dist/lib/upstash/redis-like.mjs +23 -37
- package/dist/lib/upstash/redis-lock.d.ts.map +1 -1
- package/dist/lib/upstash/redis-lock.js +22 -38
- package/dist/lib/upstash/redis-lock.mjs +23 -39
- package/dist/lib/upstash/redis-structures.d.ts.map +1 -1
- package/dist/lib/upstash/redis-structures.js +77 -113
- package/dist/lib/upstash/redis-structures.mjs +78 -114
- package/dist/lib/upstash-config.d.ts +9 -1
- package/dist/lib/upstash-config.d.ts.map +1 -1
- package/dist/lib/upstash-config.js +221 -27
- package/dist/lib/upstash-config.mjs +220 -28
- package/dist/node_modules/.pnpm/{@upstash_qstash@2.8.4/node_modules/@upstash/qstash/chunk-RQPZUJXG.js → @upstash_qstash@2.10.1/node_modules/@upstash/qstash/chunk-35B33QW3.js} +897 -468
- package/dist/node_modules/.pnpm/{@upstash_qstash@2.8.4/node_modules/@upstash/qstash/chunk-RQPZUJXG.mjs → @upstash_qstash@2.10.1/node_modules/@upstash/qstash/chunk-35B33QW3.mjs} +895 -468
- package/dist/node_modules/.pnpm/{@upstash_redis@1.36.1/node_modules/@upstash/redis/chunk-LLI2WIYN.js → @upstash_redis@1.37.0/node_modules/@upstash/redis/chunk-IH7W44G6.js} +657 -40
- package/dist/node_modules/.pnpm/{@upstash_redis@1.36.1/node_modules/@upstash/redis/chunk-LLI2WIYN.mjs → @upstash_redis@1.37.0/node_modules/@upstash/redis/chunk-IH7W44G6.mjs} +657 -41
- package/dist/node_modules/.pnpm/{@upstash_redis@1.36.1 → @upstash_redis@1.37.0}/node_modules/@upstash/redis/nodejs.js +6 -5
- package/dist/node_modules/.pnpm/{@upstash_redis@1.36.1 → @upstash_redis@1.37.0}/node_modules/@upstash/redis/nodejs.mjs +2 -2
- package/dist/node_modules/.pnpm/crypto-js@4.2.0/node_modules/crypto-js/index.js +1 -1
- package/dist/node_modules/.pnpm/crypto-js@4.2.0/node_modules/crypto-js/index.mjs +1 -1
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/jws/flattened/verify.js +6 -6
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/jwt/verify.js +1 -1
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/key/import.js +2 -2
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/lib/epoch.js +3 -1
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/lib/is_disjoint.js +3 -1
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/lib/is_jwk.js +1 -1
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/lib/is_object.js +3 -1
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/lib/jwt_claims_set.js +7 -5
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/lib/secs.js +3 -1
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/lib/validate_algorithms.js +3 -1
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/lib/validate_crit.js +3 -1
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/runtime/check_key_length.js +3 -1
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/runtime/dsa_digest.js +3 -1
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/runtime/get_named_curve.js +4 -2
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/runtime/get_sign_verify_key.js +3 -1
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/runtime/hmac_digest.js +3 -1
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/runtime/is_key_like.js +1 -1
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/runtime/is_key_object.js +3 -1
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/runtime/jwk_to_key.js +3 -1
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/runtime/node_key.js +6 -4
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/runtime/sign.js +6 -4
- package/dist/node_modules/.pnpm/jose@5.10.0/node_modules/jose/dist/node/esm/runtime/verify.js +7 -5
- package/dist/services/stripe/webhook-handler.js +4 -2
- package/dist/services/stripe/webhook-handler.mjs +3 -1
- package/package.json +5 -5
- package/src/lib/stripe-config.ts +27 -15
- package/src/lib/upstash/qstash.ts +64 -62
- package/src/lib/upstash/redis-counter.ts +10 -26
- package/src/lib/upstash/redis-favorite.ts +23 -42
- package/src/lib/upstash/redis-like.ts +23 -42
- package/src/lib/upstash/redis-lock.ts +23 -49
- package/src/lib/upstash/redis-structures.ts +82 -131
- package/src/lib/upstash-config.ts +231 -24
- package/src/services/stripe/webhook-handler.ts +3 -1
- package/dist/_virtual/index3.js +0 -5
- package/dist/_virtual/index3.mjs +0 -3
- package/dist/node_modules/.pnpm/@upstash_lock@0.2.1_typescript@5.9.3/node_modules/@upstash/lock/dist/index.js +0 -191
- package/dist/node_modules/.pnpm/@upstash_lock@0.2.1_typescript@5.9.3/node_modules/@upstash/lock/dist/index.mjs +0 -189
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/errors.js +0 -54
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/errors.mjs +0 -51
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/iso.js +0 -44
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/iso.mjs +0 -35
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/parse.js +0 -31
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/parse.mjs +0 -18
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/schemas.js +0 -587
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/schemas.mjs +0 -527
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/api.js +0 -447
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/api.mjs +0 -399
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/checks.js +0 -245
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/checks.mjs +0 -232
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/core.js +0 -68
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/core.mjs +0 -62
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/doc.js +0 -39
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/doc.mjs +0 -37
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/errors.js +0 -80
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/errors.mjs +0 -75
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/parse.js +0 -101
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/parse.mjs +0 -86
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/regexes.js +0 -102
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/regexes.mjs +0 -76
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/registries.js +0 -56
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/registries.mjs +0 -52
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/schemas.js +0 -1205
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/schemas.mjs +0 -1157
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/util.js +0 -407
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/util.mjs +0 -374
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/versions.js +0 -9
- package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/versions.mjs +0 -7
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { withRedis } from '../upstash-config';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Set a plain string value with optional TTL (seconds).
|
|
@@ -8,30 +8,22 @@ export const setString = async (
|
|
|
8
8
|
value: string,
|
|
9
9
|
ttlSec?: number
|
|
10
10
|
): Promise<boolean> => {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
return withRedis(async (redis) => {
|
|
12
|
+
if (ttlSec && ttlSec > 0) {
|
|
13
|
+
await redis.set(key, value, { ex: ttlSec });
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
await redis.set(key, value, { ex: ttlSec });
|
|
17
|
+
await redis.set(key, value);
|
|
18
18
|
return true;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
await redis.set(key, value);
|
|
22
|
-
return true;
|
|
19
|
+
}).then((result) => result ?? false);
|
|
23
20
|
};
|
|
24
21
|
|
|
25
22
|
/**
|
|
26
23
|
* Get a plain string value. Returns null if Redis is unavailable or key missing.
|
|
27
24
|
*/
|
|
28
25
|
export const getString = async (key: string): Promise<string | null> => {
|
|
29
|
-
|
|
30
|
-
if (!redis) {
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
return redis.get<string>(key);
|
|
26
|
+
return withRedis((redis) => redis.get<string>(key));
|
|
35
27
|
};
|
|
36
28
|
|
|
37
29
|
/**
|
|
@@ -42,78 +34,63 @@ export const setJson = async <T>(
|
|
|
42
34
|
value: T,
|
|
43
35
|
ttlSec?: number
|
|
44
36
|
): Promise<boolean> => {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
await redis.set(key, payload
|
|
37
|
+
return withRedis(async (redis) => {
|
|
38
|
+
const payload = JSON.stringify(value);
|
|
39
|
+
if (ttlSec && ttlSec > 0) {
|
|
40
|
+
await redis.set(key, payload, { ex: ttlSec });
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
await redis.set(key, payload);
|
|
53
45
|
return true;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
await redis.set(key, payload);
|
|
57
|
-
return true;
|
|
46
|
+
}).then((result) => result ?? false);
|
|
58
47
|
};
|
|
59
48
|
|
|
60
49
|
/**
|
|
61
50
|
* Get an object stored as JSON string. Returns null if missing or invalid JSON.
|
|
62
51
|
*/
|
|
63
52
|
export const getJson = async <T>(key: string): Promise<T | null> => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
} catch {
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
53
|
+
return withRedis(async (redis) => {
|
|
54
|
+
const payload = await redis.get<string>(key);
|
|
55
|
+
if (!payload) {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(payload) as T;
|
|
61
|
+
} catch {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
});
|
|
79
65
|
};
|
|
80
66
|
|
|
81
67
|
/**
|
|
82
68
|
* Delete a key. Returns false if Redis is unavailable.
|
|
83
69
|
*/
|
|
84
70
|
export const deleteKey = async (key: string): Promise<boolean> => {
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
return
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const deleted = await redis.del(key);
|
|
91
|
-
return deleted > 0;
|
|
71
|
+
const result = await withRedis(async (redis) => {
|
|
72
|
+
const deleted = await redis.del(key);
|
|
73
|
+
return deleted > 0;
|
|
74
|
+
});
|
|
75
|
+
return result ?? false;
|
|
92
76
|
};
|
|
93
77
|
|
|
94
78
|
/**
|
|
95
79
|
* Set a hash field value.
|
|
96
80
|
*/
|
|
97
81
|
export const setHashField = async (key: string, field: string, value: string): Promise<boolean> => {
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
return
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const result = await redis.hset(key, { [field]: value });
|
|
104
|
-
return result > 0;
|
|
82
|
+
const result = await withRedis(async (redis) => {
|
|
83
|
+
const changed = await redis.hset(key, { [field]: value });
|
|
84
|
+
return changed > 0;
|
|
85
|
+
});
|
|
86
|
+
return result ?? false;
|
|
105
87
|
};
|
|
106
88
|
|
|
107
89
|
/**
|
|
108
90
|
* Get a hash field value.
|
|
109
91
|
*/
|
|
110
92
|
export const getHashField = async (key: string, field: string): Promise<string | null> => {
|
|
111
|
-
|
|
112
|
-
if (!redis) {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return redis.hget<string>(key, field);
|
|
93
|
+
return withRedis((redis) => redis.hget<string>(key, field));
|
|
117
94
|
};
|
|
118
95
|
|
|
119
96
|
/**
|
|
@@ -124,61 +101,51 @@ export const setHashJson = async <T>(
|
|
|
124
101
|
field: string,
|
|
125
102
|
value: T
|
|
126
103
|
): Promise<boolean> => {
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const result = await redis.hset(key, { [field]: payload });
|
|
134
|
-
return result > 0;
|
|
104
|
+
const result = await withRedis(async (redis) => {
|
|
105
|
+
const payload = JSON.stringify(value);
|
|
106
|
+
const changed = await redis.hset(key, { [field]: payload });
|
|
107
|
+
return changed > 0;
|
|
108
|
+
});
|
|
109
|
+
return result ?? false;
|
|
135
110
|
};
|
|
136
111
|
|
|
137
112
|
/**
|
|
138
113
|
* Get a hash field stored as JSON string.
|
|
139
114
|
*/
|
|
140
115
|
export const getHashJson = async <T>(key: string, field: string): Promise<T | null> => {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
} catch {
|
|
154
|
-
return null;
|
|
155
|
-
}
|
|
116
|
+
return withRedis(async (redis) => {
|
|
117
|
+
const payload = await redis.hget<string>(key, field);
|
|
118
|
+
if (!payload) {
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
return JSON.parse(payload) as T;
|
|
124
|
+
} catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
156
128
|
};
|
|
157
129
|
|
|
158
130
|
/**
|
|
159
131
|
* Get all hash fields.
|
|
160
132
|
*/
|
|
161
133
|
export const getHashAll = async (key: string): Promise<Record<string, string> | null> => {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
return
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const result = await redis.hgetall<Record<string, string>>(key);
|
|
168
|
-
return result ?? {};
|
|
134
|
+
return withRedis(async (redis) => {
|
|
135
|
+
const result = await redis.hgetall<Record<string, string>>(key);
|
|
136
|
+
return result ?? {};
|
|
137
|
+
});
|
|
169
138
|
};
|
|
170
139
|
|
|
171
140
|
/**
|
|
172
141
|
* Remove a hash field.
|
|
173
142
|
*/
|
|
174
143
|
export const deleteHashField = async (key: string, field: string): Promise<boolean> => {
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
return
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const removed = await redis.hdel(key, field);
|
|
181
|
-
return removed > 0;
|
|
144
|
+
const result = await withRedis(async (redis) => {
|
|
145
|
+
const removed = await redis.hdel(key, field);
|
|
146
|
+
return removed > 0;
|
|
147
|
+
});
|
|
148
|
+
return result ?? false;
|
|
182
149
|
};
|
|
183
150
|
|
|
184
151
|
type ListDirection = 'left' | 'right';
|
|
@@ -191,18 +158,15 @@ export const pushList = async (
|
|
|
191
158
|
values: string[],
|
|
192
159
|
direction: ListDirection = 'right'
|
|
193
160
|
): Promise<number | null> => {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
return direction === 'left'
|
|
204
|
-
? redis.lpush(key, ...values)
|
|
205
|
-
: redis.rpush(key, ...values);
|
|
161
|
+
return withRedis((redis) => {
|
|
162
|
+
if (values.length === 0) {
|
|
163
|
+
return redis.llen(key);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return direction === 'left'
|
|
167
|
+
? redis.lpush(key, ...values)
|
|
168
|
+
: redis.rpush(key, ...values);
|
|
169
|
+
});
|
|
206
170
|
};
|
|
207
171
|
|
|
208
172
|
/**
|
|
@@ -212,12 +176,9 @@ export const popList = async (
|
|
|
212
176
|
key: string,
|
|
213
177
|
direction: ListDirection = 'right'
|
|
214
178
|
): Promise<string | null> => {
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return direction === 'left' ? redis.lpop<string>(key) : redis.rpop<string>(key);
|
|
179
|
+
return withRedis((redis) =>
|
|
180
|
+
direction === 'left' ? redis.lpop<string>(key) : redis.rpop<string>(key)
|
|
181
|
+
);
|
|
221
182
|
};
|
|
222
183
|
|
|
223
184
|
/**
|
|
@@ -228,22 +189,12 @@ export const rangeList = async (
|
|
|
228
189
|
start = 0,
|
|
229
190
|
stop = -1
|
|
230
191
|
): Promise<string[] | null> => {
|
|
231
|
-
|
|
232
|
-
if (!redis) {
|
|
233
|
-
return null;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
return redis.lrange<string>(key, start, stop);
|
|
192
|
+
return withRedis((redis) => redis.lrange<string>(key, start, stop));
|
|
237
193
|
};
|
|
238
194
|
|
|
239
195
|
/**
|
|
240
196
|
* Get list length.
|
|
241
197
|
*/
|
|
242
198
|
export const listLength = async (key: string): Promise<number | null> => {
|
|
243
|
-
|
|
244
|
-
if (!redis) {
|
|
245
|
-
return null;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return redis.llen(key);
|
|
199
|
+
return withRedis((redis) => redis.llen(key));
|
|
249
200
|
};
|
|
@@ -3,52 +3,259 @@ import { Client as QstashClient } from '@upstash/qstash';
|
|
|
3
3
|
|
|
4
4
|
let cachedRedis: Redis | null = null;
|
|
5
5
|
let cachedQstash: QstashClient | null = null;
|
|
6
|
-
let
|
|
7
|
-
let
|
|
6
|
+
let redisInitPromise: Promise<Redis | null> | null = null;
|
|
7
|
+
let qstashInitPromise: Promise<QstashClient | null> | null = null;
|
|
8
|
+
|
|
9
|
+
let redisWarnedMissingEnv = false;
|
|
10
|
+
let redisWarnedInvalidEnv = false;
|
|
11
|
+
let redisWarnedInitError = false;
|
|
12
|
+
let redisWarnedHealthCheck = false;
|
|
13
|
+
let redisWarnedHealthSchedule = false;
|
|
14
|
+
|
|
15
|
+
let qstashWarnedMissingEnv = false;
|
|
16
|
+
let qstashWarnedInitError = false;
|
|
17
|
+
let qstashWarnedHealthCheck = false;
|
|
18
|
+
let qstashWarnedHealthSchedule = false;
|
|
19
|
+
|
|
20
|
+
let redisHealthTimer: ReturnType<typeof setTimeout> | null = null;
|
|
21
|
+
let qstashHealthTimer: ReturnType<typeof setTimeout> | null = null;
|
|
22
|
+
|
|
23
|
+
const isNonEmpty = (value: string | undefined): value is string =>
|
|
24
|
+
typeof value === 'string' && value.trim().length > 0;
|
|
25
|
+
|
|
26
|
+
const isValidUrl = (value: string): boolean => {
|
|
27
|
+
try {
|
|
28
|
+
const parsed = new URL(value);
|
|
29
|
+
return parsed.protocol === 'http:' || parsed.protocol === 'https:';
|
|
30
|
+
} catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const parseMinutes = (value: string | undefined, fallback: number): number => {
|
|
36
|
+
if (!isNonEmpty(value)) {
|
|
37
|
+
return fallback;
|
|
38
|
+
}
|
|
39
|
+
const minutes = Number(value);
|
|
40
|
+
if (!Number.isFinite(minutes) || minutes <= 0) {
|
|
41
|
+
return fallback;
|
|
42
|
+
}
|
|
43
|
+
return minutes;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const getRedisHealthIntervalMinutes = (): number =>
|
|
47
|
+
parseMinutes(process.env.UPSTASH_REDIS_HEALTHCHECK_INTERVAL_MINUTES, 10);
|
|
48
|
+
|
|
49
|
+
const getQstashHealthIntervalMinutes = (): number =>
|
|
50
|
+
parseMinutes(process.env.UPSTASH_QSTASH_HEALTHCHECK_INTERVAL_MINUTES, 10);
|
|
51
|
+
|
|
52
|
+
const getQstashHealthcheckUrl = (): string =>
|
|
53
|
+
process.env.UPSTASH_QSTASH_HEALTHCHECK_URL ?? 'https://qstash.upstash.io/v2/topics';
|
|
54
|
+
|
|
55
|
+
const scheduleRedisHealthCheck = (): void => {
|
|
56
|
+
if (redisHealthTimer || !cachedRedis) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const delayMs = getRedisHealthIntervalMinutes() * 60_000;
|
|
60
|
+
redisHealthTimer = setTimeout(async () => {
|
|
61
|
+
redisHealthTimer = null;
|
|
62
|
+
if (!cachedRedis) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
await cachedRedis.ping();
|
|
67
|
+
} catch (error) {
|
|
68
|
+
cachedRedis = null;
|
|
69
|
+
if (!redisWarnedHealthCheck) {
|
|
70
|
+
redisWarnedHealthCheck = true;
|
|
71
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
72
|
+
console.warn(`[Upstash Config] Redis health check failed: ${message}`);
|
|
73
|
+
}
|
|
74
|
+
} finally {
|
|
75
|
+
try {
|
|
76
|
+
scheduleRedisHealthCheck();
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (!redisWarnedHealthSchedule) {
|
|
79
|
+
redisWarnedHealthSchedule = true;
|
|
80
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
81
|
+
console.warn(`[Upstash Config] Redis health check schedule failed: ${message}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}, delayMs);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const checkQstashHealth = async (token: string): Promise<void> => {
|
|
89
|
+
const response = await fetch(getQstashHealthcheckUrl(), {
|
|
90
|
+
method: 'GET',
|
|
91
|
+
headers: {
|
|
92
|
+
Authorization: `Bearer ${token}`,
|
|
93
|
+
},
|
|
94
|
+
cache: 'no-store',
|
|
95
|
+
});
|
|
96
|
+
if (!response.ok) {
|
|
97
|
+
throw new Error(`HTTP ${response.status}`);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
const scheduleQstashHealthCheck = (token: string): void => {
|
|
102
|
+
if (qstashHealthTimer || !cachedQstash) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const delayMs = getQstashHealthIntervalMinutes() * 60_000;
|
|
106
|
+
qstashHealthTimer = setTimeout(async () => {
|
|
107
|
+
qstashHealthTimer = null;
|
|
108
|
+
if (!cachedQstash) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
await checkQstashHealth(token);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
cachedQstash = null;
|
|
115
|
+
if (!qstashWarnedHealthCheck) {
|
|
116
|
+
qstashWarnedHealthCheck = true;
|
|
117
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
118
|
+
console.warn(`[Upstash Config] QStash health check failed: ${message}`);
|
|
119
|
+
}
|
|
120
|
+
} finally {
|
|
121
|
+
try {
|
|
122
|
+
scheduleQstashHealthCheck(token);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (!qstashWarnedHealthSchedule) {
|
|
125
|
+
qstashWarnedHealthSchedule = true;
|
|
126
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
127
|
+
console.warn(`[Upstash Config] QStash health check schedule failed: ${message}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}, delayMs);
|
|
132
|
+
};
|
|
8
133
|
|
|
9
134
|
/**
|
|
10
|
-
* Get the Upstash Redis client. Returns null when required env vars are missing.
|
|
135
|
+
* Get the Upstash Redis client. Returns null when required env vars are missing/invalid.
|
|
136
|
+
*
|
|
137
|
+
* Singleton semantics:
|
|
138
|
+
* - read-through cached instance only
|
|
11
139
|
*/
|
|
12
140
|
export const getRedis = (): Redis | null => {
|
|
141
|
+
return cachedRedis;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const ensureRedis = async (): Promise<Redis | null> => {
|
|
13
145
|
if (cachedRedis) {
|
|
14
146
|
return cachedRedis;
|
|
15
147
|
}
|
|
16
|
-
if (
|
|
17
|
-
return
|
|
148
|
+
if (redisInitPromise) {
|
|
149
|
+
return redisInitPromise;
|
|
18
150
|
}
|
|
19
151
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
152
|
+
redisInitPromise = (async () => {
|
|
153
|
+
const { UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN } = process.env;
|
|
154
|
+
if (!isNonEmpty(UPSTASH_REDIS_REST_URL) || !isNonEmpty(UPSTASH_REDIS_REST_TOKEN)) {
|
|
155
|
+
if (!redisWarnedMissingEnv) {
|
|
156
|
+
redisWarnedMissingEnv = true;
|
|
157
|
+
console.warn(
|
|
158
|
+
'[Upstash Config] Redis disabled: missing UPSTASH_REDIS_REST_URL or UPSTASH_REDIS_REST_TOKEN'
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
26
163
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
164
|
+
if (!isValidUrl(UPSTASH_REDIS_REST_URL)) {
|
|
165
|
+
if (!redisWarnedInvalidEnv) {
|
|
166
|
+
redisWarnedInvalidEnv = true;
|
|
167
|
+
console.warn('[Upstash Config] Redis disabled: UPSTASH_REDIS_REST_URL is not a valid URL');
|
|
168
|
+
}
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
try {
|
|
173
|
+
const client = new Redis({
|
|
174
|
+
url: UPSTASH_REDIS_REST_URL,
|
|
175
|
+
token: UPSTASH_REDIS_REST_TOKEN,
|
|
176
|
+
});
|
|
177
|
+
await client.ping();
|
|
178
|
+
cachedRedis = client;
|
|
179
|
+
scheduleRedisHealthCheck();
|
|
180
|
+
return cachedRedis;
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (!redisWarnedInitError) {
|
|
183
|
+
redisWarnedInitError = true;
|
|
184
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
185
|
+
console.warn(`[Upstash Config] Redis init failed: ${message}`);
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
} finally {
|
|
189
|
+
redisInitPromise = null;
|
|
190
|
+
}
|
|
191
|
+
})();
|
|
192
|
+
|
|
193
|
+
return redisInitPromise;
|
|
32
194
|
};
|
|
33
195
|
|
|
34
196
|
/**
|
|
35
197
|
* Get the Upstash QStash client. Returns null when required env vars are missing.
|
|
198
|
+
*
|
|
199
|
+
* Singleton semantics:
|
|
200
|
+
* - read-through cached instance only
|
|
36
201
|
*/
|
|
37
202
|
export const getQstash = (): QstashClient | null => {
|
|
203
|
+
return cachedQstash;
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const ensureQstash = async (): Promise<QstashClient | null> => {
|
|
38
207
|
if (cachedQstash) {
|
|
39
208
|
return cachedQstash;
|
|
40
209
|
}
|
|
41
|
-
if (
|
|
42
|
-
return
|
|
210
|
+
if (qstashInitPromise) {
|
|
211
|
+
return qstashInitPromise;
|
|
43
212
|
}
|
|
44
213
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
214
|
+
qstashInitPromise = (async () => {
|
|
215
|
+
const { QSTASH_TOKEN } = process.env;
|
|
216
|
+
if (!isNonEmpty(QSTASH_TOKEN)) {
|
|
217
|
+
if (!qstashWarnedMissingEnv) {
|
|
218
|
+
qstashWarnedMissingEnv = true;
|
|
219
|
+
console.warn('[Upstash Config] QStash disabled: missing QSTASH_TOKEN');
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
const client = new QstashClient({ token: QSTASH_TOKEN });
|
|
226
|
+
await checkQstashHealth(QSTASH_TOKEN);
|
|
227
|
+
cachedQstash = client;
|
|
228
|
+
scheduleQstashHealthCheck(QSTASH_TOKEN);
|
|
229
|
+
return cachedQstash;
|
|
230
|
+
} catch (error) {
|
|
231
|
+
if (!qstashWarnedInitError) {
|
|
232
|
+
qstashWarnedInitError = true;
|
|
233
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
234
|
+
console.warn(`[Upstash Config] QStash init failed: ${message}`);
|
|
235
|
+
}
|
|
236
|
+
return null;
|
|
237
|
+
} finally {
|
|
238
|
+
qstashInitPromise = null;
|
|
239
|
+
}
|
|
240
|
+
})();
|
|
241
|
+
|
|
242
|
+
return qstashInitPromise;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
export const withRedis = async <T>(fn: (redis: Redis) => Promise<T> | T): Promise<T | null> => {
|
|
246
|
+
const redis = await ensureRedis();
|
|
247
|
+
if (!redis) {
|
|
49
248
|
return null;
|
|
50
249
|
}
|
|
250
|
+
return fn(redis);
|
|
251
|
+
};
|
|
51
252
|
|
|
52
|
-
|
|
53
|
-
|
|
253
|
+
export const withQstash = async <T>(
|
|
254
|
+
fn: (qstash: QstashClient) => Promise<T> | T
|
|
255
|
+
): Promise<T | null> => {
|
|
256
|
+
const qstash = await ensureQstash();
|
|
257
|
+
if (!qstash) {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
return fn(qstash);
|
|
54
261
|
};
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import { Transaction } from '@/db/prisma-model-type';
|
|
14
14
|
import { oneTimeExpiredDays } from '@/lib/credit-init';
|
|
15
15
|
import { getCreditsFromPriceId } from '@/lib/money-price-config';
|
|
16
|
-
import { fetchPaymentId,
|
|
16
|
+
import { fetchPaymentId, getStripe } from '@/lib/stripe-config';
|
|
17
17
|
import Stripe from 'stripe';
|
|
18
18
|
import { viewLocalTime } from '@windrun-huaiin/lib/utils';
|
|
19
19
|
|
|
@@ -150,6 +150,7 @@ async function handleSubscriptionCheckoutInit(
|
|
|
150
150
|
}
|
|
151
151
|
|
|
152
152
|
const subscriptionId = session.subscription as string;
|
|
153
|
+
const stripe = getStripe();
|
|
153
154
|
|
|
154
155
|
// ===== STEP 1: FETCH EXTERNAL API DATA (BEFORE TRANSACTION) =====
|
|
155
156
|
// 2. Get COMPLETE Stripe subscription details including billing period
|
|
@@ -416,6 +417,7 @@ async function handleSubscriptionDeleted(stripeSubscription: Stripe.Subscription
|
|
|
416
417
|
|
|
417
418
|
async function handleAsyncPaymentSucceeded(session: Stripe.Checkout.Session) {
|
|
418
419
|
console.log(`Async payment succeeded: ${session.id}`);
|
|
420
|
+
const stripe = getStripe();
|
|
419
421
|
|
|
420
422
|
// Retrieve the latest session state to ensure payment_status is up to date
|
|
421
423
|
const latestSession = await stripe.checkout.sessions.retrieve(session.id);
|
package/dist/_virtual/index3.js
DELETED
package/dist/_virtual/index3.mjs
DELETED