@effect-uai/discord 0.17.0 → 0.18.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.
Files changed (32) hide show
  1. package/README.md +1 -1
  2. package/dist/Discord.d.mts +3 -3
  3. package/dist/Discord.d.mts.map +1 -1
  4. package/dist/Discord.mjs +7 -5
  5. package/dist/Discord.mjs.map +1 -1
  6. package/dist/dist-DHer1cql.mjs +1533 -0
  7. package/dist/dist-DHer1cql.mjs.map +1 -0
  8. package/dist/{gateway-C_Ae1HYB.d.mts → gateway-CYyZYm4m.d.mts} +3 -2
  9. package/dist/gateway-CYyZYm4m.d.mts.map +1 -0
  10. package/dist/{test.DNmyFkvJ-N3QqMRwZ.mjs → index.C4tKZ0yW-CUBm3Bmz.mjs} +9446 -5818
  11. package/dist/index.C4tKZ0yW-CUBm3Bmz.mjs.map +1 -0
  12. package/dist/internal/events.test.mjs +1 -1
  13. package/dist/internal/gateway.d.mts +1 -1
  14. package/dist/internal/gateway.mjs +23 -14
  15. package/dist/internal/gateway.mjs.map +1 -1
  16. package/dist/internal/gateway.test.mjs +259 -7
  17. package/dist/internal/gateway.test.mjs.map +1 -1
  18. package/dist/internal/rest.d.mts +1 -1
  19. package/dist/internal/rest.mjs +1 -1
  20. package/dist/internal/rest.mjs.map +1 -1
  21. package/dist/{rest-CZSW-VJK.d.mts → rest-SmIBt9Nh.d.mts} +2 -2
  22. package/dist/{rest-CZSW-VJK.d.mts.map → rest-SmIBt9Nh.d.mts.map} +1 -1
  23. package/dist/{rolldown-runtime-D7D4PA-g.mjs → rolldown-runtime-EAsCxrDo.mjs} +2 -1
  24. package/package.json +4 -4
  25. package/src/Discord.ts +7 -4
  26. package/src/internal/gateway.test.ts +87 -1
  27. package/src/internal/gateway.ts +32 -22
  28. package/src/internal/rest.ts +1 -1
  29. package/dist/gateway-C_Ae1HYB.d.mts.map +0 -1
  30. package/dist/magic-string.es-CUAnjKYC.mjs +0 -1014
  31. package/dist/magic-string.es-CUAnjKYC.mjs.map +0 -1
  32. package/dist/test.DNmyFkvJ-N3QqMRwZ.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { Dispatch, toEvents } from "./events.mjs";
2
- import { n as describe, r as it, t as globalExpect } from "../test.DNmyFkvJ-N3QqMRwZ.mjs";
2
+ import { n as globalExpect, o as describe, s as it } from "../index.C4tKZ0yW-CUBm3Bmz.mjs";
3
3
  import { Schema } from "effect";
4
4
  //#region src/internal/events.test.ts
