hono-serialization 0.0.0-stage → 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.md +186 -2
- package/dist/chunk-7N7SGVDF.js +14 -0
- package/dist/chunk-7N7SGVDF.js.map +1 -0
- package/dist/chunk-E2KZJRGU.js +37 -0
- package/dist/chunk-E2KZJRGU.js.map +1 -0
- package/dist/chunk-HINZPU3T.js +55 -0
- package/dist/chunk-HINZPU3T.js.map +1 -0
- package/dist/client.d.ts +41 -0
- package/dist/client.js +113 -0
- package/dist/client.js.map +1 -0
- package/dist/codec.d.ts +10 -0
- package/dist/codec.js +8 -0
- package/dist/codec.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +134 -0
- package/dist/index.js.map +1 -0
- package/dist/types-Ce9-Amxx.d.ts +76 -0
- package/package.json +91 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.0.1
|
|
4
|
+
|
|
5
|
+
Initial release.
|
|
6
|
+
|
|
7
|
+
- Opt-in JSON serialization for [Hono](https://hono.dev) RPC with bring-your-own codecs: SuperJSON, devalue, Seroval, CBOR, or any `serialize`/`parse` pair.
|
|
8
|
+
- Header-based negotiation (`x-hono-serialization`) with ordinary JSON for unnegotiated requests.
|
|
9
|
+
- Explicit codec contracts: `types` defaults to `preserve`; declare `'json-parsed'` for ordinary JSON conversion or `'unknown'` to make no promise. Optional request-body decoding via `json.input`.
|
|
10
|
+
- Strict RPC types: `s.json` marks negotiated endpoints, `createClient` maps them by codec contract, and mismatched serializers are rejected at compile time.
|
|
11
|
+
- ESM-only package with `/codec` (shared definitions, no Hono runtime) and `/client` entry points.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sebastian Broman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,187 @@
|
|
|
1
|
-
#
|
|
1
|
+
# hono-serialization
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Opt-in serialization for [Hono](https://hono.dev) RPC. Bring your own SuperJSON, devalue, Seroval, or binary codec. No serializer is bundled.
|
|
4
|
+
|
|
5
|
+
A client sends `x-hono-serialization: <name>`. The middleware encodes `s.json` responses and optionally decodes JSON request bodies. Requests without that header, or with an unknown name, receive ordinary Hono output.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install hono-serialization hono
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Peer dependency: Hono `^4.13.13`. The package is ESM-only.
|
|
14
|
+
|
|
15
|
+
## Quick start
|
|
16
|
+
|
|
17
|
+
Define the shared codec in `codecs/superjson.ts`:
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import SuperJSON from 'superjson'
|
|
21
|
+
import { defineSerializer } from 'hono-serialization/codec'
|
|
22
|
+
|
|
23
|
+
export const superjson = defineSerializer({
|
|
24
|
+
name: 'superjson',
|
|
25
|
+
json: {
|
|
26
|
+
serialize: SuperJSON.stringify,
|
|
27
|
+
parse: SuperJSON.parse,
|
|
28
|
+
input: {
|
|
29
|
+
serialize: SuperJSON.stringify,
|
|
30
|
+
parse: SuperJSON.parse,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Create the middleware in `server.ts`:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { Hono } from 'hono'
|
|
40
|
+
import { serialization } from 'hono-serialization'
|
|
41
|
+
import { superjson } from './codecs/superjson'
|
|
42
|
+
|
|
43
|
+
const s = serialization(superjson)
|
|
44
|
+
export const app = new Hono()
|
|
45
|
+
.use('/api/*', s)
|
|
46
|
+
.get('/api/item', (c) => s.json(c, { createdAt: new Date() }))
|
|
47
|
+
|
|
48
|
+
export type AppType = typeof app
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Create the browser client in `client.ts`:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import { createClient } from 'hono-serialization/client'
|
|
55
|
+
import { superjson } from './codecs/superjson'
|
|
56
|
+
import type { AppType } from './server'
|
|
57
|
+
|
|
58
|
+
const client = createClient<AppType>('http://localhost', superjson)
|
|
59
|
+
const data = await (await client.api.item.$get()).json()
|
|
60
|
+
data.createdAt // Date
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The browser imports the codec and RPC client. The `AppType` import is erased, so it does not load `server.ts` or its middleware.
|
|
64
|
+
|
|
65
|
+
`defineSerializer` is optional: `serialization({ name, json })` accepts and validates a configuration directly. A separate definition is convenient when the server and client share the codec.
|
|
66
|
+
|
|
67
|
+
Put `defineSerializer` imports in shared codec files using `hono-serialization/codec`, which has no Hono runtime or middleware dependency. Create `s = serialization(codec)` only on the server. Import `AppType` with `import type` on the client. Import the client from `hono-serialization/client` and only the codec a page needs.
|
|
68
|
+
|
|
69
|
+
## Codec contracts and RPC types
|
|
70
|
+
|
|
71
|
+
Declare how your JSON codec changes values with `types`:
|
|
72
|
+
|
|
73
|
+
- `json.types: 'preserve'` (default): the codec promises to preserve the values and types supplied to `s.json`, such as Date, Map, and Set. The client exposes the original output type.
|
|
74
|
+
- `json.types: 'json-parsed'`: the codec promises ordinary JSON conversion, as if the values passed through `JSON.parse(JSON.stringify(...))`. The client exposes Hono's `JSONParsed<T>` type: Dates become strings.
|
|
75
|
+
- `json.types: 'unknown'`: no promise. Decoded output is `unknown`; validate it before accessing properties.
|
|
76
|
+
|
|
77
|
+
Omit `types` for lossless codecs. These are explicit codec contracts, not capabilities that TypeScript can prove about arbitrary functions. Choose `preserve` only for values supported by your codec; unsupported values must be rejected by the codec rather than silently converted. Declaring a false contract or using an unchecked cast can invalidate any TypeScript API's guarantees.
|
|
78
|
+
|
|
79
|
+
`s.json` carries an opaque output type in the app schema. `createClient` maps it according to the codec contract and checks that the codec belongs to the app's typed endpoints. Direct `hc<typeof app>` cannot access negotiated output as if it were plain JSON.
|
|
80
|
+
|
|
81
|
+
For ordinary JSON clients, use:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { hc } from 'hono/client'
|
|
85
|
+
import type { JsonApp } from 'hono-serialization'
|
|
86
|
+
|
|
87
|
+
const plain = hc<JsonApp<AppType>>('http://localhost')
|
|
88
|
+
const data = await (await plain.api.item.$get()).json()
|
|
89
|
+
data.createdAt // string
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Serializer clients reject JSON responses that omit negotiation headers or advertise a different types contract. Missing middleware, unavailable codecs, and CORS configuration errors fail explicitly instead of returning a value with the wrong static type. Mount the middleware on all JSON routes used by a serializer client, including JSON error handlers, and serve those routes with `s.json`.
|
|
93
|
+
|
|
94
|
+
`c.json`, `c.text`, `c.html`, and the rest of the context keep Hono's ordinary behavior. The middleware does not patch them. Routes served with `c.json` always return ordinary JSON: stock `hc` and `hc<JsonApp<typeof app>>` read them, while a serializer client fails closed on them. Use `s.json` for routes that participate in serialization.
|
|
95
|
+
|
|
96
|
+
`s.json` defaults to status **200**, even after `c.status(...)`. Pass another status explicitly; numeric and `{ status: ... }` arguments preserve the literal RPC status. `s.json` rejects contentless statuses (204, 304) at compile time.
|
|
97
|
+
|
|
98
|
+
## Request bodies
|
|
99
|
+
|
|
100
|
+
Bodies remain ordinary JSON unless `json.input` is provided. An input codec has its own `types`, which also defaults to `'preserve'`. The client retains rich JSON argument types only for a preserving input codec; otherwise request types undergo JSON conversion.
|
|
101
|
+
|
|
102
|
+
Mount serialization **before** JSON validators. Validate the decoded values, using schemas appropriate for the input codec. Query parameters, forms, and WebSockets keep Hono's behavior.
|
|
103
|
+
|
|
104
|
+
Input and response codecs can differ. For Seroval, use its safe JSON mode on requests:
|
|
105
|
+
|
|
106
|
+
```ts
|
|
107
|
+
import { serialize, deserialize, toJSON, fromJSON } from 'seroval'
|
|
108
|
+
|
|
109
|
+
const seroval = defineSerializer({
|
|
110
|
+
name: 'seroval',
|
|
111
|
+
json: {
|
|
112
|
+
types: 'preserve',
|
|
113
|
+
serialize,
|
|
114
|
+
parse: deserialize,
|
|
115
|
+
contentType: 'application/seroval',
|
|
116
|
+
input: {
|
|
117
|
+
types: 'preserve',
|
|
118
|
+
serialize: (value: unknown) => JSON.stringify(toJSON(value)),
|
|
119
|
+
parse: (wire: string) => fromJSON(JSON.parse(wire)),
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
})
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Never evaluate request bodies with `eval`, Seroval's `deserialize`, or a devalue `uneval` decoder. A request header is not authentication, and anyone can construct an HTTP request independently of your client.
|
|
126
|
+
|
|
127
|
+
## Wire formats
|
|
128
|
+
|
|
129
|
+
The codec serializes to a string or to bytes and parses the same shape back. A text codec returns a string, and `parse` receives a string. A binary codec must explicitly set `binary: true`, return `Uint8Array` or `ArrayBuffer`, and accept `Uint8Array` in `parse`. MIME types do not imply binary behavior.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { encode, decode } from 'cbor-x'
|
|
133
|
+
|
|
134
|
+
const cbor = defineSerializer({
|
|
135
|
+
name: 'cbor',
|
|
136
|
+
json: {
|
|
137
|
+
binary: true,
|
|
138
|
+
contentType: 'application/cbor',
|
|
139
|
+
serialize: encode,
|
|
140
|
+
parse: decode,
|
|
141
|
+
},
|
|
142
|
+
})
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Binary codecs such as CBOR work through the same JSON slot: negotiation, request decoding, and client types behave identically; only the wire representation differs.
|
|
146
|
+
|
|
147
|
+
Set `contentType` to the MIME type of the wire representation. JSON-shaped strings default to `application/json`; binary codecs default to `application/octet-stream`. JavaScript-shaped output should use a private MIME type such as `application/seroval`, rather than `application/javascript`. Invalid codec output throws instead of being coerced into an empty body.
|
|
148
|
+
|
|
149
|
+
The response advertises the codec name and its types contract. Decoding uses those declarations rather than guessing from Content-Type. Response clones and per-call fetch implementations retain decoding. `json`, `text`, `arrayBuffer`, and `bytes` share a cached body; `text()` on an encoded JSON response returns its raw wire text.
|
|
150
|
+
|
|
151
|
+
## Several codecs and custom headers
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
const s = serialization([superjson, seroval])
|
|
155
|
+
app.use('/api/*', s)
|
|
156
|
+
|
|
157
|
+
const jsonClient = createClient<AppType>(url, superjson)
|
|
158
|
+
const serovalClient = createClient<AppType>(url, seroval)
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
A helper belongs to its middleware instance. Mixing helpers from different instances throws rather than using an unrelated codec.
|
|
162
|
+
|
|
163
|
+
Configure a custom header on both ends:
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
const s = serialization(superjson, { header: 'x-my-serde' })
|
|
167
|
+
const client = createClient<AppType>(url, superjson, { header: 'x-my-serde' })
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
For CORS, allow the negotiation request header in preflight. Encoded responses expose the negotiation header and its `-types` companion. The middleware adds the configured header to `Vary` on encoded and ordinary responses while preserving existing entries.
|
|
171
|
+
|
|
172
|
+
`createClient` accepts Hono client options, including async headers and per-call fetch overrides. It always sets the negotiation header on the outgoing request. `$url`, `$path`, and `$ws` retain their Hono behavior. For private MIME types, use the wrapped `res.json()`; Hono's `parseResponse()` selects a parser by Content-Type.
|
|
173
|
+
|
|
174
|
+
## Development
|
|
175
|
+
|
|
176
|
+
```sh
|
|
177
|
+
npm ci
|
|
178
|
+
npm run typecheck
|
|
179
|
+
npm test
|
|
180
|
+
npm run build
|
|
181
|
+
npm run check:package
|
|
182
|
+
npm run check:release
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Typechecking includes positive and negative RPC assertions and dependency declarations. Publishing runs the full release check, including typechecking, tests, ESM builds, package-boundary checks, and installed tarball tests over real HTTP.
|
|
186
|
+
|
|
187
|
+
See [CHANGELOG.md](CHANGELOG.md) for release notes.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
validateSerializer
|
|
3
|
+
} from "./chunk-E2KZJRGU.js";
|
|
4
|
+
|
|
5
|
+
// src/codec.ts
|
|
6
|
+
function defineSerializer(serializer) {
|
|
7
|
+
validateSerializer(serializer);
|
|
8
|
+
return serializer;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export {
|
|
12
|
+
defineSerializer
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=chunk-7N7SGVDF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/codec.ts"],"sourcesContent":["/** Shared codec definitions: no Hono runtime or server middleware. */\nimport type { Serializer } from './types'\nimport { validateSerializer } from './validation'\n\nexport function defineSerializer<const T extends Serializer>(\n serializer: T & Record<Exclude<keyof T, keyof Serializer>, never>,\n): T {\n validateSerializer(serializer)\n return serializer\n}\n\nexport type {\n Serializer,\n JsonSerializer,\n WireSerializer,\n WireBody,\n CodecTypes,\n} from './types'\n"],"mappings":";;;;;AAIO,SAAS,iBACd,YACG;AACH,qBAAmB,UAAU;AAC7B,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// src/validation.ts
|
|
2
|
+
function validateSerializer(s) {
|
|
3
|
+
if (typeof s.name !== "string" || !s.name || s.name.trim() !== s.name || /[^\x21-\x7e]/.test(s.name)) {
|
|
4
|
+
throw new TypeError(
|
|
5
|
+
"defineSerializer: name must be a non-empty printable header value without surrounding whitespace"
|
|
6
|
+
);
|
|
7
|
+
}
|
|
8
|
+
if (!s.json) {
|
|
9
|
+
throw new TypeError("defineSerializer: json codec is required");
|
|
10
|
+
}
|
|
11
|
+
for (const key of Object.keys(s)) {
|
|
12
|
+
if (key !== "name" && key !== "json") {
|
|
13
|
+
throw new TypeError(`defineSerializer: unsupported field "${key}"`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
for (const codec of [s.json, s.json.input]) {
|
|
17
|
+
if (!codec) continue;
|
|
18
|
+
if (typeof codec.serialize !== "function" || typeof codec.parse !== "function") {
|
|
19
|
+
throw new TypeError("codecs require serialize and parse functions");
|
|
20
|
+
}
|
|
21
|
+
if (codec.binary !== void 0 && typeof codec.binary !== "boolean")
|
|
22
|
+
throw new TypeError("binary must be boolean");
|
|
23
|
+
if (codec.contentType !== void 0)
|
|
24
|
+
new Headers({ "content-type": codec.contentType });
|
|
25
|
+
}
|
|
26
|
+
if (s.json.input && !["preserve", "json-parsed"].includes(s.json.input.types ?? "preserve")) {
|
|
27
|
+
throw new TypeError("json.input.types must be preserve or json-parsed");
|
|
28
|
+
}
|
|
29
|
+
if (s.json.types !== void 0 && !["preserve", "json-parsed", "unknown"].includes(s.json.types)) {
|
|
30
|
+
throw new TypeError("json.types must be preserve, json-parsed, or unknown");
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export {
|
|
35
|
+
validateSerializer
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=chunk-E2KZJRGU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/validation.ts"],"sourcesContent":["import type { Serializer } from './types'\n\nexport function validateSerializer(s: Serializer): void {\n if (\n typeof s.name !== 'string' ||\n !s.name ||\n s.name.trim() !== s.name ||\n /[^\\x21-\\x7e]/.test(s.name)\n ) {\n throw new TypeError(\n 'defineSerializer: name must be a non-empty printable header value without surrounding whitespace',\n )\n }\n if (!s.json) {\n throw new TypeError('defineSerializer: json codec is required')\n }\n for (const key of Object.keys(s)) {\n if (key !== 'name' && key !== 'json') {\n throw new TypeError(`defineSerializer: unsupported field \"${key}\"`)\n }\n }\n for (const codec of [s.json, s.json.input]) {\n if (!codec) continue\n if (\n typeof codec.serialize !== 'function' ||\n typeof codec.parse !== 'function'\n ) {\n throw new TypeError('codecs require serialize and parse functions')\n }\n if (codec.binary !== undefined && typeof codec.binary !== 'boolean')\n throw new TypeError('binary must be boolean')\n if (codec.contentType !== undefined)\n new Headers({ 'content-type': codec.contentType })\n }\n if (\n s.json.input &&\n !['preserve', 'json-parsed'].includes(s.json.input.types ?? 'preserve')\n ) {\n throw new TypeError('json.input.types must be preserve or json-parsed')\n }\n if (\n s.json.types !== undefined &&\n !['preserve', 'json-parsed', 'unknown'].includes(s.json.types)\n ) {\n throw new TypeError('json.types must be preserve, json-parsed, or unknown')\n }\n}\n"],"mappings":";AAEO,SAAS,mBAAmB,GAAqB;AACtD,MACE,OAAO,EAAE,SAAS,YAClB,CAAC,EAAE,QACH,EAAE,KAAK,KAAK,MAAM,EAAE,QACpB,eAAe,KAAK,EAAE,IAAI,GAC1B;AACA,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,EAAE,MAAM;AACX,UAAM,IAAI,UAAU,0CAA0C;AAAA,EAChE;AACA,aAAW,OAAO,OAAO,KAAK,CAAC,GAAG;AAChC,QAAI,QAAQ,UAAU,QAAQ,QAAQ;AACpC,YAAM,IAAI,UAAU,wCAAwC,GAAG,GAAG;AAAA,IACpE;AAAA,EACF;AACA,aAAW,SAAS,CAAC,EAAE,MAAM,EAAE,KAAK,KAAK,GAAG;AAC1C,QAAI,CAAC,MAAO;AACZ,QACE,OAAO,MAAM,cAAc,cAC3B,OAAO,MAAM,UAAU,YACvB;AACA,YAAM,IAAI,UAAU,8CAA8C;AAAA,IACpE;AACA,QAAI,MAAM,WAAW,UAAa,OAAO,MAAM,WAAW;AACxD,YAAM,IAAI,UAAU,wBAAwB;AAC9C,QAAI,MAAM,gBAAgB;AACxB,UAAI,QAAQ,EAAE,gBAAgB,MAAM,YAAY,CAAC;AAAA,EACrD;AACA,MACE,EAAE,KAAK,SACP,CAAC,CAAC,YAAY,aAAa,EAAE,SAAS,EAAE,KAAK,MAAM,SAAS,UAAU,GACtE;AACA,UAAM,IAAI,UAAU,kDAAkD;AAAA,EACxE;AACA,MACE,EAAE,KAAK,UAAU,UACjB,CAAC,CAAC,YAAY,eAAe,SAAS,EAAE,SAAS,EAAE,KAAK,KAAK,GAC7D;AACA,UAAM,IAAI,UAAU,sDAAsD;AAAA,EAC5E;AACF;","names":[]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// src/wire.ts
|
|
2
|
+
var DEFAULT_HEADER = "x-hono-serialization";
|
|
3
|
+
var DEFAULT_JSON_CONTENT_TYPE = "application/json; charset=UTF-8";
|
|
4
|
+
var BINARY_CONTENT_TYPE = "application/octet-stream";
|
|
5
|
+
var typesHeader = (header) => `${header}-types`;
|
|
6
|
+
var resolve = (codec) => ({
|
|
7
|
+
serialize: codec.serialize,
|
|
8
|
+
parse: codec.parse,
|
|
9
|
+
binary: codec.binary === true,
|
|
10
|
+
contentType: codec.contentType ?? (codec.binary ? BINARY_CONTENT_TYPE : DEFAULT_JSON_CONTENT_TYPE)
|
|
11
|
+
});
|
|
12
|
+
var resolveInput = (codec) => codec.input == null ? void 0 : resolve(codec.input);
|
|
13
|
+
function encode(codec, value) {
|
|
14
|
+
const body = codec.serialize(value);
|
|
15
|
+
if (codec.binary) {
|
|
16
|
+
if (!(body instanceof Uint8Array) && !(body instanceof ArrayBuffer)) {
|
|
17
|
+
throw new TypeError(
|
|
18
|
+
"binary codec serialize() must return Uint8Array or ArrayBuffer"
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
} else if (typeof body !== "string") {
|
|
22
|
+
throw new TypeError(
|
|
23
|
+
"codec serialize() must return a string; use binary: true for bytes"
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return body;
|
|
27
|
+
}
|
|
28
|
+
function validateHeader(header) {
|
|
29
|
+
new Headers({
|
|
30
|
+
[header]: "test",
|
|
31
|
+
[typesHeader(header)]: "preserve"
|
|
32
|
+
});
|
|
33
|
+
if ([
|
|
34
|
+
"content-type",
|
|
35
|
+
"content-length",
|
|
36
|
+
"vary",
|
|
37
|
+
"access-control-expose-headers"
|
|
38
|
+
].includes(header.toLowerCase())) {
|
|
39
|
+
throw new TypeError(
|
|
40
|
+
"serialization header conflicts with an HTTP protocol header"
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export {
|
|
46
|
+
DEFAULT_HEADER,
|
|
47
|
+
DEFAULT_JSON_CONTENT_TYPE,
|
|
48
|
+
BINARY_CONTENT_TYPE,
|
|
49
|
+
typesHeader,
|
|
50
|
+
resolve,
|
|
51
|
+
resolveInput,
|
|
52
|
+
encode,
|
|
53
|
+
validateHeader
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=chunk-HINZPU3T.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/wire.ts"],"sourcesContent":["import type { JsonSerializer, WireBody, WireSerializer } from './types'\n\nexport const DEFAULT_HEADER = 'x-hono-serialization'\nexport const DEFAULT_JSON_CONTENT_TYPE = 'application/json; charset=UTF-8'\nexport const BINARY_CONTENT_TYPE = 'application/octet-stream'\nexport const typesHeader = (header: string) => `${header}-types`\nexport type Resolved = {\n serialize: (value: never) => WireBody\n parse: (data: never) => unknown\n binary: boolean\n contentType: string | undefined\n}\nexport const resolve = (codec: WireSerializer<never>): Resolved => ({\n serialize: codec.serialize,\n parse: codec.parse,\n binary: codec.binary === true,\n contentType:\n codec.contentType ??\n (codec.binary ? BINARY_CONTENT_TYPE : DEFAULT_JSON_CONTENT_TYPE),\n})\nexport const resolveInput = (codec: JsonSerializer): Resolved | undefined =>\n codec.input == null ? undefined : resolve(codec.input)\n\nexport function encode(codec: Resolved, value: unknown): WireBody {\n const body: unknown = codec.serialize(value as never)\n if (codec.binary) {\n if (!(body instanceof Uint8Array) && !(body instanceof ArrayBuffer)) {\n throw new TypeError(\n 'binary codec serialize() must return Uint8Array or ArrayBuffer',\n )\n }\n } else if (typeof body !== 'string') {\n throw new TypeError(\n 'codec serialize() must return a string; use binary: true for bytes',\n )\n }\n return body as WireBody\n}\n\nexport function validateHeader(header: string): void {\n new Headers({\n [header]: 'test',\n [typesHeader(header)]: 'preserve',\n })\n if (\n [\n 'content-type',\n 'content-length',\n 'vary',\n 'access-control-expose-headers',\n ].includes(header.toLowerCase())\n ) {\n throw new TypeError(\n 'serialization header conflicts with an HTTP protocol header',\n )\n }\n}\n"],"mappings":";AAEO,IAAM,iBAAiB;AACvB,IAAM,4BAA4B;AAClC,IAAM,sBAAsB;AAC5B,IAAM,cAAc,CAAC,WAAmB,GAAG,MAAM;AAOjD,IAAM,UAAU,CAAC,WAA4C;AAAA,EAClE,WAAW,MAAM;AAAA,EACjB,OAAO,MAAM;AAAA,EACb,QAAQ,MAAM,WAAW;AAAA,EACzB,aACE,MAAM,gBACL,MAAM,SAAS,sBAAsB;AAC1C;AACO,IAAM,eAAe,CAAC,UAC3B,MAAM,SAAS,OAAO,SAAY,QAAQ,MAAM,KAAK;AAEhD,SAAS,OAAO,OAAiB,OAA0B;AAChE,QAAM,OAAgB,MAAM,UAAU,KAAc;AACpD,MAAI,MAAM,QAAQ;AAChB,QAAI,EAAE,gBAAgB,eAAe,EAAE,gBAAgB,cAAc;AACnE,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF,WAAW,OAAO,SAAS,UAAU;AACnC,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,eAAe,QAAsB;AACnD,MAAI,QAAQ;AAAA,IACV,CAAC,MAAM,GAAG;AAAA,IACV,CAAC,YAAY,MAAM,CAAC,GAAG;AAAA,EACzB,CAAC;AACD,MACE;AAAA,IACE;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,SAAS,OAAO,YAAY,CAAC,GAC/B;AACA,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { ClientRequestOptions, hc } from 'hono/client';
|
|
3
|
+
import { C as CodecTypes, a as ClientApp, S as Serializer, W as WireSerializer, A as AppSerializer } from './types-Ce9-Amxx.js';
|
|
4
|
+
import 'hono/utils/types';
|
|
5
|
+
|
|
6
|
+
type CreateClientOptions = ClientRequestOptions & {
|
|
7
|
+
header?: string;
|
|
8
|
+
};
|
|
9
|
+
type Preserving = Serializer & {
|
|
10
|
+
json: {
|
|
11
|
+
types?: 'preserve';
|
|
12
|
+
};
|
|
13
|
+
};
|
|
14
|
+
type Converting = Serializer & {
|
|
15
|
+
json: {
|
|
16
|
+
types: 'json-parsed';
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
type Unknown = Serializer & {
|
|
20
|
+
json: {
|
|
21
|
+
types: 'unknown';
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
type WithInput = Serializer & {
|
|
25
|
+
json: {
|
|
26
|
+
input: WireSerializer & {
|
|
27
|
+
types?: 'preserve';
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
type HonoClient<T extends Hono, Prefix extends string> = ReturnType<typeof hc<T, Prefix>>;
|
|
32
|
+
type SerializerClient<T extends Hono, Types extends CodecTypes, Prefix extends string = string, RichInput extends boolean = false> = HonoClient<ClientApp<T, Types, RichInput>, Prefix>;
|
|
33
|
+
declare function createClient<T extends Hono, Prefix extends string = string>(baseUrl: Prefix, serializer: Preserving & WithInput & AppSerializer<T>, options?: CreateClientOptions): SerializerClient<T, 'preserve', Prefix, true>;
|
|
34
|
+
declare function createClient<T extends Hono, Prefix extends string = string>(baseUrl: Prefix, serializer: Preserving & AppSerializer<T>, options?: CreateClientOptions): SerializerClient<T, 'preserve', Prefix>;
|
|
35
|
+
declare function createClient<T extends Hono, Prefix extends string = string>(baseUrl: Prefix, serializer: Converting & WithInput & AppSerializer<T>, options?: CreateClientOptions): SerializerClient<T, 'json-parsed', Prefix, true>;
|
|
36
|
+
declare function createClient<T extends Hono, Prefix extends string = string>(baseUrl: Prefix, serializer: Converting & AppSerializer<T>, options?: CreateClientOptions): SerializerClient<T, 'json-parsed', Prefix>;
|
|
37
|
+
declare function createClient<T extends Hono, Prefix extends string = string>(baseUrl: Prefix, serializer: Unknown & WithInput & AppSerializer<T>, options?: CreateClientOptions): SerializerClient<T, 'unknown', Prefix, true>;
|
|
38
|
+
declare function createClient<T extends Hono, Prefix extends string = string>(baseUrl: Prefix, serializer: Unknown & AppSerializer<T>, options?: CreateClientOptions): SerializerClient<T, 'unknown', Prefix>;
|
|
39
|
+
declare function createClient<T extends Hono, Prefix extends string = string>(baseUrl: Prefix, serializer: Serializer & AppSerializer<T>, options?: CreateClientOptions): SerializerClient<T, 'unknown', Prefix>;
|
|
40
|
+
|
|
41
|
+
export { type CreateClientOptions, Serializer, type SerializerClient, createClient };
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_HEADER,
|
|
3
|
+
encode,
|
|
4
|
+
resolve,
|
|
5
|
+
resolveInput,
|
|
6
|
+
typesHeader,
|
|
7
|
+
validateHeader
|
|
8
|
+
} from "./chunk-HINZPU3T.js";
|
|
9
|
+
import {
|
|
10
|
+
validateSerializer
|
|
11
|
+
} from "./chunk-E2KZJRGU.js";
|
|
12
|
+
|
|
13
|
+
// src/client.ts
|
|
14
|
+
import { hc } from "hono/client";
|
|
15
|
+
var wrapResponse = (res, serializer, header) => {
|
|
16
|
+
let buf;
|
|
17
|
+
const getBuf = () => buf ??= res.arrayBuffer();
|
|
18
|
+
const getText = async () => new TextDecoder().decode(await getBuf());
|
|
19
|
+
return new Proxy(res, {
|
|
20
|
+
get(target, prop) {
|
|
21
|
+
if (prop === "clone")
|
|
22
|
+
return () => wrapResponse(target.clone(), serializer, header);
|
|
23
|
+
if (prop === "arrayBuffer") return getBuf;
|
|
24
|
+
if (prop === "bytes") return async () => new Uint8Array(await getBuf());
|
|
25
|
+
if (prop === "json")
|
|
26
|
+
return async () => {
|
|
27
|
+
if (res.headers.get(header) !== serializer.name) {
|
|
28
|
+
throw new TypeError(
|
|
29
|
+
"JSON response did not negotiate the requested serializer; use s.json on the server and check middleware/CORS"
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
if (res.headers.get(typesHeader(header)) !== (serializer.json.types ?? "preserve")) {
|
|
33
|
+
throw new TypeError("server and client codec types differ");
|
|
34
|
+
}
|
|
35
|
+
const slot = resolve(serializer.json);
|
|
36
|
+
const data = slot.binary ? new Uint8Array(await getBuf()) : await getText();
|
|
37
|
+
return slot.parse(data);
|
|
38
|
+
};
|
|
39
|
+
if (prop === "text") return getText;
|
|
40
|
+
const value = Reflect.get(target, prop, target);
|
|
41
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
function createClient(baseUrl, serializer, options) {
|
|
46
|
+
validateSerializer(serializer);
|
|
47
|
+
const { header: headerName, ...rest } = options ?? {};
|
|
48
|
+
const header = headerName ?? DEFAULT_HEADER;
|
|
49
|
+
validateHeader(header);
|
|
50
|
+
const inputSlot = resolveInput(serializer.json);
|
|
51
|
+
const wrapFetch = (fetcher) => {
|
|
52
|
+
const wrapped = async (input, init) => {
|
|
53
|
+
const headers = new Headers(init?.headers);
|
|
54
|
+
headers.set(header, serializer.name);
|
|
55
|
+
return wrapResponse(
|
|
56
|
+
await fetcher(input, { ...init, headers }),
|
|
57
|
+
serializer,
|
|
58
|
+
header
|
|
59
|
+
);
|
|
60
|
+
};
|
|
61
|
+
return wrapped;
|
|
62
|
+
};
|
|
63
|
+
const raw = hc(baseUrl, {
|
|
64
|
+
...rest,
|
|
65
|
+
fetch: wrapFetch(rest.fetch ?? globalThis.fetch)
|
|
66
|
+
});
|
|
67
|
+
const wrapClient = (client) => new Proxy(client, {
|
|
68
|
+
get(target, prop, receiver) {
|
|
69
|
+
const value = Reflect.get(target, prop, receiver);
|
|
70
|
+
if (typeof prop === "symbol" || prop === "then" || prop === "$url" || prop === "$path" || prop === "$ws")
|
|
71
|
+
return value;
|
|
72
|
+
return value !== null && (typeof value === "function" || typeof value === "object") ? wrapClient(value) : value;
|
|
73
|
+
},
|
|
74
|
+
apply(target, _thisArg, argArray) {
|
|
75
|
+
const args = argArray[0];
|
|
76
|
+
const opts = argArray[1] ?? {};
|
|
77
|
+
const callOptions = { ...opts };
|
|
78
|
+
if (opts.fetch) callOptions.fetch = wrapFetch(opts.fetch);
|
|
79
|
+
let callArgs = args;
|
|
80
|
+
if (inputSlot && args?.json !== void 0) {
|
|
81
|
+
const { json, ...otherArgs } = args;
|
|
82
|
+
callArgs = otherArgs;
|
|
83
|
+
const body = encode(inputSlot, json);
|
|
84
|
+
const requestHeaders = opts.headers;
|
|
85
|
+
callOptions.headers = async () => ({
|
|
86
|
+
...typeof requestHeaders === "function" ? await requestHeaders() : requestHeaders,
|
|
87
|
+
"Content-Type": inputSlot.contentType ?? "application/json"
|
|
88
|
+
});
|
|
89
|
+
callOptions.init = { ...opts.init, body };
|
|
90
|
+
if (opts.init?.headers || rest.init?.headers) {
|
|
91
|
+
const headers = new Headers(
|
|
92
|
+
opts.init?.headers ?? rest.init?.headers
|
|
93
|
+
);
|
|
94
|
+
headers.set(
|
|
95
|
+
"Content-Type",
|
|
96
|
+
inputSlot.contentType ?? "application/json"
|
|
97
|
+
);
|
|
98
|
+
callOptions.init.headers = headers;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return Reflect.apply(
|
|
102
|
+
target,
|
|
103
|
+
void 0,
|
|
104
|
+
[callArgs, callOptions]
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
return wrapClient(raw);
|
|
109
|
+
}
|
|
110
|
+
export {
|
|
111
|
+
createClient
|
|
112
|
+
};
|
|
113
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts"],"sourcesContent":["import type { Hono } from 'hono'\nimport { hc } from 'hono/client'\nimport type { ClientRequestOptions } from 'hono/client'\nimport {\n DEFAULT_HEADER,\n encode,\n typesHeader,\n resolve,\n resolveInput,\n validateHeader,\n} from './wire'\nimport { validateSerializer } from './validation'\nimport type {\n AppSerializer,\n ClientApp,\n CodecTypes,\n Serializer,\n WireSerializer,\n} from './types'\nexport type { Serializer } from './types'\n\ntype AnyClient = object\nconst wrapResponse = (\n res: Response,\n serializer: Serializer,\n header: string,\n): Response => {\n let buf: Promise<ArrayBuffer> | undefined\n const getBuf = () => (buf ??= res.arrayBuffer())\n const getText = async () => new TextDecoder().decode(await getBuf())\n return new Proxy(res, {\n get(target, prop) {\n if (prop === 'clone')\n return () => wrapResponse(target.clone(), serializer, header)\n if (prop === 'arrayBuffer') return getBuf\n if (prop === 'bytes') return async () => new Uint8Array(await getBuf())\n if (prop === 'json')\n return async () => {\n if (res.headers.get(header) !== serializer.name) {\n throw new TypeError(\n 'JSON response did not negotiate the requested serializer; use s.json on the server and check middleware/CORS',\n )\n }\n if (\n res.headers.get(typesHeader(header)) !==\n (serializer.json.types ?? 'preserve')\n ) {\n throw new TypeError('server and client codec types differ')\n }\n const slot = resolve(serializer.json)\n const data = slot.binary\n ? new Uint8Array(await getBuf())\n : await getText()\n return slot.parse(data as never)\n }\n if (prop === 'text') return getText\n const value: unknown = Reflect.get(target, prop, target)\n return typeof value === 'function' ? value.bind(target) : value\n },\n })\n}\n\nexport type CreateClientOptions = ClientRequestOptions & { header?: string }\ntype Preserving = Serializer & { json: { types?: 'preserve' } }\ntype Converting = Serializer & { json: { types: 'json-parsed' } }\ntype Unknown = Serializer & { json: { types: 'unknown' } }\ntype WithInput = Serializer & {\n json: { input: WireSerializer & { types?: 'preserve' } }\n}\ntype HonoClient<T extends Hono, Prefix extends string> = ReturnType<\n typeof hc<T, Prefix>\n>\nexport type SerializerClient<\n T extends Hono,\n Types extends CodecTypes,\n Prefix extends string = string,\n RichInput extends boolean = false,\n> = HonoClient<ClientApp<T, Types, RichInput>, Prefix>\nexport function createClient<T extends Hono, Prefix extends string = string>(\n baseUrl: Prefix,\n serializer: Preserving & WithInput & AppSerializer<T>,\n options?: CreateClientOptions,\n): SerializerClient<T, 'preserve', Prefix, true>\nexport function createClient<T extends Hono, Prefix extends string = string>(\n baseUrl: Prefix,\n serializer: Preserving & AppSerializer<T>,\n options?: CreateClientOptions,\n): SerializerClient<T, 'preserve', Prefix>\nexport function createClient<T extends Hono, Prefix extends string = string>(\n baseUrl: Prefix,\n serializer: Converting & WithInput & AppSerializer<T>,\n options?: CreateClientOptions,\n): SerializerClient<T, 'json-parsed', Prefix, true>\nexport function createClient<T extends Hono, Prefix extends string = string>(\n baseUrl: Prefix,\n serializer: Converting & AppSerializer<T>,\n options?: CreateClientOptions,\n): SerializerClient<T, 'json-parsed', Prefix>\nexport function createClient<T extends Hono, Prefix extends string = string>(\n baseUrl: Prefix,\n serializer: Unknown & WithInput & AppSerializer<T>,\n options?: CreateClientOptions,\n): SerializerClient<T, 'unknown', Prefix, true>\nexport function createClient<T extends Hono, Prefix extends string = string>(\n baseUrl: Prefix,\n serializer: Unknown & AppSerializer<T>,\n options?: CreateClientOptions,\n): SerializerClient<T, 'unknown', Prefix>\nexport function createClient<T extends Hono, Prefix extends string = string>(\n baseUrl: Prefix,\n serializer: Serializer & AppSerializer<T>,\n options?: CreateClientOptions,\n): SerializerClient<T, 'unknown', Prefix>\nexport function createClient(\n baseUrl: string,\n serializer: Serializer,\n options?: CreateClientOptions,\n): object {\n validateSerializer(serializer)\n const { header: headerName, ...rest } = options ?? {}\n const header = headerName ?? DEFAULT_HEADER\n validateHeader(header)\n const inputSlot = resolveInput(serializer.json)\n const wrapFetch = (fetcher: NonNullable<ClientRequestOptions['fetch']>) => {\n const wrapped = async (\n input: RequestInfo | URL,\n init?: RequestInit,\n ): Promise<Response> => {\n const headers = new Headers(init?.headers)\n headers.set(header, serializer.name)\n return wrapResponse(\n await fetcher(input as never, { ...init, headers }),\n serializer,\n header,\n )\n }\n return wrapped as NonNullable<ClientRequestOptions['fetch']>\n }\n const raw = hc(baseUrl, {\n ...rest,\n fetch: wrapFetch(rest.fetch ?? globalThis.fetch),\n })\n const wrapClient = (client: AnyClient): AnyClient =>\n new Proxy(client, {\n get(target, prop, receiver) {\n const value: unknown = Reflect.get(target, prop, receiver)\n if (\n typeof prop === 'symbol' ||\n prop === 'then' ||\n prop === '$url' ||\n prop === '$path' ||\n prop === '$ws'\n )\n return value\n return value !== null &&\n (typeof value === 'function' || typeof value === 'object')\n ? wrapClient(value)\n : value\n },\n apply(target, _thisArg, argArray: unknown[]) {\n const args = argArray[0] as { json?: unknown } | undefined\n const opts = (argArray[1] ?? {}) as ClientRequestOptions\n const callOptions: ClientRequestOptions = { ...opts }\n if (opts.fetch) callOptions.fetch = wrapFetch(opts.fetch)\n let callArgs = args\n if (inputSlot && args?.json !== undefined) {\n const { json, ...otherArgs } = args\n callArgs = otherArgs\n const body = encode(inputSlot, json)\n const requestHeaders = opts.headers\n callOptions.headers = async () => ({\n ...(typeof requestHeaders === 'function'\n ? await requestHeaders()\n : requestHeaders),\n 'Content-Type': inputSlot.contentType ?? 'application/json',\n })\n callOptions.init = { ...opts.init, body: body as BodyInit }\n // RequestInit headers otherwise override hc's assembled headers.\n if (opts.init?.headers || rest.init?.headers) {\n const headers = new Headers(\n opts.init?.headers ?? rest.init?.headers,\n )\n headers.set(\n 'Content-Type',\n inputSlot.contentType ?? 'application/json',\n )\n callOptions.init.headers = headers\n }\n }\n return Reflect.apply(\n target as (...args: unknown[]) => unknown,\n undefined,\n [callArgs, callOptions],\n )\n },\n })\n return wrapClient(raw)\n}\n"],"mappings":"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","names":[]}
|
package/dist/codec.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { S as Serializer } from './types-Ce9-Amxx.js';
|
|
2
|
+
export { C as CodecTypes, e as JsonSerializer, f as WireBody, W as WireSerializer } from './types-Ce9-Amxx.js';
|
|
3
|
+
import 'hono';
|
|
4
|
+
import 'hono/utils/types';
|
|
5
|
+
|
|
6
|
+
/** Shared codec definitions: no Hono runtime or server middleware. */
|
|
7
|
+
|
|
8
|
+
declare function defineSerializer<const T extends Serializer>(serializer: T & Record<Exclude<keyof T, keyof Serializer>, never>): T;
|
|
9
|
+
|
|
10
|
+
export { Serializer, defineSerializer };
|
package/dist/codec.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Context, TypedResponse, MiddlewareHandler } from 'hono';
|
|
2
|
+
import { ContentfulStatusCode } from 'hono/utils/http-status';
|
|
3
|
+
import { S as Serializer, b as SerializedOutput, c as SerializerList, d as SerializerOf } from './types-Ce9-Amxx.js';
|
|
4
|
+
export { C as CodecTypes, J as JsonApp, e as JsonSerializer, f as WireBody, W as WireSerializer } from './types-Ce9-Amxx.js';
|
|
5
|
+
export { defineSerializer } from './codec.js';
|
|
6
|
+
import 'hono/utils/types';
|
|
7
|
+
|
|
8
|
+
declare const DEFAULT_HEADER = "x-hono-serialization";
|
|
9
|
+
declare const DEFAULT_JSON_CONTENT_TYPE = "application/json; charset=UTF-8";
|
|
10
|
+
declare const BINARY_CONTENT_TYPE = "application/octet-stream";
|
|
11
|
+
|
|
12
|
+
type HeaderRecord = Record<string, string | string[]>;
|
|
13
|
+
type ResponseOptions<U extends ContentfulStatusCode = ContentfulStatusCode> = Omit<ResponseInit, 'status'> & {
|
|
14
|
+
status?: U;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type SerializationMiddlewareOptions = {
|
|
18
|
+
header?: string;
|
|
19
|
+
};
|
|
20
|
+
type JsonHelper<S extends Serializer = Serializer> = {
|
|
21
|
+
<T, U extends ContentfulStatusCode = 200>(c: Context, object: T, status?: U, headers?: HeaderRecord): Response & TypedResponse<SerializedOutput<T, S>, U, 'json'>;
|
|
22
|
+
<T, U extends ContentfulStatusCode = 200>(c: Context, object: T, init: ResponseOptions<U>): Response & TypedResponse<SerializedOutput<T, S>, U, 'json'>;
|
|
23
|
+
};
|
|
24
|
+
type SerializationMiddleware<S extends Serializer = Serializer> = MiddlewareHandler & {
|
|
25
|
+
json: JsonHelper<S>;
|
|
26
|
+
};
|
|
27
|
+
declare function serialization<const S extends SerializerList>(serializers: S, options?: SerializationMiddlewareOptions): SerializationMiddleware<SerializerOf<S> & Serializer>;
|
|
28
|
+
|
|
29
|
+
export { BINARY_CONTENT_TYPE, DEFAULT_HEADER, DEFAULT_JSON_CONTENT_TYPE, type JsonHelper, type ResponseOptions, type SerializationMiddleware, type SerializationMiddlewareOptions, Serializer, serialization };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BINARY_CONTENT_TYPE,
|
|
3
|
+
DEFAULT_HEADER,
|
|
4
|
+
DEFAULT_JSON_CONTENT_TYPE,
|
|
5
|
+
encode,
|
|
6
|
+
resolve,
|
|
7
|
+
resolveInput,
|
|
8
|
+
typesHeader,
|
|
9
|
+
validateHeader
|
|
10
|
+
} from "./chunk-HINZPU3T.js";
|
|
11
|
+
import {
|
|
12
|
+
defineSerializer
|
|
13
|
+
} from "./chunk-7N7SGVDF.js";
|
|
14
|
+
import {
|
|
15
|
+
validateSerializer
|
|
16
|
+
} from "./chunk-E2KZJRGU.js";
|
|
17
|
+
|
|
18
|
+
// src/index.ts
|
|
19
|
+
import { createMiddleware } from "hono/factory";
|
|
20
|
+
|
|
21
|
+
// src/internal.ts
|
|
22
|
+
var CONTEXT_KEY = "hono-serialization";
|
|
23
|
+
var getActive = (c) => c.get(CONTEXT_KEY);
|
|
24
|
+
var setActive = (c, active) => {
|
|
25
|
+
c.set(CONTEXT_KEY, active);
|
|
26
|
+
};
|
|
27
|
+
function lookup(serializers) {
|
|
28
|
+
const list = Array.isArray(serializers) ? serializers : [serializers];
|
|
29
|
+
if (!list.length)
|
|
30
|
+
throw new TypeError("serialization: at least one serializer is required");
|
|
31
|
+
const byName = /* @__PURE__ */ new Map();
|
|
32
|
+
for (const s of list) {
|
|
33
|
+
validateSerializer(s);
|
|
34
|
+
if (byName.has(s.name))
|
|
35
|
+
throw new TypeError(`serialization: duplicate name "${s.name}"`);
|
|
36
|
+
byName.set(s.name, s);
|
|
37
|
+
}
|
|
38
|
+
return byName;
|
|
39
|
+
}
|
|
40
|
+
function appendHeader(headers, key, value) {
|
|
41
|
+
const parts = (headers.get(key) ?? "").split(",").map((p) => p.trim()).filter(Boolean);
|
|
42
|
+
if (!parts.some((p) => p.toLowerCase() === value.toLowerCase()) && !(key.toLowerCase() === "vary" && parts.includes("*")))
|
|
43
|
+
parts.push(value);
|
|
44
|
+
headers.set(key, parts.join(", "));
|
|
45
|
+
}
|
|
46
|
+
var sendEncoded = (c, value, arg, headers, slot, header, serializer) => {
|
|
47
|
+
const body = encode(slot, value);
|
|
48
|
+
const payload = body instanceof Uint8Array ? new Uint8Array(body) : body;
|
|
49
|
+
const res = typeof arg === "object" ? c.newResponse(payload, arg) : c.newResponse(payload, arg, headers);
|
|
50
|
+
if (typeof arg === "object" && headers) {
|
|
51
|
+
for (const [key, value2] of Object.entries(headers)) {
|
|
52
|
+
res.headers.delete(key);
|
|
53
|
+
for (const item of typeof value2 === "string" ? [value2] : value2)
|
|
54
|
+
res.headers.append(key, item);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (slot.contentType) res.headers.set("Content-Type", slot.contentType);
|
|
58
|
+
res.headers.set(header, serializer.name);
|
|
59
|
+
res.headers.set(typesHeader(header), serializer.json.types ?? "preserve");
|
|
60
|
+
appendHeader(res.headers, "Access-Control-Expose-Headers", header);
|
|
61
|
+
appendHeader(
|
|
62
|
+
res.headers,
|
|
63
|
+
"Access-Control-Expose-Headers",
|
|
64
|
+
typesHeader(header)
|
|
65
|
+
);
|
|
66
|
+
return res;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// src/index.ts
|
|
70
|
+
function serialization(serializers, options) {
|
|
71
|
+
const byName = lookup(serializers);
|
|
72
|
+
const header = options?.header ?? DEFAULT_HEADER;
|
|
73
|
+
validateHeader(header);
|
|
74
|
+
const middleware = createMiddleware(async (c, next) => {
|
|
75
|
+
const name = c.req.header(header)?.trim();
|
|
76
|
+
const active = name ? byName.get(name) : void 0;
|
|
77
|
+
if (active) {
|
|
78
|
+
setActive(c, { serializer: active, header });
|
|
79
|
+
const input = resolveInput(active.json);
|
|
80
|
+
if (input) {
|
|
81
|
+
let parsed;
|
|
82
|
+
c.req.json = (() => parsed ??= (async () => {
|
|
83
|
+
const data = input.binary ? new Uint8Array(await c.req.arrayBuffer()) : await c.req.text();
|
|
84
|
+
return input.parse(data);
|
|
85
|
+
})());
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
await next();
|
|
89
|
+
const responseHeaders = new Headers(c.res.headers);
|
|
90
|
+
appendHeader(responseHeaders, "Vary", header);
|
|
91
|
+
c.header("Vary", responseHeaders.get("Vary"));
|
|
92
|
+
if (c.res.headers.get(header)) {
|
|
93
|
+
for (const name2 of [header, typesHeader(header)]) {
|
|
94
|
+
appendHeader(responseHeaders, "Access-Control-Expose-Headers", name2);
|
|
95
|
+
}
|
|
96
|
+
c.header(
|
|
97
|
+
"Access-Control-Expose-Headers",
|
|
98
|
+
responseHeaders.get("Access-Control-Expose-Headers")
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
const json = (c, value, arg, headers) => {
|
|
103
|
+
const active = getActive(c);
|
|
104
|
+
if (active && (active.header !== header || byName.get(active.serializer.name) !== active.serializer)) {
|
|
105
|
+
throw new TypeError(
|
|
106
|
+
"serialization helper used with a different middleware instance"
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
const init = typeof arg === "object" ? { ...arg, status: arg.status ?? 200 } : arg ?? 200;
|
|
110
|
+
if (active) {
|
|
111
|
+
return sendEncoded(
|
|
112
|
+
c,
|
|
113
|
+
value,
|
|
114
|
+
init,
|
|
115
|
+
headers,
|
|
116
|
+
resolve(active.serializer.json),
|
|
117
|
+
header,
|
|
118
|
+
active.serializer
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
return typeof init === "object" ? c.json(value, init) : c.json(value, init, headers);
|
|
122
|
+
};
|
|
123
|
+
return Object.assign(middleware, {
|
|
124
|
+
json
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
export {
|
|
128
|
+
BINARY_CONTENT_TYPE,
|
|
129
|
+
DEFAULT_HEADER,
|
|
130
|
+
DEFAULT_JSON_CONTENT_TYPE,
|
|
131
|
+
defineSerializer,
|
|
132
|
+
serialization
|
|
133
|
+
};
|
|
134
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/internal.ts"],"sourcesContent":["import type { Context, MiddlewareHandler, TypedResponse } from 'hono'\nimport { createMiddleware } from 'hono/factory'\nimport type { ContentfulStatusCode } from 'hono/utils/http-status'\nimport {\n BINARY_CONTENT_TYPE,\n DEFAULT_HEADER,\n DEFAULT_JSON_CONTENT_TYPE,\n appendHeader,\n getActive,\n lookup,\n typesHeader,\n resolve,\n resolveInput,\n sendEncoded,\n setActive,\n validateHeader,\n type HeaderRecord,\n type ResponseOptions,\n} from './internal'\nimport type {\n SerializedOutput,\n Serializer,\n SerializerList,\n SerializerOf,\n} from './types'\n\nexport { BINARY_CONTENT_TYPE, DEFAULT_HEADER, DEFAULT_JSON_CONTENT_TYPE }\nexport type {\n JsonApp,\n JsonSerializer,\n CodecTypes,\n Serializer,\n WireBody,\n WireSerializer,\n} from './types'\nexport type { ResponseOptions } from './internal'\nexport { defineSerializer } from './codec'\n\nexport type SerializationMiddlewareOptions = { header?: string }\nexport type JsonHelper<S extends Serializer = Serializer> = {\n <T, U extends ContentfulStatusCode = 200>(\n c: Context,\n object: T,\n status?: U,\n headers?: HeaderRecord,\n ): Response & TypedResponse<SerializedOutput<T, S>, U, 'json'>\n <T, U extends ContentfulStatusCode = 200>(\n c: Context,\n object: T,\n init: ResponseOptions<U>,\n ): Response & TypedResponse<SerializedOutput<T, S>, U, 'json'>\n}\nexport type SerializationMiddleware<S extends Serializer = Serializer> =\n MiddlewareHandler & { json: JsonHelper<S> }\n\nexport function serialization<const S extends SerializerList>(\n serializers: S,\n options?: SerializationMiddlewareOptions,\n): SerializationMiddleware<SerializerOf<S> & Serializer> {\n const byName = lookup(serializers)\n const header = options?.header ?? DEFAULT_HEADER\n validateHeader(header)\n const middleware = createMiddleware(async (c, next) => {\n const name = c.req.header(header)?.trim()\n const active = name ? byName.get(name) : undefined\n if (active) {\n setActive(c, { serializer: active, header })\n const input = resolveInput(active.json)\n if (input) {\n let parsed: Promise<unknown> | undefined\n c.req.json = (() =>\n (parsed ??= (async () => {\n const data = input.binary\n ? new Uint8Array(await c.req.arrayBuffer())\n : await c.req.text()\n return input.parse(data as never)\n })())) as Context['req']['json']\n }\n }\n await next()\n const responseHeaders = new Headers(c.res.headers)\n appendHeader(responseHeaders, 'Vary', header)\n c.header('Vary', responseHeaders.get('Vary')!)\n if (c.res.headers.get(header)) {\n for (const name of [header, typesHeader(header)]) {\n appendHeader(responseHeaders, 'Access-Control-Expose-Headers', name)\n }\n c.header(\n 'Access-Control-Expose-Headers',\n responseHeaders.get('Access-Control-Expose-Headers')!,\n )\n }\n })\n const json = (\n c: Context,\n value: unknown,\n arg?: ContentfulStatusCode | ResponseOptions,\n headers?: HeaderRecord,\n ): Response => {\n const active = getActive(c)\n if (\n active &&\n (active.header !== header ||\n byName.get(active.serializer.name) !== active.serializer)\n ) {\n throw new TypeError(\n 'serialization helper used with a different middleware instance',\n )\n }\n // A helper's status must agree with its RPC type, even after c.status().\n const init =\n typeof arg === 'object'\n ? { ...arg, status: arg.status ?? 200 }\n : (arg ?? 200)\n if (active) {\n return sendEncoded(\n c,\n value,\n init,\n headers,\n resolve(active.serializer.json),\n header,\n active.serializer,\n )\n }\n // Unnegotiated requests keep Hono's ordinary JSON response.\n return typeof init === 'object'\n ? c.json(value as never, init)\n : c.json(value as never, init, headers)\n }\n return Object.assign(middleware, {\n json: json as JsonHelper<SerializerOf<S> & Serializer>,\n })\n}\n","import type { Context } from 'hono'\nimport type { ContentfulStatusCode } from 'hono/utils/http-status'\nimport type { Serializer } from './types'\nimport { encode, typesHeader, type Resolved } from './wire'\nimport { validateSerializer } from './validation'\nexport * from './wire'\n\nconst CONTEXT_KEY = 'hono-serialization'\nexport type Active = { serializer: Serializer; header: string }\nexport const getActive = (c: Context): Active | undefined =>\n c.get(CONTEXT_KEY) as Active | undefined\nexport const setActive = (c: Context, active: Active): void => {\n c.set(CONTEXT_KEY, active)\n}\n\nexport function lookup(\n serializers: Serializer | readonly Serializer[],\n): Map<string, Serializer> {\n const list: readonly Serializer[] = Array.isArray(serializers)\n ? serializers\n : [serializers as Serializer]\n if (!list.length)\n throw new TypeError('serialization: at least one serializer is required')\n const byName = new Map<string, Serializer>()\n for (const s of list) {\n validateSerializer(s)\n if (byName.has(s.name))\n throw new TypeError(`serialization: duplicate name \"${s.name}\"`)\n byName.set(s.name, s)\n }\n return byName\n}\nexport type HeaderRecord = Record<string, string | string[]>\nexport type ResponseOptions<\n U extends ContentfulStatusCode = ContentfulStatusCode,\n> = Omit<ResponseInit, 'status'> & { status?: U }\nexport function appendHeader(\n headers: Headers,\n key: string,\n value: string,\n): void {\n const parts = (headers.get(key) ?? '')\n .split(',')\n .map((p) => p.trim())\n .filter(Boolean)\n if (\n !parts.some((p) => p.toLowerCase() === value.toLowerCase()) &&\n !(key.toLowerCase() === 'vary' && parts.includes('*'))\n )\n parts.push(value)\n headers.set(key, parts.join(', '))\n}\nexport const sendEncoded = (\n c: Context,\n value: unknown,\n arg: ContentfulStatusCode | ResponseOptions | undefined,\n headers: HeaderRecord | undefined,\n slot: Resolved,\n header: string,\n serializer: Serializer,\n): Response => {\n const body = encode(slot, value)\n const payload = body instanceof Uint8Array ? new Uint8Array(body) : body\n const res =\n typeof arg === 'object'\n ? c.newResponse(payload, arg)\n : c.newResponse(payload, arg, headers)\n if (typeof arg === 'object' && headers) {\n for (const [key, value] of Object.entries(headers)) {\n res.headers.delete(key)\n for (const item of typeof value === 'string' ? [value] : value)\n res.headers.append(key, item)\n }\n }\n if (slot.contentType) res.headers.set('Content-Type', slot.contentType)\n res.headers.set(header, serializer.name)\n res.headers.set(typesHeader(header), serializer.json.types ?? 'preserve')\n appendHeader(res.headers, 'Access-Control-Expose-Headers', header)\n appendHeader(\n res.headers,\n 'Access-Control-Expose-Headers',\n typesHeader(header),\n )\n return res\n}\n"],"mappings":"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","names":["value","name"]}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { Hono, Schema } from 'hono';
|
|
2
|
+
import { JSONParsed } from 'hono/utils/types';
|
|
3
|
+
|
|
4
|
+
type WireBody = string | ArrayBuffer | Uint8Array;
|
|
5
|
+
type CodecTypes = 'preserve' | 'json-parsed' | 'unknown';
|
|
6
|
+
/** The discriminant makes the bytes passed to parse explicit. */
|
|
7
|
+
type WireSerializer<Value = unknown, Parsed = unknown> = ({
|
|
8
|
+
binary?: false;
|
|
9
|
+
serialize: (value: Value) => string;
|
|
10
|
+
parse: (data: string) => Parsed;
|
|
11
|
+
} | {
|
|
12
|
+
binary: true;
|
|
13
|
+
serialize: (value: Value) => Uint8Array | ArrayBuffer;
|
|
14
|
+
parse: (data: Uint8Array) => Parsed;
|
|
15
|
+
}) & {
|
|
16
|
+
contentType?: string;
|
|
17
|
+
};
|
|
18
|
+
type JsonSerializer<Value = unknown> = WireSerializer<Value> & {
|
|
19
|
+
input?: WireSerializer & {
|
|
20
|
+
types?: 'preserve' | 'json-parsed';
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* What a round trip does to types. Defaults to `preserve`: values and
|
|
24
|
+
* types survive. `json-parsed` promises ordinary JSON conversion and
|
|
25
|
+
* `unknown` makes no promise.
|
|
26
|
+
*/
|
|
27
|
+
types?: CodecTypes;
|
|
28
|
+
};
|
|
29
|
+
type Serializer<Name extends string = string> = {
|
|
30
|
+
name: Name;
|
|
31
|
+
json: JsonSerializer;
|
|
32
|
+
};
|
|
33
|
+
type SerializerList = Serializer | readonly Serializer[];
|
|
34
|
+
type SerializerOf<S extends SerializerList> = S extends readonly Serializer[] ? S[number] : S;
|
|
35
|
+
declare const serialized: unique symbol;
|
|
36
|
+
type SerializedOutput<T, S extends Serializer = Serializer> = {
|
|
37
|
+
readonly [serialized]: {
|
|
38
|
+
value: T;
|
|
39
|
+
serializer: S;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
type JsonInput<I> = I extends object ? {
|
|
43
|
+
[K in keyof I]: K extends 'json' ? JSONParsed<I[K]> : I[K];
|
|
44
|
+
} : I;
|
|
45
|
+
type DecodeOutput<O, Types extends CodecTypes> = O extends SerializedOutput<infer T> ? Types extends 'preserve' ? T : Types extends 'json-parsed' ? JSONParsed<T> : unknown : O;
|
|
46
|
+
type Endpoint<E, Types extends CodecTypes, Plain extends boolean> = E extends {
|
|
47
|
+
input: infer I;
|
|
48
|
+
output: infer O;
|
|
49
|
+
outputFormat: infer F;
|
|
50
|
+
status: infer St;
|
|
51
|
+
} ? {
|
|
52
|
+
input: Plain extends true ? JsonInput<I> : I;
|
|
53
|
+
output: F extends 'json' ? DecodeOutput<O, Types> : O;
|
|
54
|
+
outputFormat: F;
|
|
55
|
+
status: St;
|
|
56
|
+
} : E;
|
|
57
|
+
type MapSchema<S, Types extends CodecTypes, Plain extends boolean> = {
|
|
58
|
+
[P in keyof S]: {
|
|
59
|
+
[M in keyof S[P]]: Endpoint<S[P][M], Types, Plain>;
|
|
60
|
+
};
|
|
61
|
+
};
|
|
62
|
+
type ClientApp<T, Types extends CodecTypes, RichInput extends boolean = false> = T extends Hono<infer E, infer S, infer P> ? MapSchema<S, Types, RichInput extends true ? false : true> extends infer M extends Schema ? Hono<E, M, P> : never : never;
|
|
63
|
+
/** A plain JSON client converts both request and response JSON values. */
|
|
64
|
+
type JsonApp<T> = T extends Hono<infer E, infer S, infer P> ? MapSchema<S, 'json-parsed', true> extends infer M extends Schema ? Hono<E, M, P> : never : never;
|
|
65
|
+
type EndpointSerializer<E> = E extends {
|
|
66
|
+
output: infer O;
|
|
67
|
+
} ? O extends SerializedOutput<unknown, infer S> ? S : never : never;
|
|
68
|
+
type SchemaSerializers<S> = {
|
|
69
|
+
[P in keyof S]: {
|
|
70
|
+
[M in keyof S[P]]: EndpointSerializer<S[P][M]>;
|
|
71
|
+
}[keyof S[P]];
|
|
72
|
+
}[keyof S];
|
|
73
|
+
/** Restrict clients to serializer definitions carried by the app's typed helpers. */
|
|
74
|
+
type AppSerializer<T> = T extends Hono<infer _E, infer S, infer _P> ? [SchemaSerializers<S>] extends [never] ? Serializer : SchemaSerializers<S> : never;
|
|
75
|
+
|
|
76
|
+
export type { AppSerializer as A, CodecTypes as C, JsonApp as J, Serializer as S, WireSerializer as W, ClientApp as a, SerializedOutput as b, SerializerList as c, SerializerOf as d, JsonSerializer as e, WireBody as f };
|
package/package.json
CHANGED
|
@@ -1,6 +1,93 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hono-serialization",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Opt-in Hono serialize/deserialize via a request header. Bring your own superjson, seroval, devalue or cbor-x.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./client": {
|
|
15
|
+
"types": "./dist/client.d.ts",
|
|
16
|
+
"import": "./dist/client.js"
|
|
17
|
+
},
|
|
18
|
+
"./codec": {
|
|
19
|
+
"types": "./dist/codec.d.ts",
|
|
20
|
+
"import": "./dist/codec.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"CHANGELOG.md"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "tsup",
|
|
29
|
+
"test": "vitest run",
|
|
30
|
+
"test:watch": "vitest",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"prepublishOnly": "npm run check:release",
|
|
33
|
+
"check:package": "tsc -p tsconfig.package.json && node checks/package-boundaries.mjs",
|
|
34
|
+
"check:install": "node checks/package-install.mjs",
|
|
35
|
+
"check:release": "npm run typecheck && npm test && npm run build && npm run check:package && npm run check:install"
|
|
36
|
+
},
|
|
37
|
+
"keywords": [
|
|
38
|
+
"hono",
|
|
39
|
+
"rpc",
|
|
40
|
+
"serialization",
|
|
41
|
+
"serializer",
|
|
42
|
+
"superjson",
|
|
43
|
+
"seroval",
|
|
44
|
+
"devalue",
|
|
45
|
+
"cbor-x"
|
|
46
|
+
],
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"peerDependencies": {
|
|
49
|
+
"hono": "^4.13.13"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/node": "^26.6.5",
|
|
53
|
+
"cbor-x": "^1.6.6",
|
|
54
|
+
"devalue": "^6.0.2",
|
|
55
|
+
"hono": "^4.13.13",
|
|
56
|
+
"seroval": "^1.6.9",
|
|
57
|
+
"superjson": "^2.2.6",
|
|
58
|
+
"tsup": "^8.5.1",
|
|
59
|
+
"typescript": "^6.0.3",
|
|
60
|
+
"vitest": "^5.0.4"
|
|
61
|
+
},
|
|
62
|
+
"tsup": {
|
|
63
|
+
"entry": [
|
|
64
|
+
"src/index.ts",
|
|
65
|
+
"src/client.ts",
|
|
66
|
+
"src/codec.ts"
|
|
67
|
+
],
|
|
68
|
+
"format": [
|
|
69
|
+
"esm"
|
|
70
|
+
],
|
|
71
|
+
"dts": {
|
|
72
|
+
"compilerOptions": {
|
|
73
|
+
"ignoreDeprecations": "6.0"
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
"splitting": true,
|
|
77
|
+
"sourcemap": true,
|
|
78
|
+
"clean": true
|
|
79
|
+
},
|
|
80
|
+
"author": "Sebastian Broman",
|
|
81
|
+
"repository": {
|
|
82
|
+
"type": "git",
|
|
83
|
+
"url": "git+https://github.com/SebbeBroman/hono-serialization.git"
|
|
84
|
+
},
|
|
85
|
+
"homepage": "https://github.com/SebbeBroman/hono-serialization#readme",
|
|
86
|
+
"bugs": {
|
|
87
|
+
"url": "https://github.com/SebbeBroman/hono-serialization/issues"
|
|
88
|
+
},
|
|
89
|
+
"publishConfig": {
|
|
90
|
+
"access": "public",
|
|
91
|
+
"registry": "https://registry.npmjs.org/"
|
|
92
|
+
}
|
|
93
|
+
}
|