envio 2.13.0 → 2.14.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/README.md +1 -0
- package/evm.schema.json +123 -2
- package/package.json +5 -5
- package/src/Utils.res +5 -8
- package/src/bindings/Ethers.res +2 -31
- package/src/sources/HyperSyncJsonApi.res +2 -2
- package/src/sources/Rpc.res +1 -1
- package/src/vendored/Rest.res +289 -186
- package/src/vendored/Rest.resi +60 -41
package/README.md
CHANGED
|
@@ -18,6 +18,7 @@ Build a real-time API for your blockchain application in minutes.
|
|
|
18
18
|
- Write JavaScript, TypeScript, or ReScript with automatically generated types
|
|
19
19
|
- Detailed logging & Error messaging
|
|
20
20
|
- [Hosted Service](https://docs.envio.dev/docs/HyperIndex/hosted-service) to take care of your infrastructure
|
|
21
|
+
- Seamlessly integrate new chains and enhance reliability with the RPC data source
|
|
21
22
|
|
|
22
23
|
## Getting Started
|
|
23
24
|
|
package/evm.schema.json
CHANGED
|
@@ -272,13 +272,13 @@
|
|
|
272
272
|
"type": "object",
|
|
273
273
|
"properties": {
|
|
274
274
|
"id": {
|
|
275
|
-
"description": "
|
|
275
|
+
"description": "The public blockchain network ID.",
|
|
276
276
|
"type": "integer",
|
|
277
277
|
"format": "uint64",
|
|
278
278
|
"minimum": 0
|
|
279
279
|
},
|
|
280
280
|
"rpc_config": {
|
|
281
|
-
"description": "RPC
|
|
281
|
+
"description": "RPC configuration for utilizing as the network's data-source. Typically optional for chains with HyperSync support, which is highly recommended. HyperSync dramatically enhances performance, providing up to a 1000x speed boost over traditional RPC.",
|
|
282
282
|
"anyOf": [
|
|
283
283
|
{
|
|
284
284
|
"$ref": "#/$defs/RpcConfig"
|
|
@@ -288,6 +288,17 @@
|
|
|
288
288
|
}
|
|
289
289
|
]
|
|
290
290
|
},
|
|
291
|
+
"rpc": {
|
|
292
|
+
"description": "RPC configuration for your indexer. If not specified otherwise, for networks supported by HyperSync, RPC serves as a fallback for added reliability. For others, it acts as the primary data-source. HyperSync offers significant performance improvements, up to a 1000x faster than traditional RPC.",
|
|
293
|
+
"anyOf": [
|
|
294
|
+
{
|
|
295
|
+
"$ref": "#/$defs/NetworkRpc"
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
"type": "null"
|
|
299
|
+
}
|
|
300
|
+
]
|
|
301
|
+
},
|
|
291
302
|
"hypersync_config": {
|
|
292
303
|
"description": "Optional HyperSync Config for additional fine-tuning",
|
|
293
304
|
"anyOf": [
|
|
@@ -422,6 +433,116 @@
|
|
|
422
433
|
"url"
|
|
423
434
|
]
|
|
424
435
|
},
|
|
436
|
+
"NetworkRpc": {
|
|
437
|
+
"anyOf": [
|
|
438
|
+
{
|
|
439
|
+
"type": "string"
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
"$ref": "#/$defs/Rpc"
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
"type": "array",
|
|
446
|
+
"items": {
|
|
447
|
+
"$ref": "#/$defs/Rpc"
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
]
|
|
451
|
+
},
|
|
452
|
+
"Rpc": {
|
|
453
|
+
"type": "object",
|
|
454
|
+
"properties": {
|
|
455
|
+
"url": {
|
|
456
|
+
"description": "The RPC endpoint URL.",
|
|
457
|
+
"type": "string"
|
|
458
|
+
},
|
|
459
|
+
"for": {
|
|
460
|
+
"description": "Determines if this RPC is for historical sync, real-time chain indexing, or as a fallback.",
|
|
461
|
+
"$ref": "#/$defs/For"
|
|
462
|
+
},
|
|
463
|
+
"initial_block_interval": {
|
|
464
|
+
"description": "The starting interval in range of blocks per query",
|
|
465
|
+
"type": [
|
|
466
|
+
"integer",
|
|
467
|
+
"null"
|
|
468
|
+
],
|
|
469
|
+
"format": "uint32",
|
|
470
|
+
"minimum": 0
|
|
471
|
+
},
|
|
472
|
+
"backoff_multiplicative": {
|
|
473
|
+
"description": "After an RPC error, how much to scale back the number of blocks requested at once",
|
|
474
|
+
"type": [
|
|
475
|
+
"number",
|
|
476
|
+
"null"
|
|
477
|
+
],
|
|
478
|
+
"format": "double"
|
|
479
|
+
},
|
|
480
|
+
"acceleration_additive": {
|
|
481
|
+
"description": "Without RPC errors or timeouts, how much to increase the number of blocks requested by for the next batch",
|
|
482
|
+
"type": [
|
|
483
|
+
"integer",
|
|
484
|
+
"null"
|
|
485
|
+
],
|
|
486
|
+
"format": "uint32",
|
|
487
|
+
"minimum": 0
|
|
488
|
+
},
|
|
489
|
+
"interval_ceiling": {
|
|
490
|
+
"description": "Do not further increase the block interval past this limit",
|
|
491
|
+
"type": [
|
|
492
|
+
"integer",
|
|
493
|
+
"null"
|
|
494
|
+
],
|
|
495
|
+
"format": "uint32",
|
|
496
|
+
"minimum": 0
|
|
497
|
+
},
|
|
498
|
+
"backoff_millis": {
|
|
499
|
+
"description": "After an error, how long to wait before retrying",
|
|
500
|
+
"type": [
|
|
501
|
+
"integer",
|
|
502
|
+
"null"
|
|
503
|
+
],
|
|
504
|
+
"format": "uint32",
|
|
505
|
+
"minimum": 0
|
|
506
|
+
},
|
|
507
|
+
"fallback_stall_timeout": {
|
|
508
|
+
"description": "If a fallback RPC is provided, the amount of time in ms to wait before kicking off the next provider",
|
|
509
|
+
"type": [
|
|
510
|
+
"integer",
|
|
511
|
+
"null"
|
|
512
|
+
],
|
|
513
|
+
"format": "uint32",
|
|
514
|
+
"minimum": 0
|
|
515
|
+
},
|
|
516
|
+
"query_timeout_millis": {
|
|
517
|
+
"description": "How long to wait before cancelling an RPC request",
|
|
518
|
+
"type": [
|
|
519
|
+
"integer",
|
|
520
|
+
"null"
|
|
521
|
+
],
|
|
522
|
+
"format": "uint32",
|
|
523
|
+
"minimum": 0
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
"additionalProperties": false,
|
|
527
|
+
"required": [
|
|
528
|
+
"url",
|
|
529
|
+
"for"
|
|
530
|
+
]
|
|
531
|
+
},
|
|
532
|
+
"For": {
|
|
533
|
+
"oneOf": [
|
|
534
|
+
{
|
|
535
|
+
"description": "Use RPC as the main data-source for both historical sync and real-time chain indexing.",
|
|
536
|
+
"type": "string",
|
|
537
|
+
"const": "sync"
|
|
538
|
+
},
|
|
539
|
+
{
|
|
540
|
+
"description": "Use RPC as a backup for the main data-source. Currently, it acts as a fallback when real-time indexing stalls, with potential for more cases in the future.",
|
|
541
|
+
"type": "string",
|
|
542
|
+
"const": "fallback"
|
|
543
|
+
}
|
|
544
|
+
]
|
|
545
|
+
},
|
|
425
546
|
"HypersyncConfig": {
|
|
426
547
|
"type": "object",
|
|
427
548
|
"properties": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "envio",
|
|
3
|
-
"version": "v2.
|
|
3
|
+
"version": "v2.14.0",
|
|
4
4
|
"description": "A latency and sync speed optimized, developer friendly blockchain data indexer.",
|
|
5
5
|
"bin": "./bin.js",
|
|
6
6
|
"repository": {
|
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
},
|
|
24
24
|
"homepage": "https://envio.dev",
|
|
25
25
|
"optionalDependencies": {
|
|
26
|
-
"envio-linux-x64": "v2.
|
|
27
|
-
"envio-linux-arm64": "v2.
|
|
28
|
-
"envio-darwin-x64": "v2.
|
|
29
|
-
"envio-darwin-arm64": "v2.
|
|
26
|
+
"envio-linux-x64": "v2.14.0",
|
|
27
|
+
"envio-linux-arm64": "v2.14.0",
|
|
28
|
+
"envio-darwin-x64": "v2.14.0",
|
|
29
|
+
"envio-darwin-arm64": "v2.14.0"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@envio-dev/hypersync-client": "0.6.3",
|
package/src/Utils.res
CHANGED
|
@@ -92,6 +92,7 @@ module Math = {
|
|
|
92
92
|
| (None, None) => None
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
|
+
|
|
95
96
|
module Array = {
|
|
96
97
|
@val external jsArrayCreate: int => array<'a> = "Array"
|
|
97
98
|
|
|
@@ -306,15 +307,11 @@ module Schema = {
|
|
|
306
307
|
let coerceToJsonPgType = schema => {
|
|
307
308
|
schema->S.preprocess(s => {
|
|
308
309
|
switch s.schema->S.classify {
|
|
309
|
-
|
|
310
|
-
|
|
|
311
|
-
Unknown => {serializer: _ => %raw(`"null"`)}
|
|
312
|
-
| Null(_)
|
|
310
|
+
// This is a workaround for Fuel Bytes type
|
|
311
|
+
| Unknown => {serializer: _ => %raw(`"null"`)}
|
|
313
312
|
| Bool => {
|
|
314
313
|
serializer: unknown => {
|
|
315
|
-
if unknown === %raw(`
|
|
316
|
-
%raw(`"null"`)
|
|
317
|
-
} else if unknown === %raw(`false`) {
|
|
314
|
+
if unknown === %raw(`false`) {
|
|
318
315
|
%raw(`"false"`)
|
|
319
316
|
} else if unknown === %raw(`true`) {
|
|
320
317
|
%raw(`"true"`)
|
|
@@ -339,7 +336,7 @@ module Set = {
|
|
|
339
336
|
external make: unit => t<'value> = "Set"
|
|
340
337
|
|
|
341
338
|
@ocaml.doc("Creates a new `Set` object.") @new
|
|
342
|
-
external
|
|
339
|
+
external fromArray: array<'value> => t<'value> = "Set"
|
|
343
340
|
|
|
344
341
|
/*
|
|
345
342
|
* Instance properties
|
package/src/bindings/Ethers.res
CHANGED
|
@@ -128,43 +128,17 @@ module JsonRpcProvider = {
|
|
|
128
128
|
weight?: int,
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
type fallbackProviderOptions = {
|
|
132
|
-
// How many providers must agree on a value before reporting
|
|
133
|
-
// back the response
|
|
134
|
-
// Note: Default the half of the providers weight, so we need to set it to accept result from the first rpc
|
|
135
|
-
quorum?: int,
|
|
136
|
-
}
|
|
137
|
-
|
|
138
131
|
@module("ethers") @scope("ethers") @new
|
|
139
132
|
external makeWithOptions: (~rpcUrl: string, ~network: Network.t, ~options: rpcOptions) => t =
|
|
140
133
|
"JsonRpcProvider"
|
|
141
134
|
|
|
142
|
-
@module("ethers") @scope("ethers") @new
|
|
143
|
-
external makeFallbackProvider: (
|
|
144
|
-
~providers: array<t>,
|
|
145
|
-
~network: Network.t,
|
|
146
|
-
~options: fallbackProviderOptions,
|
|
147
|
-
) => t = "FallbackProvider"
|
|
148
|
-
|
|
149
135
|
let makeStatic = (~rpcUrl: string, ~network: Network.t, ~priority=?, ~stallTimeout=?): t => {
|
|
150
136
|
makeWithOptions(~rpcUrl, ~network, ~options={staticNetwork: network, ?priority, ?stallTimeout})
|
|
151
137
|
}
|
|
152
138
|
|
|
153
|
-
let make = (~
|
|
139
|
+
let make = (~rpcUrl: string, ~chainId: int): t => {
|
|
154
140
|
let network = Network.fromChainId(~chainId)
|
|
155
|
-
|
|
156
|
-
| [rpcUrl] => makeStatic(~rpcUrl, ~network)
|
|
157
|
-
| rpcUrls =>
|
|
158
|
-
makeFallbackProvider(
|
|
159
|
-
~providers=rpcUrls->Js.Array2.mapi((rpcUrl, index) =>
|
|
160
|
-
makeStatic(~rpcUrl, ~network, ~priority=index, ~stallTimeout=fallbackStallTimeout)
|
|
161
|
-
),
|
|
162
|
-
~network,
|
|
163
|
-
~options={
|
|
164
|
-
quorum: 1,
|
|
165
|
-
},
|
|
166
|
-
)
|
|
167
|
-
}
|
|
141
|
+
makeStatic(~rpcUrl, ~network)
|
|
168
142
|
}
|
|
169
143
|
|
|
170
144
|
@send
|
|
@@ -185,9 +159,6 @@ module JsonRpcProvider = {
|
|
|
185
159
|
fields->Obj.magic
|
|
186
160
|
}
|
|
187
161
|
|
|
188
|
-
@send
|
|
189
|
-
external getBlockNumber: t => promise<int> = "getBlockNumber"
|
|
190
|
-
|
|
191
162
|
type block = {
|
|
192
163
|
_difficulty: bigint,
|
|
193
164
|
difficulty: int,
|
|
@@ -360,13 +360,13 @@ module ResponseTypes = {
|
|
|
360
360
|
let queryRoute = Rest.route(() => {
|
|
361
361
|
path: "/query",
|
|
362
362
|
method: Post,
|
|
363
|
-
|
|
363
|
+
input: s => s.body(QueryTypes.postQueryBodySchema),
|
|
364
364
|
responses: [s => s.data(ResponseTypes.queryResponseSchema)],
|
|
365
365
|
})
|
|
366
366
|
|
|
367
367
|
let heightRoute = Rest.route(() => {
|
|
368
368
|
path: "/height",
|
|
369
369
|
method: Get,
|
|
370
|
-
|
|
370
|
+
input: _ => (),
|
|
371
371
|
responses: [s => s.field("height", S.int)],
|
|
372
372
|
})
|
package/src/sources/Rpc.res
CHANGED
|
@@ -4,7 +4,7 @@ let makeRpcRoute = (method: string, paramsSchema, resultSchema) => {
|
|
|
4
4
|
Rest.route(() => {
|
|
5
5
|
method: Post,
|
|
6
6
|
path: "",
|
|
7
|
-
|
|
7
|
+
input: s => {
|
|
8
8
|
let _ = s.field("method", S.literal(method))
|
|
9
9
|
let _ = s.field("id", idSchema)
|
|
10
10
|
let _ = s.field("jsonrpc", versionSchema)
|
package/src/vendored/Rest.res
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
Vendored from: https://github.com/DZakh/rescript-rest
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0-rc.6
|
|
4
4
|
|
|
5
5
|
IF EDITING THIS FILE, PLEASE LIST THE CHANGES BELOW
|
|
6
6
|
|
|
@@ -170,49 +170,50 @@ module Response = {
|
|
|
170
170
|
data: 'value. S.t<'value> => 'value,
|
|
171
171
|
field: 'value. (string, S.t<'value>) => 'value,
|
|
172
172
|
header: 'value. (string, S.t<'value>) => 'value,
|
|
173
|
+
redirect: 'value. S.t<'value> => 'value,
|
|
173
174
|
}
|
|
174
175
|
|
|
175
|
-
type t<'
|
|
176
|
+
type t<'output> = {
|
|
176
177
|
// When it's empty, treat response as a default
|
|
177
178
|
status: option<int>,
|
|
178
179
|
description: option<string>,
|
|
179
180
|
dataSchema: S.t<unknown>,
|
|
180
181
|
emptyData: bool,
|
|
181
|
-
schema: S.t<'
|
|
182
|
+
schema: S.t<'output>,
|
|
182
183
|
}
|
|
183
184
|
|
|
184
|
-
type builder<'
|
|
185
|
+
type builder<'output> = {
|
|
185
186
|
// When it's empty, treat response as a default
|
|
186
187
|
mutable status?: int,
|
|
187
188
|
mutable description?: string,
|
|
188
189
|
mutable dataSchema?: S.t<unknown>,
|
|
189
190
|
mutable emptyData: bool,
|
|
190
|
-
mutable schema?: S.t<'
|
|
191
|
+
mutable schema?: S.t<'output>,
|
|
191
192
|
}
|
|
192
193
|
|
|
193
194
|
let register = (
|
|
194
|
-
map: dict<t<'
|
|
195
|
+
map: dict<t<'output>>,
|
|
195
196
|
status: [< status | #default],
|
|
196
|
-
builder: builder<'
|
|
197
|
+
builder: builder<'output>,
|
|
197
198
|
) => {
|
|
198
199
|
let key = status->(Obj.magic: [< status | #default] => string)
|
|
199
200
|
if map->Dict.has(key) {
|
|
200
201
|
panic(`Response for the "${key}" status registered multiple times`)
|
|
201
202
|
} else {
|
|
202
|
-
map->Js.Dict.set(key, builder->(Obj.magic: builder<'
|
|
203
|
+
map->Js.Dict.set(key, builder->(Obj.magic: builder<'output> => t<'output>))
|
|
203
204
|
}
|
|
204
205
|
}
|
|
205
206
|
|
|
206
207
|
@inline
|
|
207
|
-
let find = (map: dict<t<'
|
|
208
|
+
let find = (map: dict<t<'output>>, responseStatus: int): option<t<'output>> => {
|
|
208
209
|
(map
|
|
209
210
|
->Js.Dict.unsafeGet(responseStatus->(Obj.magic: int => string))
|
|
210
|
-
->(Obj.magic: t<'
|
|
211
|
+
->(Obj.magic: t<'output> => bool) ||
|
|
211
212
|
map
|
|
212
213
|
->Js.Dict.unsafeGet((responseStatus / 100)->(Obj.magic: int => string) ++ "XX")
|
|
213
|
-
->(Obj.magic: t<'
|
|
214
|
-
map->Js.Dict.unsafeGet("default")->(Obj.magic: t<'
|
|
215
|
-
->(Obj.magic: bool => option<t<'
|
|
214
|
+
->(Obj.magic: t<'output> => bool) ||
|
|
215
|
+
map->Js.Dict.unsafeGet("default")->(Obj.magic: t<'output> => bool))
|
|
216
|
+
->(Obj.magic: bool => option<t<'output>>)
|
|
216
217
|
}
|
|
217
218
|
}
|
|
218
219
|
|
|
@@ -242,26 +243,48 @@ type method =
|
|
|
242
243
|
| @as("OPTIONS") Options
|
|
243
244
|
| @as("TRACE") Trace
|
|
244
245
|
|
|
245
|
-
type definition<'
|
|
246
|
+
type definition<'input, 'output> = {
|
|
246
247
|
method: method,
|
|
247
248
|
path: string,
|
|
248
|
-
|
|
249
|
-
responses: array<Response.s => '
|
|
249
|
+
input: s => 'input,
|
|
250
|
+
responses: array<Response.s => 'output>,
|
|
250
251
|
summary?: string,
|
|
251
252
|
description?: string,
|
|
252
253
|
deprecated?: bool,
|
|
254
|
+
operationId?: string,
|
|
255
|
+
tags?: array<string>,
|
|
256
|
+
// By default, all query parameters are encoded as strings, however, you can use the jsonQuery option to encode query parameters as typed JSON values.
|
|
257
|
+
jsonQuery?: bool,
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
type rpc<'input, 'output> = {
|
|
261
|
+
input: S.t<'input>,
|
|
262
|
+
output: S.t<'output>,
|
|
263
|
+
summary?: string,
|
|
264
|
+
description?: string,
|
|
265
|
+
deprecated?: bool,
|
|
266
|
+
operationId?: string,
|
|
267
|
+
tags?: array<string>,
|
|
253
268
|
}
|
|
254
269
|
|
|
255
|
-
type routeParams<'
|
|
256
|
-
|
|
270
|
+
type routeParams<'input, 'output> = {
|
|
271
|
+
method: method,
|
|
272
|
+
path: string,
|
|
257
273
|
pathItems: array<pathItem>,
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
274
|
+
inputSchema: S.t<'input>,
|
|
275
|
+
outputSchema: S.t<'output>,
|
|
276
|
+
responses: array<Response.t<'output>>,
|
|
277
|
+
responsesMap: dict<Response.t<'output>>,
|
|
261
278
|
isRawBody: bool,
|
|
279
|
+
summary?: string,
|
|
280
|
+
description?: string,
|
|
281
|
+
deprecated?: bool,
|
|
282
|
+
operationId?: string,
|
|
283
|
+
tags?: array<string>,
|
|
284
|
+
jsonQuery?: bool,
|
|
262
285
|
}
|
|
263
286
|
|
|
264
|
-
type route<'
|
|
287
|
+
type route<'input, 'output> = unit => definition<'input, 'output>
|
|
265
288
|
|
|
266
289
|
let rec parsePath = (path: string, ~pathItems, ~pathParams) => {
|
|
267
290
|
if path !== "" {
|
|
@@ -376,181 +399,230 @@ let basicAuthSchema = S.string->S.transform(s => {
|
|
|
376
399
|
})
|
|
377
400
|
|
|
378
401
|
let params = route => {
|
|
379
|
-
switch (route->Obj.magic)["_rest"]->(
|
|
380
|
-
Obj.magic: unknown => option<routeParams<'variables, 'response>>
|
|
381
|
-
) {
|
|
402
|
+
switch (route->Obj.magic)["_rest"]->(Obj.magic: unknown => option<routeParams<'input, 'output>>) {
|
|
382
403
|
| Some(params) => params
|
|
383
404
|
| None => {
|
|
384
|
-
let
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
rawBody: schema => {
|
|
408
|
-
let isNonStringBased = switch schema->S.classify {
|
|
409
|
-
| Literal(String(_))
|
|
410
|
-
| String => false
|
|
411
|
-
| _ => true
|
|
412
|
-
}
|
|
413
|
-
if isNonStringBased {
|
|
414
|
-
panic("Only string-based schemas are allowed in rawBody")
|
|
415
|
-
}
|
|
416
|
-
let _ = %raw(`isRawBody = true`)
|
|
417
|
-
s.field("body", schema)
|
|
418
|
-
},
|
|
419
|
-
header: (fieldName, schema) => {
|
|
420
|
-
s.nested("headers").field(fieldName->Js.String2.toLowerCase, coerceSchema(schema))
|
|
421
|
-
},
|
|
422
|
-
query: (fieldName, schema) => {
|
|
423
|
-
s.nested("query").field(fieldName, coerceSchema(schema))
|
|
424
|
-
},
|
|
425
|
-
param: (fieldName, schema) => {
|
|
426
|
-
if !Dict.has(pathParams, fieldName) {
|
|
427
|
-
panic(`Path parameter "${fieldName}" is not defined in the path`)
|
|
428
|
-
}
|
|
429
|
-
s.nested("params").field(fieldName, coerceSchema(schema))
|
|
430
|
-
},
|
|
431
|
-
auth: auth => {
|
|
432
|
-
s.nested("headers").field(
|
|
433
|
-
"authorization",
|
|
434
|
-
switch auth {
|
|
435
|
-
| Bearer => bearerAuthSchema
|
|
436
|
-
| Basic => basicAuthSchema
|
|
437
|
-
},
|
|
438
|
-
)
|
|
439
|
-
},
|
|
440
|
-
})
|
|
441
|
-
})
|
|
442
|
-
|
|
443
|
-
{
|
|
444
|
-
// The variables input is guaranteed to be an object, so we reset the rescript-schema type filter here
|
|
445
|
-
variablesSchema->stripInPlace
|
|
446
|
-
variablesSchema->removeTypeValidationInPlace
|
|
447
|
-
switch variablesSchema->getSchemaField("headers") {
|
|
448
|
-
| Some({schema}) =>
|
|
449
|
-
schema->stripInPlace
|
|
450
|
-
schema->removeTypeValidationInPlace
|
|
451
|
-
| None => ()
|
|
452
|
-
}
|
|
453
|
-
switch variablesSchema->getSchemaField("params") {
|
|
454
|
-
| Some({schema}) => schema->removeTypeValidationInPlace
|
|
455
|
-
| None => ()
|
|
405
|
+
let definition = (route->(Obj.magic: route<'input, 'output> => route<unknown, unknown>))()
|
|
406
|
+
|
|
407
|
+
let params = if (definition->Obj.magic)["output"] {
|
|
408
|
+
let definition =
|
|
409
|
+
definition->(Obj.magic: definition<unknown, unknown> => rpc<unknown, unknown>)
|
|
410
|
+
let path =
|
|
411
|
+
`/` ++
|
|
412
|
+
switch definition.operationId {
|
|
413
|
+
| Some(p) => p
|
|
414
|
+
| None => (route->Obj.magic)["name"]
|
|
415
|
+
}
|
|
416
|
+
let inputSchema = S.object(s => s.field("body", definition.input))
|
|
417
|
+
inputSchema->stripInPlace
|
|
418
|
+
inputSchema->removeTypeValidationInPlace
|
|
419
|
+
let outputSchema = S.object(s => s.field("data", definition.output))
|
|
420
|
+
outputSchema->stripInPlace
|
|
421
|
+
outputSchema->removeTypeValidationInPlace
|
|
422
|
+
let response: Response.t<unknown> = {
|
|
423
|
+
status: Some(200),
|
|
424
|
+
description: None,
|
|
425
|
+
dataSchema: definition.input,
|
|
426
|
+
emptyData: false,
|
|
427
|
+
schema: outputSchema,
|
|
456
428
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
429
|
+
let responsesMap = Js.Dict.empty()
|
|
430
|
+
responsesMap->Js.Dict.set("200", response)
|
|
431
|
+
{
|
|
432
|
+
method: Post,
|
|
433
|
+
path,
|
|
434
|
+
inputSchema,
|
|
435
|
+
outputSchema,
|
|
436
|
+
responses: [response],
|
|
437
|
+
responsesMap,
|
|
438
|
+
pathItems: [Static(path)],
|
|
439
|
+
isRawBody: false,
|
|
440
|
+
summary: ?definition.summary,
|
|
441
|
+
description: ?definition.description,
|
|
442
|
+
deprecated: ?definition.deprecated,
|
|
443
|
+
operationId: ?definition.operationId,
|
|
444
|
+
tags: ?definition.tags,
|
|
460
445
|
}
|
|
461
|
-
}
|
|
446
|
+
} else {
|
|
447
|
+
let pathItems = []
|
|
448
|
+
let pathParams = Js.Dict.empty()
|
|
449
|
+
parsePath(definition.path, ~pathItems, ~pathParams)
|
|
462
450
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
let
|
|
467
|
-
|
|
468
|
-
}
|
|
469
|
-
let schema = S.object(s => {
|
|
470
|
-
let definition = r({
|
|
471
|
-
status: status => {
|
|
472
|
-
builder.status = Some(status)
|
|
473
|
-
let status = status->(Obj.magic: int => Response.status)
|
|
474
|
-
responsesMap->Response.register(status, builder)
|
|
475
|
-
s.tag("status", status)
|
|
476
|
-
},
|
|
477
|
-
description: d => builder.description = Some(d),
|
|
451
|
+
// Don't use ref, since it creates an unnecessary object
|
|
452
|
+
let isRawBody = %raw(`false`)
|
|
453
|
+
|
|
454
|
+
let inputSchema = S.object(s => {
|
|
455
|
+
definition.input({
|
|
478
456
|
field: (fieldName, schema) => {
|
|
479
|
-
|
|
480
|
-
s.nested("data").field(fieldName, schema)
|
|
457
|
+
s.nested("body").field(fieldName, schema)
|
|
481
458
|
},
|
|
482
|
-
|
|
483
|
-
builder.emptyData = false
|
|
459
|
+
body: schema => {
|
|
484
460
|
if schema->isNestedFlattenSupported {
|
|
485
|
-
s.nested("
|
|
461
|
+
s.nested("body").flatten(schema)
|
|
486
462
|
} else {
|
|
487
|
-
s.field("
|
|
463
|
+
s.field("body", schema)
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
rawBody: schema => {
|
|
467
|
+
let isNonStringBased = switch schema->S.classify {
|
|
468
|
+
| Literal(String(_))
|
|
469
|
+
| String => false
|
|
470
|
+
| _ => true
|
|
488
471
|
}
|
|
472
|
+
if isNonStringBased {
|
|
473
|
+
panic("Only string-based schemas are allowed in rawBody")
|
|
474
|
+
}
|
|
475
|
+
let _ = %raw(`isRawBody = true`)
|
|
476
|
+
s.field("body", schema)
|
|
489
477
|
},
|
|
490
478
|
header: (fieldName, schema) => {
|
|
491
479
|
s.nested("headers").field(fieldName->Js.String2.toLowerCase, coerceSchema(schema))
|
|
492
480
|
},
|
|
481
|
+
query: (fieldName, schema) => {
|
|
482
|
+
s.nested("query").field(fieldName, coerceSchema(schema))
|
|
483
|
+
},
|
|
484
|
+
param: (fieldName, schema) => {
|
|
485
|
+
if !Dict.has(pathParams, fieldName) {
|
|
486
|
+
panic(`Path parameter "${fieldName}" is not defined in the path`)
|
|
487
|
+
}
|
|
488
|
+
s.nested("params").field(fieldName, coerceSchema(schema))
|
|
489
|
+
},
|
|
490
|
+
auth: auth => {
|
|
491
|
+
s.nested("headers").field(
|
|
492
|
+
"authorization",
|
|
493
|
+
switch auth {
|
|
494
|
+
| Bearer => bearerAuthSchema
|
|
495
|
+
| Basic => basicAuthSchema
|
|
496
|
+
},
|
|
497
|
+
)
|
|
498
|
+
},
|
|
493
499
|
})
|
|
494
|
-
if builder.emptyData {
|
|
495
|
-
s.tag("data", %raw(`null`))
|
|
496
|
-
}
|
|
497
|
-
definition
|
|
498
500
|
})
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
501
|
+
|
|
502
|
+
{
|
|
503
|
+
// The input input is guaranteed to be an object, so we reset the rescript-schema type filter here
|
|
504
|
+
inputSchema->stripInPlace
|
|
505
|
+
inputSchema->removeTypeValidationInPlace
|
|
506
|
+
switch inputSchema->getSchemaField("headers") {
|
|
507
|
+
| Some({schema}) =>
|
|
508
|
+
schema->stripInPlace
|
|
509
|
+
schema->removeTypeValidationInPlace
|
|
510
|
+
| None => ()
|
|
511
|
+
}
|
|
512
|
+
switch inputSchema->getSchemaField("params") {
|
|
513
|
+
| Some({schema}) => schema->removeTypeValidationInPlace
|
|
514
|
+
| None => ()
|
|
515
|
+
}
|
|
516
|
+
switch inputSchema->getSchemaField("query") {
|
|
517
|
+
| Some({schema}) => schema->removeTypeValidationInPlace
|
|
518
|
+
| None => ()
|
|
512
519
|
}
|
|
513
|
-
| _ => ()
|
|
514
520
|
}
|
|
515
|
-
|
|
516
|
-
|
|
521
|
+
|
|
522
|
+
let responsesMap = Js.Dict.empty()
|
|
523
|
+
let responses = []
|
|
524
|
+
definition.responses->Js.Array2.forEach(r => {
|
|
525
|
+
let builder: Response.builder<unknown> = {
|
|
526
|
+
emptyData: true,
|
|
527
|
+
}
|
|
528
|
+
let schema = S.object(s => {
|
|
529
|
+
let status = status => {
|
|
530
|
+
builder.status = Some(status)
|
|
531
|
+
let status = status->(Obj.magic: int => Response.status)
|
|
532
|
+
responsesMap->Response.register(status, builder)
|
|
533
|
+
s.tag("status", status)
|
|
534
|
+
}
|
|
535
|
+
let header = (fieldName, schema) => {
|
|
536
|
+
s.nested("headers").field(fieldName->Js.String2.toLowerCase, coerceSchema(schema))
|
|
537
|
+
}
|
|
538
|
+
let definition = r({
|
|
539
|
+
status,
|
|
540
|
+
redirect: schema => {
|
|
541
|
+
status(307)
|
|
542
|
+
header("location", coerceSchema(schema))
|
|
543
|
+
},
|
|
544
|
+
description: d => builder.description = Some(d),
|
|
545
|
+
field: (fieldName, schema) => {
|
|
546
|
+
builder.emptyData = false
|
|
547
|
+
s.nested("data").field(fieldName, schema)
|
|
548
|
+
},
|
|
549
|
+
data: schema => {
|
|
550
|
+
builder.emptyData = false
|
|
551
|
+
if schema->isNestedFlattenSupported {
|
|
552
|
+
s.nested("data").flatten(schema)
|
|
553
|
+
} else {
|
|
554
|
+
s.field("data", schema)
|
|
555
|
+
}
|
|
556
|
+
},
|
|
557
|
+
header,
|
|
558
|
+
})
|
|
559
|
+
if builder.emptyData {
|
|
560
|
+
s.tag("data", %raw(`null`))
|
|
561
|
+
}
|
|
562
|
+
definition
|
|
563
|
+
})
|
|
564
|
+
if builder.status === None {
|
|
565
|
+
responsesMap->Response.register(#default, builder)
|
|
566
|
+
}
|
|
517
567
|
schema->stripInPlace
|
|
518
568
|
schema->removeTypeValidationInPlace
|
|
519
|
-
|
|
569
|
+
let dataSchema = (schema->getSchemaField("data")->Option.unsafeUnwrap).schema
|
|
570
|
+
builder.dataSchema = dataSchema->Option.unsafeSome
|
|
571
|
+
switch dataSchema->S.classify {
|
|
572
|
+
| Literal(_) => {
|
|
573
|
+
let dataTypeValidation = dataSchema->unsafeGetTypeValidationInPlace
|
|
574
|
+
schema->setTypeValidationInPlace((b, ~inputVar) =>
|
|
575
|
+
dataTypeValidation(b, ~inputVar=`${inputVar}.data`)
|
|
576
|
+
)
|
|
577
|
+
}
|
|
578
|
+
| _ => ()
|
|
579
|
+
}
|
|
580
|
+
switch schema->getSchemaField("headers") {
|
|
581
|
+
| Some({schema}) =>
|
|
582
|
+
schema->stripInPlace
|
|
583
|
+
schema->removeTypeValidationInPlace
|
|
584
|
+
| None => ()
|
|
585
|
+
}
|
|
586
|
+
builder.schema = Option.unsafeSome(schema)
|
|
587
|
+
responses
|
|
588
|
+
->Js.Array2.push(builder->(Obj.magic: Response.builder<unknown> => Response.t<unknown>))
|
|
589
|
+
->ignore
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
if responses->Js.Array2.length === 0 {
|
|
593
|
+
panic("At least single response should be registered")
|
|
520
594
|
}
|
|
521
|
-
builder.schema = Option.unsafeSome(schema)
|
|
522
|
-
responses
|
|
523
|
-
->Js.Array2.push(builder->(Obj.magic: Response.builder<unknown> => Response.t<unknown>))
|
|
524
|
-
->ignore
|
|
525
|
-
})
|
|
526
595
|
|
|
527
|
-
|
|
528
|
-
|
|
596
|
+
{
|
|
597
|
+
method: definition.method,
|
|
598
|
+
path: definition.path,
|
|
599
|
+
inputSchema,
|
|
600
|
+
outputSchema: S.union(responses->Js.Array2.map(r => r.schema)),
|
|
601
|
+
responses,
|
|
602
|
+
pathItems,
|
|
603
|
+
responsesMap,
|
|
604
|
+
isRawBody,
|
|
605
|
+
summary: ?definition.summary,
|
|
606
|
+
description: ?definition.description,
|
|
607
|
+
deprecated: ?definition.deprecated,
|
|
608
|
+
operationId: ?definition.operationId,
|
|
609
|
+
tags: ?definition.tags,
|
|
610
|
+
jsonQuery: ?definition.jsonQuery,
|
|
611
|
+
}
|
|
529
612
|
}
|
|
530
613
|
|
|
531
|
-
let params = {
|
|
532
|
-
definition: routeDefinition,
|
|
533
|
-
variablesSchema,
|
|
534
|
-
responses,
|
|
535
|
-
pathItems,
|
|
536
|
-
responsesMap,
|
|
537
|
-
isRawBody,
|
|
538
|
-
}
|
|
539
614
|
(route->Obj.magic)["_rest"] = params
|
|
540
|
-
params->(Obj.magic: routeParams<unknown, unknown> => routeParams<'
|
|
615
|
+
params->(Obj.magic: routeParams<unknown, unknown> => routeParams<'input, 'output>)
|
|
541
616
|
}
|
|
542
617
|
}
|
|
543
618
|
}
|
|
544
619
|
|
|
545
|
-
external route: (unit => definition<'
|
|
546
|
-
|
|
620
|
+
external route: (unit => definition<'input, 'output>) => route<'input, 'output> = "%identity"
|
|
621
|
+
external rpc: (unit => rpc<'input, 'output>) => route<'input, 'output> = "%identity"
|
|
547
622
|
|
|
548
623
|
type client = {
|
|
549
|
-
call: 'variables 'response. (route<'variables, 'response>, 'variables) => promise<'response>,
|
|
550
624
|
baseUrl: string,
|
|
551
625
|
fetcher: ApiFetcher.t,
|
|
552
|
-
// By default, all query parameters are encoded as strings, however, you can use the jsonQuery option to encode query parameters as typed JSON values.
|
|
553
|
-
jsonQuery: bool,
|
|
554
626
|
}
|
|
555
627
|
|
|
556
628
|
/**
|
|
@@ -582,7 +654,7 @@ let rec tokeniseValue = (key, value, ~append) => {
|
|
|
582
654
|
}
|
|
583
655
|
|
|
584
656
|
// Inspired by https://github.com/ts-rest/ts-rest/blob/7792ef7bdc352e84a4f5766c53f984a9d630c60e/libs/ts-rest/core/src/lib/client.ts#L347
|
|
585
|
-
let getCompletePath = (~baseUrl, ~pathItems, ~maybeQuery, ~maybeParams, ~jsonQuery) => {
|
|
657
|
+
let getCompletePath = (~baseUrl, ~pathItems, ~maybeQuery, ~maybeParams, ~jsonQuery=false) => {
|
|
586
658
|
let path = ref(baseUrl)
|
|
587
659
|
|
|
588
660
|
for idx in 0 to pathItems->Js.Array2.length - 1 {
|
|
@@ -593,7 +665,7 @@ let getCompletePath = (~baseUrl, ~pathItems, ~maybeQuery, ~maybeParams, ~jsonQue
|
|
|
593
665
|
switch (maybeParams->Obj.magic && maybeParams->Js.Dict.unsafeGet(name)->Obj.magic)
|
|
594
666
|
->(Obj.magic: bool => option<string>) {
|
|
595
667
|
| Some(param) => path := path.contents ++ param
|
|
596
|
-
| None => panic(`Path parameter "${name}" is not defined in
|
|
668
|
+
| None => panic(`Path parameter "${name}" is not defined in input`)
|
|
597
669
|
}
|
|
598
670
|
}
|
|
599
671
|
}
|
|
@@ -648,20 +720,46 @@ let getCompletePath = (~baseUrl, ~pathItems, ~maybeQuery, ~maybeParams, ~jsonQue
|
|
|
648
720
|
path.contents
|
|
649
721
|
}
|
|
650
722
|
|
|
651
|
-
let
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
723
|
+
let url = (route, input, ~baseUrl="") => {
|
|
724
|
+
let {pathItems, inputSchema} = route->params
|
|
725
|
+
let data = input->S.reverseConvertOrThrow(inputSchema)->Obj.magic
|
|
726
|
+
getCompletePath(
|
|
727
|
+
~baseUrl,
|
|
728
|
+
~pathItems,
|
|
729
|
+
~maybeQuery=data["query"],
|
|
730
|
+
~maybeParams=data["params"],
|
|
731
|
+
~jsonQuery=false,
|
|
732
|
+
)
|
|
733
|
+
}
|
|
661
734
|
|
|
662
|
-
|
|
735
|
+
type global = {
|
|
736
|
+
@as("c")
|
|
737
|
+
mutable client: option<client>,
|
|
738
|
+
}
|
|
663
739
|
|
|
664
|
-
|
|
740
|
+
let global = {
|
|
741
|
+
client: None,
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
let fetch = (type input response, route: route<input, response>, input, ~client=?) => {
|
|
745
|
+
let route = route->(Obj.magic: route<input, response> => route<unknown, unknown>)
|
|
746
|
+
let input = input->(Obj.magic: input => unknown)
|
|
747
|
+
|
|
748
|
+
let {path, method, ?jsonQuery, inputSchema, responsesMap, pathItems, isRawBody} = route->params
|
|
749
|
+
|
|
750
|
+
let client = switch client {
|
|
751
|
+
| Some(client) => client
|
|
752
|
+
| None =>
|
|
753
|
+
switch global.client {
|
|
754
|
+
| Some(client) => client
|
|
755
|
+
| None =>
|
|
756
|
+
panic(
|
|
757
|
+
`Client is not set for the ${path} fetch request. Please, use Rest.setGlobalClient or pass a client explicitly to the Rest.fetch arguments`,
|
|
758
|
+
)
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
let data = input->S.reverseConvertOrThrow(inputSchema)->Obj.magic
|
|
665
763
|
|
|
666
764
|
if data["body"] !== %raw(`void 0`) {
|
|
667
765
|
if !isRawBody {
|
|
@@ -673,17 +771,17 @@ let fetch = (
|
|
|
673
771
|
data["headers"]["content-type"] = "application/json"
|
|
674
772
|
}
|
|
675
773
|
|
|
676
|
-
fetcher({
|
|
774
|
+
client.fetcher({
|
|
677
775
|
body: data["body"],
|
|
678
776
|
headers: data["headers"],
|
|
679
777
|
path: getCompletePath(
|
|
680
|
-
~baseUrl,
|
|
778
|
+
~baseUrl=client.baseUrl,
|
|
681
779
|
~pathItems,
|
|
682
780
|
~maybeQuery=data["query"],
|
|
683
781
|
~maybeParams=data["params"],
|
|
684
|
-
~jsonQuery
|
|
782
|
+
~jsonQuery?,
|
|
685
783
|
),
|
|
686
|
-
method: (
|
|
784
|
+
method: (method :> string),
|
|
687
785
|
})->Promise.thenResolve(fetcherResponse => {
|
|
688
786
|
switch responsesMap->Response.find(fetcherResponse.status) {
|
|
689
787
|
| None =>
|
|
@@ -716,12 +814,17 @@ let fetch = (
|
|
|
716
814
|
})
|
|
717
815
|
}
|
|
718
816
|
|
|
719
|
-
let client = (
|
|
720
|
-
let call = (route, variables) => route->fetch(baseUrl, variables, ~fetcher, ~jsonQuery)
|
|
817
|
+
let client = (baseUrl, ~fetcher=ApiFetcher.default) => {
|
|
721
818
|
{
|
|
722
819
|
baseUrl,
|
|
723
820
|
fetcher,
|
|
724
|
-
|
|
725
|
-
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
let setGlobalClient = (baseUrl, ~fetcher=?) => {
|
|
825
|
+
switch global.client {
|
|
826
|
+
| Some(_) =>
|
|
827
|
+
panic("There's already a global client defined. You can have only one global client at a time.")
|
|
828
|
+
| None => global.client = Some(client(baseUrl, ~fetcher?))
|
|
726
829
|
}
|
|
727
830
|
}
|
package/src/vendored/Rest.resi
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
Vendored from: https://github.com/DZakh/rescript-rest
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0-rc.6
|
|
4
4
|
|
|
5
5
|
IF EDITING THIS FILE, PLEASE LIST THE CHANGES BELOW
|
|
6
6
|
|
|
@@ -10,22 +10,6 @@ here
|
|
|
10
10
|
|
|
11
11
|
@@uncurried
|
|
12
12
|
|
|
13
|
-
module ApiFetcher: {
|
|
14
|
-
type args = {body: option<unknown>, headers: option<dict<unknown>>, method: string, path: string}
|
|
15
|
-
type response = {data: unknown, status: int, headers: dict<unknown>}
|
|
16
|
-
type t = args => promise<response>
|
|
17
|
-
|
|
18
|
-
// Inspired by https://github.com/ts-rest/ts-rest/blob/7792ef7bdc352e84a4f5766c53f984a9d630c60e/libs/ts-rest/core/src/lib/client.ts#L102
|
|
19
|
-
/**
|
|
20
|
-
* Default fetch api implementation:
|
|
21
|
-
*
|
|
22
|
-
* Can be used as a reference for implementing your own fetcher,
|
|
23
|
-
* or used in the "api" field of ClientArgs to allow you to hook
|
|
24
|
-
* into the request to run custom logic
|
|
25
|
-
*/
|
|
26
|
-
let default: t
|
|
27
|
-
}
|
|
28
|
-
|
|
29
13
|
module Response: {
|
|
30
14
|
type numiricStatus = [
|
|
31
15
|
| #100
|
|
@@ -94,13 +78,13 @@ module Response: {
|
|
|
94
78
|
| numiricStatus
|
|
95
79
|
]
|
|
96
80
|
|
|
97
|
-
type t<'
|
|
81
|
+
type t<'output> = {
|
|
98
82
|
// When it's empty, treat response as a default
|
|
99
83
|
status: option<int>,
|
|
100
84
|
description: option<string>,
|
|
101
85
|
dataSchema: S.t<unknown>,
|
|
102
86
|
emptyData: bool,
|
|
103
|
-
schema: S.t<'
|
|
87
|
+
schema: S.t<'output>,
|
|
104
88
|
}
|
|
105
89
|
|
|
106
90
|
type s = {
|
|
@@ -109,6 +93,7 @@ module Response: {
|
|
|
109
93
|
data: 'value. S.t<'value> => 'value,
|
|
110
94
|
field: 'value. (string, S.t<'value>) => 'value,
|
|
111
95
|
header: 'value. (string, S.t<'value>) => 'value,
|
|
96
|
+
redirect: 'value. S.t<'value> => 'value,
|
|
112
97
|
}
|
|
113
98
|
}
|
|
114
99
|
|
|
@@ -134,49 +119,83 @@ type method =
|
|
|
134
119
|
| @as("OPTIONS") Options
|
|
135
120
|
| @as("TRACE") Trace
|
|
136
121
|
|
|
137
|
-
type definition<'
|
|
122
|
+
type definition<'input, 'output> = {
|
|
138
123
|
method: method,
|
|
139
124
|
path: string,
|
|
140
|
-
|
|
141
|
-
responses: array<Response.s => '
|
|
125
|
+
input: s => 'input,
|
|
126
|
+
responses: array<Response.s => 'output>,
|
|
142
127
|
summary?: string,
|
|
143
128
|
description?: string,
|
|
144
129
|
deprecated?: bool,
|
|
130
|
+
operationId?: string,
|
|
131
|
+
tags?: array<string>,
|
|
132
|
+
// By default, all query parameters are encoded as strings, however, you can use the jsonQuery option to encode query parameters as typed JSON values.
|
|
133
|
+
jsonQuery?: bool,
|
|
145
134
|
}
|
|
146
135
|
|
|
147
|
-
type
|
|
136
|
+
type rpc<'input, 'output> = {
|
|
137
|
+
input: S.t<'input>,
|
|
138
|
+
output: S.t<'output>,
|
|
139
|
+
summary?: string,
|
|
140
|
+
description?: string,
|
|
141
|
+
deprecated?: bool,
|
|
142
|
+
operationId?: string,
|
|
143
|
+
tags?: array<string>,
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
type route<'input, 'output>
|
|
148
147
|
|
|
149
148
|
type pathParam = {name: string}
|
|
150
149
|
@unboxed
|
|
151
150
|
type pathItem = Static(string) | Param(pathParam)
|
|
152
151
|
|
|
153
|
-
type routeParams<'
|
|
154
|
-
|
|
152
|
+
type routeParams<'input, 'output> = {
|
|
153
|
+
method: method,
|
|
154
|
+
path: string,
|
|
155
155
|
pathItems: array<pathItem>,
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
156
|
+
inputSchema: S.t<'input>,
|
|
157
|
+
outputSchema: S.t<'output>,
|
|
158
|
+
responses: array<Response.t<'output>>,
|
|
159
|
+
responsesMap: dict<Response.t<'output>>,
|
|
159
160
|
isRawBody: bool,
|
|
161
|
+
summary?: string,
|
|
162
|
+
description?: string,
|
|
163
|
+
deprecated?: bool,
|
|
164
|
+
operationId?: string,
|
|
165
|
+
tags?: array<string>,
|
|
166
|
+
jsonQuery?: bool,
|
|
160
167
|
}
|
|
161
168
|
|
|
162
|
-
let params: route<'
|
|
169
|
+
let params: route<'input, 'output> => routeParams<'input, 'output>
|
|
170
|
+
|
|
171
|
+
external route: (unit => definition<'input, 'output>) => route<'input, 'output> = "%identity"
|
|
172
|
+
external rpc: (unit => rpc<'input, 'output>) => route<'input, 'output> = "%identity"
|
|
163
173
|
|
|
164
|
-
|
|
165
|
-
|
|
174
|
+
module ApiFetcher: {
|
|
175
|
+
type args = {body: option<unknown>, headers: option<dict<unknown>>, method: string, path: string}
|
|
176
|
+
type response = {data: unknown, status: int, headers: dict<unknown>}
|
|
177
|
+
type t = args => promise<response>
|
|
178
|
+
|
|
179
|
+
// Inspired by https://github.com/ts-rest/ts-rest/blob/7792ef7bdc352e84a4f5766c53f984a9d630c60e/libs/ts-rest/core/src/lib/client.ts#L102
|
|
180
|
+
/**
|
|
181
|
+
* Default fetch api implementation:
|
|
182
|
+
*
|
|
183
|
+
* Can be used as a reference for implementing your own fetcher,
|
|
184
|
+
* or used in the "api" field of ClientArgs to allow you to hook
|
|
185
|
+
* into the request to run custom logic
|
|
186
|
+
*/
|
|
187
|
+
let default: t
|
|
188
|
+
}
|
|
166
189
|
|
|
167
190
|
type client = {
|
|
168
|
-
call: 'variables 'response. (route<'variables, 'response>, 'variables) => promise<'response>,
|
|
169
191
|
baseUrl: string,
|
|
170
192
|
fetcher: ApiFetcher.t,
|
|
171
|
-
jsonQuery: bool,
|
|
172
193
|
}
|
|
173
194
|
|
|
174
|
-
let
|
|
195
|
+
let url: (route<'input, 'output>, 'input, ~baseUrl: string=?) => string
|
|
196
|
+
|
|
197
|
+
let client: (string, ~fetcher: ApiFetcher.t=?) => client
|
|
198
|
+
|
|
199
|
+
let setGlobalClient: (string, ~fetcher: ApiFetcher.t=?) => unit
|
|
175
200
|
|
|
176
|
-
let fetch: (
|
|
177
|
-
route<'variables, 'response>,
|
|
178
|
-
string,
|
|
179
|
-
'variables,
|
|
180
|
-
~fetcher: ApiFetcher.t=?,
|
|
181
|
-
~jsonQuery: bool=?,
|
|
182
|
-
) => promise<'response>
|
|
201
|
+
let fetch: (route<'input, 'output>, 'input, ~client: client=?) => promise<'output>
|