Files
headroom/frontend/node_modules/sveltekit-superforms/dist/adapters/zod4.js
Santhosh Janardhanan de2d83092e feat: Reinitialize frontend with SvelteKit and TypeScript
- Delete old Vite+Svelte frontend
- Initialize new SvelteKit project with TypeScript
- Configure Tailwind CSS v4 + DaisyUI
- Implement JWT authentication with auto-refresh
- Create login page with form validation (Zod)
- Add protected route guards
- Update Docker configuration for single-stage build
- Add E2E tests with Playwright (6/11 passing)
- Fix Svelte 5 reactivity with $state() runes

Known issues:
- 5 E2E tests failing (timing/async issues)
- Token refresh implementation needs debugging
- Validation error display timing
2026-02-17 16:19:59 -05:00

145 lines
6.4 KiB
JavaScript

import { safeParseAsync, toJSONSchema, config } from 'zod/v4/core';
import { createAdapter } from './adapters.js';
import { memoize } from '../memoize.js';
const defaultJSONSchemaOptions = {
unrepresentable: 'any',
override: (ctx) => {
const def = ctx.zodSchema._zod.def;
if (def.type === 'date') {
ctx.jsonSchema.type = 'integer';
ctx.jsonSchema.format = 'unix-time';
}
else if (def.type === 'bigint') {
ctx.jsonSchema.type = 'string';
ctx.jsonSchema.format = 'bigint';
}
else if (def.type === 'pipe') {
// Handle z.stringbool() - it's a pipe from string->transform->boolean
// Colin Hacks explained: stringbool is just string -> transform -> boolean
// When io:'input', we see the string schema; when io:'output', we see boolean
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const pipeDef = def;
const inSchema = pipeDef.in;
const outSchema = pipeDef.out;
// Check if it's: string -> (transform or pipe) -> boolean
if (inSchema?._zod?.def.type === 'string') {
// Traverse through the output side (right) to find if it ends in boolean
let currentSchema = outSchema;
let isStringBool = false;
// Traverse through transforms and pipes to find boolean
while (currentSchema?._zod?.def) {
const currentDef = currentSchema._zod.def;
if (currentDef.type === 'boolean') {
isStringBool = true;
break;
}
else if (currentDef.type === 'transform') {
// Transform doesn't have a nested schema, but we can't traverse further
// Check if the transform is inside another pipe
break;
}
else if (currentDef.type === 'pipe') {
// Continue traversing the pipe
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const nestedPipeDef = currentDef;
currentSchema = nestedPipeDef.out;
}
else {
break;
}
}
// Also check if outSchema directly is boolean
if (!isStringBool && outSchema?._zod?.def.type === 'boolean') {
isStringBool = true;
}
if (isStringBool) {
// Mark as stringbool so FormData parser knows to handle it as string
ctx.jsonSchema.type = 'string';
ctx.jsonSchema.format = 'stringbool';
}
}
}
else if (def.type === 'set') {
// Handle z.set() - convert to array with uniqueItems
ctx.jsonSchema.type = 'array';
ctx.jsonSchema.uniqueItems = true;
// If there's a default value, convert Set to Array
if ('default' in ctx.jsonSchema && ctx.jsonSchema.default instanceof Set) {
ctx.jsonSchema.default = Array.from(ctx.jsonSchema.default);
}
}
else if (def.type === 'map') {
// Handle z.map() - convert to array of [key, value] tuples
ctx.jsonSchema.type = 'array';
ctx.jsonSchema.format = 'map';
// If there's a default value, convert Map to Array
if ('default' in ctx.jsonSchema && ctx.jsonSchema.default instanceof Map) {
ctx.jsonSchema.default = Array.from(ctx.jsonSchema.default);
}
}
else if (def.type === 'default') {
// Handle z.default() wrapping unrepresentable types
// The default value was already serialized by Zod, which converts Set/Map to {}
// We need to get the original value and convert it properly
const innerDef = def.innerType._zod.def;
if (innerDef.type === 'set' && def.defaultValue instanceof Set) {
// Set the proper schema type for sets
ctx.jsonSchema.type = 'array';
ctx.jsonSchema.uniqueItems = true;
// Convert the default value from Set to Array
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ctx.jsonSchema.default = Array.from(def.defaultValue);
}
else if (innerDef.type === 'map' && def.defaultValue instanceof Map) {
// Set the proper schema type for maps
ctx.jsonSchema.type = 'array';
ctx.jsonSchema.format = 'map';
// Convert the default value from Map to Array of tuples
// eslint-disable-next-line @typescript-eslint/no-explicit-any
ctx.jsonSchema.default = Array.from(def.defaultValue);
}
}
}
};
/* @__NO_SIDE_EFFECTS__ */
export const zodToJSONSchema = (schema, options) => {
return toJSONSchema(schema, { ...defaultJSONSchemaOptions, ...options });
};
async function validate(schema, data, error) {
// Use Zod's global config error map if none provided to preserve custom messages.
// Prioritize customError over localeError when both are set.
if (error === undefined) {
const zConfig = config();
error = zConfig.customError ?? zConfig.localeError;
}
const result = await safeParseAsync(schema, data, { error });
if (result.success) {
return {
data: result.data,
success: true
};
}
return {
issues: result.error.issues.map(({ message, path }) => ({ message, path })),
success: false
};
}
function _zod4(schema, options) {
return createAdapter({
superFormValidationLibrary: 'zod4',
validate: async (data) => {
return validate(schema, data, options?.error);
},
jsonSchema: options?.jsonSchema ?? zodToJSONSchema(schema, options?.config),
defaults: options?.defaults
});
}
function _zod4Client(schema, options) {
return {
superFormValidationLibrary: 'zod4',
validate: async (data) => validate(schema, data, options?.error)
};
}
export const zod = /* @__PURE__ */ memoize(_zod4);
export const zodClient = /* @__PURE__ */ memoize(_zod4Client);