hanzio 1.0.2 → 1.0.6

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.
@@ -0,0 +1,210 @@
1
+ // src/zod/index.ts
2
+ import { z as z3 } from "zod";
3
+
4
+ // src/zod/jsonToZodSchemaMapper.ts
5
+ import { z } from "zod";
6
+ function jsonToZodSchemaMapper(jsonObject, zodSchema, config) {
7
+ const result = {};
8
+ const shape = zodSchema instanceof z.ZodObject ? zodSchema.shape : {};
9
+ for (const key of Object.keys(shape)) {
10
+ const fieldConfig = config[key];
11
+ let value = jsonObject[key];
12
+ if (fieldConfig) {
13
+ value = fieldConfig(jsonObject);
14
+ }
15
+ if (shape[key] instanceof z.ZodObject && value && typeof value === "object") {
16
+ result[key] = jsonToZodSchemaMapper(value, shape[key], config[key] || {});
17
+ } else {
18
+ result[key] = value;
19
+ }
20
+ }
21
+ return zodSchema.parse(result);
22
+ }
23
+ // src/zod/zodMinMaxFilter.ts
24
+ import { z as z2 } from "zod";
25
+ var zodMinMaxFilter = z2.object({
26
+ min: z2.number().optional(),
27
+ max: z2.number().optional()
28
+ });
29
+ function toGteLteFilter(filter) {
30
+ if (!filter)
31
+ return;
32
+ const hasMin = filter.min !== undefined;
33
+ const hasMax = filter.max !== undefined;
34
+ if (!hasMin && !hasMax)
35
+ return;
36
+ return {
37
+ ...hasMin && { gte: filter.min },
38
+ ...hasMax && { lte: filter.max }
39
+ };
40
+ }
41
+ // src/zod/zodToTypeString.ts
42
+ function getDefProp(def, prop) {
43
+ return def?.[prop];
44
+ }
45
+ function zodToTypeString(schema, indent = 0) {
46
+ const pad = " ".repeat(indent);
47
+ const innerPad = " ".repeat(indent + 1);
48
+ const def = schema._zod?.def ?? schema._def;
49
+ if (!def)
50
+ return "unknown";
51
+ const schemaType = getDefProp(def, "type") ?? getDefProp(def, "typeName");
52
+ switch (schemaType) {
53
+ case "object": {
54
+ const shape = getDefProp(def, "shape");
55
+ if (!shape || Object.keys(shape).length === 0)
56
+ return "{}";
57
+ const fields = Object.entries(shape).map(([key, value]) => {
58
+ const valueType = getDefProp(value._zod?.def ?? value._def, "type");
59
+ const isOptional = valueType === "optional";
60
+ const innerSchema = isOptional ? getDefProp(value._zod?.def ?? value._def, "innerType") ?? value : value;
61
+ const typeStr = zodToTypeString(innerSchema, indent + 1);
62
+ return `${innerPad}${key}${isOptional ? "?" : ""}: ${typeStr}`;
63
+ });
64
+ return `{
65
+ ${fields.join(`
66
+ `)}
67
+ ${pad}}`;
68
+ }
69
+ case "array": {
70
+ const itemType = getDefProp(def, "element") ?? getDefProp(def, "type");
71
+ return itemType ? `${zodToTypeString(itemType, indent)}[]` : "unknown[]";
72
+ }
73
+ case "string":
74
+ return "string";
75
+ case "number":
76
+ return "number";
77
+ case "boolean":
78
+ return "boolean";
79
+ case "date":
80
+ return "Date";
81
+ case "null":
82
+ return "null";
83
+ case "undefined":
84
+ return "undefined";
85
+ case "any":
86
+ return "any";
87
+ case "unknown":
88
+ return "unknown";
89
+ case "record": {
90
+ const valueType = getDefProp(def, "valueType");
91
+ return valueType ? `Record<string, ${zodToTypeString(valueType, indent)}>` : "Record<string, unknown>";
92
+ }
93
+ case "enum": {
94
+ const values = getDefProp(def, "values");
95
+ return values ? values.map((v) => `"${v}"`).join(" | ") : "string";
96
+ }
97
+ case "literal": {
98
+ const value = getDefProp(def, "value");
99
+ return typeof value === "string" ? `"${value}"` : String(value);
100
+ }
101
+ case "union": {
102
+ const options = getDefProp(def, "options");
103
+ return options ? options.map((o) => zodToTypeString(o, indent)).join(" | ") : "unknown";
104
+ }
105
+ case "optional": {
106
+ const inner = getDefProp(def, "innerType");
107
+ return inner ? `${zodToTypeString(inner, indent)} | undefined` : "unknown";
108
+ }
109
+ case "nullable": {
110
+ const inner = getDefProp(def, "innerType");
111
+ return inner ? `${zodToTypeString(inner, indent)} | null` : "unknown";
112
+ }
113
+ case "default": {
114
+ const inner = getDefProp(def, "innerType");
115
+ return inner ? zodToTypeString(inner, indent) : "unknown";
116
+ }
117
+ case "ZodObject":
118
+ case "ZodArray":
119
+ case "ZodString":
120
+ case "ZodNumber":
121
+ case "ZodBoolean":
122
+ case "ZodDate":
123
+ case "ZodNull":
124
+ case "ZodUndefined":
125
+ case "ZodAny":
126
+ case "ZodUnknown":
127
+ case "ZodRecord":
128
+ case "ZodEnum":
129
+ case "ZodLiteral":
130
+ case "ZodUnion":
131
+ case "ZodOptional":
132
+ case "ZodNullable":
133
+ case "ZodDefault":
134
+ return zodToTypeString(schema, indent);
135
+ default:
136
+ return "unknown";
137
+ }
138
+ }
139
+ function zodToExample(schema) {
140
+ const def = schema._zod?.def ?? schema._def;
141
+ if (!def)
142
+ return null;
143
+ const schemaType = getDefProp(def, "type") ?? getDefProp(def, "typeName");
144
+ switch (schemaType) {
145
+ case "object": {
146
+ const shape = getDefProp(def, "shape");
147
+ if (!shape)
148
+ return {};
149
+ const result = {};
150
+ for (const [key, value] of Object.entries(shape)) {
151
+ const valueType = getDefProp(value._zod?.def ?? value._def, "type");
152
+ const isOptional = valueType === "optional";
153
+ if (!isOptional) {
154
+ result[key] = zodToExample(value);
155
+ }
156
+ }
157
+ return result;
158
+ }
159
+ case "array": {
160
+ const itemType = getDefProp(def, "element") ?? getDefProp(def, "type");
161
+ return itemType ? [zodToExample(itemType)] : [];
162
+ }
163
+ case "string":
164
+ return "";
165
+ case "number":
166
+ return 0;
167
+ case "boolean":
168
+ return false;
169
+ case "enum": {
170
+ const values = getDefProp(def, "values");
171
+ return values?.[0] ?? "";
172
+ }
173
+ case "literal":
174
+ return getDefProp(def, "value");
175
+ case "optional":
176
+ case "default": {
177
+ const inner = getDefProp(def, "innerType");
178
+ return inner ? zodToExample(inner) : null;
179
+ }
180
+ case "record":
181
+ return {};
182
+ default:
183
+ return null;
184
+ }
185
+ }
186
+
187
+ // src/zod/index.ts
188
+ function createZId(typeName) {
189
+ return z3.string().min(1, `${typeName} ID is required`).brand();
190
+ }
191
+ function zSingleOrArray(schema) {
192
+ return z3.union([z3.array(schema), schema]);
193
+ }
194
+ function normalizeSingleOrArray(value) {
195
+ if (value == null)
196
+ return [];
197
+ if (Array.isArray(value))
198
+ return [...value];
199
+ return [value];
200
+ }
201
+ export {
202
+ zodToTypeString,
203
+ zodToExample,
204
+ zodMinMaxFilter,
205
+ zSingleOrArray,
206
+ toGteLteFilter,
207
+ normalizeSingleOrArray,
208
+ jsonToZodSchemaMapper,
209
+ createZId
210
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hanzio",
3
- "version": "1.0.2",
3
+ "version": "1.0.6",
4
4
  "description": "A set of utility functions for TypeScript",
5
5
  "type": "module",
6
6
  "module": "./dist/index.js",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "scripts": {
40
40
  "build": "bun run build:js && bun run build:types",
41
- "build:js": "bun build ./src/index.ts ./src/zod/index.ts ./src/api-wrapper/index.ts ./src/jwt/index.ts ./src/gzip/index.ts ./src/sitemap/index.ts ./src/p-queue/index.ts --outdir ./dist --target node --packages external",
41
+ "build:js": "bun build ./src/index.ts ./src/zod/index.ts ./src/api-wrapper/index.ts ./src/jwt/index.ts ./src/gzip/index.ts ./src/sitemap/index.ts ./src/p-queue/index.ts ./src/secrets/index.ts ./src/secrets/vite.ts --outdir ./dist --target node --packages external",
42
42
  "build:types": "tsgo -p tsconfig.build.json",
43
43
  "prepublishOnly": "bun run build",
44
44
  "publish:patch": "npm version patch && npm publish",
@@ -59,6 +59,7 @@
59
59
  "typescript": "^5.7.0"
60
60
  },
61
61
  "dependencies": {
62
+ "@infisical/sdk": "^5.0.2",
62
63
  "jose": "^6.2.3",
63
64
  "pako": "^2.1.0",
64
65
  "zod": "^4.3.6"
package/src/index.ts CHANGED
@@ -11,3 +11,7 @@ export * from './zod'
11
11
  export * from './api-wrapper'
12
12
  export * from './p-queue'
13
13
  export * from './cool-console-log'
14
+ export * from './jwt'
15
+ export * from './gzip'
16
+ export * from './sitemap'
17
+ export * from './secrets'
@@ -0,0 +1,206 @@
1
+ import { InfisicalSDK } from '@infisical/sdk'
2
+ import { networkInterfaces } from 'node:os'
3
+
4
+ const DEFAULT_INFISICAL_SITE_URL = 'https://eu.infisical.com'
5
+ const LOCAL_IP_VALUE = 'LOCAL_IP'
6
+ const VPN_INTERFACE_RE = /^(utun|tun|tailscale|wg|ipsec|ppp)/i
7
+
8
+ export type SecretEnvironment = 'dev' | 'staging' | 'prod'
9
+
10
+ export type DefineSecretSetOptions<SecretKey extends string> = {
11
+ readonly projectId: string
12
+ readonly siteUrl?: string
13
+ readonly environment?: SecretEnvironment | (() => SecretEnvironment)
14
+ readonly clientIdEnvKey?: string
15
+ readonly clientSecretEnvKey?: string
16
+ readonly loader?: SecretSetLoader<SecretKey>
17
+ readonly getLocalIp?: () => string
18
+ }
19
+
20
+ export type SecretSetLoaderContext<SecretKey extends string> = {
21
+ readonly keys: readonly SecretKey[]
22
+ readonly projectId: string
23
+ readonly environment: SecretEnvironment
24
+ readonly siteUrl: string
25
+ readonly clientIdEnvKey: string
26
+ readonly clientSecretEnvKey: string
27
+ }
28
+
29
+ export type SecretSetLoader<SecretKey extends string> = (
30
+ context: SecretSetLoaderContext<SecretKey>
31
+ ) => Promise<Partial<Record<SecretKey, string>>>
32
+
33
+ export type SecretSet<SecretKey extends string> = {
34
+ secret: (key: SecretKey) => string
35
+ secrets: () => Record<SecretKey, string>
36
+ reload: () => Promise<SecretSet<SecretKey>>
37
+ }
38
+
39
+ type InfisicalSecret = {
40
+ secretKey: string
41
+ secretValue: string
42
+ }
43
+
44
+ export * from './vite'
45
+
46
+ export function getSecretEnvironment(): SecretEnvironment {
47
+ const env = process.env.SECRETS_ENV || process.env.NODE_ENV
48
+ if (env === 'production' || env === 'prod') return 'prod'
49
+ if (env === 'staging') return 'staging'
50
+ return 'dev'
51
+ }
52
+
53
+ export async function defineSecretSet<
54
+ const SecretKeys extends readonly string[]
55
+ >(
56
+ keys: SecretKeys,
57
+ options: DefineSecretSetOptions<SecretKeys[number]>
58
+ ): Promise<SecretSet<SecretKeys[number]>> {
59
+ type SecretKey = SecretKeys[number]
60
+
61
+ const keySet = [...new Set(keys)] as SecretKey[]
62
+ const loader = options.loader ?? loadInfisicalSecrets
63
+ const getLocalIp = options.getLocalIp ?? getLocalIpAddress
64
+ let cachedSecrets: Record<SecretKey, string> | null = null
65
+
66
+ const resolveValue = (value: string) => {
67
+ if (value === LOCAL_IP_VALUE) return getLocalIp()
68
+ return value
69
+ }
70
+
71
+ const loadSecrets = async (force = false) => {
72
+ const envSecrets = readEnvSecrets(keySet, force ? cachedSecrets : null)
73
+ const keysMissingFromEnv = keySet.filter((key) => !envSecrets[key])
74
+ const loadedSecrets: Partial<Record<SecretKey, string>> =
75
+ keysMissingFromEnv.length === 0 && !force
76
+ ? {}
77
+ : await loader({
78
+ keys: keySet,
79
+ projectId: options.projectId,
80
+ environment: resolveEnvironment(options.environment),
81
+ siteUrl: options.siteUrl ?? DEFAULT_INFISICAL_SITE_URL,
82
+ clientIdEnvKey: options.clientIdEnvKey ?? 'INFISICAL_CLIENT_ID',
83
+ clientSecretEnvKey:
84
+ options.clientSecretEnvKey ?? 'INFISICAL_CLIENT_SECRET'
85
+ })
86
+
87
+ const missingKeys = keySet.filter(
88
+ (key) => !(envSecrets[key] ?? loadedSecrets[key])
89
+ )
90
+ if (missingKeys.length > 0) {
91
+ throw new Error(`Missing secrets: ${missingKeys.join(', ')}`)
92
+ }
93
+
94
+ const nextSecrets = Object.fromEntries(
95
+ keySet.map((key) => [
96
+ key,
97
+ resolveValue((envSecrets[key] ?? loadedSecrets[key]) as string)
98
+ ])
99
+ ) as Record<SecretKey, string>
100
+
101
+ for (const [key, value] of Object.entries(nextSecrets)) {
102
+ process.env[key] = value as string
103
+ }
104
+ cachedSecrets = nextSecrets
105
+ return nextSecrets
106
+ }
107
+
108
+ const secretSet: SecretSet<SecretKey> = {
109
+ secret(key) {
110
+ const value = cachedSecrets?.[key] ?? process.env[key]
111
+ if (!value) {
112
+ throw new Error(`Secret ${key} not found`)
113
+ }
114
+ return resolveValue(value)
115
+ },
116
+ secrets() {
117
+ if (!cachedSecrets) {
118
+ throw new Error('Secrets not initialized.')
119
+ }
120
+ return cachedSecrets
121
+ },
122
+ async reload() {
123
+ await loadSecrets(true)
124
+ return secretSet
125
+ }
126
+ }
127
+
128
+ await loadSecrets()
129
+ return secretSet
130
+ }
131
+
132
+ async function loadInfisicalSecrets<SecretKey extends string>({
133
+ keys,
134
+ projectId,
135
+ environment,
136
+ siteUrl,
137
+ clientIdEnvKey,
138
+ clientSecretEnvKey
139
+ }: SecretSetLoaderContext<SecretKey>): Promise<
140
+ Partial<Record<SecretKey, string>>
141
+ > {
142
+ const clientId = process.env[clientIdEnvKey]
143
+ const clientSecret = process.env[clientSecretEnvKey]
144
+
145
+ if (!clientId || !clientSecret) {
146
+ throw new Error('Missing Infisical credentials')
147
+ }
148
+
149
+ const client = new InfisicalSDK({ siteUrl })
150
+
151
+ await client.auth().universalAuth.login({
152
+ clientId,
153
+ clientSecret
154
+ })
155
+
156
+ const { secrets } = (await client.secrets().listSecrets({
157
+ environment,
158
+ projectId
159
+ })) as { secrets: InfisicalSecret[] }
160
+
161
+ const wantedKeys = new Set<string>(keys)
162
+ return Object.fromEntries(
163
+ secrets
164
+ .filter((secret) => wantedKeys.has(secret.secretKey))
165
+ .map((secret) => [secret.secretKey, secret.secretValue])
166
+ ) as Partial<Record<SecretKey, string>>
167
+ }
168
+
169
+ function resolveEnvironment(
170
+ environment: DefineSecretSetOptions<string>['environment']
171
+ ): SecretEnvironment {
172
+ if (typeof environment === 'function') return environment()
173
+ return environment ?? getSecretEnvironment()
174
+ }
175
+
176
+ function readEnvSecrets<SecretKey extends string>(
177
+ keys: readonly SecretKey[],
178
+ previousSecrets: Record<SecretKey, string> | null = null
179
+ ): Partial<Record<SecretKey, string>> {
180
+ return Object.fromEntries(
181
+ keys
182
+ .filter((key) => {
183
+ const value = process.env[key]
184
+ return value && value !== previousSecrets?.[key]
185
+ })
186
+ .map((key) => [key, process.env[key] as string])
187
+ ) as Partial<Record<SecretKey, string>>
188
+ }
189
+
190
+ function getLocalIpAddress(): string {
191
+ const interfaces = networkInterfaces()
192
+
193
+ for (const name of Object.keys(interfaces)) {
194
+ if (VPN_INTERFACE_RE.test(name)) continue
195
+ const nets = interfaces[name]
196
+ if (!nets) continue
197
+
198
+ for (const net of nets) {
199
+ if (net.family === 'IPv4' && !net.internal) {
200
+ return net.address
201
+ }
202
+ }
203
+ }
204
+
205
+ return '127.0.0.1'
206
+ }
@@ -0,0 +1,30 @@
1
+ import type { SecretSet } from '.'
2
+
3
+ export type ViteSecretSetPlugin = {
4
+ name: string
5
+ config: () => {
6
+ define: Record<string, string>
7
+ }
8
+ }
9
+
10
+ export function getViteDefine<SecretKey extends string>(
11
+ secretSet: SecretSet<SecretKey>
12
+ ): Record<string, string> {
13
+ return Object.fromEntries(
14
+ Object.entries(secretSet.secrets()).map(([key, value]) => [
15
+ `import.meta.env.${key}`,
16
+ JSON.stringify(value)
17
+ ])
18
+ )
19
+ }
20
+
21
+ export function viteSecretSetPlugin<SecretKey extends string>(
22
+ secretSet: SecretSet<SecretKey>
23
+ ): ViteSecretSetPlugin {
24
+ return {
25
+ name: 'hanzio-secret-set',
26
+ config: () => ({
27
+ define: getViteDefine(secretSet)
28
+ })
29
+ }
30
+ }