hanzio 1.0.10 → 1.2.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/README.md +70 -4
- package/dist/api-wrapper/actions-impl.d.ts +4 -0
- package/dist/api-wrapper/builders.d.ts +23 -0
- package/dist/api-wrapper/create-client.d.ts +6 -0
- package/dist/api-wrapper/error-reporting.d.ts +7 -0
- package/dist/api-wrapper/errors.d.ts +64 -0
- package/dist/api-wrapper/headers.d.ts +10 -0
- package/dist/api-wrapper/index.d.ts +5 -84
- package/dist/api-wrapper/redaction.d.ts +2 -0
- package/dist/api-wrapper/shared.d.ts +16 -0
- package/dist/api-wrapper/size.d.ts +1 -0
- package/dist/api-wrapper/test-helpers.d.ts +6 -0
- package/dist/api-wrapper/transport.d.ts +55 -0
- package/dist/api-wrapper/types.d.ts +154 -0
- package/dist/api-wrapper/zod-issues.d.ts +17 -0
- package/dist/cache/index.d.ts +26 -0
- package/dist/secrets/core.d.ts +47 -0
- package/dist/secrets/deadline.d.ts +6 -0
- package/dist/secrets/environment.d.ts +9 -0
- package/dist/secrets/errors.d.ts +11 -0
- package/dist/secrets/index.d.ts +14 -23
- package/dist/secrets/infisical-cli.d.ts +6 -0
- package/dist/secrets/infisical-config.d.ts +17 -0
- package/dist/secrets/infisical-session.d.ts +4 -0
- package/dist/secrets/infisical.d.ts +14 -2
- package/dist/secrets/loaders.d.ts +9 -0
- package/dist/secrets/vite.d.ts +7 -3
- package/dist/secrets/worker-loader.d.ts +9 -0
- package/dist/secrets/worker.d.ts +3 -0
- package/dist/src/api-wrapper/index.js +1059 -177
- package/dist/src/index.js +2807 -1397
- package/dist/src/jwt/index.js +2 -2
- package/dist/src/p-queue/index.js +3 -3
- package/dist/src/secrets/index.js +1610 -407
- package/dist/src/secrets/infisical.js +1352 -214
- package/dist/src/secrets/loaders.js +1529 -0
- package/dist/src/secrets/vite.js +33 -11
- package/dist/src/secrets/worker.js +275 -0
- package/dist/src/sitemap/index.js +1 -1
- package/dist/src/zod/index.js +7 -7
- package/dist/typedswitch/index.d.ts +9 -5
- package/package.json +12 -3
- package/src/api-wrapper/actions-impl.ts +100 -0
- package/src/api-wrapper/builders.ts +200 -0
- package/src/api-wrapper/create-client.ts +393 -0
- package/src/api-wrapper/error-reporting.ts +31 -0
- package/src/api-wrapper/errors.ts +231 -0
- package/src/api-wrapper/headers.ts +79 -0
- package/src/api-wrapper/index.ts +5 -492
- package/src/api-wrapper/redaction.ts +62 -0
- package/src/api-wrapper/shared.ts +25 -0
- package/src/api-wrapper/size.ts +19 -0
- package/src/api-wrapper/test-helpers.ts +25 -0
- package/src/api-wrapper/transport.ts +350 -0
- package/src/api-wrapper/types.ts +259 -0
- package/src/api-wrapper/zod-issues.ts +157 -0
- package/src/cache/index.ts +228 -61
- package/src/secrets/core.ts +348 -0
- package/src/secrets/deadline.ts +69 -0
- package/src/secrets/environment.ts +69 -0
- package/src/secrets/errors.ts +29 -0
- package/src/secrets/index.ts +81 -140
- package/src/secrets/infisical-cli.ts +99 -0
- package/src/secrets/infisical-config.ts +70 -0
- package/src/secrets/infisical-session.ts +42 -0
- package/src/secrets/infisical.ts +219 -35
- package/src/secrets/loaders.ts +43 -0
- package/src/secrets/vite.ts +44 -15
- package/src/secrets/worker-loader.ts +56 -0
- package/src/secrets/worker.ts +4 -0
- package/src/typedswitch/index.ts +80 -69
package/dist/src/secrets/vite.js
CHANGED
|
@@ -1,19 +1,41 @@
|
|
|
1
|
+
// src/secrets/errors.ts
|
|
2
|
+
class SecretLoadError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
operation;
|
|
5
|
+
status;
|
|
6
|
+
constructor(code, message, details = {}) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "SecretLoadError";
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.operation = details.operation;
|
|
11
|
+
this.status = details.status;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
1
15
|
// src/secrets/vite.ts
|
|
2
|
-
function getViteDefine(secretSet) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
16
|
+
function getViteDefine(secretSet, options = {}) {
|
|
17
|
+
const values = secretSet.secrets();
|
|
18
|
+
const keys = options.publicKeys ?? Object.keys(values);
|
|
19
|
+
if (!options.publicKeys && keys.some((key) => !key.startsWith("VITE_"))) {
|
|
20
|
+
throw new SecretLoadError("CONFIGURATION", "Vite secret sets must contain only VITE_ keys, or provide an explicit publicKeys list.");
|
|
21
|
+
}
|
|
22
|
+
for (const key of keys) {
|
|
23
|
+
if (!Object.hasOwn(values, key) || !/^[A-Za-z_$][\w$]*$/.test(key)) {
|
|
24
|
+
throw new SecretLoadError("CONFIGURATION", "Vite publicKeys must reference configured keys with valid JavaScript identifiers.");
|
|
25
|
+
}
|
|
26
|
+
if (["MODE", "BASE_URL", "PROD", "DEV", "SSR"].includes(key)) {
|
|
27
|
+
throw new SecretLoadError("CONFIGURATION", "Vite built-in environment constants cannot be overridden by secrets.");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return Object.fromEntries(keys.map((key) => [`import.meta.env.${key}`, JSON.stringify(values[key])]));
|
|
7
31
|
}
|
|
8
|
-
function viteSecretSetPlugin(secretSet) {
|
|
32
|
+
function viteSecretSetPlugin(secretSet, options = {}) {
|
|
9
33
|
return {
|
|
10
34
|
name: "hanzio-secret-set",
|
|
11
|
-
config: () => ({
|
|
12
|
-
define: getViteDefine(secretSet)
|
|
13
|
-
})
|
|
35
|
+
config: () => ({ define: getViteDefine(secretSet, options) })
|
|
14
36
|
};
|
|
15
37
|
}
|
|
16
38
|
export {
|
|
17
|
-
|
|
18
|
-
|
|
39
|
+
getViteDefine,
|
|
40
|
+
viteSecretSetPlugin
|
|
19
41
|
};
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// src/secrets/errors.ts
|
|
2
|
+
class SecretLoadError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
operation;
|
|
5
|
+
status;
|
|
6
|
+
constructor(code, message, details = {}) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = "SecretLoadError";
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.operation = details.operation;
|
|
11
|
+
this.status = details.status;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// src/secrets/deadline.ts
|
|
16
|
+
async function withSecretDeadline(options, operation) {
|
|
17
|
+
const timeoutMs = options.timeoutMs ?? 30000;
|
|
18
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2147483647) {
|
|
19
|
+
throw new SecretLoadError("CONFIGURATION", "Secret timeoutMs must be a positive finite timer duration.");
|
|
20
|
+
}
|
|
21
|
+
const controller = new AbortController;
|
|
22
|
+
const cancel = () => controller.abort(new SecretLoadError("ABORTED", "Secret loading was cancelled."));
|
|
23
|
+
if (options.signal?.aborted) {
|
|
24
|
+
cancel();
|
|
25
|
+
} else {
|
|
26
|
+
options.signal?.addEventListener("abort", cancel, { once: true });
|
|
27
|
+
}
|
|
28
|
+
const timer = setTimeout(() => controller.abort(new SecretLoadError("TIMEOUT", `Secret loading timed out after ${timeoutMs}ms.`)), timeoutMs);
|
|
29
|
+
let rejectOnAbort = () => {};
|
|
30
|
+
try {
|
|
31
|
+
const aborted = new Promise((_, reject) => {
|
|
32
|
+
rejectOnAbort = () => reject(controller.signal.reason);
|
|
33
|
+
controller.signal.addEventListener("abort", rejectOnAbort, { once: true });
|
|
34
|
+
});
|
|
35
|
+
if (controller.signal.aborted) {
|
|
36
|
+
throw controller.signal.reason;
|
|
37
|
+
}
|
|
38
|
+
return await Promise.race([operation(controller.signal), aborted]);
|
|
39
|
+
} finally {
|
|
40
|
+
clearTimeout(timer);
|
|
41
|
+
options.signal?.removeEventListener("abort", cancel);
|
|
42
|
+
controller.signal.removeEventListener("abort", rejectOnAbort);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/secrets/environment.ts
|
|
47
|
+
function getProcessEnvironment() {
|
|
48
|
+
return typeof process === "undefined" ? undefined : process.env;
|
|
49
|
+
}
|
|
50
|
+
function resolveSecretEnvironment(option, env) {
|
|
51
|
+
const explicit = typeof option === "function" ? option() : option;
|
|
52
|
+
if (typeof explicit === "object" && explicit !== null) {
|
|
53
|
+
if (typeof explicit.slug !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(explicit.slug)) {
|
|
54
|
+
throw new SecretLoadError("CONFIGURATION", "Custom secret environment requires a valid slug.");
|
|
55
|
+
}
|
|
56
|
+
return explicit.slug;
|
|
57
|
+
}
|
|
58
|
+
const selected = explicit ?? env?.SECRETS_ENV;
|
|
59
|
+
if (selected !== undefined) {
|
|
60
|
+
if (selected === "production" || selected === "prod") {
|
|
61
|
+
return "prod";
|
|
62
|
+
}
|
|
63
|
+
if (selected === "development" || selected === "dev") {
|
|
64
|
+
return "dev";
|
|
65
|
+
}
|
|
66
|
+
if (selected === "staging") {
|
|
67
|
+
return "staging";
|
|
68
|
+
}
|
|
69
|
+
throw new SecretLoadError("CONFIGURATION", "Invalid secret environment. Use dev, staging, prod, or an explicit { slug } option.");
|
|
70
|
+
}
|
|
71
|
+
if (env?.NODE_ENV === "production" || env?.NODE_ENV === "prod") {
|
|
72
|
+
return "prod";
|
|
73
|
+
}
|
|
74
|
+
if (env?.NODE_ENV === "staging") {
|
|
75
|
+
return "staging";
|
|
76
|
+
}
|
|
77
|
+
return "dev";
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/secrets/core.ts
|
|
81
|
+
var registryKey = Symbol.for("hanzio.secrets.environmentWrites.v1");
|
|
82
|
+
var shared = globalThis;
|
|
83
|
+
var environmentWrites = shared[registryKey] ?? new WeakMap;
|
|
84
|
+
shared[registryKey] = environmentWrites;
|
|
85
|
+
var defaultLogger = {
|
|
86
|
+
info: (message, attrs) => console.log(`${message} environment=${attrs.environment} count=${attrs.count} source=${attrs.source} durationMs=${attrs.durationMs}`, { sources: attrs.sources }),
|
|
87
|
+
error: (message, attrs) => console.error(message, attrs)
|
|
88
|
+
};
|
|
89
|
+
function readOverrides(keys, env) {
|
|
90
|
+
if (!env) {
|
|
91
|
+
return {};
|
|
92
|
+
}
|
|
93
|
+
const writes = environmentWrites.get(env);
|
|
94
|
+
return Object.fromEntries(keys.flatMap((key) => {
|
|
95
|
+
const value = Object.hasOwn(env, key) ? env[key] : undefined;
|
|
96
|
+
if (writes?.has(key)) {
|
|
97
|
+
if (writes.get(key) === value) {
|
|
98
|
+
return [];
|
|
99
|
+
}
|
|
100
|
+
writes.delete(key);
|
|
101
|
+
}
|
|
102
|
+
return value ? [[key, value]] : [];
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
async function defineSecretSet(keys, options, environmentVariables) {
|
|
106
|
+
const keySet = [...new Set(keys)];
|
|
107
|
+
const environment = resolveSecretEnvironment(options.environment, environmentVariables);
|
|
108
|
+
const logger = options.logger === false ? undefined : options.logger ?? defaultLogger;
|
|
109
|
+
let cached;
|
|
110
|
+
let parsed;
|
|
111
|
+
let sources;
|
|
112
|
+
let inFlight;
|
|
113
|
+
const log = (level, message, attributes) => {
|
|
114
|
+
try {
|
|
115
|
+
logger?.[level](message, attributes);
|
|
116
|
+
} catch {}
|
|
117
|
+
};
|
|
118
|
+
const loadSnapshot = async (request, signal) => {
|
|
119
|
+
const local = readOverrides(keySet, environmentVariables);
|
|
120
|
+
const missing = keySet.filter((key) => !Object.hasOwn(local, key));
|
|
121
|
+
let loaderSource = options.loader.source ?? "custom loader";
|
|
122
|
+
let loaded = {};
|
|
123
|
+
if (missing.length) {
|
|
124
|
+
try {
|
|
125
|
+
loaded = await options.loader({
|
|
126
|
+
keys: missing,
|
|
127
|
+
environment,
|
|
128
|
+
signal,
|
|
129
|
+
timeoutMs: request.timeoutMs ?? options.timeoutMs ?? 30000,
|
|
130
|
+
reportSource: (source) => {
|
|
131
|
+
loaderSource = source;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
} catch (error) {
|
|
135
|
+
if (error instanceof SecretLoadError) {
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
throw new SecretLoadError("LOADER", "Secret loader failed.");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const overrides = readOverrides(keySet, environmentVariables);
|
|
142
|
+
const next = Object.fromEntries(keySet.map((key) => {
|
|
143
|
+
if (Object.hasOwn(overrides, key)) {
|
|
144
|
+
return [key, overrides[key]];
|
|
145
|
+
}
|
|
146
|
+
if (Object.hasOwn(loaded, key)) {
|
|
147
|
+
return [key, loaded[key]];
|
|
148
|
+
}
|
|
149
|
+
return [key, undefined];
|
|
150
|
+
}));
|
|
151
|
+
const missingKeys = keySet.filter((key) => typeof next[key] !== "string" || !next[key]);
|
|
152
|
+
if (missingKeys.length) {
|
|
153
|
+
throw new SecretLoadError("MISSING_SECRETS", `Missing secrets: ${missingKeys.join(", ")}`);
|
|
154
|
+
}
|
|
155
|
+
let nextParsed;
|
|
156
|
+
try {
|
|
157
|
+
nextParsed = options.schema ? await options.schema.parseAsync({ ...next }) : { ...next };
|
|
158
|
+
nextParsed = structuredClone(nextParsed);
|
|
159
|
+
} catch {
|
|
160
|
+
throw new SecretLoadError("VALIDATION", "Secret schema validation failed. Check the configured schema; values are omitted.");
|
|
161
|
+
}
|
|
162
|
+
signal.throwIfAborted();
|
|
163
|
+
const nextSources = Object.fromEntries(keySet.map((key) => [
|
|
164
|
+
key,
|
|
165
|
+
Object.hasOwn(overrides, key) ? "process.env" : loaderSource
|
|
166
|
+
]));
|
|
167
|
+
if (options.writeToProcessEnv !== false && environmentVariables) {
|
|
168
|
+
let writes = environmentWrites.get(environmentVariables);
|
|
169
|
+
if (!writes) {
|
|
170
|
+
writes = new Map;
|
|
171
|
+
environmentWrites.set(environmentVariables, writes);
|
|
172
|
+
}
|
|
173
|
+
for (const key of keySet) {
|
|
174
|
+
if (Object.hasOwn(overrides, key)) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
environmentVariables[key] = next[key];
|
|
178
|
+
writes.set(key, next[key]);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
cached = next;
|
|
182
|
+
parsed = nextParsed;
|
|
183
|
+
sources = nextSources;
|
|
184
|
+
};
|
|
185
|
+
const load = (request = {}) => {
|
|
186
|
+
if (inFlight) {
|
|
187
|
+
return inFlight;
|
|
188
|
+
}
|
|
189
|
+
inFlight = (async () => {
|
|
190
|
+
const start = Date.now();
|
|
191
|
+
try {
|
|
192
|
+
await withSecretDeadline({
|
|
193
|
+
timeoutMs: request.timeoutMs ?? options.timeoutMs,
|
|
194
|
+
signal: request.signal ?? options.signal
|
|
195
|
+
}, (signal) => loadSnapshot(request, signal));
|
|
196
|
+
const sourceCounts = new Map;
|
|
197
|
+
for (const key of keySet) {
|
|
198
|
+
const source = sources[key];
|
|
199
|
+
sourceCounts.set(source, (sourceCounts.get(source) ?? 0) + 1);
|
|
200
|
+
}
|
|
201
|
+
const counts = Object.fromEntries(sourceCounts);
|
|
202
|
+
log("info", "Loaded secrets", {
|
|
203
|
+
environment,
|
|
204
|
+
count: keySet.length,
|
|
205
|
+
durationMs: Date.now() - start,
|
|
206
|
+
source: Object.keys(counts).join(" + ") || "none",
|
|
207
|
+
sources: counts
|
|
208
|
+
});
|
|
209
|
+
} catch (error) {
|
|
210
|
+
const safe = error instanceof SecretLoadError ? error : new SecretLoadError("LOADER", "Secret loading failed.");
|
|
211
|
+
log("error", "Secret loading failed", {
|
|
212
|
+
environment,
|
|
213
|
+
count: keySet.length,
|
|
214
|
+
durationMs: Date.now() - start,
|
|
215
|
+
code: safe.code
|
|
216
|
+
});
|
|
217
|
+
throw safe;
|
|
218
|
+
}
|
|
219
|
+
})().finally(() => {
|
|
220
|
+
inFlight = undefined;
|
|
221
|
+
});
|
|
222
|
+
return inFlight;
|
|
223
|
+
};
|
|
224
|
+
const secretSet = {
|
|
225
|
+
secret(key) {
|
|
226
|
+
if (!Object.hasOwn(cached, key)) {
|
|
227
|
+
throw new SecretLoadError("MISSING_SECRETS", `Secret ${key} not found`);
|
|
228
|
+
}
|
|
229
|
+
return cached[key];
|
|
230
|
+
},
|
|
231
|
+
secrets: () => ({ ...cached }),
|
|
232
|
+
parsed: () => structuredClone(parsed),
|
|
233
|
+
sources: () => ({ ...sources }),
|
|
234
|
+
async reload(request) {
|
|
235
|
+
await load(request);
|
|
236
|
+
return secretSet;
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
await load();
|
|
240
|
+
return secretSet;
|
|
241
|
+
}
|
|
242
|
+
// src/secrets/worker-loader.ts
|
|
243
|
+
function cloudflareWorkerEnvLoader(workerEnv) {
|
|
244
|
+
const loader = async (ctx) => {
|
|
245
|
+
const out = Object.create(null);
|
|
246
|
+
for (const key of ctx.keys) {
|
|
247
|
+
const str = workerBindingToString(Object.hasOwn(workerEnv, key) ? workerEnv[key] : undefined);
|
|
248
|
+
if (str !== undefined) {
|
|
249
|
+
out[key] = str;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return out;
|
|
253
|
+
};
|
|
254
|
+
return Object.assign(loader, { source: "Cloudflare Worker bindings" });
|
|
255
|
+
}
|
|
256
|
+
function workerBindingToString(value) {
|
|
257
|
+
if (value === undefined || value === null) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (typeof value === "string") {
|
|
261
|
+
return value === "" ? undefined : value;
|
|
262
|
+
}
|
|
263
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
264
|
+
return String(value);
|
|
265
|
+
}
|
|
266
|
+
if (typeof value === "object") {
|
|
267
|
+
return JSON.stringify(value);
|
|
268
|
+
}
|
|
269
|
+
return String(value);
|
|
270
|
+
}
|
|
271
|
+
export {
|
|
272
|
+
SecretLoadError,
|
|
273
|
+
cloudflareWorkerEnvLoader,
|
|
274
|
+
defineSecretSet
|
|
275
|
+
};
|
|
@@ -28,7 +28,7 @@ class PriorityQueue {
|
|
|
28
28
|
return this.#queue.filter((item) => Object.entries(options).every(([key, value]) => item[key] === value)).map((item) => item.run);
|
|
29
29
|
}
|
|
30
30
|
setPriority(id, priority) {
|
|
31
|
-
const index = this.#queue.findIndex((
|
|
31
|
+
const index = this.#queue.findIndex((item) => item.id === id);
|
|
32
32
|
if (index === -1) {
|
|
33
33
|
throw new ReferenceError(`No promise function with the id "${id}" exists in the queue.`);
|
|
34
34
|
}
|
package/dist/src/zod/index.js
CHANGED
|
@@ -199,12 +199,12 @@ function normalizeSingleOrArray(value) {
|
|
|
199
199
|
return [value];
|
|
200
200
|
}
|
|
201
201
|
export {
|
|
202
|
-
|
|
203
|
-
zodToExample,
|
|
204
|
-
zodMinMaxFilter,
|
|
205
|
-
zSingleOrArray,
|
|
206
|
-
toGteLteFilter,
|
|
207
|
-
normalizeSingleOrArray,
|
|
202
|
+
createZId,
|
|
208
203
|
jsonToZodSchemaMapper,
|
|
209
|
-
|
|
204
|
+
normalizeSingleOrArray,
|
|
205
|
+
toGteLteFilter,
|
|
206
|
+
zSingleOrArray,
|
|
207
|
+
zodMinMaxFilter,
|
|
208
|
+
zodToExample,
|
|
209
|
+
zodToTypeString
|
|
210
210
|
};
|
|
@@ -67,7 +67,11 @@
|
|
|
67
67
|
type RawHandlerReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
|
|
68
68
|
/** Get union of all raw return types from a cases object's values */
|
|
69
69
|
type RawCasesReturnType<Cases> = {
|
|
70
|
-
[K in keyof Cases]:
|
|
70
|
+
[K in keyof Cases]: RawHandlerReturnType<Cases[K]>;
|
|
71
|
+
}[keyof Cases];
|
|
72
|
+
/** Only required, defined handlers guarantee that a case is handled. */
|
|
73
|
+
type HandledCaseKeys<Cases> = {
|
|
74
|
+
[K in keyof Cases]-?: {} extends Pick<Cases, K> ? never : undefined extends Cases[K] ? never : K;
|
|
71
75
|
}[keyof Cases];
|
|
72
76
|
/** Cases for string input - all cases required */
|
|
73
77
|
export type StringSwitchCases<T extends string, R> = {
|
|
@@ -90,13 +94,13 @@ export declare function typedSwitch<T extends string, Cases extends {
|
|
|
90
94
|
}>(value: T, cases: Cases): RawCasesReturnType<Cases>;
|
|
91
95
|
export declare function typedSwitch<T extends string, Cases extends {
|
|
92
96
|
[K in T]?: (value: K) => unknown;
|
|
93
|
-
}, DefaultCase extends (value: T) => unknown>(value: T, cases: Cases, defaultCase: DefaultCase): RawCasesReturnType<Cases> | RawHandlerReturnType<DefaultCase>;
|
|
94
|
-
export declare function typedSwitch<T extends
|
|
97
|
+
}, DefaultCase extends (value: Exclude<T, HandledCaseKeys<Cases>>) => unknown>(value: T, cases: Cases, defaultCase: DefaultCase): RawCasesReturnType<Cases> | RawHandlerReturnType<DefaultCase>;
|
|
98
|
+
export declare function typedSwitch<T extends object, K extends keyof T, TKey extends T[K] & string, Cases extends {
|
|
95
99
|
[V in TKey]: (value: Extract<T, Record<K, V>>) => unknown;
|
|
96
100
|
}>(value: T, key: K, cases: Cases): RawCasesReturnType<Cases>;
|
|
97
|
-
export declare function typedSwitch<T extends
|
|
101
|
+
export declare function typedSwitch<T extends object, K extends keyof T, TKey extends T[K] & string, Cases extends {
|
|
98
102
|
[V in TKey]?: (value: Extract<T, Record<K, V>>) => unknown;
|
|
99
|
-
}, DefaultCase extends (value: T) => unknown>(value: T, key: K, cases: Cases, defaultCase: DefaultCase): RawCasesReturnType<Cases> | RawHandlerReturnType<DefaultCase>;
|
|
103
|
+
}, DefaultCase extends (value: Exclude<T, Record<K, HandledCaseKeys<Cases> & string>>) => unknown>(value: T, key: K, cases: Cases, defaultCase: DefaultCase): RawCasesReturnType<Cases> | RawHandlerReturnType<DefaultCase>;
|
|
100
104
|
export declare function typedSwitch<Constraint, T extends string = string>(value: T, cases: {
|
|
101
105
|
[K in T]: (value: K) => Constraint;
|
|
102
106
|
}): Constraint;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hanzio",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A lightweight set of utility functions for TypeScript",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "./dist/src/index.js",
|
|
@@ -46,11 +46,17 @@
|
|
|
46
46
|
"bun": "./src/secrets/vite.ts",
|
|
47
47
|
"import": "./dist/src/secrets/vite.js",
|
|
48
48
|
"default": "./dist/src/secrets/vite.js"
|
|
49
|
+
},
|
|
50
|
+
"./secrets/worker": {
|
|
51
|
+
"types": "./dist/secrets/worker.d.ts",
|
|
52
|
+
"bun": "./src/secrets/worker.ts",
|
|
53
|
+
"import": "./dist/src/secrets/worker.js",
|
|
54
|
+
"default": "./dist/src/secrets/worker.js"
|
|
49
55
|
}
|
|
50
56
|
},
|
|
51
57
|
"scripts": {
|
|
52
58
|
"build": "bun run build:js && bun run build:types",
|
|
53
|
-
"build:js": "bun build ./src/index.ts ./src/zod/index.ts ./src/api-wrapper/index.ts ./src/jwt/index.ts ./src/sitemap/index.ts ./src/p-queue/index.ts ./src/secrets/index.ts ./src/secrets/infisical.ts ./src/secrets/vite.ts --outdir ./dist --target node --packages external",
|
|
59
|
+
"build:js": "bun build ./src/index.ts ./src/zod/index.ts ./src/api-wrapper/index.ts ./src/jwt/index.ts ./src/sitemap/index.ts ./src/p-queue/index.ts ./src/secrets/index.ts ./src/secrets/infisical.ts ./src/secrets/loaders.ts ./src/secrets/vite.ts ./src/secrets/worker.ts --outdir ./dist --target node --packages external",
|
|
54
60
|
"build:types": "tsgo -p tsconfig.build.json",
|
|
55
61
|
"prepublishOnly": "bun run build",
|
|
56
62
|
"publish:patch": "npm version patch && npm publish",
|
|
@@ -60,14 +66,17 @@
|
|
|
60
66
|
"lint": "bun run typecheck && bun biome check",
|
|
61
67
|
"lint:write": "bun run typecheck && bun biome check --write",
|
|
62
68
|
"typecheck": "tsgo --noEmit",
|
|
63
|
-
"prepare": "husky"
|
|
69
|
+
"prepare": "husky",
|
|
70
|
+
"test:secrets:runtime": "bun run build && node scripts/verify-secrets.mjs"
|
|
64
71
|
},
|
|
65
72
|
"devDependencies": {
|
|
66
73
|
"@biomejs/biome": "^2.4.6",
|
|
67
74
|
"@types/bun": "latest",
|
|
68
75
|
"@typescript/native-preview": "^7.0.0-dev.20260306.1",
|
|
69
76
|
"husky": "^9.1.7",
|
|
77
|
+
"miniflare": "^4.20260730.0",
|
|
70
78
|
"typescript": "^5.7.0",
|
|
79
|
+
"vite": "^8.2.2",
|
|
71
80
|
"zod": "^4.3.6"
|
|
72
81
|
},
|
|
73
82
|
"peerDependencies": {
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { ActionCache, DefineAction } from './types'
|
|
2
|
+
|
|
3
|
+
type CacheEntry = {
|
|
4
|
+
value?: unknown
|
|
5
|
+
hasValue: boolean
|
|
6
|
+
expiresAt?: number
|
|
7
|
+
inflight?: Promise<unknown>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function createActionCache(): ActionCache {
|
|
11
|
+
const store = new Map<string, CacheEntry>()
|
|
12
|
+
|
|
13
|
+
const isExpired = (entry: CacheEntry) =>
|
|
14
|
+
entry.expiresAt !== undefined && entry.expiresAt <= Date.now()
|
|
15
|
+
|
|
16
|
+
const cache = (async <T>(
|
|
17
|
+
key: string,
|
|
18
|
+
fn: () => Promise<T> | T,
|
|
19
|
+
options?: { ttlMs?: number }
|
|
20
|
+
): Promise<T> => {
|
|
21
|
+
const existing = store.get(key)
|
|
22
|
+
if (existing && !isExpired(existing)) {
|
|
23
|
+
if (existing.inflight) return existing.inflight as Promise<T>
|
|
24
|
+
if (existing.hasValue) return existing.value as T
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const promise = (async () => fn())()
|
|
28
|
+
const entry: CacheEntry = {
|
|
29
|
+
hasValue: false,
|
|
30
|
+
inflight: promise,
|
|
31
|
+
expiresAt:
|
|
32
|
+
options?.ttlMs !== undefined ? Date.now() + options.ttlMs : undefined
|
|
33
|
+
}
|
|
34
|
+
store.set(key, entry)
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const value = await promise
|
|
38
|
+
entry.value = value
|
|
39
|
+
entry.hasValue = true
|
|
40
|
+
entry.inflight = undefined
|
|
41
|
+
return value
|
|
42
|
+
} catch (error) {
|
|
43
|
+
store.delete(key)
|
|
44
|
+
throw error
|
|
45
|
+
}
|
|
46
|
+
}) as ActionCache
|
|
47
|
+
|
|
48
|
+
cache.get = <T>(key: string): T | undefined => {
|
|
49
|
+
const entry = store.get(key)
|
|
50
|
+
if (!entry || !entry.hasValue) return undefined
|
|
51
|
+
if (isExpired(entry)) {
|
|
52
|
+
store.delete(key)
|
|
53
|
+
return undefined
|
|
54
|
+
}
|
|
55
|
+
return entry.value as T
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
cache.set = <T>(
|
|
59
|
+
key: string,
|
|
60
|
+
value: T,
|
|
61
|
+
options?: { ttlMs?: number }
|
|
62
|
+
): void => {
|
|
63
|
+
store.set(key, {
|
|
64
|
+
value,
|
|
65
|
+
hasValue: true,
|
|
66
|
+
expiresAt:
|
|
67
|
+
options?.ttlMs !== undefined ? Date.now() + options.ttlMs : undefined
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
cache.invalidate = (key: string): void => {
|
|
72
|
+
store.delete(key)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
cache.clear = (): void => {
|
|
76
|
+
store.clear()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return cache
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function makeDefineAction<TApi>(): DefineAction<TApi> {
|
|
83
|
+
function defineAction(...args: unknown[]): unknown {
|
|
84
|
+
if (args.length === 0) {
|
|
85
|
+
return (def: { handler: unknown }) => ({
|
|
86
|
+
...def,
|
|
87
|
+
noRuntimeInput: false as const
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
const def = args[0] as { handler: unknown }
|
|
91
|
+
return {
|
|
92
|
+
...def,
|
|
93
|
+
noRuntimeInput: true as const
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return defineAction as DefineAction<TApi>
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// biome-ignore lint/suspicious/noExplicitAny: top-level form keeps ctx.api as any
|
|
100
|
+
export const defineAction: DefineAction<any> = makeDefineAction<any>()
|