@effect-uai/slack 0.16.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.
@@ -1,2 +1,2 @@
1
- import { a as Upload, c as decoded, d as upload, i as Params, l as provider, n as ApiFailure, o as call, r as Config, s as callAsApp, t as ApiError, u as query } from "../api-BBgliKo7.mjs";
1
+ import { a as Upload, c as decoded, d as upload, i as Params, l as provider, n as ApiFailure, o as call, r as Config, s as callAsApp, t as ApiError, u as query } from "../api-CbBW1iF0.mjs";
2
2
  export { ApiError, ApiFailure, Config, Params, Upload, call, callAsApp, decoded, provider, query, upload };
@@ -1,5 +1,5 @@
1
1
  import { Data, Duration, Effect, Option, Redacted, Schema } from "effect";
2
- import { Headers, HttpClient, HttpClientRequest } from "effect/unstable/http";
2
+ import { Headers, HttpClient, HttpClientRequest } from "effect/http";
3
3
  import * as MessengerError from "@effect-uai/core/MessengerError";
4
4
  //#region src/internal/api.ts
5
5
  const provider = "slack";
@@ -1 +1 @@
1
- {"version":3,"file":"api.mjs","names":[],"sources":["../../src/internal/api.ts"],"sourcesContent":["import { Data, Duration, Effect, Option, Redacted, Schema } from \"effect\"\nimport {\n Headers,\n HttpClient,\n HttpClientRequest,\n type HttpClientResponse,\n} from \"effect/unstable/http\"\nimport * as MessengerError from \"@effect-uai/core/MessengerError\"\n\nexport const provider = \"slack\"\n\nexport type Config = {\n /** `xoxb-`, for every Web API call. */\n readonly botToken: Redacted.Redacted\n /** `xapp-`, for `apps.connections.open` and nothing else. */\n readonly appToken: Redacted.Redacted\n /** Defaults to `https://slack.com/api`. */\n readonly baseUrl?: string\n}\n\n/**\n * A Web API call that came back `ok: false` or never came back at all.\n * `error` is Slack's own error string (`channel_not_found`, `invalid_auth`);\n * each verb maps this onto its own `MessengerError`.\n */\nexport class ApiFailure extends Data.TaggedError(\"SlackApiFailure\")<{\n readonly method: string\n readonly status?: number\n readonly error: string\n readonly raw: unknown\n}> {}\n\nexport type ApiError = ApiFailure | MessengerError.MessengerRateLimited\n\n// Slack answers every method with `ok`, then the result fields beside it.\nconst Envelope = Schema.Struct({\n ok: Schema.Boolean,\n error: Schema.optional(Schema.String),\n})\n\nexport type Params = Readonly<Record<string, unknown>>\n\n/** The bytes behind a `files.getUploadURLExternal` URL. */\nexport type Upload = {\n readonly bytes: Uint8Array\n readonly filename: string\n readonly mimeType: string\n}\n\nconst endpoint = (cfg: Config, method: string): string =>\n `${cfg.baseUrl ?? \"https://slack.com/api\"}/${method}`\n\nconst authorized = (\n request: HttpClientRequest.HttpClientRequest,\n token: Redacted.Redacted,\n): HttpClientRequest.HttpClientRequest =>\n HttpClientRequest.setHeader(request, \"Authorization\", `Bearer ${Redacted.value(token)}`)\n\nconst transportFailure = (method: string) => (raw: unknown) =>\n new ApiFailure({ method, error: \"transport failure\", raw })\n\n// A 429 carries the wait in a header rather than the body, and Slack's own\n// floor for a missing one is a minute.\nconst retryAfter = (headers: Headers.Headers): Duration.Duration =>\n Headers.get(headers, \"retry-after\").pipe(\n Option.map(Number),\n Option.filter(Number.isFinite),\n Option.getOrElse(() => 60),\n Duration.seconds,\n )\n\nconst send = (\n method: string,\n request: HttpClientRequest.HttpClientRequest,\n): Effect.Effect<unknown, ApiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const response = yield* client.execute(request).pipe(Effect.mapError(transportFailure(method)))\n if (response.status === 429) {\n return yield* new MessengerError.MessengerRateLimited({\n provider,\n retryAfter: retryAfter(response.headers),\n })\n }\n const body = yield* response.json.pipe(Effect.mapError(transportFailure(method)))\n const envelope = yield* Schema.decodeUnknownEffect(Envelope)(body).pipe(\n Effect.mapError(() => new ApiFailure({ method, error: \"malformed envelope\", raw: body })),\n )\n // The result fields sit beside `ok`, so the whole body is the result.\n return envelope.ok\n ? body\n : yield* new ApiFailure({\n method,\n status: response.status,\n error: envelope.error ?? \"unknown error\",\n raw: body,\n })\n })\n\nconst post = (cfg: Config, method: string, params: Params | undefined) => {\n const request = HttpClientRequest.post(endpoint(cfg, method))\n // A method with no arguments takes no body at all; Slack rejects an empty one.\n return params === undefined ? request : HttpClientRequest.bodyJsonUnsafe(request, params)\n}\n\n/** One JSON method call with the bot token. The body comes back undecoded. */\nexport const call =\n (cfg: Config) =>\n (method: string, params?: Params): Effect.Effect<unknown, ApiError, HttpClient.HttpClient> =>\n send(method, authorized(post(cfg, method, params), cfg.botToken))\n\n/** `apps.connections.open`, the only call the app token is for. */\nexport const callAsApp =\n (cfg: Config) =>\n (method: string): Effect.Effect<unknown, ApiError, HttpClient.HttpClient> =>\n send(method, authorized(post(cfg, method, undefined), cfg.appToken))\n\n/** One method called with query parameters, for the few that take no JSON body. */\nexport const query =\n (cfg: Config) =>\n (method: string, params: Params): Effect.Effect<unknown, ApiError, HttpClient.HttpClient> =>\n send(\n method,\n authorized(\n HttpClientRequest.get(endpoint(cfg, method)).pipe(\n HttpClientRequest.setUrlParams(\n Object.entries(params).map(([name, value]) => [name, String(value)] as const),\n ),\n ),\n cfg.botToken,\n ),\n )\n\nconst uploadRejected = (response: HttpClientResponse.HttpClientResponse) =>\n new ApiFailure({\n method: \"files.upload\",\n status: response.status,\n error: `upload rejected with HTTP ${response.status}`,\n raw: response.status,\n })\n\n/**\n * Raw bytes to the URL `files.getUploadURLExternal` handed out. Not a Web API\n * call: no token, no `ok` envelope, and the response body is plain text.\n */\nexport const upload = (\n url: string,\n file: Upload,\n): Effect.Effect<void, ApiFailure, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const response = yield* client\n .execute(\n HttpClientRequest.post(url).pipe(\n HttpClientRequest.bodyUint8Array(file.bytes as Uint8Array<ArrayBuffer>, file.mimeType),\n ),\n )\n .pipe(Effect.mapError(transportFailure(\"files.upload\")))\n if (response.status >= 400) return yield* uploadRejected(response)\n })\n\n/** Decode a response body; a shape mismatch is a malformed response, not a defect. */\nexport const decoded =\n <A>(method: 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({ method, error: \"malformed response\", raw })),\n )\n })\n"],"mappings":"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"}
1
+ {"version":3,"file":"api.mjs","names":[],"sources":["../../src/internal/api.ts"],"sourcesContent":["import { Data, Duration, Effect, Option, Redacted, Schema } from \"effect\"\nimport { Headers, HttpClient, HttpClientRequest, type HttpClientResponse } from \"effect/http\"\nimport * as MessengerError from \"@effect-uai/core/MessengerError\"\n\nexport const provider = \"slack\"\n\nexport type Config = {\n /** `xoxb-`, for every Web API call. */\n readonly botToken: Redacted.Redacted\n /** `xapp-`, for `apps.connections.open` and nothing else. */\n readonly appToken: Redacted.Redacted\n /** Defaults to `https://slack.com/api`. */\n readonly baseUrl?: string\n}\n\n/**\n * A Web API call that came back `ok: false` or never came back at all.\n * `error` is Slack's own error string (`channel_not_found`, `invalid_auth`);\n * each verb maps this onto its own `MessengerError`.\n */\nexport class ApiFailure extends Data.TaggedError(\"SlackApiFailure\")<{\n readonly method: string\n readonly status?: number\n readonly error: string\n readonly raw: unknown\n}> {}\n\nexport type ApiError = ApiFailure | MessengerError.MessengerRateLimited\n\n// Slack answers every method with `ok`, then the result fields beside it.\nconst Envelope = Schema.Struct({\n ok: Schema.Boolean,\n error: Schema.optional(Schema.String),\n})\n\nexport type Params = Readonly<Record<string, unknown>>\n\n/** The bytes behind a `files.getUploadURLExternal` URL. */\nexport type Upload = {\n readonly bytes: Uint8Array\n readonly filename: string\n readonly mimeType: string\n}\n\nconst endpoint = (cfg: Config, method: string): string =>\n `${cfg.baseUrl ?? \"https://slack.com/api\"}/${method}`\n\nconst authorized = (\n request: HttpClientRequest.HttpClientRequest,\n token: Redacted.Redacted,\n): HttpClientRequest.HttpClientRequest =>\n HttpClientRequest.setHeader(request, \"Authorization\", `Bearer ${Redacted.value(token)}`)\n\nconst transportFailure = (method: string) => (raw: unknown) =>\n new ApiFailure({ method, error: \"transport failure\", raw })\n\n// A 429 carries the wait in a header rather than the body, and Slack's own\n// floor for a missing one is a minute.\nconst retryAfter = (headers: Headers.Headers): Duration.Duration =>\n Headers.get(headers, \"retry-after\").pipe(\n Option.map(Number),\n Option.filter(Number.isFinite),\n Option.getOrElse(() => 60),\n Duration.seconds,\n )\n\nconst send = (\n method: string,\n request: HttpClientRequest.HttpClientRequest,\n): Effect.Effect<unknown, ApiError, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const response = yield* client.execute(request).pipe(Effect.mapError(transportFailure(method)))\n if (response.status === 429) {\n return yield* new MessengerError.MessengerRateLimited({\n provider,\n retryAfter: retryAfter(response.headers),\n })\n }\n const body = yield* response.json.pipe(Effect.mapError(transportFailure(method)))\n const envelope = yield* Schema.decodeUnknownEffect(Envelope)(body).pipe(\n Effect.mapError(() => new ApiFailure({ method, error: \"malformed envelope\", raw: body })),\n )\n // The result fields sit beside `ok`, so the whole body is the result.\n return envelope.ok\n ? body\n : yield* new ApiFailure({\n method,\n status: response.status,\n error: envelope.error ?? \"unknown error\",\n raw: body,\n })\n })\n\nconst post = (cfg: Config, method: string, params: Params | undefined) => {\n const request = HttpClientRequest.post(endpoint(cfg, method))\n // A method with no arguments takes no body at all; Slack rejects an empty one.\n return params === undefined ? request : HttpClientRequest.bodyJsonUnsafe(request, params)\n}\n\n/** One JSON method call with the bot token. The body comes back undecoded. */\nexport const call =\n (cfg: Config) =>\n (method: string, params?: Params): Effect.Effect<unknown, ApiError, HttpClient.HttpClient> =>\n send(method, authorized(post(cfg, method, params), cfg.botToken))\n\n/** `apps.connections.open`, the only call the app token is for. */\nexport const callAsApp =\n (cfg: Config) =>\n (method: string): Effect.Effect<unknown, ApiError, HttpClient.HttpClient> =>\n send(method, authorized(post(cfg, method, undefined), cfg.appToken))\n\n/** One method called with query parameters, for the few that take no JSON body. */\nexport const query =\n (cfg: Config) =>\n (method: string, params: Params): Effect.Effect<unknown, ApiError, HttpClient.HttpClient> =>\n send(\n method,\n authorized(\n HttpClientRequest.get(endpoint(cfg, method)).pipe(\n HttpClientRequest.setUrlParams(\n Object.entries(params).map(([name, value]) => [name, String(value)] as const),\n ),\n ),\n cfg.botToken,\n ),\n )\n\nconst uploadRejected = (response: HttpClientResponse.HttpClientResponse) =>\n new ApiFailure({\n method: \"files.upload\",\n status: response.status,\n error: `upload rejected with HTTP ${response.status}`,\n raw: response.status,\n })\n\n/**\n * Raw bytes to the URL `files.getUploadURLExternal` handed out. Not a Web API\n * call: no token, no `ok` envelope, and the response body is plain text.\n */\nexport const upload = (\n url: string,\n file: Upload,\n): Effect.Effect<void, ApiFailure, HttpClient.HttpClient> =>\n Effect.gen(function* () {\n const client = yield* HttpClient.HttpClient\n const response = yield* client\n .execute(\n HttpClientRequest.post(url).pipe(\n HttpClientRequest.bodyUint8Array(file.bytes as Uint8Array<ArrayBuffer>, file.mimeType),\n ),\n )\n .pipe(Effect.mapError(transportFailure(\"files.upload\")))\n if (response.status >= 400) return yield* uploadRejected(response)\n })\n\n/** Decode a response body; a shape mismatch is a malformed response, not a defect. */\nexport const decoded =\n <A>(method: 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({ method, error: \"malformed response\", raw })),\n )\n })\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { Envelope, 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 bot = {
@@ -1,2 +1,2 @@
1
- import { a as Recent, c as connect, i as RECENT, l as remember, n as DisconnectAction, o as Session, r as Incoming, s as classifyDisconnect, t as Config } from "../socket-DJgdt_ka.mjs";
1
+ import { a as Recent, c as connect, i as RECENT, l as remember, n as DisconnectAction, o as Session, r as Incoming, s as classifyDisconnect, t as Config } from "../socket-qV7yc2q7.mjs";
2
2
  export { Config, DisconnectAction, Incoming, RECENT, Recent, Session, classifyDisconnect, connect, remember };
@@ -1,8 +1,9 @@
1
1
  import { provider } from "./api.mjs";
2
2
  import { Frame, eventId } from "./events.mjs";
3
- import { Array, Deferred, Duration, Effect, Match, Option, Queue, Ref, Schema } from "effect";
3
+ import { Array, Deferred, Duration, Effect, Match, Option, Queue, Ref, 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/socket.ts
7
8
  /**
8
9
  * Slack names the one reason that must not be retried; the rolling refresh is
@@ -34,10 +35,12 @@ const connect = (cfg) => Effect.gen(function* () {
34
35
  const seen = yield* Ref.make([]);
35
36
  const once = Effect.gen(function* () {
36
37
  const url = yield* cfg.open;
37
- const socket = yield* Socket.makeWebSocket(url, { closeCodeIsError: (code) => code !== 1e3 && code !== 1001 && code !== 1005 }).pipe(Effect.provide(Socket.layerWebSocketConstructorGlobal));
38
- const write = yield* socket.writer;
38
+ const socket = yield* Socket.makeWebSocket(url);
39
+ const { write } = yield* socket.writer;
40
+ const closed = yield* Ref.make(false);
41
+ const send = (chunk) => Effect.flatMap(Ref.get(closed), (isClosed) => isClosed ? Effect.void : write(chunk));
39
42
  const requested = yield* Ref.make(Option.none());
40
- const acknowledge = (envelopeId) => write(JSON.stringify({ envelope_id: envelopeId }));
43
+ const acknowledge = (envelopeId) => send(JSON.stringify({ envelope_id: envelopeId }));
41
44
  const offer = (envelope, raw) => Effect.gen(function* () {
42
45
  yield* acknowledge(envelope.envelope_id);
43
46
  const id = eventId(envelope);
@@ -57,7 +60,7 @@ const connect = (cfg) => Effect.gen(function* () {
57
60
  action,
58
61
  reason: reason ?? "disconnect"
59
62
  }));
60
- yield* write(new Socket.CloseEvent(1e3, reason ?? "disconnect"));
63
+ yield* send(new Socket.CloseEvent(1e3, reason ?? "disconnect"));
61
64
  });
62
65
  const handle = (text) => Effect.gen(function* () {
63
66
  const raw = yield* parseFrame(text).pipe(Effect.option);
@@ -66,7 +69,7 @@ const connect = (cfg) => Effect.gen(function* () {
66
69
  if (Option.isNone(frame)) return;
67
70
  yield* Match.value(frame.value).pipe(Match.when({ type: "hello" }, () => Ref.set(attempts, 0).pipe(Effect.andThen(Deferred.succeed(ready, void 0)))), Match.when({ type: "disconnect" }, ({ reason }) => disconnect(reason)), Match.orElse((envelope) => offer(envelope, raw.value)));
68
71
  }).pipe(Effect.ignore);
69
- const ended = yield* socket.runString(handle).pipe(Effect.as({
72
+ const ended = yield* Stream.fromPull(Socket.readerString(socket)).pipe(Stream.scoped, Stream.catchIf(isCleanClose, () => Stream.empty), Stream.runForEach(handle), Effect.ensuring(Ref.set(closed, true)), Effect.as({
70
73
  action: "reconnect",
71
74
  reason: "closed"
72
75
  }), Effect.catch((error) => Effect.succeed({
@@ -1 +1 @@
1
- {"version":3,"file":"socket.mjs","names":["Arr","Events.Frame","Events.eventId"],"sources":["../../src/internal/socket.ts"],"sourcesContent":["import {\n Array as Arr,\n type Cause,\n Deferred,\n Duration,\n Effect,\n Match,\n Option,\n Queue,\n Ref,\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 \"./api.js\"\n\n// ---------------------------------------------------------------------------\n// Disconnects\n// ---------------------------------------------------------------------------\n\n/** What the session does once a connection is gone. */\nexport type DisconnectAction = \"refresh\" | \"reconnect\" | \"fatal\"\n\n/**\n * Slack names the one reason that must not be retried; the rolling refresh is\n * scheduled rather than a failure, so it reconnects without backing off, and\n * an unknown reason is an ordinary reconnect.\n */\nexport const classifyDisconnect = (reason: string | undefined): DisconnectAction =>\n Match.value(reason).pipe(\n Match.when(\"link_disabled\", (): DisconnectAction => \"fatal\"),\n Match.when(\"refresh_requested\", (): DisconnectAction => \"refresh\"),\n Match.when(\"warning\", (): DisconnectAction => \"refresh\"),\n Match.orElse((): DisconnectAction => \"reconnect\"),\n )\n\n// ---------------------------------------------------------------------------\n// Redelivery\n// ---------------------------------------------------------------------------\n\n/**\n * Transport state, not conversation state: Slack redelivers an envelope whose\n * acknowledgement it missed, and this exists only to drop the second copy.\n */\nexport type Recent = ReadonlyArray<string>\n\n/** How many ids to hold. Redeliveries arrive at once, +1 minute and +5 minutes. */\nexport const RECENT = 256\n\n/** `None` when `id` has been seen; otherwise the set with `id` in it, oldest dropped. */\nexport const remember = (seen: Recent, id: string, cap: number = RECENT): Option.Option<Recent> =>\n seen.includes(id) ? Option.none() : Option.some(Arr.takeRight([...seen, id], cap))\n\n// ---------------------------------------------------------------------------\n// Session\n// ---------------------------------------------------------------------------\n\nexport type Config = {\n /**\n * A fresh single-use Socket Mode URL, called for every connection. Failing\n * before `hello` has ever arrived is what makes a bad app token a connect\n * failure rather than a silent retry.\n */\n readonly open: Effect.Effect<string, MessengerError.MessengerError>\n}\n\n/** A decoded envelope beside the frame it came from, which `raw` carries. */\nexport type Incoming = {\n readonly envelope: Events.Envelope\n readonly raw: unknown\n}\n\nexport type Session = {\n /**\n * Envelopes in arrival order, already acknowledged, ended when the scope\n * closes and failed with `MessengerTransportClosed` on a disconnect Slack\n * says not to retry.\n */\n readonly envelopes: Queue.Queue<Incoming, MessengerError.MessengerError | Cause.Done>\n}\n\ntype Ended = { readonly action: DisconnectAction; readonly reason: string }\n\nconst parseFrame = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))\nconst decodeFrame = Schema.decodeUnknownEffect(Events.Frame)\n\nconst socketReason = (error: Socket.SocketError): string =>\n error.reason._tag === \"SocketCloseError\"\n ? (error.reason.closeReason ?? error.reason.message)\n : error.reason.message\n\nconst reasonOf = (e: MessengerError.MessengerError | Socket.SocketError): string =>\n e._tag === \"SocketError\" ? socketReason(e) : MessengerError.describe(e)\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\n/**\n * One Socket Mode session: open, acknowledge, dedupe, reconnect.\n *\n * Returns once `hello` has arrived, so a rejected app token is a\n * `MessengerConnectFailed` at layer build rather than a stream that dies a\n * moment later. From then on reconnects are silent and only a `link_disabled`\n * disconnect ends `envelopes`.\n */\nexport const connect = (\n cfg: Config,\n): Effect.Effect<Session, MessengerError.MessengerConnectFailed, Scope.Scope> =>\n Effect.gen(function* () {\n const envelopes = yield* Queue.unbounded<Incoming, MessengerError.MessengerError | Cause.Done>()\n const ready = yield* Deferred.make<void, MessengerError.MessengerConnectFailed>()\n const attempts = yield* Ref.make(0)\n const seen = yield* Ref.make<Recent>([])\n\n // -- one connection ----------------------------------------------------\n\n const once = Effect.gen(function* () {\n const url = yield* cfg.open\n const socket = yield* Socket.makeWebSocket(url, {\n // Effect treats every close as an error by default; the standard clean\n // codes are not, and Slack's close codes say nothing a reconnect\n // cannot fix, so a `disconnect` frame is the only fatal signal.\n closeCodeIsError: (code: number) => code !== 1000 && code !== 1001 && code !== 1005,\n // The ticket is in the URL, so every runtime's global `WebSocket` is enough.\n }).pipe(Effect.provide(Socket.layerWebSocketConstructorGlobal))\n const write = yield* socket.writer\n // Set by a `disconnect` frame, the one end a close code cannot express.\n const requested = yield* Ref.make(Option.none<Ended>())\n\n // Acknowledged before the envelope is offered, well inside Slack's three\n // second deadline, so the platform is never waiting on the recipe.\n const acknowledge = (envelopeId: string) => write(JSON.stringify({ envelope_id: envelopeId }))\n\n // A redelivery is acknowledged like any other envelope and then dropped,\n // so Slack stops retrying it and the recipe never sees it twice.\n const offer = (envelope: Events.Envelope, raw: unknown) =>\n Effect.gen(function* () {\n yield* acknowledge(envelope.envelope_id)\n const id = Events.eventId(envelope)\n if (Option.isSome(id)) {\n const next = remember(yield* Ref.get(seen), id.value)\n if (Option.isNone(next)) return\n yield* Ref.set(seen, next.value)\n }\n yield* Queue.offer(envelopes, { envelope, raw })\n })\n\n const disconnect = (reason: string | undefined) =>\n Effect.gen(function* () {\n const action = classifyDisconnect(reason)\n yield* Ref.set(requested, Option.some<Ended>({ action, reason: reason ?? \"disconnect\" }))\n yield* write(new Socket.CloseEvent(1000, reason ?? \"disconnect\"))\n })\n\n const handle = (text: string) =>\n Effect.gen(function* () {\n const raw = yield* parseFrame(text).pipe(Effect.option)\n if (Option.isNone(raw)) return\n const frame = yield* decodeFrame(raw.value).pipe(Effect.option)\n if (Option.isNone(frame)) return\n yield* Match.value(frame.value).pipe(\n Match.when({ type: \"hello\" }, () =>\n Ref.set(attempts, 0).pipe(Effect.andThen(Deferred.succeed(ready, undefined))),\n ),\n Match.when({ type: \"disconnect\" }, ({ reason }) => disconnect(reason)),\n Match.orElse((envelope) => offer(envelope, raw.value)),\n )\n }).pipe(Effect.ignore)\n\n // A clean 1000 is only ever our own teardown or a disconnect we asked\n // for; anything else is a drop, and every drop reconnects.\n const ended = yield* socket.runString(handle).pipe(\n Effect.as<Ended>({ action: \"reconnect\", reason: \"closed\" }),\n Effect.catch((error: Socket.SocketError) =>\n Effect.succeed<Ended>({ action: \"reconnect\", reason: reasonOf(error) }),\n ),\n )\n return Option.getOrElse(yield* Ref.get(requested), () => ended)\n }).pipe(\n Effect.scoped,\n // A URL we cannot get, or a socket that will not open, is fatal only\n // before `hello`: a rejected app token fails the layer, while the same\n // failure later is one more reconnect.\n Effect.catch((e: MessengerError.MessengerError | Socket.SocketError) =>\n Effect.map(Deferred.isDone(ready), (live): Ended => ({\n action: live ? \"reconnect\" : \"fatal\",\n reason: reasonOf(e),\n })),\n ),\n )\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 })\n }\n const attempt = yield* Ref.get(attempts)\n yield* Ref.set(attempts, attempt + 1)\n // A refresh is Slack cycling the connection on schedule, so the next one\n // opens at once; anything else backs off.\n const wait = ended.action === \"refresh\" ? Duration.zero : backoff(attempt)\n yield* Effect.logDebug(\"socket mode reconnecting\", {\n reason: ended.reason,\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 `hello` has landed, so the same end\n // 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(envelopes, closed))),\n ),\n Effect.ensuring(Queue.end(envelopes)),\n Effect.forkScoped,\n )\n\n yield* Deferred.await(ready)\n return { envelopes }\n })\n"],"mappings":"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"}
1
+ {"version":3,"file":"socket.mjs","names":["Arr","Events.Frame","Events.eventId"],"sources":["../../src/internal/socket.ts"],"sourcesContent":["import {\n Array as Arr,\n type Cause,\n Deferred,\n Duration,\n Effect,\n Match,\n Option,\n Queue,\n Ref,\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 \"./api.js\"\n\n// ---------------------------------------------------------------------------\n// Disconnects\n// ---------------------------------------------------------------------------\n\n/** What the session does once a connection is gone. */\nexport type DisconnectAction = \"refresh\" | \"reconnect\" | \"fatal\"\n\n/**\n * Slack names the one reason that must not be retried; the rolling refresh is\n * scheduled rather than a failure, so it reconnects without backing off, and\n * an unknown reason is an ordinary reconnect.\n */\nexport const classifyDisconnect = (reason: string | undefined): DisconnectAction =>\n Match.value(reason).pipe(\n Match.when(\"link_disabled\", (): DisconnectAction => \"fatal\"),\n Match.when(\"refresh_requested\", (): DisconnectAction => \"refresh\"),\n Match.when(\"warning\", (): DisconnectAction => \"refresh\"),\n Match.orElse((): DisconnectAction => \"reconnect\"),\n )\n\n// ---------------------------------------------------------------------------\n// Redelivery\n// ---------------------------------------------------------------------------\n\n/**\n * Transport state, not conversation state: Slack redelivers an envelope whose\n * acknowledgement it missed, and this exists only to drop the second copy.\n */\nexport type Recent = ReadonlyArray<string>\n\n/** How many ids to hold. Redeliveries arrive at once, +1 minute and +5 minutes. */\nexport const RECENT = 256\n\n/** `None` when `id` has been seen; otherwise the set with `id` in it, oldest dropped. */\nexport const remember = (seen: Recent, id: string, cap: number = RECENT): Option.Option<Recent> =>\n seen.includes(id) ? Option.none() : Option.some(Arr.takeRight([...seen, id], cap))\n\n// ---------------------------------------------------------------------------\n// Session\n// ---------------------------------------------------------------------------\n\nexport type Config = {\n /**\n * A fresh single-use Socket Mode URL, called for every connection. Failing\n * before `hello` has ever arrived is what makes a bad app token a connect\n * failure rather than a silent retry.\n */\n readonly open: Effect.Effect<string, MessengerError.MessengerError>\n}\n\n/** A decoded envelope beside the frame it came from, which `raw` carries. */\nexport type Incoming = {\n readonly envelope: Events.Envelope\n readonly raw: unknown\n}\n\nexport type Session = {\n /**\n * Envelopes in arrival order, already acknowledged, ended when the scope\n * closes and failed with `MessengerTransportClosed` on a disconnect Slack\n * says not to retry.\n */\n readonly envelopes: Queue.Queue<Incoming, MessengerError.MessengerError | Cause.Done>\n}\n\ntype Ended = { readonly action: DisconnectAction; readonly reason: string }\n\nconst parseFrame = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Unknown))\nconst decodeFrame = Schema.decodeUnknownEffect(Events.Frame)\n\nconst socketReason = (error: Socket.SocketError): string =>\n error.reason._tag === \"SocketCloseError\"\n ? (error.reason.closeReason ?? error.reason.message)\n : error.reason.message\n\nconst reasonOf = (e: MessengerError.MessengerError | Socket.SocketError): string =>\n e._tag === \"SocketError\" ? socketReason(e) : MessengerError.describe(e)\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\n/**\n * One Socket Mode session: open, acknowledge, dedupe, reconnect.\n *\n * Returns once `hello` has arrived, so a rejected app token is a\n * `MessengerConnectFailed` at layer build rather than a stream that dies a\n * moment later. From then on reconnects are silent and only a `link_disabled`\n * disconnect ends `envelopes`.\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 envelopes = yield* Queue.unbounded<Incoming, MessengerError.MessengerError | Cause.Done>()\n const ready = yield* Deferred.make<void, MessengerError.MessengerConnectFailed>()\n const attempts = yield* Ref.make(0)\n const seen = yield* Ref.make<Recent>([])\n\n // -- one connection ----------------------------------------------------\n\n const once = Effect.gen(function* () {\n const url = yield* cfg.open\n const socket = yield* Socket.makeWebSocket(url)\n const { write } = yield* socket.writer\n // The writer waits for a reconnect that never comes on this URL, so\n // nothing is written once this connection's reader has ended.\n const closed = yield* Ref.make(false)\n const send = (chunk: string | Socket.CloseEvent) =>\n Effect.flatMap(Ref.get(closed), (isClosed) => (isClosed ? Effect.void : write(chunk)))\n // Set by a `disconnect` frame, the one end a close code cannot express.\n const requested = yield* Ref.make(Option.none<Ended>())\n\n // Acknowledged before the envelope is offered, well inside Slack's three\n // second deadline, so the platform is never waiting on the recipe.\n const acknowledge = (envelopeId: string) => send(JSON.stringify({ envelope_id: envelopeId }))\n\n // A redelivery is acknowledged like any other envelope and then dropped,\n // so Slack stops retrying it and the recipe never sees it twice.\n const offer = (envelope: Events.Envelope, raw: unknown) =>\n Effect.gen(function* () {\n yield* acknowledge(envelope.envelope_id)\n const id = Events.eventId(envelope)\n if (Option.isSome(id)) {\n const next = remember(yield* Ref.get(seen), id.value)\n if (Option.isNone(next)) return\n yield* Ref.set(seen, next.value)\n }\n yield* Queue.offer(envelopes, { envelope, raw })\n })\n\n const disconnect = (reason: string | undefined) =>\n Effect.gen(function* () {\n const action = classifyDisconnect(reason)\n yield* Ref.set(requested, Option.some<Ended>({ action, reason: reason ?? \"disconnect\" }))\n yield* send(new Socket.CloseEvent(1000, reason ?? \"disconnect\"))\n })\n\n const handle = (text: string) =>\n Effect.gen(function* () {\n const raw = yield* parseFrame(text).pipe(Effect.option)\n if (Option.isNone(raw)) return\n const frame = yield* decodeFrame(raw.value).pipe(Effect.option)\n if (Option.isNone(frame)) return\n yield* Match.value(frame.value).pipe(\n Match.when({ type: \"hello\" }, () =>\n Ref.set(attempts, 0).pipe(Effect.andThen(Deferred.succeed(ready, undefined))),\n ),\n Match.when({ type: \"disconnect\" }, ({ reason }) => disconnect(reason)),\n Match.orElse((envelope) => offer(envelope, raw.value)),\n )\n }).pipe(Effect.ignore)\n\n // A clean close is only ever our own teardown or a disconnect we asked\n // for; anything else is a drop, and every drop reconnects.\n const ended = yield* Stream.fromPull(Socket.readerString(socket)).pipe(\n Stream.scoped,\n Stream.catchIf(isCleanClose, () => Stream.empty),\n Stream.runForEach(handle),\n Effect.ensuring(Ref.set(closed, true)),\n Effect.as<Ended>({ action: \"reconnect\", reason: \"closed\" }),\n Effect.catch((error: Socket.SocketError) =>\n Effect.succeed<Ended>({ action: \"reconnect\", reason: reasonOf(error) }),\n ),\n )\n return Option.getOrElse(yield* Ref.get(requested), () => ended)\n }).pipe(\n Effect.scoped,\n // A URL we cannot get, or a socket that will not open, is fatal only\n // before `hello`: a rejected app token fails the layer, while the same\n // failure later is one more reconnect.\n Effect.catch((e: MessengerError.MessengerError | Socket.SocketError) =>\n Effect.map(Deferred.isDone(ready), (live): Ended => ({\n action: live ? \"reconnect\" : \"fatal\",\n reason: reasonOf(e),\n })),\n ),\n )\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 })\n }\n const attempt = yield* Ref.get(attempts)\n yield* Ref.set(attempts, attempt + 1)\n // A refresh is Slack cycling the connection on schedule, so the next one\n // opens at once; anything else backs off.\n const wait = ended.action === \"refresh\" ? Duration.zero : backoff(attempt)\n yield* Effect.logDebug(\"socket mode reconnecting\", {\n reason: ended.reason,\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 `hello` has landed, so the same end\n // 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(envelopes, closed))),\n ),\n Effect.ensuring(Queue.end(envelopes)),\n Effect.forkScoped,\n )\n\n yield* Deferred.await(ready)\n return { envelopes }\n })\n"],"mappings":"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"}
@@ -1,24 +1,270 @@
1
- import { classifyDisconnect, remember } from "./socket.mjs";
2
- import { n as describe, r as it, t as globalExpect } from "../test.DNmyFkvJ-N3QqMRwZ.mjs";
3
- import { Option } from "effect";
1
+ import { classifyDisconnect, connect, remember } from "./socket.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, Exit, Layer, Option, Queue, Ref } from "effect";
4
+ import * as Socket from "effect/socket/Socket";
5
+ import * as MessengerError from "@effect-uai/core/MessengerError";
6
+ import * as Arbitrary from "effect/Arbitrary";
7
+ import * as Cause$1 from "effect/Cause";
8
+ import * as Duration$1 from "effect/Duration";
9
+ import * as Effect$1 from "effect/Effect";
10
+ import * as Exit$1 from "effect/Exit";
11
+ import { constVoid, flow, pipe } from "effect/Function";
12
+ import * as Layer$1 from "effect/Layer";
13
+ import * as Schedule from "effect/Schedule";
14
+ import * as Scope$1 from "effect/Scope";
15
+ import * as TestClock from "effect/testing/TestClock";
16
+ import * as TestConsole from "effect/testing/TestConsole";
17
+ import * as FakeWebSocket from "@effect-uai/core/testing/FakeWebSocket";
18
+ //#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
19
+ /**
20
+ * @since 4.0.0
21
+ */
22
+ const getCurrentSuite = TestRunner.getCurrentSuite;
23
+ const runPromise = /*#__PURE__*/ Effect$1.fnUntraced(function* (effect, _ctx) {
24
+ const exit = yield* Effect$1.exit(effect);
25
+ if (Exit$1.isFailure(exit)) {
26
+ const errors = Cause$1.prettyErrors(exit.cause);
27
+ for (let i = 0; i < errors.length; i++) yield* Effect$1.logError(errors[i]);
28
+ }
29
+ return yield* exit;
30
+ }, (effect, _, ctx) => Effect$1.runPromise(effect, { signal: ctx?.signal }));
31
+ /** @internal */
32
+ const runTest = (ctx) => (effect) => {
33
+ const promise = runPromise(effect, ctx);
34
+ if (ctx) {
35
+ const onAbort = () => ctx.onTestFinished(() => promise.then(constVoid, constVoid));
36
+ ctx.signal.addEventListener("abort", onAbort, { once: true });
37
+ const cleanup = () => ctx.signal.removeEventListener("abort", onAbort);
38
+ promise.then(cleanup, cleanup);
39
+ if (ctx.signal.aborted) onAbort();
40
+ }
41
+ return promise;
42
+ };
43
+ const TestEnv = /*#__PURE__*/ Layer$1.mergeAll(TestConsole.layer, /*#__PURE__*/ TestClock.layer());
44
+ /** @internal */
45
+ const testOptions = (timeout) => typeof timeout === "number" ? { timeout } : timeout ?? {};
46
+ const hookTimeout = (timeout) => timeout === void 0 ? void 0 : Duration$1.toMillis(Duration$1.fromInputUnsafe(timeout));
47
+ const propertyTestOptions = (timeout) => typeof timeout === "number" ? void 0 : timeout;
48
+ const checkOptions = (timeout) => propertyTestOptions(timeout)?.arbitrary;
49
+ const compileArbitraryInput = (input) => Arbitrary.isArbitrary(input) ? input : Arbitrary.schema(input);
50
+ const makeArbitrary = (arbitraries) => Arbitrary.all(Array.isArray(arbitraries) ? arbitraries.map(compileArbitraryInput) : Object.fromEntries(Object.entries(arbitraries).map(([key, input]) => [key, compileArbitraryInput(input)])));
51
+ const normalizeProperty = (property, value) => Effect$1.catchCause(Effect$1.suspend(() => {
52
+ const output = property(value);
53
+ return Effect$1.isEffect(output) ? output : Effect$1.succeed(output);
54
+ }), (cause) => Cause$1.hasInterrupts(cause) ? Effect$1.failCause(cause) : Effect$1.fail(cause));
55
+ const runCheck = (ctx, arbitrary, property, options) => runTest(ctx)(Effect$1.flatMapEager(Arbitrary.checkEffect(arbitrary, (value) => normalizeProperty(property, value), options), (result) => {
56
+ const failure = Arbitrary.formatCheckFailure(result);
57
+ return failure === void 0 ? Effect$1.void : Effect$1.die(new Error(failure));
58
+ }));
59
+ const withFixturesOf = (source, f) => Object.defineProperty(f, "toString", { value: () => source.toString() });
60
+ const makeItProxy = (it, overrides) => new Proxy(it, {
61
+ apply(target, thisArg, argArray) {
62
+ return Reflect.apply(target, thisArg, argArray);
63
+ },
64
+ get(target, property, receiver) {
65
+ if (Object.hasOwn(overrides, property)) return Reflect.get(overrides, property);
66
+ return Reflect.get(target, property, receiver);
67
+ }
68
+ });
69
+ const collectTasks = (tasks, acc = []) => {
70
+ for (let i = 0; i < tasks.length; i++) {
71
+ const task = tasks[i];
72
+ if (task.type === "test" && task.mode !== "skip" && task.mode !== "todo") acc.push(task);
73
+ else if (task.tasks !== void 0) collectTasks(task.tasks, acc);
74
+ }
75
+ return acc;
76
+ };
77
+ const registerProp = (it, name, arbitraries, property, timeout) => {
78
+ const arbitrary = makeArbitrary(arbitraries);
79
+ it(name, testOptions(timeout), withFixturesOf(() => {}, (ctx) => runCheck(ctx, arbitrary, (values) => property(values, ctx), checkOptions(timeout))));
80
+ };
81
+ const makeTester = (mapEffect, it) => {
82
+ const run = (ctx, args, self) => pipe(Effect$1.suspend(() => self(...args)), mapEffect, runTest(ctx));
83
+ const register = (test) => (name, self, timeout) => test(name, testOptions(timeout), withFixturesOf(self, (ctx) => run(ctx, [ctx], self)));
84
+ const each = (cases) => (name, self, timeout) => it.for(cases)(name, testOptions(timeout), withFixturesOf(self, (args, ctx) => run(ctx, [args, ctx], self).then(constVoid)));
85
+ 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);
86
+ return Object.assign(register(it), {
87
+ skip: register(it.skip),
88
+ skipIf: (condition) => register(it.skipIf(condition)),
89
+ runIf: (condition) => register(it.runIf(condition)),
90
+ only: register(it.only),
91
+ each,
92
+ fails: register(it.fails),
93
+ prop
94
+ });
95
+ };
96
+ const makeProp = (it) => (name, arbitraries, self, timeout) => registerProp(it, name, arbitraries, (values, ctx) => self(values, ctx) !== false, timeout);
97
+ const makeLayer = (it) => (layer_, options) => (...args) => {
98
+ const excludeTestServices = options?.excludeTestServices ?? false;
99
+ const withTestEnv = excludeTestServices ? layer_ : Layer$1.provideMerge(layer_, TestEnv);
100
+ const memoMap = options?.memoMap ?? Effect$1.runSync(Layer$1.makeMemoMap);
101
+ const scope = Effect$1.runSync(Scope$1.make());
102
+ const contextEffect = Layer$1.buildWithMemoMap(withTestEnv, memoMap, scope).pipe(Effect$1.orDie, Effect$1.cached, Effect$1.runSync);
103
+ let closed = false;
104
+ const closeScope = () => {
105
+ if (closed) return Promise.resolve();
106
+ closed = true;
107
+ return runPromise(Scope$1.close(scope, Exit$1.void));
108
+ };
109
+ const methods = makeItProxy(it, {
110
+ effect: makeTester((effect) => Effect$1.flatMap(contextEffect, (context) => effect.pipe(Effect$1.scoped, Effect$1.provide(context))), it),
111
+ prop: makeProp(it),
112
+ flakyTest,
113
+ layer(nestedLayer, options) {
114
+ return makeLayer(it)(Layer$1.provideMerge(nestedLayer, withTestEnv), {
115
+ ...options,
116
+ memoMap: Layer$1.forkMemoMapUnsafe(memoMap),
117
+ excludeTestServices
118
+ });
119
+ }
120
+ });
121
+ if (args.length === 1) {
122
+ const currentSuite = getCurrentSuite();
123
+ const previousTasks = new Set(currentSuite.tasks);
124
+ args[0](methods);
125
+ const blockTasks = collectTasks(currentSuite.tasks.filter((task) => !previousTasks.has(task)));
126
+ if (blockTasks.length === 0) {
127
+ afterAll(() => closeScope(), hookTimeout(options?.timeout));
128
+ return;
129
+ }
130
+ const blockTaskSet = new Set(blockTasks);
131
+ let remaining = blockTasks.length;
132
+ beforeEach(({ onTestFinished, signal, task }) => {
133
+ if (!blockTaskSet.has(task)) return;
134
+ onTestFinished(() => {
135
+ remaining--;
136
+ if (remaining === 0) return closeScope();
137
+ });
138
+ return runPromise(Effect$1.asVoid(contextEffect), { signal });
139
+ }, hookTimeout(options?.timeout));
140
+ afterAll(() => closeScope(), hookTimeout(options?.timeout));
141
+ return;
142
+ }
143
+ const suiteOptions = options?.concurrent === void 0 ? {} : { concurrent: options.concurrent };
144
+ return describe(args[0], suiteOptions, () => {
145
+ beforeAll(() => runPromise(Effect$1.asVoid(contextEffect)), hookTimeout(options?.timeout));
146
+ afterAll(() => closeScope(), hookTimeout(options?.timeout));
147
+ return args[1](methods);
148
+ });
149
+ };
150
+ /** @internal */
151
+ const flakyTest = (self, timeout = Duration$1.seconds(30)) => pipe(self, Effect$1.scoped, Effect$1.sandbox, Effect$1.retry(pipe(Schedule.recurs(10), Schedule.while((_) => Effect$1.succeed(Duration$1.isLessThanOrEqualTo(Duration$1.fromInputUnsafe(_.elapsed), Duration$1.fromInputUnsafe(timeout)))))), Effect$1.orDie);
152
+ /** @internal */
153
+ const makeMethods = (it) => makeItProxy(it, {
154
+ effect: makeTester(flow(Effect$1.scoped, Effect$1.provide(TestEnv)), it),
155
+ live: makeTester(Effect$1.scoped, it),
156
+ flakyTest,
157
+ layer: makeLayer(it),
158
+ prop: makeProp(it)
159
+ });
160
+ /** @internal */
161
+ const it$1 = /*#__PURE__*/ makeMethods(it$2);
162
+ /** @internal */
163
+ const { effect, layer, live, prop } = it$1;
164
+ //#endregion
165
+ //#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
166
+ /**
167
+ * @since 4.0.0
168
+ */
169
+ /**
170
+ * @since 4.0.0
171
+ */
172
+ const it = it$1;
173
+ //#endregion
4
174
  //#region src/internal/socket.test.ts
