hanzio 1.0.8 → 1.0.9

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 CHANGED
@@ -2,17 +2,19 @@
2
2
 
3
3
  A small TypeScript-first utility library for common application code: arrays,
4
4
  strings, promises, caching, state machines, dates, URLs, Zod helpers, API clients,
5
- JWTs, gzip, sitemap crawling, queues, secrets, and colorful terminal logging.
5
+ JWTs, sitemap crawling, queues, secrets, and colorful terminal logging.
6
6
 
7
7
  ## Install
8
8
 
9
9
  ```bash
10
- bun add hanzio
10
+ bun add hanzio zod
11
11
  ```
12
12
 
13
13
  The package is ESM and Bun-friendly. Bun consumers resolve the `bun` export to
14
14
  the source TypeScript files, while Node/bundler consumers resolve built files
15
15
  from `dist`.
16
+ `zod` is a peer dependency because the Zod helpers and API wrapper share the
17
+ consumer application's Zod instance.
16
18
 
17
19
  ## Imports
18
20
 
@@ -27,10 +29,10 @@ Heavier or dependency-specific modules are also available as subpath exports:
27
29
  ```ts
28
30
  import { createZId } from 'hanzio/zod'
29
31
  import { jwtSign } from 'hanzio/jwt'
30
- import { compressString } from 'hanzio/gzip'
31
32
  import { getDomainSitemap } from 'hanzio/sitemap'
32
33
  import PQueue from 'hanzio/p-queue'
33
34
  import { createCoolLogger } from 'hanzio/cool-console-log'
35
+ import { defineSecretSet } from 'hanzio/secrets'
34
36
  ```
35
37
 
36
38
  The package currently publishes `src/**/*.ts` intentionally so Bun can consume
@@ -217,7 +219,7 @@ await promiseTimeout(250)
217
219
  Infisical before the rest of the program starts.
218
220
 
219
221
  ```ts
220
- import { defineSecretSet } from 'hanzio'
222
+ import { defineSecretSet } from 'hanzio/secrets'
221
223
 
222
224
  export const backendSecrets = await defineSecretSet(
223
225
  ['DATABASE_URL', 'APP_SECRET'] as const,
@@ -229,7 +231,8 @@ const databaseUrl = backendSecrets.secret('DATABASE_URL')
229
231
 
230
232
  For Vite config, `viteSecretSetPlugin(secretSet)` exposes the loaded values as
231
233
  `import.meta.env.*` replacements. Use `getViteDefine(secretSet)` if you need the
232
- raw `define` object.
234
+ raw `define` object. Import Vite helpers from `hanzio/secrets/vite` so browser
235
+ bundles do not pull in the Infisical SDK.
233
236
 
234
237
  ### Cache
235
238
 
@@ -470,17 +473,6 @@ const token = await jwtSign({ sub: 'user_1' }, 'secret')
470
473
  const payload = await jwtVerify<{ sub: string }>(token, 'secret')
471
474
  ```
472
475
 
473
- ### Gzip
474
-
475
- `compressString` and `decompressString` gzip roundtrip strings as base64.
476
-
477
- ```ts
478
- import { compressString, decompressString } from 'hanzio/gzip'
479
-
480
- const compressed = compressString('hello')
481
- const text = decompressString(compressed)
482
- ```
483
-
484
476
  ### Sitemap
485
477
 
486
478
  `getDomainSitemap` discovers and crawls sitemap URLs for a domain.
package/dist/index.d.ts CHANGED
@@ -12,6 +12,5 @@ export * from './api-wrapper';
12
12
  export * from './p-queue';
13
13
  export * from './cool-console-log';
14
14
  export * from './jwt';
15
- export * from './gzip';
16
15
  export * from './sitemap';
17
16
  export * from './secrets';
@@ -1,3 +1,3 @@
1
- import * as jose from 'jose';
2
- export declare function jwtSign<_Payload extends object>(payload: jose.JWTPayload, secret: string): Promise<string>;
3
- export declare function jwtVerify<Payload extends object>(token: string, secret: string): Promise<Payload | null>;
1
+ export type JWTPayload = Record<string, unknown>;
2
+ export declare function jwtSign<Payload extends JWTPayload>(payload: Payload, secret: string): Promise<string>;
3
+ export declare function jwtVerify<Payload extends JWTPayload>(token: string, secret: string): Promise<Payload | null>;
@@ -22,6 +22,7 @@ export type SecretSet<SecretKey extends string> = {
22
22
  secrets: () => Record<SecretKey, string>;
23
23
  reload: () => Promise<SecretSet<SecretKey>>;
24
24
  };
