11import type { StandardSchemaV1 } from "@standard-schema/spec" ;
22import { ZagoraError } from "../src/error" ;
3- import { createResult } from "../src/utils" ;
43
54export 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+
110120export 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+
173197function 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
193214export 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
211232export 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
217252export 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