5
5
  const events = toEvents({
@@ -1,2 +1,2 @@
1
- import { a as Ready, c as closeReason, i as Incoming, l as connect, n as Config, o as Session, r as GatewayInfo, s as classifyClose, t as CloseAction } from "../gateway-C_Ae1HYB.mjs";
1
+ import { a as Ready, c as closeReason, i as Incoming, l as connect, n as Config, o as Session, r as GatewayInfo, s as classifyClose, t as CloseAction } from "../gateway-CYyZYm4m.mjs";
2
2
  export { CloseAction, Config, GatewayInfo, Incoming, Ready, Session, classifyClose, closeReason, connect };
@@ -1,8 +1,9 @@
1
1
  import { Dispatch } from "./events.mjs";
2
2
  import { provider } from "./rest.mjs";
3
- import { Deferred, Duration, Effect, Match, Option, Queue, Redacted, Ref, Schedule, Schema } from "effect";
3
+ import { Deferred, Duration, Effect, Match, Option, Queue, Redacted, Ref, Schedule, Schema, Stream } from "effect";
4
+ import * as Socket from "effect/socket/Socket";
4
5
  import * as MessengerError from "@effect-uai/core/MessengerError";
5
- import * as Socket from "effect/unstable/socket/Socket";
6
+ import { isCleanClose } from "@effect-uai/core/WebSocketSession";
6
7
  //#region src/internal/gateway.ts
7
8
  const Payload = Schema.Struct({
8
9
  op: Schema.Number,
@@ -51,6 +52,19 @@ const closeReason = (code, reason) => fatalReasons[code] ?? (reason === void 0 |
51
52
  const RESUMABLE_CLOSE = 4e3;
52
53
  const socketCode = (error) => error.reason._tag === "SocketCloseError" ? error.reason.code : 1006;
53
54
  const socketReason = (error) => error.reason._tag === "SocketCloseError" ? error.reason.closeReason ?? error.reason.message : error.reason.message;
55
+ const endedBy = (error) => {
56
+ if (isCleanClose(error)) return {
57
+ action: "resume",
58
+ code: 1e3,
59
+ reason: "closed"
60
+ };
61
+ const code = socketCode(error);
62
+ return {
63
+ action: classifyClose(code),
64
+ code,
65
+ reason: closeReason(code, socketReason(error))
66
+ };
67
+ };
54
68
  const freshSessionDelay = Effect.sync(() => Duration.millis(1e3 + Math.random() * 4e3));
55
69
  const backoff = (attempt) => Duration.millis(Math.min(6e4, 500 * 2 ** attempt));
56
70
  const identity = {
@@ -71,16 +85,18 @@ const connect = (cfg) => Effect.gen(function* () {
71
85
  const ready = yield* Deferred.make();
72
86
  const resume = yield* Ref.make(Option.none());
73
87
  const attempts = yield* Ref.make(0);
88
+ const seq = yield* Ref.make(Option.none());
74
89
  const once = Effect.gen(function* () {
75
90
  const from = yield* Ref.get(resume);
76
91
  const host = Option.match(from, {
77
92
  onNone: () => cfg.url,
78
93
  onSome: (r) => r.url
79
94
  });
80
- const socket = yield* Socket.makeWebSocket(`${host}/?v=10&encoding=json`, { closeCodeIsError: (code) => code !== 1e3 && code !== 1001 && code !== 1005 }).pipe(Effect.provide(Socket.layerWebSocketConstructorGlobal));
81
- const write = yield* socket.writer;
95
+ const socket = yield* Socket.makeWebSocket(`${host}/?v=10&encoding=json`);
96
+ const writer = yield* socket.writer;
97
+ const closed = yield* Ref.make(false);
98
+ const write = (chunk) => Effect.flatMap(Ref.get(closed), (isClosed) => isClosed ? Effect.void : writer.write(chunk));
82
99
  const send = (payload) => write(JSON.stringify(payload));
83
- const seq = yield* Ref.make(Option.none());
84
100
  const acked = yield* Ref.make(true);
85
101
  const requested = yield* Ref.make(Option.none());
86
102
  const heartbeat = Effect.gen(function* () {
@@ -161,18 +177,11 @@ const connect = (cfg) => Effect.gen(function* () {
161
177
  const message = payload.value;
162
178
  yield* Match.value(message.op).pipe(Match.when(OP.dispatch, () => onDispatch(message)), Match.when(OP.hello, () => hello(message.d)), Match.when(OP.heartbeatAck, () => Ref.set(acked, true)), Match.when(OP.heartbeat, () => heartbeat), Match.when(OP.reconnect, () => write(new Socket.CloseEvent(RESUMABLE_CLOSE, "reconnect requested"))), Match.when(OP.invalidSession, () => invalidSession(message.d)), Match.orElse(() => Effect.void));
163
179
  }).pipe(Effect.ignore);
164
- const ended = yield* socket.runString(handle).pipe(Effect.as({
180
+ const ended = yield* Stream.fromPull(Socket.readerString(socket)).pipe(Stream.scoped, Stream.runForEach(handle), Effect.ensuring(Ref.set(closed, true)), Effect.as({
165
181
  action: "resume",
166
182
  code: 1e3,
167
183
  reason: "closed"
168
- }), Effect.catch((error) => {
169
- const code = socketCode(error);
170
- return Effect.succeed({
171
- action: classifyClose(code),
172
- code,
173
- reason: closeReason(code, socketReason(error))
174
- });
175
- }));
184
+ }), Effect.catch((error) => Effect.succeed(endedBy(error))));
176
185
  const override = yield* Ref.get(requested);
177
186
  return Option.match(override, {
178
187
  onNone: () => ended,
@@ -1 +1 @@
1
- {"version":3,"file":"gateway.mjs","names":["Events.Dispatch"],"sources":["../../src/internal/gateway.ts"],"sourcesContent":["import {\n type Cause,\n Deferred,\n Duration,\n Effect,\n Match,\n Option,\n Queue,\n Redacted,\n Ref,\n Schedule,\n Schema,\n type Scope,\n} from \"effect\"\nimport * as Socket from \"effect/unstable/socket/Socket\"\nimport * as MessengerError from \"@effect-uai/core/MessengerError\"\nimport * as Events from \"./events.js\"\nimport { provider } from \"./rest.js\"\n\n// ---------------------------------------------------------------------------\n// Wire shapes\n// ---------------------------------------------------------------------------\n\nconst Payload = Schema.Struct({\n op: Schema.Number,\n d: Schema.optional(Schema.Unknown),\n s: Schema.optional(Schema.NullOr(Schema.Number)),\n t: Schema.optional(Schema.NullOr(Schema.String)),\n})\ntype Payload = typeof Payload.Type\n\nconst decodeFrame = Schema.decodeUnknownEffect(Schema.fromJsonString(Payload))\n\nconst Hello = Schema.Struct({ heartbeat_interval: Schema.Number })\n\nexport const Ready = Schema.Struct({\n user: Schema.Struct({ id: Schema.String, username: Schema.String }),\n session_id: Schema.String,\n resume_gateway_url: Schema.String,\n})\nexport type Ready = typeof Ready.Type\n\n/** `GET /gateway/bot`, the only REST call the session needs. */\nexport const GatewayInfo = Schema.Struct({ url: Schema.String })\n\n// Send: 1 heartbeat, 2 identify, 6 resume. Receive: 0 dispatch, 7 reconnect,\n// 9 invalid session, 10 hello, 11 heartbeat ack.\nconst OP = {\n dispatch: 0,\n heartbeat: 1,\n identify: 2,\n resume: 6,\n reconnect: 7,\n invalidSession: 9,\n hello: 10,\n heartbeatAck: 11,\n} as const\n\n// ---------------------------------------------------------------------------\n// Close codes\n// ---------------------------------------------------------------------------\n\n/** What the session does once a connection is gone. */\nexport type CloseAction = \"resume\" | \"reidentify\" | \"fatal\"\n\n// Fatal: nothing about reconnecting would change the answer.\nconst fatalReasons: Record<number, string> = {\n 4004: \"authentication failed: the bot token was rejected\",\n 4010: \"invalid shard\",\n 4011: \"sharding required\",\n 4012: \"invalid API version\",\n 4013: \"invalid intents\",\n 4014: \"disallowed intents: enable the privileged intent in the developer portal\",\n}\n\n// The session is gone but the token is fine: identify fresh.\nconst staleSessionCodes = [4007, 4009]\n\n/**\n * Everything else, transport drops included, resumes: Discord only names the\n * codes that must not be retried, so an unknown code is a reconnect.\n */\nexport const classifyClose = (code: number): CloseAction =>\n Match.value(code).pipe(\n Match.when(\n (c) => c in fatalReasons,\n (): CloseAction => \"fatal\",\n ),\n Match.when(\n (c) => staleSessionCodes.includes(c),\n (): CloseAction => \"reidentify\",\n ),\n Match.orElse((): CloseAction => \"resume\"),\n )\n\n/** Discord's own words for a fatal code, since its close frames carry no reason. */\nexport const closeReason = (code: number, reason?: string): string =>\n fatalReasons[code] ??\n (reason === undefined || reason === \"\" ? `gateway closed with ${code}` : reason)\n\n// A close we send ourselves. Anything but 1000/1001 keeps the session alive on\n// Discord's side, which is what makes the next connection resumable.\nconst RESUMABLE_CLOSE = 4000\n\n// ---------------------------------------------------------------------------\n// Session\n// ---------------------------------------------------------------------------\n\nexport type Config = {\n readonly token: Redacted.Redacted\n readonly intents: number\n /** From `GET /gateway/bot`. Resumes go to `resume_gateway_url` instead. */\n readonly url: string\n}\n\n/** A decoded dispatch beside the payload it came from, which `raw` carries. */\nexport type Incoming = {\n readonly dispatch: Events.Dispatch\n readonly raw: unknown\n}\n\nexport type Session = {\n readonly bot: Events.BotIdentity\n /**\n * Dispatches in arrival order, ended when the scope closes and failed with\n * `MessengerTransportClosed` on a fatal gateway close.\n */\n readonly dispatches: Queue.Queue<Incoming, MessengerError.MessengerError | Cause.Done>\n}\n\ntype Resume = { readonly id: string; readonly url: string }\n\ntype Ended = { readonly action: CloseAction; readonly code: number; readonly reason: string }\n\nconst socketCode = (error: Socket.SocketError): number =>\n error.reason._tag === \"SocketCloseError\" ? error.reason.code : 1006\n\nconst socketReason = (error: Socket.SocketError): string =>\n error.reason._tag === \"SocketCloseError\"\n ? (error.reason.closeReason ?? error.reason.message)\n : error.reason.message\n\n// Discord asks for a random 1-5 s pause before identifying on a fresh session.\nconst freshSessionDelay = Effect.sync(() => Duration.millis(1000 + Math.random() * 4000))\n\n// Capped exponential, from a beat to a minute. Reset once a connection is live.\nconst backoff = (attempt: number): Duration.Duration =>\n Duration.millis(Math.min(60_000, 500 * 2 ** attempt))\n\nconst identity = { os: \"effect-uai\", browser: \"effect-uai\", device: \"effect-uai\" }\n\n/**\n * One gateway session: connect, identify, heartbeat, resume, reconnect.\n *\n * Returns once `READY` has arrived, so a bad token or a disallowed intent is\n * a `MessengerConnectFailed` at layer build rather than a stream that dies a\n * moment later. From then on reconnects are silent and only a fatal close\n * ends `dispatches`.\n */\nexport const connect = (\n cfg: Config,\n): Effect.Effect<Session, MessengerError.MessengerConnectFailed, Scope.Scope> =>\n Effect.gen(function* () {\n const dispatches = yield* Queue.unbounded<\n Incoming,\n MessengerError.MessengerError | Cause.Done\n >()\n const ready = yield* Deferred.make<Events.BotIdentity, MessengerError.MessengerConnectFailed>()\n const resume = yield* Ref.make(Option.none<Resume>())\n const attempts = yield* Ref.make(0)\n\n // -- one connection ----------------------------------------------------\n\n const once = Effect.gen(function* () {\n const from = yield* Ref.get(resume)\n // A resumable session has its own host; a fresh one starts at the URL\n // `GET /gateway/bot` gave us.\n const host = Option.match(from, { onNone: () => cfg.url, onSome: (r: Resume) => r.url })\n const socket = yield* Socket.makeWebSocket(`${host}/?v=10&encoding=json`, {\n // Effect treats every close as an error by default; the standard clean\n // codes are not, and Discord's 4xxx ones are what drives `classifyClose`.\n closeCodeIsError: (code: number) => code !== 1000 && code !== 1001 && code !== 1005,\n // The gateway takes its token in the identify payload, not a header,\n // so every runtime's global `WebSocket` is enough.\n }).pipe(Effect.provide(Socket.layerWebSocketConstructorGlobal))\n const write = yield* socket.writer\n const send = (payload: unknown) => write(JSON.stringify(payload))\n const seq = yield* Ref.make(Option.none<number>())\n const acked = yield* Ref.make(true)\n // Set by op 9 with `d: false`, the one case the close code cannot express.\n const requested = yield* Ref.make(Option.none<CloseAction>())\n\n const heartbeat = Effect.gen(function* () {\n const s = yield* Ref.get(seq)\n return yield* send({ op: OP.heartbeat, d: Option.getOrNull(s) })\n })\n\n // A beat with no ACK since the last one means the connection is a zombie:\n // close it non-cleanly so the next connection resumes.\n const beat = Effect.gen(function* () {\n const alive = yield* Ref.getAndSet(acked, false)\n if (!alive) {\n yield* write(new Socket.CloseEvent(RESUMABLE_CLOSE, \"no heartbeat ack\"))\n return false\n }\n yield* heartbeat\n return true\n })\n\n // Discord asks for the first beat to land at a random point in the\n // interval, so a fleet of bots does not beat in lockstep.\n const beating = (interval: number) =>\n Effect.sleep(Duration.millis(interval * Math.random())).pipe(\n Effect.andThen(\n beat.pipe(\n Effect.repeat({\n schedule: Schedule.spaced(Duration.millis(interval)),\n while: (alive: boolean) => alive,\n }),\n ),\n ),\n Effect.ignore,\n Effect.forkScoped,\n )\n\n const hello = (d: unknown) =>\n Effect.gen(function* () {\n const { heartbeat_interval } = yield* Schema.decodeUnknownEffect(Hello)(d)\n yield* beating(heartbeat_interval)\n const from = yield* Ref.get(resume)\n const s = yield* Ref.get(seq)\n return yield* Option.match(from, {\n onNone: () =>\n send({\n op: OP.identify,\n d: {\n token: Redacted.value(cfg.token),\n intents: cfg.intents,\n properties: identity,\n },\n }),\n onSome: (r) =>\n send({\n op: OP.resume,\n d: {\n token: Redacted.value(cfg.token),\n session_id: r.id,\n seq: Option.getOrNull(s),\n },\n }),\n })\n })\n\n const onReady = (d: unknown) =>\n Effect.gen(function* () {\n const info = yield* Schema.decodeUnknownEffect(Ready)(d)\n yield* Ref.set(resume, Option.some({ id: info.session_id, url: info.resume_gateway_url }))\n yield* Ref.set(attempts, 0)\n yield* Deferred.succeed(ready, info.user)\n })\n\n const onDispatch = (payload: Payload) =>\n Effect.gen(function* () {\n if (payload.s != null) yield* Ref.set(seq, Option.some(payload.s))\n if (payload.t === \"READY\") return yield* onReady(payload.d)\n if (payload.t === \"RESUMED\") return yield* Ref.set(attempts, 0)\n const dispatch = yield* Schema.decodeUnknownEffect(Events.Dispatch)({\n t: payload.t,\n d: payload.d,\n }).pipe(Effect.option)\n if (Option.isSome(dispatch)) {\n yield* Queue.offer(dispatches, { dispatch: dispatch.value, raw: payload.d })\n }\n })\n\n // `d: true` keeps the session, `d: false` drops it; both mean reconnect.\n const invalidSession = (d: unknown) =>\n Effect.gen(function* () {\n if (d !== true) {\n yield* Ref.set(resume, Option.none())\n yield* Ref.set(requested, Option.some<CloseAction>(\"reidentify\"))\n }\n yield* write(new Socket.CloseEvent(RESUMABLE_CLOSE, \"invalid session\"))\n })\n\n const handle = (raw: string) =>\n Effect.gen(function* () {\n const payload = yield* decodeFrame(raw).pipe(Effect.option)\n if (Option.isNone(payload)) return\n const message = payload.value\n yield* Match.value(message.op).pipe(\n Match.when(OP.dispatch, () => onDispatch(message)),\n Match.when(OP.hello, () => hello(message.d)),\n Match.when(OP.heartbeatAck, () => Ref.set(acked, true)),\n Match.when(OP.heartbeat, () => heartbeat),\n Match.when(OP.reconnect, () =>\n write(new Socket.CloseEvent(RESUMABLE_CLOSE, \"reconnect requested\")),\n ),\n Match.when(OP.invalidSession, () => invalidSession(message.d)),\n Match.orElse(() => Effect.void),\n )\n }).pipe(Effect.ignore)\n\n // A clean 1000 is only ever our own teardown; anything else carries the\n // close code the classifier reads.\n const ended = yield* socket.runString(handle).pipe(\n Effect.as<Ended>({ action: \"resume\", code: 1000, reason: \"closed\" }),\n Effect.catch((error: Socket.SocketError) => {\n const code = socketCode(error)\n return Effect.succeed<Ended>({\n action: classifyClose(code),\n code,\n reason: closeReason(code, socketReason(error)),\n })\n }),\n )\n const override = yield* Ref.get(requested)\n return Option.match(override, {\n onNone: (): Ended => ended,\n onSome: (action): Ended => ({ ...ended, action }),\n })\n }).pipe(Effect.scoped)\n\n // -- the reconnect loop ------------------------------------------------\n\n const cycle = Effect.gen(function* () {\n const ended = yield* once\n if (ended.action === \"fatal\") {\n return yield* new MessengerError.MessengerTransportClosed({\n provider,\n reason: ended.reason,\n raw: { code: ended.code },\n })\n }\n const attempt = yield* Ref.get(attempts)\n yield* Ref.set(attempts, attempt + 1)\n const wait = ended.action === \"reidentify\" ? yield* freshSessionDelay : backoff(attempt)\n // Reconnects never reach `events`, so this is the only sign of one. A\n // dropped connection is noticed only when a heartbeat goes unacked,\n // which is up to two intervals after the network actually went away.\n yield* Effect.logDebug(\"gateway reconnecting\", {\n code: ended.code,\n action: ended.action,\n attempt,\n in: Duration.toSeconds(wait),\n })\n yield* Effect.sleep(wait)\n })\n\n // Failing the deferred is a no-op once `READY` has landed, so the same\n // fatal close is a connect failure before it and a transport close after.\n yield* Effect.forever(cycle).pipe(\n Effect.catch((closed: MessengerError.MessengerTransportClosed) =>\n Deferred.fail(\n ready,\n new MessengerError.MessengerConnectFailed({\n provider,\n reason: closed.reason,\n raw: closed.raw,\n }),\n ).pipe(Effect.andThen(Queue.fail(dispatches, closed))),\n ),\n Effect.ensuring(Queue.end(dispatches)),\n Effect.forkScoped,\n )\n\n return { bot: yield* Deferred.await(ready), dispatches }\n })\n"],"mappings":"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"}
1
+ {"version":3,"file":"gateway.mjs","names":["Events.Dispatch"],"sources":["../../src/internal/gateway.ts"],"sourcesContent":["import {\n type Cause,\n Deferred,\n Duration,\n Effect,\n Match,\n Option,\n Queue,\n Redacted,\n Ref,\n Schedule,\n Schema,\n type Scope,\n Stream,\n} from \"effect\"\nimport * as Socket from \"effect/socket/Socket\"\nimport * as MessengerError from \"@effect-uai/core/MessengerError\"\nimport { isCleanClose } from \"@effect-uai/core/WebSocketSession\"\nimport * as Events from \"./events.js\"\nimport { provider } from \"./rest.js\"\n\n// ---------------------------------------------------------------------------\n// Wire shapes\n// ---------------------------------------------------------------------------\n\nconst Payload = Schema.Struct({\n op: Schema.Number,\n d: Schema.optional(Schema.Unknown),\n s: Schema.optional(Schema.NullOr(Schema.Number)),\n t: Schema.optional(Schema.NullOr(Schema.String)),\n})\ntype Payload = typeof Payload.Type\n\nconst decodeFrame = Schema.decodeUnknownEffect(Schema.fromJsonString(Payload))\n\nconst Hello = Schema.Struct({ heartbeat_interval: Schema.Number })\n\nexport const Ready = Schema.Struct({\n user: Schema.Struct({ id: Schema.String, username: Schema.String }),\n session_id: Schema.String,\n resume_gateway_url: Schema.String,\n})\nexport type Ready = typeof Ready.Type\n\n/** `GET /gateway/bot`, the only REST call the session needs. */\nexport const GatewayInfo = Schema.Struct({ url: Schema.String })\n\n// Send: 1 heartbeat, 2 identify, 6 resume. Receive: 0 dispatch, 7 reconnect,\n// 9 invalid session, 10 hello, 11 heartbeat ack.\nconst OP = {\n dispatch: 0,\n heartbeat: 1,\n identify: 2,\n resume: 6,\n reconnect: 7,\n invalidSession: 9,\n hello: 10,\n heartbeatAck: 11,\n} as const\n\n// ---------------------------------------------------------------------------\n// Close codes\n// ---------------------------------------------------------------------------\n\n/** What the session does once a connection is gone. */\nexport type CloseAction = \"resume\" | \"reidentify\" | \"fatal\"\n\n// Fatal: nothing about reconnecting would change the answer.\nconst fatalReasons: Record<number, string> = {\n 4004: \"authentication failed: the bot token was rejected\",\n 4010: \"invalid shard\",\n 4011: \"sharding required\",\n 4012: \"invalid API version\",\n 4013: \"invalid intents\",\n 4014: \"disallowed intents: enable the privileged intent in the developer portal\",\n}\n\n// The session is gone but the token is fine: identify fresh.\nconst staleSessionCodes = [4007, 4009]\n\n/**\n * Everything else, transport drops included, resumes: Discord only names the\n * codes that must not be retried, so an unknown code is a reconnect.\n */\nexport const classifyClose = (code: number): CloseAction =>\n Match.value(code).pipe(\n Match.when(\n (c) => c in fatalReasons,\n (): CloseAction => \"fatal\",\n ),\n Match.when(\n (c) => staleSessionCodes.includes(c),\n (): CloseAction => \"reidentify\",\n ),\n Match.orElse((): CloseAction => \"resume\"),\n )\n\n/** Discord's own words for a fatal code, since its close frames carry no reason. */\nexport const closeReason = (code: number, reason?: string): string =>\n fatalReasons[code] ??\n (reason === undefined || reason === \"\" ? `gateway closed with ${code}` : reason)\n\n// A close we send ourselves. Anything but 1000/1001 keeps the session alive on\n// Discord's side, which is what makes the next connection resumable.\nconst RESUMABLE_CLOSE = 4000\n\n// ---------------------------------------------------------------------------\n// Session\n// ---------------------------------------------------------------------------\n\nexport type Config = {\n readonly token: Redacted.Redacted\n readonly intents: number\n /** From `GET /gateway/bot`. Resumes go to `resume_gateway_url` instead. */\n readonly url: string\n}\n\n/** A decoded dispatch beside the payload it came from, which `raw` carries. */\nexport type Incoming = {\n readonly dispatch: Events.Dispatch\n readonly raw: unknown\n}\n\nexport type Session = {\n readonly bot: Events.BotIdentity\n /**\n * Dispatches in arrival order, ended when the scope closes and failed with\n * `MessengerTransportClosed` on a fatal gateway close.\n */\n readonly dispatches: Queue.Queue<Incoming, MessengerError.MessengerError | Cause.Done>\n}\n\ntype Resume = { readonly id: string; readonly url: string }\n\ntype Ended = { readonly action: CloseAction; readonly code: number; readonly reason: string }\n\nconst socketCode = (error: Socket.SocketError): number =>\n error.reason._tag === \"SocketCloseError\" ? error.reason.code : 1006\n\nconst socketReason = (error: Socket.SocketError): string =>\n error.reason._tag === \"SocketCloseError\"\n ? (error.reason.closeReason ?? error.reason.message)\n : error.reason.message\n\n// Effect's `Socket` fails on every close. A clean one is only ever our own\n// teardown; anything else carries the close code the classifier reads.\nconst endedBy = (error: Socket.SocketError): Ended => {\n if (isCleanClose(error)) return { action: \"resume\", code: 1000, reason: \"closed\" }\n const code = socketCode(error)\n return { action: classifyClose(code), code, reason: closeReason(code, socketReason(error)) }\n}\n\n// Discord asks for a random 1-5 s pause before identifying on a fresh session.\nconst freshSessionDelay = Effect.sync(() => Duration.millis(1000 + Math.random() * 4000))\n\n// Capped exponential, from a beat to a minute. Reset once a connection is live.\nconst backoff = (attempt: number): Duration.Duration =>\n Duration.millis(Math.min(60_000, 500 * 2 ** attempt))\n\nconst identity = { os: \"effect-uai\", browser: \"effect-uai\", device: \"effect-uai\" }\n\n/**\n * One gateway session: connect, identify, heartbeat, resume, reconnect.\n *\n * Returns once `READY` has arrived, so a bad token or a disallowed intent is\n * a `MessengerConnectFailed` at layer build rather than a stream that dies a\n * moment later. From then on reconnects are silent and only a fatal close\n * ends `dispatches`.\n */\nexport const connect = (\n cfg: Config,\n): Effect.Effect<\n Session,\n MessengerError.MessengerConnectFailed,\n Scope.Scope | Socket.WebSocketConstructor\n> =>\n Effect.gen(function* () {\n const dispatches = yield* Queue.unbounded<\n Incoming,\n MessengerError.MessengerError | Cause.Done\n >()\n const ready = yield* Deferred.make<Events.BotIdentity, MessengerError.MessengerConnectFailed>()\n const resume = yield* Ref.make(Option.none<Resume>())\n const attempts = yield* Ref.make(0)\n // Outlives a connection: a resume replays from the last sequence seen.\n const seq = yield* Ref.make(Option.none<number>())\n\n // -- one connection ----------------------------------------------------\n\n const once = Effect.gen(function* () {\n const from = yield* Ref.get(resume)\n // A resumable session has its own host; a fresh one starts at the URL\n // `GET /gateway/bot` gave us.\n const host = Option.match(from, { onNone: () => cfg.url, onSome: (r: Resume) => r.url })\n const socket = yield* Socket.makeWebSocket(`${host}/?v=10&encoding=json`)\n const writer = yield* socket.writer\n // The writer waits for the next connection rather than failing, so a\n // beat or handshake after this one's reader ended is dropped here.\n const closed = yield* Ref.make(false)\n const write = (chunk: string | Socket.CloseEvent) =>\n Effect.flatMap(Ref.get(closed), (isClosed) =>\n isClosed ? Effect.void : writer.write(chunk),\n )\n const send = (payload: unknown) => write(JSON.stringify(payload))\n const acked = yield* Ref.make(true)\n // Set by op 9 with `d: false`, the one case the close code cannot express.\n const requested = yield* Ref.make(Option.none<CloseAction>())\n\n const heartbeat = Effect.gen(function* () {\n const s = yield* Ref.get(seq)\n return yield* send({ op: OP.heartbeat, d: Option.getOrNull(s) })\n })\n\n // A beat with no ACK since the last one means the connection is a zombie:\n // close it non-cleanly so the next connection resumes.\n const beat = Effect.gen(function* () {\n const alive = yield* Ref.getAndSet(acked, false)\n if (!alive) {\n yield* write(new Socket.CloseEvent(RESUMABLE_CLOSE, \"no heartbeat ack\"))\n return false\n }\n yield* heartbeat\n return true\n })\n\n // Discord asks for the first beat to land at a random point in the\n // interval, so a fleet of bots does not beat in lockstep.\n const beating = (interval: number) =>\n Effect.sleep(Duration.millis(interval * Math.random())).pipe(\n Effect.andThen(\n beat.pipe(\n Effect.repeat({\n schedule: Schedule.spaced(Duration.millis(interval)),\n while: (alive: boolean) => alive,\n }),\n ),\n ),\n Effect.ignore,\n Effect.forkScoped,\n )\n\n const hello = (d: unknown) =>\n Effect.gen(function* () {\n const { heartbeat_interval } = yield* Schema.decodeUnknownEffect(Hello)(d)\n yield* beating(heartbeat_interval)\n const from = yield* Ref.get(resume)\n const s = yield* Ref.get(seq)\n return yield* Option.match(from, {\n onNone: () =>\n send({\n op: OP.identify,\n d: {\n token: Redacted.value(cfg.token),\n intents: cfg.intents,\n properties: identity,\n },\n }),\n onSome: (r) =>\n send({\n op: OP.resume,\n d: {\n token: Redacted.value(cfg.token),\n session_id: r.id,\n seq: Option.getOrNull(s),\n },\n }),\n })\n })\n\n const onReady = (d: unknown) =>\n Effect.gen(function* () {\n const info = yield* Schema.decodeUnknownEffect(Ready)(d)\n yield* Ref.set(resume, Option.some({ id: info.session_id, url: info.resume_gateway_url }))\n yield* Ref.set(attempts, 0)\n yield* Deferred.succeed(ready, info.user)\n })\n\n const onDispatch = (payload: Payload) =>\n Effect.gen(function* () {\n if (payload.s != null) yield* Ref.set(seq, Option.some(payload.s))\n if (payload.t === \"READY\") return yield* onReady(payload.d)\n if (payload.t === \"RESUMED\") return yield* Ref.set(attempts, 0)\n const dispatch = yield* Schema.decodeUnknownEffect(Events.Dispatch)({\n t: payload.t,\n d: payload.d,\n }).pipe(Effect.option)\n if (Option.isSome(dispatch)) {\n yield* Queue.offer(dispatches, { dispatch: dispatch.value, raw: payload.d })\n }\n })\n\n // `d: true` keeps the session, `d: false` drops it; both mean reconnect.\n const invalidSession = (d: unknown) =>\n Effect.gen(function* () {\n if (d !== true) {\n yield* Ref.set(resume, Option.none())\n yield* Ref.set(requested, Option.some<CloseAction>(\"reidentify\"))\n }\n yield* write(new Socket.CloseEvent(RESUMABLE_CLOSE, \"invalid session\"))\n })\n\n const handle = (raw: string) =>\n Effect.gen(function* () {\n const payload = yield* decodeFrame(raw).pipe(Effect.option)\n if (Option.isNone(payload)) return\n const message = payload.value\n yield* Match.value(message.op).pipe(\n Match.when(OP.dispatch, () => onDispatch(message)),\n Match.when(OP.hello, () => hello(message.d)),\n Match.when(OP.heartbeatAck, () => Ref.set(acked, true)),\n Match.when(OP.heartbeat, () => heartbeat),\n Match.when(OP.reconnect, () =>\n write(new Socket.CloseEvent(RESUMABLE_CLOSE, \"reconnect requested\")),\n ),\n Match.when(OP.invalidSession, () => invalidSession(message.d)),\n Match.orElse(() => Effect.void),\n )\n }).pipe(Effect.ignore)\n\n const ended = yield* Stream.fromPull(Socket.readerString(socket)).pipe(\n Stream.scoped,\n Stream.runForEach(handle),\n Effect.ensuring(Ref.set(closed, true)),\n Effect.as<Ended>({ action: \"resume\", code: 1000, reason: \"closed\" }),\n Effect.catch((error: Socket.SocketError) => Effect.succeed(endedBy(error))),\n )\n const override = yield* Ref.get(requested)\n return Option.match(override, {\n onNone: (): Ended => ended,\n onSome: (action): Ended => ({ ...ended, action }),\n })\n }).pipe(Effect.scoped)\n\n // -- the reconnect loop ------------------------------------------------\n\n const cycle = Effect.gen(function* () {\n const ended = yield* once\n if (ended.action === \"fatal\") {\n return yield* new MessengerError.MessengerTransportClosed({\n provider,\n reason: ended.reason,\n raw: { code: ended.code },\n })\n }\n const attempt = yield* Ref.get(attempts)\n yield* Ref.set(attempts, attempt + 1)\n const wait = ended.action === \"reidentify\" ? yield* freshSessionDelay : backoff(attempt)\n // Reconnects never reach `events`, so this is the only sign of one. A\n // dropped connection is noticed only when a heartbeat goes unacked,\n // which is up to two intervals after the network actually went away.\n yield* Effect.logDebug(\"gateway reconnecting\", {\n code: ended.code,\n action: ended.action,\n attempt,\n in: Duration.toSeconds(wait),\n })\n yield* Effect.sleep(wait)\n })\n\n // Failing the deferred is a no-op once `READY` has landed, so the same\n // fatal close is a connect failure before it and a transport close after.\n yield* Effect.forever(cycle).pipe(\n Effect.catch((closed: MessengerError.MessengerTransportClosed) =>\n Deferred.fail(\n ready,\n new MessengerError.MessengerConnectFailed({\n provider,\n reason: closed.reason,\n raw: closed.raw,\n }),\n ).pipe(Effect.andThen(Queue.fail(dispatches, closed))),\n ),\n Effect.ensuring(Queue.end(dispatches)),\n Effect.forkScoped,\n )\n\n return { bot: yield* Deferred.await(ready), dispatches }\n })\n"],"mappings":"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"}
@@ -1,8 +1,178 @@
1
- import { classifyClose, closeReason } from "./gateway.mjs";
2
- import { n as describe, r as it, t as globalExpect } from "../test.DNmyFkvJ-N3QqMRwZ.mjs";
1
+ import { classifyClose, closeReason, connect } from "./gateway.mjs";
2
+ import { a as beforeEach, i as beforeAll, n as globalExpect, o as describe, r as afterAll, s as it$2, t as TestRunner } from "../index.C4tKZ0yW-CUBm3Bmz.mjs";
3
+ import { Effect, Layer, Queue, Redacted } from "effect";
4
+ import * as Socket from "effect/socket/Socket";
5
+ import * as Arbitrary from "effect/Arbitrary";
6
+ import * as Cause$1 from "effect/Cause";
7
+ import * as Duration$1 from "effect/Duration";
8
+ import * as Effect$1 from "effect/Effect";
9
+ import * as Exit from "effect/Exit";
10
+ import { constVoid, flow, pipe } from "effect/Function";
11
+ import * as Layer$1 from "effect/Layer";
12
+ import * as Schedule$1 from "effect/Schedule";
13
+ import * as Scope$1 from "effect/Scope";
14
+ import * as TestClock from "effect/testing/TestClock";
15
+ import * as TestConsole from "effect/testing/TestConsole";
16
+ import * as FakeWebSocket from "@effect-uai/core/testing/FakeWebSocket";
17
+ //#region ../../../node_modules/.pnpm/@effect+vitest@4.0.1_effect@4.0.1_vitest@5.0.3_@types+node@26.2.0_vite@7.3.2_@types+nod_65816e3353e20bfeb9b2e1494bdddeef/node_modules/@effect/vitest/dist/internal/internal.js
18
+ /**
19
+ * @since 4.0.0
20
+ */
21
+ const getCurrentSuite = TestRunner.getCurrentSuite;
22
+ const runPromise = /*#__PURE__*/ Effect$1.fnUntraced(function* (effect, _ctx) {
23
+ const exit = yield* Effect$1.exit(effect);
24
+ if (Exit.isFailure(exit)) {
25
+ const errors = Cause$1.prettyErrors(exit.cause);
26
+ for (let i = 0; i < errors.length; i++) yield* Effect$1.logError(errors[i]);
27
+ }
28
+ return yield* exit;
29
+ }, (effect, _, ctx) => Effect$1.runPromise(effect, { signal: ctx?.signal }));
30
+ /** @internal */
31
+ const runTest = (ctx) => (effect) => {
32
+ const promise = runPromise(effect, ctx);
33
+ if (ctx) {
34
+ const onAbort = () => ctx.onTestFinished(() => promise.then(constVoid, constVoid));
35
+ ctx.signal.addEventListener("abort", onAbort, { once: true });
36
+ const cleanup = () => ctx.signal.removeEventListener("abort", onAbort);
37
+ promise.then(cleanup, cleanup);
38
+ if (ctx.signal.aborted) onAbort();
39
+ }
40
+ return promise;
41
+ };
42
+ const TestEnv = /*#__PURE__*/ Layer$1.mergeAll(TestConsole.layer, /*#__PURE__*/ TestClock.layer());
43
+ /** @internal */
44
+ const testOptions = (timeout) => typeof timeout === "number" ? { timeout } : timeout ?? {};
45
+ const hookTimeout = (timeout) => timeout === void 0 ? void 0 : Duration$1.toMillis(Duration$1.fromInputUnsafe(timeout));
46
+ const propertyTestOptions = (timeout) => typeof timeout === "number" ? void 0 : timeout;
47
+ const checkOptions = (timeout) => propertyTestOptions(timeout)?.arbitrary;
48
+ const compileArbitraryInput = (input) => Arbitrary.isArbitrary(input) ? input : Arbitrary.schema(input);
49
+ const makeArbitrary = (arbitraries) => Arbitrary.all(Array.isArray(arbitraries) ? arbitraries.map(compileArbitraryInput) : Object.fromEntries(Object.entries(arbitraries).map(([key, input]) => [key, compileArbitraryInput(input)])));
50
+ const normalizeProperty = (property, value) => Effect$1.catchCause(Effect$1.suspend(() => {
51
+ const output = property(value);
52
+ return Effect$1.isEffect(output) ? output : Effect$1.succeed(output);
53
+ }), (cause) => Cause$1.hasInterrupts(cause) ? Effect$1.failCause(cause) : Effect$1.fail(cause));
54
+ const runCheck = (ctx, arbitrary, property, options) => runTest(ctx)(Effect$1.flatMapEager(Arbitrary.checkEffect(arbitrary, (value) => normalizeProperty(property, value), options), (result) => {
55
+ const failure = Arbitrary.formatCheckFailure(result);
56
+ return failure === void 0 ? Effect$1.void : Effect$1.die(new Error(failure));
57
+ }));
58
+ const withFixturesOf = (source, f) => Object.defineProperty(f, "toString", { value: () => source.toString() });
59
+ const makeItProxy = (it, overrides) => new Proxy(it, {
60
+ apply(target, thisArg, argArray) {
61
+ return Reflect.apply(target, thisArg, argArray);
62
+ },
63
+ get(target, property, receiver) {
64
+ if (Object.hasOwn(overrides, property)) return Reflect.get(overrides, property);
65
+ return Reflect.get(target, property, receiver);
66
+ }
67
+ });
68
+ const collectTasks = (tasks, acc = []) => {
69
+ for (let i = 0; i < tasks.length; i++) {
70
+ const task = tasks[i];
71
+ if (task.type === "test" && task.mode !== "skip" && task.mode !== "todo") acc.push(task);
72
+ else if (task.tasks !== void 0) collectTasks(task.tasks, acc);
73
+ }
74
+ return acc;
75
+ };
76
+ const registerProp = (it, name, arbitraries, property, timeout) => {
77
+ const arbitrary = makeArbitrary(arbitraries);
78
+ it(name, testOptions(timeout), withFixturesOf(() => {}, (ctx) => runCheck(ctx, arbitrary, (values) => property(values, ctx), checkOptions(timeout))));
79
+ };
80
+ const makeTester = (mapEffect, it) => {
81
+ const run = (ctx, args, self) => pipe(Effect$1.suspend(() => self(...args)), mapEffect, runTest(ctx));
82
+ const register = (test) => (name, self, timeout) => test(name, testOptions(timeout), withFixturesOf(self, (ctx) => run(ctx, [ctx], self)));
83
+ const each = (cases) => (name, self, timeout) => it.for(cases)(name, testOptions(timeout), withFixturesOf(self, (args, ctx) => run(ctx, [args, ctx], self).then(constVoid)));
84
+ const prop = (name, arbitraries, self, timeout) => registerProp(it, name, arbitraries, (values, ctx) => Effect$1.mapEager(mapEffect(Effect$1.suspend(() => self(values, ctx))), (value) => value !== false), timeout);
85
+ return Object.assign(register(it), {
86
+ skip: register(it.skip),
87
+ skipIf: (condition) => register(it.skipIf(condition)),
88
+ runIf: (condition) => register(it.runIf(condition)),
89
+ only: register(it.only),
90
+ each,
91
+ fails: register(it.fails),
92
+ prop
93
+ });
94
+ };
95
+ const makeProp = (it) => (name, arbitraries, self, timeout) => registerProp(it, name, arbitraries, (values, ctx) => self(values, ctx) !== false, timeout);
96
+ const makeLayer = (it) => (layer_, options) => (...args) => {
97
+ const excludeTestServices = options?.excludeTestServices ?? false;
98
+ const withTestEnv = excludeTestServices ? layer_ : Layer$1.provideMerge(layer_, TestEnv);
99
+ const memoMap = options?.memoMap ?? Effect$1.runSync(Layer$1.makeMemoMap);
100
+ const scope = Effect$1.runSync(Scope$1.make());
101
+ const contextEffect = Layer$1.buildWithMemoMap(withTestEnv, memoMap, scope).pipe(Effect$1.orDie, Effect$1.cached, Effect$1.runSync);
102
+ let closed = false;
103
+ const closeScope = () => {
104
+ if (closed) return Promise.resolve();
105
+ closed = true;
106
+ return runPromise(Scope$1.close(scope, Exit.void));
107
+ };
108
+ const methods = makeItProxy(it, {
109
+ effect: makeTester((effect) => Effect$1.flatMap(contextEffect, (context) => effect.pipe(Effect$1.scoped, Effect$1.provide(context))), it),
110
+ prop: makeProp(it),
111
+ flakyTest,
112
+ layer(nestedLayer, options) {
113
+ return makeLayer(it)(Layer$1.provideMerge(nestedLayer, withTestEnv), {
114
+ ...options,
115
+ memoMap: Layer$1.forkMemoMapUnsafe(memoMap),
116
+ excludeTestServices
117
+ });
118
+ }
119
+ });
120
+ if (args.length === 1) {
121
+ const currentSuite = getCurrentSuite();
122
+ const previousTasks = new Set(currentSuite.tasks);
123
+ args[0](methods);
124
+ const blockTasks = collectTasks(currentSuite.tasks.filter((task) => !previousTasks.has(task)));
125
+ if (blockTasks.length === 0) {
126
+ afterAll(() => closeScope(), hookTimeout(options?.timeout));
127
+ return;
128
+ }
129
+ const blockTaskSet = new Set(blockTasks);
130
+ let remaining = blockTasks.length;
131
+ beforeEach(({ onTestFinished, signal, task }) => {
132
+ if (!blockTaskSet.has(task)) return;
133
+ onTestFinished(() => {
134
+ remaining--;
135
+ if (remaining === 0) return closeScope();
136
+ });
137
+ return runPromise(Effect$1.asVoid(contextEffect), { signal });
138
+ }, hookTimeout(options?.timeout));
139
+ afterAll(() => closeScope(), hookTimeout(options?.timeout));
140
+ return;
141
+ }
142
+ const suiteOptions = options?.concurrent === void 0 ? {} : { concurrent: options.concurrent };
143
+ return describe(args[0], suiteOptions, () => {
144
+ beforeAll(() => runPromise(Effect$1.asVoid(contextEffect)), hookTimeout(options?.timeout));
145
+ afterAll(() => closeScope(), hookTimeout(options?.timeout));
146
+ return args[1](methods);
147
+ });
148
+ };
149
+ /** @internal */
150
+ const flakyTest = (self, timeout = Duration$1.seconds(30)) => pipe(self, Effect$1.scoped, Effect$1.sandbox, Effect$1.retry(pipe(Schedule$1.recurs(10), Schedule$1.while((_) => Effect$1.succeed(Duration$1.isLessThanOrEqualTo(Duration$1.fromInputUnsafe(_.elapsed), Duration$1.fromInputUnsafe(timeout)))))), Effect$1.orDie);
151
+ /** @internal */
152
+ const makeMethods = (it) => makeItProxy(it, {
153
+ effect: makeTester(flow(Effect$1.scoped, Effect$1.provide(TestEnv)), it),
154
+ live: makeTester(Effect$1.scoped, it),
155
+ flakyTest,
156
+ layer: makeLayer(it),
157
+ prop: makeProp(it)
158
+ });
159
+ /** @internal */
160
+ const it$1 = /*#__PURE__*/ makeMethods(it$2);
161
+ /** @internal */
162
+ const { effect, layer, live, prop } = it$1;
163
+ //#endregion
164
+ //#region ../../../node_modules/.pnpm/@effect+vitest@4.0.1_effect@4.0.1_vitest@5.0.3_@types+node@26.2.0_vite@7.3.2_@types+nod_65816e3353e20bfeb9b2e1494bdddeef/node_modules/@effect/vitest/dist/index.js
165
+ /**
166
+ * @since 4.0.0
167
+ */
168
+ /**
169
+ * @since 4.0.0
170
+ */
171
+ const it = it$1;
172
+ //#endregion
3
173
  //#region src/internal/gateway.test.ts
4
174
  describe("classifyClose", () => {
5
- it("never retries a close reconnecting cannot fix", () => {
175
+ it$2("never retries a close reconnecting cannot fix", () => {
6
176
  globalExpect([
7
177
  4004,
8
178
  4010,
@@ -12,10 +182,10 @@ describe("classifyClose", () => {
12
182
  4014
13
183
  ].map(classifyClose)).toEqual(Array(6).fill("fatal"));
14
184
  });
15
- it("identifies fresh only where the session itself is gone", () => {
185
+ it$2("identifies fresh only where the session itself is gone", () => {
16
186
  globalExpect([4007, 4009].map(classifyClose)).toEqual(["reidentify", "reidentify"]);
17
187
  });
18
- it("resumes on everything else, transport drops included", () => {
188
+ it$2("resumes on everything else, transport drops included", () => {
19
189
  globalExpect([
20
190
  4e3,
21
191
  4003,
@@ -27,13 +197,95 @@ describe("classifyClose", () => {
27
197
  });
28
198
  });
29
199
  describe("closeReason", () => {
30
- it("names a fatal code, since Discord's close frames carry no reason", () => {
200
+ it$2("names a fatal code, since Discord's close frames carry no reason", () => {
31
201
  globalExpect(closeReason(4014, "")).toContain("privileged intent");
32
202
  });
33
- it("falls back to whatever the socket reported", () => {
203
+ it$2("falls back to whatever the socket reported", () => {
34
204
  globalExpect(closeReason(1006, "connection reset")).toBe("connection reset");
35
205
  });
36
206
  });
207
+ const cfg = {
208
+ token: Redacted.make("token"),
209
+ intents: 0,
210
+ url: "wss://gateway.test"
211
+ };
212
+ const HELLO = {
213
+ op: 10,
214
+ d: { heartbeat_interval: 36e5 }
215
+ };
216
+ const answers = {
217
+ 2: [{
218
+ op: 0,
219
+ t: "READY",
220
+ s: 1,
221
+ d: {
222
+ user: {
223
+ id: "1",
224
+ username: "bot"
225
+ },
226
+ session_id: "session",
227
+ resume_gateway_url: "wss://resume.test"
228
+ }
229
+ }],
230
+ 6: [{
231
+ op: 0,
232
+ t: "RESUMED",
233
+ s: 2
234
+ }]
235
+ };
236
+ const gateway = FakeWebSocket.make({
237
+ greeting: [HELLO],
238
+ reply: (frame) => Effect.succeed(answers[frame.op] ?? [])
239
+ });
240
+ /** One fake per connection, in dial order; the last one takes any extra dials. */
241
+ const dialing = (servers) => {
242
+ const urls = [];
243
+ return {
244
+ urls,
245
+ layer: Layer.succeed(Socket.WebSocketConstructor)((url, protocols) => {
246
+ const server = servers[Math.min(urls.length, servers.length - 1)];
247
+ urls.push(url);
248
+ return server.connect(url, protocols);
249
+ })
250
+ };
251
+ };
252
+ describe("gateway close handling", () => {
253
+ it.live.each([1e3, 4e3])("resumes on the resume host after a %i close", (code) => Effect.gen(function* () {
254
+ const first = yield* gateway;
255
+ const second = yield* gateway;
256
+ const { urls, layer } = dialing([first, second]);
257
+ const session = yield* connect(cfg).pipe(Effect.provide(layer));
258
+ globalExpect(session.bot.username).toBe("bot");
259
+ yield* first.close(code);
260
+ yield* second.awaitSent(1);
261
+ globalExpect(urls[1]).toMatch(/^wss:\/\/resume\.test\//);
262
+ globalExpect(yield* second.sent).toEqual([{
263
+ op: 6,
264
+ d: {
265
+ token: "token",
266
+ session_id: "session",
267
+ seq: 1
268
+ }
269
+ }]);
270
+ }));
271
+ it.live("fails the dispatches on a fatal close after READY", () => Effect.gen(function* () {
272
+ const server = yield* gateway;
273
+ const session = yield* connect(cfg).pipe(Effect.provide(dialing([server]).layer));
274
+ yield* server.close(4014);
275
+ const error = yield* Queue.take(session.dispatches).pipe(Effect.flip);
276
+ globalExpect(error).toMatchObject({
277
+ _tag: "MessengerTransportClosed",
278
+ raw: { code: 4014 }
279
+ });
280
+ }));
281
+ it.live("fails the connect on a fatal close before READY", () => Effect.gen(function* () {
282
+ const server = yield* FakeWebSocket.make({ greeting: [HELLO] });
283
+ yield* server.awaitSent(1).pipe(Effect.andThen(server.close(4004)), Effect.forkScoped);
284
+ const error = yield* connect(cfg).pipe(Effect.provide(dialing([server]).layer), Effect.flip);
285
+ globalExpect(error._tag).toBe("MessengerConnectFailed");
286
+ globalExpect(error.reason).toContain("authentication failed");
287
+ }));
288
+ });
37
289
  //#endregion
38
290
  export {};
39
291
 
@@ -1 +1 @@
1
- {"version":3,"file":"gateway.test.mjs","names":[],"sources":["../../src/internal/gateway.test.ts"],"sourcesContent":["import { describe, expect, it } from \"vitest\"\nimport { classifyClose, closeReason } from \"./gateway.js\"\n\ndescribe(\"classifyClose\", () => {\n it(\"never retries a close reconnecting cannot fix\", () => {\n expect([4004, 4010, 4011, 4012, 4013, 4014].map(classifyClose)).toEqual(Array(6).fill(\"fatal\"))\n })\n\n it(\"identifies fresh only where the session itself is gone\", () => {\n expect([4007, 4009].map(classifyClose)).toEqual([\"reidentify\", \"reidentify\"])\n })\n\n it(\"resumes on everything else, transport drops included\", () => {\n // 1006 is a dropped connection, 4000 the code the zombie check sends.\n expect([4000, 4003, 4005, 4008, 1006, 1011].map(classifyClose)).toEqual(Array(6).fill(\"resume\"))\n })\n})\n\ndescribe(\"closeReason\", () => {\n it(\"names a fatal code, since Discord's close frames carry no reason\", () => {\n expect(closeReason(4014, \"\")).toContain(\"privileged intent\")\n })\n\n it(\"falls back to whatever the socket reported\", () => {\n expect(closeReason(1006, \"connection reset\")).toBe(\"connection reset\")\n })\n})\n"],"mappings":";;;AAGA,SAAS,uBAAuB;CAC9B,GAAG,uDAAuD;EACxD,aAAO;GAAC;GAAM;GAAM;GAAM;GAAM;GAAM;EAAI,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC;CAChG,CAAC;CAED,GAAG,gEAAgE;EACjE,aAAO,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,cAAc,YAAY,CAAC;CAC9E,CAAC;CAED,GAAG,8DAA8D;EAE/D,aAAO;GAAC;GAAM;GAAM;GAAM;GAAM;GAAM;EAAI,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC;CACjG,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB;CAC5B,GAAG,0EAA0E;EAC3E,aAAO,YAAY,MAAM,EAAE,CAAC,CAAC,CAAC,UAAU,mBAAmB;CAC7D,CAAC;CAED,GAAG,oDAAoD;EACrD,aAAO,YAAY,MAAM,kBAAkB,CAAC,CAAC,CAAC,KAAK,kBAAkB;CACvE,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"gateway.test.mjs","names":["Effect","Cause","Duration","Layer","Scope","V.describe","Schedule","it","V.it","internal.it"],"sources":["../../../../../node_modules/.pnpm/@effect+vitest@4.0.1_effect@4.0.1_vitest@5.0.3_@types+node@26.2.0_vite@7.3.2_@types+nod_65816e3353e20bfeb9b2e1494bdddeef/node_modules/@effect/vitest/dist/internal/internal.js","../../../../../node_modules/.pnpm/@effect+vitest@4.0.1_effect@4.0.1_vitest@5.0.3_@types+node@26.2.0_vite@7.3.2_@types+nod_65816e3353e20bfeb9b2e1494bdddeef/node_modules/@effect/vitest/dist/index.js","../../src/internal/gateway.test.ts"],"sourcesContent":["/**\n * @since 4.0.0\n */\nimport * as Arbitrary from \"effect/Arbitrary\";\nimport * as Cause from \"effect/Cause\";\nimport * as Duration from \"effect/Duration\";\nimport * as Effect from \"effect/Effect\";\nimport * as Exit from \"effect/Exit\";\nimport { constVoid, flow, pipe } from \"effect/Function\";\nimport * as Layer from \"effect/Layer\";\nimport * as Schedule from \"effect/Schedule\";\nimport * as Scope from \"effect/Scope\";\nimport * as TestClock from \"effect/testing/TestClock\";\nimport * as TestConsole from \"effect/testing/TestConsole\";\nimport * as V from \"vitest\";\nconst getCurrentSuite = V.TestRunner.getCurrentSuite;\nconst runPromise = /*#__PURE__*/Effect.fnUntraced(function* (effect, _ctx) {\n const exit = yield* Effect.exit(effect);\n if (Exit.isFailure(exit)) {\n const errors = Cause.prettyErrors(exit.cause);\n for (let i = 0; i < errors.length; i++) {\n yield* Effect.logError(errors[i]);\n }\n }\n return yield* exit;\n}, (effect, _, ctx) => Effect.runPromise(effect, {\n signal: ctx?.signal\n}));\n/** @internal */\nconst runTest = ctx => effect => {\n const promise = runPromise(effect, ctx);\n if (ctx) {\n // Vitest stops awaiting the test promise once the signal aborts (timeout or\n // cancellation), so only then add a hook that waits for the finalizers.\n // Registering it unconditionally would change the teardown of every test.\n const onAbort = () => ctx.onTestFinished(() => promise.then(constVoid, constVoid));\n ctx.signal.addEventListener(\"abort\", onAbort, {\n once: true\n });\n const cleanup = () => ctx.signal.removeEventListener(\"abort\", onAbort);\n promise.then(cleanup, cleanup);\n // A retry after a timed-out attempt reuses the aborted signal, so no event fires.\n if (ctx.signal.aborted) onAbort();\n }\n return promise;\n};\nconst TestEnv = /*#__PURE__*/Layer.mergeAll(TestConsole.layer, /*#__PURE__*/TestClock.layer());\n/** @internal */\nexport const addEqualityTesters = () => {\n V.expect.addEqualityTesters([]);\n};\n/** @internal */\nconst testOptions = timeout => typeof timeout === \"number\" ? {\n timeout\n} : timeout ?? {};\nconst hookTimeout = timeout => timeout === undefined ? undefined : Duration.toMillis(Duration.fromInputUnsafe(timeout));\nconst propertyTestOptions = timeout => typeof timeout === \"number\" ? undefined : timeout;\nconst checkOptions = timeout => propertyTestOptions(timeout)?.arbitrary;\nconst compileArbitraryInput = input => Arbitrary.isArbitrary(input) ? input : Arbitrary.schema(input);\nconst makeArbitrary = arbitraries => Arbitrary.all(Array.isArray(arbitraries) ? arbitraries.map(compileArbitraryInput) : Object.fromEntries(Object.entries(arbitraries).map(([key, input]) => [key, compileArbitraryInput(input)])));\nconst normalizeProperty = (property, value) => Effect.catchCause(Effect.suspend(() => {\n const output = property(value);\n return Effect.isEffect(output) ? output : Effect.succeed(output);\n}), cause => Cause.hasInterrupts(cause) ? Effect.failCause(cause) : Effect.fail(cause));\nconst runCheck = (ctx, arbitrary, property, options) => runTest(ctx)(Effect.flatMapEager(Arbitrary.checkEffect(arbitrary, value => normalizeProperty(property, value), options), result => {\n const failure = Arbitrary.formatCheckFailure(result);\n return failure === undefined ? Effect.void : Effect.die(new Error(failure));\n}));\n// Vitest decides which fixtures to set up by parsing the source of a test\n// function's context parameter, and there is no public API to supply the names\n// for a wrapper, so wrappers expose the source of the function they call.\nconst withFixturesOf = (source, f) => Object.defineProperty(f, \"toString\", {\n value: () => source.toString()\n});\nconst makeItProxy = (it, overrides) => new Proxy(it, {\n apply(target, thisArg, argArray) {\n return Reflect.apply(target, thisArg, argArray);\n },\n get(target, property, receiver) {\n if (Object.hasOwn(overrides, property)) {\n return Reflect.get(overrides, property);\n }\n // do not bind: binding would strip vitest's static helpers (e.g. `describe.each`)\n return Reflect.get(target, property, receiver);\n }\n});\nconst collectTasks = (tasks, acc = []) => {\n for (let i = 0; i < tasks.length; i++) {\n const task = tasks[i];\n if (task.type === \"test\" && task.mode !== \"skip\" && task.mode !== \"todo\") {\n acc.push(task);\n } else if (task.tasks !== undefined) {\n collectTasks(task.tasks, acc);\n }\n }\n return acc;\n};\nconst registerProp = (it, name, arbitraries, property, timeout) => {\n const arbitrary = makeArbitrary(arbitraries);\n // Property callbacks receive only the base context; the empty source requests no fixtures.\n it(name, testOptions(timeout), withFixturesOf(() => {}, ctx => runCheck(ctx, arbitrary, values => property(values, ctx), checkOptions(timeout))));\n};\nconst makeTester = (mapEffect, it) => {\n const run = (ctx, args, self) => pipe(Effect.suspend(() => self(...args)), mapEffect, runTest(ctx));\n const register = test => (name, self, timeout) => test(name, testOptions(timeout), withFixturesOf(self, ctx => run(ctx, [ctx], self)));\n const each = cases => (name, self, timeout) => it.for(cases)(name, testOptions(timeout), withFixturesOf(self, (args, ctx) => run(ctx, [args, ctx], self).then(constVoid)));\n const prop = (name, arbitraries, self, timeout) => registerProp(it, name, arbitraries, (values, ctx) => Effect.mapEager(mapEffect(Effect.suspend(() => self(values, ctx))), value => value !== false), timeout);\n return Object.assign(register(it), {\n skip: register(it.skip),\n skipIf: condition => register(it.skipIf(condition)),\n runIf: condition => register(it.runIf(condition)),\n only: register(it.only),\n each,\n fails: register(it.fails),\n prop\n });\n};\nconst makeProp = it => (name, arbitraries, self, timeout) => registerProp(it, name, arbitraries, (values, ctx) => self(values, ctx) !== false, timeout);\nconst makeLayer = it => (layer_, options) => (...args) => {\n const excludeTestServices = options?.excludeTestServices ?? false;\n const withTestEnv = excludeTestServices ? layer_ : Layer.provideMerge(layer_, TestEnv);\n const memoMap = options?.memoMap ?? Effect.runSync(Layer.makeMemoMap);\n const scope = Effect.runSync(Scope.make());\n const contextEffect = Layer.buildWithMemoMap(withTestEnv, memoMap, scope).pipe(Effect.orDie, Effect.cached, Effect.runSync);\n let closed = false;\n const closeScope = () => {\n if (closed) {\n return Promise.resolve();\n }\n closed = true;\n // Layer cleanup must outlive the last test's already-aborted signal.\n return runPromise(Scope.close(scope, Exit.void));\n };\n const methods = makeItProxy(it, {\n effect: makeTester(effect => Effect.flatMap(contextEffect, context => effect.pipe(Effect.scoped, Effect.provide(context))), it),\n prop: makeProp(it),\n flakyTest,\n layer(nestedLayer, options) {\n return makeLayer(it)(Layer.provideMerge(nestedLayer, withTestEnv), {\n ...options,\n memoMap: Layer.forkMemoMapUnsafe(memoMap),\n excludeTestServices\n });\n }\n });\n if (args.length === 1) {\n const currentSuite = getCurrentSuite();\n const previousTasks = new Set(currentSuite.tasks);\n args[0](methods);\n const blockTasks = collectTasks(currentSuite.tasks.filter(task => !previousTasks.has(task)));\n if (blockTasks.length === 0) {\n V.afterAll(() => closeScope(), hookTimeout(options?.timeout));\n return;\n }\n const blockTaskSet = new Set(blockTasks);\n let remaining = blockTasks.length;\n V.beforeEach(\n // Destructured so Vitest can parse the hook once the suite defines fixtures.\n ({\n onTestFinished,\n signal,\n task\n }) => {\n if (!blockTaskSet.has(task)) {\n return;\n }\n onTestFinished(() => {\n remaining--;\n if (remaining === 0) {\n return closeScope();\n }\n });\n return runPromise(Effect.asVoid(contextEffect), {\n signal\n });\n }, hookTimeout(options?.timeout));\n V.afterAll(() => closeScope(), hookTimeout(options?.timeout));\n return;\n }\n const suiteOptions = options?.concurrent === undefined ? {} : {\n concurrent: options.concurrent\n };\n return V.describe(args[0], suiteOptions, () => {\n V.beforeAll(() => runPromise(Effect.asVoid(contextEffect)), hookTimeout(options?.timeout));\n V.afterAll(() => closeScope(), hookTimeout(options?.timeout));\n return args[1](methods);\n });\n};\n/** @internal */\nexport const flakyTest = (self, timeout = Duration.seconds(30)) => pipe(self, Effect.scoped, Effect.sandbox, Effect.retry(pipe(Schedule.recurs(10), Schedule.while(_ => Effect.succeed(Duration.isLessThanOrEqualTo(Duration.fromInputUnsafe(_.elapsed), Duration.fromInputUnsafe(timeout)))))), Effect.orDie);\n/** @internal */\nexport const makeMethods = it => makeItProxy(it, {\n effect: makeTester(flow(Effect.scoped, Effect.provide(TestEnv)), it),\n live: makeTester(Effect.scoped, it),\n flakyTest,\n layer: makeLayer(it),\n prop: makeProp(it)\n});\n/** @internal */\nexport const it = /*#__PURE__*/makeMethods(V.it);\n/** @internal */\nexport const {\n /** @internal */\n effect,\n /** @internal */\n layer,\n /** @internal */\n live,\n /** @internal */\n prop\n} = it;\n/** @internal */\nexport const describeWrapped = (name, f) => V.describe(name, () => f(it));\n//# sourceMappingURL=internal.js.map","import * as internal from \"./internal/internal.js\";\n/**\n * Re-exports the `vitest` API unchanged.\n *\n * @stability unstable\n * @since 4.0.0\n */\nexport * from \"vitest\";\n/**\n * @since 4.0.0\n */\nexport const addEqualityTesters = internal.addEqualityTesters;\n/**\n * @since 4.0.0\n */\nexport const effect = internal.effect;\n/**\n * @since 4.0.0\n */\nexport const live = internal.live;\n/**\n * Share a `Layer` between multiple tests, optionally wrapping\n * the tests in a `describe` block if a name is provided.\n *\n * Named layers accept `concurrent` to override inherited suite concurrency.\n * Anonymous layers always inherit the enclosing suite's concurrency.\n * Use `ctx.expect` in concurrent tests for test-local snapshots and assertion counts.\n *\n * @since 4.0.0\n *\n * ```ts\n * import { assert, layer } from \"@effect/vitest\"\n * import { Effect, Layer, Context } from \"effect\"\n *\n * class Foo extends Context.Service<Foo, \"foo\">()(\"Foo\") {\n * static layer = Layer.succeed(Foo, \"foo\")\n * }\n *\n * class Bar extends Context.Service<Bar, \"bar\">()(\"Bar\") {\n * static layer = Layer.effect(\n * Bar,\n * Effect.map(Foo, () => \"bar\" as const)\n * )\n * }\n *\n * layer(Foo.layer)(\"layer\", (it) => {\n * it.effect(\"adds context\", () =>\n * Effect.gen(function*() {\n * const foo = yield* Foo\n * assert.strictEqual(foo, \"foo\")\n * }))\n *\n * it.layer(Bar.layer)(\"nested\", (it) => {\n * it.effect(\"adds context\", () =>\n * Effect.gen(function*() {\n * const foo = yield* Foo\n * const bar = yield* Bar\n * assert.strictEqual(foo, \"foo\")\n * assert.strictEqual(bar, \"bar\")\n * }))\n * })\n * })\n * ```\n */\nexport const layer = internal.layer;\n/**\n * @since 4.0.0\n */\nexport const flakyTest = internal.flakyTest;\n/**\n * @since 4.0.0\n */\nexport const prop = internal.prop;\n/**\n * @since 4.0.0\n */\n/**\n * @since 4.0.0\n */\nexport const it = internal.it;\n/**\n * Creates the Effect test helpers for a Vitest test API, such as one extended with fixtures.\n *\n * **Details**\n *\n * Tests receive the fixtures they destructure from their context. Vitest sets them up before the test and tears\n * them down after the test's scope closes, as it does for its own tests. `it.effect.each` passes the context\n * after the test case, and named and anonymous `it.layer` blocks keep the fixtures.\n *\n * **Gotchas**\n *\n * Vitest reads the destructured names to decide which fixtures to set up. Once any fixture is defined, a test that\n * takes the whole context as a plain parameter, such as `(ctx) =>`, fails with a `FixtureParseError`. Property\n * tests receive only the base test context and cannot request fixtures; auto fixtures still run.\n *\n * **Example** (Using a Vitest fixture in an Effect test)\n *\n * ```ts\n * import { assert, makeMethods, test } from \"@effect/vitest\"\n * import { Effect } from \"effect\"\n *\n * const it = makeMethods(\n * test.extend(\"config\", { scope: \"file\" }, () => ({ port: 3000 }))\n * )\n *\n * it.effect(\"reads the config fixture\", ({ config }) =>\n * Effect.sync(() => {\n * assert.strictEqual(config.port, 3000)\n * }))\n * ```\n *\n * @since 4.0.0\n */\nexport const makeMethods = internal.makeMethods;\n/**\n * @since 4.0.0\n */\nexport const describeWrapped = internal.describeWrapped;\n//# sourceMappingURL=index.js.map","import { it as effect } from \"@effect/vitest\"\nimport { Effect, Layer, Queue, Redacted } from \"effect\"\nimport * as Socket from \"effect/socket/Socket\"\nimport * as FakeWebSocket from \"@effect-uai/core/testing/FakeWebSocket\"\nimport { describe, expect, it } from \"vitest\"\nimport { classifyClose, closeReason, connect } from \"./gateway.js\"\n\ndescribe(\"classifyClose\", () => {\n it(\"never retries a close reconnecting cannot fix\", () => {\n expect([4004, 4010, 4011, 4012, 4013, 4014].map(classifyClose)).toEqual(Array(6).fill(\"fatal\"))\n })\n\n it(\"identifies fresh only where the session itself is gone\", () => {\n expect([4007, 4009].map(classifyClose)).toEqual([\"reidentify\", \"reidentify\"])\n })\n\n it(\"resumes on everything else, transport drops included\", () => {\n // 1006 is a dropped connection, 4000 the code the zombie check sends.\n expect([4000, 4003, 4005, 4008, 1006, 1011].map(classifyClose)).toEqual(Array(6).fill(\"resume\"))\n })\n})\n\ndescribe(\"closeReason\", () => {\n it(\"names a fatal code, since Discord's close frames carry no reason\", () => {\n expect(closeReason(4014, \"\")).toContain(\"privileged intent\")\n })\n\n it(\"falls back to whatever the socket reported\", () => {\n expect(closeReason(1006, \"connection reset\")).toBe(\"connection reset\")\n })\n})\n\nconst cfg = { token: Redacted.make(\"token\"), intents: 0, url: \"wss://gateway.test\" }\n\n// Long enough that no heartbeat lands mid-test.\nconst HELLO = { op: 10, d: { heartbeat_interval: 3_600_000 } }\n\nconst READY = {\n op: 0,\n t: \"READY\",\n s: 1,\n d: {\n user: { id: \"1\", username: \"bot\" },\n session_id: \"session\",\n resume_gateway_url: \"wss://resume.test\",\n },\n}\n\n// Identify gets READY, resume gets RESUMED.\nconst answers: Record<number, ReadonlyArray<unknown>> = {\n 2: [READY],\n 6: [{ op: 0, t: \"RESUMED\", s: 2 }],\n}\n\nconst gateway = FakeWebSocket.make({\n greeting: [HELLO],\n reply: (frame) => Effect.succeed(answers[(frame as { op: number }).op] ?? []),\n})\n\n/** One fake per connection, in dial order; the last one takes any extra dials. */\nconst dialing = (servers: ReadonlyArray<FakeWebSocket.FakeWebSocketServer>) => {\n const urls: Array<string> = []\n const layer = Layer.succeed(Socket.WebSocketConstructor)((url, protocols) => {\n const server = servers[Math.min(urls.length, servers.length - 1)]!\n urls.push(url)\n return server.connect(url, protocols)\n })\n return { urls, layer }\n}\n\ndescribe(\"gateway close handling\", () => {\n effect.live.each([1000, 4000])(\"resumes on the resume host after a %i close\", (code) =>\n Effect.gen(function* () {\n const first = yield* gateway\n const second = yield* gateway\n const { urls, layer } = dialing([first, second])\n const session = yield* connect(cfg).pipe(Effect.provide(layer))\n expect(session.bot.username).toBe(\"bot\")\n\n yield* first.close(code)\n yield* second.awaitSent(1)\n\n expect(urls[1]).toMatch(/^wss:\\/\\/resume\\.test\\//)\n expect(yield* second.sent).toEqual([\n { op: 6, d: { token: \"token\", session_id: \"session\", seq: 1 } },\n ])\n }),\n )\n\n effect.live(\"fails the dispatches on a fatal close after READY\", () =>\n Effect.gen(function* () {\n const server = yield* gateway\n const session = yield* connect(cfg).pipe(Effect.provide(dialing([server]).layer))\n\n yield* server.close(4014)\n const error = yield* Queue.take(session.dispatches).pipe(Effect.flip)\n\n expect(error).toMatchObject({ _tag: \"MessengerTransportClosed\", raw: { code: 4014 } })\n }),\n )\n\n effect.live(\"fails the connect on a fatal close before READY\", () =>\n Effect.gen(function* () {\n const server = yield* FakeWebSocket.make({ greeting: [HELLO] })\n yield* server.awaitSent(1).pipe(Effect.andThen(server.close(4004)), Effect.forkScoped)\n\n const error = yield* connect(cfg).pipe(Effect.provide(dialing([server]).layer), Effect.flip)\n\n expect(error._tag).toBe(\"MessengerConnectFailed\")\n expect(error.reason).toContain(\"authentication failed\")\n }),\n )\n})\n"],"x_google_ignoreList":[0,1],"mappings":";;;;;;;;;;;;;;;;;;;;AAeA,MAAM,kBAAA,WAA+B;AACrC,MAAM,aAA0B,uBAAO,WAAW,WAAW,QAAQ,MAAM;CACzE,MAAM,OAAO,OAAOA,SAAO,KAAK,MAAM;CACtC,IAAI,KAAK,UAAU,IAAI,GAAG;EACxB,MAAM,SAASC,QAAM,aAAa,KAAK,KAAK;EAC5C,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KACjC,OAAOD,SAAO,SAAS,OAAO,EAAE;CAEpC;CACA,OAAO,OAAO;AAChB,IAAI,QAAQ,GAAG,QAAQA,SAAO,WAAW,QAAQ,EAC/C,QAAQ,KAAK,OACf,CAAC,CAAC;;AAEF,MAAM,WAAU,SAAO,WAAU;CAC/B,MAAM,UAAU,WAAW,QAAQ,GAAG;CACtC,IAAI,KAAK;EAIP,MAAM,gBAAgB,IAAI,qBAAqB,QAAQ,KAAK,WAAW,SAAS,CAAC;EACjF,IAAI,OAAO,iBAAiB,SAAS,SAAS,EAC5C,MAAM,KACR,CAAC;EACD,MAAM,gBAAgB,IAAI,OAAO,oBAAoB,SAAS,OAAO;EACrE,QAAQ,KAAK,SAAS,OAAO;EAE7B,IAAI,IAAI,OAAO,SAAS,QAAQ;CAClC;CACA,OAAO;AACT;AACA,MAAM,UAAuB,sBAAM,SAAS,YAAY,OAAoB,wBAAU,MAAM,CAAC;;AAM7F,MAAM,eAAc,YAAW,OAAO,YAAY,WAAW,EAC3D,QACF,IAAI,WAAW,CAAC;AAChB,MAAM,eAAc,YAAW,YAAY,KAAA,IAAY,KAAA,IAAYE,WAAS,SAASA,WAAS,gBAAgB,OAAO,CAAC;AACtH,MAAM,uBAAsB,YAAW,OAAO,YAAY,WAAW,KAAA,IAAY;AACjF,MAAM,gBAAe,YAAW,oBAAoB,OAAO,CAAC,EAAE;AAC9D,MAAM,yBAAwB,UAAS,UAAU,YAAY,KAAK,IAAI,QAAQ,UAAU,OAAO,KAAK;AACpG,MAAM,iBAAgB,gBAAe,UAAU,IAAI,MAAM,QAAQ,WAAW,IAAI,YAAY,IAAI,qBAAqB,IAAI,OAAO,YAAY,OAAO,QAAQ,WAAW,CAAC,CAAC,KAAK,CAAC,KAAK,WAAW,CAAC,KAAK,sBAAsB,KAAK,CAAC,CAAC,CAAC,CAAC;AACnO,MAAM,qBAAqB,UAAU,UAAUF,SAAO,WAAWA,SAAO,cAAc;CACpF,MAAM,SAAS,SAAS,KAAK;CAC7B,OAAOA,SAAO,SAAS,MAAM,IAAI,SAASA,SAAO,QAAQ,MAAM;AACjE,CAAC,IAAG,UAASC,QAAM,cAAc,KAAK,IAAID,SAAO,UAAU,KAAK,IAAIA,SAAO,KAAK,KAAK,CAAC;AACtF,MAAM,YAAY,KAAK,WAAW,UAAU,YAAY,QAAQ,GAAG,CAAC,CAACA,SAAO,aAAa,UAAU,YAAY,YAAW,UAAS,kBAAkB,UAAU,KAAK,GAAG,OAAO,IAAG,WAAU;CACzL,MAAM,UAAU,UAAU,mBAAmB,MAAM;CACnD,OAAO,YAAY,KAAA,IAAYA,SAAO,OAAOA,SAAO,IAAI,IAAI,MAAM,OAAO,CAAC;AAC5E,CAAC,CAAC;AAIF,MAAM,kBAAkB,QAAQ,MAAM,OAAO,eAAe,GAAG,YAAY,EACzE,aAAa,OAAO,SAAS,EAC/B,CAAC;AACD,MAAM,eAAe,IAAI,cAAc,IAAI,MAAM,IAAI;CACnD,MAAM,QAAQ,SAAS,UAAU;EAC/B,OAAO,QAAQ,MAAM,QAAQ,SAAS,QAAQ;CAChD;CACA,IAAI,QAAQ,UAAU,UAAU;EAC9B,IAAI,OAAO,OAAO,WAAW,QAAQ,GACnC,OAAO,QAAQ,IAAI,WAAW,QAAQ;EAGxC,OAAO,QAAQ,IAAI,QAAQ,UAAU,QAAQ;CAC/C;AACF,CAAC;AACD,MAAM,gBAAgB,OAAO,MAAM,CAAC,MAAM;CACxC,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,OAAO,MAAM;EACnB,IAAI,KAAK,SAAS,UAAU,KAAK,SAAS,UAAU,KAAK,SAAS,QAChE,IAAI,KAAK,IAAI;OACR,IAAI,KAAK,UAAU,KAAA,GACxB,aAAa,KAAK,OAAO,GAAG;CAEhC;CACA,OAAO;AACT;AACA,MAAM,gBAAgB,IAAI,MAAM,aAAa,UAAU,YAAY;CACjE,MAAM,YAAY,cAAc,WAAW;CAE3C,GAAG,MAAM,YAAY,OAAO,GAAG,qBAAqB,CAAC,IAAG,QAAO,SAAS,KAAK,YAAW,WAAU,SAAS,QAAQ,GAAG,GAAG,aAAa,OAAO,CAAC,CAAC,CAAC;AAClJ;AACA,MAAM,cAAc,WAAW,OAAO;CACpC,MAAM,OAAO,KAAK,MAAM,SAAS,KAAKA,SAAO,cAAc,KAAK,GAAG,IAAI,CAAC,GAAG,WAAW,QAAQ,GAAG,CAAC;CAClG,MAAM,YAAW,UAAS,MAAM,MAAM,YAAY,KAAK,MAAM,YAAY,OAAO,GAAG,eAAe,OAAM,QAAO,IAAI,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,CAAC;CACrI,MAAM,QAAO,WAAU,MAAM,MAAM,YAAY,GAAG,IAAI,KAAK,CAAC,CAAC,MAAM,YAAY,OAAO,GAAG,eAAe,OAAO,MAAM,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC;CACzK,MAAM,QAAQ,MAAM,aAAa,MAAM,YAAY,aAAa,IAAI,MAAM,cAAc,QAAQ,QAAQA,SAAO,SAAS,UAAUA,SAAO,cAAc,KAAK,QAAQ,GAAG,CAAC,CAAC,IAAG,UAAS,UAAU,KAAK,GAAG,OAAO;CAC9M,OAAO,OAAO,OAAO,SAAS,EAAE,GAAG;EACjC,MAAM,SAAS,GAAG,IAAI;EACtB,SAAQ,cAAa,SAAS,GAAG,OAAO,SAAS,CAAC;EAClD,QAAO,cAAa,SAAS,GAAG,MAAM,SAAS,CAAC;EAChD,MAAM,SAAS,GAAG,IAAI;EACtB;EACA,OAAO,SAAS,GAAG,KAAK;EACxB;CACF,CAAC;AACH;AACA,MAAM,YAAW,QAAO,MAAM,aAAa,MAAM,YAAY,aAAa,IAAI,MAAM,cAAc,QAAQ,QAAQ,KAAK,QAAQ,GAAG,MAAM,OAAO,OAAO;AACtJ,MAAM,aAAY,QAAO,QAAQ,aAAa,GAAG,SAAS;CACxD,MAAM,sBAAsB,SAAS,uBAAuB;CAC5D,MAAM,cAAc,sBAAsB,SAASG,QAAM,aAAa,QAAQ,OAAO;CACrF,MAAM,UAAU,SAAS,WAAWH,SAAO,QAAQG,QAAM,WAAW;CACpE,MAAM,QAAQH,SAAO,QAAQI,QAAM,KAAK,CAAC;CACzC,MAAM,gBAAgBD,QAAM,iBAAiB,aAAa,SAAS,KAAK,CAAC,CAAC,KAAKH,SAAO,OAAOA,SAAO,QAAQA,SAAO,OAAO;CAC1H,IAAI,SAAS;CACb,MAAM,mBAAmB;EACvB,IAAI,QACF,OAAO,QAAQ,QAAQ;EAEzB,SAAS;EAET,OAAO,WAAWI,QAAM,MAAM,OAAO,KAAK,IAAI,CAAC;CACjD;CACA,MAAM,UAAU,YAAY,IAAI;EAC9B,QAAQ,YAAW,WAAUJ,SAAO,QAAQ,gBAAe,YAAW,OAAO,KAAKA,SAAO,QAAQA,SAAO,QAAQ,OAAO,CAAC,CAAC,GAAG,EAAE;EAC9H,MAAM,SAAS,EAAE;EACjB;EACA,MAAM,aAAa,SAAS;GAC1B,OAAO,UAAU,EAAE,CAAC,CAACG,QAAM,aAAa,aAAa,WAAW,GAAG;IACjE,GAAG;IACH,SAASA,QAAM,kBAAkB,OAAO;IACxC;GACF,CAAC;EACH;CACF,CAAC;CACD,IAAI,KAAK,WAAW,GAAG;EACrB,MAAM,eAAe,gBAAgB;EACrC,MAAM,gBAAgB,IAAI,IAAI,aAAa,KAAK;EAChD,KAAK,EAAE,CAAC,OAAO;EACf,MAAM,aAAa,aAAa,aAAa,MAAM,QAAO,SAAQ,CAAC,cAAc,IAAI,IAAI,CAAC,CAAC;EAC3F,IAAI,WAAW,WAAW,GAAG;GAC3B,eAAiB,WAAW,GAAG,YAAY,SAAS,OAAO,CAAC;GAC5D;EACF;EACA,MAAM,eAAe,IAAI,IAAI,UAAU;EACvC,IAAI,YAAY,WAAW;EAC3B,YAEC,EACC,gBACA,QACA,WACI;GACJ,IAAI,CAAC,aAAa,IAAI,IAAI,GACxB;GAEF,qBAAqB;IACnB;IACA,IAAI,cAAc,GAChB,OAAO,WAAW;GAEtB,CAAC;GACD,OAAO,WAAWH,SAAO,OAAO,aAAa,GAAG,EAC9C,OACF,CAAC;EACH,GAAG,YAAY,SAAS,OAAO,CAAC;EAChC,eAAiB,WAAW,GAAG,YAAY,SAAS,OAAO,CAAC;EAC5D;CACF;CACA,MAAM,eAAe,SAAS,eAAe,KAAA,IAAY,CAAC,IAAI,EAC5D,YAAY,QAAQ,WACtB;CACA,OAAOK,SAAW,KAAK,IAAI,oBAAoB;EAC7C,gBAAkB,WAAWL,SAAO,OAAO,aAAa,CAAC,GAAG,YAAY,SAAS,OAAO,CAAC;EACzF,eAAiB,WAAW,GAAG,YAAY,SAAS,OAAO,CAAC;EAC5D,OAAO,KAAK,EAAE,CAAC,OAAO;CACxB,CAAC;AACH;;AAEA,MAAa,aAAa,MAAM,UAAUE,WAAS,QAAQ,EAAE,MAAM,KAAK,MAAMF,SAAO,QAAQA,SAAO,SAASA,SAAO,MAAM,KAAKM,WAAS,OAAO,EAAE,GAAGA,WAAS,OAAM,MAAKN,SAAO,QAAQE,WAAS,oBAAoBA,WAAS,gBAAgB,EAAE,OAAO,GAAGA,WAAS,gBAAgB,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,GAAGF,SAAO,KAAK;;AAE7S,MAAa,eAAc,OAAM,YAAY,IAAI;CAC/C,QAAQ,WAAW,KAAKA,SAAO,QAAQA,SAAO,QAAQ,OAAO,CAAC,GAAG,EAAE;CACnE,MAAM,WAAWA,SAAO,QAAQ,EAAE;CAClC;CACA,OAAO,UAAU,EAAE;CACnB,MAAM,SAAS,EAAE;AACnB,CAAC;;AAED,MAAaO,OAAkB,0BAAYC,IAAI;;AAE/C,MAAa,EAEX,QAEA,OAEA,MAEA,SACED;;;;;;;;;ACnIJ,MAAa,KAAKE;;;ACxElB,SAAS,uBAAuB;CAC9B,KAAG,uDAAuD;EACxD,aAAO;GAAC;GAAM;GAAM;GAAM;GAAM;GAAM;EAAI,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC;CAChG,CAAC;CAED,KAAG,gEAAgE;EACjE,aAAO,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,cAAc,YAAY,CAAC;CAC9E,CAAC;CAED,KAAG,8DAA8D;EAE/D,aAAO;GAAC;GAAM;GAAM;GAAM;GAAM;GAAM;EAAI,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,QAAQ,MAAM,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC;CACjG,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB;CAC5B,KAAG,0EAA0E;EAC3E,aAAO,YAAY,MAAM,EAAE,CAAC,CAAC,CAAC,UAAU,mBAAmB;CAC7D,CAAC;CAED,KAAG,oDAAoD;EACrD,aAAO,YAAY,MAAM,kBAAkB,CAAC,CAAC,CAAC,KAAK,kBAAkB;CACvE,CAAC;AACH,CAAC;AAED,MAAM,MAAM;CAAE,OAAO,SAAS,KAAK,OAAO;CAAG,SAAS;CAAG,KAAK;AAAqB;AAGnF,MAAM,QAAQ;CAAE,IAAI;CAAI,GAAG,EAAE,oBAAoB,KAAU;AAAE;AAc7D,MAAM,UAAkD;CACtD,GAAG,CAAC;EAZJ,IAAI;EACJ,GAAG;EACH,GAAG;EACH,GAAG;GACD,MAAM;IAAE,IAAI;IAAK,UAAU;GAAM;GACjC,YAAY;GACZ,oBAAoB;EACtB;CAKQ,CAAC;CACT,GAAG,CAAC;EAAE,IAAI;EAAG,GAAG;EAAW,GAAG;CAAE,CAAC;AACnC;AAEA,MAAM,UAAU,cAAc,KAAK;CACjC,UAAU,CAAC,KAAK;CAChB,QAAQ,UAAU,OAAO,QAAQ,QAAS,MAAyB,OAAO,CAAC,CAAC;AAC9E,CAAC;;AAGD,MAAM,WAAW,YAA8D;CAC7E,MAAM,OAAsB,CAAC;CAM7B,OAAO;EAAE;EAAM,OALD,MAAM,QAAQ,OAAO,oBAAoB,CAAC,EAAE,KAAK,cAAc;GAC3E,MAAM,SAAS,QAAQ,KAAK,IAAI,KAAK,QAAQ,QAAQ,SAAS,CAAC;GAC/D,KAAK,KAAK,GAAG;GACb,OAAO,OAAO,QAAQ,KAAK,SAAS;EACtC,CACmB;CAAE;AACvB;AAEA,SAAS,gCAAgC;CACvC,GAAO,KAAK,KAAK,CAAC,KAAM,GAAI,CAAC,CAAC,CAAC,gDAAgD,SAC7E,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO;EACrB,MAAM,SAAS,OAAO;EACtB,MAAM,EAAE,MAAM,UAAU,QAAQ,CAAC,OAAO,MAAM,CAAC;EAC/C,MAAM,UAAU,OAAO,QAAQ,GAAG,CAAC,CAAC,KAAK,OAAO,QAAQ,KAAK,CAAC;EAC9D,aAAO,QAAQ,IAAI,QAAQ,CAAC,CAAC,KAAK,KAAK;EAEvC,OAAO,MAAM,MAAM,IAAI;EACvB,OAAO,OAAO,UAAU,CAAC;EAEzB,aAAO,KAAK,EAAE,CAAC,CAAC,QAAQ,yBAAyB;EACjD,aAAO,OAAO,OAAO,IAAI,CAAC,CAAC,QAAQ,CACjC;GAAE,IAAI;GAAG,GAAG;IAAE,OAAO;IAAS,YAAY;IAAW,KAAK;GAAE;EAAE,CAChE,CAAC;CACH,CAAC,CACH;CAEA,GAAO,KAAK,2DACV,OAAO,IAAI,aAAa;EACtB,MAAM,SAAS,OAAO;EACtB,MAAM,UAAU,OAAO,QAAQ,GAAG,CAAC,CAAC,KAAK,OAAO,QAAQ,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC;EAEhF,OAAO,OAAO,MAAM,IAAI;EACxB,MAAM,QAAQ,OAAO,MAAM,KAAK,QAAQ,UAAU,CAAC,CAAC,KAAK,OAAO,IAAI;EAEpE,aAAO,KAAK,CAAC,CAAC,cAAc;GAAE,MAAM;GAA4B,KAAK,EAAE,MAAM,KAAK;EAAE,CAAC;CACvF,CAAC,CACH;CAEA,GAAO,KAAK,yDACV,OAAO,IAAI,aAAa;EACtB,MAAM,SAAS,OAAO,cAAc,KAAK,EAAE,UAAU,CAAC,KAAK,EAAE,CAAC;EAC9D,OAAO,OAAO,UAAU,CAAC,CAAC,CAAC,KAAK,OAAO,QAAQ,OAAO,MAAM,IAAI,CAAC,GAAG,OAAO,UAAU;EAErF,MAAM,QAAQ,OAAO,QAAQ,GAAG,CAAC,CAAC,KAAK,OAAO,QAAQ,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,GAAG,OAAO,IAAI;EAE3F,aAAO,MAAM,IAAI,CAAC,CAAC,KAAK,wBAAwB;EAChD,aAAO,MAAM,MAAM,CAAC,CAAC,UAAU,uBAAuB;CACxD,CAAC,CACH;AACF,CAAC"}
@@ -1,2 +1,2 @@
1
- import { a as Method, c as decoded, i as Fields, l as provider, n as ApiFailure, o as Upload, r as Config, s as call, t as ApiError, u as upload } from "../rest-CZSW-VJK.mjs";
1
+ import { a as Method, c as decoded, i as Fields, l as provider, n as ApiFailure, o as Upload, r as Config, s as call, t as ApiError, u as upload } from "../rest-SmIBt9Nh.mjs";
2
2
  export { ApiError, ApiFailure, Config, Fields, Method, Upload, call, decoded, provider, upload };
@@ -1,5 +1,5 @@
1
1
  import { Data, Duration, Effect, Redacted, Schema } from "effect";
2
- import { HttpClient, HttpClientRequest } from "effect/unstable/http";
2
+ import { HttpClient, HttpClientRequest } from "effect/http";
3
3
  import * as MessengerError from "@effect-uai/core/MessengerError";
4
4
  import * as Multipart from "@effect-uai/core/Multipart";
5
5
  //#region src/internal/rest.ts
@@ -1 +1 @@
1
- {"version":3,"file":"rest.mjs","names":[],"sources":["../../src/internal/rest.ts"],"sourcesContent":["import { Data, Duration, Effect, Redacted, Schema } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/unstable/http\"\nimport * as MessengerError from \"@effect-uai/core/MessengerError\"\nimport * as Multipart from \"@effect-uai/core/Multipart\"\n\nexport const provider = \"discord\"\n\n/** Tracks the package version; Discord requires a `DiscordBot (url, version)` agent. */\nconst userAgent = \"DiscordBot (https://effect-uai.betalyra.com, 0.14.0)\"\n\nexport type Config = {\n readonly token: Redacted.Redacted\n /** Defaults to `https://discord.com/api/v10`. Always version-pinned. */\n readonly baseUrl?: string\n}\n\nexport type Method = \"GET\" | \"POST\" | \"PATCH\" | \"PUT\" | \"DELETE\"\n\n/** JSON body fields of a request. */\nexport type Fields = Readonly<Record<string, unknown>>\n\n/**\n * A REST call that came back non-2xx or never came back at all. `code` is\n * Discord's own JSON error code, `status` the HTTP one; each verb maps this\n * onto its own `MessengerError`.\n */\nexport class ApiFailure extends Data.TaggedError(\"DiscordApiFailure\")<{\n readonly route: string\n readonly status?: number\n readonly code?: number\n readonly message: string\n readonly raw: unknown\n}> {}\n\nexport type ApiError = ApiFailure | MessengerError.MessengerRateLimited\n\nconst ErrorBody = Schema.Struct({\n message: Schema.optional(Schema.String),\n code: Schema.optional(Schema.Number),\n retry_after: Schema.optional(Schema.Number),\n})\n\n/** The file part of a multipart message. Discord names it `files[n]`. */\nexport type Upload = {\n readonly bytes: Uint8Array\n readonly filename: string\n readonly mimeType: string\n}\n\nconst endpoint = (cfg: Config, path: string): string =>\n `${cfg.baseUrl ?? \"https://discord.com/api/v10\"}${path}`\n\nconst constructors: Record<Method, (url: string) => HttpClientRequest.HttpClientRequest> = {\n GET: HttpClientRequest.get,\n POST: HttpClientRequest.post,\n PATCH: HttpClientRequest.patch,\n PUT: HttpClientRequest.put,\n DELETE: HttpClientRequest.delete,\n}\n\nconst request = (cfg: Config, method: Method, path: string) =>\n constructors[method](endpoint(cfg, path)).pipe(\n HttpClientRequest.setHeaders({\n Authorization: `Bot ${Redacted.value(cfg.token)}`,\n \"User-Agent\": userAgent,\n }),\n )\n\nconst transportFailure = (route: string) => (raw: unknown) =>\n new ApiFailure({ route, message: \"transport failure\", raw })\n\nconst rejected = (route: string, status: number, raw: unknown): Effect.Effect<never, ApiError> =>\n Effect.gen(function* () {\n const body = yield* Schema.decodeUnknownEffect(ErrorBody)(raw).pipe(\n Effect.orElseSucceed((): typeof ErrorBody.Type => ({})),\n )\n // Discord's `retry_after` is float seconds; only a 429 carries one.\n return yield* status === 429 && body.retry_after !== undefined\n ? new MessengerError.MessengerRateLimited({\n provider,\n retryAfter: Duration.seconds(body.retry_after),\n raw,\n })\n : new ApiFailure({\n route,\n status,\n ...(body.code !== undefined && { code: body.code }),\n message: body.message ?? `HTTP ${status}`,\n raw,\n })\n })\n\nconst send = (\n route: string,\n httpRequest: HttpClientRequest.HttpClientRequest,\n): Effect.Effect<unknown, ApiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const response = yield* client\n .execute(httpRequest)\n .pipe(Effect.mapError(transportFailure(route)))\n // 204 is the success shape of `typing`, `react` and every delete: no body\n // to read, and reading one would fail.\n if (response.status === 204) return undefined\n const body = yield* response.json.pipe(Effect.mapError(transportFailure(route)))\n return response.status < 400 ? body : yield* rejected(route, response.status, body)\n })\n\n/** One JSON call against the API base. The response body comes back undecoded. */\nexport const call =\n (cfg: Config) =>\n (\n method: Method,\n path: string,\n body?: unknown,\n ): Effect.Effect<unknown, ApiError, HttpClient.HttpClient> =>\n send(\n `${method} ${path}`,\n body === undefined\n ? request(cfg, method, path)\n : request(cfg, method, path).pipe(HttpClientRequest.bodyJsonUnsafe(body)),\n )\n\n// The `attachments` entry is what binds `files[0]` to the message; without it\n// Discord can drop the part and reject the rest as an empty message.\nconst formData = (payload: Fields, file: Upload): FormData => {\n const form = new FormData()\n form.append(\n \"payload_json\",\n JSON.stringify({ ...payload, attachments: [{ id: 0, filename: file.filename }] }),\n )\n form.append(\n \"files[0]\",\n new File([file.bytes as Uint8Array<ArrayBuffer>], file.filename, { type: file.mimeType }),\n )\n return form\n}\n\n/** One multipart message: the JSON fields as `payload_json` plus a single file. */\nexport const upload =\n (cfg: Config) =>\n (\n path: string,\n payload: Fields,\n file: Upload,\n ): Effect.Effect<unknown, ApiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const route = `POST ${path}`\n const body = yield* Multipart.bodyMultipart(formData(payload, file)).pipe(\n Effect.mapError(transportFailure(route)),\n )\n return yield* send(route, request(cfg, \"POST\", path).pipe(body))\n })\n\n/** Decode a response body; a shape mismatch is a malformed response, not a defect. */\nexport const decoded =\n <A>(route: string, schema: Schema.Decoder<A>) =>\n <E, R>(result: Effect.Effect<unknown, E, R>): Effect.Effect<A, E | ApiFailure, R> =>\n Effect.gen(function* () {\n const raw = yield* result\n return yield* Schema.decodeUnknownEffect(schema)(raw).pipe(\n Effect.mapError(() => new ApiFailure({ route, message: \"malformed response\", raw })),\n )\n })\n"],"mappings":"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"}
1
+ {"version":3,"file":"rest.mjs","names":[],"sources":["../../src/internal/rest.ts"],"sourcesContent":["import { Data, Duration, Effect, Redacted, Schema } from \"effect\"\nimport { HttpClient, HttpClientRequest } from \"effect/http\"\nimport * as MessengerError from \"@effect-uai/core/MessengerError\"\nimport * as Multipart from \"@effect-uai/core/Multipart\"\n\nexport const provider = \"discord\"\n\n/** Tracks the package version; Discord requires a `DiscordBot (url, version)` agent. */\nconst userAgent = \"DiscordBot (https://effect-uai.betalyra.com, 0.14.0)\"\n\nexport type Config = {\n readonly token: Redacted.Redacted\n /** Defaults to `https://discord.com/api/v10`. Always version-pinned. */\n readonly baseUrl?: string\n}\n\nexport type Method = \"GET\" | \"POST\" | \"PATCH\" | \"PUT\" | \"DELETE\"\n\n/** JSON body fields of a request. */\nexport type Fields = Readonly<Record<string, unknown>>\n\n/**\n * A REST call that came back non-2xx or never came back at all. `code` is\n * Discord's own JSON error code, `status` the HTTP one; each verb maps this\n * onto its own `MessengerError`.\n */\nexport class ApiFailure extends Data.TaggedError(\"DiscordApiFailure\")<{\n readonly route: string\n readonly status?: number\n readonly code?: number\n readonly message: string\n readonly raw: unknown\n}> {}\n\nexport type ApiError = ApiFailure | MessengerError.MessengerRateLimited\n\nconst ErrorBody = Schema.Struct({\n message: Schema.optional(Schema.String),\n code: Schema.optional(Schema.Number),\n retry_after: Schema.optional(Schema.Number),\n})\n\n/** The file part of a multipart message. Discord names it `files[n]`. */\nexport type Upload = {\n readonly bytes: Uint8Array\n readonly filename: string\n readonly mimeType: string\n}\n\nconst endpoint = (cfg: Config, path: string): string =>\n `${cfg.baseUrl ?? \"https://discord.com/api/v10\"}${path}`\n\nconst constructors: Record<Method, (url: string) => HttpClientRequest.HttpClientRequest> = {\n GET: HttpClientRequest.get,\n POST: HttpClientRequest.post,\n PATCH: HttpClientRequest.patch,\n PUT: HttpClientRequest.put,\n DELETE: HttpClientRequest.delete,\n}\n\nconst request = (cfg: Config, method: Method, path: string) =>\n constructors[method](endpoint(cfg, path)).pipe(\n HttpClientRequest.setHeaders({\n Authorization: `Bot ${Redacted.value(cfg.token)}`,\n \"User-Agent\": userAgent,\n }),\n )\n\nconst transportFailure = (route: string) => (raw: unknown) =>\n new ApiFailure({ route, message: \"transport failure\", raw })\n\nconst rejected = (route: string, status: number, raw: unknown): Effect.Effect<never, ApiError> =>\n Effect.gen(function* () {\n const body = yield* Schema.decodeUnknownEffect(ErrorBody)(raw).pipe(\n Effect.orElseSucceed((): typeof ErrorBody.Type => ({})),\n )\n // Discord's `retry_after` is float seconds; only a 429 carries one.\n return yield* status === 429 && body.retry_after !== undefined\n ? new MessengerError.MessengerRateLimited({\n provider,\n retryAfter: Duration.seconds(body.retry_after),\n raw,\n })\n : new ApiFailure({\n route,\n status,\n ...(body.code !== undefined && { code: body.code }),\n message: body.message ?? `HTTP ${status}`,\n raw,\n })\n })\n\nconst send = (\n route: string,\n httpRequest: HttpClientRequest.HttpClientRequest,\n): Effect.Effect<unknown, ApiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const response = yield* client\n .execute(httpRequest)\n .pipe(Effect.mapError(transportFailure(route)))\n // 204 is the success shape of `typing`, `react` and every delete: no body\n // to read, and reading one would fail.\n if (response.status === 204) return undefined\n const body = yield* response.json.pipe(Effect.mapError(transportFailure(route)))\n return response.status < 400 ? body : yield* rejected(route, response.status, body)\n })\n\n/** One JSON call against the API base. The response body comes back undecoded. */\nexport const call =\n (cfg: Config) =>\n (\n method: Method,\n path: string,\n body?: unknown,\n ): Effect.Effect<unknown, ApiError, HttpClient.HttpClient> =>\n send(\n `${method} ${path}`,\n body === undefined\n ? request(cfg, method, path)\n : request(cfg, method, path).pipe(HttpClientRequest.bodyJsonUnsafe(body)),\n )\n\n// The `attachments` entry is what binds `files[0]` to the message; without it\n// Discord can drop the part and reject the rest as an empty message.\nconst formData = (payload: Fields, file: Upload): FormData => {\n const form = new FormData()\n form.append(\n \"payload_json\",\n JSON.stringify({ ...payload, attachments: [{ id: 0, filename: file.filename }] }),\n )\n form.append(\n \"files[0]\",\n new File([file.bytes as Uint8Array<ArrayBuffer>], file.filename, { type: file.mimeType }),\n )\n return form\n}\n\n/** One multipart message: the JSON fields as `payload_json` plus a single file. */\nexport const upload =\n (cfg: Config) =>\n (\n path: string,\n payload: Fields,\n file: Upload,\n ): Effect.Effect<unknown, ApiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const route = `POST ${path}`\n const body = yield* Multipart.bodyMultipart(formData(payload, file)).pipe(\n Effect.mapError(transportFailure(route)),\n )\n return yield* send(route, request(cfg, \"POST\", path).pipe(body))\n })\n\n/** Decode a response body; a shape mismatch is a malformed response, not a defect. */\nexport const decoded =\n <A>(route: string, schema: Schema.Decoder<A>) =>\n <E, R>(result: Effect.Effect<unknown, E, R>): Effect.Effect<A, E | ApiFailure, R> =>\n Effect.gen(function* () {\n const raw = yield* result\n return yield* Schema.decodeUnknownEffect(schema)(raw).pipe(\n Effect.mapError(() => new ApiFailure({ route, message: \"malformed response\", raw })),\n )\n })\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { Effect, Redacted, Schema } from "effect";
2
- import { HttpClient } from "effect/unstable/http";
2
+ import { HttpClient } from "effect/http";
3
3
  import * as MessengerError from "@effect-uai/core/MessengerError";
4
4
  //#region src/internal/rest.d.ts
5
5
  declare const provider = "discord";
@@ -41,4 +41,4 @@ declare const upload: (cfg: Config) => (path: string, payload: Fields, file: Upl
41
41
  declare const decoded: <A>(route: string, schema: Schema.Decoder<A>) => <E, R>(result: Effect.Effect<unknown, E, R>) => Effect.Effect<A, E | ApiFailure, R>;
42
42
  //#endregion
43
43
  export { Method as a, decoded as c, Fields as i, provider as l, ApiFailure as n, Upload as o, Config as r, call as s, ApiError as t, upload as u };
44
- //# sourceMappingURL=rest-CZSW-VJK.d.mts.map
44
+ //# sourceMappingURL=rest-SmIBt9Nh.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rest-CZSW-VJK.d.mts","names":[],"sources":["../src/internal/rest.ts"],"mappings":";;;;cAKa;KAKD;WACD,OAAO,SAAS;;WAEhB;;KAGC;;KAGA,SAAS,SAAS;;;;;;;;;cAOjB,mBAAmB;WACrB;WACA;WACA;WACA;WACA;;KAGC,WAAW,aAAa,eAAe;;KASvC;WACD,OAAO;WACP;WACA;;;cA+DE,OAAI,KACT,YAAM,QAEF,QAAM,cACF,mBAEX,OAAO,gBAAgB,UAAU,WAAW;;cAwBpC,SAAM,KACX,YAAM,cAEE,SACH,QAAM,MACT,WACL,OAAO,gBAAgB,UAAU,WAAW;;cAUpC,UACV,GAAC,eAAe,QAAU,OAAO,QAAQ,QACzC,GAAG,GAAC,QAAU,OAAO,gBAAgB,GAAG,OAAK,OAAO,OAAO,GAAG,IAAI,YAAY"}
1
+ {"version":3,"file":"rest-SmIBt9Nh.d.mts","names":[],"sources":["../src/internal/rest.ts"],"mappings":";;;;cAKa;KAKD;WACD,OAAO,SAAS;;WAEhB;;KAGC;;KAGA,SAAS,SAAS;;;;;;;;;cAOjB,mBAAmB;WACrB;WACA;WACA;WACA;WACA;;KAGC,WAAW,aAAa,eAAe;;KASvC;WACD,OAAO;WACP;WACA;;;cA+DE,OAAI,KACT,YAAM,QAEF,QAAM,cACF,mBAEX,OAAO,gBAAgB,UAAU,WAAW;;cAwBpC,SAAM,KACX,YAAM,cAEE,SACH,QAAM,MACT,WACL,OAAO,gBAAgB,UAAU,WAAW;;cAUpC,UACV,GAAC,eAAe,QAAU,OAAO,QAAQ,QACzC,GAAG,GAAC,QAAU,OAAO,gBAAgB,GAAG,OAAK,OAAO,OAAO,GAAG,IAAI,YAAY"}