@effect/platform 0.64.0 → 0.65.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.
- package/FetchHttpClient/package.json +6 -0
- package/README.md +175 -611
- package/dist/cjs/Command.js +3 -3
- package/dist/cjs/Command.js.map +1 -1
- package/dist/cjs/FetchHttpClient.js +32 -0
- package/dist/cjs/FetchHttpClient.js.map +1 -0
- package/dist/cjs/HttpApiClient.js +1 -1
- package/dist/cjs/HttpApiClient.js.map +1 -1
- package/dist/cjs/HttpClient.js +5 -36
- package/dist/cjs/HttpClient.js.map +1 -1
- package/dist/cjs/HttpClientRequest.js +11 -11
- package/dist/cjs/HttpClientRequest.js.map +1 -1
- package/dist/cjs/HttpClientResponse.js +2 -61
- package/dist/cjs/HttpClientResponse.js.map +1 -1
- package/dist/cjs/HttpIncomingMessage.js +2 -29
- package/dist/cjs/HttpIncomingMessage.js.map +1 -1
- package/dist/cjs/Runtime.js +1 -1
- package/dist/cjs/Runtime.js.map +1 -1
- package/dist/cjs/Socket.js +42 -53
- package/dist/cjs/Socket.js.map +1 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/internal/command.js +2 -2
- package/dist/cjs/internal/command.js.map +1 -1
- package/dist/cjs/internal/fetchHttpClient.js +52 -0
- package/dist/cjs/internal/fetchHttpClient.js.map +1 -0
- package/dist/cjs/internal/httpClient.js +82 -71
- package/dist/cjs/internal/httpClient.js.map +1 -1
- package/dist/cjs/internal/httpClientRequest.js +13 -13
- package/dist/cjs/internal/httpClientRequest.js.map +1 -1
- package/dist/cjs/internal/httpClientResponse.js +1 -33
- package/dist/cjs/internal/httpClientResponse.js.map +1 -1
- package/dist/cjs/internal/worker.js +21 -29
- package/dist/cjs/internal/worker.js.map +1 -1
- package/dist/dts/Command.d.ts +4 -4
- package/dist/dts/Command.d.ts.map +1 -1
- package/dist/dts/FetchHttpClient.d.ts +27 -0
- package/dist/dts/FetchHttpClient.d.ts.map +1 -0
- package/dist/dts/HttpApiClient.d.ts +2 -2
- package/dist/dts/HttpClient.d.ts +53 -61
- package/dist/dts/HttpClient.d.ts.map +1 -1
- package/dist/dts/HttpClientRequest.d.ts +11 -11
- package/dist/dts/HttpClientRequest.d.ts.map +1 -1
- package/dist/dts/HttpClientResponse.d.ts +4 -90
- package/dist/dts/HttpClientResponse.d.ts.map +1 -1
- package/dist/dts/HttpIncomingMessage.d.ts +0 -16
- package/dist/dts/HttpIncomingMessage.d.ts.map +1 -1
- package/dist/dts/HttpServer.d.ts +1 -1
- package/dist/dts/Socket.d.ts +3 -3
- package/dist/dts/Socket.d.ts.map +1 -1
- package/dist/dts/index.d.ts +4 -0
- package/dist/dts/index.d.ts.map +1 -1
- package/dist/dts/internal/fetchHttpClient.d.ts +2 -0
- package/dist/dts/internal/fetchHttpClient.d.ts.map +1 -0
- package/dist/esm/Command.js +3 -3
- package/dist/esm/Command.js.map +1 -1
- package/dist/esm/FetchHttpClient.js +21 -0
- package/dist/esm/FetchHttpClient.js.map +1 -0
- package/dist/esm/HttpApiClient.js +1 -1
- package/dist/esm/HttpApiClient.js.map +1 -1
- package/dist/esm/HttpClient.js +4 -35
- package/dist/esm/HttpClient.js.map +1 -1
- package/dist/esm/HttpClientRequest.js +10 -10
- package/dist/esm/HttpClientRequest.js.map +1 -1
- package/dist/esm/HttpClientResponse.js +1 -63
- package/dist/esm/HttpClientResponse.js.map +1 -1
- package/dist/esm/HttpIncomingMessage.js +0 -24
- package/dist/esm/HttpIncomingMessage.js.map +1 -1
- package/dist/esm/Runtime.js +1 -1
- package/dist/esm/Runtime.js.map +1 -1
- package/dist/esm/Socket.js +42 -53
- package/dist/esm/Socket.js.map +1 -1
- package/dist/esm/index.js +4 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/internal/command.js +2 -2
- package/dist/esm/internal/command.js.map +1 -1
- package/dist/esm/internal/fetchHttpClient.js +44 -0
- package/dist/esm/internal/fetchHttpClient.js.map +1 -0
- package/dist/esm/internal/httpClient.js +79 -69
- package/dist/esm/internal/httpClient.js.map +1 -1
- package/dist/esm/internal/httpClientRequest.js +11 -11
- package/dist/esm/internal/httpClientRequest.js.map +1 -1
- package/dist/esm/internal/httpClientResponse.js +0 -24
- package/dist/esm/internal/httpClientResponse.js.map +1 -1
- package/dist/esm/internal/worker.js +21 -29
- package/dist/esm/internal/worker.js.map +1 -1
- package/package.json +11 -3
- package/src/Command.ts +7 -8
- package/src/FetchHttpClient.ts +25 -0
- package/src/HttpApiClient.ts +5 -5
- package/src/HttpClient.ts +79 -85
- package/src/HttpClientRequest.ts +21 -21
- package/src/HttpClientResponse.ts +4 -162
- package/src/HttpIncomingMessage.ts +0 -43
- package/src/HttpServer.ts +1 -1
- package/src/Runtime.ts +1 -1
- package/src/Socket.ts +42 -58
- package/src/index.ts +5 -0
- package/src/internal/command.ts +4 -3
- package/src/internal/fetchHttpClient.ts +53 -0
- package/src/internal/httpClient.ts +149 -125
- package/src/internal/httpClientRequest.ts +12 -12
- package/src/internal/httpClientResponse.ts +6 -96
- package/src/internal/worker.ts +40 -55
package/README.md
CHANGED
|
@@ -2,23 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Welcome to the documentation for `@effect/platform`, a library designed for creating platform-independent abstractions (Node.js, Bun, browsers).
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
This package empowers you to perform various operations, such as:
|
|
8
|
-
|
|
9
|
-
| **Operation** | **Description** |
|
|
10
|
-
| -------------- | ------------------------------------------------------------------------------------------------ |
|
|
11
|
-
| HTTP API | Declarative HTTP API servers & clients |
|
|
12
|
-
| HTTP Client | Sending HTTP requests and receiving responses |
|
|
13
|
-
| HTTP Server | Creating HTTP servers to handle incoming requests |
|
|
14
|
-
| HTTP Router | Routing HTTP requests to specific handlers |
|
|
15
|
-
| Terminal | Reading and writing from/to standard input/output |
|
|
16
|
-
| Command | Creating and running a command with the specified process name and an optional list of arguments |
|
|
17
|
-
| FileSystem | Reading and writing from/to the file system |
|
|
18
|
-
| KeyValueStore | Storing and retrieving key-value pairs |
|
|
19
|
-
| PlatformLogger | Creating a logger that writes to a specified file from another string logger |
|
|
20
|
-
|
|
21
|
-
By utilizing `@effect/platform`, you can write code that remains platform-agnostic, ensuring compatibility across different environments.
|
|
5
|
+
> [!WARNING]
|
|
6
|
+
> This documentation focuses on **unstable modules**. For stable modules, refer to the [official website documentation](https://effect.website/docs/guides/platform/introduction).
|
|
22
7
|
|
|
23
8
|
# HTTP API
|
|
24
9
|
|
|
@@ -579,42 +564,46 @@ Effect.gen(function* () {
|
|
|
579
564
|
|
|
580
565
|
## Overview
|
|
581
566
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
```ts
|
|
585
|
-
type HttpClient<A, E, R> = (request: HttpClientRequest): Effect<A, E, R>
|
|
586
|
-
```
|
|
567
|
+
The `@effect/platform/HttpClient*` modules provide a way to send HTTP requests,
|
|
568
|
+
handle responses, and abstract over the differences between platforms.
|
|
587
569
|
|
|
588
|
-
|
|
570
|
+
The `HttpClient` interface has a set of methods for sending requests:
|
|
589
571
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
572
|
+
- `.execute` - takes a `HttpClientRequest` and returns a `HttpClientResponse`
|
|
573
|
+
- `.{get, post, ...}` - convenience methods for creating a request and
|
|
574
|
+
executing it in one step
|
|
593
575
|
|
|
594
|
-
|
|
576
|
+
To access the `HttpClient`, you can use the `HttpClient.HttpClient` `Context.Tag`.
|
|
577
|
+
This will give you access to a `HttpClient.Service` instance, which is the default
|
|
578
|
+
type of the `HttpClient` interface.
|
|
595
579
|
|
|
596
580
|
### A First Example: Retrieving JSON Data (GET)
|
|
597
581
|
|
|
598
582
|
Here's a simple example demonstrating how to retrieve JSON data using `HttpClient` from `@effect/platform`.
|
|
599
583
|
|
|
600
584
|
```ts
|
|
601
|
-
import {
|
|
602
|
-
HttpClient,
|
|
603
|
-
HttpClientRequest,
|
|
604
|
-
HttpClientResponse
|
|
605
|
-
} from "@effect/platform"
|
|
585
|
+
import { FetchHttpClient, HttpClient } from "@effect/platform"
|
|
606
586
|
import { Effect } from "effect"
|
|
607
587
|
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
588
|
+
const program = Effect.gen(function* () {
|
|
589
|
+
// access the HttpClient
|
|
590
|
+
const client = yield* HttpClient.HttpClient
|
|
611
591
|
|
|
612
|
-
|
|
613
|
-
|
|
592
|
+
const response = yield* client.get(
|
|
593
|
+
"https://jsonplaceholder.typicode.com/posts/1"
|
|
594
|
+
)
|
|
614
595
|
|
|
615
|
-
const json =
|
|
596
|
+
const json = yield* response.json
|
|
616
597
|
|
|
617
|
-
|
|
598
|
+
console.log(json)
|
|
599
|
+
}).pipe(
|
|
600
|
+
// ensure the request is aborted if the program is interrupted
|
|
601
|
+
Effect.scoped,
|
|
602
|
+
// provide the HttpClient
|
|
603
|
+
Effect.provide(FetchHttpClient.layer)
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
Effect.runPromise(program)
|
|
618
607
|
/*
|
|
619
608
|
Output:
|
|
620
609
|
{
|
|
@@ -629,29 +618,15 @@ Output:
|
|
|
629
618
|
*/
|
|
630
619
|
```
|
|
631
620
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
- `HttpClientRequest.get` creates a GET request to the specified URL.
|
|
635
|
-
- `HttpClient.fetch` executes the request.
|
|
636
|
-
- `HttpClientResponse.json` converts the response to JSON.
|
|
637
|
-
- `Effect.runPromise` runs the effect and logs the result.
|
|
638
|
-
|
|
639
|
-
### Built-in Defaults
|
|
621
|
+
### Custom `HttpClient.Service`'s
|
|
640
622
|
|
|
641
|
-
|
|
642
|
-
| -------------------- | ------------------------------------------------------------------------- |
|
|
643
|
-
| `HttpClient.fetch` | Execute the request using the global `fetch` function |
|
|
644
|
-
| `HttpClient.fetchOk` | Same as `fetch` but ensures only `2xx` responses are treated as successes |
|
|
645
|
-
|
|
646
|
-
### Custom Default
|
|
647
|
-
|
|
648
|
-
You can create your own `Default` using the `HttpClient.makeDefault` constructor.
|
|
623
|
+
You can create your own `HttpClient.Service` using the `HttpClient.makeService` constructor.
|
|
649
624
|
|
|
650
625
|
```ts
|
|
651
626
|
import { HttpClient, HttpClientResponse } from "@effect/platform"
|
|
652
627
|
import { Effect } from "effect"
|
|
653
628
|
|
|
654
|
-
const myClient = HttpClient.
|
|
629
|
+
const myClient = HttpClient.makeService((req) =>
|
|
655
630
|
Effect.succeed(
|
|
656
631
|
HttpClientResponse.fromWeb(
|
|
657
632
|
req,
|
|
@@ -672,27 +647,25 @@ const myClient = HttpClient.makeDefault((req) =>
|
|
|
672
647
|
## Tapping
|
|
673
648
|
|
|
674
649
|
```ts
|
|
675
|
-
import {
|
|
676
|
-
HttpClient,
|
|
677
|
-
HttpClientRequest,
|
|
678
|
-
HttpClientResponse
|
|
679
|
-
} from "@effect/platform"
|
|
650
|
+
import { FetchHttpClient, HttpClient } from "@effect/platform"
|
|
680
651
|
import { Console, Effect } from "effect"
|
|
681
652
|
|
|
682
|
-
const
|
|
683
|
-
|
|
684
|
-
|
|
653
|
+
const program = Effect.gen(function* () {
|
|
654
|
+
const client = (yield* HttpClient.HttpClient).pipe(
|
|
655
|
+
// Log the request before fetching
|
|
656
|
+
HttpClient.tapRequest(Console.log)
|
|
657
|
+
)
|
|
685
658
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
)
|
|
659
|
+
const response = yield* client.get(
|
|
660
|
+
"https://jsonplaceholder.typicode.com/posts/1"
|
|
661
|
+
)
|
|
690
662
|
|
|
691
|
-
const
|
|
663
|
+
const json = yield* response.json
|
|
692
664
|
|
|
693
|
-
|
|
665
|
+
console.log(json)
|
|
666
|
+
}).pipe(Effect.scoped, Effect.provide(FetchHttpClient.layer))
|
|
694
667
|
|
|
695
|
-
Effect.runPromise(
|
|
668
|
+
Effect.runPromise(program)
|
|
696
669
|
/*
|
|
697
670
|
Output:
|
|
698
671
|
{
|
|
@@ -847,20 +820,21 @@ Output:
|
|
|
847
820
|
To convert a GET response to JSON:
|
|
848
821
|
|
|
849
822
|
```ts
|
|
850
|
-
import {
|
|
851
|
-
HttpClient,
|
|
852
|
-
HttpClientRequest,
|
|
853
|
-
HttpClientResponse
|
|
854
|
-
} from "@effect/platform"
|
|
823
|
+
import { FetchHttpClient, HttpClient } from "@effect/platform"
|
|
855
824
|
import { NodeRuntime } from "@effect/platform-node"
|
|
856
825
|
import { Console, Effect } from "effect"
|
|
857
826
|
|
|
858
|
-
const getPostAsJson =
|
|
859
|
-
|
|
860
|
-
|
|
827
|
+
const getPostAsJson = Effect.gen(function* () {
|
|
828
|
+
const client = yield* HttpClient.HttpClient
|
|
829
|
+
const response = yield* client.get(
|
|
830
|
+
"https://jsonplaceholder.typicode.com/posts/1"
|
|
831
|
+
)
|
|
832
|
+
return yield* response.json
|
|
833
|
+
}).pipe(Effect.scoped, Effect.provide(FetchHttpClient.layer))
|
|
861
834
|
|
|
862
|
-
|
|
863
|
-
|
|
835
|
+
getPostAsJson.pipe(
|
|
836
|
+
Effect.andThen((post) => Console.log(typeof post, post)),
|
|
837
|
+
NodeRuntime.runMain
|
|
864
838
|
)
|
|
865
839
|
/*
|
|
866
840
|
Output:
|
|
@@ -881,20 +855,21 @@ object {
|
|
|
881
855
|
To convert a GET response to text:
|
|
882
856
|
|
|
883
857
|
```ts
|
|
884
|
-
import {
|
|
885
|
-
HttpClient,
|
|
886
|
-
HttpClientRequest,
|
|
887
|
-
HttpClientResponse
|
|
888
|
-
} from "@effect/platform"
|
|
858
|
+
import { FetchHttpClient, HttpClient } from "@effect/platform"
|
|
889
859
|
import { NodeRuntime } from "@effect/platform-node"
|
|
890
860
|
import { Console, Effect } from "effect"
|
|
891
861
|
|
|
892
|
-
const
|
|
893
|
-
|
|
894
|
-
|
|
862
|
+
const getPostAsJson = Effect.gen(function* () {
|
|
863
|
+
const client = yield* HttpClient.HttpClient
|
|
864
|
+
const response = yield* client.get(
|
|
865
|
+
"https://jsonplaceholder.typicode.com/posts/1"
|
|
866
|
+
)
|
|
867
|
+
return yield* response.text
|
|
868
|
+
}).pipe(Effect.scoped, Effect.provide(FetchHttpClient.layer))
|
|
895
869
|
|
|
896
|
-
|
|
897
|
-
|
|
870
|
+
getPostAsJson.pipe(
|
|
871
|
+
Effect.andThen((post) => Console.log(typeof post, post)),
|
|
872
|
+
NodeRuntime.runMain
|
|
898
873
|
)
|
|
899
874
|
/*
|
|
900
875
|
Output:
|
|
@@ -914,14 +889,14 @@ string {
|
|
|
914
889
|
|
|
915
890
|
Here are some APIs you can use to convert the response:
|
|
916
891
|
|
|
917
|
-
| API
|
|
918
|
-
|
|
|
919
|
-
| `
|
|
920
|
-
| `
|
|
921
|
-
| `
|
|
922
|
-
| `
|
|
923
|
-
| `
|
|
924
|
-
| `
|
|
892
|
+
| API | Description |
|
|
893
|
+
| ------------------------ | ------------------------------------- |
|
|
894
|
+
| `response.arrayBuffer` | Convert to `ArrayBuffer` |
|
|
895
|
+
| `response.formData` | Convert to `FormData` |
|
|
896
|
+
| `response.json` | Convert to JSON |
|
|
897
|
+
| `response.stream` | Convert to a `Stream` of `Uint8Array` |
|
|
898
|
+
| `response.text` | Convert to text |
|
|
899
|
+
| `response.urlParamsBody` | Convert to `UrlParams` |
|
|
925
900
|
|
|
926
901
|
### Decoding Data with Schemas
|
|
927
902
|
|
|
@@ -929,8 +904,8 @@ A common use case when fetching data is to validate the received format. For thi
|
|
|
929
904
|
|
|
930
905
|
```ts
|
|
931
906
|
import {
|
|
907
|
+
FetchHttpClient,
|
|
932
908
|
HttpClient,
|
|
933
|
-
HttpClientRequest,
|
|
934
909
|
HttpClientResponse
|
|
935
910
|
} from "@effect/platform"
|
|
936
911
|
import { NodeRuntime } from "@effect/platform-node"
|
|
@@ -946,17 +921,17 @@ const Post = Schema.Struct({
|
|
|
946
921
|
const getPostAndValidate: Effect.Effect<{
|
|
947
922
|
readonly id: number;
|
|
948
923
|
readonly title: string;
|
|
949
|
-
},
|
|
924
|
+
}, HttpClientError | ParseError, never>
|
|
950
925
|
*/
|
|
951
|
-
const getPostAndValidate =
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
)
|
|
926
|
+
const getPostAndValidate = Effect.gen(function* () {
|
|
927
|
+
const client = yield* HttpClient.HttpClient
|
|
928
|
+
const response = yield* client.get(
|
|
929
|
+
"https://jsonplaceholder.typicode.com/posts/1"
|
|
930
|
+
)
|
|
931
|
+
return yield* HttpClientResponse.schemaBodyJson(Post)(response)
|
|
932
|
+
}).pipe(Effect.scoped, Effect.provide(FetchHttpClient.layer))
|
|
958
933
|
|
|
959
|
-
|
|
934
|
+
getPostAndValidate.pipe(Effect.andThen(Console.log), NodeRuntime.runMain)
|
|
960
935
|
/*
|
|
961
936
|
Output:
|
|
962
937
|
{
|
|
@@ -979,19 +954,19 @@ You can use `HttpClient.filterStatusOk`, or `HttpClient.fetchOk` to ensure only
|
|
|
979
954
|
In this example, we attempt to fetch a non-existent page and don't receive any error:
|
|
980
955
|
|
|
981
956
|
```ts
|
|
982
|
-
import {
|
|
983
|
-
HttpClient,
|
|
984
|
-
HttpClientRequest,
|
|
985
|
-
HttpClientResponse
|
|
986
|
-
} from "@effect/platform"
|
|
957
|
+
import { FetchHttpClient, HttpClient } from "@effect/platform"
|
|
987
958
|
import { NodeRuntime } from "@effect/platform-node"
|
|
988
959
|
import { Console, Effect } from "effect"
|
|
989
960
|
|
|
990
|
-
const getText =
|
|
991
|
-
|
|
992
|
-
|
|
961
|
+
const getText = Effect.gen(function* () {
|
|
962
|
+
const client = yield* HttpClient.HttpClient
|
|
963
|
+
const response = yield* client.get(
|
|
964
|
+
"https://jsonplaceholder.typicode.com/non-existing-page"
|
|
965
|
+
)
|
|
966
|
+
return yield* response.text
|
|
967
|
+
}).pipe(Effect.scoped, Effect.provide(FetchHttpClient.layer))
|
|
993
968
|
|
|
994
|
-
|
|
969
|
+
getText.pipe(Effect.andThen(Console.log), NodeRuntime.runMain)
|
|
995
970
|
/*
|
|
996
971
|
Output:
|
|
997
972
|
{}
|
|
@@ -1001,19 +976,19 @@ Output:
|
|
|
1001
976
|
However, if we use `HttpClient.filterStatusOk`, an error is logged:
|
|
1002
977
|
|
|
1003
978
|
```ts
|
|
1004
|
-
import {
|
|
1005
|
-
HttpClient,
|
|
1006
|
-
HttpClientRequest,
|
|
1007
|
-
HttpClientResponse
|
|
1008
|
-
} from "@effect/platform"
|
|
979
|
+
import { FetchHttpClient, HttpClient } from "@effect/platform"
|
|
1009
980
|
import { NodeRuntime } from "@effect/platform-node"
|
|
1010
981
|
import { Console, Effect } from "effect"
|
|
1011
982
|
|
|
1012
|
-
const getText =
|
|
1013
|
-
|
|
1014
|
-
|
|
983
|
+
const getText = Effect.gen(function* () {
|
|
984
|
+
const client = (yield* HttpClient.HttpClient).pipe(HttpClient.filterStatusOk)
|
|
985
|
+
const response = yield* client.get(
|
|
986
|
+
"https://jsonplaceholder.typicode.com/non-existing-page"
|
|
987
|
+
)
|
|
988
|
+
return yield* response.text
|
|
989
|
+
}).pipe(Effect.scoped, Effect.provide(FetchHttpClient.layer))
|
|
1015
990
|
|
|
1016
|
-
|
|
991
|
+
getText.pipe(Effect.andThen(Console.log), NodeRuntime.runMain)
|
|
1017
992
|
/*
|
|
1018
993
|
Output:
|
|
1019
994
|
timestamp=... level=ERROR fiber=#0 cause="ResponseError: StatusCode error (404 GET https://jsonplaceholder.typicode.com/non-existing-page): non 2xx status code
|
|
@@ -1021,60 +996,36 @@ timestamp=... level=ERROR fiber=#0 cause="ResponseError: StatusCode error (404 G
|
|
|
1021
996
|
*/
|
|
1022
997
|
```
|
|
1023
998
|
|
|
1024
|
-
Note that you can use `HttpClient.fetchOk` as a shortcut for `HttpClient.filterStatusOk(HttpClient.fetch)`:
|
|
1025
|
-
|
|
1026
|
-
```ts
|
|
1027
|
-
const getText = HttpClientRequest.get(
|
|
1028
|
-
"https://jsonplaceholder.typicode.com/non-existing-page"
|
|
1029
|
-
).pipe(HttpClient.fetchOk, HttpClientResponse.text)
|
|
1030
|
-
```
|
|
1031
|
-
|
|
1032
|
-
You can also create your own status-based filters. In fact, `HttpClient.filterStatusOk` is just a shortcut for the following filter:
|
|
1033
|
-
|
|
1034
|
-
```ts
|
|
1035
|
-
const getText = HttpClientRequest.get(
|
|
1036
|
-
"https://jsonplaceholder.typicode.com/non-existing-page"
|
|
1037
|
-
).pipe(
|
|
1038
|
-
HttpClient.filterStatus(
|
|
1039
|
-
HttpClient.fetch,
|
|
1040
|
-
(status) => status >= 200 && status < 300
|
|
1041
|
-
),
|
|
1042
|
-
HttpClientResponse.text
|
|
1043
|
-
)
|
|
1044
|
-
|
|
1045
|
-
/*
|
|
1046
|
-
Output:
|
|
1047
|
-
timestamp=... level=ERROR fiber=#0 cause="ResponseError: StatusCode error (404 GET https://jsonplaceholder.typicode.com/non-existing-page): invalid status code
|
|
1048
|
-
... stack trace ...
|
|
1049
|
-
*/
|
|
1050
|
-
```
|
|
1051
|
-
|
|
1052
999
|
## POST
|
|
1053
1000
|
|
|
1054
|
-
To make a POST request, you can use the `HttpClientRequest.post` function provided by the `
|
|
1001
|
+
To make a POST request, you can use the `HttpClientRequest.post` function provided by the `HttpClientRequest` module. Here's an example of how to create and send a POST request:
|
|
1055
1002
|
|
|
1056
1003
|
```ts
|
|
1057
1004
|
import {
|
|
1005
|
+
FetchHttpClient,
|
|
1058
1006
|
HttpClient,
|
|
1059
|
-
HttpClientRequest
|
|
1060
|
-
HttpClientResponse
|
|
1007
|
+
HttpClientRequest
|
|
1061
1008
|
} from "@effect/platform"
|
|
1062
1009
|
import { NodeRuntime } from "@effect/platform-node"
|
|
1063
1010
|
import { Console, Effect } from "effect"
|
|
1064
1011
|
|
|
1065
|
-
const addPost =
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
)
|
|
1012
|
+
const addPost = Effect.gen(function* () {
|
|
1013
|
+
const client = yield* HttpClient.HttpClient
|
|
1014
|
+
return yield* HttpClientRequest.post(
|
|
1015
|
+
"https://jsonplaceholder.typicode.com/posts"
|
|
1016
|
+
).pipe(
|
|
1017
|
+
HttpClientRequest.bodyJson({
|
|
1018
|
+
title: "foo",
|
|
1019
|
+
body: "bar",
|
|
1020
|
+
userId: 1
|
|
1021
|
+
}),
|
|
1022
|
+
Effect.flatMap(client.execute),
|
|
1023
|
+
Effect.flatMap((res) => res.json),
|
|
1024
|
+
Effect.scoped
|
|
1025
|
+
)
|
|
1026
|
+
}).pipe(Effect.provide(FetchHttpClient.layer))
|
|
1076
1027
|
|
|
1077
|
-
|
|
1028
|
+
addPost.pipe(Effect.andThen(Console.log), NodeRuntime.runMain)
|
|
1078
1029
|
/*
|
|
1079
1030
|
Output:
|
|
1080
1031
|
{ title: 'foo', body: 'bar', userId: 1, id: 101 }
|
|
@@ -1087,29 +1038,33 @@ In the following example, we send the data as text:
|
|
|
1087
1038
|
|
|
1088
1039
|
```ts
|
|
1089
1040
|
import {
|
|
1041
|
+
FetchHttpClient,
|
|
1090
1042
|
HttpClient,
|
|
1091
|
-
HttpClientRequest
|
|
1092
|
-
HttpClientResponse
|
|
1043
|
+
HttpClientRequest
|
|
1093
1044
|
} from "@effect/platform"
|
|
1094
1045
|
import { NodeRuntime } from "@effect/platform-node"
|
|
1095
1046
|
import { Console, Effect } from "effect"
|
|
1096
1047
|
|
|
1097
|
-
const addPost =
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1048
|
+
const addPost = Effect.gen(function* () {
|
|
1049
|
+
const client = yield* HttpClient.HttpClient
|
|
1050
|
+
return yield* HttpClientRequest.post(
|
|
1051
|
+
"https://jsonplaceholder.typicode.com/posts"
|
|
1052
|
+
).pipe(
|
|
1053
|
+
HttpClientRequest.bodyText(
|
|
1054
|
+
JSON.stringify({
|
|
1055
|
+
title: "foo",
|
|
1056
|
+
body: "bar",
|
|
1057
|
+
userId: 1
|
|
1058
|
+
}),
|
|
1059
|
+
"application/json; charset=UTF-8"
|
|
1060
|
+
),
|
|
1061
|
+
client.execute,
|
|
1062
|
+
Effect.flatMap((res) => res.json),
|
|
1063
|
+
Effect.scoped
|
|
1064
|
+
)
|
|
1065
|
+
}).pipe(Effect.provide(FetchHttpClient.layer))
|
|
1111
1066
|
|
|
1112
|
-
|
|
1067
|
+
addPost.pipe(Effect.andThen(Console.log), NodeRuntime.runMain)
|
|
1113
1068
|
/*
|
|
1114
1069
|
Output:
|
|
1115
1070
|
{ title: 'foo', body: 'bar', userId: 1, id: 101 }
|
|
@@ -1122,6 +1077,7 @@ A common use case when fetching data is to validate the received format. For thi
|
|
|
1122
1077
|
|
|
1123
1078
|
```ts
|
|
1124
1079
|
import {
|
|
1080
|
+
FetchHttpClient,
|
|
1125
1081
|
HttpClient,
|
|
1126
1082
|
HttpClientRequest,
|
|
1127
1083
|
HttpClientResponse
|
|
@@ -1135,20 +1091,26 @@ const Post = Schema.Struct({
|
|
|
1135
1091
|
title: Schema.String
|
|
1136
1092
|
})
|
|
1137
1093
|
|
|
1138
|
-
const addPost =
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1094
|
+
const addPost = Effect.gen(function* () {
|
|
1095
|
+
const client = yield* HttpClient.HttpClient
|
|
1096
|
+
return yield* HttpClientRequest.post(
|
|
1097
|
+
"https://jsonplaceholder.typicode.com/posts"
|
|
1098
|
+
).pipe(
|
|
1099
|
+
HttpClientRequest.bodyText(
|
|
1100
|
+
JSON.stringify({
|
|
1101
|
+
title: "foo",
|
|
1102
|
+
body: "bar",
|
|
1103
|
+
userId: 1
|
|
1104
|
+
}),
|
|
1105
|
+
"application/json; charset=UTF-8"
|
|
1106
|
+
),
|
|
1107
|
+
client.execute,
|
|
1108
|
+
Effect.flatMap(HttpClientResponse.schemaBodyJson(Post)),
|
|
1109
|
+
Effect.scoped
|
|
1110
|
+
)
|
|
1111
|
+
}).pipe(Effect.provide(FetchHttpClient.layer))
|
|
1150
1112
|
|
|
1151
|
-
|
|
1113
|
+
addPost.pipe(Effect.andThen(Console.log), NodeRuntime.runMain)
|
|
1152
1114
|
/*
|
|
1153
1115
|
Output:
|
|
1154
1116
|
{ id: 101, title: 'foo' }
|
|
@@ -1162,30 +1124,31 @@ Output:
|
|
|
1162
1124
|
To test HTTP requests, you can inject a mock fetch implementation.
|
|
1163
1125
|
|
|
1164
1126
|
```ts
|
|
1165
|
-
import {
|
|
1166
|
-
HttpClient,
|
|
1167
|
-
HttpClientRequest,
|
|
1168
|
-
HttpClientResponse
|
|
1169
|
-
} from "@effect/platform"
|
|
1127
|
+
import { FetchHttpClient, HttpClient } from "@effect/platform"
|
|
1170
1128
|
import { Effect, Layer } from "effect"
|
|
1171
1129
|
import * as assert from "node:assert"
|
|
1172
1130
|
|
|
1173
1131
|
// Mock fetch implementation
|
|
1174
|
-
const FetchTest = Layer.succeed(
|
|
1132
|
+
const FetchTest = Layer.succeed(FetchHttpClient.Fetch, () =>
|
|
1175
1133
|
Promise.resolve(new Response("not found", { status: 404 }))
|
|
1176
1134
|
)
|
|
1177
1135
|
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1136
|
+
const TestLayer = FetchHttpClient.layer.pipe(Layer.provide(FetchTest))
|
|
1137
|
+
|
|
1138
|
+
const program = Effect.gen(function* () {
|
|
1139
|
+
const client = yield* HttpClient.HttpClient
|
|
1140
|
+
|
|
1141
|
+
return yield* client.get("https://www.google.com/").pipe(
|
|
1142
|
+
Effect.flatMap((res) => res.text),
|
|
1143
|
+
Effect.scoped
|
|
1144
|
+
)
|
|
1145
|
+
})
|
|
1183
1146
|
|
|
1184
1147
|
// Test
|
|
1185
1148
|
Effect.gen(function* () {
|
|
1186
1149
|
const response = yield* program
|
|
1187
1150
|
assert.equal(response, "not found")
|
|
1188
|
-
}).pipe(Effect.provide(
|
|
1151
|
+
}).pipe(Effect.provide(TestLayer), Effect.runPromise)
|
|
1189
1152
|
```
|
|
1190
1153
|
|
|
1191
1154
|
# HTTP Server
|
|
@@ -2345,402 +2308,3 @@ const handler = HttpApp.toWebHandler(router)
|
|
|
2345
2308
|
const response = await handler(new Request("http://localhost:3000/foo"))
|
|
2346
2309
|
console.log(await response.text()) // Output: content 2
|
|
2347
2310
|
```
|
|
2348
|
-
|
|
2349
|
-
# Terminal
|
|
2350
|
-
|
|
2351
|
-
The `@effect/platform/Terminal` module exports a single `Terminal` tag, which serves as the entry point to reading from and writing to standard input and standard output.
|
|
2352
|
-
|
|
2353
|
-
## Writing to standard output
|
|
2354
|
-
|
|
2355
|
-
```ts
|
|
2356
|
-
import { Terminal } from "@effect/platform"
|
|
2357
|
-
import { NodeRuntime, NodeTerminal } from "@effect/platform-node"
|
|
2358
|
-
import { Effect } from "effect"
|
|
2359
|
-
|
|
2360
|
-
// const displayMessage: Effect.Effect<void, PlatformError, Terminal.Terminal>
|
|
2361
|
-
const displayMessage = Effect.gen(function* (_) {
|
|
2362
|
-
const terminal = yield* _(Terminal.Terminal)
|
|
2363
|
-
yield* _(terminal.display("a message\n"))
|
|
2364
|
-
})
|
|
2365
|
-
|
|
2366
|
-
NodeRuntime.runMain(displayMessage.pipe(Effect.provide(NodeTerminal.layer)))
|
|
2367
|
-
// Output: "a message"
|
|
2368
|
-
```
|
|
2369
|
-
|
|
2370
|
-
## Reading from standard input
|
|
2371
|
-
|
|
2372
|
-
```ts
|
|
2373
|
-
import { Terminal } from "@effect/platform"
|
|
2374
|
-
import { NodeRuntime, NodeTerminal } from "@effect/platform-node"
|
|
2375
|
-
import { Console, Effect } from "effect"
|
|
2376
|
-
|
|
2377
|
-
// const readLine: Effect.Effect<void, Terminal.QuitException, Terminal.Terminal>
|
|
2378
|
-
const readLine = Effect.gen(function* (_) {
|
|
2379
|
-
const terminal = yield* _(Terminal.Terminal)
|
|
2380
|
-
const input = yield* _(terminal.readLine)
|
|
2381
|
-
yield* _(Console.log(`input: ${input}`))
|
|
2382
|
-
})
|
|
2383
|
-
|
|
2384
|
-
NodeRuntime.runMain(readLine.pipe(Effect.provide(NodeTerminal.layer)))
|
|
2385
|
-
// Input: "hello"
|
|
2386
|
-
// Output: "input: hello"
|
|
2387
|
-
```
|
|
2388
|
-
|
|
2389
|
-
These simple examples illustrate how to utilize the `Terminal` module for handling standard input and output in your programs. Let's use this knowledge to build a number guessing game:
|
|
2390
|
-
|
|
2391
|
-
```ts
|
|
2392
|
-
import { Terminal } from "@effect/platform"
|
|
2393
|
-
import type { PlatformError } from "@effect/platform/Error"
|
|
2394
|
-
import { Effect, Option, Random } from "effect"
|
|
2395
|
-
|
|
2396
|
-
export const secret = Random.nextIntBetween(1, 100)
|
|
2397
|
-
|
|
2398
|
-
const parseGuess = (input: string) => {
|
|
2399
|
-
const n = parseInt(input, 10)
|
|
2400
|
-
return isNaN(n) || n < 1 || n > 100 ? Option.none() : Option.some(n)
|
|
2401
|
-
}
|
|
2402
|
-
|
|
2403
|
-
const display = (message: string) =>
|
|
2404
|
-
Effect.gen(function* (_) {
|
|
2405
|
-
const terminal = yield* _(Terminal.Terminal)
|
|
2406
|
-
yield* _(terminal.display(`${message}\n`))
|
|
2407
|
-
})
|
|
2408
|
-
|
|
2409
|
-
const prompt = Effect.gen(function* (_) {
|
|
2410
|
-
const terminal = yield* _(Terminal.Terminal)
|
|
2411
|
-
yield* _(terminal.display("Enter a guess: "))
|
|
2412
|
-
return yield* _(terminal.readLine)
|
|
2413
|
-
})
|
|
2414
|
-
|
|
2415
|
-
const answer: Effect.Effect<
|
|
2416
|
-
number,
|
|
2417
|
-
Terminal.QuitException | PlatformError,
|
|
2418
|
-
Terminal.Terminal
|
|
2419
|
-
> = Effect.gen(function* (_) {
|
|
2420
|
-
const input = yield* _(prompt)
|
|
2421
|
-
const guess = parseGuess(input)
|
|
2422
|
-
if (Option.isNone(guess)) {
|
|
2423
|
-
yield* _(display("You must enter an integer from 1 to 100"))
|
|
2424
|
-
return yield* _(answer)
|
|
2425
|
-
}
|
|
2426
|
-
return guess.value
|
|
2427
|
-
})
|
|
2428
|
-
|
|
2429
|
-
const check = <A, E, R>(
|
|
2430
|
-
secret: number,
|
|
2431
|
-
guess: number,
|
|
2432
|
-
ok: Effect.Effect<A, E, R>,
|
|
2433
|
-
ko: Effect.Effect<A, E, R>
|
|
2434
|
-
): Effect.Effect<A, E | PlatformError, R | Terminal.Terminal> =>
|
|
2435
|
-
Effect.gen(function* (_) {
|
|
2436
|
-
if (guess > secret) {
|
|
2437
|
-
yield* _(display("Too high"))
|
|
2438
|
-
return yield* _(ko)
|
|
2439
|
-
} else if (guess < secret) {
|
|
2440
|
-
yield* _(display("Too low"))
|
|
2441
|
-
return yield* _(ko)
|
|
2442
|
-
} else {
|
|
2443
|
-
return yield* _(ok)
|
|
2444
|
-
}
|
|
2445
|
-
})
|
|
2446
|
-
|
|
2447
|
-
const end = display("You guessed it!")
|
|
2448
|
-
|
|
2449
|
-
const loop = (
|
|
2450
|
-
secret: number
|
|
2451
|
-
): Effect.Effect<
|
|
2452
|
-
void,
|
|
2453
|
-
Terminal.QuitException | PlatformError,
|
|
2454
|
-
Terminal.Terminal
|
|
2455
|
-
> =>
|
|
2456
|
-
Effect.gen(function* (_) {
|
|
2457
|
-
const guess = yield* _(answer)
|
|
2458
|
-
return yield* _(
|
|
2459
|
-
check(
|
|
2460
|
-
secret,
|
|
2461
|
-
guess,
|
|
2462
|
-
end,
|
|
2463
|
-
Effect.suspend(() => loop(secret))
|
|
2464
|
-
)
|
|
2465
|
-
)
|
|
2466
|
-
})
|
|
2467
|
-
|
|
2468
|
-
export const game = Effect.gen(function* (_) {
|
|
2469
|
-
yield* _(
|
|
2470
|
-
display(
|
|
2471
|
-
"We have selected a random number between 1 and 100. See if you can guess it in 10 turns or fewer. We'll tell you if your guess was too high or too low."
|
|
2472
|
-
)
|
|
2473
|
-
)
|
|
2474
|
-
yield* _(loop(yield* _(secret)))
|
|
2475
|
-
})
|
|
2476
|
-
```
|
|
2477
|
-
|
|
2478
|
-
Let's run the game in Node.js:
|
|
2479
|
-
|
|
2480
|
-
```ts
|
|
2481
|
-
import { NodeRuntime, NodeTerminal } from "@effect/platform-node"
|
|
2482
|
-
import * as Effect from "effect/Effect"
|
|
2483
|
-
import { game } from "./game.js"
|
|
2484
|
-
|
|
2485
|
-
NodeRuntime.runMain(game.pipe(Effect.provide(NodeTerminal.layer)))
|
|
2486
|
-
```
|
|
2487
|
-
|
|
2488
|
-
Let's run the game in Bun:
|
|
2489
|
-
|
|
2490
|
-
```ts
|
|
2491
|
-
import { BunRuntime, BunTerminal } from "@effect/platform-bun"
|
|
2492
|
-
import * as Effect from "effect/Effect"
|
|
2493
|
-
import { game } from "./game.js"
|
|
2494
|
-
|
|
2495
|
-
BunRuntime.runMain(game.pipe(Effect.provide(BunTerminal.layer)))
|
|
2496
|
-
```
|
|
2497
|
-
|
|
2498
|
-
# Command
|
|
2499
|
-
|
|
2500
|
-
As an example of using the `@effect/platform/Command` module, let's see how to run the TypeScript compiler `tsc`:
|
|
2501
|
-
|
|
2502
|
-
```ts
|
|
2503
|
-
import { Command, CommandExecutor } from "@effect/platform"
|
|
2504
|
-
import {
|
|
2505
|
-
NodeCommandExecutor,
|
|
2506
|
-
NodeFileSystem,
|
|
2507
|
-
NodeRuntime
|
|
2508
|
-
} from "@effect/platform-node"
|
|
2509
|
-
import { Effect } from "effect"
|
|
2510
|
-
|
|
2511
|
-
// const program: Effect.Effect<string, PlatformError, CommandExecutor.CommandExecutor>
|
|
2512
|
-
const program = Effect.gen(function* (_) {
|
|
2513
|
-
const executor = yield* _(CommandExecutor.CommandExecutor)
|
|
2514
|
-
|
|
2515
|
-
// Creating a command to run the TypeScript compiler
|
|
2516
|
-
const command = Command.make("tsc", "--noEmit")
|
|
2517
|
-
console.log("Running tsc...")
|
|
2518
|
-
|
|
2519
|
-
// Executing the command and capturing the output
|
|
2520
|
-
const output = yield* _(executor.string(command))
|
|
2521
|
-
console.log(output)
|
|
2522
|
-
return output
|
|
2523
|
-
})
|
|
2524
|
-
|
|
2525
|
-
// Running the program with the necessary runtime and executor layers
|
|
2526
|
-
NodeRuntime.runMain(
|
|
2527
|
-
program.pipe(
|
|
2528
|
-
Effect.provide(NodeCommandExecutor.layer),
|
|
2529
|
-
Effect.provide(NodeFileSystem.layer)
|
|
2530
|
-
)
|
|
2531
|
-
)
|
|
2532
|
-
```
|
|
2533
|
-
|
|
2534
|
-
## Obtaining Information About the Running Process
|
|
2535
|
-
|
|
2536
|
-
Here, we'll explore how to retrieve information about a running process.
|
|
2537
|
-
|
|
2538
|
-
```ts
|
|
2539
|
-
import { Command, CommandExecutor } from "@effect/platform"
|
|
2540
|
-
import {
|
|
2541
|
-
NodeCommandExecutor,
|
|
2542
|
-
NodeFileSystem,
|
|
2543
|
-
NodeRuntime
|
|
2544
|
-
} from "@effect/platform-node"
|
|
2545
|
-
import { Effect, Stream, String } from "effect"
|
|
2546
|
-
|
|
2547
|
-
const runString = <E, R>(
|
|
2548
|
-
stream: Stream.Stream<Uint8Array, E, R>
|
|
2549
|
-
): Effect.Effect<string, E, R> =>
|
|
2550
|
-
stream.pipe(Stream.decodeText(), Stream.runFold(String.empty, String.concat))
|
|
2551
|
-
|
|
2552
|
-
const program = Effect.gen(function* (_) {
|
|
2553
|
-
const executor = yield* _(CommandExecutor.CommandExecutor)
|
|
2554
|
-
|
|
2555
|
-
const command = Command.make("ls")
|
|
2556
|
-
|
|
2557
|
-
const [exitCode, stdout, stderr] = yield* _(
|
|
2558
|
-
// Start running the command and return a handle to the running process.
|
|
2559
|
-
executor.start(command),
|
|
2560
|
-
Effect.flatMap((process) =>
|
|
2561
|
-
Effect.all(
|
|
2562
|
-
[
|
|
2563
|
-
// Waits for the process to exit and returns the ExitCode of the command that was run.
|
|
2564
|
-
process.exitCode,
|
|
2565
|
-
// The standard output stream of the process.
|
|
2566
|
-
runString(process.stdout),
|
|
2567
|
-
// The standard error stream of the process.
|
|
2568
|
-
runString(process.stderr)
|
|
2569
|
-
],
|
|
2570
|
-
{ concurrency: 3 }
|
|
2571
|
-
)
|
|
2572
|
-
)
|
|
2573
|
-
)
|
|
2574
|
-
console.log({ exitCode, stdout, stderr })
|
|
2575
|
-
})
|
|
2576
|
-
|
|
2577
|
-
NodeRuntime.runMain(
|
|
2578
|
-
Effect.scoped(program).pipe(
|
|
2579
|
-
Effect.provide(NodeCommandExecutor.layer),
|
|
2580
|
-
Effect.provide(NodeFileSystem.layer)
|
|
2581
|
-
)
|
|
2582
|
-
)
|
|
2583
|
-
```
|
|
2584
|
-
|
|
2585
|
-
## Running a Platform Command with stdout Streamed to process.stdout
|
|
2586
|
-
|
|
2587
|
-
To run a command (for example `cat`) and stream its `stdout` to `process.stdout` follow these steps:
|
|
2588
|
-
|
|
2589
|
-
```ts
|
|
2590
|
-
import { Command } from "@effect/platform"
|
|
2591
|
-
import { NodeContext, NodeRuntime } from "@effect/platform-node"
|
|
2592
|
-
import { Effect } from "effect"
|
|
2593
|
-
|
|
2594
|
-
// Create a command to run `cat` on a file and inherit stdout
|
|
2595
|
-
const program = Command.make("cat", "./some-file.txt").pipe(
|
|
2596
|
-
Command.stdout("inherit"),
|
|
2597
|
-
Command.exitCode
|
|
2598
|
-
)
|
|
2599
|
-
|
|
2600
|
-
// Run the command using NodeRuntime with the NodeContext layer
|
|
2601
|
-
NodeRuntime.runMain(program.pipe(Effect.provide(NodeContext.layer)))
|
|
2602
|
-
```
|
|
2603
|
-
|
|
2604
|
-
# FileSystem
|
|
2605
|
-
|
|
2606
|
-
The `@effect/platform/FileSystem` module provides a single `FileSystem` tag, which acts as the gateway for interacting with the filesystem.
|
|
2607
|
-
|
|
2608
|
-
Here's a list of operations that can be performed using the `FileSystem` tag:
|
|
2609
|
-
|
|
2610
|
-
| **Name** | **Arguments** | **Return** | **Description** |
|
|
2611
|
-
| --------------------------- | ---------------------------------------------------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2612
|
-
| **access** | `path: string`, `options?: AccessFileOptions` | `Effect<void, PlatformError>` | Check if a file can be accessed. You can optionally specify the level of access to check for. |
|
|
2613
|
-
| **copy** | `fromPath: string`, `toPath: string`, `options?: CopyOptions` | `Effect<void, PlatformError>` | Copy a file or directory from `fromPath` to `toPath`. Equivalent to `cp -r`. |
|
|
2614
|
-
| **copyFile** | `fromPath: string`, `toPath: string` | `Effect<void, PlatformError>` | Copy a file from `fromPath` to `toPath`. |
|
|
2615
|
-
| **chmod** | `path: string`, `mode: number` | `Effect<void, PlatformError>` | Change the permissions of a file. |
|
|
2616
|
-
| **chown** | `path: string`, `uid: number`, `gid: number` | `Effect<void, PlatformError>` | Change the owner and group of a file. |
|
|
2617
|
-
| **exists** | `path: string` | `Effect<boolean, PlatformError>` | Check if a path exists. |
|
|
2618
|
-
| **link** | `fromPath: string`, `toPath: string` | `Effect<void, PlatformError>` | Create a hard link from `fromPath` to `toPath`. |
|
|
2619
|
-
| **makeDirectory** | `path: string`, `options?: MakeDirectoryOptions` | `Effect<void, PlatformError>` | Create a directory at `path`. You can optionally specify the mode and whether to recursively create nested directories. |
|
|
2620
|
-
| **makeTempDirectory** | `options?: MakeTempDirectoryOptions` | `Effect<string, PlatformError>` | Create a temporary directory. By default, the directory will be created inside the system's default temporary directory. |
|
|
2621
|
-
| **makeTempDirectoryScoped** | `options?: MakeTempDirectoryOptions` | `Effect<string, PlatformError, Scope>` | Create a temporary directory inside a scope. Functionally equivalent to `makeTempDirectory`, but the directory will be automatically deleted when the scope is closed. |
|
|
2622
|
-
| **makeTempFile** | `options?: MakeTempFileOptions` | `Effect<string, PlatformError>` | Create a temporary file. The directory creation is functionally equivalent to `makeTempDirectory`. The file name will be a randomly generated string. |
|
|
2623
|
-
| **makeTempFileScoped** | `options?: MakeTempFileOptions` | `Effect<string, PlatformError, Scope>` | Create a temporary file inside a scope. Functionally equivalent to `makeTempFile`, but the file will be automatically deleted when the scope is closed. |
|
|
2624
|
-
| **open** | `path: string`, `options?: OpenFileOptions` | `Effect<File, PlatformError, Scope>` | Open a file at `path` with the specified `options`. The file handle will be automatically closed when the scope is closed. |
|
|
2625
|
-
| **readDirectory** | `path: string`, `options?: ReadDirectoryOptions` | `Effect<Array<string>, PlatformError>` | List the contents of a directory. You can recursively list the contents of nested directories by setting the `recursive` option. |
|
|
2626
|
-
| **readFile** | `path: string` | `Effect<Uint8Array, PlatformError>` | Read the contents of a file. |
|
|
2627
|
-
| **readFileString** | `path: string`, `encoding?: string` | `Effect<string, PlatformError>` | Read the contents of a file as a string. |
|
|
2628
|
-
| **readLink** | `path: string` | `Effect<string, PlatformError>` | Read the destination of a symbolic link. |
|
|
2629
|
-
| **realPath** | `path: string` | `Effect<string, PlatformError>` | Resolve a path to its canonicalized absolute pathname. |
|
|
2630
|
-
| **remove** | `path: string`, `options?: RemoveOptions` | `Effect<void, PlatformError>` | Remove a file or directory. By setting the `recursive` option to `true`, you can recursively remove nested directories. |
|
|
2631
|
-
| **rename** | `oldPath: string`, `newPath: string` | `Effect<void, PlatformError>` | Rename a file or directory. |
|
|
2632
|
-
| **sink** | `path: string`, `options?: SinkOptions` | `Sink<void, Uint8Array, never, PlatformError>` | Create a writable `Sink` for the specified `path`. |
|
|
2633
|
-
| **stat** | `path: string` | `Effect<File.Info, PlatformError>` | Get information about a file at `path`. |
|
|
2634
|
-
| **stream** | `path: string`, `options?: StreamOptions` | `Stream<Uint8Array, PlatformError>` | Create a readable `Stream` for the specified `path`. |
|
|
2635
|
-
| **symlink** | `fromPath: string`, `toPath: string` | `Effect<void, PlatformError>` | Create a symbolic link from `fromPath` to `toPath`. |
|
|
2636
|
-
| **truncate** | `path: string`, `length?: SizeInput` | `Effect<void, PlatformError>` | Truncate a file to a specified length. If the `length` is not specified, the file will be truncated to length `0`. |
|
|
2637
|
-
| **utimes** | `path: string`, `atime: Date \| number`, `mtime: Date \| number` | `Effect<void, PlatformError>` | Change the file system timestamps of the file at `path`. |
|
|
2638
|
-
| **watch** | `path: string` | `Stream<WatchEvent, PlatformError>` | Watch a directory or file for changes. |
|
|
2639
|
-
|
|
2640
|
-
Let's explore a simple example using `readFileString`:
|
|
2641
|
-
|
|
2642
|
-
```ts
|
|
2643
|
-
import { FileSystem } from "@effect/platform"
|
|
2644
|
-
import { NodeFileSystem, NodeRuntime } from "@effect/platform-node"
|
|
2645
|
-
import { Effect } from "effect"
|
|
2646
|
-
|
|
2647
|
-
// const readFileString: Effect.Effect<void, PlatformError, FileSystem.FileSystem>
|
|
2648
|
-
const readFileString = Effect.gen(function* (_) {
|
|
2649
|
-
const fs = yield* _(FileSystem.FileSystem)
|
|
2650
|
-
|
|
2651
|
-
// Reading the content of the same file where this code is written
|
|
2652
|
-
const content = yield* _(fs.readFileString("./index.ts", "utf8"))
|
|
2653
|
-
console.log(content)
|
|
2654
|
-
})
|
|
2655
|
-
|
|
2656
|
-
NodeRuntime.runMain(readFileString.pipe(Effect.provide(NodeFileSystem.layer)))
|
|
2657
|
-
```
|
|
2658
|
-
|
|
2659
|
-
# KeyValueStore
|
|
2660
|
-
|
|
2661
|
-
## Overview
|
|
2662
|
-
|
|
2663
|
-
The `KeyValueStore` module provides a robust and effectful interface for managing key-value pairs. It supports asynchronous operations, ensuring data integrity and consistency, and includes built-in implementations for in-memory, file system-based, and schema-validated stores.
|
|
2664
|
-
|
|
2665
|
-
## Basic Usage
|
|
2666
|
-
|
|
2667
|
-
The `KeyValueStore` interface includes the following operations:
|
|
2668
|
-
|
|
2669
|
-
- **get**: Retrieve a value by key.
|
|
2670
|
-
- **set**: Store a key-value pair.
|
|
2671
|
-
- **remove**: Delete a key-value pair.
|
|
2672
|
-
- **clear**: Remove all key-value pairs.
|
|
2673
|
-
- **size**: Get the number of stored pairs.
|
|
2674
|
-
- **modify**: Atomically modify a value.
|
|
2675
|
-
- **has**: Check if a key exists.
|
|
2676
|
-
- **isEmpty**: Check if the store is empty.
|
|
2677
|
-
|
|
2678
|
-
**Example**
|
|
2679
|
-
|
|
2680
|
-
```ts
|
|
2681
|
-
import { KeyValueStore, layerMemory } from "@effect/platform/KeyValueStore"
|
|
2682
|
-
import { Effect } from "effect"
|
|
2683
|
-
|
|
2684
|
-
const program = Effect.gen(function* () {
|
|
2685
|
-
const store = yield* KeyValueStore
|
|
2686
|
-
console.log(yield* store.size) // Outputs: 0
|
|
2687
|
-
|
|
2688
|
-
yield* store.set("key", "value")
|
|
2689
|
-
console.log(yield* store.size) // Outputs: 1
|
|
2690
|
-
|
|
2691
|
-
const value = yield* store.get("key")
|
|
2692
|
-
console.log(value) // Outputs: { _id: 'Option', _tag: 'Some', value: 'value' }
|
|
2693
|
-
|
|
2694
|
-
yield* store.remove("key")
|
|
2695
|
-
console.log(yield* store.size) // Outputs: 0
|
|
2696
|
-
})
|
|
2697
|
-
|
|
2698
|
-
Effect.runPromise(program.pipe(Effect.provide(layerMemory)))
|
|
2699
|
-
```
|
|
2700
|
-
|
|
2701
|
-
## Built-in Implementations
|
|
2702
|
-
|
|
2703
|
-
The module provides several built-in implementations to suit different needs:
|
|
2704
|
-
|
|
2705
|
-
- **In-Memory Store**: `layerMemory` provides a simple, in-memory key-value store, ideal for lightweight or testing scenarios.
|
|
2706
|
-
- **File System Store**: `layerFileSystem` offers a file-based store for persistent storage needs.
|
|
2707
|
-
- **Schema Store**: `layerSchema` enables schema-based validation for stored values, ensuring data integrity and type safety.
|
|
2708
|
-
|
|
2709
|
-
## Schema Store
|
|
2710
|
-
|
|
2711
|
-
The `SchemaStore` implementation allows you to validate and parse values according to a defined schema. This ensures that all data stored in the key-value store adheres to the specified structure, enhancing data integrity and type safety.
|
|
2712
|
-
|
|
2713
|
-
**Example**
|
|
2714
|
-
|
|
2715
|
-
```ts
|
|
2716
|
-
import { KeyValueStore, layerMemory } from "@effect/platform/KeyValueStore"
|
|
2717
|
-
import { Schema } from "@effect/schema"
|
|
2718
|
-
import { Effect } from "effect"
|
|
2719
|
-
|
|
2720
|
-
// Define a schema for the values
|
|
2721
|
-
const Person = Schema.Struct({
|
|
2722
|
-
name: Schema.String,
|
|
2723
|
-
age: Schema.Number
|
|
2724
|
-
})
|
|
2725
|
-
|
|
2726
|
-
const program = Effect.gen(function* () {
|
|
2727
|
-
const store = (yield* KeyValueStore).forSchema(Person)
|
|
2728
|
-
|
|
2729
|
-
// Create a value that adheres to the schema
|
|
2730
|
-
const value = { name: "Alice", age: 30 }
|
|
2731
|
-
yield* store.set("user1", value)
|
|
2732
|
-
console.log(yield* store.size) // Outputs: 1
|
|
2733
|
-
|
|
2734
|
-
// Retrieve and validate the value
|
|
2735
|
-
const retrievedValue = yield* store.get("user1")
|
|
2736
|
-
console.log(retrievedValue) // Outputs: { _id: 'Option', _tag: 'Some', value: { name: 'Alice', age: 30 } }
|
|
2737
|
-
})
|
|
2738
|
-
|
|
2739
|
-
Effect.runPromise(program.pipe(Effect.provide(layerMemory)))
|
|
2740
|
-
```
|
|
2741
|
-
|
|
2742
|
-
In this example:
|
|
2743
|
-
|
|
2744
|
-
- **Person**: Defines the structure for the values stored in the key-value store.
|
|
2745
|
-
- **store.set**: Stores a value adhering to `Person`.
|
|
2746
|
-
- **store.get**: Retrieves and validates the stored value against `Person`.
|