@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.
Files changed (103) hide show
  1. package/FetchHttpClient/package.json +6 -0
  2. package/README.md +175 -611
  3. package/dist/cjs/Command.js +3 -3
  4. package/dist/cjs/Command.js.map +1 -1
  5. package/dist/cjs/FetchHttpClient.js +32 -0
  6. package/dist/cjs/FetchHttpClient.js.map +1 -0
  7. package/dist/cjs/HttpApiClient.js +1 -1
  8. package/dist/cjs/HttpApiClient.js.map +1 -1
  9. package/dist/cjs/HttpClient.js +5 -36
  10. package/dist/cjs/HttpClient.js.map +1 -1
  11. package/dist/cjs/HttpClientRequest.js +11 -11
  12. package/dist/cjs/HttpClientRequest.js.map +1 -1
  13. package/dist/cjs/HttpClientResponse.js +2 -61
  14. package/dist/cjs/HttpClientResponse.js.map +1 -1
  15. package/dist/cjs/HttpIncomingMessage.js +2 -29
  16. package/dist/cjs/HttpIncomingMessage.js.map +1 -1
  17. package/dist/cjs/Runtime.js +1 -1
  18. package/dist/cjs/Runtime.js.map +1 -1
  19. package/dist/cjs/Socket.js +42 -53
  20. package/dist/cjs/Socket.js.map +1 -1
  21. package/dist/cjs/index.js +3 -1
  22. package/dist/cjs/internal/command.js +2 -2
  23. package/dist/cjs/internal/command.js.map +1 -1
  24. package/dist/cjs/internal/fetchHttpClient.js +52 -0
  25. package/dist/cjs/internal/fetchHttpClient.js.map +1 -0
  26. package/dist/cjs/internal/httpClient.js +82 -71
  27. package/dist/cjs/internal/httpClient.js.map +1 -1
  28. package/dist/cjs/internal/httpClientRequest.js +13 -13
  29. package/dist/cjs/internal/httpClientRequest.js.map +1 -1
  30. package/dist/cjs/internal/httpClientResponse.js +1 -33
  31. package/dist/cjs/internal/httpClientResponse.js.map +1 -1
  32. package/dist/cjs/internal/worker.js +21 -29
  33. package/dist/cjs/internal/worker.js.map +1 -1
  34. package/dist/dts/Command.d.ts +4 -4
  35. package/dist/dts/Command.d.ts.map +1 -1
  36. package/dist/dts/FetchHttpClient.d.ts +27 -0
  37. package/dist/dts/FetchHttpClient.d.ts.map +1 -0
  38. package/dist/dts/HttpApiClient.d.ts +2 -2
  39. package/dist/dts/HttpClient.d.ts +53 -61
  40. package/dist/dts/HttpClient.d.ts.map +1 -1
  41. package/dist/dts/HttpClientRequest.d.ts +11 -11
  42. package/dist/dts/HttpClientRequest.d.ts.map +1 -1
  43. package/dist/dts/HttpClientResponse.d.ts +4 -90
  44. package/dist/dts/HttpClientResponse.d.ts.map +1 -1
  45. package/dist/dts/HttpIncomingMessage.d.ts +0 -16
  46. package/dist/dts/HttpIncomingMessage.d.ts.map +1 -1
  47. package/dist/dts/HttpServer.d.ts +1 -1
  48. package/dist/dts/Socket.d.ts +3 -3
  49. package/dist/dts/Socket.d.ts.map +1 -1
  50. package/dist/dts/index.d.ts +4 -0
  51. package/dist/dts/index.d.ts.map +1 -1
  52. package/dist/dts/internal/fetchHttpClient.d.ts +2 -0
  53. package/dist/dts/internal/fetchHttpClient.d.ts.map +1 -0
  54. package/dist/esm/Command.js +3 -3
  55. package/dist/esm/Command.js.map +1 -1
  56. package/dist/esm/FetchHttpClient.js +21 -0
  57. package/dist/esm/FetchHttpClient.js.map +1 -0
  58. package/dist/esm/HttpApiClient.js +1 -1
  59. package/dist/esm/HttpApiClient.js.map +1 -1
  60. package/dist/esm/HttpClient.js +4 -35
  61. package/dist/esm/HttpClient.js.map +1 -1
  62. package/dist/esm/HttpClientRequest.js +10 -10
  63. package/dist/esm/HttpClientRequest.js.map +1 -1
  64. package/dist/esm/HttpClientResponse.js +1 -63
  65. package/dist/esm/HttpClientResponse.js.map +1 -1
  66. package/dist/esm/HttpIncomingMessage.js +0 -24
  67. package/dist/esm/HttpIncomingMessage.js.map +1 -1
  68. package/dist/esm/Runtime.js +1 -1
  69. package/dist/esm/Runtime.js.map +1 -1
  70. package/dist/esm/Socket.js +42 -53
  71. package/dist/esm/Socket.js.map +1 -1
  72. package/dist/esm/index.js +4 -0
  73. package/dist/esm/index.js.map +1 -1
  74. package/dist/esm/internal/command.js +2 -2
  75. package/dist/esm/internal/command.js.map +1 -1
  76. package/dist/esm/internal/fetchHttpClient.js +44 -0
  77. package/dist/esm/internal/fetchHttpClient.js.map +1 -0
  78. package/dist/esm/internal/httpClient.js +79 -69
  79. package/dist/esm/internal/httpClient.js.map +1 -1
  80. package/dist/esm/internal/httpClientRequest.js +11 -11
  81. package/dist/esm/internal/httpClientRequest.js.map +1 -1
  82. package/dist/esm/internal/httpClientResponse.js +0 -24
  83. package/dist/esm/internal/httpClientResponse.js.map +1 -1
  84. package/dist/esm/internal/worker.js +21 -29
  85. package/dist/esm/internal/worker.js.map +1 -1
  86. package/package.json +11 -3
  87. package/src/Command.ts +7 -8
  88. package/src/FetchHttpClient.ts +25 -0
  89. package/src/HttpApiClient.ts +5 -5
  90. package/src/HttpClient.ts +79 -85
  91. package/src/HttpClientRequest.ts +21 -21
  92. package/src/HttpClientResponse.ts +4 -162
  93. package/src/HttpIncomingMessage.ts +0 -43
  94. package/src/HttpServer.ts +1 -1
  95. package/src/Runtime.ts +1 -1
  96. package/src/Socket.ts +42 -58
  97. package/src/index.ts +5 -0
  98. package/src/internal/command.ts +4 -3
  99. package/src/internal/fetchHttpClient.ts +53 -0
  100. package/src/internal/httpClient.ts +149 -125
  101. package/src/internal/httpClientRequest.ts +12 -12
  102. package/src/internal/httpClientResponse.ts +6 -96
  103. 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
