@colisweb/rescript-toolkit 5.4.2 → 5.5.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colisweb/rescript-toolkit",
3
- "version": "5.4.2",
3
+ "version": "5.5.1",
4
4
  "type": "module",
5
5
  "scripts": {
6
6
  "clean": "rescript clean",
@@ -29,6 +29,7 @@
29
29
  "dependencies": {
30
30
  "@colisweb/bs-react-intl-extractor-bin": "0.12.2",
31
31
  "@datadog/browser-rum": "5.8.0",
32
+ "@dck/rescript-ky": "^2.0.0",
32
33
  "@dck/rescript-promise": "1.1.0",
33
34
  "@dck/restorative": "1.1.0",
34
35
  "@greenlabs/ppx-spice": "0.2.1",
@@ -43,6 +44,7 @@
43
44
  "copy-to-clipboard": "3.3.3",
44
45
  "date-fns": "3.2.0",
45
46
  "dedent": "0.7.0",
47
+ "ky": "^1.2.4",
46
48
  "lenses-ppx": "6.1.10",
47
49
  "list-selectors": "2.0.1",
48
50
  "lodash": "4.17.21",
package/rescript.json CHANGED
@@ -30,7 +30,8 @@
30
30
  "@greenlabs/ppx-spice",
31
31
  "rescript-classnames",
32
32
  "rescript-react-update",
33
- "@dck/restorative"
33
+ "@dck/restorative",
34
+ "@dck/rescript-ky"
34
35
  ],
35
36
  "ppx-flags": [
36
37
  ["@greenlabs/ppx-spice/ppx"],
@@ -0,0 +1,205 @@
1
+ type error<'apiError> =
2
+ | DecodeError(Spice.decodeError)
3
+ | Unknown(Ky.error<Js.Json.t>)
4
+ | Custom('apiError)
5
+
6
+ type requestConfig<'apiError, 'response> = {
7
+ path: string,
8
+ requestOptions: Ky.requestOptions<Js.Json.t, Js.Json.t, Js.Json.t, 'response>,
9
+ key?: array<string>,
10
+ customError?: Ky.error<Js.Json.t> => Promise.t<error<'apiError>>,
11
+ mapPromise?: Js.Json.t => result<'response, error<'apiError>>,
12
+ }
13
+
14
+ %%private(
15
+ let fetch = (
16
+ ~instance,
17
+ ~path,
18
+ ~requestOptions,
19
+ ~mapPromise=?,
20
+ ~customError=?,
21
+ ~response_decode,
22
+ ) => {
23
+ // TODO :
24
+ // - parseJson
25
+ // - abort controller signal
26
+ Ky.Instance.asCallable(instance)(path, ~options=requestOptions)
27
+ ->Ky.Response.json()
28
+ ->Promise.Js.fromBsPromise
29
+ ->Promise.Js.toResult
30
+ ->Promise.flatMap(response => {
31
+ switch response {
32
+ | Error(err) => {
33
+ let error: Ky.error<'a> = err->Obj.magic
34
+ customError->Option.mapWithDefault(Promise.resolved(Error(Unknown(error))), fn =>
35
+ fn(error)->Promise.map(e => Error(e))
36
+ )
37
+ }
38
+ | Ok(response) =>
39
+ switch mapPromise {
40
+ | None =>
41
+ Promise.resolved(
42
+ switch response->response_decode {
43
+ | Ok(_) as ok => ok
44
+ | Error(decodeError) => Error(DecodeError(decodeError))
45
+ },
46
+ )
47
+ | Some(fn) => Promise.resolved(fn(response))
48
+ }
49
+ }
50
+ })
51
+ }
52
+ )
53
+ module type Config = {
54
+ type argument
55
+ type response
56
+ type error
57
+ let instance: Ky.Instance.t
58
+ let response_decode: Js.Json.t => result<response, Spice.decodeError>
59
+ let config: argument => requestConfig<error, response>
60
+ }
61
+
62
+ let fetchAPI = (
63
+ type argument response err,
64
+ config: module(Config with
65
+ type argument = argument
66
+ and type response = response
67
+ and type error = err
68
+ ),
69
+ argument: argument,
70
+ ): Promise.t<result<response, error<err>>> => {
71
+ let module(C) = config
72
+ let requestConfig = C.config(argument)
73
+
74
+ fetch(
75
+ ~instance=C.instance,
76
+ ~path=requestConfig.path,
77
+ ~response_decode=C.response_decode,
78
+ ~requestOptions=requestConfig.requestOptions,
79
+ ~customError=?requestConfig.customError,
80
+ ~mapPromise=?requestConfig.mapPromise,
81
+ )
82
+ }
83
+ let useFetcher = (
84
+ type argument response error,
85
+ ~options: option<Swr.fetcherOptions>=?,
86
+ config: module(Config with
87
+ type argument = argument
88
+ and type response = response
89
+ and type error = error
90
+ ),
91
+ argument: option<argument>,
92
+ ): Toolkit__Hooks.fetcher<response> => {
93
+ let module(C) = config
94
+
95
+ Toolkit__Hooks.useFetcher(
96
+ ~options?,
97
+ argument->Option.flatMap(argument => {
98
+ let requestConfig = C.config(argument)
99
+
100
+ switch requestConfig.key->Obj.magic {
101
+ | None =>
102
+ Js.Exn.raiseError(
103
+ `You are using a config without a key for this path ${requestConfig.path}`,
104
+ )
105
+ | Some(key) => key
106
+ }
107
+ }),
108
+ () => {
109
+ fetchAPI(config, argument->Option.getExn)->Promise.Js.fromResult
110
+ },
111
+ )
112
+ }
113
+
114
+ let useOptionalFetcher = (
115
+ type argument response error,
116
+ ~options: option<Swr.fetcherOptions>=?,
117
+ config: module(Config with
118
+ type argument = argument
119
+ and type response = response
120
+ and type error = error
121
+ ),
122
+ argument: option<argument>,
123
+ ): Toolkit__Hooks.fetcher<response> => {
124
+ let module(C) = config
125
+
126
+ Toolkit__Hooks.useOptionalFetcher(
127
+ ~options?,
128
+ argument->Option.flatMap(argument => {
129
+ let requestConfig = C.config(argument)
130
+
131
+ switch requestConfig.key->Obj.magic {
132
+ | None =>
133
+ Js.Exn.raiseError(
134
+ `You are using a config without a key for this path ${requestConfig.path}`,
135
+ )
136
+ | Some(key) => key
137
+ }
138
+ }),
139
+ () => {
140
+ fetchAPI(config, argument->Option.getExn)->Promise.Js.fromResult
141
+ },
142
+ )
143
+ }
144
+
145
+ type state<'data, 'error> =
146
+ | NotAsked
147
+ | Loading
148
+ | Done(result<'data, 'error>)
149
+
150
+ %%private(
151
+ let minInt = -999999999
152
+ let maxInt = 1000000000
153
+
154
+ let increment = (num: int): int => num !== maxInt ? num + 1 : minInt
155
+ )
156
+
157
+ let useManualRequest = (
158
+ type argument response error,
159
+ config: module(Config with
160
+ type argument = argument
161
+ and type response = response
162
+ and type error = error
163
+ ),
164
+ ) => {
165
+ let module(Config) = config
166
+
167
+ let lastCallId = React.useRef(0)
168
+ let canceled = React.useRef(false)
169
+ let (state, set) = React.useState(() => NotAsked)
170
+ let isMounted = ReactUse.useMountedState(.)
171
+
172
+ let trigger = argument => {
173
+ lastCallId.current = lastCallId.current->increment
174
+ let callId = lastCallId.current
175
+
176
+ set(_ => Loading)
177
+
178
+ canceled.current = false
179
+
180
+ fetchAPI(module(Config), argument)->Promise.map(result => {
181
+ let isCanceled = callId !== lastCallId.current || canceled.current
182
+
183
+ if isMounted() && !isCanceled {
184
+ set(_ => Done(result))
185
+ }
186
+
187
+ (result, isCanceled)
188
+ })
189
+ }
190
+
191
+ let cancel = React.useCallback(() => canceled.current = true, [])
192
+
193
+ (state, trigger, cancel)
194
+ }
195
+
196
+ external exnToError: Js.Exn.t => error<'a> = "%identity"
197
+
198
+ let decodeResponseError = (responseError, decoder) => {
199
+ responseError
200
+ ->Ky.Response.json()
201
+ ->Promise.Js.fromBsPromise
202
+ ->Promise.Js.toResult
203
+ ->Promise.mapError(Obj.magic)
204
+ ->Promise.flatMapOk(json => json->decoder->Promise.resolved)
205
+ }
@@ -63,6 +63,11 @@ type mutationFetcher<'params, 'data, 'error> = {
63
63
 
64
64
  module SwrKey = Toolkit__Identifier.MakeString()
65
65
 
66
+ @unboxed
67
+ type key =
68
+ | String(string)
69
+ | Array(array<string>)
70
+
66
71
  @module("swr")
67
72
  external useSwr: (option<SwrKey.t>, 'fn, 'fetcherOptions) => fetcher<'data> = "default"
68
73
 
@@ -88,6 +93,8 @@ module SwrConfig = {
88
93
 
89
94
  @send
90
95
  external mutate0: (t, SwrKey.t) => unit = "mutate"
96
+ @send
97
+ external mutateByKey: (t, key) => unit = "mutate"
91
98
 
92
99
  @send
93
100
  external mutate: (t, SwrKey.t, 'data, bool) => unit = "mutate"