25
+ export * from './infisical';
25
26
  export * from './vite';
26
27
  export declare function getSecretEnvironment(): SecretEnvironment;
27
28
  export declare function defineSecretSet<const SecretKeys extends readonly string[]>(keys: SecretKeys, options: DefineSecretSetOptions<SecretKeys[number]>): Promise<SecretSet<SecretKeys[number]>>;
@@ -0,0 +1,2 @@
1
+ import type { SecretSetLoaderContext } from '.';
2
+ export declare function loadInfisicalSecrets<SecretKey extends string>({ keys, projectId, environment, siteUrl, clientIdEnvKey, clientSecretEnvKey }: SecretSetLoaderContext<SecretKey>): Promise<Partial<Record<SecretKey, string>>>;
package/dist/src/index.js CHANGED
@@ -880,32 +880,110 @@ class PQueue {
880
880
  }
881
881
 
882
882
  // src/jwt/index.ts
883
- import * as jose from "jose";
883
+ var BYTE_CHUNK_SIZE = 32768;
884
+ var textEncoder = new TextEncoder;
885
+ var textDecoder = new TextDecoder;
884
886
  async function jwtSign(payload, secret) {
885
- const secretKey = new TextEncoder().encode(secret);
886
- return await new jose.SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().sign(secretKey);
887
+ const header = encodeJson({ alg: "HS256" });
888
+ const body = encodeJson({
889
+ ...payload,
890
+ iat: Math.floor(Date.now() / 1000)
891
+ });
892
+ const data = `${header}.${body}`;
893
+ const signature = await sign(data, secret);
894
+ return `${data}.${encodeBase64Url(signature)}`;
887
895
  }
