Skip to content

Commit 02757e2

Browse files
committed
fix: broken primitive input schema
1 parent 8e16f7e commit 02757e2

File tree

3 files changed

+236
-98
lines changed

3 files changed

+236
-98
lines changed

src-v4/ss-zod.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ console.log("Test 1b:", proc1("Bob", 30));
2323
// Test 1b: Tuple with default + optional
2424
const proc1b = zagora()
2525
.input(z.tuple([z.string(), z.number().default(42), z.boolean().optional()]))
26-
.handler((options, name, age, verified) => ({
26+
.handler((_options, name, age, verified) => ({
2727
// passing!
2828
// name: string
2929
// age: number - must be, because it's defaulted to 42

src-v4/sscore-bkp.ts

Lines changed: 171 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import type { StandardSchemaV1 } from "@standard-schema/spec";
22
import { ZagoraError } from "../src/error";
3-
import { createResult } from "../src/utils";
43

54
export type Schema<I, O = I> = StandardSchemaV1<I, O>;
65

@@ -107,6 +106,17 @@ type SpreadTuple<T extends readonly any[], R> = T extends readonly [infer A]
107106
| ((arg1: A) => R)
108107
: (...args: T) => R;
109108

109+
export type ZagoraResult<
110+
TOutput,
111+
TErrors extends Record<string, any> | undefined = undefined,
112+
> = {
113+
data: TOutput;
114+
error: TErrors extends Record<string, any>
115+
? TErrors[keyof TErrors] | ZagoraError | null
116+
: ZagoraError | null;
117+
isTypedError: boolean;
118+
};
119+
110120
export function handleTupleDefaults(
111121
schema: StandardSchemaV1,
112122
rawArgs: unknown[],
@@ -159,7 +169,7 @@ function deepMerge(target: any, source: any): any {
159169
if (target == null || typeof target !== "object") return source;
160170
const result = Array.isArray(target) ? [...target] : { ...target };
161171
for (const key in source) {
162-
if (Object.hasOwn(source, key)) {
172+
if (key in source) {
163173
if (typeof source[key] === "object" && source[key] !== null) {
164174
result[key] = deepMerge(target[key], source[key]);
165175
} else {
@@ -170,17 +180,28 @@ function deepMerge(target: any, source: any): any {
170180
return result;
171181
}
172182

183+
// export const isTypedError = (val: any) => {
184+
// return Boolean(
185+
// val !== null &&
186+
// typeof val === "object" &&
187+
// "error" in val &&
188+
// "isTypedError" in val &&
189+
// val.isTypedError === true &&
190+
// val.error !== null &&
191+
// typeof val.error === "object" &&
192+
// !(val.error instanceof Error) &&
193+
// "type" in val.error,
194+
// );
195+
// };
196+
173197
function createErrorHelpers(errorMap: any): Record<string, (data: any) => any> {
174198
const helpers: any = {};
175199
for (const key in errorMap) {
176200
const schema = errorMap[key];
177201
if (!schema) continue;
202+
178203
helpers[key] = (data: any) => {
179-
const result = schema["~standard"].validate(data) as any;
180-
if (result.issues) {
181-
throw result.issues;
182-
}
183-
return { type: key, ...result.value };
204+
return { type: key, ...data };
184205
};
185206
}
186207
return helpers;
@@ -191,8 +212,8 @@ function createErrorHelpers(errorMap: any): Record<string, (data: any) => any> {
191212
// ============================================================================
192213

193214
export interface BuilderDef<
194-
TIsHandlerAsync,
195-
THandlerFn,
215+
// TIsHandlerAsync,
216+
// THandlerFn,
196217
TContext,
197218
TInputSchema extends AnySchema | undefined,
198219
TOutputSchema extends AnySchema | undefined,
@@ -210,15 +231,29 @@ export interface BuilderDef<
210231

211232
export type IsPromise<T> = T extends Promise<any> ? true : false;
212233

213-
export type ErrorHelpers<T extends Record<string, StandardSchemaV1>> = {
214-
[K in keyof T]: (data: Omit<InferSchemaInput<T[K]>, "type">) => never;
215-
};
234+
export type ErrorHelpers<
235+
TErrorsMap extends Record<string, StandardSchemaV1> | undefined,
236+
> = TErrorsMap extends Record<string, StandardSchemaV1>
237+
? {
238+
[K in keyof TErrorsMap]: (
239+
data: Prettify<
240+
Omit<StandardSchemaV1.InferInput<TErrorsMap[K]>, "type">
241+
>,
242+
) => never;
243+
}
244+
: never;
245+
246+
export type ErrorsMapResolved<
247+
TErrorsMap extends Record<string, StandardSchemaV1> | undefined,
248+
> = TErrorsMap extends Record<string, StandardSchemaV1>
249+
? { [K in keyof TErrorsMap]: StandardSchemaV1.InferInput<TErrorsMap[K]> }
250+
: undefined;
216251

217252
export interface ProcedureOptions<
218253
TContext,
219254
TErrorsMap extends Record<string, StandardSchemaV1> | undefined,
220255
> {
221-
context: TContext;
256+
context: TContext | undefined;
222257
errors: TErrorsMap extends Record<string, StandardSchemaV1>
223258
? ErrorHelpers<TErrorsMap>
224259
: undefined;
@@ -234,14 +269,7 @@ class Builder<
234269
> {
235270
constructor(
236271
private def: Partial<
237-
BuilderDef<
238-
TIsHandlerAsync,
239-
THandlerFn,
240-
TContext,
241-
TInputSchema,
242-
TOutputSchema,
243-
TErrorsMap
244-
>
272+
BuilderDef<TContext, TInputSchema, TOutputSchema, TErrorsMap>
245273
> = {},
246274
) {}
247275

@@ -293,7 +321,7 @@ class Builder<
293321
}) as any;
294322
}
295323

296-
errors<TErrors extends TErrorsMap>(
324+
errors<TErrors extends Record<string, StandardSchemaV1>>(
297325
errorsMap: TErrors,
298326
): Builder<
299327
TIsHandlerAsync,
@@ -328,25 +356,39 @@ class Builder<
328356
});
329357
}
330358

331-
callable<TContext>(
332-
context?: TContext,
333-
): InferSchemaInput<TInputSchema> extends readonly any[]
334-
? SpreadTuple<InferSchemaInput<TInputSchema>, any>
335-
: (arg: InferSchemaInput<TInputSchema>) => any {
359+
callable<
360+
TContext,
361+
TSpread extends SpreadTuple<
362+
InferSchemaInput<TInputSchema>,
363+
ZagoraResult<
364+
InferSchemaOutput<TOutputSchema>,
365+
ErrorsMapResolved<TErrorsMap>
366+
>
367+
>,
368+
TProcReturn extends InferSchemaInput<TInputSchema> extends readonly any[]
369+
? TSpread
370+
: (
371+
arg: InferSchemaOutput<TInputSchema>,
372+
) => ZagoraResult<
373+
InferSchemaOutput<TOutputSchema>,
374+
ErrorsMapResolved<TErrorsMap>
375+
>,
376+
>(context?: TContext): TProcReturn {
336377
const { initialContext, errorsMap } = this.def;
337378
const handlerFn = this.def.handler as any; // todo: fix, return internal error if not defined (thru createResult)
338379
const inputSchema = this.def.inputSchema as TInputSchema;
339380
const outputSchema = this.def.outputSchema as TOutputSchema;
381+
const isAsync = isAsyncFunction(handlerFn);
340382

341383
const mergedContext = context
342384
? deepMerge(initialContext, context)
343385
: initialContext;
344386

345387
const errors = errorsMap ? createErrorHelpers(errorsMap as any) : undefined;
346388
const options = {
347-
errors,
389+
errors: errors as ErrorHelpers<TErrorsMap>,
348390
context: mergedContext,
349-
} as ProcedureOptions<TContext & typeof mergedContext, TErrorsMap>;
391+
};
350392

351393
const wrapped = (...args: unknown[]) => {
352394
const schemaAny = inputSchema as any;
@@ -365,27 +407,32 @@ class Builder<
365407
? handleTupleDefaults(inputSchema, parsed)
366408
: [parsed];
367409

368-
const handlerResult = (
369-
handlerFn as (
370-
opts: ProcedureOptions<TContext, TErrorsMap>,
371-
...args: any[]
372-
) => any
373-
)(options, ...handlerArgs);
374-
375-
if (outputSchema) {
376-
if (handlerResult instanceof Promise) {
377-
return handlerResult.then((r) =>
378-
validateOutput(outputSchema, r, "Output validation failed"),
379-
);
380-
}
381-
return validateOutput(
382-
outputSchema,
383-
handlerResult,
384-
"Output validation failed",
410+
let handlerResult;
411+
try {
412+
handlerResult = handlerFn(
413+
options as any,
414+
...(handlerArgs as Parameters<TSpread>),
385415
);
416+
} catch (err) {
417+
return validateError(errorsMap, err, isAsync);
386418
}
387-
return createResult(handlerResult, null, false);
419+
420+
const processResult = (res: any) => {
421+
if (outputSchema) {
422+
return validateOutput(outputSchema, res, "Output validation failed");
423+
}
424+
return createResult(res, null, false);
425+
};
426+
427+
if (handlerResult instanceof Promise) {
428+
return handlerResult.then(processResult).catch((err) => {
429+
return validateError(errorsMap, err, isAsync);
430+
});
431+
}
432+
433+
return processResult(handlerResult);
388434
};
435+
389436
return wrapped as any;
390437
}
391438
}
@@ -413,6 +460,82 @@ export function validateOutput(schema: any, data: any, validationMsg: string) {
413460
}
414461
}
415462

416-
export function builder() {
463+
export function validateError(errorsMap: any, error: any, isAsync: boolean) {
464+
if (!errorsMap) {
465+
return createResult(
466+
null,
467+
error instanceof ZagoraError
468+
? error
469+
: new ZagoraError(
470+
`${isAsync ? "Async" : "Sync"} handler threw unknown error`,
471+
{ cause: error },
472+
),
473+
false,
474+
);
475+
}
476+
477+
const errorType = error?.type;
478+
console.log("errorsMap", { error });
479+
if (errorType in errorsMap) {
480+
const schema = errorsMap[errorType] as any;
481+
const result = schema["~standard"].validate(error);
482+
const processError = (res: any) =>
483+
res.issues
484+
? createResult(
485+
null,
486+
ZagoraError.fromIssues(
487+
res.issues,
488+
`Error data validation failed for ${errorType}`,
489+
),
490+
false,
491+
)
492+
: createResult(null, { type: errorType, ...res.value }, true);
493+
494+
if (result instanceof Promise) {
495+
return result.then(processError);
496+
}
497+
return processError(result);
498+
}
499+
500+
return createResult(
501+
null,
502+
new ZagoraError(`Typed error with key ${errorType} not found in errorsMap`),
503+
false,
504+
);
505+
}
506+
507+
// note: basic, but coverting a lot, if not just use `is-async-function` in future
508+
export function isAsyncFunction(fn: any) {
509+
if (typeof fn !== "function") {
510+
return false;
511+
}
512+
513+
const str = Function.prototype.toString.call(fn);
514+
515+
if (str.startsWith("async")) {
516+
return true;
517+
}
518+
519+
const obj = Object.prototype.toString.call(fn);
520+
521+
if (obj === "[object AsyncFunction]") {
522+
return true;
523+
}
524+
525+
try {
526+
const result = fn();
527+
return result instanceof Promise;
528+
} catch (_err: unknown) {
529+
return false;
530+
}
531+
}
532+
533+
export function createResult(data: any, error: any, isTypedError: boolean) {
534+
const res = { data, error, isTypedError };
535+
536+
return res;
537+
}
538+
539+
export function zagora() {
417540
return new Builder();
418541
}

0 commit comments

Comments
 (0)