@automate.ax/codec 0.81.0 → 0.82.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/dist/http.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import * as z from "zod/mini";
2
+ declare const JSON_VALUE_SCHEMA: z.ZodMiniJSONSchema;
3
+ export type JsonValue = z.infer<typeof JSON_VALUE_SCHEMA>;
4
+ export type HttpRequestBody = JsonValue | Uint8Array;
5
+ export type HttpRequestBodyType = "bytes" | "empty" | "form" | "json" | "text";
6
+ export type DecodedHttpRequestBody = {
7
+ body: HttpRequestBody;
8
+ bodyType: HttpRequestBodyType;
9
+ };
10
+ /**
11
+ * Decodes HTTP request bytes according to the declared media type.
12
+ *
13
+ * @param rawBody - Exact request body bytes.
14
+ * @param contentType - Declared Content-Type header, when present.
15
+ */
16
+ export declare function decodeHttpRequestBody(rawBody: Uint8Array, contentType?: string): DecodedHttpRequestBody;
17
+ export {};
package/dist/http.js ADDED
@@ -0,0 +1,52 @@
1
+ import * as z from "zod/mini";
2
+ const JSON_VALUE_SCHEMA = z.json();
3
+ /**
4
+ * Decodes HTTP request bytes according to the declared media type.
5
+ *
6
+ * @param rawBody - Exact request body bytes.
7
+ * @param contentType - Declared Content-Type header, when present.
8
+ */
9
+ export function decodeHttpRequestBody(rawBody, contentType) {
10
+ if (rawBody.byteLength === 0) {
11
+ return { body: null, bodyType: "empty" };
12
+ }
13
+ const mediaType = contentType?.split(";", 1)[0]?.trim().toLowerCase() ?? "";
14
+ if (mediaType === "application/x-www-form-urlencoded") {
15
+ const values = new Map();
16
+ for (const [key, value] of new URLSearchParams(decodeText(rawBody))) {
17
+ const existing = values.get(key);
18
+ if (existing)
19
+ existing.push(value);
20
+ else
21
+ values.set(key, [value]);
22
+ }
23
+ return {
24
+ body: Object.fromEntries([...values].map(([key, entries]) => [
25
+ key,
26
+ entries.length === 1 ? entries[0] : entries,
27
+ ])),
28
+ bodyType: "form",
29
+ };
30
+ }
31
+ if (mediaType === "application/json" || mediaType.endsWith("+json")) {
32
+ return {
33
+ body: JSON_VALUE_SCHEMA.parse(JSON.parse(decodeText(rawBody))),
34
+ bodyType: "json",
35
+ };
36
+ }
37
+ if (mediaType.startsWith("text/") ||
38
+ mediaType === "application/javascript" ||
39
+ mediaType === "application/xml" ||
40
+ mediaType.endsWith("+xml")) {
41
+ return { body: decodeText(rawBody), bodyType: "text" };
42
+ }
43
+ return { body: rawBody, bodyType: "bytes" };
44
+ }
45
+ /**
46
+ * Decodes request bytes as UTF-8, replacing invalid byte sequences.
47
+ *
48
+ * @param rawBody - Exact request body bytes.
49
+ */
50
+ function decodeText(rawBody) {
51
+ return new TextDecoder().decode(rawBody);
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automate.ax/codec",
3
- "version": "0.81.0",
3
+ "version": "0.82.0",
4
4
  "description": "Msgpack-based encoding helpers for Automate.ax runtime data.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -19,6 +19,7 @@
19
19
  "zshy": {
20
20
  "exports": {
21
21
  ".": "./src/index.ts",
22
+ "./http": "./src/http.ts",
22
23
  "./*": "./src/*"
23
24
  },
24
25
  "cjs": false,
@@ -32,6 +33,11 @@
32
33
  "types": "./dist/index.d.ts",
33
34
  "default": "./dist/index.js"
34
35
  },
36
+ "./http": {
37
+ "bun": "./src/http.ts",
38
+ "types": "./dist/http.d.ts",
39
+ "default": "./dist/http.js"
40
+ },
35
41
  "./*": {
36
42
  "bun": "./src/*",
37
43
  "types": "./dist/*",
package/src/http.ts ADDED
@@ -0,0 +1,75 @@
1
+ import * as z from "zod/mini"
2
+
3
+ const JSON_VALUE_SCHEMA = z.json()
4
+
5
+ export type JsonValue = z.infer<typeof JSON_VALUE_SCHEMA>
6
+
7
+ export type HttpRequestBody = JsonValue | Uint8Array
8
+
9
+ export type HttpRequestBodyType = "bytes" | "empty" | "form" | "json" | "text"
10
+
11
+ export type DecodedHttpRequestBody = {
12
+ body: HttpRequestBody
13
+ bodyType: HttpRequestBodyType
14
+ }
15
+
16
+ /**
17
+ * Decodes HTTP request bytes according to the declared media type.
18
+ *
19
+ * @param rawBody - Exact request body bytes.
20
+ * @param contentType - Declared Content-Type header, when present.
21
+ */
22
+ export function decodeHttpRequestBody(
23
+ rawBody: Uint8Array,
24
+ contentType?: string,
25
+ ): DecodedHttpRequestBody {
26
+ if (rawBody.byteLength === 0) {
27
+ return { body: null, bodyType: "empty" }
28
+ }
29
+
30
+ const mediaType = contentType?.split(";", 1)[0]?.trim().toLowerCase() ?? ""
31
+ if (mediaType === "application/x-www-form-urlencoded") {
32
+ const values = new Map<string, string[]>()
33
+ for (const [key, value] of new URLSearchParams(decodeText(rawBody))) {
34
+ const existing = values.get(key)
35
+ if (existing) existing.push(value)
36
+ else values.set(key, [value])
37
+ }
38
+ return {
39
+ body: Object.fromEntries(
40
+ [...values].map(([key, entries]): [string, string | string[]] => [
41
+ key,
42
+ entries.length === 1 ? entries[0]! : entries,
43
+ ]),
44
+ ),
45
+ bodyType: "form",
46
+ }
47
+ }
48
+
49
+ if (mediaType === "application/json" || mediaType.endsWith("+json")) {
50
+ return {
51
+ body: JSON_VALUE_SCHEMA.parse(JSON.parse(decodeText(rawBody))),
52
+ bodyType: "json",
53
+ }
54
+ }
55
+
56
+ if (
57
+ mediaType.startsWith("text/") ||
58
+ mediaType === "application/javascript" ||
59
+ mediaType === "application/xml" ||
60
+ mediaType.endsWith("+xml")
61
+ ) {
62
+ return { body: decodeText(rawBody), bodyType: "text" }
63
+ }
64
+
65
+ return { body: rawBody, bodyType: "bytes" }
66
+ }
67
+
68
+ /**
69
+ * Decodes request bytes as UTF-8, replacing invalid byte sequences.
70
+ *
71
+ * @param rawBody - Exact request body bytes.
72
+ */
73
+ function decodeText(rawBody: Uint8Array) {
74
+ return new TextDecoder().decode(rawBody)
75
+ }