From 7a04ca93539bd771d6dc1d0d003f6363046bc6a5 Mon Sep 17 00:00:00 2001 From: Tylor Steinberger Date: Wed, 19 Jun 2024 10:05:14 -0400 Subject: [PATCH] chore: update realworld to latest --- .../api/common/infrastructure/Passwords.ts | 8 ++-- .../src/api/common/infrastructure/errors.ts | 2 +- .../infrastructure/migrations/001_initial.ts | 4 +- examples/realworld/src/model/Article.ts | 23 ++++++--- examples/realworld/src/model/Comment.ts | 12 +++-- examples/realworld/src/model/Password.ts | 10 ++-- examples/realworld/src/model/Profile.ts | 2 +- examples/realworld/src/model/User.ts | 17 ++++--- .../realworld/src/services/CreateArticle.ts | 2 +- .../realworld/src/services/CreateComment.ts | 2 +- .../realworld/src/services/DeleteArticle.ts | 4 +- .../realworld/src/services/DeleteComment.ts | 4 +- examples/realworld/src/services/GetArticle.ts | 4 +- .../realworld/src/services/GetArticles.ts | 4 +- examples/realworld/src/services/GetFeed.ts | 4 +- examples/realworld/src/services/Login.ts | 5 +- examples/realworld/src/services/Register.ts | 2 +- .../realworld/src/services/UpdateArticle.ts | 4 +- examples/realworld/src/services/UpdateUser.ts | 2 +- packages/server/src/RouterBuilder.ts | 47 ++++++++++++------- 20 files changed, 96 insertions(+), 66 deletions(-) diff --git a/examples/realworld/src/api/common/infrastructure/Passwords.ts b/examples/realworld/src/api/common/infrastructure/Passwords.ts index 3d8afa3c8..c3e7eb147 100644 --- a/examples/realworld/src/api/common/infrastructure/Passwords.ts +++ b/examples/realworld/src/api/common/infrastructure/Passwords.ts @@ -1,15 +1,15 @@ import { Context } from "@typed/core" import type { Password } from "@typed/realworld/model" import { PasswordHash } from "@typed/realworld/model" -import { Effect, Secret } from "effect" +import { Effect, Redacted } from "effect" export const HashPassword = Context.Fn<(password: Password) => Effect.Effect>()("PasswordHash") export const HashPasswordLive = HashPassword.implement((password) => Effect.gen(function*(_) { const bcrypt = yield* _(Effect.promise(() => import("bcrypt"))) - const hash = yield* _(Effect.promise(() => bcrypt.hash(Secret.value(password), 10))) - return PasswordHash.make(Secret.fromString(hash)) + const hash = yield* _(Effect.promise(() => bcrypt.hash(Redacted.value(password), 10))) + return PasswordHash.make(Redacted.make(hash)) }) ) @@ -20,6 +20,6 @@ export const ComparePassword = Context.Fn<(password: Password, hash: PasswordHas export const ComparePasswordLive = ComparePassword.implement((password, hash) => Effect.gen(function*(_) { const bcrypt = yield* _(Effect.promise(() => import("bcrypt"))) - return yield* _(Effect.promise(() => bcrypt.compare(Secret.value(password), Secret.value(hash)))) + return yield* _(Effect.promise(() => bcrypt.compare(Redacted.value(password), Redacted.value(hash)))) }) ) diff --git a/examples/realworld/src/api/common/infrastructure/errors.ts b/examples/realworld/src/api/common/infrastructure/errors.ts index 7dab3db87..156dd5c57 100644 --- a/examples/realworld/src/api/common/infrastructure/errors.ts +++ b/examples/realworld/src/api/common/infrastructure/errors.ts @@ -24,7 +24,7 @@ export function handleExpectedErrors< return new Unprocessable({ errors: [(error as Sql.error.SqlError).message] }) case "ParseError": case "SchemaError": - return new Unprocessable({ errors: [TreeFormatter.formatIssueSync((error as ParseError).error)] }) + return new Unprocessable({ errors: [TreeFormatter.formatIssueSync((error as ParseError).issue)] }) case "And": case "InvalidData": case "MissingData": diff --git a/examples/realworld/src/api/common/infrastructure/migrations/001_initial.ts b/examples/realworld/src/api/common/infrastructure/migrations/001_initial.ts index e744a0e21..9bb537a2a 100644 --- a/examples/realworld/src/api/common/infrastructure/migrations/001_initial.ts +++ b/examples/realworld/src/api/common/infrastructure/migrations/001_initial.ts @@ -1,8 +1,8 @@ -import * as Pg from "@effect/sql-pg" +import * as Sql from "@effect/sql" import * as Effect from "effect/Effect" export default Effect.flatMap( - Pg.client.PgClient, + Sql.client.Client, (sql) => sql` -- Users diff --git a/examples/realworld/src/model/Article.ts b/examples/realworld/src/model/Article.ts index ccef0e899..d6e777f62 100644 --- a/examples/realworld/src/model/Article.ts +++ b/examples/realworld/src/model/Article.ts @@ -3,28 +3,37 @@ import { Profile } from "@typed/realworld/model/Profile" export const ArticleId = Schema.nanoId.pipe( Schema.brand("ArticleId"), - Schema.description("Unique identifier for the Article") + Schema.annotations({ description: "Unique identifier for the Article" }) ) export const ArticleSlug = Schema.String.pipe( Schema.brand("ArticleSlug"), - Schema.description("Slug for Article generated from Article.title") + Schema.annotations({ description: "Slug for Article generated from Article.title" }) ) export type ArticleSlug = Schema.Schema.Type -export const ArticleTitle = Schema.String.pipe(Schema.brand("ArticleTitle"), Schema.description("Title of the Article")) +export const ArticleTitle = Schema.String.pipe( + Schema.brand("ArticleTitle"), + Schema.annotations({ description: "Title of the Article" }) +) export type ArticleTitle = Schema.Schema.Type export const ArticleDescription = Schema.String.pipe( Schema.brand("ArticleDescription"), - Schema.description("Description of the Article") + Schema.annotations({ description: "Description of the Article" }) ) export type ArticleDescription = Schema.Schema.Type -export const ArticleBody = Schema.String.pipe(Schema.brand("ArticleBody"), Schema.description("Content of the Article")) +export const ArticleBody = Schema.String.pipe( + Schema.brand("ArticleBody"), + Schema.annotations({ description: "Content of the Article" }) +) export type ArticleBody = Schema.Schema.Type -export const ArticleTag = Schema.String.pipe(Schema.brand("ArticleTag"), Schema.description("Tag for the Article")) +export const ArticleTag = Schema.String.pipe( + Schema.brand("ArticleTag"), + Schema.annotations({ description: "Tag for the Article" }) +) export type ArticleTag = Schema.Schema.Type export const ArticleTagList = Schema.Array(ArticleTag) @@ -43,7 +52,7 @@ export const Article = Schema.Struct({ createdAt: Schema.Date, updatedAt: Schema.Date }).pipe( - Schema.identifier("Article") + Schema.annotations({ identifier: "Article" }) ) export interface Article extends Schema.Schema.Type {} diff --git a/examples/realworld/src/model/Comment.ts b/examples/realworld/src/model/Comment.ts index 4468b6018..5c24c2582 100644 --- a/examples/realworld/src/model/Comment.ts +++ b/examples/realworld/src/model/Comment.ts @@ -1,10 +1,16 @@ import * as Schema from "@typed/realworld/lib/Schema" import { Profile } from "./Profile" -export const CommentId = Schema.nanoId.pipe(Schema.brand("CommentId"), Schema.description("Nano ID for Comment")) +export const CommentId = Schema.nanoId.pipe( + Schema.brand("CommentId"), + Schema.annotations({ description: "Nano ID for Comment" }) +) export type CommentId = Schema.Schema.Type -export const CommentBody = Schema.String.pipe(Schema.brand("CommentBody"), Schema.description("Comment Body")) +export const CommentBody = Schema.String.pipe( + Schema.brand("CommentBody"), + Schema.annotations({ description: "Comment Body" }) +) export type CommentBody = Schema.Schema.Type export const Comment = Schema.Struct({ @@ -13,6 +19,6 @@ export const Comment = Schema.Struct({ author: Profile, createdAt: Schema.Date, updatedAt: Schema.Date -}).pipe(Schema.identifier("Comment")) +}).pipe(Schema.annotations({ identifier: "Comment" })) export interface Comment extends Schema.Schema.Type {} diff --git a/examples/realworld/src/model/Password.ts b/examples/realworld/src/model/Password.ts index 7b3a1dce1..d6c8e67f4 100644 --- a/examples/realworld/src/model/Password.ts +++ b/examples/realworld/src/model/Password.ts @@ -1,15 +1,13 @@ import * as Schema from "@typed/realworld/lib/Schema" -export const Password = Schema.Secret.pipe( +export const Password = Schema.Redacted(Schema.String).pipe( Schema.brand("Password"), - Schema.identifier("Password"), - Schema.description("Password") + Schema.annotations({ identifier: "Password", description: "Password" }) ) export type Password = Schema.Schema.Type -export const PasswordHash = Schema.Secret.pipe( +export const PasswordHash = Schema.Redacted(Schema.String).pipe( Schema.brand("PasswordHash"), - Schema.identifier("PasswordHash"), - Schema.description("PasswordHash") + Schema.annotations({ identifier: "PasswordHash", description: "PasswordHash" }) ) export type PasswordHash = Schema.Schema.Type diff --git a/examples/realworld/src/model/Profile.ts b/examples/realworld/src/model/Profile.ts index 64d4a0d7e..ce8098272 100644 --- a/examples/realworld/src/model/Profile.ts +++ b/examples/realworld/src/model/Profile.ts @@ -4,7 +4,7 @@ import { User } from "./User" export const Profile = User.pipe( Schema.omit("id", "token"), Schema.extend(Schema.Struct({ following: Schema.Boolean })), - Schema.identifier("Profile") + Schema.annotations({ identifier: "Profile" }) ) export interface Profile extends Schema.Schema.Type {} diff --git a/examples/realworld/src/model/User.ts b/examples/realworld/src/model/User.ts index edd7aee0c..e4b4c359d 100644 --- a/examples/realworld/src/model/User.ts +++ b/examples/realworld/src/model/User.ts @@ -1,21 +1,24 @@ import * as Schema from "@typed/realworld/lib/Schema" -export const UserId = Schema.nanoId.pipe(Schema.brand("UserId"), Schema.description("Nano ID for User")) +export const UserId = Schema.nanoId.pipe( + Schema.brand("UserId"), + Schema.annotations({ description: "Nano ID for User" }) +) export type UserId = Schema.Schema.Type -export const Email = Schema.String.pipe(Schema.brand("Email"), Schema.description("Email Address")) +export const Email = Schema.String.pipe(Schema.brand("Email"), Schema.annotations({ description: "Email Address" })) export type Email = Schema.Schema.Type -export const Username = Schema.String.pipe(Schema.brand("Username"), Schema.description("Username")) +export const Username = Schema.String.pipe(Schema.brand("Username"), Schema.annotations({ description: "Username" })) export type Username = Schema.Schema.Type -export const Bio = Schema.String.pipe(Schema.brand("Bio"), Schema.description("Biography")) +export const Bio = Schema.String.pipe(Schema.brand("Bio"), Schema.annotations({ description: "Biography" })) export type Bio = Schema.Schema.Type -export const Image = Schema.String.pipe(Schema.brand("Image"), Schema.description("Image URL")) +export const Image = Schema.String.pipe(Schema.brand("Image"), Schema.annotations({ description: "Image URL" })) export type Image = Schema.Schema.Type -export const JwtToken = Schema.String.pipe(Schema.brand("JwtToken"), Schema.description("JWT Token")) +export const JwtToken = Schema.String.pipe(Schema.brand("JwtToken"), Schema.annotations({ description: "JWT Token" })) export type JwtToken = Schema.Schema.Type export const User = Schema.Struct({ @@ -25,6 +28,6 @@ export const User = Schema.Struct({ token: JwtToken, bio: Schema.OptionFromNullishOr(Bio, null), image: Schema.OptionFromNullishOr(Image, null) -}).pipe(Schema.identifier("User")) +}).pipe(Schema.annotations({ identifier: "User" })) export interface User extends Schema.Schema.Type {} diff --git a/examples/realworld/src/services/CreateArticle.ts b/examples/realworld/src/services/CreateArticle.ts index 1064ac2f6..331e0263b 100644 --- a/examples/realworld/src/services/CreateArticle.ts +++ b/examples/realworld/src/services/CreateArticle.ts @@ -6,7 +6,7 @@ import type { Effect } from "effect" export const CreateArticleInput = Article.pipe( Schema.pick("title", "description", "body", "tagList"), - Schema.identifier("CreateArticleInput") + Schema.annotations({ identifier: "CreateArticleInput" }) ) export type CreateArticleInput = Schema.Schema.Type diff --git a/examples/realworld/src/services/CreateComment.ts b/examples/realworld/src/services/CreateComment.ts index d0f0a1ccd..02b450dc6 100644 --- a/examples/realworld/src/services/CreateComment.ts +++ b/examples/realworld/src/services/CreateComment.ts @@ -7,7 +7,7 @@ import type { Effect } from "effect" export const CreateCommentInput = Comment.pipe( Schema.omit("id", "author", "createdAt", "updatedAt"), - Schema.identifier("CreateCommentInput") + Schema.annotations({ identifier: "CreateCommentInput" }) ) export type CreateCommentInput = Schema.Schema.Type diff --git a/examples/realworld/src/services/DeleteArticle.ts b/examples/realworld/src/services/DeleteArticle.ts index 947291c00..56ae0f6a5 100644 --- a/examples/realworld/src/services/DeleteArticle.ts +++ b/examples/realworld/src/services/DeleteArticle.ts @@ -5,7 +5,9 @@ import { ArticleSlug } from "@typed/realworld/model" import type { Unauthorized, Unprocessable } from "@typed/realworld/services/errors" import type { Effect } from "effect" -export const DeleteArticleInput = Schema.Struct({ slug: ArticleSlug }).pipe(Schema.identifier("DeleteArticleInput")) +export const DeleteArticleInput = Schema.Struct({ slug: ArticleSlug }).pipe( + Schema.annotations({ identifier: "DeleteArticleInput" }) +) export type DeleteArticleInput = Schema.Schema.Type export type DeleteArticleError = Unauthorized | Unprocessable diff --git a/examples/realworld/src/services/DeleteComment.ts b/examples/realworld/src/services/DeleteComment.ts index dbab55d5c..7b135629f 100644 --- a/examples/realworld/src/services/DeleteComment.ts +++ b/examples/realworld/src/services/DeleteComment.ts @@ -5,7 +5,9 @@ import { CommentId } from "@typed/realworld/model" import type { Unauthorized, Unprocessable } from "@typed/realworld/services/errors" import type { Effect } from "effect" -export const DeleteCommentInput = Schema.Struct({ id: CommentId }).pipe(Schema.identifier("DeleteCommentInput")) +export const DeleteCommentInput = Schema.Struct({ id: CommentId }).pipe( + Schema.annotations({ identifier: "DeleteCommentInput" }) +) export type DeleteCommentInput = Schema.Schema.Type export type DeleteCommentError = Unauthorized | Unprocessable diff --git a/examples/realworld/src/services/GetArticle.ts b/examples/realworld/src/services/GetArticle.ts index a99b6e0a6..dc3d40f08 100644 --- a/examples/realworld/src/services/GetArticle.ts +++ b/examples/realworld/src/services/GetArticle.ts @@ -5,7 +5,9 @@ import { ArticleSlug } from "@typed/realworld/model" import type { Unprocessable } from "@typed/realworld/services/errors" import type { Effect } from "effect" -export const GetArticleInput = Schema.Struct({ slug: ArticleSlug }).pipe(Schema.identifier("GetArticleInput")) +export const GetArticleInput = Schema.Struct({ slug: ArticleSlug }).pipe( + Schema.annotations({ identifier: "GetArticleInput" }) +) export type GetArticleInput = Schema.Schema.Type export const GetArticle = Fn<(input: GetArticleInput) => Effect.Effect>()("GetArticle") diff --git a/examples/realworld/src/services/GetArticles.ts b/examples/realworld/src/services/GetArticles.ts index 92be10e11..09f5e1d24 100644 --- a/examples/realworld/src/services/GetArticles.ts +++ b/examples/realworld/src/services/GetArticles.ts @@ -11,9 +11,7 @@ export const GetArticlesInput = Schema.Struct({ favorited: Schema.optionalOrNull(Username), limit: Schema.optionalOrNull(Schema.NumberFromString), offset: Schema.optionalOrNull(Schema.NumberFromString) -}).pipe( - Schema.identifier("GetArticlesInput") -) +}).annotations({ identifier: "GetArticlesInput" }) export type GetArticlesInput = Schema.Schema.Type diff --git a/examples/realworld/src/services/GetFeed.ts b/examples/realworld/src/services/GetFeed.ts index d2926013e..1cf1c22af 100644 --- a/examples/realworld/src/services/GetFeed.ts +++ b/examples/realworld/src/services/GetFeed.ts @@ -7,9 +7,7 @@ import type { Effect } from "effect" export const GetFeedInput = Schema.Struct({ limit: Schema.optional(Schema.NumberFromString, { exact: true, as: "Option" }), offset: Schema.optional(Schema.NumberFromString, { exact: true, as: "Option" }) -}).pipe( - Schema.identifier("GetFeedInput") -) +}).annotations({ identifier: "GetFeedInput" }) export type GetFeedInput = Schema.Schema.Type export type GetFeedError = Unprocessable | Unauthorized diff --git a/examples/realworld/src/services/Login.ts b/examples/realworld/src/services/Login.ts index 9f5bb352d..9aaa800ea 100644 --- a/examples/realworld/src/services/Login.ts +++ b/examples/realworld/src/services/Login.ts @@ -8,9 +8,8 @@ import type { Effect } from "effect/Effect" export const LoginInput = Schema.Struct({ email: Email, password: Password -}).pipe( - Schema.identifier("LoginInput") -) +}).annotations({ identifier: "LoginInput" }) + export type LoginInput = Schema.Schema.Type export type LoginError = Unauthorized | Unprocessable diff --git a/examples/realworld/src/services/Register.ts b/examples/realworld/src/services/Register.ts index 049a06012..cbfc109c0 100644 --- a/examples/realworld/src/services/Register.ts +++ b/examples/realworld/src/services/Register.ts @@ -9,7 +9,7 @@ export const RegisterInput = Schema.Struct({ email: Email, username: Username, password: Password -}).pipe(Schema.identifier("RegisterInput")) +}).annotations({ identifier: "RegisterInput" }) export type RegisterInput = Schema.Schema.Type export type RegisterError = Unprocessable diff --git a/examples/realworld/src/services/UpdateArticle.ts b/examples/realworld/src/services/UpdateArticle.ts index 4a0b9b3eb..a48782c4c 100644 --- a/examples/realworld/src/services/UpdateArticle.ts +++ b/examples/realworld/src/services/UpdateArticle.ts @@ -5,9 +5,7 @@ import { CreateArticleInput } from "@typed/realworld/services/CreateArticle" import type { Unauthorized, Unprocessable } from "@typed/realworld/services/errors" import type { Effect } from "effect" -export const UpdateArticleInput = Schema.partial(CreateArticleInput).pipe( - Schema.identifier("UpdateArticleInput") -) +export const UpdateArticleInput = Schema.partial(CreateArticleInput).annotations({ identifier: "UpdateArticleInput" }) export type UpdateArticleInput = Schema.Schema.Type export type UpdateArticleError = Unauthorized | Unprocessable diff --git a/examples/realworld/src/services/UpdateUser.ts b/examples/realworld/src/services/UpdateUser.ts index 8d60a0ff8..0e07bc9ed 100644 --- a/examples/realworld/src/services/UpdateUser.ts +++ b/examples/realworld/src/services/UpdateUser.ts @@ -10,7 +10,7 @@ export const UpdateUserInput = Schema.Struct({ password: Schema.OptionFromNullishOr(Password, null), image: User.fields.image, bio: User.fields.bio -}).pipe(Schema.identifier("UpdateUserInput")) +}).annotations({ identifier: "UpdateUserInput" }) export type UpdateUserInput = Schema.Schema.Type export type UpdateUserError = Unauthorized | Unprocessable diff --git a/packages/server/src/RouterBuilder.ts b/packages/server/src/RouterBuilder.ts index a9a2b4635..0bc3c0cd8 100644 --- a/packages/server/src/RouterBuilder.ts +++ b/packages/server/src/RouterBuilder.ts @@ -3,7 +3,7 @@ */ import type { Default } from "@effect/platform/Http/App" import type { PathInput } from "@effect/platform/Http/Router" -import { ServerRequest } from "@effect/platform/Http/ServerRequest" +import { ParsedSearchParams, ServerRequest } from "@effect/platform/Http/ServerRequest" import type { Schema } from "@effect/schema" import * as Route from "@typed/route" import type { CurrentRoute } from "@typed/router" @@ -100,7 +100,7 @@ export const handle: { R2 >( id: Id, - handler: Handler.Handler.Function, R2, E2> + handler: Handler.Handler.Function, E2, R2> ): (builder: RouterBuilder) => RouterBuilder< | E | RouteHandler.RouteHandler.Error< @@ -123,7 +123,7 @@ export const handle: { >( builder: RouterBuilder, id: Id, - handler: Handler.Handler.Function, R2, E2> + handler: Handler.Handler.Function, E2, R2> ): RouterBuilder< | E | RouteHandler.RouteHandler.Error< @@ -212,13 +212,16 @@ const makeHandlerFromEndpoint: { - const { security, ...restInput } = input - return fn(restInput, security) - } - )) + const response = yield* _( + Effect.flatMap( + requestParser.parseRequest({ params, queryParams }), + (input: any) => { + const { security, ...restInput } = input + return fn(restInput, security) + } + ), + Effect.provideService(ParsedSearchParams, ParsedSearchParams.of(urlSearchParamsToRecord(queryParams))) + ) return yield* responseEncoder.encodeResponse(request, response) }) @@ -284,8 +287,8 @@ export const buildPartial = ( - handler: Handler.Handler.Function + ( + handler: Handler.Handler.Function ): (endpoint: E) => ( builder: RouterBuilder ) => RouterBuilder< @@ -302,9 +305,9 @@ export const fromEndpoint: { Exclude > - ( + ( endpoint: E, - handler: Handler.Handler.Function + handler: Handler.Handler.Function ): ( builder: RouterBuilder ) => RouterBuilder< @@ -320,9 +323,9 @@ export const fromEndpoint: { >, Exclude > -} = dual(2, ( +} = dual(2, ( endpoint: E, - handler: Handler.Handler.Function + handler: Handler.Handler.Function ) => ( builder: RouterBuilder @@ -339,3 +342,15 @@ export const fromEndpoint: { >, Exclude > => handle(builder, ApiEndpoint.getId(endpoint) as ApiEndpoint.ApiEndpoint.Id, handler as any) as any) + +function urlSearchParamsToRecord(searchParams: URLSearchParams) { + const result: Record> = {} + + for (const key of searchParams.keys()) { + const values = searchParams.getAll(key) + + result[key] = values.length === 1 ? values[0] : values + } + + return result +}