Skip to content
This repository has been archived by the owner on Jul 16, 2024. It is now read-only.

Commit

Permalink
feat: added supporting ws files
Browse files Browse the repository at this point in the history
  • Loading branch information
jessekelly881 committed Dec 6, 2023
1 parent 279d79e commit 8ece61b
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 58 deletions.
68 changes: 11 additions & 57 deletions packages/platform-browser/src/WebSocket.ts
Original file line number Diff line number Diff line change
@@ -1,65 +1,19 @@
/**
* @since 1.0.0
*
* Also includes exports from [`@effect/platform/WebSocket`](https://effect-ts.github.io/platform/platform/WebSocket.ts.html).
*/
import * as Chunk from "effect/Chunk"
import * as Context from "effect/Context"
import * as Effect from "effect/Effect"
import type * as Scope from "effect/Scope"
import * as Stream from "effect/Stream"

interface Socket {
send: (data: string | Blob) => Effect.Effect<never, never, void>
messages: Stream.Stream<never, never, string | Blob | ArrayBuffer>
errors: Stream.Stream<never, never, Event>
}

export const tag = Context.Tag<Socket>("@effect/platform/WebSocket")

/** @internal */
const createSocket = (url: string | URL) =>
Effect.acquireRelease(
Effect.flatMap(
Effect.try({
try: () => new WebSocket(url),
catch: (err) => err as Error
}),
(socket) =>
Effect.async<never, never, WebSocket>((resume) => {
socket.addEventListener("open", () => {
resume(Effect.succeed(socket))
})
})
),
(s) => Effect.succeed(s.close()) // could fail
)
import * as internal from "./internal/webSocket.js"

/**
* @since 1.0.0
*/
export const layer = (url: string | URL): Effect.Effect<Scope.Scope, Error, Socket> =>
Effect.gen(function*(_) {
const socket = yield* _(createSocket(url))

const send = (data: string | Blob) => Effect.sync(() => socket.send(data))

const messages = Stream.async<never, never, string | Blob | ArrayBuffer>((emit) => {
socket.addEventListener("message", (event) => {
emit(Effect.succeed(Chunk.of(event.data as string | Blob | ArrayBuffer)))
})
})

const errors = Stream.async<never, never, Event>((emit) => {
socket.addEventListener("message", (event) => {
emit(Effect.succeed(Chunk.of(event.data)))
})
})
export * from "@effect/platform/WebSocket"

return tag.of({ send, messages, errors })
})

const test = Effect.gen(function*(_) {
const ws = yield* _(layer("ws://localhost:3000"))
yield* _(ws.send("abc"))
}).pipe(Effect.scoped, Effect.tapError((err) => Effect.logError(err)))

Effect.runFork(test)
/**
* Creates a WebSocket layer.
*
* @since 1.0.0
* @category models
*/
export const layer = internal.layer
68 changes: 68 additions & 0 deletions packages/platform-browser/src/internal/webSocket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* @since 1.0.0
*/
import * as PlatformError from "@effect/platform/Error"
import * as Socket from "@effect/platform/WebSocket"
import * as Chunk from "effect/Chunk"
import * as Effect from "effect/Effect"
import * as Layer from "effect/Layer"
import * as Stream from "effect/Stream"

const make = (impl: Omit<Socket.Socket, Socket.TypeId>) =>
Socket.Socket.of({
[Socket.TypeId]: Socket.TypeId,
...impl
})

/** @internal */
const createSocket = (url: string | URL) =>
Effect.acquireRelease(
Effect.flatMap(
Effect.try({
try: () => new WebSocket(url),
catch: () =>
PlatformError.SystemError({
module: "WebSocket",
reason: "InvalidData",
message: "Unable to create WebSocket. Invalid host.",
method: "",
pathOrDescriptor: ""
})
}),
(socket) =>
Effect.async<never, never, WebSocket>((resume) => {
socket.addEventListener("open", () => {
resume(Effect.succeed(socket))
})
})
),
(s) => Effect.succeed(s.close(1001)) // .close is unsafe..
)

/**
* @since 1.0.0
*/
export const layer = (url: string | URL): Layer.Layer<never, PlatformError.SystemError, Socket.Socket> =>
Layer.scoped(
Socket.Socket,
Effect.gen(function*(_) {
const socket = yield* _(createSocket(url))

// Safe as long as the socket is fully connected.
const send = (data: string | Blob) => Effect.sync(() => socket.send(data))

const messages = Stream.async<never, never, string | Blob | ArrayBuffer>((emit) => {
socket.addEventListener("message", (event) => {
emit(Effect.succeed(Chunk.of(event.data as string | Blob | ArrayBuffer)))
})
})

const errors = Stream.async<never, never, Event>((emit) => {
socket.addEventListener("error", (event) => {
emit(Effect.succeed(Chunk.of(event)))
})
})

return make({ send, messages, errors })
})
)
10 changes: 9 additions & 1 deletion packages/platform/src/Error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,15 @@ export declare namespace PlatformError {
export interface Base extends Data.Case {
readonly [PlatformErrorTypeId]: typeof PlatformErrorTypeId
readonly _tag: string
readonly module: "Clipboard" | "Command" | "FileSystem" | "KeyValueStore" | "Path" | "Stream" | "Terminal"
readonly module:
| "Clipboard"
| "Command"
| "FileSystem"
| "KeyValueStore"
| "Path"
| "Stream"
| "Terminal"
| "WebSocket"
readonly method: string
readonly message: string
}
Expand Down
44 changes: 44 additions & 0 deletions packages/platform/src/WebSocket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* @since 1.0.0
*/
import type * as Context from "effect/Context"
import type * as Effect from "effect/Effect"
import type * as Stream from "effect/Stream"
import * as internal from "./internal/webSocket.js"

/**
* @since 1.0.0
* @category type id
*/
export const TypeId: unique symbol = internal.TypeId

/**
* @since 1.0.0
* @category type id
*/
export type TypeId = typeof TypeId

/**
* @since 1.0.0
* @category models
*/
export interface Socket {
readonly [TypeId]: TypeId
send: (data: string | Blob) => Effect.Effect<never, never, void>
messages: Stream.Stream<never, never, string | Blob | ArrayBuffer>
errors: Stream.Stream<never, never, Event>
}

/**
* @since 1.0.0
* @category constructors
*/
export const make: (
impl: Omit<Socket, typeof TypeId>
) => Socket = internal.make

/**
* @since 1.0.0
* @category tags
*/
export const Socket: Context.Tag<Socket, Socket> = internal.tag
17 changes: 17 additions & 0 deletions packages/platform/src/internal/webSocket.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* @since 1.0.0
*/
import * as Context from "effect/Context"
import type * as WS from "../WebSocket.js"

export const TypeId: WS.TypeId = Symbol.for(
"@effect/platform/WebSocket"
) as WS.TypeId

export const tag = Context.Tag<WS.Socket>("@effect/platform/WebSocket")

export const make = (impl: Omit<WS.Socket, WS.TypeId>) =>
tag.of({
[TypeId]: TypeId,
...impl
})

0 comments on commit 8ece61b

Please sign in to comment.