@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/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