888
- async function jwtVerify2(token, secret) {
896
+ async function jwtVerify(token, secret) {
889
897
  try {
890
- const secretKey = new TextEncoder().encode(secret);
891
- const { payload } = await jose.jwtVerify(token, secretKey);
898
+ const parts = token.split(".");
899
+ if (parts.length !== 3)
900
+ return null;
901
+ const [encodedHeader, encodedPayload, encodedSignature] = parts;
902
+ const header = decodeJson(encodedHeader);
903
+ if (!isJwtHeader(header))
904
+ return null;
905
+ const data = `${encodedHeader}.${encodedPayload}`;
906
+ const signature = decodeBase64Url(encodedSignature);
907
+ const isValid = await verify(data, signature, secret);
908
+ if (!isValid)
909
+ return null;
910
+ const payload = decodeJson(encodedPayload);
911
+ if (!isJwtPayload(payload) || !isTimeValid(payload))
912
+ return null;
892
913
  return payload;
893
914
  } catch (_error) {
894
915
  return null;
895
916
  }
896
917
  }
897
-
898
- // src/gzip/index.ts
899
- import pako from "pako";
900
- function compressString(input) {
901
- const uint8Array = new TextEncoder().encode(input);
902
- const compressed = pako.deflate(uint8Array);
903
- return btoa(String.fromCharCode.apply(null, compressed));
904
- }
905
- function decompressString(compressedString) {
906
- const compressedData = new Uint8Array(atob(compressedString).split("").map((char) => char.charCodeAt(0)));
907
- const decompressed = pako.inflate(compressedData);
908
- return new TextDecoder().decode(decompressed);
918
+ async function sign(data, secret) {
919
+ const key = await getKey(secret, ["sign"]);
920
+ return await getSubtleCrypto().sign("HMAC", key, encodeText(data));
921
+ }
922
+ async function verify(data, signature, secret) {
923
+ const key = await getKey(secret, ["verify"]);
924
+ return await getSubtleCrypto().verify("HMAC", key, signature, encodeText(data));
925
+ }
926
+ async function getKey(secret, usages) {
927
+ return await getSubtleCrypto().importKey("raw", encodeText(secret), {
928
+ name: "HMAC",
929
+ hash: "SHA-256"
930
+ }, false, usages);
931
+ }
932
+ function encodeJson(value) {
933
+ return encodeBase64Url(encodeText(JSON.stringify(value)));
934
+ }
935
+ function decodeJson(value) {
936
+ return JSON.parse(textDecoder.decode(decodeBase64Url(value)));
937
+ }
938
+ function encodeBase64Url(value) {
939
+ const bytes = value instanceof ArrayBuffer ? new Uint8Array(value) : value;
940
+ return btoa(bytesToBinaryString(bytes)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
941
+ }
942
+ function decodeBase64Url(value) {
943
+ const base64 = value.replaceAll("-", "+").replaceAll("_", "/");
944
+ const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
945
+ return binaryStringToBytes(atob(padded)).buffer;
946
+ }
947
+ function encodeText(value) {
948
+ return toArrayBuffer(textEncoder.encode(value));
949
+ }
950
+ function toArrayBuffer(bytes) {
951
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
952
+ }
953
+ function bytesToBinaryString(bytes) {
954
+ let binary = "";
955
+ for (let offset = 0;offset < bytes.length; offset += BYTE_CHUNK_SIZE) {
956
+ binary += String.fromCharCode(...bytes.subarray(offset, offset + BYTE_CHUNK_SIZE));
957
+ }
958
+ return binary;
959
+ }
960
+ function binaryStringToBytes(binary) {
961
+ const bytes = new Uint8Array(binary.length);
962
+ for (let index = 0;index < binary.length; index += 1) {
963
+ bytes[index] = binary.charCodeAt(index);
964
+ }
965
+ return bytes;
966
+ }
967
+ function getSubtleCrypto() {
968
+ const subtle = globalThis.crypto?.subtle;
969
+ if (!subtle) {
970
+ throw new Error("Web Crypto is not available in this runtime");
971
+ }
972
+ return subtle;
973
+ }
974
+ function isJwtHeader(value) {
975
+ return typeof value === "object" && value !== null && "alg" in value && value.alg === "HS256";
976
+ }
977
+ function isJwtPayload(value) {
978
+ return typeof value === "object" && value !== null && !Array.isArray(value);
979
+ }
980
+ function isTimeValid(payload) {
981
+ const now = Math.floor(Date.now() / 1000);
982
+ if (typeof payload.exp === "number" && now >= payload.exp)
983
+ return false;
984
+ if (typeof payload.nbf === "number" && now < payload.nbf)
985
+ return false;
986
+ return true;
909
987
  }
910
988
 
911
989
  // src/sitemap/index.ts
@@ -1023,6 +1101,98 @@ var getDomainSitemap = async (domain, options = {}) => {
1023
1101
  return [...result.links];
1024
1102
  };
1025
1103
 
1104
+ // src/secrets/infisical.ts
1105
+ import { z as z4 } from "zod";
1106
+ var defaultFetch = globalThis.fetch;
1107
+ var infisicalAuthResponseSchema = z4.object({
1108
+ accessToken: z4.string()
1109
+ });
1110
+ var infisicalSecretSchema = z4.object({
1111
+ secretKey: z4.string(),
1112
+ secretValue: z4.string()
1113
+ });
1114
+ var infisicalSecretsResponseSchema = z4.object({
1115
+ secrets: z4.array(infisicalSecretSchema)
1116
+ });
1117
+ async function loadInfisicalSecrets({
1118
+ keys,
1119
+ projectId,
1120
+ environment,
1121
+ siteUrl,
1122
+ clientIdEnvKey,
1123
+ clientSecretEnvKey
1124
+ }) {
1125
+ const clientId = process.env[clientIdEnvKey];
1126
+ const clientSecret = process.env[clientSecretEnvKey];
1127
+ if (!clientId || !clientSecret) {
1128
+ throw new Error("Missing Infisical credentials");
1129
+ }
1130
+ const infisical = createInfisicalApiClient(siteUrl);
1131
+ const { data: auth } = await infisical.request("login", {
1132
+ reqBody: {
1133
+ clientId,
1134
+ clientSecret
1135
+ }
1136
+ });
1137
+ const { data } = await infisical.request("listSecrets", {
1138
+ reqHeaders: {
1139
+ Authorization: `Bearer ${auth.accessToken}`
1140
+ },
1141
+ reqQuery: {
1142
+ environment,
1143
+ projectId,
1144
+ secretPath: "/",
1145
+ viewSecretValue: true,
1146
+ expandSecretReferences: true,
1147
+ includeImports: true
1148
+ }
1149
+ });
1150
+ const wantedKeys = new Set(keys);
1151
+ return Object.fromEntries(data.secrets.filter((secret) => wantedKeys.has(secret.secretKey)).map((secret) => [secret.secretKey, secret.secretValue]));
1152
+ }
1153
+ function createInfisicalApiClient(siteUrl) {
1154
+ return createApiClient({
1155
+ name: "infisical",
1156
+ baseApiUrls: {
1157
+ infisical: siteUrl
1158
+ },
1159
+ defaultBaseApiUrl: "infisical",
1160
+ fetch: getFetch(),
1161
+ retries: 1,
1162
+ endpoints: {
1163
+ login: {
1164
+ method: "POST",
1165
+ path: "/api/v1/auth/universal-auth/login",
1166
+ reqBodySchema: z4.object({
1167
+ clientId: z4.string(),
1168
+ clientSecret: z4.string()
1169
+ }),
1170
+ resSchema: infisicalAuthResponseSchema,
1171
+ reqBodyFormat: "json"
1172
+ },
1173
+ listSecrets: {
1174
+ method: "GET",
1175
+ path: "/api/v4/secrets",
1176
+ reqHeadersSchema: z4.object({
1177
+ Authorization: z4.string()
1178
+ }),
1179
+ reqQuerySchema: z4.object({
1180
+ projectId: z4.string(),
1181
+ environment: z4.string(),
1182
+ secretPath: z4.string(),
1183
+ viewSecretValue: z4.boolean(),
1184
+ expandSecretReferences: z4.boolean(),
1185
+ includeImports: z4.boolean()
1186
+ }),
1187
+ resSchema: infisicalSecretsResponseSchema
1188
+ }
1189
+ }
1190
+ });
1191
+ }
1192
+ function getFetch() {
1193
+ return globalThis.fetch ?? defaultFetch;
1194
+ }
1195
+
1026
1196
  // src/secrets/vite.ts
1027
1197
  function getViteDefine(secretSet) {
1028
1198
  return Object.fromEntries(Object.entries(secretSet.secrets()).map(([key, value]) => [
@@ -1040,7 +1210,6 @@ function viteSecretSetPlugin(secretSet) {
1040
1210
  }
1041
1211
 
1042
1212
  // src/secrets/index.ts
1043
- import { InfisicalSDK } from "@infisical/sdk";
1044
1213
  import { networkInterfaces } from "node:os";
1045
1214
 
1046
1215
  // src/cool-console-log/index.ts
@@ -1220,31 +1389,6 @@ async function defineSecretSet(keys, options) {
1220
1389
  await loadSecrets();
1221
1390
  return secretSet;
1222
1391
  }
1223
- async function loadInfisicalSecrets({
1224
- keys,
1225
- projectId,
1226
- environment,
1227
- siteUrl,
1228
- clientIdEnvKey,
1229
- clientSecretEnvKey
1230
- }) {
1231
- const clientId = process.env[clientIdEnvKey];
1232
- const clientSecret = process.env[clientSecretEnvKey];
1233
- if (!clientId || !clientSecret) {
1234
- throw new Error("Missing Infisical credentials");
1235
- }
1236
- const client = new InfisicalSDK({ siteUrl });
1237
- await client.auth().universalAuth.login({
1238
- clientId,
1239
- clientSecret
1240
- });
1241
- const { secrets } = await client.secrets().listSecrets({
1242
- environment,
1243
- projectId
1244
- });
1245
- const wantedKeys = new Set(keys);
1246
- return Object.fromEntries(secrets.filter((secret) => wantedKeys.has(secret.secretKey)).map((secret) => [secret.secretKey, secret.secretValue]));
1247
- }
1248
1392
  function resolveEnvironment(environment) {
1249
1393
  if (typeof environment === "function")
1250
1394
  return environment();
@@ -1935,9 +2079,10 @@ export {
1935
2079
  normalizeSingleOrArray,
1936
2080
  logOperationSummary,
1937
2081
  logBanner,
2082
+ loadInfisicalSecrets,
1938
2083
  linearRegressionTrend,
1939
2084
  keyBy,
1940
- jwtVerify2 as jwtVerify,
2085
+ jwtVerify,
1941
2086
  jwtSign,
1942
2087
  jsonToZodSchemaMapper,
1943
2088
  isSuccess,
@@ -1957,13 +2102,11 @@ export {
1957
2102
  findArrayDifferenceByKey,
1958
2103
  extractNumber,
1959
2104
  defineSecretSet,
1960
- decompressString,
1961
2105
  createZId,
1962
2106
  createResolveColorsByKeys,
1963
2107
  createCoolLogger,
1964
2108
  createApiWrapper,
1965
2109
  createApiClient,
1966
- compressString,
1967
2110
  compact,
1968
2111
  colorize,
1969
2112
  chunkArray,
@@ -1,19 +1,110 @@
1
1
  // src/jwt/index.ts
2
- import * as jose from "jose";
2
+ var BYTE_CHUNK_SIZE = 32768;
3
+ var textEncoder = new TextEncoder;
4
+ var textDecoder = new TextDecoder;
3
5
  async function jwtSign(payload, secret) {
4
- const secretKey = new TextEncoder().encode(secret);
5
- return await new jose.SignJWT(payload).setProtectedHeader({ alg: "HS256" }).setIssuedAt().sign(secretKey);
6
+ const header = encodeJson({ alg: "HS256" });
7
+ const body = encodeJson({
8
+ ...payload,
9
+ iat: Math.floor(Date.now() / 1000)
10
+ });
11
+ const data = `${header}.${body}`;
12
+ const signature = await sign(data, secret);
13
+ return `${data}.${encodeBase64Url(signature)}`;
6
14
  }
7
- async function jwtVerify2(token, secret) {
15
+ async function jwtVerify(token, secret) {
8
16
  try {
9
- const secretKey = new TextEncoder().encode(secret);
10
- const { payload } = await jose.jwtVerify(token, secretKey);
17
+ const parts = token.split(".");
18
+ if (parts.length !== 3)
19
+ return null;
20
+ const [encodedHeader, encodedPayload, encodedSignature] = parts;
21
+ const header = decodeJson(encodedHeader);
22
+ if (!isJwtHeader(header))
23
+ return null;
24
+ const data = `${encodedHeader}.${encodedPayload}`;
25
+ const signature = decodeBase64Url(encodedSignature);
26
+ const isValid = await verify(data, signature, secret);
27
+ if (!isValid)
28
+ return null;
29
+ const payload = decodeJson(encodedPayload);
30
+ if (!isJwtPayload(payload) || !isTimeValid(payload))
31
+ return null;
11
32
  return payload;
12
33
  } catch (_error) {
13
34
  return null;
14
35
  }
15
36
  }
37
+ async function sign(data, secret) {
38
+ const key = await getKey(secret, ["sign"]);
39
+ return await getSubtleCrypto().sign("HMAC", key, encodeText(data));
40
+ }
41
+ async function verify(data, signature, secret) {
42
+ const key = await getKey(secret, ["verify"]);
43
+ return await getSubtleCrypto().verify("HMAC", key, signature, encodeText(data));
44
+ }
45
+ async function getKey(secret, usages) {
46
+ return await getSubtleCrypto().importKey("raw", encodeText(secret), {
47
+ name: "HMAC",
48
+ hash: "SHA-256"
49
+ }, false, usages);
50
+ }
51
+ function encodeJson(value) {
52
+ return encodeBase64Url(encodeText(JSON.stringify(value)));
53
+ }
54
+ function decodeJson(value) {
55
+ return JSON.parse(textDecoder.decode(decodeBase64Url(value)));
56
+ }
57
+ function encodeBase64Url(value) {
58
+ const bytes = value instanceof ArrayBuffer ? new Uint8Array(value) : value;
59
+ return btoa(bytesToBinaryString(bytes)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
60
+ }
61
+ function decodeBase64Url(value) {
62
+ const base64 = value.replaceAll("-", "+").replaceAll("_", "/");
63
+ const padded = base64.padEnd(Math.ceil(base64.length / 4) * 4, "=");
64
+ return binaryStringToBytes(atob(padded)).buffer;
65
+ }
66
+ function encodeText(value) {
67
+ return toArrayBuffer(textEncoder.encode(value));
68
+ }
69
+ function toArrayBuffer(bytes) {
70
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
71
+ }
72
+ function bytesToBinaryString(bytes) {
73
+ let binary = "";
74
+ for (let offset = 0;offset < bytes.length; offset += BYTE_CHUNK_SIZE) {
75
+ binary += String.fromCharCode(...bytes.subarray(offset, offset + BYTE_CHUNK_SIZE));
76
+ }
77
+ return binary;
78
+ }
79
+ function binaryStringToBytes(binary) {
80
+ const bytes = new Uint8Array(binary.length);
81
+ for (let index = 0;index < binary.length; index += 1) {
82
+ bytes[index] = binary.charCodeAt(index);
83
+ }
84
+ return bytes;
85
+ }
86
+ function getSubtleCrypto() {
87
+ const subtle = globalThis.crypto?.subtle;
88
+ if (!subtle) {
89
+ throw new Error("Web Crypto is not available in this runtime");
90
+ }
91
+ return subtle;
92
+ }
93
+ function isJwtHeader(value) {
94
+ return typeof value === "object" && value !== null && "alg" in value && value.alg === "HS256";
95
+ }
96
+ function isJwtPayload(value) {
97
+ return typeof value === "object" && value !== null && !Array.isArray(value);
98
+ }
99
+ function isTimeValid(payload) {
100
+ const now = Math.floor(Date.now() / 1000);
101
+ if (typeof payload.exp === "number" && now >= payload.exp)
102
+ return false;
103
+ if (typeof payload.nbf === "number" && now < payload.nbf)
104
+ return false;
105
+ return true;
106
+ }
16
107
  export {
17
- jwtVerify2 as jwtVerify,
108
+ jwtVerify,
18
109
  jwtSign
19
110
  };