@teever/ez-hook-effect 0.4.4
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/README.md +265 -0
- package/dist/errors/WebhookError.d.ts +156 -0
- package/dist/errors/index.d.ts +1 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +952 -0
- package/dist/index.js.map +20 -0
- package/dist/layers/Config.d.ts +48 -0
- package/dist/layers/HttpClient.d.ts +80 -0
- package/dist/layers/index.d.ts +2 -0
- package/dist/pipes/Embed.d.ts +51 -0
- package/dist/pipes/Webhook.d.ts +37 -0
- package/dist/pipes/index.d.ts +2 -0
- package/dist/schemas/Common.d.ts +10 -0
- package/dist/schemas/Discord.d.ts +16 -0
- package/dist/schemas/Embed.d.ts +314 -0
- package/dist/schemas/Field.d.ts +45 -0
- package/dist/schemas/Webhook.d.ts +206 -0
- package/dist/schemas/index.d.ts +5 -0
- package/dist/services/WebhookService.d.ts +72 -0
- package/dist/services/index.d.ts +1 -0
- package/package.json +56 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,952 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, {
|
|
5
|
+
get: all[name],
|
|
6
|
+
enumerable: true,
|
|
7
|
+
configurable: true,
|
|
8
|
+
set: (newValue) => all[name] = () => newValue
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// src/errors/WebhookError.ts
|
|
13
|
+
import { Data } from "effect";
|
|
14
|
+
|
|
15
|
+
class WebhookError extends Data.TaggedError("WebhookError") {
|
|
16
|
+
}
|
|
17
|
+
var extractConstraint = (message) => {
|
|
18
|
+
if (message.includes("at most") || message.includes("maximum"))
|
|
19
|
+
return "MaxLength";
|
|
20
|
+
if (message.includes("at least") || message.includes("minimum"))
|
|
21
|
+
return "MinLength";
|
|
22
|
+
if (message.includes("required") || message.includes("missing"))
|
|
23
|
+
return "Required";
|
|
24
|
+
if (message.includes("expected") && message.includes("to be"))
|
|
25
|
+
return "Type";
|
|
26
|
+
if (message.includes("URL") || message.includes("url"))
|
|
27
|
+
return "URL";
|
|
28
|
+
if (message.includes("integer") || message.includes("number"))
|
|
29
|
+
return "Number";
|
|
30
|
+
return;
|
|
31
|
+
};
|
|
32
|
+
var truncateValue = (value, maxLength = 100) => {
|
|
33
|
+
if (typeof value === "string" && value.length > maxLength) {
|
|
34
|
+
return `${value.slice(0, maxLength)}... (${value.length} chars)`;
|
|
35
|
+
}
|
|
36
|
+
if (Array.isArray(value) && value.length > 5) {
|
|
37
|
+
return `[Array with ${value.length} items]`;
|
|
38
|
+
}
|
|
39
|
+
if (typeof value === "object" && value !== null) {
|
|
40
|
+
const keys = Object.keys(value);
|
|
41
|
+
if (keys.length > 5) {
|
|
42
|
+
return `{Object with ${keys.length} keys}`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
};
|
|
47
|
+
var pathToString = (path) => {
|
|
48
|
+
if (path.length === 0)
|
|
49
|
+
return "$";
|
|
50
|
+
return path.reduce((acc, segment) => {
|
|
51
|
+
if (typeof segment === "number") {
|
|
52
|
+
return `${acc}[${segment}]`;
|
|
53
|
+
}
|
|
54
|
+
return `${acc}.${String(segment)}`;
|
|
55
|
+
}, "$");
|
|
56
|
+
};
|
|
57
|
+
var toArray = (value) => Array.isArray(value) ? value : [value];
|
|
58
|
+
var extractIssuesFromParseIssue = (issue, currentPath = []) => {
|
|
59
|
+
const issues = [];
|
|
60
|
+
switch (issue._tag) {
|
|
61
|
+
case "Type": {
|
|
62
|
+
const message = issue.message ?? `Expected ${issue.ast._tag}`;
|
|
63
|
+
issues.push({
|
|
64
|
+
path: pathToString(currentPath),
|
|
65
|
+
message,
|
|
66
|
+
constraint: extractConstraint(message) ?? "Type",
|
|
67
|
+
actual: truncateValue(issue.actual)
|
|
68
|
+
});
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
case "Forbidden": {
|
|
72
|
+
issues.push({
|
|
73
|
+
path: pathToString(currentPath),
|
|
74
|
+
message: issue.message ?? "Value is forbidden",
|
|
75
|
+
constraint: "Forbidden"
|
|
76
|
+
});
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
case "Missing": {
|
|
80
|
+
issues.push({
|
|
81
|
+
path: pathToString(currentPath),
|
|
82
|
+
message: "Required field is missing",
|
|
83
|
+
constraint: "Required"
|
|
84
|
+
});
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
case "Unexpected": {
|
|
88
|
+
issues.push({
|
|
89
|
+
path: pathToString(currentPath),
|
|
90
|
+
message: issue.message ?? "Unexpected field",
|
|
91
|
+
constraint: "Unexpected",
|
|
92
|
+
actual: truncateValue(issue.actual)
|
|
93
|
+
});
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case "Pointer": {
|
|
97
|
+
const pathSegments = toArray(issue.path);
|
|
98
|
+
const newPath = [...currentPath, ...pathSegments];
|
|
99
|
+
issues.push(...extractIssuesFromParseIssue(issue.issue, newPath));
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case "Composite": {
|
|
103
|
+
const subIssues = toArray(issue.issues);
|
|
104
|
+
for (const subIssue of subIssues) {
|
|
105
|
+
issues.push(...extractIssuesFromParseIssue(subIssue, currentPath));
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case "Refinement": {
|
|
110
|
+
issues.push(...extractIssuesFromParseIssue(issue.issue, currentPath));
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
case "Transformation": {
|
|
114
|
+
issues.push(...extractIssuesFromParseIssue(issue.issue, currentPath));
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return issues;
|
|
119
|
+
};
|
|
120
|
+
var parseErrorToIssues = (error) => {
|
|
121
|
+
return extractIssuesFromParseIssue(error.issue);
|
|
122
|
+
};
|
|
123
|
+
var makeIssue = (field, message, options) => {
|
|
124
|
+
const constraint = options?.constraint ?? extractConstraint(message);
|
|
125
|
+
return {
|
|
126
|
+
path: field.startsWith("$") ? field : `$.${field}`,
|
|
127
|
+
message,
|
|
128
|
+
...constraint !== undefined && { constraint },
|
|
129
|
+
...options?.expected !== undefined && { expected: options.expected },
|
|
130
|
+
...options?.actual !== undefined && {
|
|
131
|
+
actual: truncateValue(options.actual)
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
class ValidationError extends Data.TaggedError("ValidationError") {
|
|
137
|
+
format(options) {
|
|
138
|
+
const maxIssues = options?.maxIssues ?? 10;
|
|
139
|
+
const displayIssues = this.issues.slice(0, maxIssues);
|
|
140
|
+
const lines = displayIssues.map((issue) => {
|
|
141
|
+
let line = ` - ${issue.path}: ${issue.message}`;
|
|
142
|
+
if (issue.constraint) {
|
|
143
|
+
line += ` [${issue.constraint}]`;
|
|
144
|
+
}
|
|
145
|
+
return line;
|
|
146
|
+
});
|
|
147
|
+
if (this.issues.length > maxIssues) {
|
|
148
|
+
lines.push(` ... and ${this.issues.length - maxIssues} more issues`);
|
|
149
|
+
}
|
|
150
|
+
return `${this.message}
|
|
151
|
+
${lines.join(`
|
|
152
|
+
`)}`;
|
|
153
|
+
}
|
|
154
|
+
static fromParseError(parseError, context) {
|
|
155
|
+
const issues = parseErrorToIssues(parseError);
|
|
156
|
+
const firstIssue = issues[0];
|
|
157
|
+
const message = issues.length === 1 && firstIssue ? firstIssue.message : `Validation failed (${issues.length} issues)`;
|
|
158
|
+
return new ValidationError({
|
|
159
|
+
message,
|
|
160
|
+
issues,
|
|
161
|
+
...context?.field !== undefined && { field: context.field },
|
|
162
|
+
...context?.value !== undefined && { value: context.value }
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
static fromIssue(field, message, options) {
|
|
166
|
+
const issue = makeIssue(field, message, options);
|
|
167
|
+
return new ValidationError({
|
|
168
|
+
message,
|
|
169
|
+
issues: [issue],
|
|
170
|
+
field,
|
|
171
|
+
value: options?.actual
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
class NetworkError extends Data.TaggedError("NetworkError") {
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
class RateLimitError extends Data.TaggedError("RateLimitError") {
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
class ConfigError extends Data.TaggedError("ConfigError") {
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
class FileError extends Data.TaggedError("FileError") {
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
class HttpError extends Data.TaggedError("HttpError") {
|
|
189
|
+
}
|
|
190
|
+
// src/layers/Config.ts
|
|
191
|
+
import { Context, Effect, Config as EffectConfig, Layer } from "effect";
|
|
192
|
+
|
|
193
|
+
// src/schemas/Discord.ts
|
|
194
|
+
import { Schema } from "effect";
|
|
195
|
+
var DISCORD_WEBHOOK_REGEX = /^https:\/\/(?:(?:canary|ptb)\.)?discord(?:app)?\.com\/api(?:\/v\d+)?\/webhooks\/(?<id>\d+)\/(?<token>[\w-]+)\/?$/;
|
|
196
|
+
var DiscordWebhookUrl = Schema.String.pipe(Schema.pattern(DISCORD_WEBHOOK_REGEX, {
|
|
197
|
+
message: () => "Invalid Discord webhook URL"
|
|
198
|
+
}));
|
|
199
|
+
|
|
200
|
+
// src/layers/Config.ts
|
|
201
|
+
class Config extends Context.Tag("Config")() {
|
|
202
|
+
}
|
|
203
|
+
var validateWebhookUrl = (url) => {
|
|
204
|
+
if (!url || url.trim() === "") {
|
|
205
|
+
return Effect.fail(new ConfigError({
|
|
206
|
+
message: `Webhook URL is empty. Please provide a valid Discord webhook URL.
|
|
207
|
+
` + `Format: https://discord.com/api/webhooks/{webhook_id}/{webhook_token}
|
|
208
|
+
` + "You can create a webhook in Discord: Server Settings > Integrations > Webhooks",
|
|
209
|
+
parameter: "webhookUrl"
|
|
210
|
+
}));
|
|
211
|
+
}
|
|
212
|
+
if (!url.startsWith("https://")) {
|
|
213
|
+
return Effect.fail(new ConfigError({
|
|
214
|
+
message: `Webhook URL must use HTTPS protocol.
|
|
215
|
+
` + `Received: ${url.substring(0, 50)}${url.length > 50 ? "..." : ""}
|
|
216
|
+
` + "Expected format: https://discord.com/api/webhooks/{webhook_id}/{webhook_token}",
|
|
217
|
+
parameter: "webhookUrl"
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
if (!DISCORD_WEBHOOK_REGEX.test(url)) {
|
|
221
|
+
const isDiscordDomain = url.includes("discord.com") || url.includes("discordapp.com");
|
|
222
|
+
const hasWebhooksPath = url.includes("/webhooks/");
|
|
223
|
+
let hint = "";
|
|
224
|
+
if (!isDiscordDomain) {
|
|
225
|
+
hint = "URL must be from discord.com or discordapp.com domain.";
|
|
226
|
+
} else if (!hasWebhooksPath) {
|
|
227
|
+
hint = "URL must include /api/webhooks/ path.";
|
|
228
|
+
} else {
|
|
229
|
+
hint = "URL must include both webhook ID (numeric) and token after /webhooks/.";
|
|
230
|
+
}
|
|
231
|
+
return Effect.fail(new ConfigError({
|
|
232
|
+
message: `Invalid Discord webhook URL format.
|
|
233
|
+
` + `${hint}
|
|
234
|
+
` + `Received: ${url.substring(0, 80)}${url.length > 80 ? "..." : ""}
|
|
235
|
+
` + `Expected format: https://discord.com/api/webhooks/{webhook_id}/{webhook_token}`,
|
|
236
|
+
parameter: "webhookUrl"
|
|
237
|
+
}));
|
|
238
|
+
}
|
|
239
|
+
return Effect.succeed(url);
|
|
240
|
+
};
|
|
241
|
+
var validateRetryOptions = (options) => {
|
|
242
|
+
const errors = [];
|
|
243
|
+
if (options?.maxRetries !== undefined) {
|
|
244
|
+
if (!Number.isInteger(options.maxRetries) || options.maxRetries < 0) {
|
|
245
|
+
errors.push(`maxRetries must be a non-negative integer (received: ${options.maxRetries})`);
|
|
246
|
+
}
|
|
247
|
+
if (options.maxRetries > 10) {
|
|
248
|
+
errors.push(`maxRetries should not exceed 10 to avoid excessive retries (received: ${options.maxRetries})`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (options?.baseDelayMs !== undefined) {
|
|
252
|
+
if (!Number.isInteger(options.baseDelayMs) || options.baseDelayMs < 0) {
|
|
253
|
+
errors.push(`baseDelayMs must be a non-negative integer (received: ${options.baseDelayMs})`);
|
|
254
|
+
}
|
|
255
|
+
if (options.baseDelayMs > 60000) {
|
|
256
|
+
errors.push(`baseDelayMs should not exceed 60000ms (received: ${options.baseDelayMs})`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (options?.maxDelayMs !== undefined) {
|
|
260
|
+
if (!Number.isInteger(options.maxDelayMs) || options.maxDelayMs < 0) {
|
|
261
|
+
errors.push(`maxDelayMs must be a non-negative integer (received: ${options.maxDelayMs})`);
|
|
262
|
+
}
|
|
263
|
+
if (options.baseDelayMs !== undefined && options.maxDelayMs < options.baseDelayMs) {
|
|
264
|
+
errors.push(`maxDelayMs (${options.maxDelayMs}) must be >= baseDelayMs (${options.baseDelayMs})`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
if (errors.length > 0) {
|
|
268
|
+
return Effect.fail(new ConfigError({
|
|
269
|
+
message: `Invalid retry configuration:
|
|
270
|
+
${errors.map((e) => ` - ${e}`).join(`
|
|
271
|
+
`)}`,
|
|
272
|
+
parameter: "retryConfig"
|
|
273
|
+
}));
|
|
274
|
+
}
|
|
275
|
+
return Effect.succeed(options);
|
|
276
|
+
};
|
|
277
|
+
var makeConfig = (webhookUrl, options) => Effect.gen(function* () {
|
|
278
|
+
const validatedUrl = yield* validateWebhookUrl(webhookUrl);
|
|
279
|
+
yield* validateRetryOptions(options);
|
|
280
|
+
return {
|
|
281
|
+
webhook: {
|
|
282
|
+
webhookUrl: validatedUrl,
|
|
283
|
+
maxRetries: options?.maxRetries ?? 3,
|
|
284
|
+
baseDelayMs: options?.baseDelayMs ?? 1000,
|
|
285
|
+
maxDelayMs: options?.maxDelayMs ?? 60000,
|
|
286
|
+
enableJitter: options?.enableJitter ?? true
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
});
|
|
290
|
+
var makeConfigLayer = (webhookUrl, options) => Layer.effect(Config, makeConfig(webhookUrl, options));
|
|
291
|
+
var ConfigFromEnv = Layer.effect(Config, Effect.gen(function* () {
|
|
292
|
+
const webhookUrl = yield* EffectConfig.string("DISCORD_WEBHOOK_URL").pipe(Effect.mapError(() => new ConfigError({
|
|
293
|
+
message: `DISCORD_WEBHOOK_URL environment variable not set.
|
|
294
|
+
` + `Please set DISCORD_WEBHOOK_URL in your environment or .env file.
|
|
295
|
+
` + "See .env.example for all available configuration options.",
|
|
296
|
+
parameter: "DISCORD_WEBHOOK_URL"
|
|
297
|
+
})), Effect.flatMap(validateWebhookUrl));
|
|
298
|
+
const maxRetries = yield* EffectConfig.integer("WEBHOOK_MAX_RETRIES").pipe(EffectConfig.withDefault(3));
|
|
299
|
+
const baseDelayMs = yield* EffectConfig.integer("WEBHOOK_BASE_DELAY_MS").pipe(EffectConfig.withDefault(1000));
|
|
300
|
+
const maxDelayMs = yield* EffectConfig.integer("WEBHOOK_MAX_DELAY_MS").pipe(EffectConfig.withDefault(60000));
|
|
301
|
+
const enableJitter = yield* EffectConfig.boolean("WEBHOOK_ENABLE_JITTER").pipe(EffectConfig.withDefault(true));
|
|
302
|
+
yield* validateRetryOptions({ maxRetries, baseDelayMs, maxDelayMs });
|
|
303
|
+
return {
|
|
304
|
+
webhook: {
|
|
305
|
+
webhookUrl,
|
|
306
|
+
maxRetries,
|
|
307
|
+
baseDelayMs,
|
|
308
|
+
maxDelayMs,
|
|
309
|
+
enableJitter
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
}));
|
|
313
|
+
var parseWebhookUrl = (url) => Effect.gen(function* () {
|
|
314
|
+
const match = url.match(DISCORD_WEBHOOK_REGEX);
|
|
315
|
+
if (!match?.groups?.id || !match.groups.token) {
|
|
316
|
+
return yield* Effect.fail(new ConfigError({
|
|
317
|
+
message: "Invalid webhook URL format",
|
|
318
|
+
parameter: "webhookUrl"
|
|
319
|
+
}));
|
|
320
|
+
}
|
|
321
|
+
const { id, token } = match.groups;
|
|
322
|
+
return { id, token };
|
|
323
|
+
});
|
|
324
|
+
// src/layers/HttpClient.ts
|
|
325
|
+
import { Context as Context2, Duration, Effect as Effect2, Layer as Layer2, Schedule } from "effect";
|
|
326
|
+
class HttpClient extends Context2.Tag("HttpClient")() {
|
|
327
|
+
}
|
|
328
|
+
var parseRateLimitHeaders = (headers) => {
|
|
329
|
+
const retryAfterHeader = headers["retry-after"];
|
|
330
|
+
const rateLimitRemaining = headers["x-ratelimit-remaining"];
|
|
331
|
+
if (!retryAfterHeader && rateLimitRemaining !== "0") {
|
|
332
|
+
return null;
|
|
333
|
+
}
|
|
334
|
+
let retryAfter = 0;
|
|
335
|
+
if (retryAfterHeader) {
|
|
336
|
+
const parsed = Number.parseFloat(retryAfterHeader);
|
|
337
|
+
if (!Number.isNaN(parsed)) {
|
|
338
|
+
retryAfter = Math.ceil(parsed * 1000);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const limit = headers["x-ratelimit-limit"];
|
|
342
|
+
const remaining = headers["x-ratelimit-remaining"];
|
|
343
|
+
const resetHeader = headers["x-ratelimit-reset"];
|
|
344
|
+
const resetAfterHeader = headers["x-ratelimit-reset-after"];
|
|
345
|
+
const globalHeader = headers["x-ratelimit-global"];
|
|
346
|
+
let reset;
|
|
347
|
+
if (resetHeader) {
|
|
348
|
+
const resetTimestamp = Number.parseFloat(resetHeader);
|
|
349
|
+
if (!Number.isNaN(resetTimestamp)) {
|
|
350
|
+
reset = new Date(resetTimestamp * 1000);
|
|
351
|
+
}
|
|
352
|
+
} else if (resetAfterHeader) {
|
|
353
|
+
const resetAfter = Number.parseFloat(resetAfterHeader);
|
|
354
|
+
if (!Number.isNaN(resetAfter)) {
|
|
355
|
+
reset = new Date(Date.now() + resetAfter * 1000);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
if (retryAfter === 0 && reset) {
|
|
359
|
+
retryAfter = Math.max(0, reset.getTime() - Date.now());
|
|
360
|
+
}
|
|
361
|
+
if (retryAfter === 0) {
|
|
362
|
+
retryAfter = 1000;
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
retryAfter,
|
|
366
|
+
...limit && { limit: Number.parseInt(limit, 10) },
|
|
367
|
+
...remaining && { remaining: Number.parseInt(remaining, 10) },
|
|
368
|
+
...reset && { reset },
|
|
369
|
+
global: globalHeader === "true"
|
|
370
|
+
};
|
|
371
|
+
};
|
|
372
|
+
var defaultRetryConfig = {
|
|
373
|
+
maxRetries: 3,
|
|
374
|
+
baseDelay: Duration.seconds(1),
|
|
375
|
+
maxDelay: Duration.seconds(60),
|
|
376
|
+
jitter: true
|
|
377
|
+
};
|
|
378
|
+
var createRetrySchedule = (config) => {
|
|
379
|
+
let policy = Schedule.exponential(config.baseDelay, 2);
|
|
380
|
+
policy = policy.pipe(Schedule.delayed((d) => Duration.lessThanOrEqualTo(d, config.maxDelay) ? d : config.maxDelay));
|
|
381
|
+
if (config.jitter) {
|
|
382
|
+
policy = policy.pipe(Schedule.jittered);
|
|
383
|
+
}
|
|
384
|
+
const capped = Schedule.intersect(policy, Schedule.recurs(config.maxRetries));
|
|
385
|
+
return capped.pipe(Schedule.map(([d]) => d));
|
|
386
|
+
};
|
|
387
|
+
var FetchHttpClient = (retryConfig = defaultRetryConfig) => ({
|
|
388
|
+
retryConfig,
|
|
389
|
+
request: (request) => Effect2.gen(function* () {
|
|
390
|
+
const controller = new AbortController;
|
|
391
|
+
const timeout = request.timeout ? setTimeout(() => controller.abort(), Duration.toMillis(request.timeout)) : undefined;
|
|
392
|
+
const requestHeaders = new Headers({
|
|
393
|
+
"Content-Type": "application/json"
|
|
394
|
+
});
|
|
395
|
+
for (const [key, value] of Object.entries(request.headers ?? {})) {
|
|
396
|
+
requestHeaders.set(key, value);
|
|
397
|
+
}
|
|
398
|
+
const response = yield* Effect2.tryPromise({
|
|
399
|
+
try: () => fetch(request.url, {
|
|
400
|
+
method: request.method,
|
|
401
|
+
headers: requestHeaders,
|
|
402
|
+
body: request.body ? JSON.stringify(request.body) : undefined,
|
|
403
|
+
signal: controller.signal
|
|
404
|
+
}),
|
|
405
|
+
catch: (error) => new NetworkError({
|
|
406
|
+
message: `Network request failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
407
|
+
statusCode: 0
|
|
408
|
+
})
|
|
409
|
+
});
|
|
410
|
+
if (timeout)
|
|
411
|
+
clearTimeout(timeout);
|
|
412
|
+
const rawTextOrFn = yield* Effect2.tryPromise({
|
|
413
|
+
try: () => response.text(),
|
|
414
|
+
catch: () => new NetworkError({ message: "Failed to read response body" })
|
|
415
|
+
});
|
|
416
|
+
const text = typeof rawTextOrFn === "function" ? yield* Effect2.tryPromise({
|
|
417
|
+
try: () => rawTextOrFn(),
|
|
418
|
+
catch: () => new NetworkError({ message: "Failed to read response body" })
|
|
419
|
+
}) : String(rawTextOrFn ?? "");
|
|
420
|
+
const headers = {};
|
|
421
|
+
response.headers.forEach((value, key) => {
|
|
422
|
+
headers[key.toLowerCase()] = value;
|
|
423
|
+
});
|
|
424
|
+
let body = text;
|
|
425
|
+
if (headers["content-type"]?.includes("application/json") && text) {
|
|
426
|
+
try {
|
|
427
|
+
body = JSON.parse(text);
|
|
428
|
+
} catch {}
|
|
429
|
+
}
|
|
430
|
+
const httpResponse = {
|
|
431
|
+
status: response.status,
|
|
432
|
+
statusText: response.statusText,
|
|
433
|
+
headers,
|
|
434
|
+
body,
|
|
435
|
+
text
|
|
436
|
+
};
|
|
437
|
+
if (!response.ok) {
|
|
438
|
+
if (response.status === 429) {
|
|
439
|
+
const rateLimitInfo = parseRateLimitHeaders(headers);
|
|
440
|
+
return yield* Effect2.fail(new RateLimitError({
|
|
441
|
+
message: `Rate limited: retry after ${rateLimitInfo?.retryAfter ?? 1000}ms`,
|
|
442
|
+
retryAfter: rateLimitInfo?.retryAfter ?? 1000,
|
|
443
|
+
...rateLimitInfo?.limit !== undefined && {
|
|
444
|
+
limit: rateLimitInfo.limit
|
|
445
|
+
},
|
|
446
|
+
...rateLimitInfo?.remaining !== undefined && {
|
|
447
|
+
remaining: rateLimitInfo.remaining
|
|
448
|
+
},
|
|
449
|
+
...rateLimitInfo?.reset !== undefined && {
|
|
450
|
+
reset: rateLimitInfo.reset
|
|
451
|
+
}
|
|
452
|
+
}));
|
|
453
|
+
}
|
|
454
|
+
return yield* Effect2.fail(new HttpError({
|
|
455
|
+
message: `HTTP ${response.status}: ${response.statusText}`,
|
|
456
|
+
request: {
|
|
457
|
+
method: request.method,
|
|
458
|
+
url: request.url
|
|
459
|
+
},
|
|
460
|
+
response: {
|
|
461
|
+
status: response.status,
|
|
462
|
+
statusText: response.statusText,
|
|
463
|
+
headers,
|
|
464
|
+
body
|
|
465
|
+
}
|
|
466
|
+
}));
|
|
467
|
+
}
|
|
468
|
+
return httpResponse;
|
|
469
|
+
})
|
|
470
|
+
});
|
|
471
|
+
var HttpClientLive = Layer2.succeed(HttpClient, FetchHttpClient());
|
|
472
|
+
var makeHttpClientLayer = (retryConfig) => Layer2.succeed(HttpClient, FetchHttpClient({ ...defaultRetryConfig, ...retryConfig }));
|
|
473
|
+
// src/pipes/Embed.ts
|
|
474
|
+
var exports_Embed = {};
|
|
475
|
+
__export(exports_Embed, {
|
|
476
|
+
setVideo: () => setVideo,
|
|
477
|
+
setURL: () => setURL,
|
|
478
|
+
setTitle: () => setTitle,
|
|
479
|
+
setTimestamp: () => setTimestamp,
|
|
480
|
+
setThumbnail: () => setThumbnail,
|
|
481
|
+
setProvider: () => setProvider,
|
|
482
|
+
setImage: () => setImage,
|
|
483
|
+
setFooter: () => setFooter,
|
|
484
|
+
setFields: () => setFields,
|
|
485
|
+
setDescription: () => setDescription,
|
|
486
|
+
setColor: () => setColor,
|
|
487
|
+
setAuthor: () => setAuthor,
|
|
488
|
+
make: () => make,
|
|
489
|
+
buildDirect: () => buildDirect,
|
|
490
|
+
build: () => build,
|
|
491
|
+
addFieldImpl: () => addFieldImpl,
|
|
492
|
+
addFieldDirect: () => addFieldDirect,
|
|
493
|
+
addField: () => addField
|
|
494
|
+
});
|
|
495
|
+
import { Effect as Effect3, pipe, Schema as Schema6 } from "effect";
|
|
496
|
+
|
|
497
|
+
// src/schemas/Common.ts
|
|
498
|
+
import { Schema as Schema2 } from "effect";
|
|
499
|
+
var maxLength = (max) => Schema2.String.pipe(Schema2.maxLength(max));
|
|
500
|
+
var positiveInt = Schema2.Number.pipe(Schema2.int(), Schema2.positive());
|
|
501
|
+
var UrlString = Schema2.String.pipe(Schema2.filter((s) => {
|
|
502
|
+
try {
|
|
503
|
+
new URL(s);
|
|
504
|
+
return true;
|
|
505
|
+
} catch {
|
|
506
|
+
return false;
|
|
507
|
+
}
|
|
508
|
+
}, { message: () => "Invalid URL format" }), Schema2.annotations({
|
|
509
|
+
arbitrary: () => (fc) => fc.webUrl()
|
|
510
|
+
}));
|
|
511
|
+
var ISO8601Timestamp = Schema2.String.pipe(Schema2.filter((s) => {
|
|
512
|
+
const iso8601Regex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{3})?Z?$/;
|
|
513
|
+
return iso8601Regex.test(s);
|
|
514
|
+
}, { message: () => "Invalid ISO8601 timestamp format" }), Schema2.annotations({
|
|
515
|
+
arbitrary: () => (fc) => fc.date().map((d) => d.toISOString())
|
|
516
|
+
}));
|
|
517
|
+
var ColorCode = Schema2.Number.pipe(Schema2.int(), Schema2.greaterThanOrEqualTo(0), Schema2.lessThanOrEqualTo(16777215));
|
|
518
|
+
var Uint8ArraySchema = Schema2.instanceOf(Uint8Array).pipe(Schema2.annotations({
|
|
519
|
+
arbitrary: () => (fc) => fc.uint8Array().map((u8) => {
|
|
520
|
+
const ab = new ArrayBuffer(u8.byteLength);
|
|
521
|
+
new Uint8Array(ab).set(u8);
|
|
522
|
+
return new Uint8Array(ab);
|
|
523
|
+
})
|
|
524
|
+
}));
|
|
525
|
+
|
|
526
|
+
// src/schemas/Embed.ts
|
|
527
|
+
import { Schema as Schema4 } from "effect";
|
|
528
|
+
|
|
529
|
+
// src/schemas/Field.ts
|
|
530
|
+
import { Schema as Schema3 } from "effect";
|
|
531
|
+
class Field extends Schema3.Class("Field")({
|
|
532
|
+
name: maxLength(256),
|
|
533
|
+
value: maxLength(1024),
|
|
534
|
+
inline: Schema3.optional(Schema3.Boolean)
|
|
535
|
+
}) {
|
|
536
|
+
}
|
|
537
|
+
var FieldArray = Schema3.Array(Field).pipe(Schema3.maxItems(25));
|
|
538
|
+
|
|
539
|
+
// src/schemas/Embed.ts
|
|
540
|
+
class Attachment extends Schema4.Class("Attachment")({
|
|
541
|
+
url: Schema4.String,
|
|
542
|
+
proxyUrl: Schema4.optional(Schema4.String),
|
|
543
|
+
filename: Schema4.optional(Schema4.String),
|
|
544
|
+
size: Schema4.optional(positiveInt),
|
|
545
|
+
height: Schema4.optional(positiveInt),
|
|
546
|
+
width: Schema4.optional(positiveInt)
|
|
547
|
+
}) {
|
|
548
|
+
}
|
|
549
|
+
var UrlOrAttachment = Schema4.Union(UrlString, Attachment);
|
|
550
|
+
|
|
551
|
+
class Author extends Schema4.Class("Author")({
|
|
552
|
+
name: Schema4.optional(maxLength(256)),
|
|
553
|
+
url: Schema4.optional(UrlString),
|
|
554
|
+
icon_url: Schema4.optional(UrlOrAttachment),
|
|
555
|
+
proxy_icon_url: Schema4.optional(Schema4.String)
|
|
556
|
+
}) {
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
class Footer extends Schema4.Class("Footer")({
|
|
560
|
+
text: maxLength(2048),
|
|
561
|
+
icon_url: Schema4.optional(UrlOrAttachment),
|
|
562
|
+
proxy_icon_url: Schema4.optional(Schema4.String)
|
|
563
|
+
}) {
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
class Image extends Schema4.Class("Image")({
|
|
567
|
+
url: UrlOrAttachment,
|
|
568
|
+
proxy_url: Schema4.optional(Schema4.String),
|
|
569
|
+
height: Schema4.optional(positiveInt),
|
|
570
|
+
width: Schema4.optional(positiveInt)
|
|
571
|
+
}) {
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
class Thumbnail extends Schema4.Class("Thumbnail")({
|
|
575
|
+
url: UrlOrAttachment,
|
|
576
|
+
proxy_url: Schema4.optional(Schema4.String),
|
|
577
|
+
height: Schema4.optional(positiveInt),
|
|
578
|
+
width: Schema4.optional(positiveInt)
|
|
579
|
+
}) {
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
class Video extends Schema4.Class("Video")({
|
|
583
|
+
url: Schema4.optional(UrlString),
|
|
584
|
+
proxy_url: Schema4.optional(Schema4.String),
|
|
585
|
+
height: Schema4.optional(positiveInt),
|
|
586
|
+
width: Schema4.optional(positiveInt)
|
|
587
|
+
}) {
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
class Provider extends Schema4.Class("Provider")({
|
|
591
|
+
name: Schema4.optional(Schema4.String),
|
|
592
|
+
url: Schema4.optional(UrlString)
|
|
593
|
+
}) {
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
class Embed extends Schema4.Class("Embed")({
|
|
597
|
+
title: Schema4.optional(maxLength(256)),
|
|
598
|
+
type: Schema4.optional(Schema4.Literal("rich")),
|
|
599
|
+
url: Schema4.optional(UrlString),
|
|
600
|
+
description: Schema4.optional(maxLength(4096)),
|
|
601
|
+
timestamp: Schema4.optional(ISO8601Timestamp),
|
|
602
|
+
color: Schema4.optional(ColorCode),
|
|
603
|
+
footer: Schema4.optional(Footer),
|
|
604
|
+
image: Schema4.optional(Image),
|
|
605
|
+
thumbnail: Schema4.optional(Thumbnail),
|
|
606
|
+
video: Schema4.optional(Video),
|
|
607
|
+
provider: Schema4.optional(Provider),
|
|
608
|
+
author: Schema4.optional(Author),
|
|
609
|
+
fields: Schema4.optional(FieldArray)
|
|
610
|
+
}) {
|
|
611
|
+
}
|
|
612
|
+
var EmbedArray = Schema4.Array(Embed).pipe(Schema4.maxItems(10));
|
|
613
|
+
|
|
614
|
+
// src/schemas/Webhook.ts
|
|
615
|
+
import { Schema as Schema5 } from "effect";
|
|
616
|
+
var WebhookFile = Schema5.Union(Schema5.String, Attachment, Schema5.Struct({
|
|
617
|
+
name: Schema5.String,
|
|
618
|
+
data: Schema5.Union(Schema5.String, Uint8ArraySchema)
|
|
619
|
+
}));
|
|
620
|
+
|
|
621
|
+
class Webhook extends Schema5.Class("Webhook")({
|
|
622
|
+
username: Schema5.optional(maxLength(80)),
|
|
623
|
+
avatar_url: Schema5.optional(UrlString),
|
|
624
|
+
tts: Schema5.optional(Schema5.Boolean),
|
|
625
|
+
content: Schema5.optional(maxLength(2000)),
|
|
626
|
+
file: Schema5.optional(WebhookFile),
|
|
627
|
+
embeds: Schema5.optional(EmbedArray)
|
|
628
|
+
}) {
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
class WebhookParameter extends Schema5.Class("WebhookParameter")({
|
|
632
|
+
name: Schema5.optional(maxLength(80)),
|
|
633
|
+
avatar: Schema5.optional(UrlString),
|
|
634
|
+
channel_id: Schema5.optional(Schema5.String)
|
|
635
|
+
}) {
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
class WebhookResponse extends Schema5.Class("WebhookResponse")({
|
|
639
|
+
id: Schema5.String,
|
|
640
|
+
type: Schema5.Number,
|
|
641
|
+
guild_id: Schema5.optional(Schema5.String),
|
|
642
|
+
channel_id: Schema5.String,
|
|
643
|
+
user: Schema5.optional(Schema5.Struct({
|
|
644
|
+
id: Schema5.String,
|
|
645
|
+
username: Schema5.String,
|
|
646
|
+
avatar: Schema5.optional(Schema5.String),
|
|
647
|
+
discriminator: Schema5.String,
|
|
648
|
+
public_flags: Schema5.optional(Schema5.Number)
|
|
649
|
+
})),
|
|
650
|
+
name: Schema5.optional(Schema5.String),
|
|
651
|
+
avatar: Schema5.optional(Schema5.String),
|
|
652
|
+
token: Schema5.optional(Schema5.String),
|
|
653
|
+
application_id: Schema5.optional(Schema5.String),
|
|
654
|
+
source_guild: Schema5.optional(Schema5.Struct({
|
|
655
|
+
id: Schema5.String,
|
|
656
|
+
name: Schema5.String,
|
|
657
|
+
icon: Schema5.optional(Schema5.String)
|
|
658
|
+
})),
|
|
659
|
+
source_channel: Schema5.optional(Schema5.Struct({
|
|
660
|
+
id: Schema5.String,
|
|
661
|
+
name: Schema5.String
|
|
662
|
+
})),
|
|
663
|
+
url: Schema5.optional(UrlString)
|
|
664
|
+
}) {
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// src/pipes/Embed.ts
|
|
668
|
+
var createDraft = () => ({});
|
|
669
|
+
var setTitleImpl = (title) => (d) => pipe(Schema6.decodeUnknown(maxLength(256))(title), Effect3.mapError(() => ValidationError.fromIssue("title", "Title must be 256 characters or less", {
|
|
670
|
+
constraint: "MaxLength",
|
|
671
|
+
expected: "256 characters or less",
|
|
672
|
+
actual: title.length
|
|
673
|
+
})), Effect3.as({ ...d, title }));
|
|
674
|
+
var setURLImpl = (url) => (d) => pipe(Schema6.decodeUnknown(UrlString)(url), Effect3.mapError(() => ValidationError.fromIssue("url", "Invalid URL", {
|
|
675
|
+
constraint: "URL",
|
|
676
|
+
expected: "valid URL",
|
|
677
|
+
actual: url
|
|
678
|
+
})), Effect3.as({ ...d, url }));
|
|
679
|
+
var setDescriptionImpl = (description) => (d) => pipe(Schema6.decodeUnknown(maxLength(4096))(description), Effect3.mapError(() => ValidationError.fromIssue("description", "Description must be 4096 characters or less", {
|
|
680
|
+
constraint: "MaxLength",
|
|
681
|
+
expected: "4096 characters or less",
|
|
682
|
+
actual: description.length
|
|
683
|
+
})), Effect3.as({ ...d, description }));
|
|
684
|
+
var setTimestampImpl = (date) => (d) => Effect3.succeed({ ...d, timestamp: (date ?? new Date).toISOString() });
|
|
685
|
+
var setColorImpl = (color) => (d) => {
|
|
686
|
+
const colorValue = typeof color === "string" ? parseInt(color.replace("#", ""), 16) : color;
|
|
687
|
+
return pipe(Schema6.decodeUnknown(ColorCode)(colorValue), Effect3.mapError(() => ValidationError.fromIssue("color", "Invalid color code (must be 0-16777215)", {
|
|
688
|
+
constraint: "Range",
|
|
689
|
+
expected: "0-16777215",
|
|
690
|
+
actual: colorValue
|
|
691
|
+
})), Effect3.as({ ...d, color: colorValue }));
|
|
692
|
+
};
|
|
693
|
+
var setFooterImpl = (footerOrText, icon_url, proxy_icon_url) => (d) => {
|
|
694
|
+
const footer = typeof footerOrText === "string" ? { text: footerOrText, icon_url, proxy_icon_url } : footerOrText;
|
|
695
|
+
return pipe(Schema6.decodeUnknown(Footer)(footer), Effect3.mapError((e) => ValidationError.fromParseError(e, { field: "footer", value: footer })), Effect3.as({ ...d, footer }));
|
|
696
|
+
};
|
|
697
|
+
var setImageImpl = (imageOrUrl, height, width) => (d) => {
|
|
698
|
+
const image = typeof imageOrUrl === "string" ? { url: imageOrUrl, height, width } : imageOrUrl;
|
|
699
|
+
return pipe(Schema6.decodeUnknown(Image)(image), Effect3.mapError((e) => ValidationError.fromParseError(e, { field: "image", value: image })), Effect3.as({ ...d, image }));
|
|
700
|
+
};
|
|
701
|
+
var setThumbnailImpl = (thumbnailOrUrl, height, width) => (d) => {
|
|
702
|
+
const thumbnail = typeof thumbnailOrUrl === "string" ? { url: thumbnailOrUrl, height, width } : thumbnailOrUrl;
|
|
703
|
+
return pipe(Schema6.decodeUnknown(Thumbnail)(thumbnail), Effect3.mapError((e) => ValidationError.fromParseError(e, {
|
|
704
|
+
field: "thumbnail",
|
|
705
|
+
value: thumbnail
|
|
706
|
+
})), Effect3.as({ ...d, thumbnail }));
|
|
707
|
+
};
|
|
708
|
+
var setVideoImpl = (videoOrUrl, height, width) => (d) => {
|
|
709
|
+
const video = typeof videoOrUrl === "string" ? { url: videoOrUrl, height, width } : videoOrUrl;
|
|
710
|
+
return pipe(Schema6.decodeUnknown(Video)(video), Effect3.mapError((e) => ValidationError.fromParseError(e, { field: "video", value: video })), Effect3.as({ ...d, video }));
|
|
711
|
+
};
|
|
712
|
+
var setProviderImpl = (providerOrName, url) => (d) => {
|
|
713
|
+
const provider = typeof providerOrName === "string" ? { name: providerOrName, url } : providerOrName;
|
|
714
|
+
return pipe(Schema6.decodeUnknown(Provider)(provider), Effect3.mapError((e) => ValidationError.fromParseError(e, {
|
|
715
|
+
field: "provider",
|
|
716
|
+
value: provider
|
|
717
|
+
})), Effect3.as({ ...d, provider }));
|
|
718
|
+
};
|
|
719
|
+
var setAuthorImpl = (authorOrName, url, icon_url) => (d) => {
|
|
720
|
+
const author = typeof authorOrName === "string" ? { name: authorOrName, url, icon_url } : authorOrName;
|
|
721
|
+
return pipe(Schema6.decodeUnknown(Author)(author), Effect3.mapError((e) => ValidationError.fromParseError(e, { field: "author", value: author })), Effect3.as({ ...d, author }));
|
|
722
|
+
};
|
|
723
|
+
var addFieldImpl = (fieldOrName, value, inline = false) => (d) => {
|
|
724
|
+
const currentFields = d.fields || [];
|
|
725
|
+
if (currentFields.length >= 25) {
|
|
726
|
+
return Effect3.fail(ValidationError.fromIssue("fields", "Cannot add more than 25 fields", {
|
|
727
|
+
constraint: "MaxItems",
|
|
728
|
+
expected: "25 fields or less",
|
|
729
|
+
actual: currentFields.length + 1
|
|
730
|
+
}));
|
|
731
|
+
}
|
|
732
|
+
const field = typeof fieldOrName === "string" ? { name: fieldOrName, value: value ?? "", inline } : fieldOrName;
|
|
733
|
+
return pipe(Schema6.decodeUnknown(Field)(field), Effect3.mapError((e) => ValidationError.fromParseError(e, { field: "fields", value: field })), Effect3.map(() => ({ ...d, fields: [...currentFields, field] })));
|
|
734
|
+
};
|
|
735
|
+
var setFieldsImpl = (fields) => (d) => {
|
|
736
|
+
if (fields.length > 25) {
|
|
737
|
+
return Effect3.fail(ValidationError.fromIssue("fields", "Cannot have more than 25 fields", {
|
|
738
|
+
constraint: "MaxItems",
|
|
739
|
+
expected: "25 fields or less",
|
|
740
|
+
actual: fields.length
|
|
741
|
+
}));
|
|
742
|
+
}
|
|
743
|
+
return pipe(Effect3.forEach(fields, (f) => Schema6.decodeUnknown(Field)(f)), Effect3.mapError((e) => ValidationError.fromParseError(e, { field: "fields", value: fields })), Effect3.map(() => ({ ...d, fields: [...fields] })));
|
|
744
|
+
};
|
|
745
|
+
var buildImpl = (d) => pipe(Schema6.decodeUnknown(Embed)({ ...d, type: "rich" }), Effect3.mapError((e) => ValidationError.fromParseError(e, { field: "embed", value: d })));
|
|
746
|
+
var buildDirect = buildImpl;
|
|
747
|
+
var addFieldDirect = addFieldImpl;
|
|
748
|
+
var make = Effect3.succeed(createDraft());
|
|
749
|
+
var chain = (f) => (self) => pipe(self, Effect3.flatMap(f));
|
|
750
|
+
var setTitle = (title) => chain(setTitleImpl(title));
|
|
751
|
+
var setURL = (url) => chain(setURLImpl(url));
|
|
752
|
+
var setDescription = (description) => chain(setDescriptionImpl(description));
|
|
753
|
+
var setTimestamp = (date) => chain(setTimestampImpl(date));
|
|
754
|
+
var setColor = (color) => chain(setColorImpl(color));
|
|
755
|
+
var setFooter = (footer, icon_url, proxy_icon_url) => chain(setFooterImpl(footer, icon_url, proxy_icon_url));
|
|
756
|
+
var setImage = (image, height, width) => chain(setImageImpl(image, height, width));
|
|
757
|
+
var setThumbnail = (thumbnail, height, width) => chain(setThumbnailImpl(thumbnail, height, width));
|
|
758
|
+
var setVideo = (video, height, width) => chain(setVideoImpl(video, height, width));
|
|
759
|
+
var setProvider = (provider, url) => chain(setProviderImpl(provider, url));
|
|
760
|
+
var setAuthor = (author, url, icon_url) => chain(setAuthorImpl(author, url, icon_url));
|
|
761
|
+
var addField = (field, value, inline = false) => chain(addFieldImpl(field, value, inline));
|
|
762
|
+
var setFields = (fields) => chain(setFieldsImpl(fields));
|
|
763
|
+
var build = (self) => pipe(self, Effect3.flatMap(buildImpl));
|
|
764
|
+
// src/pipes/Webhook.ts
|
|
765
|
+
var exports_Webhook = {};
|
|
766
|
+
__export(exports_Webhook, {
|
|
767
|
+
setUsername: () => setUsername,
|
|
768
|
+
setTTS: () => setTTS,
|
|
769
|
+
setFile: () => setFile,
|
|
770
|
+
setContent: () => setContent,
|
|
771
|
+
setAvatarUrl: () => setAvatarUrl,
|
|
772
|
+
modifyParams: () => modifyParams,
|
|
773
|
+
make: () => make2,
|
|
774
|
+
buildDirect: () => buildDirect2,
|
|
775
|
+
build: () => build2,
|
|
776
|
+
addEmbedImpl: () => addEmbedImpl,
|
|
777
|
+
addEmbedDirect: () => addEmbedDirect,
|
|
778
|
+
addEmbed: () => addEmbed
|
|
779
|
+
});
|
|
780
|
+
import { Effect as Effect4, pipe as pipe2, Schema as Schema7 } from "effect";
|
|
781
|
+
var createDraft2 = () => ({});
|
|
782
|
+
var setUsernameImpl = (username) => (d) => pipe2(Schema7.decodeUnknown(maxLength(80))(username), Effect4.mapError(() => ValidationError.fromIssue("username", "Username must be 80 characters or less", {
|
|
783
|
+
constraint: "MaxLength",
|
|
784
|
+
expected: "80 characters or less",
|
|
785
|
+
actual: username.length
|
|
786
|
+
})), Effect4.as({ ...d, username }));
|
|
787
|
+
var setAvatarUrlImpl = (avatar_url) => (d) => pipe2(Schema7.decodeUnknown(UrlString)(avatar_url), Effect4.mapError(() => ValidationError.fromIssue("avatar_url", "Invalid avatar URL", {
|
|
788
|
+
constraint: "URL",
|
|
789
|
+
expected: "valid URL",
|
|
790
|
+
actual: avatar_url
|
|
791
|
+
})), Effect4.as({ ...d, avatar_url }));
|
|
792
|
+
var setContentImpl = (content) => (d) => pipe2(Schema7.decodeUnknown(maxLength(2000))(content), Effect4.mapError(() => ValidationError.fromIssue("content", "Content must be 2000 characters or less", {
|
|
793
|
+
constraint: "MaxLength",
|
|
794
|
+
expected: "2000 characters or less",
|
|
795
|
+
actual: content.length
|
|
796
|
+
})), Effect4.as({ ...d, content }));
|
|
797
|
+
var setTTSImpl = (tts) => (d) => Effect4.succeed({ ...d, tts });
|
|
798
|
+
var addEmbedImpl = (embed) => (d) => {
|
|
799
|
+
const current = d.embeds || [];
|
|
800
|
+
const next = [...current, embed];
|
|
801
|
+
return pipe2(Schema7.decodeUnknown(EmbedArray)(next), Effect4.mapError(() => ValidationError.fromIssue("embeds", "Cannot have more than 10 embeds", {
|
|
802
|
+
constraint: "MaxItems",
|
|
803
|
+
expected: "10 embeds or less",
|
|
804
|
+
actual: next.length
|
|
805
|
+
})), Effect4.as({ ...d, embeds: next }));
|
|
806
|
+
};
|
|
807
|
+
var setFileImpl = (file) => (d) => Effect4.succeed({ ...d, file });
|
|
808
|
+
var buildImpl2 = (d) => pipe2(Schema7.decodeUnknown(Webhook)(d), Effect4.mapError((e) => ValidationError.fromParseError(e, { field: "webhook", value: d })));
|
|
809
|
+
var buildDirect2 = buildImpl2;
|
|
810
|
+
var addEmbedDirect = addEmbedImpl;
|
|
811
|
+
var modifyParams = (p) => pipe2(Schema7.decodeUnknown(WebhookParameter)(p), Effect4.mapError((e) => ValidationError.fromParseError(e, { field: "params", value: p })));
|
|
812
|
+
var make2 = Effect4.succeed(createDraft2());
|
|
813
|
+
var chain2 = (f) => (self) => pipe2(self, Effect4.flatMap(f));
|
|
814
|
+
var setUsername = (username) => chain2(setUsernameImpl(username));
|
|
815
|
+
var setAvatarUrl = (url) => chain2(setAvatarUrlImpl(url));
|
|
816
|
+
var setContent = (content) => chain2(setContentImpl(content));
|
|
817
|
+
var setTTS = (tts) => chain2(setTTSImpl(tts));
|
|
818
|
+
var addEmbed = (embed) => chain2(addEmbedImpl(embed));
|
|
819
|
+
var setFile = (file) => chain2(setFileImpl(file));
|
|
820
|
+
var build2 = (self) => pipe2(self, Effect4.flatMap(buildImpl2));
|
|
821
|
+
// src/services/WebhookService.ts
|
|
822
|
+
import {
|
|
823
|
+
Context as Context3,
|
|
824
|
+
Duration as Duration2,
|
|
825
|
+
Effect as Effect5,
|
|
826
|
+
Layer as Layer3,
|
|
827
|
+
pipe as pipe3,
|
|
828
|
+
Schedule as Schedule2,
|
|
829
|
+
Schema as Schema8
|
|
830
|
+
} from "effect";
|
|
831
|
+
class WebhookService extends Context3.Tag("WebhookService")() {
|
|
832
|
+
}
|
|
833
|
+
var makeWebhookService = Effect5.gen(function* () {
|
|
834
|
+
const config = yield* Config;
|
|
835
|
+
const httpClient = yield* HttpClient;
|
|
836
|
+
const webhookUrl = config.webhook.webhookUrl;
|
|
837
|
+
const retrySchedule = createRetrySchedule({
|
|
838
|
+
maxRetries: config.webhook.maxRetries ?? httpClient.retryConfig.maxRetries,
|
|
839
|
+
baseDelay: Duration2.millis(config.webhook.baseDelayMs ?? Duration2.toMillis(httpClient.retryConfig.baseDelay)),
|
|
840
|
+
maxDelay: Duration2.millis(config.webhook.maxDelayMs ?? Duration2.toMillis(httpClient.retryConfig.maxDelay)),
|
|
841
|
+
jitter: config.webhook.enableJitter ?? httpClient.retryConfig.jitter
|
|
842
|
+
});
|
|
843
|
+
const sendWebhook = (webhook) => pipe3(Schema8.decodeUnknown(Webhook)(webhook), Effect5.mapError((e) => ValidationError.fromParseError(e, { field: "webhook", value: webhook })), Effect5.flatMap((validatedWebhook) => httpClient.request({
|
|
844
|
+
method: "POST",
|
|
845
|
+
url: webhookUrl,
|
|
846
|
+
body: validatedWebhook
|
|
847
|
+
}).pipe(Effect5.retry(retrySchedule.pipe(Schedule2.whileInput((error) => {
|
|
848
|
+
if (error instanceof NetworkError)
|
|
849
|
+
return true;
|
|
850
|
+
if (error instanceof RateLimitError)
|
|
851
|
+
return true;
|
|
852
|
+
if (error instanceof HttpError) {
|
|
853
|
+
const status = error.response?.status;
|
|
854
|
+
return typeof status === "number" && status >= 500;
|
|
855
|
+
}
|
|
856
|
+
return false;
|
|
857
|
+
}))))), Effect5.map((response) => response.status === 204 && response.text === ""));
|
|
858
|
+
const modifyWebhook = (params) => pipe3(Schema8.decodeUnknown(WebhookParameter)(params), Effect5.mapError((e) => ValidationError.fromParseError(e, { field: "params", value: params })), Effect5.flatMap((validatedParams) => httpClient.request({
|
|
859
|
+
method: "PATCH",
|
|
860
|
+
url: webhookUrl,
|
|
861
|
+
body: validatedParams
|
|
862
|
+
})), Effect5.flatMap((response) => Schema8.decodeUnknown(WebhookResponse)(response.body).pipe(Effect5.mapError(() => new WebhookError({
|
|
863
|
+
message: "Invalid webhook response",
|
|
864
|
+
cause: response.body
|
|
865
|
+
})))));
|
|
866
|
+
const getWebhook = () => pipe3(httpClient.request({
|
|
867
|
+
method: "GET",
|
|
868
|
+
url: webhookUrl
|
|
869
|
+
}), Effect5.flatMap((response) => Schema8.decodeUnknown(WebhookResponse)(response.body).pipe(Effect5.mapError(() => new WebhookError({
|
|
870
|
+
message: "Invalid webhook response",
|
|
871
|
+
cause: response.body
|
|
872
|
+
})))));
|
|
873
|
+
const validateWebhook = () => pipe3(httpClient.request({
|
|
874
|
+
method: "GET",
|
|
875
|
+
url: webhookUrl
|
|
876
|
+
}), Effect5.as(true), Effect5.catchAll(() => Effect5.succeed(false)));
|
|
877
|
+
const deleteWebhook = () => pipe3(httpClient.request({
|
|
878
|
+
method: "DELETE",
|
|
879
|
+
url: webhookUrl
|
|
880
|
+
}), Effect5.map((response) => response.status === 204));
|
|
881
|
+
return {
|
|
882
|
+
sendWebhook,
|
|
883
|
+
modifyWebhook,
|
|
884
|
+
getWebhook,
|
|
885
|
+
deleteWebhook,
|
|
886
|
+
validateWebhook
|
|
887
|
+
};
|
|
888
|
+
});
|
|
889
|
+
var WebhookServiceLive = Layer3.effect(WebhookService, makeWebhookService);
|
|
890
|
+
var sendWebhook = (webhook) => Effect5.flatMap(WebhookService, (service) => service.sendWebhook(webhook));
|
|
891
|
+
var modifyWebhook = (params) => Effect5.flatMap(WebhookService, (service) => service.modifyWebhook(params));
|
|
892
|
+
var getWebhook = () => Effect5.flatMap(WebhookService, (service) => service.getWebhook());
|
|
893
|
+
var deleteWebhook = () => Effect5.flatMap(WebhookService, (service) => service.deleteWebhook());
|
|
894
|
+
var validateWebhook = () => Effect5.flatMap(WebhookService, (service) => service.validateWebhook());
|
|
895
|
+
export {
|
|
896
|
+
validateWebhook,
|
|
897
|
+
sendWebhook,
|
|
898
|
+
positiveInt,
|
|
899
|
+
parseWebhookUrl,
|
|
900
|
+
parseRateLimitHeaders,
|
|
901
|
+
parseErrorToIssues,
|
|
902
|
+
modifyWebhook,
|
|
903
|
+
maxLength,
|
|
904
|
+
makeWebhookService,
|
|
905
|
+
makeIssue,
|
|
906
|
+
makeHttpClientLayer,
|
|
907
|
+
makeConfigLayer,
|
|
908
|
+
makeConfig,
|
|
909
|
+
getWebhook,
|
|
910
|
+
deleteWebhook,
|
|
911
|
+
defaultRetryConfig,
|
|
912
|
+
createRetrySchedule,
|
|
913
|
+
WebhookServiceLive,
|
|
914
|
+
WebhookService,
|
|
915
|
+
WebhookResponse,
|
|
916
|
+
WebhookParameter,
|
|
917
|
+
WebhookFile,
|
|
918
|
+
WebhookError,
|
|
919
|
+
exports_Webhook as Webhook,
|
|
920
|
+
Video,
|
|
921
|
+
ValidationError,
|
|
922
|
+
UrlString,
|
|
923
|
+
UrlOrAttachment,
|
|
924
|
+
Uint8ArraySchema,
|
|
925
|
+
Thumbnail,
|
|
926
|
+
RateLimitError,
|
|
927
|
+
Provider,
|
|
928
|
+
NetworkError,
|
|
929
|
+
Image,
|
|
930
|
+
ISO8601Timestamp,
|
|
931
|
+
HttpError,
|
|
932
|
+
HttpClientLive,
|
|
933
|
+
HttpClient,
|
|
934
|
+
Footer,
|
|
935
|
+
FileError,
|
|
936
|
+
FieldArray,
|
|
937
|
+
Field,
|
|
938
|
+
FetchHttpClient,
|
|
939
|
+
EmbedArray,
|
|
940
|
+
exports_Embed as Embed,
|
|
941
|
+
DiscordWebhookUrl,
|
|
942
|
+
DISCORD_WEBHOOK_REGEX,
|
|
943
|
+
ConfigFromEnv,
|
|
944
|
+
ConfigError,
|
|
945
|
+
Config,
|
|
946
|
+
ColorCode,
|
|
947
|
+
Author,
|
|
948
|
+
Attachment
|
|
949
|
+
};
|
|
950
|
+
|
|
951
|
+
//# debugId=8ED6FFCE1ADA022964756E2164756E21
|
|
952
|
+
//# sourceMappingURL=index.js.map
|