Skip to content

Commit d21aad0

Browse files
committed
feat: prep new-src, better build & typecheck
Signed-off-by: tunnckoCore <5038030+tunnckoCore@users.noreply.github.com>
1 parent 5eb028f commit d21aad0

File tree

13 files changed

+1265
-49
lines changed

13 files changed

+1265
-49
lines changed

bun.lock

Lines changed: 50 additions & 35 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

new-src/errors.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import type {
2+
AnySchema,
3+
InferSchemaInput,
4+
InferSchemaOutput,
5+
Prettify,
6+
SchemaIssue,
7+
} from "./types";
8+
9+
export type ValidationError<ErrorKindNames = never> = Prettify<
10+
Readonly<{
11+
kind: "VALIDATION_ERROR";
12+
message: string;
13+
issues: readonly SchemaIssue[];
14+
key?: ErrorKindNames;
15+
}>
16+
>;
17+
export type InternalError = Prettify<ReturnType<typeof createInternalError>>;
18+
export type DefinedError<T> = Prettify<{ readonly kind: string } & T>;
19+
export type ZagoraError<T> =
20+
| ValidationError
21+
| InternalError
22+
| DefinedError<any>;
23+
24+
export function isValidationError(val: any): val is ValidationError {
25+
return Boolean(
26+
val &&
27+
val.kind === "VALIDATION_ERROR" &&
28+
val.issues &&
29+
Array.isArray(val.issues) &&
30+
val.message &&
31+
typeof val.message === "string",
32+
);
33+
}
34+
export function isInternalError(val: any): val is InternalError {
35+
return Boolean(
36+
val &&
37+
val.kind === "UNKNOWN_ERROR" &&
38+
val.message &&
39+
typeof val.message === "string" &&
40+
val.cause,
41+
);
42+
}
43+
export function isDefinedError<T>(val: any): val is DefinedError<T> {
44+
return Boolean(
45+
val &&
46+
val.kind &&
47+
typeof val.kind === "string" &&
48+
val.kind.length > 0 &&
49+
val.kind === val.kind.toUpperCase(),
50+
);
51+
}
52+
53+
export function isZagoraError<T>(val: any): val is ZagoraError<T> {
54+
return isValidationError(val) || isInternalError(val) || isDefinedError(val);
55+
}
56+
57+
export function createValidationError<ErrorKindNames = never>(
58+
mode: "input" | "output" | "error data",
59+
issues: SchemaIssue[],
60+
key?: ErrorKindNames,
61+
) {
62+
const modeName = mode.charAt(0).toUpperCase() + mode.slice(1);
63+
const str = key ? ` for ${(key as string).toUpperCase()}` : "";
64+
const issuesMsg = issues
65+
// strip "input" cuz it can be confusing when we are schema validating output and errors too
66+
.map((issue) => {
67+
const key = issue.path?.join(".");
68+
const message = issue.message.replace("Invalid input: ", "");
69+
return `${key} => ${message}`;
70+
})
71+
.join("; ");
72+
73+
return {
74+
kind: "VALIDATION_ERROR" as const,
75+
message: `${modeName} validation failed${str}: ${issuesMsg}`,
76+
key,
77+
issues: issues as SchemaIssue[],
78+
} as const;
79+
}
80+
81+
export function createInternalError(msg: string, cause?: Error) {
82+
return {
83+
kind: "UNKNOWN_ERROR" as const,
84+
message: msg,
85+
cause,
86+
stack: cause?.stack,
87+
} as const;
88+
}
89+
90+
export function createErrorHelpers(
91+
errorMap: any,
92+
): Record<string, (data: any) => any> {
93+
const helpers: any = {};
94+
for (const key in errorMap) {
95+
const schema = errorMap[key];
96+
if (!schema) continue;
97+
98+
helpers[key] = (data: any) => {
99+
return { kind: key, ...data };
100+
};
101+
}
102+
return helpers;
103+
}
104+
105+
export type ErrorHelpers<
106+
TErrorsMap extends Record<string, AnySchema> | undefined,
107+
> = TErrorsMap extends Record<string, AnySchema>
108+
? {
109+
[K in keyof TErrorsMap]: (
110+
data: Prettify<Omit<InferSchemaInput<TErrorsMap[K]>, "kind">>,
111+
) => never;
112+
}
113+
: never;
114+
115+
export type ErrorsMapResolved<
116+
TErrorsMap extends Record<string, AnySchema> | undefined,
117+
> = TErrorsMap extends Record<string, AnySchema>
118+
? Readonly<{ [K in keyof TErrorsMap]: InferSchemaOutput<TErrorsMap[K]> }>
119+
: undefined;
120+
121+
export type ResolveErrorKindNames<TErrorsMap> = TErrorsMap extends Record<
122+
string,
123+
AnySchema
124+
>
125+
? keyof TErrorsMap
126+
: undefined;