175
+ const command = (envelopeId) => ({
176
+ type: "slash_commands",
177
+ envelope_id: envelopeId,
178
+ payload: {
179
+ channel_id: "C1",
180
+ user_id: "U1",
181
+ command: "/ping"
182
+ }
183
+ });
184
+ /** Each connection dials its own single-use URL, so each gets its own fake server. */
185
+ const slack = (connections) => Effect.gen(function* () {
186
+ const servers = yield* Effect.forEach(Array.from({ length: connections }), () => FakeWebSocket.make({ greeting: [{ type: "hello" }] }));
187
+ const opened = yield* Ref.make(0);
188
+ const open = Ref.modify(opened, (n) => [`wss://slack.test/${n}`, n + 1]);
189
+ const dial = (url, protocols) => servers[Number(url.split("/").pop())].connect(url, protocols);
190
+ const session = yield* connect({ open }).pipe(Effect.provide(Layer.succeed(Socket.WebSocketConstructor, dial)));
191
+ return {
192
+ servers,
193
+ opened: Ref.get(opened),
194
+ session
195
+ };
196
+ });
5
197
  describe("classifyDisconnect", () => {
6
- it("ends only on the reason Slack says not to retry", () => {
198
+ it$2("ends only on the reason Slack says not to retry", () => {
7
199
  globalExpect(classifyDisconnect("link_disabled")).toBe("fatal");
8
200
  globalExpect(["refresh_requested", "warning"].map(classifyDisconnect)).toEqual(["refresh", "refresh"]);
9
201
  globalExpect(classifyDisconnect(void 0)).toBe("reconnect");
10
202
  });
11
203
  });
12
204
  describe("remember", () => {
13
- it("drops a redelivery of an id still in the set", () => {
205
+ it$2("drops a redelivery of an id still in the set", () => {
14
206
  const seen = Option.getOrThrow(remember([], "Ev1"));
15
207
  globalExpect(remember(seen, "Ev1")).toEqual(Option.none());
16
208
  globalExpect(Option.getOrThrow(remember(seen, "Ev2"))).toEqual(["Ev1", "Ev2"]);
17
209
  });
18
- it("forgets the oldest once the set is full, so it cannot grow without bound", () => {
210
+ it$2("forgets the oldest once the set is full, so it cannot grow without bound", () => {
19
211
  globalExpect(Option.getOrThrow(remember(["a", "b"], "c", 2))).toEqual(["b", "c"]);
20
212
  });
21
213
  });
214
+ describe("connect", () => {
215
+ it.live("acknowledges an envelope before handing it on", () => Effect.gen(function* () {
216
+ const { servers, session } = yield* slack(1);
217
+ yield* servers[0].push(command("e1"));
218
+ const incoming = yield* Queue.take(session.envelopes);
219
+ globalExpect(incoming.envelope.envelope_id).toBe("e1");
220
+ yield* servers[0].awaitSent(1);
221
+ globalExpect(yield* servers[0].sent).toEqual([{ envelope_id: "e1" }]);
222
+ }).pipe(Effect.scoped));
223
+ it.live("fails to connect when no URL can be had before hello", () => Effect.gen(function* () {
224
+ const exit = yield* connect({ open: Effect.fail(new MessengerError.MessengerConnectFailed({
225
+ provider: "slack",
226
+ reason: "invalid_auth",
227
+ raw: void 0
228
+ })) }).pipe(Effect.provide(Socket.layerWebSocketConstructorGlobal), Effect.exit);
229
+ globalExpect(Exit.isFailure(exit) && JSON.stringify(exit.cause)).toContain("invalid_auth");
230
+ }).pipe(Effect.scoped));
231
+ for (const code of [1e3, 1006]) it.live(`reconnects on a fresh URL after a ${code} close`, () => Effect.gen(function* () {
232
+ const { servers, opened, session } = yield* slack(2);
233
+ yield* servers[0].close(code);
234
+ yield* servers[1].greeted;
235
+ yield* servers[1].push(command("e2"));
236
+ const incoming = yield* Queue.take(session.envelopes);
237
+ globalExpect(incoming.envelope.envelope_id).toBe("e2");
238
+ globalExpect(yield* opened).toBe(2);
239
+ yield* servers[1].awaitSent(1);
240
+ globalExpect(yield* servers[1].sent).toEqual([{ envelope_id: "e2" }]);
241
+ }).pipe(Effect.scoped));
242
+ it.live("closes and reconnects on a refresh disconnect", () => Effect.gen(function* () {
243
+ const { servers, opened, session } = yield* slack(2);
244
+ yield* servers[0].push({
245
+ type: "disconnect",
246
+ reason: "refresh_requested"
247
+ });
248
+ yield* servers[1].greeted;
249
+ yield* servers[1].push(command("e3"));
250
+ const incoming = yield* Queue.take(session.envelopes);
251
+ globalExpect(incoming.envelope.envelope_id).toBe("e3");
252
+ globalExpect(yield* opened).toBe(2);
253
+ }).pipe(Effect.scoped));
254
+ it.live("ends envelopes on a disconnect Slack says not to retry", () => Effect.gen(function* () {
255
+ const { servers, opened, session } = yield* slack(1);
256
+ yield* servers[0].push({
257
+ type: "disconnect",
258
+ reason: "link_disabled"
259
+ });
260
+ const exit = yield* Effect.exit(Queue.take(session.envelopes));
261
+ globalExpect(exit).toEqual(Exit.fail(new MessengerError.MessengerTransportClosed({
262
+ provider: "slack",
263
+ reason: "link_disabled"
264
+ })));
265
+ globalExpect(yield* opened).toBe(1);
266
+ }).pipe(Effect.scoped));
267
+ });
22
268
  //#endregion
23
269
  export {};
24
270
 
@@ -1 +1 @@
1
- {"version":3,"file":"socket.test.mjs","names":[],"sources":["../../src/internal/socket.test.ts"],"sourcesContent":["import { Option } from \"effect\"\nimport { describe, expect, it } from \"vitest\"\nimport { classifyDisconnect, remember } from \"./socket.js\"\n\ndescribe(\"classifyDisconnect\", () => {\n it(\"ends only on the reason Slack says not to retry\", () => {\n expect(classifyDisconnect(\"link_disabled\")).toBe(\"fatal\")\n // A rolling refresh is scheduled, so it reopens without backing off.\n expect([\"refresh_requested\", \"warning\"].map(classifyDisconnect)).toEqual([\"refresh\", \"refresh\"])\n expect(classifyDisconnect(undefined)).toBe(\"reconnect\")\n })\n})\n\ndescribe(\"remember\", () => {\n it(\"drops a redelivery of an id still in the set\", () => {\n const seen = Option.getOrThrow(remember([], \"Ev1\"))\n expect(remember(seen, \"Ev1\")).toEqual(Option.none())\n expect(Option.getOrThrow(remember(seen, \"Ev2\"))).toEqual([\"Ev1\", \"Ev2\"])\n })\n\n it(\"forgets the oldest once the set is full, so it cannot grow without bound\", () => {\n const full = [\"a\", \"b\"]\n expect(Option.getOrThrow(remember(full, \"c\", 2))).toEqual([\"b\", \"c\"])\n })\n})\n"],"mappings":";;;;AAIA,SAAS,4BAA4B;CACnC,GAAG,yDAAyD;EAC1D,aAAO,mBAAmB,eAAe,CAAC,CAAC,CAAC,KAAK,OAAO;EAExD,aAAO,CAAC,qBAAqB,SAAS,CAAC,CAAC,IAAI,kBAAkB,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,SAAS,CAAC;EAC/F,aAAO,mBAAmB,KAAA,CAAS,CAAC,CAAC,CAAC,KAAK,WAAW;CACxD,CAAC;AACH,CAAC;AAED,SAAS,kBAAkB;CACzB,GAAG,sDAAsD;EACvD,MAAM,OAAO,OAAO,WAAW,SAAS,CAAC,GAAG,KAAK,CAAC;EAClD,aAAO,SAAS,MAAM,KAAK,CAAC,CAAC,CAAC,QAAQ,OAAO,KAAK,CAAC;EACnD,aAAO,OAAO,WAAW,SAAS,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,KAAK,CAAC;CACzE,CAAC;CAED,GAAG,kFAAkF;EAEnF,aAAO,OAAO,WAAW,SAAS,CADpB,KAAK,GACkB,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC;CACtE,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"socket.test.mjs","names":["Effect","Exit","Cause","Duration","Layer","Scope","V.describe","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/socket.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, Exit, Layer, Option, Queue, Ref } from \"effect\"\nimport * as Socket from \"effect/socket/Socket\"\nimport { describe, expect, it } from \"vitest\"\nimport * as MessengerError from \"@effect-uai/core/MessengerError\"\nimport * as FakeWebSocket from \"@effect-uai/core/testing/FakeWebSocket\"\nimport { classifyDisconnect, connect, remember } from \"./socket.js\"\n\nconst command = (envelopeId: string) => ({\n type: \"slash_commands\",\n envelope_id: envelopeId,\n payload: { channel_id: \"C1\", user_id: \"U1\", command: \"/ping\" },\n})\n\n/** Each connection dials its own single-use URL, so each gets its own fake server. */\nconst slack = (connections: number) =>\n Effect.gen(function* () {\n const servers = yield* Effect.forEach(Array.from({ length: connections }), () =>\n FakeWebSocket.make({ greeting: [{ type: \"hello\" }] }),\n )\n const opened = yield* Ref.make(0)\n const open = Ref.modify(opened, (n): [string, number] => [`wss://slack.test/${n}`, n + 1])\n const dial: Socket.WebSocketConstructor[\"Service\"] = (url, protocols) =>\n servers[Number(url.split(\"/\").pop())]!.connect(url, protocols)\n const session = yield* connect({ open }).pipe(\n Effect.provide(Layer.succeed(Socket.WebSocketConstructor, dial)),\n )\n return { servers, opened: Ref.get(opened), session }\n })\n\ndescribe(\"classifyDisconnect\", () => {\n it(\"ends only on the reason Slack says not to retry\", () => {\n expect(classifyDisconnect(\"link_disabled\")).toBe(\"fatal\")\n // A rolling refresh is scheduled, so it reopens without backing off.\n expect([\"refresh_requested\", \"warning\"].map(classifyDisconnect)).toEqual([\"refresh\", \"refresh\"])\n expect(classifyDisconnect(undefined)).toBe(\"reconnect\")\n })\n})\n\ndescribe(\"remember\", () => {\n it(\"drops a redelivery of an id still in the set\", () => {\n const seen = Option.getOrThrow(remember([], \"Ev1\"))\n expect(remember(seen, \"Ev1\")).toEqual(Option.none())\n expect(Option.getOrThrow(remember(seen, \"Ev2\"))).toEqual([\"Ev1\", \"Ev2\"])\n })\n\n it(\"forgets the oldest once the set is full, so it cannot grow without bound\", () => {\n const full = [\"a\", \"b\"]\n expect(Option.getOrThrow(remember(full, \"c\", 2))).toEqual([\"b\", \"c\"])\n })\n})\n\ndescribe(\"connect\", () => {\n effect.live(\"acknowledges an envelope before handing it on\", () =>\n Effect.gen(function* () {\n const { servers, session } = yield* slack(1)\n yield* servers[0]!.push(command(\"e1\"))\n\n const incoming = yield* Queue.take(session.envelopes)\n expect(incoming.envelope.envelope_id).toBe(\"e1\")\n yield* servers[0]!.awaitSent(1)\n expect(yield* servers[0]!.sent).toEqual([{ envelope_id: \"e1\" }])\n }).pipe(Effect.scoped),\n )\n\n effect.live(\"fails to connect when no URL can be had before hello\", () =>\n Effect.gen(function* () {\n const exit = yield* connect({\n open: Effect.fail(\n new MessengerError.MessengerConnectFailed({\n provider: \"slack\",\n reason: \"invalid_auth\",\n raw: undefined,\n }),\n ),\n }).pipe(Effect.provide(Socket.layerWebSocketConstructorGlobal), Effect.exit)\n\n expect(Exit.isFailure(exit) && JSON.stringify(exit.cause)).toContain(\"invalid_auth\")\n }).pipe(Effect.scoped),\n )\n\n // Clean or dirty, a close Slack did not announce is a drop, and every drop reconnects.\n for (const code of [1000, 1006]) {\n effect.live(`reconnects on a fresh URL after a ${code} close`, () =>\n Effect.gen(function* () {\n const { servers, opened, session } = yield* slack(2)\n yield* servers[0]!.close(code)\n yield* servers[1]!.greeted\n yield* servers[1]!.push(command(\"e2\"))\n\n const incoming = yield* Queue.take(session.envelopes)\n expect(incoming.envelope.envelope_id).toBe(\"e2\")\n expect(yield* opened).toBe(2)\n yield* servers[1]!.awaitSent(1)\n expect(yield* servers[1]!.sent).toEqual([{ envelope_id: \"e2\" }])\n }).pipe(Effect.scoped),\n )\n }\n\n effect.live(\"closes and reconnects on a refresh disconnect\", () =>\n Effect.gen(function* () {\n const { servers, opened, session } = yield* slack(2)\n yield* servers[0]!.push({ type: \"disconnect\", reason: \"refresh_requested\" })\n yield* servers[1]!.greeted\n yield* servers[1]!.push(command(\"e3\"))\n\n const incoming = yield* Queue.take(session.envelopes)\n expect(incoming.envelope.envelope_id).toBe(\"e3\")\n expect(yield* opened).toBe(2)\n }).pipe(Effect.scoped),\n )\n\n effect.live(\"ends envelopes on a disconnect Slack says not to retry\", () =>\n Effect.gen(function* () {\n const { servers, opened, session } = yield* slack(1)\n yield* servers[0]!.push({ type: \"disconnect\", reason: \"link_disabled\" })\n\n const exit = yield* Effect.exit(Queue.take(session.envelopes))\n expect(exit).toEqual(\n Exit.fail(\n new MessengerError.MessengerTransportClosed({\n provider: \"slack\",\n reason: \"link_disabled\",\n }),\n ),\n )\n expect(yield* opened).toBe(1)\n }).pipe(Effect.scoped),\n )\n})\n"],"x_google_ignoreList":[0,1],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  //#region \0rolldown/runtime.js
2
2
  var __defProp = Object.defineProperty;
3
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
3
4
  var __exportAll = (all, no_symbols) => {
4
5
  let target = {};
5
6
  for (var name in all) __defProp(target, name, {
@@ -10,4 +11,4 @@ var __exportAll = (all, no_symbols) => {
10
11
  return target;
11
12
  };
12
13
  //#endregion
13
- export { __exportAll as t };
14
+ export { __exportAll as n, __commonJSMin as t };
@@ -1,5 +1,6 @@
1
1
  import { a as Envelope } from "./events-Cj5RRjK1.mjs";
2
2
  import { Cause, Effect, Option, Queue, Scope } from "effect";
3
+ import * as Socket from "effect/socket/Socket";
3
4
  import * as MessengerError from "@effect-uai/core/MessengerError";
4
5
  //#region src/internal/socket.d.ts
5
6
  /** What the session does once a connection is gone. */
@@ -48,7 +49,7 @@ type Session = {
48
49
  * moment later. From then on reconnects are silent and only a `link_disabled`
49
50
  * disconnect ends `envelopes`.
50
51
  */
51
- declare const connect: (cfg: Config) => Effect.Effect<Session, MessengerError.MessengerConnectFailed, Scope.Scope>;
52
+ declare const connect: (cfg: Config) => Effect.Effect<Session, MessengerError.MessengerConnectFailed, Scope.Scope | Socket.WebSocketConstructor>;
52
53
  //#endregion
53
54
  export { Recent as a, connect as c, RECENT as i, remember as l, DisconnectAction as n, Session as o, Incoming as r, classifyDisconnect as s, Config as t };
54
- //# sourceMappingURL=socket-DJgdt_ka.d.mts.map
55
+ //# sourceMappingURL=socket-qV7yc2q7.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"socket-qV7yc2q7.d.mts","names":[],"sources":["../src/internal/socket.ts"],"mappings":";;;;;;KAyBY;;;;;;cAOC,qBAAkB,+BAAiC;;;;;KAgBpD,SAAS;;cAGR;;cAGA,WAAQ,MAAU,QAAM,YAAY,iBAAyB,OAAO,OAAO;KAO5E;;;;;;WAMD,MAAM,OAAO,eAAe,eAAe;;;KAI1C;WACD,UAAU;WACV;;KAGC;;;;;;WAMD,WAAW,MAAM,MAAM,UAAU,eAAe,iBAAiB,MAAM;;;;;;;;;;cA4BrE,UAAO,KACb,WACJ,OAAO,OACR,SACA,eAAe,wBACf,MAAM,QAAQ,OAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@effect-uai/slack",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "Slack provider for @effect-uai/core (Messenger capability over Socket Mode and the Web API).",
5
5
  "keywords": [
6
6
  "agents",
@@ -44,13 +44,13 @@
44
44
  "access": "public"
45
45
  },
46
46
  "devDependencies": {
47
- "effect": "4.0.0-rc.111",
47
+ "effect": "4.0.1",
48
48
  "typescript": "^7.0.2",
49
- "@effect-uai/core": "0.16.0"
49
+ "@effect-uai/core": "0.18.0"
50
50
  },
51
51
  "peerDependencies": {
52
52
  "@effect-uai/core": ">=0.15.0 <1",
53
- "effect": ">=4.0.0-rc.111 <5.0.0"
53
+ "effect": ">=4.0.1 <5.0.0"
54
54
  },
55
55
  "scripts": {
56
56
  "build": "tsdown",