acrylblends 0.6.3 → 0.7.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 +4 -3
- package/docs/framework/README.md +2 -2
- package/docs/framework/UPSTREAM.md +1 -1
- package/docs/framework/guides/jsx.md +51 -1
- package/docs/framework/helpers/ssg.md +3 -6
- package/docs/framework/middleware/builtin/jsx-renderer.md +0 -3
- package/docs/framework/upstream.json +2 -2
- package/lib/accepts.d.ts +1 -1
- package/lib/acryl-base.d.ts +1 -1
- package/lib/adapter.d.ts +1 -1
- package/lib/{app-wFwpg-bt.d.ts → app-D8riatvv.d.ts} +1 -1
- package/lib/{base-ddiVmiE7.js → base-NSN_SNv-.js} +1 -0
- package/lib/basic-auth.d.ts +1 -1
- package/lib/bearer-auth.d.ts +1 -1
- package/lib/body-limit.d.ts +1 -1
- package/lib/cache.d.ts +1 -1
- package/lib/cache.js +4 -4
- package/lib/{client-BHTr318g.js → client-DDLXwhBo.js} +2 -125
- package/lib/client.d.ts +2 -2
- package/lib/client.js +2 -1
- package/lib/combine.d.ts +1 -1
- package/lib/{components-DYZdxJIJ.js → components-JXVm45kK.js} +1 -1
- package/lib/{components-Ce5FRyHU.js → components-cEW3ovK0.js} +3 -3
- package/lib/compress.d.ts +1 -1
- package/lib/conninfo.d.ts +2 -2
- package/lib/{context-DVQBjuns.d.ts → context-DS3fYyG-.d.ts} +1 -1
- package/lib/context-storage.d.ts +1 -1
- package/lib/cookie.d.ts +1 -1
- package/lib/cors.d.ts +1 -1
- package/lib/csrf.d.ts +1 -1
- package/lib/dev.d.ts +2 -2
- package/lib/etag.d.ts +1 -1
- package/lib/factory.d.ts +2 -2
- package/lib/{hooks-Bq5q9IMl.js → hooks-w72iLeFR.js} +6 -6
- package/lib/{index-CUTfU69R.d.ts → index-Bb7YJKCq.d.ts} +2 -2
- package/lib/{index-CsvB-F9j.d.ts → index-BvFh1Gfx.d.ts} +1 -1
- package/lib/{index-Cqcdluyn.d.ts → index-CTy4N7ng.d.ts} +2 -2
- package/lib/index.d.ts +4 -4
- package/lib/ip-restriction.d.ts +2 -2
- package/lib/jsx/dom/client.js +1 -1
- package/lib/jsx/dom/jsx-dev-runtime.js +1 -1
- package/lib/jsx/dom/jsx-runtime.js +1 -1
- package/lib/jsx/dom/server.js +2 -2
- package/lib/jsx/dom.js +4 -4
- package/lib/jsx/jsx-dev-runtime.js +1 -1
- package/lib/jsx/jsx-runtime.js +1 -1
- package/lib/jsx/streaming.js +1 -1
- package/lib/{jsx-dev-runtime-kAhR_4C7.js → jsx-dev-runtime-CWLRA2F3.js} +2 -2
- package/lib/jsx-renderer.d.ts +75 -0
- package/lib/jsx-renderer.js +99 -0
- package/lib/jsx.js +4 -4
- package/lib/jwk.d.ts +1 -1
- package/lib/jwt.d.ts +1 -1
- package/lib/language.d.ts +1 -1
- package/lib/logger.d.ts +1 -1
- package/lib/loopback-only.d.ts +1 -1
- package/lib/method-not-allowed.d.ts +2 -2
- package/lib/method-override.d.ts +2 -2
- package/lib/mount.d.ts +1 -1
- package/lib/node-server/conninfo.d.ts +1 -1
- package/lib/node-server/register.d.ts +1 -0
- package/lib/node-server/register.js +73 -0
- package/lib/node-server/serve-static.d.ts +1 -1
- package/lib/node-server/serve-static.js +1 -1
- package/lib/node-server.d.ts +1 -1
- package/lib/node-server.js +1 -1
- package/lib/powered-by.d.ts +1 -1
- package/lib/pretty-json.d.ts +1 -1
- package/lib/quick.d.ts +1 -1
- package/lib/request-id.d.ts +1 -1
- package/lib/request.d.ts +1 -1
- package/lib/route.d.ts +1 -1
- package/lib/schema-validator.d.ts +1 -1
- package/lib/secure-headers.d.ts +1 -1
- package/lib/{serve-static-Cw23shXv.js → serve-static-B14MF1Va.js} +1 -0
- package/lib/serve-static.d.ts +1 -1
- package/lib/ssg-BwjcA-Ge.js +389 -0
- package/lib/ssg.d.ts +156 -0
- package/lib/ssg.js +2 -0
- package/lib/streaming.d.ts +1 -1
- package/lib/testing.d.ts +3 -3
- package/lib/testing.js +1 -1
- package/lib/timeout.d.ts +1 -1
- package/lib/timing.d.ts +1 -1
- package/lib/tiny.d.ts +1 -1
- package/lib/trailing-slash.d.ts +1 -1
- package/lib/{types-nEL8VPDC.d.ts → types-Dv_nV2cv.d.ts} +1 -1
- package/lib/{types-CQiGmsWW.d.ts → types-zNxyEpG2.d.ts} +2 -2
- package/lib/types.d.ts +1 -1
- package/lib/utils/body.d.ts +1 -1
- package/lib/utils/ipaddr.d.ts +1 -1
- package/lib/utils-Cx61GFMC.js +125 -0
- package/lib/validator.d.ts +1 -1
- package/lib/ws.d.ts +1 -1
- package/package.json +13 -1
- package/plugins/acryl-inspect/upstream.json +1 -1
- package/plugins/acryl-node-server/src/register.ts +86 -0
- package/plugins/acryl-node-server/upstream.json +5 -3
- package/plugins/acryl-router/src/core/helper/ssg/index.ts +14 -0
- package/plugins/acryl-router/src/core/helper/ssg/middleware.ts +83 -0
- package/plugins/acryl-router/src/core/helper/ssg/plugins.ts +72 -0
- package/plugins/acryl-router/src/core/helper/ssg/ssg.ts +471 -0
- package/plugins/acryl-router/src/core/helper/ssg/utils.ts +118 -0
- package/plugins/acryl-router/src/core/jsx/dom/render.ts +6 -6
- package/plugins/acryl-router/src/core/middleware/cache/index.ts +4 -4
- package/plugins/acryl-router/src/core/middleware/jsx-renderer/index.ts +162 -0
- package/plugins/acryl-router/src/helpers/index.ts +1 -0
- package/plugins/acryl-router/src/middleware/index.ts +1 -0
- package/plugins/acryl-router/upstream.json +2 -2
- package/plugins/acryl-router-http/upstream.json +1 -1
- package/plugins/acryl-state/upstream.json +1 -1
- package/plugins/desktop/main.ts +4 -1
- package/plugins/desktop/upstream.json +1 -1
- package/plugins/desktop-web/main.ts +3 -1
- package/plugins/desktop-web/upstream.json +1 -1
- package/plugins/extensions/resolve.ts +4 -0
- package/shared/AGENTS.core.md +3 -0
- package/templates/agent/package.json +7 -3
- package/templates/agent/tsconfig.json +10 -2
- package/templates/blank/package.json +7 -3
- package/templates/blank/src/about.tsx +14 -0
- package/templates/blank/src/routes.ts +2 -0
- package/templates/blank/test/app.test.ts +15 -0
- package/templates/blank/tsconfig.json +3 -1
- package/templates/todo/package.json +7 -3
- package/templates/todo/tsconfig.json +3 -1
- package/templates/todo/upstream.json +1 -1
- package/plugins/acryl-inspect/tests/report.spec.ts +0 -69
- package/plugins/acryl-inspect/tests/service.spec.ts +0 -121
- package/plugins/acryl-node-server/tests/adapter/app.ts +0 -76
- package/plugins/acryl-node-server/tests/adapter/assets/.static/plain.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/favicon.ico +0 -0
- package/plugins/acryl-node-server/tests/adapter/assets/secret.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/acryl.html +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/admin/secret.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/data.json +0 -5
- package/plugins/acryl-node-server/tests/adapter/assets/static/extensionless +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/foo..bar.txt +0 -0
- package/plugins/acryl-node-server/tests/adapter/assets/static/index.html +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static/plain.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-context-path/plain.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.bin.br +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.br +0 -1
- package/plugins/acryl-node-server/tests/adapter/assets/static-with-precompressed/hello.txt.zst +0 -1
- package/plugins/acryl-node-server/tests/adapter/conninfo.test.ts +0 -34
- package/plugins/acryl-node-server/tests/adapter/content-length.test.ts +0 -248
- package/plugins/acryl-node-server/tests/adapter/early-hints.test.ts +0 -457
- package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-cert.pem +0 -23
- package/plugins/acryl-node-server/tests/adapter/fixtures/keys/agent1-key.pem +0 -27
- package/plugins/acryl-node-server/tests/adapter/headers.test.ts +0 -252
- package/plugins/acryl-node-server/tests/adapter/helpers/request.ts +0 -190
- package/plugins/acryl-node-server/tests/adapter/listener.test.ts +0 -727
- package/plugins/acryl-node-server/tests/adapter/request-client-disconnect.test.ts +0 -189
- package/plugins/acryl-node-server/tests/adapter/request.test.ts +0 -1230
- package/plugins/acryl-node-server/tests/adapter/response.test.ts +0 -285
- package/plugins/acryl-node-server/tests/adapter/serve-static.test.ts +0 -849
- package/plugins/acryl-node-server/tests/adapter/server-socket.test.ts +0 -790
- package/plugins/acryl-node-server/tests/adapter/server.test.ts +0 -1312
- package/plugins/acryl-node-server/tests/adapter/setup.ts +0 -16
- package/plugins/acryl-node-server/tests/adapter/url.test.ts +0 -76
- package/plugins/acryl-node-server/tests/adapter/utils/response.test.ts +0 -23
- package/plugins/acryl-node-server/tests/adapter/utils/stream.test.ts +0 -75
- package/plugins/acryl-node-server/tests/adapter/utils.test.ts +0 -235
- package/plugins/acryl-node-server/tests/adapter/websocket.test.ts +0 -260
- package/plugins/acryl-node-server/tests/server.spec.ts +0 -141
- package/plugins/acryl-router/src/core/app.test.ts +0 -3833
- package/plugins/acryl-router/src/core/client/client.test.ts +0 -2313
- package/plugins/acryl-router/src/core/client/types.test.ts +0 -149
- package/plugins/acryl-router/src/core/client/utils.test.ts +0 -405
- package/plugins/acryl-router/src/core/compose.test.ts +0 -751
- package/plugins/acryl-router/src/core/context.test.ts +0 -629
- package/plugins/acryl-router/src/core/helper/accepts/accepts.test.ts +0 -344
- package/plugins/acryl-router/src/core/helper/adapter/index.test.ts +0 -55
- package/plugins/acryl-router/src/core/helper/cookie/index.test.ts +0 -510
- package/plugins/acryl-router/src/core/helper/css/common.case.test.tsx +0 -506
- package/plugins/acryl-router/src/core/helper/css/index.test.tsx +0 -678
- package/plugins/acryl-router/src/core/helper/dev/index.test.ts +0 -181
- package/plugins/acryl-router/src/core/helper/factory/index.test.ts +0 -434
- package/plugins/acryl-router/src/core/helper/html/index.test.ts +0 -78
- package/plugins/acryl-router/src/core/helper/proxy/index.test.ts +0 -356
- package/plugins/acryl-router/src/core/helper/route/index.test.ts +0 -259
- package/plugins/acryl-router/src/core/helper/streaming/sse.test.tsx +0 -557
- package/plugins/acryl-router/src/core/helper/streaming/stream.test.ts +0 -128
- package/plugins/acryl-router/src/core/helper/streaming/text.test.ts +0 -34
- package/plugins/acryl-router/src/core/helper/testing/index.test.ts +0 -42
- package/plugins/acryl-router/src/core/helper/websocket/index.test.ts +0 -124
- package/plugins/acryl-router/src/core/http-exception.test.ts +0 -45
- package/plugins/acryl-router/src/core/jsx/base.test.tsx +0 -97
- package/plugins/acryl-router/src/core/jsx/children.test.ts +0 -52
- package/plugins/acryl-router/src/core/jsx/components.test.tsx +0 -936
- package/plugins/acryl-router/src/core/jsx/context-isolation.test.ts +0 -655
- package/plugins/acryl-router/src/core/jsx/dom/client.test.tsx +0 -128
- package/plugins/acryl-router/src/core/jsx/dom/components.test.tsx +0 -330
- package/plugins/acryl-router/src/core/jsx/dom/context.test.tsx +0 -184
- package/plugins/acryl-router/src/core/jsx/dom/css.test.tsx +0 -149
- package/plugins/acryl-router/src/core/jsx/dom/hooks/index.test.tsx +0 -172
- package/plugins/acryl-router/src/core/jsx/dom/index.test.tsx +0 -3681
- package/plugins/acryl-router/src/core/jsx/dom/intrinsic-element/components.test.tsx +0 -1344
- package/plugins/acryl-router/src/core/jsx/dom/server.test.tsx +0 -199
- package/plugins/acryl-router/src/core/jsx/hooks/dom.test.tsx +0 -859
- package/plugins/acryl-router/src/core/jsx/hooks/string.test.tsx +0 -40
- package/plugins/acryl-router/src/core/jsx/index.test.tsx +0 -1744
- package/plugins/acryl-router/src/core/jsx/intrinsic-element/components.test.tsx +0 -666
- package/plugins/acryl-router/src/core/jsx/jsx-runtime.test.tsx +0 -85
- package/plugins/acryl-router/src/core/jsx/streaming.test.tsx +0 -1281
- package/plugins/acryl-router/src/core/jsx/utils.test.ts +0 -366
- package/plugins/acryl-router/src/core/middleware/basic-auth/index.test.ts +0 -434
- package/plugins/acryl-router/src/core/middleware/bearer-auth/index.test.ts +0 -1018
- package/plugins/acryl-router/src/core/middleware/body-limit/index.test.ts +0 -273
- package/plugins/acryl-router/src/core/middleware/cache/index.test.ts +0 -1211
- package/plugins/acryl-router/src/core/middleware/combine/index.test.ts +0 -355
- package/plugins/acryl-router/src/core/middleware/compress/index.test.ts +0 -535
- package/plugins/acryl-router/src/core/middleware/context-storage/index.test.ts +0 -43
- package/plugins/acryl-router/src/core/middleware/cors/index.test.ts +0 -532
- package/plugins/acryl-router/src/core/middleware/csrf/index.test.ts +0 -534
- package/plugins/acryl-router/src/core/middleware/etag/digest.test.ts +0 -21
- package/plugins/acryl-router/src/core/middleware/etag/index.test.ts +0 -503
- package/plugins/acryl-router/src/core/middleware/ip-restriction/index.test.ts +0 -217
- package/plugins/acryl-router/src/core/middleware/jwk/index.test.ts +0 -1135
- package/plugins/acryl-router/src/core/middleware/jwk/keys.test.json +0 -48
- package/plugins/acryl-router/src/core/middleware/jwt/index.test.ts +0 -802
- package/plugins/acryl-router/src/core/middleware/language/index.test.ts +0 -552
- package/plugins/acryl-router/src/core/middleware/logger/index.test.ts +0 -204
- package/plugins/acryl-router/src/core/middleware/method-not-allowed/index.test.ts +0 -292
- package/plugins/acryl-router/src/core/middleware/method-override/index.test.ts +0 -221
- package/plugins/acryl-router/src/core/middleware/mount/index.test.ts +0 -334
- package/plugins/acryl-router/src/core/middleware/powered-by/index.test.ts +0 -37
- package/plugins/acryl-router/src/core/middleware/pretty-json/index.test.ts +0 -218
- package/plugins/acryl-router/src/core/middleware/request-id/index.test.ts +0 -168
- package/plugins/acryl-router/src/core/middleware/secure-headers/index.test.ts +0 -671
- package/plugins/acryl-router/src/core/middleware/serve-static/index.test.ts +0 -450
- package/plugins/acryl-router/src/core/middleware/serve-static/path.test.ts +0 -50
- package/plugins/acryl-router/src/core/middleware/timeout/index.test.ts +0 -70
- package/plugins/acryl-router/src/core/middleware/timing/index.test.ts +0 -310
- package/plugins/acryl-router/src/core/middleware/trailing-slash/index.test.ts +0 -381
- package/plugins/acryl-router/src/core/preset/quick.test.ts +0 -42
- package/plugins/acryl-router/src/core/preset/tiny.test.ts +0 -42
- package/plugins/acryl-router/src/core/request.test.ts +0 -751
- package/plugins/acryl-router/src/core/router/common.case.test.ts +0 -1132
- package/plugins/acryl-router/src/core/router/linear-router/router.test.ts +0 -80
- package/plugins/acryl-router/src/core/router/pattern-router/router.test.ts +0 -46
- package/plugins/acryl-router/src/core/router/reg-exp-router/prepared-router.test.ts +0 -154
- package/plugins/acryl-router/src/core/router/reg-exp-router/router.test.ts +0 -265
- package/plugins/acryl-router/src/core/router/smart-router/router.test.ts +0 -13
- package/plugins/acryl-router/src/core/router/trie-router/node.test.ts +0 -896
- package/plugins/acryl-router/src/core/router/trie-router/router.test.ts +0 -8
- package/plugins/acryl-router/src/core/types.test.ts +0 -4134
- package/plugins/acryl-router/src/core/utils/accept.test.ts +0 -299
- package/plugins/acryl-router/src/core/utils/basic-auth.test.ts +0 -56
- package/plugins/acryl-router/src/core/utils/body.test.ts +0 -496
- package/plugins/acryl-router/src/core/utils/buffer.test.ts +0 -147
- package/plugins/acryl-router/src/core/utils/color.test.ts +0 -23
- package/plugins/acryl-router/src/core/utils/concurrent.test.ts +0 -69
- package/plugins/acryl-router/src/core/utils/cookie.test.ts +0 -460
- package/plugins/acryl-router/src/core/utils/crypto.test.ts +0 -43
- package/plugins/acryl-router/src/core/utils/encode.test.ts +0 -61
- package/plugins/acryl-router/src/core/utils/filepath.test.ts +0 -78
- package/plugins/acryl-router/src/core/utils/html.test.ts +0 -16
- package/plugins/acryl-router/src/core/utils/ipaddr.test.ts +0 -149
- package/plugins/acryl-router/src/core/utils/jwt/jwa.test.ts +0 -86
- package/plugins/acryl-router/src/core/utils/jwt/jwt.test.ts +0 -1788
- package/plugins/acryl-router/src/core/utils/mime.test.ts +0 -64
- package/plugins/acryl-router/src/core/utils/stream.test.ts +0 -212
- package/plugins/acryl-router/src/core/utils/types.test.ts +0 -295
- package/plugins/acryl-router/src/core/utils/url.test.ts +0 -384
- package/plugins/acryl-router/src/core/validator/utils.test.ts +0 -82
- package/plugins/acryl-router/src/core/validator/validator.test.ts +0 -1488
- package/plugins/acryl-router/tests/docs-examples.spec.ts +0 -152
- package/plugins/acryl-router/tests/jsx.spec.tsx +0 -63
- package/plugins/acryl-router/tests/live-install.spec.ts +0 -64
- package/plugins/acryl-router/tests/loader.spec.ts +0 -72
- package/plugins/acryl-router/tests/loopback-only.spec.ts +0 -80
- package/plugins/acryl-router/tests/no-dom/consumer.ts +0 -11
- package/plugins/acryl-router/tests/no-dom/tsconfig.json +0 -13
- package/plugins/acryl-router/tests/service.spec.ts +0 -426
- package/plugins/acryl-router/tests/shadow-state.spec.ts +0 -79
- package/plugins/acryl-router-http/tests/adapter.spec.ts +0 -174
- package/plugins/acryl-router-http/tests/stream.spec.ts +0 -133
- package/plugins/acryl-router-http/tests/surfaces.spec.ts +0 -101
- package/plugins/acryl-state/src/react.ts +0 -11
- package/plugins/acryl-state/tests/cell.spec.ts +0 -71
- package/plugins/acryl-state/tests/react.spec.ts +0 -39
- package/plugins/acryl-state/tests/service.spec.ts +0 -132
|
@@ -1,2313 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
-
|
|
3
|
-
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
|
4
|
-
import { HttpResponse, http } from 'msw'
|
|
5
|
-
import { setupServer } from 'msw/node'
|
|
6
|
-
import { expectTypeOf, vi } from 'vitest'
|
|
7
|
-
import { defineWebSocketHelper } from '../helper/websocket/index.ts'
|
|
8
|
-
import { Acryl } from '../app.ts'
|
|
9
|
-
import { parse } from '../utils/cookie.ts'
|
|
10
|
-
import type { Equal, Expect, JSONValue, SimplifyDeepArray } from '../utils/types.ts'
|
|
11
|
-
import { validator } from '../validator/index.ts'
|
|
12
|
-
import { hc } from './client.ts'
|
|
13
|
-
import type {
|
|
14
|
-
ClientResponse,
|
|
15
|
-
InferRequestType,
|
|
16
|
-
InferResponseType,
|
|
17
|
-
ApplyGlobalResponse,
|
|
18
|
-
PickResponseByStatusCode,
|
|
19
|
-
} from './types.ts'
|
|
20
|
-
|
|
21
|
-
// The Deno adapter is not part of ACRYL; these tests only build client URLs, so a helper that never upgrades stands in for it.
|
|
22
|
-
const upgradeWebSocket = defineWebSocketHelper(async () => {})
|
|
23
|
-
|
|
24
|
-
class SafeBigInt {
|
|
25
|
-
unsafe = BigInt(42)
|
|
26
|
-
|
|
27
|
-
toJSON() {
|
|
28
|
-
return {
|
|
29
|
-
value: '42n',
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
describe('Basic - JSON', () => {
|
|
35
|
-
const app = new Acryl()
|
|
36
|
-
|
|
37
|
-
const route = app
|
|
38
|
-
.post(
|
|
39
|
-
'/posts',
|
|
40
|
-
validator('cookie', () => {
|
|
41
|
-
return {} as {
|
|
42
|
-
debug: string
|
|
43
|
-
}
|
|
44
|
-
}),
|
|
45
|
-
validator('header', () => {
|
|
46
|
-
return {} as {
|
|
47
|
-
'x-message': string
|
|
48
|
-
}
|
|
49
|
-
}),
|
|
50
|
-
validator('json', () => {
|
|
51
|
-
return {} as {
|
|
52
|
-
id: number
|
|
53
|
-
title: string
|
|
54
|
-
}
|
|
55
|
-
}),
|
|
56
|
-
(c) => {
|
|
57
|
-
return c.json({
|
|
58
|
-
success: true,
|
|
59
|
-
message: 'dummy',
|
|
60
|
-
requestContentType: 'dummy',
|
|
61
|
-
requestAcryl: 'dummy',
|
|
62
|
-
requestMessage: 'dummy',
|
|
63
|
-
requestBody: {
|
|
64
|
-
id: 123,
|
|
65
|
-
title: 'dummy',
|
|
66
|
-
},
|
|
67
|
-
})
|
|
68
|
-
}
|
|
69
|
-
)
|
|
70
|
-
.get('/hello-not-found', (c) => c.notFound())
|
|
71
|
-
.get('/null', (c) => c.json(null))
|
|
72
|
-
.get('/empty', (c) => c.json({}))
|
|
73
|
-
.get('/bigint', (c) => c.json({ value: BigInt(42) }))
|
|
74
|
-
.get('/safe-bigint', (c) => c.json(new SafeBigInt()))
|
|
75
|
-
|
|
76
|
-
type AppType = typeof route
|
|
77
|
-
|
|
78
|
-
const server = setupServer(
|
|
79
|
-
http.post('http://localhost/posts', async ({ request }) => {
|
|
80
|
-
const requestContentType = request.headers.get('content-type')
|
|
81
|
-
const requestAcryl = request.headers.get('x-acryl')
|
|
82
|
-
const requestMessage = request.headers.get('x-message')
|
|
83
|
-
const requestBody = await request.json()
|
|
84
|
-
const payload = {
|
|
85
|
-
message: 'Hello!',
|
|
86
|
-
success: true,
|
|
87
|
-
requestContentType,
|
|
88
|
-
requestAcryl,
|
|
89
|
-
requestMessage,
|
|
90
|
-
requestBody,
|
|
91
|
-
}
|
|
92
|
-
return HttpResponse.json(payload)
|
|
93
|
-
}),
|
|
94
|
-
http.get('http://localhost/hello-not-found', () => {
|
|
95
|
-
return HttpResponse.text(null, {
|
|
96
|
-
status: 404,
|
|
97
|
-
})
|
|
98
|
-
}),
|
|
99
|
-
http.get('http://localhost/null', () => {
|
|
100
|
-
return HttpResponse.json(null)
|
|
101
|
-
}),
|
|
102
|
-
http.get('http://localhost/empty', () => {
|
|
103
|
-
return HttpResponse.json({})
|
|
104
|
-
}),
|
|
105
|
-
http.get('http://localhost/bigint', () => {
|
|
106
|
-
return HttpResponse.json({ value: BigInt(42) })
|
|
107
|
-
}),
|
|
108
|
-
http.get('http://localhost/safe-bigint', () => {
|
|
109
|
-
return HttpResponse.json(new SafeBigInt())
|
|
110
|
-
}),
|
|
111
|
-
http.get('http://localhost/api/string', () => {
|
|
112
|
-
return HttpResponse.json('a-string')
|
|
113
|
-
}),
|
|
114
|
-
http.get('http://localhost/api/number', async () => {
|
|
115
|
-
return HttpResponse.json(37)
|
|
116
|
-
}),
|
|
117
|
-
http.get('http://localhost/api/boolean', async () => {
|
|
118
|
-
return HttpResponse.json(true)
|
|
119
|
-
}),
|
|
120
|
-
http.get('http://localhost/api/generic', async () => {
|
|
121
|
-
return HttpResponse.json(Math.random() > 0.5 ? Boolean(Math.random()) : Math.random())
|
|
122
|
-
})
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
beforeAll(() => server.listen())
|
|
126
|
-
afterEach(() => server.resetHandlers())
|
|
127
|
-
afterAll(() => server.close())
|
|
128
|
-
|
|
129
|
-
const payload = {
|
|
130
|
-
id: 123,
|
|
131
|
-
title: 'Hello! Acryl!',
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const client = hc<AppType>('http://localhost', { headers: { 'x-acryl': 'acryl' } })
|
|
135
|
-
|
|
136
|
-
it('Should get 200 response', async () => {
|
|
137
|
-
const res = await client.posts.$post(
|
|
138
|
-
{
|
|
139
|
-
json: payload,
|
|
140
|
-
header: {
|
|
141
|
-
'x-message': 'foobar',
|
|
142
|
-
},
|
|
143
|
-
cookie: {
|
|
144
|
-
debug: 'true',
|
|
145
|
-
},
|
|
146
|
-
},
|
|
147
|
-
{}
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
expect(res.ok).toBe(true)
|
|
151
|
-
const data = await res.json()
|
|
152
|
-
expect(data.success).toBe(true)
|
|
153
|
-
expect(data.message).toBe('Hello!')
|
|
154
|
-
expect(data.requestContentType).toBe('application/json')
|
|
155
|
-
expect(data.requestAcryl).toBe('acryl')
|
|
156
|
-
expect(data.requestMessage).toBe('foobar')
|
|
157
|
-
expect(data.requestBody).toEqual(payload)
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
it('Should get 404 response', async () => {
|
|
161
|
-
const res = await client['hello-not-found'].$get()
|
|
162
|
-
expect(res.status).toBe(404)
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
it('Should get a `null` content', async () => {
|
|
166
|
-
const client = hc<AppType>('http://localhost')
|
|
167
|
-
const res = await client.null.$get()
|
|
168
|
-
const data = await res.json()
|
|
169
|
-
expectTypeOf(data).toMatchTypeOf<null>()
|
|
170
|
-
expect(data).toBe(null)
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
it('Should get a `{}` content', async () => {
|
|
174
|
-
const client = hc<AppType>('http://localhost')
|
|
175
|
-
const res = await client.empty.$get()
|
|
176
|
-
const data = await res.json()
|
|
177
|
-
expectTypeOf(data).toMatchTypeOf<{}>()
|
|
178
|
-
expect(data).toStrictEqual({})
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
it('Should get a `{}` content', async () => {
|
|
182
|
-
const client = hc<AppType>('http://localhost')
|
|
183
|
-
const res = await client['safe-bigint'].$get()
|
|
184
|
-
const data = await res.json()
|
|
185
|
-
expectTypeOf(data).toMatchTypeOf<{ value: string }>()
|
|
186
|
-
expect(data).toStrictEqual({ value: '42n' })
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
it('Should get an error response', async () => {
|
|
190
|
-
const client = hc<AppType>('http://localhost')
|
|
191
|
-
const res = await client.bigint.$get()
|
|
192
|
-
const data = await res.json()
|
|
193
|
-
expectTypeOf(data).toMatchTypeOf<never>()
|
|
194
|
-
expect(res.status).toBe(500)
|
|
195
|
-
expect(data).toMatchObject({
|
|
196
|
-
message: 'Do not know how to serialize a BigInt',
|
|
197
|
-
name: 'TypeError',
|
|
198
|
-
})
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
it('Should have correct types - primitives', async () => {
|
|
202
|
-
const app = new Acryl()
|
|
203
|
-
const route = app
|
|
204
|
-
.get('/api/string', (c) => c.json('a-string'))
|
|
205
|
-
.get('/api/number', (c) => c.json(37))
|
|
206
|
-
.get('/api/boolean', (c) => c.json(true))
|
|
207
|
-
.get('/api/generic', (c) =>
|
|
208
|
-
c.json(Math.random() > 0.5 ? Boolean(Math.random()) : Math.random())
|
|
209
|
-
)
|
|
210
|
-
type AppType = typeof route
|
|
211
|
-
const client = hc<AppType>('http://localhost')
|
|
212
|
-
const stringFetch = await client.api.string.$get()
|
|
213
|
-
const stringRes = await stringFetch.json()
|
|
214
|
-
const numberFetch = await client.api.number.$get()
|
|
215
|
-
const numberRes = await numberFetch.json()
|
|
216
|
-
const booleanFetch = await client.api.boolean.$get()
|
|
217
|
-
const booleanRes = await booleanFetch.json()
|
|
218
|
-
const genericFetch = await client.api.generic.$get()
|
|
219
|
-
const genericRes = await genericFetch.json()
|
|
220
|
-
type stringVerify = Expect<Equal<'a-string', typeof stringRes>>
|
|
221
|
-
expect(stringRes).toBe('a-string')
|
|
222
|
-
type numberVerify = Expect<Equal<37, typeof numberRes>>
|
|
223
|
-
expect(numberRes).toBe(37)
|
|
224
|
-
type booleanVerify = Expect<Equal<true, typeof booleanRes>>
|
|
225
|
-
expect(booleanRes).toBe(true)
|
|
226
|
-
type genericVerify = Expect<Equal<number | boolean, typeof genericRes>>
|
|
227
|
-
expect(typeof genericRes === 'number' || typeof genericRes === 'boolean').toBe(true)
|
|
228
|
-
|
|
229
|
-
// using .text() on json endpoint should return string
|
|
230
|
-
type textTest = Expect<Equal<Promise<string>, ReturnType<typeof genericFetch.text>>>
|
|
231
|
-
})
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
describe('Basic - query, queries, form, path params, header and cookie', () => {
|
|
235
|
-
const app = new Acryl()
|
|
236
|
-
|
|
237
|
-
const route = app
|
|
238
|
-
.get(
|
|
239
|
-
'/search',
|
|
240
|
-
validator('query', () => {
|
|
241
|
-
return {} as { q: string; tag: string[]; filter: string }
|
|
242
|
-
}),
|
|
243
|
-
(c) => {
|
|
244
|
-
return c.json({
|
|
245
|
-
q: 'fake',
|
|
246
|
-
tag: ['fake'],
|
|
247
|
-
filter: 'fake',
|
|
248
|
-
})
|
|
249
|
-
}
|
|
250
|
-
)
|
|
251
|
-
.put(
|
|
252
|
-
'/posts/:id',
|
|
253
|
-
validator('form', () => {
|
|
254
|
-
return {
|
|
255
|
-
title: 'Hello',
|
|
256
|
-
}
|
|
257
|
-
}),
|
|
258
|
-
(c) => {
|
|
259
|
-
const data = c.req.valid('form')
|
|
260
|
-
return c.json(data)
|
|
261
|
-
}
|
|
262
|
-
)
|
|
263
|
-
.get(
|
|
264
|
-
'/header',
|
|
265
|
-
validator('header', () => {
|
|
266
|
-
return {
|
|
267
|
-
'x-message-id': 'Hello',
|
|
268
|
-
}
|
|
269
|
-
}),
|
|
270
|
-
(c) => {
|
|
271
|
-
const data = c.req.valid('header')
|
|
272
|
-
return c.json(data)
|
|
273
|
-
}
|
|
274
|
-
)
|
|
275
|
-
.get(
|
|
276
|
-
'/cookie',
|
|
277
|
-
validator('cookie', () => {
|
|
278
|
-
return {
|
|
279
|
-
hello: 'world',
|
|
280
|
-
goodbye: 'moon',
|
|
281
|
-
}
|
|
282
|
-
}),
|
|
283
|
-
(c) => {
|
|
284
|
-
const data = c.req.valid('cookie')
|
|
285
|
-
return c.json(data)
|
|
286
|
-
}
|
|
287
|
-
)
|
|
288
|
-
|
|
289
|
-
const server = setupServer(
|
|
290
|
-
http.get('http://localhost/api/search', ({ request }) => {
|
|
291
|
-
const url = new URL(request.url)
|
|
292
|
-
const query = url.searchParams.get('q')
|
|
293
|
-
const tag = url.searchParams.getAll('tag')
|
|
294
|
-
const filter = url.searchParams.get('filter')
|
|
295
|
-
return HttpResponse.json({
|
|
296
|
-
q: query,
|
|
297
|
-
tag,
|
|
298
|
-
filter,
|
|
299
|
-
})
|
|
300
|
-
}),
|
|
301
|
-
http.get('http://localhost/api/posts', ({ request }) => {
|
|
302
|
-
const url = new URL(request.url)
|
|
303
|
-
const tags = url.searchParams.getAll('tags')
|
|
304
|
-
return HttpResponse.json({
|
|
305
|
-
tags: tags,
|
|
306
|
-
})
|
|
307
|
-
}),
|
|
308
|
-
http.put('http://localhost/api/posts/123', async ({ request }) => {
|
|
309
|
-
const buffer = await request.arrayBuffer()
|
|
310
|
-
// @ts-ignore
|
|
311
|
-
const string = String.fromCharCode.apply('', new Uint8Array(buffer))
|
|
312
|
-
return HttpResponse.text(string)
|
|
313
|
-
}),
|
|
314
|
-
http.get('http://localhost/api/header', async ({ request }) => {
|
|
315
|
-
const message = await request.headers.get('x-message-id')
|
|
316
|
-
return HttpResponse.json({ 'x-message-id': message })
|
|
317
|
-
}),
|
|
318
|
-
http.get('http://localhost/api/cookie', async ({ request }) => {
|
|
319
|
-
return HttpResponse.json(parse(request.headers.get('cookie') || ''))
|
|
320
|
-
})
|
|
321
|
-
)
|
|
322
|
-
|
|
323
|
-
beforeAll(() => server.listen())
|
|
324
|
-
afterEach(() => server.resetHandlers())
|
|
325
|
-
afterAll(() => server.close())
|
|
326
|
-
|
|
327
|
-
type AppType = typeof route
|
|
328
|
-
|
|
329
|
-
const client = hc<AppType>('http://localhost/api')
|
|
330
|
-
|
|
331
|
-
it('Should get 200 response - query', async () => {
|
|
332
|
-
const res = await client.search.$get({
|
|
333
|
-
query: {
|
|
334
|
-
q: 'foobar',
|
|
335
|
-
tag: ['a', 'b'],
|
|
336
|
-
// @ts-expect-error
|
|
337
|
-
filter: undefined,
|
|
338
|
-
},
|
|
339
|
-
})
|
|
340
|
-
|
|
341
|
-
expect(res.status).toBe(200)
|
|
342
|
-
expect(await res.json()).toEqual({
|
|
343
|
-
q: 'foobar',
|
|
344
|
-
tag: ['a', 'b'],
|
|
345
|
-
filter: null,
|
|
346
|
-
})
|
|
347
|
-
})
|
|
348
|
-
|
|
349
|
-
it('Should get 200 response - form, params', async () => {
|
|
350
|
-
const res = await client.posts[':id'].$put({
|
|
351
|
-
form: {
|
|
352
|
-
title: 'Good Night',
|
|
353
|
-
},
|
|
354
|
-
param: {
|
|
355
|
-
id: '123',
|
|
356
|
-
},
|
|
357
|
-
})
|
|
358
|
-
|
|
359
|
-
expect(res.status).toBe(200)
|
|
360
|
-
expect(await res.text()).toMatch('Good Night')
|
|
361
|
-
})
|
|
362
|
-
|
|
363
|
-
it('Should get 200 response - header', async () => {
|
|
364
|
-
const header = {
|
|
365
|
-
'x-message-id': 'Hello',
|
|
366
|
-
}
|
|
367
|
-
const res = await client.header.$get({
|
|
368
|
-
header,
|
|
369
|
-
})
|
|
370
|
-
|
|
371
|
-
expect(res.status).toBe(200)
|
|
372
|
-
expect(await res.json()).toEqual(header)
|
|
373
|
-
})
|
|
374
|
-
|
|
375
|
-
it('Should get 200 response - cookie', async () => {
|
|
376
|
-
const cookie = {
|
|
377
|
-
hello: 'world',
|
|
378
|
-
goodbye: 'moon',
|
|
379
|
-
}
|
|
380
|
-
const res = await client.cookie.$get({
|
|
381
|
-
cookie,
|
|
382
|
-
})
|
|
383
|
-
|
|
384
|
-
expect(res.status).toBe(200)
|
|
385
|
-
expect(await res.json()).toEqual(cookie)
|
|
386
|
-
})
|
|
387
|
-
})
|
|
388
|
-
|
|
389
|
-
describe('Basic - $url()', () => {
|
|
390
|
-
const api = new Acryl().get('/', (c) => c.text('API')).get('/posts/:id', (c) => c.text('Post'))
|
|
391
|
-
const content = new Acryl().get(
|
|
392
|
-
'/search',
|
|
393
|
-
validator('query', () => {
|
|
394
|
-
return { page: '1', limit: '10' }
|
|
395
|
-
}),
|
|
396
|
-
(c) => c.text('Search')
|
|
397
|
-
)
|
|
398
|
-
const app = new Acryl()
|
|
399
|
-
.get('/', (c) => c.text('Index'))
|
|
400
|
-
.route('/api', api)
|
|
401
|
-
.route('/content', content)
|
|
402
|
-
|
|
403
|
-
it('Should return a correct url via $url().href', async () => {
|
|
404
|
-
const client = hc<typeof app>('http://fake')
|
|
405
|
-
expect(client.index.$url().href).toBe('http://fake/')
|
|
406
|
-
expect(
|
|
407
|
-
client.index.$url({
|
|
408
|
-
query: {
|
|
409
|
-
page: '123',
|
|
410
|
-
limit: '20',
|
|
411
|
-
},
|
|
412
|
-
}).href
|
|
413
|
-
).toBe('http://fake/?page=123&limit=20')
|
|
414
|
-
expect(client.api.$url().href).toBe('http://fake/api')
|
|
415
|
-
expect(
|
|
416
|
-
client.api.posts[':id'].$url({
|
|
417
|
-
param: {
|
|
418
|
-
id: '123',
|
|
419
|
-
},
|
|
420
|
-
}).href
|
|
421
|
-
).toBe('http://fake/api/posts/123')
|
|
422
|
-
expect(
|
|
423
|
-
client.content.search.$url({
|
|
424
|
-
query: {
|
|
425
|
-
page: '123',
|
|
426
|
-
limit: '20',
|
|
427
|
-
},
|
|
428
|
-
}).href
|
|
429
|
-
).toBe('http://fake/content/search?page=123&limit=20')
|
|
430
|
-
})
|
|
431
|
-
|
|
432
|
-
it.each(['http://fake', 'http://fake/', 'http://fake//', 'http://fake/api'])(
|
|
433
|
-
'Should return a correct path via $path() regardless of %s',
|
|
434
|
-
async (baseURL) => {
|
|
435
|
-
const client = hc<typeof app>(baseURL)
|
|
436
|
-
expect(client.index.$path()).toBe('/')
|
|
437
|
-
expect(
|
|
438
|
-
client.index.$path({
|
|
439
|
-
query: {
|
|
440
|
-
page: '123',
|
|
441
|
-
limit: '20',
|
|
442
|
-
},
|
|
443
|
-
})
|
|
444
|
-
).toBe('/?page=123&limit=20')
|
|
445
|
-
expect(client.api.$path()).toBe('/api')
|
|
446
|
-
expect(
|
|
447
|
-
client.api.posts[':id'].$path({
|
|
448
|
-
param: {
|
|
449
|
-
id: '123',
|
|
450
|
-
},
|
|
451
|
-
})
|
|
452
|
-
).toBe('/api/posts/123')
|
|
453
|
-
expect(
|
|
454
|
-
client.content.search.$path({
|
|
455
|
-
query: {
|
|
456
|
-
page: '123',
|
|
457
|
-
limit: '20',
|
|
458
|
-
},
|
|
459
|
-
})
|
|
460
|
-
).toBe('/content/search?page=123&limit=20')
|
|
461
|
-
}
|
|
462
|
-
)
|
|
463
|
-
})
|
|
464
|
-
|
|
465
|
-
describe('Form - Multiple Values', () => {
|
|
466
|
-
const server = setupServer(
|
|
467
|
-
http.post('http://localhost/multiple-values', async ({ request }) => {
|
|
468
|
-
const data = await request.formData()
|
|
469
|
-
return HttpResponse.json(data.getAll('key'))
|
|
470
|
-
})
|
|
471
|
-
)
|
|
472
|
-
|
|
473
|
-
beforeAll(() => server.listen())
|
|
474
|
-
afterEach(() => server.resetHandlers())
|
|
475
|
-
afterAll(() => server.close())
|
|
476
|
-
|
|
477
|
-
const client = hc('http://localhost/')
|
|
478
|
-
|
|
479
|
-
it('Should get 200 response - query', async () => {
|
|
480
|
-
// @ts-expect-error `client['multiple-values'].$post` is not typed
|
|
481
|
-
const res = await client['multiple-values'].$post({
|
|
482
|
-
form: {
|
|
483
|
-
key: ['foo', 'bar'],
|
|
484
|
-
},
|
|
485
|
-
})
|
|
486
|
-
expect(res.status).toBe(200)
|
|
487
|
-
expect(await res.json()).toEqual(['foo', 'bar'])
|
|
488
|
-
})
|
|
489
|
-
})
|
|
490
|
-
|
|
491
|
-
describe('Form - Undefined Values', () => {
|
|
492
|
-
const server = setupServer(
|
|
493
|
-
http.post('http://localhost/form-undefined', async ({ request }) => {
|
|
494
|
-
const data = await request.formData()
|
|
495
|
-
return HttpResponse.json({
|
|
496
|
-
keys: [...data.keys()],
|
|
497
|
-
title: data.get('title'),
|
|
498
|
-
optional: data.get('optional'),
|
|
499
|
-
})
|
|
500
|
-
})
|
|
501
|
-
)
|
|
502
|
-
|
|
503
|
-
beforeAll(() => server.listen())
|
|
504
|
-
afterEach(() => server.resetHandlers())
|
|
505
|
-
afterAll(() => server.close())
|
|
506
|
-
|
|
507
|
-
const client = hc('http://localhost/')
|
|
508
|
-
|
|
509
|
-
it('Should skip undefined values in form data', async () => {
|
|
510
|
-
// @ts-expect-error `client['form-undefined'].$post` is not typed
|
|
511
|
-
const res = await client['form-undefined'].$post({
|
|
512
|
-
form: {
|
|
513
|
-
title: 'Hello',
|
|
514
|
-
optional: undefined,
|
|
515
|
-
},
|
|
516
|
-
})
|
|
517
|
-
expect(res.status).toBe(200)
|
|
518
|
-
const json = await res.json()
|
|
519
|
-
expect(json).toEqual({
|
|
520
|
-
keys: ['title'],
|
|
521
|
-
title: 'Hello',
|
|
522
|
-
optional: null,
|
|
523
|
-
})
|
|
524
|
-
})
|
|
525
|
-
})
|
|
526
|
-
|
|
527
|
-
describe('Form arrays containing undefined', () => {
|
|
528
|
-
const app = new Acryl().post(
|
|
529
|
-
'/',
|
|
530
|
-
validator('form', () => ({}) as { tag: (string | undefined)[] | undefined }),
|
|
531
|
-
async (c) => {
|
|
532
|
-
const body = await c.req.parseBody({ all: true })
|
|
533
|
-
return c.json({ tag: body['tag'] })
|
|
534
|
-
}
|
|
535
|
-
)
|
|
536
|
-
const client = hc<typeof app>('', { fetch: app.request })
|
|
537
|
-
|
|
538
|
-
it('Should not send the literal string "undefined" for an absent entry', async () => {
|
|
539
|
-
const res = await client.index.$post({
|
|
540
|
-
form: {
|
|
541
|
-
tag: ['a', undefined, 'b'],
|
|
542
|
-
},
|
|
543
|
-
})
|
|
544
|
-
expect(res.status).toBe(200)
|
|
545
|
-
expect(await res.json()).toEqual({ tag: ['a', 'b'] })
|
|
546
|
-
})
|
|
547
|
-
|
|
548
|
-
it('Should keep an empty string in an array', async () => {
|
|
549
|
-
const res = await client.index.$post({
|
|
550
|
-
form: {
|
|
551
|
-
tag: ['a', '', 'b'],
|
|
552
|
-
},
|
|
553
|
-
})
|
|
554
|
-
expect(res.status).toBe(200)
|
|
555
|
-
expect(await res.json()).toEqual({ tag: ['a', '', 'b'] })
|
|
556
|
-
})
|
|
557
|
-
})
|
|
558
|
-
|
|
559
|
-
describe('Optional header and cookie values', () => {
|
|
560
|
-
const app = new Acryl().get(
|
|
561
|
-
'/',
|
|
562
|
-
validator('header', () => {
|
|
563
|
-
return {} as {
|
|
564
|
-
'x-required': string
|
|
565
|
-
'x-optional'?: string
|
|
566
|
-
}
|
|
567
|
-
}),
|
|
568
|
-
validator('cookie', () => {
|
|
569
|
-
return {} as {
|
|
570
|
-
required: string
|
|
571
|
-
optional?: string
|
|
572
|
-
}
|
|
573
|
-
}),
|
|
574
|
-
(c) =>
|
|
575
|
-
c.json({
|
|
576
|
-
requiredHeader: c.req.header('x-required'),
|
|
577
|
-
optionalHeader: c.req.header('x-optional') ?? null,
|
|
578
|
-
cookies: parse(c.req.header('cookie') ?? ''),
|
|
579
|
-
})
|
|
580
|
-
)
|
|
581
|
-
const client = hc<typeof app>('', { fetch: app.request })
|
|
582
|
-
|
|
583
|
-
it('Should skip undefined header and cookie values', async () => {
|
|
584
|
-
const res = await client.index.$get({
|
|
585
|
-
header: {
|
|
586
|
-
'x-required': 'header-value',
|
|
587
|
-
'x-optional': undefined,
|
|
588
|
-
},
|
|
589
|
-
cookie: {
|
|
590
|
-
required: 'cookie-value',
|
|
591
|
-
optional: undefined,
|
|
592
|
-
},
|
|
593
|
-
})
|
|
594
|
-
|
|
595
|
-
expect(res.status).toBe(200)
|
|
596
|
-
expect(await res.json()).toEqual({
|
|
597
|
-
requiredHeader: 'header-value',
|
|
598
|
-
optionalHeader: null,
|
|
599
|
-
cookies: {
|
|
600
|
-
required: 'cookie-value',
|
|
601
|
-
},
|
|
602
|
-
})
|
|
603
|
-
})
|
|
604
|
-
|
|
605
|
-
it('Should not send a Cookie header if all cookie values are undefined', async () => {
|
|
606
|
-
const app = new Acryl().get(
|
|
607
|
-
'/',
|
|
608
|
-
validator('cookie', () => {
|
|
609
|
-
return {} as {
|
|
610
|
-
optional?: string
|
|
611
|
-
}
|
|
612
|
-
}),
|
|
613
|
-
(c) => c.json({ cookieHeader: c.req.header('cookie') ?? null })
|
|
614
|
-
)
|
|
615
|
-
const client = hc<typeof app>('', { fetch: app.request })
|
|
616
|
-
|
|
617
|
-
const res = await client.index.$get({
|
|
618
|
-
cookie: {
|
|
619
|
-
optional: undefined,
|
|
620
|
-
},
|
|
621
|
-
})
|
|
622
|
-
|
|
623
|
-
expect(res.status).toBe(200)
|
|
624
|
-
expect(await res.json()).toEqual({ cookieHeader: null })
|
|
625
|
-
})
|
|
626
|
-
})
|
|
627
|
-
|
|
628
|
-
describe('Infer the response/request type', () => {
|
|
629
|
-
const app = new Acryl()
|
|
630
|
-
const route = app.get(
|
|
631
|
-
'/',
|
|
632
|
-
validator('query', () => {
|
|
633
|
-
return {
|
|
634
|
-
name: 'dummy',
|
|
635
|
-
age: 'dummy',
|
|
636
|
-
}
|
|
637
|
-
}),
|
|
638
|
-
validator('header', () => {
|
|
639
|
-
return {
|
|
640
|
-
'x-request-id': 'dummy',
|
|
641
|
-
}
|
|
642
|
-
}),
|
|
643
|
-
validator('cookie', () => {
|
|
644
|
-
return {
|
|
645
|
-
name: 'dummy',
|
|
646
|
-
}
|
|
647
|
-
}),
|
|
648
|
-
(c) =>
|
|
649
|
-
c.json({
|
|
650
|
-
id: 123,
|
|
651
|
-
title: 'Morning!',
|
|
652
|
-
})
|
|
653
|
-
)
|
|
654
|
-
|
|
655
|
-
type AppType = typeof route
|
|
656
|
-
|
|
657
|
-
it('Should infer response type the type correctly', () => {
|
|
658
|
-
const client = hc<AppType>('/')
|
|
659
|
-
const req = client.index.$get
|
|
660
|
-
|
|
661
|
-
type Actual = InferResponseType<typeof req>
|
|
662
|
-
type Expected = {
|
|
663
|
-
id: number
|
|
664
|
-
title: string
|
|
665
|
-
}
|
|
666
|
-
type verify = Expect<Equal<Expected, Actual>>
|
|
667
|
-
})
|
|
668
|
-
|
|
669
|
-
it('Should infer request type the type correctly', () => {
|
|
670
|
-
const client = hc<AppType>('/')
|
|
671
|
-
const req = client.index.$get
|
|
672
|
-
|
|
673
|
-
type Actual = InferRequestType<typeof req>
|
|
674
|
-
type Expected = {
|
|
675
|
-
age: string | string[]
|
|
676
|
-
name: string | string[]
|
|
677
|
-
}
|
|
678
|
-
type verify = Expect<Equal<Expected, Actual['query']>>
|
|
679
|
-
})
|
|
680
|
-
|
|
681
|
-
it('Should infer request header type the type correctly', () => {
|
|
682
|
-
const client = hc<AppType>('/')
|
|
683
|
-
const req = client.index.$get
|
|
684
|
-
type c = typeof req
|
|
685
|
-
|
|
686
|
-
type Actual = InferRequestType<c>
|
|
687
|
-
type Expected = {
|
|
688
|
-
'x-request-id': string
|
|
689
|
-
}
|
|
690
|
-
type verify = Expect<Equal<Expected, Actual['header']>>
|
|
691
|
-
})
|
|
692
|
-
|
|
693
|
-
it('Should infer request cookie type the type correctly', () => {
|
|
694
|
-
const client = hc<AppType>('/')
|
|
695
|
-
const req = client.index.$get
|
|
696
|
-
type c = typeof req
|
|
697
|
-
|
|
698
|
-
type Actual = InferRequestType<c>
|
|
699
|
-
type Expected = {
|
|
700
|
-
name: string
|
|
701
|
-
}
|
|
702
|
-
type verify = Expect<Equal<Expected, Actual['cookie']>>
|
|
703
|
-
})
|
|
704
|
-
|
|
705
|
-
describe('Without input', () => {
|
|
706
|
-
const route = app.get('/', (c) => c.json({ ok: true }))
|
|
707
|
-
type AppType = typeof route
|
|
708
|
-
|
|
709
|
-
it('Should infer response type the type correctly', () => {
|
|
710
|
-
const client = hc<AppType>('/')
|
|
711
|
-
const req = client.index.$get
|
|
712
|
-
|
|
713
|
-
type Actual = InferResponseType<typeof req>
|
|
714
|
-
type Expected = { ok: true }
|
|
715
|
-
type verify = Expect<Equal<Expected, Actual>>
|
|
716
|
-
})
|
|
717
|
-
|
|
718
|
-
it('Should infer request type the type correctly', () => {
|
|
719
|
-
const client = hc<AppType>('/')
|
|
720
|
-
const req = client.index.$get
|
|
721
|
-
|
|
722
|
-
type Actual = InferRequestType<typeof req>
|
|
723
|
-
type Expected = {}
|
|
724
|
-
type verify = Expect<Equal<Expected, Actual>>
|
|
725
|
-
})
|
|
726
|
-
})
|
|
727
|
-
})
|
|
728
|
-
|
|
729
|
-
describe('Merge path with `app.route()`', () => {
|
|
730
|
-
const server = setupServer(
|
|
731
|
-
http.get('http://localhost/api/search', async () => {
|
|
732
|
-
return HttpResponse.json({
|
|
733
|
-
ok: true,
|
|
734
|
-
})
|
|
735
|
-
}),
|
|
736
|
-
http.get('http://localhost/api/searchArray', async () => {
|
|
737
|
-
return HttpResponse.json([
|
|
738
|
-
{
|
|
739
|
-
ok: true,
|
|
740
|
-
},
|
|
741
|
-
])
|
|
742
|
-
}),
|
|
743
|
-
http.get('http://localhost/api/foo', async () => {
|
|
744
|
-
return HttpResponse.json({
|
|
745
|
-
ok: true,
|
|
746
|
-
})
|
|
747
|
-
}),
|
|
748
|
-
http.post('http://localhost/api/bar', async () => {
|
|
749
|
-
return HttpResponse.json({
|
|
750
|
-
ok: true,
|
|
751
|
-
})
|
|
752
|
-
}),
|
|
753
|
-
http.get('http://localhost/v1/book', async () => {
|
|
754
|
-
return HttpResponse.json({
|
|
755
|
-
ok: true,
|
|
756
|
-
})
|
|
757
|
-
})
|
|
758
|
-
)
|
|
759
|
-
|
|
760
|
-
beforeAll(() => server.listen())
|
|
761
|
-
afterEach(() => server.resetHandlers())
|
|
762
|
-
afterAll(() => server.close())
|
|
763
|
-
|
|
764
|
-
type Env = {
|
|
765
|
-
Bindings: {
|
|
766
|
-
TOKEN: string
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
it('Should have correct types', async () => {
|
|
771
|
-
const api = new Acryl<Env>().get('/search', (c) => c.json({ ok: true }))
|
|
772
|
-
const app = new Acryl<Env>().route('/api', api)
|
|
773
|
-
type AppType = typeof app
|
|
774
|
-
const client = hc<AppType>('http://localhost')
|
|
775
|
-
const res = await client.api.search.$get()
|
|
776
|
-
const data = await res.json()
|
|
777
|
-
type verify = Expect<Equal<true, typeof data.ok>>
|
|
778
|
-
expect(data.ok).toBe(true)
|
|
779
|
-
})
|
|
780
|
-
|
|
781
|
-
it('Should have correct types - basePath() then get()', async () => {
|
|
782
|
-
const base = new Acryl<Env>().basePath('/api')
|
|
783
|
-
const app = base.get('/search', (c) => c.json({ ok: true }))
|
|
784
|
-
type AppType = typeof app
|
|
785
|
-
const client = hc<AppType>('http://localhost')
|
|
786
|
-
const res = await client.api.search.$get()
|
|
787
|
-
const data = await res.json()
|
|
788
|
-
type verify = Expect<Equal<true, typeof data.ok>>
|
|
789
|
-
expect(data.ok).toBe(true)
|
|
790
|
-
})
|
|
791
|
-
|
|
792
|
-
it('Should have correct types - basePath(), route(), get()', async () => {
|
|
793
|
-
const book = new Acryl().get('/', (c) => c.json({ ok: true }))
|
|
794
|
-
const app = new Acryl().basePath('/v1').route('/book', book)
|
|
795
|
-
type AppType = typeof app
|
|
796
|
-
const client = hc<AppType>('http://localhost')
|
|
797
|
-
const res = await client.v1.book.$get()
|
|
798
|
-
const data = await res.json()
|
|
799
|
-
type verify = Expect<Equal<true, typeof data.ok>>
|
|
800
|
-
expect(data.ok).toBe(true)
|
|
801
|
-
})
|
|
802
|
-
|
|
803
|
-
it('Should have correct types - with interface', async () => {
|
|
804
|
-
interface Result {
|
|
805
|
-
ok: boolean
|
|
806
|
-
okUndefined?: boolean
|
|
807
|
-
}
|
|
808
|
-
const result: Result = { ok: true }
|
|
809
|
-
const base = new Acryl<Env>().basePath('/api')
|
|
810
|
-
const app = base.get('/search', (c) => c.json(result))
|
|
811
|
-
type AppType = typeof app
|
|
812
|
-
const client = hc<AppType>('http://localhost')
|
|
813
|
-
const res = await client.api.search.$get()
|
|
814
|
-
const data = await res.json()
|
|
815
|
-
type verify = Expect<Equal<Result, typeof data>>
|
|
816
|
-
expect(data.ok).toBe(true)
|
|
817
|
-
|
|
818
|
-
// A few more types only tests
|
|
819
|
-
interface DeepInterface {
|
|
820
|
-
l2: {
|
|
821
|
-
l3: Result
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
interface ExtraDeepInterface {
|
|
825
|
-
l4: DeepInterface
|
|
826
|
-
}
|
|
827
|
-
type verifyDeepInterface = Expect<
|
|
828
|
-
Equal<SimplifyDeepArray<DeepInterface> extends JSONValue ? true : false, true>
|
|
829
|
-
>
|
|
830
|
-
type verifyExtraDeepInterface = Expect<
|
|
831
|
-
Equal<SimplifyDeepArray<ExtraDeepInterface> extends JSONValue ? true : false, true>
|
|
832
|
-
>
|
|
833
|
-
})
|
|
834
|
-
|
|
835
|
-
it('Should have correct types - with array of interfaces', async () => {
|
|
836
|
-
interface Result {
|
|
837
|
-
ok: boolean
|
|
838
|
-
okUndefined?: boolean
|
|
839
|
-
}
|
|
840
|
-
type Results = Result[]
|
|
841
|
-
|
|
842
|
-
const results: Results = [{ ok: true }]
|
|
843
|
-
const base = new Acryl<Env>().basePath('/api')
|
|
844
|
-
const app = base.get('/searchArray', (c) => c.json(results))
|
|
845
|
-
type AppType = typeof app
|
|
846
|
-
const client = hc<AppType>('http://localhost')
|
|
847
|
-
const res = await client.api.searchArray.$get()
|
|
848
|
-
const data = await res.json()
|
|
849
|
-
type verify = Expect<Equal<Results, typeof data>>
|
|
850
|
-
expect(data[0].ok).toBe(true)
|
|
851
|
-
|
|
852
|
-
// A few more types only tests
|
|
853
|
-
type verifyNestedArrayTyped = Expect<
|
|
854
|
-
Equal<SimplifyDeepArray<[string, Results]> extends JSONValue ? true : false, true>
|
|
855
|
-
>
|
|
856
|
-
type verifyNestedArrayInterfaceArray = Expect<
|
|
857
|
-
Equal<SimplifyDeepArray<[string, Result[]]> extends JSONValue ? true : false, true>
|
|
858
|
-
>
|
|
859
|
-
type verifyExtraNestedArrayTyped = Expect<
|
|
860
|
-
Equal<SimplifyDeepArray<[string, Results[]]> extends JSONValue ? true : false, true>
|
|
861
|
-
>
|
|
862
|
-
type verifyExtraNestedArrayInterfaceArray = Expect<
|
|
863
|
-
Equal<SimplifyDeepArray<[string, Result[][]]> extends JSONValue ? true : false, true>
|
|
864
|
-
>
|
|
865
|
-
})
|
|
866
|
-
|
|
867
|
-
it('Should allow a Date object and return it as a string', async () => {
|
|
868
|
-
const app = new Acryl()
|
|
869
|
-
const route = app.get('/api/foo', (c) => c.json({ datetime: new Date() }))
|
|
870
|
-
type AppType = typeof route
|
|
871
|
-
const client = hc<AppType>('http://localhost')
|
|
872
|
-
const res = await client.api.foo.$get()
|
|
873
|
-
const { datetime } = await res.json()
|
|
874
|
-
type verify = Expect<Equal<string, typeof datetime>>
|
|
875
|
-
})
|
|
876
|
-
|
|
877
|
-
describe('Multiple endpoints', () => {
|
|
878
|
-
const api = new Acryl()
|
|
879
|
-
.get('/foo', (c) => c.json({ foo: '' }))
|
|
880
|
-
.post('/bar', (c) => c.json({ bar: 0 }))
|
|
881
|
-
const app = new Acryl().route('/api', api)
|
|
882
|
-
type AppType = typeof app
|
|
883
|
-
const client = hc<typeof app>('http://localhost')
|
|
884
|
-
|
|
885
|
-
it('Should return correct types - GET /api/foo', async () => {
|
|
886
|
-
const res = await client.api.foo.$get()
|
|
887
|
-
const data = await res.json()
|
|
888
|
-
type verify = Expect<Equal<string, typeof data.foo>>
|
|
889
|
-
})
|
|
890
|
-
|
|
891
|
-
it('Should return correct types - POST /api/bar', async () => {
|
|
892
|
-
const res = await client.api.bar.$post()
|
|
893
|
-
const data = await res.json()
|
|
894
|
-
type verify = Expect<Equal<number, typeof data.bar>>
|
|
895
|
-
})
|
|
896
|
-
it('Should work with $url', async () => {
|
|
897
|
-
const url = client.api.bar.$url()
|
|
898
|
-
expect(url.href).toBe('http://localhost/api/bar')
|
|
899
|
-
})
|
|
900
|
-
it('Should work with $path', async () => {
|
|
901
|
-
const path = client.api.bar.$path()
|
|
902
|
-
expect(path).toBe('/api/bar')
|
|
903
|
-
})
|
|
904
|
-
})
|
|
905
|
-
|
|
906
|
-
describe('With a blank path', () => {
|
|
907
|
-
const app = new Acryl().basePath('/api/v1')
|
|
908
|
-
const routes = app.route(
|
|
909
|
-
'/me',
|
|
910
|
-
new Acryl().route(
|
|
911
|
-
'',
|
|
912
|
-
new Acryl().get('', async (c) => {
|
|
913
|
-
return c.json({ name: 'acryl' })
|
|
914
|
-
})
|
|
915
|
-
)
|
|
916
|
-
)
|
|
917
|
-
const client = hc<typeof routes>('http://localhost')
|
|
918
|
-
|
|
919
|
-
it('Should infer paths correctly', async () => {
|
|
920
|
-
// Should not a throw type error
|
|
921
|
-
const url = client.api.v1.me.$url()
|
|
922
|
-
expectTypeOf<URL>(url)
|
|
923
|
-
expect(url.href).toBe('http://localhost/api/v1/me')
|
|
924
|
-
|
|
925
|
-
const path = client.api.v1.me.$path()
|
|
926
|
-
expectTypeOf<'/api/v1/me'>(path)
|
|
927
|
-
expect(path).toBe('/api/v1/me')
|
|
928
|
-
})
|
|
929
|
-
})
|
|
930
|
-
|
|
931
|
-
describe('With endpoint pathname', () => {
|
|
932
|
-
const app = new Acryl().basePath('/api/v1')
|
|
933
|
-
const routes = app.route(
|
|
934
|
-
'/me',
|
|
935
|
-
new Acryl().route(
|
|
936
|
-
'',
|
|
937
|
-
new Acryl().get('', async (c) => {
|
|
938
|
-
return c.json({ name: 'acryl' })
|
|
939
|
-
})
|
|
940
|
-
)
|
|
941
|
-
)
|
|
942
|
-
const client = hc<typeof routes>('http://localhost/proxy')
|
|
943
|
-
|
|
944
|
-
it('Should infer paths correctly', async () => {
|
|
945
|
-
// Should not a throw type error
|
|
946
|
-
const url = client.api.v1.me.$url()
|
|
947
|
-
expectTypeOf<URL>(url)
|
|
948
|
-
expect(url.href).toBe('http://localhost/proxy/api/v1/me')
|
|
949
|
-
|
|
950
|
-
const path = client.api.v1.me.$path()
|
|
951
|
-
expectTypeOf<'/api/v1/me'>(path)
|
|
952
|
-
expect(path).toBe('/api/v1/me')
|
|
953
|
-
})
|
|
954
|
-
})
|
|
955
|
-
})
|
|
956
|
-
|
|
957
|
-
describe('Use custom fetch method', () => {
|
|
958
|
-
it('Should call the custom fetch method when provided', async () => {
|
|
959
|
-
const fetchMock = vi.fn()
|
|
960
|
-
|
|
961
|
-
const api = new Acryl().get('/search', (c) => c.json({ ok: true }))
|
|
962
|
-
const app = new Acryl().route('/api', api)
|
|
963
|
-
type AppType = typeof app
|
|
964
|
-
const client = hc<AppType>('http://localhost', { fetch: fetchMock })
|
|
965
|
-
await client.api.search.$get()
|
|
966
|
-
expect(fetchMock).toHaveBeenCalledTimes(1)
|
|
967
|
-
})
|
|
968
|
-
|
|
969
|
-
it('Should return Response from custom fetch method', async () => {
|
|
970
|
-
const fetchMock = vi.fn()
|
|
971
|
-
const returnValue = new Response(null, { status: 200 })
|
|
972
|
-
fetchMock.mockReturnValueOnce(returnValue)
|
|
973
|
-
|
|
974
|
-
const api = new Acryl().get('/search', (c) => c.json({ ok: true }))
|
|
975
|
-
const app = new Acryl().route('/api', api)
|
|
976
|
-
type AppType = typeof app
|
|
977
|
-
const client = hc<AppType>('http://localhost', { fetch: fetchMock })
|
|
978
|
-
const res = await client.api.search.$get()
|
|
979
|
-
expect(res.ok).toBe(true)
|
|
980
|
-
expect(res).toEqual(returnValue)
|
|
981
|
-
})
|
|
982
|
-
})
|
|
983
|
-
|
|
984
|
-
describe('Use custom fetch (app.request) method', () => {
|
|
985
|
-
it('Should return Response from app request method', async () => {
|
|
986
|
-
const app = new Acryl().get('/search', (c) => c.json({ ok: true }))
|
|
987
|
-
type AppType = typeof app
|
|
988
|
-
const client = hc<AppType>('', { fetch: app.request })
|
|
989
|
-
const res = await client.search.$get()
|
|
990
|
-
expect(res.ok).toBe(true)
|
|
991
|
-
})
|
|
992
|
-
|
|
993
|
-
it.each([false, 0, '', null])('Should send falsy JSON value: %j', async (json) => {
|
|
994
|
-
const app = new Acryl().post(
|
|
995
|
-
'/json',
|
|
996
|
-
validator('json', (value) => value as boolean | number | string | null),
|
|
997
|
-
(c) => c.json(c.req.valid('json'))
|
|
998
|
-
)
|
|
999
|
-
const client = hc<typeof app>('', { fetch: app.request })
|
|
1000
|
-
|
|
1001
|
-
const res = await client.json.$post({ json })
|
|
1002
|
-
|
|
1003
|
-
expect(res.status).toBe(200)
|
|
1004
|
-
expect(await res.json()).toBe(json)
|
|
1005
|
-
})
|
|
1006
|
-
})
|
|
1007
|
-
|
|
1008
|
-
describe('Optional parameters in JSON response', () => {
|
|
1009
|
-
it('Should return the correct type', async () => {
|
|
1010
|
-
const app = new Acryl().get('/', (c) => {
|
|
1011
|
-
return c.json({ message: 'foo' } as { message?: string })
|
|
1012
|
-
})
|
|
1013
|
-
type AppType = typeof app
|
|
1014
|
-
const client = hc<AppType>('', { fetch: app.request })
|
|
1015
|
-
const res = await client.index.$get()
|
|
1016
|
-
const data = await res.json()
|
|
1017
|
-
expectTypeOf(data).toEqualTypeOf<{
|
|
1018
|
-
message?: string
|
|
1019
|
-
}>()
|
|
1020
|
-
})
|
|
1021
|
-
})
|
|
1022
|
-
|
|
1023
|
-
describe('ClientResponse<T>.json() returns a Union type correctly', () => {
|
|
1024
|
-
const condition = () => true
|
|
1025
|
-
const app = new Acryl().get('/', async (c) => {
|
|
1026
|
-
const ok = condition()
|
|
1027
|
-
if (ok) {
|
|
1028
|
-
return c.json({ data: 'foo' })
|
|
1029
|
-
}
|
|
1030
|
-
return c.json({ message: 'error' })
|
|
1031
|
-
})
|
|
1032
|
-
|
|
1033
|
-
const client = hc<typeof app>('', { fetch: app.request })
|
|
1034
|
-
it('Should be a Union type', async () => {
|
|
1035
|
-
const res = await client.index.$get()
|
|
1036
|
-
const json = await res.json()
|
|
1037
|
-
expectTypeOf(json).toEqualTypeOf<{ data: string } | { message: string }>()
|
|
1038
|
-
})
|
|
1039
|
-
})
|
|
1040
|
-
|
|
1041
|
-
describe('Response with different status codes', () => {
|
|
1042
|
-
const condition = () => true
|
|
1043
|
-
const app = new Acryl().get('/', async (c) => {
|
|
1044
|
-
const ok = condition()
|
|
1045
|
-
if (ok) {
|
|
1046
|
-
return c.json({ data: 'foo' }, 200)
|
|
1047
|
-
}
|
|
1048
|
-
if (!ok) {
|
|
1049
|
-
return c.json({ message: 'error' }, 400)
|
|
1050
|
-
}
|
|
1051
|
-
return c.json(null)
|
|
1052
|
-
})
|
|
1053
|
-
|
|
1054
|
-
const client = hc<typeof app>('', { fetch: app.request })
|
|
1055
|
-
|
|
1056
|
-
it('all', async () => {
|
|
1057
|
-
const res = await client.index.$get()
|
|
1058
|
-
const json = await res.json()
|
|
1059
|
-
expectTypeOf(json).toEqualTypeOf<{ data: string } | { message: string } | null>()
|
|
1060
|
-
})
|
|
1061
|
-
|
|
1062
|
-
it('status 200', async () => {
|
|
1063
|
-
const res = await client.index.$get()
|
|
1064
|
-
if (res.status === 200) {
|
|
1065
|
-
const json = await res.json()
|
|
1066
|
-
expectTypeOf(json).toEqualTypeOf<{ data: string } | null>()
|
|
1067
|
-
}
|
|
1068
|
-
})
|
|
1069
|
-
|
|
1070
|
-
it('status 400', async () => {
|
|
1071
|
-
const res = await client.index.$get()
|
|
1072
|
-
if (res.status === 400) {
|
|
1073
|
-
const json = await res.json()
|
|
1074
|
-
expectTypeOf(json).toEqualTypeOf<{ message: string } | null>()
|
|
1075
|
-
}
|
|
1076
|
-
})
|
|
1077
|
-
|
|
1078
|
-
it('response is ok', async () => {
|
|
1079
|
-
const res = await client.index.$get()
|
|
1080
|
-
if (res.ok) {
|
|
1081
|
-
const json = await res.json()
|
|
1082
|
-
expectTypeOf(json).toEqualTypeOf<{ data: string } | null>()
|
|
1083
|
-
}
|
|
1084
|
-
})
|
|
1085
|
-
|
|
1086
|
-
it('response is not ok', async () => {
|
|
1087
|
-
const res = await client.index.$get()
|
|
1088
|
-
if (!res.ok) {
|
|
1089
|
-
const json = await res.json()
|
|
1090
|
-
expectTypeOf(json).toEqualTypeOf<{ message: string } | null>()
|
|
1091
|
-
}
|
|
1092
|
-
})
|
|
1093
|
-
})
|
|
1094
|
-
|
|
1095
|
-
describe('Infer the response type with different status codes', () => {
|
|
1096
|
-
const condition = () => true
|
|
1097
|
-
const app = new Acryl().get('/', async (c) => {
|
|
1098
|
-
const ok = condition()
|
|
1099
|
-
if (ok) {
|
|
1100
|
-
return c.json({ data: 'foo' }, 200)
|
|
1101
|
-
}
|
|
1102
|
-
if (!ok) {
|
|
1103
|
-
return c.json({ message: 'error' }, 400)
|
|
1104
|
-
}
|
|
1105
|
-
return c.json(null)
|
|
1106
|
-
})
|
|
1107
|
-
|
|
1108
|
-
const client = hc<typeof app>('', { fetch: app.request })
|
|
1109
|
-
|
|
1110
|
-
it('Should infer response type correctly', () => {
|
|
1111
|
-
const req = client.index.$get
|
|
1112
|
-
|
|
1113
|
-
type Actual = InferResponseType<typeof req>
|
|
1114
|
-
type Expected =
|
|
1115
|
-
| {
|
|
1116
|
-
data: string
|
|
1117
|
-
}
|
|
1118
|
-
| {
|
|
1119
|
-
message: string
|
|
1120
|
-
}
|
|
1121
|
-
| null
|
|
1122
|
-
type verify = Expect<Equal<Expected, Actual>>
|
|
1123
|
-
})
|
|
1124
|
-
|
|
1125
|
-
it('Should infer response type of status 200 correctly', () => {
|
|
1126
|
-
const req = client.index.$get
|
|
1127
|
-
|
|
1128
|
-
type Actual = InferResponseType<typeof req, 200>
|
|
1129
|
-
type Expected = {
|
|
1130
|
-
data: string
|
|
1131
|
-
} | null
|
|
1132
|
-
type verify = Expect<Equal<Expected, Actual>>
|
|
1133
|
-
})
|
|
1134
|
-
})
|
|
1135
|
-
|
|
1136
|
-
describe('Infer the response types from middlewares', () => {
|
|
1137
|
-
const app = new Acryl()
|
|
1138
|
-
.get(
|
|
1139
|
-
'/',
|
|
1140
|
-
validator('query', (input, c) => {
|
|
1141
|
-
if (!input.page || typeof input.page !== 'string') {
|
|
1142
|
-
return c.json({ error: 'Bad request' as const }, 400)
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
return input as { page: string }
|
|
1146
|
-
}),
|
|
1147
|
-
async (c) => {
|
|
1148
|
-
const query = c.req.valid('query')
|
|
1149
|
-
return c.json({ data: 'foo', page: query.page }, 200)
|
|
1150
|
-
}
|
|
1151
|
-
)
|
|
1152
|
-
.post(
|
|
1153
|
-
'/posts',
|
|
1154
|
-
async (c, next) => {
|
|
1155
|
-
const auth = c.req.header('authorization')
|
|
1156
|
-
if (!auth || !auth.startsWith('Bearer ')) {
|
|
1157
|
-
return c.json({ error: 'Unauthorized' as const }, 401)
|
|
1158
|
-
}
|
|
1159
|
-
return next()
|
|
1160
|
-
},
|
|
1161
|
-
validator('json', (input, c) => {
|
|
1162
|
-
if (!input.title) {
|
|
1163
|
-
return c.json({ error: 'Bad request' as const }, 400)
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
return input as { title: string }
|
|
1167
|
-
}),
|
|
1168
|
-
(c) => {
|
|
1169
|
-
const data = c.req.valid('json')
|
|
1170
|
-
return c.json(data, 200)
|
|
1171
|
-
}
|
|
1172
|
-
)
|
|
1173
|
-
|
|
1174
|
-
type AppType = typeof app
|
|
1175
|
-
const client = hc<AppType>('', { fetch: app.request })
|
|
1176
|
-
|
|
1177
|
-
it('Should infer response type of status 200 correctly', () => {
|
|
1178
|
-
const req = client.posts.$post
|
|
1179
|
-
|
|
1180
|
-
type Actual = InferResponseType<typeof req, 200>
|
|
1181
|
-
type Expected = {
|
|
1182
|
-
title: string
|
|
1183
|
-
}
|
|
1184
|
-
type verify = Expect<Equal<Expected, Actual>>
|
|
1185
|
-
})
|
|
1186
|
-
|
|
1187
|
-
it('Should infer response type of status 400 correctly', () => {
|
|
1188
|
-
const req = client.posts.$post
|
|
1189
|
-
|
|
1190
|
-
type Actual = InferResponseType<typeof req, 400>
|
|
1191
|
-
type Expected = {
|
|
1192
|
-
error: 'Bad request'
|
|
1193
|
-
}
|
|
1194
|
-
type verify = Expect<Equal<Expected, Actual>>
|
|
1195
|
-
})
|
|
1196
|
-
|
|
1197
|
-
it('Should infer response type of status 401 correctly', () => {
|
|
1198
|
-
const req = client.posts.$post
|
|
1199
|
-
|
|
1200
|
-
type Actual = InferResponseType<typeof req, 401>
|
|
1201
|
-
type Expected = {
|
|
1202
|
-
error: 'Unauthorized'
|
|
1203
|
-
}
|
|
1204
|
-
type verify = Expect<Equal<Expected, Actual>>
|
|
1205
|
-
})
|
|
1206
|
-
|
|
1207
|
-
it('Should infer all possible response statuses', async () => {
|
|
1208
|
-
const req = await client.posts.$post({
|
|
1209
|
-
json: {
|
|
1210
|
-
title: 'hello',
|
|
1211
|
-
},
|
|
1212
|
-
})
|
|
1213
|
-
|
|
1214
|
-
type Actual = typeof req.status
|
|
1215
|
-
type Expected = 200 | 400 | 401
|
|
1216
|
-
type verify = Expect<Equal<Expected, Actual>>
|
|
1217
|
-
})
|
|
1218
|
-
|
|
1219
|
-
it('Should properly assign response to corresponding status', async () => {
|
|
1220
|
-
const req = await client.posts.$post({
|
|
1221
|
-
json: {
|
|
1222
|
-
title: 'hello',
|
|
1223
|
-
},
|
|
1224
|
-
})
|
|
1225
|
-
|
|
1226
|
-
if (req.status === 200) {
|
|
1227
|
-
const data = await req.json()
|
|
1228
|
-
|
|
1229
|
-
expectTypeOf(data).toEqualTypeOf<{ title: string }>()
|
|
1230
|
-
} else if (req.status === 400) {
|
|
1231
|
-
const data = await req.json()
|
|
1232
|
-
|
|
1233
|
-
expectTypeOf(data).toEqualTypeOf<{ error: 'Bad request' }>()
|
|
1234
|
-
} else if (req.status === 401) {
|
|
1235
|
-
const data = await req.json()
|
|
1236
|
-
|
|
1237
|
-
expectTypeOf(data).toEqualTypeOf<{ error: 'Unauthorized' }>()
|
|
1238
|
-
}
|
|
1239
|
-
})
|
|
1240
|
-
})
|
|
1241
|
-
|
|
1242
|
-
const pathname = <T extends URL | string>(value: T): string =>
|
|
1243
|
-
value instanceof URL ? value.pathname : value
|
|
1244
|
-
|
|
1245
|
-
describe.each(['$path', '$url'] as const)('%s() with a param option', (cmd) => {
|
|
1246
|
-
const app = new Acryl()
|
|
1247
|
-
.get('/posts/:id/comments', (c) => c.json({ ok: true }))
|
|
1248
|
-
.get('/something/:firstId/:secondId/:version?', (c) => c.json({ ok: true }))
|
|
1249
|
-
.get('/docs/:page', (c) => c.json({ ok: true }))
|
|
1250
|
-
.get('/files/:dir/', (c) => c.json({ ok: true }))
|
|
1251
|
-
.get('/:page', (c) => c.json({ ok: true }))
|
|
1252
|
-
.get('/index/:v?', (c) => c.json({ ok: true }))
|
|
1253
|
-
type AppType = typeof app
|
|
1254
|
-
const client = hc<AppType>('http://localhost')
|
|
1255
|
-
|
|
1256
|
-
it('Should return the correct url path - /posts/123/comments', async () => {
|
|
1257
|
-
const value = client.posts[':id'].comments[cmd]({
|
|
1258
|
-
param: {
|
|
1259
|
-
id: '123',
|
|
1260
|
-
},
|
|
1261
|
-
})
|
|
1262
|
-
expect(pathname(value)).toBe('/posts/123/comments')
|
|
1263
|
-
})
|
|
1264
|
-
|
|
1265
|
-
it('Should return the correct path - /posts/:id/comments', async () => {
|
|
1266
|
-
const value = client.posts[':id'].comments[cmd]()
|
|
1267
|
-
expect(pathname(value)).toBe('/posts/:id/comments')
|
|
1268
|
-
})
|
|
1269
|
-
|
|
1270
|
-
it('Should return the correct path - /something/123/456', async () => {
|
|
1271
|
-
const value = client.something[':firstId'][':secondId'][':version?'][cmd]({
|
|
1272
|
-
param: {
|
|
1273
|
-
firstId: '123',
|
|
1274
|
-
secondId: '456',
|
|
1275
|
-
version: undefined,
|
|
1276
|
-
},
|
|
1277
|
-
})
|
|
1278
|
-
expect(pathname(value)).toBe('/something/123/456')
|
|
1279
|
-
})
|
|
1280
|
-
|
|
1281
|
-
it('Should keep a param value of "index" - /docs/index', async () => {
|
|
1282
|
-
const value = client.docs[':page'][cmd]({
|
|
1283
|
-
param: {
|
|
1284
|
-
page: 'index',
|
|
1285
|
-
},
|
|
1286
|
-
})
|
|
1287
|
-
expect(pathname(value)).toBe('/docs/index')
|
|
1288
|
-
})
|
|
1289
|
-
|
|
1290
|
-
it('Should keep a root-level param value of "index" - /index', async () => {
|
|
1291
|
-
const value = client[':page'][cmd]({
|
|
1292
|
-
param: {
|
|
1293
|
-
page: 'index',
|
|
1294
|
-
},
|
|
1295
|
-
})
|
|
1296
|
-
expect(pathname(value)).toBe('/index')
|
|
1297
|
-
})
|
|
1298
|
-
|
|
1299
|
-
it('Should keep a literal index segment when an optional param is omitted - /index', async () => {
|
|
1300
|
-
const value = client.index[':v?'][cmd]({
|
|
1301
|
-
param: {
|
|
1302
|
-
v: undefined,
|
|
1303
|
-
},
|
|
1304
|
-
})
|
|
1305
|
-
expect(pathname(value)).toBe('/index')
|
|
1306
|
-
})
|
|
1307
|
-
|
|
1308
|
-
it('Should still drop the index alias of a route with a param - /files/123', async () => {
|
|
1309
|
-
const value = client.files[':dir'].index[cmd]({
|
|
1310
|
-
param: {
|
|
1311
|
-
dir: '123',
|
|
1312
|
-
},
|
|
1313
|
-
})
|
|
1314
|
-
expect(pathname(value)).toBe('/files/123')
|
|
1315
|
-
})
|
|
1316
|
-
})
|
|
1317
|
-
|
|
1318
|
-
describe('$url() / $path() with a query option', () => {
|
|
1319
|
-
const app = new Acryl()
|
|
1320
|
-
.get(
|
|
1321
|
-
'/posts',
|
|
1322
|
-
validator('query', () => {
|
|
1323
|
-
return {} as { filter: 'test' }
|
|
1324
|
-
}),
|
|
1325
|
-
(c) => c.json({ ok: true })
|
|
1326
|
-
)
|
|
1327
|
-
.get('/docs/:page', (c) => c.json({ ok: true }))
|
|
1328
|
-
type AppType = typeof app
|
|
1329
|
-
const client = hc<AppType>('http://localhost')
|
|
1330
|
-
|
|
1331
|
-
it('Should return the correct path - /posts?filter=test', async () => {
|
|
1332
|
-
const url = client.posts.$url({
|
|
1333
|
-
query: {
|
|
1334
|
-
filter: 'test',
|
|
1335
|
-
},
|
|
1336
|
-
})
|
|
1337
|
-
expect(url.search).toBe('?filter=test')
|
|
1338
|
-
|
|
1339
|
-
const path = client.posts.$path({
|
|
1340
|
-
query: {
|
|
1341
|
-
filter: 'test',
|
|
1342
|
-
},
|
|
1343
|
-
})
|
|
1344
|
-
expect(path).toBe('/posts?filter=test')
|
|
1345
|
-
})
|
|
1346
|
-
|
|
1347
|
-
it('Should return the correct path - /docs/index?filter=test', async () => {
|
|
1348
|
-
const url = client.docs[':page'].$url({
|
|
1349
|
-
param: {
|
|
1350
|
-
page: 'index',
|
|
1351
|
-
},
|
|
1352
|
-
query: {
|
|
1353
|
-
filter: 'test',
|
|
1354
|
-
},
|
|
1355
|
-
})
|
|
1356
|
-
expect(url.href).toBe('http://localhost/docs/index?filter=test')
|
|
1357
|
-
|
|
1358
|
-
const path = client.docs[':page'].$path({
|
|
1359
|
-
param: {
|
|
1360
|
-
page: 'index',
|
|
1361
|
-
},
|
|
1362
|
-
query: {
|
|
1363
|
-
filter: 'test',
|
|
1364
|
-
},
|
|
1365
|
-
})
|
|
1366
|
-
expect(path).toBe('/docs/index?filter=test')
|
|
1367
|
-
})
|
|
1368
|
-
})
|
|
1369
|
-
|
|
1370
|
-
describe('Client can be awaited', () => {
|
|
1371
|
-
it('Can be awaited without side effects', async () => {
|
|
1372
|
-
const client = hc('http://localhost')
|
|
1373
|
-
|
|
1374
|
-
const awaited = await client
|
|
1375
|
-
|
|
1376
|
-
expect(awaited).toEqual(client)
|
|
1377
|
-
})
|
|
1378
|
-
})
|
|
1379
|
-
|
|
1380
|
-
describe('Dynamic headers', () => {
|
|
1381
|
-
const app = new Acryl()
|
|
1382
|
-
|
|
1383
|
-
const route = app.post('/posts', (c) => {
|
|
1384
|
-
return c.json({
|
|
1385
|
-
requestDynamic: 'dummy',
|
|
1386
|
-
requestAcryl: 'dummy',
|
|
1387
|
-
})
|
|
1388
|
-
})
|
|
1389
|
-
|
|
1390
|
-
type AppType = typeof route
|
|
1391
|
-
|
|
1392
|
-
const server = setupServer(
|
|
1393
|
-
http.post('http://localhost/posts', async ({ request }) => {
|
|
1394
|
-
const requestDynamic = request.headers.get('x-dynamic')
|
|
1395
|
-
const requestAcryl = request.headers.get('x-acryl')
|
|
1396
|
-
const payload = {
|
|
1397
|
-
requestDynamic,
|
|
1398
|
-
requestAcryl,
|
|
1399
|
-
}
|
|
1400
|
-
return HttpResponse.json(payload)
|
|
1401
|
-
})
|
|
1402
|
-
)
|
|
1403
|
-
|
|
1404
|
-
beforeAll(() => server.listen())
|
|
1405
|
-
afterEach(() => server.resetHandlers())
|
|
1406
|
-
afterAll(() => server.close())
|
|
1407
|
-
|
|
1408
|
-
let dynamic = ''
|
|
1409
|
-
|
|
1410
|
-
const client = hc<AppType>('http://localhost', {
|
|
1411
|
-
headers: () => ({ 'x-acryl': 'acryl', 'x-dynamic': dynamic }),
|
|
1412
|
-
})
|
|
1413
|
-
|
|
1414
|
-
it('Should have "x-dynamic": "one"', async () => {
|
|
1415
|
-
dynamic = 'one'
|
|
1416
|
-
|
|
1417
|
-
const res = await client.posts.$post()
|
|
1418
|
-
|
|
1419
|
-
expect(res.ok).toBe(true)
|
|
1420
|
-
const data = await res.json()
|
|
1421
|
-
expect(data.requestDynamic).toEqual('one')
|
|
1422
|
-
})
|
|
1423
|
-
|
|
1424
|
-
it('Should have "x-dynamic": "two"', async () => {
|
|
1425
|
-
dynamic = 'two'
|
|
1426
|
-
|
|
1427
|
-
const res = await client.posts.$post()
|
|
1428
|
-
|
|
1429
|
-
expect(res.ok).toBe(true)
|
|
1430
|
-
const data = await res.json()
|
|
1431
|
-
expect(data.requestDynamic).toEqual('two')
|
|
1432
|
-
})
|
|
1433
|
-
|
|
1434
|
-
it('Should merge function headers with per-request object headers', async () => {
|
|
1435
|
-
const clientWithFn = hc<AppType>('http://localhost', {
|
|
1436
|
-
headers: async () => ({ 'x-acryl': 'acryl' }),
|
|
1437
|
-
})
|
|
1438
|
-
|
|
1439
|
-
const res = await clientWithFn.posts.$post({}, { headers: { 'x-dynamic': 'request' } })
|
|
1440
|
-
|
|
1441
|
-
expect(res.ok).toBe(true)
|
|
1442
|
-
const data = await res.json()
|
|
1443
|
-
expect(data.requestAcryl).toEqual('acryl')
|
|
1444
|
-
expect(data.requestDynamic).toEqual('request')
|
|
1445
|
-
})
|
|
1446
|
-
|
|
1447
|
-
it('Should merge function headers with per-request function headers', async () => {
|
|
1448
|
-
const clientWithFn = hc<AppType>('http://localhost', {
|
|
1449
|
-
headers: () => ({ 'x-acryl': 'acryl' }),
|
|
1450
|
-
})
|
|
1451
|
-
|
|
1452
|
-
const res = await clientWithFn.posts.$post(
|
|
1453
|
-
{},
|
|
1454
|
-
{ headers: async () => ({ 'x-dynamic': 'request' }) }
|
|
1455
|
-
)
|
|
1456
|
-
|
|
1457
|
-
expect(res.ok).toBe(true)
|
|
1458
|
-
const data = await res.json()
|
|
1459
|
-
expect(data.requestAcryl).toEqual('acryl')
|
|
1460
|
-
expect(data.requestDynamic).toEqual('request')
|
|
1461
|
-
})
|
|
1462
|
-
|
|
1463
|
-
it('Should merge object headers with per-request function headers', async () => {
|
|
1464
|
-
const clientWithObject = hc<AppType>('http://localhost', {
|
|
1465
|
-
headers: { 'x-acryl': 'acryl' },
|
|
1466
|
-
})
|
|
1467
|
-
|
|
1468
|
-
const res = await clientWithObject.posts.$post(
|
|
1469
|
-
{},
|
|
1470
|
-
{ headers: async () => ({ 'x-dynamic': 'request' }) }
|
|
1471
|
-
)
|
|
1472
|
-
|
|
1473
|
-
expect(res.ok).toBe(true)
|
|
1474
|
-
const data = await res.json()
|
|
1475
|
-
expect(data.requestAcryl).toEqual('acryl')
|
|
1476
|
-
expect(data.requestDynamic).toEqual('request')
|
|
1477
|
-
})
|
|
1478
|
-
|
|
1479
|
-
it('Should prioritize per-request headers when the keys conflict', async () => {
|
|
1480
|
-
const clientWithFn = hc<AppType>('http://localhost', {
|
|
1481
|
-
headers: async () => ({ 'x-acryl': 'client' }),
|
|
1482
|
-
})
|
|
1483
|
-
|
|
1484
|
-
const res = await clientWithFn.posts.$post({}, { headers: { 'x-acryl': 'request' } })
|
|
1485
|
-
|
|
1486
|
-
expect(res.ok).toBe(true)
|
|
1487
|
-
const data = await res.json()
|
|
1488
|
-
expect(data.requestAcryl).toEqual('request')
|
|
1489
|
-
})
|
|
1490
|
-
})
|
|
1491
|
-
|
|
1492
|
-
describe('RequestInit work as expected', () => {
|
|
1493
|
-
const app = new Acryl()
|
|
1494
|
-
|
|
1495
|
-
const route = app
|
|
1496
|
-
.get('/credentials', (c) => {
|
|
1497
|
-
return c.text('' as RequestCredentials)
|
|
1498
|
-
})
|
|
1499
|
-
.get('/headers', (c) => {
|
|
1500
|
-
return c.json({} as Record<string, string>)
|
|
1501
|
-
})
|
|
1502
|
-
.post('/headers', (c) => c.text('Not found', 404))
|
|
1503
|
-
|
|
1504
|
-
type AppType = typeof route
|
|
1505
|
-
|
|
1506
|
-
const server = setupServer(
|
|
1507
|
-
http.get('http://localhost/credentials', ({ request }) => {
|
|
1508
|
-
return HttpResponse.text(request.credentials)
|
|
1509
|
-
}),
|
|
1510
|
-
http.get('http://localhost/headers', ({ request }) => {
|
|
1511
|
-
const allHeaders: Record<string, string> = {}
|
|
1512
|
-
for (const [k, v] of request.headers.entries()) {
|
|
1513
|
-
allHeaders[k] = v
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
return HttpResponse.json(allHeaders)
|
|
1517
|
-
}),
|
|
1518
|
-
http.post('http://localhost/headers', () => {
|
|
1519
|
-
return HttpResponse.text('Should not be here', {
|
|
1520
|
-
status: 400,
|
|
1521
|
-
})
|
|
1522
|
-
})
|
|
1523
|
-
)
|
|
1524
|
-
|
|
1525
|
-
beforeAll(() => server.listen())
|
|
1526
|
-
afterEach(() => server.resetHandlers())
|
|
1527
|
-
afterAll(() => server.close())
|
|
1528
|
-
|
|
1529
|
-
const client = hc<AppType>('http://localhost', {
|
|
1530
|
-
headers: { 'x-acryl': 'fire' },
|
|
1531
|
-
init: {
|
|
1532
|
-
credentials: 'include',
|
|
1533
|
-
},
|
|
1534
|
-
})
|
|
1535
|
-
|
|
1536
|
-
it('Should overwrite method and fail', async () => {
|
|
1537
|
-
const res = await client.headers.$get(undefined, { init: { method: 'POST' } })
|
|
1538
|
-
|
|
1539
|
-
expect(res.ok).toBe(false)
|
|
1540
|
-
})
|
|
1541
|
-
|
|
1542
|
-
it('Should clear headers', async () => {
|
|
1543
|
-
const res = await client.headers.$get(undefined, { init: { headers: undefined } })
|
|
1544
|
-
|
|
1545
|
-
expect(res.ok).toBe(true)
|
|
1546
|
-
const data = await res.json()
|
|
1547
|
-
expect(data).toEqual({})
|
|
1548
|
-
})
|
|
1549
|
-
|
|
1550
|
-
it('Should overwrite headers', async () => {
|
|
1551
|
-
const res = await client.headers.$get(undefined, {
|
|
1552
|
-
init: { headers: new Headers({ 'x-acryl': 'awesome' }) },
|
|
1553
|
-
})
|
|
1554
|
-
|
|
1555
|
-
expect(res.ok).toBe(true)
|
|
1556
|
-
const data = await res.json()
|
|
1557
|
-
expect(data).toEqual({ 'x-acryl': 'awesome' })
|
|
1558
|
-
})
|
|
1559
|
-
|
|
1560
|
-
it('credentials is include', async () => {
|
|
1561
|
-
const res = await client.credentials.$get()
|
|
1562
|
-
|
|
1563
|
-
expect(res.ok).toBe(true)
|
|
1564
|
-
const data = await res.text()
|
|
1565
|
-
expect(data).toEqual('include')
|
|
1566
|
-
})
|
|
1567
|
-
|
|
1568
|
-
it('deepMerge should works and not unset credentials', async () => {
|
|
1569
|
-
const res = await client.credentials.$get(undefined, { init: { headers: { hi: 'hello' } } })
|
|
1570
|
-
|
|
1571
|
-
expect(res.ok).toBe(true)
|
|
1572
|
-
const data = await res.text()
|
|
1573
|
-
expect(data).toEqual('include')
|
|
1574
|
-
})
|
|
1575
|
-
|
|
1576
|
-
it('Should unset credentials', async () => {
|
|
1577
|
-
const res = await client.credentials.$get(undefined, { init: { credentials: undefined } })
|
|
1578
|
-
|
|
1579
|
-
expect(res.ok).toBe(true)
|
|
1580
|
-
const data = await res.text()
|
|
1581
|
-
expect(data).toEqual('same-origin')
|
|
1582
|
-
})
|
|
1583
|
-
})
|
|
1584
|
-
|
|
1585
|
-
describe('WebSocket URL Protocol Translation', () => {
|
|
1586
|
-
const app = new Acryl()
|
|
1587
|
-
const route = app.get(
|
|
1588
|
-
'/',
|
|
1589
|
-
upgradeWebSocket((c) => ({
|
|
1590
|
-
onMessage(event, ws) {
|
|
1591
|
-
console.log(`Message from client: ${event.data}`)
|
|
1592
|
-
ws.send('Hello from server!')
|
|
1593
|
-
},
|
|
1594
|
-
onClose: () => {
|
|
1595
|
-
console.log('Connection closed')
|
|
1596
|
-
},
|
|
1597
|
-
}))
|
|
1598
|
-
)
|
|
1599
|
-
|
|
1600
|
-
type AppType = typeof route
|
|
1601
|
-
|
|
1602
|
-
const server = setupServer()
|
|
1603
|
-
const webSocketMock = vi.fn()
|
|
1604
|
-
|
|
1605
|
-
beforeAll(() => server.listen())
|
|
1606
|
-
beforeEach(() => {
|
|
1607
|
-
vi.stubGlobal('WebSocket', webSocketMock)
|
|
1608
|
-
})
|
|
1609
|
-
afterEach(() => {
|
|
1610
|
-
vi.clearAllMocks()
|
|
1611
|
-
server.resetHandlers()
|
|
1612
|
-
})
|
|
1613
|
-
afterAll(() => server.close())
|
|
1614
|
-
|
|
1615
|
-
it('Translates HTTP to ws', async () => {
|
|
1616
|
-
const client = hc<AppType>('http://localhost')
|
|
1617
|
-
client.index.$ws()
|
|
1618
|
-
expect(webSocketMock).toHaveBeenCalledWith('ws://localhost/')
|
|
1619
|
-
})
|
|
1620
|
-
|
|
1621
|
-
it('Translates HTTPS to wss', async () => {
|
|
1622
|
-
const client = hc<AppType>('https://localhost')
|
|
1623
|
-
client.index.$ws()
|
|
1624
|
-
expect(webSocketMock).toHaveBeenCalledWith('wss://localhost/')
|
|
1625
|
-
})
|
|
1626
|
-
|
|
1627
|
-
it('Keeps ws unchanged', async () => {
|
|
1628
|
-
const client = hc<AppType>('ws://localhost')
|
|
1629
|
-
client.index.$ws()
|
|
1630
|
-
expect(webSocketMock).toHaveBeenCalledWith('ws://localhost/')
|
|
1631
|
-
})
|
|
1632
|
-
|
|
1633
|
-
it('Keeps wss unchanged', async () => {
|
|
1634
|
-
const client = hc<AppType>('wss://localhost')
|
|
1635
|
-
client.index.$ws()
|
|
1636
|
-
expect(webSocketMock).toHaveBeenCalledWith('wss://localhost/')
|
|
1637
|
-
})
|
|
1638
|
-
|
|
1639
|
-
it('Preserves an index path parameter value', async () => {
|
|
1640
|
-
const dynamicRoute = new Acryl().get(
|
|
1641
|
-
'/:id',
|
|
1642
|
-
upgradeWebSocket(() => ({}))
|
|
1643
|
-
)
|
|
1644
|
-
const client = hc<typeof dynamicRoute>('http://localhost')
|
|
1645
|
-
|
|
1646
|
-
client[':id'].$ws({ param: { id: 'index' } })
|
|
1647
|
-
|
|
1648
|
-
expect(webSocketMock).toHaveBeenCalledWith('ws://localhost/index')
|
|
1649
|
-
})
|
|
1650
|
-
})
|
|
1651
|
-
|
|
1652
|
-
describe('WebSocket URL Protocol Translation with Query Parameters', () => {
|
|
1653
|
-
const app = new Acryl()
|
|
1654
|
-
const route = app.get(
|
|
1655
|
-
'/',
|
|
1656
|
-
upgradeWebSocket((c) => ({
|
|
1657
|
-
onMessage(event, ws) {
|
|
1658
|
-
ws.send('Hello from server!')
|
|
1659
|
-
},
|
|
1660
|
-
onClose: () => {
|
|
1661
|
-
console.log('Connection closed')
|
|
1662
|
-
},
|
|
1663
|
-
}))
|
|
1664
|
-
)
|
|
1665
|
-
|
|
1666
|
-
type AppType = typeof route
|
|
1667
|
-
|
|
1668
|
-
const server = setupServer()
|
|
1669
|
-
const webSocketMock = vi.fn()
|
|
1670
|
-
|
|
1671
|
-
beforeAll(() => server.listen())
|
|
1672
|
-
beforeEach(() => {
|
|
1673
|
-
vi.stubGlobal('WebSocket', webSocketMock)
|
|
1674
|
-
})
|
|
1675
|
-
afterEach(() => {
|
|
1676
|
-
vi.clearAllMocks()
|
|
1677
|
-
server.resetHandlers()
|
|
1678
|
-
})
|
|
1679
|
-
afterAll(() => server.close())
|
|
1680
|
-
|
|
1681
|
-
it('Translates HTTP to ws and includes query parameters', async () => {
|
|
1682
|
-
const client = hc<AppType>('http://localhost')
|
|
1683
|
-
client.index.$ws({
|
|
1684
|
-
query: {
|
|
1685
|
-
id: '123',
|
|
1686
|
-
type: 'test',
|
|
1687
|
-
tag: ['a', 'b'],
|
|
1688
|
-
},
|
|
1689
|
-
})
|
|
1690
|
-
expect(webSocketMock).toHaveBeenCalledWith('ws://localhost/?id=123&type=test&tag=a&tag=b')
|
|
1691
|
-
})
|
|
1692
|
-
|
|
1693
|
-
it('Translates HTTPS to wss and includes query parameters', async () => {
|
|
1694
|
-
const client = hc<AppType>('https://localhost')
|
|
1695
|
-
client.index.$ws({
|
|
1696
|
-
query: {
|
|
1697
|
-
id: '456',
|
|
1698
|
-
type: 'secure',
|
|
1699
|
-
},
|
|
1700
|
-
})
|
|
1701
|
-
expect(webSocketMock).toHaveBeenCalledWith('wss://localhost/?id=456&type=secure')
|
|
1702
|
-
})
|
|
1703
|
-
|
|
1704
|
-
it('Keeps ws unchanged and includes query parameters', async () => {
|
|
1705
|
-
const client = hc<AppType>('ws://localhost')
|
|
1706
|
-
client.index.$ws({
|
|
1707
|
-
query: {
|
|
1708
|
-
id: '789',
|
|
1709
|
-
type: 'plain',
|
|
1710
|
-
},
|
|
1711
|
-
})
|
|
1712
|
-
expect(webSocketMock).toHaveBeenCalledWith('ws://localhost/?id=789&type=plain')
|
|
1713
|
-
})
|
|
1714
|
-
|
|
1715
|
-
it('Keeps wss unchanged and includes query parameters', async () => {
|
|
1716
|
-
const client = hc<AppType>('wss://localhost')
|
|
1717
|
-
client.index.$ws({
|
|
1718
|
-
query: {
|
|
1719
|
-
id: '1011',
|
|
1720
|
-
type: 'secure',
|
|
1721
|
-
},
|
|
1722
|
-
})
|
|
1723
|
-
expect(webSocketMock).toHaveBeenCalledWith('wss://localhost/?id=1011&type=secure')
|
|
1724
|
-
})
|
|
1725
|
-
})
|
|
1726
|
-
|
|
1727
|
-
describe('Client can be console.log in react native', () => {
|
|
1728
|
-
it('Returns a function name with function.name.toString', async () => {
|
|
1729
|
-
const client = hc('http://localhost')
|
|
1730
|
-
// @ts-ignore
|
|
1731
|
-
expect(client.posts.name.toString()).toEqual('posts')
|
|
1732
|
-
})
|
|
1733
|
-
|
|
1734
|
-
it('Returns a function name with function.name.valueOf', async () => {
|
|
1735
|
-
const client = hc('http://localhost')
|
|
1736
|
-
// @ts-ignore
|
|
1737
|
-
expect(client.posts.name.valueOf()).toEqual('posts')
|
|
1738
|
-
})
|
|
1739
|
-
|
|
1740
|
-
it('Returns a function with function.valueOf', async () => {
|
|
1741
|
-
const client = hc('http://localhost')
|
|
1742
|
-
expect(typeof client.posts.valueOf()).toEqual('function')
|
|
1743
|
-
})
|
|
1744
|
-
|
|
1745
|
-
it('Returns a function source with function.toString', async () => {
|
|
1746
|
-
const client = hc('http://localhost')
|
|
1747
|
-
expect(client.posts.toString()).toMatch('function proxyCallback')
|
|
1748
|
-
})
|
|
1749
|
-
})
|
|
1750
|
-
|
|
1751
|
-
describe('Text response', () => {
|
|
1752
|
-
const text = 'My name is Acryl'
|
|
1753
|
-
const obj = { ok: true }
|
|
1754
|
-
const server = setupServer(
|
|
1755
|
-
http.get('http://localhost/about/me', async () => {
|
|
1756
|
-
return HttpResponse.text(text)
|
|
1757
|
-
}),
|
|
1758
|
-
http.get('http://localhost/api', async ({ request }) => {
|
|
1759
|
-
return HttpResponse.json(obj)
|
|
1760
|
-
})
|
|
1761
|
-
)
|
|
1762
|
-
|
|
1763
|
-
beforeAll(() => server.listen())
|
|
1764
|
-
afterEach(() => server.resetHandlers())
|
|
1765
|
-
afterAll(() => server.close())
|
|
1766
|
-
|
|
1767
|
-
const app = new Acryl().get('/about/me', (c) => c.text(text)).get('/api', (c) => c.json(obj))
|
|
1768
|
-
const client = hc<typeof app>('http://localhost/')
|
|
1769
|
-
|
|
1770
|
-
it('Should be never with res.json() - /about/me', async () => {
|
|
1771
|
-
const res = await client.about.me.$get()
|
|
1772
|
-
type Actual = ReturnType<typeof res.json>
|
|
1773
|
-
type Expected = Promise<never>
|
|
1774
|
-
type verify = Expect<Equal<Expected, Actual>>
|
|
1775
|
-
})
|
|
1776
|
-
|
|
1777
|
-
it('Should be "Hello, World!" with res.text() - /about/me', async () => {
|
|
1778
|
-
const res = await client.about.me.$get()
|
|
1779
|
-
const data = await res.text()
|
|
1780
|
-
expectTypeOf(data).toEqualTypeOf<'My name is Acryl'>()
|
|
1781
|
-
expect(data).toBe(text)
|
|
1782
|
-
})
|
|
1783
|
-
|
|
1784
|
-
/**
|
|
1785
|
-
* Also check the type of JSON response with res.text().
|
|
1786
|
-
*/
|
|
1787
|
-
it('Should be string with res.text() - /api', async () => {
|
|
1788
|
-
const res = await client.api.$get()
|
|
1789
|
-
type Actual = ReturnType<typeof res.text>
|
|
1790
|
-
type Expected = Promise<string>
|
|
1791
|
-
type verify = Expect<Equal<Expected, Actual>>
|
|
1792
|
-
})
|
|
1793
|
-
})
|
|
1794
|
-
|
|
1795
|
-
describe('Redirect response - only types', () => {
|
|
1796
|
-
const server = setupServer(
|
|
1797
|
-
http.get('http://localhost/', async () => {
|
|
1798
|
-
return HttpResponse.redirect('/')
|
|
1799
|
-
})
|
|
1800
|
-
)
|
|
1801
|
-
|
|
1802
|
-
beforeAll(() => server.listen())
|
|
1803
|
-
afterEach(() => server.resetHandlers())
|
|
1804
|
-
afterAll(() => server.close())
|
|
1805
|
-
|
|
1806
|
-
const condition = () => true
|
|
1807
|
-
const app = new Acryl().get('/', async (c) => {
|
|
1808
|
-
const ok = condition()
|
|
1809
|
-
const temporary = condition()
|
|
1810
|
-
if (ok) {
|
|
1811
|
-
return c.json({ ok: true }, 200)
|
|
1812
|
-
}
|
|
1813
|
-
if (temporary) {
|
|
1814
|
-
return c.redirect('/302')
|
|
1815
|
-
}
|
|
1816
|
-
return c.redirect('/301', 301)
|
|
1817
|
-
})
|
|
1818
|
-
|
|
1819
|
-
const client = hc<typeof app>('http://localhost/')
|
|
1820
|
-
const req = client.index.$get
|
|
1821
|
-
|
|
1822
|
-
it('Should infer request type the type correctly', () => {
|
|
1823
|
-
type Actual = InferResponseType<typeof req>
|
|
1824
|
-
type Expected =
|
|
1825
|
-
| {
|
|
1826
|
-
ok: true
|
|
1827
|
-
}
|
|
1828
|
-
| undefined
|
|
1829
|
-
type verify = Expect<Equal<Expected, Actual>>
|
|
1830
|
-
})
|
|
1831
|
-
|
|
1832
|
-
it('Should infer response type correctly', async () => {
|
|
1833
|
-
const res = await req()
|
|
1834
|
-
if (res.ok) {
|
|
1835
|
-
const data = await res.json()
|
|
1836
|
-
expectTypeOf(data).toMatchTypeOf({ ok: true })
|
|
1837
|
-
}
|
|
1838
|
-
if (res.status === 301) {
|
|
1839
|
-
type Expected = ClientResponse<undefined, 301, 'redirect'>
|
|
1840
|
-
type verify = Expect<Equal<Expected, typeof res>>
|
|
1841
|
-
}
|
|
1842
|
-
if (res.status === 302) {
|
|
1843
|
-
type Expected = ClientResponse<undefined, 302, 'redirect'>
|
|
1844
|
-
type verify = Expect<Equal<Expected, typeof res>>
|
|
1845
|
-
}
|
|
1846
|
-
})
|
|
1847
|
-
})
|
|
1848
|
-
|
|
1849
|
-
describe('WebSocket Provider Integration', () => {
|
|
1850
|
-
const app = new Acryl()
|
|
1851
|
-
const route = app.get(
|
|
1852
|
-
'/',
|
|
1853
|
-
upgradeWebSocket((c) => ({
|
|
1854
|
-
onMessage(event, ws) {
|
|
1855
|
-
ws.send('Hello from server!')
|
|
1856
|
-
},
|
|
1857
|
-
onClose() {
|
|
1858
|
-
console.log('Connection closed')
|
|
1859
|
-
},
|
|
1860
|
-
}))
|
|
1861
|
-
)
|
|
1862
|
-
|
|
1863
|
-
type AppType = typeof route
|
|
1864
|
-
|
|
1865
|
-
const server = setupServer()
|
|
1866
|
-
beforeAll(() => server.listen())
|
|
1867
|
-
afterEach(() => {
|
|
1868
|
-
vi.clearAllMocks()
|
|
1869
|
-
server.resetHandlers()
|
|
1870
|
-
})
|
|
1871
|
-
afterAll(() => server.close())
|
|
1872
|
-
|
|
1873
|
-
it.each([
|
|
1874
|
-
{
|
|
1875
|
-
description: 'should initialize the WebSocket provider correctly',
|
|
1876
|
-
url: 'http://localhost',
|
|
1877
|
-
query: undefined,
|
|
1878
|
-
expectedUrl: 'ws://localhost/',
|
|
1879
|
-
},
|
|
1880
|
-
{
|
|
1881
|
-
description: 'should correctly add query parameters to the WebSocket URL',
|
|
1882
|
-
url: 'http://localhost',
|
|
1883
|
-
query: { id: '123', type: 'test', tag: ['a', 'b'] },
|
|
1884
|
-
expectedUrl: 'ws://localhost/?id=123&type=test&tag=a&tag=b',
|
|
1885
|
-
},
|
|
1886
|
-
])('$description', ({ url, expectedUrl, query }) => {
|
|
1887
|
-
const webSocketMock = vi.fn()
|
|
1888
|
-
const client = hc<AppType>(url, {
|
|
1889
|
-
webSocket(url, options) {
|
|
1890
|
-
return webSocketMock(url, options)
|
|
1891
|
-
},
|
|
1892
|
-
})
|
|
1893
|
-
client.index.$ws({ query })
|
|
1894
|
-
expect(webSocketMock).toHaveBeenCalledWith(expectedUrl, undefined)
|
|
1895
|
-
})
|
|
1896
|
-
})
|
|
1897
|
-
|
|
1898
|
-
describe('Query arrays containing undefined', () => {
|
|
1899
|
-
// A validator whose query field is an optional union gives the client an input
|
|
1900
|
-
// type of `(string | undefined)[] | undefined`, so this compiles with no cast.
|
|
1901
|
-
const app = new Acryl().get(
|
|
1902
|
-
'/search',
|
|
1903
|
-
validator('query', () => ({}) as { tag: (string | undefined)[] | undefined }),
|
|
1904
|
-
(c) => c.json({ ok: true })
|
|
1905
|
-
)
|
|
1906
|
-
type AppType = typeof app
|
|
1907
|
-
|
|
1908
|
-
it('Should not send the literal string "undefined" for an absent entry', async () => {
|
|
1909
|
-
let requestedUrl = ''
|
|
1910
|
-
const client = hc<AppType>('http://localhost', {
|
|
1911
|
-
fetch: (input: RequestInfo | URL) => {
|
|
1912
|
-
requestedUrl = input.toString()
|
|
1913
|
-
return Promise.resolve(new Response('{}'))
|
|
1914
|
-
},
|
|
1915
|
-
})
|
|
1916
|
-
|
|
1917
|
-
await client.search.$get({ query: { tag: ['a', undefined, 'b'] } })
|
|
1918
|
-
|
|
1919
|
-
expect(requestedUrl).toBe('http://localhost/search?tag=a&tag=b')
|
|
1920
|
-
})
|
|
1921
|
-
})
|
|
1922
|
-
|
|
1923
|
-
describe('Empty serialized query', () => {
|
|
1924
|
-
const app = new Acryl().get(
|
|
1925
|
-
'/search',
|
|
1926
|
-
validator('query', () => ({}) as { q?: string }),
|
|
1927
|
-
(c) => c.json({ ok: true })
|
|
1928
|
-
)
|
|
1929
|
-
const input = { query: { q: undefined } }
|
|
1930
|
-
|
|
1931
|
-
it('Should omit the query delimiter when no query values are serialized', async () => {
|
|
1932
|
-
let requestedUrl = ''
|
|
1933
|
-
const client = hc<typeof app>('http://localhost', {
|
|
1934
|
-
fetch: (input: RequestInfo | URL) => {
|
|
1935
|
-
requestedUrl = input.toString()
|
|
1936
|
-
return Promise.resolve(new Response('{}'))
|
|
1937
|
-
},
|
|
1938
|
-
})
|
|
1939
|
-
|
|
1940
|
-
await client.search.$get(input)
|
|
1941
|
-
|
|
1942
|
-
expect(requestedUrl).toBe('http://localhost/search')
|
|
1943
|
-
expect(client.search.$url(input).href).toBe('http://localhost/search')
|
|
1944
|
-
expect(client.search.$path(input)).toBe('/search')
|
|
1945
|
-
})
|
|
1946
|
-
|
|
1947
|
-
it('Should omit the query delimiter when a custom serializer returns no values', async () => {
|
|
1948
|
-
let requestedUrl = ''
|
|
1949
|
-
const client = hc<typeof app>('http://localhost', {
|
|
1950
|
-
buildSearchParams: () => new URLSearchParams(),
|
|
1951
|
-
fetch: (input: RequestInfo | URL) => {
|
|
1952
|
-
requestedUrl = input.toString()
|
|
1953
|
-
return Promise.resolve(new Response('{}'))
|
|
1954
|
-
},
|
|
1955
|
-
})
|
|
1956
|
-
const customInput = { query: { q: 'ignored' } }
|
|
1957
|
-
|
|
1958
|
-
await client.search.$get(customInput)
|
|
1959
|
-
|
|
1960
|
-
expect(requestedUrl).toBe('http://localhost/search')
|
|
1961
|
-
expect(client.search.$url(customInput).href).toBe('http://localhost/search')
|
|
1962
|
-
expect(client.search.$path(customInput)).toBe('/search')
|
|
1963
|
-
})
|
|
1964
|
-
|
|
1965
|
-
it('Should preserve empty-string query values', async () => {
|
|
1966
|
-
let requestedUrl = ''
|
|
1967
|
-
const client = hc<typeof app>('http://localhost', {
|
|
1968
|
-
fetch: (input: RequestInfo | URL) => {
|
|
1969
|
-
requestedUrl = input.toString()
|
|
1970
|
-
return Promise.resolve(new Response('{}'))
|
|
1971
|
-
},
|
|
1972
|
-
})
|
|
1973
|
-
const emptyStringInput = { query: { q: '' } }
|
|
1974
|
-
|
|
1975
|
-
await client.search.$get(emptyStringInput)
|
|
1976
|
-
|
|
1977
|
-
expect(requestedUrl).toBe('http://localhost/search?q=')
|
|
1978
|
-
expect(client.search.$url(emptyStringInput).href).toBe('http://localhost/search?q=')
|
|
1979
|
-
expect(client.search.$path(emptyStringInput)).toBe('/search?q=')
|
|
1980
|
-
})
|
|
1981
|
-
})
|
|
1982
|
-
|
|
1983
|
-
describe('Custom buildSearchParams', () => {
|
|
1984
|
-
const app = new Acryl()
|
|
1985
|
-
const route = app.get(
|
|
1986
|
-
'/search',
|
|
1987
|
-
validator('query', () => {
|
|
1988
|
-
return {} as { q: string; tags: string[] }
|
|
1989
|
-
}),
|
|
1990
|
-
(c) => {
|
|
1991
|
-
return c.json({
|
|
1992
|
-
message: 'success',
|
|
1993
|
-
queryString: '',
|
|
1994
|
-
})
|
|
1995
|
-
}
|
|
1996
|
-
)
|
|
1997
|
-
|
|
1998
|
-
type AppType = typeof route
|
|
1999
|
-
|
|
2000
|
-
const server = setupServer(
|
|
2001
|
-
http.get('http://localhost/search', ({ request }) => {
|
|
2002
|
-
const url = new URL(request.url)
|
|
2003
|
-
return HttpResponse.json({
|
|
2004
|
-
message: 'success',
|
|
2005
|
-
queryString: url.search,
|
|
2006
|
-
})
|
|
2007
|
-
})
|
|
2008
|
-
)
|
|
2009
|
-
|
|
2010
|
-
beforeAll(() => server.listen())
|
|
2011
|
-
afterEach(() => server.resetHandlers())
|
|
2012
|
-
afterAll(() => server.close())
|
|
2013
|
-
|
|
2014
|
-
// Custom buildSearchParams that uses bracket notation for arrays (key[]=value)
|
|
2015
|
-
const customBuildSearchParams = (query: Record<string, string | string[]>) => {
|
|
2016
|
-
const searchParams = new URLSearchParams()
|
|
2017
|
-
for (const [k, v] of Object.entries(query)) {
|
|
2018
|
-
if (v === undefined) {
|
|
2019
|
-
continue
|
|
2020
|
-
}
|
|
2021
|
-
if (Array.isArray(v)) {
|
|
2022
|
-
v.forEach((item) => searchParams.append(`${k}[]`, item))
|
|
2023
|
-
} else {
|
|
2024
|
-
searchParams.set(k, v)
|
|
2025
|
-
}
|
|
2026
|
-
}
|
|
2027
|
-
return searchParams
|
|
2028
|
-
}
|
|
2029
|
-
|
|
2030
|
-
it('Should use custom buildSearchParams for query serialization', async () => {
|
|
2031
|
-
const client = hc<AppType>('http://localhost', { buildSearchParams: customBuildSearchParams })
|
|
2032
|
-
const res = await client.search.$get({ query: { q: 'test', tags: ['tag1', 'tag2', 'tag3'] } })
|
|
2033
|
-
const data = await res.json()
|
|
2034
|
-
|
|
2035
|
-
expect(res.status).toBe(200)
|
|
2036
|
-
expect(data.queryString).toBe('?q=test&tags%5B%5D=tag1&tags%5B%5D=tag2&tags%5B%5D=tag3')
|
|
2037
|
-
})
|
|
2038
|
-
|
|
2039
|
-
it('Should use default buildSearchParams when custom one is not provided', async () => {
|
|
2040
|
-
const client = hc<AppType>('http://localhost')
|
|
2041
|
-
const res = await client.search.$get({ query: { q: 'test', tags: ['tag1', 'tag2'] } })
|
|
2042
|
-
const data = await res.json()
|
|
2043
|
-
|
|
2044
|
-
expect(res.status).toBe(200)
|
|
2045
|
-
expect(data.queryString).toBe('?q=test&tags=tag1&tags=tag2')
|
|
2046
|
-
})
|
|
2047
|
-
|
|
2048
|
-
it('Should use custom buildSearchParams in $url() method', () => {
|
|
2049
|
-
const client = hc<AppType>('http://localhost', { buildSearchParams: customBuildSearchParams })
|
|
2050
|
-
const url = client.search.$url({ query: { q: 'test', tags: ['tag1', 'tag2'] } })
|
|
2051
|
-
|
|
2052
|
-
expect(url.href).toBe('http://localhost/search?q=test&tags%5B%5D=tag1&tags%5B%5D=tag2')
|
|
2053
|
-
})
|
|
2054
|
-
|
|
2055
|
-
it('Should use default buildSearchParams in $url() when custom one is not provided', () => {
|
|
2056
|
-
const client = hc<AppType>('http://localhost')
|
|
2057
|
-
const url = client.search.$url({ query: { q: 'test', tags: ['tag1', 'tag2'] } })
|
|
2058
|
-
|
|
2059
|
-
expect(url.href).toBe('http://localhost/search?q=test&tags=tag1&tags=tag2')
|
|
2060
|
-
})
|
|
2061
|
-
|
|
2062
|
-
it('Should use custom buildSearchParams in $ws() method', () => {
|
|
2063
|
-
const webSocketMock = vi.fn()
|
|
2064
|
-
const client = hc<AppType>('http://localhost', {
|
|
2065
|
-
buildSearchParams: customBuildSearchParams,
|
|
2066
|
-
webSocket(url, options) {
|
|
2067
|
-
return webSocketMock(url, options)
|
|
2068
|
-
},
|
|
2069
|
-
})
|
|
2070
|
-
// @ts-expect-error search route does not explicitly define $ws in mock AppType
|
|
2071
|
-
client.search.$ws({ query: { q: 'test', tags: ['tag1', 'tag2'] } })
|
|
2072
|
-
|
|
2073
|
-
expect(webSocketMock).toHaveBeenCalledWith(
|
|
2074
|
-
'ws://localhost/search?q=test&tags%5B%5D=tag1&tags%5B%5D=tag2',
|
|
2075
|
-
undefined
|
|
2076
|
-
)
|
|
2077
|
-
})
|
|
2078
|
-
|
|
2079
|
-
it('Should filter out undefined query parameters in $ws()', () => {
|
|
2080
|
-
const webSocketMock = vi.fn()
|
|
2081
|
-
const client = hc<AppType>('http://localhost', {
|
|
2082
|
-
webSocket(url, options) {
|
|
2083
|
-
return webSocketMock(url, options)
|
|
2084
|
-
},
|
|
2085
|
-
})
|
|
2086
|
-
// @ts-expect-error search route does not explicitly define $ws in mock AppType
|
|
2087
|
-
client.search.$ws({ query: { q: 'test', tags: undefined as any } })
|
|
2088
|
-
|
|
2089
|
-
expect(webSocketMock).toHaveBeenCalledWith('ws://localhost/search?q=test', undefined)
|
|
2090
|
-
})
|
|
2091
|
-
})
|
|
2092
|
-
|
|
2093
|
-
describe('ApplyGlobalResponse Type Helper', () => {
|
|
2094
|
-
const server = setupServer(
|
|
2095
|
-
http.get('http://localhost/api/users', () => {
|
|
2096
|
-
return HttpResponse.json({ users: ['alice', 'bob'] })
|
|
2097
|
-
}),
|
|
2098
|
-
http.get('http://localhost/api/error', () => {
|
|
2099
|
-
return HttpResponse.json(
|
|
2100
|
-
{ error: 'Internal Server Error', message: 'Something went wrong' },
|
|
2101
|
-
{ status: 500 }
|
|
2102
|
-
)
|
|
2103
|
-
}),
|
|
2104
|
-
http.get('http://localhost/api/unauthorized', () => {
|
|
2105
|
-
return HttpResponse.json({ error: 'Unauthorized', message: 'Please login' }, { status: 401 })
|
|
2106
|
-
})
|
|
2107
|
-
)
|
|
2108
|
-
|
|
2109
|
-
beforeAll(() => server.listen())
|
|
2110
|
-
afterEach(() => server.resetHandlers())
|
|
2111
|
-
afterAll(() => server.close())
|
|
2112
|
-
|
|
2113
|
-
it('Should add global error response types to all routes', () => {
|
|
2114
|
-
// Use explicit status codes for proper type narrowing
|
|
2115
|
-
const app = new Acryl().get('/api/users', (c) => c.json({ users: ['alice', 'bob'] }, 200))
|
|
2116
|
-
|
|
2117
|
-
// Apply global error responses with new object syntax
|
|
2118
|
-
type AppWithGlobalErrors = ApplyGlobalResponse<
|
|
2119
|
-
typeof app,
|
|
2120
|
-
{
|
|
2121
|
-
401: { json: { error: string; message: string } }
|
|
2122
|
-
500: { json: { error: string; message: string } }
|
|
2123
|
-
}
|
|
2124
|
-
>
|
|
2125
|
-
|
|
2126
|
-
const client = hc<AppWithGlobalErrors>('http://localhost')
|
|
2127
|
-
const req = client.api.users.$get
|
|
2128
|
-
|
|
2129
|
-
// Type should be a union of normal response and global errors
|
|
2130
|
-
type ResponseType = InferResponseType<typeof req>
|
|
2131
|
-
type Expected = { users: string[] } | { error: string; message: string }
|
|
2132
|
-
|
|
2133
|
-
type verify = Expect<Equal<ResponseType, Expected>>
|
|
2134
|
-
})
|
|
2135
|
-
|
|
2136
|
-
it('Should support multiple global error status codes', async () => {
|
|
2137
|
-
const app = new Acryl()
|
|
2138
|
-
.get('/api/users', (c) => c.json({ users: ['alice', 'bob'] }, 200))
|
|
2139
|
-
.get('/api/unauthorized', (c) =>
|
|
2140
|
-
c.json({ error: 'Unauthorized', message: 'Please login' }, 401)
|
|
2141
|
-
)
|
|
2142
|
-
.get('/api/error', (c) =>
|
|
2143
|
-
c.json({ error: 'Internal Server Error', message: 'Something went wrong' }, 500)
|
|
2144
|
-
)
|
|
2145
|
-
|
|
2146
|
-
// Apply multiple global error types in one definition
|
|
2147
|
-
type AppWithGlobalErrors = ApplyGlobalResponse<
|
|
2148
|
-
typeof app,
|
|
2149
|
-
{
|
|
2150
|
-
401: { json: { error: string; message: string } }
|
|
2151
|
-
500: { json: { error: string; message: string } }
|
|
2152
|
-
}
|
|
2153
|
-
>
|
|
2154
|
-
|
|
2155
|
-
const client = hc<AppWithGlobalErrors>('http://localhost')
|
|
2156
|
-
|
|
2157
|
-
// Verify runtime behavior for different status codes
|
|
2158
|
-
const usersRes = await client.api.users.$get()
|
|
2159
|
-
expect(usersRes.status).toBe(200)
|
|
2160
|
-
|
|
2161
|
-
const unauthorizedRes = await client.api.unauthorized.$get()
|
|
2162
|
-
expect(unauthorizedRes.status).toBe(401)
|
|
2163
|
-
expect(await unauthorizedRes.json()).toEqual({ error: 'Unauthorized', message: 'Please login' })
|
|
2164
|
-
|
|
2165
|
-
const errorRes = await client.api.error.$get()
|
|
2166
|
-
expect(errorRes.status).toBe(500)
|
|
2167
|
-
expect(await errorRes.json()).toEqual({
|
|
2168
|
-
error: 'Internal Server Error',
|
|
2169
|
-
message: 'Something went wrong',
|
|
2170
|
-
})
|
|
2171
|
-
})
|
|
2172
|
-
|
|
2173
|
-
it('Should work with onError handler pattern', () => {
|
|
2174
|
-
// Simulating typical Acryl app with onError handler
|
|
2175
|
-
// Use explicit status code for proper type narrowing
|
|
2176
|
-
const app = new Acryl().get('/api/users', (c) => c.json({ users: ['alice', 'bob'] }, 200))
|
|
2177
|
-
|
|
2178
|
-
// In real app: app.onError((err, c) => c.json({ error: err.message }, 500))
|
|
2179
|
-
type AppWithOnError = ApplyGlobalResponse<
|
|
2180
|
-
typeof app,
|
|
2181
|
-
{
|
|
2182
|
-
500: { json: { error: string } }
|
|
2183
|
-
}
|
|
2184
|
-
>
|
|
2185
|
-
|
|
2186
|
-
const client = hc<AppWithOnError>('http://localhost')
|
|
2187
|
-
const req = client.api.users.$get
|
|
2188
|
-
|
|
2189
|
-
// RPC client should know about the error format
|
|
2190
|
-
type ResponseType = InferResponseType<typeof req>
|
|
2191
|
-
type Expected = { users: string[] } | { error: string }
|
|
2192
|
-
|
|
2193
|
-
type verify = Expect<Equal<ResponseType, Expected>>
|
|
2194
|
-
})
|
|
2195
|
-
|
|
2196
|
-
it('Should keep route() paths when global responses are applied', () => {
|
|
2197
|
-
const users = new Acryl().get('/users', (c) => c.json({ users: ['alice', 'bob'] }, 200))
|
|
2198
|
-
const app = new Acryl().route('/api', users)
|
|
2199
|
-
|
|
2200
|
-
type AppWithGlobalErrors = ApplyGlobalResponse<
|
|
2201
|
-
typeof app,
|
|
2202
|
-
{
|
|
2203
|
-
500: { json: { error: string } }
|
|
2204
|
-
}
|
|
2205
|
-
>
|
|
2206
|
-
|
|
2207
|
-
const client = hc<AppWithGlobalErrors>('http://localhost')
|
|
2208
|
-
const req = client.api.users.$get
|
|
2209
|
-
|
|
2210
|
-
type ResponseType = InferResponseType<typeof req>
|
|
2211
|
-
type Expected = { users: string[] } | { error: string }
|
|
2212
|
-
|
|
2213
|
-
type verify = Expect<Equal<ResponseType, Expected>>
|
|
2214
|
-
})
|
|
2215
|
-
})
|
|
2216
|
-
|
|
2217
|
-
describe('PickResponseByStatusCode Type Helper', () => {
|
|
2218
|
-
it('Should keep only specified status code responses', () => {
|
|
2219
|
-
const app = new Acryl().get('/api/users', (c) => {
|
|
2220
|
-
try {
|
|
2221
|
-
return c.json({ users: ['alice', 'bob'] }, 200)
|
|
2222
|
-
} catch {
|
|
2223
|
-
return c.json({ error: 'Internal Server Error' }, 500)
|
|
2224
|
-
}
|
|
2225
|
-
})
|
|
2226
|
-
|
|
2227
|
-
type AppSuccessOnly = PickResponseByStatusCode<typeof app, 200>
|
|
2228
|
-
|
|
2229
|
-
const client = hc<AppSuccessOnly>('http://localhost')
|
|
2230
|
-
const req = client.api.users.$get
|
|
2231
|
-
|
|
2232
|
-
type ResponseType = InferResponseType<typeof req>
|
|
2233
|
-
type Expected = { users: string[] }
|
|
2234
|
-
type verify = Expect<Equal<ResponseType, Expected>>
|
|
2235
|
-
|
|
2236
|
-
type Res = Awaited<ReturnType<typeof req>>
|
|
2237
|
-
type verifyOk = Expect<Equal<Res['ok'], true>>
|
|
2238
|
-
type verifyStatus = Expect<Equal<Res['status'], 200>>
|
|
2239
|
-
})
|
|
2240
|
-
|
|
2241
|
-
it('Should work with ApplyGlobalResponse', () => {
|
|
2242
|
-
const app = new Acryl().get('/api/users', (c) => {
|
|
2243
|
-
try {
|
|
2244
|
-
return c.json({ users: ['alice', 'bob'] }, 200)
|
|
2245
|
-
} catch {
|
|
2246
|
-
return c.json({ error: 'Not Found' }, 404)
|
|
2247
|
-
}
|
|
2248
|
-
})
|
|
2249
|
-
|
|
2250
|
-
type AppWithGlobalErrors = ApplyGlobalResponse<
|
|
2251
|
-
typeof app,
|
|
2252
|
-
{
|
|
2253
|
-
401: { json: { error: string; message: string } }
|
|
2254
|
-
500: { json: { error: string; message: string } }
|
|
2255
|
-
}
|
|
2256
|
-
>
|
|
2257
|
-
|
|
2258
|
-
type AppSuccessOnly = PickResponseByStatusCode<AppWithGlobalErrors, 200>
|
|
2259
|
-
|
|
2260
|
-
const client = hc<AppSuccessOnly>('http://localhost')
|
|
2261
|
-
const req = client.api.users.$get
|
|
2262
|
-
|
|
2263
|
-
type ResponseType = InferResponseType<typeof req>
|
|
2264
|
-
type Expected = { users: string[] }
|
|
2265
|
-
type verify = Expect<Equal<ResponseType, Expected>>
|
|
2266
|
-
})
|
|
2267
|
-
|
|
2268
|
-
it('Should work with route() paths', () => {
|
|
2269
|
-
const users = new Acryl().get('/users', (c) => {
|
|
2270
|
-
try {
|
|
2271
|
-
return c.json({ users: ['alice', 'bob'] }, 200)
|
|
2272
|
-
} catch {
|
|
2273
|
-
return c.json({ error: 'error' }, 500)
|
|
2274
|
-
}
|
|
2275
|
-
})
|
|
2276
|
-
const app = new Acryl().route('/api', users)
|
|
2277
|
-
|
|
2278
|
-
type AppSuccessOnly = PickResponseByStatusCode<typeof app, 200>
|
|
2279
|
-
|
|
2280
|
-
const client = hc<AppSuccessOnly>('http://localhost')
|
|
2281
|
-
const req = client.api.users.$get
|
|
2282
|
-
|
|
2283
|
-
type ResponseType = InferResponseType<typeof req>
|
|
2284
|
-
type Expected = { users: string[] }
|
|
2285
|
-
type verify = Expect<Equal<ResponseType, Expected>>
|
|
2286
|
-
})
|
|
2287
|
-
|
|
2288
|
-
it('Should pick multiple status codes', () => {
|
|
2289
|
-
const app = new Acryl().get('/api/users', (c) => {
|
|
2290
|
-
try {
|
|
2291
|
-
return c.json({ users: ['alice', 'bob'] }, 200)
|
|
2292
|
-
} catch {
|
|
2293
|
-
return c.json({ error: 'Bad Request' }, 400)
|
|
2294
|
-
}
|
|
2295
|
-
})
|
|
2296
|
-
|
|
2297
|
-
type AppWithGlobalErrors = ApplyGlobalResponse<
|
|
2298
|
-
typeof app,
|
|
2299
|
-
{
|
|
2300
|
-
500: { json: { error: string } }
|
|
2301
|
-
}
|
|
2302
|
-
>
|
|
2303
|
-
|
|
2304
|
-
type AppFiltered = PickResponseByStatusCode<AppWithGlobalErrors, 200 | 400>
|
|
2305
|
-
|
|
2306
|
-
const client = hc<AppFiltered>('http://localhost')
|
|
2307
|
-
const req = client.api.users.$get
|
|
2308
|
-
|
|
2309
|
-
type ResponseType = InferResponseType<typeof req>
|
|
2310
|
-
type Expected = { users: string[] } | { error: string }
|
|
2311
|
-
type verify = Expect<Equal<ResponseType, Expected>>
|
|
2312
|
-
})
|
|
2313
|
-
})
|