hanzio 1.0.3 → 1.0.7

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.3",
3
+ "version": "1.0.7",
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
@@ -13,4 +13,5 @@ export * from './p-queue'
13
13
  export * from './cool-console-log'
14
14
  export * from './jwt'
15
15
  export * from './gzip'
16
- export * from './sitemap'
16
+ export * from './sitemap'
17
+ export * from './secrets'
@@ -0,0 +1,222 @@
1
+ import { InfisicalSDK } from '@infisical/sdk'
2
+ import { networkInterfaces } from 'node:os'
3
+ import { colorize, createCoolLogger } from '../cool-console-log'
4
+
5
+ const DEFAULT_INFISICAL_SITE_URL = 'https://eu.infisical.com'
6
+ const LOCAL_IP_VALUE = 'LOCAL_IP'
7
+ const VPN_INTERFACE_RE = /^(utun|tun|tailscale|wg|ipsec|ppp)/i
8
+ const logger = createCoolLogger()
9
+
10
+ export type SecretEnvironment = 'dev' | 'staging' | 'prod'
11
+
12
+ export type DefineSecretSetOptions<SecretKey extends string> = {
13
+ readonly projectId: string
14
+ readonly siteUrl?: string
15
+ readonly environment?: SecretEnvironment | (() => SecretEnvironment)
16
+ readonly clientIdEnvKey?: string
17
+ readonly clientSecretEnvKey?: string
18
+ readonly loader?: SecretSetLoader<SecretKey>
19
+ readonly getLocalIp?: () => string
20
+ }
21
+
22
+ export type SecretSetLoaderContext<SecretKey extends string> = {
23
+ readonly keys: readonly SecretKey[]
24
+ readonly projectId: string
25
+ readonly environment: SecretEnvironment
26
+ readonly siteUrl: string
27
+ readonly clientIdEnvKey: string
28
+ readonly clientSecretEnvKey: string
29
+ }
30
+
31
+ export type SecretSetLoader<SecretKey extends string> = (
32
+ context: SecretSetLoaderContext<SecretKey>
33
+ ) => Promise<Partial<Record<SecretKey, string>>>
34
+
35
+ export type SecretSet<SecretKey extends string> = {
36
+ secret: (key: SecretKey) => string
37
+ secrets: () => Record<SecretKey, string>
38
+ reload: () => Promise<SecretSet<SecretKey>>
39
+ }
40
+
41
+ type InfisicalSecret = {
42
+ secretKey: string
43
+ secretValue: string
44
+ }
45
+
46
+ export * from './vite'
47
+
48
+ export function getSecretEnvironment(): SecretEnvironment {
49
+ const env = process.env.SECRETS_ENV || process.env.NODE_ENV
50
+ if (env === 'production' || env === 'prod') return 'prod'
51
+ if (env === 'staging') return 'staging'
52
+ return 'dev'
53
+ }
54
+
55
+ export async function defineSecretSet<
56
+ const SecretKeys extends readonly string[]
57
+ >(
58
+ keys: SecretKeys,
59
+ options: DefineSecretSetOptions<SecretKeys[number]>
60
+ ): Promise<SecretSet<SecretKeys[number]>> {
61
+ type SecretKey = SecretKeys[number]
62
+
63
+ const keySet = [...new Set(keys)] as SecretKey[]
64
+ const loader = options.loader ?? loadInfisicalSecrets
65
+ const getLocalIp = options.getLocalIp ?? getLocalIpAddress
66
+ let cachedSecrets: Record<SecretKey, string> | null = null
67
+
68
+ const resolveValue = (value: string) => {
69
+ if (value === LOCAL_IP_VALUE) return getLocalIp()
70
+ return value
71
+ }
72
+
73
+ const loadSecrets = async (force = false) => {
74
+ const envSecrets = readEnvSecrets(keySet, force ? cachedSecrets : null)
75
+ const keysMissingFromEnv = keySet.filter((key) => !envSecrets[key])
76
+ const environment = resolveEnvironment(options.environment)
77
+
78
+ logSecretEnvironment(environment, keySet.length)
79
+
80
+ const loadedSecrets: Partial<Record<SecretKey, string>> =
81
+ keysMissingFromEnv.length === 0 && !force
82
+ ? {}
83
+ : await loader({
84
+ keys: keySet,
85
+ projectId: options.projectId,
86
+ environment,
87
+ siteUrl: options.siteUrl ?? DEFAULT_INFISICAL_SITE_URL,
88
+ clientIdEnvKey: options.clientIdEnvKey ?? 'INFISICAL_CLIENT_ID',
89
+ clientSecretEnvKey:
90
+ options.clientSecretEnvKey ?? 'INFISICAL_CLIENT_SECRET'
91
+ })
92
+
93
+ const missingKeys = keySet.filter(
94
+ (key) => !(envSecrets[key] ?? loadedSecrets[key])
95
+ )
96
+ if (missingKeys.length > 0) {
97
+ throw new Error(`Missing secrets: ${missingKeys.join(', ')}`)
98
+ }
99
+
100
+ const nextSecrets = Object.fromEntries(
101
+ keySet.map((key) => [
102
+ key,
103
+ resolveValue((envSecrets[key] ?? loadedSecrets[key]) as string)
104
+ ])
105
+ ) as Record<SecretKey, string>
106
+
107
+ for (const [key, value] of Object.entries(nextSecrets)) {
108
+ process.env[key] = value as string
109
+ }
110
+ cachedSecrets = nextSecrets
111
+ return nextSecrets
112
+ }
113
+
114
+ const secretSet: SecretSet<SecretKey> = {
115
+ secret(key) {
116
+ const value = cachedSecrets?.[key] ?? process.env[key]
117
+ if (!value) {
118
+ throw new Error(`Secret ${key} not found`)
119
+ }
120
+ return resolveValue(value)
121
+ },
122
+ secrets() {
123
+ if (!cachedSecrets) {
124
+ throw new Error('Secrets not initialized.')
125
+ }
126
+ return cachedSecrets
127
+ },
128
+ async reload() {
129
+ await loadSecrets(true)
130
+ return secretSet
131
+ }
132
+ }
133
+
134
+ await loadSecrets()
135
+ return secretSet
136
+ }
137
+
138
+ async function loadInfisicalSecrets<SecretKey extends string>({
139
+ keys,
140
+ projectId,
141
+ environment,
142
+ siteUrl,
143
+ clientIdEnvKey,
144
+ clientSecretEnvKey
145
+ }: SecretSetLoaderContext<SecretKey>): Promise<
146
+ Partial<Record<SecretKey, string>>
147
+ > {
148
+ const clientId = process.env[clientIdEnvKey]
149
+ const clientSecret = process.env[clientSecretEnvKey]
150
+
151
+ if (!clientId || !clientSecret) {
152
+ throw new Error('Missing Infisical credentials')
153
+ }
154
+
155
+ const client = new InfisicalSDK({ siteUrl })
156
+
157
+ await client.auth().universalAuth.login({
158
+ clientId,
159
+ clientSecret
160
+ })
161
+
162
+ const { secrets } = (await client.secrets().listSecrets({
163
+ environment,
164
+ projectId
165
+ })) as { secrets: InfisicalSecret[] }
166
+
167
+ const wantedKeys = new Set<string>(keys)
168
+ return Object.fromEntries(
169
+ secrets
170
+ .filter((secret) => wantedKeys.has(secret.secretKey))
171
+ .map((secret) => [secret.secretKey, secret.secretValue])
172
+ ) as Partial<Record<SecretKey, string>>
173
+ }
174
+
175
+ function resolveEnvironment(
176
+ environment: DefineSecretSetOptions<string>['environment']
177
+ ): SecretEnvironment {
178
+ if (typeof environment === 'function') return environment()
179
+ return environment ?? getSecretEnvironment()
180
+ }
181
+
182
+ function logSecretEnvironment(
183
+ environment: SecretEnvironment,
184
+ secretCount: number
185
+ ): void {
186
+ logger.info(colorize('Loading secrets', 'cyan', 'bold'), {
187
+ environment: colorize(environment, 'brightCyan', 'bold'),
188
+ count: secretCount
189
+ })
190
+ }
191
+
192
+ function readEnvSecrets<SecretKey extends string>(
193
+ keys: readonly SecretKey[],
194
+ previousSecrets: Record<SecretKey, string> | null = null
195
+ ): Partial<Record<SecretKey, string>> {
196
+ return Object.fromEntries(
197
+ keys
198
+ .filter((key) => {
199
+ const value = process.env[key]
200
+ return value && value !== previousSecrets?.[key]
201
+ })
202
+ .map((key) => [key, process.env[key] as string])
203
+ ) as Partial<Record<SecretKey, string>>
204
+ }
205
+
206
+ function getLocalIpAddress(): string {
207
+ const interfaces = networkInterfaces()
208
+
209
+ for (const name of Object.keys(interfaces)) {
210
+ if (VPN_INTERFACE_RE.test(name)) continue
211
+ const nets = interfaces[name]
212
+ if (!nets) continue
213
+
214
+ for (const net of nets) {
215
+ if (net.family === 'IPv4' && !net.internal) {
216
+ return net.address
217
+ }
218
+ }
219
+ }
220
+
221
+ return '127.0.0.1'
222
+ }
@@ -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
+ }