sonamu 0.5.4 → 0.5.5
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/dist/api/code-converters.d.ts +8 -5
- package/dist/api/code-converters.d.ts.map +1 -1
- package/dist/api/code-converters.js +1 -1
- package/dist/api/code-converters.js.map +1 -1
- package/dist/api/context.d.ts +9 -2
- package/dist/api/context.d.ts.map +1 -1
- package/dist/api/sonamu.d.ts +1 -0
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +1 -1
- package/dist/api/sonamu.js.map +1 -1
- package/dist/types/types.d.ts +11 -2
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/types.js.map +1 -1
- package/package.json +4 -1
- package/src/api/code-converters.ts +41 -31
- package/src/api/context.ts +11 -2
- package/src/api/sonamu.ts +43 -2
- package/src/types/types.ts +18 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import type { $ZodLooseShape } from "zod/v4/core"
|
|
2
3
|
import {
|
|
3
4
|
ApiParam,
|
|
4
5
|
ApiParamType,
|
|
@@ -26,6 +27,17 @@ import {
|
|
|
26
27
|
isVirtualProp,
|
|
27
28
|
} from "../types/types";
|
|
28
29
|
import { ExtendedApi } from "./decorators";
|
|
30
|
+
import type { Literal } from "zod/v4/core/util";
|
|
31
|
+
|
|
32
|
+
// <any>를 자제하고, Zod에서 제약하는 기본적인 Generic Type Parameter를 사용함.
|
|
33
|
+
type AnyZodRecord = z.ZodRecord<z.ZodString | z.ZodNumber | z.ZodSymbol, z.ZodType>;
|
|
34
|
+
type AnyZodObject = z.ZodObject<$ZodLooseShape>;
|
|
35
|
+
type AnyZodArray = z.ZodArray<z.ZodType>;
|
|
36
|
+
type AnyZodNullable = z.ZodNullable<z.ZodType>;
|
|
37
|
+
type AnyZodOptional = z.ZodOptional<z.ZodType>;
|
|
38
|
+
type AnyZodDefault = z.ZodDefault<z.ZodType>;
|
|
39
|
+
type AnyZodLiteral = z.ZodLiteral<Literal>;
|
|
40
|
+
type AnyZodUnion = z.ZodUnion<z.ZodType[]>;
|
|
29
41
|
|
|
30
42
|
/*
|
|
31
43
|
ExtendedApi 에서 ZodObject 리턴
|
|
@@ -33,7 +45,7 @@ import { ExtendedApi } from "./decorators";
|
|
|
33
45
|
export function getZodObjectFromApi(
|
|
34
46
|
api: ExtendedApi,
|
|
35
47
|
references: {
|
|
36
|
-
[id: string]:
|
|
48
|
+
[id: string]: AnyZodObject;
|
|
37
49
|
} = {}
|
|
38
50
|
) {
|
|
39
51
|
if (api.typeParameters?.length > 0) {
|
|
@@ -66,7 +78,7 @@ export function getZodObjectFromApi(
|
|
|
66
78
|
export function getZodObjectFromApiParams(
|
|
67
79
|
apiParams: ApiParam[],
|
|
68
80
|
references: {
|
|
69
|
-
[id: string]:
|
|
81
|
+
[id: string]: AnyZodObject;
|
|
70
82
|
} = {}
|
|
71
83
|
): z.ZodObject {
|
|
72
84
|
return z.object(
|
|
@@ -89,7 +101,7 @@ export function getZodObjectFromApiParams(
|
|
|
89
101
|
export function getZodTypeFromApiParamType(
|
|
90
102
|
paramType: ApiParamType,
|
|
91
103
|
references: {
|
|
92
|
-
[id: string]:
|
|
104
|
+
[id: string]: AnyZodObject;
|
|
93
105
|
}
|
|
94
106
|
): z.ZodType<unknown> {
|
|
95
107
|
switch (paramType) {
|
|
@@ -135,7 +147,7 @@ export function getZodTypeFromApiParamType(
|
|
|
135
147
|
}
|
|
136
148
|
const [obj, literalOrUnion] = refType.args!.map((arg) =>
|
|
137
149
|
getZodTypeFromApiParamType(arg, references)
|
|
138
|
-
) as [
|
|
150
|
+
) as [AnyZodObject, z.ZodUnion<any> | AnyZodLiteral];
|
|
139
151
|
let keys: string[] = [];
|
|
140
152
|
if (literalOrUnion instanceof z.ZodUnion) {
|
|
141
153
|
keys = literalOrUnion.def.options.map(
|
|
@@ -338,7 +350,7 @@ export function propToZodTypeDef(
|
|
|
338
350
|
}
|
|
339
351
|
|
|
340
352
|
// TODO(Haze, 251031): "template_literal", "file"에 대한 지원이 필요함.
|
|
341
|
-
export function zodTypeToZodCode(zt: z.ZodType
|
|
353
|
+
export function zodTypeToZodCode(zt: z.ZodType): string {
|
|
342
354
|
switch (zt.def.type) {
|
|
343
355
|
case "string":
|
|
344
356
|
return "z.string()";
|
|
@@ -361,15 +373,15 @@ export function zodTypeToZodCode(zt: z.ZodType<any>): string {
|
|
|
361
373
|
case "never":
|
|
362
374
|
return "z.never()";
|
|
363
375
|
case "nullable":
|
|
364
|
-
return zodTypeToZodCode((zt as
|
|
376
|
+
return zodTypeToZodCode((zt as AnyZodNullable).def.innerType) + ".nullable()";
|
|
365
377
|
case "default":
|
|
366
|
-
const zDefaultDef = (zt as
|
|
378
|
+
const zDefaultDef = (zt as AnyZodDefault).def;
|
|
367
379
|
return (
|
|
368
380
|
zodTypeToZodCode(zDefaultDef.innerType) +
|
|
369
|
-
`.default(${zDefaultDef.defaultValue
|
|
381
|
+
`.default(${zDefaultDef.defaultValue})`
|
|
370
382
|
);
|
|
371
383
|
case "record":
|
|
372
|
-
const zRecordDef = (zt as
|
|
384
|
+
const zRecordDef = (zt as AnyZodRecord).def;
|
|
373
385
|
return `z.record(${zodTypeToZodCode(zRecordDef.keyType)}, ${zodTypeToZodCode(
|
|
374
386
|
zRecordDef.valueType
|
|
375
387
|
)})`;
|
|
@@ -395,8 +407,8 @@ export function zodTypeToZodCode(zt: z.ZodType<any>): string {
|
|
|
395
407
|
}
|
|
396
408
|
return `z.literal([${items.join(", ")}])`;
|
|
397
409
|
case "union":
|
|
398
|
-
return `z.union([${(zt as
|
|
399
|
-
.map((option: z.ZodType
|
|
410
|
+
return `z.union([${(zt as AnyZodUnion).def.options
|
|
411
|
+
.map((option: z.ZodType) => zodTypeToZodCode(option))
|
|
400
412
|
.join(",")}])`;
|
|
401
413
|
case "enum":
|
|
402
414
|
// NOTE: z.enum(["A", "B"])도 z.enum({ A: "A", B: "B" })로 처리됨.
|
|
@@ -543,16 +555,16 @@ export function unwrapPromiseOnce(paramType: ApiParamType) {
|
|
|
543
555
|
}
|
|
544
556
|
|
|
545
557
|
// TODO(Haze, 251031): "template_literal", "file"에 대한 지원이 필요함.
|
|
546
|
-
export function serializeZodType(zt: z.
|
|
558
|
+
export function serializeZodType(zt: z.ZodType): any {
|
|
547
559
|
switch (zt.def.type) {
|
|
548
560
|
case "object":
|
|
549
561
|
return {
|
|
550
562
|
type: "object",
|
|
551
|
-
shape: Object.keys((zt as
|
|
563
|
+
shape: Object.keys((zt as AnyZodObject).shape).reduce(
|
|
552
564
|
(result, key) => {
|
|
553
565
|
return {
|
|
554
566
|
...result,
|
|
555
|
-
[key]: serializeZodType((zt as
|
|
567
|
+
[key]: serializeZodType((zt as AnyZodObject).shape[key]),
|
|
556
568
|
};
|
|
557
569
|
},
|
|
558
570
|
{}
|
|
@@ -561,7 +573,7 @@ export function serializeZodType(zt: z.ZodTypeAny): any {
|
|
|
561
573
|
case "array":
|
|
562
574
|
return {
|
|
563
575
|
type: "array",
|
|
564
|
-
element: serializeZodType((zt as
|
|
576
|
+
element: serializeZodType((zt as AnyZodArray).def.element),
|
|
565
577
|
};
|
|
566
578
|
case "enum":
|
|
567
579
|
return {
|
|
@@ -584,12 +596,12 @@ export function serializeZodType(zt: z.ZodTypeAny): any {
|
|
|
584
596
|
};
|
|
585
597
|
case "nullable":
|
|
586
598
|
return {
|
|
587
|
-
...serializeZodType((zt as
|
|
599
|
+
...serializeZodType((zt as AnyZodNullable).def.innerType),
|
|
588
600
|
nullable: true,
|
|
589
601
|
};
|
|
590
602
|
case "optional":
|
|
591
603
|
return {
|
|
592
|
-
...serializeZodType((zt as
|
|
604
|
+
...serializeZodType((zt as AnyZodOptional).def.innerType),
|
|
593
605
|
optional: true,
|
|
594
606
|
};
|
|
595
607
|
case "any":
|
|
@@ -599,13 +611,13 @@ export function serializeZodType(zt: z.ZodTypeAny): any {
|
|
|
599
611
|
case "record":
|
|
600
612
|
return {
|
|
601
613
|
type: "record",
|
|
602
|
-
keyType: serializeZodType((zt as
|
|
603
|
-
valueType: serializeZodType((zt as
|
|
614
|
+
keyType: serializeZodType((zt as AnyZodRecord).def.keyType),
|
|
615
|
+
valueType: serializeZodType((zt as AnyZodRecord).def.valueType),
|
|
604
616
|
};
|
|
605
617
|
case "union":
|
|
606
618
|
return {
|
|
607
619
|
type: "union",
|
|
608
|
-
options: (zt.def as
|
|
620
|
+
options: (zt.def as AnyZodUnion).options.map((option) =>
|
|
609
621
|
serializeZodType(option)
|
|
610
622
|
),
|
|
611
623
|
};
|
|
@@ -631,16 +643,14 @@ export function zodTypeToTsTypeDef(zt: z.ZodType): string {
|
|
|
631
643
|
case "never":
|
|
632
644
|
return zt.def.type;
|
|
633
645
|
case "nullable":
|
|
634
|
-
return zodTypeToTsTypeDef((zt as
|
|
646
|
+
return zodTypeToTsTypeDef((zt as AnyZodNullable).def.innerType) + " | null";
|
|
635
647
|
case "default":
|
|
636
|
-
return zodTypeToTsTypeDef((zt as
|
|
648
|
+
return zodTypeToTsTypeDef((zt as AnyZodDefault).def.innerType);
|
|
637
649
|
case "record":
|
|
638
|
-
const recordType = zt as
|
|
639
|
-
return `{ [ key: ${zodTypeToTsTypeDef(
|
|
640
|
-
recordType.def.keyType
|
|
641
|
-
)} ]: ${zodTypeToTsTypeDef(recordType.def.valueType)}}`;
|
|
650
|
+
const recordType = zt as AnyZodRecord;
|
|
651
|
+
return `{ [ key: ${zodTypeToTsTypeDef(recordType.def.keyType)} ]: ${zodTypeToTsTypeDef(recordType.def.valueType)}}`;
|
|
642
652
|
case "literal":
|
|
643
|
-
return Array.from((zt as z.ZodLiteral
|
|
653
|
+
return Array.from((zt as z.ZodLiteral).values).map(value => {
|
|
644
654
|
if (typeof value === "string") {
|
|
645
655
|
return `"${value}"`;
|
|
646
656
|
}
|
|
@@ -656,15 +666,15 @@ export function zodTypeToTsTypeDef(zt: z.ZodType): string {
|
|
|
656
666
|
return `${value}`;
|
|
657
667
|
}).join(" | ")
|
|
658
668
|
case "union":
|
|
659
|
-
return `${(zt as
|
|
669
|
+
return `${(zt as AnyZodUnion).options
|
|
660
670
|
.map((option) => zodTypeToTsTypeDef(option))
|
|
661
671
|
.join(" | ")}`;
|
|
662
672
|
case "enum":
|
|
663
673
|
return `${(zt as z.ZodEnum).options.map((val) => `"${val}"`).join(" | ")}`;
|
|
664
674
|
case "array":
|
|
665
|
-
return `${zodTypeToTsTypeDef((zt as
|
|
675
|
+
return `${zodTypeToTsTypeDef((zt as AnyZodArray).element)}[]`;
|
|
666
676
|
case "object":
|
|
667
|
-
const shape = (zt as
|
|
677
|
+
const shape = (zt as AnyZodObject).shape;
|
|
668
678
|
return [
|
|
669
679
|
"{",
|
|
670
680
|
...Object.keys(shape).map((key) => {
|
|
@@ -677,7 +687,7 @@ export function zodTypeToTsTypeDef(zt: z.ZodType): string {
|
|
|
677
687
|
"}",
|
|
678
688
|
].join("\n");
|
|
679
689
|
case "optional":
|
|
680
|
-
return zodTypeToTsTypeDef((zt as
|
|
690
|
+
return zodTypeToTsTypeDef((zt as AnyZodOptional).def.innerType) + " | undefined";
|
|
681
691
|
default:
|
|
682
692
|
throw new Error(`처리되지 않은 ZodType ${zt.def.type}`);
|
|
683
693
|
}
|
package/src/api/context.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { FastifyReply, FastifyRequest } from "fastify";
|
|
1
|
+
import type { FastifyReply, FastifyRequest, PassportUser } from "fastify";
|
|
2
2
|
import type { RouteGenericInterface } from "fastify/types/route";
|
|
3
3
|
import type {
|
|
4
4
|
Server,
|
|
@@ -24,7 +24,16 @@ export type Context = {
|
|
|
24
24
|
createSSE: <T extends ZodObject>(
|
|
25
25
|
events: T
|
|
26
26
|
) => ReturnType<typeof createSSEFactory<T>>;
|
|
27
|
-
} &
|
|
27
|
+
} & AuthContext &
|
|
28
|
+
ContextExtend;
|
|
29
|
+
|
|
30
|
+
export type AuthContext = {
|
|
31
|
+
user: PassportUser | null;
|
|
32
|
+
passport: {
|
|
33
|
+
login: (user: PassportUser) => Promise<void>;
|
|
34
|
+
logout: () => void;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
28
37
|
|
|
29
38
|
export type UploadContext = {
|
|
30
39
|
file?: FileStorage;
|
package/src/api/sonamu.ts
CHANGED
|
@@ -29,8 +29,9 @@ import { findApiRootPath } from "../utils/utils";
|
|
|
29
29
|
import { humanizeZodError } from "../utils/zod-error";
|
|
30
30
|
import { fastifyCaster } from "./caster";
|
|
31
31
|
import { getZodObjectFromApi } from "./code-converters";
|
|
32
|
-
import type { Context, UploadContext } from "./context";
|
|
32
|
+
import type { AuthContext, Context, UploadContext } from "./context";
|
|
33
33
|
import type { ExtendedApi } from "./decorators";
|
|
34
|
+
import fastifyPassport from "@fastify/passport";
|
|
34
35
|
|
|
35
36
|
export type SonamuConfig = {
|
|
36
37
|
projectName?: string;
|
|
@@ -240,6 +241,16 @@ class SonamuClass {
|
|
|
240
241
|
this.registerPlugins(server, options.plugins);
|
|
241
242
|
}
|
|
242
243
|
|
|
244
|
+
if (options.auth) {
|
|
245
|
+
if (!options.plugins?.session) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
"Auth requires session plugin. Please add plugins.session configuration."
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
this.registerAuth(server, options.auth);
|
|
252
|
+
}
|
|
253
|
+
|
|
243
254
|
// API 라우팅 설정
|
|
244
255
|
await this.withFastify(server, options.apiConfig, {
|
|
245
256
|
enableSync: initOptions?.enableSync,
|
|
@@ -401,7 +412,6 @@ class SonamuClass {
|
|
|
401
412
|
reply
|
|
402
413
|
);
|
|
403
414
|
|
|
404
|
-
// 결과 (AsyncLocalStorage 적용)
|
|
405
415
|
const context: Context = {
|
|
406
416
|
...config.contextProvider(
|
|
407
417
|
{
|
|
@@ -409,6 +419,17 @@ class SonamuClass {
|
|
|
409
419
|
reply,
|
|
410
420
|
headers: request.headers,
|
|
411
421
|
createSSE,
|
|
422
|
+
|
|
423
|
+
// auth
|
|
424
|
+
user: request.user ?? null,
|
|
425
|
+
passport: {
|
|
426
|
+
login: request.login.bind(
|
|
427
|
+
request
|
|
428
|
+
) as AuthContext["passport"]["login"],
|
|
429
|
+
logout: request.logout.bind(
|
|
430
|
+
request
|
|
431
|
+
) as AuthContext["passport"]["logout"],
|
|
432
|
+
},
|
|
412
433
|
},
|
|
413
434
|
request,
|
|
414
435
|
reply
|
|
@@ -490,6 +511,8 @@ class SonamuClass {
|
|
|
490
511
|
multipart: "@fastify/multipart",
|
|
491
512
|
qs: "fastify-qs",
|
|
492
513
|
sse: "fastify-sse-v2",
|
|
514
|
+
static: "@fastify/static",
|
|
515
|
+
session: "@fastify/secure-session",
|
|
493
516
|
} as const;
|
|
494
517
|
|
|
495
518
|
const registerPlugin = <K extends keyof NonNullable<typeof plugins>>(
|
|
@@ -515,6 +538,24 @@ class SonamuClass {
|
|
|
515
538
|
}
|
|
516
539
|
}
|
|
517
540
|
|
|
541
|
+
private async registerAuth(
|
|
542
|
+
server: FastifyInstance,
|
|
543
|
+
options: NonNullable<SonamuServerOptions["auth"]>
|
|
544
|
+
) {
|
|
545
|
+
server.register(fastifyPassport.initialize());
|
|
546
|
+
server.register(fastifyPassport.secureSession());
|
|
547
|
+
|
|
548
|
+
if (typeof options === "boolean") {
|
|
549
|
+
fastifyPassport.registerUserSerializer(async (user, _request) => user);
|
|
550
|
+
fastifyPassport.registerUserDeserializer(
|
|
551
|
+
async (serialized, _request) => serialized
|
|
552
|
+
);
|
|
553
|
+
} else {
|
|
554
|
+
fastifyPassport.registerUserSerializer(options.userSerializer);
|
|
555
|
+
fastifyPassport.registerUserDeserializer(options.userDeserializer);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
518
559
|
private async boot(server: FastifyInstance, options: SonamuServerOptions) {
|
|
519
560
|
const port = options.listen?.port ?? 3000;
|
|
520
561
|
const host = options.listen?.host ?? "localhost";
|
package/src/types/types.ts
CHANGED
|
@@ -9,10 +9,16 @@ import type {
|
|
|
9
9
|
} from "fastify";
|
|
10
10
|
import type { QsPluginOptions } from "fastify-qs";
|
|
11
11
|
import { z } from "zod";
|
|
12
|
-
import type {
|
|
12
|
+
import type { ApiDecoratorOptions, AuthContext, Context } from "../api";
|
|
13
13
|
import type { FastifyMultipartOptions } from "@fastify/multipart";
|
|
14
14
|
import type { Driver } from "../file-storage/driver";
|
|
15
15
|
import type { SsePluginOptions } from "fastify-sse-v2/lib/types";
|
|
16
|
+
import type { FastifyStaticOptions } from "@fastify/static";
|
|
17
|
+
import { SecureSessionPluginOptions } from "@fastify/secure-session";
|
|
18
|
+
import {
|
|
19
|
+
DeserializeFunction,
|
|
20
|
+
SerializeFunction,
|
|
21
|
+
} from "@fastify/passport/dist/Authenticator";
|
|
16
22
|
|
|
17
23
|
/*
|
|
18
24
|
Enums
|
|
@@ -837,7 +843,8 @@ export type SonamuFastifyConfig = {
|
|
|
837
843
|
defaultContext: Pick<
|
|
838
844
|
Context,
|
|
839
845
|
"request" | "reply" | "headers" | "createSSE"
|
|
840
|
-
|
|
846
|
+
> &
|
|
847
|
+
AuthContext,
|
|
841
848
|
request: FastifyRequest,
|
|
842
849
|
reply: FastifyReply
|
|
843
850
|
) => Context;
|
|
@@ -888,10 +895,19 @@ export type SonamuServerOptions = {
|
|
|
888
895
|
multipart?: boolean | FastifyMultipartOptions;
|
|
889
896
|
qs?: boolean | QsPluginOptions;
|
|
890
897
|
sse?: boolean | SsePluginOptions;
|
|
898
|
+
static?: boolean | FastifyStaticOptions;
|
|
899
|
+
session?: boolean | SecureSessionPluginOptions;
|
|
891
900
|
|
|
892
901
|
custom?: (server: FastifyInstance) => void;
|
|
893
902
|
};
|
|
894
903
|
|
|
904
|
+
auth?:
|
|
905
|
+
| boolean
|
|
906
|
+
| {
|
|
907
|
+
userSerializer: SerializeFunction<unknown, unknown>;
|
|
908
|
+
userDeserializer: DeserializeFunction<unknown, unknown>;
|
|
909
|
+
};
|
|
910
|
+
|
|
895
911
|
apiConfig: SonamuFastifyConfig;
|
|
896
912
|
|
|
897
913
|
storage?: Driver;
|