new-src/index.ts

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
import {
2+
createErrorHelpers,
3+
type ErrorsMapResolved,
4+
type ResolveErrorKindNames,
5+
} from "./errors";
6+
7+
import type {
8+
AnySchema,
9+
InferSchemaInput,
10+
InferSchemaOutput,
11+
IsPromise,
12+
Prettify,
13+
ProcedureOptions,
14+
SpreadTuple,
15+
UppercaseKeys,
16+
ZagoraDef,
17+
ZagoraResult,
18+
} from "./types";
19+
20+
import { createProcedure, deepMerge } from "./utils";
21+
22+
export * as errors from "./errors";
23+
export * as types from "./types";
24+
export * as utils from "./utils";
25+
26+
export function zagora() {
27+
return new Zagora();
28+
}
29+
30+
export class Zagora<
31+
TIsHandlerAsync,
32+
THandlerFn,
33+
TContext extends any | undefined = undefined,
34+
TInputSchema extends AnySchema = never,
35+
TOutputSchema extends AnySchema = never,
36+
TErrorsMap extends Record<string, AnySchema> | undefined = undefined,
37+
> {
38+
constructor(
39+
private def: Partial<
40+
ZagoraDef<TContext, TInputSchema, TOutputSchema, TErrorsMap>
41+
> = {},
42+
) {}
43+
44+
input<TInput extends AnySchema>(
45+
inputSchema: TInput,
46+
): Zagora<
47+
TIsHandlerAsync,
48+
THandlerFn,
49+
TContext,
50+
TInput,
51+
TOutputSchema,
52+
TErrorsMap
53+
> {
54+
return new Zagora({
55+
...this.def,
56+
inputSchema: inputSchema,
57+
});
58+
}
59+
60+
output<TOutput extends AnySchema>(
61+
outputSchema: TOutput,
62+
): Zagora<
63+
TIsHandlerAsync,
64+
THandlerFn,
65+
TContext,
66+
TInputSchema,
67+
TOutput,
68+
TErrorsMap
69+
> {
70+
return new Zagora({
71+
...this.def,
72+
outputSchema: outputSchema,
73+
});
74+
}
75+
76+
context<TNewContext>(
77+
initialContext?: TNewContext,
78+
): Zagora<
79+
TIsHandlerAsync,
80+
THandlerFn,
81+
TNewContext,
82+
TInputSchema,
83+
TOutputSchema,
84+
TErrorsMap
85+
> {
86+
return new Zagora({
87+
...this.def,
88+
initialContext,
89+
}) as any;
90+
}
91+
92+
errors<TErrors extends Record<string, AnySchema>>(
93+
errorsMap: TErrors & UppercaseKeys<TErrors>,
94+
): Zagora<
95+
TIsHandlerAsync,
96+
THandlerFn,
97+
TContext,
98+
TInputSchema,
99+
TOutputSchema,
100+
TErrors
101+
> {
102+
return new Zagora({
103+
...this.def,
104+
errorsMap,
105+
});
106+
}
107+
108+
handler<
109+
TFn extends InferSchemaOutput<TInputSchema> extends readonly any[]
110+
? SpreadTuple<
111+
[
112+
Prettify<ProcedureOptions<TContext, TErrorsMap>>,
113+
...InferSchemaOutput<TInputSchema>,
114+
],
115+
any
116+
>
117+
: (
118+
options: Prettify<ProcedureOptions<TContext, TErrorsMap>>,
119+
arg: InferSchemaOutput<TInputSchema>,
120+
) => any,
121+
TReturn = ReturnType<TFn>,
122+
TIsAsync extends boolean = IsPromise<TReturn>,
123+
>(
124+
fn: TFn,
125+
): Zagora<TIsAsync, TFn, TContext, TInputSchema, TOutputSchema, TErrorsMap> {
126+
return new Zagora({
127+
...this.def,
128+
handler: fn as any,
129+
});
130+
}
131+
132+
callable<
133+
TNewContext extends TContext,
134+
TKindNames extends ResolveErrorKindNames<TErrorsMap>,
135+
>(context?: TNewContext) {
136+
if (typeof this.def.handler !== "function") {
137+
this.def.handler = () => {};
138+
}
139+
140+
const { initialContext, errorsMap } = this.def;
141+
const handlerFn = this.def.handler;
142+
const inputSchema = this.def.inputSchema as TInputSchema;
143+
const outputSchema = this.def.outputSchema as TOutputSchema;
144+
145+
const mergedContext = context
146+
? deepMerge(initialContext, context)
147+
: initialContext;
148+
149+
const errors = errorsMap ? createErrorHelpers(errorsMap as any) : undefined;
150+
const options = {
151+
errors: errors,
152+
context: mergedContext,
153+
} as Prettify<
154+
ProcedureOptions<Prettify<TNewContext & TContext>, TErrorsMap>
155+
>;
156+
157+
const procedure = createProcedure<TKindNames>({
158+
inputSchema,
159+
outputSchema,
160+
errorsMap,
161+
options,
162+
handlerFn,
163+
});
164+
165+
type TResolvedResult = Awaited<ReturnType<typeof procedure>>;
166+
167+
type TResult = TIsHandlerAsync extends true
168+
? Promise<
169+
ZagoraResult<
170+
InferSchemaOutput<TOutputSchema>,
171+
ErrorsMapResolved<TErrorsMap>,
172+
TResolvedResult
173+
>
174+
>
175+
: ZagoraResult<
176+
InferSchemaOutput<TOutputSchema>,
177+
ErrorsMapResolved<TErrorsMap>,
178+
TResolvedResult
179+
>;
180+
181+
return procedure as InferSchemaInput<TInputSchema> extends readonly any[]
182+
? SpreadTuple<InferSchemaInput<TInputSchema>, TResult>
183+
: (arg: InferSchemaOutput<TInputSchema>) => TResult;
184+
}
185+
}

0 commit comments

Comments
 (0)