- With `@effect/platform`, you can incorporate abstract services like `Terminal` or `FileSystem` into your program. Later, during the assembly of the final application, you can provide specific layers for the target platform using the corresponding packages: `platform-node`, `platform-bun`, and `platform-browser`.
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
- An `HttpClient` is a function that takes a request and produces a certain value `A` in an effectful way (possibly resulting in an error `E` and depending on some requirement `R`).
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
- Generally, you'll deal with a specialization called `Default` where `A`, `E`, and `R` are predefined:
570
+ The `HttpClient` interface has a set of methods for sending requests:
589
571
 
590
- ```ts
591
- type Default = (request: HttpClientRequest): Effect<HttpClientResponse, RequestError | ResponseError, Scope>
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
- The goal of `Default` is straightforward: transform a `HttpClientRequest` into a `HttpClientResponse`.
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 req = HttpClientRequest.get(
609
- "https://jsonplaceholder.typicode.com/posts/1"
610
- )
588
+ const program = Effect.gen(function* () {
589
+ // access the HttpClient
590
+ const client = yield* HttpClient.HttpClient
611
591
 
612
- // HttpClient.fetch is a Default
613
- const res = HttpClient.fetch(req)
592
+ const response = yield* client.get(
593
+ "https://jsonplaceholder.typicode.com/posts/1"
594
+ )
614
595
 
615
- const json = HttpClientResponse.json(res)
596
+ const json = yield* response.json
616
597
 
617
- Effect.runPromise(json).then(console.log)
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
- In this example:
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
- | Default | Description |
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.makeDefault((req) =>
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 req = HttpClientRequest.get(
683
- "https://jsonplaceholder.typicode.com/posts/1"
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
- // Log the request before fetching
687
- const tapFetch: HttpClient.HttpClient.Default = HttpClient.fetch.pipe(
688
- HttpClient.tapRequest(Console.log)
689
- )
659
+ const response = yield* client.get(
660
+ "https://jsonplaceholder.typicode.com/posts/1"
661
+ )
690
662
 
691
- const res = tapFetch(req)
663
+ const json = yield* response.json
692
664
 
693
- const json = HttpClientResponse.json(res)
665
+ console.log(json)
666
+ }).pipe(Effect.scoped, Effect.provide(FetchHttpClient.layer))
694
667
 
695
- Effect.runPromise(json).then(console.log)
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 = HttpClientRequest.get(
859
- "https://jsonplaceholder.typicode.com/posts/1"
860
- ).pipe(HttpClient.fetch, HttpClientResponse.json)
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
- NodeRuntime.runMain(
863
- getPostAsJson.pipe(Effect.andThen((post) => Console.log(typeof post, post)))
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 getPostAsText = HttpClientRequest.get(
893
- "https://jsonplaceholder.typicode.com/posts/1"
894
- ).pipe(HttpClient.fetch, HttpClientResponse.text)
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
- NodeRuntime.runMain(
897
- getPostAsText.pipe(Effect.andThen((post) => Console.log(typeof post, post)))
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 | Description |
918
- | ---------------------------------- | ------------------------------------- |
919
- | `HttpClientResponse.arrayBuffer` | Convert to `ArrayBuffer` |
920
- | `HttpClientResponse.formData` | Convert to `FormData` |
921
- | `HttpClientResponse.json` | Convert to JSON |
922
- | `HttpClientResponse.stream` | Convert to a `Stream` of `Uint8Array` |
923
- | `HttpClientResponse.text` | Convert to text |
924
- | `HttpClientResponse.urlParamsBody` | Convert to `Http.urlParams.UrlParams` |
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
- }, Http.error.HttpClientError | ParseError, never>
924
+ }, HttpClientError | ParseError, never>
950
925
  */
951
- const getPostAndValidate = HttpClientRequest.get(
952
- "https://jsonplaceholder.typicode.com/posts/1"
953
- ).pipe(
954
- HttpClient.fetch,
955
- Effect.andThen(HttpClientResponse.schemaBodyJson(Post)),
956
- Effect.scoped
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
- NodeRuntime.runMain(getPostAndValidate.pipe(Effect.andThen(Console.log)))
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 = HttpClientRequest.get(
991
- "https://jsonplaceholder.typicode.com/non-existing-page"
992
- ).pipe(HttpClient.fetch, HttpClientResponse.text)
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
- NodeRuntime.runMain(getText.pipe(Effect.andThen(Console.log)))
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 = HttpClientRequest.get(
1013
- "https://jsonplaceholder.typicode.com/non-existing-page"
1014
- ).pipe(HttpClient.filterStatusOk(HttpClient.fetch), HttpClientResponse.text)
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
- NodeRuntime.runMain(getText.pipe(Effect.andThen(Console.log)))
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 `HttpClient` module. Here's an example of how to create and send a POST request:
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 = HttpClientRequest.post(
1066
- "https://jsonplaceholder.typicode.com/posts"
1067
- ).pipe(
1068
- HttpClientRequest.jsonBody({
1069
- title: "foo",
1070
- body: "bar",
1071
- userId: 1
1072
- }),
1073
- Effect.andThen(HttpClient.fetch),
1074
- HttpClientResponse.json
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
- NodeRuntime.runMain(addPost.pipe(Effect.andThen(Console.log)))
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 = HttpClientRequest.post(
1098
- "https://jsonplaceholder.typicode.com/posts"
1099
- ).pipe(
1100
- HttpClientRequest.textBody(
1101
- JSON.stringify({
1102
- title: "foo",
1103
- body: "bar",
1104
- userId: 1
1105
- }),
1106
- "application/json; charset=UTF-8"
1107
- ),
1108
- HttpClient.fetch,
1109
- HttpClientResponse.json
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
- NodeRuntime.runMain(Effect.andThen(addPost, Console.log))
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 = HttpClientRequest.post(
1139
- "https://jsonplaceholder.typicode.com/posts"
1140
- ).pipe(
1141
- HttpClientRequest.jsonBody({
1142
- title: "foo",
1143
- body: "bar",
1144
- userId: 1
1145
- }),
1146
- Effect.andThen(HttpClient.fetch),
1147
- Effect.andThen(HttpClientResponse.schemaBodyJson(Post)),
1148
- Effect.scoped
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
- NodeRuntime.runMain(addPost.pipe(Effect.andThen(Console.log)))
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(HttpClient.Fetch, () =>
1132
+ const FetchTest = Layer.succeed(FetchHttpClient.Fetch, () =>
1175
1133
  Promise.resolve(new Response("not found", { status: 404 }))
1176
1134
  )
1177
1135
 
1178
- // Program to test
1179
- const program = HttpClientRequest.get("https://www.google.com/").pipe(
1180
- HttpClient.fetch,
1181
- HttpClientResponse.text
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(FetchTest), Effect.runPromise)
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`.